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/core/ext_core_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 "hphp/util/configs/eval.h"
54 #include <folly/Hash.h>
55 #include <folly/ScopeGuard.h>
61 //////////////////////////////////////////////////////////////////////
63 TRACE_SET_MOD(runtime
);
65 //////////////////////////////////////////////////////////////////////
69 const StaticString
s_clone("__clone");
72 void verifyTypeHint(const Class
* thisCls
,
73 const Class::Prop
* prop
,
75 assertx(tvIsPlausible(*val
));
76 assertx(type(val
) != KindOfUninit
);
77 if (!prop
|| Cfg::Eval::CheckPropTypeHints
<= 0) return;
78 if (prop
->typeConstraint
.isCheckable()) {
79 prop
->typeConstraint
.verifyProperty(val
, thisCls
, prop
->cls
, prop
->name
);
81 for (auto const& ub
: prop
->ubs
.m_constraints
) {
82 if (ub
.isCheckable()) {
83 ub
.verifyProperty(val
, thisCls
, prop
->cls
, prop
->name
);
89 void unsetTypeHint(const Class::Prop
* prop
) {
90 if (Cfg::Eval::CheckPropTypeHints
<= 0) return;
91 if (!prop
|| prop
->typeConstraint
.isMixedResolved()) return;
92 raise_property_typehint_unset_error(
95 prop
->typeConstraint
.isSoft(),
96 prop
->typeConstraint
.isUpperBound()
102 //////////////////////////////////////////////////////////////////////
104 // Check that the given property's type matches its type-hint.
106 bool assertATypeHint(const TypeConstraint
& tc
, tv_rval val
) {
107 if (!tc
.isCheckable() || tc
.isSoft()) return true;
108 if (val
.type() == KindOfUninit
) return tc
.maybeMixed();
109 return tc
.assertCheck(val
);
113 bool ObjectData::assertTypeHint(tv_rval prop
, Slot slot
) const {
114 assertx(tvIsPlausible(*prop
));
115 assertx(slot
< m_cls
->numDeclProperties());
116 auto const& propDecl
= m_cls
->declProperties()[slot
];
118 if (prop
.type() == KindOfResource
&& g_context
->doingInlineInterp()) {
122 if (debug
&& RuntimeOption::RepoAuthoritative
) {
123 // The fact that uninitialized LateInit props are uninit isn't
124 // reflected in the repo-auth-type.
125 if (prop
.type() != KindOfUninit
|| !(propDecl
.attrs
& AttrLateInit
)) {
126 always_assert(tvMatchesRepoAuthType(*prop
, propDecl
.repoAuthType
));
130 // If we're not hard enforcing, then the prop might contain anything.
131 if (Cfg::Eval::CheckPropTypeHints
<= 2) return true;
132 if (!propDecl
.typeConstraint
.isCheckable() ||
133 propDecl
.typeConstraint
.isSoft()) return true;
134 if (prop
.type() == KindOfNull
&& !(propDecl
.attrs
& AttrNoImplicitNullable
)) {
137 if (prop
.type() == KindOfUninit
&& (propDecl
.attrs
& AttrLateInit
)) {
140 if (!assertATypeHint(propDecl
.typeConstraint
, prop
)) return false;
141 for (auto const& ub
: propDecl
.ubs
.m_constraints
) {
142 if (!assertATypeHint(ub
, prop
)) return false;
147 //////////////////////////////////////////////////////////////////////
150 static void freeDynPropArray(ObjectData
* inst
) {
151 auto& table
= g_context
->dynPropTable
;
152 auto it
= table
.find(inst
);
153 assertx(it
!= end(table
));
154 assertx(it
->second
.arr().isDict());
155 it
->second
.destroy();
160 void ObjectData::slowDestroyCases() {
161 assertx(slowDestroyCheck());
163 if (getAttribute(UsedMemoCache
)) {
164 assertx(m_cls
->hasMemoSlots());
165 auto const nSlots
= m_cls
->numMemoSlots();
166 for (Slot i
= 0; i
< nSlots
; ++i
) {
167 auto slot
= memoSlot(i
);
168 if (slot
->isCache()) {
169 if (auto cache
= slot
->getCache()) req::destroy_raw(cache
);
171 tvDecRefGen(*slot
->getValue());
176 if (UNLIKELY(getAttribute(HasDynPropArr
))) freeDynPropArray(this);
177 if (UNLIKELY(getAttribute(IsWeakRefed
))) {
178 WeakRefData::invalidateWeakRef((uintptr_t)this);
181 auto const memoSize
= m_cls
->memoSize();
182 auto const ptr
= reinterpret_cast<char*>(this) - memoSize
;
183 tl_heap
->objFreeIndex(ptr
, m_cls
->sizeIdx());
186 // Single check for a couple different unlikely actions during destruction.
187 inline bool ObjectData::slowDestroyCheck() const {
188 return m_aux16
& (HasDynPropArr
| IsWeakRefed
| UsedMemoCache
| BigAllocSize
);
191 void ObjectData::release(ObjectData
* obj
, const Class
* cls
) noexcept
{
192 assertx(obj
->kindIsValid());
193 assertx(!obj
->hasInstanceDtor());
194 assertx(!obj
->hasNativeData());
195 assertx(obj
->getVMClass() == cls
);
196 assertx(cls
->releaseFunc() == &ObjectData::release
);
197 assertx(obj
->props()->checkInvariants(cls
->numDeclProperties()));
199 // Note: cleanups done in this function are only run for classes without an
200 // instanceDtor. Some of these cleanups are duplicated in ~ObjectData, and
201 // your instanceDtor may call that to have them run; if you choose not to run
202 // ~ObjectData from your instanceDtor you MUST do some of them manually
203 // (e.g. invalidate WeakRefs). Some cleanups (e.g. clearing memo caches) are
204 // not done from ~ObjectData because it is assumed they're not needed for
205 // builtin classes (and in the case of memo caches, since the clearing needs
206 // to be done differently when there is native data).
207 // Finally, cleanups such as invalidating WeakRefs that have to be done for
208 // correctness MUST also be done in Collector::sweep, since none of the code
209 // in this function or the instanceDtor will be run when the object is
212 // `obj' is being torn down now---be careful about where/how you dereference
215 obj
->props()->release(cls
->countablePropsEnd());
217 if (UNLIKELY(obj
->slowDestroyCheck())) {
218 obj
->slowDestroyCases();
220 assertx((obj
->m_aux16
& BigAllocSize
) == 0);
221 auto const memoSize
= cls
->memoSize();
222 auto const ptr
= reinterpret_cast<char*>(obj
) - memoSize
;
223 assertx(memoSize
== 0 ||
224 reinterpret_cast<const MemoNode
*>(ptr
)->objOff() == memoSize
);
226 tl_heap
->freeSmallIndex(ptr
, cls
->sizeIdx());
229 AARCH64_WALKABLE_FRAME();
232 ///////////////////////////////////////////////////////////////////////////////
235 StrNR
ObjectData::getClassName() const {
236 return m_cls
->preClass()->nameStr();
239 bool ObjectData::instanceof(const String
& s
) const {
240 assertx(kindIsValid());
241 auto const cls
= Class::lookup(s
.get());
242 return cls
&& instanceof(cls
);
245 bool ObjectData::toBooleanImpl() const noexcept
{
246 // Note: if you add more cases here, hhbbc/class-util.cpp also needs
248 if (isCollection()) {
249 if (RuntimeOption::EvalNoticeOnCollectionToBool
) {
251 "%s to boolean cast",
252 collections::typeToString((CollectionType
)m_kind
)->data()
255 return collections::toBool(this);
258 if (instanceof(SimpleXMLElementLoader::classof())) {
259 // SimpleXMLElement is the only non-collection class that has custom bool
261 if (RuntimeOption::EvalNoticeOnSimpleXMLBehavior
) {
262 raise_notice("SimpleXMLElement to boolean cast");
264 return SimpleXMLElement_objectCast(this, KindOfBoolean
).toBoolean();
267 always_assert(false);
270 int64_t ObjectData::toInt64() const {
271 /* SimpleXMLElement is the only class that has proper custom num casting. */
272 if (LIKELY(!instanceof(SimpleXMLElementLoader::classof()))) {
273 throwObjToIntException(classname_cstr());
275 if (RuntimeOption::EvalNoticeOnSimpleXMLBehavior
) {
276 raise_notice("SimpleXMLElement to integer cast");
278 return SimpleXMLElement_objectCast(this, KindOfInt64
).toInt64();
281 double ObjectData::toDouble() const {
282 /* SimpleXMLElement is the only class that has proper custom num casting. */
283 if (LIKELY(!instanceof(SimpleXMLElementLoader::classof()))) {
284 throwObjToDoubleException(classname_cstr());
286 if (RuntimeOption::EvalNoticeOnSimpleXMLBehavior
) {
287 raise_notice("SimpleXMLElement to double cast");
289 return SimpleXMLElement_objectCast(this, KindOfDouble
).toDouble();
292 ///////////////////////////////////////////////////////////////////////////////
293 // instance methods and properties
295 const StaticString
s_getIterator("getIterator");
297 Object
ObjectData::iterableObject(bool& isIterable
,
298 bool mayImplementIterator
/* = true */) {
299 assertx(mayImplementIterator
|| !isIterator());
300 if (mayImplementIterator
&& isIterator()) {
305 CoeffectsAutoGuard _
;
306 while (obj
->instanceof(SystemLib::getIteratorAggregateClass())) {
308 obj
->o_invoke_few_args(s_getIterator
, RuntimeCoeffects::automatic(), 0);
309 if (!iterator
.isObject()) break;
310 auto o
= iterator
.getObjectData();
311 if (o
->isIterator()) {
317 if (!isIterator() && obj
->instanceof(SimpleXMLElementLoader::classof())) {
318 if (RuntimeOption::EvalNoticeOnSimpleXMLBehavior
) {
319 raise_notice("SimpleXMLElement used as iterator");
322 return create_object(
323 SimpleXMLElementIteratorLoader::className(),
331 Array
& ObjectData::dynPropArray() const {
332 assertx(getAttribute(HasDynPropArr
));
333 assertx(g_context
->dynPropTable
.count(this));
334 assertx(g_context
->dynPropTable
[this].arr().isDict());
335 return g_context
->dynPropTable
[this].arr();
338 void ObjectData::setDynProps(const Array
& newArr
) {
339 // don't expose the ref returned by setDynPropArr
340 (void)setDynPropArray(newArr
.toDict());
343 void ObjectData::reserveDynProps(int numDynamic
) {
344 // don't expose the ref returned by reserveProperties()
345 (void)reserveProperties(numDynamic
);
348 Array
& ObjectData::reserveProperties(int numDynamic
/* = 2 */) {
349 if (getAttribute(HasDynPropArr
)) {
350 return dynPropArray();
353 auto const allocsz
= VanillaDict::computeAllocBytesFromMaxElms(numDynamic
);
354 if (UNLIKELY(allocsz
> kMaxSmallSize
&& tl_heap
->preAllocOOM(allocsz
))) {
355 check_non_safepoint_surprise();
358 return setDynPropArray(Array::attach(
359 VanillaDict::MakeReserveDict(numDynamic
)));
362 Array
& ObjectData::setDynPropArray(const Array
& newArr
) {
363 assertx(!g_context
->dynPropTable
.count(this));
364 assertx(!getAttribute(HasDynPropArr
));
365 assertx(newArr
.isDict());
367 if (m_cls
->forbidsDynamicProps()) {
368 throw_object_forbids_dynamic_props(getClassName().data());
370 if (RuntimeOption::EvalNoticeOnCreateDynamicProp
) {
371 IterateKV(newArr
.get(), [&] (TypedValue k
, TypedValue v
) {
372 auto const key
= tvCastToString(k
);
373 raiseCreateDynamicProp(key
.get());
377 // newArr can have refcount 2 or higher
378 auto& arr
= g_context
->dynPropTable
[this].arr();
379 assertx(arr
.isNull());
381 setAttribute(HasDynPropArr
);
385 tv_lval
ObjectData::makeDynProp(const StringData
* key
) {
386 if (RuntimeOption::EvalNoticeOnCreateDynamicProp
) {
387 raiseCreateDynamicProp(key
);
389 if (!reserveProperties().exists(StrNR(key
))) {
390 reserveProperties().set(StrNR(key
), make_tv
<KindOfNull
>());
392 return reserveProperties().lval(StrNR(key
), AccessFlags::Key
);
395 void ObjectData::setDynProp(const StringData
* key
, TypedValue val
) {
396 if (RuntimeOption::EvalNoticeOnCreateDynamicProp
) {
397 raiseCreateDynamicProp(key
);
399 reserveProperties().set(StrNR(key
), val
, true);
402 Variant
ObjectData::o_get(const String
& propName
, bool error
/* = true */,
403 const String
& context
/*= null_string*/) {
404 assertx(kindIsValid());
406 // This is not (just) a check for empty string; property names that start
407 // with null are intentionally being rejected here.
408 if (UNLIKELY(!*propName
.data())) {
409 throw_invalid_property_name(propName
);
412 Class
* ctx
= nullptr;
413 if (!context
.empty()) {
414 ctx
= Class::lookup(context
.get());
417 ctx
? MemberLookupContext(ctx
, ctx
->moduleName()) : nullctx
;
419 // Can't use propImpl here because if the property is not accessible and
420 // there is no native get, propImpl will raise_error("Cannot access ...",
421 // but o_get will only (maybe) raise_notice("Undefined property ..." :-(
423 auto const lookup
= getPropImpl
<false, true, true>(propCtx
, propName
.get());
424 if (lookup
.val
&& lookup
.accessible
) {
425 if (lookup
.val
.type() != KindOfUninit
) {
426 return Variant::wrap(lookup
.val
.tv());
427 } else if (lookup
.prop
&& (lookup
.prop
->attrs
& AttrLateInit
)) {
428 if (error
) throw_late_init_prop(lookup
.prop
->cls
, propName
.get(), false);
429 return uninit_null();
434 SystemLib::throwUndefinedPropertyExceptionObject(
435 folly::sformat("Undefined property: {}::${}",
436 getClassName().data(),
440 return uninit_null();
443 void ObjectData::o_set(const String
& propName
, const Variant
& v
,
444 const String
& context
/* = null_string */) {
445 assertx(kindIsValid());
447 // This is not (just) a check for empty string; property names that start
448 // with null are intentionally being rejected here.
449 if (UNLIKELY(!*propName
.data())) {
450 throw_invalid_property_name(propName
);
453 Class
* ctx
= nullptr;
454 if (!context
.empty()) {
455 ctx
= Class::lookup(context
.get());
458 ctx
? MemberLookupContext(ctx
, ctx
->moduleName()) : nullctx
;
460 // Can't use setProp here because if the property is not accessible and
461 // there is no native set, setProp will raise_error("Cannot access ...",
462 // but o_set will skip writing and return normally.
464 auto const lookup
= getPropImpl
<true, false, true>(propCtx
, propName
.get());
465 auto prop
= lookup
.val
;
466 if (prop
&& lookup
.accessible
) {
467 if (UNLIKELY(lookup
.isConst
) && !isBeingConstructed()) {
468 throwMutateConstProp(lookup
.slot
);
470 auto val
= tvToInit(*v
.asTypedValue());
471 verifyTypeHint(m_cls
, lookup
.prop
, &val
);
477 setDynProp(propName
.get(), tvToInit(*v
.asTypedValue()));
481 void ObjectData::o_setArray(const Array
& properties
) {
482 for (ArrayIter
iter(properties
); iter
; ++iter
) {
483 String k
= iter
.first().toString();
484 Class
* ctx
= nullptr;
485 // If the key begins with a NUL, it's a private or protected property. Read
486 // the class name from between the two NUL bytes.
488 // Note: if you change this, you need to change similar logic in
490 if (!k
.empty() && k
[0] == '\0') {
491 int subLen
= k
.find('\0', 1) + 1;
492 String cls
= k
.substr(1, subLen
- 2);
493 if (cls
.size() == 1 && cls
[0] == '*') {
498 ctx
= Class::lookup(cls
.get());
501 k
= k
.substr(subLen
);
503 // TODO(T126821336): property can be internal
504 // This function is only used in ext_mysql.cpp,
505 // but has its own encoding mechanism
507 ctx
? MemberLookupContext(ctx
, ctx
->moduleName()) : nullctx
;
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(SimpleXMLElementLoader::classof()));
581 if (RuntimeOption::EvalNoticeOnSimpleXMLBehavior
) {
582 raise_notice("SimpleXMLElement to array cast");
584 return SimpleXMLElement_darrayCast(this);
585 } else if (UNLIKELY(instanceof(SystemLib::getArrayIteratorClass()))) {
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::classof()))) {
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 Class
* ctx
) {
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
)) };
648 ctx
? MemberLookupContext(ctx
, ctx
->moduleName()) : nullctx
;
650 // Get all declared properties first, bottom-to-top in the inheritance
651 // hierarchy, in declaration order.
652 const Class
* klass
= m_cls
;
654 const PreClass::Prop
* props
= klass
->preClass()->properties();
655 const size_t numProps
= klass
->preClass()->numProperties();
657 for (size_t i
= 0; i
< numProps
; ++i
) {
658 auto key
= const_cast<StringData
*>(props
[i
].name());
659 accessibleProps
= getPropertyIfAccessible(
660 this, propCtx
, key
, retArray
, accessibleProps
);
662 klass
= klass
->parent();
664 if (!(m_cls
->attrs() & AttrNoExpandTrait
) && accessibleProps
> 0) {
665 // we may have properties from traits
666 for (auto const& prop
: m_cls
->declProperties()) {
667 auto const key
= prop
.name
.get();
668 if (!retArray
.get()->exists(key
)) {
669 accessibleProps
= getPropertyIfAccessible(
670 this, propCtx
, key
, retArray
, accessibleProps
);
671 if (accessibleProps
== 0) break;
676 // Now get dynamic properties.
677 if (getAttribute(HasDynPropArr
)) {
678 auto& dynProps
= dynPropArray();
679 auto ad
= dynProps
.get();
680 ssize_t iter
= ad
->iter_begin();
681 auto pos_limit
= ad
->iter_end();
682 while (iter
!= pos_limit
) {
684 auto const key
= ad
->nvGetKey(iter
);
685 iter
= ad
->iter_advance(iter
);
687 if (RuntimeOption::EvalNoticeOnReadDynamicProp
) {
688 auto const k
= tvCastToString(key
);
689 raiseReadDynamicProp(k
.get());
692 // You can get this if you cast an array to object. These
693 // properties must be dynamic because you can't declare a
694 // property with a non-string name.
695 if (UNLIKELY(!isStringType(key
.m_type
))) {
696 assertx(key
.m_type
== KindOfInt64
);
697 auto const val
= dynProps
.get()->at(key
.m_data
.num
);
698 retArray
.set(key
.m_data
.num
, val
);
702 auto const strKey
= key
.m_data
.pstr
;
703 auto const val
= dynProps
.get()->at(strKey
);
704 retArray
.set(StrNR(strKey
), val
, true /* isKey */);
711 static bool decode_invoke(const String
& s
, ObjectData
* obj
, bool fatal
,
714 ctx
.cls
= obj
->getVMClass();
717 ctx
.func
= ctx
.cls
->lookupMethod(s
.get());
719 // Bail if this_ is non-null AND we could not find a method.
720 o_invoke_failed(ctx
.cls
->name()->data(), s
.data(), fatal
);
724 // Null out this_ for statically called methods
725 if (ctx
.func
->isStaticInPrologue()) {
731 Variant
ObjectData::o_invoke(const String
& s
, const Variant
& params
,
732 bool fatal
/* = true */) {
734 if (!decode_invoke(s
, this, fatal
, ctx
) ||
735 (!isContainer(params
) && !params
.isNull())) {
736 return Variant(Variant::NullInit());
738 CoeffectsAutoGuard _
;
739 return Variant::attach(
740 g_context
->invokeFunc(ctx
, params
, RuntimeCoeffects::automatic())
744 Variant
ObjectData::o_invoke_few_args(const String
& s
,
745 RuntimeCoeffects providedCoeffects
,
747 const Variant
& a0
/* = uninit_variant*/,
748 const Variant
& a1
/* = uninit_variant*/,
749 const Variant
& a2
/* = uninit_variant*/,
750 const Variant
& a3
/* = uninit_variant*/,
751 const Variant
& a4
/* = uninit_variant*/) {
754 if (!decode_invoke(s
, this, true, ctx
)) {
755 return Variant(Variant::NullInit());
760 default: not_implemented();
761 case 5: tvCopy(*a4
.asTypedValue(), args
[4]); [[fallthrough
]];
762 case 4: tvCopy(*a3
.asTypedValue(), args
[3]); [[fallthrough
]];
763 case 3: tvCopy(*a2
.asTypedValue(), args
[2]); [[fallthrough
]];
764 case 2: tvCopy(*a1
.asTypedValue(), args
[1]); [[fallthrough
]];
765 case 1: tvCopy(*a0
.asTypedValue(), args
[0]); [[fallthrough
]];
769 return Variant::attach(
770 g_context
->invokeFuncFew(ctx
, count
, args
, providedCoeffects
)
774 ObjectData
* ObjectData::clone() {
775 if (isCppBuiltin()) {
776 assertx(!m_cls
->hasMemoSlots());
777 if (isCollection()) return collections::clone(this);
778 if (instanceof(c_Closure::classof())) {
779 return c_Closure::fromObject(this)->clone();
781 assertx(instanceof(c_Awaitable::classof()));
782 // cloning WaitHandles is not allowed
783 // invoke the instanceCtor to get the right sort of exception
784 auto const ctor
= m_cls
->instanceCtor();
786 always_assert(false);
789 // clone prevents a leak if something throws before clone() returns
791 auto const nProps
= m_cls
->numDeclProperties();
792 if (hasNativeData()) {
793 assertx(m_cls
->instanceDtor() == Native::nativeDataInstanceDtor
);
794 clone
= Object::attach(
795 Native::nativeDataInstanceCopyCtor(this, m_cls
, nProps
)
797 assertx(clone
->hasExactlyOneRef());
798 assertx(clone
->hasInstanceDtor());
800 auto const alloc
= allocMemoInit(m_cls
);
802 auto const obj
= new (NotNull
{}, alloc
.mem
)
803 ObjectData(m_cls
, InitRaw
{}, alloc
.flags
);
804 clone
= Object::attach(obj
);
805 assertx(clone
->hasExactlyOneRef());
806 assertx(!clone
->hasInstanceDtor());
809 auto const cloneProps
= clone
->props();
810 cloneProps
->init(m_cls
->numDeclProperties());
811 for (auto slot
= Slot
{0}; slot
< nProps
; slot
++) {
812 auto index
= m_cls
->propSlotToIndex(slot
);
813 auto const prop
= props()->at(index
);
814 if (!isLazyProp(prop
)) {
815 tvDup(*prop
, cloneProps
->at(index
));
816 assertx(assertTypeHint(cloneProps
->at(index
), slot
));
818 auto const cloneProp
= cloneProps
->at(index
);
819 type(cloneProp
) = type(prop
);
820 val(cloneProp
).num
= val(prop
).num
;
824 if (UNLIKELY(getAttribute(HasDynPropArr
))) {
825 clone
->setAttribute(HasDynPropArr
);
826 g_context
->dynPropTable
.emplace(clone
.get(), dynPropArray().get());
828 if (m_cls
->rtAttribute(Class::HasClone
)) {
829 assertx(!isCppBuiltin());
830 auto const method
= clone
->m_cls
->lookupMethod(s_clone
.get());
832 clone
->unlockObject();
833 SCOPE_EXIT
{ clone
->lockObject(); };
834 CoeffectsAutoGuard _
;
835 g_context
->invokeMethodV(clone
.get(), method
, InvokeArgs
{},
836 RuntimeCoeffects::automatic());
838 return clone
.detach();
841 bool ObjectData::equal(const ObjectData
& other
) const {
842 if (this == &other
) return true;
843 if (isCollection()) {
844 return collections::equals(this, &other
);
846 if (UNLIKELY(instanceof(SystemLib::getDateTimeInterfaceClass()) &&
847 other
.instanceof(SystemLib::getDateTimeInterfaceClass()))) {
848 return DateTimeData::compare(this, &other
) == 0;
850 if (getVMClass() != other
.getVMClass()) return false;
851 if (UNLIKELY(instanceof(SimpleXMLElementLoader::classof()))) {
852 if (RuntimeOption::EvalNoticeOnSimpleXMLBehavior
) {
853 raise_notice("SimpleXMLElement equality comparison");
855 // Compare the whole object (including native data), not just props
856 auto ar1
= SimpleXMLElement_darrayCast(this);
857 auto ar2
= SimpleXMLElement_darrayCast(&other
);
858 return ArrayData::Equal(ar1
.get(), ar2
.get());
860 if (UNLIKELY(instanceof(c_Closure::classof()))) {
861 // First comparison already proves they are different
865 // check for dynamic props first because we need to short-circuit if there's
866 // a different number of them
867 auto thisSize
= UNLIKELY(getAttribute(HasDynPropArr
)) ?
868 dynPropArray().size() : 0;
869 size_t otherSize
= 0;
870 ArrayData
* otherDynProps
= nullptr;
871 if (UNLIKELY(other
.getAttribute(HasDynPropArr
))) {
872 otherDynProps
= other
.dynPropArray().get();
873 otherSize
= otherDynProps
->size();
875 if (thisSize
!= otherSize
) return false;
877 // Prevent circular referenced objects/arrays or deep ones.
878 check_recursion_error();
883 [&](Slot slot
, const Class::Prop
& prop
, tv_rval thisVal
) {
884 auto otherVal
= other
.propRvalAtOffset(slot
);
885 if ((UNLIKELY(thisVal
.type() == KindOfUninit
) ||
886 UNLIKELY(otherVal
.type() == KindOfUninit
)) &&
887 (prop
.attrs
& AttrLateInit
)) {
888 throw_late_init_prop(prop
.cls
, prop
.name
, false);
890 if (!tvEqual(thisVal
.tv(), otherVal
.tv())) {
896 [&](TypedValue key
, TypedValue thisVal
) {
897 auto const otherVal
= otherDynProps
->get(key
);
898 if (!otherVal
.is_init() || !tvEqual(thisVal
, otherVal
)) {
908 bool ObjectData::less(const ObjectData
& other
) const {
909 // compare is not symmetrical; order of operands matters here
910 return compare(other
) < 0;
913 bool ObjectData::lessEqual(const ObjectData
& other
) const {
914 // compare is not symmetrical; order of operands matters here
915 return compare(other
) <= 0;
918 bool ObjectData::more(const ObjectData
& other
) const {
919 // compare is not symmetrical; order of operands matters here
920 return other
.compare(*this) < 0;
923 bool ObjectData::moreEqual(const ObjectData
& other
) const {
924 // compare is not symmetrical; order of operands matters here
925 return other
.compare(*this) <= 0;
928 int64_t ObjectData::compare(const ObjectData
& other
) const {
929 if (isCollection() || other
.isCollection()) {
930 throw_collection_compare_exception();
932 if (this == &other
) return 0;
933 if (UNLIKELY(instanceof(SystemLib::getDateTimeInterfaceClass()) &&
934 other
.instanceof(SystemLib::getDateTimeInterfaceClass()))) {
935 return DateTimeData::compare(this, &other
);
937 if (getVMClass() != other
.getVMClass()) {
938 const auto lhs
= make_tv
<DataType::Object
>(const_cast<ObjectData
*>(this));
939 const auto rhs
= make_tv
<DataType::Object
>(const_cast<ObjectData
*>(&other
));
940 throwCmpBadTypesException(&lhs
, &rhs
);
943 if (UNLIKELY(instanceof(SimpleXMLElementLoader::classof()))) {
944 if (RuntimeOption::EvalNoticeOnSimpleXMLBehavior
) {
945 raise_notice("SimpleXMLElement comparison");
947 // Compare the whole object (including native data), not just props
948 auto ar1
= SimpleXMLElement_darrayCast(this);
949 auto ar2
= SimpleXMLElement_darrayCast(&other
);
950 return ArrayData::Compare(ar1
.get(), ar2
.get());
952 if (UNLIKELY(instanceof(c_Closure::classof()))) {
953 // comparing different closures with <=> always returns 1
957 // check for dynamic props first, because we need to short circuit if there's
958 // a different number of them
959 auto thisSize
= UNLIKELY(getAttribute(HasDynPropArr
)) ?
960 dynPropArray().size() : 0;
961 size_t otherSize
= 0;
962 ArrayData
* otherDynProps
= nullptr;
963 if (UNLIKELY(other
.getAttribute(HasDynPropArr
))) {
964 otherDynProps
= other
.dynPropArray().get();
965 otherSize
= otherDynProps
->size();
967 if (thisSize
> otherSize
) {
969 } else if (thisSize
< otherSize
) {
973 // Prevent circular referenced objects/arrays or deep ones.
974 check_recursion_error();
977 IteratePropToArrayOrder(
979 [&](Slot slot
, const Class::Prop
& prop
, tv_rval thisVal
) {
980 auto otherVal
= other
.propRvalAtOffset(slot
);
981 if ((UNLIKELY(thisVal
.type() == KindOfUninit
) ||
982 UNLIKELY(otherVal
.type() == KindOfUninit
)) &&
983 (prop
.attrs
& AttrLateInit
)) {
984 throw_late_init_prop(prop
.cls
, prop
.name
, false);
986 auto cmp
= tvCompare(thisVal
.tv(), otherVal
.tv());
993 [&](TypedValue key
, TypedValue thisVal
) {
994 auto const otherVal
= otherDynProps
->get(key
);
995 if (!otherVal
.is_init()) {
999 auto cmp
= tvCompare(thisVal
, otherVal
);
1010 ///////////////////////////////////////////////////////////////////////////////
1013 s___sleep("__sleep"),
1014 s___toDebugDisplay("__toDebugDisplay"),
1015 s___wakeup("__wakeup"),
1016 s___debugInfo("__debugInfo");
1018 void deepInitHelper(ObjectProps
* props
,
1019 const Class::PropInitVec
* initVec
,
1021 auto initIter
= initVec
->cbegin();
1022 props
->init(nProps
);
1023 props
->foreach(nProps
, [&](tv_lval lval
){
1024 auto entry
= *initIter
++;
1025 tvCopy(entry
.val
.tv(), lval
);
1026 if (entry
.deepInit
) {
1028 collections::deepCopy(lval
);
1033 void ObjectData::setReifiedGenerics(Class
* cls
, ArrayData
* reifiedTypes
) {
1034 auto const arg
= make_array_like_tv(reifiedTypes
);
1035 auto const meth
= cls
->lookupMethod(s_86reifiedinit
.get());
1036 assertx(meth
!= nullptr);
1037 g_context
->invokeMethod(this, meth
, InvokeArgs(&arg
, 1),
1038 RuntimeCoeffects::fixme());
1041 // called from jit code
1042 ObjectData
* ObjectData::newInstanceRawSmall(Class
* cls
, size_t size
,
1044 assertx(size
<= kMaxSmallSize
);
1045 assertx(!cls
->hasMemoSlots());
1046 assertx(cls
->sizeIdx() == index
);
1047 auto mem
= tl_heap
->mallocSmallIndexSize(index
, size
);
1048 auto const flags
= IsBeingConstructed
| SmallAllocSize
;
1049 return new (NotNull
{}, mem
) ObjectData(cls
, InitRaw
{}, flags
);
1052 ObjectData
* ObjectData::newInstanceRawBig(Class
* cls
, size_t size
) {
1053 assertx(!cls
->hasMemoSlots());
1054 auto mem
= tl_heap
->mallocBigSize(size
);
1055 auto const flags
= IsBeingConstructed
| BigAllocSize
;
1056 return new (NotNull
{}, mem
) ObjectData(cls
, InitRaw
{}, flags
);
1059 // called from jit code
1060 ObjectData
* ObjectData::newInstanceRawMemoSmall(Class
* cls
,
1064 assertx(size
<= kMaxSmallSize
);
1065 assertx(cls
->hasMemoSlots());
1066 assertx(!cls
->getNativeDataInfo());
1067 assertx(objoff
== ObjectData::objOffFromMemoNode(cls
));
1068 assertx(cls
->sizeIdx() == index
);
1069 auto mem
= tl_heap
->mallocSmallIndexSize(index
, size
);
1070 new (NotNull
{}, mem
) MemoNode(objoff
);
1071 mem
= reinterpret_cast<char*>(mem
) + objoff
;
1072 auto const flags
= IsBeingConstructed
| SmallAllocSize
;
1073 return new (NotNull
{}, mem
) ObjectData(cls
, InitRaw
{}, flags
);
1076 ObjectData
* ObjectData::newInstanceRawMemoBig(Class
* cls
,
1079 assertx(cls
->hasMemoSlots());
1080 assertx(!cls
->getNativeDataInfo());
1081 assertx(objoff
== ObjectData::objOffFromMemoNode(cls
));
1082 auto mem
= tl_heap
->mallocBigSize(size
);
1083 new (NotNull
{}, mem
) MemoNode(objoff
);
1084 mem
= reinterpret_cast<char*>(mem
) + objoff
;
1085 auto const flags
= IsBeingConstructed
| BigAllocSize
;
1086 return new (NotNull
{}, mem
) ObjectData(cls
, InitRaw
{}, flags
);
1089 // Note: the normal object destruction path does not actually call this
1090 // destructor. See ObjectData::release.
1091 ObjectData::~ObjectData() {
1092 if (UNLIKELY(slowDestroyCheck())) {
1093 // The only builtin classes that use ~ObjectData and support memoization
1094 // are ones with native data, and the memo slot cleanup for them happens
1095 // in nativeDataInstanceDtor.
1096 assertx(!getAttribute(UsedMemoCache
) || hasNativeData());
1097 if (getAttribute(HasDynPropArr
)) freeDynPropArray(this);
1098 if (getAttribute(IsWeakRefed
)) {
1099 WeakRefData::invalidateWeakRef((uintptr_t)this);
1104 Object
ObjectData::FromArray(ArrayData
* properties
) {
1105 auto const props
= properties
->toDict(true);
1106 Object retval
{SystemLib::getstdClassClass()};
1107 retval
->setAttribute(HasDynPropArr
);
1108 g_context
->dynPropTable
.emplace(retval
.get(), props
);
1109 if (props
!= properties
) decRefArr(props
);
1113 void ObjectData::throwMutateConstProp(Slot prop
) const {
1114 throw_cannot_modify_const_prop(
1115 getClassName().data(),
1116 m_cls
->declProperties()[prop
].name
->data()
1120 void ObjectData::throwMustBeMutable(Slot prop
) const {
1121 throw_must_be_mutable(
1122 getClassName().data(),
1123 m_cls
->declProperties()[prop
].name
->data()
1127 void ObjectData::throwMustBeEnclosedInReadonly(Slot prop
) const {
1128 throw_must_be_enclosed_in_readonly(
1129 getClassName().data(),
1130 m_cls
->declProperties()[prop
].name
->data()
1134 void ObjectData::throwMustBeReadonly(Slot prop
) const {
1135 throw_must_be_readonly(
1136 getClassName().data(),
1137 m_cls
->declProperties()[prop
].name
->data()
1141 void ObjectData::throwMustBeValueType(Slot prop
) const {
1142 throw_must_be_value_type(
1143 getClassName().data(),
1144 m_cls
->declProperties()[prop
].name
->data()
1148 void ObjectData::checkReadonly(const PropLookup
& lookup
, ReadonlyOp op
,
1149 bool writeMode
) const {
1150 if ((op
== ReadonlyOp::CheckMutROCOW
&& lookup
.readonly
) ||
1151 op
== ReadonlyOp::CheckROCOW
) {
1152 vmMInstrState().roProp
= true;
1154 if (lookup
.readonly
) {
1155 if (op
== ReadonlyOp::CheckMutROCOW
|| op
== ReadonlyOp::CheckROCOW
) {
1156 if (type(lookup
.val
) == KindOfObject
) {
1157 throwMustBeValueType(lookup
.slot
);
1159 } else if (op
== ReadonlyOp::Mutable
) {
1161 throwMustBeMutable(lookup
.slot
);
1163 throwMustBeEnclosedInReadonly(lookup
.slot
);
1166 } else if (op
== ReadonlyOp::Readonly
|| op
== ReadonlyOp::CheckROCOW
) {
1167 throwMustBeReadonly(lookup
.slot
);
1171 void ObjectData::deserializeAllLazyProps() {
1172 if (!m_cls
->currentlyUsingLazyAPCDeserialization()) return;
1173 props()->foreach(m_cls
->numDeclProperties(), [&](tv_lval lval
) {
1174 if (isLazyProp(lval
)) deserializeLazyProp(lval
);
1178 void ObjectData::deserializeLazyProp(tv_lval prop
) {
1179 assertx(isLazyProp(prop
));
1180 auto const handle
= reinterpret_cast<APCHandle
*>(prop
.val().num
);
1181 assertx(handle
->checkInvariants());
1182 tvCopy(handle
->toLocalHelper(false).detach(), prop
);
1185 bool ObjectData::isLazyProp(tv_rval prop
) {
1186 return prop
.type() == kInvalidDataType
;
1189 template <bool forWrite
, bool forRead
, bool ignoreLateInit
>
1191 ObjectData::PropLookup
ObjectData::getPropImpl(
1192 const MemberLookupContext
& propCtx
,
1193 const StringData
* key
1195 auto const lookup
= m_cls
->getDeclPropSlot(propCtx
, key
);
1196 auto const propSlot
= lookup
.slot
;
1198 if (LIKELY(propSlot
!= kInvalidSlot
)) {
1199 // We found a visible property in one of the object's slots. Immediately
1200 // deserialize it if it's a lazy prop. Then, check if it's accessible.
1201 auto const propIndex
= m_cls
->propSlotToIndex(propSlot
);
1202 auto prop
= props()->at(propIndex
);
1203 if (isLazyProp(prop
)) deserializeLazyProp(prop
);
1204 assertx(assertTypeHint(prop
, propSlot
));
1206 auto const& declProp
= m_cls
->declProperties()[propSlot
];
1207 if (!ignoreLateInit
&& lookup
.accessible
) {
1208 if (UNLIKELY(type(prop
) == KindOfUninit
) &&
1209 (declProp
.attrs
& AttrLateInit
)) {
1210 throw_late_init_prop(declProp
.cls
, key
, false);
1214 // If the prop is internal, check that modules are compatible
1215 if (lookup
.internal
&&
1216 will_symbol_raise_module_boundary_violation(&declProp
, &propCtx
)) {
1217 raiseModulePropertyViolation(m_cls
, key
, propCtx
.moduleName(), false);
1225 // we always return true in the !forWrite case; this way the compiler
1226 // may optimize away this value, and if a caller intends to write but
1227 // instantiates with false by mistake it will always see const
1229 ? bool(declProp
.attrs
& AttrIsConst
)
1235 // We could not find a visible declared property. We need to check for a
1236 // dynamic property with this name.
1237 if (UNLIKELY(getAttribute(HasDynPropArr
))) {
1238 auto& arr
= dynPropArray();
1239 if (arr
->exists(key
)) {
1240 if (forRead
&& RuntimeOption::EvalNoticeOnReadDynamicProp
) {
1241 raiseReadDynamicProp(key
);
1243 // Returning a non-declared property. We know that it is accessible and
1244 // not const since all dynamic properties are. If we may write to
1245 // the property we need to allow the array to escalate.
1246 auto const lval
= arr
.lval(StrNR(key
), AccessFlags::Key
);
1247 return { lval
, nullptr, kInvalidSlot
, true, !forWrite
, false };
1251 return { nullptr, nullptr, kInvalidSlot
, false, !forWrite
, false };
1254 tv_lval
ObjectData::getPropLval(const MemberLookupContext
& ctx
, const StringData
* key
) {
1255 auto const lookup
= getPropImpl
<true, false, true>(ctx
, key
);
1256 if (UNLIKELY(lookup
.isConst
) && !isBeingConstructed()) {
1257 throwMutateConstProp(lookup
.slot
);
1259 return lookup
.val
&& lookup
.accessible
? lookup
.val
: nullptr;
1262 tv_rval
ObjectData::getProp(const MemberLookupContext
& ctx
, const StringData
* key
) const {
1263 auto const lookup
= const_cast<ObjectData
*>(this)
1264 ->getPropImpl
<false, true, false>(ctx
, key
);
1265 return lookup
.val
&& lookup
.accessible
? lookup
.val
: nullptr;
1268 tv_rval
ObjectData::getPropIgnoreLateInit(const MemberLookupContext
& ctx
,
1269 const StringData
* key
) const {
1270 auto const lookup
= const_cast<ObjectData
*>(this)
1271 ->getPropImpl
<false, true, true>(ctx
, key
);
1272 return lookup
.val
&& lookup
.accessible
? lookup
.val
: nullptr;
1275 tv_lval
ObjectData::getPropIgnoreAccessibility(const StringData
* key
) {
1276 auto const lookup
= getPropImpl
<false, true, true>(MemberLookupContext(nullptr, (const StringData
*) nullptr), key
);
1277 auto prop
= lookup
.val
;
1278 if (!prop
) return nullptr;
1279 if (lookup
.prop
&& type(prop
) == KindOfUninit
&&
1280 (lookup
.prop
->attrs
& AttrLateInit
)) {
1281 throw_late_init_prop(lookup
.prop
->cls
, key
, false);
1286 //////////////////////////////////////////////////////////////////////
1288 template<ObjectData::PropMode mode
>
1290 tv_lval
ObjectData::propImpl(TypedValue
* tvRef
, const MemberLookupContext
& ctx
,
1291 const StringData
* key
, const ReadonlyOp op
) {
1292 auto constexpr write
= (mode
== PropMode::DimForWrite
);
1293 auto constexpr read
= (mode
== PropMode::ReadNoWarn
) ||
1294 (mode
== PropMode::ReadWarn
);
1295 auto const lookup
= getPropImpl
<write
, read
, false>(ctx
, key
);
1296 auto const prop
= lookup
.val
;
1298 if (lookup
.accessible
) {
1299 auto const checkPropAttrs
= [&]() {
1300 if (mode
== PropMode::DimForWrite
) {
1301 if (UNLIKELY(lookup
.isConst
) && !isBeingConstructed()) {
1302 throwMutateConstProp(lookup
.slot
);
1305 checkReadonly(lookup
, op
, mode
== PropMode::DimForWrite
);
1309 // Property exists, is accessible, and is not unset.
1310 if (type(prop
) != KindOfUninit
) return checkPropAttrs();
1312 if (mode
== PropMode::ReadWarn
) throwUndefPropException(key
);
1313 if (write
) return checkPropAttrs();
1314 return const_cast<TypedValue
*>(&immutable_null_base
);
1317 // Property exists, but it is either protected or private since accessible
1319 auto const propSlot
= m_cls
->lookupDeclProp(key
);
1320 auto const attrs
= m_cls
->declProperties()[propSlot
].attrs
;
1321 auto const priv
= (attrs
& AttrPrivate
) ? "private" : "protected";
1324 "Cannot access %s property %s::$%s",
1326 m_cls
->preClass()->name()->data(),
1331 // First see if native getter is implemented.
1332 if (m_cls
->rtAttribute(Class::HasNativePropHandler
)) {
1333 auto r
= Native::getProp(Object
{this}, StrNR(key
));
1334 if (r
.isInitialized()) {
1335 tvCopy(r
.detach(), *tvRef
);
1340 if (UNLIKELY(!*key
->data())) {
1341 throw_invalid_property_name(StrNR(key
));
1344 if (mode
== PropMode::ReadWarn
) throwUndefPropException(key
);
1345 if (write
) return makeDynProp(key
);
1346 return const_cast<TypedValue
*>(&immutable_null_base
);
1349 tv_lval
ObjectData::prop(
1351 const MemberLookupContext
& ctx
,
1352 const StringData
* key
,
1355 return propImpl
<PropMode::ReadNoWarn
>(tvRef
, ctx
, key
, op
);
1358 tv_lval
ObjectData::propW(
1360 const MemberLookupContext
& ctx
,
1361 const StringData
* key
,
1364 return propImpl
<PropMode::ReadWarn
>(tvRef
, ctx
, key
, op
);
1367 tv_lval
ObjectData::propU(
1369 const MemberLookupContext
& ctx
,
1370 const StringData
* key
,
1373 return propImpl
<PropMode::DimForWrite
>(tvRef
, ctx
, key
, op
);
1376 tv_lval
ObjectData::propD(
1378 const MemberLookupContext
& ctx
,
1379 const StringData
* key
,
1382 return propImpl
<PropMode::DimForWrite
>(tvRef
, ctx
, key
, op
);
1385 bool ObjectData::propIsset(const MemberLookupContext
& ctx
, const StringData
* key
) {
1386 auto const lookup
= getPropImpl
<false, true, true>(ctx
, key
);
1387 if (lookup
.val
&& lookup
.accessible
) {
1388 if (lookup
.val
.type() != KindOfUninit
) {
1389 return lookup
.val
.type() != KindOfNull
;
1391 if (lookup
.prop
&& (lookup
.prop
->attrs
& AttrLateInit
)) {
1396 if (m_cls
->rtAttribute(Class::HasNativePropHandler
)) {
1397 auto r
= Native::issetProp(Object
{this}, StrNR(key
));
1398 if (r
.isInitialized()) return r
.toBoolean();
1404 void ObjectData::setProp(const MemberLookupContext
& ctx
, const StringData
* key
, TypedValue val
, ReadonlyOp op
) {
1405 assertx(tvIsPlausible(val
));
1406 assertx(val
.m_type
!= KindOfUninit
);
1408 auto const lookup
= getPropImpl
<true, false, true>(ctx
, key
);
1409 auto const prop
= lookup
.val
;
1411 if (prop
&& lookup
.accessible
) {
1412 if (UNLIKELY(lookup
.isConst
) && !isBeingConstructed()) {
1413 throwMutateConstProp(lookup
.slot
);
1415 checkReadonly(lookup
, op
, true);
1416 // TODO(T61738946): We can remove the temporary here once we no longer
1417 // coerce class_meth types.
1418 Variant tmp
= tvAsVariant(&val
);
1419 verifyTypeHint(m_cls
, lookup
.prop
, tmp
.asTypedValue());
1420 tvMove(tmp
.detach(), prop
);
1424 // First see if native setter is implemented.
1425 if (m_cls
->rtAttribute(Class::HasNativePropHandler
)) {
1426 auto r
= Native::setProp(Object
{this}, StrNR(key
), tvAsCVarRef(&val
));
1427 if (r
.isInitialized()) return;
1430 if (prop
) raise_error("Cannot access protected property");
1432 if (UNLIKELY(!*key
->data())) {
1433 throw_invalid_property_name(StrNR(key
));
1435 setDynProp(key
, val
);
1438 tv_lval
ObjectData::setOpProp(TypedValue
& tvRef
,
1439 const MemberLookupContext
& ctx
,
1441 const StringData
* key
,
1443 auto const lookup
= getPropImpl
<true, true, false>(ctx
, key
);
1444 auto prop
= lookup
.val
;
1446 if (prop
&& lookup
.accessible
) {
1447 if (UNLIKELY(lookup
.isConst
) && !isBeingConstructed()) {
1448 throwMutateConstProp(lookup
.slot
);
1451 auto const needsCheck
= lookup
.prop
&& [&] {
1452 auto const& tc
= lookup
.prop
->typeConstraint
;
1453 if (setOpNeedsTypeCheck(tc
, op
, prop
)) {
1456 for (auto& ub
: lookup
.prop
->ubs
.m_constraints
) {
1457 if (setOpNeedsTypeCheck(ub
, op
, prop
)) return true;
1464 * If this property has a type-hint, we can't do the setop truly in
1465 * place. We need to verify that the new value satisfies the type-hint
1466 * before assigning back to the property (if we raise a warning and throw,
1467 * we don't want to have already put the value into the prop).
1471 SCOPE_FAIL
{ tvDecRefGen(&temp
); };
1472 setopBody(&temp
, op
, val
);
1473 verifyTypeHint(m_cls
, lookup
.prop
, &temp
);
1476 setopBody(prop
, op
, val
);
1481 if (UNLIKELY(!*key
->data())) throw_invalid_property_name(StrNR(key
));
1483 // Native accessors.
1484 if (m_cls
->rtAttribute(Class::HasNativePropHandler
)) {
1485 auto r
= Native::getProp(Object
{this}, StrNR(key
));
1486 if (r
.isInitialized()) {
1487 setopBody(r
.asTypedValue(), op
, val
);
1488 auto r2
= Native::setProp(Object
{this}, StrNR(key
), r
);
1489 if (r2
.isInitialized()) {
1490 tvCopy(r
.detach(), tvRef
);
1496 if (prop
) raise_error("Cannot access protected property");
1498 // No visible/accessible property, and no applicable native method:
1499 // create a new dynamic property. (We know this is a new property,
1500 // or it would've hit the visible && accessible case above.)
1501 prop
= makeDynProp(key
);
1502 assertx(type(prop
) == KindOfNull
); // cannot exist yet
1503 setopBody(prop
, op
, val
);
1507 TypedValue
ObjectData::incDecProp(const MemberLookupContext
& ctx
, IncDecOp op
, const StringData
* key
) {
1508 auto const lookup
= getPropImpl
<true, true, false>(ctx
, key
);
1509 auto prop
= lookup
.val
;
1511 if (prop
&& lookup
.accessible
) {
1512 if (UNLIKELY(lookup
.isConst
) && !isBeingConstructed()) {
1513 throwMutateConstProp(lookup
.slot
);
1515 if (type(prop
) == KindOfUninit
) {
1520 * If this property has a type-hint, we can't do the inc-dec truely in
1521 * place. We need to verify that the new value satisfies the type-hint
1522 * before assigning back to the property (if we raise a warning and throw,
1523 * we don't want to have already put the value into the prop).
1525 * If the prop is an integer and we're doing the common pre/post inc/dec
1526 * ops, we know the type won't change, so we can skip the type-hint check in
1529 auto const fast
= [&]{
1530 if (Cfg::Eval::CheckPropTypeHints
<= 0) return true;
1531 auto const isAnyCheckable
= lookup
.prop
&& [&] {
1532 if (lookup
.prop
->typeConstraint
.isCheckable()) return true;
1533 for (auto const& ub
: lookup
.prop
->ubs
.m_constraints
) {
1534 if (ub
.isCheckable()) return true;
1538 if (!isAnyCheckable
) return true;
1540 if (!isIntType(type(prop
))) return false;
1542 op
== IncDecOp::PreInc
|| op
== IncDecOp::PostInc
||
1543 op
== IncDecOp::PreDec
|| op
== IncDecOp::PostDec
;
1545 if (fast
) return IncDecBody(op
, tvAssertPlausible(prop
));
1548 tvDup(tvAssertPlausible(*prop
), temp
);
1549 SCOPE_FAIL
{ tvDecRefGen(&temp
); };
1550 auto result
= IncDecBody(op
, &temp
);
1551 SCOPE_FAIL
{ tvDecRefGen(&result
); };
1552 verifyTypeHint(m_cls
, lookup
.prop
, &temp
);
1553 tvMove(temp
, tvAssertPlausible(prop
));
1557 if (UNLIKELY(!*key
->data())) throw_invalid_property_name(StrNR(key
));
1559 // Native accessors.
1560 if (m_cls
->rtAttribute(Class::HasNativePropHandler
)) {
1561 auto r
= Native::getProp(Object
{this}, StrNR(key
));
1562 if (r
.isInitialized()) {
1563 auto const dest
= IncDecBody(op
, r
.asTypedValue());
1564 auto r2
= Native::setProp(Object
{this}, StrNR(key
), r
);
1565 if (r2
.isInitialized()) return dest
;
1569 if (prop
) raise_error("Cannot access protected property");
1571 // No visible/accessible property, and no applicable native method:
1572 // create a new dynamic property. (We know this is a new property,
1573 // or it would've hit the visible && accessible case above.)
1574 prop
= makeDynProp(key
);
1575 assertx(type(prop
) == KindOfNull
); // cannot exist yet
1576 return IncDecBody(op
, prop
);
1579 void ObjectData::unsetProp(const MemberLookupContext
& ctx
, const StringData
* key
) {
1580 auto const lookup
= getPropImpl
<true, false, true>(ctx
, key
);
1581 auto const prop
= lookup
.val
;
1583 if (prop
&& lookup
.accessible
&&
1584 (type(prop
) != KindOfUninit
||
1585 (lookup
.prop
&& (lookup
.prop
->attrs
& AttrLateInit
)))) {
1586 if (lookup
.slot
!= kInvalidSlot
) {
1587 // Declared property.
1588 if (UNLIKELY(lookup
.isConst
) && !isBeingConstructed()) {
1589 throwMutateConstProp(lookup
.slot
);
1591 unsetTypeHint(lookup
.prop
);
1592 tvSet(*uninit_variant
.asTypedValue(), prop
);
1594 // Dynamic property.
1595 dynPropArray().remove(StrNR(key
).asString(), true /* isString */);
1600 // Native unset first.
1601 if (m_cls
->rtAttribute(Class::HasNativePropHandler
)) {
1602 auto r
= Native::unsetProp(Object
{this}, StrNR(key
));
1603 if (r
.isInitialized()) return;
1606 if (prop
&& !lookup
.accessible
) {
1607 // Defined property that is not accessible.
1608 raise_error("Cannot unset inaccessible property");
1611 if (UNLIKELY(!*key
->data())) {
1612 throw_invalid_property_name(StrNR(key
));
1616 void ObjectData::throwObjToIntException(const char* clsName
) {
1617 SystemLib::throwTypecastExceptionObject(folly::sformat(
1618 "Object of class {} could not be converted to int", clsName
));
1621 void ObjectData::throwObjToDoubleException(const char* clsName
) {
1622 SystemLib::throwTypecastExceptionObject(folly::sformat(
1623 "Object of class {} could not be converted to float", clsName
));
1626 void ObjectData::raiseAbstractClassError(Class
* cls
) {
1627 Attr attrs
= cls
->attrs();
1628 raise_error("Cannot instantiate %s %s",
1629 (attrs
& AttrInterface
) ? "interface" :
1630 (attrs
& AttrTrait
) ? "trait" :
1631 (attrs
& (AttrEnum
|AttrEnumClass
)) ? "enum" : "abstract class",
1632 cls
->preClass()->name()->data());
1635 void ObjectData::throwUndefPropException(const StringData
* key
) const {
1636 SystemLib::throwUndefinedPropertyExceptionObject(
1637 folly::sformat("Undefined property: {}::${}",
1638 m_cls
->name()->data(),
1642 void ObjectData::raiseCreateDynamicProp(const StringData
* key
) const {
1643 if (m_cls
== SystemLib::getstdClassClass() ||
1644 m_cls
== SystemLib::get__PHP_Incomplete_ClassClass()) {
1645 // these classes (but not classes derived from them) don't get notices
1648 if (key
->isStatic()) {
1649 raise_notice("Created dynamic property with static name %s::%s",
1650 m_cls
->name()->data(), key
->data());
1652 raise_notice("Created dynamic property with dynamic name %s::%s",
1653 m_cls
->name()->data(), key
->data());
1657 void ObjectData::raiseReadDynamicProp(const StringData
* key
) const {
1658 if (m_cls
== SystemLib::getstdClassClass() ||
1659 m_cls
== SystemLib::get__PHP_Incomplete_ClassClass()) {
1660 // these classes (but not classes derived from them) don't get notices
1663 if (key
->isStatic()) {
1664 raise_notice("Read dynamic property with static name %s::%s",
1665 m_cls
->name()->data(), key
->data());
1667 raise_notice("Read dynamic property with dynamic name %s::%s",
1668 m_cls
->name()->data(), key
->data());
1672 void ObjectData::raiseImplicitInvokeToString() const {
1673 raise_notice("Implicitly invoked %s::__toString", m_cls
->name()->data());
1676 Variant
ObjectData::InvokeSimple(ObjectData
* obj
, const StaticString
& name
,
1677 RuntimeCoeffects providedCoeffects
) {
1678 auto const meth
= obj
->methodNamed(name
.get());
1680 ? g_context
->invokeMethodV(obj
, meth
, InvokeArgs
{}, providedCoeffects
)
1684 Variant
ObjectData::invokeSleep(RuntimeCoeffects provided
) {
1685 return InvokeSimple(this, s___sleep
, provided
);
1688 Variant
ObjectData::invokeToDebugDisplay(RuntimeCoeffects provided
) {
1689 return InvokeSimple(this, s___toDebugDisplay
, provided
);
1692 Variant
ObjectData::invokeWakeup(RuntimeCoeffects provided
) {
1694 SCOPE_EXIT
{ lockObject(); };
1695 return InvokeSimple(this, s___wakeup
, provided
);
1698 Variant
ObjectData::invokeDebugInfo(RuntimeCoeffects provided
) {
1699 return InvokeSimple(this, s___debugInfo
, provided
);
1702 String
ObjectData::invokeToString() {
1703 if (RuntimeOption::EvalFatalOnConvertObjectToString
) {
1704 raise_convert_object_to_string(classname_cstr());
1707 const Func
* method
= m_cls
->getToString();
1709 // If the object does not define a __toString() method, raise a
1710 // recoverable error
1711 raise_recoverable_error(
1712 "Object of class %s could not be converted to string",
1715 // If the user error handler decides to allow execution to continue,
1716 // we return the empty string.
1717 return empty_string();
1719 if (RuntimeOption::EvalNoticeOnImplicitInvokeToString
) {
1720 raiseImplicitInvokeToString();
1722 CoeffectsAutoGuard _
;
1723 auto const tv
= g_context
->invokeMethod(this, method
, InvokeArgs
{},
1724 RuntimeCoeffects::automatic());
1725 if (!isStringType(tv
.m_type
) &&
1726 !isClassType(tv
.m_type
) &&
1727 !isLazyClassType(tv
.m_type
)) {
1728 // Discard the value returned by the __toString() method and raise
1729 // a recoverable error
1731 raise_recoverable_error(
1732 "Method %s::__toString() must return a string value",
1733 m_cls
->preClass()->name()->data());
1734 // If the user error handler decides to allow execution to continue,
1735 // we return the empty string.
1736 return empty_string();
1739 if (tvIsString(tv
)) return String::attach(val(tv
).pstr
);
1740 auto const op
= "__toString()";
1741 if (tvIsLazyClass(tv
)) {
1742 return StrNR
{lazyClassToStringHelper(tv
.m_data
.plazyclass
, op
)};
1744 assertx(isClassType(type(tv
)));
1745 return StrNR(classToStringHelper(tv
.m_data
.pclass
, op
));
1748 bool ObjectData::hasToString() {
1749 return (m_cls
->getToString() != nullptr);
1752 const char* ObjectData::classname_cstr() const {
1753 return getClassName().data();