Print bounds on generic type parameters
[hiphop-php.git] / hphp / hhbbc / index.cpp
blobbda5538cfaec8d76c1634e90df56e6f5ad467aa2
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 +----------------------------------------------------------------------+
16 #include "hphp/hhbbc/index.h"
18 #include <algorithm>
19 #include <cstdio>
20 #include <cstdlib>
21 #include <iterator>
22 #include <map>
23 #include <memory>
24 #include <mutex>
25 #include <unordered_map>
26 #include <utility>
27 #include <vector>
29 #include <boost/dynamic_bitset.hpp>
31 #include <tbb/concurrent_hash_map.h>
32 #include <tbb/concurrent_unordered_map.h>
34 #include <folly/Format.h>
35 #include <folly/Hash.h>
36 #include <folly/Lazy.h>
37 #include <folly/MapUtil.h>
38 #include <folly/Memory.h>
39 #include <folly/Optional.h>
40 #include <folly/Range.h>
41 #include <folly/String.h>
42 #include <folly/concurrency/ConcurrentHashMap.h>
44 #include "hphp/runtime/base/runtime-option.h"
45 #include "hphp/runtime/base/tv-comparisons.h"
47 #include "hphp/runtime/vm/native.h"
48 #include "hphp/runtime/vm/preclass-emitter.h"
49 #include "hphp/runtime/vm/runtime.h"
50 #include "hphp/runtime/vm/trait-method-import-data.h"
51 #include "hphp/runtime/vm/unit-util.h"
53 #include "hphp/hhbbc/type-builtins.h"
54 #include "hphp/hhbbc/type-system.h"
55 #include "hphp/hhbbc/representation.h"
56 #include "hphp/hhbbc/unit-util.h"
57 #include "hphp/hhbbc/class-util.h"
58 #include "hphp/hhbbc/context.h"
59 #include "hphp/hhbbc/func-util.h"
60 #include "hphp/hhbbc/options.h"
61 #include "hphp/hhbbc/options-util.h"
62 #include "hphp/hhbbc/parallel.h"
63 #include "hphp/hhbbc/analyze.h"
65 #include "hphp/util/algorithm.h"
66 #include "hphp/util/assertions.h"
67 #include "hphp/util/hash-set.h"
68 #include "hphp/util/match.h"
70 namespace HPHP { namespace HHBBC {
72 TRACE_SET_MOD(hhbbc_index);
74 //////////////////////////////////////////////////////////////////////
76 namespace {
78 //////////////////////////////////////////////////////////////////////
80 const StaticString s_construct("__construct");
81 const StaticString s_toBoolean("__toBoolean");
82 const StaticString s_invoke("__invoke");
83 const StaticString s_Closure("Closure");
84 const StaticString s_AsyncGenerator("HH\\AsyncGenerator");
85 const StaticString s_Generator("Generator");
87 //////////////////////////////////////////////////////////////////////
90 * One-to-many case insensitive map, where the keys are static strings
91 * and the values are some kind of pointer.
93 template<class T> using ISStringToMany =
94 std::unordered_multimap<
95 SString,
96 T*,
97 string_data_hash,
98 string_data_isame
102 * One-to-one case insensitive map, where the keys are static strings
103 * and the values are some T.
105 template<class T> using ISStringToOneT =
106 hphp_hash_map<
107 SString,
109 string_data_hash,
110 string_data_isame
114 * One-to-one case insensitive map, where the keys are static strings
115 * and the values are some T.
117 * Elements are not stable under insert/erase.
119 template<class T> using ISStringToOneFastT =
120 hphp_fast_map<
121 SString,
123 string_data_hash,
124 string_data_isame
128 * One-to-one case insensitive map, where the keys are static strings
129 * and the values are some kind of pointer.
131 template<class T> using ISStringToOne = ISStringToOneT<T*>;
133 template<class MultiMap>
134 folly::Range<typename MultiMap::const_iterator>
135 find_range(const MultiMap& map, typename MultiMap::key_type key) {
136 auto const pair = map.equal_range(key);
137 return folly::range(pair.first, pair.second);
140 // Like find_range, but copy them into a temporary buffer instead of
141 // returning iterators, so you can still mutate the underlying
142 // multimap.
143 template<class MultiMap>
144 std::vector<typename MultiMap::value_type>
145 copy_range(const MultiMap& map, typename MultiMap::key_type key) {
146 auto range = find_range(map, key);
147 return std::vector<typename MultiMap::value_type>(begin(range), end(range));
150 //////////////////////////////////////////////////////////////////////
152 enum class Dep : uintptr_t {
153 /* This dependency should trigger when the return type changes */
154 ReturnTy = (1u << 0),
155 /* This dependency should trigger when a DefCns is resolved */
156 ConstVal = (1u << 1),
157 /* This dependency should trigger when a class constant is resolved */
158 ClsConst = (1u << 2),
159 /* This dependency should trigger when the bad initial prop value bit for a
160 * class changes */
161 PropBadInitialValues = (1u << 3),
162 /* This dependency should trigger when a public static property with a
163 * particular name changes */
164 PublicSPropName = (1u << 4),
165 /* This dependency means that we refused to do inline analysis on
166 * this function due to inline analysis depth. The dependency will
167 * trigger if the target function becomes effect-free, or gets a
168 * literal return value.
170 InlineDepthLimit = (1u << 5),
173 Dep operator|(Dep a, Dep b) {
174 return static_cast<Dep>(
175 static_cast<uintptr_t>(a) | static_cast<uintptr_t>(b)
179 bool has_dep(Dep m, Dep t) {
180 return static_cast<uintptr_t>(m) & static_cast<uintptr_t>(t);
184 * Maps functions to contexts that depend on information about that
185 * function, with information about the type of dependency.
187 using DepMap =
188 tbb::concurrent_hash_map<
189 DependencyContext,
190 std::map<DependencyContext,Dep,DependencyContextLess>,
191 DependencyContextHashCompare
194 //////////////////////////////////////////////////////////////////////
197 * Each ClassInfo has a table of public static properties with these entries.
198 * The `initializerType' is for use during refine_public_statics, and
199 * inferredType will always be a supertype of initializerType.
201 struct PublicSPropEntry {
202 Type inferredType;
203 Type initializerType;
204 const TypeConstraint* tc;
205 uint32_t refinements;
206 bool everModified;
208 * This flag is set during analysis to indicate that we resolved the
209 * intial value (and updated it on the php::Class). This doesn't
210 * need to be atomic, because only one thread can resolve the value
211 * (the one processing the 86sinit), and it's been joined by the
212 * time we read the flag in refine_public_statics.
214 bool initialValueResolved;
218 * Entries in the ClassInfo method table need to track some additional
219 * information.
221 * The reason for this is that we need to record attributes of the
222 * class hierarchy.
224 struct MethTabEntry {
225 MethTabEntry(const php::Func* func, Attr a, bool hpa, bool tl) :
226 func(func), attrs(a), hasPrivateAncestor(hpa), topLevel(tl) {}
227 const php::Func* func = nullptr;
228 // A method could be imported from a trait, and its attributes changed
229 Attr attrs {};
230 bool hasAncestor = false;
231 bool hasPrivateAncestor = false;
232 // This method came from the ClassInfo that owns the MethTabEntry,
233 // or one of its used traits.
234 bool topLevel = false;
235 uint32_t idx = 0;
240 struct res::Func::MethTabEntryPair :
241 ISStringToOneT<MethTabEntry>::value_type {};
243 namespace {
245 using MethTabEntryPair = res::Func::MethTabEntryPair;
247 inline MethTabEntryPair* mteFromElm(
248 ISStringToOneT<MethTabEntry>::value_type& elm) {
249 return static_cast<MethTabEntryPair*>(&elm);
252 inline const MethTabEntryPair* mteFromElm(
253 const ISStringToOneT<MethTabEntry>::value_type& elm) {
254 return static_cast<const MethTabEntryPair*>(&elm);
257 inline MethTabEntryPair* mteFromIt(ISStringToOneT<MethTabEntry>::iterator it) {
258 return static_cast<MethTabEntryPair*>(&*it);
261 struct CallContextHashCompare {
262 bool equal(const CallContext& a, const CallContext& b) const {
263 return a == b;
266 size_t hash(const CallContext& c) const {
267 auto ret = folly::hash::hash_combine(
268 c.callee,
269 c.args.size(),
270 c.context.hash()
272 for (auto& t : c.args) {
273 ret = folly::hash::hash_combine(ret, t.hash());
275 return ret;
279 using ContextRetTyMap = tbb::concurrent_hash_map<
280 CallContext,
281 Type,
282 CallContextHashCompare
285 //////////////////////////////////////////////////////////////////////
287 template<class Filter>
288 PropState make_unknown_propstate(const php::Class* cls,
289 Filter filter) {
290 auto ret = PropState{};
291 for (auto& prop : cls->properties) {
292 if (filter(prop)) {
293 ret[prop.name].ty = TCell;
296 return ret;
302 * Currently inferred information about a PHP function.
304 * Nothing in this structure can ever be untrue. The way the
305 * algorithm works, whatever is in here must be factual (even if it is
306 * not complete information), because we may deduce other facts based
307 * on it.
309 struct res::Func::FuncInfo {
310 const php::Func* func = nullptr;
312 * The best-known return type of the function, if we have any
313 * information. May be TBottom if the function is known to never
314 * return (e.g. always throws).
316 Type returnTy = TInitCell;
319 * If the function always returns the same parameter, this will be
320 * set to its id; otherwise it will be NoLocalId.
322 LocalId retParam{NoLocalId};
325 * The number of times we've refined returnTy.
327 uint32_t returnRefinments{0};
330 * Whether the function is effectFree.
332 bool effectFree{false};
335 * Bitset representing which parameters definitely don't affect the
336 * result of the function, assuming it produces one. Note that
337 * VerifyParamType does not count as a use in this context.
339 std::bitset<64> unusedParams;
342 namespace {
344 //////////////////////////////////////////////////////////////////////
347 * Known information about a particular constant:
348 * - if system is true, it's a system constant and other definitions
349 * will be ignored.
350 * - for non-system constants, if func is non-null it's the unique
351 * pseudomain defining the constant; otherwise there was more than
352 * one definition, or a non-pseudomain definition, and the type will
353 * be TInitCell
354 * - readonly is true if we've only seen uses of the constant, and no
355 * definitions (this could change during the first pass, but not after
356 * that).
359 struct ConstInfo {
360 const php::Func* func;
361 Type type;
362 bool system;
363 bool readonly;
366 using FuncFamily = res::Func::FuncFamily;
367 using FuncInfo = res::Func::FuncInfo;
368 using MethTabEntryPair = res::Func::MethTabEntryPair;
370 //////////////////////////////////////////////////////////////////////
374 //////////////////////////////////////////////////////////////////////
377 * Sometimes function resolution can't determine which function
378 * something will call, but can restrict it to a family of functions.
380 * For example, if you want to call an abstract function on a base
381 * class with all unique derived classes, we will resolve the function
382 * to a FuncFamily that contains references to all the possible
383 * overriding-functions.
385 * Carefully pack it into 8 bytes, so that hphp_fast_map will use
386 * F14VectorMap.
388 struct res::Func::FuncFamily {
389 using PFuncVec = CompactVector<const MethTabEntryPair*>;
390 static_assert(sizeof(PFuncVec) == sizeof(uintptr_t),
391 "CompactVector must be layout compatible with a pointer");
393 struct Holder {
394 Holder(const Holder& o) : bits{o.bits} {}
395 explicit Holder(PFuncVec&& o) : v{std::move(o)} {}
396 explicit Holder(uintptr_t b) : bits{b & ~1} {}
397 Holder& operator=(const Holder&) = delete;
398 ~Holder() {}
399 const PFuncVec* operator->() const { return &v; }
400 uintptr_t val() const { return bits; }
401 friend auto begin(const Holder& h) { return h->begin(); }
402 friend auto end(const Holder& h) { return h->end(); }
403 private:
404 union {
405 uintptr_t bits;
406 PFuncVec v;
410 FuncFamily(PFuncVec&& v, bool containsInterceptables)
411 : m_raw{Holder{std::move(v)}.val()} {
412 if (containsInterceptables) m_raw |= 1;
414 FuncFamily(FuncFamily&& o) noexcept : m_raw(o.m_raw) {
415 o.m_raw = 0;
417 ~FuncFamily() {
418 Holder{m_raw & ~1}->~PFuncVec();
420 FuncFamily& operator=(const FuncFamily&) = delete;
422 bool containsInterceptables() const { return m_raw & 1; };
423 const Holder possibleFuncs() const {
424 return Holder{m_raw & ~1};
426 private:
427 uintptr_t m_raw;
430 //////////////////////////////////////////////////////////////////////
432 namespace {
433 /* Known information about a particular possible instantiation of a
434 * PHP record. The php::Record will be marked AttrUnique if there is a unique
435 * RecordInfo with a given name.
437 struct RecordInfo {
438 const php::Record* rec = nullptr;
439 const RecordInfo* parent = nullptr;
444 * Known information about a particular possible instantiation of a
445 * PHP class. The php::Class will be marked AttrUnique if there is a
446 * unique ClassInfo with the same name, and no interfering class_aliases.
448 struct ClassInfo {
450 * A pointer to the underlying php::Class that we're storing
451 * information about.
453 const php::Class* cls = nullptr;
456 * The info for the parent of this Class.
458 ClassInfo* parent = nullptr;
461 * A vector of the declared interfaces class info structures. This is in
462 * declaration order mirroring the php::Class interfaceNames vector, and does
463 * not include inherited interfaces.
465 CompactVector<const ClassInfo*> declInterfaces;
468 * A (case-insensitive) map from interface names supported by this class to
469 * their ClassInfo structures, flattened across the hierarchy.
471 ISStringToOneT<const ClassInfo*> implInterfaces;
474 * A (case-sensitive) map from class constant name to the php::Const
475 * that it came from. This map is flattened across the inheritance
476 * hierarchy.
478 hphp_fast_map<SString,const php::Const*> clsConstants;
481 * A vector of the used traits, in class order, mirroring the
482 * php::Class usedTraitNames vector.
484 CompactVector<const ClassInfo*> usedTraits;
487 * A list of extra properties supplied by this class's used traits.
489 CompactVector<php::Prop> traitProps;
492 * A (case-insensitive) map from class method names to the php::Func
493 * associated with it. This map is flattened across the inheritance
494 * hierarchy.
496 ISStringToOneT<MethTabEntry> methods;
499 * A (case-insensitive) map from class method names to associated
500 * FuncFamily objects that group the set of possibly-overriding
501 * methods.
503 * Note that this does not currently encode anything for interface
504 * methods.
506 * Invariant: methods on this class with AttrNoOverride or
507 * AttrPrivate will not have an entry in this map.
509 ISStringToOneFastT<FuncFamily> methodFamilies;
512 * Subclasses of this class, including this class itself.
514 * For interfaces, this is the list of instantiable classes that
515 * implement this interface.
517 * For traits, this is the list of classes that use the trait where
518 * the trait wasn't flattened into the class (including the trait
519 * itself).
521 * Note, unlike baseList, the order of the elements in this vector
522 * is unspecified.
524 CompactVector<ClassInfo*> subclassList;
527 * A vector of ClassInfo that encodes the inheritance hierarchy,
528 * unless this ClassInfo represents an interface.
530 * This is the list of base classes for this class in inheritance
531 * order.
533 CompactVector<ClassInfo*> baseList;
536 * Property types for public static properties, declared on this exact class
537 * (i.e. not flattened in the hierarchy).
539 * These maps always have an entry for each public static property declared
540 * in this class, so it can also be used to check if this class declares a
541 * public static property of a given name.
543 * Note: the effective type we can assume a given static property may hold is
544 * not just the value in these maps. To handle mutations of public statics
545 * where the name is known, but not which class was affected, these always
546 * need to be unioned with values from IndexData::unknownClassSProps.
548 hphp_hash_map<SString,PublicSPropEntry> publicStaticProps;
551 * Flags to track if this class is mocked, or if any of its dervied classes
552 * are mocked.
554 bool isMocked{false};
555 bool isDerivedMocked{false};
558 * Track if this class has a property which might redeclare a property in a
559 * parent class with an inequivalent type-hint.
561 bool hasBadRedeclareProp{true};
564 * Track if this class has any properties with initial values that might
565 * violate their type-hints.
567 bool hasBadInitialPropValues{true};
570 * Track if this class has any const props (including inherited ones).
572 bool hasConstProp{false};
575 * Track if any derived classes (including this one) have any const props.
577 bool derivedHasConstProp{false};
580 * Flags about the existence of various magic methods, or whether
581 * any derived classes may have those methods. The non-derived
582 * flags imply the derived flags, even if the class is final, so you
583 * don't need to check both in those situations.
585 struct MagicFnInfo {
586 bool thisHas{false};
587 bool derivedHas{false};
589 MagicFnInfo
590 magicCall,
591 magicGet,
592 magicSet,
593 magicIsset,
594 magicUnset,
595 magicBool;
598 struct MagicMapInfo {
599 StaticString name;
600 ClassInfo::MagicFnInfo ClassInfo::*pmem;
601 Attr attrBit;
604 const MagicMapInfo magicMethods[] {
605 { StaticString{"__call"}, &ClassInfo::magicCall, AttrNone },
606 { StaticString{"__toBoolean"}, &ClassInfo::magicBool, AttrNone },
607 { StaticString{"__get"}, &ClassInfo::magicGet, AttrNoOverrideMagicGet },
608 { StaticString{"__set"}, &ClassInfo::magicSet, AttrNoOverrideMagicSet },
609 { StaticString{"__isset"}, &ClassInfo::magicIsset, AttrNoOverrideMagicIsset },
610 { StaticString{"__unset"}, &ClassInfo::magicUnset, AttrNoOverrideMagicUnset }
612 //////////////////////////////////////////////////////////////////////
614 namespace res {
616 Class::Class(const Index* idx,
617 Either<SString,ClassInfo*> val)
618 : index(idx)
619 , val(val)
622 // Class type operations here are very conservative for now.
624 bool Class::same(const Class& o) const {
625 return val == o.val;
628 template <bool returnTrueOnMaybe>
629 bool Class::subtypeOfImpl(const Class& o) const {
630 auto s1 = val.left();
631 auto s2 = o.val.left();
632 if (s1 || s2) return returnTrueOnMaybe || s1 == s2;
633 auto c1 = val.right();
634 auto c2 = o.val.right();
636 // If c2 is an interface, see if c1 declared it.
637 if (c2->cls->attrs & AttrInterface) {
638 if (c1->implInterfaces.count(c2->cls->name)) {
639 return true;
641 return false;
644 // Otherwise check for direct inheritance.
645 if (c1->baseList.size() >= c2->baseList.size()) {
646 return c1->baseList[c2->baseList.size() - 1] == c2;
648 return false;
651 bool Class::mustBeSubtypeOf(const Class& o) const {
652 return subtypeOfImpl<false>(o);
655 bool Class::maybeSubtypeOf(const Class& o) const {
656 return subtypeOfImpl<true>(o);
659 bool Class::couldBe(const Class& o) const {
660 // If either types are not unique return true
661 if (val.left() || o.val.left()) return true;
663 auto c1 = val.right();
664 auto c2 = o.val.right();
665 // if one or the other is an interface return true for now.
666 // TODO(#3621433): better interface stuff
667 if (c1->cls->attrs & AttrInterface || c2->cls->attrs & AttrInterface) {
668 return true;
671 // Both types are unique classes so they "could be" if they are in an
672 // inheritance relationship
673 if (c1->baseList.size() >= c2->baseList.size()) {
674 return c1->baseList[c2->baseList.size() - 1] == c2;
675 } else {
676 return c2->baseList[c1->baseList.size() - 1] == c1;
680 SString Class::name() const {
681 return val.match(
682 [] (SString s) { return s; },
683 [] (ClassInfo* ci) { return ci->cls->name.get(); }
687 bool Class::couldBeInterfaceOrTrait() const {
688 return val.match(
689 [] (SString) { return true; },
690 [] (ClassInfo* cinfo) {
691 return (cinfo->cls->attrs & (AttrInterface | AttrTrait));
696 bool Class::couldBeInterface() const {
697 return val.match(
698 [] (SString) { return true; },
699 [] (ClassInfo* cinfo) {
700 return cinfo->cls->attrs & AttrInterface;
705 bool Class::couldBeOverriden() const {
706 return val.match(
707 [] (SString) { return true; },
708 [] (ClassInfo* cinfo) {
709 return !(cinfo->cls->attrs & AttrNoOverride);
714 bool Class::couldHaveMagicGet() const {
715 return val.match(
716 [] (SString) { return true; },
717 [] (ClassInfo* cinfo) {
718 return cinfo->magicGet.derivedHas;
723 bool Class::couldHaveMagicBool() const {
724 return val.match(
725 [] (SString) { return true; },
726 [] (ClassInfo* cinfo) {
727 return cinfo->magicBool.derivedHas;
732 bool Class::couldHaveMockedDerivedClass() const {
733 return val.match(
734 [] (SString) { return true;},
735 [] (ClassInfo* cinfo) {
736 return cinfo->isDerivedMocked;
741 bool Class::couldBeMocked() const {
742 return val.match(
743 [] (SString) { return true;},
744 [] (ClassInfo* cinfo) {
745 return cinfo->isMocked;
750 bool Class::couldHaveReifiedGenerics() const {
751 return val.match(
752 [] (SString) { return true; },
753 [] (ClassInfo* cinfo) {
754 return cinfo->cls->hasReifiedGenerics;
759 bool Class::mightCareAboutDynConstructs() const {
760 if (RuntimeOption::EvalForbidDynamicConstructs > 0) {
761 return val.match(
762 [] (SString) { return true; },
763 [] (ClassInfo* cinfo) {
764 return !(cinfo->cls->attrs & AttrDynamicallyConstructible);
768 return false;
771 bool Class::couldHaveConstProp() const {
772 return val.match(
773 [] (SString) { return true; },
774 [] (ClassInfo* cinfo) { return cinfo->hasConstProp; }
778 bool Class::derivedCouldHaveConstProp() const {
779 return val.match(
780 [] (SString) { return true; },
781 [] (ClassInfo* cinfo) { return cinfo->derivedHasConstProp; }
785 folly::Optional<Class> Class::commonAncestor(const Class& o) const {
786 if (val.left() || o.val.left()) return folly::none;
787 auto const c1 = val.right();
788 auto const c2 = o.val.right();
789 // Walk the arrays of base classes until they match. For common ancestors
790 // to exist they must be on both sides of the baseList at the same positions
791 ClassInfo* ancestor = nullptr;
792 auto it1 = c1->baseList.begin();
793 auto it2 = c2->baseList.begin();
794 while (it1 != c1->baseList.end() && it2 != c2->baseList.end()) {
795 if (*it1 != *it2) break;
796 ancestor = *it1;
797 ++it1; ++it2;
799 if (ancestor == nullptr) {
800 return folly::none;
802 return res::Class { index, ancestor };
805 folly::Optional<res::Class> Class::parent() const {
806 if (!val.right()) return folly::none;
807 auto parent = val.right()->parent;
808 if (!parent) return folly::none;
809 return res::Class { index, parent };
812 const php::Class* Class::cls() const {
813 return val.right() ? val.right()->cls : nullptr;
816 std::string show(const Class& c) {
817 return c.val.match(
818 [] (SString s) -> std::string {
819 return s->data();
821 [] (ClassInfo* cinfo) {
822 return folly::sformat("{}*", cinfo->cls->name);
827 Func::Func(const Index* idx, Rep val)
828 : index(idx)
829 , val(val)
832 SString Func::name() const {
833 return match<SString>(
834 val,
835 [&] (FuncName s) { return s.name; },
836 [&] (MethodName s) { return s.name; },
837 [&] (FuncInfo* fi) { return fi->func->name; },
838 [&] (const MethTabEntryPair* mte) { return mte->first; },
839 [&] (FuncFamily* fa) -> SString {
840 auto const name = fa->possibleFuncs()->front()->first;
841 if (debug) {
842 for (DEBUG_ONLY auto const f : fa->possibleFuncs()) {
843 assert(f->first->isame(name));
846 return name;
851 const php::Func* Func::exactFunc() const {
852 using Ret = const php::Func*;
853 return match<Ret>(
854 val,
855 [&](FuncName) { return Ret{}; },
856 [&](MethodName) { return Ret{}; },
857 [&](FuncInfo* fi) { return fi->func; },
858 [&](const MethTabEntryPair* mte) { return mte->second.func; },
859 [&](FuncFamily* /*fa*/) { return Ret{}; }
863 bool Func::cantBeMagicCall() const {
864 return match<bool>(
865 val,
866 [&](FuncName) { return true; },
867 [&](MethodName) { return false; },
868 [&](FuncInfo*) { return true; },
869 [&](const MethTabEntryPair*) { return true; },
870 [&](FuncFamily*) { return true; }
874 bool Func::isFoldable() const {
875 return match<bool>(val,
876 [&](FuncName) { return false; },
877 [&](MethodName) { return false; },
878 [&](FuncInfo* fi) {
879 return fi->func->attrs & AttrIsFoldable;
881 [&](const MethTabEntryPair* mte) {
882 return mte->second.func->attrs & AttrIsFoldable;
884 [&](FuncFamily* fa) {
885 return false;
889 bool Func::couldHaveReifiedGenerics() const {
890 return match<bool>(
891 val,
892 [&](FuncName s) { return true; },
893 [&](MethodName) { return true; },
894 [&](FuncInfo* fi) { return fi->func->isReified; },
895 [&](const MethTabEntryPair* mte) {
896 return mte->second.func->isReified;
898 [&](FuncFamily* fa) {
899 for (auto const pf : fa->possibleFuncs()) {
900 if (pf->second.func->isReified) return true;
902 return false;
906 bool Func::mightCareAboutDynCalls() const {
907 if (RuntimeOption::EvalNoticeOnBuiltinDynamicCalls && mightBeBuiltin()) {
908 return true;
910 auto const mightCareAboutFuncs =
911 RuntimeOption::EvalForbidDynamicCallsToFunc > 0;
912 auto const mightCareAboutInstMeth =
913 RuntimeOption::EvalForbidDynamicCallsToInstMeth > 0;
914 auto const mightCareAboutClsMeth =
915 RuntimeOption::EvalForbidDynamicCallsToClsMeth > 0;
917 return match<bool>(
918 val,
919 [&](FuncName) { return mightCareAboutFuncs; },
920 [&](MethodName) {
921 return mightCareAboutClsMeth || mightCareAboutInstMeth;
923 [&](FuncInfo* fi) {
924 return dyn_call_error_level(fi->func) > 0;
926 [&](const MethTabEntryPair* mte) {
927 return dyn_call_error_level(mte->second.func) > 0;
929 [&](FuncFamily* fa) {
930 for (auto const pf : fa->possibleFuncs()) {
931 if (dyn_call_error_level(pf->second.func) > 0)
932 return true;
934 return false;
939 bool Func::mightBeBuiltin() const {
940 return match<bool>(
941 val,
942 // Builtins are always uniquely resolvable unless renaming is
943 // involved.
944 [&](FuncName s) { return s.renamable; },
945 [&](MethodName) { return true; },
946 [&](FuncInfo* fi) { return fi->func->attrs & AttrBuiltin; },
947 [&](const MethTabEntryPair* mte) {
948 return mte->second.func->attrs & AttrBuiltin;
950 [&](FuncFamily* fa) {
951 for (auto const pf : fa->possibleFuncs()) {
952 if (pf->second.func->attrs & AttrBuiltin) return true;
954 return false;
959 std::string show(const Func& f) {
960 auto ret = f.name()->toCppString();
961 match<void>(f.val,
962 [&](Func::FuncName s) { if (s.renamable) ret += '?'; },
963 [&](Func::MethodName) {},
964 [&](FuncInfo* /*fi*/) { ret += "*"; },
965 [&](const MethTabEntryPair* /*mte*/) { ret += "*"; },
966 [&](FuncFamily* /*fa*/) { ret += "+"; });
967 return ret;
972 //////////////////////////////////////////////////////////////////////
974 using IfaceSlotMap = hphp_hash_map<const php::Class*, Slot>;
975 using ConstInfoConcurrentMap =
976 tbb::concurrent_hash_map<SString, ConstInfo, StringDataHashCompare>;
978 struct Index::IndexData {
979 explicit IndexData(Index* index) : m_index{index} {}
980 IndexData(const IndexData&) = delete;
981 IndexData& operator=(const IndexData&) = delete;
982 ~IndexData() {
983 if (compute_iface_vtables.joinable()) {
984 compute_iface_vtables.join();
988 Index* m_index;
990 bool frozen{false};
991 bool ever_frozen{false};
992 bool any_interceptable_functions{false};
994 std::unique_ptr<ArrayTypeTable::Builder> arrTableBuilder;
996 ISStringToMany<const php::Class> classes;
997 ISStringToMany<const php::Func> methods;
998 ISStringToOneT<uint64_t> method_inout_params_by_name;
999 ISStringToMany<const php::Func> funcs;
1000 ISStringToMany<const php::TypeAlias> typeAliases;
1001 ISStringToMany<const php::Class> enums;
1002 ConstInfoConcurrentMap constants;
1003 ISStringToMany<const php::Record> records;
1004 hphp_fast_set<SString, string_data_hash, string_data_isame> classAliases;
1006 // Map from each class to all the closures that are allocated in
1007 // functions of that class.
1008 hphp_hash_map<
1009 const php::Class*,
1010 CompactVector<const php::Class*>
1011 > classClosureMap;
1013 hphp_hash_map<
1014 const php::Class*,
1015 hphp_fast_set<php::Func*>
1016 > classExtraMethodMap;
1019 * Map from each class name to ClassInfo objects for all
1020 * not-known-to-be-impossible resolutions of the class at runtime.
1022 * If the class is unique, there will only be one resolution.
1023 * Otherwise there will be one for each possible path through the
1024 * inheritance hierarchy, potentially excluding cases that we know
1025 * would definitely fatal when defined.
1027 ISStringToMany<ClassInfo> classInfo;
1030 * All the ClassInfos, sorted topologically (ie all the parents,
1031 * interfaces and traits used by the ClassInfo at index K will have
1032 * indices less than K). This mostly drops out of the way ClassInfos
1033 * are created; it would be hard to create the ClassInfos for the
1034 * php::Class X (or even know how many to create) without knowing
1035 * all the ClassInfos that were created for X's dependencies.
1037 std::vector<std::unique_ptr<ClassInfo>> allClassInfos;
1040 * Map from each record name to RecordInfo objects for all
1041 * not-known-to-be-impossible resolutions of the record at runtime.
1043 * If the record is unique, there will only be one resolution.
1044 * Otherwise there will be one for each possible path through the
1045 * inheritance hierarchy, potentially excluding cases that we know
1046 * would definitely fatal when defined.
1048 ISStringToMany<RecordInfo> recordInfo;
1051 * All the RecordInfos, sorted topologically (ie all the parents of
1052 * RecordInfo at index K will have indices less than K).
1053 * This mostly drops out of the way RecordInfos are created;
1054 * it would be hard to create the RecordInfos for the
1055 * php::Record X (or even know how many to create) without knowing
1056 * all the RecordInfos that were created for X's dependencies.
1058 std::vector<std::unique_ptr<RecordInfo>> allRecordInfos;
1060 std::vector<FuncInfo> funcInfo;
1062 // Private instance and static property types are stored separately
1063 // from ClassInfo, because you don't need to resolve a class to get
1064 // at them.
1065 hphp_hash_map<
1066 const php::Class*,
1067 PropState
1068 > privatePropInfo;
1069 hphp_hash_map<
1070 const php::Class*,
1071 PropState
1072 > privateStaticPropInfo;
1075 * Public static property information:
1078 // If this is true, we don't know anything about public static properties and
1079 // must be pessimistic. We start in this state (before we've analyzed any
1080 // mutations) and remain in it if we see a mutation where both the name and
1081 // class are unknown.
1082 bool allPublicSPropsUnknown{true};
1084 // Best known types for public static properties where we knew the name, but
1085 // not the class. The type we're allowed to assume for a public static
1086 // property is the union of the ClassInfo-specific type with the unknown class
1087 // type that's stored here. The second value is the number of times the type
1088 // has been refined.
1089 hphp_hash_map<SString, std::pair<Type, uint32_t>> unknownClassSProps;
1091 // The set of gathered public static property mutations for each function. The
1092 // inferred types for the public static properties is the union of all these
1093 // mutations. If a function is not analyzed in a particular analysis round,
1094 // its mutations are left unchanged from the previous round.
1095 folly::ConcurrentHashMap<const php::Func*,
1096 PublicSPropMutations> publicSPropMutations;
1099 * Map from interfaces to their assigned vtable slots, computed in
1100 * compute_iface_vtables().
1102 IfaceSlotMap ifaceSlotMap;
1104 hphp_hash_map<
1105 const php::Class*,
1106 CompactVector<Type>
1107 > closureUseVars;
1109 bool useClassDependencies{};
1110 DepMap dependencyMap;
1113 * If a function is effect-free when called with a particular set of
1114 * literal arguments, and produces a literal result, there will be
1115 * an entry here representing the type.
1117 * The map isn't just an optimization; we can't call
1118 * analyze_func_inline during the optimization phase, because the
1119 * bytecode could be modified while we do so.
1121 ContextRetTyMap foldableReturnTypeMap;
1124 * Call-context sensitive return types are cached here. This is not
1125 * an optimization.
1127 * The reason we need to retain this information about the
1128 * calling-context-sensitive return types is that once the Index is
1129 * frozen (during the final optimization pass), calls to
1130 * lookup_return_type with a CallContext can't look at the bytecode
1131 * bodies of functions other than the calling function. So we need
1132 * to know what we determined the last time we were alloewd to do
1133 * that so we can return it again.
1135 ContextRetTyMap contextualReturnTypes{};
1138 * Vector of class aliases that need to be added to the index when
1139 * its safe to do so (see update_class_aliases).
1141 std::vector<std::pair<SString, SString>> pending_class_aliases;
1142 std::mutex pending_class_aliases_mutex;
1144 std::thread compute_iface_vtables;
1147 //////////////////////////////////////////////////////////////////////
1149 namespace {
1151 //////////////////////////////////////////////////////////////////////
1153 using IndexData = Index::IndexData;
1155 std::mutex closure_use_vars_mutex;
1156 std::mutex private_propstate_mutex;
1158 DependencyContext make_dep(const php::Func* func) {
1159 return DependencyContext{DependencyContextType::Func, func};
1161 DependencyContext make_dep(const php::Class* cls) {
1162 return DependencyContext{DependencyContextType::Class, cls};
1164 DependencyContext make_dep(SString name) {
1165 return DependencyContext{DependencyContextType::PropName, name};
1168 DependencyContext dep_context(IndexData& data, const Context& ctx) {
1169 if (!ctx.cls || !data.useClassDependencies) return make_dep(ctx.func);
1170 auto const cls = ctx.cls->closureContextCls ?
1171 ctx.cls->closureContextCls : ctx.cls;
1172 if (is_used_trait(*cls)) return make_dep(ctx.func);
1173 return make_dep(cls);
1176 template <typename T>
1177 void add_dependency(IndexData& data,
1178 T src,
1179 const Context& dst,
1180 Dep newMask) {
1181 if (data.frozen) return;
1183 auto d = dep_context(data, dst);
1184 DepMap::accessor acc;
1185 data.dependencyMap.insert(acc, make_dep(src));
1186 auto& current = acc->second[d];
1187 current = current | newMask;
1190 std::mutex func_info_mutex;
1192 FuncInfo* create_func_info(IndexData& data, const php::Func* f) {
1193 auto fi = &data.funcInfo[f->idx];
1194 if (UNLIKELY(fi->func == nullptr)) {
1195 if (f->nativeInfo) {
1196 std::lock_guard<std::mutex> g{func_info_mutex};
1197 if (fi->func) {
1198 assert(fi->func == f);
1199 return fi;
1201 // We'd infer this anyway when we look at the bytecode body
1202 // (NativeImpl) for the HNI function, but just initializing it
1203 // here saves on whole-program iterations.
1204 fi->returnTy = native_function_return_type(f);
1206 fi->func = f;
1209 assert(fi->func == f);
1210 return fi;
1213 FuncInfo* func_info(IndexData& data, const php::Func* f) {
1214 auto const fi = &data.funcInfo[f->idx];
1215 return fi;
1218 template <typename T>
1219 void find_deps(IndexData& data,
1220 T src,
1221 Dep mask,
1222 DependencyContextSet& deps) {
1223 DepMap::const_accessor acc;
1224 if (data.dependencyMap.find(acc, make_dep(src))) {
1225 for (auto& kv : acc->second) {
1226 if (has_dep(kv.second, mask)) deps.insert(kv.first);
1231 struct TraitMethod {
1232 using class_type = const ClassInfo*;
1233 using method_type = const php::Func*;
1235 TraitMethod(class_type trait_, method_type method_, Attr modifiers_)
1236 : trait(trait_)
1237 , method(method_)
1238 , modifiers(modifiers_)
1241 class_type trait;
1242 method_type method;
1243 Attr modifiers;
1246 struct TMIOps {
1247 using string_type = LSString;
1248 using class_type = TraitMethod::class_type;
1249 using method_type = TraitMethod::method_type;
1251 struct TMIException : std::exception {
1252 explicit TMIException(std::string msg) : msg(msg) {}
1253 const char* what() const noexcept override { return msg.c_str(); }
1254 private:
1255 std::string msg;
1258 // Return the name for the trait class.
1259 static const string_type clsName(class_type traitCls) {
1260 return traitCls->cls->name;
1263 // Return the name for the trait method.
1264 static const string_type methName(method_type meth) {
1265 return meth->name;
1268 // Is-a methods.
1269 static bool isTrait(class_type traitCls) {
1270 return traitCls->cls->attrs & AttrTrait;
1272 static bool isAbstract(Attr modifiers) {
1273 return modifiers & AttrAbstract;
1276 static bool isAsync(method_type meth) {
1277 return meth->isAsync;
1279 static bool isStatic(method_type meth) {
1280 return meth->attrs & AttrStatic;
1282 static bool isFinal(method_type meth) {
1283 return meth->attrs & AttrFinal;
1286 // Whether to exclude methods with name `methName' when adding.
1287 static bool exclude(string_type methName) {
1288 return Func::isSpecial(methName);
1291 // TraitMethod constructor.
1292 static TraitMethod traitMethod(class_type traitCls,
1293 method_type traitMeth,
1294 const PreClass::TraitAliasRule& rule) {
1295 return TraitMethod { traitCls, traitMeth, rule.modifiers() };
1298 // Register a trait alias once the trait class is found.
1299 static void addTraitAlias(const ClassInfo* /*cls*/,
1300 const PreClass::TraitAliasRule& /*rule*/,
1301 class_type /*traitCls*/) {
1302 // purely a runtime thing... nothing to do
1305 // Trait class/method finders.
1306 static class_type findSingleTraitWithMethod(class_type cls,
1307 string_type origMethName) {
1308 class_type traitCls = nullptr;
1310 for (auto const t : cls->usedTraits) {
1311 // Note: m_methods includes methods from parents/traits recursively.
1312 if (t->methods.count(origMethName)) {
1313 if (traitCls != nullptr) {
1314 return nullptr;
1316 traitCls = t;
1319 return traitCls;
1322 static class_type findTraitClass(class_type cls,
1323 string_type traitName) {
1324 for (auto const t : cls->usedTraits) {
1325 if (traitName->isame(t->cls->name)) return t;
1327 return nullptr;
1330 static method_type findTraitMethod(class_type traitCls,
1331 string_type origMethName) {
1332 auto it = traitCls->methods.find(origMethName);
1333 if (it == traitCls->methods.end()) return nullptr;
1334 return it->second.func;
1337 // Errors.
1338 static void errorUnknownMethod(string_type methName) {
1339 throw TMIException(folly::sformat("Unknown method '{}'", methName));
1341 static void errorUnknownTrait(string_type traitName) {
1342 throw TMIException(folly::sformat("Unknown trait '{}'", traitName));
1344 static void errorDuplicateMethod(class_type cls,
1345 string_type methName,
1346 const std::list<TraitMethod>&) {
1347 auto const& m = cls->cls->methods;
1348 if (std::find_if(m.begin(), m.end(),
1349 [&] (auto const& f) {
1350 return f->name->isame(methName);
1351 }) != m.end()) {
1352 // the duplicate methods will be overridden by the class method.
1353 return;
1355 throw TMIException(folly::sformat("DuplicateMethod: {}", methName));
1357 static void errorInconsistentInsteadOf(class_type cls,
1358 string_type methName) {
1359 throw TMIException(folly::sformat("InconsistentInsteadOf: {} {}",
1360 methName, cls->cls->name));
1362 static void errorMultiplyExcluded(string_type traitName,
1363 string_type methName) {
1364 throw TMIException(folly::sformat("MultiplyExcluded: {}::{}",
1365 traitName, methName));
1367 static void errorInconsistentAttr(string_type traitName,
1368 string_type methName,
1369 const char* attr) {
1370 throw TMIException(folly::sformat(
1371 "Redeclaration of trait method '{}::{}' is inconsistent about '{}'",
1372 traitName, methName, attr
1375 static void errorRedeclaredNotFinal(string_type traitName,
1376 string_type methName) {
1377 throw TMIException(folly::sformat(
1378 "Redeclaration of final trait method '{}::{}' must also be final",
1379 traitName, methName
1385 using TMIData = TraitMethodImportData<TraitMethod,
1386 TMIOps>;
1388 struct BuildClsInfo {
1389 IndexData& index;
1390 ClassInfo* rleaf;
1391 hphp_hash_map<SString, std::pair<php::Prop, const ClassInfo*>,
1392 string_data_hash, string_data_same> pbuilder;
1396 * Make a flattened table of the constants on this class.
1398 bool build_class_constants(BuildClsInfo& info,
1399 const ClassInfo* rparent,
1400 bool fromTrait) {
1401 auto const removeNoOverride = [&] (const php::Const* c) {
1402 // During hhbbc/parse, all constants are pre-set to NoOverride
1403 FTRACE(2, "Removing NoOverride on {}::{}\n", c->cls->name, c->name);
1404 const_cast<php::Const*>(c)->isNoOverride = false;
1406 for (auto& c : rparent->cls->constants) {
1407 auto& cptr = info.rleaf->clsConstants[c.name];
1408 if (!cptr) {
1409 cptr = &c;
1410 continue;
1413 // Same constant (from an interface via two different paths) is ok
1414 if (cptr->cls == rparent->cls) continue;
1416 if (cptr->isTypeconst != c.isTypeconst) {
1417 ITRACE(2,
1418 "build_cls_info_rec failed for `{}' because `{}' was defined by "
1419 "`{}' as a {}constant and by `{}' as a {}constant\n",
1420 info.rleaf->cls->name, c.name,
1421 rparent->cls->name, c.isTypeconst ? "type " : "",
1422 cptr->cls->name, cptr->isTypeconst ? "type " : "");
1423 return false;
1426 // Ignore abstract constants
1427 if (!c.val) continue;
1429 if (cptr->val) {
1430 // Constants from interfaces implemented by traits silently lose
1431 if (fromTrait) {
1432 removeNoOverride(&c);
1433 continue;
1436 // A constant from an interface collides with an existing constant.
1437 if (rparent->cls->attrs & AttrInterface) {
1438 ITRACE(2,
1439 "build_cls_info_rec failed for `{}' because "
1440 "`{}' was defined by both `{}' and `{}'\n",
1441 info.rleaf->cls->name, c.name,
1442 rparent->cls->name, cptr->cls->name);
1443 return false;
1447 removeNoOverride(cptr);
1448 cptr = &c;
1450 return true;
1453 bool build_class_properties(BuildClsInfo& info,
1454 const ClassInfo* rparent) {
1455 // There's no need to do this work if traits have been flattened
1456 // already, or if the top level class has no traits. In those
1457 // cases, we might be able to rule out some ClassInfo
1458 // instantiations, but it doesn't seem worth it.
1459 if (info.rleaf->cls->attrs & AttrNoExpandTrait) return true;
1460 if (info.rleaf->usedTraits.empty()) return true;
1462 auto addProp = [&] (const php::Prop& p, bool add) {
1463 auto ent = std::make_pair(p, rparent);
1464 auto res = info.pbuilder.emplace(p.name, ent);
1465 if (res.second) {
1466 if (add) info.rleaf->traitProps.push_back(p);
1467 return true;
1469 auto& prevProp = res.first->second.first;
1470 if (rparent == res.first->second.second) {
1471 assertx(rparent == info.rleaf);
1472 if ((prevProp.attrs ^ p.attrs) &
1473 (AttrStatic | AttrPublic | AttrProtected | AttrPrivate) ||
1474 (!(p.attrs & AttrSystemInitialValue) &&
1475 !(prevProp.attrs & AttrSystemInitialValue) &&
1476 !Class::compatibleTraitPropInit(prevProp.val, p.val))) {
1477 ITRACE(2,
1478 "build_class_properties failed for `{}' because "
1479 "two declarations of `{}' at the same level had "
1480 "different attributes\n",
1481 info.rleaf->cls->name, p.name);
1482 return false;
1484 return true;
1487 if (!(prevProp.attrs & AttrPrivate)) {
1488 if ((prevProp.attrs ^ p.attrs) & AttrStatic) {
1489 ITRACE(2,
1490 "build_class_properties failed for `{}' because "
1491 "`{}' was defined both static and non-static\n",
1492 info.rleaf->cls->name, p.name);
1493 return false;
1495 if (p.attrs & AttrPrivate) {
1496 ITRACE(2,
1497 "build_class_properties failed for `{}' because "
1498 "`{}' was re-declared private\n",
1499 info.rleaf->cls->name, p.name);
1500 return false;
1502 if (p.attrs & AttrProtected && !(prevProp.attrs & AttrProtected)) {
1503 ITRACE(2,
1504 "build_class_properties failed for `{}' because "
1505 "`{}' was redeclared protected from public\n",
1506 info.rleaf->cls->name, p.name);
1507 return false;
1510 if (add && res.first->second.second != rparent) {
1511 info.rleaf->traitProps.push_back(p);
1513 res.first->second = ent;
1514 return true;
1517 for (auto const& p : rparent->cls->properties) {
1518 if (!addProp(p, false)) return false;
1521 if (rparent == info.rleaf) {
1522 for (auto t : rparent->usedTraits) {
1523 for (auto const& p : t->cls->properties) {
1524 if (!addProp(p, true)) return false;
1526 for (auto const& p : t->traitProps) {
1527 if (!addProp(p, true)) return false;
1530 } else {
1531 for (auto const& p : rparent->traitProps) {
1532 if (!addProp(p, false)) return false;
1536 return true;
1540 * Make a flattened table of the methods on this class.
1542 * Duplicate method names override parent methods, unless the parent method
1543 * is final and the class is not a __MockClass, in which case this class
1544 * definitely would fatal if ever defined.
1546 * Note: we're leaving non-overridden privates in their subclass method
1547 * table, here. This isn't currently "wrong", because calling it would be a
1548 * fatal, but note that resolve_method needs to be pretty careful about
1549 * privates and overriding in general.
1551 bool build_class_methods(BuildClsInfo& info) {
1553 auto methodOverride = [&] (auto& it,
1554 const php::Func* meth,
1555 Attr attrs,
1556 SString name) {
1557 if (it->second.func->attrs & AttrFinal) {
1558 if (!is_mock_class(info.rleaf->cls)) {
1559 ITRACE(2,
1560 "build_class_methods failed for `{}' because "
1561 "it tried to override final method `{}::{}'\n",
1562 info.rleaf->cls->name,
1563 it->second.func->cls->name, name);
1564 return false;
1567 ITRACE(9,
1568 " {}: overriding method {}::{} with {}::{}\n",
1569 info.rleaf->cls->name,
1570 it->second.func->cls->name, it->second.func->name,
1571 meth->cls->name, name);
1572 if (it->second.func->attrs & AttrPrivate) {
1573 it->second.hasPrivateAncestor = true;
1575 it->second.func = meth;
1576 it->second.attrs = attrs;
1577 it->second.hasAncestor = true;
1578 it->second.topLevel = true;
1579 if (it->first != name) {
1580 auto mte = it->second;
1581 info.rleaf->methods.erase(it);
1582 it = info.rleaf->methods.emplace(name, mte).first;
1584 return true;
1587 // If there's a parent, start by copying its methods
1588 if (auto const rparent = info.rleaf->parent) {
1589 for (auto& mte : rparent->methods) {
1590 // don't inherit the 86* methods.
1591 if (HPHP::Func::isSpecial(mte.first)) continue;
1592 auto const res = info.rleaf->methods.emplace(mte.first, mte.second);
1593 assertx(res.second);
1594 res.first->second.topLevel = false;
1595 ITRACE(9,
1596 " {}: inheriting method {}::{}\n",
1597 info.rleaf->cls->name,
1598 rparent->cls->name, mte.first);
1599 continue;
1603 uint32_t idx = info.rleaf->methods.size();
1605 // Now add our methods.
1606 for (auto& m : info.rleaf->cls->methods) {
1607 auto res = info.rleaf->methods.emplace(
1608 m->name,
1609 MethTabEntry { m.get(), m->attrs, false, true }
1611 if (res.second) {
1612 res.first->second.idx = idx++;
1613 ITRACE(9,
1614 " {}: adding method {}::{}\n",
1615 info.rleaf->cls->name,
1616 info.rleaf->cls->name, m->name);
1617 continue;
1619 if (m->attrs & AttrTrait && m->attrs & AttrAbstract) {
1620 // abstract methods from traits never override anything.
1621 continue;
1623 if (!methodOverride(res.first, m.get(), m->attrs, m->name)) return false;
1626 // If our traits were previously flattened, we're done.
1627 if (info.rleaf->cls->attrs & AttrNoExpandTrait) return true;
1629 try {
1630 TMIData tmid;
1631 for (auto const t : info.rleaf->usedTraits) {
1632 std::vector<const MethTabEntryPair*> methods(t->methods.size());
1633 for (auto& m : t->methods) {
1634 if (HPHP::Func::isSpecial(m.first)) continue;
1635 assertx(!methods[m.second.idx]);
1636 methods[m.second.idx] = mteFromElm(m);
1638 for (auto const m : methods) {
1639 if (!m) continue;
1640 TraitMethod traitMethod { t, m->second.func, m->second.attrs };
1641 tmid.add(traitMethod, m->first);
1643 for (auto const c : info.index.classClosureMap[t->cls]) {
1644 auto const invoke = find_method(c, s_invoke.get());
1645 assertx(invoke);
1646 info.index.classExtraMethodMap[info.rleaf->cls].insert(invoke);
1650 for (auto const& precRule : info.rleaf->cls->traitPrecRules) {
1651 tmid.applyPrecRule(precRule, info.rleaf);
1653 auto const& aliasRules = info.rleaf->cls->traitAliasRules;
1654 tmid.applyAliasRules(aliasRules.begin(), aliasRules.end(), info.rleaf);
1655 auto traitMethods = tmid.finish(info.rleaf);
1656 // Import the methods.
1657 for (auto const& mdata : traitMethods) {
1658 auto const method = mdata.tm.method;
1659 auto attrs = mdata.tm.modifiers;
1660 if (attrs == AttrNone) {
1661 attrs = method->attrs;
1662 } else {
1663 Attr attrMask = (Attr)(AttrPublic | AttrProtected | AttrPrivate |
1664 AttrAbstract | AttrFinal);
1665 attrs = (Attr)((attrs & attrMask) |
1666 (method->attrs & ~attrMask));
1668 auto res = info.rleaf->methods.emplace(
1669 mdata.name,
1670 MethTabEntry { method, attrs, false, true }
1672 if (res.second) {
1673 res.first->second.idx = idx++;
1674 ITRACE(9,
1675 " {}: adding trait method {}::{} as {}\n",
1676 info.rleaf->cls->name,
1677 method->cls->name, method->name, mdata.name);
1678 } else {
1679 if (attrs & AttrAbstract) continue;
1680 if (res.first->second.func->cls == info.rleaf->cls) continue;
1681 if (!methodOverride(res.first, method, attrs, mdata.name)) {
1682 return false;
1684 res.first->second.idx = idx++;
1686 info.index.classExtraMethodMap[info.rleaf->cls].insert(
1687 const_cast<php::Func*>(method));
1689 } catch (TMIOps::TMIException& ex) {
1690 ITRACE(2,
1691 "build_class_methods failed for `{}' importing traits: {}\n",
1692 info.rleaf->cls->name, ex.what());
1693 return false;
1696 return true;
1699 bool enforce_in_maybe_sealed_parent_whitelist(
1700 const ClassInfo* cls,
1701 const ClassInfo* parent);
1703 bool build_cls_info_rec(BuildClsInfo& info,
1704 const ClassInfo* rparent,
1705 bool fromTrait) {
1706 if (!rparent) return true;
1707 if (!enforce_in_maybe_sealed_parent_whitelist(rparent, rparent->parent)) {
1708 return false;
1710 if (!build_cls_info_rec(info, rparent->parent, false)) {
1711 return false;
1714 for (auto const iface : rparent->declInterfaces) {
1715 if (!enforce_in_maybe_sealed_parent_whitelist(rparent, iface)) {
1716 return false;
1718 if (!build_cls_info_rec(info, iface, fromTrait)) {
1719 return false;
1723 for (auto const trait : rparent->usedTraits) {
1724 if (!enforce_in_maybe_sealed_parent_whitelist(rparent, trait)) {
1725 return false;
1727 if (!build_cls_info_rec(info, trait, true)) return false;
1730 if (rparent->cls->attrs & AttrInterface) {
1732 * Make a flattened table of all the interfaces implemented by the class.
1734 info.rleaf->implInterfaces[rparent->cls->name] = rparent;
1735 } else {
1736 if (!fromTrait &&
1737 !build_class_properties(info, rparent)) {
1738 return false;
1741 // We don't need a method table for interfaces, and rather than
1742 // building the table recursively from scratch we just use the
1743 // parent's already constructed method table, and this class's
1744 // local method table (and traits if necessary).
1745 if (rparent == info.rleaf) {
1746 if (!build_class_methods(info)) return false;
1750 if (!build_class_constants(info, rparent, fromTrait)) return false;
1752 return true;
1755 const StaticString s___Sealed("__Sealed");
1756 bool enforce_in_maybe_sealed_parent_whitelist(
1757 const ClassInfo* cls,
1758 const ClassInfo* parent) {
1759 // if our parent isn't sealed, then we're fine.
1760 if (!parent || !(parent->cls->attrs & AttrSealed)) return true;
1761 const UserAttributeMap& parent_attrs = parent->cls->userAttributes;
1762 assert(parent_attrs.find(s___Sealed.get()) != parent_attrs.end());
1763 const auto& parent_sealed_attr = parent_attrs.find(s___Sealed.get())->second;
1764 bool in_sealed_whitelist = false;
1765 IterateV(parent_sealed_attr.m_data.parr,
1766 [&in_sealed_whitelist, cls](TypedValue v) -> bool {
1767 if (v.m_data.pstr->same(cls->cls->name)) {
1768 in_sealed_whitelist = true;
1769 return true;
1771 return false;
1773 return in_sealed_whitelist;
1777 * Note: a cyclic inheritance chain will blow this up, but right now
1778 * we'll never get here in that case because hphpc currently just
1779 * modifies classes not to have that situation. TODO(#3649211).
1781 * This function return false if we are certain instantiating cinfo
1782 * would be a fatal at runtime.
1784 bool build_cls_info(IndexData& index, ClassInfo* cinfo) {
1785 auto info = BuildClsInfo{ index, cinfo };
1786 if (!build_cls_info_rec(info, cinfo, false)) return false;
1787 return true;
1790 //////////////////////////////////////////////////////////////////////
1792 void add_system_constants_to_index(IndexData& index) {
1793 for (auto cnsPair : Native::getConstants()) {
1794 assertx(cnsPair.second.m_type != KindOfUninit ||
1795 cnsPair.second.dynamic());
1796 auto t = cnsPair.second.dynamic() ?
1797 TInitCell : from_cell(cnsPair.second);
1799 ConstInfoConcurrentMap::accessor acc;
1800 if (index.constants.insert(acc, cnsPair.first)) {
1801 acc->second.func = nullptr;
1802 acc->second.type = t;
1803 acc->second.system = true;
1804 acc->second.readonly = false;
1809 //////////////////////////////////////////////////////////////////////
1810 template<typename T> struct NamingEnv;
1812 template<class T>
1813 struct TypeHelper;
1815 template<>
1816 struct TypeHelper<php::Class> {
1817 template<class Fn>
1818 static void process_bases(const php::Class* cls, Fn&& fn) {
1819 if (cls->parentName) fn(cls->parentName);
1820 for (auto& i : cls->interfaceNames) fn(i);
1821 for (auto& t : cls->usedTraitNames) fn(t);
1824 static std::string name() { return "class"; }
1826 static void assert_bases(NamingEnv<php::Class>& env, const php::Class* cls);
1827 static void try_flatten_traits(NamingEnv<php::Class>&,
1828 const php::Class*, ClassInfo*);
1831 template<>
1832 struct TypeHelper<php::Record> {
1833 template<class Fn>
1834 static void process_bases(const php::Record* rec, Fn&& fn) {
1835 if (rec->parentName) fn(rec->parentName);
1838 static std::string name() { return "record"; }
1840 static void assert_bases(NamingEnv<php::Record>& env, const php::Record* rec);
1841 static void try_flatten_traits(NamingEnv<php::Record>&,
1842 const php::Record*, RecordInfo*);
1845 template<typename T>
1846 struct TypeInfoData {
1847 // Map from name to types that directly use that name (as parent,
1848 // interface or trait).
1849 hphp_hash_map<SString,
1850 CompactVector<const T*>,
1851 string_data_hash,
1852 string_data_isame> users;
1853 // Map from types to number of dependencies, used in
1854 // conjunction with users field above.
1855 hphp_hash_map<const T*, uint32_t> depCounts;
1857 uint32_t cqFront{};
1858 uint32_t cqBack{};
1859 std::vector<const T*> queue;
1860 bool hasPseudoCycles{};
1863 using ClassInfoData = TypeInfoData<php::Class>;
1864 using RecordInfoData = TypeInfoData<php::Record>;
1866 // We want const qualifiers on various index data structures for php
1867 // object pointers, but during index creation time we need to
1868 // manipulate some of their attributes (changing the representation).
1869 // This little wrapper keeps the const_casting out of the main line of
1870 // code below.
1871 void attribute_setter(const Attr& attrs, bool set, Attr attr) {
1872 attrSetter(const_cast<Attr&>(attrs), set, attr);
1875 void add_unit_to_index(IndexData& index, const php::Unit& unit) {
1876 hphp_hash_map<
1877 const php::Class*,
1878 hphp_hash_set<const php::Class*>
1879 > closureMap;
1881 for (auto& c : unit.classes) {
1882 auto const attrsToRemove =
1883 AttrUnique |
1884 AttrPersistent |
1885 AttrNoOverride |
1886 AttrNoOverrideMagicGet |
1887 AttrNoOverrideMagicSet |
1888 AttrNoOverrideMagicIsset |
1889 AttrNoOverrideMagicUnset;
1890 attribute_setter(c->attrs, false, attrsToRemove);
1892 // Manually set closure classes to be unique to maintain invariance.
1893 if (is_closure(*c)) {
1894 attrSetter(c->attrs, true, AttrUnique);
1897 if (c->attrs & AttrEnum) {
1898 index.enums.emplace(c->name, c.get());
1902 * A class can be defined with the same name as a builtin in the
1903 * repo. Any such attempts will fatal at runtime, so we can safely
1904 * ignore any such definitions. This ensures that names referring
1905 * to builtins are always fully resolvable.
1907 auto const classes = find_range(index.classes, c->name);
1908 if (classes.begin() != classes.end()) {
1909 if (c->attrs & AttrBuiltin) {
1910 index.classes.erase(classes.begin(), classes.end());
1911 } else if (classes.begin()->second->attrs & AttrBuiltin) {
1912 assertx(std::next(classes.begin()) == classes.end());
1913 continue;
1916 index.classes.emplace(c->name, c.get());
1918 for (auto& m : c->methods) {
1919 attribute_setter(m->attrs, false, AttrNoOverride);
1920 index.methods.insert({m->name, m.get()});
1921 if (m->attrs & AttrInterceptable) {
1922 index.any_interceptable_functions = true;
1925 if (RuntimeOption::RepoAuthoritative) {
1926 uint64_t refs = 0, cur = 1;
1927 bool anyInOut = false;
1928 for (auto& p : m->params) {
1929 if (p.inout) {
1930 refs |= cur;
1931 anyInOut = true;
1933 // It doesn't matter that we lose parameters beyond the 64th,
1934 // for those, we'll conservatively check everything anyway.
1935 cur <<= 1;
1937 if (anyInOut) {
1938 // Multiple methods with the same name will be combined in the same
1939 // cell, thus we use |=. This only makes sense in WholeProgram mode
1940 // since we use this field to check that no functions uses its n-th
1941 // parameter byref, which requires global knowledge.
1942 index.method_inout_params_by_name[m->name] |= refs;
1947 if (c->closureContextCls) {
1948 closureMap[c->closureContextCls].insert(c.get());
1952 if (!closureMap.empty()) {
1953 for (auto const& c1 : closureMap) {
1954 auto& s = index.classClosureMap[c1.first];
1955 for (auto const& c2 : c1.second) {
1956 s.push_back(c2);
1961 for (auto& f : unit.funcs) {
1963 * A function can be defined with the same name as a builtin in the
1964 * repo. Any such attempts will fatal at runtime, so we can safely ignore
1965 * any such definitions. This ensures that names referring to builtins are
1966 * always fully resolvable.
1968 auto const funcs = index.funcs.equal_range(f->name);
1969 if (funcs.first != funcs.second) {
1970 if (f->attrs & AttrIsMethCaller) {
1971 // meth_caller has builtin attr and can have duplicates definitions
1972 assertx(std::next(funcs.first) == funcs.second);
1973 assertx(funcs.first->second->attrs & AttrIsMethCaller);
1974 continue;
1977 auto const& old_func = funcs.first->second;
1978 // If there is a builtin, it will always be the first (and only) func on
1979 // the list.
1980 if (old_func->attrs & AttrBuiltin) {
1981 always_assert(!(f->attrs & AttrBuiltin));
1982 continue;
1984 if (f->attrs & AttrBuiltin) index.funcs.erase(funcs.first, funcs.second);
1986 if (f->attrs & AttrInterceptable) index.any_interceptable_functions = true;
1987 index.funcs.insert({f->name, f.get()});
1990 for (auto& ta : unit.typeAliases) {
1991 index.typeAliases.insert({ta->name, ta.get()});
1994 for (auto& rec : unit.records) {
1995 index.records.insert({rec->name, rec.get()});
1998 for (auto& ca : unit.classAliases) {
1999 index.classAliases.insert(ca.first);
2000 index.classAliases.insert(ca.second);
2004 template<class T>
2005 using TypeInfo = typename std::conditional<std::is_same<T, php::Class>::value,
2006 ClassInfo, RecordInfo>::type;
2008 template<typename T>
2009 struct NamingEnv {
2010 NamingEnv(php::Program* program, IndexData& index, TypeInfoData<T>& tid) :
2011 program{program}, index{index}, tid{tid} {}
2013 struct Define;
2015 // Returns TypeInfo for a given name, if either:
2016 // a) that name corresponds to a unique TypeInfo, or
2017 // b) he TypeInfo for that name was selected in scope with NamingEnv::Define
2018 TypeInfo<T>* try_lookup(SString name,
2019 const ISStringToMany<TypeInfo<T>>& map) const {
2020 auto const range = map.equal_range(name);
2021 // We're resolving in topological order; we shouldn't be here
2022 // unless we know there's at least one resolution of this class.
2023 assertx(range.first != range.second);
2024 // Common case will be exactly one resolution. Lets avoid the
2025 // copy_range, and iteration for that case.
2026 if (std::next(range.first) == range.second) {
2027 return range.first->second;
2029 auto const it = names.find(name);
2030 if (it != end(names)) return it->second;
2031 return nullptr;
2034 TypeInfo<T>* lookup(SString name,
2035 const ISStringToMany<TypeInfo<T>>& map) const {
2036 auto const ret = try_lookup(name, map);
2037 assertx(ret);
2038 return ret;
2041 php::Program* program;
2042 IndexData& index;
2043 TypeInfoData<T>& tid;
2044 std::unordered_multimap<
2045 const T*,
2046 TypeInfo<T>*,
2047 pointer_hash<T>> resolved;
2048 private:
2049 ISStringToOne<TypeInfo<T>> names;
2052 template<typename T>
2053 struct NamingEnv<T>::Define {
2054 explicit Define(NamingEnv& env, SString n, TypeInfo<T>* ti, const T* t)
2055 : env(env), n(n) {
2056 ITRACE(2, "defining {} {} for {}\n", TypeHelper<T>::name(), n, t->name);
2057 always_assert(!env.names.count(n));
2058 env.names[n] = ti;
2060 ~Define() {
2061 env.names.erase(n);
2064 Define(const Define&) = delete;
2065 Define& operator=(const Define&) = delete;
2067 private:
2068 Trace::Indent indent;
2069 NamingEnv<T>& env;
2070 SString n;
2073 using ClassNamingEnv = NamingEnv<php::Class>;
2074 using RecordNamingEnv = NamingEnv<php::Record>;
2076 void TypeHelper<php::Class>::assert_bases(NamingEnv<php::Class>& env,
2077 const php::Class* cls) {
2078 if (cls->parentName) {
2079 assertx(env.index.classInfo.count(cls->parentName));
2081 for (DEBUG_ONLY auto& i : cls->interfaceNames) {
2082 assertx(env.index.classInfo.count(i));
2084 for (DEBUG_ONLY auto& t : cls->usedTraitNames) {
2085 assertx(env.index.classInfo.count(t));
2089 void TypeHelper<php::Record>::assert_bases(NamingEnv<php::Record>& env,
2090 const php::Record* rec) {
2091 if (rec->parentName) {
2092 assertx(env.index.recordInfo.count(rec->parentName));
2096 using ClonedClosureMap = hphp_hash_map<
2097 php::Class*,
2098 std::pair<std::unique_ptr<php::Class>, uint32_t>
2101 std::unique_ptr<php::Func> clone_meth_helper(
2102 php::Class* newContext,
2103 const php::Func* origMeth,
2104 std::unique_ptr<php::Func> cloneMeth,
2105 std::atomic<uint32_t>& nextFuncId,
2106 uint32_t& nextClass,
2107 ClonedClosureMap& clonedClosures);
2109 std::unique_ptr<php::Class> clone_closure(php::Class* newContext,
2110 php::Class* cls,
2111 std::atomic<uint32_t>& nextFuncId,
2112 uint32_t& nextClass,
2113 ClonedClosureMap& clonedClosures) {
2114 auto clone = std::make_unique<php::Class>(*cls);
2115 assertx(clone->closureContextCls);
2116 clone->closureContextCls = newContext;
2117 clone->unit = newContext->unit;
2118 auto i = 0;
2119 for (auto& cloneMeth : clone->methods) {
2120 cloneMeth = clone_meth_helper(clone.get(),
2121 cls->methods[i++].get(),
2122 std::move(cloneMeth),
2123 nextFuncId,
2124 nextClass,
2125 clonedClosures);
2126 if (!cloneMeth) return nullptr;
2128 return clone;
2131 std::unique_ptr<php::Func> clone_meth_helper(
2132 php::Class* newContext,
2133 const php::Func* origMeth,
2134 std::unique_ptr<php::Func> cloneMeth,
2135 std::atomic<uint32_t>& nextFuncId,
2136 uint32_t& nextClass,
2137 ClonedClosureMap& clonedClosures) {
2139 cloneMeth->cls = newContext;
2140 cloneMeth->idx = nextFuncId.fetch_add(1, std::memory_order_relaxed);
2141 if (!cloneMeth->originalFilename) {
2142 cloneMeth->originalFilename = origMeth->unit->filename;
2144 if (!cloneMeth->originalUnit) {
2145 cloneMeth->originalUnit = origMeth->unit;
2147 cloneMeth->unit = newContext->unit;
2149 auto const recordClosure = [&] (uint32_t* clsId) {
2150 auto const cls = origMeth->unit->classes[*clsId].get();
2151 auto& elm = clonedClosures[cls];
2152 if (!elm.first) {
2153 elm.first = clone_closure(newContext->closureContextCls ?
2154 newContext->closureContextCls : newContext,
2155 cls, nextFuncId, nextClass, clonedClosures);
2156 if (!elm.first) return false;
2157 elm.second = nextClass++;
2159 *clsId = elm.second;
2160 return true;
2163 hphp_fast_map<size_t, hphp_fast_map<size_t, uint32_t>> updates;
2164 for (size_t bid = 0; bid < cloneMeth->blocks.size(); bid++) {
2165 auto const b = cloneMeth->blocks[bid].get();
2166 for (size_t ix = 0; ix < b->hhbcs.size(); ix++) {
2167 auto const& bc = b->hhbcs[ix];
2168 switch (bc.op) {
2169 case Op::CreateCl: {
2170 auto clsId = bc.CreateCl.arg2;
2171 if (!recordClosure(&clsId)) return nullptr;
2172 updates[bid][ix] = clsId;
2173 break;
2175 case Op::DefCls:
2176 case Op::DefClsNop:
2177 return nullptr;
2178 default:
2179 break;
2184 for (auto elm : updates) {
2185 auto& cblk = cloneMeth->blocks[elm.first];
2186 auto const blk = cblk.mutate();
2187 for (auto const& ix : elm.second) {
2188 blk->hhbcs[ix.first].CreateCl.arg2 = ix.second;
2192 return cloneMeth;
2195 std::unique_ptr<php::Func> clone_meth(php::Class* newContext,
2196 const php::Func* origMeth,
2197 SString name,
2198 Attr attrs,
2199 std::atomic<uint32_t>& nextFuncId,
2200 uint32_t& nextClass,
2201 ClonedClosureMap& clonedClosures) {
2203 auto cloneMeth = std::make_unique<php::Func>(*origMeth);
2204 cloneMeth->name = name;
2205 cloneMeth->attrs = attrs | AttrTrait;
2206 return clone_meth_helper(newContext, origMeth, std::move(cloneMeth),
2207 nextFuncId, nextClass, clonedClosures);
2210 bool merge_xinits(Attr attr,
2211 std::vector<std::unique_ptr<php::Func>>& clones,
2212 ClassInfo* cinfo,
2213 std::atomic<uint32_t>& nextFuncId,
2214 uint32_t& nextClass,
2215 ClonedClosureMap& clonedClosures) {
2216 auto const cls = const_cast<php::Class*>(cinfo->cls);
2217 auto const xinitName = [&]() {
2218 switch (attr) {
2219 case AttrNone : return s_86pinit.get();
2220 case AttrStatic: return s_86sinit.get();
2221 case AttrLSB : return s_86linit.get();
2222 default: always_assert(false);
2224 }();
2226 auto const xinitMatch = [&](Attr prop_attrs) {
2227 auto mask = AttrStatic | AttrLSB;
2228 switch (attr) {
2229 case AttrNone: return (prop_attrs & mask) == AttrNone;
2230 case AttrStatic: return (prop_attrs & mask) == AttrStatic;
2231 case AttrLSB: return (prop_attrs & mask) == mask;
2232 default: always_assert(false);
2236 auto const needsXinit = [&] {
2237 for (auto const& p : cinfo->traitProps) {
2238 if (xinitMatch(p.attrs) &&
2239 p.val.m_type == KindOfUninit &&
2240 !(p.attrs & AttrLateInit)) {
2241 ITRACE(5, "merge_xinits: {}: Needs merge for {}{}prop `{}'\n",
2242 cls->name, attr & AttrStatic ? "static " : "",
2243 attr & AttrLSB ? "lsb " : "", p.name);
2244 return true;
2247 return false;
2248 }();
2250 if (!needsXinit) return true;
2252 std::unique_ptr<php::Func> empty;
2253 auto& xinit = [&] () -> std::unique_ptr<php::Func>& {
2254 for (auto& m : cls->methods) {
2255 if (m->name == xinitName) return m;
2257 return empty;
2258 }();
2260 auto merge_one = [&] (const php::Func* func) {
2261 if (!xinit) {
2262 ITRACE(5, " - cloning {}::{} as {}::{}\n",
2263 func->cls->name, func->name, cls->name, xinitName);
2264 xinit = clone_meth(cls, func, func->name, func->attrs, nextFuncId,
2265 nextClass, clonedClosures);
2266 return xinit != nullptr;
2269 ITRACE(5, " - appending {}::{} into {}::{}\n",
2270 func->cls->name, func->name, cls->name, xinitName);
2271 return append_func(xinit.get(), *func);
2274 for (auto t : cinfo->usedTraits) {
2275 auto it = t->methods.find(xinitName);
2276 if (it != t->methods.end()) {
2277 if (!merge_one(it->second.func)) {
2278 ITRACE(5, "merge_xinits: failed to merge {}::{}\n",
2279 it->second.func->cls->name, it->second.func->name);
2280 return false;
2285 assertx(xinit);
2286 if (empty) {
2287 ITRACE(5, "merge_xinits: adding {}::{} to method table\n",
2288 xinit->cls->name, xinit->name);
2289 assertx(&empty == &xinit);
2290 DEBUG_ONLY auto res = cinfo->methods.emplace(
2291 xinit->name,
2292 MethTabEntry { xinit.get(), xinit->attrs, false, true }
2294 assertx(res.second);
2295 clones.push_back(std::move(xinit));
2298 return true;
2301 void rename_closure(ClassNamingEnv& env, php::Class* cls) {
2302 auto n = cls->name->slice();
2303 auto const p = n.find(';');
2304 if (p != std::string::npos) {
2305 n = n.subpiece(0, p);
2307 auto const newName = makeStaticString(NewAnonymousClassName(n));
2308 assertx(!env.index.classes.count(newName));
2309 cls->name = newName;
2310 env.index.classes.emplace(newName, cls);
2313 template <typename T> void preresolve(NamingEnv<T>& env, const T* type);
2315 void flatten_traits(ClassNamingEnv& env, ClassInfo* cinfo) {
2316 bool hasConstProp = false;
2317 for (auto t : cinfo->usedTraits) {
2318 if (t->usedTraits.size() && !(t->cls->attrs & AttrNoExpandTrait)) {
2319 ITRACE(5, "Not flattening {} because of {}\n",
2320 cinfo->cls->name, t->cls->name);
2321 return;
2323 if (is_noflatten_trait(t->cls)) {
2324 ITRACE(5, "Not flattening {} because {} is annotated with __NoFlatten\n",
2325 cinfo->cls->name, t->cls->name);
2326 return;
2328 if (t->cls->hasConstProp) hasConstProp = true;
2330 auto const cls = const_cast<php::Class*>(cinfo->cls);
2331 if (hasConstProp) cls->hasConstProp = true;
2332 std::vector<MethTabEntryPair*> methodsToAdd;
2333 for (auto& ent : cinfo->methods) {
2334 if (!ent.second.topLevel || ent.second.func->cls == cinfo->cls) {
2335 continue;
2337 always_assert(ent.second.func->cls->attrs & AttrTrait);
2338 methodsToAdd.push_back(mteFromElm(ent));
2341 auto const it = env.index.classExtraMethodMap.find(cinfo->cls);
2343 if (!methodsToAdd.empty()) {
2344 assertx(it != env.index.classExtraMethodMap.end());
2345 std::sort(begin(methodsToAdd), end(methodsToAdd),
2346 [] (const MethTabEntryPair* a, const MethTabEntryPair* b) {
2347 return a->second.idx < b->second.idx;
2349 } else if (debug && it != env.index.classExtraMethodMap.end()) {
2350 // When building the ClassInfos, we proactively added all closures
2351 // from usedTraits to classExtraMethodMap; but now we're going to
2352 // start from the used methods, and deduce which closures actually
2353 // get pulled in. Its possible *none* of the methods got used, in
2354 // which case, we won't need their closures either. To be safe,
2355 // verify that the only things in classExtraMethodMap are
2356 // closures.
2357 for (DEBUG_ONLY auto const f : it->second) {
2358 assertx(f->isClosureBody);
2362 std::vector<std::unique_ptr<php::Func>> clones;
2363 ClonedClosureMap clonedClosures;
2364 uint32_t nextClassId = cls->unit->classes.size();
2365 for (auto const ent : methodsToAdd) {
2366 auto clone = clone_meth(cls, ent->second.func, ent->first,
2367 ent->second.attrs, env.program->nextFuncId,
2368 nextClassId, clonedClosures);
2369 if (!clone) {
2370 ITRACE(5, "Not flattening {} because {}::{} could not be cloned\n",
2371 cls->name, ent->second.func->cls->name, ent->first);
2372 return;
2375 clone->attrs |= AttrTrait;
2376 ent->second.attrs |= AttrTrait;
2377 ent->second.func = clone.get();
2378 clones.push_back(std::move(clone));
2381 if (cinfo->traitProps.size()) {
2382 if (!merge_xinits(AttrNone, clones, cinfo,
2383 env.program->nextFuncId, nextClassId, clonedClosures) ||
2384 !merge_xinits(AttrStatic, clones, cinfo,
2385 env.program->nextFuncId, nextClassId, clonedClosures) ||
2386 !merge_xinits(AttrLSB, clones, cinfo,
2387 env.program->nextFuncId, nextClassId, clonedClosures)) {
2388 ITRACE(5, "Not flattening {} because we couldn't merge the 86xinits\n",
2389 cls->name);
2390 return;
2394 // We're now committed to flattening.
2395 ITRACE(3, "Flattening {}\n", cls->name);
2396 if (it != env.index.classExtraMethodMap.end()) it->second.clear();
2397 for (auto const& p : cinfo->traitProps) {
2398 ITRACE(5, " - prop {}\n", p.name);
2399 cls->properties.push_back(p);
2400 cls->properties.back().attrs |= AttrTrait;
2402 cinfo->traitProps.clear();
2404 if (clones.size()) {
2405 auto cinit = cls->methods.size() &&
2406 cls->methods.back()->name == s_86cinit.get() ?
2407 std::move(cls->methods.back()) : nullptr;
2408 if (cinit) cls->methods.pop_back();
2409 for (auto& clone : clones) {
2410 ITRACE(5, " - meth {}\n", clone->name);
2411 cinfo->methods.find(clone->name)->second.func = clone.get();
2412 cls->methods.push_back(std::move(clone));
2414 if (cinit) cls->methods.push_back(std::move(cinit));
2416 if (clonedClosures.size()) {
2417 auto& classClosures = env.index.classClosureMap[cls];
2418 cls->unit->classes.resize(nextClassId);
2419 for (auto& ent : clonedClosures) {
2420 auto const clo = ent.second.first.get();
2421 rename_closure(env, clo);
2422 ITRACE(5, " - closure {} as {}\n", ent.first->name, clo->name);
2423 assertx(clo->closureContextCls == cls);
2424 assertx(clo->unit == cls->unit);
2425 classClosures.push_back(clo);
2427 cls->unit->classes[ent.second.second] = std::move(ent.second.first);
2428 preresolve(env, clo);
2433 struct EqHash {
2434 bool operator()(const PreClass::ClassRequirement& a,
2435 const PreClass::ClassRequirement& b) const {
2436 return a.is_same(&b);
2438 size_t operator()(const PreClass::ClassRequirement& a) const {
2439 return a.hash();
2443 hphp_hash_set<PreClass::ClassRequirement, EqHash, EqHash> reqs;
2445 for (auto const t : cinfo->usedTraits) {
2446 for (auto const& req : t->cls->requirements) {
2447 if (reqs.empty()) {
2448 for (auto const& r : cls->requirements) {
2449 reqs.insert(r);
2452 if (reqs.insert(req).second) cls->requirements.push_back(req);
2456 cls->attrs |= AttrNoExpandTrait;
2460 * Given a static representation of a Hack record, find a possible resolution
2461 * of the record along with all records in its hierarchy.
2463 void resolve_combinations(RecordNamingEnv& env,
2464 const php::Record* rec) {
2466 auto resolve_one = [&] (SString name) {
2467 if (env.try_lookup(name, env.index.recordInfo)) return true;
2468 auto const range = copy_range(env.index.recordInfo, name);
2469 assertx(range.size() > 1);
2470 for (auto& kv : range) {
2471 RecordNamingEnv::Define def{env, name, kv.second, rec};
2472 resolve_combinations(env, rec);
2474 return false;
2477 // Recurse with all combinations of parents.
2478 if (rec->parentName) {
2479 if (!resolve_one(rec->parentName)) return;
2482 // Everything is defined in the naming environment here. (We
2483 // returned early if something didn't exist.)
2485 auto rinfo = std::make_unique<RecordInfo>();
2486 rinfo->rec = rec;
2487 if (rec->parentName) {
2488 auto const parent = env.lookup(rec->parentName, env.index.recordInfo);
2489 if (parent->rec->attrs & AttrFinal) {
2490 ITRACE(2,
2491 "Resolve combinations failed for `{}' because "
2492 "its parent record `{}' is not abstract\n",
2493 rec->name, parent->rec->name);
2494 return;
2496 rinfo->parent = parent;
2498 ITRACE(2, " resolved: {}\n", rec->name);
2499 env.resolved.emplace(rec, rinfo.get());
2500 env.index.recordInfo.emplace(rec->name, rinfo.get());
2501 env.index.allRecordInfos.push_back(std::move(rinfo));
2505 * Given a static representation of a Hack class, find a possible resolution
2506 * of the class along with all classes, interfaces and traits in its hierarchy.
2508 void resolve_combinations(ClassNamingEnv& env,
2509 const php::Class* cls) {
2511 auto resolve_one = [&] (SString name) {
2512 if (env.try_lookup(name, env.index.classInfo)) return true;
2513 auto const range = copy_range(env.index.classInfo, name);
2514 assertx(range.size() > 1);
2515 for (auto& kv : range) {
2516 ClassNamingEnv::Define def{env, name, kv.second, cls};
2517 resolve_combinations(env, cls);
2519 return false;
2522 // Recurse with all combinations of bases and interfaces in the
2523 // naming environment.
2524 if (cls->parentName) {
2525 if (!resolve_one(cls->parentName)) return;
2527 for (auto& iname : cls->interfaceNames) {
2528 if (!resolve_one(iname)) return;
2530 for (auto& tname : cls->usedTraitNames) {
2531 if (!resolve_one(tname)) return;
2534 // Everything is defined in the naming environment here. (We
2535 // returned early if something didn't exist.)
2537 auto cinfo = std::make_unique<ClassInfo>();
2538 cinfo->cls = cls;
2539 auto const& map = env.index.classInfo;
2540 if (cls->parentName) {
2541 cinfo->parent = env.lookup(cls->parentName, map);
2542 cinfo->baseList = cinfo->parent->baseList;
2543 if (cinfo->parent->cls->attrs & (AttrInterface | AttrTrait)) {
2544 ITRACE(2,
2545 "Resolve combinations failed for `{}' because "
2546 "its parent `{}' is not a class\n",
2547 cls->name, cls->parentName);
2548 return;
2551 cinfo->baseList.push_back(cinfo.get());
2553 for (auto& iname : cls->interfaceNames) {
2554 auto const iface = env.lookup(iname, map);
2555 if (!(iface->cls->attrs & AttrInterface)) {
2556 ITRACE(2,
2557 "Resolve combinations failed for `{}' because `{}' "
2558 "is not an interface\n",
2559 cls->name, iname);
2560 return;
2562 cinfo->declInterfaces.push_back(iface);
2565 for (auto& tname : cls->usedTraitNames) {
2566 auto const trait = env.lookup(tname, map);
2567 if (!(trait->cls->attrs & AttrTrait)) {
2568 ITRACE(2,
2569 "Resolve combinations failed for `{}' because `{}' "
2570 "is not a trait\n",
2571 cls->name, tname);
2572 return;
2574 cinfo->usedTraits.push_back(trait);
2577 if (!build_cls_info(env.index, cinfo.get())) return;
2579 ITRACE(2, " resolved: {}\n", cls->name);
2580 if (Trace::moduleEnabled(Trace::hhbbc_index, 3)) {
2581 for (auto const DEBUG_ONLY& iface : cinfo->implInterfaces) {
2582 ITRACE(3, " implements: {}\n", iface.second->cls->name);
2584 for (auto const DEBUG_ONLY& trait : cinfo->usedTraits) {
2585 ITRACE(3, " uses: {}\n", trait->cls->name);
2588 cinfo->baseList.shrink_to_fit();
2589 env.resolved.emplace(cls, cinfo.get());
2590 env.index.classInfo.emplace(cls->name, cinfo.get());
2591 env.index.allClassInfos.push_back(std::move(cinfo));
2596 void TypeHelper<php::Record>::try_flatten_traits(NamingEnv<php::Record>&,
2597 const php::Record*,
2598 RecordInfo*) {}
2600 void TypeHelper<php::Class>::try_flatten_traits(NamingEnv<php::Class>& env,
2601 const php::Class* cls,
2602 ClassInfo* cinfo) {
2603 if (options.FlattenTraits &&
2604 !(cls->attrs & AttrNoExpandTrait) &&
2605 !cls->usedTraitNames.empty() &&
2606 env.index.classes.count(cls->name) == 1) {
2607 Trace::Indent indent;
2608 flatten_traits(env, cinfo);
2612 template <typename T>
2613 void preresolve(NamingEnv<T>& env, const T* type) {
2614 assertx(!env.resolved.count(type));
2616 ITRACE(2, "preresolve {}: {}:{}\n",
2617 TypeHelper<T>::name(), type->name, (void*)type);
2619 Trace::Indent indent;
2620 if (debug) {
2621 TypeHelper<T>::assert_bases(env, type);
2623 resolve_combinations(env, type);
2626 ITRACE(3, "preresolve: {}:{} ({} resolutions)\n",
2627 type->name, (void*)type, env.resolved.count(type));
2629 auto const range = find_range(env.resolved, type);
2630 if (begin(range) != end(range)) {
2631 auto const& users = env.tid.users[type->name];
2632 for (auto const tu : users) {
2633 auto const it = env.tid.depCounts.find(tu);
2634 if (it == env.tid.depCounts.end()) {
2635 assertx(env.tid.hasPseudoCycles);
2636 continue;
2638 auto& depCount = it->second;
2639 assertx(depCount);
2640 if (!--depCount) {
2641 env.tid.depCounts.erase(it);
2642 ITRACE(5, " enqueue: {}:{}\n", tu->name, (void*)tu);
2643 env.tid.queue[env.tid.cqBack++] = tu;
2644 } else {
2645 ITRACE(6, " depcount: {}:{} = {}\n", tu->name, (void*)tu, depCount);
2648 if (std::next(begin(range)) == end(range)) {
2649 TypeHelper<T>::try_flatten_traits(env, type, begin(range)->second);
2654 void compute_subclass_list_rec(IndexData& index,
2655 ClassInfo* cinfo,
2656 ClassInfo* csub) {
2657 for (auto const ctrait : csub->usedTraits) {
2658 auto const ct = const_cast<ClassInfo*>(ctrait);
2659 ct->subclassList.push_back(cinfo);
2660 compute_subclass_list_rec(index, cinfo, ct);
2664 void compute_subclass_list(IndexData& index) {
2665 trace_time _("compute subclass list");
2666 auto fixupTraits = false;
2667 for (auto& cinfo : index.allClassInfos) {
2668 if (cinfo->cls->attrs & AttrInterface) continue;
2669 for (auto& cparent : cinfo->baseList) {
2670 cparent->subclassList.push_back(cinfo.get());
2672 if (!(cinfo->cls->attrs & AttrNoExpandTrait) &&
2673 cinfo->usedTraits.size()) {
2674 fixupTraits = true;
2675 compute_subclass_list_rec(index, cinfo.get(), cinfo.get());
2677 // Also add instantiable classes to their interface's subclassLists
2678 if (cinfo->cls->attrs & (AttrTrait | AttrEnum | AttrAbstract)) continue;
2679 for (auto& ipair : cinfo->implInterfaces) {
2680 auto impl = const_cast<ClassInfo*>(ipair.second);
2681 impl->subclassList.push_back(cinfo.get());
2685 for (auto& cinfo : index.allClassInfos) {
2686 auto& sub = cinfo->subclassList;
2687 if (fixupTraits && cinfo->cls->attrs & AttrTrait) {
2688 // traits can be reached by multiple paths, so we need to uniquify
2689 // their subclassLists.
2690 std::sort(begin(sub), end(sub));
2691 sub.erase(
2692 std::unique(begin(sub), end(sub)),
2693 end(sub)
2696 sub.shrink_to_fit();
2700 bool define_func_family(IndexData& index, ClassInfo* cinfo,
2701 SString name, const php::Func* func = nullptr) {
2702 FuncFamily::PFuncVec funcs{};
2703 auto containsInterceptables = false;
2704 for (auto const cleaf : cinfo->subclassList) {
2705 auto const leafFn = [&] () -> const MethTabEntryPair* {
2706 auto const leafFnIt = cleaf->methods.find(name);
2707 if (leafFnIt == end(cleaf->methods)) return nullptr;
2708 return mteFromIt(leafFnIt);
2709 }();
2710 if (!leafFn) continue;
2711 if (leafFn->second.func->attrs & AttrInterceptable) {
2712 containsInterceptables = true;
2714 funcs.push_back(leafFn);
2717 if (funcs.empty()) return false;
2719 std::sort(begin(funcs), end(funcs),
2720 [&] (const MethTabEntryPair* a, const MethTabEntryPair* b) {
2721 // We want a canonical order for the family. Putting the
2722 // one corresponding to cinfo first makes sense, because
2723 // the first one is used as the name for FCall*Method* hint,
2724 // after that, sort by name so that different case spellings
2725 // come in the same order.
2726 if (a->second.func == b->second.func) return false;
2727 if (func) {
2728 if (b->second.func == func) return false;
2729 if (a->second.func == func) return true;
2731 if (auto d = a->first->compare(b->first)) {
2732 if (!func) {
2733 if (b->first == name) return false;
2734 if (a->first == name) return true;
2736 return d < 0;
2738 return std::less<const void*>{}(a->second.func, b->second.func);
2740 funcs.erase(
2741 std::unique(begin(funcs), end(funcs),
2742 [] (const MethTabEntryPair* a, const MethTabEntryPair* b) {
2743 return a->second.func == b->second.func;
2745 end(funcs)
2748 funcs.shrink_to_fit();
2750 if (Trace::moduleEnabled(Trace::hhbbc_index, 4)) {
2751 FTRACE(4, "define_func_family: {}::{}:\n",
2752 cinfo->cls->name, name);
2753 for (auto const DEBUG_ONLY func : funcs) {
2754 FTRACE(4, " {}::{}\n",
2755 func->second.func->cls->name, func->second.func->name);
2759 cinfo->methodFamilies.emplace(
2760 std::piecewise_construct,
2761 std::forward_as_tuple(name),
2762 std::forward_as_tuple(std::move(funcs), containsInterceptables)
2765 return true;
2768 void build_abstract_func_families(IndexData& data, ClassInfo* cinfo) {
2769 std::vector<SString> extras;
2771 // We start by collecting the list of methods shared across all
2772 // subclasses of cinfo (including indirectly). And then add the
2773 // public methods which are not constructors and have no private
2774 // ancestors to the method families of cinfo. Note that this set
2775 // may be larger than the methods declared on cinfo and may also
2776 // be missing methods declared on cinfo. In practice this is the
2777 // set of methods we can depend on having accessible given any
2778 // object which is known to implement cinfo.
2779 auto it = cinfo->subclassList.begin();
2780 while (true) {
2781 if (it == cinfo->subclassList.end()) return;
2782 auto const sub = *it++;
2783 assertx(!(sub->cls->attrs & AttrInterface));
2784 if (sub == cinfo || (sub->cls->attrs & AttrAbstract)) continue;
2785 for (auto& par : sub->methods) {
2786 if (!par.second.hasPrivateAncestor &&
2787 (par.second.attrs & AttrPublic) &&
2788 !cinfo->methodFamilies.count(par.first) &&
2789 !cinfo->methods.count(par.first)) {
2790 extras.push_back(par.first);
2793 if (!extras.size()) return;
2794 break;
2797 auto end = extras.end();
2798 while (it != cinfo->subclassList.end()) {
2799 auto const sub = *it++;
2800 assertx(!(sub->cls->attrs & AttrInterface));
2801 if (sub == cinfo || (sub->cls->attrs & AttrAbstract)) continue;
2802 for (auto nameIt = extras.begin(); nameIt != end;) {
2803 auto const meth = sub->methods.find(*nameIt);
2804 if (meth == sub->methods.end() ||
2805 !(meth->second.attrs & AttrPublic) ||
2806 meth->second.hasPrivateAncestor) {
2807 *nameIt = *--end;
2808 if (end == extras.begin()) return;
2809 } else {
2810 ++nameIt;
2814 extras.erase(end, extras.end());
2816 if (Trace::moduleEnabled(Trace::hhbbc_index, 5)) {
2817 FTRACE(5, "Adding extra methods to {}:\n", cinfo->cls->name);
2818 for (auto const DEBUG_ONLY extra : extras) {
2819 FTRACE(5, " {}\n", extra);
2823 hphp_fast_set<SString> added;
2825 for (auto name : extras) {
2826 if (define_func_family(data, cinfo, name) &&
2827 (cinfo->cls->attrs & AttrInterface)) {
2828 added.emplace(name);
2832 if (cinfo->cls->attrs & AttrInterface) {
2833 for (auto& m : cinfo->cls->methods) {
2834 if (added.count(m->name)) {
2835 cinfo->methods.emplace(
2836 m->name,
2837 MethTabEntry { m.get(), m->attrs, false, true }
2842 return;
2845 void define_func_families(IndexData& index) {
2846 trace_time tracer("define_func_families");
2848 parallel::for_each(
2849 index.allClassInfos,
2850 [&] (const std::unique_ptr<ClassInfo>& cinfo) {
2851 if (cinfo->cls->attrs & AttrTrait) return;
2852 FTRACE(4, "Defining func families for {}\n", cinfo->cls->name);
2853 if (!(cinfo->cls->attrs & AttrInterface)) {
2854 for (auto& kv : cinfo->methods) {
2855 auto const mte = mteFromElm(kv);
2857 if (mte->second.attrs & AttrNoOverride) continue;
2858 if (is_special_method_name(mte->first)) continue;
2860 // We need function family for constructor even if it is private,
2861 // as `new static()` may still call a non-private constructor from
2862 // subclass.
2863 if (!mte->first->isame(s_construct.get()) &&
2864 mte->second.attrs & AttrPrivate) {
2865 continue;
2868 define_func_family(index, cinfo.get(), mte->first, mte->second.func);
2871 if (cinfo->cls->attrs & (AttrInterface | AttrAbstract)) {
2872 build_abstract_func_families(index, cinfo.get());
2879 * ConflictGraph maintains lists of interfaces that conflict with each other
2880 * due to being implemented by the same class.
2882 struct ConflictGraph {
2883 void add(const php::Class* i, const php::Class* j) {
2884 if (i == j) return;
2885 map[i].insert(j);
2888 hphp_hash_map<const php::Class*,
2889 hphp_fast_set<const php::Class*>> map;
2893 * Trace information about interface conflict sets and the vtables computed
2894 * from them.
2896 void trace_interfaces(const IndexData& index, const ConflictGraph& cg) {
2897 // Compute what the vtable for each Class will look like, and build up a list
2898 // of all interfaces.
2899 struct Cls {
2900 const ClassInfo* cinfo;
2901 std::vector<const php::Class*> vtable;
2903 std::vector<Cls> classes;
2904 std::vector<const php::Class*> ifaces;
2905 size_t total_slots = 0, empty_slots = 0;
2906 for (auto& cinfo : index.allClassInfos) {
2907 if (cinfo->cls->attrs & AttrInterface) {
2908 ifaces.emplace_back(cinfo->cls);
2909 continue;
2911 if (cinfo->cls->attrs & (AttrTrait | AttrEnum | AttrAbstract)) continue;
2913 classes.emplace_back(Cls{cinfo.get()});
2914 auto& vtable = classes.back().vtable;
2915 for (auto& pair : cinfo->implInterfaces) {
2916 auto it = index.ifaceSlotMap.find(pair.second->cls);
2917 assert(it != end(index.ifaceSlotMap));
2918 auto const slot = it->second;
2919 if (slot >= vtable.size()) vtable.resize(slot + 1);
2920 vtable[slot] = pair.second->cls;
2923 total_slots += vtable.size();
2924 for (auto iface : vtable) if (iface == nullptr) ++empty_slots;
2927 Slot max_slot = 0;
2928 for (auto const& pair : index.ifaceSlotMap) {
2929 max_slot = std::max(max_slot, pair.second);
2932 // Sort the list of class vtables so the largest ones come first.
2933 auto class_cmp = [&](const Cls& a, const Cls& b) {
2934 return a.vtable.size() > b.vtable.size();
2936 std::sort(begin(classes), end(classes), class_cmp);
2938 // Sort the list of interfaces so the biggest conflict sets come first.
2939 auto iface_cmp = [&](const php::Class* a, const php::Class* b) {
2940 return cg.map.at(a).size() > cg.map.at(b).size();
2942 std::sort(begin(ifaces), end(ifaces), iface_cmp);
2944 std::string out;
2945 folly::format(&out, "{} interfaces, {} classes\n",
2946 ifaces.size(), classes.size());
2947 folly::format(&out,
2948 "{} vtable slots, {} empty vtable slots, max slot {}\n",
2949 total_slots, empty_slots, max_slot);
2950 folly::format(&out, "\n{:-^80}\n", " interface slots & conflict sets");
2951 for (auto iface : ifaces) {
2952 auto cgIt = cg.map.find(iface);
2953 if (cgIt == end(cg.map)) break;
2954 auto& conflicts = cgIt->second;
2956 folly::format(&out, "{:>40} {:3} {:2} [", iface->name,
2957 conflicts.size(),
2958 folly::get_default(index.ifaceSlotMap, iface));
2959 auto sep = "";
2960 for (auto conflict : conflicts) {
2961 folly::format(&out, "{}{}", sep, conflict->name);
2962 sep = ", ";
2964 folly::format(&out, "]\n");
2967 folly::format(&out, "\n{:-^80}\n", " class vtables ");
2968 for (auto& item : classes) {
2969 if (item.vtable.empty()) break;
2971 folly::format(&out, "{:>30}: [", item.cinfo->cls->name);
2972 auto sep = "";
2973 for (auto iface : item.vtable) {
2974 folly::format(&out, "{}{}", sep, iface ? iface->name->data() : "null");
2975 sep = ", ";
2977 folly::format(&out, "]\n");
2980 Trace::traceRelease("%s", out.c_str());
2984 * Find the lowest Slot that doesn't conflict with anything in the conflict set
2985 * for iface.
2987 Slot find_min_slot(const php::Class* iface,
2988 const IfaceSlotMap& slots,
2989 const ConflictGraph& cg) {
2990 auto const& cit = cg.map.find(iface);
2991 if (cit == cg.map.end() || cit->second.empty()) {
2992 // No conflicts. This is the only interface implemented by the classes that
2993 // implement it.
2994 return 0;
2997 boost::dynamic_bitset<> used;
2999 for (auto const& c : cit->second) {
3000 auto const it = slots.find(c);
3001 if (it == slots.end()) continue;
3002 auto const slot = it->second;
3004 if (used.size() <= slot) used.resize(slot + 1);
3005 used.set(slot);
3007 used.flip();
3008 return used.any() ? used.find_first() : used.size();
3012 * Compute vtable slots for all interfaces. No two interfaces implemented by
3013 * the same class will share the same vtable slot.
3015 void compute_iface_vtables(IndexData& index) {
3016 trace_time tracer("compute interface vtables");
3018 ConflictGraph cg;
3019 std::vector<const php::Class*> ifaces;
3020 hphp_hash_map<const php::Class*, int> iface_uses;
3022 // Build up the conflict sets.
3023 for (auto& cinfo : index.allClassInfos) {
3024 // Gather interfaces.
3025 if (cinfo->cls->attrs & AttrInterface) {
3026 ifaces.emplace_back(cinfo->cls);
3027 // Make sure cg.map has an entry for every interface - this simplifies
3028 // some code later on.
3029 cg.map[cinfo->cls];
3030 continue;
3033 // Only worry about classes that can be instantiated. If an abstract class
3034 // has any concrete subclasses, those classes will make sure the right
3035 // entries are in the conflict sets.
3036 if (cinfo->cls->attrs & (AttrTrait | AttrEnum | AttrAbstract)) continue;
3038 for (auto& ipair : cinfo->implInterfaces) {
3039 ++iface_uses[ipair.second->cls];
3040 for (auto& jpair : cinfo->implInterfaces) {
3041 cg.add(ipair.second->cls, jpair.second->cls);
3046 if (ifaces.size() == 0) return;
3048 // Sort interfaces by usage frequencies.
3049 // We assign slots greedily, so sort the interface list so the most
3050 // frequently implemented ones come first.
3051 auto iface_cmp = [&](const php::Class* a, const php::Class* b) {
3052 return iface_uses[a] > iface_uses[b];
3054 std::sort(begin(ifaces), end(ifaces), iface_cmp);
3056 // Assign slots, keeping track of the largest assigned slot and the total
3057 // number of uses for each slot.
3058 Slot max_slot = 0;
3059 hphp_hash_map<Slot, int> slot_uses;
3060 for (auto* iface : ifaces) {
3061 auto const slot = find_min_slot(iface, index.ifaceSlotMap, cg);
3062 index.ifaceSlotMap[iface] = slot;
3063 max_slot = std::max(max_slot, slot);
3065 // Interfaces implemented by the same class never share a slot, so normal
3066 // addition is fine here.
3067 slot_uses[slot] += iface_uses[iface];
3070 // Make sure we have an initialized entry for each slot for the sort below.
3071 for (Slot slot = 0; slot < max_slot; ++slot) {
3072 assert(slot_uses.count(slot));
3075 // Finally, sort and reassign slots so the most frequently used slots come
3076 // first. This slightly reduces the number of wasted vtable vector entries at
3077 // runtime.
3078 auto const slots = sort_keys_by_value(
3079 slot_uses,
3080 [&] (int a, int b) { return a > b; }
3083 std::vector<Slot> slots_permute(max_slot + 1, 0);
3084 for (size_t i = 0; i <= max_slot; ++i) slots_permute[slots[i]] = i;
3086 // re-map interfaces to permuted slots
3087 for (auto& pair : index.ifaceSlotMap) {
3088 pair.second = slots_permute[pair.second];
3091 if (Trace::moduleEnabledRelease(Trace::hhbbc_iface)) {
3092 trace_interfaces(index, cg);
3096 void mark_magic_on_parents(ClassInfo& cinfo, ClassInfo& derived) {
3097 auto any = false;
3098 for (const auto& mm : magicMethods) {
3099 if ((derived.*mm.pmem).thisHas) {
3100 auto& derivedHas = (cinfo.*mm.pmem).derivedHas;
3101 if (!derivedHas) {
3102 derivedHas = any = true;
3106 if (!any) return;
3107 if (cinfo.parent) mark_magic_on_parents(*cinfo.parent, derived);
3108 for (auto iface : cinfo.declInterfaces) {
3109 mark_magic_on_parents(*const_cast<ClassInfo*>(iface), derived);
3113 bool has_magic_method(const ClassInfo* cinfo, SString name) {
3114 if (name == s_toBoolean.get()) {
3115 // note that "having" a magic method includes the possibility that
3116 // a parent class has it. This can't happen for the collection
3117 // classes, because they're all final; but for SimpleXMLElement,
3118 // we need to search.
3119 while (cinfo->parent) cinfo = cinfo->parent;
3120 return has_magic_bool_conversion(cinfo->cls->name);
3122 return cinfo->methods.find(name) != end(cinfo->methods);
3125 void find_magic_methods(IndexData& index) {
3126 for (auto& cinfo : index.allClassInfos) {
3127 bool any = false;
3128 for (const auto& mm : magicMethods) {
3129 bool const found = has_magic_method(cinfo.get(), mm.name.get());
3130 any = any || found;
3131 (cinfo.get()->*mm.pmem).thisHas = found;
3133 if (any) mark_magic_on_parents(*cinfo, *cinfo);
3137 void find_mocked_classes(IndexData& index) {
3138 for (auto& cinfo : index.allClassInfos) {
3139 if (is_mock_class(cinfo->cls) && cinfo->parent) {
3140 cinfo->parent->isMocked = true;
3141 for (auto c = cinfo->parent; c; c = c->parent) {
3142 c->isDerivedMocked = true;
3148 void mark_const_props(IndexData& index) {
3149 for (auto& cinfo : index.allClassInfos) {
3150 auto const hasConstProp = [&]() {
3151 if (cinfo->cls->hasConstProp) return true;
3152 if (cinfo->parent && cinfo->parent->hasConstProp) return true;
3153 if (!(cinfo->cls->attrs & AttrNoExpandTrait)) {
3154 for (auto t : cinfo->usedTraits) {
3155 if (t->cls->hasConstProp) return true;
3158 return false;
3159 }();
3160 if (hasConstProp) {
3161 cinfo->hasConstProp = true;
3162 for (auto c = cinfo.get(); c; c = c->parent) {
3163 if (c->derivedHasConstProp) break;
3164 c->derivedHasConstProp = true;
3170 void mark_no_override_classes(IndexData& index) {
3171 for (auto& cinfo : index.allClassInfos) {
3172 // We cleared all the NoOverride flags while building the
3173 // index. Set them as necessary.
3174 if (!(cinfo->cls->attrs & AttrUnique)) continue;
3175 if (!(cinfo->cls->attrs & AttrInterface) &&
3176 cinfo->subclassList.size() == 1) {
3177 attribute_setter(cinfo->cls->attrs, true, AttrNoOverride);
3180 for (const auto& mm : magicMethods) {
3181 if (mm.attrBit == AttrNone) continue;
3182 if (!(cinfo.get()->*mm.pmem).derivedHas) {
3183 FTRACE(2, "Adding no-override of {} to {}\n",
3184 mm.name.get()->data(),
3185 cinfo->cls->name);
3186 attribute_setter(cinfo->cls->attrs, true, mm.attrBit);
3192 void mark_no_override_methods(IndexData& index) {
3193 // We removed any AttrNoOverride flags from all methods while adding
3194 // the units to the index. Now start by marking every
3195 // (non-interface, non-special) method as AttrNoOverride.
3196 for (auto& cinfo : index.allClassInfos) {
3197 if (cinfo->cls->attrs & AttrInterface) continue;
3198 if (!(cinfo->cls->attrs & AttrUnique)) continue;
3200 for (auto& m : cinfo->methods) {
3201 if (!(is_special_method_name(m.first))) {
3202 FTRACE(9, "Pre-setting AttrNoOverride on {}::{}\n",
3203 m.second.func->cls->name, m.first);
3204 attribute_setter(m.second.attrs, true, AttrNoOverride);
3205 attribute_setter(m.second.func->attrs, true, AttrNoOverride);
3210 // Then run through every ClassInfo, and for each of its parent classes clear
3211 // the AttrNoOverride flag if it has a different Func with the same name.
3212 for (auto& cinfo : index.allClassInfos) {
3213 for (auto& ancestor : cinfo->baseList) {
3214 if (ancestor == cinfo.get()) continue;
3216 auto removeNoOverride = [] (auto it) {
3217 assertx(it->second.attrs & AttrNoOverride ||
3218 !(it->second.func->attrs & AttrNoOverride));
3219 if (it->second.attrs & AttrNoOverride) {
3220 FTRACE(2, "Removing AttrNoOverride on {}::{}\n",
3221 it->second.func->cls->name, it->first);
3222 attribute_setter(it->second.attrs, false, AttrNoOverride);
3223 attribute_setter(it->second.func->attrs, false, AttrNoOverride);
3227 for (auto& derivedMethod : cinfo->methods) {
3228 auto const it = ancestor->methods.find(derivedMethod.first);
3229 if (it == end(ancestor->methods)) continue;
3230 if (it->second.func != derivedMethod.second.func) {
3231 removeNoOverride(it);
3238 template <class T, class F>
3239 void mark_unique_entities(ISStringToMany<T>& entities, F marker) {
3240 for (auto it = entities.begin(), end = entities.end(); it != end; ) {
3241 auto first = it++;
3242 auto flag = true;
3243 while (it != end && it->first->isame(first->first)) {
3244 marker(it++->second, false);
3245 flag = false;
3247 marker(first->second, flag);
3251 const StaticString s__Reified("__Reified");
3254 * Emitter adds a 86reifiedinit method to all classes that have reified
3255 * generics. All base classes also need to have this method so that when we
3256 * call parent::86reifeidinit(...), there is a stopping point.
3257 * Since while emitting we do not know whether a base class will have
3258 * reified parents, during JIT time we need to add 86reifiedinit
3259 * unless AttrNoReifiedInit attribute is set. At this phase,
3260 * we set AttrNoReifiedInit attribute on classes do not have any
3261 * reified classes that extend it.
3263 void clean_86reifiedinit_methods(IndexData& index) {
3264 trace_time tracer("clean 86reifiedinit methods");
3265 folly::F14FastSet<const php::Class*> needsinit;
3267 // Find all classes that still need their 86reifiedinit methods
3268 for (auto& cinfo : index.allClassInfos) {
3269 auto ual = cinfo->cls->userAttributes;
3270 // Each class that has at least one reified generic has an attribute
3271 // __Reified added by the emitter
3272 auto has_reification = ual.find(s__Reified.get()) != ual.end();
3273 if (!has_reification) continue;
3274 // Add the base class for this reified class
3275 needsinit.emplace(cinfo->baseList[0]->cls);
3278 // Add AttrNoReifiedInit to the base classes that do not need this method
3279 for (auto& cinfo : index.allClassInfos) {
3280 if (cinfo->parent == nullptr && needsinit.count(cinfo->cls) == 0) {
3281 FTRACE(2, "Adding AttrNoReifiedInit on class {}\n", cinfo->cls->name);
3282 attribute_setter(cinfo->cls->attrs, true, AttrNoReifiedInit);
3287 //////////////////////////////////////////////////////////////////////
3289 void check_invariants(const ClassInfo* cinfo) {
3290 // All the following invariants only apply to classes
3291 if (cinfo->cls->attrs & AttrInterface) return;
3293 if (!(cinfo->cls->attrs & AttrTrait)) {
3294 // For non-interface classes, each method in a php class has an
3295 // entry in its ClassInfo method table, and if it's not special,
3296 // AttrNoOverride, or private, an entry in the family table.
3297 for (auto& m : cinfo->cls->methods) {
3298 auto const it = cinfo->methods.find(m->name);
3299 always_assert(it != cinfo->methods.end());
3300 if (it->second.attrs & (AttrNoOverride|AttrPrivate)) continue;
3301 if (is_special_method_name(m->name)) continue;
3302 always_assert(cinfo->methodFamilies.count(m->name));
3306 // The subclassList is non-empty, contains this ClassInfo, and
3307 // contains only unique elements.
3308 always_assert(!cinfo->subclassList.empty());
3309 always_assert(std::find(begin(cinfo->subclassList),
3310 end(cinfo->subclassList),
3311 cinfo) != end(cinfo->subclassList));
3312 auto cpy = cinfo->subclassList;
3313 std::sort(begin(cpy), end(cpy));
3314 cpy.erase(
3315 std::unique(begin(cpy), end(cpy)),
3316 end(cpy)
3318 always_assert(cpy.size() == cinfo->subclassList.size());
3320 // The baseList is non-empty, and the last element is this class.
3321 always_assert(!cinfo->baseList.empty());
3322 always_assert(cinfo->baseList.back() == cinfo);
3324 for (const auto& mm : magicMethods) {
3325 const auto& info = cinfo->*mm.pmem;
3327 // Magic method flags should be consistent with the method table.
3328 always_assert(info.thisHas == has_magic_method(cinfo, mm.name.get()));
3330 // Non-'derived' flags (thisHas) about magic methods imply the derived
3331 // ones.
3332 always_assert(!info.thisHas || info.derivedHas);
3335 // Every FuncFamily is non-empty and contain functions with the same
3336 // name (unless its a family of ctors).
3337 for (auto const& mfam: cinfo->methodFamilies) {
3338 always_assert(!mfam.second.possibleFuncs()->empty());
3339 auto const name = mfam.second.possibleFuncs()->front()->first;
3340 for (auto const pf : mfam.second.possibleFuncs()) {
3341 always_assert(pf->first->isame(name));
3346 void check_invariants(IndexData& data) {
3347 if (!debug) return;
3349 // Every AttrUnique non-trait class has a unique ClassInfo object,
3350 // or no ClassInfo object in the case that instantiating it would've
3351 // fataled.
3352 for (auto& kv : data.classes) {
3353 auto const name = kv.first;
3354 auto const cls = kv.second;
3355 if (!(cls->attrs & AttrUnique)) continue;
3357 auto const range = find_range(data.classInfo, name);
3358 if (begin(range) != end(range)) {
3359 always_assert(std::next(begin(range)) == end(range));
3363 for (auto& cinfo : data.allClassInfos) {
3364 check_invariants(cinfo.get());
3368 //////////////////////////////////////////////////////////////////////
3370 Type context_sensitive_return_type(IndexData& data,
3371 CallContext callCtx) {
3372 constexpr auto max_interp_nexting_level = 2;
3373 static __thread uint32_t interp_nesting_level;
3374 auto const finfo = func_info(data, callCtx.callee);
3375 auto const returnType = return_with_context(finfo->returnTy, callCtx.context);
3377 auto checkParam = [&] (int i) {
3378 auto const constraint = finfo->func->params[i].typeConstraint;
3379 if (constraint.hasConstraint() &&
3380 !constraint.isTypeVar() &&
3381 !constraint.isTypeConstant()) {
3382 auto ctx = Context {
3383 finfo->func->unit,
3384 const_cast<php::Func*>(finfo->func),
3385 finfo->func->cls
3387 auto t = loosen_dvarrayness(
3388 data.m_index->lookup_constraint(ctx, constraint));
3389 return callCtx.args[i].strictlyMoreRefined(t);
3391 return callCtx.args[i].strictSubtypeOf(TInitCell);
3394 // TODO(#3788877): more heuristics here would be useful.
3395 bool const tryContextSensitive = [&] {
3396 if (finfo->func->noContextSensitiveAnalysis ||
3397 finfo->func->params.empty() ||
3398 interp_nesting_level + 1 >= max_interp_nexting_level ||
3399 returnType == TBottom) {
3400 return false;
3403 if (finfo->retParam != NoLocalId &&
3404 callCtx.args.size() > finfo->retParam &&
3405 checkParam(finfo->retParam)) {
3406 return true;
3409 if (!options.ContextSensitiveInterp) return false;
3411 if (callCtx.args.size() < finfo->func->params.size()) return true;
3412 for (auto i = 0; i < finfo->func->params.size(); i++) {
3413 if (checkParam(i)) return true;
3415 return false;
3416 }();
3418 if (!tryContextSensitive) {
3419 return returnType;
3422 auto maybe_loosen_staticness = [&] (const Type& ty) {
3423 return returnType.subtypeOf(BUnc) ? ty : loosen_staticness(ty);
3427 ContextRetTyMap::const_accessor acc;
3428 if (data.contextualReturnTypes.find(acc, callCtx)) {
3429 if (data.frozen || acc->second == TBottom || is_scalar(acc->second)) {
3430 return maybe_loosen_staticness(acc->second);
3435 if (data.frozen) {
3436 return returnType;
3439 auto contextType = [&] {
3440 ++interp_nesting_level;
3441 SCOPE_EXIT { --interp_nesting_level; };
3443 auto const calleeCtx = Context {
3444 finfo->func->unit,
3445 const_cast<php::Func*>(finfo->func),
3446 finfo->func->cls
3448 auto const ty =
3449 analyze_func_inline(*data.m_index, calleeCtx,
3450 callCtx.context, callCtx.args).inferredReturn;
3451 return return_with_context(ty, callCtx.context);
3452 }();
3454 if (!interp_nesting_level) {
3455 FTRACE(3,
3456 "Context sensitive type: {}\n"
3457 "Context insensitive type: {}\n",
3458 show(contextType), show(returnType));
3461 auto ret = intersection_of(std::move(returnType),
3462 std::move(contextType));
3464 ContextRetTyMap::accessor acc;
3465 if (data.contextualReturnTypes.insert(acc, callCtx) ||
3466 ret.strictSubtypeOf(acc->second)) {
3467 acc->second = ret;
3470 if (!interp_nesting_level) {
3471 ret = maybe_loosen_staticness(ret);
3472 FTRACE(3, "Context sensitive result: {}\n", show(ret));
3475 return ret;
3478 //////////////////////////////////////////////////////////////////////
3480 PrepKind func_param_prep(const php::Func* func,
3481 uint32_t paramId) {
3482 if (func->attrs & AttrInterceptable) return PrepKind::Unknown;
3483 if (paramId >= func->params.size()) {
3484 return PrepKind::Val;
3486 return func->params[paramId].inout ? PrepKind::InOut : PrepKind::Val;
3489 folly::Optional<uint32_t> func_num_inout(const php::Func* func) {
3490 if (func->attrs & AttrInterceptable) return folly::none;
3491 if (!func->hasInOutArgs) return 0;
3492 uint32_t count = 0;
3493 for (auto& p : func->params) count += p.inout;
3494 return count;
3497 template<class PossibleFuncRange>
3498 PrepKind prep_kind_from_set(PossibleFuncRange range, uint32_t paramId) {
3501 * In sinlge-unit mode, the range is not complete. Without konwing all
3502 * possible resolutions, HHBBC cannot deduce anything about by-ref vs by-val.
3503 * So the caller should make sure not calling this in single-unit mode.
3505 assert(RuntimeOption::RepoAuthoritative);
3507 if (begin(range) == end(range)) {
3509 * We can assume it's by value, because either we're calling a function
3510 * that doesn't exist (about to fatal), or we're going to an __call (which
3511 * never takes parameters by reference).
3513 * Or if we've got AllFuncsInterceptable we need to assume someone could
3514 * rename a function to the new name.
3516 return RuntimeOption::EvalJitEnableRenameFunction ?
3517 PrepKind::Unknown : PrepKind::Val;
3520 struct FuncFind {
3521 using F = const php::Func*;
3522 static F get(std::pair<SString,F> p) { return p.second; }
3523 static F get(const MethTabEntryPair* mte) { return mte->second.func; }
3526 folly::Optional<PrepKind> prep;
3527 for (auto& item : range) {
3528 switch (func_param_prep(FuncFind::get(item), paramId)) {
3529 case PrepKind::Unknown:
3530 return PrepKind::Unknown;
3531 case PrepKind::InOut:
3532 if (prep && *prep != PrepKind::InOut) return PrepKind::Unknown;
3533 prep = PrepKind::InOut;
3534 break;
3535 case PrepKind::Val:
3536 if (prep && *prep != PrepKind::Val) return PrepKind::Unknown;
3537 prep = PrepKind::Val;
3538 break;
3541 return *prep;
3544 template<class PossibleFuncRange>
3545 folly::Optional<uint32_t> num_inout_from_set(PossibleFuncRange range) {
3548 * In sinlge-unit mode, the range is not complete. Without konwing all
3549 * possible resolutions, HHBBC cannot deduce anything about inout args.
3550 * So the caller should make sure not calling this in single-unit mode.
3552 assert(RuntimeOption::RepoAuthoritative);
3554 if (begin(range) == end(range)) {
3556 * We can assume it's by value, because either we're calling a function
3557 * that doesn't exist (about to fatal), or we're going to an __call (which
3558 * never takes parameters by reference).
3560 * Or if we've got AllFuncsInterceptable we need to assume someone could
3561 * rename a function to the new name.
3563 if (RuntimeOption::EvalJitEnableRenameFunction) return folly::none;
3564 return 0;
3567 struct FuncFind {
3568 using F = const php::Func*;
3569 static F get(std::pair<SString,F> p) { return p.second; }
3570 static F get(const MethTabEntryPair* mte) { return mte->second.func; }
3573 folly::Optional<uint32_t> num;
3574 for (auto& item : range) {
3575 auto const n = func_num_inout(FuncFind::get(item));
3576 if (!n.hasValue()) return folly::none;
3577 if (num.hasValue() && n != num) return folly::none;
3578 num = n;
3580 return num;
3583 template<typename F> auto
3584 visit_parent_cinfo(const ClassInfo* cinfo, F fun) -> decltype(fun(cinfo)) {
3585 for (auto ci = cinfo; ci != nullptr; ci = ci->parent) {
3586 if (auto const ret = fun(ci)) return ret;
3587 if (ci->cls->attrs & AttrNoExpandTrait) continue;
3588 for (auto ct : ci->usedTraits) {
3589 if (auto const ret = visit_parent_cinfo(ct, fun)) {
3590 return ret;
3594 return {};
3597 PublicSPropEntry lookup_public_static_impl(
3598 const IndexData& data,
3599 const ClassInfo* cinfo,
3600 SString prop
3602 auto const noInfo = PublicSPropEntry{TInitCell, TInitCell, nullptr, 0, true};
3604 if (data.allPublicSPropsUnknown) return noInfo;
3606 const ClassInfo* knownCInfo = nullptr;
3607 auto const knownClsPart = visit_parent_cinfo(
3608 cinfo,
3609 [&] (const ClassInfo* ci) -> const PublicSPropEntry* {
3610 auto const it = ci->publicStaticProps.find(prop);
3611 if (it != end(ci->publicStaticProps)) {
3612 knownCInfo = ci;
3613 return &it->second;
3615 return nullptr;
3619 auto const unkPart = [&]() -> const Type* {
3620 auto unkIt = data.unknownClassSProps.find(prop);
3621 if (unkIt != end(data.unknownClassSProps)) {
3622 return &unkIt->second.first;
3624 return nullptr;
3625 }();
3627 if (knownClsPart == nullptr) {
3628 return noInfo;
3631 for (auto const& prop_it : knownCInfo->cls->properties) {
3632 if (prop_it.name == prop && (prop_it.attrs & AttrIsConst)) {
3633 return *knownClsPart;
3637 // NB: Inferred type can be TBottom here if the property is never set to a
3638 // value which can satisfy its type constraint. Such properties can't exist at
3639 // runtime.
3641 if (unkPart != nullptr) {
3642 return PublicSPropEntry {
3643 union_of(
3644 knownClsPart->inferredType,
3645 *unkPart
3647 knownClsPart->initializerType,
3648 nullptr,
3650 true
3653 return *knownClsPart;
3656 PublicSPropEntry lookup_public_static_impl(
3657 const IndexData& data,
3658 const php::Class* cls,
3659 SString name
3661 auto const classes = find_range(data.classInfo, cls->name);
3662 if (begin(classes) == end(classes) ||
3663 std::next(begin(classes)) != end(classes)) {
3664 return PublicSPropEntry{TInitCell, TInitCell, nullptr, 0, true};
3666 return lookup_public_static_impl(data, begin(classes)->second, name);
3669 Type lookup_public_prop_impl(
3670 const IndexData& data,
3671 const ClassInfo* cinfo,
3672 SString propName
3674 // Find a property declared in this class (or a parent) with the same name.
3675 const php::Class* knownCls = nullptr;
3676 auto const prop = visit_parent_cinfo(
3677 cinfo,
3678 [&] (const ClassInfo* ci) -> const php::Prop* {
3679 for (auto const& prop : ci->cls->properties) {
3680 if (prop.name == propName) {
3681 knownCls = ci->cls;
3682 return &prop;
3685 return nullptr;
3689 if (!prop) return TCell;
3690 // Make sure its non-static and public. Otherwise its another function's
3691 // problem.
3692 if (prop->attrs & (AttrStatic | AttrPrivate)) return TCell;
3694 // Get a type corresponding to its declared type-hint (if any).
3695 auto ty = adjust_type_for_prop(
3696 *data.m_index, *knownCls, &prop->typeConstraint, TCell
3698 // We might have to include the initial value which might be outside of the
3699 // type-hint.
3700 auto initialTy = loosen_all(from_cell(prop->val));
3701 if (!initialTy.subtypeOf(TUninit) && (prop->attrs & AttrSystemInitialValue)) {
3702 ty |= initialTy;
3704 return ty;
3707 //////////////////////////////////////////////////////////////////////
3711 //////////////////////////////////////////////////////////////////////
3712 namespace {
3713 template<typename T>
3714 void buildTypeInfoData(TypeInfoData<T>& tid,
3715 const ISStringToMany<const T>& tmap) {
3716 for (auto const& elm : tmap) {
3717 auto const t = elm.second;
3718 auto const addUser = [&] (SString rName) {
3719 tid.users[rName].push_back(t);
3720 auto const count = tmap.count(rName);
3721 tid.depCounts[t] += count ? count : 1;
3723 TypeHelper<T>::process_bases(t, addUser);
3725 if (!tid.depCounts.count(t)) {
3726 FTRACE(5, "Adding no-dep {} {}:{} to queue\n",
3727 TypeHelper<T>::name(), t->name, (void*)t);
3728 // make sure that closure is first, because we end up calling
3729 // preresolve directly on closures created by trait
3730 // flattening, which assumes all dependencies are satisfied.
3731 if (tid.queue.size() && t->name == s_Closure.get()) {
3732 tid.queue.push_back(tid.queue[0]);
3733 tid.queue[0] = t;
3734 } else {
3735 tid.queue.push_back(t);
3737 } else {
3738 FTRACE(6, "{} {}:{} has {} deps\n",
3739 TypeHelper<T>::name(), t->name, (void*)t, tid.depCounts[t]);
3742 tid.cqBack = tid.queue.size();
3743 tid.queue.resize(tmap.size());
3746 template<typename T>
3747 void preresolveTypes(NamingEnv<T>& env,
3748 TypeInfoData<T>& tid,
3749 const ISStringToMany<TypeInfo<T>>& tmap) {
3750 while(true) {
3751 auto const ix = tid.cqFront++;
3752 if (ix == tid.cqBack) {
3753 // we've consumed everything where all dependencies are
3754 // satisfied. There may still be some pseudo-cycles that can
3755 // be broken though.
3757 // eg if A extends B and B' extends A', we'll resolve B and
3758 // A', and then end up here, since both A and B' still have
3759 // one dependency. But both A and B' can be resolved at this
3760 // point
3761 for (auto it = tid.depCounts.begin();
3762 it != tid.depCounts.end();
3764 auto canResolve = true;
3765 auto const checkCanResolve = [&] (SString name) {
3766 if (canResolve) canResolve = tmap.count(name);
3768 TypeHelper<T>::process_bases(it->first, checkCanResolve);
3769 if (canResolve) {
3770 FTRACE(2, "Breaking pseudo-cycle for {} {}:{}\n",
3771 TypeHelper<T>::name(), it->first->name, (void*)it->first);
3772 tid.queue[tid.cqBack++] = it->first;
3773 it = tid.depCounts.erase(it);
3774 tid.hasPseudoCycles = true;
3775 } else {
3776 ++it;
3779 if (ix == tid.cqBack) {
3780 break;
3783 auto const t = tid.queue[ix];
3784 Trace::Bump bumper{
3785 Trace::hhbbc_index, kSystemLibBump, is_systemlib_part(*t->unit)
3787 (void)bumper;
3788 preresolve(env, t);
3791 } //namespace
3793 Index::Index(php::Program* program,
3794 rebuild* rebuild_exception)
3795 : m_data(std::make_unique<IndexData>(this))
3797 trace_time tracer("create index");
3799 m_data->arrTableBuilder.reset(new ArrayTypeTable::Builder());
3801 add_system_constants_to_index(*m_data);
3803 if (rebuild_exception) {
3804 for (auto& ca : rebuild_exception->class_aliases) {
3805 m_data->classAliases.insert(ca.first);
3806 m_data->classAliases.insert(ca.second);
3808 rebuild_exception->class_aliases.clear();
3812 trace_time trace_add_units("add units to index");
3813 for (auto& u : program->units) {
3814 add_unit_to_index(*m_data, *u);
3818 RecordInfoData rid;
3820 trace_time build_record_info_data("build recordinfo data");
3821 buildTypeInfoData(rid, m_data->records);
3825 trace_time preresolve_records("preresolve records");
3826 RecordNamingEnv env{program, *m_data, rid};
3827 preresolveTypes(env, rid, m_data->recordInfo);
3830 ClassInfoData cid;
3832 trace_time build_class_info_data("build classinfo data");
3833 buildTypeInfoData(cid, m_data->classes);
3837 trace_time preresolve_classes("preresolve classes");
3838 ClassNamingEnv env{program, *m_data, cid};
3839 preresolveTypes(env, cid, m_data->classInfo);
3842 mark_unique_entities(m_data->typeAliases,
3843 [&] (const php::TypeAlias* ta, bool flag) {
3844 attribute_setter(
3845 ta->attrs,
3846 flag &&
3847 !m_data->classInfo.count(ta->name) &&
3848 !m_data->records.count(ta->name) &&
3849 !m_data->classAliases.count(ta->name),
3850 AttrUnique);
3853 for (auto& rinfo : m_data->allRecordInfos) {
3854 auto const set = [&] {
3855 auto const recname = rinfo->rec->name;
3856 if (m_data->recordInfo.count(recname) != 1 ||
3857 m_data->typeAliases.count(recname) ||
3858 m_data->classes.count(recname) ||
3859 m_data->classAliases.count(recname)) {
3860 return false;
3862 if (rinfo->parent && !(rinfo->parent->rec->attrs & AttrUnique)) {
3863 return false;
3865 FTRACE(2, "Adding AttrUnique to record {}\n", recname->data());
3866 return true;
3867 }();
3868 attribute_setter(rinfo->rec->attrs, set, AttrUnique);
3871 // Iterate allClassInfos so that we visit parent classes before
3872 // child classes.
3873 for (auto& cinfo : m_data->allClassInfos) {
3874 auto const set = [&] {
3875 if (m_data->classInfo.count(cinfo->cls->name) != 1 ||
3876 m_data->typeAliases.count(cinfo->cls->name) ||
3877 m_data->records.count(cinfo->cls->name) ||
3878 m_data->classAliases.count(cinfo->cls->name)) {
3879 return false;
3881 if (cinfo->parent && !(cinfo->parent->cls->attrs & AttrUnique)) {
3882 return false;
3884 for (auto const i : cinfo->declInterfaces) {
3885 if (!(i->cls->attrs & AttrUnique)) return false;
3887 for (auto const t : cinfo->usedTraits) {
3888 if (!(t->cls->attrs & AttrUnique)) return false;
3890 FTRACE(2, "Adding AttrUnique to class {}\n", cinfo->cls->name->data());
3891 return true;
3892 }();
3893 attribute_setter(cinfo->cls->attrs, set, AttrUnique);
3896 mark_unique_entities(m_data->funcs,
3897 [&] (const php::Func* func, bool flag) {
3898 attribute_setter(func->attrs, flag, AttrUnique);
3901 m_data->funcInfo.resize(program->nextFuncId);
3903 // Part of the index building routines happens before the various asserted
3904 // index invariants hold. These each may depend on computations from
3905 // previous functions, so be careful changing the order here.
3906 compute_subclass_list(*m_data);
3907 clean_86reifiedinit_methods(*m_data); // uses the base class lists
3908 mark_no_override_methods(*m_data); // uses AttrUnique
3909 find_magic_methods(*m_data); // uses the subclass lists
3910 find_mocked_classes(*m_data);
3911 mark_const_props(*m_data);
3912 auto const logging = Trace::moduleEnabledRelease(Trace::hhbbc_time, 1);
3913 m_data->compute_iface_vtables = std::thread([&] {
3914 HphpSessionAndThread _{Treadmill::SessionKind::HHBBC};
3915 auto const enable =
3916 logging && !Trace::moduleEnabledRelease(Trace::hhbbc_time, 1);
3917 Trace::BumpRelease bumper(Trace::hhbbc_time, -1, enable);
3918 compute_iface_vtables(*m_data);
3921 define_func_families(*m_data); // AttrNoOverride, iface_vtables,
3922 // subclass_list
3924 check_invariants(*m_data);
3926 mark_no_override_classes(*m_data); // uses AttrUnique
3928 if (RuntimeOption::EvalCheckReturnTypeHints == 3) {
3929 trace_time tracer("initialize return types");
3930 std::vector<const php::Func*> all_funcs;
3931 all_funcs.reserve(m_data->funcs.size() + m_data->methods.size());
3932 for (auto const fn : m_data->funcs) {
3933 all_funcs.push_back(fn.second);
3935 for (auto const fn : m_data->methods) {
3936 all_funcs.push_back(fn.second);
3939 parallel::for_each(all_funcs, [&] (const php::Func* f) {
3940 init_return_type(f);
3945 // Defined here so IndexData is a complete type for the unique_ptr
3946 // destructor.
3947 Index::~Index() {}
3949 //////////////////////////////////////////////////////////////////////
3951 void Index::mark_persistent_types_and_functions(php::Program& program) {
3952 auto persist = [] (const php::Unit* unit) {
3953 return
3954 unit->persistent.load(std::memory_order_relaxed) &&
3955 unit->persistent_pseudomain.load(std::memory_order_relaxed);
3957 for (auto& unit : program.units) {
3958 auto const persistent = persist(unit.get());
3959 for (auto& f : unit->funcs) {
3960 attribute_setter(f->attrs,
3961 persistent && (f->attrs & AttrUnique),
3962 AttrPersistent);
3965 for (auto& t : unit->typeAliases) {
3966 attribute_setter(t->attrs,
3967 persistent && (t->attrs & AttrUnique),
3968 AttrPersistent);
3972 auto check_persistent_class = [&] (const ClassInfo& cinfo) {
3973 if (cinfo.parent && !(cinfo.parent->cls->attrs & AttrPersistent)) {
3974 return false;
3977 for (auto const intrf : cinfo.declInterfaces) {
3978 if (!(intrf->cls->attrs & AttrPersistent)) return false;
3981 return true;
3984 auto check_persistent_record = [&] (const RecordInfo& rinfo) {
3985 return !rinfo.parent || (rinfo.parent->rec->attrs & AttrPersistent);
3988 for (auto& c : m_data->allClassInfos) {
3989 attribute_setter(c->cls->attrs,
3990 (c->cls->attrs & AttrUnique) &&
3991 (persist(c->cls->unit) ||
3992 c->cls->parentName == s_Closure.get()) &&
3993 check_persistent_class(*c),
3994 AttrPersistent);
3997 for (auto& r : m_data->allRecordInfos) {
3998 attribute_setter(r->rec->attrs,
3999 (r->rec->attrs & AttrUnique) &&
4000 persist(r->rec->unit) &&
4001 check_persistent_record(*r),
4002 AttrPersistent);
4006 void Index::mark_no_bad_redeclare_props(php::Class& cls) const {
4008 * Keep a list of properties which have not yet been found to redeclare
4009 * anything inequivalently. Start out by putting everything on the list. Then
4010 * walk up the inheritance chain, removing collisions as we find them.
4012 std::vector<php::Prop*> props;
4013 for (auto& prop : cls.properties) {
4014 if (prop.attrs & (AttrStatic | AttrPrivate)) {
4015 // Static and private properties never redeclare anything so need not be
4016 // considered.
4017 attribute_setter(prop.attrs, true, AttrNoBadRedeclare);
4018 continue;
4020 attribute_setter(prop.attrs, false, AttrNoBadRedeclare);
4021 props.emplace_back(&prop);
4024 auto currentCls = [&]() -> const ClassInfo* {
4025 auto const rcls = resolve_class(&cls);
4026 if (rcls.val.left()) return nullptr;
4027 return rcls.val.right();
4028 }();
4029 // If there's one more than one resolution for the class, be conservative and
4030 // we'll treat everything as possibly redeclaring.
4031 if (!currentCls) props.clear();
4033 while (!props.empty()) {
4034 auto const parent = currentCls->parent;
4035 if (!parent) {
4036 // No parent. We're done, so anything left on the prop list is
4037 // AttrNoBadRedeclare.
4038 for (auto& prop : props) {
4039 attribute_setter(prop->attrs, true, AttrNoBadRedeclare);
4041 break;
4044 auto const findParentProp = [&] (SString name) -> const php::Prop* {
4045 for (auto& prop : parent->cls->properties) {
4046 if (prop.name == name) return &prop;
4048 for (auto& prop : parent->traitProps) {
4049 if (prop.name == name) return &prop;
4051 return nullptr;
4054 // Remove any properties which collide with the current class.
4056 auto const propRedeclares = [&] (php::Prop* prop) {
4057 auto const pprop = findParentProp(prop->name);
4058 if (!pprop) return false;
4060 // We found a property being redeclared. Check if the type-hints on
4061 // the two are equivalent.
4062 auto const equiv = [&] {
4063 auto const& tc1 = prop->typeConstraint;
4064 auto const& tc2 = pprop->typeConstraint;
4065 // Try the cheap check first, use the index otherwise. Two
4066 // type-constraints are equivalent if all the possible values of one
4067 // satisfies the other, and vice-versa.
4068 if (!tc1.maybeInequivalentForProp(tc2)) return true;
4069 return
4070 satisfies_constraint(
4071 Context{},
4072 lookup_constraint(Context{}, tc1),
4074 ) && satisfies_constraint(
4075 Context{},
4076 lookup_constraint(Context{}, tc2),
4080 // If the property in the parent is static or private, the property in
4081 // the child isn't actually redeclaring anything. Otherwise, if the
4082 // type-hints are equivalent, remove this property from further
4083 // consideration and mark it as AttrNoBadRedeclare.
4084 if ((pprop->attrs & (AttrStatic | AttrPrivate)) || equiv()) {
4085 attribute_setter(prop->attrs, true, AttrNoBadRedeclare);
4087 return true;
4090 props.erase(
4091 std::remove_if(props.begin(), props.end(), propRedeclares),
4092 props.end()
4095 currentCls = parent;
4098 auto const possibleOverride =
4099 std::any_of(
4100 cls.properties.begin(),
4101 cls.properties.end(),
4102 [&](const php::Prop& prop) { return !(prop.attrs & AttrNoBadRedeclare); }
4105 // Mark all resolutions of this class as having any possible bad redeclaration
4106 // props, even if there's not an unique resolution.
4107 for (auto& info : find_range(m_data->classInfo, cls.name)) {
4108 auto const cinfo = info.second;
4109 if (cinfo->cls != &cls) continue;
4110 cinfo->hasBadRedeclareProp = possibleOverride;
4115 * Rewrite the initial values for any AttrSystemInitialValue properties. If the
4116 * properties' type-hint does not admit null values, change the initial value to
4117 * one (if possible) to one that is not null. This is only safe to do so if the
4118 * property is not redeclared in a derived class or if the redeclaration does
4119 * not have a null system provided default value. Otherwise, a property can have
4120 * a null value (even if its type-hint doesn't allow it) without the JIT
4121 * realizing that its possible.
4123 * Note that this ignores any unflattened traits. This is okay because
4124 * properties pulled in from traits which match an already existing property
4125 * can't change the initial value. The runtime will clear AttrNoImplicitNullable
4126 * on any property pulled from the trait if it doesn't match an existing
4127 * property.
4129 void Index::rewrite_default_initial_values(php::Program& program) const {
4130 trace_time tracer("rewrite default initial values");
4133 * Use dataflow across the whole program class hierarchy. Start from the
4134 * classes which have no derived classes and flow up the hierarchy. We flow
4135 * the set of properties which have been assigned a null system provided
4136 * default value. If a property with such a null value flows into a class
4137 * which declares a property with the same name (and isn't static or private),
4138 * than that property is forced to be null as well.
4140 using PropSet = folly::F14FastSet<SString>;
4141 using OutState = folly::F14FastMap<const ClassInfo*, PropSet>;
4142 using Worklist = folly::F14FastSet<const ClassInfo*>;
4144 OutState outStates;
4145 outStates.reserve(m_data->allClassInfos.size());
4147 // List of Class' still to process this iteration
4148 using WorkList = std::vector<const ClassInfo*>;
4149 using WorkSet = folly::F14FastSet<const ClassInfo*>;
4151 WorkList workList;
4152 WorkSet workSet;
4153 auto const enqueue = [&] (const ClassInfo& cls) {
4154 auto const result = workSet.insert(&cls);
4155 if (!result.second) return;
4156 workList.emplace_back(&cls);
4159 // Start with all the leaf classes
4160 for (auto const& cinfo : m_data->allClassInfos) {
4161 auto const isLeaf = [&] {
4162 for (auto const& sub : cinfo->subclassList) {
4163 if (sub != cinfo.get()) return false;
4165 return true;
4166 }();
4167 if (isLeaf) enqueue(*cinfo);
4170 WorkList oldWorkList;
4171 int iter = 1;
4172 while (!workList.empty()) {
4173 FTRACE(
4174 4, "rewrite_default_initial_values round #{}: {} items\n",
4175 iter, workList.size()
4177 ++iter;
4179 std::swap(workList, oldWorkList);
4180 workList.clear();
4181 workSet.clear();
4182 for (auto const& cinfo : oldWorkList) {
4183 // Retrieve the set of properties which are flowing into this Class and
4184 // have to be null.
4185 auto inState = [&] () -> folly::Optional<PropSet> {
4186 PropSet in;
4187 for (auto const& sub : cinfo->subclassList) {
4188 if (sub == cinfo || sub->parent != cinfo) continue;
4189 auto const it = outStates.find(sub);
4190 if (it == outStates.end()) return folly::none;
4191 in.insert(it->second.begin(), it->second.end());
4193 return in;
4194 }();
4195 if (!inState) continue;
4197 // Modify the in-state depending on the properties declared on this Class
4198 auto const cls = cinfo->cls;
4199 for (auto const& prop : cls->properties) {
4200 if (prop.attrs & (AttrStatic | AttrPrivate)) {
4201 // Private or static properties can't be redeclared
4202 inState->erase(prop.name);
4203 continue;
4205 // Ignore properties which have actual user provided initial values or
4206 // are LateInit.
4207 if (!(prop.attrs & AttrSystemInitialValue) ||
4208 (prop.attrs & AttrLateInit)) {
4209 continue;
4211 // Forced to be null, nothing to do
4212 if (inState->count(prop.name) > 0) continue;
4214 // Its not forced to be null. Find a better default value. If its null
4215 // anyways, force any properties this redeclares to be null as well.
4216 auto const defaultValue = prop.typeConstraint.defaultValue();
4217 if (defaultValue.m_type == KindOfNull) inState->insert(prop.name);
4220 // Push the in-state to the out-state.
4221 auto const result = outStates.emplace(std::make_pair(cinfo, *inState));
4222 if (result.second) {
4223 if (cinfo->parent) enqueue(*cinfo->parent);
4224 } else {
4225 // There shouldn't be cycles in the inheritance tree, so the out state
4226 // of Class', once set, should never change.
4227 assertx(result.first->second == *inState);
4232 // Now that we've processed all the classes, rewrite the property initial
4233 // values, unless they are forced to be nullable.
4234 for (auto& unit : program.units) {
4235 for (auto& c : unit->classes) {
4236 if (is_closure(*c)) continue;
4238 auto const out = [&] () -> folly::Optional<PropSet> {
4239 folly::Optional<PropSet> props;
4240 auto const range = m_data->classInfo.equal_range(c->name);
4241 for (auto it = range.first; it != range.second; ++it) {
4242 if (it->second->cls != c.get()) continue;
4243 auto const outStateIt = outStates.find(it->second);
4244 if (outStateIt == outStates.end()) return folly::none;
4245 if (!props) props.emplace();
4246 props->insert(outStateIt->second.begin(), outStateIt->second.end());
4248 return props;
4249 }();
4251 for (auto& prop : c->properties) {
4252 auto const nullable = [&] {
4253 if (!(prop.attrs & (AttrStatic | AttrPrivate))) {
4254 if (!out || out->count(prop.name)) return true;
4256 if (!(prop.attrs & AttrSystemInitialValue)) return false;
4257 return prop.typeConstraint.defaultValue().m_type == KindOfNull;
4258 }();
4260 attribute_setter(prop.attrs, !nullable, AttrNoImplicitNullable);
4261 if (!(prop.attrs & AttrSystemInitialValue)) continue;
4262 if (prop.val.m_type == KindOfUninit) {
4263 assertx(prop.attrs & AttrLateInit);
4264 continue;
4267 prop.val = nullable
4268 ? make_tv<KindOfNull>()
4269 : prop.typeConstraint.defaultValue();
4275 bool Index::register_class_alias(SString orig, SString alias) const {
4276 auto check = [&] (SString name) {
4277 if (m_data->classAliases.count(name)) return true;
4279 auto const classes = find_range(m_data->classInfo, name);
4280 if (begin(classes) != end(classes)) {
4281 return !(begin(classes)->second->cls->attrs & AttrUnique);
4283 auto const tas = find_range(m_data->typeAliases, name);
4284 if (begin(tas) == end(tas)) return true;
4285 return !(begin(tas)->second->attrs & AttrUnique);
4287 if (check(orig) && check(alias)) return true;
4288 if (m_data->ever_frozen) return false;
4289 std::lock_guard<std::mutex> lock{m_data->pending_class_aliases_mutex};
4290 m_data->pending_class_aliases.emplace_back(orig, alias);
4291 return true;
4294 void Index::update_class_aliases() {
4295 if (m_data->pending_class_aliases.empty()) return;
4296 FTRACE(1, "Index needs rebuilding due to {} class aliases\n",
4297 m_data->pending_class_aliases.size());
4298 throw rebuild { std::move(m_data->pending_class_aliases) };
4301 const CompactVector<const php::Class*>*
4302 Index::lookup_closures(const php::Class* cls) const {
4303 auto const it = m_data->classClosureMap.find(cls);
4304 if (it != end(m_data->classClosureMap)) {
4305 return &it->second;
4307 return nullptr;
4310 const hphp_fast_set<php::Func*>*
4311 Index::lookup_extra_methods(const php::Class* cls) const {
4312 if (cls->attrs & AttrNoExpandTrait) return nullptr;
4313 auto const it = m_data->classExtraMethodMap.find(cls);
4314 if (it != end(m_data->classExtraMethodMap)) {
4315 return &it->second;
4317 return nullptr;
4320 //////////////////////////////////////////////////////////////////////
4322 res::Class Index::resolve_class(const php::Class* cls) const {
4324 ClassInfo* result = nullptr;
4326 auto const classes = find_range(m_data->classInfo, cls->name);
4327 for (auto it = begin(classes); it != end(classes); ++it) {
4328 auto const cinfo = it->second;
4329 if (cinfo->cls == cls) {
4330 if (result) {
4331 result = nullptr;
4332 break;
4334 result = cinfo;
4338 // The function is supposed to return a cinfo if we can uniquely resolve cls.
4339 // In repo mode, if there is only one cinfo, return it.
4340 // In non-repo mode, we don't know all the cinfo's. So "only one cinfo" does
4341 // not mean anything unless it is a built-in and we disable rename/intercept.
4342 if (result && (RuntimeOption::RepoAuthoritative ||
4343 (!RuntimeOption::EvalJitEnableRenameFunction &&
4344 cls->attrs & AttrBuiltin))) {
4345 return res::Class { this, result };
4348 // We know its a class, not an enum or type alias, so return
4349 // by name
4350 return res::Class { this, cls->name.get() };
4353 folly::Optional<res::Class> Index::resolve_class(Context ctx,
4354 SString clsName) const {
4355 clsName = normalizeNS(clsName);
4357 if (ctx.cls) {
4358 if (ctx.cls->name->isame(clsName)) {
4359 return resolve_class(ctx.cls);
4361 if (ctx.cls->parentName && ctx.cls->parentName->isame(clsName)) {
4362 if (auto const parent = resolve_class(ctx.cls).parent()) return parent;
4367 * If there's only one preresolved ClassInfo, we can give out a
4368 * specific res::Class for it. (Any other possible resolutions were
4369 * known to fatal, or it was actually unique.)
4371 auto const classes = find_range(m_data->classInfo, clsName);
4372 for (auto it = begin(classes); it != end(classes); ++it) {
4373 auto const cinfo = it->second;
4374 if (cinfo->cls->attrs & AttrUnique) {
4375 if (debug &&
4376 (std::next(it) != end(classes) ||
4377 m_data->typeAliases.count(clsName))) {
4378 std::fprintf(stderr, "non unique \"unique\" class: %s\n",
4379 cinfo->cls->name->data());
4380 while (++it != end(classes)) {
4381 std::fprintf(stderr, " and %s\n", it->second->cls->name->data());
4383 auto const typeAliases = find_range(m_data->typeAliases, clsName);
4385 for (auto ta = begin(typeAliases); ta != end(typeAliases); ++ta) {
4386 std::fprintf(stderr, " and type-alias %s\n",
4387 ta->second->name->data());
4389 always_assert(0);
4391 return res::Class { this, cinfo };
4393 break;
4396 // We refuse to have name-only resolutions of enums, or typeAliases,
4397 // so that all name only resolutions can be treated as objects.
4398 if (!m_data->enums.count(clsName) &&
4399 !m_data->typeAliases.count(clsName)) {
4400 return res::Class { this, clsName };
4403 return folly::none;
4406 folly::Optional<res::Class> Index::selfCls(const Context& ctx) const {
4407 if (!ctx.cls || is_used_trait(*ctx.cls)) return folly::none;
4408 return resolve_class(ctx.cls);
4411 folly::Optional<res::Class> Index::parentCls(const Context& ctx) const {
4412 if (!ctx.cls || !ctx.cls->parentName) return folly::none;
4413 if (auto const parent = resolve_class(ctx.cls).parent()) return parent;
4414 return resolve_class(ctx, ctx.cls->parentName);
4417 Index::ResolvedInfo<folly::Optional<res::Class>>
4418 Index::resolve_type_name(SString inName) const {
4419 auto const res = resolve_type_name_internal(inName);
4420 return {
4421 res.type,
4422 res.nullable,
4423 res.value.isNull()
4424 ? folly::none
4425 : folly::make_optional(res::Class{this, res.value})
4429 Index::ResolvedInfo<Either<SString,ClassInfo*>>
4430 Index::resolve_type_name_internal(SString inName) const {
4431 folly::Optional<hphp_fast_set<const void*>> seen;
4433 auto nullable = false;
4434 auto name = inName;
4436 for (unsigned i = 0; ; ++i) {
4437 name = normalizeNS(name);
4438 auto const rec_it = m_data->records.find(name);
4439 if (rec_it != m_data->records.end()) {
4440 return { AnnotType::Record, nullable, nullptr };
4442 auto const classes = find_range(m_data->classInfo, name);
4443 auto const cls_it = begin(classes);
4444 if (cls_it != end(classes)) {
4445 auto const cinfo = cls_it->second;
4446 if (!(cinfo->cls->attrs & AttrUnique)) {
4447 if (!m_data->enums.count(name) && !m_data->typeAliases.count(name)) {
4448 break;
4450 return { AnnotType::Object, false, nullptr };
4452 if (!(cinfo->cls->attrs & AttrEnum)) {
4453 return { AnnotType::Object, nullable, cinfo };
4455 auto const& tc = cinfo->cls->enumBaseTy;
4456 assert(!tc.isNullable());
4457 if (tc.type() != AnnotType::Object) {
4458 auto const type = tc.type() == AnnotType::Mixed ?
4459 AnnotType::ArrayKey : tc.type();
4460 return { type, nullable, tc.typeName() };
4462 name = tc.typeName();
4463 } else {
4464 auto const typeAliases = find_range(m_data->typeAliases, name);
4465 auto const ta_it = begin(typeAliases);
4466 if (ta_it == end(typeAliases)) break;
4467 auto const ta = ta_it->second;
4468 if (!(ta->attrs & AttrUnique)) {
4469 return { AnnotType::Object, false, nullptr };
4471 nullable = nullable || ta->nullable;
4472 if (ta->type != AnnotType::Object) {
4473 return { ta->type, nullable, ta->value.get() };
4475 name = ta->value;
4478 // deal with cycles. Since we don't expect to
4479 // encounter them, just use a counter until we hit a chain length
4480 // of 10, then start tracking the names we resolve.
4481 if (i == 10) {
4482 seen.emplace();
4483 seen->insert(name);
4484 } else if (i > 10) {
4485 if (!seen->insert(name).second) {
4486 return { AnnotType::Object, false, nullptr };
4491 return { AnnotType::Object, nullable, name };
4494 struct Index::ConstraintResolution {
4495 /* implicit */ ConstraintResolution(Type type)
4496 : type{std::move(type)}
4497 , maybeMixed{false} {}
4498 ConstraintResolution(folly::Optional<Type> type, bool maybeMixed)
4499 : type{std::move(type)}
4500 , maybeMixed{maybeMixed} {}
4502 folly::Optional<Type> type;
4503 bool maybeMixed;
4506 Index::ConstraintResolution Index::resolve_named_type(
4507 const Context& ctx, SString name, const Type& candidate) const {
4509 auto const res = resolve_type_name_internal(name);
4511 if (res.nullable && candidate.subtypeOf(BInitNull)) return TInitNull;
4513 if (res.type == AnnotType::Object) {
4514 auto resolve = [&] (const res::Class& rcls) -> folly::Optional<Type> {
4515 if (!interface_supports_non_objects(rcls.name()) ||
4516 candidate.subtypeOrNull(BObj)) {
4517 return subObj(rcls);
4520 if (candidate.subtypeOrNull(BArr)) {
4521 if (interface_supports_array(rcls.name())) return TArr;
4522 } else if (candidate.subtypeOrNull(BVec)) {
4523 if (interface_supports_vec(rcls.name())) return TVec;
4524 } else if (candidate.subtypeOrNull(BDict)) {
4525 if (interface_supports_dict(rcls.name())) return TDict;
4526 } else if (candidate.subtypeOrNull(BKeyset)) {
4527 if (interface_supports_keyset(rcls.name())) return TKeyset;
4528 } else if (candidate.subtypeOrNull(BStr)) {
4529 if (interface_supports_string(rcls.name())) return TStr;
4530 } else if (candidate.subtypeOrNull(BInt)) {
4531 if (interface_supports_int(rcls.name())) return TInt;
4532 } else if (candidate.subtypeOrNull(BDbl)) {
4533 if (interface_supports_double(rcls.name())) return TDbl;
4535 return folly::none;
4538 if (res.value.isNull()) return ConstraintResolution{ folly::none, true };
4540 auto ty = res.value.right() ?
4541 resolve({ this, res.value.right() }) :
4542 resolve({ this, res.value.left() });
4544 if (ty && res.nullable) *ty = opt(std::move(*ty));
4545 return ConstraintResolution{ std::move(ty), false };
4548 return get_type_for_annotated_type(ctx, res.type, res.nullable,
4549 res.value.left(), candidate);
4552 std::pair<res::Class,php::Class*>
4553 Index::resolve_closure_class(Context ctx, int32_t idx) const {
4554 auto const cls = ctx.unit->classes[idx].get();
4555 auto const rcls = resolve_class(cls);
4557 // Closure classes must be unique and defined in the unit that uses
4558 // the CreateCl opcode, so resolution must succeed.
4559 always_assert_flog(
4560 rcls.resolved(),
4561 "A Closure class ({}) failed to resolve",
4562 cls->name
4565 return { rcls, cls };
4568 res::Class Index::builtin_class(SString name) const {
4569 auto const rcls = resolve_class(Context {}, name);
4570 always_assert_flog(
4571 rcls.hasValue() &&
4572 rcls->val.right() &&
4573 (rcls->val.right()->cls->attrs & AttrBuiltin),
4574 "A builtin class ({}) failed to resolve",
4575 name->data()
4577 return *rcls;
4580 res::Func Index::resolve_method(Context ctx,
4581 Type clsType,
4582 SString name) const {
4583 auto name_only = [&] {
4584 return res::Func { this, res::Func::MethodName { name } };
4587 if (!is_specialized_cls(clsType)) {
4588 return name_only();
4590 auto const dcls = dcls_of(clsType);
4591 auto const cinfo = dcls.cls.val.right();
4592 if (!cinfo) return name_only();
4594 // Classes may have more method families than methods. Any such
4595 // method families are guaranteed to all be public so we can do this
4596 // lookup as a last gasp before resorting to name_only().
4597 auto const find_extra_method = [&] {
4598 auto methIt = cinfo->methodFamilies.find(name);
4599 if (methIt == end(cinfo->methodFamilies)) return name_only();
4600 if (methIt->second.possibleFuncs()->size() == 1) {
4601 return res::Func { this, methIt->second.possibleFuncs()->front() };
4603 // If there was a sole implementer we can resolve to a single method, even
4604 // if the method was not declared on the interface itself.
4605 return res::Func { this, &methIt->second };
4608 // Interfaces *only* have the extra methods defined for all
4609 // subclasses
4610 if (cinfo->cls->attrs & AttrInterface) return find_extra_method();
4613 * Whether or not the context class has a private method with the
4614 * same name as the method we're trying to call.
4616 auto const contextMayHavePrivateWithSameName = folly::lazy([&]() -> bool {
4617 if (!ctx.cls) return false;
4618 auto const range = find_range(m_data->classInfo, ctx.cls->name);
4619 if (begin(range) == end(range)) {
4620 // This class had no pre-resolved ClassInfos, which means it
4621 // always fatals in any way it could be defined, so it doesn't
4622 // matter what we return here (as all methods in the context
4623 // class are unreachable code).
4624 return true;
4626 // Because of traits, each instantiation of the class could have
4627 // different private methods; we need to check them all.
4628 for (auto ctxInfo : range) {
4629 auto const iter = ctxInfo.second->methods.find(name);
4630 if (iter != end(ctxInfo.second->methods) &&
4631 iter->second.attrs & AttrPrivate &&
4632 iter->second.topLevel) {
4633 return true;
4636 return false;
4640 * Look up the method in the target class.
4642 auto const methIt = cinfo->methods.find(name);
4643 if (methIt == end(cinfo->methods)) return find_extra_method();
4644 if (methIt->second.attrs & AttrInterceptable) return name_only();
4645 auto const ftarget = methIt->second.func;
4647 // We need to revisit the hasPrivateAncestor code if we start being
4648 // able to look up methods on interfaces (currently they have empty
4649 // method tables).
4650 assert(!(cinfo->cls->attrs & AttrInterface));
4653 * If our candidate method has a private ancestor, unless it is
4654 * defined on this class, we need to make sure we don't erroneously
4655 * resolve the overriding method if the call is coming from the
4656 * context the defines the private method.
4658 * For now this just gives up if the context and the callee class
4659 * could be related and the context defines a private of the same
4660 * name. (We should actually try to resolve that method, though.)
4662 if (methIt->second.hasPrivateAncestor &&
4663 ctx.cls &&
4664 ctx.cls != ftarget->cls) {
4665 if (could_be_related(ctx.cls, cinfo->cls)) {
4666 if (contextMayHavePrivateWithSameName()) {
4667 return name_only();
4673 * Note: this currently isn't exhaustively checking accessibility,
4674 * except in cases where we must do a little bit of it for
4675 * correctness.
4677 * It is generally ok to resolve a method that won't actually be
4678 * called as long, as we only do so in cases where it will fatal at
4679 * runtime.
4681 * So, in the presence of magic methods, we must handle the fact
4682 * that attempting to call an inaccessible method will instead call
4683 * the magic method, if it exists. Note that if any class derives
4684 * from a class and adds magic methods, it can change still change
4685 * dispatch to call that method instead of fatalling.
4688 // If false, this method is definitely accessible. If true, it may
4689 // or may not be accessible.
4690 auto const couldBeInaccessible = [&] {
4691 // Public is always accessible.
4692 if (methIt->second.attrs & AttrPublic) return false;
4693 // An anonymous context won't have access if it wasn't public.
4694 if (!ctx.cls) return true;
4695 // If the calling context class is the same as the target class,
4696 // and the method is defined on this class or is protected, it
4697 // must be accessible.
4698 if (ctx.cls == cinfo->cls &&
4699 (methIt->second.topLevel || methIt->second.attrs & AttrProtected)) {
4700 return false;
4702 // If the method is private, the above case is the only case where
4703 // we'd know it was accessible.
4704 if (methIt->second.attrs & AttrPrivate) return true;
4706 * For the protected method case: if the context class must be
4707 * derived from the class that first defined the protected method
4708 * we know it is accessible. First check against the class of the
4709 * method (or cinfo for trait methods).
4711 if (must_be_derived_from(
4712 ctx.cls,
4713 ftarget->cls->attrs & AttrTrait ? cinfo->cls : ftarget->cls)) {
4714 return false;
4716 if (methIt->second.hasAncestor ||
4717 (ftarget->cls->attrs & AttrTrait && !methIt->second.topLevel)) {
4718 // Now we have find the first class that defined the method, and
4719 // check if *that* is an ancestor of the context class.
4720 auto parent = cinfo->parent;
4721 while (true) {
4722 assertx(parent);
4723 auto it = parent->methods.find(name);
4724 assertx(it != parent->methods.end());
4725 if (!it->second.hasAncestor && it->second.topLevel) {
4726 if (must_be_derived_from(ctx.cls, parent->cls)) return false;
4727 break;
4729 parent = parent->parent;
4733 * On the other hand, if the class that defined the method must be
4734 * derived from the context class, it is going to be accessible as
4735 * long as the context class does not define a private method with
4736 * the same name. (If it did, we'd be calling that private
4737 * method, which currently we don't ever resolve---we've removed
4738 * it from the method table in the classInfo.)
4740 if (must_be_derived_from(cinfo->cls, ctx.cls)) {
4741 if (!contextMayHavePrivateWithSameName()) {
4742 return false;
4745 // Other cases we're not sure about (maybe some non-unique classes
4746 // got in the way). Conservatively return that it might be
4747 // inaccessible.
4748 return true;
4751 auto resolve = [&] {
4752 create_func_info(*m_data, ftarget);
4753 return res::Func { this, mteFromIt(methIt) };
4756 switch (dcls.type) {
4757 case DCls::Exact:
4758 if (cinfo->magicCall.thisHas) {
4759 if (couldBeInaccessible()) return name_only();
4761 return resolve();
4762 case DCls::Sub:
4763 if (cinfo->magicCall.derivedHas) {
4764 if (couldBeInaccessible()) return name_only();
4766 if (methIt->second.attrs & AttrNoOverride) {
4767 return resolve();
4769 if (!options.FuncFamilies) return name_only();
4772 auto const famIt = cinfo->methodFamilies.find(name);
4773 if (famIt == end(cinfo->methodFamilies)) {
4774 return name_only();
4776 if (famIt->second.containsInterceptables()) {
4777 return name_only();
4779 return res::Func { this, &famIt->second };
4782 not_reached();
4785 folly::Optional<res::Func>
4786 Index::resolve_ctor(Context /*ctx*/, res::Class rcls, bool exact) const {
4787 auto const cinfo = rcls.val.right();
4788 if (!cinfo) return folly::none;
4789 if (cinfo->cls->attrs & (AttrInterface|AttrTrait)) return folly::none;
4791 auto const cit = cinfo->methods.find(s_construct.get());
4792 if (cit == end(cinfo->methods)) return folly::none;
4794 auto const ctor = mteFromIt(cit);
4795 if (exact || ctor->second.attrs & AttrNoOverride) {
4796 if (ctor->second.attrs & AttrInterceptable) return folly::none;
4797 create_func_info(*m_data, ctor->second.func);
4798 return res::Func { this, ctor };
4801 if (!options.FuncFamilies) return folly::none;
4803 auto const famIt = cinfo->methodFamilies.find(s_construct.get());
4804 if (famIt == end(cinfo->methodFamilies)) return folly::none;
4805 if (famIt->second.containsInterceptables()) return folly::none;
4806 return res::Func { this, &famIt->second };
4809 template<class FuncRange>
4810 res::Func
4811 Index::resolve_func_helper(const FuncRange& funcs, SString name) const {
4812 auto name_only = [&] (bool renamable) {
4813 return res::Func { this, res::Func::FuncName { name, renamable } };
4816 // no resolution
4817 if (begin(funcs) == end(funcs)) return name_only(false);
4819 auto const func = begin(funcs)->second;
4820 if (func->attrs & AttrInterceptable) return name_only(true);
4822 // multiple resolutions
4823 if (std::next(begin(funcs)) != end(funcs)) {
4824 assert(!(func->attrs & AttrUnique));
4825 if (debug && any_interceptable_functions()) {
4826 for (auto const DEBUG_ONLY f : funcs) {
4827 assertx(!(f.second->attrs & AttrInterceptable));
4830 return name_only(false);
4833 // single resolution, in whole-program mode, that's it
4834 if (RuntimeOption::RepoAuthoritative) {
4835 assert(func->attrs & AttrUnique);
4836 return do_resolve(func);
4839 // single-unit mode, check builtins
4840 if (func->attrs & AttrBuiltin) {
4841 assert(func->attrs & AttrUnique);
4842 return do_resolve(func);
4845 // single-unit, non-builtin, not renamable
4846 return name_only(false);
4849 res::Func Index::resolve_func(Context /*ctx*/, SString name) const {
4850 name = normalizeNS(name);
4851 auto const funcs = find_range(m_data->funcs, name);
4852 return resolve_func_helper(funcs, name);
4856 * Gets a type for the constraint.
4858 * If getSuperType is true, the type could be a super-type of the
4859 * actual type constraint (eg TCell). Otherwise its guaranteed that
4860 * for any t, t.subtypeOf(get_type_for_constraint<false>(ctx, tc, t)
4861 * implies t would pass the constraint.
4863 * The candidate type is used to disambiguate; if we're applying a
4864 * Traversable constraint to a TObj, we should return
4865 * subObj(Traversable). If we're applying it to an Array, we should
4866 * return Array.
4868 template<bool getSuperType>
4869 Type Index::get_type_for_constraint(Context ctx,
4870 const TypeConstraint& tc,
4871 const Type& candidate) const {
4872 assertx(IMPLIES(!tc.isCheckable(), tc.isMixed()));
4874 if (getSuperType) {
4876 * Soft hints (@Foo) are not checked.
4878 if (tc.isSoft()) return TCell;
4881 auto const res = get_type_for_annotated_type(
4882 ctx,
4883 tc.type(),
4884 tc.isNullable(),
4885 tc.typeName(),
4886 candidate
4888 if (res.type) return *res.type;
4889 // If the type constraint might be mixed, then the value could be
4890 // uninit. Any other type constraint implies TInitCell.
4891 return getSuperType ? (res.maybeMixed ? TCell : TInitCell) : TBottom;
4894 bool Index::prop_tc_maybe_unenforced(const php::Class& propCls,
4895 const TypeConstraint& tc) const {
4896 assertx(tc.validForProp());
4897 if (RuntimeOption::EvalCheckPropTypeHints <= 2) return true;
4898 if (!tc.isCheckable()) return true;
4899 if (tc.isSoft()) return true;
4900 auto const res = get_type_for_annotated_type(
4901 Context { nullptr, nullptr, &propCls },
4902 tc.type(),
4903 tc.isNullable(),
4904 tc.typeName(),
4905 TCell
4907 return res.maybeMixed;
4910 Index::ConstraintResolution Index::get_type_for_annotated_type(
4911 Context ctx, AnnotType annot, bool nullable,
4912 SString name, const Type& candidate) const {
4914 if (candidate.subtypeOf(BInitNull) && nullable) {
4915 return TInitNull;
4918 auto mainType = [&]() -> ConstraintResolution {
4919 switch (getAnnotMetaType(annot)) {
4920 case AnnotMetaType::Precise: {
4921 auto const dt = getAnnotDataType(annot);
4923 switch (dt) {
4924 case KindOfNull: return TNull;
4925 case KindOfBoolean: return TBool;
4926 case KindOfInt64: return TInt;
4927 case KindOfDouble: return TDbl;
4928 case KindOfPersistentString:
4929 case KindOfString: return TStr;
4930 case KindOfPersistentVec:
4931 case KindOfVec: return TVec;
4932 case KindOfPersistentDict:
4933 case KindOfDict: return TDict;
4934 case KindOfPersistentKeyset:
4935 case KindOfKeyset: return TKeyset;
4936 case KindOfPersistentArray:
4937 case KindOfArray: return TPArr;
4938 case KindOfResource: return TRes;
4939 case KindOfClsMeth: return TClsMeth;
4940 case KindOfRecord: return TRecord;
4941 case KindOfObject:
4942 return resolve_named_type(ctx, name, candidate);
4943 case KindOfUninit:
4944 case KindOfFunc:
4945 case KindOfClass:
4946 always_assert_flog(false, "Unexpected DataType");
4947 break;
4949 break;
4951 case AnnotMetaType::Mixed:
4953 * Here we handle "mixed", typevars, and some other ignored
4954 * typehints (ex. "(function(..): ..)" typehints).
4956 return { TCell, true };
4957 case AnnotMetaType::Nothing:
4958 case AnnotMetaType::NoReturn:
4959 return TBottom;
4960 case AnnotMetaType::Nonnull:
4961 if (candidate.subtypeOf(BInitNull)) return TBottom;
4962 if (!candidate.couldBe(BInitNull)) return candidate;
4963 if (is_opt(candidate)) return unopt(candidate);
4964 break;
4965 case AnnotMetaType::This:
4966 if (auto s = selfCls(ctx)) return setctx(subObj(*s));
4967 break;
4968 case AnnotMetaType::Self:
4969 if (auto s = selfCls(ctx)) return subObj(*s);
4970 break;
4971 case AnnotMetaType::Parent:
4972 if (auto p = parentCls(ctx)) return subObj(*p);
4973 break;
4974 case AnnotMetaType::Callable:
4975 break;
4976 case AnnotMetaType::Number:
4977 return TNum;
4978 case AnnotMetaType::ArrayKey:
4979 if (candidate.subtypeOf(BInt)) return TInt;
4980 if (candidate.subtypeOf(BStr)) return TStr;
4981 return TArrKey;
4982 case AnnotMetaType::VArray:
4983 assertx(!RuntimeOption::EvalHackArrDVArrs);
4984 return TVArr;
4985 case AnnotMetaType::DArray:
4986 assertx(!RuntimeOption::EvalHackArrDVArrs);
4987 return TDArr;
4988 case AnnotMetaType::VArrOrDArr:
4989 assertx(!RuntimeOption::EvalHackArrDVArrs);
4990 return TArr;
4991 case AnnotMetaType::VecOrDict:
4992 if (candidate.subtypeOf(BVec)) return TVec;
4993 if (candidate.subtypeOf(BDict)) return TDict;
4994 break;
4995 case AnnotMetaType::ArrayLike:
4996 if (candidate.subtypeOf(BVArr)) return TVArr;
4997 if (candidate.subtypeOf(BDArr)) return TDArr;
4998 if (candidate.subtypeOf(BArr)) return TArr;
4999 if (candidate.subtypeOf(BVec)) return TVec;
5000 if (candidate.subtypeOf(BDict)) return TDict;
5001 if (candidate.subtypeOf(BKeyset)) return TKeyset;
5002 break;
5004 return ConstraintResolution{ folly::none, false };
5005 }();
5007 if (mainType.type && nullable) {
5008 if (mainType.type->subtypeOf(BBottom)) {
5009 if (candidate.couldBe(BInitNull)) {
5010 mainType.type = TInitNull;
5012 } else if (!mainType.type->couldBe(BInitNull)) {
5013 mainType.type = opt(*mainType.type);
5016 return mainType;
5019 Type Index::lookup_constraint(Context ctx,
5020 const TypeConstraint& tc,
5021 const Type& t) const {
5022 return get_type_for_constraint<true>(ctx, tc, t);
5025 bool Index::satisfies_constraint(Context ctx, const Type& t,
5026 const TypeConstraint& tc) const {
5027 // T45709201: Currently record types in HHBBC are not specialized.
5028 // Therefore, they can never satisfy a constrant.
5029 if (t.subtypeOf(BOptRecord)) return false;
5030 auto const tcType = get_type_for_constraint<false>(ctx, tc, t);
5031 if (t.moreRefined(loosen_dvarrayness(tcType))) {
5032 // For d/varrays, we might satisfy the constraint, but still not want to
5033 // optimize away the type-check (because we'll raise a notice on a d/varray
5034 // mismatch), so do some additional checking here to rule that out.
5035 if (!RuntimeOption::EvalHackArrCompatTypeHintNotices) return true;
5036 if (!tcType.subtypeOrNull(BArr) || tcType.subtypeOf(BNull)) return true;
5037 assertx(t.subtypeOrNull(BArr));
5038 if (tcType.subtypeOrNull(BVArr)) return t.subtypeOrNull(BVArr);
5039 if (tcType.subtypeOrNull(BDArr)) return t.subtypeOrNull(BDArr);
5040 if (tcType.subtypeOrNull(BPArr)) return t.subtypeOrNull(BPArr);
5042 return false;
5045 bool Index::could_have_reified_type(Context ctx,
5046 const TypeConstraint& tc) const {
5047 if (ctx.func->isClosureBody) {
5048 for (auto i = ctx.func->params.size();
5049 i < ctx.func->locals.size();
5050 ++i) {
5051 auto const name = ctx.func->locals[i].name;
5052 if (!name) return false; // named locals do not appear after unnamed local
5053 if (isMangledReifiedGenericInClosure(name)) return true;
5055 return false;
5057 if (!tc.isObject()) return false;
5058 auto const name = tc.typeName();
5059 auto const resolved = resolve_type_name_internal(name);
5060 if (resolved.type != AnnotType::Object) return false;
5061 res::Class rcls{this, resolved.value};
5062 return rcls.couldHaveReifiedGenerics();
5065 folly::Optional<bool>
5066 Index::supports_async_eager_return(res::Func rfunc) const {
5067 auto const supportsAER = [] (const php::Func* func) {
5068 // Async functions always support async eager return.
5069 if (func->isAsync && !func->isGenerator) return true;
5071 // No other functions support async eager return yet.
5072 return false;
5075 return match<folly::Optional<bool>>(
5076 rfunc.val,
5077 [&](res::Func::FuncName) { return folly::none; },
5078 [&](res::Func::MethodName) { return folly::none; },
5079 [&](FuncInfo* finfo) { return supportsAER(finfo->func); },
5080 [&](const MethTabEntryPair* mte) { return supportsAER(mte->second.func); },
5081 [&](FuncFamily* fam) -> folly::Optional<bool> {
5082 auto ret = folly::Optional<bool>{};
5083 for (auto const pf : fam->possibleFuncs()) {
5084 // Abstract functions are never called.
5085 if (pf->second.attrs & AttrAbstract) continue;
5086 auto const val = supportsAER(pf->second.func);
5087 if (ret && *ret != val) return folly::none;
5088 ret = val;
5090 return ret;
5094 bool Index::is_effect_free(const php::Func* func) const {
5095 return func_info(*m_data, func)->effectFree;
5098 bool Index::is_effect_free(res::Func rfunc) const {
5099 return match<bool>(
5100 rfunc.val,
5101 [&](res::Func::FuncName) { return false; },
5102 [&](res::Func::MethodName) { return false; },
5103 [&](FuncInfo* finfo) {
5104 return finfo->effectFree;
5106 [&](const MethTabEntryPair* mte) {
5107 return func_info(*m_data, mte->second.func)->effectFree;
5109 [&](FuncFamily* fam) {
5110 return false;
5114 bool Index::any_interceptable_functions() const {
5115 return m_data->any_interceptable_functions;
5118 const php::Const* Index::lookup_class_const_ptr(Context ctx,
5119 res::Class rcls,
5120 SString cnsName,
5121 bool allow_tconst) const {
5122 if (rcls.val.left()) return nullptr;
5123 auto const cinfo = rcls.val.right();
5125 auto const it = cinfo->clsConstants.find(cnsName);
5126 if (it != end(cinfo->clsConstants)) {
5127 if (!it->second->val.hasValue() ||
5128 (!allow_tconst && it->second->isTypeconst)) {
5129 // This is an abstract class constant or typeconstant
5130 return nullptr;
5132 if (it->second->val.value().m_type == KindOfUninit) {
5133 // This is a class constant that needs an 86cinit to run.
5134 // We'll add a dependency to make sure we're re-run if it
5135 // resolves anything.
5136 auto const cinit = it->second->cls->methods.back().get();
5137 assert(cinit->name == s_86cinit.get());
5138 add_dependency(*m_data, cinit, ctx, Dep::ClsConst);
5139 return nullptr;
5141 return it->second;
5143 return nullptr;
5146 Type Index::lookup_class_constant(Context ctx,
5147 res::Class rcls,
5148 SString cnsName,
5149 bool allow_tconst) const {
5150 auto const cnst = lookup_class_const_ptr(ctx, rcls, cnsName, allow_tconst);
5151 if (!cnst) return TInitCell;
5152 return from_cell(cnst->val.value());
5155 folly::Optional<Type> Index::lookup_constant(Context ctx,
5156 SString cnsName) const {
5157 ConstInfoConcurrentMap::const_accessor acc;
5158 if (!m_data->constants.find(acc, cnsName)) {
5159 // flag to indicate that the constant isn't in the index yet.
5160 if (options.HardConstProp) return folly::none;
5161 return TInitCell;
5164 if (acc->second.func &&
5165 !acc->second.readonly &&
5166 !acc->second.system &&
5167 !tv(acc->second.type)) {
5168 // we might refine the type
5169 add_dependency(*m_data, acc->second.func, ctx, Dep::ConstVal);
5172 return acc->second.type;
5175 folly::Optional<Cell> Index::lookup_persistent_constant(SString cnsName) const {
5176 if (!options.HardConstProp) return folly::none;
5177 ConstInfoConcurrentMap::const_accessor acc;
5178 if (!m_data->constants.find(acc, cnsName)) return folly::none;
5179 return tv(acc->second.type);
5182 bool Index::func_depends_on_arg(const php::Func* func, int arg) const {
5183 auto const& finfo = *func_info(*m_data, func);
5184 return arg >= finfo.unusedParams.size() || !finfo.unusedParams.test(arg);
5187 Type Index::lookup_foldable_return_type(Context ctx,
5188 const php::Func* func,
5189 Type ctxType,
5190 CompactVector<Type> args) const {
5191 constexpr auto max_interp_nexting_level = 2;
5192 static __thread uint32_t interp_nesting_level;
5193 static __thread Context base_ctx;
5195 // Don't fold functions when staticness mismatches
5196 if ((func->attrs & AttrStatic) && ctxType.couldBe(TObj)) return TTop;
5197 if (!(func->attrs & AttrStatic) && ctxType.couldBe(TCls)) return TTop;
5199 auto const& finfo = *func_info(*m_data, func);
5200 if (finfo.effectFree && is_scalar(finfo.returnTy)) {
5201 return finfo.returnTy;
5204 auto const calleeCtx = CallContext {
5205 func,
5206 std::move(args),
5207 std::move(ctxType)
5210 auto showArgs DEBUG_ONLY = [] (const CompactVector<Type>& a) {
5211 std::string ret, sep;
5212 for (auto& arg : a) {
5213 folly::format(&ret, "{}{}", sep, show(arg));
5214 sep = ",";
5216 return ret;
5220 ContextRetTyMap::const_accessor acc;
5221 if (m_data->foldableReturnTypeMap.find(acc, calleeCtx)) {
5222 FTRACE_MOD(
5223 Trace::hhbbc, 4,
5224 "Found foldableReturnType for {}{}{} with args {} (hash: {})\n",
5225 func->cls ? func->cls->name : staticEmptyString(),
5226 func->cls ? "::" : "",
5227 func->name,
5228 showArgs(calleeCtx.args),
5229 CallContextHashCompare{}.hash(calleeCtx));
5231 assertx(is_scalar(acc->second));
5232 return acc->second;
5236 if (frozen()) {
5237 FTRACE_MOD(
5238 Trace::hhbbc, 4,
5239 "MISSING: foldableReturnType for {}{}{} with args {} (hash: {})\n",
5240 func->cls ? func->cls->name : staticEmptyString(),
5241 func->cls ? "::" : "",
5242 func->name,
5243 showArgs(calleeCtx.args),
5244 CallContextHashCompare{}.hash(calleeCtx));
5245 return TTop;
5248 if (!interp_nesting_level) {
5249 base_ctx = ctx;
5250 } else if (interp_nesting_level > max_interp_nexting_level) {
5251 add_dependency(*m_data, func, base_ctx, Dep::InlineDepthLimit);
5252 return TTop;
5255 auto const contextType = [&] {
5256 ++interp_nesting_level;
5257 SCOPE_EXIT { --interp_nesting_level; };
5259 auto const fa = analyze_func_inline(
5260 *this,
5261 Context { func->unit, const_cast<php::Func*>(func), func->cls },
5262 calleeCtx.context,
5263 calleeCtx.args,
5264 CollectionOpts::EffectFreeOnly
5266 return fa.effectFree ? fa.inferredReturn : TTop;
5267 }();
5269 if (!is_scalar(contextType)) {
5270 return TTop;
5273 ContextRetTyMap::accessor acc;
5274 if (m_data->foldableReturnTypeMap.insert(acc, calleeCtx)) {
5275 acc->second = contextType;
5276 } else {
5277 // someone beat us to it
5278 assertx(acc->second == contextType);
5280 return contextType;
5283 Type Index::lookup_return_type(Context ctx, res::Func rfunc) const {
5284 return match<Type>(
5285 rfunc.val,
5286 [&](res::Func::FuncName) { return TInitCell; },
5287 [&](res::Func::MethodName) { return TInitCell; },
5288 [&](FuncInfo* finfo) {
5289 add_dependency(*m_data, finfo->func, ctx, Dep::ReturnTy);
5290 return unctx(finfo->returnTy);
5292 [&](const MethTabEntryPair* mte) {
5293 add_dependency(*m_data, mte->second.func, ctx, Dep::ReturnTy);
5294 auto const finfo = func_info(*m_data, mte->second.func);
5295 if (!finfo->func) return TInitCell;
5296 return unctx(finfo->returnTy);
5298 [&](FuncFamily* fam) {
5299 auto ret = TBottom;
5300 for (auto const pf : fam->possibleFuncs()) {
5301 add_dependency(*m_data, pf->second.func, ctx, Dep::ReturnTy);
5302 auto const finfo = func_info(*m_data, pf->second.func);
5303 if (!finfo->func) return TInitCell;
5304 ret |= unctx(finfo->returnTy);
5306 return ret;
5310 Type Index::lookup_return_type(Context caller,
5311 const CompactVector<Type>& args,
5312 const Type& context,
5313 res::Func rfunc) const {
5314 return match<Type>(
5315 rfunc.val,
5316 [&](res::Func::FuncName) {
5317 return lookup_return_type(caller, rfunc);
5319 [&](res::Func::MethodName) {
5320 return lookup_return_type(caller, rfunc);
5322 [&](FuncInfo* finfo) {
5323 add_dependency(*m_data, finfo->func, caller, Dep::ReturnTy);
5324 return context_sensitive_return_type(*m_data,
5325 { finfo->func, args, context });
5327 [&](const MethTabEntryPair* mte) {
5328 add_dependency(*m_data, mte->second.func, caller, Dep::ReturnTy);
5329 auto const finfo = func_info(*m_data, mte->second.func);
5330 if (!finfo->func) return TInitCell;
5331 return context_sensitive_return_type(*m_data,
5332 { finfo->func, args, context });
5334 [&] (FuncFamily* fam) {
5335 auto ret = TBottom;
5336 for (auto& pf : fam->possibleFuncs()) {
5337 add_dependency(*m_data, pf->second.func, caller, Dep::ReturnTy);
5338 auto const finfo = func_info(*m_data, pf->second.func);
5339 if (!finfo->func) ret |= TInitCell;
5340 else ret |= return_with_context(finfo->returnTy, context);
5342 return ret;
5347 CompactVector<Type>
5348 Index::lookup_closure_use_vars(const php::Func* func,
5349 bool move) const {
5350 assert(func->isClosureBody);
5352 auto const numUseVars = closure_num_use_vars(func);
5353 if (!numUseVars) return {};
5354 auto const it = m_data->closureUseVars.find(func->cls);
5355 if (it == end(m_data->closureUseVars)) {
5356 return CompactVector<Type>(numUseVars, TCell);
5358 if (move) return std::move(it->second);
5359 return it->second;
5362 Type Index::lookup_return_type_raw(const php::Func* f) const {
5363 auto it = func_info(*m_data, f);
5364 if (it->func) {
5365 assertx(it->func == f);
5366 return it->returnTy;
5368 return TInitCell;
5371 bool Index::lookup_this_available(const php::Func* f) const {
5372 return !(f->cls->attrs & AttrTrait) && !(f->attrs & AttrStatic);
5375 folly::Optional<uint32_t> Index::lookup_num_inout_params(
5376 Context,
5377 res::Func rfunc
5378 ) const {
5379 return match<folly::Optional<uint32_t>>(
5380 rfunc.val,
5381 [&] (res::Func::FuncName s) -> folly::Optional<uint32_t> {
5382 if (!RuntimeOption::RepoAuthoritative || s.renamable) return folly::none;
5383 return num_inout_from_set(find_range(m_data->funcs, s.name));
5385 [&] (res::Func::MethodName s) -> folly::Optional<uint32_t> {
5386 if (!RuntimeOption::RepoAuthoritative) return folly::none;
5387 auto const it = m_data->method_inout_params_by_name.find(s.name);
5388 if (it == end(m_data->method_inout_params_by_name)) {
5389 // There was no entry, so no method by this name takes a parameter
5390 // by inout.
5391 return 0;
5393 return num_inout_from_set(find_range(m_data->methods, s.name));
5395 [&] (FuncInfo* finfo) {
5396 return func_num_inout(finfo->func);
5398 [&] (const MethTabEntryPair* mte) {
5399 return func_num_inout(mte->second.func);
5401 [&] (FuncFamily* fam) {
5402 assert(RuntimeOption::RepoAuthoritative);
5403 return num_inout_from_set(fam->possibleFuncs());
5408 PrepKind Index::lookup_param_prep(Context /*ctx*/, res::Func rfunc,
5409 uint32_t paramId) const {
5410 return match<PrepKind>(
5411 rfunc.val,
5412 [&] (res::Func::FuncName s) {
5413 if (!RuntimeOption::RepoAuthoritative || s.renamable) return PrepKind::Unknown;
5414 return prep_kind_from_set(find_range(m_data->funcs, s.name), paramId);
5416 [&] (res::Func::MethodName s) {
5417 if (!RuntimeOption::RepoAuthoritative) return PrepKind::Unknown;
5418 auto const it = m_data->method_inout_params_by_name.find(s.name);
5419 if (it == end(m_data->method_inout_params_by_name)) {
5420 // There was no entry, so no method by this name takes a parameter
5421 // by reference.
5422 return PrepKind::Val;
5425 * If we think it's supposed to be PrepKind::InOut, we still can't be sure
5426 * unless we go through some effort to guarantee that it can't be going
5427 * to an __call function magically (which will never take anything by
5428 * ref).
5430 if (paramId < sizeof(it->second) * CHAR_BIT) {
5431 return ((it->second >> paramId) & 1) ?
5432 PrepKind::Unknown : PrepKind::Val;
5434 auto const kind = prep_kind_from_set(
5435 find_range(m_data->methods, s.name),
5436 paramId
5438 return kind == PrepKind::InOut ? PrepKind::Unknown : kind;
5440 [&] (FuncInfo* finfo) {
5441 return func_param_prep(finfo->func, paramId);
5443 [&] (const MethTabEntryPair* mte) {
5444 return func_param_prep(mte->second.func, paramId);
5446 [&] (FuncFamily* fam) {
5447 assert(RuntimeOption::RepoAuthoritative);
5448 return prep_kind_from_set(fam->possibleFuncs(), paramId);
5453 PropState
5454 Index::lookup_private_props(const php::Class* cls,
5455 bool move) const {
5456 auto it = m_data->privatePropInfo.find(cls);
5457 if (it != end(m_data->privatePropInfo)) {
5458 if (move) return std::move(it->second);
5459 return it->second;
5461 return make_unknown_propstate(
5462 cls,
5463 [&] (const php::Prop& prop) {
5464 return (prop.attrs & AttrPrivate) && !(prop.attrs & AttrStatic);
5469 PropState
5470 Index::lookup_private_statics(const php::Class* cls,
5471 bool move) const {
5472 auto it = m_data->privateStaticPropInfo.find(cls);
5473 if (it != end(m_data->privateStaticPropInfo)) {
5474 if (move) return std::move(it->second);
5475 return it->second;
5477 return make_unknown_propstate(
5478 cls,
5479 [&] (const php::Prop& prop) {
5480 return (prop.attrs & AttrPrivate) && (prop.attrs & AttrStatic);
5485 Type Index::lookup_public_static(Context ctx,
5486 const Type& cls,
5487 const Type& name) const {
5488 if (!is_specialized_cls(cls)) return TInitCell;
5490 auto const vname = tv(name);
5491 if (!vname || vname->m_type != KindOfPersistentString) return TInitCell;
5492 auto const sname = vname->m_data.pstr;
5494 if (ctx.unit) add_dependency(*m_data, sname, ctx, Dep::PublicSPropName);
5496 auto const dcls = dcls_of(cls);
5497 if (dcls.cls.val.left()) return TInitCell;
5498 auto const cinfo = dcls.cls.val.right();
5500 switch (dcls.type) {
5501 case DCls::Sub: {
5502 auto ty = TBottom;
5503 for (auto const sub : cinfo->subclassList) {
5504 ty |= lookup_public_static_impl(
5505 *m_data,
5506 sub,
5507 sname
5508 ).inferredType;
5510 return ty;
5512 case DCls::Exact:
5513 return lookup_public_static_impl(
5514 *m_data,
5515 cinfo,
5516 sname
5517 ).inferredType;
5519 always_assert(false);
5522 Type Index::lookup_public_static(Context ctx,
5523 const php::Class* cls,
5524 SString name) const {
5525 if (ctx.unit) add_dependency(*m_data, name, ctx, Dep::PublicSPropName);
5526 return lookup_public_static_impl(*m_data, cls, name).inferredType;
5529 bool Index::lookup_public_static_immutable(const php::Class* cls,
5530 SString name) const {
5531 return !lookup_public_static_impl(*m_data, cls, name).everModified;
5534 bool Index::lookup_public_static_maybe_late_init(const Type& cls,
5535 const Type& name) const {
5536 auto const cinfo = [&] () -> const ClassInfo* {
5537 if (!is_specialized_cls(cls)) {
5538 return nullptr;
5540 auto const dcls = dcls_of(cls);
5541 switch (dcls.type) {
5542 case DCls::Sub: return nullptr;
5543 case DCls::Exact: return dcls.cls.val.right();
5545 not_reached();
5546 }();
5547 if (!cinfo) return true;
5549 auto const vname = tv(name);
5550 if (!vname || (vname && vname->m_type != KindOfPersistentString)) {
5551 return true;
5553 auto const sname = vname->m_data.pstr;
5555 auto isLateInit = false;
5556 visit_parent_cinfo(
5557 cinfo,
5558 [&] (const ClassInfo* ci) -> bool {
5559 for (auto const& prop : ci->cls->properties) {
5560 if (prop.name == sname) {
5561 isLateInit = prop.attrs & AttrLateInit;
5562 return true;
5565 return false;
5568 return isLateInit;
5571 Type Index::lookup_public_prop(const Type& cls, const Type& name) const {
5572 if (!is_specialized_cls(cls)) return TCell;
5574 auto const vname = tv(name);
5575 if (!vname || vname->m_type != KindOfPersistentString) return TCell;
5576 auto const sname = vname->m_data.pstr;
5578 auto const dcls = dcls_of(cls);
5579 if (dcls.cls.val.left()) return TCell;
5580 auto const cinfo = dcls.cls.val.right();
5582 switch (dcls.type) {
5583 case DCls::Sub: {
5584 auto ty = TBottom;
5585 for (auto const sub : cinfo->subclassList) {
5586 ty |= lookup_public_prop_impl(
5587 *m_data,
5588 sub,
5589 sname
5592 return ty;
5594 case DCls::Exact:
5595 return lookup_public_prop_impl(
5596 *m_data,
5597 cinfo,
5598 sname
5601 always_assert(false);
5604 Type Index::lookup_public_prop(const php::Class* cls, SString name) const {
5605 auto const classes = find_range(m_data->classInfo, cls->name);
5606 if (begin(classes) == end(classes) ||
5607 std::next(begin(classes)) != end(classes)) {
5608 return TCell;
5610 return lookup_public_prop_impl(*m_data, begin(classes)->second, name);
5613 bool Index::lookup_class_init_might_raise(Context ctx, res::Class cls) const {
5614 return cls.val.match(
5615 [] (SString) { return true; },
5616 [&] (ClassInfo* cinfo) {
5617 // Check this class and all of its parents for possible inequivalent
5618 // redeclarations or bad initial values.
5619 do {
5620 // Be conservative for now if we have unflattened traits.
5621 if (!cinfo->traitProps.empty()) return true;
5622 if (cinfo->hasBadRedeclareProp) return true;
5623 if (cinfo->hasBadInitialPropValues) {
5624 add_dependency(*m_data, cinfo->cls, ctx, Dep::PropBadInitialValues);
5625 return true;
5627 cinfo = cinfo->parent;
5628 } while (cinfo);
5629 return false;
5634 void Index::join_iface_vtable_thread() const {
5635 if (m_data->compute_iface_vtables.joinable()) {
5636 m_data->compute_iface_vtables.join();
5640 Slot
5641 Index::lookup_iface_vtable_slot(const php::Class* cls) const {
5642 return folly::get_default(m_data->ifaceSlotMap, cls, kInvalidSlot);
5645 //////////////////////////////////////////////////////////////////////
5647 DependencyContext Index::dependency_context(const Context& ctx) const {
5648 return dep_context(*m_data, ctx);
5651 void Index::use_class_dependencies(bool f) {
5652 if (f != m_data->useClassDependencies) {
5653 m_data->dependencyMap.clear();
5654 m_data->useClassDependencies = f;
5658 void Index::init_public_static_prop_types() {
5659 for (auto const& cinfo : m_data->allClassInfos) {
5660 for (auto const& prop : cinfo->cls->properties) {
5661 if (!(prop.attrs & AttrPublic) || !(prop.attrs & AttrStatic)) {
5662 continue;
5666 * If the initializer type is TUninit, it means an 86sinit provides the
5667 * actual initialization type or it is AttrLateInit. So we don't want to
5668 * include the Uninit (which isn't really a user-visible type for the
5669 * property) or by the time we union things in we'll have inferred nothing
5670 * much.
5672 auto const initial = [&] {
5673 auto const tyRaw = from_cell(prop.val);
5674 if (tyRaw.subtypeOf(BUninit)) return TBottom;
5675 if (prop.attrs & AttrSystemInitialValue) return tyRaw;
5676 return adjust_type_for_prop(
5677 *this, *cinfo->cls, &prop.typeConstraint, tyRaw
5679 }();
5681 cinfo->publicStaticProps[prop.name] =
5682 PublicSPropEntry {
5683 union_of(
5684 adjust_type_for_prop(
5685 *this,
5686 *cinfo->cls,
5687 &prop.typeConstraint,
5688 TInitCell
5690 initial
5692 initial,
5693 &prop.typeConstraint,
5695 true
5701 void Index::refine_class_constants(
5702 const Context& ctx,
5703 const CompactVector<std::pair<size_t, TypedValue>>& resolved,
5704 DependencyContextSet& deps) {
5705 if (!resolved.size()) return;
5706 auto& constants = ctx.func->cls->constants;
5707 for (auto const& c : resolved) {
5708 assertx(c.first < constants.size());
5709 auto& cnst = constants[c.first];
5710 assertx(cnst.val && cnst.val->m_type == KindOfUninit);
5711 cnst.val = c.second;
5713 find_deps(*m_data, ctx.func, Dep::ClsConst, deps);
5716 void Index::refine_constants(const FuncAnalysisResult& fa,
5717 DependencyContextSet& deps) {
5718 auto const func = fa.ctx.func;
5719 for (auto const& it : fa.cnsMap) {
5720 if (it.second.m_type == kReadOnlyConstant) {
5721 // this constant was read, but there was nothing mentioning it
5722 // in the index. Should only happen on the first iteration. We
5723 // need to reprocess this func.
5724 assert(fa.readsUntrackedConstants);
5725 // if there's already an entry, we don't want to do anything,
5726 // otherwise just insert a dummy entry to indicate that it was
5727 // read.
5728 ConstInfoConcurrentMap::accessor acc;
5729 if (m_data->constants.insert(acc, it.first)) {
5730 acc->second = ConstInfo {func, TInitCell, false, true};
5732 continue;
5735 if (it.second.m_type == kDynamicConstant || !is_pseudomain(func)) {
5736 // two definitions, or a non-pseuodmain definition
5737 ConstInfoConcurrentMap::accessor acc;
5738 m_data->constants.insert(acc, it.first);
5739 auto& c = acc->second;
5740 if (!c.system) {
5741 c.func = nullptr;
5742 c.type = TInitCell;
5743 c.readonly = false;
5745 continue;
5748 auto t = it.second.m_type == KindOfUninit ?
5749 TInitCell : from_cell(it.second);
5751 assertx(t.equivalentlyRefined(unctx(t)));
5753 ConstInfoConcurrentMap::accessor acc;
5754 if (m_data->constants.insert(acc, it.first)) {
5755 acc->second = ConstInfo {func, t};
5756 continue;
5759 if (acc->second.system) continue;
5761 if (acc->second.readonly) {
5762 acc->second.func = func;
5763 acc->second.type = t;
5764 acc->second.readonly = false;
5765 continue;
5768 if (acc->second.func != func) {
5769 acc->second.func = nullptr;
5770 acc->second.type = TInitCell;
5771 continue;
5774 assertx(t.moreRefined(acc->second.type));
5775 if (!t.equivalentlyRefined(acc->second.type)) {
5776 acc->second.type = t;
5777 find_deps(*m_data, func, Dep::ConstVal, deps);
5780 if (fa.readsUntrackedConstants) deps.emplace(dep_context(*m_data, fa.ctx));
5783 void Index::fixup_return_type(const php::Func* func,
5784 Type& retTy) const {
5785 if (func->isGenerator) {
5786 if (func->isAsync) {
5787 // Async generators always return AsyncGenerator object.
5788 retTy = objExact(builtin_class(s_AsyncGenerator.get()));
5789 } else {
5790 // Non-async generators always return Generator object.
5791 retTy = objExact(builtin_class(s_Generator.get()));
5793 } else if (func->isAsync) {
5794 // Async functions always return WaitH<T>, where T is the type returned
5795 // internally.
5796 retTy = wait_handle(*this, std::move(retTy));
5800 void Index::init_return_type(const php::Func* func) {
5801 if ((func->attrs & AttrBuiltin) || func->isMemoizeWrapper) {
5802 return;
5805 auto make_type = [&] (const TypeConstraint& tc) {
5806 if (tc.isSoft() ||
5807 (RuntimeOption::EvalThisTypeHintLevel != 3 && tc.isThis())) {
5808 return TBottom;
5810 return loosen_dvarrayness(
5811 lookup_constraint(
5812 Context {
5813 func->unit,
5814 const_cast<php::Func*>(func),
5815 func->cls && func->cls->closureContextCls ?
5816 func->cls->closureContextCls : func->cls
5822 auto const finfo = create_func_info(*m_data, func);
5824 auto tcT = make_type(func->retTypeConstraint);
5825 if (tcT == TBottom) return;
5827 if (func->hasInOutArgs) {
5828 std::vector<Type> types;
5829 types.emplace_back(intersection_of(TInitCell, std::move(tcT)));
5830 for (auto& p : func->params) {
5831 if (!p.inout) continue;
5832 auto t = make_type(p.typeConstraint);
5833 if (t == TBottom) return;
5834 types.emplace_back(intersection_of(TInitCell, std::move(t)));
5836 tcT = vec(std::move(types), folly::none);
5839 tcT = to_cell(std::move(tcT));
5840 if (is_specialized_obj(tcT)) {
5841 if (dobj_of(tcT).cls.couldBeInterfaceOrTrait()) {
5842 tcT = is_opt(tcT) ? TOptObj : TObj;
5844 } else {
5845 tcT = loosen_all(std::move(tcT));
5847 FTRACE(4, "Pre-fixup return type for {}{}{}: {}\n",
5848 func->cls ? func->cls->name->data() : "",
5849 func->cls ? "::" : "",
5850 func->name, show(tcT));
5851 fixup_return_type(func, tcT);
5852 FTRACE(3, "Initial return type for {}{}{}: {}\n",
5853 func->cls ? func->cls->name->data() : "",
5854 func->cls ? "::" : "",
5855 func->name, show(tcT));
5856 finfo->returnTy = std::move(tcT);
5859 void Index::refine_return_info(const FuncAnalysisResult& fa,
5860 DependencyContextSet& deps) {
5861 auto const& t = fa.inferredReturn;
5862 auto const func = fa.ctx.func;
5863 auto const finfo = create_func_info(*m_data, func);
5865 auto error_loc = [&] {
5866 return folly::sformat(
5867 "{} {}{}",
5868 func->unit->filename,
5869 func->cls ?
5870 folly::to<std::string>(func->cls->name->data(), "::") : std::string{},
5871 func->name
5875 auto dep = Dep{};
5876 if (finfo->retParam == NoLocalId && fa.retParam != NoLocalId) {
5877 // This is just a heuristic; it doesn't mean that the value passed
5878 // in was returned, but that the value of the parameter at the
5879 // point of the RetC was returned. We use it to make (heuristic)
5880 // decisions about whether to do inline interps, so we only allow
5881 // it to change once (otherwise later passes might not do the
5882 // inline interp, and get worse results, which could trigger other
5883 // assertions in Index::refine_*).
5884 dep = Dep::ReturnTy;
5885 finfo->retParam = fa.retParam;
5888 auto unusedParams = ~fa.usedParams;
5889 if (finfo->unusedParams != unusedParams) {
5890 dep = Dep::ReturnTy;
5891 always_assert_flog(
5892 (finfo->unusedParams | unusedParams) == unusedParams,
5893 "Index unusedParams decreased in {}.\n",
5894 error_loc()
5896 finfo->unusedParams = unusedParams;
5899 if (t.strictlyMoreRefined(finfo->returnTy)) {
5900 if (finfo->returnRefinments + 1 < options.returnTypeRefineLimit) {
5901 finfo->returnTy = t;
5902 ++finfo->returnRefinments;
5903 dep = is_scalar(t) ?
5904 Dep::ReturnTy | Dep::InlineDepthLimit : Dep::ReturnTy;
5905 } else {
5906 FTRACE(1, "maxed out return type refinements at {}\n", error_loc());
5908 } else {
5909 always_assert_flog(
5910 t.moreRefined(finfo->returnTy),
5911 "Index return type invariant violated in {}.\n"
5912 " {} is not at least as refined as {}\n",
5913 error_loc(),
5914 show(t),
5915 show(finfo->returnTy)
5919 always_assert_flog(
5920 !finfo->effectFree || fa.effectFree,
5921 "Index effectFree changed from true to false in {} {}{}.\n",
5922 func->unit->filename,
5923 func->cls ? folly::to<std::string>(func->cls->name->data(), "::") :
5924 std::string{},
5925 func->name);
5927 if (finfo->effectFree != fa.effectFree) {
5928 finfo->effectFree = fa.effectFree;
5929 dep = Dep::InlineDepthLimit | Dep::ReturnTy;
5932 if (dep != Dep{}) find_deps(*m_data, func, dep, deps);
5935 bool Index::refine_closure_use_vars(const php::Class* cls,
5936 const CompactVector<Type>& vars) {
5937 assert(is_closure(*cls));
5939 for (auto i = uint32_t{0}; i < vars.size(); ++i) {
5940 always_assert_flog(
5941 vars[i].equivalentlyRefined(unctx(vars[i])),
5942 "Closure cannot have a used var with a context dependent type"
5946 auto& current = [&] () -> CompactVector<Type>& {
5947 std::lock_guard<std::mutex> _{closure_use_vars_mutex};
5948 return m_data->closureUseVars[cls];
5949 }();
5951 always_assert(current.empty() || current.size() == vars.size());
5952 if (current.empty()) {
5953 current = vars;
5954 return true;
5957 auto changed = false;
5958 for (auto i = uint32_t{0}; i < vars.size(); ++i) {
5959 always_assert(vars[i].subtypeOf(current[i]));
5960 if (vars[i].strictSubtypeOf(current[i])) {
5961 changed = true;
5962 current[i] = vars[i];
5966 return changed;
5969 template<class Container>
5970 void refine_private_propstate(Container& cont,
5971 const php::Class* cls,
5972 const PropState& state) {
5973 assertx(!is_used_trait(*cls));
5974 auto* elm = [&] () -> typename Container::value_type* {
5975 std::lock_guard<std::mutex> _{private_propstate_mutex};
5976 auto it = cont.find(cls);
5977 if (it == end(cont)) {
5978 cont[cls] = state;
5979 return nullptr;
5981 return &*it;
5982 }();
5984 if (!elm) return;
5986 for (auto& kv : state) {
5987 auto& target = elm->second[kv.first];
5988 assertx(target.tc == kv.second.tc);
5989 always_assert_flog(
5990 kv.second.ty.moreRefined(target.ty),
5991 "PropState refinement failed on {}::${} -- {} was not a subtype of {}\n",
5992 cls->name->data(),
5993 kv.first->data(),
5994 show(kv.second.ty),
5995 show(target.ty)
5997 target.ty = kv.second.ty;
6001 void Index::refine_private_props(const php::Class* cls,
6002 const PropState& state) {
6003 refine_private_propstate(m_data->privatePropInfo, cls, state);
6006 void Index::refine_private_statics(const php::Class* cls,
6007 const PropState& state) {
6008 // We can't store context dependent types in private statics since they
6009 // could be accessed using different contexts.
6010 auto cleanedState = PropState{};
6011 for (auto const& prop : state) {
6012 auto& elem = cleanedState[prop.first];
6013 elem.ty = unctx(prop.second.ty);
6014 elem.tc = prop.second.tc;
6017 refine_private_propstate(m_data->privateStaticPropInfo, cls, cleanedState);
6020 void Index::record_public_static_mutations(const php::Func& func,
6021 PublicSPropMutations mutations) {
6022 if (!mutations.m_data) {
6023 m_data->publicSPropMutations.erase(&func);
6024 return;
6026 m_data->publicSPropMutations.insert_or_assign(&func, std::move(mutations));
6029 void Index::update_static_prop_init_val(const php::Class* cls,
6030 SString name) const {
6031 for (auto& info : find_range(m_data->classInfo, cls->name)) {
6032 auto const cinfo = info.second;
6033 if (cinfo->cls != cls) continue;
6034 auto const it = cinfo->publicStaticProps.find(name);
6035 if (it != cinfo->publicStaticProps.end()) {
6036 it->second.initialValueResolved = true;
6041 void Index::refine_public_statics(DependencyContextSet& deps) {
6042 trace_time update("update public statics");
6044 // Union together the mutations for each function, including the functions
6045 // which weren't analyzed this round.
6046 auto nothing_known = false;
6047 PublicSPropMutations::UnknownMap unknown;
6048 PublicSPropMutations::KnownMap known;
6049 for (auto const& mutations : m_data->publicSPropMutations) {
6050 if (!mutations.second.m_data) continue;
6051 if (mutations.second.m_data->m_nothing_known) {
6052 nothing_known = true;
6053 break;
6056 for (auto const& kv : mutations.second.m_data->m_unknown) {
6057 auto const ret = unknown.insert(kv);
6058 if (!ret.second) ret.first->second |= kv.second;
6060 for (auto const& kv : mutations.second.m_data->m_known) {
6061 auto const ret = known.insert(kv);
6062 if (!ret.second) ret.first->second |= kv.second;
6066 if (nothing_known) {
6067 // We cannot go from knowing the types to not knowing the types (this is
6068 // equivalent to widening the types).
6069 always_assert(m_data->allPublicSPropsUnknown);
6070 return;
6073 auto const firstRefinement = m_data->allPublicSPropsUnknown;
6074 m_data->allPublicSPropsUnknown = false;
6076 if (firstRefinement) {
6077 // If this is the first refinement, reschedule any dependency which looked
6078 // at the public static property state previously.
6079 always_assert(m_data->unknownClassSProps.empty());
6080 for (auto const& dependency : m_data->dependencyMap) {
6081 if (dependency.first.tag() != DependencyContextType::PropName) continue;
6082 for (auto const& kv : dependency.second) {
6083 if (has_dep(kv.second, Dep::PublicSPropName)) deps.insert(kv.first);
6088 // Refine unknown class state
6089 for (auto const& kv : unknown) {
6090 // We can't keep context dependent types in public properties.
6091 auto newType = unctx(kv.second);
6092 auto it = m_data->unknownClassSProps.find(kv.first);
6093 if (it == end(m_data->unknownClassSProps)) {
6094 // If this is the first refinement, our previous state was effectively
6095 // TCell for everything, so inserting a type into the map can only
6096 // refine. However, if this isn't the first refinement, a name not present
6097 // in the map means that its TBottom, so we shouldn't be inserting
6098 // anything.
6099 always_assert(firstRefinement);
6100 m_data->unknownClassSProps.emplace(
6101 kv.first,
6102 std::make_pair(std::move(newType), 0)
6104 continue;
6108 * We may only shrink the types we recorded for each property. (If a
6109 * property type ever grows, the interpreter could infer something
6110 * incorrect at some step.)
6112 always_assert(!firstRefinement);
6113 always_assert_flog(
6114 newType.subtypeOf(it->second.first),
6115 "Static property index invariant violated for name {}:\n"
6116 " {} was not a subtype of {}",
6117 kv.first->data(),
6118 show(newType),
6119 show(it->second.first)
6122 // Put a limit on the refinements to ensure termination. Since we only ever
6123 // refine types, we can stop at any point and maintain correctness.
6124 if (it->second.second + 1 < options.publicSPropRefineLimit) {
6125 if (newType.strictSubtypeOf(it->second.first)) {
6126 find_deps(*m_data, it->first, Dep::PublicSPropName, deps);
6128 it->second.first = std::move(newType);
6129 ++it->second.second;
6130 } else {
6131 FTRACE(
6132 1, "maxed out public static property refinements for name {}\n",
6133 kv.first->data()
6138 // If we didn't see a mutation among all the functions for a particular name,
6139 // it means the type is TBottom. Iterate through the unknown class state and
6140 // remove any entries which we didn't see a mutation for.
6141 if (!firstRefinement) {
6142 auto it = begin(m_data->unknownClassSProps);
6143 auto last = end(m_data->unknownClassSProps);
6144 while (it != last) {
6145 auto const unknownIt = unknown.find(it->first);
6146 if (unknownIt == end(unknown)) {
6147 if (unknownIt->second != TBottom) {
6148 find_deps(*m_data, unknownIt->first, Dep::PublicSPropName, deps);
6150 it = m_data->unknownClassSProps.erase(it);
6151 } else {
6152 ++it;
6157 // Refine known class state
6158 for (auto const& cinfo : m_data->allClassInfos) {
6159 for (auto& kv : cinfo->publicStaticProps) {
6160 auto const newType = [&] {
6161 auto const it = known.find(
6162 PublicSPropMutations::KnownKey { cinfo.get(), kv.first }
6164 // If we didn't see a mutation, the type is TBottom.
6165 if (it == end(known)) return TBottom;
6166 // We can't keep context dependent types in public properties.
6167 return adjust_type_for_prop(
6168 *this, *cinfo->cls, kv.second.tc, unctx(it->second)
6170 }();
6172 if (kv.second.initialValueResolved) {
6173 for (auto& prop : cinfo->cls->properties) {
6174 if (prop.name != kv.first) continue;
6175 kv.second.initializerType = from_cell(prop.val);
6176 kv.second.initialValueResolved = false;
6177 break;
6179 assertx(!kv.second.initialValueResolved);
6182 // The type from the indexer doesn't contain the in-class initializer
6183 // types. Add that here.
6184 auto effectiveType = union_of(newType, kv.second.initializerType);
6187 * We may only shrink the types we recorded for each property. (If a
6188 * property type ever grows, the interpreter could infer something
6189 * incorrect at some step.)
6191 always_assert_flog(
6192 effectiveType.subtypeOf(kv.second.inferredType),
6193 "Static property index invariant violated on {}::{}:\n"
6194 " {} is not a subtype of {}",
6195 cinfo->cls->name->data(),
6196 kv.first->data(),
6197 show(effectiveType),
6198 show(kv.second.inferredType)
6200 always_assert(newType == TBottom || kv.second.everModified);
6202 // Put a limit on the refinements to ensure termination. Since we only
6203 // ever refine types, we can stop at any point and still maintain
6204 // correctness.
6205 if (kv.second.refinements + 1 < options.publicSPropRefineLimit) {
6206 if (effectiveType.strictSubtypeOf(kv.second.inferredType)) {
6207 find_deps(*m_data, kv.first, Dep::PublicSPropName, deps);
6209 kv.second.inferredType = std::move(effectiveType);
6210 kv.second.everModified = newType != TBottom;
6211 ++kv.second.refinements;
6212 } else {
6213 FTRACE(
6214 1, "maxed out public static property refinements for {}:{}\n",
6215 cinfo->cls->name->data(),
6216 kv.first->data()
6223 void Index::refine_bad_initial_prop_values(const php::Class* cls,
6224 bool value,
6225 DependencyContextSet& deps) {
6226 assertx(!is_used_trait(*cls));
6228 for (auto& info : find_range(m_data->classInfo, cls->name)) {
6229 auto const cinfo = info.second;
6230 if (cinfo->cls != cls) continue;
6231 always_assert_flog(
6232 cinfo->hasBadInitialPropValues || !value,
6233 "Bad initial prop values going from false to true on {}",
6234 cls->name->data()
6237 if (cinfo->hasBadInitialPropValues && !value) {
6238 cinfo->hasBadInitialPropValues = false;
6239 find_deps(*m_data, cls, Dep::PropBadInitialValues, deps);
6244 bool Index::frozen() const {
6245 return m_data->frozen;
6248 void Index::freeze() {
6249 m_data->frozen = true;
6250 m_data->ever_frozen = true;
6254 * Note that these functions run in separate threads, and
6255 * intentionally don't bump Trace::hhbbc_time. If you want to see
6256 * these times, set TRACE=hhbbc_time:1
6258 #define CLEAR(x) \
6260 trace_time _{"Clearing " #x}; \
6261 (x).clear(); \
6264 void Index::cleanup_for_final() {
6265 trace_time _{"cleanup_for_final"};
6266 CLEAR(m_data->dependencyMap);
6270 void Index::cleanup_post_emit() {
6271 trace_time _{"cleanup_post_emit"};
6273 trace_time t{"Reset allClassInfos"};
6274 parallel::for_each(m_data->allClassInfos, [] (auto& u) { u.reset(); });
6276 std::vector<std::function<void()>> clearers;
6277 #define CLEAR_PARALLEL(x) clearers.push_back([&] CLEAR(x));
6278 CLEAR_PARALLEL(m_data->classes);
6279 CLEAR_PARALLEL(m_data->methods);
6280 CLEAR_PARALLEL(m_data->method_inout_params_by_name);
6281 CLEAR_PARALLEL(m_data->funcs);
6282 CLEAR_PARALLEL(m_data->typeAliases);
6283 CLEAR_PARALLEL(m_data->enums);
6284 CLEAR_PARALLEL(m_data->constants);
6285 CLEAR_PARALLEL(m_data->records);
6286 CLEAR_PARALLEL(m_data->classAliases);
6288 CLEAR_PARALLEL(m_data->classClosureMap);
6289 CLEAR_PARALLEL(m_data->classExtraMethodMap);
6291 CLEAR_PARALLEL(m_data->allClassInfos);
6292 CLEAR_PARALLEL(m_data->classInfo);
6293 CLEAR_PARALLEL(m_data->funcInfo);
6295 CLEAR_PARALLEL(m_data->privatePropInfo);
6296 CLEAR_PARALLEL(m_data->privateStaticPropInfo);
6297 CLEAR_PARALLEL(m_data->unknownClassSProps);
6298 CLEAR_PARALLEL(m_data->publicSPropMutations);
6299 CLEAR_PARALLEL(m_data->ifaceSlotMap);
6300 CLEAR_PARALLEL(m_data->closureUseVars);
6302 CLEAR_PARALLEL(m_data->foldableReturnTypeMap);
6303 CLEAR_PARALLEL(m_data->contextualReturnTypes);
6305 parallel::for_each(clearers, [] (const std::function<void()>& f) { f(); });
6308 void Index::thaw() {
6309 m_data->frozen = false;
6312 std::unique_ptr<ArrayTypeTable::Builder>& Index::array_table_builder() const {
6313 return m_data->arrTableBuilder;
6316 //////////////////////////////////////////////////////////////////////
6318 res::Func Index::do_resolve(const php::Func* f) const {
6319 auto const finfo = create_func_info(*m_data, f);
6320 return res::Func { this, finfo };
6323 // Return true if we know for sure that one php::Class must derive
6324 // from another at runtime, in all possible instantiations.
6325 bool Index::must_be_derived_from(const php::Class* cls,
6326 const php::Class* parent) const {
6327 if (cls == parent) return true;
6328 auto const clsClasses = find_range(m_data->classInfo, cls->name);
6329 auto const parentClasses = find_range(m_data->classInfo, parent->name);
6330 for (auto& kvCls : clsClasses) {
6331 auto const rCls = res::Class { this, kvCls.second };
6332 for (auto& kvPar : parentClasses) {
6333 auto const rPar = res::Class { this, kvPar.second };
6334 if (!rCls.mustBeSubtypeOf(rPar)) return false;
6337 return true;
6340 // Return true if any possible definition of one php::Class could
6341 // derive from another at runtime, or vice versa.
6342 bool
6343 Index::could_be_related(const php::Class* cls,
6344 const php::Class* parent) const {
6345 if (cls == parent) return true;
6346 auto const clsClasses = find_range(m_data->classInfo, cls->name);
6347 auto const parentClasses = find_range(m_data->classInfo, parent->name);
6348 for (auto& kvCls : clsClasses) {
6349 auto const rCls = res::Class { this, kvCls.second };
6350 for (auto& kvPar : parentClasses) {
6351 auto const rPar = res::Class { this, kvPar.second };
6352 if (rCls.couldBe(rPar)) return true;
6355 return false;
6358 //////////////////////////////////////////////////////////////////////
6360 void PublicSPropMutations::merge(const Index& index,
6361 Context ctx,
6362 const Type& tcls,
6363 const Type& name,
6364 const Type& val,
6365 bool ignoreConst) {
6366 // Figure out which class this can affect. If we have a DCls::Sub we have to
6367 // assume it could affect any subclass, so we repeat this merge for all exact
6368 // class types deriving from that base.
6369 if (is_specialized_cls(tcls)) {
6370 auto const dcls = dcls_of(tcls);
6371 if (auto const cinfo = dcls.cls.val.right()) {
6372 switch (dcls.type) {
6373 case DCls::Exact:
6374 return merge(index, ctx, cinfo, name, val, ignoreConst);
6375 case DCls::Sub:
6376 for (auto const sub : cinfo->subclassList) {
6377 merge(index, ctx, sub, name, val, ignoreConst);
6379 return;
6381 not_reached();
6385 merge(index, ctx, nullptr, name, val, ignoreConst);
6388 void PublicSPropMutations::merge(const Index& index,
6389 Context ctx,
6390 ClassInfo* cinfo,
6391 const Type& name,
6392 const Type& val,
6393 bool ignoreConst) {
6394 FTRACE(2, "merge_public_static: {} {} {}\n",
6395 cinfo ? cinfo->cls->name->data() : "<unknown>", show(name), show(val));
6397 auto get = [this] () -> Data& {
6398 if (!m_data) m_data = std::make_unique<Data>();
6399 return *m_data;
6402 auto const vname = tv(name);
6403 auto const unknownName = !vname ||
6404 (vname && vname->m_type != KindOfPersistentString);
6406 if (!cinfo) {
6407 if (unknownName) {
6409 * We have a case here where we know neither the class nor the static
6410 * property name. This means we have to pessimize public static property
6411 * types for the entire program.
6413 * We could limit it to pessimizing them by merging the `val' type, but
6414 * instead we just throw everything away---this optimization is not
6415 * expected to be particularly useful on programs that contain any
6416 * instances of this situation.
6418 std::fprintf(
6419 stderr,
6420 "NOTE: had to mark everything unknown for public static "
6421 "property types due to dynamic code. -fanalyze-public-statics "
6422 "will not help for this program.\n"
6423 "NOTE: The offending code occured in this context: %s\n",
6424 show(ctx).c_str()
6426 get().m_nothing_known = true;
6427 return;
6430 auto const res = get().m_unknown.emplace(vname->m_data.pstr, val);
6431 if (!res.second) res.first->second |= val;
6432 return;
6436 * We don't know the name, but we know something about the class. We need to
6437 * merge the type for every property in the class hierarchy.
6439 if (unknownName) {
6440 visit_parent_cinfo(cinfo,
6441 [&] (const ClassInfo* ci) {
6442 for (auto& kv : ci->publicStaticProps) {
6443 merge(index, ctx, cinfo, sval(kv.first),
6444 val, ignoreConst);
6446 return false;
6448 return;
6452 * Here we know both the ClassInfo and the static property name, but it may
6453 * not actually be on this ClassInfo. In php, you can access base class
6454 * static properties through derived class names, and the access affects the
6455 * property with that name on the most-recently-inherited-from base class.
6457 * If the property is not found as a public property anywhere in the
6458 * hierarchy, we don't want to merge this type. Note we don't have to worry
6459 * about the case that there is a protected property in between, because this
6460 * is a fatal at class declaration time (you can't redeclare a public static
6461 * property with narrower access in a subclass).
6463 auto const affectedInfo = (
6464 visit_parent_cinfo(
6465 cinfo,
6466 [&] (const ClassInfo* ci) ->
6467 folly::Optional<std::pair<ClassInfo*, const TypeConstraint*>> {
6468 auto const it = ci->publicStaticProps.find(vname->m_data.pstr);
6469 if (it != end(ci->publicStaticProps)) {
6470 return std::make_pair(
6471 const_cast<ClassInfo*>(ci),
6472 it->second.tc
6475 return folly::none;
6480 if (!affectedInfo) {
6481 // Either this was a mutation that's going to fatal (property doesn't
6482 // exist), or it's a private static or a protected static. We aren't in
6483 // that business here, so we don't need to record anything.
6484 return;
6487 auto const affectedCInfo = affectedInfo->first;
6488 auto const affectedTC = affectedInfo->second;
6490 if (!ignoreConst) {
6491 for (auto const& prop : affectedCInfo->cls->properties) {
6492 if (prop.name == vname->m_data.pstr && (prop.attrs & AttrIsConst)) {
6493 return;
6498 auto const adjusted =
6499 adjust_type_for_prop(index, *affectedCInfo->cls, affectedTC, val);
6501 // Merge the property type.
6502 auto const res = get().m_known.emplace(
6503 KnownKey { affectedCInfo, vname->m_data.pstr },
6504 adjusted
6506 if (!res.second) res.first->second |= adjusted;
6509 void PublicSPropMutations::merge(const Index& index,
6510 Context ctx,
6511 const php::Class& cls,
6512 const Type& name,
6513 const Type& val,
6514 bool ignoreConst) {
6515 auto range = find_range(index.m_data->classInfo, cls.name);
6516 for (auto const& pair : range) {
6517 auto const cinfo = pair.second;
6518 if (cinfo->cls != &cls) continue;
6519 // Note that this works for both traits and regular classes
6520 for (auto const sub : cinfo->subclassList) {
6521 merge(index, ctx, sub, name, val, ignoreConst);
6526 //////////////////////////////////////////////////////////////////////