2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-2014 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/class-info.h"
21 #include "hphp/runtime/base/complex-types.h"
22 #include "hphp/runtime/base/container-functions.h"
23 #include "hphp/runtime/base/execution-context.h"
24 #include "hphp/runtime/base/externals.h"
25 #include "hphp/runtime/base/memory-profile.h"
26 #include "hphp/runtime/base/runtime-error.h"
27 #include "hphp/runtime/base/smart-containers.h"
28 #include "hphp/runtime/base/type-conversions.h"
29 #include "hphp/runtime/base/variable-serializer.h"
31 #include "hphp/runtime/ext/ext_closure.h"
32 #include "hphp/runtime/ext/ext_collections.h"
33 #include "hphp/runtime/ext/ext_continuation.h"
34 #include "hphp/runtime/ext/ext_datetime.h"
35 #include "hphp/runtime/ext/ext_domdocument.h"
36 #include "hphp/runtime/ext/ext_simplexml.h"
38 #include "hphp/runtime/vm/class.h"
39 #include "hphp/runtime/vm/member-operations.h"
40 #include "hphp/runtime/vm/native-data.h"
41 #include "hphp/runtime/vm/jit/translator-inline.h"
43 #include "hphp/system/systemlib.h"
45 #include "folly/Hash.h"
46 #include "folly/ScopeGuard.h"
52 //////////////////////////////////////////////////////////////////////
54 // current maximum object identifier
55 __thread
int ObjectData::os_max_id
;
57 TRACE_SET_MOD(runtime
);
60 s_offsetGet("offsetGet"),
62 s_serialize("serialize"),
65 static Array
ArrayObject_toArray(const ObjectData
* obj
) {
66 bool visible
, accessible
, unset
;
67 auto prop
= obj
->getProp(
68 SystemLib::s_ArrayObjectClass
, s_storage
.get(),
69 visible
, accessible
, unset
71 assert(visible
&& accessible
&& !unset
);
72 return tvAsCVarRef(prop
).toArray();
75 static_assert(sizeof(ObjectData
) == 32, "Change this only on purpose");
77 //////////////////////////////////////////////////////////////////////
79 bool ObjectData::destruct() {
80 if (UNLIKELY(RuntimeOption::EnableObjDestructCall
)) {
81 g_context
->m_liveBCObjs
.erase(this);
85 if (auto meth
= m_cls
->getDtor()) {
86 // We don't run PHP destructors while we're unwinding for a C++ exception.
87 // We want to minimize the PHP code we run while propagating fatals, so
88 // we do this check here on a very common path, in the relativley slower
90 auto& faults
= g_context
->m_faults
;
91 if (!faults
.empty()) {
92 if (faults
.back().m_faultType
== Fault::Type::CppException
) return true;
94 // We raise the refcount around the call to __destruct(). This is to
95 // prevent the refcount from going to zero when the destructor returns.
96 CountableHelper
h(this);
97 RefCount c
= this->getCount();
101 // Call the destructor method
102 g_context
->invokeFuncFew(&retval
, meth
, this);
104 // Swallow any exceptions that escape the __destruct method
105 handle_destructor_exception();
107 tvRefcountedDecRef(&retval
);
108 return c
== this->getCount();
114 ///////////////////////////////////////////////////////////////////////////////
117 const String
& ObjectData::o_getClassName() const {
118 return *(const String
*)(&m_cls
->preClass()->nameRef());
121 bool ObjectData::o_instanceof(const String
& s
) const {
122 Class
* cls
= Unit::lookupClass(s
.get());
123 if (!cls
) return false;
124 return m_cls
->classof(cls
);
127 bool ObjectData::o_toBooleanImpl() const noexcept
{
128 // Note: if you add more cases here, hhbbc/class-util.cpp also needs
130 if (isCollection()) {
131 if (m_cls
== c_Vector::classof()) {
132 return c_Vector::ToBool(this);
133 } else if (m_cls
== c_Map::classof()) {
134 return c_Map::ToBool(this);
135 } else if (m_cls
== c_ImmMap::classof()) {
136 return c_ImmMap::ToBool(this);
137 } else if (m_cls
== c_Set::classof()) {
138 return c_Set::ToBool(this);
139 } else if (m_cls
== c_ImmVector::classof()) {
140 return c_ImmVector::ToBool(this);
141 } else if (m_cls
== c_ImmSet::classof()) {
142 return c_ImmSet::ToBool(this);
144 always_assert(false);
146 } else if (instanceof(c_SimpleXMLElement::classof())) {
147 // SimpleXMLElement is the only non-collection class that has custom
149 return c_SimpleXMLElement::ToBool(this);
151 always_assert(false);
155 int64_t ObjectData::o_toInt64Impl() const noexcept
{
156 // SimpleXMLElement is the only class that has proper custom int casting.
157 // If others are added in future, just turn this assert into an if and
159 assert(instanceof(c_SimpleXMLElement::classof()));
160 return c_SimpleXMLElement::ToInt64(this);
163 double ObjectData::o_toDoubleImpl() const noexcept
{
164 // SimpleXMLElement is the only non-collection class that has custom
165 // double casting. If others are added in future, just turn this assert
166 // into an if and add cases.
167 assert(instanceof(c_SimpleXMLElement::classof()));
168 return c_SimpleXMLElement::ToDouble(this);
171 ///////////////////////////////////////////////////////////////////////////////
172 // instance methods and properties
174 const StaticString
s_getIterator("getIterator");
176 Object
ObjectData::iterableObject(bool& isIterable
,
177 bool mayImplementIterator
/* = true */) {
178 assert(mayImplementIterator
|| !implementsIterator());
179 if (mayImplementIterator
&& implementsIterator()) {
184 while (obj
->instanceof(SystemLib::s_IteratorAggregateClass
)) {
185 Variant iterator
= obj
->o_invoke_few_args(s_getIterator
, 0);
186 if (!iterator
.isObject()) break;
187 ObjectData
* o
= iterator
.getObjectData();
188 if (o
->instanceof(SystemLib::s_IteratorClass
)) {
198 ArrayIter
ObjectData::begin(const String
& context
/* = null_string */) {
200 if (isCollection()) {
201 return ArrayIter(this);
203 Object iterable
= iterableObject(isIterable
);
205 return ArrayIter(iterable
.detach(), ArrayIter::noInc
);
207 return ArrayIter(iterable
->o_toIterArray(context
));
211 MutableArrayIter
ObjectData::begin(Variant
* key
, Variant
& val
,
212 const String
& context
/* = null_string */) {
214 if (isCollection()) {
215 raise_error("Collection elements cannot be taken by reference");
217 Object iterable
= iterableObject(isIterable
);
219 throw FatalErrorException("An iterator cannot be used with "
220 "foreach by reference");
222 Array properties
= iterable
->o_toIterArray(context
, true);
223 ArrayData
* arr
= properties
.detach();
224 return MutableArrayIter(arr
, key
, val
);
227 Array
& ObjectData::dynPropArray() const {
228 assert(getAttribute(HasDynPropArr
));
229 assert(g_context
->dynPropTable
.count(this));
230 return g_context
->dynPropTable
[this].arr();
233 Array
& ObjectData::reserveProperties(int numDynamic
/* = 2 */) {
234 if (getAttribute(HasDynPropArr
)) return dynPropArray();
236 assert(!g_context
->dynPropTable
.count(this));
237 auto& arr
= g_context
->dynPropTable
[this].arr();
238 arr
= Array::attach(HphpArray::MakeReserve(numDynamic
));
239 setAttribute(HasDynPropArr
);
243 Variant
* ObjectData::o_realProp(const String
& propName
, int flags
,
244 const String
& context
/* = null_string */) {
246 * Returns a pointer to a place for a property value. This should never
247 * call the magic methods __get or __set. The flags argument describes the
248 * behavior in cases where the named property is nonexistent or
251 Class
* ctx
= nullptr;
252 if (!context
.empty()) {
253 ctx
= Unit::lookupClass(context
.get());
256 bool visible
, accessible
, unset
;
257 auto ret
= getProp(ctx
, propName
.get(), visible
, accessible
, unset
);
259 // Property is not declared, and not dynamically created yet.
260 if (!(flags
& RealPropCreate
)) {
263 return &reserveProperties().lvalAt(propName
, AccessFlags::Key
);
266 // ret is non-NULL if we reach here
268 if ((accessible
&& !unset
) ||
269 (flags
& (RealPropUnchecked
|RealPropExist
))) {
270 return reinterpret_cast<Variant
*>(ret
);
276 inline Variant
ObjectData::o_getImpl(const String
& propName
, int flags
,
277 bool error
/* = true */,
278 const String
& context
/*= null_string*/) {
279 if (UNLIKELY(!*propName
.data())) {
280 throw_invalid_property_name(propName
);
283 if (Variant
* t
= o_realProp(propName
, flags
, context
)) {
284 if (t
->isInitialized())
288 if (getAttribute(UseGet
)) {
290 tvWriteNull(&tvResult
);
291 if (invokeGet(&tvResult
, propName
.get())) {
292 return tvAsCVarRef(&tvResult
);
297 raise_notice("Undefined property: %s::$%s", o_getClassName().data(),
301 return uninit_null();
304 Variant
ObjectData::o_get(const String
& propName
, bool error
/* = true */,
305 const String
& context
/* = null_string */) {
306 return o_getImpl(propName
, 0, error
, context
);
310 ALWAYS_INLINE Variant
ObjectData::o_setImpl(const String
& propName
, T v
,
311 const String
& context
) {
312 if (UNLIKELY(!*propName
.data())) {
313 throw_invalid_property_name(propName
);
316 bool useSet
= getAttribute(UseSet
);
317 auto flags
= useSet
? 0 : RealPropCreate
;
319 if (Variant
* t
= o_realProp(propName
, flags
, context
)) {
320 if (!useSet
|| t
->isInitialized()) {
328 invokeSet(&ignored
, propName
.get(), (TypedValue
*)(&variant(v
)))) {
329 tvRefcountedDecRef(&ignored
);
335 Variant
ObjectData::o_set(const String
& propName
, const Variant
& v
) {
336 return o_setImpl
<const Variant
&>(propName
, v
, null_string
);
339 Variant
ObjectData::o_set(const String
& propName
, RefResult v
) {
340 return o_setRef(propName
, variant(v
), null_string
);
343 Variant
ObjectData::o_setRef(const String
& propName
, const Variant
& v
) {
344 return o_setImpl
<RefResult
>(propName
, ref(v
), null_string
);
347 Variant
ObjectData::o_set(const String
& propName
, const Variant
& v
,
348 const String
& context
) {
349 return o_setImpl
<const Variant
&>(propName
, v
, context
);
352 Variant
ObjectData::o_set(const String
& propName
, RefResult v
,
353 const String
& context
) {
354 return o_setRef(propName
, variant(v
), context
);
357 Variant
ObjectData::o_setRef(const String
& propName
, const Variant
& v
,
358 const String
& context
) {
359 return o_setImpl
<RefResult
>(propName
, ref(v
), context
);
362 void ObjectData::o_setArray(const Array
& properties
) {
363 for (ArrayIter
iter(properties
); iter
; ++iter
) {
364 String k
= iter
.first().toString();
365 Class
* ctx
= nullptr;
366 // If the key begins with a NUL, it's a private or protected property. Read
367 // the class name from between the two NUL bytes.
369 // Note: if you change this, you need to change similar logic in
371 if (!k
.empty() && k
[0] == '\0') {
372 int subLen
= k
.find('\0', 1) + 1;
373 String cls
= k
.substr(1, subLen
- 2);
374 if (cls
.size() == 1 && cls
[0] == '*') {
379 ctx
= Unit::lookupClass(cls
.get());
382 k
= k
.substr(subLen
);
385 const Variant
& secondRef
= iter
.secondRef();
386 setProp(ctx
, k
.get(), (TypedValue
*)(&secondRef
),
387 secondRef
.isReferenced());
391 void ObjectData::o_getArray(Array
& props
, bool pubOnly
/* = false */) const {
392 // The declared properties in the resultant array should be a permutation of
393 // propVec. They appear in the following order: go most-to-least-derived in
394 // the inheritance hierarchy, inserting properties in declaration order (with
395 // the wrinkle that overridden properties should appear only once, with the
396 // access level given to it in its most-derived declaration).
398 // This is needed to keep track of which elements have been inserted. This is
399 // the smoothest way to get overridden properties right.
400 std::vector
<bool> inserted(m_cls
->numDeclProperties(), false);
402 // Iterate over declared properties and insert {mangled name --> prop} pairs.
403 const Class
* cls
= m_cls
;
405 getProps(cls
, pubOnly
, cls
->preClass(), props
, inserted
);
406 for (auto const& traitCls
: cls
->usedTraitClasses()) {
407 getProps(cls
, pubOnly
, traitCls
->preClass(), props
, inserted
);
412 // Iterate over dynamic properties and insert {name --> prop} pairs.
413 if (UNLIKELY(getAttribute(HasDynPropArr
))) {
414 auto& dynProps
= dynPropArray();
415 if (!dynProps
.empty()) {
416 for (ArrayIter
it(dynProps
.get()); !it
.end(); it
.next()) {
417 props
.setWithRef(it
.first(), it
.secondRef(), true);
423 Array
ObjectData::o_toArray(bool pubOnly
/* = false */) const {
424 // We can quickly tell if this object is a collection, which lets us avoid
425 // checking for each class in turn if it's not one.
426 if (isCollection()) {
427 if (m_cls
== c_Vector::classof()) {
428 return c_Vector::ToArray(this);
429 } else if (m_cls
== c_Map::classof()) {
430 return c_Map::ToArray(this);
431 } else if (m_cls
== c_Set::classof()) {
432 return c_Set::ToArray(this);
433 } else if (m_cls
== c_Pair::classof()) {
434 return c_Pair::ToArray(this);
435 } else if (m_cls
== c_ImmVector::classof()) {
436 return c_ImmVector::ToArray(this);
437 } else if (m_cls
== c_ImmMap::classof()) {
438 return c_ImmMap::ToArray(this);
439 } else if (m_cls
== c_ImmSet::classof()) {
440 return c_ImmSet::ToArray(this);
442 // It's undefined what happens if you reach not_reached. We want to be sure
443 // to hard fail if we get here.
444 always_assert(false);
445 } else if (UNLIKELY(getAttribute(CallToImpl
))) {
446 // If we end up with other classes that need special behavior, turn the
447 // assert into an if and add cases.
448 assert(instanceof(c_SimpleXMLElement::classof()));
449 return c_SimpleXMLElement::ToArray(this);
450 } else if (UNLIKELY(instanceof(SystemLib::s_ArrayObjectClass
))) {
451 return ArrayObject_toArray(this);
453 Array
ret(ArrayData::Create());
454 o_getArray(ret
, pubOnly
);
459 Array
ObjectData::o_toIterArray(const String
& context
,
460 bool getRef
/* = false */) {
461 Array
* dynProps
= nullptr;
462 size_t size
= m_cls
->declPropNumAccessible();
463 if (getAttribute(HasDynPropArr
)) {
464 dynProps
= &dynPropArray();
465 size
+= dynProps
->size();
467 Array retArray
{ Array::attach(HphpArray::MakeReserve(size
)) };
469 Class
* ctx
= nullptr;
470 if (!context
.empty()) {
471 ctx
= Unit::lookupClass(context
.get());
474 // Get all declared properties first, bottom-to-top in the inheritance
475 // hierarchy, in declaration order.
476 const Class
* klass
= m_cls
;
478 const PreClass::Prop
* props
= klass
->preClass()->properties();
479 const size_t numProps
= klass
->preClass()->numProperties();
481 for (size_t i
= 0; i
< numProps
; ++i
) {
482 auto key
= const_cast<StringData
*>(props
[i
].name());
483 bool visible
, accessible
, unset
;
484 auto val
= getProp(ctx
, key
, visible
, accessible
, unset
);
485 if (accessible
&& val
->m_type
!= KindOfUninit
&& !unset
) {
487 if (val
->m_type
!= KindOfRef
) {
490 retArray
.setRef(StrNR(key
), tvAsCVarRef(val
), true /* isKey */);
492 retArray
.set(StrNR(key
), tvAsCVarRef(val
), true /* isKey */);
496 klass
= klass
->parent();
499 // Now get dynamic properties.
501 ssize_t iter
= dynProps
->get()->iter_begin();
502 while (iter
!= ArrayData::invalid_index
) {
504 dynProps
->get()->nvGetKey(&key
, iter
);
505 iter
= dynProps
->get()->iter_advance(iter
);
507 // You can get this if you cast an array to object. These
508 // properties must be dynamic because you can't declare a
509 // property with a non-string name.
510 if (UNLIKELY(!IS_STRING_TYPE(key
.m_type
))) {
511 assert(key
.m_type
== KindOfInt64
);
512 TypedValue
* val
= dynProps
->get()->nvGet(key
.m_data
.num
);
514 if (val
->m_type
!= KindOfRef
) {
517 retArray
.setRef(key
.m_data
.num
, tvAsCVarRef(val
));
519 retArray
.set(key
.m_data
.num
, tvAsCVarRef(val
));
524 StringData
* strKey
= key
.m_data
.pstr
;
525 TypedValue
* val
= dynProps
->get()->nvGet(strKey
);
527 if (val
->m_type
!= KindOfRef
) {
530 retArray
.setRef(StrNR(strKey
), tvAsCVarRef(val
), true /* isKey */);
532 retArray
.set(StrNR(strKey
), tvAsCVarRef(val
), true /* isKey */);
541 static bool decode_invoke(const String
& s
, ObjectData
* obj
, bool fatal
,
544 ctx
.cls
= obj
->getVMClass();
545 ctx
.invName
= nullptr;
547 ctx
.func
= ctx
.cls
->lookupMethod(s
.get());
549 if (ctx
.func
->attrs() & AttrStatic
) {
550 // If we found a method and its static, null out this_
554 // If this_ is non-null AND we could not find a method, try
555 // looking up __call in cls's method table
556 ctx
.func
= ctx
.cls
->lookupMethod(s_call
.get());
559 // Bail if we couldn't find the method or __call
560 o_invoke_failed(ctx
.cls
->name()->data(), s
.data(), fatal
);
563 // We found __call! Stash the original name into invName.
564 assert(!(ctx
.func
->attrs() & AttrStatic
));
565 ctx
.invName
= s
.get();
566 ctx
.invName
->incRefCount();
571 Variant
ObjectData::o_invoke(const String
& s
, const Variant
& params
,
572 bool fatal
/* = true */) {
574 if (!decode_invoke(s
, this, fatal
, ctx
) ||
575 (!isContainer(params
) && !params
.isNull())) {
576 return Variant(Variant::NullInit());
579 g_context
->invokeFunc((TypedValue
*)&ret
, ctx
, params
);
583 Variant
ObjectData::o_invoke_few_args(const String
& s
, int count
,
584 INVOKE_FEW_ARGS_IMPL_ARGS
) {
587 if (!decode_invoke(s
, this, true, ctx
)) {
588 return Variant(Variant::NullInit());
591 TypedValue args
[INVOKE_FEW_ARGS_COUNT
];
593 default: not_implemented();
594 #if INVOKE_FEW_ARGS_COUNT > 6
595 case 10: tvCopy(*a9
.asTypedValue(), args
[9]);
596 case 9: tvCopy(*a8
.asTypedValue(), args
[8]);
597 case 8: tvCopy(*a7
.asTypedValue(), args
[7]);
598 case 7: tvCopy(*a6
.asTypedValue(), args
[6]);
600 #if INVOKE_FEW_ARGS_COUNT > 3
601 case 6: tvCopy(*a5
.asTypedValue(), args
[5]);
602 case 5: tvCopy(*a4
.asTypedValue(), args
[4]);
603 case 4: tvCopy(*a3
.asTypedValue(), args
[3]);
605 case 3: tvCopy(*a2
.asTypedValue(), args
[2]);
606 case 2: tvCopy(*a1
.asTypedValue(), args
[1]);
607 case 1: tvCopy(*a0
.asTypedValue(), args
[0]);
612 g_context
->invokeFuncFew(ret
.asTypedValue(), ctx
, count
, args
);
618 s_protected_prefix("\0*\0", 3);
620 void ObjectData::serialize(VariableSerializer
* serializer
) const {
621 if (UNLIKELY(serializer
->incNestedLevel((void*)this, true))) {
622 serializer
->writeOverflow((void*)this, true);
624 serializeImpl(serializer
);
626 serializer
->decNestedLevel((void*)this);
630 s_PHP_DebugDisplay("__PHP_DebugDisplay"),
631 s_PHP_Incomplete_Class("__PHP_Incomplete_Class"),
632 s_PHP_Incomplete_Class_Name("__PHP_Incomplete_Class_Name"),
633 s_PHP_Unserializable_Class_Name("__PHP_Unserializable_Class_Name"),
634 s_debugInfo("__debugInfo");
636 /* Get properties from the actual object unless we're
637 * serializing for var_dump()/print_r() and the object
638 * exports a __debugInfo() magic method.
639 * In which case, call that and use the array it returns.
641 inline Array
getSerializeProps(const ObjectData
* obj
,
642 VariableSerializer
* serializer
) {
643 if ((serializer
->getType() != VariableSerializer::Type::PrintR
) &&
644 (serializer
->getType() != VariableSerializer::Type::VarDump
)) {
645 return obj
->o_toArray();
647 auto cls
= obj
->getVMClass();
648 auto debuginfo
= cls
->lookupMethod(s_debugInfo
.get());
650 return obj
->o_toArray();
652 if (debuginfo
->attrs() & (AttrPrivate
|AttrProtected
|
653 AttrAbstract
|AttrStatic
)) {
654 raise_warning("%s::__debugInfo() must be public and non-static",
655 cls
->name()->data());
656 return obj
->o_toArray();
658 Variant ret
= const_cast<ObjectData
*>(obj
)->o_invoke_few_args(s_debugInfo
, 0);
660 return ret
.toArray();
663 return Array::Create();
665 raise_error("__debugInfo() must return an array");
669 void ObjectData::serializeImpl(VariableSerializer
* serializer
) const {
670 bool handleSleep
= false;
673 if (LIKELY(serializer
->getType() == VariableSerializer::Type::Serialize
||
674 serializer
->getType() == VariableSerializer::Type::APCSerialize
)) {
675 if (instanceof(SystemLib::s_SerializableClass
)) {
676 assert(!isCollection());
678 const_cast<ObjectData
*>(this)->o_invoke_few_args(s_serialize
, 0);
679 if (ret
.isString()) {
680 serializer
->writeSerializableObject(o_getClassName(), ret
.toString());
681 } else if (ret
.isNull()) {
682 serializer
->writeNull();
684 raise_error("%s::serialize() must return a string or NULL",
685 o_getClassName().data());
689 // Only serialize CPP extension type instances which can actually
691 auto cls
= getVMClass();
692 if (cls
->instanceCtor() && !cls
->isCppSerializable()) {
693 Object placeholder
= ObjectData::newInstance(
694 SystemLib::s___PHP_Unserializable_ClassClass
);
695 placeholder
->o_set(s_PHP_Unserializable_Class_Name
, o_getClassName());
696 placeholder
->serialize(serializer
);
699 if (getAttribute(HasSleep
)) {
701 ret
= const_cast<ObjectData
*>(this)->invokeSleep();
703 } else if (UNLIKELY(serializer
->getType() ==
704 VariableSerializer::Type::DebuggerSerialize
)) {
705 if (instanceof(SystemLib::s_SerializableClass
)) {
706 assert(!isCollection());
709 const_cast<ObjectData
*>(this)->o_invoke_few_args(s_serialize
, 0);
710 if (ret
.isString()) {
711 serializer
->writeSerializableObject(o_getClassName(), ret
.toString());
712 } else if (ret
.isNull()) {
713 serializer
->writeNull();
715 raise_warning("%s::serialize() must return a string or NULL",
716 o_getClassName().data());
717 serializer
->writeNull();
720 // serialize() throws exception
721 raise_warning("%s::serialize() throws exception",
722 o_getClassName().data());
723 serializer
->writeNull();
727 // Don't try to serialize a CPP extension class which doesn't
728 // support serialization. Just send the class name instead.
729 if (getAttribute(IsCppBuiltin
) && !getVMClass()->isCppSerializable()) {
730 serializer
->write(o_getClassName());
733 if (getAttribute(HasSleep
)) {
736 ret
= const_cast<ObjectData
*>(this)->invokeSleep();
738 raise_warning("%s::sleep() throws exception", o_getClassName().data());
739 serializer
->writeNull();
745 if (UNLIKELY(handleSleep
)) {
746 assert(!isCollection());
748 Array wanted
= Array::Create();
749 assert(ret
.getRawType() == KindOfArray
); // can't be KindOfRef
750 const Array
&props
= ret
.asCArrRef();
751 for (ArrayIter
iter(props
); iter
; ++iter
) {
752 String memberName
= iter
.second().toString();
753 String propName
= memberName
;
755 auto attrMask
= AttrNone
;
756 if (memberName
.data()[0] == 0) {
757 int subLen
= memberName
.find('\0', 1) + 1;
759 if (subLen
== 3 && memberName
.data()[1] == '*') {
760 attrMask
= AttrProtected
;
761 memberName
= memberName
.substr(subLen
);
763 attrMask
= AttrPrivate
;
764 String cls
= memberName
.substr(1, subLen
- 2);
765 ctx
= Unit::lookupClass(cls
.get());
767 memberName
= memberName
.substr(subLen
);
776 Slot propInd
= m_cls
->getDeclPropIndex(ctx
, memberName
.get(),
778 if (propInd
!= kInvalidSlot
) {
780 const TypedValue
* prop
= &propVec()[propInd
];
781 if (prop
->m_type
!= KindOfUninit
) {
782 auto attrs
= m_cls
->declProperties()[propInd
].m_attrs
;
783 if (attrs
& AttrPrivate
) {
784 memberName
= concat4(s_zero
, ctx
->nameRef(),
786 } else if (attrs
& AttrProtected
) {
787 memberName
= concat(s_protected_prefix
, memberName
);
789 if (!attrMask
|| (attrMask
& attrs
) == attrMask
) {
790 wanted
.set(memberName
, tvAsCVarRef(prop
));
796 if (!attrMask
&& UNLIKELY(getAttribute(HasDynPropArr
))) {
797 const TypedValue
* prop
= dynPropArray()->nvGet(propName
.get());
799 wanted
.set(propName
, tvAsCVarRef(prop
));
803 raise_notice("serialize(): \"%s\" returned as member variable from "
804 "__sleep() but does not exist", propName
.data());
805 wanted
.set(propName
, init_null());
807 serializer
->setObjectInfo(o_getClassName(), o_getId(), 'O');
808 wanted
.serialize(serializer
, true);
810 raise_notice("serialize(): __sleep should return an array only "
811 "containing the names of instance-variables to "
813 uninit_null().serialize(serializer
);
816 if (isCollection()) {
817 collectionSerialize(const_cast<ObjectData
*>(this), serializer
);
819 const String
& className
= o_getClassName();
820 Array properties
= getSerializeProps(this, serializer
);
821 if (serializer
->getType() ==
822 VariableSerializer::Type::DebuggerSerialize
) {
824 auto val
= const_cast<ObjectData
*>(this)->invokeToDebugDisplay();
825 if (val
.isInitialized()) {
826 properties
.lvalAt(s_PHP_DebugDisplay
).assign(val
);
829 raise_warning("%s::__toDebugDisplay() throws exception",
830 o_getClassName().data());
833 if (serializer
->getType() == VariableSerializer::Type::DebuggerDump
) {
834 Variant
* debugDispVal
= const_cast<ObjectData
*>(this)-> // XXX
835 o_realProp(s_PHP_DebugDisplay
, 0);
837 debugDispVal
->serialize(serializer
);
841 if (serializer
->getType() != VariableSerializer::Type::VarDump
&&
842 className
== s_PHP_Incomplete_Class
) {
843 Variant
* cname
= const_cast<ObjectData
*>(this)-> // XXX
844 o_realProp(s_PHP_Incomplete_Class_Name
, 0);
845 if (cname
&& cname
->isString()) {
846 serializer
->setObjectInfo(cname
->toCStrRef(), o_getId(), 'O');
847 properties
.remove(s_PHP_Incomplete_Class_Name
, true);
848 properties
.serialize(serializer
, true);
852 serializer
->setObjectInfo(className
, o_getId(), 'O');
853 properties
.serialize(serializer
, true);
858 ObjectData
* ObjectData::clone() {
859 if (getAttribute(HasClone
) && getAttribute(IsCppBuiltin
)) {
860 if (isCollection()) {
861 if (m_cls
== c_Vector::classof()) {
862 return c_Vector::Clone(this);
863 } else if (m_cls
== c_Map::classof()) {
864 return c_Map::Clone(this);
865 } else if (m_cls
== c_ImmMap::classof()) {
866 return c_ImmMap::Clone(this);
867 } else if (m_cls
== c_Set::classof()) {
868 return c_Set::Clone(this);
869 } else if (m_cls
== c_Pair::classof()) {
870 return c_Pair::Clone(this);
871 } else if (m_cls
== c_ImmVector::classof()) {
872 return c_ImmVector::Clone(this);
873 } else if (m_cls
== c_ImmSet::classof()) {
874 return c_ImmSet::Clone(this);
876 always_assert(false);
878 } else if (instanceof(c_Closure::classof())) {
879 return c_Closure::Clone(this);
880 } else if (instanceof(c_Continuation::classof())) {
881 return c_Continuation::Clone(this);
882 } else if (instanceof(c_DateTime::classof())) {
883 return c_DateTime::Clone(this);
884 } else if (instanceof(c_DateTimeZone::classof())) {
885 return c_DateTimeZone::Clone(this);
886 } else if (instanceof(c_DateInterval::classof())) {
887 return c_DateInterval::Clone(this);
888 } else if (instanceof(c_DOMNode::classof())) {
889 return c_DOMNode::Clone(this);
890 } else if (instanceof(c_SimpleXMLElement::classof())) {
891 return c_SimpleXMLElement::Clone(this);
893 always_assert(false);
899 Variant
ObjectData::offsetGet(Variant key
) {
900 assert(instanceof(SystemLib::s_ArrayAccessClass
));
901 const Func
* method
= m_cls
->lookupMethod(s_offsetGet
.get());
904 return uninit_null();
907 g_context
->invokeFuncFew(v
.asTypedValue(), method
,
908 this, nullptr, 1, key
.asCell());
912 ///////////////////////////////////////////////////////////////////////////////
915 s___get(LITSTR_INIT("__get")),
916 s___set(LITSTR_INIT("__set")),
917 s___isset(LITSTR_INIT("__isset")),
918 s___unset(LITSTR_INIT("__unset")),
919 s___init__(LITSTR_INIT("__init__")),
920 s___sleep(LITSTR_INIT("__sleep")),
921 s___toDebugDisplay(LITSTR_INIT("__toDebugDisplay")),
922 s___wakeup(LITSTR_INIT("__wakeup"));
924 void deepInitHelper(TypedValue
* propVec
, const TypedValueAux
* propData
,
927 auto* src
= propData
;
928 for (; src
!= propData
+ nProps
; ++src
, ++dst
) {
930 // m_aux.u_deepInit is true for properties that need "deep" initialization
931 if (src
->deepInit()) {
933 collectionDeepCopyTV(dst
);
938 TypedValue
* ObjectData::propVec() {
939 auto const ret
= reinterpret_cast<uintptr_t>(this + 1);
940 if (UNLIKELY(getAttribute(IsCppBuiltin
))) {
941 return reinterpret_cast<TypedValue
*>(ret
+ m_cls
->builtinODTailSize());
943 return reinterpret_cast<TypedValue
*>(ret
);
946 const TypedValue
* ObjectData::propVec() const {
947 return const_cast<ObjectData
*>(this)->propVec();
951 * Only call this if cls->callsCustomInstanceInit() is true
953 ObjectData
* ObjectData::callCustomInstanceInit() {
954 const Func
* init
= m_cls
->lookupMethod(s___init__
.get());
957 // We need to incRef/decRef here because we're still a new (m_count
958 // == 0) object and invokeFunc is going to expect us to have a
959 // reasonable refcount.
962 g_context
->invokeFuncFew(&tv
, init
, this);
964 assert(!IS_REFCOUNTED_TYPE(tv
.m_type
));
966 this->setNoDestruct();
973 ObjectData
* ObjectData::newInstanceRaw(Class
* cls
, uint32_t size
) {
974 return new (MM().smartMallocSizeLogged(size
))
975 ObjectData(cls
, NoInit::noinit
);
978 ObjectData
* ObjectData::newInstanceRawBig(Class
* cls
, size_t size
) {
979 return new (MM().smartMallocSizeBigLogged
<false>(size
).first
)
980 ObjectData(cls
, NoInit::noinit
);
984 static void freeDynPropArray(ObjectData
* inst
) {
985 auto& table
= g_context
->dynPropTable
;
986 auto it
= table
.find(inst
);
987 assert(it
!= end(table
));
988 it
->second
.destroy();
992 ObjectData::~ObjectData() {
993 int& pmax
= os_max_id
;
994 if (o_id
&& o_id
== pmax
) {
997 if (UNLIKELY(getAttribute(HasDynPropArr
))) freeDynPropArray(this);
1000 void ObjectData::DeleteObject(ObjectData
* objectData
) {
1001 auto const cls
= objectData
->getVMClass();
1003 if (UNLIKELY(objectData
->getAttribute(InstanceDtor
))) {
1004 return cls
->instanceDtor()(objectData
, cls
);
1007 assert(!cls
->preClass()->builtinObjSize());
1008 assert(!cls
->preClass()->builtinODOffset());
1009 objectData
->~ObjectData();
1011 // ObjectData subobject is logically destructed now---don't access
1012 // objectData->foo for anything.
1014 auto const nProps
= size_t{cls
->numDeclProperties()};
1015 auto prop
= reinterpret_cast<TypedValue
*>(objectData
+ 1);
1016 auto const stop
= prop
+ nProps
;
1017 for (; prop
!= stop
; ++prop
) {
1018 tvRefcountedDecRef(prop
);
1021 auto const size
= sizeForNProps(nProps
);
1022 if (LIKELY(size
<= kMaxSmartSize
)) {
1023 return MM().smartFreeSizeLogged(objectData
, size
);
1025 MM().smartFreeSizeBigLogged(objectData
, size
);
1028 Object
ObjectData::FromArray(ArrayData
* properties
) {
1029 ObjectData
* retval
= ObjectData::newInstance(SystemLib::s_stdclassClass
);
1030 auto& dynArr
= retval
->reserveProperties(properties
->size());
1031 for (ssize_t pos
= properties
->iter_begin(); pos
!= ArrayData::invalid_index
;
1032 pos
= properties
->iter_advance(pos
)) {
1033 TypedValue
* value
= properties
->nvGetValueRef(pos
);
1035 properties
->nvGetKey(&key
, pos
);
1036 if (key
.m_type
== KindOfInt64
) {
1037 dynArr
.set(key
.m_data
.num
, tvAsCVarRef(value
));
1039 assert(IS_STRING_TYPE(key
.m_type
));
1040 StringData
* strKey
= key
.m_data
.pstr
;
1041 dynArr
.set(StrNR(strKey
), tvAsCVarRef(value
), true /* isKey */);
1048 Slot
ObjectData::declPropInd(TypedValue
* prop
) const {
1049 // Do an address range check to determine whether prop physically resides
1051 const TypedValue
* pv
= propVec();
1052 if (prop
>= pv
&& prop
< &pv
[m_cls
->numDeclProperties()]) {
1055 return kInvalidSlot
;
1059 TypedValue
* ObjectData::getProp(Class
* ctx
, const StringData
* key
,
1060 bool& visible
, bool& accessible
,
1062 TypedValue
* prop
= nullptr;
1064 Slot propInd
= m_cls
->getDeclPropIndex(ctx
, key
, accessible
);
1065 visible
= (propInd
!= kInvalidSlot
);
1066 if (propInd
!= kInvalidSlot
) {
1067 // We found a visible property, but it might not be accessible.
1068 // No need to check if there is a dynamic property with this name.
1069 prop
= &propVec()[propInd
];
1070 if (prop
->m_type
== KindOfUninit
) {
1074 assert(!visible
&& !accessible
);
1075 // We could not find a visible declared property. We need to check
1076 // for a dynamic property with this name.
1077 if (UNLIKELY(getAttribute(HasDynPropArr
))) {
1078 prop
= dynPropArray()->nvGet(key
);
1080 // Returned a non-declared property, we know that it is
1081 // visible and accessible (since all dynamic properties are),
1082 // and we know it is not unset (since unset dynamic properties
1083 // don't appear in the dynamic property array).
1092 const TypedValue
* ObjectData::getProp(Class
* ctx
, const StringData
* key
,
1093 bool& visible
, bool& accessible
,
1094 bool& unset
) const {
1095 return const_cast<ObjectData
*>(this)->getProp(
1096 ctx
, key
, visible
, accessible
, unset
1100 //////////////////////////////////////////////////////////////////////
1105 * Recursion of magic property accessors is allowed, but if you
1106 * recurse on the same object, for the same property, for the same
1107 * kind of magic method, it doesn't actually enter the magic method
1108 * anymore. This matches zend behavior.
1110 * This means we need to track all active property getters and ensure
1111 * we aren't recursing for the same one. Since most accesses to magic
1112 * property getters aren't going to recurse, we optimize for the case
1113 * where only a single getter is active. If it recurses again, we
1114 * promote to a hash set to track all the information needed.
1116 * The various invokeFoo functions are the entry points here. They
1117 * require that the appropriate ObjectData::Attribute has been checked
1118 * first, and return false if they refused to run the magic method due
1119 * to a recursion error.
1122 struct PropAccessInfo
{
1125 bool operator==(const PropAccessInfo
& o
) const {
1126 return obj
== o
.obj
&& attr
== o
.attr
&& key
->same(o
.key
);
1130 const StringData
* key
; // note: not necessarily static
1131 ObjectData::Attribute attr
;
1134 struct PropAccessInfo::Hash
{
1135 size_t operator()(PropAccessInfo
const& info
) const {
1136 return folly::hash::hash_combine(
1137 hash_int64(reinterpret_cast<intptr_t>(info
.obj
)),
1139 static_cast<uint32_t>(info
.attr
)
1144 struct PropRecurInfo
{
1145 typedef smart::hash_set
<PropAccessInfo
,PropAccessInfo::Hash
> RecurSet
;
1147 const PropAccessInfo
* activePropInfo
;
1148 RecurSet
* activeSet
;
1151 __thread PropRecurInfo propRecurInfo
;
1153 template<class Invoker
>
1154 bool magic_prop_impl(TypedValue
* retval
,
1155 const StringData
* key
,
1156 const PropAccessInfo
& info
,
1158 if (UNLIKELY(propRecurInfo
.activePropInfo
!= nullptr)) {
1159 if (!propRecurInfo
.activeSet
) {
1160 propRecurInfo
.activeSet
= smart_new
<PropRecurInfo::RecurSet
>();
1161 propRecurInfo
.activeSet
->insert(*propRecurInfo
.activePropInfo
);
1163 if (!propRecurInfo
.activeSet
->insert(info
).second
) {
1164 // We're already running a magic method on the same type here.
1168 propRecurInfo
.activeSet
->erase(info
);
1175 propRecurInfo
.activePropInfo
= &info
;
1177 propRecurInfo
.activePropInfo
= nullptr;
1178 if (UNLIKELY(propRecurInfo
.activeSet
!= nullptr)) {
1179 smart_delete(propRecurInfo
.activeSet
);
1180 propRecurInfo
.activeSet
= nullptr;
1188 // Helper for making invokers for the single-argument magic property
1189 // methods. __set takes 2 args, so it uses its own function.
1190 struct MagicInvoker
{
1192 const StringData
* magicFuncName
;
1193 const PropAccessInfo
& info
;
1195 void operator()() const {
1196 auto const meth
= info
.obj
->getVMClass()->lookupMethod(magicFuncName
);
1197 TypedValue args
[1] = {
1198 make_tv
<KindOfString
>(const_cast<StringData
*>(info
.key
))
1200 g_context
->invokeFuncFew(retval
, meth
, info
.obj
, nullptr, 1, args
);
1206 bool ObjectData::invokeSet(TypedValue
* retval
, const StringData
* key
,
1208 auto const info
= PropAccessInfo
{ this, key
, UseSet
};
1209 return magic_prop_impl(
1214 auto const meth
= m_cls
->lookupMethod(s___set
.get());
1215 TypedValue args
[2] = {
1216 make_tv
<KindOfString
>(const_cast<StringData
*>(key
)),
1219 g_context
->invokeFuncFew(retval
, meth
, this, nullptr, 2, args
);
1224 bool ObjectData::invokeGet(TypedValue
* retval
, const StringData
* key
) {
1225 auto const info
= PropAccessInfo
{ this, key
, UseGet
};
1226 return magic_prop_impl(
1230 MagicInvoker
{ retval
, s___get
.get(), info
}
1234 bool ObjectData::invokeIsset(TypedValue
* retval
, const StringData
* key
) {
1235 auto const info
= PropAccessInfo
{ this, key
, UseIsset
};
1236 return magic_prop_impl(
1240 MagicInvoker
{ retval
, s___isset
.get(), info
}
1244 bool ObjectData::invokeUnset(TypedValue
* retval
, const StringData
* key
) {
1245 auto const info
= PropAccessInfo
{ this, key
, UseUnset
};
1246 return magic_prop_impl(
1250 MagicInvoker
{ retval
, s___unset
.get(), info
}
1254 bool ObjectData::invokeGetProp(TypedValue
*& retval
, TypedValue
& tvRef
,
1255 const StringData
* key
) {
1256 if (!invokeGet(&tvRef
, key
)) return false;
1261 //////////////////////////////////////////////////////////////////////
1263 template <bool warn
, bool define
>
1264 void ObjectData::propImpl(TypedValue
*& retval
, TypedValue
& tvRef
,
1266 const StringData
* key
) {
1267 bool visible
, accessible
, unset
;
1268 auto propVal
= getProp(ctx
, key
, visible
, accessible
, unset
);
1273 if (!getAttribute(UseGet
) || !invokeGetProp(retval
, tvRef
, key
)) {
1275 raiseUndefProp(key
);
1280 retval
= (TypedValue
*)&init_null_variant
;
1287 if (!getAttribute(UseGet
) || !invokeGetProp(retval
, tvRef
, key
)) {
1288 // No need to check hasProp since visible is true
1289 // Visibility is either protected or private since accessible is false
1290 Slot propInd
= m_cls
->lookupDeclProp(key
);
1291 bool priv
= m_cls
->declProperties()[propInd
].m_attrs
& AttrPrivate
;
1293 raise_error("Cannot access %s property %s::$%s",
1294 priv
? "private" : "protected",
1295 m_cls
->preClass()->name()->data(),
1300 if (getAttribute(UseGet
) && invokeGetProp(retval
, tvRef
, key
)) {
1304 if (UNLIKELY(!*key
->data())) {
1305 throw_invalid_property_name(StrNR(key
));
1308 raiseUndefProp(key
);
1311 retval
= reinterpret_cast<TypedValue
*>(
1312 &reserveProperties().lvalAt(StrNR(key
), AccessFlags::Key
)
1315 retval
= const_cast<TypedValue
*>(
1316 reinterpret_cast<const TypedValue
*>(&init_null_variant
)
1323 void ObjectData::prop(TypedValue
*& retval
, TypedValue
& tvRef
,
1324 Class
* ctx
, const StringData
* key
) {
1325 propImpl
<false, false>(retval
, tvRef
, ctx
, key
);
1328 void ObjectData::propD(TypedValue
*& retval
, TypedValue
& tvRef
,
1329 Class
* ctx
, const StringData
* key
) {
1330 propImpl
<false, true>(retval
, tvRef
, ctx
, key
);
1333 void ObjectData::propW(TypedValue
*& retval
, TypedValue
& tvRef
,
1334 Class
* ctx
, const StringData
* key
) {
1335 propImpl
<true, false>(retval
, tvRef
, ctx
, key
);
1338 void ObjectData::propWD(TypedValue
*& retval
, TypedValue
& tvRef
,
1339 Class
* ctx
, const StringData
* key
) {
1340 propImpl
<true, true>(retval
, tvRef
, ctx
, key
);
1343 bool ObjectData::propIsset(Class
* ctx
, const StringData
* key
) {
1344 bool visible
, accessible
, unset
;
1345 auto propVal
= getProp(ctx
, key
, visible
, accessible
, unset
);
1346 if (visible
&& accessible
&& !unset
) {
1347 return !cellIsNull(tvToCell(propVal
));
1350 auto tv
= make_tv
<KindOfUninit
>();
1351 if (!getAttribute(UseIsset
) || !invokeIsset(&tv
, key
)) {
1354 tvCastToBooleanInPlace(&tv
);
1355 return tv
.m_data
.num
;
1358 bool ObjectData::propEmpty(Class
* ctx
, const StringData
* key
) {
1359 bool visible
, accessible
, unset
;
1360 auto propVal
= getProp(ctx
, key
, visible
, accessible
, unset
);
1361 if (visible
&& accessible
&& !unset
) {
1362 return !cellToBool(*tvToCell(propVal
));
1365 auto tv
= make_tv
<KindOfUninit
>();
1366 if (!getAttribute(UseIsset
) || !invokeIsset(&tv
, key
)) {
1369 tvCastToBooleanInPlace(&tv
);
1370 if (!tv
.m_data
.num
) {
1373 if (getAttribute(UseGet
)) {
1374 if (invokeGet(&tv
, key
)) {
1375 bool emptyResult
= !cellToBool(*tvToCell(&tv
));
1376 tvRefcountedDecRef(&tv
);
1383 void ObjectData::setProp(Class
* ctx
,
1384 const StringData
* key
,
1386 bool bindingAssignment
/* = false */) {
1387 bool visible
, accessible
, unset
;
1388 auto propVal
= getProp(ctx
, key
, visible
, accessible
, unset
);
1389 if (visible
&& accessible
) {
1393 if (!unset
|| !getAttribute(UseSet
) || !invokeSet(&ignored
, key
, val
)) {
1394 if (UNLIKELY(bindingAssignment
)) {
1395 tvBind(val
, propVal
);
1397 tvSet(*val
, *propVal
);
1401 tvRefcountedDecRef(&ignored
);
1406 if (!getAttribute(UseSet
) || !invokeSet(&ignored
, key
, val
)) {
1409 * Note: this differs from Zend right now in the case of a
1410 * failed recursive __set. In Zend, the __set is silently
1411 * dropped, and the protected property is not modified.
1413 raise_error("Cannot access protected property");
1415 if (UNLIKELY(!*key
->data())) {
1416 throw_invalid_property_name(StrNR(key
));
1418 // when seting a dynamic property, do not write
1419 // directly to the TypedValue in the HphpArray, since
1420 // its m_aux field is used to store the string hash of
1421 // the property name. Instead, call the appropriate
1422 // setters (set() or setRef()).
1423 if (UNLIKELY(bindingAssignment
)) {
1424 reserveProperties().setRef(
1425 StrNR(key
), tvAsCVarRef(val
), true /* isKey */);
1427 reserveProperties().set(
1428 StrNR(key
), tvAsCVarRef(val
), true /* isKey */);
1433 tvRefcountedDecRef(&ignored
);
1436 TypedValue
* ObjectData::setOpProp(TypedValue
& tvRef
, Class
* ctx
,
1437 SetOpOp op
, const StringData
* key
,
1439 bool visible
, accessible
, unset
;
1440 auto propVal
= getProp(ctx
, key
, visible
, accessible
, unset
);
1442 if (visible
&& accessible
) {
1443 if (unset
&& getAttribute(UseGet
)) {
1444 auto tvResult
= make_tv
<KindOfUninit
>();
1445 if (invokeGet(&tvResult
, key
)) {
1446 SETOP_BODY(&tvResult
, op
, val
);
1447 if (getAttribute(UseSet
)) {
1448 assert(tvRef
.m_type
== KindOfUninit
);
1449 cellDup(*tvToCell(&tvResult
), tvRef
);
1451 if (invokeSet(&ignored
, key
, &tvRef
)) {
1452 tvRefcountedDecRef(&ignored
);
1455 tvRef
.m_type
= KindOfUninit
;
1457 cellDup(*tvToCell(&tvResult
), *propVal
);
1462 propVal
= tvToCell(propVal
);
1463 SETOP_BODY_CELL(propVal
, op
, val
);
1467 if (UNLIKELY(!*key
->data())) throw_invalid_property_name(StrNR(key
));
1469 auto const useSet
= getAttribute(UseSet
);
1470 auto const useGet
= getAttribute(UseGet
);
1472 if (useGet
&& !useSet
) {
1473 auto tvResult
= make_tv
<KindOfNull
>();
1474 // If invokeGet fails due to recursion, it leaves the KindOfNull.
1475 invokeGet(&tvResult
, key
);
1477 // Note: the tvUnboxIfNeeded comes *after* the setop on purpose
1478 // here, even though it comes before the IncDecOp in the analagous
1479 // situation in incDecProp. This is to match zend 5.5 behavior.
1480 SETOP_BODY(&tvResult
, op
, val
);
1481 tvUnboxIfNeeded(&tvResult
);
1483 if (visible
) raise_error("Cannot access protected property");
1484 propVal
= reinterpret_cast<TypedValue
*>(
1485 &reserveProperties().lvalAt(StrNR(key
), AccessFlags::Key
)
1488 // Normally this code path is defining a new dynamic property, but
1489 // unlike the non-magic case below, we may have already created it
1490 // under the recursion into invokeGet above, so we need to do a
1492 tvSet(tvResult
, *propVal
);
1496 if (useGet
&& useSet
) {
1497 if (invokeGet(&tvRef
, key
)) {
1498 // Matching zend again: incDecProp does an unbox before the
1499 // operation, but setop doesn't need to here. (We'll unbox the
1500 // value that gets passed to the magic setter, though, since
1501 // __set functions can't take parameters by reference.)
1502 SETOP_BODY(&tvRef
, op
, val
);
1504 if (invokeSet(&ignored
, key
, &tvRef
)) {
1505 tvRefcountedDecRef(&ignored
);
1511 if (visible
) raise_error("Cannot access protected property");
1513 // No visible/accessible property, and no applicable magic method:
1514 // create a new dynamic property. (We know this is a new property,
1515 // or it would've hit the visible && accessible case above.)
1516 propVal
= reinterpret_cast<TypedValue
*>(
1517 &reserveProperties().lvalAt(StrNR(key
), AccessFlags::Key
)
1519 assert(propVal
->m_type
== KindOfNull
); // cannot exist yet
1520 SETOP_BODY_CELL(propVal
, op
, val
);
1524 template <bool setResult
>
1525 void ObjectData::incDecProp(TypedValue
& tvRef
,
1528 const StringData
* key
,
1530 bool visible
, accessible
, unset
;
1531 auto propVal
= getProp(ctx
, key
, visible
, accessible
, unset
);
1533 if (visible
&& accessible
) {
1534 auto tvResult
= make_tv
<KindOfUninit
>();
1535 if (unset
&& getAttribute(UseGet
) && invokeGet(&tvResult
, key
)) {
1536 IncDecBody
<setResult
>(op
, &tvResult
, &dest
);
1538 if (getAttribute(UseSet
) && invokeSet(&ignored
, key
, &tvResult
)) {
1539 tvRefcountedDecRef(&ignored
);
1540 propVal
= &tvResult
;
1542 memcpy(propVal
, &tvResult
, sizeof(TypedValue
));
1547 IncDecBody
<setResult
>(op
, propVal
, &dest
);
1551 if (UNLIKELY(!*key
->data())) throw_invalid_property_name(StrNR(key
));
1553 auto const useSet
= getAttribute(UseSet
);
1554 auto const useGet
= getAttribute(UseGet
);
1556 if (useGet
&& !useSet
) {
1557 auto tvResult
= make_tv
<KindOfNull
>();
1558 // If invokeGet fails due to recursion, it leaves the KindOfNull
1560 invokeGet(&tvResult
, key
);
1561 tvUnboxIfNeeded(&tvResult
);
1562 IncDecBody
<setResult
>(op
, &tvResult
, &dest
);
1563 if (visible
) raise_error("Cannot access protected property");
1564 propVal
= reinterpret_cast<TypedValue
*>(
1565 &reserveProperties().lvalAt(StrNR(key
), AccessFlags::Key
)
1568 // Normally this code path is defining a new dynamic property, but
1569 // unlike the non-magic case below, we may have already created it
1570 // under the recursion into invokeGet above, so we need to do a
1572 tvSet(tvResult
, *propVal
);
1576 if (useGet
&& useSet
) {
1577 if (invokeGet(&tvRef
, key
)) {
1578 tvUnboxIfNeeded(&tvRef
);
1579 IncDecBody
<setResult
>(op
, &tvRef
, &dest
);
1581 if (invokeSet(&ignored
, key
, &tvRef
)) {
1582 tvRefcountedDecRef(&ignored
);
1588 if (visible
) raise_error("Cannot access protected property");
1590 // No visible/accessible property, and no applicable magic method:
1591 // create a new dynamic property. (We know this is a new property,
1592 // or it would've hit the visible && accessible case above.)
1593 propVal
= reinterpret_cast<TypedValue
*>(
1594 &reserveProperties().lvalAt(StrNR(key
), AccessFlags::Key
)
1596 assert(propVal
->m_type
== KindOfNull
); // cannot exist yet
1597 IncDecBody
<setResult
>(op
, propVal
, &dest
);
1600 template void ObjectData::incDecProp
<true>(TypedValue
&,
1605 template void ObjectData::incDecProp
<false>(TypedValue
&,
1611 void ObjectData::unsetProp(Class
* ctx
, const StringData
* key
) {
1612 bool visible
, accessible
, unset
;
1613 auto propVal
= getProp(ctx
, key
, visible
, accessible
, unset
);
1614 Slot propInd
= declPropInd(propVal
);
1616 if (visible
&& accessible
&& !unset
) {
1617 if (propInd
!= kInvalidSlot
) {
1618 // Declared property.
1619 tvSetIgnoreRef(*null_variant
.asTypedValue(), *propVal
);
1621 // Dynamic property.
1622 dynPropArray().remove(StrNR(key
).asString(),
1623 true /* isString */);
1628 bool tryUnset
= getAttribute(UseUnset
);
1630 if (propInd
!= kInvalidSlot
&& !accessible
&& !tryUnset
) {
1631 // defined property that is not accessible
1632 raise_error("Cannot unset inaccessible property");
1636 if (!tryUnset
|| !invokeUnset(&ignored
, key
)) {
1637 if (UNLIKELY(!*key
->data())) {
1638 throw_invalid_property_name(StrNR(key
));
1643 tvRefcountedDecRef(&ignored
);
1646 void ObjectData::raiseObjToIntNotice(const char* clsName
) {
1647 raise_notice("Object of class %s could not be converted to int", clsName
);
1650 void ObjectData::raiseAbstractClassError(Class
* cls
) {
1651 Attr attrs
= cls
->attrs();
1652 raise_error("Cannot instantiate %s %s",
1653 (attrs
& AttrInterface
) ? "interface" :
1654 (attrs
& AttrTrait
) ? "trait" : "abstract class",
1655 cls
->preClass()->name()->data());
1658 void ObjectData::raiseUndefProp(const StringData
* key
) {
1659 raise_notice("Undefined property: %s::$%s",
1660 m_cls
->name()->data(), key
->data());
1663 void ObjectData::getProp(const Class
* klass
, bool pubOnly
,
1664 const PreClass::Prop
* prop
,
1666 std::vector
<bool>& inserted
) const {
1667 if (prop
->attrs() & AttrStatic
) {
1671 Slot propInd
= klass
->lookupDeclProp(prop
->name());
1672 assert(propInd
!= kInvalidSlot
);
1673 const TypedValue
* propVal
= &propVec()[propInd
];
1675 if ((!pubOnly
|| (prop
->attrs() & AttrPublic
)) &&
1676 propVal
->m_type
!= KindOfUninit
&&
1677 !inserted
[propInd
]) {
1678 inserted
[propInd
] = true;
1680 StrNR(klass
->declProperties()[propInd
].m_mangledName
).asString())
1681 .setWithRef(tvAsCVarRef(propVal
));
1685 void ObjectData::getProps(const Class
* klass
, bool pubOnly
,
1688 std::vector
<bool>& inserted
) const {
1689 PreClass::Prop
const* propVec
= pc
->properties();
1690 size_t count
= pc
->numProperties();
1691 for (size_t i
= 0; i
< count
; ++i
) {
1692 getProp(klass
, pubOnly
, &propVec
[i
], props
, inserted
);
1696 Variant
ObjectData::invokeSleep() {
1697 const Func
* method
= m_cls
->lookupMethod(s___sleep
.get());
1700 g_context
->invokeFuncFew(&tv
, method
, this);
1701 return tvAsVariant(&tv
);
1703 return uninit_null();
1707 Variant
ObjectData::invokeToDebugDisplay() {
1708 const Func
* method
= m_cls
->lookupMethod(s___toDebugDisplay
.get());
1711 g_context
->invokeFuncFew(&tv
, method
, this);
1712 return tvAsVariant(&tv
);
1714 return uninit_null();
1718 Variant
ObjectData::invokeWakeup() {
1719 const Func
* method
= m_cls
->lookupMethod(s___wakeup
.get());
1722 g_context
->invokeFuncFew(&tv
, method
, this);
1723 return tvAsVariant(&tv
);
1725 return uninit_null();
1729 String
ObjectData::invokeToString() {
1730 const Func
* method
= m_cls
->getToString();
1732 // If the object does not define a __toString() method, raise a
1733 // recoverable error
1734 raise_recoverable_error(
1735 "Object of class %s could not be converted to string",
1736 m_cls
->preClass()->name()->data()
1738 // If the user error handler decides to allow execution to continue,
1739 // we return the empty string.
1740 return empty_string
;
1743 g_context
->invokeFuncFew(&tv
, method
, this);
1744 if (!IS_STRING_TYPE(tv
.m_type
)) {
1745 // Discard the value returned by the __toString() method and raise
1746 // a recoverable error
1747 tvRefcountedDecRef(tv
);
1748 raise_recoverable_error(
1749 "Method %s::__toString() must return a string value",
1750 m_cls
->preClass()->name()->data());
1751 // If the user error handler decides to allow execution to continue,
1752 // we return the empty string.
1753 return empty_string
;
1755 String ret
= tv
.m_data
.pstr
;
1756 decRefStr(tv
.m_data
.pstr
);
1760 bool ObjectData::hasToString() {
1761 return (m_cls
->getToString() != nullptr);
1764 void ObjectData::cloneSet(ObjectData
* clone
) {
1765 auto const nProps
= m_cls
->numDeclProperties();
1766 auto const clonePropVec
= clone
->propVec();
1767 for (auto i
= Slot
{0}; i
< nProps
; i
++) {
1768 tvRefcountedDecRef(&clonePropVec
[i
]);
1769 tvDupFlattenVars(&propVec()[i
], &clonePropVec
[i
]);
1771 if (UNLIKELY(getAttribute(HasDynPropArr
))) {
1772 auto& dynProps
= dynPropArray();
1773 auto& cloneProps
= clone
->reserveProperties(dynProps
.size());
1775 ssize_t iter
= dynProps
.get()->iter_begin();
1776 while (iter
!= ArrayData::invalid_index
) {
1777 auto props
= static_cast<HphpArray
*>(dynProps
.get());
1779 props
->nvGetKey(&key
, iter
);
1780 assert(tvIsString(&key
));
1781 StringData
* strKey
= key
.m_data
.pstr
;
1782 TypedValue
* val
= props
->nvGet(strKey
);
1784 auto const retval
= reinterpret_cast<TypedValue
*>(
1785 &cloneProps
.lvalAt(String(strKey
), AccessFlags::Key
)
1787 tvDupFlattenVars(val
, retval
, cloneProps
.get());
1788 iter
= dynProps
.get()->iter_advance(iter
);
1794 ObjectData
* ObjectData::cloneImpl() {
1796 Object o
= obj
= ObjectData::newInstance(m_cls
);
1798 if (UNLIKELY(getAttribute(HasNativeData
))) {
1799 Native::nativeDataInstanceCopy(obj
, this);
1802 auto const hasCloneBit
= getAttribute(HasClone
);
1804 if (!hasCloneBit
) return o
.detach();
1806 auto const method
= obj
->m_cls
->lookupMethod(s_clone
.get());
1808 // PHP classes that inherit from cpp builtins that have special clone
1809 // functionality *may* also define a __clone method, but it's totally
1810 // fine if a __clone doesn't exist.
1811 if (!method
&& getAttribute(IsCppBuiltin
)) return o
.detach();
1816 g_context
->invokeFuncFew(&tv
, method
, obj
);
1817 tvRefcountedDecRef(&tv
);
1822 RefData
* ObjectData::zGetProp(Class
* ctx
, const StringData
* key
,
1823 bool& visible
, bool& accessible
,
1825 auto tv
= getProp(ctx
, key
, visible
, accessible
, unset
);
1826 if (tv
->m_type
!= KindOfRef
) {
1829 return tv
->m_data
.pref
;
1832 bool ObjectData::hasDynProps() const {
1833 return getAttribute(HasDynPropArr
) && dynPropArray().size() != 0;
1836 void ObjectData::getChildren(std::vector
<TypedValue
*>& out
) {
1837 Slot nProps
= m_cls
->numDeclProperties();
1838 for (Slot i
= 0; i
< nProps
; ++i
) {
1839 out
.push_back(&propVec()[i
]);
1841 if (UNLIKELY(getAttribute(HasDynPropArr
))) {
1842 dynPropArray()->getChildren(out
);
1846 const char* ObjectData::classname_cstr() const {
1847 return o_getClassName().data();
1850 void ObjectData::compileTimeAssertions() {
1851 static_assert(offsetof(ObjectData
, m_count
) == FAST_REFCOUNT_OFFSET
, "");