Commit message: Fix self recursion in compact

Summary: There were no checks at all.

Reviewed By: alexmalyshev

Differential Revision: D3623763

fbshipit-source-id: 9d708deca05bbd121503e8f323b4f295fde8e835
CVE: CVE-2016-6873
Vulnerability description: Self recursion in compact in Facebook HHVM before 3.15.0 allows attackers to have unspecified impact via unknown vectors.

Project: hhvm
File: ext_array.cpp

php_asort(VRefParam container, int sort_flags,
          bool ascending, bool use_zend_sort) {
  if (container.isArray()) {
    auto ref = container.getVariantOrNull();
    if (!ref) return true;
    Array& arr_array = ref->asArrRef();
    if (use_zend_sort) {
      return zend_asort(*ref, sort_flags, ascending);
    }
    SortFunction sf = getSortFunction(SORTFUNC_ASORT, ascending);
    ArraySortTmp ast(arr_array, 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) {
        HashCollection* hc = static_cast<HashCollection*>(obj);
        hc->asort(sort_flags, ascending);
        return true;
      }
    }
  }
  throw_expected_array_or_collection_exception(ascending ? "asort" : "arsort");
  return false;
}

Project: hhvm
File: ext_array.cpp

static inline void updateIntersectMapHelper(const req::ptr<c_Map>& mp,
                                            const Cell 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) {
      assert(val->m_type == KindOfInt64);
      ++val->m_data.num;
    }
  } else {
    StringData* s;
    if (LIKELY(isStringType(c.m_type))) {
      s = c.m_data.pstr;
    } else {
      s = tvCastToString(&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) {
        assert(val->m_type == KindOfInt64);
        ++val->m_data.num;
      }
    } else {
      auto val = mp->get(s);
      if (val && val->m_data.num == pos) {
        assert(val->m_type == KindOfInt64);
        ++val->m_data.num;
      }
    }
  }
}

Project: hhvm
File: ext_array.cpp

TypedValue HHVM_FUNCTION(array_replace,
                         const Variant& array1,
                         const Variant& array2 /* = null_variant */,
                         const Array& args /* = null array */) {
  getCheckedArray(array1);
  Array ret = Array::Create();
  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) {
    const Variant& v = iter.secondRef();
    getCheckedArray(v);
    php_array_replace(ret, arr_v);
  }
  return tvReturn(std::move(ret));
}

Project: hhvm
File: ext_array.cpp

  ~ArraySortTmp() {
    if (m_ad != m_arr.get()) {
      m_arr = Array::attach(m_ad);
    }
  }

Project: hhvm
File: ext_array.cpp

TypedValue HHVM_FUNCTION(array_map,
                         const Variant& callback,
                         const Variant& arr1,
                         const Array& _argv) {
  VMRegGuard _;
  CallCtx ctx;
  ctx.func = nullptr;
  if (!callback.isNull()) {
    CallerFrame cf;
    vm_decode_function(callback, cf(), false, ctx);
  }
  const auto& cell_arr1 = *arr1.asCell();
  if (UNLIKELY(!isContainer(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 (isArrayType(cell_arr1.m_type)) {
        return tvReturn(arr1);
      } else {
        return tvReturn(arr1.toArray());
      }
    }
    ArrayInit ret(getContainerSize(cell_arr1), ArrayInit::Map{});
    bool keyConverted = isArrayType(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) {
      Variant result;
      g_context->invokeFuncFew((TypedValue*)&result, ctx, 1,
                               iter.secondRefPlus().asCell());
      // 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.add(iter.first(), result, keyConverted);
    }
    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) {
    const auto& c = *it.secondRefPlus().asCell();
    if (UNLIKELY(!isContainer(c))) {
      raise_warning("array_map(): Argument #%d should be an array or "
                    "collection", (int)(iters.size() + 2));
      iters.emplace_back(it.secondRefPlus().toArray());
    } else {
      iters.emplace_back(c);
      size_t len = getContainerSize(c);
      if (len > maxLen) maxLen = len;
    }
  }
  PackedArrayInit ret_ai(maxLen);
  for (size_t k = 0; k < maxLen; k++) {
    PackedArrayInit params_ai(iters.size());
    for (auto& iter : iters) {
      if (iter) {
        params_ai.append(iter.secondRefPlus());
        ++iter;
      } else {
        params_ai.append(init_null_variant);
      }
    }
    Array params = params_ai.toArray();
    if (ctx.func) {
      Variant result;
      g_context->invokeFunc((TypedValue*)&result,
                              ctx.func, params, ctx.this_,
                              ctx.cls, nullptr, ctx.invName);
      ret_ai.append(result);
    } else {
      ret_ai.append(params);
    }
  }
  return tvReturn(ret_ai.toVariant());
}

Project: hhvm
File: ext_array.cpp

TypedValue HHVM_FUNCTION(array_flip,
                         const Variant& trans) {
  auto const& transCell = *trans.asCell();
  if (UNLIKELY(!isContainer(transCell))) {
    raise_warning("Invalid operand type was used: %s expects "
                  "an array or collection", __FUNCTION__+2);
    return make_tv<KindOfNull>();
  }

  ArrayInit ret(getContainerSize(transCell), ArrayInit::Mixed{});
  for (ArrayIter iter(transCell); iter; ++iter) {
    const Variant& value(iter.secondRefPlus());
    if (value.isString() || value.isInteger()) {
      ret.setUnknownKey(value, iter.first());
    } else {
      raise_warning("Can only flip STRING and INTEGER values!");
    }
  }
  return tvReturn(ret.toVariant());
}

Project: hhvm
File: ext_array.cpp

TypedValue HHVM_FUNCTION(array_replace_recursive,
                         const Variant& array1,
                         const Variant& array2 /* = null_variant */,
                         const Array& args /* = null array */) {
  getCheckedArray(array1);
  Array ret = Array::Create();
  PointerSet seen;
  php_array_replace_recursive(seen, false, ret, arr_array1);
  assert(seen.empty());

  if (UNLIKELY(array2.isNull() && args.empty())) {
    return tvReturn(std::move(ret));
  }

  getCheckedArray(array2);
  php_array_replace_recursive(seen, false, ret, arr_array2);
  assert(seen.empty());

  for (ArrayIter iter(args); iter; ++iter) {
    const Variant& v = iter.secondRef();
    getCheckedArray(v);
    php_array_replace_recursive(seen, false, ret, arr_v);
    assert(seen.empty());
  }
  return tvReturn(std::move(ret));
}

Project: hhvm
File: ext_array.cpp

Variant array_values(const Variant& input) {
  if (input.isArray() && input.asCArrRef()->isVecArray()) {
    return input;
  }

  folly::Optional<PackedArrayInit> ai;
  auto ok = IterateV(*input.asCell(),
                     [&](ArrayData* adata) {
                       ai.emplace(adata->size());
                     },
                     [&](const TypedValue* tv) {
                       ai->appendWithRef(tvAsCVarRef(tv));
                     },
                     [&](ObjectData* coll) {
                       if (coll->collectionType() == CollectionType::Pair) {
                         ai.emplace(2);
                       }
                     });

  if (!ok) {
    raise_warning("array_values() expects parameter 1 to be an array "
                  "or collection");
    return init_null();
  }

  assert(ai.hasValue());
  return ai->toVariant();
}

Project: hhvm
File: ext_array.cpp

  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,      CaseMode::LOWER);
    HHVM_RC_INT(CASE_UPPER,      CaseMode::UPPER);

    HHVM_RC_INT(COUNT_NORMAL,    CountMode::NORMAL);
    HHVM_RC_INT(COUNT_RECURSIVE, 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_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(array_walk_recursive);
    HHVM_FE(array_walk);
    HHVM_FE(compact);
    HHVM_FALIAS(__SystemLib\\compact_sl, __SystemLib_compact_sl);
    HHVM_FE(shuffle);
    HHVM_FE(count);
    HHVM_FE(sizeof);
    HHVM_FE(each);
    HHVM_FE(current);
    HHVM_FE(next);
    HHVM_FE(pos);
    HHVM_FE(prev);
    HHVM_FE(reset);
    HHVM_FE(end);
    HHVM_FE(key);
    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_multisort);
    HHVM_FALIAS(HH\\dict, HH_dict);
    HHVM_FALIAS(HH\\vec, HH_vec);

    loadSystemlib();
  }

Project: hhvm
File: ext_array.cpp

  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;
  }

Project: hhvm
File: ext_array.cpp

Variant HHVM_FUNCTION(array_keys, int64_t argc,
                                  const Variant& input,
                                  const Variant& search_value /*=null*/,
                                  bool strict /*=false*/) {
  return array_keys_helper(
    input,
    argc < 2 ? null_variant : search_value,
    strict
  );
}

Project: hhvm
File: ext_array.cpp

Variant HHVM_FUNCTION(i18n_loc_get_error_code) {
  return s_collator->getErrorCode();
}

Project: hhvm
File: ext_array.cpp

Variant HHVM_FUNCTION(next,
                      VRefParam refParam) {
  return iter_op_impl(
    refParam,
    &ArrayData::next,
    s___next,
    false
  );
}

