Add sub-controls for Hack array compat runtime checks
[hiphop-php.git] / hphp / runtime / base / array-common.cpp
blob1429510e0ba2fbdfd1760989c71952e74901d533
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
17 #include "hphp/runtime/base/array-common.h"
19 #include "hphp/runtime/base/array-data.h"
20 #include "hphp/runtime/base/array-data-defs.h"
21 #include "hphp/runtime/base/array-init.h"
22 #include "hphp/runtime/base/array-iterator.h"
23 #include "hphp/runtime/base/mixed-array-defs.h"
24 #include "hphp/runtime/base/object-data.h"
25 #include "hphp/runtime/base/set-array.h"
26 #include "hphp/runtime/base/packed-array.h"
27 #include "hphp/runtime/base/type-variant.h"
29 namespace HPHP {
31 //////////////////////////////////////////////////////////////////////
33 ssize_t ArrayCommon::ReturnInvalidIndex(const ArrayData*) {
34 return 0;
37 bool ArrayCommon::ValidMArrayIter(const ArrayData* ad, const MArrayIter& fp) {
38 assert(fp.getContainer() == ad);
39 if (fp.getResetFlag()) return false;
40 if (ad->hasPackedLayout()) {
41 assert(PackedArray::checkInvariants(ad));
42 return fp.m_pos != ad->getSize();
43 } else if (ad->isKeyset()) {
44 return false;
45 } else {
46 assert(MixedArray::asMixed(ad));
47 return fp.m_pos != MixedArray::asMixed(ad)->iterLimit();
51 ArrayData* ArrayCommon::Pop(ArrayData* a, Variant &value) {
52 if (!a->empty()) {
53 auto const pos = a->iter_last();
54 value = a->getValue(pos);
55 return a->remove(a->getKey(pos), a->cowCheck());
57 value = uninit_null();
58 return a;
61 ArrayData* ArrayCommon::Dequeue(ArrayData* a, Variant &value) {
62 if (!a->empty()) {
63 auto const pos = a->iter_begin();
64 value = a->getValue(pos);
65 auto const ret = a->remove(a->getKey(pos), a->cowCheck());
66 // In PHP, array_shift() will cause all numerically key-ed values re-keyed
67 ret->renumber();
68 return ret;
70 value = uninit_null();
71 return a;
74 ArrayData* ArrayCommon::ToVec(ArrayData* a, bool) {
75 auto const size = a->size();
76 if (!size) return staticEmptyVecArray();
77 VecArrayInit init{size};
78 IterateV(
80 [&](TypedValue v) {
81 if (UNLIKELY(v.m_type == KindOfRef)) {
82 if (v.m_data.pref->isReferenced()) {
83 throwRefInvalidArrayValueException(init.toArray());
86 init.append(v);
89 return init.create();
92 ArrayData* ArrayCommon::ToDict(ArrayData* a, bool) {
93 auto const size = a->size();
94 if (!size) return staticEmptyDictArray();
95 DictInit init{size};
96 IterateKV(
98 [&](Cell k, TypedValue v) {
99 if (UNLIKELY(v.m_type == KindOfRef)) {
100 if (v.m_data.pref->isReferenced()) {
101 throwRefInvalidArrayValueException(init.toArray());
104 init.setValidKey(k, v);
107 return init.create();
110 ArrayData* ArrayCommon::ToKeyset(ArrayData* a, bool) {
111 auto const size = a->size();
112 if (!size) return staticEmptyKeysetArray();
113 KeysetInit init{size};
114 IterateV(
116 [&](TypedValue v) {
117 if (UNLIKELY(v.m_type == KindOfRef)) {
118 if (v.m_data.pref->isReferenced()) {
119 throwRefInvalidArrayValueException(init.toArray());
121 v = *v.m_data.pref->tv();
122 assertx(v.m_type != KindOfRef);
125 if (LIKELY(isStringType(v.m_type))) {
126 init.add(v.m_data.pstr);
127 } else if (LIKELY(isIntType(v.m_type))) {
128 init.add(v.m_data.num);
129 } else {
130 throwInvalidArrayKeyException(&v, init.toArray().get());
134 return init.create();
137 ArrayData* ArrayCommon::ToVArray(ArrayData* a, bool) {
138 if (a->isVArray()) return a;
139 auto const size = a->size();
140 if (!size) return staticEmptyVArray();
141 VArrayInit init{size};
142 IterateV(a, [&](TypedValue v) { init.appendWithRef(v); });
143 return init.create();
146 ArrayData* ArrayCommon::ToDArray(ArrayData* a, bool) {
147 if (a->isDArray()) return a;
148 auto const size = a->size();
149 if (!size) return staticEmptyDArray();
150 DArrayInit init{size};
151 IterateKV(
153 [&](Cell k, TypedValue v) {
154 init.setUnknownKey(tvAsCVarRef(&k), tvAsCVarRef(&v));
157 return init.create();
160 ArrayCommon::RefCheckResult
161 ArrayCommon::CheckForRefs(const ArrayData* ad) {
162 auto result = RefCheckResult::Pass;
163 IterateV(
165 [&](TypedValue v) {
166 if (UNLIKELY(v.m_type == KindOfRef)) {
167 auto const ref = v.m_data.pref;
168 if (ref->isReferenced() || ref->tv()->m_data.parr == ad) {
169 result = RefCheckResult::Fail;
170 return true;
172 result = RefCheckResult::Collapse;
174 return false;
177 return result;
180 //////////////////////////////////////////////////////////////////////
182 namespace {
184 template <typename E, typename C, typename A>
185 ALWAYS_INLINE
186 ArrayData* castObjToHackArrImpl(ObjectData* obj,
187 E empty,
188 C cast,
189 A add,
190 const char* msg) {
191 if (LIKELY(obj->isCollection())) {
192 if (auto ad = collections::asArray(obj)) {
193 return cast(ArrNR{ad}.asArray()).detach();
195 return cast(collections::toArray(obj)).detach();
198 // iterableObject can re-enter, so bump the ref-count to prevent it from
199 // possibly being freed.
200 obj->incRefCount();
201 SCOPE_EXIT { decRefObj(obj); };
203 bool isIter;
204 auto iterObj = obj->iterableObject(isIter);
205 if (!isIter) SystemLib::throwInvalidOperationExceptionObject(msg);
207 auto arr = empty();
208 for (ArrayIter iter(iterObj); iter; ++iter) add(arr, iter);
209 return arr.detach();
214 ArrayData* castObjToVec(ObjectData* obj) {
215 return castObjToHackArrImpl(
216 obj,
217 Array::CreateVec,
218 [](const Array& arr) { return arr.toVec(); },
219 [](Array& arr, ArrayIter& iter) { arr.append(iter.second()); },
220 "Non-iterable object to vec conversion"
224 ArrayData* castObjToDict(ObjectData* obj) {
225 return castObjToHackArrImpl(
226 obj,
227 Array::CreateDict,
228 [](const Array& arr) { return arr.toDict(); },
229 [](Array& arr, ArrayIter& iter) { arr.set(iter.first(), iter.second()); },
230 "Non-iterable object to dict conversion"
234 ArrayData* castObjToKeyset(ObjectData* obj) {
235 return castObjToHackArrImpl(
236 obj,
237 Array::CreateKeyset,
238 [](const Array& arr) { return arr.toKeyset(); },
239 [](Array& arr, ArrayIter& iter) { arr.append(iter.second()); },
240 "Non-iterable object to keyset conversion"
244 ArrayData* castObjToVArray(ObjectData* obj) {
245 assertx(!RuntimeOption::EvalHackArrDVArrs);
246 return castObjToHackArrImpl(
247 obj,
248 Array::CreateVArray,
249 [](const Array& arr) { return arr.toVArray(); },
250 [](Array& arr, ArrayIter& iter) { arr.append(iter.second()); },
251 "Non-iterable object to varray conversion"
256 ArrayData* castObjToDArray(ObjectData* obj) {
257 assertx(!RuntimeOption::EvalHackArrDVArrs);
258 return castObjToHackArrImpl(
259 obj,
260 Array::CreateDArray,
261 [](const Array& arr) { return arr.toDArray(); },
262 [](Array& arr, ArrayIter& iter) { arr.set(iter.first(), iter.second()); },
263 "Non-iterable object to darray conversion"
267 //////////////////////////////////////////////////////////////////////