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