More HHBBC preresolve logic a bit more deterministic
[hiphop-php.git] / hphp / hhbbc / index.cpp
blob760e0d05f76632bf7c23d55d6a580330b6699cbe
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/Range.h>
40 #include <folly/String.h>
41 #include <folly/concurrency/ConcurrentHashMap.h>
43 #include "hphp/runtime/base/array-iterator.h"
44 #include "hphp/runtime/base/runtime-option.h"
45 #include "hphp/runtime/base/tv-comparisons.h"
47 #include "hphp/runtime/vm/native.h"
48 #include "hphp/runtime/vm/preclass-emitter.h"
49 #include "hphp/runtime/vm/runtime.h"
50 #include "hphp/runtime/vm/trait-method-import-data.h"
51 #include "hphp/runtime/vm/unit-util.h"
53 #include "hphp/hhbbc/analyze.h"
54 #include "hphp/hhbbc/class-util.h"
55 #include "hphp/hhbbc/context.h"
56 #include "hphp/hhbbc/func-util.h"
57 #include "hphp/hhbbc/options.h"
58 #include "hphp/hhbbc/options-util.h"
59 #include "hphp/hhbbc/parallel.h"
60 #include "hphp/hhbbc/representation.h"
61 #include "hphp/hhbbc/type-builtins.h"
62 #include "hphp/hhbbc/type-system.h"
63 #include "hphp/hhbbc/unit-util.h"
64 #include "hphp/hhbbc/wide-func.h"
66 #include "hphp/util/algorithm.h"
67 #include "hphp/util/assertions.h"
68 #include "hphp/util/hash-set.h"
69 #include "hphp/util/lock-free-lazy.h"
70 #include "hphp/util/match.h"
72 namespace HPHP { namespace HHBBC {
74 TRACE_SET_MOD(hhbbc_index);
76 //////////////////////////////////////////////////////////////////////
78 namespace {
80 //////////////////////////////////////////////////////////////////////
82 const StaticString s_construct("__construct");
83 const StaticString s_toBoolean("__toBoolean");
84 const StaticString s_invoke("__invoke");
85 const StaticString s_Closure("Closure");
86 const StaticString s_AsyncGenerator("HH\\AsyncGenerator");
87 const StaticString s_Generator("Generator");
89 //////////////////////////////////////////////////////////////////////
91 // HHBBC consumes a LOT of memory, so we keep representation types small.
92 template <typename T, size_t Expected, size_t Actual = sizeof(T)>
93 constexpr bool CheckSize() { static_assert(Expected == Actual); return true; };
94 static_assert(CheckSize<php::Block, 24>(), "");
95 static_assert(CheckSize<php::Local, use_lowptr ? 12 : 16>(), "");
96 static_assert(CheckSize<php::Param, use_lowptr ? 64 : 88>(), "");
97 static_assert(CheckSize<php::Func, use_lowptr ? 176 : 208>(), "");
99 // Likewise, we also keep the bytecode and immediate types small.
100 static_assert(CheckSize<Bytecode, use_lowptr ? 32 : 40>(), "");
101 static_assert(CheckSize<MKey, 16>(), "");
102 static_assert(CheckSize<IterArgs, 16>(), "");
103 static_assert(CheckSize<FCallArgs, 8>(), "");
104 static_assert(CheckSize<RepoAuthType, 8>(), "");
106 //////////////////////////////////////////////////////////////////////
109 * One-to-many case insensitive map, where the keys are static strings
110 * and the values are some kind of pointer.
112 template<class T> using ISStringToMany =
113 std::unordered_multimap<
114 SString,
116 string_data_hash,
117 string_data_isame
120 template<class T> using SStringToMany =
121 std::unordered_multimap<
122 SString,
124 string_data_hash,
125 string_data_same
129 * One-to-one case insensitive map, where the keys are static strings
130 * and the values are some T.
132 template<class T> using ISStringToOneT =
133 hphp_hash_map<
134 SString,
136 string_data_hash,
137 string_data_isame
140 template<class T> using SStringToOneT =
141 hphp_hash_map<
142 SString,
144 string_data_hash,
145 string_data_same
149 * One-to-one case sensitive map, where the keys are static strings
150 * and the values are some T.
152 * Elements are not stable under insert/erase.
154 template<class T> using SStringToOneFastT =
155 hphp_fast_map<
156 SString,
158 string_data_hash,
159 string_data_same
162 template<class T> using SStringToOneFastT =
163 hphp_fast_map<
164 SString,
166 string_data_hash,
167 string_data_same
171 * One-to-one case insensitive map, where the keys are static strings
172 * and the values are some kind of pointer.
174 template<class T> using ISStringToOne = ISStringToOneT<T*>;
176 //////////////////////////////////////////////////////////////////////
178 Dep operator|(Dep a, Dep b) {
179 return static_cast<Dep>(
180 static_cast<uintptr_t>(a) | static_cast<uintptr_t>(b)
184 bool has_dep(Dep m, Dep t) {
185 return static_cast<uintptr_t>(m) & static_cast<uintptr_t>(t);
189 * Maps functions to contexts that depend on information about that
190 * function, with information about the type of dependency.
192 using DepMap =
193 tbb::concurrent_hash_map<
194 DependencyContext,
195 std::map<DependencyContext,Dep,DependencyContextLess>,
196 DependencyContextHashCompare
199 //////////////////////////////////////////////////////////////////////
202 * Each ClassInfo has a table of public static properties with these entries.
203 * The `initializerType' is for use during refine_public_statics, and
204 * inferredType will always be a supertype of initializerType.
206 struct PublicSPropEntry {
207 Type inferredType;
208 Type initializerType;
209 const php::Prop* prop;
210 uint32_t refinements;
212 * This flag is set during analysis to indicate that we resolved the
213 * initial value (and updated it on the php::Class). This doesn't
214 * need to be atomic, because only one thread can resolve the value
215 * (the one processing the 86sinit), and it's been joined by the
216 * time we read the flag in refine_public_statics.
218 bool initialValueResolved;
219 bool everModified;
223 * Entries in the ClassInfo method table need to track some additional
224 * information.
226 * The reason for this is that we need to record attributes of the
227 * class hierarchy.
229 struct MethTabEntry {
230 MethTabEntry(const php::Func* func, Attr a, bool hpa, bool tl) :
231 func(func), attrs(a), hasPrivateAncestor(hpa), topLevel(tl) {}
232 const php::Func* func = nullptr;
233 // A method could be imported from a trait, and its attributes changed
234 Attr attrs {};
235 bool hasAncestor = false;
236 bool hasPrivateAncestor = false;
237 // This method came from the ClassInfo that owns the MethTabEntry,
238 // or one of its used traits.
239 bool topLevel = false;
240 uint32_t idx = 0;
245 struct res::Func::MethTabEntryPair :
246 SStringToOneT<MethTabEntry>::value_type {};
248 namespace {
250 using MethTabEntryPair = res::Func::MethTabEntryPair;
252 inline MethTabEntryPair* mteFromElm(
253 SStringToOneT<MethTabEntry>::value_type& elm) {
254 return static_cast<MethTabEntryPair*>(&elm);
257 inline const MethTabEntryPair* mteFromElm(
258 const SStringToOneT<MethTabEntry>::value_type& elm) {
259 return static_cast<const MethTabEntryPair*>(&elm);
262 inline MethTabEntryPair* mteFromIt(SStringToOneT<MethTabEntry>::iterator it) {
263 return static_cast<MethTabEntryPair*>(&*it);
266 struct CallContextHashCompare {
267 bool equal(const CallContext& a, const CallContext& b) const {
268 return a == b;
271 size_t hash(const CallContext& c) const {
272 auto ret = folly::hash::hash_combine(
273 c.callee,
274 c.args.size(),
275 c.context.hash()
277 for (auto& t : c.args) {
278 ret = folly::hash::hash_combine(ret, t.hash());
280 return ret;
284 using ContextRetTyMap = tbb::concurrent_hash_map<
285 CallContext,
286 Type,
287 CallContextHashCompare
290 //////////////////////////////////////////////////////////////////////
292 template<class Filter>
293 PropState make_unknown_propstate(const php::Class* cls,
294 Filter filter) {
295 auto ret = PropState{};
296 for (auto& prop : cls->properties) {
297 if (filter(prop)) {
298 auto& elem = ret[prop.name];
299 elem.ty = TCell;
300 elem.tc = &prop.typeConstraint;
301 elem.attrs = prop.attrs;
302 elem.everModified = true;
305 return ret;
311 * Currently inferred information about a PHP function.
313 * Nothing in this structure can ever be untrue. The way the
314 * algorithm works, whatever is in here must be factual (even if it is
315 * not complete information), because we may deduce other facts based
316 * on it.
318 struct res::Func::FuncInfo {
319 const php::Func* func = nullptr;
321 * The best-known return type of the function, if we have any
322 * information. May be TBottom if the function is known to never
323 * return (e.g. always throws).
325 Type returnTy = TInitCell;
328 * If the function always returns the same parameter, this will be
329 * set to its id; otherwise it will be NoLocalId.
331 LocalId retParam{NoLocalId};
334 * The number of times we've refined returnTy.
336 uint32_t returnRefinements{0};
339 * Whether the function is effectFree.
341 bool effectFree{false};
344 * Bitset representing which parameters definitely don't affect the
345 * result of the function, assuming it produces one. Note that
346 * VerifyParamType does not count as a use in this context.
348 std::bitset<64> unusedParams;
351 * List of all func families this function belongs to.
353 CompactVector<FuncFamily*> families;
356 namespace {
358 //////////////////////////////////////////////////////////////////////
361 * Known information about a particular constant:
362 * - if system is true, it's a system constant and other definitions
363 * will be ignored.
364 * - for non-system constants, if func is non-null it's the unique
365 * pseudomain defining the constant; otherwise there was more than
366 * one definition, or a non-pseudomain definition, and the type will
367 * be TInitCell
368 * - readonly is true if we've only seen uses of the constant, and no
369 * definitions (this could change during the first pass, but not after
370 * that).
373 struct ConstInfo {
374 const php::Func* func;
375 Type type;
376 bool system;
377 bool readonly;
380 using FuncFamily = res::Func::FuncFamily;
381 using FuncInfo = res::Func::FuncInfo;
382 using MethTabEntryPair = res::Func::MethTabEntryPair;
384 //////////////////////////////////////////////////////////////////////
388 //////////////////////////////////////////////////////////////////////
391 * Sometimes function resolution can't determine which function
392 * something will call, but can restrict it to a family of functions.
394 * For example, if you want to call an abstract function on a base
395 * class with all unique derived classes, we will resolve the function
396 * to a FuncFamily that contains references to all the possible
397 * overriding-functions.
399 struct res::Func::FuncFamily {
400 using PFuncVec = CompactVector<const MethTabEntryPair*>;
402 explicit FuncFamily(PFuncVec&& v) : m_v{std::move(v)} {}
403 FuncFamily(FuncFamily&& o) noexcept : m_v(std::move(o.m_v)) {}
404 FuncFamily& operator=(FuncFamily&& o) noexcept {
405 m_v = std::move(o.m_v);
406 return *this;
408 FuncFamily(const FuncFamily&) = delete;
409 FuncFamily& operator=(const FuncFamily&) = delete;
411 const PFuncVec& possibleFuncs() const {
412 return m_v;
415 friend auto begin(const FuncFamily& ff) { return ff.m_v.begin(); }
416 friend auto end(const FuncFamily& ff) { return ff.m_v.end(); }
418 PFuncVec m_v;
419 LockFreeLazy<Type> m_returnTy;
420 Optional<uint32_t> m_numInOut;
423 namespace {
425 struct PFuncVecHasher {
426 size_t operator()(const FuncFamily::PFuncVec& v) const {
427 return folly::hash::hash_range(
428 v.begin(),
429 v.end(),
431 pointer_hash<MethTabEntryPair>{}
438 //////////////////////////////////////////////////////////////////////
440 /* Known information about a particular possible instantiation of a
441 * PHP record. The php::Record will be marked AttrUnique if there is a unique
442 * RecordInfo with a given name.
444 struct RecordInfo {
445 const php::Record* rec = nullptr;
446 const RecordInfo* parent = nullptr;
448 * A vector of RecordInfo that encodes the inheritance hierarchy.
450 CompactVector<RecordInfo*> baseList;
451 const php::Record* phpType() const { return rec; }
455 * Known information about a particular possible instantiation of a
456 * PHP class. The php::Class will be marked AttrUnique if there is a
457 * unique ClassInfo with the same name.
459 struct ClassInfo {
461 * A pointer to the underlying php::Class that we're storing
462 * information about.
464 const php::Class* cls = nullptr;
467 * The info for the parent of this Class.
469 ClassInfo* parent = nullptr;
472 * A vector of the declared interfaces class info structures. This is in
473 * declaration order mirroring the php::Class interfaceNames vector, and does
474 * not include inherited interfaces.
476 CompactVector<const ClassInfo*> declInterfaces;
479 * A (case-insensitive) map from interface names supported by this class to
480 * their ClassInfo structures, flattened across the hierarchy.
482 ISStringToOneT<const ClassInfo*> implInterfaces;
485 * A vector of the included enums, in class order, mirroring the
486 * php::Class includedEnums vector.
488 CompactVector<const ClassInfo*> includedEnums;
490 struct ConstIndex {
491 php::Const operator*() const {
492 return cls->constants[idx];
494 const php::Const* operator->() const {
495 return get();
497 const php::Const* get() const {
498 return &cls->constants[idx];
500 const php::Class* cls;
501 uint32_t idx;
505 * A (case-sensitive) map from class constant name to the php::Class* and
506 * index into the constants vector that it came from. This map is flattened
507 * across the inheritance hierarchy.
509 hphp_fast_map<SString, ConstIndex> clsConstants;
512 * A vector of the used traits, in class order, mirroring the
513 * php::Class usedTraitNames vector.
515 CompactVector<const ClassInfo*> usedTraits;
518 * A list of extra properties supplied by this class's used traits.
520 CompactVector<php::Prop> traitProps;
523 * A list of extra consts supplied by this class's used traits.
525 CompactVector<php::Const> traitConsts;
528 * A (case-sensitive) map from class method names to the php::Func
529 * associated with it. This map is flattened across the inheritance
530 * hierarchy.
532 SStringToOneT<MethTabEntry> methods;
535 * A (case-sensitive) map from class method names to associated
536 * FuncFamily objects that group the set of possibly-overriding
537 * methods.
539 * Note that this does not currently encode anything for interface
540 * methods.
542 * Invariant: methods on this class with AttrNoOverride or
543 * AttrPrivate will not have an entry in this map.
545 SStringToOneFastT<FuncFamily*> methodFamilies;
546 // Resolutions to single entries do not require a FuncFamily (this
547 // saves space).
548 SStringToOneFastT<const MethTabEntryPair*> singleMethodFamilies;
551 * Subclasses of this class, including this class itself.
553 * For interfaces, this is the list of instantiable classes that
554 * implement this interface.
556 * For traits, this is the list of classes that use the trait where
557 * the trait wasn't flattened into the class (including the trait
558 * itself).
560 * Note, unlike baseList, the order of the elements in this vector
561 * is unspecified.
563 CompactVector<ClassInfo*> subclassList;
566 * A vector of ClassInfo that encodes the inheritance hierarchy,
567 * unless this ClassInfo represents an interface.
569 * This is the list of base classes for this class in inheritance
570 * order.
572 CompactVector<ClassInfo*> baseList;
575 * Property types for public static properties, declared on this exact class
576 * (i.e. not flattened in the hierarchy).
578 * These maps always have an entry for each public static property declared
579 * in this class, so it can also be used to check if this class declares a
580 * public static property of a given name.
582 * Note: the effective type we can assume a given static property may hold is
583 * not just the value in these maps.
585 hphp_hash_map<SString,PublicSPropEntry> publicStaticProps;
587 struct PreResolveState {
588 hphp_fast_map<SString, std::pair<php::Prop, const ClassInfo*>> pbuildNoTrait;
589 hphp_fast_map<SString, std::pair<php::Prop, const ClassInfo*>> pbuildTrait;
590 hphp_fast_set<SString> constsFromTraits;
592 std::unique_ptr<PreResolveState> preResolveState;
595 * Flags to track if this class is mocked, or if any of its dervied classes
596 * are mocked.
598 bool isMocked{false};
599 bool isDerivedMocked{false};
602 * Track if this class has a property which might redeclare a property in a
603 * parent class with an inequivalent type-hint.
605 bool hasBadRedeclareProp{true};
608 * Track if this class has any properties with initial values that might
609 * violate their type-hints.
611 bool hasBadInitialPropValues{true};
614 * Track if this class has any const props (including inherited ones).
616 bool hasConstProp{false};
619 * Track if any derived classes (including this one) have any const props.
621 bool derivedHasConstProp{false};
623 const php::Class* phpType() const { return cls; }
626 * Return true if this is derived from o.
628 bool derivedFrom(const ClassInfo& o) const {
629 if (this == &o) return true;
630 // If o is an interface, see if this declared it.
631 if (o.cls->attrs & AttrInterface) return implInterfaces.count(o.cls->name);
632 // Otherwise check for direct inheritance.
633 if (baseList.size() >= o.baseList.size()) {
634 return baseList[o.baseList.size() - 1] == &o;
636 return false;
640 * Flags about the existence of various magic methods, or whether
641 * any derived classes may have those methods. The non-derived
642 * flags imply the derived flags, even if the class is final, so you
643 * don't need to check both in those situations.
645 struct MagicFnInfo {
646 bool thisHas{false};
647 bool derivedHas{false};
649 MagicFnInfo magicBool;
652 struct MagicMapInfo {
653 StaticString name;
654 ClassInfo::MagicFnInfo ClassInfo::*pmem;
657 const MagicMapInfo magicMethods[] {
658 { StaticString{"__toBoolean"}, &ClassInfo::magicBool },
660 //////////////////////////////////////////////////////////////////////
662 namespace res {
663 Record::Record(Either<SString, RecordInfo*> val) : val(val) {}
665 bool Record::same(const Record& o) const {
666 return val == o.val;
669 bool Record::couldBe(const Record& o) const {
670 // If either types are not unique return true
671 if (val.left() || o.val.left()) return true;
673 auto r1 = val.right();
674 auto r2 = o.val.right();
675 assertx(r1 && r2);
676 // Both types are unique records so they "could be" if they are in an
677 // inheritance relationship
678 if (r1->baseList.size() >= r2->baseList.size()) {
679 return r1->baseList[r2->baseList.size() - 1] == r2;
680 } else {
681 return r2->baseList[r1->baseList.size() - 1] == r1;
685 SString Record::name() const {
686 return val.match(
687 [] (SString s) { return s; },
688 [] (RecordInfo* ri) { return ri->rec->name.get(); }
692 template <bool returnTrueOnMaybe>
693 bool Record::subtypeOfImpl(const Record& o) const {
694 auto s1 = val.left();
695 auto s2 = o.val.left();
696 if (s1 || s2) return returnTrueOnMaybe || s1 == s2;
697 auto r1 = val.right();
698 auto r2 = o.val.right();
699 assertx(r1 && r2);
700 if (r1->baseList.size() >= r2->baseList.size()) {
701 return r1->baseList[r2->baseList.size() - 1] == r2;
703 return false;
706 bool Record::mustBeSubtypeOf(const Record& o) const {
707 return subtypeOfImpl<false>(o);
710 bool Record::maybeSubtypeOf(const Record& o) const {
711 return subtypeOfImpl<true>(o);
714 bool Record::couldBeOverriden() const {
715 return val.match(
716 [] (SString) { return true; },
717 [] (RecordInfo* rinfo) {
718 return !(rinfo->rec->attrs & AttrFinal);
723 std::string show(const Record& r) {
724 return r.val.match(
725 [] (SString s) -> std::string {
726 return s->data();
728 [] (RecordInfo* rinfo) {
729 return folly::sformat("{}*", rinfo->rec->name);
734 Optional<Record> Record::commonAncestor(const Record& r) const {
735 if (val.left() || r.val.left()) return std::nullopt;
736 auto const c1 = val.right();
737 auto const c2 = r.val.right();
738 // Walk the arrays of base classes until they match. For common ancestors
739 // to exist they must be on both sides of the baseList at the same positions
740 RecordInfo* ancestor = nullptr;
741 auto it1 = c1->baseList.begin();
742 auto it2 = c2->baseList.begin();
743 while (it1 != c1->baseList.end() && it2 != c2->baseList.end()) {
744 if (*it1 != *it2) break;
745 ancestor = *it1;
746 ++it1; ++it2;
748 if (ancestor == nullptr) {
749 return std::nullopt;
751 return res::Record { ancestor };
754 Class::Class(Either<SString,ClassInfo*> val) : val(val) {}
756 // Class type operations here are very conservative for now.
758 bool Class::same(const Class& o) const {
759 return val == o.val;
762 template <bool returnTrueOnMaybe>
763 bool Class::subtypeOfImpl(const Class& o) const {
764 auto s1 = val.left();
765 auto s2 = o.val.left();
766 if (s1 || s2) return returnTrueOnMaybe || s1 == s2;
767 auto c1 = val.right();
768 auto c2 = o.val.right();
769 return c1->derivedFrom(*c2);
772 bool Class::mustBeSubtypeOf(const Class& o) const {
773 return subtypeOfImpl<false>(o);
776 bool Class::maybeSubtypeOf(const Class& o) const {
777 return subtypeOfImpl<true>(o);
780 bool Class::couldBe(const Class& o) const {
781 if (same(o)) return true;
783 // If either types are not unique return true
784 if (val.left() || o.val.left()) return true;
786 auto c1 = val.right();
787 auto c2 = o.val.right();
788 // if one or the other is an interface return true for now.
789 // TODO(#3621433): better interface stuff
790 if (c1->cls->attrs & AttrInterface || c2->cls->attrs & AttrInterface) {
791 return true;
794 // Both types are unique classes so they "could be" if they are in an
795 // inheritance relationship
796 if (c1->baseList.size() >= c2->baseList.size()) {
797 return c1->baseList[c2->baseList.size() - 1] == c2;
798 } else {
799 return c2->baseList[c1->baseList.size() - 1] == c1;
803 SString Class::name() const {
804 return val.match(
805 [] (SString s) { return s; },
806 [] (ClassInfo* ci) { return ci->cls->name.get(); }
810 bool Class::couldBeInterface() const {
811 return val.match(
812 [] (SString) { return true; },
813 [] (ClassInfo* cinfo) {
814 return cinfo->cls->attrs & AttrInterface;
819 bool Class::mustBeInterface() const {
820 return val.match(
821 [] (SString) { return false; },
822 [] (ClassInfo* cinfo) {
823 return cinfo->cls->attrs & AttrInterface;
828 bool Class::couldBeOverriden() const {
829 return val.match(
830 [] (SString) { return true; },
831 [] (ClassInfo* cinfo) {
832 return !(cinfo->cls->attrs & AttrNoOverride);
837 bool Class::couldHaveMagicBool() const {
838 return val.match(
839 [] (SString) { return true; },
840 [] (ClassInfo* cinfo) {
841 return cinfo->magicBool.derivedHas;
846 bool Class::couldHaveMockedDerivedClass() const {
847 return val.match(
848 [] (SString) { return true;},
849 [] (ClassInfo* cinfo) {
850 return cinfo->isDerivedMocked;
855 bool Class::couldBeMocked() const {
856 return val.match(
857 [] (SString) { return true;},
858 [] (ClassInfo* cinfo) {
859 return cinfo->isMocked;
864 bool Class::couldHaveReifiedGenerics() const {
865 return val.match(
866 [] (SString) { return true; },
867 [] (ClassInfo* cinfo) {
868 return cinfo->cls->hasReifiedGenerics;
873 bool Class::mightCareAboutDynConstructs() const {
874 if (RuntimeOption::EvalForbidDynamicConstructs > 0) {
875 return val.match(
876 [] (SString) { return true; },
877 [] (ClassInfo* cinfo) {
878 return !(cinfo->cls->attrs & AttrDynamicallyConstructible);
882 return false;
885 bool Class::couldHaveConstProp() const {
886 return val.match(
887 [] (SString) { return true; },
888 [] (ClassInfo* cinfo) { return cinfo->hasConstProp; }
892 bool Class::derivedCouldHaveConstProp() const {
893 return val.match(
894 [] (SString) { return true; },
895 [] (ClassInfo* cinfo) { return cinfo->derivedHasConstProp; }
899 Optional<Class> Class::commonAncestor(const Class& o) const {
900 if (val.left() || o.val.left()) return std::nullopt;
901 auto const c1 = val.right();
902 auto const c2 = o.val.right();
903 if (c1 == c2) return res::Class { c1 };
904 // Walk the arrays of base classes until they match. For common ancestors
905 // to exist they must be on both sides of the baseList at the same positions
906 ClassInfo* ancestor = nullptr;
907 auto it1 = c1->baseList.begin();
908 auto it2 = c2->baseList.begin();
909 while (it1 != c1->baseList.end() && it2 != c2->baseList.end()) {
910 if (*it1 != *it2) break;
911 ancestor = *it1;
912 ++it1; ++it2;
914 if (ancestor == nullptr) {
915 return std::nullopt;
917 return res::Class { ancestor };
920 Optional<res::Class> Class::parent() const {
921 if (!val.right()) return std::nullopt;
922 auto parent = val.right()->parent;
923 if (!parent) return std::nullopt;
924 return res::Class { parent };
927 const php::Class* Class::cls() const {
928 return val.right() ? val.right()->cls : nullptr;
931 std::string show(const Class& c) {
932 return c.val.match(
933 [] (SString s) -> std::string {
934 return s->data();
936 [] (ClassInfo* cinfo) {
937 return folly::sformat("{}*", cinfo->cls->name);
942 Func::Func(const Index* idx, Rep val)
943 : index(idx)
944 , val(val)
947 SString Func::name() const {
948 return match<SString>(
949 val,
950 [&] (FuncName s) { return s.name; },
951 [&] (MethodName s) { return s.name; },
952 [&] (FuncInfo* fi) { return fi->func->name; },
953 [&] (const MethTabEntryPair* mte) { return mte->first; },
954 [&] (FuncFamily* fa) -> SString {
955 auto const name = fa->possibleFuncs().front()->first;
956 if (debug) {
957 for (DEBUG_ONLY auto const f : fa->possibleFuncs()) {
958 assertx(f->first->isame(name));
961 return name;
966 const php::Func* Func::exactFunc() const {
967 using Ret = const php::Func*;
968 return match<Ret>(
969 val,
970 [&](FuncName) { return Ret{}; },
971 [&](MethodName) { return Ret{}; },
972 [&](FuncInfo* fi) { return fi->func; },
973 [&](const MethTabEntryPair* mte) { return mte->second.func; },
974 [&](FuncFamily* /*fa*/) { return Ret{}; }
978 bool Func::isFoldable() const {
979 return match<bool>(
980 val,
981 [&](FuncName) { return false; },
982 [&](MethodName) { return false; },
983 [&](FuncInfo* fi) {
984 return fi->func->attrs & AttrIsFoldable;
986 [&](const MethTabEntryPair* mte) {
987 return mte->second.func->attrs & AttrIsFoldable;
989 [&](FuncFamily* fa) { return false; }
993 bool Func::couldHaveReifiedGenerics() const {
994 return match<bool>(
995 val,
996 [&](FuncName s) { return true; },
997 [&](MethodName) { return true; },
998 [&](FuncInfo* fi) { return fi->func->isReified; },
999 [&](const MethTabEntryPair* mte) {
1000 return mte->second.func->isReified;
1002 [&](FuncFamily* fa) {
1003 for (auto const pf : fa->possibleFuncs()) {
1004 if (pf->second.func->isReified) return true;
1006 return false;
1011 bool Func::mightCareAboutDynCalls() const {
1012 if (RuntimeOption::EvalNoticeOnBuiltinDynamicCalls && mightBeBuiltin()) {
1013 return true;
1015 auto const mightCareAboutFuncs =
1016 RuntimeOption::EvalForbidDynamicCallsToFunc > 0;
1017 auto const mightCareAboutInstMeth =
1018 RuntimeOption::EvalForbidDynamicCallsToInstMeth > 0;
1019 auto const mightCareAboutClsMeth =
1020 RuntimeOption::EvalForbidDynamicCallsToClsMeth > 0;
1022 return match<bool>(
1023 val,
1024 [&](FuncName) { return mightCareAboutFuncs; },
1025 [&](MethodName) {
1026 return mightCareAboutClsMeth || mightCareAboutInstMeth;
1028 [&](FuncInfo* fi) {
1029 return dyn_call_error_level(fi->func) > 0;
1031 [&](const MethTabEntryPair* mte) {
1032 return dyn_call_error_level(mte->second.func) > 0;
1034 [&](FuncFamily* fa) {
1035 for (auto const pf : fa->possibleFuncs()) {
1036 if (dyn_call_error_level(pf->second.func) > 0)
1037 return true;
1039 return false;
1044 bool Func::mightBeBuiltin() const {
1045 return match<bool>(
1046 val,
1047 // Builtins are always uniquely resolvable unless renaming is
1048 // involved.
1049 [&](FuncName s) { return s.renamable; },
1050 [&](MethodName) { return true; },
1051 [&](FuncInfo* fi) { return fi->func->attrs & AttrBuiltin; },
1052 [&](const MethTabEntryPair* mte) {
1053 return mte->second.func->attrs & AttrBuiltin;
1055 [&](FuncFamily* fa) {
1056 for (auto const pf : fa->possibleFuncs()) {
1057 if (pf->second.func->attrs & AttrBuiltin) return true;
1059 return false;
1064 namespace {
1066 uint32_t numNVArgs(const php::Func& f) {
1067 uint32_t cnt = f.params.size();
1068 return cnt && f.params[cnt - 1].isVariadic ? cnt - 1 : cnt;
1073 uint32_t Func::minNonVariadicParams() const {
1074 return match<uint32_t>(
1075 val,
1076 [&] (FuncName) { return 0; },
1077 [&] (MethodName) { return 0; },
1078 [&] (FuncInfo* fi) { return numNVArgs(*fi->func); },
1079 [&] (const MethTabEntryPair* mte) { return numNVArgs(*mte->second.func); },
1080 [&] (FuncFamily* fa) {
1081 auto c = std::numeric_limits<uint32_t>::max();
1082 for (auto const pf : fa->possibleFuncs()) {
1083 c = std::min(c, numNVArgs(*pf->second.func));
1085 return c;
1090 uint32_t Func::maxNonVariadicParams() const {
1091 return match<uint32_t>(
1092 val,
1093 [&] (FuncName) { return std::numeric_limits<uint32_t>::max(); },
1094 [&] (MethodName) { return std::numeric_limits<uint32_t>::max(); },
1095 [&] (FuncInfo* fi) { return numNVArgs(*fi->func); },
1096 [&] (const MethTabEntryPair* mte) { return numNVArgs(*mte->second.func); },
1097 [&] (FuncFamily* fa) {
1098 uint32_t c = 0;
1099 for (auto const pf : fa->possibleFuncs()) {
1100 c = std::max(c, numNVArgs(*pf->second.func));
1102 return c;
1107 std::string show(const Func& f) {
1108 auto ret = f.name()->toCppString();
1109 match<void>(
1110 f.val,
1111 [&](Func::FuncName s) { if (s.renamable) ret += '?'; },
1112 [&](Func::MethodName) {},
1113 [&](FuncInfo*) { ret += "*"; },
1114 [&](const MethTabEntryPair*) { ret += "*"; },
1115 [&](FuncFamily*) { ret += "+"; }
1117 return ret;
1122 //////////////////////////////////////////////////////////////////////
1124 using IfaceSlotMap = hphp_hash_map<const php::Class*, Slot>;
1125 using ConstInfoConcurrentMap =
1126 tbb::concurrent_hash_map<SString, ConstInfo, StringDataHashCompare>;
1128 template <typename T>
1129 struct ResTypeHelper;
1131 template <>
1132 struct ResTypeHelper<res::Class> {
1133 using InfoT = ClassInfo;
1134 using InfoMapT = ISStringToOneT<InfoT*>;
1135 using OtherT = res::Record;
1136 static std::string name() { return "class"; }
1139 template <>
1140 struct ResTypeHelper<res::Record> {
1141 using InfoT = RecordInfo;
1142 using InfoMapT = ISStringToOneT<InfoT*>;
1143 using OtherT = res::Class;
1144 static std::string name() { return "record"; }
1147 struct Index::IndexData {
1148 explicit IndexData(Index* index) : m_index{index} {}
1149 IndexData(const IndexData&) = delete;
1150 IndexData& operator=(const IndexData&) = delete;
1151 ~IndexData() {
1152 if (compute_iface_vtables.joinable()) {
1153 compute_iface_vtables.join();
1157 Index* m_index;
1159 bool frozen{false};
1160 bool ever_frozen{false};
1162 std::unique_ptr<ArrayTypeTable::Builder> arrTableBuilder;
1164 ISStringToOneT<const php::Class*> classes;
1165 SStringToMany<const php::Func> methods;
1166 SStringToOneFastT<uint64_t> method_inout_params_by_name;
1167 ISStringToOneT<const php::Func*> funcs;
1168 ISStringToOneT<const php::TypeAlias*> typeAliases;
1169 ISStringToOneT<const php::Class*> enums;
1170 SStringToOneT<const php::Constant*> constants;
1171 ISStringToOneT<const php::Record*> records;
1173 // Map from each class to all the closures that are allocated in
1174 // functions of that class.
1175 hphp_hash_map<
1176 const php::Class*,
1177 CompactVector<const php::Class*>
1178 > classClosureMap;
1180 hphp_hash_map<
1181 const php::Class*,
1182 hphp_fast_set<const php::Func*>
1183 > classExtraMethodMap;
1186 * Map from each class name to ClassInfo objects if one exists.
1188 * It may not exists if we would fatal when defining the class. That could
1189 * happen for if the inheritance is bad or __Sealed or other things.
1191 ISStringToOneT<ClassInfo*> classInfo;
1194 * All the ClassInfos, sorted topologically (ie all the parents,
1195 * interfaces and traits used by the ClassInfo at index K will have
1196 * indices less than K). This mostly drops out of the way ClassInfos
1197 * are created; it would be hard to create the ClassInfos for the
1198 * php::Class X (or even know how many to create) without knowing
1199 * all the ClassInfos that were created for X's dependencies.
1201 std::vector<std::unique_ptr<ClassInfo>> allClassInfos;
1204 * Map from each record name to RecordInfo objects if one exists.
1206 * It may not exists if we would fatal when defining the record.
1208 ISStringToOneT<RecordInfo*> recordInfo;
1211 * All the RecordInfos, sorted topologically (ie all the parents of
1212 * RecordInfo at index K will have indices less than K).
1213 * This mostly drops out of the way RecordInfos are created;
1214 * it would be hard to create the RecordInfos for the
1215 * php::Record X (or even know how many to create) without knowing
1216 * all the RecordInfos that were created for X's dependencies.
1218 std::vector<std::unique_ptr<RecordInfo>> allRecordInfos;
1220 std::vector<FuncInfo> funcInfo;
1222 // Private instance and static property types are stored separately
1223 // from ClassInfo, because you don't need to resolve a class to get
1224 // at them.
1225 hphp_hash_map<
1226 const php::Class*,
1227 PropState
1228 > privatePropInfo;
1229 hphp_hash_map<
1230 const php::Class*,
1231 PropState
1232 > privateStaticPropInfo;
1235 * Public static property information:
1238 // If this is true, we've seen mutations to public static
1239 // properties. Once this is true, it's no longer legal to report a
1240 // pessimistic static property set (unknown class and
1241 // property). Doing so is a monotonicity violation.
1242 bool seenPublicSPropMutations{false};
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_concurrent_hash_map_simd<
1249 const php::Func*,
1250 PublicSPropMutations,
1251 pointer_hash<const php::Func>> publicSPropMutations;
1253 // All FuncFamilies. These are stored globally so we can avoid
1254 // generating duplicates.
1255 struct FuncFamilyPtrHasher {
1256 using is_transparent = void;
1257 size_t operator()(const std::unique_ptr<FuncFamily>& ff) const {
1258 return PFuncVecHasher{}(ff->possibleFuncs());
1260 size_t operator()(const FuncFamily::PFuncVec& pf) const {
1261 return PFuncVecHasher{}(pf);
1264 struct FuncFamilyPtrEquals {
1265 using is_transparent = void;
1266 bool operator()(const std::unique_ptr<FuncFamily>& a,
1267 const std::unique_ptr<FuncFamily>& b) const {
1268 return a->possibleFuncs() == b->possibleFuncs();
1270 bool operator()(const FuncFamily::PFuncVec& pf,
1271 const std::unique_ptr<FuncFamily>& ff) const {
1272 return pf == ff->possibleFuncs();
1275 folly_concurrent_hash_map_simd<
1276 std::unique_ptr<FuncFamily>,
1277 bool,
1278 FuncFamilyPtrHasher,
1279 FuncFamilyPtrEquals
1280 > funcFamilies;
1283 * Map from interfaces to their assigned vtable slots, computed in
1284 * compute_iface_vtables().
1286 IfaceSlotMap ifaceSlotMap;
1288 hphp_hash_map<
1289 const php::Class*,
1290 CompactVector<Type>
1291 > closureUseVars;
1293 bool useClassDependencies{};
1294 DepMap dependencyMap;
1297 * If a function is effect-free when called with a particular set of
1298 * literal arguments, and produces a literal result, there will be
1299 * an entry here representing the type.
1301 * The map isn't just an optimization; we can't call
1302 * analyze_func_inline during the optimization phase, because the
1303 * bytecode could be modified while we do so.
1305 ContextRetTyMap foldableReturnTypeMap;
1308 * Call-context sensitive return types are cached here. This is not
1309 * an optimization.
1311 * The reason we need to retain this information about the
1312 * calling-context-sensitive return types is that once the Index is
1313 * frozen (during the final optimization pass), calls to
1314 * lookup_return_type with a CallContext can't look at the bytecode
1315 * bodies of functions other than the calling function. So we need
1316 * to know what we determined the last time we were alloewd to do
1317 * that so we can return it again.
1319 ContextRetTyMap contextualReturnTypes{};
1321 std::thread compute_iface_vtables;
1323 template<typename T>
1324 const typename ResTypeHelper<T>::InfoMapT& infoMap() const;
1327 template<>
1328 const typename ResTypeHelper<res::Class>::InfoMapT&
1329 Index::IndexData::infoMap<res::Class>() const {
1330 return classInfo;
1332 template<>
1333 const typename ResTypeHelper<res::Record>::InfoMapT&
1334 Index::IndexData::infoMap<res::Record>() const {
1335 return recordInfo;
1338 //////////////////////////////////////////////////////////////////////
1340 namespace {
1342 //////////////////////////////////////////////////////////////////////
1344 using IndexData = Index::IndexData;
1346 std::mutex closure_use_vars_mutex;
1347 std::mutex private_propstate_mutex;
1349 DependencyContext make_dep(const php::Func* func) {
1350 return DependencyContext{DependencyContextType::Func, func};
1352 DependencyContext make_dep(const php::Class* cls) {
1353 return DependencyContext{DependencyContextType::Class, cls};
1355 DependencyContext make_dep(const php::Prop* prop) {
1356 return DependencyContext{DependencyContextType::Prop, prop};
1358 DependencyContext make_dep(const FuncFamily* family) {
1359 return DependencyContext{DependencyContextType::FuncFamily, family};
1362 DependencyContext dep_context(IndexData& data, const Context& ctx) {
1363 if (!ctx.cls || !data.useClassDependencies) return make_dep(ctx.func);
1364 auto const cls = ctx.cls->closureContextCls ?
1365 ctx.cls->closureContextCls : ctx.cls;
1366 if (is_used_trait(*cls)) return make_dep(ctx.func);
1367 return make_dep(cls);
1370 template <typename T>
1371 void add_dependency(IndexData& data,
1372 T src,
1373 const Context& dst,
1374 Dep newMask) {
1375 if (data.frozen) return;
1377 auto d = dep_context(data, dst);
1378 DepMap::accessor acc;
1379 data.dependencyMap.insert(acc, make_dep(src));
1380 auto& current = acc->second[d];
1381 current = current | newMask;
1384 std::mutex func_info_mutex;
1386 FuncInfo* create_func_info(IndexData& data, const php::Func* f) {
1387 auto fi = &data.funcInfo[f->idx];
1388 if (UNLIKELY(fi->func == nullptr)) {
1389 if (f->nativeInfo) {
1390 std::lock_guard<std::mutex> g{func_info_mutex};
1391 if (fi->func) {
1392 assertx(fi->func == f);
1393 return fi;
1395 // We'd infer this anyway when we look at the bytecode body
1396 // (NativeImpl) for the HNI function, but just initializing it
1397 // here saves on whole-program iterations.
1398 fi->returnTy = native_function_return_type(f);
1400 fi->func = f;
1403 assertx(fi->func == f);
1404 return fi;
1407 FuncInfo* func_info(IndexData& data, const php::Func* f) {
1408 auto const fi = &data.funcInfo[f->idx];
1409 return fi;
1412 template <typename T>
1413 void find_deps(IndexData& data,
1414 T src,
1415 Dep mask,
1416 DependencyContextSet& deps) {
1417 auto const srcDep = make_dep(src);
1420 DepMap::const_accessor acc;
1421 if (data.dependencyMap.find(acc, srcDep)) {
1422 for (auto const& kv : acc->second) {
1423 if (has_dep(kv.second, mask)) deps.insert(kv.first);
1428 // If this is a Func dep, we need to also check if any FuncFamily
1429 // dependencies need to be added.
1430 if (srcDep.tag() != DependencyContextType::Func) return;
1432 auto const fi = func_info(data, static_cast<const php::Func*>(srcDep.ptr()));
1433 if (!fi->func) return;
1435 // Add any associated FuncFamilies
1436 for (auto const ff : fi->families) {
1437 DepMap::const_accessor acc;
1438 if (data.dependencyMap.find(acc, make_dep(ff))) {
1439 for (auto const& kv : acc->second) {
1440 if (has_dep(kv.second, mask)) deps.insert(kv.first);
1446 struct TraitMethod {
1447 using class_type = const ClassInfo*;
1448 using method_type = const php::Func*;
1450 TraitMethod(class_type trait_, method_type method_, Attr modifiers_)
1451 : trait(trait_)
1452 , method(method_)
1453 , modifiers(modifiers_)
1456 class_type trait;
1457 method_type method;
1458 Attr modifiers;
1461 struct TMIOps {
1462 using string_type = LSString;
1463 using class_type = TraitMethod::class_type;
1464 using method_type = TraitMethod::method_type;
1466 struct TMIException : std::exception {
1467 explicit TMIException(std::string msg) : msg(msg) {}
1468 const char* what() const noexcept override { return msg.c_str(); }
1469 private:
1470 std::string msg;
1473 // Return the name for the trait class.
1474 static const string_type clsName(class_type traitCls) {
1475 return traitCls->cls->name;
1478 // Return the name for the trait method.
1479 static const string_type methName(method_type meth) {
1480 return meth->name;
1483 // Is-a methods.
1484 static bool isTrait(class_type traitCls) {
1485 return traitCls->cls->attrs & AttrTrait;
1487 static bool isAbstract(Attr modifiers) {
1488 return modifiers & AttrAbstract;
1491 // Whether to exclude methods with name `methName' when adding.
1492 static bool exclude(string_type methName) {
1493 return Func::isSpecial(methName);
1496 // TraitMethod constructor.
1497 static TraitMethod traitMethod(class_type traitCls,
1498 method_type traitMeth,
1499 const PreClass::TraitAliasRule& rule) {
1500 return TraitMethod { traitCls, traitMeth, rule.modifiers() };
1503 // Register a trait alias once the trait class is found.
1504 static void addTraitAlias(const ClassInfo* /*cls*/,
1505 const PreClass::TraitAliasRule& /*rule*/,
1506 class_type /*traitCls*/) {
1507 // purely a runtime thing... nothing to do
1510 // Trait class/method finders.
1511 static class_type findSingleTraitWithMethod(class_type cls,
1512 string_type origMethName) {
1513 class_type traitCls = nullptr;
1515 for (auto const t : cls->usedTraits) {
1516 // Note: m_methods includes methods from parents/traits recursively.
1517 if (t->methods.count(origMethName)) {
1518 if (traitCls != nullptr) {
1519 return nullptr;
1521 traitCls = t;
1524 return traitCls;
1527 static class_type findTraitClass(class_type cls,
1528 string_type traitName) {
1529 for (auto const t : cls->usedTraits) {
1530 if (traitName->isame(t->cls->name)) return t;
1532 return nullptr;
1535 static method_type findTraitMethod(class_type traitCls,
1536 string_type origMethName) {
1537 auto it = traitCls->methods.find(origMethName);
1538 if (it == traitCls->methods.end()) return nullptr;
1539 return it->second.func;
1542 // Errors.
1543 static void errorUnknownMethod(string_type methName) {
1544 throw TMIException(folly::sformat("Unknown method '{}'", methName));
1546 static void errorUnknownTrait(string_type traitName) {
1547 throw TMIException(folly::sformat("Unknown trait '{}'", traitName));
1549 static void errorDuplicateMethod(class_type cls,
1550 string_type methName,
1551 const std::list<TraitMethod>&) {
1552 auto const& m = cls->cls->methods;
1553 if (std::find_if(m.begin(), m.end(),
1554 [&] (auto const& f) {
1555 return f->name->isame(methName);
1556 }) != m.end()) {
1557 // the duplicate methods will be overridden by the class method.
1558 return;
1560 throw TMIException(folly::sformat("DuplicateMethod: {}", methName));
1562 static void errorInconsistentInsteadOf(class_type cls,
1563 string_type methName) {
1564 throw TMIException(folly::sformat("InconsistentInsteadOf: {} {}",
1565 methName, cls->cls->name));
1567 static void errorMultiplyExcluded(string_type traitName,
1568 string_type methName) {
1569 throw TMIException(folly::sformat("MultiplyExcluded: {}::{}",
1570 traitName, methName));
1574 using TMIData = TraitMethodImportData<TraitMethod,
1575 TMIOps>;
1577 template<typename T>
1578 struct PreResolveUpdates {
1579 TinyVector<std::unique_ptr<T>> newInfos;
1580 TinyVector<T*> updateDeps;
1582 struct CnsHash {
1583 size_t operator()(const ClassInfo::ConstIndex& cns) const {
1584 return hash_int64_pair((uintptr_t)cns.cls, cns.idx);
1587 struct CnsEquals {
1588 bool operator()(const ClassInfo::ConstIndex& cns1,
1589 const ClassInfo::ConstIndex& cns2) const {
1590 return
1591 cns1.cls == cns2.cls &&
1592 cns1.idx == cns2.idx;
1596 hphp_fast_set<ClassInfo::ConstIndex, CnsHash, CnsEquals> removeNoOverride;
1598 hphp_hash_map<
1599 const php::Class*,
1600 hphp_fast_set<const php::Func*>
1601 > extraMethods;
1602 hphp_hash_map<
1603 const php::Class*,
1604 CompactVector<const php::Class*>
1605 > closures;
1606 CompactVector<const php::Class*> newClosures;
1607 CompactVector<
1608 std::tuple<std::unique_ptr<php::Class>, php::Unit*, uint32_t>
1609 > newClasses;
1611 uint32_t nextClassId = 0;
1614 using RecPreResolveUpdates = PreResolveUpdates<RecordInfo>;
1615 using ClsPreResolveUpdates = PreResolveUpdates<ClassInfo>;
1617 // Keep track of order of closure creation to make the logic more
1618 // deterministic.
1619 struct ClonedClosureMap {
1620 using Tuple = std::tuple<const php::Class*,
1621 std::unique_ptr<php::Class>,
1622 uint32_t>;
1624 bool empty() const { return ordered.empty(); }
1626 CompactVector<Tuple>::iterator find(const php::Class* cls) {
1627 auto const it = map.find(cls);
1628 if (it == map.end()) return ordered.end();
1629 auto const idx = it->second;
1630 assertx(idx < ordered.size());
1631 assertx(std::get<0>(ordered[idx]) == cls);
1632 return ordered.begin() + idx;
1635 bool emplace(const php::Class* cls,
1636 std::unique_ptr<php::Class> clo,
1637 uint32_t id) {
1638 auto const inserted = map.emplace(cls, ordered.size()).second;
1639 if (!inserted) return false;
1640 ordered.emplace_back(cls, std::move(clo), id);
1641 return true;
1644 CompactVector<Tuple>::iterator begin() {
1645 return ordered.begin();
1647 CompactVector<Tuple>::iterator end() {
1648 return ordered.end();
1651 private:
1652 hphp_fast_map<const php::Class*, size_t> map;
1653 CompactVector<Tuple> ordered;
1656 std::unique_ptr<php::Func> clone_meth(php::Unit* unit,
1657 php::Class* newContext,
1658 const php::Func* origMeth,
1659 SString name,
1660 Attr attrs,
1661 std::atomic<uint32_t>& nextFuncId,
1662 ClsPreResolveUpdates& updates,
1663 ClonedClosureMap& clonedClosures);
1665 * Make a flattened table of the constants on this class.
1667 bool build_class_constants(const php::Program* program, ClassInfo* cinfo, ClsPreResolveUpdates& updates) {
1668 auto const removeNoOverride = [&] (ClassInfo::ConstIndex cns) {
1669 // During hhbbc/parse, all constants are pre-set to NoOverride
1670 ITRACE(2, "Removing NoOverride on {}::{}\n", cns->cls->name, cns->name);
1671 if (cns->isNoOverride) updates.removeNoOverride.emplace(cns);
1674 if (cinfo->parent) cinfo->clsConstants = cinfo->parent->clsConstants;
1676 auto const add = [&] (const ClassInfo::ConstIndex& cns, bool fromTrait) {
1677 auto insert = cinfo->clsConstants.emplace(cns->name, cns);
1678 if (insert.second) {
1679 if (fromTrait) {
1680 cinfo->preResolveState->constsFromTraits.emplace(cns->name);
1682 return true;
1684 auto& existing = insert.first->second;
1686 // Same constant (from an interface via two different paths) is ok
1687 if (existing->cls == cns->cls) return true;
1689 if (existing->kind != cns->kind) {
1690 ITRACE(
1692 "build_class_constants failed for `{}' because `{}' was defined by "
1693 "`{}' as a {} and by `{}' as a {}\n",
1694 cinfo->cls->name,
1695 cns->name,
1696 cns->cls->name,
1697 ConstModifiers::show(cns->kind),
1698 existing->cls->name,
1699 ConstModifiers::show(existing->kind)
1701 return false;
1704 // Ignore abstract constants
1705 if (cns->isAbstract && !cns->val) return true;
1707 // if the existing constant in the map is concrete, then don't overwrite it with an incoming
1708 // abstract constant's default
1709 if (!existing->isAbstract && cns->isAbstract) {
1710 return true;
1713 if (existing->val) {
1714 // A constant from a declared interface collides with a constant
1715 // (Excluding constants from interfaces a trait implements)
1716 // Need this check otherwise constants from traits that conflict with
1717 // declared interfaces will silently lose and not conflict in the runtime
1718 // Type and Context constants can be overriden.
1719 if (cns->kind == ConstModifiers::Kind::Value &&
1720 !existing->isAbstract &&
1721 existing->cls->attrs & AttrInterface &&
1722 !(cns->cls->attrs & AttrInterface && fromTrait)) {
1723 for (auto const& interface : cinfo->declInterfaces) {
1724 if (existing->cls == interface->cls) {
1725 ITRACE(
1727 "build_class_constants failed for `{}' because "
1728 "`{}' was defined by both `{}' and `{}'\n",
1729 cinfo->cls->name,
1730 cns->name,
1731 cns->cls->name,
1732 existing->cls->name
1734 return false;
1739 if (!RO::EvalTraitConstantInterfaceBehavior) {
1740 // Constants from traits silently lose
1741 if (fromTrait) {
1742 removeNoOverride(cns);
1743 return true;
1747 if ((cns->cls->attrs & AttrInterface ||
1748 (RO::EvalTraitConstantInterfaceBehavior && (cns->cls->attrs & AttrTrait))) &&
1749 existing->isAbstract) {
1750 // because existing has val, this covers the case where it is abstract with default
1751 // allow incoming to win
1752 } else {
1753 // A constant from an interface or from an included enum collides
1754 // with an existing constant.
1755 if (cns->cls->attrs & (AttrInterface | AttrEnum | AttrEnumClass) ||
1756 (RO::EvalTraitConstantInterfaceBehavior && (cns->cls->attrs & AttrTrait))) {
1757 ITRACE(
1759 "build_class_constants failed for `{}' because "
1760 "`{}' was defined by both `{}' and `{}'\n",
1761 cinfo->cls->name,
1762 cns->name,
1763 cns->cls->name,
1764 existing->cls->name
1766 return false;
1771 removeNoOverride(existing);
1772 existing = cns;
1773 if (fromTrait) {
1774 cinfo->preResolveState->constsFromTraits.emplace(cns->name);
1775 } else {
1776 cinfo->preResolveState->constsFromTraits.erase(cns->name);
1778 return true;
1781 for (auto const iface : cinfo->declInterfaces) {
1782 for (auto const& cns : iface->clsConstants) {
1783 if (!add(cns.second,
1784 iface->preResolveState->constsFromTraits.count(cns.first))) {
1785 return false;
1790 auto const addShallowConstants = [&]() {
1791 for (uint32_t idx = 0; idx < cinfo->cls->constants.size(); ++idx) {
1792 auto const cns = ClassInfo::ConstIndex { cinfo->cls, idx };
1793 if (cinfo->cls->attrs & AttrTrait) removeNoOverride(cns);
1794 if (!add(cns, false)) return false;
1796 return true;
1799 auto const addTraitConstants = [&]() {
1800 for (auto const trait : cinfo->usedTraits) {
1801 for (auto const& cns : trait->clsConstants) {
1802 if (!add(cns.second, true)) return false;
1805 return true;
1808 if (RO::EvalTraitConstantInterfaceBehavior) {
1809 // trait constants must be inserted before constants shallowly declared on the class
1810 // to match the interface semantics
1811 if (!addTraitConstants()) return false;
1812 if (!addShallowConstants()) return false;
1813 } else {
1814 if (!addShallowConstants()) return false;
1815 if (!addTraitConstants()) return false;
1818 for (auto const ienum : cinfo->includedEnums) {
1819 for (auto const& cns : ienum->clsConstants) {
1820 if (!add(cns.second, true)) return false;
1824 auto const addTraitConst = [&] (const php::Const& c) {
1826 * Only copy in constants that win. Otherwise, in the runtime, if
1827 * we have a constant from an interface implemented by a trait
1828 * that wins over this fromTrait constant, we won't know which
1829 * trait it came from, and therefore won't know which constant
1830 * should win. Dropping losing constants here works because if
1831 * they fatal with constants in declared interfaces, we catch that
1832 * above.
1834 auto const& existing = cinfo->clsConstants.find(c.name);
1835 if (existing->second->cls == c.cls) {
1836 cinfo->traitConsts.emplace_back(c);
1837 cinfo->traitConsts.back().isFromTrait = true;
1840 for (auto const t : cinfo->usedTraits) {
1841 for (auto const& c : t->cls->constants) addTraitConst(c);
1842 for (auto const& c : t->traitConsts) addTraitConst(c);
1845 if (!(cinfo->cls->attrs & (AttrAbstract | AttrInterface | AttrTrait))) {
1846 // If we are in a concrete class, concretize the defaults of inherited abstract constants
1847 auto const cls = const_cast<php::Class*>(cinfo->cls);
1848 for (auto t : cinfo->clsConstants) {
1849 auto const& cns = *t.second;
1850 if (cns.isAbstract && cns.val) {
1851 if (cns.val.value().m_type == KindOfUninit) {
1852 // We need to copy the constant's initializer into this class
1853 auto const& cns_86cinit = cns.cls->methods.back().get();
1854 assertx(cns_86cinit->name == s_86cinit.get());
1856 std::unique_ptr<php::Func> empty;
1857 auto& current_86cinit = [&] () -> std::unique_ptr<php::Func>& {
1858 for (auto& m : cls->methods) {
1859 if (m->name == cns_86cinit->name) return m;
1861 return empty;
1862 }();
1864 if (!current_86cinit) {
1865 ClonedClosureMap clonedClosures;
1866 auto& nextFuncId = const_cast<php::Program*>(program)->nextFuncId;
1867 current_86cinit = clone_meth(cls->unit, cls, cns_86cinit, cns_86cinit->name,
1868 cns_86cinit->attrs, nextFuncId, updates, clonedClosures);
1869 assertx(clonedClosures.empty());
1870 DEBUG_ONLY auto res = cinfo->methods.emplace(
1871 current_86cinit->name,
1872 MethTabEntry { current_86cinit.get(), current_86cinit->attrs, false, true }
1874 assertx(res.second);
1875 cls->methods.push_back(std::move(current_86cinit));
1876 } else {
1877 append_86cinit(current_86cinit.get(), *cns_86cinit);
1881 auto concretizedCns = cns;
1882 concretizedCns.cls = cls;
1883 concretizedCns.isAbstract = false;
1885 // this is similar to trait constant flattening
1886 cls->constants.push_back(concretizedCns);
1887 cinfo->clsConstants[concretizedCns.name].cls = cls;
1888 cinfo->clsConstants[concretizedCns.name].idx = cls->constants.size() - 1;
1893 return true;
1896 bool build_class_impl_interfaces(ClassInfo* cinfo) {
1897 if (cinfo->parent) cinfo->implInterfaces = cinfo->parent->implInterfaces;
1899 for (auto const ienum : cinfo->includedEnums) {
1900 cinfo->implInterfaces.insert(
1901 ienum->implInterfaces.begin(),
1902 ienum->implInterfaces.end()
1906 for (auto const iface : cinfo->declInterfaces) {
1907 cinfo->implInterfaces.insert(
1908 iface->implInterfaces.begin(),
1909 iface->implInterfaces.end()
1913 for (auto const trait : cinfo->usedTraits) {
1914 cinfo->implInterfaces.insert(
1915 trait->implInterfaces.begin(),
1916 trait->implInterfaces.end()
1920 if (cinfo->cls->attrs & AttrInterface) {
1921 cinfo->implInterfaces.emplace(cinfo->cls->name, cinfo);
1924 return true;
1927 bool build_class_properties(ClassInfo* cinfo) {
1928 if (cinfo->parent) {
1929 cinfo->preResolveState->pbuildNoTrait =
1930 cinfo->parent->preResolveState->pbuildNoTrait;
1931 cinfo->preResolveState->pbuildTrait =
1932 cinfo->parent->preResolveState->pbuildNoTrait;
1935 auto const add = [&] (auto& m,
1936 SString name,
1937 const php::Prop& p,
1938 const ClassInfo* cls,
1939 bool add) {
1940 auto res = m.emplace(name, std::make_pair(p, cls));
1941 if (res.second) {
1942 if (add) cinfo->traitProps.emplace_back(p);
1943 return true;
1946 auto const& prev = res.first->second.first;
1948 if (cinfo == res.first->second.second) {
1949 if ((prev.attrs ^ p.attrs) &
1950 (AttrStatic | AttrPublic | AttrProtected | AttrPrivate) ||
1951 (!(p.attrs & AttrSystemInitialValue) &&
1952 !(prev.attrs & AttrSystemInitialValue) &&
1953 !Class::compatibleTraitPropInit(prev.val, p.val))) {
1954 ITRACE(2,
1955 "build_class_properties failed for `{}' because "
1956 "two declarations of `{}' at the same level had "
1957 "different attributes\n",
1958 cinfo->cls->name, p.name);
1959 return false;
1961 return true;
1964 if (!(prev.attrs & AttrPrivate)) {
1965 if ((prev.attrs ^ p.attrs) & AttrStatic) {
1966 ITRACE(2,
1967 "build_class_properties failed for `{}' because "
1968 "`{}' was defined both static and non-static\n",
1969 cinfo->cls->name, p.name);
1970 return false;
1972 if (p.attrs & AttrPrivate) {
1973 ITRACE(2,
1974 "build_class_properties failed for `{}' because "
1975 "`{}' was re-declared private\n",
1976 cinfo->cls->name, p.name);
1977 return false;
1979 if (p.attrs & AttrProtected && !(prev.attrs & AttrProtected)) {
1980 ITRACE(2,
1981 "build_class_properties failed for `{}' because "
1982 "`{}' was redeclared protected from public\n",
1983 cinfo->cls->name, p.name);
1984 return false;
1988 if (add) cinfo->traitProps.emplace_back(p);
1989 res.first->second = std::make_pair(p, cls);
1990 return true;
1993 auto const merge = [&] (const ClassInfo::PreResolveState& src) {
1994 for (auto const& p : src.pbuildNoTrait) {
1995 if (!add(cinfo->preResolveState->pbuildNoTrait, p.first,
1996 p.second.first, p.second.second, false)) {
1997 return false;
2000 for (auto const& p : src.pbuildTrait) {
2001 if (!add(cinfo->preResolveState->pbuildTrait, p.first,
2002 p.second.first, p.second.second, false)) {
2003 return false;
2006 return true;
2009 for (auto const iface : cinfo->declInterfaces) {
2010 if (!merge(*iface->preResolveState)) return false;
2013 for (auto const trait : cinfo->usedTraits) {
2014 if (!merge(*trait->preResolveState)) return false;
2017 for (auto const ienum : cinfo->includedEnums) {
2018 if (!merge(*ienum->preResolveState)) return false;
2021 if (!(cinfo->cls->attrs & AttrInterface)) {
2022 for (auto const& p : cinfo->cls->properties) {
2023 if (!add(cinfo->preResolveState->pbuildNoTrait,
2024 p.name, p, cinfo, false)) {
2025 return false;
2029 // There's no need to do this work if traits have been flattened
2030 // already, or if the top level class has no traits. In those
2031 // cases, we might be able to rule out some ClassInfo
2032 // instantiations, but it doesn't seem worth it.
2034 if (!(cinfo->cls->attrs & AttrNoExpandTrait)) {
2035 for (auto const trait : cinfo->usedTraits) {
2036 for (auto const& p : trait->cls->properties) {
2037 if (!add(cinfo->preResolveState->pbuildNoTrait,
2038 p.name, p, cinfo, true)) {
2039 return false;
2042 for (auto const& p : trait->traitProps) {
2043 if (!add(cinfo->preResolveState->pbuildNoTrait,
2044 p.name, p, cinfo, true)) {
2045 return false;
2052 return true;
2056 * Make a flattened table of the methods on this class.
2058 * Duplicate method names override parent methods, unless the parent method
2059 * is final and the class is not a __MockClass, in which case this class
2060 * definitely would fatal if ever defined.
2062 * Note: we're leaving non-overridden privates in their subclass method
2063 * table, here. This isn't currently "wrong", because calling it would be a
2064 * fatal, but note that resolve_method needs to be pretty careful about
2065 * privates and overriding in general.
2067 bool build_class_methods(const IndexData& index,
2068 ClassInfo* cinfo,
2069 ClsPreResolveUpdates& updates) {
2070 if (cinfo->cls->attrs & AttrInterface) return true;
2072 auto const methodOverride = [&] (auto& it,
2073 const php::Func* meth,
2074 Attr attrs,
2075 SString name) {
2076 if (it->second.func->attrs & AttrFinal) {
2077 if (!is_mock_class(cinfo->cls)) {
2078 ITRACE(2,
2079 "build_class_methods failed for `{}' because "
2080 "it tried to override final method `{}::{}'\n",
2081 cinfo->cls->name,
2082 it->second.func->cls->name, name);
2083 return false;
2086 ITRACE(9,
2087 " {}: overriding method {}::{} with {}::{}\n",
2088 cinfo->cls->name,
2089 it->second.func->cls->name, it->second.func->name,
2090 meth->cls->name, name);
2091 if (it->second.func->attrs & AttrPrivate) {
2092 it->second.hasPrivateAncestor = true;
2094 it->second.func = meth;
2095 it->second.attrs = attrs;
2096 it->second.hasAncestor = true;
2097 it->second.topLevel = true;
2098 if (it->first != name) {
2099 auto mte = it->second;
2100 cinfo->methods.erase(it);
2101 it = cinfo->methods.emplace(name, mte).first;
2103 return true;
2106 // If there's a parent, start by copying its methods
2107 if (auto const rparent = cinfo->parent) {
2108 for (auto& mte : rparent->methods) {
2109 // don't inherit the 86* methods.
2110 if (HPHP::Func::isSpecial(mte.first)) continue;
2111 auto const res = cinfo->methods.emplace(mte.first, mte.second);
2112 assertx(res.second);
2113 res.first->second.topLevel = false;
2114 ITRACE(9,
2115 " {}: inheriting method {}::{}\n",
2116 cinfo->cls->name,
2117 rparent->cls->name, mte.first);
2118 continue;
2122 uint32_t idx = cinfo->methods.size();
2124 // Now add our methods.
2125 for (auto& m : cinfo->cls->methods) {
2126 auto res = cinfo->methods.emplace(
2127 m->name,
2128 MethTabEntry { m.get(), m->attrs, false, true }
2130 if (res.second) {
2131 res.first->second.idx = idx++;
2132 ITRACE(9,
2133 " {}: adding method {}::{}\n",
2134 cinfo->cls->name,
2135 cinfo->cls->name, m->name);
2136 continue;
2138 if (m->attrs & AttrTrait && m->attrs & AttrAbstract) {
2139 // abstract methods from traits never override anything.
2140 continue;
2142 if (!methodOverride(res.first, m.get(), m->attrs, m->name)) return false;
2145 // If our traits were previously flattened, we're done.
2146 if (cinfo->cls->attrs & AttrNoExpandTrait) return true;
2148 try {
2149 TMIData tmid;
2150 for (auto const t : cinfo->usedTraits) {
2151 std::vector<const MethTabEntryPair*> methods(t->methods.size());
2152 for (auto& m : t->methods) {
2153 if (HPHP::Func::isSpecial(m.first)) continue;
2154 assertx(!methods[m.second.idx]);
2155 methods[m.second.idx] = mteFromElm(m);
2157 for (auto const m : methods) {
2158 if (!m) continue;
2159 TraitMethod traitMethod { t, m->second.func, m->second.attrs };
2160 tmid.add(traitMethod, m->first);
2162 if (auto const it = index.classClosureMap.find(t->cls);
2163 it != index.classClosureMap.end()) {
2164 for (auto const& c : it->second) {
2165 auto const invoke = find_method(c, s_invoke.get());
2166 assertx(invoke);
2167 updates.extraMethods[cinfo->cls].emplace(invoke);
2172 for (auto const& precRule : cinfo->cls->traitPrecRules) {
2173 tmid.applyPrecRule(precRule, cinfo);
2175 for (auto const& aliasRule : cinfo->cls->traitAliasRules) {
2176 tmid.applyAliasRule(aliasRule, cinfo);
2178 auto traitMethods = tmid.finish(cinfo);
2179 // Import the methods.
2180 for (auto const& mdata : traitMethods) {
2181 auto const method = mdata.tm.method;
2182 auto attrs = mdata.tm.modifiers;
2183 if (attrs == AttrNone) {
2184 attrs = method->attrs;
2185 } else {
2186 Attr attrMask = (Attr)(AttrPublic | AttrProtected | AttrPrivate |
2187 AttrAbstract | AttrFinal);
2188 attrs = (Attr)((attrs & attrMask) |
2189 (method->attrs & ~attrMask));
2191 auto res = cinfo->methods.emplace(
2192 mdata.name,
2193 MethTabEntry { method, attrs, false, true }
2195 if (res.second) {
2196 res.first->second.idx = idx++;
2197 ITRACE(9,
2198 " {}: adding trait method {}::{} as {}\n",
2199 cinfo->cls->name,
2200 method->cls->name, method->name, mdata.name);
2201 } else {
2202 if (attrs & AttrAbstract) continue;
2203 if (res.first->second.func->cls == cinfo->cls) continue;
2204 if (!methodOverride(res.first, method, attrs, mdata.name)) {
2205 return false;
2207 res.first->second.idx = idx++;
2209 updates.extraMethods[cinfo->cls].emplace(method);
2211 } catch (TMIOps::TMIException& ex) {
2212 ITRACE(2,
2213 "build_class_methods failed for `{}' importing traits: {}\n",
2214 cinfo->cls->name, ex.what());
2215 return false;
2218 return true;
2221 const StaticString s___Sealed("__Sealed");
2223 bool enforce_in_maybe_sealed_parent_whitelist(
2224 const ClassInfo* cls,
2225 const ClassInfo* parent) {
2226 // if our parent isn't sealed, then we're fine.
2227 if (!parent || !(parent->cls->attrs & AttrSealed)) return true;
2228 const UserAttributeMap& parent_attrs = parent->cls->userAttributes;
2229 assertx(parent_attrs.find(s___Sealed.get()) != parent_attrs.end());
2230 const auto& parent_sealed_attr = parent_attrs.find(s___Sealed.get())->second;
2231 bool in_sealed_whitelist = false;
2232 IterateV(parent_sealed_attr.m_data.parr,
2233 [&in_sealed_whitelist, cls](TypedValue v) -> bool {
2234 if (v.m_data.pstr->same(cls->cls->name)) {
2235 in_sealed_whitelist = true;
2236 return true;
2238 return false;
2240 return in_sealed_whitelist;
2244 * This function return false if instantiating the cinfo would be a
2245 * fatal at runtime.
2247 bool build_cls_info(const php::Program* program,
2248 const IndexData& index,
2249 ClassInfo* cinfo,
2250 ClsPreResolveUpdates& updates) {
2251 if (!enforce_in_maybe_sealed_parent_whitelist(cinfo, cinfo->parent)) {
2252 return false;
2255 for (auto const iface : cinfo->declInterfaces) {
2256 if (!enforce_in_maybe_sealed_parent_whitelist(cinfo, iface)) {
2257 return false;
2260 for (auto const trait : cinfo->usedTraits) {
2261 if (!enforce_in_maybe_sealed_parent_whitelist(cinfo, trait)) {
2262 return false;
2265 for (auto const ienum : cinfo->includedEnums) {
2266 if (!enforce_in_maybe_sealed_parent_whitelist(cinfo, ienum)) {
2267 return false;
2271 if (!build_class_constants(program, cinfo, updates)) return false;
2272 if (!build_class_impl_interfaces(cinfo)) return false;
2273 if (!build_class_properties(cinfo)) return false;
2274 if (!build_class_methods(index, cinfo, updates)) return false;
2275 return true;
2278 //////////////////////////////////////////////////////////////////////
2280 void add_system_constants_to_index(IndexData& index) {
2281 for (auto cnsPair : Native::getConstants()) {
2282 assertx(cnsPair.second.m_type != KindOfUninit ||
2283 cnsPair.second.dynamic());
2284 auto pc = new php::Constant { nullptr, cnsPair.first, cnsPair.second, AttrUnique | AttrPersistent };
2285 add_symbol(index.constants, pc, "constant");
2289 //////////////////////////////////////////////////////////////////////
2291 Optional<uint32_t> func_num_inout(const php::Func* func) {
2292 if (!func->hasInOutArgs) return 0;
2293 uint32_t count = 0;
2294 for (auto& p : func->params) count += p.inout;
2295 return count;
2298 template<typename PossibleFuncRange>
2299 Optional<uint32_t> num_inout_from_set(PossibleFuncRange range) {
2300 if (begin(range) == end(range)) return 0;
2302 struct FuncFind {
2303 using F = const php::Func*;
2304 static F get(std::pair<SString,F> p) { return p.second; }
2305 static F get(const MethTabEntryPair* mte) { return mte->second.func; }
2308 Optional<uint32_t> num;
2309 for (auto const& item : range) {
2310 auto const n = func_num_inout(FuncFind::get(item));
2311 if (!n.has_value()) return std::nullopt;
2312 if (num.has_value() && n != num) return std::nullopt;
2313 num = n;
2315 return num;
2318 //////////////////////////////////////////////////////////////////////
2320 template<class T>
2321 struct PhpTypeHelper;
2323 template<>
2324 struct PhpTypeHelper<php::Class> {
2325 template<class Fn>
2326 static void process_bases(const php::Class* cls, Fn&& fn) {
2327 if (cls->parentName) fn(cls->parentName);
2328 for (auto& i : cls->interfaceNames) fn(i);
2329 for (auto& t : cls->usedTraitNames) fn(t);
2330 for (auto& t : cls->includedEnumNames) fn(t);
2333 static std::string name() { return "class"; }
2335 static void assert_bases(const IndexData&, const php::Class* cls);
2336 static void try_flatten_traits(const php::Program*, const IndexData&,
2337 const php::Class*, ClassInfo*,
2338 ClsPreResolveUpdates&);
2340 using Info = ClassInfo;
2343 template<>
2344 struct PhpTypeHelper<php::Record> {
2345 template<class Fn>
2346 static void process_bases(const php::Record* rec, Fn&& fn) {
2347 if (rec->parentName) fn(rec->parentName);
2350 static std::string name() { return "record"; }
2352 static void assert_bases(const IndexData&, const php::Record* rec);
2353 static void try_flatten_traits(const php::Program*, const IndexData&,
2354 const php::Record*, RecordInfo*,
2355 RecPreResolveUpdates&);
2357 using Info = RecordInfo;
2360 template<typename T>
2361 struct TypeInfoData {
2362 // Map from name to types that directly use that name (as parent,
2363 // interface or trait).
2364 hphp_hash_map<SString,
2365 CompactVector<const T*>,
2366 string_data_hash,
2367 string_data_isame> users;
2368 // Map from types to number of dependencies, used in
2369 // conjunction with users field above.
2370 hphp_hash_map<const T*, uint32_t> depCounts;
2372 uint32_t cqFront{};
2373 uint32_t cqBack{};
2374 std::vector<const T*> queue;
2375 bool hasPseudoCycles{};
2378 using ClassInfoData = TypeInfoData<php::Class>;
2379 using RecordInfoData = TypeInfoData<php::Record>;
2381 // We want const qualifiers on various index data structures for php
2382 // object pointers, but during index creation time we need to
2383 // manipulate some of their attributes (changing the representation).
2384 // This little wrapper keeps the const_casting out of the main line of
2385 // code below.
2386 void attribute_setter(const Attr& attrs, bool set, Attr attr) {
2387 attrSetter(const_cast<Attr&>(attrs), set, attr);
2390 void add_unit_to_index(IndexData& index, php::Unit& unit) {
2391 hphp_hash_map<
2392 const php::Class*,
2393 hphp_hash_set<const php::Class*>
2394 > closureMap;
2396 for (auto& c : unit.classes) {
2397 assertx(!(c->attrs & AttrNoOverride));
2399 if (c->attrs & AttrEnum) {
2400 add_symbol(index.enums, c.get(), "enum");
2403 add_symbol(index.classes, c.get(), "class", index.records, index.typeAliases);
2405 for (auto& m : c->methods) {
2406 attribute_setter(m->attrs, false, AttrNoOverride);
2407 index.methods.insert({m->name, m.get()});
2409 uint64_t refs = 0, cur = 1;
2410 bool anyInOut = false;
2411 for (auto& p : m->params) {
2412 if (p.inout) {
2413 refs |= cur;
2414 anyInOut = true;
2416 // It doesn't matter that we lose parameters beyond the 64th,
2417 // for those, we'll conservatively check everything anyway.
2418 cur <<= 1;
2420 if (anyInOut) {
2421 // Multiple methods with the same name will be combined in the same
2422 // cell, thus we use |=. This only makes sense in WholeProgram mode
2423 // since we use this field to check that no functions has its n-th
2424 // parameter as inout, which requires global knowledge.
2425 index.method_inout_params_by_name[m->name] |= refs;
2429 if (c->closureContextCls) {
2430 closureMap[c->closureContextCls].insert(c.get());
2434 if (!closureMap.empty()) {
2435 for (auto const& c1 : closureMap) {
2436 auto& s = index.classClosureMap[c1.first];
2437 for (auto const& c2 : c1.second) {
2438 s.push_back(c2);
2443 for (auto i = unit.funcs.begin(); i != unit.funcs.end();) {
2444 auto& f = *i;
2445 // Deduplicate meth_caller wrappers- We just take the first one we see.
2446 if (f->attrs & AttrIsMethCaller && index.funcs.count(f->name)) {
2447 unit.funcs.erase(i);
2448 continue;
2450 add_symbol(index.funcs, f.get(), "function");
2451 ++i;
2454 for (auto& ta : unit.typeAliases) {
2455 add_symbol(index.typeAliases, ta.get(), "type alias", index.classes, index.records);
2458 for (auto& c : unit.constants) {
2459 add_symbol(index.constants, c.get(), "constant");
2462 for (auto& rec : unit.records) {
2463 assertx(!(rec->attrs & AttrNoOverride));
2464 add_symbol(index.records, rec.get(), "record", index.classes, index.typeAliases);
2468 template<class T>
2469 using TypeInfo = typename std::conditional<std::is_same<T, php::Class>::value,
2470 ClassInfo, RecordInfo>::type;
2473 void PhpTypeHelper<php::Class>::assert_bases(const IndexData& index,
2474 const php::Class* cls) {
2475 if (cls->parentName) {
2476 assertx(index.classInfo.count(cls->parentName));
2478 for (DEBUG_ONLY auto& i : cls->interfaceNames) {
2479 assertx(index.classInfo.count(i));
2481 for (DEBUG_ONLY auto& t : cls->usedTraitNames) {
2482 assertx(index.classInfo.count(t));
2486 void PhpTypeHelper<php::Record>::assert_bases(const IndexData& index,
2487 const php::Record* rec) {
2488 if (rec->parentName) {
2489 assertx(index.recordInfo.count(rec->parentName));
2493 std::unique_ptr<php::Func> clone_meth_helper(
2494 php::Unit* unit,
2495 php::Class* newContext,
2496 const php::Func* origMeth,
2497 std::unique_ptr<php::Func> cloneMeth,
2498 std::atomic<uint32_t>& nextFuncId,
2499 ClsPreResolveUpdates& updates,
2500 ClonedClosureMap& clonedClosures
2503 std::unique_ptr<php::Class> clone_closure(php::Unit* unit,
2504 php::Class* newContext,
2505 php::Class* cls,
2506 std::atomic<uint32_t>& nextFuncId,
2507 ClsPreResolveUpdates& updates,
2508 ClonedClosureMap& clonedClosures) {
2509 auto clone = std::make_unique<php::Class>(*cls);
2510 assertx(clone->closureContextCls);
2511 clone->closureContextCls = newContext;
2512 clone->unit = newContext->unit;
2513 auto i = 0;
2514 for (auto& cloneMeth : clone->methods) {
2515 cloneMeth = clone_meth_helper(unit,
2516 clone.get(),
2517 cls->methods[i++].get(),
2518 std::move(cloneMeth),
2519 nextFuncId,
2520 updates,
2521 clonedClosures);
2522 if (!cloneMeth) return nullptr;
2524 return clone;
2527 std::unique_ptr<php::Func> clone_meth_helper(
2528 php::Unit* unit,
2529 php::Class* newContext,
2530 const php::Func* origMeth,
2531 std::unique_ptr<php::Func> cloneMeth,
2532 std::atomic<uint32_t>& nextFuncId,
2533 ClsPreResolveUpdates& preResolveUpdates,
2534 ClonedClosureMap& clonedClosures) {
2536 cloneMeth->cls = newContext;
2537 cloneMeth->idx = nextFuncId.fetch_add(1, std::memory_order_relaxed);
2538 if (!cloneMeth->originalFilename) {
2539 cloneMeth->originalFilename = origMeth->unit->filename;
2541 if (!cloneMeth->originalUnit) {
2542 cloneMeth->originalUnit = origMeth->unit;
2544 cloneMeth->unit = newContext->unit;
2546 if (!origMeth->hasCreateCl) return cloneMeth;
2548 auto const recordClosure = [&] (uint32_t& clsId) {
2549 auto const cls = origMeth->unit->classes[clsId].get();
2551 auto it = clonedClosures.find(cls);
2552 if (it == clonedClosures.end()) {
2553 auto cloned = clone_closure(
2554 unit,
2555 newContext->closureContextCls ?
2556 newContext->closureContextCls : newContext,
2557 cls,
2558 nextFuncId,
2559 preResolveUpdates,
2560 clonedClosures
2562 if (!cloned) return false;
2563 clsId = preResolveUpdates.nextClassId++;
2564 always_assert(clonedClosures.emplace(cls, std::move(cloned), clsId));
2565 } else {
2566 clsId = std::get<2>(*it);
2568 return true;
2571 auto mf = php::WideFunc::mut(cloneMeth.get());
2572 hphp_fast_map<size_t, hphp_fast_map<size_t, uint32_t>> updates;
2574 for (size_t bid = 0; bid < mf.blocks().size(); bid++) {
2575 auto const b = mf.blocks()[bid].get();
2576 for (size_t ix = 0; ix < b->hhbcs.size(); ix++) {
2577 auto const& bc = b->hhbcs[ix];
2578 switch (bc.op) {
2579 case Op::CreateCl: {
2580 auto clsId = bc.CreateCl.arg2;
2581 if (!recordClosure(clsId)) return nullptr;
2582 updates[bid][ix] = clsId;
2583 break;
2585 default:
2586 break;
2591 for (auto const& elm : updates) {
2592 auto const blk = mf.blocks()[elm.first].mutate();
2593 for (auto const& ix : elm.second) {
2594 blk->hhbcs[ix.first].CreateCl.arg2 = ix.second;
2598 return cloneMeth;
2601 std::unique_ptr<php::Func> clone_meth(php::Unit* unit,
2602 php::Class* newContext,
2603 const php::Func* origMeth,
2604 SString name,
2605 Attr attrs,
2606 std::atomic<uint32_t>& nextFuncId,
2607 ClsPreResolveUpdates& updates,
2608 ClonedClosureMap& clonedClosures) {
2610 auto cloneMeth = std::make_unique<php::Func>(*origMeth);
2611 cloneMeth->name = name;
2612 cloneMeth->attrs = attrs | AttrTrait;
2613 return clone_meth_helper(unit, newContext, origMeth, std::move(cloneMeth),
2614 nextFuncId, updates, clonedClosures);
2617 bool merge_inits(std::vector<std::unique_ptr<php::Func>>& clones,
2618 php::Unit* unit,
2619 ClassInfo* cinfo,
2620 std::atomic<uint32_t>& nextFuncId,
2621 ClsPreResolveUpdates& updates,
2622 ClonedClosureMap& clonedClosures,
2623 SString xinitName) {
2624 auto const cls = const_cast<php::Class*>(cinfo->cls);
2625 std::unique_ptr<php::Func> empty;
2626 auto& xinit = [&] () -> std::unique_ptr<php::Func>& {
2627 for (auto& m : cls->methods) {
2628 if (m->name == xinitName) return m;
2630 return empty;
2631 }();
2633 auto merge_one = [&] (const php::Func* func) {
2634 if (!xinit) {
2635 ITRACE(5, " - cloning {}::{} as {}::{}\n",
2636 func->cls->name, func->name, cls->name, xinitName);
2637 xinit = clone_meth(unit, cls, func, func->name, func->attrs, nextFuncId,
2638 updates, clonedClosures);
2639 return xinit != nullptr;
2642 ITRACE(5, " - appending {}::{} into {}::{}\n",
2643 func->cls->name, func->name, cls->name, xinitName);
2644 if (xinitName == s_86cinit.get()) {
2645 return append_86cinit(xinit.get(), *func);
2646 } else {
2647 return append_func(xinit.get(), *func);
2651 for (auto t : cinfo->usedTraits) {
2652 auto it = t->methods.find(xinitName);
2653 if (it != t->methods.end()) {
2654 if (!merge_one(it->second.func)) {
2655 ITRACE(5, "merge_xinits: failed to merge {}::{}\n",
2656 it->second.func->cls->name, it->second.func->name);
2657 return false;
2662 assertx(xinit);
2663 if (empty) {
2664 ITRACE(5, "merge_xinits: adding {}::{} to method table\n",
2665 xinit->cls->name, xinit->name);
2666 assertx(&empty == &xinit);
2667 clones.push_back(std::move(xinit));
2670 return true;
2673 bool merge_xinits(Attr attr,
2674 std::vector<std::unique_ptr<php::Func>>& clones,
2675 php::Unit* unit,
2676 ClassInfo* cinfo,
2677 std::atomic<uint32_t>& nextFuncId,
2678 ClsPreResolveUpdates& updates,
2679 ClonedClosureMap& clonedClosures) {
2680 auto const xinitName = [&]() {
2681 switch (attr) {
2682 case AttrNone : return s_86pinit.get();
2683 case AttrStatic: return s_86sinit.get();
2684 case AttrLSB : return s_86linit.get();
2685 default: always_assert(false);
2687 }();
2689 auto const xinitMatch = [&](Attr prop_attrs) {
2690 auto mask = AttrStatic | AttrLSB;
2691 switch (attr) {
2692 case AttrNone: return (prop_attrs & mask) == AttrNone;
2693 case AttrStatic: return (prop_attrs & mask) == AttrStatic;
2694 case AttrLSB: return (prop_attrs & mask) == mask;
2695 default: always_assert(false);
2699 for (auto const& p : cinfo->traitProps) {
2700 if (xinitMatch(p.attrs) &&
2701 p.val.m_type == KindOfUninit &&
2702 !(p.attrs & AttrLateInit)) {
2703 ITRACE(5, "merge_xinits: {}: Needs merge for {}{}prop `{}'\n",
2704 cinfo->cls->name, attr & AttrStatic ? "static " : "",
2705 attr & AttrLSB ? "lsb " : "", p.name);
2706 return merge_inits(clones, unit, cinfo, nextFuncId,
2707 updates, clonedClosures, xinitName);
2710 return true;
2713 bool merge_cinits(std::vector<std::unique_ptr<php::Func>>& clones,
2714 php::Unit* unit,
2715 ClassInfo* cinfo,
2716 std::atomic<uint32_t>& nextFuncId,
2717 ClsPreResolveUpdates& updates,
2718 ClonedClosureMap& clonedClosures) {
2719 auto const xinitName = s_86cinit.get();
2720 for (auto const& c : cinfo->traitConsts) {
2721 if (c.val && c.val->m_type == KindOfUninit) {
2722 return merge_inits(clones, unit, cinfo, nextFuncId,
2723 updates, clonedClosures, xinitName);
2726 return true;
2729 void rename_closure(const IndexData& index,
2730 php::Class* cls,
2731 ClsPreResolveUpdates& updates) {
2732 auto n = cls->name->slice();
2733 auto const p = n.find(';');
2734 if (p != std::string::npos) {
2735 n = n.subpiece(0, p);
2737 auto const newName = makeStaticString(NewAnonymousClassName(n));
2738 assertx(!index.classes.count(newName));
2739 cls->name = newName;
2740 updates.newClosures.emplace_back(cls);
2743 template <typename T>
2744 void preresolve(const php::Program*,
2745 const IndexData&,
2746 const T*,
2747 PreResolveUpdates<typename PhpTypeHelper<T>::Info>&);
2749 void flatten_traits(const php::Program* program,
2750 const IndexData& index,
2751 ClassInfo* cinfo,
2752 ClsPreResolveUpdates& updates) {
2753 bool hasConstProp = false;
2754 for (auto const t : cinfo->usedTraits) {
2755 if (t->usedTraits.size() && !(t->cls->attrs & AttrNoExpandTrait)) {
2756 ITRACE(5, "Not flattening {} because of {}\n",
2757 cinfo->cls->name, t->cls->name);
2758 return;
2760 if (is_noflatten_trait(t->cls)) {
2761 ITRACE(5, "Not flattening {} because {} is annotated with __NoFlatten\n",
2762 cinfo->cls->name, t->cls->name);
2763 return;
2765 if (t->cls->hasConstProp) hasConstProp = true;
2767 auto const cls = const_cast<php::Class*>(cinfo->cls);
2768 if (hasConstProp) cls->hasConstProp = true;
2769 std::vector<MethTabEntryPair*> methodsToAdd;
2770 for (auto& ent : cinfo->methods) {
2771 if (!ent.second.topLevel || ent.second.func->cls == cinfo->cls) {
2772 continue;
2774 always_assert(ent.second.func->cls->attrs & AttrTrait);
2775 methodsToAdd.push_back(mteFromElm(ent));
2778 auto const it = updates.extraMethods.find(cinfo->cls);
2780 if (!methodsToAdd.empty()) {
2781 assertx(it != updates.extraMethods.end());
2782 std::sort(begin(methodsToAdd), end(methodsToAdd),
2783 [] (const MethTabEntryPair* a, const MethTabEntryPair* b) {
2784 return a->second.idx < b->second.idx;
2786 } else if (debug && it != updates.extraMethods.end()) {
2787 // When building the ClassInfos, we proactively added all closures
2788 // from usedTraits to classExtraMethodMap; but now we're going to
2789 // start from the used methods, and deduce which closures actually
2790 // get pulled in. Its possible *none* of the methods got used, in
2791 // which case, we won't need their closures either. To be safe,
2792 // verify that the only things in classExtraMethodMap are
2793 // closures.
2794 for (DEBUG_ONLY auto const f : it->second) {
2795 assertx(f->isClosureBody);
2799 std::vector<std::unique_ptr<php::Func>> clones;
2800 ClonedClosureMap clonedClosures;
2801 auto& nextFuncId = const_cast<php::Program*>(program)->nextFuncId;
2803 for (auto const ent : methodsToAdd) {
2804 auto clone = clone_meth(cls->unit, cls, ent->second.func, ent->first,
2805 ent->second.attrs, nextFuncId,
2806 updates, clonedClosures);
2807 if (!clone) {
2808 ITRACE(5, "Not flattening {} because {}::{} could not be cloned\n",
2809 cls->name, ent->second.func->cls->name, ent->first);
2810 return;
2813 clone->attrs |= AttrTrait;
2814 ent->second.attrs |= AttrTrait;
2815 ent->second.func = clone.get();
2816 clones.push_back(std::move(clone));
2819 if (cinfo->traitProps.size()) {
2820 if (!merge_xinits(AttrNone, clones, cls->unit, cinfo,
2821 nextFuncId, updates, clonedClosures) ||
2822 !merge_xinits(AttrStatic, clones, cls->unit, cinfo,
2823 nextFuncId, updates, clonedClosures) ||
2824 !merge_xinits(AttrLSB, clones, cls->unit, cinfo,
2825 nextFuncId, updates, clonedClosures)) {
2826 ITRACE(5, "Not flattening {} because we couldn't merge the 86xinits\n",
2827 cls->name);
2828 return;
2832 // flatten initializers for constants in traits
2833 if (cinfo->traitConsts.size()) {
2834 if (!merge_cinits(clones, cls->unit, cinfo, nextFuncId, updates,
2835 clonedClosures)) {
2836 ITRACE(5, "Not flattening {} because we couldn't merge the 86cinits\n",
2837 cls->name);
2838 return;
2842 // We're now committed to flattening.
2843 ITRACE(3, "Flattening {}\n", cls->name);
2844 if (it != updates.extraMethods.end()) it->second.clear();
2845 for (auto const& p : cinfo->traitProps) {
2846 ITRACE(5, " - prop {}\n", p.name);
2847 cls->properties.push_back(p);
2848 cls->properties.back().attrs |= AttrTrait;
2850 cinfo->traitProps.clear();
2852 for (auto const& c : cinfo->traitConsts) {
2853 ITRACE(5, " - const {}\n", c.name);
2854 cls->constants.push_back(c);
2855 cinfo->clsConstants[c.name].cls = cls;
2856 cinfo->clsConstants[c.name].idx = cls->constants.size()-1;
2857 cinfo->preResolveState->constsFromTraits.erase(c.name);
2859 cinfo->traitConsts.clear();
2861 if (clones.size()) {
2862 auto cinit = cls->methods.size() &&
2863 cls->methods.back()->name == s_86cinit.get() ?
2864 std::move(cls->methods.back()) : nullptr;
2865 if (cinit) cls->methods.pop_back();
2866 for (auto& clone : clones) {
2867 if (is_special_method_name(clone->name)) {
2868 DEBUG_ONLY auto res = cinfo->methods.emplace(
2869 clone->name,
2870 MethTabEntry { clone.get(), clone->attrs, false, true }
2872 assertx(res.second);
2874 ITRACE(5, " - meth {}\n", clone->name);
2875 cinfo->methods.find(clone->name)->second.func = clone.get();
2876 if (clone->name == s_86cinit.get()) {
2877 cinit = std::move(clone);
2878 continue;
2880 cls->methods.push_back(std::move(clone));
2882 if (cinit) cls->methods.push_back(std::move(cinit));
2884 if (!clonedClosures.empty()) {
2885 auto& closures = updates.closures[cls];
2886 for (auto& [orig, clo, idx] : clonedClosures) {
2887 rename_closure(index, clo.get(), updates);
2888 ITRACE(5, " - closure {} as {}\n", orig->name, clo->name);
2889 assertx(clo->closureContextCls == cls);
2890 assertx(clo->unit == cls->unit);
2891 closures.emplace_back(clo.get());
2892 updates.newClasses.emplace_back(
2893 std::move(clo),
2894 cls->unit,
2897 preresolve(program, index, closures.back(), updates);
2902 struct EqHash {
2903 bool operator()(const PreClass::ClassRequirement& a,
2904 const PreClass::ClassRequirement& b) const {
2905 return a.is_same(&b);
2907 size_t operator()(const PreClass::ClassRequirement& a) const {
2908 return a.hash();
2912 hphp_hash_set<PreClass::ClassRequirement, EqHash, EqHash> reqs;
2914 for (auto const t : cinfo->usedTraits) {
2915 for (auto const& req : t->cls->requirements) {
2916 if (reqs.empty()) {
2917 for (auto const& r : cls->requirements) {
2918 reqs.insert(r);
2921 if (reqs.insert(req).second) cls->requirements.push_back(req);
2925 cls->attrs |= AttrNoExpandTrait;
2929 * Given a static representation of a Hack record, find a possible resolution
2930 * of the record along with all records in its hierarchy.
2932 RecordInfo* resolve_combinations(const php::Program* program,
2933 const IndexData& index,
2934 const php::Record* rec,
2935 RecPreResolveUpdates& updates) {
2936 auto rinfo = std::make_unique<RecordInfo>();
2937 rinfo->rec = rec;
2938 if (rec->parentName) {
2939 auto const parent = index.recordInfo.at(rec->parentName);
2940 if (parent->rec->attrs & AttrFinal) {
2941 ITRACE(2,
2942 "Resolve combinations failed for `{}' because "
2943 "its parent record `{}' is not abstract\n",
2944 rec->name, parent->rec->name);
2945 return nullptr;
2947 rinfo->parent = parent;
2948 rinfo->baseList = rinfo->parent->baseList;
2950 rinfo->baseList.push_back(rinfo.get());
2951 rinfo->baseList.shrink_to_fit();
2952 ITRACE(2, " resolved: {}\n", rec->name);
2953 updates.newInfos.emplace_back(std::move(rinfo));
2954 return updates.newInfos.back().get();
2958 * Given a static representation of a Hack class, find a possible resolution
2959 * of the class along with all classes, interfaces and traits in its hierarchy.
2961 * Returns the resultant ClassInfo, or nullptr if the Hack class
2962 * cannot be instantiated at runtime.
2964 ClassInfo* resolve_combinations(const php::Program* program,
2965 const IndexData& index,
2966 const php::Class* cls,
2967 ClsPreResolveUpdates& updates) {
2968 auto cinfo = std::make_unique<ClassInfo>();
2969 cinfo->cls = cls;
2970 auto const& map = index.classInfo;
2971 if (cls->parentName) {
2972 cinfo->parent = map.at(cls->parentName);
2973 cinfo->baseList = cinfo->parent->baseList;
2974 if (cinfo->parent->cls->attrs & (AttrInterface | AttrTrait)) {
2975 ITRACE(2,
2976 "Resolve combinations failed for `{}' because "
2977 "its parent `{}' is not a class\n",
2978 cls->name, cls->parentName);
2979 return nullptr;
2982 cinfo->baseList.push_back(cinfo.get());
2984 for (auto& iname : cls->interfaceNames) {
2985 auto const iface = map.at(iname);
2986 if (!(iface->cls->attrs & AttrInterface)) {
2987 ITRACE(2,
2988 "Resolve combinations failed for `{}' because `{}' "
2989 "is not an interface\n",
2990 cls->name, iname);
2991 return nullptr;
2993 cinfo->declInterfaces.push_back(iface);
2996 for (auto& included_enum_name : cls->includedEnumNames) {
2997 auto const included_enum = map.at(included_enum_name);
2998 auto const want_attr = cls->attrs & (AttrEnum | AttrEnumClass);
2999 if (!(included_enum->cls->attrs & want_attr)) {
3000 ITRACE(2,
3001 "Resolve combinations failed for `{}' because `{}' "
3002 "is not an enum{}\n",
3003 cls->name, included_enum_name,
3004 want_attr & AttrEnumClass ? " class" : "");
3005 return nullptr;
3007 cinfo->includedEnums.push_back(included_enum);
3010 for (auto& tname : cls->usedTraitNames) {
3011 auto const trait = map.at(tname);
3012 if (!(trait->cls->attrs & AttrTrait)) {
3013 ITRACE(2,
3014 "Resolve combinations failed for `{}' because `{}' "
3015 "is not a trait\n",
3016 cls->name, tname);
3017 return nullptr;
3019 cinfo->usedTraits.push_back(trait);
3022 cinfo->preResolveState = std::make_unique<ClassInfo::PreResolveState>();
3023 if (!build_cls_info(program, index, cinfo.get(), updates)) return nullptr;
3025 ITRACE(2, " resolved: {}\n", cls->name);
3026 if (Trace::moduleEnabled(Trace::hhbbc_index, 3)) {
3027 for (auto const DEBUG_ONLY& iface : cinfo->implInterfaces) {
3028 ITRACE(3, " implements: {}\n", iface.second->cls->name);
3030 for (auto const DEBUG_ONLY& trait : cinfo->usedTraits) {
3031 ITRACE(3, " uses: {}\n", trait->cls->name);
3034 cinfo->baseList.shrink_to_fit();
3035 updates.newInfos.emplace_back(std::move(cinfo));
3036 return updates.newInfos.back().get();
3039 void PhpTypeHelper<php::Record>::try_flatten_traits(const php::Program*,
3040 const IndexData&,
3041 const php::Record*,
3042 RecordInfo*,
3043 RecPreResolveUpdates&) {}
3045 void PhpTypeHelper<php::Class>::try_flatten_traits(
3046 const php::Program* program,
3047 const IndexData& index,
3048 const php::Class* cls,
3049 ClassInfo* cinfo,
3050 ClsPreResolveUpdates& updates) {
3051 if (options.FlattenTraits &&
3052 !(cls->attrs & AttrNoExpandTrait) &&
3053 !cls->usedTraitNames.empty() &&
3054 index.classes.count(cls->name) == 1) {
3055 Trace::Indent indent;
3056 flatten_traits(program, index, cinfo, updates);
3060 template <typename T>
3061 void preresolve(const php::Program* program,
3062 const IndexData& index,
3063 const T* type,
3064 PreResolveUpdates<typename PhpTypeHelper<T>::Info>& updates) {
3065 ITRACE(2, "preresolve {}: {}:{}\n",
3066 PhpTypeHelper<T>::name(), type->name, (void*)type);
3068 auto const resolved = [&] {
3069 Trace::Indent indent;
3070 if (debug) {
3071 PhpTypeHelper<T>::assert_bases(index, type);
3073 return resolve_combinations(program, index, type, updates);
3074 }();
3076 ITRACE(3, "preresolve: {}:{} ({} resolutions)\n",
3077 type->name, (void*)type, resolved ? 1 : 0);
3079 if (resolved) {
3080 updates.updateDeps.emplace_back(resolved);
3081 PhpTypeHelper<T>::try_flatten_traits(
3082 program, index, type, resolved, updates
3087 void compute_subclass_list_rec(IndexData& index,
3088 ClassInfo* cinfo,
3089 ClassInfo* csub) {
3090 for (auto const ctrait : csub->usedTraits) {
3091 auto const ct = const_cast<ClassInfo*>(ctrait);
3092 ct->subclassList.push_back(cinfo);
3093 compute_subclass_list_rec(index, cinfo, ct);
3097 void compute_included_enums_list_rec(IndexData& index,
3098 ClassInfo* cinfo,
3099 ClassInfo* csub) {
3100 for (auto const cincluded_enum : csub->includedEnums) {
3101 auto const cie = const_cast<ClassInfo*>(cincluded_enum);
3102 cie->subclassList.push_back(cinfo);
3103 compute_included_enums_list_rec(index, cinfo, cie);
3107 void compute_subclass_list(IndexData& index) {
3108 trace_time _("compute subclass list");
3109 auto fixupTraits = false;
3110 auto fixupEnums = false;
3111 auto const AnyEnum = AttrEnum | AttrEnumClass;
3112 for (auto& cinfo : index.allClassInfos) {
3113 if (cinfo->cls->attrs & AttrInterface) continue;
3114 for (auto& cparent : cinfo->baseList) {
3115 cparent->subclassList.push_back(cinfo.get());
3117 if (!(cinfo->cls->attrs & AttrNoExpandTrait) &&
3118 cinfo->usedTraits.size()) {
3119 fixupTraits = true;
3120 compute_subclass_list_rec(index, cinfo.get(), cinfo.get());
3122 // Add the included enum lists if cinfo is an enum
3123 if ((cinfo->cls->attrs & AnyEnum) &&
3124 cinfo->cls->includedEnumNames.size()) {
3125 fixupEnums = true;
3126 compute_included_enums_list_rec(index, cinfo.get(), cinfo.get());
3128 // Also add instantiable classes to their interface's subclassLists
3129 if (cinfo->cls->attrs & (AttrTrait | AnyEnum | AttrAbstract)) continue;
3130 for (auto& ipair : cinfo->implInterfaces) {
3131 auto impl = const_cast<ClassInfo*>(ipair.second);
3132 impl->subclassList.push_back(cinfo.get());
3136 for (auto& cinfo : index.allClassInfos) {
3137 auto& sub = cinfo->subclassList;
3138 if ((fixupTraits && cinfo->cls->attrs & AttrTrait) ||
3139 (fixupEnums && cinfo->cls->attrs & AnyEnum)) {
3140 // traits and enums can be reached by multiple paths, so we need to
3141 // uniquify their subclassLists.
3142 std::sort(begin(sub), end(sub));
3143 sub.erase(
3144 std::unique(begin(sub), end(sub)),
3145 end(sub)
3148 sub.shrink_to_fit();
3152 bool define_func_family(IndexData& index, ClassInfo* cinfo,
3153 SString name, const php::Func* func = nullptr) {
3154 FuncFamily::PFuncVec funcs{};
3155 for (auto const cleaf : cinfo->subclassList) {
3156 auto const leafFn = [&] () -> const MethTabEntryPair* {
3157 auto const leafFnIt = cleaf->methods.find(name);
3158 if (leafFnIt == end(cleaf->methods)) return nullptr;
3159 return mteFromIt(leafFnIt);
3160 }();
3161 if (!leafFn) continue;
3162 funcs.push_back(leafFn);
3165 if (funcs.empty()) return false;
3167 std::sort(
3168 begin(funcs), end(funcs),
3169 [&] (const MethTabEntryPair* a, const MethTabEntryPair* b) {
3170 // We want a canonical order for the family. Putting the
3171 // one corresponding to cinfo first makes sense, because
3172 // the first one is used as the name for FCall*Method* hint,
3173 // after that, sort by name so that different case spellings
3174 // come in the same order.
3175 if (a->second.func == b->second.func) return false;
3176 if (func) {
3177 if (b->second.func == func) return false;
3178 if (a->second.func == func) return true;
3180 if (auto d = a->first->compare(b->first)) {
3181 if (!func) {
3182 if (b->first == name) return false;
3183 if (a->first == name) return true;
3185 return d < 0;
3187 return std::less<const void*>{}(a->second.func, b->second.func);
3190 funcs.erase(
3191 std::unique(
3192 begin(funcs), end(funcs),
3193 [] (const MethTabEntryPair* a, const MethTabEntryPair* b) {
3194 return a->second.func == b->second.func;
3197 end(funcs)
3200 funcs.shrink_to_fit();
3202 if (Trace::moduleEnabled(Trace::hhbbc_index, 4)) {
3203 FTRACE(4, "define_func_family: {}::{}:\n",
3204 cinfo->cls->name, name);
3205 for (auto const DEBUG_ONLY func : funcs) {
3206 FTRACE(4, " {}::{}\n",
3207 func->second.func->cls->name, func->second.func->name);
3211 // Single func resolutions are stored separately. They don't need a
3212 // FuncFamily and this saves space.
3213 if (funcs.size() == 1) {
3214 cinfo->singleMethodFamilies.emplace(name, funcs[0]);
3215 return true;
3218 // Otherwise re-use an existing identical FuncFamily, or create a
3219 // new one.
3220 auto const ff = [&] {
3221 auto it = index.funcFamilies.find(funcs);
3222 if (it != index.funcFamilies.end()) return it->first.get();
3223 return index.funcFamilies.insert(
3224 std::make_unique<FuncFamily>(std::move(funcs)),
3225 false
3226 ).first->first.get();
3227 }();
3229 cinfo->methodFamilies.emplace(
3230 std::piecewise_construct,
3231 std::forward_as_tuple(name),
3232 std::forward_as_tuple(ff)
3235 return true;
3238 void build_abstract_func_families(IndexData& data, ClassInfo* cinfo) {
3239 std::vector<SString> extras;
3241 // We start by collecting the list of methods shared across all
3242 // subclasses of cinfo (including indirectly). And then add the
3243 // public methods which are not constructors and have no private
3244 // ancestors to the method families of cinfo. Note that this set
3245 // may be larger than the methods declared on cinfo and may also
3246 // be missing methods declared on cinfo. In practice this is the
3247 // set of methods we can depend on having accessible given any
3248 // object which is known to implement cinfo.
3249 auto it = cinfo->subclassList.begin();
3250 while (true) {
3251 if (it == cinfo->subclassList.end()) return;
3252 auto const sub = *it++;
3253 assertx(!(sub->cls->attrs & AttrInterface));
3254 if (sub == cinfo || (sub->cls->attrs & AttrAbstract)) continue;
3255 for (auto& par : sub->methods) {
3256 if (!par.second.hasPrivateAncestor &&
3257 (par.second.attrs & AttrPublic) &&
3258 !cinfo->methodFamilies.count(par.first) &&
3259 !cinfo->singleMethodFamilies.count(par.first) &&
3260 !cinfo->methods.count(par.first)) {
3261 extras.push_back(par.first);
3264 if (!extras.size()) return;
3265 break;
3268 auto end = extras.end();
3269 while (it != cinfo->subclassList.end()) {
3270 auto const sub = *it++;
3271 assertx(!(sub->cls->attrs & AttrInterface));
3272 if (sub == cinfo || (sub->cls->attrs & AttrAbstract)) continue;
3273 for (auto nameIt = extras.begin(); nameIt != end;) {
3274 auto const meth = sub->methods.find(*nameIt);
3275 if (meth == sub->methods.end() ||
3276 !(meth->second.attrs & AttrPublic) ||
3277 meth->second.hasPrivateAncestor) {
3278 *nameIt = *--end;
3279 if (end == extras.begin()) return;
3280 } else {
3281 ++nameIt;
3285 extras.erase(end, extras.end());
3287 if (Trace::moduleEnabled(Trace::hhbbc_index, 5)) {
3288 FTRACE(5, "Adding extra methods to {}:\n", cinfo->cls->name);
3289 for (auto const DEBUG_ONLY extra : extras) {
3290 FTRACE(5, " {}\n", extra);
3294 hphp_fast_set<SString> added;
3296 for (auto name : extras) {
3297 if (define_func_family(data, cinfo, name) &&
3298 (cinfo->cls->attrs & AttrInterface)) {
3299 added.emplace(name);
3303 if (cinfo->cls->attrs & AttrInterface) {
3304 for (auto& m : cinfo->cls->methods) {
3305 if (added.count(m->name)) {
3306 cinfo->methods.emplace(
3307 m->name,
3308 MethTabEntry { m.get(), m->attrs, false, true }
3313 return;
3316 void define_func_families(IndexData& index) {
3317 trace_time tracer("define_func_families");
3319 parallel::for_each(
3320 index.allClassInfos,
3321 [&] (const std::unique_ptr<ClassInfo>& cinfo) {
3322 if (cinfo->cls->attrs & AttrTrait) return;
3323 FTRACE(4, "Defining func families for {}\n", cinfo->cls->name);
3324 if (!(cinfo->cls->attrs & AttrInterface)) {
3325 for (auto& kv : cinfo->methods) {
3326 auto const mte = mteFromElm(kv);
3328 if (mte->second.attrs & AttrNoOverride) continue;
3329 if (is_special_method_name(mte->first)) continue;
3331 // We need function family for constructor even if it is private,
3332 // as `new static()` may still call a non-private constructor from
3333 // subclass.
3334 if (!mte->first->isame(s_construct.get()) &&
3335 mte->second.attrs & AttrPrivate) {
3336 continue;
3339 define_func_family(index, cinfo.get(), mte->first, mte->second.func);
3342 if (cinfo->cls->attrs & (AttrInterface | AttrAbstract)) {
3343 build_abstract_func_families(index, cinfo.get());
3348 // Now that all of the FuncFamilies have been created, generate the
3349 // back links from FuncInfo to their FuncFamilies.
3350 std::vector<FuncFamily*> work;
3351 work.reserve(index.funcFamilies.size());
3352 for (auto const& kv : index.funcFamilies) work.emplace_back(kv.first.get());
3354 // Different threads can touch the same FuncInfo, so use sharded
3355 // locking scheme.
3356 std::array<std::mutex, 256> locks;
3358 parallel::for_each(
3359 work,
3360 [&] (FuncFamily* ff) {
3361 ff->m_numInOut = num_inout_from_set(ff->possibleFuncs());
3362 for (auto const pf : ff->possibleFuncs()) {
3363 auto finfo = create_func_info(index, pf->second.func);
3364 auto& lock = locks[pointer_hash<FuncInfo>{}(finfo) % locks.size()];
3365 std::lock_guard<std::mutex> _{lock};
3366 finfo->families.emplace_back(ff);
3371 parallel::for_each(
3372 index.funcInfo,
3373 [&] (FuncInfo& fi) { fi.families.shrink_to_fit(); }
3378 * ConflictGraph maintains lists of interfaces that conflict with each other
3379 * due to being implemented by the same class.
3381 struct ConflictGraph {
3382 void add(const php::Class* i, const php::Class* j) {
3383 if (i == j) return;
3384 map[i].insert(j);
3387 hphp_hash_map<const php::Class*,
3388 hphp_fast_set<const php::Class*>> map;
3392 * Trace information about interface conflict sets and the vtables computed
3393 * from them.
3395 void trace_interfaces(const IndexData& index, const ConflictGraph& cg) {
3396 // Compute what the vtable for each Class will look like, and build up a list
3397 // of all interfaces.
3398 struct Cls {
3399 const ClassInfo* cinfo;
3400 std::vector<const php::Class*> vtable;
3402 std::vector<Cls> classes;
3403 std::vector<const php::Class*> ifaces;
3404 size_t total_slots = 0, empty_slots = 0;
3405 for (auto& cinfo : index.allClassInfos) {
3406 if (cinfo->cls->attrs & AttrInterface) {
3407 ifaces.emplace_back(cinfo->cls);
3408 continue;
3410 if (cinfo->cls->attrs & (AttrTrait | AttrEnum | AttrEnumClass)) continue;
3412 classes.emplace_back(Cls{cinfo.get()});
3413 auto& vtable = classes.back().vtable;
3414 for (auto& pair : cinfo->implInterfaces) {
3415 auto it = index.ifaceSlotMap.find(pair.second->cls);
3416 assertx(it != end(index.ifaceSlotMap));
3417 auto const slot = it->second;
3418 if (slot >= vtable.size()) vtable.resize(slot + 1);
3419 vtable[slot] = pair.second->cls;
3422 total_slots += vtable.size();
3423 for (auto iface : vtable) if (iface == nullptr) ++empty_slots;
3426 Slot max_slot = 0;
3427 for (auto const& pair : index.ifaceSlotMap) {
3428 max_slot = std::max(max_slot, pair.second);
3431 // Sort the list of class vtables so the largest ones come first.
3432 auto class_cmp = [&](const Cls& a, const Cls& b) {
3433 return a.vtable.size() > b.vtable.size();
3435 std::sort(begin(classes), end(classes), class_cmp);
3437 // Sort the list of interfaces so the biggest conflict sets come first.
3438 auto iface_cmp = [&](const php::Class* a, const php::Class* b) {
3439 return cg.map.at(a).size() > cg.map.at(b).size();
3441 std::sort(begin(ifaces), end(ifaces), iface_cmp);
3443 std::string out;
3444 folly::format(&out, "{} interfaces, {} classes\n",
3445 ifaces.size(), classes.size());
3446 folly::format(&out,
3447 "{} vtable slots, {} empty vtable slots, max slot {}\n",
3448 total_slots, empty_slots, max_slot);
3449 folly::format(&out, "\n{:-^80}\n", " interface slots & conflict sets");
3450 for (auto iface : ifaces) {
3451 auto cgIt = cg.map.find(iface);
3452 if (cgIt == end(cg.map)) break;
3453 auto& conflicts = cgIt->second;
3455 folly::format(&out, "{:>40} {:3} {:2} [", iface->name,
3456 conflicts.size(),
3457 folly::get_default(index.ifaceSlotMap, iface));
3458 auto sep = "";
3459 for (auto conflict : conflicts) {
3460 folly::format(&out, "{}{}", sep, conflict->name);
3461 sep = ", ";
3463 folly::format(&out, "]\n");
3466 folly::format(&out, "\n{:-^80}\n", " class vtables ");
3467 for (auto& item : classes) {
3468 if (item.vtable.empty()) break;
3470 folly::format(&out, "{:>30}: [", item.cinfo->cls->name);
3471 auto sep = "";
3472 for (auto iface : item.vtable) {
3473 folly::format(&out, "{}{}", sep, iface ? iface->name->data() : "null");
3474 sep = ", ";
3476 folly::format(&out, "]\n");
3479 Trace::traceRelease("%s", out.c_str());
3483 * Find the lowest Slot that doesn't conflict with anything in the conflict set
3484 * for iface.
3486 Slot find_min_slot(const php::Class* iface,
3487 const IfaceSlotMap& slots,
3488 const ConflictGraph& cg) {
3489 auto const& cit = cg.map.find(iface);
3490 if (cit == cg.map.end() || cit->second.empty()) {
3491 // No conflicts. This is the only interface implemented by the classes that
3492 // implement it.
3493 return 0;
3496 boost::dynamic_bitset<> used;
3498 for (auto const& c : cit->second) {
3499 auto const it = slots.find(c);
3500 if (it == slots.end()) continue;
3501 auto const slot = it->second;
3503 if (used.size() <= slot) used.resize(slot + 1);
3504 used.set(slot);
3506 used.flip();
3507 return used.any() ? used.find_first() : used.size();
3511 * Compute vtable slots for all interfaces. No two interfaces implemented by
3512 * the same class will share the same vtable slot.
3514 void compute_iface_vtables(IndexData& index) {
3515 trace_time tracer("compute interface vtables");
3517 ConflictGraph cg;
3518 std::vector<const php::Class*> ifaces;
3519 hphp_hash_map<const php::Class*, int> iface_uses;
3521 // Build up the conflict sets.
3522 for (auto& cinfo : index.allClassInfos) {
3523 // Gather interfaces.
3524 if (cinfo->cls->attrs & AttrInterface) {
3525 ifaces.emplace_back(cinfo->cls);
3526 // Make sure cg.map has an entry for every interface - this simplifies
3527 // some code later on.
3528 cg.map[cinfo->cls];
3529 continue;
3532 // Only worry about classes with methods that can be called.
3533 if (cinfo->cls->attrs & (AttrTrait | AttrEnum | AttrEnumClass)) continue;
3535 for (auto& ipair : cinfo->implInterfaces) {
3536 ++iface_uses[ipair.second->cls];
3537 for (auto& jpair : cinfo->implInterfaces) {
3538 cg.add(ipair.second->cls, jpair.second->cls);
3543 if (ifaces.size() == 0) return;
3545 // Sort interfaces by usage frequencies.
3546 // We assign slots greedily, so sort the interface list so the most
3547 // frequently implemented ones come first.
3548 auto iface_cmp = [&](const php::Class* a, const php::Class* b) {
3549 return iface_uses[a] > iface_uses[b];
3551 std::sort(begin(ifaces), end(ifaces), iface_cmp);
3553 // Assign slots, keeping track of the largest assigned slot and the total
3554 // number of uses for each slot.
3555 Slot max_slot = 0;
3556 hphp_hash_map<Slot, int> slot_uses;
3557 for (auto* iface : ifaces) {
3558 auto const slot = find_min_slot(iface, index.ifaceSlotMap, cg);
3559 index.ifaceSlotMap[iface] = slot;
3560 max_slot = std::max(max_slot, slot);
3562 // Interfaces implemented by the same class never share a slot, so normal
3563 // addition is fine here.
3564 slot_uses[slot] += iface_uses[iface];
3567 // Make sure we have an initialized entry for each slot for the sort below.
3568 for (Slot slot = 0; slot < max_slot; ++slot) {
3569 assertx(slot_uses.count(slot));
3572 // Finally, sort and reassign slots so the most frequently used slots come
3573 // first. This slightly reduces the number of wasted vtable vector entries at
3574 // runtime.
3575 auto const slots = sort_keys_by_value(
3576 slot_uses,
3577 [&] (int a, int b) { return a > b; }
3580 std::vector<Slot> slots_permute(max_slot + 1, 0);
3581 for (size_t i = 0; i <= max_slot; ++i) slots_permute[slots[i]] = i;
3583 // re-map interfaces to permuted slots
3584 for (auto& pair : index.ifaceSlotMap) {
3585 pair.second = slots_permute[pair.second];
3588 if (Trace::moduleEnabledRelease(Trace::hhbbc_iface)) {
3589 trace_interfaces(index, cg);
3593 void mark_magic_on_parents(ClassInfo& cinfo, ClassInfo& derived) {
3594 auto any = false;
3595 for (const auto& mm : magicMethods) {
3596 if ((derived.*mm.pmem).thisHas) {
3597 auto& derivedHas = (cinfo.*mm.pmem).derivedHas;
3598 if (!derivedHas) {
3599 derivedHas = any = true;
3603 if (!any) return;
3604 if (cinfo.parent) mark_magic_on_parents(*cinfo.parent, derived);
3605 for (auto iface : cinfo.declInterfaces) {
3606 mark_magic_on_parents(*const_cast<ClassInfo*>(iface), derived);
3610 bool has_magic_method(const ClassInfo* cinfo, SString name) {
3611 if (name == s_toBoolean.get()) {
3612 // note that "having" a magic method includes the possibility that
3613 // a parent class has it. This can't happen for the collection
3614 // classes, because they're all final; but for SimpleXMLElement,
3615 // we need to search.
3616 while (cinfo->parent) cinfo = cinfo->parent;
3617 return has_magic_bool_conversion(cinfo->cls->name);
3619 return cinfo->methods.find(name) != end(cinfo->methods);
3622 void find_magic_methods(IndexData& index) {
3623 trace_time tracer("find magic methods");
3625 for (auto& cinfo : index.allClassInfos) {
3626 bool any = false;
3627 for (const auto& mm : magicMethods) {
3628 bool const found = has_magic_method(cinfo.get(), mm.name.get());
3629 any = any || found;
3630 (cinfo.get()->*mm.pmem).thisHas = found;
3632 if (any) mark_magic_on_parents(*cinfo, *cinfo);
3636 void find_mocked_classes(IndexData& index) {
3637 trace_time tracer("find mocked classes");
3639 for (auto& cinfo : index.allClassInfos) {
3640 if (is_mock_class(cinfo->cls) && cinfo->parent) {
3641 cinfo->parent->isMocked = true;
3642 for (auto c = cinfo->parent; c; c = c->parent) {
3643 c->isDerivedMocked = true;
3649 void mark_const_props(IndexData& index) {
3650 trace_time tracer("mark const props");
3652 for (auto& cinfo : index.allClassInfos) {
3653 auto const hasConstProp = [&]() {
3654 if (cinfo->cls->hasConstProp) return true;
3655 if (cinfo->parent && cinfo->parent->hasConstProp) return true;
3656 if (!(cinfo->cls->attrs & AttrNoExpandTrait)) {
3657 for (auto t : cinfo->usedTraits) {
3658 if (t->cls->hasConstProp) return true;
3661 return false;
3662 }();
3663 if (hasConstProp) {
3664 cinfo->hasConstProp = true;
3665 for (auto c = cinfo.get(); c; c = c->parent) {
3666 if (c->derivedHasConstProp) break;
3667 c->derivedHasConstProp = true;
3673 void mark_no_override_classes(IndexData& index) {
3674 trace_time tracer("mark no override classes");
3676 for (auto& cinfo : index.allClassInfos) {
3677 // We cleared all the NoOverride flags while building the
3678 // index. Set them as necessary.
3679 if (!(cinfo->cls->attrs & AttrInterface) &&
3680 cinfo->subclassList.size() == 1) {
3681 attribute_setter(cinfo->cls->attrs, true, AttrNoOverride);
3686 void mark_no_override_methods(IndexData& index) {
3687 trace_time tracer("mark no override methods");
3689 // We removed any AttrNoOverride flags from all methods while adding
3690 // the units to the index. Now start by marking every
3691 // (non-interface, non-special) method as AttrNoOverride.
3692 parallel::for_each(
3693 index.allClassInfos,
3694 [&] (const std::unique_ptr<ClassInfo>& cinfo) {
3695 if (cinfo->cls->attrs & AttrInterface) return;
3697 for (auto& m : cinfo->methods) {
3698 if (!(is_special_method_name(m.first))) {
3699 FTRACE(9, "Pre-setting AttrNoOverride on {}::{}\n",
3700 m.second.func->cls->name, m.first);
3701 attribute_setter(m.second.attrs, true, AttrNoOverride);
3702 attribute_setter(m.second.func->attrs, true, AttrNoOverride);
3708 // Then run through every ClassInfo, and for each of its parent
3709 // classes clear the AttrNoOverride flag if it has a different Func
3710 // with the same name.
3711 auto const updates = parallel::map(
3712 index.allClassInfos,
3713 [&] (const std::unique_ptr<ClassInfo>& cinfo) {
3714 hphp_fast_set<MethTabEntry*> changes;
3716 for (auto const& ancestor : cinfo->baseList) {
3717 if (ancestor == cinfo.get()) continue;
3719 for (auto const& derivedMethod : cinfo->methods) {
3720 auto const it = ancestor->methods.find(derivedMethod.first);
3721 if (it == end(ancestor->methods)) continue;
3722 if (it->second.func != derivedMethod.second.func) {
3723 FTRACE(2, "Removing AttrNoOverride on {}::{}\n",
3724 it->second.func->cls->name, it->first);
3725 changes.emplace(&it->second);
3730 return changes;
3734 for (auto const& u : updates) {
3735 for (auto& mte : u) {
3736 assertx(mte->attrs & AttrNoOverride ||
3737 !(mte->func->attrs & AttrNoOverride));
3738 if (mte->attrs & AttrNoOverride) {
3739 attribute_setter(mte->attrs, false, AttrNoOverride);
3740 attribute_setter(mte->func->attrs, false, AttrNoOverride);
3746 const StaticString s__Reified("__Reified");
3749 * Emitter adds a 86reifiedinit method to all classes that have reified
3750 * generics. All base classes also need to have this method so that when we
3751 * call parent::86reifeidinit(...), there is a stopping point.
3752 * Since while emitting we do not know whether a base class will have
3753 * reified parents, during JIT time we need to add 86reifiedinit
3754 * unless AttrNoReifiedInit attribute is set. At this phase,
3755 * we set AttrNoReifiedInit attribute on classes do not have any
3756 * reified classes that extend it.
3758 void clean_86reifiedinit_methods(IndexData& index) {
3759 trace_time tracer("clean 86reifiedinit methods");
3760 hphp_fast_set<const php::Class*> needsinit;
3762 // Find all classes that still need their 86reifiedinit methods
3763 for (auto const& cinfo : index.allClassInfos) {
3764 auto const& ual = cinfo->cls->userAttributes;
3765 // Each class that has at least one reified generic has an attribute
3766 // __Reified added by the emitter
3767 auto has_reification = ual.find(s__Reified.get()) != ual.end();
3768 if (!has_reification) continue;
3769 // Add the base class for this reified class
3770 needsinit.emplace(cinfo->baseList[0]->cls);
3773 // Add AttrNoReifiedInit to the base classes that do not need this method
3774 for (auto& cinfo : index.allClassInfos) {
3775 if (cinfo->parent == nullptr && needsinit.count(cinfo->cls) == 0) {
3776 FTRACE(2, "Adding AttrNoReifiedInit on class {}\n", cinfo->cls->name);
3777 attribute_setter(cinfo->cls->attrs, true, AttrNoReifiedInit);
3782 //////////////////////////////////////////////////////////////////////
3784 void check_invariants(const ClassInfo* cinfo) {
3785 // All the following invariants only apply to classes
3786 if (cinfo->cls->attrs & AttrInterface) return;
3788 if (!(cinfo->cls->attrs & AttrTrait)) {
3789 // For non-interface classes, each method in a php class has an
3790 // entry in its ClassInfo method table, and if it's not special,
3791 // AttrNoOverride, or private, an entry in the family table.
3792 for (auto& m : cinfo->cls->methods) {
3793 auto const it = cinfo->methods.find(m->name);
3794 always_assert(it != cinfo->methods.end());
3795 if (it->second.attrs & (AttrNoOverride|AttrPrivate)) continue;
3796 if (is_special_method_name(m->name)) continue;
3797 always_assert(
3798 cinfo->methodFamilies.count(m->name) ||
3799 cinfo->singleMethodFamilies.count(m->name)
3804 // The subclassList is non-empty, contains this ClassInfo, and
3805 // contains only unique elements.
3806 always_assert(!cinfo->subclassList.empty());
3807 always_assert(std::find(begin(cinfo->subclassList),
3808 end(cinfo->subclassList),
3809 cinfo) != end(cinfo->subclassList));
3810 auto cpy = cinfo->subclassList;
3811 std::sort(begin(cpy), end(cpy));
3812 cpy.erase(
3813 std::unique(begin(cpy), end(cpy)),
3814 end(cpy)
3816 always_assert(cpy.size() == cinfo->subclassList.size());
3818 // The baseList is non-empty, and the last element is this class.
3819 always_assert(!cinfo->baseList.empty());
3820 always_assert(cinfo->baseList.back() == cinfo);
3822 for (const auto& mm : magicMethods) {
3823 const auto& info = cinfo->*mm.pmem;
3825 // Magic method flags should be consistent with the method table.
3826 always_assert(info.thisHas == has_magic_method(cinfo, mm.name.get()));
3828 // Non-'derived' flags (thisHas) about magic methods imply the derived
3829 // ones.
3830 always_assert(!info.thisHas || info.derivedHas);
3833 // Every FuncFamily has more than function and contain functions
3834 // with the same name (unless its a family of ctors). methodFamilies
3835 // and singleMethodFamilies should have disjoint keys.
3836 for (auto const& mfam: cinfo->methodFamilies) {
3837 always_assert(mfam.second->possibleFuncs().size() > 1);
3838 auto const name = mfam.second->possibleFuncs().front()->first;
3839 for (auto const pf : mfam.second->possibleFuncs()) {
3840 always_assert(pf->first->isame(name));
3842 always_assert(!cinfo->singleMethodFamilies.count(mfam.first));
3844 for (auto const& mfam : cinfo->singleMethodFamilies) {
3845 always_assert(!cinfo->methodFamilies.count(mfam.first));
3849 void check_invariants(IndexData& data) {
3850 if (!debug) return;
3852 for (auto& cinfo : data.allClassInfos) {
3853 check_invariants(cinfo.get());
3857 //////////////////////////////////////////////////////////////////////
3859 Type context_sensitive_return_type(IndexData& data,
3860 CallContext callCtx,
3861 Type returnType) {
3862 constexpr auto max_interp_nexting_level = 2;
3863 static __thread uint32_t interp_nesting_level;
3864 auto const finfo = func_info(data, callCtx.callee);
3865 returnType = return_with_context(std::move(returnType), callCtx.context);
3867 auto checkParam = [&] (int i) {
3868 auto const constraint = finfo->func->params[i].typeConstraint;
3869 if (constraint.hasConstraint() &&
3870 !constraint.isTypeVar() &&
3871 !constraint.isTypeConstant()) {
3872 auto ctx = Context { finfo->func->unit, finfo->func, finfo->func->cls };
3873 auto t = data.m_index->lookup_constraint(ctx, constraint);
3874 return callCtx.args[i].strictlyMoreRefined(t);
3876 return callCtx.args[i].strictSubtypeOf(TInitCell);
3879 // TODO(#3788877): more heuristics here would be useful.
3880 bool const tryContextSensitive = [&] {
3881 if (finfo->func->noContextSensitiveAnalysis ||
3882 finfo->func->params.empty() ||
3883 interp_nesting_level + 1 >= max_interp_nexting_level ||
3884 returnType == TBottom) {
3885 return false;
3888 if (finfo->retParam != NoLocalId &&
3889 callCtx.args.size() > finfo->retParam &&
3890 checkParam(finfo->retParam)) {
3891 return true;
3894 if (!options.ContextSensitiveInterp) return false;
3896 if (callCtx.args.size() < finfo->func->params.size()) return true;
3897 for (auto i = 0; i < finfo->func->params.size(); i++) {
3898 if (checkParam(i)) return true;
3900 return false;
3901 }();
3903 if (!tryContextSensitive) {
3904 return returnType;
3908 ContextRetTyMap::const_accessor acc;
3909 if (data.contextualReturnTypes.find(acc, callCtx)) {
3910 if (data.frozen || acc->second == TBottom || is_scalar(acc->second)) {
3911 return acc->second;
3916 if (data.frozen) {
3917 return returnType;
3920 auto contextType = [&] {
3921 ++interp_nesting_level;
3922 SCOPE_EXIT { --interp_nesting_level; };
3924 auto const func = finfo->func;
3925 auto const wf = php::WideFunc::cns(func);
3926 auto const calleeCtx = AnalysisContext { func->unit, wf, func->cls };
3927 auto const ty =
3928 analyze_func_inline(*data.m_index, calleeCtx,
3929 callCtx.context, callCtx.args).inferredReturn;
3930 return return_with_context(ty, callCtx.context);
3931 }();
3933 if (!interp_nesting_level) {
3934 FTRACE(3,
3935 "Context sensitive type: {}\n"
3936 "Context insensitive type: {}\n",
3937 show(contextType), show(returnType));
3940 if (!returnType.subtypeOf(BUnc)) {
3941 // If the context insensitive return type could be non-static, staticness
3942 // could be a result of temporary context sensitive bytecode optimizations.
3943 contextType = loosen_staticness(std::move(contextType));
3946 auto ret = intersection_of(std::move(returnType), std::move(contextType));
3948 if (!interp_nesting_level) {
3949 FTRACE(3, "Context sensitive result: {}\n", show(ret));
3952 ContextRetTyMap::accessor acc;
3953 if (data.contextualReturnTypes.insert(acc, callCtx) ||
3954 ret.strictSubtypeOf(acc->second)) {
3955 acc->second = ret;
3958 return ret;
3961 //////////////////////////////////////////////////////////////////////
3963 PrepKind func_param_prep_default() {
3964 return PrepKind::Val;
3967 PrepKind func_param_prep(const php::Func* func,
3968 uint32_t paramId) {
3969 if (paramId >= func->params.size()) {
3970 return PrepKind::Val;
3972 return func->params[paramId].inout ? PrepKind::InOut : PrepKind::Val;
3975 template<class PossibleFuncRange>
3976 PrepKind prep_kind_from_set(PossibleFuncRange range, uint32_t paramId) {
3979 * In single-unit mode, the range is not complete. Without konwing all
3980 * possible resolutions, HHBBC cannot deduce anything about by-val vs inout.
3981 * So the caller should make sure not calling this in single-unit mode.
3983 if (begin(range) == end(range)) {
3984 return func_param_prep_default();
3987 struct FuncFind {
3988 using F = const php::Func*;
3989 static F get(std::pair<SString,F> p) { return p.second; }
3990 static F get(const MethTabEntryPair* mte) { return mte->second.func; }
3993 Optional<PrepKind> prep;
3994 for (auto& item : range) {
3995 switch (func_param_prep(FuncFind::get(item), paramId)) {
3996 case PrepKind::Unknown:
3997 return PrepKind::Unknown;
3998 case PrepKind::InOut:
3999 if (prep && *prep != PrepKind::InOut) return PrepKind::Unknown;
4000 prep = PrepKind::InOut;
4001 break;
4002 case PrepKind::Val:
4003 if (prep && *prep != PrepKind::Val) return PrepKind::Unknown;
4004 prep = PrepKind::Val;
4005 break;
4008 return *prep;
4011 template<typename F> auto
4012 visit_parent_cinfo(const ClassInfo* cinfo, F fun) -> decltype(fun(cinfo)) {
4013 for (auto ci = cinfo; ci != nullptr; ci = ci->parent) {
4014 if (auto const ret = fun(ci)) return ret;
4015 if (ci->cls->attrs & AttrNoExpandTrait) continue;
4016 for (auto ct : ci->usedTraits) {
4017 if (auto const ret = visit_parent_cinfo(ct, fun)) {
4018 return ret;
4022 return {};
4025 Type lookup_public_prop_impl(
4026 const IndexData& data,
4027 const ClassInfo* cinfo,
4028 SString propName
4030 // Find a property declared in this class (or a parent) with the same name.
4031 const php::Class* knownCls = nullptr;
4032 auto const prop = visit_parent_cinfo(
4033 cinfo,
4034 [&] (const ClassInfo* ci) -> const php::Prop* {
4035 for (auto const& prop : ci->cls->properties) {
4036 if (prop.name == propName) {
4037 knownCls = ci->cls;
4038 return &prop;
4041 return nullptr;
4045 if (!prop) return TCell;
4046 // Make sure its non-static and public. Otherwise its another function's
4047 // problem.
4048 if (prop->attrs & (AttrStatic | AttrPrivate)) return TCell;
4050 // Get a type corresponding to its declared type-hint (if any).
4051 auto ty = adjust_type_for_prop(
4052 *data.m_index, *knownCls, &prop->typeConstraint, TCell
4054 // We might have to include the initial value which might be outside of the
4055 // type-hint.
4056 auto initialTy = loosen_all(from_cell(prop->val));
4057 if (!initialTy.subtypeOf(TUninit) && (prop->attrs & AttrSystemInitialValue)) {
4058 ty |= initialTy;
4060 return ty;
4063 // Test if the given property (declared in `cls') is accessible in the
4064 // given context (null if we're not in a class).
4065 bool static_is_accessible(const ClassInfo* clsCtx,
4066 const ClassInfo* cls,
4067 const php::Prop& prop) {
4068 assertx(prop.attrs & AttrStatic);
4069 switch (prop.attrs & (AttrPublic|AttrProtected|AttrPrivate)) {
4070 case AttrPublic:
4071 // Public is accessible everywhere
4072 return true;
4073 case AttrProtected:
4074 // Protected is accessible from both derived classes and parent
4075 // classes
4076 return clsCtx && (clsCtx->derivedFrom(*cls) || cls->derivedFrom(*clsCtx));
4077 case AttrPrivate:
4078 // Private is only accessible from within the declared class
4079 return clsCtx == cls;
4081 always_assert(false);
4084 // Return true if the given class can possibly throw when its
4085 // initialized. Initialization can happen when an object of that class
4086 // is instantiated, or (more importantly) when static properties are
4087 // accessed.
4088 bool class_init_might_raise(IndexData& data,
4089 Context ctx,
4090 const ClassInfo* cinfo) {
4091 // Check this class and all of its parents for possible inequivalent
4092 // redeclarations or bad initial values.
4093 do {
4094 // Be conservative for now if we have unflattened traits.
4095 if (!cinfo->traitProps.empty()) return true;
4096 if (cinfo->hasBadRedeclareProp) return true;
4097 if (cinfo->hasBadInitialPropValues) {
4098 add_dependency(data, cinfo->cls, ctx, Dep::PropBadInitialValues);
4099 return true;
4101 cinfo = cinfo->parent;
4102 } while (cinfo);
4103 return false;
4107 * Calculate the effects of applying the given type against the
4108 * type-constraints for the given prop. This includes the subtype
4109 * which will succeed (if any), and if the type-constraint check might
4110 * throw.
4112 PropMergeResult<> prop_tc_effects(const Index& index,
4113 const ClassInfo* ci,
4114 const php::Prop& prop,
4115 const Type& val,
4116 bool checkUB) {
4117 assertx(prop.typeConstraint.validForProp());
4119 using R = PropMergeResult<>;
4121 // If we're not actually checking property type-hints, everything
4122 // goes
4123 if (RuntimeOption::EvalCheckPropTypeHints <= 0) return R{ val, TriBool::No };
4125 auto const ctx = Context { nullptr, nullptr, ci->cls };
4127 auto const check = [&] (const TypeConstraint& tc, const Type& t) {
4128 // If the type as is satisfies the constraint, we won't throw and
4129 // the type is unchanged.
4130 if (index.satisfies_constraint(ctx, t, tc)) return R{ t, TriBool::No };
4131 // Otherwise adjust the type. If we get a Bottom we'll definitely
4132 // throw. We already know the type doesn't completely satisfy the
4133 // constraint, so we'll at least maybe throw.
4134 auto adjusted = adjust_type_for_prop(index, *ctx.cls, &tc, t);
4135 auto const throws = yesOrMaybe(adjusted.subtypeOf(BBottom));
4136 return R{ std::move(adjusted), throws };
4139 // First check the main type-constraint.
4140 auto result = check(prop.typeConstraint, val);
4141 // If we're not checking generics upper-bounds, or if we already
4142 // know we'll fail, we're done.
4143 if (!checkUB ||
4144 RuntimeOption::EvalEnforceGenericsUB <= 0 ||
4145 result.throws == TriBool::Yes) {
4146 return result;
4149 // Otherwise check every generic upper-bound. We'll feed the
4150 // narrowed type into each successive round. If we reach the point
4151 // where we'll know we'll definitely fail, just stop.
4152 for (auto ub : prop.ubs) {
4153 applyFlagsToUB(ub, prop.typeConstraint);
4154 auto r = check(ub, result.adjusted);
4155 result.throws &= r.throws;
4156 result.adjusted = std::move(r.adjusted);
4157 if (result.throws == TriBool::Yes) break;
4160 return result;
4164 * Lookup data for the static property named `propName', starting from
4165 * the specified class `start'. If `propName' is nullptr, then any
4166 * accessible static property in the class hierarchy is considered. If
4167 * `startOnly' is specified, if the property isn't found in `start',
4168 * it is treated as a lookup failure. Otherwise the lookup continues
4169 * in all parent classes of `start', until a property is found, or
4170 * until all parent classes have been exhausted (`startOnly' is used
4171 * to avoid redundant class hierarchy walks). `clsCtx' is the current
4172 * context, converted to a ClassInfo* (or nullptr if not in a class).
4174 PropLookupResult<> lookup_static_impl(IndexData& data,
4175 Context ctx,
4176 const ClassInfo* clsCtx,
4177 const PropertiesInfo& privateProps,
4178 const ClassInfo* start,
4179 SString propName,
4180 bool startOnly) {
4181 ITRACE(
4182 6, "lookup_static_impl: {} {} {}\n",
4183 clsCtx ? clsCtx->cls->name->toCppString() : std::string{"-"},
4184 start->cls->name,
4185 propName ? propName->toCppString() : std::string{"*"}
4187 Trace::Indent _;
4189 auto const type = [&] (const php::Prop& prop,
4190 const ClassInfo* ci) {
4191 switch (prop.attrs & (AttrPublic|AttrProtected|AttrPrivate)) {
4192 case AttrPublic:
4193 case AttrProtected: {
4194 if (ctx.unit) add_dependency(data, &prop, ctx, Dep::PublicSProp);
4195 auto const it = ci->publicStaticProps.find(propName);
4196 assertx(it != end(ci->publicStaticProps));
4197 return remove_uninit(it->second.inferredType);
4199 case AttrPrivate: {
4200 assertx(clsCtx == ci);
4201 auto const elem = privateProps.readPrivateStatic(prop.name);
4202 if (!elem) return TInitCell;
4203 return remove_uninit(elem->ty);
4206 always_assert(false);
4209 auto const initMightRaise = class_init_might_raise(data, ctx, start);
4211 auto const fromProp = [&] (const php::Prop& prop,
4212 const ClassInfo* ci) {
4213 // The property was definitely found. Compute its attributes
4214 // from the prop metadata.
4215 return PropLookupResult<>{
4216 type(prop, ci),
4217 propName,
4218 TriBool::Yes,
4219 yesOrNo(prop.attrs & AttrIsConst),
4220 yesOrNo(prop.attrs & AttrIsReadOnly),
4221 yesOrNo(prop.attrs & AttrLateInit),
4222 initMightRaise
4226 auto const notFound = [&] {
4227 // The property definitely wasn't found.
4228 return PropLookupResult<>{
4229 TBottom,
4230 propName,
4231 TriBool::No,
4232 TriBool::No,
4233 TriBool::No,
4234 TriBool::No,
4235 false
4239 if (!propName) {
4240 // We don't statically know the prop name. Walk up the hierarchy
4241 // and union the data for any accessible static property.
4242 ITRACE(4, "no prop name, considering all accessible\n");
4243 auto result = notFound();
4244 visit_parent_cinfo(
4245 start,
4246 [&] (const ClassInfo* ci) {
4247 for (auto const& prop : ci->cls->properties) {
4248 if (!(prop.attrs & AttrStatic) ||
4249 !static_is_accessible(clsCtx, ci, prop)) {
4250 ITRACE(
4251 6, "skipping inaccessible {}::${}\n",
4252 ci->cls->name, prop.name
4254 continue;
4256 auto const r = fromProp(prop, ci);
4257 ITRACE(6, "including {}:${} {}\n", ci->cls->name, prop.name, show(r));
4258 result |= r;
4260 // If we're only interested in the starting class, don't walk
4261 // up to the parents.
4262 return startOnly;
4265 return result;
4268 // We statically know the prop name. Walk up the hierarchy and stop
4269 // at the first matching property and use that data.
4270 assertx(!startOnly);
4271 auto const result = visit_parent_cinfo(
4272 start,
4273 [&] (const ClassInfo* ci) -> Optional<PropLookupResult<>> {
4274 for (auto const& prop : ci->cls->properties) {
4275 if (prop.name != propName) continue;
4276 // We have a matching prop. If its not static or not
4277 // accessible, the access will not succeed.
4278 if (!(prop.attrs & AttrStatic) ||
4279 !static_is_accessible(clsCtx, ci, prop)) {
4280 ITRACE(
4281 6, "{}::${} found but inaccessible, stopping\n",
4282 ci->cls->name, propName
4284 return notFound();
4286 // Otherwise its a match
4287 auto const r = fromProp(prop, ci);
4288 ITRACE(6, "found {}:${} {}\n", ci->cls->name, propName, show(r));
4289 return r;
4291 return std::nullopt;
4294 if (!result) {
4295 // We walked up to all of the base classes and didn't find a
4296 // property with a matching name. The access will fail.
4297 ITRACE(6, "nothing found\n");
4298 return notFound();
4300 return *result;
4304 * Lookup the static property named `propName', starting from the
4305 * specified class `start'. If an accessible property is found, then
4306 * merge the given type `val' into the already known type for that
4307 * property. If `propName' is nullptr, then any accessible static
4308 * property in the class hierarchy is considered. If `startOnly' is
4309 * specified, if the property isn't found in `start', then the nothing
4310 * is done. Otherwise the lookup continues in all parent classes of
4311 * `start', until a property is found, or until all parent classes
4312 * have been exhausted (`startOnly' is to avoid redundant class
4313 * hierarchy walks). `clsCtx' is the current context, converted to a
4314 * ClassInfo* (or nullptr if not in a class). If `ignoreConst' is
4315 * false, then AttrConst properties will not have their type
4316 * modified. `mergePublic' is a lambda with the logic to merge a type
4317 * for a public property (this is needed to avoid cyclic
4318 * dependencies).
4320 template <typename F>
4321 PropMergeResult<> merge_static_type_impl(IndexData& data,
4322 Context ctx,
4323 F mergePublic,
4324 PropertiesInfo& privateProps,
4325 const ClassInfo* clsCtx,
4326 const ClassInfo* start,
4327 SString propName,
4328 const Type& val,
4329 bool checkUB,
4330 bool ignoreConst,
4331 bool mustBeReadOnly,
4332 bool startOnly) {
4333 ITRACE(
4334 6, "merge_static_type_impl: {} {} {} {}\n",
4335 clsCtx ? clsCtx->cls->name->toCppString() : std::string{"-"},
4336 start->cls->name,
4337 propName ? propName->toCppString() : std::string{"*"},
4338 show(val)
4340 Trace::Indent _;
4342 assertx(!val.subtypeOf(BBottom));
4344 // Perform the actual merge for a given property, returning the
4345 // effects of that merge.
4346 auto const merge = [&] (const php::Prop& prop, const ClassInfo* ci) {
4347 // First calculate the effects of the type-constraint.
4348 auto const effects = prop_tc_effects(*data.m_index, ci, prop, val, checkUB);
4349 // No point in merging if the type-constraint will always fail.
4350 if (effects.throws == TriBool::Yes) {
4351 ITRACE(
4352 6, "tc would throw on {}::${} with {}, skipping\n",
4353 ci->cls->name, prop.name, show(val)
4355 return effects;
4357 assertx(!effects.adjusted.subtypeOf(BBottom));
4359 ITRACE(
4360 6, "merging {} into {}::${}\n",
4361 show(effects.adjusted), ci->cls->name, prop.name
4364 switch (prop.attrs & (AttrPublic|AttrProtected|AttrPrivate)) {
4365 case AttrPublic:
4366 case AttrProtected:
4367 mergePublic(ci, prop, unctx(effects.adjusted));
4368 return effects;
4369 case AttrPrivate: {
4370 assertx(clsCtx == ci);
4371 privateProps.mergeInPrivateStaticPreAdjusted(
4372 prop.name,
4373 unctx(effects.adjusted)
4375 return effects;
4378 always_assert(false);
4381 // If we don't find a property, then the mutation will definitely
4382 // fail.
4383 auto const notFound = [&] {
4384 return PropMergeResult<>{
4385 TBottom,
4386 TriBool::Yes
4390 if (!propName) {
4391 // We don't statically know the prop name. Walk up the hierarchy
4392 // and merge the type for any accessible static property.
4393 ITRACE(6, "no prop name, considering all accessible\n");
4394 auto result = notFound();
4395 visit_parent_cinfo(
4396 start,
4397 [&] (const ClassInfo* ci) {
4398 for (auto const& prop : ci->cls->properties) {
4399 if (!(prop.attrs & AttrStatic) ||
4400 !static_is_accessible(clsCtx, ci, prop)) {
4401 ITRACE(
4402 6, "skipping inaccessible {}::${}\n",
4403 ci->cls->name, prop.name
4405 continue;
4407 if (!ignoreConst && (prop.attrs & AttrIsConst)) {
4408 ITRACE(6, "skipping const {}::${}\n", ci->cls->name, prop.name);
4409 continue;
4411 if (mustBeReadOnly && !(prop.attrs & AttrIsReadOnly)) {
4412 ITRACE(6, "skipping mutable property that must be readonly {}::${}\n",
4413 ci->cls->name, prop.name);
4414 continue;
4416 result |= merge(prop, ci);
4418 return startOnly;
4421 return result;
4424 // We statically know the prop name. Walk up the hierarchy and stop
4425 // at the first matching property and merge the type there.
4426 assertx(!startOnly);
4427 auto result = visit_parent_cinfo(
4428 start,
4429 [&] (const ClassInfo* ci) -> Optional<PropMergeResult<>> {
4430 for (auto const& prop : ci->cls->properties) {
4431 if (prop.name != propName) continue;
4432 // We found a property with the right name, but its
4433 // inaccessible from this context (or not even static). This
4434 // mutation will fail, so we don't need to modify the type.
4435 if (!(prop.attrs & AttrStatic) ||
4436 !static_is_accessible(clsCtx, ci, prop)) {
4437 ITRACE(
4438 6, "{}::${} found but inaccessible, stopping\n",
4439 ci->cls->name, propName
4441 return notFound();
4443 // Mutations to AttrConst properties will fail as well, unless
4444 // it we want to override that behavior.
4445 if (!ignoreConst && (prop.attrs & AttrIsConst)) {
4446 ITRACE(
4447 6, "{}:${} found but const, stopping\n",
4448 ci->cls->name, propName
4450 return notFound();
4452 if (mustBeReadOnly && !(prop.attrs & AttrIsReadOnly)) {
4453 ITRACE(
4454 6, "{}:${} found but is mutable and must be readonly, stopping\n",
4455 ci->cls->name, propName
4457 return notFound();
4459 return merge(prop, ci);
4461 return std::nullopt;
4464 if (!result) {
4465 ITRACE(6, "nothing found\n");
4466 return notFound();
4469 // If the mutation won't throw, we still need to check if the class
4470 // initialization can throw. If we might already throw (or
4471 // definitely will throw), this doesn't matter.
4472 if (result->throws == TriBool::No) {
4473 return PropMergeResult<>{
4474 std::move(result->adjusted),
4475 maybeOrNo(class_init_might_raise(data, ctx, start))
4478 return *result;
4481 //////////////////////////////////////////////////////////////////////
4485 //////////////////////////////////////////////////////////////////////
4486 namespace {
4487 template<typename T>
4488 void buildTypeInfoData(TypeInfoData<T>& tid,
4489 const ISStringToOneT<const T*>& tmap) {
4490 for (auto const& elm : tmap) {
4491 auto const t = elm.second;
4492 auto const addUser = [&] (SString rName) {
4493 tid.users[rName].push_back(t);
4494 ++tid.depCounts[t];
4496 PhpTypeHelper<T>::process_bases(t, addUser);
4498 if (!tid.depCounts.count(t)) {
4499 FTRACE(5, "Adding no-dep {} {}:{} to queue\n",
4500 PhpTypeHelper<T>::name(), t->name, (void*)t);
4501 // make sure that closure is first, because we end up calling
4502 // preresolve directly on closures created by trait
4503 // flattening, which assumes all dependencies are satisfied.
4504 if (tid.queue.size() && t->name == s_Closure.get()) {
4505 tid.queue.push_back(tid.queue[0]);
4506 tid.queue[0] = t;
4507 } else {
4508 tid.queue.push_back(t);
4510 } else {
4511 FTRACE(6, "{} {}:{} has {} deps\n",
4512 PhpTypeHelper<T>::name(), t->name, (void*)t, tid.depCounts[t]);
4515 tid.cqBack = tid.queue.size();
4516 tid.queue.resize(tmap.size());
4519 SString nameFromInfo(const RecordInfo* r) { return r->rec->name; }
4520 SString nameFromInfo(const ClassInfo* c) { return c->cls->name; }
4522 template <typename T>
4523 void updatePreResolveDeps(
4524 TypeInfoData<T>& tid,
4525 const PreResolveUpdates<typename PhpTypeHelper<T>::Info>& updates) {
4526 for (auto const info : updates.updateDeps) {
4527 auto const& users = tid.users[nameFromInfo(info)];
4528 for (auto const tu : users) {
4529 auto const it = tid.depCounts.find(tu);
4530 if (it == tid.depCounts.end()) {
4531 assertx(tid.hasPseudoCycles);
4532 continue;
4534 auto& depCount = it->second;
4535 assertx(depCount);
4536 if (!--depCount) {
4537 tid.depCounts.erase(it);
4538 ITRACE(5, " enqueue: {}:{}\n", tu->name, (void*)tu);
4539 tid.queue[tid.cqBack++] = tu;
4540 } else {
4541 ITRACE(6, " depcount: {}:{} = {}\n", tu->name, (void*)tu, depCount);
4547 void commitPreResolveUpdates(IndexData& index,
4548 TypeInfoData<php::Record>& tid,
4549 std::vector<RecPreResolveUpdates>& updates) {
4550 parallel::parallel(
4551 [&] {
4552 for (auto const& u : updates) updatePreResolveDeps(tid, u);
4554 [&] {
4555 for (auto& u : updates) {
4556 for (size_t i = 0; i < u.newInfos.size(); ++i) {
4557 auto& rinfo = u.newInfos[i];
4558 auto const UNUSED it =
4559 index.recordInfo.emplace(rinfo->rec->name, rinfo.get());
4560 assertx(it.second);
4561 index.allRecordInfos.emplace_back(std::move(rinfo));
4568 void commitPreResolveUpdates(IndexData& index,
4569 TypeInfoData<php::Class>& tid,
4570 std::vector<ClsPreResolveUpdates>& updates) {
4571 parallel::parallel(
4572 [&] {
4573 for (auto const& u : updates) updatePreResolveDeps(tid, u);
4575 [&] {
4576 for (auto& u : updates) {
4577 for (size_t i = 0; i < u.newInfos.size(); ++i) {
4578 auto& cinfo = u.newInfos[i];
4579 auto const UNUSED it =
4580 index.classInfo.emplace(cinfo->cls->name, cinfo.get());
4581 assertx(it.second);
4582 index.allClassInfos.emplace_back(std::move(cinfo));
4586 [&] {
4587 for (auto& u : updates) {
4588 for (auto& cns : u.removeNoOverride) {
4589 const_cast<php::Const*>(cns.get())->isNoOverride = false;
4593 [&] {
4594 for (auto& u : updates) {
4595 for (auto const& p : u.extraMethods) {
4596 index.classExtraMethodMap[p.first].insert(
4597 p.second.begin(),
4598 p.second.end()
4603 [&] {
4604 for (auto& u : updates) {
4605 for (auto const& p : u.closures) {
4606 auto& map = index.classClosureMap[p.first];
4607 map.insert(map.end(), p.second.begin(), p.second.end());
4611 [&] {
4612 for (auto& u : updates) {
4613 for (auto const c : u.newClosures) index.classes.emplace(c->name, c);
4616 [&] {
4617 for (auto& u : updates) {
4618 for (auto& p : u.newClasses) {
4619 auto unit = std::get<1>(p);
4620 auto const idx = std::get<2>(p);
4621 if (unit->classes.size() <= idx) unit->classes.resize(idx+1);
4622 unit->classes[idx] = std::move(std::get<0>(p));
4629 template<typename T>
4630 void preresolveTypes(php::Program* program,
4631 IndexData& index,
4632 TypeInfoData<T>& tid,
4633 const ISStringToOneT<TypeInfo<T>*>& tmap) {
4634 auto round = uint32_t{0};
4635 while (true) {
4636 if (tid.cqFront == tid.cqBack) {
4637 // we've consumed everything where all dependencies are
4638 // satisfied. There may still be some pseudo-cycles that can
4639 // be broken though.
4641 // eg if A extends B and B' extends A', we'll resolve B and
4642 // A', and then end up here, since both A and B' still have
4643 // one dependency. But both A and B' can be resolved at this
4644 // point
4645 for (auto it = tid.depCounts.begin();
4646 it != tid.depCounts.end();) {
4647 auto canResolve = true;
4648 auto const checkCanResolve = [&] (SString name) {
4649 if (canResolve) canResolve = tmap.count(name);
4651 PhpTypeHelper<T>::process_bases(it->first, checkCanResolve);
4652 if (canResolve) {
4653 FTRACE(2, "Breaking pseudo-cycle for {} {}:{}\n",
4654 PhpTypeHelper<T>::name(), it->first->name, (void*)it->first);
4655 tid.queue[tid.cqBack++] = it->first;
4656 it = tid.depCounts.erase(it);
4657 tid.hasPseudoCycles = true;
4658 } else {
4659 ++it;
4662 if (tid.cqFront == tid.cqBack) break;
4665 auto const workitems = tid.cqBack - tid.cqFront;
4666 auto updates = [&] {
4667 trace_time trace(
4668 "preresolve",
4669 folly::sformat("round {} -- {} work items", round, workitems)
4672 // Aggregate the types together by their Unit. This means only
4673 // one thread will be processing a particular Unit at a
4674 // time. This lets us avoid locking access to the Unit, and also
4675 // keeps the flattening logic deterministic.
4676 using UnitGroup = std::pair<const php::Unit*, CompactVector<const T*>>;
4678 hphp_fast_map<const php::Unit*, CompactVector<const T*>> group;
4679 for (auto idx = tid.cqFront; idx < tid.cqBack; ++idx) {
4680 auto const t = tid.queue[idx];
4681 group[t->unit].emplace_back(t);
4683 std::vector<UnitGroup> worklist{group.begin(), group.end()};
4685 using U = PreResolveUpdates<typename PhpTypeHelper<T>::Info>;
4687 return parallel::map(
4688 worklist,
4689 [&] (UnitGroup& group) {
4690 Trace::Bump bumper{
4691 Trace::hhbbc_index, kSystemLibBump, is_systemlib_part(*group.first)
4693 (void)bumper;
4695 std::sort(
4696 group.second.begin(), group.second.end(),
4697 [&] (const T* a, const T* b) {
4698 return strcmp(a->name->data(), b->name->data()) < 0;
4702 // NB: Even though we can freely access the Unit, we cannot
4703 // modify it in preresolve because other threads might also
4704 // be accessing it.
4705 U updates;
4706 updates.nextClassId = group.first->classes.size();
4707 for (auto const t : group.second) {
4708 preresolve(program, index, t, updates);
4710 return updates;
4713 }();
4715 ++round;
4716 tid.cqFront += workitems;
4718 trace_time trace("update");
4719 commitPreResolveUpdates(index, tid, updates);
4722 trace_time trace("preresolve clear state");
4723 parallel::for_each(
4724 index.allClassInfos,
4725 [&] (const std::unique_ptr<ClassInfo>& cinfo) {
4726 cinfo->preResolveState.reset();
4731 } //namespace
4733 Index::Index(php::Program* program)
4734 : m_data(std::make_unique<IndexData>(this))
4736 trace_time tracer("create index");
4738 m_data->arrTableBuilder.reset(new ArrayTypeTable::Builder());
4740 add_system_constants_to_index(*m_data);
4743 trace_time trace_add_units("add units to index");
4744 for (auto& u : program->units) {
4745 add_unit_to_index(*m_data, *u);
4749 RecordInfoData rid;
4751 trace_time build_record_info_data("build recordinfo data");
4752 buildTypeInfoData(rid, m_data->records);
4756 trace_time preresolve_records("preresolve records");
4757 preresolveTypes(program, *m_data, rid, m_data->recordInfo);
4760 ClassInfoData cid;
4762 trace_time build_class_info_data("build classinfo data");
4763 buildTypeInfoData(cid, m_data->classes);
4767 trace_time preresolve_classes("preresolve classes");
4768 preresolveTypes(program, *m_data, cid, m_data->classInfo);
4771 m_data->funcInfo.resize(program->nextFuncId);
4773 // Part of the index building routines happens before the various asserted
4774 // index invariants hold. These each may depend on computations from
4775 // previous functions, so be careful changing the order here.
4776 compute_subclass_list(*m_data);
4777 clean_86reifiedinit_methods(*m_data); // uses the base class lists
4778 mark_no_override_methods(*m_data);
4779 find_magic_methods(*m_data); // uses the subclass lists
4780 find_mocked_classes(*m_data);
4781 mark_const_props(*m_data);
4782 auto const logging = Trace::moduleEnabledRelease(Trace::hhbbc_time, 1);
4783 m_data->compute_iface_vtables = std::thread([&] {
4784 HphpSessionAndThread _{Treadmill::SessionKind::HHBBC};
4785 auto const enable =
4786 logging && !Trace::moduleEnabledRelease(Trace::hhbbc_time, 1);
4787 Trace::BumpRelease bumper(Trace::hhbbc_time, -1, enable);
4788 compute_iface_vtables(*m_data);
4791 define_func_families(*m_data); // AttrNoOverride, iface_vtables,
4792 // subclass_list
4794 check_invariants(*m_data);
4796 mark_no_override_classes(*m_data);
4798 trace_time tracer_2("initialize return types");
4799 std::vector<const php::Func*> all_funcs;
4800 all_funcs.reserve(m_data->funcs.size() + m_data->methods.size());
4801 for (auto const fn : m_data->funcs) {
4802 all_funcs.push_back(fn.second);
4804 for (auto const fn : m_data->methods) {
4805 all_funcs.push_back(fn.second);
4807 parallel::for_each(
4808 all_funcs,
4809 [&] (const php::Func* f) { init_return_type(f); }
4813 // Defined here so IndexData is a complete type for the unique_ptr
4814 // destructor.
4815 Index::~Index() {}
4817 //////////////////////////////////////////////////////////////////////
4819 void Index::mark_no_bad_redeclare_props(php::Class& cls) const {
4821 * Keep a list of properties which have not yet been found to redeclare
4822 * anything inequivalently. Start out by putting everything on the list. Then
4823 * walk up the inheritance chain, removing collisions as we find them.
4825 std::vector<php::Prop*> props;
4826 for (auto& prop : cls.properties) {
4827 if (prop.attrs & (AttrStatic | AttrPrivate)) {
4828 // Static and private properties never redeclare anything so need not be
4829 // considered.
4830 attribute_setter(prop.attrs, true, AttrNoBadRedeclare);
4831 continue;
4833 attribute_setter(prop.attrs, false, AttrNoBadRedeclare);
4834 props.emplace_back(&prop);
4837 auto currentCls = [&]() -> const ClassInfo* {
4838 auto const rcls = resolve_class(&cls);
4839 if (rcls.val.left()) return nullptr;
4840 return rcls.val.right();
4841 }();
4842 // If there's one more than one resolution for the class, be conservative and
4843 // we'll treat everything as possibly redeclaring.
4844 if (!currentCls) props.clear();
4846 while (!props.empty()) {
4847 auto const parent = currentCls->parent;
4848 if (!parent) {
4849 // No parent. We're done, so anything left on the prop list is
4850 // AttrNoBadRedeclare.
4851 for (auto& prop : props) {
4852 attribute_setter(prop->attrs, true, AttrNoBadRedeclare);
4854 break;
4857 auto const findParentProp = [&] (SString name) -> const php::Prop* {
4858 for (auto& prop : parent->cls->properties) {
4859 if (prop.name == name) return &prop;
4861 for (auto& prop : parent->traitProps) {
4862 if (prop.name == name) return &prop;
4864 return nullptr;
4867 // Remove any properties which collide with the current class.
4869 auto const propRedeclares = [&] (php::Prop* prop) {
4870 auto const pprop = findParentProp(prop->name);
4871 if (!pprop) return false;
4873 // We found a property being redeclared. Check if the type-hints on
4874 // the two are equivalent.
4875 auto const equivOneTCPair =
4876 [&](const TypeConstraint& tc1, const TypeConstraint& tc2) {
4877 // Try the cheap check first, use the index otherwise. Two
4878 // type-constraints are equivalent if all the possible values of one
4879 // satisfies the other, and vice-versa.
4880 if (!tc1.maybeInequivalentForProp(tc2)) return true;
4881 return
4882 satisfies_constraint(
4883 Context{},
4884 lookup_constraint(Context{}, tc1),
4886 ) && satisfies_constraint(
4887 Context{},
4888 lookup_constraint(Context{}, tc2),
4892 auto const equiv = [&] {
4893 if (!equivOneTCPair(prop->typeConstraint, pprop->typeConstraint)) {
4894 return false;
4896 for (auto ub : prop->ubs) {
4897 applyFlagsToUB(ub, prop->typeConstraint);
4898 auto foundEquiv = false;
4899 for (auto pub : pprop->ubs) {
4900 applyFlagsToUB(pub, pprop->typeConstraint);
4901 if (equivOneTCPair(ub, pub)) {
4902 foundEquiv = true;
4903 break;
4906 if (!foundEquiv) return false;
4908 return true;
4910 // If the property in the parent is static or private, the property in
4911 // the child isn't actually redeclaring anything. Otherwise, if the
4912 // type-hints are equivalent, remove this property from further
4913 // consideration and mark it as AttrNoBadRedeclare.
4914 if ((pprop->attrs & (AttrStatic | AttrPrivate)) || equiv()) {
4915 attribute_setter(prop->attrs, true, AttrNoBadRedeclare);
4917 return true;
4920 props.erase(
4921 std::remove_if(props.begin(), props.end(), propRedeclares),
4922 props.end()
4925 currentCls = parent;
4928 auto const possibleOverride =
4929 std::any_of(
4930 cls.properties.begin(),
4931 cls.properties.end(),
4932 [&](const php::Prop& prop) { return !(prop.attrs & AttrNoBadRedeclare); }
4935 // Mark all resolutions of this class as having any possible bad redeclaration
4936 // props, even if there's not an unique resolution.
4937 auto const it = m_data->classInfo.find(cls.name);
4938 if (it != end(m_data->classInfo)) {
4939 auto const cinfo = it->second;
4940 if (cinfo->cls == &cls) {
4941 cinfo->hasBadRedeclareProp = possibleOverride;
4947 * Rewrite the initial values for any AttrSystemInitialValue properties. If the
4948 * properties' type-hint does not admit null values, change the initial value to
4949 * one (if possible) to one that is not null. This is only safe to do so if the
4950 * property is not redeclared in a derived class or if the redeclaration does
4951 * not have a null system provided default value. Otherwise, a property can have
4952 * a null value (even if its type-hint doesn't allow it) without the JIT
4953 * realizing that its possible.
4955 * Note that this ignores any unflattened traits. This is okay because
4956 * properties pulled in from traits which match an already existing property
4957 * can't change the initial value. The runtime will clear AttrNoImplicitNullable
4958 * on any property pulled from the trait if it doesn't match an existing
4959 * property.
4961 void Index::rewrite_default_initial_values(php::Program& program) const {
4962 trace_time tracer("rewrite default initial values");
4965 * Use dataflow across the whole program class hierarchy. Start from the
4966 * classes which have no derived classes and flow up the hierarchy. We flow
4967 * the set of properties which have been assigned a null system provided
4968 * default value. If a property with such a null value flows into a class
4969 * which declares a property with the same name (and isn't static or private),
4970 * than that property is forced to be null as well.
4972 using PropSet = folly::F14FastSet<SString>;
4973 using OutState = folly::F14FastMap<const ClassInfo*, PropSet>;
4974 using Worklist = folly::F14FastSet<const ClassInfo*>;
4976 OutState outStates;
4977 outStates.reserve(m_data->allClassInfos.size());
4979 // List of Class' still to process this iteration
4980 using WorkList = std::vector<const ClassInfo*>;
4981 using WorkSet = folly::F14FastSet<const ClassInfo*>;
4983 WorkList workList;
4984 WorkSet workSet;
4985 auto const enqueue = [&] (const ClassInfo& cls) {
4986 auto const result = workSet.insert(&cls);
4987 if (!result.second) return;
4988 workList.emplace_back(&cls);
4991 // Start with all the leaf classes
4992 for (auto const& cinfo : m_data->allClassInfos) {
4993 auto const isLeaf = [&] {
4994 for (auto const& sub : cinfo->subclassList) {
4995 if (sub != cinfo.get()) return false;
4997 return true;
4998 }();
4999 if (isLeaf) enqueue(*cinfo);
5002 WorkList oldWorkList;
5003 int iter = 1;
5004 while (!workList.empty()) {
5005 FTRACE(
5006 4, "rewrite_default_initial_values round #{}: {} items\n",
5007 iter, workList.size()
5009 ++iter;
5011 std::swap(workList, oldWorkList);
5012 workList.clear();
5013 workSet.clear();
5014 for (auto const& cinfo : oldWorkList) {
5015 // Retrieve the set of properties which are flowing into this Class and
5016 // have to be null.
5017 auto inState = [&] () -> Optional<PropSet> {
5018 PropSet in;
5019 for (auto const& sub : cinfo->subclassList) {
5020 if (sub == cinfo || sub->parent != cinfo) continue;
5021 auto const it = outStates.find(sub);
5022 if (it == outStates.end()) return std::nullopt;
5023 in.insert(it->second.begin(), it->second.end());
5025 return in;
5026 }();
5027 if (!inState) continue;
5029 // Modify the in-state depending on the properties declared on this Class
5030 auto const cls = cinfo->cls;
5031 for (auto const& prop : cls->properties) {
5032 if (prop.attrs & (AttrStatic | AttrPrivate)) {
5033 // Private or static properties can't be redeclared
5034 inState->erase(prop.name);
5035 continue;
5037 // Ignore properties which have actual user provided initial values or
5038 // are LateInit.
5039 if (!(prop.attrs & AttrSystemInitialValue) ||
5040 (prop.attrs & AttrLateInit)) {
5041 continue;
5043 // Forced to be null, nothing to do
5044 if (inState->count(prop.name) > 0) continue;
5046 // Its not forced to be null. Find a better default value. If its null
5047 // anyways, force any properties this redeclares to be null as well.
5048 auto const defaultValue = prop.typeConstraint.defaultValue();
5049 if (defaultValue.m_type == KindOfNull) inState->insert(prop.name);
5052 // Push the in-state to the out-state.
5053 auto const result = outStates.emplace(std::make_pair(cinfo, *inState));
5054 if (result.second) {
5055 if (cinfo->parent) enqueue(*cinfo->parent);
5056 } else {
5057 // There shouldn't be cycles in the inheritance tree, so the out state
5058 // of Class', once set, should never change.
5059 assertx(result.first->second == *inState);
5064 // Now that we've processed all the classes, rewrite the property initial
5065 // values, unless they are forced to be nullable.
5066 for (auto& unit : program.units) {
5067 for (auto& c : unit->classes) {
5068 if (is_closure(*c)) continue;
5070 auto const out = [&] () -> Optional<PropSet> {
5071 Optional<PropSet> props;
5072 auto const range = m_data->classInfo.equal_range(c->name);
5073 for (auto it = range.first; it != range.second; ++it) {
5074 if (it->second->cls != c.get()) continue;
5075 auto const outStateIt = outStates.find(it->second);
5076 if (outStateIt == outStates.end()) return std::nullopt;
5077 if (!props) props.emplace();
5078 props->insert(outStateIt->second.begin(), outStateIt->second.end());
5080 return props;
5081 }();
5083 for (auto& prop : c->properties) {
5084 auto const nullable = [&] {
5085 if (!(prop.attrs & (AttrStatic | AttrPrivate))) {
5086 if (!out || out->count(prop.name)) return true;
5088 if (!(prop.attrs & AttrSystemInitialValue)) return false;
5089 return prop.typeConstraint.defaultValue().m_type == KindOfNull;
5090 }();
5092 attribute_setter(prop.attrs, !nullable, AttrNoImplicitNullable);
5093 if (!(prop.attrs & AttrSystemInitialValue)) continue;
5094 if (prop.val.m_type == KindOfUninit) {
5095 assertx(prop.attrs & AttrLateInit);
5096 continue;
5099 prop.val = nullable
5100 ? make_tv<KindOfNull>()
5101 : prop.typeConstraint.defaultValue();
5107 void Index::preinit_bad_initial_prop_values() {
5108 trace_time tracer("preinit bad initial prop values");
5109 parallel::for_each(
5110 m_data->allClassInfos,
5111 [&] (std::unique_ptr<ClassInfo>& cinfo) {
5112 if (is_used_trait(*cinfo->cls)) return;
5114 cinfo->hasBadInitialPropValues = false;
5115 for (auto& prop : const_cast<php::Class*>(cinfo->cls)->properties) {
5116 if (prop_might_have_bad_initial_value(*this, *cinfo->cls, prop)) {
5117 cinfo->hasBadInitialPropValues = true;
5118 prop.attrs = (Attr)(prop.attrs & ~AttrInitialSatisfiesTC);
5119 } else {
5120 prop.attrs |= AttrInitialSatisfiesTC;
5127 //////////////////////////////////////////////////////////////////////
5129 const CompactVector<const php::Class*>*
5130 Index::lookup_closures(const php::Class* cls) const {
5131 auto const it = m_data->classClosureMap.find(cls);
5132 if (it != end(m_data->classClosureMap)) {
5133 return &it->second;
5135 return nullptr;
5138 const hphp_fast_set<const php::Func*>*
5139 Index::lookup_extra_methods(const php::Class* cls) const {
5140 if (cls->attrs & AttrNoExpandTrait) return nullptr;
5141 auto const it = m_data->classExtraMethodMap.find(cls);
5142 if (it != end(m_data->classExtraMethodMap)) {
5143 return &it->second;
5145 return nullptr;
5148 //////////////////////////////////////////////////////////////////////
5150 template<typename T>
5151 Optional<T> Index::resolve_type_impl(SString name) const {
5152 auto const& infomap = m_data->infoMap<T>();
5153 auto const& omap = m_data->infoMap<typename ResTypeHelper<T>::OtherT>();
5154 auto const it = infomap.find(name);
5155 if (it != end(infomap)) {
5156 auto const tinfo = it->second;
5158 * If the preresolved [Class|Record]Info is Unique we can give it out.
5160 assertx(tinfo->phpType()->attrs & AttrUnique);
5161 if (debug &&
5162 (omap.count(name) ||
5163 m_data->typeAliases.count(name))) {
5164 std::fprintf(stderr, "non unique \"unique\" %s: %s\n",
5165 ResTypeHelper<T>::name().c_str(),
5166 tinfo->phpType()->name->data());
5168 auto const ta = m_data->typeAliases.find(name);
5169 if (ta != end(m_data->typeAliases)) {
5170 std::fprintf(stderr, " and type-alias %s\n",
5171 ta->second->name->data());
5174 auto const to = omap.find(name);
5175 if (to != end(omap)) {
5176 std::fprintf(stderr, " and %s %s\n",
5177 ResTypeHelper<typename ResTypeHelper<T>::OtherT>::
5178 name().c_str(),
5179 to->second->phpType()->name->data());
5181 always_assert(0);
5183 return T { tinfo };
5185 // We refuse to have name-only resolutions of enums and typeAliases,
5186 // so that all name only resolutions can be treated as records or classes.
5187 if (!m_data->enums.count(name) &&
5188 !m_data->typeAliases.count(name) &&
5189 !omap.count(name)) {
5190 return T { name };
5193 return std::nullopt;
5196 Optional<res::Record> Index::resolve_record(SString recName) const {
5197 recName = normalizeNS(recName);
5198 return resolve_type_impl<res::Record>(recName);
5201 //////////////////////////////////////////////////////////////////////
5203 res::Class Index::resolve_class(const php::Class* cls) const {
5205 auto const it = m_data->classInfo.find(cls->name);
5206 if (it != end(m_data->classInfo)) {
5207 auto const cinfo = it->second;
5208 if (cinfo->cls == cls) {
5209 return res::Class { cinfo };
5213 // We know its a class, not an enum or type alias, so return
5214 // by name
5215 return res::Class { cls->name.get() };
5218 Optional<res::Class> Index::resolve_class(Context ctx,
5219 SString clsName) const {
5220 clsName = normalizeNS(clsName);
5222 if (ctx.cls) {
5223 if (ctx.cls->name->isame(clsName)) {
5224 return resolve_class(ctx.cls);
5226 if (ctx.cls->parentName && ctx.cls->parentName->isame(clsName)) {
5227 if (auto const parent = resolve_class(ctx.cls).parent()) return parent;
5231 return resolve_type_impl<res::Class>(clsName);
5234 Optional<res::Class> Index::selfCls(const Context& ctx) const {
5235 if (!ctx.cls || is_used_trait(*ctx.cls)) return std::nullopt;
5236 return resolve_class(ctx.cls);
5239 Optional<res::Class> Index::parentCls(const Context& ctx) const {
5240 if (!ctx.cls || !ctx.cls->parentName) return std::nullopt;
5241 if (auto const parent = resolve_class(ctx.cls).parent()) return parent;
5242 return resolve_class(ctx, ctx.cls->parentName);
5245 Index::ResolvedInfo<boost::variant<boost::blank,res::Class,res::Record>>
5246 Index::resolve_type_name(SString inName) const {
5247 auto const res = resolve_type_name_internal(inName);
5248 using Ret = boost::variant<boost::blank,res::Class,res::Record>;
5249 auto const val = match<Ret>(
5250 res.value,
5251 [&] (boost::blank) { return Ret{}; },
5252 [&] (SString s) {
5253 return (res.type == AnnotType::Record) ?
5254 Ret{res::Record{s}} : Ret{res::Class{s}};
5256 [&] (ClassInfo* c) { return res::Class{c}; },
5257 [&] (RecordInfo* r) { return res::Record{r}; }
5259 return { res.type, res.nullable, val };
5262 Index::ResolvedInfo<boost::variant<boost::blank,SString,ClassInfo*,RecordInfo*>>
5263 Index::resolve_type_name_internal(SString inName) const {
5264 Optional<hphp_fast_set<const void*>> seen;
5266 auto nullable = false;
5267 auto name = inName;
5269 for (unsigned i = 0; ; ++i) {
5270 name = normalizeNS(name);
5271 auto const rec_it = m_data->recordInfo.find(name);
5272 if (rec_it != end(m_data->recordInfo)) {
5273 auto const rinfo = rec_it->second;
5274 assertx(rinfo->rec->attrs & AttrUnique);
5275 return { AnnotType::Record, nullable, rinfo };
5277 auto const cls_it = m_data->classInfo.find(name);
5278 if (cls_it != end(m_data->classInfo)) {
5279 auto const cinfo = cls_it->second;
5280 assertx(cinfo->cls->attrs & AttrUnique);
5281 if (!(cinfo->cls->attrs & AttrEnum)) {
5282 return { AnnotType::Object, nullable, cinfo };
5284 auto const& tc = cinfo->cls->enumBaseTy;
5285 assertx(!tc.isNullable());
5286 if (tc.type() != AnnotType::Object) {
5287 auto const type = tc.type() == AnnotType::Mixed ?
5288 AnnotType::ArrayKey : tc.type();
5289 return { type, nullable, tc.typeName() };
5291 name = tc.typeName();
5292 } else {
5293 auto const ta_it = m_data->typeAliases.find(name);
5294 if (ta_it == end(m_data->typeAliases)) break;
5295 auto const ta = ta_it->second;
5296 assertx(ta->attrs & AttrUnique);
5297 nullable = nullable || ta->nullable;
5298 if (ta->type != AnnotType::Object) {
5299 return { ta->type, nullable, ta->value.get() };
5301 name = ta->value;
5304 // deal with cycles. Since we don't expect to
5305 // encounter them, just use a counter until we hit a chain length
5306 // of 10, then start tracking the names we resolve.
5307 if (i == 10) {
5308 seen.emplace();
5309 seen->insert(name);
5310 } else if (i > 10) {
5311 if (!seen->insert(name).second) {
5312 return { AnnotType::Object, false, {} };
5317 return { AnnotType::Object, nullable, name };
5320 struct Index::ConstraintResolution {
5321 /* implicit */ ConstraintResolution(Type type)
5322 : type{std::move(type)}
5323 , maybeMixed{false} {}
5324 ConstraintResolution(Optional<Type> type, bool maybeMixed)
5325 : type{std::move(type)}
5326 , maybeMixed{maybeMixed} {}
5328 Optional<Type> type;
5329 bool maybeMixed;
5332 Index::ConstraintResolution Index::resolve_named_type(
5333 const Context& ctx, SString name, const Type& candidate) const {
5335 auto const res = resolve_type_name_internal(name);
5337 if (res.nullable && candidate.subtypeOf(BInitNull)) return TInitNull;
5339 if (res.type == AnnotType::Object) {
5340 auto resolve = [&] (const res::Class& rcls) -> Optional<Type> {
5341 if (!interface_supports_non_objects(rcls.name()) ||
5342 candidate.subtypeOf(BOptObj)) {
5343 return subObj(rcls);
5346 if (candidate.subtypeOf(BOptVec)) {
5347 if (interface_supports_arrlike(rcls.name())) return TVec;
5348 } else if (candidate.subtypeOf(BOptDict)) {
5349 if (interface_supports_arrlike(rcls.name())) return TDict;
5350 } else if (candidate.subtypeOf(BOptKeyset)) {
5351 if (interface_supports_arrlike(rcls.name())) return TKeyset;
5352 } else if (candidate.subtypeOf(BOptStr)) {
5353 if (interface_supports_string(rcls.name())) return TStr;
5354 } else if (candidate.subtypeOf(BOptInt)) {
5355 if (interface_supports_int(rcls.name())) return TInt;
5356 } else if (candidate.subtypeOf(BOptDbl)) {
5357 if (interface_supports_double(rcls.name())) return TDbl;
5359 return std::nullopt;
5362 auto const val = match<Either<SString, ClassInfo*>>(
5363 res.value,
5364 [&] (boost::blank) { return nullptr; },
5365 [&] (SString s) { return s; },
5366 [&] (ClassInfo* c) { return c; },
5367 [&] (RecordInfo*) { always_assert(false); return nullptr; }
5369 if (val.isNull()) return ConstraintResolution{ std::nullopt, true };
5370 auto ty = resolve(res::Class { val });
5371 if (ty && res.nullable) *ty = opt(std::move(*ty));
5372 return ConstraintResolution{ std::move(ty), false };
5373 } else if (res.type == AnnotType::Record) {
5374 auto const val = match<Either<SString, RecordInfo*>>(
5375 res.value,
5376 [&] (boost::blank) { return nullptr; },
5377 [&] (SString s) { return s; },
5378 [&] (ClassInfo* c) { always_assert(false); return nullptr; },
5379 [&] (RecordInfo* r) { return r; }
5381 if (val.isNull()) return ConstraintResolution{ std::nullopt, true };
5382 return subRecord(res::Record { val });
5385 return get_type_for_annotated_type(ctx, res.type, res.nullable,
5386 boost::get<SString>(res.value), candidate);
5389 std::pair<res::Class,php::Class*>
5390 Index::resolve_closure_class(Context ctx, int32_t idx) const {
5391 auto const cls = ctx.unit->classes[idx].get();
5392 auto const rcls = resolve_class(cls);
5394 // Closure classes must be unique and defined in the unit that uses
5395 // the CreateCl opcode, so resolution must succeed.
5396 always_assert_flog(
5397 rcls.resolved(),
5398 "A Closure class ({}) failed to resolve",
5399 cls->name
5402 return { rcls, cls };
5405 res::Class Index::builtin_class(SString name) const {
5406 auto const rcls = resolve_class(Context {}, name);
5407 always_assert_flog(
5408 rcls.has_value() &&
5409 rcls->val.right() &&
5410 (rcls->val.right()->cls->attrs & AttrBuiltin),
5411 "A builtin class ({}) failed to resolve",
5412 name->data()
5414 return *rcls;
5417 res::Func Index::resolve_method(Context ctx,
5418 Type clsType,
5419 SString name) const {
5420 auto name_only = [&] {
5421 return res::Func { this, res::Func::MethodName { name } };
5424 if (!is_specialized_cls(clsType)) {
5425 return name_only();
5427 auto const dcls = dcls_of(clsType);
5428 auto const cinfo = dcls.cls.val.right();
5429 if (!cinfo) return name_only();
5431 // Classes may have more method families than methods. Any such
5432 // method families are guaranteed to all be public so we can do this
5433 // lookup as a last gasp before resorting to name_only().
5434 auto const find_extra_method = [&] {
5435 auto singleMethIt = cinfo->singleMethodFamilies.find(name);
5436 if (singleMethIt != cinfo->singleMethodFamilies.end()) {
5437 return res::Func { this, singleMethIt->second };
5439 auto methIt = cinfo->methodFamilies.find(name);
5440 if (methIt == end(cinfo->methodFamilies)) return name_only();
5441 // If there was a sole implementer we can resolve to a single method, even
5442 // if the method was not declared on the interface itself.
5443 assertx(methIt->second->possibleFuncs().size() > 1);
5444 return res::Func { this, methIt->second };
5447 // Interfaces *only* have the extra methods defined for all
5448 // subclasses
5449 if (cinfo->cls->attrs & AttrInterface) return find_extra_method();
5452 * Whether or not the context class has a private method with the
5453 * same name as the method we're trying to call.
5455 auto const contextMayHavePrivateWithSameName = folly::lazy([&]() -> bool {
5456 if (!ctx.cls) return false;
5457 auto const cls_it = m_data->classInfo.find(ctx.cls->name);
5458 if (cls_it == end(m_data->classInfo)) {
5459 // This class had no pre-resolved ClassInfos, which means it
5460 // always fatals in any way it could be defined, so it doesn't
5461 // matter what we return here (as all methods in the context
5462 // class are unreachable code).
5463 return true;
5465 // Because of traits, each instantiation of the class could have
5466 // different private methods; we need to check them all.
5467 auto const iter = cls_it->second->methods.find(name);
5468 if (iter != end(cls_it->second->methods) &&
5469 iter->second.attrs & AttrPrivate &&
5470 iter->second.topLevel) {
5471 return true;
5473 return false;
5477 * Look up the method in the target class.
5479 auto const methIt = cinfo->methods.find(name);
5480 if (methIt == end(cinfo->methods)) return find_extra_method();
5481 auto const ftarget = methIt->second.func;
5483 // We need to revisit the hasPrivateAncestor code if we start being
5484 // able to look up methods on interfaces (currently they have empty
5485 // method tables).
5486 assertx(!(cinfo->cls->attrs & AttrInterface));
5489 * If our candidate method has a private ancestor, unless it is
5490 * defined on this class, we need to make sure we don't erroneously
5491 * resolve the overriding method if the call is coming from the
5492 * context the defines the private method.
5494 * For now this just gives up if the context and the callee class
5495 * could be related and the context defines a private of the same
5496 * name. (We should actually try to resolve that method, though.)
5498 if (methIt->second.hasPrivateAncestor &&
5499 ctx.cls &&
5500 ctx.cls != ftarget->cls) {
5501 if (could_be_related(ctx.cls, cinfo->cls)) {
5502 if (contextMayHavePrivateWithSameName()) {
5503 return name_only();
5508 auto resolve = [&] {
5509 create_func_info(*m_data, ftarget);
5510 return res::Func { this, mteFromIt(methIt) };
5513 switch (dcls.type) {
5514 case DCls::Exact:
5515 return resolve();
5516 case DCls::Sub:
5517 if (methIt->second.attrs & AttrNoOverride) {
5518 return resolve();
5520 if (!options.FuncFamilies) return name_only();
5523 auto const singleFamIt = cinfo->singleMethodFamilies.find(name);
5524 if (singleFamIt != cinfo->singleMethodFamilies.end()) {
5525 return res::Func { this, singleFamIt->second };
5527 auto const famIt = cinfo->methodFamilies.find(name);
5528 if (famIt == end(cinfo->methodFamilies)) return name_only();
5529 assertx(famIt->second->possibleFuncs().size() > 1);
5530 return res::Func { this, famIt->second };
5533 not_reached();
5536 Optional<res::Func>
5537 Index::resolve_ctor(Context /*ctx*/, res::Class rcls, bool exact) const {
5538 auto const cinfo = rcls.val.right();
5539 if (!cinfo) return std::nullopt;
5540 if (cinfo->cls->attrs & (AttrInterface|AttrTrait)) return std::nullopt;
5542 auto const cit = cinfo->methods.find(s_construct.get());
5543 if (cit == end(cinfo->methods)) return std::nullopt;
5545 auto const ctor = mteFromIt(cit);
5546 if (exact || ctor->second.attrs & AttrNoOverride) {
5547 create_func_info(*m_data, ctor->second.func);
5548 return res::Func { this, ctor };
5551 if (!options.FuncFamilies) return std::nullopt;
5553 auto const singleFamIt = cinfo->singleMethodFamilies.find(s_construct.get());
5554 if (singleFamIt != cinfo->singleMethodFamilies.end()) {
5555 return res::Func { this, singleFamIt->second};
5557 auto const famIt = cinfo->methodFamilies.find(s_construct.get());
5558 if (famIt == end(cinfo->methodFamilies)) return std::nullopt;
5559 assertx(famIt->second->possibleFuncs().size() > 1);
5560 return res::Func { this, famIt->second };
5563 res::Func
5564 Index::resolve_func_helper(const php::Func* func, SString name) const {
5565 auto name_only = [&] (bool renamable) {
5566 return res::Func { this, res::Func::FuncName { name, renamable } };
5569 // no resolution
5570 if (func == nullptr) return name_only(false);
5572 // single resolution, in whole-program mode, that's it
5573 assertx(func->attrs & AttrUnique);
5574 return do_resolve(func);
5577 res::Func Index::resolve_func(Context /*ctx*/, SString name) const {
5578 name = normalizeNS(name);
5579 auto const it = m_data->funcs.find(name);
5580 return resolve_func_helper((it != end(m_data->funcs)) ? it->second : nullptr, name);
5584 * Gets a type for the constraint.
5586 * If getSuperType is true, the type could be a super-type of the
5587 * actual type constraint (eg TCell). Otherwise its guaranteed that
5588 * for any t, t.subtypeOf(get_type_for_constraint<false>(ctx, tc, t)
5589 * implies t would pass the constraint.
5591 * The candidate type is used to disambiguate; if we're applying a
5592 * Traversable constraint to a TObj, we should return
5593 * subObj(Traversable). If we're applying it to an Array, we should
5594 * return Array.
5596 template<bool getSuperType>
5597 Type Index::get_type_for_constraint(Context ctx,
5598 const TypeConstraint& tc,
5599 const Type& candidate) const {
5600 assertx(IMPLIES(!tc.isCheckable(),
5601 tc.isMixed() ||
5602 (tc.isUpperBound() &&
5603 RuntimeOption::EvalEnforceGenericsUB == 0)));
5605 if (getSuperType) {
5607 * Soft hints (@Foo) are not checked.
5608 * Also upper-bound type hints are not checked when they do not error.
5610 if (tc.isSoft() ||
5611 (RuntimeOption::EvalEnforceGenericsUB < 2 && tc.isUpperBound())) {
5612 return TCell;
5616 auto const res = get_type_for_annotated_type(
5617 ctx,
5618 tc.type(),
5619 tc.isNullable(),
5620 tc.typeName(),
5621 candidate
5623 if (res.type) return *res.type;
5624 // If the type constraint might be mixed, then the value could be
5625 // uninit. Any other type constraint implies TInitCell.
5626 return getSuperType ? (res.maybeMixed ? TCell : TInitCell) : TBottom;
5629 bool Index::prop_tc_maybe_unenforced(const php::Class& propCls,
5630 const TypeConstraint& tc) const {
5631 assertx(tc.validForProp());
5632 if (RuntimeOption::EvalCheckPropTypeHints <= 2) return true;
5633 if (!tc.isCheckable()) return true;
5634 if (tc.isSoft()) return true;
5635 if (tc.isUpperBound() && RuntimeOption::EvalEnforceGenericsUB < 2) {
5636 return true;
5638 auto const res = get_type_for_annotated_type(
5639 Context { nullptr, nullptr, &propCls },
5640 tc.type(),
5641 tc.isNullable(),
5642 tc.typeName(),
5643 TCell
5645 return res.maybeMixed;
5648 Index::ConstraintResolution Index::get_type_for_annotated_type(
5649 Context ctx, AnnotType annot, bool nullable,
5650 SString name, const Type& candidate) const {
5652 if (candidate.subtypeOf(BInitNull) && nullable) {
5653 return TInitNull;
5656 auto mainType = [&]() -> ConstraintResolution {
5657 switch (getAnnotMetaType(annot)) {
5658 case AnnotMetaType::Precise: {
5659 auto const dt = getAnnotDataType(annot);
5661 switch (dt) {
5662 case KindOfNull: return TNull;
5663 case KindOfBoolean: return TBool;
5664 case KindOfInt64: return TInt;
5665 case KindOfDouble: return TDbl;
5666 case KindOfPersistentString:
5667 case KindOfString: return TStr;
5668 case KindOfPersistentVec:
5669 case KindOfVec: return TVec;
5670 case KindOfPersistentDict:
5671 case KindOfDict: return TDict;
5672 case KindOfPersistentKeyset:
5673 case KindOfKeyset: return TKeyset;
5674 case KindOfResource: return TRes;
5675 case KindOfClsMeth: return TClsMeth;
5676 case KindOfRecord: // fallthrough
5677 case KindOfObject:
5678 return resolve_named_type(ctx, name, candidate);
5679 case KindOfUninit:
5680 case KindOfRFunc:
5681 case KindOfFunc:
5682 case KindOfRClsMeth:
5683 case KindOfClass:
5684 case KindOfLazyClass:
5685 always_assert_flog(false, "Unexpected DataType");
5686 break;
5688 break;
5690 case AnnotMetaType::Mixed:
5692 * Here we handle "mixed", typevars, and some other ignored
5693 * typehints (ex. "(function(..): ..)" typehints).
5695 return { TCell, true };
5696 case AnnotMetaType::Nothing:
5697 case AnnotMetaType::NoReturn:
5698 return TBottom;
5699 case AnnotMetaType::Nonnull:
5700 if (candidate.subtypeOf(BInitNull)) return TBottom;
5701 if (!candidate.couldBe(BInitNull)) return candidate;
5702 return unopt(candidate);
5703 case AnnotMetaType::This:
5704 if (auto s = selfCls(ctx)) return setctx(subObj(*s));
5705 break;
5706 case AnnotMetaType::Self:
5707 if (auto s = selfCls(ctx)) return subObj(*s);
5708 break;
5709 case AnnotMetaType::Parent:
5710 if (auto p = parentCls(ctx)) return subObj(*p);
5711 break;
5712 case AnnotMetaType::Callable:
5713 break;
5714 case AnnotMetaType::Number:
5715 return TNum;
5716 case AnnotMetaType::ArrayKey:
5717 if (candidate.subtypeOf(BInt)) return TInt;
5718 if (candidate.subtypeOf(BStr)) return TStr;
5719 return TArrKey;
5720 case AnnotMetaType::VecOrDict:
5721 if (candidate.subtypeOf(BVec)) return TVec;
5722 if (candidate.subtypeOf(BDict)) return TDict;
5723 return union_of(TVec, TDict);
5724 case AnnotMetaType::ArrayLike:
5725 if (candidate.subtypeOf(BVec)) return TVec;
5726 if (candidate.subtypeOf(BDict)) return TDict;
5727 if (candidate.subtypeOf(BKeyset)) return TKeyset;
5728 return TArrLike;
5729 case AnnotMetaType::Classname:
5730 if (candidate.subtypeOf(BStr)) return TStr;
5731 if (!RuntimeOption::EvalClassnameNotices) {
5732 if (candidate.subtypeOf(BCls)) return TCls;
5733 if (candidate.subtypeOf(BLazyCls)) return TLazyCls;
5736 return ConstraintResolution{ std::nullopt, false };
5737 }();
5739 if (mainType.type && nullable) {
5740 if (mainType.type->subtypeOf(BBottom)) {
5741 if (candidate.couldBe(BInitNull)) {
5742 mainType.type = TInitNull;
5744 } else if (!mainType.type->couldBe(BInitNull)) {
5745 mainType.type = opt(*mainType.type);
5748 return mainType;
5751 Type Index::lookup_constraint(Context ctx,
5752 const TypeConstraint& tc,
5753 const Type& t) const {
5754 return get_type_for_constraint<true>(ctx, tc, t);
5757 bool Index::satisfies_constraint(Context ctx, const Type& t,
5758 const TypeConstraint& tc) const {
5759 auto const tcType = get_type_for_constraint<false>(ctx, tc, t);
5760 return t.moreRefined(tcType);
5763 bool Index::could_have_reified_type(Context ctx,
5764 const TypeConstraint& tc) const {
5765 if (ctx.func->isClosureBody) {
5766 for (auto i = ctx.func->params.size();
5767 i < ctx.func->locals.size();
5768 ++i) {
5769 auto const name = ctx.func->locals[i].name;
5770 if (!name) return false; // named locals do not appear after unnamed local
5771 if (isMangledReifiedGenericInClosure(name)) return true;
5773 return false;
5775 if (!tc.isObject()) return false;
5776 auto const name = tc.typeName();
5777 auto const resolved = resolve_type_name_internal(name);
5778 if (resolved.type != AnnotType::Object) return false;
5779 auto const val = match<Either<SString, ClassInfo*>>(
5780 resolved.value,
5781 [&] (boost::blank) { return nullptr; },
5782 [&] (SString s) { return s; },
5783 [&] (ClassInfo* c) { return c; },
5784 [&] (RecordInfo*) { always_assert(false); return nullptr; }
5786 res::Class rcls{val};
5787 return rcls.couldHaveReifiedGenerics();
5790 Optional<bool>
5791 Index::supports_async_eager_return(res::Func rfunc) const {
5792 auto const supportsAER = [] (const php::Func* func) {
5793 // Async functions always support async eager return.
5794 if (func->isAsync && !func->isGenerator) return true;
5796 // No other functions support async eager return yet.
5797 return false;
5800 return match<Optional<bool>>(
5801 rfunc.val,
5802 [&](res::Func::FuncName) { return std::nullopt; },
5803 [&](res::Func::MethodName) { return std::nullopt; },
5804 [&](FuncInfo* finfo) { return supportsAER(finfo->func); },
5805 [&](const MethTabEntryPair* mte) { return supportsAER(mte->second.func); },
5806 [&](FuncFamily* fam) -> Optional<bool> {
5807 auto ret = Optional<bool>{};
5808 for (auto const pf : fam->possibleFuncs()) {
5809 // Abstract functions are never called.
5810 if (pf->second.attrs & AttrAbstract) continue;
5811 auto const val = supportsAER(pf->second.func);
5812 if (ret && *ret != val) return std::nullopt;
5813 ret = val;
5815 return ret;
5819 bool Index::is_effect_free(const php::Func* func) const {
5820 return func_info(*m_data, func)->effectFree;
5823 bool Index::is_effect_free(res::Func rfunc) const {
5824 return match<bool>(
5825 rfunc.val,
5826 [&](res::Func::FuncName) { return false; },
5827 [&](res::Func::MethodName) { return false; },
5828 [&](FuncInfo* finfo) { return finfo->effectFree; },
5829 [&](const MethTabEntryPair* mte) {
5830 return func_info(*m_data, mte->second.func)->effectFree;
5832 [&](FuncFamily* fam) {
5833 for (auto const mte : fam->possibleFuncs()) {
5834 if (!func_info(*m_data, mte->second.func)->effectFree) return false;
5836 return true;
5841 const php::Const* Index::lookup_class_const_ptr(Context ctx,
5842 res::Class rcls,
5843 SString cnsName,
5844 bool allow_tconst) const {
5845 if (rcls.val.left()) return nullptr;
5846 auto const cinfo = rcls.val.right();
5848 auto const it = cinfo->clsConstants.find(cnsName);
5849 if (it != end(cinfo->clsConstants)) {
5850 if (!it->second->val.has_value() ||
5851 it->second->kind == ConstModifiers::Kind::Context ||
5852 (!allow_tconst && it->second->kind == ConstModifiers::Kind::Type)) {
5853 // This is an abstract class constant, context constant or type constant
5854 return nullptr;
5856 if (it->second->val.value().m_type == KindOfUninit) {
5857 // This is a class constant that needs an 86cinit to run.
5858 // We'll add a dependency to make sure we're re-run if it
5859 // resolves anything.
5860 auto const cinit = it->second->cls->methods.back().get();
5861 assertx(cinit->name == s_86cinit.get());
5862 add_dependency(*m_data, cinit, ctx, Dep::ClsConst);
5863 return nullptr;
5865 return it->second.get();
5867 return nullptr;
5870 Type Index::lookup_class_constant(Context ctx,
5871 res::Class rcls,
5872 SString cnsName,
5873 bool allow_tconst) const {
5874 auto const cnst = lookup_class_const_ptr(ctx, rcls, cnsName, allow_tconst);
5875 if (!cnst) return TInitCell;
5876 return from_cell(cnst->val.value());
5879 Type Index::lookup_constant(Context ctx, SString cnsName) const {
5880 auto iter = m_data->constants.find(cnsName);
5881 if (iter == end(m_data->constants)) {
5882 return TInitCell;
5885 auto constant = iter->second;
5886 if (type(constant->val) != KindOfUninit) {
5887 return from_cell(constant->val);
5890 auto const func_name = Constant::funcNameFromName(cnsName);
5891 assertx(func_name && "func_name will never be nullptr");
5893 auto rfunc = resolve_func(ctx, func_name);
5894 return lookup_return_type(ctx, nullptr, rfunc, Dep::ConstVal);
5897 bool Index::func_depends_on_arg(const php::Func* func, int arg) const {
5898 auto const& finfo = *func_info(*m_data, func);
5899 return arg >= finfo.unusedParams.size() || !finfo.unusedParams.test(arg);
5902 Type Index::lookup_foldable_return_type(Context ctx,
5903 const php::Func* func,
5904 Type ctxType,
5905 CompactVector<Type> args) const {
5906 constexpr auto max_interp_nexting_level = 2;
5907 static __thread uint32_t interp_nesting_level;
5908 static __thread Context base_ctx;
5910 // Don't fold functions when staticness mismatches
5911 if ((func->attrs & AttrStatic) && ctxType.couldBe(TObj)) return TInitCell;
5912 if (!(func->attrs & AttrStatic) && ctxType.couldBe(TCls)) return TInitCell;
5914 auto const& finfo = *func_info(*m_data, func);
5915 if (finfo.effectFree && is_scalar(finfo.returnTy)) {
5916 return finfo.returnTy;
5919 auto const calleeCtx = CallContext {
5920 func,
5921 std::move(args),
5922 std::move(ctxType)
5925 auto showArgs DEBUG_ONLY = [] (const CompactVector<Type>& a) {
5926 std::string ret, sep;
5927 for (auto& arg : a) {
5928 folly::format(&ret, "{}{}", sep, show(arg));
5929 sep = ",";
5931 return ret;
5935 ContextRetTyMap::const_accessor acc;
5936 if (m_data->foldableReturnTypeMap.find(acc, calleeCtx)) {
5937 FTRACE_MOD(
5938 Trace::hhbbc, 4,
5939 "Found foldableReturnType for {}{}{} with args {} (hash: {})\n",
5940 func->cls ? func->cls->name : staticEmptyString(),
5941 func->cls ? "::" : "",
5942 func->name,
5943 showArgs(calleeCtx.args),
5944 CallContextHashCompare{}.hash(calleeCtx));
5946 assertx(is_scalar(acc->second));
5947 return acc->second;
5951 if (frozen()) {
5952 FTRACE_MOD(
5953 Trace::hhbbc, 4,
5954 "MISSING: foldableReturnType for {}{}{} with args {} (hash: {})\n",
5955 func->cls ? func->cls->name : staticEmptyString(),
5956 func->cls ? "::" : "",
5957 func->name,
5958 showArgs(calleeCtx.args),
5959 CallContextHashCompare{}.hash(calleeCtx));
5960 return TInitCell;
5963 if (!interp_nesting_level) {
5964 base_ctx = ctx;
5965 } else if (interp_nesting_level > max_interp_nexting_level) {
5966 add_dependency(*m_data, func, base_ctx, Dep::InlineDepthLimit);
5967 return TInitCell;
5970 auto const contextType = [&] {
5971 ++interp_nesting_level;
5972 SCOPE_EXIT { --interp_nesting_level; };
5974 auto const wf = php::WideFunc::cns(func);
5975 auto const fa = analyze_func_inline(
5976 *this,
5977 AnalysisContext { func->unit, wf, func->cls },
5978 calleeCtx.context,
5979 calleeCtx.args,
5980 CollectionOpts::EffectFreeOnly
5982 return fa.effectFree ? fa.inferredReturn : TInitCell;
5983 }();
5985 if (!is_scalar(contextType)) {
5986 return TInitCell;
5989 ContextRetTyMap::accessor acc;
5990 if (m_data->foldableReturnTypeMap.insert(acc, calleeCtx)) {
5991 acc->second = contextType;
5992 } else {
5993 // someone beat us to it
5994 assertx(acc->second == contextType);
5996 return contextType;
5999 Type Index::lookup_return_type(Context ctx,
6000 MethodsInfo* methods,
6001 res::Func rfunc,
6002 Dep dep) const {
6003 return match<Type>(
6004 rfunc.val,
6005 [&] (res::Func::FuncName) { return TInitCell; },
6006 [&] (res::Func::MethodName) { return TInitCell; },
6007 [&] (FuncInfo* finfo) {
6008 add_dependency(*m_data, finfo->func, ctx, dep);
6009 return unctx(finfo->returnTy);
6011 [&] (const MethTabEntryPair* mte) {
6012 if (methods) {
6013 if (auto ret = methods->lookupReturnType(*mte->second.func)) {
6014 return unctx(std::move(*ret));
6017 add_dependency(*m_data, mte->second.func, ctx, dep);
6018 auto const finfo = func_info(*m_data, mte->second.func);
6019 if (!finfo->func) return TInitCell;
6020 return unctx(finfo->returnTy);
6022 [&] (FuncFamily* fam) {
6023 add_dependency(*m_data, fam, ctx, dep);
6024 return fam->m_returnTy.get(
6025 [&] {
6026 auto ret = TBottom;
6027 for (auto const pf : fam->possibleFuncs()) {
6028 auto const finfo = func_info(*m_data, pf->second.func);
6029 if (!finfo->func) return TInitCell;
6030 ret |= unctx(finfo->returnTy);
6031 if (!ret.strictSubtypeOf(BInitCell)) return ret;
6033 return ret;
6040 Type Index::lookup_return_type(Context caller,
6041 MethodsInfo* methods,
6042 const CompactVector<Type>& args,
6043 const Type& context,
6044 res::Func rfunc,
6045 Dep dep) const {
6046 return match<Type>(
6047 rfunc.val,
6048 [&] (res::Func::FuncName) {
6049 return lookup_return_type(caller, methods, rfunc, dep);
6051 [&] (res::Func::MethodName) {
6052 return lookup_return_type(caller, methods, rfunc, dep);
6054 [&] (FuncInfo* finfo) {
6055 add_dependency(*m_data, finfo->func, caller, dep);
6056 return context_sensitive_return_type(
6057 *m_data,
6058 { finfo->func, args, context },
6059 finfo->returnTy
6062 [&] (const MethTabEntryPair* mte) {
6063 auto const finfo = func_info(*m_data, mte->second.func);
6064 if (!finfo->func) return TInitCell;
6066 auto returnType = [&] {
6067 if (methods) {
6068 if (auto ret = methods->lookupReturnType(*mte->second.func)) {
6069 return *ret;
6072 add_dependency(*m_data, mte->second.func, caller, dep);
6073 return finfo->returnTy;
6074 }();
6076 return context_sensitive_return_type(
6077 *m_data,
6078 { finfo->func, args, context },
6079 std::move(returnType)
6082 [&] (FuncFamily* fam) {
6083 add_dependency(*m_data, fam, caller, dep);
6084 auto ret = fam->m_returnTy.get(
6085 [&] {
6086 auto ret = TBottom;
6087 for (auto const pf : fam->possibleFuncs()) {
6088 auto const finfo = func_info(*m_data, pf->second.func);
6089 if (!finfo->func) return TInitCell;
6090 ret |= finfo->returnTy;
6091 if (!ret.strictSubtypeOf(BInitCell)) return ret;
6093 return ret;
6096 return return_with_context(std::move(ret), context);
6101 CompactVector<Type>
6102 Index::lookup_closure_use_vars(const php::Func* func,
6103 bool move) const {
6104 assertx(func->isClosureBody);
6106 auto const numUseVars = closure_num_use_vars(func);
6107 if (!numUseVars) return {};
6108 auto const it = m_data->closureUseVars.find(func->cls);
6109 if (it == end(m_data->closureUseVars)) {
6110 return CompactVector<Type>(numUseVars, TCell);
6112 if (move) return std::move(it->second);
6113 return it->second;
6116 std::pair<Type, size_t> Index::lookup_return_type_raw(const php::Func* f) const {
6117 auto it = func_info(*m_data, f);
6118 if (it->func) {
6119 assertx(it->func == f);
6120 return { it->returnTy, it->returnRefinements };
6122 return { TInitCell, 0 };
6125 bool Index::lookup_this_available(const php::Func* f) const {
6126 return !(f->cls->attrs & AttrTrait) && !(f->attrs & AttrStatic);
6129 Optional<uint32_t> Index::lookup_num_inout_params(
6130 Context,
6131 res::Func rfunc
6132 ) const {
6133 return match<Optional<uint32_t>>(
6134 rfunc.val,
6135 [&] (res::Func::FuncName s) -> Optional<uint32_t> {
6136 if (s.renamable) return std::nullopt;
6137 auto const it = m_data->funcs.find(s.name);
6138 return it != end(m_data->funcs)
6139 ? func_num_inout(it->second)
6140 : 0;
6142 [&] (res::Func::MethodName s) -> Optional<uint32_t> {
6143 auto const it = m_data->method_inout_params_by_name.find(s.name);
6144 if (it == end(m_data->method_inout_params_by_name)) {
6145 // There was no entry, so no method by this name takes a parameter
6146 // by inout.
6147 return 0;
6149 auto const pair = m_data->methods.equal_range(s.name);
6150 return num_inout_from_set(folly::range(pair.first, pair.second));
6152 [&] (FuncInfo* finfo) {
6153 return func_num_inout(finfo->func);
6155 [&] (const MethTabEntryPair* mte) {
6156 return func_num_inout(mte->second.func);
6158 [&] (FuncFamily* fam) -> Optional<uint32_t> {
6159 return fam->m_numInOut;
6164 PrepKind Index::lookup_param_prep(Context /*ctx*/, res::Func rfunc,
6165 uint32_t paramId) const {
6166 return match<PrepKind>(
6167 rfunc.val,
6168 [&] (res::Func::FuncName s) {
6169 if (s.renamable) return PrepKind::Unknown;
6170 auto const it = m_data->funcs.find(s.name);
6171 return it != end(m_data->funcs)
6172 ? func_param_prep(it->second, paramId)
6173 : func_param_prep_default();
6175 [&] (res::Func::MethodName s) {
6176 auto const it = m_data->method_inout_params_by_name.find(s.name);
6177 if (it == end(m_data->method_inout_params_by_name)) {
6178 // There was no entry, so no method by this name takes a parameter
6179 // by inout.
6180 return PrepKind::Val;
6182 if (paramId < sizeof(it->second) * CHAR_BIT &&
6183 !((it->second >> paramId) & 1)) {
6184 // No method of this name takes parameter paramId by inout.
6185 return PrepKind::Val;
6187 auto const pair = m_data->methods.equal_range(s.name);
6188 return prep_kind_from_set(folly::range(pair.first, pair.second), paramId);
6190 [&] (FuncInfo* finfo) {
6191 return func_param_prep(finfo->func, paramId);
6193 [&] (const MethTabEntryPair* mte) {
6194 return func_param_prep(mte->second.func, paramId);
6196 [&] (FuncFamily* fam) {
6197 return prep_kind_from_set(fam->possibleFuncs(), paramId);
6202 PropState
6203 Index::lookup_private_props(const php::Class* cls,
6204 bool move) const {
6205 auto it = m_data->privatePropInfo.find(cls);
6206 if (it != end(m_data->privatePropInfo)) {
6207 if (move) return std::move(it->second);
6208 return it->second;
6210 return make_unknown_propstate(
6211 cls,
6212 [&] (const php::Prop& prop) {
6213 return (prop.attrs & AttrPrivate) && !(prop.attrs & AttrStatic);
6218 PropState
6219 Index::lookup_private_statics(const php::Class* cls,
6220 bool move) const {
6221 auto it = m_data->privateStaticPropInfo.find(cls);
6222 if (it != end(m_data->privateStaticPropInfo)) {
6223 if (move) return std::move(it->second);
6224 return it->second;
6226 return make_unknown_propstate(
6227 cls,
6228 [&] (const php::Prop& prop) {
6229 return (prop.attrs & AttrPrivate) && (prop.attrs & AttrStatic);
6234 PropState Index::lookup_public_statics(const php::Class* cls) const {
6235 auto const cinfo = [&] () -> const ClassInfo* {
6236 auto const it = m_data->classInfo.find(cls->name);
6237 if (it == end(m_data->classInfo)) return nullptr;
6238 return it->second;
6239 }();
6241 PropState state;
6242 for (auto const& prop : cls->properties) {
6243 if (!(prop.attrs & (AttrPublic|AttrProtected)) ||
6244 !(prop.attrs & AttrStatic)) {
6245 continue;
6248 auto [ty, everModified] = [&] {
6249 if (!cinfo) return std::make_pair(TInitCell, true);
6250 auto const it = cinfo->publicStaticProps.find(prop.name);
6251 assertx(it != end(cinfo->publicStaticProps));
6252 return std::make_pair(
6253 remove_uninit(it->second.inferredType),
6254 it->second.everModified
6256 }();
6257 state.emplace(
6258 prop.name,
6259 PropStateElem<>{
6260 std::move(ty),
6261 &prop.typeConstraint,
6262 prop.attrs,
6263 everModified
6267 return state;
6271 * Entry point for static property lookups from the Index. Return
6272 * metadata about a `cls'::`name' static property access in the given
6273 * context.
6275 PropLookupResult<> Index::lookup_static(Context ctx,
6276 const PropertiesInfo& privateProps,
6277 const Type& cls,
6278 const Type& name) const {
6279 ITRACE(4, "lookup_static: {} {}::${}\n", show(ctx), show(cls), show(name));
6280 Trace::Indent _;
6282 // First try to obtain the property name as a static string
6283 auto const sname = [&] () -> SString {
6284 // Treat non-string names conservatively, but the caller should be
6285 // checking this.
6286 if (!name.subtypeOf(BStr)) return nullptr;
6287 auto const vname = tv(name);
6288 if (!vname || vname->m_type != KindOfPersistentString) return nullptr;
6289 return vname->m_data.pstr;
6290 }();
6292 // Conservative result when we can't do any better. The type can be
6293 // anything, and anything might throw.
6294 auto const conservative = [&] {
6295 ITRACE(4, "conservative\n");
6296 return PropLookupResult<>{
6297 TInitCell,
6298 sname,
6299 TriBool::Maybe,
6300 TriBool::Maybe,
6301 TriBool::Maybe,
6302 TriBool::Maybe,
6303 true
6307 // If we don't know what `cls' is, there's not much we can do.
6308 if (!is_specialized_cls(cls)) return conservative();
6310 auto const dcls = dcls_of(cls);
6311 if (dcls.cls.val.left()) return conservative();
6312 auto const cinfo = dcls.cls.val.right();
6314 // Turn the context class into a ClassInfo* for convenience.
6315 const ClassInfo* ctxCls = nullptr;
6316 if (ctx.cls) {
6317 // I don't think this can ever fail (we should always be able to
6318 // resolve the class since we're currently processing it). If it
6319 // does, be conservative.
6320 auto const rCtx = resolve_class(ctx.cls);
6321 if (rCtx.val.left()) return conservative();
6322 ctxCls = rCtx.val.right();
6325 switch (dcls.type) {
6326 case DCls::Sub: {
6327 // We know that `cls' is at least dcls.type, but could be a
6328 // subclass. For every subclass (including dcls.type itself),
6329 // start the property lookup from there, and union together all
6330 // the potential results. This could potentially visit a lot of
6331 // parent classes redundently, so tell it not to look into
6332 // parent classes, unless we're processing dcls.type.
6333 Optional<PropLookupResult<>> result;
6334 for (auto const sub : cinfo->subclassList) {
6335 auto r = lookup_static_impl(
6336 *m_data,
6337 ctx,
6338 ctxCls,
6339 privateProps,
6340 sub,
6341 sname,
6342 !sname && sub != cinfo
6344 ITRACE(4, "{} -> {}\n", sub->cls->name, show(r));
6345 if (!result) {
6346 result.emplace(std::move(r));
6347 } else {
6348 *result |= r;
6351 assertx(result.has_value());
6352 ITRACE(4, "union -> {}\n", show(*result));
6353 return *result;
6355 case DCls::Exact: {
6356 // We know what exactly `cls' is. Just do the property lookup
6357 // starting from there.
6358 auto const r = lookup_static_impl(
6359 *m_data,
6360 ctx,
6361 ctxCls,
6362 privateProps,
6363 cinfo,
6364 sname,
6365 false
6367 ITRACE(4, "{} -> {}\n", cinfo->cls->name, show(r));
6368 return r;
6371 always_assert(false);
6374 Type Index::lookup_public_prop(const Type& cls, const Type& name) const {
6375 if (!is_specialized_cls(cls)) return TCell;
6377 auto const vname = tv(name);
6378 if (!vname || vname->m_type != KindOfPersistentString) return TCell;
6379 auto const sname = vname->m_data.pstr;
6381 auto const dcls = dcls_of(cls);
6382 if (dcls.cls.val.left()) return TCell;
6383 auto const cinfo = dcls.cls.val.right();
6385 switch (dcls.type) {
6386 case DCls::Sub: {
6387 auto ty = TBottom;
6388 for (auto const sub : cinfo->subclassList) {
6389 ty |= lookup_public_prop_impl(
6390 *m_data,
6391 sub,
6392 sname
6395 return ty;
6397 case DCls::Exact:
6398 return lookup_public_prop_impl(
6399 *m_data,
6400 cinfo,
6401 sname
6404 always_assert(false);
6407 Type Index::lookup_public_prop(const php::Class* cls, SString name) const {
6408 auto const it = m_data->classInfo.find(cls->name);
6409 if (it == end(m_data->classInfo)) {
6410 return TCell;
6412 return lookup_public_prop_impl(*m_data, it->second, name);
6415 bool Index::lookup_class_init_might_raise(Context ctx, res::Class cls) const {
6416 return cls.val.match(
6417 [] (SString) { return true; },
6418 [&] (ClassInfo* cinfo) {
6419 return class_init_might_raise(*m_data, ctx, cinfo);
6424 void Index::join_iface_vtable_thread() const {
6425 if (m_data->compute_iface_vtables.joinable()) {
6426 m_data->compute_iface_vtables.join();
6430 Slot
6431 Index::lookup_iface_vtable_slot(const php::Class* cls) const {
6432 return folly::get_default(m_data->ifaceSlotMap, cls, kInvalidSlot);
6435 //////////////////////////////////////////////////////////////////////
6438 * Entry point for static property type mutation from the Index. Merge
6439 * `val' into the known type for any accessible `cls'::`name' static
6440 * property. The mutation will be recovered into either
6441 * `publicMutations' or `privateProps' depending on the properties
6442 * found. Mutations to AttrConst properties are ignored, unless
6443 * `ignoreConst' is true.
6445 PropMergeResult<> Index::merge_static_type(
6446 Context ctx,
6447 PublicSPropMutations& publicMutations,
6448 PropertiesInfo& privateProps,
6449 const Type& cls,
6450 const Type& name,
6451 const Type& val,
6452 bool checkUB,
6453 bool ignoreConst,
6454 bool mustBeReadOnly) const {
6455 ITRACE(
6456 4, "merge_static_type: {} {}::${} {}\n",
6457 show(ctx), show(cls), show(name), show(val)
6459 Trace::Indent _;
6461 assertx(val.subtypeOf(BInitCell));
6463 using R = PropMergeResult<>;
6465 // In some cases we might try to merge Bottom if we're in
6466 // unreachable code. This won't affect anything, so just skip out
6467 // early.
6468 if (val.subtypeOf(BBottom)) return R{ TBottom, TriBool::No };
6470 // Try to turn the given property name into a static string
6471 auto const sname = [&] () -> SString {
6472 // Non-string names are treated conservatively here. The caller
6473 // should be checking for these and doing the right thing.
6474 if (!name.subtypeOf(BStr)) return nullptr;
6475 auto const vname = tv(name);
6476 if (!vname || vname->m_type != KindOfPersistentString) return nullptr;
6477 return vname->m_data.pstr;
6478 }();
6480 // The case where we don't know `cls':
6481 auto const unknownCls = [&] {
6482 if (!sname) {
6483 // Very bad case. We don't know `cls' or the property name. This
6484 // mutation can be affecting anything, so merge it into all
6485 // properties (this drops type information for public
6486 // properties).
6487 ITRACE(4, "unknown class and prop. merging everything\n");
6488 publicMutations.mergeUnknown(ctx);
6489 privateProps.mergeInAllPrivateStatics(
6490 *this, unctx(val), ignoreConst, mustBeReadOnly
6492 } else {
6493 // Otherwise we don't know `cls', but do know the property
6494 // name. We'll store this mutation separately and union it in to
6495 // any lookup with the same name.
6496 ITRACE(4, "unknown class. merging all props with name {}\n", sname);
6498 publicMutations.mergeUnknownClass(sname, unctx(val));
6500 // Assume that it could possibly affect any private property with
6501 // the same name.
6502 privateProps.mergeInPrivateStatic(
6503 *this, sname, unctx(val), ignoreConst, mustBeReadOnly
6507 // To be conservative, say we might throw and be conservative about
6508 // conversions.
6509 return PropMergeResult<>{
6510 loosen_likeness(val),
6511 TriBool::Maybe
6515 // check if we can determine the class.
6516 if (!is_specialized_cls(cls)) return unknownCls();
6518 auto const dcls = dcls_of(cls);
6519 if (dcls.cls.val.left()) return unknownCls();
6520 auto const cinfo = dcls.cls.val.right();
6522 const ClassInfo* ctxCls = nullptr;
6523 if (ctx.cls) {
6524 auto const rCtx = resolve_class(ctx.cls);
6525 // We should only be not able to resolve our own context if the
6526 // class is not instantiable. In that case, the merge can't
6527 // happen.
6528 if (rCtx.val.left()) return R{ TBottom, TriBool::No };
6529 ctxCls = rCtx.val.right();
6532 auto const mergePublic = [&] (const ClassInfo* ci,
6533 const php::Prop& prop,
6534 const Type& val) {
6535 publicMutations.mergeKnown(ci, prop, val);
6538 switch (dcls.type) {
6539 case DCls::Sub: {
6540 // We know this class is either dcls.type, or a child class of
6541 // it. For every child of dcls.type (including dcls.type
6542 // itself), do the merge starting from it. To avoid redundant
6543 // work, only iterate into parent classes if we're dcls.type
6544 // (this is only a matter of efficiency. The merge is
6545 // idiompotent).
6546 Optional<PropMergeResult<>> result;
6547 for (auto const sub : cinfo->subclassList) {
6548 auto r = merge_static_type_impl(
6549 *m_data,
6550 ctx,
6551 mergePublic,
6552 privateProps,
6553 ctxCls,
6554 sub,
6555 sname,
6556 val,
6557 checkUB,
6558 ignoreConst,
6559 mustBeReadOnly,
6560 !sname && sub != cinfo
6562 ITRACE(4, "{} -> {}\n", sub->cls->name, show(r));
6563 if (!result) {
6564 result.emplace(std::move(r));
6565 } else {
6566 *result |= r;
6569 assertx(result.has_value());
6570 ITRACE(4, "union -> {}\n", show(*result));
6571 return *result;
6573 case DCls::Exact: {
6574 // We know the class exactly. Do the merge starting from only
6575 // it.
6576 auto const r = merge_static_type_impl(
6577 *m_data,
6578 ctx,
6579 mergePublic,
6580 privateProps,
6581 ctxCls,
6582 cinfo,
6583 sname,
6584 val,
6585 checkUB,
6586 ignoreConst,
6587 mustBeReadOnly,
6588 false
6590 ITRACE(4, "{} -> {}\n", cinfo->cls->name, show(r));
6591 return r;
6594 always_assert(false);
6597 //////////////////////////////////////////////////////////////////////
6599 DependencyContext Index::dependency_context(const Context& ctx) const {
6600 return dep_context(*m_data, ctx);
6603 void Index::use_class_dependencies(bool f) {
6604 if (f != m_data->useClassDependencies) {
6605 m_data->dependencyMap.clear();
6606 m_data->useClassDependencies = f;
6610 void Index::init_public_static_prop_types() {
6611 trace_time tracer("init public static prop types");
6613 for (auto const& cinfo : m_data->allClassInfos) {
6614 for (auto const& prop : cinfo->cls->properties) {
6615 if (!(prop.attrs & (AttrPublic|AttrProtected)) ||
6616 !(prop.attrs & AttrStatic)) {
6617 continue;
6621 * If the initializer type is TUninit, it means an 86sinit provides the
6622 * actual initialization type or it is AttrLateInit. So we don't want to
6623 * include the Uninit (which isn't really a user-visible type for the
6624 * property) or by the time we union things in we'll have inferred nothing
6625 * much.
6627 auto const initial = [&] {
6628 auto const tyRaw = from_cell(prop.val);
6629 if (tyRaw.subtypeOf(BUninit)) return TBottom;
6630 if (prop.attrs & AttrSystemInitialValue) return tyRaw;
6631 return adjust_type_for_prop(
6632 *this, *cinfo->cls, &prop.typeConstraint, tyRaw
6634 }();
6636 cinfo->publicStaticProps[prop.name] =
6637 PublicSPropEntry {
6638 union_of(
6639 adjust_type_for_prop(
6640 *this,
6641 *cinfo->cls,
6642 &prop.typeConstraint,
6643 TInitCell
6645 initial
6647 initial,
6648 &prop,
6650 false,
6651 true
6657 void Index::refine_class_constants(
6658 const Context& ctx,
6659 const CompactVector<std::pair<size_t, TypedValue>>& resolved,
6660 DependencyContextSet& deps) {
6661 if (!resolved.size()) return;
6662 auto& constants = ctx.func->cls->constants;
6663 for (auto const& c : resolved) {
6664 assertx(c.first < constants.size());
6665 auto& cnst = constants[c.first];
6666 assertx(cnst.val && cnst.val->m_type == KindOfUninit);
6667 cnst.val = c.second;
6669 find_deps(*m_data, ctx.func, Dep::ClsConst, deps);
6672 void Index::refine_constants(const FuncAnalysisResult& fa,
6673 DependencyContextSet& deps) {
6674 auto const& func = fa.ctx.func;
6675 if (func->cls != nullptr) return;
6677 auto const val = tv(fa.inferredReturn);
6678 if (!val) return;
6680 auto const cns_name = Constant::nameFromFuncName(func->name);
6681 if (!cns_name) return;
6683 auto& cs = fa.ctx.unit->constants;
6684 auto it = std::find_if(
6685 cs.begin(),
6686 cs.end(),
6687 [&] (auto const& c) {
6688 return cns_name->same(c->name);
6690 assertx(it != cs.end() && "Did not find constant");
6691 (*it)->val = val.value();
6692 find_deps(*m_data, func, Dep::ConstVal, deps);
6695 void Index::fixup_return_type(const php::Func* func,
6696 Type& retTy) const {
6697 if (func->isGenerator) {
6698 if (func->isAsync) {
6699 // Async generators always return AsyncGenerator object.
6700 retTy = objExact(builtin_class(s_AsyncGenerator.get()));
6701 } else {
6702 // Non-async generators always return Generator object.
6703 retTy = objExact(builtin_class(s_Generator.get()));
6705 } else if (func->isAsync) {
6706 // Async functions always return WaitH<T>, where T is the type returned
6707 // internally.
6708 retTy = wait_handle(*this, std::move(retTy));
6712 void Index::init_return_type(const php::Func* func) {
6713 if ((func->attrs & AttrBuiltin) || func->isMemoizeWrapper) {
6714 return;
6717 auto make_type = [&] (const TypeConstraint& tc) {
6718 if (tc.isSoft() ||
6719 (RuntimeOption::EvalEnforceGenericsUB < 2 && tc.isUpperBound())) {
6720 return TBottom;
6722 auto const cls = func->cls && func->cls->closureContextCls
6723 ? func->cls->closureContextCls
6724 : func->cls;
6725 return lookup_constraint(Context { func->unit, func, cls }, tc);
6728 auto const finfo = create_func_info(*m_data, func);
6730 auto tcT = make_type(func->retTypeConstraint);
6731 if (tcT.is(BBottom)) return;
6733 if (func->hasInOutArgs) {
6734 std::vector<Type> types;
6735 types.emplace_back(intersection_of(TInitCell, std::move(tcT)));
6736 for (auto& p : func->params) {
6737 if (!p.inout) continue;
6738 auto t = make_type(p.typeConstraint);
6739 if (t.is(BBottom)) return;
6740 types.emplace_back(intersection_of(TInitCell, std::move(t)));
6742 tcT = vec(std::move(types));
6745 tcT = loosen_interfaces(loosen_all(to_cell(std::move(tcT))));
6747 FTRACE(4, "Pre-fixup return type for {}{}{}: {}\n",
6748 func->cls ? func->cls->name->data() : "",
6749 func->cls ? "::" : "",
6750 func->name, show(tcT));
6751 fixup_return_type(func, tcT);
6752 FTRACE(3, "Initial return type for {}{}{}: {}\n",
6753 func->cls ? func->cls->name->data() : "",
6754 func->cls ? "::" : "",
6755 func->name, show(tcT));
6756 finfo->returnTy = std::move(tcT);
6759 void Index::refine_return_info(const FuncAnalysisResult& fa,
6760 DependencyContextSet& deps) {
6761 auto const& func = fa.ctx.func;
6762 auto const finfo = create_func_info(*m_data, func);
6763 auto const t = loosen_interfaces(fa.inferredReturn);
6765 auto const error_loc = [&] {
6766 return folly::sformat(
6767 "{} {}{}",
6768 func->unit->filename,
6769 func->cls ?
6770 folly::to<std::string>(func->cls->name->data(), "::") : std::string{},
6771 func->name
6775 auto dep = Dep{};
6776 if (finfo->retParam == NoLocalId && fa.retParam != NoLocalId) {
6777 // This is just a heuristic; it doesn't mean that the value passed
6778 // in was returned, but that the value of the parameter at the
6779 // point of the RetC was returned. We use it to make (heuristic)
6780 // decisions about whether to do inline interps, so we only allow
6781 // it to change once (otherwise later passes might not do the
6782 // inline interp, and get worse results, which could trigger other
6783 // assertions in Index::refine_*).
6784 dep = Dep::ReturnTy;
6785 finfo->retParam = fa.retParam;
6788 auto unusedParams = ~fa.usedParams;
6789 if (finfo->unusedParams != unusedParams) {
6790 dep = Dep::ReturnTy;
6791 always_assert_flog(
6792 (finfo->unusedParams | unusedParams) == unusedParams,
6793 "Index unusedParams decreased in {}.\n",
6794 error_loc()
6796 finfo->unusedParams = unusedParams;
6799 if (t.strictlyMoreRefined(finfo->returnTy)) {
6800 if (finfo->returnRefinements < options.returnTypeRefineLimit) {
6801 finfo->returnTy = t;
6802 // We've modifed the return type, so reset any cached FuncFamily
6803 // return types.
6804 for (auto const ff : finfo->families) ff->m_returnTy.reset();
6805 dep = is_scalar(t) ?
6806 Dep::ReturnTy | Dep::InlineDepthLimit : Dep::ReturnTy;
6807 finfo->returnRefinements += fa.localReturnRefinements + 1;
6808 if (finfo->returnRefinements > options.returnTypeRefineLimit) {
6809 FTRACE(1, "maxed out return type refinements at {}\n", error_loc());
6811 } else {
6812 FTRACE(1, "maxed out return type refinements at {}\n", error_loc());
6814 } else {
6815 always_assert_flog(
6816 more_refined_for_index(t, finfo->returnTy),
6817 "Index return type invariant violated in {}.\n"
6818 " {} is not at least as refined as {}\n",
6819 error_loc(),
6820 show(t),
6821 show(finfo->returnTy)
6825 always_assert_flog(
6826 !finfo->effectFree || fa.effectFree,
6827 "Index effectFree changed from true to false in {} {}{}.\n",
6828 func->unit->filename,
6829 func->cls ? folly::to<std::string>(func->cls->name->data(), "::") :
6830 std::string{},
6831 func->name);
6833 if (finfo->effectFree != fa.effectFree) {
6834 finfo->effectFree = fa.effectFree;
6835 dep = Dep::InlineDepthLimit | Dep::ReturnTy;
6838 if (dep != Dep{}) find_deps(*m_data, func, dep, deps);
6841 bool Index::refine_closure_use_vars(const php::Class* cls,
6842 const CompactVector<Type>& vars) {
6843 assertx(is_closure(*cls));
6845 for (auto i = uint32_t{0}; i < vars.size(); ++i) {
6846 always_assert_flog(
6847 vars[i].equivalentlyRefined(unctx(vars[i])),
6848 "Closure cannot have a used var with a context dependent type"
6852 auto& current = [&] () -> CompactVector<Type>& {
6853 std::lock_guard<std::mutex> _{closure_use_vars_mutex};
6854 return m_data->closureUseVars[cls];
6855 }();
6857 always_assert(current.empty() || current.size() == vars.size());
6858 if (current.empty()) {
6859 current = vars;
6860 return true;
6863 auto changed = false;
6864 for (auto i = uint32_t{0}; i < vars.size(); ++i) {
6865 if (vars[i].strictSubtypeOf(current[i])) {
6866 changed = true;
6867 current[i] = vars[i];
6868 } else {
6869 always_assert_flog(
6870 more_refined_for_index(vars[i], current[i]),
6871 "Index closure_use_var invariant violated in {}.\n"
6872 " {} is not at least as refined as {}\n",
6873 cls->name,
6874 show(vars[i]),
6875 show(current[i])
6880 return changed;
6883 template<class Container>
6884 void refine_private_propstate(Container& cont,
6885 const php::Class* cls,
6886 const PropState& state) {
6887 assertx(!is_used_trait(*cls));
6888 auto* elm = [&] () -> typename Container::value_type* {
6889 std::lock_guard<std::mutex> _{private_propstate_mutex};
6890 auto it = cont.find(cls);
6891 if (it == end(cont)) {
6892 if (!state.empty()) cont[cls] = state;
6893 return nullptr;
6895 return &*it;
6896 }();
6898 if (!elm) return;
6900 for (auto& kv : state) {
6901 auto& target = elm->second[kv.first];
6902 assertx(target.tc == kv.second.tc);
6903 always_assert_flog(
6904 more_refined_for_index(kv.second.ty, target.ty),
6905 "PropState refinement failed on {}::${} -- {} was not a subtype of {}\n",
6906 cls->name->data(),
6907 kv.first->data(),
6908 show(kv.second.ty),
6909 show(target.ty)
6911 target.ty = kv.second.ty;
6913 if (kv.second.everModified) {
6914 always_assert_flog(
6915 target.everModified,
6916 "PropState refinement failed on {}::${} -- "
6917 "everModified flag went from false to true\n",
6918 cls->name->data(),
6919 kv.first->data()
6921 } else {
6922 target.everModified = false;
6927 void Index::refine_private_props(const php::Class* cls,
6928 const PropState& state) {
6929 refine_private_propstate(m_data->privatePropInfo, cls, state);
6932 void Index::refine_private_statics(const php::Class* cls,
6933 const PropState& state) {
6934 // We can't store context dependent types in private statics since they
6935 // could be accessed using different contexts.
6936 auto cleanedState = PropState{};
6937 for (auto const& prop : state) {
6938 auto& elem = cleanedState[prop.first];
6939 elem.ty = unctx(prop.second.ty);
6940 elem.tc = prop.second.tc;
6941 elem.attrs = prop.second.attrs;
6942 elem.everModified = prop.second.everModified;
6945 refine_private_propstate(m_data->privateStaticPropInfo, cls, cleanedState);
6948 void Index::record_public_static_mutations(const php::Func& func,
6949 PublicSPropMutations mutations) {
6950 if (!mutations.m_data) {
6951 m_data->publicSPropMutations.erase(&func);
6952 return;
6954 m_data->publicSPropMutations.insert_or_assign(&func, std::move(mutations));
6957 void Index::update_static_prop_init_val(const php::Class* cls,
6958 SString name) const {
6959 auto const cls_it = m_data->classInfo.find(cls->name);
6960 if (cls_it == end(m_data->classInfo)) {
6961 return;
6963 auto const cinfo = cls_it->second;
6964 if (cinfo->cls != cls) {
6965 return;
6967 auto const it = cinfo->publicStaticProps.find(name);
6968 if (it != cinfo->publicStaticProps.end()) {
6969 it->second.initialValueResolved = true;
6973 void Index::refine_public_statics(DependencyContextSet& deps) {
6974 trace_time update("update public statics");
6976 // Union together the mutations for each function, including the functions
6977 // which weren't analyzed this round.
6978 auto nothing_known = false;
6979 PublicSPropMutations::UnknownMap unknown;
6980 PublicSPropMutations::KnownMap known;
6981 for (auto const& mutations : m_data->publicSPropMutations) {
6982 if (!mutations.second.m_data) continue;
6983 if (mutations.second.m_data->m_nothing_known) {
6984 nothing_known = true;
6985 break;
6988 for (auto const& kv : mutations.second.m_data->m_unknown) {
6989 auto const ret = unknown.insert(kv);
6990 if (!ret.second) ret.first->second |= kv.second;
6992 for (auto const& kv : mutations.second.m_data->m_known) {
6993 auto const ret = known.insert(kv);
6994 if (!ret.second) ret.first->second |= kv.second;
6998 if (nothing_known) {
6999 // We cannot go from knowing the types to not knowing the types (this is
7000 // equivalent to widening the types).
7001 always_assert(!m_data->seenPublicSPropMutations);
7002 return;
7004 m_data->seenPublicSPropMutations = true;
7006 // Refine known class state
7007 for (auto const& cinfo : m_data->allClassInfos) {
7008 for (auto& kv : cinfo->publicStaticProps) {
7009 auto knownClsType = [&] {
7010 auto const it = known.find(
7011 PublicSPropMutations::KnownKey { cinfo.get(), kv.first }
7013 // If we didn't see a mutation, the type is TBottom.
7014 return it == end(known) ? TBottom : it->second;
7015 }();
7017 auto unknownClsType = [&] {
7018 auto const it = unknown.find(kv.first);
7019 // If we didn't see a mutation, the type is TBottom.
7020 return it == end(unknown) ? TBottom : it->second;
7021 }();
7023 // We can't keep context dependent types in public properties.
7024 auto newType = adjust_type_for_prop(
7025 *this,
7026 *cinfo->cls,
7027 &kv.second.prop->typeConstraint,
7028 unctx(union_of(std::move(knownClsType), std::move(unknownClsType)))
7031 if (!newType.is(BBottom)) {
7032 always_assert_flog(
7033 kv.second.everModified,
7034 "Static property index invariant violated on {}::{}:\n"
7035 " everModified flag went from false to true",
7036 cinfo->cls->name->data(),
7037 kv.first->data()
7039 } else {
7040 kv.second.everModified = false;
7043 if (kv.second.initialValueResolved) {
7044 for (auto& prop : cinfo->cls->properties) {
7045 if (prop.name != kv.first) continue;
7046 kv.second.initializerType = from_cell(prop.val);
7047 kv.second.initialValueResolved = false;
7048 break;
7050 assertx(!kv.second.initialValueResolved);
7053 // The type from the indexer doesn't contain the in-class initializer
7054 // types. Add that here.
7055 auto effectiveType =
7056 union_of(std::move(newType), kv.second.initializerType);
7059 * We may only shrink the types we recorded for each property. (If a
7060 * property type ever grows, the interpreter could infer something
7061 * incorrect at some step.)
7063 always_assert_flog(
7064 effectiveType.subtypeOf(kv.second.inferredType),
7065 "Static property index invariant violated on {}::{}:\n"
7066 " {} is not a subtype of {}",
7067 cinfo->cls->name->data(),
7068 kv.first->data(),
7069 show(effectiveType),
7070 show(kv.second.inferredType)
7073 // Put a limit on the refinements to ensure termination. Since we only
7074 // ever refine types, we can stop at any point and still maintain
7075 // correctness.
7076 if (effectiveType.strictSubtypeOf(kv.second.inferredType)) {
7077 if (kv.second.refinements + 1 < options.publicSPropRefineLimit) {
7078 find_deps(*m_data, kv.second.prop, Dep::PublicSProp, deps);
7079 kv.second.inferredType = std::move(effectiveType);
7080 ++kv.second.refinements;
7081 } else {
7082 FTRACE(
7083 1, "maxed out public static property refinements for {}:{}\n",
7084 cinfo->cls->name->data(),
7085 kv.first->data()
7093 void Index::refine_bad_initial_prop_values(const php::Class* cls,
7094 bool value,
7095 DependencyContextSet& deps) {
7096 assertx(!is_used_trait(*cls));
7097 auto const it = m_data->classInfo.find(cls->name);
7098 if (it == end(m_data->classInfo)) {
7099 return;
7101 auto const cinfo = it->second;
7102 if (cinfo->cls != cls) {
7103 return;
7105 always_assert_flog(
7106 cinfo->hasBadInitialPropValues || !value,
7107 "Bad initial prop values going from false to true on {}",
7108 cls->name->data()
7111 if (cinfo->hasBadInitialPropValues && !value) {
7112 cinfo->hasBadInitialPropValues = false;
7113 find_deps(*m_data, cls, Dep::PropBadInitialValues, deps);
7117 bool Index::frozen() const {
7118 return m_data->frozen;
7121 void Index::freeze() {
7122 m_data->frozen = true;
7123 m_data->ever_frozen = true;
7127 * Note that these functions run in separate threads, and
7128 * intentionally don't bump Trace::hhbbc_time. If you want to see
7129 * these times, set TRACE=hhbbc_time:1
7131 #define CLEAR(x) \
7133 trace_time _{"clearing " #x}; \
7134 (x).clear(); \
7137 void Index::cleanup_for_final() {
7138 trace_time _{"cleanup_for_final"};
7139 CLEAR(m_data->dependencyMap);
7143 void Index::cleanup_post_emit(php::ProgramPtr program) {
7144 trace_time _{"cleanup_post_emit"};
7146 trace_time t{"reset allClassInfos"};
7147 parallel::for_each(m_data->allClassInfos, [] (auto& u) { u.reset(); });
7150 trace_time t{"reset funcInfo"};
7151 parallel::for_each(
7152 m_data->funcInfo,
7153 [] (auto& u) {
7154 u.returnTy = TBottom;
7155 u.families.clear();
7160 trace_time t{"reset program"};
7161 parallel::for_each(program->units, [] (auto& u) { u.reset(); });
7163 std::vector<std::function<void()>> clearers;
7164 #define CLEAR_PARALLEL(x) clearers.push_back([&] CLEAR(x));
7165 CLEAR_PARALLEL(m_data->classes);
7166 CLEAR_PARALLEL(m_data->methods);
7167 CLEAR_PARALLEL(m_data->method_inout_params_by_name);
7168 CLEAR_PARALLEL(m_data->funcs);
7169 CLEAR_PARALLEL(m_data->typeAliases);
7170 CLEAR_PARALLEL(m_data->enums);
7171 CLEAR_PARALLEL(m_data->constants);
7172 CLEAR_PARALLEL(m_data->records);
7174 CLEAR_PARALLEL(m_data->classClosureMap);
7175 CLEAR_PARALLEL(m_data->classExtraMethodMap);
7177 CLEAR_PARALLEL(m_data->allClassInfos);
7178 CLEAR_PARALLEL(m_data->classInfo);
7179 CLEAR_PARALLEL(m_data->funcInfo);
7181 CLEAR_PARALLEL(m_data->privatePropInfo);
7182 CLEAR_PARALLEL(m_data->privateStaticPropInfo);
7183 CLEAR_PARALLEL(m_data->publicSPropMutations);
7184 CLEAR_PARALLEL(m_data->funcFamilies);
7185 CLEAR_PARALLEL(m_data->ifaceSlotMap);
7186 CLEAR_PARALLEL(m_data->closureUseVars);
7188 CLEAR_PARALLEL(m_data->foldableReturnTypeMap);
7189 CLEAR_PARALLEL(m_data->contextualReturnTypes);
7191 parallel::for_each(clearers, [] (const std::function<void()>& f) { f(); });
7194 void Index::thaw() {
7195 m_data->frozen = false;
7198 std::unique_ptr<ArrayTypeTable::Builder>& Index::array_table_builder() const {
7199 return m_data->arrTableBuilder;
7202 //////////////////////////////////////////////////////////////////////
7204 res::Func Index::do_resolve(const php::Func* f) const {
7205 auto const finfo = create_func_info(*m_data, f);
7206 return res::Func { this, finfo };
7209 // Return true if we know for sure that one php::Class must derive
7210 // from another at runtime, in all possible instantiations.
7211 bool Index::must_be_derived_from(const php::Class* cls,
7212 const php::Class* parent) const {
7213 if (cls == parent) return true;
7214 auto const clsClass_it = m_data->classInfo.find(cls->name);
7215 auto const parentClass_it = m_data->classInfo.find(parent->name);
7216 if (clsClass_it == end(m_data->classInfo) || parentClass_it == end(m_data->classInfo)) {
7217 return true;
7220 auto const rCls = res::Class { clsClass_it->second };
7221 auto const rPar = res::Class { parentClass_it->second };
7222 return rCls.mustBeSubtypeOf(rPar);
7225 // Return true if any possible definition of one php::Class could
7226 // derive from another at runtime, or vice versa.
7227 bool
7228 Index::could_be_related(const php::Class* cls,
7229 const php::Class* parent) const {
7230 if (cls == parent) return true;
7231 auto const clsClass_it = m_data->classInfo.find(cls->name);
7232 auto const parentClass_it = m_data->classInfo.find(parent->name);
7233 if (clsClass_it == end(m_data->classInfo) || parentClass_it == end(m_data->classInfo)) {
7234 return false;
7237 auto const rCls = res::Class { clsClass_it->second };
7238 auto const rPar = res::Class { parentClass_it->second };
7239 return rCls.couldBe(rPar);
7242 //////////////////////////////////////////////////////////////////////
7244 PublicSPropMutations::Data& PublicSPropMutations::get() {
7245 if (!m_data) m_data = std::make_unique<Data>();
7246 return *m_data;
7249 void PublicSPropMutations::mergeKnown(const ClassInfo* ci,
7250 const php::Prop& prop,
7251 const Type& val) {
7252 ITRACE(4, "PublicSPropMutations::mergeKnown: {} {} {}\n",
7253 ci->cls->name->data(), prop.name, show(val));
7255 auto const res = get().m_known.emplace(
7256 KnownKey { const_cast<ClassInfo*>(ci), prop.name }, val
7258 if (!res.second) res.first->second |= val;
7261 void PublicSPropMutations::mergeUnknownClass(SString prop, const Type& val) {
7262 ITRACE(4, "PublicSPropMutations::mergeUnknownClass: {} {}\n",
7263 prop, show(val));
7265 auto const res = get().m_unknown.emplace(prop, val);
7266 if (!res.second) res.first->second |= val;
7269 void PublicSPropMutations::mergeUnknown(Context ctx) {
7270 ITRACE(4, "PublicSPropMutations::mergeUnknown\n");
7273 * We have a case here where we know neither the class nor the static
7274 * property name. This means we have to pessimize public static property
7275 * types for the entire program.
7277 * We could limit it to pessimizing them by merging the `val' type, but
7278 * instead we just throw everything away---this optimization is not
7279 * expected to be particularly useful on programs that contain any
7280 * instances of this situation.
7282 std::fprintf(
7283 stderr,
7284 "NOTE: had to mark everything unknown for public static "
7285 "property types due to dynamic code. -fanalyze-public-statics "
7286 "will not help for this program.\n"
7287 "NOTE: The offending code occured in this context: %s\n",
7288 show(ctx).c_str()
7290 get().m_nothing_known = true;
7293 //////////////////////////////////////////////////////////////////////