Project: hhvm
File: ext_array.cpp

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();
      VarNR index = key.toKey(arr);
      if (!index.isNull()) {
        const Variant& ret = arr->get(index, false);
        return tvReturn((&ret != &null_variant) ? ret : def);
      }
    } else {
      raise_error("hphp_array_idx: search must be an array");
    }
  }
  return tvReturn(def);
}

Project: hhvm
File: ext_array.cpp

bool HHVM_FUNCTION(usort,
                   VRefParam container,
                   const Variant& cmp_function) {
  if (container.isArray()) {
    auto sort = [](Array& arr_array, const Variant& cmp_function) -> bool {
      if (RuntimeOption::EnableZendSorting) {
        arr_array.sort(cmp_func, false, true, &cmp_function);
        return true;
      } else {
        ArraySortTmp ast(arr_array, SORTFUNC_USORT);
        return ast->usort(cmp_function);
      }
    };
    auto ref = container.getVariantOrNull();
    if (LIKELY(ref != nullptr)) {
      return sort(ref->asArrRef(), cmp_function);
    }
    auto tmp = container->asCArrRef();
    return sort(tmp, cmp_function);
  }
  if (container.isObject()) {
    ObjectData* obj = container.getObjectData();
    if (obj->isCollection()) {
      if (obj->collectionType() == CollectionType::Vector) {
        c_Vector* vec = static_cast<c_Vector*>(obj);
        return vec->usort(cmp_function);
      }
    }
    // other collections are not supported:
    //  - Maps and Sets require associative sort
    //  - Immutable collections are not to be modified
  }
  throw_expected_array_or_collection_exception("usort");
  return false;
}

Project: hhvm
File: ext_array.cpp

Variant array_keys_helper(const Variant& input,
                          const Variant& search_value /* = uninit_null */,
                          bool strict /* = false */) {
  const auto& cell_input = *input.asCell();
  if (UNLIKELY(!isContainer(cell_input))) {
    raise_warning("array_keys() expects parameter 1 to be an array "
                  "or collection");
    return init_null();
  }

  if (LIKELY(!search_value.isInitialized())) {
    PackedArrayInit ai(getContainerSize(cell_input));
    for (ArrayIter iter(cell_input); iter; ++iter) {
      ai.append(iter.first());
    }
    return ai.toVariant();
  } else {
    Array ai = Array::attach(PackedArray::MakeReserve(0));
    for (ArrayIter iter(cell_input); iter; ++iter) {
      if ((strict && HPHP::same(iter.secondRefPlus(), search_value)) ||
          (!strict && HPHP::equal(iter.secondRefPlus(), search_value))) {
        ai.append(iter.first());
      }
    }
    return ai;
  }
}

Project: hhvm
File: ext_array.cpp

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 = (int64_t) dstep;
      if (type1 == KindOfInt64 || type2 == KindOfInt64) {
        if (type1 != KindOfInt64) n1 = slow.toInt64();
        if (type2 != KindOfInt64) n2 = shigh.toInt64();
        return tvReturn(ArrayUtil::Range((double)n1, (double)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 = (int64_t) dstep;
  return tvReturn(ArrayUtil::Range(low.toDouble(), high.toDouble(), lstep));
}

Project: hhvm
File: ext_array.cpp

Array HHVM_FUNCTION(HH_vec, const Array& arr) {
  return arr.toVec();
}

Project: hhvm
File: ext_array.cpp

TypedValue HHVM_FUNCTION(array_intersect_key,
                         const Variant& container1,
                         const Variant& container2,
                         const Array& args /* = null array */) {
  ARRAY_INTERSECT_PRELUDE()
  // If we're only dealing with two containers and if they are both arrays,
  // we can avoid creating an intermediate Set
  if (!moreThanTwo && isArrayType(c1.m_type) && isArrayType(c2.m_type)) {
    auto ad2 = c2.m_data.parr;
    for (ArrayIter iter(container1); iter; ++iter) {
      auto key = iter.first();
      const auto& c = *key.asCell();
      if (c.m_type == KindOfInt64) {
        if (!ad2->exists(c.m_data.num)) continue;
      } else {
        assert(isStringType(c.m_type));
        if (!ad2->exists(c.m_data.pstr)) continue;
      }
      ret.setWithRef(key, iter.secondRefPlus(), true);
    }
    return tvReturn(std::move(ret));
  }
  // Build up a Set containing the keys 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 containerKeysToSetHelper() to build the Set.
    containerKeysToSetHelper(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 keys that were present in all of the containers
    containerKeysIntersectHelper(st, containers, count);
  }
  // Loop over container1, only copying over key/value pairs where the key
  // is present in the Set. When checking if a key 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 isKey = isArrayType(c1.m_type);
  for (ArrayIter iter(container1); iter; ++iter) {
    auto key = iter.first();
    const auto& c = *key.asCell();
    if (!checkSetHelper(st, c, strTv, !isKey)) continue;
    ret.setWithRef(key, iter.secondRefPlus(), isKey);
  }
  return tvReturn(std::move(ret));
}

Project: hhvm
File: ext_array.cpp

bool HHVM_FUNCTION(i18n_loc_set_attribute,
                   int64_t attr,
                   int64_t val) {
  return s_collator->setAttribute(attr, val);
}

Project: hhvm
File: ext_array.cpp

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 = key.toString();
    return true;
  } else {
    raise_warning("array_column(): The %s key should be either a string "
                  "or an integer", name);
    return false;
  }
}

Project: hhvm
File: ext_array.cpp

TypedValue HHVM_FUNCTION(array_uintersect_assoc,
                         const Variant& array1,
                         const Variant& array2,
                         const Variant& data_compare_func,
                         const Array& args /* = null array */) {
  Variant func = data_compare_func;
  Array extra = args;
  if (!extra.empty()) {
    extra.prepend(func);
    func = extra.pop();
  }
  diff_intersect_body(intersect, extra, true COMMA true COMMA NULL COMMA NULL
                      COMMA cmp_func COMMA &func);
}

Project: hhvm
File: ext_array.cpp

static void compact(PointerSet& seen,
                    VarEnv* v, Array &ret, const Variant& var) {
  if (var.isArray()) {
    auto adata = var.getArrayData();
    auto check = couldRecur(var, adata);
    if (check && !seen.insert(adata).second) {
      raise_warning("compact(): recursion detected");
      return;
    }
    for (ArrayIter iter(adata); iter; ++iter) {
      compact(seen, v, ret, iter.secondRef());
    }
    if (check) seen.erase(adata);
  } else {
    String varname = var.toString();
    if (!varname.empty() && v->lookup(varname.get()) != NULL) {
      ret.set(varname, *reinterpret_cast<Variant*>(v->lookup(varname.get())));
    }
  }
}

Project: hhvm
File: ext_array.cpp

TypedValue HHVM_FUNCTION(array_diff,
                         const Variant& container1,
                         const Variant& container2,
                         const Array& args /* = null array */) {
  ARRAY_DIFF_PRELUDE()
  // 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) {
      const auto& container = argvIter.secondRef();
      containerValuesToSetHelper(st, container);
    }
  }
  // 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 isKey = isArrayType(c1.m_type);
  for (ArrayIter iter(container1); iter; ++iter) {
    const auto& val = iter.secondRefPlus();
    const auto& c = *val.asCell();
    if (checkSetHelper(st, c, strTv, true)) continue;
    ret.setWithRef(iter.first(), val, isKey);
  }
  return tvReturn(std::move(ret));
}

Project: hhvm
File: ext_array.cpp

TypedValue HHVM_FUNCTION(array_count_values,
                         ArrayArg input) {
  return tvReturn(ArrayUtil::CountValues(ArrNR(input.get())));
}

Project: hhvm
File: ext_array.cpp

static void containerKeysToSetHelper(const req::ptr<c_Set>& st,
                                     const Variant& container) {
  Variant strHolder(empty_string_variant());
  TypedValue* strTv = strHolder.asTypedValue();
  bool isKey = isArrayType(container.asCell()->m_type);
  for (ArrayIter iter(container); iter; ++iter) {
    addToSetHelper(st, *iter.first().asCell(), strTv, !isKey);
  }
}

Project: hhvm
File: ext_array.cpp

bool HHVM_FUNCTION(krsort,
                   VRefParam array,
                   int sort_flags /* = 0 */) {
  bool use_zend_sort = RuntimeOption::EnableZendSorting;
  return php_ksort(array, sort_flags, false, use_zend_sort);
}

Project: hhvm
File: ext_array.cpp

static void php_array_merge(Array &arr1, const Array& arr2) {
  arr1.merge(arr2);
}

Project: hhvm
File: ext_array.cpp

TypedValue HHVM_FUNCTION(array_reverse,
                         ArrayArg input,
                         bool preserve_keys /* = false */) {

  ArrNR arrNR(input.get());
  const Array& arr = arrNR.asArray();
  return tvReturn(ArrayUtil::Reverse(arr, preserve_keys));
}

Project: hhvm
File: ext_array.cpp

