Add sub-controls for Hack array compat runtime checks
[hiphop-php.git] / hphp / runtime / base / array-iterator.h
blob4aad220fd3f31e1ff0124abb5ea76f88889129d3
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
17 #ifndef incl_HPHP_ARRAY_ITERATOR_H_
18 #define incl_HPHP_ARRAY_ITERATOR_H_
20 #include <array>
21 #include <cstdint>
23 #include "hphp/runtime/base/array-data-defs.h"
24 #include "hphp/runtime/base/collections.h"
25 #include "hphp/runtime/base/packed-array.h"
26 #include "hphp/runtime/base/packed-array-defs.h"
27 #include "hphp/runtime/base/mixed-array.h"
28 #include "hphp/runtime/base/member-val.h"
29 #include "hphp/runtime/base/set-array.h"
30 #include "hphp/runtime/base/req-containers.h"
31 #include "hphp/runtime/base/req-ptr.h"
32 #include "hphp/runtime/base/type-variant.h"
33 #include "hphp/util/tls-pod-bag.h"
34 #include "hphp/util/type-scan.h"
36 namespace HPHP {
37 ///////////////////////////////////////////////////////////////////////////////
39 struct TypedValue;
40 struct Iter;
41 struct MixedArray;
43 enum class IterNextIndex : uint16_t {
44 ArrayPacked = 0,
45 ArrayMixed,
46 Array,
47 Object,
50 /**
51 * An iteration normally looks like this:
53 * for (ArrayIter iter(data); iter; ++iter) {
54 * ...
55 * }
58 /**
59 * Iterator for an immutable array.
61 struct ArrayIter {
62 enum Type : uint16_t {
63 TypeUndefined = 0,
64 TypeArray,
65 TypeIterator // for objects that implement Iterator or
66 // IteratorAggregate
69 enum NoInc { noInc = 0 };
72 * Constructors. Note that sometimes ArrayIter objects are created
73 * without running their C++ constructor. (See new_iter_array.)
75 ArrayIter() {
76 m_data = nullptr;
78 explicit ArrayIter(const ArrayData* data);
79 ArrayIter(const ArrayData* data, NoInc) {
80 setArrayData(data);
81 if (data) {
82 m_pos = data->iter_begin();
85 explicit ArrayIter(const MixedArray*) = delete;
86 explicit ArrayIter(const Array& array);
87 explicit ArrayIter(ObjectData* obj);
88 ArrayIter(ObjectData* obj, NoInc);
89 explicit ArrayIter(const Object& obj);
90 explicit ArrayIter(Cell);
91 explicit ArrayIter(const Variant& v);
93 // Copy ctor
94 ArrayIter(const ArrayIter& iter);
96 // Move ctor
97 ArrayIter(ArrayIter&& iter) noexcept {
98 m_data = iter.m_data;
99 m_pos = iter.m_pos;
100 m_itype = iter.m_itype;
101 m_nextHelperIdx = iter.m_nextHelperIdx;
102 iter.m_data = nullptr;
105 // Copy assignment
106 ArrayIter& operator=(const ArrayIter& iter);
108 // Move assignment
109 ArrayIter& operator=(ArrayIter&& iter);
111 // Destructor
112 ~ArrayIter() {
113 destruct();
116 void reset() {
117 destruct();
118 m_data = nullptr;
121 explicit operator bool() { return !end(); }
122 void operator++() { next(); }
123 bool end() const {
124 if (LIKELY(hasArrayData())) {
125 auto* ad = getArrayData();
126 return !ad || m_pos == ad->iter_end();
128 return endHelper();
130 bool endHelper() const;
132 void next() {
133 if (LIKELY(hasArrayData())) {
134 const ArrayData* ad = getArrayData();
135 assert(ad);
136 assert(m_pos != ad->iter_end());
137 m_pos = ad->iter_advance(m_pos);
138 return;
140 nextHelper();
142 void nextHelper();
144 Variant first() {
145 if (LIKELY(hasArrayData())) {
146 const ArrayData* ad = getArrayData();
147 assert(ad);
148 assert(m_pos != ad->iter_end());
149 return ad->getKey(m_pos);
151 return firstHelper();
153 Variant firstHelper();
155 TypedValue nvFirst() {
156 auto const ad = getArrayData();
157 assert(ad && m_pos != ad->iter_end());
158 return ad->nvGetKey(m_pos);
162 * Retrieve the value at the current position.
164 Variant second();
167 * Get a member_rval for the current iterator position.
169 * The difference between secondRval and secondRvalPlus is that, if called
170 * when iterating an Iterable object the former will fatal and the latter
171 * will throw (whereas second will invoke the current() method on the
172 * Iterable object). Why this is has been lost in the mists of time.
174 member_rval secondRval() const;
175 member_rval secondRvalPlus();
177 TypedValue secondVal() const { return secondRval().tv(); }
178 TypedValue secondValPlus() { return secondRvalPlus().tv(); }
180 const Variant& secondRef() const {
181 return tvAsCVarRef(secondRval().tv_ptr());
184 // Inline version of secondRef. Only for use in iterator helpers.
185 member_rval nvSecond() const {
186 auto const ad = getArrayData();
187 assert(ad && m_pos != ad->iter_end());
188 return ad->rvalPos(m_pos);
191 bool hasArrayData() const {
192 return !((intptr_t)m_data & 1);
195 const ArrayData* getArrayData() const {
196 assert(hasArrayData());
197 return m_data;
199 ssize_t getPos() {
200 return m_pos;
202 void setPos(ssize_t newPos) {
203 m_pos = newPos;
205 void advance(ssize_t count) {
206 while (!end() && count--) {
207 next();
210 void rewind();
211 Type getIterType() const {
212 return m_itype;
214 void setIterType(Type iterType) {
215 m_itype = iterType;
218 IterNextIndex getHelperIndex() {
219 return m_nextHelperIdx;
222 ObjectData* getObject() const {
223 assert(!hasArrayData());
224 return (ObjectData*)((intptr_t)m_obj & ~1);
227 private:
228 friend int64_t new_iter_array(Iter*, ArrayData*, TypedValue*);
229 template<bool withRef>
230 friend int64_t new_iter_array_key(Iter*, ArrayData*, TypedValue*,
231 TypedValue*);
233 void arrInit(const ArrayData* arr);
235 template <bool incRef>
236 void objInit(ObjectData* obj);
238 void cellInit(Cell);
240 void destruct();
242 void setArrayData(const ArrayData* ad) {
243 assert((intptr_t(ad) & 1) == 0);
244 m_data = ad;
245 m_nextHelperIdx = IterNextIndex::ArrayMixed;
246 if (ad != nullptr) {
247 if (ad->hasPackedLayout()) {
248 m_nextHelperIdx = IterNextIndex::ArrayPacked;
249 } else if (!ad->hasMixedLayout()) {
250 m_nextHelperIdx = IterNextIndex::Array;
255 void setObject(ObjectData* obj) {
256 assert((intptr_t(obj) & 1) == 0);
257 m_obj = (ObjectData*)((intptr_t)obj | 1);
258 m_nextHelperIdx = IterNextIndex::Object;
261 union {
262 const ArrayData* m_data;
263 ObjectData* m_obj;
265 public:
266 // m_pos is used by the array implementation to track the current position
267 // in the array. Beware that when m_data is null, m_pos is uninitialized.
268 ssize_t m_pos;
269 private:
270 // we don't use this pointer, but it gives ArrayIter the same layout
271 // as MArrayIter and CufIter, allowing Iter to be scanned without a union
272 // descriminator.
273 MaybeCountable* m_unused;
274 UNUSED int m_alsoUnused;
275 // This is unioned so new_iter_array can initialize it more
276 // efficiently.
277 union {
278 struct {
279 Type m_itype;
280 IterNextIndex m_nextHelperIdx;
282 uint32_t m_itypeAndNextHelperIdx;
285 friend struct Iter;
288 ///////////////////////////////////////////////////////////////////////////////
291 * MArrayIter provides the necessary functionality for supporting
292 * "foreach by reference" (also called "strong foreach").
294 * In the common case, a MArrayIter is bound to a RefData when it is
295 * initialized. When iterating objects with foreach by reference, a
296 * MArrayIter may instead be bound directly to an array which m_data
297 * points to. (This is because the array is created as a temporary.)
299 * Foreach by reference is a pain. Iteration needs to be robust in
300 * the face of two challenges: (1) the case where an element is unset
301 * during iteration, and (2) the case where user code modifies the
302 * RefData to be a different array or a non-array value. In such
303 * cases, we should never crash and ideally when an element is unset
304 * we should be able to keep track of where we are in the array.
306 * MArrayIter works by "registering" itself with the array being
307 * iterated over, in a way that any array can find out all active
308 * MArrayIters associated with it (if any). See MIterTable below.
310 * Using this association, when an array mutation occurs, if there are
311 * active MArrayIters the array will update them to ensure they behave
312 * coherently. For example, if an element is unset, the MArrayIter's
313 * that were pointing to that element are moved to point to the
314 * element before the element being unset.
316 * Note that it is possible for an iterator to point to the position
317 * before the first element (this is what the "reset" flag is for).
319 * MArrayIter has also has a m_container field to keep track of which
320 * array it has "registered" itself with. By comparing the array
321 * pointed to through m_ref with the array pointed to by m_container,
322 * MArrayIter can detect if user code has modified the inner cell to
323 * be a different array or a non-array value. When this happens, the
324 * MArrayIter unregisters itself with the old array (pointed to by
325 * m_container) and registers itself with the new array (pointed to by
326 * m_ref->tv().m_data.parr) and resumes iteration at the position
327 * pointed to by the new array's internal cursor (ArrayData::m_pos).
328 * If m_ref points to a non-array value, iteration terminates.
330 struct MArrayIter {
331 explicit MArrayIter(RefData* ref);
332 explicit MArrayIter(ArrayData* data);
333 ~MArrayIter();
335 MArrayIter(const MArrayIter&) = delete;
336 MArrayIter& operator=(const MArrayIter&) = delete;
339 * It is only safe to call key() and val() if all of the following
340 * conditions are met:
341 * 1) The calls to key() and/or val() are immediately preceded by
342 * a call to advance(), prepare(), or end().
343 * 2) The iterator points to a valid position in the array.
345 Variant key() {
346 ArrayData* data = getArray();
347 assert(data && data == getContainer());
348 assert(!getResetFlag() && data->validMArrayIter(*this));
349 return data->getKey(m_pos);
352 Variant& val() {
353 ArrayData* data = getArray();
354 assert(data && data == getContainer());
355 assert(!data->cowCheck() || data->noCopyOnWrite());
356 assert(!getResetFlag());
357 assert(data->validMArrayIter(*this));
358 // Normally it's not ok to modify the return value of rvalPos,
359 // but the whole point of mutable array iteration is that this is
360 // allowed, so this const_cast is not actually evil.
361 // TODO(#9077255): Use member_lval for this somehow.
362 return tvAsVariant(const_cast<TypedValue*>(
363 data->rvalPos(m_pos).tv_ptr()
367 void release() { delete this; }
369 // Returns true if the iterator points past the last element (or if
370 // it points before the first element)
371 bool end() const;
373 // Move the iterator forward one element
374 bool advance();
376 // Returns true if the iterator points to a valid element
377 bool prepare();
379 ArrayData* getArray() const {
380 return hasRef() ? getData() : getAd();
383 bool hasRef() const {
384 return m_ref && !(intptr_t(m_ref) & 1LL);
386 bool hasAd() const {
387 return bool(intptr_t(m_data) & 1LL);
389 RefData* getRef() const {
390 assert(hasRef());
391 return m_ref;
393 ArrayData* getAd() const {
394 assert(hasAd());
395 return (ArrayData*)(intptr_t(m_data) & ~1LL);
397 void setRef(RefData* ref) {
398 m_ref = ref;
400 void setAd(ArrayData* val) {
401 m_data = (ArrayData*)(intptr_t(val) | 1LL);
403 ArrayData* getContainer() const {
404 return m_container;
406 void setContainer(ArrayData* arr) {
407 m_container = arr;
410 bool getResetFlag() const { return m_resetFlag; }
411 void setResetFlag(bool reset) { m_resetFlag = reset; }
413 private:
414 ArrayData* getData() const {
415 assert(hasRef());
416 return isArrayType(m_ref->tv()->m_type)
417 ? m_ref->tv()->m_data.parr
418 : nullptr;
421 ArrayData* cowCheck();
422 void escalateCheck();
423 ArrayData* reregister();
425 private:
427 * m_ref/m_data are used to keep track of the array that we're
428 * supposed to be iterating over. The low bit is used to indicate
429 * whether we are using m_ref or m_data.
431 * Mutable array iteration usually iterates over m_ref---the m_data
432 * case here occurs is when we've converted an object to an array
433 * before iterating it (and this MArrayIter object actually owns a
434 * temporary array).
436 union {
437 RefData* m_ref;
438 ArrayData* m_data;
440 public:
441 // m_pos is used by the array implementation to track the current position
442 // in the array.
443 ssize_t m_pos;
444 private:
445 // m_container keeps track of which array we're "registered" with. Normally
446 // getArray() and m_container refer to the same array. However, the two may
447 // differ in cases where user code has modified the inner cell to be a
448 // different array or non-array value.
449 ArrayData* m_container;
450 // The m_resetFlag is used to indicate a mutable array iterator is
451 // "before the first" position in the array.
452 UNUSED uint32_t m_unused;
453 uint32_t m_resetFlag;
454 friend struct Iter;
457 //////////////////////////////////////////////////////////////////////
460 * Active mutable iterators are associated with their corresponding
461 * arrays using a table in thread local storage. The iterators
462 * themselves can find their registered container using their
463 * m_container pointer, but arrays must linearly search this table to
464 * go the other direction.
466 * This scheme is optimized for the overwhelmingly common case that
467 * there are no active mutable array iterations in the whole request.
468 * When there are active mutable iterators, it is also overwhelmingly
469 * the case that there is only one, and on real applications exceeding
470 * 4 or 5 simultaneously is apparently rare.
472 * This table has the following semantics:
474 * o If there are any 'active' MArrayIters (i.e. ones that are
475 * actually associated with arrays), one of them will be present
476 * in the first Ent slot in this table. This is so that any array
477 * can check that there are no active MArrayIters just by
478 * comparing the first slot of this table with null. (See
479 * strong_iterators_exist().)
481 * o Secondly we expect that we essentially never exceed a small
482 * number iterators (outside of code specifically designed to
483 * stress mutable array iteration). We've chosen 7 preallocated
484 * slots because it fills out two cache lines, and we've observed
485 * 4 or 5 occasionally in some real programs. If there are
486 * actually more live than 7, we allocate additional space and
487 * point to it with 'extras'.
489 * o The entries in this table (including 'extras') are not
490 * guaranteed to be contiguous. Empty entries may be present in
491 * the middle, and there is no ordering.
493 * o If an entry has a non-null array pointer, it must have a
494 * non-null iter pointer. Checking either one for null are both
495 * valid ways to check if a slot is empty.
497 struct MIterTable {
498 struct Ent {
499 ArrayData* array;
500 MArrayIter* iter;
503 static void clear();
505 static constexpr int ents_size = 7;
506 std::array<Ent, ents_size> ents;
507 // Slow path: we expect this `extras' list to rarely be allocated.
508 TlsPodBag<Ent,req::Allocator<Ent>> extras;
510 static_assert(sizeof(MIterTable) == 2*64, "want multiple of cache line size");
511 extern THREAD_LOCAL_FLAT(MIterTable, tl_miter_table);
513 void free_strong_iterators(ArrayData*);
514 void move_strong_iterators(ArrayData* dest, ArrayData* src);
515 bool has_strong_iterator(ArrayData*);
516 void reset_strong_iterators(ArrayData* ad);
518 //////////////////////////////////////////////////////////////////////
520 struct CufIter {
521 CufIter() : m_obj_or_cls(nullptr), m_func(nullptr),
522 m_name(nullptr), m_dynamic{false} {}
523 ~CufIter();
524 const Func* func() const { return m_func; }
525 void* ctx() const { return m_obj_or_cls; }
526 StringData* name() const { return m_name; }
527 bool dynamic() const { return m_dynamic; }
529 void setFunc(const Func* f) { m_func = f; }
530 void setCtx(ObjectData* obj) { m_obj_or_cls = obj; }
531 void setCtx(const Class* cls) {
532 m_obj_or_cls = !cls ? nullptr :
533 reinterpret_cast<ObjectData*>((char*)cls + 1);
535 void setName(StringData* name) { m_name = name; }
536 void setDynamic(bool dynamic) { m_dynamic = dynamic; }
538 static constexpr uint32_t funcOff() { return offsetof(CufIter, m_func); }
539 static constexpr uint32_t ctxOff() { return offsetof(CufIter, m_obj_or_cls); }
540 static constexpr uint32_t nameOff() { return offsetof(CufIter, m_name); }
541 static constexpr uint32_t dynamicOff() {
542 return offsetof(CufIter, m_dynamic);
545 private:
546 ObjectData* m_obj_or_cls; // maybe a Class* if lsb set.
547 const Func* m_func;
548 StringData* m_name;
549 bool m_dynamic;
550 friend struct Iter;
553 struct alignas(16) Iter {
554 const ArrayIter& arr() const { return m_u.aiter; }
555 const MArrayIter& marr() const { return m_u.maiter; }
556 const CufIter& cuf() const { return m_u.cufiter; }
557 ArrayIter& arr() { return m_u.aiter; }
558 MArrayIter& marr() { return m_u.maiter; }
559 CufIter& cuf() { return m_u.cufiter; }
561 bool init(TypedValue* c1);
562 bool next();
563 void free();
564 void mfree();
565 void cfree();
567 private:
568 // ArrayIter, MArrayIter, and CufIter all declare pointers at the
569 // same offsets, allowing gen-type-scanners to generate a scanner
570 // automatically, for the union. If the layouts become incompatible,
571 // gen-type-scanners will report a build-time error.
572 union Data {
573 Data() {}
574 ArrayIter aiter;
575 MArrayIter maiter;
576 CufIter cufiter;
577 } m_u;
580 //////////////////////////////////////////////////////////////////////
581 // Template based iteration, bypassing ArrayIter where possible
584 * Iterate the values of the iterable 'it'.
586 * If it is a collection, preCollFn will be called first, with the ObjectData
587 * as a parameter. If it returns true, no further iteration will be performed.
588 * This allows for certain optimizations - see eg BaseSet::addAll. Otherwise...
590 * If its an array or a collection, the ArrayData is passed to preArrFn, which
591 * can do any necessary setup, and as with preCollFn can return true to bypass
592 * any further work. Otherwise...
594 * The array is iterated efficiently (without ArrayIter for MixedArray,
595 * PackedArray, and SetArray), and ArrFn is called for each element.
596 * Otherwise...
598 * If its an iterable object, the object is iterated using ArrayIter, and
599 * objFn is called on each element. Otherwise...
601 * If none of the above apply, the function returns false.
603 * During iteration, if objFn or arrFn returns true, iteration stops.
605 * There are also two supported shortcuts:
606 * If ObjFn is a bool, and 'it' is not an array, and not a collection,
607 * IterateV will do nothing, and return the value of objFn.
609 * If PreCollFn is a bool, and 'it' is not an array, IterateV will do nothing,
610 * and return the value of preCollFn.
612 * There are overloads that take 4 and 3 arguments respectively, that pass
613 * false for the trailing arguments as a convenience.
616 // Overload for the case where we already know we have an array
617 template <typename ArrFn, bool IncRef = true>
618 bool IterateV(const ArrayData* adata, ArrFn arrFn) {
619 if (adata->empty()) return true;
620 if (adata->hasPackedLayout()) {
621 PackedArray::IterateV<ArrFn, IncRef>(adata, arrFn);
622 } else if (adata->hasMixedLayout()) {
623 MixedArray::IterateV<ArrFn, IncRef>(MixedArray::asMixed(adata), arrFn);
624 } else if (adata->isKeyset()) {
625 SetArray::Iterate<ArrFn, IncRef>(SetArray::asSet(adata), arrFn);
626 } else {
627 for (ArrayIter iter(adata); iter; ++iter) {
628 if (ArrayData::call_helper(arrFn, iter.secondVal())) {
629 break;
633 return true;
636 template <typename PreArrFn, typename ArrFn, typename PreCollFn, typename ObjFn>
637 bool IterateV(const TypedValue& it,
638 PreArrFn preArrFn,
639 ArrFn arrFn,
640 PreCollFn preCollFn,
641 ObjFn objFn) {
642 assert(it.m_type != KindOfRef);
643 ArrayData* adata;
644 if (LIKELY(isArrayLikeType(it.m_type))) {
645 adata = it.m_data.parr;
646 do_array:
647 adata->incRefCount();
648 SCOPE_EXIT { decRefArr(adata); };
649 if (ArrayData::call_helper(preArrFn, adata)) return true;
650 return IterateV<ArrFn, false>(adata, arrFn);
652 if (std::is_same<PreCollFn, bool>::value) {
653 return ArrayData::call_helper(preCollFn, nullptr);
655 if (it.m_type != KindOfObject) return false;
656 auto odata = it.m_data.pobj;
657 if (odata->isCollection()) {
658 if (ArrayData::call_helper(preCollFn, odata)) return true;
659 adata = collections::asArray(odata);
660 if (adata) goto do_array;
661 assert(odata->collectionType() == CollectionType::Pair);
662 auto tv = make_tv<KindOfInt64>(0);
663 if (!ArrayData::call_helper(arrFn, *collections::at(odata, &tv))) {
664 tv.m_data.num = 1;
665 ArrayData::call_helper(arrFn, *collections::at(odata, &tv));
667 return true;
669 if (std::is_same<ObjFn, bool>::value) {
670 return ArrayData::call_helper(objFn, nullptr);
672 bool isIterable;
673 Object iterable = odata->iterableObject(isIterable);
674 if (!isIterable) return false;
675 for (ArrayIter iter(iterable.detach(), ArrayIter::noInc); iter; ++iter) {
676 if (ArrayData::call_helper(objFn, iter.second().asTypedValue())) break;
678 return true;
681 template <typename PreArrFn, typename ArrFn, typename PreCollFn>
682 bool IterateV(const TypedValue& it,
683 PreArrFn preArrFn,
684 ArrFn arrFn,
685 PreCollFn preCollFn) {
686 return IterateV(it, preArrFn, arrFn, preCollFn, false);
689 template <typename PreArrFn, typename ArrFn>
690 bool IterateV(const TypedValue& it,
691 PreArrFn preArrFn,
692 ArrFn arrFn) {
693 return IterateV(it, preArrFn, arrFn, false);
697 * Iterate the keys and values of the iterable 'it'.
699 * The behavior is identical to that of IterateV, except the ArrFn and ObjFn
700 * callbacks are called with both a key and a value.
703 // Overload for the case where we already know we have an array
704 template <typename ArrFn, bool IncRef = true>
705 bool IterateKV(const ArrayData* adata, ArrFn arrFn) {
706 if (adata->empty()) return true;
707 if (adata->hasMixedLayout()) {
708 MixedArray::IterateKV<ArrFn, IncRef>(MixedArray::asMixed(adata), arrFn);
709 } else if (adata->hasPackedLayout()) {
710 PackedArray::IterateKV<ArrFn, IncRef>(adata, arrFn);
711 } else if (adata->isKeyset()) {
712 auto fun = [&](TypedValue v) { return arrFn(v, v); };
713 SetArray::Iterate<decltype(fun), IncRef>(SetArray::asSet(adata), fun);
714 } else {
715 for (ArrayIter iter(adata); iter; ++iter) {
716 if (ArrayData::call_helper(arrFn, iter.nvFirst(), iter.secondVal())) {
717 break;
721 return true;
724 template <typename PreArrFn, typename ArrFn, typename PreCollFn, typename ObjFn>
725 bool IterateKV(const TypedValue& it,
726 PreArrFn preArrFn,
727 ArrFn arrFn,
728 PreCollFn preCollFn,
729 ObjFn objFn) {
730 assert(it.m_type != KindOfRef);
731 ArrayData* adata;
732 if (LIKELY(isArrayLikeType(it.m_type))) {
733 adata = it.m_data.parr;
734 do_array:
735 adata->incRefCount();
736 SCOPE_EXIT { decRefArr(adata); };
737 if (preArrFn(adata)) return true;
738 return IterateKV<ArrFn, false>(adata, arrFn);
740 if (std::is_same<PreCollFn, bool>::value) {
741 return ArrayData::call_helper(preCollFn, nullptr);
743 if (it.m_type != KindOfObject) return false;
744 auto odata = it.m_data.pobj;
745 if (odata->isCollection()) {
746 if (ArrayData::call_helper(preCollFn, odata)) return true;
747 adata = collections::asArray(odata);
748 if (adata) goto do_array;
749 assert(odata->collectionType() == CollectionType::Pair);
750 auto tv = make_tv<KindOfInt64>(0);
751 if (!ArrayData::call_helper(arrFn, tv, *collections::at(odata, &tv))) {
752 tv.m_data.num = 1;
753 ArrayData::call_helper(arrFn, tv, *collections::at(odata, &tv));
755 return true;
757 if (std::is_same<ObjFn, bool>::value) {
758 return ArrayData::call_helper(objFn, nullptr, nullptr);
760 bool isIterable;
761 Object iterable = odata->iterableObject(isIterable);
762 if (!isIterable) return false;
763 for (ArrayIter iter(iterable.detach(), ArrayIter::noInc); iter; ++iter) {
764 if (ArrayData::call_helper(objFn,
765 iter.first().asTypedValue(),
766 iter.second().asTypedValue())) {
767 break;
770 return true;
773 template <typename PreArrFn, typename ArrFn, typename PreCollFn>
774 bool IterateKV(const TypedValue& it,
775 PreArrFn preArrFn,
776 ArrFn arrFn,
777 PreCollFn preCollFn) {
778 return IterateKV(it, preArrFn, arrFn, preCollFn, false);
781 template <typename PreArrFn, typename ArrFn>
782 bool IterateKV(const TypedValue& it,
783 PreArrFn preArrFn,
784 ArrFn arrFn) {
785 return IterateKV(it, preArrFn, arrFn, false);
788 //////////////////////////////////////////////////////////////////////
790 int64_t new_iter_array(Iter* dest, ArrayData* arr, TypedValue* val);
791 template <bool withRef>
792 int64_t new_iter_array_key(Iter* dest, ArrayData* arr, TypedValue* val,
793 TypedValue* key);
794 int64_t new_iter_object(Iter* dest, ObjectData* obj, Class* ctx,
795 TypedValue* val, TypedValue* key);
796 int64_t witer_next_key(Iter* dest, TypedValue* val, TypedValue* key);
799 int64_t new_miter_array_key(Iter* dest, RefData* arr, TypedValue* val,
800 TypedValue* key);
801 int64_t new_miter_object(Iter* dest, RefData* obj, Class* ctx,
802 TypedValue* val, TypedValue* key);
803 int64_t new_miter_other(Iter* dest, RefData* data);
804 int64_t miter_next_key(Iter* dest, TypedValue* val, TypedValue* key);
806 int64_t iter_next_ind(Iter* iter, TypedValue* valOut);
807 int64_t iter_next_key_ind(Iter* iter, TypedValue* valOut, TypedValue* keyOut);
809 //////////////////////////////////////////////////////////////////////
813 #endif // incl_HPHP_ARRAY_ITERATOR_H_