Parallelize preresolve algorithm
[hiphop-php.git] / hphp / hhbbc / index.cpp
blobfe1cfa56ff01dbbf4ced22591953ce3d7fe29a2c
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/array-iterator.h"
45 #include "hphp/runtime/base/runtime-option.h"
46 #include "hphp/runtime/base/tv-comparisons.h"
48 #include "hphp/runtime/vm/native.h"
49 #include "hphp/runtime/vm/preclass-emitter.h"
50 #include "hphp/runtime/vm/runtime.h"
51 #include "hphp/runtime/vm/trait-method-import-data.h"
52 #include "hphp/runtime/vm/unit-util.h"
54 #include "hphp/hhbbc/analyze.h"
55 #include "hphp/hhbbc/class-util.h"
56 #include "hphp/hhbbc/context.h"
57 #include "hphp/hhbbc/func-util.h"
58 #include "hphp/hhbbc/options.h"
59 #include "hphp/hhbbc/options-util.h"
60 #include "hphp/hhbbc/parallel.h"
61 #include "hphp/hhbbc/representation.h"
62 #include "hphp/hhbbc/type-builtins.h"
63 #include "hphp/hhbbc/type-system.h"
64 #include "hphp/hhbbc/unit-util.h"
65 #include "hphp/hhbbc/wide-func.h"
67 #include "hphp/util/algorithm.h"
68 #include "hphp/util/assertions.h"
69 #include "hphp/util/hash-set.h"
70 #include "hphp/util/lock-free-lazy.h"
71 #include "hphp/util/match.h"
73 namespace HPHP { namespace HHBBC {
75 TRACE_SET_MOD(hhbbc_index);
77 //////////////////////////////////////////////////////////////////////
79 namespace {
81 //////////////////////////////////////////////////////////////////////
83 const StaticString s_construct("__construct");
84 const StaticString s_toBoolean("__toBoolean");
85 const StaticString s_invoke("__invoke");
86 const StaticString s_Closure("Closure");
87 const StaticString s_AsyncGenerator("HH\\AsyncGenerator");
88 const StaticString s_Generator("Generator");
90 //////////////////////////////////////////////////////////////////////
92 // HHBBC consumes a LOT of memory, so we keep representation types small.
93 template <typename T, size_t Expected, size_t Actual = sizeof(T)>
94 constexpr bool CheckSize() { static_assert(Expected == Actual); return true; };
95 static_assert(CheckSize<php::Block, 24>(), "");
96 static_assert(CheckSize<php::Local, use_lowptr ? 12 : 16>(), "");
97 static_assert(CheckSize<php::Param, use_lowptr ? 64 : 88>(), "");
98 static_assert(CheckSize<php::Func, use_lowptr ? 168 : 200>(), "");
100 // Likewise, we also keep the bytecode and immediate types small.
101 static_assert(CheckSize<Bytecode, use_lowptr ? 32 : 40>(), "");
102 static_assert(CheckSize<MKey, 16>(), "");
103 static_assert(CheckSize<IterArgs, 16>(), "");
104 static_assert(CheckSize<FCallArgs, 8>(), "");
105 static_assert(CheckSize<RepoAuthType, 8>(), "");
107 //////////////////////////////////////////////////////////////////////
110 * One-to-many case insensitive map, where the keys are static strings
111 * and the values are some kind of pointer.
113 template<class T> using ISStringToMany =
114 std::unordered_multimap<
115 SString,
117 string_data_hash,
118 string_data_isame
121 template<class T> using SStringToMany =
122 std::unordered_multimap<
123 SString,
125 string_data_hash,
126 string_data_same
130 * One-to-one case insensitive map, where the keys are static strings
131 * and the values are some T.
133 template<class T> using ISStringToOneT =
134 hphp_hash_map<
135 SString,
137 string_data_hash,
138 string_data_isame
141 template<class T> using SStringToOneT =
142 hphp_hash_map<
143 SString,
145 string_data_hash,
146 string_data_same
150 * One-to-one case sensitive map, where the keys are static strings
151 * and the values are some T.
153 * Elements are not stable under insert/erase.
155 template<class T> using SStringToOneFastT =
156 hphp_fast_map<
157 SString,
159 string_data_hash,
160 string_data_same
164 * One-to-one case insensitive map, where the keys are static strings
165 * and the values are some kind of pointer.
167 template<class T> using ISStringToOne = ISStringToOneT<T*>;
169 //////////////////////////////////////////////////////////////////////
171 Dep operator|(Dep a, Dep b) {
172 return static_cast<Dep>(
173 static_cast<uintptr_t>(a) | static_cast<uintptr_t>(b)
177 bool has_dep(Dep m, Dep t) {
178 return static_cast<uintptr_t>(m) & static_cast<uintptr_t>(t);
182 * Maps functions to contexts that depend on information about that
183 * function, with information about the type of dependency.
185 using DepMap =
186 tbb::concurrent_hash_map<
187 DependencyContext,
188 std::map<DependencyContext,Dep,DependencyContextLess>,
189 DependencyContextHashCompare
192 //////////////////////////////////////////////////////////////////////
195 * Each ClassInfo has a table of public static properties with these entries.
196 * The `initializerType' is for use during refine_public_statics, and
197 * inferredType will always be a supertype of initializerType.
199 struct PublicSPropEntry {
200 Type inferredType;
201 Type initializerType;
202 const TypeConstraint* tc;
203 uint32_t refinements;
205 * This flag is set during analysis to indicate that we resolved the
206 * initial value (and updated it on the php::Class). This doesn't
207 * need to be atomic, because only one thread can resolve the value
208 * (the one processing the 86sinit), and it's been joined by the
209 * time we read the flag in refine_public_statics.
211 bool initialValueResolved;
215 * Entries in the ClassInfo method table need to track some additional
216 * information.
218 * The reason for this is that we need to record attributes of the
219 * class hierarchy.
221 struct MethTabEntry {
222 MethTabEntry(const php::Func* func, Attr a, bool hpa, bool tl) :
223 func(func), attrs(a), hasPrivateAncestor(hpa), topLevel(tl) {}
224 const php::Func* func = nullptr;
225 // A method could be imported from a trait, and its attributes changed
226 Attr attrs {};
227 bool hasAncestor = false;
228 bool hasPrivateAncestor = false;
229 // This method came from the ClassInfo that owns the MethTabEntry,
230 // or one of its used traits.
231 bool topLevel = false;
232 uint32_t idx = 0;
237 struct res::Func::MethTabEntryPair :
238 SStringToOneT<MethTabEntry>::value_type {};
240 namespace {
242 using MethTabEntryPair = res::Func::MethTabEntryPair;
244 inline MethTabEntryPair* mteFromElm(
245 SStringToOneT<MethTabEntry>::value_type& elm) {
246 return static_cast<MethTabEntryPair*>(&elm);
249 inline const MethTabEntryPair* mteFromElm(
250 const SStringToOneT<MethTabEntry>::value_type& elm) {
251 return static_cast<const MethTabEntryPair*>(&elm);
254 inline MethTabEntryPair* mteFromIt(SStringToOneT<MethTabEntry>::iterator it) {
255 return static_cast<MethTabEntryPair*>(&*it);
258 struct CallContextHashCompare {
259 bool equal(const CallContext& a, const CallContext& b) const {
260 return a == b;
263 size_t hash(const CallContext& c) const {
264 auto ret = folly::hash::hash_combine(
265 c.callee,
266 c.args.size(),
267 c.context.hash()
269 for (auto& t : c.args) {
270 ret = folly::hash::hash_combine(ret, t.hash());
272 return ret;
276 using ContextRetTyMap = tbb::concurrent_hash_map<
277 CallContext,
278 Type,
279 CallContextHashCompare
282 //////////////////////////////////////////////////////////////////////
284 template<class Filter>
285 PropState make_unknown_propstate(const php::Class* cls,
286 Filter filter) {
287 auto ret = PropState{};
288 for (auto& prop : cls->properties) {
289 if (filter(prop)) {
290 auto& elem = ret[prop.name];
291 elem.ty = TCell;
292 elem.tc = &prop.typeConstraint;
293 elem.attrs = prop.attrs;
296 return ret;
302 * Currently inferred information about a PHP function.
304 * Nothing in this structure can ever be untrue. The way the
305 * algorithm works, whatever is in here must be factual (even if it is
306 * not complete information), because we may deduce other facts based
307 * on it.
309 struct res::Func::FuncInfo {
310 const php::Func* func = nullptr;
312 * The best-known return type of the function, if we have any
313 * information. May be TBottom if the function is known to never
314 * return (e.g. always throws).
316 Type returnTy = TInitCell;
319 * If the function always returns the same parameter, this will be
320 * set to its id; otherwise it will be NoLocalId.
322 LocalId retParam{NoLocalId};
325 * The number of times we've refined returnTy.
327 uint32_t returnRefinements{0};
330 * Whether the function is effectFree.
332 bool effectFree{false};
335 * Bitset representing which parameters definitely don't affect the
336 * result of the function, assuming it produces one. Note that
337 * VerifyParamType does not count as a use in this context.
339 std::bitset<64> unusedParams;
342 * List of all func families this function belongs to.
344 CompactVector<FuncFamily*> families;
347 namespace {
349 //////////////////////////////////////////////////////////////////////
352 * Known information about a particular constant:
353 * - if system is true, it's a system constant and other definitions
354 * will be ignored.
355 * - for non-system constants, if func is non-null it's the unique
356 * pseudomain defining the constant; otherwise there was more than
357 * one definition, or a non-pseudomain definition, and the type will
358 * be TInitCell
359 * - readonly is true if we've only seen uses of the constant, and no
360 * definitions (this could change during the first pass, but not after
361 * that).
364 struct ConstInfo {
365 const php::Func* func;
366 Type type;
367 bool system;
368 bool readonly;
371 using FuncFamily = res::Func::FuncFamily;
372 using FuncInfo = res::Func::FuncInfo;
373 using MethTabEntryPair = res::Func::MethTabEntryPair;
375 //////////////////////////////////////////////////////////////////////
379 //////////////////////////////////////////////////////////////////////
382 * Sometimes function resolution can't determine which function
383 * something will call, but can restrict it to a family of functions.
385 * For example, if you want to call an abstract function on a base
386 * class with all unique derived classes, we will resolve the function
387 * to a FuncFamily that contains references to all the possible
388 * overriding-functions.
390 struct res::Func::FuncFamily {
391 using PFuncVec = CompactVector<const MethTabEntryPair*>;
393 explicit FuncFamily(PFuncVec&& v) : m_v{std::move(v)} {}
394 FuncFamily(FuncFamily&& o) noexcept : m_v(std::move(o.m_v)) {}
395 FuncFamily& operator=(FuncFamily&& o) noexcept {
396 m_v = std::move(o.m_v);
397 return *this;
399 FuncFamily(const FuncFamily&) = delete;
400 FuncFamily& operator=(const FuncFamily&) = delete;
402 const PFuncVec& possibleFuncs() const {
403 return m_v;
406 friend auto begin(const FuncFamily& ff) { return ff.m_v.begin(); }
407 friend auto end(const FuncFamily& ff) { return ff.m_v.end(); }
409 PFuncVec m_v;
410 LockFreeLazy<Type> m_returnTy;
411 folly::Optional<uint32_t> m_numInOut;
414 namespace {
416 struct PFuncVecHasher {
417 size_t operator()(const FuncFamily::PFuncVec& v) const {
418 return folly::hash::hash_range(
419 v.begin(),
420 v.end(),
422 pointer_hash<MethTabEntryPair>{}
429 //////////////////////////////////////////////////////////////////////
431 /* Known information about a particular possible instantiation of a
432 * PHP record. The php::Record will be marked AttrUnique if there is a unique
433 * RecordInfo with a given name.
435 struct RecordInfo {
436 const php::Record* rec = nullptr;
437 const RecordInfo* parent = nullptr;
439 * A vector of RecordInfo that encodes the inheritance hierarchy.
441 CompactVector<RecordInfo*> baseList;
442 const php::Record* phpType() const { return rec; }
446 * Known information about a particular possible instantiation of a
447 * PHP class. The php::Class will be marked AttrUnique if there is a
448 * unique ClassInfo with the same name.
450 struct ClassInfo {
452 * A pointer to the underlying php::Class that we're storing
453 * information about.
455 const php::Class* cls = nullptr;
458 * The info for the parent of this Class.
460 ClassInfo* parent = nullptr;
463 * A vector of the declared interfaces class info structures. This is in
464 * declaration order mirroring the php::Class interfaceNames vector, and does
465 * not include inherited interfaces.
467 CompactVector<const ClassInfo*> declInterfaces;
470 * A (case-insensitive) map from interface names supported by this class to
471 * their ClassInfo structures, flattened across the hierarchy.
473 ISStringToOneT<const ClassInfo*> implInterfaces;
476 * A vector of the included enums, in class order, mirroring the
477 * php::Class includedEnums vector.
479 CompactVector<const ClassInfo*> includedEnums;
481 struct ConstIndex {
482 php::Const operator*() const {
483 return cls->constants[idx];
485 const php::Const* operator->() const {
486 return get();
488 const php::Const* get() const {
489 return &cls->constants[idx];
491 const php::Class* cls;
492 uint32_t idx;
496 * A (case-sensitive) map from class constant name to the php::Class* and
497 * index into the constants vector that it came from. This map is flattened
498 * across the inheritance hierarchy.
500 hphp_fast_map<SString, ConstIndex> clsConstants;
503 * A vector of the used traits, in class order, mirroring the
504 * php::Class usedTraitNames vector.
506 CompactVector<const ClassInfo*> usedTraits;
509 * A list of extra properties supplied by this class's used traits.
511 CompactVector<php::Prop> traitProps;
514 * A list of extra consts supplied by this class's used traits.
516 CompactVector<php::Const> traitConsts;
519 * A (case-sensitive) map from class method names to the php::Func
520 * associated with it. This map is flattened across the inheritance
521 * hierarchy.
523 SStringToOneT<MethTabEntry> methods;
526 * A (case-sensitive) map from class method names to associated
527 * FuncFamily objects that group the set of possibly-overriding
528 * methods.
530 * Note that this does not currently encode anything for interface
531 * methods.
533 * Invariant: methods on this class with AttrNoOverride or
534 * AttrPrivate will not have an entry in this map.
536 SStringToOneFastT<FuncFamily*> methodFamilies;
537 // Resolutions to single entries do not require a FuncFamily (this
538 // saves space).
539 SStringToOneFastT<const MethTabEntryPair*> singleMethodFamilies;
542 * Subclasses of this class, including this class itself.
544 * For interfaces, this is the list of instantiable classes that
545 * implement this interface.
547 * For traits, this is the list of classes that use the trait where
548 * the trait wasn't flattened into the class (including the trait
549 * itself).
551 * Note, unlike baseList, the order of the elements in this vector
552 * is unspecified.
554 CompactVector<ClassInfo*> subclassList;
557 * A vector of ClassInfo that encodes the inheritance hierarchy,
558 * unless this ClassInfo represents an interface.
560 * This is the list of base classes for this class in inheritance
561 * order.
563 CompactVector<ClassInfo*> baseList;
566 * Property types for public static properties, declared on this exact class
567 * (i.e. not flattened in the hierarchy).
569 * These maps always have an entry for each public static property declared
570 * in this class, so it can also be used to check if this class declares a
571 * public static property of a given name.
573 * Note: the effective type we can assume a given static property may hold is
574 * not just the value in these maps. To handle mutations of public statics
575 * where the name is known, but not which class was affected, these always
576 * need to be unioned with values from IndexData::unknownClassSProps.
578 hphp_hash_map<SString,PublicSPropEntry> publicStaticProps;
580 struct PreResolveState {
581 hphp_fast_map<SString, std::pair<php::Prop, const ClassInfo*>> pbuildNoTrait;
582 hphp_fast_map<SString, std::pair<php::Prop, const ClassInfo*>> pbuildTrait;
583 hphp_fast_set<SString> constsFromTraits;
585 std::unique_ptr<PreResolveState> preResolveState;
588 * Flags to track if this class is mocked, or if any of its dervied classes
589 * are mocked.
591 bool isMocked{false};
592 bool isDerivedMocked{false};
595 * Track if this class has a property which might redeclare a property in a
596 * parent class with an inequivalent type-hint.
598 bool hasBadRedeclareProp{true};
601 * Track if this class has any properties with initial values that might
602 * violate their type-hints.
604 bool hasBadInitialPropValues{true};
607 * Track if this class has any const props (including inherited ones).
609 bool hasConstProp{false};
612 * Track if any derived classes (including this one) have any const props.
614 bool derivedHasConstProp{false};
616 const php::Class* phpType() const { return cls; }
619 * Return true if this is derived from o.
621 bool derivedFrom(const ClassInfo& o) const {
622 if (this == &o) return true;
623 // If o is an interface, see if this declared it.
624 if (o.cls->attrs & AttrInterface) return implInterfaces.count(o.cls->name);
625 // Otherwise check for direct inheritance.
626 if (baseList.size() >= o.baseList.size()) {
627 return baseList[o.baseList.size() - 1] == &o;
629 return false;
633 * Flags about the existence of various magic methods, or whether
634 * any derived classes may have those methods. The non-derived
635 * flags imply the derived flags, even if the class is final, so you
636 * don't need to check both in those situations.
638 struct MagicFnInfo {
639 bool thisHas{false};
640 bool derivedHas{false};
642 MagicFnInfo magicBool;
645 struct MagicMapInfo {
646 StaticString name;
647 ClassInfo::MagicFnInfo ClassInfo::*pmem;
650 const MagicMapInfo magicMethods[] {
651 { StaticString{"__toBoolean"}, &ClassInfo::magicBool },
653 //////////////////////////////////////////////////////////////////////
655 namespace res {
656 Record::Record(Either<SString, RecordInfo*> val) : val(val) {}
658 bool Record::same(const Record& o) const {
659 return val == o.val;
662 bool Record::couldBe(const Record& o) const {
663 // If either types are not unique return true
664 if (val.left() || o.val.left()) return true;
666 auto r1 = val.right();
667 auto r2 = o.val.right();
668 assertx(r1 && r2);
669 // Both types are unique records so they "could be" if they are in an
670 // inheritance relationship
671 if (r1->baseList.size() >= r2->baseList.size()) {
672 return r1->baseList[r2->baseList.size() - 1] == r2;
673 } else {
674 return r2->baseList[r1->baseList.size() - 1] == r1;
678 SString Record::name() const {
679 return val.match(
680 [] (SString s) { return s; },
681 [] (RecordInfo* ri) { return ri->rec->name.get(); }
685 template <bool returnTrueOnMaybe>
686 bool Record::subtypeOfImpl(const Record& o) const {
687 auto s1 = val.left();
688 auto s2 = o.val.left();
689 if (s1 || s2) return returnTrueOnMaybe || s1 == s2;
690 auto r1 = val.right();
691 auto r2 = o.val.right();
692 assertx(r1 && r2);
693 if (r1->baseList.size() >= r2->baseList.size()) {
694 return r1->baseList[r2->baseList.size() - 1] == r2;
696 return false;
699 bool Record::mustBeSubtypeOf(const Record& o) const {
700 return subtypeOfImpl<false>(o);
703 bool Record::maybeSubtypeOf(const Record& o) const {
704 return subtypeOfImpl<true>(o);
707 bool Record::couldBeOverriden() const {
708 return val.match(
709 [] (SString) { return true; },
710 [] (RecordInfo* rinfo) {
711 return !(rinfo->rec->attrs & AttrFinal);
716 std::string show(const Record& r) {
717 return r.val.match(
718 [] (SString s) -> std::string {
719 return s->data();
721 [] (RecordInfo* rinfo) {
722 return folly::sformat("{}*", rinfo->rec->name);
727 folly::Optional<Record> Record::commonAncestor(const Record& r) const {
728 if (val.left() || r.val.left()) return folly::none;
729 auto const c1 = val.right();
730 auto const c2 = r.val.right();
731 // Walk the arrays of base classes until they match. For common ancestors
732 // to exist they must be on both sides of the baseList at the same positions
733 RecordInfo* ancestor = nullptr;
734 auto it1 = c1->baseList.begin();
735 auto it2 = c2->baseList.begin();
736 while (it1 != c1->baseList.end() && it2 != c2->baseList.end()) {
737 if (*it1 != *it2) break;
738 ancestor = *it1;
739 ++it1; ++it2;
741 if (ancestor == nullptr) {
742 return folly::none;
744 return res::Record { ancestor };
747 Class::Class(Either<SString,ClassInfo*> val) : val(val) {}
749 // Class type operations here are very conservative for now.
751 bool Class::same(const Class& o) const {
752 return val == o.val;
755 template <bool returnTrueOnMaybe>
756 bool Class::subtypeOfImpl(const Class& o) const {
757 auto s1 = val.left();
758 auto s2 = o.val.left();
759 if (s1 || s2) return returnTrueOnMaybe || s1 == s2;
760 auto c1 = val.right();
761 auto c2 = o.val.right();
762 return c1->derivedFrom(*c2);
765 bool Class::mustBeSubtypeOf(const Class& o) const {
766 return subtypeOfImpl<false>(o);
769 bool Class::maybeSubtypeOf(const Class& o) const {
770 return subtypeOfImpl<true>(o);
773 bool Class::couldBe(const Class& o) const {
774 if (same(o)) return true;
776 // If either types are not unique return true
777 if (val.left() || o.val.left()) return true;
779 auto c1 = val.right();
780 auto c2 = o.val.right();
781 // if one or the other is an interface return true for now.
782 // TODO(#3621433): better interface stuff
783 if (c1->cls->attrs & AttrInterface || c2->cls->attrs & AttrInterface) {
784 return true;
787 // Both types are unique classes so they "could be" if they are in an
788 // inheritance relationship
789 if (c1->baseList.size() >= c2->baseList.size()) {
790 return c1->baseList[c2->baseList.size() - 1] == c2;
791 } else {
792 return c2->baseList[c1->baseList.size() - 1] == c1;
796 SString Class::name() const {
797 return val.match(
798 [] (SString s) { return s; },
799 [] (ClassInfo* ci) { return ci->cls->name.get(); }
803 bool Class::couldBeInterface() const {
804 return val.match(
805 [] (SString) { return true; },
806 [] (ClassInfo* cinfo) {
807 return cinfo->cls->attrs & AttrInterface;
812 bool Class::mustBeInterface() const {
813 return val.match(
814 [] (SString) { return false; },
815 [] (ClassInfo* cinfo) {
816 return cinfo->cls->attrs & AttrInterface;
821 bool Class::couldBeOverriden() const {
822 return val.match(
823 [] (SString) { return true; },
824 [] (ClassInfo* cinfo) {
825 return !(cinfo->cls->attrs & AttrNoOverride);
830 bool Class::couldHaveMagicBool() const {
831 return val.match(
832 [] (SString) { return true; },
833 [] (ClassInfo* cinfo) {
834 return cinfo->magicBool.derivedHas;
839 bool Class::couldHaveMockedDerivedClass() const {
840 return val.match(
841 [] (SString) { return true;},
842 [] (ClassInfo* cinfo) {
843 return cinfo->isDerivedMocked;
848 bool Class::couldBeMocked() const {
849 return val.match(
850 [] (SString) { return true;},
851 [] (ClassInfo* cinfo) {
852 return cinfo->isMocked;
857 bool Class::couldHaveReifiedGenerics() const {
858 return val.match(
859 [] (SString) { return true; },
860 [] (ClassInfo* cinfo) {
861 return cinfo->cls->hasReifiedGenerics;
866 bool Class::mightCareAboutDynConstructs() const {
867 if (RuntimeOption::EvalForbidDynamicConstructs > 0) {
868 return val.match(
869 [] (SString) { return true; },
870 [] (ClassInfo* cinfo) {
871 return !(cinfo->cls->attrs & AttrDynamicallyConstructible);
875 return false;
878 bool Class::couldHaveConstProp() const {
879 return val.match(
880 [] (SString) { return true; },
881 [] (ClassInfo* cinfo) { return cinfo->hasConstProp; }
885 bool Class::derivedCouldHaveConstProp() const {
886 return val.match(
887 [] (SString) { return true; },
888 [] (ClassInfo* cinfo) { return cinfo->derivedHasConstProp; }
892 folly::Optional<Class> Class::commonAncestor(const Class& o) const {
893 if (val.left() || o.val.left()) return folly::none;
894 auto const c1 = val.right();
895 auto const c2 = o.val.right();
896 if (c1 == c2) return res::Class { c1 };
897 // Walk the arrays of base classes until they match. For common ancestors
898 // to exist they must be on both sides of the baseList at the same positions
899 ClassInfo* ancestor = nullptr;
900 auto it1 = c1->baseList.begin();
901 auto it2 = c2->baseList.begin();
902 while (it1 != c1->baseList.end() && it2 != c2->baseList.end()) {
903 if (*it1 != *it2) break;
904 ancestor = *it1;
905 ++it1; ++it2;
907 if (ancestor == nullptr) {
908 return folly::none;
910 return res::Class { ancestor };
913 folly::Optional<res::Class> Class::parent() const {
914 if (!val.right()) return folly::none;
915 auto parent = val.right()->parent;
916 if (!parent) return folly::none;
917 return res::Class { parent };
920 const php::Class* Class::cls() const {
921 return val.right() ? val.right()->cls : nullptr;
924 std::string show(const Class& c) {
925 return c.val.match(
926 [] (SString s) -> std::string {
927 return s->data();
929 [] (ClassInfo* cinfo) {
930 return folly::sformat("{}*", cinfo->cls->name);
935 Func::Func(const Index* idx, Rep val)
936 : index(idx)
937 , val(val)
940 SString Func::name() const {
941 return match<SString>(
942 val,
943 [&] (FuncName s) { return s.name; },
944 [&] (MethodName s) { return s.name; },
945 [&] (FuncInfo* fi) { return fi->func->name; },
946 [&] (const MethTabEntryPair* mte) { return mte->first; },
947 [&] (FuncFamily* fa) -> SString {
948 auto const name = fa->possibleFuncs().front()->first;
949 if (debug) {
950 for (DEBUG_ONLY auto const f : fa->possibleFuncs()) {
951 assertx(f->first->isame(name));
954 return name;
959 const php::Func* Func::exactFunc() const {
960 using Ret = const php::Func*;
961 return match<Ret>(
962 val,
963 [&](FuncName) { return Ret{}; },
964 [&](MethodName) { return Ret{}; },
965 [&](FuncInfo* fi) { return fi->func; },
966 [&](const MethTabEntryPair* mte) { return mte->second.func; },
967 [&](FuncFamily* /*fa*/) { return Ret{}; }
971 bool Func::isFoldable() const {
972 return match<bool>(
973 val,
974 [&](FuncName) { return false; },
975 [&](MethodName) { return false; },
976 [&](FuncInfo* fi) {
977 return fi->func->attrs & AttrIsFoldable;
979 [&](const MethTabEntryPair* mte) {
980 return mte->second.func->attrs & AttrIsFoldable;
982 [&](FuncFamily* fa) { return false; }
986 bool Func::couldHaveReifiedGenerics() const {
987 return match<bool>(
988 val,
989 [&](FuncName s) { return true; },
990 [&](MethodName) { return true; },
991 [&](FuncInfo* fi) { return fi->func->isReified; },
992 [&](const MethTabEntryPair* mte) {
993 return mte->second.func->isReified;
995 [&](FuncFamily* fa) {
996 for (auto const pf : fa->possibleFuncs()) {
997 if (pf->second.func->isReified) return true;
999 return false;
1004 bool Func::mightCareAboutDynCalls() const {
1005 if (RuntimeOption::EvalNoticeOnBuiltinDynamicCalls && mightBeBuiltin()) {
1006 return true;
1008 auto const mightCareAboutFuncs =
1009 RuntimeOption::EvalForbidDynamicCallsToFunc > 0;
1010 auto const mightCareAboutInstMeth =
1011 RuntimeOption::EvalForbidDynamicCallsToInstMeth > 0;
1012 auto const mightCareAboutClsMeth =
1013 RuntimeOption::EvalForbidDynamicCallsToClsMeth > 0;
1015 return match<bool>(
1016 val,
1017 [&](FuncName) { return mightCareAboutFuncs; },
1018 [&](MethodName) {
1019 return mightCareAboutClsMeth || mightCareAboutInstMeth;
1021 [&](FuncInfo* fi) {
1022 return dyn_call_error_level(fi->func) > 0;
1024 [&](const MethTabEntryPair* mte) {
1025 return dyn_call_error_level(mte->second.func) > 0;
1027 [&](FuncFamily* fa) {
1028 for (auto const pf : fa->possibleFuncs()) {
1029 if (dyn_call_error_level(pf->second.func) > 0)
1030 return true;
1032 return false;
1037 bool Func::mightBeBuiltin() const {
1038 return match<bool>(
1039 val,
1040 // Builtins are always uniquely resolvable unless renaming is
1041 // involved.
1042 [&](FuncName s) { return s.renamable; },
1043 [&](MethodName) { return true; },
1044 [&](FuncInfo* fi) { return fi->func->attrs & AttrBuiltin; },
1045 [&](const MethTabEntryPair* mte) {
1046 return mte->second.func->attrs & AttrBuiltin;
1048 [&](FuncFamily* fa) {
1049 for (auto const pf : fa->possibleFuncs()) {
1050 if (pf->second.func->attrs & AttrBuiltin) return true;
1052 return false;
1057 namespace {
1059 uint32_t numNVArgs(const php::Func& f) {
1060 uint32_t cnt = f.params.size();
1061 return cnt && f.params[cnt - 1].isVariadic ? cnt - 1 : cnt;
1066 uint32_t Func::minNonVariadicParams() const {
1067 return match<uint32_t>(
1068 val,
1069 [&] (FuncName) { return 0; },
1070 [&] (MethodName) { return 0; },
1071 [&] (FuncInfo* fi) { return numNVArgs(*fi->func); },
1072 [&] (const MethTabEntryPair* mte) { return numNVArgs(*mte->second.func); },
1073 [&] (FuncFamily* fa) {
1074 auto c = std::numeric_limits<uint32_t>::max();
1075 for (auto const pf : fa->possibleFuncs()) {
1076 c = std::min(c, numNVArgs(*pf->second.func));
1078 return c;
1083 uint32_t Func::maxNonVariadicParams() const {
1084 return match<uint32_t>(
1085 val,
1086 [&] (FuncName) { return std::numeric_limits<uint32_t>::max(); },
1087 [&] (MethodName) { return std::numeric_limits<uint32_t>::max(); },
1088 [&] (FuncInfo* fi) { return numNVArgs(*fi->func); },
1089 [&] (const MethTabEntryPair* mte) { return numNVArgs(*mte->second.func); },
1090 [&] (FuncFamily* fa) {
1091 uint32_t c = 0;
1092 for (auto const pf : fa->possibleFuncs()) {
1093 c = std::max(c, numNVArgs(*pf->second.func));
1095 return c;
1100 std::string show(const Func& f) {
1101 auto ret = f.name()->toCppString();
1102 match<void>(
1103 f.val,
1104 [&](Func::FuncName s) { if (s.renamable) ret += '?'; },
1105 [&](Func::MethodName) {},
1106 [&](FuncInfo*) { ret += "*"; },
1107 [&](const MethTabEntryPair*) { ret += "*"; },
1108 [&](FuncFamily*) { ret += "+"; }
1110 return ret;
1115 //////////////////////////////////////////////////////////////////////
1117 using IfaceSlotMap = hphp_hash_map<const php::Class*, Slot>;
1118 using ConstInfoConcurrentMap =
1119 tbb::concurrent_hash_map<SString, ConstInfo, StringDataHashCompare>;
1121 template <typename T>
1122 struct ResTypeHelper;
1124 template <>
1125 struct ResTypeHelper<res::Class> {
1126 using InfoT = ClassInfo;
1127 using InfoMapT = ISStringToOneT<InfoT*>;
1128 using OtherT = res::Record;
1129 static std::string name() { return "class"; }
1132 template <>
1133 struct ResTypeHelper<res::Record> {
1134 using InfoT = RecordInfo;
1135 using InfoMapT = ISStringToOneT<InfoT*>;
1136 using OtherT = res::Class;
1137 static std::string name() { return "record"; }
1140 struct Index::IndexData {
1141 explicit IndexData(Index* index) : m_index{index} {}
1142 IndexData(const IndexData&) = delete;
1143 IndexData& operator=(const IndexData&) = delete;
1144 ~IndexData() {
1145 if (compute_iface_vtables.joinable()) {
1146 compute_iface_vtables.join();
1150 Index* m_index;
1152 bool frozen{false};
1153 bool ever_frozen{false};
1155 std::unique_ptr<ArrayTypeTable::Builder> arrTableBuilder;
1157 ISStringToOneT<const php::Class*> classes;
1158 SStringToMany<const php::Func> methods;
1159 SStringToOneT<uint64_t> method_inout_params_by_name;
1160 ISStringToOneT<const php::Func*> funcs;
1161 ISStringToOneT<const php::TypeAlias*> typeAliases;
1162 ISStringToOneT<const php::Class*> enums;
1163 SStringToOneT<const php::Constant*> constants;
1164 ISStringToOneT<const php::Record*> records;
1166 // Map from each class to all the closures that are allocated in
1167 // functions of that class.
1168 hphp_hash_map<
1169 const php::Class*,
1170 CompactVector<const php::Class*>
1171 > classClosureMap;
1173 hphp_hash_map<
1174 const php::Class*,
1175 hphp_fast_set<const php::Func*>
1176 > classExtraMethodMap;
1179 * Map from each class name to ClassInfo objects if one exists.
1181 * It may not exists if we would fatal when defining the class. That could
1182 * happen for if the inheritance is bad or __Sealed or other things.
1184 ISStringToOneT<ClassInfo*> classInfo;
1187 * All the ClassInfos, sorted topologically (ie all the parents,
1188 * interfaces and traits used by the ClassInfo at index K will have
1189 * indices less than K). This mostly drops out of the way ClassInfos
1190 * are created; it would be hard to create the ClassInfos for the
1191 * php::Class X (or even know how many to create) without knowing
1192 * all the ClassInfos that were created for X's dependencies.
1194 std::vector<std::unique_ptr<ClassInfo>> allClassInfos;
1197 * Map from each record name to RecordInfo objects if one exists.
1199 * It may not exists if we would fatal when defining the record.
1201 ISStringToOneT<RecordInfo*> recordInfo;
1204 * All the RecordInfos, sorted topologically (ie all the parents of
1205 * RecordInfo at index K will have indices less than K).
1206 * This mostly drops out of the way RecordInfos are created;
1207 * it would be hard to create the RecordInfos for the
1208 * php::Record X (or even know how many to create) without knowing
1209 * all the RecordInfos that were created for X's dependencies.
1211 std::vector<std::unique_ptr<RecordInfo>> allRecordInfos;
1213 std::vector<FuncInfo> funcInfo;
1215 // Private instance and static property types are stored separately
1216 // from ClassInfo, because you don't need to resolve a class to get
1217 // at them.
1218 hphp_hash_map<
1219 const php::Class*,
1220 PropState
1221 > privatePropInfo;
1222 hphp_hash_map<
1223 const php::Class*,
1224 PropState
1225 > privateStaticPropInfo;
1228 * Public static property information:
1231 // If this is true, we don't know anything about public static properties and
1232 // must be pessimistic. We start in this state (before we've analyzed any
1233 // mutations) and remain in it if we see a mutation where both the name and
1234 // class are unknown.
1235 bool allPublicSPropsUnknown{true};
1237 // Best known types for public static properties where we knew the name, but
1238 // not the class. The type we're allowed to assume for a public static
1239 // property is the union of the ClassInfo-specific type with the unknown class
1240 // type that's stored here. The second value is the number of times the type
1241 // has been refined.
1242 hphp_hash_map<SString, std::pair<Type, uint32_t>> unknownClassSProps;
1244 // The set of gathered public static property mutations for each function. The
1245 // inferred types for the public static properties is the union of all these
1246 // mutations. If a function is not analyzed in a particular analysis round,
1247 // its mutations are left unchanged from the previous round.
1248 folly::ConcurrentHashMap<const php::Func*,
1249 PublicSPropMutations> publicSPropMutations;
1251 // All FuncFamilies. These are stored globally so we can avoid
1252 // generating duplicates.
1253 struct FuncFamilyPtrHasher {
1254 using is_transparent = void;
1255 size_t operator()(const std::unique_ptr<FuncFamily>& ff) const {
1256 return PFuncVecHasher{}(ff->possibleFuncs());
1258 size_t operator()(const FuncFamily::PFuncVec& pf) const {
1259 return PFuncVecHasher{}(pf);
1262 struct FuncFamilyPtrEquals {
1263 using is_transparent = void;
1264 bool operator()(const std::unique_ptr<FuncFamily>& a,
1265 const std::unique_ptr<FuncFamily>& b) const {
1266 return a->possibleFuncs() == b->possibleFuncs();
1268 bool operator()(const FuncFamily::PFuncVec& pf,
1269 const std::unique_ptr<FuncFamily>& ff) const {
1270 return pf == ff->possibleFuncs();
1273 folly_concurrent_hash_map_simd<
1274 std::unique_ptr<FuncFamily>,
1275 bool,
1276 FuncFamilyPtrHasher,
1277 FuncFamilyPtrEquals
1278 > funcFamilies;
1281 * Map from interfaces to their assigned vtable slots, computed in
1282 * compute_iface_vtables().
1284 IfaceSlotMap ifaceSlotMap;
1286 hphp_hash_map<
1287 const php::Class*,
1288 CompactVector<Type>
1289 > closureUseVars;
1291 bool useClassDependencies{};
1292 DepMap dependencyMap;
1295 * If a function is effect-free when called with a particular set of
1296 * literal arguments, and produces a literal result, there will be
1297 * an entry here representing the type.
1299 * The map isn't just an optimization; we can't call
1300 * analyze_func_inline during the optimization phase, because the
1301 * bytecode could be modified while we do so.
1303 ContextRetTyMap foldableReturnTypeMap;
1306 * Call-context sensitive return types are cached here. This is not
1307 * an optimization.
1309 * The reason we need to retain this information about the
1310 * calling-context-sensitive return types is that once the Index is
1311 * frozen (during the final optimization pass), calls to
1312 * lookup_return_type with a CallContext can't look at the bytecode
1313 * bodies of functions other than the calling function. So we need
1314 * to know what we determined the last time we were alloewd to do
1315 * that so we can return it again.
1317 ContextRetTyMap contextualReturnTypes{};
1319 std::thread compute_iface_vtables;
1321 template<typename T>
1322 const typename ResTypeHelper<T>::InfoMapT& infoMap() const;
1325 template<>
1326 const typename ResTypeHelper<res::Class>::InfoMapT&
1327 Index::IndexData::infoMap<res::Class>() const {
1328 return classInfo;
1330 template<>
1331 const typename ResTypeHelper<res::Record>::InfoMapT&
1332 Index::IndexData::infoMap<res::Record>() const {
1333 return recordInfo;
1336 //////////////////////////////////////////////////////////////////////
1338 namespace {
1340 //////////////////////////////////////////////////////////////////////
1342 using IndexData = Index::IndexData;
1344 std::mutex closure_use_vars_mutex;
1345 std::mutex private_propstate_mutex;
1347 DependencyContext make_dep(const php::Func* func) {
1348 return DependencyContext{DependencyContextType::Func, func};
1350 DependencyContext make_dep(const php::Class* cls) {
1351 return DependencyContext{DependencyContextType::Class, cls};
1353 DependencyContext make_dep(SString name) {
1354 return DependencyContext{DependencyContextType::PropName, name};
1356 DependencyContext make_dep(const FuncFamily* family) {
1357 return DependencyContext{DependencyContextType::FuncFamily, family};
1360 DependencyContext dep_context(IndexData& data, const Context& ctx) {
1361 if (!ctx.cls || !data.useClassDependencies) return make_dep(ctx.func);
1362 auto const cls = ctx.cls->closureContextCls ?
1363 ctx.cls->closureContextCls : ctx.cls;
1364 if (is_used_trait(*cls)) return make_dep(ctx.func);
1365 return make_dep(cls);
1368 template <typename T>
1369 void add_dependency(IndexData& data,
1370 T src,
1371 const Context& dst,
1372 Dep newMask) {
1373 if (data.frozen) return;
1375 auto d = dep_context(data, dst);
1376 DepMap::accessor acc;
1377 data.dependencyMap.insert(acc, make_dep(src));
1378 auto& current = acc->second[d];
1379 current = current | newMask;
1382 std::mutex func_info_mutex;
1384 FuncInfo* create_func_info(IndexData& data, const php::Func* f) {
1385 auto fi = &data.funcInfo[f->idx];
1386 if (UNLIKELY(fi->func == nullptr)) {
1387 if (f->nativeInfo) {
1388 std::lock_guard<std::mutex> g{func_info_mutex};
1389 if (fi->func) {
1390 assertx(fi->func == f);
1391 return fi;
1393 // We'd infer this anyway when we look at the bytecode body
1394 // (NativeImpl) for the HNI function, but just initializing it
1395 // here saves on whole-program iterations.
1396 fi->returnTy = native_function_return_type(f);
1398 fi->func = f;
1401 assertx(fi->func == f);
1402 return fi;
1405 FuncInfo* func_info(IndexData& data, const php::Func* f) {
1406 auto const fi = &data.funcInfo[f->idx];
1407 return fi;
1410 template <typename T>
1411 void find_deps(IndexData& data,
1412 T src,
1413 Dep mask,
1414 DependencyContextSet& deps) {
1415 auto const srcDep = make_dep(src);
1418 DepMap::const_accessor acc;
1419 if (data.dependencyMap.find(acc, srcDep)) {
1420 for (auto const& kv : acc->second) {
1421 if (has_dep(kv.second, mask)) deps.insert(kv.first);
1426 // If this is a Func dep, we need to also check if any FuncFamily
1427 // dependencies need to be added.
1428 if (srcDep.tag() != DependencyContextType::Func) return;
1430 auto const fi = func_info(data, static_cast<const php::Func*>(srcDep.ptr()));
1431 if (!fi->func) return;
1433 // Add any associated FuncFamilies
1434 for (auto const ff : fi->families) {
1435 DepMap::const_accessor acc;
1436 if (data.dependencyMap.find(acc, make_dep(ff))) {
1437 for (auto const& kv : acc->second) {
1438 if (has_dep(kv.second, mask)) deps.insert(kv.first);
1444 struct TraitMethod {
1445 using class_type = const ClassInfo*;
1446 using method_type = const php::Func*;
1448 TraitMethod(class_type trait_, method_type method_, Attr modifiers_)
1449 : trait(trait_)
1450 , method(method_)
1451 , modifiers(modifiers_)
1454 class_type trait;
1455 method_type method;
1456 Attr modifiers;
1459 struct TMIOps {
1460 using string_type = LSString;
1461 using class_type = TraitMethod::class_type;
1462 using method_type = TraitMethod::method_type;
1464 struct TMIException : std::exception {
1465 explicit TMIException(std::string msg) : msg(msg) {}
1466 const char* what() const noexcept override { return msg.c_str(); }
1467 private:
1468 std::string msg;
1471 // Return the name for the trait class.
1472 static const string_type clsName(class_type traitCls) {
1473 return traitCls->cls->name;
1476 // Return the name for the trait method.
1477 static const string_type methName(method_type meth) {
1478 return meth->name;
1481 // Is-a methods.
1482 static bool isTrait(class_type traitCls) {
1483 return traitCls->cls->attrs & AttrTrait;
1485 static bool isAbstract(Attr modifiers) {
1486 return modifiers & AttrAbstract;
1489 // Whether to exclude methods with name `methName' when adding.
1490 static bool exclude(string_type methName) {
1491 return Func::isSpecial(methName);
1494 // TraitMethod constructor.
1495 static TraitMethod traitMethod(class_type traitCls,
1496 method_type traitMeth,
1497 const PreClass::TraitAliasRule& rule) {
1498 return TraitMethod { traitCls, traitMeth, rule.modifiers() };
1501 // Register a trait alias once the trait class is found.
1502 static void addTraitAlias(const ClassInfo* /*cls*/,
1503 const PreClass::TraitAliasRule& /*rule*/,
1504 class_type /*traitCls*/) {
1505 // purely a runtime thing... nothing to do
1508 // Trait class/method finders.
1509 static class_type findSingleTraitWithMethod(class_type cls,
1510 string_type origMethName) {
1511 class_type traitCls = nullptr;
1513 for (auto const t : cls->usedTraits) {
1514 // Note: m_methods includes methods from parents/traits recursively.
1515 if (t->methods.count(origMethName)) {
1516 if (traitCls != nullptr) {
1517 return nullptr;
1519 traitCls = t;
1522 return traitCls;
1525 static class_type findTraitClass(class_type cls,
1526 string_type traitName) {
1527 for (auto const t : cls->usedTraits) {
1528 if (traitName->isame(t->cls->name)) return t;
1530 return nullptr;
1533 static method_type findTraitMethod(class_type traitCls,
1534 string_type origMethName) {
1535 auto it = traitCls->methods.find(origMethName);
1536 if (it == traitCls->methods.end()) return nullptr;
1537 return it->second.func;
1540 // Errors.
1541 static void errorUnknownMethod(string_type methName) {
1542 throw TMIException(folly::sformat("Unknown method '{}'", methName));
1544 static void errorUnknownTrait(string_type traitName) {
1545 throw TMIException(folly::sformat("Unknown trait '{}'", traitName));
1547 static void errorDuplicateMethod(class_type cls,
1548 string_type methName,
1549 const std::list<TraitMethod>&) {
1550 auto const& m = cls->cls->methods;
1551 if (std::find_if(m.begin(), m.end(),
1552 [&] (auto const& f) {
1553 return f->name->isame(methName);
1554 }) != m.end()) {
1555 // the duplicate methods will be overridden by the class method.
1556 return;
1558 throw TMIException(folly::sformat("DuplicateMethod: {}", methName));
1560 static void errorInconsistentInsteadOf(class_type cls,
1561 string_type methName) {
1562 throw TMIException(folly::sformat("InconsistentInsteadOf: {} {}",
1563 methName, cls->cls->name));
1565 static void errorMultiplyExcluded(string_type traitName,
1566 string_type methName) {
1567 throw TMIException(folly::sformat("MultiplyExcluded: {}::{}",
1568 traitName, methName));
1572 using TMIData = TraitMethodImportData<TraitMethod,
1573 TMIOps>;
1575 template<typename T>
1576 struct PreResolveUpdates {
1577 TinyVector<std::unique_ptr<T>> newInfos;
1578 TinyVector<T*> updateDeps;
1580 struct CnsHash {
1581 size_t operator()(const ClassInfo::ConstIndex& cns) const {
1582 return hash_int64_pair((uintptr_t)cns.cls, cns.idx);
1585 struct CnsEquals {
1586 bool operator()(const ClassInfo::ConstIndex& cns1,
1587 const ClassInfo::ConstIndex& cns2) const {
1588 return
1589 cns1.cls == cns2.cls &&
1590 cns1.idx == cns2.idx;
1594 hphp_fast_set<ClassInfo::ConstIndex, CnsHash, CnsEquals> removeNoOverride;
1596 hphp_hash_map<
1597 const php::Class*,
1598 hphp_fast_set<const php::Func*>
1599 > extraMethods;
1600 hphp_hash_map<
1601 const php::Class*,
1602 CompactVector<const php::Class*>
1603 > closures;
1604 CompactVector<const php::Class*> newClosures;
1605 CompactVector<
1606 std::tuple<std::unique_ptr<php::Class>, php::Unit*, uint32_t>
1607 > newClasses;
1609 struct UnitPtrHashCompare {
1610 bool equal(const php::Unit* u1, const php::Unit* u2) const {
1611 return u1 == u2;
1613 size_t hash(const php::Unit* u) const {
1614 return pointer_hash<const php::Unit>{}(u);
1618 using UnitNumClasses =
1619 tbb::concurrent_hash_map<const php::Unit*, uint32_t, UnitPtrHashCompare>;
1620 UnitNumClasses* numClasses = nullptr;
1622 uint32_t nextClass(const php::Unit& unit) {
1623 typename UnitNumClasses::accessor acc;
1624 numClasses->insert(
1625 acc,
1626 std::make_pair(&unit, unit.classes.size())
1628 return acc->second++;
1632 using RecPreResolveUpdates = PreResolveUpdates<RecordInfo>;
1633 using ClsPreResolveUpdates = PreResolveUpdates<ClassInfo>;
1636 * Make a flattened table of the constants on this class.
1638 bool build_class_constants(ClassInfo* cinfo, ClsPreResolveUpdates& updates) {
1639 auto const removeNoOverride = [&] (ClassInfo::ConstIndex cns) {
1640 // During hhbbc/parse, all constants are pre-set to NoOverride
1641 ITRACE(2, "Removing NoOverride on {}::{}\n", cns->cls->name, cns->name);
1642 if (cns->isNoOverride) updates.removeNoOverride.emplace(cns);
1645 if (cinfo->parent) cinfo->clsConstants = cinfo->parent->clsConstants;
1647 auto const add = [&] (const ClassInfo::ConstIndex& cns, bool fromTrait) {
1648 auto insert = cinfo->clsConstants.emplace(cns->name, cns);
1649 if (insert.second) {
1650 if (fromTrait) {
1651 cinfo->preResolveState->constsFromTraits.emplace(cns->name);
1653 return true;
1655 auto& existing = insert.first->second;
1657 // Same constant (from an interface via two different paths) is ok
1658 if (existing->cls == cns->cls) return true;
1660 if (existing->kind != cns->kind) {
1661 ITRACE(
1663 "build_class_constants failed for `{}' because `{}' was defined by "
1664 "`{}' as a {} and by `{}' as a {}\n",
1665 cinfo->cls->name,
1666 cns->name,
1667 cns->cls->name,
1668 ConstModifiers::show(cns->kind),
1669 existing->cls->name,
1670 ConstModifiers::show(existing->kind)
1672 return false;
1675 // Ignore abstract constants
1676 if (cns->isAbstract) return true;
1678 if (existing->val) {
1679 // A constant from a declared interface collides with a constant
1680 // (Excluding constants from interfaces a trait implements)
1681 // Need this check otherwise constants from traits that conflict with
1682 // declared interfaces will silently lose and not conflict in the runtime
1683 // Type and Context constants can be overriden.
1684 if (cns->kind != ConstModifiers::Kind::Type &&
1685 cns->kind != ConstModifiers::Kind::Context &&
1686 existing->cls->attrs & AttrInterface &&
1687 !(cns->cls->attrs & AttrInterface && fromTrait)) {
1688 for (auto const& interface : cinfo->declInterfaces) {
1689 if (existing->cls == interface->cls) {
1690 ITRACE(
1692 "build_class_constants failed for `{}' because "
1693 "`{}' was defined by both `{}' and `{}'\n",
1694 cinfo->cls->name,
1695 cns->name,
1696 cns->cls->name,
1697 existing->cls->name
1699 return false;
1704 // Constants from traits silently lose
1705 if (fromTrait) {
1706 removeNoOverride(cns);
1707 return true;
1710 // A constant from an interface or from an included enum collides
1711 // with an existing constant.
1712 if (cns->cls->attrs & (AttrInterface | AttrEnum | AttrEnumClass)) {
1713 ITRACE(
1715 "build_class_constants failed for `{}' because "
1716 "`{}' was defined by both `{}' and `{}'\n",
1717 cinfo->cls->name,
1718 cns->name,
1719 cns->cls->name,
1720 existing->cls->name
1722 return false;
1726 removeNoOverride(existing);
1727 existing = cns;
1728 if (fromTrait) {
1729 cinfo->preResolveState->constsFromTraits.emplace(cns->name);
1730 } else {
1731 cinfo->preResolveState->constsFromTraits.erase(cns->name);
1733 return true;
1736 for (auto const iface : cinfo->declInterfaces) {
1737 for (auto const& cns : iface->clsConstants) {
1738 if (!add(cns.second,
1739 iface->preResolveState->constsFromTraits.count(cns.first))) {
1740 return false;
1745 for (uint32_t idx = 0; idx < cinfo->cls->constants.size(); ++idx) {
1746 auto const cns = ClassInfo::ConstIndex { cinfo->cls, idx };
1747 if (cinfo->cls->attrs & AttrTrait) removeNoOverride(cns);
1748 if (!add(cns, false)) return false;
1751 for (auto const trait : cinfo->usedTraits) {
1752 for (auto const& cns : trait->clsConstants) {
1753 if (!add(cns.second, true)) return false;
1757 for (auto const ienum : cinfo->includedEnums) {
1758 for (auto const& cns : ienum->clsConstants) {
1759 if (!add(cns.second, true)) return false;
1763 auto const addTraitConst = [&] (const php::Const& c) {
1765 * Only copy in constants that win. Otherwise, in the runtime, if
1766 * we have a constant from an interface implemented by a trait
1767 * that wins over this fromTrait constant, we won't know which
1768 * trait it came from, and therefore won't know which constant
1769 * should win. Dropping losing constants here works because if
1770 * they fatal with constants in declared interfaces, we catch that
1771 * above.
1773 auto const& existing = cinfo->clsConstants.find(c.name);
1774 if (existing->second->cls == c.cls) {
1775 cinfo->traitConsts.emplace_back(c);
1776 cinfo->traitConsts.back().isFromTrait = true;
1779 for (auto const t : cinfo->usedTraits) {
1780 for (auto const& c : t->cls->constants) addTraitConst(c);
1781 for (auto const& c : t->traitConsts) addTraitConst(c);
1784 return true;
1787 bool build_class_impl_interfaces(ClassInfo* cinfo) {
1788 if (cinfo->parent) cinfo->implInterfaces = cinfo->parent->implInterfaces;
1790 for (auto const ienum : cinfo->includedEnums) {
1791 cinfo->implInterfaces.insert(
1792 ienum->implInterfaces.begin(),
1793 ienum->implInterfaces.end()
1797 for (auto const iface : cinfo->declInterfaces) {
1798 cinfo->implInterfaces.insert(
1799 iface->implInterfaces.begin(),
1800 iface->implInterfaces.end()
1804 for (auto const trait : cinfo->usedTraits) {
1805 cinfo->implInterfaces.insert(
1806 trait->implInterfaces.begin(),
1807 trait->implInterfaces.end()
1811 if (cinfo->cls->attrs & AttrInterface) {
1812 cinfo->implInterfaces.emplace(cinfo->cls->name, cinfo);
1815 return true;
1818 bool build_class_properties(ClassInfo* cinfo) {
1819 if (cinfo->parent) {
1820 cinfo->preResolveState->pbuildNoTrait =
1821 cinfo->parent->preResolveState->pbuildNoTrait;
1822 cinfo->preResolveState->pbuildTrait =
1823 cinfo->parent->preResolveState->pbuildNoTrait;
1826 auto const add = [&] (auto& m,
1827 SString name,
1828 const php::Prop& p,
1829 const ClassInfo* cls,
1830 bool add) {
1831 auto res = m.emplace(name, std::make_pair(p, cls));
1832 if (res.second) {
1833 if (add) cinfo->traitProps.emplace_back(p);
1834 return true;
1837 auto const& prev = res.first->second.first;
1839 if (cinfo == res.first->second.second) {
1840 if ((prev.attrs ^ p.attrs) &
1841 (AttrStatic | AttrPublic | AttrProtected | AttrPrivate) ||
1842 (!(p.attrs & AttrSystemInitialValue) &&
1843 !(prev.attrs & AttrSystemInitialValue) &&
1844 !Class::compatibleTraitPropInit(prev.val, p.val))) {
1845 ITRACE(2,
1846 "build_class_properties failed for `{}' because "
1847 "two declarations of `{}' at the same level had "
1848 "different attributes\n",
1849 cinfo->cls->name, p.name);
1850 return false;
1852 return true;
1855 if (!(prev.attrs & AttrPrivate)) {
1856 if ((prev.attrs ^ p.attrs) & AttrStatic) {
1857 ITRACE(2,
1858 "build_class_properties failed for `{}' because "
1859 "`{}' was defined both static and non-static\n",
1860 cinfo->cls->name, p.name);
1861 return false;
1863 if (p.attrs & AttrPrivate) {
1864 ITRACE(2,
1865 "build_class_properties failed for `{}' because "
1866 "`{}' was re-declared private\n",
1867 cinfo->cls->name, p.name);
1868 return false;
1870 if (p.attrs & AttrProtected && !(prev.attrs & AttrProtected)) {
1871 ITRACE(2,
1872 "build_class_properties failed for `{}' because "
1873 "`{}' was redeclared protected from public\n",
1874 cinfo->cls->name, p.name);
1875 return false;
1879 if (add) cinfo->traitProps.emplace_back(p);
1880 res.first->second = std::make_pair(p, cls);
1881 return true;
1884 auto const merge = [&] (const ClassInfo::PreResolveState& src) {
1885 for (auto const& p : src.pbuildNoTrait) {
1886 if (!add(cinfo->preResolveState->pbuildNoTrait, p.first,
1887 p.second.first, p.second.second, false)) {
1888 return false;
1891 for (auto const& p : src.pbuildTrait) {
1892 if (!add(cinfo->preResolveState->pbuildTrait, p.first,
1893 p.second.first, p.second.second, false)) {
1894 return false;
1897 return true;
1900 for (auto const iface : cinfo->declInterfaces) {
1901 if (!merge(*iface->preResolveState)) return false;
1904 for (auto const trait : cinfo->usedTraits) {
1905 if (!merge(*trait->preResolveState)) return false;
1908 for (auto const ienum : cinfo->includedEnums) {
1909 if (!merge(*ienum->preResolveState)) return false;
1912 if (!(cinfo->cls->attrs & AttrInterface)) {
1913 for (auto const& p : cinfo->cls->properties) {
1914 if (!add(cinfo->preResolveState->pbuildNoTrait,
1915 p.name, p, cinfo, false)) {
1916 return false;
1920 // There's no need to do this work if traits have been flattened
1921 // already, or if the top level class has no traits. In those
1922 // cases, we might be able to rule out some ClassInfo
1923 // instantiations, but it doesn't seem worth it.
1925 if (!(cinfo->cls->attrs & AttrNoExpandTrait)) {
1926 for (auto const trait : cinfo->usedTraits) {
1927 for (auto const& p : trait->cls->properties) {
1928 if (!add(cinfo->preResolveState->pbuildNoTrait,
1929 p.name, p, cinfo, true)) {
1930 return false;
1933 for (auto const& p : trait->traitProps) {
1934 if (!add(cinfo->preResolveState->pbuildNoTrait,
1935 p.name, p, cinfo, true)) {
1936 return false;
1943 return true;
1947 * Make a flattened table of the methods on this class.
1949 * Duplicate method names override parent methods, unless the parent method
1950 * is final and the class is not a __MockClass, in which case this class
1951 * definitely would fatal if ever defined.
1953 * Note: we're leaving non-overridden privates in their subclass method
1954 * table, here. This isn't currently "wrong", because calling it would be a
1955 * fatal, but note that resolve_method needs to be pretty careful about
1956 * privates and overriding in general.
1958 bool build_class_methods(const IndexData& index,
1959 ClassInfo* cinfo,
1960 ClsPreResolveUpdates& updates) {
1961 if (cinfo->cls->attrs & AttrInterface) return true;
1963 auto const methodOverride = [&] (auto& it,
1964 const php::Func* meth,
1965 Attr attrs,
1966 SString name) {
1967 if (it->second.func->attrs & AttrFinal) {
1968 if (!is_mock_class(cinfo->cls)) {
1969 ITRACE(2,
1970 "build_class_methods failed for `{}' because "
1971 "it tried to override final method `{}::{}'\n",
1972 cinfo->cls->name,
1973 it->second.func->cls->name, name);
1974 return false;
1977 ITRACE(9,
1978 " {}: overriding method {}::{} with {}::{}\n",
1979 cinfo->cls->name,
1980 it->second.func->cls->name, it->second.func->name,
1981 meth->cls->name, name);
1982 if (it->second.func->attrs & AttrPrivate) {
1983 it->second.hasPrivateAncestor = true;
1985 it->second.func = meth;
1986 it->second.attrs = attrs;
1987 it->second.hasAncestor = true;
1988 it->second.topLevel = true;
1989 if (it->first != name) {
1990 auto mte = it->second;
1991 cinfo->methods.erase(it);
1992 it = cinfo->methods.emplace(name, mte).first;
1994 return true;
1997 // If there's a parent, start by copying its methods
1998 if (auto const rparent = cinfo->parent) {
1999 for (auto& mte : rparent->methods) {
2000 // don't inherit the 86* methods.
2001 if (HPHP::Func::isSpecial(mte.first)) continue;
2002 auto const res = cinfo->methods.emplace(mte.first, mte.second);
2003 assertx(res.second);
2004 res.first->second.topLevel = false;
2005 ITRACE(9,
2006 " {}: inheriting method {}::{}\n",
2007 cinfo->cls->name,
2008 rparent->cls->name, mte.first);
2009 continue;
2013 uint32_t idx = cinfo->methods.size();
2015 // Now add our methods.
2016 for (auto& m : cinfo->cls->methods) {
2017 auto res = cinfo->methods.emplace(
2018 m->name,
2019 MethTabEntry { m.get(), m->attrs, false, true }
2021 if (res.second) {
2022 res.first->second.idx = idx++;
2023 ITRACE(9,
2024 " {}: adding method {}::{}\n",
2025 cinfo->cls->name,
2026 cinfo->cls->name, m->name);
2027 continue;
2029 if (m->attrs & AttrTrait && m->attrs & AttrAbstract) {
2030 // abstract methods from traits never override anything.
2031 continue;
2033 if (!methodOverride(res.first, m.get(), m->attrs, m->name)) return false;
2036 // If our traits were previously flattened, we're done.
2037 if (cinfo->cls->attrs & AttrNoExpandTrait) return true;
2039 try {
2040 TMIData tmid;
2041 for (auto const t : cinfo->usedTraits) {
2042 std::vector<const MethTabEntryPair*> methods(t->methods.size());
2043 for (auto& m : t->methods) {
2044 if (HPHP::Func::isSpecial(m.first)) continue;
2045 assertx(!methods[m.second.idx]);
2046 methods[m.second.idx] = mteFromElm(m);
2048 for (auto const m : methods) {
2049 if (!m) continue;
2050 TraitMethod traitMethod { t, m->second.func, m->second.attrs };
2051 tmid.add(traitMethod, m->first);
2053 if (auto const it = index.classClosureMap.find(t->cls);
2054 it != index.classClosureMap.end()) {
2055 for (auto const& c : it->second) {
2056 auto const invoke = find_method(c, s_invoke.get());
2057 assertx(invoke);
2058 updates.extraMethods[cinfo->cls].emplace(invoke);
2063 for (auto const& precRule : cinfo->cls->traitPrecRules) {
2064 tmid.applyPrecRule(precRule, cinfo);
2066 for (auto const& aliasRule : cinfo->cls->traitAliasRules) {
2067 tmid.applyAliasRule(aliasRule, cinfo);
2069 auto traitMethods = tmid.finish(cinfo);
2070 // Import the methods.
2071 for (auto const& mdata : traitMethods) {
2072 auto const method = mdata.tm.method;
2073 auto attrs = mdata.tm.modifiers;
2074 if (attrs == AttrNone) {
2075 attrs = method->attrs;
2076 } else {
2077 Attr attrMask = (Attr)(AttrPublic | AttrProtected | AttrPrivate |
2078 AttrAbstract | AttrFinal);
2079 attrs = (Attr)((attrs & attrMask) |
2080 (method->attrs & ~attrMask));
2082 auto res = cinfo->methods.emplace(
2083 mdata.name,
2084 MethTabEntry { method, attrs, false, true }
2086 if (res.second) {
2087 res.first->second.idx = idx++;
2088 ITRACE(9,
2089 " {}: adding trait method {}::{} as {}\n",
2090 cinfo->cls->name,
2091 method->cls->name, method->name, mdata.name);
2092 } else {
2093 if (attrs & AttrAbstract) continue;
2094 if (res.first->second.func->cls == cinfo->cls) continue;
2095 if (!methodOverride(res.first, method, attrs, mdata.name)) {
2096 return false;
2098 res.first->second.idx = idx++;
2100 updates.extraMethods[cinfo->cls].emplace(method);
2102 } catch (TMIOps::TMIException& ex) {
2103 ITRACE(2,
2104 "build_class_methods failed for `{}' importing traits: {}\n",
2105 cinfo->cls->name, ex.what());
2106 return false;
2109 return true;
2112 const StaticString s___Sealed("__Sealed");
2114 bool enforce_in_maybe_sealed_parent_whitelist(
2115 const ClassInfo* cls,
2116 const ClassInfo* parent) {
2117 // if our parent isn't sealed, then we're fine.
2118 if (!parent || !(parent->cls->attrs & AttrSealed)) return true;
2119 const UserAttributeMap& parent_attrs = parent->cls->userAttributes;
2120 assertx(parent_attrs.find(s___Sealed.get()) != parent_attrs.end());
2121 const auto& parent_sealed_attr = parent_attrs.find(s___Sealed.get())->second;
2122 bool in_sealed_whitelist = false;
2123 IterateV(parent_sealed_attr.m_data.parr,
2124 [&in_sealed_whitelist, cls](TypedValue v) -> bool {
2125 if (v.m_data.pstr->same(cls->cls->name)) {
2126 in_sealed_whitelist = true;
2127 return true;
2129 return false;
2131 return in_sealed_whitelist;
2135 * This function return false if instantiating the cinfo would be a
2136 * fatal at runtime.
2138 bool build_cls_info(const IndexData& index,
2139 ClassInfo* cinfo,
2140 ClsPreResolveUpdates& updates) {
2141 if (!enforce_in_maybe_sealed_parent_whitelist(cinfo, cinfo->parent)) {
2142 return false;
2145 for (auto const iface : cinfo->declInterfaces) {
2146 if (!enforce_in_maybe_sealed_parent_whitelist(cinfo, iface)) {
2147 return false;
2150 for (auto const trait : cinfo->usedTraits) {
2151 if (!enforce_in_maybe_sealed_parent_whitelist(cinfo, trait)) {
2152 return false;
2155 for (auto const ienum : cinfo->includedEnums) {
2156 if (!enforce_in_maybe_sealed_parent_whitelist(cinfo, ienum)) {
2157 return false;
2161 if (!build_class_constants(cinfo, updates)) return false;
2162 if (!build_class_impl_interfaces(cinfo)) return false;
2163 if (!build_class_properties(cinfo)) return false;
2164 if (!build_class_methods(index, cinfo, updates)) return false;
2165 return true;
2168 template <typename T>
2169 static const char* filename_from_symbol(const T* t) {
2170 auto unit = t->unit;
2171 if (!unit) return "BUILTIN";
2172 return unit->filename->data();
2175 template <typename T, typename R>
2176 static void add_symbol(R&& map, const T* t, const char* type) {
2177 assertx(t->attrs & AttrUnique);
2178 assertx(t->attrs & AttrPersistent);
2180 auto ret = map.insert({t->name, t});
2181 if (!ret.second) {
2182 throw Index::NonUniqueSymbolException(folly::sformat(
2183 "More than one {} with the name {}. In {} and {}", type,
2184 t->name->data(), filename_from_symbol(t), filename_from_symbol(ret.first->second)));
2188 template <typename T, typename E>
2189 static void validate_uniqueness(const T* t, E&& other_map) {
2190 auto iter = other_map.find(t->name);
2191 if (iter != other_map.end()) {
2192 throw Index::NonUniqueSymbolException(folly::sformat(
2193 "More than one symbol with the name {}. In {} and {}",
2194 t->name->data(), filename_from_symbol(t), filename_from_symbol(iter->second)));
2198 template <typename T, typename R, typename E, typename F>
2199 static void add_symbol(R&& map, const T* t, const char* type, E&& other_map1, F&& other_map2) {
2200 validate_uniqueness(t, std::forward<E>(other_map1));
2201 validate_uniqueness(t, std::forward<F>(other_map2));
2202 add_symbol(std::forward<R>(map), t, type);
2205 //////////////////////////////////////////////////////////////////////
2207 void add_system_constants_to_index(IndexData& index) {
2208 for (auto cnsPair : Native::getConstants()) {
2209 assertx(cnsPair.second.m_type != KindOfUninit ||
2210 cnsPair.second.dynamic());
2211 auto pc = new php::Constant { nullptr, cnsPair.first, cnsPair.second, AttrUnique | AttrPersistent };
2212 add_symbol(index.constants, pc, "constant");
2216 //////////////////////////////////////////////////////////////////////
2218 folly::Optional<uint32_t> func_num_inout(const php::Func* func) {
2219 if (!func->hasInOutArgs) return 0;
2220 uint32_t count = 0;
2221 for (auto& p : func->params) count += p.inout;
2222 return count;
2225 template<typename PossibleFuncRange>
2226 folly::Optional<uint32_t> num_inout_from_set(PossibleFuncRange range) {
2227 if (begin(range) == end(range)) return 0;
2229 struct FuncFind {
2230 using F = const php::Func*;
2231 static F get(std::pair<SString,F> p) { return p.second; }
2232 static F get(const MethTabEntryPair* mte) { return mte->second.func; }
2235 folly::Optional<uint32_t> num;
2236 for (auto const& item : range) {
2237 auto const n = func_num_inout(FuncFind::get(item));
2238 if (!n.hasValue()) return folly::none;
2239 if (num.hasValue() && n != num) return folly::none;
2240 num = n;
2242 return num;
2245 //////////////////////////////////////////////////////////////////////
2247 template<class T>
2248 struct PhpTypeHelper;
2250 template<>
2251 struct PhpTypeHelper<php::Class> {
2252 template<class Fn>
2253 static void process_bases(const php::Class* cls, Fn&& fn) {
2254 if (cls->parentName) fn(cls->parentName);
2255 for (auto& i : cls->interfaceNames) fn(i);
2256 for (auto& t : cls->usedTraitNames) fn(t);
2257 for (auto& t : cls->includedEnumNames) fn(t);
2260 static std::string name() { return "class"; }
2262 static void assert_bases(const IndexData&, const php::Class* cls);
2263 static void try_flatten_traits(const php::Program*, const IndexData&,
2264 const php::Class*, ClassInfo*,
2265 ClsPreResolveUpdates&);
2267 using Info = ClassInfo;
2270 template<>
2271 struct PhpTypeHelper<php::Record> {
2272 template<class Fn>
2273 static void process_bases(const php::Record* rec, Fn&& fn) {
2274 if (rec->parentName) fn(rec->parentName);
2277 static std::string name() { return "record"; }
2279 static void assert_bases(const IndexData&, const php::Record* rec);
2280 static void try_flatten_traits(const php::Program*, const IndexData&,
2281 const php::Record*, RecordInfo*,
2282 RecPreResolveUpdates&);
2284 using Info = RecordInfo;
2287 template<typename T>
2288 struct TypeInfoData {
2289 // Map from name to types that directly use that name (as parent,
2290 // interface or trait).
2291 hphp_hash_map<SString,
2292 CompactVector<const T*>,
2293 string_data_hash,
2294 string_data_isame> users;
2295 // Map from types to number of dependencies, used in
2296 // conjunction with users field above.
2297 hphp_hash_map<const T*, uint32_t> depCounts;
2299 uint32_t cqFront{};
2300 uint32_t cqBack{};
2301 std::vector<const T*> queue;
2302 bool hasPseudoCycles{};
2305 using ClassInfoData = TypeInfoData<php::Class>;
2306 using RecordInfoData = TypeInfoData<php::Record>;
2308 // We want const qualifiers on various index data structures for php
2309 // object pointers, but during index creation time we need to
2310 // manipulate some of their attributes (changing the representation).
2311 // This little wrapper keeps the const_casting out of the main line of
2312 // code below.
2313 void attribute_setter(const Attr& attrs, bool set, Attr attr) {
2314 attrSetter(const_cast<Attr&>(attrs), set, attr);
2317 void add_unit_to_index(IndexData& index, php::Unit& unit) {
2318 hphp_hash_map<
2319 const php::Class*,
2320 hphp_hash_set<const php::Class*>
2321 > closureMap;
2323 for (auto& c : unit.classes) {
2324 assertx(!(c->attrs & AttrNoOverride));
2326 if (c->attrs & AttrEnum) {
2327 add_symbol(index.enums, c.get(), "enum");
2330 add_symbol(index.classes, c.get(), "class", index.records, index.typeAliases);
2332 for (auto& m : c->methods) {
2333 attribute_setter(m->attrs, false, AttrNoOverride);
2334 index.methods.insert({m->name, m.get()});
2336 uint64_t refs = 0, cur = 1;
2337 bool anyInOut = false;
2338 for (auto& p : m->params) {
2339 if (p.inout) {
2340 refs |= cur;
2341 anyInOut = true;
2343 // It doesn't matter that we lose parameters beyond the 64th,
2344 // for those, we'll conservatively check everything anyway.
2345 cur <<= 1;
2347 if (anyInOut) {
2348 // Multiple methods with the same name will be combined in the same
2349 // cell, thus we use |=. This only makes sense in WholeProgram mode
2350 // since we use this field to check that no functions has its n-th
2351 // parameter as inout, which requires global knowledge.
2352 index.method_inout_params_by_name[m->name] |= refs;
2356 if (c->closureContextCls) {
2357 closureMap[c->closureContextCls].insert(c.get());
2361 if (!closureMap.empty()) {
2362 for (auto const& c1 : closureMap) {
2363 auto& s = index.classClosureMap[c1.first];
2364 for (auto const& c2 : c1.second) {
2365 s.push_back(c2);
2370 for (auto i = unit.funcs.begin(); i != unit.funcs.end();) {
2371 auto& f = *i;
2372 // Deduplicate meth_caller wrappers- We just take the first one we see.
2373 if (f->attrs & AttrIsMethCaller && index.funcs.count(f->name)) {
2374 unit.funcs.erase(i);
2375 continue;
2377 add_symbol(index.funcs, f.get(), "function");
2378 ++i;
2381 for (auto& ta : unit.typeAliases) {
2382 add_symbol(index.typeAliases, ta.get(), "type alias", index.classes, index.records);
2385 for (auto& c : unit.constants) {
2386 add_symbol(index.constants, c.get(), "constant");
2389 for (auto& rec : unit.records) {
2390 assertx(!(rec->attrs & AttrNoOverride));
2391 add_symbol(index.records, rec.get(), "record", index.classes, index.typeAliases);
2395 template<class T>
2396 using TypeInfo = typename std::conditional<std::is_same<T, php::Class>::value,
2397 ClassInfo, RecordInfo>::type;
2400 void PhpTypeHelper<php::Class>::assert_bases(const IndexData& index,
2401 const php::Class* cls) {
2402 if (cls->parentName) {
2403 assertx(index.classInfo.count(cls->parentName));
2405 for (DEBUG_ONLY auto& i : cls->interfaceNames) {
2406 assertx(index.classInfo.count(i));
2408 for (DEBUG_ONLY auto& t : cls->usedTraitNames) {
2409 assertx(index.classInfo.count(t));
2413 void PhpTypeHelper<php::Record>::assert_bases(const IndexData& index,
2414 const php::Record* rec) {
2415 if (rec->parentName) {
2416 assertx(index.recordInfo.count(rec->parentName));
2420 using ClonedClosureMap = hphp_hash_map<
2421 php::Class*,
2422 std::pair<std::unique_ptr<php::Class>, uint32_t>
2426 std::unique_ptr<php::Func> clone_meth_helper(
2427 php::Unit* unit,
2428 php::Class* newContext,
2429 const php::Func* origMeth,
2430 std::unique_ptr<php::Func> cloneMeth,
2431 std::atomic<uint32_t>& nextFuncId,
2432 ClsPreResolveUpdates& updates,
2433 ClonedClosureMap& clonedClosures
2436 std::unique_ptr<php::Class> clone_closure(php::Unit* unit,
2437 php::Class* newContext,
2438 php::Class* cls,
2439 std::atomic<uint32_t>& nextFuncId,
2440 ClsPreResolveUpdates& updates,
2441 ClonedClosureMap& clonedClosures) {
2442 auto clone = std::make_unique<php::Class>(*cls);
2443 assertx(clone->closureContextCls);
2444 clone->closureContextCls = newContext;
2445 clone->unit = newContext->unit;
2446 auto i = 0;
2447 for (auto& cloneMeth : clone->methods) {
2448 cloneMeth = clone_meth_helper(unit,
2449 clone.get(),
2450 cls->methods[i++].get(),
2451 std::move(cloneMeth),
2452 nextFuncId,
2453 updates,
2454 clonedClosures);
2455 if (!cloneMeth) return nullptr;
2457 return clone;
2460 std::unique_ptr<php::Func> clone_meth_helper(
2461 php::Unit* unit,
2462 php::Class* newContext,
2463 const php::Func* origMeth,
2464 std::unique_ptr<php::Func> cloneMeth,
2465 std::atomic<uint32_t>& nextFuncId,
2466 ClsPreResolveUpdates& preResolveUpdates,
2467 ClonedClosureMap& clonedClosures) {
2469 cloneMeth->cls = newContext;
2470 cloneMeth->idx = nextFuncId.fetch_add(1, std::memory_order_relaxed);
2471 if (!cloneMeth->originalFilename) {
2472 cloneMeth->originalFilename = origMeth->unit->filename;
2474 if (!cloneMeth->originalUnit) {
2475 cloneMeth->originalUnit = origMeth->unit;
2477 cloneMeth->unit = newContext->unit;
2479 if (!origMeth->hasCreateCl) return cloneMeth;
2481 auto const recordClosure = [&] (uint32_t* clsId) {
2482 auto const cls = origMeth->unit->classes[*clsId].get();
2483 auto& elm = clonedClosures[cls];
2484 if (!elm.first) {
2485 elm.first = clone_closure(unit,
2486 newContext->closureContextCls ?
2487 newContext->closureContextCls : newContext,
2488 cls, nextFuncId, preResolveUpdates,
2489 clonedClosures);
2490 if (!elm.first) return false;
2491 elm.second = preResolveUpdates.nextClass(*unit);
2493 *clsId = elm.second;
2494 return true;
2497 auto mf = php::WideFunc::mut(cloneMeth.get());
2498 hphp_fast_map<size_t, hphp_fast_map<size_t, uint32_t>> updates;
2500 for (size_t bid = 0; bid < mf.blocks().size(); bid++) {
2501 auto const b = mf.blocks()[bid].get();
2502 for (size_t ix = 0; ix < b->hhbcs.size(); ix++) {
2503 auto const& bc = b->hhbcs[ix];
2504 switch (bc.op) {
2505 case Op::CreateCl: {
2506 auto clsId = bc.CreateCl.arg2;
2507 if (!recordClosure(&clsId)) return nullptr;
2508 updates[bid][ix] = clsId;
2509 break;
2511 default:
2512 break;
2517 for (auto const& elm : updates) {
2518 auto const blk = mf.blocks()[elm.first].mutate();
2519 for (auto const& ix : elm.second) {
2520 blk->hhbcs[ix.first].CreateCl.arg2 = ix.second;
2524 return cloneMeth;
2527 std::unique_ptr<php::Func> clone_meth(php::Unit* unit,
2528 php::Class* newContext,
2529 const php::Func* origMeth,
2530 SString name,
2531 Attr attrs,
2532 std::atomic<uint32_t>& nextFuncId,
2533 ClsPreResolveUpdates& updates,
2534 ClonedClosureMap& clonedClosures) {
2536 auto cloneMeth = std::make_unique<php::Func>(*origMeth);
2537 cloneMeth->name = name;
2538 cloneMeth->attrs = attrs | AttrTrait;
2539 return clone_meth_helper(unit, newContext, origMeth, std::move(cloneMeth),
2540 nextFuncId, updates, clonedClosures);
2543 bool merge_inits(std::vector<std::unique_ptr<php::Func>>& clones,
2544 php::Unit* unit,
2545 ClassInfo* cinfo,
2546 std::atomic<uint32_t>& nextFuncId,
2547 ClsPreResolveUpdates& updates,
2548 ClonedClosureMap& clonedClosures,
2549 SString xinitName) {
2550 auto const cls = const_cast<php::Class*>(cinfo->cls);
2551 std::unique_ptr<php::Func> empty;
2552 auto& xinit = [&] () -> std::unique_ptr<php::Func>& {
2553 for (auto& m : cls->methods) {
2554 if (m->name == xinitName) return m;
2556 return empty;
2557 }();
2559 auto merge_one = [&] (const php::Func* func) {
2560 if (!xinit) {
2561 ITRACE(5, " - cloning {}::{} as {}::{}\n",
2562 func->cls->name, func->name, cls->name, xinitName);
2563 xinit = clone_meth(unit, cls, func, func->name, func->attrs, nextFuncId,
2564 updates, clonedClosures);
2565 return xinit != nullptr;
2568 ITRACE(5, " - appending {}::{} into {}::{}\n",
2569 func->cls->name, func->name, cls->name, xinitName);
2570 if (xinitName == s_86cinit.get()) {
2571 return append_86cinit(xinit.get(), *func);
2572 } else {
2573 return append_func(xinit.get(), *func);
2577 for (auto t : cinfo->usedTraits) {
2578 auto it = t->methods.find(xinitName);
2579 if (it != t->methods.end()) {
2580 if (!merge_one(it->second.func)) {
2581 ITRACE(5, "merge_xinits: failed to merge {}::{}\n",
2582 it->second.func->cls->name, it->second.func->name);
2583 return false;
2588 assertx(xinit);
2589 if (empty) {
2590 ITRACE(5, "merge_xinits: adding {}::{} to method table\n",
2591 xinit->cls->name, xinit->name);
2592 assertx(&empty == &xinit);
2593 clones.push_back(std::move(xinit));
2596 return true;
2599 bool merge_xinits(Attr attr,
2600 std::vector<std::unique_ptr<php::Func>>& clones,
2601 php::Unit* unit,
2602 ClassInfo* cinfo,
2603 std::atomic<uint32_t>& nextFuncId,
2604 ClsPreResolveUpdates& updates,
2605 ClonedClosureMap& clonedClosures) {
2606 auto const xinitName = [&]() {
2607 switch (attr) {
2608 case AttrNone : return s_86pinit.get();
2609 case AttrStatic: return s_86sinit.get();
2610 case AttrLSB : return s_86linit.get();
2611 default: always_assert(false);
2613 }();
2615 auto const xinitMatch = [&](Attr prop_attrs) {
2616 auto mask = AttrStatic | AttrLSB;
2617 switch (attr) {
2618 case AttrNone: return (prop_attrs & mask) == AttrNone;
2619 case AttrStatic: return (prop_attrs & mask) == AttrStatic;
2620 case AttrLSB: return (prop_attrs & mask) == mask;
2621 default: always_assert(false);
2625 for (auto const& p : cinfo->traitProps) {
2626 if (xinitMatch(p.attrs) &&
2627 p.val.m_type == KindOfUninit &&
2628 !(p.attrs & AttrLateInit)) {
2629 ITRACE(5, "merge_xinits: {}: Needs merge for {}{}prop `{}'\n",
2630 cinfo->cls->name, attr & AttrStatic ? "static " : "",
2631 attr & AttrLSB ? "lsb " : "", p.name);
2632 return merge_inits(clones, unit, cinfo, nextFuncId,
2633 updates, clonedClosures, xinitName);
2636 return true;
2639 bool merge_cinits(std::vector<std::unique_ptr<php::Func>>& clones,
2640 php::Unit* unit,
2641 ClassInfo* cinfo,
2642 std::atomic<uint32_t>& nextFuncId,
2643 ClsPreResolveUpdates& updates,
2644 ClonedClosureMap& clonedClosures) {
2645 auto const xinitName = s_86cinit.get();
2646 for (auto const& c : cinfo->traitConsts) {
2647 if (c.val && c.val->m_type == KindOfUninit) {
2648 return merge_inits(clones, unit, cinfo, nextFuncId,
2649 updates, clonedClosures, xinitName);
2652 return true;
2655 void rename_closure(const IndexData& index,
2656 php::Class* cls,
2657 ClsPreResolveUpdates& updates) {
2658 auto n = cls->name->slice();
2659 auto const p = n.find(';');
2660 if (p != std::string::npos) {
2661 n = n.subpiece(0, p);
2663 auto const newName = makeStaticString(NewAnonymousClassName(n));
2664 assertx(!index.classes.count(newName));
2665 cls->name = newName;
2666 updates.newClosures.emplace_back(cls);
2669 template <typename T>
2670 void preresolve(const php::Program*,
2671 const IndexData&,
2672 const T*,
2673 PreResolveUpdates<typename PhpTypeHelper<T>::Info>&);
2675 void flatten_traits(const php::Program* program,
2676 const IndexData& index,
2677 ClassInfo* cinfo,
2678 ClsPreResolveUpdates& updates) {
2679 bool hasConstProp = false;
2680 for (auto const t : cinfo->usedTraits) {
2681 if (t->usedTraits.size() && !(t->cls->attrs & AttrNoExpandTrait)) {
2682 ITRACE(5, "Not flattening {} because of {}\n",
2683 cinfo->cls->name, t->cls->name);
2684 return;
2686 if (is_noflatten_trait(t->cls)) {
2687 ITRACE(5, "Not flattening {} because {} is annotated with __NoFlatten\n",
2688 cinfo->cls->name, t->cls->name);
2689 return;
2691 if (t->cls->hasConstProp) hasConstProp = true;
2693 auto const cls = const_cast<php::Class*>(cinfo->cls);
2694 if (hasConstProp) cls->hasConstProp = true;
2695 std::vector<MethTabEntryPair*> methodsToAdd;
2696 for (auto& ent : cinfo->methods) {
2697 if (!ent.second.topLevel || ent.second.func->cls == cinfo->cls) {
2698 continue;
2700 always_assert(ent.second.func->cls->attrs & AttrTrait);
2701 methodsToAdd.push_back(mteFromElm(ent));
2704 auto const it = updates.extraMethods.find(cinfo->cls);
2706 if (!methodsToAdd.empty()) {
2707 assertx(it != updates.extraMethods.end());
2708 std::sort(begin(methodsToAdd), end(methodsToAdd),
2709 [] (const MethTabEntryPair* a, const MethTabEntryPair* b) {
2710 return a->second.idx < b->second.idx;
2712 } else if (debug && it != updates.extraMethods.end()) {
2713 // When building the ClassInfos, we proactively added all closures
2714 // from usedTraits to classExtraMethodMap; but now we're going to
2715 // start from the used methods, and deduce which closures actually
2716 // get pulled in. Its possible *none* of the methods got used, in
2717 // which case, we won't need their closures either. To be safe,
2718 // verify that the only things in classExtraMethodMap are
2719 // closures.
2720 for (DEBUG_ONLY auto const f : it->second) {
2721 assertx(f->isClosureBody);
2725 std::vector<std::unique_ptr<php::Func>> clones;
2726 ClonedClosureMap clonedClosures;
2727 auto& nextFuncId = const_cast<php::Program*>(program)->nextFuncId;
2729 for (auto const ent : methodsToAdd) {
2730 auto clone = clone_meth(cls->unit, cls, ent->second.func, ent->first,
2731 ent->second.attrs, nextFuncId,
2732 updates, clonedClosures);
2733 if (!clone) {
2734 ITRACE(5, "Not flattening {} because {}::{} could not be cloned\n",
2735 cls->name, ent->second.func->cls->name, ent->first);
2736 return;
2739 clone->attrs |= AttrTrait;
2740 ent->second.attrs |= AttrTrait;
2741 ent->second.func = clone.get();
2742 clones.push_back(std::move(clone));
2745 if (cinfo->traitProps.size()) {
2746 if (!merge_xinits(AttrNone, clones, cls->unit, cinfo,
2747 nextFuncId, updates, clonedClosures) ||
2748 !merge_xinits(AttrStatic, clones, cls->unit, cinfo,
2749 nextFuncId, updates, clonedClosures) ||
2750 !merge_xinits(AttrLSB, clones, cls->unit, cinfo,
2751 nextFuncId, updates, clonedClosures)) {
2752 ITRACE(5, "Not flattening {} because we couldn't merge the 86xinits\n",
2753 cls->name);
2754 return;
2758 // flatten initializers for constants in traits
2759 if (cinfo->traitConsts.size()) {
2760 if (!merge_cinits(clones, cls->unit, cinfo, nextFuncId, updates,
2761 clonedClosures)) {
2762 ITRACE(5, "Not flattening {} because we couldn't merge the 86cinits\n",
2763 cls->name);
2764 return;
2768 // We're now committed to flattening.
2769 ITRACE(3, "Flattening {}\n", cls->name);
2770 if (it != updates.extraMethods.end()) it->second.clear();
2771 for (auto const& p : cinfo->traitProps) {
2772 ITRACE(5, " - prop {}\n", p.name);
2773 cls->properties.push_back(p);
2774 cls->properties.back().attrs |= AttrTrait;
2776 cinfo->traitProps.clear();
2778 for (auto const& c : cinfo->traitConsts) {
2779 ITRACE(5, " - const {}\n", c.name);
2780 cls->constants.push_back(c);
2781 cinfo->clsConstants[c.name].cls = cls;
2782 cinfo->clsConstants[c.name].idx = cls->constants.size()-1;
2783 cinfo->preResolveState->constsFromTraits.erase(c.name);
2785 cinfo->traitConsts.clear();
2787 if (clones.size()) {
2788 auto cinit = cls->methods.size() &&
2789 cls->methods.back()->name == s_86cinit.get() ?
2790 std::move(cls->methods.back()) : nullptr;
2791 if (cinit) cls->methods.pop_back();
2792 for (auto& clone : clones) {
2793 if (is_special_method_name(clone->name)) {
2794 DEBUG_ONLY auto res = cinfo->methods.emplace(
2795 clone->name,
2796 MethTabEntry { clone.get(), clone->attrs, false, true }
2798 assertx(res.second);
2800 ITRACE(5, " - meth {}\n", clone->name);
2801 cinfo->methods.find(clone->name)->second.func = clone.get();
2802 if (clone->name == s_86cinit.get()) {
2803 cinit = std::move(clone);
2804 continue;
2806 cls->methods.push_back(std::move(clone));
2808 if (cinit) cls->methods.push_back(std::move(cinit));
2810 if (clonedClosures.size()) {
2811 auto& closures = updates.closures[cls];
2812 for (auto& ent : clonedClosures) {
2813 auto clo = ent.second.first.get();
2814 rename_closure(index, clo, updates);
2815 ITRACE(5, " - closure {} as {}\n", ent.first->name, clo->name);
2816 assertx(clo->closureContextCls == cls);
2817 assertx(clo->unit == cls->unit);
2818 closures.emplace_back(clo);
2819 updates.newClasses.emplace_back(
2820 std::move(ent.second.first),
2821 cls->unit,
2822 ent.second.second
2824 preresolve(program, index, clo, updates);
2829 struct EqHash {
2830 bool operator()(const PreClass::ClassRequirement& a,
2831 const PreClass::ClassRequirement& b) const {
2832 return a.is_same(&b);
2834 size_t operator()(const PreClass::ClassRequirement& a) const {
2835 return a.hash();
2839 hphp_hash_set<PreClass::ClassRequirement, EqHash, EqHash> reqs;
2841 for (auto const t : cinfo->usedTraits) {
2842 for (auto const& req : t->cls->requirements) {
2843 if (reqs.empty()) {
2844 for (auto const& r : cls->requirements) {
2845 reqs.insert(r);
2848 if (reqs.insert(req).second) cls->requirements.push_back(req);
2852 cls->attrs |= AttrNoExpandTrait;
2856 * Given a static representation of a Hack record, find a possible resolution
2857 * of the record along with all records in its hierarchy.
2859 RecordInfo* resolve_combinations(const IndexData& index,
2860 const php::Record* rec,
2861 RecPreResolveUpdates& updates) {
2862 auto rinfo = std::make_unique<RecordInfo>();
2863 rinfo->rec = rec;
2864 if (rec->parentName) {
2865 auto const parent = index.recordInfo.at(rec->parentName);
2866 if (parent->rec->attrs & AttrFinal) {
2867 ITRACE(2,
2868 "Resolve combinations failed for `{}' because "
2869 "its parent record `{}' is not abstract\n",
2870 rec->name, parent->rec->name);
2871 return nullptr;
2873 rinfo->parent = parent;
2874 rinfo->baseList = rinfo->parent->baseList;
2876 rinfo->baseList.push_back(rinfo.get());
2877 rinfo->baseList.shrink_to_fit();
2878 ITRACE(2, " resolved: {}\n", rec->name);
2879 updates.newInfos.emplace_back(std::move(rinfo));
2880 return updates.newInfos.back().get();
2884 * Given a static representation of a Hack class, find a possible resolution
2885 * of the class along with all classes, interfaces and traits in its hierarchy.
2887 * Returns the resultant ClassInfo, or nullptr if the Hack class
2888 * cannot be instantiated at runtime.
2890 ClassInfo* resolve_combinations(const IndexData& index,
2891 const php::Class* cls,
2892 ClsPreResolveUpdates& updates) {
2893 auto cinfo = std::make_unique<ClassInfo>();
2894 cinfo->cls = cls;
2895 auto const& map = index.classInfo;
2896 if (cls->parentName) {
2897 cinfo->parent = map.at(cls->parentName);
2898 cinfo->baseList = cinfo->parent->baseList;
2899 if (cinfo->parent->cls->attrs & (AttrInterface | AttrTrait)) {
2900 ITRACE(2,
2901 "Resolve combinations failed for `{}' because "
2902 "its parent `{}' is not a class\n",
2903 cls->name, cls->parentName);
2904 return nullptr;
2907 cinfo->baseList.push_back(cinfo.get());
2909 for (auto& iname : cls->interfaceNames) {
2910 auto const iface = map.at(iname);
2911 if (!(iface->cls->attrs & AttrInterface)) {
2912 ITRACE(2,
2913 "Resolve combinations failed for `{}' because `{}' "
2914 "is not an interface\n",
2915 cls->name, iname);
2916 return nullptr;
2918 cinfo->declInterfaces.push_back(iface);
2921 for (auto& included_enum_name : cls->includedEnumNames) {
2922 auto const included_enum = map.at(included_enum_name);
2923 auto const want_attr = cls->attrs & (AttrEnum | AttrEnumClass);
2924 if (!(included_enum->cls->attrs & want_attr)) {
2925 ITRACE(2,
2926 "Resolve combinations failed for `{}' because `{}' "
2927 "is not an enum{}\n",
2928 cls->name, included_enum_name,
2929 want_attr & AttrEnumClass ? " class" : "");
2930 return nullptr;
2932 cinfo->includedEnums.push_back(included_enum);
2935 for (auto& tname : cls->usedTraitNames) {
2936 auto const trait = map.at(tname);
2937 if (!(trait->cls->attrs & AttrTrait)) {
2938 ITRACE(2,
2939 "Resolve combinations failed for `{}' because `{}' "
2940 "is not a trait\n",
2941 cls->name, tname);
2942 return nullptr;
2944 cinfo->usedTraits.push_back(trait);
2947 cinfo->preResolveState = std::make_unique<ClassInfo::PreResolveState>();
2948 if (!build_cls_info(index, cinfo.get(), updates)) return nullptr;
2950 ITRACE(2, " resolved: {}\n", cls->name);
2951 if (Trace::moduleEnabled(Trace::hhbbc_index, 3)) {
2952 for (auto const DEBUG_ONLY& iface : cinfo->implInterfaces) {
2953 ITRACE(3, " implements: {}\n", iface.second->cls->name);
2955 for (auto const DEBUG_ONLY& trait : cinfo->usedTraits) {
2956 ITRACE(3, " uses: {}\n", trait->cls->name);
2959 cinfo->baseList.shrink_to_fit();
2960 updates.newInfos.emplace_back(std::move(cinfo));
2961 return updates.newInfos.back().get();
2964 void PhpTypeHelper<php::Record>::try_flatten_traits(const php::Program*,
2965 const IndexData&,
2966 const php::Record*,
2967 RecordInfo*,
2968 RecPreResolveUpdates&) {}
2970 void PhpTypeHelper<php::Class>::try_flatten_traits(
2971 const php::Program* program,
2972 const IndexData& index,
2973 const php::Class* cls,
2974 ClassInfo* cinfo,
2975 ClsPreResolveUpdates& updates) {
2976 if (options.FlattenTraits &&
2977 !(cls->attrs & AttrNoExpandTrait) &&
2978 !cls->usedTraitNames.empty() &&
2979 index.classes.count(cls->name) == 1) {
2980 Trace::Indent indent;
2981 flatten_traits(program, index, cinfo, updates);
2985 template <typename T>
2986 void preresolve(const php::Program* program,
2987 const IndexData& index,
2988 const T* type,
2989 PreResolveUpdates<typename PhpTypeHelper<T>::Info>& updates) {
2990 ITRACE(2, "preresolve {}: {}:{}\n",
2991 PhpTypeHelper<T>::name(), type->name, (void*)type);
2993 auto const resolved = [&] {
2994 Trace::Indent indent;
2995 if (debug) {
2996 PhpTypeHelper<T>::assert_bases(index, type);
2998 return resolve_combinations(index, type, updates);
2999 }();
3001 ITRACE(3, "preresolve: {}:{} ({} resolutions)\n",
3002 type->name, (void*)type, resolved ? 1 : 0);
3004 if (resolved) {
3005 updates.updateDeps.emplace_back(resolved);
3006 PhpTypeHelper<T>::try_flatten_traits(
3007 program, index, type, resolved, updates
3012 void compute_subclass_list_rec(IndexData& index,
3013 ClassInfo* cinfo,
3014 ClassInfo* csub) {
3015 for (auto const ctrait : csub->usedTraits) {
3016 auto const ct = const_cast<ClassInfo*>(ctrait);
3017 ct->subclassList.push_back(cinfo);
3018 compute_subclass_list_rec(index, cinfo, ct);
3022 void compute_included_enums_list_rec(IndexData& index,
3023 ClassInfo* cinfo,
3024 ClassInfo* csub) {
3025 for (auto const cincluded_enum : csub->includedEnums) {
3026 auto const cie = const_cast<ClassInfo*>(cincluded_enum);
3027 cie->subclassList.push_back(cinfo);
3028 compute_included_enums_list_rec(index, cinfo, cie);
3032 void compute_subclass_list(IndexData& index) {
3033 trace_time _("compute subclass list");
3034 auto fixupTraits = false;
3035 auto fixupEnums = false;
3036 auto const AnyEnum = AttrEnum | AttrEnumClass;
3037 for (auto& cinfo : index.allClassInfos) {
3038 if (cinfo->cls->attrs & AttrInterface) continue;
3039 for (auto& cparent : cinfo->baseList) {
3040 cparent->subclassList.push_back(cinfo.get());
3042 if (!(cinfo->cls->attrs & AttrNoExpandTrait) &&
3043 cinfo->usedTraits.size()) {
3044 fixupTraits = true;
3045 compute_subclass_list_rec(index, cinfo.get(), cinfo.get());
3047 // Add the included enum lists if cinfo is an enum
3048 if ((cinfo->cls->attrs & AnyEnum) &&
3049 cinfo->cls->includedEnumNames.size()) {
3050 fixupEnums = true;
3051 compute_included_enums_list_rec(index, cinfo.get(), cinfo.get());
3053 // Also add instantiable classes to their interface's subclassLists
3054 if (cinfo->cls->attrs & (AttrTrait | AnyEnum | AttrAbstract)) continue;
3055 for (auto& ipair : cinfo->implInterfaces) {
3056 auto impl = const_cast<ClassInfo*>(ipair.second);
3057 impl->subclassList.push_back(cinfo.get());
3061 for (auto& cinfo : index.allClassInfos) {
3062 auto& sub = cinfo->subclassList;
3063 if ((fixupTraits && cinfo->cls->attrs & AttrTrait) ||
3064 (fixupEnums && cinfo->cls->attrs & AnyEnum)) {
3065 // traits and enums can be reached by multiple paths, so we need to
3066 // uniquify their subclassLists.
3067 std::sort(begin(sub), end(sub));
3068 sub.erase(
3069 std::unique(begin(sub), end(sub)),
3070 end(sub)
3073 sub.shrink_to_fit();
3077 bool define_func_family(IndexData& index, ClassInfo* cinfo,
3078 SString name, const php::Func* func = nullptr) {
3079 FuncFamily::PFuncVec funcs{};
3080 for (auto const cleaf : cinfo->subclassList) {
3081 auto const leafFn = [&] () -> const MethTabEntryPair* {
3082 auto const leafFnIt = cleaf->methods.find(name);
3083 if (leafFnIt == end(cleaf->methods)) return nullptr;
3084 return mteFromIt(leafFnIt);
3085 }();
3086 if (!leafFn) continue;
3087 funcs.push_back(leafFn);
3090 if (funcs.empty()) return false;
3092 std::sort(
3093 begin(funcs), end(funcs),
3094 [&] (const MethTabEntryPair* a, const MethTabEntryPair* b) {
3095 // We want a canonical order for the family. Putting the
3096 // one corresponding to cinfo first makes sense, because
3097 // the first one is used as the name for FCall*Method* hint,
3098 // after that, sort by name so that different case spellings
3099 // come in the same order.
3100 if (a->second.func == b->second.func) return false;
3101 if (func) {
3102 if (b->second.func == func) return false;
3103 if (a->second.func == func) return true;
3105 if (auto d = a->first->compare(b->first)) {
3106 if (!func) {
3107 if (b->first == name) return false;
3108 if (a->first == name) return true;
3110 return d < 0;
3112 return std::less<const void*>{}(a->second.func, b->second.func);
3115 funcs.erase(
3116 std::unique(
3117 begin(funcs), end(funcs),
3118 [] (const MethTabEntryPair* a, const MethTabEntryPair* b) {
3119 return a->second.func == b->second.func;
3122 end(funcs)
3125 funcs.shrink_to_fit();
3127 if (Trace::moduleEnabled(Trace::hhbbc_index, 4)) {
3128 FTRACE(4, "define_func_family: {}::{}:\n",
3129 cinfo->cls->name, name);
3130 for (auto const DEBUG_ONLY func : funcs) {
3131 FTRACE(4, " {}::{}\n",
3132 func->second.func->cls->name, func->second.func->name);
3136 // Single func resolutions are stored separately. They don't need a
3137 // FuncFamily and this saves space.
3138 if (funcs.size() == 1) {
3139 cinfo->singleMethodFamilies.emplace(name, funcs[0]);
3140 return true;
3143 // Otherwise re-use an existing identical FuncFamily, or create a
3144 // new one.
3145 auto const ff = [&] {
3146 auto it = index.funcFamilies.find(funcs);
3147 if (it != index.funcFamilies.end()) return it->first.get();
3148 return index.funcFamilies.insert(
3149 std::make_unique<FuncFamily>(std::move(funcs)),
3150 false
3151 ).first->first.get();
3152 }();
3154 cinfo->methodFamilies.emplace(
3155 std::piecewise_construct,
3156 std::forward_as_tuple(name),
3157 std::forward_as_tuple(ff)
3160 return true;
3163 void build_abstract_func_families(IndexData& data, ClassInfo* cinfo) {
3164 std::vector<SString> extras;
3166 // We start by collecting the list of methods shared across all
3167 // subclasses of cinfo (including indirectly). And then add the
3168 // public methods which are not constructors and have no private
3169 // ancestors to the method families of cinfo. Note that this set
3170 // may be larger than the methods declared on cinfo and may also
3171 // be missing methods declared on cinfo. In practice this is the
3172 // set of methods we can depend on having accessible given any
3173 // object which is known to implement cinfo.
3174 auto it = cinfo->subclassList.begin();
3175 while (true) {
3176 if (it == cinfo->subclassList.end()) return;
3177 auto const sub = *it++;
3178 assertx(!(sub->cls->attrs & AttrInterface));
3179 if (sub == cinfo || (sub->cls->attrs & AttrAbstract)) continue;
3180 for (auto& par : sub->methods) {
3181 if (!par.second.hasPrivateAncestor &&
3182 (par.second.attrs & AttrPublic) &&
3183 !cinfo->methodFamilies.count(par.first) &&
3184 !cinfo->singleMethodFamilies.count(par.first) &&
3185 !cinfo->methods.count(par.first)) {
3186 extras.push_back(par.first);
3189 if (!extras.size()) return;
3190 break;
3193 auto end = extras.end();
3194 while (it != cinfo->subclassList.end()) {
3195 auto const sub = *it++;
3196 assertx(!(sub->cls->attrs & AttrInterface));
3197 if (sub == cinfo || (sub->cls->attrs & AttrAbstract)) continue;
3198 for (auto nameIt = extras.begin(); nameIt != end;) {
3199 auto const meth = sub->methods.find(*nameIt);
3200 if (meth == sub->methods.end() ||
3201 !(meth->second.attrs & AttrPublic) ||
3202 meth->second.hasPrivateAncestor) {
3203 *nameIt = *--end;
3204 if (end == extras.begin()) return;
3205 } else {
3206 ++nameIt;
3210 extras.erase(end, extras.end());
3212 if (Trace::moduleEnabled(Trace::hhbbc_index, 5)) {
3213 FTRACE(5, "Adding extra methods to {}:\n", cinfo->cls->name);
3214 for (auto const DEBUG_ONLY extra : extras) {
3215 FTRACE(5, " {}\n", extra);
3219 hphp_fast_set<SString> added;
3221 for (auto name : extras) {
3222 if (define_func_family(data, cinfo, name) &&
3223 (cinfo->cls->attrs & AttrInterface)) {
3224 added.emplace(name);
3228 if (cinfo->cls->attrs & AttrInterface) {
3229 for (auto& m : cinfo->cls->methods) {
3230 if (added.count(m->name)) {
3231 cinfo->methods.emplace(
3232 m->name,
3233 MethTabEntry { m.get(), m->attrs, false, true }
3238 return;
3241 void define_func_families(IndexData& index) {
3242 trace_time tracer("define_func_families");
3244 parallel::for_each(
3245 index.allClassInfos,
3246 [&] (const std::unique_ptr<ClassInfo>& cinfo) {
3247 if (cinfo->cls->attrs & AttrTrait) return;
3248 FTRACE(4, "Defining func families for {}\n", cinfo->cls->name);
3249 if (!(cinfo->cls->attrs & AttrInterface)) {
3250 for (auto& kv : cinfo->methods) {
3251 auto const mte = mteFromElm(kv);
3253 if (mte->second.attrs & AttrNoOverride) continue;
3254 if (is_special_method_name(mte->first)) continue;
3256 // We need function family for constructor even if it is private,
3257 // as `new static()` may still call a non-private constructor from
3258 // subclass.
3259 if (!mte->first->isame(s_construct.get()) &&
3260 mte->second.attrs & AttrPrivate) {
3261 continue;
3264 define_func_family(index, cinfo.get(), mte->first, mte->second.func);
3267 if (cinfo->cls->attrs & (AttrInterface | AttrAbstract)) {
3268 build_abstract_func_families(index, cinfo.get());
3273 // Now that all of the FuncFamilies have been created, generate the
3274 // back links from FuncInfo to their FuncFamilies.
3275 std::vector<FuncFamily*> work;
3276 work.reserve(index.funcFamilies.size());
3277 for (auto const& kv : index.funcFamilies) work.emplace_back(kv.first.get());
3279 // Different threads can touch the same FuncInfo, so use sharded
3280 // locking scheme.
3281 std::array<std::mutex, 256> locks;
3283 parallel::for_each(
3284 work,
3285 [&] (FuncFamily* ff) {
3286 ff->m_numInOut = num_inout_from_set(ff->possibleFuncs());
3287 for (auto const pf : ff->possibleFuncs()) {
3288 auto finfo = create_func_info(index, pf->second.func);
3289 auto& lock = locks[pointer_hash<FuncInfo>{}(finfo) % locks.size()];
3290 std::lock_guard<std::mutex> _{lock};
3291 finfo->families.emplace_back(ff);
3296 parallel::for_each(
3297 index.funcInfo,
3298 [&] (FuncInfo& fi) { fi.families.shrink_to_fit(); }
3303 * ConflictGraph maintains lists of interfaces that conflict with each other
3304 * due to being implemented by the same class.
3306 struct ConflictGraph {
3307 void add(const php::Class* i, const php::Class* j) {
3308 if (i == j) return;
3309 map[i].insert(j);
3312 hphp_hash_map<const php::Class*,
3313 hphp_fast_set<const php::Class*>> map;
3317 * Trace information about interface conflict sets and the vtables computed
3318 * from them.
3320 void trace_interfaces(const IndexData& index, const ConflictGraph& cg) {
3321 // Compute what the vtable for each Class will look like, and build up a list
3322 // of all interfaces.
3323 struct Cls {
3324 const ClassInfo* cinfo;
3325 std::vector<const php::Class*> vtable;
3327 std::vector<Cls> classes;
3328 std::vector<const php::Class*> ifaces;
3329 size_t total_slots = 0, empty_slots = 0;
3330 for (auto& cinfo : index.allClassInfos) {
3331 if (cinfo->cls->attrs & AttrInterface) {
3332 ifaces.emplace_back(cinfo->cls);
3333 continue;
3335 if (cinfo->cls->attrs & (AttrTrait | AttrEnum | AttrEnumClass)) continue;
3337 classes.emplace_back(Cls{cinfo.get()});
3338 auto& vtable = classes.back().vtable;
3339 for (auto& pair : cinfo->implInterfaces) {
3340 auto it = index.ifaceSlotMap.find(pair.second->cls);
3341 assertx(it != end(index.ifaceSlotMap));
3342 auto const slot = it->second;
3343 if (slot >= vtable.size()) vtable.resize(slot + 1);
3344 vtable[slot] = pair.second->cls;
3347 total_slots += vtable.size();
3348 for (auto iface : vtable) if (iface == nullptr) ++empty_slots;
3351 Slot max_slot = 0;
3352 for (auto const& pair : index.ifaceSlotMap) {
3353 max_slot = std::max(max_slot, pair.second);
3356 // Sort the list of class vtables so the largest ones come first.
3357 auto class_cmp = [&](const Cls& a, const Cls& b) {
3358 return a.vtable.size() > b.vtable.size();
3360 std::sort(begin(classes), end(classes), class_cmp);
3362 // Sort the list of interfaces so the biggest conflict sets come first.
3363 auto iface_cmp = [&](const php::Class* a, const php::Class* b) {
3364 return cg.map.at(a).size() > cg.map.at(b).size();
3366 std::sort(begin(ifaces), end(ifaces), iface_cmp);
3368 std::string out;
3369 folly::format(&out, "{} interfaces, {} classes\n",
3370 ifaces.size(), classes.size());
3371 folly::format(&out,
3372 "{} vtable slots, {} empty vtable slots, max slot {}\n",
3373 total_slots, empty_slots, max_slot);
3374 folly::format(&out, "\n{:-^80}\n", " interface slots & conflict sets");
3375 for (auto iface : ifaces) {
3376 auto cgIt = cg.map.find(iface);
3377 if (cgIt == end(cg.map)) break;
3378 auto& conflicts = cgIt->second;
3380 folly::format(&out, "{:>40} {:3} {:2} [", iface->name,
3381 conflicts.size(),
3382 folly::get_default(index.ifaceSlotMap, iface));
3383 auto sep = "";
3384 for (auto conflict : conflicts) {
3385 folly::format(&out, "{}{}", sep, conflict->name);
3386 sep = ", ";
3388 folly::format(&out, "]\n");
3391 folly::format(&out, "\n{:-^80}\n", " class vtables ");
3392 for (auto& item : classes) {
3393 if (item.vtable.empty()) break;
3395 folly::format(&out, "{:>30}: [", item.cinfo->cls->name);
3396 auto sep = "";
3397 for (auto iface : item.vtable) {
3398 folly::format(&out, "{}{}", sep, iface ? iface->name->data() : "null");
3399 sep = ", ";
3401 folly::format(&out, "]\n");
3404 Trace::traceRelease("%s", out.c_str());
3408 * Find the lowest Slot that doesn't conflict with anything in the conflict set
3409 * for iface.
3411 Slot find_min_slot(const php::Class* iface,
3412 const IfaceSlotMap& slots,
3413 const ConflictGraph& cg) {
3414 auto const& cit = cg.map.find(iface);
3415 if (cit == cg.map.end() || cit->second.empty()) {
3416 // No conflicts. This is the only interface implemented by the classes that
3417 // implement it.
3418 return 0;
3421 boost::dynamic_bitset<> used;
3423 for (auto const& c : cit->second) {
3424 auto const it = slots.find(c);
3425 if (it == slots.end()) continue;
3426 auto const slot = it->second;
3428 if (used.size() <= slot) used.resize(slot + 1);
3429 used.set(slot);
3431 used.flip();
3432 return used.any() ? used.find_first() : used.size();
3436 * Compute vtable slots for all interfaces. No two interfaces implemented by
3437 * the same class will share the same vtable slot.
3439 void compute_iface_vtables(IndexData& index) {
3440 trace_time tracer("compute interface vtables");
3442 ConflictGraph cg;
3443 std::vector<const php::Class*> ifaces;
3444 hphp_hash_map<const php::Class*, int> iface_uses;
3446 // Build up the conflict sets.
3447 for (auto& cinfo : index.allClassInfos) {
3448 // Gather interfaces.
3449 if (cinfo->cls->attrs & AttrInterface) {
3450 ifaces.emplace_back(cinfo->cls);
3451 // Make sure cg.map has an entry for every interface - this simplifies
3452 // some code later on.
3453 cg.map[cinfo->cls];
3454 continue;
3457 // Only worry about classes with methods that can be called.
3458 if (cinfo->cls->attrs & (AttrTrait | AttrEnum | AttrEnumClass)) continue;
3460 for (auto& ipair : cinfo->implInterfaces) {
3461 ++iface_uses[ipair.second->cls];
3462 for (auto& jpair : cinfo->implInterfaces) {
3463 cg.add(ipair.second->cls, jpair.second->cls);
3468 if (ifaces.size() == 0) return;
3470 // Sort interfaces by usage frequencies.
3471 // We assign slots greedily, so sort the interface list so the most
3472 // frequently implemented ones come first.
3473 auto iface_cmp = [&](const php::Class* a, const php::Class* b) {
3474 return iface_uses[a] > iface_uses[b];
3476 std::sort(begin(ifaces), end(ifaces), iface_cmp);
3478 // Assign slots, keeping track of the largest assigned slot and the total
3479 // number of uses for each slot.
3480 Slot max_slot = 0;
3481 hphp_hash_map<Slot, int> slot_uses;
3482 for (auto* iface : ifaces) {
3483 auto const slot = find_min_slot(iface, index.ifaceSlotMap, cg);
3484 index.ifaceSlotMap[iface] = slot;
3485 max_slot = std::max(max_slot, slot);
3487 // Interfaces implemented by the same class never share a slot, so normal
3488 // addition is fine here.
3489 slot_uses[slot] += iface_uses[iface];
3492 // Make sure we have an initialized entry for each slot for the sort below.
3493 for (Slot slot = 0; slot < max_slot; ++slot) {
3494 assertx(slot_uses.count(slot));
3497 // Finally, sort and reassign slots so the most frequently used slots come
3498 // first. This slightly reduces the number of wasted vtable vector entries at
3499 // runtime.
3500 auto const slots = sort_keys_by_value(
3501 slot_uses,
3502 [&] (int a, int b) { return a > b; }
3505 std::vector<Slot> slots_permute(max_slot + 1, 0);
3506 for (size_t i = 0; i <= max_slot; ++i) slots_permute[slots[i]] = i;
3508 // re-map interfaces to permuted slots
3509 for (auto& pair : index.ifaceSlotMap) {
3510 pair.second = slots_permute[pair.second];
3513 if (Trace::moduleEnabledRelease(Trace::hhbbc_iface)) {
3514 trace_interfaces(index, cg);
3518 void mark_magic_on_parents(ClassInfo& cinfo, ClassInfo& derived) {
3519 auto any = false;
3520 for (const auto& mm : magicMethods) {
3521 if ((derived.*mm.pmem).thisHas) {
3522 auto& derivedHas = (cinfo.*mm.pmem).derivedHas;
3523 if (!derivedHas) {
3524 derivedHas = any = true;
3528 if (!any) return;
3529 if (cinfo.parent) mark_magic_on_parents(*cinfo.parent, derived);
3530 for (auto iface : cinfo.declInterfaces) {
3531 mark_magic_on_parents(*const_cast<ClassInfo*>(iface), derived);
3535 bool has_magic_method(const ClassInfo* cinfo, SString name) {
3536 if (name == s_toBoolean.get()) {
3537 // note that "having" a magic method includes the possibility that
3538 // a parent class has it. This can't happen for the collection
3539 // classes, because they're all final; but for SimpleXMLElement,
3540 // we need to search.
3541 while (cinfo->parent) cinfo = cinfo->parent;
3542 return has_magic_bool_conversion(cinfo->cls->name);
3544 return cinfo->methods.find(name) != end(cinfo->methods);
3547 void find_magic_methods(IndexData& index) {
3548 trace_time tracer("find magic methods");
3550 for (auto& cinfo : index.allClassInfos) {
3551 bool any = false;
3552 for (const auto& mm : magicMethods) {
3553 bool const found = has_magic_method(cinfo.get(), mm.name.get());
3554 any = any || found;
3555 (cinfo.get()->*mm.pmem).thisHas = found;
3557 if (any) mark_magic_on_parents(*cinfo, *cinfo);
3561 void find_mocked_classes(IndexData& index) {
3562 trace_time tracer("find mocked classes");
3564 for (auto& cinfo : index.allClassInfos) {
3565 if (is_mock_class(cinfo->cls) && cinfo->parent) {
3566 cinfo->parent->isMocked = true;
3567 for (auto c = cinfo->parent; c; c = c->parent) {
3568 c->isDerivedMocked = true;
3574 void mark_const_props(IndexData& index) {
3575 trace_time tracer("mark const props");
3577 for (auto& cinfo : index.allClassInfos) {
3578 auto const hasConstProp = [&]() {
3579 if (cinfo->cls->hasConstProp) return true;
3580 if (cinfo->parent && cinfo->parent->hasConstProp) return true;
3581 if (!(cinfo->cls->attrs & AttrNoExpandTrait)) {
3582 for (auto t : cinfo->usedTraits) {
3583 if (t->cls->hasConstProp) return true;
3586 return false;
3587 }();
3588 if (hasConstProp) {
3589 cinfo->hasConstProp = true;
3590 for (auto c = cinfo.get(); c; c = c->parent) {
3591 if (c->derivedHasConstProp) break;
3592 c->derivedHasConstProp = true;
3598 void mark_no_override_classes(IndexData& index) {
3599 trace_time tracer("mark no override classes");
3601 for (auto& cinfo : index.allClassInfos) {
3602 // We cleared all the NoOverride flags while building the
3603 // index. Set them as necessary.
3604 if (!(cinfo->cls->attrs & AttrInterface) &&
3605 cinfo->subclassList.size() == 1) {
3606 attribute_setter(cinfo->cls->attrs, true, AttrNoOverride);
3611 void mark_no_override_methods(IndexData& index) {
3612 trace_time tracer("mark no override methods");
3614 // We removed any AttrNoOverride flags from all methods while adding
3615 // the units to the index. Now start by marking every
3616 // (non-interface, non-special) method as AttrNoOverride.
3617 for (auto& cinfo : index.allClassInfos) {
3618 if (cinfo->cls->attrs & AttrInterface) continue;
3620 for (auto& m : cinfo->methods) {
3621 if (!(is_special_method_name(m.first))) {
3622 FTRACE(9, "Pre-setting AttrNoOverride on {}::{}\n",
3623 m.second.func->cls->name, m.first);
3624 attribute_setter(m.second.attrs, true, AttrNoOverride);
3625 attribute_setter(m.second.func->attrs, true, AttrNoOverride);
3630 // Then run through every ClassInfo, and for each of its parent classes clear
3631 // the AttrNoOverride flag if it has a different Func with the same name.
3632 for (auto& cinfo : index.allClassInfos) {
3633 for (auto& ancestor : cinfo->baseList) {
3634 if (ancestor == cinfo.get()) continue;
3636 auto removeNoOverride = [] (auto it) {
3637 assertx(it->second.attrs & AttrNoOverride ||
3638 !(it->second.func->attrs & AttrNoOverride));
3639 if (it->second.attrs & AttrNoOverride) {
3640 FTRACE(2, "Removing AttrNoOverride on {}::{}\n",
3641 it->second.func->cls->name, it->first);
3642 attribute_setter(it->second.attrs, false, AttrNoOverride);
3643 attribute_setter(it->second.func->attrs, false, AttrNoOverride);
3647 for (auto& derivedMethod : cinfo->methods) {
3648 auto const it = ancestor->methods.find(derivedMethod.first);
3649 if (it == end(ancestor->methods)) continue;
3650 if (it->second.func != derivedMethod.second.func) {
3651 removeNoOverride(it);
3658 const StaticString s__Reified("__Reified");
3661 * Emitter adds a 86reifiedinit method to all classes that have reified
3662 * generics. All base classes also need to have this method so that when we
3663 * call parent::86reifeidinit(...), there is a stopping point.
3664 * Since while emitting we do not know whether a base class will have
3665 * reified parents, during JIT time we need to add 86reifiedinit
3666 * unless AttrNoReifiedInit attribute is set. At this phase,
3667 * we set AttrNoReifiedInit attribute on classes do not have any
3668 * reified classes that extend it.
3670 void clean_86reifiedinit_methods(IndexData& index) {
3671 trace_time tracer("clean 86reifiedinit methods");
3672 folly::F14FastSet<const php::Class*> needsinit;
3674 // Find all classes that still need their 86reifiedinit methods
3675 for (auto& cinfo : index.allClassInfos) {
3676 auto ual = cinfo->cls->userAttributes;
3677 // Each class that has at least one reified generic has an attribute
3678 // __Reified added by the emitter
3679 auto has_reification = ual.find(s__Reified.get()) != ual.end();
3680 if (!has_reification) continue;
3681 // Add the base class for this reified class
3682 needsinit.emplace(cinfo->baseList[0]->cls);
3685 // Add AttrNoReifiedInit to the base classes that do not need this method
3686 for (auto& cinfo : index.allClassInfos) {
3687 if (cinfo->parent == nullptr && needsinit.count(cinfo->cls) == 0) {
3688 FTRACE(2, "Adding AttrNoReifiedInit on class {}\n", cinfo->cls->name);
3689 attribute_setter(cinfo->cls->attrs, true, AttrNoReifiedInit);
3694 //////////////////////////////////////////////////////////////////////
3696 void check_invariants(const ClassInfo* cinfo) {
3697 // All the following invariants only apply to classes
3698 if (cinfo->cls->attrs & AttrInterface) return;
3700 if (!(cinfo->cls->attrs & AttrTrait)) {
3701 // For non-interface classes, each method in a php class has an
3702 // entry in its ClassInfo method table, and if it's not special,
3703 // AttrNoOverride, or private, an entry in the family table.
3704 for (auto& m : cinfo->cls->methods) {
3705 auto const it = cinfo->methods.find(m->name);
3706 always_assert(it != cinfo->methods.end());
3707 if (it->second.attrs & (AttrNoOverride|AttrPrivate)) continue;
3708 if (is_special_method_name(m->name)) continue;
3709 always_assert(
3710 cinfo->methodFamilies.count(m->name) ||
3711 cinfo->singleMethodFamilies.count(m->name)
3716 // The subclassList is non-empty, contains this ClassInfo, and
3717 // contains only unique elements.
3718 always_assert(!cinfo->subclassList.empty());
3719 always_assert(std::find(begin(cinfo->subclassList),
3720 end(cinfo->subclassList),
3721 cinfo) != end(cinfo->subclassList));
3722 auto cpy = cinfo->subclassList;
3723 std::sort(begin(cpy), end(cpy));
3724 cpy.erase(
3725 std::unique(begin(cpy), end(cpy)),
3726 end(cpy)
3728 always_assert(cpy.size() == cinfo->subclassList.size());
3730 // The baseList is non-empty, and the last element is this class.
3731 always_assert(!cinfo->baseList.empty());
3732 always_assert(cinfo->baseList.back() == cinfo);
3734 for (const auto& mm : magicMethods) {
3735 const auto& info = cinfo->*mm.pmem;
3737 // Magic method flags should be consistent with the method table.
3738 always_assert(info.thisHas == has_magic_method(cinfo, mm.name.get()));
3740 // Non-'derived' flags (thisHas) about magic methods imply the derived
3741 // ones.
3742 always_assert(!info.thisHas || info.derivedHas);
3745 // Every FuncFamily has more than function and contain functions
3746 // with the same name (unless its a family of ctors). methodFamilies
3747 // and singleMethodFamilies should have disjoint keys.
3748 for (auto const& mfam: cinfo->methodFamilies) {
3749 always_assert(mfam.second->possibleFuncs().size() > 1);
3750 auto const name = mfam.second->possibleFuncs().front()->first;
3751 for (auto const pf : mfam.second->possibleFuncs()) {
3752 always_assert(pf->first->isame(name));
3754 always_assert(!cinfo->singleMethodFamilies.count(mfam.first));
3756 for (auto const& mfam : cinfo->singleMethodFamilies) {
3757 always_assert(!cinfo->methodFamilies.count(mfam.first));
3761 void check_invariants(IndexData& data) {
3762 if (!debug) return;
3764 for (auto& cinfo : data.allClassInfos) {
3765 check_invariants(cinfo.get());
3769 //////////////////////////////////////////////////////////////////////
3771 Type context_sensitive_return_type(IndexData& data,
3772 CallContext callCtx) {
3773 constexpr auto max_interp_nexting_level = 2;
3774 static __thread uint32_t interp_nesting_level;
3775 auto const finfo = func_info(data, callCtx.callee);
3776 auto returnType = return_with_context(finfo->returnTy, callCtx.context);
3778 auto checkParam = [&] (int i) {
3779 auto const constraint = finfo->func->params[i].typeConstraint;
3780 if (constraint.hasConstraint() &&
3781 !constraint.isTypeVar() &&
3782 !constraint.isTypeConstant()) {
3783 auto ctx = Context { finfo->func->unit, finfo->func, finfo->func->cls };
3784 auto t = data.m_index->lookup_constraint(ctx, constraint);
3785 return callCtx.args[i].strictlyMoreRefined(t);
3787 return callCtx.args[i].strictSubtypeOf(TInitCell);
3790 // TODO(#3788877): more heuristics here would be useful.
3791 bool const tryContextSensitive = [&] {
3792 if (finfo->func->noContextSensitiveAnalysis ||
3793 finfo->func->params.empty() ||
3794 interp_nesting_level + 1 >= max_interp_nexting_level ||
3795 returnType == TBottom) {
3796 return false;
3799 if (finfo->retParam != NoLocalId &&
3800 callCtx.args.size() > finfo->retParam &&
3801 checkParam(finfo->retParam)) {
3802 return true;
3805 if (!options.ContextSensitiveInterp) return false;
3807 if (callCtx.args.size() < finfo->func->params.size()) return true;
3808 for (auto i = 0; i < finfo->func->params.size(); i++) {
3809 if (checkParam(i)) return true;
3811 return false;
3812 }();
3814 if (!tryContextSensitive) {
3815 return returnType;
3819 ContextRetTyMap::const_accessor acc;
3820 if (data.contextualReturnTypes.find(acc, callCtx)) {
3821 if (data.frozen || acc->second == TBottom || is_scalar(acc->second)) {
3822 return acc->second;
3827 if (data.frozen) {
3828 return returnType;
3831 auto contextType = [&] {
3832 ++interp_nesting_level;
3833 SCOPE_EXIT { --interp_nesting_level; };
3835 auto const func = finfo->func;
3836 auto const wf = php::WideFunc::cns(func);
3837 auto const calleeCtx = AnalysisContext { func->unit, wf, func->cls };
3838 auto const ty =
3839 analyze_func_inline(*data.m_index, calleeCtx,
3840 callCtx.context, callCtx.args).inferredReturn;
3841 return return_with_context(ty, callCtx.context);
3842 }();
3844 if (!interp_nesting_level) {
3845 FTRACE(3,
3846 "Context sensitive type: {}\n"
3847 "Context insensitive type: {}\n",
3848 show(contextType), show(returnType));
3851 if (!returnType.subtypeOf(BUnc)) {
3852 // If the context insensitive return type could be non-static, staticness
3853 // could be a result of temporary context sensitive bytecode optimizations.
3854 contextType = loosen_staticness(std::move(contextType));
3857 auto ret = intersection_of(std::move(returnType), std::move(contextType));
3859 if (!interp_nesting_level) {
3860 FTRACE(3, "Context sensitive result: {}\n", show(ret));
3863 ContextRetTyMap::accessor acc;
3864 if (data.contextualReturnTypes.insert(acc, callCtx) ||
3865 ret.strictSubtypeOf(acc->second)) {
3866 acc->second = ret;
3869 return ret;
3872 //////////////////////////////////////////////////////////////////////
3874 PrepKind func_param_prep_default() {
3875 return PrepKind::Val;
3878 PrepKind func_param_prep(const php::Func* func,
3879 uint32_t paramId) {
3880 if (paramId >= func->params.size()) {
3881 return PrepKind::Val;
3883 return func->params[paramId].inout ? PrepKind::InOut : PrepKind::Val;
3886 template<class PossibleFuncRange>
3887 PrepKind prep_kind_from_set(PossibleFuncRange range, uint32_t paramId) {
3890 * In single-unit mode, the range is not complete. Without konwing all
3891 * possible resolutions, HHBBC cannot deduce anything about by-val vs inout.
3892 * So the caller should make sure not calling this in single-unit mode.
3894 if (begin(range) == end(range)) {
3895 return func_param_prep_default();
3898 struct FuncFind {
3899 using F = const php::Func*;
3900 static F get(std::pair<SString,F> p) { return p.second; }
3901 static F get(const MethTabEntryPair* mte) { return mte->second.func; }
3904 folly::Optional<PrepKind> prep;
3905 for (auto& item : range) {
3906 switch (func_param_prep(FuncFind::get(item), paramId)) {
3907 case PrepKind::Unknown:
3908 return PrepKind::Unknown;
3909 case PrepKind::InOut:
3910 if (prep && *prep != PrepKind::InOut) return PrepKind::Unknown;
3911 prep = PrepKind::InOut;
3912 break;
3913 case PrepKind::Val:
3914 if (prep && *prep != PrepKind::Val) return PrepKind::Unknown;
3915 prep = PrepKind::Val;
3916 break;
3919 return *prep;
3922 template<typename F> auto
3923 visit_parent_cinfo(const ClassInfo* cinfo, F fun) -> decltype(fun(cinfo)) {
3924 for (auto ci = cinfo; ci != nullptr; ci = ci->parent) {
3925 if (auto const ret = fun(ci)) return ret;
3926 if (ci->cls->attrs & AttrNoExpandTrait) continue;
3927 for (auto ct : ci->usedTraits) {
3928 if (auto const ret = visit_parent_cinfo(ct, fun)) {
3929 return ret;
3933 return {};
3936 Type lookup_public_prop_impl(
3937 const IndexData& data,
3938 const ClassInfo* cinfo,
3939 SString propName
3941 // Find a property declared in this class (or a parent) with the same name.
3942 const php::Class* knownCls = nullptr;
3943 auto const prop = visit_parent_cinfo(
3944 cinfo,
3945 [&] (const ClassInfo* ci) -> const php::Prop* {
3946 for (auto const& prop : ci->cls->properties) {
3947 if (prop.name == propName) {
3948 knownCls = ci->cls;
3949 return &prop;
3952 return nullptr;
3956 if (!prop) return TCell;
3957 // Make sure its non-static and public. Otherwise its another function's
3958 // problem.
3959 if (prop->attrs & (AttrStatic | AttrPrivate)) return TCell;
3961 // Get a type corresponding to its declared type-hint (if any).
3962 auto ty = adjust_type_for_prop(
3963 *data.m_index, *knownCls, &prop->typeConstraint, TCell
3965 // We might have to include the initial value which might be outside of the
3966 // type-hint.
3967 auto initialTy = loosen_all(from_cell(prop->val));
3968 if (!initialTy.subtypeOf(TUninit) && (prop->attrs & AttrSystemInitialValue)) {
3969 ty |= initialTy;
3971 return ty;
3974 // Return the best known type for the given static public/protected
3975 // property from the Index.
3976 Type calc_public_static_type(const IndexData& data,
3977 const ClassInfo* cinfo,
3978 const php::Prop& prop,
3979 SString propName) {
3980 assertx(prop.attrs & AttrStatic);
3981 assertx(prop.attrs & (AttrPublic|AttrProtected));
3982 assertx(!(prop.attrs & AttrPrivate));
3983 assertx(propName);
3985 // We haven't recorded any public static information yet, or we saw
3986 // a set using both an unknown class and prop name.
3987 if (data.allPublicSPropsUnknown) return TInitCell;
3989 // The type we know from sets using a known class and prop name.
3990 auto const knownClsPart = [&] {
3991 auto const it = cinfo->publicStaticProps.find(propName);
3992 if (it == end(cinfo->publicStaticProps)) return TInitCell;
3993 return it->second.inferredType;
3994 }();
3996 // Const properties don't need the unknown part because they can
3997 // only be set with an initial value, which will always be known.
3998 if (prop.attrs & AttrIsConst) return knownClsPart;
4000 // The type we know from sets with just a known prop name. Since any
4001 // of those potentially could be affecting our desired prop, we need
4002 // to union those into the result.
4003 auto const unknownClsPart = [&] {
4004 auto const it = data.unknownClassSProps.find(propName);
4005 if (it == end(data.unknownClassSProps)) return TBottom;
4006 return it->second.first;
4007 }();
4009 // NB: Type can be TBottom here if the property is never set to a
4010 // value which can satisfy its type constraint. Such properties
4011 // can't exist at runtime.
4013 return remove_uninit(union_of(knownClsPart, unknownClsPart));
4016 // Test if the given property (declared in `cls') is accessible in the
4017 // given context (null if we're not in a class).
4018 bool static_is_accessible(const ClassInfo* clsCtx,
4019 const ClassInfo* cls,
4020 const php::Prop& prop) {
4021 assertx(prop.attrs & AttrStatic);
4022 switch (prop.attrs & (AttrPublic|AttrProtected|AttrPrivate)) {
4023 case AttrPublic:
4024 // Public is accessible everywhere
4025 return true;
4026 case AttrProtected:
4027 // Protected is accessible from both derived classes and parent
4028 // classes
4029 return clsCtx && (clsCtx->derivedFrom(*cls) || cls->derivedFrom(*clsCtx));
4030 case AttrPrivate:
4031 // Private is only accessible from within the declared class
4032 return clsCtx == cls;
4034 always_assert(false);
4037 // Return true if the given class can possibly throw when its
4038 // initialized. Initialization can happen when an object of that class
4039 // is instantiated, or (more importantly) when static properties are
4040 // accessed.
4041 bool class_init_might_raise(IndexData& data,
4042 Context ctx,
4043 const ClassInfo* cinfo) {
4044 // Check this class and all of its parents for possible inequivalent
4045 // redeclarations or bad initial values.
4046 do {
4047 // Be conservative for now if we have unflattened traits.
4048 if (!cinfo->traitProps.empty()) return true;
4049 if (cinfo->hasBadRedeclareProp) return true;
4050 if (cinfo->hasBadInitialPropValues) {
4051 add_dependency(data, cinfo->cls, ctx, Dep::PropBadInitialValues);
4052 return true;
4054 cinfo = cinfo->parent;
4055 } while (cinfo);
4056 return false;
4060 * Calculate the effects of applying the given type against the
4061 * type-constraints for the given prop. This includes the subtype
4062 * which will succeed (if any), and if the type-constraint check might
4063 * throw.
4065 PropMergeResult<> prop_tc_effects(const Index& index,
4066 const ClassInfo* ci,
4067 const php::Prop& prop,
4068 const Type& val,
4069 bool checkUB) {
4070 assertx(prop.typeConstraint.validForProp());
4072 using R = PropMergeResult<>;
4074 // If we're not actually checking property type-hints, everything
4075 // goes
4076 if (RuntimeOption::EvalCheckPropTypeHints <= 0) return R{ val, TriBool::No };
4078 auto const ctx = Context { nullptr, nullptr, ci->cls };
4080 auto const check = [&] (const TypeConstraint& tc, const Type& t) {
4081 // If the type as is satisfies the constraint, we won't throw and
4082 // the type is unchanged.
4083 if (index.satisfies_constraint(ctx, t, tc)) return R{ t, TriBool::No };
4084 // Otherwise adjust the type. If we get a Bottom we'll definitely
4085 // throw. We already know the type doesn't completely satisfy the
4086 // constraint, so we'll at least maybe throw.
4087 auto adjusted = adjust_type_for_prop(index, *ctx.cls, &tc, t);
4088 auto const throws = yesOrMaybe(adjusted.subtypeOf(BBottom));
4089 return R{ std::move(adjusted), throws };
4092 // First check the main type-constraint.
4093 auto result = check(prop.typeConstraint, val);
4094 // If we're not checking generics upper-bounds, or if we already
4095 // know we'll fail, we're done.
4096 if (!checkUB ||
4097 RuntimeOption::EvalEnforceGenericsUB <= 0 ||
4098 result.throws == TriBool::Yes) {
4099 return result;
4102 // Otherwise check every generic upper-bound. We'll feed the
4103 // narrowed type into each successive round. If we reach the point
4104 // where we'll know we'll definitely fail, just stop.
4105 for (auto ub : prop.ubs) {
4106 applyFlagsToUB(ub, prop.typeConstraint);
4107 auto r = check(ub, result.adjusted);
4108 result.throws &= r.throws;
4109 result.adjusted = std::move(r.adjusted);
4110 if (result.throws == TriBool::Yes) break;
4113 return result;
4117 * Lookup data for the static property named `propName', starting from
4118 * the specified class `start'. If `propName' is nullptr, then any
4119 * accessible static property in the class hierarchy is considered. If
4120 * `startOnly' is specified, if the property isn't found in `start',
4121 * it is treated as a lookup failure. Otherwise the lookup continues
4122 * in all parent classes of `start', until a property is found, or
4123 * until all parent classes have been exhausted (`startOnly' is used
4124 * to avoid redundant class hierarchy walks). `clsCtx' is the current
4125 * context, converted to a ClassInfo* (or nullptr if not in a class).
4127 PropLookupResult<> lookup_static_impl(IndexData& data,
4128 Context ctx,
4129 const ClassInfo* clsCtx,
4130 const PropertiesInfo& privateProps,
4131 const ClassInfo* start,
4132 SString propName,
4133 bool startOnly) {
4134 ITRACE(
4135 6, "lookup_static_impl: {} {} {}\n",
4136 clsCtx ? clsCtx->cls->name->toCppString() : std::string{"-"},
4137 start->cls->name,
4138 propName ? propName->toCppString() : std::string{"*"}
4140 Trace::Indent _;
4142 auto const type = [&] (const php::Prop& prop,
4143 const ClassInfo* ci) {
4144 switch (prop.attrs & (AttrPublic|AttrProtected|AttrPrivate)) {
4145 case AttrPublic:
4146 case AttrProtected:
4147 return calc_public_static_type(data, ci, prop, prop.name);
4148 case AttrPrivate: {
4149 assertx(clsCtx == ci);
4150 auto const& privateStatics = privateProps.privateStatics();
4151 auto const it = privateStatics.find(prop.name);
4152 if (it == end(privateStatics)) return TInitCell;
4153 return remove_uninit(it->second.ty);
4156 always_assert(false);
4159 auto const initMightRaise = class_init_might_raise(data, ctx, start);
4161 auto const fromProp = [&] (const php::Prop& prop,
4162 const ClassInfo* ci) {
4163 // The property was definitely found. Compute its attributes
4164 // from the prop metadata.
4165 return PropLookupResult<>{
4166 type(prop, ci),
4167 propName,
4168 TriBool::Yes,
4169 yesOrNo(prop.attrs & AttrIsConst),
4170 yesOrNo(prop.attrs & AttrIsReadOnly),
4171 yesOrNo(prop.attrs & AttrLateInit),
4172 initMightRaise
4176 auto const notFound = [&] {
4177 // The property definitely wasn't found.
4178 return PropLookupResult<>{
4179 TBottom,
4180 propName,
4181 TriBool::No,
4182 TriBool::No,
4183 TriBool::No,
4184 TriBool::No,
4185 false
4189 if (!propName) {
4190 // We don't statically know the prop name. Walk up the hierarchy
4191 // and union the data for any accessible static property.
4192 ITRACE(4, "no prop name, considering all accessible\n");
4193 auto result = notFound();
4194 visit_parent_cinfo(
4195 start,
4196 [&] (const ClassInfo* ci) {
4197 for (auto const& prop : ci->cls->properties) {
4198 if (!(prop.attrs & AttrStatic) ||
4199 !static_is_accessible(clsCtx, ci, prop)) {
4200 ITRACE(
4201 6, "skipping inaccessible {}::${}\n",
4202 ci->cls->name, prop.name
4204 continue;
4206 if (ctx.unit) {
4207 add_dependency(data, prop.name, ctx, Dep::PublicSPropName);
4209 auto const r = fromProp(prop, ci);
4210 ITRACE(6, "including {}:${} {}\n", ci->cls->name, prop.name, show(r));
4211 result |= r;
4213 // If we're only interested in the starting class, don't walk
4214 // up to the parents.
4215 return startOnly;
4218 return result;
4221 // We statically know the prop name. Walk up the hierarchy and stop
4222 // at the first matching property and use that data.
4223 assertx(!startOnly);
4224 auto const result = visit_parent_cinfo(
4225 start,
4226 [&] (const ClassInfo* ci) -> folly::Optional<PropLookupResult<>> {
4227 for (auto const& prop : ci->cls->properties) {
4228 if (prop.name != propName) continue;
4229 // We have a matching prop. If its not static or not
4230 // accessible, the access will not succeed.
4231 if (!(prop.attrs & AttrStatic) ||
4232 !static_is_accessible(clsCtx, ci, prop)) {
4233 ITRACE(
4234 6, "{}::${} found but inaccessible, stopping\n",
4235 ci->cls->name, propName
4237 return notFound();
4239 // Otherwise its a match
4240 if (ctx.unit) add_dependency(data, propName, ctx, Dep::PublicSPropName);
4241 auto const r = fromProp(prop, ci);
4242 ITRACE(6, "found {}:${} {}\n", ci->cls->name, propName, show(r));
4243 return r;
4245 return folly::none;
4248 if (!result) {
4249 // We walked up to all of the base classes and didn't find a
4250 // property with a matching name. The access will fail.
4251 ITRACE(6, "nothing found\n");
4252 return notFound();
4254 return *result;
4258 * Lookup the static property named `propName', starting from the
4259 * specified class `start'. If an accessible property is found, then
4260 * merge the given type `val' into the already known type for that
4261 * property. If `propName' is nullptr, then any accessible static
4262 * property in the class hierarchy is considered. If `startOnly' is
4263 * specified, if the property isn't found in `start', then the nothing
4264 * is done. Otherwise the lookup continues in all parent classes of
4265 * `start', until a property is found, or until all parent classes
4266 * have been exhausted (`startOnly' is to avoid redundant class
4267 * hierarchy walks). `clsCtx' is the current context, converted to a
4268 * ClassInfo* (or nullptr if not in a class). If `ignoreConst' is
4269 * false, then AttrConst properties will not have their type
4270 * modified. `mergePublic' is a lambda with the logic to merge a type
4271 * for a public property (this is needed to avoid cyclic
4272 * dependencies).
4274 template <typename F>
4275 PropMergeResult<> merge_static_type_impl(IndexData& data,
4276 Context ctx,
4277 F mergePublic,
4278 PropertiesInfo& privateProps,
4279 const ClassInfo* clsCtx,
4280 const ClassInfo* start,
4281 SString propName,
4282 const Type& val,
4283 bool checkUB,
4284 bool ignoreConst,
4285 bool mustBeReadOnly,
4286 bool startOnly) {
4287 ITRACE(
4288 6, "merge_static_type_impl: {} {} {} {}\n",
4289 clsCtx ? clsCtx->cls->name->toCppString() : std::string{"-"},
4290 start->cls->name,
4291 propName ? propName->toCppString() : std::string{"*"},
4292 show(val)
4294 Trace::Indent _;
4296 assertx(!val.subtypeOf(BBottom));
4298 // Perform the actual merge for a given property, returning the
4299 // effects of that merge.
4300 auto const merge = [&] (const php::Prop& prop, const ClassInfo* ci) {
4301 // First calculate the effects of the type-constraint.
4302 auto const effects = prop_tc_effects(*data.m_index, ci, prop, val, checkUB);
4303 // No point in merging if the type-constraint will always fail.
4304 if (effects.throws == TriBool::Yes) {
4305 ITRACE(
4306 6, "tc would throw on {}::${} with {}, skipping\n",
4307 ci->cls->name, prop.name, show(val)
4309 return effects;
4311 assertx(!effects.adjusted.subtypeOf(BBottom));
4313 ITRACE(
4314 6, "merging {} into {}::${}\n",
4315 show(effects.adjusted), ci->cls->name, prop.name
4318 switch (prop.attrs & (AttrPublic|AttrProtected|AttrPrivate)) {
4319 case AttrPublic:
4320 case AttrProtected:
4321 mergePublic(ci, prop, unctx(effects.adjusted));
4322 return effects;
4323 case AttrPrivate: {
4324 assertx(clsCtx == ci);
4325 auto& statics = privateProps.privateStatics();
4326 auto const it = statics.find(prop.name);
4327 if (it != end(statics)) {
4328 ITRACE(6, " {} |= {}\n", show(it->second.ty), show(effects.adjusted));
4329 it->second.ty |= unctx(effects.adjusted);
4331 return effects;
4334 always_assert(false);
4337 // If we don't find a property, then the mutation will definitely
4338 // fail.
4339 auto const notFound = [&] {
4340 return PropMergeResult<>{
4341 TBottom,
4342 TriBool::Yes
4346 if (!propName) {
4347 // We don't statically know the prop name. Walk up the hierarchy
4348 // and merge the type for any accessible static property.
4349 ITRACE(6, "no prop name, considering all accessible\n");
4350 auto result = notFound();
4351 visit_parent_cinfo(
4352 start,
4353 [&] (const ClassInfo* ci) {
4354 for (auto const& prop : ci->cls->properties) {
4355 if (!(prop.attrs & AttrStatic) ||
4356 !static_is_accessible(clsCtx, ci, prop)) {
4357 ITRACE(
4358 6, "skipping inaccessible {}::${}\n",
4359 ci->cls->name, prop.name
4361 continue;
4363 if (!ignoreConst && (prop.attrs & AttrIsConst)) {
4364 ITRACE(6, "skipping const {}::${}\n", ci->cls->name, prop.name);
4365 continue;
4367 if (mustBeReadOnly && !(prop.attrs & AttrIsReadOnly)) {
4368 ITRACE(6, "skipping mutable property that must be readonly {}::${}\n",
4369 ci->cls->name, prop.name);
4370 continue;
4372 result |= merge(prop, ci);
4374 return startOnly;
4377 return result;
4380 // We statically know the prop name. Walk up the hierarchy and stop
4381 // at the first matching property and merge the type there.
4382 assertx(!startOnly);
4383 auto result = visit_parent_cinfo(
4384 start,
4385 [&] (const ClassInfo* ci) -> folly::Optional<PropMergeResult<>> {
4386 for (auto const& prop : ci->cls->properties) {
4387 if (prop.name != propName) continue;
4388 // We found a property with the right name, but its
4389 // inaccessible from this context (or not even static). This
4390 // mutation will fail, so we don't need to modify the type.
4391 if (!(prop.attrs & AttrStatic) ||
4392 !static_is_accessible(clsCtx, ci, prop)) {
4393 ITRACE(
4394 6, "{}::${} found but inaccessible, stopping\n",
4395 ci->cls->name, propName
4397 return notFound();
4399 // Mutations to AttrConst properties will fail as well, unless
4400 // it we want to override that behavior.
4401 if (!ignoreConst && (prop.attrs & AttrIsConst)) {
4402 ITRACE(
4403 6, "{}:${} found but const, stopping\n",
4404 ci->cls->name, propName
4406 return notFound();
4408 if (mustBeReadOnly && !(prop.attrs & AttrIsReadOnly)) {
4409 ITRACE(
4410 6, "{}:${} found but is mutable and must be readonly, stopping\n",
4411 ci->cls->name, propName
4413 return notFound();
4415 return merge(prop, ci);
4417 return folly::none;
4420 if (!result) {
4421 ITRACE(6, "nothing found\n");
4422 return notFound();
4425 // If the mutation won't throw, we still need to check if the class
4426 // initialization can throw. If we might already throw (or
4427 // definitely will throw), this doesn't matter.
4428 if (result->throws == TriBool::No) {
4429 return PropMergeResult<>{
4430 std::move(result->adjusted),
4431 maybeOrNo(class_init_might_raise(data, ctx, start))
4434 return *result;
4437 //////////////////////////////////////////////////////////////////////
4441 //////////////////////////////////////////////////////////////////////
4442 namespace {
4443 template<typename T>
4444 void buildTypeInfoData(TypeInfoData<T>& tid,
4445 const ISStringToOneT<const T*>& tmap) {
4446 for (auto const& elm : tmap) {
4447 auto const t = elm.second;
4448 auto const addUser = [&] (SString rName) {
4449 tid.users[rName].push_back(t);
4450 auto const count = tmap.count(rName);
4451 tid.depCounts[t] += count ? count : 1;
4453 PhpTypeHelper<T>::process_bases(t, addUser);
4455 if (!tid.depCounts.count(t)) {
4456 FTRACE(5, "Adding no-dep {} {}:{} to queue\n",
4457 PhpTypeHelper<T>::name(), t->name, (void*)t);
4458 // make sure that closure is first, because we end up calling
4459 // preresolve directly on closures created by trait
4460 // flattening, which assumes all dependencies are satisfied.
4461 if (tid.queue.size() && t->name == s_Closure.get()) {
4462 tid.queue.push_back(tid.queue[0]);
4463 tid.queue[0] = t;
4464 } else {
4465 tid.queue.push_back(t);
4467 } else {
4468 FTRACE(6, "{} {}:{} has {} deps\n",
4469 PhpTypeHelper<T>::name(), t->name, (void*)t, tid.depCounts[t]);
4472 tid.cqBack = tid.queue.size();
4473 tid.queue.resize(tmap.size());
4476 SString nameFromInfo(const RecordInfo* r) { return r->rec->name; }
4477 SString nameFromInfo(const ClassInfo* c) { return c->cls->name; }
4479 template <typename T>
4480 void updatePreResolveDeps(
4481 TypeInfoData<T>& tid,
4482 const PreResolveUpdates<typename PhpTypeHelper<T>::Info>& updates) {
4483 for (auto const info : updates.updateDeps) {
4484 auto const& users = tid.users[nameFromInfo(info)];
4485 for (auto const tu : users) {
4486 auto const it = tid.depCounts.find(tu);
4487 if (it == tid.depCounts.end()) {
4488 assertx(tid.hasPseudoCycles);
4489 continue;
4491 auto& depCount = it->second;
4492 assertx(depCount);
4493 if (!--depCount) {
4494 tid.depCounts.erase(it);
4495 ITRACE(5, " enqueue: {}:{}\n", tu->name, (void*)tu);
4496 tid.queue[tid.cqBack++] = tu;
4497 } else {
4498 ITRACE(6, " depcount: {}:{} = {}\n", tu->name, (void*)tu, depCount);
4504 void commitPreResolveUpdates(IndexData& index,
4505 TypeInfoData<php::Record>& tid,
4506 std::vector<RecPreResolveUpdates>& updates) {
4507 parallel::parallel(
4508 [&] {
4509 for (auto const& u : updates) updatePreResolveDeps(tid, u);
4511 [&] {
4512 for (auto& u : updates) {
4513 for (size_t i = 0; i < u.newInfos.size(); ++i) {
4514 auto& rinfo = u.newInfos[i];
4515 auto const UNUSED it =
4516 index.recordInfo.emplace(rinfo->rec->name, rinfo.get());
4517 assertx(it.second);
4518 index.allRecordInfos.emplace_back(std::move(rinfo));
4525 void commitPreResolveUpdates(IndexData& index,
4526 TypeInfoData<php::Class>& tid,
4527 std::vector<ClsPreResolveUpdates>& updates) {
4528 parallel::parallel(
4529 [&] {
4530 for (auto const& u : updates) updatePreResolveDeps(tid, u);
4532 [&] {
4533 for (auto& u : updates) {
4534 for (size_t i = 0; i < u.newInfos.size(); ++i) {
4535 auto& cinfo = u.newInfos[i];
4536 auto const UNUSED it =
4537 index.classInfo.emplace(cinfo->cls->name, cinfo.get());
4538 assertx(it.second);
4539 index.allClassInfos.emplace_back(std::move(cinfo));
4543 [&] {
4544 for (auto& u : updates) {
4545 for (auto& cns : u.removeNoOverride) {
4546 const_cast<php::Const*>(cns.get())->isNoOverride = false;
4550 [&] {
4551 for (auto& u : updates) {
4552 for (auto const& p : u.extraMethods) {
4553 index.classExtraMethodMap[p.first].insert(
4554 p.second.begin(),
4555 p.second.end()
4560 [&] {
4561 for (auto& u : updates) {
4562 for (auto const& p : u.closures) {
4563 auto& map = index.classClosureMap[p.first];
4564 map.insert(map.end(), p.second.begin(), p.second.end());
4568 [&] {
4569 for (auto& u : updates) {
4570 for (auto const c : u.newClosures) index.classes.emplace(c->name, c);
4573 [&] {
4574 for (auto& u : updates) {
4575 for (auto& p : u.newClasses) {
4576 auto unit = std::get<1>(p);
4577 auto const idx = std::get<2>(p);
4578 if (unit->classes.size() <= idx) unit->classes.resize(idx+1);
4579 unit->classes[idx] = std::move(std::get<0>(p));
4586 template<typename T>
4587 void preresolveTypes(php::Program* program,
4588 IndexData& index,
4589 TypeInfoData<T>& tid,
4590 const ISStringToOneT<TypeInfo<T>*>& tmap) {
4591 auto round = uint32_t{0};
4592 while (true) {
4593 if (tid.cqFront == tid.cqBack) {
4594 // we've consumed everything where all dependencies are
4595 // satisfied. There may still be some pseudo-cycles that can
4596 // be broken though.
4598 // eg if A extends B and B' extends A', we'll resolve B and
4599 // A', and then end up here, since both A and B' still have
4600 // one dependency. But both A and B' can be resolved at this
4601 // point
4602 for (auto it = tid.depCounts.begin();
4603 it != tid.depCounts.end();) {
4604 auto canResolve = true;
4605 auto const checkCanResolve = [&] (SString name) {
4606 if (canResolve) canResolve = tmap.count(name);
4608 PhpTypeHelper<T>::process_bases(it->first, checkCanResolve);
4609 if (canResolve) {
4610 FTRACE(2, "Breaking pseudo-cycle for {} {}:{}\n",
4611 PhpTypeHelper<T>::name(), it->first->name, (void*)it->first);
4612 tid.queue[tid.cqBack++] = it->first;
4613 it = tid.depCounts.erase(it);
4614 tid.hasPseudoCycles = true;
4615 } else {
4616 ++it;
4619 if (tid.cqFront == tid.cqBack) break;
4622 auto const workitems = tid.cqBack - tid.cqFront;
4623 auto updates = [&] {
4624 trace_time trace(
4625 "preresolve",
4626 folly::sformat("round {} -- {} work items", round, workitems)
4629 using U = PreResolveUpdates<typename PhpTypeHelper<T>::Info>;
4630 typename U::UnitNumClasses numClasses;
4632 return parallel::gen(
4633 workitems,
4634 [&] (size_t idx) {
4635 auto const t = tid.queue[idx + tid.cqFront];
4636 Trace::Bump bumper{
4637 Trace::hhbbc_index, kSystemLibBump, is_systemlib_part(*t->unit)
4639 (void)bumper;
4640 U updates;
4641 updates.numClasses = &numClasses;
4642 preresolve(program, index, t, updates);
4643 return updates;
4646 }();
4648 ++round;
4649 tid.cqFront += workitems;
4651 trace_time trace("update");
4652 commitPreResolveUpdates(index, tid, updates);
4655 trace_time trace("preresolve clear state");
4656 parallel::for_each(
4657 index.allClassInfos,
4658 [&] (const std::unique_ptr<ClassInfo>& cinfo) {
4659 cinfo->preResolveState.reset();
4664 } //namespace
4666 Index::Index(php::Program* program)
4667 : m_data(std::make_unique<IndexData>(this))
4669 trace_time tracer("create index");
4671 m_data->arrTableBuilder.reset(new ArrayTypeTable::Builder());
4673 add_system_constants_to_index(*m_data);
4676 trace_time trace_add_units("add units to index");
4677 for (auto& u : program->units) {
4678 add_unit_to_index(*m_data, *u);
4682 RecordInfoData rid;
4684 trace_time build_record_info_data("build recordinfo data");
4685 buildTypeInfoData(rid, m_data->records);
4689 trace_time preresolve_records("preresolve records");
4690 preresolveTypes(program, *m_data, rid, m_data->recordInfo);
4693 ClassInfoData cid;
4695 trace_time build_class_info_data("build classinfo data");
4696 buildTypeInfoData(cid, m_data->classes);
4700 trace_time preresolve_classes("preresolve classes");
4701 preresolveTypes(program, *m_data, cid, m_data->classInfo);
4704 m_data->funcInfo.resize(program->nextFuncId);
4706 // Part of the index building routines happens before the various asserted
4707 // index invariants hold. These each may depend on computations from
4708 // previous functions, so be careful changing the order here.
4709 compute_subclass_list(*m_data);
4710 clean_86reifiedinit_methods(*m_data); // uses the base class lists
4711 mark_no_override_methods(*m_data);
4712 find_magic_methods(*m_data); // uses the subclass lists
4713 find_mocked_classes(*m_data);
4714 mark_const_props(*m_data);
4715 auto const logging = Trace::moduleEnabledRelease(Trace::hhbbc_time, 1);
4716 m_data->compute_iface_vtables = std::thread([&] {
4717 HphpSessionAndThread _{Treadmill::SessionKind::HHBBC};
4718 auto const enable =
4719 logging && !Trace::moduleEnabledRelease(Trace::hhbbc_time, 1);
4720 Trace::BumpRelease bumper(Trace::hhbbc_time, -1, enable);
4721 compute_iface_vtables(*m_data);
4724 define_func_families(*m_data); // AttrNoOverride, iface_vtables,
4725 // subclass_list
4727 check_invariants(*m_data);
4729 mark_no_override_classes(*m_data);
4731 trace_time tracer_2("initialize return types");
4732 std::vector<const php::Func*> all_funcs;
4733 all_funcs.reserve(m_data->funcs.size() + m_data->methods.size());
4734 for (auto const fn : m_data->funcs) {
4735 all_funcs.push_back(fn.second);
4737 for (auto const fn : m_data->methods) {
4738 all_funcs.push_back(fn.second);
4740 parallel::for_each(
4741 all_funcs,
4742 [&] (const php::Func* f) { init_return_type(f); }
4746 // Defined here so IndexData is a complete type for the unique_ptr
4747 // destructor.
4748 Index::~Index() {}
4750 //////////////////////////////////////////////////////////////////////
4752 void Index::mark_no_bad_redeclare_props(php::Class& cls) const {
4754 * Keep a list of properties which have not yet been found to redeclare
4755 * anything inequivalently. Start out by putting everything on the list. Then
4756 * walk up the inheritance chain, removing collisions as we find them.
4758 std::vector<php::Prop*> props;
4759 for (auto& prop : cls.properties) {
4760 if (prop.attrs & (AttrStatic | AttrPrivate)) {
4761 // Static and private properties never redeclare anything so need not be
4762 // considered.
4763 attribute_setter(prop.attrs, true, AttrNoBadRedeclare);
4764 continue;
4766 attribute_setter(prop.attrs, false, AttrNoBadRedeclare);
4767 props.emplace_back(&prop);
4770 auto currentCls = [&]() -> const ClassInfo* {
4771 auto const rcls = resolve_class(&cls);
4772 if (rcls.val.left()) return nullptr;
4773 return rcls.val.right();
4774 }();
4775 // If there's one more than one resolution for the class, be conservative and
4776 // we'll treat everything as possibly redeclaring.
4777 if (!currentCls) props.clear();
4779 while (!props.empty()) {
4780 auto const parent = currentCls->parent;
4781 if (!parent) {
4782 // No parent. We're done, so anything left on the prop list is
4783 // AttrNoBadRedeclare.
4784 for (auto& prop : props) {
4785 attribute_setter(prop->attrs, true, AttrNoBadRedeclare);
4787 break;
4790 auto const findParentProp = [&] (SString name) -> const php::Prop* {
4791 for (auto& prop : parent->cls->properties) {
4792 if (prop.name == name) return &prop;
4794 for (auto& prop : parent->traitProps) {
4795 if (prop.name == name) return &prop;
4797 return nullptr;
4800 // Remove any properties which collide with the current class.
4802 auto const propRedeclares = [&] (php::Prop* prop) {
4803 auto const pprop = findParentProp(prop->name);
4804 if (!pprop) return false;
4806 // We found a property being redeclared. Check if the type-hints on
4807 // the two are equivalent.
4808 auto const equivOneTCPair =
4809 [&](const TypeConstraint& tc1, const TypeConstraint& tc2) {
4810 // Try the cheap check first, use the index otherwise. Two
4811 // type-constraints are equivalent if all the possible values of one
4812 // satisfies the other, and vice-versa.
4813 if (!tc1.maybeInequivalentForProp(tc2)) return true;
4814 return
4815 satisfies_constraint(
4816 Context{},
4817 lookup_constraint(Context{}, tc1),
4819 ) && satisfies_constraint(
4820 Context{},
4821 lookup_constraint(Context{}, tc2),
4825 auto const equiv = [&] {
4826 if (!equivOneTCPair(prop->typeConstraint, pprop->typeConstraint)) {
4827 return false;
4829 for (auto ub : prop->ubs) {
4830 applyFlagsToUB(ub, prop->typeConstraint);
4831 auto foundEquiv = false;
4832 for (auto pub : pprop->ubs) {
4833 applyFlagsToUB(pub, pprop->typeConstraint);
4834 if (equivOneTCPair(ub, pub)) {
4835 foundEquiv = true;
4836 break;
4839 if (!foundEquiv) return false;
4841 return true;
4843 // If the property in the parent is static or private, the property in
4844 // the child isn't actually redeclaring anything. Otherwise, if the
4845 // type-hints are equivalent, remove this property from further
4846 // consideration and mark it as AttrNoBadRedeclare.
4847 if ((pprop->attrs & (AttrStatic | AttrPrivate)) || equiv()) {
4848 attribute_setter(prop->attrs, true, AttrNoBadRedeclare);
4850 return true;
4853 props.erase(
4854 std::remove_if(props.begin(), props.end(), propRedeclares),
4855 props.end()
4858 currentCls = parent;
4861 auto const possibleOverride =
4862 std::any_of(
4863 cls.properties.begin(),
4864 cls.properties.end(),
4865 [&](const php::Prop& prop) { return !(prop.attrs & AttrNoBadRedeclare); }
4868 // Mark all resolutions of this class as having any possible bad redeclaration
4869 // props, even if there's not an unique resolution.
4870 auto const it = m_data->classInfo.find(cls.name);
4871 if (it != end(m_data->classInfo)) {
4872 auto const cinfo = it->second;
4873 if (cinfo->cls == &cls) {
4874 cinfo->hasBadRedeclareProp = possibleOverride;
4880 * Rewrite the initial values for any AttrSystemInitialValue properties. If the
4881 * properties' type-hint does not admit null values, change the initial value to
4882 * one (if possible) to one that is not null. This is only safe to do so if the
4883 * property is not redeclared in a derived class or if the redeclaration does
4884 * not have a null system provided default value. Otherwise, a property can have
4885 * a null value (even if its type-hint doesn't allow it) without the JIT
4886 * realizing that its possible.
4888 * Note that this ignores any unflattened traits. This is okay because
4889 * properties pulled in from traits which match an already existing property
4890 * can't change the initial value. The runtime will clear AttrNoImplicitNullable
4891 * on any property pulled from the trait if it doesn't match an existing
4892 * property.
4894 void Index::rewrite_default_initial_values(php::Program& program) const {
4895 trace_time tracer("rewrite default initial values");
4898 * Use dataflow across the whole program class hierarchy. Start from the
4899 * classes which have no derived classes and flow up the hierarchy. We flow
4900 * the set of properties which have been assigned a null system provided
4901 * default value. If a property with such a null value flows into a class
4902 * which declares a property with the same name (and isn't static or private),
4903 * than that property is forced to be null as well.
4905 using PropSet = folly::F14FastSet<SString>;
4906 using OutState = folly::F14FastMap<const ClassInfo*, PropSet>;
4907 using Worklist = folly::F14FastSet<const ClassInfo*>;
4909 OutState outStates;
4910 outStates.reserve(m_data->allClassInfos.size());
4912 // List of Class' still to process this iteration
4913 using WorkList = std::vector<const ClassInfo*>;
4914 using WorkSet = folly::F14FastSet<const ClassInfo*>;
4916 WorkList workList;
4917 WorkSet workSet;
4918 auto const enqueue = [&] (const ClassInfo& cls) {
4919 auto const result = workSet.insert(&cls);
4920 if (!result.second) return;
4921 workList.emplace_back(&cls);
4924 // Start with all the leaf classes
4925 for (auto const& cinfo : m_data->allClassInfos) {
4926 auto const isLeaf = [&] {
4927 for (auto const& sub : cinfo->subclassList) {
4928 if (sub != cinfo.get()) return false;
4930 return true;
4931 }();
4932 if (isLeaf) enqueue(*cinfo);
4935 WorkList oldWorkList;
4936 int iter = 1;
4937 while (!workList.empty()) {
4938 FTRACE(
4939 4, "rewrite_default_initial_values round #{}: {} items\n",
4940 iter, workList.size()
4942 ++iter;
4944 std::swap(workList, oldWorkList);
4945 workList.clear();
4946 workSet.clear();
4947 for (auto const& cinfo : oldWorkList) {
4948 // Retrieve the set of properties which are flowing into this Class and
4949 // have to be null.
4950 auto inState = [&] () -> folly::Optional<PropSet> {
4951 PropSet in;
4952 for (auto const& sub : cinfo->subclassList) {
4953 if (sub == cinfo || sub->parent != cinfo) continue;
4954 auto const it = outStates.find(sub);
4955 if (it == outStates.end()) return folly::none;
4956 in.insert(it->second.begin(), it->second.end());
4958 return in;
4959 }();
4960 if (!inState) continue;
4962 // Modify the in-state depending on the properties declared on this Class
4963 auto const cls = cinfo->cls;
4964 for (auto const& prop : cls->properties) {
4965 if (prop.attrs & (AttrStatic | AttrPrivate)) {
4966 // Private or static properties can't be redeclared
4967 inState->erase(prop.name);
4968 continue;
4970 // Ignore properties which have actual user provided initial values or
4971 // are LateInit.
4972 if (!(prop.attrs & AttrSystemInitialValue) ||
4973 (prop.attrs & AttrLateInit)) {
4974 continue;
4976 // Forced to be null, nothing to do
4977 if (inState->count(prop.name) > 0) continue;
4979 // Its not forced to be null. Find a better default value. If its null
4980 // anyways, force any properties this redeclares to be null as well.
4981 auto const defaultValue = prop.typeConstraint.defaultValue();
4982 if (defaultValue.m_type == KindOfNull) inState->insert(prop.name);
4985 // Push the in-state to the out-state.
4986 auto const result = outStates.emplace(std::make_pair(cinfo, *inState));
4987 if (result.second) {
4988 if (cinfo->parent) enqueue(*cinfo->parent);
4989 } else {
4990 // There shouldn't be cycles in the inheritance tree, so the out state
4991 // of Class', once set, should never change.
4992 assertx(result.first->second == *inState);
4997 // Now that we've processed all the classes, rewrite the property initial
4998 // values, unless they are forced to be nullable.
4999 for (auto& unit : program.units) {
5000 for (auto& c : unit->classes) {
5001 if (is_closure(*c)) continue;
5003 auto const out = [&] () -> folly::Optional<PropSet> {
5004 folly::Optional<PropSet> props;
5005 auto const range = m_data->classInfo.equal_range(c->name);
5006 for (auto it = range.first; it != range.second; ++it) {
5007 if (it->second->cls != c.get()) continue;
5008 auto const outStateIt = outStates.find(it->second);
5009 if (outStateIt == outStates.end()) return folly::none;
5010 if (!props) props.emplace();
5011 props->insert(outStateIt->second.begin(), outStateIt->second.end());
5013 return props;
5014 }();
5016 for (auto& prop : c->properties) {
5017 auto const nullable = [&] {
5018 if (!(prop.attrs & (AttrStatic | AttrPrivate))) {
5019 if (!out || out->count(prop.name)) return true;
5021 if (!(prop.attrs & AttrSystemInitialValue)) return false;
5022 return prop.typeConstraint.defaultValue().m_type == KindOfNull;
5023 }();
5025 attribute_setter(prop.attrs, !nullable, AttrNoImplicitNullable);
5026 if (!(prop.attrs & AttrSystemInitialValue)) continue;
5027 if (prop.val.m_type == KindOfUninit) {
5028 assertx(prop.attrs & AttrLateInit);
5029 continue;
5032 prop.val = nullable
5033 ? make_tv<KindOfNull>()
5034 : prop.typeConstraint.defaultValue();
5040 const CompactVector<const php::Class*>*
5041 Index::lookup_closures(const php::Class* cls) const {
5042 auto const it = m_data->classClosureMap.find(cls);
5043 if (it != end(m_data->classClosureMap)) {
5044 return &it->second;
5046 return nullptr;
5049 const hphp_fast_set<const php::Func*>*
5050 Index::lookup_extra_methods(const php::Class* cls) const {
5051 if (cls->attrs & AttrNoExpandTrait) return nullptr;
5052 auto const it = m_data->classExtraMethodMap.find(cls);
5053 if (it != end(m_data->classExtraMethodMap)) {
5054 return &it->second;
5056 return nullptr;
5058 //////////////////////////////////////////////////////////////////////
5060 template<typename T>
5061 folly::Optional<T> Index::resolve_type_impl(SString name) const {
5062 auto const& infomap = m_data->infoMap<T>();
5063 auto const& omap = m_data->infoMap<typename ResTypeHelper<T>::OtherT>();
5064 auto const it = infomap.find(name);
5065 if (it != end(infomap)) {
5066 auto const tinfo = it->second;
5068 * If the preresolved [Class|Record]Info is Unique we can give it out.
5070 assertx(tinfo->phpType()->attrs & AttrUnique);
5071 if (debug &&
5072 (omap.count(name) ||
5073 m_data->typeAliases.count(name))) {
5074 std::fprintf(stderr, "non unique \"unique\" %s: %s\n",
5075 ResTypeHelper<T>::name().c_str(),
5076 tinfo->phpType()->name->data());
5078 auto const ta = m_data->typeAliases.find(name);
5079 if (ta != end(m_data->typeAliases)) {
5080 std::fprintf(stderr, " and type-alias %s\n",
5081 ta->second->name->data());
5084 auto const to = omap.find(name);
5085 if (to != end(omap)) {
5086 std::fprintf(stderr, " and %s %s\n",
5087 ResTypeHelper<typename ResTypeHelper<T>::OtherT>::
5088 name().c_str(),
5089 to->second->phpType()->name->data());
5091 always_assert(0);
5093 return T { tinfo };
5095 // We refuse to have name-only resolutions of enums and typeAliases,
5096 // so that all name only resolutions can be treated as records or classes.
5097 if (!m_data->enums.count(name) &&
5098 !m_data->typeAliases.count(name) &&
5099 !omap.count(name)) {
5100 return T { name };
5103 return folly::none;
5106 folly::Optional<res::Record> Index::resolve_record(SString recName) const {
5107 recName = normalizeNS(recName);
5108 return resolve_type_impl<res::Record>(recName);
5111 //////////////////////////////////////////////////////////////////////
5113 res::Class Index::resolve_class(const php::Class* cls) const {
5115 auto const it = m_data->classInfo.find(cls->name);
5116 if (it != end(m_data->classInfo)) {
5117 auto const cinfo = it->second;
5118 if (cinfo->cls == cls) {
5119 return res::Class { cinfo };
5123 // We know its a class, not an enum or type alias, so return
5124 // by name
5125 return res::Class { cls->name.get() };
5128 folly::Optional<res::Class> Index::resolve_class(Context ctx,
5129 SString clsName) const {
5130 clsName = normalizeNS(clsName);
5132 if (ctx.cls) {
5133 if (ctx.cls->name->isame(clsName)) {
5134 return resolve_class(ctx.cls);
5136 if (ctx.cls->parentName && ctx.cls->parentName->isame(clsName)) {
5137 if (auto const parent = resolve_class(ctx.cls).parent()) return parent;
5141 return resolve_type_impl<res::Class>(clsName);
5144 folly::Optional<res::Class> Index::selfCls(const Context& ctx) const {
5145 if (!ctx.cls || is_used_trait(*ctx.cls)) return folly::none;
5146 return resolve_class(ctx.cls);
5149 folly::Optional<res::Class> Index::parentCls(const Context& ctx) const {
5150 if (!ctx.cls || !ctx.cls->parentName) return folly::none;
5151 if (auto const parent = resolve_class(ctx.cls).parent()) return parent;
5152 return resolve_class(ctx, ctx.cls->parentName);
5155 Index::ResolvedInfo<boost::variant<boost::blank,res::Class,res::Record>>
5156 Index::resolve_type_name(SString inName) const {
5157 auto const res = resolve_type_name_internal(inName);
5158 using Ret = boost::variant<boost::blank,res::Class,res::Record>;
5159 auto const val = match<Ret>(
5160 res.value,
5161 [&] (boost::blank) { return Ret{}; },
5162 [&] (SString s) {
5163 return (res.type == AnnotType::Record) ?
5164 Ret{res::Record{s}} : Ret{res::Class{s}};
5166 [&] (ClassInfo* c) { return res::Class{c}; },
5167 [&] (RecordInfo* r) { return res::Record{r}; }
5169 return { res.type, res.nullable, val };
5172 Index::ResolvedInfo<boost::variant<boost::blank,SString,ClassInfo*,RecordInfo*>>
5173 Index::resolve_type_name_internal(SString inName) const {
5174 folly::Optional<hphp_fast_set<const void*>> seen;
5176 auto nullable = false;
5177 auto name = inName;
5179 for (unsigned i = 0; ; ++i) {
5180 name = normalizeNS(name);
5181 auto const rec_it = m_data->recordInfo.find(name);
5182 if (rec_it != end(m_data->recordInfo)) {
5183 auto const rinfo = rec_it->second;
5184 assertx(rinfo->rec->attrs & AttrUnique);
5185 return { AnnotType::Record, nullable, rinfo };
5187 auto const cls_it = m_data->classInfo.find(name);
5188 if (cls_it != end(m_data->classInfo)) {
5189 auto const cinfo = cls_it->second;
5190 assertx(cinfo->cls->attrs & AttrUnique);
5191 if (!(cinfo->cls->attrs & AttrEnum)) {
5192 return { AnnotType::Object, nullable, cinfo };
5194 auto const& tc = cinfo->cls->enumBaseTy;
5195 assertx(!tc.isNullable());
5196 if (tc.type() != AnnotType::Object) {
5197 auto const type = tc.type() == AnnotType::Mixed ?
5198 AnnotType::ArrayKey : tc.type();
5199 return { type, nullable, tc.typeName() };
5201 name = tc.typeName();
5202 } else {
5203 auto const ta_it = m_data->typeAliases.find(name);
5204 if (ta_it == end(m_data->typeAliases)) break;
5205 auto const ta = ta_it->second;
5206 assertx(ta->attrs & AttrUnique);
5207 nullable = nullable || ta->nullable;
5208 if (ta->type != AnnotType::Object) {
5209 return { ta->type, nullable, ta->value.get() };
5211 name = ta->value;
5214 // deal with cycles. Since we don't expect to
5215 // encounter them, just use a counter until we hit a chain length
5216 // of 10, then start tracking the names we resolve.
5217 if (i == 10) {
5218 seen.emplace();
5219 seen->insert(name);
5220 } else if (i > 10) {
5221 if (!seen->insert(name).second) {
5222 return { AnnotType::Object, false, {} };
5227 return { AnnotType::Object, nullable, name };
5230 struct Index::ConstraintResolution {
5231 /* implicit */ ConstraintResolution(Type type)
5232 : type{std::move(type)}
5233 , maybeMixed{false} {}
5234 ConstraintResolution(folly::Optional<Type> type, bool maybeMixed)
5235 : type{std::move(type)}
5236 , maybeMixed{maybeMixed} {}
5238 folly::Optional<Type> type;
5239 bool maybeMixed;
5242 Index::ConstraintResolution Index::resolve_named_type(
5243 const Context& ctx, SString name, const Type& candidate) const {
5245 auto const res = resolve_type_name_internal(name);
5247 if (res.nullable && candidate.subtypeOf(BInitNull)) return TInitNull;
5249 if (res.type == AnnotType::Object) {
5250 auto resolve = [&] (const res::Class& rcls) -> folly::Optional<Type> {
5251 if (!interface_supports_non_objects(rcls.name()) ||
5252 candidate.subtypeOf(BOptObj)) {
5253 return subObj(rcls);
5256 if (candidate.subtypeOf(BOptVec)) {
5257 if (interface_supports_arrlike(rcls.name())) return TVec;
5258 } else if (candidate.subtypeOf(BOptDict)) {
5259 if (interface_supports_arrlike(rcls.name())) return TDict;
5260 } else if (candidate.subtypeOf(BOptKeyset)) {
5261 if (interface_supports_arrlike(rcls.name())) return TKeyset;
5262 } else if (candidate.subtypeOf(BOptStr)) {
5263 if (interface_supports_string(rcls.name())) return TStr;
5264 } else if (candidate.subtypeOf(BOptInt)) {
5265 if (interface_supports_int(rcls.name())) return TInt;
5266 } else if (candidate.subtypeOf(BOptDbl)) {
5267 if (interface_supports_double(rcls.name())) return TDbl;
5269 return folly::none;
5272 auto const val = match<Either<SString, ClassInfo*>>(
5273 res.value,
5274 [&] (boost::blank) { return nullptr; },
5275 [&] (SString s) { return s; },
5276 [&] (ClassInfo* c) { return c; },
5277 [&] (RecordInfo*) { always_assert(false); return nullptr; }
5279 if (val.isNull()) return ConstraintResolution{ folly::none, true };
5280 auto ty = resolve(res::Class { val });
5281 if (ty && res.nullable) *ty = opt(std::move(*ty));
5282 return ConstraintResolution{ std::move(ty), false };
5283 } else if (res.type == AnnotType::Record) {
5284 auto const val = match<Either<SString, RecordInfo*>>(
5285 res.value,
5286 [&] (boost::blank) { return nullptr; },
5287 [&] (SString s) { return s; },
5288 [&] (ClassInfo* c) { always_assert(false); return nullptr; },
5289 [&] (RecordInfo* r) { return r; }
5291 if (val.isNull()) return ConstraintResolution{ folly::none, true };
5292 return subRecord(res::Record { val });
5295 return get_type_for_annotated_type(ctx, res.type, res.nullable,
5296 boost::get<SString>(res.value), candidate);
5299 std::pair<res::Class,php::Class*>
5300 Index::resolve_closure_class(Context ctx, int32_t idx) const {
5301 auto const cls = ctx.unit->classes[idx].get();
5302 auto const rcls = resolve_class(cls);
5304 // Closure classes must be unique and defined in the unit that uses
5305 // the CreateCl opcode, so resolution must succeed.
5306 always_assert_flog(
5307 rcls.resolved(),
5308 "A Closure class ({}) failed to resolve",
5309 cls->name
5312 return { rcls, cls };
5315 res::Class Index::builtin_class(SString name) const {
5316 auto const rcls = resolve_class(Context {}, name);
5317 always_assert_flog(
5318 rcls.has_value() &&
5319 rcls->val.right() &&
5320 (rcls->val.right()->cls->attrs & AttrBuiltin),
5321 "A builtin class ({}) failed to resolve",
5322 name->data()
5324 return *rcls;
5327 res::Func Index::resolve_method(Context ctx,
5328 Type clsType,
5329 SString name) const {
5330 auto name_only = [&] {
5331 return res::Func { this, res::Func::MethodName { name } };
5334 if (!is_specialized_cls(clsType)) {
5335 return name_only();
5337 auto const dcls = dcls_of(clsType);
5338 auto const cinfo = dcls.cls.val.right();
5339 if (!cinfo) return name_only();
5341 // Classes may have more method families than methods. Any such
5342 // method families are guaranteed to all be public so we can do this
5343 // lookup as a last gasp before resorting to name_only().
5344 auto const find_extra_method = [&] {
5345 auto singleMethIt = cinfo->singleMethodFamilies.find(name);
5346 if (singleMethIt != cinfo->singleMethodFamilies.end()) {
5347 return res::Func { this, singleMethIt->second };
5349 auto methIt = cinfo->methodFamilies.find(name);
5350 if (methIt == end(cinfo->methodFamilies)) return name_only();
5351 // If there was a sole implementer we can resolve to a single method, even
5352 // if the method was not declared on the interface itself.
5353 assertx(methIt->second->possibleFuncs().size() > 1);
5354 return res::Func { this, methIt->second };
5357 // Interfaces *only* have the extra methods defined for all
5358 // subclasses
5359 if (cinfo->cls->attrs & AttrInterface) return find_extra_method();
5362 * Whether or not the context class has a private method with the
5363 * same name as the method we're trying to call.
5365 auto const contextMayHavePrivateWithSameName = folly::lazy([&]() -> bool {
5366 if (!ctx.cls) return false;
5367 auto const cls_it = m_data->classInfo.find(ctx.cls->name);
5368 if (cls_it == end(m_data->classInfo)) {
5369 // This class had no pre-resolved ClassInfos, which means it
5370 // always fatals in any way it could be defined, so it doesn't
5371 // matter what we return here (as all methods in the context
5372 // class are unreachable code).
5373 return true;
5375 // Because of traits, each instantiation of the class could have
5376 // different private methods; we need to check them all.
5377 auto const iter = cls_it->second->methods.find(name);
5378 if (iter != end(cls_it->second->methods) &&
5379 iter->second.attrs & AttrPrivate &&
5380 iter->second.topLevel) {
5381 return true;
5383 return false;
5387 * Look up the method in the target class.
5389 auto const methIt = cinfo->methods.find(name);
5390 if (methIt == end(cinfo->methods)) return find_extra_method();
5391 auto const ftarget = methIt->second.func;
5393 // We need to revisit the hasPrivateAncestor code if we start being
5394 // able to look up methods on interfaces (currently they have empty
5395 // method tables).
5396 assertx(!(cinfo->cls->attrs & AttrInterface));
5399 * If our candidate method has a private ancestor, unless it is
5400 * defined on this class, we need to make sure we don't erroneously
5401 * resolve the overriding method if the call is coming from the
5402 * context the defines the private method.
5404 * For now this just gives up if the context and the callee class
5405 * could be related and the context defines a private of the same
5406 * name. (We should actually try to resolve that method, though.)
5408 if (methIt->second.hasPrivateAncestor &&
5409 ctx.cls &&
5410 ctx.cls != ftarget->cls) {
5411 if (could_be_related(ctx.cls, cinfo->cls)) {
5412 if (contextMayHavePrivateWithSameName()) {
5413 return name_only();
5418 auto resolve = [&] {
5419 create_func_info(*m_data, ftarget);
5420 return res::Func { this, mteFromIt(methIt) };
5423 switch (dcls.type) {
5424 case DCls::Exact:
5425 return resolve();
5426 case DCls::Sub:
5427 if (methIt->second.attrs & AttrNoOverride) {
5428 return resolve();
5430 if (!options.FuncFamilies) return name_only();
5433 auto const singleFamIt = cinfo->singleMethodFamilies.find(name);
5434 if (singleFamIt != cinfo->singleMethodFamilies.end()) {
5435 return res::Func { this, singleFamIt->second };
5437 auto const famIt = cinfo->methodFamilies.find(name);
5438 if (famIt == end(cinfo->methodFamilies)) return name_only();
5439 assertx(famIt->second->possibleFuncs().size() > 1);
5440 return res::Func { this, famIt->second };
5443 not_reached();
5446 folly::Optional<res::Func>
5447 Index::resolve_ctor(Context /*ctx*/, res::Class rcls, bool exact) const {
5448 auto const cinfo = rcls.val.right();
5449 if (!cinfo) return folly::none;
5450 if (cinfo->cls->attrs & (AttrInterface|AttrTrait)) return folly::none;
5452 auto const cit = cinfo->methods.find(s_construct.get());
5453 if (cit == end(cinfo->methods)) return folly::none;
5455 auto const ctor = mteFromIt(cit);
5456 if (exact || ctor->second.attrs & AttrNoOverride) {
5457 create_func_info(*m_data, ctor->second.func);
5458 return res::Func { this, ctor };
5461 if (!options.FuncFamilies) return folly::none;
5463 auto const singleFamIt = cinfo->singleMethodFamilies.find(s_construct.get());
5464 if (singleFamIt != cinfo->singleMethodFamilies.end()) {
5465 return res::Func { this, singleFamIt->second};
5467 auto const famIt = cinfo->methodFamilies.find(s_construct.get());
5468 if (famIt == end(cinfo->methodFamilies)) return folly::none;
5469 assertx(famIt->second->possibleFuncs().size() > 1);
5470 return res::Func { this, famIt->second };
5473 res::Func
5474 Index::resolve_func_helper(const php::Func* func, SString name) const {
5475 auto name_only = [&] (bool renamable) {
5476 return res::Func { this, res::Func::FuncName { name, renamable } };
5479 // no resolution
5480 if (func == nullptr) return name_only(false);
5482 // single resolution, in whole-program mode, that's it
5483 assertx(func->attrs & AttrUnique);
5484 return do_resolve(func);
5487 res::Func Index::resolve_func(Context /*ctx*/, SString name) const {
5488 name = normalizeNS(name);
5489 auto const it = m_data->funcs.find(name);
5490 return resolve_func_helper((it != end(m_data->funcs)) ? it->second : nullptr, name);
5494 * Gets a type for the constraint.
5496 * If getSuperType is true, the type could be a super-type of the
5497 * actual type constraint (eg TCell). Otherwise its guaranteed that
5498 * for any t, t.subtypeOf(get_type_for_constraint<false>(ctx, tc, t)
5499 * implies t would pass the constraint.
5501 * The candidate type is used to disambiguate; if we're applying a
5502 * Traversable constraint to a TObj, we should return
5503 * subObj(Traversable). If we're applying it to an Array, we should
5504 * return Array.
5506 template<bool getSuperType>
5507 Type Index::get_type_for_constraint(Context ctx,
5508 const TypeConstraint& tc,
5509 const Type& candidate) const {
5510 assertx(IMPLIES(!tc.isCheckable(),
5511 tc.isMixed() ||
5512 (tc.isUpperBound() &&
5513 RuntimeOption::EvalEnforceGenericsUB == 0)));
5515 if (getSuperType) {
5517 * Soft hints (@Foo) are not checked.
5518 * Also upper-bound type hints are not checked when they do not error.
5520 if (tc.isSoft() ||
5521 (RuntimeOption::EvalEnforceGenericsUB < 2 && tc.isUpperBound())) {
5522 return TCell;
5526 auto const res = get_type_for_annotated_type(
5527 ctx,
5528 tc.type(),
5529 tc.isNullable(),
5530 tc.typeName(),
5531 candidate
5533 if (res.type) return *res.type;
5534 // If the type constraint might be mixed, then the value could be
5535 // uninit. Any other type constraint implies TInitCell.
5536 return getSuperType ? (res.maybeMixed ? TCell : TInitCell) : TBottom;
5539 bool Index::prop_tc_maybe_unenforced(const php::Class& propCls,
5540 const TypeConstraint& tc) const {
5541 assertx(tc.validForProp());
5542 if (RuntimeOption::EvalCheckPropTypeHints <= 2) return true;
5543 if (!tc.isCheckable()) return true;
5544 if (tc.isSoft()) return true;
5545 if (tc.isUpperBound() && RuntimeOption::EvalEnforceGenericsUB < 2) {
5546 return true;
5548 auto const res = get_type_for_annotated_type(
5549 Context { nullptr, nullptr, &propCls },
5550 tc.type(),
5551 tc.isNullable(),
5552 tc.typeName(),
5553 TCell
5555 return res.maybeMixed;
5558 Index::ConstraintResolution Index::get_type_for_annotated_type(
5559 Context ctx, AnnotType annot, bool nullable,
5560 SString name, const Type& candidate) const {
5562 if (candidate.subtypeOf(BInitNull) && nullable) {
5563 return TInitNull;
5566 auto mainType = [&]() -> ConstraintResolution {
5567 switch (getAnnotMetaType(annot)) {
5568 case AnnotMetaType::Precise: {
5569 auto const dt = getAnnotDataType(annot);
5571 switch (dt) {
5572 case KindOfNull: return TNull;
5573 case KindOfBoolean: return TBool;
5574 case KindOfInt64: return TInt;
5575 case KindOfDouble: return TDbl;
5576 case KindOfPersistentString:
5577 case KindOfString: return TStr;
5578 case KindOfPersistentVec:
5579 case KindOfVec: return TVec;
5580 case KindOfPersistentDict:
5581 case KindOfDict: return TDict;
5582 case KindOfPersistentKeyset:
5583 case KindOfKeyset: return TKeyset;
5584 case KindOfResource: return TRes;
5585 case KindOfClsMeth: return TClsMeth;
5586 case KindOfRecord: // fallthrough
5587 case KindOfObject:
5588 return resolve_named_type(ctx, name, candidate);
5589 case KindOfUninit:
5590 case KindOfRFunc:
5591 case KindOfFunc:
5592 case KindOfRClsMeth:
5593 case KindOfClass:
5594 case KindOfLazyClass:
5595 always_assert_flog(false, "Unexpected DataType");
5596 break;
5598 break;
5600 case AnnotMetaType::Mixed:
5602 * Here we handle "mixed", typevars, and some other ignored
5603 * typehints (ex. "(function(..): ..)" typehints).
5605 return { TCell, true };
5606 case AnnotMetaType::Nothing:
5607 case AnnotMetaType::NoReturn:
5608 return TBottom;
5609 case AnnotMetaType::Nonnull:
5610 if (candidate.subtypeOf(BInitNull)) return TBottom;
5611 if (!candidate.couldBe(BInitNull)) return candidate;
5612 return unopt(candidate);
5613 case AnnotMetaType::This:
5614 if (auto s = selfCls(ctx)) return setctx(subObj(*s));
5615 break;
5616 case AnnotMetaType::Self:
5617 if (auto s = selfCls(ctx)) return subObj(*s);
5618 break;
5619 case AnnotMetaType::Parent:
5620 if (auto p = parentCls(ctx)) return subObj(*p);
5621 break;
5622 case AnnotMetaType::Callable:
5623 break;
5624 case AnnotMetaType::Number:
5625 return TNum;
5626 case AnnotMetaType::ArrayKey:
5627 if (candidate.subtypeOf(BInt)) return TInt;
5628 if (candidate.subtypeOf(BStr)) return TStr;
5629 return TArrKey;
5630 case AnnotMetaType::VecOrDict:
5631 if (candidate.subtypeOf(BVec)) return TVec;
5632 if (candidate.subtypeOf(BDict)) return TDict;
5633 return union_of(TVec, TDict);
5634 case AnnotMetaType::ArrayLike:
5635 if (candidate.subtypeOf(BVec)) return TVec;
5636 if (candidate.subtypeOf(BDict)) return TDict;
5637 if (candidate.subtypeOf(BKeyset)) return TKeyset;
5638 return TArrLike;
5639 case AnnotMetaType::Classname:
5640 if (candidate.subtypeOf(BStr)) return TStr;
5641 if (!RuntimeOption::EvalClassnameNotices) {
5642 if (candidate.subtypeOf(BCls)) return TCls;
5643 if (candidate.subtypeOf(BLazyCls)) return TLazyCls;
5646 return ConstraintResolution{ folly::none, false };
5647 }();
5649 if (mainType.type && nullable) {
5650 if (mainType.type->subtypeOf(BBottom)) {
5651 if (candidate.couldBe(BInitNull)) {
5652 mainType.type = TInitNull;
5654 } else if (!mainType.type->couldBe(BInitNull)) {
5655 mainType.type = opt(*mainType.type);
5658 return mainType;
5661 Type Index::lookup_constraint(Context ctx,
5662 const TypeConstraint& tc,
5663 const Type& t) const {
5664 return get_type_for_constraint<true>(ctx, tc, t);
5667 bool Index::satisfies_constraint(Context ctx, const Type& t,
5668 const TypeConstraint& tc) const {
5669 auto const tcType = get_type_for_constraint<false>(ctx, tc, t);
5670 return t.moreRefined(tcType);
5673 bool Index::could_have_reified_type(Context ctx,
5674 const TypeConstraint& tc) const {
5675 if (ctx.func->isClosureBody) {
5676 for (auto i = ctx.func->params.size();
5677 i < ctx.func->locals.size();
5678 ++i) {
5679 auto const name = ctx.func->locals[i].name;
5680 if (!name) return false; // named locals do not appear after unnamed local
5681 if (isMangledReifiedGenericInClosure(name)) return true;
5683 return false;
5685 if (!tc.isObject()) return false;
5686 auto const name = tc.typeName();
5687 auto const resolved = resolve_type_name_internal(name);
5688 if (resolved.type != AnnotType::Object) return false;
5689 auto const val = match<Either<SString, ClassInfo*>>(
5690 resolved.value,
5691 [&] (boost::blank) { return nullptr; },
5692 [&] (SString s) { return s; },
5693 [&] (ClassInfo* c) { return c; },
5694 [&] (RecordInfo*) { always_assert(false); return nullptr; }
5696 res::Class rcls{val};
5697 return rcls.couldHaveReifiedGenerics();
5700 folly::Optional<bool>
5701 Index::supports_async_eager_return(res::Func rfunc) const {
5702 auto const supportsAER = [] (const php::Func* func) {
5703 // Async functions always support async eager return.
5704 if (func->isAsync && !func->isGenerator) return true;
5706 // No other functions support async eager return yet.
5707 return false;
5710 return match<folly::Optional<bool>>(
5711 rfunc.val,
5712 [&](res::Func::FuncName) { return folly::none; },
5713 [&](res::Func::MethodName) { return folly::none; },
5714 [&](FuncInfo* finfo) { return supportsAER(finfo->func); },
5715 [&](const MethTabEntryPair* mte) { return supportsAER(mte->second.func); },
5716 [&](FuncFamily* fam) -> folly::Optional<bool> {
5717 auto ret = folly::Optional<bool>{};
5718 for (auto const pf : fam->possibleFuncs()) {
5719 // Abstract functions are never called.
5720 if (pf->second.attrs & AttrAbstract) continue;
5721 auto const val = supportsAER(pf->second.func);
5722 if (ret && *ret != val) return folly::none;
5723 ret = val;
5725 return ret;
5729 bool Index::is_effect_free(const php::Func* func) const {
5730 return func_info(*m_data, func)->effectFree;
5733 bool Index::is_effect_free(res::Func rfunc) const {
5734 return match<bool>(
5735 rfunc.val,
5736 [&](res::Func::FuncName) { return false; },
5737 [&](res::Func::MethodName) { return false; },
5738 [&](FuncInfo* finfo) { return finfo->effectFree; },
5739 [&](const MethTabEntryPair* mte) {
5740 return func_info(*m_data, mte->second.func)->effectFree;
5742 [&](FuncFamily* fam) {
5743 for (auto const mte : fam->possibleFuncs()) {
5744 if (!func_info(*m_data, mte->second.func)->effectFree) return false;
5746 return true;
5751 const php::Const* Index::lookup_class_const_ptr(Context ctx,
5752 res::Class rcls,
5753 SString cnsName,
5754 bool allow_tconst) const {
5755 if (rcls.val.left()) return nullptr;
5756 auto const cinfo = rcls.val.right();
5758 auto const it = cinfo->clsConstants.find(cnsName);
5759 if (it != end(cinfo->clsConstants)) {
5760 if (!it->second->val.has_value() ||
5761 it->second->kind == ConstModifiers::Kind::Context ||
5762 (!allow_tconst && it->second->kind == ConstModifiers::Kind::Type)) {
5763 // This is an abstract class constant, context constant or type constant
5764 return nullptr;
5766 if (it->second->val.value().m_type == KindOfUninit) {
5767 // This is a class constant that needs an 86cinit to run.
5768 // We'll add a dependency to make sure we're re-run if it
5769 // resolves anything.
5770 auto const cinit = it->second->cls->methods.back().get();
5771 assertx(cinit->name == s_86cinit.get());
5772 add_dependency(*m_data, cinit, ctx, Dep::ClsConst);
5773 return nullptr;
5775 return it->second.get();
5777 return nullptr;
5780 Type Index::lookup_class_constant(Context ctx,
5781 res::Class rcls,
5782 SString cnsName,
5783 bool allow_tconst) const {
5784 auto const cnst = lookup_class_const_ptr(ctx, rcls, cnsName, allow_tconst);
5785 if (!cnst) return TInitCell;
5786 return from_cell(cnst->val.value());
5789 Type Index::lookup_constant(Context ctx, SString cnsName) const {
5790 auto iter = m_data->constants.find(cnsName);
5791 if (iter == end(m_data->constants)) {
5792 return TInitCell;
5795 auto constant = iter->second;
5796 if (type(constant->val) != KindOfUninit) {
5797 return from_cell(constant->val);
5800 auto const func_name = Constant::funcNameFromName(cnsName);
5801 assertx(func_name && "func_name will never be nullptr");
5803 auto rfunc = resolve_func(ctx, func_name);
5804 return lookup_return_type(ctx, rfunc, Dep::ConstVal);
5807 bool Index::func_depends_on_arg(const php::Func* func, int arg) const {
5808 auto const& finfo = *func_info(*m_data, func);
5809 return arg >= finfo.unusedParams.size() || !finfo.unusedParams.test(arg);
5812 Type Index::lookup_foldable_return_type(Context ctx,
5813 const php::Func* func,
5814 Type ctxType,
5815 CompactVector<Type> args) const {
5816 constexpr auto max_interp_nexting_level = 2;
5817 static __thread uint32_t interp_nesting_level;
5818 static __thread Context base_ctx;
5820 // Don't fold functions when staticness mismatches
5821 if ((func->attrs & AttrStatic) && ctxType.couldBe(TObj)) return TInitCell;
5822 if (!(func->attrs & AttrStatic) && ctxType.couldBe(TCls)) return TInitCell;
5824 auto const& finfo = *func_info(*m_data, func);
5825 if (finfo.effectFree && is_scalar(finfo.returnTy)) {
5826 return finfo.returnTy;
5829 auto const calleeCtx = CallContext {
5830 func,
5831 std::move(args),
5832 std::move(ctxType)
5835 auto showArgs DEBUG_ONLY = [] (const CompactVector<Type>& a) {
5836 std::string ret, sep;
5837 for (auto& arg : a) {
5838 folly::format(&ret, "{}{}", sep, show(arg));
5839 sep = ",";
5841 return ret;
5845 ContextRetTyMap::const_accessor acc;
5846 if (m_data->foldableReturnTypeMap.find(acc, calleeCtx)) {
5847 FTRACE_MOD(
5848 Trace::hhbbc, 4,
5849 "Found foldableReturnType for {}{}{} with args {} (hash: {})\n",
5850 func->cls ? func->cls->name : staticEmptyString(),
5851 func->cls ? "::" : "",
5852 func->name,
5853 showArgs(calleeCtx.args),
5854 CallContextHashCompare{}.hash(calleeCtx));
5856 assertx(is_scalar(acc->second));
5857 return acc->second;
5861 if (frozen()) {
5862 FTRACE_MOD(
5863 Trace::hhbbc, 4,
5864 "MISSING: foldableReturnType for {}{}{} with args {} (hash: {})\n",
5865 func->cls ? func->cls->name : staticEmptyString(),
5866 func->cls ? "::" : "",
5867 func->name,
5868 showArgs(calleeCtx.args),
5869 CallContextHashCompare{}.hash(calleeCtx));
5870 return TInitCell;
5873 if (!interp_nesting_level) {
5874 base_ctx = ctx;
5875 } else if (interp_nesting_level > max_interp_nexting_level) {
5876 add_dependency(*m_data, func, base_ctx, Dep::InlineDepthLimit);
5877 return TInitCell;
5880 auto const contextType = [&] {
5881 ++interp_nesting_level;
5882 SCOPE_EXIT { --interp_nesting_level; };
5884 auto const wf = php::WideFunc::cns(func);
5885 auto const fa = analyze_func_inline(
5886 *this,
5887 AnalysisContext { func->unit, wf, func->cls },
5888 calleeCtx.context,
5889 calleeCtx.args,
5890 CollectionOpts::EffectFreeOnly
5892 return fa.effectFree ? fa.inferredReturn : TInitCell;
5893 }();
5895 if (!is_scalar(contextType)) {
5896 return TInitCell;
5899 ContextRetTyMap::accessor acc;
5900 if (m_data->foldableReturnTypeMap.insert(acc, calleeCtx)) {
5901 acc->second = contextType;
5902 } else {
5903 // someone beat us to it
5904 assertx(acc->second == contextType);
5906 return contextType;
5909 Type Index::lookup_return_type(Context ctx, res::Func rfunc, Dep dep) const {
5910 return match<Type>(
5911 rfunc.val,
5912 [&](res::Func::FuncName) { return TInitCell; },
5913 [&](res::Func::MethodName) { return TInitCell; },
5914 [&](FuncInfo* finfo) {
5915 add_dependency(*m_data, finfo->func, ctx, dep);
5916 return unctx(finfo->returnTy);
5918 [&](const MethTabEntryPair* mte) {
5919 add_dependency(*m_data, mte->second.func, ctx, dep);
5920 auto const finfo = func_info(*m_data, mte->second.func);
5921 if (!finfo->func) return TInitCell;
5922 return unctx(finfo->returnTy);
5924 [&](FuncFamily* fam) {
5925 add_dependency(*m_data, fam, ctx, dep);
5926 return fam->m_returnTy.get(
5927 [&] {
5928 auto ret = TBottom;
5929 for (auto const pf : fam->possibleFuncs()) {
5930 auto const finfo = func_info(*m_data, pf->second.func);
5931 if (!finfo->func) return TInitCell;
5932 ret |= unctx(finfo->returnTy);
5933 if (!ret.strictSubtypeOf(BInitCell)) return ret;
5935 return ret;
5942 Type Index::lookup_return_type(Context caller,
5943 const CompactVector<Type>& args,
5944 const Type& context,
5945 res::Func rfunc,
5946 Dep dep) const {
5947 return match<Type>(
5948 rfunc.val,
5949 [&](res::Func::FuncName) {
5950 return lookup_return_type(caller, rfunc);
5952 [&](res::Func::MethodName) {
5953 return lookup_return_type(caller, rfunc);
5955 [&](FuncInfo* finfo) {
5956 add_dependency(*m_data, finfo->func, caller, dep);
5957 return context_sensitive_return_type(*m_data,
5958 { finfo->func, args, context });
5960 [&](const MethTabEntryPair* mte) {
5961 add_dependency(*m_data, mte->second.func, caller, dep);
5962 auto const finfo = func_info(*m_data, mte->second.func);
5963 if (!finfo->func) return TInitCell;
5964 return context_sensitive_return_type(*m_data,
5965 { finfo->func, args, context });
5967 [&] (FuncFamily* fam) {
5968 add_dependency(*m_data, fam, caller, dep);
5969 auto ret = fam->m_returnTy.get(
5970 [&] {
5971 auto ret = TBottom;
5972 for (auto const pf : fam->possibleFuncs()) {
5973 auto const finfo = func_info(*m_data, pf->second.func);
5974 if (!finfo->func) return TInitCell;
5975 ret |= finfo->returnTy;
5976 if (!ret.strictSubtypeOf(BInitCell)) return ret;
5978 return ret;
5981 return return_with_context(std::move(ret), context);
5986 CompactVector<Type>
5987 Index::lookup_closure_use_vars(const php::Func* func,
5988 bool move) const {
5989 assertx(func->isClosureBody);
5991 auto const numUseVars = closure_num_use_vars(func);
5992 if (!numUseVars) return {};
5993 auto const it = m_data->closureUseVars.find(func->cls);
5994 if (it == end(m_data->closureUseVars)) {
5995 return CompactVector<Type>(numUseVars, TCell);
5997 if (move) return std::move(it->second);
5998 return it->second;
6001 Type Index::lookup_return_type_raw(const php::Func* f) const {
6002 auto it = func_info(*m_data, f);
6003 if (it->func) {
6004 assertx(it->func == f);
6005 return it->returnTy;
6007 return TInitCell;
6010 bool Index::lookup_this_available(const php::Func* f) const {
6011 return !(f->cls->attrs & AttrTrait) && !(f->attrs & AttrStatic);
6014 folly::Optional<uint32_t> Index::lookup_num_inout_params(
6015 Context,
6016 res::Func rfunc
6017 ) const {
6018 return match<folly::Optional<uint32_t>>(
6019 rfunc.val,
6020 [&] (res::Func::FuncName s) -> folly::Optional<uint32_t> {
6021 if (s.renamable) return folly::none;
6022 auto const it = m_data->funcs.find(s.name);
6023 return it != end(m_data->funcs)
6024 ? func_num_inout(it->second)
6025 : 0;
6027 [&] (res::Func::MethodName s) -> folly::Optional<uint32_t> {
6028 auto const it = m_data->method_inout_params_by_name.find(s.name);
6029 if (it == end(m_data->method_inout_params_by_name)) {
6030 // There was no entry, so no method by this name takes a parameter
6031 // by inout.
6032 return 0;
6034 auto const pair = m_data->methods.equal_range(s.name);
6035 return num_inout_from_set(folly::range(pair.first, pair.second));
6037 [&] (FuncInfo* finfo) {
6038 return func_num_inout(finfo->func);
6040 [&] (const MethTabEntryPair* mte) {
6041 return func_num_inout(mte->second.func);
6043 [&] (FuncFamily* fam) -> folly::Optional<uint32_t> {
6044 return fam->m_numInOut;
6049 PrepKind Index::lookup_param_prep(Context /*ctx*/, res::Func rfunc,
6050 uint32_t paramId) const {
6051 return match<PrepKind>(
6052 rfunc.val,
6053 [&] (res::Func::FuncName s) {
6054 if (s.renamable) return PrepKind::Unknown;
6055 auto const it = m_data->funcs.find(s.name);
6056 return it != end(m_data->funcs)
6057 ? func_param_prep(it->second, paramId)
6058 : func_param_prep_default();
6060 [&] (res::Func::MethodName s) {
6061 auto const it = m_data->method_inout_params_by_name.find(s.name);
6062 if (it == end(m_data->method_inout_params_by_name)) {
6063 // There was no entry, so no method by this name takes a parameter
6064 // by inout.
6065 return PrepKind::Val;
6067 if (paramId < sizeof(it->second) * CHAR_BIT &&
6068 !((it->second >> paramId) & 1)) {
6069 // No method of this name takes parameter paramId by inout.
6070 return PrepKind::Val;
6072 auto const pair = m_data->methods.equal_range(s.name);
6073 return prep_kind_from_set(folly::range(pair.first, pair.second), paramId);
6075 [&] (FuncInfo* finfo) {
6076 return func_param_prep(finfo->func, paramId);
6078 [&] (const MethTabEntryPair* mte) {
6079 return func_param_prep(mte->second.func, paramId);
6081 [&] (FuncFamily* fam) {
6082 return prep_kind_from_set(fam->possibleFuncs(), paramId);
6087 PropState
6088 Index::lookup_private_props(const php::Class* cls,
6089 bool move) const {
6090 auto it = m_data->privatePropInfo.find(cls);
6091 if (it != end(m_data->privatePropInfo)) {
6092 if (move) return std::move(it->second);
6093 return it->second;
6095 return make_unknown_propstate(
6096 cls,
6097 [&] (const php::Prop& prop) {
6098 return (prop.attrs & AttrPrivate) && !(prop.attrs & AttrStatic);
6103 PropState
6104 Index::lookup_private_statics(const php::Class* cls,
6105 bool move) const {
6106 auto it = m_data->privateStaticPropInfo.find(cls);
6107 if (it != end(m_data->privateStaticPropInfo)) {
6108 if (move) return std::move(it->second);
6109 return it->second;
6111 return make_unknown_propstate(
6112 cls,
6113 [&] (const php::Prop& prop) {
6114 return (prop.attrs & AttrPrivate) && (prop.attrs & AttrStatic);
6119 PropState Index::lookup_public_statics(const php::Class* cls) const {
6120 auto const cinfo = [&] () -> const ClassInfo* {
6121 auto const it = m_data->classInfo.find(cls->name);
6122 if (it == end(m_data->classInfo)) return nullptr;
6123 return it->second;
6124 }();
6126 PropState state;
6127 for (auto const& prop : cls->properties) {
6128 if (!(prop.attrs & (AttrPublic|AttrProtected)) ||
6129 !(prop.attrs & AttrStatic)) {
6130 continue;
6132 auto ty = cinfo
6133 ? calc_public_static_type(*m_data, cinfo, prop, prop.name)
6134 : TInitCell;
6135 state.emplace(
6136 prop.name,
6137 PropStateElem<>{std::move(ty), &prop.typeConstraint, prop.attrs}
6140 return state;
6144 * Entry point for static property lookups from the Index. Return
6145 * metadata about a `cls'::`name' static property access in the given
6146 * context.
6148 PropLookupResult<> Index::lookup_static(Context ctx,
6149 const PropertiesInfo& privateProps,
6150 const Type& cls,
6151 const Type& name) const {
6152 ITRACE(4, "lookup_static: {} {}::${}\n", show(ctx), show(cls), show(name));
6153 Trace::Indent _;
6155 // First try to obtain the property name as a static string
6156 auto const sname = [&] () -> SString {
6157 // Treat non-string names conservatively, but the caller should be
6158 // checking this.
6159 if (!name.subtypeOf(BStr)) return nullptr;
6160 auto const vname = tv(name);
6161 if (!vname || vname->m_type != KindOfPersistentString) return nullptr;
6162 return vname->m_data.pstr;
6163 }();
6165 // Conservative result when we can't do any better. The type can be
6166 // anything, and anything might throw.
6167 auto const conservative = [&] {
6168 ITRACE(4, "conservative\n");
6169 return PropLookupResult<>{
6170 TInitCell,
6171 sname,
6172 TriBool::Maybe,
6173 TriBool::Maybe,
6174 TriBool::Maybe,
6175 TriBool::Maybe,
6176 true
6180 // If we don't know what `cls' is, there's not much we can do.
6181 if (!is_specialized_cls(cls)) return conservative();
6183 auto const dcls = dcls_of(cls);
6184 if (dcls.cls.val.left()) return conservative();
6185 auto const cinfo = dcls.cls.val.right();
6187 // Turn the context class into a ClassInfo* for convenience.
6188 const ClassInfo* ctxCls = nullptr;
6189 if (ctx.cls) {
6190 // I don't think this can ever fail (we should always be able to
6191 // resolve the class since we're currently processing it). If it
6192 // does, be conservative.
6193 auto const rCtx = resolve_class(ctx.cls);
6194 if (rCtx.val.left()) return conservative();
6195 ctxCls = rCtx.val.right();
6198 switch (dcls.type) {
6199 case DCls::Sub: {
6200 // We know that `cls' is at least dcls.type, but could be a
6201 // subclass. For every subclass (including dcls.type itself),
6202 // start the property lookup from there, and union together all
6203 // the potential results. This could potentially visit a lot of
6204 // parent classes redundently, so tell it not to look into
6205 // parent classes, unless we're processing dcls.type.
6206 folly::Optional<PropLookupResult<>> result;
6207 for (auto const sub : cinfo->subclassList) {
6208 auto r = lookup_static_impl(
6209 *m_data,
6210 ctx,
6211 ctxCls,
6212 privateProps,
6213 sub,
6214 sname,
6215 !sname && sub != cinfo
6217 ITRACE(4, "{} -> {}\n", sub->cls->name, show(r));
6218 if (!result) {
6219 result.emplace(std::move(r));
6220 } else {
6221 *result |= r;
6224 assertx(result.has_value());
6225 ITRACE(4, "union -> {}\n", show(*result));
6226 return *result;
6228 case DCls::Exact: {
6229 // We know what exactly `cls' is. Just do the property lookup
6230 // starting from there.
6231 auto const r = lookup_static_impl(
6232 *m_data,
6233 ctx,
6234 ctxCls,
6235 privateProps,
6236 cinfo,
6237 sname,
6238 false
6240 ITRACE(4, "{} -> {}\n", cinfo->cls->name, show(r));
6241 return r;
6244 always_assert(false);
6247 Type Index::lookup_public_prop(const Type& cls, const Type& name) const {
6248 if (!is_specialized_cls(cls)) return TCell;
6250 auto const vname = tv(name);
6251 if (!vname || vname->m_type != KindOfPersistentString) return TCell;
6252 auto const sname = vname->m_data.pstr;
6254 auto const dcls = dcls_of(cls);
6255 if (dcls.cls.val.left()) return TCell;
6256 auto const cinfo = dcls.cls.val.right();
6258 switch (dcls.type) {
6259 case DCls::Sub: {
6260 auto ty = TBottom;
6261 for (auto const sub : cinfo->subclassList) {
6262 ty |= lookup_public_prop_impl(
6263 *m_data,
6264 sub,
6265 sname
6268 return ty;
6270 case DCls::Exact:
6271 return lookup_public_prop_impl(
6272 *m_data,
6273 cinfo,
6274 sname
6277 always_assert(false);
6280 Type Index::lookup_public_prop(const php::Class* cls, SString name) const {
6281 auto const it = m_data->classInfo.find(cls->name);
6282 if (it == end(m_data->classInfo)) {
6283 return TCell;
6285 return lookup_public_prop_impl(*m_data, it->second, name);
6288 bool Index::lookup_class_init_might_raise(Context ctx, res::Class cls) const {
6289 return cls.val.match(
6290 [] (SString) { return true; },
6291 [&] (ClassInfo* cinfo) {
6292 return class_init_might_raise(*m_data, ctx, cinfo);
6297 void Index::join_iface_vtable_thread() const {
6298 if (m_data->compute_iface_vtables.joinable()) {
6299 m_data->compute_iface_vtables.join();
6303 Slot
6304 Index::lookup_iface_vtable_slot(const php::Class* cls) const {
6305 return folly::get_default(m_data->ifaceSlotMap, cls, kInvalidSlot);
6308 //////////////////////////////////////////////////////////////////////
6311 * Entry point for static property type mutation from the Index. Merge
6312 * `val' into the known type for any accessible `cls'::`name' static
6313 * property. The mutation will be recovered into either
6314 * `publicMutations' or `privateProps' depending on the properties
6315 * found. Mutations to AttrConst properties are ignored, unless
6316 * `ignoreConst' is true.
6318 PropMergeResult<> Index::merge_static_type(
6319 Context ctx,
6320 PublicSPropMutations& publicMutations,
6321 PropertiesInfo& privateProps,
6322 const Type& cls,
6323 const Type& name,
6324 const Type& val,
6325 bool checkUB,
6326 bool ignoreConst,
6327 bool mustBeReadOnly) const {
6328 ITRACE(
6329 4, "merge_static_type: {} {}::${} {}\n",
6330 show(ctx), show(cls), show(name), show(val)
6332 Trace::Indent _;
6334 assertx(val.subtypeOf(BInitCell));
6336 using R = PropMergeResult<>;
6338 // In some cases we might try to merge Bottom if we're in
6339 // unreachable code. This won't affect anything, so just skip out
6340 // early.
6341 if (val.subtypeOf(BBottom)) return R{ TBottom, TriBool::No };
6343 // Try to turn the given property name into a static string
6344 auto const sname = [&] () -> SString {
6345 // Non-string names are treated conservatively here. The caller
6346 // should be checking for these and doing the right thing.
6347 if (!name.subtypeOf(BStr)) return nullptr;
6348 auto const vname = tv(name);
6349 if (!vname || vname->m_type != KindOfPersistentString) return nullptr;
6350 return vname->m_data.pstr;
6351 }();
6353 // To be conservative, say we might throw and be conservative about
6354 // conversions.
6355 auto const conservative = [&] {
6356 return PropMergeResult<>{
6357 loosen_likeness(val),
6358 TriBool::Maybe
6362 // The case where we don't know `cls':
6363 auto const unknownCls = [&] {
6364 auto& statics = privateProps.privateStatics();
6366 if (!sname) {
6367 // Very bad case. We don't know `cls' or the property name. This
6368 // mutation can be affecting anything, so merge it into all
6369 // properties (this drops type information for public
6370 // properties).
6371 ITRACE(4, "unknown class and prop. merging everything\n");
6372 publicMutations.mergeUnknown(ctx);
6374 // Private properties can only be affected if they're accessible
6375 // in the current context.
6376 if (!ctx.cls) return conservative();
6378 for (auto& kv : statics) {
6379 if (!ignoreConst && (kv.second.attrs & AttrIsConst)) continue;
6380 if (mustBeReadOnly && !(kv.second.attrs & AttrIsReadOnly)) continue;
6381 kv.second.ty |=
6382 unctx(adjust_type_for_prop(*this, *ctx.cls, kv.second.tc, val));
6384 return conservative();
6387 // Otherwise we don't know `cls', but do know the property
6388 // name. We'll store this mutation separately and union it in to
6389 // any lookup with the same name.
6390 ITRACE(4, "unknown class. merging all props with name {}\n", sname);
6392 publicMutations.mergeUnknownClass(sname, unctx(val));
6394 // Assume that it could possibly affect any private property with
6395 // the same name.
6396 if (!ctx.cls) return conservative();
6397 auto it = statics.find(sname);
6398 if (it == end(statics)) return conservative();
6399 if (!ignoreConst && (it->second.attrs & AttrIsConst)) return conservative();
6400 if (mustBeReadOnly && !(it->second.attrs & AttrIsReadOnly)) return conservative();
6402 it->second.ty |=
6403 unctx(adjust_type_for_prop(*this, *ctx.cls, it->second.tc, val));
6404 return conservative();
6407 // check if we can determine the class.
6408 if (!is_specialized_cls(cls)) return unknownCls();
6410 auto const dcls = dcls_of(cls);
6411 if (dcls.cls.val.left()) return unknownCls();
6412 auto const cinfo = dcls.cls.val.right();
6414 const ClassInfo* ctxCls = nullptr;
6415 if (ctx.cls) {
6416 auto const rCtx = resolve_class(ctx.cls);
6417 // We should only be not able to resolve our own context if the
6418 // class is not instantiable. In that case, the merge can't
6419 // happen.
6420 if (rCtx.val.left()) return R{ TBottom, TriBool::No };
6421 ctxCls = rCtx.val.right();
6424 auto const mergePublic = [&] (const ClassInfo* ci,
6425 const php::Prop& prop,
6426 const Type& val) {
6427 publicMutations.mergeKnown(ci, prop, val);
6430 switch (dcls.type) {
6431 case DCls::Sub: {
6432 // We know this class is either dcls.type, or a child class of
6433 // it. For every child of dcls.type (including dcls.type
6434 // itself), do the merge starting from it. To avoid redundant
6435 // work, only iterate into parent classes if we're dcls.type
6436 // (this is only a matter of efficiency. The merge is
6437 // idiompotent).
6438 folly::Optional<PropMergeResult<>> result;
6439 for (auto const sub : cinfo->subclassList) {
6440 auto r = merge_static_type_impl(
6441 *m_data,
6442 ctx,
6443 mergePublic,
6444 privateProps,
6445 ctxCls,
6446 sub,
6447 sname,
6448 val,
6449 checkUB,
6450 ignoreConst,
6451 mustBeReadOnly,
6452 !sname && sub != cinfo
6454 ITRACE(4, "{} -> {}\n", sub->cls->name, show(r));
6455 if (!result) {
6456 result.emplace(std::move(r));
6457 } else {
6458 *result |= r;
6461 assertx(result.has_value());
6462 ITRACE(4, "union -> {}\n", show(*result));
6463 return *result;
6465 case DCls::Exact: {
6466 // We know the class exactly. Do the merge starting from only
6467 // it.
6468 auto const r = merge_static_type_impl(
6469 *m_data,
6470 ctx,
6471 mergePublic,
6472 privateProps,
6473 ctxCls,
6474 cinfo,
6475 sname,
6476 val,
6477 checkUB,
6478 ignoreConst,
6479 mustBeReadOnly,
6480 false
6482 ITRACE(4, "{} -> {}\n", cinfo->cls->name, show(r));
6483 return r;
6486 always_assert(false);
6489 //////////////////////////////////////////////////////////////////////
6491 DependencyContext Index::dependency_context(const Context& ctx) const {
6492 return dep_context(*m_data, ctx);
6495 void Index::use_class_dependencies(bool f) {
6496 if (f != m_data->useClassDependencies) {
6497 m_data->dependencyMap.clear();
6498 m_data->useClassDependencies = f;
6502 void Index::init_public_static_prop_types() {
6503 for (auto const& cinfo : m_data->allClassInfos) {
6504 for (auto const& prop : cinfo->cls->properties) {
6505 if (!(prop.attrs & (AttrPublic|AttrProtected)) ||
6506 !(prop.attrs & AttrStatic)) {
6507 continue;
6511 * If the initializer type is TUninit, it means an 86sinit provides the
6512 * actual initialization type or it is AttrLateInit. So we don't want to
6513 * include the Uninit (which isn't really a user-visible type for the
6514 * property) or by the time we union things in we'll have inferred nothing
6515 * much.
6517 auto const initial = [&] {
6518 auto const tyRaw = from_cell(prop.val);
6519 if (tyRaw.subtypeOf(BUninit)) return TBottom;
6520 if (prop.attrs & AttrSystemInitialValue) return tyRaw;
6521 return adjust_type_for_prop(
6522 *this, *cinfo->cls, &prop.typeConstraint, tyRaw
6524 }();
6526 cinfo->publicStaticProps[prop.name] =
6527 PublicSPropEntry {
6528 union_of(
6529 adjust_type_for_prop(
6530 *this,
6531 *cinfo->cls,
6532 &prop.typeConstraint,
6533 TInitCell
6535 initial
6537 initial,
6538 &prop.typeConstraint,
6540 false
6546 void Index::refine_class_constants(
6547 const Context& ctx,
6548 const CompactVector<std::pair<size_t, TypedValue>>& resolved,
6549 DependencyContextSet& deps) {
6550 if (!resolved.size()) return;
6551 auto& constants = ctx.func->cls->constants;
6552 for (auto const& c : resolved) {
6553 assertx(c.first < constants.size());
6554 auto& cnst = constants[c.first];
6555 assertx(cnst.val && cnst.val->m_type == KindOfUninit);
6556 cnst.val = c.second;
6558 find_deps(*m_data, ctx.func, Dep::ClsConst, deps);
6561 void Index::refine_constants(const FuncAnalysisResult& fa,
6562 DependencyContextSet& deps) {
6563 auto const& func = fa.ctx.func;
6564 if (func->cls != nullptr) return;
6566 auto const val = tv(fa.inferredReturn);
6567 if (!val) return;
6569 auto const cns_name = Constant::nameFromFuncName(func->name);
6570 if (!cns_name) return;
6572 auto& cs = fa.ctx.unit->constants;
6573 auto it = std::find_if(
6574 cs.begin(),
6575 cs.end(),
6576 [&] (auto const& c) {
6577 return cns_name->same(c->name);
6579 assertx(it != cs.end() && "Did not find constant");
6580 (*it)->val = val.value();
6581 find_deps(*m_data, func, Dep::ConstVal, deps);
6584 void Index::fixup_return_type(const php::Func* func,
6585 Type& retTy) const {
6586 if (func->isGenerator) {
6587 if (func->isAsync) {
6588 // Async generators always return AsyncGenerator object.
6589 retTy = objExact(builtin_class(s_AsyncGenerator.get()));
6590 } else {
6591 // Non-async generators always return Generator object.
6592 retTy = objExact(builtin_class(s_Generator.get()));
6594 } else if (func->isAsync) {
6595 // Async functions always return WaitH<T>, where T is the type returned
6596 // internally.
6597 retTy = wait_handle(*this, std::move(retTy));
6601 void Index::init_return_type(const php::Func* func) {
6602 if ((func->attrs & AttrBuiltin) || func->isMemoizeWrapper) {
6603 return;
6606 auto make_type = [&] (const TypeConstraint& tc) {
6607 if (tc.isSoft() ||
6608 (RuntimeOption::EvalEnforceGenericsUB < 2 && tc.isUpperBound())) {
6609 return TBottom;
6611 auto const cls = func->cls && func->cls->closureContextCls
6612 ? func->cls->closureContextCls
6613 : func->cls;
6614 return lookup_constraint(Context { func->unit, func, cls }, tc);
6617 auto const finfo = create_func_info(*m_data, func);
6619 auto tcT = make_type(func->retTypeConstraint);
6620 if (tcT.is(BBottom)) return;
6622 if (func->hasInOutArgs) {
6623 std::vector<Type> types;
6624 types.emplace_back(intersection_of(TInitCell, std::move(tcT)));
6625 for (auto& p : func->params) {
6626 if (!p.inout) continue;
6627 auto t = make_type(p.typeConstraint);
6628 if (t.is(BBottom)) return;
6629 types.emplace_back(intersection_of(TInitCell, std::move(t)));
6631 tcT = vec(std::move(types));
6634 tcT = loosen_interfaces(loosen_all(to_cell(std::move(tcT))));
6636 FTRACE(4, "Pre-fixup return type for {}{}{}: {}\n",
6637 func->cls ? func->cls->name->data() : "",
6638 func->cls ? "::" : "",
6639 func->name, show(tcT));
6640 fixup_return_type(func, tcT);
6641 FTRACE(3, "Initial return type for {}{}{}: {}\n",
6642 func->cls ? func->cls->name->data() : "",
6643 func->cls ? "::" : "",
6644 func->name, show(tcT));
6645 finfo->returnTy = std::move(tcT);
6648 static bool moreRefinedForIndex(const Type& newType,
6649 const Type& oldType)
6651 if (newType.moreRefined(oldType)) return true;
6652 if (!newType.subtypeOf(BOptObj) ||
6653 !oldType.subtypeOf(BOptObj) ||
6654 !is_specialized_obj(oldType)) {
6655 return false;
6657 return dobj_of(oldType).cls.mustBeInterface();
6660 void Index::refine_return_info(const FuncAnalysisResult& fa,
6661 DependencyContextSet& deps) {
6662 auto const& func = fa.ctx.func;
6663 auto const finfo = create_func_info(*m_data, func);
6664 auto const t = loosen_interfaces(fa.inferredReturn);
6666 auto const error_loc = [&] {
6667 return folly::sformat(
6668 "{} {}{}",
6669 func->unit->filename,
6670 func->cls ?
6671 folly::to<std::string>(func->cls->name->data(), "::") : std::string{},
6672 func->name
6676 auto dep = Dep{};
6677 if (finfo->retParam == NoLocalId && fa.retParam != NoLocalId) {
6678 // This is just a heuristic; it doesn't mean that the value passed
6679 // in was returned, but that the value of the parameter at the
6680 // point of the RetC was returned. We use it to make (heuristic)
6681 // decisions about whether to do inline interps, so we only allow
6682 // it to change once (otherwise later passes might not do the
6683 // inline interp, and get worse results, which could trigger other
6684 // assertions in Index::refine_*).
6685 dep = Dep::ReturnTy;
6686 finfo->retParam = fa.retParam;
6689 auto unusedParams = ~fa.usedParams;
6690 if (finfo->unusedParams != unusedParams) {
6691 dep = Dep::ReturnTy;
6692 always_assert_flog(
6693 (finfo->unusedParams | unusedParams) == unusedParams,
6694 "Index unusedParams decreased in {}.\n",
6695 error_loc()
6697 finfo->unusedParams = unusedParams;
6700 if (t.strictlyMoreRefined(finfo->returnTy)) {
6701 if (finfo->returnRefinements + 1 < options.returnTypeRefineLimit) {
6702 finfo->returnTy = t;
6703 // We've modifed the return type, so reset any cached FuncFamily
6704 // return types.
6705 for (auto const ff : finfo->families) ff->m_returnTy.reset();
6706 ++finfo->returnRefinements;
6707 dep = is_scalar(t) ?
6708 Dep::ReturnTy | Dep::InlineDepthLimit : Dep::ReturnTy;
6709 } else {
6710 FTRACE(1, "maxed out return type refinements at {}\n", error_loc());
6712 } else {
6713 always_assert_flog(
6714 moreRefinedForIndex(t, finfo->returnTy),
6715 "Index return type invariant violated in {}.\n"
6716 " {} is not at least as refined as {}\n",
6717 error_loc(),
6718 show(t),
6719 show(finfo->returnTy)
6723 always_assert_flog(
6724 !finfo->effectFree || fa.effectFree,
6725 "Index effectFree changed from true to false in {} {}{}.\n",
6726 func->unit->filename,
6727 func->cls ? folly::to<std::string>(func->cls->name->data(), "::") :
6728 std::string{},
6729 func->name);
6731 if (finfo->effectFree != fa.effectFree) {
6732 finfo->effectFree = fa.effectFree;
6733 dep = Dep::InlineDepthLimit | Dep::ReturnTy;
6736 if (dep != Dep{}) find_deps(*m_data, func, dep, deps);
6739 bool Index::refine_closure_use_vars(const php::Class* cls,
6740 const CompactVector<Type>& vars) {
6741 assertx(is_closure(*cls));
6743 for (auto i = uint32_t{0}; i < vars.size(); ++i) {
6744 always_assert_flog(
6745 vars[i].equivalentlyRefined(unctx(vars[i])),
6746 "Closure cannot have a used var with a context dependent type"
6750 auto& current = [&] () -> CompactVector<Type>& {
6751 std::lock_guard<std::mutex> _{closure_use_vars_mutex};
6752 return m_data->closureUseVars[cls];
6753 }();
6755 always_assert(current.empty() || current.size() == vars.size());
6756 if (current.empty()) {
6757 current = vars;
6758 return true;
6761 auto changed = false;
6762 for (auto i = uint32_t{0}; i < vars.size(); ++i) {
6763 if (vars[i].strictSubtypeOf(current[i])) {
6764 changed = true;
6765 current[i] = vars[i];
6766 } else {
6767 always_assert_flog(
6768 moreRefinedForIndex(vars[i], current[i]),
6769 "Index closure_use_var invariant violated in {}.\n"
6770 " {} is not at least as refined as {}\n",
6771 cls->name,
6772 show(vars[i]),
6773 show(current[i])
6778 return changed;
6781 template<class Container>
6782 void refine_private_propstate(Container& cont,
6783 const php::Class* cls,
6784 const PropState& state) {
6785 assertx(!is_used_trait(*cls));
6786 auto* elm = [&] () -> typename Container::value_type* {
6787 std::lock_guard<std::mutex> _{private_propstate_mutex};
6788 auto it = cont.find(cls);
6789 if (it == end(cont)) {
6790 cont[cls] = state;
6791 return nullptr;
6793 return &*it;
6794 }();
6796 if (!elm) return;
6798 for (auto& kv : state) {
6799 auto& target = elm->second[kv.first];
6800 assertx(target.tc == kv.second.tc);
6801 always_assert_flog(
6802 moreRefinedForIndex(kv.second.ty, target.ty),
6803 "PropState refinement failed on {}::${} -- {} was not a subtype of {}\n",
6804 cls->name->data(),
6805 kv.first->data(),
6806 show(kv.second.ty),
6807 show(target.ty)
6809 target.ty = kv.second.ty;
6813 void Index::refine_private_props(const php::Class* cls,
6814 const PropState& state) {
6815 refine_private_propstate(m_data->privatePropInfo, cls, state);
6818 void Index::refine_private_statics(const php::Class* cls,
6819 const PropState& state) {
6820 // We can't store context dependent types in private statics since they
6821 // could be accessed using different contexts.
6822 auto cleanedState = PropState{};
6823 for (auto const& prop : state) {
6824 auto& elem = cleanedState[prop.first];
6825 elem.ty = unctx(prop.second.ty);
6826 elem.tc = prop.second.tc;
6827 elem.attrs = prop.second.attrs;
6830 refine_private_propstate(m_data->privateStaticPropInfo, cls, cleanedState);
6833 void Index::record_public_static_mutations(const php::Func& func,
6834 PublicSPropMutations mutations) {
6835 if (!mutations.m_data) {
6836 m_data->publicSPropMutations.erase(&func);
6837 return;
6839 m_data->publicSPropMutations.insert_or_assign(&func, std::move(mutations));
6842 void Index::update_static_prop_init_val(const php::Class* cls,
6843 SString name) const {
6844 auto const cls_it = m_data->classInfo.find(cls->name);
6845 if (cls_it == end(m_data->classInfo)) {
6846 return;
6848 auto const cinfo = cls_it->second;
6849 if (cinfo->cls != cls) {
6850 return;
6852 auto const it = cinfo->publicStaticProps.find(name);
6853 if (it != cinfo->publicStaticProps.end()) {
6854 it->second.initialValueResolved = true;
6858 void Index::refine_public_statics(DependencyContextSet& deps) {
6859 trace_time update("update public statics");
6861 // Union together the mutations for each function, including the functions
6862 // which weren't analyzed this round.
6863 auto nothing_known = false;
6864 PublicSPropMutations::UnknownMap unknown;
6865 PublicSPropMutations::KnownMap known;
6866 for (auto const& mutations : m_data->publicSPropMutations) {
6867 if (!mutations.second.m_data) continue;
6868 if (mutations.second.m_data->m_nothing_known) {
6869 nothing_known = true;
6870 break;
6873 for (auto const& kv : mutations.second.m_data->m_unknown) {
6874 auto const ret = unknown.insert(kv);
6875 if (!ret.second) ret.first->second |= kv.second;
6877 for (auto const& kv : mutations.second.m_data->m_known) {
6878 auto const ret = known.insert(kv);
6879 if (!ret.second) ret.first->second |= kv.second;
6883 if (nothing_known) {
6884 // We cannot go from knowing the types to not knowing the types (this is
6885 // equivalent to widening the types).
6886 always_assert(m_data->allPublicSPropsUnknown);
6887 return;
6890 auto const firstRefinement = m_data->allPublicSPropsUnknown;
6891 m_data->allPublicSPropsUnknown = false;
6893 if (firstRefinement) {
6894 // If this is the first refinement, reschedule any dependency which looked
6895 // at the public static property state previously.
6896 always_assert(m_data->unknownClassSProps.empty());
6897 for (auto const& dependency : m_data->dependencyMap) {
6898 if (dependency.first.tag() != DependencyContextType::PropName) continue;
6899 for (auto const& kv : dependency.second) {
6900 if (has_dep(kv.second, Dep::PublicSPropName)) deps.insert(kv.first);
6905 // Refine unknown class state
6906 for (auto const& kv : unknown) {
6907 // We can't keep context dependent types in public properties.
6908 auto newType = unctx(kv.second);
6909 auto it = m_data->unknownClassSProps.find(kv.first);
6910 if (it == end(m_data->unknownClassSProps)) {
6911 // If this is the first refinement, our previous state was effectively
6912 // TCell for everything, so inserting a type into the map can only
6913 // refine. However, if this isn't the first refinement, a name not present
6914 // in the map means that its TBottom, so we shouldn't be inserting
6915 // anything.
6916 always_assert(firstRefinement);
6917 m_data->unknownClassSProps.emplace(
6918 kv.first,
6919 std::make_pair(std::move(newType), 0)
6921 continue;
6925 * We may only shrink the types we recorded for each property. (If a
6926 * property type ever grows, the interpreter could infer something
6927 * incorrect at some step.)
6929 always_assert(!firstRefinement);
6930 always_assert_flog(
6931 newType.subtypeOf(it->second.first),
6932 "Static property index invariant violated for name {}:\n"
6933 " {} was not a subtype of {}",
6934 kv.first->data(),
6935 show(newType),
6936 show(it->second.first)
6939 // Put a limit on the refinements to ensure termination. Since we only ever
6940 // refine types, we can stop at any point and maintain correctness.
6941 if (it->second.second + 1 < options.publicSPropRefineLimit) {
6942 if (newType.strictSubtypeOf(it->second.first)) {
6943 find_deps(*m_data, it->first, Dep::PublicSPropName, deps);
6945 it->second.first = std::move(newType);
6946 ++it->second.second;
6947 } else {
6948 FTRACE(
6949 1, "maxed out public static property refinements for name {}\n",
6950 kv.first->data()
6955 // If we didn't see a mutation among all the functions for a particular name,
6956 // it means the type is TBottom. Iterate through the unknown class state and
6957 // remove any entries which we didn't see a mutation for.
6958 if (!firstRefinement) {
6959 auto it = begin(m_data->unknownClassSProps);
6960 auto last = end(m_data->unknownClassSProps);
6961 while (it != last) {
6962 auto const unknownIt = unknown.find(it->first);
6963 if (unknownIt == end(unknown)) {
6964 if (unknownIt->second != TBottom) {
6965 find_deps(*m_data, unknownIt->first, Dep::PublicSPropName, deps);
6967 it = m_data->unknownClassSProps.erase(it);
6968 } else {
6969 ++it;
6974 // Refine known class state
6975 for (auto const& cinfo : m_data->allClassInfos) {
6976 for (auto& kv : cinfo->publicStaticProps) {
6977 auto const newType = [&] {
6978 auto const it = known.find(
6979 PublicSPropMutations::KnownKey { cinfo.get(), kv.first }
6981 // If we didn't see a mutation, the type is TBottom.
6982 if (it == end(known)) return TBottom;
6983 // We can't keep context dependent types in public properties.
6984 return adjust_type_for_prop(
6985 *this, *cinfo->cls, kv.second.tc, unctx(it->second)
6987 }();
6989 if (kv.second.initialValueResolved) {
6990 for (auto& prop : cinfo->cls->properties) {
6991 if (prop.name != kv.first) continue;
6992 kv.second.initializerType = from_cell(prop.val);
6993 kv.second.initialValueResolved = false;
6994 break;
6996 assertx(!kv.second.initialValueResolved);
6999 // The type from the indexer doesn't contain the in-class initializer
7000 // types. Add that here.
7001 auto effectiveType = union_of(newType, kv.second.initializerType);
7004 * We may only shrink the types we recorded for each property. (If a
7005 * property type ever grows, the interpreter could infer something
7006 * incorrect at some step.)
7008 always_assert_flog(
7009 effectiveType.subtypeOf(kv.second.inferredType),
7010 "Static property index invariant violated on {}::{}:\n"
7011 " {} is not a subtype of {}",
7012 cinfo->cls->name->data(),
7013 kv.first->data(),
7014 show(effectiveType),
7015 show(kv.second.inferredType)
7018 // Put a limit on the refinements to ensure termination. Since we only
7019 // ever refine types, we can stop at any point and still maintain
7020 // correctness.
7021 if (kv.second.refinements + 1 < options.publicSPropRefineLimit) {
7022 if (effectiveType.strictSubtypeOf(kv.second.inferredType)) {
7023 find_deps(*m_data, kv.first, Dep::PublicSPropName, deps);
7025 kv.second.inferredType = std::move(effectiveType);
7026 ++kv.second.refinements;
7027 } else {
7028 FTRACE(
7029 1, "maxed out public static property refinements for {}:{}\n",
7030 cinfo->cls->name->data(),
7031 kv.first->data()
7038 void Index::refine_bad_initial_prop_values(const php::Class* cls,
7039 bool value,
7040 DependencyContextSet& deps) {
7041 assertx(!is_used_trait(*cls));
7042 auto const it = m_data->classInfo.find(cls->name);
7043 if (it == end(m_data->classInfo)) {
7044 return;
7046 auto const cinfo = it->second;
7047 if (cinfo->cls != cls) {
7048 return;
7050 always_assert_flog(
7051 cinfo->hasBadInitialPropValues || !value,
7052 "Bad initial prop values going from false to true on {}",
7053 cls->name->data()
7056 if (cinfo->hasBadInitialPropValues && !value) {
7057 cinfo->hasBadInitialPropValues = false;
7058 find_deps(*m_data, cls, Dep::PropBadInitialValues, deps);
7062 bool Index::frozen() const {
7063 return m_data->frozen;
7066 void Index::freeze() {
7067 m_data->frozen = true;
7068 m_data->ever_frozen = true;
7072 * Note that these functions run in separate threads, and
7073 * intentionally don't bump Trace::hhbbc_time. If you want to see
7074 * these times, set TRACE=hhbbc_time:1
7076 #define CLEAR(x) \
7078 trace_time _{"Clearing " #x}; \
7079 (x).clear(); \
7082 void Index::cleanup_for_final() {
7083 trace_time _{"cleanup_for_final"};
7084 CLEAR(m_data->dependencyMap);
7088 void Index::cleanup_post_emit() {
7089 trace_time _{"cleanup_post_emit"};
7091 trace_time t{"Reset allClassInfos"};
7092 parallel::for_each(m_data->allClassInfos, [] (auto& u) { u.reset(); });
7094 std::vector<std::function<void()>> clearers;
7095 #define CLEAR_PARALLEL(x) clearers.push_back([&] CLEAR(x));
7096 CLEAR_PARALLEL(m_data->classes);
7097 CLEAR_PARALLEL(m_data->methods);
7098 CLEAR_PARALLEL(m_data->method_inout_params_by_name);
7099 CLEAR_PARALLEL(m_data->funcs);
7100 CLEAR_PARALLEL(m_data->typeAliases);
7101 CLEAR_PARALLEL(m_data->enums);
7102 CLEAR_PARALLEL(m_data->constants);
7103 CLEAR_PARALLEL(m_data->records);
7105 CLEAR_PARALLEL(m_data->classClosureMap);
7106 CLEAR_PARALLEL(m_data->classExtraMethodMap);
7108 CLEAR_PARALLEL(m_data->allClassInfos);
7109 CLEAR_PARALLEL(m_data->classInfo);
7110 CLEAR_PARALLEL(m_data->funcInfo);
7112 CLEAR_PARALLEL(m_data->privatePropInfo);
7113 CLEAR_PARALLEL(m_data->privateStaticPropInfo);
7114 CLEAR_PARALLEL(m_data->unknownClassSProps);
7115 CLEAR_PARALLEL(m_data->publicSPropMutations);
7116 CLEAR_PARALLEL(m_data->funcFamilies);
7117 CLEAR_PARALLEL(m_data->ifaceSlotMap);
7118 CLEAR_PARALLEL(m_data->closureUseVars);
7120 CLEAR_PARALLEL(m_data->foldableReturnTypeMap);
7121 CLEAR_PARALLEL(m_data->contextualReturnTypes);
7123 parallel::for_each(clearers, [] (const std::function<void()>& f) { f(); });
7126 void Index::thaw() {
7127 m_data->frozen = false;
7130 std::unique_ptr<ArrayTypeTable::Builder>& Index::array_table_builder() const {
7131 return m_data->arrTableBuilder;
7134 //////////////////////////////////////////////////////////////////////
7136 res::Func Index::do_resolve(const php::Func* f) const {
7137 auto const finfo = create_func_info(*m_data, f);
7138 return res::Func { this, finfo };
7141 // Return true if we know for sure that one php::Class must derive
7142 // from another at runtime, in all possible instantiations.
7143 bool Index::must_be_derived_from(const php::Class* cls,
7144 const php::Class* parent) const {
7145 if (cls == parent) return true;
7146 auto const clsClass_it = m_data->classInfo.find(cls->name);
7147 auto const parentClass_it = m_data->classInfo.find(parent->name);
7148 if (clsClass_it == end(m_data->classInfo) || parentClass_it == end(m_data->classInfo)) {
7149 return true;
7152 auto const rCls = res::Class { clsClass_it->second };
7153 auto const rPar = res::Class { parentClass_it->second };
7154 return rCls.mustBeSubtypeOf(rPar);
7157 // Return true if any possible definition of one php::Class could
7158 // derive from another at runtime, or vice versa.
7159 bool
7160 Index::could_be_related(const php::Class* cls,
7161 const php::Class* parent) const {
7162 if (cls == parent) return true;
7163 auto const clsClass_it = m_data->classInfo.find(cls->name);
7164 auto const parentClass_it = m_data->classInfo.find(parent->name);
7165 if (clsClass_it == end(m_data->classInfo) || parentClass_it == end(m_data->classInfo)) {
7166 return false;
7169 auto const rCls = res::Class { clsClass_it->second };
7170 auto const rPar = res::Class { parentClass_it->second };
7171 return rCls.couldBe(rPar);
7174 //////////////////////////////////////////////////////////////////////
7176 PublicSPropMutations::Data& PublicSPropMutations::get() {
7177 if (!m_data) m_data = std::make_unique<Data>();
7178 return *m_data;
7181 void PublicSPropMutations::mergeKnown(const ClassInfo* ci,
7182 const php::Prop& prop,
7183 const Type& val) {
7184 ITRACE(4, "PublicSPropMutations::mergeKnown: {} {} {}\n",
7185 ci->cls->name->data(), prop.name, show(val));
7187 auto const res = get().m_known.emplace(
7188 KnownKey { const_cast<ClassInfo*>(ci), prop.name }, val
7190 if (!res.second) res.first->second |= val;
7193 void PublicSPropMutations::mergeUnknownClass(SString prop, const Type& val) {
7194 ITRACE(4, "PublicSPropMutations::mergeUnknownClass: {} {}\n",
7195 prop, show(val));
7197 auto const res = get().m_unknown.emplace(prop, val);
7198 if (!res.second) res.first->second |= val;
7201 void PublicSPropMutations::mergeUnknown(Context ctx) {
7202 ITRACE(4, "PublicSPropMutations::mergeUnknown\n");
7205 * We have a case here where we know neither the class nor the static
7206 * property name. This means we have to pessimize public static property
7207 * types for the entire program.
7209 * We could limit it to pessimizing them by merging the `val' type, but
7210 * instead we just throw everything away---this optimization is not
7211 * expected to be particularly useful on programs that contain any
7212 * instances of this situation.
7214 std::fprintf(
7215 stderr,
7216 "NOTE: had to mark everything unknown for public static "
7217 "property types due to dynamic code. -fanalyze-public-statics "
7218 "will not help for this program.\n"
7219 "NOTE: The offending code occured in this context: %s\n",
7220 show(ctx).c_str()
7222 get().m_nothing_known = true;
7225 //////////////////////////////////////////////////////////////////////