TypedValue HHVM_FUNCTION(array_intersect,
                         const Variant& container1,
                         const Variant& container2,
                         const Array& args /* = null array */) {
  ARRAY_INTERSECT_PRELUDE()
  // 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 isKey = isArrayType(c1.m_type);
  for (ArrayIter iter(container1); iter; ++iter) {
    const auto& val = iter.secondRefPlus();
    const auto& c = *val.asCell();
    if (!checkSetHelper(st, c, strTv, true)) continue;
    ret.setWithRef(iter.first(), val, isKey);
  }
  return tvReturn(std::move(ret));
}

Project: hhvm
File: ext_array.cpp

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 */) {
  Variant data_func = data_compare_func;
  Variant key_func = key_compare_func;
  Array extra = args;
  if (!extra.empty()) {
    extra.prepend(key_func);
    extra.prepend(data_func);
    key_func = extra.pop();
    data_func = extra.pop();
  }
  diff_intersect_body(intersect, extra, true COMMA true COMMA cmp_func
                      COMMA &key_func COMMA cmp_func COMMA &data_func);
}

Project: hhvm
File: ext_array.cpp

TypedValue HHVM_FUNCTION(array_rand,
                         const Variant& input,
                         int num_req /* = 1 */) {
  getCheckedArray(input);
  return tvReturn(ArrayUtil::RandomKeys(arr_input, num_req));
}

Project: hhvm
File: ext_array.cpp

  void vscan(IMarker& mark) const override {
    mark(m_locale);
  }

Project: hhvm
File: ext_array.cpp

bool HHVM_FUNCTION(key_exists,
                   const Variant& key,
                   const Variant& search) {
  return HHVM_FN(array_key_exists)(key, search);
}

Project: hhvm
File: ext_array.cpp

Variant array_splice(VRefParam input, int offset,
                     const Variant& length, const Variant& replacement) {
  getCheckedArrayVariant(input);
  Array ret(Array::Create());
  int64_t len = length.isNull() ? 0x7FFFFFFF : length.toInt64();
  input.assignIfRef(ArrayUtil::Splice(arr_input, offset, len, replacement, &ret));
  return ret;
}

Project: hhvm
File: ext_array.cpp

TypedValue HHVM_FUNCTION(array_fill,
                         int start_index,
                         int num,
                         const Variant& value) {
  if (num < 0) {
    throw_invalid_argument("Number of elements can't be negative");
    return make_tv<KindOfBoolean>(false);
  }

  if (start_index == 0) {
    PackedArrayInit pai(num, CheckAllocation{});
    for (size_t k = 0; k < num; k++) {
      pai.append(value);
    }
    return tvReturn(pai.toVariant());
  } else {
    ArrayInit ret(num, ArrayInit::Mixed{}, CheckAllocation{});
    ret.set(start_index, value);
    for (int i = num - 1; i > 0; i--) {
      ret.append(value);
    }
    return tvReturn(ret.toVariant());
  }
}

Project: hhvm
File: ext_array.cpp

bool HHVM_FUNCTION(array_key_exists,
                   const Variant& key,
                   const Variant& search) {
  const ArrayData *ad;

  auto const searchCell = search.asCell();
  if (LIKELY(isArrayType(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);
    }
    return HHVM_FN(array_key_exists)(key, toArray(search));
  } else {
    throw_bad_type_exception("array_key_exists expects an array or an object; "
                             "false returned.");
    return false;
  }

  auto const cell = key.asCell();

  switch (cell->m_type) {
    case KindOfUninit:
    case KindOfNull:
      return ad->useWeakKeys() && ad->exists(staticEmptyString());

    case KindOfBoolean:
    case KindOfDouble:
    case KindOfPersistentArray:
    case KindOfArray:
    case KindOfObject:
    case KindOfResource:
      if (!ad->useWeakKeys()) throwInvalidArrayKeyException(cell, ad);
      raise_warning("Array key should be either a string or an integer");
      return false;

    case KindOfPersistentString:
    case KindOfString: {
      int64_t n = 0;
      if (ad->convertKey(cell->m_data.pstr, n)) {
        return ad->exists(n);
      }
      return ad->exists(StrNR(cell->m_data.pstr));
    }
    case KindOfInt64:
      return ad->exists(cell->m_data.num);
    case KindOfRef:
    case KindOfClass:
      break;
  }
  not_reached();
}

Project: hhvm
File: ext_array.cpp

TypedValue HHVM_FUNCTION(array_uintersect,
                         const Variant& array1,
                         const Variant& array2,
                         const Variant& data_compare_func,
                         const Array& args /* = null array */) {
  Variant func = data_compare_func;
  Array extra = args;
  if (!extra.empty()) {
    extra.prepend(func);
    func = extra.pop();
  }
  diff_intersect_body(intersect, extra, false COMMA true COMMA NULL COMMA NULL
                      COMMA cmp_func COMMA &func);
}

Project: hhvm
File: ext_array.cpp

  UCollator *getCollator() {
    return m_ucoll;
  }

Project: hhvm
File: ext_array.cpp

bool HHVM_FUNCTION(array_walk,
                   VRefParam input,
                   const Variant& funcname,
                   const Variant& userdata /* = null_variant */) {
  if (!input.isArray()) {
    throw_expected_array_exception("array_walk");
    return false;
  }
  CallCtx ctx;
  CallerFrame cf;
  vm_decode_function(funcname, cf(), false, ctx);
  if (ctx.func == NULL) {
    return false;
  }
  Variant var(input, Variant::WithRefBind{});
  ArrayUtil::Walk(var, walk_func, &ctx, false, NULL, userdata);
  return true;
}

Project: hhvm
File: ext_array.cpp

bool HHVM_FUNCTION(arsort,
                   VRefParam array,
                   int sort_flags /* = 0 */) {
  bool use_zend_sort = RuntimeOption::EnableZendSorting;
  return php_asort(array, sort_flags, false, use_zend_sort);
}

Project: hhvm
File: ext_array.cpp

bool HHVM_FUNCTION(in_array,
                   const Variant& needle,
                   const Variant& haystack,
                   bool strict /* = false */) {
  bool ret = false;
  auto ok = strict ?
    IterateV(*haystack.asCell(),
             [](ArrayData*) { return false; },
             [&](const TypedValue* tv) -> bool {
               if (HPHP::same(tvAsCVarRef(tv), needle)) {
                 ret = true;
                 return true;
               }
               return false;
             },
             [](ObjectData*) { return false; }) :
    IterateV(*haystack.asCell(),
             [](ArrayData*) { return false; },
             [&](const TypedValue* tv) -> bool {
               if (HPHP::equal(tvAsCVarRef(tv), needle)) {
                 ret = true;
                 return true;
               }
               return false;
             },
             [](ObjectData*) { return false; });

  if (UNLIKELY(!ok)) {
    raise_warning("in_array() expects parameter 2 to be an array "
                  "or collection");
  }
  return ret;
}

Project: hhvm
File: ext_array.cpp

TypedValue HHVM_FUNCTION(array_udiff_assoc,
                         const Variant& array1,
                         const Variant& array2,
                         const Variant& data_compare_func,
                         const Array& args /* = null array */) {
  Variant func = data_compare_func;
  Array extra = args;
  if (!extra.empty()) {
    extra.prepend(func);
    func = extra.pop();
  }
  diff_intersect_body(diff, extra, true COMMA true COMMA NULL COMMA NULL
                      COMMA cmp_func COMMA &func);
}

Project: hhvm
File: ext_array.cpp

TypedValue HHVM_FUNCTION(array_pop,
                         VRefParam containerRef) {
  const auto* container = containerRef->asCell();
  if (UNLIKELY(!isMutableContainer(*container))) {
    raise_warning("array_pop() expects parameter 1 to be an "
                  "array or mutable collection");
    return make_tv<KindOfNull>();
  }
  if (!getContainerSize(containerRef)) {
    return make_tv<KindOfNull>();
  }
  if (isArrayType(container->m_type)) {
    if (auto ref = containerRef.getVariantOrNull()) {
      return tvReturn(ref->asArrRef().pop());
    }
    auto ad = container->m_data.parr;
    if (ad->size()) {
      auto last = ad->iter_last();
      return tvReturn(ad->getValue(last));
    }
    return make_tv<KindOfNull>();
  }
  assert(container->m_type == KindOfObject);
  return tvReturn(collections::pop(container->m_data.pobj));
}

Project: hhvm
File: ext_array.cpp

bool HHVM_FUNCTION(i18n_loc_set_strength,
                   int64_t strength) {
  return s_collator->setStrength(strength);
}

Project: hhvm
File: ext_array.cpp

Array HHVM_FUNCTION(HH_dict, const Array& arr) {
  return arr.toDict();
}

Project: hhvm
File: ext_array.cpp

Variant HHVM_FUNCTION(end,
                      VRefParam refParam) {
  return iter_op_impl(
    refParam,
    &ArrayData::end,
    s___end,
    false,
    &ArrayData::isTail
  );
}

Project: hhvm
File: ext_array.cpp

