2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
17 #ifndef incl_HPHP_VM_MEMBER_OPERATIONS_H_
18 #define incl_HPHP_VM_MEMBER_OPERATIONS_H_
20 #include <type_traits>
22 #include "hphp/runtime/base/array-data.h"
23 #include "hphp/runtime/base/array-data-defs.h"
24 #include "hphp/runtime/base/builtin-functions.h"
25 #include "hphp/runtime/base/collections.h"
26 #include "hphp/runtime/base/req-root.h"
27 #include "hphp/runtime/base/strings.h"
28 #include "hphp/runtime/base/tv-conversions.h"
29 #include "hphp/runtime/base/tv-refcount.h"
30 #include "hphp/runtime/base/tv-type.h"
31 #include "hphp/runtime/base/type-array.h"
32 #include "hphp/runtime/base/type-string.h"
33 #include "hphp/runtime/base/mixed-array.h"
34 #include "hphp/runtime/base/packed-array.h"
35 #include "hphp/runtime/base/set-array.h"
36 #include "hphp/runtime/vm/runtime.h"
37 #include "hphp/system/systemlib.h"
41 const StaticString
s_storage("storage");
43 struct InvalidSetMException
: std::runtime_error
{
44 InvalidSetMException()
45 : std::runtime_error("Empty InvalidSetMException")
46 , m_tv(make_tv
<KindOfNull
>())
49 explicit InvalidSetMException(const TypedValue
& value
)
50 : std::runtime_error(folly::format("InvalidSetMException containing {}",
51 value
.pretty()).str())
55 ~InvalidSetMException() noexcept override
{}
57 const TypedValue
& tv() const { return m_tv
; };
60 /* m_tv will contain a TypedValue with a reference destined for the
62 req::root
<TypedValue
> m_tv
;
65 // When MoreWarnings is set to true, the VM will raise more warnings
66 // on SetOpM, IncDecM and CGetG, intended to match Zend.
67 const bool MoreWarnings
=
68 #ifdef HHVM_MORE_WARNINGS
76 * KeyType and the associated functions below are used to generate member
77 * operation functions specialized for certain key types. Many functions take a
78 * KeyType template parameter, then use key_type<keyType> as the type of their
79 * key parameter. Depending on which KeyType is used, the parameter will be a
80 * TypedValue, int64_t, or StringData*.
83 Any
, // Key is passed as a TypedValue and could be any type
84 Int
, // Key is passed as an int64_t
85 Str
, // Key is passed as a StringData*
88 /* KeyTypeTraits maps from KeyType to the C++ type holding they key. */
89 template<KeyType
> struct KeyTypeTraits
;
90 template<> struct KeyTypeTraits
<KeyType::Any
> { typedef TypedValue type
; };
91 template<> struct KeyTypeTraits
<KeyType::Int
> { typedef int64_t type
; };
92 template<> struct KeyTypeTraits
<KeyType::Str
> { typedef StringData
* type
; };
94 /* key_type is the type used in the signatures of functions taking a member
96 template<KeyType kt
> using key_type
= typename KeyTypeTraits
<kt
>::type
;
98 /* initScratchKey is used in scenarios where we want a TypedValue key
99 * regardless of what the current function was given. */
100 inline TypedValue
initScratchKey(TypedValue tv
) {
101 assertx(!isRefType(tv
.m_type
));
105 inline TypedValue
initScratchKey(int64_t key
) {
106 return make_tv
<KindOfInt64
>(key
);
109 inline TypedValue
initScratchKey(StringData
* key
) {
110 return make_tv
<KindOfString
>(key
);
113 /* keyAsValue transforms a key into a value suitable for indexing into an
115 inline const Variant
& keyAsValue(TypedValue
& key
) {
116 return tvAsCVarRef(&key
);
118 inline int64_t keyAsValue(int64_t key
) { return key
; }
119 inline StrNR
keyAsValue(StringData
* key
) { return StrNR(key
); }
121 /* prepareKey is used by operations that need to cast their key to a
122 * string. For generic keys, the returned value must be decreffed after use. */
123 StringData
* prepareAnyKey(TypedValue
* tv
);
124 inline StringData
* prepareKey(TypedValue tv
) { return prepareAnyKey(&tv
); }
125 inline StringData
* prepareKey(StringData
* sd
) { return sd
; }
127 /* releaseKey helps with decreffing a StringData* returned from
128 * prepareKey. When used with KeyType::Any, corresponding to
129 * prepareKey(TypedValue), it will consume the reference produced by
130 * prepareKey. When used with KeyType::Str, corresponding to
131 * prepareKey(StringData*), it is a nop. */
132 template<KeyType keyType
>
133 inline void releaseKey(StringData
* sd
) {
134 static_assert(keyType
== KeyType::Any
, "bad KeyType");
139 inline void releaseKey
<KeyType::Str
>(StringData
*) {
140 // do nothing. we don't own a reference to this string.
143 void objArrayAccess(ObjectData
* base
);
145 TypedValue
objOffsetGet(
151 bool objOffsetIsset(ObjectData
* base
, TypedValue offset
, bool validate
= true);
152 bool objOffsetEmpty(ObjectData
* base
, TypedValue offset
, bool validate
= true);
161 void objOffsetAppend(ObjectData
* base
, TypedValue
* val
, bool validate
= true);
162 void objOffsetUnset(ObjectData
* base
, TypedValue offset
);
164 inline ObjectData
* instanceFromTv(tv_rval tv
) {
165 assertx(tvIsObject(tv
));
169 [[noreturn
]] void throw_cannot_use_newelem_for_lval_read_col();
170 [[noreturn
]] void throw_cannot_use_newelem_for_lval_read_vec();
171 [[noreturn
]] void throw_cannot_use_newelem_for_lval_read_dict();
172 [[noreturn
]] void throw_cannot_use_newelem_for_lval_read_keyset();
174 [[noreturn
]] void unknownBaseType(DataType
);
176 void raise_inout_undefined_index(TypedValue
);
177 void raise_inout_undefined_index(int64_t i
);
178 void raise_inout_undefined_index(const StringData
* sd
);
182 ALWAYS_INLINE
void checkPromotion(tv_rval base
) {
183 if (LIKELY(!checkHACFalseyPromote())) return;
185 if (tvIsNull(base
)) {
186 raise_hackarr_compat_notice("Promoting null to array");
187 } else if (tvIsBool(base
)) {
188 raise_hackarr_compat_notice("Promoting false to array");
195 * Elem when base is Null
197 inline tv_rval
ElemEmptyish() {
198 return tv_rval
{ &immutable_null_base
};
201 template<MOpMode mode
, bool intishWarn
>
202 inline tv_rval
ElemArrayPre(ArrayData
* base
, int64_t key
) {
203 return mode
== MOpMode::Warn
? base
->rvalStrict(key
) : base
->rval(key
);
206 template<MOpMode mode
, bool intishWarn
>
207 inline tv_rval
ElemArrayPre(ArrayData
* base
, StringData
* key
) {
208 auto constexpr warn
= mode
== MOpMode::Warn
;
210 assertx(base
->isPHPArray());
211 if (key
->isStrictlyInteger(n
)) {
212 if (intishWarn
) raise_intish_index_cast();
213 return warn
? base
->rvalStrict(n
) : base
->rval(n
);
215 return warn
? base
->rvalStrict(key
) : base
->rval(key
);
219 template<MOpMode mode
, bool intishWarn
>
220 inline tv_rval
ElemArrayPre(ArrayData
* base
, TypedValue key
) {
221 auto const dt
= key
.m_type
;
223 return ElemArrayPre
<mode
, false>(base
, key
.m_data
.num
);
225 if (isStringType(dt
)) {
226 return ElemArrayPre
<mode
, intishWarn
>(base
, key
.m_data
.pstr
);
229 // TODO(#3888164): Array elements can never be KindOfUninit. This API should
231 auto const rval
= ArrNR
{base
}.asArray().rvalAt(cellAsCVarRef(key
));
232 return rval
.type() != KindOfUninit
? rval
: tv_rval
{ nullptr };
236 * Fast path for Elem assuming base is an Array. Does not unbox the returned
239 template<MOpMode mode
, KeyType keyType
, bool intishWarn
>
240 inline tv_rval
ElemArray(ArrayData
* base
, key_type
<keyType
> key
) {
241 assertx(base
->isPHPArray());
243 auto result
= ElemArrayPre
<mode
, intishWarn
>(base
, key
);
245 if (UNLIKELY(!result
)) {
246 if (mode
== MOpMode::Warn
) {
247 auto const scratch
= initScratchKey(key
);
248 raise_notice(Strings::UNDEFINED_INDEX
,
249 tvAsCVarRef(&scratch
).toString().data());
251 if (mode
== MOpMode::InOut
) {
252 raise_inout_undefined_index(initScratchKey(key
));
254 return ElemEmptyish();
257 assertx(result
.type() != KindOfUninit
);
262 * Elem when base is a Vec
264 template<MOpMode mode
>
265 inline tv_rval
ElemVecPre(ArrayData
* base
, int64_t key
) {
266 return mode
== MOpMode::Warn
|| mode
== MOpMode::InOut
267 ? PackedArray::RvalIntStrictVec(base
, key
)
268 : PackedArray::RvalIntVec(base
, key
);
271 template<MOpMode mode
>
272 inline tv_rval
ElemVecPre(ArrayData
* base
, StringData
* key
) {
273 if (mode
== MOpMode::Warn
|| mode
== MOpMode::InOut
) {
274 throwInvalidArrayKeyException(key
, base
);
279 template<MOpMode mode
>
280 inline tv_rval
ElemVecPre(ArrayData
* base
, TypedValue key
) {
281 auto const dt
= key
.m_type
;
282 if (LIKELY(isIntType(dt
))) return ElemVecPre
<mode
>(base
, key
.m_data
.num
);
283 if (isStringType(dt
)) return ElemVecPre
<mode
>(base
, key
.m_data
.pstr
);
284 throwInvalidArrayKeyException(&key
, base
);
287 template<MOpMode mode
, KeyType keyType
>
288 inline tv_rval
ElemVec(ArrayData
* base
, key_type
<keyType
> key
) {
289 assertx(base
->isVecArray());
290 auto result
= ElemVecPre
<mode
>(base
, key
);
291 if (mode
!= MOpMode::Warn
&& mode
!= MOpMode::InOut
) {
292 if (UNLIKELY(!result
)) return ElemEmptyish();
294 assertx(result
.type() != KindOfUninit
);
299 * Elem when base is a Dict
301 template<MOpMode mode
>
302 inline tv_rval
ElemDictPre(ArrayData
* base
, int64_t key
) {
303 return mode
== MOpMode::Warn
|| mode
== MOpMode::InOut
304 ? MixedArray::RvalIntStrictDict(base
, key
)
305 : MixedArray::RvalIntDict(base
, key
);
308 template<MOpMode mode
>
309 inline tv_rval
ElemDictPre(ArrayData
* base
, StringData
* key
) {
310 return mode
== MOpMode::Warn
|| mode
== MOpMode::InOut
311 ? MixedArray::RvalStrStrictDict(base
, key
)
312 : MixedArray::RvalStrDict(base
, key
);
315 template<MOpMode mode
>
316 inline tv_rval
ElemDictPre(ArrayData
* base
, TypedValue key
) {
317 auto const dt
= key
.m_type
;
318 if (isIntType(dt
)) return ElemDictPre
<mode
>(base
, key
.m_data
.num
);
319 if (isStringType(dt
)) return ElemDictPre
<mode
>(base
, key
.m_data
.pstr
);
320 throwInvalidArrayKeyException(&key
, base
);
323 template<MOpMode mode
, KeyType keyType
>
324 inline tv_rval
ElemDict(ArrayData
* base
, key_type
<keyType
> key
) {
325 assertx(base
->isDict());
326 auto result
= ElemDictPre
<mode
>(base
, key
);
327 if (mode
!= MOpMode::Warn
&& mode
!= MOpMode::InOut
) {
328 if (UNLIKELY(!result
)) return ElemEmptyish();
330 assertx(result
.type() != KindOfUninit
);
335 * Elem when base is a Keyset
337 template<MOpMode mode
>
338 inline tv_rval
ElemKeysetPre(ArrayData
* base
, int64_t key
) {
339 return mode
== MOpMode::Warn
|| mode
== MOpMode::InOut
340 ? SetArray::RvalIntStrict(base
, key
)
341 : SetArray::RvalInt(base
, key
);
344 template<MOpMode mode
>
345 inline tv_rval
ElemKeysetPre(ArrayData
* base
, StringData
* key
) {
346 return mode
== MOpMode::Warn
|| mode
== MOpMode::InOut
347 ? SetArray::RvalStrStrict(base
, key
)
348 : SetArray::RvalStr(base
, key
);
351 template<MOpMode mode
>
352 inline tv_rval
ElemKeysetPre(ArrayData
* base
, TypedValue key
) {
353 auto const dt
= key
.m_type
;
354 if (isIntType(dt
)) return ElemKeysetPre
<mode
>(base
, key
.m_data
.num
);
355 if (isStringType(dt
)) return ElemKeysetPre
<mode
>(base
, key
.m_data
.pstr
);
356 throwInvalidArrayKeyException(&key
, base
);
359 template<MOpMode mode
, KeyType keyType
>
360 inline tv_rval
ElemKeyset(ArrayData
* base
, key_type
<keyType
> key
) {
361 assertx(base
->isKeyset());
362 auto result
= ElemKeysetPre
<mode
>(base
, key
);
363 if (mode
!= MOpMode::Warn
&& mode
!= MOpMode::InOut
) {
364 if (UNLIKELY(!result
)) return ElemEmptyish();
366 assertx(isIntType(result
.type()) || isStringType(result
.type()));
371 * Elem when base is an Int64, Double, Resource, or Func.
373 inline tv_rval
ElemScalar() {
374 if (RuntimeOption::EnableHipHopSyntax
) {
375 raise_warning(Strings::CANNOT_USE_SCALAR_AS_ARRAY
);
377 return ElemEmptyish();
381 * Elem when base is a Boolean
383 inline tv_rval
ElemBoolean(tv_rval base
) {
384 return base
.val().num
? ElemScalar() : ElemEmptyish();
387 inline int64_t ElemStringPre(int64_t key
) {
391 inline int64_t ElemStringPre(StringData
* key
) {
392 return key
->toInt64(10);
395 inline int64_t ElemStringPre(TypedValue key
) {
396 if (LIKELY(isIntType(key
.m_type
))) {
397 return key
.m_data
.num
;
398 } else if (LIKELY(isStringType(key
.m_type
))) {
399 return key
.m_data
.pstr
->toInt64(10);
401 raise_notice("String offset cast occurred");
402 return cellAsCVarRef(key
).toInt64();
407 * Elem when base is a String
409 template<MOpMode mode
, KeyType keyType
>
410 inline tv_rval
ElemString(TypedValue
& tvRef
,
411 const StringData
* base
,
412 key_type
<keyType
> key
) {
413 auto offset
= ElemStringPre(key
);
415 if (offset
< 0 || offset
>= base
->size()) {
416 if (mode
== MOpMode::Warn
) {
417 raise_notice("Uninitialized string offset: %" PRId64
, offset
);
419 tvRef
= make_tv
<KindOfPersistentString
>(staticEmptyString());
421 tvRef
= make_tv
<KindOfPersistentString
>(base
->getChar(offset
));
422 assertx(tvRef
.m_data
.pstr
->isStatic());
424 return tv_rval
{ &tvRef
};
428 * Elem when base is an Object
430 template<MOpMode mode
, KeyType keyType
>
431 inline tv_rval
ElemObject(TypedValue
& tvRef
,
433 key_type
<keyType
> key
) {
434 auto scratch
= initScratchKey(key
);
436 if (LIKELY(base
->isCollection())) {
437 if (mode
== MOpMode::Warn
) {
438 return collections::at(base
, &scratch
);
440 auto res
= collections::get(base
, &scratch
);
448 tvRef
= objOffsetGet(base
, scratch
);
453 * $result = $base[$key];
455 template<MOpMode mode
, KeyType keyType
, bool intishWarn
>
456 NEVER_INLINE tv_rval
ElemSlow(TypedValue
& tvRef
,
458 key_type
<keyType
> key
) {
459 base
= base
.unboxed();
460 assertx(cellIsPlausible(*base
));
462 switch (base
.type()) {
465 return ElemEmptyish();
467 return ElemBoolean(base
);
474 case KindOfPersistentString
:
476 return ElemString
<mode
, keyType
>(tvRef
, base
.val().pstr
, key
);
477 case KindOfPersistentVec
:
479 return ElemVec
<mode
, keyType
>(base
.val().parr
, key
);
480 case KindOfPersistentDict
:
482 return ElemDict
<mode
, keyType
>(base
.val().parr
, key
);
483 case KindOfPersistentKeyset
:
485 return ElemKeyset
<mode
, keyType
>(base
.val().parr
, key
);
486 case KindOfPersistentArray
:
488 return ElemArray
<mode
, keyType
, intishWarn
>(base
.val().parr
, key
);
490 return ElemObject
<mode
, keyType
>(tvRef
, base
.val().pobj
, key
);
494 unknownBaseType(type(base
));
497 template<MOpMode mode
, bool intishWarn
, KeyType keyType
= KeyType::Any
>
498 inline tv_rval
Elem(TypedValue
& tvRef
,
500 key_type
<keyType
> key
) {
501 assertx(mode
!= MOpMode::Define
&& mode
!= MOpMode::Unset
);
502 assertx(tvIsPlausible(base
.tv()));
504 if (mode
== MOpMode::InOut
) {
505 if (UNLIKELY(tvIsRef(base
) && !base
.val().pref
->isReferenced())) {
506 base
= base
.unboxed();
510 if (LIKELY(tvIsArray(base
))) {
511 return ElemArray
<mode
, keyType
, intishWarn
>(base
.val().parr
, key
);
513 if (LIKELY(tvIsVec(base
))) {
514 return ElemVec
<mode
, keyType
>(base
.val().parr
, key
);
516 if (LIKELY(tvIsDict(base
))) {
517 return ElemDict
<mode
, keyType
>(base
.val().parr
, key
);
519 if (LIKELY(tvIsKeyset(base
))) {
520 return ElemKeyset
<mode
, keyType
>(base
.val().parr
, key
);
523 if (mode
== MOpMode::InOut
) throw_invalid_inout_base();
525 return ElemSlow
<mode
, keyType
, intishWarn
>(tvRef
, base
, key
);
528 template<MOpMode mode
, bool reffy
, bool intishWarn
>
529 inline tv_lval
ElemDArrayPre(tv_lval base
, int64_t key
, bool& defined
) {
530 auto oldArr
= val(base
).parr
;
532 defined
= (mode
!= MOpMode::Warn
) || oldArr
->exists(key
);
533 auto const lval
= reffy
534 ? oldArr
->lvalRef(key
, oldArr
->cowCheck())
535 : oldArr
->lval(key
, oldArr
->cowCheck());
537 if (lval
.arr
!= oldArr
) {
538 type(base
) = KindOfArray
;
539 val(base
).parr
= lval
.arr
;
540 assertx(cellIsPlausible(*base
));
547 template<MOpMode mode
, bool reffy
, bool intishWarn
>
548 inline tv_lval
ElemDArrayPre(tv_lval base
, StringData
* key
,
550 auto oldArr
= val(base
).parr
;
552 auto const lval
= [&]{
553 auto const cow
= oldArr
->cowCheck();
555 if (oldArr
->convertKey(key
, n
, intishWarn
)) {
556 defined
= (mode
!= MOpMode::Warn
) || oldArr
->exists(n
);
557 return reffy
? oldArr
->lvalRef(n
, cow
) : oldArr
->lval(n
, cow
);
559 defined
= (mode
!= MOpMode::Warn
) || oldArr
->exists(key
);
560 return reffy
? oldArr
->lvalRef(key
, cow
) : oldArr
->lval(key
, cow
);
564 if (lval
.arr
!= oldArr
) {
565 type(base
) = KindOfArray
;
566 val(base
).parr
= lval
.arr
;
567 assertx(cellIsPlausible(*base
));
573 template<MOpMode mode
, bool reffy
, bool intishWarn
>
574 inline tv_lval
ElemDArrayPre(tv_lval base
, TypedValue key
,
576 auto const dt
= key
.m_type
;
578 return ElemDArrayPre
<mode
, reffy
, false>(base
, key
.m_data
.num
, defined
);
580 if (isStringType(dt
)) {
581 return ElemDArrayPre
<mode
, reffy
, intishWarn
>(
582 base
, key
.m_data
.pstr
, defined
585 auto& arr
= asArrRef(base
);
586 defined
= (mode
!= MOpMode::Warn
) || arr
.exists(tvAsCVarRef(&key
));
587 return reffy
? arr
.lvalAtRef(key
) : arr
.lvalAt(key
);
591 * ElemD when base is an Array
593 template<MOpMode mode
, bool reffy
, bool intishWarn
, KeyType keyType
>
594 inline tv_lval
ElemDArray(tv_lval base
, key_type
<keyType
> key
) {
595 assertx(tvIsArray(base
));
596 assertx(tvIsPlausible(*base
));
599 auto lval
= ElemDArrayPre
<mode
, reffy
, intishWarn
>(base
, key
, defined
);
601 assertx(tvIsArray(base
));
602 assertx(tvIsPlausible(*base
));
603 assertx(lval
.type() != KindOfUninit
);
606 auto scratchKey
= initScratchKey(key
);
607 raise_notice(Strings::UNDEFINED_INDEX
,
608 tvAsCVarRef(&scratchKey
).toString().data());
615 * ElemD when base is a Vec
617 template <bool reffy
>
618 inline tv_lval
ElemDVecPre(tv_lval base
, int64_t key
) {
619 ArrayData
* oldArr
= base
.val().parr
;
621 if (reffy
) throwRefInvalidArrayValueException(oldArr
);
623 auto const lval
= PackedArray::LvalIntVec(oldArr
, key
, oldArr
->cowCheck());
624 if (lval
.arr
!= oldArr
) {
625 base
.type() = KindOfVec
;
626 base
.val().parr
= lval
.arr
;
627 assertx(cellIsPlausible(base
.tv()));
633 template <bool reffy
>
634 inline tv_lval
ElemDVecPre(tv_lval base
, StringData
* key
) {
635 throwInvalidArrayKeyException(key
, base
.val().parr
);
638 template <bool reffy
>
639 inline tv_lval
ElemDVecPre(tv_lval base
, TypedValue key
) {
640 auto const dt
= key
.m_type
;
641 if (LIKELY(isIntType(dt
))) return ElemDVecPre
<reffy
>(base
, key
.m_data
.num
);
642 if (isStringType(dt
)) return ElemDVecPre
<reffy
>(base
, key
.m_data
.pstr
);
643 throwInvalidArrayKeyException(&key
, base
.val().parr
);
646 template <bool reffy
, KeyType keyType
>
647 inline tv_lval
ElemDVec(tv_lval base
, key_type
<keyType
> key
) {
648 assertx(tvIsVec(base
));
649 assertx(tvIsPlausible(base
.tv()));
650 auto const result
= ElemDVecPre
<reffy
>(base
, key
);
651 assertx(tvIsVec(base
));
652 assertx(tvIsPlausible(base
.tv()));
654 assertx(result
.type() != KindOfUninit
);
659 * ElemD when base is a Dict
661 template <bool reffy
>
662 inline tv_lval
ElemDDictPre(tv_lval base
, int64_t key
) {
663 ArrayData
* oldArr
= base
.val().parr
;
665 if (reffy
) throwRefInvalidArrayValueException(oldArr
);
668 MixedArray::LvalSilentIntDict(oldArr
, key
, oldArr
->cowCheck());
670 if (UNLIKELY(!lval
)) {
671 assertx(oldArr
== lval
.arr
);
672 throwOOBArrayKeyException(key
, oldArr
);
675 if (lval
.arr
!= oldArr
) {
676 base
.type() = KindOfDict
;
677 base
.val().parr
= lval
.arr
;
678 assertx(cellIsPlausible(base
.tv()));
685 template <bool reffy
>
686 inline tv_lval
ElemDDictPre(tv_lval base
, StringData
* key
) {
687 ArrayData
* oldArr
= base
.val().parr
;
689 if (reffy
) throwRefInvalidArrayValueException(oldArr
);
692 MixedArray::LvalSilentStrDict(oldArr
, key
, oldArr
->cowCheck());
694 if (UNLIKELY(!lval
)) {
695 assertx(oldArr
== lval
.arr
);
696 throwOOBArrayKeyException(key
, oldArr
);
699 if (lval
.arr
!= oldArr
) {
700 base
.type() = KindOfDict
;
701 base
.val().parr
= lval
.arr
;
702 assertx(cellIsPlausible(base
.tv()));
709 template <bool reffy
>
710 inline tv_lval
ElemDDictPre(tv_lval base
, TypedValue key
) {
711 auto const dt
= key
.m_type
;
712 if (isIntType(dt
)) return ElemDDictPre
<reffy
>(base
, key
.m_data
.num
);
713 if (isStringType(dt
)) return ElemDDictPre
<reffy
>(base
, key
.m_data
.pstr
);
714 throwInvalidArrayKeyException(&key
, base
.val().parr
);
717 template <bool reffy
, KeyType keyType
>
718 inline tv_lval
ElemDDict(tv_lval base
, key_type
<keyType
> key
) {
719 assertx(isDictType(base
.type()));
720 assertx(tvIsPlausible(base
.tv()));
721 auto result
= ElemDDictPre
<reffy
>(base
, key
);
722 assertx(isDictType(base
.type()));
723 assertx(tvIsPlausible(base
.tv()));
725 assertx(result
.type() != KindOfUninit
);
730 * ElemD when base is a Keyset
732 template <bool reffy
>
734 inline tv_lval
ElemDKeysetPre(tv_lval base
, int64_t /*key*/) {
735 if (reffy
) throwRefInvalidArrayValueException(base
.val().parr
);
736 throwInvalidKeysetOperation();
739 template <bool reffy
>
741 inline tv_lval
ElemDKeysetPre(tv_lval base
, StringData
* /*key*/) {
742 if (reffy
) throwRefInvalidArrayValueException(base
.val().parr
);
743 throwInvalidKeysetOperation();
746 template <bool reffy
>
748 inline tv_lval
ElemDKeysetPre(tv_lval base
, TypedValue key
) {
749 auto const dt
= key
.m_type
;
750 if (isIntType(dt
)) ElemDKeysetPre
<reffy
>(base
, key
.m_data
.num
);
751 if (isStringType(dt
)) ElemDKeysetPre
<reffy
>(base
, key
.m_data
.pstr
);
752 throwInvalidArrayKeyException(&key
, base
.val().parr
);
755 template <bool reffy
, KeyType keyType
>
757 inline tv_lval
ElemDKeyset(tv_lval base
, key_type
<keyType
> key
) {
758 assertx(tvIsKeyset(base
));
759 assertx(tvIsPlausible(base
.tv()));
760 ElemDKeysetPre
<reffy
>(base
, key
);
764 * ElemD when base is Null
766 template<MOpMode mode
, KeyType keyType
>
767 inline tv_lval
ElemDEmptyish(tv_lval base
, key_type
<keyType
> key
) {
768 detail::checkPromotion(base
);
769 auto scratchKey
= initScratchKey(key
);
771 tvMove(make_tv
<KindOfArray
>(ArrayData::Create()), base
);
773 auto const result
= asArrRef(base
).lvalAt(cellAsCVarRef(scratchKey
));
774 if (mode
== MOpMode::Warn
) {
775 raise_notice(Strings::UNDEFINED_INDEX
,
776 tvAsCVarRef(&scratchKey
).toString().data());
782 * ElemD when base is an Int64, Double, Resource, or Func.
784 inline tv_lval
ElemDScalar(TypedValue
& tvRef
) {
785 raise_warning(Strings::CANNOT_USE_SCALAR_AS_ARRAY
);
787 return tv_lval(&tvRef
);
791 * ElemD when base is a Boolean
793 template<MOpMode mode
, KeyType keyType
>
794 inline tv_lval
ElemDBoolean(TypedValue
& tvRef
,
796 key_type
<keyType
> key
) {
797 return base
.val().num
799 : ElemDEmptyish
<mode
, keyType
>(base
, key
);
803 * ElemD when base is a String
805 template<MOpMode mode
, KeyType keyType
>
806 inline tv_lval
ElemDString(tv_lval base
, key_type
<keyType
> key
) {
807 if (base
.val().pstr
->size() == 0) {
808 return ElemDEmptyish
<mode
, keyType
>(base
, key
);
810 raise_error("Operator not supported for strings");
811 return tv_lval(nullptr);
815 * ElemD when base is an Object
817 template<MOpMode mode
, bool reffy
, KeyType keyType
>
818 inline tv_lval
ElemDObject(TypedValue
& tvRef
, tv_lval base
,
819 key_type
<keyType
> key
) {
820 auto scratchKey
= initScratchKey(key
);
821 auto obj
= base
.val().pobj
;
823 if (LIKELY(obj
->isCollection())) {
825 raise_error("Collection elements cannot be taken by reference");
826 return tv_lval(nullptr);
828 return collections::atLval(obj
, &scratchKey
);
829 } else if (obj
->getVMClass()->classof(SystemLib::s_ArrayObjectClass
)) {
830 auto storage
= obj
->getPropLval(SystemLib::s_ArrayObjectClass
,
832 // ArrayObject should always have the 'storage' property...
834 return UNLIKELY(checkHACIntishCast())
835 ? ElemDArray
<mode
, reffy
, true, keyType
>(storage
, key
)
836 : ElemDArray
<mode
, reffy
, false, keyType
>(storage
, key
);
840 tvRef
= objOffsetGet(instanceFromTv(base
), scratchKey
);
841 return tv_lval(&tvRef
);
845 * Intermediate elem operation for defining member instructions.
847 * Returned pointer is not yet unboxed. (I.e. it cannot point into a RefData.)
849 template<MOpMode mode
, bool reffy
, bool intishWarn
,
850 KeyType keyType
= KeyType::Any
>
851 tv_lval
ElemD(TypedValue
& tvRef
, tv_lval base
, key_type
<keyType
> key
) {
852 assertx(mode
== MOpMode::Define
);
854 base
= base
.unboxed();
855 assertx(cellIsPlausible(base
.tv()));
857 switch (base
.type()) {
860 return ElemDEmptyish
<mode
, keyType
>(base
, key
);
862 return ElemDBoolean
<mode
, keyType
>(tvRef
, base
, key
);
867 return ElemDScalar(tvRef
);
868 case KindOfPersistentString
:
870 return ElemDString
<mode
, keyType
>(base
, key
);
871 case KindOfPersistentVec
:
873 return ElemDVec
<reffy
, keyType
>(base
, key
);
874 case KindOfPersistentDict
:
876 return ElemDDict
<reffy
, keyType
>(base
, key
);
877 case KindOfPersistentKeyset
:
879 return ElemDKeyset
<reffy
, keyType
>(base
, key
);
880 case KindOfPersistentArray
:
882 return ElemDArray
<mode
, reffy
, intishWarn
, keyType
>(base
, key
);
884 return ElemDObject
<mode
, reffy
, keyType
>(tvRef
, base
, key
);
888 unknownBaseType(type(base
));
892 * Implementation for SetWithRef*ML bytecodes.
894 template<MOpMode mode
, bool reffy
, bool intishWarn
,
895 KeyType keyType
= KeyType::Any
>
896 void SetWithRefMLElem(TypedValue
& tvRef
, tv_lval base
,
897 key_type
<keyType
> key
, TypedValue val
) {
898 assertx(mode
== MOpMode::Define
);
900 base
= tvToCell(base
);
901 assertx(cellIsPlausible(*base
));
903 auto const elem
= [&] {
904 switch (type(base
)) {
907 return ElemDEmptyish
<mode
, keyType
>(base
, key
);
909 return ElemDBoolean
<mode
, keyType
>(tvRef
, base
, key
);
914 return ElemDScalar(tvRef
);
915 case KindOfPersistentString
:
917 return ElemDString
<mode
, keyType
>(base
, key
);
918 case KindOfPersistentVec
:
920 return ElemDVec
<reffy
, keyType
>(base
, key
);
921 case KindOfPersistentDict
:
923 return ElemDDict
<reffy
, keyType
>(base
, key
);
924 case KindOfPersistentKeyset
:
926 return ElemDKeyset
<reffy
, keyType
>(base
, key
);
927 case KindOfPersistentArray
:
929 // We want to notice for binding assignments here, but not for missing
930 // index, since we're about to initialize the value in that case.
931 // Rather than fork the Lval API to have warn and no-warn flavors, we
932 // instead lift the binding assignment warning here, and then disable
933 // Hack array notices.
934 if (reffy
&& checkHACRefBind() &&
935 !HPHP::val(base
).parr
->isGlobalsArray()) {
936 raiseHackArrCompatRefBind(key
);
938 SuppressHackArrCompatNotices shacn
;
939 return ElemDArray
<mode
, reffy
, intishWarn
, keyType
>(base
, key
);
942 return ElemDObject
<mode
, reffy
, keyType
>(tvRef
, base
, key
);
946 unknownBaseType(type(base
));
949 // Intentionally leak the old value pointed to by elem, including from magic
955 * ElemU when base is Null
957 inline tv_lval
ElemUEmptyish() {
958 return const_cast<TypedValue
*>(&immutable_null_base
);
961 template <bool intishWarn
>
962 inline tv_lval
ElemUArrayImpl(tv_lval base
, int64_t key
) {
963 auto oldArr
= val(base
).parr
;
964 if (!oldArr
->exists(key
)) return ElemUEmptyish();
965 auto const lval
= oldArr
->lval(key
, oldArr
->cowCheck());
966 if (lval
.arr
!= oldArr
) {
967 type(base
) = KindOfArray
;
968 val(base
).parr
= lval
.arr
;
969 assertx(cellIsPlausible(*base
));
975 template <bool intishWarn
>
976 inline tv_lval
ElemUArrayImpl(tv_lval base
, StringData
* key
) {
977 auto oldArr
= val(base
).parr
;
979 if (oldArr
->convertKey(key
, n
, intishWarn
)) {
980 if (!oldArr
->exists(n
)) return ElemUEmptyish();
981 auto const lval
= oldArr
->lval(n
, oldArr
->cowCheck());
982 if (lval
.arr
!= oldArr
) {
983 type(base
) = KindOfArray
;
984 val(base
).parr
= lval
.arr
;
985 assertx(cellIsPlausible(*base
));
990 if (!oldArr
->exists(key
)) return ElemUEmptyish();
991 auto const lval
= oldArr
->lval(key
, oldArr
->cowCheck());
992 if (lval
.arr
!= oldArr
) {
993 type(base
) = KindOfArray
;
994 val(base
).parr
= lval
.arr
;
995 assertx(cellIsPlausible(*base
));
1002 template <bool intishWarn
>
1003 inline tv_lval
ElemUArrayImpl(tv_lval base
, TypedValue key
) {
1004 auto const dt
= key
.m_type
;
1005 if (isIntType(dt
)) {
1006 return ElemUArrayImpl
<false>(base
, key
.m_data
.num
);
1008 if (isStringType(dt
)) {
1009 return ElemUArrayImpl
<intishWarn
>(base
, key
.m_data
.pstr
);
1011 auto& arr
= asArrRef(base
);
1012 if (!arr
.exists(keyAsValue(key
))) {
1013 return ElemUEmptyish();
1015 return arr
.lvalAt(tvAsCVarRef(&key
));
1019 * ElemU when base is an Array
1021 template <bool intishWarn
, KeyType keyType
>
1022 inline tv_lval
ElemUArray(tv_lval base
, key_type
<keyType
> key
) {
1023 assertx(tvIsArray(base
));
1024 assertx(tvIsPlausible(*base
));
1025 auto lval
= ElemUArrayImpl
<intishWarn
>(base
, key
);
1026 assertx(tvIsArray(base
));
1027 assertx(tvIsPlausible(*base
));
1028 assertx(lval
.type() != KindOfUninit
);
1033 * ElemU when base is a Vec
1035 inline tv_lval
ElemUVecPre(tv_lval base
, int64_t key
) {
1036 ArrayData
* oldArr
= val(base
).parr
;
1038 PackedArray::LvalSilentIntVec(oldArr
, key
, oldArr
->cowCheck());
1040 if (UNLIKELY(!lval
)) {
1041 return ElemUEmptyish();
1043 if (lval
.arr
!= oldArr
) {
1044 type(base
) = KindOfVec
;
1045 val(base
).parr
= lval
.arr
;
1046 assertx(cellIsPlausible(*base
));
1052 inline tv_lval
ElemUVecPre(tv_lval
/*base*/, StringData
* /*key*/) {
1053 return ElemUEmptyish();
1056 inline tv_lval
ElemUVecPre(tv_lval base
, TypedValue key
) {
1057 auto const dt
= key
.m_type
;
1058 if (LIKELY(isIntType(dt
))) return ElemUVecPre(base
, key
.m_data
.num
);
1059 if (isStringType(dt
)) return ElemUVecPre(base
, key
.m_data
.pstr
);
1060 throwInvalidArrayKeyException(&key
, val(base
).parr
);
1063 template <KeyType keyType
>
1064 inline tv_lval
ElemUVec(tv_lval base
, key_type
<keyType
> key
) {
1065 assertx(tvIsVec(base
));
1066 assertx(tvIsPlausible(*base
));
1067 auto result
= ElemUVecPre(base
, key
);
1068 assertx(tvIsVec(base
));
1069 assertx(tvIsPlausible(*base
));
1070 assertx(type(result
) != KindOfUninit
);
1075 * ElemU when base is a Dict
1077 inline tv_lval
ElemUDictPre(tv_lval base
, int64_t key
) {
1078 ArrayData
* oldArr
= val(base
).parr
;
1080 MixedArray::LvalSilentIntDict(oldArr
, key
, oldArr
->cowCheck());
1082 if (UNLIKELY(!lval
)) {
1083 return ElemUEmptyish();
1085 if (lval
.arr
!= oldArr
) {
1086 type(base
) = KindOfDict
;
1087 val(base
).parr
= lval
.arr
;
1088 assertx(cellIsPlausible(*base
));
1094 inline tv_lval
ElemUDictPre(tv_lval base
, StringData
* key
) {
1095 ArrayData
* oldArr
= val(base
).parr
;
1097 MixedArray::LvalSilentStrDict(oldArr
, key
, oldArr
->cowCheck());
1099 if (UNLIKELY(!lval
)) {
1100 return ElemUEmptyish();
1102 if (lval
.arr
!= oldArr
) {
1103 type(base
) = KindOfDict
;
1104 val(base
).parr
= lval
.arr
;
1105 assertx(cellIsPlausible(*base
));
1111 inline tv_lval
ElemUDictPre(tv_lval base
, TypedValue key
) {
1112 auto const dt
= key
.m_type
;
1113 if (isIntType(dt
)) return ElemUDictPre(base
, key
.m_data
.num
);
1114 if (isStringType(dt
)) return ElemUDictPre(base
, key
.m_data
.pstr
);
1115 throwInvalidArrayKeyException(&key
, val(base
).parr
);
1118 template <KeyType keyType
>
1119 inline tv_lval
ElemUDict(tv_lval base
, key_type
<keyType
> key
) {
1120 assertx(tvIsDict(base
));
1121 assertx(tvIsPlausible(*base
));
1122 auto result
= ElemUDictPre(base
, key
);
1123 assertx(tvIsDict(base
));
1124 assertx(tvIsPlausible(*base
));
1125 assertx(type(result
) != KindOfUninit
);
1130 * ElemU when base is a Keyset
1132 [[noreturn
]] inline tv_lval
1133 ElemUKeysetPre(tv_lval
/*base*/, int64_t /*key*/) {
1134 throwInvalidKeysetOperation();
1137 [[noreturn
]] inline tv_lval
1138 ElemUKeysetPre(tv_lval
/*base*/, StringData
* /*key*/) {
1139 throwInvalidKeysetOperation();
1143 inline tv_lval
ElemUKeysetPre(tv_lval base
, TypedValue key
) {
1144 auto const dt
= key
.m_type
;
1145 if (isIntType(dt
)) ElemUKeysetPre(base
, key
.m_data
.num
);
1146 if (isStringType(dt
)) ElemUKeysetPre(base
, key
.m_data
.pstr
);
1147 throwInvalidArrayKeyException(&key
, val(base
).parr
);
1150 template <KeyType keyType
>
1152 inline tv_lval
ElemUKeyset(tv_lval base
, key_type
<keyType
> key
) {
1153 assertx(tvIsKeyset(base
));
1154 assertx(tvIsPlausible(*base
));
1155 ElemUKeysetPre(base
, key
);
1159 * ElemU when base is an Object
1161 template <KeyType keyType
>
1162 inline tv_lval
ElemUObject(TypedValue
& tvRef
, tv_lval base
,
1163 key_type
<keyType
> key
) {
1164 auto const scratchKey
= initScratchKey(key
);
1165 if (LIKELY(val(base
).pobj
->isCollection())) {
1166 return collections::atLval(val(base
).pobj
, &scratchKey
);
1168 tvRef
= objOffsetGet(instanceFromTv(base
), scratchKey
);
1173 * Intermediate Elem operation for an unsetting member instruction.
1175 * Returned pointer is not yet unboxed. (I.e. it cannot point into a RefData.)
1177 template <bool intishWarn
, KeyType keyType
= KeyType::Any
>
1178 tv_lval
ElemU(TypedValue
& tvRef
, tv_lval base
, key_type
<keyType
> key
) {
1179 base
= tvToCell(base
);
1180 assertx(cellIsPlausible(*base
));
1182 switch (type(base
)) {
1188 case KindOfResource
:
1190 // Unset on scalar base never modifies the base, but the const_cast is
1191 // necessary to placate the type system.
1192 return const_cast<TypedValue
*>(&immutable_uninit_base
);
1193 case KindOfPersistentString
:
1195 raise_error(Strings::OP_NOT_SUPPORTED_STRING
);
1197 case KindOfPersistentVec
:
1199 return ElemUVec
<keyType
>(base
, key
);
1200 case KindOfPersistentDict
:
1202 return ElemUDict
<keyType
>(base
, key
);
1203 case KindOfPersistentKeyset
:
1205 return ElemUKeyset
<keyType
>(base
, key
);
1206 case KindOfPersistentArray
:
1208 return ElemUArray
<intishWarn
, keyType
>(base
, key
);
1210 return ElemUObject
<keyType
>(tvRef
, base
, key
);
1214 unknownBaseType(type(base
));
1218 * NewElem when base is Null
1220 inline tv_lval
NewElemEmptyish(tv_lval base
) {
1221 detail::checkPromotion(base
);
1222 tvMove(make_tv
<KindOfArray
>(ArrayData::Create()), base
);
1223 return asArrRef(base
).lvalAt();
1227 * NewElem when base is not a valid type (a number, true boolean,
1228 * non-empty string, etc.)
1230 inline tv_lval
NewElemInvalid(TypedValue
& tvRef
) {
1231 raise_warning("Cannot use a scalar value as an array");
1233 return tv_lval(&tvRef
);
1237 * NewElem when base is a Boolean
1239 inline tv_lval
NewElemBoolean(TypedValue
& tvRef
, tv_lval base
) {
1240 return val(base
).num
1241 ? NewElemInvalid(tvRef
)
1242 : NewElemEmptyish(base
);
1246 * NewElem when base is a String
1248 inline tv_lval
NewElemString(TypedValue
& tvRef
, tv_lval base
) {
1249 if (val(base
).pstr
->size() == 0) {
1250 return NewElemEmptyish(base
);
1252 return NewElemInvalid(tvRef
);
1256 * NewElem when base is an Array
1258 template <bool reffy
>
1259 inline tv_lval
NewElemArray(tv_lval base
) {
1260 assertx(tvIsArray(base
));
1261 assertx(tvIsPlausible(*base
));
1262 return reffy
? asArrRef(base
).lvalAtRef() : asArrRef(base
).lvalAt();
1266 * NewElem when base is an Object
1268 inline tv_lval
NewElemObject(TypedValue
& tvRef
, tv_lval base
) {
1269 if (val(base
).pobj
->isCollection()) {
1270 throw_cannot_use_newelem_for_lval_read_col();
1273 tvRef
= objOffsetGet(instanceFromTv(base
), make_tv
<KindOfNull
>());
1278 * $result = ($base[] = ...);
1280 template <bool reffy
>
1281 inline tv_lval
NewElem(TypedValue
& tvRef
, tv_lval base
) {
1282 base
= base
.unboxed();
1283 assertx(cellIsPlausible(base
.tv()));
1285 switch (base
.type()) {
1288 return NewElemEmptyish(base
);
1290 return NewElemBoolean(tvRef
, base
);
1293 case KindOfResource
:
1295 return NewElemInvalid(tvRef
);
1296 case KindOfPersistentString
:
1298 return NewElemString(tvRef
, base
);
1299 case KindOfPersistentVec
:
1301 throw_cannot_use_newelem_for_lval_read_vec();
1302 case KindOfPersistentDict
:
1304 throw_cannot_use_newelem_for_lval_read_dict();
1305 case KindOfPersistentKeyset
:
1307 throw_cannot_use_newelem_for_lval_read_keyset();
1308 case KindOfPersistentArray
:
1310 return NewElemArray
<reffy
>(base
);
1312 return NewElemObject(tvRef
, base
);
1316 unknownBaseType(type(base
));
1320 * SetElem when base is Null
1322 template <KeyType keyType
>
1323 inline void SetElemEmptyish(tv_lval base
, key_type
<keyType
> key
,
1325 detail::checkPromotion(base
);
1326 auto const& scratchKey
= initScratchKey(key
);
1327 cellMove(make_tv
<KindOfArray
>(staticEmptyArray()), base
);
1328 asArrRef(base
).set(tvAsCVarRef(&scratchKey
), tvAsCVarRef(value
));
1332 * SetElem when base is an Int64, Double, Resource, or Func.
1334 template <bool setResult
>
1335 inline void SetElemScalar(Cell
* value
) {
1336 raise_warning(Strings::CANNOT_USE_SCALAR_AS_ARRAY
);
1338 throw InvalidSetMException(make_tv
<KindOfNull
>());
1341 tvWriteNull(*value
);
1345 * SetElem when base is a Boolean
1347 template <bool setResult
, KeyType keyType
>
1348 inline void SetElemBoolean(tv_lval base
, key_type
<keyType
> key
,
1350 if (val(base
).num
) {
1351 SetElemScalar
<setResult
>(value
);
1353 SetElemEmptyish
<keyType
>(base
, key
, value
);
1358 * Convert a key to integer for SetElem
1360 template<KeyType keyType
>
1361 inline int64_t castKeyToInt(key_type
<keyType
> key
) {
1362 return cellToInt(initScratchKey(key
));
1366 inline int64_t castKeyToInt
<KeyType::Int
>(int64_t key
) {
1371 * SetElem when base is a String
1373 template <bool setResult
, KeyType keyType
>
1374 inline StringData
* SetElemString(tv_lval base
, key_type
<keyType
> key
,
1376 int baseLen
= val(base
).pstr
->size();
1378 SetElemEmptyish
<keyType
>(base
, key
, value
);
1380 tvIncRefGen(*value
);
1381 throw InvalidSetMException(*value
);
1386 // Convert key to string offset.
1387 int64_t x
= castKeyToInt
<keyType
>(key
);
1388 if (UNLIKELY(x
< 0 || x
>= StringData::MaxSize
)) {
1389 // Can't use PRId64 here because of order of inclusion issues
1390 raise_warning("Illegal string offset: %lld", (long long)x
);
1392 throw InvalidSetMException(make_tv
<KindOfNull
>());
1395 tvWriteNull(*value
);
1399 // Compute how long the resulting string will be. Type needs
1408 // Extract the first character of (string)value.
1412 if (LIKELY(isStringType(value
->m_type
))) {
1413 valStr
= value
->m_data
.pstr
;
1414 valStr
->incRefCount();
1416 valStr
= tvCastToStringData(*value
);
1419 if (valStr
->size() > 0) {
1420 y
[0] = valStr
->data()[0];
1428 // Create and save the result.
1429 if (x
>= 0 && x
< baseLen
&& !val(base
).pstr
->cowCheck()) {
1430 // Modify base in place. This is safe because the LHS owns the
1432 auto const oldp
= val(base
).pstr
;
1433 auto const newp
= oldp
->modifyChar(x
, y
[0]);
1434 if (UNLIKELY(newp
!= oldp
)) {
1436 val(base
).pstr
= newp
;
1437 type(base
) = KindOfString
;
1440 StringData
* sd
= StringData::Make(slen
);
1441 char* s
= sd
->mutableData();
1442 memcpy(s
, val(base
).pstr
->data(), baseLen
);
1444 memset(&s
[baseLen
], ' ', slen
- baseLen
- 1);
1448 decRefStr(val(base
).pstr
);
1449 val(base
).pstr
= sd
;
1450 type(base
) = KindOfString
;
1453 return StringData::Make(y
, strlen(y
), CopyString
);
1457 * SetElem when base is an Object
1459 template <KeyType keyType
>
1460 inline void SetElemObject(tv_lval base
, key_type
<keyType
> key
,
1462 auto const scratchKey
= initScratchKey(key
);
1463 if (LIKELY(val(base
).pobj
->isCollection())) {
1464 collections::set(val(base
).pobj
, &scratchKey
, value
);
1466 objOffsetSet(instanceFromTv(base
), scratchKey
, value
);
1471 * arrayRefShuffle is used by SetElemArray and by helpers for translated code
1472 * to do the necessary bookkeeping after mutating an array. The helpers return
1473 * an ArrayData* if and only if the base array was not in a php reference. If
1474 * the base array was in a reference, that reference may no longer refer to an
1475 * array after the set operation, so the helpers don't return anything.
1477 template<bool setRef
> struct ShuffleReturn
{};
1479 template<> struct ShuffleReturn
<true> {
1480 static void do_return(ArrayData
* /*a*/) {}
1483 template<> struct ShuffleReturn
<false> {
1484 static ArrayData
* do_return(ArrayData
* a
) { return a
; }
1487 template<bool setRef
, DataType dt
> inline
1488 auto arrayRefShuffle(ArrayData
* oldData
, ArrayData
* newData
, tv_lval base
) {
1489 if (newData
== oldData
) {
1490 return ShuffleReturn
<setRef
>::do_return(oldData
);
1494 if (isArrayLikeType(type(base
)) && val(base
).parr
== oldData
) {
1496 val(base
).parr
= newData
;
1497 assertx(cellIsPlausible(*base
));
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.
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
,
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
,
1531 assertx(a
->isPHPArray());
1532 if (key
->isStrictlyInteger(n
)) {
1533 if (intishWarn
) raise_intish_index_cast();
1534 return a
->set(n
, *value
, copy
);
1536 return a
->set(key
, *value
, copy
);
1540 template<bool setResult
, bool intishWarn
>
1541 inline ArrayData
* SetElemArrayPre(ArrayData
* a
,
1545 if (isStringType(key
.m_type
)) {
1546 return SetElemArrayPre
<setResult
, intishWarn
>(
1547 a
, key
.m_data
.pstr
, value
, copy
1550 if (key
.m_type
== KindOfInt64
) {
1551 return SetElemArrayPre
<setResult
, false>(
1552 a
, key
.m_data
.num
, value
, copy
1555 if (checkHACMisc()) {
1556 raiseHackArrCompatImplicitArrayKey(&key
);
1558 if (isNullType(key
.m_type
)) {
1559 return a
->set(staticEmptyString(), *value
, copy
);
1561 if (!isArrayLikeType(key
.m_type
) && key
.m_type
!= KindOfObject
) {
1562 return SetElemArrayPre
<setResult
, false>(a
, tvAsCVarRef(&key
).toInt64(),
1566 raise_warning("Illegal offset type");
1567 // Assignment failed, so the result is null rather than the RHS.
1570 tvWriteNull(*value
);
1572 throw InvalidSetMException(make_tv
<KindOfNull
>());
1578 * SetElem when base is an Array
1580 template <bool setResult
, KeyType keyType
, bool intishWarn
>
1581 inline void SetElemArray(tv_lval base
, key_type
<keyType
> key
,
1583 assertx(tvIsArray(base
));
1584 assertx(tvIsPlausible(*base
));
1586 ArrayData
* a
= val(base
).parr
;
1587 bool copy
= a
->cowCheck() ||
1588 (tvIsArray(value
) && value
->m_data
.parr
== a
);
1590 auto* newData
= SetElemArrayPre
<setResult
, intishWarn
>(a
, key
, value
, copy
);
1591 assertx(newData
->isPHPArray());
1593 arrayRefShuffle
<true, KindOfArray
>(a
, newData
, base
);
1597 * SetElem when base is a Vec
1599 template<bool setResult
>
1600 inline ArrayData
* SetElemVecPre(ArrayData
* a
,
1604 return PackedArray::SetIntVec(a
, key
, *value
, copy
);
1607 template <bool setResult
>
1609 SetElemVecPre(ArrayData
* a
, StringData
* key
, Cell
* /*value*/, bool /*copy*/) {
1610 throwInvalidArrayKeyException(key
, a
);
1613 template<bool setResult
>
1614 inline ArrayData
* SetElemVecPre(ArrayData
* a
,
1618 auto const dt
= key
.m_type
;
1619 if (LIKELY(isIntType(dt
))) return SetElemVecPre
<setResult
>(a
, key
.m_data
.num
,
1621 if (isStringType(dt
)) return SetElemVecPre
<setResult
>(a
, key
.m_data
.pstr
,
1623 throwInvalidArrayKeyException(&key
, a
);
1626 template <bool setResult
, KeyType keyType
>
1627 inline void SetElemVec(tv_lval base
, key_type
<keyType
> key
,
1629 assertx(tvIsVec(base
));
1630 assertx(tvIsPlausible(*base
));
1632 ArrayData
* a
= val(base
).parr
;
1633 bool copy
= a
->cowCheck() || (tvIsVec(value
) && value
->m_data
.parr
== a
);
1635 auto* newData
= SetElemVecPre
<setResult
>(a
, key
, value
, copy
);
1636 assertx(newData
->isVecArray());
1638 arrayRefShuffle
<true, KindOfVec
>(a
, newData
, base
);
1642 * SetElem when base is a Dict
1644 template<bool setResult
>
1645 inline ArrayData
* SetElemDictPre(ArrayData
* a
,
1649 return MixedArray::SetIntDict(a
, key
, *value
, copy
);
1652 template<bool setResult
>
1653 inline ArrayData
* SetElemDictPre(ArrayData
* a
,
1657 return MixedArray::SetStrDict(a
, key
, *value
, copy
);
1660 template<bool setResult
>
1661 inline ArrayData
* SetElemDictPre(ArrayData
* a
,
1665 auto const dt
= key
.m_type
;
1666 if (isIntType(dt
)) return SetElemDictPre
<setResult
>(a
, key
.m_data
.num
,
1668 if (isStringType(dt
)) return SetElemDictPre
<setResult
>(a
, key
.m_data
.pstr
,
1670 throwInvalidArrayKeyException(&key
, a
);
1673 template <bool setResult
, KeyType keyType
>
1674 inline void SetElemDict(tv_lval base
, key_type
<keyType
> key
,
1676 assertx(tvIsDict(base
));
1677 assertx(tvIsPlausible(*base
));
1679 ArrayData
* a
= val(base
).parr
;
1680 bool copy
= a
->cowCheck() ||
1681 (tvIsDict(value
) && value
->m_data
.parr
== a
);
1683 auto newData
= SetElemDictPre
<setResult
>(a
, key
, value
, copy
);
1684 assertx(newData
->isDict());
1686 arrayRefShuffle
<true, KindOfDict
>(a
, newData
, base
);
1690 * SetElem() leaves the result in 'value', rather than returning it as in
1691 * SetOpElem(), because doing so avoids a dup operation that SetOpElem() can't
1694 template <bool setResult
, KeyType keyType
, bool intishWarn
>
1696 StringData
* SetElemSlow(tv_lval base
, key_type
<keyType
> key
, Cell
* value
) {
1697 base
= tvToCell(base
);
1698 assertx(cellIsPlausible(*base
));
1700 switch (type(base
)) {
1703 SetElemEmptyish
<keyType
>(base
, key
, value
);
1706 SetElemBoolean
<setResult
, keyType
>(base
, key
, value
);
1710 case KindOfResource
:
1712 SetElemScalar
<setResult
>(value
);
1714 case KindOfPersistentString
:
1716 return SetElemString
<setResult
, keyType
>(base
, key
, value
);
1717 case KindOfPersistentVec
:
1719 SetElemVec
<setResult
, keyType
>(base
, key
, value
);
1721 case KindOfPersistentDict
:
1723 SetElemDict
<setResult
, keyType
>(base
, key
, value
);
1725 case KindOfPersistentKeyset
:
1727 throwInvalidKeysetOperation();
1728 case KindOfPersistentArray
:
1730 SetElemArray
<setResult
, keyType
, intishWarn
>(base
, key
, value
);
1733 SetElemObject
<keyType
>(base
, key
, value
);
1738 unknownBaseType(type(base
));
1742 * Fast path for SetElem assuming base is an Array
1744 template <bool setResult
, bool intishWarn
, KeyType keyType
= KeyType::Any
>
1745 inline StringData
* SetElem(tv_lval base
, key_type
<keyType
> key
,
1747 assertx(tvIsPlausible(*base
));
1749 if (LIKELY(tvIsArray(base
))) {
1750 SetElemArray
<setResult
, keyType
, intishWarn
>(base
, key
, value
);
1753 if (LIKELY(tvIsVec(base
))) {
1754 SetElemVec
<setResult
, keyType
>(base
, key
, value
);
1757 if (LIKELY(tvIsDict(base
))) {
1758 SetElemDict
<setResult
, keyType
>(base
, key
, value
);
1761 return SetElemSlow
<setResult
, keyType
, intishWarn
>(base
, key
, value
);
1765 * SetNewElem when base is Null
1767 inline void SetNewElemEmptyish(tv_lval base
, Cell
* value
) {
1768 detail::checkPromotion(base
);
1769 Array a
= Array::Create();
1770 a
.append(cellAsCVarRef(*value
));
1771 cellMove(make_tv
<KindOfArray
>(a
.detach()), base
);
1775 * SetNewElem when base is Int64, Double, Resource or Func
1777 template <bool setResult
>
1778 inline void SetNewElemScalar(Cell
* value
) {
1779 raise_warning(Strings::CANNOT_USE_SCALAR_AS_ARRAY
);
1781 throw InvalidSetMException(make_tv
<KindOfNull
>());
1784 tvWriteNull(*value
);
1788 * SetNewElem when base is a Boolean
1790 template <bool setResult
>
1791 inline void SetNewElemBoolean(tv_lval base
, Cell
* value
) {
1792 if (val(base
).num
) {
1793 SetNewElemScalar
<setResult
>(value
);
1795 SetNewElemEmptyish(base
, value
);
1800 * SetNewElem when base is a String
1802 inline void SetNewElemString(tv_lval base
, Cell
* value
) {
1803 int baseLen
= val(base
).pstr
->size();
1805 SetNewElemEmptyish(base
, value
);
1807 raise_error("[] operator not supported for strings");
1812 * SetNewElem when base is an Array
1814 inline void SetNewElemArray(tv_lval base
, Cell
* value
) {
1815 base
= tvToCell(base
);
1816 assertx(tvIsArray(base
));
1817 assertx(tvIsPlausible(*base
));
1818 auto a
= val(base
).parr
;
1819 auto const copy
= a
->cowCheck() ||
1820 (tvIsArray(value
) && value
->m_data
.parr
== a
);
1821 auto a2
= a
->append(*value
, copy
);
1823 type(base
) = KindOfArray
;
1824 val(base
).parr
= a2
;
1825 a
->decRefAndRelease();
1830 * SetNewElem when base is a Vec
1832 inline void SetNewElemVec(tv_lval base
, Cell
* value
) {
1833 base
= tvToCell(base
);
1834 assertx(tvIsVec(base
));
1835 assertx(tvIsPlausible(*base
));
1836 auto a
= val(base
).parr
;
1837 auto const copy
= a
->cowCheck() ||
1838 (tvIsVec(value
) && value
->m_data
.parr
== a
);
1839 auto a2
= PackedArray::AppendVec(a
, *value
, copy
);
1841 type(base
) = KindOfVec
;
1842 val(base
).parr
= a2
;
1843 assertx(cellIsPlausible(*base
));
1844 a
->decRefAndRelease();
1849 * SetNewElem when base is a Dict
1851 inline void SetNewElemDict(tv_lval base
, Cell
* value
) {
1852 base
= tvToCell(base
);
1853 assertx(tvIsDict(base
));
1854 assertx(tvIsPlausible(*base
));
1855 auto a
= val(base
).parr
;
1856 auto const copy
= a
->cowCheck() ||
1857 (tvIsDict(value
) && value
->m_data
.parr
== a
);
1858 auto a2
= MixedArray::AppendDict(a
, *value
, copy
);
1860 type(base
) = KindOfDict
;
1861 val(base
).parr
= a2
;
1862 assertx(cellIsPlausible(*base
));
1863 a
->decRefAndRelease();
1868 * SetNewElem when base is a Keyset
1870 inline void SetNewElemKeyset(tv_lval base
, Cell
* value
) {
1871 base
= tvToCell(base
);
1872 assertx(tvIsKeyset(base
));
1873 assertx(tvIsPlausible(*base
));
1874 auto a
= val(base
).parr
;
1875 auto const copy
= a
->cowCheck() ||
1876 (tvIsKeyset(value
) && value
->m_data
.parr
== a
);
1877 auto a2
= SetArray::Append(a
, *value
, copy
);
1879 type(base
) = KindOfKeyset
;
1880 val(base
).parr
= a2
;
1881 assertx(cellIsPlausible(*base
));
1882 a
->decRefAndRelease();
1887 * SetNewElem when base is an Object
1889 inline void SetNewElemObject(tv_lval base
, Cell
* value
) {
1890 if (LIKELY(val(base
).pobj
->isCollection())) {
1891 collections::append(val(base
).pobj
, value
);
1893 objOffsetAppend(instanceFromTv(base
), value
);
1900 template <bool setResult
>
1901 inline void SetNewElem(tv_lval base
, Cell
* value
) {
1902 base
= tvToCell(base
);
1903 assertx(cellIsPlausible(*base
));
1905 switch (type(base
)) {
1908 return SetNewElemEmptyish(base
, value
);
1910 return SetNewElemBoolean
<setResult
>(base
, value
);
1913 case KindOfResource
:
1915 return SetNewElemScalar
<setResult
>(value
);
1916 case KindOfPersistentString
:
1918 return SetNewElemString(base
, value
);
1919 case KindOfPersistentVec
:
1921 return SetNewElemVec(base
, value
);
1922 case KindOfPersistentDict
:
1924 return SetNewElemDict(base
, value
);
1925 case KindOfPersistentKeyset
:
1927 return SetNewElemKeyset(base
, value
);
1928 case KindOfPersistentArray
:
1930 return SetNewElemArray(base
, value
);
1932 return SetNewElemObject(base
, value
);
1936 unknownBaseType(type(base
));
1940 * SetOpElem when base is Null
1942 inline tv_lval
SetOpElemEmptyish(SetOpOp op
, tv_lval base
,
1943 TypedValue key
, Cell
* rhs
) {
1944 assertx(cellIsPlausible(*base
));
1946 detail::checkPromotion(base
);
1948 cellMove(make_tv
<KindOfArray
>(staticEmptyArray()), base
);
1949 auto const lval
= asArrRef(base
).lvalAt(tvAsCVarRef(&key
));
1951 raise_notice(Strings::UNDEFINED_INDEX
,
1952 tvAsCVarRef(&key
).toString().data());
1954 setopBody(lval
, op
, rhs
);
1959 * SetOpElem when base is Int64, Double, Resource or Func
1961 inline tv_lval
SetOpElemScalar(TypedValue
& tvRef
) {
1962 raise_warning(Strings::CANNOT_USE_SCALAR_AS_ARRAY
);
1968 * $result = ($base[$x] <op>= $y)
1970 template <bool intishWarn
>
1971 inline tv_lval
SetOpElem(TypedValue
& tvRef
,
1972 SetOpOp op
, tv_lval base
,
1973 TypedValue key
, Cell
* rhs
) {
1974 base
= tvToCell(base
);
1975 assertx(cellIsPlausible(*base
));
1977 switch (type(base
)) {
1980 return SetOpElemEmptyish(op
, base
, key
, rhs
);
1983 if (val(base
).num
) {
1984 return SetOpElemScalar(tvRef
);
1986 return SetOpElemEmptyish(op
, base
, key
, rhs
);
1990 case KindOfResource
:
1992 return SetOpElemScalar(tvRef
);
1994 case KindOfPersistentString
:
1996 if (val(base
).pstr
->size() != 0) {
1997 raise_error("Cannot use assign-op operators with overloaded "
1998 "objects nor string offsets");
2000 return SetOpElemEmptyish(op
, base
, key
, rhs
);
2002 case KindOfPersistentVec
:
2004 auto result
= ElemDVec
<false, KeyType::Any
>(base
, key
);
2005 result
= tvAssertCell(result
);
2006 setopBody(result
, op
, rhs
);
2010 case KindOfPersistentDict
:
2012 auto result
= ElemDDict
<false, KeyType::Any
>(base
, key
);
2013 result
= tvAssertCell(result
);
2014 setopBody(result
, op
, rhs
);
2018 case KindOfPersistentKeyset
:
2020 throwInvalidKeysetOperation();
2022 case KindOfPersistentArray
:
2025 checkHACFalseyPromote() &&
2026 !asCArrRef(base
).exists(tvAsCVarRef(&key
))
2028 raiseHackArrCompatMissingSetOp();
2030 auto constexpr mode
= MoreWarnings
? MOpMode::Warn
: MOpMode::None
;
2032 ElemDArray
<mode
, false, intishWarn
, KeyType::Any
>(base
, key
);
2033 result
= tvToCell(result
);
2034 setopBody(result
, op
, rhs
);
2038 case KindOfObject
: {
2040 if (LIKELY(val(base
).pobj
->isCollection())) {
2041 result
= collections::atRw(val(base
).pobj
, &key
);
2042 setopBody(tvToCell(result
), op
, rhs
);
2044 tvRef
= objOffsetGet(instanceFromTv(base
), key
);
2046 setopBody(tvToCell(result
), op
, rhs
);
2047 objOffsetSet(instanceFromTv(base
), key
, result
, false);
2055 unknownBaseType(type(base
));
2058 inline tv_lval
SetOpNewElemEmptyish(SetOpOp op
, tv_lval base
, Cell
* rhs
) {
2059 detail::checkPromotion(base
);
2060 cellMove(make_tv
<KindOfArray
>(staticEmptyArray()), base
);
2061 auto result
= asArrRef(base
).lvalAt();
2062 setopBody(tvToCell(result
), op
, rhs
);
2065 inline tv_lval
SetOpNewElemScalar(TypedValue
& tvRef
) {
2066 raise_warning(Strings::CANNOT_USE_SCALAR_AS_ARRAY
);
2070 inline tv_lval
SetOpNewElem(TypedValue
& tvRef
,
2071 SetOpOp op
, tv_lval base
,
2073 base
= tvToCell(base
);
2074 assertx(cellIsPlausible(*base
));
2076 switch (type(base
)) {
2079 return SetOpNewElemEmptyish(op
, base
, rhs
);
2082 if (val(base
).num
) {
2083 return SetOpNewElemScalar(tvRef
);
2085 return SetOpNewElemEmptyish(op
, base
, rhs
);
2089 case KindOfResource
:
2091 return SetOpNewElemScalar(tvRef
);
2093 case KindOfPersistentString
:
2095 if (val(base
).pstr
->size() != 0) {
2096 raise_error("[] operator not supported for strings");
2098 return SetOpNewElemEmptyish(op
, base
, rhs
);
2100 case KindOfPersistentVec
:
2102 throw_cannot_use_newelem_for_lval_read_vec();
2103 case KindOfPersistentDict
:
2105 throw_cannot_use_newelem_for_lval_read_dict();
2106 case KindOfPersistentKeyset
:
2108 throw_cannot_use_newelem_for_lval_read_keyset();
2110 case KindOfPersistentArray
:
2112 auto result
= asArrRef(base
).lvalAt();
2113 setopBody(result
, op
, rhs
);
2117 case KindOfObject
: {
2119 if (val(base
).pobj
->isCollection()) {
2120 throw_cannot_use_newelem_for_lval_read_col();
2123 tvRef
= objOffsetGet(instanceFromTv(base
), make_tv
<KindOfNull
>());
2125 setopBody(tvToCell(result
), op
, rhs
);
2126 objOffsetAppend(instanceFromTv(base
), result
, false);
2134 unknownBaseType(type(base
));
2137 NEVER_INLINE Cell
incDecBodySlow(IncDecOp op
, tv_lval fr
);
2139 inline Cell
IncDecBody(IncDecOp op
, tv_lval fr
) {
2140 assertx(cellIsPlausible(*fr
));
2142 if (UNLIKELY(!isIntType(type(fr
)))) {
2143 return incDecBodySlow(op
, fr
);
2146 // fast cases, assuming integers overflow to ints
2148 case IncDecOp::PreInc
:
2151 case IncDecOp::PostInc
: {
2152 auto const tmp
= *fr
;
2156 case IncDecOp::PreDec
:
2159 case IncDecOp::PostDec
: {
2160 auto const tmp
= *fr
;
2165 return incDecBodySlow(op
, fr
);
2169 inline Cell
IncDecElemEmptyish(
2174 detail::checkPromotion(base
);
2176 cellMove(make_tv
<KindOfArray
>(staticEmptyArray()), base
);
2177 auto const lval
= asArrRef(base
).lvalAt(tvAsCVarRef(&key
));
2179 raise_notice(Strings::UNDEFINED_INDEX
,
2180 tvAsCVarRef(&key
).toString().data());
2182 assertx(type(lval
) == KindOfNull
);
2183 return IncDecBody(op
, lval
);
2186 inline Cell
IncDecElemScalar() {
2187 raise_warning(Strings::CANNOT_USE_SCALAR_AS_ARRAY
);
2188 return make_tv
<KindOfNull
>();
2191 template <bool intishWarn
>
2192 inline Cell
IncDecElem(
2197 base
= tvToCell(base
);
2198 assertx(cellIsPlausible(*base
));
2200 switch (type(base
)) {
2203 return IncDecElemEmptyish(op
, base
, key
);
2206 if (val(base
).num
) {
2207 return IncDecElemScalar();
2209 return IncDecElemEmptyish(op
, base
, key
);
2213 case KindOfResource
:
2215 return IncDecElemScalar();
2217 case KindOfPersistentString
:
2219 if (val(base
).pstr
->size() != 0) {
2220 raise_error("Cannot increment/decrement overloaded objects "
2221 "nor string offsets");
2223 return IncDecElemEmptyish(op
, base
, key
);
2225 case KindOfPersistentVec
:
2227 auto result
= ElemDVec
<false, KeyType::Any
>(base
, key
);
2228 return IncDecBody(op
, tvAssertCell(result
));
2231 case KindOfPersistentDict
:
2233 auto result
= ElemDDict
<false, KeyType::Any
>(base
, key
);
2234 return IncDecBody(op
, tvAssertCell(result
));
2237 case KindOfPersistentKeyset
:
2239 throwInvalidKeysetOperation();
2241 case KindOfPersistentArray
:
2244 checkHACFalseyPromote() &&
2245 !asCArrRef(base
).exists(tvAsCVarRef(&key
))
2247 raiseHackArrCompatMissingIncDec();
2249 auto constexpr mode
= MoreWarnings
? MOpMode::Warn
: MOpMode::None
;
2251 ElemDArray
<mode
, false, intishWarn
, KeyType::Any
>(base
, key
);
2252 return IncDecBody(op
, tvToCell(result
));
2255 case KindOfObject
: {
2257 auto localTvRef
= make_tv
<KindOfUninit
>();
2259 if (LIKELY(val(base
).pobj
->isCollection())) {
2260 result
= collections::atRw(val(base
).pobj
, &key
);
2261 assertx(cellIsPlausible(*result
));
2263 localTvRef
= objOffsetGet(instanceFromTv(base
), key
);
2264 result
= tvToCell(&localTvRef
);
2267 auto const dest
= IncDecBody(op
, result
);
2268 tvDecRefGen(localTvRef
);
2275 unknownBaseType(type(base
));
2278 inline Cell
IncDecNewElemEmptyish(
2282 detail::checkPromotion(base
);
2283 cellMove(make_tv
<KindOfArray
>(staticEmptyArray()), base
);
2284 auto result
= asArrRef(base
).lvalAt();
2285 assertx(type(result
) == KindOfNull
);
2286 return IncDecBody(op
, result
);
2289 inline Cell
IncDecNewElemScalar() {
2290 raise_warning(Strings::CANNOT_USE_SCALAR_AS_ARRAY
);
2291 return make_tv
<KindOfNull
>();
2294 inline Cell
IncDecNewElem(
2299 base
= tvToCell(base
);
2300 assertx(cellIsPlausible(*base
));
2302 switch (type(base
)) {
2305 return IncDecNewElemEmptyish(op
, base
);
2308 if (val(base
).num
) {
2309 return IncDecNewElemScalar();
2311 return IncDecNewElemEmptyish(op
, base
);
2315 case KindOfResource
:
2317 return IncDecNewElemScalar();
2319 case KindOfPersistentString
:
2321 if (val(base
).pstr
->size() != 0) {
2322 raise_error("[] operator not supported for strings");
2324 return IncDecNewElemEmptyish(op
, base
);
2326 case KindOfPersistentVec
:
2328 throw_cannot_use_newelem_for_lval_read_vec();
2329 case KindOfPersistentDict
:
2331 throw_cannot_use_newelem_for_lval_read_dict();
2332 case KindOfPersistentKeyset
:
2334 throw_cannot_use_newelem_for_lval_read_keyset();
2336 case KindOfPersistentArray
:
2338 auto result
= asArrRef(base
).lvalAt();
2339 assertx(type(result
) == KindOfNull
);
2340 return IncDecBody(op
, result
);
2343 case KindOfObject
: {
2344 if (val(base
).pobj
->isCollection()) {
2345 throw_cannot_use_newelem_for_lval_read_col();
2347 tvRef
= objOffsetGet(instanceFromTv(base
), make_tv
<KindOfNull
>());
2348 auto result
= tvToCell(&tvRef
);
2349 return IncDecBody(op
, result
);
2355 unknownBaseType(type(base
));
2359 * UnsetElemArray when key is an Int64
2361 template <bool intishWarn
>
2362 inline ArrayData
* UnsetElemArrayPre(ArrayData
* a
, int64_t key
,
2364 return a
->remove(key
, copy
);
2368 * UnsetElemArray when key is a String
2370 template <bool intishWarn
>
2371 inline ArrayData
* UnsetElemArrayPre(ArrayData
* a
, StringData
* key
,
2374 assertx(a
->isPHPArray());
2375 if (key
->isStrictlyInteger(n
)) {
2376 if (intishWarn
) raise_intish_index_cast();
2377 return a
->remove(n
, copy
);
2379 return a
->remove(key
, copy
);
2383 template <bool intishWarn
>
2384 inline ArrayData
* UnsetElemArrayPre(ArrayData
* a
, TypedValue key
,
2386 if (isStringType(key
.m_type
)) {
2387 return UnsetElemArrayPre
<intishWarn
>(a
, key
.m_data
.pstr
, copy
);
2389 if (key
.m_type
== KindOfInt64
) {
2390 return UnsetElemArrayPre
<false>(a
, key
.m_data
.num
, copy
);
2392 auto const k
= tvToKey(key
, a
);
2393 if (isNullType(k
.m_type
)) return a
;
2394 return a
->remove(k
, copy
);
2398 * UnsetElem when base is an Array
2400 template <KeyType keyType
, bool intishWarn
>
2401 inline void UnsetElemArray(tv_lval base
, key_type
<keyType
> key
) {
2402 assertx(tvIsArray(base
));
2403 assertx(tvIsPlausible(*base
));
2404 ArrayData
* a
= val(base
).parr
;
2405 ArrayData
* a2
= UnsetElemArrayPre
<intishWarn
>(a
, key
, a
->cowCheck());
2408 type(base
) = KindOfArray
;
2409 val(base
).parr
= a2
;
2410 assertx(cellIsPlausible(*base
));
2411 a
->decRefAndRelease();
2416 * UnsetElem when base is a Vec
2419 inline ArrayData
* UnsetElemVecPre(ArrayData
* a
, int64_t key
,
2421 return PackedArray::RemoveIntVec(a
, key
, copy
);
2425 UnsetElemVecPre(ArrayData
* a
, StringData
* /*key*/, bool /*copy*/) {
2426 /* Never contains strings, so a no-op. */
2430 inline ArrayData
* UnsetElemVecPre(ArrayData
* a
, TypedValue key
,
2432 auto const dt
= key
.m_type
;
2433 if (LIKELY(isIntType(dt
))) return UnsetElemVecPre(a
, key
.m_data
.num
, copy
);
2434 if (isStringType(dt
)) return UnsetElemVecPre(a
, key
.m_data
.pstr
, copy
);
2435 throwInvalidArrayKeyException(&key
, a
);
2438 template <KeyType keyType
>
2439 inline void UnsetElemVec(tv_lval base
, key_type
<keyType
> key
) {
2440 assertx(tvIsVec(base
));
2441 assertx(tvIsPlausible(*base
));
2442 ArrayData
* a
= val(base
).parr
;
2443 ArrayData
* a2
= UnsetElemVecPre(a
, key
, a
->cowCheck());
2444 assertx(a2
->isVecArray() || a2
->isDict());
2447 type(base
) = a2
->toDataType();
2448 val(base
).parr
= a2
;
2449 assertx(cellIsPlausible(*base
));
2450 a
->decRefAndRelease();
2455 * UnsetElem when base is a Dict
2458 inline ArrayData
* UnsetElemDictPre(ArrayData
* a
, int64_t key
,
2460 return MixedArray::RemoveIntDict(a
, key
, copy
);
2463 inline ArrayData
* UnsetElemDictPre(ArrayData
* a
, StringData
* key
,
2465 return MixedArray::RemoveStrDict(a
, key
, copy
);
2468 inline ArrayData
* UnsetElemDictPre(ArrayData
* a
, TypedValue key
,
2470 auto const dt
= key
.m_type
;
2471 if (isIntType(dt
)) return UnsetElemDictPre(a
, key
.m_data
.num
, copy
);
2472 if (isStringType(dt
)) return UnsetElemDictPre(a
, key
.m_data
.pstr
, copy
);
2473 throwInvalidArrayKeyException(&key
, a
);
2476 template <KeyType keyType
>
2477 inline void UnsetElemDict(tv_lval base
, key_type
<keyType
> key
) {
2478 assertx(tvIsDict(base
));
2479 assertx(tvIsPlausible(*base
));
2480 ArrayData
* a
= val(base
).parr
;
2481 ArrayData
* a2
= UnsetElemDictPre(a
, key
, a
->cowCheck());
2484 type(base
) = KindOfDict
;
2485 val(base
).parr
= a2
;
2486 assertx(cellIsPlausible(*base
));
2487 a
->decRefAndRelease();
2492 * UnsetElem when base is a Keyset
2495 inline ArrayData
* UnsetElemKeysetPre(ArrayData
* a
, int64_t key
,
2497 return SetArray::RemoveInt(a
, key
, copy
);
2500 inline ArrayData
* UnsetElemKeysetPre(ArrayData
* a
, StringData
* key
,
2502 return SetArray::RemoveStr(a
, key
, copy
);
2505 inline ArrayData
* UnsetElemKeysetPre(ArrayData
* a
, TypedValue key
,
2507 auto const dt
= key
.m_type
;
2508 if (isIntType(dt
)) return UnsetElemKeysetPre(a
, key
.m_data
.num
, copy
);
2509 if (isStringType(dt
)) return UnsetElemKeysetPre(a
, key
.m_data
.pstr
, copy
);
2510 throwInvalidArrayKeyException(&key
, a
);
2513 template <KeyType keyType
>
2514 inline void UnsetElemKeyset(tv_lval base
, key_type
<keyType
> key
) {
2515 assertx(tvIsKeyset(base
));
2516 assertx(tvIsPlausible(*base
));
2517 ArrayData
* a
= val(base
).parr
;
2518 ArrayData
* a2
= UnsetElemKeysetPre(a
, key
, a
->cowCheck());
2521 type(base
) = KindOfKeyset
;
2522 val(base
).parr
= a2
;
2523 assertx(cellIsPlausible(*base
));
2524 a
->decRefAndRelease();
2529 * unset($base[$member])
2531 template <KeyType keyType
, bool intishWarn
>
2533 void UnsetElemSlow(tv_lval base
, key_type
<keyType
> key
) {
2534 base
= tvToCell(base
);
2535 assertx(cellIsPlausible(*base
));
2537 switch (type(base
)) {
2543 case KindOfResource
:
2544 return; // Do nothing.
2547 raise_error("Cannot unset a func");
2550 case KindOfPersistentString
:
2552 raise_error(Strings::CANT_UNSET_STRING
);
2555 case KindOfPersistentVec
:
2557 UnsetElemVec
<keyType
>(base
, key
);
2560 case KindOfPersistentDict
:
2562 UnsetElemDict
<keyType
>(base
, key
);
2565 case KindOfPersistentKeyset
:
2567 UnsetElemKeyset
<keyType
>(base
, key
);
2570 case KindOfPersistentArray
:
2572 UnsetElemArray
<keyType
, intishWarn
>(base
, key
);
2575 case KindOfObject
: {
2576 auto const& scratchKey
= initScratchKey(key
);
2577 if (LIKELY(val(base
).pobj
->isCollection())) {
2578 collections::unset(val(base
).pobj
, &scratchKey
);
2580 objOffsetUnset(instanceFromTv(base
), scratchKey
);
2588 unknownBaseType(type(base
));
2592 * Fast path for UnsetElem assuming base is an Array
2594 template <bool intishWarn
, KeyType keyType
= KeyType::Any
>
2595 inline void UnsetElem(tv_lval base
, key_type
<keyType
> key
) {
2596 assertx(tvIsPlausible(*base
));
2598 if (LIKELY(tvIsArray(base
))) {
2599 return UnsetElemArray
<keyType
, intishWarn
>(base
, key
);
2601 if (LIKELY(tvIsVec(base
))) {
2602 return UnsetElemVec
<keyType
>(base
, key
);
2604 if (LIKELY(tvIsDict(base
))) {
2605 return UnsetElemDict
<keyType
>(base
, key
);
2607 if (LIKELY(tvIsKeyset(base
))) {
2608 return UnsetElemKeyset
<keyType
>(base
, key
);
2610 return UnsetElemSlow
<keyType
, intishWarn
>(base
, key
);
2614 * IssetEmptyElem when base is an Object
2616 template<bool useEmpty
, KeyType keyType
>
2617 bool IssetEmptyElemObj(ObjectData
* instance
, key_type
<keyType
> key
) {
2618 auto scratchKey
= initScratchKey(key
);
2619 if (LIKELY(instance
->isCollection())) {
2621 ? collections::empty(instance
, &scratchKey
)
2622 : collections::isset(instance
, &scratchKey
);
2626 ? objOffsetEmpty(instance
, scratchKey
)
2627 : objOffsetIsset(instance
, scratchKey
);
2631 * IssetEmptyElem when base is a String
2633 template <bool useEmpty
, KeyType keyType
>
2634 bool IssetEmptyElemString(tv_rval base
, key_type
<keyType
> key
) {
2635 // TODO Task #2716479: Fix this so that the warnings raised match
2637 auto scratchKey
= initScratchKey(key
);
2639 if (LIKELY(scratchKey
.m_type
== KindOfInt64
)) {
2640 x
= scratchKey
.m_data
.num
;
2643 cellDup(scratchKey
, tv
);
2644 bool badKey
= false;
2645 if (isStringType(tv
.m_type
)) {
2646 const char* str
= tv
.m_data
.pstr
->data();
2647 size_t len
= tv
.m_data
.pstr
->size();
2649 (*str
== ' ' || *str
== '\t' || *str
== '\r' || *str
== '\n')) {
2654 badKey
= !is_strictly_integer(str
, len
, n
);
2655 } else if (isArrayLikeType(tv
.m_type
) || tv
.m_type
== KindOfObject
||
2656 tv
.m_type
== KindOfResource
) {
2659 // Even if badKey == true, we still perform the cast so that we
2660 // raise the appropriate warnings.
2661 tvCastToInt64InPlace(&tv
);
2667 if (x
< 0 || x
>= val(base
).pstr
->size()) {
2674 auto str
= val(base
).pstr
->getChar(x
);
2675 assertx(str
->isStatic());
2676 return !str
->toBoolean();
2680 * IssetEmptyElem when base is an Array
2682 template <bool useEmpty
, KeyType keyType
, bool intishWarn
>
2683 bool IssetEmptyElemArray(ArrayData
* a
, key_type
<keyType
> key
) {
2684 assertx(a
->isPHPArray());
2685 auto const result
= ElemArray
<MOpMode::None
, keyType
, intishWarn
>(a
, key
);
2687 return !cellToBool(tvToCell(result
.tv()));
2689 return !cellIsNull(tvToCell(result
.tv()));
2693 * IssetEmptyElem when base is a Vec
2695 template <bool useEmpty
, KeyType keyType
>
2696 bool IssetEmptyElemVec(ArrayData
* a
, key_type
<keyType
> key
) {
2697 assertx(a
->isVecArray());
2698 auto const result
= ElemVec
<MOpMode::None
, keyType
>(a
, key
);
2700 return !cellToBool(tvAssertCell(result
.tv()));
2702 return !cellIsNull(tvAssertCell(result
.tv()));
2706 * IssetEmptyElem when base is a Dict
2708 template <bool useEmpty
, KeyType keyType
>
2709 bool IssetEmptyElemDict(ArrayData
* a
, key_type
<keyType
> key
) {
2710 assertx(a
->isDict());
2711 auto const result
= ElemDict
<MOpMode::None
, keyType
>(a
, key
);
2713 return !cellToBool(tvAssertCell(result
.tv()));
2715 return !cellIsNull(tvAssertCell(result
.tv()));
2719 * IssetEmptyElem when base is a Keyset
2721 template <bool useEmpty
, KeyType keyType
>
2722 bool IssetEmptyElemKeyset(ArrayData
* a
, key_type
<keyType
> key
) {
2723 assertx(a
->isKeyset());
2724 auto const result
= ElemKeyset
<MOpMode::None
, keyType
>(a
, key
);
2726 return !cellToBool(tvAssertCell(result
.tv()));
2728 return !cellIsNull(tvAssertCell(result
.tv()));
2732 * isset/empty($base[$key])
2734 template <bool useEmpty
, KeyType keyType
, bool intishWarn
>
2735 NEVER_INLINE
bool IssetEmptyElemSlow(tv_rval base
, key_type
<keyType
> key
) {
2736 base
= tvToCell(base
);
2737 assertx(cellIsPlausible(*base
));
2739 switch (type(base
)) {
2745 case KindOfResource
:
2750 case KindOfPersistentString
:
2752 return IssetEmptyElemString
<useEmpty
, keyType
>(base
, key
);
2754 case KindOfPersistentVec
:
2756 return IssetEmptyElemVec
<useEmpty
, keyType
>(val(base
).parr
, key
);
2758 case KindOfPersistentDict
:
2760 return IssetEmptyElemDict
<useEmpty
, keyType
>(val(base
).parr
, key
);
2762 case KindOfPersistentKeyset
:
2764 return IssetEmptyElemKeyset
<useEmpty
, keyType
>(val(base
).parr
, key
);
2766 case KindOfPersistentArray
:
2768 return IssetEmptyElemArray
<useEmpty
, keyType
, intishWarn
>(
2773 return IssetEmptyElemObj
<useEmpty
, keyType
>(val(base
).pobj
, key
);
2778 unknownBaseType(type(base
));
2781 template <bool useEmpty
, bool intishWarn
, KeyType keyType
= KeyType::Any
>
2782 bool IssetEmptyElem(tv_rval base
, key_type
<keyType
> key
) {
2783 assertx(tvIsPlausible(*base
));
2785 if (LIKELY(tvIsArray(base
))) {
2786 return IssetEmptyElemArray
<useEmpty
, keyType
, intishWarn
>(
2790 if (LIKELY(tvIsVec(base
))) {
2791 return IssetEmptyElemVec
<useEmpty
, keyType
>(val(base
).parr
, key
);
2793 if (LIKELY(tvIsDict(base
))) {
2794 return IssetEmptyElemDict
<useEmpty
, keyType
>(val(base
).parr
, key
);
2796 if (LIKELY(tvIsKeyset(base
))) {
2797 return IssetEmptyElemKeyset
<useEmpty
, keyType
>(val(base
).parr
, key
);
2799 return IssetEmptyElemSlow
<useEmpty
, keyType
, intishWarn
>(base
, key
);
2802 template<MOpMode mode
>
2803 inline tv_lval
propPreNull(TypedValue
& tvRef
) {
2805 if (mode
== MOpMode::Warn
) {
2806 raise_notice("Cannot access property on non-object");
2808 return tv_lval(&tvRef
);
2812 inline void promoteToStdClass(tv_lval base
, bool warn
, F fun
) {
2813 if (!RuntimeOption::EvalPromoteEmptyObject
) {
2814 // note that the whole point here is to guarantee that the property
2815 // never auto updates to a stdclass - so we must do this before
2816 // calling promote, and we don't want the try catch below around
2818 if (RuntimeOption::PHP7_EngineExceptions
) {
2819 SystemLib::throwErrorObject(Strings::SET_PROP_NON_OBJECT
);
2821 SystemLib::throwExceptionObject(Strings::SET_PROP_NON_OBJECT
);
2826 Object obj
{ ObjectData::newInstance(SystemLib::s_stdclassClass
) };
2827 if (base
.type() == KindOfString
) {
2828 decRefStr(base
.val().pstr
);
2830 assertx(!isRefcountedType(base
.type()));
2832 base
.type() = KindOfObject
;
2833 base
.val().pobj
= obj
.get();
2836 // Behavior here is observable.
2837 // In PHP 5.6, raise_warning is called before updating base, so
2838 // the error_handler sees the original base; but if an exception
2839 // is thrown from the error handler, any catch block will see the
2841 // In PHP 7+, raise_warning is called after updating base, but before
2842 // doing the work of fun, and again, if an exception is thrown, fun
2843 // still gets called before reaching the catch block.
2844 // We'll match PHP7, because we have no way of ensuring that base survives
2845 // across a call to the error_handler: eg $a[0][0][0]->foo = 0; if $a
2846 // started out null, and the error handler resets it to null, base is
2848 // Note that this means that the error handler can overwrite the object
2849 // so there is no guarantee that we have an object on return from
2850 // promoteToStdClass.
2852 raise_warning(Strings::CREATING_DEFAULT_OBJECT
);
2853 } catch (const Object
&) {
2862 template<MOpMode mode
>
2863 tv_lval
propPreStdclass(TypedValue
& tvRef
, tv_lval base
) {
2864 if (mode
!= MOpMode::Define
) {
2865 return propPreNull
<mode
>(tvRef
);
2868 promoteToStdClass(base
, RuntimeOption::EnableHipHopSyntax
,
2869 [] (ObjectData
*) {});
2870 if (UNLIKELY(base
.type() != KindOfObject
)) {
2871 // See the comments above. Although promoteToStdClass will have
2872 // either thrown an exception, or promoted base to an object, an
2873 // installed error handler might have caused it to be overwritten
2875 return tv_lval(&tvRef
);
2881 template<MOpMode mode
>
2882 tv_lval
propPre(TypedValue
& tvRef
, tv_lval base
) {
2883 base
= base
.unboxed();
2884 switch (base
.type()) {
2887 return propPreStdclass
<mode
>(tvRef
, base
);
2890 if (base
.val().num
) {
2891 return propPreNull
<mode
>(tvRef
);
2893 return propPreStdclass
<mode
>(tvRef
, base
);
2897 case KindOfResource
:
2899 return propPreNull
<mode
>(tvRef
);
2901 case KindOfPersistentString
:
2903 if (base
.val().pstr
->size() != 0) {
2904 return propPreNull
<mode
>(tvRef
);
2906 return propPreStdclass
<mode
>(tvRef
, base
);
2908 case KindOfPersistentVec
:
2910 case KindOfPersistentDict
:
2912 case KindOfPersistentKeyset
:
2914 case KindOfPersistentArray
:
2916 return propPreNull
<mode
>(tvRef
);
2924 unknownBaseType(type(base
));
2927 inline tv_lval
nullSafeProp(TypedValue
& tvRef
,
2931 base
= base
.unboxed();
2932 switch (base
.type()) {
2940 case KindOfResource
:
2941 case KindOfPersistentString
:
2943 case KindOfPersistentVec
:
2945 case KindOfPersistentDict
:
2947 case KindOfPersistentKeyset
:
2949 case KindOfPersistentArray
:
2953 raise_notice("Cannot access property on non-object");
2956 return val(base
).pobj
->prop(&tvRef
, ctx
, key
);
2958 always_assert(false);
2964 * Generic property access (PropX and PropDX end up here).
2966 * Returns a pointer to a number of possible places, but does not unbox it.
2967 * (The returned pointer is never pointing into a RefData.)
2969 template<MOpMode mode
, KeyType keyType
= KeyType::Any
, bool reffy
= false>
2970 inline tv_lval
PropObj(TypedValue
& tvRef
, const Class
* ctx
,
2971 ObjectData
* instance
, key_type
<keyType
> key
) {
2972 auto keySD
= prepareKey(key
);
2973 SCOPE_EXIT
{ releaseKey
<keyType
>(keySD
); };
2976 if (mode
== MOpMode::Define
) {
2978 return instance
->propB(&tvRef
, ctx
, keySD
);
2980 return instance
->propD(&tvRef
, ctx
, keySD
);
2984 if (mode
== MOpMode::None
) {
2985 return instance
->prop(&tvRef
, ctx
, keySD
);
2987 if (mode
== MOpMode::Warn
) {
2988 return instance
->propW(&tvRef
, ctx
, keySD
);
2990 assertx(mode
== MOpMode::Unset
);
2991 return instance
->propD(&tvRef
, ctx
, keySD
);
2994 template<MOpMode mode
, KeyType keyType
= KeyType::Any
, bool reffy
= false>
2995 inline tv_lval
Prop(TypedValue
& tvRef
,
2998 key_type
<keyType
> key
) {
2999 auto result
= propPre
<mode
>(tvRef
, base
);
3000 if (result
.type() == KindOfNull
) return result
;
3002 return PropObj
<mode
,keyType
,reffy
>(tvRef
, ctx
, instanceFromTv(result
), key
);
3005 template <bool useEmpty
, KeyType kt
>
3006 inline bool IssetEmptyPropObj(Class
* ctx
, ObjectData
* instance
,
3008 auto keySD
= prepareKey(key
);
3009 SCOPE_EXIT
{ releaseKey
<kt
>(keySD
); };
3012 instance
->propEmpty(ctx
, keySD
) :
3013 instance
->propIsset(ctx
, keySD
);
3016 template <bool useEmpty
, KeyType kt
= KeyType::Any
>
3017 bool IssetEmptyProp(Class
* ctx
, tv_lval base
, key_type
<kt
> key
) {
3018 base
= tvToCell(base
);
3019 if (LIKELY(type(base
) == KindOfObject
)) {
3020 return IssetEmptyPropObj
<useEmpty
, kt
>(ctx
, instanceFromTv(base
), key
);
3025 template <bool setResult
>
3026 inline void SetPropNull(Cell
* val
) {
3027 raise_warning("Cannot access property on non-object");
3032 throw InvalidSetMException(make_tv
<KindOfNull
>());
3036 inline void SetPropStdclass(tv_lval base
, TypedValue key
, Cell
* val
) {
3040 [&] (ObjectData
* obj
) {
3041 auto const keySD
= prepareKey(key
);
3042 SCOPE_EXIT
{ decRefStr(keySD
); };
3043 obj
->setProp(nullptr, keySD
, *val
);
3047 template <KeyType keyType
>
3048 inline void SetPropObj(Class
* ctx
, ObjectData
* instance
,
3049 key_type
<keyType
> key
, Cell
* val
) {
3050 StringData
* keySD
= prepareKey(key
);
3051 SCOPE_EXIT
{ releaseKey
<keyType
>(keySD
); };
3054 instance
->setProp(ctx
, keySD
, *val
);
3057 // $base->$key = $val
3058 template <bool setResult
, KeyType keyType
= KeyType::Any
>
3059 inline void SetProp(Class
* ctx
, tv_lval base
, key_type
<keyType
> key
,
3061 base
= tvToCell(base
);
3062 switch (type(base
)) {
3065 return SetPropStdclass(base
, initScratchKey(key
), val
);
3068 if (HPHP::val(base
).num
) {
3069 return SetPropNull
<setResult
>(val
);
3071 return SetPropStdclass(base
, initScratchKey(key
), val
);
3075 case KindOfPersistentVec
:
3077 case KindOfPersistentDict
:
3079 case KindOfPersistentKeyset
:
3081 case KindOfPersistentArray
:
3083 case KindOfResource
:
3085 return SetPropNull
<setResult
>(val
);
3087 case KindOfPersistentString
:
3089 if (HPHP::val(base
).pstr
->size() != 0) {
3090 return SetPropNull
<setResult
>(val
);
3092 return SetPropStdclass(base
, initScratchKey(key
), val
);
3095 return SetPropObj
<keyType
>(ctx
, HPHP::val(base
).pobj
, key
, val
);
3100 unknownBaseType(type(base
));
3103 inline tv_lval
SetOpPropNull(TypedValue
& tvRef
) {
3104 raise_warning("Attempt to assign property of non-object");
3109 inline tv_lval
SetOpPropStdclass(TypedValue
& tvRef
, SetOpOp op
,
3110 tv_lval base
, TypedValue key
,
3115 [&] (ObjectData
* obj
) {
3116 StringData
* keySD
= prepareKey(key
);
3117 SCOPE_EXIT
{ decRefStr(keySD
); };
3119 setopBody(tvAssertCell(&tvRef
), op
, rhs
);
3120 obj
->setProp(nullptr, keySD
, tvAssertCell(tvRef
));
3126 inline tv_lval
SetOpPropObj(TypedValue
& tvRef
, Class
* ctx
,
3127 SetOpOp op
, ObjectData
* instance
,
3128 TypedValue key
, Cell
* rhs
) {
3129 StringData
* keySD
= prepareKey(key
);
3130 SCOPE_EXIT
{ decRefStr(keySD
); };
3131 return instance
->setOpProp(tvRef
, ctx
, op
, keySD
, rhs
);
3134 // $base->$key <op>= $rhs
3135 inline tv_lval
SetOpProp(TypedValue
& tvRef
,
3136 Class
* ctx
, SetOpOp op
,
3137 tv_lval base
, TypedValue key
,
3139 base
= tvToCell(base
);
3140 switch (type(base
)) {
3143 return SetOpPropStdclass(tvRef
, op
, base
, key
, rhs
);
3146 if (val(base
).num
) {
3147 return SetOpPropNull(tvRef
);
3149 return SetOpPropStdclass(tvRef
, op
, base
, key
, rhs
);
3153 case KindOfPersistentVec
:
3155 case KindOfPersistentDict
:
3157 case KindOfPersistentKeyset
:
3159 case KindOfPersistentArray
:
3161 case KindOfResource
:
3163 return SetOpPropNull(tvRef
);
3165 case KindOfPersistentString
:
3167 if (val(base
).pstr
->size() != 0) {
3168 return SetOpPropNull(tvRef
);
3170 return SetOpPropStdclass(tvRef
, op
, base
, key
, rhs
);
3173 return SetOpPropObj(tvRef
, ctx
, op
, instanceFromTv(base
), key
, rhs
);
3178 unknownBaseType(type(base
));
3181 inline Cell
IncDecPropNull() {
3182 raise_warning("Attempt to increment/decrement property of non-object");
3183 return make_tv
<KindOfNull
>();
3186 inline Cell
IncDecPropStdclass(IncDecOp op
, tv_lval base
,
3192 [&] (ObjectData
* obj
) {
3193 StringData
* keySD
= prepareKey(key
);
3194 SCOPE_EXIT
{ decRefStr(keySD
); };
3197 dest
= IncDecBody(op
, &tv
);
3198 obj
->setProp(nullptr, keySD
, dest
);
3199 assertx(!isRefcountedType(tv
.m_type
));
3205 inline Cell
IncDecPropObj(Class
* ctx
,
3209 auto keySD
= prepareKey(key
);
3210 SCOPE_EXIT
{ decRefStr(keySD
); };
3211 return base
->incDecProp(ctx
, op
, keySD
);
3214 inline Cell
IncDecProp(
3220 base
= tvToCell(base
);
3221 switch (type(base
)) {
3224 return IncDecPropStdclass(op
, base
, key
);
3227 if (val(base
).num
) {
3228 return IncDecPropNull();
3230 return IncDecPropStdclass(op
, base
, key
);
3234 case KindOfPersistentVec
:
3236 case KindOfPersistentDict
:
3238 case KindOfPersistentKeyset
:
3240 case KindOfPersistentArray
:
3242 case KindOfResource
:
3244 return IncDecPropNull();
3246 case KindOfPersistentString
:
3248 if (val(base
).pstr
->size() != 0) {
3249 return IncDecPropNull();
3251 return IncDecPropStdclass(op
, base
, key
);
3254 return IncDecPropObj(ctx
, op
, instanceFromTv(base
), key
);
3259 unknownBaseType(type(base
));
3262 inline void UnsetPropObj(Class
* ctx
, ObjectData
* instance
, TypedValue key
) {
3264 auto keySD
= prepareKey(key
);
3265 SCOPE_EXIT
{ decRefStr(keySD
); };
3267 instance
->unsetProp(ctx
, keySD
);
3270 inline void UnsetProp(Class
* ctx
, tv_lval base
, TypedValue key
) {
3272 base
= tvToCell(base
);
3273 if (LIKELY(type(base
) == KindOfObject
)) {
3274 UnsetPropObj(ctx
, instanceFromTv(base
), key
);
3278 ///////////////////////////////////////////////////////////////////////////////
3280 #endif // incl_HPHP_VM_MEMBER_OPERATIONS_H_