naming 2/2 - use naming_db_path provider
[hiphop-php.git] / hphp / hhbbc / index.cpp
blob5c13565a065e0c5ad0932f457da97baa275f230c
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
101 template<class T> using SStringToMany =
102 std::unordered_multimap<
103 SString,
105 string_data_hash,
106 string_data_same
110 * One-to-one case insensitive map, where the keys are static strings
111 * and the values are some T.
113 template<class T> using ISStringToOneT =
114 hphp_hash_map<
115 SString,
117 string_data_hash,
118 string_data_isame
122 * One-to-one case insensitive map, where the keys are static strings
123 * and the values are some T.
125 * Elements are not stable under insert/erase.
127 template<class T> using ISStringToOneFastT =
128 hphp_fast_map<
129 SString,
131 string_data_hash,
132 string_data_isame
136 * One-to-one case insensitive map, where the keys are static strings
137 * and the values are some kind of pointer.
139 template<class T> using ISStringToOne = ISStringToOneT<T*>;
141 template<class MultiMap>
142 folly::Range<typename MultiMap::const_iterator>
143 find_range(const MultiMap& map, typename MultiMap::key_type key) {
144 auto const pair = map.equal_range(key);
145 return folly::range(pair.first, pair.second);
148 // Like find_range, but copy them into a temporary buffer instead of
149 // returning iterators, so you can still mutate the underlying
150 // multimap.
151 template<class MultiMap>
152 std::vector<typename MultiMap::value_type>
153 copy_range(const MultiMap& map, typename MultiMap::key_type key) {
154 auto range = find_range(map, key);
155 return std::vector<typename MultiMap::value_type>(begin(range), end(range));
158 //////////////////////////////////////////////////////////////////////
160 Dep operator|(Dep a, Dep b) {
161 return static_cast<Dep>(
162 static_cast<uintptr_t>(a) | static_cast<uintptr_t>(b)
166 bool has_dep(Dep m, Dep t) {
167 return static_cast<uintptr_t>(m) & static_cast<uintptr_t>(t);
171 * Maps functions to contexts that depend on information about that
172 * function, with information about the type of dependency.
174 using DepMap =
175 tbb::concurrent_hash_map<
176 DependencyContext,
177 std::map<DependencyContext,Dep,DependencyContextLess>,
178 DependencyContextHashCompare
181 //////////////////////////////////////////////////////////////////////
184 * Each ClassInfo has a table of public static properties with these entries.
185 * The `initializerType' is for use during refine_public_statics, and
186 * inferredType will always be a supertype of initializerType.
188 struct PublicSPropEntry {
189 Type inferredType;
190 Type initializerType;
191 const TypeConstraint* tc;
192 uint32_t refinements;
193 bool everModified;
195 * This flag is set during analysis to indicate that we resolved the
196 * intial value (and updated it on the php::Class). This doesn't
197 * need to be atomic, because only one thread can resolve the value
198 * (the one processing the 86sinit), and it's been joined by the
199 * time we read the flag in refine_public_statics.
201 bool initialValueResolved;
205 * Entries in the ClassInfo method table need to track some additional
206 * information.
208 * The reason for this is that we need to record attributes of the
209 * class hierarchy.
211 struct MethTabEntry {
212 MethTabEntry(const php::Func* func, Attr a, bool hpa, bool tl) :
213 func(func), attrs(a), hasPrivateAncestor(hpa), topLevel(tl) {}
214 const php::Func* func = nullptr;
215 // A method could be imported from a trait, and its attributes changed
216 Attr attrs {};
217 bool hasAncestor = false;
218 bool hasPrivateAncestor = false;
219 // This method came from the ClassInfo that owns the MethTabEntry,
220 // or one of its used traits.
221 bool topLevel = false;
222 uint32_t idx = 0;
227 struct res::Func::MethTabEntryPair :
228 ISStringToOneT<MethTabEntry>::value_type {};
230 namespace {
232 using MethTabEntryPair = res::Func::MethTabEntryPair;
234 inline MethTabEntryPair* mteFromElm(
235 ISStringToOneT<MethTabEntry>::value_type& elm) {
236 return static_cast<MethTabEntryPair*>(&elm);
239 inline const MethTabEntryPair* mteFromElm(
240 const ISStringToOneT<MethTabEntry>::value_type& elm) {
241 return static_cast<const MethTabEntryPair*>(&elm);
244 inline MethTabEntryPair* mteFromIt(ISStringToOneT<MethTabEntry>::iterator it) {
245 return static_cast<MethTabEntryPair*>(&*it);
248 struct CallContextHashCompare {
249 bool equal(const CallContext& a, const CallContext& b) const {
250 return a == b;
253 size_t hash(const CallContext& c) const {
254 auto ret = folly::hash::hash_combine(
255 c.callee,
256 c.args.size(),
257 c.context.hash()
259 for (auto& t : c.args) {
260 ret = folly::hash::hash_combine(ret, t.hash());
262 return ret;
266 using ContextRetTyMap = tbb::concurrent_hash_map<
267 CallContext,
268 Type,
269 CallContextHashCompare
272 //////////////////////////////////////////////////////////////////////
274 template<class Filter>
275 PropState make_unknown_propstate(const php::Class* cls,
276 Filter filter) {
277 auto ret = PropState{};
278 for (auto& prop : cls->properties) {
279 if (filter(prop)) {
280 ret[prop.name].ty = TCell;
283 return ret;
289 * Currently inferred information about a PHP function.
291 * Nothing in this structure can ever be untrue. The way the
292 * algorithm works, whatever is in here must be factual (even if it is
293 * not complete information), because we may deduce other facts based
294 * on it.
296 struct res::Func::FuncInfo {
297 const php::Func* func = nullptr;
299 * The best-known return type of the function, if we have any
300 * information. May be TBottom if the function is known to never
301 * return (e.g. always throws).
303 Type returnTy = TInitCell;
306 * If the function always returns the same parameter, this will be
307 * set to its id; otherwise it will be NoLocalId.
309 LocalId retParam{NoLocalId};
312 * The number of times we've refined returnTy.
314 uint32_t returnRefinments{0};
317 * Whether the function is effectFree.
319 bool effectFree{false};
322 * Bitset representing which parameters definitely don't affect the
323 * result of the function, assuming it produces one. Note that
324 * VerifyParamType does not count as a use in this context.
326 std::bitset<64> unusedParams;
329 namespace {
331 //////////////////////////////////////////////////////////////////////
334 * Known information about a particular constant:
335 * - if system is true, it's a system constant and other definitions
336 * will be ignored.
337 * - for non-system constants, if func is non-null it's the unique
338 * pseudomain defining the constant; otherwise there was more than
339 * one definition, or a non-pseudomain definition, and the type will
340 * be TInitCell
341 * - readonly is true if we've only seen uses of the constant, and no
342 * definitions (this could change during the first pass, but not after
343 * that).
346 struct ConstInfo {
347 const php::Func* func;
348 Type type;
349 bool system;
350 bool readonly;
353 using FuncFamily = res::Func::FuncFamily;
354 using FuncInfo = res::Func::FuncInfo;
355 using MethTabEntryPair = res::Func::MethTabEntryPair;
357 //////////////////////////////////////////////////////////////////////
361 //////////////////////////////////////////////////////////////////////
364 * Sometimes function resolution can't determine which function
365 * something will call, but can restrict it to a family of functions.
367 * For example, if you want to call an abstract function on a base
368 * class with all unique derived classes, we will resolve the function
369 * to a FuncFamily that contains references to all the possible
370 * overriding-functions.
372 * Carefully pack it into 8 bytes, so that hphp_fast_map will use
373 * F14VectorMap.
375 struct res::Func::FuncFamily {
376 using PFuncVec = CompactVector<const MethTabEntryPair*>;
377 static_assert(sizeof(PFuncVec) == sizeof(uintptr_t),
378 "CompactVector must be layout compatible with a pointer");
380 struct Holder {
381 Holder(const Holder& o) : bits{o.bits} {}
382 explicit Holder(PFuncVec&& o) : v{std::move(o)} {}
383 explicit Holder(uintptr_t b) : bits{b & ~1} {}
384 Holder& operator=(const Holder&) = delete;
385 ~Holder() {}
386 const PFuncVec* operator->() const { return &v; }
387 uintptr_t val() const { return bits; }
388 friend auto begin(const Holder& h) { return h->begin(); }
389 friend auto end(const Holder& h) { return h->end(); }
390 private:
391 union {
392 uintptr_t bits;
393 PFuncVec v;
397 FuncFamily(PFuncVec&& v, bool containsInterceptables)
398 : m_raw{Holder{std::move(v)}.val()} {
399 if (containsInterceptables) m_raw |= 1;
401 FuncFamily(FuncFamily&& o) noexcept : m_raw(o.m_raw) {
402 o.m_raw = 0;
404 ~FuncFamily() {
405 Holder{m_raw & ~1}->~PFuncVec();
407 FuncFamily& operator=(const FuncFamily&) = delete;
409 bool containsInterceptables() const { return m_raw & 1; };
410 const Holder possibleFuncs() const {
411 return Holder{m_raw & ~1};
413 private:
414 uintptr_t m_raw;
417 //////////////////////////////////////////////////////////////////////
419 /* Known information about a particular possible instantiation of a
420 * PHP record. The php::Record will be marked AttrUnique if there is a unique
421 * RecordInfo with a given name.
423 struct RecordInfo {
424 const php::Record* rec = nullptr;
425 const RecordInfo* parent = nullptr;
427 * A vector of RecordInfo that encodes the inheritance hierarchy.
429 CompactVector<RecordInfo*> baseList;
430 const php::Record* phpType() const { return rec; }
434 * Known information about a particular possible instantiation of a
435 * PHP class. The php::Class will be marked AttrUnique if there is a
436 * unique ClassInfo with the same name.
438 struct ClassInfo {
440 * A pointer to the underlying php::Class that we're storing
441 * information about.
443 const php::Class* cls = nullptr;
446 * The info for the parent of this Class.
448 ClassInfo* parent = nullptr;
451 * A vector of the declared interfaces class info structures. This is in
452 * declaration order mirroring the php::Class interfaceNames vector, and does
453 * not include inherited interfaces.
455 CompactVector<const ClassInfo*> declInterfaces;
458 * A (case-insensitive) map from interface names supported by this class to
459 * their ClassInfo structures, flattened across the hierarchy.
461 ISStringToOneT<const ClassInfo*> implInterfaces;
464 * A (case-sensitive) map from class constant name to the php::Const
465 * that it came from. This map is flattened across the inheritance
466 * hierarchy.
468 hphp_fast_map<SString,const php::Const*> clsConstants;
471 * A vector of the used traits, in class order, mirroring the
472 * php::Class usedTraitNames vector.
474 CompactVector<const ClassInfo*> usedTraits;
477 * A list of extra properties supplied by this class's used traits.
479 CompactVector<php::Prop> traitProps;
482 * A (case-insensitive) map from class method names to the php::Func
483 * associated with it. This map is flattened across the inheritance
484 * hierarchy.
486 ISStringToOneT<MethTabEntry> methods;
489 * A (case-insensitive) map from class method names to associated
490 * FuncFamily objects that group the set of possibly-overriding
491 * methods.
493 * Note that this does not currently encode anything for interface
494 * methods.
496 * Invariant: methods on this class with AttrNoOverride or
497 * AttrPrivate will not have an entry in this map.
499 ISStringToOneFastT<FuncFamily> methodFamilies;
502 * Subclasses of this class, including this class itself.
504 * For interfaces, this is the list of instantiable classes that
505 * implement this interface.
507 * For traits, this is the list of classes that use the trait where
508 * the trait wasn't flattened into the class (including the trait
509 * itself).
511 * Note, unlike baseList, the order of the elements in this vector
512 * is unspecified.
514 CompactVector<ClassInfo*> subclassList;
517 * A vector of ClassInfo that encodes the inheritance hierarchy,
518 * unless this ClassInfo represents an interface.
520 * This is the list of base classes for this class in inheritance
521 * order.
523 CompactVector<ClassInfo*> baseList;
526 * Property types for public static properties, declared on this exact class
527 * (i.e. not flattened in the hierarchy).
529 * These maps always have an entry for each public static property declared
530 * in this class, so it can also be used to check if this class declares a
531 * public static property of a given name.
533 * Note: the effective type we can assume a given static property may hold is
534 * not just the value in these maps. To handle mutations of public statics
535 * where the name is known, but not which class was affected, these always
536 * need to be unioned with values from IndexData::unknownClassSProps.
538 hphp_hash_map<SString,PublicSPropEntry> publicStaticProps;
541 * Flags to track if this class is mocked, or if any of its dervied classes
542 * are mocked.
544 bool isMocked{false};
545 bool isDerivedMocked{false};
548 * Track if this class has a property which might redeclare a property in a
549 * parent class with an inequivalent type-hint.
551 bool hasBadRedeclareProp{true};
554 * Track if this class has any properties with initial values that might
555 * violate their type-hints.
557 bool hasBadInitialPropValues{true};
560 * Track if this class has any const props (including inherited ones).
562 bool hasConstProp{false};
565 * Track if any derived classes (including this one) have any const props.
567 bool derivedHasConstProp{false};
569 const php::Class* phpType() const { return cls; }
572 * Flags about the existence of various magic methods, or whether
573 * any derived classes may have those methods. The non-derived
574 * flags imply the derived flags, even if the class is final, so you
575 * don't need to check both in those situations.
577 struct MagicFnInfo {
578 bool thisHas{false};
579 bool derivedHas{false};
581 MagicFnInfo
582 magicCall,
583 magicGet,
584 magicSet,
585 magicIsset,
586 magicUnset,
587 magicBool;
590 struct MagicMapInfo {
591 StaticString name;
592 ClassInfo::MagicFnInfo ClassInfo::*pmem;
593 Attr attrBit;
596 const MagicMapInfo magicMethods[] {
597 { StaticString{"__call"}, &ClassInfo::magicCall, AttrNone },
598 { StaticString{"__toBoolean"}, &ClassInfo::magicBool, AttrNone },
599 { StaticString{"__get"}, &ClassInfo::magicGet, AttrNoOverrideMagicGet },
600 { StaticString{"__set"}, &ClassInfo::magicSet, AttrNoOverrideMagicSet },
601 { StaticString{"__isset"}, &ClassInfo::magicIsset, AttrNoOverrideMagicIsset },
602 { StaticString{"__unset"}, &ClassInfo::magicUnset, AttrNoOverrideMagicUnset }
604 //////////////////////////////////////////////////////////////////////
606 namespace res {
607 Record::Record(const Index* idx, Either<SString, RecordInfo*> val)
608 : index(idx)
609 , val(val)
612 bool Record::same(const Record& o) const {
613 return val == o.val;
616 bool Record::couldBe(const Record& o) const {
617 // If either types are not unique return true
618 if (val.left() || o.val.left()) return true;
620 auto r1 = val.right();
621 auto r2 = o.val.right();
622 assertx(r1 && r2);
623 // Both types are unique records so they "could be" if they are in an
624 // inheritance relationship
625 if (r1->baseList.size() >= r2->baseList.size()) {
626 return r1->baseList[r2->baseList.size() - 1] == r2;
627 } else {
628 return r2->baseList[r1->baseList.size() - 1] == r1;
632 SString Record::name() const {
633 return val.match(
634 [] (SString s) { return s; },
635 [] (RecordInfo* ri) { return ri->rec->name.get(); }
639 bool Record::mustBeSubtypeOf(const Record& o) const {
640 auto s1 = val.left();
641 auto s2 = o.val.left();
642 if (s1 || s2) return s1 == s2;
643 auto r1 = val.right();
644 auto r2 = o.val.right();
645 assertx(r1 && r2);
646 if (r1->baseList.size() >= r2->baseList.size()) {
647 return r1->baseList[r2->baseList.size() - 1] == r2;
649 return false;
652 bool Record::couldBeOverriden() const {
653 return val.match(
654 [] (SString) { return true; },
655 [] (RecordInfo* rinfo) {
656 return !(rinfo->rec->attrs & AttrFinal);
661 std::string show(const Record& r) {
662 return r.val.match(
663 [] (SString s) -> std::string {
664 return s->data();
666 [] (RecordInfo* rinfo) {
667 return folly::sformat("{}*", rinfo->rec->name);
672 folly::Optional<Record> Record::commonAncestor(const Record& r) const {
673 if (val.left() || r.val.left()) return folly::none;
674 auto const c1 = val.right();
675 auto const c2 = r.val.right();
676 // Walk the arrays of base classes until they match. For common ancestors
677 // to exist they must be on both sides of the baseList at the same positions
678 RecordInfo* ancestor = nullptr;
679 auto it1 = c1->baseList.begin();
680 auto it2 = c2->baseList.begin();
681 while (it1 != c1->baseList.end() && it2 != c2->baseList.end()) {
682 if (*it1 != *it2) break;
683 ancestor = *it1;
684 ++it1; ++it2;
686 if (ancestor == nullptr) {
687 return folly::none;
689 return res::Record { index, ancestor };
692 Class::Class(const Index* idx,
693 Either<SString,ClassInfo*> val)
694 : index(idx)
695 , val(val)
698 // Class type operations here are very conservative for now.
700 bool Class::same(const Class& o) const {
701 return val == o.val;
704 template <bool returnTrueOnMaybe>
705 bool Class::subtypeOfImpl(const Class& o) const {
706 auto s1 = val.left();
707 auto s2 = o.val.left();
708 if (s1 || s2) return returnTrueOnMaybe || s1 == s2;
709 auto c1 = val.right();
710 auto c2 = o.val.right();
712 // If c2 is an interface, see if c1 declared it.
713 if (c2->cls->attrs & AttrInterface) {
714 if (c1->implInterfaces.count(c2->cls->name)) {
715 return true;
717 return false;
720 // Otherwise check for direct inheritance.
721 if (c1->baseList.size() >= c2->baseList.size()) {
722 return c1->baseList[c2->baseList.size() - 1] == c2;
724 return false;
727 bool Class::mustBeSubtypeOf(const Class& o) const {
728 return subtypeOfImpl<false>(o);
731 bool Class::maybeSubtypeOf(const Class& o) const {
732 return subtypeOfImpl<true>(o);
735 bool Class::couldBe(const Class& o) const {
736 // If either types are not unique return true
737 if (val.left() || o.val.left()) return true;
739 auto c1 = val.right();
740 auto c2 = o.val.right();
741 // if one or the other is an interface return true for now.
742 // TODO(#3621433): better interface stuff
743 if (c1->cls->attrs & AttrInterface || c2->cls->attrs & AttrInterface) {
744 return true;
747 // Both types are unique classes so they "could be" if they are in an
748 // inheritance relationship
749 if (c1->baseList.size() >= c2->baseList.size()) {
750 return c1->baseList[c2->baseList.size() - 1] == c2;
751 } else {
752 return c2->baseList[c1->baseList.size() - 1] == c1;
756 SString Class::name() const {
757 return val.match(
758 [] (SString s) { return s; },
759 [] (ClassInfo* ci) { return ci->cls->name.get(); }
763 bool Class::couldBeInterfaceOrTrait() const {
764 return val.match(
765 [] (SString) { return true; },
766 [] (ClassInfo* cinfo) {
767 return (cinfo->cls->attrs & (AttrInterface | AttrTrait));
772 bool Class::couldBeInterface() const {
773 return val.match(
774 [] (SString) { return true; },
775 [] (ClassInfo* cinfo) {
776 return cinfo->cls->attrs & AttrInterface;
781 bool Class::mustBeInterface() const {
782 return val.match(
783 [] (SString) { return false; },
784 [] (ClassInfo* cinfo) {
785 return cinfo->cls->attrs & AttrInterface;
790 bool Class::couldBeOverriden() const {
791 return val.match(
792 [] (SString) { return true; },
793 [] (ClassInfo* cinfo) {
794 return !(cinfo->cls->attrs & AttrNoOverride);
799 bool Class::couldHaveMagicGet() const {
800 return val.match(
801 [] (SString) { return true; },
802 [] (ClassInfo* cinfo) {
803 return cinfo->magicGet.derivedHas;
808 bool Class::couldHaveMagicBool() const {
809 return val.match(
810 [] (SString) { return true; },
811 [] (ClassInfo* cinfo) {
812 return cinfo->magicBool.derivedHas;
817 bool Class::couldHaveMockedDerivedClass() const {
818 return val.match(
819 [] (SString) { return true;},
820 [] (ClassInfo* cinfo) {
821 return cinfo->isDerivedMocked;
826 bool Class::couldBeMocked() const {
827 return val.match(
828 [] (SString) { return true;},
829 [] (ClassInfo* cinfo) {
830 return cinfo->isMocked;
835 bool Class::couldHaveReifiedGenerics() const {
836 return val.match(
837 [] (SString) { return true; },
838 [] (ClassInfo* cinfo) {
839 return cinfo->cls->hasReifiedGenerics;
844 bool Class::mightCareAboutDynConstructs() const {
845 if (RuntimeOption::EvalForbidDynamicConstructs > 0) {
846 return val.match(
847 [] (SString) { return true; },
848 [] (ClassInfo* cinfo) {
849 return !(cinfo->cls->attrs & AttrDynamicallyConstructible);
853 return false;
856 bool Class::couldHaveConstProp() const {
857 return val.match(
858 [] (SString) { return true; },
859 [] (ClassInfo* cinfo) { return cinfo->hasConstProp; }
863 bool Class::derivedCouldHaveConstProp() const {
864 return val.match(
865 [] (SString) { return true; },
866 [] (ClassInfo* cinfo) { return cinfo->derivedHasConstProp; }
870 folly::Optional<Class> Class::commonAncestor(const Class& o) const {
871 if (val.left() || o.val.left()) return folly::none;
872 auto const c1 = val.right();
873 auto const c2 = o.val.right();
874 // Walk the arrays of base classes until they match. For common ancestors
875 // to exist they must be on both sides of the baseList at the same positions
876 ClassInfo* ancestor = nullptr;
877 auto it1 = c1->baseList.begin();
878 auto it2 = c2->baseList.begin();
879 while (it1 != c1->baseList.end() && it2 != c2->baseList.end()) {
880 if (*it1 != *it2) break;
881 ancestor = *it1;
882 ++it1; ++it2;
884 if (ancestor == nullptr) {
885 return folly::none;
887 return res::Class { index, ancestor };
890 folly::Optional<res::Class> Class::parent() const {
891 if (!val.right()) return folly::none;
892 auto parent = val.right()->parent;
893 if (!parent) return folly::none;
894 return res::Class { index, parent };
897 const php::Class* Class::cls() const {
898 return val.right() ? val.right()->cls : nullptr;
901 std::string show(const Class& c) {
902 return c.val.match(
903 [] (SString s) -> std::string {
904 return s->data();
906 [] (ClassInfo* cinfo) {
907 return folly::sformat("{}*", cinfo->cls->name);
912 Func::Func(const Index* idx, Rep val)
913 : index(idx)
914 , val(val)
917 SString Func::name() const {
918 return match<SString>(
919 val,
920 [&] (FuncName s) { return s.name; },
921 [&] (MethodName s) { return s.name; },
922 [&] (FuncInfo* fi) { return fi->func->name; },
923 [&] (const MethTabEntryPair* mte) { return mte->first; },
924 [&] (FuncFamily* fa) -> SString {
925 auto const name = fa->possibleFuncs()->front()->first;
926 if (debug) {
927 for (DEBUG_ONLY auto const f : fa->possibleFuncs()) {
928 assert(f->first->isame(name));
931 return name;
936 const php::Func* Func::exactFunc() const {
937 using Ret = const php::Func*;
938 return match<Ret>(
939 val,
940 [&](FuncName) { return Ret{}; },
941 [&](MethodName) { return Ret{}; },
942 [&](FuncInfo* fi) { return fi->func; },
943 [&](const MethTabEntryPair* mte) { return mte->second.func; },
944 [&](FuncFamily* /*fa*/) { return Ret{}; }
948 bool Func::cantBeMagicCall() const {
949 return match<bool>(
950 val,
951 [&](FuncName) { return true; },
952 [&](MethodName) { return false; },
953 [&](FuncInfo*) { return true; },
954 [&](const MethTabEntryPair*) { return true; },
955 [&](FuncFamily*) { return true; }
959 bool Func::isFoldable() const {
960 return match<bool>(val,
961 [&](FuncName) { return false; },
962 [&](MethodName) { return false; },
963 [&](FuncInfo* fi) {
964 return fi->func->attrs & AttrIsFoldable;
966 [&](const MethTabEntryPair* mte) {
967 return mte->second.func->attrs & AttrIsFoldable;
969 [&](FuncFamily* fa) {
970 return false;
974 bool Func::couldHaveReifiedGenerics() const {
975 return match<bool>(
976 val,
977 [&](FuncName s) { return true; },
978 [&](MethodName) { return true; },
979 [&](FuncInfo* fi) { return fi->func->isReified; },
980 [&](const MethTabEntryPair* mte) {
981 return mte->second.func->isReified;
983 [&](FuncFamily* fa) {
984 for (auto const pf : fa->possibleFuncs()) {
985 if (pf->second.func->isReified) return true;
987 return false;
991 bool Func::mightCareAboutDynCalls() const {
992 if (RuntimeOption::EvalNoticeOnBuiltinDynamicCalls && mightBeBuiltin()) {
993 return true;
995 auto const mightCareAboutFuncs =
996 RuntimeOption::EvalForbidDynamicCallsToFunc > 0;
997 auto const mightCareAboutInstMeth =
998 RuntimeOption::EvalForbidDynamicCallsToInstMeth > 0;
999 auto const mightCareAboutClsMeth =
1000 RuntimeOption::EvalForbidDynamicCallsToClsMeth > 0;
1002 return match<bool>(
1003 val,
1004 [&](FuncName) { return mightCareAboutFuncs; },
1005 [&](MethodName) {
1006 return mightCareAboutClsMeth || mightCareAboutInstMeth;
1008 [&](FuncInfo* fi) {
1009 return dyn_call_error_level(fi->func) > 0;
1011 [&](const MethTabEntryPair* mte) {
1012 return dyn_call_error_level(mte->second.func) > 0;
1014 [&](FuncFamily* fa) {
1015 for (auto const pf : fa->possibleFuncs()) {
1016 if (dyn_call_error_level(pf->second.func) > 0)
1017 return true;
1019 return false;
1024 bool Func::mightBeBuiltin() const {
1025 return match<bool>(
1026 val,
1027 // Builtins are always uniquely resolvable unless renaming is
1028 // involved.
1029 [&](FuncName s) { return s.renamable; },
1030 [&](MethodName) { return true; },
1031 [&](FuncInfo* fi) { return fi->func->attrs & AttrBuiltin; },
1032 [&](const MethTabEntryPair* mte) {
1033 return mte->second.func->attrs & AttrBuiltin;
1035 [&](FuncFamily* fa) {
1036 for (auto const pf : fa->possibleFuncs()) {
1037 if (pf->second.func->attrs & AttrBuiltin) return true;
1039 return false;
1044 uint32_t Func::minNonVariadicParams() const {
1045 auto const count = [] (const php::Func& f) -> uint32_t {
1046 for (size_t i = 0; i < f.params.size(); ++i) {
1047 if (f.params[i].isVariadic) return i;
1049 return f.params.size();
1052 return match<uint32_t>(
1053 val,
1054 [&] (FuncName) { return 0; },
1055 [&] (MethodName) { return 0; },
1056 [&] (FuncInfo* fi) { return count(*fi->func); },
1057 [&] (const MethTabEntryPair* mte) { return count(*mte->second.func); },
1058 [&] (FuncFamily* fa) {
1059 auto c = std::numeric_limits<uint32_t>::max();
1060 for (auto const pf : fa->possibleFuncs()) {
1061 c = std::min(c, count(*pf->second.func));
1063 return c;
1068 std::string show(const Func& f) {
1069 auto ret = f.name()->toCppString();
1070 match<void>(f.val,
1071 [&](Func::FuncName s) { if (s.renamable) ret += '?'; },
1072 [&](Func::MethodName) {},
1073 [&](FuncInfo* /*fi*/) { ret += "*"; },
1074 [&](const MethTabEntryPair* /*mte*/) { ret += "*"; },
1075 [&](FuncFamily* /*fa*/) { ret += "+"; });
1076 return ret;
1081 //////////////////////////////////////////////////////////////////////
1083 using IfaceSlotMap = hphp_hash_map<const php::Class*, Slot>;
1084 using ConstInfoConcurrentMap =
1085 tbb::concurrent_hash_map<SString, ConstInfo, StringDataHashCompare>;
1087 template <typename T>
1088 struct ResTypeHelper;
1090 template <>
1091 struct ResTypeHelper<res::Class> {
1092 using InfoT = ClassInfo;
1093 using InfoMapT = ISStringToMany<InfoT>;
1094 using OtherT = res::Record;
1095 static std::string name() { return "class"; }
1098 template <>
1099 struct ResTypeHelper<res::Record> {
1100 using InfoT = RecordInfo;
1101 using InfoMapT = ISStringToMany<InfoT>;
1102 using OtherT = res::Class;
1103 static std::string name() { return "record"; }
1106 struct Index::IndexData {
1107 explicit IndexData(Index* index) : m_index{index} {}
1108 IndexData(const IndexData&) = delete;
1109 IndexData& operator=(const IndexData&) = delete;
1110 ~IndexData() {
1111 if (compute_iface_vtables.joinable()) {
1112 compute_iface_vtables.join();
1116 Index* m_index;
1118 bool frozen{false};
1119 bool ever_frozen{false};
1120 bool any_interceptable_functions{false};
1122 std::unique_ptr<ArrayTypeTable::Builder> arrTableBuilder;
1124 ISStringToMany<const php::Class> classes;
1125 ISStringToMany<const php::Func> methods;
1126 ISStringToOneT<uint64_t> method_inout_params_by_name;
1127 ISStringToMany<const php::Func> funcs;
1128 ISStringToMany<const php::TypeAlias> typeAliases;
1129 ISStringToMany<const php::Class> enums;
1130 SStringToMany<const php::Constant> constants;
1131 ISStringToMany<const php::Record> records;
1133 // Map from each class to all the closures that are allocated in
1134 // functions of that class.
1135 hphp_hash_map<
1136 const php::Class*,
1137 CompactVector<const php::Class*>
1138 > classClosureMap;
1140 hphp_hash_map<
1141 const php::Class*,
1142 hphp_fast_set<php::Func*>
1143 > classExtraMethodMap;
1146 * Map from each class name to ClassInfo objects for all
1147 * not-known-to-be-impossible resolutions of the class at runtime.
1149 * If the class is unique, there will only be one resolution.
1150 * Otherwise there will be one for each possible path through the
1151 * inheritance hierarchy, potentially excluding cases that we know
1152 * would definitely fatal when defined.
1154 ISStringToMany<ClassInfo> classInfo;
1157 * All the ClassInfos, sorted topologically (ie all the parents,
1158 * interfaces and traits used by the ClassInfo at index K will have
1159 * indices less than K). This mostly drops out of the way ClassInfos
1160 * are created; it would be hard to create the ClassInfos for the
1161 * php::Class X (or even know how many to create) without knowing
1162 * all the ClassInfos that were created for X's dependencies.
1164 std::vector<std::unique_ptr<ClassInfo>> allClassInfos;
1167 * Map from each record name to RecordInfo objects for all
1168 * not-known-to-be-impossible resolutions of the record at runtime.
1170 * If the record is unique, there will only be one resolution.
1171 * Otherwise there will be one for each possible path through the
1172 * inheritance hierarchy, potentially excluding cases that we know
1173 * would definitely fatal when defined.
1175 ISStringToMany<RecordInfo> recordInfo;
1178 * All the RecordInfos, sorted topologically (ie all the parents of
1179 * RecordInfo at index K will have indices less than K).
1180 * This mostly drops out of the way RecordInfos are created;
1181 * it would be hard to create the RecordInfos for the
1182 * php::Record X (or even know how many to create) without knowing
1183 * all the RecordInfos that were created for X's dependencies.
1185 std::vector<std::unique_ptr<RecordInfo>> allRecordInfos;
1187 std::vector<FuncInfo> funcInfo;
1189 // Private instance and static property types are stored separately
1190 // from ClassInfo, because you don't need to resolve a class to get
1191 // at them.
1192 hphp_hash_map<
1193 const php::Class*,
1194 PropState
1195 > privatePropInfo;
1196 hphp_hash_map<
1197 const php::Class*,
1198 PropState
1199 > privateStaticPropInfo;
1202 * Public static property information:
1205 // If this is true, we don't know anything about public static properties and
1206 // must be pessimistic. We start in this state (before we've analyzed any
1207 // mutations) and remain in it if we see a mutation where both the name and
1208 // class are unknown.
1209 bool allPublicSPropsUnknown{true};
1211 // Best known types for public static properties where we knew the name, but
1212 // not the class. The type we're allowed to assume for a public static
1213 // property is the union of the ClassInfo-specific type with the unknown class
1214 // type that's stored here. The second value is the number of times the type
1215 // has been refined.
1216 hphp_hash_map<SString, std::pair<Type, uint32_t>> unknownClassSProps;
1218 // The set of gathered public static property mutations for each function. The
1219 // inferred types for the public static properties is the union of all these
1220 // mutations. If a function is not analyzed in a particular analysis round,
1221 // its mutations are left unchanged from the previous round.
1222 folly::ConcurrentHashMap<const php::Func*,
1223 PublicSPropMutations> publicSPropMutations;
1226 * Map from interfaces to their assigned vtable slots, computed in
1227 * compute_iface_vtables().
1229 IfaceSlotMap ifaceSlotMap;
1231 hphp_hash_map<
1232 const php::Class*,
1233 CompactVector<Type>
1234 > closureUseVars;
1236 bool useClassDependencies{};
1237 DepMap dependencyMap;
1240 * If a function is effect-free when called with a particular set of
1241 * literal arguments, and produces a literal result, there will be
1242 * an entry here representing the type.
1244 * The map isn't just an optimization; we can't call
1245 * analyze_func_inline during the optimization phase, because the
1246 * bytecode could be modified while we do so.
1248 ContextRetTyMap foldableReturnTypeMap;
1251 * Call-context sensitive return types are cached here. This is not
1252 * an optimization.
1254 * The reason we need to retain this information about the
1255 * calling-context-sensitive return types is that once the Index is
1256 * frozen (during the final optimization pass), calls to
1257 * lookup_return_type with a CallContext can't look at the bytecode
1258 * bodies of functions other than the calling function. So we need
1259 * to know what we determined the last time we were alloewd to do
1260 * that so we can return it again.
1262 ContextRetTyMap contextualReturnTypes{};
1264 std::thread compute_iface_vtables;
1266 template<typename T>
1267 const typename ResTypeHelper<T>::InfoMapT& infoMap() const;
1270 template<>
1271 const typename ResTypeHelper<res::Class>::InfoMapT&
1272 Index::IndexData::infoMap<res::Class>() const {
1273 return classInfo;
1275 template<>
1276 const typename ResTypeHelper<res::Record>::InfoMapT&
1277 Index::IndexData::infoMap<res::Record>() const {
1278 return recordInfo;
1281 //////////////////////////////////////////////////////////////////////
1283 namespace {
1285 //////////////////////////////////////////////////////////////////////
1287 using IndexData = Index::IndexData;
1289 std::mutex closure_use_vars_mutex;
1290 std::mutex private_propstate_mutex;
1292 DependencyContext make_dep(const php::Func* func) {
1293 return DependencyContext{DependencyContextType::Func, func};
1295 DependencyContext make_dep(const php::Class* cls) {
1296 return DependencyContext{DependencyContextType::Class, cls};
1298 DependencyContext make_dep(SString name) {
1299 return DependencyContext{DependencyContextType::PropName, name};
1302 DependencyContext dep_context(IndexData& data, const Context& ctx) {
1303 if (!ctx.cls || !data.useClassDependencies) return make_dep(ctx.func);
1304 auto const cls = ctx.cls->closureContextCls ?
1305 ctx.cls->closureContextCls : ctx.cls;
1306 if (is_used_trait(*cls)) return make_dep(ctx.func);
1307 return make_dep(cls);
1310 template <typename T>
1311 void add_dependency(IndexData& data,
1312 T src,
1313 const Context& dst,
1314 Dep newMask) {
1315 if (data.frozen) return;
1317 auto d = dep_context(data, dst);
1318 DepMap::accessor acc;
1319 data.dependencyMap.insert(acc, make_dep(src));
1320 auto& current = acc->second[d];
1321 current = current | newMask;
1324 std::mutex func_info_mutex;
1326 FuncInfo* create_func_info(IndexData& data, const php::Func* f) {
1327 auto fi = &data.funcInfo[f->idx];
1328 if (UNLIKELY(fi->func == nullptr)) {
1329 if (f->nativeInfo) {
1330 std::lock_guard<std::mutex> g{func_info_mutex};
1331 if (fi->func) {
1332 assert(fi->func == f);
1333 return fi;
1335 // We'd infer this anyway when we look at the bytecode body
1336 // (NativeImpl) for the HNI function, but just initializing it
1337 // here saves on whole-program iterations.
1338 fi->returnTy = native_function_return_type(f);
1340 fi->func = f;
1343 assert(fi->func == f);
1344 return fi;
1347 FuncInfo* func_info(IndexData& data, const php::Func* f) {
1348 auto const fi = &data.funcInfo[f->idx];
1349 return fi;
1352 template <typename T>
1353 void find_deps(IndexData& data,
1354 T src,
1355 Dep mask,
1356 DependencyContextSet& deps) {
1357 DepMap::const_accessor acc;
1358 if (data.dependencyMap.find(acc, make_dep(src))) {
1359 for (auto& kv : acc->second) {
1360 if (has_dep(kv.second, mask)) deps.insert(kv.first);
1365 struct TraitMethod {
1366 using class_type = const ClassInfo*;
1367 using method_type = const php::Func*;
1369 TraitMethod(class_type trait_, method_type method_, Attr modifiers_)
1370 : trait(trait_)
1371 , method(method_)
1372 , modifiers(modifiers_)
1375 class_type trait;
1376 method_type method;
1377 Attr modifiers;
1380 struct TMIOps {
1381 using string_type = LSString;
1382 using class_type = TraitMethod::class_type;
1383 using method_type = TraitMethod::method_type;
1385 struct TMIException : std::exception {
1386 explicit TMIException(std::string msg) : msg(msg) {}
1387 const char* what() const noexcept override { return msg.c_str(); }
1388 private:
1389 std::string msg;
1392 // Return the name for the trait class.
1393 static const string_type clsName(class_type traitCls) {
1394 return traitCls->cls->name;
1397 // Return the name for the trait method.
1398 static const string_type methName(method_type meth) {
1399 return meth->name;
1402 // Is-a methods.
1403 static bool isTrait(class_type traitCls) {
1404 return traitCls->cls->attrs & AttrTrait;
1406 static bool isAbstract(Attr modifiers) {
1407 return modifiers & AttrAbstract;
1410 static bool isAsync(method_type meth) {
1411 return meth->isAsync;
1413 static bool isStatic(method_type meth) {
1414 return meth->attrs & AttrStatic;
1416 static bool isFinal(method_type meth) {
1417 return meth->attrs & AttrFinal;
1420 // Whether to exclude methods with name `methName' when adding.
1421 static bool exclude(string_type methName) {
1422 return Func::isSpecial(methName);
1425 // TraitMethod constructor.
1426 static TraitMethod traitMethod(class_type traitCls,
1427 method_type traitMeth,
1428 const PreClass::TraitAliasRule& rule) {
1429 return TraitMethod { traitCls, traitMeth, rule.modifiers() };
1432 // Register a trait alias once the trait class is found.
1433 static void addTraitAlias(const ClassInfo* /*cls*/,
1434 const PreClass::TraitAliasRule& /*rule*/,
1435 class_type /*traitCls*/) {
1436 // purely a runtime thing... nothing to do
1439 // Trait class/method finders.
1440 static class_type findSingleTraitWithMethod(class_type cls,
1441 string_type origMethName) {
1442 class_type traitCls = nullptr;
1444 for (auto const t : cls->usedTraits) {
1445 // Note: m_methods includes methods from parents/traits recursively.
1446 if (t->methods.count(origMethName)) {
1447 if (traitCls != nullptr) {
1448 return nullptr;
1450 traitCls = t;
1453 return traitCls;
1456 static class_type findTraitClass(class_type cls,
1457 string_type traitName) {
1458 for (auto const t : cls->usedTraits) {
1459 if (traitName->isame(t->cls->name)) return t;
1461 return nullptr;
1464 static method_type findTraitMethod(class_type traitCls,
1465 string_type origMethName) {
1466 auto it = traitCls->methods.find(origMethName);
1467 if (it == traitCls->methods.end()) return nullptr;
1468 return it->second.func;
1471 // Errors.
1472 static void errorUnknownMethod(string_type methName) {
1473 throw TMIException(folly::sformat("Unknown method '{}'", methName));
1475 static void errorUnknownTrait(string_type traitName) {
1476 throw TMIException(folly::sformat("Unknown trait '{}'", traitName));
1478 static void errorDuplicateMethod(class_type cls,
1479 string_type methName,
1480 const std::list<TraitMethod>&) {
1481 auto const& m = cls->cls->methods;
1482 if (std::find_if(m.begin(), m.end(),
1483 [&] (auto const& f) {
1484 return f->name->isame(methName);
1485 }) != m.end()) {
1486 // the duplicate methods will be overridden by the class method.
1487 return;
1489 throw TMIException(folly::sformat("DuplicateMethod: {}", methName));
1491 static void errorInconsistentInsteadOf(class_type cls,
1492 string_type methName) {
1493 throw TMIException(folly::sformat("InconsistentInsteadOf: {} {}",
1494 methName, cls->cls->name));
1496 static void errorMultiplyExcluded(string_type traitName,
1497 string_type methName) {
1498 throw TMIException(folly::sformat("MultiplyExcluded: {}::{}",
1499 traitName, methName));
1501 static void errorInconsistentAttr(string_type traitName,
1502 string_type methName,
1503 const char* attr) {
1504 throw TMIException(folly::sformat(
1505 "Redeclaration of trait method '{}::{}' is inconsistent about '{}'",
1506 traitName, methName, attr
1509 static void errorRedeclaredNotFinal(string_type traitName,
1510 string_type methName) {
1511 throw TMIException(folly::sformat(
1512 "Redeclaration of final trait method '{}::{}' must also be final",
1513 traitName, methName
1519 using TMIData = TraitMethodImportData<TraitMethod,
1520 TMIOps>;
1522 struct BuildClsInfo {
1523 IndexData& index;
1524 ClassInfo* rleaf;
1525 hphp_hash_map<SString, std::pair<php::Prop, const ClassInfo*>,
1526 string_data_hash, string_data_same> pbuilder;
1530 * Make a flattened table of the constants on this class.
1532 bool build_class_constants(BuildClsInfo& info,
1533 const ClassInfo* rparent,
1534 bool fromTrait) {
1535 auto const removeNoOverride = [&] (const php::Const* c) {
1536 // During hhbbc/parse, all constants are pre-set to NoOverride
1537 FTRACE(2, "Removing NoOverride on {}::{}\n", c->cls->name, c->name);
1538 const_cast<php::Const*>(c)->isNoOverride = false;
1540 for (auto& c : rparent->cls->constants) {
1541 auto& cptr = info.rleaf->clsConstants[c.name];
1542 if (!cptr) {
1543 cptr = &c;
1544 continue;
1547 // Same constant (from an interface via two different paths) is ok
1548 if (cptr->cls == rparent->cls) continue;
1550 if (cptr->isTypeconst != c.isTypeconst) {
1551 ITRACE(2,
1552 "build_cls_info_rec failed for `{}' because `{}' was defined by "
1553 "`{}' as a {}constant and by `{}' as a {}constant\n",
1554 info.rleaf->cls->name, c.name,
1555 rparent->cls->name, c.isTypeconst ? "type " : "",
1556 cptr->cls->name, cptr->isTypeconst ? "type " : "");
1557 return false;
1560 // Ignore abstract constants
1561 if (!c.val) continue;
1563 if (cptr->val) {
1564 // Constants from interfaces implemented by traits silently lose
1565 if (fromTrait) {
1566 removeNoOverride(&c);
1567 continue;
1570 // A constant from an interface collides with an existing constant.
1571 if (rparent->cls->attrs & AttrInterface) {
1572 ITRACE(2,
1573 "build_cls_info_rec failed for `{}' because "
1574 "`{}' was defined by both `{}' and `{}'\n",
1575 info.rleaf->cls->name, c.name,
1576 rparent->cls->name, cptr->cls->name);
1577 return false;
1581 removeNoOverride(cptr);
1582 cptr = &c;
1584 return true;
1587 bool build_class_properties(BuildClsInfo& info,
1588 const ClassInfo* rparent) {
1589 // There's no need to do this work if traits have been flattened
1590 // already, or if the top level class has no traits. In those
1591 // cases, we might be able to rule out some ClassInfo
1592 // instantiations, but it doesn't seem worth it.
1593 if (info.rleaf->cls->attrs & AttrNoExpandTrait) return true;
1594 if (info.rleaf->usedTraits.empty()) return true;
1596 auto addProp = [&] (const php::Prop& p, bool add) {
1597 auto ent = std::make_pair(p, rparent);
1598 auto res = info.pbuilder.emplace(p.name, ent);
1599 if (res.second) {
1600 if (add) info.rleaf->traitProps.push_back(p);
1601 return true;
1603 auto& prevProp = res.first->second.first;
1604 if (rparent == res.first->second.second) {
1605 assertx(rparent == info.rleaf);
1606 if ((prevProp.attrs ^ p.attrs) &
1607 (AttrStatic | AttrPublic | AttrProtected | AttrPrivate) ||
1608 (!(p.attrs & AttrSystemInitialValue) &&
1609 !(prevProp.attrs & AttrSystemInitialValue) &&
1610 !Class::compatibleTraitPropInit(prevProp.val, p.val))) {
1611 ITRACE(2,
1612 "build_class_properties failed for `{}' because "
1613 "two declarations of `{}' at the same level had "
1614 "different attributes\n",
1615 info.rleaf->cls->name, p.name);
1616 return false;
1618 return true;
1621 if (!(prevProp.attrs & AttrPrivate)) {
1622 if ((prevProp.attrs ^ p.attrs) & AttrStatic) {
1623 ITRACE(2,
1624 "build_class_properties failed for `{}' because "
1625 "`{}' was defined both static and non-static\n",
1626 info.rleaf->cls->name, p.name);
1627 return false;
1629 if (p.attrs & AttrPrivate) {
1630 ITRACE(2,
1631 "build_class_properties failed for `{}' because "
1632 "`{}' was re-declared private\n",
1633 info.rleaf->cls->name, p.name);
1634 return false;
1636 if (p.attrs & AttrProtected && !(prevProp.attrs & AttrProtected)) {
1637 ITRACE(2,
1638 "build_class_properties failed for `{}' because "
1639 "`{}' was redeclared protected from public\n",
1640 info.rleaf->cls->name, p.name);
1641 return false;
1644 if (add && res.first->second.second != rparent) {
1645 info.rleaf->traitProps.push_back(p);
1647 res.first->second = ent;
1648 return true;
1651 for (auto const& p : rparent->cls->properties) {
1652 if (!addProp(p, false)) return false;
1655 if (rparent == info.rleaf) {
1656 for (auto t : rparent->usedTraits) {
1657 for (auto const& p : t->cls->properties) {
1658 if (!addProp(p, true)) return false;
1660 for (auto const& p : t->traitProps) {
1661 if (!addProp(p, true)) return false;
1664 } else {
1665 for (auto const& p : rparent->traitProps) {
1666 if (!addProp(p, false)) return false;
1670 return true;
1674 * Make a flattened table of the methods on this class.
1676 * Duplicate method names override parent methods, unless the parent method
1677 * is final and the class is not a __MockClass, in which case this class
1678 * definitely would fatal if ever defined.
1680 * Note: we're leaving non-overridden privates in their subclass method
1681 * table, here. This isn't currently "wrong", because calling it would be a
1682 * fatal, but note that resolve_method needs to be pretty careful about
1683 * privates and overriding in general.
1685 bool build_class_methods(BuildClsInfo& info) {
1687 auto methodOverride = [&] (auto& it,
1688 const php::Func* meth,
1689 Attr attrs,
1690 SString name) {
1691 if (it->second.func->attrs & AttrFinal) {
1692 if (!is_mock_class(info.rleaf->cls)) {
1693 ITRACE(2,
1694 "build_class_methods failed for `{}' because "
1695 "it tried to override final method `{}::{}'\n",
1696 info.rleaf->cls->name,
1697 it->second.func->cls->name, name);
1698 return false;
1701 ITRACE(9,
1702 " {}: overriding method {}::{} with {}::{}\n",
1703 info.rleaf->cls->name,
1704 it->second.func->cls->name, it->second.func->name,
1705 meth->cls->name, name);
1706 if (it->second.func->attrs & AttrPrivate) {
1707 it->second.hasPrivateAncestor = true;
1709 it->second.func = meth;
1710 it->second.attrs = attrs;
1711 it->second.hasAncestor = true;
1712 it->second.topLevel = true;
1713 if (it->first != name) {
1714 auto mte = it->second;
1715 info.rleaf->methods.erase(it);
1716 it = info.rleaf->methods.emplace(name, mte).first;
1718 return true;
1721 // If there's a parent, start by copying its methods
1722 if (auto const rparent = info.rleaf->parent) {
1723 for (auto& mte : rparent->methods) {
1724 // don't inherit the 86* methods.
1725 if (HPHP::Func::isSpecial(mte.first)) continue;
1726 auto const res = info.rleaf->methods.emplace(mte.first, mte.second);
1727 assertx(res.second);
1728 res.first->second.topLevel = false;
1729 ITRACE(9,
1730 " {}: inheriting method {}::{}\n",
1731 info.rleaf->cls->name,
1732 rparent->cls->name, mte.first);
1733 continue;
1737 uint32_t idx = info.rleaf->methods.size();
1739 // Now add our methods.
1740 for (auto& m : info.rleaf->cls->methods) {
1741 auto res = info.rleaf->methods.emplace(
1742 m->name,
1743 MethTabEntry { m.get(), m->attrs, false, true }
1745 if (res.second) {
1746 res.first->second.idx = idx++;
1747 ITRACE(9,
1748 " {}: adding method {}::{}\n",
1749 info.rleaf->cls->name,
1750 info.rleaf->cls->name, m->name);
1751 continue;
1753 if (m->attrs & AttrTrait && m->attrs & AttrAbstract) {
1754 // abstract methods from traits never override anything.
1755 continue;
1757 if (!methodOverride(res.first, m.get(), m->attrs, m->name)) return false;
1760 // If our traits were previously flattened, we're done.
1761 if (info.rleaf->cls->attrs & AttrNoExpandTrait) return true;
1763 try {
1764 TMIData tmid;
1765 for (auto const t : info.rleaf->usedTraits) {
1766 std::vector<const MethTabEntryPair*> methods(t->methods.size());
1767 for (auto& m : t->methods) {
1768 if (HPHP::Func::isSpecial(m.first)) continue;
1769 assertx(!methods[m.second.idx]);
1770 methods[m.second.idx] = mteFromElm(m);
1772 for (auto const m : methods) {
1773 if (!m) continue;
1774 TraitMethod traitMethod { t, m->second.func, m->second.attrs };
1775 tmid.add(traitMethod, m->first);
1777 for (auto const c : info.index.classClosureMap[t->cls]) {
1778 auto const invoke = find_method(c, s_invoke.get());
1779 assertx(invoke);
1780 info.index.classExtraMethodMap[info.rleaf->cls].insert(invoke);
1784 for (auto const& precRule : info.rleaf->cls->traitPrecRules) {
1785 tmid.applyPrecRule(precRule, info.rleaf);
1787 auto const& aliasRules = info.rleaf->cls->traitAliasRules;
1788 tmid.applyAliasRules(aliasRules.begin(), aliasRules.end(), info.rleaf);
1789 auto traitMethods = tmid.finish(info.rleaf);
1790 // Import the methods.
1791 for (auto const& mdata : traitMethods) {
1792 auto const method = mdata.tm.method;
1793 auto attrs = mdata.tm.modifiers;
1794 if (attrs == AttrNone) {
1795 attrs = method->attrs;
1796 } else {
1797 Attr attrMask = (Attr)(AttrPublic | AttrProtected | AttrPrivate |
1798 AttrAbstract | AttrFinal);
1799 attrs = (Attr)((attrs & attrMask) |
1800 (method->attrs & ~attrMask));
1802 auto res = info.rleaf->methods.emplace(
1803 mdata.name,
1804 MethTabEntry { method, attrs, false, true }
1806 if (res.second) {
1807 res.first->second.idx = idx++;
1808 ITRACE(9,
1809 " {}: adding trait method {}::{} as {}\n",
1810 info.rleaf->cls->name,
1811 method->cls->name, method->name, mdata.name);
1812 } else {
1813 if (attrs & AttrAbstract) continue;
1814 if (res.first->second.func->cls == info.rleaf->cls) continue;
1815 if (!methodOverride(res.first, method, attrs, mdata.name)) {
1816 return false;
1818 res.first->second.idx = idx++;
1820 info.index.classExtraMethodMap[info.rleaf->cls].insert(
1821 const_cast<php::Func*>(method));
1823 } catch (TMIOps::TMIException& ex) {
1824 ITRACE(2,
1825 "build_class_methods failed for `{}' importing traits: {}\n",
1826 info.rleaf->cls->name, ex.what());
1827 return false;
1830 return true;
1833 bool enforce_in_maybe_sealed_parent_whitelist(
1834 const ClassInfo* cls,
1835 const ClassInfo* parent);
1837 bool build_cls_info_rec(BuildClsInfo& info,
1838 const ClassInfo* rparent,
1839 bool fromTrait) {
1840 if (!rparent) return true;
1841 if (!enforce_in_maybe_sealed_parent_whitelist(rparent, rparent->parent)) {
1842 return false;
1844 if (!build_cls_info_rec(info, rparent->parent, false)) {
1845 return false;
1848 for (auto const iface : rparent->declInterfaces) {
1849 if (!enforce_in_maybe_sealed_parent_whitelist(rparent, iface)) {
1850 return false;
1852 if (!build_cls_info_rec(info, iface, fromTrait)) {
1853 return false;
1857 for (auto const trait : rparent->usedTraits) {
1858 if (!enforce_in_maybe_sealed_parent_whitelist(rparent, trait)) {
1859 return false;
1861 if (!build_cls_info_rec(info, trait, true)) return false;
1864 if (rparent->cls->attrs & AttrInterface) {
1866 * Make a flattened table of all the interfaces implemented by the class.
1868 info.rleaf->implInterfaces[rparent->cls->name] = rparent;
1869 } else {
1870 if (!fromTrait &&
1871 !build_class_properties(info, rparent)) {
1872 return false;
1875 // We don't need a method table for interfaces, and rather than
1876 // building the table recursively from scratch we just use the
1877 // parent's already constructed method table, and this class's
1878 // local method table (and traits if necessary).
1879 if (rparent == info.rleaf) {
1880 if (!build_class_methods(info)) return false;
1884 if (!build_class_constants(info, rparent, fromTrait)) return false;
1886 return true;
1889 const StaticString s___Sealed("__Sealed");
1890 bool enforce_in_maybe_sealed_parent_whitelist(
1891 const ClassInfo* cls,
1892 const ClassInfo* parent) {
1893 // if our parent isn't sealed, then we're fine.
1894 if (!parent || !(parent->cls->attrs & AttrSealed)) return true;
1895 const UserAttributeMap& parent_attrs = parent->cls->userAttributes;
1896 assert(parent_attrs.find(s___Sealed.get()) != parent_attrs.end());
1897 const auto& parent_sealed_attr = parent_attrs.find(s___Sealed.get())->second;
1898 bool in_sealed_whitelist = false;
1899 IterateV(parent_sealed_attr.m_data.parr,
1900 [&in_sealed_whitelist, cls](TypedValue v) -> bool {
1901 if (v.m_data.pstr->same(cls->cls->name)) {
1902 in_sealed_whitelist = true;
1903 return true;
1905 return false;
1907 return in_sealed_whitelist;
1911 * Note: a cyclic inheritance chain will blow this up, but right now
1912 * we'll never get here in that case because hphpc currently just
1913 * modifies classes not to have that situation. TODO(#3649211).
1915 * This function return false if we are certain instantiating cinfo
1916 * would be a fatal at runtime.
1918 bool build_cls_info(IndexData& index, ClassInfo* cinfo) {
1919 auto info = BuildClsInfo{ index, cinfo };
1920 if (!build_cls_info_rec(info, cinfo, false)) return false;
1921 return true;
1924 //////////////////////////////////////////////////////////////////////
1926 void add_system_constants_to_index(IndexData& index) {
1927 for (auto cnsPair : Native::getConstants()) {
1928 assertx(cnsPair.second.m_type != KindOfUninit ||
1929 cnsPair.second.dynamic());
1930 auto c = new Constant();
1931 c->name = cnsPair.first;
1932 c->val = cnsPair.second;
1933 c->attrs = AttrNone;
1935 index.constants.insert({c->name, c});
1939 //////////////////////////////////////////////////////////////////////
1941 template<typename T> struct NamingEnv;
1943 template<class T>
1944 struct PhpTypeHelper;
1946 template<>
1947 struct PhpTypeHelper<php::Class> {
1948 template<class Fn>
1949 static void process_bases(const php::Class* cls, Fn&& fn) {
1950 if (cls->parentName) fn(cls->parentName);
1951 for (auto& i : cls->interfaceNames) fn(i);
1952 for (auto& t : cls->usedTraitNames) fn(t);
1955 static std::string name() { return "class"; }
1957 static void assert_bases(NamingEnv<php::Class>& env, const php::Class* cls);
1958 static void try_flatten_traits(NamingEnv<php::Class>&,
1959 const php::Class*, ClassInfo*);
1962 template<>
1963 struct PhpTypeHelper<php::Record> {
1964 template<class Fn>
1965 static void process_bases(const php::Record* rec, Fn&& fn) {
1966 if (rec->parentName) fn(rec->parentName);
1969 static std::string name() { return "record"; }
1971 static void assert_bases(NamingEnv<php::Record>& env, const php::Record* rec);
1972 static void try_flatten_traits(NamingEnv<php::Record>&,
1973 const php::Record*, RecordInfo*);
1976 template<typename T>
1977 struct TypeInfoData {
1978 // Map from name to types that directly use that name (as parent,
1979 // interface or trait).
1980 hphp_hash_map<SString,
1981 CompactVector<const T*>,
1982 string_data_hash,
1983 string_data_isame> users;
1984 // Map from types to number of dependencies, used in
1985 // conjunction with users field above.
1986 hphp_hash_map<const T*, uint32_t> depCounts;
1988 uint32_t cqFront{};
1989 uint32_t cqBack{};
1990 std::vector<const T*> queue;
1991 bool hasPseudoCycles{};
1994 using ClassInfoData = TypeInfoData<php::Class>;
1995 using RecordInfoData = TypeInfoData<php::Record>;
1997 // We want const qualifiers on various index data structures for php
1998 // object pointers, but during index creation time we need to
1999 // manipulate some of their attributes (changing the representation).
2000 // This little wrapper keeps the const_casting out of the main line of
2001 // code below.
2002 void attribute_setter(const Attr& attrs, bool set, Attr attr) {
2003 attrSetter(const_cast<Attr&>(attrs), set, attr);
2006 void add_unit_to_index(IndexData& index, const php::Unit& unit) {
2007 hphp_hash_map<
2008 const php::Class*,
2009 hphp_hash_set<const php::Class*>
2010 > closureMap;
2012 for (auto& c : unit.classes) {
2013 auto const attrsToRemove =
2014 AttrUnique |
2015 AttrPersistent |
2016 AttrNoOverride |
2017 AttrNoOverrideMagicGet |
2018 AttrNoOverrideMagicSet |
2019 AttrNoOverrideMagicIsset |
2020 AttrNoOverrideMagicUnset;
2021 attribute_setter(c->attrs, false, attrsToRemove);
2023 // Manually set closure classes to be unique to maintain invariance.
2024 if (is_closure(*c)) {
2025 attrSetter(c->attrs, true, AttrUnique);
2028 if (c->attrs & AttrEnum) {
2029 index.enums.emplace(c->name, c.get());
2033 * A class can be defined with the same name as a builtin in the
2034 * repo. Any such attempts will fatal at runtime, so we can safely
2035 * ignore any such definitions. This ensures that names referring
2036 * to builtins are always fully resolvable.
2038 auto const classes = find_range(index.classes, c->name);
2039 if (classes.begin() != classes.end()) {
2040 if (c->attrs & AttrBuiltin) {
2041 index.classes.erase(classes.begin(), classes.end());
2042 } else if (classes.begin()->second->attrs & AttrBuiltin) {
2043 assertx(std::next(classes.begin()) == classes.end());
2044 continue;
2047 index.classes.emplace(c->name, c.get());
2049 for (auto& m : c->methods) {
2050 attribute_setter(m->attrs, false, AttrNoOverride);
2051 index.methods.insert({m->name, m.get()});
2052 if (m->attrs & AttrInterceptable) {
2053 index.any_interceptable_functions = true;
2056 if (RuntimeOption::RepoAuthoritative) {
2057 uint64_t refs = 0, cur = 1;
2058 bool anyInOut = false;
2059 for (auto& p : m->params) {
2060 if (p.inout) {
2061 refs |= cur;
2062 anyInOut = true;
2064 // It doesn't matter that we lose parameters beyond the 64th,
2065 // for those, we'll conservatively check everything anyway.
2066 cur <<= 1;
2068 if (anyInOut) {
2069 // Multiple methods with the same name will be combined in the same
2070 // cell, thus we use |=. This only makes sense in WholeProgram mode
2071 // since we use this field to check that no functions has its n-th
2072 // parameter as inout, which requires global knowledge.
2073 index.method_inout_params_by_name[m->name] |= refs;
2078 if (c->closureContextCls) {
2079 closureMap[c->closureContextCls].insert(c.get());
2083 if (!closureMap.empty()) {
2084 for (auto const& c1 : closureMap) {
2085 auto& s = index.classClosureMap[c1.first];
2086 for (auto const& c2 : c1.second) {
2087 s.push_back(c2);
2092 for (auto& f : unit.funcs) {
2094 * A function can be defined with the same name as a builtin in the
2095 * repo. Any such attempts will fatal at runtime, so we can safely ignore
2096 * any such definitions. This ensures that names referring to builtins are
2097 * always fully resolvable.
2099 auto const funcs = index.funcs.equal_range(f->name);
2100 if (funcs.first != funcs.second) {
2101 if (f->attrs & AttrIsMethCaller) {
2102 // meth_caller has builtin attr and can have duplicates definitions
2103 assertx(std::next(funcs.first) == funcs.second);
2104 assertx(funcs.first->second->attrs & AttrIsMethCaller);
2105 continue;
2108 auto const& old_func = funcs.first->second;
2109 // If there is a builtin, it will always be the first (and only) func on
2110 // the list.
2111 if (old_func->attrs & AttrBuiltin) {
2112 always_assert(!(f->attrs & AttrBuiltin));
2113 continue;
2115 if (f->attrs & AttrBuiltin) index.funcs.erase(funcs.first, funcs.second);
2117 if (f->attrs & AttrInterceptable) index.any_interceptable_functions = true;
2118 index.funcs.insert({f->name, f.get()});
2121 for (auto& ta : unit.typeAliases) {
2122 index.typeAliases.insert({ta->name, ta.get()});
2125 for (auto& c : unit.constants) {
2126 index.constants.insert({c->name, c.get()});
2129 for (auto& rec : unit.records) {
2130 index.records.insert({rec->name, rec.get()});
2135 template<class T>
2136 using TypeInfo = typename std::conditional<std::is_same<T, php::Class>::value,
2137 ClassInfo, RecordInfo>::type;
2139 template<typename T>
2140 struct NamingEnv {
2141 NamingEnv(php::Program* program, IndexData& index, TypeInfoData<T>& tid) :
2142 program{program}, index{index}, tid{tid} {}
2144 struct Define;
2146 // Returns TypeInfo for a given name, if either:
2147 // a) that name corresponds to a unique TypeInfo, or
2148 // b) he TypeInfo for that name was selected in scope with NamingEnv::Define
2149 TypeInfo<T>* try_lookup(SString name,
2150 const ISStringToMany<TypeInfo<T>>& map) const {
2151 auto const range = map.equal_range(name);
2152 // We're resolving in topological order; we shouldn't be here
2153 // unless we know there's at least one resolution of this class.
2154 assertx(range.first != range.second);
2155 // Common case will be exactly one resolution. Lets avoid the
2156 // copy_range, and iteration for that case.
2157 if (std::next(range.first) == range.second) {
2158 return range.first->second;
2160 auto const it = names.find(name);
2161 if (it != end(names)) return it->second;
2162 return nullptr;
2165 TypeInfo<T>* lookup(SString name,
2166 const ISStringToMany<TypeInfo<T>>& map) const {
2167 auto const ret = try_lookup(name, map);
2168 assertx(ret);
2169 return ret;
2172 php::Program* program;
2173 IndexData& index;
2174 TypeInfoData<T>& tid;
2175 std::unordered_multimap<
2176 const T*,
2177 TypeInfo<T>*,
2178 pointer_hash<T>> resolved;
2179 private:
2180 ISStringToOne<TypeInfo<T>> names;
2183 template<typename T>
2184 struct NamingEnv<T>::Define {
2185 explicit Define(NamingEnv& env, SString n, TypeInfo<T>* ti, const T* t)
2186 : env(env), n(n) {
2187 ITRACE(2, "defining {} {} for {}\n", PhpTypeHelper<T>::name(), n, t->name);
2188 always_assert(!env.names.count(n));
2189 env.names[n] = ti;
2191 ~Define() {
2192 env.names.erase(n);
2195 Define(const Define&) = delete;
2196 Define& operator=(const Define&) = delete;
2198 private:
2199 Trace::Indent indent;
2200 NamingEnv<T>& env;
2201 SString n;
2204 using ClassNamingEnv = NamingEnv<php::Class>;
2205 using RecordNamingEnv = NamingEnv<php::Record>;
2207 void PhpTypeHelper<php::Class>::assert_bases(NamingEnv<php::Class>& env,
2208 const php::Class* cls) {
2209 if (cls->parentName) {
2210 assertx(env.index.classInfo.count(cls->parentName));
2212 for (DEBUG_ONLY auto& i : cls->interfaceNames) {
2213 assertx(env.index.classInfo.count(i));
2215 for (DEBUG_ONLY auto& t : cls->usedTraitNames) {
2216 assertx(env.index.classInfo.count(t));
2220 void PhpTypeHelper<php::Record>::assert_bases(NamingEnv<php::Record>& env,
2221 const php::Record* rec) {
2222 if (rec->parentName) {
2223 assertx(env.index.recordInfo.count(rec->parentName));
2227 using ClonedClosureMap = hphp_hash_map<
2228 php::Class*,
2229 std::pair<std::unique_ptr<php::Class>, uint32_t>
2232 std::unique_ptr<php::Func> clone_meth_helper(
2233 php::Class* newContext,
2234 const php::Func* origMeth,
2235 std::unique_ptr<php::Func> cloneMeth,
2236 std::atomic<uint32_t>& nextFuncId,
2237 uint32_t& nextClass,
2238 ClonedClosureMap& clonedClosures);
2240 std::unique_ptr<php::Class> clone_closure(php::Class* newContext,
2241 php::Class* cls,
2242 std::atomic<uint32_t>& nextFuncId,
2243 uint32_t& nextClass,
2244 ClonedClosureMap& clonedClosures) {
2245 auto clone = std::make_unique<php::Class>(*cls);
2246 assertx(clone->closureContextCls);
2247 clone->closureContextCls = newContext;
2248 clone->unit = newContext->unit;
2249 auto i = 0;
2250 for (auto& cloneMeth : clone->methods) {
2251 cloneMeth = clone_meth_helper(clone.get(),
2252 cls->methods[i++].get(),
2253 std::move(cloneMeth),
2254 nextFuncId,
2255 nextClass,
2256 clonedClosures);
2257 if (!cloneMeth) return nullptr;
2259 return clone;
2262 std::unique_ptr<php::Func> clone_meth_helper(
2263 php::Class* newContext,
2264 const php::Func* origMeth,
2265 std::unique_ptr<php::Func> cloneMeth,
2266 std::atomic<uint32_t>& nextFuncId,
2267 uint32_t& nextClass,
2268 ClonedClosureMap& clonedClosures) {
2270 cloneMeth->cls = newContext;
2271 cloneMeth->idx = nextFuncId.fetch_add(1, std::memory_order_relaxed);
2272 if (!cloneMeth->originalFilename) {
2273 cloneMeth->originalFilename = origMeth->unit->filename;
2275 if (!cloneMeth->originalUnit) {
2276 cloneMeth->originalUnit = origMeth->unit;
2278 cloneMeth->unit = newContext->unit;
2280 auto const recordClosure = [&] (uint32_t* clsId) {
2281 auto const cls = origMeth->unit->classes[*clsId].get();
2282 auto& elm = clonedClosures[cls];
2283 if (!elm.first) {
2284 elm.first = clone_closure(newContext->closureContextCls ?
2285 newContext->closureContextCls : newContext,
2286 cls, nextFuncId, nextClass, clonedClosures);
2287 if (!elm.first) return false;
2288 elm.second = nextClass++;
2290 *clsId = elm.second;
2291 return true;
2294 hphp_fast_map<size_t, hphp_fast_map<size_t, uint32_t>> updates;
2295 for (size_t bid = 0; bid < cloneMeth->blocks.size(); bid++) {
2296 auto const b = cloneMeth->blocks[bid].get();
2297 for (size_t ix = 0; ix < b->hhbcs.size(); ix++) {
2298 auto const& bc = b->hhbcs[ix];
2299 switch (bc.op) {
2300 case Op::CreateCl: {
2301 auto clsId = bc.CreateCl.arg2;
2302 if (!recordClosure(&clsId)) return nullptr;
2303 updates[bid][ix] = clsId;
2304 break;
2306 case Op::DefCls:
2307 case Op::DefClsNop:
2308 return nullptr;
2309 default:
2310 break;
2315 for (auto elm : updates) {
2316 auto& cblk = cloneMeth->blocks[elm.first];
2317 auto const blk = cblk.mutate();
2318 for (auto const& ix : elm.second) {
2319 blk->hhbcs[ix.first].CreateCl.arg2 = ix.second;
2323 return cloneMeth;
2326 std::unique_ptr<php::Func> clone_meth(php::Class* newContext,
2327 const php::Func* origMeth,
2328 SString name,
2329 Attr attrs,
2330 std::atomic<uint32_t>& nextFuncId,
2331 uint32_t& nextClass,
2332 ClonedClosureMap& clonedClosures) {
2334 auto cloneMeth = std::make_unique<php::Func>(*origMeth);
2335 cloneMeth->name = name;
2336 cloneMeth->attrs = attrs | AttrTrait;
2337 return clone_meth_helper(newContext, origMeth, std::move(cloneMeth),
2338 nextFuncId, nextClass, clonedClosures);
2341 bool merge_xinits(Attr attr,
2342 std::vector<std::unique_ptr<php::Func>>& clones,
2343 ClassInfo* cinfo,
2344 std::atomic<uint32_t>& nextFuncId,
2345 uint32_t& nextClass,
2346 ClonedClosureMap& clonedClosures) {
2347 auto const cls = const_cast<php::Class*>(cinfo->cls);
2348 auto const xinitName = [&]() {
2349 switch (attr) {
2350 case AttrNone : return s_86pinit.get();
2351 case AttrStatic: return s_86sinit.get();
2352 case AttrLSB : return s_86linit.get();
2353 default: always_assert(false);
2355 }();
2357 auto const xinitMatch = [&](Attr prop_attrs) {
2358 auto mask = AttrStatic | AttrLSB;
2359 switch (attr) {
2360 case AttrNone: return (prop_attrs & mask) == AttrNone;
2361 case AttrStatic: return (prop_attrs & mask) == AttrStatic;
2362 case AttrLSB: return (prop_attrs & mask) == mask;
2363 default: always_assert(false);
2367 auto const needsXinit = [&] {
2368 for (auto const& p : cinfo->traitProps) {
2369 if (xinitMatch(p.attrs) &&
2370 p.val.m_type == KindOfUninit &&
2371 !(p.attrs & AttrLateInit)) {
2372 ITRACE(5, "merge_xinits: {}: Needs merge for {}{}prop `{}'\n",
2373 cls->name, attr & AttrStatic ? "static " : "",
2374 attr & AttrLSB ? "lsb " : "", p.name);
2375 return true;
2378 return false;
2379 }();
2381 if (!needsXinit) return true;
2383 std::unique_ptr<php::Func> empty;
2384 auto& xinit = [&] () -> std::unique_ptr<php::Func>& {
2385 for (auto& m : cls->methods) {
2386 if (m->name == xinitName) return m;
2388 return empty;
2389 }();
2391 auto merge_one = [&] (const php::Func* func) {
2392 if (!xinit) {
2393 ITRACE(5, " - cloning {}::{} as {}::{}\n",
2394 func->cls->name, func->name, cls->name, xinitName);
2395 xinit = clone_meth(cls, func, func->name, func->attrs, nextFuncId,
2396 nextClass, clonedClosures);
2397 return xinit != nullptr;
2400 ITRACE(5, " - appending {}::{} into {}::{}\n",
2401 func->cls->name, func->name, cls->name, xinitName);
2402 return append_func(xinit.get(), *func);
2405 for (auto t : cinfo->usedTraits) {
2406 auto it = t->methods.find(xinitName);
2407 if (it != t->methods.end()) {
2408 if (!merge_one(it->second.func)) {
2409 ITRACE(5, "merge_xinits: failed to merge {}::{}\n",
2410 it->second.func->cls->name, it->second.func->name);
2411 return false;
2416 assertx(xinit);
2417 if (empty) {
2418 ITRACE(5, "merge_xinits: adding {}::{} to method table\n",
2419 xinit->cls->name, xinit->name);
2420 assertx(&empty == &xinit);
2421 DEBUG_ONLY auto res = cinfo->methods.emplace(
2422 xinit->name,
2423 MethTabEntry { xinit.get(), xinit->attrs, false, true }
2425 assertx(res.second);
2426 clones.push_back(std::move(xinit));
2429 return true;
2432 void rename_closure(ClassNamingEnv& env, php::Class* cls) {
2433 auto n = cls->name->slice();
2434 auto const p = n.find(';');
2435 if (p != std::string::npos) {
2436 n = n.subpiece(0, p);
2438 auto const newName = makeStaticString(NewAnonymousClassName(n));
2439 assertx(!env.index.classes.count(newName));
2440 cls->name = newName;
2441 env.index.classes.emplace(newName, cls);
2444 template <typename T> void preresolve(NamingEnv<T>& env, const T* type);
2446 void flatten_traits(ClassNamingEnv& env, ClassInfo* cinfo) {
2447 bool hasConstProp = false;
2448 for (auto t : cinfo->usedTraits) {
2449 if (t->usedTraits.size() && !(t->cls->attrs & AttrNoExpandTrait)) {
2450 ITRACE(5, "Not flattening {} because of {}\n",
2451 cinfo->cls->name, t->cls->name);
2452 return;
2454 if (is_noflatten_trait(t->cls)) {
2455 ITRACE(5, "Not flattening {} because {} is annotated with __NoFlatten\n",
2456 cinfo->cls->name, t->cls->name);
2457 return;
2459 if (t->cls->hasConstProp) hasConstProp = true;
2461 auto const cls = const_cast<php::Class*>(cinfo->cls);
2462 if (hasConstProp) cls->hasConstProp = true;
2463 std::vector<MethTabEntryPair*> methodsToAdd;
2464 for (auto& ent : cinfo->methods) {
2465 if (!ent.second.topLevel || ent.second.func->cls == cinfo->cls) {
2466 continue;
2468 always_assert(ent.second.func->cls->attrs & AttrTrait);
2469 methodsToAdd.push_back(mteFromElm(ent));
2472 auto const it = env.index.classExtraMethodMap.find(cinfo->cls);
2474 if (!methodsToAdd.empty()) {
2475 assertx(it != env.index.classExtraMethodMap.end());
2476 std::sort(begin(methodsToAdd), end(methodsToAdd),
2477 [] (const MethTabEntryPair* a, const MethTabEntryPair* b) {
2478 return a->second.idx < b->second.idx;
2480 } else if (debug && it != env.index.classExtraMethodMap.end()) {
2481 // When building the ClassInfos, we proactively added all closures
2482 // from usedTraits to classExtraMethodMap; but now we're going to
2483 // start from the used methods, and deduce which closures actually
2484 // get pulled in. Its possible *none* of the methods got used, in
2485 // which case, we won't need their closures either. To be safe,
2486 // verify that the only things in classExtraMethodMap are
2487 // closures.
2488 for (DEBUG_ONLY auto const f : it->second) {
2489 assertx(f->isClosureBody);
2493 std::vector<std::unique_ptr<php::Func>> clones;
2494 ClonedClosureMap clonedClosures;
2495 uint32_t nextClassId = cls->unit->classes.size();
2496 for (auto const ent : methodsToAdd) {
2497 auto clone = clone_meth(cls, ent->second.func, ent->first,
2498 ent->second.attrs, env.program->nextFuncId,
2499 nextClassId, clonedClosures);
2500 if (!clone) {
2501 ITRACE(5, "Not flattening {} because {}::{} could not be cloned\n",
2502 cls->name, ent->second.func->cls->name, ent->first);
2503 return;
2506 clone->attrs |= AttrTrait;
2507 ent->second.attrs |= AttrTrait;
2508 ent->second.func = clone.get();
2509 clones.push_back(std::move(clone));
2512 if (cinfo->traitProps.size()) {
2513 if (!merge_xinits(AttrNone, clones, cinfo,
2514 env.program->nextFuncId, nextClassId, clonedClosures) ||
2515 !merge_xinits(AttrStatic, clones, cinfo,
2516 env.program->nextFuncId, nextClassId, clonedClosures) ||
2517 !merge_xinits(AttrLSB, clones, cinfo,
2518 env.program->nextFuncId, nextClassId, clonedClosures)) {
2519 ITRACE(5, "Not flattening {} because we couldn't merge the 86xinits\n",
2520 cls->name);
2521 return;
2525 // We're now committed to flattening.
2526 ITRACE(3, "Flattening {}\n", cls->name);
2527 if (it != env.index.classExtraMethodMap.end()) it->second.clear();
2528 for (auto const& p : cinfo->traitProps) {
2529 ITRACE(5, " - prop {}\n", p.name);
2530 cls->properties.push_back(p);
2531 cls->properties.back().attrs |= AttrTrait;
2533 cinfo->traitProps.clear();
2535 if (clones.size()) {
2536 auto cinit = cls->methods.size() &&
2537 cls->methods.back()->name == s_86cinit.get() ?
2538 std::move(cls->methods.back()) : nullptr;
2539 if (cinit) cls->methods.pop_back();
2540 for (auto& clone : clones) {
2541 ITRACE(5, " - meth {}\n", clone->name);
2542 cinfo->methods.find(clone->name)->second.func = clone.get();
2543 cls->methods.push_back(std::move(clone));
2545 if (cinit) cls->methods.push_back(std::move(cinit));
2547 if (clonedClosures.size()) {
2548 auto& classClosures = env.index.classClosureMap[cls];
2549 cls->unit->classes.resize(nextClassId);
2550 for (auto& ent : clonedClosures) {
2551 auto const clo = ent.second.first.get();
2552 rename_closure(env, clo);
2553 ITRACE(5, " - closure {} as {}\n", ent.first->name, clo->name);
2554 assertx(clo->closureContextCls == cls);
2555 assertx(clo->unit == cls->unit);
2556 classClosures.push_back(clo);
2558 cls->unit->classes[ent.second.second] = std::move(ent.second.first);
2559 preresolve(env, clo);
2564 struct EqHash {
2565 bool operator()(const PreClass::ClassRequirement& a,
2566 const PreClass::ClassRequirement& b) const {
2567 return a.is_same(&b);
2569 size_t operator()(const PreClass::ClassRequirement& a) const {
2570 return a.hash();
2574 hphp_hash_set<PreClass::ClassRequirement, EqHash, EqHash> reqs;
2576 for (auto const t : cinfo->usedTraits) {
2577 for (auto const& req : t->cls->requirements) {
2578 if (reqs.empty()) {
2579 for (auto const& r : cls->requirements) {
2580 reqs.insert(r);
2583 if (reqs.insert(req).second) cls->requirements.push_back(req);
2587 cls->attrs |= AttrNoExpandTrait;
2591 * Given a static representation of a Hack record, find a possible resolution
2592 * of the record along with all records in its hierarchy.
2594 void resolve_combinations(RecordNamingEnv& env,
2595 const php::Record* rec) {
2597 auto resolve_one = [&] (SString name) {
2598 if (env.try_lookup(name, env.index.recordInfo)) return true;
2599 auto const range = copy_range(env.index.recordInfo, name);
2600 assertx(range.size() > 1);
2601 for (auto& kv : range) {
2602 RecordNamingEnv::Define def{env, name, kv.second, rec};
2603 resolve_combinations(env, rec);
2605 return false;
2608 // Recurse with all combinations of parents.
2609 if (rec->parentName) {
2610 if (!resolve_one(rec->parentName)) return;
2613 // Everything is defined in the naming environment here. (We
2614 // returned early if something didn't exist.)
2616 auto rinfo = std::make_unique<RecordInfo>();
2617 rinfo->rec = rec;
2618 if (rec->parentName) {
2619 auto const parent = env.lookup(rec->parentName, env.index.recordInfo);
2620 if (parent->rec->attrs & AttrFinal) {
2621 ITRACE(2,
2622 "Resolve combinations failed for `{}' because "
2623 "its parent record `{}' is not abstract\n",
2624 rec->name, parent->rec->name);
2625 return;
2627 rinfo->parent = parent;
2628 rinfo->baseList = rinfo->parent->baseList;
2630 rinfo->baseList.push_back(rinfo.get());
2631 rinfo->baseList.shrink_to_fit();
2632 ITRACE(2, " resolved: {}\n", rec->name);
2633 env.resolved.emplace(rec, rinfo.get());
2634 env.index.recordInfo.emplace(rec->name, rinfo.get());
2635 env.index.allRecordInfos.push_back(std::move(rinfo));
2639 * Given a static representation of a Hack class, find a possible resolution
2640 * of the class along with all classes, interfaces and traits in its hierarchy.
2642 void resolve_combinations(ClassNamingEnv& env,
2643 const php::Class* cls) {
2645 auto resolve_one = [&] (SString name) {
2646 if (env.try_lookup(name, env.index.classInfo)) return true;
2647 auto const range = copy_range(env.index.classInfo, name);
2648 assertx(range.size() > 1);
2649 for (auto& kv : range) {
2650 ClassNamingEnv::Define def{env, name, kv.second, cls};
2651 resolve_combinations(env, cls);
2653 return false;
2656 // Recurse with all combinations of bases and interfaces in the
2657 // naming environment.
2658 if (cls->parentName) {
2659 if (!resolve_one(cls->parentName)) return;
2661 for (auto& iname : cls->interfaceNames) {
2662 if (!resolve_one(iname)) return;
2664 for (auto& tname : cls->usedTraitNames) {
2665 if (!resolve_one(tname)) return;
2668 // Everything is defined in the naming environment here. (We
2669 // returned early if something didn't exist.)
2671 auto cinfo = std::make_unique<ClassInfo>();
2672 cinfo->cls = cls;
2673 auto const& map = env.index.classInfo;
2674 if (cls->parentName) {
2675 cinfo->parent = env.lookup(cls->parentName, map);
2676 cinfo->baseList = cinfo->parent->baseList;
2677 if (cinfo->parent->cls->attrs & (AttrInterface | AttrTrait)) {
2678 ITRACE(2,
2679 "Resolve combinations failed for `{}' because "
2680 "its parent `{}' is not a class\n",
2681 cls->name, cls->parentName);
2682 return;
2685 cinfo->baseList.push_back(cinfo.get());
2687 for (auto& iname : cls->interfaceNames) {
2688 auto const iface = env.lookup(iname, map);
2689 if (!(iface->cls->attrs & AttrInterface)) {
2690 ITRACE(2,
2691 "Resolve combinations failed for `{}' because `{}' "
2692 "is not an interface\n",
2693 cls->name, iname);
2694 return;
2696 cinfo->declInterfaces.push_back(iface);
2699 for (auto& tname : cls->usedTraitNames) {
2700 auto const trait = env.lookup(tname, map);
2701 if (!(trait->cls->attrs & AttrTrait)) {
2702 ITRACE(2,
2703 "Resolve combinations failed for `{}' because `{}' "
2704 "is not a trait\n",
2705 cls->name, tname);
2706 return;
2708 cinfo->usedTraits.push_back(trait);
2711 if (!build_cls_info(env.index, cinfo.get())) return;
2713 ITRACE(2, " resolved: {}\n", cls->name);
2714 if (Trace::moduleEnabled(Trace::hhbbc_index, 3)) {
2715 for (auto const DEBUG_ONLY& iface : cinfo->implInterfaces) {
2716 ITRACE(3, " implements: {}\n", iface.second->cls->name);
2718 for (auto const DEBUG_ONLY& trait : cinfo->usedTraits) {
2719 ITRACE(3, " uses: {}\n", trait->cls->name);
2722 cinfo->baseList.shrink_to_fit();
2723 env.resolved.emplace(cls, cinfo.get());
2724 env.index.classInfo.emplace(cls->name, cinfo.get());
2725 env.index.allClassInfos.push_back(std::move(cinfo));
2730 void PhpTypeHelper<php::Record>::try_flatten_traits(NamingEnv<php::Record>&,
2731 const php::Record*,
2732 RecordInfo*) {}
2734 void PhpTypeHelper<php::Class>::try_flatten_traits(NamingEnv<php::Class>& env,
2735 const php::Class* cls,
2736 ClassInfo* cinfo) {
2737 if (options.FlattenTraits &&
2738 !(cls->attrs & AttrNoExpandTrait) &&
2739 !cls->usedTraitNames.empty() &&
2740 env.index.classes.count(cls->name) == 1) {
2741 Trace::Indent indent;
2742 flatten_traits(env, cinfo);
2746 template <typename T>
2747 void preresolve(NamingEnv<T>& env, const T* type) {
2748 assertx(!env.resolved.count(type));
2750 ITRACE(2, "preresolve {}: {}:{}\n",
2751 PhpTypeHelper<T>::name(), type->name, (void*)type);
2753 Trace::Indent indent;
2754 if (debug) {
2755 PhpTypeHelper<T>::assert_bases(env, type);
2757 resolve_combinations(env, type);
2760 ITRACE(3, "preresolve: {}:{} ({} resolutions)\n",
2761 type->name, (void*)type, env.resolved.count(type));
2763 auto const range = find_range(env.resolved, type);
2764 if (begin(range) != end(range)) {
2765 auto const& users = env.tid.users[type->name];
2766 for (auto const tu : users) {
2767 auto const it = env.tid.depCounts.find(tu);
2768 if (it == env.tid.depCounts.end()) {
2769 assertx(env.tid.hasPseudoCycles);
2770 continue;
2772 auto& depCount = it->second;
2773 assertx(depCount);
2774 if (!--depCount) {
2775 env.tid.depCounts.erase(it);
2776 ITRACE(5, " enqueue: {}:{}\n", tu->name, (void*)tu);
2777 env.tid.queue[env.tid.cqBack++] = tu;
2778 } else {
2779 ITRACE(6, " depcount: {}:{} = {}\n", tu->name, (void*)tu, depCount);
2782 if (std::next(begin(range)) == end(range)) {
2783 PhpTypeHelper<T>::try_flatten_traits(env, type, begin(range)->second);
2788 void compute_subclass_list_rec(IndexData& index,
2789 ClassInfo* cinfo,
2790 ClassInfo* csub) {
2791 for (auto const ctrait : csub->usedTraits) {
2792 auto const ct = const_cast<ClassInfo*>(ctrait);
2793 ct->subclassList.push_back(cinfo);
2794 compute_subclass_list_rec(index, cinfo, ct);
2798 void compute_subclass_list(IndexData& index) {
2799 trace_time _("compute subclass list");
2800 auto fixupTraits = false;
2801 for (auto& cinfo : index.allClassInfos) {
2802 if (cinfo->cls->attrs & AttrInterface) continue;
2803 for (auto& cparent : cinfo->baseList) {
2804 cparent->subclassList.push_back(cinfo.get());
2806 if (!(cinfo->cls->attrs & AttrNoExpandTrait) &&
2807 cinfo->usedTraits.size()) {
2808 fixupTraits = true;
2809 compute_subclass_list_rec(index, cinfo.get(), cinfo.get());
2811 // Also add instantiable classes to their interface's subclassLists
2812 if (cinfo->cls->attrs & (AttrTrait | AttrEnum | AttrAbstract)) continue;
2813 for (auto& ipair : cinfo->implInterfaces) {
2814 auto impl = const_cast<ClassInfo*>(ipair.second);
2815 impl->subclassList.push_back(cinfo.get());
2819 for (auto& cinfo : index.allClassInfos) {
2820 auto& sub = cinfo->subclassList;
2821 if (fixupTraits && cinfo->cls->attrs & AttrTrait) {
2822 // traits can be reached by multiple paths, so we need to uniquify
2823 // their subclassLists.
2824 std::sort(begin(sub), end(sub));
2825 sub.erase(
2826 std::unique(begin(sub), end(sub)),
2827 end(sub)
2830 sub.shrink_to_fit();
2834 bool define_func_family(IndexData& index, ClassInfo* cinfo,
2835 SString name, const php::Func* func = nullptr) {
2836 FuncFamily::PFuncVec funcs{};
2837 auto containsInterceptables = false;
2838 for (auto const cleaf : cinfo->subclassList) {
2839 auto const leafFn = [&] () -> const MethTabEntryPair* {
2840 auto const leafFnIt = cleaf->methods.find(name);
2841 if (leafFnIt == end(cleaf->methods)) return nullptr;
2842 return mteFromIt(leafFnIt);
2843 }();
2844 if (!leafFn) continue;
2845 if (leafFn->second.func->attrs & AttrInterceptable) {
2846 containsInterceptables = true;
2848 funcs.push_back(leafFn);
2851 if (funcs.empty()) return false;
2853 std::sort(begin(funcs), end(funcs),
2854 [&] (const MethTabEntryPair* a, const MethTabEntryPair* b) {
2855 // We want a canonical order for the family. Putting the
2856 // one corresponding to cinfo first makes sense, because
2857 // the first one is used as the name for FCall*Method* hint,
2858 // after that, sort by name so that different case spellings
2859 // come in the same order.
2860 if (a->second.func == b->second.func) return false;
2861 if (func) {
2862 if (b->second.func == func) return false;
2863 if (a->second.func == func) return true;
2865 if (auto d = a->first->compare(b->first)) {
2866 if (!func) {
2867 if (b->first == name) return false;
2868 if (a->first == name) return true;
2870 return d < 0;
2872 return std::less<const void*>{}(a->second.func, b->second.func);
2874 funcs.erase(
2875 std::unique(begin(funcs), end(funcs),
2876 [] (const MethTabEntryPair* a, const MethTabEntryPair* b) {
2877 return a->second.func == b->second.func;
2879 end(funcs)
2882 funcs.shrink_to_fit();
2884 if (Trace::moduleEnabled(Trace::hhbbc_index, 4)) {
2885 FTRACE(4, "define_func_family: {}::{}:\n",
2886 cinfo->cls->name, name);
2887 for (auto const DEBUG_ONLY func : funcs) {
2888 FTRACE(4, " {}::{}\n",
2889 func->second.func->cls->name, func->second.func->name);
2893 cinfo->methodFamilies.emplace(
2894 std::piecewise_construct,
2895 std::forward_as_tuple(name),
2896 std::forward_as_tuple(std::move(funcs), containsInterceptables)
2899 return true;
2902 void build_abstract_func_families(IndexData& data, ClassInfo* cinfo) {
2903 std::vector<SString> extras;
2905 // We start by collecting the list of methods shared across all
2906 // subclasses of cinfo (including indirectly). And then add the
2907 // public methods which are not constructors and have no private
2908 // ancestors to the method families of cinfo. Note that this set
2909 // may be larger than the methods declared on cinfo and may also
2910 // be missing methods declared on cinfo. In practice this is the
2911 // set of methods we can depend on having accessible given any
2912 // object which is known to implement cinfo.
2913 auto it = cinfo->subclassList.begin();
2914 while (true) {
2915 if (it == cinfo->subclassList.end()) return;
2916 auto const sub = *it++;
2917 assertx(!(sub->cls->attrs & AttrInterface));
2918 if (sub == cinfo || (sub->cls->attrs & AttrAbstract)) continue;
2919 for (auto& par : sub->methods) {
2920 if (!par.second.hasPrivateAncestor &&
2921 (par.second.attrs & AttrPublic) &&
2922 !cinfo->methodFamilies.count(par.first) &&
2923 !cinfo->methods.count(par.first)) {
2924 extras.push_back(par.first);
2927 if (!extras.size()) return;
2928 break;
2931 auto end = extras.end();
2932 while (it != cinfo->subclassList.end()) {
2933 auto const sub = *it++;
2934 assertx(!(sub->cls->attrs & AttrInterface));
2935 if (sub == cinfo || (sub->cls->attrs & AttrAbstract)) continue;
2936 for (auto nameIt = extras.begin(); nameIt != end;) {
2937 auto const meth = sub->methods.find(*nameIt);
2938 if (meth == sub->methods.end() ||
2939 !(meth->second.attrs & AttrPublic) ||
2940 meth->second.hasPrivateAncestor) {
2941 *nameIt = *--end;
2942 if (end == extras.begin()) return;
2943 } else {
2944 ++nameIt;
2948 extras.erase(end, extras.end());
2950 if (Trace::moduleEnabled(Trace::hhbbc_index, 5)) {
2951 FTRACE(5, "Adding extra methods to {}:\n", cinfo->cls->name);
2952 for (auto const DEBUG_ONLY extra : extras) {
2953 FTRACE(5, " {}\n", extra);
2957 hphp_fast_set<SString> added;
2959 for (auto name : extras) {
2960 if (define_func_family(data, cinfo, name) &&
2961 (cinfo->cls->attrs & AttrInterface)) {
2962 added.emplace(name);
2966 if (cinfo->cls->attrs & AttrInterface) {
2967 for (auto& m : cinfo->cls->methods) {
2968 if (added.count(m->name)) {
2969 cinfo->methods.emplace(
2970 m->name,
2971 MethTabEntry { m.get(), m->attrs, false, true }
2976 return;
2979 void define_func_families(IndexData& index) {
2980 trace_time tracer("define_func_families");
2982 parallel::for_each(
2983 index.allClassInfos,
2984 [&] (const std::unique_ptr<ClassInfo>& cinfo) {
2985 if (cinfo->cls->attrs & AttrTrait) return;
2986 FTRACE(4, "Defining func families for {}\n", cinfo->cls->name);
2987 if (!(cinfo->cls->attrs & AttrInterface)) {
2988 for (auto& kv : cinfo->methods) {
2989 auto const mte = mteFromElm(kv);
2991 if (mte->second.attrs & AttrNoOverride) continue;
2992 if (is_special_method_name(mte->first)) continue;
2994 // We need function family for constructor even if it is private,
2995 // as `new static()` may still call a non-private constructor from
2996 // subclass.
2997 if (!mte->first->isame(s_construct.get()) &&
2998 mte->second.attrs & AttrPrivate) {
2999 continue;
3002 define_func_family(index, cinfo.get(), mte->first, mte->second.func);
3005 if (cinfo->cls->attrs & (AttrInterface | AttrAbstract)) {
3006 build_abstract_func_families(index, cinfo.get());
3013 * ConflictGraph maintains lists of interfaces that conflict with each other
3014 * due to being implemented by the same class.
3016 struct ConflictGraph {
3017 void add(const php::Class* i, const php::Class* j) {
3018 if (i == j) return;
3019 map[i].insert(j);
3022 hphp_hash_map<const php::Class*,
3023 hphp_fast_set<const php::Class*>> map;
3027 * Trace information about interface conflict sets and the vtables computed
3028 * from them.
3030 void trace_interfaces(const IndexData& index, const ConflictGraph& cg) {
3031 // Compute what the vtable for each Class will look like, and build up a list
3032 // of all interfaces.
3033 struct Cls {
3034 const ClassInfo* cinfo;
3035 std::vector<const php::Class*> vtable;
3037 std::vector<Cls> classes;
3038 std::vector<const php::Class*> ifaces;
3039 size_t total_slots = 0, empty_slots = 0;
3040 for (auto& cinfo : index.allClassInfos) {
3041 if (cinfo->cls->attrs & AttrInterface) {
3042 ifaces.emplace_back(cinfo->cls);
3043 continue;
3045 if (cinfo->cls->attrs & (AttrTrait | AttrEnum | AttrAbstract)) continue;
3047 classes.emplace_back(Cls{cinfo.get()});
3048 auto& vtable = classes.back().vtable;
3049 for (auto& pair : cinfo->implInterfaces) {
3050 auto it = index.ifaceSlotMap.find(pair.second->cls);
3051 assert(it != end(index.ifaceSlotMap));
3052 auto const slot = it->second;
3053 if (slot >= vtable.size()) vtable.resize(slot + 1);
3054 vtable[slot] = pair.second->cls;
3057 total_slots += vtable.size();
3058 for (auto iface : vtable) if (iface == nullptr) ++empty_slots;
3061 Slot max_slot = 0;
3062 for (auto const& pair : index.ifaceSlotMap) {
3063 max_slot = std::max(max_slot, pair.second);
3066 // Sort the list of class vtables so the largest ones come first.
3067 auto class_cmp = [&](const Cls& a, const Cls& b) {
3068 return a.vtable.size() > b.vtable.size();
3070 std::sort(begin(classes), end(classes), class_cmp);
3072 // Sort the list of interfaces so the biggest conflict sets come first.
3073 auto iface_cmp = [&](const php::Class* a, const php::Class* b) {
3074 return cg.map.at(a).size() > cg.map.at(b).size();
3076 std::sort(begin(ifaces), end(ifaces), iface_cmp);
3078 std::string out;
3079 folly::format(&out, "{} interfaces, {} classes\n",
3080 ifaces.size(), classes.size());
3081 folly::format(&out,
3082 "{} vtable slots, {} empty vtable slots, max slot {}\n",
3083 total_slots, empty_slots, max_slot);
3084 folly::format(&out, "\n{:-^80}\n", " interface slots & conflict sets");
3085 for (auto iface : ifaces) {
3086 auto cgIt = cg.map.find(iface);
3087 if (cgIt == end(cg.map)) break;
3088 auto& conflicts = cgIt->second;
3090 folly::format(&out, "{:>40} {:3} {:2} [", iface->name,
3091 conflicts.size(),
3092 folly::get_default(index.ifaceSlotMap, iface));
3093 auto sep = "";
3094 for (auto conflict : conflicts) {
3095 folly::format(&out, "{}{}", sep, conflict->name);
3096 sep = ", ";
3098 folly::format(&out, "]\n");
3101 folly::format(&out, "\n{:-^80}\n", " class vtables ");
3102 for (auto& item : classes) {
3103 if (item.vtable.empty()) break;
3105 folly::format(&out, "{:>30}: [", item.cinfo->cls->name);
3106 auto sep = "";
3107 for (auto iface : item.vtable) {
3108 folly::format(&out, "{}{}", sep, iface ? iface->name->data() : "null");
3109 sep = ", ";
3111 folly::format(&out, "]\n");
3114 Trace::traceRelease("%s", out.c_str());
3118 * Find the lowest Slot that doesn't conflict with anything in the conflict set
3119 * for iface.
3121 Slot find_min_slot(const php::Class* iface,
3122 const IfaceSlotMap& slots,
3123 const ConflictGraph& cg) {
3124 auto const& cit = cg.map.find(iface);
3125 if (cit == cg.map.end() || cit->second.empty()) {
3126 // No conflicts. This is the only interface implemented by the classes that
3127 // implement it.
3128 return 0;
3131 boost::dynamic_bitset<> used;
3133 for (auto const& c : cit->second) {
3134 auto const it = slots.find(c);
3135 if (it == slots.end()) continue;
3136 auto const slot = it->second;
3138 if (used.size() <= slot) used.resize(slot + 1);
3139 used.set(slot);
3141 used.flip();
3142 return used.any() ? used.find_first() : used.size();
3146 * Compute vtable slots for all interfaces. No two interfaces implemented by
3147 * the same class will share the same vtable slot.
3149 void compute_iface_vtables(IndexData& index) {
3150 trace_time tracer("compute interface vtables");
3152 ConflictGraph cg;
3153 std::vector<const php::Class*> ifaces;
3154 hphp_hash_map<const php::Class*, int> iface_uses;
3156 // Build up the conflict sets.
3157 for (auto& cinfo : index.allClassInfos) {
3158 // Gather interfaces.
3159 if (cinfo->cls->attrs & AttrInterface) {
3160 ifaces.emplace_back(cinfo->cls);
3161 // Make sure cg.map has an entry for every interface - this simplifies
3162 // some code later on.
3163 cg.map[cinfo->cls];
3164 continue;
3167 // Only worry about classes that can be instantiated. If an abstract class
3168 // has any concrete subclasses, those classes will make sure the right
3169 // entries are in the conflict sets.
3170 if (cinfo->cls->attrs & (AttrTrait | AttrEnum | AttrAbstract)) continue;
3172 for (auto& ipair : cinfo->implInterfaces) {
3173 ++iface_uses[ipair.second->cls];
3174 for (auto& jpair : cinfo->implInterfaces) {
3175 cg.add(ipair.second->cls, jpair.second->cls);
3180 if (ifaces.size() == 0) return;
3182 // Sort interfaces by usage frequencies.
3183 // We assign slots greedily, so sort the interface list so the most
3184 // frequently implemented ones come first.
3185 auto iface_cmp = [&](const php::Class* a, const php::Class* b) {
3186 return iface_uses[a] > iface_uses[b];
3188 std::sort(begin(ifaces), end(ifaces), iface_cmp);
3190 // Assign slots, keeping track of the largest assigned slot and the total
3191 // number of uses for each slot.
3192 Slot max_slot = 0;
3193 hphp_hash_map<Slot, int> slot_uses;
3194 for (auto* iface : ifaces) {
3195 auto const slot = find_min_slot(iface, index.ifaceSlotMap, cg);
3196 index.ifaceSlotMap[iface] = slot;
3197 max_slot = std::max(max_slot, slot);
3199 // Interfaces implemented by the same class never share a slot, so normal
3200 // addition is fine here.
3201 slot_uses[slot] += iface_uses[iface];
3204 // Make sure we have an initialized entry for each slot for the sort below.
3205 for (Slot slot = 0; slot < max_slot; ++slot) {
3206 assert(slot_uses.count(slot));
3209 // Finally, sort and reassign slots so the most frequently used slots come
3210 // first. This slightly reduces the number of wasted vtable vector entries at
3211 // runtime.
3212 auto const slots = sort_keys_by_value(
3213 slot_uses,
3214 [&] (int a, int b) { return a > b; }
3217 std::vector<Slot> slots_permute(max_slot + 1, 0);
3218 for (size_t i = 0; i <= max_slot; ++i) slots_permute[slots[i]] = i;
3220 // re-map interfaces to permuted slots
3221 for (auto& pair : index.ifaceSlotMap) {
3222 pair.second = slots_permute[pair.second];
3225 if (Trace::moduleEnabledRelease(Trace::hhbbc_iface)) {
3226 trace_interfaces(index, cg);
3230 void mark_magic_on_parents(ClassInfo& cinfo, ClassInfo& derived) {
3231 auto any = false;
3232 for (const auto& mm : magicMethods) {
3233 if ((derived.*mm.pmem).thisHas) {
3234 auto& derivedHas = (cinfo.*mm.pmem).derivedHas;
3235 if (!derivedHas) {
3236 derivedHas = any = true;
3240 if (!any) return;
3241 if (cinfo.parent) mark_magic_on_parents(*cinfo.parent, derived);
3242 for (auto iface : cinfo.declInterfaces) {
3243 mark_magic_on_parents(*const_cast<ClassInfo*>(iface), derived);
3247 bool has_magic_method(const ClassInfo* cinfo, SString name) {
3248 if (name == s_toBoolean.get()) {
3249 // note that "having" a magic method includes the possibility that
3250 // a parent class has it. This can't happen for the collection
3251 // classes, because they're all final; but for SimpleXMLElement,
3252 // we need to search.
3253 while (cinfo->parent) cinfo = cinfo->parent;
3254 return has_magic_bool_conversion(cinfo->cls->name);
3256 return cinfo->methods.find(name) != end(cinfo->methods);
3259 void find_magic_methods(IndexData& index) {
3260 for (auto& cinfo : index.allClassInfos) {
3261 bool any = false;
3262 for (const auto& mm : magicMethods) {
3263 bool const found = has_magic_method(cinfo.get(), mm.name.get());
3264 any = any || found;
3265 (cinfo.get()->*mm.pmem).thisHas = found;
3267 if (any) mark_magic_on_parents(*cinfo, *cinfo);
3271 void find_mocked_classes(IndexData& index) {
3272 for (auto& cinfo : index.allClassInfos) {
3273 if (is_mock_class(cinfo->cls) && cinfo->parent) {
3274 cinfo->parent->isMocked = true;
3275 for (auto c = cinfo->parent; c; c = c->parent) {
3276 c->isDerivedMocked = true;
3282 void mark_const_props(IndexData& index) {
3283 for (auto& cinfo : index.allClassInfos) {
3284 auto const hasConstProp = [&]() {
3285 if (cinfo->cls->hasConstProp) return true;
3286 if (cinfo->parent && cinfo->parent->hasConstProp) return true;
3287 if (!(cinfo->cls->attrs & AttrNoExpandTrait)) {
3288 for (auto t : cinfo->usedTraits) {
3289 if (t->cls->hasConstProp) return true;
3292 return false;
3293 }();
3294 if (hasConstProp) {
3295 cinfo->hasConstProp = true;
3296 for (auto c = cinfo.get(); c; c = c->parent) {
3297 if (c->derivedHasConstProp) break;
3298 c->derivedHasConstProp = true;
3304 void mark_no_override_classes(IndexData& index) {
3305 for (auto& cinfo : index.allClassInfos) {
3306 // We cleared all the NoOverride flags while building the
3307 // index. Set them as necessary.
3308 if (!(cinfo->cls->attrs & AttrUnique)) continue;
3309 if (!(cinfo->cls->attrs & AttrInterface) &&
3310 cinfo->subclassList.size() == 1) {
3311 attribute_setter(cinfo->cls->attrs, true, AttrNoOverride);
3314 for (const auto& mm : magicMethods) {
3315 if (mm.attrBit == AttrNone) continue;
3316 if (!(cinfo.get()->*mm.pmem).derivedHas) {
3317 FTRACE(2, "Adding no-override of {} to {}\n",
3318 mm.name.get()->data(),
3319 cinfo->cls->name);
3320 attribute_setter(cinfo->cls->attrs, true, mm.attrBit);
3326 void mark_no_override_methods(IndexData& index) {
3327 // We removed any AttrNoOverride flags from all methods while adding
3328 // the units to the index. Now start by marking every
3329 // (non-interface, non-special) method as AttrNoOverride.
3330 for (auto& cinfo : index.allClassInfos) {
3331 if (cinfo->cls->attrs & AttrInterface) continue;
3332 if (!(cinfo->cls->attrs & AttrUnique)) continue;
3334 for (auto& m : cinfo->methods) {
3335 if (!(is_special_method_name(m.first))) {
3336 FTRACE(9, "Pre-setting AttrNoOverride on {}::{}\n",
3337 m.second.func->cls->name, m.first);
3338 attribute_setter(m.second.attrs, true, AttrNoOverride);
3339 attribute_setter(m.second.func->attrs, true, AttrNoOverride);
3344 // Then run through every ClassInfo, and for each of its parent classes clear
3345 // the AttrNoOverride flag if it has a different Func with the same name.
3346 for (auto& cinfo : index.allClassInfos) {
3347 for (auto& ancestor : cinfo->baseList) {
3348 if (ancestor == cinfo.get()) continue;
3350 auto removeNoOverride = [] (auto it) {
3351 assertx(it->second.attrs & AttrNoOverride ||
3352 !(it->second.func->attrs & AttrNoOverride));
3353 if (it->second.attrs & AttrNoOverride) {
3354 FTRACE(2, "Removing AttrNoOverride on {}::{}\n",
3355 it->second.func->cls->name, it->first);
3356 attribute_setter(it->second.attrs, false, AttrNoOverride);
3357 attribute_setter(it->second.func->attrs, false, AttrNoOverride);
3361 for (auto& derivedMethod : cinfo->methods) {
3362 auto const it = ancestor->methods.find(derivedMethod.first);
3363 if (it == end(ancestor->methods)) continue;
3364 if (it->second.func != derivedMethod.second.func) {
3365 removeNoOverride(it);
3372 template <class T, class S, class F>
3373 void mark_unique_entities(
3374 std::unordered_multimap<SString, T*, string_data_hash, S> entities,
3375 F marker) {
3376 auto key_eq = entities.key_eq();
3377 for (auto it = entities.begin(), end = entities.end(); it != end; ) {
3378 auto first = it++;
3379 auto flag = true;
3380 while (it != end && key_eq(it->first, first->first)) {
3381 marker(it++->second, false);
3382 flag = false;
3384 marker(first->second, flag);
3388 const StaticString s__Reified("__Reified");
3391 * Emitter adds a 86reifiedinit method to all classes that have reified
3392 * generics. All base classes also need to have this method so that when we
3393 * call parent::86reifeidinit(...), there is a stopping point.
3394 * Since while emitting we do not know whether a base class will have
3395 * reified parents, during JIT time we need to add 86reifiedinit
3396 * unless AttrNoReifiedInit attribute is set. At this phase,
3397 * we set AttrNoReifiedInit attribute on classes do not have any
3398 * reified classes that extend it.
3400 void clean_86reifiedinit_methods(IndexData& index) {
3401 trace_time tracer("clean 86reifiedinit methods");
3402 folly::F14FastSet<const php::Class*> needsinit;
3404 // Find all classes that still need their 86reifiedinit methods
3405 for (auto& cinfo : index.allClassInfos) {
3406 auto ual = cinfo->cls->userAttributes;
3407 // Each class that has at least one reified generic has an attribute
3408 // __Reified added by the emitter
3409 auto has_reification = ual.find(s__Reified.get()) != ual.end();
3410 if (!has_reification) continue;
3411 // Add the base class for this reified class
3412 needsinit.emplace(cinfo->baseList[0]->cls);
3415 // Add AttrNoReifiedInit to the base classes that do not need this method
3416 for (auto& cinfo : index.allClassInfos) {
3417 if (cinfo->parent == nullptr && needsinit.count(cinfo->cls) == 0) {
3418 FTRACE(2, "Adding AttrNoReifiedInit on class {}\n", cinfo->cls->name);
3419 attribute_setter(cinfo->cls->attrs, true, AttrNoReifiedInit);
3424 //////////////////////////////////////////////////////////////////////
3426 void check_invariants(const ClassInfo* cinfo) {
3427 // All the following invariants only apply to classes
3428 if (cinfo->cls->attrs & AttrInterface) return;
3430 if (!(cinfo->cls->attrs & AttrTrait)) {
3431 // For non-interface classes, each method in a php class has an
3432 // entry in its ClassInfo method table, and if it's not special,
3433 // AttrNoOverride, or private, an entry in the family table.
3434 for (auto& m : cinfo->cls->methods) {
3435 auto const it = cinfo->methods.find(m->name);
3436 always_assert(it != cinfo->methods.end());
3437 if (it->second.attrs & (AttrNoOverride|AttrPrivate)) continue;
3438 if (is_special_method_name(m->name)) continue;
3439 always_assert(cinfo->methodFamilies.count(m->name));
3443 // The subclassList is non-empty, contains this ClassInfo, and
3444 // contains only unique elements.
3445 always_assert(!cinfo->subclassList.empty());
3446 always_assert(std::find(begin(cinfo->subclassList),
3447 end(cinfo->subclassList),
3448 cinfo) != end(cinfo->subclassList));
3449 auto cpy = cinfo->subclassList;
3450 std::sort(begin(cpy), end(cpy));
3451 cpy.erase(
3452 std::unique(begin(cpy), end(cpy)),
3453 end(cpy)
3455 always_assert(cpy.size() == cinfo->subclassList.size());
3457 // The baseList is non-empty, and the last element is this class.
3458 always_assert(!cinfo->baseList.empty());
3459 always_assert(cinfo->baseList.back() == cinfo);
3461 for (const auto& mm : magicMethods) {
3462 const auto& info = cinfo->*mm.pmem;
3464 // Magic method flags should be consistent with the method table.
3465 always_assert(info.thisHas == has_magic_method(cinfo, mm.name.get()));
3467 // Non-'derived' flags (thisHas) about magic methods imply the derived
3468 // ones.
3469 always_assert(!info.thisHas || info.derivedHas);
3472 // Every FuncFamily is non-empty and contain functions with the same
3473 // name (unless its a family of ctors).
3474 for (auto const& mfam: cinfo->methodFamilies) {
3475 always_assert(!mfam.second.possibleFuncs()->empty());
3476 auto const name = mfam.second.possibleFuncs()->front()->first;
3477 for (auto const pf : mfam.second.possibleFuncs()) {
3478 always_assert(pf->first->isame(name));
3483 void check_invariants(IndexData& data) {
3484 if (!debug) return;
3486 // Every AttrUnique non-trait class has a unique ClassInfo object,
3487 // or no ClassInfo object in the case that instantiating it would've
3488 // fataled.
3489 for (auto& kv : data.classes) {
3490 auto const name = kv.first;
3491 auto const cls = kv.second;
3492 if (!(cls->attrs & AttrUnique)) continue;
3494 auto const range = find_range(data.classInfo, name);
3495 if (begin(range) != end(range)) {
3496 always_assert(std::next(begin(range)) == end(range));
3500 for (auto& cinfo : data.allClassInfos) {
3501 check_invariants(cinfo.get());
3505 //////////////////////////////////////////////////////////////////////
3507 Type context_sensitive_return_type(IndexData& data,
3508 CallContext callCtx) {
3509 constexpr auto max_interp_nexting_level = 2;
3510 static __thread uint32_t interp_nesting_level;
3511 auto const finfo = func_info(data, callCtx.callee);
3512 auto const returnType = return_with_context(finfo->returnTy, callCtx.context);
3514 auto checkParam = [&] (int i) {
3515 auto const constraint = finfo->func->params[i].typeConstraint;
3516 if (constraint.hasConstraint() &&
3517 !constraint.isTypeVar() &&
3518 !constraint.isTypeConstant()) {
3519 auto ctx = Context {
3520 finfo->func->unit,
3521 const_cast<php::Func*>(finfo->func),
3522 finfo->func->cls
3524 auto t = loosen_dvarrayness(
3525 data.m_index->lookup_constraint(ctx, constraint));
3526 return callCtx.args[i].strictlyMoreRefined(t);
3528 return callCtx.args[i].strictSubtypeOf(TInitCell);
3531 // TODO(#3788877): more heuristics here would be useful.
3532 bool const tryContextSensitive = [&] {
3533 if (finfo->func->noContextSensitiveAnalysis ||
3534 finfo->func->params.empty() ||
3535 interp_nesting_level + 1 >= max_interp_nexting_level ||
3536 returnType == TBottom) {
3537 return false;
3540 if (finfo->retParam != NoLocalId &&
3541 callCtx.args.size() > finfo->retParam &&
3542 checkParam(finfo->retParam)) {
3543 return true;
3546 if (!options.ContextSensitiveInterp) return false;
3548 if (callCtx.args.size() < finfo->func->params.size()) return true;
3549 for (auto i = 0; i < finfo->func->params.size(); i++) {
3550 if (checkParam(i)) return true;
3552 return false;
3553 }();
3555 if (!tryContextSensitive) {
3556 return returnType;
3559 auto maybe_loosen_staticness = [&] (const Type& ty) {
3560 return returnType.subtypeOf(BUnc) ? ty : loosen_staticness(ty);
3564 ContextRetTyMap::const_accessor acc;
3565 if (data.contextualReturnTypes.find(acc, callCtx)) {
3566 if (data.frozen || acc->second == TBottom || is_scalar(acc->second)) {
3567 return maybe_loosen_staticness(acc->second);
3572 if (data.frozen) {
3573 return returnType;
3576 auto contextType = [&] {
3577 ++interp_nesting_level;
3578 SCOPE_EXIT { --interp_nesting_level; };
3580 auto const calleeCtx = Context {
3581 finfo->func->unit,
3582 const_cast<php::Func*>(finfo->func),
3583 finfo->func->cls
3585 auto const ty =
3586 analyze_func_inline(*data.m_index, calleeCtx,
3587 callCtx.context, callCtx.args).inferredReturn;
3588 return return_with_context(ty, callCtx.context);
3589 }();
3591 if (!interp_nesting_level) {
3592 FTRACE(3,
3593 "Context sensitive type: {}\n"
3594 "Context insensitive type: {}\n",
3595 show(contextType), show(returnType));
3598 auto ret = intersection_of(std::move(returnType),
3599 std::move(contextType));
3601 ContextRetTyMap::accessor acc;
3602 if (data.contextualReturnTypes.insert(acc, callCtx) ||
3603 ret.strictSubtypeOf(acc->second)) {
3604 acc->second = ret;
3607 if (!interp_nesting_level) {
3608 ret = maybe_loosen_staticness(ret);
3609 FTRACE(3, "Context sensitive result: {}\n", show(ret));
3612 return ret;
3615 //////////////////////////////////////////////////////////////////////
3617 PrepKind func_param_prep(const php::Func* func,
3618 uint32_t paramId) {
3619 if (func->attrs & AttrInterceptable) return PrepKind::Unknown;
3620 if (paramId >= func->params.size()) {
3621 return PrepKind::Val;
3623 return func->params[paramId].inout ? PrepKind::InOut : PrepKind::Val;
3626 folly::Optional<uint32_t> func_num_inout(const php::Func* func) {
3627 if (func->attrs & AttrInterceptable) return folly::none;
3628 if (!func->hasInOutArgs) return 0;
3629 uint32_t count = 0;
3630 for (auto& p : func->params) count += p.inout;
3631 return count;
3634 template<class PossibleFuncRange>
3635 PrepKind prep_kind_from_set(PossibleFuncRange range, uint32_t paramId) {
3638 * In sinlge-unit mode, the range is not complete. Without konwing all
3639 * possible resolutions, HHBBC cannot deduce anything about by-ref vs by-val.
3640 * So the caller should make sure not calling this in single-unit mode.
3642 assert(RuntimeOption::RepoAuthoritative);
3644 if (begin(range) == end(range)) {
3646 * We can assume it's by value, because either we're calling a function
3647 * that doesn't exist (about to fatal), or we're going to an __call (which
3648 * never takes parameters by reference).
3650 * Or if we've got AllFuncsInterceptable we need to assume someone could
3651 * rename a function to the new name.
3653 return RuntimeOption::EvalJitEnableRenameFunction ?
3654 PrepKind::Unknown : PrepKind::Val;
3657 struct FuncFind {
3658 using F = const php::Func*;
3659 static F get(std::pair<SString,F> p) { return p.second; }
3660 static F get(const MethTabEntryPair* mte) { return mte->second.func; }
3663 folly::Optional<PrepKind> prep;
3664 for (auto& item : range) {
3665 switch (func_param_prep(FuncFind::get(item), paramId)) {
3666 case PrepKind::Unknown:
3667 return PrepKind::Unknown;
3668 case PrepKind::InOut:
3669 if (prep && *prep != PrepKind::InOut) return PrepKind::Unknown;
3670 prep = PrepKind::InOut;
3671 break;
3672 case PrepKind::Val:
3673 if (prep && *prep != PrepKind::Val) return PrepKind::Unknown;
3674 prep = PrepKind::Val;
3675 break;
3678 return *prep;
3681 template<class PossibleFuncRange>
3682 folly::Optional<uint32_t> num_inout_from_set(PossibleFuncRange range) {
3685 * In sinlge-unit mode, the range is not complete. Without konwing all
3686 * possible resolutions, HHBBC cannot deduce anything about inout args.
3687 * So the caller should make sure not calling this in single-unit mode.
3689 assert(RuntimeOption::RepoAuthoritative);
3691 if (begin(range) == end(range)) {
3693 * We can assume it's by value, because either we're calling a function
3694 * that doesn't exist (about to fatal), or we're going to an __call (which
3695 * never takes parameters by reference).
3697 * Or if we've got AllFuncsInterceptable we need to assume someone could
3698 * rename a function to the new name.
3700 if (RuntimeOption::EvalJitEnableRenameFunction) return folly::none;
3701 return 0;
3704 struct FuncFind {
3705 using F = const php::Func*;
3706 static F get(std::pair<SString,F> p) { return p.second; }
3707 static F get(const MethTabEntryPair* mte) { return mte->second.func; }
3710 folly::Optional<uint32_t> num;
3711 for (auto& item : range) {
3712 auto const n = func_num_inout(FuncFind::get(item));
3713 if (!n.hasValue()) return folly::none;
3714 if (num.hasValue() && n != num) return folly::none;
3715 num = n;
3717 return num;
3720 template<typename F> auto
3721 visit_parent_cinfo(const ClassInfo* cinfo, F fun) -> decltype(fun(cinfo)) {
3722 for (auto ci = cinfo; ci != nullptr; ci = ci->parent) {
3723 if (auto const ret = fun(ci)) return ret;
3724 if (ci->cls->attrs & AttrNoExpandTrait) continue;
3725 for (auto ct : ci->usedTraits) {
3726 if (auto const ret = visit_parent_cinfo(ct, fun)) {
3727 return ret;
3731 return {};
3734 PublicSPropEntry lookup_public_static_impl(
3735 const IndexData& data,
3736 const ClassInfo* cinfo,
3737 SString prop
3739 auto const noInfo = PublicSPropEntry{TInitCell, TInitCell, nullptr, 0, true};
3741 if (data.allPublicSPropsUnknown) return noInfo;
3743 const ClassInfo* knownCInfo = nullptr;
3744 auto const knownClsPart = visit_parent_cinfo(
3745 cinfo,
3746 [&] (const ClassInfo* ci) -> const PublicSPropEntry* {
3747 auto const it = ci->publicStaticProps.find(prop);
3748 if (it != end(ci->publicStaticProps)) {
3749 knownCInfo = ci;
3750 return &it->second;
3752 return nullptr;
3756 auto const unkPart = [&]() -> const Type* {
3757 auto unkIt = data.unknownClassSProps.find(prop);
3758 if (unkIt != end(data.unknownClassSProps)) {
3759 return &unkIt->second.first;
3761 return nullptr;
3762 }();
3764 if (knownClsPart == nullptr) {
3765 return noInfo;
3768 for (auto const& prop_it : knownCInfo->cls->properties) {
3769 if (prop_it.name == prop && (prop_it.attrs & AttrIsConst)) {
3770 return *knownClsPart;
3774 // NB: Inferred type can be TBottom here if the property is never set to a
3775 // value which can satisfy its type constraint. Such properties can't exist at
3776 // runtime.
3778 if (unkPart != nullptr) {
3779 return PublicSPropEntry {
3780 union_of(
3781 knownClsPart->inferredType,
3782 *unkPart
3784 knownClsPart->initializerType,
3785 nullptr,
3787 true
3790 return *knownClsPart;
3793 PublicSPropEntry lookup_public_static_impl(
3794 const IndexData& data,
3795 const php::Class* cls,
3796 SString name
3798 auto const classes = find_range(data.classInfo, cls->name);
3799 if (begin(classes) == end(classes) ||
3800 std::next(begin(classes)) != end(classes)) {
3801 return PublicSPropEntry{TInitCell, TInitCell, nullptr, 0, true};
3803 return lookup_public_static_impl(data, begin(classes)->second, name);
3806 Type lookup_public_prop_impl(
3807 const IndexData& data,
3808 const ClassInfo* cinfo,
3809 SString propName
3811 // Find a property declared in this class (or a parent) with the same name.
3812 const php::Class* knownCls = nullptr;
3813 auto const prop = visit_parent_cinfo(
3814 cinfo,
3815 [&] (const ClassInfo* ci) -> const php::Prop* {
3816 for (auto const& prop : ci->cls->properties) {
3817 if (prop.name == propName) {
3818 knownCls = ci->cls;
3819 return &prop;
3822 return nullptr;
3826 if (!prop) return TCell;
3827 // Make sure its non-static and public. Otherwise its another function's
3828 // problem.
3829 if (prop->attrs & (AttrStatic | AttrPrivate)) return TCell;
3831 // Get a type corresponding to its declared type-hint (if any).
3832 auto ty = adjust_type_for_prop(
3833 *data.m_index, *knownCls, &prop->typeConstraint, TCell
3835 // We might have to include the initial value which might be outside of the
3836 // type-hint.
3837 auto initialTy = loosen_all(from_cell(prop->val));
3838 if (!initialTy.subtypeOf(TUninit) && (prop->attrs & AttrSystemInitialValue)) {
3839 ty |= initialTy;
3841 return ty;
3844 //////////////////////////////////////////////////////////////////////
3848 //////////////////////////////////////////////////////////////////////
3849 namespace {
3850 template<typename T>
3851 void buildTypeInfoData(TypeInfoData<T>& tid,
3852 const ISStringToMany<const T>& tmap) {
3853 for (auto const& elm : tmap) {
3854 auto const t = elm.second;
3855 auto const addUser = [&] (SString rName) {
3856 tid.users[rName].push_back(t);
3857 auto const count = tmap.count(rName);
3858 tid.depCounts[t] += count ? count : 1;
3860 PhpTypeHelper<T>::process_bases(t, addUser);
3862 if (!tid.depCounts.count(t)) {
3863 FTRACE(5, "Adding no-dep {} {}:{} to queue\n",
3864 PhpTypeHelper<T>::name(), t->name, (void*)t);
3865 // make sure that closure is first, because we end up calling
3866 // preresolve directly on closures created by trait
3867 // flattening, which assumes all dependencies are satisfied.
3868 if (tid.queue.size() && t->name == s_Closure.get()) {
3869 tid.queue.push_back(tid.queue[0]);
3870 tid.queue[0] = t;
3871 } else {
3872 tid.queue.push_back(t);
3874 } else {
3875 FTRACE(6, "{} {}:{} has {} deps\n",
3876 PhpTypeHelper<T>::name(), t->name, (void*)t, tid.depCounts[t]);
3879 tid.cqBack = tid.queue.size();
3880 tid.queue.resize(tmap.size());
3883 template<typename T>
3884 void preresolveTypes(NamingEnv<T>& env,
3885 TypeInfoData<T>& tid,
3886 const ISStringToMany<TypeInfo<T>>& tmap) {
3887 while(true) {
3888 auto const ix = tid.cqFront++;
3889 if (ix == tid.cqBack) {
3890 // we've consumed everything where all dependencies are
3891 // satisfied. There may still be some pseudo-cycles that can
3892 // be broken though.
3894 // eg if A extends B and B' extends A', we'll resolve B and
3895 // A', and then end up here, since both A and B' still have
3896 // one dependency. But both A and B' can be resolved at this
3897 // point
3898 for (auto it = tid.depCounts.begin();
3899 it != tid.depCounts.end();
3901 auto canResolve = true;
3902 auto const checkCanResolve = [&] (SString name) {
3903 if (canResolve) canResolve = tmap.count(name);
3905 PhpTypeHelper<T>::process_bases(it->first, checkCanResolve);
3906 if (canResolve) {
3907 FTRACE(2, "Breaking pseudo-cycle for {} {}:{}\n",
3908 PhpTypeHelper<T>::name(), it->first->name, (void*)it->first);
3909 tid.queue[tid.cqBack++] = it->first;
3910 it = tid.depCounts.erase(it);
3911 tid.hasPseudoCycles = true;
3912 } else {
3913 ++it;
3916 if (ix == tid.cqBack) {
3917 break;
3920 auto const t = tid.queue[ix];
3921 Trace::Bump bumper{
3922 Trace::hhbbc_index, kSystemLibBump, is_systemlib_part(*t->unit)
3924 (void)bumper;
3925 preresolve(env, t);
3928 } //namespace
3930 Index::Index(php::Program* program)
3931 : m_data(std::make_unique<IndexData>(this))
3933 trace_time tracer("create index");
3935 m_data->arrTableBuilder.reset(new ArrayTypeTable::Builder());
3937 add_system_constants_to_index(*m_data);
3940 trace_time trace_add_units("add units to index");
3941 for (auto& u : program->units) {
3942 add_unit_to_index(*m_data, *u);
3946 RecordInfoData rid;
3948 trace_time build_record_info_data("build recordinfo data");
3949 buildTypeInfoData(rid, m_data->records);
3953 trace_time preresolve_records("preresolve records");
3954 RecordNamingEnv env{program, *m_data, rid};
3955 preresolveTypes(env, rid, m_data->recordInfo);
3958 ClassInfoData cid;
3960 trace_time build_class_info_data("build classinfo data");
3961 buildTypeInfoData(cid, m_data->classes);
3965 trace_time preresolve_classes("preresolve classes");
3966 ClassNamingEnv env{program, *m_data, cid};
3967 preresolveTypes(env, cid, m_data->classInfo);
3970 mark_unique_entities(
3971 m_data->typeAliases,
3972 [&] (const php::TypeAlias* ta, bool flag) {
3973 attribute_setter(
3974 ta->attrs,
3975 flag &&
3976 !m_data->classInfo.count(ta->name) &&
3977 !m_data->records.count(ta->name),
3978 AttrUnique);
3981 mark_unique_entities(
3982 m_data->constants,
3983 [&] (const php::Constant* c, bool flag) {
3984 attribute_setter(c->attrs, flag, AttrUnique);
3987 for (auto& rinfo : m_data->allRecordInfos) {
3988 auto const set = [&] {
3989 auto const recname = rinfo->rec->name;
3990 if (m_data->recordInfo.count(recname) != 1 ||
3991 m_data->typeAliases.count(recname) ||
3992 m_data->classes.count(recname)) {
3993 return false;
3995 if (rinfo->parent && !(rinfo->parent->rec->attrs & AttrUnique)) {
3996 return false;
3998 FTRACE(2, "Adding AttrUnique to record {}\n", recname->data());
3999 return true;
4000 }();
4001 attribute_setter(rinfo->rec->attrs, set, AttrUnique);
4004 // Iterate allClassInfos so that we visit parent classes before
4005 // child classes.
4006 for (auto& cinfo : m_data->allClassInfos) {
4007 auto const set = [&] {
4008 if (m_data->classInfo.count(cinfo->cls->name) != 1 ||
4009 m_data->typeAliases.count(cinfo->cls->name) ||
4010 m_data->records.count(cinfo->cls->name)) {
4011 return false;
4013 if (cinfo->parent && !(cinfo->parent->cls->attrs & AttrUnique)) {
4014 return false;
4016 for (auto const i : cinfo->declInterfaces) {
4017 if (!(i->cls->attrs & AttrUnique)) return false;
4019 for (auto const t : cinfo->usedTraits) {
4020 if (!(t->cls->attrs & AttrUnique)) return false;
4022 FTRACE(2, "Adding AttrUnique to class {}\n", cinfo->cls->name->data());
4023 return true;
4024 }();
4025 attribute_setter(cinfo->cls->attrs, set, AttrUnique);
4028 mark_unique_entities(
4029 m_data->funcs,
4030 [&] (const php::Func* func, bool flag) {
4031 attribute_setter(func->attrs, flag, AttrUnique);
4034 m_data->funcInfo.resize(program->nextFuncId);
4036 // Part of the index building routines happens before the various asserted
4037 // index invariants hold. These each may depend on computations from
4038 // previous functions, so be careful changing the order here.
4039 compute_subclass_list(*m_data);
4040 clean_86reifiedinit_methods(*m_data); // uses the base class lists
4041 mark_no_override_methods(*m_data); // uses AttrUnique
4042 find_magic_methods(*m_data); // uses the subclass lists
4043 find_mocked_classes(*m_data);
4044 mark_const_props(*m_data);
4045 auto const logging = Trace::moduleEnabledRelease(Trace::hhbbc_time, 1);
4046 m_data->compute_iface_vtables = std::thread([&] {
4047 HphpSessionAndThread _{Treadmill::SessionKind::HHBBC};
4048 auto const enable =
4049 logging && !Trace::moduleEnabledRelease(Trace::hhbbc_time, 1);
4050 Trace::BumpRelease bumper(Trace::hhbbc_time, -1, enable);
4051 compute_iface_vtables(*m_data);
4054 define_func_families(*m_data); // AttrNoOverride, iface_vtables,
4055 // subclass_list
4057 check_invariants(*m_data);
4059 mark_no_override_classes(*m_data); // uses AttrUnique
4061 if (RuntimeOption::EvalCheckReturnTypeHints == 3) {
4062 trace_time tracer("initialize return types");
4063 std::vector<const php::Func*> all_funcs;
4064 all_funcs.reserve(m_data->funcs.size() + m_data->methods.size());
4065 for (auto const fn : m_data->funcs) {
4066 all_funcs.push_back(fn.second);
4068 for (auto const fn : m_data->methods) {
4069 all_funcs.push_back(fn.second);
4072 parallel::for_each(all_funcs, [&] (const php::Func* f) {
4073 init_return_type(f);
4078 // Defined here so IndexData is a complete type for the unique_ptr
4079 // destructor.
4080 Index::~Index() {}
4082 //////////////////////////////////////////////////////////////////////
4084 void Index::mark_persistent_types_and_functions(php::Program& program) {
4085 auto persist = [] (const php::Unit* unit) {
4086 return
4087 unit->persistent.load(std::memory_order_relaxed) &&
4088 unit->persistent_pseudomain.load(std::memory_order_relaxed);
4090 for (auto& unit : program.units) {
4091 auto const persistent = persist(unit.get());
4092 for (auto& f : unit->funcs) {
4093 attribute_setter(f->attrs,
4094 persistent && (f->attrs & AttrUnique),
4095 AttrPersistent);
4098 for (auto& t : unit->typeAliases) {
4099 attribute_setter(t->attrs,
4100 persistent && (t->attrs & AttrUnique),
4101 AttrPersistent);
4104 for (auto& c : unit->constants) {
4105 attribute_setter(c->attrs,
4106 persistent && (c->attrs & AttrUnique),
4107 AttrPersistent);
4111 auto check_persistent_class = [&] (const ClassInfo& cinfo) {
4112 if (cinfo.parent && !(cinfo.parent->cls->attrs & AttrPersistent)) {
4113 return false;
4116 for (auto const intrf : cinfo.declInterfaces) {
4117 if (!(intrf->cls->attrs & AttrPersistent)) return false;
4120 return true;
4123 auto check_persistent_record = [&] (const RecordInfo& rinfo) {
4124 return !rinfo.parent || (rinfo.parent->rec->attrs & AttrPersistent);
4127 for (auto& c : m_data->allClassInfos) {
4128 attribute_setter(c->cls->attrs,
4129 (c->cls->attrs & AttrUnique) &&
4130 (persist(c->cls->unit) ||
4131 c->cls->parentName == s_Closure.get()) &&
4132 check_persistent_class(*c),
4133 AttrPersistent);
4136 for (auto& r : m_data->allRecordInfos) {
4137 attribute_setter(r->rec->attrs,
4138 (r->rec->attrs & AttrUnique) &&
4139 persist(r->rec->unit) &&
4140 check_persistent_record(*r),
4141 AttrPersistent);
4145 void Index::mark_no_bad_redeclare_props(php::Class& cls) const {
4147 * Keep a list of properties which have not yet been found to redeclare
4148 * anything inequivalently. Start out by putting everything on the list. Then
4149 * walk up the inheritance chain, removing collisions as we find them.
4151 std::vector<php::Prop*> props;
4152 for (auto& prop : cls.properties) {
4153 if (prop.attrs & (AttrStatic | AttrPrivate)) {
4154 // Static and private properties never redeclare anything so need not be
4155 // considered.
4156 attribute_setter(prop.attrs, true, AttrNoBadRedeclare);
4157 continue;
4159 attribute_setter(prop.attrs, false, AttrNoBadRedeclare);
4160 props.emplace_back(&prop);
4163 auto currentCls = [&]() -> const ClassInfo* {
4164 auto const rcls = resolve_class(&cls);
4165 if (rcls.val.left()) return nullptr;
4166 return rcls.val.right();
4167 }();
4168 // If there's one more than one resolution for the class, be conservative and
4169 // we'll treat everything as possibly redeclaring.
4170 if (!currentCls) props.clear();
4172 while (!props.empty()) {
4173 auto const parent = currentCls->parent;
4174 if (!parent) {
4175 // No parent. We're done, so anything left on the prop list is
4176 // AttrNoBadRedeclare.
4177 for (auto& prop : props) {
4178 attribute_setter(prop->attrs, true, AttrNoBadRedeclare);
4180 break;
4183 auto const findParentProp = [&] (SString name) -> const php::Prop* {
4184 for (auto& prop : parent->cls->properties) {
4185 if (prop.name == name) return &prop;
4187 for (auto& prop : parent->traitProps) {
4188 if (prop.name == name) return &prop;
4190 return nullptr;
4193 // Remove any properties which collide with the current class.
4195 auto const propRedeclares = [&] (php::Prop* prop) {
4196 auto const pprop = findParentProp(prop->name);
4197 if (!pprop) return false;
4199 // We found a property being redeclared. Check if the type-hints on
4200 // the two are equivalent.
4201 auto const equiv = [&] {
4202 auto const& tc1 = prop->typeConstraint;
4203 auto const& tc2 = pprop->typeConstraint;
4204 // Try the cheap check first, use the index otherwise. Two
4205 // type-constraints are equivalent if all the possible values of one
4206 // satisfies the other, and vice-versa.
4207 if (!tc1.maybeInequivalentForProp(tc2)) return true;
4208 return
4209 satisfies_constraint(
4210 Context{},
4211 lookup_constraint(Context{}, tc1),
4213 ) && satisfies_constraint(
4214 Context{},
4215 lookup_constraint(Context{}, tc2),
4219 // If the property in the parent is static or private, the property in
4220 // the child isn't actually redeclaring anything. Otherwise, if the
4221 // type-hints are equivalent, remove this property from further
4222 // consideration and mark it as AttrNoBadRedeclare.
4223 if ((pprop->attrs & (AttrStatic | AttrPrivate)) || equiv()) {
4224 attribute_setter(prop->attrs, true, AttrNoBadRedeclare);
4226 return true;
4229 props.erase(
4230 std::remove_if(props.begin(), props.end(), propRedeclares),
4231 props.end()
4234 currentCls = parent;
4237 auto const possibleOverride =
4238 std::any_of(
4239 cls.properties.begin(),
4240 cls.properties.end(),
4241 [&](const php::Prop& prop) { return !(prop.attrs & AttrNoBadRedeclare); }
4244 // Mark all resolutions of this class as having any possible bad redeclaration
4245 // props, even if there's not an unique resolution.
4246 for (auto& info : find_range(m_data->classInfo, cls.name)) {
4247 auto const cinfo = info.second;
4248 if (cinfo->cls != &cls) continue;
4249 cinfo->hasBadRedeclareProp = possibleOverride;
4254 * Rewrite the initial values for any AttrSystemInitialValue properties. If the
4255 * properties' type-hint does not admit null values, change the initial value to
4256 * one (if possible) to one that is not null. This is only safe to do so if the
4257 * property is not redeclared in a derived class or if the redeclaration does
4258 * not have a null system provided default value. Otherwise, a property can have
4259 * a null value (even if its type-hint doesn't allow it) without the JIT
4260 * realizing that its possible.
4262 * Note that this ignores any unflattened traits. This is okay because
4263 * properties pulled in from traits which match an already existing property
4264 * can't change the initial value. The runtime will clear AttrNoImplicitNullable
4265 * on any property pulled from the trait if it doesn't match an existing
4266 * property.
4268 void Index::rewrite_default_initial_values(php::Program& program) const {
4269 trace_time tracer("rewrite default initial values");
4272 * Use dataflow across the whole program class hierarchy. Start from the
4273 * classes which have no derived classes and flow up the hierarchy. We flow
4274 * the set of properties which have been assigned a null system provided
4275 * default value. If a property with such a null value flows into a class
4276 * which declares a property with the same name (and isn't static or private),
4277 * than that property is forced to be null as well.
4279 using PropSet = folly::F14FastSet<SString>;
4280 using OutState = folly::F14FastMap<const ClassInfo*, PropSet>;
4281 using Worklist = folly::F14FastSet<const ClassInfo*>;
4283 OutState outStates;
4284 outStates.reserve(m_data->allClassInfos.size());
4286 // List of Class' still to process this iteration
4287 using WorkList = std::vector<const ClassInfo*>;
4288 using WorkSet = folly::F14FastSet<const ClassInfo*>;
4290 WorkList workList;
4291 WorkSet workSet;
4292 auto const enqueue = [&] (const ClassInfo& cls) {
4293 auto const result = workSet.insert(&cls);
4294 if (!result.second) return;
4295 workList.emplace_back(&cls);
4298 // Start with all the leaf classes
4299 for (auto const& cinfo : m_data->allClassInfos) {
4300 auto const isLeaf = [&] {
4301 for (auto const& sub : cinfo->subclassList) {
4302 if (sub != cinfo.get()) return false;
4304 return true;
4305 }();
4306 if (isLeaf) enqueue(*cinfo);
4309 WorkList oldWorkList;
4310 int iter = 1;
4311 while (!workList.empty()) {
4312 FTRACE(
4313 4, "rewrite_default_initial_values round #{}: {} items\n",
4314 iter, workList.size()
4316 ++iter;
4318 std::swap(workList, oldWorkList);
4319 workList.clear();
4320 workSet.clear();
4321 for (auto const& cinfo : oldWorkList) {
4322 // Retrieve the set of properties which are flowing into this Class and
4323 // have to be null.
4324 auto inState = [&] () -> folly::Optional<PropSet> {
4325 PropSet in;
4326 for (auto const& sub : cinfo->subclassList) {
4327 if (sub == cinfo || sub->parent != cinfo) continue;
4328 auto const it = outStates.find(sub);
4329 if (it == outStates.end()) return folly::none;
4330 in.insert(it->second.begin(), it->second.end());
4332 return in;
4333 }();
4334 if (!inState) continue;
4336 // Modify the in-state depending on the properties declared on this Class
4337 auto const cls = cinfo->cls;
4338 for (auto const& prop : cls->properties) {
4339 if (prop.attrs & (AttrStatic | AttrPrivate)) {
4340 // Private or static properties can't be redeclared
4341 inState->erase(prop.name);
4342 continue;
4344 // Ignore properties which have actual user provided initial values or
4345 // are LateInit.
4346 if (!(prop.attrs & AttrSystemInitialValue) ||
4347 (prop.attrs & AttrLateInit)) {
4348 continue;
4350 // Forced to be null, nothing to do
4351 if (inState->count(prop.name) > 0) continue;
4353 // Its not forced to be null. Find a better default value. If its null
4354 // anyways, force any properties this redeclares to be null as well.
4355 auto const defaultValue = prop.typeConstraint.defaultValue();
4356 if (defaultValue.m_type == KindOfNull) inState->insert(prop.name);
4359 // Push the in-state to the out-state.
4360 auto const result = outStates.emplace(std::make_pair(cinfo, *inState));
4361 if (result.second) {
4362 if (cinfo->parent) enqueue(*cinfo->parent);
4363 } else {
4364 // There shouldn't be cycles in the inheritance tree, so the out state
4365 // of Class', once set, should never change.
4366 assertx(result.first->second == *inState);
4371 // Now that we've processed all the classes, rewrite the property initial
4372 // values, unless they are forced to be nullable.
4373 for (auto& unit : program.units) {
4374 for (auto& c : unit->classes) {
4375 if (is_closure(*c)) continue;
4377 auto const out = [&] () -> folly::Optional<PropSet> {
4378 folly::Optional<PropSet> props;
4379 auto const range = m_data->classInfo.equal_range(c->name);
4380 for (auto it = range.first; it != range.second; ++it) {
4381 if (it->second->cls != c.get()) continue;
4382 auto const outStateIt = outStates.find(it->second);
4383 if (outStateIt == outStates.end()) return folly::none;
4384 if (!props) props.emplace();
4385 props->insert(outStateIt->second.begin(), outStateIt->second.end());
4387 return props;
4388 }();
4390 for (auto& prop : c->properties) {
4391 auto const nullable = [&] {
4392 if (!(prop.attrs & (AttrStatic | AttrPrivate))) {
4393 if (!out || out->count(prop.name)) return true;
4395 if (!(prop.attrs & AttrSystemInitialValue)) return false;
4396 return prop.typeConstraint.defaultValue().m_type == KindOfNull;
4397 }();
4399 attribute_setter(prop.attrs, !nullable, AttrNoImplicitNullable);
4400 if (!(prop.attrs & AttrSystemInitialValue)) continue;
4401 if (prop.val.m_type == KindOfUninit) {
4402 assertx(prop.attrs & AttrLateInit);
4403 continue;
4406 prop.val = nullable
4407 ? make_tv<KindOfNull>()
4408 : prop.typeConstraint.defaultValue();
4414 const CompactVector<const php::Class*>*
4415 Index::lookup_closures(const php::Class* cls) const {
4416 auto const it = m_data->classClosureMap.find(cls);
4417 if (it != end(m_data->classClosureMap)) {
4418 return &it->second;
4420 return nullptr;
4423 const hphp_fast_set<php::Func*>*
4424 Index::lookup_extra_methods(const php::Class* cls) const {
4425 if (cls->attrs & AttrNoExpandTrait) return nullptr;
4426 auto const it = m_data->classExtraMethodMap.find(cls);
4427 if (it != end(m_data->classExtraMethodMap)) {
4428 return &it->second;
4430 return nullptr;
4432 //////////////////////////////////////////////////////////////////////
4434 template<typename T>
4435 folly::Optional<T> Index::resolve_type_impl(SString name) const {
4436 auto const& infomap = m_data->infoMap<T>();
4437 auto const& omap = m_data->infoMap<typename ResTypeHelper<T>::OtherT>();
4438 auto const tinfos = find_range(infomap, name);
4439 for (auto it = begin(tinfos); it != end(tinfos); ++it) {
4440 auto const tinfo = it->second;
4442 * If there's only one preresolved [Class|Record]Info, we can give out a
4443 * specific res::Class or res::Record for it.
4444 * (Any other possible resolutions were known to fatal,
4445 * or it was actually unique.)
4447 if (tinfo->phpType()->attrs & AttrUnique) {
4448 if (debug &&
4449 (std::next(it) != end(tinfos) ||
4450 omap.count(name) ||
4451 m_data->typeAliases.count(name))) {
4452 std::fprintf(stderr, "non unique \"unique\" %s: %s\n",
4453 ResTypeHelper<T>::name().c_str(),
4454 tinfo->phpType()->name->data());
4455 while (++it != end(tinfos)) {
4456 std::fprintf(stderr, " and %s\n",
4457 it->second->phpType()->name->data());
4460 auto const typeAliases = find_range(m_data->typeAliases, name);
4461 for (auto ta = begin(typeAliases); ta != end(typeAliases); ++ta) {
4462 std::fprintf(stderr, " and type-alias %s\n",
4463 ta->second->name->data());
4466 auto const others = find_range(omap, name);
4467 for (auto to = begin(others); to != end(others); ++to) {
4468 std::fprintf(stderr, " and %s %s\n",
4469 ResTypeHelper<typename ResTypeHelper<T>::OtherT>::
4470 name().c_str(),
4471 to->second->phpType()->name->data());
4473 always_assert(0);
4475 return T { this, tinfo };
4477 break;
4479 // We refuse to have name-only resolutions of enums and typeAliases,
4480 // so that all name only resolutions can be treated as records or classes.
4481 if (!m_data->enums.count(name) &&
4482 !m_data->typeAliases.count(name) &&
4483 !omap.count(name)) {
4484 return T { this, name };
4487 return folly::none;
4490 folly::Optional<res::Record> Index::resolve_record(SString recName) const {
4491 recName = normalizeNS(recName);
4492 return resolve_type_impl<res::Record>(recName);
4495 //////////////////////////////////////////////////////////////////////
4497 res::Class Index::resolve_class(const php::Class* cls) const {
4499 ClassInfo* result = nullptr;
4501 auto const classes = find_range(m_data->classInfo, cls->name);
4502 for (auto it = begin(classes); it != end(classes); ++it) {
4503 auto const cinfo = it->second;
4504 if (cinfo->cls == cls) {
4505 if (result) {
4506 result = nullptr;
4507 break;
4509 result = cinfo;
4513 // The function is supposed to return a cinfo if we can uniquely resolve cls.
4514 // In repo mode, if there is only one cinfo, return it.
4515 // In non-repo mode, we don't know all the cinfo's. So "only one cinfo" does
4516 // not mean anything unless it is a built-in and we disable rename/intercept.
4517 if (result && (RuntimeOption::RepoAuthoritative ||
4518 (!RuntimeOption::EvalJitEnableRenameFunction &&
4519 cls->attrs & AttrBuiltin))) {
4520 return res::Class { this, result };
4523 // We know its a class, not an enum or type alias, so return
4524 // by name
4525 return res::Class { this, cls->name.get() };
4528 folly::Optional<res::Class> Index::resolve_class(Context ctx,
4529 SString clsName) const {
4530 clsName = normalizeNS(clsName);
4532 if (ctx.cls) {
4533 if (ctx.cls->name->isame(clsName)) {
4534 return resolve_class(ctx.cls);
4536 if (ctx.cls->parentName && ctx.cls->parentName->isame(clsName)) {
4537 if (auto const parent = resolve_class(ctx.cls).parent()) return parent;
4541 return resolve_type_impl<res::Class>(clsName);
4544 folly::Optional<res::Class> Index::selfCls(const Context& ctx) const {
4545 if (!ctx.cls || is_used_trait(*ctx.cls)) return folly::none;
4546 return resolve_class(ctx.cls);
4549 folly::Optional<res::Class> Index::parentCls(const Context& ctx) const {
4550 if (!ctx.cls || !ctx.cls->parentName) return folly::none;
4551 if (auto const parent = resolve_class(ctx.cls).parent()) return parent;
4552 return resolve_class(ctx, ctx.cls->parentName);
4555 Index::ResolvedInfo<boost::variant<boost::blank,res::Class,res::Record>>
4556 Index::resolve_type_name(SString inName) const {
4557 auto const res = resolve_type_name_internal(inName);
4558 using Ret = boost::variant<boost::blank,res::Class,res::Record>;
4559 auto const val = match<Ret>(
4560 res.value,
4561 [&] (boost::blank) { return Ret{}; },
4562 [&] (SString s) {
4563 return (res.type == AnnotType::Record) ?
4564 Ret{res::Record{this, s}} : Ret{res::Class{this, s}};
4566 [&] (ClassInfo* c) { return res::Class{this, c}; },
4567 [&] (RecordInfo* r) { return res::Record{this, r}; }
4569 return { res.type, res.nullable, val };
4572 Index::ResolvedInfo<boost::variant<boost::blank,SString,ClassInfo*,RecordInfo*>>
4573 Index::resolve_type_name_internal(SString inName) const {
4574 folly::Optional<hphp_fast_set<const void*>> seen;
4576 auto nullable = false;
4577 auto name = inName;
4579 for (unsigned i = 0; ; ++i) {
4580 name = normalizeNS(name);
4581 auto const records = find_range(m_data->recordInfo, name);
4582 auto const rec_it = begin(records);
4583 if (rec_it != end(records)) {
4584 auto const rinfo = rec_it->second;
4585 if (rinfo->rec->attrs & AttrUnique) {
4586 return { AnnotType::Record, nullable, rinfo };
4587 } else {
4588 return { AnnotType::Record, nullable, name };
4591 auto const classes = find_range(m_data->classInfo, name);
4592 auto const cls_it = begin(classes);
4593 if (cls_it != end(classes)) {
4594 auto const cinfo = cls_it->second;
4595 if (!(cinfo->cls->attrs & AttrUnique)) {
4596 if (!m_data->enums.count(name) && !m_data->typeAliases.count(name)) {
4597 break;
4599 return { AnnotType::Object, false, {} };
4601 if (!(cinfo->cls->attrs & AttrEnum)) {
4602 return { AnnotType::Object, nullable, cinfo };
4604 auto const& tc = cinfo->cls->enumBaseTy;
4605 assert(!tc.isNullable());
4606 if (tc.type() != AnnotType::Object) {
4607 auto const type = tc.type() == AnnotType::Mixed ?
4608 AnnotType::ArrayKey : tc.type();
4609 return { type, nullable, tc.typeName() };
4611 name = tc.typeName();
4612 } else {
4613 auto const typeAliases = find_range(m_data->typeAliases, name);
4614 auto const ta_it = begin(typeAliases);
4615 if (ta_it == end(typeAliases)) break;
4616 auto const ta = ta_it->second;
4617 if (!(ta->attrs & AttrUnique)) {
4618 return { AnnotType::Object, false, {} };
4620 nullable = nullable || ta->nullable;
4621 if (ta->type != AnnotType::Object) {
4622 return { ta->type, nullable, ta->value.get() };
4624 name = ta->value;
4627 // deal with cycles. Since we don't expect to
4628 // encounter them, just use a counter until we hit a chain length
4629 // of 10, then start tracking the names we resolve.
4630 if (i == 10) {
4631 seen.emplace();
4632 seen->insert(name);
4633 } else if (i > 10) {
4634 if (!seen->insert(name).second) {
4635 return { AnnotType::Object, false, {} };
4640 return { AnnotType::Object, nullable, name };
4643 struct Index::ConstraintResolution {
4644 /* implicit */ ConstraintResolution(Type type)
4645 : type{std::move(type)}
4646 , maybeMixed{false} {}
4647 ConstraintResolution(folly::Optional<Type> type, bool maybeMixed)
4648 : type{std::move(type)}
4649 , maybeMixed{maybeMixed} {}
4651 folly::Optional<Type> type;
4652 bool maybeMixed;
4655 Index::ConstraintResolution Index::resolve_named_type(
4656 const Context& ctx, SString name, const Type& candidate) const {
4658 auto const res = resolve_type_name_internal(name);
4660 if (res.nullable && candidate.subtypeOf(BInitNull)) return TInitNull;
4662 if (res.type == AnnotType::Object) {
4663 auto resolve = [&] (const res::Class& rcls) -> folly::Optional<Type> {
4664 if (!interface_supports_non_objects(rcls.name()) ||
4665 candidate.subtypeOrNull(BObj)) {
4666 return subObj(rcls);
4669 if (candidate.subtypeOrNull(BArr)) {
4670 if (interface_supports_array(rcls.name())) return TArr;
4671 } else if (candidate.subtypeOrNull(BVec)) {
4672 if (interface_supports_vec(rcls.name())) return TVec;
4673 } else if (candidate.subtypeOrNull(BDict)) {
4674 if (interface_supports_dict(rcls.name())) return TDict;
4675 } else if (candidate.subtypeOrNull(BKeyset)) {
4676 if (interface_supports_keyset(rcls.name())) return TKeyset;
4677 } else if (candidate.subtypeOrNull(BStr)) {
4678 if (interface_supports_string(rcls.name())) return TStr;
4679 } else if (candidate.subtypeOrNull(BInt)) {
4680 if (interface_supports_int(rcls.name())) return TInt;
4681 } else if (candidate.subtypeOrNull(BDbl)) {
4682 if (interface_supports_double(rcls.name())) return TDbl;
4684 return folly::none;
4687 auto const val = match<Either<SString, ClassInfo*>>(
4688 res.value,
4689 [&] (boost::blank) { return nullptr; },
4690 [&] (SString s) { return s; },
4691 [&] (ClassInfo* c) { return c; },
4692 [&] (RecordInfo*) { always_assert(false); return nullptr; }
4694 if (val.isNull()) return ConstraintResolution{ folly::none, true };
4695 auto ty = resolve({ this, val });
4696 if (ty && res.nullable) *ty = opt(std::move(*ty));
4697 return ConstraintResolution{ std::move(ty), false };
4698 } else if (res.type == AnnotType::Record) {
4699 auto const val = match<Either<SString, RecordInfo*>>(
4700 res.value,
4701 [&] (boost::blank) { return nullptr; },
4702 [&] (SString s) { return s; },
4703 [&] (ClassInfo* c) { always_assert(false); return nullptr; },
4704 [&] (RecordInfo* r) { return r; }
4706 if (val.isNull()) return ConstraintResolution{ folly::none, true };
4707 return subRecord({ this, val });
4710 return get_type_for_annotated_type(ctx, res.type, res.nullable,
4711 boost::get<SString>(res.value), candidate);
4714 std::pair<res::Class,php::Class*>
4715 Index::resolve_closure_class(Context ctx, int32_t idx) const {
4716 auto const cls = ctx.unit->classes[idx].get();
4717 auto const rcls = resolve_class(cls);
4719 // Closure classes must be unique and defined in the unit that uses
4720 // the CreateCl opcode, so resolution must succeed.
4721 always_assert_flog(
4722 rcls.resolved(),
4723 "A Closure class ({}) failed to resolve",
4724 cls->name
4727 return { rcls, cls };
4730 res::Class Index::builtin_class(SString name) const {
4731 auto const rcls = resolve_class(Context {}, name);
4732 always_assert_flog(
4733 rcls.has_value() &&
4734 rcls->val.right() &&
4735 (rcls->val.right()->cls->attrs & AttrBuiltin),
4736 "A builtin class ({}) failed to resolve",
4737 name->data()
4739 return *rcls;
4742 res::Func Index::resolve_method(Context ctx,
4743 Type clsType,
4744 SString name) const {
4745 auto name_only = [&] {
4746 return res::Func { this, res::Func::MethodName { name } };
4749 if (!is_specialized_cls(clsType)) {
4750 return name_only();
4752 auto const dcls = dcls_of(clsType);
4753 auto const cinfo = dcls.cls.val.right();
4754 if (!cinfo) return name_only();
4756 // Classes may have more method families than methods. Any such
4757 // method families are guaranteed to all be public so we can do this
4758 // lookup as a last gasp before resorting to name_only().
4759 auto const find_extra_method = [&] {
4760 auto methIt = cinfo->methodFamilies.find(name);
4761 if (methIt == end(cinfo->methodFamilies)) return name_only();
4762 if (methIt->second.possibleFuncs()->size() == 1) {
4763 return res::Func { this, methIt->second.possibleFuncs()->front() };
4765 // If there was a sole implementer we can resolve to a single method, even
4766 // if the method was not declared on the interface itself.
4767 return res::Func { this, &methIt->second };
4770 // Interfaces *only* have the extra methods defined for all
4771 // subclasses
4772 if (cinfo->cls->attrs & AttrInterface) return find_extra_method();
4775 * Whether or not the context class has a private method with the
4776 * same name as the method we're trying to call.
4778 auto const contextMayHavePrivateWithSameName = folly::lazy([&]() -> bool {
4779 if (!ctx.cls) return false;
4780 auto const range = find_range(m_data->classInfo, ctx.cls->name);
4781 if (begin(range) == end(range)) {
4782 // This class had no pre-resolved ClassInfos, which means it
4783 // always fatals in any way it could be defined, so it doesn't
4784 // matter what we return here (as all methods in the context
4785 // class are unreachable code).
4786 return true;
4788 // Because of traits, each instantiation of the class could have
4789 // different private methods; we need to check them all.
4790 for (auto ctxInfo : range) {
4791 auto const iter = ctxInfo.second->methods.find(name);
4792 if (iter != end(ctxInfo.second->methods) &&
4793 iter->second.attrs & AttrPrivate &&
4794 iter->second.topLevel) {
4795 return true;
4798 return false;
4802 * Look up the method in the target class.
4804 auto const methIt = cinfo->methods.find(name);
4805 if (methIt == end(cinfo->methods)) return find_extra_method();
4806 if (methIt->second.attrs & AttrInterceptable) return name_only();
4807 auto const ftarget = methIt->second.func;
4809 // We need to revisit the hasPrivateAncestor code if we start being
4810 // able to look up methods on interfaces (currently they have empty
4811 // method tables).
4812 assert(!(cinfo->cls->attrs & AttrInterface));
4815 * If our candidate method has a private ancestor, unless it is
4816 * defined on this class, we need to make sure we don't erroneously
4817 * resolve the overriding method if the call is coming from the
4818 * context the defines the private method.
4820 * For now this just gives up if the context and the callee class
4821 * could be related and the context defines a private of the same
4822 * name. (We should actually try to resolve that method, though.)
4824 if (methIt->second.hasPrivateAncestor &&
4825 ctx.cls &&
4826 ctx.cls != ftarget->cls) {
4827 if (could_be_related(ctx.cls, cinfo->cls)) {
4828 if (contextMayHavePrivateWithSameName()) {
4829 return name_only();
4835 * Note: this currently isn't exhaustively checking accessibility,
4836 * except in cases where we must do a little bit of it for
4837 * correctness.
4839 * It is generally ok to resolve a method that won't actually be
4840 * called as long, as we only do so in cases where it will fatal at
4841 * runtime.
4843 * So, in the presence of magic methods, we must handle the fact
4844 * that attempting to call an inaccessible method will instead call
4845 * the magic method, if it exists. Note that if any class derives
4846 * from a class and adds magic methods, it can change still change
4847 * dispatch to call that method instead of fatalling.
4850 // If false, this method is definitely accessible. If true, it may
4851 // or may not be accessible.
4852 auto const couldBeInaccessible = [&] {
4853 // Public is always accessible.
4854 if (methIt->second.attrs & AttrPublic) return false;
4855 // An anonymous context won't have access if it wasn't public.
4856 if (!ctx.cls) return true;
4857 // If the calling context class is the same as the target class,
4858 // and the method is defined on this class or is protected, it
4859 // must be accessible.
4860 if (ctx.cls == cinfo->cls &&
4861 (methIt->second.topLevel || methIt->second.attrs & AttrProtected)) {
4862 return false;
4864 // If the method is private, the above case is the only case where
4865 // we'd know it was accessible.
4866 if (methIt->second.attrs & AttrPrivate) return true;
4868 * For the protected method case: if the context class must be
4869 * derived from the class that first defined the protected method
4870 * we know it is accessible. First check against the class of the
4871 * method (or cinfo for trait methods).
4873 if (must_be_derived_from(
4874 ctx.cls,
4875 ftarget->cls->attrs & AttrTrait ? cinfo->cls : ftarget->cls)) {
4876 return false;
4878 if (methIt->second.hasAncestor ||
4879 (ftarget->cls->attrs & AttrTrait && !methIt->second.topLevel)) {
4880 // Now we have find the first class that defined the method, and
4881 // check if *that* is an ancestor of the context class.
4882 auto parent = cinfo->parent;
4883 while (true) {
4884 assertx(parent);
4885 auto it = parent->methods.find(name);
4886 assertx(it != parent->methods.end());
4887 if (!it->second.hasAncestor && it->second.topLevel) {
4888 if (must_be_derived_from(ctx.cls, parent->cls)) return false;
4889 break;
4891 parent = parent->parent;
4895 * On the other hand, if the class that defined the method must be
4896 * derived from the context class, it is going to be accessible as
4897 * long as the context class does not define a private method with
4898 * the same name. (If it did, we'd be calling that private
4899 * method, which currently we don't ever resolve---we've removed
4900 * it from the method table in the classInfo.)
4902 if (must_be_derived_from(cinfo->cls, ctx.cls)) {
4903 if (!contextMayHavePrivateWithSameName()) {
4904 return false;
4907 // Other cases we're not sure about (maybe some non-unique classes
4908 // got in the way). Conservatively return that it might be
4909 // inaccessible.
4910 return true;
4913 auto resolve = [&] {
4914 create_func_info(*m_data, ftarget);
4915 return res::Func { this, mteFromIt(methIt) };
4918 switch (dcls.type) {
4919 case DCls::Exact:
4920 if (cinfo->magicCall.thisHas) {
4921 if (couldBeInaccessible()) return name_only();
4923 return resolve();
4924 case DCls::Sub:
4925 if (cinfo->magicCall.derivedHas) {
4926 if (couldBeInaccessible()) return name_only();
4928 if (methIt->second.attrs & AttrNoOverride) {
4929 return resolve();
4931 if (!options.FuncFamilies) return name_only();
4934 auto const famIt = cinfo->methodFamilies.find(name);
4935 if (famIt == end(cinfo->methodFamilies)) {
4936 return name_only();
4938 if (famIt->second.containsInterceptables()) {
4939 return name_only();
4941 return res::Func { this, &famIt->second };
4944 not_reached();
4947 folly::Optional<res::Func>
4948 Index::resolve_ctor(Context /*ctx*/, res::Class rcls, bool exact) const {
4949 auto const cinfo = rcls.val.right();
4950 if (!cinfo) return folly::none;
4951 if (cinfo->cls->attrs & (AttrInterface|AttrTrait)) return folly::none;
4953 auto const cit = cinfo->methods.find(s_construct.get());
4954 if (cit == end(cinfo->methods)) return folly::none;
4956 auto const ctor = mteFromIt(cit);
4957 if (exact || ctor->second.attrs & AttrNoOverride) {
4958 if (ctor->second.attrs & AttrInterceptable) return folly::none;
4959 create_func_info(*m_data, ctor->second.func);
4960 return res::Func { this, ctor };
4963 if (!options.FuncFamilies) return folly::none;
4965 auto const famIt = cinfo->methodFamilies.find(s_construct.get());
4966 if (famIt == end(cinfo->methodFamilies)) return folly::none;
4967 if (famIt->second.containsInterceptables()) return folly::none;
4968 return res::Func { this, &famIt->second };
4971 template<class FuncRange>
4972 res::Func
4973 Index::resolve_func_helper(const FuncRange& funcs, SString name) const {
4974 auto name_only = [&] (bool renamable) {
4975 return res::Func { this, res::Func::FuncName { name, renamable } };
4978 // no resolution
4979 if (begin(funcs) == end(funcs)) return name_only(false);
4981 auto const func = begin(funcs)->second;
4982 if (func->attrs & AttrInterceptable) return name_only(true);
4984 // multiple resolutions
4985 if (std::next(begin(funcs)) != end(funcs)) {
4986 assert(!(func->attrs & AttrUnique));
4987 if (debug && any_interceptable_functions()) {
4988 for (auto const DEBUG_ONLY f : funcs) {
4989 assertx(!(f.second->attrs & AttrInterceptable));
4992 return name_only(false);
4995 // single resolution, in whole-program mode, that's it
4996 if (RuntimeOption::RepoAuthoritative) {
4997 assert(func->attrs & AttrUnique);
4998 return do_resolve(func);
5001 // single-unit mode, check builtins
5002 if (func->attrs & AttrBuiltin) {
5003 assert(func->attrs & AttrUnique);
5004 return do_resolve(func);
5007 // single-unit, non-builtin, not renamable
5008 return name_only(false);
5011 res::Func Index::resolve_func(Context /*ctx*/, SString name) const {
5012 name = normalizeNS(name);
5013 auto const funcs = find_range(m_data->funcs, name);
5014 return resolve_func_helper(funcs, name);
5018 * Gets a type for the constraint.
5020 * If getSuperType is true, the type could be a super-type of the
5021 * actual type constraint (eg TCell). Otherwise its guaranteed that
5022 * for any t, t.subtypeOf(get_type_for_constraint<false>(ctx, tc, t)
5023 * implies t would pass the constraint.
5025 * The candidate type is used to disambiguate; if we're applying a
5026 * Traversable constraint to a TObj, we should return
5027 * subObj(Traversable). If we're applying it to an Array, we should
5028 * return Array.
5030 template<bool getSuperType>
5031 Type Index::get_type_for_constraint(Context ctx,
5032 const TypeConstraint& tc,
5033 const Type& candidate) const {
5034 assertx(IMPLIES(!tc.isCheckable(),
5035 tc.isMixed() ||
5036 (tc.isUpperBound() &&
5037 RuntimeOption::EvalEnforceGenericsUB == 0)));
5039 if (getSuperType) {
5041 * Soft hints (@Foo) are not checked.
5042 * Also upper-bound type hints are not checked when they do not error.
5044 if (tc.isSoft() ||
5045 (RuntimeOption::EvalEnforceGenericsUB < 2 && tc.isUpperBound())) {
5046 return TCell;
5050 auto const res = get_type_for_annotated_type(
5051 ctx,
5052 tc.type(),
5053 tc.isNullable(),
5054 tc.typeName(),
5055 candidate
5057 if (res.type) return *res.type;
5058 // If the type constraint might be mixed, then the value could be
5059 // uninit. Any other type constraint implies TInitCell.
5060 return getSuperType ? (res.maybeMixed ? TCell : TInitCell) : TBottom;
5063 bool Index::prop_tc_maybe_unenforced(const php::Class& propCls,
5064 const TypeConstraint& tc) const {
5065 assertx(tc.validForProp());
5066 if (RuntimeOption::EvalCheckPropTypeHints <= 2) return true;
5067 if (!tc.isCheckable()) return true;
5068 if (tc.isSoft()) return true;
5069 auto const res = get_type_for_annotated_type(
5070 Context { nullptr, nullptr, &propCls },
5071 tc.type(),
5072 tc.isNullable(),
5073 tc.typeName(),
5074 TCell
5076 return res.maybeMixed;
5079 Index::ConstraintResolution Index::get_type_for_annotated_type(
5080 Context ctx, AnnotType annot, bool nullable,
5081 SString name, const Type& candidate) const {
5083 if (candidate.subtypeOf(BInitNull) && nullable) {
5084 return TInitNull;
5087 auto mainType = [&]() -> ConstraintResolution {
5088 switch (getAnnotMetaType(annot)) {
5089 case AnnotMetaType::Precise: {
5090 auto const dt = getAnnotDataType(annot);
5092 switch (dt) {
5093 case KindOfNull: return TNull;
5094 case KindOfBoolean: return TBool;
5095 case KindOfInt64: return TInt;
5096 case KindOfDouble: return TDbl;
5097 case KindOfPersistentString:
5098 case KindOfString: return TStr;
5099 case KindOfPersistentVec:
5100 case KindOfVec: return TVec;
5101 case KindOfPersistentDict:
5102 case KindOfDict: return TDict;
5103 case KindOfPersistentKeyset:
5104 case KindOfKeyset: return TKeyset;
5105 case KindOfPersistentDArray:
5106 case KindOfDArray: return TDArr;
5107 case KindOfPersistentVArray:
5108 case KindOfVArray: return TVArr;
5109 case KindOfPersistentArray:
5110 case KindOfArray: return TPArr;
5111 case KindOfResource: return TRes;
5112 case KindOfClsMeth: return TClsMeth;
5113 case KindOfRecord: // fallthrough
5114 case KindOfObject:
5115 return resolve_named_type(ctx, name, candidate);
5116 case KindOfUninit:
5117 case KindOfFunc:
5118 case KindOfClass:
5119 always_assert_flog(false, "Unexpected DataType");
5120 break;
5122 break;
5124 case AnnotMetaType::Mixed:
5126 * Here we handle "mixed", typevars, and some other ignored
5127 * typehints (ex. "(function(..): ..)" typehints).
5129 return { TCell, true };
5130 case AnnotMetaType::Nothing:
5131 case AnnotMetaType::NoReturn:
5132 return TBottom;
5133 case AnnotMetaType::Nonnull:
5134 if (candidate.subtypeOf(BInitNull)) return TBottom;
5135 if (!candidate.couldBe(BInitNull)) return candidate;
5136 if (is_opt(candidate)) return unopt(candidate);
5137 break;
5138 case AnnotMetaType::This:
5139 if (auto s = selfCls(ctx)) return setctx(subObj(*s));
5140 break;
5141 case AnnotMetaType::Self:
5142 if (auto s = selfCls(ctx)) return subObj(*s);
5143 break;
5144 case AnnotMetaType::Parent:
5145 if (auto p = parentCls(ctx)) return subObj(*p);
5146 break;
5147 case AnnotMetaType::Callable:
5148 break;
5149 case AnnotMetaType::Number:
5150 return TNum;
5151 case AnnotMetaType::ArrayKey:
5152 if (candidate.subtypeOf(BInt)) return TInt;
5153 if (candidate.subtypeOf(BStr)) return TStr;
5154 return TArrKey;
5155 case AnnotMetaType::VArray:
5156 assertx(!RuntimeOption::EvalHackArrDVArrs);
5157 return TVArr;
5158 case AnnotMetaType::DArray:
5159 assertx(!RuntimeOption::EvalHackArrDVArrs);
5160 return TDArr;
5161 case AnnotMetaType::VArrOrDArr:
5162 assertx(!RuntimeOption::EvalHackArrDVArrs);
5163 return TArr;
5164 case AnnotMetaType::VecOrDict:
5165 if (candidate.subtypeOf(BVec)) return TVec;
5166 if (candidate.subtypeOf(BDict)) return TDict;
5167 break;
5168 case AnnotMetaType::ArrayLike:
5169 if (candidate.subtypeOf(BVArr)) return TVArr;
5170 if (candidate.subtypeOf(BDArr)) return TDArr;
5171 if (candidate.subtypeOf(BArr)) return TArr;
5172 if (candidate.subtypeOf(BVec)) return TVec;
5173 if (candidate.subtypeOf(BDict)) return TDict;
5174 if (candidate.subtypeOf(BKeyset)) return TKeyset;
5175 break;
5177 return ConstraintResolution{ folly::none, false };
5178 }();
5180 if (mainType.type && nullable) {
5181 if (mainType.type->subtypeOf(BBottom)) {
5182 if (candidate.couldBe(BInitNull)) {
5183 mainType.type = TInitNull;
5185 } else if (!mainType.type->couldBe(BInitNull)) {
5186 mainType.type = opt(*mainType.type);
5189 return mainType;
5192 Type Index::lookup_constraint(Context ctx,
5193 const TypeConstraint& tc,
5194 const Type& t) const {
5195 return get_type_for_constraint<true>(ctx, tc, t);
5198 bool Index::satisfies_constraint(Context ctx, const Type& t,
5199 const TypeConstraint& tc) const {
5200 auto const tcType = get_type_for_constraint<false>(ctx, tc, t);
5201 if (t.moreRefined(loosen_dvarrayness(tcType))) {
5202 // For d/varrays, we might satisfy the constraint, but still not want to
5203 // optimize away the type-check (because we'll raise a notice on a d/varray
5204 // mismatch), so do some additional checking here to rule that out.
5205 if (!RuntimeOption::EvalHackArrCompatTypeHintNotices) return true;
5206 if (!tcType.subtypeOrNull(BArr) || tcType.subtypeOf(BNull)) return true;
5207 assertx(t.subtypeOrNull(BArr));
5208 if (tcType.subtypeOrNull(BVArr)) return t.subtypeOrNull(BVArr);
5209 if (tcType.subtypeOrNull(BDArr)) return t.subtypeOrNull(BDArr);
5210 if (tcType.subtypeOrNull(BPArr)) return t.subtypeOrNull(BPArr);
5212 return false;
5215 bool Index::could_have_reified_type(Context ctx,
5216 const TypeConstraint& tc) const {
5217 if (ctx.func->isClosureBody) {
5218 for (auto i = ctx.func->params.size();
5219 i < ctx.func->locals.size();
5220 ++i) {
5221 auto const name = ctx.func->locals[i].name;
5222 if (!name) return false; // named locals do not appear after unnamed local
5223 if (isMangledReifiedGenericInClosure(name)) return true;
5225 return false;
5227 if (!tc.isObject()) return false;
5228 auto const name = tc.typeName();
5229 auto const resolved = resolve_type_name_internal(name);
5230 if (resolved.type != AnnotType::Object) return false;
5231 auto const val = match<Either<SString, ClassInfo*>>(
5232 resolved.value,
5233 [&] (boost::blank) { return nullptr; },
5234 [&] (SString s) { return s; },
5235 [&] (ClassInfo* c) { return c; },
5236 [&] (RecordInfo*) { always_assert(false); return nullptr; }
5238 res::Class rcls{this, val};
5239 return rcls.couldHaveReifiedGenerics();
5242 folly::Optional<bool>
5243 Index::supports_async_eager_return(res::Func rfunc) const {
5244 auto const supportsAER = [] (const php::Func* func) {
5245 // Async functions always support async eager return.
5246 if (func->isAsync && !func->isGenerator) return true;
5248 // No other functions support async eager return yet.
5249 return false;
5252 return match<folly::Optional<bool>>(
5253 rfunc.val,
5254 [&](res::Func::FuncName) { return folly::none; },
5255 [&](res::Func::MethodName) { return folly::none; },
5256 [&](FuncInfo* finfo) { return supportsAER(finfo->func); },
5257 [&](const MethTabEntryPair* mte) { return supportsAER(mte->second.func); },
5258 [&](FuncFamily* fam) -> folly::Optional<bool> {
5259 auto ret = folly::Optional<bool>{};
5260 for (auto const pf : fam->possibleFuncs()) {
5261 // Abstract functions are never called.
5262 if (pf->second.attrs & AttrAbstract) continue;
5263 auto const val = supportsAER(pf->second.func);
5264 if (ret && *ret != val) return folly::none;
5265 ret = val;
5267 return ret;
5271 bool Index::is_effect_free(const php::Func* func) const {
5272 return func_info(*m_data, func)->effectFree;
5275 bool Index::is_effect_free(res::Func rfunc) const {
5276 return match<bool>(
5277 rfunc.val,
5278 [&](res::Func::FuncName) { return false; },
5279 [&](res::Func::MethodName) { return false; },
5280 [&](FuncInfo* finfo) {
5281 return finfo->effectFree;
5283 [&](const MethTabEntryPair* mte) {
5284 return func_info(*m_data, mte->second.func)->effectFree;
5286 [&](FuncFamily* fam) {
5287 return false;
5291 bool Index::any_interceptable_functions() const {
5292 return m_data->any_interceptable_functions;
5295 const php::Const* Index::lookup_class_const_ptr(Context ctx,
5296 res::Class rcls,
5297 SString cnsName,
5298 bool allow_tconst) const {
5299 if (rcls.val.left()) return nullptr;
5300 auto const cinfo = rcls.val.right();
5302 auto const it = cinfo->clsConstants.find(cnsName);
5303 if (it != end(cinfo->clsConstants)) {
5304 if (!it->second->val.has_value() ||
5305 (!allow_tconst && it->second->isTypeconst)) {
5306 // This is an abstract class constant or typeconstant
5307 return nullptr;
5309 if (it->second->val.value().m_type == KindOfUninit) {
5310 // This is a class constant that needs an 86cinit to run.
5311 // We'll add a dependency to make sure we're re-run if it
5312 // resolves anything.
5313 auto const cinit = it->second->cls->methods.back().get();
5314 assert(cinit->name == s_86cinit.get());
5315 add_dependency(*m_data, cinit, ctx, Dep::ClsConst);
5316 return nullptr;
5318 return it->second;
5320 return nullptr;
5323 Type Index::lookup_class_constant(Context ctx,
5324 res::Class rcls,
5325 SString cnsName,
5326 bool allow_tconst) const {
5327 auto const cnst = lookup_class_const_ptr(ctx, rcls, cnsName, allow_tconst);
5328 if (!cnst) return TInitCell;
5329 return from_cell(cnst->val.value());
5332 Type Index::lookup_constant(Context ctx, SString cnsName) const {
5333 auto constants = find_range(m_data->constants, cnsName);
5334 if (begin(constants) == end(constants) ||
5335 std::next(begin(constants)) != end(constants)) {
5336 return TInitCell;
5339 auto constant = begin(constants)->second;
5340 if (type(constant->val) != KindOfUninit) {
5341 return from_cell(constant->val);
5344 auto const func_name = Constant::funcNameFromName(cnsName);
5345 assertx(func_name && "func_name will never be nullptr");
5347 auto rfunc = resolve_func(ctx, func_name);
5348 return lookup_return_type(ctx, rfunc, Dep::ConstVal);
5351 bool Index::func_depends_on_arg(const php::Func* func, int arg) const {
5352 auto const& finfo = *func_info(*m_data, func);
5353 return arg >= finfo.unusedParams.size() || !finfo.unusedParams.test(arg);
5356 Type Index::lookup_foldable_return_type(Context ctx,
5357 const php::Func* func,
5358 Type ctxType,
5359 CompactVector<Type> args) const {
5360 constexpr auto max_interp_nexting_level = 2;
5361 static __thread uint32_t interp_nesting_level;
5362 static __thread Context base_ctx;
5364 // Don't fold functions when staticness mismatches
5365 if ((func->attrs & AttrStatic) && ctxType.couldBe(TObj)) return TTop;
5366 if (!(func->attrs & AttrStatic) && ctxType.couldBe(TCls)) return TTop;
5368 auto const& finfo = *func_info(*m_data, func);
5369 if (finfo.effectFree && is_scalar(finfo.returnTy)) {
5370 return finfo.returnTy;
5373 auto const calleeCtx = CallContext {
5374 func,
5375 std::move(args),
5376 std::move(ctxType)
5379 auto showArgs DEBUG_ONLY = [] (const CompactVector<Type>& a) {
5380 std::string ret, sep;
5381 for (auto& arg : a) {
5382 folly::format(&ret, "{}{}", sep, show(arg));
5383 sep = ",";
5385 return ret;
5389 ContextRetTyMap::const_accessor acc;
5390 if (m_data->foldableReturnTypeMap.find(acc, calleeCtx)) {
5391 FTRACE_MOD(
5392 Trace::hhbbc, 4,
5393 "Found foldableReturnType for {}{}{} with args {} (hash: {})\n",
5394 func->cls ? func->cls->name : staticEmptyString(),
5395 func->cls ? "::" : "",
5396 func->name,
5397 showArgs(calleeCtx.args),
5398 CallContextHashCompare{}.hash(calleeCtx));
5400 assertx(is_scalar(acc->second));
5401 return acc->second;
5405 if (frozen()) {
5406 FTRACE_MOD(
5407 Trace::hhbbc, 4,
5408 "MISSING: foldableReturnType for {}{}{} with args {} (hash: {})\n",
5409 func->cls ? func->cls->name : staticEmptyString(),
5410 func->cls ? "::" : "",
5411 func->name,
5412 showArgs(calleeCtx.args),
5413 CallContextHashCompare{}.hash(calleeCtx));
5414 return TTop;
5417 if (!interp_nesting_level) {
5418 base_ctx = ctx;
5419 } else if (interp_nesting_level > max_interp_nexting_level) {
5420 add_dependency(*m_data, func, base_ctx, Dep::InlineDepthLimit);
5421 return TTop;
5424 auto const contextType = [&] {
5425 ++interp_nesting_level;
5426 SCOPE_EXIT { --interp_nesting_level; };
5428 auto const fa = analyze_func_inline(
5429 *this,
5430 Context { func->unit, const_cast<php::Func*>(func), func->cls },
5431 calleeCtx.context,
5432 calleeCtx.args,
5433 CollectionOpts::EffectFreeOnly
5435 return fa.effectFree ? fa.inferredReturn : TTop;
5436 }();
5438 if (!is_scalar(contextType)) {
5439 return TTop;
5442 ContextRetTyMap::accessor acc;
5443 if (m_data->foldableReturnTypeMap.insert(acc, calleeCtx)) {
5444 acc->second = contextType;
5445 } else {
5446 // someone beat us to it
5447 assertx(acc->second == contextType);
5449 return contextType;
5452 Type Index::lookup_return_type(Context ctx, res::Func rfunc, Dep dep) const {
5453 return match<Type>(
5454 rfunc.val,
5455 [&](res::Func::FuncName) { return TInitCell; },
5456 [&](res::Func::MethodName) { return TInitCell; },
5457 [&](FuncInfo* finfo) {
5458 add_dependency(*m_data, finfo->func, ctx, dep);
5459 return unctx(finfo->returnTy);
5461 [&](const MethTabEntryPair* mte) {
5462 add_dependency(*m_data, mte->second.func, ctx, dep);
5463 auto const finfo = func_info(*m_data, mte->second.func);
5464 if (!finfo->func) return TInitCell;
5465 return unctx(finfo->returnTy);
5467 [&](FuncFamily* fam) {
5468 auto ret = TBottom;
5469 for (auto const pf : fam->possibleFuncs()) {
5470 add_dependency(*m_data, pf->second.func, ctx, dep);
5471 auto const finfo = func_info(*m_data, pf->second.func);
5472 if (!finfo->func) return TInitCell;
5473 ret |= unctx(finfo->returnTy);
5475 return ret;
5479 Type Index::lookup_return_type(Context caller,
5480 const CompactVector<Type>& args,
5481 const Type& context,
5482 res::Func rfunc,
5483 Dep dep) const {
5484 return match<Type>(
5485 rfunc.val,
5486 [&](res::Func::FuncName) {
5487 return lookup_return_type(caller, rfunc);
5489 [&](res::Func::MethodName) {
5490 return lookup_return_type(caller, rfunc);
5492 [&](FuncInfo* finfo) {
5493 add_dependency(*m_data, finfo->func, caller, dep);
5494 return context_sensitive_return_type(*m_data,
5495 { finfo->func, args, context });
5497 [&](const MethTabEntryPair* mte) {
5498 add_dependency(*m_data, mte->second.func, caller, dep);
5499 auto const finfo = func_info(*m_data, mte->second.func);
5500 if (!finfo->func) return TInitCell;
5501 return context_sensitive_return_type(*m_data,
5502 { finfo->func, args, context });
5504 [&] (FuncFamily* fam) {
5505 auto ret = TBottom;
5506 for (auto& pf : fam->possibleFuncs()) {
5507 add_dependency(*m_data, pf->second.func, caller, dep);
5508 auto const finfo = func_info(*m_data, pf->second.func);
5509 if (!finfo->func) ret |= TInitCell;
5510 else ret |= return_with_context(finfo->returnTy, context);
5512 return ret;
5517 CompactVector<Type>
5518 Index::lookup_closure_use_vars(const php::Func* func,
5519 bool move) const {
5520 assert(func->isClosureBody);
5522 auto const numUseVars = closure_num_use_vars(func);
5523 if (!numUseVars) return {};
5524 auto const it = m_data->closureUseVars.find(func->cls);
5525 if (it == end(m_data->closureUseVars)) {
5526 return CompactVector<Type>(numUseVars, TCell);
5528 if (move) return std::move(it->second);
5529 return it->second;
5532 Type Index::lookup_return_type_raw(const php::Func* f) const {
5533 auto it = func_info(*m_data, f);
5534 if (it->func) {
5535 assertx(it->func == f);
5536 return it->returnTy;
5538 return TInitCell;
5541 bool Index::lookup_this_available(const php::Func* f) const {
5542 return !(f->cls->attrs & AttrTrait) && !(f->attrs & AttrStatic);
5545 folly::Optional<uint32_t> Index::lookup_num_inout_params(
5546 Context,
5547 res::Func rfunc
5548 ) const {
5549 return match<folly::Optional<uint32_t>>(
5550 rfunc.val,
5551 [&] (res::Func::FuncName s) -> folly::Optional<uint32_t> {
5552 if (!RuntimeOption::RepoAuthoritative || s.renamable) return folly::none;
5553 return num_inout_from_set(find_range(m_data->funcs, s.name));
5555 [&] (res::Func::MethodName s) -> folly::Optional<uint32_t> {
5556 if (!RuntimeOption::RepoAuthoritative) return folly::none;
5557 auto const it = m_data->method_inout_params_by_name.find(s.name);
5558 if (it == end(m_data->method_inout_params_by_name)) {
5559 // There was no entry, so no method by this name takes a parameter
5560 // by inout.
5561 return 0;
5563 return num_inout_from_set(find_range(m_data->methods, s.name));
5565 [&] (FuncInfo* finfo) {
5566 return func_num_inout(finfo->func);
5568 [&] (const MethTabEntryPair* mte) {
5569 return func_num_inout(mte->second.func);
5571 [&] (FuncFamily* fam) {
5572 assert(RuntimeOption::RepoAuthoritative);
5573 return num_inout_from_set(fam->possibleFuncs());
5578 PrepKind Index::lookup_param_prep(Context /*ctx*/, res::Func rfunc,
5579 uint32_t paramId) const {
5580 return match<PrepKind>(
5581 rfunc.val,
5582 [&] (res::Func::FuncName s) {
5583 if (!RuntimeOption::RepoAuthoritative || s.renamable) return PrepKind::Unknown;
5584 return prep_kind_from_set(find_range(m_data->funcs, s.name), paramId);
5586 [&] (res::Func::MethodName s) {
5587 if (!RuntimeOption::RepoAuthoritative) return PrepKind::Unknown;
5588 auto const it = m_data->method_inout_params_by_name.find(s.name);
5589 if (it == end(m_data->method_inout_params_by_name)) {
5590 // There was no entry, so no method by this name takes a parameter
5591 // by reference.
5592 return PrepKind::Val;
5595 * If we think it's supposed to be PrepKind::InOut, we still can't be sure
5596 * unless we go through some effort to guarantee that it can't be going
5597 * to an __call function magically (which will never take anything by
5598 * ref).
5600 if (paramId < sizeof(it->second) * CHAR_BIT) {
5601 return ((it->second >> paramId) & 1) ?
5602 PrepKind::Unknown : PrepKind::Val;
5604 auto const kind = prep_kind_from_set(
5605 find_range(m_data->methods, s.name),
5606 paramId
5608 return kind == PrepKind::InOut ? PrepKind::Unknown : kind;
5610 [&] (FuncInfo* finfo) {
5611 return func_param_prep(finfo->func, paramId);
5613 [&] (const MethTabEntryPair* mte) {
5614 return func_param_prep(mte->second.func, paramId);
5616 [&] (FuncFamily* fam) {
5617 assert(RuntimeOption::RepoAuthoritative);
5618 return prep_kind_from_set(fam->possibleFuncs(), paramId);
5623 PropState
5624 Index::lookup_private_props(const php::Class* cls,
5625 bool move) const {
5626 auto it = m_data->privatePropInfo.find(cls);
5627 if (it != end(m_data->privatePropInfo)) {
5628 if (move) return std::move(it->second);
5629 return it->second;
5631 return make_unknown_propstate(
5632 cls,
5633 [&] (const php::Prop& prop) {
5634 return (prop.attrs & AttrPrivate) && !(prop.attrs & AttrStatic);
5639 PropState
5640 Index::lookup_private_statics(const php::Class* cls,
5641 bool move) const {
5642 auto it = m_data->privateStaticPropInfo.find(cls);
5643 if (it != end(m_data->privateStaticPropInfo)) {
5644 if (move) return std::move(it->second);
5645 return it->second;
5647 return make_unknown_propstate(
5648 cls,
5649 [&] (const php::Prop& prop) {
5650 return (prop.attrs & AttrPrivate) && (prop.attrs & AttrStatic);
5655 Type Index::lookup_public_static(Context ctx,
5656 const Type& cls,
5657 const Type& name) const {
5658 if (!is_specialized_cls(cls)) return TInitCell;
5660 auto const vname = tv(name);
5661 if (!vname || vname->m_type != KindOfPersistentString) return TInitCell;
5662 auto const sname = vname->m_data.pstr;
5664 if (ctx.unit) add_dependency(*m_data, sname, ctx, Dep::PublicSPropName);
5666 auto const dcls = dcls_of(cls);
5667 if (dcls.cls.val.left()) return TInitCell;
5668 auto const cinfo = dcls.cls.val.right();
5670 switch (dcls.type) {
5671 case DCls::Sub: {
5672 auto ty = TBottom;
5673 for (auto const sub : cinfo->subclassList) {
5674 ty |= lookup_public_static_impl(
5675 *m_data,
5676 sub,
5677 sname
5678 ).inferredType;
5680 return ty;
5682 case DCls::Exact:
5683 return lookup_public_static_impl(
5684 *m_data,
5685 cinfo,
5686 sname
5687 ).inferredType;
5689 always_assert(false);
5692 Type Index::lookup_public_static(Context ctx,
5693 const php::Class* cls,
5694 SString name) const {
5695 if (ctx.unit) add_dependency(*m_data, name, ctx, Dep::PublicSPropName);
5696 return lookup_public_static_impl(*m_data, cls, name).inferredType;
5699 bool Index::lookup_public_static_immutable(const php::Class* cls,
5700 SString name) const {
5701 return !lookup_public_static_impl(*m_data, cls, name).everModified;
5704 bool Index::lookup_public_static_maybe_late_init(const Type& cls,
5705 const Type& name) const {
5706 auto const cinfo = [&] () -> const ClassInfo* {
5707 if (!is_specialized_cls(cls)) {
5708 return nullptr;
5710 auto const dcls = dcls_of(cls);
5711 switch (dcls.type) {
5712 case DCls::Sub: return nullptr;
5713 case DCls::Exact: return dcls.cls.val.right();
5715 not_reached();
5716 }();
5717 if (!cinfo) return true;
5719 auto const vname = tv(name);
5720 if (!vname || (vname && vname->m_type != KindOfPersistentString)) {
5721 return true;
5723 auto const sname = vname->m_data.pstr;
5725 auto isLateInit = false;
5726 visit_parent_cinfo(
5727 cinfo,
5728 [&] (const ClassInfo* ci) -> bool {
5729 for (auto const& prop : ci->cls->properties) {
5730 if (prop.name == sname) {
5731 isLateInit = prop.attrs & AttrLateInit;
5732 return true;
5735 return false;
5738 return isLateInit;
5741 Type Index::lookup_public_prop(const Type& cls, const Type& name) const {
5742 if (!is_specialized_cls(cls)) return TCell;
5744 auto const vname = tv(name);
5745 if (!vname || vname->m_type != KindOfPersistentString) return TCell;
5746 auto const sname = vname->m_data.pstr;
5748 auto const dcls = dcls_of(cls);
5749 if (dcls.cls.val.left()) return TCell;
5750 auto const cinfo = dcls.cls.val.right();
5752 switch (dcls.type) {
5753 case DCls::Sub: {
5754 auto ty = TBottom;
5755 for (auto const sub : cinfo->subclassList) {
5756 ty |= lookup_public_prop_impl(
5757 *m_data,
5758 sub,
5759 sname
5762 return ty;
5764 case DCls::Exact:
5765 return lookup_public_prop_impl(
5766 *m_data,
5767 cinfo,
5768 sname
5771 always_assert(false);
5774 Type Index::lookup_public_prop(const php::Class* cls, SString name) const {
5775 auto const classes = find_range(m_data->classInfo, cls->name);
5776 if (begin(classes) == end(classes) ||
5777 std::next(begin(classes)) != end(classes)) {
5778 return TCell;
5780 return lookup_public_prop_impl(*m_data, begin(classes)->second, name);
5783 bool Index::lookup_class_init_might_raise(Context ctx, res::Class cls) const {
5784 return cls.val.match(
5785 [] (SString) { return true; },
5786 [&] (ClassInfo* cinfo) {
5787 // Check this class and all of its parents for possible inequivalent
5788 // redeclarations or bad initial values.
5789 do {
5790 // Be conservative for now if we have unflattened traits.
5791 if (!cinfo->traitProps.empty()) return true;
5792 if (cinfo->hasBadRedeclareProp) return true;
5793 if (cinfo->hasBadInitialPropValues) {
5794 add_dependency(*m_data, cinfo->cls, ctx, Dep::PropBadInitialValues);
5795 return true;
5797 cinfo = cinfo->parent;
5798 } while (cinfo);
5799 return false;
5804 void Index::join_iface_vtable_thread() const {
5805 if (m_data->compute_iface_vtables.joinable()) {
5806 m_data->compute_iface_vtables.join();
5810 Slot
5811 Index::lookup_iface_vtable_slot(const php::Class* cls) const {
5812 return folly::get_default(m_data->ifaceSlotMap, cls, kInvalidSlot);
5815 //////////////////////////////////////////////////////////////////////
5817 DependencyContext Index::dependency_context(const Context& ctx) const {
5818 return dep_context(*m_data, ctx);
5821 void Index::use_class_dependencies(bool f) {
5822 if (f != m_data->useClassDependencies) {
5823 m_data->dependencyMap.clear();
5824 m_data->useClassDependencies = f;
5828 void Index::init_public_static_prop_types() {
5829 for (auto const& cinfo : m_data->allClassInfos) {
5830 for (auto const& prop : cinfo->cls->properties) {
5831 if (!(prop.attrs & AttrPublic) || !(prop.attrs & AttrStatic)) {
5832 continue;
5836 * If the initializer type is TUninit, it means an 86sinit provides the
5837 * actual initialization type or it is AttrLateInit. So we don't want to
5838 * include the Uninit (which isn't really a user-visible type for the
5839 * property) or by the time we union things in we'll have inferred nothing
5840 * much.
5842 auto const initial = [&] {
5843 auto const tyRaw = from_cell(prop.val);
5844 if (tyRaw.subtypeOf(BUninit)) return TBottom;
5845 if (prop.attrs & AttrSystemInitialValue) return tyRaw;
5846 return adjust_type_for_prop(
5847 *this, *cinfo->cls, &prop.typeConstraint, tyRaw
5849 }();
5851 cinfo->publicStaticProps[prop.name] =
5852 PublicSPropEntry {
5853 union_of(
5854 adjust_type_for_prop(
5855 *this,
5856 *cinfo->cls,
5857 &prop.typeConstraint,
5858 TInitCell
5860 initial
5862 initial,
5863 &prop.typeConstraint,
5865 true
5871 void Index::refine_class_constants(
5872 const Context& ctx,
5873 const CompactVector<std::pair<size_t, TypedValue>>& resolved,
5874 DependencyContextSet& deps) {
5875 if (!resolved.size()) return;
5876 auto& constants = ctx.func->cls->constants;
5877 for (auto const& c : resolved) {
5878 assertx(c.first < constants.size());
5879 auto& cnst = constants[c.first];
5880 assertx(cnst.val && cnst.val->m_type == KindOfUninit);
5881 cnst.val = c.second;
5883 find_deps(*m_data, ctx.func, Dep::ClsConst, deps);
5886 void Index::refine_constants(const FuncAnalysisResult& fa,
5887 DependencyContextSet& deps) {
5888 auto const func = fa.ctx.func;
5889 if (func->cls != nullptr) return;
5891 auto const val = tv(fa.inferredReturn);
5892 if (!val) return;
5894 auto const cns_name = Constant::nameFromFuncName(func->name);
5895 if (!cns_name) return;
5897 auto& cs = fa.ctx.unit->constants;
5898 auto it = std::find_if(
5899 cs.begin(),
5900 cs.end(),
5901 [&] (auto const& c) {
5902 return cns_name->same(c->name);
5904 assertx(it != cs.end() && "Did not find constant");
5905 (*it)->val = val.value();
5906 find_deps(*m_data, func, Dep::ConstVal, deps);
5909 void Index::fixup_return_type(const php::Func* func,
5910 Type& retTy) const {
5911 if (func->isGenerator) {
5912 if (func->isAsync) {
5913 // Async generators always return AsyncGenerator object.
5914 retTy = objExact(builtin_class(s_AsyncGenerator.get()));
5915 } else {
5916 // Non-async generators always return Generator object.
5917 retTy = objExact(builtin_class(s_Generator.get()));
5919 } else if (func->isAsync) {
5920 // Async functions always return WaitH<T>, where T is the type returned
5921 // internally.
5922 retTy = wait_handle(*this, std::move(retTy));
5926 void Index::init_return_type(const php::Func* func) {
5927 if ((func->attrs & AttrBuiltin) || func->isMemoizeWrapper) {
5928 return;
5931 auto make_type = [&] (const TypeConstraint& tc) {
5932 if (tc.isSoft() ||
5933 (RuntimeOption::EvalEnforceGenericsUB < 2 && tc.isUpperBound())) {
5934 return TBottom;
5936 return loosen_dvarrayness(
5937 lookup_constraint(
5938 Context {
5939 func->unit,
5940 const_cast<php::Func*>(func),
5941 func->cls && func->cls->closureContextCls ?
5942 func->cls->closureContextCls : func->cls
5948 auto const finfo = create_func_info(*m_data, func);
5950 auto tcT = make_type(func->retTypeConstraint);
5951 if (tcT == TBottom) return;
5953 if (func->hasInOutArgs) {
5954 std::vector<Type> types;
5955 types.emplace_back(intersection_of(TInitCell, std::move(tcT)));
5956 for (auto& p : func->params) {
5957 if (!p.inout) continue;
5958 auto t = make_type(p.typeConstraint);
5959 if (t == TBottom) return;
5960 types.emplace_back(intersection_of(TInitCell, std::move(t)));
5962 tcT = vec(std::move(types), ProvTag::Top);
5965 tcT = to_cell(std::move(tcT));
5966 if (is_specialized_obj(tcT)) {
5967 if (dobj_of(tcT).cls.couldBeInterfaceOrTrait()) {
5968 tcT = is_opt(tcT) ? TOptObj : TObj;
5970 } else {
5971 tcT = loosen_all(std::move(tcT));
5973 FTRACE(4, "Pre-fixup return type for {}{}{}: {}\n",
5974 func->cls ? func->cls->name->data() : "",
5975 func->cls ? "::" : "",
5976 func->name, show(tcT));
5977 fixup_return_type(func, tcT);
5978 FTRACE(3, "Initial return type for {}{}{}: {}\n",
5979 func->cls ? func->cls->name->data() : "",
5980 func->cls ? "::" : "",
5981 func->name, show(tcT));
5982 finfo->returnTy = std::move(tcT);
5985 static bool moreRefinedForIndex(const Type& newType,
5986 const Type& oldType)
5988 if (newType.moreRefined(oldType)) return true;
5989 if (!newType.subtypeOf(BOptObj) ||
5990 !oldType.subtypeOf(BOptObj) ||
5991 !oldType.couldBe(BObj) ||
5992 !is_specialized_obj(oldType)) {
5993 return false;
5995 return dobj_of(oldType).cls.mustBeInterface();
5998 void Index::refine_return_info(const FuncAnalysisResult& fa,
5999 DependencyContextSet& deps) {
6000 auto const& t = fa.inferredReturn;
6001 auto const func = fa.ctx.func;
6002 auto const finfo = create_func_info(*m_data, func);
6004 auto error_loc = [&] {
6005 return folly::sformat(
6006 "{} {}{}",
6007 func->unit->filename,
6008 func->cls ?
6009 folly::to<std::string>(func->cls->name->data(), "::") : std::string{},
6010 func->name
6014 auto dep = Dep{};
6015 if (finfo->retParam == NoLocalId && fa.retParam != NoLocalId) {
6016 // This is just a heuristic; it doesn't mean that the value passed
6017 // in was returned, but that the value of the parameter at the
6018 // point of the RetC was returned. We use it to make (heuristic)
6019 // decisions about whether to do inline interps, so we only allow
6020 // it to change once (otherwise later passes might not do the
6021 // inline interp, and get worse results, which could trigger other
6022 // assertions in Index::refine_*).
6023 dep = Dep::ReturnTy;
6024 finfo->retParam = fa.retParam;
6027 auto unusedParams = ~fa.usedParams;
6028 if (finfo->unusedParams != unusedParams) {
6029 dep = Dep::ReturnTy;
6030 always_assert_flog(
6031 (finfo->unusedParams | unusedParams) == unusedParams,
6032 "Index unusedParams decreased in {}.\n",
6033 error_loc()
6035 finfo->unusedParams = unusedParams;
6038 if (t.strictlyMoreRefined(finfo->returnTy)) {
6039 if (finfo->returnRefinments + 1 < options.returnTypeRefineLimit) {
6040 finfo->returnTy = t;
6041 ++finfo->returnRefinments;
6042 dep = is_scalar(t) ?
6043 Dep::ReturnTy | Dep::InlineDepthLimit : Dep::ReturnTy;
6044 } else {
6045 FTRACE(1, "maxed out return type refinements at {}\n", error_loc());
6047 } else {
6048 always_assert_flog(
6049 moreRefinedForIndex(t, finfo->returnTy),
6050 "Index return type invariant violated in {}.\n"
6051 " {} is not at least as refined as {}\n",
6052 error_loc(),
6053 show(t),
6054 show(finfo->returnTy)
6058 always_assert_flog(
6059 !finfo->effectFree || fa.effectFree,
6060 "Index effectFree changed from true to false in {} {}{}.\n",
6061 func->unit->filename,
6062 func->cls ? folly::to<std::string>(func->cls->name->data(), "::") :
6063 std::string{},
6064 func->name);
6066 if (finfo->effectFree != fa.effectFree) {
6067 finfo->effectFree = fa.effectFree;
6068 dep = Dep::InlineDepthLimit | Dep::ReturnTy;
6071 if (dep != Dep{}) find_deps(*m_data, func, dep, deps);
6074 bool Index::refine_closure_use_vars(const php::Class* cls,
6075 const CompactVector<Type>& vars) {
6076 assert(is_closure(*cls));
6078 for (auto i = uint32_t{0}; i < vars.size(); ++i) {
6079 always_assert_flog(
6080 vars[i].equivalentlyRefined(unctx(vars[i])),
6081 "Closure cannot have a used var with a context dependent type"
6085 auto& current = [&] () -> CompactVector<Type>& {
6086 std::lock_guard<std::mutex> _{closure_use_vars_mutex};
6087 return m_data->closureUseVars[cls];
6088 }();
6090 always_assert(current.empty() || current.size() == vars.size());
6091 if (current.empty()) {
6092 current = vars;
6093 return true;
6096 auto changed = false;
6097 for (auto i = uint32_t{0}; i < vars.size(); ++i) {
6098 if (vars[i].strictSubtypeOf(current[i])) {
6099 changed = true;
6100 current[i] = vars[i];
6101 } else {
6102 always_assert_flog(
6103 moreRefinedForIndex(vars[i], current[i]),
6104 "Index closure_use_var invariant violated in {}.\n"
6105 " {} is not at least as refined as {}\n",
6106 cls->name,
6107 show(vars[i]),
6108 show(current[i])
6113 return changed;
6116 template<class Container>
6117 void refine_private_propstate(Container& cont,
6118 const php::Class* cls,
6119 const PropState& state) {
6120 assertx(!is_used_trait(*cls));
6121 auto* elm = [&] () -> typename Container::value_type* {
6122 std::lock_guard<std::mutex> _{private_propstate_mutex};
6123 auto it = cont.find(cls);
6124 if (it == end(cont)) {
6125 cont[cls] = state;
6126 return nullptr;
6128 return &*it;
6129 }();
6131 if (!elm) return;
6133 for (auto& kv : state) {
6134 auto& target = elm->second[kv.first];
6135 assertx(target.tc == kv.second.tc);
6136 always_assert_flog(
6137 moreRefinedForIndex(kv.second.ty, target.ty),
6138 "PropState refinement failed on {}::${} -- {} was not a subtype of {}\n",
6139 cls->name->data(),
6140 kv.first->data(),
6141 show(kv.second.ty),
6142 show(target.ty)
6144 target.ty = kv.second.ty;
6148 void Index::refine_private_props(const php::Class* cls,
6149 const PropState& state) {
6150 refine_private_propstate(m_data->privatePropInfo, cls, state);
6153 void Index::refine_private_statics(const php::Class* cls,
6154 const PropState& state) {
6155 // We can't store context dependent types in private statics since they
6156 // could be accessed using different contexts.
6157 auto cleanedState = PropState{};
6158 for (auto const& prop : state) {
6159 auto& elem = cleanedState[prop.first];
6160 elem.ty = unctx(prop.second.ty);
6161 elem.tc = prop.second.tc;
6164 refine_private_propstate(m_data->privateStaticPropInfo, cls, cleanedState);
6167 void Index::record_public_static_mutations(const php::Func& func,
6168 PublicSPropMutations mutations) {
6169 if (!mutations.m_data) {
6170 m_data->publicSPropMutations.erase(&func);
6171 return;
6173 m_data->publicSPropMutations.insert_or_assign(&func, std::move(mutations));
6176 void Index::update_static_prop_init_val(const php::Class* cls,
6177 SString name) const {
6178 for (auto& info : find_range(m_data->classInfo, cls->name)) {
6179 auto const cinfo = info.second;
6180 if (cinfo->cls != cls) continue;
6181 auto const it = cinfo->publicStaticProps.find(name);
6182 if (it != cinfo->publicStaticProps.end()) {
6183 it->second.initialValueResolved = true;
6188 void Index::refine_public_statics(DependencyContextSet& deps) {
6189 trace_time update("update public statics");
6191 // Union together the mutations for each function, including the functions
6192 // which weren't analyzed this round.
6193 auto nothing_known = false;
6194 PublicSPropMutations::UnknownMap unknown;
6195 PublicSPropMutations::KnownMap known;
6196 for (auto const& mutations : m_data->publicSPropMutations) {
6197 if (!mutations.second.m_data) continue;
6198 if (mutations.second.m_data->m_nothing_known) {
6199 nothing_known = true;
6200 break;
6203 for (auto const& kv : mutations.second.m_data->m_unknown) {
6204 auto const ret = unknown.insert(kv);
6205 if (!ret.second) ret.first->second |= kv.second;
6207 for (auto const& kv : mutations.second.m_data->m_known) {
6208 auto const ret = known.insert(kv);
6209 if (!ret.second) ret.first->second |= kv.second;
6213 if (nothing_known) {
6214 // We cannot go from knowing the types to not knowing the types (this is
6215 // equivalent to widening the types).
6216 always_assert(m_data->allPublicSPropsUnknown);
6217 return;
6220 auto const firstRefinement = m_data->allPublicSPropsUnknown;
6221 m_data->allPublicSPropsUnknown = false;
6223 if (firstRefinement) {
6224 // If this is the first refinement, reschedule any dependency which looked
6225 // at the public static property state previously.
6226 always_assert(m_data->unknownClassSProps.empty());
6227 for (auto const& dependency : m_data->dependencyMap) {
6228 if (dependency.first.tag() != DependencyContextType::PropName) continue;
6229 for (auto const& kv : dependency.second) {
6230 if (has_dep(kv.second, Dep::PublicSPropName)) deps.insert(kv.first);
6235 // Refine unknown class state
6236 for (auto const& kv : unknown) {
6237 // We can't keep context dependent types in public properties.
6238 auto newType = unctx(kv.second);
6239 auto it = m_data->unknownClassSProps.find(kv.first);
6240 if (it == end(m_data->unknownClassSProps)) {
6241 // If this is the first refinement, our previous state was effectively
6242 // TCell for everything, so inserting a type into the map can only
6243 // refine. However, if this isn't the first refinement, a name not present
6244 // in the map means that its TBottom, so we shouldn't be inserting
6245 // anything.
6246 always_assert(firstRefinement);
6247 m_data->unknownClassSProps.emplace(
6248 kv.first,
6249 std::make_pair(std::move(newType), 0)
6251 continue;
6255 * We may only shrink the types we recorded for each property. (If a
6256 * property type ever grows, the interpreter could infer something
6257 * incorrect at some step.)
6259 always_assert(!firstRefinement);
6260 always_assert_flog(
6261 newType.subtypeOf(it->second.first),
6262 "Static property index invariant violated for name {}:\n"
6263 " {} was not a subtype of {}",
6264 kv.first->data(),
6265 show(newType),
6266 show(it->second.first)
6269 // Put a limit on the refinements to ensure termination. Since we only ever
6270 // refine types, we can stop at any point and maintain correctness.
6271 if (it->second.second + 1 < options.publicSPropRefineLimit) {
6272 if (newType.strictSubtypeOf(it->second.first)) {
6273 find_deps(*m_data, it->first, Dep::PublicSPropName, deps);
6275 it->second.first = std::move(newType);
6276 ++it->second.second;
6277 } else {
6278 FTRACE(
6279 1, "maxed out public static property refinements for name {}\n",
6280 kv.first->data()
6285 // If we didn't see a mutation among all the functions for a particular name,
6286 // it means the type is TBottom. Iterate through the unknown class state and
6287 // remove any entries which we didn't see a mutation for.
6288 if (!firstRefinement) {
6289 auto it = begin(m_data->unknownClassSProps);
6290 auto last = end(m_data->unknownClassSProps);
6291 while (it != last) {
6292 auto const unknownIt = unknown.find(it->first);
6293 if (unknownIt == end(unknown)) {
6294 if (unknownIt->second != TBottom) {
6295 find_deps(*m_data, unknownIt->first, Dep::PublicSPropName, deps);
6297 it = m_data->unknownClassSProps.erase(it);
6298 } else {
6299 ++it;
6304 // Refine known class state
6305 for (auto const& cinfo : m_data->allClassInfos) {
6306 for (auto& kv : cinfo->publicStaticProps) {
6307 auto const newType = [&] {
6308 auto const it = known.find(
6309 PublicSPropMutations::KnownKey { cinfo.get(), kv.first }
6311 // If we didn't see a mutation, the type is TBottom.
6312 if (it == end(known)) return TBottom;
6313 // We can't keep context dependent types in public properties.
6314 return adjust_type_for_prop(
6315 *this, *cinfo->cls, kv.second.tc, unctx(it->second)
6317 }();
6319 if (kv.second.initialValueResolved) {
6320 for (auto& prop : cinfo->cls->properties) {
6321 if (prop.name != kv.first) continue;
6322 kv.second.initializerType = from_cell(prop.val);
6323 kv.second.initialValueResolved = false;
6324 break;
6326 assertx(!kv.second.initialValueResolved);
6329 // The type from the indexer doesn't contain the in-class initializer
6330 // types. Add that here.
6331 auto effectiveType = union_of(newType, kv.second.initializerType);
6334 * We may only shrink the types we recorded for each property. (If a
6335 * property type ever grows, the interpreter could infer something
6336 * incorrect at some step.)
6338 always_assert_flog(
6339 effectiveType.subtypeOf(kv.second.inferredType),
6340 "Static property index invariant violated on {}::{}:\n"
6341 " {} is not a subtype of {}",
6342 cinfo->cls->name->data(),
6343 kv.first->data(),
6344 show(effectiveType),
6345 show(kv.second.inferredType)
6347 always_assert(newType == TBottom || kv.second.everModified);
6349 // Put a limit on the refinements to ensure termination. Since we only
6350 // ever refine types, we can stop at any point and still maintain
6351 // correctness.
6352 if (kv.second.refinements + 1 < options.publicSPropRefineLimit) {
6353 if (effectiveType.strictSubtypeOf(kv.second.inferredType)) {
6354 find_deps(*m_data, kv.first, Dep::PublicSPropName, deps);
6356 kv.second.inferredType = std::move(effectiveType);
6357 kv.second.everModified = newType != TBottom;
6358 ++kv.second.refinements;
6359 } else {
6360 FTRACE(
6361 1, "maxed out public static property refinements for {}:{}\n",
6362 cinfo->cls->name->data(),
6363 kv.first->data()
6370 void Index::refine_bad_initial_prop_values(const php::Class* cls,
6371 bool value,
6372 DependencyContextSet& deps) {
6373 assertx(!is_used_trait(*cls));
6375 for (auto& info : find_range(m_data->classInfo, cls->name)) {
6376 auto const cinfo = info.second;
6377 if (cinfo->cls != cls) continue;
6378 always_assert_flog(
6379 cinfo->hasBadInitialPropValues || !value,
6380 "Bad initial prop values going from false to true on {}",
6381 cls->name->data()
6384 if (cinfo->hasBadInitialPropValues && !value) {
6385 cinfo->hasBadInitialPropValues = false;
6386 find_deps(*m_data, cls, Dep::PropBadInitialValues, deps);
6391 bool Index::frozen() const {
6392 return m_data->frozen;
6395 void Index::freeze() {
6396 m_data->frozen = true;
6397 m_data->ever_frozen = true;
6401 * Note that these functions run in separate threads, and
6402 * intentionally don't bump Trace::hhbbc_time. If you want to see
6403 * these times, set TRACE=hhbbc_time:1
6405 #define CLEAR(x) \
6407 trace_time _{"Clearing " #x}; \
6408 (x).clear(); \
6411 void Index::cleanup_for_final() {
6412 trace_time _{"cleanup_for_final"};
6413 CLEAR(m_data->dependencyMap);
6417 void Index::cleanup_post_emit() {
6418 trace_time _{"cleanup_post_emit"};
6420 trace_time t{"Reset allClassInfos"};
6421 parallel::for_each(m_data->allClassInfos, [] (auto& u) { u.reset(); });
6423 std::vector<std::function<void()>> clearers;
6424 #define CLEAR_PARALLEL(x) clearers.push_back([&] CLEAR(x));
6425 CLEAR_PARALLEL(m_data->classes);
6426 CLEAR_PARALLEL(m_data->methods);
6427 CLEAR_PARALLEL(m_data->method_inout_params_by_name);
6428 CLEAR_PARALLEL(m_data->funcs);
6429 CLEAR_PARALLEL(m_data->typeAliases);
6430 CLEAR_PARALLEL(m_data->enums);
6431 CLEAR_PARALLEL(m_data->constants);
6432 CLEAR_PARALLEL(m_data->records);
6434 CLEAR_PARALLEL(m_data->classClosureMap);
6435 CLEAR_PARALLEL(m_data->classExtraMethodMap);
6437 CLEAR_PARALLEL(m_data->allClassInfos);
6438 CLEAR_PARALLEL(m_data->classInfo);
6439 CLEAR_PARALLEL(m_data->funcInfo);
6441 CLEAR_PARALLEL(m_data->privatePropInfo);
6442 CLEAR_PARALLEL(m_data->privateStaticPropInfo);
6443 CLEAR_PARALLEL(m_data->unknownClassSProps);
6444 CLEAR_PARALLEL(m_data->publicSPropMutations);
6445 CLEAR_PARALLEL(m_data->ifaceSlotMap);
6446 CLEAR_PARALLEL(m_data->closureUseVars);
6448 CLEAR_PARALLEL(m_data->foldableReturnTypeMap);
6449 CLEAR_PARALLEL(m_data->contextualReturnTypes);
6451 parallel::for_each(clearers, [] (const std::function<void()>& f) { f(); });
6454 void Index::thaw() {
6455 m_data->frozen = false;
6458 std::unique_ptr<ArrayTypeTable::Builder>& Index::array_table_builder() const {
6459 return m_data->arrTableBuilder;
6462 //////////////////////////////////////////////////////////////////////
6464 res::Func Index::do_resolve(const php::Func* f) const {
6465 auto const finfo = create_func_info(*m_data, f);
6466 return res::Func { this, finfo };
6469 // Return true if we know for sure that one php::Class must derive
6470 // from another at runtime, in all possible instantiations.
6471 bool Index::must_be_derived_from(const php::Class* cls,
6472 const php::Class* parent) const {
6473 if (cls == parent) return true;
6474 auto const clsClasses = find_range(m_data->classInfo, cls->name);
6475 auto const parentClasses = find_range(m_data->classInfo, parent->name);
6476 for (auto& kvCls : clsClasses) {
6477 auto const rCls = res::Class { this, kvCls.second };
6478 for (auto& kvPar : parentClasses) {
6479 auto const rPar = res::Class { this, kvPar.second };
6480 if (!rCls.mustBeSubtypeOf(rPar)) return false;
6483 return true;
6486 // Return true if any possible definition of one php::Class could
6487 // derive from another at runtime, or vice versa.
6488 bool
6489 Index::could_be_related(const php::Class* cls,
6490 const php::Class* parent) const {
6491 if (cls == parent) return true;
6492 auto const clsClasses = find_range(m_data->classInfo, cls->name);
6493 auto const parentClasses = find_range(m_data->classInfo, parent->name);
6494 for (auto& kvCls : clsClasses) {
6495 auto const rCls = res::Class { this, kvCls.second };
6496 for (auto& kvPar : parentClasses) {
6497 auto const rPar = res::Class { this, kvPar.second };
6498 if (rCls.couldBe(rPar)) return true;
6501 return false;
6504 //////////////////////////////////////////////////////////////////////
6506 void PublicSPropMutations::merge(const Index& index,
6507 Context ctx,
6508 const Type& tcls,
6509 const Type& name,
6510 const Type& val,
6511 bool ignoreConst) {
6512 // Figure out which class this can affect. If we have a DCls::Sub we have to
6513 // assume it could affect any subclass, so we repeat this merge for all exact
6514 // class types deriving from that base.
6515 if (is_specialized_cls(tcls)) {
6516 auto const dcls = dcls_of(tcls);
6517 if (auto const cinfo = dcls.cls.val.right()) {
6518 switch (dcls.type) {
6519 case DCls::Exact:
6520 return merge(index, ctx, cinfo, name, val, ignoreConst);
6521 case DCls::Sub:
6522 for (auto const sub : cinfo->subclassList) {
6523 merge(index, ctx, sub, name, val, ignoreConst);
6525 return;
6527 not_reached();
6531 merge(index, ctx, nullptr, name, val, ignoreConst);
6534 void PublicSPropMutations::merge(const Index& index,
6535 Context ctx,
6536 ClassInfo* cinfo,
6537 const Type& name,
6538 const Type& val,
6539 bool ignoreConst) {
6540 FTRACE(2, "merge_public_static: {} {} {}\n",
6541 cinfo ? cinfo->cls->name->data() : "<unknown>", show(name), show(val));
6543 auto get = [this] () -> Data& {
6544 if (!m_data) m_data = std::make_unique<Data>();
6545 return *m_data;
6548 auto const vname = tv(name);
6549 auto const unknownName = !vname ||
6550 (vname && vname->m_type != KindOfPersistentString);
6552 if (!cinfo) {
6553 if (unknownName) {
6555 * We have a case here where we know neither the class nor the static
6556 * property name. This means we have to pessimize public static property
6557 * types for the entire program.
6559 * We could limit it to pessimizing them by merging the `val' type, but
6560 * instead we just throw everything away---this optimization is not
6561 * expected to be particularly useful on programs that contain any
6562 * instances of this situation.
6564 std::fprintf(
6565 stderr,
6566 "NOTE: had to mark everything unknown for public static "
6567 "property types due to dynamic code. -fanalyze-public-statics "
6568 "will not help for this program.\n"
6569 "NOTE: The offending code occured in this context: %s\n",
6570 show(ctx).c_str()
6572 get().m_nothing_known = true;
6573 return;
6576 auto const res = get().m_unknown.emplace(vname->m_data.pstr, val);
6577 if (!res.second) res.first->second |= val;
6578 return;
6582 * We don't know the name, but we know something about the class. We need to
6583 * merge the type for every property in the class hierarchy.
6585 if (unknownName) {
6586 visit_parent_cinfo(cinfo,
6587 [&] (const ClassInfo* ci) {
6588 for (auto& kv : ci->publicStaticProps) {
6589 merge(index, ctx, cinfo, sval(kv.first),
6590 val, ignoreConst);
6592 return false;
6594 return;
6598 * Here we know both the ClassInfo and the static property name, but it may
6599 * not actually be on this ClassInfo. In php, you can access base class
6600 * static properties through derived class names, and the access affects the
6601 * property with that name on the most-recently-inherited-from base class.
6603 * If the property is not found as a public property anywhere in the
6604 * hierarchy, we don't want to merge this type. Note we don't have to worry
6605 * about the case that there is a protected property in between, because this
6606 * is a fatal at class declaration time (you can't redeclare a public static
6607 * property with narrower access in a subclass).
6609 auto const affectedInfo = (
6610 visit_parent_cinfo(
6611 cinfo,
6612 [&] (const ClassInfo* ci) ->
6613 folly::Optional<std::pair<ClassInfo*, const TypeConstraint*>> {
6614 auto const it = ci->publicStaticProps.find(vname->m_data.pstr);
6615 if (it != end(ci->publicStaticProps)) {
6616 return std::make_pair(
6617 const_cast<ClassInfo*>(ci),
6618 it->second.tc
6621 return folly::none;
6626 if (!affectedInfo) {
6627 // Either this was a mutation that's going to fatal (property doesn't
6628 // exist), or it's a private static or a protected static. We aren't in
6629 // that business here, so we don't need to record anything.
6630 return;
6633 auto const affectedCInfo = affectedInfo->first;
6634 auto const affectedTC = affectedInfo->second;
6636 if (!ignoreConst) {
6637 for (auto const& prop : affectedCInfo->cls->properties) {
6638 if (prop.name == vname->m_data.pstr && (prop.attrs & AttrIsConst)) {
6639 return;
6644 auto const adjusted =
6645 adjust_type_for_prop(index, *affectedCInfo->cls, affectedTC, val);
6647 // Merge the property type.
6648 auto const res = get().m_known.emplace(
6649 KnownKey { affectedCInfo, vname->m_data.pstr },
6650 adjusted
6652 if (!res.second) res.first->second |= adjusted;
6655 void PublicSPropMutations::merge(const Index& index,
6656 Context ctx,
6657 const php::Class& cls,
6658 const Type& name,
6659 const Type& val,
6660 bool ignoreConst) {
6661 auto range = find_range(index.m_data->classInfo, cls.name);
6662 for (auto const& pair : range) {
6663 auto const cinfo = pair.second;
6664 if (cinfo->cls != &cls) continue;
6665 // Note that this works for both traits and regular classes
6666 for (auto const sub : cinfo->subclassList) {
6667 merge(index, ctx, sub, name, val, ignoreConst);
6672 //////////////////////////////////////////////////////////////////////