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