2 +----------------------------------------------------------------------+
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"
25 #include <unordered_map>
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 //////////////////////////////////////////////////////////////////////
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
<
120 template<class T
> using SStringToMany
=
121 std::unordered_multimap
<
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
=
140 template<class T
> using SStringToOneT
=
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
=
162 template<class T
> using SStringToOneFastT
=
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.
193 tbb::concurrent_hash_map
<
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
{
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
;
223 * Entries in the ClassInfo method table need to track some additional
226 * The reason for this is that we need to record attributes of the
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
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;
245 struct res::Func::MethTabEntryPair
:
246 SStringToOneT
<MethTabEntry
>::value_type
{};
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 {
271 size_t hash(const CallContext
& c
) const {
272 auto ret
= folly::hash::hash_combine(
277 for (auto& t
: c
.args
) {
278 ret
= folly::hash::hash_combine(ret
, t
.hash());
284 using ContextRetTyMap
= tbb::concurrent_hash_map
<
287 CallContextHashCompare
290 //////////////////////////////////////////////////////////////////////
292 template<class Filter
>
293 PropState
make_unknown_propstate(const php::Class
* cls
,
295 auto ret
= PropState
{};
296 for (auto& prop
: cls
->properties
) {
298 auto& elem
= ret
[prop
.name
];
300 elem
.tc
= &prop
.typeConstraint
;
301 elem
.attrs
= prop
.attrs
;
302 elem
.everModified
= true;
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
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
;
358 //////////////////////////////////////////////////////////////////////
361 * Known information about a particular constant:
362 * - if system is true, it's a system constant and other definitions
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
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
374 const php::Func
* func
;
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
);
408 FuncFamily(const FuncFamily
&) = delete;
409 FuncFamily
& operator=(const FuncFamily
&) = delete;
411 const PFuncVec
& possibleFuncs() const {
415 friend auto begin(const FuncFamily
& ff
) { return ff
.m_v
.begin(); }
416 friend auto end(const FuncFamily
& ff
) { return ff
.m_v
.end(); }
419 LockFreeLazy
<Type
> m_returnTy
;
420 Optional
<uint32_t> m_numInOut
;
425 struct PFuncVecHasher
{
426 size_t operator()(const FuncFamily::PFuncVec
& v
) const {
427 return folly::hash::hash_range(
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.
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.
461 * A pointer to the underlying php::Class that we're storing
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
;
491 php::Const
operator*() const {
492 return cls
->constants
[idx
];
494 const php::Const
* operator->() const {
497 const php::Const
* get() const {
498 return &cls
->constants
[idx
];
500 const php::Class
* cls
;
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
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
539 * Note that this does not currently encode anything for interface
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
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
560 * Note, unlike baseList, the order of the elements in this vector
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
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
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
;
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.
647 bool derivedHas
{false};
649 MagicFnInfo magicBool
;
652 struct MagicMapInfo
{
654 ClassInfo::MagicFnInfo
ClassInfo::*pmem
;
657 const MagicMapInfo magicMethods
[] {
658 { StaticString
{"__toBoolean"}, &ClassInfo::magicBool
},
660 //////////////////////////////////////////////////////////////////////
663 Record::Record(Either
<SString
, RecordInfo
*> val
) : val(val
) {}
665 bool Record::same(const Record
& o
) const {
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();
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
;
681 return r2
->baseList
[r1
->baseList
.size() - 1] == r1
;
685 SString
Record::name() const {
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();
700 if (r1
->baseList
.size() >= r2
->baseList
.size()) {
701 return r1
->baseList
[r2
->baseList
.size() - 1] == r2
;
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 {
716 [] (SString
) { return true; },
717 [] (RecordInfo
* rinfo
) {
718 return !(rinfo
->rec
->attrs
& AttrFinal
);
723 std::string
show(const Record
& r
) {
725 [] (SString s
) -> std::string
{
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;
748 if (ancestor
== nullptr) {
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 {
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
) {
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
;
799 return c2
->baseList
[c1
->baseList
.size() - 1] == c1
;
803 SString
Class::name() const {
805 [] (SString s
) { return s
; },
806 [] (ClassInfo
* ci
) { return ci
->cls
->name
.get(); }
810 bool Class::couldBeInterface() const {
812 [] (SString
) { return true; },
813 [] (ClassInfo
* cinfo
) {
814 return cinfo
->cls
->attrs
& AttrInterface
;
819 bool Class::mustBeInterface() const {
821 [] (SString
) { return false; },
822 [] (ClassInfo
* cinfo
) {
823 return cinfo
->cls
->attrs
& AttrInterface
;
828 bool Class::couldBeOverriden() const {
830 [] (SString
) { return true; },
831 [] (ClassInfo
* cinfo
) {
832 return !(cinfo
->cls
->attrs
& AttrNoOverride
);
837 bool Class::couldHaveMagicBool() const {
839 [] (SString
) { return true; },
840 [] (ClassInfo
* cinfo
) {
841 return cinfo
->magicBool
.derivedHas
;
846 bool Class::couldHaveMockedDerivedClass() const {
848 [] (SString
) { return true;},
849 [] (ClassInfo
* cinfo
) {
850 return cinfo
->isDerivedMocked
;
855 bool Class::couldBeMocked() const {
857 [] (SString
) { return true;},
858 [] (ClassInfo
* cinfo
) {
859 return cinfo
->isMocked
;
864 bool Class::couldHaveReifiedGenerics() const {
866 [] (SString
) { return true; },
867 [] (ClassInfo
* cinfo
) {
868 return cinfo
->cls
->hasReifiedGenerics
;
873 bool Class::mightCareAboutDynConstructs() const {
874 if (RuntimeOption::EvalForbidDynamicConstructs
> 0) {
876 [] (SString
) { return true; },
877 [] (ClassInfo
* cinfo
) {
878 return !(cinfo
->cls
->attrs
& AttrDynamicallyConstructible
);
885 bool Class::couldHaveConstProp() const {
887 [] (SString
) { return true; },
888 [] (ClassInfo
* cinfo
) { return cinfo
->hasConstProp
; }
892 bool Class::derivedCouldHaveConstProp() const {
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;
914 if (ancestor
== nullptr) {
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
) {
933 [] (SString s
) -> std::string
{
936 [] (ClassInfo
* cinfo
) {
937 return folly::sformat("{}*", cinfo
->cls
->name
);
942 Func::Func(const Index
* idx
, Rep val
)
947 SString
Func::name() const {
948 return match
<SString
>(
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
;
957 for (DEBUG_ONLY
auto const f
: fa
->possibleFuncs()) {
958 assertx(f
->first
->isame(name
));
966 const php::Func
* Func::exactFunc() const {
967 using Ret
= const php::Func
*;
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 {
981 [&](FuncName
) { return false; },
982 [&](MethodName
) { return false; },
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 {
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;
1011 bool Func::mightCareAboutDynCalls() const {
1012 if (RuntimeOption::EvalNoticeOnBuiltinDynamicCalls
&& mightBeBuiltin()) {
1015 auto const mightCareAboutFuncs
=
1016 RuntimeOption::EvalForbidDynamicCallsToFunc
> 0;
1017 auto const mightCareAboutInstMeth
=
1018 RuntimeOption::EvalForbidDynamicCallsToInstMeth
> 0;
1019 auto const mightCareAboutClsMeth
=
1020 RuntimeOption::EvalForbidDynamicCallsToClsMeth
> 0;
1024 [&](FuncName
) { return mightCareAboutFuncs
; },
1026 return mightCareAboutClsMeth
|| mightCareAboutInstMeth
;
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)
1044 bool Func::mightBeBuiltin() const {
1047 // Builtins are always uniquely resolvable unless renaming is
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;
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>(
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
));
1090 uint32_t Func::maxNonVariadicParams() const {
1091 return match
<uint32_t>(
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
) {
1099 for (auto const pf
: fa
->possibleFuncs()) {
1100 c
= std::max(c
, numNVArgs(*pf
->second
.func
));
1107 std::string
show(const Func
& f
) {
1108 auto ret
= f
.name()->toCppString();
1111 [&](Func::FuncName s
) { if (s
.renamable
) ret
+= '?'; },
1112 [&](Func::MethodName
) {},
1113 [&](FuncInfo
*) { ret
+= "*"; },
1114 [&](const MethTabEntryPair
*) { ret
+= "*"; },
1115 [&](FuncFamily
*) { 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
;
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"; }
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;
1152 if (compute_iface_vtables
.joinable()) {
1153 compute_iface_vtables
.join();
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.
1177 CompactVector
<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
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
<
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
>,
1278 FuncFamilyPtrHasher
,
1283 * Map from interfaces to their assigned vtable slots, computed in
1284 * compute_iface_vtables().
1286 IfaceSlotMap ifaceSlotMap
;
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
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;
1328 const typename ResTypeHelper
<res::Class
>::InfoMapT
&
1329 Index::IndexData::infoMap
<res::Class
>() const {
1333 const typename ResTypeHelper
<res::Record
>::InfoMapT
&
1334 Index::IndexData::infoMap
<res::Record
>() const {
1338 //////////////////////////////////////////////////////////////////////
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
,
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
};
1392 assertx(fi
->func
== f
);
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
);
1403 assertx(fi
->func
== f
);
1407 FuncInfo
* func_info(IndexData
& data
, const php::Func
* f
) {
1408 auto const fi
= &data
.funcInfo
[f
->idx
];
1412 template <typename T
>
1413 void find_deps(IndexData
& data
,
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_
)
1453 , modifiers(modifiers_
)
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(); }
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
) {
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) {
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
;
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
;
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
);
1557 // the duplicate methods will be overridden by the class method.
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
,
1577 template<typename T
>
1578 struct PreResolveUpdates
{
1579 TinyVector
<std::unique_ptr
<T
>> newInfos
;
1580 TinyVector
<T
*> updateDeps
;
1583 size_t operator()(const ClassInfo::ConstIndex
& cns
) const {
1584 return hash_int64_pair((uintptr_t)cns
.cls
, cns
.idx
);
1588 bool operator()(const ClassInfo::ConstIndex
& cns1
,
1589 const ClassInfo::ConstIndex
& cns2
) const {
1591 cns1
.cls
== cns2
.cls
&&
1592 cns1
.idx
== cns2
.idx
;
1596 hphp_fast_set
<ClassInfo::ConstIndex
, CnsHash
, CnsEquals
> removeNoOverride
;
1600 hphp_fast_set
<const php::Func
*>
1604 CompactVector
<const php::Class
*>
1606 CompactVector
<const php::Class
*> newClosures
;
1608 std::tuple
<std::unique_ptr
<php::Class
>, php::Unit
*, uint32_t>
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
1619 struct ClonedClosureMap
{
1620 using Tuple
= std::tuple
<const php::Class
*,
1621 std::unique_ptr
<php::Class
>,
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
,
1638 auto const inserted
= map
.emplace(cls
, ordered
.size()).second
;
1639 if (!inserted
) return false;
1640 ordered
.emplace_back(cls
, std::move(clo
), id
);
1644 CompactVector
<Tuple
>::iterator
begin() {
1645 return ordered
.begin();
1647 CompactVector
<Tuple
>::iterator
end() {
1648 return ordered
.end();
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
,
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
) {
1680 cinfo
->preResolveState
->constsFromTraits
.emplace(cns
->name
);
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
) {
1692 "build_class_constants failed for `{}' because `{}' was defined by "
1693 "`{}' as a {} and by `{}' as a {}\n",
1697 ConstModifiers::show(cns
->kind
),
1698 existing
->cls
->name
,
1699 ConstModifiers::show(existing
->kind
)
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
) {
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
) {
1727 "build_class_constants failed for `{}' because "
1728 "`{}' was defined by both `{}' and `{}'\n",
1739 if (!RO::EvalTraitConstantInterfaceBehavior
) {
1740 // Constants from traits silently lose
1742 removeNoOverride(cns
);
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
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
))) {
1759 "build_class_constants failed for `{}' because "
1760 "`{}' was defined by both `{}' and `{}'\n",
1771 removeNoOverride(existing
);
1774 cinfo
->preResolveState
->constsFromTraits
.emplace(cns
->name
);
1776 cinfo
->preResolveState
->constsFromTraits
.erase(cns
->name
);
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
))) {
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;
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;
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;
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
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
;
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
));
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;
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
);
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
,
1938 const ClassInfo
* cls
,
1940 auto res
= m
.emplace(name
, std::make_pair(p
, cls
));
1942 if (add
) cinfo
->traitProps
.emplace_back(p
);
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
))) {
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
);
1964 if (!(prev
.attrs
& AttrPrivate
)) {
1965 if ((prev
.attrs
^ p
.attrs
) & AttrStatic
) {
1967 "build_class_properties failed for `{}' because "
1968 "`{}' was defined both static and non-static\n",
1969 cinfo
->cls
->name
, p
.name
);
1972 if (p
.attrs
& AttrPrivate
) {
1974 "build_class_properties failed for `{}' because "
1975 "`{}' was re-declared private\n",
1976 cinfo
->cls
->name
, p
.name
);
1979 if (p
.attrs
& AttrProtected
&& !(prev
.attrs
& AttrProtected
)) {
1981 "build_class_properties failed for `{}' because "
1982 "`{}' was redeclared protected from public\n",
1983 cinfo
->cls
->name
, p
.name
);
1988 if (add
) cinfo
->traitProps
.emplace_back(p
);
1989 res
.first
->second
= std::make_pair(p
, cls
);
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)) {
2000 for (auto const& p
: src
.pbuildTrait
) {
2001 if (!add(cinfo
->preResolveState
->pbuildTrait
, p
.first
,
2002 p
.second
.first
, p
.second
.second
, false)) {
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)) {
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)) {
2042 for (auto const& p
: trait
->traitProps
) {
2043 if (!add(cinfo
->preResolveState
->pbuildNoTrait
,
2044 p
.name
, p
, cinfo
, 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
,
2069 ClsPreResolveUpdates
& updates
) {
2070 if (cinfo
->cls
->attrs
& AttrInterface
) return true;
2072 auto const methodOverride
= [&] (auto& it
,
2073 const php::Func
* meth
,
2076 if (it
->second
.func
->attrs
& AttrFinal
) {
2077 if (!is_mock_class(cinfo
->cls
)) {
2079 "build_class_methods failed for `{}' because "
2080 "it tried to override final method `{}::{}'\n",
2082 it
->second
.func
->cls
->name
, name
);
2087 " {}: overriding method {}::{} with {}::{}\n",
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
;
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;
2115 " {}: inheriting method {}::{}\n",
2117 rparent
->cls
->name
, mte
.first
);
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(
2128 MethTabEntry
{ m
.get(), m
->attrs
, false, true }
2131 res
.first
->second
.idx
= idx
++;
2133 " {}: adding method {}::{}\n",
2135 cinfo
->cls
->name
, m
->name
);
2138 if (m
->attrs
& AttrTrait
&& m
->attrs
& AttrAbstract
) {
2139 // abstract methods from traits never override anything.
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;
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
) {
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());
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
;
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(
2193 MethTabEntry
{ method
, attrs
, false, true }
2196 res
.first
->second
.idx
= idx
++;
2198 " {}: adding trait method {}::{} as {}\n",
2200 method
->cls
->name
, method
->name
, mdata
.name
);
2202 if (attrs
& AttrAbstract
) continue;
2203 if (res
.first
->second
.func
->cls
== cinfo
->cls
) continue;
2204 if (!methodOverride(res
.first
, method
, attrs
, mdata
.name
)) {
2207 res
.first
->second
.idx
= idx
++;
2209 updates
.extraMethods
[cinfo
->cls
].emplace(method
);
2211 } catch (TMIOps::TMIException
& ex
) {
2213 "build_class_methods failed for `{}' importing traits: {}\n",
2214 cinfo
->cls
->name
, ex
.what());
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;
2240 return in_sealed_whitelist
;
2244 * This function return false if instantiating the cinfo would be a
2247 bool build_cls_info(const php::Program
* program
,
2248 const IndexData
& index
,
2250 ClsPreResolveUpdates
& updates
) {
2251 if (!enforce_in_maybe_sealed_parent_whitelist(cinfo
, cinfo
->parent
)) {
2255 for (auto const iface
: cinfo
->declInterfaces
) {
2256 if (!enforce_in_maybe_sealed_parent_whitelist(cinfo
, iface
)) {
2260 for (auto const trait
: cinfo
->usedTraits
) {
2261 if (!enforce_in_maybe_sealed_parent_whitelist(cinfo
, trait
)) {
2265 for (auto const ienum
: cinfo
->includedEnums
) {
2266 if (!enforce_in_maybe_sealed_parent_whitelist(cinfo
, ienum
)) {
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;
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;
2294 for (auto& p
: func
->params
) count
+= p
.inout
;
2298 template<typename PossibleFuncRange
>
2299 Optional
<uint32_t> num_inout_from_set(PossibleFuncRange range
) {
2300 if (begin(range
) == end(range
)) return 0;
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
;
2318 //////////////////////////////////////////////////////////////////////
2321 struct PhpTypeHelper
;
2324 struct PhpTypeHelper
<php::Class
> {
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
;
2344 struct PhpTypeHelper
<php::Record
> {
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
*>,
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
;
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
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
) {
2393 hphp_hash_set
<const php::Class
*>
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
) {
2416 // It doesn't matter that we lose parameters beyond the 64th,
2417 // for those, we'll conservatively check everything anyway.
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
) {
2443 for (auto i
= unit
.funcs
.begin(); i
!= unit
.funcs
.end();) {
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
);
2450 add_symbol(index
.funcs
, f
.get(), "function");
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
);
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(
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
,
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
;
2514 for (auto& cloneMeth
: clone
->methods
) {
2515 cloneMeth
= clone_meth_helper(unit
,
2517 cls
->methods
[i
++].get(),
2518 std::move(cloneMeth
),
2522 if (!cloneMeth
) return nullptr;
2527 std::unique_ptr
<php::Func
> clone_meth_helper(
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(
2555 newContext
->closureContextCls
?
2556 newContext
->closureContextCls
: newContext
,
2562 if (!cloned
) return false;
2563 clsId
= preResolveUpdates
.nextClassId
++;
2564 always_assert(clonedClosures
.emplace(cls
, std::move(cloned
), clsId
));
2566 clsId
= std::get
<2>(*it
);
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
];
2579 case Op::CreateCl
: {
2580 auto clsId
= bc
.CreateCl
.arg2
;
2581 if (!recordClosure(clsId
)) return nullptr;
2582 updates
[bid
][ix
] = clsId
;
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
;
2601 std::unique_ptr
<php::Func
> clone_meth(php::Unit
* unit
,
2602 php::Class
* newContext
,
2603 const php::Func
* origMeth
,
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
,
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
;
2633 auto merge_one
= [&] (const php::Func
* func
) {
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
);
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
);
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
));
2673 bool merge_xinits(Attr attr
,
2674 std::vector
<std::unique_ptr
<php::Func
>>& clones
,
2677 std::atomic
<uint32_t>& nextFuncId
,
2678 ClsPreResolveUpdates
& updates
,
2679 ClonedClosureMap
& clonedClosures
) {
2680 auto const xinitName
= [&]() {
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);
2689 auto const xinitMatch
= [&](Attr prop_attrs
) {
2690 auto mask
= AttrStatic
| AttrLSB
;
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
);
2713 bool merge_cinits(std::vector
<std::unique_ptr
<php::Func
>>& clones
,
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
);
2729 void rename_closure(const IndexData
& index
,
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
*,
2747 PreResolveUpdates
<typename PhpTypeHelper
<T
>::Info
>&);
2749 void flatten_traits(const php::Program
* program
,
2750 const IndexData
& index
,
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
);
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
);
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
) {
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
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
);
2808 ITRACE(5, "Not flattening {} because {}::{} could not be cloned\n",
2809 cls
->name
, ent
->second
.func
->cls
->name
, ent
->first
);
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",
2832 // flatten initializers for constants in traits
2833 if (cinfo
->traitConsts
.size()) {
2834 if (!merge_cinits(clones
, cls
->unit
, cinfo
, nextFuncId
, updates
,
2836 ITRACE(5, "Not flattening {} because we couldn't merge the 86cinits\n",
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(
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
);
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(
2897 preresolve(program
, index
, closures
.back(), updates
);
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 {
2912 hphp_hash_set
<PreClass::ClassRequirement
, EqHash
, EqHash
> reqs
;
2914 for (auto const t
: cinfo
->usedTraits
) {
2915 for (auto const& req
: t
->cls
->requirements
) {
2917 for (auto const& r
: cls
->requirements
) {
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
>();
2938 if (rec
->parentName
) {
2939 auto const parent
= index
.recordInfo
.at(rec
->parentName
);
2940 if (parent
->rec
->attrs
& AttrFinal
) {
2942 "Resolve combinations failed for `{}' because "
2943 "its parent record `{}' is not abstract\n",
2944 rec
->name
, parent
->rec
->name
);
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
>();
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
)) {
2976 "Resolve combinations failed for `{}' because "
2977 "its parent `{}' is not a class\n",
2978 cls
->name
, cls
->parentName
);
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
)) {
2988 "Resolve combinations failed for `{}' because `{}' "
2989 "is not an interface\n",
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
)) {
3001 "Resolve combinations failed for `{}' because `{}' "
3002 "is not an enum{}\n",
3003 cls
->name
, included_enum_name
,
3004 want_attr
& AttrEnumClass
? " class" : "");
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
)) {
3014 "Resolve combinations failed for `{}' because `{}' "
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
*,
3043 RecPreResolveUpdates
&) {}
3045 void PhpTypeHelper
<php::Class
>::try_flatten_traits(
3046 const php::Program
* program
,
3047 const IndexData
& index
,
3048 const php::Class
* cls
,
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
,
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
;
3071 PhpTypeHelper
<T
>::assert_bases(index
, type
);
3073 return resolve_combinations(program
, index
, type
, updates
);
3076 ITRACE(3, "preresolve: {}:{} ({} resolutions)\n",
3077 type
->name
, (void*)type
, resolved
? 1 : 0);
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
,
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
,
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()) {
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()) {
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
));
3144 std::unique(begin(sub
), 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
);
3161 if (!leafFn
) continue;
3162 funcs
.push_back(leafFn
);
3165 if (funcs
.empty()) return false;
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;
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
)) {
3182 if (b
->first
== name
) return false;
3183 if (a
->first
== name
) return true;
3187 return std::less
<const void*>{}(a
->second
.func
, b
->second
.func
);
3192 begin(funcs
), end(funcs
),
3193 [] (const MethTabEntryPair
* a
, const MethTabEntryPair
* b
) {
3194 return a
->second
.func
== b
->second
.func
;
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]);
3218 // Otherwise re-use an existing identical FuncFamily, or create a
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
)),
3226 ).first
->first
.get();
3229 cinfo
->methodFamilies
.emplace(
3230 std::piecewise_construct
,
3231 std::forward_as_tuple(name
),
3232 std::forward_as_tuple(ff
)
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();
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;
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
) {
3279 if (end
== extras
.begin()) return;
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(
3308 MethTabEntry
{ m
.get(), m
->attrs
, false, true }
3316 void define_func_families(IndexData
& index
) {
3317 trace_time
tracer("define_func_families");
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
3334 if (!mte
->first
->isame(s_construct
.get()) &&
3335 mte
->second
.attrs
& AttrPrivate
) {
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
3356 std::array
<std::mutex
, 256> locks
;
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
);
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
) {
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
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.
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
);
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
;
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
);
3444 folly::format(&out
, "{} interfaces, {} classes\n",
3445 ifaces
.size(), classes
.size());
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
,
3457 folly::get_default(index
.ifaceSlotMap
, iface
));
3459 for (auto conflict
: conflicts
) {
3460 folly::format(&out
, "{}{}", sep
, conflict
->name
);
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
);
3472 for (auto iface
: item
.vtable
) {
3473 folly::format(&out
, "{}{}", sep
, iface
? iface
->name
->data() : "null");
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
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
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);
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");
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.
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.
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
3575 auto const slots
= sort_keys_by_value(
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
) {
3595 for (const auto& mm
: magicMethods
) {
3596 if ((derived
.*mm
.pmem
).thisHas
) {
3597 auto& derivedHas
= (cinfo
.*mm
.pmem
).derivedHas
;
3599 derivedHas
= any
= true;
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
) {
3627 for (const auto& mm
: magicMethods
) {
3628 bool const found
= has_magic_method(cinfo
.get(), mm
.name
.get());
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;
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.
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
);
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;
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
));
3813 std::unique(begin(cpy
), 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
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
) {
3852 for (auto& cinfo
: data
.allClassInfos
) {
3853 check_invariants(cinfo
.get());
3857 //////////////////////////////////////////////////////////////////////
3859 Type
context_sensitive_return_type(IndexData
& data
,
3860 CallContext callCtx
,
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
) {
3888 if (finfo
->retParam
!= NoLocalId
&&
3889 callCtx
.args
.size() > finfo
->retParam
&&
3890 checkParam(finfo
->retParam
)) {
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;
3903 if (!tryContextSensitive
) {
3908 ContextRetTyMap::const_accessor acc
;
3909 if (data
.contextualReturnTypes
.find(acc
, callCtx
)) {
3910 if (data
.frozen
|| acc
->second
== TBottom
|| is_scalar(acc
->second
)) {
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
};
3928 analyze_func_inline(*data
.m_index
, calleeCtx
,
3929 callCtx
.context
, callCtx
.args
).inferredReturn
;
3930 return return_with_context(ty
, callCtx
.context
);
3933 if (!interp_nesting_level
) {
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
)) {
3961 //////////////////////////////////////////////////////////////////////
3963 PrepKind
func_param_prep_default() {
3964 return PrepKind::Val
;
3967 PrepKind
func_param_prep(const php::Func
* func
,
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();
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
;
4003 if (prep
&& *prep
!= PrepKind::Val
) return PrepKind::Unknown
;
4004 prep
= PrepKind::Val
;
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
)) {
4025 Type
lookup_public_prop_impl(
4026 const IndexData
& data
,
4027 const ClassInfo
* cinfo
,
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(
4034 [&] (const ClassInfo
* ci
) -> const php::Prop
* {
4035 for (auto const& prop
: ci
->cls
->properties
) {
4036 if (prop
.name
== propName
) {
4045 if (!prop
) return TCell
;
4046 // Make sure its non-static and public. Otherwise its another function's
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
4056 auto initialTy
= loosen_all(from_cell(prop
->val
));
4057 if (!initialTy
.subtypeOf(TUninit
) && (prop
->attrs
& AttrSystemInitialValue
)) {
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
)) {
4071 // Public is accessible everywhere
4074 // Protected is accessible from both derived classes and parent
4076 return clsCtx
&& (clsCtx
->derivedFrom(*cls
) || cls
->derivedFrom(*clsCtx
));
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
4088 bool class_init_might_raise(IndexData
& data
,
4090 const ClassInfo
* cinfo
) {
4091 // Check this class and all of its parents for possible inequivalent
4092 // redeclarations or bad initial values.
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
);
4101 cinfo
= cinfo
->parent
;
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
4112 PropMergeResult
<> prop_tc_effects(const Index
& index
,
4113 const ClassInfo
* ci
,
4114 const php::Prop
& prop
,
4117 assertx(prop
.typeConstraint
.validForProp());
4119 using R
= PropMergeResult
<>;
4121 // If we're not actually checking property type-hints, everything
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.
4144 RuntimeOption::EvalEnforceGenericsUB
<= 0 ||
4145 result
.throws
== TriBool::Yes
) {
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;
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
,
4176 const ClassInfo
* clsCtx
,
4177 const PropertiesInfo
& privateProps
,
4178 const ClassInfo
* start
,
4182 6, "lookup_static_impl: {} {} {}\n",
4183 clsCtx
? clsCtx
->cls
->name
->toCppString() : std::string
{"-"},
4185 propName
? propName
->toCppString() : std::string
{"*"}
4189 auto const type
= [&] (const php::Prop
& prop
,
4190 const ClassInfo
* ci
) {
4191 switch (prop
.attrs
& (AttrPublic
|AttrProtected
|AttrPrivate
)) {
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
);
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
<>{
4219 yesOrNo(prop
.attrs
& AttrIsConst
),
4220 yesOrNo(prop
.attrs
& AttrIsReadOnly
),
4221 yesOrNo(prop
.attrs
& AttrLateInit
),
4226 auto const notFound
= [&] {
4227 // The property definitely wasn't found.
4228 return PropLookupResult
<>{
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();
4246 [&] (const ClassInfo
* ci
) {
4247 for (auto const& prop
: ci
->cls
->properties
) {
4248 if (!(prop
.attrs
& AttrStatic
) ||
4249 !static_is_accessible(clsCtx
, ci
, prop
)) {
4251 6, "skipping inaccessible {}::${}\n",
4252 ci
->cls
->name
, prop
.name
4256 auto const r
= fromProp(prop
, ci
);
4257 ITRACE(6, "including {}:${} {}\n", ci
->cls
->name
, prop
.name
, show(r
));
4260 // If we're only interested in the starting class, don't walk
4261 // up to the parents.
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(
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
)) {
4281 6, "{}::${} found but inaccessible, stopping\n",
4282 ci
->cls
->name
, propName
4286 // Otherwise its a match
4287 auto const r
= fromProp(prop
, ci
);
4288 ITRACE(6, "found {}:${} {}\n", ci
->cls
->name
, propName
, show(r
));
4291 return std::nullopt
;
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");
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
4320 template <typename F
>
4321 PropMergeResult
<> merge_static_type_impl(IndexData
& data
,
4324 PropertiesInfo
& privateProps
,
4325 const ClassInfo
* clsCtx
,
4326 const ClassInfo
* start
,
4331 bool mustBeReadOnly
,
4334 6, "merge_static_type_impl: {} {} {} {}\n",
4335 clsCtx
? clsCtx
->cls
->name
->toCppString() : std::string
{"-"},
4337 propName
? propName
->toCppString() : std::string
{"*"},
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
) {
4352 6, "tc would throw on {}::${} with {}, skipping\n",
4353 ci
->cls
->name
, prop
.name
, show(val
)
4357 assertx(!effects
.adjusted
.subtypeOf(BBottom
));
4360 6, "merging {} into {}::${}\n",
4361 show(effects
.adjusted
), ci
->cls
->name
, prop
.name
4364 switch (prop
.attrs
& (AttrPublic
|AttrProtected
|AttrPrivate
)) {
4367 mergePublic(ci
, prop
, unctx(effects
.adjusted
));
4370 assertx(clsCtx
== ci
);
4371 privateProps
.mergeInPrivateStaticPreAdjusted(
4373 unctx(effects
.adjusted
)
4378 always_assert(false);
4381 // If we don't find a property, then the mutation will definitely
4383 auto const notFound
= [&] {
4384 return PropMergeResult
<>{
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();
4397 [&] (const ClassInfo
* ci
) {
4398 for (auto const& prop
: ci
->cls
->properties
) {
4399 if (!(prop
.attrs
& AttrStatic
) ||
4400 !static_is_accessible(clsCtx
, ci
, prop
)) {
4402 6, "skipping inaccessible {}::${}\n",
4403 ci
->cls
->name
, prop
.name
4407 if (!ignoreConst
&& (prop
.attrs
& AttrIsConst
)) {
4408 ITRACE(6, "skipping const {}::${}\n", ci
->cls
->name
, prop
.name
);
4411 if (mustBeReadOnly
&& !(prop
.attrs
& AttrIsReadOnly
)) {
4412 ITRACE(6, "skipping mutable property that must be readonly {}::${}\n",
4413 ci
->cls
->name
, prop
.name
);
4416 result
|= merge(prop
, ci
);
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(
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
)) {
4438 6, "{}::${} found but inaccessible, stopping\n",
4439 ci
->cls
->name
, propName
4443 // Mutations to AttrConst properties will fail as well, unless
4444 // it we want to override that behavior.
4445 if (!ignoreConst
&& (prop
.attrs
& AttrIsConst
)) {
4447 6, "{}:${} found but const, stopping\n",
4448 ci
->cls
->name
, propName
4452 if (mustBeReadOnly
&& !(prop
.attrs
& AttrIsReadOnly
)) {
4454 6, "{}:${} found but is mutable and must be readonly, stopping\n",
4455 ci
->cls
->name
, propName
4459 return merge(prop
, ci
);
4461 return std::nullopt
;
4465 ITRACE(6, "nothing found\n");
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
))
4481 //////////////////////////////////////////////////////////////////////
4485 //////////////////////////////////////////////////////////////////////
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
);
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]);
4508 tid
.queue
.push_back(t
);
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
);
4534 auto& depCount
= it
->second
;
4537 tid
.depCounts
.erase(it
);
4538 ITRACE(5, " enqueue: {}:{}\n", tu
->name
, (void*)tu
);
4539 tid
.queue
[tid
.cqBack
++] = tu
;
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
) {
4552 for (auto const& u
: updates
) updatePreResolveDeps(tid
, u
);
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());
4561 index
.allRecordInfos
.emplace_back(std::move(rinfo
));
4568 void commitPreResolveUpdates(IndexData
& index
,
4569 TypeInfoData
<php::Class
>& tid
,
4570 std::vector
<ClsPreResolveUpdates
>& updates
) {
4573 for (auto const& u
: updates
) updatePreResolveDeps(tid
, u
);
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());
4582 index
.allClassInfos
.emplace_back(std::move(cinfo
));
4587 for (auto& u
: updates
) {
4588 for (auto& cns
: u
.removeNoOverride
) {
4589 const_cast<php::Const
*>(cns
.get())->isNoOverride
= false;
4594 for (auto& u
: updates
) {
4595 for (auto const& p
: u
.extraMethods
) {
4596 index
.classExtraMethodMap
[p
.first
].insert(
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());
4612 for (auto& u
: updates
) {
4613 for (auto const c
: u
.newClosures
) index
.classes
.emplace(c
->name
, c
);
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
,
4632 TypeInfoData
<T
>& tid
,
4633 const ISStringToOneT
<TypeInfo
<T
>*>& tmap
) {
4634 auto round
= uint32_t{0};
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
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
);
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;
4662 if (tid
.cqFront
== tid
.cqBack
) break;
4665 auto const workitems
= tid
.cqBack
- tid
.cqFront
;
4666 auto updates
= [&] {
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(
4689 [&] (UnitGroup
& group
) {
4691 Trace::hhbbc_index
, kSystemLibBump
, is_systemlib_part(*group
.first
)
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
4706 updates
.nextClassId
= group
.first
->classes
.size();
4707 for (auto const t
: group
.second
) {
4708 preresolve(program
, index
, t
, updates
);
4716 tid
.cqFront
+= workitems
;
4718 trace_time
trace("update");
4719 commitPreResolveUpdates(index
, tid
, updates
);
4722 trace_time
trace("preresolve clear state");
4724 index
.allClassInfos
,
4725 [&] (const std::unique_ptr
<ClassInfo
>& cinfo
) {
4726 cinfo
->preResolveState
.reset();
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
);
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
);
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
};
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,
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
);
4809 [&] (const php::Func
* f
) { init_return_type(f
); }
4813 // Defined here so IndexData is a complete type for the unique_ptr
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
4830 attribute_setter(prop
.attrs
, true, AttrNoBadRedeclare
);
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();
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
;
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
);
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
;
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;
4882 satisfies_constraint(
4884 lookup_constraint(Context
{}, tc1
),
4886 ) && satisfies_constraint(
4888 lookup_constraint(Context
{}, tc2
),
4892 auto const equiv
= [&] {
4893 if (!equivOneTCPair(prop
->typeConstraint
, pprop
->typeConstraint
)) {
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
)) {
4906 if (!foundEquiv
) return false;
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
);
4921 std::remove_if(props
.begin(), props
.end(), propRedeclares
),
4925 currentCls
= parent
;
4928 auto const possibleOverride
=
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
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
*>;
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
*>;
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;
4999 if (isLeaf
) enqueue(*cinfo
);
5002 WorkList oldWorkList
;
5004 while (!workList
.empty()) {
5006 4, "rewrite_default_initial_values round #{}: {} items\n",
5007 iter
, workList
.size()
5011 std::swap(workList
, oldWorkList
);
5014 for (auto const& cinfo
: oldWorkList
) {
5015 // Retrieve the set of properties which are flowing into this Class and
5017 auto inState
= [&] () -> Optional
<PropSet
> {
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());
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
);
5037 // Ignore properties which have actual user provided initial values or
5039 if (!(prop
.attrs
& AttrSystemInitialValue
) ||
5040 (prop
.attrs
& AttrLateInit
)) {
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
);
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());
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
;
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
);
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");
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
);
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
)) {
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
)) {
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
);
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
>::
5179 to
->second
->phpType()->name
->data());
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
)) {
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
5215 return res::Class
{ cls
->name
.get() };
5218 Optional
<res::Class
> Index::resolve_class(Context ctx
,
5219 SString clsName
) const {
5220 clsName
= normalizeNS(clsName
);
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
>(
5251 [&] (boost::blank
) { return Ret
{}; },
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;
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();
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() };
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.
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
;
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
*>>(
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
*>>(
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.
5398 "A Closure class ({}) failed to resolve",
5402 return { rcls
, cls
};
5405 res::Class
Index::builtin_class(SString name
) const {
5406 auto const rcls
= resolve_class(Context
{}, name
);
5409 rcls
->val
.right() &&
5410 (rcls
->val
.right()->cls
->attrs
& AttrBuiltin
),
5411 "A builtin class ({}) failed to resolve",
5417 res::Func
Index::resolve_method(Context ctx
,
5419 SString name
) const {
5420 auto name_only
= [&] {
5421 return res::Func
{ this, res::Func::MethodName
{ name
} };
5424 if (!is_specialized_cls(clsType
)) {
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
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).
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
) {
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
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
&&
5500 ctx
.cls
!= ftarget
->cls
) {
5501 if (could_be_related(ctx
.cls
, cinfo
->cls
)) {
5502 if (contextMayHavePrivateWithSameName()) {
5508 auto resolve
= [&] {
5509 create_func_info(*m_data
, ftarget
);
5510 return res::Func
{ this, mteFromIt(methIt
) };
5513 switch (dcls
.type
) {
5517 if (methIt
->second
.attrs
& AttrNoOverride
) {
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
};
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
};
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
} };
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
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(),
5602 (tc
.isUpperBound() &&
5603 RuntimeOption::EvalEnforceGenericsUB
== 0)));
5607 * Soft hints (@Foo) are not checked.
5608 * Also upper-bound type hints are not checked when they do not error.
5611 (RuntimeOption::EvalEnforceGenericsUB
< 2 && tc
.isUpperBound())) {
5616 auto const res
= get_type_for_annotated_type(
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) {
5638 auto const res
= get_type_for_annotated_type(
5639 Context
{ nullptr, nullptr, &propCls
},
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
) {
5656 auto mainType
= [&]() -> ConstraintResolution
{
5657 switch (getAnnotMetaType(annot
)) {
5658 case AnnotMetaType::Precise
: {
5659 auto const dt
= getAnnotDataType(annot
);
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
5678 return resolve_named_type(ctx
, name
, candidate
);
5682 case KindOfRClsMeth
:
5684 case KindOfLazyClass
:
5685 always_assert_flog(false, "Unexpected DataType");
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
:
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
));
5706 case AnnotMetaType::Self
:
5707 if (auto s
= selfCls(ctx
)) return subObj(*s
);
5709 case AnnotMetaType::Parent
:
5710 if (auto p
= parentCls(ctx
)) return subObj(*p
);
5712 case AnnotMetaType::Callable
:
5714 case AnnotMetaType::Number
:
5716 case AnnotMetaType::ArrayKey
:
5717 if (candidate
.subtypeOf(BInt
)) return TInt
;
5718 if (candidate
.subtypeOf(BStr
)) return TStr
;
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
;
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 };
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
);
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();
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;
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
*>>(
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();
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.
5800 return match
<Optional
<bool>>(
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
;
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 {
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;
5841 const php::Const
* Index::lookup_class_const_ptr(Context ctx
,
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
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
);
5865 return it
->second
.get();
5870 Type
Index::lookup_class_constant(Context ctx
,
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
)) {
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
,
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
{
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
));
5935 ContextRetTyMap::const_accessor acc
;
5936 if (m_data
->foldableReturnTypeMap
.find(acc
, calleeCtx
)) {
5939 "Found foldableReturnType for {}{}{} with args {} (hash: {})\n",
5940 func
->cls
? func
->cls
->name
: staticEmptyString(),
5941 func
->cls
? "::" : "",
5943 showArgs(calleeCtx
.args
),
5944 CallContextHashCompare
{}.hash(calleeCtx
));
5946 assertx(is_scalar(acc
->second
));
5954 "MISSING: foldableReturnType for {}{}{} with args {} (hash: {})\n",
5955 func
->cls
? func
->cls
->name
: staticEmptyString(),
5956 func
->cls
? "::" : "",
5958 showArgs(calleeCtx
.args
),
5959 CallContextHashCompare
{}.hash(calleeCtx
));
5963 if (!interp_nesting_level
) {
5965 } else if (interp_nesting_level
> max_interp_nexting_level
) {
5966 add_dependency(*m_data
, func
, base_ctx
, Dep::InlineDepthLimit
);
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(
5977 AnalysisContext
{ func
->unit
, wf
, func
->cls
},
5980 CollectionOpts::EffectFreeOnly
5982 return fa
.effectFree
? fa
.inferredReturn
: TInitCell
;
5985 if (!is_scalar(contextType
)) {
5989 ContextRetTyMap::accessor acc
;
5990 if (m_data
->foldableReturnTypeMap
.insert(acc
, calleeCtx
)) {
5991 acc
->second
= contextType
;
5993 // someone beat us to it
5994 assertx(acc
->second
== contextType
);
5999 Type
Index::lookup_return_type(Context ctx
,
6000 MethodsInfo
* methods
,
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
) {
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(
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
;
6040 Type
Index::lookup_return_type(Context caller
,
6041 MethodsInfo
* methods
,
6042 const CompactVector
<Type
>& args
,
6043 const Type
& context
,
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(
6058 { finfo
->func
, args
, context
},
6062 [&] (const MethTabEntryPair
* mte
) {
6063 auto const finfo
= func_info(*m_data
, mte
->second
.func
);
6064 if (!finfo
->func
) return TInitCell
;
6066 auto returnType
= [&] {
6068 if (auto ret
= methods
->lookupReturnType(*mte
->second
.func
)) {
6072 add_dependency(*m_data
, mte
->second
.func
, caller
, dep
);
6073 return finfo
->returnTy
;
6076 return context_sensitive_return_type(
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(
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
;
6096 return return_with_context(std::move(ret
), context
);
6102 Index::lookup_closure_use_vars(const php::Func
* func
,
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
);
6116 std::pair
<Type
, size_t> Index::lookup_return_type_raw(const php::Func
* f
) const {
6117 auto it
= func_info(*m_data
, f
);
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(
6133 return match
<Optional
<uint32_t>>(
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
)
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
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
>(
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
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
);
6203 Index::lookup_private_props(const php::Class
* cls
,
6205 auto it
= m_data
->privatePropInfo
.find(cls
);
6206 if (it
!= end(m_data
->privatePropInfo
)) {
6207 if (move
) return std::move(it
->second
);
6210 return make_unknown_propstate(
6212 [&] (const php::Prop
& prop
) {
6213 return (prop
.attrs
& AttrPrivate
) && !(prop
.attrs
& AttrStatic
);
6219 Index::lookup_private_statics(const php::Class
* cls
,
6221 auto it
= m_data
->privateStaticPropInfo
.find(cls
);
6222 if (it
!= end(m_data
->privateStaticPropInfo
)) {
6223 if (move
) return std::move(it
->second
);
6226 return make_unknown_propstate(
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;
6242 for (auto const& prop
: cls
->properties
) {
6243 if (!(prop
.attrs
& (AttrPublic
|AttrProtected
)) ||
6244 !(prop
.attrs
& AttrStatic
)) {
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
6261 &prop
.typeConstraint
,
6271 * Entry point for static property lookups from the Index. Return
6272 * metadata about a `cls'::`name' static property access in the given
6275 PropLookupResult
<> Index::lookup_static(Context ctx
,
6276 const PropertiesInfo
& privateProps
,
6278 const Type
& name
) const {
6279 ITRACE(4, "lookup_static: {} {}::${}\n", show(ctx
), show(cls
), show(name
));
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
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
;
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
<>{
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;
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
) {
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(
6342 !sname
&& sub
!= cinfo
6344 ITRACE(4, "{} -> {}\n", sub
->cls
->name
, show(r
));
6346 result
.emplace(std::move(r
));
6351 assertx(result
.has_value());
6352 ITRACE(4, "union -> {}\n", show(*result
));
6356 // We know what exactly `cls' is. Just do the property lookup
6357 // starting from there.
6358 auto const r
= lookup_static_impl(
6367 ITRACE(4, "{} -> {}\n", cinfo
->cls
->name
, show(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
) {
6388 for (auto const sub
: cinfo
->subclassList
) {
6389 ty
|= lookup_public_prop_impl(
6398 return lookup_public_prop_impl(
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
)) {
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();
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(
6447 PublicSPropMutations
& publicMutations
,
6448 PropertiesInfo
& privateProps
,
6454 bool mustBeReadOnly
) const {
6456 4, "merge_static_type: {} {}::${} {}\n",
6457 show(ctx
), show(cls
), show(name
), show(val
)
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
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
;
6480 // The case where we don't know `cls':
6481 auto const unknownCls
= [&] {
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
6487 ITRACE(4, "unknown class and prop. merging everything\n");
6488 publicMutations
.mergeUnknown(ctx
);
6489 privateProps
.mergeInAllPrivateStatics(
6490 *this, unctx(val
), ignoreConst
, mustBeReadOnly
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
6502 privateProps
.mergeInPrivateStatic(
6503 *this, sname
, unctx(val
), ignoreConst
, mustBeReadOnly
6507 // To be conservative, say we might throw and be conservative about
6509 return PropMergeResult
<>{
6510 loosen_likeness(val
),
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;
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
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
,
6535 publicMutations
.mergeKnown(ci
, prop
, val
);
6538 switch (dcls
.type
) {
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
6546 Optional
<PropMergeResult
<>> result
;
6547 for (auto const sub
: cinfo
->subclassList
) {
6548 auto r
= merge_static_type_impl(
6560 !sname
&& sub
!= cinfo
6562 ITRACE(4, "{} -> {}\n", sub
->cls
->name
, show(r
));
6564 result
.emplace(std::move(r
));
6569 assertx(result
.has_value());
6570 ITRACE(4, "union -> {}\n", show(*result
));
6574 // We know the class exactly. Do the merge starting from only
6576 auto const r
= merge_static_type_impl(
6590 ITRACE(4, "{} -> {}\n", cinfo
->cls
->name
, show(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
)) {
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
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
6636 cinfo
->publicStaticProps
[prop
.name
] =
6639 adjust_type_for_prop(
6642 &prop
.typeConstraint
,
6657 void Index::refine_class_constants(
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
);
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(
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()));
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
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
) {
6717 auto make_type
= [&] (const TypeConstraint
& tc
) {
6719 (RuntimeOption::EvalEnforceGenericsUB
< 2 && tc
.isUpperBound())) {
6722 auto const cls
= func
->cls
&& func
->cls
->closureContextCls
6723 ? func
->cls
->closureContextCls
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(
6768 func
->unit
->filename
,
6770 folly::to
<std::string
>(func
->cls
->name
->data(), "::") : std::string
{},
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
;
6792 (finfo
->unusedParams
| unusedParams
) == unusedParams
,
6793 "Index unusedParams decreased in {}.\n",
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
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());
6812 FTRACE(1, "maxed out return type refinements at {}\n", error_loc());
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",
6821 show(finfo
->returnTy
)
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(), "::") :
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
) {
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
];
6857 always_assert(current
.empty() || current
.size() == vars
.size());
6858 if (current
.empty()) {
6863 auto changed
= false;
6864 for (auto i
= uint32_t{0}; i
< vars
.size(); ++i
) {
6865 if (vars
[i
].strictSubtypeOf(current
[i
])) {
6867 current
[i
] = vars
[i
];
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",
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
;
6900 for (auto& kv
: state
) {
6901 auto& target
= elm
->second
[kv
.first
];
6902 assertx(target
.tc
== kv
.second
.tc
);
6904 more_refined_for_index(kv
.second
.ty
, target
.ty
),
6905 "PropState refinement failed on {}::${} -- {} was not a subtype of {}\n",
6911 target
.ty
= kv
.second
.ty
;
6913 if (kv
.second
.everModified
) {
6915 target
.everModified
,
6916 "PropState refinement failed on {}::${} -- "
6917 "everModified flag went from false to true\n",
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
);
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
)) {
6963 auto const cinfo
= cls_it
->second
;
6964 if (cinfo
->cls
!= cls
) {
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;
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
);
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
;
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
;
7023 // We can't keep context dependent types in public properties.
7024 auto newType
= adjust_type_for_prop(
7027 &kv
.second
.prop
->typeConstraint
,
7028 unctx(union_of(std::move(knownClsType
), std::move(unknownClsType
)))
7031 if (!newType
.is(BBottom
)) {
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(),
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;
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.)
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(),
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
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
;
7083 1, "maxed out public static property refinements for {}:{}\n",
7084 cinfo
->cls
->name
->data(),
7093 void Index::refine_bad_initial_prop_values(const php::Class
* cls
,
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
)) {
7101 auto const cinfo
= it
->second
;
7102 if (cinfo
->cls
!= cls
) {
7106 cinfo
->hasBadInitialPropValues
|| !value
,
7107 "Bad initial prop values going from false to true on {}",
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
7133 trace_time _{"clearing " #x}; \
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"};
7154 u
.returnTy
= TBottom
;
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
)) {
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.
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
)) {
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
>();
7249 void PublicSPropMutations::mergeKnown(const ClassInfo
* ci
,
7250 const php::Prop
& prop
,
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",
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.
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",
7290 get().m_nothing_known
= true;
7293 //////////////////////////////////////////////////////////////////////