Codemod asserts to assertxs in the runtime
[hiphop-php.git] / hphp / runtime / ext / array / ext_array.cpp
blob48e2bca444aea641ca94b2c9753eb810c1367e96
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
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"
48 #include <vector>
50 namespace HPHP {
51 ///////////////////////////////////////////////////////////////////////////////
53 #define SORT_DESC 3
54 #define SORT_ASC 4
56 const StaticString s_count("count");
58 enum class CaseMode {
59 LOWER = 0,
60 UPPER = 1,
63 TypedValue HHVM_FUNCTION(array_change_key_case,
64 const Variant& input,
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,
72 const Variant& input,
73 int chunkSize,
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>();
82 if (chunkSize < 1) {
83 throw_invalid_argument("size: %d", chunkSize);
84 return make_tv<KindOfNull>();
87 auto const retSize = (getContainerSize(cellInput) / chunkSize) + 1;
88 PackedArrayInit ret(retSize);
89 Array chunk;
90 int current = 0;
91 for (ArrayIter iter(cellInput); iter; ++iter) {
92 if (preserve_keys) {
93 chunk.setWithRef(iter.first(), iter.secondValPlus(), true);
94 } else {
95 chunk.appendWithRef(iter.secondValPlus());
97 if ((++current % chunkSize) == 0) {
98 ret.append(chunk);
99 chunk.clear();
102 if (!chunk.empty()) {
103 ret.append(chunk);
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 */
111 if (key.isNull()) {
112 return true;
115 /* Custom coercion rules for key types */
116 if (key.isInteger() || key.isDouble()) {
117 key = key.toInt64();
118 return true;
119 } else if (key.isString() || key.isObject()) {
120 key = key.toString();
121 return true;
122 } else {
123 raise_warning("array_column(): The %s key should be either a string "
124 "or an integer", name);
125 return false;
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) {
143 Array sub;
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();
148 } else {
149 continue;
152 Variant elem;
153 if (val.isNull()) {
154 elem = sub;
155 } else if (sub.exists(val)) {
156 elem = sub[val];
157 } else {
158 // skip subarray without named element
159 continue;
162 if (idx.isNull() || !sub.exists(idx)) {
163 ret.append(elem);
164 } else if (sub[idx].isObject()) {
165 ret.setUnknownKey(sub[idx].toString(), elem);
166 } else {
167 ret.setUnknownKey(sub[idx], elem);
170 return tvReturn(ret.toVariant());
173 TypedValue HHVM_FUNCTION(array_combine,
174 const Variant& keys,
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());
198 } else {
199 ret.setWithRef(tvCastToString(key.tv()), iter2.secondValPlus());
202 return tvReturn(std::move(ret));
205 TypedValue HHVM_FUNCTION(array_count_values,
206 ArrayArg input) {
207 SuppressHackArrCompatNotices suppress;
208 return tvReturn(ArrayUtil::CountValues(ArrNR(input.get())));
211 TypedValue HHVM_FUNCTION(array_fill_keys,
212 const Variant& keys,
213 const Variant& value) {
214 SuppressHackArrCompatNotices suppress;
216 folly::Optional<ArrayInit> ai;
217 auto ok = IterateV(
218 *keys.asCell(),
219 [&](ArrayData* adata) {
220 ai.emplace(adata->size(), ArrayInit::Mixed{});
222 [&](TypedValue v) {
223 auto const inner = tvToCell(v);
224 if (isIntType(inner.m_type) || isStringType(inner.m_type)) {
225 ai->setUnknownKey(VarNR(inner), value);
226 } else {
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{});
240 if (!ok) {
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,
250 int start_index,
251 int num,
252 const Variant& value) {
253 if (num < 0) {
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++) {
263 pai.append(value);
265 return tvReturn(pai.toVariant());
266 } else {
267 ArrayInit ret(num, ArrayInit::Mixed{}, CheckAllocation{});
268 ret.set(start_index, value);
269 for (int i = num - 1; i > 0; i--) {
270 ret.append(value);
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());
292 } else {
293 raise_warning("Can only flip STRING and INTEGER values!");
296 return tvReturn(ret.toVariant());
299 bool HHVM_FUNCTION(array_key_exists,
300 const Variant& key,
301 const Variant& search) {
302 const ArrayData *ad;
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());
313 } else {
314 throw_bad_type_exception("array_key_exists expects an array or an object; "
315 "false returned.");
316 return false;
319 auto const cell = key.asCell();
321 switch (cell->m_type) {
322 case KindOfUninit:
323 case KindOfNull:
324 if (checkHACMisc() && ad->useWeakKeys()) {
325 raiseHackArrCompatImplicitArrayKey(cell);
327 return ad->useWeakKeys() && ad->exists(staticEmptyString());
329 case KindOfBoolean:
330 case KindOfDouble:
331 case KindOfPersistentVec:
332 case KindOfVec:
333 case KindOfPersistentDict:
334 case KindOfDict:
335 case KindOfPersistentKeyset:
336 case KindOfKeyset:
337 case KindOfPersistentArray:
338 case KindOfArray:
339 case KindOfObject:
340 case KindOfResource:
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");
346 return false;
348 case KindOfPersistentString:
349 case KindOfString: {
350 int64_t n = 0;
351 if (ad->convertKey(cell->m_data.pstr, n)) {
352 return ad->exists(n);
354 return ad->exists(StrNR(cell->m_data.pstr));
356 case KindOfInt64:
357 return ad->exists(cell->m_data.num);
358 case KindOfRef:
359 break;
361 not_reached();
364 bool HHVM_FUNCTION(key_exists,
365 const Variant& key,
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 "
376 "or collection");
377 return init_null();
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();
386 } else {
387 Array ai = Array::attach(PackedArray::MakeReserve(0));
388 for (ArrayIter iter(cell_input); iter; ++iter) {
389 if ((strict &&
390 tvSame(iter.secondValPlus(), *search_value.asTypedValue())) ||
391 (!strict &&
392 tvEqual(iter.secondValPlus(), *search_value.asTypedValue()))) {
393 ai.append(iter.first());
396 return ai;
400 static
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(
406 input,
407 argc < 2 ? uninit_variant : search_value,
408 strict
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();
423 if (check) {
424 if (seen.find(arr1_ptr) != seen.end()) {
425 raise_warning("array_merge_recursive(): recursion detected");
426 return;
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
437 // in the array.
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);
446 } else {
447 arr1.setWithRef(key, iter.secondVal(), true);
451 if (check) {
452 seen.erase(arr1_ptr);
456 TypedValue HHVM_FUNCTION(array_map,
457 const Variant& callback,
458 const Variant& arr1,
459 const Array& _argv) {
460 VMRegGuard _;
461 CallCtx ctx;
462 ctx.func = nullptr;
463 if (!callback.isNull()) {
464 CallerFrame cf;
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)
475 if (!ctx.func) {
476 if (isArrayLikeType(cell_arr1.m_type)) {
477 return tvReturn(arr1);
478 } else {
479 return tvReturn(arr1.toArray());
482 ArrayInit ret(getContainerSize(cell_arr1), ArrayInit::Map{});
483 bool keyConverted = isArrayLikeType(cell_arr1.m_type);
484 if (!keyConverted) {
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
496 // present
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));
514 } else {
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) {
524 if (iter) {
525 params_ai.append(iter.secondValPlus());
526 ++iter;
527 } else {
528 params_ai.append(init_null_variant);
531 Array params = params_ai.toArray();
532 if (ctx.func) {
533 auto result = Variant::attach(
534 g_context->invokeFunc(ctx, params, nullptr)
536 ret_ai.append(result);
537 } else {
538 ret_ai.append(params);
541 return tvReturn(ret_ai.toVariant());
544 TypedValue HHVM_FUNCTION(array_merge,
545 int64_t numArgs,
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();
560 if (!v.isArray()) {
561 throw_expected_array_exception("array_merge");
562 return make_tv<KindOfNull>();
564 const Array& arr_v = v.asCArrRef();
565 ret.merge(arr_v);
567 return tvReturn(std::move(ret));
570 TypedValue HHVM_FUNCTION(array_merge_recursive,
571 int64_t numArgs,
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();
578 PointerSet seen;
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();
590 if (!v.isArray()) {
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.
616 return;
619 auto const arr1_ptr = (void*)arr1.get();
620 if (check) {
621 if (seen.find(arr1_ptr) != seen.end()) {
622 raise_warning("array_replace_recursive(): recursion detected");
623 return;
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);
638 } else {
639 arr1.set(key, iter.secondVal(), true);
641 } else {
642 arr1.setWithRef(key, iter.secondVal(), true);
646 if (check) {
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());
668 getCheckedArray(v);
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();
680 PointerSet seen;
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());
694 getCheckedArray(v);
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,
703 int pad_size,
704 const Variant& pad_value) {
705 getCheckedArray(input);
706 auto arr =
707 UNLIKELY(input.isHackArray()) ? arr_input.toPHPArray() : arr_input;
708 if (pad_size > 0) {
709 return tvReturn(ArrayUtil::PadRight(arr, pad_value, pad_size));
710 } else {
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;
731 if (ad->size()) {
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",
746 __FUNCTION__+2);
747 return make_tv<KindOfNull>();
750 int64_t i = 1;
751 ArrayIter iter(input);
752 for (; iter; ++iter) {
753 auto const rval = iter.secondRvalPlus().unboxed();
755 switch (rval.type()) {
756 case KindOfUninit:
757 case KindOfNull:
758 case KindOfBoolean:
759 case KindOfInt64:
760 case KindOfRef:
761 i *= cellToInt(rval.tv());
762 continue;
764 case KindOfDouble:
765 goto DOUBLE;
767 case KindOfPersistentString:
768 case KindOfString: {
769 int64_t ti;
770 double td;
771 if (rval.val().pstr->isNumericWithVal(ti, td, 1) == KindOfInt64) {
772 i *= ti;
773 continue;
774 } else {
775 goto DOUBLE;
779 case KindOfPersistentVec:
780 case KindOfVec:
781 case KindOfPersistentDict:
782 case KindOfDict:
783 case KindOfPersistentKeyset:
784 case KindOfKeyset:
785 case KindOfPersistentArray:
786 case KindOfArray:
787 case KindOfObject:
788 case KindOfResource:
789 continue;
791 not_reached();
793 return make_tv<KindOfInt64>(i);
795 DOUBLE:
796 double d = i;
797 for (; iter; ++iter) {
798 auto const rval = iter.secondRvalPlus().unboxed();
799 switch (rval.type()) {
800 DT_UNCOUNTED_CASE:
801 case KindOfString:
802 case KindOfRef:
803 d *= cellToDouble(rval.tv());
805 case KindOfVec:
806 case KindOfDict:
807 case KindOfKeyset:
808 case KindOfArray:
809 case KindOfObject:
810 case KindOfResource:
811 continue;
813 not_reached();
815 return make_tv<KindOfDouble>(d);
818 TypedValue HHVM_FUNCTION(array_push,
819 VRefParam container,
820 const Variant& var,
821 const Array& args /* = null array */) {
822 if (LIKELY(container->isArray())) {
823 auto ref = container.getVariantOrNull();
824 if (!ref) {
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
833 * might escalate.
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);
850 vec->add(var);
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);
859 set->add(var);
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
873 break;
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,
897 VRefParam array) {
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;
912 if (ad->size()) {
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,
924 int64_t offset,
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",
930 __FUNCTION__+2);
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) {
937 offset = num_in;
938 } else if (offset < 0 && (offset = (num_in + offset)) < 0) {
939 offset = 0;
942 auto const maxLen = num_in - offset;
943 if (len < 0) {
944 len = maxLen + len;
945 } else if (len > maxLen) {
946 len = maxLen;
949 if (len <= 0) {
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());
968 int pos = 0;
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();
986 if (doAppend) {
987 ret.appendWithRef(iter.secondValPlus());
988 } else {
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");
1000 return init_null();
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));
1005 return ret;
1008 TypedValue HHVM_FUNCTION(array_splice,
1009 VRefParam input,
1010 int offset,
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",
1021 __FUNCTION__+2);
1022 return make_tv<KindOfNull>();
1025 int64_t i = 0;
1026 ArrayIter iter(input);
1027 for (; iter; ++iter) {
1028 auto const rval = iter.secondRvalPlus().unboxed();
1030 switch (rval.type()) {
1031 case KindOfUninit:
1032 case KindOfNull:
1033 case KindOfBoolean:
1034 case KindOfInt64:
1035 case KindOfRef:
1036 i += cellToInt(rval.tv());
1037 continue;
1039 case KindOfDouble:
1040 goto DOUBLE;
1042 case KindOfPersistentString:
1043 case KindOfString: {
1044 int64_t ti;
1045 double td;
1046 if (rval.val().pstr->isNumericWithVal(ti, td, 1) == KindOfInt64) {
1047 i += ti;
1048 continue;
1049 } else {
1050 goto DOUBLE;
1054 case KindOfPersistentVec:
1055 case KindOfVec:
1056 case KindOfPersistentDict:
1057 case KindOfDict:
1058 case KindOfPersistentKeyset:
1059 case KindOfKeyset:
1060 case KindOfPersistentArray:
1061 case KindOfArray:
1062 case KindOfObject:
1063 case KindOfResource:
1064 continue;
1066 not_reached();
1068 return make_tv<KindOfInt64>(i);
1070 DOUBLE:
1071 double d = i;
1072 for (; iter; ++iter) {
1073 auto const rval = iter.secondRvalPlus().unboxed();
1074 switch (rval.type()) {
1075 DT_UNCOUNTED_CASE:
1076 case KindOfString:
1077 case KindOfRef:
1078 d += cellToDouble(rval.tv());
1080 case KindOfVec:
1081 case KindOfDict:
1082 case KindOfKeyset:
1083 case KindOfArray:
1084 case KindOfObject:
1085 case KindOfResource:
1086 continue;
1088 not_reached();
1090 return make_tv<KindOfDouble>(d);
1093 TypedValue HHVM_FUNCTION(array_unshift,
1094 VRefParam array,
1095 const Variant& var,
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();
1105 if (!ref_array) {
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);
1119 } else {
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);
1136 } else {
1137 for (ArrayIter iter(array.toArray()); iter; ++iter) {
1138 Variant key(iter.first());
1139 if (key.isInteger()) {
1140 newArray.appendWithRef(iter.secondVal());
1141 } else {
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:
1187 break;
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()) {
1198 return input;
1200 if (cell.m_data.parr->isMixed()) {
1201 if (MixedArray::IsStrictVector(cell.m_data.parr)) {
1202 return input;
1204 } else if (cell.m_data.parr->isApcArray() &&
1205 APCLocalArray::IsVectorData(cell.m_data.parr)) {
1206 return input;
1210 folly::Optional<PackedArrayInit> ai;
1211 auto ok = IterateV(cell,
1212 [&](ArrayData* adata) {
1213 ai.emplace(adata->size());
1215 [&](TypedValue v) {
1216 ai->appendWithRef(v);
1218 [&](ObjectData* coll) {
1219 if (coll->collectionType() == CollectionType::Pair) {
1220 ai.emplace(2);
1224 if (!ok) {
1225 raise_warning("array_values() expects parameter 1 to be an array "
1226 "or collection");
1227 return init_null();
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,
1240 const Variant& key,
1241 const Variant& userdata,
1242 const void *data) {
1243 CallCtx* ctx = (CallCtx*)data;
1244 int nargs = userdata.isInitialized() ? 3 : 2;
1245 TypedValue args[3] = { *value.asRef(), *key.asCell(), *userdata.asCell() };
1246 tvDecRefGen(
1247 g_context->invokeFuncFew(*ctx, nargs, args)
1251 bool HHVM_FUNCTION(array_walk_recursive,
1252 VRefParam input,
1253 const Variant& funcname,
1254 const Variant& userdata /* = uninit_variant */) {
1255 if (!input.isPHPArray()) {
1256 throw_expected_array_exception("array_walk_recursive");
1257 return false;
1259 CallCtx ctx;
1260 CallerFrame cf;
1261 vm_decode_function(funcname, cf(), false, ctx);
1262 if (ctx.func == NULL) {
1263 return false;
1265 PointerSet seen;
1266 Variant var(input, Variant::WithRefBind{});
1267 ArrayUtil::Walk(var, walk_func, &ctx, true, &seen, userdata);
1268 return true;
1271 bool HHVM_FUNCTION(array_walk,
1272 VRefParam input,
1273 const Variant& funcname,
1274 const Variant& userdata /* = uninit_variant */) {
1275 if (!input.isPHPArray()) {
1276 throw_expected_array_exception("array_walk");
1277 return false;
1279 CallCtx ctx;
1280 CallerFrame cf;
1281 vm_decode_function(funcname, cf(), false, ctx);
1282 if (ctx.func == NULL) {
1283 return false;
1285 Variant var(input, Variant::WithRefBind{});
1286 ArrayUtil::Walk(var, walk_func, &ctx, false, NULL, userdata);
1287 return true;
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);
1295 if (check) {
1296 if (seen.find(adata) != seen.end()) {
1297 raise_warning("compact(): recursion detected");
1298 return;
1300 seen.insert(adata);
1302 for (ArrayIter iter(adata); iter; ++iter) {
1303 compact(seen, v, ret, VarNR(iter.secondVal()));
1305 if (check) seen.erase(adata);
1306 } else {
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();
1319 if (v) {
1320 PointerSet seen;
1321 compact(seen, v, ret, varname);
1322 if (!args.empty()) compact(seen, v, ret, args);
1324 return ret;
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);
1337 return cnt;
1340 bool HHVM_FUNCTION(shuffle,
1341 VRefParam array) {
1342 if (!array.isArray()) {
1343 throw_expected_array_exception("shuffle");
1344 return false;
1346 array.assignIfRef(ArrayUtil::Shuffle(array));
1347 return true;
1350 enum class CountMode {
1351 NORMAL = 0,
1352 RECURSIVE = 1,
1355 int64_t HHVM_FUNCTION(count,
1356 const Variant& var,
1357 int64_t mode /* = 0 */) {
1358 switch (var.getType()) {
1359 case KindOfUninit:
1360 case KindOfNull:
1361 return 0;
1363 case KindOfBoolean:
1364 case KindOfInt64:
1365 case KindOfDouble:
1366 case KindOfPersistentString:
1367 case KindOfString:
1368 case KindOfResource:
1369 return 1;
1371 case KindOfPersistentVec:
1372 case KindOfVec:
1373 case KindOfPersistentDict:
1374 case KindOfDict:
1375 case KindOfPersistentKeyset:
1376 case KindOfKeyset:
1377 case KindOfPersistentArray:
1378 case KindOfArray:
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();
1385 case KindOfObject:
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();
1395 return 1;
1397 case KindOfRef:
1398 break;
1400 not_reached();
1403 int64_t HHVM_FUNCTION(sizeof,
1404 const Variant& var,
1405 int64_t mode /* = 0 */) {
1406 return HHVM_FN(count)(var, mode);
1409 namespace {
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()) {
1433 ad = ad->copy();
1434 if (LIKELY(refParam.isRefData())) {
1435 cellMove(make_array_like_tv(ad), *refParam.getRefData()->tv());
1436 } else {
1437 req::ptr<ArrayData> tmp(ad, req::ptr<ArrayData>::NoIncRef{});
1438 return (ad->*op)();
1441 return (ad->*op)();
1446 const StaticString
1447 s___each("__each"),
1448 s___current("__current"),
1449 s___key("__key"),
1450 s___next("__next"),
1451 s___prev("__prev"),
1452 s___reset("__reset"),
1453 s___end("__end");
1456 Variant HHVM_FUNCTION(each,
1457 VRefParam refParam) {
1458 return iter_op_impl(
1459 refParam,
1460 &ArrayData::each,
1461 s___each,
1462 Variant::NullInit()
1466 Variant HHVM_FUNCTION(current,
1467 VRefParam refParam) {
1468 return iter_op_impl<NoCow>(
1469 refParam,
1470 &ArrayData::current,
1471 s___current,
1472 false
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>(
1484 refParam,
1485 &ArrayData::key,
1486 s___key,
1487 false
1491 Variant HHVM_FUNCTION(next,
1492 VRefParam refParam) {
1493 return iter_op_impl(
1494 refParam,
1495 &ArrayData::next,
1496 s___next,
1497 false
1501 Variant HHVM_FUNCTION(prev,
1502 VRefParam refParam) {
1503 return iter_op_impl(
1504 refParam,
1505 &ArrayData::prev,
1506 s___prev,
1507 false
1511 Variant HHVM_FUNCTION(reset,
1512 VRefParam refParam) {
1513 return iter_op_impl(
1514 refParam,
1515 &ArrayData::reset,
1516 s___reset,
1517 false,
1518 &ArrayData::isHead
1522 Variant HHVM_FUNCTION(end,
1523 VRefParam refParam) {
1524 return iter_op_impl(
1525 refParam,
1526 &ArrayData::end,
1527 s___end,
1528 false,
1529 &ArrayData::isTail
1533 bool HHVM_FUNCTION(in_array,
1534 const Variant& needle,
1535 const Variant& haystack,
1536 bool strict /* = false */) {
1537 bool ret = false;
1538 auto ok = strict ?
1539 IterateV(*haystack.asCell(),
1540 [](ArrayData*) { return false; },
1541 [&](TypedValue v) -> bool {
1542 if (tvSame(v, *needle.asTypedValue())) {
1543 ret = true;
1544 return true;
1546 return false;
1548 [](ObjectData*) { return false; }) :
1549 IterateV(*haystack.asCell(),
1550 [](ArrayData*) { return false; },
1551 [&](TypedValue v) -> bool {
1552 if (tvEqual(v, *needle.asTypedValue())) {
1553 ret = true;
1554 return true;
1556 return false;
1558 [](ObjectData*) { return false; });
1560 if (UNLIKELY(!ok)) {
1561 raise_warning("in_array() expects parameter 2 to be an array "
1562 "or collection");
1564 return ret;
1567 Variant array_search(const Variant& needle,
1568 const Variant& haystack,
1569 bool strict /* = false */) {
1570 Variant ret = false;
1571 auto ok = strict ?
1572 IterateKV(*haystack.asCell(),
1573 [](ArrayData*) { return false; },
1574 [&](Cell k, TypedValue v) -> bool {
1575 if (tvSame(v, *needle.asTypedValue())) {
1576 ret = VarNR(k);
1577 return true;
1579 return false;
1581 [](ObjectData*) { return false; }) :
1582 IterateKV(*haystack.asCell(),
1583 [](ArrayData*) { return false; },
1584 [&](Cell k, TypedValue v) -> bool {
1585 if (tvEqual(v, *needle.asTypedValue())) {
1586 ret = VarNR(k);
1587 return true;
1589 return false;
1591 [](ObjectData*) { return false; });
1593 if (UNLIKELY(!ok)) {
1594 raise_warning("array_search() expects parameter 2 to be an array "
1595 "or collection");
1596 return init_null();
1599 return ret;
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,
1610 const Variant& low,
1611 const Variant& high,
1612 const Variant& step /* = 1 */) {
1613 bool is_step_double = false;
1614 double dstep = 1.0;
1615 if (step.isDouble()) {
1616 dstep = step.toDouble();
1617 is_step_double = true;
1618 } else if (step.isString()) {
1619 int64_t sn;
1620 double sd;
1621 DataType stype = step.toString().get()->isNumericWithVal(sn, sd, 0);
1622 if (stype == KindOfDouble) {
1623 is_step_double = true;
1624 dstep = sd;
1625 } else if (stype == KindOfInt64) {
1626 dstep = (double)sn;
1627 } else {
1628 dstep = step.toDouble();
1630 } else {
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) {
1639 int64_t n1, n2;
1640 double d1, d2;
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
1680 #define COMMA ,
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); \
1694 if (ret.size()) { \
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 ///////////////////////////////////////////////////////////////////////////////
1703 // diff functions
1705 static inline void addToSetHelper(const req::ptr<c_Set>& st,
1706 const Cell c,
1707 TypedValue* strTv,
1708 bool convertIntLikeStrs) {
1709 if (c.m_type == KindOfInt64) {
1710 st->add(c.m_data.num);
1711 } else {
1712 StringData* s;
1713 if (LIKELY(isStringType(c.m_type))) {
1714 s = c.m_data.pstr;
1715 } else {
1716 s = tvCastToStringData(c);
1717 decRefStr(strTv->m_data.pstr);
1718 strTv->m_data.pstr = s;
1720 int64_t n;
1721 if (convertIntLikeStrs && s->isStrictlyInteger(n)) {
1722 if (checkHACIntishCast()) raise_intish_index_cast();
1723 st->add(n);
1724 } else {
1725 st->add(s);
1730 static inline bool checkSetHelper(const req::ptr<c_Set>& st,
1731 const Cell c,
1732 TypedValue* strTv,
1733 bool convertIntLikeStrs) {
1734 if (c.m_type == KindOfInt64) {
1735 return st->contains(c.m_data.num);
1737 StringData* s;
1738 if (LIKELY(isStringType(c.m_type))) {
1739 s = c.m_data.pstr;
1740 } else {
1741 s = tvCastToStringData(c);
1742 decRefStr(strTv->m_data.pstr);
1743 strTv->m_data.pstr = s;
1745 int64_t n;
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)) { \
1786 int pos = 3; \
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 */ \
1792 pos); \
1793 return make_tv<KindOfNull>(); \
1795 size_t sz = getContainerSize(c); \
1796 if (sz > largestSize) { \
1797 largestSize = sz; \
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); \
1810 } else { \
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
1857 if (!moreThanTwo &&
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;
1866 } else {
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;
1909 Array extra = args;
1910 if (!extra.empty()) {
1911 extra.prepend(func);
1912 func = extra.pop();
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;
1931 Array extra = args;
1932 if (!extra.empty()) {
1933 extra.prepend(func);
1934 func = extra.pop();
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;
1945 Array extra = args;
1946 if (!extra.empty()) {
1947 extra.prepend(func);
1948 func = extra.pop();
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;
1962 Array extra = args;
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;
1980 Array extra = args;
1981 if (!extra.empty()) {
1982 extra.prepend(func);
1983 func = extra.pop();
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,
1992 const Array& argv,
1993 int count,
1994 int smallestPos) {
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]);
2001 int pos = 1;
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) {
2009 TypedValue tmp;
2010 tvCopy(containers[0], tmp);
2011 tvCopy(containers[smallestPos], containers[0]);
2012 tvCopy(tmp, containers[smallestPos]);
2014 return containers;
2017 static inline void addToIntersectMapHelper(const req::ptr<c_Map>& mp,
2018 const Cell c,
2019 TypedValue* intOneTv,
2020 TypedValue* strTv,
2021 bool convertIntLikeStrs) {
2022 if (c.m_type == KindOfInt64) {
2023 mp->set(c.m_data.num, *intOneTv);
2024 } else {
2025 StringData* s;
2026 if (LIKELY(isStringType(c.m_type))) {
2027 s = c.m_data.pstr;
2028 } else {
2029 s = tvCastToStringData(c);
2030 decRefStr(strTv->m_data.pstr);
2031 strTv->m_data.pstr = s;
2033 int64_t n;
2034 if (convertIntLikeStrs && s->isStrictlyInteger(n)) {
2035 if (checkHACIntishCast()) raise_intish_index_cast();
2036 mp->set(n, *intOneTv);
2037 } else {
2038 mp->set(s, *intOneTv);
2043 static inline void updateIntersectMapHelper(const req::ptr<c_Map>& mp,
2044 const Cell c,
2045 int pos,
2046 TypedValue* strTv,
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);
2052 ++val->m_data.num;
2054 } else {
2055 StringData* s;
2056 if (LIKELY(isStringType(c.m_type))) {
2057 s = c.m_data.pstr;
2058 } else {
2059 s = tvCastToStringData(c);
2060 decRefStr(strTv->m_data.pstr);
2061 strTv->m_data.pstr = s;
2063 int64_t n;
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);
2069 ++val->m_data.num;
2071 } else {
2072 auto val = mp->get(s);
2073 if (val && val->m_data.num == pos) {
2074 assertx(val->m_type == KindOfInt64);
2075 ++val->m_data.num;
2081 static void containerValuesIntersectHelper(const req::ptr<c_Set>& st,
2082 TypedValue* containers,
2083 int count) {
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
2094 // fine.
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
2104 // than once.
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,
2122 int count) {
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
2135 // that's fine.
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 \
2170 container1) */ \
2171 int smallestPos = 0; \
2172 size_t smallestSize = getContainerSize(c2); \
2173 if (UNLIKELY(moreThanTwo)) { \
2174 int pos = 1; \
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 */ \
2180 pos+2); \
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 \
2191 empty array */ \
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);
2211 } else {
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
2243 if (!moreThanTwo &&
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;
2252 } else {
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);
2267 } else {
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;
2301 Array extra = args;
2302 if (!extra.empty()) {
2303 extra.prepend(func);
2304 func = extra.pop();
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;
2323 Array extra = args;
2324 if (!extra.empty()) {
2325 extra.prepend(func);
2326 func = extra.pop();
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;
2338 Array extra = args;
2339 if (!extra.empty()) {
2340 extra.prepend(func);
2341 func = extra.pop();
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;
2355 Array extra = args;
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;
2372 Array extra = args;
2373 if (!extra.empty()) {
2374 extra.prepend(func);
2375 func = extra.pop();
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() {
2386 return m_locale;
2388 Intl::IntlError &getErrorRef() {
2389 return m_errcode;
2391 bool setLocale(const String& locale) {
2392 if (m_locale.same(locale)) {
2393 return true;
2395 if (m_ucoll) {
2396 ucol_close(m_ucoll);
2397 m_ucoll = NULL;
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());
2404 return false;
2406 if (U_FAILURE(error)) {
2407 m_errcode.setError(error);
2408 ucol_close(m_ucoll);
2409 m_ucoll = NULL;
2410 return false;
2412 m_locale = locale;
2413 return true;
2416 UCollator *getCollator() {
2417 return m_ucoll;
2420 bool setAttribute(int64_t attr, int64_t val) {
2421 if (!m_ucoll) {
2422 Logger::Verbose("m_ucoll is NULL");
2423 return false;
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");
2432 return false;
2434 return true;
2437 bool setStrength(int64_t strength) {
2438 if (!m_ucoll) {
2439 Logger::Verbose("m_ucoll is NULL");
2440 return false;
2442 ucol_setStrength(m_ucoll, (UCollationStrength)strength);
2443 return true;
2446 Variant getErrorCode() {
2447 if (!m_ucoll) {
2448 Logger::Verbose("m_ucoll is NULL");
2449 return false;
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);
2462 assertx(m_ucoll);
2465 void requestShutdown() override {
2466 m_locale.reset();
2467 m_errcode.clearError(false);
2468 if (m_ucoll) {
2469 ucol_close(m_ucoll);
2470 m_ucoll = NULL;
2474 private:
2475 String m_locale;
2476 UCollator *m_ucoll;
2477 Intl::IntlError m_errcode;
2479 IMPLEMENT_STATIC_REQUEST_LOCAL(Collator, s_collator);
2481 namespace {
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());
2487 ~ArraySortTmp() {
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; }
2495 private:
2496 TypedValue* m_arr;
2497 ArrayData* m_ad;
2501 static bool
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);
2513 return true;
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);
2521 return true;
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");
2528 return false;
2531 static bool
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);
2543 return true;
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);
2552 return true;
2556 throw_expected_array_or_collection_exception(ascending ? "asort" : "arsort");
2557 return false;
2560 static bool
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);
2572 return true;
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);
2581 return true;
2585 throw_expected_array_or_collection_exception(ascending ? "ksort" : "krsort");
2586 return false;
2589 bool HHVM_FUNCTION(sort,
2590 VRefParam array,
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,
2597 VRefParam array,
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,
2604 VRefParam array,
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,
2611 VRefParam array,
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,
2618 VRefParam array,
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,
2625 VRefParam array,
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
2633 // here.
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,
2650 &cmp_function);
2651 return true;
2652 } else {
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");
2677 return false;
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,
2687 &cmp_function);
2688 return true;
2689 } else {
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");
2715 return false;
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");
2747 return false;
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) {
2758 case SORT_STRING:
2759 case SORT_LOCALE_STRING:
2760 return tvReturn(ArrayUtil::StringUnique(arr_array));
2761 case SORT_NUMERIC:
2762 return tvReturn(ArrayUtil::NumericUnique(arr_array));
2763 case SORT_REGULAR:
2764 default:
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,
2779 int64_t attr,
2780 int64_t val) {
2781 return s_collator->setAttribute(attr, val);
2784 bool HHVM_FUNCTION(i18n_loc_set_strength,
2785 int64_t 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,
2795 const Variant& key,
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);
2805 } else {
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) {
2814 case SORT_NATURAL:
2815 return ascending ?
2816 Array::SortNaturalAscending : Array::SortNaturalDescending;
2817 case SORT_NATURAL_CASE:
2818 return ascending ?
2819 Array::SortNaturalCaseAscending: Array::SortNaturalCaseDescending;
2820 case SORT_NUMERIC:
2821 return ascending ?
2822 Array::SortNumericAscending : Array::SortNumericDescending;
2823 case SORT_STRING:
2824 return ascending ?
2825 Array::SortStringAscending : Array::SortStringDescending;
2826 case SORT_STRING_CASE:
2827 return ascending ?
2828 Array::SortStringAscendingCase : Array::SortStringDescendingCase;
2829 case SORT_LOCALE_STRING:
2830 return ascending ?
2831 Array::SortLocaleStringAscending : Array::SortLocaleStringDescending;
2832 case SORT_REGULAR:
2833 default:
2834 return ascending ?
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
2850 Array::SortData sd;
2851 sd.original = &tvAsVariant(tv);
2852 arrays.push_back(Array(sd.original->getArrayData()));
2853 sd.array = &arrays.back();
2854 sd.by_key = false;
2856 int sort_flags = SORT_REGULAR;
2857 bool ascending = true;
2858 for (int i = 1; i < ar->numArgs(); i++) {
2859 tv = getArg(ar, i);
2860 if (tvAsVariant(tv).isArray()) {
2861 sd.cmp_func = get_cmp_func(sort_flags, ascending);
2862 data.push_back(sd);
2864 sort_flags = SORT_REGULAR;
2865 ascending = true;
2867 sd.original = &tvAsVariant(tv);
2868 arrays.push_back(Array(sd.original->getArrayData()));
2869 sd.array = &arrays.back();
2870 } else {
2871 int n = getArg<KindOfInt64>(ar, i);
2872 if (n == SORT_ASC) {
2873 } else if (n == SORT_DESC) {
2874 ascending = false;
2875 } else {
2876 sort_flags = n;
2881 sd.cmp_func = get_cmp_func(sort_flags, ascending);
2882 data.push_back(sd);
2884 return arReturn(ar, Array::MultiSort(data, true));
2887 // HH\\dict
2888 Array HHVM_FUNCTION(HH_dict, const Variant& input) {
2889 return input.toDict();
2892 // HH\\keyset
2893 Array HHVM_FUNCTION(HH_keyset, const Variant& input) {
2894 return input.toKeyset();
2897 // HH\\vec
2898 Array HHVM_FUNCTION(HH_vec, const Variant& input) {
2899 return input.toVecArray();
2902 // HH\\varray
2903 Array HHVM_FUNCTION(HH_varray, const Variant& input) {
2904 return input.toVArray();
2907 // HH\\darray
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: {
2916 int64_t n;
2917 auto const& str = input.asCStrRef();
2918 if (str.get()->isStrictlyInteger(n)) {
2919 return tvReturn(n);
2921 return tvReturn(str);
2924 case KindOfInt64:
2925 case KindOfBoolean:
2926 case KindOfDouble:
2927 case KindOfResource:
2928 return tvReturn(input.toInt64());
2930 case KindOfUninit:
2931 case KindOfNull:
2932 return tvReturn(staticEmptyString());
2934 case KindOfPersistentVec:
2935 case KindOfVec:
2936 SystemLib::throwInvalidArgumentExceptionObject(
2937 "Vecs cannot be cast to an array-key"
2939 case KindOfPersistentDict:
2940 case KindOfDict:
2941 SystemLib::throwInvalidArgumentExceptionObject(
2942 "Dicts cannot be cast to an array-key"
2944 case KindOfPersistentKeyset:
2945 case KindOfKeyset:
2946 SystemLib::throwInvalidArgumentExceptionObject(
2947 "Keysets cannot be cast to an array-key"
2949 case KindOfPersistentArray:
2950 case KindOfArray:
2951 SystemLib::throwInvalidArgumentExceptionObject(
2952 "Arrays cannot be cast to an array-key"
2954 case KindOfObject:
2955 SystemLib::throwInvalidArgumentExceptionObject(
2956 "Objects cannot be cast to an array-key"
2959 case KindOfRef:
2960 break;
2962 not_reached();
2965 Array HHVM_FUNCTION(merge_xhp_attr_declarations,
2966 const Array& arr1,
2967 const Array& arr2,
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); });
2972 int idx = 2;
2973 IterateV(
2974 rest.get(),
2975 [&](TypedValue arr) {
2976 if (!isArrayType(arr.m_type)) {
2977 raise_param_type_warning(
2978 "__SystemLib\\merge_xhp_attr_declarations",
2979 idx+1,
2980 RuntimeOption::EvalHackArrDVArrs ? KindOfDict : KindOfArray,
2981 arr.m_type
2983 ret = Array{};
2984 return true;
2986 IterateKV(arr.m_data.parr, [&](Cell k, TypedValue v) { ret.set(k, v); });
2987 ++idx;
2988 return false;
2991 return ret;
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);
3060 HHVM_FE(array_pad);
3061 HHVM_FE(array_pop);
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);
3070 HHVM_FE(array_sum);
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);
3076 HHVM_FE(compact);
3077 HHVM_FE(shuffle);
3078 HHVM_FE(count);
3079 HHVM_FE(sizeof);
3080 HHVM_FE(each);
3081 HHVM_FE(current);
3082 HHVM_FE(next);
3083 HHVM_FE(pos);
3084 HHVM_FE(prev);
3085 HHVM_FE(reset);
3086 HHVM_FE(end);
3087 HHVM_FE(key);
3088 HHVM_FE(in_array);
3089 HHVM_FE(range);
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);
3106 HHVM_FE(sort);
3107 HHVM_FE(rsort);
3108 HHVM_FE(asort);
3109 HHVM_FE(arsort);
3110 HHVM_FE(ksort);
3111 HHVM_FE(krsort);
3112 HHVM_FE(usort);
3113 HHVM_FE(uasort);
3114 HHVM_FE(uksort);
3115 HHVM_FE(natsort);
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);
3133 loadSystemlib();
3135 } s_array_extension;