2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
17 #include "hphp/runtime/base/object-data.h"
19 #include "hphp/runtime/base/builtin-functions.h"
20 #include "hphp/runtime/base/collections.h"
21 #include "hphp/runtime/base/container-functions.h"
22 #include "hphp/runtime/base/exceptions.h"
23 #include "hphp/runtime/base/execution-context.h"
24 #include "hphp/runtime/base/externals.h"
25 #include "hphp/runtime/base/runtime-error.h"
26 #include "hphp/runtime/base/req-containers.h"
27 #include "hphp/runtime/base/tv-refcount.h"
28 #include "hphp/runtime/base/tv-type.h"
29 #include "hphp/runtime/base/variable-serializer.h"
30 #include "hphp/runtime/base/mixed-array-defs.h"
32 #include "hphp/runtime/ext/generator/ext_generator.h"
33 #include "hphp/runtime/ext/simplexml/ext_simplexml.h"
34 #include "hphp/runtime/ext/datetime/ext_datetime.h"
35 #include "hphp/runtime/ext/std/ext_std_closure.h"
37 #include "hphp/runtime/vm/class.h"
38 #include "hphp/runtime/vm/member-operations.h"
39 #include "hphp/runtime/vm/native-data.h"
40 #include "hphp/runtime/vm/native-prop-handler.h"
41 #include "hphp/runtime/vm/jit/translator-inline.h"
42 #include "hphp/runtime/vm/repo.h"
43 #include "hphp/runtime/vm/repo-global-data.h"
45 #include "hphp/system/systemlib.h"
47 #include <folly/Hash.h>
48 #include <folly/ScopeGuard.h>
54 //////////////////////////////////////////////////////////////////////
56 // current maximum object identifier
57 __thread
uint32_t ObjectData::os_max_id
;
59 TRACE_SET_MOD(runtime
);
62 s_offsetGet("offsetGet"),
66 static Array
convert_to_array(const ObjectData
* obj
, Class
* cls
) {
67 auto const prop
= obj
->getProp(cls
, s_storage
.get());
69 // We currently do not special case ArrayObjects / ArrayIterators in
70 // reflectionClass. Until, either ArrayObject moves to HNI or a special
71 // case is added to reflection unset should be turned off.
72 assert(prop
.has_val() /* && prop.type() != KindOfUninit */);
73 return tvAsCVarRef(prop
.tv_ptr()).toArray();
77 static_assert(sizeof(ObjectData
) == (use_lowptr
? 16 : 20),
78 "Change this only on purpose");
80 static_assert(sizeof(ObjectData
) == (use_lowptr
? 16 : 24),
81 "Change this only on purpose");
84 //////////////////////////////////////////////////////////////////////
87 static void invoke_destructor(ObjectData
* obj
, const Func
* dtor
) {
89 // Call the destructor method
90 g_context
->invokeMethodV(obj
, dtor
);
92 // Swallow any exceptions that escape the __destruct method
93 handle_destructor_exception();
97 NEVER_INLINE
bool ObjectData::destructImpl() {
99 auto const dtor
= m_cls
->getDtor();
100 if (!dtor
) return true;
102 // We don't run PHP destructors while we're unwinding for a C++
103 // exception. We want to minimize the PHP code we run while propagating
104 // fatals, so we do this check here on a very common path, in the
105 // relatively slower case.
106 if (g_context
->m_unwindingCppException
) return true;
108 // Some decref paths call release() when --count == 0 and some call it when
109 // count == 1. This difference only matters for objects that resurrect
110 // themselves in their destructors, so make sure count is consistent here.
111 assert(m_count
== 0 || m_count
== 1);
112 m_count
= static_cast<RefCount
>(0);
114 // We raise the refcount around the call to __destruct(). This is to prevent
115 // the refcount from going to zero when the destructor returns.
116 CountableHelper
h(this);
117 invoke_destructor(this, dtor
);
118 return hasExactlyOneRef();
121 void ObjectData::destructForExit() {
122 assert(RuntimeOption::EnableObjDestructCall
);
123 auto const dtor
= m_cls
->getDtor();
125 g_context
->m_liveBCObjs
.erase(this);
128 if (noDestruct()) return;
131 // We're exiting, so there should not be any live faults.
132 assert(g_context
->m_faults
.empty());
133 assert(!g_context
->m_unwindingCppException
);
135 CountableHelper
h(this);
136 invoke_destructor(this, dtor
);
140 static void freeDynPropArray(ObjectData
* inst
) {
141 auto& table
= g_context
->dynPropTable
;
142 auto it
= table
.find(inst
);
143 assert(it
!= end(table
));
144 assert(it
->second
.arr().isPHPArray());
145 it
->second
.destroy();
150 void ObjectData::releaseNoObjDestructCheck() noexcept
{
151 assert(kindIsValid());
153 // Destructors are unsupported in one-bit reference counting mode.
154 if (!one_bit_refcount
&& UNLIKELY(!getAttribute(NoDestructor
))) {
155 if (UNLIKELY(!destructImpl())) return;
158 auto const cls
= getVMClass();
160 if (UNLIKELY(hasInstanceDtor())) {
161 return cls
->instanceDtor()(this, cls
);
164 // `this' is being torn down now---be careful about where/how you dereference
165 // this from here on.
167 auto const nProps
= size_t{cls
->numDeclProperties()};
168 auto prop
= reinterpret_cast<TypedValue
*>(this + 1);
169 auto const stop
= prop
+ nProps
;
170 for (; prop
!= stop
; ++prop
) {
174 // Deliberately reload `attrs' to check for dynamic properties. This made
175 // gcc generate better code at the time it was done (saving a spill).
176 if (UNLIKELY(getAttribute(HasDynPropArr
))) freeDynPropArray(this);
178 auto& pmax
= os_max_id
;
179 if (o_id
&& o_id
== pmax
) --pmax
;
183 reinterpret_cast<char*>(stop
) - reinterpret_cast<char*>(this);
184 assert(size
== sizeForNProps(nProps
));
185 tl_heap
->objFree(this, size
);
186 AARCH64_WALKABLE_FRAME();
190 static void tail_call_remove_live_bc_obj(ObjectData
* obj
) {
191 g_context
->m_liveBCObjs
.erase(obj
);
192 return obj
->releaseNoObjDestructCheck();
195 void ObjectData::release() noexcept
{
196 assert(kindIsValid());
197 if (UNLIKELY(RuntimeOption::EnableObjDestructCall
&& m_cls
->getDtor())) {
198 tail_call_remove_live_bc_obj(this);
199 AARCH64_WALKABLE_FRAME();
202 releaseNoObjDestructCheck();
203 AARCH64_WALKABLE_FRAME();
206 ///////////////////////////////////////////////////////////////////////////////
209 StrNR
ObjectData::getClassName() const {
210 return m_cls
->preClass()->nameStr();
213 bool ObjectData::instanceof(const String
& s
) const {
214 assert(kindIsValid());
215 auto const cls
= Unit::lookupClass(s
.get());
216 return cls
&& instanceof(cls
);
219 bool ObjectData::toBooleanImpl() const noexcept
{
220 // Note: if you add more cases here, hhbbc/class-util.cpp also needs
222 if (isCollection()) {
223 return collections::toBool(this);
226 if (instanceof(SimpleXMLElement_classof())) {
227 // SimpleXMLElement is the only non-collection class that has custom bool
229 return SimpleXMLElement_objectCast(this, KindOfBoolean
).toBoolean();
232 always_assert(false);
236 int64_t ObjectData::toInt64Impl() const noexcept
{
237 // SimpleXMLElement is the only class that has proper custom int casting.
238 assert(instanceof(SimpleXMLElement_classof()));
239 return SimpleXMLElement_objectCast(this, KindOfInt64
).toInt64();
242 double ObjectData::toDoubleImpl() const noexcept
{
243 // SimpleXMLElement is the only class that has custom double casting.
244 assert(instanceof(SimpleXMLElement_classof()));
245 return SimpleXMLElement_objectCast(this, KindOfDouble
).toDouble();
248 ///////////////////////////////////////////////////////////////////////////////
249 // instance methods and properties
251 const StaticString
s_getIterator("getIterator");
253 Object
ObjectData::iterableObject(bool& isIterable
,
254 bool mayImplementIterator
/* = true */) {
255 assert(mayImplementIterator
|| !isIterator());
256 if (mayImplementIterator
&& isIterator()) {
261 while (obj
->instanceof(SystemLib::s_IteratorAggregateClass
)) {
262 auto iterator
= obj
->o_invoke_few_args(s_getIterator
, 0);
263 if (!iterator
.isObject()) break;
264 auto o
= iterator
.getObjectData();
265 if (o
->isIterator()) {
271 if (!isIterator() && obj
->instanceof(SimpleXMLElement_classof())) {
273 return create_object(
274 s_SimpleXMLElementIterator
,
275 make_packed_array(obj
)
282 Array
& ObjectData::dynPropArray() const {
283 assert(getAttribute(HasDynPropArr
));
284 assert(g_context
->dynPropTable
.count(this));
285 assert(g_context
->dynPropTable
[this].arr().isPHPArray());
286 return g_context
->dynPropTable
[this].arr();
289 Array
& ObjectData::reserveProperties(int numDynamic
/* = 2 */) {
290 if (getAttribute(HasDynPropArr
)) {
291 return dynPropArray();
295 setDynPropArray(Array::attach(MixedArray::MakeReserveMixed(numDynamic
)));
298 Array
& ObjectData::setDynPropArray(const Array
& newArr
) {
299 assert(!g_context
->dynPropTable
.count(this));
300 assert(!getAttribute(HasDynPropArr
));
301 assert(newArr
.isPHPArray());
303 auto& arr
= g_context
->dynPropTable
[this].arr();
304 assert(arr
.isPHPArray());
306 setAttribute(HasDynPropArr
);
311 TypedValue
* ObjectData::makeDynProp(K key
, AccessFlags flags
) {
312 SuppressHackArrCompatNotices shacn
;
313 return reserveProperties().lvalAt(key
, flags
).tv_ptr();
316 Variant
ObjectData::o_get(const String
& propName
, bool error
/* = true */,
317 const String
& context
/*= null_string*/) {
318 assert(kindIsValid());
320 // This is not (just) a check for empty string; property names that start
321 // with null are intentionally being rejected here.
322 if (UNLIKELY(!*propName
.data())) {
323 throw_invalid_property_name(propName
);
326 Class
* ctx
= nullptr;
327 if (!context
.empty()) {
328 ctx
= Unit::lookupClass(context
.get());
331 // Can't use propImpl here because if the property is not accessible and
332 // there is no magic __get, propImpl will raise_error("Cannot access ...",
333 // but o_get will only (maybe) raise_notice("Undefined property ..." :-(
335 auto const lookup
= getPropImpl(ctx
, propName
.get(), false);
336 auto prop
= lookup
.prop
;
337 if (lookup
.accessible
&& prop
&& prop
->m_type
!= KindOfUninit
) {
338 return tvAsVariant(prop
);
341 if (getAttribute(UseGet
)) {
342 if (auto r
= invokeGet(propName
.get())) {
343 return std::move(tvAsVariant(&r
.val
));
348 raise_notice("Undefined property: %s::$%s", getClassName().data(),
352 return uninit_null();
355 void ObjectData::o_set(const String
& propName
, const Variant
& v
,
356 const String
& context
/* = null_string */) {
357 assert(kindIsValid());
359 // This is not (just) a check for empty string; property names that start
360 // with null are intentionally being rejected here.
361 if (UNLIKELY(!*propName
.data())) {
362 throw_invalid_property_name(propName
);
365 Class
* ctx
= nullptr;
366 if (!context
.empty()) {
367 ctx
= Unit::lookupClass(context
.get());
370 // Can't use setProp here because if the property is not accessible and
371 // there is no magic __set, setProp will raise_error("Cannot access ...",
372 // but o_set will skip writing and return normally. Also, if we try to
373 // invoke __set and fail due to recursion, setProp will fall back to writing
374 // the property normally, but o_set will just skip writing and return :-(
376 bool useSet
= getAttribute(UseSet
);
378 auto const lookup
= getPropImpl(ctx
, propName
.get(), true);
379 auto prop
= lookup
.prop
;
380 if (prop
&& lookup
.accessible
) {
381 if (!useSet
|| prop
->m_type
!= KindOfUninit
) {
382 tvSet(tvToInitCell(*v
.asTypedValue()), *prop
);
388 invokeSet(propName
.get(), *v
.asCell());
390 reserveProperties().set(propName
, tvToInitCell(*v
.asTypedValue()), true);
394 void ObjectData::o_setArray(const Array
& properties
) {
395 for (ArrayIter
iter(properties
); iter
; ++iter
) {
396 String k
= iter
.first().toString();
397 Class
* ctx
= nullptr;
398 // If the key begins with a NUL, it's a private or protected property. Read
399 // the class name from between the two NUL bytes.
401 // Note: if you change this, you need to change similar logic in
403 if (!k
.empty() && k
[0] == '\0') {
404 int subLen
= k
.find('\0', 1) + 1;
405 String cls
= k
.substr(1, subLen
- 2);
406 if (cls
.size() == 1 && cls
[0] == '*') {
411 ctx
= Unit::lookupClass(cls
.get());
414 k
= k
.substr(subLen
);
417 setProp(ctx
, k
.get(), tvAssertCell(iter
.secondRval().tv()));
421 void ObjectData::o_getArray(Array
& props
, bool pubOnly
/* = false */) const {
422 assert(kindIsValid());
424 // Fast path for classes with no declared properties
425 if (!m_cls
->numDeclProperties() && getAttribute(HasDynPropArr
)) {
426 props
= dynPropArray();
429 // The declared properties in the resultant array should be a permutation of
430 // propVec. They appear in the following order: go most-to-least-derived in
431 // the inheritance hierarchy, inserting properties in declaration order (with
432 // the wrinkle that overridden properties should appear only once, with the
433 // access level given to it in its most-derived declaration).
435 // This is needed to keep track of which elements have been inserted. This is
436 // the smoothest way to get overridden properties right.
437 std::vector
<bool> inserted(m_cls
->numDeclProperties(), false);
439 // Iterate over declared properties and insert {mangled name --> prop} pairs.
440 const Class
* cls
= m_cls
;
442 getProps(cls
, pubOnly
, cls
->preClass(), props
, inserted
);
443 for (auto const& traitCls
: cls
->usedTraitClasses()) {
444 getTraitProps(cls
, pubOnly
, traitCls
.get(), props
, inserted
);
449 // Iterate over dynamic properties and insert {name --> prop} pairs.
450 if (UNLIKELY(getAttribute(HasDynPropArr
))) {
451 auto& dynProps
= dynPropArray();
452 if (!dynProps
.empty()) {
453 for (ArrayIter
it(dynProps
.get()); !it
.end(); it
.next()) {
454 props
.setWithRef(it
.first(), it
.secondVal(), true);
460 // a constant for arrayobjects that changes the way the array is
461 // converted to an object
462 const int64_t ARRAYOBJ_STD_PROP_LIST
= 1;
464 const StaticString
s_flags("flags");
466 Array
ObjectData::toArray(bool pubOnly
/* = false */) const {
467 assert(kindIsValid());
469 // We can quickly tell if this object is a collection, which lets us avoid
470 // checking for each class in turn if it's not one.
471 if (isCollection()) {
472 return collections::toArray(this);
473 } else if (UNLIKELY(getAttribute(CallToImpl
))) {
474 // If we end up with other classes that need special behavior, turn the
475 // assert into an if and add cases.
476 assert(instanceof(SimpleXMLElement_classof()));
477 return SimpleXMLElement_objectCast(this, KindOfArray
).toArray();
478 } else if (UNLIKELY(instanceof(SystemLib::s_ArrayObjectClass
))) {
479 auto const flags
= getProp(SystemLib::s_ArrayObjectClass
, s_flags
.get());
480 assert(flags
.has_val());
482 if (UNLIKELY(flags
.type() == KindOfInt64
&&
483 flags
.val().num
== ARRAYOBJ_STD_PROP_LIST
)) {
484 auto ret
= Array::Create();
485 o_getArray(ret
, true);
488 return convert_to_array(this, SystemLib::s_ArrayObjectClass
);
489 } else if (UNLIKELY(instanceof(SystemLib::s_ArrayIteratorClass
))) {
490 return convert_to_array(this, SystemLib::s_ArrayIteratorClass
);
491 } else if (UNLIKELY(instanceof(c_Closure::classof()))) {
492 return Array::Create(Object(const_cast<ObjectData
*>(this)));
493 } else if (UNLIKELY(instanceof(DateTimeData::getClass()))) {
494 return Native::data
<DateTimeData
>(this)->getDebugInfo();
496 auto ret
= Array::Create();
497 o_getArray(ret
, pubOnly
);
504 size_t getPropertyIfAccessible(ObjectData
* obj
,
506 const StringData
* key
,
507 ObjectData::IterMode mode
,
510 if (mode
== ObjectData::CreateRefs
) {
511 auto const prop
= obj
->vGetProp(ctx
, key
);
514 properties
.setRef(StrNR(key
), tvAsVariant(prop
.tv_ptr()), true);
517 auto const prop
= obj
->getProp(ctx
, key
);
518 if (prop
.has_val() && prop
.type() != KindOfUninit
) {
520 if (mode
== ObjectData::EraseRefs
) {
521 properties
.set(StrNR(key
), prop
.tv(), true);
523 properties
.setWithRef(StrNR(key
), prop
.tv(), true);
532 Array
ObjectData::o_toIterArray(const String
& context
, IterMode mode
) {
533 if (mode
== PreserveRefs
&& !m_cls
->numDeclProperties()) {
534 if (getAttribute(HasDynPropArr
)) return dynPropArray();
535 return Array::Create();
538 Array
* dynProps
= nullptr;
539 size_t accessibleProps
= m_cls
->declPropNumAccessible();
540 size_t size
= accessibleProps
;
541 if (getAttribute(HasDynPropArr
)) {
542 dynProps
= &dynPropArray();
543 size
+= dynProps
->size();
545 Array retArray
{ Array::attach(MixedArray::MakeReserveMixed(size
)) };
547 Class
* ctx
= nullptr;
548 if (!context
.empty()) {
549 ctx
= Unit::lookupClass(context
.get());
552 // Get all declared properties first, bottom-to-top in the inheritance
553 // hierarchy, in declaration order.
554 const Class
* klass
= m_cls
;
556 const PreClass::Prop
* props
= klass
->preClass()->properties();
557 const size_t numProps
= klass
->preClass()->numProperties();
559 for (size_t i
= 0; i
< numProps
; ++i
) {
560 auto key
= const_cast<StringData
*>(props
[i
].name());
561 accessibleProps
= getPropertyIfAccessible(
562 this, ctx
, key
, mode
, retArray
, accessibleProps
);
564 klass
= klass
->parent();
566 if (!(m_cls
->attrs() & AttrNoExpandTrait
) && accessibleProps
> 0) {
567 // we may have properties from traits
568 for (auto const& prop
: m_cls
->declProperties()) {
569 auto const key
= prop
.name
.get();
570 if (!retArray
.get()->exists(key
)) {
571 accessibleProps
= getPropertyIfAccessible(
572 this, ctx
, key
, mode
, retArray
, accessibleProps
);
573 if (accessibleProps
== 0) break;
578 // Now get dynamic properties.
580 auto ad
= dynProps
->get();
581 ssize_t iter
= ad
->iter_begin();
582 auto pos_limit
= ad
->iter_end();
583 while (iter
!= pos_limit
) {
584 auto const key
= dynProps
->get()->nvGetKey(iter
);
585 iter
= dynProps
->get()->iter_advance(iter
);
587 // You can get this if you cast an array to object. These
588 // properties must be dynamic because you can't declare a
589 // property with a non-string name.
590 if (UNLIKELY(!isStringType(key
.m_type
))) {
591 assert(key
.m_type
== KindOfInt64
);
594 auto& lval
= tvAsVariant(dynProps
->lvalAt(key
.m_data
.num
).tv_ptr());
595 retArray
.setRef(key
.m_data
.num
, lval
);
599 auto const val
= dynProps
->get()->at(key
.m_data
.num
);
600 retArray
.set(key
.m_data
.num
, val
);
604 auto const val
= dynProps
->get()->at(key
.m_data
.num
);
605 retArray
.setWithRef(key
.m_data
.num
, val
);
612 auto const strKey
= key
.m_data
.pstr
;
615 auto& lval
= tvAsVariant(
616 dynProps
->lvalAt(StrNR(strKey
), AccessFlags::Key
).tv_ptr()
618 retArray
.setRef(StrNR(strKey
), lval
, true /* isKey */);
622 auto const val
= dynProps
->get()->at(strKey
);
623 retArray
.set(StrNR(strKey
), val
, true /* isKey */);
627 auto const val
= dynProps
->get()->at(strKey
);
628 retArray
.setWithRef(make_tv
<KindOfString
>(strKey
),
629 val
, true /* isKey */);
640 static bool decode_invoke(const String
& s
, ObjectData
* obj
, bool fatal
,
643 ctx
.cls
= obj
->getVMClass();
644 ctx
.invName
= nullptr;
646 ctx
.func
= ctx
.cls
->lookupMethod(s
.get());
648 // Null out this_ for statically called methods
649 if (ctx
.func
->isStaticInPrologue()) {
653 // If this_ is non-null AND we could not find a method, try
654 // looking up __call in cls's method table
655 ctx
.func
= ctx
.cls
->lookupMethod(s_call
.get());
658 // Bail if we couldn't find the method or __call
659 o_invoke_failed(ctx
.cls
->name()->data(), s
.data(), fatal
);
662 // We found __call! Stash the original name into invName.
663 assert(!(ctx
.func
->attrs() & AttrStatic
));
664 ctx
.invName
= s
.get();
665 ctx
.invName
->incRefCount();
670 Variant
ObjectData::o_invoke(const String
& s
, const Variant
& params
,
671 bool fatal
/* = true */) {
673 if (!decode_invoke(s
, this, fatal
, ctx
) ||
674 (!isContainer(params
) && !params
.isNull())) {
675 return Variant(Variant::NullInit());
677 return Variant::attach(
678 g_context
->invokeFunc(ctx
, params
)
682 #define INVOKE_FEW_ARGS_IMPL3 \
683 const Variant& a0, const Variant& a1, const Variant& a2
684 #define INVOKE_FEW_ARGS_IMPL6 \
685 INVOKE_FEW_ARGS_IMPL3, \
686 const Variant& a3, const Variant& a4, const Variant& a5
687 #define INVOKE_FEW_ARGS_IMPL10 \
688 INVOKE_FEW_ARGS_IMPL6, \
689 const Variant& a6, const Variant& a7, const Variant& a8, const Variant& a9
690 #define INVOKE_FEW_ARGS_IMPL_ARGS INVOKE_FEW_ARGS(IMPL,INVOKE_FEW_ARGS_COUNT)
692 Variant
ObjectData::o_invoke_few_args(const String
& s
, int count
,
693 INVOKE_FEW_ARGS_IMPL_ARGS
) {
696 if (!decode_invoke(s
, this, true, ctx
)) {
697 return Variant(Variant::NullInit());
700 TypedValue args
[INVOKE_FEW_ARGS_COUNT
];
702 default: not_implemented();
703 #if INVOKE_FEW_ARGS_COUNT > 6
704 case 10: tvCopy(*a9
.asTypedValue(), args
[9]);
705 case 9: tvCopy(*a8
.asTypedValue(), args
[8]);
706 case 8: tvCopy(*a7
.asTypedValue(), args
[7]);
707 case 7: tvCopy(*a6
.asTypedValue(), args
[6]);
709 #if INVOKE_FEW_ARGS_COUNT > 3
710 case 6: tvCopy(*a5
.asTypedValue(), args
[5]);
711 case 5: tvCopy(*a4
.asTypedValue(), args
[4]);
712 case 4: tvCopy(*a3
.asTypedValue(), args
[3]);
714 case 3: tvCopy(*a2
.asTypedValue(), args
[2]);
715 case 2: tvCopy(*a1
.asTypedValue(), args
[1]);
716 case 1: tvCopy(*a0
.asTypedValue(), args
[0]);
720 return Variant::attach(
721 g_context
->invokeFuncFew(ctx
, count
, args
)
725 ObjectData
* ObjectData::clone() {
726 if (isCppBuiltin()) {
727 if (isCollection()) return collections::clone(this);
728 if (instanceof(c_Closure::classof())) {
729 return c_Closure::fromObject(this)->clone();
731 assertx(instanceof(c_WaitHandle::classof()));
732 // cloning WaitHandles is not allowed
733 // invoke the instanceCtor to get the right sort of exception
734 auto const ctor
= m_cls
->instanceCtor();
736 always_assert(false);
739 // clone prevents a leak if something throws before clone() returns
741 auto const nProps
= m_cls
->numDeclProperties();
742 if (getAttribute(HasNativeData
)) {
743 assertx(m_cls
->instanceCtor() == Native::nativeDataInstanceCtor
);
744 clone
= Object::attach(
745 Native::nativeDataInstanceCopyCtor(this, m_cls
, nProps
)
747 assertx(clone
->hasExactlyOneRef());
748 assertx(clone
->hasInstanceDtor());
750 auto const size
= sizeForNProps(nProps
);
751 auto const obj
= new (tl_heap
->objMalloc(size
))
752 ObjectData(m_cls
, InitRaw
{}, m_cls
->getODAttrs());
753 clone
= Object::attach(obj
);
754 assertx(clone
->hasExactlyOneRef());
755 assertx(!clone
->hasInstanceDtor());
758 auto const clonePropVec
= clone
->propVecForWrite();
759 auto const props
= m_cls
->declProperties();
760 for (auto i
= Slot
{0}; i
< nProps
; i
++) {
761 if (UNLIKELY(props
[i
].attrs
& AttrNoSerialize
)) {
762 // need to write default value, not value from instance we're cloning
763 if (m_cls
->pinitVec().size() > 0) {
764 const Class::PropInitVec
* propInitVec
= m_cls
->getPropData();
765 cellCopy((*propInitVec
)[i
], clonePropVec
[i
]);
766 if ((*propInitVec
)[i
].deepInit()) {
767 tvIncRefGen(clonePropVec
[i
]);
768 collections::deepCopy(&clonePropVec
[i
]);
771 cellCopy(m_cls
->declPropInit()[i
], clonePropVec
[i
]);
774 tvDupWithRef(propVec()[i
], clonePropVec
[i
]);
777 if (UNLIKELY(getAttribute(HasDynPropArr
))) {
778 clone
->setAttribute(HasDynPropArr
);
779 g_context
->dynPropTable
.emplace(clone
.get(), dynPropArray().get());
781 if (getAttribute(HasClone
)) {
782 assertx(!isCppBuiltin());
783 auto const method
= clone
->m_cls
->lookupMethod(s_clone
.get());
785 g_context
->invokeMethodV(clone
.get(), method
);
787 return clone
.detach();
790 bool ObjectData::equal(const ObjectData
& other
) const {
791 if (this == &other
) return true;
792 if (isCollection()) {
793 return collections::equals(this, &other
);
795 if (UNLIKELY(instanceof(SystemLib::s_DateTimeInterfaceClass
) &&
796 other
.instanceof(SystemLib::s_DateTimeInterfaceClass
))) {
797 return DateTimeData::compare(this, &other
) == 0;
799 if (getVMClass() != other
.getVMClass()) return false;
800 if (UNLIKELY(instanceof(SystemLib::s_ArrayObjectClass
))) {
801 // Compare the whole object, not just the array representation
802 auto ar1
= Array::Create();
803 auto ar2
= Array::Create();
805 other
.o_getArray(ar2
);
806 return ar1
->equal(ar2
.get(), false);
808 if (UNLIKELY(instanceof(c_Closure::classof()))) {
809 // First comparison already proves they are different
812 return toArray()->equal(other
.toArray().get(), false);
815 bool ObjectData::less(const ObjectData
& other
) const {
816 if (isCollection() || other
.isCollection()) {
817 throw_collection_compare_exception();
819 if (this == &other
) return false;
820 if (UNLIKELY(instanceof(SystemLib::s_DateTimeInterfaceClass
) &&
821 other
.instanceof(SystemLib::s_DateTimeInterfaceClass
))) {
822 return DateTimeData::compare(this, &other
) == -1;
824 if (UNLIKELY(instanceof(c_Closure::classof()))) {
825 // First comparison already proves they are different
828 if (getVMClass() != other
.getVMClass()) return false;
829 return toArray().less(other
.toArray());
832 bool ObjectData::more(const ObjectData
& other
) const {
833 if (isCollection() || other
.isCollection()) {
834 throw_collection_compare_exception();
836 if (this == &other
) return false;
837 if (UNLIKELY(instanceof(SystemLib::s_DateTimeInterfaceClass
) &&
838 other
.instanceof(SystemLib::s_DateTimeInterfaceClass
))) {
839 return DateTimeData::compare(this, &other
) == 1;
841 if (UNLIKELY(instanceof(c_Closure::classof()))) {
842 // First comparison already proves they are different
845 if (getVMClass() != other
.getVMClass()) return false;
846 return toArray().more(other
.toArray());
849 int64_t ObjectData::compare(const ObjectData
& other
) const {
850 if (isCollection() || other
.isCollection()) {
851 throw_collection_compare_exception();
853 if (this == &other
) return 0;
854 if (UNLIKELY(instanceof(SystemLib::s_DateTimeInterfaceClass
) &&
855 other
.instanceof(SystemLib::s_DateTimeInterfaceClass
))) {
856 auto t1
= DateTimeData::getTimestamp(this);
857 auto t2
= DateTimeData::getTimestamp(&other
);
858 return (t1
< t2
) ? -1 : ((t1
> t2
) ? 1 : 0);
860 // Return 1 for different classes to match PHP7 behavior.
861 if (UNLIKELY(instanceof(c_Closure::classof()))) {
862 // First comparison already proves they are different
865 if (getVMClass() != other
.getVMClass()) return 1;
866 return toArray().compare(other
.toArray());
869 Variant
ObjectData::offsetGet(Variant key
) {
870 assert(instanceof(SystemLib::s_ArrayAccessClass
));
872 auto const method
= m_cls
->lookupMethod(s_offsetGet
.get());
875 return g_context
->invokeMethodV(this, method
, InvokeArgs(key
.asCell(), 1));
878 ///////////////////////////////////////////////////////////////////////////////
883 s___isset("__isset"),
884 s___unset("__unset"),
885 s___sleep("__sleep"),
886 s___toDebugDisplay("__toDebugDisplay"),
887 s___wakeup("__wakeup");
889 void deepInitHelper(TypedValue
* propVec
, const TypedValueAux
* propData
,
893 for (; src
!= propData
+ nProps
; ++src
, ++dst
) {
895 // m_aux.u_deepInit is true for properties that need "deep" initialization
896 if (src
->deepInit()) {
898 collections::deepCopy(dst
);
903 // called from jit code
905 ObjectData
* ObjectData::newInstanceRaw(Class
* cls
, size_t size
) {
906 assert(cls
->getODAttrs() == DefaultAttrs
);
907 assert(Big
|| size
<= kMaxSmallSize
);
908 auto mem
= Big
? tl_heap
->mallocBigSize
<MemoryManager::Unzeroed
>(size
) :
909 tl_heap
->mallocSmallSize(size
);
910 return new (mem
) ObjectData(cls
, InitRaw
{}, DefaultAttrs
);
913 // called from jit code
915 ObjectData
* ObjectData::newInstanceRawAttrs(Class
* cls
, size_t size
,
917 assert(Big
|| size
<= kMaxSmallSize
);
918 auto mem
= Big
? tl_heap
->mallocBigSize
<MemoryManager::Unzeroed
>(size
) :
919 tl_heap
->mallocSmallSize(size
);
920 return new (mem
) ObjectData(cls
, InitRaw
{}, attrs
);
923 // Explicitly instantiate newInstanceRaw[Attrs]() template funcs.
924 template ObjectData
* ObjectData::newInstanceRaw
<false>(Class
*, size_t);
925 template ObjectData
* ObjectData::newInstanceRaw
<true>(Class
*, size_t);
927 ObjectData::newInstanceRawAttrs
<false>(Class
*, size_t, uint16_t);
929 ObjectData::newInstanceRawAttrs
<true>(Class
*, size_t, uint16_t);
931 // Note: the normal object destruction path does not actually call this
932 // destructor. See ObjectData::release.
933 ObjectData::~ObjectData() {
934 auto& pmax
= os_max_id
;
935 if (o_id
&& o_id
== pmax
) {
938 if (UNLIKELY(getAttribute(HasDynPropArr
))) freeDynPropArray(this);
941 Object
ObjectData::FromArray(ArrayData
* properties
) {
942 assert(properties
->isPHPArray());
943 Object retval
{SystemLib::s_stdclassClass
};
944 retval
->setAttribute(HasDynPropArr
);
945 g_context
->dynPropTable
.emplace(retval
.get(), properties
);
949 Slot
ObjectData::declPropInd(const TypedValue
* prop
) const {
950 // Do an address range check to determine whether prop physically resides
952 const TypedValue
* pv
= propVec();
953 if (prop
>= pv
&& prop
< &pv
[m_cls
->numDeclProperties()]) {
960 ObjectData::PropLookup
<TypedValue
*> ObjectData::getPropImpl(
962 const StringData
* key
,
965 auto const lookup
= m_cls
->getDeclPropIndex(ctx
, key
);
966 auto const propIdx
= lookup
.prop
;
968 if (LIKELY(propIdx
!= kInvalidSlot
)) {
969 // We found a visible property, but it might not be accessible. No need to
970 // check if there is a dynamic property with this name.
971 auto const prop
= &propVec()[propIdx
];
974 if (RuntimeOption::RepoAuthoritative
) {
975 auto const repoTy
= m_cls
->declPropRepoAuthType(propIdx
);
976 always_assert(tvMatchesRepoAuthType(*prop
, repoTy
));
980 return PropLookup
<TypedValue
*> {
981 const_cast<TypedValue
*>(prop
),
986 // We could not find a visible declared property. We need to check for a
987 // dynamic property with this name.
988 if (UNLIKELY(getAttribute(HasDynPropArr
))) {
989 if (auto const rval
= dynPropArray()->rval(key
)) {
990 // If we may write to the property we need to allow the array to escalate.
991 auto const prop
= copyDynArray
992 ? dynPropArray().lvalAt(StrNR(key
), AccessFlags::Key
).tv_ptr()
995 // Returning a non-declared property, we know that it is accessible since
996 // all dynamic properties are.
997 return PropLookup
<TypedValue
*> { const_cast<TypedValue
*>(prop
), true };
1001 return PropLookup
<TypedValue
*> { nullptr, false };
1004 member_lval
ObjectData::getPropLval(const Class
* ctx
, const StringData
* key
) {
1005 auto const lookup
= getPropImpl(ctx
, key
, true);
1006 return member_lval
{
1007 this, lookup
.prop
&& lookup
.accessible
? lookup
.prop
: nullptr
1011 member_rval
ObjectData::getProp(const Class
* ctx
, const StringData
* key
) const {
1012 auto const lookup
= const_cast<ObjectData
*>(this)
1013 ->getPropImpl(ctx
, key
, false);
1014 return member_rval
{
1015 this, lookup
.prop
&& lookup
.accessible
? lookup
.prop
: nullptr
1019 member_lval
ObjectData::vGetProp(const Class
* ctx
, const StringData
* key
) {
1020 auto const lookup
= getPropImpl(ctx
, key
, true);
1021 auto prop
= lookup
.prop
;
1022 if (lookup
.accessible
&& prop
&& prop
->m_type
!= KindOfUninit
) {
1023 tvBoxIfNeeded(*prop
);
1024 return member_lval
{ this, prop
};
1026 return member_lval
{ this, nullptr };
1029 member_lval
ObjectData::vGetPropIgnoreAccessibility(const StringData
* key
) {
1030 auto const lookup
= getPropImpl(nullptr, key
, true);
1031 auto prop
= lookup
.prop
;
1032 if (prop
&& prop
->m_type
!= KindOfUninit
) {
1033 tvBoxIfNeeded(*prop
);
1034 return member_lval
{ this, prop
};
1036 return member_lval
{ this, nullptr };
1039 //////////////////////////////////////////////////////////////////////
1041 inline InvokeResult::InvokeResult(bool ok
, Variant
&& v
) :
1042 val(*v
.asTypedValue()) {
1043 tvWriteUninit(*v
.asTypedValue());
1044 val
.m_aux
.u_ok
= ok
;
1047 struct PropAccessInfo
{
1050 bool operator==(const PropAccessInfo
& o
) const {
1051 return obj
== o
.obj
&& attr
== o
.attr
&& key
->same(o
.key
);
1055 const StringData
* key
; // note: not necessarily static
1056 ObjectData::Attribute attr
;
1059 struct PropAccessInfo::Hash
{
1060 size_t operator()(PropAccessInfo
const& info
) const {
1061 return hash_int64_pair(reinterpret_cast<intptr_t>(info
.obj
),
1063 (static_cast<int64_t>(info
.attr
) << 32));
1067 struct PropRecurInfo
{
1068 using RecurSet
= req::hash_set
<PropAccessInfo
, PropAccessInfo::Hash
>;
1069 const PropAccessInfo
* activePropInfo
{nullptr};
1070 RecurSet
* activeSet
{nullptr};
1076 * Recursion of magic property accessors is allowed, but if you
1077 * recurse on the same object, for the same property, for the same
1078 * kind of magic method, it doesn't actually enter the magic method
1079 * anymore. This matches zend behavior.
1081 * This means we need to track all active property getters and ensure
1082 * we aren't recursing for the same one. Since most accesses to magic
1083 * property getters aren't going to recurse, we optimize for the case
1084 * where only a single getter is active. If it recurses again, we
1085 * promote to a hash set to track all the information needed.
1087 * The various invokeFoo functions are the entry points here. They
1088 * require that the appropriate ObjectData::Attribute has been checked
1089 * first, and return false if they refused to run the magic method due
1090 * to a recursion error.
1093 THREAD_LOCAL(PropRecurInfo
, propRecurInfo
);
1095 template <class Invoker
>
1097 magic_prop_impl(const StringData
* /*key*/, const PropAccessInfo
& info
,
1099 auto recur_info
= propRecurInfo
.get();
1100 if (UNLIKELY(recur_info
->activePropInfo
!= nullptr)) {
1101 auto activeSet
= recur_info
->activeSet
;
1103 activeSet
= req::make_raw
<PropRecurInfo::RecurSet
>();
1104 activeSet
->insert(*recur_info
->activePropInfo
);
1105 recur_info
->activeSet
= activeSet
;
1107 if (!activeSet
->insert(info
).second
) {
1108 // We're already running a magic method on the same type here.
1109 return {false, make_tv
<KindOfUninit
>()};
1112 activeSet
->erase(info
);
1115 return {true, invoker()};
1118 recur_info
->activePropInfo
= &info
;
1120 recur_info
->activePropInfo
= nullptr;
1121 auto activeSet
= recur_info
->activeSet
;
1122 if (UNLIKELY(activeSet
!= nullptr)) {
1123 req::destroy_raw(activeSet
);
1124 recur_info
->activeSet
= nullptr;
1128 return {true, invoker()};
1131 // Helper for making invokers for the single-argument magic property
1132 // methods. __set takes 2 args, so it uses its own function.
1133 struct MagicInvoker
{
1134 const StringData
* magicFuncName
;
1135 const PropAccessInfo
& info
;
1137 TypedValue
operator()() const {
1138 auto const meth
= info
.obj
->getVMClass()->lookupMethod(magicFuncName
);
1139 TypedValue args
[1] = {
1140 make_tv
<KindOfString
>(const_cast<StringData
*>(info
.key
))
1142 return g_context
->invokeMethod(info
.obj
, meth
, folly::range(args
));
1148 bool ObjectData::invokeSet(const StringData
* key
, Cell val
) {
1149 auto const info
= PropAccessInfo
{ this, key
, UseSet
};
1150 auto r
= magic_prop_impl(key
, info
, [&] {
1151 auto const meth
= m_cls
->lookupMethod(s___set
.get());
1152 TypedValue args
[2] = {
1153 make_tv
<KindOfString
>(const_cast<StringData
*>(key
)),
1156 return g_context
->invokeMethod(this, meth
, folly::range(args
));
1158 if (r
) tvDecRefGen(r
.val
);
1162 InvokeResult
ObjectData::invokeGet(const StringData
* key
) {
1163 auto const info
= PropAccessInfo
{ this, key
, UseGet
};
1164 return magic_prop_impl(
1167 MagicInvoker
{ s___get
.get(), info
}
1171 InvokeResult
ObjectData::invokeIsset(const StringData
* key
) {
1172 auto const info
= PropAccessInfo
{ this, key
, UseIsset
};
1173 return magic_prop_impl(
1176 MagicInvoker
{ s___isset
.get(), info
}
1180 bool ObjectData::invokeUnset(const StringData
* key
) {
1181 auto const info
= PropAccessInfo
{ this, key
, UseUnset
};
1182 auto r
= magic_prop_impl(key
, info
,
1183 MagicInvoker
{s___unset
.get(), info
});
1184 if (r
) tvDecRefGen(r
.val
);
1188 static InvokeResult
guardedNativePropResult(Variant result
) {
1189 if (!Native::isPropHandled(result
)) {
1190 return {false, make_tv
<KindOfUninit
>()};
1192 return InvokeResult
{true, std::move(result
)};
1195 InvokeResult
ObjectData::invokeNativeGetProp(const StringData
* key
) {
1196 return guardedNativePropResult(
1197 Native::getProp(Object
{this}, StrNR(key
))
1201 bool ObjectData::invokeNativeSetProp(const StringData
* key
, Cell val
) {
1202 auto r
= guardedNativePropResult(
1203 Native::setProp(Object
{this}, StrNR(key
), tvAsCVarRef(&val
))
1209 InvokeResult
ObjectData::invokeNativeIssetProp(const StringData
* key
) {
1210 return guardedNativePropResult(
1211 Native::issetProp(Object
{this}, StrNR(key
))
1215 bool ObjectData::invokeNativeUnsetProp(const StringData
* key
) {
1216 auto r
= guardedNativePropResult(
1217 Native::unsetProp(Object
{this}, StrNR(key
))
1223 //////////////////////////////////////////////////////////////////////
1225 template<ObjectData::PropMode mode
>
1226 TypedValue
* ObjectData::propImpl(TypedValue
* tvRef
, const Class
* ctx
,
1227 const StringData
* key
) {
1228 auto constexpr write
= (mode
== PropMode::DimForWrite
) ||
1229 (mode
== PropMode::Bind
);
1230 auto const lookup
= getPropImpl(ctx
, key
, write
);
1231 auto const prop
= lookup
.prop
;
1234 if (lookup
.accessible
) {
1235 // Property exists, is accessible, and is not unset.
1236 if (prop
->m_type
!= KindOfUninit
) return prop
;
1238 // Property is unset, try __get.
1239 if (getAttribute(UseGet
)) {
1240 if (auto r
= invokeGet(key
)) {
1241 tvCopy(r
.val
, *tvRef
);
1246 if (mode
== PropMode::ReadWarn
) raiseUndefProp(key
);
1247 if (write
) return prop
;
1248 return const_cast<TypedValue
*>(&immutable_null_base
);
1251 // Property is not accessible, try __get.
1252 if (getAttribute(UseGet
)) {
1253 if (auto r
= invokeGet(key
)) {
1254 tvCopy(r
.val
, *tvRef
);
1259 // Property exists, but it is either protected or private since accessible
1261 auto const propInd
= m_cls
->lookupDeclProp(key
);
1262 auto const attrs
= m_cls
->declProperties()[propInd
].attrs
;
1263 auto const priv
= (attrs
& AttrPrivate
) ? "private" : "protected";
1266 "Cannot access %s property %s::$%s",
1268 m_cls
->preClass()->name()->data(),
1273 // First see if native getter is implemented.
1274 if (getAttribute(HasNativePropHandler
)) {
1275 if (auto r
= invokeNativeGetProp(key
)) {
1276 tvCopy(r
.val
, *tvRef
);
1281 // Next try calling user-level `__get` if it's used.
1282 if (getAttribute(UseGet
)) {
1283 if (auto r
= invokeGet(key
)) {
1284 tvCopy(r
.val
, *tvRef
);
1289 if (UNLIKELY(!*key
->data())) {
1290 throw_invalid_property_name(StrNR(key
));
1293 if (mode
== PropMode::ReadWarn
) raiseUndefProp(key
);
1294 if (write
) return makeDynProp(StrNR(key
), AccessFlags::Key
);
1295 return const_cast<TypedValue
*>(&immutable_null_base
);
1298 TypedValue
* ObjectData::prop(
1301 const StringData
* key
1303 return propImpl
<PropMode::ReadNoWarn
>(tvRef
, ctx
, key
);
1306 TypedValue
* ObjectData::propW(
1309 const StringData
* key
1311 return propImpl
<PropMode::ReadWarn
>(tvRef
, ctx
, key
);
1314 TypedValue
* ObjectData::propD(
1317 const StringData
* key
1319 return propImpl
<PropMode::DimForWrite
>(tvRef
, ctx
, key
);
1322 TypedValue
* ObjectData::propB(
1325 const StringData
* key
1327 return propImpl
<PropMode::Bind
>(tvRef
, ctx
, key
);
1330 bool ObjectData::propIsset(const Class
* ctx
, const StringData
* key
) {
1331 auto const lookup
= getPropImpl(ctx
, key
, false);
1332 auto const prop
= lookup
.prop
;
1334 if (prop
&& lookup
.accessible
&& prop
->m_type
!= KindOfUninit
) {
1335 return !cellIsNull(tvToCell(prop
));
1338 if (getAttribute(HasNativePropHandler
)) {
1339 if (auto r
= invokeNativeIssetProp(key
)) {
1340 tvCastToBooleanInPlace(&r
.val
);
1341 return r
.val
.m_data
.num
;
1345 if (!getAttribute(UseIsset
)) return false;
1346 auto r
= invokeIsset(key
);
1347 if (!r
) return false;
1348 tvCastToBooleanInPlace(&r
.val
);
1349 return r
.val
.m_data
.num
;
1352 bool ObjectData::propEmptyImpl(const Class
* ctx
, const StringData
* key
) {
1353 auto const lookup
= getPropImpl(ctx
, key
, false);
1354 auto const prop
= lookup
.prop
;
1356 if (prop
&& lookup
.accessible
&& prop
->m_type
!= KindOfUninit
) {
1357 return !cellToBool(*tvToCell(prop
));
1360 if (getAttribute(HasNativePropHandler
)) {
1361 if (auto r
= invokeNativeIssetProp(key
)) {
1362 tvCastToBooleanInPlace(&r
.val
);
1363 if (!r
.val
.m_data
.num
) return true;
1364 if (auto r2
= invokeNativeGetProp(key
)) {
1365 auto const emptyResult
= !cellToBool(*tvToCell(&r2
.val
));
1366 tvDecRefGen(&r2
.val
);
1373 if (!getAttribute(UseIsset
)) return true;
1374 auto r
= invokeIsset(key
);
1375 if (!r
) return true;
1377 tvCastToBooleanInPlace(&r
.val
);
1378 if (!r
.val
.m_data
.num
) return true;
1380 if (getAttribute(UseGet
)) {
1381 if (auto r
= invokeGet(key
)) {
1382 auto const emptyResult
= !cellToBool(*tvToCell(&r
.val
));
1383 tvDecRefGen(&r
.val
);
1390 bool ObjectData::propEmpty(const Class
* ctx
, const StringData
* key
) {
1391 if (UNLIKELY(getAttribute(HasPropEmpty
))) {
1392 if (instanceof(SimpleXMLElement_classof())) {
1393 return SimpleXMLElement_propEmpty(this, key
);
1396 return propEmptyImpl(ctx
, key
);
1399 void ObjectData::setProp(Class
* ctx
, const StringData
* key
, Cell val
) {
1400 auto const lookup
= getPropImpl(ctx
, key
, true);
1401 auto const prop
= lookup
.prop
;
1403 if (prop
&& lookup
.accessible
) {
1404 if (prop
->m_type
!= KindOfUninit
||
1405 !getAttribute(UseSet
) ||
1406 !invokeSet(key
, val
)) {
1412 // First see if native setter is implemented.
1413 if (getAttribute(HasNativePropHandler
) && invokeNativeSetProp(key
, val
)) {
1417 // Then go to user-level `__set`.
1418 if (!getAttribute(UseSet
) || !invokeSet(key
, val
)) {
1421 * Note: this differs from Zend right now in the case of a
1422 * failed recursive __set. In Zend, the __set is silently
1423 * dropped, and the protected property is not modified.
1425 raise_error("Cannot access protected property");
1427 if (UNLIKELY(!*key
->data())) {
1428 throw_invalid_property_name(StrNR(key
));
1430 reserveProperties().set(StrNR(key
), val
, true);
1435 TypedValue
* ObjectData::setOpProp(TypedValue
& tvRef
,
1438 const StringData
* key
,
1440 auto const lookup
= getPropImpl(ctx
, key
, true);
1441 auto prop
= lookup
.prop
;
1443 if (prop
&& lookup
.accessible
) {
1444 if (prop
->m_type
== KindOfUninit
&& getAttribute(UseGet
)) {
1445 if (auto r
= invokeGet(key
)) {
1446 SCOPE_EXIT
{ tvDecRefGen(r
.val
); };
1447 // don't unbox until after setopBody; see longer comment below
1448 setopBody(tvToCell(&r
.val
), op
, val
);
1449 tvUnboxIfNeeded(r
.val
);
1450 if (getAttribute(UseSet
)) {
1451 cellDup(tvAssertCell(r
.val
), tvRef
);
1452 if (invokeSet(key
, tvAssertCell(tvRef
))) {
1455 tvRef
.m_type
= KindOfUninit
;
1457 cellDup(tvAssertCell(r
.val
), *prop
);
1461 prop
= tvToCell(prop
);
1462 setopBody(prop
, op
, val
);
1466 if (UNLIKELY(!*key
->data())) throw_invalid_property_name(StrNR(key
));
1468 // Native accessors.
1469 if (getAttribute(HasNativePropHandler
)) {
1470 if (auto r
= invokeNativeGetProp(key
)) {
1471 tvCopy(r
.val
, tvRef
);
1472 setopBody(tvToCell(&tvRef
), op
, val
);
1473 if (invokeNativeSetProp(key
, tvToCell(tvRef
))) {
1477 // XXX else, write tvRef = null?
1480 auto const useSet
= getAttribute(UseSet
);
1481 auto const useGet
= getAttribute(UseGet
);
1483 if (useGet
&& !useSet
) {
1484 auto r
= invokeGet(key
);
1485 if (!r
) tvWriteNull(r
.val
);
1486 SCOPE_EXIT
{ tvDecRefGen(r
.val
); };
1488 // Note: the tvUnboxIfNeeded comes *after* the setop on purpose
1489 // here, even though it comes before the IncDecOp in the analogous
1490 // situation in incDecProp. This is to match zend 5.5 behavior.
1491 setopBody(tvToCell(&r
.val
), op
, val
);
1492 tvUnboxIfNeeded(r
.val
);
1494 if (prop
) raise_error("Cannot access protected property");
1495 prop
= makeDynProp(StrNR(key
), AccessFlags::Key
);
1497 // Normally this code path is defining a new dynamic property, but
1498 // unlike the non-magic case below, we may have already created it
1499 // under the recursion into invokeGet above, so we need to do a
1501 tvSet(r
.val
, *prop
);
1505 if (useGet
&& useSet
) {
1506 if (auto r
= invokeGet(key
)) {
1507 // Matching zend again: incDecProp does an unbox before the
1508 // operation, but setop doesn't need to here. (We'll unbox the
1509 // value that gets passed to the magic setter, though, since
1510 // __set functions can't take parameters by reference.)
1511 tvCopy(r
.val
, tvRef
);
1512 setopBody(tvToCell(&tvRef
), op
, val
);
1513 invokeSet(key
, tvToCell(tvRef
));
1518 if (prop
) raise_error("Cannot access protected property");
1520 // No visible/accessible property, and no applicable magic method:
1521 // create a new dynamic property. (We know this is a new property,
1522 // or it would've hit the visible && accessible case above.)
1523 prop
= makeDynProp(StrNR(key
), AccessFlags::Key
);
1524 assert(prop
->m_type
== KindOfNull
); // cannot exist yet
1525 setopBody(prop
, op
, val
);
1529 Cell
ObjectData::incDecProp(Class
* ctx
, IncDecOp op
, const StringData
* key
) {
1530 auto const lookup
= getPropImpl(ctx
, key
, true);
1531 auto prop
= lookup
.prop
;
1533 if (prop
&& lookup
.accessible
) {
1534 if (prop
->m_type
== KindOfUninit
&& getAttribute(UseGet
)) {
1535 if (auto r
= invokeGet(key
)) {
1536 SCOPE_EXIT
{ tvDecRefGen(r
.val
); };
1537 tvUnboxIfNeeded(r
.val
);
1538 auto const dest
= IncDecBody(op
, tvAssertCell(&r
.val
));
1539 if (getAttribute(UseSet
)) {
1540 invokeSet(key
, tvAssertCell(r
.val
));
1543 cellCopy(tvAssertCell(r
.val
), *prop
);
1544 tvWriteNull(r
.val
); // suppress decref
1548 if (prop
->m_type
== KindOfUninit
) {
1551 prop
= tvToCell(prop
);
1553 return IncDecBody(op
, tvAssertCell(prop
));
1556 if (UNLIKELY(!*key
->data())) throw_invalid_property_name(StrNR(key
));
1558 // Native accessors.
1559 if (getAttribute(HasNativePropHandler
)) {
1560 if (auto r
= invokeNativeGetProp(key
)) {
1561 SCOPE_EXIT
{ tvDecRefGen(r
.val
); };
1562 tvUnboxIfNeeded(r
.val
);
1563 auto const dest
= IncDecBody(op
, tvAssertCell(&r
.val
));
1564 if (invokeNativeSetProp(key
, tvAssertCell(r
.val
))) {
1570 auto const useSet
= getAttribute(UseSet
);
1571 auto const useGet
= getAttribute(UseGet
);
1573 if (useGet
&& !useSet
) {
1574 auto r
= invokeGet(key
);
1575 if (!r
) tvWriteNull(r
.val
);
1576 SCOPE_EXIT
{ tvDecRefGen(r
.val
); };
1577 tvUnboxIfNeeded(r
.val
);
1578 auto const dest
= IncDecBody(op
, tvAssertCell(&r
.val
));
1579 if (prop
) raise_error("Cannot access protected property");
1580 prop
= makeDynProp(StrNR(key
), AccessFlags::Key
);
1582 // Normally this code path is defining a new dynamic property, but
1583 // unlike the non-magic case below, we may have already created it
1584 // under the recursion into invokeGet above, so we need to do a
1586 tvSet(r
.val
, *prop
);
1590 if (useGet
&& useSet
) {
1591 if (auto r
= invokeGet(key
)) {
1592 SCOPE_EXIT
{ tvDecRefGen(r
.val
); };
1593 tvUnboxIfNeeded(r
.val
);
1594 auto const dest
= IncDecBody(op
, tvAssertCell(&r
.val
));
1595 invokeSet(key
, tvAssertCell(r
.val
));
1600 if (prop
) raise_error("Cannot access protected property");
1602 // No visible/accessible property, and no applicable magic method:
1603 // create a new dynamic property. (We know this is a new property,
1604 // or it would've hit the visible && accessible case above.)
1605 prop
= makeDynProp(StrNR(key
), AccessFlags::Key
);
1606 assert(prop
->m_type
== KindOfNull
); // cannot exist yet
1607 return IncDecBody(op
, prop
);
1610 void ObjectData::unsetProp(Class
* ctx
, const StringData
* key
) {
1611 auto const lookup
= getPropImpl(ctx
, key
, true);
1612 auto const prop
= lookup
.prop
;
1613 auto const propInd
= declPropInd(prop
);
1615 if (prop
&& lookup
.accessible
&& prop
->m_type
!= KindOfUninit
) {
1616 if (propInd
!= kInvalidSlot
) {
1617 // Declared property.
1618 tvSetIgnoreRef(*uninit_variant
.asTypedValue(), *prop
);
1620 // Dynamic property.
1621 dynPropArray().remove(StrNR(key
).asString(), true /* isString */);
1626 // Native unset first.
1627 if (getAttribute(HasNativePropHandler
) && invokeNativeUnsetProp(key
)) {
1631 auto const tryUnset
= getAttribute(UseUnset
);
1633 if (propInd
!= kInvalidSlot
&& !lookup
.accessible
&& !tryUnset
) {
1634 // Defined property that is not accessible.
1635 raise_error("Cannot unset inaccessible property");
1638 if (!tryUnset
|| !invokeUnset(key
)) {
1639 if (UNLIKELY(!*key
->data())) {
1640 throw_invalid_property_name(StrNR(key
));
1646 void ObjectData::raiseObjToIntNotice(const char* clsName
) {
1647 raise_notice("Object of class %s could not be converted to int", clsName
);
1650 void ObjectData::raiseObjToDoubleNotice(const char* clsName
) {
1651 raise_notice("Object of class %s could not be converted to float", clsName
);
1654 void ObjectData::raiseAbstractClassError(Class
* cls
) {
1655 Attr attrs
= cls
->attrs();
1656 raise_error("Cannot instantiate %s %s",
1657 (attrs
& AttrInterface
) ? "interface" :
1658 (attrs
& AttrTrait
) ? "trait" :
1659 (attrs
& AttrEnum
) ? "enum" : "abstract class",
1660 cls
->preClass()->name()->data());
1663 void ObjectData::raiseUndefProp(const StringData
* key
) {
1664 raise_notice("Undefined property: %s::$%s",
1665 m_cls
->name()->data(), key
->data());
1668 void ObjectData::getProp(const Class
* klass
,
1670 const PreClass::Prop
* prop
,
1672 std::vector
<bool>& inserted
) const {
1674 & (AttrStatic
| // statics aren't part of individual instances
1675 AttrNoSerialize
// runtime-internal attrs, such as the
1676 // <<__Memoize>> cache
1681 Slot propInd
= klass
->lookupDeclProp(prop
->name());
1682 assert(propInd
!= kInvalidSlot
);
1683 const TypedValue
* propVal
= &propVec()[propInd
];
1685 if ((!pubOnly
|| (prop
->attrs() & AttrPublic
)) &&
1686 propVal
->m_type
!= KindOfUninit
&&
1687 !inserted
[propInd
]) {
1688 inserted
[propInd
] = true;
1690 StrNR(klass
->declProperties()[propInd
].mangledName
).asString(),
1691 tvAsCVarRef(propVal
));
1695 void ObjectData::getProps(const Class
* klass
, bool pubOnly
,
1698 std::vector
<bool>& inserted
) const {
1699 PreClass::Prop
const* propVec
= pc
->properties();
1700 size_t count
= pc
->numProperties();
1701 for (size_t i
= 0; i
< count
; ++i
) {
1702 getProp(klass
, pubOnly
, &propVec
[i
], props
, inserted
);
1706 void ObjectData::getTraitProps(const Class
* klass
, bool pubOnly
,
1707 const Class
* trait
, Array
& props
,
1708 std::vector
<bool>& inserted
) const {
1709 assert(isNormalClass(klass
));
1710 assert(isTrait(trait
));
1712 getProps(klass
, pubOnly
, trait
->preClass(), props
, inserted
);
1713 for (auto const& traitCls
: trait
->usedTraitClasses()) {
1714 getProps(klass
, pubOnly
, traitCls
->preClass(), props
, inserted
);
1715 getTraitProps(klass
, pubOnly
, traitCls
.get(), props
, inserted
);
1719 static Variant
invokeSimple(ObjectData
* obj
, const StaticString
& name
) {
1720 auto const meth
= obj
->methodNamed(name
.get());
1721 return meth
? g_context
->invokeMethodV(obj
, meth
) : uninit_null();
1724 Variant
ObjectData::invokeSleep() {
1725 return invokeSimple(this, s___sleep
);
1728 Variant
ObjectData::invokeToDebugDisplay() {
1729 return invokeSimple(this, s___toDebugDisplay
);
1732 Variant
ObjectData::invokeWakeup() {
1733 return invokeSimple(this, s___wakeup
);
1736 String
ObjectData::invokeToString() {
1737 const Func
* method
= m_cls
->getToString();
1739 // If the object does not define a __toString() method, raise a
1740 // recoverable error
1741 raise_recoverable_error(
1742 "Object of class %s could not be converted to string",
1745 // If the user error handler decides to allow execution to continue,
1746 // we return the empty string.
1747 return empty_string();
1749 auto const tv
= g_context
->invokeMethod(this, method
);
1750 if (!isStringType(tv
.m_type
)) {
1751 // Discard the value returned by the __toString() method and raise
1752 // a recoverable error
1754 raise_recoverable_error(
1755 "Method %s::__toString() must return a string value",
1756 m_cls
->preClass()->name()->data());
1757 // If the user error handler decides to allow execution to continue,
1758 // we return the empty string.
1759 return empty_string();
1762 return String::attach(tv
.m_data
.pstr
);
1765 bool ObjectData::hasToString() {
1766 return (m_cls
->getToString() != nullptr);
1769 const char* ObjectData::classname_cstr() const {
1770 return getClassName().data();