2 +----------------------------------------------------------------------+
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 +----------------------------------------------------------------------+
16 #include "hphp/runtime/base/array-data.h"
20 #include <tbb/concurrent_unordered_set.h>
22 #include "hphp/runtime/base/array-common.h"
23 #include "hphp/runtime/base/array-init.h"
24 #include "hphp/runtime/base/array-iterator.h"
25 #include "hphp/runtime/base/array-provenance.h"
26 #include "hphp/runtime/base/bespoke-array.h"
27 #include "hphp/runtime/base/builtin-functions.h"
28 #include "hphp/runtime/base/comparisons.h"
29 #include "hphp/runtime/base/memory-manager.h"
30 #include "hphp/runtime/base/memory-manager-defs.h"
31 #include "hphp/runtime/base/mixed-array.h"
32 #include "hphp/runtime/base/packed-array.h"
33 #include "hphp/runtime/base/request-info.h"
34 #include "hphp/runtime/base/runtime-option.h"
35 #include "hphp/runtime/base/set-array.h"
36 #include "hphp/runtime/base/variable-serializer.h"
37 #include "hphp/runtime/server/memory-stats.h"
38 #include "hphp/runtime/vm/interp-helpers.h"
40 #include "hphp/util/exception.h"
42 #include "hphp/zend/zend-string.h"
45 ///////////////////////////////////////////////////////////////////////////////
48 s_InvalidKeysetOperationMsg
{"Invalid operation on keyset"},
49 s_VarrayUnsetMsg
{"varrays do not support unsetting non-end elements"},
50 s_VecUnsetMsg
{"Vecs do not support unsetting non-end elements"};
51 ///////////////////////////////////////////////////////////////////////////////
53 __thread
std::pair
<const ArrayData
*, size_t> s_cachedHash
;
57 sizeof(ArrayData
) == 16,
58 "Performance is sensitive to sizeof(ArrayData)."
59 " Make sure you changed it with good reason and then update this assert.");
61 size_t hashArrayPortion(const ArrayData
* arr
) {
62 assertx(arr
->isVanilla());
64 auto hash
= folly::hash::hash_128_to_64(arr
->kind(), arr
->isLegacyArray());
68 [&](TypedValue k
, TypedValue v
) {
69 assertx(tvIsString(k
) || tvIsInt(k
));
70 assertx(IMPLIES(tvIsString(k
), val(k
).pstr
->isStatic()));
71 hash
= folly::hash::hash_combine(
73 dt_with_persistence(k
.m_type
),
83 case KindOfPersistentString
:
84 case KindOfPersistentVec
:
85 case KindOfPersistentDict
:
86 case KindOfPersistentKeyset
:
87 hash
= folly::hash::hash_combine(hash
, v
.m_data
.num
);
90 assertx(v
.m_data
.plazyclass
.name()->isStatic());
91 hash
= folly::hash::hash_combine(
93 (uintptr_t)v
.m_data
.plazyclass
.name()
109 always_assert(false);
116 bool compareArrayPortion(const ArrayData
* ad1
, const ArrayData
* ad2
) {
117 assertx(ad1
->isVanilla());
118 assertx(ad2
->isVanilla());
120 if (ad1
== ad2
) return true;
121 if (ad1
->kind() != ad2
->kind()) return false;
122 if (ad1
->size() != ad2
->size()) return false;
123 if (ad1
->isLegacyArray() != ad2
->isLegacyArray()) return false;
125 auto const check
= [] (const TypedValue
& tv1
, const TypedValue
& tv2
) {
126 if (tv1
.m_type
!= tv2
.m_type
) {
127 // String keys from arrays might be KindOfString, even when
128 // the StringData is static.
129 if (!isStringType(tv1
.m_type
) || !isStringType(tv2
.m_type
)) {
132 assertx(tv1
.m_data
.pstr
->isStatic());
133 assertx(tv2
.m_data
.pstr
->isStatic());
135 if (isNullType(tv1
.m_type
)) return true;
136 return tv1
.m_data
.num
== tv2
.m_data
.num
;
140 ArrayIter iter2
{ad2
};
143 [&](TypedValue k
, TypedValue v
) {
144 if (!check(k
, *iter2
.first().asTypedValue()) ||
145 !check(v
, iter2
.secondVal())) {
157 size_t operator()(ArrayData
* arr
) const {
158 return s_cachedHash
.first
== arr
159 ? s_cachedHash
.second
160 : hashArrayPortion(arr
);
162 bool operator()(ArrayData
* arr1
, ArrayData
* arr2
) const {
163 return compareArrayPortion(arr1
, arr2
);
168 tbb::concurrent_unordered_set
<ArrayData
*, ScalarHash
, ScalarHash
>;
169 ArrayDataMap s_arrayDataMap
;
173 size_t loadedStaticArrayCount() {
174 return s_arrayDataMap
.size();
177 ///////////////////////////////////////////////////////////////////////////////
179 void ArrayData::GetScalarArray(ArrayData
** parr
) {
180 auto const base
= *parr
;
181 if (base
->isStatic()) return;
183 auto const arr
= [&]{
184 if (base
->isVanilla()) return base
;
185 *parr
= BespokeArray::ToVanilla(base
, "ArrayData::GetScalarArray");
190 auto const replace
= [&] (ArrayData
* rep
) {
193 s_cachedHash
.first
= nullptr;
198 auto const legacy
= arr
->isLegacyArray();
199 switch (arr
->toDataType()) {
200 case KindOfVec
: return ArrayData::CreateVec(legacy
);
201 case KindOfDict
: return ArrayData::CreateDict(legacy
);
202 case KindOfKeyset
: return ArrayData::CreateKeyset();
203 default: always_assert(false);
208 arr
->onSetEvalScalar();
210 s_cachedHash
.first
= arr
;
211 s_cachedHash
.second
= hashArrayPortion(arr
);
213 auto const lookup
= [&] (ArrayData
* a
) -> ArrayData
* {
214 if (auto const it
= s_arrayDataMap
.find(a
);
215 it
!= s_arrayDataMap
.end()) {
221 if (auto const a
= lookup(arr
)) return replace(a
);
223 static std::array
<std::mutex
, 128> s_mutexes
;
224 std::lock_guard
<std::mutex
> g
{
225 s_mutexes
[s_cachedHash
.second
% s_mutexes
.size()]
228 if (auto const a
= lookup(arr
)) return replace(a
);
230 // We should clear the sampled bit in the new static array regardless of
231 // whether the input array was sampled, because specializing the input is
232 // not sufficient to specialize this new static array.
233 auto const ad
= arr
->copyStatic();
234 ad
->m_aux16
&= ~kSampledArray
;
235 assertx(ad
->isStatic());
237 // TODO(T68458896): allocSize rounds up to size class, which we shouldn't do.
238 MemoryStats::LogAlloc(AllocKind::StaticArray
, allocSize(ad
));
240 s_cachedHash
.first
= ad
;
241 assertx(hashArrayPortion(ad
) == s_cachedHash
.second
);
243 auto const DEBUG_ONLY inserted
= s_arrayDataMap
.insert(ad
).second
;
248 ArrayData
* ArrayData::GetScalarArray(Array
&& arr
) {
249 auto a
= arr
.detach();
254 ArrayData
* ArrayData::GetScalarArray(Variant
&& arr
) {
255 assertx(arr
.isArray());
256 auto a
= arr
.detach().m_data
.parr
;
261 //////////////////////////////////////////////////////////////////////
263 static_assert(ArrayFunctions::NK
== ArrayData::ArrayKind::kNumKinds
,
264 "add new kinds here");
266 #define DISPATCH(entry) \
267 { MixedArray::entry, /* darray */ \
268 BespokeArray::entry, /* bespoke darray */ \
269 PackedArray::entry, /* varray */ \
270 BespokeArray::entry, /* bespoke varray */ \
271 MixedArray::entry, /* dict */ \
272 BespokeArray::entry, /* bespoke dict */ \
273 PackedArray::entry, /* vec */ \
274 BespokeArray::entry, /* bespoke vec */ \
275 SetArray::entry, /* keyset */ \
276 BespokeArray::entry, /* bespoke keyset */ \
280 * "Copy/grow" semantics:
282 * Many of the functions that mutate arrays return an ArrayData* and
283 * take a boolean parameter called 'copy'. The semantics of these
284 * functions is the following:
286 * If the `copy' argument is false, a COW is not required for
287 * language semantics. The array may mutate itself in place and
288 * return the same pointer, or the array may allocate a new array
289 * and return that. This is called "growing", since it may be used
290 * if an array is out of capacity for new elements---but it is also
291 * what happens if an array needs to change kinds and can't do that
292 * in-place. If an array grows, the old array pointer may not be
293 * used for any more array operations except to eventually call
294 * Release---these grown-from arrays are sometimes described as
295 * being in "zombie" state.
297 * If the `copy' argument is true, the returned array must be
298 * indistinguishable from an array that did a COW, in terms of the
299 * contents of the array. Whether it is the same pointer value or
300 * not is not specified. Note, for example, that this means an
301 * array doesn't have to copy if it was asked to remove an element
302 * that doesn't exist.
304 * When a function with these semantics returns a new array, the new array is
305 * already incref'd. In a few cases, an existing array (different than the
306 * source array) may be returned. In this case, the array will already be
310 const ArrayFunctions g_array_funcs
= {
312 * void Release(ArrayData*)
314 * Free memory associated with an array. Generally called when
315 * the reference count on an array drops to zero.
320 * TypedValue NvGetInt(const ArrayData*, int64_t key)
321 * TypedValue NvGetStr(const ArrayData*, const StringData*)
323 * Lookup a value in an array using an int or string key. Returns Uninit if
324 * the key is not in the array. This method does not inc-ref the result.
330 * TypedValue GetPosKey(const ArrayData*, ssize_t pos)
331 * TypedValue GetPosVal(const ArrayData*, ssize_t pos)
333 * Look up the key or value at a valid iterator position in this array.
334 * Both of these methods return the result without inc-ref-ing it.
340 * ArrayData* SetInt(ArrayData*, int64_t key, TypedValue v)
342 * Set a value in the array for an integer key, with copies / escalation.
343 * SetIntMove is equivalent to SetInt, followed by a dec-ref of the value,
344 * followed by a dec-ref of the old array (if it was copied or escalated).
349 * ArrayData* SetStr(ArrayData*, StringData*, TypedValue v)
351 * Set a value in the array for a string key, with copies / escalation.
352 * SetStrMove is equivalent to SetStr, followed by a dec-ref of the value,
353 * followed by a dec-ref of the old array (if it was copied or escalated).
358 * bool IsVectorData(const ArrayData*)
360 * Return true if this array is empty, or if it has only contiguous integer
361 * keys and the first key is zero. Determining this may be an O(N)
364 DISPATCH(IsVectorData
)
367 * bool ExistsInt(const ArrayData*, int64_t key)
369 * Return true iff this array contains an element with the supplied integer
375 * bool ExistsStr(const ArrayData*, const StringData*)
377 * Return true iff this array contains an element with the supplied string
378 * key. The string will not undergo intish-key cast.
383 * arr_lval LvalInt(ArrayData*, int64_t k)
384 * arr_lval LvalStr(ArrayData*, StringData* key)
386 * Look up a value in the array by the supplied key, throwing if it doesn't
387 * exist, and return a reference to it. This function has copy/grow
394 * ArrayData* RemoveInt(ArrayData*, int64_t key)
396 * Remove an array element with an integer key, copying or escalating
397 * as necessary. If there was no entry for that element, this function
398 * will not remove it, but it may still copy the array in that case.
403 * ArrayData* RemoveStr(ArrayData*, const StringData*)
405 * Remove an array element with a string key, copying or escalating
406 * as necessary. If there was no entry for that element, this function
407 * will not remove it, but it may still copy the array in that case.
412 * ssize_t IterEnd(const ArrayData*)
414 * Returns the canonical invalid position for this array. Note
415 * that if elements are added or removed from the array, the value
416 * of the array's canonical invalid position may change.
418 * ssize_t IterBegin(const ArrayData*)
420 * Returns the position of the first element, or the canonical
421 * invalid position if this array is empty.
423 * ssize_t IterLast(const ArrayData*)
425 * Returns the position of the last element, or the canonical
426 * invalid position if this array is empty.
433 * ssize_t IterAdvance(const ArrayData*, size_t pos)
435 * Returns the position of the element that comes after pos, or the
436 * canonical invalid position if there are no more elements after pos.
437 * If pos is the canonical invalid position, this method will return
438 * the canonical invalid position.
440 DISPATCH(IterAdvance
)
443 * ssize_t IterRewind(const ArrayData*, size_t pos)
445 * Returns the position of the element that comes before pos, or the
446 * canonical invalid position if there are no elements before pos. If
447 * pos is the canonical invalid position, no guarantees are made about
448 * what this method returns.
453 * ArrayData* EscalateForSort(ArrayData*, SortFunction)
455 * Must be called before calling any of the sort routines on an
456 * array. This gives arrays a chance to change to a kind that
459 DISPATCH(EscalateForSort
)
462 * void Ksort(ArrayData*, int sort_flags, bool ascending)
464 * Sort an array by its keys, keeping the values associated with
465 * their respective keys.
470 * void Sort(ArrayData*, int sort_flags, bool ascending)
472 * Sort an array, by values, and then assign new keys to the
473 * elements in the resulting array.
478 * void Asort(ArrayData*, int sort_flags, bool ascending)
480 * Sort an array and maintain index association. This means sort
481 * the array by values, but keep the keys associated with the
482 * values they used to be associated with.
487 * bool Uksort(ArrayData*, const Variant&)
489 * Sort on keys with a user-defined compare function (in the
490 * variant argument). Returns false if the user comparison
491 * function modifies the array we are sorting.
496 * bool Usort(ArrayData*, const Variant&)
498 * Sort the array by values with a user-defined comparison
499 * function (in the variant). Returns false if the user-defined
500 * comparison function modifies the array we are sorting.
505 * bool Uasort(ArrayData*, const Variant&)
507 * Sort array by values with a user-defined comparison function
508 * (in the variant arg), keeping the original indexes associated
509 * with the values. Returns false if the user-defined comparison
510 * function modifies the array we are sorting.
515 * ArrayData* CopyStatic(const ArrayData*)
517 * Copy an array, allocating the new array with malloc() instead
518 * of from the request local allocator. This function does
519 * guarantee the returned array is a new copy, but it may throw a
520 * fatal error if this cannot be accomplished.
525 * ArrayData* Append(ArrayData*, TypedValue v);
527 * Append a new value to the array, with the next available integer key,
528 * copying or escalating as necessary. If there is no available integer
529 * key, no value is appended, but this method may still copy the array.
531 * AppendMove is equivalent to calling Append, dec-ref-ing the value,
532 * and (if copy or escalation was needed), dec-ref-ing the old array.
537 * ArrayData* Pop(ArrayData*, Variant& value);
539 * Remove the last element from the array and assign it to `value'. This
540 * function may return a new array if it decided to COW due to
546 * void OnSetEvalScalar(ArrayData*)
548 * Go through an array and call Variant::setEvalScalar on each
549 * value, and make all string keys into static strings.
551 DISPATCH(OnSetEvalScalar
)
556 ///////////////////////////////////////////////////////////////////////////////
560 DEBUG_ONLY
void assertForCreate(TypedValue name
) {
561 always_assert(isIntType(name
.m_type
) || isStringType(name
.m_type
));
566 // In general, arrays can contain int-valued-strings, even though plain array
567 // access converts them to integers. non-int-string assertions should go
568 // upstream of the ArrayData api.
570 bool ArrayData::IsValidKey(TypedValue k
) {
571 return isIntType(k
.m_type
) ||
572 (isStringType(k
.m_type
) && IsValidKey(k
.m_data
.pstr
));
575 bool ArrayData::IsValidKey(const Variant
& k
) {
576 return IsValidKey(*k
.asTypedValue());
579 bool ArrayData::IsValidKey(const String
& k
) {
580 return IsValidKey(k
.get());
583 ///////////////////////////////////////////////////////////////////////////////
587 bool ArrayData::EqualHelper(const ArrayData
* ad1
, const ArrayData
* ad2
,
589 if (ad1
== ad2
) return true;
590 if (ad1
->size() != ad2
->size()) return false;
592 // Prevent circular referenced objects/arrays or deep ones.
593 check_recursion_error();
596 for (ArrayIter iter1
{ad1
}, iter2
{ad2
}; iter1
; ++iter1
, ++iter2
) {
598 if (!HPHP::same(iter1
.first(), iter2
.first()) ||
599 !tvSame(iter1
.secondVal(), iter2
.secondVal())) {
608 [&](TypedValue k
, TypedValue v
) {
609 auto const v2
= ad2
->get(k
);
610 if (!v2
.is_init() || !tvEqual(v
, v2
)) {
622 int64_t ArrayData::CompareHelper(const ArrayData
* ad1
, const ArrayData
* ad2
) {
623 auto const size1
= ad1
->size();
624 auto const size2
= ad2
->size();
625 if (size1
< size2
) return -1;
626 if (size1
> size2
) return 1;
628 // Prevent circular referenced objects/arrays or deep ones.
629 check_recursion_error();
634 [&](TypedValue k
, TypedValue v
) {
635 auto const v2
= ad2
->get(k
);
640 auto const cmp
= tvCompare(v
, v2
);
652 bool ArrayData::Equal(const ArrayData
* ad1
, const ArrayData
* ad2
) {
653 return EqualHelper(ad1
, ad2
, false);
656 bool ArrayData::NotEqual(const ArrayData
* ad1
, const ArrayData
* ad2
) {
657 return !EqualHelper(ad1
, ad2
, false);
660 bool ArrayData::Same(const ArrayData
* ad1
, const ArrayData
* ad2
) {
661 return EqualHelper(ad1
, ad2
, true);
664 bool ArrayData::NotSame(const ArrayData
* ad1
, const ArrayData
* ad2
) {
665 return !EqualHelper(ad1
, ad2
, true);
668 bool ArrayData::Lt(const ArrayData
* ad1
, const ArrayData
* ad2
) {
669 return CompareHelper(ad1
, ad2
) < 0;
672 bool ArrayData::Lte(const ArrayData
* ad1
, const ArrayData
* ad2
) {
673 return CompareHelper(ad1
, ad2
) <= 0;
676 bool ArrayData::Gt(const ArrayData
* ad1
, const ArrayData
* ad2
) {
677 return CompareHelper(ad1
, ad2
) > 0;
680 bool ArrayData::Gte(const ArrayData
* ad1
, const ArrayData
* ad2
) {
681 return CompareHelper(ad1
, ad2
) >= 0;
684 int64_t ArrayData::Compare(const ArrayData
* ad1
, const ArrayData
* ad2
) {
685 return CompareHelper(ad1
, ad2
);
688 bool ArrayData::same(const ArrayData
* v2
) const {
691 if (toDataType() != v2
->toDataType()) {
692 if (UNLIKELY(checkHACCompare())) {
693 raiseHackArrCompatArrHackArrCmp();
698 if (!bothVanilla(this, v2
)) return Same(this, v2
);
699 if (isVanillaVec()) return PackedArray::VecSame(this, v2
);
700 if (isVanillaDict()) return MixedArray::DictSame(this, v2
);
701 return SetArray::Same(this, v2
);
704 ///////////////////////////////////////////////////////////////////////////////
707 void ArrayData::getNotFound(int64_t k
) const {
708 throwOOBArrayKeyException(k
, this);
711 void ArrayData::getNotFound(const StringData
* k
) const {
712 if (isVecType()) throwInvalidArrayKeyException(k
, this);
713 throwOOBArrayKeyException(k
, this);
716 const char* ArrayData::kindToString(ArrayKind kind
) {
717 std::array
<const char*, 10> names
= {{
729 static_assert(names
.size() == kNumKinds
, "add new kinds here");
735 ////////////////////////////////////////////////////////////////////////////////
737 std::string
describeKeyType(const TypedValue
* tv
) {
738 switch (tv
->m_type
) {
740 case KindOfNull
: return "null";
741 case KindOfBoolean
: return "bool";
742 case KindOfInt64
: return "int";
743 case KindOfDouble
: return "double";
744 case KindOfPersistentString
:
745 case KindOfString
: return "string";
746 case KindOfPersistentVec
:
748 return val(*tv
).parr
->isLegacyArray() ? "array" : "vec";
749 case KindOfPersistentDict
:
751 return val(*tv
).parr
->isLegacyArray() ? "array" : "dict";
752 case KindOfPersistentKeyset
:
753 case KindOfKeyset
: return "keyset";
756 return tv
->m_data
.pres
->data()->o_getClassName().toCppString();
759 return tv
->m_data
.pobj
->getClassName().get()->toCppString();
762 return tv
->m_data
.prec
->record()->name()->toCppString();
764 case KindOfRFunc
: return "rfunc";
765 case KindOfFunc
: return "func";
766 case KindOfClass
: return "class";
767 case KindOfLazyClass
: return "lclass";
768 case KindOfClsMeth
: return "clsmeth";
769 case KindOfRClsMeth
: return "rclsmeth";
774 std::string
describeKeyValue(TypedValue tv
) {
776 case KindOfPersistentString
:
778 return folly::sformat("\"{}\"", tv
.m_data
.pstr
->data());
780 return folly::to
<std::string
>(tv
.m_data
.num
);
785 case KindOfPersistentVec
:
787 case KindOfPersistentDict
:
789 case KindOfPersistentKeyset
:
796 case KindOfLazyClass
:
800 return "<invalid key type>";
805 ////////////////////////////////////////////////////////////////////////////////
809 void throwInvalidArrayKeyException(const TypedValue
* key
, const ArrayData
* ad
) {
810 std::pair
<const char*, const char*> kind_type
= [&]{
811 if (ad
->isVecType()) return std::make_pair("vec", "int");
812 if (ad
->isDictType()) return std::make_pair("dict", "int or string");
813 assertx(ad
->isKeysetType());
814 return std::make_pair("keyset", "int or string");
816 SystemLib::throwInvalidArgumentExceptionObject(
818 "Invalid {} key: expected a key of type {}, {} given",
819 kind_type
.first
, kind_type
.second
, describeKeyType(key
)
824 void throwInvalidArrayKeyException(const StringData
* key
, const ArrayData
* ad
) {
825 auto const tv
= make_tv
<KindOfString
>(const_cast<StringData
*>(key
));
826 throwInvalidArrayKeyException(&tv
, ad
);
829 void throwFalseyPromoteException(const char* type
) {
830 SystemLib::throwOutOfBoundsExceptionObject(
831 folly::sformat("Promoting {} to array", type
)
835 void throwOOBArrayKeyException(TypedValue key
, const ArrayData
* ad
) {
836 SystemLib::throwOutOfBoundsExceptionObject(
838 "Out of bounds {} access: invalid index {}",
839 getDataTypeString(ad
->toDataType(), ad
->isLegacyArray()).data(),
840 describeKeyValue(key
)
845 void throwOOBArrayKeyException(int64_t key
, const ArrayData
* ad
) {
846 throwOOBArrayKeyException(make_tv
<KindOfInt64
>(key
), ad
);
849 void throwOOBArrayKeyException(const StringData
* key
, const ArrayData
* ad
) {
850 throwOOBArrayKeyException(
851 make_tv
<KindOfString
>(const_cast<StringData
*>(key
)),
856 void throwInvalidKeysetOperation() {
857 SystemLib::throwInvalidOperationExceptionObject(s_InvalidKeysetOperationMsg
);
860 void throwVarrayUnsetException() {
861 SystemLib::throwInvalidOperationExceptionObject(s_VarrayUnsetMsg
);
864 void throwVecUnsetException() {
865 SystemLib::throwInvalidOperationExceptionObject(s_VecUnsetMsg
);
868 ///////////////////////////////////////////////////////////////////////////////
870 void raiseHackArrCompatArrHackArrCmp() {
871 raise_hac_compare_notice(Strings::HACKARR_COMPAT_ARR_HACK_ARR_CMP
);
874 ///////////////////////////////////////////////////////////////////////////////
876 ArrayData
* ArrayData::toDictIntishCast(bool copy
) {
877 auto const base
= toDict(copy
);
878 if (isVecType()) return base
;
880 // Check if we need to intish-cast any string keys.
883 IterateKV(base
, [&](TypedValue k
, TypedValue
) {
884 return intish
|= tvIsString(k
) && val(k
).pstr
->isStrictlyInteger(i
);
886 if (!intish
) return base
;
888 if (RO::EvalHackArrCompatIntishCastNotices
) {
889 raise_hackarr_compat_notice("triggered IntishCast for toDictIntishCast");
892 // Create a new, plain PHP array with the casted keys.
893 auto result
= MixedArrayInit(base
->size());
894 IterateKV(base
, [&](TypedValue k
, TypedValue v
) {
895 result
.setUnknownKey
<IntishCast::Cast
>(tvAsCVarRef(&k
), tvAsCVarRef(&v
));
897 if (base
!= this) decRefArr(base
);
898 auto ad
= result
.create();
899 if (!isKeysetType()) return ad
;
901 // When keysets are intish-casted, we cast the values, too. Do so in place.
902 // This case is rare, so we keep the extra checks out of the loop above.
903 assertx(ad
->hasExactlyOneRef());
904 auto elm
= MixedArray::asMixed(ad
)->data();
905 for (auto const end
= elm
+ ad
->size(); elm
!= end
; elm
++) {
906 if (tvIsString(elm
->data
) && elm
->hasIntKey()) {
907 decRefStr(val(elm
->data
).pstr
);
908 tvCopy(make_tv
<KindOfInt64
>(elm
->intKey()), elm
->data
);
914 bool ArrayData::intishCastKey(const StringData
* key
, int64_t& i
) const {
915 if (key
->isStrictlyInteger(i
)) {
916 if (RO::EvalHackArrCompatIntishCastNotices
) {
917 raise_hackarr_compat_notice("triggered IntishCast for set");
924 ArrayData
* ArrayData::setLegacyArray(bool copy
, bool legacy
) {
925 assertx(IMPLIES(cowCheck(), copy
));
926 assertx(isDictType() || isVecType());
928 if (legacy
== isLegacyArray()) return this;
930 if (!isVanilla()) return BespokeArray::SetLegacyArray(this, copy
, legacy
);
933 if (!copy
) return this;
934 return isVanillaVec() ? PackedArray::Copy(this) : MixedArray::Copy(this);
936 ad
->setLegacyArrayInPlace(legacy
);
940 void ArrayData::setLegacyArrayInPlace(bool legacy
) {
941 assertx(hasExactlyOneRef());
943 m_aux16
|= kLegacyArray
;
945 m_aux16
&= ~kLegacyArray
;
949 ///////////////////////////////////////////////////////////////////////////////
951 ArrayData
* ArrayData::toVec(bool copy
) {
952 assertx(IMPLIES(cowCheck(), copy
));
953 if (isVecType()) return this;
955 if (empty()) return ArrayData::CreateVec();
956 VecInit init
{size()};
957 IterateV(this, [&](auto v
) { init
.append(v
); });
958 return init
.create();
961 ArrayData
* ArrayData::toDict(bool copy
) {
962 assertx(IMPLIES(cowCheck(), copy
));
963 if (isDictType()) return this;
965 if (empty()) return ArrayData::CreateDict();
966 DictInit init
{size()};
967 IterateKV(this, [&](auto k
, auto v
) { init
.setValidKey(k
, v
); });
968 return init
.create();
971 ArrayData
* ArrayData::toKeyset(bool copy
) {
972 assertx(IMPLIES(cowCheck(), copy
));
973 if (isKeysetType()) return this;
974 if (empty()) return ArrayData::CreateKeyset();
975 KeysetInit init
{size()};
976 IterateV(this, [&](auto v
) { init
.add(v
); });
977 return init
.create();
980 ///////////////////////////////////////////////////////////////////////////////