Fix inout calls with boxed but unreferenced values
[hiphop-php.git] / hphp / runtime / vm / member-operations.h
blob7eea9a1da89cffa19cfc4dd143e32c3755769750
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 #ifndef incl_HPHP_VM_MEMBER_OPERATIONS_H_
18 #define incl_HPHP_VM_MEMBER_OPERATIONS_H_
20 #include <type_traits>
22 #include "hphp/runtime/base/array-data.h"
23 #include "hphp/runtime/base/array-data-defs.h"
24 #include "hphp/runtime/base/builtin-functions.h"
25 #include "hphp/runtime/base/collections.h"
26 #include "hphp/runtime/base/req-root.h"
27 #include "hphp/runtime/base/strings.h"
28 #include "hphp/runtime/base/tv-conversions.h"
29 #include "hphp/runtime/base/tv-refcount.h"
30 #include "hphp/runtime/base/tv-type.h"
31 #include "hphp/runtime/base/type-array.h"
32 #include "hphp/runtime/base/type-string.h"
33 #include "hphp/runtime/base/mixed-array.h"
34 #include "hphp/runtime/base/packed-array.h"
35 #include "hphp/runtime/base/set-array.h"
36 #include "hphp/runtime/vm/runtime.h"
37 #include "hphp/system/systemlib.h"
39 namespace HPHP {
41 const StaticString s_storage("storage");
43 struct InvalidSetMException : std::runtime_error {
44 InvalidSetMException()
45 : std::runtime_error("Empty InvalidSetMException")
46 , m_tv(make_tv<KindOfNull>())
49 explicit InvalidSetMException(const TypedValue& value)
50 : std::runtime_error(folly::format("InvalidSetMException containing {}",
51 value.pretty()).str())
52 , m_tv(value)
55 ~InvalidSetMException() noexcept override {}
57 const TypedValue& tv() const { return m_tv; };
59 private:
60 /* m_tv will contain a TypedValue with a reference destined for the
61 * VM eval stack. */
62 req::root<TypedValue> m_tv;
65 // When MoreWarnings is set to true, the VM will raise more warnings
66 // on SetOpM, IncDecM and CGetG, intended to match Zend.
67 const bool MoreWarnings =
68 #ifdef HHVM_MORE_WARNINGS
69 true
70 #else
71 false
72 #endif
76 * KeyType and the associated functions below are used to generate member
77 * operation functions specialized for certain key types. Many functions take a
78 * KeyType template parameter, then use key_type<keyType> as the type of their
79 * key parameter. Depending on which KeyType is used, the parameter will be a
80 * TypedValue, int64_t, or StringData*.
82 enum class KeyType {
83 Any, // Key is passed as a TypedValue and could be any type
84 Int, // Key is passed as an int64_t
85 Str, // Key is passed as a StringData*
88 /* KeyTypeTraits maps from KeyType to the C++ type holding they key. */
89 template<KeyType> struct KeyTypeTraits;
90 template<> struct KeyTypeTraits<KeyType::Any> { typedef TypedValue type; };
91 template<> struct KeyTypeTraits<KeyType::Int> { typedef int64_t type; };
92 template<> struct KeyTypeTraits<KeyType::Str> { typedef StringData* type; };
94 /* key_type is the type used in the signatures of functions taking a member
95 * key. */
96 template<KeyType kt> using key_type = typename KeyTypeTraits<kt>::type;
98 /* initScratchKey is used in scenarios where we want a TypedValue key
99 * regardless of what the current function was given. */
100 inline TypedValue initScratchKey(TypedValue tv) {
101 assertx(!isRefType(tv.m_type));
102 return tv;
105 inline TypedValue initScratchKey(int64_t key) {
106 return make_tv<KindOfInt64>(key);
109 inline TypedValue initScratchKey(StringData* key) {
110 return make_tv<KindOfString>(key);
113 /* keyAsValue transforms a key into a value suitable for indexing into an
114 * Array. */
115 inline const Variant& keyAsValue(TypedValue& key) {
116 return tvAsCVarRef(&key);
118 inline int64_t keyAsValue(int64_t key) { return key; }
119 inline StrNR keyAsValue(StringData* key) { return StrNR(key); }
121 /* prepareKey is used by operations that need to cast their key to a
122 * string. For generic keys, the returned value must be decreffed after use. */
123 StringData* prepareAnyKey(TypedValue* tv);
124 inline StringData* prepareKey(TypedValue tv) { return prepareAnyKey(&tv); }
125 inline StringData* prepareKey(StringData* sd) { return sd; }
127 /* releaseKey helps with decreffing a StringData* returned from
128 * prepareKey. When used with KeyType::Any, corresponding to
129 * prepareKey(TypedValue), it will consume the reference produced by
130 * prepareKey. When used with KeyType::Str, corresponding to
131 * prepareKey(StringData*), it is a nop. */
132 template<KeyType keyType>
133 inline void releaseKey(StringData* sd) {
134 static_assert(keyType == KeyType::Any, "bad KeyType");
135 decRefStr(sd);
138 template<>
139 inline void releaseKey<KeyType::Str>(StringData*) {
140 // do nothing. we don't own a reference to this string.
143 void objArrayAccess(ObjectData* base);
145 TypedValue objOffsetGet(
146 ObjectData* base,
147 TypedValue offset,
148 bool validate = true
151 bool objOffsetIsset(ObjectData* base, TypedValue offset, bool validate = true);
152 bool objOffsetEmpty(ObjectData* base, TypedValue offset, bool validate = true);
154 void objOffsetSet(
155 ObjectData* base,
156 TypedValue offset,
157 TypedValue* val,
158 bool validate = true
161 void objOffsetAppend(ObjectData* base, TypedValue* val, bool validate = true);
162 void objOffsetUnset(ObjectData* base, TypedValue offset);
164 inline ObjectData* instanceFromTv(tv_rval tv) {
165 assertx(tvIsObject(tv));
166 return val(tv).pobj;
169 [[noreturn]] void throw_cannot_use_newelem_for_lval_read_col();
170 [[noreturn]] void throw_cannot_use_newelem_for_lval_read_vec();
171 [[noreturn]] void throw_cannot_use_newelem_for_lval_read_dict();
172 [[noreturn]] void throw_cannot_use_newelem_for_lval_read_keyset();
174 [[noreturn]] void unknownBaseType(DataType);
176 void raise_inout_undefined_index(TypedValue);
177 void raise_inout_undefined_index(int64_t i);
178 void raise_inout_undefined_index(const StringData* sd);
180 namespace detail {
182 ALWAYS_INLINE void checkPromotion(tv_rval base) {
183 if (LIKELY(!checkHACFalseyPromote())) return;
185 if (tvIsNull(base)) {
186 raise_hackarr_compat_notice("Promoting null to array");
187 } else if (tvIsBool(base)) {
188 raise_hackarr_compat_notice("Promoting false to array");
195 * Elem when base is Null
197 inline tv_rval ElemEmptyish() {
198 return tv_rval { &immutable_null_base };
201 template<MOpMode mode, bool intishWarn>
202 inline tv_rval ElemArrayPre(ArrayData* base, int64_t key) {
203 return mode == MOpMode::Warn ? base->rvalStrict(key) : base->rval(key);
206 template<MOpMode mode, bool intishWarn>
207 inline tv_rval ElemArrayPre(ArrayData* base, StringData* key) {
208 auto constexpr warn = mode == MOpMode::Warn;
209 int64_t n;
210 assertx(base->isPHPArray());
211 if (key->isStrictlyInteger(n)) {
212 if (intishWarn) raise_intish_index_cast();
213 return warn ? base->rvalStrict(n) : base->rval(n);
214 } else {
215 return warn ? base->rvalStrict(key) : base->rval(key);
219 template<MOpMode mode, bool intishWarn>
220 inline tv_rval ElemArrayPre(ArrayData* base, TypedValue key) {
221 auto const dt = key.m_type;
222 if (isIntType(dt)) {
223 return ElemArrayPre<mode, false>(base, key.m_data.num);
225 if (isStringType(dt)) {
226 return ElemArrayPre<mode, intishWarn>(base, key.m_data.pstr);
229 // TODO(#3888164): Array elements can never be KindOfUninit. This API should
230 // be changed.
231 auto const rval = ArrNR{base}.asArray().rvalAt(cellAsCVarRef(key));
232 return rval.type() != KindOfUninit ? rval : tv_rval { nullptr };
236 * Fast path for Elem assuming base is an Array. Does not unbox the returned
237 * pointer.
239 template<MOpMode mode, KeyType keyType, bool intishWarn>
240 inline tv_rval ElemArray(ArrayData* base, key_type<keyType> key) {
241 assertx(base->isPHPArray());
243 auto result = ElemArrayPre<mode, intishWarn>(base, key);
245 if (UNLIKELY(!result)) {
246 if (mode == MOpMode::Warn) {
247 auto const scratch = initScratchKey(key);
248 raise_notice(Strings::UNDEFINED_INDEX,
249 tvAsCVarRef(&scratch).toString().data());
251 if (mode == MOpMode::InOut) {
252 raise_inout_undefined_index(initScratchKey(key));
254 return ElemEmptyish();
257 assertx(result.type() != KindOfUninit);
258 return result;
262 * Elem when base is a Vec
264 template<MOpMode mode>
265 inline tv_rval ElemVecPre(ArrayData* base, int64_t key) {
266 return mode == MOpMode::Warn || mode == MOpMode::InOut
267 ? PackedArray::RvalIntStrictVec(base, key)
268 : PackedArray::RvalIntVec(base, key);
271 template<MOpMode mode>
272 inline tv_rval ElemVecPre(ArrayData* base, StringData* key) {
273 if (mode == MOpMode::Warn || mode == MOpMode::InOut) {
274 throwInvalidArrayKeyException(key, base);
276 return tv_rval{};
279 template<MOpMode mode>
280 inline tv_rval ElemVecPre(ArrayData* base, TypedValue key) {
281 auto const dt = key.m_type;
282 if (LIKELY(isIntType(dt))) return ElemVecPre<mode>(base, key.m_data.num);
283 if (isStringType(dt)) return ElemVecPre<mode>(base, key.m_data.pstr);
284 throwInvalidArrayKeyException(&key, base);
287 template<MOpMode mode, KeyType keyType>
288 inline tv_rval ElemVec(ArrayData* base, key_type<keyType> key) {
289 assertx(base->isVecArray());
290 auto result = ElemVecPre<mode>(base, key);
291 if (mode != MOpMode::Warn && mode != MOpMode::InOut) {
292 if (UNLIKELY(!result)) return ElemEmptyish();
294 assertx(result.type() != KindOfUninit);
295 return result;
299 * Elem when base is a Dict
301 template<MOpMode mode>
302 inline tv_rval ElemDictPre(ArrayData* base, int64_t key) {
303 return mode == MOpMode::Warn || mode == MOpMode::InOut
304 ? MixedArray::RvalIntStrictDict(base, key)
305 : MixedArray::RvalIntDict(base, key);
308 template<MOpMode mode>
309 inline tv_rval ElemDictPre(ArrayData* base, StringData* key) {
310 return mode == MOpMode::Warn || mode == MOpMode::InOut
311 ? MixedArray::RvalStrStrictDict(base, key)
312 : MixedArray::RvalStrDict(base, key);
315 template<MOpMode mode>
316 inline tv_rval ElemDictPre(ArrayData* base, TypedValue key) {
317 auto const dt = key.m_type;
318 if (isIntType(dt)) return ElemDictPre<mode>(base, key.m_data.num);
319 if (isStringType(dt)) return ElemDictPre<mode>(base, key.m_data.pstr);
320 throwInvalidArrayKeyException(&key, base);
323 template<MOpMode mode, KeyType keyType>
324 inline tv_rval ElemDict(ArrayData* base, key_type<keyType> key) {
325 assertx(base->isDict());
326 auto result = ElemDictPre<mode>(base, key);
327 if (mode != MOpMode::Warn && mode != MOpMode::InOut) {
328 if (UNLIKELY(!result)) return ElemEmptyish();
330 assertx(result.type() != KindOfUninit);
331 return result;
335 * Elem when base is a Keyset
337 template<MOpMode mode>
338 inline tv_rval ElemKeysetPre(ArrayData* base, int64_t key) {
339 return mode == MOpMode::Warn || mode == MOpMode::InOut
340 ? SetArray::RvalIntStrict(base, key)
341 : SetArray::RvalInt(base, key);
344 template<MOpMode mode>
345 inline tv_rval ElemKeysetPre(ArrayData* base, StringData* key) {
346 return mode == MOpMode::Warn || mode == MOpMode::InOut
347 ? SetArray::RvalStrStrict(base, key)
348 : SetArray::RvalStr(base, key);
351 template<MOpMode mode>
352 inline tv_rval ElemKeysetPre(ArrayData* base, TypedValue key) {
353 auto const dt = key.m_type;
354 if (isIntType(dt)) return ElemKeysetPre<mode>(base, key.m_data.num);
355 if (isStringType(dt)) return ElemKeysetPre<mode>(base, key.m_data.pstr);
356 throwInvalidArrayKeyException(&key, base);
359 template<MOpMode mode, KeyType keyType>
360 inline tv_rval ElemKeyset(ArrayData* base, key_type<keyType> key) {
361 assertx(base->isKeyset());
362 auto result = ElemKeysetPre<mode>(base, key);
363 if (mode != MOpMode::Warn && mode != MOpMode::InOut) {
364 if (UNLIKELY(!result)) return ElemEmptyish();
366 assertx(isIntType(result.type()) || isStringType(result.type()));
367 return result;
371 * Elem when base is an Int64, Double, Resource, or Func.
373 inline tv_rval ElemScalar() {
374 if (RuntimeOption::EnableHipHopSyntax) {
375 raise_warning(Strings::CANNOT_USE_SCALAR_AS_ARRAY);
377 return ElemEmptyish();
381 * Elem when base is a Boolean
383 inline tv_rval ElemBoolean(tv_rval base) {
384 return base.val().num ? ElemScalar() : ElemEmptyish();
387 inline int64_t ElemStringPre(int64_t key) {
388 return key;
391 inline int64_t ElemStringPre(StringData* key) {
392 return key->toInt64(10);
395 inline int64_t ElemStringPre(TypedValue key) {
396 if (LIKELY(isIntType(key.m_type))) {
397 return key.m_data.num;
398 } else if (LIKELY(isStringType(key.m_type))) {
399 return key.m_data.pstr->toInt64(10);
400 } else {
401 raise_notice("String offset cast occurred");
402 return cellAsCVarRef(key).toInt64();
407 * Elem when base is a String
409 template<MOpMode mode, KeyType keyType>
410 inline tv_rval ElemString(TypedValue& tvRef,
411 const StringData* base,
412 key_type<keyType> key) {
413 auto offset = ElemStringPre(key);
415 if (offset < 0 || offset >= base->size()) {
416 if (mode == MOpMode::Warn) {
417 raise_notice("Uninitialized string offset: %" PRId64, offset);
419 tvRef = make_tv<KindOfPersistentString>(staticEmptyString());
420 } else {
421 tvRef = make_tv<KindOfPersistentString>(base->getChar(offset));
422 assertx(tvRef.m_data.pstr->isStatic());
424 return tv_rval { &tvRef };
428 * Elem when base is an Object
430 template<MOpMode mode, KeyType keyType>
431 inline tv_rval ElemObject(TypedValue& tvRef,
432 ObjectData* base,
433 key_type<keyType> key) {
434 auto scratch = initScratchKey(key);
436 if (LIKELY(base->isCollection())) {
437 if (mode == MOpMode::Warn) {
438 return collections::at(base, &scratch);
440 auto res = collections::get(base, &scratch);
441 if (!res) {
442 res = &tvRef;
443 tvWriteNull(*res);
445 return res;
448 tvRef = objOffsetGet(base, scratch);
449 return &tvRef;
453 * $result = $base[$key];
455 template<MOpMode mode, KeyType keyType, bool intishWarn>
456 NEVER_INLINE tv_rval ElemSlow(TypedValue& tvRef,
457 tv_rval base,
458 key_type<keyType> key) {
459 base = base.unboxed();
460 assertx(cellIsPlausible(*base));
462 switch (base.type()) {
463 case KindOfUninit:
464 case KindOfNull:
465 return ElemEmptyish();
466 case KindOfBoolean:
467 return ElemBoolean(base);
468 case KindOfInt64:
469 case KindOfDouble:
470 case KindOfResource:
471 // TODO (T29639296)
472 case KindOfFunc:
473 return ElemScalar();
474 case KindOfPersistentString:
475 case KindOfString:
476 return ElemString<mode, keyType>(tvRef, base.val().pstr, key);
477 case KindOfPersistentVec:
478 case KindOfVec:
479 return ElemVec<mode, keyType>(base.val().parr, key);
480 case KindOfPersistentDict:
481 case KindOfDict:
482 return ElemDict<mode, keyType>(base.val().parr, key);
483 case KindOfPersistentKeyset:
484 case KindOfKeyset:
485 return ElemKeyset<mode, keyType>(base.val().parr, key);
486 case KindOfPersistentArray:
487 case KindOfArray:
488 return ElemArray<mode, keyType, intishWarn>(base.val().parr, key);
489 case KindOfObject:
490 return ElemObject<mode, keyType>(tvRef, base.val().pobj, key);
491 case KindOfRef:
492 break;
494 unknownBaseType(type(base));
497 template<MOpMode mode, bool intishWarn, KeyType keyType = KeyType::Any>
498 inline tv_rval Elem(TypedValue& tvRef,
499 tv_rval base,
500 key_type<keyType> key) {
501 assertx(mode != MOpMode::Define && mode != MOpMode::Unset);
502 assertx(tvIsPlausible(base.tv()));
504 if (mode == MOpMode::InOut) {
505 if (UNLIKELY(tvIsRef(base) && !base.val().pref->isReferenced())) {
506 base = base.unboxed();
510 if (LIKELY(tvIsArray(base))) {
511 return ElemArray<mode, keyType, intishWarn>(base.val().parr, key);
513 if (LIKELY(tvIsVec(base))) {
514 return ElemVec<mode, keyType>(base.val().parr, key);
516 if (LIKELY(tvIsDict(base))) {
517 return ElemDict<mode, keyType>(base.val().parr, key);
519 if (LIKELY(tvIsKeyset(base))) {
520 return ElemKeyset<mode, keyType>(base.val().parr, key);
523 if (mode == MOpMode::InOut) throw_invalid_inout_base();
525 return ElemSlow<mode, keyType, intishWarn>(tvRef, base, key);
528 template<MOpMode mode, bool reffy, bool intishWarn>
529 inline tv_lval ElemDArrayPre(tv_lval base, int64_t key, bool& defined) {
530 auto oldArr = val(base).parr;
532 defined = (mode != MOpMode::Warn) || oldArr->exists(key);
533 auto const lval = reffy
534 ? oldArr->lvalRef(key, oldArr->cowCheck())
535 : oldArr->lval(key, oldArr->cowCheck());
537 if (lval.arr != oldArr) {
538 type(base) = KindOfArray;
539 val(base).parr = lval.arr;
540 assertx(cellIsPlausible(*base));
541 decRefArr(oldArr);
544 return lval;
547 template<MOpMode mode, bool reffy, bool intishWarn>
548 inline tv_lval ElemDArrayPre(tv_lval base, StringData* key,
549 bool& defined) {
550 auto oldArr = val(base).parr;
552 auto const lval = [&]{
553 auto const cow = oldArr->cowCheck();
554 int64_t n;
555 if (oldArr->convertKey(key, n, intishWarn)) {
556 defined = (mode != MOpMode::Warn) || oldArr->exists(n);
557 return reffy ? oldArr->lvalRef(n, cow) : oldArr->lval(n, cow);
558 } else {
559 defined = (mode != MOpMode::Warn) || oldArr->exists(key);
560 return reffy ? oldArr->lvalRef(key, cow) : oldArr->lval(key, cow);
562 }();
564 if (lval.arr != oldArr) {
565 type(base) = KindOfArray;
566 val(base).parr = lval.arr;
567 assertx(cellIsPlausible(*base));
568 decRefArr(oldArr);
570 return lval;
573 template<MOpMode mode, bool reffy, bool intishWarn>
574 inline tv_lval ElemDArrayPre(tv_lval base, TypedValue key,
575 bool& defined) {
576 auto const dt = key.m_type;
577 if (isIntType(dt)) {
578 return ElemDArrayPre<mode, reffy, false>(base, key.m_data.num, defined);
580 if (isStringType(dt)) {
581 return ElemDArrayPre<mode, reffy, intishWarn>(
582 base, key.m_data.pstr, defined
585 auto& arr = asArrRef(base);
586 defined = (mode != MOpMode::Warn) || arr.exists(tvAsCVarRef(&key));
587 return reffy ? arr.lvalAtRef(key) : arr.lvalAt(key);
591 * ElemD when base is an Array
593 template<MOpMode mode, bool reffy, bool intishWarn, KeyType keyType>
594 inline tv_lval ElemDArray(tv_lval base, key_type<keyType> key) {
595 assertx(tvIsArray(base));
596 assertx(tvIsPlausible(*base));
598 bool defined;
599 auto lval = ElemDArrayPre<mode, reffy, intishWarn>(base, key, defined);
601 assertx(tvIsArray(base));
602 assertx(tvIsPlausible(*base));
603 assertx(lval.type() != KindOfUninit);
605 if (!defined) {
606 auto scratchKey = initScratchKey(key);
607 raise_notice(Strings::UNDEFINED_INDEX,
608 tvAsCVarRef(&scratchKey).toString().data());
611 return lval;
615 * ElemD when base is a Vec
617 template <bool reffy>
618 inline tv_lval ElemDVecPre(tv_lval base, int64_t key) {
619 ArrayData* oldArr = base.val().parr;
621 if (reffy) throwRefInvalidArrayValueException(oldArr);
623 auto const lval = PackedArray::LvalIntVec(oldArr, key, oldArr->cowCheck());
624 if (lval.arr != oldArr) {
625 base.type() = KindOfVec;
626 base.val().parr = lval.arr;
627 assertx(cellIsPlausible(base.tv()));
628 decRefArr(oldArr);
630 return lval;
633 template <bool reffy>
634 inline tv_lval ElemDVecPre(tv_lval base, StringData* key) {
635 throwInvalidArrayKeyException(key, base.val().parr);
638 template <bool reffy>
639 inline tv_lval ElemDVecPre(tv_lval base, TypedValue key) {
640 auto const dt = key.m_type;
641 if (LIKELY(isIntType(dt))) return ElemDVecPre<reffy>(base, key.m_data.num);
642 if (isStringType(dt)) return ElemDVecPre<reffy>(base, key.m_data.pstr);
643 throwInvalidArrayKeyException(&key, base.val().parr);
646 template <bool reffy, KeyType keyType>
647 inline tv_lval ElemDVec(tv_lval base, key_type<keyType> key) {
648 assertx(tvIsVec(base));
649 assertx(tvIsPlausible(base.tv()));
650 auto const result = ElemDVecPre<reffy>(base, key);
651 assertx(tvIsVec(base));
652 assertx(tvIsPlausible(base.tv()));
654 assertx(result.type() != KindOfUninit);
655 return result;
659 * ElemD when base is a Dict
661 template <bool reffy>
662 inline tv_lval ElemDDictPre(tv_lval base, int64_t key) {
663 ArrayData* oldArr = base.val().parr;
665 if (reffy) throwRefInvalidArrayValueException(oldArr);
667 auto const lval =
668 MixedArray::LvalSilentIntDict(oldArr, key, oldArr->cowCheck());
670 if (UNLIKELY(!lval)) {
671 assertx(oldArr == lval.arr);
672 throwOOBArrayKeyException(key, oldArr);
675 if (lval.arr != oldArr) {
676 base.type() = KindOfDict;
677 base.val().parr = lval.arr;
678 assertx(cellIsPlausible(base.tv()));
679 decRefArr(oldArr);
682 return lval;
685 template <bool reffy>
686 inline tv_lval ElemDDictPre(tv_lval base, StringData* key) {
687 ArrayData* oldArr = base.val().parr;
689 if (reffy) throwRefInvalidArrayValueException(oldArr);
691 auto const lval =
692 MixedArray::LvalSilentStrDict(oldArr, key, oldArr->cowCheck());
694 if (UNLIKELY(!lval)) {
695 assertx(oldArr == lval.arr);
696 throwOOBArrayKeyException(key, oldArr);
699 if (lval.arr != oldArr) {
700 base.type() = KindOfDict;
701 base.val().parr = lval.arr;
702 assertx(cellIsPlausible(base.tv()));
703 decRefArr(oldArr);
706 return lval;
709 template <bool reffy>
710 inline tv_lval ElemDDictPre(tv_lval base, TypedValue key) {
711 auto const dt = key.m_type;
712 if (isIntType(dt)) return ElemDDictPre<reffy>(base, key.m_data.num);
713 if (isStringType(dt)) return ElemDDictPre<reffy>(base, key.m_data.pstr);
714 throwInvalidArrayKeyException(&key, base.val().parr);
717 template <bool reffy, KeyType keyType>
718 inline tv_lval ElemDDict(tv_lval base, key_type<keyType> key) {
719 assertx(isDictType(base.type()));
720 assertx(tvIsPlausible(base.tv()));
721 auto result = ElemDDictPre<reffy>(base, key);
722 assertx(isDictType(base.type()));
723 assertx(tvIsPlausible(base.tv()));
725 assertx(result.type() != KindOfUninit);
726 return result;
730 * ElemD when base is a Keyset
732 template <bool reffy>
733 [[noreturn]]
734 inline tv_lval ElemDKeysetPre(tv_lval base, int64_t /*key*/) {
735 if (reffy) throwRefInvalidArrayValueException(base.val().parr);
736 throwInvalidKeysetOperation();
739 template <bool reffy>
740 [[noreturn]]
741 inline tv_lval ElemDKeysetPre(tv_lval base, StringData* /*key*/) {
742 if (reffy) throwRefInvalidArrayValueException(base.val().parr);
743 throwInvalidKeysetOperation();
746 template <bool reffy>
747 [[noreturn]]
748 inline tv_lval ElemDKeysetPre(tv_lval base, TypedValue key) {
749 auto const dt = key.m_type;
750 if (isIntType(dt)) ElemDKeysetPre<reffy>(base, key.m_data.num);
751 if (isStringType(dt)) ElemDKeysetPre<reffy>(base, key.m_data.pstr);
752 throwInvalidArrayKeyException(&key, base.val().parr);
755 template <bool reffy, KeyType keyType>
756 [[noreturn]]
757 inline tv_lval ElemDKeyset(tv_lval base, key_type<keyType> key) {
758 assertx(tvIsKeyset(base));
759 assertx(tvIsPlausible(base.tv()));
760 ElemDKeysetPre<reffy>(base, key);
764 * ElemD when base is Null
766 template<MOpMode mode, KeyType keyType>
767 inline tv_lval ElemDEmptyish(tv_lval base, key_type<keyType> key) {
768 detail::checkPromotion(base);
769 auto scratchKey = initScratchKey(key);
771 tvMove(make_tv<KindOfArray>(ArrayData::Create()), base);
773 auto const result = asArrRef(base).lvalAt(cellAsCVarRef(scratchKey));
774 if (mode == MOpMode::Warn) {
775 raise_notice(Strings::UNDEFINED_INDEX,
776 tvAsCVarRef(&scratchKey).toString().data());
778 return result;
782 * ElemD when base is an Int64, Double, Resource, or Func.
784 inline tv_lval ElemDScalar(TypedValue& tvRef) {
785 raise_warning(Strings::CANNOT_USE_SCALAR_AS_ARRAY);
786 tvWriteNull(tvRef);
787 return tv_lval(&tvRef);
791 * ElemD when base is a Boolean
793 template<MOpMode mode, KeyType keyType>
794 inline tv_lval ElemDBoolean(TypedValue& tvRef,
795 tv_lval base,
796 key_type<keyType> key) {
797 return base.val().num
798 ? ElemDScalar(tvRef)
799 : ElemDEmptyish<mode, keyType>(base, key);
803 * ElemD when base is a String
805 template<MOpMode mode, KeyType keyType>
806 inline tv_lval ElemDString(tv_lval base, key_type<keyType> key) {
807 if (base.val().pstr->size() == 0) {
808 return ElemDEmptyish<mode, keyType>(base, key);
810 raise_error("Operator not supported for strings");
811 return tv_lval(nullptr);
815 * ElemD when base is an Object
817 template<MOpMode mode, bool reffy, KeyType keyType>
818 inline tv_lval ElemDObject(TypedValue& tvRef, tv_lval base,
819 key_type<keyType> key) {
820 auto scratchKey = initScratchKey(key);
821 auto obj = base.val().pobj;
823 if (LIKELY(obj->isCollection())) {
824 if (reffy) {
825 raise_error("Collection elements cannot be taken by reference");
826 return tv_lval(nullptr);
828 return collections::atLval(obj, &scratchKey);
829 } else if (obj->getVMClass()->classof(SystemLib::s_ArrayObjectClass)) {
830 auto storage = obj->getPropLval(SystemLib::s_ArrayObjectClass,
831 s_storage.get());
832 // ArrayObject should always have the 'storage' property...
833 assertx(storage);
834 return UNLIKELY(checkHACIntishCast())
835 ? ElemDArray<mode, reffy, true, keyType>(storage, key)
836 : ElemDArray<mode, reffy, false, keyType>(storage, key);
840 tvRef = objOffsetGet(instanceFromTv(base), scratchKey);
841 return tv_lval(&tvRef);
845 * Intermediate elem operation for defining member instructions.
847 * Returned pointer is not yet unboxed. (I.e. it cannot point into a RefData.)
849 template<MOpMode mode, bool reffy, bool intishWarn,
850 KeyType keyType = KeyType::Any>
851 tv_lval ElemD(TypedValue& tvRef, tv_lval base, key_type<keyType> key) {
852 assertx(mode == MOpMode::Define);
854 base = base.unboxed();
855 assertx(cellIsPlausible(base.tv()));
857 switch (base.type()) {
858 case KindOfUninit:
859 case KindOfNull:
860 return ElemDEmptyish<mode, keyType>(base, key);
861 case KindOfBoolean:
862 return ElemDBoolean<mode, keyType>(tvRef, base, key);
863 case KindOfInt64:
864 case KindOfDouble:
865 case KindOfResource:
866 case KindOfFunc:
867 return ElemDScalar(tvRef);
868 case KindOfPersistentString:
869 case KindOfString:
870 return ElemDString<mode, keyType>(base, key);
871 case KindOfPersistentVec:
872 case KindOfVec:
873 return ElemDVec<reffy, keyType>(base, key);
874 case KindOfPersistentDict:
875 case KindOfDict:
876 return ElemDDict<reffy, keyType>(base, key);
877 case KindOfPersistentKeyset:
878 case KindOfKeyset:
879 return ElemDKeyset<reffy, keyType>(base, key);
880 case KindOfPersistentArray:
881 case KindOfArray:
882 return ElemDArray<mode, reffy, intishWarn, keyType>(base, key);
883 case KindOfObject:
884 return ElemDObject<mode, reffy, keyType>(tvRef, base, key);
885 case KindOfRef:
886 break;
888 unknownBaseType(type(base));
892 * Implementation for SetWithRef*ML bytecodes.
894 template<MOpMode mode, bool reffy, bool intishWarn,
895 KeyType keyType = KeyType::Any>
896 void SetWithRefMLElem(TypedValue& tvRef, tv_lval base,
897 key_type<keyType> key, TypedValue val) {
898 assertx(mode == MOpMode::Define);
900 base = tvToCell(base);
901 assertx(cellIsPlausible(*base));
903 auto const elem = [&] {
904 switch (type(base)) {
905 case KindOfUninit:
906 case KindOfNull:
907 return ElemDEmptyish<mode, keyType>(base, key);
908 case KindOfBoolean:
909 return ElemDBoolean<mode, keyType>(tvRef, base, key);
910 case KindOfInt64:
911 case KindOfDouble:
912 case KindOfResource:
913 case KindOfFunc:
914 return ElemDScalar(tvRef);
915 case KindOfPersistentString:
916 case KindOfString:
917 return ElemDString<mode, keyType>(base, key);
918 case KindOfPersistentVec:
919 case KindOfVec:
920 return ElemDVec<reffy, keyType>(base, key);
921 case KindOfPersistentDict:
922 case KindOfDict:
923 return ElemDDict<reffy, keyType>(base, key);
924 case KindOfPersistentKeyset:
925 case KindOfKeyset:
926 return ElemDKeyset<reffy, keyType>(base, key);
927 case KindOfPersistentArray:
928 case KindOfArray: {
929 // We want to notice for binding assignments here, but not for missing
930 // index, since we're about to initialize the value in that case.
931 // Rather than fork the Lval API to have warn and no-warn flavors, we
932 // instead lift the binding assignment warning here, and then disable
933 // Hack array notices.
934 if (reffy && checkHACRefBind() &&
935 !HPHP::val(base).parr->isGlobalsArray()) {
936 raiseHackArrCompatRefBind(key);
938 SuppressHackArrCompatNotices shacn;
939 return ElemDArray<mode, reffy, intishWarn, keyType>(base, key);
941 case KindOfObject:
942 return ElemDObject<mode, reffy, keyType>(tvRef, base, key);
943 case KindOfRef:
944 break;
946 unknownBaseType(type(base));
947 }();
949 // Intentionally leak the old value pointed to by elem, including from magic
950 // methods.
951 tvDup(val, elem);
955 * ElemU when base is Null
957 inline tv_lval ElemUEmptyish() {
958 return const_cast<TypedValue*>(&immutable_null_base);
961 template <bool intishWarn>
962 inline tv_lval ElemUArrayImpl(tv_lval base, int64_t key) {
963 auto oldArr = val(base).parr;
964 if (!oldArr->exists(key)) return ElemUEmptyish();
965 auto const lval = oldArr->lval(key, oldArr->cowCheck());
966 if (lval.arr != oldArr) {
967 type(base) = KindOfArray;
968 val(base).parr = lval.arr;
969 assertx(cellIsPlausible(*base));
970 decRefArr(oldArr);
972 return lval;
975 template <bool intishWarn>
976 inline tv_lval ElemUArrayImpl(tv_lval base, StringData* key) {
977 auto oldArr = val(base).parr;
978 int64_t n;
979 if (oldArr->convertKey(key, n, intishWarn)) {
980 if (!oldArr->exists(n)) return ElemUEmptyish();
981 auto const lval = oldArr->lval(n, oldArr->cowCheck());
982 if (lval.arr != oldArr) {
983 type(base) = KindOfArray;
984 val(base).parr = lval.arr;
985 assertx(cellIsPlausible(*base));
986 decRefArr(oldArr);
988 return lval;
989 } else {
990 if (!oldArr->exists(key)) return ElemUEmptyish();
991 auto const lval = oldArr->lval(key, oldArr->cowCheck());
992 if (lval.arr != oldArr) {
993 type(base) = KindOfArray;
994 val(base).parr = lval.arr;
995 assertx(cellIsPlausible(*base));
996 decRefArr(oldArr);
998 return lval;
1002 template <bool intishWarn>
1003 inline tv_lval ElemUArrayImpl(tv_lval base, TypedValue key) {
1004 auto const dt = key.m_type;
1005 if (isIntType(dt)) {
1006 return ElemUArrayImpl<false>(base, key.m_data.num);
1008 if (isStringType(dt)) {
1009 return ElemUArrayImpl<intishWarn>(base, key.m_data.pstr);
1011 auto& arr = asArrRef(base);
1012 if (!arr.exists(keyAsValue(key))) {
1013 return ElemUEmptyish();
1015 return arr.lvalAt(tvAsCVarRef(&key));
1019 * ElemU when base is an Array
1021 template <bool intishWarn, KeyType keyType>
1022 inline tv_lval ElemUArray(tv_lval base, key_type<keyType> key) {
1023 assertx(tvIsArray(base));
1024 assertx(tvIsPlausible(*base));
1025 auto lval = ElemUArrayImpl<intishWarn>(base, key);
1026 assertx(tvIsArray(base));
1027 assertx(tvIsPlausible(*base));
1028 assertx(lval.type() != KindOfUninit);
1029 return lval;
1033 * ElemU when base is a Vec
1035 inline tv_lval ElemUVecPre(tv_lval base, int64_t key) {
1036 ArrayData* oldArr = val(base).parr;
1037 auto const lval =
1038 PackedArray::LvalSilentIntVec(oldArr, key, oldArr->cowCheck());
1040 if (UNLIKELY(!lval)) {
1041 return ElemUEmptyish();
1043 if (lval.arr != oldArr) {
1044 type(base) = KindOfVec;
1045 val(base).parr = lval.arr;
1046 assertx(cellIsPlausible(*base));
1047 decRefArr(oldArr);
1049 return lval;
1052 inline tv_lval ElemUVecPre(tv_lval /*base*/, StringData* /*key*/) {
1053 return ElemUEmptyish();
1056 inline tv_lval ElemUVecPre(tv_lval base, TypedValue key) {
1057 auto const dt = key.m_type;
1058 if (LIKELY(isIntType(dt))) return ElemUVecPre(base, key.m_data.num);
1059 if (isStringType(dt)) return ElemUVecPre(base, key.m_data.pstr);
1060 throwInvalidArrayKeyException(&key, val(base).parr);
1063 template <KeyType keyType>
1064 inline tv_lval ElemUVec(tv_lval base, key_type<keyType> key) {
1065 assertx(tvIsVec(base));
1066 assertx(tvIsPlausible(*base));
1067 auto result = ElemUVecPre(base, key);
1068 assertx(tvIsVec(base));
1069 assertx(tvIsPlausible(*base));
1070 assertx(type(result) != KindOfUninit);
1071 return result;
1075 * ElemU when base is a Dict
1077 inline tv_lval ElemUDictPre(tv_lval base, int64_t key) {
1078 ArrayData* oldArr = val(base).parr;
1079 auto const lval =
1080 MixedArray::LvalSilentIntDict(oldArr, key, oldArr->cowCheck());
1082 if (UNLIKELY(!lval)) {
1083 return ElemUEmptyish();
1085 if (lval.arr != oldArr) {
1086 type(base) = KindOfDict;
1087 val(base).parr = lval.arr;
1088 assertx(cellIsPlausible(*base));
1089 decRefArr(oldArr);
1091 return lval;
1094 inline tv_lval ElemUDictPre(tv_lval base, StringData* key) {
1095 ArrayData* oldArr = val(base).parr;
1096 auto const lval =
1097 MixedArray::LvalSilentStrDict(oldArr, key, oldArr->cowCheck());
1099 if (UNLIKELY(!lval)) {
1100 return ElemUEmptyish();
1102 if (lval.arr != oldArr) {
1103 type(base) = KindOfDict;
1104 val(base).parr = lval.arr;
1105 assertx(cellIsPlausible(*base));
1106 decRefArr(oldArr);
1108 return lval;
1111 inline tv_lval ElemUDictPre(tv_lval base, TypedValue key) {
1112 auto const dt = key.m_type;
1113 if (isIntType(dt)) return ElemUDictPre(base, key.m_data.num);
1114 if (isStringType(dt)) return ElemUDictPre(base, key.m_data.pstr);
1115 throwInvalidArrayKeyException(&key, val(base).parr);
1118 template <KeyType keyType>
1119 inline tv_lval ElemUDict(tv_lval base, key_type<keyType> key) {
1120 assertx(tvIsDict(base));
1121 assertx(tvIsPlausible(*base));
1122 auto result = ElemUDictPre(base, key);
1123 assertx(tvIsDict(base));
1124 assertx(tvIsPlausible(*base));
1125 assertx(type(result) != KindOfUninit);
1126 return result;
1130 * ElemU when base is a Keyset
1132 [[noreturn]] inline tv_lval
1133 ElemUKeysetPre(tv_lval /*base*/, int64_t /*key*/) {
1134 throwInvalidKeysetOperation();
1137 [[noreturn]] inline tv_lval
1138 ElemUKeysetPre(tv_lval /*base*/, StringData* /*key*/) {
1139 throwInvalidKeysetOperation();
1142 [[noreturn]]
1143 inline tv_lval ElemUKeysetPre(tv_lval base, TypedValue key) {
1144 auto const dt = key.m_type;
1145 if (isIntType(dt)) ElemUKeysetPre(base, key.m_data.num);
1146 if (isStringType(dt)) ElemUKeysetPre(base, key.m_data.pstr);
1147 throwInvalidArrayKeyException(&key, val(base).parr);
1150 template <KeyType keyType>
1151 [[noreturn]]
1152 inline tv_lval ElemUKeyset(tv_lval base, key_type<keyType> key) {
1153 assertx(tvIsKeyset(base));
1154 assertx(tvIsPlausible(*base));
1155 ElemUKeysetPre(base, key);
1159 * ElemU when base is an Object
1161 template <KeyType keyType>
1162 inline tv_lval ElemUObject(TypedValue& tvRef, tv_lval base,
1163 key_type<keyType> key) {
1164 auto const scratchKey = initScratchKey(key);
1165 if (LIKELY(val(base).pobj->isCollection())) {
1166 return collections::atLval(val(base).pobj, &scratchKey);
1168 tvRef = objOffsetGet(instanceFromTv(base), scratchKey);
1169 return &tvRef;
1173 * Intermediate Elem operation for an unsetting member instruction.
1175 * Returned pointer is not yet unboxed. (I.e. it cannot point into a RefData.)
1177 template <bool intishWarn, KeyType keyType = KeyType::Any>
1178 tv_lval ElemU(TypedValue& tvRef, tv_lval base, key_type<keyType> key) {
1179 base = tvToCell(base);
1180 assertx(cellIsPlausible(*base));
1182 switch (type(base)) {
1183 case KindOfUninit:
1184 case KindOfNull:
1185 case KindOfBoolean:
1186 case KindOfInt64:
1187 case KindOfDouble:
1188 case KindOfResource:
1189 case KindOfFunc:
1190 // Unset on scalar base never modifies the base, but the const_cast is
1191 // necessary to placate the type system.
1192 return const_cast<TypedValue*>(&immutable_uninit_base);
1193 case KindOfPersistentString:
1194 case KindOfString:
1195 raise_error(Strings::OP_NOT_SUPPORTED_STRING);
1196 return nullptr;
1197 case KindOfPersistentVec:
1198 case KindOfVec:
1199 return ElemUVec<keyType>(base, key);
1200 case KindOfPersistentDict:
1201 case KindOfDict:
1202 return ElemUDict<keyType>(base, key);
1203 case KindOfPersistentKeyset:
1204 case KindOfKeyset:
1205 return ElemUKeyset<keyType>(base, key);
1206 case KindOfPersistentArray:
1207 case KindOfArray:
1208 return ElemUArray<intishWarn, keyType>(base, key);
1209 case KindOfObject:
1210 return ElemUObject<keyType>(tvRef, base, key);
1211 case KindOfRef:
1212 break;
1214 unknownBaseType(type(base));
1218 * NewElem when base is Null
1220 inline tv_lval NewElemEmptyish(tv_lval base) {
1221 detail::checkPromotion(base);
1222 tvMove(make_tv<KindOfArray>(ArrayData::Create()), base);
1223 return asArrRef(base).lvalAt();
1227 * NewElem when base is not a valid type (a number, true boolean,
1228 * non-empty string, etc.)
1230 inline tv_lval NewElemInvalid(TypedValue& tvRef) {
1231 raise_warning("Cannot use a scalar value as an array");
1232 tvWriteNull(tvRef);
1233 return tv_lval(&tvRef);
1237 * NewElem when base is a Boolean
1239 inline tv_lval NewElemBoolean(TypedValue& tvRef, tv_lval base) {
1240 return val(base).num
1241 ? NewElemInvalid(tvRef)
1242 : NewElemEmptyish(base);
1246 * NewElem when base is a String
1248 inline tv_lval NewElemString(TypedValue& tvRef, tv_lval base) {
1249 if (val(base).pstr->size() == 0) {
1250 return NewElemEmptyish(base);
1252 return NewElemInvalid(tvRef);
1256 * NewElem when base is an Array
1258 template <bool reffy>
1259 inline tv_lval NewElemArray(tv_lval base) {
1260 assertx(tvIsArray(base));
1261 assertx(tvIsPlausible(*base));
1262 return reffy ? asArrRef(base).lvalAtRef() : asArrRef(base).lvalAt();
1266 * NewElem when base is an Object
1268 inline tv_lval NewElemObject(TypedValue& tvRef, tv_lval base) {
1269 if (val(base).pobj->isCollection()) {
1270 throw_cannot_use_newelem_for_lval_read_col();
1271 return nullptr;
1273 tvRef = objOffsetGet(instanceFromTv(base), make_tv<KindOfNull>());
1274 return &tvRef;
1278 * $result = ($base[] = ...);
1280 template <bool reffy>
1281 inline tv_lval NewElem(TypedValue& tvRef, tv_lval base) {
1282 base = base.unboxed();
1283 assertx(cellIsPlausible(base.tv()));
1285 switch (base.type()) {
1286 case KindOfUninit:
1287 case KindOfNull:
1288 return NewElemEmptyish(base);
1289 case KindOfBoolean:
1290 return NewElemBoolean(tvRef, base);
1291 case KindOfInt64:
1292 case KindOfDouble:
1293 case KindOfResource:
1294 case KindOfFunc:
1295 return NewElemInvalid(tvRef);
1296 case KindOfPersistentString:
1297 case KindOfString:
1298 return NewElemString(tvRef, base);
1299 case KindOfPersistentVec:
1300 case KindOfVec:
1301 throw_cannot_use_newelem_for_lval_read_vec();
1302 case KindOfPersistentDict:
1303 case KindOfDict:
1304 throw_cannot_use_newelem_for_lval_read_dict();
1305 case KindOfPersistentKeyset:
1306 case KindOfKeyset:
1307 throw_cannot_use_newelem_for_lval_read_keyset();
1308 case KindOfPersistentArray:
1309 case KindOfArray:
1310 return NewElemArray<reffy>(base);
1311 case KindOfObject:
1312 return NewElemObject(tvRef, base);
1313 case KindOfRef:
1314 break;
1316 unknownBaseType(type(base));
1320 * SetElem when base is Null
1322 template <KeyType keyType>
1323 inline void SetElemEmptyish(tv_lval base, key_type<keyType> key,
1324 Cell* value) {
1325 detail::checkPromotion(base);
1326 auto const& scratchKey = initScratchKey(key);
1327 cellMove(make_tv<KindOfArray>(staticEmptyArray()), base);
1328 asArrRef(base).set(tvAsCVarRef(&scratchKey), tvAsCVarRef(value));
1332 * SetElem when base is an Int64, Double, Resource, or Func.
1334 template <bool setResult>
1335 inline void SetElemScalar(Cell* value) {
1336 raise_warning(Strings::CANNOT_USE_SCALAR_AS_ARRAY);
1337 if (!setResult) {
1338 throw InvalidSetMException(make_tv<KindOfNull>());
1340 tvDecRefGen(value);
1341 tvWriteNull(*value);
1345 * SetElem when base is a Boolean
1347 template <bool setResult, KeyType keyType>
1348 inline void SetElemBoolean(tv_lval base, key_type<keyType> key,
1349 Cell* value) {
1350 if (val(base).num) {
1351 SetElemScalar<setResult>(value);
1352 } else {
1353 SetElemEmptyish<keyType>(base, key, value);
1358 * Convert a key to integer for SetElem
1360 template<KeyType keyType>
1361 inline int64_t castKeyToInt(key_type<keyType> key) {
1362 return cellToInt(initScratchKey(key));
1365 template<>
1366 inline int64_t castKeyToInt<KeyType::Int>(int64_t key) {
1367 return key;
1371 * SetElem when base is a String
1373 template <bool setResult, KeyType keyType>
1374 inline StringData* SetElemString(tv_lval base, key_type<keyType> key,
1375 Cell* value) {
1376 int baseLen = val(base).pstr->size();
1377 if (baseLen == 0) {
1378 SetElemEmptyish<keyType>(base, key, value);
1379 if (!setResult) {
1380 tvIncRefGen(*value);
1381 throw InvalidSetMException(*value);
1383 return nullptr;
1386 // Convert key to string offset.
1387 int64_t x = castKeyToInt<keyType>(key);
1388 if (UNLIKELY(x < 0 || x >= StringData::MaxSize)) {
1389 // Can't use PRId64 here because of order of inclusion issues
1390 raise_warning("Illegal string offset: %lld", (long long)x);
1391 if (!setResult) {
1392 throw InvalidSetMException(make_tv<KindOfNull>());
1394 tvDecRefGen(value);
1395 tvWriteNull(*value);
1396 return nullptr;
1399 // Compute how long the resulting string will be. Type needs
1400 // to agree with x.
1401 int64_t slen;
1402 if (x >= baseLen) {
1403 slen = x + 1;
1404 } else {
1405 slen = baseLen;
1408 // Extract the first character of (string)value.
1409 char y[2];
1411 StringData* valStr;
1412 if (LIKELY(isStringType(value->m_type))) {
1413 valStr = value->m_data.pstr;
1414 valStr->incRefCount();
1415 } else {
1416 valStr = tvCastToStringData(*value);
1419 if (valStr->size() > 0) {
1420 y[0] = valStr->data()[0];
1421 y[1] = '\0';
1422 } else {
1423 y[0] = '\0';
1425 decRefStr(valStr);
1428 // Create and save the result.
1429 if (x >= 0 && x < baseLen && !val(base).pstr->cowCheck()) {
1430 // Modify base in place. This is safe because the LHS owns the
1431 // only reference.
1432 auto const oldp = val(base).pstr;
1433 auto const newp = oldp->modifyChar(x, y[0]);
1434 if (UNLIKELY(newp != oldp)) {
1435 decRefStr(oldp);
1436 val(base).pstr = newp;
1437 type(base) = KindOfString;
1439 } else {
1440 StringData* sd = StringData::Make(slen);
1441 char* s = sd->mutableData();
1442 memcpy(s, val(base).pstr->data(), baseLen);
1443 if (x > baseLen) {
1444 memset(&s[baseLen], ' ', slen - baseLen - 1);
1446 s[x] = y[0];
1447 sd->setSize(slen);
1448 decRefStr(val(base).pstr);
1449 val(base).pstr = sd;
1450 type(base) = KindOfString;
1453 return StringData::Make(y, strlen(y), CopyString);
1457 * SetElem when base is an Object
1459 template <KeyType keyType>
1460 inline void SetElemObject(tv_lval base, key_type<keyType> key,
1461 Cell* value) {
1462 auto const scratchKey = initScratchKey(key);
1463 if (LIKELY(val(base).pobj->isCollection())) {
1464 collections::set(val(base).pobj, &scratchKey, value);
1465 } else {
1466 objOffsetSet(instanceFromTv(base), scratchKey, value);
1471 * arrayRefShuffle is used by SetElemArray and by helpers for translated code
1472 * to do the necessary bookkeeping after mutating an array. The helpers return
1473 * an ArrayData* if and only if the base array was not in a php reference. If
1474 * the base array was in a reference, that reference may no longer refer to an
1475 * array after the set operation, so the helpers don't return anything.
1477 template<bool setRef> struct ShuffleReturn {};
1479 template<> struct ShuffleReturn<true> {
1480 static void do_return(ArrayData* /*a*/) {}
1483 template<> struct ShuffleReturn<false> {
1484 static ArrayData* do_return(ArrayData* a) { return a; }
1487 template<bool setRef, DataType dt> inline
1488 auto arrayRefShuffle(ArrayData* oldData, ArrayData* newData, tv_lval base) {
1489 if (newData == oldData) {
1490 return ShuffleReturn<setRef>::do_return(oldData);
1493 if (setRef) {
1494 if (isArrayLikeType(type(base)) && val(base).parr == oldData) {
1495 type(base) = dt;
1496 val(base).parr = newData;
1497 assertx(cellIsPlausible(*base));
1498 } else {
1499 // The base was in a reference that was overwritten by the set operation,
1500 // so we don't want to store the new ArrayData to it. oldData has already
1501 // been decrefed and there's nobody left to care about newData, so decref
1502 // newData instead of oldData.
1503 oldData = newData;
1506 decRefArr(oldData);
1507 return ShuffleReturn<setRef>::do_return(newData);
1512 * SetElem helper with Array base and Int64 key
1514 template<bool setResult, bool intishWarn>
1515 inline ArrayData* SetElemArrayPre(ArrayData* a,
1516 int64_t key,
1517 Cell* value,
1518 bool copy) {
1519 return a->set(key, *value, copy);
1523 * SetElem helper with Array base and String key
1525 template<bool setResult, bool intishWarn>
1526 inline ArrayData* SetElemArrayPre(ArrayData* a,
1527 StringData* key,
1528 Cell* value,
1529 bool copy) {
1530 int64_t n;
1531 assertx(a->isPHPArray());
1532 if (key->isStrictlyInteger(n)) {
1533 if (intishWarn) raise_intish_index_cast();
1534 return a->set(n, *value, copy);
1535 } else {
1536 return a->set(key, *value, copy);
1540 template<bool setResult, bool intishWarn>
1541 inline ArrayData* SetElemArrayPre(ArrayData* a,
1542 TypedValue key,
1543 Cell* value,
1544 bool copy) {
1545 if (isStringType(key.m_type)) {
1546 return SetElemArrayPre<setResult, intishWarn>(
1547 a, key.m_data.pstr, value, copy
1550 if (key.m_type == KindOfInt64) {
1551 return SetElemArrayPre<setResult, false>(
1552 a, key.m_data.num, value, copy
1555 if (checkHACMisc()) {
1556 raiseHackArrCompatImplicitArrayKey(&key);
1558 if (isNullType(key.m_type)) {
1559 return a->set(staticEmptyString(), *value, copy);
1561 if (!isArrayLikeType(key.m_type) && key.m_type != KindOfObject) {
1562 return SetElemArrayPre<setResult, false>(a, tvAsCVarRef(&key).toInt64(),
1563 value, copy);
1566 raise_warning("Illegal offset type");
1567 // Assignment failed, so the result is null rather than the RHS.
1568 if (setResult) {
1569 tvDecRefGen(value);
1570 tvWriteNull(*value);
1571 } else {
1572 throw InvalidSetMException(make_tv<KindOfNull>());
1574 return a;
1578 * SetElem when base is an Array
1580 template <bool setResult, KeyType keyType, bool intishWarn>
1581 inline void SetElemArray(tv_lval base, key_type<keyType> key,
1582 Cell* value) {
1583 assertx(tvIsArray(base));
1584 assertx(tvIsPlausible(*base));
1586 ArrayData* a = val(base).parr;
1587 bool copy = a->cowCheck() ||
1588 (tvIsArray(value) && value->m_data.parr == a);
1590 auto* newData = SetElemArrayPre<setResult, intishWarn>(a, key, value, copy);
1591 assertx(newData->isPHPArray());
1593 arrayRefShuffle<true, KindOfArray>(a, newData, base);
1597 * SetElem when base is a Vec
1599 template<bool setResult>
1600 inline ArrayData* SetElemVecPre(ArrayData* a,
1601 int64_t key,
1602 Cell* value,
1603 bool copy) {
1604 return PackedArray::SetIntVec(a, key, *value, copy);
1607 template <bool setResult>
1608 inline ArrayData*
1609 SetElemVecPre(ArrayData* a, StringData* key, Cell* /*value*/, bool /*copy*/) {
1610 throwInvalidArrayKeyException(key, a);
1613 template<bool setResult>
1614 inline ArrayData* SetElemVecPre(ArrayData* a,
1615 TypedValue key,
1616 Cell* value,
1617 bool copy) {
1618 auto const dt = key.m_type;
1619 if (LIKELY(isIntType(dt))) return SetElemVecPre<setResult>(a, key.m_data.num,
1620 value, copy);
1621 if (isStringType(dt)) return SetElemVecPre<setResult>(a, key.m_data.pstr,
1622 value, copy);
1623 throwInvalidArrayKeyException(&key, a);
1626 template <bool setResult, KeyType keyType>
1627 inline void SetElemVec(tv_lval base, key_type<keyType> key,
1628 Cell* value) {
1629 assertx(tvIsVec(base));
1630 assertx(tvIsPlausible(*base));
1632 ArrayData* a = val(base).parr;
1633 bool copy = a->cowCheck() || (tvIsVec(value) && value->m_data.parr == a);
1635 auto* newData = SetElemVecPre<setResult>(a, key, value, copy);
1636 assertx(newData->isVecArray());
1638 arrayRefShuffle<true, KindOfVec>(a, newData, base);
1642 * SetElem when base is a Dict
1644 template<bool setResult>
1645 inline ArrayData* SetElemDictPre(ArrayData* a,
1646 int64_t key,
1647 Cell* value,
1648 bool copy) {
1649 return MixedArray::SetIntDict(a, key, *value, copy);
1652 template<bool setResult>
1653 inline ArrayData* SetElemDictPre(ArrayData* a,
1654 StringData* key,
1655 Cell* value,
1656 bool copy) {
1657 return MixedArray::SetStrDict(a, key, *value, copy);
1660 template<bool setResult>
1661 inline ArrayData* SetElemDictPre(ArrayData* a,
1662 TypedValue key,
1663 Cell* value,
1664 bool copy) {
1665 auto const dt = key.m_type;
1666 if (isIntType(dt)) return SetElemDictPre<setResult>(a, key.m_data.num,
1667 value, copy);
1668 if (isStringType(dt)) return SetElemDictPre<setResult>(a, key.m_data.pstr,
1669 value, copy);
1670 throwInvalidArrayKeyException(&key, a);
1673 template <bool setResult, KeyType keyType>
1674 inline void SetElemDict(tv_lval base, key_type<keyType> key,
1675 Cell* value) {
1676 assertx(tvIsDict(base));
1677 assertx(tvIsPlausible(*base));
1679 ArrayData* a = val(base).parr;
1680 bool copy = a->cowCheck() ||
1681 (tvIsDict(value) && value->m_data.parr == a);
1683 auto newData = SetElemDictPre<setResult>(a, key, value, copy);
1684 assertx(newData->isDict());
1686 arrayRefShuffle<true, KindOfDict>(a, newData, base);
1690 * SetElem() leaves the result in 'value', rather than returning it as in
1691 * SetOpElem(), because doing so avoids a dup operation that SetOpElem() can't
1692 * get around.
1694 template <bool setResult, KeyType keyType, bool intishWarn>
1695 NEVER_INLINE
1696 StringData* SetElemSlow(tv_lval base, key_type<keyType> key, Cell* value) {
1697 base = tvToCell(base);
1698 assertx(cellIsPlausible(*base));
1700 switch (type(base)) {
1701 case KindOfUninit:
1702 case KindOfNull:
1703 SetElemEmptyish<keyType>(base, key, value);
1704 return nullptr;
1705 case KindOfBoolean:
1706 SetElemBoolean<setResult, keyType>(base, key, value);
1707 return nullptr;
1708 case KindOfInt64:
1709 case KindOfDouble:
1710 case KindOfResource:
1711 case KindOfFunc:
1712 SetElemScalar<setResult>(value);
1713 return nullptr;
1714 case KindOfPersistentString:
1715 case KindOfString:
1716 return SetElemString<setResult, keyType>(base, key, value);
1717 case KindOfPersistentVec:
1718 case KindOfVec:
1719 SetElemVec<setResult, keyType>(base, key, value);
1720 return nullptr;
1721 case KindOfPersistentDict:
1722 case KindOfDict:
1723 SetElemDict<setResult, keyType>(base, key, value);
1724 return nullptr;
1725 case KindOfPersistentKeyset:
1726 case KindOfKeyset:
1727 throwInvalidKeysetOperation();
1728 case KindOfPersistentArray:
1729 case KindOfArray:
1730 SetElemArray<setResult, keyType, intishWarn>(base, key, value);
1731 return nullptr;
1732 case KindOfObject:
1733 SetElemObject<keyType>(base, key, value);
1734 return nullptr;
1735 case KindOfRef:
1736 break;
1738 unknownBaseType(type(base));
1742 * Fast path for SetElem assuming base is an Array
1744 template <bool setResult, bool intishWarn, KeyType keyType = KeyType::Any>
1745 inline StringData* SetElem(tv_lval base, key_type<keyType> key,
1746 Cell* value) {
1747 assertx(tvIsPlausible(*base));
1749 if (LIKELY(tvIsArray(base))) {
1750 SetElemArray<setResult, keyType, intishWarn>(base, key, value);
1751 return nullptr;
1753 if (LIKELY(tvIsVec(base))) {
1754 SetElemVec<setResult, keyType>(base, key, value);
1755 return nullptr;
1757 if (LIKELY(tvIsDict(base))) {
1758 SetElemDict<setResult, keyType>(base, key, value);
1759 return nullptr;
1761 return SetElemSlow<setResult, keyType, intishWarn>(base, key, value);
1765 * SetNewElem when base is Null
1767 inline void SetNewElemEmptyish(tv_lval base, Cell* value) {
1768 detail::checkPromotion(base);
1769 Array a = Array::Create();
1770 a.append(cellAsCVarRef(*value));
1771 cellMove(make_tv<KindOfArray>(a.detach()), base);
1775 * SetNewElem when base is Int64, Double, Resource or Func
1777 template <bool setResult>
1778 inline void SetNewElemScalar(Cell* value) {
1779 raise_warning(Strings::CANNOT_USE_SCALAR_AS_ARRAY);
1780 if (!setResult) {
1781 throw InvalidSetMException(make_tv<KindOfNull>());
1783 tvDecRefGen(value);
1784 tvWriteNull(*value);
1788 * SetNewElem when base is a Boolean
1790 template <bool setResult>
1791 inline void SetNewElemBoolean(tv_lval base, Cell* value) {
1792 if (val(base).num) {
1793 SetNewElemScalar<setResult>(value);
1794 } else {
1795 SetNewElemEmptyish(base, value);
1800 * SetNewElem when base is a String
1802 inline void SetNewElemString(tv_lval base, Cell* value) {
1803 int baseLen = val(base).pstr->size();
1804 if (baseLen == 0) {
1805 SetNewElemEmptyish(base, value);
1806 } else {
1807 raise_error("[] operator not supported for strings");
1812 * SetNewElem when base is an Array
1814 inline void SetNewElemArray(tv_lval base, Cell* value) {
1815 base = tvToCell(base);
1816 assertx(tvIsArray(base));
1817 assertx(tvIsPlausible(*base));
1818 auto a = val(base).parr;
1819 auto const copy = a->cowCheck() ||
1820 (tvIsArray(value) && value->m_data.parr == a);
1821 auto a2 = a->append(*value, copy);
1822 if (a2 != a) {
1823 type(base) = KindOfArray;
1824 val(base).parr = a2;
1825 a->decRefAndRelease();
1830 * SetNewElem when base is a Vec
1832 inline void SetNewElemVec(tv_lval base, Cell* value) {
1833 base = tvToCell(base);
1834 assertx(tvIsVec(base));
1835 assertx(tvIsPlausible(*base));
1836 auto a = val(base).parr;
1837 auto const copy = a->cowCheck() ||
1838 (tvIsVec(value) && value->m_data.parr == a);
1839 auto a2 = PackedArray::AppendVec(a, *value, copy);
1840 if (a2 != a) {
1841 type(base) = KindOfVec;
1842 val(base).parr = a2;
1843 assertx(cellIsPlausible(*base));
1844 a->decRefAndRelease();
1849 * SetNewElem when base is a Dict
1851 inline void SetNewElemDict(tv_lval base, Cell* value) {
1852 base = tvToCell(base);
1853 assertx(tvIsDict(base));
1854 assertx(tvIsPlausible(*base));
1855 auto a = val(base).parr;
1856 auto const copy = a->cowCheck() ||
1857 (tvIsDict(value) && value->m_data.parr == a);
1858 auto a2 = MixedArray::AppendDict(a, *value, copy);
1859 if (a2 != a) {
1860 type(base) = KindOfDict;
1861 val(base).parr = a2;
1862 assertx(cellIsPlausible(*base));
1863 a->decRefAndRelease();
1868 * SetNewElem when base is a Keyset
1870 inline void SetNewElemKeyset(tv_lval base, Cell* value) {
1871 base = tvToCell(base);
1872 assertx(tvIsKeyset(base));
1873 assertx(tvIsPlausible(*base));
1874 auto a = val(base).parr;
1875 auto const copy = a->cowCheck() ||
1876 (tvIsKeyset(value) && value->m_data.parr == a);
1877 auto a2 = SetArray::Append(a, *value, copy);
1878 if (a2 != a) {
1879 type(base) = KindOfKeyset;
1880 val(base).parr = a2;
1881 assertx(cellIsPlausible(*base));
1882 a->decRefAndRelease();
1887 * SetNewElem when base is an Object
1889 inline void SetNewElemObject(tv_lval base, Cell* value) {
1890 if (LIKELY(val(base).pobj->isCollection())) {
1891 collections::append(val(base).pobj, value);
1892 } else {
1893 objOffsetAppend(instanceFromTv(base), value);
1898 * $base[] = ...
1900 template <bool setResult>
1901 inline void SetNewElem(tv_lval base, Cell* value) {
1902 base = tvToCell(base);
1903 assertx(cellIsPlausible(*base));
1905 switch (type(base)) {
1906 case KindOfUninit:
1907 case KindOfNull:
1908 return SetNewElemEmptyish(base, value);
1909 case KindOfBoolean:
1910 return SetNewElemBoolean<setResult>(base, value);
1911 case KindOfInt64:
1912 case KindOfDouble:
1913 case KindOfResource:
1914 case KindOfFunc:
1915 return SetNewElemScalar<setResult>(value);
1916 case KindOfPersistentString:
1917 case KindOfString:
1918 return SetNewElemString(base, value);
1919 case KindOfPersistentVec:
1920 case KindOfVec:
1921 return SetNewElemVec(base, value);
1922 case KindOfPersistentDict:
1923 case KindOfDict:
1924 return SetNewElemDict(base, value);
1925 case KindOfPersistentKeyset:
1926 case KindOfKeyset:
1927 return SetNewElemKeyset(base, value);
1928 case KindOfPersistentArray:
1929 case KindOfArray:
1930 return SetNewElemArray(base, value);
1931 case KindOfObject:
1932 return SetNewElemObject(base, value);
1933 case KindOfRef:
1934 break;
1936 unknownBaseType(type(base));
1940 * SetOpElem when base is Null
1942 inline tv_lval SetOpElemEmptyish(SetOpOp op, tv_lval base,
1943 TypedValue key, Cell* rhs) {
1944 assertx(cellIsPlausible(*base));
1946 detail::checkPromotion(base);
1948 cellMove(make_tv<KindOfArray>(staticEmptyArray()), base);
1949 auto const lval = asArrRef(base).lvalAt(tvAsCVarRef(&key));
1950 if (MoreWarnings) {
1951 raise_notice(Strings::UNDEFINED_INDEX,
1952 tvAsCVarRef(&key).toString().data());
1954 setopBody(lval, op, rhs);
1955 return lval;
1959 * SetOpElem when base is Int64, Double, Resource or Func
1961 inline tv_lval SetOpElemScalar(TypedValue& tvRef) {
1962 raise_warning(Strings::CANNOT_USE_SCALAR_AS_ARRAY);
1963 tvWriteNull(tvRef);
1964 return &tvRef;
1968 * $result = ($base[$x] <op>= $y)
1970 template <bool intishWarn>
1971 inline tv_lval SetOpElem(TypedValue& tvRef,
1972 SetOpOp op, tv_lval base,
1973 TypedValue key, Cell* rhs) {
1974 base = tvToCell(base);
1975 assertx(cellIsPlausible(*base));
1977 switch (type(base)) {
1978 case KindOfUninit:
1979 case KindOfNull:
1980 return SetOpElemEmptyish(op, base, key, rhs);
1982 case KindOfBoolean:
1983 if (val(base).num) {
1984 return SetOpElemScalar(tvRef);
1986 return SetOpElemEmptyish(op, base, key, rhs);
1988 case KindOfInt64:
1989 case KindOfDouble:
1990 case KindOfResource:
1991 case KindOfFunc:
1992 return SetOpElemScalar(tvRef);
1994 case KindOfPersistentString:
1995 case KindOfString:
1996 if (val(base).pstr->size() != 0) {
1997 raise_error("Cannot use assign-op operators with overloaded "
1998 "objects nor string offsets");
2000 return SetOpElemEmptyish(op, base, key, rhs);
2002 case KindOfPersistentVec:
2003 case KindOfVec: {
2004 auto result = ElemDVec<false, KeyType::Any>(base, key);
2005 result = tvAssertCell(result);
2006 setopBody(result, op, rhs);
2007 return result;
2010 case KindOfPersistentDict:
2011 case KindOfDict: {
2012 auto result = ElemDDict<false, KeyType::Any>(base, key);
2013 result = tvAssertCell(result);
2014 setopBody(result, op, rhs);
2015 return result;
2018 case KindOfPersistentKeyset:
2019 case KindOfKeyset:
2020 throwInvalidKeysetOperation();
2022 case KindOfPersistentArray:
2023 case KindOfArray: {
2024 if (UNLIKELY(
2025 checkHACFalseyPromote() &&
2026 !asCArrRef(base).exists(tvAsCVarRef(&key))
2027 )) {
2028 raiseHackArrCompatMissingSetOp();
2030 auto constexpr mode = MoreWarnings ? MOpMode::Warn : MOpMode::None;
2031 auto result =
2032 ElemDArray<mode, false, intishWarn, KeyType::Any>(base, key);
2033 result = tvToCell(result);
2034 setopBody(result, op, rhs);
2035 return result;
2038 case KindOfObject: {
2039 TypedValue* result;
2040 if (LIKELY(val(base).pobj->isCollection())) {
2041 result = collections::atRw(val(base).pobj, &key);
2042 setopBody(tvToCell(result), op, rhs);
2043 } else {
2044 tvRef = objOffsetGet(instanceFromTv(base), key);
2045 result = &tvRef;
2046 setopBody(tvToCell(result), op, rhs);
2047 objOffsetSet(instanceFromTv(base), key, result, false);
2049 return result;
2052 case KindOfRef:
2053 break;
2055 unknownBaseType(type(base));
2058 inline tv_lval SetOpNewElemEmptyish(SetOpOp op, tv_lval base, Cell* rhs) {
2059 detail::checkPromotion(base);
2060 cellMove(make_tv<KindOfArray>(staticEmptyArray()), base);
2061 auto result = asArrRef(base).lvalAt();
2062 setopBody(tvToCell(result), op, rhs);
2063 return result;
2065 inline tv_lval SetOpNewElemScalar(TypedValue& tvRef) {
2066 raise_warning(Strings::CANNOT_USE_SCALAR_AS_ARRAY);
2067 tvWriteNull(tvRef);
2068 return &tvRef;
2070 inline tv_lval SetOpNewElem(TypedValue& tvRef,
2071 SetOpOp op, tv_lval base,
2072 Cell* rhs) {
2073 base = tvToCell(base);
2074 assertx(cellIsPlausible(*base));
2076 switch (type(base)) {
2077 case KindOfUninit:
2078 case KindOfNull:
2079 return SetOpNewElemEmptyish(op, base, rhs);
2081 case KindOfBoolean:
2082 if (val(base).num) {
2083 return SetOpNewElemScalar(tvRef);
2085 return SetOpNewElemEmptyish(op, base, rhs);
2087 case KindOfInt64:
2088 case KindOfDouble:
2089 case KindOfResource:
2090 case KindOfFunc:
2091 return SetOpNewElemScalar(tvRef);
2093 case KindOfPersistentString:
2094 case KindOfString:
2095 if (val(base).pstr->size() != 0) {
2096 raise_error("[] operator not supported for strings");
2098 return SetOpNewElemEmptyish(op, base, rhs);
2100 case KindOfPersistentVec:
2101 case KindOfVec:
2102 throw_cannot_use_newelem_for_lval_read_vec();
2103 case KindOfPersistentDict:
2104 case KindOfDict:
2105 throw_cannot_use_newelem_for_lval_read_dict();
2106 case KindOfPersistentKeyset:
2107 case KindOfKeyset:
2108 throw_cannot_use_newelem_for_lval_read_keyset();
2110 case KindOfPersistentArray:
2111 case KindOfArray: {
2112 auto result = asArrRef(base).lvalAt();
2113 setopBody(result, op, rhs);
2114 return result;
2117 case KindOfObject: {
2118 TypedValue* result;
2119 if (val(base).pobj->isCollection()) {
2120 throw_cannot_use_newelem_for_lval_read_col();
2121 result = nullptr;
2122 } else {
2123 tvRef = objOffsetGet(instanceFromTv(base), make_tv<KindOfNull>());
2124 result = &tvRef;
2125 setopBody(tvToCell(result), op, rhs);
2126 objOffsetAppend(instanceFromTv(base), result, false);
2128 return result;
2131 case KindOfRef:
2132 break;
2134 unknownBaseType(type(base));
2137 NEVER_INLINE Cell incDecBodySlow(IncDecOp op, tv_lval fr);
2139 inline Cell IncDecBody(IncDecOp op, tv_lval fr) {
2140 assertx(cellIsPlausible(*fr));
2142 if (UNLIKELY(!isIntType(type(fr)))) {
2143 return incDecBodySlow(op, fr);
2146 // fast cases, assuming integers overflow to ints
2147 switch (op) {
2148 case IncDecOp::PreInc:
2149 ++val(fr).num;
2150 return *fr;
2151 case IncDecOp::PostInc: {
2152 auto const tmp = *fr;
2153 ++val(fr).num;
2154 return tmp;
2156 case IncDecOp::PreDec:
2157 --val(fr).num;
2158 return *fr;
2159 case IncDecOp::PostDec: {
2160 auto const tmp = *fr;
2161 --val(fr).num;
2162 return tmp;
2164 default:
2165 return incDecBodySlow(op, fr);
2169 inline Cell IncDecElemEmptyish(
2170 IncDecOp op,
2171 tv_lval base,
2172 TypedValue key
2174 detail::checkPromotion(base);
2176 cellMove(make_tv<KindOfArray>(staticEmptyArray()), base);
2177 auto const lval = asArrRef(base).lvalAt(tvAsCVarRef(&key));
2178 if (MoreWarnings) {
2179 raise_notice(Strings::UNDEFINED_INDEX,
2180 tvAsCVarRef(&key).toString().data());
2182 assertx(type(lval) == KindOfNull);
2183 return IncDecBody(op, lval);
2186 inline Cell IncDecElemScalar() {
2187 raise_warning(Strings::CANNOT_USE_SCALAR_AS_ARRAY);
2188 return make_tv<KindOfNull>();
2191 template <bool intishWarn>
2192 inline Cell IncDecElem(
2193 IncDecOp op,
2194 tv_lval base,
2195 TypedValue key
2197 base = tvToCell(base);
2198 assertx(cellIsPlausible(*base));
2200 switch (type(base)) {
2201 case KindOfUninit:
2202 case KindOfNull:
2203 return IncDecElemEmptyish(op, base, key);
2205 case KindOfBoolean:
2206 if (val(base).num) {
2207 return IncDecElemScalar();
2209 return IncDecElemEmptyish(op, base, key);
2211 case KindOfInt64:
2212 case KindOfDouble:
2213 case KindOfResource:
2214 case KindOfFunc:
2215 return IncDecElemScalar();
2217 case KindOfPersistentString:
2218 case KindOfString:
2219 if (val(base).pstr->size() != 0) {
2220 raise_error("Cannot increment/decrement overloaded objects "
2221 "nor string offsets");
2223 return IncDecElemEmptyish(op, base, key);
2225 case KindOfPersistentVec:
2226 case KindOfVec: {
2227 auto result = ElemDVec<false, KeyType::Any>(base, key);
2228 return IncDecBody(op, tvAssertCell(result));
2231 case KindOfPersistentDict:
2232 case KindOfDict: {
2233 auto result = ElemDDict<false, KeyType::Any>(base, key);
2234 return IncDecBody(op, tvAssertCell(result));
2237 case KindOfPersistentKeyset:
2238 case KindOfKeyset:
2239 throwInvalidKeysetOperation();
2241 case KindOfPersistentArray:
2242 case KindOfArray: {
2243 if (UNLIKELY(
2244 checkHACFalseyPromote() &&
2245 !asCArrRef(base).exists(tvAsCVarRef(&key))
2246 )) {
2247 raiseHackArrCompatMissingIncDec();
2249 auto constexpr mode = MoreWarnings ? MOpMode::Warn : MOpMode::None;
2250 auto result =
2251 ElemDArray<mode, false, intishWarn, KeyType::Any>(base, key);
2252 return IncDecBody(op, tvToCell(result));
2255 case KindOfObject: {
2256 tv_lval result;
2257 auto localTvRef = make_tv<KindOfUninit>();
2259 if (LIKELY(val(base).pobj->isCollection())) {
2260 result = collections::atRw(val(base).pobj, &key);
2261 assertx(cellIsPlausible(*result));
2262 } else {
2263 localTvRef = objOffsetGet(instanceFromTv(base), key);
2264 result = tvToCell(&localTvRef);
2267 auto const dest = IncDecBody(op, result);
2268 tvDecRefGen(localTvRef);
2269 return dest;
2272 case KindOfRef:
2273 break;
2275 unknownBaseType(type(base));
2278 inline Cell IncDecNewElemEmptyish(
2279 IncDecOp op,
2280 tv_lval base
2282 detail::checkPromotion(base);
2283 cellMove(make_tv<KindOfArray>(staticEmptyArray()), base);
2284 auto result = asArrRef(base).lvalAt();
2285 assertx(type(result) == KindOfNull);
2286 return IncDecBody(op, result);
2289 inline Cell IncDecNewElemScalar() {
2290 raise_warning(Strings::CANNOT_USE_SCALAR_AS_ARRAY);
2291 return make_tv<KindOfNull>();
2294 inline Cell IncDecNewElem(
2295 TypedValue& tvRef,
2296 IncDecOp op,
2297 tv_lval base
2299 base = tvToCell(base);
2300 assertx(cellIsPlausible(*base));
2302 switch (type(base)) {
2303 case KindOfUninit:
2304 case KindOfNull:
2305 return IncDecNewElemEmptyish(op, base);
2307 case KindOfBoolean:
2308 if (val(base).num) {
2309 return IncDecNewElemScalar();
2311 return IncDecNewElemEmptyish(op, base);
2313 case KindOfInt64:
2314 case KindOfDouble:
2315 case KindOfResource:
2316 case KindOfFunc:
2317 return IncDecNewElemScalar();
2319 case KindOfPersistentString:
2320 case KindOfString:
2321 if (val(base).pstr->size() != 0) {
2322 raise_error("[] operator not supported for strings");
2324 return IncDecNewElemEmptyish(op, base);
2326 case KindOfPersistentVec:
2327 case KindOfVec:
2328 throw_cannot_use_newelem_for_lval_read_vec();
2329 case KindOfPersistentDict:
2330 case KindOfDict:
2331 throw_cannot_use_newelem_for_lval_read_dict();
2332 case KindOfPersistentKeyset:
2333 case KindOfKeyset:
2334 throw_cannot_use_newelem_for_lval_read_keyset();
2336 case KindOfPersistentArray:
2337 case KindOfArray: {
2338 auto result = asArrRef(base).lvalAt();
2339 assertx(type(result) == KindOfNull);
2340 return IncDecBody(op, result);
2343 case KindOfObject: {
2344 if (val(base).pobj->isCollection()) {
2345 throw_cannot_use_newelem_for_lval_read_col();
2347 tvRef = objOffsetGet(instanceFromTv(base), make_tv<KindOfNull>());
2348 auto result = tvToCell(&tvRef);
2349 return IncDecBody(op, result);
2352 case KindOfRef:
2353 break;
2355 unknownBaseType(type(base));
2359 * UnsetElemArray when key is an Int64
2361 template <bool intishWarn>
2362 inline ArrayData* UnsetElemArrayPre(ArrayData* a, int64_t key,
2363 bool copy) {
2364 return a->remove(key, copy);
2368 * UnsetElemArray when key is a String
2370 template <bool intishWarn>
2371 inline ArrayData* UnsetElemArrayPre(ArrayData* a, StringData* key,
2372 bool copy) {
2373 int64_t n;
2374 assertx(a->isPHPArray());
2375 if (key->isStrictlyInteger(n)) {
2376 if (intishWarn) raise_intish_index_cast();
2377 return a->remove(n, copy);
2378 } else {
2379 return a->remove(key, copy);
2383 template <bool intishWarn>
2384 inline ArrayData* UnsetElemArrayPre(ArrayData* a, TypedValue key,
2385 bool copy) {
2386 if (isStringType(key.m_type)) {
2387 return UnsetElemArrayPre<intishWarn>(a, key.m_data.pstr, copy);
2389 if (key.m_type == KindOfInt64) {
2390 return UnsetElemArrayPre<false>(a, key.m_data.num, copy);
2392 auto const k = tvToKey(key, a);
2393 if (isNullType(k.m_type)) return a;
2394 return a->remove(k, copy);
2398 * UnsetElem when base is an Array
2400 template <KeyType keyType, bool intishWarn>
2401 inline void UnsetElemArray(tv_lval base, key_type<keyType> key) {
2402 assertx(tvIsArray(base));
2403 assertx(tvIsPlausible(*base));
2404 ArrayData* a = val(base).parr;
2405 ArrayData* a2 = UnsetElemArrayPre<intishWarn>(a, key, a->cowCheck());
2407 if (a2 != a) {
2408 type(base) = KindOfArray;
2409 val(base).parr = a2;
2410 assertx(cellIsPlausible(*base));
2411 a->decRefAndRelease();
2416 * UnsetElem when base is a Vec
2419 inline ArrayData* UnsetElemVecPre(ArrayData* a, int64_t key,
2420 bool copy) {
2421 return PackedArray::RemoveIntVec(a, key, copy);
2424 inline ArrayData*
2425 UnsetElemVecPre(ArrayData* a, StringData* /*key*/, bool /*copy*/) {
2426 /* Never contains strings, so a no-op. */
2427 return a;
2430 inline ArrayData* UnsetElemVecPre(ArrayData* a, TypedValue key,
2431 bool copy) {
2432 auto const dt = key.m_type;
2433 if (LIKELY(isIntType(dt))) return UnsetElemVecPre(a, key.m_data.num, copy);
2434 if (isStringType(dt)) return UnsetElemVecPre(a, key.m_data.pstr, copy);
2435 throwInvalidArrayKeyException(&key, a);
2438 template <KeyType keyType>
2439 inline void UnsetElemVec(tv_lval base, key_type<keyType> key) {
2440 assertx(tvIsVec(base));
2441 assertx(tvIsPlausible(*base));
2442 ArrayData* a = val(base).parr;
2443 ArrayData* a2 = UnsetElemVecPre(a, key, a->cowCheck());
2444 assertx(a2->isVecArray() || a2->isDict());
2446 if (a2 != a) {
2447 type(base) = a2->toDataType();
2448 val(base).parr = a2;
2449 assertx(cellIsPlausible(*base));
2450 a->decRefAndRelease();
2455 * UnsetElem when base is a Dict
2458 inline ArrayData* UnsetElemDictPre(ArrayData* a, int64_t key,
2459 bool copy) {
2460 return MixedArray::RemoveIntDict(a, key, copy);
2463 inline ArrayData* UnsetElemDictPre(ArrayData* a, StringData* key,
2464 bool copy) {
2465 return MixedArray::RemoveStrDict(a, key, copy);
2468 inline ArrayData* UnsetElemDictPre(ArrayData* a, TypedValue key,
2469 bool copy) {
2470 auto const dt = key.m_type;
2471 if (isIntType(dt)) return UnsetElemDictPre(a, key.m_data.num, copy);
2472 if (isStringType(dt)) return UnsetElemDictPre(a, key.m_data.pstr, copy);
2473 throwInvalidArrayKeyException(&key, a);
2476 template <KeyType keyType>
2477 inline void UnsetElemDict(tv_lval base, key_type<keyType> key) {
2478 assertx(tvIsDict(base));
2479 assertx(tvIsPlausible(*base));
2480 ArrayData* a = val(base).parr;
2481 ArrayData* a2 = UnsetElemDictPre(a, key, a->cowCheck());
2483 if (a2 != a) {
2484 type(base) = KindOfDict;
2485 val(base).parr = a2;
2486 assertx(cellIsPlausible(*base));
2487 a->decRefAndRelease();
2492 * UnsetElem when base is a Keyset
2495 inline ArrayData* UnsetElemKeysetPre(ArrayData* a, int64_t key,
2496 bool copy) {
2497 return SetArray::RemoveInt(a, key, copy);
2500 inline ArrayData* UnsetElemKeysetPre(ArrayData* a, StringData* key,
2501 bool copy) {
2502 return SetArray::RemoveStr(a, key, copy);
2505 inline ArrayData* UnsetElemKeysetPre(ArrayData* a, TypedValue key,
2506 bool copy) {
2507 auto const dt = key.m_type;
2508 if (isIntType(dt)) return UnsetElemKeysetPre(a, key.m_data.num, copy);
2509 if (isStringType(dt)) return UnsetElemKeysetPre(a, key.m_data.pstr, copy);
2510 throwInvalidArrayKeyException(&key, a);
2513 template <KeyType keyType>
2514 inline void UnsetElemKeyset(tv_lval base, key_type<keyType> key) {
2515 assertx(tvIsKeyset(base));
2516 assertx(tvIsPlausible(*base));
2517 ArrayData* a = val(base).parr;
2518 ArrayData* a2 = UnsetElemKeysetPre(a, key, a->cowCheck());
2520 if (a2 != a) {
2521 type(base) = KindOfKeyset;
2522 val(base).parr = a2;
2523 assertx(cellIsPlausible(*base));
2524 a->decRefAndRelease();
2529 * unset($base[$member])
2531 template <KeyType keyType, bool intishWarn>
2532 NEVER_INLINE
2533 void UnsetElemSlow(tv_lval base, key_type<keyType> key) {
2534 base = tvToCell(base);
2535 assertx(cellIsPlausible(*base));
2537 switch (type(base)) {
2538 case KindOfUninit:
2539 case KindOfNull:
2540 case KindOfBoolean:
2541 case KindOfInt64:
2542 case KindOfDouble:
2543 case KindOfResource:
2544 return; // Do nothing.
2546 case KindOfFunc:
2547 raise_error("Cannot unset a func");
2548 return;
2550 case KindOfPersistentString:
2551 case KindOfString:
2552 raise_error(Strings::CANT_UNSET_STRING);
2553 return;
2555 case KindOfPersistentVec:
2556 case KindOfVec:
2557 UnsetElemVec<keyType>(base, key);
2558 return;
2560 case KindOfPersistentDict:
2561 case KindOfDict:
2562 UnsetElemDict<keyType>(base, key);
2563 return;
2565 case KindOfPersistentKeyset:
2566 case KindOfKeyset:
2567 UnsetElemKeyset<keyType>(base, key);
2568 return;
2570 case KindOfPersistentArray:
2571 case KindOfArray:
2572 UnsetElemArray<keyType, intishWarn>(base, key);
2573 return;
2575 case KindOfObject: {
2576 auto const& scratchKey = initScratchKey(key);
2577 if (LIKELY(val(base).pobj->isCollection())) {
2578 collections::unset(val(base).pobj, &scratchKey);
2579 } else {
2580 objOffsetUnset(instanceFromTv(base), scratchKey);
2582 return;
2585 case KindOfRef:
2586 break;
2588 unknownBaseType(type(base));
2592 * Fast path for UnsetElem assuming base is an Array
2594 template <bool intishWarn, KeyType keyType = KeyType::Any>
2595 inline void UnsetElem(tv_lval base, key_type<keyType> key) {
2596 assertx(tvIsPlausible(*base));
2598 if (LIKELY(tvIsArray(base))) {
2599 return UnsetElemArray<keyType, intishWarn>(base, key);
2601 if (LIKELY(tvIsVec(base))) {
2602 return UnsetElemVec<keyType>(base, key);
2604 if (LIKELY(tvIsDict(base))) {
2605 return UnsetElemDict<keyType>(base, key);
2607 if (LIKELY(tvIsKeyset(base))) {
2608 return UnsetElemKeyset<keyType>(base, key);
2610 return UnsetElemSlow<keyType, intishWarn>(base, key);
2614 * IssetEmptyElem when base is an Object
2616 template<bool useEmpty, KeyType keyType>
2617 bool IssetEmptyElemObj(ObjectData* instance, key_type<keyType> key) {
2618 auto scratchKey = initScratchKey(key);
2619 if (LIKELY(instance->isCollection())) {
2620 return useEmpty
2621 ? collections::empty(instance, &scratchKey)
2622 : collections::isset(instance, &scratchKey);
2625 return useEmpty
2626 ? objOffsetEmpty(instance, scratchKey)
2627 : objOffsetIsset(instance, scratchKey);
2631 * IssetEmptyElem when base is a String
2633 template <bool useEmpty, KeyType keyType>
2634 bool IssetEmptyElemString(tv_rval base, key_type<keyType> key) {
2635 // TODO Task #2716479: Fix this so that the warnings raised match
2636 // PHP5.
2637 auto scratchKey = initScratchKey(key);
2638 int64_t x;
2639 if (LIKELY(scratchKey.m_type == KindOfInt64)) {
2640 x = scratchKey.m_data.num;
2641 } else {
2642 TypedValue tv;
2643 cellDup(scratchKey, tv);
2644 bool badKey = false;
2645 if (isStringType(tv.m_type)) {
2646 const char* str = tv.m_data.pstr->data();
2647 size_t len = tv.m_data.pstr->size();
2648 while (len > 0 &&
2649 (*str == ' ' || *str == '\t' || *str == '\r' || *str == '\n')) {
2650 ++str;
2651 --len;
2653 int64_t n;
2654 badKey = !is_strictly_integer(str, len, n);
2655 } else if (isArrayLikeType(tv.m_type) || tv.m_type == KindOfObject ||
2656 tv.m_type == KindOfResource) {
2657 badKey = true;
2659 // Even if badKey == true, we still perform the cast so that we
2660 // raise the appropriate warnings.
2661 tvCastToInt64InPlace(&tv);
2662 if (badKey) {
2663 return useEmpty;
2665 x = tv.m_data.num;
2667 if (x < 0 || x >= val(base).pstr->size()) {
2668 return useEmpty;
2670 if (!useEmpty) {
2671 return true;
2674 auto str = val(base).pstr->getChar(x);
2675 assertx(str->isStatic());
2676 return !str->toBoolean();
2680 * IssetEmptyElem when base is an Array
2682 template <bool useEmpty, KeyType keyType, bool intishWarn>
2683 bool IssetEmptyElemArray(ArrayData* a, key_type<keyType> key) {
2684 assertx(a->isPHPArray());
2685 auto const result = ElemArray<MOpMode::None, keyType, intishWarn>(a, key);
2686 if (useEmpty) {
2687 return !cellToBool(tvToCell(result.tv()));
2689 return !cellIsNull(tvToCell(result.tv()));
2693 * IssetEmptyElem when base is a Vec
2695 template <bool useEmpty, KeyType keyType>
2696 bool IssetEmptyElemVec(ArrayData* a, key_type<keyType> key) {
2697 assertx(a->isVecArray());
2698 auto const result = ElemVec<MOpMode::None, keyType>(a, key);
2699 if (useEmpty) {
2700 return !cellToBool(tvAssertCell(result.tv()));
2702 return !cellIsNull(tvAssertCell(result.tv()));
2706 * IssetEmptyElem when base is a Dict
2708 template <bool useEmpty, KeyType keyType>
2709 bool IssetEmptyElemDict(ArrayData* a, key_type<keyType> key) {
2710 assertx(a->isDict());
2711 auto const result = ElemDict<MOpMode::None, keyType>(a, key);
2712 if (useEmpty) {
2713 return !cellToBool(tvAssertCell(result.tv()));
2715 return !cellIsNull(tvAssertCell(result.tv()));
2719 * IssetEmptyElem when base is a Keyset
2721 template <bool useEmpty, KeyType keyType>
2722 bool IssetEmptyElemKeyset(ArrayData* a, key_type<keyType> key) {
2723 assertx(a->isKeyset());
2724 auto const result = ElemKeyset<MOpMode::None, keyType>(a, key);
2725 if (useEmpty) {
2726 return !cellToBool(tvAssertCell(result.tv()));
2728 return !cellIsNull(tvAssertCell(result.tv()));
2732 * isset/empty($base[$key])
2734 template <bool useEmpty, KeyType keyType, bool intishWarn>
2735 NEVER_INLINE bool IssetEmptyElemSlow(tv_rval base, key_type<keyType> key) {
2736 base = tvToCell(base);
2737 assertx(cellIsPlausible(*base));
2739 switch (type(base)) {
2740 case KindOfUninit:
2741 case KindOfNull:
2742 case KindOfBoolean:
2743 case KindOfInt64:
2744 case KindOfDouble:
2745 case KindOfResource:
2746 // TODO (T29639296)
2747 case KindOfFunc:
2748 return useEmpty;
2750 case KindOfPersistentString:
2751 case KindOfString:
2752 return IssetEmptyElemString<useEmpty, keyType>(base, key);
2754 case KindOfPersistentVec:
2755 case KindOfVec:
2756 return IssetEmptyElemVec<useEmpty, keyType>(val(base).parr, key);
2758 case KindOfPersistentDict:
2759 case KindOfDict:
2760 return IssetEmptyElemDict<useEmpty, keyType>(val(base).parr, key);
2762 case KindOfPersistentKeyset:
2763 case KindOfKeyset:
2764 return IssetEmptyElemKeyset<useEmpty, keyType>(val(base).parr, key);
2766 case KindOfPersistentArray:
2767 case KindOfArray:
2768 return IssetEmptyElemArray<useEmpty, keyType, intishWarn>(
2769 val(base).parr, key
2772 case KindOfObject:
2773 return IssetEmptyElemObj<useEmpty, keyType>(val(base).pobj, key);
2775 case KindOfRef:
2776 break;
2778 unknownBaseType(type(base));
2781 template <bool useEmpty, bool intishWarn, KeyType keyType = KeyType::Any>
2782 bool IssetEmptyElem(tv_rval base, key_type<keyType> key) {
2783 assertx(tvIsPlausible(*base));
2785 if (LIKELY(tvIsArray(base))) {
2786 return IssetEmptyElemArray<useEmpty, keyType, intishWarn>(
2787 val(base).parr, key
2790 if (LIKELY(tvIsVec(base))) {
2791 return IssetEmptyElemVec<useEmpty, keyType>(val(base).parr, key);
2793 if (LIKELY(tvIsDict(base))) {
2794 return IssetEmptyElemDict<useEmpty, keyType>(val(base).parr, key);
2796 if (LIKELY(tvIsKeyset(base))) {
2797 return IssetEmptyElemKeyset<useEmpty, keyType>(val(base).parr, key);
2799 return IssetEmptyElemSlow<useEmpty, keyType, intishWarn>(base, key);
2802 template<MOpMode mode>
2803 inline tv_lval propPreNull(TypedValue& tvRef) {
2804 tvWriteNull(tvRef);
2805 if (mode == MOpMode::Warn) {
2806 raise_notice("Cannot access property on non-object");
2808 return tv_lval(&tvRef);
2811 template <class F>
2812 inline void promoteToStdClass(tv_lval base, bool warn, F fun) {
2813 if (!RuntimeOption::EvalPromoteEmptyObject) {
2814 // note that the whole point here is to guarantee that the property
2815 // never auto updates to a stdclass - so we must do this before
2816 // calling promote, and we don't want the try catch below around
2817 // this call.
2818 if (RuntimeOption::PHP7_EngineExceptions) {
2819 SystemLib::throwErrorObject(Strings::SET_PROP_NON_OBJECT);
2820 } else {
2821 SystemLib::throwExceptionObject(Strings::SET_PROP_NON_OBJECT);
2823 not_reached();
2826 Object obj { ObjectData::newInstance(SystemLib::s_stdclassClass) };
2827 if (base.type() == KindOfString) {
2828 decRefStr(base.val().pstr);
2829 } else {
2830 assertx(!isRefcountedType(base.type()));
2832 base.type() = KindOfObject;
2833 base.val().pobj = obj.get();
2835 if (warn) {
2836 // Behavior here is observable.
2837 // In PHP 5.6, raise_warning is called before updating base, so
2838 // the error_handler sees the original base; but if an exception
2839 // is thrown from the error handler, any catch block will see the
2840 // updated base.
2841 // In PHP 7+, raise_warning is called after updating base, but before
2842 // doing the work of fun, and again, if an exception is thrown, fun
2843 // still gets called before reaching the catch block.
2844 // We'll match PHP7, because we have no way of ensuring that base survives
2845 // across a call to the error_handler: eg $a[0][0][0]->foo = 0; if $a
2846 // started out null, and the error handler resets it to null, base is
2847 // left dangling.
2848 // Note that this means that the error handler can overwrite the object
2849 // so there is no guarantee that we have an object on return from
2850 // promoteToStdClass.
2851 try {
2852 raise_warning(Strings::CREATING_DEFAULT_OBJECT);
2853 } catch (const Object&) {
2854 fun(obj.get());
2855 throw;
2859 fun(obj.get());
2862 template<MOpMode mode>
2863 tv_lval propPreStdclass(TypedValue& tvRef, tv_lval base) {
2864 if (mode != MOpMode::Define) {
2865 return propPreNull<mode>(tvRef);
2868 promoteToStdClass(base, RuntimeOption::EnableHipHopSyntax,
2869 [] (ObjectData*) {});
2870 if (UNLIKELY(base.type() != KindOfObject)) {
2871 // See the comments above. Although promoteToStdClass will have
2872 // either thrown an exception, or promoted base to an object, an
2873 // installed error handler might have caused it to be overwritten
2874 tvWriteNull(tvRef);
2875 return tv_lval(&tvRef);
2878 return base;
2881 template<MOpMode mode>
2882 tv_lval propPre(TypedValue& tvRef, tv_lval base) {
2883 base = base.unboxed();
2884 switch (base.type()) {
2885 case KindOfUninit:
2886 case KindOfNull:
2887 return propPreStdclass<mode>(tvRef, base);
2889 case KindOfBoolean:
2890 if (base.val().num) {
2891 return propPreNull<mode>(tvRef);
2893 return propPreStdclass<mode>(tvRef, base);
2895 case KindOfInt64:
2896 case KindOfDouble:
2897 case KindOfResource:
2898 case KindOfFunc:
2899 return propPreNull<mode>(tvRef);
2901 case KindOfPersistentString:
2902 case KindOfString:
2903 if (base.val().pstr->size() != 0) {
2904 return propPreNull<mode>(tvRef);
2906 return propPreStdclass<mode>(tvRef, base);
2908 case KindOfPersistentVec:
2909 case KindOfVec:
2910 case KindOfPersistentDict:
2911 case KindOfDict:
2912 case KindOfPersistentKeyset:
2913 case KindOfKeyset:
2914 case KindOfPersistentArray:
2915 case KindOfArray:
2916 return propPreNull<mode>(tvRef);
2918 case KindOfObject:
2919 return base;
2921 case KindOfRef:
2922 break;
2924 unknownBaseType(type(base));
2927 inline tv_lval nullSafeProp(TypedValue& tvRef,
2928 Class* ctx,
2929 tv_rval base,
2930 StringData* key) {
2931 base = base.unboxed();
2932 switch (base.type()) {
2933 case KindOfUninit:
2934 case KindOfNull:
2935 tvWriteNull(tvRef);
2936 return &tvRef;
2937 case KindOfBoolean:
2938 case KindOfInt64:
2939 case KindOfDouble:
2940 case KindOfResource:
2941 case KindOfPersistentString:
2942 case KindOfString:
2943 case KindOfPersistentVec:
2944 case KindOfVec:
2945 case KindOfPersistentDict:
2946 case KindOfDict:
2947 case KindOfPersistentKeyset:
2948 case KindOfKeyset:
2949 case KindOfPersistentArray:
2950 case KindOfArray:
2951 case KindOfFunc:
2952 tvWriteNull(tvRef);
2953 raise_notice("Cannot access property on non-object");
2954 return &tvRef;
2955 case KindOfObject:
2956 return val(base).pobj->prop(&tvRef, ctx, key);
2957 case KindOfRef:
2958 always_assert(false);
2960 not_reached();
2964 * Generic property access (PropX and PropDX end up here).
2966 * Returns a pointer to a number of possible places, but does not unbox it.
2967 * (The returned pointer is never pointing into a RefData.)
2969 template<MOpMode mode, KeyType keyType = KeyType::Any, bool reffy = false>
2970 inline tv_lval PropObj(TypedValue& tvRef, const Class* ctx,
2971 ObjectData* instance, key_type<keyType> key) {
2972 auto keySD = prepareKey(key);
2973 SCOPE_EXIT { releaseKey<keyType>(keySD); };
2975 // Get property.
2976 if (mode == MOpMode::Define) {
2977 if (reffy) {
2978 return instance->propB(&tvRef, ctx, keySD);
2979 } else {
2980 return instance->propD(&tvRef, ctx, keySD);
2983 assertx(!reffy);
2984 if (mode == MOpMode::None) {
2985 return instance->prop(&tvRef, ctx, keySD);
2987 if (mode == MOpMode::Warn) {
2988 return instance->propW(&tvRef, ctx, keySD);
2990 assertx(mode == MOpMode::Unset);
2991 return instance->propD(&tvRef, ctx, keySD);
2994 template<MOpMode mode, KeyType keyType = KeyType::Any, bool reffy = false>
2995 inline tv_lval Prop(TypedValue& tvRef,
2996 const Class* ctx,
2997 tv_lval base,
2998 key_type<keyType> key) {
2999 auto result = propPre<mode>(tvRef, base);
3000 if (result.type() == KindOfNull) return result;
3002 return PropObj<mode,keyType,reffy>(tvRef, ctx, instanceFromTv(result), key);
3005 template <bool useEmpty, KeyType kt>
3006 inline bool IssetEmptyPropObj(Class* ctx, ObjectData* instance,
3007 key_type<kt> key) {
3008 auto keySD = prepareKey(key);
3009 SCOPE_EXIT { releaseKey<kt>(keySD); };
3011 return useEmpty ?
3012 instance->propEmpty(ctx, keySD) :
3013 instance->propIsset(ctx, keySD);
3016 template <bool useEmpty, KeyType kt = KeyType::Any>
3017 bool IssetEmptyProp(Class* ctx, tv_lval base, key_type<kt> key) {
3018 base = tvToCell(base);
3019 if (LIKELY(type(base) == KindOfObject)) {
3020 return IssetEmptyPropObj<useEmpty, kt>(ctx, instanceFromTv(base), key);
3022 return useEmpty;
3025 template <bool setResult>
3026 inline void SetPropNull(Cell* val) {
3027 raise_warning("Cannot access property on non-object");
3028 if (setResult) {
3029 tvDecRefGen(val);
3030 tvWriteNull(*val);
3031 } else {
3032 throw InvalidSetMException(make_tv<KindOfNull>());
3036 inline void SetPropStdclass(tv_lval base, TypedValue key, Cell* val) {
3037 promoteToStdClass(
3038 base,
3039 true,
3040 [&] (ObjectData* obj) {
3041 auto const keySD = prepareKey(key);
3042 SCOPE_EXIT { decRefStr(keySD); };
3043 obj->setProp(nullptr, keySD, *val);
3047 template <KeyType keyType>
3048 inline void SetPropObj(Class* ctx, ObjectData* instance,
3049 key_type<keyType> key, Cell* val) {
3050 StringData* keySD = prepareKey(key);
3051 SCOPE_EXIT { releaseKey<keyType>(keySD); };
3053 // Set property.
3054 instance->setProp(ctx, keySD, *val);
3057 // $base->$key = $val
3058 template <bool setResult, KeyType keyType = KeyType::Any>
3059 inline void SetProp(Class* ctx, tv_lval base, key_type<keyType> key,
3060 Cell* val) {
3061 base = tvToCell(base);
3062 switch (type(base)) {
3063 case KindOfUninit:
3064 case KindOfNull:
3065 return SetPropStdclass(base, initScratchKey(key), val);
3067 case KindOfBoolean:
3068 if (HPHP::val(base).num) {
3069 return SetPropNull<setResult>(val);
3071 return SetPropStdclass(base, initScratchKey(key), val);
3073 case KindOfInt64:
3074 case KindOfDouble:
3075 case KindOfPersistentVec:
3076 case KindOfVec:
3077 case KindOfPersistentDict:
3078 case KindOfDict:
3079 case KindOfPersistentKeyset:
3080 case KindOfKeyset:
3081 case KindOfPersistentArray:
3082 case KindOfArray:
3083 case KindOfResource:
3084 case KindOfFunc:
3085 return SetPropNull<setResult>(val);
3087 case KindOfPersistentString:
3088 case KindOfString:
3089 if (HPHP::val(base).pstr->size() != 0) {
3090 return SetPropNull<setResult>(val);
3092 return SetPropStdclass(base, initScratchKey(key), val);
3094 case KindOfObject:
3095 return SetPropObj<keyType>(ctx, HPHP::val(base).pobj, key, val);
3097 case KindOfRef:
3098 break;
3100 unknownBaseType(type(base));
3103 inline tv_lval SetOpPropNull(TypedValue& tvRef) {
3104 raise_warning("Attempt to assign property of non-object");
3105 tvWriteNull(tvRef);
3106 return &tvRef;
3109 inline tv_lval SetOpPropStdclass(TypedValue& tvRef, SetOpOp op,
3110 tv_lval base, TypedValue key,
3111 Cell* rhs) {
3112 promoteToStdClass(
3113 base,
3114 true,
3115 [&] (ObjectData* obj) {
3116 StringData* keySD = prepareKey(key);
3117 SCOPE_EXIT { decRefStr(keySD); };
3118 tvWriteNull(tvRef);
3119 setopBody(tvAssertCell(&tvRef), op, rhs);
3120 obj->setProp(nullptr, keySD, tvAssertCell(tvRef));
3123 return &tvRef;
3126 inline tv_lval SetOpPropObj(TypedValue& tvRef, Class* ctx,
3127 SetOpOp op, ObjectData* instance,
3128 TypedValue key, Cell* rhs) {
3129 StringData* keySD = prepareKey(key);
3130 SCOPE_EXIT { decRefStr(keySD); };
3131 return instance->setOpProp(tvRef, ctx, op, keySD, rhs);
3134 // $base->$key <op>= $rhs
3135 inline tv_lval SetOpProp(TypedValue& tvRef,
3136 Class* ctx, SetOpOp op,
3137 tv_lval base, TypedValue key,
3138 Cell* rhs) {
3139 base = tvToCell(base);
3140 switch (type(base)) {
3141 case KindOfUninit:
3142 case KindOfNull:
3143 return SetOpPropStdclass(tvRef, op, base, key, rhs);
3145 case KindOfBoolean:
3146 if (val(base).num) {
3147 return SetOpPropNull(tvRef);
3149 return SetOpPropStdclass(tvRef, op, base, key, rhs);
3151 case KindOfInt64:
3152 case KindOfDouble:
3153 case KindOfPersistentVec:
3154 case KindOfVec:
3155 case KindOfPersistentDict:
3156 case KindOfDict:
3157 case KindOfPersistentKeyset:
3158 case KindOfKeyset:
3159 case KindOfPersistentArray:
3160 case KindOfArray:
3161 case KindOfResource:
3162 case KindOfFunc:
3163 return SetOpPropNull(tvRef);
3165 case KindOfPersistentString:
3166 case KindOfString:
3167 if (val(base).pstr->size() != 0) {
3168 return SetOpPropNull(tvRef);
3170 return SetOpPropStdclass(tvRef, op, base, key, rhs);
3172 case KindOfObject:
3173 return SetOpPropObj(tvRef, ctx, op, instanceFromTv(base), key, rhs);
3175 case KindOfRef:
3176 break;
3178 unknownBaseType(type(base));
3181 inline Cell IncDecPropNull() {
3182 raise_warning("Attempt to increment/decrement property of non-object");
3183 return make_tv<KindOfNull>();
3186 inline Cell IncDecPropStdclass(IncDecOp op, tv_lval base,
3187 TypedValue key) {
3188 Cell dest;
3189 promoteToStdClass(
3190 base,
3191 true,
3192 [&] (ObjectData* obj) {
3193 StringData* keySD = prepareKey(key);
3194 SCOPE_EXIT { decRefStr(keySD); };
3195 TypedValue tv;
3196 tvWriteNull(tv);
3197 dest = IncDecBody(op, &tv);
3198 obj->setProp(nullptr, keySD, dest);
3199 assertx(!isRefcountedType(tv.m_type));
3202 return dest;
3205 inline Cell IncDecPropObj(Class* ctx,
3206 IncDecOp op,
3207 ObjectData* base,
3208 TypedValue key) {
3209 auto keySD = prepareKey(key);
3210 SCOPE_EXIT { decRefStr(keySD); };
3211 return base->incDecProp(ctx, op, keySD);
3214 inline Cell IncDecProp(
3215 Class* ctx,
3216 IncDecOp op,
3217 tv_lval base,
3218 TypedValue key
3220 base = tvToCell(base);
3221 switch (type(base)) {
3222 case KindOfUninit:
3223 case KindOfNull:
3224 return IncDecPropStdclass(op, base, key);
3226 case KindOfBoolean:
3227 if (val(base).num) {
3228 return IncDecPropNull();
3230 return IncDecPropStdclass(op, base, key);
3232 case KindOfInt64:
3233 case KindOfDouble:
3234 case KindOfPersistentVec:
3235 case KindOfVec:
3236 case KindOfPersistentDict:
3237 case KindOfDict:
3238 case KindOfPersistentKeyset:
3239 case KindOfKeyset:
3240 case KindOfPersistentArray:
3241 case KindOfArray:
3242 case KindOfResource:
3243 case KindOfFunc:
3244 return IncDecPropNull();
3246 case KindOfPersistentString:
3247 case KindOfString:
3248 if (val(base).pstr->size() != 0) {
3249 return IncDecPropNull();
3251 return IncDecPropStdclass(op, base, key);
3253 case KindOfObject:
3254 return IncDecPropObj(ctx, op, instanceFromTv(base), key);
3256 case KindOfRef:
3257 break;
3259 unknownBaseType(type(base));
3262 inline void UnsetPropObj(Class* ctx, ObjectData* instance, TypedValue key) {
3263 // Prepare key.
3264 auto keySD = prepareKey(key);
3265 SCOPE_EXIT { decRefStr(keySD); };
3266 // Unset property.
3267 instance->unsetProp(ctx, keySD);
3270 inline void UnsetProp(Class* ctx, tv_lval base, TypedValue key) {
3271 // Validate base.
3272 base = tvToCell(base);
3273 if (LIKELY(type(base) == KindOfObject)) {
3274 UnsetPropObj(ctx, instanceFromTv(base), key);
3278 ///////////////////////////////////////////////////////////////////////////////
3280 #endif // incl_HPHP_VM_MEMBER_OPERATIONS_H_