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 #include "hphp/runtime/base/object-data.h"
19 #include "hphp/runtime/base/array-init.h"
20 #include "hphp/runtime/base/builtin-functions.h"
21 #include "hphp/runtime/base/collections.h"
22 #include "hphp/runtime/base/container-functions.h"
23 #include "hphp/runtime/base/datatype.h"
24 #include "hphp/runtime/base/exceptions.h"
25 #include "hphp/runtime/base/execution-context.h"
26 #include "hphp/runtime/base/object-iterator.h"
27 #include "hphp/runtime/base/request-info.h"
28 #include "hphp/runtime/base/runtime-error.h"
29 #include "hphp/runtime/base/tv-comparisons.h"
30 #include "hphp/runtime/base/tv-refcount.h"
31 #include "hphp/runtime/base/tv-type.h"
32 #include "hphp/runtime/base/type-variant.h"
33 #include "hphp/runtime/base/vanilla-dict-defs.h"
34 #include "hphp/runtime/base/variable-serializer.h"
36 #include "hphp/runtime/ext/generator/ext_generator.h"
37 #include "hphp/runtime/ext/simplexml/ext_simplexml.h"
38 #include "hphp/runtime/ext/datetime/ext_datetime.h"
39 #include "hphp/runtime/ext/std/ext_std_closure.h"
41 #include "hphp/runtime/vm/class.h"
42 #include "hphp/runtime/vm/member-operations.h"
43 #include "hphp/runtime/vm/memo-cache.h"
44 #include "hphp/runtime/vm/native-data.h"
45 #include "hphp/runtime/vm/native-prop-handler.h"
46 #include "hphp/runtime/vm/jit/translator-inline.h"
47 #include "hphp/runtime/vm/repo-global-data.h"
48 #include "hphp/runtime/vm/runtime.h"
50 #include "hphp/system/systemlib.h"
52 #include <folly/Hash.h>
53 #include <folly/ScopeGuard.h>
59 //////////////////////////////////////////////////////////////////////
61 TRACE_SET_MOD(runtime
);
63 //////////////////////////////////////////////////////////////////////
67 const StaticString
s_clone("__clone");
70 void verifyTypeHint(const Class
* thisCls
,
71 const Class::Prop
* prop
,
73 assertx(tvIsPlausible(*val
));
74 assertx(type(val
) != KindOfUninit
);
75 if (!prop
|| RuntimeOption::EvalCheckPropTypeHints
<= 0) return;
76 if (prop
->typeConstraint
.isCheckable()) {
77 prop
->typeConstraint
.verifyProperty(val
, thisCls
, prop
->cls
, prop
->name
);
79 if (RuntimeOption::EvalEnforceGenericsUB
<= 0) return;
80 for (auto const& ub
: prop
->ubs
) {
81 if (ub
.isCheckable()) {
82 ub
.verifyProperty(val
, thisCls
, prop
->cls
, prop
->name
);
88 void unsetTypeHint(const Class::Prop
* prop
) {
89 if (RuntimeOption::EvalCheckPropTypeHints
<= 0) return;
90 if (!prop
|| prop
->typeConstraint
.isMixedResolved()) return;
91 raise_property_typehint_unset_error(
94 prop
->typeConstraint
.isSoft(),
95 prop
->typeConstraint
.isUpperBound()
101 //////////////////////////////////////////////////////////////////////
103 // Check that the given property's type matches its type-hint.
105 bool assertATypeHint(const TypeConstraint
& tc
, tv_rval val
) {
106 if (!tc
.isCheckable() || tc
.isSoft()) return true;
107 if (val
.type() == KindOfUninit
) return tc
.maybeMixed();
108 return tc
.assertCheck(val
);
112 bool ObjectData::assertTypeHint(tv_rval prop
, Slot slot
) const {
113 assertx(tvIsPlausible(*prop
));
114 assertx(slot
< m_cls
->numDeclProperties());
115 auto const& propDecl
= m_cls
->declProperties()[slot
];
117 if (prop
.type() == KindOfResource
&& g_context
->doingInlineInterp()) {
121 if (debug
&& RuntimeOption::RepoAuthoritative
) {
122 // The fact that uninitialized LateInit props are uninit isn't
123 // reflected in the repo-auth-type.
124 if (prop
.type() != KindOfUninit
|| !(propDecl
.attrs
& AttrLateInit
)) {
125 always_assert(tvMatchesRepoAuthType(*prop
, propDecl
.repoAuthType
));
129 // If we're not hard enforcing, then the prop might contain anything.
130 if (RuntimeOption::EvalCheckPropTypeHints
<= 2) return true;
131 if (!propDecl
.typeConstraint
.isCheckable() ||
132 propDecl
.typeConstraint
.isSoft()) return true;
133 if (propDecl
.typeConstraint
.isUpperBound() &&
134 RuntimeOption::EvalEnforceGenericsUB
< 2) return true;
135 if (prop
.type() == KindOfNull
&& !(propDecl
.attrs
& AttrNoImplicitNullable
)) {
138 if (prop
.type() == KindOfUninit
&& (propDecl
.attrs
& AttrLateInit
)) {
141 if (!assertATypeHint(propDecl
.typeConstraint
, prop
)) return false;
142 if (RuntimeOption::EvalEnforceGenericsUB
<= 2) return true;
143 for (auto const& ub
: propDecl
.ubs
) {
144 if (!assertATypeHint(ub
, prop
)) return false;
149 //////////////////////////////////////////////////////////////////////
152 static void freeDynPropArray(ObjectData
* inst
) {
153 auto& table
= g_context
->dynPropTable
;
154 auto it
= table
.find(inst
);
155 assertx(it
!= end(table
));
156 assertx(it
->second
.arr().isDict());
157 it
->second
.destroy();
162 void ObjectData::slowDestroyCases() {
163 assertx(slowDestroyCheck());
165 if (getAttribute(UsedMemoCache
)) {
166 assertx(m_cls
->hasMemoSlots());
167 auto const nSlots
= m_cls
->numMemoSlots();
168 for (Slot i
= 0; i
< nSlots
; ++i
) {
169 auto slot
= memoSlot(i
);
170 if (slot
->isCache()) {
171 if (auto cache
= slot
->getCache()) req::destroy_raw(cache
);
173 tvDecRefGen(*slot
->getValue());
178 if (UNLIKELY(getAttribute(HasDynPropArr
))) freeDynPropArray(this);
179 if (UNLIKELY(getAttribute(IsWeakRefed
))) {
180 WeakRefData::invalidateWeakRef((uintptr_t)this);
183 auto const memoSize
= m_cls
->memoSize();
184 auto const ptr
= reinterpret_cast<char*>(this) - memoSize
;
185 tl_heap
->objFreeIndex(ptr
, m_cls
->sizeIdx());
188 // Single check for a couple different unlikely actions during destruction.
189 inline bool ObjectData::slowDestroyCheck() const {
190 return m_aux16
& (HasDynPropArr
| IsWeakRefed
| UsedMemoCache
| BigAllocSize
);
193 void ObjectData::release(ObjectData
* obj
, const Class
* cls
) noexcept
{
194 assertx(obj
->kindIsValid());
195 assertx(!obj
->hasInstanceDtor());
196 assertx(!obj
->hasNativeData());
197 assertx(obj
->getVMClass() == cls
);
198 assertx(cls
->releaseFunc() == &ObjectData::release
);
199 assertx(obj
->props()->checkInvariants(cls
->numDeclProperties()));
201 // Note: cleanups done in this function are only run for classes without an
202 // instanceDtor. Some of these cleanups are duplicated in ~ObjectData, and
203 // your instanceDtor may call that to have them run; if you choose not to run
204 // ~ObjectData from your instanceDtor you MUST do some of them manually
205 // (e.g. invalidate WeakRefs). Some cleanups (e.g. clearing memo caches) are
206 // not done from ~ObjectData because it is assumed they're not needed for
207 // builtin classes (and in the case of memo caches, since the clearing needs
208 // to be done differently when there is native data).
209 // Finally, cleanups such as invalidating WeakRefs that have to be done for
210 // correctness MUST also be done in Collector::sweep, since none of the code
211 // in this function or the instanceDtor will be run when the object is
214 // `obj' is being torn down now---be careful about where/how you dereference
217 obj
->props()->release(cls
->countablePropsEnd());
219 if (UNLIKELY(obj
->slowDestroyCheck())) {
220 obj
->slowDestroyCases();
222 assertx((obj
->m_aux16
& BigAllocSize
) == 0);
223 auto const memoSize
= cls
->memoSize();
224 auto const ptr
= reinterpret_cast<char*>(obj
) - memoSize
;
225 assertx(memoSize
== 0 ||
226 reinterpret_cast<const MemoNode
*>(ptr
)->objOff() == memoSize
);
228 tl_heap
->freeSmallIndex(ptr
, cls
->sizeIdx());
231 AARCH64_WALKABLE_FRAME();
234 ///////////////////////////////////////////////////////////////////////////////
237 StrNR
ObjectData::getClassName() const {
238 return m_cls
->preClass()->nameStr();
241 bool ObjectData::instanceof(const String
& s
) const {
242 assertx(kindIsValid());
243 auto const cls
= Class::lookup(s
.get());
244 return cls
&& instanceof(cls
);
247 bool ObjectData::toBooleanImpl() const noexcept
{
248 // Note: if you add more cases here, hhbbc/class-util.cpp also needs
250 if (isCollection()) {
251 if (RuntimeOption::EvalNoticeOnCollectionToBool
) {
253 "%s to boolean cast",
254 collections::typeToString((CollectionType
)m_kind
)->data()
257 return collections::toBool(this);
260 if (instanceof(SimpleXMLElement_classof())) {
261 // SimpleXMLElement is the only non-collection class that has custom bool
263 if (RuntimeOption::EvalNoticeOnSimpleXMLBehavior
) {
264 raise_notice("SimpleXMLElement to boolean cast");
266 return SimpleXMLElement_objectCast(this, KindOfBoolean
).toBoolean();
269 always_assert(false);
273 int64_t ObjectData::toInt64() const {
274 /* SimpleXMLElement is the only class that has proper custom num casting. */
275 if (LIKELY(!instanceof(SimpleXMLElement_classof()))) {
276 throwObjToIntException(classname_cstr());
278 if (RuntimeOption::EvalNoticeOnSimpleXMLBehavior
) {
279 raise_notice("SimpleXMLElement to integer cast");
281 return SimpleXMLElement_objectCast(this, KindOfInt64
).toInt64();
284 double ObjectData::toDouble() const {
285 /* SimpleXMLElement is the only class that has proper custom num casting. */
286 if (LIKELY(!instanceof(SimpleXMLElement_classof()))) {
287 throwObjToDoubleException(classname_cstr());
289 if (RuntimeOption::EvalNoticeOnSimpleXMLBehavior
) {
290 raise_notice("SimpleXMLElement to double cast");
292 return SimpleXMLElement_objectCast(this, KindOfDouble
).toDouble();
295 ///////////////////////////////////////////////////////////////////////////////
296 // instance methods and properties
298 const StaticString
s_getIterator("getIterator");
300 Object
ObjectData::iterableObject(bool& isIterable
,
301 bool mayImplementIterator
/* = true */) {
302 assertx(mayImplementIterator
|| !isIterator());
303 if (mayImplementIterator
&& isIterator()) {
308 CoeffectsAutoGuard _
;
309 while (obj
->instanceof(SystemLib::s_IteratorAggregateClass
)) {
311 obj
->o_invoke_few_args(s_getIterator
, RuntimeCoeffects::automatic(), 0);
312 if (!iterator
.isObject()) break;
313 auto o
= iterator
.getObjectData();
314 if (o
->isIterator()) {
320 if (!isIterator() && obj
->instanceof(SimpleXMLElement_classof())) {
321 if (RuntimeOption::EvalNoticeOnSimpleXMLBehavior
) {
322 raise_notice("SimpleXMLElement used as iterator");
325 return create_object(
326 s_SimpleXMLElementIterator
,
334 Array
& ObjectData::dynPropArray() const {
335 assertx(getAttribute(HasDynPropArr
));
336 assertx(g_context
->dynPropTable
.count(this));
337 assertx(g_context
->dynPropTable
[this].arr().isDict());
338 return g_context
->dynPropTable
[this].arr();
341 void ObjectData::setDynProps(const Array
& newArr
) {
342 // don't expose the ref returned by setDynPropArr
343 (void)setDynPropArray(newArr
.toDict());
346 void ObjectData::reserveDynProps(int numDynamic
) {
347 // don't expose the ref returned by reserveProperties()
348 (void)reserveProperties(numDynamic
);
351 Array
& ObjectData::reserveProperties(int numDynamic
/* = 2 */) {
352 if (getAttribute(HasDynPropArr
)) {
353 return dynPropArray();
356 auto const allocsz
= VanillaDict::computeAllocBytesFromMaxElms(numDynamic
);
357 if (UNLIKELY(allocsz
> kMaxSmallSize
&& tl_heap
->preAllocOOM(allocsz
))) {
358 check_non_safepoint_surprise();
361 return setDynPropArray(Array::attach(
362 VanillaDict::MakeReserveDict(numDynamic
)));
365 Array
& ObjectData::setDynPropArray(const Array
& newArr
) {
366 assertx(!g_context
->dynPropTable
.count(this));
367 assertx(!getAttribute(HasDynPropArr
));
368 assertx(newArr
.isDict());
370 if (m_cls
->forbidsDynamicProps()) {
371 throw_object_forbids_dynamic_props(getClassName().data());
373 if (RuntimeOption::EvalNoticeOnCreateDynamicProp
) {
374 IterateKV(newArr
.get(), [&] (TypedValue k
, TypedValue v
) {
375 auto const key
= tvCastToString(k
);
376 raiseCreateDynamicProp(key
.get());
380 // newArr can have refcount 2 or higher
381 auto& arr
= g_context
->dynPropTable
[this].arr();
382 assertx(arr
.isNull());
384 setAttribute(HasDynPropArr
);
388 tv_lval
ObjectData::makeDynProp(const StringData
* key
) {
389 if (RuntimeOption::EvalNoticeOnCreateDynamicProp
) {
390 raiseCreateDynamicProp(key
);
392 if (!reserveProperties().exists(StrNR(key
))) {
393 reserveProperties().set(StrNR(key
), make_tv
<KindOfNull
>());
395 return reserveProperties().lval(StrNR(key
), AccessFlags::Key
);
398 void ObjectData::setDynProp(const StringData
* key
, TypedValue val
) {
399 if (RuntimeOption::EvalNoticeOnCreateDynamicProp
) {
400 raiseCreateDynamicProp(key
);
402 reserveProperties().set(StrNR(key
), val
, true);
405 Variant
ObjectData::o_get(const String
& propName
, bool error
/* = true */,
406 const String
& context
/*= null_string*/) {
407 assertx(kindIsValid());
409 // This is not (just) a check for empty string; property names that start
410 // with null are intentionally being rejected here.
411 if (UNLIKELY(!*propName
.data())) {
412 throw_invalid_property_name(propName
);
415 Class
* ctx
= nullptr;
416 if (!context
.empty()) {
417 ctx
= Class::lookup(context
.get());
419 auto const propCtx
= MemberLookupContext(ctx
);
421 // Can't use propImpl here because if the property is not accessible and
422 // there is no native get, propImpl will raise_error("Cannot access ...",
423 // but o_get will only (maybe) raise_notice("Undefined property ..." :-(
425 auto const lookup
= getPropImpl
<false, true, true>(propCtx
, propName
.get());
426 if (lookup
.val
&& lookup
.accessible
) {
427 if (lookup
.val
.type() != KindOfUninit
) {
428 return Variant::wrap(lookup
.val
.tv());
429 } else if (lookup
.prop
&& (lookup
.prop
->attrs
& AttrLateInit
)) {
430 if (error
) throw_late_init_prop(lookup
.prop
->cls
, propName
.get(), false);
431 return uninit_null();
436 SystemLib::throwUndefinedPropertyExceptionObject(
437 folly::sformat("Undefined property: {}::${}",
438 getClassName().data(),
442 return uninit_null();
445 void ObjectData::o_set(const String
& propName
, const Variant
& v
,
446 const String
& context
/* = null_string */) {
447 assertx(kindIsValid());
449 // This is not (just) a check for empty string; property names that start
450 // with null are intentionally being rejected here.
451 if (UNLIKELY(!*propName
.data())) {
452 throw_invalid_property_name(propName
);
455 Class
* ctx
= nullptr;
456 if (!context
.empty()) {
457 ctx
= Class::lookup(context
.get());
459 auto const propCtx
= MemberLookupContext(ctx
);
461 // Can't use setProp here because if the property is not accessible and
462 // there is no native set, setProp will raise_error("Cannot access ...",
463 // but o_set will skip writing and return normally.
465 auto const lookup
= getPropImpl
<true, false, true>(propCtx
, propName
.get());
466 auto prop
= lookup
.val
;
467 if (prop
&& lookup
.accessible
) {
468 if (UNLIKELY(lookup
.isConst
) && !isBeingConstructed()) {
469 throwMutateConstProp(lookup
.slot
);
471 auto val
= tvToInit(*v
.asTypedValue());
472 verifyTypeHint(m_cls
, lookup
.prop
, &val
);
478 setDynProp(propName
.get(), tvToInit(*v
.asTypedValue()));
482 void ObjectData::o_setArray(const Array
& properties
) {
483 for (ArrayIter
iter(properties
); iter
; ++iter
) {
484 String k
= iter
.first().toString();
485 Class
* ctx
= nullptr;
486 // If the key begins with a NUL, it's a private or protected property. Read
487 // the class name from between the two NUL bytes.
489 // Note: if you change this, you need to change similar logic in
491 if (!k
.empty() && k
[0] == '\0') {
492 int subLen
= k
.find('\0', 1) + 1;
493 String cls
= k
.substr(1, subLen
- 2);
494 if (cls
.size() == 1 && cls
[0] == '*') {
499 ctx
= Class::lookup(cls
.get());
502 k
= k
.substr(subLen
);
504 // TODO(T126821336): property can be internal
505 // This function is only used in ext_mysql.cpp,
506 // but has its own encoding mechanism
507 auto const propCtx
= MemberLookupContext(ctx
);
508 setProp(propCtx
, k
.get(), tvAssertPlausible(iter
.secondVal()));
512 void ObjectData::o_getArray(Array
& props
,
513 bool pubOnly
/* = false */,
514 bool ignoreLateInit
/* = false */) const {
515 assertx(kindIsValid());
517 // Fast path for classes with no declared properties
518 if (!m_cls
->numDeclProperties() && getAttribute(HasDynPropArr
)) {
519 props
= dynPropArray();
520 if (RuntimeOption::EvalNoticeOnReadDynamicProp
) {
521 IterateKV(props
.get(), [&](TypedValue k
, TypedValue
) {
522 auto const key
= tvCastToString(k
);
523 raiseReadDynamicProp(key
.get());
530 if (cls
->hasReifiedGenerics()) {
531 auto const slot
= cls
->lookupReifiedInitProp();
532 assertx(slot
!= kInvalidSlot
);
533 auto const declProps
= cls
->declProperties();
534 auto const prop
= declProps
[slot
];
535 auto val
= this->propRvalAtOffset(slot
);
536 props
.set(StrNR(prop
.name
).asString(), val
.tv());
538 IteratePropToArrayOrder(
540 [&](Slot slot
, const Class::Prop
& prop
, tv_rval val
) {
541 assertx(assertTypeHint(val
, slot
));
542 if (UNLIKELY(val
.type() == KindOfUninit
)) {
543 if (!ignoreLateInit
&& (prop
.attrs
& AttrLateInit
)) {
544 throw_late_init_prop(prop
.cls
, prop
.name
, false);
546 } else if (!pubOnly
|| (prop
.attrs
& AttrPublic
)) {
547 // Skip all the reified properties since we already prepended the
548 // current class' reified property to the list
549 if (prop
.name
!= s_86reified_prop
.get()) {
550 props
.set(StrNR(prop
.mangledName
).asString(), val
.tv());
554 [&](TypedValue key_tv
, TypedValue val
) {
555 props
.set(key_tv
, val
, true);
556 if (RuntimeOption::EvalNoticeOnReadDynamicProp
) {
557 auto const key
= tvCastToString(key_tv
);
558 raiseReadDynamicProp(key
.get());
563 if (m_cls
->needsInitThrowable()) {
564 throwable_mark_array(this, props
);
568 template <IntishCast IC
/* = IntishCast::None */>
569 Array
ObjectData::toArray(bool pubOnly
/* = false */,
570 bool ignoreLateInit
/* = false */) const {
571 assertx(kindIsValid());
573 // We can quickly tell if this object is a collection, which lets us avoid
574 // checking for each class in turn if it's not one.
575 if (isCollection()) {
576 return collections::toArray
<IC
>(this);
577 } else if (UNLIKELY(m_cls
->rtAttribute(Class::CallToImpl
))) {
578 // If we end up with other classes that need special behavior, turn the
579 // assert into an if and add cases.
580 assertx(instanceof(SimpleXMLElement_classof()));
581 if (RuntimeOption::EvalNoticeOnSimpleXMLBehavior
) {
582 raise_notice("SimpleXMLElement to array cast");
584 return SimpleXMLElement_darrayCast(this);
585 } else if (UNLIKELY(instanceof(SystemLib::s_ArrayIteratorClass
))) {
586 SystemLib::throwInvalidOperationExceptionObject(
587 "ArrayIterator to array cast"
590 } else if (UNLIKELY(instanceof(c_Closure::classof()))) {
591 return make_vec_array(Object(const_cast<ObjectData
*>(this)));
592 } else if (UNLIKELY(instanceof(DateTimeData::getClass()))) {
593 return Native::data
<DateTimeData
>(this)->getDebugInfo();
595 auto ret
= Array::CreateDict();
596 o_getArray(ret
, pubOnly
, ignoreLateInit
);
602 Array
ObjectData::toArray
<IntishCast::None
>(bool, bool) const;
604 Array
ObjectData::toArray
<IntishCast::Cast
>(bool, bool) const;
609 size_t getPropertyIfAccessible(ObjectData
* obj
,
610 const MemberLookupContext
& ctx
,
611 const StringData
* key
,
614 auto const prop
= obj
->getProp(ctx
, key
);
615 if (prop
&& prop
.type() != KindOfUninit
) {
617 properties
.set(StrNR(key
), prop
.tv(), true);
624 Array
ObjectData::o_toIterArray(const String
& context
) {
625 if (!m_cls
->numDeclProperties()) {
626 if (getAttribute(HasDynPropArr
)) {
627 auto const props
= dynPropArray();
628 if (RuntimeOption::EvalNoticeOnReadDynamicProp
) {
629 IterateKV(props
.get(), [&](TypedValue k
, TypedValue
) {
630 auto const key
= tvCastToString(k
);
631 raiseReadDynamicProp(key
.get());
634 // not returning Array&; makes a copy
637 return Array::CreateDict();
640 size_t accessibleProps
= m_cls
->declPropNumAccessible();
641 size_t size
= accessibleProps
;
642 if (getAttribute(HasDynPropArr
)) {
643 size
+= dynPropArray().size();
645 Array retArray
{ Array::attach(VanillaDict::MakeReserveDict(size
)) };
647 Class
* ctx
= nullptr;
648 if (!context
.empty()) {
649 ctx
= Class::lookup(context
.get());
651 auto const propCtx
= MemberLookupContext(ctx
);
653 // Get all declared properties first, bottom-to-top in the inheritance
654 // hierarchy, in declaration order.
655 const Class
* klass
= m_cls
;
657 const PreClass::Prop
* props
= klass
->preClass()->properties();
658 const size_t numProps
= klass
->preClass()->numProperties();
660 for (size_t i
= 0; i
< numProps
; ++i
) {
661 auto key
= const_cast<StringData
*>(props
[i
].name());
662 accessibleProps
= getPropertyIfAccessible(
663 this, propCtx
, key
, retArray
, accessibleProps
);
665 klass
= klass
->parent();
667 if (!(m_cls
->attrs() & AttrNoExpandTrait
) && accessibleProps
> 0) {
668 // we may have properties from traits
669 for (auto const& prop
: m_cls
->declProperties()) {
670 auto const key
= prop
.name
.get();
671 if (!retArray
.get()->exists(key
)) {
672 accessibleProps
= getPropertyIfAccessible(
673 this, propCtx
, key
, retArray
, accessibleProps
);
674 if (accessibleProps
== 0) break;
679 // Now get dynamic properties.
680 if (getAttribute(HasDynPropArr
)) {
681 auto& dynProps
= dynPropArray();
682 auto ad
= dynProps
.get();
683 ssize_t iter
= ad
->iter_begin();
684 auto pos_limit
= ad
->iter_end();
685 while (iter
!= pos_limit
) {
687 auto const key
= ad
->nvGetKey(iter
);
688 iter
= ad
->iter_advance(iter
);
690 if (RuntimeOption::EvalNoticeOnReadDynamicProp
) {
691 auto const k
= tvCastToString(key
);
692 raiseReadDynamicProp(k
.get());
695 // You can get this if you cast an array to object. These
696 // properties must be dynamic because you can't declare a
697 // property with a non-string name.
698 if (UNLIKELY(!isStringType(key
.m_type
))) {
699 assertx(key
.m_type
== KindOfInt64
);
700 auto const val
= dynProps
.get()->at(key
.m_data
.num
);
701 retArray
.set(key
.m_data
.num
, val
);
705 auto const strKey
= key
.m_data
.pstr
;
706 auto const val
= dynProps
.get()->at(strKey
);
707 retArray
.set(StrNR(strKey
), val
, true /* isKey */);
714 static bool decode_invoke(const String
& s
, ObjectData
* obj
, bool fatal
,
717 ctx
.cls
= obj
->getVMClass();
720 ctx
.func
= ctx
.cls
->lookupMethod(s
.get());
722 // Bail if this_ is non-null AND we could not find a method.
723 o_invoke_failed(ctx
.cls
->name()->data(), s
.data(), fatal
);
727 // Null out this_ for statically called methods
728 if (ctx
.func
->isStaticInPrologue()) {
734 Variant
ObjectData::o_invoke(const String
& s
, const Variant
& params
,
735 bool fatal
/* = true */) {
737 if (!decode_invoke(s
, this, fatal
, ctx
) ||
738 (!isContainer(params
) && !params
.isNull())) {
739 return Variant(Variant::NullInit());
741 CoeffectsAutoGuard _
;
742 return Variant::attach(
743 g_context
->invokeFunc(ctx
, params
, RuntimeCoeffects::automatic())
747 Variant
ObjectData::o_invoke_few_args(const String
& s
,
748 RuntimeCoeffects providedCoeffects
,
750 const Variant
& a0
/* = uninit_variant*/,
751 const Variant
& a1
/* = uninit_variant*/,
752 const Variant
& a2
/* = uninit_variant*/,
753 const Variant
& a3
/* = uninit_variant*/,
754 const Variant
& a4
/* = uninit_variant*/) {
757 if (!decode_invoke(s
, this, true, ctx
)) {
758 return Variant(Variant::NullInit());
763 default: not_implemented();
764 case 5: tvCopy(*a4
.asTypedValue(), args
[4]);
765 case 4: tvCopy(*a3
.asTypedValue(), args
[3]);
766 case 3: tvCopy(*a2
.asTypedValue(), args
[2]);
767 case 2: tvCopy(*a1
.asTypedValue(), args
[1]);
768 case 1: tvCopy(*a0
.asTypedValue(), args
[0]);
772 return Variant::attach(
773 g_context
->invokeFuncFew(ctx
, count
, args
, providedCoeffects
)
777 ObjectData
* ObjectData::clone() {
778 if (isCppBuiltin()) {
779 assertx(!m_cls
->hasMemoSlots());
780 if (isCollection()) return collections::clone(this);
781 if (instanceof(c_Closure::classof())) {
782 return c_Closure::fromObject(this)->clone();
784 assertx(instanceof(c_Awaitable::classof()));
785 // cloning WaitHandles is not allowed
786 // invoke the instanceCtor to get the right sort of exception
787 auto const ctor
= m_cls
->instanceCtor();
789 always_assert(false);
792 // clone prevents a leak if something throws before clone() returns
794 auto const nProps
= m_cls
->numDeclProperties();
795 if (hasNativeData()) {
796 assertx(m_cls
->instanceDtor() == Native::nativeDataInstanceDtor
);
797 clone
= Object::attach(
798 Native::nativeDataInstanceCopyCtor(this, m_cls
, nProps
)
800 assertx(clone
->hasExactlyOneRef());
801 assertx(clone
->hasInstanceDtor());
803 auto const alloc
= allocMemoInit(m_cls
);
805 auto const obj
= new (NotNull
{}, alloc
.mem
)
806 ObjectData(m_cls
, InitRaw
{}, alloc
.flags
);
807 clone
= Object::attach(obj
);
808 assertx(clone
->hasExactlyOneRef());
809 assertx(!clone
->hasInstanceDtor());
812 auto const cloneProps
= clone
->props();
813 cloneProps
->init(m_cls
->numDeclProperties());
814 for (auto slot
= Slot
{0}; slot
< nProps
; slot
++) {
815 auto index
= m_cls
->propSlotToIndex(slot
);
816 auto const prop
= props()->at(index
);
817 if (!isLazyProp(prop
)) {
818 tvDup(*prop
, cloneProps
->at(index
));
819 assertx(assertTypeHint(cloneProps
->at(index
), slot
));
821 auto const cloneProp
= cloneProps
->at(index
);
822 type(cloneProp
) = type(prop
);
823 val(cloneProp
).num
= val(prop
).num
;
827 if (UNLIKELY(getAttribute(HasDynPropArr
))) {
828 clone
->setAttribute(HasDynPropArr
);
829 g_context
->dynPropTable
.emplace(clone
.get(), dynPropArray().get());
831 if (m_cls
->rtAttribute(Class::HasClone
)) {
832 assertx(!isCppBuiltin());
833 auto const method
= clone
->m_cls
->lookupMethod(s_clone
.get());
835 clone
->unlockObject();
836 SCOPE_EXIT
{ clone
->lockObject(); };
837 CoeffectsAutoGuard _
;
838 g_context
->invokeMethodV(clone
.get(), method
, InvokeArgs
{},
839 RuntimeCoeffects::automatic());
841 return clone
.detach();
844 bool ObjectData::equal(const ObjectData
& other
) const {
845 if (this == &other
) return true;
846 if (isCollection()) {
847 return collections::equals(this, &other
);
849 if (UNLIKELY(instanceof(SystemLib::s_DateTimeInterfaceClass
) &&
850 other
.instanceof(SystemLib::s_DateTimeInterfaceClass
))) {
851 return DateTimeData::compare(this, &other
) == 0;
853 if (getVMClass() != other
.getVMClass()) return false;
854 if (UNLIKELY(instanceof(SimpleXMLElement_classof()))) {
855 if (RuntimeOption::EvalNoticeOnSimpleXMLBehavior
) {
856 raise_notice("SimpleXMLElement equality comparison");
858 // Compare the whole object (including native data), not just props
859 auto ar1
= SimpleXMLElement_darrayCast(this);
860 auto ar2
= SimpleXMLElement_darrayCast(&other
);
861 return ArrayData::Equal(ar1
.get(), ar2
.get());
863 if (UNLIKELY(instanceof(c_Closure::classof()))) {
864 // First comparison already proves they are different
868 // check for dynamic props first because we need to short-circuit if there's
869 // a different number of them
870 auto thisSize
= UNLIKELY(getAttribute(HasDynPropArr
)) ?
871 dynPropArray().size() : 0;
872 size_t otherSize
= 0;
873 ArrayData
* otherDynProps
= nullptr;
874 if (UNLIKELY(other
.getAttribute(HasDynPropArr
))) {
875 otherDynProps
= other
.dynPropArray().get();
876 otherSize
= otherDynProps
->size();
878 if (thisSize
!= otherSize
) return false;
880 // Prevent circular referenced objects/arrays or deep ones.
881 check_recursion_error();
886 [&](Slot slot
, const Class::Prop
& prop
, tv_rval thisVal
) {
887 auto otherVal
= other
.propRvalAtOffset(slot
);
888 if ((UNLIKELY(thisVal
.type() == KindOfUninit
) ||
889 UNLIKELY(otherVal
.type() == KindOfUninit
)) &&
890 (prop
.attrs
& AttrLateInit
)) {
891 throw_late_init_prop(prop
.cls
, prop
.name
, false);
893 if (!tvEqual(thisVal
.tv(), otherVal
.tv())) {
899 [&](TypedValue key
, TypedValue thisVal
) {
900 auto const otherVal
= otherDynProps
->get(key
);
901 if (!otherVal
.is_init() || !tvEqual(thisVal
, otherVal
)) {
911 bool ObjectData::less(const ObjectData
& other
) const {
912 // compare is not symmetrical; order of operands matters here
913 return compare(other
) < 0;
916 bool ObjectData::lessEqual(const ObjectData
& other
) const {
917 // compare is not symmetrical; order of operands matters here
918 return compare(other
) <= 0;
921 bool ObjectData::more(const ObjectData
& other
) const {
922 // compare is not symmetrical; order of operands matters here
923 return other
.compare(*this) < 0;
926 bool ObjectData::moreEqual(const ObjectData
& other
) const {
927 // compare is not symmetrical; order of operands matters here
928 return other
.compare(*this) <= 0;
931 int64_t ObjectData::compare(const ObjectData
& other
) const {
932 if (isCollection() || other
.isCollection()) {
933 throw_collection_compare_exception();
935 if (this == &other
) return 0;
936 if (UNLIKELY(instanceof(SystemLib::s_DateTimeInterfaceClass
) &&
937 other
.instanceof(SystemLib::s_DateTimeInterfaceClass
))) {
938 return DateTimeData::compare(this, &other
);
940 if (getVMClass() != other
.getVMClass()) {
941 const auto lhs
= make_tv
<DataType::Object
>(const_cast<ObjectData
*>(this));
942 const auto rhs
= make_tv
<DataType::Object
>(const_cast<ObjectData
*>(&other
));
943 throwCmpBadTypesException(&lhs
, &rhs
);
946 if (UNLIKELY(instanceof(SimpleXMLElement_classof()))) {
947 if (RuntimeOption::EvalNoticeOnSimpleXMLBehavior
) {
948 raise_notice("SimpleXMLElement comparison");
950 // Compare the whole object (including native data), not just props
951 auto ar1
= SimpleXMLElement_darrayCast(this);
952 auto ar2
= SimpleXMLElement_darrayCast(&other
);
953 return ArrayData::Compare(ar1
.get(), ar2
.get());
955 if (UNLIKELY(instanceof(c_Closure::classof()))) {
956 // comparing different closures with <=> always returns 1
960 // check for dynamic props first, because we need to short circuit if there's
961 // a different number of them
962 auto thisSize
= UNLIKELY(getAttribute(HasDynPropArr
)) ?
963 dynPropArray().size() : 0;
964 size_t otherSize
= 0;
965 ArrayData
* otherDynProps
= nullptr;
966 if (UNLIKELY(other
.getAttribute(HasDynPropArr
))) {
967 otherDynProps
= other
.dynPropArray().get();
968 otherSize
= otherDynProps
->size();
970 if (thisSize
> otherSize
) {
972 } else if (thisSize
< otherSize
) {
976 // Prevent circular referenced objects/arrays or deep ones.
977 check_recursion_error();
980 IteratePropToArrayOrder(
982 [&](Slot slot
, const Class::Prop
& prop
, tv_rval thisVal
) {
983 auto otherVal
= other
.propRvalAtOffset(slot
);
984 if ((UNLIKELY(thisVal
.type() == KindOfUninit
) ||
985 UNLIKELY(otherVal
.type() == KindOfUninit
)) &&
986 (prop
.attrs
& AttrLateInit
)) {
987 throw_late_init_prop(prop
.cls
, prop
.name
, false);
989 auto cmp
= tvCompare(thisVal
.tv(), otherVal
.tv());
996 [&](TypedValue key
, TypedValue thisVal
) {
997 auto const otherVal
= otherDynProps
->get(key
);
998 if (!otherVal
.is_init()) {
1002 auto cmp
= tvCompare(thisVal
, otherVal
);
1013 ///////////////////////////////////////////////////////////////////////////////
1016 s___sleep("__sleep"),
1017 s___toDebugDisplay("__toDebugDisplay"),
1018 s___wakeup("__wakeup"),
1019 s___debugInfo("__debugInfo");
1021 void deepInitHelper(ObjectProps
* props
,
1022 const Class::PropInitVec
* initVec
,
1024 auto initIter
= initVec
->cbegin();
1025 props
->init(nProps
);
1026 props
->foreach(nProps
, [&](tv_lval lval
){
1027 auto entry
= *initIter
++;
1028 tvCopy(entry
.val
.tv(), lval
);
1029 if (entry
.deepInit
) {
1031 collections::deepCopy(lval
);
1036 void ObjectData::setReifiedGenerics(Class
* cls
, ArrayData
* reifiedTypes
) {
1037 auto const arg
= make_array_like_tv(reifiedTypes
);
1038 auto const meth
= cls
->lookupMethod(s_86reifiedinit
.get());
1039 assertx(meth
!= nullptr);
1040 g_context
->invokeMethod(this, meth
, InvokeArgs(&arg
, 1),
1041 RuntimeCoeffects::fixme());
1044 // called from jit code
1045 ObjectData
* ObjectData::newInstanceRawSmall(Class
* cls
, size_t size
,
1047 assertx(size
<= kMaxSmallSize
);
1048 assertx(!cls
->hasMemoSlots());
1049 assertx(cls
->sizeIdx() == index
);
1050 auto mem
= tl_heap
->mallocSmallIndexSize(index
, size
);
1051 auto const flags
= IsBeingConstructed
| SmallAllocSize
;
1052 return new (NotNull
{}, mem
) ObjectData(cls
, InitRaw
{}, flags
);
1055 ObjectData
* ObjectData::newInstanceRawBig(Class
* cls
, size_t size
) {
1056 assertx(!cls
->hasMemoSlots());
1057 auto mem
= tl_heap
->mallocBigSize(size
);
1058 auto const flags
= IsBeingConstructed
| BigAllocSize
;
1059 return new (NotNull
{}, mem
) ObjectData(cls
, InitRaw
{}, flags
);
1062 // called from jit code
1063 ObjectData
* ObjectData::newInstanceRawMemoSmall(Class
* cls
,
1067 assertx(size
<= kMaxSmallSize
);
1068 assertx(cls
->hasMemoSlots());
1069 assertx(!cls
->getNativeDataInfo());
1070 assertx(objoff
== ObjectData::objOffFromMemoNode(cls
));
1071 assertx(cls
->sizeIdx() == index
);
1072 auto mem
= tl_heap
->mallocSmallIndexSize(index
, size
);
1073 new (NotNull
{}, mem
) MemoNode(objoff
);
1074 mem
= reinterpret_cast<char*>(mem
) + objoff
;
1075 auto const flags
= IsBeingConstructed
| SmallAllocSize
;
1076 return new (NotNull
{}, mem
) ObjectData(cls
, InitRaw
{}, flags
);
1079 ObjectData
* ObjectData::newInstanceRawMemoBig(Class
* cls
,
1082 assertx(cls
->hasMemoSlots());
1083 assertx(!cls
->getNativeDataInfo());
1084 assertx(objoff
== ObjectData::objOffFromMemoNode(cls
));
1085 auto mem
= tl_heap
->mallocBigSize(size
);
1086 new (NotNull
{}, mem
) MemoNode(objoff
);
1087 mem
= reinterpret_cast<char*>(mem
) + objoff
;
1088 auto const flags
= IsBeingConstructed
| BigAllocSize
;
1089 return new (NotNull
{}, mem
) ObjectData(cls
, InitRaw
{}, flags
);
1092 // Note: the normal object destruction path does not actually call this
1093 // destructor. See ObjectData::release.
1094 ObjectData::~ObjectData() {
1095 if (UNLIKELY(slowDestroyCheck())) {
1096 // The only builtin classes that use ~ObjectData and support memoization
1097 // are ones with native data, and the memo slot cleanup for them happens
1098 // in nativeDataInstanceDtor.
1099 assertx(!getAttribute(UsedMemoCache
) || hasNativeData());
1100 if (getAttribute(HasDynPropArr
)) freeDynPropArray(this);
1101 if (getAttribute(IsWeakRefed
)) {
1102 WeakRefData::invalidateWeakRef((uintptr_t)this);
1107 Object
ObjectData::FromArray(ArrayData
* properties
) {
1108 auto const props
= properties
->toDict(true);
1109 Object retval
{SystemLib::s_stdClassClass
};
1110 retval
->setAttribute(HasDynPropArr
);
1111 g_context
->dynPropTable
.emplace(retval
.get(), props
);
1112 if (props
!= properties
) decRefArr(props
);
1116 void ObjectData::throwMutateConstProp(Slot prop
) const {
1117 throw_cannot_modify_const_prop(
1118 getClassName().data(),
1119 m_cls
->declProperties()[prop
].name
->data()
1123 void ObjectData::throwMustBeMutable(Slot prop
) const {
1124 throw_must_be_mutable(
1125 getClassName().data(),
1126 m_cls
->declProperties()[prop
].name
->data()
1130 void ObjectData::throwMustBeEnclosedInReadonly(Slot prop
) const {
1131 throw_must_be_enclosed_in_readonly(
1132 getClassName().data(),
1133 m_cls
->declProperties()[prop
].name
->data()
1137 void ObjectData::throwMustBeReadonly(Slot prop
) const {
1138 throw_must_be_readonly(
1139 getClassName().data(),
1140 m_cls
->declProperties()[prop
].name
->data()
1144 void ObjectData::throwMustBeValueType(Slot prop
) const {
1145 throw_must_be_value_type(
1146 getClassName().data(),
1147 m_cls
->declProperties()[prop
].name
->data()
1151 void ObjectData::checkReadonly(const PropLookup
& lookup
, ReadonlyOp op
,
1152 bool writeMode
) const {
1153 if ((op
== ReadonlyOp::CheckMutROCOW
&& lookup
.readonly
) ||
1154 op
== ReadonlyOp::CheckROCOW
) {
1155 vmMInstrState().roProp
= true;
1157 if (lookup
.readonly
) {
1158 if (op
== ReadonlyOp::CheckMutROCOW
|| op
== ReadonlyOp::CheckROCOW
) {
1159 if (type(lookup
.val
) == KindOfObject
) {
1160 throwMustBeValueType(lookup
.slot
);
1162 } else if (op
== ReadonlyOp::Mutable
) {
1164 throwMustBeMutable(lookup
.slot
);
1166 throwMustBeEnclosedInReadonly(lookup
.slot
);
1169 } else if (op
== ReadonlyOp::Readonly
|| op
== ReadonlyOp::CheckROCOW
) {
1170 throwMustBeReadonly(lookup
.slot
);
1174 void ObjectData::deserializeAllLazyProps() {
1175 if (!m_cls
->currentlyUsingLazyAPCDeserialization()) return;
1176 props()->foreach(m_cls
->numDeclProperties(), [&](tv_lval lval
) {
1177 if (isLazyProp(lval
)) deserializeLazyProp(lval
);
1181 void ObjectData::deserializeLazyProp(tv_lval prop
) {
1182 assertx(isLazyProp(prop
));
1183 auto const handle
= reinterpret_cast<APCHandle
*>(prop
.val().num
);
1184 assertx(handle
->checkInvariants());
1185 tvCopy(handle
->toLocalHelper(false).detach(), prop
);
1188 bool ObjectData::isLazyProp(tv_rval prop
) {
1189 return prop
.type() == kInvalidDataType
;
1192 template <bool forWrite
, bool forRead
, bool ignoreLateInit
>
1194 ObjectData::PropLookup
ObjectData::getPropImpl(
1195 const MemberLookupContext
& propCtx
,
1196 const StringData
* key
1198 auto const lookup
= m_cls
->getDeclPropSlot(propCtx
, key
);
1199 auto const propSlot
= lookup
.slot
;
1201 if (LIKELY(propSlot
!= kInvalidSlot
)) {
1202 // We found a visible property in one of the object's slots. Immediately
1203 // deserialize it if it's a lazy prop. Then, check if it's accessible.
1204 auto const propIndex
= m_cls
->propSlotToIndex(propSlot
);
1205 auto prop
= props()->at(propIndex
);
1206 if (isLazyProp(prop
)) deserializeLazyProp(prop
);
1207 assertx(assertTypeHint(prop
, propSlot
));
1209 auto const& declProp
= m_cls
->declProperties()[propSlot
];
1210 if (!ignoreLateInit
&& lookup
.accessible
) {
1211 if (UNLIKELY(type(prop
) == KindOfUninit
) &&
1212 (declProp
.attrs
& AttrLateInit
)) {
1213 throw_late_init_prop(declProp
.cls
, key
, false);
1217 // If the prop is internal, check that modules are compatible
1218 if (lookup
.internal
&&
1219 will_symbol_raise_module_boundary_violation(&declProp
, &propCtx
)) {
1220 raiseModulePropertyViolation(m_cls
, key
, propCtx
.moduleName(), false);
1228 // we always return true in the !forWrite case; this way the compiler
1229 // may optimize away this value, and if a caller intends to write but
1230 // instantiates with false by mistake it will always see const
1232 ? bool(declProp
.attrs
& AttrIsConst
)
1238 // We could not find a visible declared property. We need to check for a
1239 // dynamic property with this name.
1240 if (UNLIKELY(getAttribute(HasDynPropArr
))) {
1241 auto& arr
= dynPropArray();
1242 if (arr
->exists(key
)) {
1243 if (forRead
&& RuntimeOption::EvalNoticeOnReadDynamicProp
) {
1244 raiseReadDynamicProp(key
);
1246 // Returning a non-declared property. We know that it is accessible and
1247 // not const since all dynamic properties are. If we may write to
1248 // the property we need to allow the array to escalate.
1249 auto const lval
= arr
.lval(StrNR(key
), AccessFlags::Key
);
1250 return { lval
, nullptr, kInvalidSlot
, true, !forWrite
, false };
1254 return { nullptr, nullptr, kInvalidSlot
, false, !forWrite
, false };
1257 tv_lval
ObjectData::getPropLval(const MemberLookupContext
& ctx
, const StringData
* key
) {
1258 auto const lookup
= getPropImpl
<true, false, true>(ctx
, key
);
1259 if (UNLIKELY(lookup
.isConst
) && !isBeingConstructed()) {
1260 throwMutateConstProp(lookup
.slot
);
1262 return lookup
.val
&& lookup
.accessible
? lookup
.val
: nullptr;
1265 tv_rval
ObjectData::getProp(const MemberLookupContext
& ctx
, const StringData
* key
) const {
1266 auto const lookup
= const_cast<ObjectData
*>(this)
1267 ->getPropImpl
<false, true, false>(ctx
, key
);
1268 return lookup
.val
&& lookup
.accessible
? lookup
.val
: nullptr;
1271 tv_rval
ObjectData::getPropIgnoreLateInit(const MemberLookupContext
& ctx
,
1272 const StringData
* key
) const {
1273 auto const lookup
= const_cast<ObjectData
*>(this)
1274 ->getPropImpl
<false, true, true>(ctx
, key
);
1275 return lookup
.val
&& lookup
.accessible
? lookup
.val
: nullptr;
1278 tv_lval
ObjectData::getPropIgnoreAccessibility(const StringData
* key
) {
1279 auto const lookup
= getPropImpl
<false, true, true>(MemberLookupContext(nullptr, (const StringData
*) nullptr), key
);
1280 auto prop
= lookup
.val
;
1281 if (!prop
) return nullptr;
1282 if (lookup
.prop
&& type(prop
) == KindOfUninit
&&
1283 (lookup
.prop
->attrs
& AttrLateInit
)) {
1284 throw_late_init_prop(lookup
.prop
->cls
, key
, false);
1289 //////////////////////////////////////////////////////////////////////
1291 template<ObjectData::PropMode mode
>
1293 tv_lval
ObjectData::propImpl(TypedValue
* tvRef
, const MemberLookupContext
& ctx
,
1294 const StringData
* key
, const ReadonlyOp op
) {
1295 auto constexpr write
= (mode
== PropMode::DimForWrite
);
1296 auto constexpr read
= (mode
== PropMode::ReadNoWarn
) ||
1297 (mode
== PropMode::ReadWarn
);
1298 auto const lookup
= getPropImpl
<write
, read
, false>(ctx
, key
);
1299 auto const prop
= lookup
.val
;
1301 if (lookup
.accessible
) {
1302 auto const checkPropAttrs
= [&]() {
1303 if (mode
== PropMode::DimForWrite
) {
1304 if (UNLIKELY(lookup
.isConst
) && !isBeingConstructed()) {
1305 throwMutateConstProp(lookup
.slot
);
1308 checkReadonly(lookup
, op
, mode
== PropMode::DimForWrite
);
1312 // Property exists, is accessible, and is not unset.
1313 if (type(prop
) != KindOfUninit
) return checkPropAttrs();
1315 if (mode
== PropMode::ReadWarn
) throwUndefPropException(key
);
1316 if (write
) return checkPropAttrs();
1317 return const_cast<TypedValue
*>(&immutable_null_base
);
1320 // Property exists, but it is either protected or private since accessible
1322 auto const propSlot
= m_cls
->lookupDeclProp(key
);
1323 auto const attrs
= m_cls
->declProperties()[propSlot
].attrs
;
1324 auto const priv
= (attrs
& AttrPrivate
) ? "private" : "protected";
1327 "Cannot access %s property %s::$%s",
1329 m_cls
->preClass()->name()->data(),
1334 // First see if native getter is implemented.
1335 if (m_cls
->rtAttribute(Class::HasNativePropHandler
)) {
1336 auto r
= Native::getProp(Object
{this}, StrNR(key
));
1337 if (r
.isInitialized()) {
1338 tvCopy(r
.detach(), *tvRef
);
1343 if (UNLIKELY(!*key
->data())) {
1344 throw_invalid_property_name(StrNR(key
));
1347 if (mode
== PropMode::ReadWarn
) throwUndefPropException(key
);
1348 if (write
) return makeDynProp(key
);
1349 return const_cast<TypedValue
*>(&immutable_null_base
);
1352 tv_lval
ObjectData::prop(
1354 const MemberLookupContext
& ctx
,
1355 const StringData
* key
,
1358 return propImpl
<PropMode::ReadNoWarn
>(tvRef
, ctx
, key
, op
);
1361 tv_lval
ObjectData::propW(
1363 const MemberLookupContext
& ctx
,
1364 const StringData
* key
,
1367 return propImpl
<PropMode::ReadWarn
>(tvRef
, ctx
, key
, op
);
1370 tv_lval
ObjectData::propU(
1372 const MemberLookupContext
& ctx
,
1373 const StringData
* key
,
1376 return propImpl
<PropMode::DimForWrite
>(tvRef
, ctx
, key
, op
);
1379 tv_lval
ObjectData::propD(
1381 const MemberLookupContext
& ctx
,
1382 const StringData
* key
,
1385 return propImpl
<PropMode::DimForWrite
>(tvRef
, ctx
, key
, op
);
1388 bool ObjectData::propIsset(const MemberLookupContext
& ctx
, const StringData
* key
) {
1389 auto const lookup
= getPropImpl
<false, true, true>(ctx
, key
);
1390 if (lookup
.val
&& lookup
.accessible
) {
1391 if (lookup
.val
.type() != KindOfUninit
) {
1392 return lookup
.val
.type() != KindOfNull
;
1394 if (lookup
.prop
&& (lookup
.prop
->attrs
& AttrLateInit
)) {
1399 if (m_cls
->rtAttribute(Class::HasNativePropHandler
)) {
1400 auto r
= Native::issetProp(Object
{this}, StrNR(key
));
1401 if (r
.isInitialized()) return r
.toBoolean();
1407 void ObjectData::setProp(const MemberLookupContext
& ctx
, const StringData
* key
, TypedValue val
, ReadonlyOp op
) {
1408 assertx(tvIsPlausible(val
));
1409 assertx(val
.m_type
!= KindOfUninit
);
1411 auto const lookup
= getPropImpl
<true, false, true>(ctx
, key
);
1412 auto const prop
= lookup
.val
;
1414 if (prop
&& lookup
.accessible
) {
1415 if (UNLIKELY(lookup
.isConst
) && !isBeingConstructed()) {
1416 throwMutateConstProp(lookup
.slot
);
1418 checkReadonly(lookup
, op
, true);
1419 // TODO(T61738946): We can remove the temporary here once we no longer
1420 // coerce class_meth types.
1421 Variant tmp
= tvAsVariant(&val
);
1422 verifyTypeHint(m_cls
, lookup
.prop
, tmp
.asTypedValue());
1423 tvMove(tmp
.detach(), prop
);
1427 // First see if native setter is implemented.
1428 if (m_cls
->rtAttribute(Class::HasNativePropHandler
)) {
1429 auto r
= Native::setProp(Object
{this}, StrNR(key
), tvAsCVarRef(&val
));
1430 if (r
.isInitialized()) return;
1433 if (prop
) raise_error("Cannot access protected property");
1435 if (UNLIKELY(!*key
->data())) {
1436 throw_invalid_property_name(StrNR(key
));
1438 setDynProp(key
, val
);
1441 tv_lval
ObjectData::setOpProp(TypedValue
& tvRef
,
1442 const MemberLookupContext
& ctx
,
1444 const StringData
* key
,
1446 auto const lookup
= getPropImpl
<true, true, false>(ctx
, key
);
1447 auto prop
= lookup
.val
;
1449 if (prop
&& lookup
.accessible
) {
1450 if (UNLIKELY(lookup
.isConst
) && !isBeingConstructed()) {
1451 throwMutateConstProp(lookup
.slot
);
1454 auto const needsCheck
= lookup
.prop
&& [&] {
1455 auto const& tc
= lookup
.prop
->typeConstraint
;
1456 if (setOpNeedsTypeCheck(tc
, op
, prop
)) {
1459 for (auto& ub
: lookup
.prop
->ubs
) {
1460 if (setOpNeedsTypeCheck(ub
, op
, prop
)) return true;
1467 * If this property has a type-hint, we can't do the setop truly in
1468 * place. We need to verify that the new value satisfies the type-hint
1469 * before assigning back to the property (if we raise a warning and throw,
1470 * we don't want to have already put the value into the prop).
1474 SCOPE_FAIL
{ tvDecRefGen(&temp
); };
1475 setopBody(&temp
, op
, val
);
1476 verifyTypeHint(m_cls
, lookup
.prop
, &temp
);
1479 setopBody(prop
, op
, val
);
1484 if (UNLIKELY(!*key
->data())) throw_invalid_property_name(StrNR(key
));
1486 // Native accessors.
1487 if (m_cls
->rtAttribute(Class::HasNativePropHandler
)) {
1488 auto r
= Native::getProp(Object
{this}, StrNR(key
));
1489 if (r
.isInitialized()) {
1490 setopBody(r
.asTypedValue(), op
, val
);
1491 auto r2
= Native::setProp(Object
{this}, StrNR(key
), r
);
1492 if (r2
.isInitialized()) {
1493 tvCopy(r
.detach(), tvRef
);
1499 if (prop
) raise_error("Cannot access protected property");
1501 // No visible/accessible property, and no applicable native method:
1502 // create a new dynamic property. (We know this is a new property,
1503 // or it would've hit the visible && accessible case above.)
1504 prop
= makeDynProp(key
);
1505 assertx(type(prop
) == KindOfNull
); // cannot exist yet
1506 setopBody(prop
, op
, val
);
1510 TypedValue
ObjectData::incDecProp(const MemberLookupContext
& ctx
, IncDecOp op
, const StringData
* key
) {
1511 auto const lookup
= getPropImpl
<true, true, false>(ctx
, key
);
1512 auto prop
= lookup
.val
;
1514 if (prop
&& lookup
.accessible
) {
1515 if (UNLIKELY(lookup
.isConst
) && !isBeingConstructed()) {
1516 throwMutateConstProp(lookup
.slot
);
1518 if (type(prop
) == KindOfUninit
) {
1523 * If this property has a type-hint, we can't do the inc-dec truely in
1524 * place. We need to verify that the new value satisfies the type-hint
1525 * before assigning back to the property (if we raise a warning and throw,
1526 * we don't want to have already put the value into the prop).
1528 * If the prop is an integer and we're doing the common pre/post inc/dec
1529 * ops, we know the type won't change, so we can skip the type-hint check in
1532 auto const fast
= [&]{
1533 if (RuntimeOption::EvalCheckPropTypeHints
<= 0) return true;
1534 auto const isAnyCheckable
= lookup
.prop
&& [&] {
1535 if (lookup
.prop
->typeConstraint
.isCheckable()) return true;
1536 for (auto const& ub
: lookup
.prop
->ubs
) {
1537 if (ub
.isCheckable()) return true;
1541 if (!isAnyCheckable
) return true;
1543 if (!isIntType(type(prop
))) return false;
1545 op
== IncDecOp::PreInc
|| op
== IncDecOp::PostInc
||
1546 op
== IncDecOp::PreDec
|| op
== IncDecOp::PostDec
;
1548 if (fast
) return IncDecBody(op
, tvAssertPlausible(prop
));
1551 tvDup(tvAssertPlausible(*prop
), temp
);
1552 SCOPE_FAIL
{ tvDecRefGen(&temp
); };
1553 auto result
= IncDecBody(op
, &temp
);
1554 SCOPE_FAIL
{ tvDecRefGen(&result
); };
1555 verifyTypeHint(m_cls
, lookup
.prop
, &temp
);
1556 tvMove(temp
, tvAssertPlausible(prop
));
1560 if (UNLIKELY(!*key
->data())) throw_invalid_property_name(StrNR(key
));
1562 // Native accessors.
1563 if (m_cls
->rtAttribute(Class::HasNativePropHandler
)) {
1564 auto r
= Native::getProp(Object
{this}, StrNR(key
));
1565 if (r
.isInitialized()) {
1566 auto const dest
= IncDecBody(op
, r
.asTypedValue());
1567 auto r2
= Native::setProp(Object
{this}, StrNR(key
), r
);
1568 if (r2
.isInitialized()) return dest
;
1572 if (prop
) raise_error("Cannot access protected property");
1574 // No visible/accessible property, and no applicable native method:
1575 // create a new dynamic property. (We know this is a new property,
1576 // or it would've hit the visible && accessible case above.)
1577 prop
= makeDynProp(key
);
1578 assertx(type(prop
) == KindOfNull
); // cannot exist yet
1579 return IncDecBody(op
, prop
);
1582 void ObjectData::unsetProp(const MemberLookupContext
& ctx
, const StringData
* key
) {
1583 auto const lookup
= getPropImpl
<true, false, true>(ctx
, key
);
1584 auto const prop
= lookup
.val
;
1586 if (prop
&& lookup
.accessible
&&
1587 (type(prop
) != KindOfUninit
||
1588 (lookup
.prop
&& (lookup
.prop
->attrs
& AttrLateInit
)))) {
1589 if (lookup
.slot
!= kInvalidSlot
) {
1590 // Declared property.
1591 if (UNLIKELY(lookup
.isConst
) && !isBeingConstructed()) {
1592 throwMutateConstProp(lookup
.slot
);
1594 unsetTypeHint(lookup
.prop
);
1595 tvSet(*uninit_variant
.asTypedValue(), prop
);
1597 // Dynamic property.
1598 dynPropArray().remove(StrNR(key
).asString(), true /* isString */);
1603 // Native unset first.
1604 if (m_cls
->rtAttribute(Class::HasNativePropHandler
)) {
1605 auto r
= Native::unsetProp(Object
{this}, StrNR(key
));
1606 if (r
.isInitialized()) return;
1609 if (prop
&& !lookup
.accessible
) {
1610 // Defined property that is not accessible.
1611 raise_error("Cannot unset inaccessible property");
1614 if (UNLIKELY(!*key
->data())) {
1615 throw_invalid_property_name(StrNR(key
));
1619 void ObjectData::throwObjToIntException(const char* clsName
) {
1620 SystemLib::throwTypecastExceptionObject(folly::sformat(
1621 "Object of class {} could not be converted to int", clsName
));
1624 void ObjectData::throwObjToDoubleException(const char* clsName
) {
1625 SystemLib::throwTypecastExceptionObject(folly::sformat(
1626 "Object of class {} could not be converted to float", clsName
));
1629 void ObjectData::raiseAbstractClassError(Class
* cls
) {
1630 Attr attrs
= cls
->attrs();
1631 raise_error("Cannot instantiate %s %s",
1632 (attrs
& AttrInterface
) ? "interface" :
1633 (attrs
& AttrTrait
) ? "trait" :
1634 (attrs
& (AttrEnum
|AttrEnumClass
)) ? "enum" : "abstract class",
1635 cls
->preClass()->name()->data());
1638 void ObjectData::throwUndefPropException(const StringData
* key
) const {
1639 SystemLib::throwUndefinedPropertyExceptionObject(
1640 folly::sformat("Undefined property: {}::${}",
1641 m_cls
->name()->data(),
1645 void ObjectData::raiseCreateDynamicProp(const StringData
* key
) const {
1646 if (m_cls
== SystemLib::s_stdClassClass
||
1647 m_cls
== SystemLib::s___PHP_Incomplete_ClassClass
) {
1648 // these classes (but not classes derived from them) don't get notices
1651 if (key
->isStatic()) {
1652 raise_notice("Created dynamic property with static name %s::%s",
1653 m_cls
->name()->data(), key
->data());
1655 raise_notice("Created dynamic property with dynamic name %s::%s",
1656 m_cls
->name()->data(), key
->data());
1660 void ObjectData::raiseReadDynamicProp(const StringData
* key
) const {
1661 if (m_cls
== SystemLib::s_stdClassClass
||
1662 m_cls
== SystemLib::s___PHP_Incomplete_ClassClass
) {
1663 // these classes (but not classes derived from them) don't get notices
1666 if (key
->isStatic()) {
1667 raise_notice("Read dynamic property with static name %s::%s",
1668 m_cls
->name()->data(), key
->data());
1670 raise_notice("Read dynamic property with dynamic name %s::%s",
1671 m_cls
->name()->data(), key
->data());
1675 void ObjectData::raiseImplicitInvokeToString() const {
1676 raise_notice("Implicitly invoked %s::__toString", m_cls
->name()->data());
1679 Variant
ObjectData::InvokeSimple(ObjectData
* obj
, const StaticString
& name
,
1680 RuntimeCoeffects providedCoeffects
) {
1681 auto const meth
= obj
->methodNamed(name
.get());
1683 ? g_context
->invokeMethodV(obj
, meth
, InvokeArgs
{}, providedCoeffects
)
1687 Variant
ObjectData::invokeSleep(RuntimeCoeffects provided
) {
1688 return InvokeSimple(this, s___sleep
, provided
);
1691 Variant
ObjectData::invokeToDebugDisplay(RuntimeCoeffects provided
) {
1692 return InvokeSimple(this, s___toDebugDisplay
, provided
);
1695 Variant
ObjectData::invokeWakeup(RuntimeCoeffects provided
) {
1697 SCOPE_EXIT
{ lockObject(); };
1698 return InvokeSimple(this, s___wakeup
, provided
);
1701 Variant
ObjectData::invokeDebugInfo(RuntimeCoeffects provided
) {
1702 return InvokeSimple(this, s___debugInfo
, provided
);
1705 String
ObjectData::invokeToString() {
1706 if (RuntimeOption::EvalFatalOnConvertObjectToString
) {
1707 raise_convert_object_to_string(classname_cstr());
1710 const Func
* method
= m_cls
->getToString();
1712 // If the object does not define a __toString() method, raise a
1713 // recoverable error
1714 raise_recoverable_error(
1715 "Object of class %s could not be converted to string",
1718 // If the user error handler decides to allow execution to continue,
1719 // we return the empty string.
1720 return empty_string();
1722 if (RuntimeOption::EvalNoticeOnImplicitInvokeToString
) {
1723 raiseImplicitInvokeToString();
1725 CoeffectsAutoGuard _
;
1726 auto const tv
= g_context
->invokeMethod(this, method
, InvokeArgs
{},
1727 RuntimeCoeffects::automatic());
1728 if (!isStringType(tv
.m_type
) &&
1729 !isClassType(tv
.m_type
) &&
1730 !isLazyClassType(tv
.m_type
)) {
1731 // Discard the value returned by the __toString() method and raise
1732 // a recoverable error
1734 raise_recoverable_error(
1735 "Method %s::__toString() must return a string value",
1736 m_cls
->preClass()->name()->data());
1737 // If the user error handler decides to allow execution to continue,
1738 // we return the empty string.
1739 return empty_string();
1742 if (tvIsString(tv
)) return String::attach(val(tv
).pstr
);
1743 if (tvIsLazyClass(tv
)) {
1744 return StrNR
{lazyClassToStringHelper(tv
.m_data
.plazyclass
)};
1746 assertx(isClassType(type(tv
)));
1747 return StrNR(classToStringHelper(tv
.m_data
.pclass
));
1750 bool ObjectData::hasToString() {
1751 return (m_cls
->getToString() != nullptr);
1754 const char* ObjectData::classname_cstr() const {
1755 return getClassName().data();