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