TypedValue HHVM_FUNCTION(array_diff_assoc,
                         const Variant& array1,
                         const Variant& array2,
                         const Array& args /* = null array */) {
  diff_intersect_body(diff, args, true COMMA true);
}

Project: hhvm
File: ext_array.cpp

  ArrayExtension() : Extension("array") {}

Project: hhvm
File: ext_array.cpp

bool HHVM_FUNCTION(ksort,
                   VRefParam array,
                   int sort_flags /* = 0 */) {
  bool use_zend_sort = RuntimeOption::EnableZendSorting;
  return php_ksort(array, sort_flags, true, use_zend_sort);
}

Project: hhvm
File: ext_array.cpp

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;
  }
}

Project: hhvm
File: ext_array.cpp

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:
      return 1;

    case KindOfPersistentArray:
    case KindOfArray:
      if ((CountMode)mode == CountMode::RECURSIVE) {
        const Array& arr_var = var.toCArrRef();
        return php_count_recursive(arr_var);
      }
      return var.getArrayData()->size();

    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, 0).toInt64();
        }
      }
      return 1;

    case KindOfRef:
    case KindOfClass:
      break;
  }
  not_reached();
}

Project: hhvm
File: ext_array.cpp

Variant HHVM_FUNCTION(pos,
                      VRefParam refParam) {
  return HHVM_FN(current)(refParam);
}

Project: hhvm
File: ext_array.cpp

php_ksort(VRefParam container, int sort_flags, bool ascending,
          bool use_zend_sort) {
  if (container.isArray()) {
    auto ref = container.getVariantOrNull();
    if (!ref) return true;
    Array& arr_array = ref->asArrRef();
    if (use_zend_sort) {
      return zend_ksort(*ref, sort_flags, ascending);
    }
    SortFunction sf = getSortFunction(SORTFUNC_KRSORT, ascending);
    ArraySortTmp ast(arr_array, 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) {
        HashCollection* hc = static_cast<HashCollection*>(obj);
        hc->ksort(sort_flags, ascending);
        return true;
      }
    }
  }
  throw_expected_array_or_collection_exception(ascending ? "ksort" : "krsort");
  return false;
}

Project: hhvm
File: ext_array.cpp

Variant HHVM_FUNCTION(each,
                      VRefParam refParam) {
  return iter_op_impl(
    refParam,
    &ArrayData::each,
    s___each,
    Variant::NullInit()
  );
}

Project: hhvm
File: ext_array.cpp

TypedValue HHVM_FUNCTION(array_slice,
                         TypedValue cell_input,
                         int64_t offset,
                         const Variant& length /* = null_variant */,
                         bool preserve_keys /* = false */) {
  if (UNLIKELY(!isContainer(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 = getContainerSize(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_tv<KindOfPersistentArray>(staticEmptyArray());
  }

  bool input_is_packed = isPackedContainer(cell_input);

  // If the slice covers the entire input container, we can just nop when
  // preserve_keys is true, or when preserve_keys is false but the container
  // is packed so we know the keys already map to [0,N].
  if (offset == 0 && len == num_in && (preserve_keys || input_is_packed)) {
    if (isArrayType(cell_input.m_type)) {
      return tvReturn(Variant(cell_input.m_data.parr));
    }
    return tvReturn(cell_input.m_data.pobj->toArray());
  }

  int pos = 0;
  ArrayIter iter(cell_input);
  for (; pos < offset && iter; ++pos, ++iter) {}

  if (input_is_packed && (offset == 0 || !preserve_keys)) {
    PackedArrayInit ret(len);
    for (; pos < (offset + len) && iter; ++pos, ++iter) {
      ret.appendWithRef(iter.secondRefPlus());
    }
    return tvReturn(ret.toVariant());
  }

  // Otherwise PackedArrayInit can't be used because non-numeric keys are
  // preserved even when preserve_keys is false
  Array ret = Array::attach(PackedArray::MakeReserve(len));
  for (; pos < (offset + len) && iter; ++pos, ++iter) {
    Variant key(iter.first());
    bool doAppend = !preserve_keys && key.isNumeric();
    const Variant& v = iter.secondRefPlus();
    if (doAppend) {
      ret.appendWithRef(v);
    } else {
      ret.setWithRef(key, v, true);
    }
  }
  return tvReturn(std::move(ret));
}

Project: hhvm
File: ext_array.cpp

TypedValue HHVM_FUNCTION(array_push,
                         VRefParam container,
                         const Variant& var,
                         const Array& args /* = null array */) {
  if (LIKELY(container->isArray())) {
    auto ref = container.getVariantOrNull();
    if (!ref) {
      return make_tv<KindOfInt64>(
        1 + args.size() + container->asCArrRef().size()
      );
    }

    /*
     * 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 = ref->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;
      }
    }
  }
  throw_expected_array_or_collection_exception("array_push");
  return make_tv<KindOfNull>();
}

Project: hhvm
File: ext_array.cpp

int64_t HHVM_FUNCTION(sizeof,
                      const Variant& var,
                      int64_t mode /* = 0 */) {
  return HHVM_FN(count)(var, mode);
}

Project: hhvm
File: ext_array.cpp

TypedValue* HHVM_FN(array_multisort)(ActRec* ar) {
  TypedValue* tv = getArg(ar, 0);
  if (tv == nullptr || !tvAsVariant(tv).isArray()) {
    throw_expected_array_exception("array_multisort");
    return arReturn(ar, false);
  }

  std::vector<Array::SortData> data;
  std::vector<Array> arrays;
  arrays.reserve(ar->numArgs()); // so no resize would happen

  Array::SortData sd;
  sd.original = &tvAsVariant(tv);
  arrays.push_back(Array(sd.original->getArrayData()));
  sd.array = &arrays.back();
  sd.by_key = false;

  int sort_flags = SORT_REGULAR;
  bool ascending = true;
  for (int i = 1; i < ar->numArgs(); i++) {
    tv = getArg(ar, i);
    if (tvAsVariant(tv).isArray()) {
      sd.cmp_func = get_cmp_func(sort_flags, ascending);
      data.push_back(sd);

      sort_flags = SORT_REGULAR;
      ascending = true;

      sd.original = &tvAsVariant(tv);
      arrays.push_back(Array(sd.original->getArrayData()));
      sd.array = &arrays.back();
    } else {
      int n = toInt32(getArg<KindOfInt64>(ar, i));
      if (n == SORT_ASC) {
      } else if (n == SORT_DESC) {
        ascending = false;
      } else {
        sort_flags = n;
      }
    }
  }

  sd.cmp_func = get_cmp_func(sort_flags, ascending);
  data.push_back(sd);

  return arReturn(ar, Array::MultiSort(data, true));
}

Project: hhvm
File: ext_array.cpp

  ArrayData* operator->() { return m_ad; }

Project: hhvm
File: ext_array.cpp

php_sort(VRefParam container, int sort_flags,
         bool ascending, bool use_zend_sort) {
  if (container.isArray()) {
    auto ref = container.getVariantOrNull();
    if (!ref) return true;
    Array& arr_array = ref->asArrRef();
    if (use_zend_sort) {
      return zend_sort(*ref, sort_flags, ascending);
    }
    SortFunction sf = getSortFunction(SORTFUNC_SORT, ascending);
    ArraySortTmp ast(arr_array, sf);
    ast->sort(sort_flags, ascending);
    return true;
  }
  if (container.isObject()) {
    ObjectData* obj = container.getObjectData();
    if (obj->isCollection() &&
        obj->collectionType() == CollectionType::Vector) {
      c_Vector* vec = static_cast<c_Vector*>(obj);
      vec->sort(sort_flags, ascending);
      return true;
    }
    // other collections are not supported:
    //  - Maps and Sets require associative sort
    //  - Immutable collections are not to be modified
  }
  throw_expected_array_or_collection_exception(ascending ? "sort" : "rsort");
  return false;
}

Project: hhvm
File: ext_array.cpp

bool HHVM_FUNCTION(sort,
                  VRefParam array,
                  int sort_flags /* = 0 */) {
  bool use_zend_sort = RuntimeOption::EnableZendSorting;
  return php_sort(array, sort_flags, true, use_zend_sort);
}

Project: hhvm
File: ext_array.cpp

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) {
    const Variant& entry(iter.secondRefPlus());

    switch (entry.getType()) {
      case KindOfUninit:
      case KindOfNull:
      case KindOfBoolean:
      case KindOfInt64:
      case KindOfRef:
        i *= entry.toInt64();
        continue;

      case KindOfDouble:
        goto DOUBLE;

      case KindOfPersistentString:
      case KindOfString: {
        int64_t ti;
        double td;
        if (entry.getStringData()->isNumericWithVal(ti, td, 1) ==
            KindOfInt64) {
          i *= ti;
          continue;
        } else {
          goto DOUBLE;
        }
      }

      case KindOfPersistentArray:
      case KindOfArray:
      case KindOfObject:
      case KindOfResource:
        continue;

      case KindOfClass:
        break;
    }
    not_reached();
  }
  return make_tv<KindOfInt64>(i);

DOUBLE:
  double d = i;
  for (; iter; ++iter) {
    const Variant& entry(iter.secondRefPlus());
    switch (entry.getType()) {
      DT_UNCOUNTED_CASE:
      case KindOfString:
      case KindOfRef:
        d *= entry.toDouble();

      case KindOfArray:
      case KindOfObject:
      case KindOfResource:
        continue;

      case KindOfClass:
        break;
    }
    not_reached();
  }
  return make_tv<KindOfDouble>(d);
}

Project: hhvm
File: ext_array.cpp

bool HHVM_FUNCTION(rsort,
                   VRefParam array,
                   int sort_flags /* = 0 */) {
  bool use_zend_sort = RuntimeOption::EnableZendSorting;
  return php_sort(array, sort_flags, false, use_zend_sort);
}

Project: hhvm
File: ext_array.cpp

bool HHVM_FUNCTION(uasort,
                   VRefParam container,
                   const Variant& cmp_function) {
  if (container.isArray()) {
    auto sort = [](Array& arr_array, const Variant& cmp_function) -> bool {
      if (RuntimeOption::EnableZendSorting) {
        arr_array.sort(cmp_func, false, false, &cmp_function);
        return true;
      } else {
        ArraySortTmp ast(arr_array, SORTFUNC_UASORT);
        return ast->uasort(cmp_function);
      }
    };
    auto ref = container.getVariantOrNull();
    if (LIKELY(ref != nullptr)) {
      return sort(ref->asArrRef(), cmp_function);
    }
    auto tmp = container->asCArrRef();
    return sort(tmp, cmp_function);
  }
  if (container.isObject()) {
    ObjectData* obj = container.getObjectData();
    if (obj->isCollection()) {
      auto type = obj->collectionType();
      if (type == CollectionType::Map || type == CollectionType::Set) {
        HashCollection* hc = static_cast<HashCollection*>(obj);
        return hc->uasort(cmp_function);
      }
    }
    // other collections are not supported:
    //  - Vectors require a non-associative sort
    //  - Immutable collections are not to be modified
  }
  throw_expected_array_or_collection_exception("uasort");
  return false;
}

Project: hhvm
File: ext_array.cpp

Array HHVM_FUNCTION(compact,
                    const Variant& varname,
                    const Array& args /* = null array */) {
  raise_disallowed_dynamic_call("compact should not be called dynamically");
  Array ret = Array::attach(PackedArray::MakeReserve(args.size() + 1));
  VarEnv* v = g_context->getOrCreateVarEnv();
  if (v) {
    PointerSet seen;
    compact(seen, v, ret, varname);
    if (!args.empty()) compact(seen, v, ret, args);
  }
  return ret;
}

Project: hhvm
File: ext_array.cpp

bool HHVM_FUNCTION(shuffle,
                   VRefParam array) {
  if (!array.isArray()) {
    throw_expected_array_exception("shuffle");
    return false;
  }
  array.assignIfRef(ArrayUtil::Shuffle(array));
  return true;
}

Project: hhvm
File: ext_array.cpp

bool HHVM_FUNCTION(uksort,
                   VRefParam container,
                   const Variant& cmp_function) {
  if (container.isArray()) {
    auto sort = [](Array& arr_array, const Variant& cmp_function) -> bool {
      ArraySortTmp ast(arr_array, SORTFUNC_UKSORT);
      return ast->uksort(cmp_function);
    };
    auto ref = container.getVariantOrNull();
    if (LIKELY(ref != nullptr)) {
      return sort(ref->asArrRef(), cmp_function);
    }
    auto tmp = container->asCArrRef();
    return sort(tmp, cmp_function);
  }
  if (container.isObject()) {
    ObjectData* obj = container.getObjectData();
    if (obj->isCollection()) {
      auto type = obj->collectionType();
      if (type == CollectionType::Map || type == CollectionType::Set) {
        HashCollection* hc = static_cast<HashCollection*>(obj);
        return hc->uksort(cmp_function);
      }
    }
    // other collections are not supported:
    //  - Vectors require a non-associative sort
    //  - Immutable collections are not to be modified
  }
  throw_expected_array_or_collection_exception("uksort");
  return false;
}

Project: hhvm
File: ext_array.cpp

bool HHVM_FUNCTION(i18n_loc_set_default,
                   const String& locale) {
  return s_collator->setLocale(locale);
}

Project: hhvm
File: ext_array.cpp

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.secondRefPlus().asCell();
    addToSetHelper(st, c, strTv, true);
  }
}

