Add sub-controls for Hack array compat runtime checks
[hiphop-php.git] / hphp / runtime / base / empty-array.cpp
blob063208351d27c80408a8383fbdd0242def764995
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/empty-array.h"
19 #include <type_traits>
21 #include "hphp/util/assertions.h"
23 #include "hphp/runtime/base/array-data.h"
24 #include "hphp/runtime/base/array-init.h"
25 #include "hphp/runtime/base/member-val.h"
26 #include "hphp/runtime/base/mixed-array-defs.h"
27 #include "hphp/runtime/base/mixed-array.h"
28 #include "hphp/runtime/base/packed-array-defs.h"
29 #include "hphp/runtime/base/set-array.h"
30 #include "hphp/runtime/base/tv-mutate.h"
31 #include "hphp/runtime/base/tv-type.h"
32 #include "hphp/runtime/base/tv-variant.h"
33 #include "hphp/runtime/base/type-variant.h"
35 namespace HPHP {
37 //////////////////////////////////////////////////////////////////////
39 std::aligned_storage<sizeof(ArrayData), 16>::type s_theEmptyArray;
41 struct EmptyArray::Initializer {
42 Initializer() {
43 auto const ad = reinterpret_cast<ArrayData*>(&s_theEmptyArray);
44 ad->m_sizeAndPos = 0;
45 ad->initHeader(HeaderKind::Empty, StaticValue);
48 EmptyArray::Initializer EmptyArray::s_initializer;
50 //////////////////////////////////////////////////////////////////////
52 void EmptyArray::Release(ArrayData*) {
53 always_assert(!"never try to free the empty array");
56 Cell EmptyArray::NvGetKey(const ArrayData*, ssize_t /*pos*/) {
57 // We have no valid positions---no one should call this function.
58 not_reached();
61 size_t EmptyArray::Vsize(const ArrayData*) { not_reached(); }
63 member_rval::ptr_u
64 EmptyArray::GetValueRef(const ArrayData* /*ad*/, ssize_t /*pos*/) {
65 // We have no valid positions---no one should call this function.
66 not_reached();
69 // EmptyArray::IterAdvance() is reachable; see ArrayData::next() for details
70 ssize_t EmptyArray::IterAdvance(const ArrayData*, ssize_t /*prev*/) {
71 return 0;
74 // EmptyArray::IterRewind() is NOT reachable; see ArrayData::prev() for details
75 ssize_t EmptyArray::IterRewind(const ArrayData*, ssize_t /*prev*/) {
76 not_reached();
79 // Even though we always return false in ValidMArrayIter, this function may
80 // still be called because MArrayIters are constructed in an invalid position,
81 // and then advanced to the first element.
82 bool EmptyArray::AdvanceMArrayIter(ArrayData*, MArrayIter& /*fp*/) {
83 return false;
86 // We're always already a static array.
87 void EmptyArray::OnSetEvalScalar(ArrayData*) { not_reached(); }
88 ArrayData* EmptyArray::CopyStatic(const ArrayData* /*ad*/) {
89 not_reached();
92 //////////////////////////////////////////////////////////////////////
94 NEVER_INLINE
95 ArrayData* EmptyArray::Copy(const ArrayData*) { return staticEmptyArray(); }
97 //////////////////////////////////////////////////////////////////////
100 * Note: if you try to tail-call these helper routines, gcc will
101 * unfortunately still generate functions with frames and and makes a
102 * call instead of a jump. It's because of std::pair (and is still
103 * the case if you return a custom struct).
105 * For now we're leaving this, because it's essentially free for these
106 * routines to leave the lval pointer in the second return register,
107 * and it seems questionable to clone the whole function just to avoid
108 * the frame creation in these callers. (It works to reinterpret_cast
109 * these functions to one that returns ArrayData* instead of a pair in
110 * the cases we don't need the second value, but this seems a tad too
111 * sketchy for probably-unmeasurable benefits. I'll admit I didn't
112 * try to measure it though... ;)
116 * Helper for empty array -> packed transitions. Creates an array
117 * with one element. The element is transferred into the array (should
118 * already be incref'd).
120 ALWAYS_INLINE
121 member_lval EmptyArray::MakePackedInl(TypedValue tv) {
122 auto const ad = static_cast<ArrayData*>(
123 tl_heap->objMallocIndex(PackedArray::SmallSizeIndex)
125 ad->initHeader_16(
126 HeaderKind::Packed,
127 OneReference,
128 PackedArray::packSizeIndexAndDV(
129 PackedArray::SmallSizeIndex,
130 ArrayData::kNotDVArray
133 ad->m_sizeAndPos = 1; // size=1, pos=0
135 auto const elem = packedData(ad);
136 *elem = tv;
138 assert(ad->kind() == ArrayData::kPackedKind);
139 assert(ad->dvArray() == ArrayData::kNotDVArray);
140 assert(ad->m_size == 1);
141 assert(ad->m_pos == 0);
142 assert(ad->hasExactlyOneRef());
143 assert(PackedArray::checkInvariants(ad));
144 return member_lval { ad, elem };
147 NEVER_INLINE
148 member_lval EmptyArray::MakePacked(TypedValue tv) {
149 return MakePackedInl(tv);
153 * Helper for creating a single-element mixed array with a string key.
155 * Note: the key is not already incref'd, but the value must be.
157 NEVER_INLINE
158 member_lval EmptyArray::MakeMixed(StringData* key, TypedValue val) {
159 auto const ad = MixedArray::reqAlloc(MixedArray::SmallScale);
160 MixedArray::InitSmall(ad, 1/*size*/, 0/*nextIntKey*/);
161 auto const data = ad->data();
162 auto const hash = reinterpret_cast<int32_t*>(data + MixedArray::SmallSize);
163 auto const khash = key->hash();
164 auto const mask = MixedArray::SmallMask;
165 hash[khash & mask] = 0;
166 data[0].setStrKey(key, khash);
168 auto& elem = data[0].data;
169 elem.m_data = val.m_data;
170 elem.m_type = val.m_type;
172 assert(ad->m_size == 1);
173 assert(ad->m_pos == 0);
174 assert(ad->m_scale == MixedArray::SmallScale);
175 assert(ad->kind() == ArrayData::kMixedKind);
176 assert(ad->hasExactlyOneRef());
177 assert(ad->m_used == 1);
178 assert(ad->checkInvariants());
179 return member_lval { ad, &elem };
183 * Creating a single-element mixed array with a integer key. The
184 * value is already incref'd.
186 member_lval EmptyArray::MakeMixed(int64_t key, TypedValue val) {
187 auto const ad = MixedArray::reqAlloc(MixedArray::SmallScale);
188 MixedArray::InitSmall(ad, 1/*size*/, (key >= 0) ? key + 1 : 0);
189 auto const data = ad->data();
190 auto const hash = reinterpret_cast<int32_t*>(data + MixedArray::SmallSize);
192 auto const mask = MixedArray::SmallMask;
193 auto h = hash_int64(key);
194 hash[h & mask] = 0;
195 data[0].setIntKey(key, h);
197 auto& elem = data[0].data;
198 elem.m_data = val.m_data;
199 elem.m_type = val.m_type;
201 assert(ad->kind() == ArrayData::kMixedKind);
202 assert(ad->m_size == 1);
203 assert(ad->m_pos == 0);
204 assert(ad->hasExactlyOneRef());
205 assert(ad->m_scale == MixedArray::SmallScale);
206 assert(ad->m_used == 1);
207 assert(ad->checkInvariants());
208 return member_lval { ad, &elem };
211 //////////////////////////////////////////////////////////////////////
213 ArrayData* EmptyArray::SetInt(ArrayData*, int64_t k, Cell v, bool) {
214 // TODO(#3888164): we should make it so we don't need KindOfUninit checks
215 if (v.m_type == KindOfUninit) v.m_type = KindOfNull;
216 tvIncRefGen(v);
217 auto const lval = k == 0 ? EmptyArray::MakePacked(v)
218 : EmptyArray::MakeMixed(k, v);
219 return lval.arr_base();
222 ArrayData*
223 EmptyArray::SetStr(ArrayData*, StringData* k, Cell v, bool /*copy*/) {
224 tvIncRefGen(v);
225 // TODO(#3888164): we should make it so we don't need KindOfUninit checks
226 if (v.m_type == KindOfUninit) v.m_type = KindOfNull;
227 return EmptyArray::MakeMixed(k, v).arr_base();
230 ArrayData* EmptyArray::SetWithRefInt(ArrayData* ad, int64_t k,
231 TypedValue v, bool copy) {
232 if (checkHACRefBind() && tvIsReferenced(v)) {
233 raiseHackArrCompatRefBind(k);
235 auto const lval = LvalInt(ad, k, copy);
236 tvSetWithRef(v, lval);
237 return lval.arr_base();
240 ArrayData* EmptyArray::SetWithRefStr(ArrayData* ad, StringData* k,
241 TypedValue v, bool copy) {
242 if (checkHACRefBind() && tvIsReferenced(v)) {
243 raiseHackArrCompatRefBind(k);
245 auto const lval = LvalStr(ad, k, copy);
246 tvSetWithRef(v, lval);
247 return lval.arr_base();
250 member_lval EmptyArray::LvalInt(ArrayData*, int64_t k, bool) {
251 return k == 0 ? EmptyArray::MakePacked(make_tv<KindOfNull>())
252 : EmptyArray::MakeMixed(k, make_tv<KindOfNull>());
255 member_lval EmptyArray::LvalIntRef(ArrayData* ad, int64_t k, bool copy) {
256 if (checkHACRefBind()) raiseHackArrCompatRefBind(k);
257 return LvalInt(ad, k, copy);
260 member_lval EmptyArray::LvalStr(ArrayData*, StringData* k, bool) {
261 return EmptyArray::MakeMixed(k, make_tv<KindOfNull>());
264 member_lval EmptyArray::LvalStrRef(ArrayData* ad, StringData* k, bool copy) {
265 if (checkHACRefBind()) raiseHackArrCompatRefBind(k);
266 return LvalStr(ad, k, copy);
269 member_lval EmptyArray::LvalNew(ArrayData*, bool) {
270 return EmptyArray::MakePacked(make_tv<KindOfNull>());
273 member_lval EmptyArray::LvalNewRef(ArrayData* ad, bool copy) {
274 if (checkHACRefBind()) raiseHackArrCompatRefNew();
275 return LvalNew(ad, copy);
278 ArrayData* EmptyArray::SetRefInt(ArrayData*, int64_t k,
279 member_lval v, bool) {
280 if (checkHACRefBind()) raiseHackArrCompatRefBind(k);
281 tvBoxIfNeeded(v);
282 tvIncRefCountable(v.tv());
283 auto const lval = k == 0 ? EmptyArray::MakePacked(v.tv())
284 : EmptyArray::MakeMixed(k, v.tv());
285 return lval.arr_base();
288 ArrayData* EmptyArray::SetRefStr(ArrayData*, StringData* k,
289 member_lval v, bool) {
290 if (checkHACRefBind()) raiseHackArrCompatRefBind(k);
291 tvBoxIfNeeded(v);
292 tvIncRefCountable(v.tv());
293 return EmptyArray::MakeMixed(k, v.tv()).arr_base();
296 ArrayData* EmptyArray::Append(ArrayData*, Cell v, bool /*copy*/) {
297 tvIncRefGen(v);
298 return EmptyArray::MakePackedInl(v).arr_base();
301 ArrayData* EmptyArray::AppendRef(ArrayData*, member_lval v, bool) {
302 if (checkHACRefBind()) raiseHackArrCompatRefNew();
303 tvBoxIfNeeded(v);
304 tvIncRefCountable(v.tv());
305 return EmptyArray::MakePacked(v.tv()).arr_base();
308 ArrayData* EmptyArray::AppendWithRef(ArrayData*, TypedValue v, bool /*copy*/) {
309 if (checkHACRefBind() && tvIsReferenced(v)) {
310 raiseHackArrCompatRefNew();
312 auto tv = make_tv<KindOfNull>();
313 tvAsVariant(&tv).setWithRef(v);
314 return EmptyArray::MakePacked(tv).arr_base();
317 //////////////////////////////////////////////////////////////////////
319 ArrayData* EmptyArray::PlusEq(ArrayData*, const ArrayData* elems) {
320 if (!elems->isPHPArray()) throwInvalidAdditionException(elems);
321 elems->incRefCount();
322 return const_cast<ArrayData*>(elems);
325 ArrayData* EmptyArray::Merge(ArrayData*, const ArrayData* elems) {
326 if (elems->isNotDVArray()) {
327 // Packed arrays don't need renumbering, so don't make a copy.
328 if (elems->isPacked()) {
329 elems->incRefCount();
330 return const_cast<ArrayData*>(elems);
332 // Fast path the common case that elems is mixed.
333 if (elems->isMixed()) {
334 auto const copy = MixedArray::Copy(elems);
335 assert(copy != elems);
336 MixedArray::Renumber(copy);
337 return copy;
340 auto copy = const_cast<ArrayData*>(elems)->toPHPArray(true);
341 copy = copy == elems ? elems->copy() : copy;
342 assert(copy != elems);
343 copy->renumber();
344 return copy;
347 ArrayData* EmptyArray::PopOrDequeue(ArrayData* ad, Variant& value) {
348 value = uninit_null();
349 return ad;
352 ArrayData* EmptyArray::Prepend(ArrayData*, Cell v, bool) {
353 tvIncRefGen(v);
354 return EmptyArray::MakePacked(v).arr_base();
357 ArrayData* EmptyArray::ToDict(ArrayData*, bool) {
358 return staticEmptyDictArray();
361 ArrayData* EmptyArray::ToVec(ArrayData*, bool) {
362 return staticEmptyVecArray();
365 ArrayData* EmptyArray::ToKeyset(ArrayData*, bool) {
366 return staticEmptyKeysetArray();
369 //////////////////////////////////////////////////////////////////////