2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-2013 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/complex_types.h"
18 #include "hphp/runtime/base/type-conversions.h"
19 #include "hphp/runtime/base/builtin_functions.h"
20 #include "hphp/runtime/base/externals.h"
21 #include "hphp/runtime/base/variable-serializer.h"
22 #include "hphp/runtime/base/execution_context.h"
23 #include "hphp/runtime/base/runtime_error.h"
24 #include "hphp/runtime/base/memory_profile.h"
25 #include "hphp/util/lock.h"
26 #include "hphp/runtime/base/class_info.h"
27 #include "hphp/runtime/ext/ext_closure.h"
28 #include "hphp/runtime/ext/ext_continuation.h"
29 #include "hphp/runtime/ext/ext_collections.h"
30 #include "hphp/runtime/ext/ext_simplexml.h"
31 #include "hphp/runtime/vm/class.h"
32 #include "hphp/runtime/vm/member-operations.h"
33 #include "hphp/runtime/vm/object-allocator-sizes.h"
34 #include "hphp/runtime/vm/jit/translator-inline.h"
35 #include "hphp/system/systemlib.h"
38 ///////////////////////////////////////////////////////////////////////////////
41 // current maximum object identifier
42 IMPLEMENT_THREAD_LOCAL_NO_CHECK_HOT(int, ObjectData::os_max_id
);
44 int ObjectData::GetMaxId() {
45 return *(ObjectData::os_max_id
.getCheck());
49 s_offsetGet("offsetGet"),
51 s___callStatic("__callStatic"),
52 s_serialize("serialize");
54 ///////////////////////////////////////////////////////////////////////////////
55 // constructor/destructor
57 ObjectData::~ObjectData() {
58 if (ArrayData
* a
= o_properties
.get()) decRefArr(a
);
59 int& pmax
= *os_max_id
;
60 if (o_id
&& o_id
== pmax
) {
65 bool ObjectData::instanceof(const Class
* c
) const {
66 return m_cls
->classof(c
);
70 void ObjectData::destruct() {
71 if (UNLIKELY(RuntimeOption::EnableObjDestructCall
)) {
72 assert(RuntimeOption::EnableObjDestructCall
);
73 g_vmContext
->m_liveBCObjs
.erase(this);
77 if (auto meth
= m_cls
->getDtor()) {
78 // We don't run PHP destructors while we're unwinding for a C++ exception.
79 // We want to minimize the PHP code we run while propagating fatals, so
80 // we do this check here on a very common path, in the relativley slower
82 auto& faults
= g_vmContext
->m_faults
;
83 if (!faults
.empty()) {
84 if (faults
.back().m_faultType
== Fault::Type::CppException
) return;
86 // We raise the refcount around the call to __destruct(). This is to
87 // prevent the refcount from going to zero when the destructor returns.
88 CountableHelper
h(this);
92 // Call the destructor method
93 g_vmContext
->invokeFuncFew(&retval
, meth
, this);
95 // Swallow any exceptions that escape the __destruct method
96 handle_destructor_exception();
98 tvRefcountedDecRef(&retval
);
103 ///////////////////////////////////////////////////////////////////////////////
106 CStrRef
ObjectData::o_getClassName() const {
107 return *(const String
*)(&m_cls
->m_preClass
->nameRef());
111 bool ObjectData::o_instanceof(CStrRef s
) const {
112 Class
* cls
= Unit::lookupClass(s
.get());
113 if (!cls
) return false;
114 return m_cls
->classof(cls
);
117 bool ObjectData::o_toBooleanImpl() const noexcept
{
120 int64_t ObjectData::o_toInt64Impl() const noexcept
{
123 double ObjectData::o_toDoubleImpl() const noexcept
{
127 ///////////////////////////////////////////////////////////////////////////////
128 // instance methods and properties
130 const StaticString
s_getIterator("getIterator");
132 Object
ObjectData::iterableObject(bool& isIterable
,
133 bool mayImplementIterator
/* = true */) {
134 assert(mayImplementIterator
|| !implementsIterator());
135 if (mayImplementIterator
&& implementsIterator()) {
140 while (obj
->instanceof(SystemLib::s_IteratorAggregateClass
)) {
141 Variant iterator
= obj
->o_invoke_few_args(s_getIterator
, 0);
142 if (!iterator
.isObject()) break;
143 ObjectData
* o
= iterator
.getObjectData();
144 if (o
->instanceof(SystemLib::s_IteratorClass
)) {
148 obj
= iterator
.getObjectData();
154 ArrayIter
ObjectData::begin(CStrRef context
/* = null_string */) {
156 if (isCollection()) {
157 return ArrayIter(this);
159 Object iterable
= iterableObject(isIterable
);
161 return ArrayIter(iterable
, ArrayIter::transferOwner
);
163 return ArrayIter(iterable
->o_toIterArray(context
));
167 MutableArrayIter
ObjectData::begin(Variant
* key
, Variant
& val
,
168 CStrRef context
/* = null_string */) {
170 if (isCollection()) {
171 raise_error("Collection elements cannot be taken by reference");
173 Object iterable
= iterableObject(isIterable
);
175 throw FatalErrorException("An iterator cannot be used with "
176 "foreach by reference");
178 Array properties
= iterable
->o_toIterArray(context
, true);
179 ArrayData
* arr
= properties
.detach();
180 return MutableArrayIter(arr
, key
, val
);
183 void ObjectData::initProperties(int nProp
) {
184 if (!o_properties
.get()) initDynProps(nProp
);
187 Variant
* ObjectData::o_realProp(CStrRef propName
, int flags
,
188 CStrRef context
/* = null_string */) const {
190 * Returns a pointer to a place for a property value. This should never
191 * call the magic methods __get or __set. The flags argument describes the
192 * behavior in cases where the named property is nonexistent or
195 Class
* ctx
= nullptr;
196 if (!context
.empty()) {
197 ctx
= Unit::lookupClass(context
.get());
200 auto thiz
= const_cast<ObjectData
*>(this);
201 bool visible
, accessible
, unset
;
202 TypedValue
* ret
= thiz
->getProp(ctx
, propName
.get(), visible
,
205 // Property is not declared, and not dynamically created yet.
206 if (!(flags
& RealPropCreate
)) {
209 if (!o_properties
.get()) {
210 thiz
->initDynProps();
212 o_properties
.get()->lval(propName
, *(Variant
**)(&ret
), false);
213 return (Variant
*)ret
;
216 // ret is non-NULL if we reach here
218 if ((accessible
&& !unset
) ||
219 (flags
& (RealPropUnchecked
|RealPropExist
))) {
220 return (Variant
*)ret
;
226 inline Variant
ObjectData::o_getImpl(CStrRef propName
, int flags
,
227 bool error
/* = true */,
228 CStrRef context
/* = null_string */) {
229 if (UNLIKELY(!*propName
.data())) {
230 throw_invalid_property_name(propName
);
233 if (Variant
* t
= o_realProp(propName
, flags
, context
)) {
234 if (t
->isInitialized())
238 if (getAttribute(UseGet
)) {
240 tvWriteNull(&tvResult
);
241 invokeGet(&tvResult
, propName
.get());
242 return tvAsCVarRef(&tvResult
);
246 raise_notice("Undefined property: %s::$%s", o_getClassName().data(),
250 return uninit_null();
253 Variant
ObjectData::o_get(CStrRef propName
, bool error
/* = true */,
254 CStrRef context
/* = null_string */) {
255 return o_getImpl(propName
, 0, error
, context
);
259 inline ALWAYS_INLINE Variant
ObjectData::o_setImpl(CStrRef propName
, T v
,
261 if (UNLIKELY(!*propName
.data())) {
262 throw_invalid_property_name(propName
);
265 bool useSet
= getAttribute(UseSet
);
266 auto flags
= useSet
? 0 : RealPropCreate
;
268 if (Variant
* t
= o_realProp(propName
, flags
, context
)) {
269 if (!useSet
|| t
->isInitialized()) {
277 invokeSet(&ignored
, propName
.get(), (TypedValue
*)(&variant(v
)));
278 tvRefcountedDecRef(&ignored
);
285 Variant
ObjectData::o_set(CStrRef propName
, CVarRef v
) {
286 return o_setImpl
<CVarRef
>(propName
, v
, null_string
);
289 Variant
ObjectData::o_set(CStrRef propName
, RefResult v
) {
290 return o_setRef(propName
, variant(v
), null_string
);
293 Variant
ObjectData::o_setRef(CStrRef propName
, CVarRef v
) {
294 return o_setImpl
<RefResult
>(propName
, ref(v
), null_string
);
297 Variant
ObjectData::o_set(CStrRef propName
, CVarRef v
, CStrRef context
) {
298 return o_setImpl
<CVarRef
>(propName
, v
, context
);
301 Variant
ObjectData::o_set(CStrRef propName
, RefResult v
, CStrRef context
) {
302 return o_setRef(propName
, variant(v
), context
);
305 Variant
ObjectData::o_setRef(CStrRef propName
, CVarRef v
, CStrRef context
) {
306 return o_setImpl
<RefResult
>(propName
, ref(v
), context
);
310 void ObjectData::o_setArray(CArrRef properties
) {
311 for (ArrayIter
iter(properties
); iter
; ++iter
) {
312 String k
= iter
.first().toString();
313 Class
* ctx
= nullptr;
314 // If the key begins with a NUL, it's a private or protected property. Read
315 // the class name from between the two NUL bytes.
316 if (!k
.empty() && k
[0] == '\0') {
317 int subLen
= k
.find('\0', 1) + 1;
318 String cls
= k
.substr(1, subLen
- 2);
319 if (cls
.size() == 1 && cls
[0] == '*') {
324 ctx
= Unit::lookupClass(cls
.get());
327 k
= k
.substr(subLen
);
330 CVarRef secondRef
= iter
.secondRef();
331 setProp(ctx
, k
.get(), (TypedValue
*)(&secondRef
),
332 secondRef
.isReferenced());
336 void ObjectData::o_getArray(Array
& props
, bool pubOnly
/* = false */) const {
337 // The declared properties in the resultant array should be a permutation of
338 // propVec. They appear in the following order: go most-to-least-derived in
339 // the inheritance hierarchy, inserting properties in declaration order (with
340 // the wrinkle that overridden properties should appear only once, with the
341 // access level given to it in its most-derived declaration).
343 // This is needed to keep track of which elements have been inserted. This is
344 // the smoothest way to get overridden properties right.
345 std::vector
<bool> inserted(m_cls
->numDeclProperties(), false);
347 // Iterate over declared properties and insert {mangled name --> prop} pairs.
348 const Class
* cls
= m_cls
;
350 getProps(cls
, pubOnly
, cls
->m_preClass
.get(), props
, inserted
);
351 for (auto const& trait
: cls
->m_usedTraits
) {
352 getProps(cls
, pubOnly
, trait
->m_preClass
.get(), props
, inserted
);
357 // Iterate over dynamic properties and insert {name --> prop} pairs.
358 if (o_properties
.get() && !o_properties
.get()->empty()) {
359 for (ArrayIter
it(o_properties
.get()); !it
.end(); it
.next()) {
360 Variant key
= it
.first();
361 CVarRef value
= it
.secondRef();
362 props
.addLval(key
, true).setWithRef(value
);
367 Array
ObjectData::o_toArray() const {
368 Array
ret(ArrayData::Create());
369 o_getArray(ret
, false);
373 Array
ObjectData::o_toIterArray(CStrRef context
,
374 bool getRef
/* = false */) {
375 size_t size
= m_cls
->m_declPropNumAccessible
+
376 (o_properties
.get() ? o_properties
.get()->size() : 0);
377 auto retval
= ArrayData::Make(size
);
378 Class
* ctx
= nullptr;
379 if (!context
.empty()) {
380 ctx
= Unit::lookupClass(context
.get());
383 // Get all declared properties first, bottom-to-top in the inheritance
384 // hierarchy, in declaration order.
385 const Class
* klass
= m_cls
;
387 const PreClass::Prop
* props
= klass
->m_preClass
->properties();
388 const size_t numProps
= klass
->m_preClass
->numProperties();
390 for (size_t i
= 0; i
< numProps
; ++i
) {
391 auto key
= const_cast<StringData
*>(props
[i
].name());
392 bool visible
, accessible
, unset
;
393 TypedValue
* val
= getProp(ctx
, key
, visible
, accessible
, unset
);
394 if (accessible
&& val
->m_type
!= KindOfUninit
&& !unset
) {
396 if (val
->m_type
!= KindOfRef
) {
399 retval
->nvBind(key
, val
);
401 retval
->set(key
, tvAsCVarRef(val
), false);
405 klass
= klass
->parent();
408 // Now get dynamic properties.
409 if (o_properties
.get()) {
410 ssize_t iter
= o_properties
.get()->iter_begin();
411 while (iter
!= HphpArray::ElmIndEmpty
) {
413 static_cast<HphpArray
*>(o_properties
.get())->nvGetKey(&key
, iter
);
414 iter
= o_properties
.get()->iter_advance(iter
);
416 // You can get this if you cast an array to object. These properties must
417 // be dynamic because you can't declare a property with a non-string name.
418 if (UNLIKELY(!IS_STRING_TYPE(key
.m_type
))) {
419 assert(key
.m_type
== KindOfInt64
);
421 static_cast<HphpArray
*>(o_properties
.get())->nvGet(key
.m_data
.num
);
423 if (val
->m_type
!= KindOfRef
) {
426 retval
->nvBind(key
.m_data
.num
, val
);
428 retval
->set(key
.m_data
.num
, tvAsCVarRef(val
), false);
433 StringData
* strKey
= key
.m_data
.pstr
;
435 static_cast<HphpArray
*>(o_properties
.get())->nvGet(strKey
);
437 if (val
->m_type
!= KindOfRef
) {
440 retval
->nvBind(strKey
, val
);
442 retval
->set(strKey
, tvAsCVarRef(val
), false);
448 return Array(retval
);
451 static bool decode_invoke(CStrRef s
, ObjectData
* obj
, bool fatal
,
454 ctx
.cls
= obj
->getVMClass();
455 ctx
.invName
= nullptr;
457 ctx
.func
= ctx
.cls
->lookupMethod(s
.get());
459 if (ctx
.func
->attrs() & AttrStatic
) {
460 // If we found a method and its static, null out this_
464 // If this_ is non-null AND we could not find a method, try
465 // looking up __call in cls's method table
466 ctx
.func
= ctx
.cls
->lookupMethod(s___call
.get());
469 // Bail if we couldn't find the method or __call
470 o_invoke_failed(ctx
.cls
->name()->data(), s
.data(), fatal
);
473 // We found __call! Stash the original name into invName.
474 assert(!(ctx
.func
->attrs() & AttrStatic
));
475 ctx
.invName
= s
.get();
476 ctx
.invName
->incRefCount();
481 Variant
ObjectData::o_invoke(CStrRef s
, CArrRef params
,
482 bool fatal
/* = true */) {
484 if (!decode_invoke(s
, this, fatal
, ctx
)) {
485 return Variant(Variant::NullInit());
488 g_vmContext
->invokeFunc((TypedValue
*)&ret
, ctx
, params
);
492 Variant
ObjectData::o_invoke_few_args(CStrRef s
, int count
,
493 INVOKE_FEW_ARGS_IMPL_ARGS
) {
496 if (!decode_invoke(s
, this, true, ctx
)) {
497 return Variant(Variant::NullInit());
500 TypedValue args
[INVOKE_FEW_ARGS_COUNT
];
502 default: not_implemented();
503 #if INVOKE_FEW_ARGS_COUNT > 6
504 case 10: tvDup(*a9
.asTypedValue(), args
[9]);
505 case 9: tvDup(*a8
.asTypedValue(), args
[8]);
506 case 8: tvDup(*a7
.asTypedValue(), args
[7]);
507 case 7: tvDup(*a6
.asTypedValue(), args
[6]);
509 #if INVOKE_FEW_ARGS_COUNT > 3
510 case 6: tvDup(*a5
.asTypedValue(), args
[5]);
511 case 5: tvDup(*a4
.asTypedValue(), args
[4]);
512 case 4: tvDup(*a3
.asTypedValue(), args
[3]);
514 case 3: tvDup(*a2
.asTypedValue(), args
[2]);
515 case 2: tvDup(*a1
.asTypedValue(), args
[1]);
516 case 1: tvDup(*a0
.asTypedValue(), args
[0]);
521 g_vmContext
->invokeFuncFew(ret
.asTypedValue(), ctx
, count
, args
);
525 StaticString
s_zero("\0", 1);
527 void ObjectData::serialize(VariableSerializer
* serializer
) const {
528 if (UNLIKELY(serializer
->incNestedLevel((void*)this, true))) {
529 serializer
->writeOverflow((void*)this, true);
531 serializeImpl(serializer
);
533 serializer
->decNestedLevel((void*)this);
537 s_PHP_Incomplete_Class("__PHP_Incomplete_Class"),
538 s_PHP_Incomplete_Class_Name("__PHP_Incomplete_Class_Name"),
539 s_PHP_Unserializable_Class_Name("__PHP_Unserializable_Class_Name");
541 void ObjectData::serializeImpl(VariableSerializer
* serializer
) const {
542 bool handleSleep
= false;
544 if (LIKELY(serializer
->getType() == VariableSerializer::Type::Serialize
||
545 serializer
->getType() == VariableSerializer::Type::APCSerialize
)) {
546 if (instanceof(SystemLib::s_SerializableClass
)) {
547 assert(!isCollection());
549 const_cast<ObjectData
*>(this)->o_invoke_few_args(s_serialize
, 0);
550 if (ret
.isString()) {
551 serializer
->writeSerializableObject(o_getClassName(), ret
.toString());
552 } else if (ret
.isNull()) {
553 serializer
->writeNull();
555 raise_error("%s::serialize() must return a string or NULL",
556 o_getClassName().data());
560 // Only serialize CPP extension type instances which can actually
562 if ((builtinPropSize() > 0) && !getVMClass()->isCppSerializable()) {
563 Object placeholder
= ObjectData::newInstance(
564 SystemLib::s___PHP_Unserializable_ClassClass
);
565 placeholder
->o_set(s_PHP_Unserializable_Class_Name
, o_getClassName());
566 placeholder
->serialize(serializer
);
569 if (getAttribute(HasSleep
)) {
571 ret
= const_cast<ObjectData
*>(this)->t___sleep();
573 } else if (UNLIKELY(serializer
->getType() ==
574 VariableSerializer::Type::DebuggerSerialize
)) {
575 if (instanceof(SystemLib::s_SerializableClass
)) {
576 assert(!isCollection());
579 const_cast<ObjectData
*>(this)->o_invoke_few_args(s_serialize
, 0);
580 if (ret
.isString()) {
581 serializer
->writeSerializableObject(o_getClassName(), ret
.toString());
582 } else if (ret
.isNull()) {
583 serializer
->writeNull();
585 raise_warning("%s::serialize() must return a string or NULL",
586 o_getClassName().data());
587 serializer
->writeNull();
590 // serialize() throws exception
591 raise_warning("%s::serialize() throws exception",
592 o_getClassName().data());
593 serializer
->writeNull();
597 // Don't try to serialize a CPP extension class which doesn't
598 // support serialization. Just send the class name instead.
599 if ((builtinPropSize() > 0) && !getVMClass()->isCppSerializable()) {
600 serializer
->write(o_getClassName());
603 if (getAttribute(HasSleep
)) {
606 ret
= const_cast<ObjectData
*>(this)->t___sleep();
608 raise_warning("%s::sleep() throws exception", o_getClassName().data());
609 serializer
->writeNull();
614 if (UNLIKELY(handleSleep
)) {
615 assert(!isCollection());
617 auto thiz
= const_cast<ObjectData
*>(this);
618 Array wanted
= Array::Create();
619 Array props
= ret
.toArray();
620 for (ArrayIter
iter(props
); iter
; ++iter
) {
621 String name
= iter
.second().toString();
622 bool visible
, accessible
, unset
;
623 thiz
->getProp(m_cls
, name
.get(), visible
, accessible
, unset
);
624 if (accessible
&& !unset
) {
625 String propName
= name
;
626 Slot propInd
= m_cls
->getDeclPropIndex(m_cls
, name
.get(), accessible
);
627 if (accessible
&& propInd
!= kInvalidSlot
) {
628 if (m_cls
->m_declProperties
[propInd
].m_attrs
& AttrPrivate
) {
629 propName
= concat4(s_zero
, o_getClassName(), s_zero
, name
);
632 wanted
.set(propName
, const_cast<ObjectData
*>(this)->
633 o_getImpl(name
, RealPropUnchecked
, true, o_getClassName()));
635 raise_warning("\"%s\" returned as member variable from "
636 "__sleep() but does not exist", name
.data());
637 wanted
.set(name
, uninit_null());
640 serializer
->setObjectInfo(o_getClassName(), o_getId(), 'O');
641 wanted
.serialize(serializer
, true);
643 raise_warning("serialize(): __sleep should return an array only "
644 "containing the names of instance-variables to "
646 uninit_null().serialize(serializer
);
649 if (isCollection()) {
650 collectionSerialize(const_cast<ObjectData
*>(this), serializer
);
652 CStrRef className
= o_getClassName();
653 Array properties
= o_toArray();
654 if (serializer
->getType() != VariableSerializer::Type::VarDump
&&
655 className
== s_PHP_Incomplete_Class
) {
656 Variant
* cname
= o_realProp(s_PHP_Incomplete_Class_Name
, 0);
657 if (cname
&& cname
->isString()) {
658 serializer
->setObjectInfo(cname
->toCStrRef(), o_getId(), 'O');
659 properties
.remove(s_PHP_Incomplete_Class_Name
, true);
660 properties
.serialize(serializer
, true);
664 serializer
->setObjectInfo(className
, o_getId(), 'O');
665 properties
.serialize(serializer
, true);
670 bool ObjectData::hasInternalReference(PointerSet
& vars
,
671 bool ds
/* = false */) const {
672 if (isCollection()) {
675 return o_toArray().get()->hasInternalReference(vars
, ds
);
678 void ObjectData::dump() const {
682 ObjectData
* ObjectData::clone() {
686 Variant
ObjectData::offsetGet(Variant key
) {
687 assert(instanceof(SystemLib::s_ArrayAccessClass
));
688 const Func
* method
= m_cls
->lookupMethod(s_offsetGet
.get());
691 return uninit_null();
695 tvDup(*key
.asTypedValue(), args
[0]);
696 g_vmContext
->invokeFuncFew(v
.asTypedValue(), method
,
697 this, nullptr, 1, args
);
701 ///////////////////////////////////////////////////////////////////////////////
707 static int run(int size
) {
708 if (size
<= ObjectSizeTable
<Idx
>::value
) {
711 return FindIndex
<Idx
+ 1>::run(size
);
716 struct FindIndex
<NumObjectSizeClasses
> {
717 static int run(int) {
724 static int run(int idx
) {
726 return ObjectSizeTable
<Idx
>::value
;
728 return FindSize
<Idx
+ 1>::run(idx
);
733 struct FindSize
<NumObjectSizeClasses
> {
734 static int run(int) {
741 int object_alloc_size_to_index(size_t size
) {
742 return FindIndex
<0>::run(size
);
745 // This returns the maximum size for the size class
746 size_t object_alloc_index_to_size(int idx
) {
747 return FindSize
<0>::run(idx
);
750 ///////////////////////////////////////////////////////////////////////////////
753 s___get(LITSTR_INIT("__get")),
754 s___set(LITSTR_INIT("__set")),
755 s___isset(LITSTR_INIT("__isset")),
756 s___unset(LITSTR_INIT("__unset")),
757 s___init__(LITSTR_INIT("__init__")),
758 s___sleep(LITSTR_INIT("__sleep")),
759 s___wakeup(LITSTR_INIT("__wakeup"));
761 TRACE_SET_MOD(runtime
);
763 int ObjectData::ObjAllocatorSizeClassCount
= InitializeAllocators();
765 void deepInitHelper(TypedValue
* propVec
, const TypedValueAux
* propData
,
768 auto* src
= propData
;
769 for (; src
!= propData
+ nProps
; ++src
, ++dst
) {
771 // m_aux.u_deepInit is true for properties that need "deep" initialization
772 if (src
->deepInit()) {
774 collectionDeepCopyTV(dst
);
779 TypedValue
* ObjectData::propVec() {
780 uintptr_t ret
= (uintptr_t)this + sizeof(ObjectData
) + builtinPropSize();
781 // TODO(#1432007): some builtins still do not have TypedValue-aligned sizes.
782 assert(ret
% sizeof(TypedValue
) == builtinPropSize() % sizeof(TypedValue
));
783 return (TypedValue
*) ret
;
786 const TypedValue
* ObjectData::propVec() const {
787 return const_cast<ObjectData
*>(this)->propVec();
790 ObjectData
* ObjectData::callCustomInstanceInit() {
791 const Func
* init
= m_cls
->lookupMethod(s___init__
.get());
792 if (init
!= nullptr) {
794 // We need to incRef/decRef here because we're still a new (m_count
795 // == 0) object and invokeFunc is going to expect us to have a
796 // reasonable refcount.
799 g_vmContext
->invokeFuncFew(&tv
, init
, this);
801 assert(!IS_REFCOUNTED_TYPE(tv
.m_type
));
803 this->setNoDestruct();
812 ObjectData
* ObjectData::newInstanceRaw(Class
* cls
, int idx
) {
813 ObjectData
* obj
= (ObjectData
*)ALLOCOBJIDX(idx
);
814 new (obj
) ObjectData(cls
, NoInit::noinit
);
818 void ObjectData::operator delete(void* p
) {
819 ObjectData
* this_
= (ObjectData
*)p
;
820 Class
* cls
= this_
->getVMClass();
821 size_t nProps
= cls
->numDeclProperties();
822 // cppext classes have their own implementation of delete
823 assert(this_
->builtinPropSize() == 0);
824 TypedValue
* propVec
= (TypedValue
*)((uintptr_t)this_
+ sizeof(ObjectData
));
825 for (unsigned i
= 0; i
< nProps
; ++i
) {
826 TypedValue
* prop
= &propVec
[i
];
827 tvRefcountedDecRef(prop
);
829 DELETEOBJSZ(sizeForNProps(nProps
))(this_
);
832 void ObjectData::invokeUserMethod(TypedValue
* retval
, const Func
* method
,
834 g_vmContext
->invokeFunc(retval
, method
, params
, this);
837 Object
ObjectData::FromArray(ArrayData
* properties
) {
838 ObjectData
* retval
= ObjectData::newInstance(SystemLib::s_stdclassClass
);
839 retval
->initDynProps();
840 HphpArray
* props
= static_cast<HphpArray
*>(retval
->o_properties
.get());
841 for (ssize_t pos
= properties
->iter_begin(); pos
!= ArrayData::invalid_index
;
842 pos
= properties
->iter_advance(pos
)) {
843 TypedValue
* value
= properties
->nvGetValueRef(pos
);
845 properties
->nvGetKey(&key
, pos
);
846 if (key
.m_type
== KindOfInt64
) {
847 props
->set(key
.m_data
.num
, tvAsCVarRef(value
), false);
849 assert(IS_STRING_TYPE(key
.m_type
));
850 StringData
* strKey
= key
.m_data
.pstr
;
851 props
->set(strKey
, tvAsCVarRef(value
), false);
858 void ObjectData::initDynProps(int numDynamic
/* = 0 */) {
859 // Create o_properties with room for numDynamic
860 o_properties
.asArray() = ArrayData::Make(numDynamic
);
863 Slot
ObjectData::declPropInd(TypedValue
* prop
) const {
864 // Do an address range check to determine whether prop physically resides
866 const TypedValue
* pv
= propVec();
867 if (prop
>= pv
&& prop
< &pv
[m_cls
->numDeclProperties()]) {
874 TypedValue
* ObjectData::getProp(Class
* ctx
, const StringData
* key
,
875 bool& visible
, bool& accessible
,
877 TypedValue
* prop
= nullptr;
879 Slot propInd
= m_cls
->getDeclPropIndex(ctx
, key
, accessible
);
880 visible
= (propInd
!= kInvalidSlot
);
881 if (propInd
!= kInvalidSlot
) {
882 // We found a visible property, but it might not be accessible.
883 // No need to check if there is a dynamic property with this name.
884 prop
= &propVec()[propInd
];
885 if (prop
->m_type
== KindOfUninit
) {
889 assert(!visible
&& !accessible
);
890 // We could not find a visible declared property. We need to check
891 // for a dynamic property with this name.
892 if (o_properties
.get()) {
893 prop
= static_cast<HphpArray
*>(o_properties
.get())->nvGet(key
);
895 // o_properties.get()->nvGet() returned a non-declared property,
896 // we know that it is visible and accessible (since all
897 // dynamic properties are), and we know it is not unset
898 // (since unset dynamic properties don't appear in o_properties.get()).
907 void ObjectData::invokeSet(TypedValue
* retval
, const StringData
* key
,
909 AttributeClearer
a(UseSet
, this);
910 const Func
* meth
= m_cls
->lookupMethod(s___set
.get());
912 invokeUserMethod(retval
, meth
,
913 CREATE_VECTOR2(CStrRef(key
), tvAsVariant(val
)));
916 #define MAGIC_PROP_BODY(name, attr) \
917 AttributeClearer a((attr), this); \
918 const Func* meth = m_cls->lookupMethod(name); \
920 invokeUserMethod(retval, meth, CREATE_VECTOR1(CStrRef(key))); \
922 void ObjectData::invokeGet(TypedValue* retval, const StringData* key) {
923 MAGIC_PROP_BODY(s___get
.get(), UseGet
);
926 void ObjectData::invokeIsset(TypedValue
* retval
, const StringData
* key
) {
927 MAGIC_PROP_BODY(s___isset
.get(), UseIsset
);
930 void ObjectData::invokeUnset(TypedValue
* retval
, const StringData
* key
) {
931 MAGIC_PROP_BODY(s___unset
.get(), UseUnset
);
934 void ObjectData::invokeGetProp(TypedValue
*& retval
, TypedValue
& tvRef
,
935 const StringData
* key
) {
936 invokeGet(&tvRef
, key
);
940 template <bool warn
, bool define
>
941 void ObjectData::propImpl(TypedValue
*& retval
, TypedValue
& tvRef
,
943 const StringData
* key
) {
944 bool visible
, accessible
, unset
;
945 TypedValue
* propVal
= getProp(ctx
, key
, visible
, accessible
, unset
);
950 if (getAttribute(UseGet
)) {
951 invokeGetProp(retval
, tvRef
, key
);
959 retval
= (TypedValue
*)&init_null_variant
;
966 if (getAttribute(UseGet
)) {
967 invokeGetProp(retval
, tvRef
, key
);
969 // No need to check hasProp since visible is true
970 // Visibility is either protected or private since accessible is false
971 Slot propInd
= m_cls
->lookupDeclProp(key
);
972 bool priv
= m_cls
->declProperties()[propInd
].m_attrs
& AttrPrivate
;
974 raise_error("Cannot access %s property %s::$%s",
975 priv
? "private" : "protected",
976 m_cls
->m_preClass
->name()->data(),
980 } else if (UNLIKELY(!*key
->data())) {
981 throw_invalid_property_name(StrNR(key
));
983 if (getAttribute(UseGet
)) {
984 invokeGetProp(retval
, tvRef
, key
);
990 if (o_properties
.get() == nullptr) {
993 o_properties
.get()->lval(*(const String
*)&key
,
994 *(Variant
**)(&retval
), false);
996 retval
= (TypedValue
*)&init_null_variant
;
1002 void ObjectData::prop(TypedValue
*& retval
, TypedValue
& tvRef
,
1003 Class
* ctx
, const StringData
* key
) {
1004 propImpl
<false, false>(retval
, tvRef
, ctx
, key
);
1007 void ObjectData::propD(TypedValue
*& retval
, TypedValue
& tvRef
,
1008 Class
* ctx
, const StringData
* key
) {
1009 propImpl
<false, true>(retval
, tvRef
, ctx
, key
);
1012 void ObjectData::propW(TypedValue
*& retval
, TypedValue
& tvRef
,
1013 Class
* ctx
, const StringData
* key
) {
1014 propImpl
<true, false>(retval
, tvRef
, ctx
, key
);
1017 void ObjectData::propWD(TypedValue
*& retval
, TypedValue
& tvRef
,
1018 Class
* ctx
, const StringData
* key
) {
1019 propImpl
<true, true>(retval
, tvRef
, ctx
, key
);
1022 bool ObjectData::propIsset(Class
* ctx
, const StringData
* key
) {
1023 bool visible
, accessible
, unset
;
1024 TypedValue
* propVal
= getProp(ctx
, key
, visible
, accessible
, unset
);
1025 if (visible
&& accessible
&& !unset
) {
1026 return isset(tvAsCVarRef(propVal
));
1028 if (!getAttribute(UseIsset
)) {
1033 invokeIsset(&tv
, key
);
1034 tvCastToBooleanInPlace(&tv
);
1035 return tv
.m_data
.num
;
1038 bool ObjectData::propEmpty(Class
* ctx
, const StringData
* key
) {
1039 bool visible
, accessible
, unset
;
1040 TypedValue
* propVal
= getProp(ctx
, key
, visible
, accessible
, unset
);
1041 if (visible
&& accessible
&& !unset
) {
1042 return empty(tvAsCVarRef(propVal
));
1044 if (!getAttribute(UseIsset
)) {
1049 invokeIsset(&tv
, key
);
1050 tvCastToBooleanInPlace(&tv
);
1051 if (!tv
.m_data
.num
) {
1054 if (getAttribute(UseGet
)) {
1055 invokeGet(&tv
, key
);
1056 bool emptyResult
= empty(tvAsCVarRef(&tv
));
1057 tvRefcountedDecRef(&tv
);
1063 TypedValue
* ObjectData::setProp(Class
* ctx
, const StringData
* key
,
1065 bool bindingAssignment
/* = false */) {
1066 bool visible
, accessible
, unset
;
1067 TypedValue
* propVal
= getProp(ctx
, key
, visible
, accessible
, unset
);
1068 if (visible
&& accessible
) {
1070 if (unset
&& getAttribute(UseSet
)) {
1072 invokeSet(&ignored
, key
, val
);
1073 tvRefcountedDecRef(&ignored
);
1075 if (UNLIKELY(bindingAssignment
)) {
1076 tvBind(val
, propVal
);
1078 tvSet(*val
, *propVal
);
1081 // Return a pointer to the property if it's a declared property
1082 return declPropInd(propVal
) != kInvalidSlot
? propVal
: nullptr;
1084 assert(!accessible
);
1087 if (!getAttribute(UseSet
)) {
1088 raise_error("Cannot access protected property");
1090 // Fall through to the last case below
1091 } else if (UNLIKELY(!*key
->data())) {
1092 throw_invalid_property_name(StrNR(key
));
1093 } else if (!getAttribute(UseSet
)) {
1094 if (o_properties
.get() == nullptr) {
1097 // when seting a dynamic property, do not write
1098 // directly to the TypedValue in the HphpArray, since
1099 // its m_aux field is used to store the string hash of
1100 // the property name. Instead, call the appropriate
1101 // setters (set() or setRef()).
1102 if (UNLIKELY(bindingAssignment
)) {
1103 o_properties
.get()->setRef(const_cast<StringData
*>(key
),
1104 tvAsCVarRef(val
), false);
1106 o_properties
.get()->set(const_cast<StringData
*>(key
),
1107 tvAsCVarRef(val
), false);
1111 assert(!accessible
);
1112 assert(getAttribute(UseSet
));
1114 invokeSet(&ignored
, key
, val
);
1115 tvRefcountedDecRef(&ignored
);
1119 TypedValue
* ObjectData::setOpProp(TypedValue
& tvRef
, Class
* ctx
,
1120 unsigned char op
, const StringData
* key
,
1122 bool visible
, accessible
, unset
;
1123 TypedValue
* propVal
= getProp(ctx
, key
, visible
, accessible
, unset
);
1124 if (visible
&& accessible
) {
1126 if (unset
&& getAttribute(UseGet
)) {
1127 TypedValue tvResult
;
1128 tvWriteUninit(&tvResult
);
1129 invokeGet(&tvResult
, key
);
1130 SETOP_BODY(&tvResult
, op
, val
);
1131 if (getAttribute(UseSet
)) {
1132 assert(tvRef
.m_type
== KindOfUninit
);
1133 memcpy(&tvRef
, &tvResult
, sizeof(TypedValue
));
1135 invokeSet(&ignored
, key
, &tvRef
);
1136 tvRefcountedDecRef(&ignored
);
1139 memcpy(propVal
, &tvResult
, sizeof(TypedValue
));
1142 SETOP_BODY(propVal
, op
, val
);
1146 assert(!accessible
);
1149 if (!getAttribute(UseGet
) || !getAttribute(UseSet
)) {
1150 raise_error("Cannot access protected property");
1152 // Fall through to the last case below
1153 } else if (UNLIKELY(!*key
->data())) {
1154 throw_invalid_property_name(StrNR(key
));
1155 } else if (!getAttribute(UseGet
)) {
1156 if (o_properties
.get() == nullptr) {
1159 o_properties
.get()->lval(*(const String
*)&key
,
1160 *(Variant
**)(&propVal
), false);
1161 // don't write propVal->m_aux because it holds data
1162 // owned by the HphpArray
1163 propVal
->m_type
= KindOfNull
;
1164 SETOP_BODY(propVal
, op
, val
);
1166 } else if (!getAttribute(UseSet
)) {
1167 TypedValue tvResult
;
1168 tvWriteUninit(&tvResult
);
1169 invokeGet(&tvResult
, key
);
1170 SETOP_BODY(&tvResult
, op
, val
);
1171 if (o_properties
.get() == nullptr) {
1174 o_properties
.get()->lval(*(const String
*)&key
,
1175 *(Variant
**)(&propVal
), false);
1176 // don't write propVal->m_aux because it holds data
1177 // owned by the HphpArray
1178 propVal
->m_data
.num
= tvResult
.m_data
.num
;
1179 propVal
->m_type
= tvResult
.m_type
;
1182 assert(!accessible
);
1183 assert(getAttribute(UseGet
) && getAttribute(UseSet
));
1184 invokeGet(&tvRef
, key
);
1185 SETOP_BODY(&tvRef
, op
, val
);
1187 invokeSet(&ignored
, key
, &tvRef
);
1188 tvRefcountedDecRef(&ignored
);
1193 template <bool setResult
>
1194 void ObjectData::incDecPropImpl(TypedValue
& tvRef
, Class
* ctx
,
1195 unsigned char op
, const StringData
* key
,
1197 bool visible
, accessible
, unset
;
1198 TypedValue
* propVal
= getProp(ctx
, key
, visible
, accessible
, unset
);
1199 if (visible
&& accessible
) {
1201 if (unset
&& getAttribute(UseGet
)) {
1202 TypedValue tvResult
;
1203 tvWriteUninit(&tvResult
);
1204 invokeGet(&tvResult
, key
);
1205 IncDecBody
<setResult
>(op
, &tvResult
, &dest
);
1206 if (getAttribute(UseSet
)) {
1208 invokeSet(&ignored
, key
, &tvResult
);
1209 tvRefcountedDecRef(&ignored
);
1210 propVal
= &tvResult
;
1212 memcpy((void*)propVal
, (void*)&tvResult
, sizeof(TypedValue
));
1215 IncDecBody
<setResult
>(op
, propVal
, &dest
);
1219 assert(!accessible
);
1222 if (!getAttribute(UseGet
) || !getAttribute(UseSet
)) {
1223 raise_error("Cannot access protected property");
1225 // Fall through to the last case below
1226 } else if (UNLIKELY(!*key
->data())) {
1227 throw_invalid_property_name(StrNR(key
));
1228 } else if (!getAttribute(UseGet
)) {
1229 if (o_properties
.get() == nullptr) {
1232 o_properties
.get()->lval(*(const String
*)&key
,
1233 *(Variant
**)(&propVal
), false);
1234 // don't write propVal->m_aux because it holds data
1235 // owned by the HphpArray
1236 propVal
->m_type
= KindOfNull
;
1237 IncDecBody
<setResult
>(op
, propVal
, &dest
);
1239 } else if (!getAttribute(UseSet
)) {
1240 TypedValue tvResult
;
1241 tvWriteUninit(&tvResult
);
1242 invokeGet(&tvResult
, key
);
1243 IncDecBody
<setResult
>(op
, &tvResult
, &dest
);
1244 if (o_properties
.get() == nullptr) {
1247 o_properties
.get()->lval(*(const String
*)&key
,
1248 *(Variant
**)(&propVal
), false);
1249 // don't write propVal->m_aux because it holds data
1250 // owned by the HphpArray
1251 propVal
->m_data
.num
= tvResult
.m_data
.num
;
1252 propVal
->m_type
= tvResult
.m_type
;
1255 assert(!accessible
);
1256 assert(getAttribute(UseGet
) && getAttribute(UseSet
));
1257 invokeGet(&tvRef
, key
);
1258 IncDecBody
<setResult
>(op
, &tvRef
, &dest
);
1260 invokeSet(&ignored
, key
, &tvRef
);
1261 tvRefcountedDecRef(&ignored
);
1266 void ObjectData::incDecProp
<false>(TypedValue
& tvRef
, Class
* ctx
,
1267 unsigned char op
, const StringData
* key
,
1269 incDecPropImpl
<false>(tvRef
, ctx
, op
, key
, dest
);
1273 void ObjectData::incDecProp
<true>(TypedValue
& tvRef
, Class
* ctx
,
1274 unsigned char op
, const StringData
* key
,
1276 incDecPropImpl
<true>(tvRef
, ctx
, op
, key
, dest
);
1279 void ObjectData::unsetProp(Class
* ctx
, const StringData
* key
) {
1280 bool visible
, accessible
, unset
;
1281 TypedValue
* propVal
= getProp(ctx
, key
, visible
, accessible
, unset
);
1282 if (visible
&& accessible
) {
1283 Slot propInd
= declPropInd(propVal
);
1284 if (propInd
!= kInvalidSlot
) {
1285 // Declared property.
1286 tvSetIgnoreRef(*null_variant
.asTypedValue(), *propVal
);
1288 // Dynamic property.
1289 assert(o_properties
.get() != nullptr);
1290 o_properties
.get()->remove(CStrRef(key
), false);
1292 } else if (UNLIKELY(!*key
->data())) {
1293 throw_invalid_property_name(StrNR(key
));
1295 assert(!accessible
);
1296 if (getAttribute(UseUnset
)) {
1298 invokeUnset(&ignored
, key
);
1299 tvRefcountedDecRef(&ignored
);
1300 } else if (visible
) {
1301 raise_error("Cannot unset inaccessible property");
1306 void ObjectData::raiseObjToIntNotice(const char* clsName
) {
1307 raise_notice("Object of class %s could not be converted to int", clsName
);
1310 void ObjectData::raiseAbstractClassError(Class
* cls
) {
1311 Attr attrs
= cls
->attrs();
1312 raise_error("Cannot instantiate %s %s",
1313 (attrs
& AttrInterface
) ? "interface" :
1314 (attrs
& AttrTrait
) ? "trait" : "abstract class",
1315 cls
->preClass()->name()->data());
1318 void ObjectData::raiseUndefProp(const StringData
* key
) {
1319 raise_notice("Undefined property: %s::$%s",
1320 m_cls
->name()->data(), key
->data());
1323 void ObjectData::getProp(const Class
* klass
, bool pubOnly
,
1324 const PreClass::Prop
* prop
,
1326 std::vector
<bool>& inserted
) const {
1327 if (prop
->attrs() & AttrStatic
) {
1331 Slot propInd
= klass
->lookupDeclProp(prop
->name());
1332 assert(propInd
!= kInvalidSlot
);
1333 const TypedValue
* propVal
= &propVec()[propInd
];
1335 if ((!pubOnly
|| (prop
->attrs() & AttrPublic
)) &&
1336 propVal
->m_type
!= KindOfUninit
&&
1337 !inserted
[propInd
]) {
1338 inserted
[propInd
] = true;
1339 props
.lvalAt(CStrRef(klass
->declProperties()[propInd
].m_mangledName
))
1340 .setWithRef(tvAsCVarRef(propVal
));
1344 void ObjectData::getProps(const Class
* klass
, bool pubOnly
,
1347 std::vector
<bool>& inserted
) const {
1348 PreClass::Prop
const* propVec
= pc
->properties();
1349 size_t count
= pc
->numProperties();
1350 for (size_t i
= 0; i
< count
; ++i
) {
1351 getProp(klass
, pubOnly
, &propVec
[i
], props
, inserted
);
1355 Variant
ObjectData::t___sleep() {
1356 const Func
* method
= m_cls
->lookupMethod(s___sleep
.get());
1359 g_vmContext
->invokeFuncFew(&tv
, method
, this);
1360 return tvAsVariant(&tv
);
1362 return uninit_null();
1366 Variant
ObjectData::t___wakeup() {
1367 const Func
* method
= m_cls
->lookupMethod(s___wakeup
.get());
1370 g_vmContext
->invokeFuncFew(&tv
, method
, this);
1371 return tvAsVariant(&tv
);
1373 return uninit_null();
1377 String
ObjectData::invokeToString() {
1378 const Func
* method
= m_cls
->getToString();
1381 g_vmContext
->invokeFuncFew(&tv
, method
, this);
1382 if (!IS_STRING_TYPE(tv
.m_type
)) {
1383 void (*notify_user
)(const char*, ...) = &raise_error
;
1385 tvCastToStringInPlace(&tv
);
1386 notify_user
= &raise_warning
;
1388 notify_user("Method %s::__toString() must return a string value",
1389 m_cls
->m_preClass
->name()->data());
1391 return tv
.m_data
.pstr
;
1393 raise_recoverable_error(
1394 "Object of class %s could not be converted to string",
1395 m_cls
->m_preClass
->name()->data()
1397 // If the user error handler decides to allow execution to continue,
1398 // we return the empty string.
1399 return empty_string
;
1402 bool ObjectData::hasToString() {
1403 return (m_cls
->getToString() != nullptr);
1406 void ObjectData::cloneSet(ObjectData
* clone
) {
1407 Slot nProps
= m_cls
->numDeclProperties();
1408 TypedValue
* clonePropVec
= (TypedValue
*)((uintptr_t)clone
+
1409 sizeof(ObjectData
) + builtinPropSize());
1410 for (Slot i
= 0; i
< nProps
; i
++) {
1411 tvRefcountedDecRef(&clonePropVec
[i
]);
1412 tvDupFlattenVars(&propVec()[i
], &clonePropVec
[i
], nullptr);
1414 if (o_properties
.get()) {
1415 clone
->initDynProps();
1416 ssize_t iter
= o_properties
.get()->iter_begin();
1417 while (iter
!= HphpArray::ElmIndEmpty
) {
1418 auto props
= static_cast<HphpArray
*>(o_properties
.get());
1420 props
->nvGetKey(&key
, iter
);
1421 assert(tvIsString(&key
));
1422 StringData
* strKey
= key
.m_data
.pstr
;
1423 TypedValue
* val
= props
->nvGet(strKey
);
1425 auto cloneProps
= clone
->o_properties
.get();
1426 cloneProps
->lval(strKey
, *(Variant
**)&retval
, false);
1427 tvDupFlattenVars(val
, retval
, cloneProps
);
1428 iter
= o_properties
.get()->iter_advance(iter
);
1434 ObjectData
* ObjectData::cloneImpl() {
1436 Object o
= obj
= ObjectData::newInstance(m_cls
);
1438 static StringData
* sd__clone
= StringData::GetStaticString("__clone");
1439 const Func
* method
= obj
->m_cls
->lookupMethod(sd__clone
);
1443 g_vmContext
->invokeFuncFew(&tv
, method
, obj
);
1444 tvRefcountedDecRef(&tv
);