hphp/runtime/ext/array/ext_array.cpp (2,815 lines of code) (raw):
/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
| Copyright (c) 1997-2010 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
*/
#include "hphp/runtime/ext/array/ext_array.h"
#include "hphp/runtime/base/array-data-defs.h"
#include "hphp/runtime/base/array-init.h"
#include "hphp/runtime/base/array-provenance.h"
#include "hphp/runtime/base/bespoke-array.h"
#include "hphp/runtime/base/builtin-functions.h"
#include "hphp/runtime/base/collections.h"
#include "hphp/runtime/base/comparisons.h"
#include "hphp/runtime/base/container-functions.h"
#include "hphp/runtime/base/datatype.h"
#include "hphp/runtime/base/double-to-int64.h"
#include "hphp/runtime/base/request-event-handler.h"
#include "hphp/runtime/base/request-info.h"
#include "hphp/runtime/base/sort-flags.h"
#include "hphp/runtime/base/tv-refcount.h"
#include "hphp/runtime/base/vanilla-dict.h"
#include "hphp/runtime/ext/collections/ext_collections-map.h"
#include "hphp/runtime/ext/collections/ext_collections-pair.h"
#include "hphp/runtime/ext/collections/ext_collections-set.h"
#include "hphp/runtime/ext/collections/ext_collections-vector.h"
#include "hphp/runtime/ext/generator/ext_generator.h"
#include "hphp/runtime/ext/std/ext_std_closure.h"
#include "hphp/runtime/ext/std/ext_std_function.h"
#include "hphp/runtime/vm/class-meth-data-ref.h"
#include "hphp/runtime/vm/coeffects.h"
#include "hphp/runtime/vm/jit/translator.h"
#include "hphp/runtime/vm/jit/translator-inline.h"
#include "hphp/util/logger.h"
#include "hphp/util/rds-local.h"
#include <vector>
namespace HPHP {
///////////////////////////////////////////////////////////////////////////////
#define SORT_DESC 3
#define SORT_ASC 4
const StaticString s_count("count");
enum class CaseMode {
LOWER = 0,
UPPER = 1,
};
namespace {
// Create a new array-like with the given type and with enough capacity to
// store `size` elements.
Array makeReserveLike(DataType type, size_t size) {
auto const ad = [&]{
switch (dt_with_rc(type)) {
case KindOfVec: return VanillaVec::MakeReserveVec(size);
case KindOfDict: return VanillaDict::MakeReserveDict(size);
case KindOfKeyset: return VanillaKeyset::MakeReserveSet(size);
default: always_assert(false);
}
not_reached();
}();
return Array::attach(ad);
}
// Appends all keys and values in the ArrayIter `it` to the array `array`.
// If `array` is a keyset, we preserve int keys; otherwise, we renumber them.
//
// `array` must be the same type of array that `it` iterates over. If `array`
// is a dict or darray, it must have vector-like keys. We use both constraints
// for optimizations in the loops below.
void appendKeysAndVals(Array& array, ArrayIter& it) {
assertx(array->toDataType() == it.getArrayData()->toDataType());
if (array.isVec() || array.isKeyset()) {
for (; it; ++it) {
array.append(it.secondVal());
}
} else {
assertx(array->isVectorData());
auto nextKI = safe_cast<int64_t>(array->size());
for (; it; ++it) {
auto const key = it.nvFirst();
if (tvIsInt(key)) {
array.set(nextKI++, it.secondVal());
} else {
auto const str = StrNR(val(key).pstr);
array.set(str.asString(), it.secondVal(), true);
}
}
}
}
// Pushes the given value into a (non-empty) array and pops the last element,
// allowing us to "cycle" optional arguments for certain variadic functions.
Variant cycleVariadics(Array& array, const Variant& val) {
assertx(!array.empty());
auto result = makeReserveLike(array->toDataType(), array.size() + 1);
result.append(val);
ArrayIter it(array.detach(), ArrayIter::noInc);
appendKeysAndVals(result, it);
array = std::move(result);
return array.pop();
}
}
TypedValue HHVM_FUNCTION(array_change_key_case,
const Variant& input,
int64_t case_ /* = 0 */) {
getCheckedContainer(input);
return tvReturn(ArrayUtil::ChangeKeyCase(arr_input,
(CaseMode)case_ == CaseMode::LOWER));
}
TypedValue HHVM_FUNCTION(array_chunk,
const Variant& input,
int chunkSize,
bool preserve_keys /* = false */) {
const auto& cellInput = *input.asTypedValue();
if (UNLIKELY(!isClsMethCompactContainer(cellInput))) {
raise_warning("Invalid operand type was used: %s expects "
"an array or collection as argument 1", __FUNCTION__+2);
return make_tv<KindOfNull>();
}
if (chunkSize < 1) {
raise_invalid_argument_warning("size: %d", chunkSize);
return make_tv<KindOfNull>();
}
const size_t inputSize = getClsMethCompactContainerSize(cellInput);
VecInit ret((inputSize + chunkSize - 1) / chunkSize);
Array chunk;
int current = 0;
for (ArrayIter iter(cellInput); iter; ++iter) {
if (preserve_keys) {
chunk.set(iter.first(), iter.secondValPlus(), true);
} else {
chunk.set(safe_cast<int64_t>(chunk.size()), iter.secondValPlus());
}
if ((++current % chunkSize) == 0) {
ret.append(chunk);
chunk.clear();
}
}
if (!chunk.empty()) {
ret.append(chunk);
}
return tvReturn(ret.toVariant());
}
static inline bool array_column_coerce_key(Variant &key, const char *name) {
/* NULL has a special meaning for each field */
if (key.isNull()) {
return true;
}
/* Custom coercion rules for key types */
if (key.isInteger() || key.isDouble()) {
key = key.toInt64();
return true;
} else if (key.isString() || key.isObject() || key.isLazyClass() ||
key.isClass()) {
key = key.toString();
return true;
} else {
raise_warning("array_column(): The %s key should be either a string "
"or an integer", name);
return false;
}
}
TypedValue HHVM_FUNCTION(array_column,
const Variant& input,
const Variant& val_in,
const Variant& idx_in /* = uninit_variant */) {
getCheckedContainer(input);
Variant val = val_in, idx = idx_in;
if (!array_column_coerce_key(val, "column") ||
!array_column_coerce_key(idx, "index")) {
return make_tv<KindOfBoolean>(false);
}
DictInit ret(arr_input.size());
int64_t nextKI = 0; // for appends
for (ArrayIter it(arr_input); it; ++it) {
Array sub;
if (UNLIKELY(RuntimeOption::PHP7_Builtins && it.second().isObject())) {
sub = it.second().toObject().get()->toArray<IntishCast::Cast>();
} else if (it.second().isArray()) {
sub = it.second().toArray();
} else {
continue;
}
Variant elem;
if (val.isNull()) {
elem = sub;
} else {
auto const val_key = sub.convertKey<IntishCast::Cast>(val);
if (sub.exists(val_key)) {
elem = sub[val_key];
} else {
// skip subarray without named element
continue;
}
}
if (idx.isNull()) {
ret.set(nextKI++, elem);
} else {
auto const idx_key = sub.convertKey<IntishCast::Cast>(idx);
if (!sub.exists(idx_key)) {
// nextKI may wrap around due to overflow if we intish-cast a string
// key to std::numeric_limits<int64_t>::max() in the case below...
if (nextKI >= 0) ret.set(nextKI++, elem);
} else {
auto const sub_idx = [&]{
auto const result = sub[idx_key];
return result.isObject() ? result.toString() : result;
}();
auto const converted = sub.convertKey<IntishCast::Cast>(sub_idx);
if (tvIsInt(converted) && converted.val().num >= nextKI) {
nextKI = int64_t(size_t(converted.val().num) + 1);
}
ret.setUnknownKey<IntishCast::None>(converted, elem);
}
}
}
return tvReturn(ret.toVariant());
}
TypedValue HHVM_FUNCTION(array_combine,
const Variant& keys,
const Variant& values) {
const auto& cell_keys = *keys.asTypedValue();
const auto& cell_values = *values.asTypedValue();
if (UNLIKELY(!isClsMethCompactContainer(cell_keys) ||
!isClsMethCompactContainer(cell_values))) {
raise_warning("Invalid operand type was used: array_combine expects "
"arrays or collections");
return make_tv<KindOfNull>();
}
auto keys_size = getClsMethCompactContainerSize(cell_keys);
if (UNLIKELY(keys_size != getClsMethCompactContainerSize(cell_values))) {
raise_warning("array_combine(): Both parameters should have an equal "
"number of elements");
return make_tv<KindOfBoolean>(false);
}
Array ret = Array::attach(VanillaDict::MakeReserveDict(keys_size));
for (ArrayIter iter1(cell_keys), iter2(cell_values);
iter1; ++iter1, ++iter2) {
auto const key = iter1.secondValPlus();
if (isIntType(type(key)) || isStringType(type(key))) {
ret.set(ret.convertKey<IntishCast::Cast>(key), iter2.secondValPlus());
} else {
ret.set(ret.convertKey<IntishCast::Cast>(tvCastToString(key)),
iter2.secondValPlus());
}
}
return tvReturn(std::move(ret));
}
TypedValue HHVM_FUNCTION(array_count_values,
const Variant& input) {
if (!isClsMethCompactContainer(input)) {
raise_warning("array_count_values() expects parameter 1 to be array, "
"%s given",
getDataTypeString(input.getType()).c_str());
return make_tv<KindOfNull>();
}
return tvReturn(
ArrayUtil::CountValues(
input.isClsMeth() ? input.toArray() : input.isArray() ?
input.asCArrRef()
// If this isn't exactly an Array, then it must be a hack collection,
// so it is safe to get the object data
: collections::toArray<IntishCast::Cast>(
input.getObjectData()
)));
}
Array HHVM_FUNCTION(array_fill_keys,
const Variant& keys,
const Variant& value) {
Optional<DictInit> ai;
auto ok = IterateV(
*keys.asTypedValue(),
[&](ArrayData* adata) {
ai.emplace(adata->size());
},
[&](TypedValue v) {
if (isIntType(v.m_type) || isStringType(v.m_type)) {
ai->setUnknownKey<IntishCast::Cast>(v, value);
} else {
raise_hack_strict(RuntimeOption::StrictArrayFillKeys,
"strict_array_fill_keys",
"keys must be ints or strings");
ai->setUnknownKey<IntishCast::Cast>(
tvCastToString(v).asTypedValue(), value);
}
},
[&](ObjectData* coll) {
if (coll->collectionType() == CollectionType::Pair) {
ai.emplace(2);
}
},
false
);
if (!ok) {
SystemLib::throwInvalidArgumentExceptionObject(
"Invalid operand type was used: array_fill_keys expects an array or "
"collection");
}
assertx(ai.has_value());
return ai->toArray();
}
TypedValue HHVM_FUNCTION(array_fill,
int start_index,
int num,
const Variant& value) {
if (num < 0) {
raise_invalid_argument_warning("Number of elements can't be negative");
return make_tv<KindOfBoolean>(false);
} else if (num == 0) {
return tvReturn(start_index == 0 ? empty_vec_array() : empty_dict_array());
}
if (start_index == 0) {
VecInit vai(num);
for (size_t k = 0; k < num; k++) {
vai.append(value);
}
return make_array_like_tv(vai.create());
} else {
DictInit ret(num, CheckAllocation{});
ret.set(start_index, value);
auto const base = std::max(start_index + 1, 0);
for (auto i = 1; i < num; i++) {
ret.set(base + i - 1, value);
}
return make_array_like_tv(ret.create());
}
}
TypedValue HHVM_FUNCTION(array_flip,
const Variant& trans) {
auto const& transCell = *trans.asTypedValue();
if (UNLIKELY(!isClsMethCompactContainer(transCell))) {
raise_warning("Invalid operand type was used: %s expects "
"an array or collection", __FUNCTION__+2);
return make_tv<KindOfNull>();
}
DictInit ret(getClsMethCompactContainerSize(transCell));
for (ArrayIter iter(transCell); iter; ++iter) {
auto const inner = iter.secondValPlus();
if (isIntType(type(inner)) || isStringType(type(inner))) {
ret.setUnknownKey<IntishCast::Cast>(inner, iter.first());
} else if (isLazyClassType(type(inner)) || isClassType(type(inner))) {
ret.setValidKey(tvAsCVarRef(tvClassToString(inner)), iter.first());
} else {
raise_warning("Can only flip STRING and INTEGER values!");
}
}
return tvReturn(ret.toVariant());
}
bool HHVM_FUNCTION(array_key_exists,
const Variant& key,
const Variant& search) {
const ArrayData *ad;
auto const searchCell = search.asTypedValue();
if (LIKELY(isArrayLikeType(searchCell->m_type))) {
ad = searchCell->m_data.parr;
} else if (searchCell->m_type == KindOfObject) {
ObjectData* obj = searchCell->m_data.pobj;
if (obj->isCollection()) {
return collections::contains(obj, key);
} else if (obj->instanceof(c_Closure::classof())) {
return false;
}
return HHVM_FN(array_key_exists)(key, obj->toArray(false, true));
} else {
raise_bad_type_warning("array_key_exists expects an array or an object; "
"false returned.");
return false;
}
auto const cell = key.asTypedValue();
switch (cell->m_type) {
case KindOfUninit:
case KindOfNull:
throwInvalidArrayKeyException(cell, ad);
case KindOfClsMeth:
case KindOfRClsMeth:
case KindOfBoolean:
case KindOfDouble:
case KindOfPersistentVec:
case KindOfVec:
case KindOfPersistentDict:
case KindOfDict:
case KindOfPersistentKeyset:
case KindOfKeyset:
case KindOfObject:
case KindOfResource:
case KindOfRFunc:
case KindOfFunc:
throwInvalidArrayKeyException(cell, ad);
case KindOfClass:
return ad->exists(StrNR(classToStringHelper(cell->m_data.pclass)));
case KindOfLazyClass:
return ad->exists(StrNR(lazyClassToStringHelper(cell->m_data.plazyclass)));
case KindOfPersistentString:
case KindOfString: {
return ad->exists(StrNR(cell->m_data.pstr));
}
case KindOfInt64:
return ad->exists(cell->m_data.num);
}
not_reached();
}
bool HHVM_FUNCTION(key_exists,
const Variant& key,
const Variant& search) {
return HHVM_FN(array_key_exists)(key, search);
}
TypedValue HHVM_FUNCTION(array_keys,
TypedValue input) {
if (UNLIKELY(!isClsMethCompactContainer(input))) {
raise_warning("array_keys() expects parameter 1 to be an array "
"or collection");
return make_tv<KindOfNull>();
}
VecInit ai(getClsMethCompactContainerSize(input));
IterateKV(input, [&](TypedValue k, TypedValue) {
ai.append(k);
});
return make_array_like_tv(ai.create());
}
namespace {
void php_array_merge(Array& arr1, const Array& arr2) {
assertx(arr1->isVanillaDict());
arr1.reset(!arr2.empty() ? VanillaDict::Merge(arr1.get(), arr2.get())
: VanillaDict::Renumber(arr1.get()));
}
void php_array_merge_recursive(Array& arr1, const Array& arr2) {
for (ArrayIter iter(arr2); iter; ++iter) {
Variant key(iter.first());
if (key.isNumeric()) {
arr1.append(iter.secondVal());
} else if (arr1.exists(key, true)) {
// There is no need to do toKey() conversion, for a key that is already
// in the array.
auto const arrkey =
arr1.convertKey<IntishCast::Cast>(*key.asTypedValue());
auto const lval = arr1.lval(arrkey, AccessFlags::Key);
auto subarr1 = tvCastToArrayLike<IntishCast::Cast>(lval.tv());
subarr1 = subarr1.toDict();
auto subarr2 = tvCastToArrayLike<IntishCast::Cast>(iter.secondVal());
php_array_merge_recursive(subarr1, subarr2);
tvUnset(lval); // avoid contamination of the value that was strongly bound
tvSet(make_array_like_tv(subarr1.get()), lval);
} else {
arr1.set(key, iter.secondVal(), true);
}
}
}
}
TypedValue HHVM_FUNCTION(array_map,
const Variant& callback,
const Variant& arr1,
const Array& _argv) {
VMRegGuard _;
CallCtx ctx;
ctx.func = nullptr;
if (!callback.isNull()) {
vm_decode_function(callback, ctx);
}
const auto& cell_arr1 = *arr1.asTypedValue();
if (UNLIKELY(!isClsMethCompactContainer(cell_arr1))) {
raise_warning("array_map(): Argument #2 should be an array or collection");
return make_tv<KindOfNull>();
}
if (LIKELY(_argv.empty())) {
// Handle the common case where the caller passed two
// params (a callback and a container)
if (!ctx.func) {
if (isArrayLikeType(cell_arr1.m_type)) {
return tvReturn(arr1);
} else {
return tvReturn(arr1.toArray());
}
}
DictInit ret(getContainerSize(cell_arr1));
bool keyConverted = isArrayLikeType(cell_arr1.m_type);
if (!keyConverted) {
auto col_type = cell_arr1.m_data.pobj->collectionType();
keyConverted = !collectionAllowsIntStringKeys(col_type);
}
for (ArrayIter iter(arr1); iter; ++iter) {
auto const arg = iter.secondValPlus();
auto result =
g_context->invokeFuncFew(ctx, 1, &arg, RuntimeCoeffects::fixme());
// if keyConverted is false, it's possible that ret will have fewer
// elements than cell_arr1; keys int(1) and string('1') may both be
// present
ret.setUnknownKey<IntishCast::None>(
*iter.first().asTypedValue(), Variant::attach(result));
}
return tvReturn(ret.toVariant());
}
// Handle the uncommon case where the caller passed a callback
// and two or more containers
req::vector<ArrayIter> iters;
iters.reserve(_argv.size() + 1);
size_t maxLen = getContainerSize(cell_arr1);
iters.emplace_back(cell_arr1);
for (ArrayIter it(_argv); it; ++it) {
auto const c = it.secondValPlus();
if (UNLIKELY(!isContainer(c))) {
raise_warning("array_map(): Argument #%d should be an array or "
"collection", (int)(iters.size() + 2));
iters.emplace_back(tvCastToArrayLike(c));
} else {
iters.emplace_back(c);
size_t len = getContainerSize(c);
if (len > maxLen) maxLen = len;
}
}
VecInit ret_ai(maxLen);
for (size_t k = 0; k < maxLen; k++) {
VecInit params_ai(iters.size());
for (auto& iter : iters) {
if (iter) {
params_ai.append(iter.secondValPlus());
++iter;
} else {
params_ai.append(init_null_variant);
}
}
Array params = params_ai.toArray();
if (ctx.func) {
auto result = Variant::attach(
g_context->invokeFunc(ctx, params, RuntimeCoeffects::fixme())
);
ret_ai.append(result);
} else {
ret_ai.append(params);
}
}
return tvReturn(ret_ai.toVariant());
}
TypedValue HHVM_FUNCTION(array_merge,
const Variant& array1,
const Array& arrays /* = null array */) {
getCheckedContainer(array1);
Array ret = Array::CreateDict();
php_array_merge(ret, arr_array1);
bool success = true;
IterateV(
arrays.get(),
[&](TypedValue v) -> bool {
if (!tvIsArrayLike(v)) {
raise_expected_array_warning("array_merge");
success = false;
return true;
}
php_array_merge(ret, asCArrRef(&v));
return false;
}
);
if (UNLIKELY(!success)) {
return make_tv<KindOfNull>();
}
return tvReturn(std::move(ret));
}
TypedValue HHVM_FUNCTION(array_merge_recursive,
const Variant& array1,
const Array& arrays /* = null array */) {
getCheckedArray(array1);
auto ret = Array::CreateDict();
php_array_merge_recursive(ret, arr_array1);
bool success = true;
IterateV(
arrays.get(),
[&](TypedValue v) -> bool {
if (!tvIsArrayLike(v)) {
raise_expected_array_warning("array_merge_recursive");
success = false;
return true;
}
php_array_merge_recursive(ret, asCArrRef(&v));
return false;
}
);
if (UNLIKELY(!success)) {
return make_tv<KindOfNull>();
}
return tvReturn(std::move(ret));
}
static void php_array_replace(Array &arr1, const Array& arr2) {
for (ArrayIter iter(arr2); iter; ++iter) {
Variant key = iter.first();
arr1.set(key, iter.secondVal(), true);
}
}
static void php_array_replace_recursive(Array &arr1, const Array& arr2) {
if (arr1.get() == arr2.get()) {
// This is an optimization; if the arrays are self recursive, this does
// change the behavior slightly - it skips the "recursion detected" warning.
return;
}
for (ArrayIter iter(arr2); iter; ++iter) {
const auto key =
Variant::wrap(arr1.convertKey<IntishCast::Cast>(iter.first()));
auto const tv = iter.secondVal();
if (arr1.exists(key, true) && isArrayLikeType(type(tv))) {
auto const lval = arr1.lval(key, AccessFlags::Key);
if (isArrayLikeType(lval.type())) {
Array subarr1 = tvCastToArrayLike<IntishCast::Cast>(
lval.tv()
).toDictIntishCast();
php_array_replace_recursive(subarr1, ArrNR(val(tv).parr));
tvSet(make_array_like_tv(subarr1.get()), lval);
} else {
arr1.set(key, iter.secondVal(), true);
}
} else {
arr1.set(key, iter.secondVal(), true);
}
}
}
TypedValue HHVM_FUNCTION(array_replace,
const Variant& array1,
const Variant& array2 /* = uninit_variant */,
const Array& args /* = null array */) {
getCheckedArray(array1);
Array ret = Array::CreateDict();
php_array_replace(ret, arr_array1);
if (UNLIKELY(array2.isNull() && args.empty())) {
return tvReturn(std::move(ret));
}
getCheckedArray(array2);
php_array_replace(ret, arr_array2);
for (ArrayIter iter(args); iter; ++iter) {
auto const v = VarNR(iter.secondVal());
getCheckedArray(v);
php_array_replace(ret, arr_v);
}
return tvReturn(std::move(ret));
}
TypedValue HHVM_FUNCTION(array_replace_recursive,
const Variant& array1,
const Variant& array2 /* = uninit_variant */,
const Array& args /* = null array */) {
getCheckedArray(array1);
Array ret = Array::CreateDict();
php_array_replace_recursive(ret, arr_array1);
if (UNLIKELY(array2.isNull() && args.empty())) {
return tvReturn(std::move(ret));
}
getCheckedArray(array2);
php_array_replace_recursive(ret, arr_array2);
for (ArrayIter iter(args); iter; ++iter) {
auto const v = VarNR(iter.secondVal());
getCheckedArray(v);
php_array_replace_recursive(ret, arr_v);
}
return tvReturn(std::move(ret));
}
TypedValue HHVM_FUNCTION(array_pad,
const Variant& input,
int pad_size,
const Variant& pad_value) {
getCheckedArray(input);
auto arr = arr_input.isKeyset() ? arr_input.toDict() : arr_input;
assertx(arr->isVecType() || arr->isDictType());
if (pad_size > 0) {
return tvReturn(ArrayUtil::PadRight(arr, pad_value, pad_size));
} else {
return tvReturn(ArrayUtil::PadLeft(arr, pad_value, -pad_size));
}
}
TypedValue HHVM_FUNCTION(array_pop,
Variant& containerRef) {
auto* container = containerRef.asTypedValue();
if (UNLIKELY(!isMutableContainer(*container))) {
raise_warning("array_pop() expects parameter 1 to be an "
"array or mutable collection");
return make_tv<KindOfNull>();
}
if (!getContainerSize(*container)) {
return make_tv<KindOfNull>();
}
if (isArrayLikeType(container->m_type)) {
return tvReturn(containerRef.asArrRef().pop());
}
assertx(container->m_type == KindOfObject);
return tvReturn(collections::pop(container->m_data.pobj));
}
TypedValue HHVM_FUNCTION(array_product,
const Variant& input) {
if (UNLIKELY(!isContainer(input))) {
raise_warning("Invalid operand type was used: %s expects "
"an array or collection as argument 1",
__FUNCTION__+2);
return make_tv<KindOfNull>();
}
int64_t i = 1;
ArrayIter iter(input);
for (; iter; ++iter) {
auto const tv = iter.secondValPlus();
switch (type(tv)) {
case KindOfUninit:
case KindOfNull:
case KindOfBoolean:
case KindOfInt64:
i *= tvToInt(tv);
continue;
case KindOfDouble:
goto DOUBLE;
case KindOfPersistentString:
case KindOfString: {
int64_t ti;
double td;
if (val(tv).pstr->isNumericWithVal(ti, td, 1) == KindOfInt64) {
i *= ti;
continue;
} else {
goto DOUBLE;
}
}
case KindOfPersistentVec:
case KindOfVec:
case KindOfPersistentDict:
case KindOfDict:
case KindOfPersistentKeyset:
case KindOfKeyset:
case KindOfObject:
case KindOfResource:
case KindOfRFunc:
case KindOfFunc:
case KindOfClass:
case KindOfLazyClass:
case KindOfClsMeth:
case KindOfRClsMeth:
continue;
}
not_reached();
}
return make_tv<KindOfInt64>(i);
DOUBLE:
double d = i;
for (; iter; ++iter) {
auto const tv = iter.secondValPlus();
if (tvIsClsMeth(tv)) continue;
switch (type(tv)) {
DT_UNCOUNTED_CASE:
case KindOfString:
d *= tvCastToDouble(tv);
case KindOfVec:
case KindOfDict:
case KindOfKeyset:
case KindOfRClsMeth:
case KindOfObject:
case KindOfResource:
case KindOfRFunc:
continue;
}
not_reached();
}
return make_tv<KindOfDouble>(d);
}
TypedValue HHVM_FUNCTION(array_push,
Variant& container,
const Variant& var,
const Array& args /* = null array */) {
if (LIKELY(container.isArray())) {
/*
* Important note: this *must* cast the parr in the inner cell to
* the Array&---we can't copy it to the stack or anything because we
* might escalate.
*/
Array& arr_array = container.asArrRef();
arr_array.append(var);
for (ArrayIter iter(args); iter; ++iter) {
arr_array.append(iter.second());
}
return make_tv<KindOfInt64>(arr_array.size());
}
if (container.isObject()) {
ObjectData* obj = container.getObjectData();
if (obj->isCollection()) {
switch (obj->collectionType()) {
case CollectionType::Vector: {
c_Vector* vec = static_cast<c_Vector*>(obj);
vec->reserve(vec->size() + args.size() + 1);
vec->add(var);
for (ArrayIter iter(args); iter; ++iter) {
vec->add(iter.second());
}
return make_tv<KindOfInt64>(vec->size());
}
case CollectionType::Set: {
c_Set* set = static_cast<c_Set*>(obj);
set->reserve(set->size() + args.size() + 1);
set->add(var);
for (ArrayIter iter(args); iter; ++iter) {
set->add(iter.second());
}
return make_tv<KindOfInt64>(set->size());
}
case CollectionType::Map:
case CollectionType::Pair:
case CollectionType::ImmVector:
case CollectionType::ImmMap:
case CollectionType::ImmSet:
// other collection types are unsupported:
// - mapping collections require a key
// - immutable collections don't allow insertion
break;
}
}
}
raise_expected_array_or_collection_warning("array_push");
return make_tv<KindOfNull>();
}
TypedValue HHVM_FUNCTION(array_rand,
const Variant& input,
int num_req /* = 1 */) {
getCheckedArray(input);
return tvReturn(ArrayUtil::RandomKeys(arr_input, num_req));
}
TypedValue HHVM_FUNCTION(array_reverse,
const Variant& input,
bool preserve_keys /* = false */) {
getCheckedContainer(input);
return tvReturn(ArrayUtil::Reverse(arr_input, preserve_keys));
}
TypedValue HHVM_FUNCTION(array_shift,
Variant& array) {
auto* cell_array = array.asTypedValue();
if (UNLIKELY(!isMutableContainer(*cell_array))) {
raise_warning("array_shift() expects parameter 1 to be an "
"array or mutable collection");
return make_tv<KindOfNull>();
}
if (!getContainerSize(*cell_array)) {
return make_tv<KindOfNull>();
}
if (isArrayLikeType(cell_array->m_type)) {
auto& arr_array = array.asArrRef();
assertx(!arr_array.empty());
auto newArray = makeReserveLike(cell_array->m_type, arr_array.size() - 1);
if (arr_array->isLegacyArray()) newArray->setLegacyArrayInPlace(true);
ArrayIter it(arr_array.detach(), ArrayIter::noInc);
auto const result = it.second();
++it;
appendKeysAndVals(newArray, it);
arr_array = std::move(newArray);
return tvReturn(result);
}
assertx(cell_array->m_type == KindOfObject);
return tvReturn(collections::shift(cell_array->m_data.pobj));
}
TypedValue HHVM_FUNCTION(array_slice,
TypedValue cell_input,
int64_t offset,
const Variant& length /* = uninit_variant */,
bool preserve_keys /* = false */) {
if (UNLIKELY(!isClsMethCompactContainer(cell_input))) {
raise_warning("Invalid operand type was used: %s expects "
"an array or collection as argument 1",
__FUNCTION__+2);
return make_tv<KindOfNull>();
}
int64_t len = length.isNull() ? 0x7FFFFFFF : length.toInt64();
const int64_t num_in = getClsMethCompactContainerSize(cell_input);
if (offset > num_in) {
offset = num_in;
} else if (offset < 0 && (offset = (num_in + offset)) < 0) {
offset = 0;
}
auto const maxLen = num_in - offset;
if (len < 0) {
len = maxLen + len;
} else if (len > maxLen) {
len = maxLen;
}
if (len <= 0) {
return make_persistent_array_like_tv(ArrayData::CreateDict());
}
bool input_is_packed = [&] {
if (isClsMethType(cell_input.m_type)) {
return true;
} else if (isArrayLikeType(cell_input.m_type)) {
return tvIsVec(cell_input);
}
assertx(cell_input.m_type == KindOfObject);
assertx(cell_input.m_data.pobj->isCollection());
return isVectorCollection(cell_input.m_data.pobj->collectionType());
}();
// If the slice covers the entirety of a packed input container, we can cast
// the whole thing into a varray and return it immediately.
if (offset == 0 && len == num_in && input_is_packed) {
if (tvIsArrayLike(cell_input)) {
return tvReturn(ArrNR{val(cell_input).parr}.asArray().toVec());
}
return tvReturn(val(cell_input).pobj->toArray());
}
int pos = 0;
ArrayIter iter(cell_input);
for (; pos < offset && iter; ++pos, ++iter) {}
if (input_is_packed && (offset == 0 || !preserve_keys)) {
VecInit ret(len);
for (; pos < (offset + len) && iter; ++pos, ++iter) {
ret.append(iter.secondValPlus());
}
return tvReturn(ret.toVariant());
}
// Otherwise VecInit can't be used because non-numeric keys are
// preserved even when preserve_keys is false
Array ret = Array::attach(VanillaDict::MakeReserveDict(len));
auto nextKI = 0; // for appends
for (; pos < (offset + len) && iter; ++pos, ++iter) {
auto const key = iter.first();
if (!preserve_keys && key.isInteger()) {
ret.set(nextKI++, iter.secondValPlus());
} else {
ret.set(key, iter.secondValPlus(), true);
}
}
return tvReturn(std::move(ret));
}
Variant array_splice(Variant& input, int offset,
const Variant& length, const Variant& replacement) {
getCheckedArrayVariant(input);
Array ret = Array::CreateDict();
int64_t len = length.isNull() ? 0x7FFFFFFF : length.toInt64();
input = ArrayUtil::Splice(arr_input, offset, len, replacement, ret);
return ret;
}
TypedValue HHVM_FUNCTION(array_splice,
Variant& input,
int offset,
const Variant& length,
const Variant& replacement) {
return tvReturn(array_splice(input, offset, length, replacement));
}
TypedValue HHVM_FUNCTION(array_sum,
const Variant& input) {
if (UNLIKELY(!isContainer(input))) {
raise_warning("Invalid operand type was used: %s expects "
"an array or collection as argument 1",
__FUNCTION__+2);
return make_tv<KindOfNull>();
}
int64_t i = 0;
ArrayIter iter(input);
for (; iter; ++iter) {
auto const tv = iter.secondValPlus();
switch (type(tv)) {
case KindOfUninit:
case KindOfNull:
case KindOfBoolean:
case KindOfInt64:
i += tvToInt(tv);
continue;
case KindOfDouble:
goto DOUBLE;
case KindOfPersistentString:
case KindOfString: {
int64_t ti;
double td;
if (val(tv).pstr->isNumericWithVal(ti, td, 1) == KindOfInt64) {
i += ti;
continue;
} else {
goto DOUBLE;
}
}
case KindOfPersistentVec:
case KindOfVec:
case KindOfPersistentDict:
case KindOfDict:
case KindOfPersistentKeyset:
case KindOfKeyset:
case KindOfObject:
case KindOfResource:
case KindOfRFunc:
case KindOfFunc:
case KindOfClass:
case KindOfLazyClass:
case KindOfClsMeth:
case KindOfRClsMeth:
continue;
}
not_reached();
}
return make_tv<KindOfInt64>(i);
DOUBLE:
double d = i;
for (; iter; ++iter) {
auto const tv = iter.secondValPlus();
if (tvIsClsMeth(tv)) continue;
switch (type(tv)) {
DT_UNCOUNTED_CASE:
case KindOfString:
d += tvCastToDouble(tv);
case KindOfVec:
case KindOfDict:
case KindOfKeyset:
case KindOfRClsMeth:
case KindOfObject:
case KindOfResource:
case KindOfRFunc:
continue;
}
not_reached();
}
return make_tv<KindOfDouble>(d);
}
TypedValue HHVM_FUNCTION(array_unshift,
Variant& array,
const Variant& var,
const Array& args /* = null array */) {
auto* cell_array = array.asTypedValue();
if (UNLIKELY(!isContainer(*cell_array))) {
raise_warning("%s() expects parameter 1 to be an array, Vector, or Set",
__FUNCTION__+2 /* remove the "f_" prefix */);
return make_tv<KindOfNull>();
}
auto const dt = type(cell_array);
if (isArrayLikeType(dt)) {
auto const ad = val(cell_array).parr;
auto const size = ad->size() + args.size() + 1;
auto newArray = makeReserveLike(dt, size);
if (ad->isLegacyArray()) newArray->setLegacyArrayInPlace(true);
if (isDictType(dt)) {
int64_t i = 0;
newArray.set(i++, var);
if (!args.empty()) {
IterateV(args.get(), [&](auto val) { newArray.set(i++, val); });
}
} else {
newArray.append(var);
if (!args.empty()) {
IterateV(args.get(), [&](auto val) { newArray.append(val); });
}
}
Array& arr_array = array.asArrRef();
ArrayIter it(arr_array.detach(), ArrayIter::noInc);
appendKeysAndVals(newArray, it);
arr_array = std::move(newArray);
return make_tv<KindOfInt64>(arr_array.size());
}
// Handle collections
assertx(cell_array->m_type == KindOfObject);
auto* obj = cell_array->m_data.pobj;
assertx(obj->isCollection());
switch (obj->collectionType()) {
case CollectionType::Vector: {
auto* vec = static_cast<c_Vector*>(obj);
if (!args.empty()) {
auto pos_limit = args->iter_end();
for (ssize_t pos = args->iter_last(); pos != pos_limit;
pos = args->iter_rewind(pos)) {
vec->addFront(args->nvGetVal(pos));
}
}
vec->addFront(*var.asTypedValue());
return make_tv<KindOfInt64>(vec->size());
}
case CollectionType::Set: {
auto* st = static_cast<c_Set*>(obj);
if (!args.empty()) {
auto pos_limit = args->iter_end();
for (ssize_t pos = args->iter_last(); pos != pos_limit;
pos = args->iter_rewind(pos)) {
st->addFront(args->nvGetVal(pos));
}
}
st->addFront(*var.asTypedValue());
return make_tv<KindOfInt64>(st->size());
}
case CollectionType::Map:
case CollectionType::Pair:
case CollectionType::ImmVector:
case CollectionType::ImmMap:
case CollectionType::ImmSet:
break;
}
raise_warning("%s() expects parameter 1 to be an array, Vector, or Set",
__FUNCTION__+2 /* remove the "f_" prefix */);
return make_tv<KindOfNull>();
}
TypedValue HHVM_FUNCTION(array_values,
const Variant& input) {
if (input.isArray() || input.isClsMeth()) {
return tvReturn(input.toVec());
}
Optional<VecInit> ai;
auto ok = IterateV(*input.asTypedValue(),
[&](ArrayData* adata) {
ai.emplace(adata->size());
},
[&](TypedValue v) {
ai->append(v);
},
[&](ObjectData* coll) {
if (coll->collectionType() == CollectionType::Pair) {
ai.emplace(2);
}
},
false);
if (!ok) {
raise_warning("array_values() expects parameter 1 to be an array "
"or collection");
return make_tv<KindOfNull>();
}
assertx(ai.has_value());
return tvReturn(ai->toArray());
}
static int php_count_recursive(const Array& array) {
long cnt = array.size();
for (ArrayIter iter(array); iter; ++iter) {
Variant value = iter.second();
if (value.isArray()) {
const Array& arr_value = value.asCArrRef();
check_recursion_throw();
cnt += php_count_recursive(arr_value);
}
}
return cnt;
}
bool HHVM_FUNCTION(shuffle, Variant& array) {
if (!array.isArray()) {
raise_expected_array_warning("shuffle");
return false;
}
auto const ad = array.asCArrRef().get();
if (ad->empty()) return true;
auto const legacy = ad->isLegacyArray();
array = ArrayUtil::Shuffle(array.asCArrRef());
if (legacy) array.asArrRef()->setLegacyArrayInPlace(true);
return true;
}
enum class CountMode {
NORMAL = 0,
RECURSIVE = 1,
};
int64_t HHVM_FUNCTION(count,
const Variant& var,
int64_t mode /* = 0 */) {
switch (var.getType()) {
case KindOfUninit:
case KindOfNull:
return 0;
case KindOfBoolean:
case KindOfInt64:
case KindOfDouble:
case KindOfPersistentString:
case KindOfString:
case KindOfResource:
case KindOfRFunc:
case KindOfFunc:
case KindOfRClsMeth:
case KindOfClass:
case KindOfLazyClass:
return 1;
case KindOfPersistentVec:
case KindOfVec:
case KindOfPersistentDict:
case KindOfDict:
case KindOfPersistentKeyset:
case KindOfKeyset:
if ((CountMode)mode == CountMode::RECURSIVE) {
const Array& arr_var = var.asCArrRef();
return php_count_recursive(arr_var);
}
return var.getArrayData()->size();
case KindOfClsMeth:
return 1;
case KindOfObject:
{
Object obj = var.toObject();
if (obj->isCollection()) {
return collections::getSize(obj.get());
}
if (obj.instanceof(SystemLib::s_CountableClass)) {
return obj->o_invoke_few_args(s_count, RuntimeCoeffects::fixme(), 0).toInt64();
}
}
return 1;
}
not_reached();
}
int64_t HHVM_FUNCTION(sizeof,
const Variant& var) {
return HHVM_FN(count)(var, 0);
}
bool HHVM_FUNCTION(in_array,
const Variant& needle,
const Variant& haystack,
bool strict /* = false */) {
bool ret = false;
auto ok = strict ?
IterateV(*haystack.asTypedValue(),
[&](TypedValue v) -> bool {
if (tvSame(v, *needle.asTypedValue())) {
ret = true;
return true;
}
return false;
}) :
IterateV(*haystack.asTypedValue(),
[&](TypedValue v) -> bool {
if (tvEqual(v, *needle.asTypedValue())) {
ret = true;
return true;
}
return false;
});
if (UNLIKELY(!ok)) {
raise_warning("in_array() expects parameter 2 to be an array "
"or collection");
}
return ret;
}
Variant array_search(const Variant& needle,
const Variant& haystack,
bool strict /* = false */) {
Variant ret = false;
auto ok = strict ?
IterateKV(*haystack.asTypedValue(),
[&](TypedValue k, TypedValue v) -> bool {
if (tvSame(v, *needle.asTypedValue())) {
ret = VarNR(k);
return true;
}
return false;
}) :
IterateKV(*haystack.asTypedValue(),
[&](TypedValue k, TypedValue v) -> bool {
if (tvEqual(v, *needle.asTypedValue())) {
ret = VarNR(k);
return true;
}
return false;
});
if (UNLIKELY(!ok)) {
raise_warning("array_search() expects parameter 2 to be an array "
"or collection");
return init_null();
}
return ret;
}
TypedValue HHVM_FUNCTION(array_search,
const Variant& needle,
const Variant& haystack,
bool strict /* = false */) {
return tvReturn(array_search(needle, haystack, strict));
}
TypedValue HHVM_FUNCTION(range,
const Variant& low,
const Variant& high,
const Variant& step /* = 1 */) {
bool is_step_double = false;
double dstep = 1.0;
if (step.isDouble()) {
dstep = step.toDouble();
is_step_double = true;
} else if (step.isString()) {
int64_t sn;
double sd;
DataType stype = step.toString().get()->isNumericWithVal(sn, sd, 0);
if (stype == KindOfDouble) {
is_step_double = true;
dstep = sd;
} else if (stype == KindOfInt64) {
dstep = (double)sn;
} else {
dstep = step.toDouble();
}
} else {
dstep = step.toDouble();
}
/* We only want positive step values. */
if (dstep < 0.0) dstep *= -1;
if (low.isString() && high.isString()) {
String slow = low.toString();
String shigh = high.toString();
if (slow.size() >= 1 && shigh.size() >=1) {
int64_t n1, n2;
double d1, d2;
DataType type1 = slow.get()->isNumericWithVal(n1, d1, 0);
DataType type2 = shigh.get()->isNumericWithVal(n2, d2, 0);
if (type1 == KindOfDouble || type2 == KindOfDouble || is_step_double) {
if (type1 != KindOfDouble) d1 = slow.toDouble();
if (type2 != KindOfDouble) d2 = shigh.toDouble();
return tvReturn(ArrayUtil::Range(d1, d2, dstep));
}
int64_t lstep = double_to_int64(dstep);
if (type1 == KindOfInt64 || type2 == KindOfInt64) {
if (type1 != KindOfInt64) n1 = slow.toInt64();
if (type2 != KindOfInt64) n2 = shigh.toInt64();
return tvReturn(ArrayUtil::Range(n1, n2, lstep));
}
return tvReturn(ArrayUtil::Range((unsigned char)slow.charAt(0),
(unsigned char)shigh.charAt(0), lstep));
}
}
if (low.is(KindOfDouble) || high.is(KindOfDouble) || is_step_double) {
return tvReturn(ArrayUtil::Range(low.toDouble(), high.toDouble(), dstep));
}
int64_t lstep = double_to_int64(dstep);
return tvReturn(ArrayUtil::Range(low.toInt64(), high.toInt64(), lstep));
}
///////////////////////////////////////////////////////////////////////////////
// diff/intersect helpers
static int cmp_func(const Variant& v1, const Variant& v2, const void *data) {
auto callback = static_cast<const Variant*>(data);
return vm_call_user_func(*callback, make_vec_array(v1, v2)).toInt32();
}
#define COMMA ,
#define diff_intersect_body(type, vararg, intersect_params) \
getCheckedArray(array1); \
if (!arr_array1.size()) { \
return tvReturn(empty_dict_array()); \
} \
CoeffectsAutoGuard _; \
auto ret = arr_array1.type(array2, intersect_params); \
if (ret.size()) { \
for (ArrayIter iter(vararg); iter; ++iter) { \
ret = ret.type(iter.second(), intersect_params); \
if (!ret.size()) break; \
} \
} \
return tvReturn(std::move(ret));
///////////////////////////////////////////////////////////////////////////////
// diff functions
static inline void addToSetHelper(const req::ptr<c_Set>& st,
const TypedValue c,
TypedValue* strTv,
bool convertIntLikeStrs) {
if (c.m_type == KindOfInt64) {
st->add(c.m_data.num);
} else {
StringData* s;
if (LIKELY(isStringType(c.m_type))) {
s = c.m_data.pstr;
} else {
s = tvCastToStringData(c);
decRefStr(strTv->m_data.pstr);
strTv->m_data.pstr = s;
}
int64_t n;
if (convertIntLikeStrs && s->isStrictlyInteger(n)) {
st->add(n);
} else {
st->add(s);
}
}
}
static inline bool checkSetHelper(const req::ptr<c_Set>& st,
const TypedValue c,
TypedValue* strTv,
bool convertIntLikeStrs) {
if (c.m_type == KindOfInt64) {
return st->contains(c.m_data.num);
}
StringData* s;
if (LIKELY(isStringType(c.m_type))) {
s = c.m_data.pstr;
} else {
s = tvCastToStringData(c);
decRefStr(strTv->m_data.pstr);
strTv->m_data.pstr = s;
}
int64_t n;
if (convertIntLikeStrs && s->isStrictlyInteger(n)) {
return st->contains(n);
}
return st->contains(s);
}
static void containerValuesToSetHelper(const req::ptr<c_Set>& st,
const Variant& container) {
Variant strHolder(empty_string_variant());
TypedValue* strTv = strHolder.asTypedValue();
for (ArrayIter iter(container); iter; ++iter) {
auto const c = iter.secondValPlus();
addToSetHelper(st, c, strTv, true);
}
}
static void containerKeysToSetHelper(const req::ptr<c_Set>& st,
const Variant& container) {
Variant strHolder(empty_string_variant());
TypedValue* strTv = strHolder.asTypedValue();
bool convertIntLikeStrs = !isArrayLikeType(container.asTypedValue()->m_type);
for (ArrayIter iter(container); iter; ++iter) {
addToSetHelper(st, *iter.first().asTypedValue(), strTv, convertIntLikeStrs);
}
}
static inline void raiseIsClsMethWarning(const char* fn, int pos) {
raise_warning(
"%s() expects parameter %d to be an array or collection, clsmeth given",
fn, pos);
}
static inline bool checkIsClsMethAndRaise(
const char* fn, const Variant& arr1, int idx = 1) {
if (arr1.isClsMeth()) {
raiseIsClsMethWarning(fn, idx);
return true;
}
return false;
}
static inline bool checkIsClsMethAndRaise(
const char* fn, const Variant& arr1, const Variant& arr2) {
if (checkIsClsMethAndRaise(fn, arr1, 1)) return true;
if (checkIsClsMethAndRaise(fn, arr2, 2)) return true;
return false;
}
TypedValue HHVM_FUNCTION(array_diff,
const Variant& container1,
const Variant& container2,
const Array& args /* = null array */) {
/* Check to make sure all inputs are containers */
const auto& c1 = *container1.asTypedValue();
const auto& c2 = *container2.asTypedValue();
if (isClsMethType(c1.m_type) || isClsMethType(c2.m_type)) {
raiseIsClsMethWarning(__FUNCTION__+2, isClsMethType(c1.m_type) ? 1 : 2);
return make_tv<KindOfNull>();
}
if (UNLIKELY(!isContainer(c1) || !isContainer(c2))) {
raise_warning("%s() expects parameter %d to be an array or collection",
__FUNCTION__+2, /* remove the "f_" prefix */
isContainer(c1) ? 2 : 1);
return make_tv<KindOfNull>();
}
bool moreThanTwo = !args.empty();
size_t largestSize = getContainerSize(c2);
if (UNLIKELY(moreThanTwo)) {
int pos = 3;
for (ArrayIter argvIter(args); argvIter; ++argvIter, ++pos) {
auto const c = argvIter.secondVal();
if (!isContainer(c)) {
raise_warning("%s() expects parameter %d to be an array or collection",
__FUNCTION__+2, /* remove the "f_" prefix */
pos);
return make_tv<KindOfNull>();
}
size_t sz = getContainerSize(c);
if (sz > largestSize) {
largestSize = sz;
}
}
}
/* If container1 is empty, we can stop here and return the empty array */
if (!getContainerSize(c1)) {
return make_persistent_array_like_tv(ArrayData::CreateDict());
}
/* If all of the containers (except container1) are empty, we can just
return container1 (converting it to an array if needed) */
if (!largestSize) {
if (isArrayLikeType(c1.m_type)) {
return tvReturn(container1);
} else {
return tvReturn(container1.toArray<IntishCast::Cast>());
}
}
Array ret = Array::CreateDict();
// Put all of the values from all the containers (except container1 into a
// Set. All types aside from integer and string will be cast to string, and
// we also convert int-like strings to integers.
auto st = req::make<c_Set>();
st->reserve(largestSize);
containerValuesToSetHelper(st, container2);
if (UNLIKELY(moreThanTwo)) {
for (ArrayIter argvIter(args); argvIter; ++argvIter) {
containerValuesToSetHelper(st, VarNR(argvIter.secondVal()));
}
}
// Loop over container1, only copying over key/value pairs where the value
// is not present in the Set. When checking if a value is present in the
// Set, any value that is not an integer or string is cast to a string, and
// we convert int-like strings to integers.
Variant strHolder(empty_string_variant());
TypedValue* strTv = strHolder.asTypedValue();
bool convertIntLikeStrs = !isArrayLikeType(c1.m_type);
for (ArrayIter iter(container1); iter; ++iter) {
auto const c = iter.secondValPlus();
if (checkSetHelper(st, c, strTv, true)) continue;
auto const key = convertIntLikeStrs
? ret.convertKey<IntishCast::Cast>(iter.first())
: *iter.first().asTypedValue();
ret.set(key, iter.secondValPlus(), true);
}
return tvReturn(std::move(ret));
}
namespace {
template <typename T>
ALWAYS_INLINE
bool array_diff_intersect_key_inputs_ok(const TypedValue& c1, const TypedValue& c2,
const ArrayData* args,
const char* fname,
T callback) {
if (isClsMethType(c1.m_type) || isClsMethType(c2.m_type)) {
raiseIsClsMethWarning(fname, isClsMethType(c1.m_type) ? 1 : 2);
return false;
}
if (!isContainer(c1) || !isContainer(c2)) {
raise_warning("%s() expects parameter %d to be an array or collection",
fname, isContainer(c1) ? 2 : 1);
return false;
}
callback(getContainerSize(c2));
bool ok = true;
IterateKV(args, [&](TypedValue k, TypedValue v) {
assertx(k.m_type == KindOfInt64);
if (isClsMethType(v.m_type)) {
raiseIsClsMethWarning(fname, k.m_data.num + 3);
ok = false;
return true;
}
if (!isContainer(v)) {
raise_warning("%s() expects parameter %ld to be an array or collection",
fname, k.m_data.num + 3);
ok = false;
return true;
}
callback(getContainerSize(v));
return false;
});
return ok;
}
template <bool diff, bool coerceThis, bool coerceAd, typename SI, typename SS>
ALWAYS_INLINE
void array_diff_intersect_key_check_arr(ArrayData* ad, TypedValue k, TypedValue v,
SI setInt, SS setStr) {
if (k.m_type == KindOfInt64) {
if (ad->exists(k.m_data.num)) {
if (!diff) setInt(k.m_data.num, v);
return;
}
if (coerceAd) {
// Also need to check if ad has a key that will coerce to this int value.
auto s = String::attach(buildStringData(k.m_data.num));
if (ad->exists(s.get())) {
if (!diff) setInt(k.m_data.num, v);
return;
}
}
if (diff) setInt(k.m_data.num, v);
return;
}
if (coerceThis) {
int64_t n;
if (k.m_data.pstr->isStrictlyInteger(n)) {
if (ad->exists(n)) {
if (!diff) setInt(n, v);
return;
}
if (coerceAd) {
// Also need to check if ad has a key that will coerce to this int
// value (as did this key).
if (ad->exists(k.m_data.pstr)) {
if (!diff) setInt(n, v);
return;
}
}
if (diff) setInt(n, v);
return;
}
} else if (coerceAd) {
// We're coercing keys from ad, but not this. If this string key
// isStrictlyInteger it can never match a key from ad after that key
// is coerced.
int64_t n;
if (k.m_data.pstr->isStrictlyInteger(n)) {
if (diff) setStr(k.m_data.pstr, v);
return;
}
}
if (ad->exists(k.m_data.pstr)) {
if (!diff) setStr(k.m_data.pstr, v);
} else {
if (diff) setStr(k.m_data.pstr, v);
}
}
template <bool diff, bool coerceThis, typename SI, typename SS>
ALWAYS_INLINE
void array_diff_intersect_key_check_pair(TypedValue k, TypedValue v, SI setInt,
SS setStr) {
if (k.m_type == KindOfInt64) {
if (k.m_data.num == 0 || k.m_data.num == 1) {
if (!diff) setInt(k.m_data.num, v);
} else {
if (diff) setInt(k.m_data.num, v);
}
return;
}
if (coerceThis) {
int64_t n;
if (k.m_data.pstr->isStrictlyInteger(n)) {
if (n == 0 || n == 1) {
if (!diff) setInt(n, v);
} else {
if (diff) setInt(n, v);
}
return;
}
}
if (diff) setStr(k.m_data.pstr, v);
}
}
TypedValue HHVM_FUNCTION(array_diff_key,
const Variant& container1,
const Variant& container2,
const Array& args /* = null array */) {
const auto& c1 = *container1.asTypedValue();
const auto& c2 = *container2.asTypedValue();
size_t largestSize = 0;
auto check_cb = [&] (size_t s) {
if (s > largestSize) largestSize = s;
};
if (!array_diff_intersect_key_inputs_ok(c1, c2, args.get(), "array_diff_key",
check_cb)) {
return make_tv<KindOfNull>();
}
if (getContainerSize(c1) == 0) {
return make_persistent_array_like_tv(ArrayData::CreateDict());
}
if (largestSize == 0) {
if (isArrayLikeType(c1.m_type)) {
return tvReturn(container1);
} else {
return tvReturn(container1.toArray<IntishCast::Cast>());
}
}
auto diff_step = [](TypedValue left, TypedValue right) {
auto leftSize = getContainerSize(left);
// If we have more than 2 args, the left input could end up empty
if (leftSize == 0) return empty_dict_array();
DictInit ret(leftSize);
auto setInt = [&](int64_t k, TypedValue v) { ret.set(k, v); };
auto setStr = [&](StringData* k, TypedValue v) { ret.set(k, v); };
auto iterate_left_with = [&](auto test_key) {
IterateKV(left, test_key);
};
// rightAd will be the backing ArrayData for right, or nullptr if right
// is a Pair
auto rightAd = [&](){
if (isArrayLikeType(type(right))) return right.m_data.parr;
return collections::asArray(right.m_data.pobj);
}();
// For historical reasons, we coerce intish string keys only when they
// came from a hack collection. We also need to do the lookup in the right
// array differently if right was a Pair (and so rightAd is nullptr)
if (!rightAd) {
if (isArrayLikeType(type(left))) {
iterate_left_with([&](TypedValue k, TypedValue v) {
array_diff_intersect_key_check_pair<true, false>(
k, v, setInt, setStr);
});
} else {
iterate_left_with([&](TypedValue k, TypedValue v) {
array_diff_intersect_key_check_pair<true, true>(
k, v, setInt, setStr);
});
}
} else {
if (isArrayLikeType(type(left))) {
if (isArrayLikeType(type(right))) {
iterate_left_with([&](TypedValue k, TypedValue v) {
array_diff_intersect_key_check_arr<true, false, false>(
rightAd, k, v, setInt, setStr);
});
} else {
iterate_left_with([&](TypedValue k, TypedValue v) {
array_diff_intersect_key_check_arr<true, false, true>(
rightAd, k, v, setInt, setStr);
});
}
} else {
if (isArrayLikeType(type(right))) {
iterate_left_with([&](TypedValue k, TypedValue v) {
array_diff_intersect_key_check_arr<true, true, false>(
rightAd, k, v, setInt, setStr);
});
} else {
iterate_left_with([&](TypedValue k, TypedValue v) {
array_diff_intersect_key_check_arr<true, true, true>(
rightAd, k, v, setInt, setStr);
});
}
}
}
return ret.toArray();
};
auto ret = diff_step(c1, c2);
IterateV(args.get(), [&](TypedValue v) {
ret = diff_step(make_array_like_tv(ret.get()), v);
});
return make_array_like_tv(ret.detach());
}
TypedValue HHVM_FUNCTION(array_udiff,
const Variant& array1,
const Variant& array2,
const Variant& data_compare_func,
const Array& args /* = null array */) {
if (checkIsClsMethAndRaise( __FUNCTION__+2, array1, array2)) {
return make_tv<KindOfNull>();
}
Variant func = data_compare_func;
Array extra = args;
if (!extra.empty()) {
func = cycleVariadics(extra, func);
}
diff_intersect_body(diff, extra, false COMMA true COMMA NULL COMMA NULL
COMMA cmp_func COMMA &func);
}
TypedValue HHVM_FUNCTION(array_diff_assoc,
const Variant& array1,
const Variant& array2,
const Array& args /* = null array */) {
if (checkIsClsMethAndRaise( __FUNCTION__+2, array1, array2)) {
return make_tv<KindOfNull>();
}
diff_intersect_body(diff, args, true COMMA true);
}
TypedValue HHVM_FUNCTION(array_diff_uassoc,
const Variant& array1,
const Variant& array2,
const Variant& key_compare_func,
const Array& args /* = null array */) {
if (checkIsClsMethAndRaise( __FUNCTION__+2, array1, array2)) {
return make_tv<KindOfNull>();
}
Variant func = key_compare_func;
Array extra = args;
if (!extra.empty()) {
func = cycleVariadics(extra, func);
}
diff_intersect_body(diff, extra, true COMMA true COMMA cmp_func COMMA &func);
}
TypedValue HHVM_FUNCTION(array_udiff_assoc,
const Variant& array1,
const Variant& array2,
const Variant& data_compare_func,
const Array& args /* = null array */) {
if (checkIsClsMethAndRaise( __FUNCTION__+2, array1, array2)) {
return make_tv<KindOfNull>();
}
Variant func = data_compare_func;
Array extra = args;
if (!extra.empty()) {
func = cycleVariadics(extra, func);
}
diff_intersect_body(diff, extra, true COMMA true COMMA NULL COMMA NULL
COMMA cmp_func COMMA &func);
}
TypedValue HHVM_FUNCTION(array_udiff_uassoc,
const Variant& array1,
const Variant& array2,
const Variant& data_compare_func,
const Variant& key_compare_func,
const Array& args /* = null array */) {
if (checkIsClsMethAndRaise( __FUNCTION__+2, array1, array2)) {
return make_tv<KindOfNull>();
}
Variant data_func = data_compare_func;
Variant key_func = key_compare_func;
Array extra = args;
if (!extra.empty()) {
key_func = cycleVariadics(extra, key_func);
data_func = cycleVariadics(extra, data_func);
}
diff_intersect_body(diff, extra, true
COMMA true COMMA cmp_func COMMA &key_func
COMMA cmp_func COMMA &data_func);
}
TypedValue HHVM_FUNCTION(array_diff_ukey,
const Variant& array1,
const Variant& array2,
const Variant& key_compare_func,
const Array& args /* = null array */) {
if (checkIsClsMethAndRaise( __FUNCTION__+2, array1, array2)) {
return make_tv<KindOfNull>();
}
Variant func = key_compare_func;
Array extra = args;
if (!extra.empty()) {
func = cycleVariadics(extra, func);
}
diff_intersect_body(diff, extra, true COMMA false COMMA cmp_func COMMA &func);
}
///////////////////////////////////////////////////////////////////////////////
// intersect functions
static inline TypedValue* makeContainerListHelper(const Variant& a,
const Array& argv,
int count,
int smallestPos) {
assertx(count == argv.size() + 1);
assertx(0 <= smallestPos);
assertx(smallestPos < count);
// Allocate a TypedValue array and copy 'a' and the contents of 'argv'
TypedValue* containers = req::make_raw_array<TypedValue>(count);
tvCopy(*a.asTypedValue(), containers[0]);
int pos = 1;
for (ArrayIter argvIter(argv); argvIter; ++argvIter, ++pos) {
tvCopy(argvIter.secondVal(), containers[pos]);
}
// Perform a swap so that the smallest container occurs at the first
// position in the TypedValue array; this helps improve the performance
// of containerValuesIntersectHelper()
if (smallestPos != 0) {
TypedValue tmp;
tvCopy(containers[0], tmp);
tvCopy(containers[smallestPos], containers[0]);
tvCopy(tmp, containers[smallestPos]);
}
return containers;
}
static inline void addToIntersectMapHelper(const req::ptr<c_Map>& mp,
const TypedValue c,
TypedValue* intOneTv,
TypedValue* strTv,
bool convertIntLikeStrs) {
if (c.m_type == KindOfInt64) {
mp->set(c.m_data.num, *intOneTv);
} else {
StringData* s;
if (LIKELY(isStringType(c.m_type))) {
s = c.m_data.pstr;
} else {
s = tvCastToStringData(c);
decRefStr(strTv->m_data.pstr);
strTv->m_data.pstr = s;
}
int64_t n;
if (convertIntLikeStrs && s->isStrictlyInteger(n)) {
mp->set(n, *intOneTv);
} else {
mp->set(s, *intOneTv);
}
}
}
static inline void updateIntersectMapHelper(const req::ptr<c_Map>& mp,
const TypedValue c,
int pos,
TypedValue* strTv,
bool convertIntLikeStrs) {
if (c.m_type == KindOfInt64) {
auto val = mp->get(c.m_data.num);
if (val && val->m_data.num == pos) {
assertx(val->m_type == KindOfInt64);
++val->m_data.num;
}
} else {
StringData* s;
if (LIKELY(isStringType(c.m_type))) {
s = c.m_data.pstr;
} else {
s = tvCastToStringData(c);
decRefStr(strTv->m_data.pstr);
strTv->m_data.pstr = s;
}
int64_t n;
if (convertIntLikeStrs && s->isStrictlyInteger(n)) {
auto val = mp->get(n);
if (val && val->m_data.num == pos) {
assertx(val->m_type == KindOfInt64);
++val->m_data.num;
}
} else {
auto val = mp->get(s);
if (val && val->m_data.num == pos) {
assertx(val->m_type == KindOfInt64);
++val->m_data.num;
}
}
}
}
static void containerValuesIntersectHelper(const req::ptr<c_Set>& st,
TypedValue* containers,
int count) {
assertx(count >= 2);
auto mp = req::make<c_Map>();
Variant strHolder(empty_string_variant());
TypedValue* strTv = strHolder.asTypedValue();
TypedValue intOneTv = make_tv<KindOfInt64>(1);
for (ArrayIter iter(tvAsCVarRef(&containers[0])); iter; ++iter) {
auto const c = iter.secondValPlus();
// For each value v in containers[0], we add the key/value pair (v, 1)
// to the map. If a value (after various conversions) occurs more than
// once in the container, we'll simply overwrite the old entry and that's
// fine.
addToIntersectMapHelper(mp, c, &intOneTv, strTv, true);
}
for (int pos = 1; pos < count; ++pos) {
for (ArrayIter iter(tvAsCVarRef(&containers[pos])); iter; ++iter) {
auto const c = iter.secondValPlus();
// We check if the value is present as a key in the map. If an entry
// exists and its value equals pos, we increment it, otherwise we do
// nothing. This is essential so that we don't accidentally double-count
// a key (after various conversions) that occurs in the container more
// than once.
updateIntersectMapHelper(mp, c, pos, strTv, true);
}
}
for (ArrayIter iter(mp.get()); iter; ++iter) {
// For each key in the map, we copy the key to the set if the
// corresponding value is equal to pos exactly (which means it
// was present in all of the containers).
auto const tv = iter.secondValPlus();
assertx(type(tv) == KindOfInt64);
if (val(tv).num == count) {
st->add(*iter.first().asTypedValue());
}
}
}
static void containerKeysIntersectHelper(const req::ptr<c_Set>& st,
TypedValue* containers,
int count) {
assertx(count >= 2);
auto mp = req::make<c_Map>();
Variant strHolder(empty_string_variant());
TypedValue* strTv = strHolder.asTypedValue();
TypedValue intOneTv = make_tv<KindOfInt64>(1);
bool convertIntLikeStrs = !isArrayLikeType(containers[0].m_type);
for (ArrayIter iter(tvAsCVarRef(&containers[0])); iter; ++iter) {
auto key = iter.first();
const auto& c = *key.asTypedValue();
// For each key k in containers[0], we add the key/value pair (k, 1)
// to the map. If a key (after various conversions) occurs more than
// once in the container, we'll simply overwrite the old entry and
// that's fine.
addToIntersectMapHelper(mp, c, &intOneTv, strTv, convertIntLikeStrs);
}
for (int pos = 1; pos < count; ++pos) {
convertIntLikeStrs = !isArrayLikeType(containers[pos].m_type);
for (ArrayIter iter(tvAsCVarRef(&containers[pos])); iter; ++iter) {
auto key = iter.first();
const auto& c = *key.asTypedValue();
updateIntersectMapHelper(mp, c, pos, strTv, convertIntLikeStrs);
}
}
for (ArrayIter iter(mp.get()); iter; ++iter) {
// For each key in the map, we copy the key to the set if the
// corresponding value is equal to pos exactly (which means it
// was present in all of the containers).
auto const tv = iter.secondValPlus();
assertx(type(tv) == KindOfInt64);
if (val(tv).num == count) {
st->add(*iter.first().asTypedValue());
}
}
}
TypedValue HHVM_FUNCTION(array_intersect,
const Variant& container1,
const Variant& container2,
const Array& args /* = null array */) {
/* Check to make sure all inputs are containers */
const auto& c1 = *container1.asTypedValue();
const auto& c2 = *container2.asTypedValue();
if (isClsMethType(c1.m_type) || isClsMethType(c2.m_type)) {
raiseIsClsMethWarning(__FUNCTION__+2, isClsMethType(c1.m_type) ? 1 : 2);
return make_tv<KindOfNull>();
}
if (!isContainer(c1) || !isContainer(c2)) {
raise_warning("%s() expects parameter %d to be an array or collection",
__FUNCTION__+2, /* remove the "f_" prefix */
isContainer(c1) ? 2 : 1);
return make_tv<KindOfNull>();
}
bool moreThanTwo = !args.empty();
/* Keep track of which input container was the smallest (excluding
container1) */
int smallestPos = 0;
size_t smallestSize = getContainerSize(c2);
if (UNLIKELY(moreThanTwo)) {
int pos = 1;
for (ArrayIter argvIter(args); argvIter; ++argvIter, ++pos) {
auto const c = argvIter.secondVal();
if (!isContainer(c)) {
raise_warning("%s() expects parameter %d to be an array or collection",
__FUNCTION__+2, /* remove the "f_" prefix */
pos+2);
return make_tv<KindOfNull>();
}
size_t sz = getContainerSize(c);
if (sz < smallestSize) {
smallestSize = sz;
smallestPos = pos;
}
}
}
/* If any of the containers were empty, we can stop here and return the
empty array */
if (!getContainerSize(c1) || !smallestSize) {
return make_persistent_array_like_tv(ArrayData::CreateDict());
}
Array ret = Array::CreateDict();
// Build up a Set containing the values that are present in all the
// containers (except container1)
auto st = req::make<c_Set>();
if (LIKELY(!moreThanTwo)) {
// There is only one container (not counting container1) so we can
// just call containerValuesToSetHelper() to build the Set.
containerValuesToSetHelper(st, container2);
} else {
// We're dealing with three or more containers. Copy all of the containers
// (except the first) into a TypedValue array.
int count = args.size() + 1;
TypedValue* containers =
makeContainerListHelper(container2, args, count, smallestPos);
SCOPE_EXIT { req::free(containers); };
// Build a Set of the values that were present in all of the containers
containerValuesIntersectHelper(st, containers, count);
}
// Loop over container1, only copying over key/value pairs where the value
// is present in the Set. When checking if a value is present in the Set,
// any value that is not an integer or string is cast to a string, and we
// convert int-like strings to integers.
Variant strHolder(empty_string_variant());
TypedValue* strTv = strHolder.asTypedValue();
bool convertIntLikeStrs = !isArrayLikeType(c1.m_type);
for (ArrayIter iter(container1); iter; ++iter) {
auto const c = iter.secondValPlus();
if (!checkSetHelper(st, c, strTv, true)) continue;
const auto key = convertIntLikeStrs
? Variant::wrap(ret.convertKey<IntishCast::Cast>(iter.first()))
: iter.first();
ret.set(key, iter.secondValPlus(), true);
}
return tvReturn(std::move(ret));
}
TypedValue HHVM_FUNCTION(array_intersect_key,
const Variant& container1,
const Variant& container2,
const Array& args /* = null array */) {
const auto& c1 = *container1.asTypedValue();
const auto& c2 = *container2.asTypedValue();
bool empty_arg = false;
auto check_cb = [&] (size_t s) {
if (s == 0) empty_arg = true;
};
if (!array_diff_intersect_key_inputs_ok(c1, c2, args.get(),
"array_intersect_key", check_cb)) {
return make_tv<KindOfNull>();
}
if ((getContainerSize(c1) == 0) || empty_arg) {
return make_persistent_array_like_tv(ArrayData::CreateDict());
}
auto intersect_step = [](TypedValue left, TypedValue right) {
auto const leftSize = getContainerSize(left);
if (leftSize == 0) return empty_dict_array();
DictInit ret(leftSize);
auto setInt = [&](int64_t k, TypedValue v) { ret.set(k, v); };
auto setStr = [&](StringData* k, TypedValue v) { ret.set(k, v); };
auto iterate_left_with = [&](auto test_key) {
IterateKV(left, test_key);
};
// rightAd will be the backing ArrayData for right, or nullptr if right
// is a Pair
auto rightAd = [&](){
if (isArrayLikeType(type(right))) return right.m_data.parr;
return collections::asArray(right.m_data.pobj);
}();
// For historical reasons, we coerce intish string keys only when they
// came from a hack collection. We also need to do the lookup in the right
// array differently if right was a Pair (and so rightAd is nullptr)
if (!rightAd) {
if (isArrayLikeType(type(left))) {
iterate_left_with([&](TypedValue k, TypedValue v) {
array_diff_intersect_key_check_pair<false, false>(
k, v, setInt, setStr);
});
} else {
iterate_left_with([&](TypedValue k, TypedValue v) {
array_diff_intersect_key_check_pair<false, true>(
k, v, setInt, setStr);
});
}
} else {
if (isArrayLikeType(type(left))) {
if (isArrayLikeType(type(right))) {
iterate_left_with([&](TypedValue k, TypedValue v) {
array_diff_intersect_key_check_arr<false, false, false>(
rightAd, k, v, setInt, setStr);
});
} else {
iterate_left_with([&](TypedValue k, TypedValue v) {
array_diff_intersect_key_check_arr<false, false, true>(
rightAd, k, v, setInt, setStr);
});
}
} else {
if (isArrayLikeType(type(right))) {
iterate_left_with([&](TypedValue k, TypedValue v) {
array_diff_intersect_key_check_arr<false, true, false>(
rightAd, k, v, setInt, setStr);
});
} else {
iterate_left_with([&](TypedValue k, TypedValue v) {
array_diff_intersect_key_check_arr<false, true, true>(
rightAd, k, v, setInt, setStr);
});
}
}
}
return ret.toArray();
};
auto ret = intersect_step(c1, c2);
IterateV(args.get(), [&](TypedValue v) {
ret = intersect_step(make_array_like_tv(ret.get()), v);
});
return make_array_like_tv(ret.detach());
}
TypedValue HHVM_FUNCTION(array_uintersect,
const Variant& array1,
const Variant& array2,
const Variant& data_compare_func,
const Array& args /* = null array */) {
if (checkIsClsMethAndRaise( __FUNCTION__+2, array1, array2)) {
return make_tv<KindOfNull>();
}
Variant func = data_compare_func;
Array extra = args;
if (!extra.empty()) {
func = cycleVariadics(extra, func);
}
diff_intersect_body(intersect, extra, false COMMA true COMMA NULL COMMA NULL
COMMA cmp_func COMMA &func);
}
TypedValue HHVM_FUNCTION(array_intersect_assoc,
const Variant& array1,
const Variant& array2,
const Array& args /* = null array */) {
if (checkIsClsMethAndRaise( __FUNCTION__+2, array1, array2)) {
return make_tv<KindOfNull>();
}
diff_intersect_body(intersect, args, true COMMA true);
}
TypedValue HHVM_FUNCTION(array_intersect_uassoc,
const Variant& array1,
const Variant& array2,
const Variant& key_compare_func,
const Array& args /* = null array */) {
if (checkIsClsMethAndRaise( __FUNCTION__+2, array1, array2)) {
return make_tv<KindOfNull>();
}
Variant func = key_compare_func;
Array extra = args;
if (!extra.empty()) {
func = cycleVariadics(extra, func);
}
diff_intersect_body(intersect, extra, true COMMA true
COMMA cmp_func COMMA &func);
}
TypedValue HHVM_FUNCTION(array_uintersect_assoc,
const Variant& array1,
const Variant& array2,
const Variant& data_compare_func,
const Array& args /* = null array */) {
if (checkIsClsMethAndRaise( __FUNCTION__+2, array1, array2)) {
return make_tv<KindOfNull>();
}
Variant func = data_compare_func;
Array extra = args;
if (!extra.empty()) {
func = cycleVariadics(extra, func);
}
diff_intersect_body(intersect, extra, true COMMA true COMMA NULL COMMA NULL
COMMA cmp_func COMMA &func);
}
TypedValue HHVM_FUNCTION(array_uintersect_uassoc,
const Variant& array1,
const Variant& array2,
const Variant& data_compare_func,
const Variant& key_compare_func,
const Array& args /* = null array */) {
if (checkIsClsMethAndRaise( __FUNCTION__+2, array1, array2)) {
return make_tv<KindOfNull>();
}
Variant data_func = data_compare_func;
Variant key_func = key_compare_func;
Array extra = args;
if (!extra.empty()) {
key_func = cycleVariadics(extra, key_func);
data_func = cycleVariadics(extra, data_func);
}
diff_intersect_body(intersect, extra, true COMMA true COMMA cmp_func
COMMA &key_func COMMA cmp_func COMMA &data_func);
}
TypedValue HHVM_FUNCTION(array_intersect_ukey,
const Variant& array1,
const Variant& array2,
const Variant& key_compare_func,
const Array& args /* = null array */) {
if (checkIsClsMethAndRaise( __FUNCTION__+2, array1, array2)) {
return make_tv<KindOfNull>();
}
Variant func = key_compare_func;
Array extra = args;
if (!extra.empty()) {
func = cycleVariadics(extra, func);
}
diff_intersect_body(intersect, extra, true COMMA false
COMMA cmp_func COMMA &func);
}
///////////////////////////////////////////////////////////////////////////////
// sorting functions
struct Collator final : RequestEventHandler {
String getLocale() {
return m_locale;
}
Intl::IntlError &getErrorRef() {
return m_errcode;
}
bool setLocale(const String& locale) {
if (m_locale.same(locale)) {
return true;
}
if (m_ucoll) {
ucol_close(m_ucoll);
m_ucoll = NULL;
}
m_errcode.clearError();
UErrorCode error = U_ZERO_ERROR;
m_ucoll = ucol_open(locale.data(), &error);
if (m_ucoll == NULL) {
raise_warning("failed to load %s locale from icu data", locale.data());
return false;
}
if (U_FAILURE(error)) {
m_errcode.setError(error);
ucol_close(m_ucoll);
m_ucoll = NULL;
return false;
}
m_locale = locale;
return true;
}
UCollator *getCollator() {
return m_ucoll;
}
bool setAttribute(int64_t attr, int64_t val) {
if (!m_ucoll) {
Logger::Verbose("m_ucoll is NULL");
return false;
}
m_errcode.clearError();
UErrorCode error = U_ZERO_ERROR;
ucol_setAttribute(m_ucoll, (UColAttribute)attr,
(UColAttributeValue)val, &error);
if (U_FAILURE(error)) {
m_errcode.setError(error);
Logger::Verbose("Error setting attribute value");
return false;
}
return true;
}
bool setStrength(int64_t strength) {
if (!m_ucoll) {
Logger::Verbose("m_ucoll is NULL");
return false;
}
ucol_setStrength(m_ucoll, (UCollationStrength)strength);
return true;
}
Variant getErrorCode() {
if (!m_ucoll) {
Logger::Verbose("m_ucoll is NULL");
return false;
}
return m_errcode.getErrorCode();
}
void requestInit() override {
m_locale = String(uloc_getDefault(), CopyString);
m_errcode.clearError();
UErrorCode error = U_ZERO_ERROR;
m_ucoll = ucol_open(m_locale.data(), &error);
if (U_FAILURE(error)) {
m_errcode.setError(error);
}
assertx(m_ucoll);
}
void requestShutdown() override {
m_locale.reset();
m_errcode.clearError(false);
if (m_ucoll) {
ucol_close(m_ucoll);
m_ucoll = NULL;
}
}
private:
String m_locale;
UCollator *m_ucoll;
Intl::IntlError m_errcode;
};
IMPLEMENT_STATIC_REQUEST_LOCAL(Collator, s_collator);
namespace {
struct ArraySortTmp {
ArraySortTmp(TypedValue* tv, SortFunction sf) : m_tv(tv) {
m_ad = val(tv).parr->escalateForSort(sf);
assertx(m_ad->empty() || m_ad->hasExactlyOneRef());
}
~ArraySortTmp() {
auto const old = val(m_tv).parr;
auto const legacy = old->isLegacyArray();
// We must call BespokeArray::PostSort before cleaning up the old array,
// because some bespoke arrays may move values from old -> m_ad at PreSort
// and move them back at PostSort. (LoggingArray moves the entire array.)
if (!old->isVanilla()) {
m_ad = BespokeArray::PostSort(old, m_ad);
}
if (m_ad != old) {
tvMove(make_array_like_tv(m_ad), m_tv);
}
// All sort functions preserve the legacy bit.
assertx(m_ad == val(m_tv).parr);
if (legacy != m_ad->isLegacyArray()) {
auto const marked = arrprov::markTvShallow(*m_tv, legacy);
tvMove(marked, m_tv);
}
}
ArrayData* operator->() { return m_ad; }
private:
TypedValue* m_tv;
ArrayData* m_ad;
};
}
static bool
php_sort(Variant& container, int sort_flags, bool ascending) {
if (container.isArray()) {
auto const ad = container.asTypedValue()->val().parr;
if (ad->size() == 1 && ad->isVecType()) return true;
if (ad->empty()) return true;
SortFunction sf = getSortFunction(SORTFUNC_SORT, ascending);
ArraySortTmp ast(container.asTypedValue(), sf);
ast->sort(sort_flags, ascending);
return true;
}
if (container.isObject()) {
ObjectData* obj = container.getObjectData();
if (obj->isCollection() &&
obj->collectionType() == CollectionType::Vector) {
SortFunction sf = getSortFunction(SORTFUNC_SORT, ascending);
c_Vector::SortTmp vst(static_cast<c_Vector*>(obj), sf);
vst->sort(sort_flags, ascending);
return true;
}
// other collections are not supported:
// - Maps and Sets require associative sort
// - Immutable collections are not to be modified
}
raise_expected_array_or_collection_warning(ascending ? "sort" : "rsort");
return false;
}
static bool
php_asort(Variant& container, int sort_flags, bool ascending) {
if (container.isArray()) {
auto const ad = container.asTypedValue()->val().parr;
if (ad->size() <= 1 && !ad->isVecType()) return true;
SortFunction sf = getSortFunction(SORTFUNC_ASORT, ascending);
ArraySortTmp ast(container.asTypedValue(), sf);
ast->asort(sort_flags, ascending);
return true;
}
if (container.isObject()) {
ObjectData* obj = container.getObjectData();
if (obj->isCollection()) {
auto type = obj->collectionType();
if (type == CollectionType::Map || type == CollectionType::Set) {
SortFunction sf = getSortFunction(SORTFUNC_ASORT, ascending);
HashCollection::SortTmp hst(static_cast<HashCollection*>(obj), sf);
hst->asort(sort_flags, ascending);
return true;
}
}
}
raise_expected_array_or_collection_warning(ascending ? "asort" : "arsort");
return false;
}
static bool
php_ksort(Variant& container, int sort_flags, bool ascending) {
if (container.isArray()) {
auto const ad = container.asTypedValue()->val().parr;
auto const vec = ad->isVecType();
if ((vec && ascending) || (!vec && ad->size() <= 1)) return true;
SortFunction sf = getSortFunction(SORTFUNC_KSORT, ascending);
ArraySortTmp ast(container.asTypedValue(), sf);
ast->ksort(sort_flags, ascending);
return true;
}
if (container.isObject()) {
ObjectData* obj = container.getObjectData();
if (obj->isCollection()) {
auto type = obj->collectionType();
if (type == CollectionType::Map || type == CollectionType::Set) {
SortFunction sf = getSortFunction(SORTFUNC_KSORT, ascending);
HashCollection::SortTmp hst(static_cast<HashCollection*>(obj), sf);
hst->ksort(sort_flags, ascending);
return true;
}
}
}
raise_expected_array_or_collection_warning(ascending ? "ksort" : "krsort");
return false;
}
bool HHVM_FUNCTION(sort,
Variant& array,
int sort_flags /* = 0 */) {
if (checkIsClsMethAndRaise( __FUNCTION__+2, array)) return false;
return php_sort(array, sort_flags, true);
}
bool HHVM_FUNCTION(rsort,
Variant& array,
int sort_flags /* = 0 */) {
if (checkIsClsMethAndRaise( __FUNCTION__+2, array)) return false;
return php_sort(array, sort_flags, false);
}
bool HHVM_FUNCTION(asort,
Variant& array,
int sort_flags /* = 0 */) {
if (checkIsClsMethAndRaise( __FUNCTION__+2, array)) return false;
return php_asort(array, sort_flags, true);
}
bool HHVM_FUNCTION(arsort,
Variant& array,
int sort_flags /* = 0 */) {
if (checkIsClsMethAndRaise( __FUNCTION__+2, array)) return false;
return php_asort(array, sort_flags, false);
}
bool HHVM_FUNCTION(ksort,
Variant& array,
int sort_flags /* = 0 */) {
if (checkIsClsMethAndRaise( __FUNCTION__+2, array)) return false;
return php_ksort(array, sort_flags, true);
}
bool HHVM_FUNCTION(krsort,
Variant& array,
int sort_flags /* = 0 */) {
if (checkIsClsMethAndRaise( __FUNCTION__+2, array)) return false;
return php_ksort(array, sort_flags, false);
}
bool HHVM_FUNCTION(natsort, Variant& array) {
if (checkIsClsMethAndRaise( __FUNCTION__+2, array)) return false;
return php_asort(array, SORT_NATURAL, true);
}
bool HHVM_FUNCTION(natcasesort, Variant& array) {
if (checkIsClsMethAndRaise( __FUNCTION__+2, array)) return false;
return php_asort(array, SORT_NATURAL_CASE, true);
}
bool HHVM_FUNCTION(usort,
Variant& container,
const Variant& cmp_function) {
if (checkIsClsMethAndRaise( __FUNCTION__+2, container)) return false;
if (container.isArray()) {
auto const ad = container.asTypedValue()->val().parr;
if (ad->size() == 1 && ad->isVecType()) return true;
if (ad->empty()) return true;
ArraySortTmp ast(container.asTypedValue(), SORTFUNC_USORT);
return ast->usort(cmp_function);
}
if (container.isObject()) {
ObjectData* obj = container.getObjectData();
if (obj->isCollection()) {
if (obj->collectionType() == CollectionType::Vector) {
c_Vector::SortTmp vst(static_cast<c_Vector*>(obj), SORTFUNC_USORT);
return vst->usort(cmp_function);
}
}
// other collections are not supported:
// - Maps and Sets require associative sort
// - Immutable collections are not to be modified
}
raise_expected_array_or_collection_warning("usort");
return false;
}
bool HHVM_FUNCTION(uasort,
Variant& container,
const Variant& cmp_function) {
if (checkIsClsMethAndRaise( __FUNCTION__+2, container)) return false;
if (container.isArray()) {
auto const ad = container.asTypedValue()->val().parr;
if (ad->size() <= 1 && !ad->isVecType()) return true;
ArraySortTmp ast(container.asTypedValue(), SORTFUNC_UASORT);
return ast->uasort(cmp_function);
}
if (container.isObject()) {
ObjectData* obj = container.getObjectData();
if (obj->isCollection()) {
auto type = obj->collectionType();
if (type == CollectionType::Map || type == CollectionType::Set) {
HashCollection::SortTmp hst(static_cast<HashCollection*>(obj),
SORTFUNC_UASORT);
return hst->uasort(cmp_function);
}
}
// other collections are not supported:
// - Vectors require a non-associative sort
// - Immutable collections are not to be modified
}
raise_expected_array_or_collection_warning("uasort");
return false;
}
bool HHVM_FUNCTION(uksort,
Variant& container,
const Variant& cmp_function) {
if (checkIsClsMethAndRaise( __FUNCTION__+2, container)) return false;
if (container.isArray()) {
auto const ad = container.asTypedValue()->val().parr;
if (ad->size() <= 1 && !ad->isVecType()) return true;
ArraySortTmp ast(container.asTypedValue(), SORTFUNC_UKSORT);
return ast->uksort(cmp_function);
}
if (container.isObject()) {
ObjectData* obj = container.getObjectData();
if (obj->isCollection()) {
auto type = obj->collectionType();
if (type == CollectionType::Map || type == CollectionType::Set) {
HashCollection::SortTmp hst(static_cast<HashCollection*>(obj),
SORTFUNC_UKSORT);
return hst->uksort(cmp_function);
}
}
// other collections are not supported:
// - Vectors require a non-associative sort
// - Immutable collections are not to be modified
}
raise_expected_array_or_collection_warning("uksort");
return false;
}
TypedValue HHVM_FUNCTION(array_unique,
const Variant& array,
int sort_flags /* = 2 */) {
getCheckedArray(array);
switch (sort_flags) {
case SORT_STRING:
case SORT_LOCALE_STRING:
return tvReturn(ArrayUtil::StringUnique(arr_array));
case SORT_NUMERIC:
return tvReturn(ArrayUtil::NumericUnique(arr_array));
case SORT_REGULAR:
default:
return tvReturn(ArrayUtil::RegularSortUnique(arr_array));
}
}
String HHVM_FUNCTION(i18n_loc_get_default) {
return s_collator->getLocale();
}
bool HHVM_FUNCTION(i18n_loc_set_default,
const String& locale) {
return s_collator->setLocale(locale);
}
bool HHVM_FUNCTION(i18n_loc_set_attribute,
int64_t attr,
int64_t val) {
return s_collator->setAttribute(attr, val);
}
bool HHVM_FUNCTION(i18n_loc_set_strength,
int64_t strength) {
return s_collator->setStrength(strength);
}
Variant HHVM_FUNCTION(i18n_loc_get_error_code) {
return s_collator->getErrorCode();
}
TypedValue HHVM_FUNCTION(hphp_array_idx,
const Variant& search,
const Variant& key,
const Variant& def) {
if (!key.isNull()) {
if (LIKELY(search.isArray())) {
ArrayData *arr = search.getArrayData();
auto const index = key.toKey(arr).tv();
if (!isNullType(index.m_type)) {
auto const ret = arr->get(index, false);
return tvReturn(ret.is_init() ? tvAsCVarRef(ret) : def);
}
} else {
raise_error("hphp_array_idx: search must be an array");
}
}
return tvReturn(def);
}
static Array::PFUNC_CMP get_cmp_func(int sort_flags, bool ascending) {
switch (sort_flags) {
case SORT_NATURAL:
return ascending ?
Array::SortNaturalAscending : Array::SortNaturalDescending;
case SORT_NATURAL_CASE:
return ascending ?
Array::SortNaturalCaseAscending: Array::SortNaturalCaseDescending;
case SORT_NUMERIC:
return ascending ?
Array::SortNumericAscending : Array::SortNumericDescending;
case SORT_STRING:
return ascending ?
Array::SortStringAscending : Array::SortStringDescending;
case SORT_STRING_CASE:
return ascending ?
Array::SortStringAscendingCase : Array::SortStringDescendingCase;
case SORT_LOCALE_STRING:
return ascending ?
Array::SortLocaleStringAscending : Array::SortLocaleStringDescending;
case SORT_REGULAR:
default:
return ascending ?
Array::SortRegularAscending : Array::SortRegularDescending;
}
}
namespace {
bool array_multisort_impl(
Variant* arg1,
Variant* arg2 = nullptr,
Variant* arg3 = nullptr,
Variant* arg4 = nullptr,
Variant* arg5 = nullptr,
Variant* arg6 = nullptr,
Variant* arg7 = nullptr,
Variant* arg8 = nullptr,
Variant* arg9 = nullptr
) {
if (!arg1->isArray()) {
if (arg1->isClsMeth()) {
raiseIsClsMethWarning("array_multisort", 1);
return false;
}
raise_expected_array_warning("array_multisort");
return false;
}
std::vector<Array::SortData> data;
std::vector<Array> arrays;
arrays.reserve(9); // so no resize would happen
Array::SortData sd;
sd.original = arg1;
arrays.push_back(Array(sd.original->getArrayData()));
sd.array = &arrays.back();
sd.by_key = false;
int sort_flags = SORT_REGULAR;
bool ascending = true;
auto const handleArg = [&] (Variant* arg) {
if (!arg || arg->isNull()) return;
if (arg->isArray()) {
sd.cmp_func = get_cmp_func(sort_flags, ascending);
data.push_back(sd);
sort_flags = SORT_REGULAR;
ascending = true;
sd.original = arg;
arrays.push_back(Array(sd.original->getArrayData()));
sd.array = &arrays.back();
} else {
int n = arg->toInt64();
if (n == SORT_ASC) {
} else if (n == SORT_DESC) {
ascending = false;
} else {
sort_flags = n;
}
}
};
handleArg(arg2);
handleArg(arg3);
handleArg(arg4);
handleArg(arg5);
handleArg(arg6);
handleArg(arg7);
handleArg(arg8);
handleArg(arg9);
sd.cmp_func = get_cmp_func(sort_flags, ascending);
data.push_back(sd);
return Array::MultiSort(data);
}
} // anonymous namespace
bool HHVM_FUNCTION(array_multisort1,
Variant& arg1) {
return array_multisort_impl(&arg1);
}
bool HHVM_FUNCTION(array_multisort2,
Variant& arg1,
Variant& arg2) {
return array_multisort_impl(&arg1, &arg2);
}
bool HHVM_FUNCTION(array_multisort3,
Variant& arg1,
Variant& arg2,
Variant& arg3) {
return array_multisort_impl(&arg1, &arg2, &arg3);
}
bool HHVM_FUNCTION(array_multisort4,
Variant& arg1,
Variant& arg2,
Variant& arg3,
Variant& arg4) {
return array_multisort_impl(&arg1, &arg2, &arg3, &arg4);
}
bool HHVM_FUNCTION(array_multisort5,
Variant& arg1,
Variant& arg2,
Variant& arg3,
Variant& arg4,
Variant& arg5) {
return array_multisort_impl(&arg1, &arg2, &arg3, &arg4, &arg5);
}
bool HHVM_FUNCTION(array_multisort6,
Variant& arg1,
Variant& arg2,
Variant& arg3,
Variant& arg4,
Variant& arg5,
Variant& arg6) {
return array_multisort_impl(&arg1, &arg2, &arg3, &arg4, &arg5, &arg6);
}
bool HHVM_FUNCTION(array_multisort7,
Variant& arg1,
Variant& arg2,
Variant& arg3,
Variant& arg4,
Variant& arg5,
Variant& arg6,
Variant& arg7) {
return array_multisort_impl(&arg1, &arg2, &arg3, &arg4, &arg5, &arg6, &arg7);
}
bool HHVM_FUNCTION(array_multisort8,
Variant& arg1,
Variant& arg2,
Variant& arg3,
Variant& arg4,
Variant& arg5,
Variant& arg6,
Variant& arg7,
Variant& arg8) {
return array_multisort_impl(
&arg1, &arg2, &arg3, &arg4, &arg5, &arg6, &arg7, &arg8);
}
bool HHVM_FUNCTION(array_multisort9,
Variant& arg1,
Variant& arg2,
Variant& arg3,
Variant& arg4,
Variant& arg5,
Variant& arg6,
Variant& arg7,
Variant& arg8,
Variant& arg9) {
return array_multisort_impl(
&arg1, &arg2, &arg3, &arg4, &arg5, &arg6, &arg7, &arg8, &arg9);
}
// HH\\dict
Array HHVM_FUNCTION(HH_dict, const Variant& input) {
return input.toDict();
}
// HH\\keyset
Array HHVM_FUNCTION(HH_keyset, const Variant& input) {
return input.toKeyset();
}
// HH\\vec
Array HHVM_FUNCTION(HH_vec, const Variant& input) {
return input.toVec();
}
// HH\\varray
Array HHVM_FUNCTION(HH_varray, const Variant& input) {
return input.toVec();
}
// HH\\darray
Array HHVM_FUNCTION(HH_darray, const Variant& input) {
return input.toDict();
}
TypedValue HHVM_FUNCTION(HH_array_key_cast, const Variant& input) {
switch (input.getType()) {
case KindOfPersistentString:
case KindOfString: {
int64_t n;
auto const& str = input.asCStrRef();
if (str.get()->isStrictlyInteger(n)) {
return tvReturn(n);
}
return tvReturn(str);
}
case KindOfFunc:
SystemLib::throwInvalidArgumentExceptionObject(
"Funcs cannot be cast to an array-key"
);
case KindOfClass:
return tvReturn(StrNR(classToStringHelper(input.toClassVal())));
case KindOfLazyClass:
return tvReturn(StrNR(lazyClassToStringHelper(input.toLazyClassVal())));
case KindOfInt64:
case KindOfBoolean:
case KindOfDouble:
case KindOfResource:
return tvReturn(input.toInt64());
case KindOfUninit:
case KindOfNull:
return tvReturn(staticEmptyString());
case KindOfPersistentVec:
case KindOfVec:
SystemLib::throwInvalidArgumentExceptionObject(
"Vecs cannot be cast to an array-key"
);
case KindOfPersistentDict:
case KindOfDict:
SystemLib::throwInvalidArgumentExceptionObject(
"Dicts cannot be cast to an array-key"
);
case KindOfPersistentKeyset:
case KindOfKeyset:
SystemLib::throwInvalidArgumentExceptionObject(
"Keysets cannot be cast to an array-key"
);
case KindOfClsMeth:
SystemLib::throwInvalidArgumentExceptionObject(
"ClsMeths cannot be cast to an array-key"
);
case KindOfRClsMeth:
SystemLib::throwInvalidArgumentExceptionObject(
"RClsMeths cannot be cast to an array-key"
);
case KindOfObject:
SystemLib::throwInvalidArgumentExceptionObject(
"Objects cannot be cast to an array-key"
);
case KindOfRFunc:
SystemLib::throwInvalidArgumentExceptionObject(
"Reified functions cannot be cast to an array key"
);
}
not_reached();
}
Array HHVM_FUNCTION(merge_xhp_attr_declarations,
const Array& arr1,
const Array& arr2,
const Array& rest) {
auto ret = Array::CreateDict();
IterateKV(arr1.get(), [&](TypedValue k, TypedValue v) { ret.set(k, v); });
IterateKV(arr2.get(), [&](TypedValue k, TypedValue v) { ret.set(k, v); });
int idx = 2;
IterateV(
rest.get(),
[&](TypedValue arr) {
if (!tvIsArrayLike(arr)) {
raise_param_type_warning("__SystemLib\\merge_xhp_attr_declarations",
idx+1, "array-like", arr);
ret = Array{};
return true;
}
IterateKV(arr.m_data.parr, [&](TypedValue k, TypedValue v) { ret.set(k, v); });
++idx;
return false;
}
);
return ret;
}
///////////////////////////////////////////////////////////////////////////////
struct ArrayExtension final : Extension {
ArrayExtension() : Extension("array") {}
void moduleInit() override {
HHVM_RC_INT_SAME(UCOL_DEFAULT);
HHVM_RC_INT_SAME(UCOL_PRIMARY);
HHVM_RC_INT_SAME(UCOL_SECONDARY);
HHVM_RC_INT_SAME(UCOL_TERTIARY);
HHVM_RC_INT_SAME(UCOL_DEFAULT_STRENGTH);
HHVM_RC_INT_SAME(UCOL_QUATERNARY);
HHVM_RC_INT_SAME(UCOL_IDENTICAL);
HHVM_RC_INT_SAME(UCOL_OFF);
HHVM_RC_INT_SAME(UCOL_ON);
HHVM_RC_INT_SAME(UCOL_SHIFTED);
HHVM_RC_INT_SAME(UCOL_NON_IGNORABLE);
HHVM_RC_INT_SAME(UCOL_LOWER_FIRST);
HHVM_RC_INT_SAME(UCOL_UPPER_FIRST);
HHVM_RC_INT_SAME(UCOL_FRENCH_COLLATION);
HHVM_RC_INT_SAME(UCOL_ALTERNATE_HANDLING);
HHVM_RC_INT_SAME(UCOL_CASE_FIRST);
HHVM_RC_INT_SAME(UCOL_CASE_LEVEL);
HHVM_RC_INT_SAME(UCOL_NORMALIZATION_MODE);
HHVM_RC_INT_SAME(UCOL_STRENGTH);
HHVM_RC_INT_SAME(UCOL_HIRAGANA_QUATERNARY_MODE);
HHVM_RC_INT_SAME(UCOL_NUMERIC_COLLATION);
HHVM_RC_INT(ARRAY_FILTER_USE_BOTH, 1);
HHVM_RC_INT(ARRAY_FILTER_USE_KEY, 2);
HHVM_RC_INT(CASE_LOWER, static_cast<int64_t>(CaseMode::LOWER));
HHVM_RC_INT(CASE_UPPER, static_cast<int64_t>(CaseMode::UPPER));
HHVM_RC_INT(COUNT_NORMAL, static_cast<int64_t>(CountMode::NORMAL));
HHVM_RC_INT(COUNT_RECURSIVE, static_cast<int64_t>(CountMode::RECURSIVE));
HHVM_RC_INT_SAME(SORT_ASC);
HHVM_RC_INT_SAME(SORT_DESC);
HHVM_RC_INT_SAME(SORT_FLAG_CASE);
HHVM_RC_INT_SAME(SORT_LOCALE_STRING);
HHVM_RC_INT_SAME(SORT_NATURAL);
HHVM_RC_INT_SAME(SORT_NUMERIC);
HHVM_RC_INT_SAME(SORT_REGULAR);
HHVM_RC_INT_SAME(SORT_STRING);
HHVM_RC_INT(TAG_PROVENANCE_HERE_MUTATE_COLLECTIONS,
arrprov::TagTVFlags::TAG_PROVENANCE_HERE_MUTATE_COLLECTIONS);
HHVM_FE(array_change_key_case);
HHVM_FE(array_chunk);
HHVM_FE(array_column);
HHVM_FE(array_combine);
HHVM_FE(array_count_values);
HHVM_FE(array_fill_keys);
HHVM_FE(array_fill);
HHVM_FE(array_flip);
HHVM_FE(array_key_exists);
HHVM_FE(key_exists);
HHVM_FE(array_keys);
HHVM_FALIAS(__SystemLib\\array_map, array_map);
HHVM_FE(array_merge_recursive);
HHVM_FE(array_merge);
HHVM_FE(array_replace_recursive);
HHVM_FE(array_replace);
HHVM_FE(array_pad);
HHVM_FE(array_pop);
HHVM_FE(array_product);
HHVM_FE(array_push);
HHVM_FE(array_rand);
HHVM_FE(array_reverse);
HHVM_FE(array_search);
HHVM_FE(array_shift);
HHVM_FE(array_slice);
HHVM_FE(array_splice);
HHVM_FE(array_sum);
HHVM_FE(array_unique);
HHVM_FE(array_unshift);
HHVM_FE(array_values);
HHVM_FE(shuffle);
HHVM_FE(count);
HHVM_FE(sizeof);
HHVM_FE(in_array);
HHVM_FE(range);
HHVM_FE(array_diff);
HHVM_FE(array_udiff);
HHVM_FE(array_diff_assoc);
HHVM_FE(array_diff_uassoc);
HHVM_FE(array_udiff_assoc);
HHVM_FE(array_udiff_uassoc);
HHVM_FE(array_diff_key);
HHVM_FE(array_diff_ukey);
HHVM_FE(array_intersect);
HHVM_FE(array_uintersect);
HHVM_FE(array_intersect_assoc);
HHVM_FE(array_intersect_uassoc);
HHVM_FE(array_uintersect_assoc);
HHVM_FE(array_uintersect_uassoc);
HHVM_FE(array_intersect_key);
HHVM_FE(array_intersect_ukey);
HHVM_FE(sort);
HHVM_FE(rsort);
HHVM_FE(asort);
HHVM_FE(arsort);
HHVM_FE(ksort);
HHVM_FE(krsort);
HHVM_FE(usort);
HHVM_FE(uasort);
HHVM_FE(uksort);
HHVM_FE(natsort);
HHVM_FE(natcasesort);
HHVM_FE(i18n_loc_get_default);
HHVM_FE(i18n_loc_set_default);
HHVM_FE(i18n_loc_set_attribute);
HHVM_FE(i18n_loc_set_strength);
HHVM_FE(i18n_loc_get_error_code);
HHVM_FE(hphp_array_idx);
HHVM_FE(array_multisort1);
HHVM_FE(array_multisort2);
HHVM_FE(array_multisort3);
HHVM_FE(array_multisort4);
HHVM_FE(array_multisort5);
HHVM_FE(array_multisort6);
HHVM_FE(array_multisort7);
HHVM_FE(array_multisort8);
HHVM_FE(array_multisort9);
HHVM_FALIAS(HH\\dict, HH_dict);
HHVM_FALIAS(HH\\vec, HH_vec);
HHVM_FALIAS(HH\\keyset, HH_keyset);
HHVM_FALIAS(HH\\varray, HH_varray);
HHVM_FALIAS(HH\\darray, HH_darray);
HHVM_FALIAS(HH\\array_key_cast, HH_array_key_cast);
HHVM_FALIAS(__SystemLib\\merge_xhp_attr_declarations,
merge_xhp_attr_declarations);
loadSystemlib();
}
} s_array_extension;
}