Make Array(ArrayData*) constructor explicit
[hiphop-php.git] / hphp / runtime / ext / ext_array.cpp
blobcd9c5043cdadc7b85d744e101878eff493777150
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-2014 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 +----------------------------------------------------------------------+
17 #include "hphp/runtime/ext/ext_array.h"
19 #include <vector>
21 #include "hphp/runtime/base/container-functions.h"
22 #include "hphp/runtime/ext/ext_function.h"
23 #include "hphp/runtime/ext/ext_continuation.h"
24 #include "hphp/runtime/ext/ext_collections.h"
25 #include "hphp/runtime/base/request-local.h"
26 #include "hphp/runtime/base/zend-collator.h"
27 #include "hphp/runtime/base/builtin-functions.h"
28 #include "hphp/runtime/base/sort-flags.h"
29 #include "hphp/runtime/vm/jit/translator.h"
30 #include "hphp/runtime/vm/jit/translator-inline.h"
31 #include "hphp/runtime/base/hphp-array.h"
32 #include "hphp/runtime/base/request-event-handler.h"
33 #include "hphp/util/logger.h"
35 #define SORT_DESC 3
36 #define SORT_ASC 4
37 namespace HPHP {
38 ///////////////////////////////////////////////////////////////////////////////
40 const StaticString s_count("count");
42 const int64_t k_UCOL_DEFAULT = UCOL_DEFAULT;
44 const int64_t k_UCOL_PRIMARY = UCOL_PRIMARY;
45 const int64_t k_UCOL_SECONDARY = UCOL_SECONDARY;
46 const int64_t k_UCOL_TERTIARY = UCOL_TERTIARY;
47 const int64_t k_UCOL_DEFAULT_STRENGTH = UCOL_DEFAULT_STRENGTH;
48 const int64_t k_UCOL_QUATERNARY = UCOL_QUATERNARY;
49 const int64_t k_UCOL_IDENTICAL = UCOL_IDENTICAL;
51 const int64_t k_UCOL_OFF = UCOL_OFF;
52 const int64_t k_UCOL_ON = UCOL_ON;
54 const int64_t k_UCOL_SHIFTED = UCOL_SHIFTED;
55 const int64_t k_UCOL_NON_IGNORABLE = UCOL_NON_IGNORABLE;
57 const int64_t k_UCOL_LOWER_FIRST = UCOL_LOWER_FIRST;
58 const int64_t k_UCOL_UPPER_FIRST = UCOL_UPPER_FIRST;
60 const int64_t k_UCOL_FRENCH_COLLATION = UCOL_FRENCH_COLLATION;
61 const int64_t k_UCOL_ALTERNATE_HANDLING = UCOL_ALTERNATE_HANDLING;
62 const int64_t k_UCOL_CASE_FIRST = UCOL_CASE_FIRST;
63 const int64_t k_UCOL_CASE_LEVEL = UCOL_CASE_LEVEL;
64 const int64_t k_UCOL_NORMALIZATION_MODE = UCOL_NORMALIZATION_MODE;
65 const int64_t k_UCOL_STRENGTH = UCOL_STRENGTH;
66 const int64_t k_UCOL_HIRAGANA_QUATERNARY_MODE = UCOL_HIRAGANA_QUATERNARY_MODE;
67 const int64_t k_UCOL_NUMERIC_COLLATION = UCOL_NUMERIC_COLLATION;
69 using HPHP::JIT::CallerFrame;
70 using HPHP::JIT::EagerCallerFrame;
72 #define getCheckedArrayRet(input, fail) \
73 auto const cell_##input = static_cast<const Variant&>(input).asCell(); \
74 if (UNLIKELY(cell_##input->m_type != KindOfArray)) { \
75 throw_expected_array_exception(); \
76 return fail; \
77 } \
78 ArrNR arrNR_##input(cell_##input->m_data.parr); \
79 const Array& arr_##input = arrNR_##input.asArray();
81 #define getCheckedArray(input) getCheckedArrayRet(input, uninit_null())
83 Variant f_array_change_key_case(const Variant& input, int64_t case_ /* = 0 */) {
84 getCheckedArrayRet(input, false);
85 return ArrayUtil::ChangeKeyCase(arr_input, !case_);
88 Variant f_array_chunk(const Variant& input, int chunkSize,
89 bool preserve_keys /* = false */) {
91 const auto& cellInput = *input.asCell();
92 if (UNLIKELY(!isContainer(cellInput))) {
93 raise_warning("Invalid operand type was used: %s expects "
94 "an array or collection as argument 1", __FUNCTION__+2);
95 return init_null();
98 if (chunkSize < 1) {
99 throw_invalid_argument("size: %d", chunkSize);
100 return init_null();
103 Array ret = Array::Create();
104 Array chunk;
105 int current = 0;
106 for (ArrayIter iter(cellInput); iter; ++iter) {
107 if (preserve_keys) {
108 chunk.setWithRef(iter.first(), iter.secondRefPlus(), true);
109 } else {
110 chunk.appendWithRef(iter.secondRefPlus());
112 if ((++current % chunkSize) == 0) {
113 ret.append(chunk);
114 chunk.clear();
117 if (!chunk.empty()) {
118 ret.append(chunk);
121 return ret;
124 static inline bool array_column_coerce_key(Variant &key, const char *name) {
125 /* NULL has a special meaning for each field */
126 if (key.isNull()) {
127 return true;
130 /* Custom coercion rules for key types */
131 if (key.isInteger() || key.isDouble()) {
132 key = key.toInt64();
133 return true;
134 } else if (key.isString() || key.isObject()) {
135 key = key.toString();
136 return true;
137 } else {
138 raise_warning("The %s key should be either a string or an integer", name);
139 return false;
143 Variant f_array_column(const Variant& input, const Variant& val_key,
144 const Variant& idx_key /* = null_variant */) {
145 /* Be strict about array type */
146 getCheckedArrayRet(input, uninit_null());
147 Variant val = val_key, idx = idx_key;
148 if (!array_column_coerce_key(val, "column") ||
149 !array_column_coerce_key(idx, "index")) {
150 return false;
152 Array ret = Array::Create();
153 for(auto it = arr_input.begin(); !it.end(); it.next()) {
154 if (!it.second().isArray()) {
155 continue;
157 Array sub = it.second().toArray();
159 Variant elem;
160 if (val.isNull()) {
161 elem = sub;
162 } else if (sub.exists(val)) {
163 elem = sub[val];
164 } else {
165 // skip subarray without named element
166 continue;
169 if (idx.isNull() || !sub.exists(idx)) {
170 ret.append(elem);
171 } else if (sub[idx].isObject()) {
172 ret.set(sub[idx].toString(), elem);
173 } else {
174 ret.set(sub[idx], elem);
177 return ret;
180 Variant f_array_combine(const Variant& keys, const Variant& values) {
181 const auto& cell_keys = *keys.asCell();
182 const auto& cell_values = *values.asCell();
183 if (UNLIKELY(!isContainer(cell_keys) || !isContainer(cell_values))) {
184 raise_warning("Invalid operand type was used: array_combine expects "
185 "arrays or collections");
186 return uninit_null();
188 if (UNLIKELY(getContainerSize(cell_keys) != getContainerSize(cell_values))) {
189 raise_warning("array_combine(): Both parameters should have an equal "
190 "number of elements");
191 return false;
193 Array ret = Array::Create();
194 for (ArrayIter iter1(cell_keys), iter2(cell_values);
195 iter1; ++iter1, ++iter2) {
196 const Variant& key = iter1.secondRefPlus();
197 if (key.isInteger() || key.isString()) {
198 ret.setWithRef(key, iter2.secondRefPlus());
199 } else {
200 ret.setWithRef(key.toString(), iter2.secondRefPlus());
203 return ret;
206 Variant f_array_count_values(const Variant& input) {
207 getCheckedArray(input);
208 return ArrayUtil::CountValues(arr_input);
211 Variant f_array_fill_keys(const Variant& keys, const Variant& value) {
212 const auto& cell_keys = *keys.asCell();
213 if (UNLIKELY(!isContainer(cell_keys))) {
214 raise_warning("Invalid operand type was used: array_fill_keys expects "
215 "an array or collection");
216 return uninit_null();
219 auto size = getContainerSize(cell_keys);
220 if (!size) return empty_array;
222 ArrayInit ai(size);
223 for (ArrayIter iter(cell_keys); iter; ++iter) {
224 auto& key = iter.secondRefPlus();
225 // This is intentionally different to the $foo[$invalid_key] coercion.
226 // See tests/slow/ext_array/array_fill_keys_tostring.php for examples.
227 if (LIKELY(key.isInteger() || key.isString())) {
228 ai.set(key, value);
229 } else if (RuntimeOption::EnableHipHopSyntax) {
230 // @todo (fredemmott): Use the Zend toString() behavior, but retain the
231 // warning/error behind a separate config setting
232 raise_warning("array_fill_keys: keys must be ints or strings");
233 ai.set(key, value);
234 } else {
235 ai.set(key.toString(), value);
238 return ai.create();
241 Variant f_array_fill(int start_index, int num, const Variant& value) {
242 if (num <= 0) {
243 throw_invalid_argument("num: [non-positive]");
244 return false;
247 Array ret;
248 ret.set(start_index, value);
249 for (int i = num - 1; i > 0; i--) {
250 ret.append(value);
252 return ret;
255 Variant f_array_flip(const Variant& trans) {
257 auto const& transCell = *trans.asCell();
258 if (UNLIKELY(!isContainer(transCell))) {
259 raise_warning("Invalid operand type was used: %s expects "
260 "an array or collection", __FUNCTION__+2);
261 return uninit_null();
264 ArrayInit ret(getContainerSize(transCell));
265 for (ArrayIter iter(transCell); iter; ++iter) {
266 const Variant& value(iter.secondRefPlus());
267 if (value.isString() || value.isInteger()) {
268 ret.set(value, iter.first());
269 } else {
270 raise_warning("Can only flip STRING and INTEGER values!");
273 return ret.toVariant();
276 bool f_array_key_exists(const Variant& key, const Variant& search) {
277 const ArrayData *ad;
279 auto const searchCell = search.asCell();
280 if (LIKELY(searchCell->m_type == KindOfArray)) {
281 ad = searchCell->m_data.parr;
282 } else if (searchCell->m_type == KindOfObject) {
283 ObjectData* obj = searchCell->m_data.pobj;
284 if (obj->isCollection()) {
285 return collectionContains(obj, key);
287 return f_array_key_exists(key, toArray(search));
288 } else {
289 throw_bad_type_exception("array_key_exists expects an array or an object; "
290 "false returned.");
291 return false;
294 auto const cell = key.asCell();
295 switch (cell->m_type) {
296 case KindOfString:
297 case KindOfStaticString: {
298 int64_t n = 0;
299 StringData *sd = cell->m_data.pstr;
300 if (sd->isStrictlyInteger(n)) {
301 return ad->exists(n);
303 return ad->exists(StrNR(sd));
305 case KindOfInt64:
306 return ad->exists(cell->m_data.num);
307 case KindOfUninit:
308 case KindOfNull:
309 return ad->exists(empty_string);
310 default:
311 break;
313 raise_warning("Array key should be either a string or an integer");
314 return false;
317 bool f_key_exists(const Variant& key, const Variant& search) {
318 return f_array_key_exists(key, search);
321 Variant f_array_keys(const Variant& input, const Variant& search_value /* = null_variant */,
322 bool strict /* = false */) {
323 const auto& cell_input = *input.asCell();
324 if (UNLIKELY(!isContainer(cell_input))) {
325 raise_warning("array_keys() expects parameter 1 to be an array "
326 "or collection");
327 return uninit_null();
330 if (LIKELY(!search_value.isInitialized())) {
331 PackedArrayInit ai(getContainerSize(cell_input));
332 for (ArrayIter iter(cell_input); iter; ++iter) {
333 ai.append(iter.first());
335 return ai.toArray();
336 } else {
337 Array ai = Array::attach(HphpArray::MakeReserve(0));
338 for (ArrayIter iter(cell_input); iter; ++iter) {
339 if ((strict && HPHP::same(iter.secondRefPlus(), search_value)) ||
340 (!strict && HPHP::equal(iter.secondRefPlus(), search_value))) {
341 ai.append(iter.first());
344 return ai;
348 Variant f_array_map(int _argc, const Variant& callback, const Variant& arr1, const Array& _argv /* = null_array */) {
349 CallCtx ctx;
350 ctx.func = NULL;
351 if (!callback.isNull()) {
352 EagerCallerFrame cf;
353 vm_decode_function(callback, cf(), false, ctx);
355 const auto& cell_arr1 = *arr1.asCell();
356 if (UNLIKELY(!isContainer(cell_arr1))) {
357 raise_warning("array_map(): Argument #2 should be an array or collection");
358 return uninit_null();
360 if (LIKELY(_argv.empty())) {
361 // Handle the common case where the caller passed two
362 // params (a callback and a container)
363 if (!ctx.func) {
364 if (cell_arr1.m_type == KindOfArray) {
365 return arr1;
366 } else {
367 return arr1.toArray();
370 Array ret = Array::Create();
371 for (ArrayIter iter(arr1); iter; ++iter) {
372 Variant result;
373 g_context->invokeFuncFew((TypedValue*)&result, ctx, 1,
374 iter.secondRefPlus().asCell());
375 ret.add(iter.first(), result, true);
377 return ret;
380 // Handle the uncommon case where the caller passed a callback
381 // and two or more containers
382 ArrayIter* iters =
383 (ArrayIter*)smart_malloc(sizeof(ArrayIter) * (_argv.size() + 1));
384 size_t numIters = 0;
385 SCOPE_EXIT {
386 while (numIters--) iters[numIters].~ArrayIter();
387 smart_free(iters);
389 size_t maxLen = getContainerSize(cell_arr1);
390 (void) new (&iters[numIters]) ArrayIter(cell_arr1);
391 ++numIters;
392 for (ArrayIter it(_argv); it; ++it, ++numIters) {
393 const auto& c = *it.secondRefPlus().asCell();
394 if (UNLIKELY(!isContainer(c))) {
395 raise_warning("array_map(): Argument #%d should be an array or "
396 "collection", (int)(numIters + 2));
397 (void) new (&iters[numIters]) ArrayIter(it.secondRefPlus().toArray());
398 } else {
399 (void) new (&iters[numIters]) ArrayIter(c);
400 size_t len = getContainerSize(c);
401 if (len > maxLen) maxLen = len;
404 Array ret = Array::Create();
405 for (size_t k = 0; k < maxLen; k++) {
406 Array params;
407 for (size_t i = 0; i < numIters; ++i) {
408 if (iters[i]) {
409 params.append(iters[i].secondRefPlus());
410 ++iters[i];
411 } else {
412 params.append(init_null_variant);
415 if (ctx.func) {
416 Variant result;
417 g_context->invokeFunc((TypedValue*)&result,
418 ctx.func, params, ctx.this_,
419 ctx.cls, nullptr, ctx.invName);
420 ret.append(result);
421 } else {
422 ret.append(params);
425 return ret;
428 static void php_array_merge(Array &arr1, const Array& arr2) {
429 arr1.merge(arr2);
432 static void php_array_merge_recursive(PointerSet &seen, bool check,
433 Array &arr1, const Array& arr2) {
434 if (check) {
435 if (seen.find((void*)arr1.get()) != seen.end()) {
436 raise_warning("array_merge_recursive(): recursion detected");
437 return;
439 seen.insert((void*)arr1.get());
442 for (ArrayIter iter(arr2); iter; ++iter) {
443 Variant key(iter.first());
444 const Variant& value(iter.secondRef());
445 if (key.isNumeric()) {
446 arr1.appendWithRef(value);
447 } else if (arr1.exists(key, true)) {
448 // There is no need to do toKey() conversion, for a key that is already
449 // in the array.
450 Variant &v = arr1.lvalAt(key, AccessFlags::Key);
451 Array subarr1(v.toArray()->copy());
452 php_array_merge_recursive(seen, v.isReferenced(), subarr1,
453 value.toArray());
454 v.unset(); // avoid contamination of the value that was strongly bound
455 v = subarr1;
456 } else {
457 arr1.setWithRef(key, value, true);
461 if (check) {
462 seen.erase((void*)arr1.get());
466 Variant f_array_merge(int _argc, const Variant& array1,
467 const Array& _argv /* = null_array */) {
468 getCheckedArray(array1);
469 Array ret = Array::Create();
470 php_array_merge(ret, arr_array1);
471 for (ArrayIter iter(_argv); iter; ++iter) {
472 Variant v = iter.second();
473 if (!v.isArray()) {
474 throw_expected_array_exception();
475 return uninit_null();
477 const Array& arr_v = v.asCArrRef();
478 php_array_merge(ret, arr_v);
480 return ret;
483 Variant f_array_merge_recursive(int _argc, const Variant& array1,
484 const Array& _argv /* = null_array */) {
485 getCheckedArray(array1);
486 Array ret = Array::Create();
487 PointerSet seen;
488 php_array_merge_recursive(seen, false, ret, arr_array1);
489 assert(seen.empty());
490 for (ArrayIter iter(_argv); iter; ++iter) {
491 Variant v = iter.second();
492 if (!v.isArray()) {
493 throw_expected_array_exception();
494 return uninit_null();
496 const Array& arr_v = v.asCArrRef();
497 php_array_merge_recursive(seen, false, ret, arr_v);
498 assert(seen.empty());
500 return ret;
503 static void php_array_replace(Array &arr1, const Array& arr2) {
504 for (ArrayIter iter(arr2); iter; ++iter) {
505 Variant key = iter.first();
506 const Variant& value = iter.secondRef();
507 arr1.setWithRef(key, value, true);
511 static void php_array_replace_recursive(PointerSet &seen, bool check,
512 Array &arr1, const Array& arr2) {
513 if (check) {
514 if (seen.find((void*)arr1.get()) != seen.end()) {
515 raise_warning("array_replace_recursive(): recursion detected");
516 return;
518 seen.insert((void*)arr1.get());
521 for (ArrayIter iter(arr2); iter; ++iter) {
522 Variant key = iter.first();
523 const Variant& value = iter.secondRef();
524 if (arr1.exists(key, true) && value.isArray()) {
525 Variant &v = arr1.lvalAt(key, AccessFlags::Key);
526 if (v.isArray()) {
527 Array subarr1 = v.toArray();
528 const ArrNR& arr_value = value.toArrNR();
529 php_array_replace_recursive(seen, v.isReferenced(), subarr1,
530 arr_value);
531 v = subarr1;
532 } else {
533 arr1.set(key, value, true);
535 } else {
536 arr1.setWithRef(key, value, true);
540 if (check) {
541 seen.erase((void*)arr1.get());
545 Variant f_array_replace(int _argc, const Variant& array1,
546 const Array& _argv /* = null_array */) {
547 getCheckedArray(array1);
548 Array ret = Array::Create();
549 php_array_replace(ret, arr_array1);
550 for (ArrayIter iter(_argv); iter; ++iter) {
551 const Variant& v = iter.secondRef();
552 getCheckedArray(v);
553 php_array_replace(ret, arr_v);
555 return ret;
558 Variant f_array_replace_recursive(int _argc, const Variant& array1,
559 const Array& _argv /* = null_array */) {
560 getCheckedArray(array1);
561 Array ret = Array::Create();
562 PointerSet seen;
563 php_array_replace_recursive(seen, false, ret, arr_array1);
564 assert(seen.empty());
565 for (ArrayIter iter(_argv); iter; ++iter) {
566 const Variant& v = iter.secondRef();
567 getCheckedArray(v);
568 php_array_replace_recursive(seen, false, ret, arr_v);
569 assert(seen.empty());
571 return ret;
574 Variant f_array_pad(const Variant& input, int pad_size, const Variant& pad_value) {
575 getCheckedArray(input);
576 if (pad_size > 0) {
577 return ArrayUtil::Pad(arr_input, pad_value, pad_size, true);
579 return ArrayUtil::Pad(arr_input, pad_value, -pad_size, false);
582 Variant f_array_pop(VRefParam containerRef) {
583 const auto* container = containerRef->asCell();
584 if (UNLIKELY(!isMutableContainer(*container))) {
585 raise_warning(
586 "%s() expects parameter 1 to be an array or mutable collection",
587 __FUNCTION__+2 /* remove the "f_" prefix */);
588 return uninit_null();
590 if (!getContainerSize(containerRef)) {
591 return uninit_null();
593 if (container->m_type == KindOfArray) {
594 return containerRef.wrapped().toArrRef().pop();
596 assert(container->m_type == KindOfObject);
597 auto* obj = container->m_data.pobj;
598 assert(obj->isCollection());
599 switch (obj->getCollectionType()) {
600 case Collection::VectorType: return static_cast<c_Vector*>(obj)->t_pop();
601 case Collection::MapType: return static_cast<c_Map*>(obj)->pop();
602 case Collection::SetType: return static_cast<c_Set*>(obj)->pop();
603 default: break;
605 assert(false);
606 return uninit_null();
609 Variant f_array_product(const Variant& array) {
610 getCheckedArray(array);
611 int64_t i;
612 double d;
613 if (ArrayUtil::Product(arr_array, &i, &d) == KindOfInt64) {
614 return i;
615 } else {
616 return d;
620 Variant f_array_push(int _argc, VRefParam container,
621 const Variant& var, const Array& _argv /* = null_array */) {
623 if (LIKELY(container->isArray())) {
624 auto const array_cell = container.wrapped().asCell();
625 assert(array_cell->m_type == KindOfArray);
628 * Important note: this *must* cast the parr in the inner cell to
629 * the Array&---we can't copy it to the stack or anything because we
630 * might escalate.
632 Array& arr_array = *reinterpret_cast<Array*>(&array_cell->m_data.parr);
633 arr_array.append(var);
634 for (ArrayIter iter(_argv); iter; ++iter) {
635 arr_array.append(iter.second());
637 return arr_array.size();
640 if (container.isObject()) {
641 ObjectData* obj = container.getObjectData();
642 auto collection_type = obj->getCollectionType();
643 if (collection_type == Collection::VectorType) {
644 c_Vector* vec = static_cast<c_Vector*>(obj);
645 vec->reserve(vec->size() + _argc + 1);
646 vec->t_add(var);
647 for (ArrayIter iter(_argv); iter; ++iter) {
648 vec->t_add(iter.second());
650 return vec->size();
651 } else if (collection_type == Collection::SetType) {
652 c_Set* set = static_cast<c_Set*>(obj);
653 set->reserve(set->size() + _argc + 1);
654 set->t_add(var);
655 for (ArrayIter iter(_argv); iter; ++iter) {
656 set->t_add(iter.second());
658 return set->size();
660 // other collection types are unsupported:
661 // - mapping collections require a key
662 // - frozen collections don't allow insertion
665 throw_expected_array_or_collection_exception();
666 return uninit_null();
669 Variant f_array_rand(const Variant& input, int num_req /* = 1 */) {
670 getCheckedArray(input);
671 return ArrayUtil::RandomKeys(arr_input, num_req);
674 static Variant reduce_func(const Variant& result, const Variant& operand, const void *data) {
675 CallCtx* ctx = (CallCtx*)data;
676 Variant ret;
677 TypedValue args[2] = { *result.asCell(), *operand.asCell() };
678 g_context->invokeFuncFew(ret.asTypedValue(), *ctx, 2, args);
679 return ret;
681 Variant f_array_reduce(const Variant& input, const Variant& callback,
682 const Variant& initial /* = null_variant */) {
683 getCheckedArray(input);
684 CallCtx ctx;
685 CallerFrame cf;
686 vm_decode_function(callback, cf(), false, ctx);
687 if (ctx.func == NULL) {
688 return uninit_null();
690 return ArrayUtil::Reduce(arr_input, reduce_func, &ctx, initial);
693 Variant f_array_reverse(const Variant& input, bool preserve_keys /* = false */) {
695 const auto& cell_input = *input.asCell();
696 if (UNLIKELY(!isContainer(cell_input))) {
697 raise_warning("Invalid operand type was used: %s expects "
698 "an array or collection as argument 1",
699 __FUNCTION__+2);
700 return uninit_null();
703 if (LIKELY(cell_input.m_type == KindOfArray)) {
704 ArrNR arrNR(cell_input.m_data.parr);
705 const Array& arr = arrNR.asArray();
706 return ArrayUtil::Reverse(arr, preserve_keys);
709 // For collections, we convert to their array representation and then
710 // reverse it, rather than building a reversed version of ArrayIter
711 assert(cell_input.m_type == KindOfObject);
712 ObjectData* obj = cell_input.m_data.pobj;
713 assert(obj && obj->isCollection());
714 return ArrayUtil::Reverse(obj->o_toArray(), preserve_keys);
717 Variant f_array_shift(VRefParam array) {
718 const auto* cell_array = array->asCell();
719 if (UNLIKELY(!isContainer(*cell_array))) {
720 raise_warning(
721 "%s() expects parameter 1 to be an array or mutable collection",
722 __FUNCTION__+2 /* remove the "f_" prefix */);
723 return uninit_null();
725 if (cell_array->m_type == KindOfArray) {
726 return array.wrapped().toArrRef().dequeue();
728 assert(cell_array->m_type == KindOfObject);
729 auto* obj = cell_array->m_data.pobj;
730 assert(obj->isCollection());
731 switch (obj->getCollectionType()) {
732 case Collection::VectorType: {
733 auto* vec = static_cast<c_Vector*>(obj);
734 if (!vec->size()) return uninit_null();
735 return vec->popFront();
737 case Collection::MapType: {
738 auto* mp = static_cast<BaseMap*>(obj);
739 if (!mp->size()) return uninit_null();
740 return mp->popFront();
742 case Collection::SetType: {
743 auto* st = static_cast<c_Set*>(obj);
744 if (!st->size()) return uninit_null();
745 return st->popFront();
747 default: {
748 raise_warning(
749 "%s() expects parameter 1 to be an array or mutable collection",
750 __FUNCTION__+2 /* remove the "f_" prefix */);
751 return uninit_null();
756 Variant f_array_slice(const Variant& input, int offset,
757 const Variant& length /* = null_variant */,
758 bool preserve_keys /* = false */) {
759 const auto& cell_input = *input.asCell();
760 if (UNLIKELY(!isContainer(cell_input))) {
761 raise_warning("Invalid operand type was used: %s expects "
762 "an array or collection as argument 1",
763 __FUNCTION__+2);
764 return uninit_null();
766 int64_t len = length.isNull() ? 0x7FFFFFFF : length.toInt64();
768 int num_in = getContainerSize(cell_input);
769 if (offset > num_in) {
770 offset = num_in;
771 } else if (offset < 0 && (offset = (num_in + offset)) < 0) {
772 offset = 0;
775 if (len < 0) {
776 len = num_in - offset + len;
777 } else if (((unsigned)offset + (unsigned)len) > (unsigned)num_in) {
778 len = num_in - offset;
781 if (len <= 0) {
782 return empty_array;
785 // PackedArrayInit can't be used because non-numeric keys are preserved
786 // even when preserve_keys is false
787 Array ret = Array::attach(HphpArray::MakeReserve(len));
788 int pos = 0;
789 ArrayIter iter(input);
790 for (; pos < offset && iter; ++pos, ++iter) {}
791 for (; pos < (offset + len) && iter; ++pos, ++iter) {
792 Variant key(iter.first());
793 bool doAppend = !preserve_keys && key.isNumeric();
794 const Variant& v = iter.secondRefPlus();
795 if (doAppend) {
796 ret.appendWithRef(v);
797 } else {
798 ret.setWithRef(key, v, true);
801 return ret;
804 Variant f_array_splice(VRefParam input, int offset,
805 const Variant& length /* = null_variant */,
806 const Variant& replacement /* = null_variant */) {
807 getCheckedArray(input);
808 Array ret(Array::Create());
809 int64_t len = length.isNull() ? 0x7FFFFFFF : length.toInt64();
810 input = ArrayUtil::Splice(arr_input, offset, len, replacement, &ret);
811 return ret;
814 Variant f_array_sum(const Variant& array) {
815 getCheckedArray(array);
816 int64_t i;
817 double d;
818 if (ArrayUtil::Sum(arr_array, &i, &d) == KindOfInt64) {
819 return i;
820 } else {
821 return d;
825 Variant f_array_unshift(int _argc, VRefParam array, const Variant& var, const Array& _argv /* = null_array */) {
826 const auto* cell_array = array->asCell();
827 if (UNLIKELY(!isContainer(*cell_array))) {
828 raise_warning("%s() expects parameter 1 to be an array, Vector, or Set",
829 __FUNCTION__+2 /* remove the "f_" prefix */);
830 return uninit_null();
832 if (cell_array->m_type == KindOfArray) {
833 if (array.toArray()->isVectorData()) {
834 if (!_argv.empty()) {
835 for (ssize_t pos = _argv->iter_end(); pos != ArrayData::invalid_index;
836 pos = _argv->iter_rewind(pos)) {
837 array.wrapped().toArrRef().prepend(_argv->getValueRef(pos));
840 array.wrapped().toArrRef().prepend(var);
841 } else {
843 Array newArray;
844 newArray.append(var);
845 if (!_argv.empty()) {
846 for (ssize_t pos = _argv->iter_begin();
847 pos != ArrayData::invalid_index;
848 pos = _argv->iter_advance(pos)) {
849 newArray.append(_argv->getValueRef(pos));
852 for (ArrayIter iter(array.toArray()); iter; ++iter) {
853 Variant key(iter.first());
854 const Variant& value(iter.secondRef());
855 if (key.isInteger()) {
856 newArray.appendWithRef(value);
857 } else {
858 newArray.setWithRef(key, value, true);
861 array = newArray;
863 // Reset the array's internal pointer
864 if (array.is(KindOfArray)) {
865 f_reset(array);
868 return array.toArray().size();
870 // Handle collections
871 assert(cell_array->m_type == KindOfObject);
872 auto* obj = cell_array->m_data.pobj;
873 assert(obj->isCollection());
874 switch (obj->getCollectionType()) {
875 case Collection::VectorType: {
876 auto* vec = static_cast<c_Vector*>(obj);
877 if (!_argv.empty()) {
878 for (ssize_t pos = _argv->iter_end(); pos != ArrayData::invalid_index;
879 pos = _argv->iter_rewind(pos)) {
880 vec->addFront(cvarToCell(&_argv->getValueRef(pos)));
883 vec->addFront(cvarToCell(&var));
884 return vec->size();
886 case Collection::SetType: {
887 auto* st = static_cast<c_Set*>(obj);
888 if (!_argv.empty()) {
889 for (ssize_t pos = _argv->iter_end(); pos != ArrayData::invalid_index;
890 pos = _argv->iter_rewind(pos)) {
891 st->addFront(cvarToCell(&_argv->getValueRef(pos)));
894 st->addFront(cvarToCell(&var));
895 return st->size();
897 default: {
898 raise_warning("%s() expects parameter 1 to be an array, Vector, or Set",
899 __FUNCTION__+2 /* remove the "f_" prefix */);
900 return uninit_null();
905 Variant f_array_values(const Variant& input) {
906 const auto& cell_input = *input.asCell();
907 if (!isContainer(cell_input)) {
908 raise_warning("array_values() expects parameter 1 to be an array "
909 "or collection");
910 return uninit_null();
912 PackedArrayInit ai(getContainerSize(cell_input));
913 for (ArrayIter iter(cell_input); iter; ++iter) {
914 ai.appendWithRef(iter.secondRefPlus());
916 return ai.toArray();
919 static void walk_func(VRefParam value, const Variant& key, const Variant& userdata,
920 const void *data) {
921 CallCtx* ctx = (CallCtx*)data;
922 Variant sink;
923 TypedValue args[3] = { *value->asRef(), *key.asCell(), *userdata.asCell() };
924 g_context->invokeFuncFew(sink.asTypedValue(), *ctx, 3, args);
927 bool f_array_walk_recursive(VRefParam input, const Variant& funcname,
928 const Variant& userdata /* = null_variant */) {
929 if (!input.isArray()) {
930 throw_expected_array_exception();
931 return false;
933 CallCtx ctx;
934 CallerFrame cf;
935 vm_decode_function(funcname, cf(), false, ctx);
936 if (ctx.func == NULL) {
937 return false;
939 PointerSet seen;
940 ArrayUtil::Walk(input, walk_func, &ctx, true, &seen, userdata);
941 return true;
944 bool f_array_walk(VRefParam input, const Variant& funcname,
945 const Variant& userdata /* = null_variant */) {
946 if (!input.isArray()) {
947 throw_expected_array_exception();
948 return false;
950 CallCtx ctx;
951 CallerFrame cf;
952 vm_decode_function(funcname, cf(), false, ctx);
953 if (ctx.func == NULL) {
954 return false;
956 ArrayUtil::Walk(input, walk_func, &ctx, false, NULL, userdata);
957 return true;
960 static void compact(VarEnv* v, Array &ret, const Variant& var) {
961 if (var.isArray()) {
962 for (ArrayIter iter(var.getArrayData()); iter; ++iter) {
963 compact(v, ret, iter.second());
965 } else {
966 String varname = var.toString();
967 if (!varname.empty() && v->lookup(varname.get()) != NULL) {
968 ret.set(varname, *reinterpret_cast<Variant*>(v->lookup(varname.get())));
973 Array f_compact(int _argc, const Variant& varname, const Array& _argv /* = null_array */) {
974 Array ret = Array::Create();
975 VarEnv* v = g_context->getVarEnv();
976 if (v) {
977 compact(v, ret, varname);
978 compact(v, ret, _argv);
980 return ret;
983 static int php_count_recursive(const Array& array) {
984 long cnt = array.size();
985 for (ArrayIter iter(array); iter; ++iter) {
986 Variant value = iter.second();
987 if (value.isArray()) {
988 const Array& arr_value = value.asCArrRef();
989 cnt += php_count_recursive(arr_value);
992 return cnt;
995 bool f_shuffle(VRefParam array) {
996 if (!array.isArray()) {
997 throw_expected_array_exception();
998 return false;
1000 array = ArrayUtil::Shuffle(array);
1001 return true;
1004 int64_t f_count(const Variant& var, int64_t mode /* = 0 */) {
1005 switch (var.getType()) {
1006 case KindOfUninit:
1007 case KindOfNull:
1008 return 0;
1009 case KindOfObject:
1011 Object obj = var.toObject();
1012 if (obj->isCollection()) {
1013 return getCollectionSize(obj.get());
1015 if (obj.instanceof(SystemLib::s_CountableClass)) {
1016 return obj->o_invoke_few_args(s_count, 0).toInt64();
1019 break;
1020 case KindOfArray:
1021 if (mode) {
1022 const Array& arr_var = var.toCArrRef();
1023 return php_count_recursive(arr_var);
1025 return var.getArrayData()->size();
1026 default:
1027 break;
1029 return 1;
1032 int64_t f_sizeof(const Variant& var, int64_t mode /* = 0 */) {
1033 return f_count(var, mode);
1036 namespace {
1038 enum class NoCow {};
1039 template<class DoCow = void, class NonArrayRet, class OpPtr>
1040 static Variant iter_op_impl(VRefParam refParam, OpPtr op, NonArrayRet nonArray,
1041 bool(ArrayData::*pred)() const =
1042 &ArrayData::isInvalid) {
1043 auto& cell = *refParam.wrapped().asCell();
1044 if (cell.m_type != KindOfArray) {
1045 throw_bad_type_exception("expecting an array");
1046 return Variant(nonArray);
1049 auto ad = cell.m_data.parr;
1050 auto constexpr doCow = !std::is_same<DoCow, NoCow>::value;
1051 if (doCow && ad->hasMultipleRefs() && !(ad->*pred)() &&
1052 !ad->noCopyOnWrite()) {
1053 ad = ad->copy();
1054 cellSet(make_tv<KindOfArray>(ad), cell);
1056 return (ad->*op)();
1061 Variant f_each(VRefParam refParam) {
1062 return iter_op_impl(
1063 refParam,
1064 &ArrayData::each,
1065 Variant::NullInit()
1069 Variant f_current(VRefParam refParam) {
1070 return iter_op_impl<NoCow>(
1071 refParam,
1072 &ArrayData::current,
1073 false
1077 Variant f_pos(VRefParam refParam) {
1078 return f_current(refParam);
1081 Variant f_key(VRefParam refParam) {
1082 return iter_op_impl<NoCow>(
1083 refParam,
1084 &ArrayData::key,
1085 false
1089 Variant f_next(VRefParam refParam) {
1090 return iter_op_impl(
1091 refParam,
1092 &ArrayData::next,
1093 false
1097 Variant f_prev(VRefParam refParam) {
1098 return iter_op_impl(
1099 refParam,
1100 &ArrayData::prev,
1101 false
1105 Variant f_reset(VRefParam refParam) {
1106 return iter_op_impl(
1107 refParam,
1108 &ArrayData::reset,
1109 false,
1110 &ArrayData::isHead
1114 Variant f_end(VRefParam refParam) {
1115 return iter_op_impl(
1116 refParam,
1117 &ArrayData::end,
1118 false,
1119 &ArrayData::isTail
1123 bool f_in_array(const Variant& needle, const Variant& haystack, bool strict /* = false */) {
1124 const auto& cell_haystack = *haystack.asCell();
1125 if (UNLIKELY(!isContainer(cell_haystack))) {
1126 raise_warning("in_array() expects parameter 2 to be an array "
1127 "or collection");
1128 return false;
1131 ArrayIter iter(cell_haystack);
1132 if (strict) {
1133 for (; iter; ++iter) {
1134 if (HPHP::same(iter.secondRefPlus(), needle)) {
1135 return true;
1138 } else {
1139 for (; iter; ++iter) {
1140 if (HPHP::equal(iter.secondRefPlus(), needle)) {
1141 return true;
1145 return false;
1148 Variant f_array_search(const Variant& needle, const Variant& haystack,
1149 bool strict /* = false */) {
1150 const auto& cell_haystack = *haystack.asCell();
1151 if (UNLIKELY(!isContainer(cell_haystack))) {
1152 raise_warning("array_search() expects parameter 2 to be an array "
1153 "or collection");
1154 return uninit_null();
1157 ArrayIter iter(cell_haystack);
1158 if (strict) {
1159 for (; iter; ++iter) {
1160 if (HPHP::same(iter.secondRefPlus(), needle)) {
1161 return iter.first();
1164 } else {
1165 for (; iter; ++iter) {
1166 if (HPHP::equal(iter.secondRefPlus(), needle)) {
1167 return iter.first();
1171 return false;
1174 Variant f_range(const Variant& low, const Variant& high, const Variant& step /* = 1 */) {
1175 bool is_step_double = false;
1176 double dstep = 1.0;
1177 if (step.isDouble()) {
1178 dstep = step.toDouble();
1179 is_step_double = true;
1180 } else if (step.isString()) {
1181 int64_t sn;
1182 double sd;
1183 DataType stype = step.toString().get()->isNumericWithVal(sn, sd, 0);
1184 if (stype == KindOfDouble) {
1185 is_step_double = true;
1186 dstep = sd;
1187 } else if (stype == KindOfInt64) {
1188 dstep = (double)sn;
1189 } else {
1190 dstep = step.toDouble();
1192 } else {
1193 dstep = step.toDouble();
1195 /* We only want positive step values. */
1196 if (dstep < 0.0) dstep *= -1;
1197 if (low.isString() && high.isString()) {
1198 String slow = low.toString();
1199 String shigh = high.toString();
1200 if (slow.size() >= 1 && shigh.size() >=1) {
1201 int64_t n1, n2;
1202 double d1, d2;
1203 DataType type1 = slow.get()->isNumericWithVal(n1, d1, 0);
1204 DataType type2 = shigh.get()->isNumericWithVal(n2, d2, 0);
1205 if (type1 == KindOfDouble || type2 == KindOfDouble || is_step_double) {
1206 if (type1 != KindOfDouble) d1 = slow.toDouble();
1207 if (type2 != KindOfDouble) d2 = shigh.toDouble();
1208 return ArrayUtil::Range(d1, d2, dstep);
1211 int64_t lstep = (int64_t) dstep;
1212 if (type1 == KindOfInt64 || type2 == KindOfInt64) {
1213 if (type1 != KindOfInt64) n1 = slow.toInt64();
1214 if (type2 != KindOfInt64) n2 = shigh.toInt64();
1215 return ArrayUtil::Range((double)n1, (double)n2, lstep);
1218 return ArrayUtil::Range((unsigned char)slow.charAt(0),
1219 (unsigned char)shigh.charAt(0), lstep);
1223 if (low.is(KindOfDouble) || high.is(KindOfDouble) || is_step_double) {
1224 return ArrayUtil::Range(low.toDouble(), high.toDouble(), dstep);
1227 int64_t lstep = (int64_t) dstep;
1228 return ArrayUtil::Range(low.toDouble(), high.toDouble(), lstep);
1230 ///////////////////////////////////////////////////////////////////////////////
1231 // diff/intersect helpers
1233 static int cmp_func(const Variant& v1, const Variant& v2, const void *data) {
1234 Variant *callback = (Variant *)data;
1235 return vm_call_user_func(*callback, make_packed_array(v1, v2)).toInt32();
1238 #define COMMA ,
1239 #define diff_intersect_body(type,intersect_params,user_setup) \
1240 getCheckedArray(array1); \
1241 if (!arr_array1.size()) return arr_array1; \
1242 user_setup \
1243 Array ret = arr_array1.type(array2, intersect_params); \
1244 if (ret.size()) { \
1245 for (ArrayIter iter(_argv); iter; ++iter) { \
1246 ret = ret.type(iter.second(), intersect_params); \
1247 if (!ret.size()) break; \
1250 return ret;
1252 ///////////////////////////////////////////////////////////////////////////////
1253 // diff functions
1255 static inline void addToSetHelper(c_Set* st, const Cell& c, TypedValue* strTv,
1256 bool convertIntLikeStrs) {
1257 if (c.m_type == KindOfInt64) {
1258 st->add(c.m_data.num);
1259 } else {
1260 StringData* s;
1261 if (LIKELY(IS_STRING_TYPE(c.m_type))) {
1262 s = c.m_data.pstr;
1263 } else {
1264 s = tvCastToString(&c);
1265 decRefStr(strTv->m_data.pstr);
1266 strTv->m_data.pstr = s;
1268 int64_t n;
1269 if (convertIntLikeStrs && s->isStrictlyInteger(n)) {
1270 st->add(n);
1271 } else {
1272 st->add(s);
1277 static inline bool checkSetHelper(c_Set* st, const Cell& c, TypedValue* strTv,
1278 bool convertIntLikeStrs) {
1279 if (c.m_type == KindOfInt64) {
1280 return st->contains(c.m_data.num);
1282 StringData* s;
1283 if (LIKELY(IS_STRING_TYPE(c.m_type))) {
1284 s = c.m_data.pstr;
1285 } else {
1286 s = tvCastToString(&c);
1287 decRefStr(strTv->m_data.pstr);
1288 strTv->m_data.pstr = s;
1290 int64_t n;
1291 if (convertIntLikeStrs && s->isStrictlyInteger(n)) {
1292 return st->contains(n);
1294 return st->contains(s);
1297 static void containerValuesToSetHelper(c_Set* st, const Variant& container) {
1298 Variant strHolder(empty_string.get());
1299 TypedValue* strTv = strHolder.asTypedValue();
1300 for (ArrayIter iter(container); iter; ++iter) {
1301 const auto& c = *const_cast<TypedValue*>(iter.secondRefPlus().asCell());
1302 addToSetHelper(st, c, strTv, true);
1306 static void containerKeysToSetHelper(c_Set* st, const Variant& container) {
1307 Variant strHolder(empty_string.get());
1308 TypedValue* strTv = strHolder.asTypedValue();
1309 bool isKey = container.asCell()->m_type == KindOfArray;
1310 for (ArrayIter iter(container); iter; ++iter) {
1311 auto key = iter.first();
1312 const auto& c = *const_cast<TypedValue*>(key.asCell());
1313 addToSetHelper(st, c, strTv, !isKey);
1317 #define ARRAY_DIFF_PRELUDE() \
1318 /* Check to make sure all inputs are containers */ \
1319 const auto& c1 = *container1.asCell(); \
1320 const auto& c2 = *container2.asCell(); \
1321 if (UNLIKELY(!isContainer(c1) || !isContainer(c2))) { \
1322 raise_warning("%s() expects parameter %d to be an array or collection", \
1323 __FUNCTION__+2, /* remove the "f_" prefix */ \
1324 isContainer(c1) ? 2 : 1); \
1325 return uninit_null(); \
1327 bool moreThanTwo = (_argc > 2); \
1328 size_t largestSize = getContainerSize(c2); \
1329 if (UNLIKELY(moreThanTwo)) { \
1330 int pos = 3; \
1331 for (ArrayIter argvIter(_argv); argvIter; ++argvIter, ++pos) { \
1332 const auto& c = *argvIter.secondRef().asCell(); \
1333 if (!isContainer(c)) { \
1334 raise_warning("%s() expects parameter %d to be an array or collection",\
1335 __FUNCTION__+2, /* remove the "f_" prefix */ \
1336 pos); \
1337 return uninit_null(); \
1339 size_t sz = getContainerSize(c); \
1340 if (sz > largestSize) { \
1341 largestSize = sz; \
1345 /* If container1 is empty, we can stop here and return the empty array */ \
1346 if (!getContainerSize(c1)) return empty_array; \
1347 /* If all of the containers (except container1) are empty, we can just \
1348 return container1 (converting it to an array if needed) */ \
1349 if (!largestSize) { \
1350 if (c1.m_type == KindOfArray) { \
1351 return container1; \
1352 } else { \
1353 return container1.toArray(); \
1356 Array ret = Array::Create();
1358 Variant f_array_diff(int _argc, const Variant& container1, const Variant& container2,
1359 const Array& _argv /* = null_array */) {
1360 ARRAY_DIFF_PRELUDE()
1361 // Put all of the values from all the containers (except container1 into a
1362 // Set. All types aside from integer and string will be cast to string, and
1363 // we also convert int-like strings to integers.
1364 c_Set* st;
1365 Object setObj = st = NEWOBJ(c_Set)();
1366 st->reserve(largestSize);
1367 containerValuesToSetHelper(st, container2);
1368 if (UNLIKELY(moreThanTwo)) {
1369 for (ArrayIter argvIter(_argv); argvIter; ++argvIter) {
1370 const auto& container = argvIter.secondRef();
1371 containerValuesToSetHelper(st, container);
1374 // Loop over container1, only copying over key/value pairs where the value
1375 // is not present in the Set. When checking if a value is present in the
1376 // Set, any value that is not an integer or string is cast to a string, and
1377 // we convert int-like strings to integers.
1378 Variant strHolder(empty_string.get());
1379 TypedValue* strTv = strHolder.asTypedValue();
1380 bool isKey = c1.m_type == KindOfArray;
1381 for (ArrayIter iter(container1); iter; ++iter) {
1382 const auto& val = iter.secondRefPlus();
1383 const auto& c = *val.asCell();
1384 if (checkSetHelper(st, c, strTv, true)) continue;
1385 ret.setWithRef(iter.first(), val, isKey);
1387 return ret;
1390 Variant f_array_diff_key(int _argc, const Variant& container1, const Variant& container2,
1391 const Array& _argv /* = null_array */) {
1392 ARRAY_DIFF_PRELUDE()
1393 // If we're only dealing with two containers and if they are both arrays,
1394 // we can avoid creating an intermediate Set
1395 if (!moreThanTwo && c1.m_type == KindOfArray && c2.m_type == KindOfArray) {
1396 auto ad2 = c2.m_data.parr;
1397 for (ArrayIter iter(container1); iter; ++iter) {
1398 auto key = iter.first();
1399 const auto& c = *key.asCell();
1400 if (c.m_type == KindOfInt64) {
1401 if (ad2->exists(c.m_data.num)) continue;
1402 } else {
1403 assert(IS_STRING_TYPE(c.m_type));
1404 if (ad2->exists(c.m_data.pstr)) continue;
1406 ret.setWithRef(key, iter.secondRefPlus(), true);
1408 return ret;
1410 // Put all of the keys from all the containers (except container1) into a
1411 // Set. All types aside from integer and string will be cast to string, and
1412 // we also convert int-like strings to integers.
1413 c_Set* st;
1414 Object setObj = st = NEWOBJ(c_Set)();
1415 st->reserve(largestSize);
1416 containerKeysToSetHelper(st, container2);
1417 if (UNLIKELY(moreThanTwo)) {
1418 for (ArrayIter argvIter(_argv); argvIter; ++argvIter) {
1419 const auto& container = argvIter.secondRef();
1420 containerKeysToSetHelper(st, container);
1423 // Loop over container1, only copying over key/value pairs where the key is
1424 // not present in the Set. When checking if a key is present in the Set, any
1425 // key that is not an integer or string is cast to a string, and we convert
1426 // int-like strings to integers.
1427 Variant strHolder(empty_string.get());
1428 TypedValue* strTv = strHolder.asTypedValue();
1429 bool isKey = c1.m_type == KindOfArray;
1430 for (ArrayIter iter(container1); iter; ++iter) {
1431 auto key = iter.first();
1432 const auto& c = *key.asCell();
1433 if (checkSetHelper(st, c, strTv, !isKey)) continue;
1434 ret.setWithRef(key, iter.secondRefPlus(), isKey);
1436 return ret;
1439 #undef ARRAY_DIFF_PRELUDE
1441 Variant f_array_udiff(int _argc, const Variant& array1, const Variant& array2,
1442 const Variant& data_compare_func,
1443 const Array& _argv /* = null_array */) {
1444 diff_intersect_body(diff, false COMMA true COMMA NULL COMMA NULL
1445 COMMA cmp_func COMMA &func,
1446 Variant func = data_compare_func;
1447 Array extra = _argv;
1448 if (!extra.empty()) {
1449 extra.prepend(func);
1450 func = extra.pop();
1454 Variant f_array_diff_assoc(int _argc, const Variant& array1, const Variant& array2,
1455 const Array& _argv /* = null_array */) {
1456 diff_intersect_body(diff, true COMMA true,);
1459 Variant f_array_diff_uassoc(int _argc, const Variant& array1, const Variant& array2,
1460 const Variant& key_compare_func,
1461 const Array& _argv /* = null_array */) {
1462 diff_intersect_body(diff, true COMMA true COMMA cmp_func COMMA &func,
1463 Variant func = key_compare_func;
1464 Array extra = _argv;
1465 if (!extra.empty()) {
1466 extra.prepend(func);
1467 func = extra.pop();
1471 Variant f_array_udiff_assoc(int _argc, const Variant& array1, const Variant& array2,
1472 const Variant& data_compare_func,
1473 const Array& _argv /* = null_array */) {
1474 diff_intersect_body(diff, true COMMA true COMMA NULL COMMA NULL
1475 COMMA cmp_func COMMA &func,
1476 Variant func = data_compare_func;
1477 Array extra = _argv;
1478 if (!extra.empty()) {
1479 extra.prepend(func);
1480 func = extra.pop();
1484 Variant f_array_udiff_uassoc(int _argc, const Variant& array1, const Variant& array2,
1485 const Variant& data_compare_func,
1486 const Variant& key_compare_func,
1487 const Array& _argv /* = null_array */) {
1488 diff_intersect_body(diff, true COMMA true COMMA cmp_func COMMA &key_func
1489 COMMA cmp_func COMMA &data_func,
1490 Variant data_func = data_compare_func;
1491 Variant key_func = key_compare_func;
1492 Array extra = _argv;
1493 if (!extra.empty()) {
1494 extra.prepend(key_func);
1495 extra.prepend(data_func);
1496 key_func = extra.pop();
1497 data_func = extra.pop();
1501 Variant f_array_diff_ukey(int _argc, const Variant& array1, const Variant& array2,
1502 const Variant& key_compare_func,
1503 const Array& _argv /* = null_array */) {
1504 diff_intersect_body(diff, true COMMA false COMMA cmp_func COMMA &func,
1505 Variant func = key_compare_func;
1506 Array extra = _argv;
1507 if (!extra.empty()) {
1508 extra.prepend(func);
1509 func = extra.pop();
1513 ///////////////////////////////////////////////////////////////////////////////
1514 // intersect functions
1516 static inline TypedValue* makeContainerListHelper(const Variant& a,
1517 const Array& argv,
1518 int count,
1519 int smallestPos) {
1520 assert(count == argv.size() + 1);
1521 assert(0 <= smallestPos);
1522 assert(smallestPos < count);
1523 // Allocate a TypedValue array and copy 'a' and the contents of 'argv'
1524 TypedValue* containers =
1525 (TypedValue*)smart_malloc(count * sizeof(TypedValue));
1526 tvCopy(*a.asCell(), containers[0]);
1527 int pos = 1;
1528 for (ArrayIter argvIter(argv); argvIter; ++argvIter, ++pos) {
1529 const auto& c = *argvIter.secondRef().asCell();
1530 tvCopy(c, containers[pos]);
1532 // Perform a swap so that the smallest container occurs at the first
1533 // position in the TypedValue array; this helps improve the performance
1534 // of containerValuesIntersectHelper()
1535 if (smallestPos != 0) {
1536 TypedValue tmp;
1537 tvCopy(containers[0], tmp);
1538 tvCopy(containers[smallestPos], containers[0]);
1539 tvCopy(tmp, containers[smallestPos]);
1541 return containers;
1544 static inline void addToIntersectMapHelper(c_Map* mp,
1545 const Cell& c,
1546 TypedValue* intOneTv,
1547 TypedValue* strTv,
1548 bool convertIntLikeStrs) {
1549 if (c.m_type == KindOfInt64) {
1550 mp->set(c.m_data.num, intOneTv);
1551 } else {
1552 StringData* s;
1553 if (LIKELY(IS_STRING_TYPE(c.m_type))) {
1554 s = c.m_data.pstr;
1555 } else {
1556 s = tvCastToString(&c);
1557 decRefStr(strTv->m_data.pstr);
1558 strTv->m_data.pstr = s;
1560 int64_t n;
1561 if (convertIntLikeStrs && s->isStrictlyInteger(n)) {
1562 mp->set(n, intOneTv);
1563 } else {
1564 mp->set(s, intOneTv);
1569 static inline void updateIntersectMapHelper(c_Map* mp,
1570 const Cell& c,
1571 int pos,
1572 TypedValue* strTv,
1573 bool convertIntLikeStrs) {
1574 if (c.m_type == KindOfInt64) {
1575 auto val = mp->get(c.m_data.num);
1576 if (val && val->m_data.num == pos) {
1577 assert(val->m_type == KindOfInt64);
1578 ++val->m_data.num;
1580 } else {
1581 StringData* s;
1582 if (LIKELY(IS_STRING_TYPE(c.m_type))) {
1583 s = c.m_data.pstr;
1584 } else {
1585 s = tvCastToString(&c);
1586 decRefStr(strTv->m_data.pstr);
1587 strTv->m_data.pstr = s;
1589 int64_t n;
1590 if (convertIntLikeStrs && s->isStrictlyInteger(n)) {
1591 auto val = mp->get(n);
1592 if (val && val->m_data.num == pos) {
1593 assert(val->m_type == KindOfInt64);
1594 ++val->m_data.num;
1596 } else {
1597 auto val = mp->get(s);
1598 if (val && val->m_data.num == pos) {
1599 assert(val->m_type == KindOfInt64);
1600 ++val->m_data.num;
1606 static void containerValuesIntersectHelper(c_Set* st,
1607 TypedValue* containers,
1608 int count) {
1609 assert(count >= 2);
1610 c_Map* mp;
1611 Object mapObj = mp = NEWOBJ(c_Map)();
1612 Variant strHolder(empty_string.get());
1613 TypedValue* strTv = strHolder.asTypedValue();
1614 TypedValue intOneTv = make_tv<KindOfInt64>(1);
1615 for (ArrayIter iter(tvAsCVarRef(&containers[0])); iter; ++iter) {
1616 const auto& c = *const_cast<TypedValue*>(iter.secondRefPlus().asCell());
1617 // For each value v in containers[0], we add the key/value pair (v, 1)
1618 // to the map. If a value (after various conversions) occurs more than
1619 // once in the container, we'll simply overwrite the old entry and that's
1620 // fine.
1621 addToIntersectMapHelper(mp, c, &intOneTv, strTv, true);
1623 for (int pos = 1; pos < count; ++pos) {
1624 for (ArrayIter iter(tvAsCVarRef(&containers[pos])); iter; ++iter) {
1625 const auto& c = *const_cast<TypedValue*>(iter.secondRefPlus().asCell());
1626 // We check if the value is present as a key in the map. If an entry
1627 // exists and its value equals pos, we increment it, otherwise we do
1628 // nothing. This is essential so that we don't accidentally double-count
1629 // a key (after various conversions) that occurs in the container more
1630 // than once.
1631 updateIntersectMapHelper(mp, c, pos, strTv, true);
1634 for (ArrayIter iter(mapObj); iter; ++iter) {
1635 // For each key in the map, we copy the key to the set if the
1636 // corresponding value is equal to pos exactly (which means it
1637 // was present in all of the containers).
1638 const auto& val = *iter.secondRefPlus().asCell();
1639 assert(val.m_type == KindOfInt64);
1640 if (val.m_data.num == count) {
1641 st->add(iter.first().asCell());
1646 static void containerKeysIntersectHelper(c_Set* st,
1647 TypedValue* containers,
1648 int count) {
1649 assert(count >= 2);
1650 c_Map* mp;
1651 Object mapObj = mp = NEWOBJ(c_Map)();
1652 Variant strHolder(empty_string.get());
1653 TypedValue* strTv = strHolder.asTypedValue();
1654 TypedValue intOneTv = make_tv<KindOfInt64>(1);
1655 bool isKey = containers[0].m_type == KindOfArray;
1656 for (ArrayIter iter(tvAsCVarRef(&containers[0])); iter; ++iter) {
1657 auto key = iter.first();
1658 const auto& c = *key.asCell();
1659 // For each key k in containers[0], we add the key/value pair (k, 1)
1660 // to the map. If a key (after various conversions) occurs more than
1661 // once in the container, we'll simply overwrite the old entry and
1662 // that's fine.
1663 addToIntersectMapHelper(mp, c, &intOneTv, strTv, !isKey);
1665 for (int pos = 1; pos < count; ++pos) {
1666 isKey = containers[pos].m_type == KindOfArray;
1667 for (ArrayIter iter(tvAsCVarRef(&containers[pos])); iter; ++iter) {
1668 auto key = iter.first();
1669 const auto& c = *key.asCell();
1670 updateIntersectMapHelper(mp, c, pos, strTv, !isKey);
1673 for (ArrayIter iter(mapObj); iter; ++iter) {
1674 // For each key in the map, we copy the key to the set if the
1675 // corresponding value is equal to pos exactly (which means it
1676 // was present in all of the containers).
1677 const auto& val = *iter.secondRefPlus().asCell();
1678 assert(val.m_type == KindOfInt64);
1679 if (val.m_data.num == count) {
1680 st->add(iter.first().asCell());
1685 #define ARRAY_INTERSECT_PRELUDE() \
1686 /* Check to make sure all inputs are containers */ \
1687 const auto& c1 = *container1.asCell(); \
1688 const auto& c2 = *container2.asCell(); \
1689 if (!isContainer(c1) || !isContainer(c2)) { \
1690 raise_warning("%s() expects parameter %d to be an array or collection", \
1691 __FUNCTION__+2, /* remove the "f_" prefix */ \
1692 isContainer(c1) ? 2 : 1); \
1693 return uninit_null(); \
1695 bool moreThanTwo = (_argc > 2); \
1696 /* Keep track of which input container was the smallest (excluding \
1697 container1) */ \
1698 int smallestPos = 0; \
1699 size_t smallestSize = getContainerSize(c2); \
1700 if (UNLIKELY(moreThanTwo)) { \
1701 int pos = 1; \
1702 for (ArrayIter argvIter(_argv); argvIter; ++argvIter, ++pos) { \
1703 const auto& c = *argvIter.secondRef().asCell(); \
1704 if (!isContainer(c)) { \
1705 raise_warning("%s() expects parameter %d to be an array or collection",\
1706 __FUNCTION__+2, /* remove the "f_" prefix */ \
1707 pos+2); \
1708 return uninit_null(); \
1710 size_t sz = getContainerSize(c); \
1711 if (sz < smallestSize) { \
1712 smallestSize = sz; \
1713 smallestPos = pos; \
1717 /* If any of the containers were empty, we can stop here and return the \
1718 empty array */ \
1719 if (!getContainerSize(c1) || !smallestSize) return empty_array; \
1720 Array ret = Array::Create();
1722 Variant f_array_intersect(int _argc, const Variant& container1, const Variant& container2,
1723 const Array& _argv /* = null_array */) {
1724 ARRAY_INTERSECT_PRELUDE()
1725 // Build up a Set containing the values that are present in all the
1726 // containers (except container1)
1727 c_Set* st;
1728 Object setObj = st = NEWOBJ(c_Set)();
1729 if (LIKELY(!moreThanTwo)) {
1730 // There is only one container (not counting container1) so we can
1731 // just call containerValuesToSetHelper() to build the Set.
1732 containerValuesToSetHelper(st, container2);
1733 } else {
1734 // We're dealing with three or more containers. Copy all of the containers
1735 // (except the first) into a TypedValue array.
1736 int count = _argv.size() + 1;
1737 TypedValue* containers =
1738 makeContainerListHelper(container2, _argv, count, smallestPos);
1739 SCOPE_EXIT { smart_free(containers); };
1740 // Build a Set of the values that were present in all of the containers
1741 containerValuesIntersectHelper(st, containers, count);
1743 // Loop over container1, only copying over key/value pairs where the value
1744 // is present in the Set. When checking if a value is present in the Set,
1745 // any value that is not an integer or string is cast to a string, and we
1746 // convert int-like strings to integers.
1747 Variant strHolder(empty_string.get());
1748 TypedValue* strTv = strHolder.asTypedValue();
1749 bool isKey = c1.m_type == KindOfArray;
1750 for (ArrayIter iter(container1); iter; ++iter) {
1751 const auto& val = iter.secondRefPlus();
1752 const auto& c = *val.asCell();
1753 if (!checkSetHelper(st, c, strTv, true)) continue;
1754 ret.setWithRef(iter.first(), val, isKey);
1756 return ret;
1759 Variant f_array_intersect_key(int _argc, const Variant& container1, const Variant& container2,
1760 const Array& _argv /* = null_array */) {
1761 ARRAY_INTERSECT_PRELUDE()
1762 // If we're only dealing with two containers and if they are both arrays,
1763 // we can avoid creating an intermediate Set
1764 if (!moreThanTwo && c1.m_type == KindOfArray && c2.m_type == KindOfArray) {
1765 auto ad2 = c2.m_data.parr;
1766 for (ArrayIter iter(container1); iter; ++iter) {
1767 auto key = iter.first();
1768 const auto& c = *key.asCell();
1769 if (c.m_type == KindOfInt64) {
1770 if (!ad2->exists(c.m_data.num)) continue;
1771 } else {
1772 assert(IS_STRING_TYPE(c.m_type));
1773 if (!ad2->exists(c.m_data.pstr)) continue;
1775 ret.setWithRef(key, iter.secondRefPlus(), true);
1777 return ret;
1779 // Build up a Set containing the keys that are present in all the containers
1780 // (except container1)
1781 c_Set* st;
1782 Object setObj = st = NEWOBJ(c_Set)();
1783 if (LIKELY(!moreThanTwo)) {
1784 // There is only one container (not counting container1) so we can just
1785 // call containerKeysToSetHelper() to build the Set.
1786 containerKeysToSetHelper(st, container2);
1787 } else {
1788 // We're dealing with three or more containers. Copy all of the containers
1789 // (except the first) into a TypedValue array.
1790 int count = _argv.size() + 1;
1791 TypedValue* containers =
1792 makeContainerListHelper(container2, _argv, count, smallestPos);
1793 SCOPE_EXIT { smart_free(containers); };
1794 // Build a Set of the keys that were present in all of the containers
1795 containerKeysIntersectHelper(st, containers, count);
1797 // Loop over container1, only copying over key/value pairs where the key
1798 // is present in the Set. When checking if a key is present in the Set,
1799 // any value that is not an integer or string is cast to a string, and we
1800 // convert int-like strings to integers.
1801 Variant strHolder(empty_string.get());
1802 TypedValue* strTv = strHolder.asTypedValue();
1803 bool isKey = c1.m_type == KindOfArray;
1804 for (ArrayIter iter(container1); iter; ++iter) {
1805 auto key = iter.first();
1806 const auto& c = *key.asCell();
1807 if (!checkSetHelper(st, c, strTv, !isKey)) continue;
1808 ret.setWithRef(key, iter.secondRefPlus(), isKey);
1810 return ret;
1813 #undef ARRAY_INTERSECT_PRELUDE
1815 Variant f_array_uintersect(int _argc, const Variant& array1, const Variant& array2,
1816 const Variant& data_compare_func,
1817 const Array& _argv /* = null_array */) {
1818 diff_intersect_body(intersect, false COMMA true COMMA NULL COMMA NULL
1819 COMMA cmp_func COMMA &func,
1820 Variant func = data_compare_func;
1821 Array extra = _argv;
1822 if (!extra.empty()) {
1823 extra.prepend(func);
1824 func = extra.pop();
1828 Variant f_array_intersect_assoc(int _argc, const Variant& array1, const Variant& array2,
1829 const Array& _argv /* = null_array */) {
1830 diff_intersect_body(intersect, true COMMA true,);
1833 Variant f_array_intersect_uassoc(int _argc, const Variant& array1, const Variant& array2,
1834 const Variant& key_compare_func,
1835 const Array& _argv /* = null_array */) {
1836 diff_intersect_body(intersect, true COMMA true COMMA cmp_func COMMA &func,
1837 Variant func = key_compare_func;
1838 Array extra = _argv;
1839 if (!extra.empty()) {
1840 extra.prepend(func);
1841 func = extra.pop();
1845 Variant f_array_uintersect_assoc(int _argc, const Variant& array1, const Variant& array2,
1846 const Variant& data_compare_func,
1847 const Array& _argv /* = null_array */) {
1848 diff_intersect_body(intersect, true COMMA true COMMA NULL COMMA NULL
1849 COMMA cmp_func COMMA &func,
1850 Variant func = data_compare_func;
1851 Array extra = _argv;
1852 if (!extra.empty()) {
1853 extra.prepend(func);
1854 func = extra.pop();
1858 Variant f_array_uintersect_uassoc(int _argc, const Variant& array1, const Variant& array2,
1859 const Variant& data_compare_func,
1860 const Variant& key_compare_func,
1861 const Array& _argv /* = null_array */) {
1862 diff_intersect_body(intersect, true COMMA true COMMA cmp_func COMMA &key_func
1863 COMMA cmp_func COMMA &data_func,
1864 Variant data_func = data_compare_func;
1865 Variant key_func = key_compare_func;
1866 Array extra = _argv;
1867 if (!extra.empty()) {
1868 extra.prepend(key_func);
1869 extra.prepend(data_func);
1870 key_func = extra.pop();
1871 data_func = extra.pop();
1875 Variant f_array_intersect_ukey(int _argc, const Variant& array1, const Variant& array2,
1876 const Variant& key_compare_func, const Array& _argv /* = null_array */) {
1877 diff_intersect_body(intersect, true COMMA false COMMA cmp_func COMMA &func,
1878 Variant func = key_compare_func;
1879 Array extra = _argv;
1880 if (!extra.empty()) {
1881 extra.prepend(func);
1882 func = extra.pop();
1886 ///////////////////////////////////////////////////////////////////////////////
1887 // sorting functions
1889 struct Collator final : RequestEventHandler {
1890 String getLocale() {
1891 return m_locale;
1893 Intl::IntlError &getErrorRef() {
1894 return m_errcode;
1896 bool setLocale(const String& locale) {
1897 if (m_locale.same(locale)) {
1898 return true;
1900 if (m_ucoll) {
1901 ucol_close(m_ucoll);
1902 m_ucoll = NULL;
1904 m_errcode.clearError();
1905 UErrorCode error = U_ZERO_ERROR;
1906 m_ucoll = ucol_open(locale.data(), &error);
1907 if (m_ucoll == NULL) {
1908 raise_warning("failed to load %s locale from icu data", locale.data());
1909 return false;
1911 if (U_FAILURE(error)) {
1912 m_errcode.setError(error);
1913 ucol_close(m_ucoll);
1914 m_ucoll = NULL;
1915 return false;
1917 m_locale = locale;
1918 return true;
1921 UCollator *getCollator() {
1922 return m_ucoll;
1925 bool setAttribute(int64_t attr, int64_t val) {
1926 if (!m_ucoll) {
1927 Logger::Verbose("m_ucoll is NULL");
1928 return false;
1930 m_errcode.clearError();
1931 UErrorCode error = U_ZERO_ERROR;
1932 ucol_setAttribute(m_ucoll, (UColAttribute)attr,
1933 (UColAttributeValue)val, &error);
1934 if (U_FAILURE(error)) {
1935 m_errcode.setError(error);
1936 Logger::Verbose("Error setting attribute value");
1937 return false;
1939 return true;
1942 bool setStrength(int64_t strength) {
1943 if (!m_ucoll) {
1944 Logger::Verbose("m_ucoll is NULL");
1945 return false;
1947 ucol_setStrength(m_ucoll, (UCollationStrength)strength);
1948 return true;
1951 Variant getErrorCode() {
1952 if (!m_ucoll) {
1953 Logger::Verbose("m_ucoll is NULL");
1954 return false;
1956 return m_errcode.getErrorCode();
1959 void requestInit() override {
1960 m_locale = String(uloc_getDefault(), CopyString);
1961 m_errcode.clearError();
1962 UErrorCode error = U_ZERO_ERROR;
1963 m_ucoll = ucol_open(m_locale.data(), &error);
1964 if (U_FAILURE(error)) {
1965 m_errcode.setError(error);
1967 assert(m_ucoll);
1969 void requestShutdown() override {
1970 m_locale.reset();
1971 m_errcode.clearError(false);
1972 if (m_ucoll) {
1973 ucol_close(m_ucoll);
1974 m_ucoll = NULL;
1978 private:
1979 String m_locale;
1980 UCollator *m_ucoll;
1981 Intl::IntlError m_errcode;
1983 IMPLEMENT_STATIC_REQUEST_LOCAL(Collator, s_collator);
1985 static Array::PFUNC_CMP get_cmp_func(int sort_flags, bool ascending) {
1986 switch (sort_flags) {
1987 case SORT_NATURAL:
1988 return Array::SortNatural;
1989 case SORT_NATURAL_CASE:
1990 return Array::SortNaturalCase;
1991 case SORT_NUMERIC:
1992 return ascending ?
1993 Array::SortNumericAscending : Array::SortNumericDescending;
1994 case SORT_STRING:
1995 return ascending ?
1996 Array::SortStringAscending : Array::SortStringDescending;
1997 case SORT_STRING_CASE:
1998 return ascending ?
1999 Array::SortStringAscendingCase : Array::SortStringDescendingCase;
2000 case SORT_LOCALE_STRING:
2001 return ascending ?
2002 Array::SortLocaleStringAscending : Array::SortLocaleStringDescending;
2003 case SORT_REGULAR:
2004 default:
2005 return ascending ?
2006 Array::SortRegularAscending : Array::SortRegularDescending;
2010 class ArraySortTmp {
2011 public:
2012 explicit ArraySortTmp(Array& arr) : m_arr(arr) {
2013 m_ad = arr.get()->escalateForSort();
2014 m_ad->incRefCount();
2016 ~ArraySortTmp() {
2017 if (m_ad != m_arr.get()) {
2018 m_arr = m_ad;
2019 m_ad->decRefCount();
2022 ArrayData* operator->() { return m_ad; }
2023 private:
2024 Array& m_arr;
2025 ArrayData* m_ad;
2028 static bool
2029 php_sort(VRefParam container, int sort_flags,
2030 bool ascending, bool use_collator) {
2031 if (container.isArray()) {
2032 Array& arr_array = container.wrapped().toArrRef();
2033 if (use_collator && sort_flags != SORT_LOCALE_STRING) {
2034 UCollator *coll = s_collator->getCollator();
2035 if (coll) {
2036 Intl::IntlError &errcode = s_collator->getErrorRef();
2037 return collator_sort(container, sort_flags, ascending,
2038 coll, &errcode);
2041 ArraySortTmp ast(arr_array);
2042 ast->sort(sort_flags, ascending);
2043 return true;
2045 if (container.isObject()) {
2046 ObjectData* obj = container.getObjectData();
2047 if (obj->getCollectionType() == Collection::VectorType) {
2048 c_Vector* vec = static_cast<c_Vector*>(obj);
2049 vec->sort(sort_flags, ascending);
2050 return true;
2052 // other collections are not supported:
2053 // - mapping types require associative sort
2054 // - frozen types are not to be modified
2055 // - set types are not ordered
2057 throw_expected_array_or_collection_exception();
2058 return false;
2061 static bool
2062 php_asort(VRefParam container, int sort_flags,
2063 bool ascending, bool use_collator) {
2064 if (container.isArray()) {
2065 Array& arr_array = container.wrapped().toArrRef();
2066 if (use_collator && sort_flags != SORT_LOCALE_STRING) {
2067 UCollator *coll = s_collator->getCollator();
2068 if (coll) {
2069 Intl::IntlError &errcode = s_collator->getErrorRef();
2070 return collator_asort(container, sort_flags, ascending,
2071 coll, &errcode);
2074 ArraySortTmp ast(arr_array);
2075 ast->asort(sort_flags, ascending);
2076 return true;
2078 if (container.isObject()) {
2079 ObjectData* obj = container.getObjectData();
2080 if (obj->getCollectionType() == Collection::MapType) {
2081 BaseMap* mp = static_cast<BaseMap*>(obj);
2082 mp->asort(sort_flags, ascending);
2083 return true;
2086 throw_expected_array_or_collection_exception();
2087 return false;
2090 static bool
2091 php_ksort(VRefParam container, int sort_flags, bool ascending) {
2092 if (container.isArray()) {
2093 Array& arr_array = container.wrapped().toArrRef();
2094 ArraySortTmp ast(arr_array);
2095 ast->ksort(sort_flags, ascending);
2096 return true;
2098 if (container.isObject()) {
2099 ObjectData* obj = container.getObjectData();
2100 if (obj->getCollectionType() == Collection::MapType) {
2101 BaseMap* mp = static_cast<BaseMap*>(obj);
2102 mp->ksort(sort_flags, ascending);
2103 return true;
2106 throw_expected_array_or_collection_exception();
2107 return false;
2110 bool f_sort(VRefParam array, int sort_flags /* = 0 */,
2111 bool use_collator /* = false */) {
2112 return php_sort(array, sort_flags, true, use_collator);
2115 bool f_rsort(VRefParam array, int sort_flags /* = 0 */,
2116 bool use_collator /* = false */) {
2117 return php_sort(array, sort_flags, false, use_collator);
2120 bool f_asort(VRefParam array, int sort_flags /* = 0 */,
2121 bool use_collator /* = false */) {
2122 return php_asort(array, sort_flags, true, use_collator);
2125 bool f_arsort(VRefParam array, int sort_flags /* = 0 */,
2126 bool use_collator /* = false */) {
2127 return php_asort(array, sort_flags, false, use_collator);
2130 bool f_ksort(VRefParam array, int sort_flags /* = 0 */) {
2131 return php_ksort(array, sort_flags, true);
2134 bool f_krsort(VRefParam array, int sort_flags /* = 0 */) {
2135 return php_ksort(array, sort_flags, false);
2138 // NOTE: PHP's implementation of natsort and natcasesort accepts ArrayAccess
2139 // objects as well, which does not make much sense, and which is not supported
2140 // here.
2142 Variant f_natsort(VRefParam array) {
2143 return php_asort(array, SORT_NATURAL, true, false);
2146 Variant f_natcasesort(VRefParam array) {
2147 return php_asort(array, SORT_NATURAL_CASE, true, false);
2150 bool f_usort(VRefParam container, const Variant& cmp_function) {
2151 if (container.isArray()) {
2152 Array& arr_array = container.wrapped().toArrRef();
2153 ArraySortTmp ast(arr_array);
2154 return ast->usort(cmp_function);
2156 if (container.isObject()) {
2157 ObjectData* obj = container.getObjectData();
2158 if (obj->getCollectionType() == Collection::VectorType) {
2159 c_Vector* vec = static_cast<c_Vector*>(obj);
2160 return vec->usort(cmp_function);
2162 // other collections are not supported:
2163 // - mapping types require associative sort
2164 // - frozen types are not to be modified
2165 // - set types are not ordered
2167 throw_expected_array_or_collection_exception();
2168 return false;
2171 bool f_uasort(VRefParam container, const Variant& cmp_function) {
2172 if (container.isArray()) {
2173 Array& arr_array = container.wrapped().toArrRef();
2174 ArraySortTmp ast(arr_array);
2175 return ast->uasort(cmp_function);
2177 if (container.isObject()) {
2178 ObjectData* obj = container.getObjectData();
2179 if (obj->getCollectionType() == Collection::MapType) {
2180 BaseMap* mp = static_cast<BaseMap*>(obj);
2181 return mp->uasort(cmp_function);
2184 throw_expected_array_or_collection_exception();
2185 return false;
2188 bool f_uksort(VRefParam container, const Variant& cmp_function) {
2189 if (container.isArray()) {
2190 Array& arr_array = container.wrapped().toArrRef();
2191 ArraySortTmp ast(arr_array);
2192 return ast->uksort(cmp_function);
2194 if (container.isObject()) {
2195 ObjectData* obj = container.getObjectData();
2196 if (obj->getCollectionType() == Collection::MapType) {
2197 BaseMap* mp = static_cast<BaseMap*>(obj);
2198 return mp->uksort(cmp_function);
2201 throw_expected_array_or_collection_exception();
2202 return false;
2205 bool f_array_multisort(int _argc, VRefParam ar1,
2206 const Array& _argv /* = null_array */) {
2207 getCheckedArrayRet(ar1, false);
2208 std::vector<Array::SortData> data;
2209 std::vector<Array> arrays;
2210 arrays.reserve(1 + _argv.size()); // so no resize would happen
2212 Array::SortData sd;
2213 sd.original = &ar1;
2214 arrays.push_back(arr_ar1);
2215 sd.array = &arrays.back();
2216 sd.by_key = false;
2218 int sort_flags = SORT_REGULAR;
2219 bool ascending = true;
2220 for (int i = 0; i < _argv.size(); i++) {
2221 Variant *v = &((Array&)_argv).lvalAt(i);
2222 auto const cell = v->asCell();
2223 if (cell->m_type == KindOfArray) {
2224 sd.cmp_func = get_cmp_func(sort_flags, ascending);
2225 data.push_back(sd);
2227 sort_flags = SORT_REGULAR;
2228 ascending = true;
2230 sd.original = v;
2231 arrays.push_back(Array(cell->m_data.parr));
2232 sd.array = &arrays.back();
2233 } else {
2234 int n = v->toInt32();
2235 if (n == SORT_ASC) {
2236 ascending = true;
2237 } else if (n == SORT_DESC) {
2238 ascending = false;
2239 } else {
2240 sort_flags = n;
2245 sd.cmp_func = get_cmp_func(sort_flags, ascending);
2246 data.push_back(sd);
2248 return Array::MultiSort(data, true);
2251 Variant f_array_unique(const Variant& array, int sort_flags /* = 2 */) {
2252 // NOTE, PHP array_unique accepts ArrayAccess objects as well,
2253 // which is not supported here.
2254 getCheckedArray(array);
2255 switch (sort_flags) {
2256 case SORT_STRING:
2257 case SORT_LOCALE_STRING:
2258 return ArrayUtil::StringUnique(arr_array);
2259 case SORT_NUMERIC:
2260 return ArrayUtil::NumericUnique(arr_array);
2261 case SORT_REGULAR:
2262 default:
2263 return ArrayUtil::RegularSortUnique(arr_array);
2267 String f_i18n_loc_get_default() {
2268 return s_collator->getLocale();
2271 bool f_i18n_loc_set_default(const String& locale) {
2272 return s_collator->setLocale(locale);
2275 bool f_i18n_loc_set_attribute(int64_t attr, int64_t val) {
2276 return s_collator->setAttribute(attr, val);
2279 bool f_i18n_loc_set_strength(int64_t strength) {
2280 return s_collator->setStrength(strength);
2283 Variant f_i18n_loc_get_error_code() {
2284 return s_collator->getErrorCode();
2287 Variant f_hphp_array_idx(const Variant& search, const Variant& key, const Variant& def) {
2288 if (!key.isNull()) {
2289 if (LIKELY(search.isArray())) {
2290 ArrayData *arr = search.getArrayData();
2291 VarNR index = key.toKey();
2292 if (!index.isNull()) {
2293 const Variant& ret = arr->get(index, false);
2294 return (&ret != &null_variant) ? ret : def;
2296 } else {
2297 raise_error("hphp_array_idx: search must be an array");
2300 return def;
2303 ///////////////////////////////////////////////////////////////////////////////