Project: hhvm
File: ext_array.cpp

static inline TypedValue* makeContainerListHelper(const Variant& a,
                                                  const Array& argv,
                                                  int count,
                                                  int smallestPos) {
  assert(count == argv.size() + 1);
  assert(0 <= smallestPos);
  assert(smallestPos < count);
  // Allocate a TypedValue array and copy 'a' and the contents of 'argv'
  TypedValue* containers = req::make_raw_array<TypedValue>(count);
  tvCopy(*a.asCell(), containers[0]);
  int pos = 1;
  for (ArrayIter argvIter(argv); argvIter; ++argvIter, ++pos) {
    const auto& c = *argvIter.secondRef().asCell();
    tvCopy(c, 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;
}

Project: hhvm
File: ext_array.cpp

static void php_array_replace_recursive(PointerSet &seen, bool check,
                                        Array &arr1, const Array& arr2) {
  if (arr1.get() == arr2.get()) {
    // This is an optimization, but it also avoids an assert in
    // setWithRef (Variant::setWithRef asserts that its source
    // and destination are not the same).
    // If the arrays are self recursive, this does change the behavior
    // slightly - it skips the "recursion detected" warning.
    return;
  }

  if (check && !seen.insert((void*)arr1.get()).second) {
    raise_warning("array_replace_recursive(): recursion detected");
    return;
  }

  for (ArrayIter iter(arr2); iter; ++iter) {
    Variant key = iter.first();
    const Variant& value = iter.secondRef();
    if (arr1.exists(key, true) && value.isArray()) {
      Variant &v = arr1.lvalAt(key, AccessFlags::Key);
      if (v.isArray()) {
        Array subarr1 = v.toArray();
        const ArrNR& arr_value = value.toArrNR();
        php_array_replace_recursive(seen, couldRecur(v, subarr1.get()),
                                    subarr1, arr_value);
        v = subarr1;
      } else {
        arr1.set(key, value, true);
      }
    } else {
      arr1.setWithRef(key, value, true);
    }
  }

  if (check) {
    seen.erase((void*)arr1.get());
  }
}

Project: hhvm
File: ext_array.cpp

TypedValue HHVM_FUNCTION(array_change_key_case,
                         ArrayArg input,
                         int64_t case_ /* = 0 */) {
  return tvReturn(ArrayUtil::ChangeKeyCase(ArrNR(input.get()),
                                           (CaseMode)case_ == CaseMode::LOWER));
}

Project: hhvm
File: ext_array.cpp

TypedValue HHVM_FUNCTION(array_merge,
                         int64_t numArgs,
                         ArrayArg array1,
                         const Variant& array2 /* = null_variant */,
                         const Array& args /* = null array */) {
  Array ret = Array::attach(MixedArray::MakeReserveLike(array1.get(), 0));
  php_array_merge(ret, ArrNR(array1.get()));

  if (UNLIKELY(numArgs < 2)) return tvReturn(std::move(ret));

  getCheckedArrayRet(array2, make_tv<KindOfNull>());
  php_array_merge(ret, arr_array2);

  for (ArrayIter iter(args); iter; ++iter) {
    Variant v = iter.second();
    if (!v.isArray()) {
      throw_expected_array_exception("array_merge");
      return make_tv<KindOfNull>();
    }
    const Array& arr_v = v.asCArrRef();
    php_array_merge(ret, arr_v);
  }
  return tvReturn(std::move(ret));
}

Project: hhvm
File: ext_array.cpp

  bool setStrength(int64_t strength) {
    if (!m_ucoll) {
      Logger::Verbose("m_ucoll is NULL");
      return false;
    }
    ucol_setStrength(m_ucoll, (UCollationStrength)strength);
    return true;
  }

Project: hhvm
File: ext_array.cpp

TypedValue HHVM_FUNCTION(array_diff_key,
                         const Variant& container1,
                         const Variant& container2,
                         const Array& args /* = null array */) {
  ARRAY_DIFF_PRELUDE()
  // If we're only dealing with two containers and if they are both arrays,
  // we can avoid creating an intermediate Set
  if (!moreThanTwo && isArrayType(c1.m_type) && isArrayType(c2.m_type)) {
    auto ad2 = c2.m_data.parr;
    for (ArrayIter iter(container1); iter; ++iter) {
      auto key = iter.first();
      const auto& c = *key.asCell();
      if (c.m_type == KindOfInt64) {
        if (ad2->exists(c.m_data.num)) continue;
      } else {
        assert(isStringType(c.m_type));
        if (ad2->exists(c.m_data.pstr)) continue;
      }
      ret.setWithRef(key, iter.secondRefPlus(), true);
    }
    return tvReturn(std::move(ret));
  }
  // Put all of the keys 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);
  containerKeysToSetHelper(st, container2);
  if (UNLIKELY(moreThanTwo)) {
    for (ArrayIter argvIter(args); argvIter; ++argvIter) {
      const auto& container = argvIter.secondRef();
      containerKeysToSetHelper(st, container);
    }
  }
  // Loop over container1, only copying over key/value pairs where the key is
  // not present in the Set. When checking if a key is present in the Set, any
  // key 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 isKey = isArrayType(c1.m_type);
  for (ArrayIter iter(container1); iter; ++iter) {
    auto key = iter.first();
    const auto& c = *key.asCell();
    if (checkSetHelper(st, c, strTv, !isKey)) continue;
    ret.setWithRef(key, iter.secondRefPlus(), isKey);
  }
  return tvReturn(std::move(ret));
}

Project: hhvm
File: ext_array.cpp

  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;
  }

Project: hhvm
File: ext_array.cpp

static inline bool checkSetHelper(const req::ptr<c_Set>& st,
                                  const Cell 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 = tvCastToString(&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);
}

Project: hhvm
File: ext_array.cpp

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 */) {
  Variant data_func = data_compare_func;
  Variant key_func = key_compare_func;
  Array extra = args;
  if (!extra.empty()) {
    extra.prepend(key_func);
    extra.prepend(data_func);
    key_func = extra.pop();
    data_func = extra.pop();
  }
  diff_intersect_body(diff, extra, true
                      COMMA true COMMA cmp_func COMMA &key_func
                      COMMA cmp_func COMMA &data_func);
}

