|
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
|
2 * vim: set ts=8 sts=4 et sw=4 tw=99: |
|
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 /* JavaScript iterators. */ |
|
8 |
|
9 #include "jsiter.h" |
|
10 |
|
11 #include "mozilla/ArrayUtils.h" |
|
12 #include "mozilla/MemoryReporting.h" |
|
13 #include "mozilla/PodOperations.h" |
|
14 |
|
15 #include "jsarray.h" |
|
16 #include "jsatom.h" |
|
17 #include "jscntxt.h" |
|
18 #include "jsgc.h" |
|
19 #include "jsobj.h" |
|
20 #include "jsopcode.h" |
|
21 #include "jsproxy.h" |
|
22 #include "jsscript.h" |
|
23 #include "jstypes.h" |
|
24 #include "jsutil.h" |
|
25 |
|
26 #include "ds/Sort.h" |
|
27 #include "gc/Marking.h" |
|
28 #include "vm/GeneratorObject.h" |
|
29 #include "vm/GlobalObject.h" |
|
30 #include "vm/Interpreter.h" |
|
31 #include "vm/Shape.h" |
|
32 #include "vm/StopIterationObject.h" |
|
33 |
|
34 #include "jsinferinlines.h" |
|
35 #include "jsobjinlines.h" |
|
36 #include "jsscriptinlines.h" |
|
37 |
|
38 #include "vm/Stack-inl.h" |
|
39 #include "vm/String-inl.h" |
|
40 |
|
41 using namespace js; |
|
42 using namespace js::gc; |
|
43 using JS::ForOfIterator; |
|
44 |
|
45 using mozilla::ArrayLength; |
|
46 #ifdef JS_MORE_DETERMINISTIC |
|
47 using mozilla::PodCopy; |
|
48 #endif |
|
49 using mozilla::PodZero; |
|
50 |
|
51 typedef Rooted<PropertyIteratorObject*> RootedPropertyIteratorObject; |
|
52 |
|
53 static const gc::AllocKind ITERATOR_FINALIZE_KIND = gc::FINALIZE_OBJECT2_BACKGROUND; |
|
54 |
|
55 void |
|
56 NativeIterator::mark(JSTracer *trc) |
|
57 { |
|
58 for (HeapPtr<JSFlatString> *str = begin(); str < end(); str++) |
|
59 MarkString(trc, str, "prop"); |
|
60 if (obj) |
|
61 MarkObject(trc, &obj, "obj"); |
|
62 |
|
63 // The SuppressDeletedPropertyHelper loop can GC, so make sure that if the |
|
64 // GC removes any elements from the list, it won't remove this one. |
|
65 if (iterObj_) |
|
66 MarkObjectUnbarriered(trc, &iterObj_, "iterObj"); |
|
67 } |
|
68 |
|
69 struct IdHashPolicy { |
|
70 typedef jsid Lookup; |
|
71 static HashNumber hash(jsid id) { |
|
72 return JSID_BITS(id); |
|
73 } |
|
74 static bool match(jsid id1, jsid id2) { |
|
75 return id1 == id2; |
|
76 } |
|
77 }; |
|
78 |
|
79 typedef HashSet<jsid, IdHashPolicy> IdSet; |
|
80 |
|
81 static inline bool |
|
82 NewKeyValuePair(JSContext *cx, jsid id, const Value &val, MutableHandleValue rval) |
|
83 { |
|
84 JS::AutoValueArray<2> vec(cx); |
|
85 vec[0].set(IdToValue(id)); |
|
86 vec[1].set(val); |
|
87 |
|
88 JSObject *aobj = NewDenseCopiedArray(cx, 2, vec.begin()); |
|
89 if (!aobj) |
|
90 return false; |
|
91 rval.setObject(*aobj); |
|
92 return true; |
|
93 } |
|
94 |
|
95 static inline bool |
|
96 Enumerate(JSContext *cx, HandleObject pobj, jsid id, |
|
97 bool enumerable, unsigned flags, IdSet& ht, AutoIdVector *props) |
|
98 { |
|
99 /* |
|
100 * We implement __proto__ using a property on |Object.prototype|, but |
|
101 * because __proto__ is highly deserving of removal, we don't want it to |
|
102 * show up in property enumeration, even if only for |Object.prototype| |
|
103 * (think introspection by Prototype-like frameworks that add methods to |
|
104 * the built-in prototypes). So exclude __proto__ if the object where the |
|
105 * property was found has no [[Prototype]] and might be |Object.prototype|. |
|
106 */ |
|
107 if (MOZ_UNLIKELY(!pobj->getTaggedProto().isObject() && JSID_IS_ATOM(id, cx->names().proto))) |
|
108 return true; |
|
109 |
|
110 if (!(flags & JSITER_OWNONLY) || pobj->is<ProxyObject>() || pobj->getOps()->enumerate) { |
|
111 /* If we've already seen this, we definitely won't add it. */ |
|
112 IdSet::AddPtr p = ht.lookupForAdd(id); |
|
113 if (MOZ_UNLIKELY(!!p)) |
|
114 return true; |
|
115 |
|
116 /* |
|
117 * It's not necessary to add properties to the hash table at the end of |
|
118 * the prototype chain, but custom enumeration behaviors might return |
|
119 * duplicated properties, so always add in such cases. |
|
120 */ |
|
121 if ((pobj->is<ProxyObject>() || pobj->getProto() || pobj->getOps()->enumerate) && !ht.add(p, id)) |
|
122 return false; |
|
123 } |
|
124 |
|
125 if (enumerable || (flags & JSITER_HIDDEN)) |
|
126 return props->append(id); |
|
127 |
|
128 return true; |
|
129 } |
|
130 |
|
131 static bool |
|
132 EnumerateNativeProperties(JSContext *cx, HandleObject pobj, unsigned flags, IdSet &ht, |
|
133 AutoIdVector *props) |
|
134 { |
|
135 /* Collect any dense elements from this object. */ |
|
136 size_t initlen = pobj->getDenseInitializedLength(); |
|
137 const Value *vp = pobj->getDenseElements(); |
|
138 for (size_t i = 0; i < initlen; ++i, ++vp) { |
|
139 if (!vp->isMagic(JS_ELEMENTS_HOLE)) { |
|
140 /* Dense arrays never get so large that i would not fit into an integer id. */ |
|
141 if (!Enumerate(cx, pobj, INT_TO_JSID(i), /* enumerable = */ true, flags, ht, props)) |
|
142 return false; |
|
143 } |
|
144 } |
|
145 |
|
146 /* Collect any typed array elements from this object. */ |
|
147 if (pobj->is<TypedArrayObject>()) { |
|
148 size_t len = pobj->as<TypedArrayObject>().length(); |
|
149 for (size_t i = 0; i < len; i++) { |
|
150 if (!Enumerate(cx, pobj, INT_TO_JSID(i), /* enumerable = */ true, flags, ht, props)) |
|
151 return false; |
|
152 } |
|
153 } |
|
154 |
|
155 size_t initialLength = props->length(); |
|
156 |
|
157 /* Collect all unique properties from this object's scope. */ |
|
158 Shape::Range<NoGC> r(pobj->lastProperty()); |
|
159 for (; !r.empty(); r.popFront()) { |
|
160 Shape &shape = r.front(); |
|
161 |
|
162 if (!Enumerate(cx, pobj, shape.propid(), shape.enumerable(), flags, ht, props)) |
|
163 return false; |
|
164 } |
|
165 |
|
166 ::Reverse(props->begin() + initialLength, props->end()); |
|
167 return true; |
|
168 } |
|
169 |
|
170 #ifdef JS_MORE_DETERMINISTIC |
|
171 |
|
172 struct SortComparatorIds |
|
173 { |
|
174 JSContext *const cx; |
|
175 |
|
176 SortComparatorIds(JSContext *cx) |
|
177 : cx(cx) {} |
|
178 |
|
179 bool operator()(jsid a, jsid b, bool *lessOrEqualp) |
|
180 { |
|
181 /* Pick an arbitrary total order on jsids that is stable across executions. */ |
|
182 RootedString astr(cx, IdToString(cx, a)); |
|
183 if (!astr) |
|
184 return false; |
|
185 RootedString bstr(cx, IdToString(cx, b)); |
|
186 if (!bstr) |
|
187 return false; |
|
188 |
|
189 int32_t result; |
|
190 if (!CompareStrings(cx, astr, bstr, &result)) |
|
191 return false; |
|
192 |
|
193 *lessOrEqualp = (result <= 0); |
|
194 return true; |
|
195 } |
|
196 }; |
|
197 |
|
198 #endif /* JS_MORE_DETERMINISTIC */ |
|
199 |
|
200 static bool |
|
201 Snapshot(JSContext *cx, JSObject *pobj_, unsigned flags, AutoIdVector *props) |
|
202 { |
|
203 IdSet ht(cx); |
|
204 if (!ht.init(32)) |
|
205 return false; |
|
206 |
|
207 RootedObject pobj(cx, pobj_); |
|
208 |
|
209 do { |
|
210 const Class *clasp = pobj->getClass(); |
|
211 if (pobj->isNative() && |
|
212 !pobj->getOps()->enumerate && |
|
213 !(clasp->flags & JSCLASS_NEW_ENUMERATE)) { |
|
214 if (!clasp->enumerate(cx, pobj)) |
|
215 return false; |
|
216 if (!EnumerateNativeProperties(cx, pobj, flags, ht, props)) |
|
217 return false; |
|
218 } else { |
|
219 if (pobj->is<ProxyObject>()) { |
|
220 AutoIdVector proxyProps(cx); |
|
221 if (flags & JSITER_OWNONLY) { |
|
222 if (flags & JSITER_HIDDEN) { |
|
223 if (!Proxy::getOwnPropertyNames(cx, pobj, proxyProps)) |
|
224 return false; |
|
225 } else { |
|
226 if (!Proxy::keys(cx, pobj, proxyProps)) |
|
227 return false; |
|
228 } |
|
229 } else { |
|
230 if (!Proxy::enumerate(cx, pobj, proxyProps)) |
|
231 return false; |
|
232 } |
|
233 for (size_t n = 0, len = proxyProps.length(); n < len; n++) { |
|
234 if (!Enumerate(cx, pobj, proxyProps[n], true, flags, ht, props)) |
|
235 return false; |
|
236 } |
|
237 /* Proxy objects enumerate the prototype on their own, so we are done here. */ |
|
238 break; |
|
239 } |
|
240 RootedValue state(cx); |
|
241 RootedId id(cx); |
|
242 JSIterateOp op = (flags & JSITER_HIDDEN) ? JSENUMERATE_INIT_ALL : JSENUMERATE_INIT; |
|
243 if (!JSObject::enumerate(cx, pobj, op, &state, &id)) |
|
244 return false; |
|
245 if (state.isMagic(JS_NATIVE_ENUMERATE)) { |
|
246 if (!EnumerateNativeProperties(cx, pobj, flags, ht, props)) |
|
247 return false; |
|
248 } else { |
|
249 while (true) { |
|
250 RootedId id(cx); |
|
251 if (!JSObject::enumerate(cx, pobj, JSENUMERATE_NEXT, &state, &id)) |
|
252 return false; |
|
253 if (state.isNull()) |
|
254 break; |
|
255 if (!Enumerate(cx, pobj, id, true, flags, ht, props)) |
|
256 return false; |
|
257 } |
|
258 } |
|
259 } |
|
260 |
|
261 if (flags & JSITER_OWNONLY) |
|
262 break; |
|
263 |
|
264 } while ((pobj = pobj->getProto()) != nullptr); |
|
265 |
|
266 #ifdef JS_MORE_DETERMINISTIC |
|
267 |
|
268 /* |
|
269 * In some cases the enumeration order for an object depends on the |
|
270 * execution mode (interpreter vs. JIT), especially for native objects |
|
271 * with a class enumerate hook (where resolving a property changes the |
|
272 * resulting enumeration order). These aren't really bugs, but the |
|
273 * differences can change the generated output and confuse correctness |
|
274 * fuzzers, so we sort the ids if such a fuzzer is running. |
|
275 * |
|
276 * We don't do this in the general case because (a) doing so is slow, |
|
277 * and (b) it also breaks the web, which expects enumeration order to |
|
278 * follow the order in which properties are added, in certain cases. |
|
279 * Since ECMA does not specify an enumeration order for objects, both |
|
280 * behaviors are technically correct to do. |
|
281 */ |
|
282 |
|
283 jsid *ids = props->begin(); |
|
284 size_t n = props->length(); |
|
285 |
|
286 AutoIdVector tmp(cx); |
|
287 if (!tmp.resize(n)) |
|
288 return false; |
|
289 PodCopy(tmp.begin(), ids, n); |
|
290 |
|
291 if (!MergeSort(ids, n, tmp.begin(), SortComparatorIds(cx))) |
|
292 return false; |
|
293 |
|
294 #endif /* JS_MORE_DETERMINISTIC */ |
|
295 |
|
296 return true; |
|
297 } |
|
298 |
|
299 bool |
|
300 js::VectorToIdArray(JSContext *cx, AutoIdVector &props, JSIdArray **idap) |
|
301 { |
|
302 JS_STATIC_ASSERT(sizeof(JSIdArray) > sizeof(jsid)); |
|
303 size_t len = props.length(); |
|
304 size_t idsz = len * sizeof(jsid); |
|
305 size_t sz = (sizeof(JSIdArray) - sizeof(jsid)) + idsz; |
|
306 JSIdArray *ida = static_cast<JSIdArray *>(cx->malloc_(sz)); |
|
307 if (!ida) |
|
308 return false; |
|
309 |
|
310 ida->length = static_cast<int>(len); |
|
311 jsid *v = props.begin(); |
|
312 for (int i = 0; i < ida->length; i++) |
|
313 ida->vector[i].init(v[i]); |
|
314 *idap = ida; |
|
315 return true; |
|
316 } |
|
317 |
|
318 JS_FRIEND_API(bool) |
|
319 js::GetPropertyNames(JSContext *cx, JSObject *obj, unsigned flags, AutoIdVector *props) |
|
320 { |
|
321 return Snapshot(cx, obj, flags & (JSITER_OWNONLY | JSITER_HIDDEN), props); |
|
322 } |
|
323 |
|
324 size_t sCustomIteratorCount = 0; |
|
325 |
|
326 static inline bool |
|
327 GetCustomIterator(JSContext *cx, HandleObject obj, unsigned flags, MutableHandleValue vp) |
|
328 { |
|
329 JS_CHECK_RECURSION(cx, return false); |
|
330 |
|
331 /* Check whether we have a valid __iterator__ method. */ |
|
332 HandlePropertyName name = cx->names().iteratorIntrinsic; |
|
333 if (!JSObject::getProperty(cx, obj, obj, name, vp)) |
|
334 return false; |
|
335 |
|
336 /* If there is no custom __iterator__ method, we are done here. */ |
|
337 if (!vp.isObject()) { |
|
338 vp.setUndefined(); |
|
339 return true; |
|
340 } |
|
341 |
|
342 if (!cx->runningWithTrustedPrincipals()) |
|
343 ++sCustomIteratorCount; |
|
344 |
|
345 /* Otherwise call it and return that object. */ |
|
346 Value arg = BooleanValue((flags & JSITER_FOREACH) == 0); |
|
347 if (!Invoke(cx, ObjectValue(*obj), vp, 1, &arg, vp)) |
|
348 return false; |
|
349 if (vp.isPrimitive()) { |
|
350 /* |
|
351 * We are always coming from js::ValueToIterator, and we are no longer on |
|
352 * trace, so the object we are iterating over is on top of the stack (-1). |
|
353 */ |
|
354 JSAutoByteString bytes; |
|
355 if (!AtomToPrintableString(cx, name, &bytes)) |
|
356 return false; |
|
357 RootedValue val(cx, ObjectValue(*obj)); |
|
358 js_ReportValueError2(cx, JSMSG_BAD_TRAP_RETURN_VALUE, |
|
359 -1, val, js::NullPtr(), bytes.ptr()); |
|
360 return false; |
|
361 } |
|
362 return true; |
|
363 } |
|
364 |
|
365 template <typename T> |
|
366 static inline bool |
|
367 Compare(T *a, T *b, size_t c) |
|
368 { |
|
369 size_t n = (c + size_t(7)) / size_t(8); |
|
370 switch (c % 8) { |
|
371 case 0: do { if (*a++ != *b++) return false; |
|
372 case 7: if (*a++ != *b++) return false; |
|
373 case 6: if (*a++ != *b++) return false; |
|
374 case 5: if (*a++ != *b++) return false; |
|
375 case 4: if (*a++ != *b++) return false; |
|
376 case 3: if (*a++ != *b++) return false; |
|
377 case 2: if (*a++ != *b++) return false; |
|
378 case 1: if (*a++ != *b++) return false; |
|
379 } while (--n > 0); |
|
380 } |
|
381 return true; |
|
382 } |
|
383 |
|
384 static inline PropertyIteratorObject * |
|
385 NewPropertyIteratorObject(JSContext *cx, unsigned flags) |
|
386 { |
|
387 if (flags & JSITER_ENUMERATE) { |
|
388 RootedTypeObject type(cx, cx->getNewType(&PropertyIteratorObject::class_, nullptr)); |
|
389 if (!type) |
|
390 return nullptr; |
|
391 |
|
392 JSObject *metadata = nullptr; |
|
393 if (!NewObjectMetadata(cx, &metadata)) |
|
394 return nullptr; |
|
395 |
|
396 const Class *clasp = &PropertyIteratorObject::class_; |
|
397 RootedShape shape(cx, EmptyShape::getInitialShape(cx, clasp, nullptr, nullptr, metadata, |
|
398 ITERATOR_FINALIZE_KIND)); |
|
399 if (!shape) |
|
400 return nullptr; |
|
401 |
|
402 JSObject *obj = JSObject::create(cx, ITERATOR_FINALIZE_KIND, |
|
403 GetInitialHeap(GenericObject, clasp), shape, type); |
|
404 if (!obj) |
|
405 return nullptr; |
|
406 |
|
407 JS_ASSERT(obj->numFixedSlots() == JSObject::ITER_CLASS_NFIXED_SLOTS); |
|
408 return &obj->as<PropertyIteratorObject>(); |
|
409 } |
|
410 |
|
411 JSObject *obj = NewBuiltinClassInstance(cx, &PropertyIteratorObject::class_); |
|
412 if (!obj) |
|
413 return nullptr; |
|
414 |
|
415 return &obj->as<PropertyIteratorObject>(); |
|
416 } |
|
417 |
|
418 NativeIterator * |
|
419 NativeIterator::allocateIterator(JSContext *cx, uint32_t slength, const AutoIdVector &props) |
|
420 { |
|
421 size_t plength = props.length(); |
|
422 NativeIterator *ni = (NativeIterator *) |
|
423 cx->malloc_(sizeof(NativeIterator) |
|
424 + plength * sizeof(JSString *) |
|
425 + slength * sizeof(Shape *)); |
|
426 if (!ni) |
|
427 return nullptr; |
|
428 AutoValueVector strings(cx); |
|
429 ni->props_array = ni->props_cursor = (HeapPtr<JSFlatString> *) (ni + 1); |
|
430 ni->props_end = ni->props_array + plength; |
|
431 if (plength) { |
|
432 for (size_t i = 0; i < plength; i++) { |
|
433 JSFlatString *str = IdToString(cx, props[i]); |
|
434 if (!str || !strings.append(StringValue(str))) |
|
435 return nullptr; |
|
436 ni->props_array[i].init(str); |
|
437 } |
|
438 } |
|
439 ni->next_ = nullptr; |
|
440 ni->prev_ = nullptr; |
|
441 return ni; |
|
442 } |
|
443 |
|
444 NativeIterator * |
|
445 NativeIterator::allocateSentinel(JSContext *cx) |
|
446 { |
|
447 NativeIterator *ni = (NativeIterator *)js_malloc(sizeof(NativeIterator)); |
|
448 if (!ni) |
|
449 return nullptr; |
|
450 |
|
451 PodZero(ni); |
|
452 |
|
453 ni->next_ = ni; |
|
454 ni->prev_ = ni; |
|
455 return ni; |
|
456 } |
|
457 |
|
458 inline void |
|
459 NativeIterator::init(JSObject *obj, JSObject *iterObj, unsigned flags, uint32_t slength, uint32_t key) |
|
460 { |
|
461 this->obj.init(obj); |
|
462 this->iterObj_ = iterObj; |
|
463 this->flags = flags; |
|
464 this->shapes_array = (Shape **) this->props_end; |
|
465 this->shapes_length = slength; |
|
466 this->shapes_key = key; |
|
467 } |
|
468 |
|
469 static inline void |
|
470 RegisterEnumerator(JSContext *cx, PropertyIteratorObject *iterobj, NativeIterator *ni) |
|
471 { |
|
472 /* Register non-escaping native enumerators (for-in) with the current context. */ |
|
473 if (ni->flags & JSITER_ENUMERATE) { |
|
474 ni->link(cx->compartment()->enumerators); |
|
475 |
|
476 JS_ASSERT(!(ni->flags & JSITER_ACTIVE)); |
|
477 ni->flags |= JSITER_ACTIVE; |
|
478 } |
|
479 } |
|
480 |
|
481 static inline bool |
|
482 VectorToKeyIterator(JSContext *cx, HandleObject obj, unsigned flags, AutoIdVector &keys, |
|
483 uint32_t slength, uint32_t key, MutableHandleValue vp) |
|
484 { |
|
485 JS_ASSERT(!(flags & JSITER_FOREACH)); |
|
486 |
|
487 if (obj) { |
|
488 if (obj->hasSingletonType() && !obj->setIteratedSingleton(cx)) |
|
489 return false; |
|
490 types::MarkTypeObjectFlags(cx, obj, types::OBJECT_FLAG_ITERATED); |
|
491 } |
|
492 |
|
493 Rooted<PropertyIteratorObject *> iterobj(cx, NewPropertyIteratorObject(cx, flags)); |
|
494 if (!iterobj) |
|
495 return false; |
|
496 |
|
497 NativeIterator *ni = NativeIterator::allocateIterator(cx, slength, keys); |
|
498 if (!ni) |
|
499 return false; |
|
500 ni->init(obj, iterobj, flags, slength, key); |
|
501 |
|
502 if (slength) { |
|
503 /* |
|
504 * Fill in the shape array from scratch. We can't use the array that was |
|
505 * computed for the cache lookup earlier, as constructing iterobj could |
|
506 * have triggered a shape-regenerating GC. Don't bother with regenerating |
|
507 * the shape key; if such a GC *does* occur, we can only get hits through |
|
508 * the one-slot lastNativeIterator cache. |
|
509 */ |
|
510 JSObject *pobj = obj; |
|
511 size_t ind = 0; |
|
512 do { |
|
513 ni->shapes_array[ind++] = pobj->lastProperty(); |
|
514 pobj = pobj->getProto(); |
|
515 } while (pobj); |
|
516 JS_ASSERT(ind == slength); |
|
517 } |
|
518 |
|
519 iterobj->setNativeIterator(ni); |
|
520 vp.setObject(*iterobj); |
|
521 |
|
522 RegisterEnumerator(cx, iterobj, ni); |
|
523 return true; |
|
524 } |
|
525 |
|
526 bool |
|
527 js::VectorToKeyIterator(JSContext *cx, HandleObject obj, unsigned flags, AutoIdVector &props, |
|
528 MutableHandleValue vp) |
|
529 { |
|
530 return VectorToKeyIterator(cx, obj, flags, props, 0, 0, vp); |
|
531 } |
|
532 |
|
533 bool |
|
534 js::VectorToValueIterator(JSContext *cx, HandleObject obj, unsigned flags, AutoIdVector &keys, |
|
535 MutableHandleValue vp) |
|
536 { |
|
537 JS_ASSERT(flags & JSITER_FOREACH); |
|
538 |
|
539 if (obj) { |
|
540 if (obj->hasSingletonType() && !obj->setIteratedSingleton(cx)) |
|
541 return false; |
|
542 types::MarkTypeObjectFlags(cx, obj, types::OBJECT_FLAG_ITERATED); |
|
543 } |
|
544 |
|
545 Rooted<PropertyIteratorObject*> iterobj(cx, NewPropertyIteratorObject(cx, flags)); |
|
546 if (!iterobj) |
|
547 return false; |
|
548 |
|
549 NativeIterator *ni = NativeIterator::allocateIterator(cx, 0, keys); |
|
550 if (!ni) |
|
551 return false; |
|
552 ni->init(obj, iterobj, flags, 0, 0); |
|
553 |
|
554 iterobj->setNativeIterator(ni); |
|
555 vp.setObject(*iterobj); |
|
556 |
|
557 RegisterEnumerator(cx, iterobj, ni); |
|
558 return true; |
|
559 } |
|
560 |
|
561 bool |
|
562 js::EnumeratedIdVectorToIterator(JSContext *cx, HandleObject obj, unsigned flags, |
|
563 AutoIdVector &props, MutableHandleValue vp) |
|
564 { |
|
565 if (!(flags & JSITER_FOREACH)) |
|
566 return VectorToKeyIterator(cx, obj, flags, props, vp); |
|
567 |
|
568 return VectorToValueIterator(cx, obj, flags, props, vp); |
|
569 } |
|
570 |
|
571 static inline void |
|
572 UpdateNativeIterator(NativeIterator *ni, JSObject *obj) |
|
573 { |
|
574 // Update the object for which the native iterator is associated, so |
|
575 // SuppressDeletedPropertyHelper will recognize the iterator as a match. |
|
576 ni->obj = obj; |
|
577 } |
|
578 |
|
579 bool |
|
580 js::GetIterator(JSContext *cx, HandleObject obj, unsigned flags, MutableHandleValue vp) |
|
581 { |
|
582 Vector<Shape *, 8> shapes(cx); |
|
583 uint32_t key = 0; |
|
584 |
|
585 bool keysOnly = (flags == JSITER_ENUMERATE); |
|
586 |
|
587 if (obj) { |
|
588 if (JSIteratorOp op = obj->getClass()->ext.iteratorObject) { |
|
589 JSObject *iterobj = op(cx, obj, !(flags & JSITER_FOREACH)); |
|
590 if (!iterobj) |
|
591 return false; |
|
592 vp.setObject(*iterobj); |
|
593 return true; |
|
594 } |
|
595 |
|
596 if (keysOnly) { |
|
597 /* |
|
598 * Check to see if this is the same as the most recent object which |
|
599 * was iterated over. We don't explicitly check for shapeless |
|
600 * objects here, as they are not inserted into the cache and |
|
601 * will result in a miss. |
|
602 */ |
|
603 PropertyIteratorObject *last = cx->runtime()->nativeIterCache.last; |
|
604 if (last) { |
|
605 NativeIterator *lastni = last->getNativeIterator(); |
|
606 if (!(lastni->flags & (JSITER_ACTIVE|JSITER_UNREUSABLE)) && |
|
607 obj->isNative() && |
|
608 obj->hasEmptyElements() && |
|
609 obj->lastProperty() == lastni->shapes_array[0]) |
|
610 { |
|
611 JSObject *proto = obj->getProto(); |
|
612 if (proto->isNative() && |
|
613 proto->hasEmptyElements() && |
|
614 proto->lastProperty() == lastni->shapes_array[1] && |
|
615 !proto->getProto()) |
|
616 { |
|
617 vp.setObject(*last); |
|
618 UpdateNativeIterator(lastni, obj); |
|
619 RegisterEnumerator(cx, last, lastni); |
|
620 return true; |
|
621 } |
|
622 } |
|
623 } |
|
624 |
|
625 /* |
|
626 * The iterator object for JSITER_ENUMERATE never escapes, so we |
|
627 * don't care for the proper parent/proto to be set. This also |
|
628 * allows us to re-use a previous iterator object that is not |
|
629 * currently active. |
|
630 */ |
|
631 { |
|
632 JSObject *pobj = obj; |
|
633 do { |
|
634 if (!pobj->isNative() || |
|
635 !pobj->hasEmptyElements() || |
|
636 pobj->is<TypedArrayObject>() || |
|
637 pobj->hasUncacheableProto() || |
|
638 pobj->getOps()->enumerate || |
|
639 pobj->getClass()->enumerate != JS_EnumerateStub || |
|
640 pobj->nativeContainsPure(cx->names().iteratorIntrinsic)) |
|
641 { |
|
642 shapes.clear(); |
|
643 goto miss; |
|
644 } |
|
645 Shape *shape = pobj->lastProperty(); |
|
646 key = (key + (key << 16)) ^ (uintptr_t(shape) >> 3); |
|
647 if (!shapes.append(shape)) |
|
648 return false; |
|
649 pobj = pobj->getProto(); |
|
650 } while (pobj); |
|
651 } |
|
652 |
|
653 PropertyIteratorObject *iterobj = cx->runtime()->nativeIterCache.get(key); |
|
654 if (iterobj) { |
|
655 NativeIterator *ni = iterobj->getNativeIterator(); |
|
656 if (!(ni->flags & (JSITER_ACTIVE|JSITER_UNREUSABLE)) && |
|
657 ni->shapes_key == key && |
|
658 ni->shapes_length == shapes.length() && |
|
659 Compare(ni->shapes_array, shapes.begin(), ni->shapes_length)) { |
|
660 vp.setObject(*iterobj); |
|
661 |
|
662 UpdateNativeIterator(ni, obj); |
|
663 RegisterEnumerator(cx, iterobj, ni); |
|
664 if (shapes.length() == 2) |
|
665 cx->runtime()->nativeIterCache.last = iterobj; |
|
666 return true; |
|
667 } |
|
668 } |
|
669 } |
|
670 |
|
671 miss: |
|
672 if (obj->is<ProxyObject>()) |
|
673 return Proxy::iterate(cx, obj, flags, vp); |
|
674 |
|
675 if (!GetCustomIterator(cx, obj, flags, vp)) |
|
676 return false; |
|
677 if (!vp.isUndefined()) |
|
678 return true; |
|
679 } |
|
680 |
|
681 /* NB: for (var p in null) succeeds by iterating over no properties. */ |
|
682 |
|
683 AutoIdVector keys(cx); |
|
684 if (flags & JSITER_FOREACH) { |
|
685 if (MOZ_LIKELY(obj != nullptr) && !Snapshot(cx, obj, flags, &keys)) |
|
686 return false; |
|
687 JS_ASSERT(shapes.empty()); |
|
688 if (!VectorToValueIterator(cx, obj, flags, keys, vp)) |
|
689 return false; |
|
690 } else { |
|
691 if (MOZ_LIKELY(obj != nullptr) && !Snapshot(cx, obj, flags, &keys)) |
|
692 return false; |
|
693 if (!VectorToKeyIterator(cx, obj, flags, keys, shapes.length(), key, vp)) |
|
694 return false; |
|
695 } |
|
696 |
|
697 PropertyIteratorObject *iterobj = &vp.toObject().as<PropertyIteratorObject>(); |
|
698 |
|
699 /* Cache the iterator object if possible. */ |
|
700 if (shapes.length()) |
|
701 cx->runtime()->nativeIterCache.set(key, iterobj); |
|
702 |
|
703 if (shapes.length() == 2) |
|
704 cx->runtime()->nativeIterCache.last = iterobj; |
|
705 return true; |
|
706 } |
|
707 |
|
708 JSObject * |
|
709 js::GetIteratorObject(JSContext *cx, HandleObject obj, uint32_t flags) |
|
710 { |
|
711 RootedValue value(cx); |
|
712 if (!GetIterator(cx, obj, flags, &value)) |
|
713 return nullptr; |
|
714 return &value.toObject(); |
|
715 } |
|
716 |
|
717 JSObject * |
|
718 js::CreateItrResultObject(JSContext *cx, HandleValue value, bool done) |
|
719 { |
|
720 // FIXME: We can cache the iterator result object shape somewhere. |
|
721 AssertHeapIsIdle(cx); |
|
722 |
|
723 RootedObject proto(cx, cx->global()->getOrCreateObjectPrototype(cx)); |
|
724 if (!proto) |
|
725 return nullptr; |
|
726 |
|
727 RootedObject obj(cx, NewObjectWithGivenProto(cx, &JSObject::class_, proto, cx->global())); |
|
728 if (!obj) |
|
729 return nullptr; |
|
730 |
|
731 if (!JSObject::defineProperty(cx, obj, cx->names().value, value)) |
|
732 return nullptr; |
|
733 |
|
734 RootedValue doneBool(cx, BooleanValue(done)); |
|
735 if (!JSObject::defineProperty(cx, obj, cx->names().done, doneBool)) |
|
736 return nullptr; |
|
737 |
|
738 return obj; |
|
739 } |
|
740 |
|
741 bool |
|
742 js_ThrowStopIteration(JSContext *cx) |
|
743 { |
|
744 JS_ASSERT(!JS_IsExceptionPending(cx)); |
|
745 |
|
746 // StopIteration isn't a constructor, but it's stored in GlobalObject |
|
747 // as one, out of laziness. Hence the GetBuiltinConstructor call here. |
|
748 RootedObject ctor(cx); |
|
749 if (GetBuiltinConstructor(cx, JSProto_StopIteration, &ctor)) |
|
750 cx->setPendingException(ObjectValue(*ctor)); |
|
751 return false; |
|
752 } |
|
753 |
|
754 /*** Iterator objects ****************************************************************************/ |
|
755 |
|
756 bool |
|
757 js::IteratorConstructor(JSContext *cx, unsigned argc, Value *vp) |
|
758 { |
|
759 CallArgs args = CallArgsFromVp(argc, vp); |
|
760 if (args.length() == 0) { |
|
761 js_ReportMissingArg(cx, args.calleev(), 0); |
|
762 return false; |
|
763 } |
|
764 |
|
765 bool keyonly = false; |
|
766 if (args.length() >= 2) |
|
767 keyonly = ToBoolean(args[1]); |
|
768 unsigned flags = JSITER_OWNONLY | (keyonly ? 0 : (JSITER_FOREACH | JSITER_KEYVALUE)); |
|
769 |
|
770 if (!ValueToIterator(cx, flags, args[0])) |
|
771 return false; |
|
772 args.rval().set(args[0]); |
|
773 return true; |
|
774 } |
|
775 |
|
776 MOZ_ALWAYS_INLINE bool |
|
777 IsIterator(HandleValue v) |
|
778 { |
|
779 return v.isObject() && v.toObject().hasClass(&PropertyIteratorObject::class_); |
|
780 } |
|
781 |
|
782 MOZ_ALWAYS_INLINE bool |
|
783 iterator_next_impl(JSContext *cx, CallArgs args) |
|
784 { |
|
785 JS_ASSERT(IsIterator(args.thisv())); |
|
786 |
|
787 RootedObject thisObj(cx, &args.thisv().toObject()); |
|
788 |
|
789 if (!js_IteratorMore(cx, thisObj, args.rval())) |
|
790 return false; |
|
791 |
|
792 if (!args.rval().toBoolean()) { |
|
793 js_ThrowStopIteration(cx); |
|
794 return false; |
|
795 } |
|
796 |
|
797 return js_IteratorNext(cx, thisObj, args.rval()); |
|
798 } |
|
799 |
|
800 static bool |
|
801 iterator_next(JSContext *cx, unsigned argc, Value *vp) |
|
802 { |
|
803 CallArgs args = CallArgsFromVp(argc, vp); |
|
804 return CallNonGenericMethod<IsIterator, iterator_next_impl>(cx, args); |
|
805 } |
|
806 |
|
807 static const JSFunctionSpec iterator_methods[] = { |
|
808 JS_SELF_HOSTED_FN("@@iterator", "LegacyIteratorShim", 0, 0), |
|
809 JS_FN("next", iterator_next, 0, 0), |
|
810 JS_FS_END |
|
811 }; |
|
812 |
|
813 static JSObject * |
|
814 iterator_iteratorObject(JSContext *cx, HandleObject obj, bool keysonly) |
|
815 { |
|
816 return obj; |
|
817 } |
|
818 |
|
819 size_t |
|
820 PropertyIteratorObject::sizeOfMisc(mozilla::MallocSizeOf mallocSizeOf) const |
|
821 { |
|
822 return mallocSizeOf(getPrivate()); |
|
823 } |
|
824 |
|
825 void |
|
826 PropertyIteratorObject::trace(JSTracer *trc, JSObject *obj) |
|
827 { |
|
828 if (NativeIterator *ni = obj->as<PropertyIteratorObject>().getNativeIterator()) |
|
829 ni->mark(trc); |
|
830 } |
|
831 |
|
832 void |
|
833 PropertyIteratorObject::finalize(FreeOp *fop, JSObject *obj) |
|
834 { |
|
835 if (NativeIterator *ni = obj->as<PropertyIteratorObject>().getNativeIterator()) |
|
836 fop->free_(ni); |
|
837 } |
|
838 |
|
839 const Class PropertyIteratorObject::class_ = { |
|
840 "Iterator", |
|
841 JSCLASS_IMPLEMENTS_BARRIERS | |
|
842 JSCLASS_HAS_CACHED_PROTO(JSProto_Iterator) | |
|
843 JSCLASS_HAS_PRIVATE | |
|
844 JSCLASS_BACKGROUND_FINALIZE, |
|
845 JS_PropertyStub, /* addProperty */ |
|
846 JS_DeletePropertyStub, /* delProperty */ |
|
847 JS_PropertyStub, /* getProperty */ |
|
848 JS_StrictPropertyStub, /* setProperty */ |
|
849 JS_EnumerateStub, |
|
850 JS_ResolveStub, |
|
851 JS_ConvertStub, |
|
852 finalize, |
|
853 nullptr, /* call */ |
|
854 nullptr, /* hasInstance */ |
|
855 nullptr, /* construct */ |
|
856 trace, |
|
857 JS_NULL_CLASS_SPEC, |
|
858 { |
|
859 nullptr, /* outerObject */ |
|
860 nullptr, /* innerObject */ |
|
861 iterator_iteratorObject, |
|
862 } |
|
863 }; |
|
864 |
|
865 enum { |
|
866 ArrayIteratorSlotIteratedObject, |
|
867 ArrayIteratorSlotNextIndex, |
|
868 ArrayIteratorSlotItemKind, |
|
869 ArrayIteratorSlotCount |
|
870 }; |
|
871 |
|
872 const Class ArrayIteratorObject::class_ = { |
|
873 "Array Iterator", |
|
874 JSCLASS_IMPLEMENTS_BARRIERS | |
|
875 JSCLASS_HAS_RESERVED_SLOTS(ArrayIteratorSlotCount), |
|
876 JS_PropertyStub, /* addProperty */ |
|
877 JS_DeletePropertyStub, /* delProperty */ |
|
878 JS_PropertyStub, /* getProperty */ |
|
879 JS_StrictPropertyStub, /* setProperty */ |
|
880 JS_EnumerateStub, |
|
881 JS_ResolveStub, |
|
882 JS_ConvertStub, |
|
883 nullptr /* finalize */ |
|
884 }; |
|
885 |
|
886 static const JSFunctionSpec array_iterator_methods[] = { |
|
887 JS_SELF_HOSTED_FN("@@iterator", "ArrayIteratorIdentity", 0, 0), |
|
888 JS_SELF_HOSTED_FN("next", "ArrayIteratorNext", 0, 0), |
|
889 JS_FS_END |
|
890 }; |
|
891 |
|
892 static const Class StringIteratorPrototypeClass = { |
|
893 "String Iterator", |
|
894 JSCLASS_IMPLEMENTS_BARRIERS, |
|
895 JS_PropertyStub, /* addProperty */ |
|
896 JS_DeletePropertyStub, /* delProperty */ |
|
897 JS_PropertyStub, /* getProperty */ |
|
898 JS_StrictPropertyStub, /* setProperty */ |
|
899 JS_EnumerateStub, |
|
900 JS_ResolveStub, |
|
901 JS_ConvertStub, |
|
902 nullptr /* finalize */ |
|
903 }; |
|
904 |
|
905 enum { |
|
906 StringIteratorSlotIteratedObject, |
|
907 StringIteratorSlotNextIndex, |
|
908 StringIteratorSlotCount |
|
909 }; |
|
910 |
|
911 const Class StringIteratorObject::class_ = { |
|
912 "String Iterator", |
|
913 JSCLASS_IMPLEMENTS_BARRIERS | |
|
914 JSCLASS_HAS_RESERVED_SLOTS(StringIteratorSlotCount), |
|
915 JS_PropertyStub, /* addProperty */ |
|
916 JS_DeletePropertyStub, /* delProperty */ |
|
917 JS_PropertyStub, /* getProperty */ |
|
918 JS_StrictPropertyStub, /* setProperty */ |
|
919 JS_EnumerateStub, |
|
920 JS_ResolveStub, |
|
921 JS_ConvertStub, |
|
922 nullptr /* finalize */ |
|
923 }; |
|
924 |
|
925 static const JSFunctionSpec string_iterator_methods[] = { |
|
926 JS_SELF_HOSTED_FN("@@iterator", "StringIteratorIdentity", 0, 0), |
|
927 JS_SELF_HOSTED_FN("next", "StringIteratorNext", 0, 0), |
|
928 JS_FS_END |
|
929 }; |
|
930 |
|
931 static bool |
|
932 CloseLegacyGenerator(JSContext *cx, HandleObject genobj); |
|
933 |
|
934 bool |
|
935 js::ValueToIterator(JSContext *cx, unsigned flags, MutableHandleValue vp) |
|
936 { |
|
937 /* JSITER_KEYVALUE must always come with JSITER_FOREACH */ |
|
938 JS_ASSERT_IF(flags & JSITER_KEYVALUE, flags & JSITER_FOREACH); |
|
939 |
|
940 /* |
|
941 * Make sure the more/next state machine doesn't get stuck. A value might |
|
942 * be left in iterValue when a trace is left due to an interrupt after |
|
943 * JSOP_MOREITER but before the value is picked up by FOR*. |
|
944 */ |
|
945 cx->iterValue.setMagic(JS_NO_ITER_VALUE); |
|
946 |
|
947 RootedObject obj(cx); |
|
948 if (vp.isObject()) { |
|
949 /* Common case. */ |
|
950 obj = &vp.toObject(); |
|
951 } else { |
|
952 /* |
|
953 * Enumerating over null and undefined gives an empty enumerator, so |
|
954 * that |for (var p in <null or undefined>) <loop>;| never executes |
|
955 * <loop>, per ES5 12.6.4. |
|
956 */ |
|
957 if (!(flags & JSITER_ENUMERATE) || !vp.isNullOrUndefined()) { |
|
958 obj = ToObject(cx, vp); |
|
959 if (!obj) |
|
960 return false; |
|
961 } |
|
962 } |
|
963 |
|
964 return GetIterator(cx, obj, flags, vp); |
|
965 } |
|
966 |
|
967 bool |
|
968 js::CloseIterator(JSContext *cx, HandleObject obj) |
|
969 { |
|
970 cx->iterValue.setMagic(JS_NO_ITER_VALUE); |
|
971 |
|
972 if (obj->is<PropertyIteratorObject>()) { |
|
973 /* Remove enumerators from the active list, which is a stack. */ |
|
974 NativeIterator *ni = obj->as<PropertyIteratorObject>().getNativeIterator(); |
|
975 |
|
976 if (ni->flags & JSITER_ENUMERATE) { |
|
977 ni->unlink(); |
|
978 |
|
979 JS_ASSERT(ni->flags & JSITER_ACTIVE); |
|
980 ni->flags &= ~JSITER_ACTIVE; |
|
981 |
|
982 /* |
|
983 * Reset the enumerator; it may still be in the cached iterators |
|
984 * for this thread, and can be reused. |
|
985 */ |
|
986 ni->props_cursor = ni->props_array; |
|
987 } |
|
988 } else if (obj->is<LegacyGeneratorObject>()) { |
|
989 return CloseLegacyGenerator(cx, obj); |
|
990 } |
|
991 return true; |
|
992 } |
|
993 |
|
994 bool |
|
995 js::UnwindIteratorForException(JSContext *cx, HandleObject obj) |
|
996 { |
|
997 RootedValue v(cx); |
|
998 bool getOk = cx->getPendingException(&v); |
|
999 cx->clearPendingException(); |
|
1000 if (!CloseIterator(cx, obj)) |
|
1001 return false; |
|
1002 if (!getOk) |
|
1003 return false; |
|
1004 cx->setPendingException(v); |
|
1005 return true; |
|
1006 } |
|
1007 |
|
1008 void |
|
1009 js::UnwindIteratorForUncatchableException(JSContext *cx, JSObject *obj) |
|
1010 { |
|
1011 if (obj->is<PropertyIteratorObject>()) { |
|
1012 NativeIterator *ni = obj->as<PropertyIteratorObject>().getNativeIterator(); |
|
1013 if (ni->flags & JSITER_ENUMERATE) |
|
1014 ni->unlink(); |
|
1015 } |
|
1016 } |
|
1017 |
|
1018 /* |
|
1019 * Suppress enumeration of deleted properties. This function must be called |
|
1020 * when a property is deleted and there might be active enumerators. |
|
1021 * |
|
1022 * We maintain a list of active non-escaping for-in enumerators. To suppress |
|
1023 * a property, we check whether each active enumerator contains the (obj, id) |
|
1024 * pair and has not yet enumerated |id|. If so, and |id| is the next property, |
|
1025 * we simply advance the cursor. Otherwise, we delete |id| from the list. |
|
1026 * |
|
1027 * We do not suppress enumeration of a property deleted along an object's |
|
1028 * prototype chain. Only direct deletions on the object are handled. |
|
1029 * |
|
1030 * This function can suppress multiple properties at once. The |predicate| |
|
1031 * argument is an object which can be called on an id and returns true or |
|
1032 * false. It also must have a method |matchesAtMostOne| which allows us to |
|
1033 * stop searching after the first deletion if true. |
|
1034 */ |
|
1035 template<typename StringPredicate> |
|
1036 static bool |
|
1037 SuppressDeletedPropertyHelper(JSContext *cx, HandleObject obj, StringPredicate predicate) |
|
1038 { |
|
1039 NativeIterator *enumeratorList = cx->compartment()->enumerators; |
|
1040 NativeIterator *ni = enumeratorList->next(); |
|
1041 |
|
1042 while (ni != enumeratorList) { |
|
1043 again: |
|
1044 /* This only works for identified suppressed keys, not values. */ |
|
1045 if (ni->isKeyIter() && ni->obj == obj && ni->props_cursor < ni->props_end) { |
|
1046 /* Check whether id is still to come. */ |
|
1047 HeapPtr<JSFlatString> *props_cursor = ni->current(); |
|
1048 HeapPtr<JSFlatString> *props_end = ni->end(); |
|
1049 for (HeapPtr<JSFlatString> *idp = props_cursor; idp < props_end; ++idp) { |
|
1050 if (predicate(*idp)) { |
|
1051 /* |
|
1052 * Check whether another property along the prototype chain |
|
1053 * became visible as a result of this deletion. |
|
1054 */ |
|
1055 RootedObject proto(cx); |
|
1056 if (!JSObject::getProto(cx, obj, &proto)) |
|
1057 return false; |
|
1058 if (proto) { |
|
1059 RootedObject obj2(cx); |
|
1060 RootedShape prop(cx); |
|
1061 RootedId id(cx); |
|
1062 RootedValue idv(cx, StringValue(*idp)); |
|
1063 if (!ValueToId<CanGC>(cx, idv, &id)) |
|
1064 return false; |
|
1065 if (!JSObject::lookupGeneric(cx, proto, id, &obj2, &prop)) |
|
1066 return false; |
|
1067 if (prop) { |
|
1068 unsigned attrs; |
|
1069 if (obj2->isNative()) |
|
1070 attrs = GetShapeAttributes(obj2, prop); |
|
1071 else if (!JSObject::getGenericAttributes(cx, obj2, id, &attrs)) |
|
1072 return false; |
|
1073 |
|
1074 if (attrs & JSPROP_ENUMERATE) |
|
1075 continue; |
|
1076 } |
|
1077 } |
|
1078 |
|
1079 /* |
|
1080 * If lookupProperty or getAttributes above removed a property from |
|
1081 * ni, start over. |
|
1082 */ |
|
1083 if (props_end != ni->props_end || props_cursor != ni->props_cursor) |
|
1084 goto again; |
|
1085 |
|
1086 /* |
|
1087 * No property along the prototype chain stepped in to take the |
|
1088 * property's place, so go ahead and delete id from the list. |
|
1089 * If it is the next property to be enumerated, just skip it. |
|
1090 */ |
|
1091 if (idp == props_cursor) { |
|
1092 ni->incCursor(); |
|
1093 } else { |
|
1094 for (HeapPtr<JSFlatString> *p = idp; p + 1 != props_end; p++) |
|
1095 *p = *(p + 1); |
|
1096 ni->props_end = ni->end() - 1; |
|
1097 |
|
1098 /* |
|
1099 * This invokes the pre barrier on this element, since |
|
1100 * it's no longer going to be marked, and ensures that |
|
1101 * any existing remembered set entry will be dropped. |
|
1102 */ |
|
1103 *ni->props_end = nullptr; |
|
1104 } |
|
1105 |
|
1106 /* Don't reuse modified native iterators. */ |
|
1107 ni->flags |= JSITER_UNREUSABLE; |
|
1108 |
|
1109 if (predicate.matchesAtMostOne()) |
|
1110 break; |
|
1111 } |
|
1112 } |
|
1113 } |
|
1114 ni = ni->next(); |
|
1115 } |
|
1116 return true; |
|
1117 } |
|
1118 |
|
1119 namespace { |
|
1120 |
|
1121 class SingleStringPredicate { |
|
1122 Handle<JSFlatString*> str; |
|
1123 public: |
|
1124 SingleStringPredicate(Handle<JSFlatString*> str) : str(str) {} |
|
1125 |
|
1126 bool operator()(JSFlatString *str) { return EqualStrings(str, this->str); } |
|
1127 bool matchesAtMostOne() { return true; } |
|
1128 }; |
|
1129 |
|
1130 } /* anonymous namespace */ |
|
1131 |
|
1132 bool |
|
1133 js_SuppressDeletedProperty(JSContext *cx, HandleObject obj, jsid id) |
|
1134 { |
|
1135 Rooted<JSFlatString*> str(cx, IdToString(cx, id)); |
|
1136 if (!str) |
|
1137 return false; |
|
1138 return SuppressDeletedPropertyHelper(cx, obj, SingleStringPredicate(str)); |
|
1139 } |
|
1140 |
|
1141 bool |
|
1142 js_SuppressDeletedElement(JSContext *cx, HandleObject obj, uint32_t index) |
|
1143 { |
|
1144 RootedId id(cx); |
|
1145 if (!IndexToId(cx, index, &id)) |
|
1146 return false; |
|
1147 return js_SuppressDeletedProperty(cx, obj, id); |
|
1148 } |
|
1149 |
|
1150 namespace { |
|
1151 |
|
1152 class IndexRangePredicate { |
|
1153 uint32_t begin, end; |
|
1154 |
|
1155 public: |
|
1156 IndexRangePredicate(uint32_t begin, uint32_t end) : begin(begin), end(end) {} |
|
1157 |
|
1158 bool operator()(JSFlatString *str) { |
|
1159 uint32_t index; |
|
1160 return str->isIndex(&index) && begin <= index && index < end; |
|
1161 } |
|
1162 |
|
1163 bool matchesAtMostOne() { return false; } |
|
1164 }; |
|
1165 |
|
1166 } /* anonymous namespace */ |
|
1167 |
|
1168 bool |
|
1169 js_SuppressDeletedElements(JSContext *cx, HandleObject obj, uint32_t begin, uint32_t end) |
|
1170 { |
|
1171 return SuppressDeletedPropertyHelper(cx, obj, IndexRangePredicate(begin, end)); |
|
1172 } |
|
1173 |
|
1174 bool |
|
1175 js_IteratorMore(JSContext *cx, HandleObject iterobj, MutableHandleValue rval) |
|
1176 { |
|
1177 /* Fast path for native iterators */ |
|
1178 NativeIterator *ni = nullptr; |
|
1179 if (iterobj->is<PropertyIteratorObject>()) { |
|
1180 /* Key iterators are handled by fast-paths. */ |
|
1181 ni = iterobj->as<PropertyIteratorObject>().getNativeIterator(); |
|
1182 bool more = ni->props_cursor < ni->props_end; |
|
1183 if (ni->isKeyIter() || !more) { |
|
1184 rval.setBoolean(more); |
|
1185 return true; |
|
1186 } |
|
1187 } |
|
1188 |
|
1189 /* We might still have a pending value. */ |
|
1190 if (!cx->iterValue.isMagic(JS_NO_ITER_VALUE)) { |
|
1191 rval.setBoolean(true); |
|
1192 return true; |
|
1193 } |
|
1194 |
|
1195 /* We're reentering below and can call anything. */ |
|
1196 JS_CHECK_RECURSION(cx, return false); |
|
1197 |
|
1198 /* Fetch and cache the next value from the iterator. */ |
|
1199 if (ni) { |
|
1200 JS_ASSERT(!ni->isKeyIter()); |
|
1201 RootedId id(cx); |
|
1202 RootedValue current(cx, StringValue(*ni->current())); |
|
1203 if (!ValueToId<CanGC>(cx, current, &id)) |
|
1204 return false; |
|
1205 ni->incCursor(); |
|
1206 RootedObject obj(cx, ni->obj); |
|
1207 if (!JSObject::getGeneric(cx, obj, obj, id, rval)) |
|
1208 return false; |
|
1209 if ((ni->flags & JSITER_KEYVALUE) && !NewKeyValuePair(cx, id, rval, rval)) |
|
1210 return false; |
|
1211 } else { |
|
1212 /* Call the iterator object's .next method. */ |
|
1213 if (!JSObject::getProperty(cx, iterobj, iterobj, cx->names().next, rval)) |
|
1214 return false; |
|
1215 if (!Invoke(cx, ObjectValue(*iterobj), rval, 0, nullptr, rval)) { |
|
1216 /* Check for StopIteration. */ |
|
1217 if (!cx->isExceptionPending()) |
|
1218 return false; |
|
1219 RootedValue exception(cx); |
|
1220 if (!cx->getPendingException(&exception)) |
|
1221 return false; |
|
1222 if (!JS_IsStopIteration(exception)) |
|
1223 return false; |
|
1224 |
|
1225 cx->clearPendingException(); |
|
1226 cx->iterValue.setMagic(JS_NO_ITER_VALUE); |
|
1227 rval.setBoolean(false); |
|
1228 return true; |
|
1229 } |
|
1230 } |
|
1231 |
|
1232 /* Cache the value returned by iterobj.next() so js_IteratorNext() can find it. */ |
|
1233 JS_ASSERT(!rval.isMagic(JS_NO_ITER_VALUE)); |
|
1234 cx->iterValue = rval; |
|
1235 rval.setBoolean(true); |
|
1236 return true; |
|
1237 } |
|
1238 |
|
1239 bool |
|
1240 js_IteratorNext(JSContext *cx, HandleObject iterobj, MutableHandleValue rval) |
|
1241 { |
|
1242 /* Fast path for native iterators */ |
|
1243 if (iterobj->is<PropertyIteratorObject>()) { |
|
1244 /* |
|
1245 * Implement next directly as all the methods of the native iterator are |
|
1246 * read-only and permanent. |
|
1247 */ |
|
1248 NativeIterator *ni = iterobj->as<PropertyIteratorObject>().getNativeIterator(); |
|
1249 if (ni->isKeyIter()) { |
|
1250 JS_ASSERT(ni->props_cursor < ni->props_end); |
|
1251 rval.setString(*ni->current()); |
|
1252 ni->incCursor(); |
|
1253 return true; |
|
1254 } |
|
1255 } |
|
1256 |
|
1257 JS_ASSERT(!cx->iterValue.isMagic(JS_NO_ITER_VALUE)); |
|
1258 rval.set(cx->iterValue); |
|
1259 cx->iterValue.setMagic(JS_NO_ITER_VALUE); |
|
1260 |
|
1261 return true; |
|
1262 } |
|
1263 |
|
1264 static bool |
|
1265 stopiter_hasInstance(JSContext *cx, HandleObject obj, MutableHandleValue v, bool *bp) |
|
1266 { |
|
1267 *bp = JS_IsStopIteration(v); |
|
1268 return true; |
|
1269 } |
|
1270 |
|
1271 const Class StopIterationObject::class_ = { |
|
1272 "StopIteration", |
|
1273 JSCLASS_HAS_CACHED_PROTO(JSProto_StopIteration), |
|
1274 JS_PropertyStub, /* addProperty */ |
|
1275 JS_DeletePropertyStub, /* delProperty */ |
|
1276 JS_PropertyStub, /* getProperty */ |
|
1277 JS_StrictPropertyStub, /* setProperty */ |
|
1278 JS_EnumerateStub, |
|
1279 JS_ResolveStub, |
|
1280 JS_ConvertStub, |
|
1281 nullptr, /* finalize */ |
|
1282 nullptr, /* call */ |
|
1283 stopiter_hasInstance, |
|
1284 nullptr /* construct */ |
|
1285 }; |
|
1286 |
|
1287 bool |
|
1288 ForOfIterator::init(HandleValue iterable, NonIterableBehavior nonIterableBehavior) |
|
1289 { |
|
1290 JSContext *cx = cx_; |
|
1291 RootedObject iterableObj(cx, ToObject(cx, iterable)); |
|
1292 if (!iterableObj) |
|
1293 return false; |
|
1294 |
|
1295 JS_ASSERT(index == NOT_ARRAY); |
|
1296 |
|
1297 // Check the PIC first for a match. |
|
1298 if (iterableObj->is<ArrayObject>()) { |
|
1299 ForOfPIC::Chain *stubChain = ForOfPIC::getOrCreate(cx); |
|
1300 if (!stubChain) |
|
1301 return false; |
|
1302 |
|
1303 bool optimized; |
|
1304 if (!stubChain->tryOptimizeArray(cx, iterableObj, &optimized)) |
|
1305 return false; |
|
1306 |
|
1307 if (optimized) { |
|
1308 // Got optimized stub. Array is optimizable. |
|
1309 index = 0; |
|
1310 iterator = iterableObj; |
|
1311 return true; |
|
1312 } |
|
1313 } |
|
1314 |
|
1315 JS_ASSERT(index == NOT_ARRAY); |
|
1316 |
|
1317 // The iterator is the result of calling obj[@@iterator](). |
|
1318 InvokeArgs args(cx); |
|
1319 if (!args.init(0)) |
|
1320 return false; |
|
1321 args.setThis(ObjectValue(*iterableObj)); |
|
1322 |
|
1323 RootedValue callee(cx); |
|
1324 if (!JSObject::getProperty(cx, iterableObj, iterableObj, cx->names().std_iterator, &callee)) |
|
1325 return false; |
|
1326 |
|
1327 // Throw if obj[@@iterator] isn't callable if we were asked to do so. |
|
1328 // js::Invoke is about to check for this kind of error anyway, but it would |
|
1329 // throw an inscrutable error message about |method| rather than this nice |
|
1330 // one about |obj|. |
|
1331 if (!callee.isObject() || !callee.toObject().isCallable()) { |
|
1332 if (nonIterableBehavior == AllowNonIterable) |
|
1333 return true; |
|
1334 char *bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, iterable, NullPtr()); |
|
1335 if (!bytes) |
|
1336 return false; |
|
1337 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_ITERABLE, bytes); |
|
1338 js_free(bytes); |
|
1339 return false; |
|
1340 } |
|
1341 |
|
1342 args.setCallee(callee); |
|
1343 if (!Invoke(cx, args)) |
|
1344 return false; |
|
1345 |
|
1346 iterator = ToObject(cx, args.rval()); |
|
1347 if (!iterator) |
|
1348 return false; |
|
1349 |
|
1350 return true; |
|
1351 } |
|
1352 |
|
1353 inline bool |
|
1354 ForOfIterator::nextFromOptimizedArray(MutableHandleValue vp, bool *done) |
|
1355 { |
|
1356 JS_ASSERT(index != NOT_ARRAY); |
|
1357 |
|
1358 if (!CheckForInterrupt(cx_)) |
|
1359 return false; |
|
1360 |
|
1361 JS_ASSERT(iterator->isNative()); |
|
1362 JS_ASSERT(iterator->is<ArrayObject>()); |
|
1363 |
|
1364 if (index >= iterator->as<ArrayObject>().length()) { |
|
1365 vp.setUndefined(); |
|
1366 *done = true; |
|
1367 return true; |
|
1368 } |
|
1369 *done = false; |
|
1370 |
|
1371 // Try to get array element via direct access. |
|
1372 if (index < iterator->getDenseInitializedLength()) { |
|
1373 vp.set(iterator->getDenseElement(index)); |
|
1374 if (!vp.isMagic(JS_ELEMENTS_HOLE)) { |
|
1375 ++index; |
|
1376 return true; |
|
1377 } |
|
1378 } |
|
1379 |
|
1380 return JSObject::getElement(cx_, iterator, iterator, index++, vp); |
|
1381 } |
|
1382 |
|
1383 bool |
|
1384 ForOfIterator::next(MutableHandleValue vp, bool *done) |
|
1385 { |
|
1386 JS_ASSERT(iterator); |
|
1387 |
|
1388 if (index != NOT_ARRAY) { |
|
1389 ForOfPIC::Chain *stubChain = ForOfPIC::getOrCreate(cx_); |
|
1390 if (!stubChain) |
|
1391 return false; |
|
1392 |
|
1393 if (stubChain->isArrayNextStillSane()) |
|
1394 return nextFromOptimizedArray(vp, done); |
|
1395 |
|
1396 // ArrayIterator.prototype.next changed, materialize a proper |
|
1397 // ArrayIterator instance and fall through to slowpath case. |
|
1398 if (!materializeArrayIterator()) |
|
1399 return false; |
|
1400 } |
|
1401 |
|
1402 RootedValue method(cx_); |
|
1403 if (!JSObject::getProperty(cx_, iterator, iterator, cx_->names().next, &method)) |
|
1404 return false; |
|
1405 |
|
1406 InvokeArgs args(cx_); |
|
1407 if (!args.init(1)) |
|
1408 return false; |
|
1409 args.setCallee(method); |
|
1410 args.setThis(ObjectValue(*iterator)); |
|
1411 args[0].setUndefined(); |
|
1412 if (!Invoke(cx_, args)) |
|
1413 return false; |
|
1414 |
|
1415 RootedObject resultObj(cx_, ToObject(cx_, args.rval())); |
|
1416 if (!resultObj) |
|
1417 return false; |
|
1418 RootedValue doneVal(cx_); |
|
1419 if (!JSObject::getProperty(cx_, resultObj, resultObj, cx_->names().done, &doneVal)) |
|
1420 return false; |
|
1421 *done = ToBoolean(doneVal); |
|
1422 if (*done) { |
|
1423 vp.setUndefined(); |
|
1424 return true; |
|
1425 } |
|
1426 return JSObject::getProperty(cx_, resultObj, resultObj, cx_->names().value, vp); |
|
1427 } |
|
1428 |
|
1429 bool |
|
1430 ForOfIterator::materializeArrayIterator() |
|
1431 { |
|
1432 JS_ASSERT(index != NOT_ARRAY); |
|
1433 |
|
1434 const char *nameString = "ArrayValuesAt"; |
|
1435 |
|
1436 RootedAtom name(cx_, Atomize(cx_, nameString, strlen(nameString))); |
|
1437 if (!name) |
|
1438 return false; |
|
1439 |
|
1440 RootedValue val(cx_); |
|
1441 if (!cx_->global()->getSelfHostedFunction(cx_, name, name, 1, &val)) |
|
1442 return false; |
|
1443 |
|
1444 InvokeArgs args(cx_); |
|
1445 if (!args.init(1)) |
|
1446 return false; |
|
1447 args.setCallee(val); |
|
1448 args.setThis(ObjectValue(*iterator)); |
|
1449 args[0].set(Int32Value(index)); |
|
1450 if (!Invoke(cx_, args)) |
|
1451 return false; |
|
1452 |
|
1453 index = NOT_ARRAY; |
|
1454 // Result of call to ArrayValuesAt must be an object. |
|
1455 iterator = &args.rval().toObject(); |
|
1456 return true; |
|
1457 } |
|
1458 |
|
1459 /*** Generators **********************************************************************************/ |
|
1460 |
|
1461 template<typename T> |
|
1462 static void |
|
1463 FinalizeGenerator(FreeOp *fop, JSObject *obj) |
|
1464 { |
|
1465 JS_ASSERT(obj->is<T>()); |
|
1466 JSGenerator *gen = obj->as<T>().getGenerator(); |
|
1467 JS_ASSERT(gen); |
|
1468 // gen is open when a script has not called its close method while |
|
1469 // explicitly manipulating it. |
|
1470 JS_ASSERT(gen->state == JSGEN_NEWBORN || |
|
1471 gen->state == JSGEN_CLOSED || |
|
1472 gen->state == JSGEN_OPEN); |
|
1473 // If gen->state is JSGEN_CLOSED, gen->fp may be nullptr. |
|
1474 if (gen->fp) |
|
1475 JS_POISON(gen->fp, JS_SWEPT_FRAME_PATTERN, sizeof(InterpreterFrame)); |
|
1476 JS_POISON(gen, JS_SWEPT_FRAME_PATTERN, sizeof(JSGenerator)); |
|
1477 fop->free_(gen); |
|
1478 } |
|
1479 |
|
1480 static void |
|
1481 MarkGeneratorFrame(JSTracer *trc, JSGenerator *gen) |
|
1482 { |
|
1483 MarkValueRange(trc, |
|
1484 HeapValueify(gen->fp->generatorArgsSnapshotBegin()), |
|
1485 HeapValueify(gen->fp->generatorArgsSnapshotEnd()), |
|
1486 "Generator Floating Args"); |
|
1487 gen->fp->mark(trc); |
|
1488 MarkValueRange(trc, |
|
1489 HeapValueify(gen->fp->generatorSlotsSnapshotBegin()), |
|
1490 HeapValueify(gen->regs.sp), |
|
1491 "Generator Floating Stack"); |
|
1492 } |
|
1493 |
|
1494 static void |
|
1495 GeneratorWriteBarrierPre(JSContext *cx, JSGenerator *gen) |
|
1496 { |
|
1497 JS::Zone *zone = cx->zone(); |
|
1498 if (zone->needsBarrier()) |
|
1499 MarkGeneratorFrame(zone->barrierTracer(), gen); |
|
1500 } |
|
1501 |
|
1502 static void |
|
1503 GeneratorWriteBarrierPost(JSContext *cx, JSGenerator *gen) |
|
1504 { |
|
1505 #ifdef JSGC_GENERATIONAL |
|
1506 cx->runtime()->gcStoreBuffer.putWholeCell(gen->obj); |
|
1507 #endif |
|
1508 } |
|
1509 |
|
1510 /* |
|
1511 * Only mark generator frames/slots when the generator is not active on the |
|
1512 * stack or closed. Barriers when copying onto the stack or closing preserve |
|
1513 * gc invariants. |
|
1514 */ |
|
1515 static bool |
|
1516 GeneratorHasMarkableFrame(JSGenerator *gen) |
|
1517 { |
|
1518 return gen->state == JSGEN_NEWBORN || gen->state == JSGEN_OPEN; |
|
1519 } |
|
1520 |
|
1521 /* |
|
1522 * When a generator is closed, the GC things reachable from the contained frame |
|
1523 * and slots become unreachable and thus require a write barrier. |
|
1524 */ |
|
1525 static void |
|
1526 SetGeneratorClosed(JSContext *cx, JSGenerator *gen) |
|
1527 { |
|
1528 JS_ASSERT(gen->state != JSGEN_CLOSED); |
|
1529 if (GeneratorHasMarkableFrame(gen)) |
|
1530 GeneratorWriteBarrierPre(cx, gen); |
|
1531 gen->state = JSGEN_CLOSED; |
|
1532 |
|
1533 #ifdef DEBUG |
|
1534 MakeRangeGCSafe(gen->fp->generatorArgsSnapshotBegin(), |
|
1535 gen->fp->generatorArgsSnapshotEnd()); |
|
1536 MakeRangeGCSafe(gen->fp->generatorSlotsSnapshotBegin(), |
|
1537 gen->regs.sp); |
|
1538 PodZero(&gen->regs, 1); |
|
1539 gen->fp = nullptr; |
|
1540 #endif |
|
1541 } |
|
1542 |
|
1543 template<typename T> |
|
1544 static void |
|
1545 TraceGenerator(JSTracer *trc, JSObject *obj) |
|
1546 { |
|
1547 JS_ASSERT(obj->is<T>()); |
|
1548 JSGenerator *gen = obj->as<T>().getGenerator(); |
|
1549 JS_ASSERT(gen); |
|
1550 if (GeneratorHasMarkableFrame(gen)) |
|
1551 MarkGeneratorFrame(trc, gen); |
|
1552 } |
|
1553 |
|
1554 GeneratorState::GeneratorState(JSContext *cx, JSGenerator *gen, JSGeneratorState futureState) |
|
1555 : RunState(cx, Generator, gen->fp->script()), |
|
1556 cx_(cx), |
|
1557 gen_(gen), |
|
1558 futureState_(futureState), |
|
1559 entered_(false) |
|
1560 { } |
|
1561 |
|
1562 GeneratorState::~GeneratorState() |
|
1563 { |
|
1564 gen_->fp->setSuspended(); |
|
1565 |
|
1566 if (entered_) |
|
1567 cx_->leaveGenerator(gen_); |
|
1568 } |
|
1569 |
|
1570 InterpreterFrame * |
|
1571 GeneratorState::pushInterpreterFrame(JSContext *cx) |
|
1572 { |
|
1573 /* |
|
1574 * Write barrier is needed since the generator stack can be updated, |
|
1575 * and it's not barriered in any other way. We need to do it before |
|
1576 * gen->state changes, which can cause us to trace the generator |
|
1577 * differently. |
|
1578 * |
|
1579 * We could optimize this by setting a bit on the generator to signify |
|
1580 * that it has been marked. If this bit has already been set, there is no |
|
1581 * need to mark again. The bit would have to be reset before the next GC, |
|
1582 * or else some kind of epoch scheme would have to be used. |
|
1583 */ |
|
1584 GeneratorWriteBarrierPre(cx, gen_); |
|
1585 gen_->state = futureState_; |
|
1586 |
|
1587 gen_->fp->clearSuspended(); |
|
1588 |
|
1589 cx->enterGenerator(gen_); /* OOM check above. */ |
|
1590 entered_ = true; |
|
1591 return gen_->fp; |
|
1592 } |
|
1593 |
|
1594 const Class LegacyGeneratorObject::class_ = { |
|
1595 "Generator", |
|
1596 JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS, |
|
1597 JS_PropertyStub, /* addProperty */ |
|
1598 JS_DeletePropertyStub, /* delProperty */ |
|
1599 JS_PropertyStub, /* getProperty */ |
|
1600 JS_StrictPropertyStub, /* setProperty */ |
|
1601 JS_EnumerateStub, |
|
1602 JS_ResolveStub, |
|
1603 JS_ConvertStub, |
|
1604 FinalizeGenerator<LegacyGeneratorObject>, |
|
1605 nullptr, /* call */ |
|
1606 nullptr, /* hasInstance */ |
|
1607 nullptr, /* construct */ |
|
1608 TraceGenerator<LegacyGeneratorObject>, |
|
1609 JS_NULL_CLASS_SPEC, |
|
1610 { |
|
1611 nullptr, /* outerObject */ |
|
1612 nullptr, /* innerObject */ |
|
1613 iterator_iteratorObject, |
|
1614 } |
|
1615 }; |
|
1616 |
|
1617 const Class StarGeneratorObject::class_ = { |
|
1618 "Generator", |
|
1619 JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS, |
|
1620 JS_PropertyStub, /* addProperty */ |
|
1621 JS_DeletePropertyStub, /* delProperty */ |
|
1622 JS_PropertyStub, /* getProperty */ |
|
1623 JS_StrictPropertyStub, /* setProperty */ |
|
1624 JS_EnumerateStub, |
|
1625 JS_ResolveStub, |
|
1626 JS_ConvertStub, |
|
1627 FinalizeGenerator<StarGeneratorObject>, |
|
1628 nullptr, /* call */ |
|
1629 nullptr, /* hasInstance */ |
|
1630 nullptr, /* construct */ |
|
1631 TraceGenerator<StarGeneratorObject>, |
|
1632 JS_NULL_CLASS_SPEC, |
|
1633 { |
|
1634 nullptr, /* outerObject */ |
|
1635 nullptr, /* innerObject */ |
|
1636 iterator_iteratorObject, |
|
1637 } |
|
1638 }; |
|
1639 |
|
1640 /* |
|
1641 * Called from the JSOP_GENERATOR case in the interpreter, with fp referring |
|
1642 * to the frame by which the generator function was activated. Create a new |
|
1643 * JSGenerator object, which contains its own InterpreterFrame that we populate |
|
1644 * from *fp. We know that upon return, the JSOP_GENERATOR opcode will return |
|
1645 * from the activation in fp, so we can steal away fp->callobj and fp->argsobj |
|
1646 * if they are non-null. |
|
1647 */ |
|
1648 JSObject * |
|
1649 js_NewGenerator(JSContext *cx, const InterpreterRegs &stackRegs) |
|
1650 { |
|
1651 JS_ASSERT(stackRegs.stackDepth() == 0); |
|
1652 InterpreterFrame *stackfp = stackRegs.fp(); |
|
1653 |
|
1654 JS_ASSERT(stackfp->script()->isGenerator()); |
|
1655 |
|
1656 Rooted<GlobalObject*> global(cx, &stackfp->global()); |
|
1657 RootedObject obj(cx); |
|
1658 if (stackfp->script()->isStarGenerator()) { |
|
1659 RootedValue pval(cx); |
|
1660 RootedObject fun(cx, stackfp->fun()); |
|
1661 // FIXME: This would be faster if we could avoid doing a lookup to get |
|
1662 // the prototype for the instance. Bug 906600. |
|
1663 if (!JSObject::getProperty(cx, fun, fun, cx->names().prototype, &pval)) |
|
1664 return nullptr; |
|
1665 JSObject *proto = pval.isObject() ? &pval.toObject() : nullptr; |
|
1666 if (!proto) { |
|
1667 proto = GlobalObject::getOrCreateStarGeneratorObjectPrototype(cx, global); |
|
1668 if (!proto) |
|
1669 return nullptr; |
|
1670 } |
|
1671 obj = NewObjectWithGivenProto(cx, &StarGeneratorObject::class_, proto, global); |
|
1672 } else { |
|
1673 JS_ASSERT(stackfp->script()->isLegacyGenerator()); |
|
1674 JSObject *proto = GlobalObject::getOrCreateLegacyGeneratorObjectPrototype(cx, global); |
|
1675 if (!proto) |
|
1676 return nullptr; |
|
1677 obj = NewObjectWithGivenProto(cx, &LegacyGeneratorObject::class_, proto, global); |
|
1678 } |
|
1679 if (!obj) |
|
1680 return nullptr; |
|
1681 |
|
1682 /* Load and compute stack slot counts. */ |
|
1683 Value *stackvp = stackfp->generatorArgsSnapshotBegin(); |
|
1684 unsigned vplen = stackfp->generatorArgsSnapshotEnd() - stackvp; |
|
1685 |
|
1686 /* Compute JSGenerator size. */ |
|
1687 unsigned nbytes = sizeof(JSGenerator) + |
|
1688 (-1 + /* one Value included in JSGenerator */ |
|
1689 vplen + |
|
1690 VALUES_PER_STACK_FRAME + |
|
1691 stackfp->script()->nslots()) * sizeof(HeapValue); |
|
1692 |
|
1693 JS_ASSERT(nbytes % sizeof(Value) == 0); |
|
1694 JS_STATIC_ASSERT(sizeof(InterpreterFrame) % sizeof(HeapValue) == 0); |
|
1695 |
|
1696 JSGenerator *gen = (JSGenerator *) cx->calloc_(nbytes); |
|
1697 if (!gen) |
|
1698 return nullptr; |
|
1699 |
|
1700 /* Cut up floatingStack space. */ |
|
1701 HeapValue *genvp = gen->stackSnapshot; |
|
1702 SetValueRangeToUndefined((Value *)genvp, vplen); |
|
1703 |
|
1704 InterpreterFrame *genfp = reinterpret_cast<InterpreterFrame *>(genvp + vplen); |
|
1705 |
|
1706 /* Initialize JSGenerator. */ |
|
1707 gen->obj.init(obj); |
|
1708 gen->state = JSGEN_NEWBORN; |
|
1709 gen->fp = genfp; |
|
1710 gen->prevGenerator = nullptr; |
|
1711 |
|
1712 /* Copy from the stack to the generator's floating frame. */ |
|
1713 gen->regs.rebaseFromTo(stackRegs, *genfp); |
|
1714 genfp->copyFrameAndValues<InterpreterFrame::DoPostBarrier>(cx, (Value *)genvp, stackfp, |
|
1715 stackvp, stackRegs.sp); |
|
1716 genfp->setSuspended(); |
|
1717 obj->setPrivate(gen); |
|
1718 return obj; |
|
1719 } |
|
1720 |
|
1721 static void |
|
1722 SetGeneratorClosed(JSContext *cx, JSGenerator *gen); |
|
1723 |
|
1724 typedef enum JSGeneratorOp { |
|
1725 JSGENOP_NEXT, |
|
1726 JSGENOP_SEND, |
|
1727 JSGENOP_THROW, |
|
1728 JSGENOP_CLOSE |
|
1729 } JSGeneratorOp; |
|
1730 |
|
1731 /* |
|
1732 * Start newborn or restart yielding generator and perform the requested |
|
1733 * operation inside its frame. |
|
1734 */ |
|
1735 static bool |
|
1736 SendToGenerator(JSContext *cx, JSGeneratorOp op, HandleObject obj, |
|
1737 JSGenerator *gen, HandleValue arg, GeneratorKind generatorKind, |
|
1738 MutableHandleValue rval) |
|
1739 { |
|
1740 JS_ASSERT(generatorKind == LegacyGenerator || generatorKind == StarGenerator); |
|
1741 |
|
1742 if (gen->state == JSGEN_RUNNING || gen->state == JSGEN_CLOSING) { |
|
1743 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NESTING_GENERATOR); |
|
1744 return false; |
|
1745 } |
|
1746 |
|
1747 JSGeneratorState futureState; |
|
1748 JS_ASSERT(gen->state == JSGEN_NEWBORN || gen->state == JSGEN_OPEN); |
|
1749 switch (op) { |
|
1750 case JSGENOP_NEXT: |
|
1751 case JSGENOP_SEND: |
|
1752 if (gen->state == JSGEN_OPEN) { |
|
1753 /* |
|
1754 * Store the argument to send as the result of the yield |
|
1755 * expression. The generator stack is not barriered, so we need |
|
1756 * write barriers here. |
|
1757 */ |
|
1758 HeapValue::writeBarrierPre(gen->regs.sp[-1]); |
|
1759 gen->regs.sp[-1] = arg; |
|
1760 HeapValue::writeBarrierPost(cx->runtime(), gen->regs.sp[-1], &gen->regs.sp[-1]); |
|
1761 } |
|
1762 futureState = JSGEN_RUNNING; |
|
1763 break; |
|
1764 |
|
1765 case JSGENOP_THROW: |
|
1766 cx->setPendingException(arg); |
|
1767 futureState = JSGEN_RUNNING; |
|
1768 break; |
|
1769 |
|
1770 default: |
|
1771 JS_ASSERT(op == JSGENOP_CLOSE); |
|
1772 JS_ASSERT(generatorKind == LegacyGenerator); |
|
1773 cx->setPendingException(MagicValue(JS_GENERATOR_CLOSING)); |
|
1774 futureState = JSGEN_CLOSING; |
|
1775 break; |
|
1776 } |
|
1777 |
|
1778 bool ok; |
|
1779 { |
|
1780 GeneratorState state(cx, gen, futureState); |
|
1781 ok = RunScript(cx, state); |
|
1782 if (!ok && gen->state == JSGEN_CLOSED) |
|
1783 return false; |
|
1784 } |
|
1785 |
|
1786 if (gen->fp->isYielding()) { |
|
1787 /* |
|
1788 * Yield is ordinarily infallible, but ok can be false here if a |
|
1789 * Debugger.Frame.onPop hook fails. |
|
1790 */ |
|
1791 JS_ASSERT(gen->state == JSGEN_RUNNING); |
|
1792 JS_ASSERT(op != JSGENOP_CLOSE); |
|
1793 gen->fp->clearYielding(); |
|
1794 gen->state = JSGEN_OPEN; |
|
1795 GeneratorWriteBarrierPost(cx, gen); |
|
1796 rval.set(gen->fp->returnValue()); |
|
1797 return ok; |
|
1798 } |
|
1799 |
|
1800 if (ok) { |
|
1801 if (generatorKind == StarGenerator) { |
|
1802 // Star generators return a {value:FOO, done:true} object. |
|
1803 rval.set(gen->fp->returnValue()); |
|
1804 } else { |
|
1805 JS_ASSERT(generatorKind == LegacyGenerator); |
|
1806 |
|
1807 // Otherwise we discard the return value and throw a StopIteration |
|
1808 // if needed. |
|
1809 rval.setUndefined(); |
|
1810 if (op != JSGENOP_CLOSE) |
|
1811 ok = js_ThrowStopIteration(cx); |
|
1812 } |
|
1813 } |
|
1814 |
|
1815 SetGeneratorClosed(cx, gen); |
|
1816 return ok; |
|
1817 } |
|
1818 |
|
1819 MOZ_ALWAYS_INLINE bool |
|
1820 star_generator_next(JSContext *cx, CallArgs args) |
|
1821 { |
|
1822 RootedObject thisObj(cx, &args.thisv().toObject()); |
|
1823 JSGenerator *gen = thisObj->as<StarGeneratorObject>().getGenerator(); |
|
1824 |
|
1825 if (gen->state == JSGEN_CLOSED) { |
|
1826 RootedObject obj(cx, CreateItrResultObject(cx, JS::UndefinedHandleValue, true)); |
|
1827 if (!obj) |
|
1828 return false; |
|
1829 args.rval().setObject(*obj); |
|
1830 return true; |
|
1831 } |
|
1832 |
|
1833 if (gen->state == JSGEN_NEWBORN && args.hasDefined(0)) { |
|
1834 RootedValue val(cx, args[0]); |
|
1835 js_ReportValueError(cx, JSMSG_BAD_GENERATOR_SEND, |
|
1836 JSDVG_SEARCH_STACK, val, js::NullPtr()); |
|
1837 return false; |
|
1838 } |
|
1839 |
|
1840 return SendToGenerator(cx, JSGENOP_SEND, thisObj, gen, args.get(0), StarGenerator, |
|
1841 args.rval()); |
|
1842 } |
|
1843 |
|
1844 MOZ_ALWAYS_INLINE bool |
|
1845 star_generator_throw(JSContext *cx, CallArgs args) |
|
1846 { |
|
1847 RootedObject thisObj(cx, &args.thisv().toObject()); |
|
1848 |
|
1849 JSGenerator *gen = thisObj->as<StarGeneratorObject>().getGenerator(); |
|
1850 if (gen->state == JSGEN_CLOSED) { |
|
1851 cx->setPendingException(args.get(0)); |
|
1852 return false; |
|
1853 } |
|
1854 |
|
1855 return SendToGenerator(cx, JSGENOP_THROW, thisObj, gen, args.get(0), StarGenerator, |
|
1856 args.rval()); |
|
1857 } |
|
1858 |
|
1859 MOZ_ALWAYS_INLINE bool |
|
1860 legacy_generator_next(JSContext *cx, CallArgs args) |
|
1861 { |
|
1862 RootedObject thisObj(cx, &args.thisv().toObject()); |
|
1863 |
|
1864 JSGenerator *gen = thisObj->as<LegacyGeneratorObject>().getGenerator(); |
|
1865 if (gen->state == JSGEN_CLOSED) |
|
1866 return js_ThrowStopIteration(cx); |
|
1867 |
|
1868 if (gen->state == JSGEN_NEWBORN && args.hasDefined(0)) { |
|
1869 RootedValue val(cx, args[0]); |
|
1870 js_ReportValueError(cx, JSMSG_BAD_GENERATOR_SEND, |
|
1871 JSDVG_SEARCH_STACK, val, js::NullPtr()); |
|
1872 return false; |
|
1873 } |
|
1874 |
|
1875 return SendToGenerator(cx, JSGENOP_SEND, thisObj, gen, args.get(0), LegacyGenerator, |
|
1876 args.rval()); |
|
1877 } |
|
1878 |
|
1879 MOZ_ALWAYS_INLINE bool |
|
1880 legacy_generator_throw(JSContext *cx, CallArgs args) |
|
1881 { |
|
1882 RootedObject thisObj(cx, &args.thisv().toObject()); |
|
1883 |
|
1884 JSGenerator *gen = thisObj->as<LegacyGeneratorObject>().getGenerator(); |
|
1885 if (gen->state == JSGEN_CLOSED) { |
|
1886 cx->setPendingException(args.length() >= 1 ? args[0] : UndefinedValue()); |
|
1887 return false; |
|
1888 } |
|
1889 |
|
1890 return SendToGenerator(cx, JSGENOP_THROW, thisObj, gen, args.get(0), LegacyGenerator, |
|
1891 args.rval()); |
|
1892 } |
|
1893 |
|
1894 static bool |
|
1895 CloseLegacyGenerator(JSContext *cx, HandleObject obj, MutableHandleValue rval) |
|
1896 { |
|
1897 JS_ASSERT(obj->is<LegacyGeneratorObject>()); |
|
1898 |
|
1899 JSGenerator *gen = obj->as<LegacyGeneratorObject>().getGenerator(); |
|
1900 |
|
1901 if (gen->state == JSGEN_CLOSED) { |
|
1902 rval.setUndefined(); |
|
1903 return true; |
|
1904 } |
|
1905 |
|
1906 if (gen->state == JSGEN_NEWBORN) { |
|
1907 SetGeneratorClosed(cx, gen); |
|
1908 rval.setUndefined(); |
|
1909 return true; |
|
1910 } |
|
1911 |
|
1912 return SendToGenerator(cx, JSGENOP_CLOSE, obj, gen, JS::UndefinedHandleValue, LegacyGenerator, |
|
1913 rval); |
|
1914 } |
|
1915 |
|
1916 static bool |
|
1917 CloseLegacyGenerator(JSContext *cx, HandleObject obj) |
|
1918 { |
|
1919 RootedValue rval(cx); |
|
1920 return CloseLegacyGenerator(cx, obj, &rval); |
|
1921 } |
|
1922 |
|
1923 MOZ_ALWAYS_INLINE bool |
|
1924 legacy_generator_close(JSContext *cx, CallArgs args) |
|
1925 { |
|
1926 RootedObject thisObj(cx, &args.thisv().toObject()); |
|
1927 |
|
1928 return CloseLegacyGenerator(cx, thisObj, args.rval()); |
|
1929 } |
|
1930 |
|
1931 template<typename T> |
|
1932 MOZ_ALWAYS_INLINE bool |
|
1933 IsObjectOfType(HandleValue v) |
|
1934 { |
|
1935 return v.isObject() && v.toObject().is<T>(); |
|
1936 } |
|
1937 |
|
1938 template<typename T, NativeImpl Impl> |
|
1939 static bool |
|
1940 NativeMethod(JSContext *cx, unsigned argc, Value *vp) |
|
1941 { |
|
1942 CallArgs args = CallArgsFromVp(argc, vp); |
|
1943 return CallNonGenericMethod<IsObjectOfType<T>, Impl>(cx, args); |
|
1944 } |
|
1945 |
|
1946 #define JSPROP_ROPERM (JSPROP_READONLY | JSPROP_PERMANENT) |
|
1947 #define JS_METHOD(name, T, impl, len, attrs) JS_FN(name, (NativeMethod<T,impl>), len, attrs) |
|
1948 |
|
1949 static const JSFunctionSpec star_generator_methods[] = { |
|
1950 JS_SELF_HOSTED_FN("@@iterator", "IteratorIdentity", 0, 0), |
|
1951 JS_METHOD("next", StarGeneratorObject, star_generator_next, 1, 0), |
|
1952 JS_METHOD("throw", StarGeneratorObject, star_generator_throw, 1, 0), |
|
1953 JS_FS_END |
|
1954 }; |
|
1955 |
|
1956 static const JSFunctionSpec legacy_generator_methods[] = { |
|
1957 JS_SELF_HOSTED_FN("@@iterator", "LegacyGeneratorIteratorShim", 0, 0), |
|
1958 // "send" is an alias for "next". |
|
1959 JS_METHOD("next", LegacyGeneratorObject, legacy_generator_next, 1, JSPROP_ROPERM), |
|
1960 JS_METHOD("send", LegacyGeneratorObject, legacy_generator_next, 1, JSPROP_ROPERM), |
|
1961 JS_METHOD("throw", LegacyGeneratorObject, legacy_generator_throw, 1, JSPROP_ROPERM), |
|
1962 JS_METHOD("close", LegacyGeneratorObject, legacy_generator_close, 0, JSPROP_ROPERM), |
|
1963 JS_FS_END |
|
1964 }; |
|
1965 |
|
1966 static JSObject* |
|
1967 NewSingletonObjectWithObjectPrototype(JSContext *cx, Handle<GlobalObject *> global) |
|
1968 { |
|
1969 JSObject *proto = global->getOrCreateObjectPrototype(cx); |
|
1970 if (!proto) |
|
1971 return nullptr; |
|
1972 return NewObjectWithGivenProto(cx, &JSObject::class_, proto, global, SingletonObject); |
|
1973 } |
|
1974 |
|
1975 static JSObject* |
|
1976 NewSingletonObjectWithFunctionPrototype(JSContext *cx, Handle<GlobalObject *> global) |
|
1977 { |
|
1978 JSObject *proto = global->getOrCreateFunctionPrototype(cx); |
|
1979 if (!proto) |
|
1980 return nullptr; |
|
1981 return NewObjectWithGivenProto(cx, &JSObject::class_, proto, global, SingletonObject); |
|
1982 } |
|
1983 |
|
1984 /* static */ bool |
|
1985 GlobalObject::initIteratorClasses(JSContext *cx, Handle<GlobalObject *> global) |
|
1986 { |
|
1987 RootedObject iteratorProto(cx); |
|
1988 Value iteratorProtoVal = global->getPrototype(JSProto_Iterator); |
|
1989 if (iteratorProtoVal.isObject()) { |
|
1990 iteratorProto = &iteratorProtoVal.toObject(); |
|
1991 } else { |
|
1992 iteratorProto = global->createBlankPrototype(cx, &PropertyIteratorObject::class_); |
|
1993 if (!iteratorProto) |
|
1994 return false; |
|
1995 |
|
1996 AutoIdVector blank(cx); |
|
1997 NativeIterator *ni = NativeIterator::allocateIterator(cx, 0, blank); |
|
1998 if (!ni) |
|
1999 return false; |
|
2000 ni->init(nullptr, nullptr, 0 /* flags */, 0, 0); |
|
2001 |
|
2002 iteratorProto->as<PropertyIteratorObject>().setNativeIterator(ni); |
|
2003 |
|
2004 Rooted<JSFunction*> ctor(cx); |
|
2005 ctor = global->createConstructor(cx, IteratorConstructor, cx->names().Iterator, 2); |
|
2006 if (!ctor) |
|
2007 return false; |
|
2008 if (!LinkConstructorAndPrototype(cx, ctor, iteratorProto)) |
|
2009 return false; |
|
2010 if (!DefinePropertiesAndBrand(cx, iteratorProto, nullptr, iterator_methods)) |
|
2011 return false; |
|
2012 if (!GlobalObject::initBuiltinConstructor(cx, global, JSProto_Iterator, |
|
2013 ctor, iteratorProto)) |
|
2014 { |
|
2015 return false; |
|
2016 } |
|
2017 } |
|
2018 |
|
2019 RootedObject proto(cx); |
|
2020 if (global->getSlot(ARRAY_ITERATOR_PROTO).isUndefined()) { |
|
2021 const Class *cls = &ArrayIteratorObject::class_; |
|
2022 proto = global->createBlankPrototypeInheriting(cx, cls, *iteratorProto); |
|
2023 if (!proto || !DefinePropertiesAndBrand(cx, proto, nullptr, array_iterator_methods)) |
|
2024 return false; |
|
2025 global->setReservedSlot(ARRAY_ITERATOR_PROTO, ObjectValue(*proto)); |
|
2026 } |
|
2027 |
|
2028 if (global->getSlot(STRING_ITERATOR_PROTO).isUndefined()) { |
|
2029 const Class *cls = &StringIteratorPrototypeClass; |
|
2030 proto = global->createBlankPrototype(cx, cls); |
|
2031 if (!proto || !DefinePropertiesAndBrand(cx, proto, nullptr, string_iterator_methods)) |
|
2032 return false; |
|
2033 global->setReservedSlot(STRING_ITERATOR_PROTO, ObjectValue(*proto)); |
|
2034 } |
|
2035 |
|
2036 if (global->getSlot(LEGACY_GENERATOR_OBJECT_PROTO).isUndefined()) { |
|
2037 proto = NewSingletonObjectWithObjectPrototype(cx, global); |
|
2038 if (!proto || !DefinePropertiesAndBrand(cx, proto, nullptr, legacy_generator_methods)) |
|
2039 return false; |
|
2040 global->setReservedSlot(LEGACY_GENERATOR_OBJECT_PROTO, ObjectValue(*proto)); |
|
2041 } |
|
2042 |
|
2043 if (global->getSlot(STAR_GENERATOR_OBJECT_PROTO).isUndefined()) { |
|
2044 RootedObject genObjectProto(cx, NewSingletonObjectWithObjectPrototype(cx, global)); |
|
2045 if (!genObjectProto) |
|
2046 return false; |
|
2047 if (!DefinePropertiesAndBrand(cx, genObjectProto, nullptr, star_generator_methods)) |
|
2048 return false; |
|
2049 |
|
2050 RootedObject genFunctionProto(cx, NewSingletonObjectWithFunctionPrototype(cx, global)); |
|
2051 if (!genFunctionProto) |
|
2052 return false; |
|
2053 if (!LinkConstructorAndPrototype(cx, genFunctionProto, genObjectProto)) |
|
2054 return false; |
|
2055 |
|
2056 RootedValue function(cx, global->getConstructor(JSProto_Function)); |
|
2057 if (!function.toObjectOrNull()) |
|
2058 return false; |
|
2059 RootedAtom name(cx, cx->names().GeneratorFunction); |
|
2060 RootedObject genFunction(cx, NewFunctionWithProto(cx, NullPtr(), Generator, 1, |
|
2061 JSFunction::NATIVE_CTOR, global, name, |
|
2062 &function.toObject())); |
|
2063 if (!genFunction) |
|
2064 return false; |
|
2065 if (!LinkConstructorAndPrototype(cx, genFunction, genFunctionProto)) |
|
2066 return false; |
|
2067 |
|
2068 global->setSlot(STAR_GENERATOR_OBJECT_PROTO, ObjectValue(*genObjectProto)); |
|
2069 global->setConstructor(JSProto_GeneratorFunction, ObjectValue(*genFunction)); |
|
2070 global->setPrototype(JSProto_GeneratorFunction, ObjectValue(*genFunctionProto)); |
|
2071 } |
|
2072 |
|
2073 if (global->getPrototype(JSProto_StopIteration).isUndefined()) { |
|
2074 proto = global->createBlankPrototype(cx, &StopIterationObject::class_); |
|
2075 if (!proto || !JSObject::freeze(cx, proto)) |
|
2076 return false; |
|
2077 |
|
2078 // This should use a non-JSProtoKey'd slot, but this is easier for now. |
|
2079 if (!GlobalObject::initBuiltinConstructor(cx, global, JSProto_StopIteration, proto, proto)) |
|
2080 return false; |
|
2081 |
|
2082 global->setConstructor(JSProto_StopIteration, ObjectValue(*proto)); |
|
2083 } |
|
2084 |
|
2085 return true; |
|
2086 } |
|
2087 |
|
2088 JSObject * |
|
2089 js_InitIteratorClasses(JSContext *cx, HandleObject obj) |
|
2090 { |
|
2091 Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>()); |
|
2092 if (!GlobalObject::initIteratorClasses(cx, global)) |
|
2093 return nullptr; |
|
2094 return global->getIteratorPrototype(); |
|
2095 } |