2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 | Copyright (c) 1997-2010 The PHP Group |
7 +----------------------------------------------------------------------+
8 | This source file is subject to version 3.01 of the PHP license, |
9 | that is bundled with this package in the file LICENSE, and is |
10 | available through the world-wide-web at the following url: |
11 | http://www.php.net/license/3_01.txt |
12 | If you did not receive a copy of the PHP license and are unable to |
13 | obtain it through the world-wide-web, please send a note to |
14 | license@php.net so we can mail you a copy immediately. |
15 +----------------------------------------------------------------------+
18 #include "hphp/runtime/ext/array/ext_array.h"
20 #include "hphp/runtime/base/actrec-args.h"
21 #include "hphp/runtime/base/apc-local-array.h"
22 #include "hphp/runtime/base/array-data-defs.h"
23 #include "hphp/runtime/base/array-init.h"
24 #include "hphp/runtime/base/builtin-functions.h"
25 #include "hphp/runtime/base/collections.h"
26 #include "hphp/runtime/base/comparisons.h"
27 #include "hphp/runtime/base/container-functions.h"
28 #include "hphp/runtime/base/double-to-int64.h"
29 #include "hphp/runtime/base/mixed-array.h"
30 #include "hphp/runtime/base/req-containers.h"
31 #include "hphp/runtime/base/request-event-handler.h"
32 #include "hphp/runtime/base/request-local.h"
33 #include "hphp/runtime/base/sort-flags.h"
34 #include "hphp/runtime/base/thread-info.h"
35 #include "hphp/runtime/base/tv-refcount.h"
36 #include "hphp/runtime/base/zend-collator.h"
37 #include "hphp/runtime/base/zend-sort.h"
38 #include "hphp/runtime/ext/collections/ext_collections-map.h"
39 #include "hphp/runtime/ext/collections/ext_collections-pair.h"
40 #include "hphp/runtime/ext/collections/ext_collections-set.h"
41 #include "hphp/runtime/ext/collections/ext_collections-vector.h"
42 #include "hphp/runtime/ext/generator/ext_generator.h"
43 #include "hphp/runtime/ext/std/ext_std_function.h"
44 #include "hphp/runtime/vm/jit/translator-inline.h"
45 #include "hphp/runtime/vm/jit/translator.h"
46 #include "hphp/util/logger.h"
51 ///////////////////////////////////////////////////////////////////////////////
56 const StaticString
s_count("count");
63 TypedValue
HHVM_FUNCTION(array_change_key_case
,
65 int64_t case_
/* = 0 */) {
66 getCheckedContainer(input
);
67 return tvReturn(ArrayUtil::ChangeKeyCase(arr_input
,
68 (CaseMode
)case_
== CaseMode::LOWER
));
71 TypedValue
HHVM_FUNCTION(array_chunk
,
74 bool preserve_keys
/* = false */) {
75 const auto& cellInput
= *input
.asCell();
76 if (UNLIKELY(!isContainer(cellInput
))) {
77 raise_warning("Invalid operand type was used: %s expects "
78 "an array or collection as argument 1", __FUNCTION__
+2);
79 return make_tv
<KindOfNull
>();
83 throw_invalid_argument("size: %d", chunkSize
);
84 return make_tv
<KindOfNull
>();
87 auto const retSize
= (getContainerSize(cellInput
) / chunkSize
) + 1;
88 PackedArrayInit
ret(retSize
);
91 for (ArrayIter
iter(cellInput
); iter
; ++iter
) {
93 chunk
.setWithRef(iter
.first(), iter
.secondValPlus(), true);
95 chunk
.appendWithRef(iter
.secondValPlus());
97 if ((++current
% chunkSize
) == 0) {
102 if (!chunk
.empty()) {
106 return tvReturn(ret
.toVariant());
109 static inline bool array_column_coerce_key(Variant
&key
, const char *name
) {
110 /* NULL has a special meaning for each field */
115 /* Custom coercion rules for key types */
116 if (key
.isInteger() || key
.isDouble()) {
119 } else if (key
.isString() || key
.isObject()) {
120 key
= key
.toString();
123 raise_warning("array_column(): The %s key should be either a string "
124 "or an integer", name
);
129 TypedValue
HHVM_FUNCTION(array_column
,
130 const Variant
& input
,
131 const Variant
& val_key
,
132 const Variant
& idx_key
/* = uninit_variant */) {
133 SuppressHackArrCompatNotices suppress
;
135 getCheckedContainer(input
);
136 Variant val
= val_key
, idx
= idx_key
;
137 if (!array_column_coerce_key(val
, "column") ||
138 !array_column_coerce_key(idx
, "index")) {
139 return make_tv
<KindOfBoolean
>(false);
141 ArrayInit
ret(arr_input
.size(), ArrayInit::Map
{});
142 for (ArrayIter
it(arr_input
); it
; ++it
) {
144 if (UNLIKELY(RuntimeOption::PHP7_Builtins
&& it
.second().isObject())) {
145 sub
= it
.second().toObject().toArray();
146 } else if (it
.second().isArray()) {
147 sub
= it
.second().toArray();
155 } else if (sub
.exists(val
)) {
158 // skip subarray without named element
162 if (idx
.isNull() || !sub
.exists(idx
)) {
164 } else if (sub
[idx
].isObject()) {
165 ret
.setUnknownKey(sub
[idx
].toString(), elem
);
167 ret
.setUnknownKey(sub
[idx
], elem
);
170 return tvReturn(ret
.toVariant());
173 TypedValue
HHVM_FUNCTION(array_combine
,
175 const Variant
& values
) {
176 SuppressHackArrCompatNotices suppress
;
178 const auto& cell_keys
= *keys
.asCell();
179 const auto& cell_values
= *values
.asCell();
180 if (UNLIKELY(!isContainer(cell_keys
) || !isContainer(cell_values
))) {
181 raise_warning("Invalid operand type was used: array_combine expects "
182 "arrays or collections");
183 return make_tv
<KindOfNull
>();
185 auto keys_size
= getContainerSize(cell_keys
);
186 if (UNLIKELY(keys_size
!= getContainerSize(cell_values
))) {
187 raise_warning("array_combine(): Both parameters should have an equal "
188 "number of elements");
189 return make_tv
<KindOfBoolean
>(false);
191 Array ret
= Array::attach(MixedArray::MakeReserveMixed(keys_size
));
192 for (ArrayIter
iter1(cell_keys
), iter2(cell_values
);
193 iter1
; ++iter1
, ++iter2
) {
194 auto const key
= iter1
.secondRvalPlus().unboxed();
195 if (key
.type() == KindOfInt64
|| isStringType(key
.type())) {
196 ret
.setWithRef(iter1
.secondValPlus(),
197 iter2
.secondValPlus());
199 ret
.setWithRef(tvCastToString(key
.tv()), iter2
.secondValPlus());
202 return tvReturn(std::move(ret
));
205 TypedValue
HHVM_FUNCTION(array_count_values
,
207 SuppressHackArrCompatNotices suppress
;
208 return tvReturn(ArrayUtil::CountValues(ArrNR(input
.get())));
211 TypedValue
HHVM_FUNCTION(array_fill_keys
,
213 const Variant
& value
) {
214 SuppressHackArrCompatNotices suppress
;
216 folly::Optional
<ArrayInit
> ai
;
219 [&](ArrayData
* adata
) {
220 ai
.emplace(adata
->size(), ArrayInit::Mixed
{});
223 auto const inner
= tvToCell(v
);
224 if (isIntType(inner
.m_type
) || isStringType(inner
.m_type
)) {
225 ai
->setUnknownKey(VarNR(inner
), value
);
227 raise_hack_strict(RuntimeOption::StrictArrayFillKeys
,
228 "strict_array_fill_keys",
229 "keys must be ints or strings");
230 ai
->setUnknownKey(tvCastToString(v
), value
);
233 [&](ObjectData
* coll
) {
234 if (coll
->collectionType() == CollectionType::Pair
) {
235 ai
.emplace(2, ArrayInit::Mixed
{});
241 raise_warning("Invalid operand type was used: array_fill_keys expects "
242 "an array or collection");
243 return make_tv
<KindOfNull
>();
245 assertx(ai
.hasValue());
246 return tvReturn(ai
->toVariant());
249 TypedValue
HHVM_FUNCTION(array_fill
,
252 const Variant
& value
) {
254 throw_invalid_argument("Number of elements can't be negative");
255 return make_tv
<KindOfBoolean
>(false);
256 } else if (num
== 0) {
257 return tvReturn(empty_array());
260 if (start_index
== 0) {
261 PackedArrayInit
pai(num
, CheckAllocation
{});
262 for (size_t k
= 0; k
< num
; k
++) {
265 return tvReturn(pai
.toVariant());
267 ArrayInit
ret(num
, ArrayInit::Mixed
{}, CheckAllocation
{});
268 ret
.set(start_index
, value
);
269 for (int i
= num
- 1; i
> 0; i
--) {
272 return tvReturn(ret
.toVariant());
276 TypedValue
HHVM_FUNCTION(array_flip
,
277 const Variant
& trans
) {
278 SuppressHackArrCompatNotices suppress
;
280 auto const& transCell
= *trans
.asCell();
281 if (UNLIKELY(!isContainer(transCell
))) {
282 raise_warning("Invalid operand type was used: %s expects "
283 "an array or collection", __FUNCTION__
+2);
284 return make_tv
<KindOfNull
>();
287 ArrayInit
ret(getContainerSize(transCell
), ArrayInit::Mixed
{});
288 for (ArrayIter
iter(transCell
); iter
; ++iter
) {
289 auto const inner
= iter
.secondRvalPlus().unboxed();
290 if (inner
.type() == KindOfInt64
|| isStringType(inner
.type())) {
291 ret
.setUnknownKey(VarNR(inner
.tv()), iter
.first());
293 raise_warning("Can only flip STRING and INTEGER values!");
296 return tvReturn(ret
.toVariant());
299 bool HHVM_FUNCTION(array_key_exists
,
301 const Variant
& search
) {
304 auto const searchCell
= search
.asCell();
305 if (LIKELY(isArrayLikeType(searchCell
->m_type
))) {
306 ad
= searchCell
->m_data
.parr
;
307 } else if (searchCell
->m_type
== KindOfObject
) {
308 ObjectData
* obj
= searchCell
->m_data
.pobj
;
309 if (obj
->isCollection()) {
310 return collections::contains(obj
, key
);
312 return HHVM_FN(array_key_exists
)(key
, search
.toArray());
314 throw_bad_type_exception("array_key_exists expects an array or an object; "
319 auto const cell
= key
.asCell();
321 switch (cell
->m_type
) {
324 if (checkHACMisc() && ad
->useWeakKeys()) {
325 raiseHackArrCompatImplicitArrayKey(cell
);
327 return ad
->useWeakKeys() && ad
->exists(staticEmptyString());
331 case KindOfPersistentVec
:
333 case KindOfPersistentDict
:
335 case KindOfPersistentKeyset
:
337 case KindOfPersistentArray
:
341 if (!ad
->useWeakKeys()) throwInvalidArrayKeyException(cell
, ad
);
342 if (checkHACMisc()) {
343 raiseHackArrCompatImplicitArrayKey(cell
);
345 raise_warning("Array key should be either a string or an integer");
348 case KindOfPersistentString
:
351 if (ad
->convertKey(cell
->m_data
.pstr
, n
)) {
352 return ad
->exists(n
);
354 return ad
->exists(StrNR(cell
->m_data
.pstr
));
357 return ad
->exists(cell
->m_data
.num
);
364 bool HHVM_FUNCTION(key_exists
,
366 const Variant
& search
) {
367 return HHVM_FN(array_key_exists
)(key
, search
);
370 Variant
array_keys_helper(const Variant
& input
,
371 const Variant
& search_value
/* = uninit_null */,
372 bool strict
/* = false */) {
373 const auto& cell_input
= *input
.asCell();
374 if (UNLIKELY(!isContainer(cell_input
))) {
375 raise_warning("array_keys() expects parameter 1 to be an array "
380 if (LIKELY(!search_value
.isInitialized())) {
381 PackedArrayInit
ai(getContainerSize(cell_input
));
382 for (ArrayIter
iter(cell_input
); iter
; ++iter
) {
383 ai
.append(iter
.first());
385 return ai
.toVariant();
387 Array ai
= Array::attach(PackedArray::MakeReserve(0));
388 for (ArrayIter
iter(cell_input
); iter
; ++iter
) {
390 tvSame(iter
.secondValPlus(), *search_value
.asTypedValue())) ||
392 tvEqual(iter
.secondValPlus(), *search_value
.asTypedValue()))) {
393 ai
.append(iter
.first());
401 Variant
HHVM_FUNCTION(array_keys
, int64_t argc
,
402 const Variant
& input
,
403 const Variant
& search_value
/*=null*/,
404 bool strict
/*=false*/) {
405 return array_keys_helper(
407 argc
< 2 ? uninit_variant
: search_value
,
412 static bool couldRecur(const Variant
& v
, const ArrayData
* arr
) {
413 return v
.isReferenced() || arr
->kind() == ArrayData::kGlobalsKind
;
416 static bool couldRecur(member_lval lval
, const ArrayData
* arr
) {
417 return tvIsReferenced(lval
.tv()) || arr
->kind() == ArrayData::kGlobalsKind
;
420 static void php_array_merge_recursive(PointerSet
&seen
, bool check
,
421 Array
&arr1
, const Array
& arr2
) {
422 auto const arr1_ptr
= (void*)arr1
.get();
424 if (seen
.find(arr1_ptr
) != seen
.end()) {
425 raise_warning("array_merge_recursive(): recursion detected");
428 seen
.insert(arr1_ptr
);
431 for (ArrayIter
iter(arr2
); iter
; ++iter
) {
432 Variant
key(iter
.first());
433 if (key
.isNumeric()) {
434 arr1
.appendWithRef(iter
.secondVal());
435 } else if (arr1
.exists(key
, true)) {
436 // There is no need to do toKey() conversion, for a key that is already
438 auto const lval
= arr1
.lvalAt(key
, AccessFlags::Key
);
439 auto subarr1
= tvCastToArrayLike(lval
.tv()).toPHPArray();
440 php_array_merge_recursive(
441 seen
, couldRecur(lval
, subarr1
.get()), subarr1
,
442 tvCastToArrayLike(iter
.secondVal())
444 tvUnset(lval
); // avoid contamination of the value that was strongly bound
445 tvSet(make_tv
<KindOfArray
>(subarr1
.get()), lval
);
447 arr1
.setWithRef(key
, iter
.secondVal(), true);
452 seen
.erase(arr1_ptr
);
456 TypedValue
HHVM_FUNCTION(array_map
,
457 const Variant
& callback
,
459 const Array
& _argv
) {
463 if (!callback
.isNull()) {
465 vm_decode_function(callback
, cf(), false, ctx
);
467 const auto& cell_arr1
= *arr1
.asCell();
468 if (UNLIKELY(!isContainer(cell_arr1
))) {
469 raise_warning("array_map(): Argument #2 should be an array or collection");
470 return make_tv
<KindOfNull
>();
472 if (LIKELY(_argv
.empty())) {
473 // Handle the common case where the caller passed two
474 // params (a callback and a container)
476 if (isArrayLikeType(cell_arr1
.m_type
)) {
477 return tvReturn(arr1
);
479 return tvReturn(arr1
.toArray());
482 ArrayInit
ret(getContainerSize(cell_arr1
), ArrayInit::Map
{});
483 bool keyConverted
= isArrayLikeType(cell_arr1
.m_type
);
485 auto col_type
= cell_arr1
.m_data
.pobj
->collectionType();
486 keyConverted
= !collectionAllowsIntStringKeys(col_type
);
488 for (ArrayIter
iter(arr1
); iter
; ++iter
) {
489 auto result
= Variant::attach(
490 g_context
->invokeFuncFew(
491 ctx
, 1, iter
.secondRvalPlus().unboxed().tv_ptr()
494 // if keyConverted is false, it's possible that ret will have fewer
495 // elements than cell_arr1; keys int(1) and string('1') may both be
497 ret
.add(iter
.first(), result
, keyConverted
);
499 return tvReturn(ret
.toVariant());
502 // Handle the uncommon case where the caller passed a callback
503 // and two or more containers
504 req::vector
<ArrayIter
> iters
;
505 iters
.reserve(_argv
.size() + 1);
506 size_t maxLen
= getContainerSize(cell_arr1
);
507 iters
.emplace_back(cell_arr1
);
508 for (ArrayIter
it(_argv
); it
; ++it
) {
509 auto const c
= tvToCell(it
.secondValPlus());
510 if (UNLIKELY(!isContainer(c
))) {
511 raise_warning("array_map(): Argument #%d should be an array or "
512 "collection", (int)(iters
.size() + 2));
513 iters
.emplace_back(tvCastToArrayLike(c
));
515 iters
.emplace_back(c
);
516 size_t len
= getContainerSize(c
);
517 if (len
> maxLen
) maxLen
= len
;
520 PackedArrayInit
ret_ai(maxLen
);
521 for (size_t k
= 0; k
< maxLen
; k
++) {
522 PackedArrayInit
params_ai(iters
.size());
523 for (auto& iter
: iters
) {
525 params_ai
.append(iter
.secondValPlus());
528 params_ai
.append(init_null_variant
);
531 Array params
= params_ai
.toArray();
533 auto result
= Variant::attach(
534 g_context
->invokeFunc(ctx
, params
, nullptr)
536 ret_ai
.append(result
);
538 ret_ai
.append(params
);
541 return tvReturn(ret_ai
.toVariant());
544 TypedValue
HHVM_FUNCTION(array_merge
,
546 const Variant
& array1
,
547 const Variant
& array2
/* = uninit_variant */,
548 const Array
& args
/* = null array */) {
549 getCheckedContainer(array1
);
550 Array ret
= Array::Create();
551 ret
.merge(arr_array1
);
553 if (UNLIKELY(numArgs
< 2)) return tvReturn(std::move(ret
));
555 getCheckedArray(array2
);
556 ret
.merge(arr_array2
);
558 for (ArrayIter
iter(args
); iter
; ++iter
) {
559 Variant v
= iter
.second();
561 throw_expected_array_exception("array_merge");
562 return make_tv
<KindOfNull
>();
564 const Array
& arr_v
= v
.asCArrRef();
567 return tvReturn(std::move(ret
));
570 TypedValue
HHVM_FUNCTION(array_merge_recursive
,
572 const Variant
& array1
,
573 const Variant
& array2
/* = uninit_variant */,
574 const Array
& args
/* = null array */) {
575 getCheckedArray(array1
);
576 auto in1
= array1
.asCArrRef();
577 Array ret
= Array::Create();
579 php_array_merge_recursive(seen
, false, ret
, arr_array1
);
580 assertx(seen
.empty());
582 if (UNLIKELY(numArgs
< 2)) return tvReturn(std::move(ret
));
584 getCheckedArray(array2
);
585 php_array_merge_recursive(seen
, false, ret
, arr_array2
);
586 assertx(seen
.empty());
588 for (ArrayIter
iter(args
); iter
; ++iter
) {
589 Variant v
= iter
.second();
591 throw_expected_array_exception("array_merge_recursive");
592 return make_tv
<KindOfNull
>();
594 const Array
& arr_v
= v
.asCArrRef();
595 php_array_merge_recursive(seen
, false, ret
, arr_v
);
596 assertx(seen
.empty());
598 return tvReturn(std::move(ret
));
601 static void php_array_replace(Array
&arr1
, const Array
& arr2
) {
602 for (ArrayIter
iter(arr2
); iter
; ++iter
) {
603 Variant key
= iter
.first();
604 arr1
.setWithRef(key
, iter
.secondVal(), true);
608 static void php_array_replace_recursive(PointerSet
&seen
, bool check
,
609 Array
&arr1
, const Array
& arr2
) {
610 if (arr1
.get() == arr2
.get()) {
611 // This is an optimization, but it also avoids an assert in
612 // setWithRef (Variant::setWithRef asserts that its source
613 // and destination are not the same).
614 // If the arrays are self recursive, this does change the behavior
615 // slightly - it skips the "recursion detected" warning.
619 auto const arr1_ptr
= (void*)arr1
.get();
621 if (seen
.find(arr1_ptr
) != seen
.end()) {
622 raise_warning("array_replace_recursive(): recursion detected");
625 seen
.insert(arr1_ptr
);
628 for (ArrayIter
iter(arr2
); iter
; ++iter
) {
629 Variant key
= iter
.first();
630 auto const rval
= iter
.secondRval().unboxed();
631 if (arr1
.exists(key
, true) && isArrayLikeType(rval
.type())) {
632 auto const lval
= arr1
.lvalAt(key
, AccessFlags::Key
);
633 if (isArrayLikeType(lval
.unboxed().type())) {
634 Array subarr1
= tvCastToArrayLike(lval
.tv()).toPHPArray();
635 php_array_replace_recursive(seen
, couldRecur(lval
, subarr1
.get()),
636 subarr1
, ArrNR(rval
.val().parr
));
637 tvSet(make_tv
<KindOfArray
>(subarr1
.get()), lval
);
639 arr1
.set(key
, iter
.secondVal(), true);
642 arr1
.setWithRef(key
, iter
.secondVal(), true);
647 seen
.erase(arr1_ptr
);
651 TypedValue
HHVM_FUNCTION(array_replace
,
652 const Variant
& array1
,
653 const Variant
& array2
/* = uninit_variant */,
654 const Array
& args
/* = null array */) {
655 getCheckedArray(array1
);
656 Array ret
= Array::Create();
657 php_array_replace(ret
, arr_array1
);
659 if (UNLIKELY(array2
.isNull() && args
.empty())) {
660 return tvReturn(std::move(ret
));
663 getCheckedArray(array2
);
664 php_array_replace(ret
, arr_array2
);
666 for (ArrayIter
iter(args
); iter
; ++iter
) {
667 auto const v
= VarNR(iter
.secondVal());
669 php_array_replace(ret
, arr_v
);
671 return tvReturn(std::move(ret
));
674 TypedValue
HHVM_FUNCTION(array_replace_recursive
,
675 const Variant
& array1
,
676 const Variant
& array2
/* = uninit_variant */,
677 const Array
& args
/* = null array */) {
678 getCheckedArray(array1
);
679 Array ret
= Array::Create();
681 php_array_replace_recursive(seen
, false, ret
, arr_array1
);
682 assertx(seen
.empty());
684 if (UNLIKELY(array2
.isNull() && args
.empty())) {
685 return tvReturn(std::move(ret
));
688 getCheckedArray(array2
);
689 php_array_replace_recursive(seen
, false, ret
, arr_array2
);
690 assertx(seen
.empty());
692 for (ArrayIter
iter(args
); iter
; ++iter
) {
693 auto const v
= VarNR(iter
.secondVal());
695 php_array_replace_recursive(seen
, false, ret
, arr_v
);
696 assertx(seen
.empty());
698 return tvReturn(std::move(ret
));
701 TypedValue
HHVM_FUNCTION(array_pad
,
702 const Variant
& input
,
704 const Variant
& pad_value
) {
705 getCheckedArray(input
);
707 UNLIKELY(input
.isHackArray()) ? arr_input
.toPHPArray() : arr_input
;
709 return tvReturn(ArrayUtil::PadRight(arr
, pad_value
, pad_size
));
711 return tvReturn(ArrayUtil::PadLeft(arr
, pad_value
, -pad_size
));
715 TypedValue
HHVM_FUNCTION(array_pop
,
716 VRefParam containerRef
) {
717 const auto* container
= containerRef
->asCell();
718 if (UNLIKELY(!isMutableContainer(*container
))) {
719 raise_warning("array_pop() expects parameter 1 to be an "
720 "array or mutable collection");
721 return make_tv
<KindOfNull
>();
723 if (!getContainerSize(containerRef
)) {
724 return make_tv
<KindOfNull
>();
726 if (isArrayLikeType(container
->m_type
)) {
727 if (auto ref
= containerRef
.getVariantOrNull()) {
728 return tvReturn(ref
->asArrRef().pop());
730 auto ad
= container
->m_data
.parr
;
732 auto last
= ad
->iter_last();
733 return tvReturn(ad
->getValue(last
));
735 return make_tv
<KindOfNull
>();
737 assertx(container
->m_type
== KindOfObject
);
738 return tvReturn(collections::pop(container
->m_data
.pobj
));
741 TypedValue
HHVM_FUNCTION(array_product
,
742 const Variant
& input
) {
743 if (UNLIKELY(!isContainer(input
))) {
744 raise_warning("Invalid operand type was used: %s expects "
745 "an array or collection as argument 1",
747 return make_tv
<KindOfNull
>();
751 ArrayIter
iter(input
);
752 for (; iter
; ++iter
) {
753 auto const rval
= iter
.secondRvalPlus().unboxed();
755 switch (rval
.type()) {
761 i
*= cellToInt(rval
.tv());
767 case KindOfPersistentString
:
771 if (rval
.val().pstr
->isNumericWithVal(ti
, td
, 1) == KindOfInt64
) {
779 case KindOfPersistentVec
:
781 case KindOfPersistentDict
:
783 case KindOfPersistentKeyset
:
785 case KindOfPersistentArray
:
793 return make_tv
<KindOfInt64
>(i
);
797 for (; iter
; ++iter
) {
798 auto const rval
= iter
.secondRvalPlus().unboxed();
799 switch (rval
.type()) {
803 d
*= cellToDouble(rval
.tv());
815 return make_tv
<KindOfDouble
>(d
);
818 TypedValue
HHVM_FUNCTION(array_push
,
821 const Array
& args
/* = null array */) {
822 if (LIKELY(container
->isArray())) {
823 auto ref
= container
.getVariantOrNull();
825 return make_tv
<KindOfInt64
>(
826 1 + args
.size() + container
->asCArrRef().size()
831 * Important note: this *must* cast the parr in the inner cell to
832 * the Array&---we can't copy it to the stack or anything because we
835 Array
& arr_array
= ref
->asArrRef();
836 arr_array
.append(var
);
837 for (ArrayIter
iter(args
); iter
; ++iter
) {
838 arr_array
.append(iter
.second());
840 return make_tv
<KindOfInt64
>(arr_array
.size());
843 if (container
.isObject()) {
844 ObjectData
* obj
= container
.getObjectData();
845 if (obj
->isCollection()) {
846 switch (obj
->collectionType()) {
847 case CollectionType::Vector
: {
848 c_Vector
* vec
= static_cast<c_Vector
*>(obj
);
849 vec
->reserve(vec
->size() + args
.size() + 1);
851 for (ArrayIter
iter(args
); iter
; ++iter
) {
852 vec
->add(iter
.second());
854 return make_tv
<KindOfInt64
>(vec
->size());
856 case CollectionType::Set
: {
857 c_Set
* set
= static_cast<c_Set
*>(obj
);
858 set
->reserve(set
->size() + args
.size() + 1);
860 for (ArrayIter
iter(args
); iter
; ++iter
) {
861 set
->add(iter
.second());
863 return make_tv
<KindOfInt64
>(set
->size());
865 case CollectionType::Map
:
866 case CollectionType::Pair
:
867 case CollectionType::ImmVector
:
868 case CollectionType::ImmMap
:
869 case CollectionType::ImmSet
:
870 // other collection types are unsupported:
871 // - mapping collections require a key
872 // - immutable collections don't allow insertion
877 throw_expected_array_or_collection_exception("array_push");
878 return make_tv
<KindOfNull
>();
881 TypedValue
HHVM_FUNCTION(array_rand
,
882 const Variant
& input
,
883 int num_req
/* = 1 */) {
884 getCheckedArray(input
);
885 return tvReturn(ArrayUtil::RandomKeys(arr_input
, num_req
));
888 TypedValue
HHVM_FUNCTION(array_reverse
,
889 const Variant
& input
,
890 bool preserve_keys
/* = false */) {
892 getCheckedContainer(input
);
893 return tvReturn(ArrayUtil::Reverse(arr_input
, preserve_keys
));
896 TypedValue
HHVM_FUNCTION(array_shift
,
898 const auto* cell_array
= array
->asCell();
899 if (UNLIKELY(!isMutableContainer(*cell_array
))) {
900 raise_warning("array_shift() expects parameter 1 to be an "
901 "array or mutable collection");
902 return make_tv
<KindOfNull
>();
904 if (!getContainerSize(array
)) {
905 return make_tv
<KindOfNull
>();
907 if (isArrayLikeType(cell_array
->m_type
)) {
908 if (auto ref
= array
.getVariantOrNull()) {
909 return tvReturn(ref
->asArrRef().dequeue());
911 auto ad
= cell_array
->m_data
.parr
;
913 auto first
= ad
->iter_begin();
914 return tvReturn(ad
->getValue(first
));
916 return make_tv
<KindOfNull
>();
918 assertx(cell_array
->m_type
== KindOfObject
);
919 return tvReturn(collections::shift(cell_array
->m_data
.pobj
));
922 TypedValue
HHVM_FUNCTION(array_slice
,
923 TypedValue cell_input
,
925 const Variant
& length
/* = uninit_variant */,
926 bool preserve_keys
/* = false */) {
927 if (UNLIKELY(!isContainer(cell_input
))) {
928 raise_warning("Invalid operand type was used: %s expects "
929 "an array or collection as argument 1",
931 return make_tv
<KindOfNull
>();
933 int64_t len
= length
.isNull() ? 0x7FFFFFFF : length
.toInt64();
935 const int64_t num_in
= getContainerSize(cell_input
);
936 if (offset
> num_in
) {
938 } else if (offset
< 0 && (offset
= (num_in
+ offset
)) < 0) {
942 auto const maxLen
= num_in
- offset
;
945 } else if (len
> maxLen
) {
950 return make_tv
<KindOfPersistentArray
>(staticEmptyArray());
953 bool input_is_packed
= isPackedContainer(cell_input
);
955 // If the slice covers the entire input container, we can just nop when
956 // preserve_keys is true, or when preserve_keys is false but the container
957 // is packed so we know the keys already map to [0,N].
958 if (offset
== 0 && len
== num_in
&& (preserve_keys
|| input_is_packed
)) {
959 if (isArrayType(cell_input
.m_type
)) {
960 return tvReturn(Variant
{cell_input
.m_data
.parr
});
962 if (isArrayLikeType(cell_input
.m_type
)) {
963 return tvReturn(ArrNR
{cell_input
.m_data
.parr
}.asArray().toPHPArray());
965 return tvReturn(cell_input
.m_data
.pobj
->toArray());
969 ArrayIter
iter(cell_input
);
970 for (; pos
< offset
&& iter
; ++pos
, ++iter
) {}
972 if (input_is_packed
&& (offset
== 0 || !preserve_keys
)) {
973 PackedArrayInit
ret(len
);
974 for (; pos
< (offset
+ len
) && iter
; ++pos
, ++iter
) {
975 ret
.appendWithRef(iter
.secondValPlus());
977 return tvReturn(ret
.toVariant());
980 // Otherwise PackedArrayInit can't be used because non-numeric keys are
981 // preserved even when preserve_keys is false
982 Array ret
= Array::attach(PackedArray::MakeReserve(len
));
983 for (; pos
< (offset
+ len
) && iter
; ++pos
, ++iter
) {
984 Variant
key(iter
.first());
985 bool doAppend
= !preserve_keys
&& key
.isNumeric();
987 ret
.appendWithRef(iter
.secondValPlus());
989 ret
.setWithRef(key
, iter
.secondValPlus(), true);
992 return tvReturn(std::move(ret
));
995 Variant
array_splice(VRefParam input
, int offset
,
996 const Variant
& length
, const Variant
& replacement
) {
997 getCheckedArrayVariant(input
);
998 if (arr_input
.isHackArray()) {
999 throw_expected_array_exception("array_splice");
1002 Array ret
= Array::Create();
1003 int64_t len
= length
.isNull() ? 0x7FFFFFFF : length
.toInt64();
1004 input
.assignIfRef(ArrayUtil::Splice(arr_input
, offset
, len
, replacement
, &ret
));
1008 TypedValue
HHVM_FUNCTION(array_splice
,
1011 const Variant
& length
,
1012 const Variant
& replacement
) {
1013 return tvReturn(array_splice(input
, offset
, length
, replacement
));
1016 TypedValue
HHVM_FUNCTION(array_sum
,
1017 const Variant
& input
) {
1018 if (UNLIKELY(!isContainer(input
))) {
1019 raise_warning("Invalid operand type was used: %s expects "
1020 "an array or collection as argument 1",
1022 return make_tv
<KindOfNull
>();
1026 ArrayIter
iter(input
);
1027 for (; iter
; ++iter
) {
1028 auto const rval
= iter
.secondRvalPlus().unboxed();
1030 switch (rval
.type()) {
1036 i
+= cellToInt(rval
.tv());
1042 case KindOfPersistentString
:
1043 case KindOfString
: {
1046 if (rval
.val().pstr
->isNumericWithVal(ti
, td
, 1) == KindOfInt64
) {
1054 case KindOfPersistentVec
:
1056 case KindOfPersistentDict
:
1058 case KindOfPersistentKeyset
:
1060 case KindOfPersistentArray
:
1063 case KindOfResource
:
1068 return make_tv
<KindOfInt64
>(i
);
1072 for (; iter
; ++iter
) {
1073 auto const rval
= iter
.secondRvalPlus().unboxed();
1074 switch (rval
.type()) {
1078 d
+= cellToDouble(rval
.tv());
1085 case KindOfResource
:
1090 return make_tv
<KindOfDouble
>(d
);
1093 TypedValue
HHVM_FUNCTION(array_unshift
,
1096 const Array
& args
/* = null array */) {
1097 const auto* cell_array
= array
->asCell();
1098 if (UNLIKELY(!isContainer(*cell_array
))) {
1099 raise_warning("%s() expects parameter 1 to be an array, Vector, or Set",
1100 __FUNCTION__
+2 /* remove the "f_" prefix */);
1101 return make_tv
<KindOfNull
>();
1103 if (isArrayLikeType(cell_array
->m_type
)) {
1104 auto ref_array
= array
.getVariantOrNull();
1106 return make_tv
<KindOfInt64
>(
1107 cell_array
->m_data
.parr
->size() + args
.size() + 1
1110 if (cell_array
->m_data
.parr
->isVectorData()) {
1111 if (!args
.empty()) {
1112 auto pos_limit
= args
->iter_end();
1113 for (ssize_t pos
= args
->iter_last(); pos
!= pos_limit
;
1114 pos
= args
->iter_rewind(pos
)) {
1115 ref_array
->asArrRef().prepend(args
->atPos(pos
));
1118 ref_array
->asArrRef().prepend(var
);
1121 auto newArray
= Array::attach(
1122 MixedArray::MakeReserveSame(cell_array
->m_data
.parr
, 0));
1123 newArray
.append(var
);
1124 if (!args
.empty()) {
1125 auto pos_limit
= args
->iter_end();
1126 for (ssize_t pos
= args
->iter_begin(); pos
!= pos_limit
;
1127 pos
= args
->iter_advance(pos
)) {
1128 newArray
.append(args
->atPos(pos
));
1131 if (cell_array
->m_data
.parr
->isKeyset()) {
1132 for (ArrayIter
iter(array
.toArray()); iter
; ++iter
) {
1133 Variant
key(iter
.first());
1134 newArray
.append(key
);
1137 for (ArrayIter
iter(array
.toArray()); iter
; ++iter
) {
1138 Variant
key(iter
.first());
1139 if (key
.isInteger()) {
1140 newArray
.appendWithRef(iter
.secondVal());
1142 newArray
.setWithRef(key
, iter
.secondVal(), true);
1146 *ref_array
= std::move(newArray
);
1148 // Reset the array's internal pointer
1149 ref_array
->asArrRef()->reset();
1151 return make_tv
<KindOfInt64
>(ref_array
->asArrRef().size());
1153 // Handle collections
1154 assertx(cell_array
->m_type
== KindOfObject
);
1155 auto* obj
= cell_array
->m_data
.pobj
;
1156 assertx(obj
->isCollection());
1157 switch (obj
->collectionType()) {
1158 case CollectionType::Vector
: {
1159 auto* vec
= static_cast<c_Vector
*>(obj
);
1160 if (!args
.empty()) {
1161 auto pos_limit
= args
->iter_end();
1162 for (ssize_t pos
= args
->iter_last(); pos
!= pos_limit
;
1163 pos
= args
->iter_rewind(pos
)) {
1164 vec
->addFront(tvToCell(args
->atPos(pos
)));
1167 vec
->addFront(*var
.asCell());
1168 return make_tv
<KindOfInt64
>(vec
->size());
1170 case CollectionType::Set
: {
1171 auto* st
= static_cast<c_Set
*>(obj
);
1172 if (!args
.empty()) {
1173 auto pos_limit
= args
->iter_end();
1174 for (ssize_t pos
= args
->iter_last(); pos
!= pos_limit
;
1175 pos
= args
->iter_rewind(pos
)) {
1176 st
->addFront(tvToCell(args
->atPos(pos
)));
1179 st
->addFront(*var
.asCell());
1180 return make_tv
<KindOfInt64
>(st
->size());
1182 case CollectionType::Map
:
1183 case CollectionType::Pair
:
1184 case CollectionType::ImmVector
:
1185 case CollectionType::ImmMap
:
1186 case CollectionType::ImmSet
:
1189 raise_warning("%s() expects parameter 1 to be an array, Vector, or Set",
1190 __FUNCTION__
+2 /* remove the "f_" prefix */);
1191 return make_tv
<KindOfNull
>();
1194 Variant
array_values(const Variant
& input
) {
1195 auto const cell
= *input
.asCell();
1196 if (isArrayType(cell
.m_type
)) {
1197 if (cell
.m_data
.parr
->isPacked()) {
1200 if (cell
.m_data
.parr
->isMixed()) {
1201 if (MixedArray::IsStrictVector(cell
.m_data
.parr
)) {
1204 } else if (cell
.m_data
.parr
->isApcArray() &&
1205 APCLocalArray::IsVectorData(cell
.m_data
.parr
)) {
1210 folly::Optional
<PackedArrayInit
> ai
;
1211 auto ok
= IterateV(cell
,
1212 [&](ArrayData
* adata
) {
1213 ai
.emplace(adata
->size());
1216 ai
->appendWithRef(v
);
1218 [&](ObjectData
* coll
) {
1219 if (coll
->collectionType() == CollectionType::Pair
) {
1225 raise_warning("array_values() expects parameter 1 to be an array "
1230 assertx(ai
.hasValue());
1231 return ai
->toVariant();
1234 TypedValue
HHVM_FUNCTION(array_values
,
1235 const Variant
& input
) {
1236 return tvReturn(array_values(input
));
1239 static void walk_func(Variant
& value
,
1241 const Variant
& userdata
,
1243 CallCtx
* ctx
= (CallCtx
*)data
;
1244 int nargs
= userdata
.isInitialized() ? 3 : 2;
1245 TypedValue args
[3] = { *value
.asRef(), *key
.asCell(), *userdata
.asCell() };
1247 g_context
->invokeFuncFew(*ctx
, nargs
, args
)
1251 bool HHVM_FUNCTION(array_walk_recursive
,
1253 const Variant
& funcname
,
1254 const Variant
& userdata
/* = uninit_variant */) {
1255 if (!input
.isPHPArray()) {
1256 throw_expected_array_exception("array_walk_recursive");
1261 vm_decode_function(funcname
, cf(), false, ctx
);
1262 if (ctx
.func
== NULL
) {
1266 Variant
var(input
, Variant::WithRefBind
{});
1267 ArrayUtil::Walk(var
, walk_func
, &ctx
, true, &seen
, userdata
);
1271 bool HHVM_FUNCTION(array_walk
,
1273 const Variant
& funcname
,
1274 const Variant
& userdata
/* = uninit_variant */) {
1275 if (!input
.isPHPArray()) {
1276 throw_expected_array_exception("array_walk");
1281 vm_decode_function(funcname
, cf(), false, ctx
);
1282 if (ctx
.func
== NULL
) {
1285 Variant
var(input
, Variant::WithRefBind
{});
1286 ArrayUtil::Walk(var
, walk_func
, &ctx
, false, NULL
, userdata
);
1290 static void compact(PointerSet
& seen
, VarEnv
* v
, Array
&ret
,
1291 const Variant
& var
) {
1292 if (var
.isArray()) {
1293 auto adata
= var
.getArrayData();
1294 auto check
= couldRecur(var
, adata
);
1296 if (seen
.find(adata
) != seen
.end()) {
1297 raise_warning("compact(): recursion detected");
1302 for (ArrayIter
iter(adata
); iter
; ++iter
) {
1303 compact(seen
, v
, ret
, VarNR(iter
.secondVal()));
1305 if (check
) seen
.erase(adata
);
1307 String varname
= var
.toString();
1308 if (!varname
.empty() && v
->lookup(varname
.get()) != NULL
) {
1309 ret
.set(varname
, tvAsVariant(v
->lookup(varname
.get())));
1314 Array
HHVM_FUNCTION(compact
,
1315 const Variant
& varname
,
1316 const Array
& args
/* = null array */) {
1317 Array ret
= Array::attach(PackedArray::MakeReserve(args
.size() + 1));
1318 VarEnv
* v
= g_context
->getOrCreateVarEnv();
1321 compact(seen
, v
, ret
, varname
);
1322 if (!args
.empty()) compact(seen
, v
, ret
, args
);
1327 static int php_count_recursive(const Array
& array
) {
1328 long cnt
= array
.size();
1329 for (ArrayIter
iter(array
); iter
; ++iter
) {
1330 Variant value
= iter
.second();
1331 if (value
.isArray()) {
1332 const Array
& arr_value
= value
.asCArrRef();
1333 check_recursion_throw();
1334 cnt
+= php_count_recursive(arr_value
);
1340 bool HHVM_FUNCTION(shuffle
,
1342 if (!array
.isArray()) {
1343 throw_expected_array_exception("shuffle");
1346 array
.assignIfRef(ArrayUtil::Shuffle(array
));
1350 enum class CountMode
{
1355 int64_t HHVM_FUNCTION(count
,
1357 int64_t mode
/* = 0 */) {
1358 switch (var
.getType()) {
1366 case KindOfPersistentString
:
1368 case KindOfResource
:
1371 case KindOfPersistentVec
:
1373 case KindOfPersistentDict
:
1375 case KindOfPersistentKeyset
:
1377 case KindOfPersistentArray
:
1379 if ((CountMode
)mode
== CountMode::RECURSIVE
) {
1380 const Array
& arr_var
= var
.toCArrRef();
1381 return php_count_recursive(arr_var
);
1383 return var
.getArrayData()->size();
1387 Object obj
= var
.toObject();
1388 if (obj
->isCollection()) {
1389 return collections::getSize(obj
.get());
1391 if (obj
.instanceof(SystemLib::s_CountableClass
)) {
1392 return obj
->o_invoke_few_args(s_count
, 0).toInt64();
1403 int64_t HHVM_FUNCTION(sizeof,
1405 int64_t mode
/* = 0 */) {
1406 return HHVM_FN(count
)(var
, mode
);
1411 enum class NoCow
{};
1412 template<class DoCow
= void, class NonArrayRet
, class OpPtr
>
1413 static Variant
iter_op_impl(VRefParam refParam
, OpPtr op
, const String
& objOp
,
1414 NonArrayRet nonArray
,
1415 bool(ArrayData::*pred
)() const =
1416 &ArrayData::isInvalid
) {
1417 auto& cell
= *refParam
.wrapped().asCell();
1418 if (!isArrayLikeType(cell
.m_type
)) {
1419 if (cell
.m_type
== KindOfObject
) {
1420 auto obj
= refParam
.wrapped().toObject();
1421 if (obj
->instanceof(SystemLib::s_ArrayObjectClass
)) {
1422 return obj
->o_invoke_few_args(objOp
, 0);
1425 throw_bad_type_exception("expecting an array");
1426 return Variant(nonArray
);
1429 auto ad
= cell
.m_data
.parr
;
1430 auto constexpr doCow
= !std::is_same
<DoCow
, NoCow
>::value
;
1431 if (doCow
&& ad
->cowCheck() && !(ad
->*pred
)() &&
1432 !ad
->noCopyOnWrite()) {
1434 if (LIKELY(refParam
.isRefData())) {
1435 cellMove(make_array_like_tv(ad
), *refParam
.getRefData()->tv());
1437 req::ptr
<ArrayData
> tmp(ad
, req::ptr
<ArrayData
>::NoIncRef
{});
1448 s___current("__current"),
1452 s___reset("__reset"),
1456 Variant
HHVM_FUNCTION(each
,
1457 VRefParam refParam
) {
1458 return iter_op_impl(
1466 Variant
HHVM_FUNCTION(current
,
1467 VRefParam refParam
) {
1468 return iter_op_impl
<NoCow
>(
1470 &ArrayData::current
,
1476 Variant
HHVM_FUNCTION(pos
,
1477 VRefParam refParam
) {
1478 return HHVM_FN(current
)(refParam
);
1481 Variant
HHVM_FUNCTION(key
,
1482 VRefParam refParam
) {
1483 return iter_op_impl
<NoCow
>(
1491 Variant
HHVM_FUNCTION(next
,
1492 VRefParam refParam
) {
1493 return iter_op_impl(
1501 Variant
HHVM_FUNCTION(prev
,
1502 VRefParam refParam
) {
1503 return iter_op_impl(
1511 Variant
HHVM_FUNCTION(reset
,
1512 VRefParam refParam
) {
1513 return iter_op_impl(
1522 Variant
HHVM_FUNCTION(end
,
1523 VRefParam refParam
) {
1524 return iter_op_impl(
1533 bool HHVM_FUNCTION(in_array
,
1534 const Variant
& needle
,
1535 const Variant
& haystack
,
1536 bool strict
/* = false */) {
1539 IterateV(*haystack
.asCell(),
1540 [](ArrayData
*) { return false; },
1541 [&](TypedValue v
) -> bool {
1542 if (tvSame(v
, *needle
.asTypedValue())) {
1548 [](ObjectData
*) { return false; }) :
1549 IterateV(*haystack
.asCell(),
1550 [](ArrayData
*) { return false; },
1551 [&](TypedValue v
) -> bool {
1552 if (tvEqual(v
, *needle
.asTypedValue())) {
1558 [](ObjectData
*) { return false; });
1560 if (UNLIKELY(!ok
)) {
1561 raise_warning("in_array() expects parameter 2 to be an array "
1567 Variant
array_search(const Variant
& needle
,
1568 const Variant
& haystack
,
1569 bool strict
/* = false */) {
1570 Variant ret
= false;
1572 IterateKV(*haystack
.asCell(),
1573 [](ArrayData
*) { return false; },
1574 [&](Cell k
, TypedValue v
) -> bool {
1575 if (tvSame(v
, *needle
.asTypedValue())) {
1581 [](ObjectData
*) { return false; }) :
1582 IterateKV(*haystack
.asCell(),
1583 [](ArrayData
*) { return false; },
1584 [&](Cell k
, TypedValue v
) -> bool {
1585 if (tvEqual(v
, *needle
.asTypedValue())) {
1591 [](ObjectData
*) { return false; });
1593 if (UNLIKELY(!ok
)) {
1594 raise_warning("array_search() expects parameter 2 to be an array "
1602 TypedValue
HHVM_FUNCTION(array_search
,
1603 const Variant
& needle
,
1604 const Variant
& haystack
,
1605 bool strict
/* = false */) {
1606 return tvReturn(array_search(needle
, haystack
, strict
));
1609 TypedValue
HHVM_FUNCTION(range
,
1611 const Variant
& high
,
1612 const Variant
& step
/* = 1 */) {
1613 bool is_step_double
= false;
1615 if (step
.isDouble()) {
1616 dstep
= step
.toDouble();
1617 is_step_double
= true;
1618 } else if (step
.isString()) {
1621 DataType stype
= step
.toString().get()->isNumericWithVal(sn
, sd
, 0);
1622 if (stype
== KindOfDouble
) {
1623 is_step_double
= true;
1625 } else if (stype
== KindOfInt64
) {
1628 dstep
= step
.toDouble();
1631 dstep
= step
.toDouble();
1633 /* We only want positive step values. */
1634 if (dstep
< 0.0) dstep
*= -1;
1635 if (low
.isString() && high
.isString()) {
1636 String slow
= low
.toString();
1637 String shigh
= high
.toString();
1638 if (slow
.size() >= 1 && shigh
.size() >=1) {
1641 DataType type1
= slow
.get()->isNumericWithVal(n1
, d1
, 0);
1642 DataType type2
= shigh
.get()->isNumericWithVal(n2
, d2
, 0);
1643 if (type1
== KindOfDouble
|| type2
== KindOfDouble
|| is_step_double
) {
1644 if (type1
!= KindOfDouble
) d1
= slow
.toDouble();
1645 if (type2
!= KindOfDouble
) d2
= shigh
.toDouble();
1646 return tvReturn(ArrayUtil::Range(d1
, d2
, dstep
));
1649 int64_t lstep
= double_to_int64(dstep
);
1650 if (type1
== KindOfInt64
|| type2
== KindOfInt64
) {
1651 if (type1
!= KindOfInt64
) n1
= slow
.toInt64();
1652 if (type2
!= KindOfInt64
) n2
= shigh
.toInt64();
1653 return tvReturn(ArrayUtil::Range(n1
, n2
, lstep
));
1656 return tvReturn(ArrayUtil::Range((unsigned char)slow
.charAt(0),
1657 (unsigned char)shigh
.charAt(0), lstep
));
1661 if (low
.is(KindOfDouble
) || high
.is(KindOfDouble
) || is_step_double
) {
1662 return tvReturn(ArrayUtil::Range(low
.toDouble(), high
.toDouble(), dstep
));
1665 int64_t lstep
= double_to_int64(dstep
);
1666 return tvReturn(ArrayUtil::Range(low
.toInt64(), high
.toInt64(), lstep
));
1668 ///////////////////////////////////////////////////////////////////////////////
1669 // diff/intersect helpers
1671 static int cmp_func(const Variant
& v1
, const Variant
& v2
, const void *data
) {
1672 auto callback
= static_cast<const Variant
*>(data
);
1673 return vm_call_user_func(*callback
, make_packed_array(v1
, v2
)).toInt32();
1676 // PHP 5.x does different things when diffing against the same array,
1677 // particularly when the comparison function is outside the norm of
1678 // return -1, 0, 1 specification. To do what PHP 5.x in these cases,
1679 // use the RuntimeOption
1681 #define diff_intersect_body(type, vararg, intersect_params) \
1682 getCheckedArray(array1); \
1683 if (!arr_array1.size()) { \
1684 return tvReturn(empty_array()); \
1686 Array ret = Array::Create(); \
1687 if (RuntimeOption::EnableZendSorting) { \
1688 getCheckedArray(array2); \
1689 if (arr_array1.same(arr_array2)) { \
1690 return tvReturn(std::move(ret)); \
1693 ret = arr_array1.type(array2, intersect_params); \
1695 for (ArrayIter iter(vararg); iter; ++iter) { \
1696 ret = ret.type(iter.second(), intersect_params); \
1697 if (!ret.size()) break; \
1700 return tvReturn(std::move(ret));
1702 ///////////////////////////////////////////////////////////////////////////////
1705 static inline void addToSetHelper(const req::ptr
<c_Set
>& st
,
1708 bool convertIntLikeStrs
) {
1709 if (c
.m_type
== KindOfInt64
) {
1710 st
->add(c
.m_data
.num
);
1713 if (LIKELY(isStringType(c
.m_type
))) {
1716 s
= tvCastToStringData(c
);
1717 decRefStr(strTv
->m_data
.pstr
);
1718 strTv
->m_data
.pstr
= s
;
1721 if (convertIntLikeStrs
&& s
->isStrictlyInteger(n
)) {
1722 if (checkHACIntishCast()) raise_intish_index_cast();
1730 static inline bool checkSetHelper(const req::ptr
<c_Set
>& st
,
1733 bool convertIntLikeStrs
) {
1734 if (c
.m_type
== KindOfInt64
) {
1735 return st
->contains(c
.m_data
.num
);
1738 if (LIKELY(isStringType(c
.m_type
))) {
1741 s
= tvCastToStringData(c
);
1742 decRefStr(strTv
->m_data
.pstr
);
1743 strTv
->m_data
.pstr
= s
;
1746 if (convertIntLikeStrs
&& s
->isStrictlyInteger(n
)) {
1747 if (checkHACIntishCast()) raise_intish_index_cast();
1748 return st
->contains(n
);
1750 return st
->contains(s
);
1753 static void containerValuesToSetHelper(const req::ptr
<c_Set
>& st
,
1754 const Variant
& container
) {
1755 Variant
strHolder(empty_string_variant());
1756 TypedValue
* strTv
= strHolder
.asTypedValue();
1757 for (ArrayIter
iter(container
); iter
; ++iter
) {
1758 auto const c
= tvToCell(iter
.secondValPlus());
1759 addToSetHelper(st
, c
, strTv
, true);
1763 static void containerKeysToSetHelper(const req::ptr
<c_Set
>& st
,
1764 const Variant
& container
) {
1765 Variant
strHolder(empty_string_variant());
1766 TypedValue
* strTv
= strHolder
.asTypedValue();
1767 bool isKey
= isArrayLikeType(container
.asCell()->m_type
);
1768 for (ArrayIter
iter(container
); iter
; ++iter
) {
1769 addToSetHelper(st
, *iter
.first().asCell(), strTv
, !isKey
);
1773 #define ARRAY_DIFF_PRELUDE() \
1774 /* Check to make sure all inputs are containers */ \
1775 const auto& c1 = *container1.asCell(); \
1776 const auto& c2 = *container2.asCell(); \
1777 if (UNLIKELY(!isContainer(c1) || !isContainer(c2))) { \
1778 raise_warning("%s() expects parameter %d to be an array or collection", \
1779 __FUNCTION__+2, /* remove the "f_" prefix */ \
1780 isContainer(c1) ? 2 : 1); \
1781 return make_tv<KindOfNull>(); \
1783 bool moreThanTwo = !args.empty(); \
1784 size_t largestSize = getContainerSize(c2); \
1785 if (UNLIKELY(moreThanTwo)) { \
1787 for (ArrayIter argvIter(args); argvIter; ++argvIter, ++pos) { \
1788 auto const c = tvToCell(argvIter.secondVal()); \
1789 if (!isContainer(c)) { \
1790 raise_warning("%s() expects parameter %d to be an array or collection",\
1791 __FUNCTION__+2, /* remove the "f_" prefix */ \
1793 return make_tv<KindOfNull>(); \
1795 size_t sz = getContainerSize(c); \
1796 if (sz > largestSize) { \
1801 /* If container1 is empty, we can stop here and return the empty array */ \
1802 if (!getContainerSize(c1)) { \
1803 return make_tv<KindOfPersistentArray>(staticEmptyArray()); \
1805 /* If all of the containers (except container1) are empty, we can just \
1806 return container1 (converting it to an array if needed) */ \
1807 if (!largestSize) { \
1808 if (isArrayLikeType(c1.m_type)) { \
1809 return tvReturn(container1); \
1811 return tvReturn(container1.toArray()); \
1814 Array ret = Array::Create();
1816 TypedValue
HHVM_FUNCTION(array_diff
,
1817 const Variant
& container1
,
1818 const Variant
& container2
,
1819 const Array
& args
/* = null array */) {
1820 SuppressHackArrCompatNotices suppress
;
1821 ARRAY_DIFF_PRELUDE()
1822 // Put all of the values from all the containers (except container1 into a
1823 // Set. All types aside from integer and string will be cast to string, and
1824 // we also convert int-like strings to integers.
1825 auto st
= req::make
<c_Set
>();
1826 st
->reserve(largestSize
);
1827 containerValuesToSetHelper(st
, container2
);
1828 if (UNLIKELY(moreThanTwo
)) {
1829 for (ArrayIter
argvIter(args
); argvIter
; ++argvIter
) {
1830 containerValuesToSetHelper(st
, VarNR(argvIter
.secondVal()));
1833 // Loop over container1, only copying over key/value pairs where the value
1834 // is not present in the Set. When checking if a value is present in the
1835 // Set, any value that is not an integer or string is cast to a string, and
1836 // we convert int-like strings to integers.
1837 Variant
strHolder(empty_string_variant());
1838 TypedValue
* strTv
= strHolder
.asTypedValue();
1839 bool isKey
= isArrayLikeType(c1
.m_type
);
1840 for (ArrayIter
iter(container1
); iter
; ++iter
) {
1841 auto const c
= tvToCell(iter
.secondValPlus());
1842 if (checkSetHelper(st
, c
, strTv
, true)) continue;
1843 ret
.setWithRef(iter
.first(), iter
.secondValPlus(), isKey
);
1845 return tvReturn(std::move(ret
));
1848 TypedValue
HHVM_FUNCTION(array_diff_key
,
1849 const Variant
& container1
,
1850 const Variant
& container2
,
1851 const Array
& args
/* = null array */) {
1852 SuppressHackArrCompatNotices suppress
;
1854 ARRAY_DIFF_PRELUDE()
1855 // If we're only dealing with two containers and if they are both arrays,
1856 // we can avoid creating an intermediate Set
1858 isArrayLikeType(c1
.m_type
) &&
1859 isArrayLikeType(c2
.m_type
)) {
1860 auto ad2
= c2
.m_data
.parr
;
1861 for (ArrayIter
iter(container1
); iter
; ++iter
) {
1862 auto key
= iter
.first();
1863 const auto& c
= *key
.asCell();
1864 if (c
.m_type
== KindOfInt64
) {
1865 if (ad2
->exists(c
.m_data
.num
)) continue;
1867 assertx(isStringType(c
.m_type
));
1868 if (ad2
->exists(c
.m_data
.pstr
)) continue;
1870 ret
.setWithRef(key
, iter
.secondValPlus(), true);
1872 return tvReturn(std::move(ret
));
1874 // Put all of the keys from all the containers (except container1) into a
1875 // Set. All types aside from integer and string will be cast to string, and
1876 // we also convert int-like strings to integers.
1877 auto st
= req::make
<c_Set
>();
1878 st
->reserve(largestSize
);
1879 containerKeysToSetHelper(st
, container2
);
1880 if (UNLIKELY(moreThanTwo
)) {
1881 for (ArrayIter
argvIter(args
); argvIter
; ++argvIter
) {
1882 containerKeysToSetHelper(st
, VarNR(argvIter
.secondVal()));
1885 // Loop over container1, only copying over key/value pairs where the key is
1886 // not present in the Set. When checking if a key is present in the Set, any
1887 // key that is not an integer or string is cast to a string, and we convert
1888 // int-like strings to integers.
1889 Variant
strHolder(empty_string_variant());
1890 TypedValue
* strTv
= strHolder
.asTypedValue();
1891 bool isKey
= isArrayLikeType(c1
.m_type
);
1892 for (ArrayIter
iter(container1
); iter
; ++iter
) {
1893 auto key
= iter
.first();
1894 const auto& c
= *key
.asCell();
1895 if (checkSetHelper(st
, c
, strTv
, !isKey
)) continue;
1896 ret
.setWithRef(key
, iter
.secondValPlus(), isKey
);
1898 return tvReturn(std::move(ret
));
1901 #undef ARRAY_DIFF_PRELUDE
1903 TypedValue
HHVM_FUNCTION(array_udiff
,
1904 const Variant
& array1
,
1905 const Variant
& array2
,
1906 const Variant
& data_compare_func
,
1907 const Array
& args
/* = null array */) {
1908 Variant func
= data_compare_func
;
1910 if (!extra
.empty()) {
1911 extra
.prepend(func
);
1914 diff_intersect_body(diff
, extra
, false COMMA
true COMMA NULL COMMA NULL
1915 COMMA cmp_func COMMA
&func
);
1918 TypedValue
HHVM_FUNCTION(array_diff_assoc
,
1919 const Variant
& array1
,
1920 const Variant
& array2
,
1921 const Array
& args
/* = null array */) {
1922 diff_intersect_body(diff
, args
, true COMMA
true);
1925 TypedValue
HHVM_FUNCTION(array_diff_uassoc
,
1926 const Variant
& array1
,
1927 const Variant
& array2
,
1928 const Variant
& key_compare_func
,
1929 const Array
& args
/* = null array */) {
1930 Variant func
= key_compare_func
;
1932 if (!extra
.empty()) {
1933 extra
.prepend(func
);
1936 diff_intersect_body(diff
, extra
, true COMMA
true COMMA cmp_func COMMA
&func
);
1939 TypedValue
HHVM_FUNCTION(array_udiff_assoc
,
1940 const Variant
& array1
,
1941 const Variant
& array2
,
1942 const Variant
& data_compare_func
,
1943 const Array
& args
/* = null array */) {
1944 Variant func
= data_compare_func
;
1946 if (!extra
.empty()) {
1947 extra
.prepend(func
);
1950 diff_intersect_body(diff
, extra
, true COMMA
true COMMA NULL COMMA NULL
1951 COMMA cmp_func COMMA
&func
);
1954 TypedValue
HHVM_FUNCTION(array_udiff_uassoc
,
1955 const Variant
& array1
,
1956 const Variant
& array2
,
1957 const Variant
& data_compare_func
,
1958 const Variant
& key_compare_func
,
1959 const Array
& args
/* = null array */) {
1960 Variant data_func
= data_compare_func
;
1961 Variant key_func
= key_compare_func
;
1963 if (!extra
.empty()) {
1964 extra
.prepend(key_func
);
1965 extra
.prepend(data_func
);
1966 key_func
= extra
.pop();
1967 data_func
= extra
.pop();
1969 diff_intersect_body(diff
, extra
, true
1970 COMMA
true COMMA cmp_func COMMA
&key_func
1971 COMMA cmp_func COMMA
&data_func
);
1974 TypedValue
HHVM_FUNCTION(array_diff_ukey
,
1975 const Variant
& array1
,
1976 const Variant
& array2
,
1977 const Variant
& key_compare_func
,
1978 const Array
& args
/* = null array */) {
1979 Variant func
= key_compare_func
;
1981 if (!extra
.empty()) {
1982 extra
.prepend(func
);
1985 diff_intersect_body(diff
, extra
, true COMMA
false COMMA cmp_func COMMA
&func
);
1988 ///////////////////////////////////////////////////////////////////////////////
1989 // intersect functions
1991 static inline TypedValue
* makeContainerListHelper(const Variant
& a
,
1995 assertx(count
== argv
.size() + 1);
1996 assertx(0 <= smallestPos
);
1997 assertx(smallestPos
< count
);
1998 // Allocate a TypedValue array and copy 'a' and the contents of 'argv'
1999 TypedValue
* containers
= req::make_raw_array
<TypedValue
>(count
);
2000 tvCopy(*a
.asCell(), containers
[0]);
2002 for (ArrayIter
argvIter(argv
); argvIter
; ++argvIter
, ++pos
) {
2003 cellCopy(tvToCell(argvIter
.secondVal()), containers
[pos
]);
2005 // Perform a swap so that the smallest container occurs at the first
2006 // position in the TypedValue array; this helps improve the performance
2007 // of containerValuesIntersectHelper()
2008 if (smallestPos
!= 0) {
2010 tvCopy(containers
[0], tmp
);
2011 tvCopy(containers
[smallestPos
], containers
[0]);
2012 tvCopy(tmp
, containers
[smallestPos
]);
2017 static inline void addToIntersectMapHelper(const req::ptr
<c_Map
>& mp
,
2019 TypedValue
* intOneTv
,
2021 bool convertIntLikeStrs
) {
2022 if (c
.m_type
== KindOfInt64
) {
2023 mp
->set(c
.m_data
.num
, *intOneTv
);
2026 if (LIKELY(isStringType(c
.m_type
))) {
2029 s
= tvCastToStringData(c
);
2030 decRefStr(strTv
->m_data
.pstr
);
2031 strTv
->m_data
.pstr
= s
;
2034 if (convertIntLikeStrs
&& s
->isStrictlyInteger(n
)) {
2035 if (checkHACIntishCast()) raise_intish_index_cast();
2036 mp
->set(n
, *intOneTv
);
2038 mp
->set(s
, *intOneTv
);
2043 static inline void updateIntersectMapHelper(const req::ptr
<c_Map
>& mp
,
2047 bool convertIntLikeStrs
) {
2048 if (c
.m_type
== KindOfInt64
) {
2049 auto val
= mp
->get(c
.m_data
.num
);
2050 if (val
&& val
->m_data
.num
== pos
) {
2051 assertx(val
->m_type
== KindOfInt64
);
2056 if (LIKELY(isStringType(c
.m_type
))) {
2059 s
= tvCastToStringData(c
);
2060 decRefStr(strTv
->m_data
.pstr
);
2061 strTv
->m_data
.pstr
= s
;
2064 if (convertIntLikeStrs
&& s
->isStrictlyInteger(n
)) {
2065 if (checkHACIntishCast()) raise_intish_index_cast();
2066 auto val
= mp
->get(n
);
2067 if (val
&& val
->m_data
.num
== pos
) {
2068 assertx(val
->m_type
== KindOfInt64
);
2072 auto val
= mp
->get(s
);
2073 if (val
&& val
->m_data
.num
== pos
) {
2074 assertx(val
->m_type
== KindOfInt64
);
2081 static void containerValuesIntersectHelper(const req::ptr
<c_Set
>& st
,
2082 TypedValue
* containers
,
2084 assertx(count
>= 2);
2085 auto mp
= req::make
<c_Map
>();
2086 Variant
strHolder(empty_string_variant());
2087 TypedValue
* strTv
= strHolder
.asTypedValue();
2088 TypedValue intOneTv
= make_tv
<KindOfInt64
>(1);
2089 for (ArrayIter
iter(tvAsCVarRef(&containers
[0])); iter
; ++iter
) {
2090 auto const c
= tvToCell(iter
.secondValPlus());
2091 // For each value v in containers[0], we add the key/value pair (v, 1)
2092 // to the map. If a value (after various conversions) occurs more than
2093 // once in the container, we'll simply overwrite the old entry and that's
2095 addToIntersectMapHelper(mp
, c
, &intOneTv
, strTv
, true);
2097 for (int pos
= 1; pos
< count
; ++pos
) {
2098 for (ArrayIter
iter(tvAsCVarRef(&containers
[pos
])); iter
; ++iter
) {
2099 auto const c
= tvToCell(iter
.secondValPlus());
2100 // We check if the value is present as a key in the map. If an entry
2101 // exists and its value equals pos, we increment it, otherwise we do
2102 // nothing. This is essential so that we don't accidentally double-count
2103 // a key (after various conversions) that occurs in the container more
2105 updateIntersectMapHelper(mp
, c
, pos
, strTv
, true);
2108 for (ArrayIter
iter(mp
.get()); iter
; ++iter
) {
2109 // For each key in the map, we copy the key to the set if the
2110 // corresponding value is equal to pos exactly (which means it
2111 // was present in all of the containers).
2112 auto const rval
= iter
.secondRvalPlus().unboxed();
2113 assertx(rval
.type() == KindOfInt64
);
2114 if (rval
.val().num
== count
) {
2115 st
->add(*iter
.first().asCell());
2120 static void containerKeysIntersectHelper(const req::ptr
<c_Set
>& st
,
2121 TypedValue
* containers
,
2123 assertx(count
>= 2);
2124 auto mp
= req::make
<c_Map
>();
2125 Variant
strHolder(empty_string_variant());
2126 TypedValue
* strTv
= strHolder
.asTypedValue();
2127 TypedValue intOneTv
= make_tv
<KindOfInt64
>(1);
2128 bool isKey
= isArrayLikeType(containers
[0].m_type
);
2129 for (ArrayIter
iter(tvAsCVarRef(&containers
[0])); iter
; ++iter
) {
2130 auto key
= iter
.first();
2131 const auto& c
= *key
.asCell();
2132 // For each key k in containers[0], we add the key/value pair (k, 1)
2133 // to the map. If a key (after various conversions) occurs more than
2134 // once in the container, we'll simply overwrite the old entry and
2136 addToIntersectMapHelper(mp
, c
, &intOneTv
, strTv
, !isKey
);
2138 for (int pos
= 1; pos
< count
; ++pos
) {
2139 isKey
= isArrayLikeType(containers
[pos
].m_type
);
2140 for (ArrayIter
iter(tvAsCVarRef(&containers
[pos
])); iter
; ++iter
) {
2141 auto key
= iter
.first();
2142 const auto& c
= *key
.asCell();
2143 updateIntersectMapHelper(mp
, c
, pos
, strTv
, !isKey
);
2146 for (ArrayIter
iter(mp
.get()); iter
; ++iter
) {
2147 // For each key in the map, we copy the key to the set if the
2148 // corresponding value is equal to pos exactly (which means it
2149 // was present in all of the containers).
2150 auto const rval
= iter
.secondRvalPlus().unboxed();
2151 assertx(rval
.type() == KindOfInt64
);
2152 if (rval
.val().num
== count
) {
2153 st
->add(*iter
.first().asCell());
2158 #define ARRAY_INTERSECT_PRELUDE() \
2159 /* Check to make sure all inputs are containers */ \
2160 const auto& c1 = *container1.asCell(); \
2161 const auto& c2 = *container2.asCell(); \
2162 if (!isContainer(c1) || !isContainer(c2)) { \
2163 raise_warning("%s() expects parameter %d to be an array or collection", \
2164 __FUNCTION__+2, /* remove the "f_" prefix */ \
2165 isContainer(c1) ? 2 : 1); \
2166 return make_tv<KindOfNull>(); \
2168 bool moreThanTwo = !args.empty(); \
2169 /* Keep track of which input container was the smallest (excluding \
2171 int smallestPos = 0; \
2172 size_t smallestSize = getContainerSize(c2); \
2173 if (UNLIKELY(moreThanTwo)) { \
2175 for (ArrayIter argvIter(args); argvIter; ++argvIter, ++pos) { \
2176 auto const c = tvToCell(argvIter.secondVal()); \
2177 if (!isContainer(c)) { \
2178 raise_warning("%s() expects parameter %d to be an array or collection",\
2179 __FUNCTION__+2, /* remove the "f_" prefix */ \
2181 return make_tv<KindOfNull>(); \
2183 size_t sz = getContainerSize(c); \
2184 if (sz < smallestSize) { \
2185 smallestSize = sz; \
2186 smallestPos = pos; \
2190 /* If any of the containers were empty, we can stop here and return the \
2192 if (!getContainerSize(c1) || !smallestSize) { \
2193 return make_tv<KindOfPersistentArray>(staticEmptyArray()); \
2195 Array ret = Array::Create();
2197 TypedValue
HHVM_FUNCTION(array_intersect
,
2198 const Variant
& container1
,
2199 const Variant
& container2
,
2200 const Array
& args
/* = null array */) {
2201 SuppressHackArrCompatNotices suppress
;
2203 ARRAY_INTERSECT_PRELUDE()
2204 // Build up a Set containing the values that are present in all the
2205 // containers (except container1)
2206 auto st
= req::make
<c_Set
>();
2207 if (LIKELY(!moreThanTwo
)) {
2208 // There is only one container (not counting container1) so we can
2209 // just call containerValuesToSetHelper() to build the Set.
2210 containerValuesToSetHelper(st
, container2
);
2212 // We're dealing with three or more containers. Copy all of the containers
2213 // (except the first) into a TypedValue array.
2214 int count
= args
.size() + 1;
2215 TypedValue
* containers
=
2216 makeContainerListHelper(container2
, args
, count
, smallestPos
);
2217 SCOPE_EXIT
{ req::free(containers
); };
2218 // Build a Set of the values that were present in all of the containers
2219 containerValuesIntersectHelper(st
, containers
, count
);
2221 // Loop over container1, only copying over key/value pairs where the value
2222 // is present in the Set. When checking if a value is present in the Set,
2223 // any value that is not an integer or string is cast to a string, and we
2224 // convert int-like strings to integers.
2225 Variant
strHolder(empty_string_variant());
2226 TypedValue
* strTv
= strHolder
.asTypedValue();
2227 bool isKey
= isArrayLikeType(c1
.m_type
);
2228 for (ArrayIter
iter(container1
); iter
; ++iter
) {
2229 auto const c
= tvToCell(iter
.secondValPlus());
2230 if (!checkSetHelper(st
, c
, strTv
, true)) continue;
2231 ret
.setWithRef(iter
.first(), iter
.secondValPlus(), isKey
);
2233 return tvReturn(std::move(ret
));
2236 TypedValue
HHVM_FUNCTION(array_intersect_key
,
2237 const Variant
& container1
,
2238 const Variant
& container2
,
2239 const Array
& args
/* = null array */) {
2240 ARRAY_INTERSECT_PRELUDE()
2241 // If we're only dealing with two containers and if they are both arrays,
2242 // we can avoid creating an intermediate Set
2244 isArrayLikeType(c1
.m_type
) &&
2245 isArrayLikeType(c2
.m_type
)) {
2246 auto ad2
= c2
.m_data
.parr
;
2247 for (ArrayIter
iter(container1
); iter
; ++iter
) {
2248 auto key
= iter
.first();
2249 const auto& c
= *key
.asCell();
2250 if (c
.m_type
== KindOfInt64
) {
2251 if (!ad2
->exists(c
.m_data
.num
)) continue;
2253 assertx(isStringType(c
.m_type
));
2254 if (!ad2
->exists(c
.m_data
.pstr
)) continue;
2256 ret
.setWithRef(key
, iter
.secondValPlus(), true);
2258 return tvReturn(std::move(ret
));
2260 // Build up a Set containing the keys that are present in all the containers
2261 // (except container1)
2262 auto st
= req::make
<c_Set
>();
2263 if (LIKELY(!moreThanTwo
)) {
2264 // There is only one container (not counting container1) so we can just
2265 // call containerKeysToSetHelper() to build the Set.
2266 containerKeysToSetHelper(st
, container2
);
2268 // We're dealing with three or more containers. Copy all of the containers
2269 // (except the first) into a TypedValue array.
2270 int count
= args
.size() + 1;
2271 TypedValue
* containers
=
2272 makeContainerListHelper(container2
, args
, count
, smallestPos
);
2273 SCOPE_EXIT
{ req::free(containers
); };
2274 // Build a Set of the keys that were present in all of the containers
2275 containerKeysIntersectHelper(st
, containers
, count
);
2277 // Loop over container1, only copying over key/value pairs where the key
2278 // is present in the Set. When checking if a key is present in the Set,
2279 // any value that is not an integer or string is cast to a string, and we
2280 // convert int-like strings to integers.
2281 Variant
strHolder(empty_string_variant());
2282 TypedValue
* strTv
= strHolder
.asTypedValue();
2283 bool isKey
= isArrayLikeType(c1
.m_type
);
2284 for (ArrayIter
iter(container1
); iter
; ++iter
) {
2285 auto key
= iter
.first();
2286 const auto& c
= *key
.asCell();
2287 if (!checkSetHelper(st
, c
, strTv
, !isKey
)) continue;
2288 ret
.setWithRef(key
, iter
.secondValPlus(), isKey
);
2290 return tvReturn(std::move(ret
));
2293 #undef ARRAY_INTERSECT_PRELUDE
2295 TypedValue
HHVM_FUNCTION(array_uintersect
,
2296 const Variant
& array1
,
2297 const Variant
& array2
,
2298 const Variant
& data_compare_func
,
2299 const Array
& args
/* = null array */) {
2300 Variant func
= data_compare_func
;
2302 if (!extra
.empty()) {
2303 extra
.prepend(func
);
2306 diff_intersect_body(intersect
, extra
, false COMMA
true COMMA NULL COMMA NULL
2307 COMMA cmp_func COMMA
&func
);
2310 TypedValue
HHVM_FUNCTION(array_intersect_assoc
,
2311 const Variant
& array1
,
2312 const Variant
& array2
,
2313 const Array
& args
/* = null array */) {
2314 diff_intersect_body(intersect
, args
, true COMMA
true);
2317 TypedValue
HHVM_FUNCTION(array_intersect_uassoc
,
2318 const Variant
& array1
,
2319 const Variant
& array2
,
2320 const Variant
& key_compare_func
,
2321 const Array
& args
/* = null array */) {
2322 Variant func
= key_compare_func
;
2324 if (!extra
.empty()) {
2325 extra
.prepend(func
);
2328 diff_intersect_body(intersect
, extra
, true COMMA
true
2329 COMMA cmp_func COMMA
&func
);
2332 TypedValue
HHVM_FUNCTION(array_uintersect_assoc
,
2333 const Variant
& array1
,
2334 const Variant
& array2
,
2335 const Variant
& data_compare_func
,
2336 const Array
& args
/* = null array */) {
2337 Variant func
= data_compare_func
;
2339 if (!extra
.empty()) {
2340 extra
.prepend(func
);
2343 diff_intersect_body(intersect
, extra
, true COMMA
true COMMA NULL COMMA NULL
2344 COMMA cmp_func COMMA
&func
);
2347 TypedValue
HHVM_FUNCTION(array_uintersect_uassoc
,
2348 const Variant
& array1
,
2349 const Variant
& array2
,
2350 const Variant
& data_compare_func
,
2351 const Variant
& key_compare_func
,
2352 const Array
& args
/* = null array */) {
2353 Variant data_func
= data_compare_func
;
2354 Variant key_func
= key_compare_func
;
2356 if (!extra
.empty()) {
2357 extra
.prepend(key_func
);
2358 extra
.prepend(data_func
);
2359 key_func
= extra
.pop();
2360 data_func
= extra
.pop();
2362 diff_intersect_body(intersect
, extra
, true COMMA
true COMMA cmp_func
2363 COMMA
&key_func COMMA cmp_func COMMA
&data_func
);
2366 TypedValue
HHVM_FUNCTION(array_intersect_ukey
,
2367 const Variant
& array1
,
2368 const Variant
& array2
,
2369 const Variant
& key_compare_func
,
2370 const Array
& args
/* = null array */) {
2371 Variant func
= key_compare_func
;
2373 if (!extra
.empty()) {
2374 extra
.prepend(func
);
2377 diff_intersect_body(intersect
, extra
, true COMMA
false
2378 COMMA cmp_func COMMA
&func
);
2381 ///////////////////////////////////////////////////////////////////////////////
2382 // sorting functions
2384 struct Collator final
: RequestEventHandler
{
2385 String
getLocale() {
2388 Intl::IntlError
&getErrorRef() {
2391 bool setLocale(const String
& locale
) {
2392 if (m_locale
.same(locale
)) {
2396 ucol_close(m_ucoll
);
2399 m_errcode
.clearError();
2400 UErrorCode error
= U_ZERO_ERROR
;
2401 m_ucoll
= ucol_open(locale
.data(), &error
);
2402 if (m_ucoll
== NULL
) {
2403 raise_warning("failed to load %s locale from icu data", locale
.data());
2406 if (U_FAILURE(error
)) {
2407 m_errcode
.setError(error
);
2408 ucol_close(m_ucoll
);
2416 UCollator
*getCollator() {
2420 bool setAttribute(int64_t attr
, int64_t val
) {
2422 Logger::Verbose("m_ucoll is NULL");
2425 m_errcode
.clearError();
2426 UErrorCode error
= U_ZERO_ERROR
;
2427 ucol_setAttribute(m_ucoll
, (UColAttribute
)attr
,
2428 (UColAttributeValue
)val
, &error
);
2429 if (U_FAILURE(error
)) {
2430 m_errcode
.setError(error
);
2431 Logger::Verbose("Error setting attribute value");
2437 bool setStrength(int64_t strength
) {
2439 Logger::Verbose("m_ucoll is NULL");
2442 ucol_setStrength(m_ucoll
, (UCollationStrength
)strength
);
2446 Variant
getErrorCode() {
2448 Logger::Verbose("m_ucoll is NULL");
2451 return m_errcode
.getErrorCode();
2454 void requestInit() override
{
2455 m_locale
= String(uloc_getDefault(), CopyString
);
2456 m_errcode
.clearError();
2457 UErrorCode error
= U_ZERO_ERROR
;
2458 m_ucoll
= ucol_open(m_locale
.data(), &error
);
2459 if (U_FAILURE(error
)) {
2460 m_errcode
.setError(error
);
2465 void requestShutdown() override
{
2467 m_errcode
.clearError(false);
2469 ucol_close(m_ucoll
);
2477 Intl::IntlError m_errcode
;
2479 IMPLEMENT_STATIC_REQUEST_LOCAL(Collator
, s_collator
);
2482 struct ArraySortTmp
{
2483 explicit ArraySortTmp(TypedValue
* arr
, SortFunction sf
) : m_arr(arr
) {
2484 m_ad
= arr
->m_data
.parr
->escalateForSort(sf
);
2485 assertx(m_ad
== arr
->m_data
.parr
|| m_ad
->hasExactlyOneRef());
2488 if (m_ad
!= m_arr
->m_data
.parr
) {
2489 Array tmp
= Array::attach(m_arr
->m_data
.parr
);
2490 m_arr
->m_data
.parr
= m_ad
;
2491 m_arr
->m_type
= m_ad
->toDataType();
2494 ArrayData
* operator->() { return m_ad
; }
2502 php_sort(VRefParam container
, int sort_flags
,
2503 bool ascending
, bool use_zend_sort
) {
2504 if (container
.isArray()) {
2505 auto ref
= container
.getVariantOrNull();
2506 if (!ref
) return true;
2507 if (use_zend_sort
) {
2508 return zend_sort(*ref
, sort_flags
, ascending
);
2510 SortFunction sf
= getSortFunction(SORTFUNC_SORT
, ascending
);
2511 ArraySortTmp
ast(ref
->asTypedValue(), sf
);
2512 ast
->sort(sort_flags
, ascending
);
2515 if (container
.isObject()) {
2516 ObjectData
* obj
= container
.getObjectData();
2517 if (obj
->isCollection() &&
2518 obj
->collectionType() == CollectionType::Vector
) {
2519 c_Vector
* vec
= static_cast<c_Vector
*>(obj
);
2520 vec
->sort(sort_flags
, ascending
);
2523 // other collections are not supported:
2524 // - Maps and Sets require associative sort
2525 // - Immutable collections are not to be modified
2527 throw_expected_array_or_collection_exception(ascending
? "sort" : "rsort");
2532 php_asort(VRefParam container
, int sort_flags
,
2533 bool ascending
, bool use_zend_sort
) {
2534 if (container
.isArray()) {
2535 auto ref
= container
.getVariantOrNull();
2536 if (!ref
) return true;
2537 if (use_zend_sort
) {
2538 return zend_asort(*ref
, sort_flags
, ascending
);
2540 SortFunction sf
= getSortFunction(SORTFUNC_ASORT
, ascending
);
2541 ArraySortTmp
ast(ref
->asTypedValue(), sf
);
2542 ast
->asort(sort_flags
, ascending
);
2545 if (container
.isObject()) {
2546 ObjectData
* obj
= container
.getObjectData();
2547 if (obj
->isCollection()) {
2548 auto type
= obj
->collectionType();
2549 if (type
== CollectionType::Map
|| type
== CollectionType::Set
) {
2550 HashCollection
* hc
= static_cast<HashCollection
*>(obj
);
2551 hc
->asort(sort_flags
, ascending
);
2556 throw_expected_array_or_collection_exception(ascending
? "asort" : "arsort");
2561 php_ksort(VRefParam container
, int sort_flags
, bool ascending
,
2562 bool use_zend_sort
) {
2563 if (container
.isArray()) {
2564 auto ref
= container
.getVariantOrNull();
2565 if (!ref
) return true;
2566 if (use_zend_sort
) {
2567 return zend_ksort(*ref
, sort_flags
, ascending
);
2569 SortFunction sf
= getSortFunction(SORTFUNC_KRSORT
, ascending
);
2570 ArraySortTmp
ast(ref
->asTypedValue(), sf
);
2571 ast
->ksort(sort_flags
, ascending
);
2574 if (container
.isObject()) {
2575 ObjectData
* obj
= container
.getObjectData();
2576 if (obj
->isCollection()) {
2577 auto type
= obj
->collectionType();
2578 if (type
== CollectionType::Map
|| type
== CollectionType::Set
) {
2579 HashCollection
* hc
= static_cast<HashCollection
*>(obj
);
2580 hc
->ksort(sort_flags
, ascending
);
2585 throw_expected_array_or_collection_exception(ascending
? "ksort" : "krsort");
2589 bool HHVM_FUNCTION(sort
,
2591 int sort_flags
/* = 0 */) {
2592 bool use_zend_sort
= RuntimeOption::EnableZendSorting
;
2593 return php_sort(array
, sort_flags
, true, use_zend_sort
);
2596 bool HHVM_FUNCTION(rsort
,
2598 int sort_flags
/* = 0 */) {
2599 bool use_zend_sort
= RuntimeOption::EnableZendSorting
;
2600 return php_sort(array
, sort_flags
, false, use_zend_sort
);
2603 bool HHVM_FUNCTION(asort
,
2605 int sort_flags
/* = 0 */) {
2606 bool use_zend_sort
= RuntimeOption::EnableZendSorting
;
2607 return php_asort(array
, sort_flags
, true, use_zend_sort
);
2610 bool HHVM_FUNCTION(arsort
,
2612 int sort_flags
/* = 0 */) {
2613 bool use_zend_sort
= RuntimeOption::EnableZendSorting
;
2614 return php_asort(array
, sort_flags
, false, use_zend_sort
);
2617 bool HHVM_FUNCTION(ksort
,
2619 int sort_flags
/* = 0 */) {
2620 bool use_zend_sort
= RuntimeOption::EnableZendSorting
;
2621 return php_ksort(array
, sort_flags
, true, use_zend_sort
);
2624 bool HHVM_FUNCTION(krsort
,
2626 int sort_flags
/* = 0 */) {
2627 bool use_zend_sort
= RuntimeOption::EnableZendSorting
;
2628 return php_ksort(array
, sort_flags
, false, use_zend_sort
);
2631 // NOTE: PHP's implementation of natsort and natcasesort accepts ArrayAccess
2632 // objects as well, which does not make much sense, and which is not supported
2635 bool HHVM_FUNCTION(natsort
, VRefParam array
) {
2636 return php_asort(array
, SORT_NATURAL
, true, false);
2639 bool HHVM_FUNCTION(natcasesort
, VRefParam array
) {
2640 return php_asort(array
, SORT_NATURAL_CASE
, true, false);
2643 bool HHVM_FUNCTION(usort
,
2644 VRefParam container
,
2645 const Variant
& cmp_function
) {
2646 if (container
.isArray()) {
2647 auto sort
= [](TypedValue
* arr_array
, const Variant
& cmp_function
) -> bool {
2648 if (RuntimeOption::EnableZendSorting
) {
2649 tvAsVariant(arr_array
).asArrRef().sort(cmp_func
, false, true,
2653 ArraySortTmp
ast(arr_array
, SORTFUNC_USORT
);
2654 return ast
->usort(cmp_function
);
2657 auto ref
= container
.getVariantOrNull();
2658 if (LIKELY(ref
!= nullptr)) {
2659 return sort(ref
->asTypedValue(), cmp_function
);
2661 auto tmp
= container
.wrapped();
2662 return sort(tmp
.asTypedValue(), cmp_function
);
2664 if (container
.isObject()) {
2665 ObjectData
* obj
= container
.getObjectData();
2666 if (obj
->isCollection()) {
2667 if (obj
->collectionType() == CollectionType::Vector
) {
2668 c_Vector
* vec
= static_cast<c_Vector
*>(obj
);
2669 return vec
->usort(cmp_function
);
2672 // other collections are not supported:
2673 // - Maps and Sets require associative sort
2674 // - Immutable collections are not to be modified
2676 throw_expected_array_or_collection_exception("usort");
2680 bool HHVM_FUNCTION(uasort
,
2681 VRefParam container
,
2682 const Variant
& cmp_function
) {
2683 if (container
.isArray()) {
2684 auto sort
= [](TypedValue
* arr_array
, const Variant
& cmp_function
) -> bool {
2685 if (RuntimeOption::EnableZendSorting
) {
2686 tvAsVariant(arr_array
).asArrRef().sort(cmp_func
, false, false,
2690 ArraySortTmp
ast(arr_array
, SORTFUNC_UASORT
);
2691 return ast
->uasort(cmp_function
);
2694 auto ref
= container
.getVariantOrNull();
2695 if (LIKELY(ref
!= nullptr)) {
2696 return sort(ref
->asTypedValue(), cmp_function
);
2698 auto tmp
= container
.wrapped();
2699 return sort(tmp
.asTypedValue(), cmp_function
);
2701 if (container
.isObject()) {
2702 ObjectData
* obj
= container
.getObjectData();
2703 if (obj
->isCollection()) {
2704 auto type
= obj
->collectionType();
2705 if (type
== CollectionType::Map
|| type
== CollectionType::Set
) {
2706 HashCollection
* hc
= static_cast<HashCollection
*>(obj
);
2707 return hc
->uasort(cmp_function
);
2710 // other collections are not supported:
2711 // - Vectors require a non-associative sort
2712 // - Immutable collections are not to be modified
2714 throw_expected_array_or_collection_exception("uasort");
2718 bool HHVM_FUNCTION(uksort
,
2719 VRefParam container
,
2720 const Variant
& cmp_function
) {
2721 if (container
.isArray()) {
2722 auto sort
= [](TypedValue
* arr_array
, const Variant
& cmp_function
) -> bool {
2723 ArraySortTmp
ast(arr_array
, SORTFUNC_UKSORT
);
2724 return ast
->uksort(cmp_function
);
2726 auto ref
= container
.getVariantOrNull();
2727 if (LIKELY(ref
!= nullptr)) {
2728 return sort(ref
->asTypedValue(), cmp_function
);
2730 auto tmp
= container
.wrapped();
2731 return sort(tmp
.asTypedValue(), cmp_function
);
2733 if (container
.isObject()) {
2734 ObjectData
* obj
= container
.getObjectData();
2735 if (obj
->isCollection()) {
2736 auto type
= obj
->collectionType();
2737 if (type
== CollectionType::Map
|| type
== CollectionType::Set
) {
2738 HashCollection
* hc
= static_cast<HashCollection
*>(obj
);
2739 return hc
->uksort(cmp_function
);
2742 // other collections are not supported:
2743 // - Vectors require a non-associative sort
2744 // - Immutable collections are not to be modified
2746 throw_expected_array_or_collection_exception("uksort");
2750 TypedValue
HHVM_FUNCTION(array_unique
,
2751 const Variant
& array
,
2752 int sort_flags
/* = 2 */) {
2753 SuppressHackArrCompatNotices suppress
;
2754 // NOTE, PHP array_unique accepts ArrayAccess objects as well,
2755 // which is not supported here.
2756 getCheckedArray(array
);
2757 switch (sort_flags
) {
2759 case SORT_LOCALE_STRING
:
2760 return tvReturn(ArrayUtil::StringUnique(arr_array
));
2762 return tvReturn(ArrayUtil::NumericUnique(arr_array
));
2765 return tvReturn(ArrayUtil::RegularSortUnique(arr_array
));
2769 String
HHVM_FUNCTION(i18n_loc_get_default
) {
2770 return s_collator
->getLocale();
2773 bool HHVM_FUNCTION(i18n_loc_set_default
,
2774 const String
& locale
) {
2775 return s_collator
->setLocale(locale
);
2778 bool HHVM_FUNCTION(i18n_loc_set_attribute
,
2781 return s_collator
->setAttribute(attr
, val
);
2784 bool HHVM_FUNCTION(i18n_loc_set_strength
,
2786 return s_collator
->setStrength(strength
);
2789 Variant
HHVM_FUNCTION(i18n_loc_get_error_code
) {
2790 return s_collator
->getErrorCode();
2793 TypedValue
HHVM_FUNCTION(hphp_array_idx
,
2794 const Variant
& search
,
2796 const Variant
& def
) {
2797 if (!key
.isNull()) {
2798 if (LIKELY(search
.isArray())) {
2799 ArrayData
*arr
= search
.getArrayData();
2800 auto const index
= key
.toKey(arr
).tv();
2801 if (!isNullType(index
.m_type
)) {
2802 auto const ret
= arr
->get(index
, false);
2803 return tvReturn(!ret
.is_dummy() ? tvAsCVarRef(ret
.tv_ptr()) : def
);
2806 raise_error("hphp_array_idx: search must be an array");
2809 return tvReturn(def
);
2812 static Array::PFUNC_CMP
get_cmp_func(int sort_flags
, bool ascending
) {
2813 switch (sort_flags
) {
2816 Array::SortNaturalAscending
: Array::SortNaturalDescending
;
2817 case SORT_NATURAL_CASE
:
2819 Array::SortNaturalCaseAscending
: Array::SortNaturalCaseDescending
;
2822 Array::SortNumericAscending
: Array::SortNumericDescending
;
2825 Array::SortStringAscending
: Array::SortStringDescending
;
2826 case SORT_STRING_CASE
:
2828 Array::SortStringAscendingCase
: Array::SortStringDescendingCase
;
2829 case SORT_LOCALE_STRING
:
2831 Array::SortLocaleStringAscending
: Array::SortLocaleStringDescending
;
2835 Array::SortRegularAscending
: Array::SortRegularDescending
;
2839 TypedValue
* HHVM_FN(array_multisort
)(ActRec
* ar
) {
2840 TypedValue
* tv
= getArg(ar
, 0);
2841 if (tv
== nullptr || !tvAsVariant(tv
).isPHPArray()) {
2842 throw_expected_array_exception("array_multisort");
2843 return arReturn(ar
, false);
2846 std::vector
<Array::SortData
> data
;
2847 std::vector
<Array
> arrays
;
2848 arrays
.reserve(ar
->numArgs()); // so no resize would happen
2851 sd
.original
= &tvAsVariant(tv
);
2852 arrays
.push_back(Array(sd
.original
->getArrayData()));
2853 sd
.array
= &arrays
.back();
2856 int sort_flags
= SORT_REGULAR
;
2857 bool ascending
= true;
2858 for (int i
= 1; i
< ar
->numArgs(); i
++) {
2860 if (tvAsVariant(tv
).isArray()) {
2861 sd
.cmp_func
= get_cmp_func(sort_flags
, ascending
);
2864 sort_flags
= SORT_REGULAR
;
2867 sd
.original
= &tvAsVariant(tv
);
2868 arrays
.push_back(Array(sd
.original
->getArrayData()));
2869 sd
.array
= &arrays
.back();
2871 int n
= getArg
<KindOfInt64
>(ar
, i
);
2872 if (n
== SORT_ASC
) {
2873 } else if (n
== SORT_DESC
) {
2881 sd
.cmp_func
= get_cmp_func(sort_flags
, ascending
);
2884 return arReturn(ar
, Array::MultiSort(data
, true));
2888 Array
HHVM_FUNCTION(HH_dict
, const Variant
& input
) {
2889 return input
.toDict();
2893 Array
HHVM_FUNCTION(HH_keyset
, const Variant
& input
) {
2894 return input
.toKeyset();
2898 Array
HHVM_FUNCTION(HH_vec
, const Variant
& input
) {
2899 return input
.toVecArray();
2903 Array
HHVM_FUNCTION(HH_varray
, const Variant
& input
) {
2904 return input
.toVArray();
2908 Array
HHVM_FUNCTION(HH_darray
, const Variant
& input
) {
2909 return input
.toDArray();
2912 TypedValue
HHVM_FUNCTION(HH_array_key_cast
, const Variant
& input
) {
2913 switch (input
.getType()) {
2914 case KindOfPersistentString
:
2915 case KindOfString
: {
2917 auto const& str
= input
.asCStrRef();
2918 if (str
.get()->isStrictlyInteger(n
)) {
2921 return tvReturn(str
);
2927 case KindOfResource
:
2928 return tvReturn(input
.toInt64());
2932 return tvReturn(staticEmptyString());
2934 case KindOfPersistentVec
:
2936 SystemLib::throwInvalidArgumentExceptionObject(
2937 "Vecs cannot be cast to an array-key"
2939 case KindOfPersistentDict
:
2941 SystemLib::throwInvalidArgumentExceptionObject(
2942 "Dicts cannot be cast to an array-key"
2944 case KindOfPersistentKeyset
:
2946 SystemLib::throwInvalidArgumentExceptionObject(
2947 "Keysets cannot be cast to an array-key"
2949 case KindOfPersistentArray
:
2951 SystemLib::throwInvalidArgumentExceptionObject(
2952 "Arrays cannot be cast to an array-key"
2955 SystemLib::throwInvalidArgumentExceptionObject(
2956 "Objects cannot be cast to an array-key"
2965 Array
HHVM_FUNCTION(merge_xhp_attr_declarations
,
2968 const Array
& rest
) {
2969 auto ret
= Array::CreateDArray();
2970 IterateKV(arr1
.get(), [&](Cell k
, TypedValue v
) { ret
.set(k
, v
); });
2971 IterateKV(arr2
.get(), [&](Cell k
, TypedValue v
) { ret
.set(k
, v
); });
2975 [&](TypedValue arr
) {
2976 if (!isArrayType(arr
.m_type
)) {
2977 raise_param_type_warning(
2978 "__SystemLib\\merge_xhp_attr_declarations",
2980 RuntimeOption::EvalHackArrDVArrs
? KindOfDict
: KindOfArray
,
2986 IterateKV(arr
.m_data
.parr
, [&](Cell k
, TypedValue v
) { ret
.set(k
, v
); });
2994 ///////////////////////////////////////////////////////////////////////////////
2996 struct ArrayExtension final
: Extension
{
2997 ArrayExtension() : Extension("array") {}
2998 void moduleInit() override
{
2999 HHVM_RC_INT_SAME(UCOL_DEFAULT
);
3001 HHVM_RC_INT_SAME(UCOL_PRIMARY
);
3002 HHVM_RC_INT_SAME(UCOL_SECONDARY
);
3003 HHVM_RC_INT_SAME(UCOL_TERTIARY
);
3004 HHVM_RC_INT_SAME(UCOL_DEFAULT_STRENGTH
);
3005 HHVM_RC_INT_SAME(UCOL_QUATERNARY
);
3006 HHVM_RC_INT_SAME(UCOL_IDENTICAL
);
3008 HHVM_RC_INT_SAME(UCOL_OFF
);
3009 HHVM_RC_INT_SAME(UCOL_ON
);
3011 HHVM_RC_INT_SAME(UCOL_SHIFTED
);
3012 HHVM_RC_INT_SAME(UCOL_NON_IGNORABLE
);
3014 HHVM_RC_INT_SAME(UCOL_LOWER_FIRST
);
3015 HHVM_RC_INT_SAME(UCOL_UPPER_FIRST
);
3017 HHVM_RC_INT_SAME(UCOL_FRENCH_COLLATION
);
3018 HHVM_RC_INT_SAME(UCOL_ALTERNATE_HANDLING
);
3019 HHVM_RC_INT_SAME(UCOL_CASE_FIRST
);
3020 HHVM_RC_INT_SAME(UCOL_CASE_LEVEL
);
3021 HHVM_RC_INT_SAME(UCOL_NORMALIZATION_MODE
);
3022 HHVM_RC_INT_SAME(UCOL_STRENGTH
);
3023 HHVM_RC_INT_SAME(UCOL_HIRAGANA_QUATERNARY_MODE
);
3024 HHVM_RC_INT_SAME(UCOL_NUMERIC_COLLATION
);
3026 HHVM_RC_INT(ARRAY_FILTER_USE_BOTH
, 1);
3027 HHVM_RC_INT(ARRAY_FILTER_USE_KEY
, 2);
3029 HHVM_RC_INT(CASE_LOWER
, static_cast<int64_t>(CaseMode::LOWER
));
3030 HHVM_RC_INT(CASE_UPPER
, static_cast<int64_t>(CaseMode::UPPER
));
3032 HHVM_RC_INT(COUNT_NORMAL
, static_cast<int64_t>(CountMode::NORMAL
));
3033 HHVM_RC_INT(COUNT_RECURSIVE
, static_cast<int64_t>(CountMode::RECURSIVE
));
3035 HHVM_RC_INT_SAME(SORT_ASC
);
3036 HHVM_RC_INT_SAME(SORT_DESC
);
3037 HHVM_RC_INT_SAME(SORT_FLAG_CASE
);
3038 HHVM_RC_INT_SAME(SORT_LOCALE_STRING
);
3039 HHVM_RC_INT_SAME(SORT_NATURAL
);
3040 HHVM_RC_INT_SAME(SORT_NUMERIC
);
3041 HHVM_RC_INT_SAME(SORT_REGULAR
);
3042 HHVM_RC_INT_SAME(SORT_STRING
);
3044 HHVM_FE(array_change_key_case
);
3045 HHVM_FE(array_chunk
);
3046 HHVM_FE(array_column
);
3047 HHVM_FE(array_combine
);
3048 HHVM_FE(array_count_values
);
3049 HHVM_FE(array_fill_keys
);
3050 HHVM_FE(array_fill
);
3051 HHVM_FE(array_flip
);
3052 HHVM_FE(array_key_exists
);
3053 HHVM_FE(key_exists
);
3054 HHVM_FE(array_keys
);
3055 HHVM_FALIAS(__SystemLib
\\array_map
, array_map
);
3056 HHVM_FE(array_merge_recursive
);
3057 HHVM_FE(array_merge
);
3058 HHVM_FE(array_replace_recursive
);
3059 HHVM_FE(array_replace
);
3062 HHVM_FE(array_product
);
3063 HHVM_FE(array_push
);
3064 HHVM_FE(array_rand
);
3065 HHVM_FE(array_reverse
);
3066 HHVM_FE(array_search
);
3067 HHVM_FE(array_shift
);
3068 HHVM_FE(array_slice
);
3069 HHVM_FE(array_splice
);
3071 HHVM_FE(array_unique
);
3072 HHVM_FE(array_unshift
);
3073 HHVM_FE(array_values
);
3074 HHVM_FE(array_walk_recursive
);
3075 HHVM_FE(array_walk
);
3090 HHVM_FE(array_diff
);
3091 HHVM_FE(array_udiff
);
3092 HHVM_FE(array_diff_assoc
);
3093 HHVM_FE(array_diff_uassoc
);
3094 HHVM_FE(array_udiff_assoc
);
3095 HHVM_FE(array_udiff_uassoc
);
3096 HHVM_FE(array_diff_key
);
3097 HHVM_FE(array_diff_ukey
);
3098 HHVM_FE(array_intersect
);
3099 HHVM_FE(array_uintersect
);
3100 HHVM_FE(array_intersect_assoc
);
3101 HHVM_FE(array_intersect_uassoc
);
3102 HHVM_FE(array_uintersect_assoc
);
3103 HHVM_FE(array_uintersect_uassoc
);
3104 HHVM_FE(array_intersect_key
);
3105 HHVM_FE(array_intersect_ukey
);
3116 HHVM_FE(natcasesort
);
3117 HHVM_FE(i18n_loc_get_default
);
3118 HHVM_FE(i18n_loc_set_default
);
3119 HHVM_FE(i18n_loc_set_attribute
);
3120 HHVM_FE(i18n_loc_set_strength
);
3121 HHVM_FE(i18n_loc_get_error_code
);
3122 HHVM_FE(hphp_array_idx
);
3123 HHVM_FE(array_multisort
);
3124 HHVM_FALIAS(HH
\\dict
, HH_dict
);
3125 HHVM_FALIAS(HH
\\vec
, HH_vec
);
3126 HHVM_FALIAS(HH
\\keyset
, HH_keyset
);
3127 HHVM_FALIAS(HH
\\varray
, HH_varray
);
3128 HHVM_FALIAS(HH
\\darray
, HH_darray
);
3129 HHVM_FALIAS(HH
\\array_key_cast
, HH_array_key_cast
);
3130 HHVM_FALIAS(__SystemLib
\\merge_xhp_attr_declarations
,
3131 merge_xhp_attr_declarations
);
3135 } s_array_extension
;