Project: hhvm
File: ext_array.cpp

TypedValue HHVM_FUNCTION(array_fill_keys,
                         const Variant& keys,
                         const Variant& value) {
  folly::Optional<ArrayInit> ai;
  auto ok = IterateV(*keys.asCell(),
                     [&](ArrayData* adata) {
                       ai.emplace(adata->size(), ArrayInit::Mixed{});
                     },
                     [&](const TypedValue* tv) {
                       auto& key = tvAsCVarRef(tv);
                       if (key.isInteger() || key.isString()) {
                         ai->setUnknownKey(key, value);
                       } else {
                         raise_hack_strict(RuntimeOption::StrictArrayFillKeys,
                                           "strict_array_fill_keys",
                                           "keys must be ints or strings");
                         ai->setUnknownKey(key.toString(), value);
                       }
                     },
                     [&](ObjectData* coll) {
                       if (coll->collectionType() == CollectionType::Pair) {
                         ai.emplace(2, ArrayInit::Mixed{});
                       }
                     });

  if (!ok) {
    raise_warning("Invalid operand type was used: array_fill_keys expects "
                  "an array or collection");
    return make_tv<KindOfNull>();
  }
  assert(ai.hasValue());
  return tvReturn(ai->toVariant());
}

Project: hhvm
File: ext_array.cpp

TypedValue HHVM_FUNCTION(array_splice,
                         VRefParam input,
                         int offset,
                         const Variant& length,
                         const Variant& replacement) {
  return tvReturn(array_splice(input, offset, length, replacement));
}

Project: hhvm
File: ext_array.cpp

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) {
    const Variant& entry(iter.secondRefPlus());

    switch (entry.getType()) {
      case KindOfUninit:
      case KindOfNull:
      case KindOfBoolean:
      case KindOfInt64:
      case KindOfRef:
        i += entry.toInt64();
        continue;

      case KindOfDouble:
        goto DOUBLE;

      case KindOfPersistentString:
      case KindOfString: {
        int64_t ti;
        double td;
        if (entry.getStringData()->isNumericWithVal(ti, td, 1) ==
            KindOfInt64) {
          i += ti;
          continue;
        } else {
          goto DOUBLE;
        }
      }

      case KindOfPersistentArray:
      case KindOfArray:
      case KindOfObject:
      case KindOfResource:
        continue;

      case KindOfClass:
        break;
    }
    not_reached();
  }
  return make_tv<KindOfInt64>(i);

DOUBLE:
  double d = i;
  for (; iter; ++iter) {
    const Variant& entry(iter.secondRefPlus());
    switch (entry.getType()) {
      DT_UNCOUNTED_CASE:
      case KindOfString:
      case KindOfRef:
        d += entry.toDouble();

      case KindOfArray:
      case KindOfObject:
      case KindOfResource:
        continue;

      case KindOfClass:
        break;
    }
    not_reached();
  }
  return make_tv<KindOfDouble>(d);
}

Project: hhvm
File: ext_array.cpp

bool HHVM_FUNCTION(asort,
                   VRefParam array,
                   int sort_flags /* = 0 */) {
  bool use_zend_sort = RuntimeOption::EnableZendSorting;
  return php_asort(array, sort_flags, true, use_zend_sort);
}

Project: hhvm
File: ext_array.cpp

Variant HHVM_FUNCTION(current,
                      VRefParam refParam) {
  return iter_op_impl<NoCow>(
    refParam,
    &ArrayData::current,
    s___current,
    false
  );
}

Project: hhvm
File: ext_array.cpp

TypedValue HHVM_FUNCTION(array_shift,
                         VRefParam array) {
  const auto* cell_array = array->asCell();
  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(array)) {
    return make_tv<KindOfNull>();
  }
  if (isArrayType(cell_array->m_type)) {
    if (auto ref = array.getVariantOrNull()) {
      return tvReturn(ref->asArrRef().dequeue());
    }
    auto ad = cell_array->m_data.parr;
    if (ad->size()) {
      auto first = ad->iter_begin();
      return tvReturn(ad->getValue(first));
    }
    return make_tv<KindOfNull>();
  }
  assertx(cell_array->m_type == KindOfObject);
  return tvReturn(collections::shift(cell_array->m_data.pobj));
}

Project: hhvm
File: ext_array.cpp

TypedValue HHVM_FUNCTION(array_merge_recursive,
                         int64_t numArgs,
                         const Variant& array1,
                         const Variant& array2 /* = null_variant */,
                         const Array& args /* = null array */) {
  getCheckedArray(array1);
  auto in1 = array1.asCArrRef();
  auto ret = Array::attach(MixedArray::MakeReserveLike(in1.get(), 0));
  PointerSet seen;
  php_array_merge_recursive(seen, false, ret, arr_array1);
  assert(seen.empty());

  if (UNLIKELY(numArgs < 2)) return tvReturn(std::move(ret));

  getCheckedArray(array2);
  php_array_merge_recursive(seen, false, ret, arr_array2);
  assert(seen.empty());

  for (ArrayIter iter(args); iter; ++iter) {
    Variant v = iter.second();
    if (!v.isArray()) {
      throw_expected_array_exception("array_merge_recursive");
      return make_tv<KindOfNull>();
    }
    const Array& arr_v = v.asCArrRef();
    php_array_merge_recursive(seen, false, ret, arr_v);
    assert(seen.empty());
  }
  return tvReturn(std::move(ret));
}

Project: hhvm
File: ext_array.cpp

static Variant iter_op_impl(VRefParam refParam, OpPtr op, const String& objOp,
                            NonArrayRet nonArray,
                            bool(ArrayData::*pred)() const =
                              &ArrayData::isInvalid) {
  auto& cell = *refParam.wrapped().asCell();
  if (!isArrayType(cell.m_type)) {
    if (cell.m_type == KindOfObject) {
      auto obj = refParam.wrapped().toObject();
      if (obj->instanceof(SystemLib::s_ArrayObjectClass)) {
        return obj->o_invoke_few_args(objOp, 0);
      }
    }
    throw_bad_type_exception("expecting an array");
    return Variant(nonArray);
  }

  auto ad = cell.m_data.parr;
  auto constexpr doCow = !std::is_same<DoCow, NoCow>::value;
  if (doCow && ad->cowCheck() && !(ad->*pred)() &&
      !ad->noCopyOnWrite()) {
    ad = ad->copy();
    if (LIKELY(refParam.isRefData()))
      cellMove(make_tv<KindOfArray>(ad), *refParam.getRefData()->tv());
    else {
      req::ptr<ArrayData> tmp(ad, req::ptr<ArrayData>::NoIncRef{});
      return (ad->*op)();
    }
  }
  return (ad->*op)();
}

Project: hhvm
File: ext_array.cpp

TypedValue HHVM_FUNCTION(array_intersect_uassoc,
                         const Variant& array1,
                         const Variant& array2,
                         const Variant& key_compare_func,
                         const Array& args /* = null array */) {
  Variant func = key_compare_func;
  Array extra = args;
  if (!extra.empty()) {
    extra.prepend(func);
    func = extra.pop();
  }
  diff_intersect_body(intersect, extra, true COMMA true
                      COMMA cmp_func COMMA &func);
}

Project: hhvm
File: ext_array.cpp

static bool couldRecur(const Variant& v, const ArrayData* arr) {
  return v.isReferenced() ||
    arr->kind() == ArrayData::kGlobalsKind ||
    arr->kind() == ArrayData::kProxyKind;
}

