sync the repo
[hiphop-php.git] / hphp / runtime / base / object-data.cpp
blobcb62bf8d1fd2a5334274a2487834e0be1d389081
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/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>
57 #include <vector>
59 namespace HPHP {
61 //////////////////////////////////////////////////////////////////////
63 TRACE_SET_MOD(runtime);
65 //////////////////////////////////////////////////////////////////////
67 namespace {
69 const StaticString s_clone("__clone");
71 ALWAYS_INLINE
72 void verifyTypeHint(const Class* thisCls,
73 const Class::Prop* prop,
74 tv_lval val) {
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);
88 ALWAYS_INLINE
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(
93 prop->cls,
94 prop->name,
95 prop->typeConstraint.isSoft(),
96 prop->typeConstraint.isUpperBound()
102 //////////////////////////////////////////////////////////////////////
104 // Check that the given property's type matches its type-hint.
105 namespace {
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()) {
119 return true;
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)) {
135 return true;
137 if (prop.type() == KindOfUninit && (propDecl.attrs & AttrLateInit)) {
138 return true;
140 if (!assertATypeHint(propDecl.typeConstraint, prop)) return false;
141 for (auto const& ub : propDecl.ubs.m_constraints) {
142 if (!assertATypeHint(ub, prop)) return false;
144 return true;
147 //////////////////////////////////////////////////////////////////////
149 NEVER_INLINE
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();
156 table.erase(it);
159 NEVER_INLINE
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);
170 } else {
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
210 // collected by GC.
212 // `obj' is being torn down now---be careful about where/how you dereference
213 // it from here on.
215 obj->props()->release(cls->countablePropsEnd());
217 if (UNLIKELY(obj->slowDestroyCheck())) {
218 obj->slowDestroyCases();
219 } else {
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 ///////////////////////////////////////////////////////////////////////////////
233 // class info
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
247 // to be changed.
248 if (isCollection()) {
249 if (RuntimeOption::EvalNoticeOnCollectionToBool) {
250 raise_notice(
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
260 // casting.
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()) {
301 isIterable = true;
302 return Object(this);
304 Object obj(this);
305 CoeffectsAutoGuard _;
306 while (obj->instanceof(SystemLib::getIteratorAggregateClass())) {
307 auto iterator =
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()) {
312 isIterable = true;
313 return Object{o};
315 obj.reset(o);
317 if (!isIterator() && obj->instanceof(SimpleXMLElementLoader::classof())) {
318 if (RuntimeOption::EvalNoticeOnSimpleXMLBehavior) {
319 raise_notice("SimpleXMLElement used as iterator");
321 isIterable = true;
322 return create_object(
323 SimpleXMLElementIteratorLoader::className(),
324 make_vec_array(obj)
327 isIterable = false;
328 return obj;
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());
380 arr = newArr;
381 setAttribute(HasDynPropArr);
382 return arr;
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());
416 auto const propCtx =
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();
433 if (error) {
434 SystemLib::throwUndefinedPropertyExceptionObject(
435 folly::sformat("Undefined property: {}::${}",
436 getClassName().data(),
437 propName.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());
457 auto const propCtx =
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);
472 tvSet(val, prop);
473 return;
476 if (!prop) {
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
489 // apc-object.
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] == '*') {
494 // Protected.
495 ctx = m_cls;
496 } else {
497 // Private.
498 ctx = Class::lookup(cls.get());
499 if (!ctx) continue;
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
506 auto const propCtx =
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());
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(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"
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::classof()))) {
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 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
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 auto const propCtx =
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;
653 while (klass) {
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) {
683 ad = dynProps.get();
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);
699 continue;
702 auto const strKey = key.m_data.pstr;
703 auto const val = dynProps.get()->at(strKey);
704 retArray.set(StrNR(strKey), val, true /* isKey */);
708 return retArray;
711 static bool decode_invoke(const String& s, ObjectData* obj, bool fatal,
712 CallCtx& ctx) {
713 ctx.this_ = obj;
714 ctx.cls = obj->getVMClass();
715 ctx.dynamic = true;
717 ctx.func = ctx.cls->lookupMethod(s.get());
718 if (!ctx.func) {
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);
721 return false;
724 // Null out this_ for statically called methods
725 if (ctx.func->isStaticInPrologue()) {
726 ctx.this_ = nullptr;
728 return true;
731 Variant ObjectData::o_invoke(const String& s, const Variant& params,
732 bool fatal /* = true */) {
733 CallCtx ctx;
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,
746 int count,
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*/) {
753 CallCtx ctx;
754 if (!decode_invoke(s, this, true, ctx)) {
755 return Variant(Variant::NullInit());
758 TypedValue args[5];
759 switch(count) {
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]];
766 case 0: break;
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();
785 ctor(m_cls);
786 always_assert(false);
789 // clone prevents a leak if something throws before clone() returns
790 Object clone;
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());
799 } else {
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));
817 } else {
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());
831 assertx(method);
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
862 return false;
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();
880 bool result = true;
881 IteratePropMemOrder(
882 this,
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())) {
891 result = false;
892 return true;
894 return false;
896 [&](TypedValue key, TypedValue thisVal) {
897 auto const otherVal = otherDynProps->get(key);
898 if (!otherVal.is_init() || !tvEqual(thisVal, otherVal)) {
899 result = false;
900 return true;
902 return false;
905 return result;
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);
941 not_reached();
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
954 return 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) {
968 return 1;
969 } else if (thisSize < otherSize) {
970 return -1;
973 // Prevent circular referenced objects/arrays or deep ones.
974 check_recursion_error();
976 int64_t result = 0;
977 IteratePropToArrayOrder(
978 this,
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());
987 if (cmp != 0) {
988 result = cmp;
989 return true;
991 return false;
993 [&](TypedValue key, TypedValue thisVal) {
994 auto const otherVal = otherDynProps->get(key);
995 if (!otherVal.is_init()) {
996 result = 1;
997 return true;
999 auto cmp = tvCompare(thisVal, otherVal);
1000 if (cmp != 0) {
1001 result = cmp;
1002 return true;
1004 return false;
1007 return result;
1010 ///////////////////////////////////////////////////////////////////////////////
1012 const StaticString
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,
1020 size_t nProps) {
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) {
1027 tvIncRefGen(*lval);
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,
1043 size_t index) {
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,
1061 size_t size,
1062 size_t index,
1063 size_t objoff) {
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,
1077 size_t size,
1078 size_t objoff) {
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);
1110 return retval;
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) {
1160 if (writeMode) {
1161 throwMustBeMutable(lookup.slot);
1162 } else {
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>
1190 ALWAYS_INLINE
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);
1220 return {
1221 prop,
1222 &declProp,
1223 propSlot,
1224 lookup.accessible,
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
1228 forWrite
1229 ? bool(declProp.attrs & AttrIsConst)
1230 : true,
1231 lookup.readonly
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);
1283 return prop;
1286 //////////////////////////////////////////////////////////////////////
1288 template<ObjectData::PropMode mode>
1289 ALWAYS_INLINE
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;
1297 if (prop) {
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);
1306 return prop;
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
1318 // is false.
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";
1323 raise_error(
1324 "Cannot access %s property %s::$%s",
1325 priv,
1326 m_cls->preClass()->name()->data(),
1327 key->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);
1336 return 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(
1350 TypedValue* tvRef,
1351 const MemberLookupContext& ctx,
1352 const StringData* key,
1353 const ReadonlyOp op
1355 return propImpl<PropMode::ReadNoWarn>(tvRef, ctx, key, op);
1358 tv_lval ObjectData::propW(
1359 TypedValue* tvRef,
1360 const MemberLookupContext& ctx,
1361 const StringData* key,
1362 const ReadonlyOp op
1364 return propImpl<PropMode::ReadWarn>(tvRef, ctx, key, op);
1367 tv_lval ObjectData::propU(
1368 TypedValue* tvRef,
1369 const MemberLookupContext& ctx,
1370 const StringData* key,
1371 const ReadonlyOp op
1373 return propImpl<PropMode::DimForWrite>(tvRef, ctx, key, op);
1376 tv_lval ObjectData::propD(
1377 TypedValue* tvRef,
1378 const MemberLookupContext& ctx,
1379 const StringData* key,
1380 const ReadonlyOp op
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)) {
1392 return false;
1396 if (m_cls->rtAttribute(Class::HasNativePropHandler)) {
1397 auto r = Native::issetProp(Object{this}, StrNR(key));
1398 if (r.isInitialized()) return r.toBoolean();
1401 return false;
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);
1421 return;
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,
1440 SetOpOp op,
1441 const StringData* key,
1442 TypedValue* val) {
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)) {
1454 return true;
1456 for (auto& ub : lookup.prop->ubs.m_constraints) {
1457 if (setOpNeedsTypeCheck(ub, op, prop)) return true;
1459 return false;
1460 }();
1462 if (needsCheck) {
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).
1469 TypedValue temp;
1470 tvDup(*prop, temp);
1471 SCOPE_FAIL { tvDecRefGen(&temp); };
1472 setopBody(&temp, op, val);
1473 verifyTypeHint(m_cls, lookup.prop, &temp);
1474 tvMove(temp, prop);
1475 } else {
1476 setopBody(prop, op, val);
1478 return prop;
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);
1491 return &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);
1504 return prop;
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) {
1516 tvWriteNull(prop);
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
1527 * that case.
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;
1536 return false;
1537 }();
1538 if (!isAnyCheckable) return true;
1540 if (!isIntType(type(prop))) return false;
1541 return
1542 op == IncDecOp::PreInc || op == IncDecOp::PostInc ||
1543 op == IncDecOp::PreDec || op == IncDecOp::PostDec;
1544 }();
1545 if (fast) return IncDecBody(op, tvAssertPlausible(prop));
1547 TypedValue temp;
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));
1554 return result;
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);
1593 } else {
1594 // Dynamic property.
1595 dynPropArray().remove(StrNR(key).asString(), true /* isString */);
1597 return;
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(),
1639 key->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
1646 return;
1648 if (key->isStatic()) {
1649 raise_notice("Created dynamic property with static name %s::%s",
1650 m_cls->name()->data(), key->data());
1651 } else {
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
1661 return;
1663 if (key->isStatic()) {
1664 raise_notice("Read dynamic property with static name %s::%s",
1665 m_cls->name()->data(), key->data());
1666 } else {
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());
1679 return meth
1680 ? g_context->invokeMethodV(obj, meth, InvokeArgs{}, providedCoeffects)
1681 : uninit_null();
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) {
1693 unlockObject();
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();
1708 if (!method) {
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",
1713 classname_cstr()
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
1730 tvDecRefGen(tv);
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();
1756 } // HPHP