Remove tracer (debugger "heaptrace" command)
[hiphop-php.git] / hphp / runtime / base / object-data.cpp
blob2a50b1d8177a7054df3f7b5c49562e0a3dbd8001
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
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"
49 #include <vector>
51 namespace HPHP {
53 //////////////////////////////////////////////////////////////////////
55 // current maximum object identifier
56 __thread int ObjectData::os_max_id;
58 TRACE_SET_MOD(runtime);
60 const StaticString
61 s_offsetGet("offsetGet"),
62 s_call("__call"),
63 s_serialize("serialize"),
64 s_clone("__clone");
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);
85 if (!noDestruct()) {
86 setNoDestruct();
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
91 // case.
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();
100 TypedValue retval;
101 tvWriteNull(&retval);
102 try {
103 // Call the destructor method
104 g_context->invokeFuncFew(&retval, meth, this);
105 } catch (...) {
106 // Swallow any exceptions that escape the __destruct method
107 handle_destructor_exception();
109 tvRefcountedDecRef(&retval);
110 return c == this->getCount();
113 return true;
116 ///////////////////////////////////////////////////////////////////////////////
117 // class info
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
131 // to be changed.
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);
145 } else {
146 always_assert(false);
148 } else if (instanceof(c_SimpleXMLElement::classof())) {
149 // SimpleXMLElement is the only non-collection class that has custom
150 // bool casting.
151 return c_SimpleXMLElement::ToBool(this);
153 always_assert(false);
154 return 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
160 // add cases.
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()) {
182 isIterable = true;
183 return Object(this);
185 Object obj(this);
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)) {
191 isIterable = true;
192 return o;
194 obj = o;
196 isIterable = false;
197 return obj;
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);
213 return arr;
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
222 * inaccessible.
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);
231 if (!ret) {
232 // Property is not declared, and not dynamically created yet.
233 if (!(flags & RealPropCreate)) {
234 return nullptr;
236 return &reserveProperties().lvalAt(propName, AccessFlags::Key);
239 // ret is non-NULL if we reach here
240 assert(visible);
241 if ((accessible && !unset) ||
242 (flags & (RealPropUnchecked|RealPropExist))) {
243 return reinterpret_cast<Variant*>(ret);
244 } else {
245 return nullptr;
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())
258 return *t;
261 if (getAttribute(UseGet)) {
262 TypedValue tvResult;
263 tvWriteNull(&tvResult);
264 if (invokeGet(&tvResult, propName.get())) {
265 return tvAsCVarRef(&tvResult);
269 if (error) {
270 raise_notice("Undefined property: %s::$%s", o_getClassName().data(),
271 propName.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);
282 template <class T>
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()) {
294 *t = v;
295 return variant(v);
299 TypedValue ignored;
300 if (useSet &&
301 invokeSet(&ignored, propName.get(), (TypedValue*)(&variant(v)))) {
302 tvRefcountedDecRef(&ignored);
305 return variant(v);
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
325 // apc-object.
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] == '*') {
330 // Protected.
331 ctx = m_cls;
332 } else {
333 // Private.
334 ctx = Unit::lookupClass(cls.get());
335 if (!ctx) continue;
337 k = k.substr(subLen);
340 const Variant& secondRef = iter.secondRef();
341 setProp(ctx,
342 k.get(),
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;
365 do {
366 getProps(cls, pubOnly, cls->preClass(), props, inserted);
367 for (auto const& traitCls : cls->usedTraitClasses()) {
368 getProps(cls, pubOnly, traitCls->preClass(), props, inserted);
370 cls = cls->parent();
371 } while (cls);
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);
413 } else {
414 Array ret(ArrayData::Create());
415 o_getArray(ret, pubOnly);
416 return ret;
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;
438 while (klass) {
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) {
447 if (getRef) {
448 if (val->m_type != KindOfRef) {
449 tvBox(val);
451 retArray.setRef(StrNR(key), tvAsVariant(val), true /* isKey */);
452 } else {
453 retArray.set(StrNR(key), tvAsCVarRef(val), true /* isKey */);
457 klass = klass->parent();
460 // Now get dynamic properties.
461 if (dynProps) {
462 ssize_t iter = dynProps->get()->iter_begin();
463 while (iter != ArrayData::invalid_index) {
464 TypedValue key;
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);
473 if (getRef) {
474 auto& lval = dynProps->lvalAt(key.m_data.num);
475 retArray.setRef(key.m_data.num, lval);
476 } else {
477 auto const val = dynProps->get()->nvGet(key.m_data.num);
478 retArray.set(key.m_data.num, tvAsCVarRef(val));
480 continue;
483 auto const strKey = key.m_data.pstr;
484 if (getRef) {
485 auto& lval = dynProps->lvalAt(StrNR(strKey), AccessFlags::Key);
486 retArray.setRef(StrNR(strKey), lval, true /* isKey */);
487 } else {
488 auto const val = dynProps->get()->nvGet(strKey);
489 retArray.set(StrNR(strKey), tvAsCVarRef(val), true /* isKey */);
491 decRefStr(strKey);
495 return retArray;
498 static bool decode_invoke(const String& s, ObjectData* obj, bool fatal,
499 CallCtx& ctx) {
500 ctx.this_ = obj;
501 ctx.cls = obj->getVMClass();
502 ctx.invName = nullptr;
504 ctx.func = ctx.cls->lookupMethod(s.get());
505 if (ctx.func) {
506 if (ctx.func->attrs() & AttrStatic) {
507 // If we found a method and its static, null out this_
508 ctx.this_ = nullptr;
510 } else {
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());
515 if (!ctx.func) {
516 // Bail if we couldn't find the method or __call
517 o_invoke_failed(ctx.cls->name()->data(), s.data(), fatal);
518 return false;
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();
525 return true;
528 Variant ObjectData::o_invoke(const String& s, const Variant& params,
529 bool fatal /* = true */) {
530 CallCtx ctx;
531 if (!decode_invoke(s, this, fatal, ctx) ||
532 (!isContainer(params) && !params.isNull())) {
533 return Variant(Variant::NullInit());
535 Variant ret;
536 g_context->invokeFunc((TypedValue*)&ret, ctx, params);
537 return ret;
540 Variant ObjectData::o_invoke_few_args(const String& s, int count,
541 INVOKE_FEW_ARGS_IMPL_ARGS) {
543 CallCtx ctx;
544 if (!decode_invoke(s, this, true, ctx)) {
545 return Variant(Variant::NullInit());
548 TypedValue args[INVOKE_FEW_ARGS_COUNT];
549 switch(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]);
556 #endif
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]);
561 #endif
562 case 3: tvCopy(*a2.asTypedValue(), args[2]);
563 case 2: tvCopy(*a1.asTypedValue(), args[1]);
564 case 1: tvCopy(*a0.asTypedValue(), args[0]);
565 case 0: break;
568 Variant ret;
569 g_context->invokeFuncFew(ret.asTypedValue(), ctx, count, args);
570 return ret;
573 const StaticString
574 s_zero("\0", 1),
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);
580 } else {
581 serializeImpl(serializer);
583 serializer->decNestedLevel((void*)this);
586 const StaticString
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());
606 if (!debuginfo) {
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);
616 if (ret.isArray()) {
617 return ret.toArray();
619 if (ret.isNull()) {
620 return Array::Create();
622 raise_error("__debugInfo() must return an array");
623 not_reached();
626 void ObjectData::serializeImpl(VariableSerializer* serializer) const {
627 bool handleSleep = false;
628 Variant ret;
630 if (LIKELY(serializer->getType() == VariableSerializer::Type::Serialize ||
631 serializer->getType() == VariableSerializer::Type::APCSerialize)) {
632 if (instanceof(SystemLib::s_SerializableClass)) {
633 assert(!isCollection());
634 Variant ret =
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();
640 } else {
641 raise_error("%s::serialize() must return a string or NULL",
642 o_getClassName().data());
644 return;
646 // Only serialize CPP extension type instances which can actually
647 // be deserialized.
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);
654 return;
656 if (getAttribute(HasSleep)) {
657 handleSleep = true;
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());
664 try {
665 Variant ret =
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();
671 } else {
672 raise_warning("%s::serialize() must return a string or NULL",
673 o_getClassName().data());
674 serializer->writeNull();
676 } catch (...) {
677 // serialize() throws exception
678 raise_warning("%s::serialize() throws exception",
679 o_getClassName().data());
680 serializer->writeNull();
682 return;
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());
688 return;
690 if (getAttribute(HasSleep)) {
691 try {
692 handleSleep = true;
693 ret = const_cast<ObjectData*>(this)->invokeSleep();
694 } catch (...) {
695 raise_warning("%s::sleep() throws exception", o_getClassName().data());
696 serializer->writeNull();
697 return;
702 if (UNLIKELY(handleSleep)) {
703 assert(!isCollection());
704 if (ret.isArray()) {
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;
711 Class* ctx = m_cls;
712 auto attrMask = AttrNone;
713 if (memberName.data()[0] == 0) {
714 int subLen = memberName.find('\0', 1) + 1;
715 if (subLen > 2) {
716 if (subLen == 3 && memberName.data()[1] == '*') {
717 attrMask = AttrProtected;
718 memberName = memberName.substr(subLen);
719 } else {
720 attrMask = AttrPrivate;
721 String cls = memberName.substr(1, subLen - 2);
722 ctx = Unit::lookupClass(cls.get());
723 if (ctx) {
724 memberName = memberName.substr(subLen);
725 } else {
726 ctx = m_cls;
732 bool accessible;
733 Slot propInd = m_cls->getDeclPropIndex(ctx, memberName.get(),
734 accessible);
735 if (propInd != kInvalidSlot) {
736 if (accessible) {
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(),
742 s_zero, memberName);
743 } else if (attrs & AttrProtected) {
744 memberName = concat(s_protected_prefix, memberName);
746 if (!attrMask || (attrMask & attrs) == attrMask) {
747 wanted.set(memberName, tvAsCVarRef(prop));
748 continue;
753 if (!attrMask && UNLIKELY(getAttribute(HasDynPropArr))) {
754 const TypedValue* prop = dynPropArray()->nvGet(propName.get());
755 if (prop) {
756 wanted.set(propName, tvAsCVarRef(prop));
757 continue;
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);
766 } else {
767 raise_notice("serialize(): __sleep should return an array only "
768 "containing the names of instance-variables to "
769 "serialize");
770 uninit_null().serialize(serializer);
772 } else {
773 if (isCollection()) {
774 collectionSerialize(const_cast<ObjectData*>(this), serializer);
775 } else {
776 const String& className = o_getClassName();
777 Array properties = getSerializeProps(this, serializer);
778 if (serializer->getType() ==
779 VariableSerializer::Type::DebuggerSerialize) {
780 try {
781 auto val = const_cast<ObjectData*>(this)->invokeToDebugDisplay();
782 if (val.isInitialized()) {
783 properties.lvalAt(s_PHP_DebugDisplay).assign(val);
785 } catch (...) {
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);
793 if (debugDispVal) {
794 debugDispVal->serialize(serializer);
795 return;
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);
806 return;
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);
832 } else {
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);
853 return cloneImpl();
856 Variant ObjectData::offsetGet(Variant key) {
857 assert(instanceof(SystemLib::s_ArrayAccessClass));
858 const Func* method = m_cls->lookupMethod(s_offsetGet.get());
859 assert(method);
860 if (!method) {
861 return uninit_null();
863 Variant v;
864 g_context->invokeFuncFew(v.asTypedValue(), method,
865 this, nullptr, 1, key.asCell());
866 return v;
869 ///////////////////////////////////////////////////////////////////////////////
871 const StaticString
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,
882 size_t nProps) {
883 auto* dst = propVec;
884 auto* src = propData;
885 for (; src != propData + nProps; ++src, ++dst) {
886 *dst = *src;
887 // m_aux.u_deepInit is true for properties that need "deep" initialization
888 if (src->deepInit()) {
889 tvIncRef(dst);
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());
912 assert(init);
913 TypedValue tv;
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.
917 try {
918 incRefCount();
919 g_context->invokeFuncFew(&tv, init, this);
920 decRefCount();
921 assert(!IS_REFCOUNTED_TYPE(tv.m_type));
922 } catch (...) {
923 this->setNoDestruct();
924 decRefObj(this);
925 throw;
927 return this;
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);
940 NEVER_INLINE
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();
946 table.erase(it);
949 ObjectData::~ObjectData() {
950 int& pmax = os_max_id;
951 if (o_id && o_id == pmax) {
952 --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);
991 TypedValue key;
992 properties->nvGetKey(&key, pos);
993 if (key.m_type == KindOfInt64) {
994 dynArr.set(key.m_data.num, value);
995 } else {
996 assert(IS_STRING_TYPE(key.m_type));
997 StringData* strKey = key.m_data.pstr;
998 dynArr.set(StrNR(strKey), value, true /* isKey */);
999 decRefStr(strKey);
1002 return retval;
1005 Slot ObjectData::declPropInd(TypedValue* prop) const {
1006 // Do an address range check to determine whether prop physically resides
1007 // in propVec.
1008 const TypedValue* pv = propVec();
1009 if (prop >= pv && prop < &pv[m_cls->numDeclProperties()]) {
1010 return prop - pv;
1011 } else {
1012 return kInvalidSlot;
1016 TypedValue* ObjectData::getProp(Class* ctx, const StringData* key,
1017 bool& visible, bool& accessible,
1018 bool& unset) {
1019 unset = false;
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) {
1028 unset = true;
1031 if (debug) {
1032 if (RuntimeOption::RepoAuthoritative && Repo::get().global().UsedHHBBC) {
1033 auto const repoTy = m_cls->declPropRepoAuthType(propInd);
1034 always_assert(tvMatchesRepoAuthType(*prop, repoTy));
1038 return prop;
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).
1050 visible = true;
1051 accessible = true;
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
1054 // situation.
1055 assert(!dynPropArray()->hasMultipleRefs());
1056 assert(dynPropArray()->isMixed());
1057 return const_cast<TypedValue*>(prop);
1061 return nullptr;
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 //////////////////////////////////////////////////////////////////////
1074 namespace {
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 {
1095 struct Hash;
1097 bool operator==(const PropAccessInfo& o) const {
1098 return obj == o.obj && attr == o.attr && key->same(o.key);
1101 ObjectData* obj;
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)),
1110 info.key->hash(),
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,
1129 Invoker invoker) {
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.
1137 return false;
1139 SCOPE_EXIT {
1140 propRecurInfo.activeSet->erase(info);
1143 invoker();
1144 return true;
1147 propRecurInfo.activePropInfo = &info;
1148 SCOPE_EXIT {
1149 propRecurInfo.activePropInfo = nullptr;
1150 if (UNLIKELY(propRecurInfo.activeSet != nullptr)) {
1151 smart_delete(propRecurInfo.activeSet);
1152 propRecurInfo.activeSet = nullptr;
1156 invoker();
1157 return true;
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 {
1163 TypedValue* retval;
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,
1179 TypedValue* val) {
1180 auto const info = PropAccessInfo { this, key, UseSet };
1181 return magic_prop_impl(
1182 retval,
1183 key,
1184 info,
1185 [&] {
1186 auto const meth = m_cls->lookupMethod(s___set.get());
1187 TypedValue args[2] = {
1188 make_tv<KindOfString>(const_cast<StringData*>(key)),
1189 *tvToCell(val)
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(
1199 retval,
1200 key,
1201 info,
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(
1209 retval,
1210 key,
1211 info,
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(
1219 retval,
1220 key,
1221 info,
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;
1229 retval = &tvRef;
1230 return true;
1233 //////////////////////////////////////////////////////////////////////
1235 template <bool warn, bool define>
1236 void ObjectData::propImpl(TypedValue*& retval, TypedValue& tvRef,
1237 Class* ctx,
1238 const StringData* key) {
1239 bool visible, accessible, unset;
1240 auto propVal = getProp(ctx, key, visible, accessible, unset);
1242 if (visible) {
1243 if (accessible) {
1244 if (unset) {
1245 if (!getAttribute(UseGet) || !invokeGetProp(retval, tvRef, key)) {
1246 if (warn) {
1247 raiseUndefProp(key);
1249 if (define) {
1250 retval = propVal;
1251 } else {
1252 retval = (TypedValue*)&init_null_variant;
1255 } else {
1256 retval = propVal;
1258 } else {
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(),
1268 key->data());
1271 } else {
1272 if (getAttribute(UseGet) && invokeGetProp(retval, tvRef, key)) {
1273 return;
1276 if (UNLIKELY(!*key->data())) {
1277 throw_invalid_property_name(StrNR(key));
1278 } else {
1279 if (warn) {
1280 raiseUndefProp(key);
1282 if (define) {
1283 retval = reinterpret_cast<TypedValue*>(
1284 &reserveProperties().lvalAt(StrNR(key), AccessFlags::Key)
1286 } else {
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)) {
1324 return false;
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)) {
1339 return true;
1341 tvCastToBooleanInPlace(&tv);
1342 if (!tv.m_data.num) {
1343 return true;
1345 if (getAttribute(UseGet)) {
1346 if (invokeGet(&tv, key)) {
1347 bool emptyResult = !cellToBool(*tvToCell(&tv));
1348 tvRefcountedDecRef(&tv);
1349 return emptyResult;
1352 return false;
1355 void ObjectData::setProp(Class* ctx,
1356 const StringData* key,
1357 TypedValue* val,
1358 bool bindingAssignment /* = false */) {
1359 bool visible, accessible, unset;
1360 auto propVal = getProp(ctx, key, visible, accessible, unset);
1361 if (visible && accessible) {
1362 assert(propVal);
1364 TypedValue ignored;
1365 if (!unset || !getAttribute(UseSet) || !invokeSet(&ignored, key, val)) {
1366 if (UNLIKELY(bindingAssignment)) {
1367 tvBind(val, propVal);
1368 } else {
1369 tvSet(*val, *propVal);
1371 return;
1373 tvRefcountedDecRef(&ignored);
1374 return;
1377 TypedValue ignored;
1378 if (!getAttribute(UseSet) || !invokeSet(&ignored, key, val)) {
1379 if (visible) {
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 */);
1398 } else {
1399 reserveProperties().set(
1400 StrNR(key), tvAsCVarRef(val), true /* isKey */);
1402 return;
1405 tvRefcountedDecRef(&ignored);
1408 TypedValue* ObjectData::setOpProp(TypedValue& tvRef, Class* ctx,
1409 SetOpOp op, const StringData* key,
1410 Cell* val) {
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);
1422 TypedValue ignored;
1423 if (invokeSet(&ignored, key, &tvRef)) {
1424 tvRefcountedDecRef(&ignored);
1425 return &tvRef;
1427 tvRef.m_type = KindOfUninit;
1429 cellDup(*tvToCell(&tvResult), *propVal);
1430 return propVal;
1434 propVal = tvToCell(propVal);
1435 SETOP_BODY_CELL(propVal, op, val);
1436 return propVal;
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
1463 // tvSet here.
1464 tvSet(tvResult, *propVal);
1465 return 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);
1475 TypedValue ignored;
1476 if (invokeSet(&ignored, key, &tvRef)) {
1477 tvRefcountedDecRef(&ignored);
1479 return &tvRef;
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);
1493 return propVal;
1496 template <bool setResult>
1497 void ObjectData::incDecProp(TypedValue& tvRef,
1498 Class* ctx,
1499 IncDecOp op,
1500 const StringData* key,
1501 TypedValue& dest) {
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);
1509 TypedValue ignored;
1510 if (getAttribute(UseSet) && invokeSet(&ignored, key, &tvResult)) {
1511 tvRefcountedDecRef(&ignored);
1512 propVal = &tvResult;
1513 } else {
1514 memcpy(propVal, &tvResult, sizeof(TypedValue));
1516 return;
1519 if (unset) {
1520 tvWriteNull(propVal);
1521 } else {
1522 propVal = tvToCell(propVal);
1524 IncDecBody<setResult>(op, propVal, &dest);
1525 return;
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
1536 // in tvResult.
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
1548 // tvSet here.
1549 tvSet(tvResult, *propVal);
1550 return;
1553 if (useGet && useSet) {
1554 if (invokeGet(&tvRef, key)) {
1555 tvUnboxIfNeeded(&tvRef);
1556 IncDecBody<setResult>(op, &tvRef, &dest);
1557 TypedValue ignored;
1558 if (invokeSet(&ignored, key, &tvRef)) {
1559 tvRefcountedDecRef(&ignored);
1561 return;
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&,
1578 Class*,
1579 IncDecOp,
1580 const StringData*,
1581 TypedValue&);
1582 template void ObjectData::incDecProp<false>(TypedValue&,
1583 Class*,
1584 IncDecOp,
1585 const StringData*,
1586 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);
1597 } else {
1598 // Dynamic property.
1599 dynPropArray().remove(StrNR(key).asString(),
1600 true /* isString */);
1602 return;
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");
1612 TypedValue ignored;
1613 if (!tryUnset || !invokeUnset(&ignored, key)) {
1614 if (UNLIKELY(!*key->data())) {
1615 throw_invalid_property_name(StrNR(key));
1618 return;
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,
1642 Array& props,
1643 std::vector<bool>& inserted) const {
1644 if (prop->attrs() & AttrStatic) {
1645 return;
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;
1656 props.lvalAt(
1657 StrNR(klass->declProperties()[propInd].m_mangledName).asString())
1658 .setWithRef(tvAsCVarRef(propVal));
1662 void ObjectData::getProps(const Class* klass, bool pubOnly,
1663 const PreClass* pc,
1664 Array& props,
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());
1675 if (method) {
1676 TypedValue tv;
1677 g_context->invokeFuncFew(&tv, method, this);
1678 return tvAsVariant(&tv);
1679 } else {
1680 return uninit_null();
1684 Variant ObjectData::invokeToDebugDisplay() {
1685 const Func* method = m_cls->lookupMethod(s___toDebugDisplay.get());
1686 if (method) {
1687 TypedValue tv;
1688 g_context->invokeFuncFew(&tv, method, this);
1689 return tvAsVariant(&tv);
1690 } else {
1691 return uninit_null();
1695 Variant ObjectData::invokeWakeup() {
1696 const Func* method = m_cls->lookupMethod(s___wakeup.get());
1697 if (method) {
1698 TypedValue tv;
1699 g_context->invokeFuncFew(&tv, method, this);
1700 return tvAsVariant(&tv);
1701 } else {
1702 return uninit_null();
1706 String ObjectData::invokeToString() {
1707 const Func* method = m_cls->getToString();
1708 if (!method) {
1709 // If the object does not define a __toString() method, raise a
1710 // recoverable error
1711 raise_recoverable_error(
1712 "Object of class %s could not be converted to string",
1713 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;
1719 TypedValue tv;
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);
1734 return ret;
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());
1755 TypedValue key;
1756 props->nvGetKey(&key, iter);
1758 const TypedValue* val;
1759 TypedValue* ret;
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();
1765 decRefStr(str);
1766 break;
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();
1772 break;
1774 default:
1775 always_assert(false);
1778 tvDupFlattenVars(val, ret, cloneProps.get());
1779 iter = dynProps.get()->iter_advance(iter);
1784 ObjectData* ObjectData::cloneImpl() {
1785 ObjectData* obj;
1786 Object o = obj = ObjectData::newInstance(m_cls);
1787 cloneSet(obj);
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();
1802 assert(method);
1804 TypedValue tv;
1805 tvWriteNull(&tv);
1806 g_context->invokeFuncFew(&tv, method, obj);
1807 tvRefcountedDecRef(&tv);
1809 return o.detach();
1812 RefData* ObjectData::zGetProp(Class* ctx, const StringData* key,
1813 bool& visible, bool& accessible,
1814 bool& unset) {
1815 auto tv = getProp(ctx, key, visible, accessible, unset);
1816 if (tv->m_type != KindOfRef) {
1817 tvBox(tv);
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, "");
1834 } // HPHP