Project: hhvm
File: ext_array.cpp

TypedValue HHVM_FUNCTION(array_intersect_ukey,
                         const Variant& array1,
                         const Variant& array2,
                         const Variant& key_compare_func,
                         const Array& args /* = null array */) {
  Variant func = key_compare_func;
  Array extra = args;
  if (!extra.empty()) {
    extra.prepend(func);
    func = extra.pop();
  }
  diff_intersect_body(intersect, extra, true COMMA false
                      COMMA cmp_func COMMA &func);
}

Project: hhvm
File: ext_array.cpp

  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);
    }
    assert(m_ucoll);
  }

Project: hhvm
File: ext_array.cpp

TypedValue HHVM_FUNCTION(array_diff_uassoc,
                         const Variant& array1,
                         const Variant& array2,
                         const Variant& key_compare_func,
                         const Array& args /* = null array */) {
  Variant func = key_compare_func;
  Array extra = args;
  if (!extra.empty()) {
    extra.prepend(func);
    func = extra.pop();
  }
  diff_intersect_body(diff, extra, true COMMA true COMMA cmp_func COMMA &func);
}

Project: hhvm
File: ext_array.cpp

static inline void addToIntersectMapHelper(const req::ptr<c_Map>& mp,
                                           const Cell 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 = tvCastToString(&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);
    }
  }
}

Project: hhvm
File: ext_array.cpp

TypedValue HHVM_FUNCTION(array_combine,
                         const Variant& keys,
                         const Variant& values) {
  const auto& cell_keys = *keys.asCell();
  const auto& cell_values = *values.asCell();
  if (UNLIKELY(!isContainer(cell_keys) || !isContainer(cell_values))) {
    raise_warning("Invalid operand type was used: array_combine expects "
                  "arrays or collections");
    return make_tv<KindOfNull>();
  }
  auto keys_size = getContainerSize(cell_keys);
  if (UNLIKELY(keys_size != getContainerSize(cell_values))) {
    raise_warning("array_combine(): Both parameters should have an equal "
                  "number of elements");
    return make_tv<KindOfBoolean>(false);
  }
  Array ret = Array::attach(MixedArray::MakeReserveMixed(keys_size));
  for (ArrayIter iter1(cell_keys), iter2(cell_values);
       iter1; ++iter1, ++iter2) {
    const Variant& key = iter1.secondRefPlus();
    if (key.isInteger() || key.isString()) {
      ret.setWithRef(key, iter2.secondRefPlus());
    } else {
      ret.setWithRef(key.toString(), iter2.secondRefPlus());
    }
  }
  return tvReturn(std::move(ret));
}

Project: hhvm
File: ext_array.cpp

static void containerKeysIntersectHelper(const req::ptr<c_Set>& st,
                                         TypedValue* containers,
                                         int count) {
  assert(count >= 2);
  auto mp = req::make<c_Map>();
  Variant strHolder(empty_string_variant());
  TypedValue* strTv = strHolder.asTypedValue();
  TypedValue intOneTv = make_tv<KindOfInt64>(1);
  bool isKey = isArrayType(containers[0].m_type);
  for (ArrayIter iter(tvAsCVarRef(&containers[0])); iter; ++iter) {
    auto key = iter.first();
    const auto& c = *key.asCell();
    // 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, !isKey);
  }
  for (int pos = 1; pos < count; ++pos) {
    isKey = isArrayType(containers[pos].m_type);
    for (ArrayIter iter(tvAsCVarRef(&containers[pos])); iter; ++iter) {
      auto key = iter.first();
      const auto& c = *key.asCell();
      updateIntersectMapHelper(mp, c, pos, strTv, !isKey);
    }
  }
  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).
    const auto& val = *iter.secondRefPlus().asCell();
    assert(val.m_type == KindOfInt64);
    if (val.m_data.num == count) {
      st->add(iter.first().asCell());
    }
  }
}

Project: hhvm
File: ext_array.cpp

  String getLocale() {
    return m_locale;
  }

Project: hhvm
File: ext_array.cpp

Variant HHVM_FUNCTION(key,
                      VRefParam refParam) {
  return iter_op_impl<NoCow>(
    refParam,
    &ArrayData::key,
    s___key,
    false
  );
}

Project: hhvm
File: ext_array.cpp

  Intl::IntlError &getErrorRef() {
    return m_errcode;
  }

Project: hhvm
File: ext_array.cpp

TypedValue HHVM_FUNCTION(array_udiff,
                         const Variant& array1,
                         const Variant& array2,
                         const Variant& data_compare_func,
                         const Array& args /* = null array */) {
  Variant func = data_compare_func;
  Array extra = args;
  if (!extra.empty()) {
    extra.prepend(func);
    func = extra.pop();
  }
  diff_intersect_body(diff, extra, false COMMA true COMMA NULL COMMA NULL
                      COMMA cmp_func COMMA &func);
}

Project: hhvm
File: ext_array.cpp

Array HHVM_FUNCTION(__SystemLib_compact_sl,
                    const Variant& varname,
                    const Array& args /* = null array */) {
  Array ret = Array::attach(PackedArray::MakeReserve(args.size() + 1));
  VarEnv* v = g_context->getOrCreateVarEnv();
  if (v) {
    PointerSet seen;
    compact(seen, v, ret, varname);
    if (!args.empty()) compact(seen, v, ret, args);
  }
  return ret;
}

Project: hhvm
File: ext_array.cpp

  explicit ArraySortTmp(Array& arr, SortFunction sf) : m_arr(arr) {
    m_ad = arr.get()->escalateForSort(sf);
    assert(m_ad == arr.get() || m_ad->hasExactlyOneRef());
  }

Project: hhvm
File: ext_array.cpp

  void requestShutdown() override {
    m_locale.reset();
    m_errcode.clearError(false);
    if (m_ucoll) {
      ucol_close(m_ucoll);
      m_ucoll = NULL;
    }
  }

Project: hhvm
File: ext_array.cpp

TypedValue HHVM_FUNCTION(array_pad,
                         const Variant& input,
                         int pad_size,
                         const Variant& pad_value) {
  getCheckedArray(input);
  if (pad_size > 0) {
    return tvReturn(ArrayUtil::Pad(arr_input, pad_value, pad_size, true));
  }
  return tvReturn(ArrayUtil::Pad(arr_input, pad_value, -pad_size, false));
}

Project: hhvm
File: ext_array.cpp

  Variant getErrorCode() {
    if (!m_ucoll) {
      Logger::Verbose("m_ucoll is NULL");
      return false;
    }
    return m_errcode.getErrorCode();
  }

Project: hhvm
File: ext_array.cpp

bool HHVM_FUNCTION(array_walk_recursive,
                   VRefParam input,
                   const Variant& funcname,
                   const Variant& userdata /* = null_variant */) {
  if (!input.isArray()) {
    throw_expected_array_exception("array_walk_recursive");
    return false;
  }
  CallCtx ctx;
  CallerFrame cf;
  vm_decode_function(funcname, cf(), false, ctx);
  if (ctx.func == NULL) {
    return false;
  }
  PointerSet seen;
  Variant var(input, Variant::WithRefBind{});
  ArrayUtil::Walk(var, walk_func, &ctx, true, &seen, userdata);
  return true;
}

Project: hhvm
File: ext_array.cpp

bool HHVM_FUNCTION(natsort, VRefParam array) {
  return php_asort(array, SORT_NATURAL, true, false);
}

Project: hhvm
File: ext_array.cpp

TypedValue HHVM_FUNCTION(array_values,
                         const Variant& input) {
  return tvReturn(array_values(input));
}

Project: hhvm
File: ext_array.cpp

static void php_array_merge_recursive(PointerSet &seen, bool check,
                                      Array &arr1, const Array& arr2) {
  if (check && !seen.insert((void*)arr1.get()).second) {
    raise_warning("array_merge_recursive(): recursion detected");
    return;
  }

  for (ArrayIter iter(arr2); iter; ++iter) {
    Variant key(iter.first());
    const Variant& value(iter.secondRef());
    if (key.isNumeric()) {
      arr1.appendWithRef(value);
    } else if (arr1.exists(key, true)) {
      // There is no need to do toKey() conversion, for a key that is already
      // in the array.
      Variant &v = arr1.lvalAt(key, AccessFlags::Key);
      auto subarr1 = v.toArray().copy();
      php_array_merge_recursive(seen,
                                couldRecur(v, subarr1.get()),
                                subarr1,
                                value.toArray());
      v.unset(); // avoid contamination of the value that was strongly bound
      v = subarr1;
    } else {
      arr1.setWithRef(key, value, true);
    }
  }

  if (check) {
    seen.erase((void*)arr1.get());
  }
}

Project: hhvm
File: ext_array.cpp

static void walk_func(Variant& value,
                      const Variant& key,
                      const Variant& userdata,
                      const void *data) {
  CallCtx* ctx = (CallCtx*)data;
  Variant sink;
  int nargs = userdata.isInitialized() ? 3 : 2;
  TypedValue args[3] = { *value.asRef(), *key.asCell(), *userdata.asCell() };
  g_context->invokeFuncFew(sink.asTypedValue(), *ctx, nargs, args);
}

