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"
42 #include "hphp/runtime/vm/repo.h"
44 #include "hphp/system/systemlib.h"
46 #include "folly/Hash.h"
47 #include "folly/ScopeGuard.h"
53 //////////////////////////////////////////////////////////////////////
55 // current maximum object identifier
56 __thread
int ObjectData::os_max_id
;
58 TRACE_SET_MOD(runtime
);
61 s_offsetGet("offsetGet"),
63 s_serialize("serialize"),
66 static Array
ArrayObject_toArray(const ObjectData
* obj
) {
67 bool visible
, accessible
, unset
;
68 auto prop
= obj
->getProp(
69 SystemLib::s_ArrayObjectClass
, s_storage
.get(),
70 visible
, accessible
, unset
72 assert(visible
&& accessible
&& !unset
);
73 return tvAsCVarRef(prop
).toArray();
76 static_assert(sizeof(ObjectData
) == use_lowptr
? 16 : 32,
77 "Change this only on purpose");
79 //////////////////////////////////////////////////////////////////////
81 bool ObjectData::destruct() {
82 if (UNLIKELY(RuntimeOption::EnableObjDestructCall
)) {
83 g_context
->m_liveBCObjs
.erase(this);
87 if (auto meth
= m_cls
->getDtor()) {
88 // We don't run PHP destructors while we're unwinding for a C++ exception.
89 // We want to minimize the PHP code we run while propagating fatals, so
90 // we do this check here on a very common path, in the relativley slower
92 auto& faults
= g_context
->m_faults
;
93 if (!faults
.empty()) {
94 if (faults
.back().m_faultType
== Fault::Type::CppException
) return true;
96 // We raise the refcount around the call to __destruct(). This is to
97 // prevent the refcount from going to zero when the destructor returns.
98 CountableHelper
h(this);
99 RefCount c
= this->getCount();
101 tvWriteNull(&retval
);
103 // Call the destructor method
104 g_context
->invokeFuncFew(&retval
, meth
, this);
106 // Swallow any exceptions that escape the __destruct method
107 handle_destructor_exception();
109 tvRefcountedDecRef(&retval
);
110 return c
== this->getCount();
116 ///////////////////////////////////////////////////////////////////////////////
119 const String
& ObjectData::o_getClassName() const {
120 return *(const String
*)(&m_cls
->preClass()->nameRef());
123 bool ObjectData::o_instanceof(const String
& s
) const {
124 Class
* cls
= Unit::lookupClass(s
.get());
125 if (!cls
) return false;
126 return m_cls
->classof(cls
);
129 bool ObjectData::o_toBooleanImpl() const noexcept
{
130 // Note: if you add more cases here, hhbbc/class-util.cpp also needs
132 if (isCollection()) {
133 if (m_cls
== c_Vector::classof()) {
134 return c_Vector::ToBool(this);
135 } else if (m_cls
== c_Map::classof()) {
136 return c_Map::ToBool(this);
137 } else if (m_cls
== c_ImmMap::classof()) {
138 return c_ImmMap::ToBool(this);
139 } else if (m_cls
== c_Set::classof()) {
140 return c_Set::ToBool(this);
141 } else if (m_cls
== c_ImmVector::classof()) {
142 return c_ImmVector::ToBool(this);
143 } else if (m_cls
== c_ImmSet::classof()) {
144 return c_ImmSet::ToBool(this);
146 always_assert(false);
148 } else if (instanceof(c_SimpleXMLElement::classof())) {
149 // SimpleXMLElement is the only non-collection class that has custom
151 return c_SimpleXMLElement::ToBool(this);
153 always_assert(false);
157 int64_t ObjectData::o_toInt64Impl() const noexcept
{
158 // SimpleXMLElement is the only class that has proper custom int casting.
159 // If others are added in future, just turn this assert into an if and
161 assert(instanceof(c_SimpleXMLElement::classof()));
162 return c_SimpleXMLElement::ToInt64(this);
165 double ObjectData::o_toDoubleImpl() const noexcept
{
166 // SimpleXMLElement is the only non-collection class that has custom
167 // double casting. If others are added in future, just turn this assert
168 // into an if and add cases.
169 assert(instanceof(c_SimpleXMLElement::classof()));
170 return c_SimpleXMLElement::ToDouble(this);
173 ///////////////////////////////////////////////////////////////////////////////
174 // instance methods and properties
176 const StaticString
s_getIterator("getIterator");
178 Object
ObjectData::iterableObject(bool& isIterable
,
179 bool mayImplementIterator
/* = true */) {
180 assert(mayImplementIterator
|| !implementsIterator());
181 if (mayImplementIterator
&& implementsIterator()) {
186 while (obj
->instanceof(SystemLib::s_IteratorAggregateClass
)) {
187 Variant iterator
= obj
->o_invoke_few_args(s_getIterator
, 0);
188 if (!iterator
.isObject()) break;
189 ObjectData
* o
= iterator
.getObjectData();
190 if (o
->instanceof(SystemLib::s_IteratorClass
)) {
200 Array
& ObjectData::dynPropArray() const {
201 assert(getAttribute(HasDynPropArr
));
202 assert(g_context
->dynPropTable
.count(this));
203 return g_context
->dynPropTable
[this].arr();
206 Array
& ObjectData::reserveProperties(int numDynamic
/* = 2 */) {
207 if (getAttribute(HasDynPropArr
)) return dynPropArray();
209 assert(!g_context
->dynPropTable
.count(this));
210 auto& arr
= g_context
->dynPropTable
[this].arr();
211 arr
= Array::attach(MixedArray::MakeReserveMixed(numDynamic
));
212 setAttribute(HasDynPropArr
);
216 Variant
* ObjectData::o_realProp(const String
& propName
, int flags
,
217 const String
& context
/* = null_string */) {
219 * Returns a pointer to a place for a property value. This should never
220 * call the magic methods __get or __set. The flags argument describes the
221 * behavior in cases where the named property is nonexistent or
224 Class
* ctx
= nullptr;
225 if (!context
.empty()) {
226 ctx
= Unit::lookupClass(context
.get());
229 bool visible
, accessible
, unset
;
230 auto ret
= getProp(ctx
, propName
.get(), visible
, accessible
, unset
);
232 // Property is not declared, and not dynamically created yet.
233 if (!(flags
& RealPropCreate
)) {
236 return &reserveProperties().lvalAt(propName
, AccessFlags::Key
);
239 // ret is non-NULL if we reach here
241 if ((accessible
&& !unset
) ||
242 (flags
& (RealPropUnchecked
|RealPropExist
))) {
243 return reinterpret_cast<Variant
*>(ret
);
249 inline Variant
ObjectData::o_getImpl(const String
& propName
, int flags
,
250 bool error
/* = true */,
251 const String
& context
/*= null_string*/) {
252 if (UNLIKELY(!*propName
.data())) {
253 throw_invalid_property_name(propName
);
256 if (Variant
* t
= o_realProp(propName
, flags
, context
)) {
257 if (t
->isInitialized())
261 if (getAttribute(UseGet
)) {
263 tvWriteNull(&tvResult
);
264 if (invokeGet(&tvResult
, propName
.get())) {
265 return tvAsCVarRef(&tvResult
);
270 raise_notice("Undefined property: %s::$%s", o_getClassName().data(),
274 return uninit_null();
277 Variant
ObjectData::o_get(const String
& propName
, bool error
/* = true */,
278 const String
& context
/* = null_string */) {
279 return o_getImpl(propName
, 0, error
, context
);
283 ALWAYS_INLINE Variant
ObjectData::o_setImpl(const String
& propName
, T v
,
284 const String
& context
) {
285 if (UNLIKELY(!*propName
.data())) {
286 throw_invalid_property_name(propName
);
289 bool useSet
= getAttribute(UseSet
);
290 auto flags
= useSet
? 0 : RealPropCreate
;
292 if (Variant
* t
= o_realProp(propName
, flags
, context
)) {
293 if (!useSet
|| t
->isInitialized()) {
301 invokeSet(&ignored
, propName
.get(), (TypedValue
*)(&variant(v
)))) {
302 tvRefcountedDecRef(&ignored
);
308 Variant
ObjectData::o_set(const String
& propName
, const Variant
& v
) {
309 return o_setImpl
<const Variant
&>(propName
, v
, null_string
);
312 Variant
ObjectData::o_set(const String
& propName
, const Variant
& v
,
313 const String
& context
) {
314 return o_setImpl
<const Variant
&>(propName
, v
, context
);
317 void ObjectData::o_setArray(const Array
& properties
) {
318 for (ArrayIter
iter(properties
); iter
; ++iter
) {
319 String k
= iter
.first().toString();
320 Class
* ctx
= nullptr;
321 // If the key begins with a NUL, it's a private or protected property. Read
322 // the class name from between the two NUL bytes.
324 // Note: if you change this, you need to change similar logic in
326 if (!k
.empty() && k
[0] == '\0') {
327 int subLen
= k
.find('\0', 1) + 1;
328 String cls
= k
.substr(1, subLen
- 2);
329 if (cls
.size() == 1 && cls
[0] == '*') {
334 ctx
= Unit::lookupClass(cls
.get());
337 k
= k
.substr(subLen
);
340 const Variant
& secondRef
= iter
.secondRef();
343 // Set prop is happening with WithRef semantics---we only
344 // use a binding assignment if it was already KindOfRef,
345 // so despite the const_cast here we're safely not
346 // modifying the original Variant.
347 const_cast<TypedValue
*>(secondRef
.asTypedValue()),
348 secondRef
.isReferenced());
352 void ObjectData::o_getArray(Array
& props
, bool pubOnly
/* = false */) const {
353 // The declared properties in the resultant array should be a permutation of
354 // propVec. They appear in the following order: go most-to-least-derived in
355 // the inheritance hierarchy, inserting properties in declaration order (with
356 // the wrinkle that overridden properties should appear only once, with the
357 // access level given to it in its most-derived declaration).
359 // This is needed to keep track of which elements have been inserted. This is
360 // the smoothest way to get overridden properties right.
361 std::vector
<bool> inserted(m_cls
->numDeclProperties(), false);
363 // Iterate over declared properties and insert {mangled name --> prop} pairs.
364 const Class
* cls
= m_cls
;
366 getProps(cls
, pubOnly
, cls
->preClass(), props
, inserted
);
367 for (auto const& traitCls
: cls
->usedTraitClasses()) {
368 getProps(cls
, pubOnly
, traitCls
->preClass(), props
, inserted
);
373 // Iterate over dynamic properties and insert {name --> prop} pairs.
374 if (UNLIKELY(getAttribute(HasDynPropArr
))) {
375 auto& dynProps
= dynPropArray();
376 if (!dynProps
.empty()) {
377 for (ArrayIter
it(dynProps
.get()); !it
.end(); it
.next()) {
378 props
.setWithRef(it
.first(), it
.secondRef(), true);
384 Array
ObjectData::o_toArray(bool pubOnly
/* = false */) const {
385 // We can quickly tell if this object is a collection, which lets us avoid
386 // checking for each class in turn if it's not one.
387 if (isCollection()) {
388 if (m_cls
== c_Vector::classof()) {
389 return c_Vector::ToArray(this);
390 } else if (m_cls
== c_Map::classof()) {
391 return c_Map::ToArray(this);
392 } else if (m_cls
== c_Set::classof()) {
393 return c_Set::ToArray(this);
394 } else if (m_cls
== c_Pair::classof()) {
395 return c_Pair::ToArray(this);
396 } else if (m_cls
== c_ImmVector::classof()) {
397 return c_ImmVector::ToArray(this);
398 } else if (m_cls
== c_ImmMap::classof()) {
399 return c_ImmMap::ToArray(this);
400 } else if (m_cls
== c_ImmSet::classof()) {
401 return c_ImmSet::ToArray(this);
403 // It's undefined what happens if you reach not_reached. We want to be sure
404 // to hard fail if we get here.
405 always_assert(false);
406 } else if (UNLIKELY(getAttribute(CallToImpl
))) {
407 // If we end up with other classes that need special behavior, turn the
408 // assert into an if and add cases.
409 assert(instanceof(c_SimpleXMLElement::classof()));
410 return c_SimpleXMLElement::ToArray(this);
411 } else if (UNLIKELY(instanceof(SystemLib::s_ArrayObjectClass
))) {
412 return ArrayObject_toArray(this);
414 Array
ret(ArrayData::Create());
415 o_getArray(ret
, pubOnly
);
420 Array
ObjectData::o_toIterArray(const String
& context
,
421 bool getRef
/* = false */) {
422 Array
* dynProps
= nullptr;
423 size_t size
= m_cls
->declPropNumAccessible();
424 if (getAttribute(HasDynPropArr
)) {
425 dynProps
= &dynPropArray();
426 size
+= dynProps
->size();
428 Array retArray
{ Array::attach(MixedArray::MakeReserveMixed(size
)) };
430 Class
* ctx
= nullptr;
431 if (!context
.empty()) {
432 ctx
= Unit::lookupClass(context
.get());
435 // Get all declared properties first, bottom-to-top in the inheritance
436 // hierarchy, in declaration order.
437 const Class
* klass
= m_cls
;
439 const PreClass::Prop
* props
= klass
->preClass()->properties();
440 const size_t numProps
= klass
->preClass()->numProperties();
442 for (size_t i
= 0; i
< numProps
; ++i
) {
443 auto key
= const_cast<StringData
*>(props
[i
].name());
444 bool visible
, accessible
, unset
;
445 auto val
= getProp(ctx
, key
, visible
, accessible
, unset
);
446 if (accessible
&& val
->m_type
!= KindOfUninit
&& !unset
) {
448 if (val
->m_type
!= KindOfRef
) {
451 retArray
.setRef(StrNR(key
), tvAsVariant(val
), true /* isKey */);
453 retArray
.set(StrNR(key
), tvAsCVarRef(val
), true /* isKey */);
457 klass
= klass
->parent();
460 // Now get dynamic properties.
462 ssize_t iter
= dynProps
->get()->iter_begin();
463 while (iter
!= ArrayData::invalid_index
) {
465 dynProps
->get()->nvGetKey(&key
, iter
);
466 iter
= dynProps
->get()->iter_advance(iter
);
468 // You can get this if you cast an array to object. These
469 // properties must be dynamic because you can't declare a
470 // property with a non-string name.
471 if (UNLIKELY(!IS_STRING_TYPE(key
.m_type
))) {
472 assert(key
.m_type
== KindOfInt64
);
474 auto& lval
= dynProps
->lvalAt(key
.m_data
.num
);
475 retArray
.setRef(key
.m_data
.num
, lval
);
477 auto const val
= dynProps
->get()->nvGet(key
.m_data
.num
);
478 retArray
.set(key
.m_data
.num
, tvAsCVarRef(val
));
483 auto const strKey
= key
.m_data
.pstr
;
485 auto& lval
= dynProps
->lvalAt(StrNR(strKey
), AccessFlags::Key
);
486 retArray
.setRef(StrNR(strKey
), lval
, true /* isKey */);
488 auto const val
= dynProps
->get()->nvGet(strKey
);
489 retArray
.set(StrNR(strKey
), tvAsCVarRef(val
), true /* isKey */);
498 static bool decode_invoke(const String
& s
, ObjectData
* obj
, bool fatal
,
501 ctx
.cls
= obj
->getVMClass();
502 ctx
.invName
= nullptr;
504 ctx
.func
= ctx
.cls
->lookupMethod(s
.get());
506 if (ctx
.func
->attrs() & AttrStatic
) {
507 // If we found a method and its static, null out this_
511 // If this_ is non-null AND we could not find a method, try
512 // looking up __call in cls's method table
513 ctx
.func
= ctx
.cls
->lookupMethod(s_call
.get());
516 // Bail if we couldn't find the method or __call
517 o_invoke_failed(ctx
.cls
->name()->data(), s
.data(), fatal
);
520 // We found __call! Stash the original name into invName.
521 assert(!(ctx
.func
->attrs() & AttrStatic
));
522 ctx
.invName
= s
.get();
523 ctx
.invName
->incRefCount();
528 Variant
ObjectData::o_invoke(const String
& s
, const Variant
& params
,
529 bool fatal
/* = true */) {
531 if (!decode_invoke(s
, this, fatal
, ctx
) ||
532 (!isContainer(params
) && !params
.isNull())) {
533 return Variant(Variant::NullInit());
536 g_context
->invokeFunc((TypedValue
*)&ret
, ctx
, params
);
540 Variant
ObjectData::o_invoke_few_args(const String
& s
, int count
,
541 INVOKE_FEW_ARGS_IMPL_ARGS
) {
544 if (!decode_invoke(s
, this, true, ctx
)) {
545 return Variant(Variant::NullInit());
548 TypedValue args
[INVOKE_FEW_ARGS_COUNT
];
550 default: not_implemented();
551 #if INVOKE_FEW_ARGS_COUNT > 6
552 case 10: tvCopy(*a9
.asTypedValue(), args
[9]);
553 case 9: tvCopy(*a8
.asTypedValue(), args
[8]);
554 case 8: tvCopy(*a7
.asTypedValue(), args
[7]);
555 case 7: tvCopy(*a6
.asTypedValue(), args
[6]);
557 #if INVOKE_FEW_ARGS_COUNT > 3
558 case 6: tvCopy(*a5
.asTypedValue(), args
[5]);
559 case 5: tvCopy(*a4
.asTypedValue(), args
[4]);
560 case 4: tvCopy(*a3
.asTypedValue(), args
[3]);
562 case 3: tvCopy(*a2
.asTypedValue(), args
[2]);
563 case 2: tvCopy(*a1
.asTypedValue(), args
[1]);
564 case 1: tvCopy(*a0
.asTypedValue(), args
[0]);
569 g_context
->invokeFuncFew(ret
.asTypedValue(), ctx
, count
, args
);
575 s_protected_prefix("\0*\0", 3);
577 void ObjectData::serialize(VariableSerializer
* serializer
) const {
578 if (UNLIKELY(serializer
->incNestedLevel((void*)this, true))) {
579 serializer
->writeOverflow((void*)this, true);
581 serializeImpl(serializer
);
583 serializer
->decNestedLevel((void*)this);
587 s_PHP_DebugDisplay("__PHP_DebugDisplay"),
588 s_PHP_Incomplete_Class("__PHP_Incomplete_Class"),
589 s_PHP_Incomplete_Class_Name("__PHP_Incomplete_Class_Name"),
590 s_PHP_Unserializable_Class_Name("__PHP_Unserializable_Class_Name"),
591 s_debugInfo("__debugInfo");
593 /* Get properties from the actual object unless we're
594 * serializing for var_dump()/print_r() and the object
595 * exports a __debugInfo() magic method.
596 * In which case, call that and use the array it returns.
598 inline Array
getSerializeProps(const ObjectData
* obj
,
599 VariableSerializer
* serializer
) {
600 if ((serializer
->getType() != VariableSerializer::Type::PrintR
) &&
601 (serializer
->getType() != VariableSerializer::Type::VarDump
)) {
602 return obj
->o_toArray();
604 auto cls
= obj
->getVMClass();
605 auto debuginfo
= cls
->lookupMethod(s_debugInfo
.get());
607 return obj
->o_toArray();
609 if (debuginfo
->attrs() & (AttrPrivate
|AttrProtected
|
610 AttrAbstract
|AttrStatic
)) {
611 raise_warning("%s::__debugInfo() must be public and non-static",
612 cls
->name()->data());
613 return obj
->o_toArray();
615 Variant ret
= const_cast<ObjectData
*>(obj
)->o_invoke_few_args(s_debugInfo
, 0);
617 return ret
.toArray();
620 return Array::Create();
622 raise_error("__debugInfo() must return an array");
626 void ObjectData::serializeImpl(VariableSerializer
* serializer
) const {
627 bool handleSleep
= false;
630 if (LIKELY(serializer
->getType() == VariableSerializer::Type::Serialize
||
631 serializer
->getType() == VariableSerializer::Type::APCSerialize
)) {
632 if (instanceof(SystemLib::s_SerializableClass
)) {
633 assert(!isCollection());
635 const_cast<ObjectData
*>(this)->o_invoke_few_args(s_serialize
, 0);
636 if (ret
.isString()) {
637 serializer
->writeSerializableObject(o_getClassName(), ret
.toString());
638 } else if (ret
.isNull()) {
639 serializer
->writeNull();
641 raise_error("%s::serialize() must return a string or NULL",
642 o_getClassName().data());
646 // Only serialize CPP extension type instances which can actually
648 auto cls
= getVMClass();
649 if (cls
->instanceCtor() && !cls
->isCppSerializable()) {
650 Object placeholder
= ObjectData::newInstance(
651 SystemLib::s___PHP_Unserializable_ClassClass
);
652 placeholder
->o_set(s_PHP_Unserializable_Class_Name
, o_getClassName());
653 placeholder
->serialize(serializer
);
656 if (getAttribute(HasSleep
)) {
658 ret
= const_cast<ObjectData
*>(this)->invokeSleep();
660 } else if (UNLIKELY(serializer
->getType() ==
661 VariableSerializer::Type::DebuggerSerialize
)) {
662 if (instanceof(SystemLib::s_SerializableClass
)) {
663 assert(!isCollection());
666 const_cast<ObjectData
*>(this)->o_invoke_few_args(s_serialize
, 0);
667 if (ret
.isString()) {
668 serializer
->writeSerializableObject(o_getClassName(), ret
.toString());
669 } else if (ret
.isNull()) {
670 serializer
->writeNull();
672 raise_warning("%s::serialize() must return a string or NULL",
673 o_getClassName().data());
674 serializer
->writeNull();
677 // serialize() throws exception
678 raise_warning("%s::serialize() throws exception",
679 o_getClassName().data());
680 serializer
->writeNull();
684 // Don't try to serialize a CPP extension class which doesn't
685 // support serialization. Just send the class name instead.
686 if (getAttribute(IsCppBuiltin
) && !getVMClass()->isCppSerializable()) {
687 serializer
->write(o_getClassName());
690 if (getAttribute(HasSleep
)) {
693 ret
= const_cast<ObjectData
*>(this)->invokeSleep();
695 raise_warning("%s::sleep() throws exception", o_getClassName().data());
696 serializer
->writeNull();
702 if (UNLIKELY(handleSleep
)) {
703 assert(!isCollection());
705 Array wanted
= Array::Create();
706 assert(ret
.getRawType() == KindOfArray
); // can't be KindOfRef
707 const Array
&props
= ret
.asCArrRef();
708 for (ArrayIter
iter(props
); iter
; ++iter
) {
709 String memberName
= iter
.second().toString();
710 String propName
= memberName
;
712 auto attrMask
= AttrNone
;
713 if (memberName
.data()[0] == 0) {
714 int subLen
= memberName
.find('\0', 1) + 1;
716 if (subLen
== 3 && memberName
.data()[1] == '*') {
717 attrMask
= AttrProtected
;
718 memberName
= memberName
.substr(subLen
);
720 attrMask
= AttrPrivate
;
721 String cls
= memberName
.substr(1, subLen
- 2);
722 ctx
= Unit::lookupClass(cls
.get());
724 memberName
= memberName
.substr(subLen
);
733 Slot propInd
= m_cls
->getDeclPropIndex(ctx
, memberName
.get(),
735 if (propInd
!= kInvalidSlot
) {
737 const TypedValue
* prop
= &propVec()[propInd
];
738 if (prop
->m_type
!= KindOfUninit
) {
739 auto attrs
= m_cls
->declProperties()[propInd
].m_attrs
;
740 if (attrs
& AttrPrivate
) {
741 memberName
= concat4(s_zero
, ctx
->nameRef(),
743 } else if (attrs
& AttrProtected
) {
744 memberName
= concat(s_protected_prefix
, memberName
);
746 if (!attrMask
|| (attrMask
& attrs
) == attrMask
) {
747 wanted
.set(memberName
, tvAsCVarRef(prop
));
753 if (!attrMask
&& UNLIKELY(getAttribute(HasDynPropArr
))) {
754 const TypedValue
* prop
= dynPropArray()->nvGet(propName
.get());
756 wanted
.set(propName
, tvAsCVarRef(prop
));
760 raise_notice("serialize(): \"%s\" returned as member variable from "
761 "__sleep() but does not exist", propName
.data());
762 wanted
.set(propName
, init_null());
764 serializer
->setObjectInfo(o_getClassName(), o_getId(), 'O');
765 wanted
.serialize(serializer
, true);
767 raise_notice("serialize(): __sleep should return an array only "
768 "containing the names of instance-variables to "
770 uninit_null().serialize(serializer
);
773 if (isCollection()) {
774 collectionSerialize(const_cast<ObjectData
*>(this), serializer
);
776 const String
& className
= o_getClassName();
777 Array properties
= getSerializeProps(this, serializer
);
778 if (serializer
->getType() ==
779 VariableSerializer::Type::DebuggerSerialize
) {
781 auto val
= const_cast<ObjectData
*>(this)->invokeToDebugDisplay();
782 if (val
.isInitialized()) {
783 properties
.lvalAt(s_PHP_DebugDisplay
).assign(val
);
786 raise_warning("%s::__toDebugDisplay() throws exception",
787 o_getClassName().data());
790 if (serializer
->getType() == VariableSerializer::Type::DebuggerDump
) {
791 Variant
* debugDispVal
= const_cast<ObjectData
*>(this)-> // XXX
792 o_realProp(s_PHP_DebugDisplay
, 0);
794 debugDispVal
->serialize(serializer
);
798 if (serializer
->getType() != VariableSerializer::Type::VarDump
&&
799 className
== s_PHP_Incomplete_Class
) {
800 Variant
* cname
= const_cast<ObjectData
*>(this)-> // XXX
801 o_realProp(s_PHP_Incomplete_Class_Name
, 0);
802 if (cname
&& cname
->isString()) {
803 serializer
->setObjectInfo(cname
->toCStrRef(), o_getId(), 'O');
804 properties
.remove(s_PHP_Incomplete_Class_Name
, true);
805 properties
.serialize(serializer
, true);
809 serializer
->setObjectInfo(className
, o_getId(), 'O');
810 properties
.serialize(serializer
, true);
815 ObjectData
* ObjectData::clone() {
816 if (getAttribute(HasClone
) && getAttribute(IsCppBuiltin
)) {
817 if (isCollection()) {
818 if (m_cls
== c_Vector::classof()) {
819 return c_Vector::Clone(this);
820 } else if (m_cls
== c_Map::classof()) {
821 return c_Map::Clone(this);
822 } else if (m_cls
== c_ImmMap::classof()) {
823 return c_ImmMap::Clone(this);
824 } else if (m_cls
== c_Set::classof()) {
825 return c_Set::Clone(this);
826 } else if (m_cls
== c_Pair::classof()) {
827 return c_Pair::Clone(this);
828 } else if (m_cls
== c_ImmVector::classof()) {
829 return c_ImmVector::Clone(this);
830 } else if (m_cls
== c_ImmSet::classof()) {
831 return c_ImmSet::Clone(this);
833 always_assert(false);
835 } else if (instanceof(c_Closure::classof())) {
836 return c_Closure::Clone(this);
837 } else if (instanceof(c_Continuation::classof())) {
838 return c_Continuation::Clone(this);
839 } else if (instanceof(c_DateTime::classof())) {
840 return c_DateTime::Clone(this);
841 } else if (instanceof(c_DateTimeZone::classof())) {
842 return c_DateTimeZone::Clone(this);
843 } else if (instanceof(c_DateInterval::classof())) {
844 return c_DateInterval::Clone(this);
845 } else if (instanceof(c_DOMNode::classof())) {
846 return c_DOMNode::Clone(this);
847 } else if (instanceof(c_SimpleXMLElement::classof())) {
848 return c_SimpleXMLElement::Clone(this);
850 always_assert(false);
856 Variant
ObjectData::offsetGet(Variant key
) {
857 assert(instanceof(SystemLib::s_ArrayAccessClass
));
858 const Func
* method
= m_cls
->lookupMethod(s_offsetGet
.get());
861 return uninit_null();
864 g_context
->invokeFuncFew(v
.asTypedValue(), method
,
865 this, nullptr, 1, key
.asCell());
869 ///////////////////////////////////////////////////////////////////////////////
872 s___get(LITSTR_INIT("__get")),
873 s___set(LITSTR_INIT("__set")),
874 s___isset(LITSTR_INIT("__isset")),
875 s___unset(LITSTR_INIT("__unset")),
876 s___init__(LITSTR_INIT("__init__")),
877 s___sleep(LITSTR_INIT("__sleep")),
878 s___toDebugDisplay(LITSTR_INIT("__toDebugDisplay")),
879 s___wakeup(LITSTR_INIT("__wakeup"));
881 void deepInitHelper(TypedValue
* propVec
, const TypedValueAux
* propData
,
884 auto* src
= propData
;
885 for (; src
!= propData
+ nProps
; ++src
, ++dst
) {
887 // m_aux.u_deepInit is true for properties that need "deep" initialization
888 if (src
->deepInit()) {
890 collectionDeepCopyTV(dst
);
895 TypedValue
* ObjectData::propVec() {
896 auto const ret
= reinterpret_cast<uintptr_t>(this + 1);
897 if (UNLIKELY(getAttribute(IsCppBuiltin
))) {
898 return reinterpret_cast<TypedValue
*>(ret
+ m_cls
->builtinODTailSize());
900 return reinterpret_cast<TypedValue
*>(ret
);
903 const TypedValue
* ObjectData::propVec() const {
904 return const_cast<ObjectData
*>(this)->propVec();
908 * Only call this if cls->callsCustomInstanceInit() is true
910 ObjectData
* ObjectData::callCustomInstanceInit() {
911 const Func
* init
= m_cls
->lookupMethod(s___init__
.get());
914 // We need to incRef/decRef here because we're still a new (m_count
915 // == 0) object and invokeFunc is going to expect us to have a
916 // reasonable refcount.
919 g_context
->invokeFuncFew(&tv
, init
, this);
921 assert(!IS_REFCOUNTED_TYPE(tv
.m_type
));
923 this->setNoDestruct();
930 ObjectData
* ObjectData::newInstanceRaw(Class
* cls
, uint32_t size
) {
931 return new (MM().smartMallocSizeLogged(size
))
932 ObjectData(cls
, NoInit::noinit
);
935 ObjectData
* ObjectData::newInstanceRawBig(Class
* cls
, size_t size
) {
936 return new (MM().smartMallocSizeBigLogged
<false>(size
).first
)
937 ObjectData(cls
, NoInit::noinit
);
941 static void freeDynPropArray(ObjectData
* inst
) {
942 auto& table
= g_context
->dynPropTable
;
943 auto it
= table
.find(inst
);
944 assert(it
!= end(table
));
945 it
->second
.destroy();
949 ObjectData::~ObjectData() {
950 int& pmax
= os_max_id
;
951 if (o_id
&& o_id
== pmax
) {
954 if (UNLIKELY(getAttribute(HasDynPropArr
))) freeDynPropArray(this);
957 void ObjectData::DeleteObject(ObjectData
* objectData
) {
958 auto const cls
= objectData
->getVMClass();
960 if (UNLIKELY(objectData
->getAttribute(InstanceDtor
))) {
961 return cls
->instanceDtor()(objectData
, cls
);
964 assert(!cls
->preClass()->builtinObjSize());
965 assert(!cls
->preClass()->builtinODOffset());
966 objectData
->~ObjectData();
968 // ObjectData subobject is logically destructed now---don't access
969 // objectData->foo for anything.
971 auto const nProps
= size_t{cls
->numDeclProperties()};
972 auto prop
= reinterpret_cast<TypedValue
*>(objectData
+ 1);
973 auto const stop
= prop
+ nProps
;
974 for (; prop
!= stop
; ++prop
) {
975 tvRefcountedDecRef(prop
);
978 auto const size
= sizeForNProps(nProps
);
979 if (LIKELY(size
<= kMaxSmartSize
)) {
980 return MM().smartFreeSizeLogged(objectData
, size
);
982 MM().smartFreeSizeBigLogged(objectData
, size
);
985 Object
ObjectData::FromArray(ArrayData
* properties
) {
986 ObjectData
* retval
= ObjectData::newInstance(SystemLib::s_stdclassClass
);
987 auto& dynArr
= retval
->reserveProperties(properties
->size());
988 for (ssize_t pos
= properties
->iter_begin(); pos
!= ArrayData::invalid_index
;
989 pos
= properties
->iter_advance(pos
)) {
990 auto const value
= properties
->getValueRef(pos
);
992 properties
->nvGetKey(&key
, pos
);
993 if (key
.m_type
== KindOfInt64
) {
994 dynArr
.set(key
.m_data
.num
, value
);
996 assert(IS_STRING_TYPE(key
.m_type
));
997 StringData
* strKey
= key
.m_data
.pstr
;
998 dynArr
.set(StrNR(strKey
), value
, true /* isKey */);
1005 Slot
ObjectData::declPropInd(TypedValue
* prop
) const {
1006 // Do an address range check to determine whether prop physically resides
1008 const TypedValue
* pv
= propVec();
1009 if (prop
>= pv
&& prop
< &pv
[m_cls
->numDeclProperties()]) {
1012 return kInvalidSlot
;
1016 TypedValue
* ObjectData::getProp(Class
* ctx
, const StringData
* key
,
1017 bool& visible
, bool& accessible
,
1021 Slot propInd
= m_cls
->getDeclPropIndex(ctx
, key
, accessible
);
1022 visible
= (propInd
!= kInvalidSlot
);
1023 if (LIKELY(propInd
!= kInvalidSlot
)) {
1024 // We found a visible property, but it might not be accessible.
1025 // No need to check if there is a dynamic property with this name.
1026 auto const prop
= &propVec()[propInd
];
1027 if (prop
->m_type
== KindOfUninit
) {
1032 if (RuntimeOption::RepoAuthoritative
&& Repo::get().global().UsedHHBBC
) {
1033 auto const repoTy
= m_cls
->declPropRepoAuthType(propInd
);
1034 always_assert(tvMatchesRepoAuthType(*prop
, repoTy
));
1041 // We could not find a visible declared property. We need to check
1042 // for a dynamic property with this name.
1043 assert(!visible
&& !accessible
);
1044 if (UNLIKELY(getAttribute(HasDynPropArr
))) {
1045 if (auto const prop
= dynPropArray()->nvGet(key
)) {
1046 // Returned a non-declared property, we know that it is
1047 // visible and accessible (since all dynamic properties are),
1048 // and we know it is not unset (since unset dynamic properties
1049 // don't appear in the dynamic property array).
1052 // We are using an HphpArray for storage, but not really
1053 // treating it as a normal array, so this cast is safe in this
1055 assert(!dynPropArray()->hasMultipleRefs());
1056 assert(dynPropArray()->isMixed());
1057 return const_cast<TypedValue
*>(prop
);
1064 const TypedValue
* ObjectData::getProp(Class
* ctx
, const StringData
* key
,
1065 bool& visible
, bool& accessible
,
1066 bool& unset
) const {
1067 return const_cast<ObjectData
*>(this)->getProp(
1068 ctx
, key
, visible
, accessible
, unset
1072 //////////////////////////////////////////////////////////////////////
1077 * Recursion of magic property accessors is allowed, but if you
1078 * recurse on the same object, for the same property, for the same
1079 * kind of magic method, it doesn't actually enter the magic method
1080 * anymore. This matches zend behavior.
1082 * This means we need to track all active property getters and ensure
1083 * we aren't recursing for the same one. Since most accesses to magic
1084 * property getters aren't going to recurse, we optimize for the case
1085 * where only a single getter is active. If it recurses again, we
1086 * promote to a hash set to track all the information needed.
1088 * The various invokeFoo functions are the entry points here. They
1089 * require that the appropriate ObjectData::Attribute has been checked
1090 * first, and return false if they refused to run the magic method due
1091 * to a recursion error.
1094 struct PropAccessInfo
{
1097 bool operator==(const PropAccessInfo
& o
) const {
1098 return obj
== o
.obj
&& attr
== o
.attr
&& key
->same(o
.key
);
1102 const StringData
* key
; // note: not necessarily static
1103 ObjectData::Attribute attr
;
1106 struct PropAccessInfo::Hash
{
1107 size_t operator()(PropAccessInfo
const& info
) const {
1108 return folly::hash::hash_combine(
1109 hash_int64(reinterpret_cast<intptr_t>(info
.obj
)),
1111 static_cast<uint32_t>(info
.attr
)
1116 struct PropRecurInfo
{
1117 typedef smart::hash_set
<PropAccessInfo
,PropAccessInfo::Hash
> RecurSet
;
1119 const PropAccessInfo
* activePropInfo
;
1120 RecurSet
* activeSet
;
1123 __thread PropRecurInfo propRecurInfo
;
1125 template<class Invoker
>
1126 bool magic_prop_impl(TypedValue
* retval
,
1127 const StringData
* key
,
1128 const PropAccessInfo
& info
,
1130 if (UNLIKELY(propRecurInfo
.activePropInfo
!= nullptr)) {
1131 if (!propRecurInfo
.activeSet
) {
1132 propRecurInfo
.activeSet
= smart_new
<PropRecurInfo::RecurSet
>();
1133 propRecurInfo
.activeSet
->insert(*propRecurInfo
.activePropInfo
);
1135 if (!propRecurInfo
.activeSet
->insert(info
).second
) {
1136 // We're already running a magic method on the same type here.
1140 propRecurInfo
.activeSet
->erase(info
);
1147 propRecurInfo
.activePropInfo
= &info
;
1149 propRecurInfo
.activePropInfo
= nullptr;
1150 if (UNLIKELY(propRecurInfo
.activeSet
!= nullptr)) {
1151 smart_delete(propRecurInfo
.activeSet
);
1152 propRecurInfo
.activeSet
= nullptr;
1160 // Helper for making invokers for the single-argument magic property
1161 // methods. __set takes 2 args, so it uses its own function.
1162 struct MagicInvoker
{
1164 const StringData
* magicFuncName
;
1165 const PropAccessInfo
& info
;
1167 void operator()() const {
1168 auto const meth
= info
.obj
->getVMClass()->lookupMethod(magicFuncName
);
1169 TypedValue args
[1] = {
1170 make_tv
<KindOfString
>(const_cast<StringData
*>(info
.key
))
1172 g_context
->invokeFuncFew(retval
, meth
, info
.obj
, nullptr, 1, args
);
1178 bool ObjectData::invokeSet(TypedValue
* retval
, const StringData
* key
,
1180 auto const info
= PropAccessInfo
{ this, key
, UseSet
};
1181 return magic_prop_impl(
1186 auto const meth
= m_cls
->lookupMethod(s___set
.get());
1187 TypedValue args
[2] = {
1188 make_tv
<KindOfString
>(const_cast<StringData
*>(key
)),
1191 g_context
->invokeFuncFew(retval
, meth
, this, nullptr, 2, args
);
1196 bool ObjectData::invokeGet(TypedValue
* retval
, const StringData
* key
) {
1197 auto const info
= PropAccessInfo
{ this, key
, UseGet
};
1198 return magic_prop_impl(
1202 MagicInvoker
{ retval
, s___get
.get(), info
}
1206 bool ObjectData::invokeIsset(TypedValue
* retval
, const StringData
* key
) {
1207 auto const info
= PropAccessInfo
{ this, key
, UseIsset
};
1208 return magic_prop_impl(
1212 MagicInvoker
{ retval
, s___isset
.get(), info
}
1216 bool ObjectData::invokeUnset(TypedValue
* retval
, const StringData
* key
) {
1217 auto const info
= PropAccessInfo
{ this, key
, UseUnset
};
1218 return magic_prop_impl(
1222 MagicInvoker
{ retval
, s___unset
.get(), info
}
1226 bool ObjectData::invokeGetProp(TypedValue
*& retval
, TypedValue
& tvRef
,
1227 const StringData
* key
) {
1228 if (!invokeGet(&tvRef
, key
)) return false;
1233 //////////////////////////////////////////////////////////////////////
1235 template <bool warn
, bool define
>
1236 void ObjectData::propImpl(TypedValue
*& retval
, TypedValue
& tvRef
,
1238 const StringData
* key
) {
1239 bool visible
, accessible
, unset
;
1240 auto propVal
= getProp(ctx
, key
, visible
, accessible
, unset
);
1245 if (!getAttribute(UseGet
) || !invokeGetProp(retval
, tvRef
, key
)) {
1247 raiseUndefProp(key
);
1252 retval
= (TypedValue
*)&init_null_variant
;
1259 if (!getAttribute(UseGet
) || !invokeGetProp(retval
, tvRef
, key
)) {
1260 // No need to check hasProp since visible is true
1261 // Visibility is either protected or private since accessible is false
1262 Slot propInd
= m_cls
->lookupDeclProp(key
);
1263 bool priv
= m_cls
->declProperties()[propInd
].m_attrs
& AttrPrivate
;
1265 raise_error("Cannot access %s property %s::$%s",
1266 priv
? "private" : "protected",
1267 m_cls
->preClass()->name()->data(),
1272 if (getAttribute(UseGet
) && invokeGetProp(retval
, tvRef
, key
)) {
1276 if (UNLIKELY(!*key
->data())) {
1277 throw_invalid_property_name(StrNR(key
));
1280 raiseUndefProp(key
);
1283 retval
= reinterpret_cast<TypedValue
*>(
1284 &reserveProperties().lvalAt(StrNR(key
), AccessFlags::Key
)
1287 retval
= const_cast<TypedValue
*>(
1288 reinterpret_cast<const TypedValue
*>(&init_null_variant
)
1295 void ObjectData::prop(TypedValue
*& retval
, TypedValue
& tvRef
,
1296 Class
* ctx
, const StringData
* key
) {
1297 propImpl
<false, false>(retval
, tvRef
, ctx
, key
);
1300 void ObjectData::propD(TypedValue
*& retval
, TypedValue
& tvRef
,
1301 Class
* ctx
, const StringData
* key
) {
1302 propImpl
<false, true>(retval
, tvRef
, ctx
, key
);
1305 void ObjectData::propW(TypedValue
*& retval
, TypedValue
& tvRef
,
1306 Class
* ctx
, const StringData
* key
) {
1307 propImpl
<true, false>(retval
, tvRef
, ctx
, key
);
1310 void ObjectData::propWD(TypedValue
*& retval
, TypedValue
& tvRef
,
1311 Class
* ctx
, const StringData
* key
) {
1312 propImpl
<true, true>(retval
, tvRef
, ctx
, key
);
1315 bool ObjectData::propIsset(Class
* ctx
, const StringData
* key
) {
1316 bool visible
, accessible
, unset
;
1317 auto propVal
= getProp(ctx
, key
, visible
, accessible
, unset
);
1318 if (visible
&& accessible
&& !unset
) {
1319 return !cellIsNull(tvToCell(propVal
));
1322 auto tv
= make_tv
<KindOfUninit
>();
1323 if (!getAttribute(UseIsset
) || !invokeIsset(&tv
, key
)) {
1326 tvCastToBooleanInPlace(&tv
);
1327 return tv
.m_data
.num
;
1330 bool ObjectData::propEmpty(Class
* ctx
, const StringData
* key
) {
1331 bool visible
, accessible
, unset
;
1332 auto propVal
= getProp(ctx
, key
, visible
, accessible
, unset
);
1333 if (visible
&& accessible
&& !unset
) {
1334 return !cellToBool(*tvToCell(propVal
));
1337 auto tv
= make_tv
<KindOfUninit
>();
1338 if (!getAttribute(UseIsset
) || !invokeIsset(&tv
, key
)) {
1341 tvCastToBooleanInPlace(&tv
);
1342 if (!tv
.m_data
.num
) {
1345 if (getAttribute(UseGet
)) {
1346 if (invokeGet(&tv
, key
)) {
1347 bool emptyResult
= !cellToBool(*tvToCell(&tv
));
1348 tvRefcountedDecRef(&tv
);
1355 void ObjectData::setProp(Class
* ctx
,
1356 const StringData
* key
,
1358 bool bindingAssignment
/* = false */) {
1359 bool visible
, accessible
, unset
;
1360 auto propVal
= getProp(ctx
, key
, visible
, accessible
, unset
);
1361 if (visible
&& accessible
) {
1365 if (!unset
|| !getAttribute(UseSet
) || !invokeSet(&ignored
, key
, val
)) {
1366 if (UNLIKELY(bindingAssignment
)) {
1367 tvBind(val
, propVal
);
1369 tvSet(*val
, *propVal
);
1373 tvRefcountedDecRef(&ignored
);
1378 if (!getAttribute(UseSet
) || !invokeSet(&ignored
, key
, val
)) {
1381 * Note: this differs from Zend right now in the case of a
1382 * failed recursive __set. In Zend, the __set is silently
1383 * dropped, and the protected property is not modified.
1385 raise_error("Cannot access protected property");
1387 if (UNLIKELY(!*key
->data())) {
1388 throw_invalid_property_name(StrNR(key
));
1390 // when seting a dynamic property, do not write
1391 // directly to the TypedValue in the MixedArray, since
1392 // its m_aux field is used to store the string hash of
1393 // the property name. Instead, call the appropriate
1394 // setters (set() or setRef()).
1395 if (UNLIKELY(bindingAssignment
)) {
1396 reserveProperties().setRef(
1397 StrNR(key
), tvAsVariant(val
), true /* isKey */);
1399 reserveProperties().set(
1400 StrNR(key
), tvAsCVarRef(val
), true /* isKey */);
1405 tvRefcountedDecRef(&ignored
);
1408 TypedValue
* ObjectData::setOpProp(TypedValue
& tvRef
, Class
* ctx
,
1409 SetOpOp op
, const StringData
* key
,
1411 bool visible
, accessible
, unset
;
1412 auto propVal
= getProp(ctx
, key
, visible
, accessible
, unset
);
1414 if (visible
&& accessible
) {
1415 if (unset
&& getAttribute(UseGet
)) {
1416 auto tvResult
= make_tv
<KindOfUninit
>();
1417 if (invokeGet(&tvResult
, key
)) {
1418 SETOP_BODY(&tvResult
, op
, val
);
1419 if (getAttribute(UseSet
)) {
1420 assert(tvRef
.m_type
== KindOfUninit
);
1421 cellDup(*tvToCell(&tvResult
), tvRef
);
1423 if (invokeSet(&ignored
, key
, &tvRef
)) {
1424 tvRefcountedDecRef(&ignored
);
1427 tvRef
.m_type
= KindOfUninit
;
1429 cellDup(*tvToCell(&tvResult
), *propVal
);
1434 propVal
= tvToCell(propVal
);
1435 SETOP_BODY_CELL(propVal
, op
, val
);
1439 if (UNLIKELY(!*key
->data())) throw_invalid_property_name(StrNR(key
));
1441 auto const useSet
= getAttribute(UseSet
);
1442 auto const useGet
= getAttribute(UseGet
);
1444 if (useGet
&& !useSet
) {
1445 auto tvResult
= make_tv
<KindOfNull
>();
1446 // If invokeGet fails due to recursion, it leaves the KindOfNull.
1447 invokeGet(&tvResult
, key
);
1449 // Note: the tvUnboxIfNeeded comes *after* the setop on purpose
1450 // here, even though it comes before the IncDecOp in the analagous
1451 // situation in incDecProp. This is to match zend 5.5 behavior.
1452 SETOP_BODY(&tvResult
, op
, val
);
1453 tvUnboxIfNeeded(&tvResult
);
1455 if (visible
) raise_error("Cannot access protected property");
1456 propVal
= reinterpret_cast<TypedValue
*>(
1457 &reserveProperties().lvalAt(StrNR(key
), AccessFlags::Key
)
1460 // Normally this code path is defining a new dynamic property, but
1461 // unlike the non-magic case below, we may have already created it
1462 // under the recursion into invokeGet above, so we need to do a
1464 tvSet(tvResult
, *propVal
);
1468 if (useGet
&& useSet
) {
1469 if (invokeGet(&tvRef
, key
)) {
1470 // Matching zend again: incDecProp does an unbox before the
1471 // operation, but setop doesn't need to here. (We'll unbox the
1472 // value that gets passed to the magic setter, though, since
1473 // __set functions can't take parameters by reference.)
1474 SETOP_BODY(&tvRef
, op
, val
);
1476 if (invokeSet(&ignored
, key
, &tvRef
)) {
1477 tvRefcountedDecRef(&ignored
);
1483 if (visible
) raise_error("Cannot access protected property");
1485 // No visible/accessible property, and no applicable magic method:
1486 // create a new dynamic property. (We know this is a new property,
1487 // or it would've hit the visible && accessible case above.)
1488 propVal
= reinterpret_cast<TypedValue
*>(
1489 &reserveProperties().lvalAt(StrNR(key
), AccessFlags::Key
)
1491 assert(propVal
->m_type
== KindOfNull
); // cannot exist yet
1492 SETOP_BODY_CELL(propVal
, op
, val
);
1496 template <bool setResult
>
1497 void ObjectData::incDecProp(TypedValue
& tvRef
,
1500 const StringData
* key
,
1502 bool visible
, accessible
, unset
;
1503 auto propVal
= getProp(ctx
, key
, visible
, accessible
, unset
);
1505 if (visible
&& accessible
) {
1506 auto tvResult
= make_tv
<KindOfNull
>();
1507 if (unset
&& getAttribute(UseGet
) && invokeGet(&tvResult
, key
)) {
1508 IncDecBody
<setResult
>(op
, &tvResult
, &dest
);
1510 if (getAttribute(UseSet
) && invokeSet(&ignored
, key
, &tvResult
)) {
1511 tvRefcountedDecRef(&ignored
);
1512 propVal
= &tvResult
;
1514 memcpy(propVal
, &tvResult
, sizeof(TypedValue
));
1520 tvWriteNull(propVal
);
1522 propVal
= tvToCell(propVal
);
1524 IncDecBody
<setResult
>(op
, propVal
, &dest
);
1528 if (UNLIKELY(!*key
->data())) throw_invalid_property_name(StrNR(key
));
1530 auto const useSet
= getAttribute(UseSet
);
1531 auto const useGet
= getAttribute(UseGet
);
1533 if (useGet
&& !useSet
) {
1534 auto tvResult
= make_tv
<KindOfNull
>();
1535 // If invokeGet fails due to recursion, it leaves the KindOfNull
1537 invokeGet(&tvResult
, key
);
1538 tvUnboxIfNeeded(&tvResult
);
1539 IncDecBody
<setResult
>(op
, &tvResult
, &dest
);
1540 if (visible
) raise_error("Cannot access protected property");
1541 propVal
= reinterpret_cast<TypedValue
*>(
1542 &reserveProperties().lvalAt(StrNR(key
), AccessFlags::Key
)
1545 // Normally this code path is defining a new dynamic property, but
1546 // unlike the non-magic case below, we may have already created it
1547 // under the recursion into invokeGet above, so we need to do a
1549 tvSet(tvResult
, *propVal
);
1553 if (useGet
&& useSet
) {
1554 if (invokeGet(&tvRef
, key
)) {
1555 tvUnboxIfNeeded(&tvRef
);
1556 IncDecBody
<setResult
>(op
, &tvRef
, &dest
);
1558 if (invokeSet(&ignored
, key
, &tvRef
)) {
1559 tvRefcountedDecRef(&ignored
);
1565 if (visible
) raise_error("Cannot access protected property");
1567 // No visible/accessible property, and no applicable magic method:
1568 // create a new dynamic property. (We know this is a new property,
1569 // or it would've hit the visible && accessible case above.)
1570 propVal
= reinterpret_cast<TypedValue
*>(
1571 &reserveProperties().lvalAt(StrNR(key
), AccessFlags::Key
)
1573 assert(propVal
->m_type
== KindOfNull
); // cannot exist yet
1574 IncDecBody
<setResult
>(op
, propVal
, &dest
);
1577 template void ObjectData::incDecProp
<true>(TypedValue
&,
1582 template void ObjectData::incDecProp
<false>(TypedValue
&,
1588 void ObjectData::unsetProp(Class
* ctx
, const StringData
* key
) {
1589 bool visible
, accessible
, unset
;
1590 auto propVal
= getProp(ctx
, key
, visible
, accessible
, unset
);
1591 Slot propInd
= declPropInd(propVal
);
1593 if (visible
&& accessible
&& !unset
) {
1594 if (propInd
!= kInvalidSlot
) {
1595 // Declared property.
1596 tvSetIgnoreRef(*null_variant
.asTypedValue(), *propVal
);
1598 // Dynamic property.
1599 dynPropArray().remove(StrNR(key
).asString(),
1600 true /* isString */);
1605 bool tryUnset
= getAttribute(UseUnset
);
1607 if (propInd
!= kInvalidSlot
&& !accessible
&& !tryUnset
) {
1608 // defined property that is not accessible
1609 raise_error("Cannot unset inaccessible property");
1613 if (!tryUnset
|| !invokeUnset(&ignored
, key
)) {
1614 if (UNLIKELY(!*key
->data())) {
1615 throw_invalid_property_name(StrNR(key
));
1620 tvRefcountedDecRef(&ignored
);
1623 void ObjectData::raiseObjToIntNotice(const char* clsName
) {
1624 raise_notice("Object of class %s could not be converted to int", clsName
);
1627 void ObjectData::raiseAbstractClassError(Class
* cls
) {
1628 Attr attrs
= cls
->attrs();
1629 raise_error("Cannot instantiate %s %s",
1630 (attrs
& AttrInterface
) ? "interface" :
1631 (attrs
& AttrTrait
) ? "trait" : "abstract class",
1632 cls
->preClass()->name()->data());
1635 void ObjectData::raiseUndefProp(const StringData
* key
) {
1636 raise_notice("Undefined property: %s::$%s",
1637 m_cls
->name()->data(), key
->data());
1640 void ObjectData::getProp(const Class
* klass
, bool pubOnly
,
1641 const PreClass::Prop
* prop
,
1643 std::vector
<bool>& inserted
) const {
1644 if (prop
->attrs() & AttrStatic
) {
1648 Slot propInd
= klass
->lookupDeclProp(prop
->name());
1649 assert(propInd
!= kInvalidSlot
);
1650 const TypedValue
* propVal
= &propVec()[propInd
];
1652 if ((!pubOnly
|| (prop
->attrs() & AttrPublic
)) &&
1653 propVal
->m_type
!= KindOfUninit
&&
1654 !inserted
[propInd
]) {
1655 inserted
[propInd
] = true;
1657 StrNR(klass
->declProperties()[propInd
].m_mangledName
).asString())
1658 .setWithRef(tvAsCVarRef(propVal
));
1662 void ObjectData::getProps(const Class
* klass
, bool pubOnly
,
1665 std::vector
<bool>& inserted
) const {
1666 PreClass::Prop
const* propVec
= pc
->properties();
1667 size_t count
= pc
->numProperties();
1668 for (size_t i
= 0; i
< count
; ++i
) {
1669 getProp(klass
, pubOnly
, &propVec
[i
], props
, inserted
);
1673 Variant
ObjectData::invokeSleep() {
1674 const Func
* method
= m_cls
->lookupMethod(s___sleep
.get());
1677 g_context
->invokeFuncFew(&tv
, method
, this);
1678 return tvAsVariant(&tv
);
1680 return uninit_null();
1684 Variant
ObjectData::invokeToDebugDisplay() {
1685 const Func
* method
= m_cls
->lookupMethod(s___toDebugDisplay
.get());
1688 g_context
->invokeFuncFew(&tv
, method
, this);
1689 return tvAsVariant(&tv
);
1691 return uninit_null();
1695 Variant
ObjectData::invokeWakeup() {
1696 const Func
* method
= m_cls
->lookupMethod(s___wakeup
.get());
1699 g_context
->invokeFuncFew(&tv
, method
, this);
1700 return tvAsVariant(&tv
);
1702 return uninit_null();
1706 String
ObjectData::invokeToString() {
1707 const Func
* method
= m_cls
->getToString();
1709 // If the object does not define a __toString() method, raise a
1710 // recoverable error
1711 raise_recoverable_error(
1712 "Object of class %s could not be converted to string",
1713 m_cls
->preClass()->name()->data()
1715 // If the user error handler decides to allow execution to continue,
1716 // we return the empty string.
1717 return empty_string
;
1720 g_context
->invokeFuncFew(&tv
, method
, this);
1721 if (!IS_STRING_TYPE(tv
.m_type
)) {
1722 // Discard the value returned by the __toString() method and raise
1723 // a recoverable error
1724 tvRefcountedDecRef(tv
);
1725 raise_recoverable_error(
1726 "Method %s::__toString() must return a string value",
1727 m_cls
->preClass()->name()->data());
1728 // If the user error handler decides to allow execution to continue,
1729 // we return the empty string.
1730 return empty_string
;
1732 String ret
= tv
.m_data
.pstr
;
1733 decRefStr(tv
.m_data
.pstr
);
1737 bool ObjectData::hasToString() {
1738 return (m_cls
->getToString() != nullptr);
1741 void ObjectData::cloneSet(ObjectData
* clone
) {
1742 auto const nProps
= m_cls
->numDeclProperties();
1743 auto const clonePropVec
= clone
->propVec();
1744 for (auto i
= Slot
{0}; i
< nProps
; i
++) {
1745 tvRefcountedDecRef(&clonePropVec
[i
]);
1746 tvDupFlattenVars(&propVec()[i
], &clonePropVec
[i
]);
1748 if (UNLIKELY(getAttribute(HasDynPropArr
))) {
1749 auto& dynProps
= dynPropArray();
1750 auto& cloneProps
= clone
->reserveProperties(dynProps
.size());
1752 ssize_t iter
= dynProps
.get()->iter_begin();
1753 while (iter
!= ArrayData::invalid_index
) {
1754 auto props
= static_cast<MixedArray
*>(dynProps
.get());
1756 props
->nvGetKey(&key
, iter
);
1758 const TypedValue
* val
;
1760 switch (key
.m_type
) {
1761 case HPHP::KindOfString
: {
1762 StringData
* str
= key
.m_data
.pstr
;
1763 val
= props
->nvGet(str
);
1764 ret
= cloneProps
.lvalAt(String(str
), AccessFlags::Key
).asTypedValue();
1768 case HPHP::KindOfInt64
: {
1769 int64_t num
= key
.m_data
.num
;
1770 val
= props
->nvGet(num
);
1771 ret
= cloneProps
.lvalAt(num
, AccessFlags::Key
).asTypedValue();
1775 always_assert(false);
1778 tvDupFlattenVars(val
, ret
, cloneProps
.get());
1779 iter
= dynProps
.get()->iter_advance(iter
);
1784 ObjectData
* ObjectData::cloneImpl() {
1786 Object o
= obj
= ObjectData::newInstance(m_cls
);
1788 if (UNLIKELY(getAttribute(HasNativeData
))) {
1789 Native::nativeDataInstanceCopy(obj
, this);
1792 auto const hasCloneBit
= getAttribute(HasClone
);
1794 if (!hasCloneBit
) return o
.detach();
1796 auto const method
= obj
->m_cls
->lookupMethod(s_clone
.get());
1798 // PHP classes that inherit from cpp builtins that have special clone
1799 // functionality *may* also define a __clone method, but it's totally
1800 // fine if a __clone doesn't exist.
1801 if (!method
&& getAttribute(IsCppBuiltin
)) return o
.detach();
1806 g_context
->invokeFuncFew(&tv
, method
, obj
);
1807 tvRefcountedDecRef(&tv
);
1812 RefData
* ObjectData::zGetProp(Class
* ctx
, const StringData
* key
,
1813 bool& visible
, bool& accessible
,
1815 auto tv
= getProp(ctx
, key
, visible
, accessible
, unset
);
1816 if (tv
->m_type
!= KindOfRef
) {
1819 return tv
->m_data
.pref
;
1822 bool ObjectData::hasDynProps() const {
1823 return getAttribute(HasDynPropArr
) && dynPropArray().size() != 0;
1826 const char* ObjectData::classname_cstr() const {
1827 return o_getClassName().data();
1830 void ObjectData::compileTimeAssertions() {
1831 static_assert(offsetof(ObjectData
, m_count
) == FAST_REFCOUNT_OFFSET
, "");