Don't return Variant& from Array functions
[hiphop-php.git] / hphp / runtime / base / object-data.cpp
blobf30cd95a8e243f3854822fff755b03adb84c357e
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present 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/collections.h"
21 #include "hphp/runtime/base/container-functions.h"
22 #include "hphp/runtime/base/exceptions.h"
23 #include "hphp/runtime/base/execution-context.h"
24 #include "hphp/runtime/base/externals.h"
25 #include "hphp/runtime/base/runtime-error.h"
26 #include "hphp/runtime/base/req-containers.h"
27 #include "hphp/runtime/base/tv-refcount.h"
28 #include "hphp/runtime/base/tv-type.h"
29 #include "hphp/runtime/base/variable-serializer.h"
30 #include "hphp/runtime/base/mixed-array-defs.h"
32 #include "hphp/runtime/ext/generator/ext_generator.h"
33 #include "hphp/runtime/ext/simplexml/ext_simplexml.h"
34 #include "hphp/runtime/ext/datetime/ext_datetime.h"
35 #include "hphp/runtime/ext/std/ext_std_closure.h"
37 #include "hphp/runtime/vm/class.h"
38 #include "hphp/runtime/vm/member-operations.h"
39 #include "hphp/runtime/vm/native-data.h"
40 #include "hphp/runtime/vm/native-prop-handler.h"
41 #include "hphp/runtime/vm/jit/translator-inline.h"
42 #include "hphp/runtime/vm/repo.h"
43 #include "hphp/runtime/vm/repo-global-data.h"
45 #include "hphp/system/systemlib.h"
47 #include <folly/Hash.h>
48 #include <folly/ScopeGuard.h>
50 #include <vector>
52 namespace HPHP {
54 //////////////////////////////////////////////////////////////////////
56 // current maximum object identifier
57 __thread uint32_t ObjectData::os_max_id;
59 TRACE_SET_MOD(runtime);
61 const StaticString
62 s_offsetGet("offsetGet"),
63 s_call("__call"),
64 s_clone("__clone");
66 static Array convert_to_array(const ObjectData* obj, Class* cls) {
67 auto const prop = obj->getProp(cls, s_storage.get());
69 // We currently do not special case ArrayObjects / ArrayIterators in
70 // reflectionClass. Until, either ArrayObject moves to HNI or a special
71 // case is added to reflection unset should be turned off.
72 assert(prop.has_val() /* && prop.type() != KindOfUninit */);
73 return tvAsCVarRef(prop.tv_ptr()).toArray();
76 #ifdef _MSC_VER
77 static_assert(sizeof(ObjectData) == (use_lowptr ? 16 : 20),
78 "Change this only on purpose");
79 #else
80 static_assert(sizeof(ObjectData) == (use_lowptr ? 16 : 24),
81 "Change this only on purpose");
82 #endif
84 //////////////////////////////////////////////////////////////////////
86 ALWAYS_INLINE
87 static void invoke_destructor(ObjectData* obj, const Func* dtor) {
88 try {
89 // Call the destructor method
90 g_context->invokeMethodV(obj, dtor);
91 } catch (...) {
92 // Swallow any exceptions that escape the __destruct method
93 handle_destructor_exception();
97 NEVER_INLINE bool ObjectData::destructImpl() {
98 setNoDestruct();
99 auto const dtor = m_cls->getDtor();
100 if (!dtor) return true;
102 // We don't run PHP destructors while we're unwinding for a C++
103 // exception. We want to minimize the PHP code we run while propagating
104 // fatals, so we do this check here on a very common path, in the
105 // relatively slower case.
106 if (g_context->m_unwindingCppException) return true;
108 // Some decref paths call release() when --count == 0 and some call it when
109 // count == 1. This difference only matters for objects that resurrect
110 // themselves in their destructors, so make sure count is consistent here.
111 assert(m_count == 0 || m_count == 1);
112 m_count = static_cast<RefCount>(0);
114 // We raise the refcount around the call to __destruct(). This is to prevent
115 // the refcount from going to zero when the destructor returns.
116 CountableHelper h(this);
117 invoke_destructor(this, dtor);
118 return hasExactlyOneRef();
121 void ObjectData::destructForExit() {
122 assert(RuntimeOption::EnableObjDestructCall);
123 auto const dtor = m_cls->getDtor();
124 if (dtor) {
125 g_context->m_liveBCObjs.erase(this);
128 if (noDestruct()) return;
129 setNoDestruct();
131 // We're exiting, so there should not be any live faults.
132 assert(g_context->m_faults.empty());
133 assert(!g_context->m_unwindingCppException);
135 CountableHelper h(this);
136 invoke_destructor(this, dtor);
139 NEVER_INLINE
140 static void freeDynPropArray(ObjectData* inst) {
141 auto& table = g_context->dynPropTable;
142 auto it = table.find(inst);
143 assert(it != end(table));
144 assert(it->second.arr().isPHPArray());
145 it->second.destroy();
146 table.erase(it);
149 NEVER_INLINE
150 void ObjectData::releaseNoObjDestructCheck() noexcept {
151 assert(kindIsValid());
153 // Destructors are unsupported in one-bit reference counting mode.
154 if (!one_bit_refcount && UNLIKELY(!getAttribute(NoDestructor))) {
155 if (UNLIKELY(!destructImpl())) return;
158 auto const cls = getVMClass();
160 if (UNLIKELY(hasInstanceDtor())) {
161 return cls->instanceDtor()(this, cls);
164 // `this' is being torn down now---be careful about where/how you dereference
165 // this from here on.
167 auto const nProps = size_t{cls->numDeclProperties()};
168 auto prop = reinterpret_cast<TypedValue*>(this + 1);
169 auto const stop = prop + nProps;
170 for (; prop != stop; ++prop) {
171 tvDecRefGen(prop);
174 // Deliberately reload `attrs' to check for dynamic properties. This made
175 // gcc generate better code at the time it was done (saving a spill).
176 if (UNLIKELY(getAttribute(HasDynPropArr))) freeDynPropArray(this);
178 auto& pmax = os_max_id;
179 if (o_id && o_id == pmax) --pmax;
181 invalidateWeakRef();
182 auto const size =
183 reinterpret_cast<char*>(stop) - reinterpret_cast<char*>(this);
184 assert(size == sizeForNProps(nProps));
185 tl_heap->objFree(this, size);
186 AARCH64_WALKABLE_FRAME();
189 NEVER_INLINE
190 static void tail_call_remove_live_bc_obj(ObjectData* obj) {
191 g_context->m_liveBCObjs.erase(obj);
192 return obj->releaseNoObjDestructCheck();
195 void ObjectData::release() noexcept {
196 assert(kindIsValid());
197 if (UNLIKELY(RuntimeOption::EnableObjDestructCall && m_cls->getDtor())) {
198 tail_call_remove_live_bc_obj(this);
199 AARCH64_WALKABLE_FRAME();
200 return;
202 releaseNoObjDestructCheck();
203 AARCH64_WALKABLE_FRAME();
206 ///////////////////////////////////////////////////////////////////////////////
207 // class info
209 StrNR ObjectData::getClassName() const {
210 return m_cls->preClass()->nameStr();
213 bool ObjectData::instanceof(const String& s) const {
214 assert(kindIsValid());
215 auto const cls = Unit::lookupClass(s.get());
216 return cls && instanceof(cls);
219 bool ObjectData::toBooleanImpl() const noexcept {
220 // Note: if you add more cases here, hhbbc/class-util.cpp also needs
221 // to be changed.
222 if (isCollection()) {
223 return collections::toBool(this);
226 if (instanceof(SimpleXMLElement_classof())) {
227 // SimpleXMLElement is the only non-collection class that has custom bool
228 // casting.
229 return SimpleXMLElement_objectCast(this, KindOfBoolean).toBoolean();
232 always_assert(false);
233 return false;
236 int64_t ObjectData::toInt64Impl() const noexcept {
237 // SimpleXMLElement is the only class that has proper custom int casting.
238 assert(instanceof(SimpleXMLElement_classof()));
239 return SimpleXMLElement_objectCast(this, KindOfInt64).toInt64();
242 double ObjectData::toDoubleImpl() const noexcept {
243 // SimpleXMLElement is the only class that has custom double casting.
244 assert(instanceof(SimpleXMLElement_classof()));
245 return SimpleXMLElement_objectCast(this, KindOfDouble).toDouble();
248 ///////////////////////////////////////////////////////////////////////////////
249 // instance methods and properties
251 const StaticString s_getIterator("getIterator");
253 Object ObjectData::iterableObject(bool& isIterable,
254 bool mayImplementIterator /* = true */) {
255 assert(mayImplementIterator || !isIterator());
256 if (mayImplementIterator && isIterator()) {
257 isIterable = true;
258 return Object(this);
260 Object obj(this);
261 while (obj->instanceof(SystemLib::s_IteratorAggregateClass)) {
262 auto iterator = obj->o_invoke_few_args(s_getIterator, 0);
263 if (!iterator.isObject()) break;
264 auto o = iterator.getObjectData();
265 if (o->isIterator()) {
266 isIterable = true;
267 return Object{o};
269 obj.reset(o);
271 if (!isIterator() && obj->instanceof(SimpleXMLElement_classof())) {
272 isIterable = true;
273 return create_object(
274 s_SimpleXMLElementIterator,
275 make_packed_array(obj)
278 isIterable = false;
279 return obj;
282 Array& ObjectData::dynPropArray() const {
283 assert(getAttribute(HasDynPropArr));
284 assert(g_context->dynPropTable.count(this));
285 assert(g_context->dynPropTable[this].arr().isPHPArray());
286 return g_context->dynPropTable[this].arr();
289 Array& ObjectData::reserveProperties(int numDynamic /* = 2 */) {
290 if (getAttribute(HasDynPropArr)) {
291 return dynPropArray();
294 return
295 setDynPropArray(Array::attach(MixedArray::MakeReserveMixed(numDynamic)));
298 Array& ObjectData::setDynPropArray(const Array& newArr) {
299 assert(!g_context->dynPropTable.count(this));
300 assert(!getAttribute(HasDynPropArr));
301 assert(newArr.isPHPArray());
303 auto& arr = g_context->dynPropTable[this].arr();
304 assert(arr.isPHPArray());
305 arr = newArr;
306 setAttribute(HasDynPropArr);
307 return arr;
310 template<typename K>
311 TypedValue* ObjectData::makeDynProp(K key, AccessFlags flags) {
312 SuppressHackArrCompatNotices shacn;
313 return reserveProperties().lvalAt(key, flags).tv_ptr();
316 Variant ObjectData::o_get(const String& propName, bool error /* = true */,
317 const String& context /*= null_string*/) {
318 assert(kindIsValid());
320 // This is not (just) a check for empty string; property names that start
321 // with null are intentionally being rejected here.
322 if (UNLIKELY(!*propName.data())) {
323 throw_invalid_property_name(propName);
326 Class* ctx = nullptr;
327 if (!context.empty()) {
328 ctx = Unit::lookupClass(context.get());
331 // Can't use propImpl here because if the property is not accessible and
332 // there is no magic __get, propImpl will raise_error("Cannot access ...",
333 // but o_get will only (maybe) raise_notice("Undefined property ..." :-(
335 auto const lookup = getPropImpl(ctx, propName.get(), false);
336 auto prop = lookup.prop;
337 if (lookup.accessible && prop && prop->m_type != KindOfUninit) {
338 return tvAsVariant(prop);
341 if (getAttribute(UseGet)) {
342 if (auto r = invokeGet(propName.get())) {
343 return std::move(tvAsVariant(&r.val));
347 if (error) {
348 raise_notice("Undefined property: %s::$%s", getClassName().data(),
349 propName.data());
352 return uninit_null();
355 void ObjectData::o_set(const String& propName, const Variant& v,
356 const String& context /* = null_string */) {
357 assert(kindIsValid());
359 // This is not (just) a check for empty string; property names that start
360 // with null are intentionally being rejected here.
361 if (UNLIKELY(!*propName.data())) {
362 throw_invalid_property_name(propName);
365 Class* ctx = nullptr;
366 if (!context.empty()) {
367 ctx = Unit::lookupClass(context.get());
370 // Can't use setProp here because if the property is not accessible and
371 // there is no magic __set, setProp will raise_error("Cannot access ...",
372 // but o_set will skip writing and return normally. Also, if we try to
373 // invoke __set and fail due to recursion, setProp will fall back to writing
374 // the property normally, but o_set will just skip writing and return :-(
376 bool useSet = getAttribute(UseSet);
378 auto const lookup = getPropImpl(ctx, propName.get(), true);
379 auto prop = lookup.prop;
380 if (prop && lookup.accessible) {
381 if (!useSet || prop->m_type != KindOfUninit) {
382 tvSet(tvToInitCell(*v.asTypedValue()), *prop);
383 return;
387 if (useSet) {
388 invokeSet(propName.get(), *v.asCell());
389 } else if (!prop) {
390 reserveProperties().set(propName, tvToInitCell(*v.asTypedValue()), true);
394 void ObjectData::o_setArray(const Array& properties) {
395 for (ArrayIter iter(properties); iter; ++iter) {
396 String k = iter.first().toString();
397 Class* ctx = nullptr;
398 // If the key begins with a NUL, it's a private or protected property. Read
399 // the class name from between the two NUL bytes.
401 // Note: if you change this, you need to change similar logic in
402 // apc-object.
403 if (!k.empty() && k[0] == '\0') {
404 int subLen = k.find('\0', 1) + 1;
405 String cls = k.substr(1, subLen - 2);
406 if (cls.size() == 1 && cls[0] == '*') {
407 // Protected.
408 ctx = m_cls;
409 } else {
410 // Private.
411 ctx = Unit::lookupClass(cls.get());
412 if (!ctx) continue;
414 k = k.substr(subLen);
417 setProp(ctx, k.get(), tvAssertCell(iter.secondRval().tv()));
421 void ObjectData::o_getArray(Array& props, bool pubOnly /* = false */) const {
422 assert(kindIsValid());
424 // Fast path for classes with no declared properties
425 if (!m_cls->numDeclProperties() && getAttribute(HasDynPropArr)) {
426 props = dynPropArray();
427 return;
429 // The declared properties in the resultant array should be a permutation of
430 // propVec. They appear in the following order: go most-to-least-derived in
431 // the inheritance hierarchy, inserting properties in declaration order (with
432 // the wrinkle that overridden properties should appear only once, with the
433 // access level given to it in its most-derived declaration).
435 // This is needed to keep track of which elements have been inserted. This is
436 // the smoothest way to get overridden properties right.
437 std::vector<bool> inserted(m_cls->numDeclProperties(), false);
439 // Iterate over declared properties and insert {mangled name --> prop} pairs.
440 const Class* cls = m_cls;
441 do {
442 getProps(cls, pubOnly, cls->preClass(), props, inserted);
443 for (auto const& traitCls : cls->usedTraitClasses()) {
444 getTraitProps(cls, pubOnly, traitCls.get(), props, inserted);
446 cls = cls->parent();
447 } while (cls);
449 // Iterate over dynamic properties and insert {name --> prop} pairs.
450 if (UNLIKELY(getAttribute(HasDynPropArr))) {
451 auto& dynProps = dynPropArray();
452 if (!dynProps.empty()) {
453 for (ArrayIter it(dynProps.get()); !it.end(); it.next()) {
454 props.setWithRef(it.first(), it.secondVal(), true);
460 // a constant for arrayobjects that changes the way the array is
461 // converted to an object
462 const int64_t ARRAYOBJ_STD_PROP_LIST = 1;
464 const StaticString s_flags("flags");
466 Array ObjectData::toArray(bool pubOnly /* = false */) const {
467 assert(kindIsValid());
469 // We can quickly tell if this object is a collection, which lets us avoid
470 // checking for each class in turn if it's not one.
471 if (isCollection()) {
472 return collections::toArray(this);
473 } else if (UNLIKELY(getAttribute(CallToImpl))) {
474 // If we end up with other classes that need special behavior, turn the
475 // assert into an if and add cases.
476 assert(instanceof(SimpleXMLElement_classof()));
477 return SimpleXMLElement_objectCast(this, KindOfArray).toArray();
478 } else if (UNLIKELY(instanceof(SystemLib::s_ArrayObjectClass))) {
479 auto const flags = getProp(SystemLib::s_ArrayObjectClass, s_flags.get());
480 assert(flags.has_val());
482 if (UNLIKELY(flags.type() == KindOfInt64 &&
483 flags.val().num == ARRAYOBJ_STD_PROP_LIST)) {
484 auto ret = Array::Create();
485 o_getArray(ret, true);
486 return ret;
488 return convert_to_array(this, SystemLib::s_ArrayObjectClass);
489 } else if (UNLIKELY(instanceof(SystemLib::s_ArrayIteratorClass))) {
490 return convert_to_array(this, SystemLib::s_ArrayIteratorClass);
491 } else if (UNLIKELY(instanceof(c_Closure::classof()))) {
492 return Array::Create(Object(const_cast<ObjectData*>(this)));
493 } else if (UNLIKELY(instanceof(DateTimeData::getClass()))) {
494 return Native::data<DateTimeData>(this)->getDebugInfo();
495 } else {
496 auto ret = Array::Create();
497 o_getArray(ret, pubOnly);
498 return ret;
502 namespace {
504 size_t getPropertyIfAccessible(ObjectData* obj,
505 const Class* ctx,
506 const StringData* key,
507 ObjectData::IterMode mode,
508 Array& properties,
509 size_t propLeft) {
510 if (mode == ObjectData::CreateRefs) {
511 auto const prop = obj->vGetProp(ctx, key);
512 if (prop) {
513 --propLeft;
514 properties.setRef(StrNR(key), tvAsVariant(prop.tv_ptr()), true);
516 } else {
517 auto const prop = obj->getProp(ctx, key);
518 if (prop.has_val() && prop.type() != KindOfUninit) {
519 --propLeft;
520 if (mode == ObjectData::EraseRefs) {
521 properties.set(StrNR(key), prop.tv(), true);
522 } else {
523 properties.setWithRef(StrNR(key), prop.tv(), true);
527 return propLeft;
532 Array ObjectData::o_toIterArray(const String& context, IterMode mode) {
533 if (mode == PreserveRefs && !m_cls->numDeclProperties()) {
534 if (getAttribute(HasDynPropArr)) return dynPropArray();
535 return Array::Create();
538 Array* dynProps = nullptr;
539 size_t accessibleProps = m_cls->declPropNumAccessible();
540 size_t size = accessibleProps;
541 if (getAttribute(HasDynPropArr)) {
542 dynProps = &dynPropArray();
543 size += dynProps->size();
545 Array retArray { Array::attach(MixedArray::MakeReserveMixed(size)) };
547 Class* ctx = nullptr;
548 if (!context.empty()) {
549 ctx = Unit::lookupClass(context.get());
552 // Get all declared properties first, bottom-to-top in the inheritance
553 // hierarchy, in declaration order.
554 const Class* klass = m_cls;
555 while (klass) {
556 const PreClass::Prop* props = klass->preClass()->properties();
557 const size_t numProps = klass->preClass()->numProperties();
559 for (size_t i = 0; i < numProps; ++i) {
560 auto key = const_cast<StringData*>(props[i].name());
561 accessibleProps = getPropertyIfAccessible(
562 this, ctx, key, mode, retArray, accessibleProps);
564 klass = klass->parent();
566 if (!(m_cls->attrs() & AttrNoExpandTrait) && accessibleProps > 0) {
567 // we may have properties from traits
568 for (auto const& prop : m_cls->declProperties()) {
569 auto const key = prop.name.get();
570 if (!retArray.get()->exists(key)) {
571 accessibleProps = getPropertyIfAccessible(
572 this, ctx, key, mode, retArray, accessibleProps);
573 if (accessibleProps == 0) break;
578 // Now get dynamic properties.
579 if (dynProps) {
580 auto ad = dynProps->get();
581 ssize_t iter = ad->iter_begin();
582 auto pos_limit = ad->iter_end();
583 while (iter != pos_limit) {
584 auto const key = dynProps->get()->nvGetKey(iter);
585 iter = dynProps->get()->iter_advance(iter);
587 // You can get this if you cast an array to object. These
588 // properties must be dynamic because you can't declare a
589 // property with a non-string name.
590 if (UNLIKELY(!isStringType(key.m_type))) {
591 assert(key.m_type == KindOfInt64);
592 switch (mode) {
593 case CreateRefs: {
594 auto& lval = tvAsVariant(dynProps->lvalAt(key.m_data.num).tv_ptr());
595 retArray.setRef(key.m_data.num, lval);
596 break;
598 case EraseRefs: {
599 auto const val = dynProps->get()->at(key.m_data.num);
600 retArray.set(key.m_data.num, val);
601 break;
603 case PreserveRefs: {
604 auto const val = dynProps->get()->at(key.m_data.num);
605 retArray.setWithRef(key.m_data.num, val);
606 break;
609 continue;
612 auto const strKey = key.m_data.pstr;
613 switch (mode) {
614 case CreateRefs: {
615 auto& lval = tvAsVariant(
616 dynProps->lvalAt(StrNR(strKey), AccessFlags::Key).tv_ptr()
618 retArray.setRef(StrNR(strKey), lval, true /* isKey */);
619 break;
621 case EraseRefs: {
622 auto const val = dynProps->get()->at(strKey);
623 retArray.set(StrNR(strKey), val, true /* isKey */);
624 break;
626 case PreserveRefs: {
627 auto const val = dynProps->get()->at(strKey);
628 retArray.setWithRef(make_tv<KindOfString>(strKey),
629 val, true /* isKey */);
630 break;
633 decRefStr(strKey);
637 return retArray;
640 static bool decode_invoke(const String& s, ObjectData* obj, bool fatal,
641 CallCtx& ctx) {
642 ctx.this_ = obj;
643 ctx.cls = obj->getVMClass();
644 ctx.invName = nullptr;
646 ctx.func = ctx.cls->lookupMethod(s.get());
647 if (ctx.func) {
648 // Null out this_ for statically called methods
649 if (ctx.func->isStaticInPrologue()) {
650 ctx.this_ = nullptr;
652 } else {
653 // If this_ is non-null AND we could not find a method, try
654 // looking up __call in cls's method table
655 ctx.func = ctx.cls->lookupMethod(s_call.get());
657 if (!ctx.func) {
658 // Bail if we couldn't find the method or __call
659 o_invoke_failed(ctx.cls->name()->data(), s.data(), fatal);
660 return false;
662 // We found __call! Stash the original name into invName.
663 assert(!(ctx.func->attrs() & AttrStatic));
664 ctx.invName = s.get();
665 ctx.invName->incRefCount();
667 return true;
670 Variant ObjectData::o_invoke(const String& s, const Variant& params,
671 bool fatal /* = true */) {
672 CallCtx ctx;
673 if (!decode_invoke(s, this, fatal, ctx) ||
674 (!isContainer(params) && !params.isNull())) {
675 return Variant(Variant::NullInit());
677 return Variant::attach(
678 g_context->invokeFunc(ctx, params)
682 #define INVOKE_FEW_ARGS_IMPL3 \
683 const Variant& a0, const Variant& a1, const Variant& a2
684 #define INVOKE_FEW_ARGS_IMPL6 \
685 INVOKE_FEW_ARGS_IMPL3, \
686 const Variant& a3, const Variant& a4, const Variant& a5
687 #define INVOKE_FEW_ARGS_IMPL10 \
688 INVOKE_FEW_ARGS_IMPL6, \
689 const Variant& a6, const Variant& a7, const Variant& a8, const Variant& a9
690 #define INVOKE_FEW_ARGS_IMPL_ARGS INVOKE_FEW_ARGS(IMPL,INVOKE_FEW_ARGS_COUNT)
692 Variant ObjectData::o_invoke_few_args(const String& s, int count,
693 INVOKE_FEW_ARGS_IMPL_ARGS) {
695 CallCtx ctx;
696 if (!decode_invoke(s, this, true, ctx)) {
697 return Variant(Variant::NullInit());
700 TypedValue args[INVOKE_FEW_ARGS_COUNT];
701 switch(count) {
702 default: not_implemented();
703 #if INVOKE_FEW_ARGS_COUNT > 6
704 case 10: tvCopy(*a9.asTypedValue(), args[9]);
705 case 9: tvCopy(*a8.asTypedValue(), args[8]);
706 case 8: tvCopy(*a7.asTypedValue(), args[7]);
707 case 7: tvCopy(*a6.asTypedValue(), args[6]);
708 #endif
709 #if INVOKE_FEW_ARGS_COUNT > 3
710 case 6: tvCopy(*a5.asTypedValue(), args[5]);
711 case 5: tvCopy(*a4.asTypedValue(), args[4]);
712 case 4: tvCopy(*a3.asTypedValue(), args[3]);
713 #endif
714 case 3: tvCopy(*a2.asTypedValue(), args[2]);
715 case 2: tvCopy(*a1.asTypedValue(), args[1]);
716 case 1: tvCopy(*a0.asTypedValue(), args[0]);
717 case 0: break;
720 return Variant::attach(
721 g_context->invokeFuncFew(ctx, count, args)
725 ObjectData* ObjectData::clone() {
726 if (isCppBuiltin()) {
727 if (isCollection()) return collections::clone(this);
728 if (instanceof(c_Closure::classof())) {
729 return c_Closure::fromObject(this)->clone();
731 assertx(instanceof(c_WaitHandle::classof()));
732 // cloning WaitHandles is not allowed
733 // invoke the instanceCtor to get the right sort of exception
734 auto const ctor = m_cls->instanceCtor();
735 ctor(m_cls);
736 always_assert(false);
739 // clone prevents a leak if something throws before clone() returns
740 Object clone;
741 auto const nProps = m_cls->numDeclProperties();
742 if (getAttribute(HasNativeData)) {
743 assertx(m_cls->instanceCtor() == Native::nativeDataInstanceCtor);
744 clone = Object::attach(
745 Native::nativeDataInstanceCopyCtor(this, m_cls, nProps)
747 assertx(clone->hasExactlyOneRef());
748 assertx(clone->hasInstanceDtor());
749 } else {
750 auto const size = sizeForNProps(nProps);
751 auto const obj = new (tl_heap->objMalloc(size))
752 ObjectData(m_cls, InitRaw{}, m_cls->getODAttrs());
753 clone = Object::attach(obj);
754 assertx(clone->hasExactlyOneRef());
755 assertx(!clone->hasInstanceDtor());
758 auto const clonePropVec = clone->propVecForWrite();
759 auto const props = m_cls->declProperties();
760 for (auto i = Slot{0}; i < nProps; i++) {
761 if (UNLIKELY(props[i].attrs & AttrNoSerialize)) {
762 // need to write default value, not value from instance we're cloning
763 if (m_cls->pinitVec().size() > 0) {
764 const Class::PropInitVec* propInitVec = m_cls->getPropData();
765 cellCopy((*propInitVec)[i], clonePropVec[i]);
766 if ((*propInitVec)[i].deepInit()) {
767 tvIncRefGen(clonePropVec[i]);
768 collections::deepCopy(&clonePropVec[i]);
770 } else {
771 cellCopy(m_cls->declPropInit()[i], clonePropVec[i]);
773 } else {
774 tvDupWithRef(propVec()[i], clonePropVec[i]);
777 if (UNLIKELY(getAttribute(HasDynPropArr))) {
778 clone->setAttribute(HasDynPropArr);
779 g_context->dynPropTable.emplace(clone.get(), dynPropArray().get());
781 if (getAttribute(HasClone)) {
782 assertx(!isCppBuiltin());
783 auto const method = clone->m_cls->lookupMethod(s_clone.get());
784 assertx(method);
785 g_context->invokeMethodV(clone.get(), method);
787 return clone.detach();
790 bool ObjectData::equal(const ObjectData& other) const {
791 if (this == &other) return true;
792 if (isCollection()) {
793 return collections::equals(this, &other);
795 if (UNLIKELY(instanceof(SystemLib::s_DateTimeInterfaceClass) &&
796 other.instanceof(SystemLib::s_DateTimeInterfaceClass))) {
797 return DateTimeData::compare(this, &other) == 0;
799 if (getVMClass() != other.getVMClass()) return false;
800 if (UNLIKELY(instanceof(SystemLib::s_ArrayObjectClass))) {
801 // Compare the whole object, not just the array representation
802 auto ar1 = Array::Create();
803 auto ar2 = Array::Create();
804 o_getArray(ar1);
805 other.o_getArray(ar2);
806 return ar1->equal(ar2.get(), false);
808 if (UNLIKELY(instanceof(c_Closure::classof()))) {
809 // First comparison already proves they are different
810 return false;
812 return toArray()->equal(other.toArray().get(), false);
815 bool ObjectData::less(const ObjectData& other) const {
816 if (isCollection() || other.isCollection()) {
817 throw_collection_compare_exception();
819 if (this == &other) return false;
820 if (UNLIKELY(instanceof(SystemLib::s_DateTimeInterfaceClass) &&
821 other.instanceof(SystemLib::s_DateTimeInterfaceClass))) {
822 return DateTimeData::compare(this, &other) == -1;
824 if (UNLIKELY(instanceof(c_Closure::classof()))) {
825 // First comparison already proves they are different
826 return false;
828 if (getVMClass() != other.getVMClass()) return false;
829 return toArray().less(other.toArray());
832 bool ObjectData::more(const ObjectData& other) const {
833 if (isCollection() || other.isCollection()) {
834 throw_collection_compare_exception();
836 if (this == &other) return false;
837 if (UNLIKELY(instanceof(SystemLib::s_DateTimeInterfaceClass) &&
838 other.instanceof(SystemLib::s_DateTimeInterfaceClass))) {
839 return DateTimeData::compare(this, &other) == 1;
841 if (UNLIKELY(instanceof(c_Closure::classof()))) {
842 // First comparison already proves they are different
843 return false;
845 if (getVMClass() != other.getVMClass()) return false;
846 return toArray().more(other.toArray());
849 int64_t ObjectData::compare(const ObjectData& other) const {
850 if (isCollection() || other.isCollection()) {
851 throw_collection_compare_exception();
853 if (this == &other) return 0;
854 if (UNLIKELY(instanceof(SystemLib::s_DateTimeInterfaceClass) &&
855 other.instanceof(SystemLib::s_DateTimeInterfaceClass))) {
856 auto t1 = DateTimeData::getTimestamp(this);
857 auto t2 = DateTimeData::getTimestamp(&other);
858 return (t1 < t2) ? -1 : ((t1 > t2) ? 1 : 0);
860 // Return 1 for different classes to match PHP7 behavior.
861 if (UNLIKELY(instanceof(c_Closure::classof()))) {
862 // First comparison already proves they are different
863 return 1;
865 if (getVMClass() != other.getVMClass()) return 1;
866 return toArray().compare(other.toArray());
869 Variant ObjectData::offsetGet(Variant key) {
870 assert(instanceof(SystemLib::s_ArrayAccessClass));
872 auto const method = m_cls->lookupMethod(s_offsetGet.get());
873 assert(method);
875 return g_context->invokeMethodV(this, method, InvokeArgs(key.asCell(), 1));
878 ///////////////////////////////////////////////////////////////////////////////
880 const StaticString
881 s___get("__get"),
882 s___set("__set"),
883 s___isset("__isset"),
884 s___unset("__unset"),
885 s___sleep("__sleep"),
886 s___toDebugDisplay("__toDebugDisplay"),
887 s___wakeup("__wakeup");
889 void deepInitHelper(TypedValue* propVec, const TypedValueAux* propData,
890 size_t nProps) {
891 auto dst = propVec;
892 auto src = propData;
893 for (; src != propData + nProps; ++src, ++dst) {
894 *dst = *src;
895 // m_aux.u_deepInit is true for properties that need "deep" initialization
896 if (src->deepInit()) {
897 tvIncRefGen(*dst);
898 collections::deepCopy(dst);
903 // called from jit code
904 template<bool Big>
905 ObjectData* ObjectData::newInstanceRaw(Class* cls, size_t size) {
906 assert(cls->getODAttrs() == DefaultAttrs);
907 assert(Big || size <= kMaxSmallSize);
908 auto mem = Big ? tl_heap->mallocBigSize<MemoryManager::Unzeroed>(size) :
909 tl_heap->mallocSmallSize(size);
910 return new (mem) ObjectData(cls, InitRaw{}, DefaultAttrs);
913 // called from jit code
914 template<bool Big>
915 ObjectData* ObjectData::newInstanceRawAttrs(Class* cls, size_t size,
916 uint16_t attrs) {
917 assert(Big || size <= kMaxSmallSize);
918 auto mem = Big ? tl_heap->mallocBigSize<MemoryManager::Unzeroed>(size) :
919 tl_heap->mallocSmallSize(size);
920 return new (mem) ObjectData(cls, InitRaw{}, attrs);
923 // Explicitly instantiate newInstanceRaw[Attrs]() template funcs.
924 template ObjectData* ObjectData::newInstanceRaw<false>(Class*, size_t);
925 template ObjectData* ObjectData::newInstanceRaw<true>(Class*, size_t);
926 template ObjectData*
927 ObjectData::newInstanceRawAttrs<false>(Class*, size_t, uint16_t);
928 template ObjectData*
929 ObjectData::newInstanceRawAttrs<true>(Class*, size_t, uint16_t);
931 // Note: the normal object destruction path does not actually call this
932 // destructor. See ObjectData::release.
933 ObjectData::~ObjectData() {
934 auto& pmax = os_max_id;
935 if (o_id && o_id == pmax) {
936 --pmax;
938 if (UNLIKELY(getAttribute(HasDynPropArr))) freeDynPropArray(this);
941 Object ObjectData::FromArray(ArrayData* properties) {
942 assert(properties->isPHPArray());
943 Object retval{SystemLib::s_stdclassClass};
944 retval->setAttribute(HasDynPropArr);
945 g_context->dynPropTable.emplace(retval.get(), properties);
946 return retval;
949 Slot ObjectData::declPropInd(const TypedValue* prop) const {
950 // Do an address range check to determine whether prop physically resides
951 // in propVec.
952 const TypedValue* pv = propVec();
953 if (prop >= pv && prop < &pv[m_cls->numDeclProperties()]) {
954 return prop - pv;
955 } else {
956 return kInvalidSlot;
960 ObjectData::PropLookup<TypedValue*> ObjectData::getPropImpl(
961 const Class* ctx,
962 const StringData* key,
963 bool copyDynArray
965 auto const lookup = m_cls->getDeclPropIndex(ctx, key);
966 auto const propIdx = lookup.prop;
968 if (LIKELY(propIdx != kInvalidSlot)) {
969 // We found a visible property, but it might not be accessible. No need to
970 // check if there is a dynamic property with this name.
971 auto const prop = &propVec()[propIdx];
973 if (debug) {
974 if (RuntimeOption::RepoAuthoritative) {
975 auto const repoTy = m_cls->declPropRepoAuthType(propIdx);
976 always_assert(tvMatchesRepoAuthType(*prop, repoTy));
980 return PropLookup<TypedValue*> {
981 const_cast<TypedValue*>(prop),
982 lookup.accessible
986 // We could not find a visible declared property. We need to check for a
987 // dynamic property with this name.
988 if (UNLIKELY(getAttribute(HasDynPropArr))) {
989 if (auto const rval = dynPropArray()->rval(key)) {
990 // If we may write to the property we need to allow the array to escalate.
991 auto const prop = copyDynArray
992 ? dynPropArray().lvalAt(StrNR(key), AccessFlags::Key).tv_ptr()
993 : rval.tv_ptr();
995 // Returning a non-declared property, we know that it is accessible since
996 // all dynamic properties are.
997 return PropLookup<TypedValue*> { const_cast<TypedValue*>(prop), true };
1001 return PropLookup<TypedValue*> { nullptr, false };
1004 member_lval ObjectData::getPropLval(const Class* ctx, const StringData* key) {
1005 auto const lookup = getPropImpl(ctx, key, true);
1006 return member_lval {
1007 this, lookup.prop && lookup.accessible ? lookup.prop : nullptr
1011 member_rval ObjectData::getProp(const Class* ctx, const StringData* key) const {
1012 auto const lookup = const_cast<ObjectData*>(this)
1013 ->getPropImpl(ctx, key, false);
1014 return member_rval {
1015 this, lookup.prop && lookup.accessible ? lookup.prop : nullptr
1019 member_lval ObjectData::vGetProp(const Class* ctx, const StringData* key) {
1020 auto const lookup = getPropImpl(ctx, key, true);
1021 auto prop = lookup.prop;
1022 if (lookup.accessible && prop && prop->m_type != KindOfUninit) {
1023 tvBoxIfNeeded(*prop);
1024 return member_lval { this, prop };
1026 return member_lval { this, nullptr };
1029 member_lval ObjectData::vGetPropIgnoreAccessibility(const StringData* key) {
1030 auto const lookup = getPropImpl(nullptr, key, true);
1031 auto prop = lookup.prop;
1032 if (prop && prop->m_type != KindOfUninit) {
1033 tvBoxIfNeeded(*prop);
1034 return member_lval { this, prop };
1036 return member_lval { this, nullptr };
1039 //////////////////////////////////////////////////////////////////////
1041 inline InvokeResult::InvokeResult(bool ok, Variant&& v) :
1042 val(*v.asTypedValue()) {
1043 tvWriteUninit(*v.asTypedValue());
1044 val.m_aux.u_ok = ok;
1047 struct PropAccessInfo {
1048 struct Hash;
1050 bool operator==(const PropAccessInfo& o) const {
1051 return obj == o.obj && attr == o.attr && key->same(o.key);
1054 ObjectData* obj;
1055 const StringData* key; // note: not necessarily static
1056 ObjectData::Attribute attr;
1059 struct PropAccessInfo::Hash {
1060 size_t operator()(PropAccessInfo const& info) const {
1061 return hash_int64_pair(reinterpret_cast<intptr_t>(info.obj),
1062 info.key->hash() |
1063 (static_cast<int64_t>(info.attr) << 32));
1067 struct PropRecurInfo {
1068 using RecurSet = req::hash_set<PropAccessInfo, PropAccessInfo::Hash>;
1069 const PropAccessInfo* activePropInfo{nullptr};
1070 RecurSet* activeSet{nullptr};
1073 namespace {
1076 * Recursion of magic property accessors is allowed, but if you
1077 * recurse on the same object, for the same property, for the same
1078 * kind of magic method, it doesn't actually enter the magic method
1079 * anymore. This matches zend behavior.
1081 * This means we need to track all active property getters and ensure
1082 * we aren't recursing for the same one. Since most accesses to magic
1083 * property getters aren't going to recurse, we optimize for the case
1084 * where only a single getter is active. If it recurses again, we
1085 * promote to a hash set to track all the information needed.
1087 * The various invokeFoo functions are the entry points here. They
1088 * require that the appropriate ObjectData::Attribute has been checked
1089 * first, and return false if they refused to run the magic method due
1090 * to a recursion error.
1093 THREAD_LOCAL(PropRecurInfo, propRecurInfo);
1095 template <class Invoker>
1096 InvokeResult
1097 magic_prop_impl(const StringData* /*key*/, const PropAccessInfo& info,
1098 Invoker invoker) {
1099 auto recur_info = propRecurInfo.get();
1100 if (UNLIKELY(recur_info->activePropInfo != nullptr)) {
1101 auto activeSet = recur_info->activeSet;
1102 if (!activeSet) {
1103 activeSet = req::make_raw<PropRecurInfo::RecurSet>();
1104 activeSet->insert(*recur_info->activePropInfo);
1105 recur_info->activeSet = activeSet;
1107 if (!activeSet->insert(info).second) {
1108 // We're already running a magic method on the same type here.
1109 return {false, make_tv<KindOfUninit>()};
1111 SCOPE_EXIT {
1112 activeSet->erase(info);
1115 return {true, invoker()};
1118 recur_info->activePropInfo = &info;
1119 SCOPE_EXIT {
1120 recur_info->activePropInfo = nullptr;
1121 auto activeSet = recur_info->activeSet;
1122 if (UNLIKELY(activeSet != nullptr)) {
1123 req::destroy_raw(activeSet);
1124 recur_info->activeSet = nullptr;
1128 return {true, invoker()};
1131 // Helper for making invokers for the single-argument magic property
1132 // methods. __set takes 2 args, so it uses its own function.
1133 struct MagicInvoker {
1134 const StringData* magicFuncName;
1135 const PropAccessInfo& info;
1137 TypedValue operator()() const {
1138 auto const meth = info.obj->getVMClass()->lookupMethod(magicFuncName);
1139 TypedValue args[1] = {
1140 make_tv<KindOfString>(const_cast<StringData*>(info.key))
1142 return g_context->invokeMethod(info.obj, meth, folly::range(args));
1148 bool ObjectData::invokeSet(const StringData* key, Cell val) {
1149 auto const info = PropAccessInfo { this, key, UseSet };
1150 auto r = magic_prop_impl(key, info, [&] {
1151 auto const meth = m_cls->lookupMethod(s___set.get());
1152 TypedValue args[2] = {
1153 make_tv<KindOfString>(const_cast<StringData*>(key)),
1156 return g_context->invokeMethod(this, meth, folly::range(args));
1158 if (r) tvDecRefGen(r.val);
1159 return r.ok();
1162 InvokeResult ObjectData::invokeGet(const StringData* key) {
1163 auto const info = PropAccessInfo { this, key, UseGet };
1164 return magic_prop_impl(
1165 key,
1166 info,
1167 MagicInvoker { s___get.get(), info }
1171 InvokeResult ObjectData::invokeIsset(const StringData* key) {
1172 auto const info = PropAccessInfo { this, key, UseIsset };
1173 return magic_prop_impl(
1174 key,
1175 info,
1176 MagicInvoker { s___isset.get(), info }
1180 bool ObjectData::invokeUnset(const StringData* key) {
1181 auto const info = PropAccessInfo { this, key, UseUnset };
1182 auto r = magic_prop_impl(key, info,
1183 MagicInvoker{s___unset.get(), info});
1184 if (r) tvDecRefGen(r.val);
1185 return r.ok();
1188 static InvokeResult guardedNativePropResult(Variant result) {
1189 if (!Native::isPropHandled(result)) {
1190 return {false, make_tv<KindOfUninit>()};
1192 return InvokeResult{true, std::move(result)};
1195 InvokeResult ObjectData::invokeNativeGetProp(const StringData* key) {
1196 return guardedNativePropResult(
1197 Native::getProp(Object{this}, StrNR(key))
1201 bool ObjectData::invokeNativeSetProp(const StringData* key, Cell val) {
1202 auto r = guardedNativePropResult(
1203 Native::setProp(Object{this}, StrNR(key), tvAsCVarRef(&val))
1205 tvDecRefGen(r.val);
1206 return r.ok();
1209 InvokeResult ObjectData::invokeNativeIssetProp(const StringData* key) {
1210 return guardedNativePropResult(
1211 Native::issetProp(Object{this}, StrNR(key))
1215 bool ObjectData::invokeNativeUnsetProp(const StringData* key) {
1216 auto r = guardedNativePropResult(
1217 Native::unsetProp(Object{this}, StrNR(key))
1219 tvDecRefGen(r.val);
1220 return r.ok();
1223 //////////////////////////////////////////////////////////////////////
1225 template<ObjectData::PropMode mode>
1226 TypedValue* ObjectData::propImpl(TypedValue* tvRef, const Class* ctx,
1227 const StringData* key) {
1228 auto constexpr write = (mode == PropMode::DimForWrite) ||
1229 (mode == PropMode::Bind);
1230 auto const lookup = getPropImpl(ctx, key, write);
1231 auto const prop = lookup.prop;
1233 if (prop) {
1234 if (lookup.accessible) {
1235 // Property exists, is accessible, and is not unset.
1236 if (prop->m_type != KindOfUninit) return prop;
1238 // Property is unset, try __get.
1239 if (getAttribute(UseGet)) {
1240 if (auto r = invokeGet(key)) {
1241 tvCopy(r.val, *tvRef);
1242 return tvRef;
1246 if (mode == PropMode::ReadWarn) raiseUndefProp(key);
1247 if (write) return prop;
1248 return const_cast<TypedValue*>(&immutable_null_base);
1251 // Property is not accessible, try __get.
1252 if (getAttribute(UseGet)) {
1253 if (auto r = invokeGet(key)) {
1254 tvCopy(r.val, *tvRef);
1255 return tvRef;
1259 // Property exists, but it is either protected or private since accessible
1260 // is false.
1261 auto const propInd = m_cls->lookupDeclProp(key);
1262 auto const attrs = m_cls->declProperties()[propInd].attrs;
1263 auto const priv = (attrs & AttrPrivate) ? "private" : "protected";
1265 raise_error(
1266 "Cannot access %s property %s::$%s",
1267 priv,
1268 m_cls->preClass()->name()->data(),
1269 key->data()
1273 // First see if native getter is implemented.
1274 if (getAttribute(HasNativePropHandler)) {
1275 if (auto r = invokeNativeGetProp(key)) {
1276 tvCopy(r.val, *tvRef);
1277 return tvRef;
1281 // Next try calling user-level `__get` if it's used.
1282 if (getAttribute(UseGet)) {
1283 if (auto r = invokeGet(key)) {
1284 tvCopy(r.val, *tvRef);
1285 return tvRef;
1289 if (UNLIKELY(!*key->data())) {
1290 throw_invalid_property_name(StrNR(key));
1293 if (mode == PropMode::ReadWarn) raiseUndefProp(key);
1294 if (write) return makeDynProp(StrNR(key), AccessFlags::Key);
1295 return const_cast<TypedValue*>(&immutable_null_base);
1298 TypedValue* ObjectData::prop(
1299 TypedValue* tvRef,
1300 const Class* ctx,
1301 const StringData* key
1303 return propImpl<PropMode::ReadNoWarn>(tvRef, ctx, key);
1306 TypedValue* ObjectData::propW(
1307 TypedValue* tvRef,
1308 const Class* ctx,
1309 const StringData* key
1311 return propImpl<PropMode::ReadWarn>(tvRef, ctx, key);
1314 TypedValue* ObjectData::propD(
1315 TypedValue* tvRef,
1316 const Class* ctx,
1317 const StringData* key
1319 return propImpl<PropMode::DimForWrite>(tvRef, ctx, key);
1322 TypedValue* ObjectData::propB(
1323 TypedValue* tvRef,
1324 const Class* ctx,
1325 const StringData* key
1327 return propImpl<PropMode::Bind>(tvRef, ctx, key);
1330 bool ObjectData::propIsset(const Class* ctx, const StringData* key) {
1331 auto const lookup = getPropImpl(ctx, key, false);
1332 auto const prop = lookup.prop;
1334 if (prop && lookup.accessible && prop->m_type != KindOfUninit) {
1335 return !cellIsNull(tvToCell(prop));
1338 if (getAttribute(HasNativePropHandler)) {
1339 if (auto r = invokeNativeIssetProp(key)) {
1340 tvCastToBooleanInPlace(&r.val);
1341 return r.val.m_data.num;
1345 if (!getAttribute(UseIsset)) return false;
1346 auto r = invokeIsset(key);
1347 if (!r) return false;
1348 tvCastToBooleanInPlace(&r.val);
1349 return r.val.m_data.num;
1352 bool ObjectData::propEmptyImpl(const Class* ctx, const StringData* key) {
1353 auto const lookup = getPropImpl(ctx, key, false);
1354 auto const prop = lookup.prop;
1356 if (prop && lookup.accessible && prop->m_type != KindOfUninit) {
1357 return !cellToBool(*tvToCell(prop));
1360 if (getAttribute(HasNativePropHandler)) {
1361 if (auto r = invokeNativeIssetProp(key)) {
1362 tvCastToBooleanInPlace(&r.val);
1363 if (!r.val.m_data.num) return true;
1364 if (auto r2 = invokeNativeGetProp(key)) {
1365 auto const emptyResult = !cellToBool(*tvToCell(&r2.val));
1366 tvDecRefGen(&r2.val);
1367 return emptyResult;
1369 return false;
1373 if (!getAttribute(UseIsset)) return true;
1374 auto r = invokeIsset(key);
1375 if (!r) return true;
1377 tvCastToBooleanInPlace(&r.val);
1378 if (!r.val.m_data.num) return true;
1380 if (getAttribute(UseGet)) {
1381 if (auto r = invokeGet(key)) {
1382 auto const emptyResult = !cellToBool(*tvToCell(&r.val));
1383 tvDecRefGen(&r.val);
1384 return emptyResult;
1387 return false;
1390 bool ObjectData::propEmpty(const Class* ctx, const StringData* key) {
1391 if (UNLIKELY(getAttribute(HasPropEmpty))) {
1392 if (instanceof(SimpleXMLElement_classof())) {
1393 return SimpleXMLElement_propEmpty(this, key);
1396 return propEmptyImpl(ctx, key);
1399 void ObjectData::setProp(Class* ctx, const StringData* key, Cell val) {
1400 auto const lookup = getPropImpl(ctx, key, true);
1401 auto const prop = lookup.prop;
1403 if (prop && lookup.accessible) {
1404 if (prop->m_type != KindOfUninit ||
1405 !getAttribute(UseSet) ||
1406 !invokeSet(key, val)) {
1407 tvSet(val, *prop);
1409 return;
1412 // First see if native setter is implemented.
1413 if (getAttribute(HasNativePropHandler) && invokeNativeSetProp(key, val)) {
1414 return;
1417 // Then go to user-level `__set`.
1418 if (!getAttribute(UseSet) || !invokeSet(key, val)) {
1419 if (prop) {
1421 * Note: this differs from Zend right now in the case of a
1422 * failed recursive __set. In Zend, the __set is silently
1423 * dropped, and the protected property is not modified.
1425 raise_error("Cannot access protected property");
1427 if (UNLIKELY(!*key->data())) {
1428 throw_invalid_property_name(StrNR(key));
1430 reserveProperties().set(StrNR(key), val, true);
1431 return;
1435 TypedValue* ObjectData::setOpProp(TypedValue& tvRef,
1436 Class* ctx,
1437 SetOpOp op,
1438 const StringData* key,
1439 Cell* val) {
1440 auto const lookup = getPropImpl(ctx, key, true);
1441 auto prop = lookup.prop;
1443 if (prop && lookup.accessible) {
1444 if (prop->m_type == KindOfUninit && getAttribute(UseGet)) {
1445 if (auto r = invokeGet(key)) {
1446 SCOPE_EXIT { tvDecRefGen(r.val); };
1447 // don't unbox until after setopBody; see longer comment below
1448 setopBody(tvToCell(&r.val), op, val);
1449 tvUnboxIfNeeded(r.val);
1450 if (getAttribute(UseSet)) {
1451 cellDup(tvAssertCell(r.val), tvRef);
1452 if (invokeSet(key, tvAssertCell(tvRef))) {
1453 return &tvRef;
1455 tvRef.m_type = KindOfUninit;
1457 cellDup(tvAssertCell(r.val), *prop);
1458 return prop;
1461 prop = tvToCell(prop);
1462 setopBody(prop, op, val);
1463 return prop;
1466 if (UNLIKELY(!*key->data())) throw_invalid_property_name(StrNR(key));
1468 // Native accessors.
1469 if (getAttribute(HasNativePropHandler)) {
1470 if (auto r = invokeNativeGetProp(key)) {
1471 tvCopy(r.val, tvRef);
1472 setopBody(tvToCell(&tvRef), op, val);
1473 if (invokeNativeSetProp(key, tvToCell(tvRef))) {
1474 return &tvRef;
1477 // XXX else, write tvRef = null?
1480 auto const useSet = getAttribute(UseSet);
1481 auto const useGet = getAttribute(UseGet);
1483 if (useGet && !useSet) {
1484 auto r = invokeGet(key);
1485 if (!r) tvWriteNull(r.val);
1486 SCOPE_EXIT { tvDecRefGen(r.val); };
1488 // Note: the tvUnboxIfNeeded comes *after* the setop on purpose
1489 // here, even though it comes before the IncDecOp in the analogous
1490 // situation in incDecProp. This is to match zend 5.5 behavior.
1491 setopBody(tvToCell(&r.val), op, val);
1492 tvUnboxIfNeeded(r.val);
1494 if (prop) raise_error("Cannot access protected property");
1495 prop = makeDynProp(StrNR(key), AccessFlags::Key);
1497 // Normally this code path is defining a new dynamic property, but
1498 // unlike the non-magic case below, we may have already created it
1499 // under the recursion into invokeGet above, so we need to do a
1500 // tvSet here.
1501 tvSet(r.val, *prop);
1502 return prop;
1505 if (useGet && useSet) {
1506 if (auto r = invokeGet(key)) {
1507 // Matching zend again: incDecProp does an unbox before the
1508 // operation, but setop doesn't need to here. (We'll unbox the
1509 // value that gets passed to the magic setter, though, since
1510 // __set functions can't take parameters by reference.)
1511 tvCopy(r.val, tvRef);
1512 setopBody(tvToCell(&tvRef), op, val);
1513 invokeSet(key, tvToCell(tvRef));
1514 return &tvRef;
1518 if (prop) raise_error("Cannot access protected property");
1520 // No visible/accessible property, and no applicable magic method:
1521 // create a new dynamic property. (We know this is a new property,
1522 // or it would've hit the visible && accessible case above.)
1523 prop = makeDynProp(StrNR(key), AccessFlags::Key);
1524 assert(prop->m_type == KindOfNull); // cannot exist yet
1525 setopBody(prop, op, val);
1526 return prop;
1529 Cell ObjectData::incDecProp(Class* ctx, IncDecOp op, const StringData* key) {
1530 auto const lookup = getPropImpl(ctx, key, true);
1531 auto prop = lookup.prop;
1533 if (prop && lookup.accessible) {
1534 if (prop->m_type == KindOfUninit && getAttribute(UseGet)) {
1535 if (auto r = invokeGet(key)) {
1536 SCOPE_EXIT { tvDecRefGen(r.val); };
1537 tvUnboxIfNeeded(r.val);
1538 auto const dest = IncDecBody(op, tvAssertCell(&r.val));
1539 if (getAttribute(UseSet)) {
1540 invokeSet(key, tvAssertCell(r.val));
1541 return dest;
1543 cellCopy(tvAssertCell(r.val), *prop);
1544 tvWriteNull(r.val); // suppress decref
1545 return dest;
1548 if (prop->m_type == KindOfUninit) {
1549 tvWriteNull(*prop);
1550 } else {
1551 prop = tvToCell(prop);
1553 return IncDecBody(op, tvAssertCell(prop));
1556 if (UNLIKELY(!*key->data())) throw_invalid_property_name(StrNR(key));
1558 // Native accessors.
1559 if (getAttribute(HasNativePropHandler)) {
1560 if (auto r = invokeNativeGetProp(key)) {
1561 SCOPE_EXIT { tvDecRefGen(r.val); };
1562 tvUnboxIfNeeded(r.val);
1563 auto const dest = IncDecBody(op, tvAssertCell(&r.val));
1564 if (invokeNativeSetProp(key, tvAssertCell(r.val))) {
1565 return dest;
1570 auto const useSet = getAttribute(UseSet);
1571 auto const useGet = getAttribute(UseGet);
1573 if (useGet && !useSet) {
1574 auto r = invokeGet(key);
1575 if (!r) tvWriteNull(r.val);
1576 SCOPE_EXIT { tvDecRefGen(r.val); };
1577 tvUnboxIfNeeded(r.val);
1578 auto const dest = IncDecBody(op, tvAssertCell(&r.val));
1579 if (prop) raise_error("Cannot access protected property");
1580 prop = makeDynProp(StrNR(key), AccessFlags::Key);
1582 // Normally this code path is defining a new dynamic property, but
1583 // unlike the non-magic case below, we may have already created it
1584 // under the recursion into invokeGet above, so we need to do a
1585 // tvSet here.
1586 tvSet(r.val, *prop);
1587 return dest;
1590 if (useGet && useSet) {
1591 if (auto r = invokeGet(key)) {
1592 SCOPE_EXIT { tvDecRefGen(r.val); };
1593 tvUnboxIfNeeded(r.val);
1594 auto const dest = IncDecBody(op, tvAssertCell(&r.val));
1595 invokeSet(key, tvAssertCell(r.val));
1596 return dest;
1600 if (prop) raise_error("Cannot access protected property");
1602 // No visible/accessible property, and no applicable magic method:
1603 // create a new dynamic property. (We know this is a new property,
1604 // or it would've hit the visible && accessible case above.)
1605 prop = makeDynProp(StrNR(key), AccessFlags::Key);
1606 assert(prop->m_type == KindOfNull); // cannot exist yet
1607 return IncDecBody(op, prop);
1610 void ObjectData::unsetProp(Class* ctx, const StringData* key) {
1611 auto const lookup = getPropImpl(ctx, key, true);
1612 auto const prop = lookup.prop;
1613 auto const propInd = declPropInd(prop);
1615 if (prop && lookup.accessible && prop->m_type != KindOfUninit) {
1616 if (propInd != kInvalidSlot) {
1617 // Declared property.
1618 tvSetIgnoreRef(*uninit_variant.asTypedValue(), *prop);
1619 } else {
1620 // Dynamic property.
1621 dynPropArray().remove(StrNR(key).asString(), true /* isString */);
1623 return;
1626 // Native unset first.
1627 if (getAttribute(HasNativePropHandler) && invokeNativeUnsetProp(key)) {
1628 return;
1631 auto const tryUnset = getAttribute(UseUnset);
1633 if (propInd != kInvalidSlot && !lookup.accessible && !tryUnset) {
1634 // Defined property that is not accessible.
1635 raise_error("Cannot unset inaccessible property");
1638 if (!tryUnset || !invokeUnset(key)) {
1639 if (UNLIKELY(!*key->data())) {
1640 throw_invalid_property_name(StrNR(key));
1642 return;
1646 void ObjectData::raiseObjToIntNotice(const char* clsName) {
1647 raise_notice("Object of class %s could not be converted to int", clsName);
1650 void ObjectData::raiseObjToDoubleNotice(const char* clsName) {
1651 raise_notice("Object of class %s could not be converted to float", clsName);
1654 void ObjectData::raiseAbstractClassError(Class* cls) {
1655 Attr attrs = cls->attrs();
1656 raise_error("Cannot instantiate %s %s",
1657 (attrs & AttrInterface) ? "interface" :
1658 (attrs & AttrTrait) ? "trait" :
1659 (attrs & AttrEnum) ? "enum" : "abstract class",
1660 cls->preClass()->name()->data());
1663 void ObjectData::raiseUndefProp(const StringData* key) {
1664 raise_notice("Undefined property: %s::$%s",
1665 m_cls->name()->data(), key->data());
1668 void ObjectData::getProp(const Class* klass,
1669 bool pubOnly,
1670 const PreClass::Prop* prop,
1671 Array& props,
1672 std::vector<bool>& inserted) const {
1673 if (prop->attrs()
1674 & (AttrStatic | // statics aren't part of individual instances
1675 AttrNoSerialize // runtime-internal attrs, such as the
1676 // <<__Memoize>> cache
1677 )) {
1678 return;
1681 Slot propInd = klass->lookupDeclProp(prop->name());
1682 assert(propInd != kInvalidSlot);
1683 const TypedValue* propVal = &propVec()[propInd];
1685 if ((!pubOnly || (prop->attrs() & AttrPublic)) &&
1686 propVal->m_type != KindOfUninit &&
1687 !inserted[propInd]) {
1688 inserted[propInd] = true;
1689 props.setWithRef(
1690 StrNR(klass->declProperties()[propInd].mangledName).asString(),
1691 tvAsCVarRef(propVal));
1695 void ObjectData::getProps(const Class* klass, bool pubOnly,
1696 const PreClass* pc,
1697 Array& props,
1698 std::vector<bool>& inserted) const {
1699 PreClass::Prop const* propVec = pc->properties();
1700 size_t count = pc->numProperties();
1701 for (size_t i = 0; i < count; ++i) {
1702 getProp(klass, pubOnly, &propVec[i], props, inserted);
1706 void ObjectData::getTraitProps(const Class* klass, bool pubOnly,
1707 const Class* trait, Array& props,
1708 std::vector<bool>& inserted) const {
1709 assert(isNormalClass(klass));
1710 assert(isTrait(trait));
1712 getProps(klass, pubOnly, trait->preClass(), props, inserted);
1713 for (auto const& traitCls : trait->usedTraitClasses()) {
1714 getProps(klass, pubOnly, traitCls->preClass(), props, inserted);
1715 getTraitProps(klass, pubOnly, traitCls.get(), props, inserted);
1719 static Variant invokeSimple(ObjectData* obj, const StaticString& name) {
1720 auto const meth = obj->methodNamed(name.get());
1721 return meth ? g_context->invokeMethodV(obj, meth) : uninit_null();
1724 Variant ObjectData::invokeSleep() {
1725 return invokeSimple(this, s___sleep);
1728 Variant ObjectData::invokeToDebugDisplay() {
1729 return invokeSimple(this, s___toDebugDisplay);
1732 Variant ObjectData::invokeWakeup() {
1733 return invokeSimple(this, s___wakeup);
1736 String ObjectData::invokeToString() {
1737 const Func* method = m_cls->getToString();
1738 if (!method) {
1739 // If the object does not define a __toString() method, raise a
1740 // recoverable error
1741 raise_recoverable_error(
1742 "Object of class %s could not be converted to string",
1743 classname_cstr()
1745 // If the user error handler decides to allow execution to continue,
1746 // we return the empty string.
1747 return empty_string();
1749 auto const tv = g_context->invokeMethod(this, method);
1750 if (!isStringType(tv.m_type)) {
1751 // Discard the value returned by the __toString() method and raise
1752 // a recoverable error
1753 tvDecRefGen(tv);
1754 raise_recoverable_error(
1755 "Method %s::__toString() must return a string value",
1756 m_cls->preClass()->name()->data());
1757 // If the user error handler decides to allow execution to continue,
1758 // we return the empty string.
1759 return empty_string();
1762 return String::attach(tv.m_data.pstr);
1765 bool ObjectData::hasToString() {
1766 return (m_cls->getToString() != nullptr);
1769 const char* ObjectData::classname_cstr() const {
1770 return getClassName().data();
1773 } // HPHP