Project: hhvm
File: ext_array.cpp

static int cmp_func(const Variant& v1, const Variant& v2, const void *data) {
  Variant *callback = (Variant *)data;
  return vm_call_user_func(*callback, make_packed_array(v1, v2)).toInt32();
}

Project: hhvm
File: ext_array.cpp

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;
}

Project: hhvm
File: ext_array.cpp

String HHVM_FUNCTION(i18n_loc_get_default) {
  return s_collator->getLocale();
}

Project: hhvm
File: ext_array.cpp

Variant HHVM_FUNCTION(reset,
                      VRefParam refParam) {
  return iter_op_impl(
    refParam,
    &ArrayData::reset,
    s___reset,
    false,
    &ArrayData::isHead
  );
}

Project: hhvm
File: ext_array.cpp

bool HHVM_FUNCTION(natcasesort, VRefParam array) {
  return php_asort(array, SORT_NATURAL_CASE, true, false);
}

Project: hhvm
File: ext_array.cpp

static inline void addToSetHelper(const req::ptr<c_Set>& st,
                                  const Cell 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 = tvCastToString(&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);
    }
  }
}

Project: hhvm
File: ext_array.cpp

Variant array_search(const Variant& needle,
                     const Variant& haystack,
                     bool strict /* = false */) {
  Variant ret = false;
  auto ok = strict ?
    IterateKV(*haystack.asCell(),
              [](ArrayData*) { return false; },
              [&](const TypedValue* key, const TypedValue* tv) -> bool {
                if (HPHP::same(tvAsCVarRef(tv), needle)) {
                  ret = tvAsCVarRef(key);
                  return true;
                }
                return false;
              },
              [](ObjectData*) { return false; }) :
    IterateKV(*haystack.asCell(),
              [](ArrayData*) { return false; },
              [&](const TypedValue* key, const TypedValue* tv) -> bool {
                if (HPHP::equal(tvAsCVarRef(tv), needle)) {
                  ret = tvAsCVarRef(key);
                  return true;
                }
                return false;
              },
              [](ObjectData*) { return false; });

  if (UNLIKELY(!ok)) {
    raise_warning("array_search() expects parameter 2 to be an array "
                  "or collection");
    return init_null();
  }

  return ret;
}

Project: hhvm
File: ext_array.cpp

TypedValue HHVM_FUNCTION(array_intersect_assoc,
                         const Variant& array1,
                         const Variant& array2,
                         const Array& args /* = null array */) {
  diff_intersect_body(intersect, args, true COMMA true);
}

Project: hhvm
File: ext_array.cpp

TypedValue HHVM_FUNCTION(array_diff_ukey,
                         const Variant& array1,
                         const Variant& array2,
                         const Variant& key_compare_func,
                         const Array& args /* = null array */) {
  Variant func = key_compare_func;
  Array extra = args;
  if (!extra.empty()) {
    extra.prepend(func);
    func = extra.pop();
  }
  diff_intersect_body(diff, extra, true COMMA false COMMA cmp_func COMMA &func);
}

Project: hhvm
File: ext_array.cpp

TypedValue HHVM_FUNCTION(array_search,
                         const Variant& needle,
                         const Variant& haystack,
                         bool strict /* = false */) {
  return tvReturn(array_search(needle, haystack, strict));
}

Project: hhvm
File: ext_array.cpp

TypedValue HHVM_FUNCTION(array_chunk,
                         const Variant& input,
                         int chunkSize,
                         bool preserve_keys /* = false */) {
  const auto& cellInput = *input.asCell();
  if (UNLIKELY(!isContainer(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) {
    throw_invalid_argument("size: %d", chunkSize);
    return make_tv<KindOfNull>();
  }

  auto const retSize = (getContainerSize(cellInput) / chunkSize) + 1;
  PackedArrayInit ret(retSize);
  Array chunk;
  int current = 0;
  for (ArrayIter iter(cellInput); iter; ++iter) {
    if (preserve_keys) {
      chunk.setWithRef(iter.first(), iter.secondRefPlus(), true);
    } else {
      chunk.appendWithRef(iter.secondRefPlus());
    }
    if ((++current % chunkSize) == 0) {
      ret.append(chunk);
      chunk.clear();
    }
  }
  if (!chunk.empty()) {
    ret.append(chunk);
  }

  return tvReturn(ret.toVariant());
}

Project: hhvm
File: ext_array.cpp

static void php_array_replace(Array &arr1, const Array& arr2) {
  for (ArrayIter iter(arr2); iter; ++iter) {
    Variant key = iter.first();
    const Variant& value = iter.secondRef();
    arr1.setWithRef(key, value, true);
  }
}

Project: hhvm
File: ext_array.cpp

TypedValue HHVM_FUNCTION(array_unshift,
                         VRefParam array,
                         const Variant& var,
                         const Array& args /* = null array */) {
  const auto* cell_array = array->asCell();
  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>();
  }
  if (isArrayType(cell_array->m_type)) {
    auto ref_array = array.getVariantOrNull();
    if (!ref_array) {
      return make_tv<KindOfInt64>(
        cell_array->m_data.parr->size() + args.size() + 1
      );
    }
    if (cell_array->m_data.parr->isVectorData()) {
      if (!args.empty()) {
        auto pos_limit = args->iter_end();
        for (ssize_t pos = args->iter_last(); pos != pos_limit;
             pos = args->iter_rewind(pos)) {
          ref_array->asArrRef().prepend(args->getValueRef(pos));
        }
      }
      ref_array->asArrRef().prepend(var);
    } else {
      {
        Array newArray;
        newArray.append(var);
        if (!args.empty()) {
          auto pos_limit = args->iter_end();
          for (ssize_t pos = args->iter_begin(); pos != pos_limit;
               pos = args->iter_advance(pos)) {
            newArray.append(args->getValueRef(pos));
          }
        }
        for (ArrayIter iter(array.toArray()); iter; ++iter) {
          Variant key(iter.first());
          const Variant& value(iter.secondRef());
          if (key.isInteger()) {
            newArray.appendWithRef(value);
          } else {
            newArray.setWithRef(key, value, true);
          }
        }
        *ref_array = std::move(newArray);
      }
      // Reset the array's internal pointer
      ref_array->asArrRef()->reset();
    }
    return make_tv<KindOfInt64>(ref_array->asArrRef().size());
  }
  // Handle collections
  assert(cell_array->m_type == KindOfObject);
  auto* obj = cell_array->m_data.pobj;
  assert(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->getValueRef(pos).asCell());
        }
      }
      vec->addFront(var.asCell());
      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->getValueRef(pos).asCell());
        }
      }
      st->addFront(var.asCell());
      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>();
}

Project: hhvm
File: ext_array.cpp

Variant HHVM_FUNCTION(prev,
                      VRefParam refParam) {
  return iter_op_impl(
    refParam,
    &ArrayData::prev,
    s___prev,
    false
  );
}

Project: hhvm
File: ext_array.cpp

static void containerValuesIntersectHelper(const req::ptr<c_Set>& st,
                                           TypedValue* containers,
                                           int count) {
  assert(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) {
    const auto& c = *iter.secondRefPlus().asCell();
    // 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) {
      const auto& c = *iter.secondRefPlus().asCell();
      // 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).
    const auto& val = *iter.secondRefPlus().asCell();
    assert(val.m_type == KindOfInt64);
    if (val.m_data.num == count) {
      st->add(iter.first().asCell());
    }
  }
}

Project: hhvm
File: ext_array.cpp

TypedValue HHVM_FUNCTION(array_column,
                         ArrayArg input,
                         const Variant& val_key,
                         const Variant& idx_key /* = null_variant */) {

  ArrNR arr_input(input.get());
  Variant val = val_key, idx = idx_key;
  if (!array_column_coerce_key(val, "column") ||
      !array_column_coerce_key(idx, "index")) {
    return make_tv<KindOfBoolean>(false);
  }
  ArrayInit ret(input->size(), ArrayInit::Map{});
  for(auto it = arr_input.asArray().begin(); !it.end(); it.next()) {
    if (!it.second().isArray()) {
      continue;
    }
    Array sub = it.second().toArray();

    Variant elem;
    if (val.isNull()) {
      elem = sub;
    } else if (sub.exists(val)) {
      elem = sub[val];
    } else {
      // skip subarray without named element
      continue;
    }

    if (idx.isNull() || !sub.exists(idx)) {
      ret.append(elem);
    } else if (sub[idx].isObject()) {
      ret.setUnknownKey(sub[idx].toString(), elem);
    } else {
      ret.setUnknownKey(sub[idx], elem);
    }
  }
  return tvReturn(ret.toVariant());
}

Project: hhvm
File: ext_array.cpp

TypedValue HHVM_FUNCTION(array_unique,
                         const Variant& array,
                         int sort_flags /* = 2 */) {
  // NOTE, PHP array_unique accepts ArrayAccess objects as well,
  // which is not supported here.
  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));
  }
}