Reland D23318594 and D23318592 add recordbasenativesp instr
[hiphop-php.git] / hphp / runtime / vm / class.cpp
blob8d70fff7ee8302af20bcd1d506a5b261775e274a
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/array-init.h"
18 #include "hphp/runtime/base/collections.h"
19 #include "hphp/runtime/base/comparisons.h"
20 #include "hphp/runtime/base/enum-cache.h"
21 #include "hphp/runtime/base/mixed-array.h"
22 #include "hphp/runtime/base/rds.h"
23 #include "hphp/runtime/base/strings.h"
24 #include "hphp/runtime/base/tv-refcount.h"
25 #include "hphp/runtime/base/type-structure.h"
26 #include "hphp/runtime/base/typed-value.h"
27 #include "hphp/runtime/server/memory-stats.h"
28 #include "hphp/runtime/vm/jit/irgen-minstr.h"
29 #include "hphp/runtime/vm/jit/translator.h"
30 #include "hphp/runtime/vm/instance-bits.h"
31 #include "hphp/runtime/vm/memo-cache.h"
32 #include "hphp/runtime/vm/native-data.h"
33 #include "hphp/runtime/vm/native-prop-handler.h"
34 #include "hphp/runtime/vm/property-profile.h"
35 #include "hphp/runtime/vm/reified-generics.h"
36 #include "hphp/runtime/vm/trait-method-import-data.h"
37 #include "hphp/runtime/vm/treadmill.h"
38 #include "hphp/runtime/vm/unit-util.h"
39 #include "hphp/runtime/vm/vm-regs.h"
41 #include "hphp/runtime/ext/collections/ext_collections.h"
42 #include "hphp/runtime/ext/string/ext_string.h"
43 #include "hphp/runtime/ext/std/ext_std_closure.h"
45 #include "hphp/util/logger.h"
47 #include <folly/Bits.h>
48 #include <folly/MapUtil.h>
49 #include <folly/Optional.h>
51 #include <algorithm>
52 #include <iostream>
54 TRACE_SET_MOD(class_load);
56 namespace HPHP {
57 ///////////////////////////////////////////////////////////////////////////////
59 const StaticString s_86cinit("86cinit");
60 const StaticString s_86pinit("86pinit");
61 const StaticString s_86sinit("86sinit");
62 const StaticString s_86linit("86linit");
63 const StaticString s_86ctor("86ctor");
64 const StaticString s_86metadata("86metadata");
65 const StaticString s_86reified_prop("86reified_prop");
66 const StaticString s_86reifiedinit("86reifiedinit");
67 const StaticString s___MockClass("__MockClass");
68 const StaticString s___Reified("__Reified");
70 Mutex g_classesMutex;
72 ///////////////////////////////////////////////////////////////////////////////
73 // Class::PropInitVec.
75 Class::PropInitVec::~PropInitVec() {
76 if (m_capacity > 0) {
77 // allocated in low heap
78 lower_free(m_data);
82 Class::PropInitVec*
83 Class::PropInitVec::allocWithReqAllocator(const PropInitVec& src) {
84 uint32_t size = src.m_size;
85 auto const props_size = ObjectProps::sizeFor(size);
86 auto const bits_size = BitsetView<true>::sizeFor(size);
88 PropInitVec* p = reinterpret_cast<PropInitVec*>(req::malloc(
89 sizeof(PropInitVec) + props_size + bits_size,
90 type_scan::getIndexForMalloc<
91 PropInitVec,
92 type_scan::Action::WithSuffix<char>
93 >()
94 ));
96 p->m_size = size;
97 p->m_capacity = ~size;
98 p->m_data = reinterpret_cast<ObjectProps*>(p + 1);
100 // these are two distinct memcpys because the props and deep init bits
101 // aren't necessarily contiguous in src (because capacity >= size)
102 // but will be in the destination (which is exactly sized)
103 memcpy16_inline(p->m_data, src.m_data, props_size);
104 memcpy(reinterpret_cast<char*>(p->m_data) +
105 ObjectProps::sizeFor(size),
106 src.deepInitBits().buffer(),
107 bits_size);
109 return p;
112 const Class::PropInitVec&
113 Class::PropInitVec::operator=(const PropInitVec& piv) {
114 assertx(!reqAllocated());
115 if (this != &piv) {
116 if (UNLIKELY(m_capacity)) {
117 vm_free(m_data);
118 m_data = nullptr;
120 m_size = m_capacity = piv.size();
121 auto const props_size = ObjectProps::sizeFor(m_size);
122 auto const bits_size = BitsetView<true>::sizeFor(m_size);
123 if (m_size == 0) return *this;
125 m_data = reinterpret_cast<ObjectProps*>(
126 lower_malloc(props_size + bits_size)
128 // these are two distinct memcpys because the props and deep init bits
129 // aren't necessarily contiguous in src (because capacity >= size)
130 // but will be in the destination (which is exactly sized)
131 memcpy16_inline(m_data, piv.m_data, props_size);
132 memcpy(deepInitBits().buffer(),
133 piv.deepInitBits().buffer(),
134 bits_size);
136 assertx(m_data);
137 assertx(m_data->checkInvariants(m_capacity));
139 return *this;
142 void Class::PropInitVec::push_back(const TypedValue& v) {
143 assertx(!reqAllocated());
144 if (m_size == m_capacity) {
145 unsigned newCap = folly::nextPowTwo(m_size + 1);
146 auto const props_size = ObjectProps::sizeFor(newCap);
147 auto const bits_size = BitsetView<true>::sizeFor(newCap);
149 auto newData = reinterpret_cast<char*>(
150 lower_malloc(props_size + bits_size)
153 auto const oldSize = ObjectProps::sizeFor(m_size);
155 // TODO(jgriego) this is a kludge to preserve invariants of
156 // ObjectProps (when using 7up layouts) and should be replaced
157 // with some kind of `grow` fn on the layout class
158 memset(newData + oldSize, 0, props_size - oldSize);
160 if (m_data) {
161 // these two memcpys are separate because we're going from
162 // contiguous memory (since the size == capacity) to noncontiguous memory
163 // (because the structure has grown)
164 memcpy16_inline(newData, m_data, oldSize);
165 memcpy(newData + props_size,
166 deepInitBits().buffer(),
167 (m_size + 7) / 8);
168 lower_free(m_data);
171 m_data = reinterpret_cast<ObjectProps*>(newData);
172 m_capacity = static_cast<int32_t>(newCap);
174 assertx(m_data);
175 assertx(m_data->checkInvariants(m_capacity));
177 auto const idx = m_size++;
178 tvDup(v, m_data->at(idx));
179 deepInitBits()[idx] = false;
183 ///////////////////////////////////////////////////////////////////////////////
184 // Class.
186 namespace {
189 * Load used traits of PreClass `preClass', and append the trait Class*'s to
190 * 'usedTraits'. Return an estimate of the method count of all used traits.
192 unsigned loadUsedTraits(PreClass* preClass,
193 VMCompactVector<ClassPtr>& usedTraits) {
194 unsigned methodCount = 0;
196 auto const traitsFlattened = !!(preClass->attrs() & AttrNoExpandTrait);
197 for (auto const& traitName : preClass->usedTraits()) {
198 Class* classPtr = Unit::loadClass(traitName);
199 if (classPtr == nullptr) {
200 raise_error(Strings::TRAITS_UNKNOWN_TRAIT, traitName->data());
202 if (!(classPtr->attrs() & AttrTrait)) {
203 raise_error("%s cannot use %s - it is not a trait",
204 preClass->name()->data(),
205 classPtr->name()->data());
208 preClass->enforceInMaybeSealedParentWhitelist(classPtr->preClass());
209 if (traitsFlattened) {
210 // In RepoAuthoritative mode (with the WholeProgram compiler
211 // optimizations), the contents of traits can be flattened into
212 // the preClasses of "use"r classes. Continuing here allows us
213 // to avoid unnecessarily attempting to re-import trait methods
214 // and properties, only to fail due to (surprise surprise!) the
215 // same method/property existing on m_preClass.
216 continue;
219 usedTraits.push_back(ClassPtr(classPtr));
220 methodCount += classPtr->numMethods();
224 if (!traitsFlattened) {
225 // Trait aliases can increase method count. Get an estimate of the
226 // number of aliased functions. This doesn't need to be done in
227 // classes with flattened traits because those methods are already
228 // present in the preclass.
229 for (auto const& rule : preClass->traitAliasRules()) {
230 auto origName = rule.origMethodName();
231 auto newName = rule.newMethodName();
232 if (origName != newName) {
233 methodCount++;
237 return methodCount;
241 * Class ends with a dynamically sized array, m_classVec. C++ doesn't allow
242 * declaring empty arrays like C does, so we give it size 1 and use
243 * m_classVec's offset as the true size of Class when allocating memory to
244 * construct one.
246 constexpr size_t sizeof_Class = Class::classVecOff();
248 template<size_t sz>
249 struct assert_sizeof_class {
250 // If this static_assert fails, the compiler error will have the real value
251 // of sizeof_Class in it since it's in this struct's type.
252 #ifndef NDEBUG
253 static_assert(sz == (use_lowptr ? 276 : 328), "Change this only on purpose");
254 #else
255 static_assert(sz == (use_lowptr ? 272 : 320), "Change this only on purpose");
256 #endif
258 template struct assert_sizeof_class<sizeof_Class>;
261 * R/W lock for caching scopings of closures.
263 ReadWriteMutex s_scope_cache_mutex;
267 Class* Class::newClass(PreClass* preClass, Class* parent) {
268 auto const classVecLen = parent != nullptr ? parent->m_classVecLen + 1 : 1;
269 auto funcVecLen = (parent != nullptr ? parent->m_methods.size() : 0)
270 + preClass->numMethods();
271 if (parent == nullptr && !(preClass->attrs() & AttrNoReifiedInit)) {
272 // We will need to add 86reifiedinit method
273 funcVecLen++;
276 VMCompactVector<ClassPtr> usedTraits;
277 auto numTraitMethodsEstimate = loadUsedTraits(preClass, usedTraits);
278 funcVecLen += numTraitMethodsEstimate;
280 // We need to pad this allocation so that the actual start of the Class is
281 // 8-byte aligned.
282 auto const mask = alignof(Class) - 1;
283 auto const funcvec_sz = sizeof(LowPtr<Func>) * funcVecLen;
284 auto const prefix_sz = (funcvec_sz + mask) & ~mask;
286 auto const size = sizeof_Class + prefix_sz
287 + sizeof(m_classVec[0]) * classVecLen;
289 auto const mem = lower_malloc(size);
290 MemoryStats::LogAlloc(AllocKind::Class, size);
291 auto const classPtr = reinterpret_cast<void*>(
292 reinterpret_cast<uintptr_t>(mem) + prefix_sz
294 try {
295 return new (classPtr) Class(preClass, parent, std::move(usedTraits),
296 classVecLen, funcVecLen);
297 } catch (...) {
298 lower_free(mem);
299 throw;
303 Class* Class::rescope(Class* ctx) {
304 assertx(parent() == c_Closure::classof());
305 assertx(m_invoke);
307 // Look up the generated template class for this particular subclass of
308 // Closure. This class maintains the table of scoped clones of itself, and
309 // if we create a new scoped clone, we need to map it there.
310 auto template_cls = this;
311 auto const invoke = template_cls->m_invoke;
313 auto const try_template = [&]() -> Class* {
314 if (invoke->cls() != ctx) return nullptr;
316 // The first scoping will never be for `template_cls' (since it's
317 // impossible to define a closure in the context of its own Closure
318 // subclass), so if this happens, it means that `template_cls' is in a
319 // "de-scoped" state and we shouldn't use it. (See Class::releaseRefs().)
320 if (template_cls->m_scoped && invoke->cls() == template_cls) {
321 return nullptr;
323 return template_cls;
326 // If the template class has already been scoped to `ctx', we're done. This
327 // is the common case in repo mode.
328 if (auto cls = try_template()) return cls;
330 template_cls->allocExtraData();
331 auto& scopedClones = template_cls->m_extra.raw()->m_scopedClones;
333 auto const key = ctx;
335 auto const try_cache = [&] {
336 auto it = scopedClones.find(key);
337 return it != scopedClones.end() ? it->second.get() : nullptr;
340 { // Return the cached clone if we have one.
341 ReadLock l(s_scope_cache_mutex);
343 // If this succeeds, someone raced us to scoping the template. We may have
344 // unnecessarily allocated an ExtraData, but whatever.
345 if (auto cls = try_template()) return cls;
347 if (auto cls = try_cache()) return cls;
350 auto cloneClass = [&] {
351 auto const cls = newClass(m_preClass.get(), m_parent.get());
352 return cls;
355 // We use the French for closure because using the English crashes gcc in the
356 // implicit lambda capture below. (This is fixed in gcc 4.8.5.)
357 auto fermeture = ClassPtr {
358 template_cls->m_scoped ? cloneClass() : template_cls
361 WriteLock l(s_scope_cache_mutex);
363 if (fermeture->m_scoped) {
364 // We raced with someone who scoped the template_cls just as we were about
365 // to, so make a new Class. (Note that we don't want to do this with the
366 // lock held, since it's very expensive.)
368 // This race should be far less likely than a race between two attempted
369 // first-scopings for `template_cls', which is why we don't do an test-and-
370 // set when we first check `m_scoped' before acquiring the lock.
371 s_scope_cache_mutex.release();
372 SCOPE_EXIT { s_scope_cache_mutex.acquireWrite(); };
373 fermeture = ClassPtr { cloneClass() };
376 // Check the caches again.
377 if (auto cls = try_template()) return cls;
378 if (auto cls = try_cache()) return cls;
380 fermeture->m_invoke->rescope(ctx);
381 fermeture->m_invoke->setHasForeignThis(false);
382 fermeture->m_scoped = true;
384 if (ctx != nullptr &&
385 !RuntimeOption::RepoAuthoritative &&
386 !classHasPersistentRDS(ctx)) {
387 // If the context Class might be destroyed, we need to do extra accounting
388 // so that we can drop all clones scoped to it at the time of destruction.
389 ctx->allocExtraData();
390 ctx->m_extra.raw()->m_clonesWithThisScope.push_back(
391 ClassPtr(template_cls));
394 auto updateClones = [&] {
395 if (template_cls != fermeture.get()) {
396 scopedClones[key] = fermeture;
397 fermeture.get()->setClassHandle(template_cls->m_cachedClass);
401 InstanceBits::ifInitElse(
402 [&] {
403 fermeture->setInstanceBits();
404 updateClones();
406 [&] {
407 updateClones();
411 return fermeture.get();
414 EnumValues* Class::setEnumValues(EnumValues* values) {
415 auto extra = m_extra.ensureAllocated();
416 EnumValues* expected = nullptr;
417 if (!extra->m_enumValues.compare_exchange_strong(
418 expected, values, std::memory_order_relaxed)) {
419 // Already set by someone else, use theirs.
420 delete values;
421 return expected;
422 } else {
423 return values;
427 Class::ExtraData::~ExtraData() {
428 delete m_enumValues.load(std::memory_order_relaxed);
429 if (m_lsbMemoExtra.m_handles) {
430 vm_free(m_lsbMemoExtra.m_handles);
434 void Class::destroy() {
436 * If we were never put on NamedEntity::classList, or
437 * we've already been destroy'd, there's nothing to do
439 if (!m_cachedClass.bound()) return;
441 Lock l(g_classesMutex);
442 // Need to recheck now we have the lock
443 if (!m_cachedClass.bound()) return;
444 // Only do this once.
445 m_cachedClass = rds::Link<LowPtr<Class>, rds::Mode::NonLocal>{};
448 * Regardless of refCount, this Class is now unusable. Remove it
449 * from the class list.
451 * Needs to be under the lock, because multiple threads could call
452 * destroy, or want to manipulate the class list. (It's safe for
453 * other threads to concurrently read the class list without the
454 * lock.)
456 auto const pcls = m_preClass.get();
457 pcls->namedEntity()->removeClass(this);
459 if (m_sPropCache) {
460 // Other threads find this class via rds::s_handleTable.
461 // Remove our sprop entries before the treadmill delay.
462 for (Slot i = 0, n = numStaticProperties(); i < n; ++i) {
463 if (m_staticProperties[i].cls == this) {
464 auto const &link = m_sPropCache[i];
465 if (link.bound()) {
466 rds::unbind(rds::SPropCache{this, i}, link.handle());
472 Treadmill::enqueue(
473 [this] {
474 releaseRefs();
475 if (!this->decAtomicCount()) this->atomicRelease();
480 void Class::atomicRelease() {
481 assertx(!m_cachedClass.bound());
482 assertx(!getCount());
483 this->~Class();
484 lower_free(mallocPtr());
487 Class::~Class() {
488 releaseRefs(); // must be called for Func-nulling side effects
490 if (m_sPropCache) {
491 for (unsigned i = 0, n = numStaticProperties(); i < n; ++i) {
492 m_sPropCache[i].~Link();
494 vm_sized_free(m_sPropCache, numStaticProperties() * sizeof(*m_sPropCache));
497 for (auto i = size_t{}, n = numMethods(); i < n; i++) {
498 if (auto meth = getMethod(i)) {
499 if (meth->isPreFunc()) {
500 meth->freeClone();
501 } else {
502 Func::destroy(meth);
507 if (m_extra) {
508 delete m_extra.raw();
511 // clean enum cache
512 EnumCache::deleteValues(this);
514 if (auto p = m_vtableVec.get()) {
515 low_free(p);
518 #ifndef NDEBUG
519 validate();
520 m_magic = ~m_magic;
521 #endif
524 void Class::releaseRefs() {
526 * We have to be careful here.
527 * We want to free up as much as possible as early as possible, but
528 * some of our methods may actually belong to our parent
529 * This means we can't destroy *our* Funcs until our refCount
530 * hits zero (ie when Class::~Class gets called), because there
531 * could be a child class which hasn't yet been destroyed, which
532 * will need to inspect them. Also, we need to inspect the Funcs
533 * now (while we still have a references to parent) to determine
534 * which ones we will eventually need to free.
535 * Similarly, if any of our func's belong to a parent class, we
536 * can't free the parent, because one of our children could also
537 * have a reference to those func's (and its only reference to
538 * our parent is via this class).
540 auto num = numMethods();
541 bool okToReleaseParent = true;
542 for (auto i = 0; i < num; i++) {
543 Func* meth = getMethod(i);
544 if (meth /* releaseRefs can be called more than once */ &&
545 meth->cls() != this) {
546 setMethod(i, nullptr);
547 okToReleaseParent = false;
551 if (okToReleaseParent) {
552 m_parent.reset();
555 m_declInterfaces.clear();
556 m_requirements.clear();
558 if (m_extra) {
559 auto xtra = m_extra.raw();
560 xtra->m_usedTraits.clear();
562 if (xtra->m_clonesWithThisScope.size() > 0) {
563 WriteLock l(s_scope_cache_mutex);
565 // Purge all references to scoped closure clones that are scoped to
566 // `this'---there is no way anyone can find them at this point.
567 for (auto const template_cls : xtra->m_clonesWithThisScope) {
568 auto const invoke = template_cls->m_invoke;
570 if (invoke->cls() == this) {
571 // We only hijack the `template_cls' as a clone for static rescopings
572 // (which were signified by CloneAttr::None). To undo this, we need
573 // to make sure that /no/ scoping will match with that of
574 // `template_cls'. We can accomplish this by using `template_cls'
575 // itself as the context.
576 // Any instance of this closure will have its own scoped clone, and
577 // we are de-scoping `template_cls' here, so the only way to obtain
578 // it for dynamic binding is to reference it by name, which is
579 // logically private (in PHP7, it's always just "Closure"). In HHVM,
580 // this is possible by obtaining the Closure subclass's name, in
581 // which case we'll just force a fresh clone via a special-case check
582 // in Class::rescope().
583 invoke->rescope(template_cls.get());
584 // We explicitly decline to reset template_cls->m_scoped. This lets
585 // us simplify some assertions in rescope(), gives us a nice sanity
586 // check for debugging, and avoids having to play around too much
587 // with how Func::rescope() works, at the cost of preventing the
588 // template from being scoped again. This should only happen outside
589 // of RepoAuthoritative mode while code is being modified, so the
590 // extra memory usage is not a substantial concern.
591 } else {
592 assertx(template_cls->m_extra);
593 auto& scopedClones = template_cls->m_extra.raw()->m_scopedClones;
595 auto const it = scopedClones.find(this);
596 assertx(it != scopedClones.end());
597 it->second->m_cachedClass =
598 rds::Link<LowPtr<Class>, rds::Mode::NonLocal>{};
599 scopedClones.erase(it);
603 xtra->m_clonesWithThisScope.clear();
607 Class::Avail Class::avail(Class*& parent,
608 bool tryAutoload /* = false */) const {
609 if (Class *ourParent = m_parent.get()) {
610 if (!parent) {
611 PreClass *ppcls = ourParent->m_preClass.get();
612 parent = Unit::getClass(ppcls->namedEntity(),
613 m_preClass.get()->parent(), tryAutoload);
614 if (!parent) {
615 parent = ourParent;
616 return Avail::Fail;
619 if (parent != ourParent) {
620 if (UNLIKELY(ourParent->isZombie())) {
621 const_cast<Class*>(this)->destroy();
623 return Avail::False;
627 for (size_t i = 0; i < m_declInterfaces.size(); i++) {
628 auto di = m_declInterfaces[i].get();
629 const StringData* pdi = m_preClass.get()->interfaces()[i];
630 assertx(pdi->isame(di->name()));
632 PreClass *pint = di->m_preClass.get();
633 Class* interface = Unit::getClass(pint->namedEntity(), pdi,
634 tryAutoload);
635 if (interface != di) {
636 if (interface == nullptr) {
637 parent = di;
638 return Avail::Fail;
640 if (UNLIKELY(di->isZombie())) {
641 const_cast<Class*>(this)->destroy();
643 return Avail::False;
647 if (RuntimeOption::RepoAuthoritative) {
648 if (m_preClass->usedTraits().size()) {
649 int numIfaces = m_interfaces.size();
650 for (int i = 0; i < numIfaces; i++) {
651 auto di = m_interfaces[i].get();
653 PreClass *pint = di->m_preClass.get();
654 Class* interface = Unit::getClass(pint->namedEntity(), pint->name(),
655 tryAutoload);
656 if (interface != di) {
657 if (interface == nullptr) {
658 parent = di;
659 return Avail::Fail;
661 if (UNLIKELY(di->isZombie())) {
662 const_cast<Class*>(this)->destroy();
664 return Avail::False;
668 } else {
669 for (size_t i = 0, n = m_extra->m_usedTraits.size(); i < n; ++i) {
670 auto usedTrait = m_extra->m_usedTraits[i].get();
671 const StringData* usedTraitName = m_preClass.get()->usedTraits()[i];
672 PreClass* ptrait = usedTrait->m_preClass.get();
673 Class* trait = Unit::getClass(ptrait->namedEntity(), usedTraitName,
674 tryAutoload);
675 if (trait != usedTrait) {
676 if (trait == nullptr) {
677 parent = usedTrait;
678 return Avail::Fail;
680 if (UNLIKELY(usedTrait->isZombie())) {
681 const_cast<Class*>(this)->destroy();
683 return Avail::False;
688 return Avail::True;
692 ///////////////////////////////////////////////////////////////////////////////
693 // Ancestry.
695 const Class* Class::commonAncestor(const Class* cls) const {
696 assertx(isNormalClass(this) && isNormalClass(cls));
698 // Walk up m_classVec for both classes to look for a common ancestor.
699 auto vecIdx = std::min(m_classVecLen, cls->m_classVecLen) - 1;
700 do {
701 assertx(vecIdx < m_classVecLen && vecIdx < cls->m_classVecLen);
702 if (m_classVec[vecIdx] == cls->m_classVec[vecIdx]) {
703 return m_classVec[vecIdx];
705 } while (vecIdx--);
707 return nullptr;
710 const Class* Class::getClassDependency(const StringData* name) const {
711 for (auto idx = m_classVecLen; idx--; ) {
712 auto cls = m_classVec[idx];
713 if (cls->name()->isame(name)) return cls;
716 return m_interfaces.lookupDefault(name, nullptr);
719 ///////////////////////////////////////////////////////////////////////////////
720 // Magic methods.
722 const Func* Class::getDeclaredCtor() const {
723 const Func* f = getCtor();
724 return f != SystemLib::s_nullCtor ? f : nullptr;
727 const Func* Class::getCachedInvoke() const {
728 assertx(IMPLIES(m_invoke, !m_invoke->isStaticInPrologue()));
729 return m_invoke;
732 ///////////////////////////////////////////////////////////////////////////////
733 // Builtin classes.
735 bool Class::isCppSerializable() const {
736 assertx(instanceCtor()); // Only call this on CPP classes
737 auto* ndi = m_extra ? m_extra.raw()->m_nativeDataInfo : nullptr;
738 return ndi != nullptr && ndi->isSerializable();
741 bool Class::isCollectionClass() const {
742 auto s = name();
743 return collections::isTypeName(s);
747 ///////////////////////////////////////////////////////////////////////////////
748 // Property initialization.
750 void Class::initialize() const {
751 if (m_maybeRedefsPropTy) checkPropTypeRedefinitions();
752 if (m_needsPropInitialCheck) checkPropInitialValues();
754 if (m_pinitVec.size() > 0 && getPropData() == nullptr) {
755 initProps();
757 if (numStaticProperties() > 0 && needsInitSProps()) {
758 initSProps();
762 bool Class::initialized() const {
763 if (m_pinitVec.size() > 0 && getPropData() == nullptr) {
764 return false;
766 if (numStaticProperties() > 0 && needsInitSProps()) {
767 return false;
769 if (m_maybeRedefsPropTy &&
770 (!m_extra->m_checkedPropTypeRedefs.bound() ||
771 !m_extra->m_checkedPropTypeRedefs.isInit())) {
772 return false;
774 if (m_needsPropInitialCheck &&
775 (!m_extra->m_checkedPropInitialValues.bound() ||
776 !m_extra->m_checkedPropInitialValues.isInit())) {
777 return false;
779 return true;
782 void Class::initProps() const {
783 assertx(m_pinitVec.size() > 0);
784 assertx(getPropData() == nullptr);
785 // Copy initial values for properties to a new vector that can be used to
786 // complete initialization for non-scalar properties via the iterative
787 // 86pinit() calls below. 86pinit() takes a reference to an array to populate
788 // with initial property values; after it completes, we copy the values into
789 // the new propVec.
790 auto propVec = PropInitVec::allocWithReqAllocator(m_declPropInit);
792 VMRegAnchor _;
794 initPropHandle();
795 m_propDataCache.initWith(propVec);
797 try {
798 // Iteratively invoke 86pinit() methods upward
799 // through the inheritance chain.
800 for (auto it = m_pinitVec.rbegin(); it != m_pinitVec.rend(); ++it) {
801 DEBUG_ONLY auto retval = g_context->invokeFunc(
802 *it, init_null_variant, nullptr, const_cast<Class*>(this), false
804 assertx(retval.m_type == KindOfNull);
806 } catch (...) {
807 // Undo the allocation of propVec
808 req::destroy_raw(propVec);
809 *m_propDataCache = nullptr;
810 m_propDataCache.markUninit();
811 throw;
814 // For properties that do not require deep initialization, promote strings
815 // and arrays that came from 86pinit to static. This allows us to initialize
816 // object properties very quickly because we can just memcpy and we don't
817 // have to do any refcounting.
818 // For properties that require "deep" initialization, we have to do a little
819 // more work at object creation time.
820 for (Slot slot = 0; slot < propVec->size(); slot++) {
821 auto index = propSlotToIndex(slot);
822 auto piv_entry = (*propVec)[index];
823 assertx(!piv_entry.deepInit);
824 // Set deepInit if the property requires "deep" initialization.
825 if (m_declProperties[slot].attrs & AttrDeepInit) {
826 piv_entry.deepInit = true;
827 } else {
828 TypedValue tv = piv_entry.val.tv();
829 tvAsVariant(&tv).setEvalScalar();
830 tvCopy(tv, piv_entry.val);
835 bool Class::needsInitSProps() const {
836 return !m_sPropCacheInit.bound() || !m_sPropCacheInit.isInit();
839 void Class::initSProps() const {
840 assertx(needsInitSProps() || m_sPropCacheInit.isPersistent());
842 const bool hasNonscalarInit = !m_sinitVec.empty() || !m_linitVec.empty();
843 folly::Optional<VMRegAnchor> _;
844 if (hasNonscalarInit) {
845 _.emplace();
848 // Initialize static props for parent.
849 Class* parent = this->parent();
850 if (parent && parent->needsInitSProps()) {
851 parent->initSProps();
854 if (!numStaticProperties()) return;
856 initSPropHandles();
858 // Perform scalar inits.
859 for (Slot slot = 0, n = m_staticProperties.size(); slot < n; ++slot) {
860 auto const& sProp = m_staticProperties[slot];
861 // TODO(T61738946): We can remove the temporary here once we no longer
862 // coerce class_meth types.
863 auto val = sProp.val;
865 if ((sProp.cls == this && !m_sPropCache[slot].isPersistent()) ||
866 sProp.attrs & AttrLSB) {
867 if (RuntimeOption::EvalCheckPropTypeHints > 0 &&
868 !(sProp.attrs & (AttrInitialSatisfiesTC|AttrSystemInitialValue)) &&
869 sProp.val.m_type != KindOfUninit) {
870 if (sProp.typeConstraint.isCheckable()) {
871 sProp.typeConstraint.verifyStaticProperty(
872 &val,
873 this,
874 sProp.cls,
875 sProp.name
878 if (RuntimeOption::EvalEnforceGenericsUB > 0) {
879 for (auto const& ub : sProp.ubs) {
880 if (ub.isCheckable()) {
881 ub.verifyStaticProperty(&val, this, sProp.cls, sProp.name);
886 m_sPropCache[slot]->val = val;
890 // If there are non-scalar initializers (i.e. 86sinit or 86linit methods),
891 // run them now.
892 // They will override the KindOfUninit values set by scalar initialization.
893 if (hasNonscalarInit) {
894 for (unsigned i = 0, n = m_sinitVec.size(); i < n; i++) {
895 DEBUG_ONLY auto retval = g_context->invokeFunc(
896 m_sinitVec[i], init_null_variant, nullptr, const_cast<Class*>(this),
897 false
899 assertx(retval.m_type == KindOfNull);
901 for (unsigned i = 0, n = m_linitVec.size(); i < n; i++) {
902 DEBUG_ONLY auto retval = g_context->invokeFunc(
903 m_linitVec[i], init_null_variant, nullptr, const_cast<Class*>(this),
904 false
906 assertx(retval.m_type == KindOfNull);
910 m_sPropCacheInit.initWith(true);
913 Slot Class::lsbMemoSlot(const Func* func, bool forValue) const {
914 assertx(m_extra);
915 if (forValue) {
916 assertx(func->numParams() == 0);
917 } else {
918 assertx(func->numParams() > 0);
920 const auto& slots = m_extra->m_lsbMemoExtra.m_slots;
921 auto it = slots.find(func->getFuncId());
922 always_assert(it != slots.end());
923 return it->second;
926 void Class::checkPropInitialValues() const {
927 assertx(m_needsPropInitialCheck);
928 assertx(RuntimeOption::EvalCheckPropTypeHints > 0);
929 assertx(m_extra.get() != nullptr);
931 auto extra = m_extra.get();
932 checkedPropInitialValuesHandle(); // init handle
933 if (extra->m_checkedPropInitialValues.isInit()) return;
935 for (Slot slot = 0; slot < m_declProperties.size(); ++slot) {
936 auto const& prop = m_declProperties[slot];
937 if (prop.attrs & (AttrInitialSatisfiesTC|AttrSystemInitialValue)) continue;
938 auto const& tc = prop.typeConstraint;
939 auto const index = propSlotToIndex(slot);
940 auto const rval = m_declPropInit[index].val;
941 if (type(rval) == KindOfUninit) continue;
942 auto tv = rval.tv();
943 if (tc.isCheckable()) tc.verifyProperty(&tv, this, prop.cls, prop.name);
944 if (RuntimeOption::EvalEnforceGenericsUB > 0) {
945 for (auto const& ub : prop.ubs) {
946 if (ub.isCheckable()) {
947 ub.verifyProperty(&tv, this, prop.cls, prop.name);
952 // No coercion for statically initialized properties.
953 assertx(type(tv) == type(rval));
954 assertx(val(tv).num == val(rval).num);
957 extra->m_checkedPropInitialValues.initWith(true);
960 void Class::checkPropTypeRedefinitions() const {
961 assertx(m_maybeRedefsPropTy);
962 assertx(RuntimeOption::EvalCheckPropTypeHints > 0);
963 assertx(m_parent);
964 assertx(m_extra.get() != nullptr);
966 auto extra = m_extra.get();
967 checkedPropTypeRedefinesHandle(); // init handle
968 if (extra->m_checkedPropTypeRedefs.isInit()) return;
970 if (m_parent->m_maybeRedefsPropTy) m_parent->checkPropTypeRedefinitions();
972 if (m_selfMaybeRedefsPropTy) {
973 for (Slot slot = 0; slot < m_declProperties.size(); slot++) {
974 auto const& prop = m_declProperties[slot];
975 if (prop.attrs & AttrNoBadRedeclare) continue;
976 checkPropTypeRedefinition(slot);
980 extra->m_checkedPropTypeRedefs.initWith(true);
983 void Class::checkPropTypeRedefinition(Slot slot) const {
984 assertx(m_maybeRedefsPropTy);
985 assertx(RuntimeOption::EvalCheckPropTypeHints > 0);
986 assertx(m_parent);
987 assertx(slot != kInvalidSlot);
988 assertx(slot < numDeclProperties());
990 auto const& prop = m_declProperties[slot];
991 assertx(!(prop.attrs & AttrNoBadRedeclare));
993 auto const& oldProp = m_parent->m_declProperties[slot];
995 auto const& oldTC = oldProp.typeConstraint;
996 auto const& newTC = prop.typeConstraint;
998 if (!oldTC.equivalentForProp(newTC)) {
999 auto const oldTCName =
1000 oldTC.hasConstraint() ? oldTC.displayName() : "mixed";
1001 auto const newTCName =
1002 newTC.hasConstraint() ? newTC.displayName() : "mixed";
1004 auto const msg = folly::sformat(
1005 "Type-hint of '{}::{}' must be {}{} (as in class {}), not {}",
1006 prop.cls->name(),
1007 prop.name,
1008 oldTC.isUpperBound()? "upper-bounded by " : "",
1009 oldTCName,
1010 oldProp.cls->name(),
1011 newTCName
1013 raise_property_typehint_error(
1014 msg,
1015 oldTC.isSoft() && newTC.isSoft(),
1016 oldTC.isUpperBound() || newTC.isUpperBound()
1020 if (RuntimeOption::EvalEnforceGenericsUB > 0 &&
1021 (!prop.ubs.empty() || !oldProp.ubs.empty())) {
1022 std::vector<TypeConstraint> newTCs = {newTC};
1023 for (auto const& ub : prop.ubs) newTCs.push_back(ub);
1024 std::vector<TypeConstraint> oldTCs = {oldTC};
1025 for (auto const& ub : oldProp.ubs) oldTCs.push_back(ub);
1027 for (auto const& ub : newTCs) {
1028 if (std::none_of(oldTCs.begin(), oldTCs.end(),
1029 [&](const TypeConstraint& tc) {
1030 return tc.equivalentForProp(ub);
1031 })) {
1032 auto const ubName = ub.hasConstraint() ? ub.displayName() : "mixed";
1033 auto const msg = folly::sformat(
1034 "Upper-bound {} of {}::{} has no equivalent upper-bound in {}",
1035 ubName, prop.cls->name(), prop.name, oldProp.cls->name()
1037 raise_property_typehint_error(msg, ub.isSoft(), true);
1040 for (auto const& ub : oldTCs) {
1041 if (std::none_of(newTCs.begin(), newTCs.end(),
1042 [&](const TypeConstraint& tc) {
1043 return tc.equivalentForProp(ub);
1044 })) {
1045 auto const ubName = ub.hasConstraint() ? ub.displayName() : "mixed";
1046 auto const msg = folly::sformat(
1047 "Upper-bound {} of {}::{} has no equivalent upper-bound in {}",
1048 ubName, oldProp.cls->name(), oldProp.name, prop.cls->name()
1050 raise_property_typehint_error(msg, ub.isSoft(), true);
1056 ///////////////////////////////////////////////////////////////////////////////
1057 // Property storage.
1059 void Class::initSPropHandles() const {
1060 if (m_sPropCacheInit.bound()) return;
1062 bool usePersistentHandles = m_cachedClass.isPersistent();
1063 bool allPersistentHandles = usePersistentHandles;
1065 // Propagate to parents so we can link inherited static props.
1066 Class* parent = this->parent();
1067 if (parent) {
1068 parent->initSPropHandles();
1069 if (!rds::isPersistentHandle(parent->sPropInitHandle())) {
1070 allPersistentHandles = false;
1074 // Bind all the static prop handles.
1075 for (Slot slot = 0, n = m_staticProperties.size(); slot < n; ++slot) {
1076 auto& propHandle = m_sPropCache[slot];
1077 auto const& sProp = m_staticProperties[slot];
1079 if (sProp.cls == this || (sProp.attrs & AttrLSB)) {
1080 if (usePersistentHandles && (sProp.attrs & AttrPersistent)) {
1081 static_assert(sizeof(StaticPropData) == sizeof(sProp.val),
1082 "StaticPropData must be a simple wrapper "
1083 "around TypedValue");
1084 propHandle.bind(
1085 rds::Mode::Persistent,
1086 rds::SPropCache{this, slot},
1087 reinterpret_cast<const StaticPropData*>(&sProp.val)
1089 } else {
1090 propHandle.bind(
1091 rds::Mode::Local,
1092 rds::SPropCache{this, slot}
1095 } else {
1096 auto const realSlot = sProp.cls->lookupSProp(sProp.name);
1097 propHandle = sProp.cls->m_sPropCache[realSlot];
1099 if (!propHandle.isPersistent()) {
1100 allPersistentHandles = false;
1104 // Bind the init handle; this indicates that all handles are bound.
1105 if (allPersistentHandles) {
1106 // We must make sure the value stored at the handle is correct before
1107 // setting m_sPropCacheInit in case another thread tries to read it at just
1108 // the wrong time. And rather than giving each Class its own persistent
1109 // handle that always points to an immutable 'true', share one between all
1110 // of them.
1111 m_sPropCacheInit = rds::s_persistentTrue;
1112 } else {
1113 m_sPropCacheInit.bind(
1114 rds::Mode::Normal,
1115 rds::LinkName{"PropCacheInit", name()}
1118 rds::recordRds(m_sPropCacheInit.handle(),
1119 sizeof(bool), "SPropCacheInit", name()->slice());
1122 Class::PropInitVec* Class::getPropData() const {
1123 return (m_propDataCache.bound() && m_propDataCache.isInit())
1124 ? *m_propDataCache
1125 : nullptr;
1128 TypedValue* Class::getSPropData(Slot index) const {
1129 assertx(numStaticProperties() > index);
1130 return m_sPropCache[index].bound()
1131 ? &m_sPropCache[index].get()->val
1132 : nullptr;
1136 ///////////////////////////////////////////////////////////////////////////////
1137 // Property lookup and accessibility.
1139 Class::PropSlotLookup Class::getDeclPropSlot(
1140 const Class* ctx,
1141 const StringData* key
1142 ) const {
1143 auto const propSlot = lookupDeclProp(key);
1145 auto accessible = false;
1147 if (propSlot != kInvalidSlot) {
1148 auto const attrs = m_declProperties[propSlot].attrs;
1149 if ((attrs & (AttrProtected|AttrPrivate)) &&
1150 (g_context.isNull() || !g_context->debuggerSettings.bypassCheck)) {
1151 // Fetch the class in the inheritance tree which first declared the
1152 // property
1153 auto const baseClass = m_declProperties[propSlot].cls;
1154 assertx(baseClass);
1156 // If ctx == baseClass, we have the right property and we can stop here.
1157 if (ctx == baseClass) return PropSlotLookup { propSlot, true };
1159 // The anonymous context cannot access protected or private properties, so
1160 // we can fail fast here.
1161 if (ctx == nullptr) return PropSlotLookup { propSlot, false };
1163 assertx(ctx);
1164 if (attrs & AttrPrivate) {
1165 // ctx != baseClass and the property is private, so it is not
1166 // accessible. We need to keep going because ctx may define a private
1167 // property with this name.
1168 accessible = false;
1169 } else {
1170 if (ctx == (Class*)-1 || ctx->classof(baseClass)) {
1171 // The special ctx (Class*)-1 is used by unserialization to
1172 // mean that protected properties are ok. Otherwise,
1173 // ctx is derived from baseClass, so we know this protected
1174 // property is accessible and we know ctx cannot have private
1175 // property with the same name, so we're done.
1176 return PropSlotLookup { propSlot, true };
1178 if (!baseClass->classof(ctx)) {
1179 // ctx is not the same, an ancestor, or a descendent of baseClass,
1180 // so the property is not accessible. Also, we know that ctx cannot
1181 // be the same or an ancestor of this, so we don't need to check if
1182 // ctx declares a private property with the same name and we can
1183 // fail fast here.
1184 return PropSlotLookup { propSlot, false };
1186 // We now know this protected property is accessible, but we need to
1187 // keep going because ctx may define a private property with the same
1188 // name.
1189 accessible = true;
1190 assertx(baseClass->classof(ctx));
1192 } else {
1193 // The property is public (or we're in the debugger and we are bypassing
1194 // accessibility checks).
1195 accessible = true;
1196 // If ctx == this, we don't have to check if ctx defines a private
1197 // property with the same name and we can stop here.
1198 if (ctx == this) return PropSlotLookup { propSlot, true };
1200 // We still need to check if ctx defines a private property with the same
1201 // name.
1203 } else {
1204 // We didn't find a visible declared property in this's property map
1205 accessible = false;
1208 // If ctx is an ancestor of this, check if ctx has a private property with the
1209 // same name.
1210 if (ctx && ctx != (Class*)-1 && classof(ctx)) {
1211 auto const ctxPropSlot = ctx->lookupDeclProp(key);
1213 if (ctxPropSlot != kInvalidSlot &&
1214 ctx->m_declProperties[ctxPropSlot].cls == ctx &&
1215 (ctx->m_declProperties[ctxPropSlot].attrs & AttrPrivate)) {
1216 // A private property from ctx trumps any other property we may
1217 // have found.
1218 return PropSlotLookup { ctxPropSlot, true };
1222 if (propSlot == kInvalidSlot &&
1223 !g_context.isNull() &&
1224 g_context->debuggerSettings.bypassCheck &&
1225 m_parent) {
1226 // If the property could not be located on the current class, and this
1227 // class has a parent class, and the current evaluation is a debugger
1228 // eval with bypassCheck == true, search for the property as a member of
1229 // the parent class. The debugger access is not subject to visibilty checks.
1230 return m_parent->getDeclPropSlot(ctx, key);
1233 return PropSlotLookup { propSlot, accessible };
1236 Class::PropSlotLookup Class::findSProp(
1237 const Class* ctx,
1238 const StringData* sPropName
1239 ) const {
1240 auto const sPropInd = lookupSProp(sPropName);
1242 // Non-existent property.
1243 if (sPropInd == kInvalidSlot)
1244 return PropSlotLookup { kInvalidSlot, false, false };
1246 auto const& sProp = m_staticProperties[sPropInd];
1247 auto const sPropAttrs = sProp.attrs;
1248 auto const sPropConst = bool(sPropAttrs & AttrIsConst);
1249 const Class* baseCls = this;
1250 if (sPropAttrs & AttrLSB) {
1251 // For an LSB static, accessibility attributes are relative to the class
1252 // that originally declared it.
1253 baseCls = sProp.cls;
1255 // Property access within this Class's context.
1256 if (ctx == baseCls) return PropSlotLookup { sPropInd, true, sPropConst };
1258 auto const accessible = [&] {
1259 switch (sPropAttrs & (AttrPublic | AttrProtected | AttrPrivate)) {
1260 // Public properties are always accessible.
1261 case AttrPublic:
1262 return true;
1264 // Property access is from within a parent class's method, which is
1265 // allowed for protected properties.
1266 case AttrProtected:
1267 return ctx != nullptr &&
1268 (baseCls->classof(ctx) || ctx->classof(baseCls));
1270 // Can only access private properties via the debugger.
1271 case AttrPrivate:
1272 return g_context->debuggerSettings.bypassCheck;
1274 default: break;
1276 not_reached();
1277 }();
1279 return PropSlotLookup { sPropInd, accessible, sPropConst };
1282 Class::PropValLookup Class::getSPropIgnoreLateInit(
1283 const Class* ctx,
1284 const StringData* sPropName
1285 ) const {
1286 initialize();
1288 auto const lookup = findSProp(ctx, sPropName);
1289 if (lookup.slot == kInvalidSlot) {
1290 return PropValLookup { nullptr, kInvalidSlot, false, false };
1293 auto const sProp = getSPropData(lookup.slot);
1295 if (debug) {
1296 auto const& decl = m_staticProperties[lookup.slot];
1297 auto const lateInit = bool(decl.attrs & AttrLateInit);
1299 always_assert(
1300 sProp && (sProp->m_type != KindOfUninit || lateInit) &&
1301 "Static property initialization failed to initialize a property."
1304 if (sProp->m_type != KindOfUninit) {
1305 if (RuntimeOption::RepoAuthoritative) {
1306 auto const repoTy = staticPropRepoAuthType(lookup.slot);
1307 always_assert(tvMatchesRepoAuthType(*sProp, repoTy));
1310 if (RuntimeOption::EvalCheckPropTypeHints > 2) {
1311 auto const typeOk = [&]{
1312 auto skipCheck =
1313 !decl.typeConstraint.isCheckable() ||
1314 decl.typeConstraint.isSoft() ||
1315 (decl.typeConstraint.isUpperBound() &&
1316 RuntimeOption::EvalEnforceGenericsUB < 2) ||
1317 (sProp->m_type == KindOfNull &&
1318 !(decl.attrs & AttrNoImplicitNullable));
1320 auto res = skipCheck ? true : decl.typeConstraint.assertCheck(sProp);
1321 if (RuntimeOption::EvalEnforceGenericsUB >= 2) {
1322 for (auto const& ub : decl.ubs) {
1323 if (ub.isCheckable() && !ub.isSoft()) {
1324 res = res && ub.assertCheck(sProp);
1328 return res;
1329 }();
1330 always_assert(typeOk);
1335 return
1336 PropValLookup { sProp, lookup.slot, lookup.accessible, lookup.constant };
1339 Class::PropValLookup Class::getSProp(
1340 const Class* ctx,
1341 const StringData* sPropName
1342 ) const {
1343 auto const lookup = getSPropIgnoreLateInit(ctx, sPropName);
1344 if (lookup.val && UNLIKELY(lookup.val->m_type == KindOfUninit)) {
1345 auto const& decl = m_staticProperties[lookup.slot];
1346 if (decl.attrs & AttrLateInit) {
1347 throw_late_init_prop(decl.cls, sPropName, true);
1350 return lookup;
1353 bool Class::IsPropAccessible(const Prop& prop, Class* ctx) {
1354 if (prop.attrs & AttrPublic) return true;
1355 if (prop.attrs & AttrPrivate) return prop.cls == ctx;
1356 if (!ctx) return false;
1358 return prop.cls->classof(ctx) || ctx->classof(prop.cls);
1361 Slot Class::propIndexToSlot(uint16_t index) const {
1362 auto const nprops = m_declProperties.size();
1363 for (Slot slot = 0; slot < nprops; slot++) {
1364 if (m_slotIndex[slot] == index) return slot;
1366 always_assert_flog(0, "propIndexToSlot: no slot found for index = {}", index);
1369 ///////////////////////////////////////////////////////////////////////////////
1370 // Constants.
1372 const StaticString s_classname("classname");
1374 TypedValue Class::clsCnsGet(const StringData* clsCnsName, ClsCnsLookup what) const {
1375 Slot clsCnsInd;
1376 auto cnsVal = cnsNameToTV(clsCnsName, clsCnsInd, what);
1377 if (!cnsVal) return make_tv<KindOfUninit>();
1379 auto& cns = m_constants[clsCnsInd];
1380 ArrayData* typeCns = nullptr;
1382 if (cnsVal->m_type != KindOfUninit) {
1383 if (cns.isType()) {
1384 // Type constants with the low bit set are already resolved and can be
1385 // returned after masking out that bit.
1387 // We can't check isHAMSafeDArray here because we're using that low bit as
1388 // a marker; instead, check isArrayLike and do the stricter check below.
1389 assertx(isArrayLikeType(type(cnsVal)));
1390 assertx(!isRefcountedType(type(cnsVal)));
1391 typeCns = val(cnsVal).parr;
1392 auto const rawData = reinterpret_cast<intptr_t>(typeCns);
1393 if (rawData & 0x1) {
1394 auto const resolved = reinterpret_cast<ArrayData*>(rawData ^ 0x1);
1395 assertx(resolved->isHAMSafeDArray());
1396 return make_persistent_array_like_tv(resolved);
1398 if (what == ClsCnsLookup::IncludeTypesPartial) {
1399 return make_tv<KindOfUninit>();
1401 } else {
1402 return *cnsVal;
1407 * We use a sentinel static array to mark constants that are being evaluated
1408 * during recursive resolution. This array is never exposed to the rest of
1409 * the runtime, so we can test for it by pointer equality.
1411 static auto const s_sentinelVec = PackedArray::CopyStatic(staticEmptyVec());
1412 assertx(s_sentinelVec != staticEmptyVec());
1415 * We have either a constant with a non-scalar initializer or an unresolved
1416 * type constant, meaning it will be potentially different in different
1417 * requests, which we store separately in an array in RDS.
1419 * We need a special marker value in the non-scalar constant cache to indicate
1420 * that we're currently evaluating the value of a (type) constant. If we
1421 * attempt to evaluate the value of a (type) constant, and the special marker
1422 * is present, that means the (type) constant is recursively defined and
1423 * we'll raise an error. The globals array is never a valid value of a (type)
1424 * constant, so we use it as the marker.
1426 m_nonScalarConstantCache.bind(
1427 rds::Mode::Normal,
1428 rds::LinkName{"ClassNonScalarCnsCache", name()}
1430 auto& clsCnsData = *m_nonScalarConstantCache;
1431 if (m_nonScalarConstantCache.isInit()) {
1432 auto const cCns = clsCnsData->get(clsCnsName);
1433 if (cCns.is_init()) {
1434 // There's an entry in the cache for this (type) constant. If it's the
1435 // sentinel value, the constant is recursively defined - throw an error.
1436 // Otherwise, return the cached result.
1437 if (UNLIKELY(tvIsVec(cCns) && val(cCns).parr == s_sentinelVec)) {
1438 raise_error(
1439 folly::sformat(
1440 "Cannot declare self-referencing {} '{}::{}'",
1441 cns.isType() ? "type constant" : "constant",
1442 name(),
1443 clsCnsName
1447 return cCns;
1449 } else {
1450 // Because RDS uses a generation number scheme, when isInit was false we
1451 // may have a pointer to a (no-longer extant) ArrayData in our RDS entry.
1452 // Use detach to clear our Array so we don't attempt to decref the stale
1453 // ArrayData.
1454 clsCnsData.detach();
1455 clsCnsData = Array::attach(
1456 MixedArray::MakeReserveDict(m_constants.size())
1458 m_nonScalarConstantCache.markInit();
1461 // We're going to run the 86cinit to get the constant's value, or try to
1462 // resolve the type constant. Store the sentinel value as this (type)
1463 // constant's cache entry // so that we can detect recursion.
1464 auto marker = make_array_like_tv(s_sentinelVec);
1465 clsCnsData.set(StrNR(clsCnsName), tvAsCVarRef(&marker), true /* isKey */);
1467 if (cns.isType()) {
1468 // Resolve type constant, if needed
1469 if (what == ClsCnsLookup::IncludeTypesPartial) {
1470 return make_tv<KindOfUninit>();
1472 Array resolvedTS;
1473 bool persistent = true;
1474 try {
1475 resolvedTS = TypeStructure::resolve(typeCns, cns.name, cns.cls, this,
1476 persistent);
1477 } catch (const Exception& e) {
1478 raise_error(e.getMessage());
1480 assertx(resolvedTS.isHAMSafeDArray());
1482 auto const ad = ArrayData::GetScalarArray(std::move(resolvedTS));
1483 if (persistent) {
1484 auto const rawData = reinterpret_cast<intptr_t>(ad);
1485 assertx((rawData & 0x7) == 0 && "ArrayData not 8-byte aligned");
1486 auto taggedData = reinterpret_cast<ArrayData*>(rawData | 0x1);
1488 // Multiple threads might create and store the resolved type structure
1489 // here, but that's fine since they'll all store the same thing thanks to
1490 // GetScalarArray(). Ditto for the pointed class.
1491 // We could avoid a little duplicated work during warmup with more
1492 // complexity but it's not worth it.
1493 auto& cns_nc = const_cast<Const&>(cns);
1494 auto const classname_field = ad->get(s_classname.get());
1495 if (isStringType(classname_field.type())) {
1496 cns_nc.setPointedClsName(classname_field.val().pstr);
1498 cns_nc.val.m_data.parr = taggedData;
1499 return make_persistent_array_like_tv(ad);
1502 auto tv = make_persistent_array_like_tv(ad);
1503 clsCnsData.set(StrNR(clsCnsName), tvAsCVarRef(&tv), true /* isKey */);
1504 return tv;
1507 // The class constant has not been initialized yet; do so.
1508 auto const meth86cinit = cns.cls->lookupMethod(s_86cinit.get());
1509 TypedValue args[1] = {
1510 make_tv<KindOfPersistentString>(const_cast<StringData*>(cns.name.get()))
1513 auto ret = g_context->invokeFuncFew(
1514 meth86cinit,
1515 const_cast<Class*>(this),
1517 args,
1518 false,
1519 false
1522 assertx(tvAsCVarRef(&ret).isAllowedAsConstantValue());
1523 clsCnsData.set(StrNR(clsCnsName), tvAsCVarRef(ret), true /* isKey */);
1525 // The caller will inc-ref the returned value, so undo the inc-ref caused by
1526 // storing it in the cache.
1527 tvDecRefGenNZ(&ret);
1528 return ret;
1531 const TypedValue* Class::cnsNameToTV(const StringData* clsCnsName,
1532 Slot& clsCnsInd,
1533 ClsCnsLookup what) const {
1534 clsCnsInd = m_constants.findIndex(clsCnsName);
1535 if (clsCnsInd == kInvalidSlot) {
1536 return nullptr;
1538 if (m_constants[clsCnsInd].isAbstract()) {
1539 return nullptr;
1541 if (what == ClsCnsLookup::NoTypes && m_constants[clsCnsInd].isType()) {
1542 return nullptr;
1544 auto const ret = &m_constants[clsCnsInd].val;
1545 assertx(m_constants[clsCnsInd].isType() || tvIsPlausible(*ret));
1546 return ret;
1549 Slot Class::clsCnsSlot(
1550 const StringData* name, bool wantTypeCns, bool allowAbstract
1551 ) const {
1552 auto slot = m_constants.findIndex(name);
1553 if (slot == kInvalidSlot) return slot;
1554 if (!allowAbstract && m_constants[slot].isAbstract()) return kInvalidSlot;
1555 return m_constants[slot].isType() == wantTypeCns ? slot : kInvalidSlot;
1558 DataType Class::clsCnsType(const StringData* cnsName) const {
1559 Slot slot;
1560 auto const cns = cnsNameToTV(cnsName, slot);
1561 // TODO(#2913342): lookup the constant in RDS in case it's dynamic
1562 // and already initialized.
1563 if (!cns) return KindOfUninit;
1564 return cns->m_type;
1567 ///////////////////////////////////////////////////////////////////////////////
1568 // Other methods.
1570 bool Class::verifyPersistent() const {
1571 if (!(attrs() & AttrPersistent)) return false;
1572 if (m_parent.get() && !classHasPersistentRDS(m_parent.get())) {
1573 return false;
1575 int numIfaces = m_interfaces.size();
1576 for (int i = 0; i < numIfaces; i++) {
1577 if (!classHasPersistentRDS(m_interfaces[i])) {
1578 return false;
1581 for (auto const& usedTrait : m_extra->m_usedTraits) {
1582 if (!classHasPersistentRDS(usedTrait.get())) {
1583 return false;
1586 return true;
1589 void Class::setInstanceBits() {
1590 setInstanceBitsImpl<false>();
1592 void Class::setInstanceBitsAndParents() {
1593 setInstanceBitsImpl<true>();
1596 template<bool setParents>
1597 void Class::setInstanceBitsImpl() {
1598 // Bit 0 is reserved to indicate whether or not the rest of the bits
1599 // are initialized yet.
1600 if (m_instanceBits.test(0)) return;
1602 InstanceBits::BitSet bits;
1603 bits.set(0);
1604 auto setBits = [&](Class* c) {
1605 if (setParents) c->setInstanceBitsAndParents();
1606 bits |= c->m_instanceBits;
1608 if (m_parent.get()) setBits(m_parent.get());
1610 int numIfaces = m_interfaces.size();
1611 for (int i = 0; i < numIfaces; i++) setBits(m_interfaces[i]);
1613 // XXX: this assert fails on the initFlag; oops.
1614 if (unsigned bit = InstanceBits::lookup(m_preClass->name())) {
1615 bits.set(bit);
1617 m_instanceBits = bits;
1620 namespace {
1621 const ReifiedGenericsInfo k_defaultReifiedGenericsInfo{0, false, 0, {}};
1622 } // namespace
1624 const ReifiedGenericsInfo& Class::getReifiedGenericsInfo() const {
1625 if (!m_hasReifiedGenerics) return k_defaultReifiedGenericsInfo;
1626 assertx(m_extra);
1627 return m_extra.raw()->m_reifiedGenericsInfo;
1630 ///////////////////////////////////////////////////////////////////////////////
1631 // Private methods.
1633 // These are mostly for the class creation path.
1635 void Class::setParent() {
1636 // Cache m_preClass->attrs()
1637 m_attrCopy = m_preClass->attrs();
1639 // Validate the parent
1640 if (m_parent.get() != nullptr) {
1641 Attr parentAttrs = m_parent->attrs();
1642 if (UNLIKELY(parentAttrs &
1643 (AttrFinal | AttrInterface | AttrTrait | AttrEnum))) {
1644 if (!(parentAttrs & AttrFinal) ||
1645 (parentAttrs & AttrEnum) ||
1646 m_preClass->userAttributes().find(s___MockClass.get()) ==
1647 m_preClass->userAttributes().end() ||
1648 m_parent->isCollectionClass()) {
1649 raise_error("Class %s may not inherit from %s (%s)",
1650 m_preClass->name()->data(),
1651 ((parentAttrs & AttrEnum) ? "enum" :
1652 (parentAttrs & AttrFinal) ? "final class" :
1653 (parentAttrs & AttrInterface) ? "interface" : "trait"),
1654 m_parent->name()->data());
1656 if ((parentAttrs & AttrAbstract) &&
1657 ((m_attrCopy & (AttrAbstract|AttrFinal)) != (AttrAbstract|AttrFinal))) {
1658 raise_error(
1659 "Class %s with %s inheriting 'abstract final' class %s"
1660 " must also be 'abstract final'",
1661 m_preClass->name()->data(),
1662 s___MockClass.data(),
1663 m_parent->name()->data()
1667 if ((m_attrCopy & AttrIsConst) && !(parentAttrs & AttrIsConst)) {
1668 raise_error(
1669 "Const class %s cannot extend non-const parent %s",
1670 m_preClass->name()->data(),
1671 m_parent->name()->data()
1674 m_preClass->enforceInMaybeSealedParentWhitelist(m_parent->preClass());
1675 if (m_parent->m_maybeRedefsPropTy) m_maybeRedefsPropTy = true;
1678 // Handle stuff specific to cppext classes
1679 if (m_parent.get() && m_parent->m_extra->m_instanceCtor) {
1680 allocExtraData();
1681 m_extra.raw()->m_instanceCtor = m_parent->m_extra->m_instanceCtor;
1682 m_extra.raw()->m_instanceCtorUnlocked =
1683 m_parent->m_extra->m_instanceCtorUnlocked;
1684 m_extra.raw()->m_instanceDtor = m_parent->m_extra->m_instanceDtor;
1685 assertx(m_parent->m_releaseFunc == m_parent->m_extra->m_instanceDtor);
1686 m_releaseFunc = m_parent->m_extra->m_instanceDtor;
1687 // XXX: should this be copying over the clsInfo also? Might be broken...
1691 static Func* findSpecialMethod(Class* cls, const StringData* name) {
1692 if (!cls->preClass()->hasMethod(name)) return nullptr;
1693 Func* f = cls->preClass()->lookupMethod(name);
1694 f = f->clone(cls);
1695 f->setNewFuncId();
1696 f->setBaseCls(cls);
1697 f->setHasPrivateAncestor(false);
1698 return f;
1701 const StaticString
1702 s_toString("__toString"),
1703 s_construct("__construct"),
1704 s_invoke("__invoke"),
1705 s_sleep("__sleep"),
1706 s_debugInfo("__debugInfo"),
1707 s_clone("__clone");
1709 void Class::setSpecial() {
1710 m_toString = lookupMethod(s_toString.get());
1713 * The invoke method is only cached in the Class for a fast path JIT
1714 * translation. If someone defines a weird __invoke (e.g. as a
1715 * static method), we don't bother caching it here so the translated
1716 * code won't have to check for that case.
1718 * Note that AttrStatic on a closure's __invoke Func* means it is a
1719 * static closure---but the call to __invoke still works as if it
1720 * were a non-static method call---so they are excluded from that
1721 * here. (The closure prologue uninstalls the $this and installs
1722 * the appropriate static context.)
1724 m_invoke = lookupMethod(s_invoke.get());
1725 if (m_invoke && m_invoke->isStaticInPrologue()) {
1726 m_invoke = nullptr;
1729 // Look for __construct() declared in either this class or a trait
1730 auto const func = lookupMethod(s_construct.get());
1731 if (func && func->cls() == this) {
1732 if (func->takesInOutParams()) {
1733 raise_error("Parameters may not be marked inout on constructors");
1736 m_ctor = func;
1737 return;
1740 // Look for parent constructor.
1741 if (m_parent.get() != nullptr && m_parent->m_ctor) {
1742 m_ctor = m_parent->m_ctor;
1743 return;
1746 if (UNLIKELY(!SystemLib::s_nullCtor)) {
1747 SystemLib::setupNullCtor(this);
1750 m_ctor = SystemLib::s_nullCtor;
1753 namespace {
1755 inline void raiseIncompat(const PreClass* implementor,
1756 const Func* imeth) {
1757 const char* name = imeth->name()->data();
1758 raise_error("Declaration of %s::%s() must be compatible with "
1759 "that of %s::%s()",
1760 implementor->name()->data(), name,
1761 imeth->cls()->preClass()->name()->data(), name);
1764 inline void checkRefCompat(const char* kind, const Func* self,
1765 const Func* inherit) {
1766 // Shadowing is okay, if we inherit a private method we can't access it
1767 // anyway.
1768 if (inherit->attrs() & AttrPrivate) return;
1770 if (!self->takesInOutParams() && !inherit->takesInOutParams()) return;
1772 auto const max = std::max(
1773 self->numNonVariadicParams(),
1774 inherit->numNonVariadicParams()
1777 for (int i = 0; i < max; ++i) {
1778 // Since we're looking at ref wrappers of inout functions we need to check
1779 // inout, but if one of the functions isn't a wrapper we do actually have
1780 // a mismatch.
1781 auto const smode = self->isInOut(i);
1782 auto const imode = inherit->isInOut(i);
1783 if (smode != imode) {
1784 auto const msg = [&] {
1785 auto const sname = self->fullName()->data();
1786 auto const iname = inherit->fullName()->data();
1788 if (smode) {
1789 auto const idecl =
1790 i >= inherit->numNonVariadicParams() ? "" : "inout ";
1791 return folly::sformat(
1792 "Parameter {} on function {} was declared inout but is not "
1793 "declared {}on {} function {}", i + 1, sname, idecl,
1794 kind, iname);
1795 } else {
1796 auto const sdecl = i >= self->numNonVariadicParams() ? "" : "inout ";
1797 return folly::sformat(
1798 "Parameter {} on function {} was not declared {}but is "
1799 "declared inout on {} function {}", i + 1, sname, sdecl,
1800 kind, iname);
1802 }();
1803 raise_error(msg);
1808 // Check compatibility vs interface and abstract declarations
1809 void checkDeclarationCompat(const PreClass* preClass,
1810 const Func* func, const Func* imeth) {
1811 const Func::ParamInfoVec& params = func->params();
1812 const Func::ParamInfoVec& iparams = imeth->params();
1814 auto const ivariadic = imeth->hasVariadicCaptureParam();
1815 if (ivariadic && !func->hasVariadicCaptureParam()) {
1816 raiseIncompat(preClass, imeth);
1819 // Verify that meth has at least as many parameters as imeth.
1820 if (func->numParams() < imeth->numParams()) {
1821 // This check doesn't require special casing for variadics, because
1822 // it's not ok to turn a variadic function into a non-variadic.
1823 raiseIncompat(preClass, imeth);
1825 // Verify that the typehints for meth's parameters are compatible with
1826 // imeth's corresponding parameter typehints.
1827 size_t firstOptional = 0;
1829 size_t i = 0;
1830 for (; i < imeth->numNonVariadicParams(); ++i) {
1831 auto const& p = params[i];
1832 if (p.isVariadic()) { raiseIncompat(preClass, imeth); }
1833 if (!iparams[i].hasDefaultValue()) {
1834 // The leftmost of imeth's contiguous trailing optional parameters
1835 // must start somewhere to the right of this parameter (which may
1836 // be the variadic param)
1837 firstOptional = i + 1;
1840 assertx(!ivariadic || iparams[iparams.size() - 1].isVariadic());
1841 assertx(!ivariadic || params[params.size() - 1].isVariadic());
1844 // Verify that meth provides defaults, starting with the parameter that
1845 // corresponds to the leftmost of imeth's contiguous trailing optional
1846 // parameters and *not* including any variadic last param (variadics
1847 // don't have any default values).
1848 for (unsigned i = firstOptional; i < func->numNonVariadicParams(); ++i) {
1849 if (!params[i].hasDefaultValue()) {
1850 raiseIncompat(preClass, imeth);
1854 checkRefCompat(
1855 imeth->attrs() & AttrAbstract ? "abstract" : "interface",
1856 func,
1857 imeth
1861 } // namespace
1863 Class::Class(PreClass* preClass, Class* parent,
1864 VMCompactVector<ClassPtr>&& usedTraits,
1865 unsigned classVecLen, unsigned funcVecLen)
1866 : m_releaseFunc{ObjectData::release}
1867 , m_preClass(PreClassPtr(preClass))
1868 , m_classVecLen(always_safe_cast<decltype(m_classVecLen)>(classVecLen))
1869 , m_funcVecLen(always_safe_cast<decltype(m_funcVecLen)>(funcVecLen))
1870 , m_maybeRedefsPropTy{false}
1871 , m_selfMaybeRedefsPropTy{false}
1872 , m_needsPropInitialCheck{false}
1873 , m_hasReifiedGenerics{false}
1874 , m_hasReifiedParent{false}
1875 , m_serialized(false)
1876 , m_parent(parent)
1877 #ifndef NDEBUG
1878 , m_magic{kMagic}
1879 #endif
1881 if (usedTraits.size()) {
1882 allocExtraData();
1883 m_extra.raw()->m_usedTraits = std::move(usedTraits);
1885 setParent();
1886 setMethods();
1887 setSpecial(); // must run before setRTAttributes
1888 setRTAttributes();
1889 setInterfaces();
1890 setConstants();
1891 setProperties(); // must run before setInitializers
1892 setReifiedData();
1893 setInitializers();
1894 setClassVec();
1895 setRequirements();
1896 setNativeDataInfo();
1897 initClosure();
1898 setEnumType();
1899 setInstanceMemoCacheInfo();
1900 setLSBMemoCacheInfo();
1902 // A class is allowed to implement two interfaces that share the same slot if
1903 // we'll fatal trying to define that class, so this has to happen after all
1904 // of those fatals could be thrown.
1905 setInterfaceVtables();
1907 // Calculates the base pointer offset and
1908 // the MemoryManager index of the class size.
1909 setReleaseData();
1912 void Class::methodOverrideCheck(const Func* parentMethod, const Func* method) {
1913 // Skip special methods
1914 if (method->isGenerated()) return;
1916 if ((parentMethod->attrs() & AttrFinal)) {
1917 if (m_preClass->userAttributes().find(s___MockClass.get()) ==
1918 m_preClass->userAttributes().end()) {
1919 raise_error("Cannot override final method %s::%s()",
1920 m_parent->name()->data(), parentMethod->name()->data());
1924 if ((method->attrs() & AttrAbstract) &&
1925 !(parentMethod->attrs() & AttrAbstract) &&
1926 !method->isFromTrait()) {
1927 raise_error("Cannot re-declare non-abstract method %s::%s() abstract in "
1928 "class %s",
1929 m_parent->m_preClass->name()->data(),
1930 parentMethod->name()->data(), m_preClass->name()->data());
1933 if ((method->attrs() & (AttrPublic | AttrProtected | AttrPrivate)) >
1934 (parentMethod->attrs() & (AttrPublic | AttrProtected | AttrPrivate))) {
1935 raise_error(
1936 "Access level to %s::%s() must be %s (as in class %s) or weaker",
1937 m_preClass->name()->data(), method->name()->data(),
1938 attrToVisibilityStr(parentMethod->attrs()),
1939 m_parent->name()->data());
1942 if ((method->attrs() & AttrStatic) != (parentMethod->attrs() & AttrStatic)) {
1943 raise_error("Cannot change %sstatic method %s::%s() to %sstatic in %s",
1944 (parentMethod->attrs() & AttrStatic) ? "" : "non-",
1945 parentMethod->baseCls()->name()->data(),
1946 method->name()->data(),
1947 (method->attrs() & AttrStatic) ? "" : "non-",
1948 m_preClass->name()->data());
1951 Func* baseMethod = parentMethod->baseCls()->lookupMethod(method->name());
1952 if (!(method->attrs() & AttrAbstract) &&
1953 (baseMethod->attrs() & AttrAbstract)) {
1954 checkDeclarationCompat(m_preClass.get(), method, baseMethod);
1955 } else {
1956 checkRefCompat("parent", method, parentMethod);
1960 void Class::setMethods() {
1961 MethodMapBuilder builder;
1963 ITRACE(5, "----------\nsetMethods() for {}:\n", this->name()->data());
1964 if (m_parent.get() != nullptr) {
1965 // Copy down the parent's method entries. These may be overridden below.
1966 for (Slot i = 0; i < m_parent->m_methods.size(); ++i) {
1967 Func* f = m_parent->getMethod(i);
1968 assertx(f);
1969 ITRACE(5, " - adding parent method {}\n", f->name()->data());
1970 assertx(builder.size() == i);
1971 builder.add(f->name(), f);
1975 static_assert(AttrPublic < AttrProtected && AttrProtected < AttrPrivate, "");
1976 // Overlay/append this class's public/protected methods onto/to those of the
1977 // parent.
1978 for (size_t methI = 0; methI < m_preClass->numMethods(); ++methI) {
1979 Func* method = m_preClass->methods()[methI];
1980 ITRACE(5, " - processing pre-class method {}\n", method->name()->data());
1981 if (Func::isSpecial(method->name())) {
1982 if (method->name() == s_86sinit.get() ||
1983 method->name() == s_86pinit.get()) {
1985 * we could also skip the cinit function here, but
1986 * that would mean storing it somewhere else.
1988 continue;
1992 MethodMapBuilder::iterator it2 = builder.find(method->name());
1993 if (it2 != builder.end()) {
1994 Func* parentMethod = builder[it2->second];
1995 // We should never have null func pointers to deal with
1996 assertx(parentMethod);
1997 // An abstract method that came from a trait doesn't override another
1998 // method.
1999 if (method->isFromTrait() && (method->attrs() & AttrAbstract)) continue;
2000 methodOverrideCheck(parentMethod, method);
2001 // Overlay.
2002 Func* f = method->clone(this);
2003 f->setNewFuncId();
2004 Class* baseClass;
2005 assertx(!(f->attrs() & AttrPrivate) ||
2006 (parentMethod->attrs() & AttrPrivate));
2007 if ((parentMethod->attrs() & AttrPrivate) || (f->attrs() & AttrPrivate)) {
2008 baseClass = this;
2009 } else {
2010 baseClass = parentMethod->baseCls();
2012 f->setBaseCls(baseClass);
2013 f->setHasPrivateAncestor(
2014 parentMethod->hasPrivateAncestor() ||
2015 (parentMethod->attrs() & AttrPrivate));
2016 builder[it2->second] = f;
2017 } else {
2018 // This is the first class that declares the method
2019 Class* baseClass = this;
2020 // Append.
2021 Func* f = method->clone(this);
2022 f->setNewFuncId();
2023 f->setBaseCls(baseClass);
2024 f->setHasPrivateAncestor(false);
2025 builder.add(method->name(), f);
2029 // Add 86reifiedinit to base classes that do not have AttrNoReifiedInit set
2030 if (m_parent.get() == nullptr && !(attrs() & AttrNoReifiedInit)) {
2031 assertx(builder.find(s_86reifiedinit.get()) == builder.end());
2032 auto f = SystemLib::getNull86reifiedinit(this);
2033 builder.add(f->name(), f);
2036 auto const traitsBeginIdx = builder.size();
2037 if (m_extra->m_usedTraits.size()) {
2038 importTraitMethods(builder);
2040 auto const traitsEndIdx = builder.size();
2042 if (m_extra) {
2043 m_extra.raw()->m_traitsBeginIdx = traitsBeginIdx;
2044 m_extra.raw()->m_traitsEndIdx = traitsEndIdx;
2047 // If class is not abstract, check that all abstract methods have been defined
2048 if (!(attrs() & (AttrTrait | AttrInterface | AttrAbstract))) {
2049 for (Slot i = 0; i < builder.size(); i++) {
2050 const Func* meth = builder[i];
2051 if (meth->attrs() & AttrAbstract) {
2052 raise_error("Class %s contains abstract method (%s) and "
2053 "must therefore be declared abstract or implement "
2054 "the remaining methods", m_preClass->name()->data(),
2055 meth->name()->data());
2060 // If class is abstract final, its static methods should not be abstract
2061 if ((attrs() & (AttrAbstract | AttrFinal)) == (AttrAbstract | AttrFinal)) {
2062 for (Slot i = 0; i < builder.size(); i++) {
2063 const Func* meth = builder[i];
2064 if ((meth->attrs() & (AttrAbstract | AttrStatic))
2065 == (AttrAbstract | AttrStatic)) {
2066 raise_error(
2067 "Class %s contains abstract static method (%s) and "
2068 "therefore cannot be declared 'abstract final'",
2069 m_preClass->name()->data(), meth->name()->data());
2074 builder.create(m_methods);
2075 for (Slot i = 0; i < builder.size(); ++i) {
2076 builder[i]->setMethodSlot(i);
2078 setFuncVec(builder);
2082 * Initialize m_RTAttrs by inspecting the class methods and parents.
2084 void Class::setRTAttributes() {
2085 m_RTAttrs = 0;
2086 if (lookupMethod(s_sleep.get())) { m_RTAttrs |= Class::HasSleep; }
2087 if (lookupMethod(s_clone.get())) { m_RTAttrs |= Class::HasClone; }
2089 if ((isBuiltin() && Native::getNativePropHandler(name())) ||
2090 (m_parent && m_parent->hasNativePropHandler())) {
2091 m_RTAttrs |= Class::HasNativePropHandler;
2095 void Class::setConstants() {
2096 ConstMap::Builder builder;
2098 if (m_parent.get() != nullptr) {
2099 for (Slot i = 0; i < m_parent->m_constants.size(); ++i) {
2100 // Copy parent's constants.
2101 builder.add(m_parent->m_constants[i].name, m_parent->m_constants[i]);
2105 // Copy in interface constants.
2106 for (int i = 0, size = m_interfaces.size(); i < size; ++i) {
2107 const Class* iface = m_interfaces[i];
2109 for (Slot slot = 0; slot < iface->m_constants.size(); ++slot) {
2110 auto const iConst = iface->m_constants[slot];
2112 // If you're inheriting a constant with the same name as an existing
2113 // one, they must originate from the same place, unless the constant
2114 // was defined as abstract.
2115 auto const existing = builder.find(iConst.name);
2117 if (existing == builder.end()) {
2118 builder.add(iConst.name, iConst);
2119 continue;
2121 auto& existingConst = builder[existing->second];
2123 if (iConst.isType() != existingConst.isType()) {
2124 raise_error("%s cannot inherit the %sconstant %s from %s, because it "
2125 "was previously inherited as a %sconstant from %s",
2126 m_preClass->name()->data(),
2127 iConst.isType() ? "type " : "",
2128 iConst.name->data(),
2129 iConst.cls->name()->data(),
2130 iConst.isType() ? "" : "type ",
2131 existingConst.cls->name()->data());
2134 if (iConst.isAbstract()) {
2135 continue;
2138 if (existingConst.isAbstract()) {
2139 existingConst.cls = iConst.cls;
2140 existingConst.val = iConst.val;
2141 continue;
2144 if (existingConst.cls != iConst.cls) {
2145 // It's only an error if the constant comes from the declared
2146 // interfaces.
2147 for (auto const& interface : m_declInterfaces) {
2148 if (interface.get() == iface) {
2149 raise_error("%s cannot inherit the %sconstant %s from %s, because "
2150 "it was previously inherited from %s",
2151 m_preClass->name()->data(),
2152 iConst.isType() ? "type " : "",
2153 iConst.name->data(),
2154 iConst.cls->name()->data(),
2155 existingConst.cls->name()->data());
2162 for (Slot i = 0, sz = m_preClass->numConstants(); i < sz; ++i) {
2163 const PreClass::Const* preConst = &m_preClass->constants()[i];
2164 ConstMap::Builder::iterator it2 = builder.find(preConst->name());
2165 if (it2 != builder.end()) {
2166 auto definingClass = builder[it2->second].cls;
2167 // Forbid redefining constants from interfaces, but not superclasses.
2168 // Constants from interfaces implemented by superclasses can be
2169 // overridden.
2170 if (definingClass->attrs() & AttrInterface) {
2171 for (auto interface : m_declInterfaces) {
2172 if (interface->hasConstant(preConst->name()) ||
2173 interface->hasTypeConstant(preConst->name())) {
2174 raise_error("Cannot override previously defined %sconstant "
2175 "%s::%s in %s",
2176 builder[it2->second].isType() ? "type " : "",
2177 builder[it2->second].cls->name()->data(),
2178 preConst->name()->data(),
2179 m_preClass->name()->data());
2184 if (preConst->isAbstract() &&
2185 !builder[it2->second].isAbstract()) {
2186 raise_error("Cannot re-declare as abstract previously defined "
2187 "%sconstant %s::%s in %s",
2188 builder[it2->second].isType() ? "type " : "",
2189 builder[it2->second].cls->name()->data(),
2190 preConst->name()->data(),
2191 m_preClass->name()->data());
2194 if (preConst->isType() != builder[it2->second].isType()) {
2195 raise_error("Cannot re-declare as a %sconstant previously defined "
2196 "%sconstant %s::%s in %s",
2197 preConst->isType() ? "type " : "",
2198 preConst->isType() ? "" : "type ",
2199 builder[it2->second].cls->name()->data(),
2200 preConst->name()->data(),
2201 m_preClass->name()->data());
2203 builder[it2->second].cls = this;
2204 builder[it2->second].val = preConst->val();
2205 } else {
2206 // Append constant.
2207 Const constant;
2208 constant.cls = this;
2209 constant.name = preConst->name();
2210 constant.val = preConst->val();
2211 builder.add(preConst->name(), constant);
2215 // If class is not abstract, all abstract constants should have been
2216 // defined
2217 if (!(attrs() & (AttrTrait | AttrInterface | AttrAbstract))) {
2218 for (Slot i = 0; i < builder.size(); i++) {
2219 const Const& constant = builder[i];
2220 if (constant.isAbstract()) {
2221 raise_error("Class %s contains abstract %sconstant (%s) and "
2222 "must therefore be declared abstract or define "
2223 "the remaining constants",
2224 m_preClass->name()->data(),
2225 constant.isType() ? "type " : "",
2226 constant.name->data());
2231 // If class is abstract final, its constants should not be abstract
2232 else if (
2233 (attrs() & (AttrAbstract | AttrFinal)) == (AttrAbstract | AttrFinal)) {
2234 for (Slot i = 0; i < builder.size(); i++) {
2235 const Const& constant = builder[i];
2236 if (constant.isAbstract()) {
2237 raise_error(
2238 "Class %s contains abstract %sconstant (%s) and "
2239 "therefore cannot be declared 'abstract final'",
2240 m_preClass->name()->data(),
2241 constant.isType() ? "type " : "",
2242 constant.name->data());
2248 // For type constants, we have to use the value from the PreClass of the
2249 // declaring class, because the parent class or interface we got it from may
2250 // have replaced it with a resolved value.
2251 for (auto& pair : builder) {
2252 auto& cns = builder[pair.second];
2253 if (cns.isType()) {
2254 auto& preConsts = cns.cls->preClass()->constantsMap();
2255 auto const idx = preConsts.findIndex(cns.name.get());
2256 assertx(idx != -1);
2257 cns.val = preConsts[idx].val();
2259 #ifndef USE_LOWPTR
2260 cns.pointedClsName = nullptr;
2261 #endif
2264 m_constants.create(builder);
2267 namespace {
2269 void copyDeepInitAttr(const PreClass::Prop* pclsProp, Class::Prop* clsProp) {
2270 if (pclsProp->attrs() & AttrDeepInit) {
2271 clsProp->attrs = (Attr)(clsProp->attrs | AttrDeepInit);
2272 } else {
2273 clsProp->attrs = (Attr)(clsProp->attrs & ~AttrDeepInit);
2278 * KeyFn should be a function that takes an index and returns a key to sort by.
2279 * To sort lexicographically by multiple fields, the key can be a tuple type.
2281 template<typename KeyFn>
2282 void sortByKey(std::vector<uint32_t>& indices, KeyFn keyFn) {
2283 std::sort(indices.begin(), indices.end(), [&](uint32_t a, uint32_t b) {
2284 return keyFn(a) < keyFn(b);
2290 void Class::sortOwnProps(const PropMap::Builder& curPropMap,
2291 uint32_t first,
2292 uint32_t past,
2293 std::vector<uint16_t>& slotIndex) {
2294 auto const serverMode = RuntimeOption::ServerExecutionMode();
2295 FTRACE(3, "Class::sortOwnProps: PreClass: {}\n", m_preClass->name()->data());
2296 if (serverMode && RuntimeOption::ServerLogReorderProps) {
2297 Logger::FInfo("Class::sortOwnProps: PreClass: {}",
2298 m_preClass->name()->data());
2300 auto const size = past - first;
2301 if (size == 0) return; // no own props
2302 std::vector<uint32_t> order(size);
2303 for (uint32_t i = 0; i < size; i++) {
2304 order[i] = first + i;
2306 // We don't change the order of the properties for closures.
2307 if (c_Closure::initialized() && parent() != c_Closure::classof()) {
2308 if (RuntimeOption::EvalReorderProps == "hotness") {
2309 /* Sort the properties in decreasing order of their profile counts,
2310 * according to the serialized JIT profile data. In case of ties, we
2311 * preserve the logical order among the properties. Note that, in the
2312 * absence of profile data (e.g. if jumpstart failed to deserialize the
2313 * JIT profile data), then the physical layout of the properties in memory
2314 * will match their logical order. */
2315 sortByKey(order, [&](uint32_t index) {
2316 auto const& prop = curPropMap[index];
2317 int64_t count = PropertyProfile::getCount(prop.cls->name(), prop.name);
2318 return std::make_tuple(-count, index);
2320 } else if (RuntimeOption::EvalReorderProps == "alphabetical") {
2321 std::sort(order.begin(), order.end(),
2322 [&] (uint32_t a, uint32_t b) {
2323 auto const& propa = curPropMap[a];
2324 auto const& propb = curPropMap[b];
2325 return strcmp(propa.name->data(), propb.name->data()) < 0;
2327 } else if (RuntimeOption::EvalReorderProps == "countedness") {
2328 // Countable properties come earlier. Break ties by logical order.
2329 sortByKey(order, [&](uint32_t index) {
2330 auto const& prop = curPropMap[index];
2331 int64_t countable = jit::irgen::propertyMayBeCountable(prop);
2332 return std::make_tuple(-countable, index);
2334 } else if (RuntimeOption::EvalReorderProps == "countedness-hotness") {
2335 // Countable properties come earlier. Break ties by profile counts
2336 // (assuming that we have them from jumpstart), then by logical order.
2337 sortByKey(order, [&](uint32_t index) {
2338 auto const& prop = curPropMap[index];
2339 int64_t count = PropertyProfile::getCount(prop.cls->name(), prop.name);
2340 int64_t countable = jit::irgen::propertyMayBeCountable(prop);
2341 return std::make_tuple(-countable, -count, index);
2345 assertx(slotIndex.size() == past);
2346 for (uint32_t i = 0; i < size; i++) {
2347 auto slot = order[i];
2348 auto index = first + i;
2349 slotIndex[slot] = index;
2350 auto const& prop = curPropMap[slot];
2351 FTRACE(
2352 3, " index={}: slot={}, prop={}, count={}, countable={}\n",
2353 index, slot, prop.name->data(),
2354 PropertyProfile::getCount(prop.cls->name(), prop.name),
2355 jit::irgen::propertyMayBeCountable(prop)
2357 if (serverMode && RuntimeOption::ServerLogReorderProps) {
2358 Logger::FInfo(
2359 " index={}: slot={}, prop={}, count={}, countable={}",
2360 index, slot, prop.name->data(),
2361 PropertyProfile::getCount(prop.cls->name(), prop.name),
2362 jit::irgen::propertyMayBeCountable(prop)
2366 sortOwnPropsInitVec(first, past, slotIndex);
2369 void Class::sortOwnPropsInitVec(uint32_t first, uint32_t past,
2370 const std::vector<uint16_t>& slotIndex) {
2371 auto const size = past - first;
2372 std::vector<TypedValue> tmpPropInitVec(size);
2373 std::vector<bool> tmpDeepInit(size);
2374 for (uint32_t i = first; i < past; i++) {
2375 tvCopy(m_declPropInit[i].val.tv(), tmpPropInitVec[i - first]);
2376 tmpDeepInit[i - first] = m_declPropInit[i].deepInit;
2378 for (uint32_t slot = first; slot < past; slot++) {
2379 auto index = slotIndex[slot];
2380 FTRACE(3, " - placing propInit for slot {} at index {}\n", slot, index);
2381 tvCopy(tmpPropInitVec[slot - first], m_declPropInit[index].val);
2382 m_declPropInit[index].deepInit = tmpDeepInit[slot - first];
2386 void Class::setProperties() {
2387 int numInaccessible = 0;
2388 PropMap::Builder curPropMap;
2389 SPropMap::Builder curSPropMap;
2390 m_hasDeepInitProps = false;
2391 std::vector<uint16_t> slotIndex;
2393 if (m_parent.get() != nullptr) {
2394 // m_hasDeepInitProps indicates if there are properties that require
2395 // deep initialization. Note there are cases where m_hasDeepInitProps is
2396 // true but none of the properties require deep initialization; this can
2397 // happen if a derived class redeclares a public or protected property
2398 // from an ancestor class. We still get correct behavior in these cases,
2399 // so it works out okay.
2400 m_hasDeepInitProps = m_parent->m_hasDeepInitProps;
2401 for (auto const& parentProp : m_parent->declProperties()) {
2402 // Copy parent's declared property. Protected properties may be
2403 // weakened to public below, but otherwise, the parent's properties
2404 // will stay the same for this class.
2405 Prop prop;
2406 prop.preProp = parentProp.preProp;
2407 prop.cls = parentProp.cls;
2408 prop.baseCls = parentProp.baseCls;
2409 prop.mangledName = parentProp.mangledName;
2410 prop.attrs = parentProp.attrs | AttrNoBadRedeclare;
2411 prop.typeConstraint = parentProp.typeConstraint;
2412 prop.ubs = parentProp.ubs;
2413 prop.name = parentProp.name;
2414 prop.repoAuthType = parentProp.repoAuthType;
2416 if (!(parentProp.attrs & AttrPrivate)) {
2417 curPropMap.add(prop.name, prop);
2418 } else {
2419 ++numInaccessible;
2420 curPropMap.addUnnamed(prop);
2423 m_declPropInit = m_parent->m_declPropInit;
2424 m_needsPropInitialCheck = m_parent->m_needsPropInitialCheck;
2425 auto& parentSlotIndex = m_parent->m_slotIndex;
2426 slotIndex.insert(slotIndex.end(),
2427 parentSlotIndex.begin(), parentSlotIndex.end());
2428 for (auto const& parentProp : m_parent->staticProperties()) {
2429 if ((parentProp.attrs & AttrPrivate) &&
2430 !(parentProp.attrs & AttrLSB)) continue;
2432 // Alias parent's static property.
2433 SProp sProp;
2434 sProp.preProp = parentProp.preProp;
2435 sProp.name = parentProp.name;
2436 sProp.attrs = parentProp.attrs | AttrNoBadRedeclare;
2437 sProp.typeConstraint = parentProp.typeConstraint;
2438 sProp.ubs = parentProp.ubs;
2439 sProp.cls = parentProp.cls;
2440 sProp.repoAuthType = parentProp.repoAuthType;
2441 tvWriteUninit(sProp.val);
2442 curSPropMap.add(sProp.name, sProp);
2446 Slot traitIdx = m_preClass->numProperties();
2447 if (m_preClass->attrs() & AttrNoExpandTrait) {
2448 while (traitIdx &&
2449 (m_preClass->properties()[traitIdx - 1].attrs() & AttrTrait)) {
2450 traitIdx--;
2454 Slot serializationIdx = 0;
2455 std::vector<bool> serializationVisited(curPropMap.size(), false);
2456 Slot staticSerializationIdx = 0;
2457 std::vector<bool> staticSerializationVisited(curSPropMap.size(), false);
2459 static_assert(AttrPublic < AttrProtected && AttrProtected < AttrPrivate, "");
2460 auto const firstOwnPropSlot = curPropMap.size();
2462 for (Slot slot = 0; slot < traitIdx; ++slot) {
2463 const PreClass::Prop* preProp = &m_preClass->properties()[slot];
2465 if (!(preProp->attrs() & AttrStatic)) {
2466 // Overlay/append this class's protected and public properties onto/to
2467 // those of the parent, and append this class's private properties.
2468 // Append order doesn't matter here (unlike in setMethods()).
2469 // Prohibit static-->non-static redeclaration.
2470 SPropMap::Builder::iterator it5 = curSPropMap.find(preProp->name());
2471 if (it5 != curSPropMap.end()) {
2472 raise_error("Cannot redeclare static %s::$%s as non-static %s::$%s",
2473 curSPropMap[it5->second].cls->name()->data(),
2474 preProp->name()->data(), m_preClass->name()->data(),
2475 preProp->name()->data());
2477 // Get parent's equivalent property, if one exists.
2478 const Prop* parentProp = nullptr;
2479 if (m_parent.get() != nullptr) {
2480 Slot id = m_parent->m_declProperties.findIndex(preProp->name());
2481 if (id != kInvalidSlot) {
2482 parentProp = &m_parent->m_declProperties[id];
2485 // Prohibit strengthening.
2486 if (parentProp
2487 && (preProp->attrs() & VisibilityAttrs)
2488 > (parentProp->attrs & VisibilityAttrs)) {
2489 raise_error(
2490 "Access level to %s::$%s must be %s (as in class %s) or weaker",
2491 m_preClass->name()->data(), preProp->name()->data(),
2492 attrToVisibilityStr(parentProp->attrs),
2493 m_parent->name()->data());
2495 if (preProp->attrs() & AttrDeepInit) {
2496 m_hasDeepInitProps = true;
2499 auto addNewProp = [&] {
2500 Prop prop;
2501 initProp(prop, preProp);
2503 curPropMap.add(preProp->name(), prop);
2504 curPropMap[serializationIdx++].serializationIdx = curPropMap.size() - 1;
2505 serializationVisited.push_back(true);
2506 m_declPropInit.push_back(preProp->val());
2507 slotIndex.push_back(slotIndex.size());
2510 auto const lateInitCheck = [&] (const Class::Prop& prop) {
2511 if ((prop.attrs ^ preProp->attrs()) & AttrLateInit) {
2512 raise_error(
2513 "Property %s::$%s must %sbe <<__LateInit>> (as in class %s)",
2514 m_preClass->name()->data(),
2515 preProp->name()->data(),
2516 prop.attrs & AttrLateInit ? "" : "not ",
2517 m_parent->name()->data()
2522 auto const redeclareProp = [&] (const Slot slot) {
2523 // For duplicate property name, add the slot # defined in curPropMap,
2524 // and mark the property visited.
2525 assertx(serializationVisited.size() > slot);
2526 if (!serializationVisited[slot]) {
2527 curPropMap[serializationIdx++].serializationIdx = slot;
2528 serializationVisited[slot] = true;
2531 auto& prop = curPropMap[slot];
2532 assertx((preProp->attrs() & VisibilityAttrs)
2533 <= (prop.attrs & VisibilityAttrs));
2534 assertx(!(prop.attrs & AttrNoImplicitNullable) ||
2535 (preProp->attrs() & AttrNoImplicitNullable));
2536 assertx(prop.attrs & AttrNoBadRedeclare);
2538 if ((preProp->attrs() & AttrIsConst) != (prop.attrs & AttrIsConst)) {
2539 raise_error("Cannot redeclare property %s of class %s with different "
2540 "constness in class %s", preProp->name()->data(),
2541 m_parent->name()->data(), m_preClass->name()->data());
2544 lateInitCheck(prop);
2546 prop.preProp = preProp;
2547 prop.cls = this;
2548 if ((preProp->attrs() & VisibilityAttrs)
2549 < (prop.attrs & VisibilityAttrs)) {
2550 assertx((prop.attrs & VisibilityAttrs) == AttrProtected);
2551 assertx((preProp->attrs() & VisibilityAttrs) == AttrPublic);
2552 // Weaken protected property to public.
2553 prop.mangledName = preProp->mangledName();
2554 prop.attrs = Attr(prop.attrs ^ (AttrProtected|AttrPublic));
2557 auto const& tc = preProp->typeConstraint();
2558 if (RuntimeOption::EvalCheckPropTypeHints > 0 &&
2559 !(preProp->attrs() & AttrNoBadRedeclare) &&
2560 (tc.maybeInequivalentForProp(prop.typeConstraint) ||
2561 !preProp->upperBounds().empty() ||
2562 !prop.ubs.empty())) {
2563 // If this property isn't obviously not redeclaring a property in
2564 // the parent, we need to check that when we initialize the class.
2565 prop.attrs = Attr(prop.attrs & ~AttrNoBadRedeclare);
2566 m_selfMaybeRedefsPropTy = true;
2567 m_maybeRedefsPropTy = true;
2569 prop.typeConstraint = tc;
2571 prop.ubs = preProp->upperBounds();
2573 if (preProp->attrs() & AttrNoImplicitNullable) {
2574 prop.attrs |= AttrNoImplicitNullable;
2576 attrSetter(prop.attrs,
2577 preProp->attrs() & AttrSystemInitialValue,
2578 AttrSystemInitialValue);
2579 if (preProp->attrs() & AttrInitialSatisfiesTC) {
2580 prop.attrs |= AttrInitialSatisfiesTC;
2583 checkPrePropVal(prop, preProp);
2584 auto index = slotIndex[slot];
2586 tvCopy(preProp->val(), m_declPropInit[index].val);
2587 copyDeepInitAttr(preProp, &prop);
2590 switch (preProp->attrs() & VisibilityAttrs) {
2591 case AttrPrivate: {
2592 addNewProp();
2593 break;
2595 case AttrProtected: {
2596 // Check whether a superclass has already declared this protected
2597 // property.
2598 PropMap::Builder::iterator it2 = curPropMap.find(preProp->name());
2599 if (it2 == curPropMap.end()) {
2600 addNewProp();
2601 } else {
2602 redeclareProp(it2->second);
2604 break;
2606 case AttrPublic: {
2607 // Check whether a superclass has already declared this as a
2608 // protected/public property.
2609 auto it2 = curPropMap.find(preProp->name());
2610 if (it2 == curPropMap.end()) {
2611 addNewProp();
2612 } else {
2613 redeclareProp(it2->second);
2615 break;
2617 default: always_assert(false);
2619 } else { // Static property.
2620 // Prohibit non-static-->static redeclaration.
2621 auto const it2 = curPropMap.find(preProp->name());
2622 if (it2 != curPropMap.end()) {
2623 auto& prop = curPropMap[it2->second];
2624 raise_error("Cannot redeclare non-static %s::$%s as static %s::$%s",
2625 prop.cls->name()->data(),
2626 preProp->name()->data(),
2627 m_preClass->name()->data(),
2628 preProp->name()->data());
2630 // Get parent's equivalent property, if one exists.
2631 auto const it3 = curSPropMap.find(preProp->name());
2632 Slot sPropInd = kInvalidSlot;
2633 // Prohibit strengthening.
2634 if (it3 != curSPropMap.end()) {
2635 const SProp& parentSProp = curSPropMap[it3->second];
2636 if ((preProp->attrs() & VisibilityAttrs)
2637 > (parentSProp.attrs & VisibilityAttrs)) {
2638 raise_error(
2639 "Access level to %s::$%s must be %s (as in class %s) or weaker",
2640 m_preClass->name()->data(), preProp->name()->data(),
2641 attrToVisibilityStr(parentSProp.attrs),
2642 m_parent->name()->data());
2644 // Prohibit overlaying LSB static properties.
2645 if (parentSProp.attrs & AttrLSB) {
2646 raise_error(
2647 "Cannot redeclare LSB static %s::%s as %s::%s",
2648 parentSProp.cls->name()->data(),
2649 preProp->name()->data(),
2650 m_preClass->name()->data(),
2651 preProp->name()->data());
2653 sPropInd = it3->second;
2656 if (sPropInd == kInvalidSlot) {
2657 SProp sProp;
2658 initProp(sProp, preProp);
2659 curSPropMap.add(sProp.name, sProp);
2661 curSPropMap[staticSerializationIdx++].serializationIdx =
2662 curSPropMap.size() - 1;
2663 staticSerializationVisited.push_back(true);
2664 } else {
2665 // Overlay ancestor's property.
2666 auto& sProp = curSPropMap[sPropInd];
2667 initProp(sProp, preProp);
2669 assertx(staticSerializationVisited.size() > sPropInd);
2670 if (!staticSerializationVisited[sPropInd]) {
2671 staticSerializationVisited[sPropInd] = true;
2672 curSPropMap[staticSerializationIdx++].serializationIdx = sPropInd;
2678 importTraitProps(traitIdx, curPropMap, curSPropMap, slotIndex,
2679 serializationIdx, serializationVisited,
2680 staticSerializationIdx, staticSerializationVisited);
2682 auto const pastOwnPropSlot = curPropMap.size();
2683 sortOwnProps(curPropMap, firstOwnPropSlot, pastOwnPropSlot, slotIndex);
2684 assertx(slotIndex.size() == curPropMap.size());
2686 // set serialization index for parent properties at the end
2687 if (m_parent.get() != nullptr) {
2688 for (Slot i = 0; i < m_parent->declProperties().size(); ++i) {
2689 // For non-static properties, slot is always equal to parentSlot
2690 Slot slot = m_parent->declProperties()[i].serializationIdx;
2691 assertx(serializationVisited.size() > slot);
2692 if (!serializationVisited[slot]) {
2693 curPropMap[serializationIdx++].serializationIdx = slot;
2697 for (Slot i = 0; i < m_parent->staticProperties().size(); ++i) {
2698 Slot parentSlot = m_parent->staticProperties()[i].serializationIdx;
2699 auto const& prop = m_parent->staticProperties()[parentSlot];
2701 if ((prop.attrs & AttrPrivate) && !(prop.attrs & AttrLSB)) continue;
2703 auto it = curSPropMap.find(prop.name);
2704 assertx(it != curSPropMap.end());
2706 Slot slot = it->second;
2707 assertx(staticSerializationVisited.size() > slot);
2708 if (!staticSerializationVisited[slot]) {
2709 curSPropMap[staticSerializationIdx++].serializationIdx = slot;
2713 assertx(serializationIdx == curPropMap.size());
2714 assertx(staticSerializationIdx == curSPropMap.size());
2717 // LSB static properties that were inherited must be initialized separately.
2718 for (Slot slot = 0; slot < curSPropMap.size(); ++slot) {
2719 auto& sProp = curSPropMap[slot];
2720 if ((sProp.attrs & AttrLSB) && sProp.cls != this) {
2721 auto const& prevSProps = sProp.cls->m_staticProperties;
2722 auto prevInd = prevSProps.findIndex(sProp.name);
2723 assertx(prevInd != kInvalidSlot);
2724 sProp.val = prevSProps[prevInd].val;
2728 m_declProperties.create(curPropMap);
2729 m_staticProperties.create(curSPropMap);
2731 std::vector<ObjectProps::quick_index> slotQuickIndex;
2732 slotQuickIndex.reserve(slotIndex.size());
2733 for (auto i : slotIndex) {
2734 slotQuickIndex.push_back(ObjectProps::quickIndex(i));
2736 m_slotIndex = slotQuickIndex;
2738 if (unsigned n = numStaticProperties()) {
2739 using LinkT = std::remove_pointer<decltype(m_sPropCache)>::type;
2740 m_sPropCache = static_cast<LinkT*>(vm_malloc(n * sizeof(LinkT)));
2741 for (unsigned i = 0; i < n; ++i) {
2742 new (&m_sPropCache[i]) LinkT;
2746 m_declPropNumAccessible = m_declProperties.size() - numInaccessible;
2748 // To speed up ObjectData::release, we only iterate over property indices
2749 // up to the last countable property index. Here, "index" refers to the
2750 // position of the property in memory, and "slot" to its logical slot.
2751 uint16_t countablePropsEnd = 0;
2752 for (Slot slot = 0; slot < m_declProperties.size(); ++slot) {
2753 if (jit::irgen::propertyMayBeCountable(m_declProperties[slot])) {
2754 auto const index =
2755 safe_cast<uint16_t>(propSlotToIndex(slot) + uint16_t{1});
2756 countablePropsEnd = std::max(countablePropsEnd, index);
2760 assertx(m_declProperties.size() <= ObjectProps::max_index + 1);
2761 assertx(countablePropsEnd <= ObjectProps::max_index);
2763 m_countablePropsEnd = ObjectProps::quickIndex(countablePropsEnd);
2764 FTRACE(3, "numDeclProperties = {}\n", m_declProperties.size());
2765 FTRACE(3, "countablePropsEnd = {}\n", countablePropsEnd);
2768 bool Class::compatibleTraitPropInit(const TypedValue& tv1,
2769 const TypedValue& tv2) {
2770 if (tv1.m_type != tv2.m_type) return false;
2772 switch (tv1.m_type) {
2773 case KindOfNull:
2774 return true;
2776 case KindOfBoolean:
2777 case KindOfInt64:
2778 case KindOfDouble:
2779 case KindOfPersistentString:
2780 case KindOfString:
2781 return same(tvAsCVarRef(&tv1), tvAsCVarRef(&tv2));
2783 case KindOfUninit:
2784 case KindOfPersistentVec:
2785 case KindOfVec:
2786 case KindOfPersistentDict:
2787 case KindOfDict:
2788 case KindOfPersistentKeyset:
2789 case KindOfKeyset:
2790 case KindOfPersistentDArray:
2791 case KindOfDArray:
2792 case KindOfPersistentVArray:
2793 case KindOfVArray:
2794 case KindOfObject:
2795 case KindOfResource:
2796 case KindOfRFunc:
2797 case KindOfFunc:
2798 case KindOfClass:
2799 case KindOfLazyClass:
2800 case KindOfClsMeth:
2801 case KindOfRClsMeth:
2802 case KindOfRecord:
2803 return false;
2805 not_reached();
2808 namespace {
2810 const constexpr Attr kRedeclarePropAttrMask =
2811 Attr(
2812 ~(AttrNoBadRedeclare |
2813 AttrSystemInitialValue |
2814 AttrNoImplicitNullable |
2815 AttrInitialSatisfiesTC |
2816 AttrPersistent)
2821 void Class::importTraitInstanceProp(Prop& traitProp,
2822 TypedValue traitPropVal,
2823 PropMap::Builder& curPropMap,
2824 SPropMap::Builder& curSPropMap,
2825 std::vector<uint16_t>& slotIndex,
2826 Slot& serializationIdx,
2827 std::vector<bool>& serializationVisited) {
2828 // Check if prop already declared as static
2829 if (curSPropMap.find(traitProp.name) != curSPropMap.end()) {
2830 raise_error("trait declaration of property '%s' is incompatible with "
2831 "previous declaration", traitProp.name->data());
2834 if ((attrs() & AttrIsConst) && !(traitProp.attrs & AttrIsConst)) {
2835 raise_error("trait's non-const declaration of property '%s' is "
2836 "incompatible with a const class", traitProp.name->data());
2839 auto prevIt = curPropMap.find(traitProp.name);
2841 if (prevIt == curPropMap.end()) {
2842 // New prop, go ahead and add it
2843 auto prop = traitProp;
2844 prop.baseCls = this;
2845 // private props' mangled names contain the class name, so regenerate them
2846 if (prop.attrs & AttrPrivate) {
2847 prop.mangledName = PreClass::manglePropName(m_preClass->name(),
2848 prop.name,
2849 prop.attrs);
2851 if (prop.attrs & AttrDeepInit) {
2852 m_hasDeepInitProps = true;
2854 if (traitProp.cls != this) {
2855 // this was a non-flattened trait property.
2856 prop.cls = this;
2858 // Clear NoImplicitNullable on the property. HHBBC analyzed the property
2859 // in the context of the trait, not this class, so we cannot predict what
2860 // derived class' will do with it. Be conservative.
2861 prop.attrs = Attr(
2862 prop.attrs & ~AttrNoImplicitNullable) | AttrNoBadRedeclare;
2863 } else {
2864 assertx(prop.attrs & AttrNoBadRedeclare);
2867 curPropMap.add(prop.name, prop);
2868 curPropMap[serializationIdx++].serializationIdx = curPropMap.size() - 1;
2869 serializationVisited.push_back(true);
2870 slotIndex.push_back(slotIndex.size());
2871 m_declPropInit.push_back(traitPropVal);
2872 } else {
2873 // Redeclared prop, make sure it matches previous declarations
2874 auto& prevProp = curPropMap[prevIt->second];
2875 auto prevPropIndex = slotIndex[prevIt->second];
2876 auto const prevPropVal = m_declPropInit[prevPropIndex].val.tv();
2877 if (((prevProp.attrs ^ traitProp.attrs) & kRedeclarePropAttrMask) ||
2878 (!(prevProp.attrs & AttrSystemInitialValue) &&
2879 !(traitProp.attrs & AttrSystemInitialValue) &&
2880 !compatibleTraitPropInit(prevPropVal, traitPropVal))) {
2881 raise_error("trait declaration of property '%s' is incompatible with "
2882 "previous declaration", traitProp.name->data());
2885 assertx(serializationVisited.size() > prevIt->second);
2886 if (!serializationVisited[prevIt->second]) {
2887 curPropMap[serializationIdx++].serializationIdx = prevIt->second;
2888 serializationVisited[prevIt->second] = true;
2893 void Class::importTraitStaticProp(
2894 SProp& traitProp,
2895 PropMap::Builder& curPropMap,
2896 SPropMap::Builder& curSPropMap,
2897 Slot& staticSerializationIdx,
2898 std::vector<bool>& staticSerializationVisited) {
2899 // Check if prop already declared as non-static
2900 if (curPropMap.find(traitProp.name) != curPropMap.end()) {
2901 raise_error("trait declaration of property '%s' is incompatible with "
2902 "previous declaration", traitProp.name->data());
2905 auto prevIt = curSPropMap.find(traitProp.name);
2906 if (prevIt == curSPropMap.end()) {
2907 // New prop, go ahead and add it
2908 auto prop = traitProp;
2909 prop.cls = this; // set current class as the first declaring prop
2910 prop.attrs |= AttrNoBadRedeclare;
2912 curSPropMap.add(prop.name, prop);
2913 curSPropMap[staticSerializationIdx++].serializationIdx =
2914 curSPropMap.size() - 1;
2915 staticSerializationVisited.push_back(true);
2916 } else {
2917 // Redeclared prop, make sure it matches previous declaration
2918 auto& prevProp = curSPropMap[prevIt->second];
2919 TypedValue prevPropVal;
2920 if (prevProp.attrs & AttrLSB) {
2921 raise_error("trait declaration of property '%s' would redeclare "
2922 "LSB static", traitProp.name->data());
2924 if (prevProp.cls == this) {
2925 // If this static property was declared by this class, we can get the
2926 // initial value directly from its value.
2927 prevPropVal = prevProp.val;
2928 } else {
2929 // If this static property was declared in a parent class, its value will
2930 // be KindOfUninit, and we'll need to consult the appropriate parent class
2931 // to get the initial value.
2932 auto const& prevSProps = prevProp.cls->m_staticProperties;
2934 auto prevPropInd = prevSProps.findIndex(prevProp.name);
2935 assertx(prevPropInd != kInvalidSlot);
2937 prevPropVal = prevSProps[prevPropInd].val;
2939 if (((prevProp.attrs ^ traitProp.attrs) & kRedeclarePropAttrMask) ||
2940 (!(prevProp.attrs & AttrSystemInitialValue) &&
2941 !(traitProp.attrs & AttrSystemInitialValue) &&
2942 !compatibleTraitPropInit(traitProp.val, prevPropVal))) {
2943 raise_error("trait declaration of property '%s' is incompatible with "
2944 "previous declaration", traitProp.name->data());
2946 prevProp.cls = this;
2947 prevProp.val = prevPropVal;
2949 assertx(staticSerializationVisited.size() > prevIt->second);
2950 if (!staticSerializationVisited[prevIt->second]) {
2951 curSPropMap[staticSerializationIdx++].serializationIdx = prevIt->second;
2952 staticSerializationVisited[prevIt->second] = true;
2957 template<typename XProp>
2958 void Class::checkPrePropVal(XProp& prop, const PreClass::Prop* preProp) {
2959 auto const& tv = preProp->val();
2960 auto const& tc = preProp->typeConstraint();
2962 assertx(
2963 !(preProp->attrs() & AttrSystemInitialValue) ||
2964 tv.m_type != KindOfNull ||
2965 !(preProp->attrs() & AttrNoImplicitNullable)
2968 auto const alwaysPassesAll = [&] {
2969 if (!tc.alwaysPasses(&tv)) return false;
2970 if (RuntimeOption::EvalEnforceGenericsUB > 0) {
2971 auto& ubs = const_cast<PreClass::UpperBoundVec&>(preProp->upperBounds());
2972 for (auto& ub : ubs) {
2973 if (!ub.alwaysPasses(&tv)) return false;
2976 return true;
2979 if (RuntimeOption::EvalCheckPropTypeHints > 0 &&
2980 !(preProp->attrs() & AttrInitialSatisfiesTC) &&
2981 tv.m_type != KindOfUninit) {
2982 // System provided initial values should always be correct
2983 if ((preProp->attrs() & AttrSystemInitialValue) || alwaysPassesAll()) {
2984 prop.attrs |= AttrInitialSatisfiesTC;
2985 } else if (preProp->attrs() & AttrStatic) {
2986 prop.attrs = Attr(prop.attrs & ~AttrPersistent);
2987 } else {
2988 m_needsPropInitialCheck = true;
2993 template<typename XProp>
2994 void Class::initProp(XProp& prop, const PreClass::Prop* preProp) {
2995 prop.preProp = preProp;
2996 prop.name = preProp->name();
2997 prop.attrs = Attr(preProp->attrs() & ~AttrTrait) |
2998 AttrNoBadRedeclare;
2999 // This is the first class to declare this property
3000 prop.cls = this;
3001 prop.typeConstraint = preProp->typeConstraint();
3002 prop.ubs = preProp->upperBounds();
3003 prop.repoAuthType = preProp->repoAuthType();
3005 // If type constraint has soft or nullable flags, apply them to upper-bounds.
3006 for (auto &ub : prop.ubs) applyFlagsToUB(ub, prop.typeConstraint);
3007 // Check if this property's initial value needs to be type checked at
3008 // runtime.
3009 checkPrePropVal(prop, preProp);
3012 void Class::initProp(Prop& prop, const PreClass::Prop* preProp) {
3013 initProp<Prop>(prop, preProp);
3014 prop.mangledName = preProp->mangledName();
3015 prop.baseCls = this;
3018 void Class::initProp(SProp& prop, const PreClass::Prop* preProp) {
3019 initProp<SProp>(prop, preProp);
3020 prop.val = preProp->val();
3023 void Class::importTraitProps(int traitIdx,
3024 PropMap::Builder& curPropMap,
3025 SPropMap::Builder& curSPropMap,
3026 std::vector<uint16_t>& slotIndex,
3027 Slot& serializationIdx,
3028 std::vector<bool>& serializationVisited,
3029 Slot& staticSerializationIdx,
3030 std::vector<bool>& staticSerializationVisited) {
3031 if (attrs() & AttrNoExpandTrait) {
3032 for (Slot p = traitIdx; p < m_preClass->numProperties(); p++) {
3033 auto const* preProp = &m_preClass->properties()[p];
3034 assertx(preProp->attrs() & AttrTrait);
3035 if (!(preProp->attrs() & AttrStatic)) {
3036 Prop prop;
3037 initProp(prop, preProp);
3038 importTraitInstanceProp(prop, preProp->val(),
3039 curPropMap, curSPropMap, slotIndex,
3040 serializationIdx, serializationVisited);
3041 } else {
3042 SProp prop;
3043 initProp(prop, preProp);
3044 importTraitStaticProp(
3045 prop, curPropMap, curSPropMap,
3046 staticSerializationIdx, staticSerializationVisited);
3049 return;
3052 for (auto const& t : m_extra->m_usedTraits) {
3053 auto trait = t.get();
3055 m_needsPropInitialCheck |= trait->m_needsPropInitialCheck;
3057 // instance properties
3058 for (Slot p = 0; p < trait->m_declProperties.size(); p++) {
3059 auto& traitProp = trait->m_declProperties[p];
3060 auto traitPropIndex = trait->propSlotToIndex(p);
3061 auto traitPropVal = trait->m_declPropInit[traitPropIndex].val.tv();
3062 importTraitInstanceProp(traitProp, traitPropVal,
3063 curPropMap, curSPropMap, slotIndex,
3064 serializationIdx, serializationVisited);
3067 // static properties
3068 for (Slot p = 0; p < trait->m_staticProperties.size(); ++p) {
3069 auto& traitProp = trait->m_staticProperties[p];
3070 importTraitStaticProp(
3071 traitProp, curPropMap, curSPropMap,
3072 staticSerializationIdx, staticSerializationVisited);
3077 void Class::addTraitPropInitializers(std::vector<const Func*>& thisInitVec,
3078 Attr which) {
3079 if (attrs() & AttrNoExpandTrait) return;
3080 auto getInitVec = [&](Class* trait) -> auto& {
3081 switch (which) {
3082 case AttrNone: return trait->m_pinitVec;
3083 case AttrStatic: return trait->m_sinitVec;
3084 case AttrLSB: return trait->m_linitVec;
3085 default: always_assert(false);
3088 for (auto const& t : m_extra->m_usedTraits) {
3089 Class* trait = t.get();
3090 auto& traitInitVec = getInitVec(trait);
3091 // Insert trait's 86[psl]init into the current class, avoiding repetitions.
3092 for (unsigned m = 0; m < traitInitVec.size(); m++) {
3093 // Clone 86[psl]init methods, and set the class to the current class.
3094 // This allows 86[psl]init to determine the property offset for the
3095 // initializer array corectly.
3096 Func *f = traitInitVec[m]->clone(this);
3097 f->setNewFuncId();
3098 f->setBaseCls(this);
3099 f->setHasPrivateAncestor(false);
3100 thisInitVec.push_back(f);
3105 void Class::setReifiedData() {
3106 auto const ua = m_preClass->userAttributes();
3107 auto const it = ua.find(s___Reified.get());
3108 if (it != ua.end()) m_hasReifiedGenerics = true;
3109 if (m_parent.get()) {
3110 m_hasReifiedParent =
3111 m_parent->m_hasReifiedGenerics || m_parent->m_hasReifiedParent;
3113 if (m_hasReifiedGenerics) {
3114 auto tv = it->second;
3115 assertx(tvIsHAMSafeVArray(tv));
3116 allocExtraData();
3117 m_extra.raw()->m_reifiedGenericsInfo =
3118 extractSizeAndPosFromReifiedAttribute(tv.m_data.parr);
3122 namespace {
3123 const StaticString s_Error("Error");
3124 const StaticString s_Exception("Exception");
3127 void Class::setInitializers() {
3128 std::vector<const Func*> pinits;
3129 std::vector<const Func*> sinits;
3130 std::vector<const Func*> linits;
3132 if (m_parent.get() != nullptr) {
3133 // Copy parent's 86pinit() vector, so that the 86pinit() methods can be
3134 // called in reverse order without any search/recursion during
3135 // initialization.
3136 pinits.assign(m_parent->m_pinitVec.begin(), m_parent->m_pinitVec.end());
3138 // Copy parent's 86linit into the current class, and set class to the
3139 // current class.
3140 for (const auto& linit : m_parent->m_linitVec) {
3141 Func *f = linit->clone(this);
3142 f->setNewFuncId();
3143 f->setBaseCls(this);
3144 f->setHasPrivateAncestor(false);
3145 linits.push_back(f);
3149 // Clone 86pinit methods from traits
3150 addTraitPropInitializers(pinits, AttrNone);
3152 // If this class has an 86pinit method, append it last so that
3153 // reverse iteration of the vector runs this class's 86pinit
3154 // first, in case multiple classes in the hierarchy initialize
3155 // the same property.
3156 const Func* meth86pinit = findSpecialMethod(this, s_86pinit.get());
3157 if (meth86pinit != nullptr) {
3158 pinits.push_back(meth86pinit);
3161 // If this class has an 86sinit method, it must be the last element
3162 // in the vector. See get86sinit().
3163 addTraitPropInitializers(sinits, AttrStatic);
3164 const Func* sinit = findSpecialMethod(this, s_86sinit.get());
3165 if (sinit) {
3166 sinits.push_back(sinit);
3169 addTraitPropInitializers(linits, AttrLSB);
3170 const Func* meth86linit = findSpecialMethod(this, s_86linit.get());
3171 if (meth86linit != nullptr) {
3172 linits.push_back(meth86linit);
3175 m_pinitVec = pinits;
3176 m_sinitVec = sinits;
3177 m_linitVec = linits;
3179 m_needInitialization =
3180 (m_pinitVec.size() > 0 ||
3181 m_staticProperties.size() > 0 ||
3182 m_maybeRedefsPropTy ||
3183 m_needsPropInitialCheck);
3185 if (m_maybeRedefsPropTy || m_needsPropInitialCheck) allocExtraData();
3187 // Implementations of Throwable get special treatment.
3188 if (m_parent.get() != nullptr) {
3189 m_needsInitThrowable = m_parent->needsInitThrowable();
3190 } else {
3191 m_needsInitThrowable = name()->same(s_Exception.get()) ||
3192 name()->same(s_Error.get());
3196 const StaticString s_HH_Iterator("HH\\Iterator");
3197 const StaticString s_IteratorAggregate("IteratorAggregate");
3198 void Class::checkInterfaceConstraints() {
3199 if (UNLIKELY(m_interfaces.contains(s_HH_Iterator.get()) &&
3200 m_interfaces.contains(s_IteratorAggregate.get()))) {
3201 raise_error("Class %s cannot implement both IteratorAggregate and Iterator"
3202 " at the same time", name()->data());
3206 // Checks if interface methods are OK:
3207 // - there's no requirement if this is a trait, interface, or abstract class
3208 // - a non-abstract class must implement all methods from interfaces it
3209 // declares to implement (either directly or indirectly), arity must be
3210 // compatible (at least as many parameters, additional parameters must have
3211 // defaults), and typehints must be compatible
3212 void Class::checkInterfaceMethods() {
3213 for (int i = 0, size = m_interfaces.size(); i < size; i++) {
3214 const Class* iface = m_interfaces[i];
3216 for (size_t m = 0; m < iface->m_methods.size(); m++) {
3217 Func* imeth = iface->getMethod(m);
3218 const StringData* methName = imeth->name();
3220 // Skip special methods
3221 if (Func::isSpecial(methName)) continue;
3223 Func* meth = lookupMethod(methName);
3225 if (attrs() & (AttrTrait | AttrInterface | AttrAbstract)) {
3226 if (meth == nullptr) {
3227 // Skip unimplemented method.
3228 continue;
3230 } else {
3231 // Verify that method is not abstract within concrete class.
3232 if (meth == nullptr || (meth->attrs() & AttrAbstract)) {
3233 raise_error("Class %s contains abstract method (%s) and "
3234 "must therefore be declared abstract or implement "
3235 "the remaining methods", name()->data(),
3236 methName->data());
3239 bool ifaceStaticMethod = imeth->attrs() & AttrStatic;
3240 bool classStaticMethod = meth->attrs() & AttrStatic;
3241 if (classStaticMethod != ifaceStaticMethod) {
3242 raise_error("Cannot make %sstatic method %s::%s() %sstatic "
3243 "in class %s",
3244 ifaceStaticMethod ? "" : "non-",
3245 iface->m_preClass->name()->data(), methName->data(),
3246 classStaticMethod ? "" : "non-",
3247 m_preClass->name()->data());
3249 if ((imeth->attrs() & AttrPublic) &&
3250 !(meth->attrs() & AttrPublic)) {
3251 raise_error("Access level to %s::%s() must be public "
3252 "(as in interface %s)", m_preClass->name()->data(),
3253 methName->data(), iface->m_preClass->name()->data());
3255 checkDeclarationCompat(m_preClass.get(), meth, imeth);
3261 * Look up the interfaces implemented by traits used by the class, and add them
3262 * to the provided builder.
3264 void Class::addInterfacesFromUsedTraits(InterfaceMap::Builder& builder) const {
3266 for (auto const& traitName : m_preClass->usedTraits()) {
3267 auto const trait = Unit::lookupClass(traitName);
3268 assertx(trait->attrs() & AttrTrait);
3269 int numIfcs = trait->m_interfaces.size();
3271 for (int i = 0; i < numIfcs; i++) {
3272 auto interface = trait->m_interfaces[i];
3273 if (!builder.contains(interface->name())) {
3274 builder.add(interface->name(), interface);
3280 const StaticString s_Stringish("Stringish");
3281 const StaticString s_XHPChild("XHPChild");
3283 void Class::setInterfaces() {
3284 InterfaceMap::Builder interfacesBuilder;
3285 if (m_parent.get() != nullptr) {
3286 int size = m_parent->m_interfaces.size();
3287 for (int i = 0; i < size; i++) {
3288 auto interface = m_parent->m_interfaces[i];
3289 interfacesBuilder.add(interface->name(), interface);
3293 std::vector<ClassPtr> declInterfaces;
3295 for (auto it = m_preClass->interfaces().begin();
3296 it != m_preClass->interfaces().end(); ++it) {
3297 auto cp = Unit::loadClass(*it);
3298 if (cp == nullptr) {
3299 raise_error("Undefined interface: %s", (*it)->data());
3301 if (!(cp->attrs() & AttrInterface)) {
3302 raise_error("%s cannot implement %s - it is not an interface",
3303 m_preClass->name()->data(), cp->name()->data());
3305 m_preClass->enforceInMaybeSealedParentWhitelist(cp->preClass());
3306 declInterfaces.push_back(ClassPtr(cp));
3307 if (!interfacesBuilder.contains(cp->name())) {
3308 interfacesBuilder.add(cp->name(), LowPtr<Class>(cp));
3310 int size = cp->m_interfaces.size();
3311 for (int i = 0; i < size; i++) {
3312 auto interface = cp->m_interfaces[i];
3313 if (!interfacesBuilder.contains(interface->name())) {
3314 interfacesBuilder.add(interface->name(), interface);
3319 if (auto sz = declInterfaces.size()) {
3320 m_declInterfaces.reserve(sz);
3321 for (auto interface : declInterfaces) {
3322 m_declInterfaces.push_back(interface);
3326 addInterfacesFromUsedTraits(interfacesBuilder);
3328 if (m_toString) {
3329 if (!interfacesBuilder.contains(s_Stringish.get()) &&
3330 (!(attrs() & AttrInterface) ||
3331 !m_preClass->name()->isame(s_Stringish.get()))) {
3332 // Add Stringish
3333 Class* stringish = Unit::lookupClass(s_Stringish.get());
3334 assertx(stringish != nullptr);
3335 assertx((stringish->attrs() & AttrInterface));
3336 interfacesBuilder.add(stringish->name(), LowPtr<Class>(stringish));
3338 if (!m_preClass->name()->isame(s_XHPChild.get()) &&
3339 !interfacesBuilder.contains(s_XHPChild.get())) {
3340 // All Stringish are also XHPChild
3341 Class* xhpChild = Unit::lookupClass(s_XHPChild.get());
3342 assertx(xhpChild != nullptr);
3343 assertx((xhpChild->attrs() & AttrInterface));
3344 interfacesBuilder.add(xhpChild->name(), LowPtr<Class>(xhpChild));
3349 m_interfaces.create(interfacesBuilder);
3350 checkInterfaceConstraints();
3351 checkInterfaceMethods();
3354 void Class::setInterfaceVtables() {
3355 // We only need to set interface vtables for classes that can be instantiated
3356 // and implement more than 0 interfaces.
3357 if (!RuntimeOption::RepoAuthoritative ||
3358 !isNormalClass(this) || isAbstract(this) || m_interfaces.empty()) return;
3360 size_t totalMethods = 0;
3361 Slot maxSlot = 0;
3362 for (auto iface : m_interfaces.range()) {
3363 auto const slot = iface->preClass()->ifaceVtableSlot();
3364 if (slot == kInvalidSlot) continue;
3366 maxSlot = std::max(maxSlot, slot);
3367 totalMethods += iface->numMethods();
3370 const size_t nVtables = maxSlot + 1;
3371 auto const vtableVecSz = nVtables * sizeof(VtableVecSlot);
3372 auto const memSz = vtableVecSz + totalMethods * sizeof(LowPtr<Func>);
3373 auto const mem = static_cast<char*>(low_malloc(memSz));
3374 auto cursor = mem;
3376 ITRACE(3, "Setting interface vtables for class {}. "
3377 "{} interfaces, {} vtable slots, {} total methods\n",
3378 name()->data(), m_interfaces.size(), nVtables, totalMethods);
3379 Trace::Indent indent;
3381 auto const vtableVec = reinterpret_cast<VtableVecSlot*>(cursor);
3382 cursor += vtableVecSz;
3383 m_vtableVecLen = always_safe_cast<decltype(m_vtableVecLen)>(nVtables);
3384 m_vtableVec = vtableVec;
3385 memset(vtableVec, 0, vtableVecSz);
3387 for (auto iface : m_interfaces.range()) {
3388 auto const slot = iface->preClass()->ifaceVtableSlot();
3389 if (slot == kInvalidSlot) continue;
3390 ITRACE(3, "{} @ slot {}\n", iface->name()->data(), slot);
3391 Trace::Indent indent2;
3392 always_assert(slot < nVtables);
3394 auto const nMethods = iface->numMethods();
3395 auto const vtable = reinterpret_cast<LowPtr<Func>*>(cursor);
3396 cursor += nMethods * sizeof(LowPtr<Func>);
3397 if (vtableVec[slot].vtable != nullptr) {
3398 raise_error("Static analysis failure: "
3399 "%s was expected to fatal at runtime, but didn't",
3400 m_preClass->name()->data());
3402 vtableVec[slot].vtable = vtable;
3403 vtableVec[slot].iface = iface;
3405 for (size_t i = 0; i < nMethods; ++i) {
3406 auto ifunc = iface->getMethod(i);
3407 auto func = lookupMethod(ifunc->name());
3408 ITRACE(3, "{}:{} @ slot {}\n", ifunc->name()->data(), func, i);
3409 always_assert(func || Func::isSpecial(ifunc->name()));
3410 vtable[i] = func;
3414 always_assert(cursor == mem + memSz);
3417 void Class::setRequirements() {
3418 RequirementMap::Builder reqBuilder;
3420 auto addReq = [&](const PreClass::ClassRequirement* req) {
3421 auto it = reqBuilder.find(req->name());
3422 if (it == reqBuilder.end()) {
3423 reqBuilder.add(req->name(), req);
3424 return;
3426 if (!reqBuilder[it->second]->is_same(req)) {
3427 raise_error("Conflicting requirements for '%s'",
3428 req->name()->data());
3432 if (m_parent.get() != nullptr) {
3433 for (auto const& req : m_parent->allRequirements().range()) {
3434 // no need for addReq; parent won't have duplicates
3435 reqBuilder.add(req->name(), req);
3438 for (auto const& iface : m_interfaces.range()) {
3439 for (auto const& req : iface->allRequirements().range()) {
3440 addReq(req);
3443 for (auto const& ut : m_extra->m_usedTraits) {
3444 for (auto const& req : ut->allRequirements().range()) {
3445 addReq(req);
3449 if (attrs() & AttrInterface) {
3450 // Check that requirements are semantically valid
3451 // We don't require the class to exist yet, to avoid creating
3452 // pointless circular dependencies, but if it does, we check
3453 // that it's the right kind.
3454 for (auto const& req : m_preClass->requirements()) {
3455 assertx(req.is_extends());
3456 auto const reqName = req.name();
3457 auto const reqCls = Unit::lookupClass(reqName);
3458 if (reqCls) {
3459 if (reqCls->attrs() & (AttrTrait | AttrInterface | AttrFinal)) {
3460 raise_error("Interface '%s' requires extension of '%s', but %s "
3461 "is not an extendable class",
3462 m_preClass->name()->data(),
3463 reqName->data(),
3464 reqName->data());
3467 addReq(&req);
3469 } else if (attrs() & (AttrTrait | AttrNoExpandTrait)) {
3470 // Check that requirements are semantically valid.
3471 // Note that trait flattening could have migrated
3472 // requirements onto a class's preClass.
3473 for (auto const& req : m_preClass->requirements()) {
3474 auto const reqName = req.name();
3475 if (auto const reqCls = Unit::lookupClass(reqName)) {
3476 if (req.is_extends()) {
3477 if (reqCls->attrs() & (AttrTrait | AttrInterface | AttrFinal)) {
3478 raise_error(Strings::TRAIT_BAD_REQ_EXTENDS,
3479 m_preClass->name()->data(),
3480 reqName->data(),
3481 reqName->data());
3483 } else {
3484 assertx(req.is_implements());
3485 if (!(reqCls->attrs() & AttrInterface)) {
3486 raise_error(Strings::TRAIT_BAD_REQ_IMPLEMENTS,
3487 m_preClass->name()->data(),
3488 reqName->data(),
3489 reqName->data());
3493 addReq(&req);
3497 m_requirements.create(reqBuilder);
3498 checkRequirementConstraints();
3501 void Class::setEnumType() {
3502 if (attrs() & AttrEnum) {
3503 m_enumBaseTy = m_preClass->enumBaseTy().underlyingDataTypeResolved();
3505 // Make sure we've loaded a valid underlying type.
3506 if (m_enumBaseTy &&
3507 !isIntType(*m_enumBaseTy) &&
3508 !isStringType(*m_enumBaseTy)) {
3509 raise_error("Invalid base type for enum %s",
3510 m_preClass->name()->data());
3515 void Class::setLSBMemoCacheInfo() {
3516 boost::container::flat_map<FuncId, Slot> slots;
3517 Slot numSlots = 0;
3519 /* Inherit slots from parent */
3520 if (m_parent && m_parent->m_extra) {
3521 numSlots = m_parent->m_extra->m_lsbMemoExtra.m_numSlots;
3524 /* If any of our methods need slots, insert them at the end */
3525 auto const methodCount = numMethods();
3526 uint64_t assignCount = 0;
3527 for (Slot i = 0; i < methodCount; ++i) {
3528 auto const f = getMethod(i);
3529 if (f->isMemoizeWrapperLSB() && f->cls() == this) {
3530 slots.emplace(f->getFuncId(), numSlots++);
3531 ++assignCount;
3534 always_assert(assignCount == slots.size());
3536 if (numSlots != 0) {
3537 allocExtraData();
3538 auto& mx = m_extra->m_lsbMemoExtra;
3539 mx.m_slots = std::move(slots);
3540 mx.m_numSlots = numSlots;
3541 initLSBMemoHandles();
3545 void Class::initLSBMemoHandles() {
3546 /* Allocate handles array */
3547 auto& mx = m_extra->m_lsbMemoExtra;
3548 auto const numSlots = mx.m_numSlots;
3549 assertx(numSlots > 0);
3550 rds::Handle* handles = static_cast<rds::Handle*>(
3551 vm_malloc(numSlots * sizeof(rds::Handle)));
3553 /* Assign handle to every slot */
3554 uint64_t assignCount = 0;
3555 uint64_t assignSum = 0;
3556 const Class* cls = this;
3557 while (cls != nullptr && cls->m_extra) {
3558 for (const auto& kv : cls->m_extra->m_lsbMemoExtra.m_slots) {
3559 auto const func = Func::fromFuncId(kv.first);
3560 auto const slot = kv.second;
3561 assertx(slot >= 0 && slot < numSlots);
3562 if (func->numParams() == 0) {
3563 handles[slot] = rds::bindLSBMemoValue(this, func).handle();
3564 } else {
3565 handles[slot] = rds::bindLSBMemoCache(this, func).handle();
3567 ++assignCount;
3568 assignSum += slot;
3570 cls = cls->m_parent.get();
3572 /* Sanity check: Make sure we assigned every slot exactly once */
3573 always_assert(numSlots == assignCount);
3574 always_assert(assignSum == ((numSlots - 1) * numSlots) / 2);
3576 assertx(mx.m_handles == nullptr);
3577 mx.m_handles = handles;
3580 void Class::setInstanceMemoCacheInfo() {
3581 // Inherit the data from the parent, if any.
3582 if (m_parent && m_parent->hasMemoSlots()) {
3583 allocExtraData();
3584 m_extra.raw()->m_nextMemoSlot = m_parent->m_extra->m_nextMemoSlot;
3585 m_extra.raw()->m_sharedMemoSlots = m_parent->m_extra->m_sharedMemoSlots;
3588 // If we have memo slots defined already, and this class is adding (or
3589 // changing) native data, forbid it. The parent class won't realize the native
3590 // data has changed size, so code we generate in it to access the slot will be
3591 // incorrect (it won't find the slot in the right place). If we ever have a
3592 // need to actually do this, we'll have to revisit this.
3593 if (m_preClass->nativeDataInfo() && m_extra->m_nextMemoSlot > 0) {
3594 raise_error(
3595 "Class '%s' with existing memoized methods cannot have native-data added",
3596 m_preClass->name()->data()
3600 auto const forEachMeth = [&](auto f) {
3601 auto const methodCount = numMethods();
3602 for (Slot i = 0; i < methodCount; ++i) {
3603 auto const m = getMethod(i);
3604 if (m->isMemoizeWrapper() && !m->isStatic() && m->cls() == this) f(m);
3608 auto const addNonShared = [&](const Func* f) {
3609 allocExtraData();
3610 auto extra = m_extra.raw();
3611 extra->m_memoMappings.emplace(
3612 f->getFuncId(),
3613 std::make_pair(extra->m_nextMemoSlot++, false)
3617 auto const addShared = [&](const Func* f) {
3618 allocExtraData();
3619 auto extra = m_extra.raw();
3621 // There's no point in allocating slots for parameter counts greater than
3622 // kMemoCacheMaxSpecializedKeys, since they'll all be generic caches
3623 // anyways. Cap the count at that.
3624 auto const keyCount =
3625 std::min<size_t>(f->numParams(), kMemoCacheMaxSpecializedKeys + 1);
3626 auto const it = extra->m_sharedMemoSlots.find(keyCount);
3627 if (it != extra->m_sharedMemoSlots.end()) {
3628 extra->m_memoMappings.emplace(
3629 f->getFuncId(),
3630 std::make_pair(it->second, true)
3632 return;
3635 extra->m_sharedMemoSlots.emplace(keyCount, extra->m_nextMemoSlot);
3636 extra->m_memoMappings.emplace(
3637 f->getFuncId(),
3638 std::make_pair(extra->m_nextMemoSlot++, true)
3642 // First count how many methods we have without parameters or with parameters.
3643 size_t methNoKeys = 0;
3644 size_t methWithKeys = 0;
3645 forEachMeth(
3646 [&](const Func* f) {
3647 if (f->numParams() == 0) {
3648 ++methNoKeys;
3649 } else {
3650 ++methWithKeys;
3655 // We only want to assign up to EvalNonSharedInstanceMemoCaches non-shared
3656 // caches. With the remaining non-assigned slots, we give preference to the
3657 // parameter-less methods first. This is because there's a greater benefit to
3658 // giving parameter-less methods non-shared slots (they can just be a
3659 // TypedValue). However, we only give the methods non-shared slots if we can give
3660 // them to all the methods. If there's more methods than we have available
3661 // non-shared slots, its not clear which ones should get the non-shared slots
3662 // and which ones shouldn't, so we treat them all equally and give them all
3663 // shared slots. After processing the parameter-less methods, we apply the
3664 // same logic to the methods with parameters.
3666 // How many non-shared slots do we have left to allocate?
3667 auto slotsLeft =
3668 RuntimeOption::EvalNonSharedInstanceMemoCaches -
3669 std::min(
3670 m_extra->m_nextMemoSlot,
3671 RuntimeOption::EvalNonSharedInstanceMemoCaches
3674 if (methNoKeys > 0) {
3675 forEachMeth(
3676 [&](const Func* f) {
3677 if (f->numParams() == 0) {
3678 (methNoKeys <= slotsLeft) ? addNonShared(f) : addShared(f);
3682 if (methNoKeys <= slotsLeft) slotsLeft -= methNoKeys;
3685 if (methWithKeys > 0) {
3686 forEachMeth(
3687 [&](const Func* f) {
3688 if (f->numParams() > 0) {
3689 (methWithKeys <= slotsLeft) ? addNonShared(f) : addShared(f);
3696 namespace {
3697 ObjectData* throwingInstanceCtor(Class* cls) {
3698 SystemLib::throwExceptionObject(
3699 folly::sformat("Class {} may not be directly instantiated", cls->name())
3702 } // namespace
3704 void Class::setNativeDataInfo() {
3705 for (auto cls = this; cls; cls = cls->parent()) {
3706 if (auto ndi = cls->preClass()->nativeDataInfo()) {
3707 allocExtraData();
3708 m_extra.raw()->m_nativeDataInfo = ndi;
3709 if (ndi->ctor_throws) {
3710 m_extra.raw()->m_instanceCtor = throwingInstanceCtor;
3711 m_extra.raw()->m_instanceCtorUnlocked = throwingInstanceCtor;
3712 } else {
3713 m_extra.raw()->m_instanceCtor = Native::nativeDataInstanceCtor<false>;
3714 m_extra.raw()->m_instanceCtorUnlocked =
3715 Native::nativeDataInstanceCtor<true>;
3717 m_extra.raw()->m_instanceDtor = Native::nativeDataInstanceDtor;
3718 m_releaseFunc = Native::nativeDataInstanceDtor;
3719 m_RTAttrs |= ndi->rt_attrs;
3720 break;
3725 void Class::initClosure() {
3726 if (!isBuiltin() || !m_preClass->name()->equal(s_Closure.get())) return;
3728 c_Closure::cls_Closure = this;
3729 assertx(!hasMemoSlots());
3730 allocExtraData();
3731 auto& extraData = *m_extra.raw();
3732 extraData.m_instanceCtor = c_Closure::instanceCtor;
3733 extraData.m_instanceCtorUnlocked = c_Closure::instanceCtor;
3734 extraData.m_instanceDtor = c_Closure::instanceDtor;
3735 m_releaseFunc = c_Closure::instanceDtor;
3738 bool Class::hasNativePropHandler() const {
3739 return m_RTAttrs & Class::HasNativePropHandler;
3742 const Native::NativePropHandler* Class::getNativePropHandler() const {
3743 assertx(hasNativePropHandler());
3745 for (auto cls = this; cls; cls = cls->parent()) {
3746 auto propHandler = Native::getNativePropHandler(cls->name());
3747 if (propHandler != nullptr) {
3748 return propHandler;
3752 not_reached();
3755 void Class::raiseUnsatisfiedRequirement(const PreClass::ClassRequirement* req) const {
3756 assertx(!(attrs() & (AttrInterface | AttrTrait)));
3758 auto const reqName = req->name();
3759 if (req->is_implements()) {
3760 // "require implements" is only allowed on traits.
3762 assertx(attrs() & AttrNoExpandTrait ||
3763 (m_extra && m_extra->m_usedTraits.size() > 0));
3764 for (auto const& traitCls : m_extra->m_usedTraits) {
3765 if (traitCls->allRequirements().contains(reqName)) {
3766 raise_error(Strings::TRAIT_REQ_IMPLEMENTS,
3767 m_preClass->name()->data(),
3768 reqName->data(),
3769 traitCls->preClass()->name()->data());
3773 if (attrs() & AttrNoExpandTrait) {
3774 // As a result of trait flattening, the PreClass of this normal class
3775 // contains a requirement. To save space, we don't include the source
3776 // trait in the requirement. For details, see
3777 // ClassScope::importUsedTraits in the compiler.
3778 assertx(!m_extra || m_extra->m_usedTraits.size() == 0);
3779 assertx(m_preClass->requirements().size() > 0);
3780 raise_error(Strings::TRAIT_REQ_IMPLEMENTS,
3781 m_preClass->name()->data(),
3782 reqName->data(),
3783 "<<flattened>>");
3786 always_assert(false); // requirements cannot spontaneously generate
3787 return;
3790 assertx(req->is_extends());
3791 for (auto const& iface : m_interfaces.range()) {
3792 if (iface->allRequirements().contains(reqName)) {
3793 raise_error("Class '%s' required to extend class '%s'"
3794 " by interface '%s'",
3795 m_preClass->name()->data(),
3796 reqName->data(),
3797 iface->preClass()->name()->data());
3801 for (auto const& traitCls : m_extra->m_usedTraits) {
3802 if (traitCls->allRequirements().contains(reqName)) {
3803 raise_error(Strings::TRAIT_REQ_EXTENDS,
3804 m_preClass->name()->data(),
3805 reqName->data(),
3806 traitCls->preClass()->name()->data());
3810 if (attrs() & AttrNoExpandTrait) {
3811 // A result of trait flattening, as with the is_implements case above
3812 assertx(!m_extra || m_extra->m_usedTraits.size() == 0);
3813 assertx(m_preClass->requirements().size() > 0);
3814 raise_error(Strings::TRAIT_REQ_EXTENDS,
3815 m_preClass->name()->data(),
3816 reqName->data(),
3817 "<<flattened>>");
3820 // calls to this method are expected to come as a result of an error due
3821 // to a requirement coming from traits or interfaces
3822 always_assert(false);
3825 void Class::checkRequirementConstraints() const {
3826 if (attrs() & (AttrInterface | AttrTrait)) return;
3828 for (auto const& req : m_requirements.range()) {
3829 auto const reqName = req->name();
3830 if (req->is_implements()) {
3831 if (UNLIKELY(!ifaceofDirect(reqName))) {
3832 raiseUnsatisfiedRequirement(req);
3834 } else {
3835 auto reqExtCls = Unit::lookupClass(reqName);
3836 if (UNLIKELY(
3837 (reqExtCls == nullptr) ||
3838 (reqExtCls->attrs() & (AttrTrait | AttrInterface)))) {
3839 raiseUnsatisfiedRequirement(req);
3842 if (UNLIKELY(!classofNonIFace(reqExtCls))) {
3843 raiseUnsatisfiedRequirement(req);
3849 void Class::setClassVec() {
3850 if (m_classVecLen > 1) {
3851 assertx(m_parent.get() != nullptr);
3852 memcpy(m_classVec, m_parent->m_classVec,
3853 (m_classVecLen-1) * sizeof(m_classVec[0]));
3855 m_classVec[m_classVecLen-1] = this;
3858 void Class::setFuncVec(MethodMapBuilder& builder) {
3859 auto funcVec = this->funcVec();
3861 memset(funcVec, 0, m_funcVecLen * sizeof(LowPtr<Func>));
3863 funcVec = (LowPtr<Func>*)this;
3864 assertx(builder.size() <= m_funcVecLen);
3866 for (Slot i = 0; i < builder.size(); i++) {
3867 assertx(builder[i]->methodSlot() < builder.size());
3868 funcVec[-((int32_t)builder[i]->methodSlot() + 1)] = builder[i];
3872 void Class::setReleaseData() {
3873 if (getNativeDataInfo()) return;
3874 if (hasMemoSlots()) {
3875 m_memoSize = ObjectData::objOffFromMemoNode(this);
3877 auto const nProps = numDeclProperties();
3878 auto const size = m_memoSize + ObjectData::sizeForNProps(nProps);
3879 m_sizeIdx = MemoryManager::size2Index(size);
3882 void Class::getMethodNames(const Class* cls,
3883 const Class* ctx,
3884 Array& out) {
3886 // The order of these methods is so that the first ones win on
3887 // case insensitive name conflicts.
3889 auto const numMethods = cls->numMethods();
3891 for (Slot i = 0; i < numMethods; ++i) {
3892 auto const meth = cls->getMethod(i);
3893 auto const declCls = meth->cls();
3894 auto addMeth = [&]() {
3895 auto const methName = Variant(meth->name(), Variant::PersistentStrInit{});
3896 auto const lowerName = f_strtolower(methName.toString());
3897 if (!out.exists(lowerName)) {
3898 out.set(lowerName, methName);
3902 // Only pick methods declared in this class, in order to match
3903 // Zend's order. Inherited methods will be inserted in the
3904 // recursive call later.
3905 if (declCls != cls) continue;
3907 // Skip generated, internal methods.
3908 if (meth->isGenerated()) continue;
3910 // Public methods are always visible.
3911 if ((meth->attrs() & AttrPublic)) {
3912 addMeth();
3913 continue;
3916 // In anonymous contexts, only public methods are visible.
3917 if (!ctx) continue;
3919 // All methods are visible if the context is the class that
3920 // declared them. If the context is not the declCls, protected
3921 // methods are visible in context classes related the declCls.
3922 if (declCls == ctx ||
3923 ((meth->attrs() & AttrProtected) &&
3924 (ctx->classof(declCls) || declCls->classof(ctx)))) {
3925 addMeth();
3929 // Now add the inherited methods.
3930 if (auto const parent = cls->parent()) {
3931 getMethodNames(parent, ctx, out);
3934 // Add interface methods that the class may not have implemented yet.
3935 for (auto& iface : cls->m_declInterfaces) {
3936 getMethodNames(iface.get(), ctx, out);
3941 ///////////////////////////////////////////////////////////////////////////////
3942 // Trait method import.
3944 namespace {
3946 struct TraitMethod {
3947 TraitMethod(const Class* trait_, const Func* method_, Attr modifiers_)
3948 : trait(trait_)
3949 , method(method_)
3950 , modifiers(modifiers_)
3953 using class_type = const Class*;
3954 using method_type = const Func*;
3956 const Class* trait;
3957 const Func* method;
3958 Attr modifiers;
3961 struct TMIOps {
3962 // Return the name for the trait class.
3963 static const StringData* clsName(const Class* traitCls) {
3964 return traitCls->name();
3967 static const StringData* methName(const Func* method) {
3968 return method->name();
3971 // Is-a methods.
3972 static bool isTrait(const Class* traitCls) {
3973 return traitCls->attrs() & AttrTrait;
3976 static bool isAbstract(Attr modifiers) {
3977 return modifiers & AttrAbstract;
3980 // Whether to exclude methods with name `methName' when adding.
3981 static bool exclude(const StringData* methName) {
3982 return Func::isSpecial(methName);
3985 // TraitMethod constructor.
3986 static TraitMethod traitMethod(const Class* traitCls,
3987 const Func* traitMeth,
3988 const PreClass::TraitAliasRule& rule) {
3989 return TraitMethod { traitCls, traitMeth, rule.modifiers() };
3992 // Register a trait alias once the trait class is found.
3993 static void addTraitAlias(const Class* cls,
3994 const PreClass::TraitAliasRule& rule,
3995 const Class* traitCls) {
3996 PreClass::TraitAliasRule newRule { traitCls->name(),
3997 rule.origMethodName(),
3998 rule.newMethodName(),
3999 rule.modifiers() };
4000 cls->addTraitAlias(newRule);
4003 // Trait class/method finders.
4004 static const Class* findSingleTraitWithMethod(const Class* cls,
4005 const StringData* methName) {
4006 Class* traitCls = nullptr;
4008 for (auto const& t : cls->usedTraitClasses()) {
4009 // Note: m_methods includes methods from parents/traits recursively.
4010 if (t->lookupMethod(methName)) {
4011 if (traitCls != nullptr) {
4012 raise_error("more than one trait contains method '%s'",
4013 methName->data());
4015 traitCls = t.get();
4018 return traitCls;
4021 static const Class* findTraitClass(const Class* cls,
4022 const StringData* traitName) {
4023 auto ret = Unit::loadClass(traitName);
4024 if (!ret) return nullptr;
4025 auto const& usedTraits = cls->preClass()->usedTraits();
4026 if (std::find_if(usedTraits.begin(), usedTraits.end(),
4027 [&] (auto const name) {
4028 return traitName->isame(name);
4029 }) == usedTraits.end()) {
4030 return nullptr;
4032 return ret;
4034 static const Func* findTraitMethod(const Class* traitCls,
4035 const StringData* origMethName) {
4036 return traitCls->lookupMethod(origMethName);
4039 // Errors.
4040 static void errorUnknownMethod(const StringData* methName) {
4041 raise_error(Strings::TRAITS_UNKNOWN_TRAIT_METHOD, methName->data());
4044 static void errorUnknownTrait(const StringData* traitName) {
4045 raise_error(Strings::TRAITS_UNKNOWN_TRAIT, traitName->data());
4047 static void errorDuplicateMethod(const Class* cls,
4048 const StringData* methName,
4049 const std::list<TraitMethod>& methods) {
4050 // No error if the class will override the method.
4051 if (cls->preClass()->hasMethod(methName)) return;
4053 std::vector<folly::StringPiece> traitNames;
4054 for (auto& method : methods) {
4055 traitNames.push_back(method.trait->name()->slice());
4057 std::string traits;
4058 folly::join(", ", traitNames, traits);
4060 raise_error(Strings::METHOD_IN_MULTIPLE_TRAITS, methName->data(), traits.c_str());
4062 static void errorInconsistentInsteadOf(const Class* cls,
4063 const StringData* methName) {
4064 raise_error(Strings::INCONSISTENT_INSTEADOF, methName->data(),
4065 cls->name()->data(), cls->name()->data());
4067 static void errorMultiplyExcluded(const StringData* traitName,
4068 const StringData* methName) {
4069 raise_error(Strings::MULTIPLY_EXCLUDED,
4070 traitName->data(), methName->data());
4074 using TMIData = TraitMethodImportData<TraitMethod, TMIOps>;
4076 void applyTraitRules(Class* cls, TMIData& tmid) {
4077 for (auto const& precRule : cls->preClass()->traitPrecRules()) {
4078 tmid.applyPrecRule(precRule, cls);
4080 for (auto const& aliasRule : cls->preClass()->traitAliasRules()) {
4081 tmid.applyAliasRule(aliasRule, cls);
4085 void importTraitMethod(Class* cls,
4086 const TMIData::MethodData& mdata,
4087 Class::MethodMapBuilder& builder) {
4088 const Func* method = mdata.tm.method;
4089 Attr modifiers = mdata.tm.modifiers;
4091 auto mm_iter = builder.find(mdata.name);
4093 ITRACE(5, " - processing trait method {}\n", method->name()->data());
4095 // For abstract methods, simply return if method already declared.
4096 if ((modifiers & AttrAbstract) && mm_iter != builder.end()) {
4097 return;
4100 if (modifiers == AttrNone) {
4101 modifiers = method->attrs();
4102 } else {
4103 // Trait alias statements are only allowed to change the attributes that
4104 // are part 'attrMask' below; all other method attributes are preserved
4105 Attr attrMask = (Attr)(AttrPublic | AttrProtected | AttrPrivate |
4106 AttrAbstract | AttrFinal);
4107 modifiers = (Attr)((modifiers & (attrMask)) |
4108 (method->attrs() & ~(attrMask)));
4111 Func* parentMethod = nullptr;
4112 if (mm_iter != builder.end()) {
4113 Func* existingMethod = builder[mm_iter->second];
4114 if (existingMethod->cls() == cls) {
4115 // Don't override an existing method if this class provided an
4116 // implementation
4117 return;
4119 parentMethod = existingMethod;
4121 Func* f = method->clone(cls, mdata.name);
4122 f->setNewFuncId();
4123 f->setAttrs(modifiers | AttrTrait);
4124 if (!parentMethod) {
4125 // New method
4126 builder.add(mdata.name, f);
4127 f->setBaseCls(cls);
4128 f->setHasPrivateAncestor(false);
4129 } else {
4130 // Override an existing method
4131 Class* baseClass;
4133 cls->methodOverrideCheck(parentMethod, f);
4135 assertx(!(f->attrs() & AttrPrivate) ||
4136 (parentMethod->attrs() & AttrPrivate));
4137 if ((parentMethod->attrs() & AttrPrivate) || (f->attrs() & AttrPrivate)) {
4138 baseClass = cls;
4139 } else {
4140 baseClass = parentMethod->baseCls();
4142 f->setBaseCls(baseClass);
4143 f->setHasPrivateAncestor(
4144 parentMethod->hasPrivateAncestor() ||
4145 (parentMethod->attrs() & AttrPrivate));
4146 builder[mm_iter->second] = f;
4152 void Class::importTraitMethods(MethodMapBuilder& builder) {
4153 TMIData tmid;
4155 // Find all methods to be imported.
4156 for (auto const& t : m_extra->m_usedTraits) {
4157 Class* trait = t.get();
4158 for (Slot i = 0; i < trait->m_methods.size(); ++i) {
4159 auto const method = trait->getMethod(i);
4161 TraitMethod traitMethod { trait, method, method->attrs() };
4162 tmid.add(traitMethod, method->name());
4166 // Apply trait rules and import the methods.
4167 applyTraitRules(this, tmid);
4168 auto traitMethods = tmid.finish(this);
4170 // Import the methods.
4171 for (auto const& mdata : traitMethods) {
4172 importTraitMethod(this, mdata, builder);
4176 ///////////////////////////////////////////////////////////////////////////////