Don't return Variant& from Array functions
[hiphop-php.git] / hphp / runtime / vm / member-operations.h
blob02abe5d61aaf44edc9e6b6461f29a6d35743ee5b
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-defs.h"
23 #include "hphp/runtime/base/builtin-functions.h"
24 #include "hphp/runtime/base/collections.h"
25 #include "hphp/runtime/base/req-root.h"
26 #include "hphp/runtime/base/strings.h"
27 #include "hphp/runtime/base/tv-conversions.h"
28 #include "hphp/runtime/base/tv-refcount.h"
29 #include "hphp/runtime/base/tv-type.h"
30 #include "hphp/runtime/base/type-array.h"
31 #include "hphp/runtime/base/type-string.h"
32 #include "hphp/runtime/base/mixed-array.h"
33 #include "hphp/runtime/base/packed-array.h"
34 #include "hphp/runtime/base/set-array.h"
35 #include "hphp/runtime/vm/runtime.h"
36 #include "hphp/system/systemlib.h"
38 namespace HPHP {
40 const StaticString s_storage("storage");
42 struct InvalidSetMException : std::runtime_error {
43 InvalidSetMException()
44 : std::runtime_error("Empty InvalidSetMException")
45 , m_tv(make_tv<KindOfNull>())
48 explicit InvalidSetMException(const TypedValue& value)
49 : std::runtime_error(folly::format("InvalidSetMException containing {}",
50 value.pretty()).str())
51 , m_tv(value)
54 ~InvalidSetMException() noexcept override {}
56 const TypedValue& tv() const { return m_tv; };
58 private:
59 /* m_tv will contain a TypedValue with a reference destined for the
60 * VM eval stack. */
61 req::root<TypedValue> m_tv;
64 // When MoreWarnings is set to true, the VM will raise more warnings
65 // on SetOpM, IncDecM and CGetG, intended to match Zend.
66 const bool MoreWarnings =
67 #ifdef HHVM_MORE_WARNINGS
68 true
69 #else
70 false
71 #endif
75 * KeyType and the associated functions below are used to generate member
76 * operation functions specialized for certain key types. Many functions take a
77 * KeyType template parameter, then use key_type<keyType> as the type of their
78 * key parameter. Depending on which KeyType is used, the parameter will be a
79 * TypedValue, int64_t, or StringData*.
81 enum class KeyType {
82 Any, // Key is passed as a TypedValue and could be any type
83 Int, // Key is passed as an int64_t
84 Str, // Key is passed as a StringData*
87 /* KeyTypeTraits maps from KeyType to the C++ type holding they key. */
88 template<KeyType> struct KeyTypeTraits;
89 template<> struct KeyTypeTraits<KeyType::Any> { typedef TypedValue type; };
90 template<> struct KeyTypeTraits<KeyType::Int> { typedef int64_t type; };
91 template<> struct KeyTypeTraits<KeyType::Str> { typedef StringData* type; };
93 /* key_type is the type used in the signatures of functions taking a member
94 * key. */
95 template<KeyType kt> using key_type = typename KeyTypeTraits<kt>::type;
97 /* initScratchKey is used in scenarios where we want a TypedValue key
98 * regardless of what the current function was given. */
99 inline TypedValue initScratchKey(TypedValue tv) {
100 assertx(tv.m_type != KindOfRef);
101 return tv;
104 inline TypedValue initScratchKey(int64_t key) {
105 return make_tv<KindOfInt64>(key);
108 inline TypedValue initScratchKey(StringData* key) {
109 return make_tv<KindOfString>(key);
112 /* keyAsValue transforms a key into a value suitable for indexing into an
113 * Array. */
114 inline const Variant& keyAsValue(TypedValue& key) {
115 return tvAsCVarRef(&key);
117 inline int64_t keyAsValue(int64_t key) { return key; }
118 inline StrNR keyAsValue(StringData* key) { return StrNR(key); }
120 /* prepareKey is used by operations that need to cast their key to a
121 * string. For generic keys, the returned value must be decreffed after use. */
122 StringData* prepareAnyKey(TypedValue* tv);
123 inline StringData* prepareKey(TypedValue tv) { return prepareAnyKey(&tv); }
124 inline StringData* prepareKey(StringData* sd) { return sd; }
126 /* releaseKey helps with decreffing a StringData* returned from
127 * prepareKey. When used with KeyType::Any, corresponding to
128 * prepareKey(TypedValue), it will consume the reference produced by
129 * prepareKey. When used with KeyType::Str, corresponding to
130 * prepareKey(StringData*), it is a nop. */
131 template<KeyType keyType>
132 inline void releaseKey(StringData* sd) {
133 static_assert(keyType == KeyType::Any, "bad KeyType");
134 decRefStr(sd);
137 template<>
138 inline void releaseKey<KeyType::Str>(StringData*) {
139 // do nothing. we don't own a reference to this string.
142 void objArrayAccess(ObjectData* base);
144 TypedValue objOffsetGet(
145 ObjectData* base,
146 TypedValue offset,
147 bool validate = true
150 bool objOffsetIsset(ObjectData* base, TypedValue offset, bool validate = true);
151 bool objOffsetEmpty(ObjectData* base, TypedValue offset, bool validate = true);
153 void objOffsetSet(
154 ObjectData* base,
155 TypedValue offset,
156 TypedValue* val,
157 bool validate = true
160 void objOffsetAppend(ObjectData* base, TypedValue* val, bool validate = true);
161 void objOffsetUnset(ObjectData* base, TypedValue offset);
163 [[noreturn]] void throw_cannot_use_newelem_for_lval_read_col();
164 [[noreturn]] void throw_cannot_use_newelem_for_lval_read_vec();
165 [[noreturn]] void throw_cannot_use_newelem_for_lval_read_dict();
166 [[noreturn]] void throw_cannot_use_newelem_for_lval_read_keyset();
168 [[noreturn]] void unknownBaseType(const TypedValue*);
170 namespace detail {
172 ALWAYS_INLINE void checkPromotion(const TypedValue* base) {
173 if (LIKELY(!RuntimeOption::EvalHackArrCompatNotices)) return;
175 if (base->m_type == KindOfNull) {
176 raise_hackarr_compat_notice("Promoting null to array");
177 } else if (base->m_type == KindOfBoolean) {
178 raise_hackarr_compat_notice("Promoting false to array");
185 * Elem when base is Null
187 inline member_rval ElemEmptyish() {
188 return member_rval { nullptr, &immutable_null_base };
191 template<MOpMode mode, bool intishWarn>
192 inline member_rval ElemArrayPre(ArrayData* base, int64_t key) {
193 return mode == MOpMode::Warn ? base->rvalStrict(key) : base->rval(key);
196 template<MOpMode mode, bool intishWarn>
197 inline member_rval ElemArrayPre(ArrayData* base, StringData* key) {
198 auto constexpr warn = mode == MOpMode::Warn;
199 int64_t n;
200 assert(base->isPHPArray());
201 if (key->isStrictlyInteger(n)) {
202 if (intishWarn) raise_intish_index_cast();
203 return warn ? base->rvalStrict(n) : base->rval(n);
204 } else {
205 return warn ? base->rvalStrict(key) : base->rval(key);
209 template<MOpMode mode, bool intishWarn>
210 inline member_rval ElemArrayPre(ArrayData* base, TypedValue key) {
211 auto const dt = key.m_type;
212 if (isIntType(dt)) {
213 return ElemArrayPre<mode, false>(base, key.m_data.num);
215 if (isStringType(dt)) {
216 return ElemArrayPre<mode, intishWarn>(base, key.m_data.pstr);
219 // TODO(#3888164): Array elements can never be KindOfUninit. This API should
220 // be changed.
221 auto const rval = ArrNR(base).asArray().rvalAt(cellAsCVarRef(key));
222 return rval.type() != KindOfUninit ? rval : member_rval { base, nullptr };
226 * Elem when base is an Array
228 template<MOpMode mode, KeyType keyType, bool intishWarn>
229 inline member_rval ElemArray(ArrayData* base, key_type<keyType> key) {
230 assert(base->isPHPArray());
232 auto result = ElemArrayPre<mode, intishWarn>(base, key);
234 if (UNLIKELY(!result)) {
235 if (mode == MOpMode::Warn) {
236 auto const scratch = initScratchKey(key);
237 raise_notice(Strings::UNDEFINED_INDEX,
238 tvAsCVarRef(&scratch).toString().data());
240 return ElemEmptyish();
243 assertx(result.type() != KindOfUninit);
244 return result;
248 * Elem when base is a Vec
250 template<MOpMode mode>
251 inline member_rval ElemVecPre(ArrayData* base, int64_t key) {
252 return mode == MOpMode::Warn
253 ? PackedArray::RvalIntStrictVec(base, key)
254 : PackedArray::RvalIntVec(base, key);
257 template<MOpMode mode>
258 inline member_rval ElemVecPre(ArrayData* base, StringData* key) {
259 if (mode == MOpMode::Warn) throwInvalidArrayKeyException(key, base);
260 return member_rval{};
263 template<MOpMode mode>
264 inline member_rval ElemVecPre(ArrayData* base, TypedValue key) {
265 auto const dt = key.m_type;
266 if (LIKELY(isIntType(dt))) return ElemVecPre<mode>(base, key.m_data.num);
267 if (isStringType(dt)) return ElemVecPre<mode>(base, key.m_data.pstr);
268 throwInvalidArrayKeyException(&key, base);
271 template<MOpMode mode, KeyType keyType>
272 inline member_rval ElemVec(ArrayData* base, key_type<keyType> key) {
273 assertx(base->isVecArray());
274 auto result = ElemVecPre<mode>(base, key);
275 if (mode != MOpMode::Warn) {
276 if (UNLIKELY(!result)) return ElemEmptyish();
278 assertx(result.type() != KindOfUninit);
279 return result;
283 * Elem when base is a Dict
285 template<MOpMode mode>
286 inline member_rval ElemDictPre(ArrayData* base, int64_t key) {
287 return mode == MOpMode::Warn
288 ? MixedArray::RvalIntStrictDict(base, key)
289 : MixedArray::RvalIntDict(base, key);
292 template<MOpMode mode>
293 inline member_rval ElemDictPre(ArrayData* base, StringData* key) {
294 return mode == MOpMode::Warn
295 ? MixedArray::RvalStrStrictDict(base, key)
296 : MixedArray::RvalStrDict(base, key);
299 template<MOpMode mode>
300 inline member_rval ElemDictPre(ArrayData* base, TypedValue key) {
301 auto const dt = key.m_type;
302 if (isIntType(dt)) return ElemDictPre<mode>(base, key.m_data.num);
303 if (isStringType(dt)) return ElemDictPre<mode>(base, key.m_data.pstr);
304 throwInvalidArrayKeyException(&key, base);
307 template<MOpMode mode, KeyType keyType>
308 inline member_rval ElemDict(ArrayData* base, key_type<keyType> key) {
309 assertx(base->isDict());
310 auto result = ElemDictPre<mode>(base, key);
311 if (mode != MOpMode::Warn) {
312 if (UNLIKELY(!result)) return ElemEmptyish();
314 assertx(result.type() != KindOfUninit);
315 return result;
319 * Elem when base is a Keyset
321 template<MOpMode mode>
322 inline member_rval ElemKeysetPre(ArrayData* base, int64_t key) {
323 return mode == MOpMode::Warn
324 ? SetArray::RvalIntStrict(base, key)
325 : SetArray::RvalInt(base, key);
328 template<MOpMode mode>
329 inline member_rval ElemKeysetPre(ArrayData* base, StringData* key) {
330 return mode == MOpMode::Warn
331 ? SetArray::RvalStrStrict(base, key)
332 : SetArray::RvalStr(base, key);
335 template<MOpMode mode>
336 inline member_rval ElemKeysetPre(ArrayData* base, TypedValue key) {
337 auto const dt = key.m_type;
338 if (isIntType(dt)) return ElemKeysetPre<mode>(base, key.m_data.num);
339 if (isStringType(dt)) return ElemKeysetPre<mode>(base, key.m_data.pstr);
340 throwInvalidArrayKeyException(&key, base);
343 template<MOpMode mode, KeyType keyType>
344 inline member_rval ElemKeyset(ArrayData* base, key_type<keyType> key) {
345 assertx(base->isKeyset());
346 auto result = ElemKeysetPre<mode>(base, key);
347 if (mode != MOpMode::Warn) {
348 if (UNLIKELY(!result)) return ElemEmptyish();
350 assertx(isIntType(result.type()) || isStringType(result.type()));
351 return result;
355 * Elem when base is an Int64, Double, or Resource.
357 inline member_rval ElemScalar() {
358 if (RuntimeOption::EnableHipHopSyntax) {
359 raise_warning(Strings::CANNOT_USE_SCALAR_AS_ARRAY);
361 return ElemEmptyish();
365 * Elem when base is a Boolean
367 inline member_rval ElemBoolean(TypedValue* base) {
368 if (base->m_data.num) {
369 return ElemScalar();
371 return ElemEmptyish();
374 inline int64_t ElemStringPre(int64_t key) {
375 return key;
378 inline int64_t ElemStringPre(StringData* key) {
379 return key->toInt64(10);
382 inline int64_t ElemStringPre(TypedValue key) {
383 if (LIKELY(isIntType(key.m_type))) {
384 return key.m_data.num;
385 } else if (LIKELY(isStringType(key.m_type))) {
386 return key.m_data.pstr->toInt64(10);
387 } else {
388 raise_notice("String offset cast occurred");
389 return cellAsCVarRef(key).toInt64();
394 * Elem when base is a String
396 template<MOpMode mode, KeyType keyType>
397 inline member_rval ElemString(TypedValue& tvRef,
398 TypedValue* base,
399 key_type<keyType> key) {
400 auto offset = ElemStringPre(key);
402 if (offset < 0 || offset >= base->m_data.pstr->size()) {
403 if (mode == MOpMode::Warn) {
404 raise_notice("Uninitialized string offset: %" PRId64, offset);
406 tvRef = make_tv<KindOfPersistentString>(staticEmptyString());
407 } else {
408 tvRef = make_tv<KindOfPersistentString>(base->m_data.pstr->getChar(offset));
409 assert(tvRef.m_data.pstr->isStatic());
411 return member_rval { base->m_data.pstr, &tvRef };
415 * Elem when base is an Object
417 template<MOpMode mode, KeyType keyType>
418 inline member_rval ElemObject(TypedValue& tvRef,
419 TypedValue* base,
420 key_type<keyType> key) {
421 auto scratch = initScratchKey(key);
423 if (LIKELY(base->m_data.pobj->isCollection())) {
424 if (mode == MOpMode::Warn) {
425 return member_rval {
426 base->m_data.pobj,
427 collections::at(base->m_data.pobj, &scratch)
430 auto res = collections::get(base->m_data.pobj, &scratch);
431 if (!res) {
432 res = &tvRef;
433 tvWriteNull(*res);
435 return member_rval { base->m_data.pobj, res };
438 tvRef = objOffsetGet(instanceFromTv(base), scratch);
439 return member_rval { base->m_data.pobj, &tvRef };
443 * $result = $base[$key];
445 template<MOpMode mode, KeyType keyType, bool intishWarn>
446 NEVER_INLINE member_rval ElemSlow(TypedValue& tvRef,
447 TypedValue* base,
448 key_type<keyType> key) {
449 base = tvToCell(base);
450 assertx(cellIsPlausible(*base));
452 switch (base->m_type) {
453 case KindOfUninit:
454 case KindOfNull:
455 if (mode != MOpMode::None && RuntimeOption::EvalHackArrCompatNotices) {
456 raise_hackarr_compat_notice("Cannot index into null");
458 return ElemEmptyish();
459 case KindOfBoolean:
460 if (mode != MOpMode::None && RuntimeOption::EvalHackArrCompatNotices) {
461 raise_hackarr_compat_notice("Cannot index into a boolean");
463 return ElemBoolean(base);
464 case KindOfInt64:
465 case KindOfDouble:
466 case KindOfResource:
467 return ElemScalar();
468 case KindOfPersistentString:
469 case KindOfString:
470 return ElemString<mode, keyType>(tvRef, base, key);
471 case KindOfPersistentVec:
472 case KindOfVec:
473 return ElemVec<mode, keyType>(base->m_data.parr, key);
474 case KindOfPersistentDict:
475 case KindOfDict:
476 return ElemDict<mode, keyType>(base->m_data.parr, key);
477 case KindOfPersistentKeyset:
478 case KindOfKeyset:
479 return ElemKeyset<mode, keyType>(base->m_data.parr, key);
480 case KindOfPersistentArray:
481 case KindOfArray:
482 return ElemArray<mode, keyType, intishWarn>(base->m_data.parr, key);
483 case KindOfObject:
484 return ElemObject<mode, keyType>(tvRef, base, key);
485 case KindOfRef:
486 break;
488 unknownBaseType(base);
492 * Fast path for Elem assuming base is an Array. Does not unbox the returned
493 * pointer.
495 template<MOpMode mode, bool intishWarn, KeyType keyType = KeyType::Any>
496 inline const TypedValue* Elem(TypedValue& tvRef,
497 TypedValue* base,
498 key_type<keyType> key) {
499 assertx(mode != MOpMode::Define && mode != MOpMode::Unset);
500 assertx(tvIsPlausible(*base));
502 if (LIKELY(tvIsArray(base))) {
503 return ElemArray<mode, keyType, intishWarn>(
504 base->m_data.parr,
506 ).tv_ptr();
508 if (LIKELY(tvIsVecArray(base))) {
509 return ElemVec<mode, keyType>(base->m_data.parr, key).tv_ptr();
511 if (LIKELY(tvIsDict(base))) {
512 return ElemDict<mode, keyType>(base->m_data.parr, key).tv_ptr();
514 if (LIKELY(tvIsKeyset(base))) {
515 return ElemKeyset<mode, keyType>(base->m_data.parr, key).tv_ptr();
518 if (mode == MOpMode::InOut) throw_invalid_inout_base();
520 return ElemSlow<mode, keyType, intishWarn>(tvRef, base, key).tv_ptr();
523 template<MOpMode mode, bool reffy, bool intishWarn>
524 inline member_lval ElemDArrayPre(TypedValue* base, int64_t key, bool& defined) {
525 auto oldArr = base->m_data.parr;
527 defined = (mode != MOpMode::Warn) || oldArr->exists(key);
528 auto const lval = reffy
529 ? oldArr->lvalRef(key, oldArr->cowCheck())
530 : oldArr->lval(key, oldArr->cowCheck());
532 if (lval.arr_base() != oldArr) {
533 base->m_type = KindOfArray;
534 base->m_data.parr = lval.arr_base();
535 assertx(cellIsPlausible(*base));
536 decRefArr(oldArr);
539 return lval;
542 template<MOpMode mode, bool reffy, bool intishWarn>
543 inline member_lval ElemDArrayPre(TypedValue* base, StringData* key,
544 bool& defined) {
545 auto oldArr = base->m_data.parr;
547 auto const lval = [&]{
548 auto const cow = oldArr->cowCheck();
549 int64_t n;
550 if (oldArr->convertKey(key, n, intishWarn)) {
551 defined = (mode != MOpMode::Warn) || oldArr->exists(n);
552 return reffy ? oldArr->lvalRef(n, cow) : oldArr->lval(n, cow);
553 } else {
554 defined = (mode != MOpMode::Warn) || oldArr->exists(key);
555 return reffy ? oldArr->lvalRef(key, cow) : oldArr->lval(key, cow);
557 }();
559 if (lval.arr_base() != oldArr) {
560 base->m_type = KindOfArray;
561 base->m_data.parr = lval.arr_base();
562 assertx(cellIsPlausible(*base));
563 decRefArr(oldArr);
565 return lval;
568 template<MOpMode mode, bool reffy, bool intishWarn>
569 inline member_lval ElemDArrayPre(TypedValue* base, TypedValue key,
570 bool& defined) {
571 auto const dt = key.m_type;
572 if (isIntType(dt)) {
573 return ElemDArrayPre<mode, reffy, false>(base, key.m_data.num, defined);
575 if (isStringType(dt)) {
576 return ElemDArrayPre<mode, reffy, intishWarn>(
577 base, key.m_data.pstr, defined
580 auto& arr = tvAsVariant(base).asArrRef();
581 defined = (mode != MOpMode::Warn) || arr.exists(tvAsCVarRef(&key));
582 return reffy ? arr.lvalAtRef(key) : arr.lvalAt(key);
586 * ElemD when base is an Array
588 template<MOpMode mode, bool reffy, bool intishWarn, KeyType keyType>
589 inline TypedValue* ElemDArray(TypedValue* base, key_type<keyType> key) {
590 assertx(tvIsArray(base));
591 assertx(tvIsPlausible(*base));
593 bool defined;
594 auto lval = ElemDArrayPre<mode, reffy, intishWarn>(base, key, defined);
596 assertx(tvIsArray(base));
597 assertx(tvIsPlausible(*base));
598 assertx(lval.type() != KindOfUninit);
600 if (!defined) {
601 auto scratchKey = initScratchKey(key);
602 raise_notice(Strings::UNDEFINED_INDEX,
603 tvAsCVarRef(&scratchKey).toString().data());
606 return lval.tv_ptr();
610 * ElemD when base is a Vec
612 template <bool reffy>
613 inline TypedValue* ElemDVecPre(TypedValue* base, int64_t key) {
614 ArrayData* oldArr = base->m_data.parr;
616 if (reffy) throwRefInvalidArrayValueException(oldArr);
618 auto const lval = PackedArray::LvalIntVec(oldArr, key, oldArr->cowCheck());
619 if (lval.arr_base() != oldArr) {
620 base->m_type = KindOfVec;
621 base->m_data.parr = lval.arr_base();
622 assertx(cellIsPlausible(*base));
623 decRefArr(oldArr);
625 return lval.tv_ptr();
628 template <bool reffy>
629 inline TypedValue* ElemDVecPre(TypedValue* base, StringData* key) {
630 throwInvalidArrayKeyException(key, base->m_data.parr);
633 template <bool reffy>
634 inline TypedValue* ElemDVecPre(TypedValue* base, TypedValue key) {
635 auto const dt = key.m_type;
636 if (LIKELY(isIntType(dt))) return ElemDVecPre<reffy>(base, key.m_data.num);
637 if (isStringType(dt)) return ElemDVecPre<reffy>(base, key.m_data.pstr);
638 throwInvalidArrayKeyException(&key, base->m_data.parr);
641 template <bool reffy, KeyType keyType>
642 inline TypedValue* ElemDVec(TypedValue* base, key_type<keyType> key) {
643 assertx(tvIsVecArray(base));
644 assertx(tvIsPlausible(*base));
645 auto* result = ElemDVecPre<reffy>(base, key);
646 assertx(tvIsVecArray(base));
647 assertx(tvIsPlausible(*base));
648 assertx(result->m_type != KindOfUninit);
649 return result;
653 * ElemD when base is a Dict
655 template <bool reffy>
656 inline TypedValue* ElemDDictPre(TypedValue* base, int64_t key) {
657 ArrayData* oldArr = base->m_data.parr;
659 if (reffy) throwRefInvalidArrayValueException(oldArr);
661 auto const lval =
662 MixedArray::LvalSilentIntDict(oldArr, key, oldArr->cowCheck());
664 if (UNLIKELY(!lval)) {
665 assertx(oldArr == lval.arr_base());
666 throwOOBArrayKeyException(key, oldArr);
669 if (lval.arr_base() != oldArr) {
670 base->m_type = KindOfDict;
671 base->m_data.parr = lval.arr_base();
672 assertx(cellIsPlausible(*base));
673 decRefArr(oldArr);
676 return lval.tv_ptr();
679 template <bool reffy>
680 inline TypedValue* ElemDDictPre(TypedValue* base, StringData* key) {
681 ArrayData* oldArr = base->m_data.parr;
683 if (reffy) throwRefInvalidArrayValueException(oldArr);
685 auto const lval =
686 MixedArray::LvalSilentStrDict(oldArr, key, oldArr->cowCheck());
688 if (UNLIKELY(!lval)) {
689 assertx(oldArr == lval.arr_base());
690 throwOOBArrayKeyException(key, oldArr);
693 if (lval.arr_base() != oldArr) {
694 base->m_type = KindOfDict;
695 base->m_data.parr = lval.arr_base();
696 assertx(cellIsPlausible(*base));
697 decRefArr(oldArr);
700 return lval.tv_ptr();
703 template <bool reffy>
704 inline TypedValue* ElemDDictPre(TypedValue* base, TypedValue key) {
705 auto const dt = key.m_type;
706 if (isIntType(dt)) return ElemDDictPre<reffy>(base, key.m_data.num);
707 if (isStringType(dt)) return ElemDDictPre<reffy>(base, key.m_data.pstr);
708 throwInvalidArrayKeyException(&key, base->m_data.parr);
711 template <bool reffy, KeyType keyType>
712 inline TypedValue* ElemDDict(TypedValue* base, key_type<keyType> key) {
713 assertx(tvIsDict(base));
714 assertx(tvIsPlausible(*base));
715 auto* result = ElemDDictPre<reffy>(base, key);
716 assertx(tvIsDict(base));
717 assertx(tvIsPlausible(*base));
718 assertx(result->m_type != KindOfUninit);
719 return result;
723 * ElemD when base is a Keyset
725 template <bool reffy>
726 [[noreturn]] inline TypedValue*
727 ElemDKeysetPre(TypedValue* base, int64_t /*key*/) {
728 if (reffy) throwRefInvalidArrayValueException(base->m_data.parr);
729 throwInvalidKeysetOperation();
732 template <bool reffy>
733 [[noreturn]] inline TypedValue*
734 ElemDKeysetPre(TypedValue* base, StringData* /*key*/) {
735 if (reffy) throwRefInvalidArrayValueException(base->m_data.parr);
736 throwInvalidKeysetOperation();
739 template <bool reffy>
740 [[noreturn]]
741 inline TypedValue* ElemDKeysetPre(TypedValue* base, TypedValue key) {
742 auto const dt = key.m_type;
743 if (isIntType(dt)) ElemDKeysetPre<reffy>(base, key.m_data.num);
744 if (isStringType(dt)) ElemDKeysetPre<reffy>(base, key.m_data.pstr);
745 throwInvalidArrayKeyException(&key, base->m_data.parr);
748 template <bool reffy, KeyType keyType>
749 [[noreturn]]
750 inline TypedValue* ElemDKeyset(TypedValue* base, key_type<keyType> key) {
751 assertx(tvIsKeyset(base));
752 assertx(tvIsPlausible(*base));
753 ElemDKeysetPre<reffy>(base, key);
757 * ElemD when base is Null
759 template<MOpMode mode, KeyType keyType>
760 inline TypedValue* ElemDEmptyish(TypedValue* base, key_type<keyType> key) {
761 detail::checkPromotion(base);
762 auto scratchKey = initScratchKey(key);
763 tvAsVariant(base) = Array::Create();
764 auto const result = tvAsVariant(base).asArrRef().lvalAt(
765 cellAsCVarRef(scratchKey)
766 ).tv_ptr();
767 if (mode == MOpMode::Warn) {
768 raise_notice(Strings::UNDEFINED_INDEX,
769 tvAsCVarRef(&scratchKey).toString().data());
771 return result;
775 * ElemD when base is an Int64, Double, or Resource.
777 inline TypedValue* ElemDScalar(TypedValue& tvRef) {
778 raise_warning(Strings::CANNOT_USE_SCALAR_AS_ARRAY);
779 tvWriteNull(tvRef);
780 return &tvRef;
784 * ElemD when base is a Boolean
786 template<MOpMode mode, KeyType keyType>
787 inline TypedValue* ElemDBoolean(TypedValue& tvRef,
788 TypedValue* base,
789 key_type<keyType> key) {
790 if (base->m_data.num) {
791 return ElemDScalar(tvRef);
793 return ElemDEmptyish<mode, keyType>(base, key);
797 * ElemD when base is a String
799 template<MOpMode mode, KeyType keyType>
800 inline TypedValue* ElemDString(TypedValue* base, key_type<keyType> key) {
801 if (base->m_data.pstr->size() == 0) {
802 return ElemDEmptyish<mode, keyType>(base, key);
804 raise_error("Operator not supported for strings");
805 return nullptr;
809 * ElemD when base is an Object
811 template<MOpMode mode, bool reffy, KeyType keyType>
812 inline TypedValue* ElemDObject(TypedValue& tvRef, TypedValue* base,
813 key_type<keyType> key) {
814 auto scratchKey = initScratchKey(key);
815 auto obj = base->m_data.pobj;
817 if (LIKELY(obj->isCollection())) {
818 if (reffy) {
819 raise_error("Collection elements cannot be taken by reference");
820 return nullptr;
822 return collections::atLval(obj, &scratchKey);
823 } else if (obj->getVMClass()->classof(SystemLib::s_ArrayObjectClass)) {
824 auto storage = obj->getPropLval(SystemLib::s_ArrayObjectClass,
825 s_storage.get());
826 // ArrayObject should always have the 'storage' property...
827 assert(storage.has_ref());
828 return UNLIKELY(RuntimeOption::EvalHackArrCompatNotices)
829 ? ElemDArray<mode, reffy, true, keyType>(storage.tv_ptr(), key)
830 : ElemDArray<mode, reffy, false, keyType>(storage.tv_ptr(), key);
834 tvRef = objOffsetGet(instanceFromTv(base), scratchKey);
835 return &tvRef;
839 * Intermediate elem operation for defining member instructions.
841 * Returned pointer is not yet unboxed. (I.e. it cannot point into a RefData.)
843 template<MOpMode mode, bool reffy, bool intishWarn,
844 KeyType keyType = KeyType::Any>
845 TypedValue* ElemD(TypedValue& tvRef, TypedValue* base, key_type<keyType> key) {
846 assertx(mode == MOpMode::Define);
848 base = tvToCell(base);
849 assertx(cellIsPlausible(*base));
851 switch (base->m_type) {
852 case KindOfUninit:
853 case KindOfNull:
854 return ElemDEmptyish<mode, keyType>(base, key);
855 case KindOfBoolean:
856 return ElemDBoolean<mode, keyType>(tvRef, base, key);
857 case KindOfInt64:
858 case KindOfDouble:
859 case KindOfResource:
860 return ElemDScalar(tvRef);
861 case KindOfPersistentString:
862 case KindOfString:
863 return ElemDString<mode, keyType>(base, key);
864 case KindOfPersistentVec:
865 case KindOfVec:
866 return ElemDVec<reffy, keyType>(base, key);
867 case KindOfPersistentDict:
868 case KindOfDict:
869 return ElemDDict<reffy, keyType>(base, key);
870 case KindOfPersistentKeyset:
871 case KindOfKeyset:
872 return ElemDKeyset<reffy, keyType>(base, key);
873 case KindOfPersistentArray:
874 case KindOfArray:
875 return ElemDArray<mode, reffy, intishWarn, keyType>(base, key);
876 case KindOfObject:
877 return ElemDObject<mode, reffy, keyType>(tvRef, base, key);
878 case KindOfRef:
879 break;
881 unknownBaseType(base);
885 * Implementation for SetWithRef*ML bytecodes.
887 template<MOpMode mode, bool reffy, bool intishWarn,
888 KeyType keyType = KeyType::Any>
889 void SetWithRefMLElem(TypedValue& tvRef, TypedValue* base,
890 key_type<keyType> key, TypedValue val) {
891 assertx(mode == MOpMode::Define);
893 base = tvToCell(base);
894 assertx(cellIsPlausible(*base));
896 auto const elem = [&] {
897 switch (base->m_type) {
898 case KindOfUninit:
899 case KindOfNull:
900 return ElemDEmptyish<mode, keyType>(base, key);
901 case KindOfBoolean:
902 return ElemDBoolean<mode, keyType>(tvRef, base, key);
903 case KindOfInt64:
904 case KindOfDouble:
905 case KindOfResource:
906 return ElemDScalar(tvRef);
907 case KindOfPersistentString:
908 case KindOfString:
909 return ElemDString<mode, keyType>(base, key);
910 case KindOfPersistentVec:
911 case KindOfVec:
912 return ElemDVec<reffy, keyType>(base, key);
913 case KindOfPersistentDict:
914 case KindOfDict:
915 return ElemDDict<reffy, keyType>(base, key);
916 case KindOfPersistentKeyset:
917 case KindOfKeyset:
918 return ElemDKeyset<reffy, keyType>(base, key);
919 case KindOfPersistentArray:
920 case KindOfArray: {
921 // We want to notice for binding assignments here, but not for missing
922 // index, since we're about to initialize the value in that case.
923 // Rather than fork the Lval API to have warn and no-warn flavors, we
924 // instead lift the binding assignment warning here, and then disable
925 // Hack array notices.
926 if (reffy && RuntimeOption::EvalHackArrCompatNotices &&
927 !base->m_data.parr->isGlobalsArray()) {
928 raiseHackArrCompatRefBind(key);
930 SuppressHackArrCompatNotices shacn;
931 return ElemDArray<mode, reffy, intishWarn, keyType>(base, key);
933 case KindOfObject:
934 return ElemDObject<mode, reffy, keyType>(tvRef, base, key);
935 case KindOfRef:
936 break;
938 unknownBaseType(base);
939 }();
941 // Intentionally leak the old value pointed to by elem, including from magic
942 // methods.
943 tvDup(val, *elem);
947 * ElemU when base is Null
949 inline member_lval ElemUEmptyish() {
950 return member_lval { nullptr, const_cast<TypedValue*>(&immutable_null_base) };
953 template <bool intishWarn>
954 inline member_lval ElemUArrayImpl(TypedValue* base, int64_t key) {
955 auto oldArr = base->m_data.parr;
956 if (!oldArr->exists(key)) return ElemUEmptyish();
957 auto const lval = oldArr->lval(key, oldArr->cowCheck());
958 if (lval.arr_base() != oldArr) {
959 base->m_type = KindOfArray;
960 base->m_data.parr = lval.arr_base();
961 assertx(cellIsPlausible(*base));
962 decRefArr(oldArr);
964 return lval;
967 template <bool intishWarn>
968 inline member_lval ElemUArrayImpl(TypedValue* base, StringData* key) {
969 auto oldArr = base->m_data.parr;
970 int64_t n;
971 if (oldArr->convertKey(key, n, intishWarn)) {
972 if (!oldArr->exists(n)) return ElemUEmptyish();
973 auto const lval = oldArr->lval(n, oldArr->cowCheck());
974 if (lval.arr_base() != oldArr) {
975 base->m_type = KindOfArray;
976 base->m_data.parr = lval.arr_base();
977 assertx(cellIsPlausible(*base));
978 decRefArr(oldArr);
980 return lval;
981 } else {
982 if (!oldArr->exists(key)) return ElemUEmptyish();
983 auto const lval = oldArr->lval(key, oldArr->cowCheck());
984 if (lval.arr_base() != oldArr) {
985 base->m_type = KindOfArray;
986 base->m_data.parr = lval.arr_base();
987 assertx(cellIsPlausible(*base));
988 decRefArr(oldArr);
990 return lval;
994 template <bool intishWarn>
995 inline member_lval ElemUArrayImpl(TypedValue* base, TypedValue key) {
996 auto const dt = key.m_type;
997 if (isIntType(dt)) {
998 return ElemUArrayImpl<false>(base, key.m_data.num);
1000 if (isStringType(dt)) {
1001 return ElemUArrayImpl<intishWarn>(base, key.m_data.pstr);
1003 auto& arr = tvAsVariant(base).asArrRef();
1004 if (!arr.exists(keyAsValue(key))) {
1005 return ElemUEmptyish();
1007 return arr.lvalAt(tvAsCVarRef(&key));
1011 * ElemU when base is an Array
1013 template <bool intishWarn, KeyType keyType>
1014 inline TypedValue* ElemUArray(TypedValue* base, key_type<keyType> key) {
1015 assertx(tvIsArray(base));
1016 assertx(tvIsPlausible(*base));
1017 auto lval = ElemUArrayImpl<intishWarn>(base, key);
1018 assertx(tvIsArray(base));
1019 assertx(tvIsPlausible(*base));
1020 assertx(lval.type() != KindOfUninit);
1021 return lval.tv_ptr();
1025 * ElemU when base is a Vec
1027 inline TypedValue* ElemUVecPre(TypedValue* base, int64_t key) {
1028 ArrayData* oldArr = base->m_data.parr;
1029 auto const lval =
1030 PackedArray::LvalSilentIntVec(oldArr, key, oldArr->cowCheck());
1032 if (UNLIKELY(!lval)) {
1033 return ElemUEmptyish().tv_ptr();
1035 if (lval.arr_base() != oldArr) {
1036 base->m_type = KindOfVec;
1037 base->m_data.parr = lval.arr_base();
1038 assertx(cellIsPlausible(*base));
1039 decRefArr(oldArr);
1041 return lval.tv_ptr();
1044 inline TypedValue* ElemUVecPre(TypedValue* /*base*/, StringData* /*key*/) {
1045 return ElemUEmptyish().tv_ptr();
1048 inline TypedValue* ElemUVecPre(TypedValue* base, TypedValue key) {
1049 auto const dt = key.m_type;
1050 if (LIKELY(isIntType(dt))) return ElemUVecPre(base, key.m_data.num);
1051 if (isStringType(dt)) return ElemUVecPre(base, key.m_data.pstr);
1052 throwInvalidArrayKeyException(&key, base->m_data.parr);
1055 template <KeyType keyType>
1056 inline TypedValue* ElemUVec(TypedValue* base, key_type<keyType> key) {
1057 assertx(tvIsVecArray(base));
1058 assertx(tvIsPlausible(*base));
1059 auto* result = ElemUVecPre(base, key);
1060 assertx(tvIsVecArray(base));
1061 assertx(tvIsPlausible(*base));
1062 assertx(result->m_type != KindOfUninit);
1063 return result;
1067 * ElemU when base is a Dict
1069 inline TypedValue* ElemUDictPre(TypedValue* base, int64_t key) {
1070 ArrayData* oldArr = base->m_data.parr;
1071 auto const lval =
1072 MixedArray::LvalSilentIntDict(oldArr, key, oldArr->cowCheck());
1074 if (UNLIKELY(!lval)) {
1075 return ElemUEmptyish().tv_ptr();
1077 if (lval.arr_base() != oldArr) {
1078 base->m_type = KindOfDict;
1079 base->m_data.parr = lval.arr_base();
1080 assertx(cellIsPlausible(*base));
1081 decRefArr(oldArr);
1083 return lval.tv_ptr();
1086 inline TypedValue* ElemUDictPre(TypedValue* base, StringData* key) {
1087 ArrayData* oldArr = base->m_data.parr;
1088 auto const lval =
1089 MixedArray::LvalSilentStrDict(oldArr, key, oldArr->cowCheck());
1091 if (UNLIKELY(!lval)) {
1092 return ElemUEmptyish().tv_ptr();
1094 if (lval.arr_base() != oldArr) {
1095 base->m_type = KindOfDict;
1096 base->m_data.parr = lval.arr_base();
1097 assertx(cellIsPlausible(*base));
1098 decRefArr(oldArr);
1100 return lval.tv_ptr();
1103 inline TypedValue* ElemUDictPre(TypedValue* base, TypedValue key) {
1104 auto const dt = key.m_type;
1105 if (isIntType(dt)) return ElemUDictPre(base, key.m_data.num);
1106 if (isStringType(dt)) return ElemUDictPre(base, key.m_data.pstr);
1107 throwInvalidArrayKeyException(&key, base->m_data.parr);
1110 template <KeyType keyType>
1111 inline TypedValue* ElemUDict(TypedValue* base, key_type<keyType> key) {
1112 assertx(tvIsDict(base));
1113 assertx(tvIsPlausible(*base));
1114 auto* result = ElemUDictPre(base, key);
1115 assertx(tvIsDict(base));
1116 assertx(tvIsPlausible(*base));
1117 assertx(result->m_type != KindOfUninit);
1118 return result;
1122 * ElemU when base is a Keyset
1124 [[noreturn]] inline TypedValue*
1125 ElemUKeysetPre(TypedValue* /*base*/, int64_t /*key*/) {
1126 throwInvalidKeysetOperation();
1129 [[noreturn]] inline TypedValue*
1130 ElemUKeysetPre(TypedValue* /*base*/, StringData* /*key*/) {
1131 throwInvalidKeysetOperation();
1134 [[noreturn]]
1135 inline TypedValue* ElemUKeysetPre(TypedValue* base, TypedValue key) {
1136 auto const dt = key.m_type;
1137 if (isIntType(dt)) ElemUKeysetPre(base, key.m_data.num);
1138 if (isStringType(dt)) ElemUKeysetPre(base, key.m_data.pstr);
1139 throwInvalidArrayKeyException(&key, base->m_data.parr);
1142 template <KeyType keyType>
1143 [[noreturn]]
1144 inline TypedValue* ElemUKeyset(TypedValue* base, key_type<keyType> key) {
1145 assertx(tvIsKeyset(base));
1146 assertx(tvIsPlausible(*base));
1147 ElemUKeysetPre(base, key);
1151 * ElemU when base is an Object
1153 template <KeyType keyType>
1154 inline TypedValue* ElemUObject(TypedValue& tvRef, TypedValue* base,
1155 key_type<keyType> key) {
1156 auto const scratchKey = initScratchKey(key);
1157 if (LIKELY(base->m_data.pobj->isCollection())) {
1158 return collections::atLval(base->m_data.pobj, &scratchKey);
1160 tvRef = objOffsetGet(instanceFromTv(base), scratchKey);
1161 return &tvRef;
1165 * Intermediate Elem operation for an unsetting member instruction.
1167 * Returned pointer is not yet unboxed. (I.e. it cannot point into a RefData.)
1169 template <bool intishWarn, KeyType keyType = KeyType::Any>
1170 TypedValue* ElemU(TypedValue& tvRef, TypedValue* base, key_type<keyType> key) {
1171 base = tvToCell(base);
1172 assertx(cellIsPlausible(*base));
1174 switch (base->m_type) {
1175 case KindOfUninit:
1176 case KindOfNull:
1177 case KindOfBoolean:
1178 case KindOfInt64:
1179 case KindOfDouble:
1180 case KindOfResource:
1181 // Unset on scalar base never modifies the base, but the const_cast is
1182 // necessary to placate the type system.
1183 return const_cast<TypedValue*>(&immutable_uninit_base);
1184 case KindOfPersistentString:
1185 case KindOfString:
1186 raise_error(Strings::OP_NOT_SUPPORTED_STRING);
1187 return nullptr;
1188 case KindOfPersistentVec:
1189 case KindOfVec:
1190 return ElemUVec<keyType>(base, key);
1191 case KindOfPersistentDict:
1192 case KindOfDict:
1193 return ElemUDict<keyType>(base, key);
1194 case KindOfPersistentKeyset:
1195 case KindOfKeyset:
1196 return ElemUKeyset<keyType>(base, key);
1197 case KindOfPersistentArray:
1198 case KindOfArray:
1199 return ElemUArray<intishWarn, keyType>(base, key);
1200 case KindOfObject:
1201 return ElemUObject<keyType>(tvRef, base, key);
1202 case KindOfRef:
1203 break;
1205 unknownBaseType(base);
1209 * NewElem when base is Null
1211 inline TypedValue* NewElemEmptyish(TypedValue* base) {
1212 detail::checkPromotion(base);
1213 Array a = Array::Create();
1214 TypedValue* result = a.lvalAt().tv_ptr();
1215 tvAsVariant(base) = a;
1216 return result;
1220 * NewElem when base is not a valid type (a number, true boolean,
1221 * non-empty string, etc.)
1223 inline TypedValue* NewElemInvalid(TypedValue& tvRef) {
1224 raise_warning("Cannot use a scalar value as an array");
1225 tvWriteNull(tvRef);
1226 return &tvRef;
1230 * NewElem when base is a Boolean
1232 inline TypedValue* NewElemBoolean(TypedValue& tvRef, TypedValue* base) {
1233 if (base->m_data.num) {
1234 return NewElemInvalid(tvRef);
1236 return NewElemEmptyish(base);
1240 * NewElem when base is a String
1242 inline TypedValue* NewElemString(TypedValue& tvRef, TypedValue* base) {
1243 if (base->m_data.pstr->size() == 0) {
1244 return NewElemEmptyish(base);
1246 return NewElemInvalid(tvRef);
1250 * NewElem when base is an Array
1252 template <bool reffy>
1253 inline TypedValue* NewElemArray(TypedValue* base) {
1254 assertx(tvIsArray(base));
1255 assertx(tvIsPlausible(*base));
1256 return reffy ?
1257 tvAsVariant(base).asArrRef().lvalAtRef().tv_ptr() :
1258 tvAsVariant(base).asArrRef().lvalAt().tv_ptr();
1262 * NewElem when base is an Object
1264 inline TypedValue* NewElemObject(TypedValue& tvRef, TypedValue* base) {
1265 if (base->m_data.pobj->isCollection()) {
1266 throw_cannot_use_newelem_for_lval_read_col();
1267 return nullptr;
1269 tvRef = objOffsetGet(instanceFromTv(base), make_tv<KindOfNull>());
1270 return &tvRef;
1274 * $result = ($base[] = ...);
1276 template <bool reffy>
1277 inline TypedValue* NewElem(TypedValue& tvRef,
1278 TypedValue* base) {
1279 base = tvToCell(base);
1280 assertx(cellIsPlausible(*base));
1282 switch (base->m_type) {
1283 case KindOfUninit:
1284 case KindOfNull:
1285 return NewElemEmptyish(base);
1286 case KindOfBoolean:
1287 return NewElemBoolean(tvRef, base);
1288 case KindOfInt64:
1289 case KindOfDouble:
1290 case KindOfResource:
1291 return NewElemInvalid(tvRef);
1292 case KindOfPersistentString:
1293 case KindOfString:
1294 return NewElemString(tvRef, base);
1295 case KindOfPersistentVec:
1296 case KindOfVec:
1297 throw_cannot_use_newelem_for_lval_read_vec();
1298 case KindOfPersistentDict:
1299 case KindOfDict:
1300 throw_cannot_use_newelem_for_lval_read_dict();
1301 case KindOfPersistentKeyset:
1302 case KindOfKeyset:
1303 throw_cannot_use_newelem_for_lval_read_keyset();
1304 case KindOfPersistentArray:
1305 case KindOfArray:
1306 return NewElemArray<reffy>(base);
1307 case KindOfObject:
1308 return NewElemObject(tvRef, base);
1309 case KindOfRef:
1310 break;
1312 unknownBaseType(base);
1316 * SetElem when base is Null
1318 template <KeyType keyType>
1319 inline void SetElemEmptyish(TypedValue* base, key_type<keyType> key,
1320 Cell* value) {
1321 detail::checkPromotion(base);
1322 auto const& scratchKey = initScratchKey(key);
1323 tvAsVariant(base) = Array::Create();
1324 tvAsVariant(base).asArrRef().set(tvAsCVarRef(&scratchKey),
1325 tvAsCVarRef(value));
1329 * SetElem when base is an Int64, Double, or Resource.
1331 template <bool setResult>
1332 inline void SetElemScalar(Cell* value) {
1333 raise_warning(Strings::CANNOT_USE_SCALAR_AS_ARRAY);
1334 if (!setResult) {
1335 throw InvalidSetMException(make_tv<KindOfNull>());
1337 tvDecRefGen((TypedValue*)value);
1338 tvWriteNull(*(TypedValue*)value);
1342 * SetElem when base is a Boolean
1344 template <bool setResult, KeyType keyType>
1345 inline void SetElemBoolean(TypedValue* base, key_type<keyType> key,
1346 Cell* value) {
1347 if (base->m_data.num) {
1348 SetElemScalar<setResult>(value);
1349 } else {
1350 SetElemEmptyish<keyType>(base, key, value);
1355 * Convert a key to integer for SetElem
1357 template<KeyType keyType>
1358 inline int64_t castKeyToInt(key_type<keyType> key) {
1359 return cellToInt(initScratchKey(key));
1362 template<>
1363 inline int64_t castKeyToInt<KeyType::Int>(int64_t key) {
1364 return key;
1368 * SetElem when base is a String
1370 template <bool setResult, KeyType keyType>
1371 inline StringData* SetElemString(TypedValue* base, key_type<keyType> key,
1372 Cell* value) {
1373 int baseLen = base->m_data.pstr->size();
1374 if (baseLen == 0) {
1375 SetElemEmptyish<keyType>(base, key, value);
1376 if (!setResult) {
1377 tvIncRefGen(*value);
1378 throw InvalidSetMException(*value);
1380 return nullptr;
1383 // Convert key to string offset.
1384 int64_t x = castKeyToInt<keyType>(key);
1385 if (UNLIKELY(x < 0 || x >= StringData::MaxSize)) {
1386 // Can't use PRId64 here because of order of inclusion issues
1387 raise_warning("Illegal string offset: %lld", (long long)x);
1388 if (!setResult) {
1389 throw InvalidSetMException(make_tv<KindOfNull>());
1391 tvDecRefGen(value);
1392 tvWriteNull(*value);
1393 return nullptr;
1396 // Compute how long the resulting string will be. Type needs
1397 // to agree with x.
1398 int64_t slen;
1399 if (x >= baseLen) {
1400 slen = x + 1;
1401 } else {
1402 slen = baseLen;
1405 // Extract the first character of (string)value.
1406 char y[2];
1408 StringData* valStr;
1409 if (LIKELY(isStringType(value->m_type))) {
1410 valStr = value->m_data.pstr;
1411 valStr->incRefCount();
1412 } else {
1413 valStr = tvCastToStringData(*value);
1416 if (valStr->size() > 0) {
1417 y[0] = valStr->data()[0];
1418 y[1] = '\0';
1419 } else {
1420 y[0] = '\0';
1422 decRefStr(valStr);
1425 // Create and save the result.
1426 if (x >= 0 && x < baseLen && !base->m_data.pstr->cowCheck()) {
1427 // Modify base in place. This is safe because the LHS owns the
1428 // only reference.
1429 auto const oldp = base->m_data.pstr;
1430 auto const newp = oldp->modifyChar(x, y[0]);
1431 if (UNLIKELY(newp != oldp)) {
1432 decRefStr(oldp);
1433 base->m_data.pstr = newp;
1434 base->m_type = KindOfString;
1436 } else {
1437 StringData* sd = StringData::Make(slen);
1438 char* s = sd->mutableData();
1439 memcpy(s, base->m_data.pstr->data(), baseLen);
1440 if (x > baseLen) {
1441 memset(&s[baseLen], ' ', slen - baseLen - 1);
1443 s[x] = y[0];
1444 sd->setSize(slen);
1445 decRefStr(base->m_data.pstr);
1446 base->m_data.pstr = sd;
1447 base->m_type = KindOfString;
1450 return StringData::Make(y, strlen(y), CopyString);
1454 * SetElem when base is an Object
1456 template <KeyType keyType>
1457 inline void SetElemObject(TypedValue* base, key_type<keyType> key,
1458 Cell* value) {
1459 auto const scratchKey = initScratchKey(key);
1460 if (LIKELY(base->m_data.pobj->isCollection())) {
1461 collections::set(base->m_data.pobj, &scratchKey, value);
1462 } else {
1463 objOffsetSet(instanceFromTv(base), scratchKey, value);
1468 * arrayRefShuffle is used by SetElemArray and by helpers for translated code
1469 * to do the necessary bookkeeping after mutating an array. The helpers return
1470 * an ArrayData* if and only if the base array was not in a php reference. If
1471 * the base array was in a reference, that reference may no longer refer to an
1472 * array after the set operation, so the helpers don't return anything.
1474 template<bool setRef> struct ShuffleReturn {};
1476 template<> struct ShuffleReturn<true> {
1477 typedef void return_type;
1478 static void do_return(ArrayData* /*a*/) {}
1481 template<> struct ShuffleReturn<false> {
1482 typedef ArrayData* return_type;
1483 static ArrayData* do_return(ArrayData* a) { return a; }
1486 template<bool setRef, DataType dt> inline
1487 typename ShuffleReturn<setRef>::return_type
1488 arrayRefShuffle(ArrayData* oldData, ArrayData* newData, TypedValue* base) {
1489 if (newData == oldData) {
1490 return ShuffleReturn<setRef>::do_return(oldData);
1493 if (setRef) {
1494 if (isArrayLikeType(base->m_type) && base->m_data.parr == oldData) {
1495 base->m_type = dt;
1496 base->m_data.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 assert(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 (RuntimeOption::EvalHackArrCompatNotices) {
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(TypedValue* base, key_type<keyType> key,
1582 Cell* value) {
1583 assertx(tvIsArray(base));
1584 assertx(tvIsPlausible(*base));
1586 ArrayData* a = base->m_data.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(TypedValue* base, key_type<keyType> key,
1628 Cell* value) {
1629 assertx(tvIsVecArray(base));
1630 assertx(tvIsPlausible(*base));
1632 ArrayData* a = base->m_data.parr;
1633 bool copy = a->cowCheck() ||
1634 (tvIsVecArray(value) && value->m_data.parr == a);
1636 auto* newData = SetElemVecPre<setResult>(a, key, value, copy);
1637 assertx(newData->isVecArray());
1639 arrayRefShuffle<true, KindOfVec>(a, newData, base);
1643 * SetElem when base is a Dict
1645 template<bool setResult>
1646 inline ArrayData* SetElemDictPre(ArrayData* a,
1647 int64_t key,
1648 Cell* value,
1649 bool copy) {
1650 return MixedArray::SetIntDict(a, key, *value, copy);
1653 template<bool setResult>
1654 inline ArrayData* SetElemDictPre(ArrayData* a,
1655 StringData* key,
1656 Cell* value,
1657 bool copy) {
1658 return MixedArray::SetStrDict(a, key, *value, copy);
1661 template<bool setResult>
1662 inline ArrayData* SetElemDictPre(ArrayData* a,
1663 TypedValue key,
1664 Cell* value,
1665 bool copy) {
1666 auto const dt = key.m_type;
1667 if (isIntType(dt)) return SetElemDictPre<setResult>(a, key.m_data.num,
1668 value, copy);
1669 if (isStringType(dt)) return SetElemDictPre<setResult>(a, key.m_data.pstr,
1670 value, copy);
1671 throwInvalidArrayKeyException(&key, a);
1674 template <bool setResult, KeyType keyType>
1675 inline void SetElemDict(TypedValue* base, key_type<keyType> key,
1676 Cell* value) {
1677 assertx(tvIsDict(base));
1678 assertx(tvIsPlausible(*base));
1680 ArrayData* a = base->m_data.parr;
1681 bool copy = a->cowCheck() ||
1682 (tvIsDict(value) && value->m_data.parr == a);
1684 auto* newData = SetElemDictPre<setResult>(a, key, value, copy);
1685 assertx(newData->isDict());
1687 arrayRefShuffle<true, KindOfDict>(a, newData, base);
1691 * SetElem() leaves the result in 'value', rather than returning it as in
1692 * SetOpElem(), because doing so avoids a dup operation that SetOpElem() can't
1693 * get around.
1695 template <bool setResult, KeyType keyType, bool intishWarn>
1696 NEVER_INLINE
1697 StringData* SetElemSlow(TypedValue* base, key_type<keyType> key, Cell* value) {
1698 base = tvToCell(base);
1699 assertx(cellIsPlausible(*base));
1701 switch (base->m_type) {
1702 case KindOfUninit:
1703 case KindOfNull:
1704 SetElemEmptyish<keyType>(base, key, value);
1705 return nullptr;
1706 case KindOfBoolean:
1707 SetElemBoolean<setResult, keyType>(base, key, value);
1708 return nullptr;
1709 case KindOfInt64:
1710 case KindOfDouble:
1711 case KindOfResource:
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(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(TypedValue* 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(tvIsVecArray(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(TypedValue* base, Cell* value) {
1768 detail::checkPromotion(base);
1769 Array a = Array::Create();
1770 a.append(cellAsCVarRef(*value));
1771 tvAsVariant(base) = a;
1775 * SetNewElem when base is Int64 or Double
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((TypedValue*)value);
1784 tvWriteNull(*(TypedValue*)value);
1788 * SetNewElem when base is a Boolean
1790 template <bool setResult>
1791 inline void SetNewElemBoolean(TypedValue* base, Cell* value) {
1792 if (base->m_data.num) {
1793 SetNewElemScalar<setResult>(value);
1794 } else {
1795 SetNewElemEmptyish(base, value);
1800 * SetNewElem when base is a String
1802 inline void SetNewElemString(TypedValue* base, Cell* value) {
1803 int baseLen = base->m_data.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(TypedValue* base, Cell* value) {
1815 base = tvToCell(base);
1816 assertx(tvIsArray(base));
1817 assertx(tvIsPlausible(*base));
1818 auto a = base->m_data.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 base->m_type = KindOfArray;
1824 base->m_data.parr = a2;
1825 a->decRefAndRelease();
1830 * SetNewElem when base is a Vec
1832 inline void SetNewElemVec(TypedValue* base, Cell* value) {
1833 base = tvToCell(base);
1834 assertx(tvIsVecArray(base));
1835 assertx(tvIsPlausible(*base));
1836 auto a = base->m_data.parr;
1837 auto const copy = a->cowCheck() ||
1838 (tvIsVecArray(value) && value->m_data.parr == a);
1839 auto a2 = PackedArray::AppendVec(a, *value, copy);
1840 if (a2 != a) {
1841 base->m_type = KindOfVec;
1842 base->m_data.parr = a2;
1843 assertx(cellIsPlausible(*base));
1844 a->decRefAndRelease();
1849 * SetNewElem when base is a Dict
1851 inline void SetNewElemDict(TypedValue* base, Cell* value) {
1852 base = tvToCell(base);
1853 assertx(tvIsDict(base));
1854 assertx(tvIsPlausible(*base));
1855 auto a = base->m_data.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 base->m_type = KindOfDict;
1861 base->m_data.parr = a2;
1862 assertx(cellIsPlausible(*base));
1863 a->decRefAndRelease();
1868 * SetNewElem when base is a Keyset
1870 inline void SetNewElemKeyset(TypedValue* base, Cell* value) {
1871 base = tvToCell(base);
1872 assertx(tvIsKeyset(base));
1873 assertx(tvIsPlausible(*base));
1874 auto a = base->m_data.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 base->m_type = KindOfKeyset;
1880 base->m_data.parr = a2;
1881 assertx(cellIsPlausible(*base));
1882 a->decRefAndRelease();
1887 * SetNewElem when base is an Object
1889 inline void SetNewElemObject(TypedValue* base, Cell* value) {
1890 if (LIKELY(base->m_data.pobj->isCollection())) {
1891 collections::append(base->m_data.pobj, (TypedValue*)value);
1892 } else {
1893 objOffsetAppend(instanceFromTv(base), (TypedValue*)value);
1898 * $base[] = ...
1900 template <bool setResult>
1901 inline void SetNewElem(TypedValue* base, Cell* value) {
1902 base = tvToCell(base);
1903 assertx(cellIsPlausible(*base));
1905 switch (base->m_type) {
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 return SetNewElemScalar<setResult>(value);
1915 case KindOfPersistentString:
1916 case KindOfString:
1917 return SetNewElemString(base, value);
1918 case KindOfPersistentVec:
1919 case KindOfVec:
1920 return SetNewElemVec(base, value);
1921 case KindOfPersistentDict:
1922 case KindOfDict:
1923 return SetNewElemDict(base, value);
1924 case KindOfPersistentKeyset:
1925 case KindOfKeyset:
1926 return SetNewElemKeyset(base, value);
1927 case KindOfPersistentArray:
1928 case KindOfArray:
1929 return SetNewElemArray(base, value);
1930 case KindOfObject:
1931 return SetNewElemObject(base, value);
1932 case KindOfRef:
1933 break;
1935 unknownBaseType(base);
1939 * SetOpElem when base is Null
1941 inline TypedValue* SetOpElemEmptyish(SetOpOp op, Cell* base,
1942 TypedValue key, Cell* rhs) {
1943 assert(cellIsPlausible(*base));
1945 detail::checkPromotion(base);
1947 Array a = Array::Create();
1948 auto const lval = a.lvalAt(tvAsCVarRef(&key));
1949 tvAsVariant(base) = a;
1950 if (MoreWarnings) {
1951 raise_notice(Strings::UNDEFINED_INDEX,
1952 tvAsCVarRef(&key).toString().data());
1954 setopBody(lval.tv_ptr(), op, rhs);
1955 return lval.tv_ptr();
1959 * SetOpElem when base is Int64 or Double
1961 inline TypedValue* 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 TypedValue* SetOpElem(TypedValue& tvRef,
1972 SetOpOp op, TypedValue* base,
1973 TypedValue key, Cell* rhs) {
1974 base = tvToCell(base);
1975 assertx(cellIsPlausible(*base));
1977 switch (base->m_type) {
1978 case KindOfUninit:
1979 case KindOfNull:
1980 return SetOpElemEmptyish(op, base, key, rhs);
1982 case KindOfBoolean:
1983 if (base->m_data.num) {
1984 return SetOpElemScalar(tvRef);
1986 return SetOpElemEmptyish(op, base, key, rhs);
1988 case KindOfInt64:
1989 case KindOfDouble:
1990 case KindOfResource:
1991 return SetOpElemScalar(tvRef);
1993 case KindOfPersistentString:
1994 case KindOfString:
1995 if (base->m_data.pstr->size() != 0) {
1996 raise_error("Cannot use assign-op operators with overloaded "
1997 "objects nor string offsets");
1999 return SetOpElemEmptyish(op, base, key, rhs);
2001 case KindOfPersistentVec:
2002 case KindOfVec: {
2003 TypedValue* result;
2004 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 TypedValue* result;
2013 result = ElemDDict<false, KeyType::Any>(base, key);
2014 result = tvAssertCell(result);
2015 setopBody(result, op, rhs);
2016 return result;
2019 case KindOfPersistentKeyset:
2020 case KindOfKeyset:
2021 throwInvalidKeysetOperation();
2023 case KindOfPersistentArray:
2024 case KindOfArray: {
2025 if (UNLIKELY(
2026 RuntimeOption::EvalHackArrCompatNotices &&
2027 !ArrNR{base->m_data.parr}.asArray().exists(tvAsCVarRef(&key))
2028 )) {
2029 raiseHackArrCompatMissingSetOp();
2031 TypedValue* result;
2032 auto constexpr mode = MoreWarnings ? MOpMode::Warn : MOpMode::None;
2033 result = ElemDArray<mode, false, intishWarn, KeyType::Any>(base, key);
2034 result = tvToCell(result);
2035 setopBody(result, op, rhs);
2036 return result;
2039 case KindOfObject: {
2040 TypedValue* result;
2041 if (LIKELY(base->m_data.pobj->isCollection())) {
2042 result = collections::atRw(base->m_data.pobj, &key);
2043 setopBody(tvToCell(result), op, rhs);
2044 } else {
2045 tvRef = objOffsetGet(instanceFromTv(base), key);
2046 result = &tvRef;
2047 setopBody(tvToCell(result), op, rhs);
2048 objOffsetSet(instanceFromTv(base), key, result, false);
2050 return result;
2053 case KindOfRef:
2054 break;
2056 unknownBaseType(base);
2059 inline TypedValue* SetOpNewElemEmptyish(SetOpOp op,
2060 TypedValue* base, Cell* rhs) {
2061 detail::checkPromotion(base);
2062 Array a = Array::Create();
2063 TypedValue* result = a.lvalAt().tv_ptr();
2064 tvAsVariant(base) = a;
2065 setopBody(tvToCell(result), op, rhs);
2066 return result;
2068 inline TypedValue* SetOpNewElemScalar(TypedValue& tvRef) {
2069 raise_warning(Strings::CANNOT_USE_SCALAR_AS_ARRAY);
2070 tvWriteNull(tvRef);
2071 return &tvRef;
2073 inline TypedValue* SetOpNewElem(TypedValue& tvRef,
2074 SetOpOp op, TypedValue* base,
2075 Cell* rhs) {
2076 base = tvToCell(base);
2077 assertx(cellIsPlausible(*base));
2079 switch (base->m_type) {
2080 case KindOfUninit:
2081 case KindOfNull:
2082 return SetOpNewElemEmptyish(op, base, rhs);
2084 case KindOfBoolean:
2085 if (base->m_data.num) {
2086 return SetOpNewElemScalar(tvRef);
2088 return SetOpNewElemEmptyish(op, base, rhs);
2090 case KindOfInt64:
2091 case KindOfDouble:
2092 case KindOfResource:
2093 return SetOpNewElemScalar(tvRef);
2095 case KindOfPersistentString:
2096 case KindOfString:
2097 if (base->m_data.pstr->size() != 0) {
2098 raise_error("[] operator not supported for strings");
2100 return SetOpNewElemEmptyish(op, base, rhs);
2102 case KindOfPersistentVec:
2103 case KindOfVec:
2104 throw_cannot_use_newelem_for_lval_read_vec();
2105 case KindOfPersistentDict:
2106 case KindOfDict:
2107 throw_cannot_use_newelem_for_lval_read_dict();
2108 case KindOfPersistentKeyset:
2109 case KindOfKeyset:
2110 throw_cannot_use_newelem_for_lval_read_keyset();
2112 case KindOfPersistentArray:
2113 case KindOfArray: {
2114 TypedValue* result;
2115 result = tvAsVariant(base).asArrRef().lvalAt().tv_ptr();
2116 setopBody(tvToCell(result), op, rhs);
2117 return result;
2120 case KindOfObject: {
2121 TypedValue* result;
2122 if (base->m_data.pobj->isCollection()) {
2123 throw_cannot_use_newelem_for_lval_read_col();
2124 result = nullptr;
2125 } else {
2126 tvRef = objOffsetGet(instanceFromTv(base), make_tv<KindOfNull>());
2127 result = &tvRef;
2128 setopBody(tvToCell(result), op, rhs);
2129 objOffsetAppend(instanceFromTv(base), result, false);
2131 return result;
2134 case KindOfRef:
2135 break;
2137 unknownBaseType(base);
2140 Cell incDecBodySlow(IncDecOp op, Cell* fr);
2142 inline Cell IncDecBody(IncDecOp op, Cell* fr) {
2143 assert(cellIsPlausible(*fr));
2145 if (UNLIKELY(fr->m_type != KindOfInt64)) {
2146 return incDecBodySlow(op, fr);
2149 auto copy = [&]() {
2150 assert(cellIsPlausible(*fr));
2151 return *fr;
2154 // fast cases, assuming integers overflow to ints
2155 switch (op) {
2156 case IncDecOp::PreInc:
2157 ++fr->m_data.num;
2158 return copy();
2159 case IncDecOp::PostInc: {
2160 auto const tmp = copy();
2161 ++fr->m_data.num;
2162 return tmp;
2164 case IncDecOp::PreDec:
2165 --fr->m_data.num;
2166 return copy();
2167 case IncDecOp::PostDec: {
2168 auto const tmp = copy();
2169 --fr->m_data.num;
2170 return tmp;
2172 default: break;
2175 // slow case, where integers can overflow to floats
2176 switch (op) {
2177 case IncDecOp::PreIncO:
2178 cellIncO(*fr);
2179 return copy();
2180 case IncDecOp::PostIncO: {
2181 auto const tmp = copy();
2182 cellIncO(*fr);
2183 return tmp;
2185 case IncDecOp::PreDecO:
2186 cellDecO(*fr);
2187 return copy();
2188 case IncDecOp::PostDecO: {
2189 auto const tmp = copy();
2190 cellDecO(*fr);
2191 return tmp;
2193 default: break;
2195 not_reached();
2198 inline Cell IncDecElemEmptyish(
2199 IncDecOp op,
2200 TypedValue* base,
2201 TypedValue key
2203 detail::checkPromotion(base);
2205 auto a = Array::Create();
2206 auto const lval = a.lvalAt(tvAsCVarRef(&key));
2207 tvAsVariant(base) = a;
2208 if (MoreWarnings) {
2209 raise_notice(Strings::UNDEFINED_INDEX,
2210 tvAsCVarRef(&key).toString().data());
2212 assert(lval.type() == KindOfNull);
2213 return IncDecBody(op, lval.tv_ptr());
2216 inline Cell IncDecElemScalar() {
2217 raise_warning(Strings::CANNOT_USE_SCALAR_AS_ARRAY);
2218 return make_tv<KindOfNull>();
2221 template <bool intishWarn>
2222 inline Cell IncDecElem(
2223 IncDecOp op,
2224 TypedValue* base,
2225 TypedValue key
2227 base = tvToCell(base);
2228 assertx(cellIsPlausible(*base));
2230 switch (base->m_type) {
2231 case KindOfUninit:
2232 case KindOfNull:
2233 return IncDecElemEmptyish(op, base, key);
2235 case KindOfBoolean:
2236 if (base->m_data.num) {
2237 return IncDecElemScalar();
2239 return IncDecElemEmptyish(op, base, key);
2241 case KindOfInt64:
2242 case KindOfDouble:
2243 case KindOfResource:
2244 return IncDecElemScalar();
2246 case KindOfPersistentString:
2247 case KindOfString:
2248 if (base->m_data.pstr->size() != 0) {
2249 raise_error("Cannot increment/decrement overloaded objects "
2250 "nor string offsets");
2252 return IncDecElemEmptyish(op, base, key);
2254 case KindOfPersistentVec:
2255 case KindOfVec: {
2256 auto result = ElemDVec<false, KeyType::Any>(base, key);
2257 return IncDecBody(op, tvAssertCell(result));
2260 case KindOfPersistentDict:
2261 case KindOfDict: {
2262 auto result = ElemDDict<false, KeyType::Any>(base, key);
2263 return IncDecBody(op, tvAssertCell(result));
2266 case KindOfPersistentKeyset:
2267 case KindOfKeyset:
2268 throwInvalidKeysetOperation();
2270 case KindOfPersistentArray:
2271 case KindOfArray: {
2272 if (UNLIKELY(
2273 RuntimeOption::EvalHackArrCompatNotices &&
2274 !ArrNR{base->m_data.parr}.asArray().exists(tvAsCVarRef(&key))
2275 )) {
2276 raiseHackArrCompatMissingIncDec();
2278 auto constexpr mode = MoreWarnings ? MOpMode::Warn : MOpMode::None;
2279 auto result =
2280 ElemDArray<mode, false, intishWarn, KeyType::Any>(base, key);
2281 return IncDecBody(op, tvToCell(result));
2284 case KindOfObject: {
2285 TypedValue* result;
2286 auto localTvRef = make_tv<KindOfUninit>();
2288 if (LIKELY(base->m_data.pobj->isCollection())) {
2289 result = collections::atRw(base->m_data.pobj, &key);
2290 assert(cellIsPlausible(*result));
2291 } else {
2292 localTvRef = objOffsetGet(instanceFromTv(base), key);
2293 result = tvToCell(&localTvRef);
2296 auto const dest = IncDecBody(op, result);
2297 tvDecRefGen(localTvRef);
2298 return dest;
2301 case KindOfRef:
2302 break;
2304 unknownBaseType(base);
2307 inline Cell IncDecNewElemEmptyish(
2308 IncDecOp op,
2309 TypedValue* base
2311 detail::checkPromotion(base);
2312 auto a = Array::Create();
2313 auto result = a.lvalAt().tv_ptr();
2314 tvAsVariant(base) = a;
2315 assert(result->m_type == KindOfNull);
2316 return IncDecBody(op, result);
2319 inline Cell IncDecNewElemScalar() {
2320 raise_warning(Strings::CANNOT_USE_SCALAR_AS_ARRAY);
2321 return make_tv<KindOfNull>();
2324 inline Cell IncDecNewElem(
2325 TypedValue& tvRef,
2326 IncDecOp op,
2327 TypedValue* base
2329 base = tvToCell(base);
2330 assertx(cellIsPlausible(*base));
2332 switch (base->m_type) {
2333 case KindOfUninit:
2334 case KindOfNull:
2335 return IncDecNewElemEmptyish(op, base);
2337 case KindOfBoolean:
2338 if (base->m_data.num) {
2339 return IncDecNewElemScalar();
2341 return IncDecNewElemEmptyish(op, base);
2343 case KindOfInt64:
2344 case KindOfDouble:
2345 case KindOfResource:
2346 return IncDecNewElemScalar();
2348 case KindOfPersistentString:
2349 case KindOfString:
2350 if (base->m_data.pstr->size() != 0) {
2351 raise_error("[] operator not supported for strings");
2353 return IncDecNewElemEmptyish(op, base);
2355 case KindOfPersistentVec:
2356 case KindOfVec:
2357 throw_cannot_use_newelem_for_lval_read_vec();
2358 case KindOfPersistentDict:
2359 case KindOfDict:
2360 throw_cannot_use_newelem_for_lval_read_dict();
2361 case KindOfPersistentKeyset:
2362 case KindOfKeyset:
2363 throw_cannot_use_newelem_for_lval_read_keyset();
2365 case KindOfPersistentArray:
2366 case KindOfArray: {
2367 TypedValue* result = tvAsVariant(base).asArrRef().lvalAt().tv_ptr();
2368 assert(result->m_type == KindOfNull);
2369 return IncDecBody(op, tvToCell(result));
2372 case KindOfObject: {
2373 TypedValue* result;
2374 if (base->m_data.pobj->isCollection()) {
2375 throw_cannot_use_newelem_for_lval_read_col();
2377 tvRef = objOffsetGet(instanceFromTv(base), make_tv<KindOfNull>());
2378 result = tvToCell(&tvRef);
2379 return IncDecBody(op, result);
2382 case KindOfRef:
2383 break;
2385 unknownBaseType(base);
2389 * UnsetElemArray when key is an Int64
2391 template <bool intishWarn>
2392 inline ArrayData* UnsetElemArrayPre(ArrayData* a, int64_t key,
2393 bool copy) {
2394 return a->remove(key, copy);
2398 * UnsetElemArray when key is a String
2400 template <bool intishWarn>
2401 inline ArrayData* UnsetElemArrayPre(ArrayData* a, StringData* key,
2402 bool copy) {
2403 int64_t n;
2404 assert(a->isPHPArray());
2405 if (key->isStrictlyInteger(n)) {
2406 if (intishWarn) raise_intish_index_cast();
2407 return a->remove(n, copy);
2408 } else {
2409 return a->remove(key, copy);
2413 template <bool intishWarn>
2414 inline ArrayData* UnsetElemArrayPre(ArrayData* a, TypedValue key,
2415 bool copy) {
2416 if (isStringType(key.m_type)) {
2417 return UnsetElemArrayPre<intishWarn>(a, key.m_data.pstr, copy);
2419 if (key.m_type == KindOfInt64) {
2420 return UnsetElemArrayPre<false>(a, key.m_data.num, copy);
2422 auto const k = tvToKey(key, a);
2423 if (isNullType(k.m_type)) return a;
2424 return a->remove(k, copy);
2428 * UnsetElem when base is an Array
2430 template <KeyType keyType, bool intishWarn>
2431 inline void UnsetElemArray(TypedValue* base, key_type<keyType> key) {
2432 assertx(tvIsArray(base));
2433 assertx(tvIsPlausible(*base));
2434 ArrayData* a = base->m_data.parr;
2435 ArrayData* a2 = UnsetElemArrayPre<intishWarn>(a, key, a->cowCheck());
2437 if (a2 != a) {
2438 base->m_type = KindOfArray;
2439 base->m_data.parr = a2;
2440 assertx(cellIsPlausible(*base));
2441 a->decRefAndRelease();
2446 * UnsetElem when base is a Vec
2449 inline ArrayData* UnsetElemVecPre(ArrayData* a, int64_t key,
2450 bool copy) {
2451 return PackedArray::RemoveIntVec(a, key, copy);
2454 inline ArrayData*
2455 UnsetElemVecPre(ArrayData* a, StringData* /*key*/, bool /*copy*/) {
2456 /* Never contains strings, so a no-op. */
2457 return a;
2460 inline ArrayData* UnsetElemVecPre(ArrayData* a, TypedValue key,
2461 bool copy) {
2462 auto const dt = key.m_type;
2463 if (LIKELY(isIntType(dt))) return UnsetElemVecPre(a, key.m_data.num, copy);
2464 if (isStringType(dt)) return UnsetElemVecPre(a, key.m_data.pstr, copy);
2465 throwInvalidArrayKeyException(&key, a);
2468 template <KeyType keyType>
2469 inline void UnsetElemVec(TypedValue* base, key_type<keyType> key) {
2470 assertx(tvIsVecArray(base));
2471 assertx(tvIsPlausible(*base));
2472 ArrayData* a = base->m_data.parr;
2473 ArrayData* a2 = UnsetElemVecPre(a, key, a->cowCheck());
2474 assertx(a2->isVecArray() || a2->isDict());
2476 if (a2 != a) {
2477 base->m_type = a2->toDataType();
2478 base->m_data.parr = a2;
2479 assertx(cellIsPlausible(*base));
2480 a->decRefAndRelease();
2485 * UnsetElem when base is a Dict
2488 inline ArrayData* UnsetElemDictPre(ArrayData* a, int64_t key,
2489 bool copy) {
2490 return MixedArray::RemoveIntDict(a, key, copy);
2493 inline ArrayData* UnsetElemDictPre(ArrayData* a, StringData* key,
2494 bool copy) {
2495 return MixedArray::RemoveStrDict(a, key, copy);
2498 inline ArrayData* UnsetElemDictPre(ArrayData* a, TypedValue key,
2499 bool copy) {
2500 auto const dt = key.m_type;
2501 if (isIntType(dt)) return UnsetElemDictPre(a, key.m_data.num, copy);
2502 if (isStringType(dt)) return UnsetElemDictPre(a, key.m_data.pstr, copy);
2503 throwInvalidArrayKeyException(&key, a);
2506 template <KeyType keyType>
2507 inline void UnsetElemDict(TypedValue* base, key_type<keyType> key) {
2508 assertx(tvIsDict(base));
2509 assertx(tvIsPlausible(*base));
2510 ArrayData* a = base->m_data.parr;
2511 ArrayData* a2 = UnsetElemDictPre(a, key, a->cowCheck());
2513 if (a2 != a) {
2514 base->m_type = KindOfDict;
2515 base->m_data.parr = a2;
2516 assertx(cellIsPlausible(*base));
2517 a->decRefAndRelease();
2522 * UnsetElem when base is a Keyset
2525 inline ArrayData* UnsetElemKeysetPre(ArrayData* a, int64_t key,
2526 bool copy) {
2527 return SetArray::RemoveInt(a, key, copy);
2530 inline ArrayData* UnsetElemKeysetPre(ArrayData* a, StringData* key,
2531 bool copy) {
2532 return SetArray::RemoveStr(a, key, copy);
2535 inline ArrayData* UnsetElemKeysetPre(ArrayData* a, TypedValue key,
2536 bool copy) {
2537 auto const dt = key.m_type;
2538 if (isIntType(dt)) return UnsetElemKeysetPre(a, key.m_data.num, copy);
2539 if (isStringType(dt)) return UnsetElemKeysetPre(a, key.m_data.pstr, copy);
2540 throwInvalidArrayKeyException(&key, a);
2543 template <KeyType keyType>
2544 inline void UnsetElemKeyset(TypedValue* base, key_type<keyType> key) {
2545 assertx(tvIsKeyset(base));
2546 assertx(tvIsPlausible(*base));
2547 ArrayData* a = base->m_data.parr;
2548 ArrayData* a2 = UnsetElemKeysetPre(a, key, a->cowCheck());
2550 if (a2 != a) {
2551 base->m_type = KindOfKeyset;
2552 base->m_data.parr = a2;
2553 assertx(cellIsPlausible(*base));
2554 a->decRefAndRelease();
2559 * unset($base[$member])
2561 template <KeyType keyType, bool intishWarn>
2562 NEVER_INLINE
2563 void UnsetElemSlow(TypedValue* base, key_type<keyType> key) {
2564 base = tvToCell(base);
2565 assertx(cellIsPlausible(*base));
2567 switch (base->m_type) {
2568 case KindOfUninit:
2569 case KindOfNull:
2570 case KindOfBoolean:
2571 case KindOfInt64:
2572 case KindOfDouble:
2573 case KindOfResource:
2574 return; // Do nothing.
2576 case KindOfPersistentString:
2577 case KindOfString:
2578 raise_error(Strings::CANT_UNSET_STRING);
2579 return;
2581 case KindOfPersistentVec:
2582 case KindOfVec:
2583 UnsetElemVec<keyType>(base, key);
2584 return;
2586 case KindOfPersistentDict:
2587 case KindOfDict:
2588 UnsetElemDict<keyType>(base, key);
2589 return;
2591 case KindOfPersistentKeyset:
2592 case KindOfKeyset:
2593 UnsetElemKeyset<keyType>(base, key);
2594 return;
2596 case KindOfPersistentArray:
2597 case KindOfArray:
2598 UnsetElemArray<keyType, intishWarn>(base, key);
2599 return;
2601 case KindOfObject: {
2602 auto const& scratchKey = initScratchKey(key);
2603 if (LIKELY(base->m_data.pobj->isCollection())) {
2604 collections::unset(base->m_data.pobj, &scratchKey);
2605 } else {
2606 objOffsetUnset(instanceFromTv(base), scratchKey);
2608 return;
2611 case KindOfRef:
2612 break;
2614 unknownBaseType(base);
2618 * Fast path for UnsetElem assuming base is an Array
2620 template <bool intishWarn, KeyType keyType = KeyType::Any>
2621 inline void UnsetElem(TypedValue* base, key_type<keyType> key) {
2622 assertx(tvIsPlausible(*base));
2624 if (LIKELY(tvIsArray(base))) {
2625 return UnsetElemArray<keyType, intishWarn>(base, key);
2627 if (LIKELY(tvIsVecArray(base))) {
2628 return UnsetElemVec<keyType>(base, key);
2630 if (LIKELY(tvIsDict(base))) {
2631 return UnsetElemDict<keyType>(base, key);
2633 if (LIKELY(tvIsKeyset(base))) {
2634 return UnsetElemKeyset<keyType>(base, key);
2636 return UnsetElemSlow<keyType, intishWarn>(base, key);
2640 * IssetEmptyElem when base is an Object
2642 template<bool useEmpty, KeyType keyType>
2643 bool IssetEmptyElemObj(ObjectData* instance, key_type<keyType> key) {
2644 auto scratchKey = initScratchKey(key);
2645 if (LIKELY(instance->isCollection())) {
2646 return useEmpty
2647 ? collections::empty(instance, &scratchKey)
2648 : collections::isset(instance, &scratchKey);
2651 return useEmpty
2652 ? objOffsetEmpty(instance, scratchKey)
2653 : objOffsetIsset(instance, scratchKey);
2657 * IssetEmptyElem when base is a String
2659 template <bool useEmpty, KeyType keyType>
2660 bool IssetEmptyElemString(TypedValue* base, key_type<keyType> key) {
2661 // TODO Task #2716479: Fix this so that the warnings raised match
2662 // PHP5.
2663 auto scratchKey = initScratchKey(key);
2664 int64_t x;
2665 if (LIKELY(scratchKey.m_type == KindOfInt64)) {
2666 x = scratchKey.m_data.num;
2667 } else {
2668 TypedValue tv;
2669 cellDup(scratchKey, tv);
2670 bool badKey = false;
2671 if (isStringType(tv.m_type)) {
2672 const char* str = tv.m_data.pstr->data();
2673 size_t len = tv.m_data.pstr->size();
2674 while (len > 0 &&
2675 (*str == ' ' || *str == '\t' || *str == '\r' || *str == '\n')) {
2676 ++str;
2677 --len;
2679 int64_t n;
2680 badKey = !is_strictly_integer(str, len, n);
2681 } else if (isArrayLikeType(tv.m_type) || tv.m_type == KindOfObject ||
2682 tv.m_type == KindOfResource) {
2683 badKey = true;
2685 // Even if badKey == true, we still perform the cast so that we
2686 // raise the appropriate warnings.
2687 tvCastToInt64InPlace(&tv);
2688 if (badKey) {
2689 return useEmpty;
2691 x = tv.m_data.num;
2693 if (x < 0 || x >= base->m_data.pstr->size()) {
2694 return useEmpty;
2696 if (!useEmpty) {
2697 return true;
2700 auto str = base->m_data.pstr->getChar(x);
2701 assert(str->isStatic());
2702 return !str->toBoolean();
2706 * IssetEmptyElem when base is an Array
2708 template <bool useEmpty, KeyType keyType, bool intishWarn>
2709 bool IssetEmptyElemArray(ArrayData* a, key_type<keyType> key) {
2710 assertx(a->isPHPArray());
2711 auto const result = ElemArray<MOpMode::None, keyType, intishWarn>(a, key);
2712 if (useEmpty) {
2713 return !cellToBool(tvToCell(result.tv()));
2715 return !cellIsNull(tvToCell(result.tv()));
2719 * IssetEmptyElem when base is a Vec
2721 template <bool useEmpty, KeyType keyType>
2722 bool IssetEmptyElemVec(ArrayData* a, key_type<keyType> key) {
2723 assertx(a->isVecArray());
2724 auto const result = ElemVec<MOpMode::None, keyType>(a, key);
2725 if (useEmpty) {
2726 return !cellToBool(tvAssertCell(result.tv()));
2728 return !cellIsNull(tvAssertCell(result.tv()));
2732 * IssetEmptyElem when base is a Dict
2734 template <bool useEmpty, KeyType keyType>
2735 bool IssetEmptyElemDict(ArrayData* a, key_type<keyType> key) {
2736 assertx(a->isDict());
2737 auto const result = ElemDict<MOpMode::None, keyType>(a, key);
2738 if (useEmpty) {
2739 return !cellToBool(tvAssertCell(result.tv()));
2741 return !cellIsNull(tvAssertCell(result.tv()));
2745 * IssetEmptyElem when base is a Keyset
2747 template <bool useEmpty, KeyType keyType>
2748 bool IssetEmptyElemKeyset(ArrayData* a, key_type<keyType> key) {
2749 assertx(a->isKeyset());
2750 auto const result = ElemKeyset<MOpMode::None, keyType>(a, key);
2751 if (useEmpty) {
2752 return !cellToBool(tvAssertCell(result.tv()));
2754 return !cellIsNull(tvAssertCell(result.tv()));
2758 * isset/empty($base[$key])
2760 template <bool useEmpty, KeyType keyType, bool intishWarn>
2761 NEVER_INLINE bool IssetEmptyElemSlow(TypedValue* base, key_type<keyType> key) {
2762 base = tvToCell(base);
2763 assertx(cellIsPlausible(*base));
2765 switch (base->m_type) {
2766 case KindOfUninit:
2767 case KindOfNull:
2768 case KindOfBoolean:
2769 case KindOfInt64:
2770 case KindOfDouble:
2771 case KindOfResource:
2772 return useEmpty;
2774 case KindOfPersistentString:
2775 case KindOfString:
2776 return IssetEmptyElemString<useEmpty, keyType>(base, key);
2778 case KindOfPersistentVec:
2779 case KindOfVec:
2780 return IssetEmptyElemVec<useEmpty, keyType>(base->m_data.parr, key);
2782 case KindOfPersistentDict:
2783 case KindOfDict:
2784 return IssetEmptyElemDict<useEmpty, keyType>(base->m_data.parr, key);
2786 case KindOfPersistentKeyset:
2787 case KindOfKeyset:
2788 return IssetEmptyElemKeyset<useEmpty, keyType>(base->m_data.parr, key);
2790 case KindOfPersistentArray:
2791 case KindOfArray:
2792 return IssetEmptyElemArray<useEmpty, keyType, intishWarn>(
2793 base->m_data.parr, key
2796 case KindOfObject:
2797 return IssetEmptyElemObj<useEmpty, keyType>(base->m_data.pobj, key);
2799 case KindOfRef:
2800 break;
2802 unknownBaseType(base);
2806 * Fast path for IssetEmptyElem assuming base is an Array
2808 template <bool useEmpty, bool intishWarn, KeyType keyType = KeyType::Any>
2809 bool IssetEmptyElem(TypedValue* base, key_type<keyType> key) {
2810 assertx(tvIsPlausible(*base));
2812 if (LIKELY(tvIsArray(base))) {
2813 return IssetEmptyElemArray<useEmpty, keyType, intishWarn>(
2814 base->m_data.parr, key
2817 if (LIKELY(tvIsVecArray(base))) {
2818 return IssetEmptyElemVec<useEmpty, keyType>(base->m_data.parr, key);
2820 if (LIKELY(tvIsDict(base))) {
2821 return IssetEmptyElemDict<useEmpty, keyType>(base->m_data.parr, key);
2823 if (LIKELY(tvIsKeyset(base))) {
2824 return IssetEmptyElemKeyset<useEmpty, keyType>(base->m_data.parr, key);
2826 return IssetEmptyElemSlow<useEmpty, keyType, intishWarn>(base, key);
2829 template<MOpMode mode>
2830 inline TypedValue* propPreNull(TypedValue& tvRef) {
2831 tvWriteNull(tvRef);
2832 if (mode == MOpMode::Warn) {
2833 raise_notice("Cannot access property on non-object");
2835 return &tvRef;
2838 template <class F>
2839 inline void promoteToStdClass(TypedValue* base, bool warn, F fun) {
2840 if (!RuntimeOption::EvalPromoteEmptyObject) {
2841 // note that the whole point here is to guarantee that the property
2842 // never auto updates to a stdclass - so we must do this before
2843 // calling promote, and we don't want the try catch below around
2844 // this call.
2845 if (RuntimeOption::PHP7_EngineExceptions) {
2846 SystemLib::throwErrorObject(Strings::SET_PROP_NON_OBJECT);
2847 } else {
2848 SystemLib::throwExceptionObject(Strings::SET_PROP_NON_OBJECT);
2850 not_reached();
2853 Object obj { ObjectData::newInstance(SystemLib::s_stdclassClass) };
2854 if (base->m_type == KindOfString) {
2855 decRefStr(base->m_data.pstr);
2856 } else {
2857 assert(!isRefcountedType(base->m_type));
2859 base->m_type = KindOfObject;
2860 base->m_data.pobj = obj.get();
2862 if (warn) {
2863 // Behavior here is observable.
2864 // In PHP 5.6, raise_warning is called before updating base, so
2865 // the error_handler sees the original base; but if an exception
2866 // is thrown from the error handler, any catch block will see the
2867 // updated base.
2868 // In PHP 7+, raise_warning is called after updating base, but before
2869 // doing the work of fun, and again, if an exception is thrown, fun
2870 // still gets called before reaching the catch block.
2871 // We'll match PHP7, because we have no way of ensuring that base survives
2872 // across a call to the error_handler: eg $a[0][0][0]->foo = 0; if $a
2873 // started out null, and the error handler resets it to null, base is
2874 // left dangling.
2875 // Note that this means that the error handler can overwrite the object
2876 // so there is no guarantee that we have an object on return from
2877 // promoteToStdClass.
2878 try {
2879 raise_warning(Strings::CREATING_DEFAULT_OBJECT);
2880 } catch (const Object&) {
2881 fun(obj.get());
2882 throw;
2886 fun(obj.get());
2889 template<MOpMode mode>
2890 TypedValue* propPreStdclass(TypedValue& tvRef, TypedValue* base) {
2891 if (mode != MOpMode::Define) {
2892 return propPreNull<mode>(tvRef);
2895 promoteToStdClass(base, RuntimeOption::EnableHipHopSyntax,
2896 [] (ObjectData*) {});
2897 if (UNLIKELY(base->m_type != KindOfObject)) {
2898 // See the comments above. Although promoteToStdClass will have
2899 // either thrown an exception, or promoted base to an object, an
2900 // installed error handler might have caused it to be overwritten
2901 tvWriteNull(tvRef);
2902 return &tvRef;
2905 return base;
2908 template<MOpMode mode>
2909 TypedValue* propPre(TypedValue& tvRef, TypedValue* base) {
2910 base = tvToCell(base);
2911 switch (base->m_type) {
2912 case KindOfUninit:
2913 case KindOfNull:
2914 return propPreStdclass<mode>(tvRef, base);
2916 case KindOfBoolean:
2917 if (base->m_data.num) {
2918 return propPreNull<mode>(tvRef);
2920 return propPreStdclass<mode>(tvRef, base);
2922 case KindOfInt64:
2923 case KindOfDouble:
2924 case KindOfResource:
2925 return propPreNull<mode>(tvRef);
2927 case KindOfPersistentString:
2928 case KindOfString:
2929 if (base->m_data.pstr->size() != 0) {
2930 return propPreNull<mode>(tvRef);
2932 return propPreStdclass<mode>(tvRef, base);
2934 case KindOfPersistentVec:
2935 case KindOfVec:
2936 case KindOfPersistentDict:
2937 case KindOfDict:
2938 case KindOfPersistentKeyset:
2939 case KindOfKeyset:
2940 case KindOfPersistentArray:
2941 case KindOfArray:
2942 return propPreNull<mode>(tvRef);
2944 case KindOfObject:
2945 return base;
2947 case KindOfRef:
2948 break;
2950 unknownBaseType(base);
2953 inline TypedValue* nullSafeProp(TypedValue& tvRef,
2954 Class* ctx,
2955 TypedValue* base,
2956 StringData* key) {
2957 base = tvToCell(base);
2958 switch (base->m_type) {
2959 case KindOfUninit:
2960 case KindOfNull:
2961 tvWriteNull(tvRef);
2962 return &tvRef;
2963 case KindOfBoolean:
2964 case KindOfInt64:
2965 case KindOfDouble:
2966 case KindOfResource:
2967 case KindOfPersistentString:
2968 case KindOfString:
2969 case KindOfPersistentVec:
2970 case KindOfVec:
2971 case KindOfPersistentDict:
2972 case KindOfDict:
2973 case KindOfPersistentKeyset:
2974 case KindOfKeyset:
2975 case KindOfPersistentArray:
2976 case KindOfArray:
2977 tvWriteNull(tvRef);
2978 raise_notice("Cannot access property on non-object");
2979 return &tvRef;
2980 case KindOfObject:
2981 return base->m_data.pobj->prop(&tvRef, ctx, key);
2982 case KindOfRef:
2983 always_assert(false);
2985 not_reached();
2989 * Generic property access (PropX and PropDX end up here).
2991 * Returns a pointer to a number of possible places, but does not unbox it.
2992 * (The returned pointer is never pointing into a RefData.)
2994 template<MOpMode mode, KeyType keyType = KeyType::Any, bool reffy = false>
2995 inline TypedValue* PropObj(TypedValue& tvRef, const Class* ctx,
2996 ObjectData* instance, key_type<keyType> key) {
2997 auto keySD = prepareKey(key);
2998 SCOPE_EXIT { releaseKey<keyType>(keySD); };
3000 // Get property.
3001 if (mode == MOpMode::Define) {
3002 if (reffy) {
3003 return instance->propB(&tvRef, ctx, keySD);
3004 } else {
3005 return instance->propD(&tvRef, ctx, keySD);
3008 assert(!reffy);
3009 if (mode == MOpMode::None) {
3010 return instance->prop(&tvRef, ctx, keySD);
3012 if (mode == MOpMode::Warn) {
3013 return instance->propW(&tvRef, ctx, keySD);
3015 assert(mode == MOpMode::Unset);
3016 return instance->propD(&tvRef, ctx, keySD);
3019 template<MOpMode mode, KeyType keyType = KeyType::Any, bool reffy = false>
3020 inline TypedValue* Prop(TypedValue& tvRef,
3021 const Class* ctx,
3022 TypedValue* base,
3023 key_type<keyType> key) {
3024 auto result = propPre<mode>(tvRef, base);
3025 if (result->m_type == KindOfNull) {
3026 return result;
3028 assertx(result->m_type == KindOfObject);
3029 auto instance = instanceFromTv(result);
3030 return PropObj<mode,keyType,reffy>(tvRef, ctx, instance, key);
3033 template <bool useEmpty, KeyType kt>
3034 inline bool IssetEmptyPropObj(Class* ctx, ObjectData* instance,
3035 key_type<kt> key) {
3036 auto keySD = prepareKey(key);
3037 SCOPE_EXIT { releaseKey<kt>(keySD); };
3039 return useEmpty ?
3040 instance->propEmpty(ctx, keySD) :
3041 instance->propIsset(ctx, keySD);
3044 template <bool useEmpty, KeyType kt = KeyType::Any>
3045 bool IssetEmptyProp(Class* ctx, TypedValue* base, key_type<kt> key) {
3046 base = tvToCell(base);
3047 if (LIKELY(base->m_type == KindOfObject)) {
3048 return IssetEmptyPropObj<useEmpty, kt>(ctx, instanceFromTv(base), key);
3050 return useEmpty;
3053 template <bool setResult>
3054 inline void SetPropNull(Cell* val) {
3055 raise_warning("Cannot access property on non-object");
3056 if (setResult) {
3057 tvDecRefGen(val);
3058 tvWriteNull(*val);
3059 } else {
3060 throw InvalidSetMException(make_tv<KindOfNull>());
3064 inline void SetPropStdclass(TypedValue* base, TypedValue key, Cell* val) {
3065 promoteToStdClass(
3066 base,
3067 true,
3068 [&] (ObjectData* obj) {
3069 auto const keySD = prepareKey(key);
3070 SCOPE_EXIT { decRefStr(keySD); };
3071 obj->setProp(nullptr, keySD, *val);
3075 template <KeyType keyType>
3076 inline void SetPropObj(Class* ctx, ObjectData* instance,
3077 key_type<keyType> key, Cell* val) {
3078 StringData* keySD = prepareKey(key);
3079 SCOPE_EXIT { releaseKey<keyType>(keySD); };
3081 // Set property.
3082 instance->setProp(ctx, keySD, *val);
3085 // $base->$key = $val
3086 template <bool setResult, KeyType keyType = KeyType::Any>
3087 inline void SetProp(Class* ctx, TypedValue* base, key_type<keyType> key,
3088 Cell* val) {
3089 base = tvToCell(base);
3090 switch (base->m_type) {
3091 case KindOfUninit:
3092 case KindOfNull:
3093 return SetPropStdclass(base, initScratchKey(key), val);
3095 case KindOfBoolean:
3096 if (base->m_data.num) {
3097 return SetPropNull<setResult>(val);
3099 return SetPropStdclass(base, initScratchKey(key), val);
3101 case KindOfInt64:
3102 case KindOfDouble:
3103 case KindOfPersistentVec:
3104 case KindOfVec:
3105 case KindOfPersistentDict:
3106 case KindOfDict:
3107 case KindOfPersistentKeyset:
3108 case KindOfKeyset:
3109 case KindOfPersistentArray:
3110 case KindOfArray:
3111 case KindOfResource:
3112 return SetPropNull<setResult>(val);
3114 case KindOfPersistentString:
3115 case KindOfString:
3116 if (base->m_data.pstr->size() != 0) {
3117 return SetPropNull<setResult>(val);
3119 return SetPropStdclass(base, initScratchKey(key), val);
3121 case KindOfObject:
3122 return SetPropObj<keyType>(ctx, base->m_data.pobj, key, val);
3124 case KindOfRef:
3125 break;
3127 unknownBaseType(base);
3130 inline TypedValue* SetOpPropNull(TypedValue& tvRef) {
3131 raise_warning("Attempt to assign property of non-object");
3132 tvWriteNull(tvRef);
3133 return &tvRef;
3136 inline TypedValue* SetOpPropStdclass(TypedValue& tvRef, SetOpOp op,
3137 TypedValue* base, TypedValue key,
3138 Cell* rhs) {
3139 promoteToStdClass(
3140 base,
3141 true,
3142 [&] (ObjectData* obj) {
3143 StringData* keySD = prepareKey(key);
3144 SCOPE_EXIT { decRefStr(keySD); };
3145 tvWriteNull(tvRef);
3146 setopBody(tvAssertCell(&tvRef), op, rhs);
3147 obj->setProp(nullptr, keySD, tvAssertCell(tvRef));
3150 return &tvRef;
3153 inline TypedValue* SetOpPropObj(TypedValue& tvRef, Class* ctx,
3154 SetOpOp op, ObjectData* instance,
3155 TypedValue key, Cell* rhs) {
3156 StringData* keySD = prepareKey(key);
3157 SCOPE_EXIT { decRefStr(keySD); };
3158 TypedValue* result = instance->setOpProp(tvRef, ctx, op, keySD, rhs);
3159 return result;
3162 // $base->$key <op>= $rhs
3163 inline TypedValue* SetOpProp(TypedValue& tvRef,
3164 Class* ctx, SetOpOp op,
3165 TypedValue* base, TypedValue key,
3166 Cell* rhs) {
3167 base = tvToCell(base);
3168 switch (base->m_type) {
3169 case KindOfUninit:
3170 case KindOfNull:
3171 return SetOpPropStdclass(tvRef, op, base, key, rhs);
3173 case KindOfBoolean:
3174 if (base->m_data.num) {
3175 return SetOpPropNull(tvRef);
3177 return SetOpPropStdclass(tvRef, op, base, key, rhs);
3179 case KindOfInt64:
3180 case KindOfDouble:
3181 case KindOfPersistentVec:
3182 case KindOfVec:
3183 case KindOfPersistentDict:
3184 case KindOfDict:
3185 case KindOfPersistentKeyset:
3186 case KindOfKeyset:
3187 case KindOfPersistentArray:
3188 case KindOfArray:
3189 case KindOfResource:
3190 return SetOpPropNull(tvRef);
3192 case KindOfPersistentString:
3193 case KindOfString:
3194 if (base->m_data.pstr->size() != 0) {
3195 return SetOpPropNull(tvRef);
3197 return SetOpPropStdclass(tvRef, op, base, key, rhs);
3199 case KindOfObject:
3200 return SetOpPropObj(tvRef, ctx, op, instanceFromTv(base), key, rhs);
3202 case KindOfRef:
3203 break;
3205 unknownBaseType(base);
3208 inline Cell IncDecPropNull() {
3209 raise_warning("Attempt to increment/decrement property of non-object");
3210 return make_tv<KindOfNull>();
3213 inline Cell IncDecPropStdclass(IncDecOp op, TypedValue* base,
3214 TypedValue key) {
3215 Cell dest;
3216 promoteToStdClass(
3217 base,
3218 true,
3219 [&] (ObjectData* obj) {
3220 StringData* keySD = prepareKey(key);
3221 SCOPE_EXIT { decRefStr(keySD); };
3222 TypedValue tv;
3223 tvWriteNull(tv);
3224 dest = IncDecBody(op, &tv);
3225 obj->setProp(nullptr, keySD, dest);
3226 assert(!isRefcountedType(tv.m_type));
3229 return dest;
3232 inline Cell IncDecPropObj(Class* ctx,
3233 IncDecOp op,
3234 ObjectData* base,
3235 TypedValue key) {
3236 auto keySD = prepareKey(key);
3237 SCOPE_EXIT { decRefStr(keySD); };
3238 return base->incDecProp(ctx, op, keySD);
3241 inline Cell IncDecProp(
3242 Class* ctx,
3243 IncDecOp op,
3244 TypedValue* base,
3245 TypedValue key
3247 base = tvToCell(base);
3248 switch (base->m_type) {
3249 case KindOfUninit:
3250 case KindOfNull:
3251 return IncDecPropStdclass(op, base, key);
3253 case KindOfBoolean:
3254 if (base->m_data.num) {
3255 return IncDecPropNull();
3257 return IncDecPropStdclass(op, base, key);
3259 case KindOfInt64:
3260 case KindOfDouble:
3261 case KindOfPersistentVec:
3262 case KindOfVec:
3263 case KindOfPersistentDict:
3264 case KindOfDict:
3265 case KindOfPersistentKeyset:
3266 case KindOfKeyset:
3267 case KindOfPersistentArray:
3268 case KindOfArray:
3269 case KindOfResource:
3270 return IncDecPropNull();
3272 case KindOfPersistentString:
3273 case KindOfString:
3274 if (base->m_data.pstr->size() != 0) {
3275 return IncDecPropNull();
3277 return IncDecPropStdclass(op, base, key);
3279 case KindOfObject:
3280 return IncDecPropObj(ctx, op, instanceFromTv(base), key);
3282 case KindOfRef:
3283 break;
3285 unknownBaseType(base);
3288 inline void UnsetPropObj(Class* ctx, ObjectData* instance, TypedValue key) {
3289 // Prepare key.
3290 auto keySD = prepareKey(key);
3291 SCOPE_EXIT { decRefStr(keySD); };
3292 // Unset property.
3293 instance->unsetProp(ctx, keySD);
3296 inline void UnsetProp(Class* ctx, TypedValue* base, TypedValue key) {
3297 // Validate base.
3298 base = tvToCell(base);
3299 if (LIKELY(base->m_type == KindOfObject)) {
3300 UnsetPropObj(ctx, instanceFromTv(base), key);
3304 ///////////////////////////////////////////////////////////////////////////////
3306 #endif // incl_HPHP_VM_MEMBER_OPERATIONS_H_