Fix cloning of objects with lazy APC properties
[hiphop-php.git] / hphp / runtime / base / object-data.cpp
blob8d7cf1b5b58386174cca4eb339dc8c67e11ee13b
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
17 #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>
55 #include <vector>
57 namespace HPHP {
59 //////////////////////////////////////////////////////////////////////
61 TRACE_SET_MOD(runtime);
63 //////////////////////////////////////////////////////////////////////
65 namespace {
67 const StaticString s_clone("__clone");
69 ALWAYS_INLINE
70 void verifyTypeHint(const Class* thisCls,
71 const Class::Prop* prop,
72 tv_lval val) {
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);
87 ALWAYS_INLINE
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(
92 prop->cls,
93 prop->name,
94 prop->typeConstraint.isSoft(),
95 prop->typeConstraint.isUpperBound()
101 //////////////////////////////////////////////////////////////////////
103 // Check that the given property's type matches its type-hint.
104 namespace {
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()) {
118 return true;
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)) {
136 return true;
138 if (prop.type() == KindOfUninit && (propDecl.attrs & AttrLateInit)) {
139 return true;
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;
146 return true;
149 //////////////////////////////////////////////////////////////////////
151 NEVER_INLINE
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();
158 table.erase(it);
161 NEVER_INLINE
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);
172 } else {
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
212 // collected by GC.
214 // `obj' is being torn down now---be careful about where/how you dereference
215 // it from here on.
217 obj->props()->release(cls->countablePropsEnd());
219 if (UNLIKELY(obj->slowDestroyCheck())) {
220 obj->slowDestroyCases();
221 } else {
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 ///////////////////////////////////////////////////////////////////////////////
235 // class info
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
249 // to be changed.
250 if (isCollection()) {
251 if (RuntimeOption::EvalNoticeOnCollectionToBool) {
252 raise_notice(
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
262 // casting.
263 if (RuntimeOption::EvalNoticeOnSimpleXMLBehavior) {
264 raise_notice("SimpleXMLElement to boolean cast");
266 return SimpleXMLElement_objectCast(this, KindOfBoolean).toBoolean();
269 always_assert(false);
270 return 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()) {
304 isIterable = true;
305 return Object(this);
307 Object obj(this);
308 CoeffectsAutoGuard _;
309 while (obj->instanceof(SystemLib::s_IteratorAggregateClass)) {
310 auto iterator =
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()) {
315 isIterable = true;
316 return Object{o};
318 obj.reset(o);
320 if (!isIterator() && obj->instanceof(SimpleXMLElement_classof())) {
321 if (RuntimeOption::EvalNoticeOnSimpleXMLBehavior) {
322 raise_notice("SimpleXMLElement used as iterator");
324 isIterable = true;
325 return create_object(
326 s_SimpleXMLElementIterator,
327 make_vec_array(obj)
330 isIterable = false;
331 return obj;
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());
383 arr = newArr;
384 setAttribute(HasDynPropArr);
385 return arr;
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();
435 if (error) {
436 SystemLib::throwUndefinedPropertyExceptionObject(
437 folly::sformat("Undefined property: {}::${}",
438 getClassName().data(),
439 propName.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);
473 tvSet(val, prop);
474 return;
477 if (!prop) {
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
490 // apc-object.
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] == '*') {
495 // Protected.
496 ctx = m_cls;
497 } else {
498 // Private.
499 ctx = Class::lookup(cls.get());
500 if (!ctx) continue;
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());
526 return;
529 auto cls = m_cls;
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(
539 this,
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"
589 not_reached();
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();
594 } else {
595 auto ret = Array::CreateDict();
596 o_getArray(ret, pubOnly, ignoreLateInit);
597 return ret;
601 template
602 Array ObjectData::toArray<IntishCast::None>(bool, bool) const;
603 template
604 Array ObjectData::toArray<IntishCast::Cast>(bool, bool) const;
607 namespace {
609 size_t getPropertyIfAccessible(ObjectData* obj,
610 const MemberLookupContext& ctx,
611 const StringData* key,
612 Array& properties,
613 size_t propLeft) {
614 auto const prop = obj->getProp(ctx, key);
615 if (prop && prop.type() != KindOfUninit) {
616 --propLeft;
617 properties.set(StrNR(key), prop.tv(), true);
619 return propLeft;
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
635 return props;
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;
656 while (klass) {
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) {
686 ad = dynProps.get();
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);
702 continue;
705 auto const strKey = key.m_data.pstr;
706 auto const val = dynProps.get()->at(strKey);
707 retArray.set(StrNR(strKey), val, true /* isKey */);
711 return retArray;
714 static bool decode_invoke(const String& s, ObjectData* obj, bool fatal,
715 CallCtx& ctx) {
716 ctx.this_ = obj;
717 ctx.cls = obj->getVMClass();
718 ctx.dynamic = true;
720 ctx.func = ctx.cls->lookupMethod(s.get());
721 if (!ctx.func) {
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);
724 return false;
727 // Null out this_ for statically called methods
728 if (ctx.func->isStaticInPrologue()) {
729 ctx.this_ = nullptr;
731 return true;
734 Variant ObjectData::o_invoke(const String& s, const Variant& params,
735 bool fatal /* = true */) {
736 CallCtx ctx;
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,
749 int count,
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*/) {
756 CallCtx ctx;
757 if (!decode_invoke(s, this, true, ctx)) {
758 return Variant(Variant::NullInit());
761 TypedValue args[5];
762 switch(count) {
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]);
769 case 0: break;
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();
788 ctor(m_cls);
789 always_assert(false);
792 // clone prevents a leak if something throws before clone() returns
793 Object clone;
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());
802 } else {
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));
820 } else {
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());
834 assertx(method);
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
865 return false;
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();
883 bool result = true;
884 IteratePropMemOrder(
885 this,
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())) {
894 result = false;
895 return true;
897 return false;
899 [&](TypedValue key, TypedValue thisVal) {
900 auto const otherVal = otherDynProps->get(key);
901 if (!otherVal.is_init() || !tvEqual(thisVal, otherVal)) {
902 result = false;
903 return true;
905 return false;
908 return result;
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);
944 not_reached();
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
957 return 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) {
971 return 1;
972 } else if (thisSize < otherSize) {
973 return -1;
976 // Prevent circular referenced objects/arrays or deep ones.
977 check_recursion_error();
979 int64_t result = 0;
980 IteratePropToArrayOrder(
981 this,
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());
990 if (cmp != 0) {
991 result = cmp;
992 return true;
994 return false;
996 [&](TypedValue key, TypedValue thisVal) {
997 auto const otherVal = otherDynProps->get(key);
998 if (!otherVal.is_init()) {
999 result = 1;
1000 return true;
1002 auto cmp = tvCompare(thisVal, otherVal);
1003 if (cmp != 0) {
1004 result = cmp;
1005 return true;
1007 return false;
1010 return result;
1013 ///////////////////////////////////////////////////////////////////////////////
1015 const StaticString
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,
1023 size_t nProps) {
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) {
1030 tvIncRefGen(*lval);
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,
1046 size_t index) {
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,
1064 size_t size,
1065 size_t index,
1066 size_t objoff) {
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,
1080 size_t size,
1081 size_t objoff) {
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);
1113 return retval;
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) {
1163 if (writeMode) {
1164 throwMustBeMutable(lookup.slot);
1165 } else {
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>
1193 ALWAYS_INLINE
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);
1223 return {
1224 prop,
1225 &declProp,
1226 propSlot,
1227 lookup.accessible,
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
1231 forWrite
1232 ? bool(declProp.attrs & AttrIsConst)
1233 : true,
1234 lookup.readonly
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);
1286 return prop;
1289 //////////////////////////////////////////////////////////////////////
1291 template<ObjectData::PropMode mode>
1292 ALWAYS_INLINE
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;
1300 if (prop) {
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);
1309 return prop;
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
1321 // is false.
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";
1326 raise_error(
1327 "Cannot access %s property %s::$%s",
1328 priv,
1329 m_cls->preClass()->name()->data(),
1330 key->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);
1339 return 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(
1353 TypedValue* tvRef,
1354 const MemberLookupContext& ctx,
1355 const StringData* key,
1356 const ReadonlyOp op
1358 return propImpl<PropMode::ReadNoWarn>(tvRef, ctx, key, op);
1361 tv_lval ObjectData::propW(
1362 TypedValue* tvRef,
1363 const MemberLookupContext& ctx,
1364 const StringData* key,
1365 const ReadonlyOp op
1367 return propImpl<PropMode::ReadWarn>(tvRef, ctx, key, op);
1370 tv_lval ObjectData::propU(
1371 TypedValue* tvRef,
1372 const MemberLookupContext& ctx,
1373 const StringData* key,
1374 const ReadonlyOp op
1376 return propImpl<PropMode::DimForWrite>(tvRef, ctx, key, op);
1379 tv_lval ObjectData::propD(
1380 TypedValue* tvRef,
1381 const MemberLookupContext& ctx,
1382 const StringData* key,
1383 const ReadonlyOp op
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)) {
1395 return false;
1399 if (m_cls->rtAttribute(Class::HasNativePropHandler)) {
1400 auto r = Native::issetProp(Object{this}, StrNR(key));
1401 if (r.isInitialized()) return r.toBoolean();
1404 return false;
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);
1424 return;
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,
1443 SetOpOp op,
1444 const StringData* key,
1445 TypedValue* val) {
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)) {
1457 return true;
1459 for (auto& ub : lookup.prop->ubs) {
1460 if (setOpNeedsTypeCheck(ub, op, prop)) return true;
1462 return false;
1463 }();
1465 if (needsCheck) {
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).
1472 TypedValue temp;
1473 tvDup(*prop, temp);
1474 SCOPE_FAIL { tvDecRefGen(&temp); };
1475 setopBody(&temp, op, val);
1476 verifyTypeHint(m_cls, lookup.prop, &temp);
1477 tvMove(temp, prop);
1478 } else {
1479 setopBody(prop, op, val);
1481 return prop;
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);
1494 return &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);
1507 return prop;
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) {
1519 tvWriteNull(prop);
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
1530 * that case.
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;
1539 return false;
1540 }();
1541 if (!isAnyCheckable) return true;
1543 if (!isIntType(type(prop))) return false;
1544 return
1545 op == IncDecOp::PreInc || op == IncDecOp::PostInc ||
1546 op == IncDecOp::PreDec || op == IncDecOp::PostDec;
1547 }();
1548 if (fast) return IncDecBody(op, tvAssertPlausible(prop));
1550 TypedValue temp;
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));
1557 return result;
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);
1596 } else {
1597 // Dynamic property.
1598 dynPropArray().remove(StrNR(key).asString(), true /* isString */);
1600 return;
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(),
1642 key->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
1649 return;
1651 if (key->isStatic()) {
1652 raise_notice("Created dynamic property with static name %s::%s",
1653 m_cls->name()->data(), key->data());
1654 } else {
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
1664 return;
1666 if (key->isStatic()) {
1667 raise_notice("Read dynamic property with static name %s::%s",
1668 m_cls->name()->data(), key->data());
1669 } else {
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());
1682 return meth
1683 ? g_context->invokeMethodV(obj, meth, InvokeArgs{}, providedCoeffects)
1684 : uninit_null();
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) {
1696 unlockObject();
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();
1711 if (!method) {
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",
1716 classname_cstr()
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
1733 tvDecRefGen(tv);
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();
1758 } // HPHP