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>
26 #include <unordered_set>
30 #include <boost/dynamic_bitset.hpp>
32 #include <tbb/concurrent_hash_map.h>
33 #include <tbb/concurrent_unordered_map.h>
35 #include <folly/Format.h>
36 #include <folly/Hash.h>
37 #include <folly/Lazy.h>
38 #include <folly/MapUtil.h>
39 #include <folly/Memory.h>
40 #include <folly/Optional.h>
41 #include <folly/Range.h>
42 #include <folly/String.h>
43 #include <folly/concurrency/ConcurrentHashMap.h>
45 #include "hphp/runtime/base/runtime-option.h"
46 #include "hphp/runtime/base/tv-comparisons.h"
48 #include "hphp/runtime/vm/native.h"
49 #include "hphp/runtime/vm/preclass-emitter.h"
50 #include "hphp/runtime/vm/runtime.h"
51 #include "hphp/runtime/vm/trait-method-import-data.h"
52 #include "hphp/runtime/vm/unit-util.h"
54 #include "hphp/hhbbc/type-builtins.h"
55 #include "hphp/hhbbc/type-system.h"
56 #include "hphp/hhbbc/representation.h"
57 #include "hphp/hhbbc/unit-util.h"
58 #include "hphp/hhbbc/class-util.h"
59 #include "hphp/hhbbc/context.h"
60 #include "hphp/hhbbc/func-util.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/match.h"
69 namespace HPHP
{ namespace HHBBC
{
71 TRACE_SET_MOD(hhbbc_index
);
73 //////////////////////////////////////////////////////////////////////
77 //////////////////////////////////////////////////////////////////////
79 const StaticString
s_construct("__construct");
80 const StaticString
s_call("__call");
81 const StaticString
s_get("__get");
82 const StaticString
s_set("__set");
83 const StaticString
s_isset("__isset");
84 const StaticString
s_unset("__unset");
85 const StaticString
s_toBoolean("__toBoolean");
86 const StaticString
s_invoke("__invoke");
87 const StaticString
s_Closure("Closure");
88 const StaticString
s_AsyncGenerator("HH\\AsyncGenerator");
89 const StaticString
s_Generator("Generator");
91 //////////////////////////////////////////////////////////////////////
94 * One-to-many case insensitive map, where the keys are static strings
95 * and the values are some kind of pointer.
97 template<class T
> using ISStringToMany
=
98 std::unordered_multimap
<
106 * One-to-one case insensitive map, where the keys are static strings
107 * and the values are some T.
109 template<class T
> using ISStringToOneT
=
118 * One-to-one case insensitive map, where the keys are static strings
119 * and the values are some T.
121 * Elements are not stable under insert/erase.
123 template<class T
> using ISStringToOneFastT
=
132 * One-to-one case insensitive map, where the keys are static strings
133 * and the values are some kind of pointer.
135 template<class T
> using ISStringToOne
= ISStringToOneT
<T
*>;
137 template<class MultiMap
>
138 folly::Range
<typename
MultiMap::const_iterator
>
139 find_range(const MultiMap
& map
, typename
MultiMap::key_type key
) {
140 auto const pair
= map
.equal_range(key
);
141 return folly::range(pair
.first
, pair
.second
);
144 // Like find_range, but copy them into a temporary buffer instead of
145 // returning iterators, so you can still mutate the underlying
147 template<class MultiMap
>
148 std::vector
<typename
MultiMap::value_type
>
149 copy_range(const MultiMap
& map
, typename
MultiMap::key_type key
) {
150 auto range
= find_range(map
, key
);
151 return std::vector
<typename
MultiMap::value_type
>(begin(range
), end(range
));
154 //////////////////////////////////////////////////////////////////////
156 enum class Dep
: uintptr_t {
157 /* This dependency should trigger when the return type changes */
158 ReturnTy
= (1u << 0),
159 /* This dependency should trigger when a DefCns is resolved */
160 ConstVal
= (1u << 1),
161 /* This dependency should trigger when a class constant is resolved */
162 ClsConst
= (1u << 2),
163 /* This dependency should trigger when the bad initial prop value bit for a
165 PropBadInitialValues
= (1u << 3),
166 /* This dependency should trigger when a public static property with a
167 * particular name changes */
168 PublicSPropName
= (1u << 4),
169 /* This dependency means that we refused to do inline analysis on
170 * this function due to inline analysis depth. The dependency will
171 * trigger if the target function becomes effect-free, or gets a
172 * literal return value.
174 InlineDepthLimit
= (1u << 5),
177 Dep
operator|(Dep a
, Dep b
) {
178 return static_cast<Dep
>(
179 static_cast<uintptr_t>(a
) | static_cast<uintptr_t>(b
)
183 bool has_dep(Dep m
, Dep t
) {
184 return static_cast<uintptr_t>(m
) & static_cast<uintptr_t>(t
);
188 * Maps functions to contexts that depend on information about that
189 * function, with information about the type of dependency.
192 tbb::concurrent_hash_map
<
194 std::map
<DependencyContext
,Dep
,DependencyContextLess
>,
195 DependencyContextHashCompare
198 //////////////////////////////////////////////////////////////////////
201 * Each ClassInfo has a table of public static properties with these entries.
202 * The `initializerType' is for use during refine_public_statics, and
203 * inferredType will always be a supertype of initializerType.
205 struct PublicSPropEntry
{
207 Type initializerType
;
208 const TypeConstraint
* tc
;
209 uint32_t refinements
;
212 * This flag is set during analysis to indicate that we resolved the
213 * intial value (and updated it on the php::Class). This doesn't
214 * need to be atomic, because only one thread can resolve the value
215 * (the one processing the 86sinit), and it's been joined by the
216 * time we read the flag in refine_public_statics.
218 bool initialValueResolved
;
222 * Entries in the ClassInfo method table need to track some additional
225 * The reason for this is that we need to record attributes of the
228 struct MethTabEntry
{
229 MethTabEntry(const php::Func
* func
, Attr a
, bool hpa
, bool tl
) :
230 func(func
), attrs(a
), hasPrivateAncestor(hpa
), topLevel(tl
) {}
231 const php::Func
* func
= nullptr;
232 // A method could be imported from a trait, and its attributes changed
234 bool hasAncestor
= false;
235 bool hasPrivateAncestor
= false;
236 // This method came from the ClassInfo that owns the MethTabEntry,
237 // or one of its used traits.
238 bool topLevel
= false;
244 struct res::Func::MethTabEntryPair
:
245 ISStringToOneT
<MethTabEntry
>::value_type
{};
249 using MethTabEntryPair
= res::Func::MethTabEntryPair
;
251 inline MethTabEntryPair
* mteFromElm(
252 ISStringToOneT
<MethTabEntry
>::value_type
& elm
) {
253 return static_cast<MethTabEntryPair
*>(&elm
);
256 inline const MethTabEntryPair
* mteFromElm(
257 const ISStringToOneT
<MethTabEntry
>::value_type
& elm
) {
258 return static_cast<const MethTabEntryPair
*>(&elm
);
261 inline MethTabEntryPair
* mteFromIt(ISStringToOneT
<MethTabEntry
>::iterator it
) {
262 return static_cast<MethTabEntryPair
*>(&*it
);
265 struct CallContextHashCompare
{
266 bool equal(const CallContext
& a
, const CallContext
& b
) const {
270 size_t hash(const CallContext
& c
) const {
271 auto ret
= folly::hash::hash_combine(
276 for (auto& t
: c
.args
) {
277 ret
= folly::hash::hash_combine(ret
, t
.hash());
283 using ContextRetTyMap
= tbb::concurrent_hash_map
<
286 CallContextHashCompare
289 //////////////////////////////////////////////////////////////////////
291 template<class Filter
>
292 PropState
make_unknown_propstate(const php::Class
* cls
,
294 auto ret
= PropState
{};
295 for (auto& prop
: cls
->properties
) {
297 ret
[prop
.name
].ty
= TGen
;
306 * Currently inferred information about a PHP function.
308 * Nothing in this structure can ever be untrue. The way the
309 * algorithm works, whatever is in here must be factual (even if it is
310 * not complete information), because we may deduce other facts based
313 struct res::Func::FuncInfo
{
314 const php::Func
* func
= nullptr;
316 * The best-known return type of the function, if we have any
317 * information. May be TBottom if the function is known to never
318 * return (e.g. always throws).
320 Type returnTy
= TInitCell
;
323 * If the function always returns the same parameter, this will be
324 * set to its id; otherwise it will be NoLocalId.
326 LocalId retParam
{NoLocalId
};
329 * The number of times we've refined returnTy.
331 uint32_t returnRefinments
{0};
334 * Whether the function is effectFree.
336 bool effectFree
{false};
339 * Bitset representing which parameters definitely don't affect the
340 * result of the function, assuming it produces one. Note that
341 * VerifyParamType does not count as a use in this context.
343 std::bitset
<64> unusedParams
;
348 //////////////////////////////////////////////////////////////////////
351 * Known information about a particular constant:
352 * - if system is true, it's a system constant and other definitions
354 * - for non-system constants, if func is non-null it's the unique
355 * pseudomain defining the constant; otherwise there was more than
356 * one definition, or a non-pseudomain definition, and the type will
358 * - readonly is true if we've only seen uses of the constant, and no
359 * definitions (this could change during the first pass, but not after
364 const php::Func
* func
;
370 using FuncFamily
= res::Func::FuncFamily
;
371 using FuncInfo
= res::Func::FuncInfo
;
372 using MethTabEntryPair
= res::Func::MethTabEntryPair
;
374 //////////////////////////////////////////////////////////////////////
378 //////////////////////////////////////////////////////////////////////
381 * Sometimes function resolution can't determine which function
382 * something will call, but can restrict it to a family of functions.
384 * For example, if you want to call an abstract function on a base
385 * class with all unique derived classes, we will resolve the function
386 * to a FuncFamily that contains references to all the possible
387 * overriding-functions.
389 * Carefully pack it into 8 bytes, so that hphp_fast_map will use
392 struct res::Func::FuncFamily
{
393 using PFuncVec
= CompactVector
<const MethTabEntryPair
*>;
394 static_assert(sizeof(PFuncVec
) == sizeof(uintptr_t),
395 "CompactVector must be layout compatible with a pointer");
398 Holder(const Holder
& o
) : bits
{o
.bits
} {}
399 explicit Holder(PFuncVec
&& o
) : v
{std::move(o
)} {}
400 explicit Holder(uintptr_t b
) : bits
{b
& ~1} {}
401 Holder
& operator=(const Holder
&) = delete;
403 const PFuncVec
* operator->() const { return &v
; }
404 uintptr_t val() const { return bits
; }
405 friend auto begin(const Holder
& h
) { return h
->begin(); }
406 friend auto end(const Holder
& h
) { return h
->end(); }
414 FuncFamily(PFuncVec
&& v
, bool containsInterceptables
)
415 : m_raw
{Holder
{std::move(v
)}.val()} {
416 if (containsInterceptables
) m_raw
|= 1;
418 FuncFamily(FuncFamily
&& o
) noexcept
: m_raw(o
.m_raw
) {
422 Holder
{m_raw
& ~1}->~PFuncVec();
424 FuncFamily
& operator=(const FuncFamily
&) = delete;
426 bool containsInterceptables() const { return m_raw
& 1; };
427 const Holder
possibleFuncs() const {
428 return Holder
{m_raw
& ~1};
434 //////////////////////////////////////////////////////////////////////
437 * Known information about a particular possible instantiation of a
438 * PHP class. The php::Class will be marked AttrUnique if there is a
439 * unique ClassInfo with the same name, and no interfering class_aliases.
443 * A pointer to the underlying php::Class that we're storing
446 const php::Class
* cls
= nullptr;
449 * The info for the parent of this Class.
451 ClassInfo
* parent
= nullptr;
454 * A vector of the declared interfaces class info structures. This is in
455 * declaration order mirroring the php::Class interfaceNames vector, and does
456 * not include inherited interfaces.
458 CompactVector
<const ClassInfo
*> declInterfaces
;
461 * A (case-insensitive) map from interface names supported by this class to
462 * their ClassInfo structures, flattened across the hierarchy.
464 ISStringToOneT
<const ClassInfo
*> implInterfaces
;
467 * A (case-sensitive) map from class constant name to the php::Const
468 * that it came from. This map is flattened across the inheritance
471 hphp_fast_map
<SString
,const php::Const
*> clsConstants
;
474 * A vector of the used traits, in class order, mirroring the
475 * php::Class usedTraitNames vector.
477 CompactVector
<const ClassInfo
*> usedTraits
;
480 * A list of extra properties supplied by this class's used traits.
482 CompactVector
<php::Prop
> traitProps
;
485 * A (case-insensitive) map from class method names to the php::Func
486 * associated with it. This map is flattened across the inheritance
489 ISStringToOneT
<MethTabEntry
> methods
;
492 * A (case-insensitive) map from class method names to associated
493 * FuncFamily objects that group the set of possibly-overriding
496 * Note that this does not currently encode anything for interface
499 * Invariant: methods on this class with AttrNoOverride or
500 * AttrPrivate will not have an entry in this map.
502 ISStringToOneFastT
<FuncFamily
> methodFamilies
;
505 * Subclasses of this class, including this class itself.
507 * For interfaces, this is the list of instantiable classes that
508 * implement this interface.
510 * For traits, this is the list of classes that use the trait where
511 * the trait wasn't flattened into the class (including the trait
514 * Note, unlike baseList, the order of the elements in this vector
517 CompactVector
<ClassInfo
*> subclassList
;
520 * A vector of ClassInfo that encodes the inheritance hierarchy,
521 * unless this ClassInfo represents an interface.
523 * This is the list of base classes for this class in inheritance
526 CompactVector
<ClassInfo
*> baseList
;
529 * Property types for public static properties, declared on this exact class
530 * (i.e. not flattened in the hierarchy).
532 * These maps always have an entry for each public static property declared
533 * in this class, so it can also be used to check if this class declares a
534 * public static property of a given name.
536 * Note: the effective type we can assume a given static property may hold is
537 * not just the value in these maps. To handle mutations of public statics
538 * where the name is known, but not which class was affected, these always
539 * need to be unioned with values from IndexData::unknownClassSProps.
541 hphp_hash_map
<SString
,PublicSPropEntry
> publicStaticProps
;
544 * Flags to track if this class is mocked, or if any of its dervied classes
547 bool isMocked
{false};
548 bool isDerivedMocked
{false};
551 * Track if this class has a property which might redeclare a property in a
552 * parent class with an inequivalent type-hint.
554 bool hasBadRedeclareProp
{true};
557 * Track if this class has any properties with initial values that might
558 * violate their type-hints.
560 bool hasBadInitialPropValues
{true};
563 * Flags about the existence of various magic methods, or whether
564 * any derived classes may have those methods. The non-derived
565 * flags imply the derived flags, even if the class is final, so you
566 * don't need to check both in those situations.
570 bool derivedHas
{false};
581 using MagicMapInfo
= struct {
582 ClassInfo::MagicFnInfo
ClassInfo::*pmem
;
586 const std::vector
<std::pair
<SString
,MagicMapInfo
>> magicMethodMap
{
587 { s_call
.get(), { &ClassInfo::magicCall
, AttrNone
} },
588 { s_toBoolean
.get(), { &ClassInfo::magicBool
, AttrNone
} },
589 { s_get
.get(), { &ClassInfo::magicGet
, AttrNoOverrideMagicGet
} },
590 { s_set
.get(), { &ClassInfo::magicSet
, AttrNoOverrideMagicSet
} },
591 { s_isset
.get(), { &ClassInfo::magicIsset
, AttrNoOverrideMagicIsset
} },
592 { s_unset
.get(), { &ClassInfo::magicUnset
, AttrNoOverrideMagicUnset
} }
595 //////////////////////////////////////////////////////////////////////
599 Class::Class(const Index
* idx
,
600 Either
<SString
,ClassInfo
*> val
)
605 // Class type operations here are very conservative for now.
607 bool Class::same(const Class
& o
) const {
611 template <bool returnTrueOnMaybe
>
612 bool Class::subtypeOfImpl(const Class
& o
) const {
613 auto s1
= val
.left();
614 auto s2
= o
.val
.left();
615 if (s1
|| s2
) return returnTrueOnMaybe
|| s1
== s2
;
616 auto c1
= val
.right();
617 auto c2
= o
.val
.right();
619 // If c2 is an interface, see if c1 declared it.
620 if (c2
->cls
->attrs
& AttrInterface
) {
621 if (c1
->implInterfaces
.count(c2
->cls
->name
)) {
627 // Otherwise check for direct inheritance.
628 if (c1
->baseList
.size() >= c2
->baseList
.size()) {
629 return c1
->baseList
[c2
->baseList
.size() - 1] == c2
;
634 bool Class::mustBeSubtypeOf(const Class
& o
) const {
635 return subtypeOfImpl
<false>(o
);
638 bool Class::maybeSubtypeOf(const Class
& o
) const {
639 return subtypeOfImpl
<true>(o
);
642 bool Class::couldBe(const Class
& o
) const {
643 // If either types are not unique return true
644 if (val
.left() || o
.val
.left()) return true;
646 auto c1
= val
.right();
647 auto c2
= o
.val
.right();
648 // if one or the other is an interface return true for now.
649 // TODO(#3621433): better interface stuff
650 if (c1
->cls
->attrs
& AttrInterface
|| c2
->cls
->attrs
& AttrInterface
) {
654 // Both types are unique classes so they "could be" if they are in an
655 // inheritance relationship
656 if (c1
->baseList
.size() >= c2
->baseList
.size()) {
657 return c1
->baseList
[c2
->baseList
.size() - 1] == c2
;
659 return c2
->baseList
[c1
->baseList
.size() - 1] == c1
;
663 SString
Class::name() const {
665 [] (SString s
) { return s
; },
666 [] (ClassInfo
* ci
) { return ci
->cls
->name
.get(); }
670 bool Class::couldBeInterfaceOrTrait() const {
672 [] (SString
) { return true; },
673 [] (ClassInfo
* cinfo
) {
674 return (cinfo
->cls
->attrs
& (AttrInterface
| AttrTrait
));
679 bool Class::couldBeInterface() const {
681 [] (SString
) { return true; },
682 [] (ClassInfo
* cinfo
) {
683 return cinfo
->cls
->attrs
& AttrInterface
;
688 bool Class::couldBeOverriden() const {
690 [] (SString
) { return true; },
691 [] (ClassInfo
* cinfo
) {
692 return !(cinfo
->cls
->attrs
& AttrNoOverride
);
697 bool Class::couldHaveMagicGet() const {
699 [] (SString
) { return true; },
700 [] (ClassInfo
* cinfo
) {
701 return cinfo
->magicGet
.derivedHas
;
706 bool Class::couldHaveMagicBool() const {
708 [] (SString
) { return true; },
709 [] (ClassInfo
* cinfo
) {
710 return cinfo
->magicBool
.derivedHas
;
715 bool Class::couldHaveMockedDerivedClass() const {
717 [] (SString
) { return true;},
718 [] (ClassInfo
* cinfo
) {
719 return cinfo
->isDerivedMocked
;
724 bool Class::couldBeMocked() const {
726 [] (SString
) { return true;},
727 [] (ClassInfo
* cinfo
) {
728 return cinfo
->isMocked
;
733 bool Class::couldHaveReifiedGenerics() const {
735 [] (SString
) { return true; },
736 [] (ClassInfo
* cinfo
) {
737 return cinfo
->cls
->hasReifiedGenerics
;
742 bool Class::mightCareAboutDynConstructs() const {
743 if (RuntimeOption::EvalForbidDynamicCalls
> 0) {
745 [] (SString
) { return true; },
746 [] (ClassInfo
* cinfo
) {
747 return !(cinfo
->cls
->attrs
& AttrDynamicallyConstructible
);
754 folly::Optional
<Class
> Class::commonAncestor(const Class
& o
) const {
755 if (val
.left() || o
.val
.left()) return folly::none
;
756 auto const c1
= val
.right();
757 auto const c2
= o
.val
.right();
758 // Walk the arrays of base classes until they match. For common ancestors
759 // to exist they must be on both sides of the baseList at the same positions
760 ClassInfo
* ancestor
= nullptr;
761 auto it1
= c1
->baseList
.begin();
762 auto it2
= c2
->baseList
.begin();
763 while (it1
!= c1
->baseList
.end() && it2
!= c2
->baseList
.end()) {
764 if (*it1
!= *it2
) break;
768 if (ancestor
== nullptr) {
771 return res::Class
{ index
, ancestor
};
774 folly::Optional
<res::Class
> Class::parent() const {
775 if (!val
.right()) return folly::none
;
776 auto parent
= val
.right()->parent
;
777 if (!parent
) return folly::none
;
778 return res::Class
{ index
, parent
};
781 const php::Class
* Class::cls() const {
782 return val
.right() ? val
.right()->cls
: nullptr;
785 std::string
show(const Class
& c
) {
787 [] (SString s
) -> std::string
{
790 [] (ClassInfo
* cinfo
) {
791 return folly::sformat("{}*", cinfo
->cls
->name
);
796 Func::Func(const Index
* idx
, Rep val
)
801 bool Func::same(const Func
& o
) const {
803 * TODO(#3666699): function name case sensitivity here shouldn't
809 SString
Func::name() const {
810 return match
<SString
>(
812 [&] (FuncName s
) { return s
.name
; },
813 [&] (MethodName s
) { return s
.name
; },
814 [&] (FuncInfo
* fi
) { return fi
->func
->name
; },
815 [&] (const MethTabEntryPair
* mte
) { return mte
->first
; },
816 [&] (FuncFamily
* fa
) -> SString
{
817 auto const name
= fa
->possibleFuncs()->front()->first
;
819 for (DEBUG_ONLY
auto const f
: fa
->possibleFuncs()) {
820 assert(f
->first
->isame(name
));
828 const php::Func
* Func::exactFunc() const {
829 using Ret
= const php::Func
*;
832 [&](FuncName
) { return Ret
{}; },
833 [&](MethodName
) { return Ret
{}; },
834 [&](FuncInfo
* fi
) { return fi
->func
; },
835 [&](const MethTabEntryPair
* mte
) { return mte
->second
.func
; },
836 [&](FuncFamily
* /*fa*/) { return Ret
{}; }
840 bool Func::cantBeMagicCall() const {
843 [&](FuncName
) { return true; },
844 [&](MethodName
) { return false; },
845 [&](FuncInfo
*) { return true; },
846 [&](const MethTabEntryPair
*) { return true; },
847 [&](FuncFamily
*) { return true; }
851 bool Func::isFoldable() const {
852 return match
<bool>(val
,
853 [&](FuncName
) { return false; },
854 [&](MethodName
) { return false; },
856 return fi
->func
->attrs
& AttrIsFoldable
;
858 [&](const MethTabEntryPair
* mte
) {
859 return mte
->second
.func
->attrs
& AttrIsFoldable
;
861 [&](FuncFamily
* fa
) {
866 bool Func::couldHaveReifiedGenerics() const {
869 [&](FuncName s
) { return true; },
870 [&](MethodName
) { return true; },
871 [&](FuncInfo
* fi
) { return fi
->func
->isReified
; },
872 [&](const MethTabEntryPair
* mte
) {
873 return mte
->second
.func
->isReified
;
875 [&](FuncFamily
* fa
) {
876 for (auto const pf
: fa
->possibleFuncs()) {
877 if (pf
->second
.func
->isReified
) return true;
883 bool Func::mightCareAboutDynCalls() const {
884 if (RuntimeOption::EvalNoticeOnBuiltinDynamicCalls
&& mightBeBuiltin()) {
887 if (RuntimeOption::EvalForbidDynamicCalls
> 0) {
888 auto const res
= match
<bool>(
890 [&](FuncName
) { return true; },
891 [&](MethodName
) { return true; },
893 return !(fi
->func
->attrs
& AttrDynamicallyCallable
);
895 [&](const MethTabEntryPair
* mte
) {
896 return !(mte
->second
.func
->attrs
& AttrDynamicallyCallable
);
898 [&](FuncFamily
* fa
) {
899 for (auto const pf
: fa
->possibleFuncs()) {
900 if (!(pf
->second
.func
->attrs
& AttrDynamicallyCallable
)) return true;
905 if (res
) return true;
910 bool Func::mightBeBuiltin() const {
913 // Builtins are always uniquely resolvable unless renaming is
915 [&](FuncName s
) { return s
.renamable
; },
916 [&](MethodName
) { return true; },
917 [&](FuncInfo
* fi
) { return fi
->func
->attrs
& AttrBuiltin
; },
918 [&](const MethTabEntryPair
* mte
) {
919 return mte
->second
.func
->attrs
& AttrBuiltin
;
921 [&](FuncFamily
* fa
) {
922 for (auto const pf
: fa
->possibleFuncs()) {
923 if (pf
->second
.func
->attrs
& AttrBuiltin
) return true;
930 std::string
show(const Func
& f
) {
931 auto ret
= f
.name()->toCppString();
933 [&](Func::FuncName s
) { if (s
.renamable
) ret
+= '?'; },
934 [&](Func::MethodName
) {},
935 [&](FuncInfo
* /*fi*/) { ret
+= "*"; },
936 [&](const MethTabEntryPair
* /*mte*/) { ret
+= "*"; },
937 [&](FuncFamily
* /*fa*/) { ret
+= "+"; });
943 //////////////////////////////////////////////////////////////////////
945 using IfaceSlotMap
= hphp_hash_map
<const php::Class
*, Slot
>;
946 using ConstInfoConcurrentMap
=
947 tbb::concurrent_hash_map
<SString
, ConstInfo
, StringDataHashCompare
>;
949 struct Index::IndexData
{
950 explicit IndexData(Index
* index
) : m_index
{index
} {}
951 IndexData(const IndexData
&) = delete;
952 IndexData
& operator=(const IndexData
&) = delete;
954 if (compute_iface_vtables
.joinable()) {
955 compute_iface_vtables
.join();
962 bool ever_frozen
{false};
963 bool any_interceptable_functions
{false};
965 std::unique_ptr
<ArrayTypeTable::Builder
> arrTableBuilder
;
967 ISStringToMany
<const php::Class
> classes
;
968 ISStringToMany
<const php::Func
> methods
;
969 ISStringToOneT
<uint64_t> method_ref_params_by_name
;
970 ISStringToMany
<const php::Func
> funcs
;
971 ISStringToMany
<const php::TypeAlias
> typeAliases
;
972 ISStringToMany
<const php::Class
> enums
;
973 ConstInfoConcurrentMap constants
;
974 ISStringToMany
<const php::Record
> records
;
975 hphp_fast_set
<SString
, string_data_hash
, string_data_isame
> classAliases
;
977 // Map from each class to all the closures that are allocated in
978 // functions of that class.
981 CompactVector
<const php::Class
*>
986 hphp_fast_set
<php::Func
*>
987 > classExtraMethodMap
;
990 * Map from each class name to ClassInfo objects for all
991 * not-known-to-be-impossible resolutions of the class at runtime.
993 * If the class is unique, there will only be one resolution.
994 * Otherwise there will be one for each possible path through the
995 * inheritance hierarchy, potentially excluding cases that we know
996 * would definitely fatal when defined.
998 ISStringToMany
<ClassInfo
> classInfo
;
1001 * All the ClassInfos, sorted topologically (ie all the parents,
1002 * interfaces and traits used by the ClassInfo at index K will have
1003 * indices less than K). This mostly drops out of the way ClassInfos
1004 * are created; it would be hard to create the ClassInfos for the
1005 * php::Class X (or even know how many to create) without knowing
1006 * all the ClassInfos that were created for X's dependencies.
1008 std::vector
<std::unique_ptr
<ClassInfo
>> allClassInfos
;
1010 std::vector
<FuncInfo
> funcInfo
;
1012 // Private instance and static property types are stored separately
1013 // from ClassInfo, because you don't need to resolve a class to get
1022 > privateStaticPropInfo
;
1025 * Public static property information:
1028 // If this is true, we don't know anything about public static properties and
1029 // must be pessimistic. We start in this state (before we've analyzed any
1030 // mutations) and remain in it if we see a mutation where both the name and
1031 // class are unknown.
1032 bool allPublicSPropsUnknown
{true};
1034 // Best known types for public static properties where we knew the name, but
1035 // not the class. The type we're allowed to assume for a public static
1036 // property is the union of the ClassInfo-specific type with the unknown class
1037 // type that's stored here. The second value is the number of times the type
1038 // has been refined.
1039 hphp_hash_map
<SString
, std::pair
<Type
, uint32_t>> unknownClassSProps
;
1041 // The set of gathered public static property mutations for each function. The
1042 // inferred types for the public static properties is the union of all these
1043 // mutations. If a function is not analyzed in a particular analysis round,
1044 // its mutations are left unchanged from the previous round.
1045 folly::ConcurrentHashMap
<const php::Func
*,
1046 PublicSPropMutations
> publicSPropMutations
;
1049 * Map from interfaces to their assigned vtable slots, computed in
1050 * compute_iface_vtables().
1052 IfaceSlotMap ifaceSlotMap
;
1059 bool useClassDependencies
{};
1060 DepMap dependencyMap
;
1063 * If a function is effect-free when called with a particular set of
1064 * literal arguments, and produces a literal result, there will be
1065 * an entry here representing the type.
1067 * The map isn't just an optimization; we can't call
1068 * analyze_func_inline during the optimization phase, because the
1069 * bytecode could be modified while we do so.
1071 ContextRetTyMap foldableReturnTypeMap
;
1074 * Call-context sensitive return types are cached here. This is not
1077 * The reason we need to retain this information about the
1078 * calling-context-sensitive return types is that once the Index is
1079 * frozen (during the final optimization pass), calls to
1080 * lookup_return_type with a CallContext can't look at the bytecode
1081 * bodies of functions other than the calling function. So we need
1082 * to know what we determined the last time we were alloewd to do
1083 * that so we can return it again.
1085 ContextRetTyMap contextualReturnTypes
{};
1088 * Vector of class aliases that need to be added to the index when
1089 * its safe to do so (see update_class_aliases).
1091 std::vector
<std::pair
<SString
, SString
>> pending_class_aliases
;
1092 std::mutex pending_class_aliases_mutex
;
1094 std::thread compute_iface_vtables
;
1097 //////////////////////////////////////////////////////////////////////
1101 //////////////////////////////////////////////////////////////////////
1103 using IndexData
= Index::IndexData
;
1105 std::mutex closure_use_vars_mutex
;
1106 std::mutex private_propstate_mutex
;
1108 DependencyContext
make_dep(const php::Func
* func
) {
1109 return DependencyContext
{DependencyContextType::Func
, func
};
1111 DependencyContext
make_dep(const php::Class
* cls
) {
1112 return DependencyContext
{DependencyContextType::Class
, cls
};
1114 DependencyContext
make_dep(SString name
) {
1115 return DependencyContext
{DependencyContextType::PropName
, name
};
1118 DependencyContext
dep_context(IndexData
& data
, const Context
& ctx
) {
1119 if (!ctx
.cls
|| !data
.useClassDependencies
) return make_dep(ctx
.func
);
1120 auto const cls
= ctx
.cls
->closureContextCls
?
1121 ctx
.cls
->closureContextCls
: ctx
.cls
;
1122 if (is_used_trait(*cls
)) return make_dep(ctx
.func
);
1123 return make_dep(cls
);
1126 template <typename T
>
1127 void add_dependency(IndexData
& data
,
1131 if (data
.frozen
) return;
1133 auto d
= dep_context(data
, dst
);
1134 DepMap::accessor acc
;
1135 data
.dependencyMap
.insert(acc
, make_dep(src
));
1136 auto& current
= acc
->second
[d
];
1137 current
= current
| newMask
;
1140 std::mutex func_info_mutex
;
1142 FuncInfo
* create_func_info(IndexData
& data
, const php::Func
* f
) {
1143 auto fi
= &data
.funcInfo
[f
->idx
];
1144 if (UNLIKELY(fi
->func
== nullptr)) {
1145 if (f
->nativeInfo
) {
1146 std::lock_guard
<std::mutex
> g
{func_info_mutex
};
1148 assert(fi
->func
== f
);
1151 // We'd infer this anyway when we look at the bytecode body
1152 // (NativeImpl) for the HNI function, but just initializing it
1153 // here saves on whole-program iterations.
1154 fi
->returnTy
= native_function_return_type(f
);
1159 assert(fi
->func
== f
);
1163 FuncInfo
* func_info(IndexData
& data
, const php::Func
* f
) {
1164 auto const fi
= &data
.funcInfo
[f
->idx
];
1168 template <typename T
>
1169 void find_deps(IndexData
& data
,
1172 DependencyContextSet
& deps
) {
1173 DepMap::const_accessor acc
;
1174 if (data
.dependencyMap
.find(acc
, make_dep(src
))) {
1175 for (auto& kv
: acc
->second
) {
1176 if (has_dep(kv
.second
, mask
)) deps
.insert(kv
.first
);
1181 struct TraitMethod
{
1182 using class_type
= const ClassInfo
*;
1183 using method_type
= const php::Func
*;
1185 TraitMethod(class_type trait_
, method_type method_
, Attr modifiers_
)
1188 , modifiers(modifiers_
)
1197 using string_type
= LSString
;
1198 using class_type
= TraitMethod::class_type
;
1199 using method_type
= TraitMethod::method_type
;
1201 struct TMIException
: std::exception
{
1202 explicit TMIException(std::string msg
) : msg(msg
) {}
1203 const char* what() const noexcept override
{ return msg
.c_str(); }
1208 // Return the name for the trait class.
1209 static const string_type
clsName(class_type traitCls
) {
1210 return traitCls
->cls
->name
;
1213 // Return the name for the trait method.
1214 static const string_type
methName(method_type meth
) {
1219 static bool isTrait(class_type traitCls
) {
1220 return traitCls
->cls
->attrs
& AttrTrait
;
1222 static bool isAbstract(Attr modifiers
) {
1223 return modifiers
& AttrAbstract
;
1226 static bool isAsync(method_type meth
) {
1227 return meth
->isAsync
;
1229 static bool isStatic(method_type meth
) {
1230 return meth
->attrs
& AttrStatic
;
1232 static bool isFinal(method_type meth
) {
1233 return meth
->attrs
& AttrFinal
;
1236 // Whether to exclude methods with name `methName' when adding.
1237 static bool exclude(string_type methName
) {
1238 return Func::isSpecial(methName
);
1241 // TraitMethod constructor.
1242 static TraitMethod
traitMethod(class_type traitCls
,
1243 method_type traitMeth
,
1244 const PreClass::TraitAliasRule
& rule
) {
1245 return TraitMethod
{ traitCls
, traitMeth
, rule
.modifiers() };
1248 // Register a trait alias once the trait class is found.
1249 static void addTraitAlias(const ClassInfo
* /*cls*/,
1250 const PreClass::TraitAliasRule
& /*rule*/,
1251 class_type
/*traitCls*/) {
1252 // purely a runtime thing... nothing to do
1255 // Trait class/method finders.
1256 static class_type
findSingleTraitWithMethod(class_type cls
,
1257 string_type origMethName
) {
1258 class_type traitCls
= nullptr;
1260 for (auto const t
: cls
->usedTraits
) {
1261 // Note: m_methods includes methods from parents/traits recursively.
1262 if (t
->methods
.count(origMethName
)) {
1263 if (traitCls
!= nullptr) {
1272 static class_type
findTraitClass(class_type cls
,
1273 string_type traitName
) {
1274 for (auto const t
: cls
->usedTraits
) {
1275 if (traitName
->isame(t
->cls
->name
)) return t
;
1280 static method_type
findTraitMethod(class_type traitCls
,
1281 string_type origMethName
) {
1282 auto it
= traitCls
->methods
.find(origMethName
);
1283 if (it
== traitCls
->methods
.end()) return nullptr;
1284 return it
->second
.func
;
1288 static void errorUnknownMethod(string_type methName
) {
1289 throw TMIException(folly::sformat("Unknown method '{}'", methName
));
1291 static void errorUnknownTrait(string_type traitName
) {
1292 throw TMIException(folly::sformat("Unknown trait '{}'", traitName
));
1294 static void errorDuplicateMethod(class_type cls
,
1295 string_type methName
,
1296 const std::list
<TraitMethod
>&) {
1297 auto const& m
= cls
->cls
->methods
;
1298 if (std::find_if(m
.begin(), m
.end(),
1299 [&] (auto const& f
) {
1300 return f
->name
->isame(methName
);
1302 // the duplicate methods will be overridden by the class method.
1305 throw TMIException(folly::sformat("DuplicateMethod: {}", methName
));
1307 static void errorInconsistentInsteadOf(class_type cls
,
1308 string_type methName
) {
1309 throw TMIException(folly::sformat("InconsistentInsteadOf: {} {}",
1310 methName
, cls
->cls
->name
));
1312 static void errorMultiplyExcluded(string_type traitName
,
1313 string_type methName
) {
1314 throw TMIException(folly::sformat("MultiplyExcluded: {}::{}",
1315 traitName
, methName
));
1317 static void errorInconsistentAttr(string_type traitName
,
1318 string_type methName
,
1320 throw TMIException(folly::sformat(
1321 "Redeclaration of trait method '{}::{}' is inconsistent about '{}'",
1322 traitName
, methName
, attr
1325 static void errorRedeclaredNotFinal(string_type traitName
,
1326 string_type methName
) {
1327 throw TMIException(folly::sformat(
1328 "Redeclaration of final trait method '{}::{}' must also be final",
1335 using TMIData
= TraitMethodImportData
<TraitMethod
,
1338 struct BuildClsInfo
{
1341 hphp_hash_map
<SString
, std::pair
<php::Prop
, const ClassInfo
*>,
1342 string_data_hash
, string_data_same
> pbuilder
;
1346 * Make a flattened table of the constants on this class.
1348 bool build_class_constants(BuildClsInfo
& info
,
1349 const ClassInfo
* rparent
,
1351 auto const removeNoOverride
= [&] (const php::Const
* c
) {
1352 // During hhbbc/parse, all constants are pre-set to NoOverride
1353 FTRACE(2, "Removing NoOverride on {}::{}\n", c
->cls
->name
, c
->name
);
1354 const_cast<php::Const
*>(c
)->isNoOverride
= false;
1356 for (auto& c
: rparent
->cls
->constants
) {
1357 auto& cptr
= info
.rleaf
->clsConstants
[c
.name
];
1363 // Same constant (from an interface via two different paths) is ok
1364 if (cptr
->cls
== rparent
->cls
) continue;
1366 if (cptr
->isTypeconst
!= c
.isTypeconst
) {
1368 "build_cls_info_rec failed for `{}' because `{}' was defined by "
1369 "`{}' as a {}constant and by `{}' as a {}constant\n",
1370 info
.rleaf
->cls
->name
, c
.name
,
1371 rparent
->cls
->name
, c
.isTypeconst
? "type " : "",
1372 cptr
->cls
->name
, cptr
->isTypeconst
? "type " : "");
1376 // Ignore abstract constants
1377 if (!c
.val
) continue;
1380 // Constants from interfaces implemented by traits silently lose
1382 removeNoOverride(&c
);
1386 // A constant from an interface collides with an existing constant.
1387 if (rparent
->cls
->attrs
& AttrInterface
) {
1389 "build_cls_info_rec failed for `{}' because "
1390 "`{}' was defined by both `{}' and `{}'\n",
1391 info
.rleaf
->cls
->name
, c
.name
,
1392 rparent
->cls
->name
, cptr
->cls
->name
);
1397 removeNoOverride(cptr
);
1403 bool build_class_properties(BuildClsInfo
& info
,
1404 const ClassInfo
* rparent
) {
1405 // There's no need to do this work if traits have been flattened
1406 // already, or if the top level class has no traits. In those
1407 // cases, we might be able to rule out some ClassInfo
1408 // instantiations, but it doesn't seem worth it.
1409 if (info
.rleaf
->cls
->attrs
& AttrNoExpandTrait
) return true;
1410 if (info
.rleaf
->usedTraits
.empty()) return true;
1412 auto addProp
= [&] (const php::Prop
& p
, bool add
) {
1413 auto ent
= std::make_pair(p
, rparent
);
1414 auto res
= info
.pbuilder
.emplace(p
.name
, ent
);
1416 if (add
) info
.rleaf
->traitProps
.push_back(p
);
1419 auto& prevProp
= res
.first
->second
.first
;
1420 if (rparent
== res
.first
->second
.second
) {
1421 assertx(rparent
== info
.rleaf
);
1422 if ((prevProp
.attrs
^ p
.attrs
) &
1423 (AttrStatic
| AttrPublic
| AttrProtected
| AttrPrivate
) ||
1424 (!(p
.attrs
& AttrSystemInitialValue
) &&
1425 !(prevProp
.attrs
& AttrSystemInitialValue
) &&
1426 !Class::compatibleTraitPropInit(prevProp
.val
, p
.val
))) {
1428 "build_class_properties failed for `{}' because "
1429 "two declarations of `{}' at the same level had "
1430 "different attributes\n",
1431 info
.rleaf
->cls
->name
, p
.name
);
1437 if (!(prevProp
.attrs
& AttrPrivate
)) {
1438 if ((prevProp
.attrs
^ p
.attrs
) & AttrStatic
) {
1440 "build_class_properties failed for `{}' because "
1441 "`{}' was defined both static and non-static\n",
1442 info
.rleaf
->cls
->name
, p
.name
);
1445 if (p
.attrs
& AttrPrivate
) {
1447 "build_class_properties failed for `{}' because "
1448 "`{}' was re-declared private\n",
1449 info
.rleaf
->cls
->name
, p
.name
);
1452 if (p
.attrs
& AttrProtected
&& !(prevProp
.attrs
& AttrProtected
)) {
1454 "build_class_properties failed for `{}' because "
1455 "`{}' was redeclared protected from public\n",
1456 info
.rleaf
->cls
->name
, p
.name
);
1460 if (add
&& res
.first
->second
.second
!= rparent
) {
1461 info
.rleaf
->traitProps
.push_back(p
);
1463 res
.first
->second
= ent
;
1467 for (auto const& p
: rparent
->cls
->properties
) {
1468 if (!addProp(p
, false)) return false;
1471 if (rparent
== info
.rleaf
) {
1472 for (auto t
: rparent
->usedTraits
) {
1473 for (auto const& p
: t
->cls
->properties
) {
1474 if (!addProp(p
, true)) return false;
1476 for (auto const& p
: t
->traitProps
) {
1477 if (!addProp(p
, true)) return false;
1481 for (auto const& p
: rparent
->traitProps
) {
1482 if (!addProp(p
, false)) return false;
1490 * Make a flattened table of the methods on this class.
1492 * Duplicate method names override parent methods, unless the parent method
1493 * is final and the class is not a __MockClass, in which case this class
1494 * definitely would fatal if ever defined.
1496 * Note: we're leaving non-overridden privates in their subclass method
1497 * table, here. This isn't currently "wrong", because calling it would be a
1498 * fatal, but note that resolve_method needs to be pretty careful about
1499 * privates and overriding in general.
1501 bool build_class_methods(BuildClsInfo
& info
) {
1503 auto methodOverride
= [&] (auto& it
,
1504 const php::Func
* meth
,
1507 if (it
->second
.func
->attrs
& AttrFinal
) {
1508 if (!is_mock_class(info
.rleaf
->cls
)) {
1510 "build_class_methods failed for `{}' because "
1511 "it tried to override final method `{}::{}'\n",
1512 info
.rleaf
->cls
->name
,
1513 it
->second
.func
->cls
->name
, name
);
1518 " {}: overriding method {}::{} with {}::{}\n",
1519 info
.rleaf
->cls
->name
,
1520 it
->second
.func
->cls
->name
, it
->second
.func
->name
,
1521 meth
->cls
->name
, name
);
1522 if (it
->second
.func
->attrs
& AttrPrivate
) {
1523 it
->second
.hasPrivateAncestor
= true;
1525 it
->second
.func
= meth
;
1526 it
->second
.attrs
= attrs
;
1527 it
->second
.hasAncestor
= true;
1528 it
->second
.topLevel
= true;
1529 if (it
->first
!= name
) {
1530 auto mte
= it
->second
;
1531 info
.rleaf
->methods
.erase(it
);
1532 it
= info
.rleaf
->methods
.emplace(name
, mte
).first
;
1537 // If there's a parent, start by copying its methods
1538 if (auto const rparent
= info
.rleaf
->parent
) {
1539 for (auto& mte
: rparent
->methods
) {
1540 // don't inherit the 86* methods.
1541 if (HPHP::Func::isSpecial(mte
.first
)) continue;
1542 auto const res
= info
.rleaf
->methods
.emplace(mte
.first
, mte
.second
);
1543 assertx(res
.second
);
1544 res
.first
->second
.topLevel
= false;
1546 " {}: inheriting method {}::{}\n",
1547 info
.rleaf
->cls
->name
,
1548 rparent
->cls
->name
, mte
.first
);
1553 uint32_t idx
= info
.rleaf
->methods
.size();
1555 // Now add our methods.
1556 for (auto& m
: info
.rleaf
->cls
->methods
) {
1557 auto res
= info
.rleaf
->methods
.emplace(
1559 MethTabEntry
{ m
.get(), m
->attrs
, false, true }
1562 res
.first
->second
.idx
= idx
++;
1564 " {}: adding method {}::{}\n",
1565 info
.rleaf
->cls
->name
,
1566 info
.rleaf
->cls
->name
, m
->name
);
1569 if (m
->attrs
& AttrTrait
&& m
->attrs
& AttrAbstract
) {
1570 // abstract methods from traits never override anything.
1573 if (!methodOverride(res
.first
, m
.get(), m
->attrs
, m
->name
)) return false;
1576 // If our traits were previously flattened, we're done.
1577 if (info
.rleaf
->cls
->attrs
& AttrNoExpandTrait
) return true;
1581 for (auto const t
: info
.rleaf
->usedTraits
) {
1582 std::vector
<const MethTabEntryPair
*> methods(t
->methods
.size());
1583 for (auto& m
: t
->methods
) {
1584 if (HPHP::Func::isSpecial(m
.first
)) continue;
1585 assertx(!methods
[m
.second
.idx
]);
1586 methods
[m
.second
.idx
] = mteFromElm(m
);
1588 for (auto const m
: methods
) {
1590 TraitMethod traitMethod
{ t
, m
->second
.func
, m
->second
.attrs
};
1591 tmid
.add(traitMethod
, m
->first
);
1593 for (auto const c
: info
.index
.classClosureMap
[t
->cls
]) {
1594 auto const invoke
= find_method(c
, s_invoke
.get());
1596 info
.index
.classExtraMethodMap
[info
.rleaf
->cls
].insert(invoke
);
1600 for (auto const& precRule
: info
.rleaf
->cls
->traitPrecRules
) {
1601 tmid
.applyPrecRule(precRule
, info
.rleaf
);
1603 auto const& aliasRules
= info
.rleaf
->cls
->traitAliasRules
;
1604 tmid
.applyAliasRules(aliasRules
.begin(), aliasRules
.end(), info
.rleaf
);
1605 auto traitMethods
= tmid
.finish(info
.rleaf
);
1606 // Import the methods.
1607 for (auto const& mdata
: traitMethods
) {
1608 auto const method
= mdata
.tm
.method
;
1609 auto attrs
= mdata
.tm
.modifiers
;
1610 if (attrs
== AttrNone
) {
1611 attrs
= method
->attrs
;
1613 Attr attrMask
= (Attr
)(AttrPublic
| AttrProtected
| AttrPrivate
|
1614 AttrAbstract
| AttrFinal
);
1615 attrs
= (Attr
)((attrs
& attrMask
) |
1616 (method
->attrs
& ~attrMask
));
1618 auto res
= info
.rleaf
->methods
.emplace(
1620 MethTabEntry
{ method
, attrs
, false, true }
1623 res
.first
->second
.idx
= idx
++;
1625 " {}: adding trait method {}::{} as {}\n",
1626 info
.rleaf
->cls
->name
,
1627 method
->cls
->name
, method
->name
, mdata
.name
);
1629 if (attrs
& AttrAbstract
) continue;
1630 if (res
.first
->second
.func
->cls
== info
.rleaf
->cls
) continue;
1631 if (!methodOverride(res
.first
, method
, attrs
, mdata
.name
)) {
1634 res
.first
->second
.idx
= idx
++;
1636 info
.index
.classExtraMethodMap
[info
.rleaf
->cls
].insert(
1637 const_cast<php::Func
*>(method
));
1639 } catch (TMIOps::TMIException
& ex
) {
1641 "build_class_methods failed for `{}' importing traits: {}\n",
1642 info
.rleaf
->cls
->name
, ex
.what());
1649 bool enforce_in_maybe_sealed_parent_whitelist(
1650 const ClassInfo
* cls
,
1651 const ClassInfo
* parent
);
1653 bool build_cls_info_rec(BuildClsInfo
& info
,
1654 const ClassInfo
* rparent
,
1656 if (!rparent
) return true;
1657 if (!enforce_in_maybe_sealed_parent_whitelist(rparent
, rparent
->parent
)) {
1660 if (!build_cls_info_rec(info
, rparent
->parent
, false)) {
1664 for (auto const iface
: rparent
->declInterfaces
) {
1665 if (!enforce_in_maybe_sealed_parent_whitelist(rparent
, iface
)) {
1668 if (!build_cls_info_rec(info
, iface
, fromTrait
)) {
1673 for (auto const trait
: rparent
->usedTraits
) {
1674 if (!enforce_in_maybe_sealed_parent_whitelist(rparent
, trait
)) {
1677 if (!build_cls_info_rec(info
, trait
, true)) return false;
1680 if (rparent
->cls
->attrs
& AttrInterface
) {
1682 * Make a flattened table of all the interfaces implemented by the class.
1684 info
.rleaf
->implInterfaces
[rparent
->cls
->name
] = rparent
;
1687 !build_class_properties(info
, rparent
)) {
1691 // We don't need a method table for interfaces, and rather than
1692 // building the table recursively from scratch we just use the
1693 // parent's already constructed method table, and this class's
1694 // local method table (and traits if necessary).
1695 if (rparent
== info
.rleaf
) {
1696 if (!build_class_methods(info
)) return false;
1700 if (!build_class_constants(info
, rparent
, fromTrait
)) return false;
1705 const StaticString
s___Sealed("__Sealed");
1706 bool enforce_in_maybe_sealed_parent_whitelist(
1707 const ClassInfo
* cls
,
1708 const ClassInfo
* parent
) {
1709 // if our parent isn't sealed, then we're fine.
1710 if (!parent
|| !(parent
->cls
->attrs
& AttrSealed
)) return true;
1711 const UserAttributeMap
& parent_attrs
= parent
->cls
->userAttributes
;
1712 assert(parent_attrs
.find(s___Sealed
.get()) != parent_attrs
.end());
1713 const auto& parent_sealed_attr
= parent_attrs
.find(s___Sealed
.get())->second
;
1714 bool in_sealed_whitelist
= false;
1715 IterateV(parent_sealed_attr
.m_data
.parr
,
1716 [&in_sealed_whitelist
, cls
](TypedValue v
) -> bool {
1717 if (v
.m_data
.pstr
->same(cls
->cls
->name
)) {
1718 in_sealed_whitelist
= true;
1723 return in_sealed_whitelist
;
1727 * Note: a cyclic inheritance chain will blow this up, but right now
1728 * we'll never get here in that case because hphpc currently just
1729 * modifies classes not to have that situation. TODO(#3649211).
1731 * This function return false if we are certain instantiating cinfo
1732 * would be a fatal at runtime.
1734 bool build_cls_info(IndexData
& index
, ClassInfo
* cinfo
) {
1735 auto info
= BuildClsInfo
{ index
, cinfo
};
1736 if (!build_cls_info_rec(info
, cinfo
, false)) return false;
1740 //////////////////////////////////////////////////////////////////////
1742 void add_system_constants_to_index(IndexData
& index
) {
1743 for (auto cnsPair
: Native::getConstants()) {
1744 assertx(cnsPair
.second
.m_type
!= KindOfUninit
||
1745 cnsPair
.second
.dynamic());
1746 auto t
= cnsPair
.second
.dynamic() ?
1747 TInitCell
: from_cell(cnsPair
.second
);
1749 ConstInfoConcurrentMap::accessor acc
;
1750 if (index
.constants
.insert(acc
, cnsPair
.first
)) {
1751 acc
->second
.func
= nullptr;
1752 acc
->second
.type
= t
;
1753 acc
->second
.system
= true;
1754 acc
->second
.readonly
= false;
1759 //////////////////////////////////////////////////////////////////////
1761 // We want const qualifiers on various index data structures for php
1762 // object pointers, but during index creation time we need to
1763 // manipulate some of their attributes (changing the representation).
1764 // This little wrapper keeps the const_casting out of the main line of
1766 void attribute_setter(const Attr
& attrs
, bool set
, Attr attr
) {
1767 attrSetter(const_cast<Attr
&>(attrs
), set
, attr
);
1770 void add_unit_to_index(IndexData
& index
, const php::Unit
& unit
) {
1773 hphp_hash_set
<const php::Class
*>
1776 for (auto& c
: unit
.classes
) {
1777 auto const attrsToRemove
=
1779 AttrNoOverrideMagicGet
|
1780 AttrNoOverrideMagicSet
|
1781 AttrNoOverrideMagicIsset
|
1782 AttrNoOverrideMagicUnset
;
1783 attribute_setter(c
->attrs
, false, attrsToRemove
);
1785 if (c
->attrs
& AttrEnum
) {
1786 index
.enums
.insert({c
->name
, c
.get()});
1789 index
.classes
.insert({c
->name
, c
.get()});
1791 for (auto& m
: c
->methods
) {
1792 attribute_setter(m
->attrs
, false, AttrNoOverride
);
1793 index
.methods
.insert({m
->name
, m
.get()});
1794 if (m
->attrs
& AttrInterceptable
) {
1795 index
.any_interceptable_functions
= true;
1798 if (RuntimeOption::RepoAuthoritative
) {
1799 uint64_t refs
= 0, cur
= 1;
1800 bool anyByRef
= false;
1801 for (auto& p
: m
->params
) {
1806 // It doesn't matter that we lose parameters beyond the 64th,
1807 // for those, we'll conservatively check everything anyway.
1811 // Multiple methods with the same name will be combined in the same
1812 // cell, thus we use |=. This only makes sense in WholeProgram mode
1813 // since we use this field to check that no functions uses its n-th
1814 // parameter byref, which requires global knowledge.
1815 index
.method_ref_params_by_name
[m
->name
] |= refs
;
1820 if (c
->closureContextCls
) {
1821 closureMap
[c
->closureContextCls
].insert(c
.get());
1825 if (!closureMap
.empty()) {
1826 for (auto const& c1
: closureMap
) {
1827 auto& s
= index
.classClosureMap
[c1
.first
];
1828 for (auto const& c2
: c1
.second
) {
1834 for (auto& f
: unit
.funcs
) {
1836 * A function can be defined with the same name as a builtin in the
1837 * repo. Any such attempts will fatal at runtime, so we can safely ignore
1838 * any such definitions. This ensures that names referring to builtins are
1839 * always fully resolvable.
1841 auto const funcs
= index
.funcs
.equal_range(f
->name
);
1842 if (funcs
.first
!= funcs
.second
) {
1843 if (f
->attrs
& AttrIsMethCaller
) {
1844 // meth_caller has builtin attr and can have duplicates definitions
1845 assertx(std::next(funcs
.first
) == funcs
.second
);
1846 assertx(funcs
.first
->second
->attrs
& AttrIsMethCaller
);
1850 auto const& old_func
= funcs
.first
->second
;
1851 // If there is a builtin, it will always be the first (and only) func on
1853 if (old_func
->attrs
& AttrBuiltin
) {
1854 always_assert(!(f
->attrs
& AttrBuiltin
));
1857 if (f
->attrs
& AttrBuiltin
) index
.funcs
.erase(funcs
.first
, funcs
.second
);
1859 if (f
->attrs
& AttrInterceptable
) index
.any_interceptable_functions
= true;
1860 index
.funcs
.insert({f
->name
, f
.get()});
1863 for (auto& ta
: unit
.typeAliases
) {
1864 index
.typeAliases
.insert({ta
->name
, ta
.get()});
1867 for (auto& rec
: unit
.records
) {
1868 index
.records
.insert({rec
->name
, rec
.get()});
1871 for (auto& ca
: unit
.classAliases
) {
1872 index
.classAliases
.insert(ca
.first
);
1873 index
.classAliases
.insert(ca
.second
);
1878 NamingEnv(php::Program
* program
, IndexData
& index
) :
1879 program
{program
}, index
{index
} {}
1884 ClassInfo
* try_lookup(SString name
) const {
1885 auto const it
= names
.find(name
);
1886 return it
== end(names
) ? nullptr : it
->second
;
1889 ClassInfo
* lookup(SString name
) const {
1890 auto ret
= try_lookup(name
);
1891 always_assert(ret
&& "NamingEnv::lookup failed unexpectedly");
1895 // Return true when the name has been seen before.
1896 // This is intended only for checking circular dependencies in
1897 // pre-resolution time.
1898 bool seen(SString name
) const {
1899 return names
.count(name
);
1902 php::Program
* program
;
1905 ISStringToOne
<ClassInfo
> names
;
1908 struct NamingEnv::Seen
{
1910 // Add to the seen set.
1911 explicit Seen(NamingEnv
& env
, SString name
): env(env
), name(name
) {
1912 ITRACE(3, "visiting {}\n", name
);
1913 assert(!env
.seen(name
));
1915 // A name can not be simultaneously in "defined" and "visiting" state.
1916 // When one reaches the "define" case, it is already preresolved, and thus
1917 // it has been removed from the "visiting" set since we've done visiting it.
1918 env
.names
[name
] = nullptr;
1921 // Remove from the seen set when going out-of-scope
1923 env
.names
.erase(name
);
1927 Seen(const Seen
&) = delete;
1928 Seen
& operator=(const Seen
&) = delete;
1931 Trace::Indent indent
;
1936 struct NamingEnv::Define
{
1937 explicit Define(NamingEnv
& env
, SString n
, ClassInfo
* ci
,
1938 const php::Class
* cls
)
1940 ITRACE(2, "defining {} for {}\n", n
, cls
->name
);
1941 always_assert(!env
.names
.count(n
));
1948 Define(const Define
&) = delete;
1949 Define
& operator=(const Define
&) = delete;
1952 Trace::Indent indent
;
1957 using ClonedClosureMap
= hphp_hash_map
<
1959 std::pair
<std::unique_ptr
<php::Class
>, uint32_t>
1962 std::unique_ptr
<php::Func
> clone_meth_helper(
1963 php::Class
* newContext
,
1964 const php::Func
* origMeth
,
1965 std::unique_ptr
<php::Func
> cloneMeth
,
1966 std::atomic
<uint32_t>& nextFuncId
,
1967 uint32_t& nextClass
,
1968 ClonedClosureMap
& clonedClosures
);
1970 std::unique_ptr
<php::Class
> clone_closure(php::Class
* newContext
,
1972 std::atomic
<uint32_t>& nextFuncId
,
1973 uint32_t& nextClass
,
1974 ClonedClosureMap
& clonedClosures
) {
1975 auto clone
= std::make_unique
<php::Class
>(*cls
);
1976 assertx(clone
->closureContextCls
);
1977 clone
->closureContextCls
= newContext
;
1978 clone
->unit
= newContext
->unit
;
1980 for (auto& cloneMeth
: clone
->methods
) {
1981 cloneMeth
= clone_meth_helper(clone
.get(),
1982 cls
->methods
[i
++].get(),
1983 std::move(cloneMeth
),
1987 if (!cloneMeth
) return nullptr;
1992 std::unique_ptr
<php::Func
> clone_meth_helper(
1993 php::Class
* newContext
,
1994 const php::Func
* origMeth
,
1995 std::unique_ptr
<php::Func
> cloneMeth
,
1996 std::atomic
<uint32_t>& nextFuncId
,
1997 uint32_t& nextClass
,
1998 ClonedClosureMap
& clonedClosures
) {
2000 cloneMeth
->cls
= newContext
;
2001 cloneMeth
->idx
= nextFuncId
.fetch_add(1, std::memory_order_relaxed
);
2002 if (!cloneMeth
->originalFilename
) {
2003 cloneMeth
->originalFilename
= origMeth
->unit
->filename
;
2005 if (!cloneMeth
->originalUnit
) {
2006 cloneMeth
->originalUnit
= origMeth
->unit
;
2008 cloneMeth
->unit
= newContext
->unit
;
2010 auto const recordClosure
= [&] (uint32_t* clsId
) {
2011 auto const cls
= origMeth
->unit
->classes
[*clsId
].get();
2012 auto& elm
= clonedClosures
[cls
];
2014 elm
.first
= clone_closure(newContext
->closureContextCls
?
2015 newContext
->closureContextCls
: newContext
,
2016 cls
, nextFuncId
, nextClass
, clonedClosures
);
2017 if (!elm
.first
) return false;
2018 elm
.second
= nextClass
++;
2020 *clsId
= elm
.second
;
2024 hphp_fast_map
<size_t, hphp_fast_map
<size_t, uint32_t>> updates
;
2025 for (size_t bid
= 0; bid
< cloneMeth
->blocks
.size(); bid
++) {
2026 auto const b
= cloneMeth
->blocks
[bid
].get();
2027 for (size_t ix
= 0; ix
< b
->hhbcs
.size(); ix
++) {
2028 auto const& bc
= b
->hhbcs
[ix
];
2030 case Op::CreateCl
: {
2031 auto clsId
= bc
.CreateCl
.arg2
;
2032 if (!recordClosure(&clsId
)) return nullptr;
2033 updates
[bid
][ix
] = clsId
;
2045 for (auto elm
: updates
) {
2046 auto& cblk
= cloneMeth
->blocks
[elm
.first
];
2047 auto const blk
= cblk
.mutate();
2048 for (auto const& ix
: elm
.second
) {
2049 blk
->hhbcs
[ix
.first
].CreateCl
.arg2
= ix
.second
;
2056 std::unique_ptr
<php::Func
> clone_meth(php::Class
* newContext
,
2057 const php::Func
* origMeth
,
2060 std::atomic
<uint32_t>& nextFuncId
,
2061 uint32_t& nextClass
,
2062 ClonedClosureMap
& clonedClosures
) {
2064 auto cloneMeth
= std::make_unique
<php::Func
>(*origMeth
);
2065 cloneMeth
->name
= name
;
2066 cloneMeth
->attrs
= attrs
| AttrTrait
;
2067 return clone_meth_helper(newContext
, origMeth
, std::move(cloneMeth
),
2068 nextFuncId
, nextClass
, clonedClosures
);
2071 bool merge_xinits(Attr attr
,
2072 std::vector
<std::unique_ptr
<php::Func
>>& clones
,
2074 std::atomic
<uint32_t>& nextFuncId
,
2075 uint32_t& nextClass
,
2076 ClonedClosureMap
& clonedClosures
) {
2077 auto const cls
= const_cast<php::Class
*>(cinfo
->cls
);
2078 auto const xinitName
= [&]() {
2080 case AttrNone
: return s_86pinit
.get();
2081 case AttrStatic
: return s_86sinit
.get();
2082 case AttrLSB
: return s_86linit
.get();
2083 default: always_assert(false);
2087 auto const xinitMatch
= [&](Attr prop_attrs
) {
2088 auto mask
= AttrStatic
| AttrLSB
;
2090 case AttrNone
: return (prop_attrs
& mask
) == AttrNone
;
2091 case AttrStatic
: return (prop_attrs
& mask
) == AttrStatic
;
2092 case AttrLSB
: return (prop_attrs
& mask
) == mask
;
2093 default: always_assert(false);
2097 auto const needsXinit
= [&] {
2098 for (auto const& p
: cinfo
->traitProps
) {
2099 if (xinitMatch(p
.attrs
) &&
2100 p
.val
.m_type
== KindOfUninit
&&
2101 !(p
.attrs
& AttrLateInit
)) {
2102 ITRACE(5, "merge_xinits: {}: Needs merge for {}{}prop `{}'\n",
2103 cls
->name
, attr
& AttrStatic
? "static " : "",
2104 attr
& AttrLSB
? "lsb " : "", p
.name
);
2111 if (!needsXinit
) return true;
2113 std::unique_ptr
<php::Func
> empty
;
2114 auto& xinit
= [&] () -> std::unique_ptr
<php::Func
>& {
2115 for (auto& m
: cls
->methods
) {
2116 if (m
->name
== xinitName
) return m
;
2121 auto merge_one
= [&] (const php::Func
* func
) {
2123 ITRACE(5, " - cloning {}::{} as {}::{}\n",
2124 func
->cls
->name
, func
->name
, cls
->name
, xinitName
);
2125 xinit
= clone_meth(cls
, func
, func
->name
, func
->attrs
, nextFuncId
,
2126 nextClass
, clonedClosures
);
2127 return xinit
!= nullptr;
2130 ITRACE(5, " - appending {}::{} into {}::{}\n",
2131 func
->cls
->name
, func
->name
, cls
->name
, xinitName
);
2132 return append_func(xinit
.get(), *func
);
2135 for (auto t
: cinfo
->usedTraits
) {
2136 auto it
= t
->methods
.find(xinitName
);
2137 if (it
!= t
->methods
.end()) {
2138 if (!merge_one(it
->second
.func
)) {
2139 ITRACE(5, "merge_xinits: failed to merge {}::{}\n",
2140 it
->second
.func
->cls
->name
, it
->second
.func
->name
);
2148 ITRACE(5, "merge_xinits: adding {}::{} to method table\n",
2149 xinit
->cls
->name
, xinit
->name
);
2150 assertx(&empty
== &xinit
);
2151 DEBUG_ONLY
auto res
= cinfo
->methods
.emplace(
2153 MethTabEntry
{ xinit
.get(), xinit
->attrs
, false, true }
2155 assertx(res
.second
);
2156 clones
.push_back(std::move(xinit
));
2162 void rename_closure(NamingEnv
& env
, php::Class
* cls
) {
2163 auto n
= cls
->name
->slice();
2164 auto const p
= n
.find(';');
2165 if (p
!= std::string::npos
) {
2166 n
= n
.subpiece(0, p
);
2168 auto const newName
= makeStaticString(NewAnonymousClassName(n
));
2169 assertx(!env
.index
.classes
.count(newName
));
2170 cls
->name
= newName
;
2171 env
.index
.classes
.emplace(newName
, cls
);
2174 void flatten_traits(NamingEnv
& env
, ClassInfo
* cinfo
) {
2175 for (auto t
: cinfo
->usedTraits
) {
2176 if (t
->usedTraits
.size() && !(t
->cls
->attrs
& AttrNoExpandTrait
)) {
2177 ITRACE(5, "Not flattening {} because of {}\n",
2178 cinfo
->cls
->name
, t
->cls
->name
);
2182 auto const cls
= const_cast<php::Class
*>(cinfo
->cls
);
2183 std::vector
<MethTabEntryPair
*> methodsToAdd
;
2184 for (auto& ent
: cinfo
->methods
) {
2185 if (!ent
.second
.topLevel
|| ent
.second
.func
->cls
== cinfo
->cls
) {
2188 always_assert(ent
.second
.func
->cls
->attrs
& AttrTrait
);
2189 methodsToAdd
.push_back(mteFromElm(ent
));
2192 auto const it
= env
.index
.classExtraMethodMap
.find(cinfo
->cls
);
2194 if (!methodsToAdd
.empty()) {
2195 assertx(it
!= env
.index
.classExtraMethodMap
.end());
2196 std::sort(begin(methodsToAdd
), end(methodsToAdd
),
2197 [] (const MethTabEntryPair
* a
, const MethTabEntryPair
* b
) {
2198 return a
->second
.idx
< b
->second
.idx
;
2200 } else if (debug
&& it
!= env
.index
.classExtraMethodMap
.end()) {
2201 // When building the ClassInfos, we proactively added all closures
2202 // from usedTraits to classExtraMethodMap; but now we're going to
2203 // start from the used methods, and deduce which closures actually
2204 // get pulled in. Its possible *none* of the methods got used, in
2205 // which case, we won't need their closures either. To be safe,
2206 // verify that the only things in classExtraMethodMap are
2208 for (DEBUG_ONLY
auto const f
: it
->second
) {
2209 assertx(f
->isClosureBody
);
2213 std::vector
<std::unique_ptr
<php::Func
>> clones
;
2214 ClonedClosureMap clonedClosures
;
2215 uint32_t nextClassId
= cls
->unit
->classes
.size();
2216 for (auto const ent
: methodsToAdd
) {
2217 auto clone
= clone_meth(cls
, ent
->second
.func
, ent
->first
,
2218 ent
->second
.attrs
, env
.program
->nextFuncId
,
2219 nextClassId
, clonedClosures
);
2221 ITRACE(5, "Not flattening {} because {}::{} could not be cloned\n",
2222 cls
->name
, ent
->second
.func
->cls
->name
, ent
->first
);
2226 clone
->attrs
|= AttrTrait
;
2227 ent
->second
.attrs
|= AttrTrait
;
2228 ent
->second
.func
= clone
.get();
2229 clones
.push_back(std::move(clone
));
2232 if (cinfo
->traitProps
.size()) {
2233 if (!merge_xinits(AttrNone
, clones
, cinfo
,
2234 env
.program
->nextFuncId
, nextClassId
, clonedClosures
) ||
2235 !merge_xinits(AttrStatic
, clones
, cinfo
,
2236 env
.program
->nextFuncId
, nextClassId
, clonedClosures
) ||
2237 !merge_xinits(AttrLSB
, clones
, cinfo
,
2238 env
.program
->nextFuncId
, nextClassId
, clonedClosures
)) {
2239 ITRACE(5, "Not flattening {} because we couldn't merge the 86xinits\n",
2245 // We're now committed to flattening.
2246 ITRACE(3, "Flattening {}\n", cls
->name
);
2247 if (it
!= env
.index
.classExtraMethodMap
.end()) it
->second
.clear();
2248 for (auto const& p
: cinfo
->traitProps
) {
2249 ITRACE(5, " - prop {}\n", p
.name
);
2250 cls
->properties
.push_back(p
);
2251 cls
->properties
.back().attrs
|= AttrTrait
;
2253 cinfo
->traitProps
.clear();
2255 if (clones
.size()) {
2256 auto cinit
= cls
->methods
.size() &&
2257 cls
->methods
.back()->name
== s_86cinit
.get() ?
2258 std::move(cls
->methods
.back()) : nullptr;
2259 if (cinit
) cls
->methods
.pop_back();
2260 for (auto& clone
: clones
) {
2261 ITRACE(5, " - meth {}\n", clone
->name
);
2262 cinfo
->methods
.find(clone
->name
)->second
.func
= clone
.get();
2263 cls
->methods
.push_back(std::move(clone
));
2265 if (cinit
) cls
->methods
.push_back(std::move(cinit
));
2267 if (clonedClosures
.size()) {
2268 auto& classClosures
= env
.index
.classClosureMap
[cls
];
2269 cls
->unit
->classes
.resize(nextClassId
);
2270 for (auto& ent
: clonedClosures
) {
2271 auto const clo
= ent
.second
.first
.get();
2272 rename_closure(env
, clo
);
2273 ITRACE(5, " - closure {} as {}\n", ent
.first
->name
, clo
->name
);
2274 assertx(clo
->closureContextCls
== cls
);
2275 assertx(clo
->unit
== cls
->unit
);
2276 classClosures
.push_back(clo
);
2278 cls
->unit
->classes
[ent
.second
.second
] = std::move(ent
.second
.first
);
2284 bool operator()(const PreClass::ClassRequirement
& a
,
2285 const PreClass::ClassRequirement
& b
) const {
2286 return a
.is_same(&b
);
2288 size_t operator()(const PreClass::ClassRequirement
& a
) const {
2293 hphp_hash_set
<PreClass::ClassRequirement
, EqHash
, EqHash
> reqs
;
2295 for (auto const t
: cinfo
->usedTraits
) {
2296 for (auto const& req
: t
->cls
->requirements
) {
2298 for (auto const& r
: cls
->requirements
) {
2302 if (reqs
.insert(req
).second
) cls
->requirements
.push_back(req
);
2306 cls
->attrs
|= AttrNoExpandTrait
;
2309 void resolve_combinations(NamingEnv
& env
,
2310 const php::Class
* cls
) {
2312 auto resolve_one
= [&] (SString name
) {
2313 if (env
.try_lookup(name
)) return true;
2315 for (auto& kv
: copy_range(env
.index
.classInfo
, name
)) {
2316 NamingEnv::Define def
{env
, name
, kv
.second
, cls
};
2317 resolve_combinations(env
, cls
);
2322 "Resolve combinations failed for `{}' because "
2323 "there were no resolutions of `{}'\n",
2329 // Recurse with all combinations of bases and interfaces in the
2330 // naming environment.
2331 if (cls
->parentName
) {
2332 if (!resolve_one(cls
->parentName
)) return;
2334 for (auto& iname
: cls
->interfaceNames
) {
2335 if (!resolve_one(iname
)) return;
2337 for (auto& tname
: cls
->usedTraitNames
) {
2338 if (!resolve_one(tname
)) return;
2341 // Everything is defined in the naming environment here. (We
2342 // returned early if something didn't exist.)
2344 auto cinfo
= std::make_unique
<ClassInfo
>();
2346 if (cls
->parentName
) {
2347 cinfo
->parent
= env
.lookup(cls
->parentName
);
2348 cinfo
->baseList
= cinfo
->parent
->baseList
;
2349 if (cinfo
->parent
->cls
->attrs
& (AttrInterface
| AttrTrait
)) {
2351 "Resolve combinations failed for `{}' because "
2352 "its parent `{}' is not a class\n",
2353 cls
->name
, cls
->parentName
);
2357 cinfo
->baseList
.push_back(cinfo
.get());
2359 for (auto& iname
: cls
->interfaceNames
) {
2360 auto const iface
= env
.lookup(iname
);
2361 if (!(iface
->cls
->attrs
& AttrInterface
)) {
2363 "Resolve combinations failed for `{}' because `{}' "
2364 "is not an interface\n",
2368 cinfo
->declInterfaces
.push_back(iface
);
2371 for (auto& tname
: cls
->usedTraitNames
) {
2372 auto const trait
= env
.lookup(tname
);
2373 if (!(trait
->cls
->attrs
& AttrTrait
)) {
2375 "Resolve combinations failed for `{}' because `{}' "
2380 cinfo
->usedTraits
.push_back(trait
);
2383 if (!build_cls_info(env
.index
, cinfo
.get())) return;
2385 ITRACE(2, " resolved: {}\n", cls
->name
);
2386 if (Trace::moduleEnabled(Trace::hhbbc_index
, 3)) {
2387 for (auto const DEBUG_ONLY
& iface
: cinfo
->implInterfaces
) {
2388 ITRACE(3, " implements: {}\n", iface
.second
->cls
->name
);
2390 for (auto const DEBUG_ONLY
& trait
: cinfo
->usedTraits
) {
2391 ITRACE(3, " uses: {}\n", trait
->cls
->name
);
2394 cinfo
->baseList
.shrink_to_fit();
2395 env
.index
.classInfo
.emplace(cls
->name
, cinfo
.get());
2396 env
.index
.allClassInfos
.push_back(std::move(cinfo
));
2399 void preresolve(NamingEnv
& env
, SString clsName
) {
2400 if (env
.index
.classInfo
.count(clsName
)) return;
2402 ITRACE(2, "preresolve: {}\n", clsName
);
2403 if (env
.seen(clsName
)) {
2404 ITRACE(3, "Circular inheritance detected: {}\n", clsName
);
2407 NamingEnv::Seen
seen(env
, clsName
);
2409 Trace::Indent indent
;
2410 auto process_one
= [&] (const php::Class
* cls
) {
2411 if (cls
->parentName
) {
2412 preresolve(env
, cls
->parentName
);
2414 for (auto& i
: cls
->interfaceNames
) {
2417 for (auto& t
: cls
->usedTraitNames
) {
2420 resolve_combinations(env
, cls
);
2422 auto const classRange
= find_range(env
.index
.classes
, clsName
);
2424 if (begin(classRange
) == end(classRange
)) {
2427 if (std::next(begin(classRange
)) == end(classRange
)) {
2428 return process_one(begin(classRange
)->second
);
2430 for (auto& kv
: classRange
) {
2431 if (is_systemlib_part(*kv
.second
->unit
)) {
2432 return process_one(kv
.second
);
2435 for (auto& kv
: classRange
) {
2436 process_one(kv
.second
);
2441 ITRACE(3, "preresolve: {} ({} resolutions)\n",
2442 clsName
, env
.index
.classInfo
.count(clsName
));
2444 if (options
.FlattenTraits
) {
2445 auto const range
= find_range(env
.index
.classInfo
, clsName
);
2446 if (begin(range
) != end(range
) && std::next(begin(range
)) == end(range
)) {
2447 Trace::Indent indent
;
2448 auto const cinfo
= begin(range
)->second
;
2449 if (!(cinfo
->cls
->attrs
& AttrNoExpandTrait
) &&
2450 !cinfo
->usedTraits
.empty()) {
2451 flatten_traits(env
, cinfo
);
2457 void compute_subclass_list_rec(IndexData
& index
,
2460 for (auto const ctrait
: csub
->usedTraits
) {
2461 auto const ct
= const_cast<ClassInfo
*>(ctrait
);
2462 ct
->subclassList
.push_back(cinfo
);
2463 compute_subclass_list_rec(index
, cinfo
, ct
);
2467 void compute_subclass_list(IndexData
& index
) {
2468 trace_time
_("compute subclass list");
2469 auto fixupTraits
= false;
2470 for (auto& cinfo
: index
.allClassInfos
) {
2471 if (cinfo
->cls
->attrs
& AttrInterface
) continue;
2472 for (auto& cparent
: cinfo
->baseList
) {
2473 cparent
->subclassList
.push_back(cinfo
.get());
2475 if (!(cinfo
->cls
->attrs
& AttrNoExpandTrait
) &&
2476 cinfo
->usedTraits
.size()) {
2478 compute_subclass_list_rec(index
, cinfo
.get(), cinfo
.get());
2480 // Also add instantiable classes to their interface's subclassLists
2481 if (cinfo
->cls
->attrs
& (AttrTrait
| AttrEnum
| AttrAbstract
)) continue;
2482 for (auto& ipair
: cinfo
->implInterfaces
) {
2483 auto impl
= const_cast<ClassInfo
*>(ipair
.second
);
2484 impl
->subclassList
.push_back(cinfo
.get());
2488 for (auto& cinfo
: index
.allClassInfos
) {
2489 auto& sub
= cinfo
->subclassList
;
2490 if (fixupTraits
&& cinfo
->cls
->attrs
& AttrTrait
) {
2491 // traits can be reached by multiple paths, so we need to uniquify
2492 // their subclassLists.
2493 std::sort(begin(sub
), end(sub
));
2495 std::unique(begin(sub
), end(sub
)),
2499 sub
.shrink_to_fit();
2503 bool define_func_family(IndexData
& index
, ClassInfo
* cinfo
,
2504 SString name
, const php::Func
* func
= nullptr) {
2505 FuncFamily::PFuncVec funcs
{};
2506 auto containsInterceptables
= false;
2507 for (auto const cleaf
: cinfo
->subclassList
) {
2508 auto const leafFn
= [&] () -> const MethTabEntryPair
* {
2509 auto const leafFnIt
= cleaf
->methods
.find(name
);
2510 if (leafFnIt
== end(cleaf
->methods
)) return nullptr;
2511 return mteFromIt(leafFnIt
);
2513 if (!leafFn
) continue;
2514 if (leafFn
->second
.func
->attrs
& AttrInterceptable
) {
2515 containsInterceptables
= true;
2517 funcs
.push_back(leafFn
);
2520 if (funcs
.empty()) return false;
2522 std::sort(begin(funcs
), end(funcs
),
2523 [&] (const MethTabEntryPair
* a
, const MethTabEntryPair
* b
) {
2524 // We want a canonical order for the family. Putting the
2525 // one corresponding to cinfo first makes sense, because
2526 // the first one is used as the name for FCall hint, after
2527 // that, sort by name so that different case spellings
2528 // come in the same order.
2529 if (a
->second
.func
== b
->second
.func
) return false;
2531 if (b
->second
.func
== func
) return false;
2532 if (a
->second
.func
== func
) return true;
2534 if (auto d
= a
->first
->compare(b
->first
)) {
2536 if (b
->first
== name
) return false;
2537 if (a
->first
== name
) return true;
2541 return std::less
<const void*>{}(a
->second
.func
, b
->second
.func
);
2544 std::unique(begin(funcs
), end(funcs
),
2545 [] (const MethTabEntryPair
* a
, const MethTabEntryPair
* b
) {
2546 return a
->second
.func
== b
->second
.func
;
2551 funcs
.shrink_to_fit();
2553 if (Trace::moduleEnabled(Trace::hhbbc_index
, 4)) {
2554 FTRACE(4, "define_func_family: {}::{}:\n",
2555 cinfo
->cls
->name
, name
);
2556 for (auto const DEBUG_ONLY func
: funcs
) {
2557 FTRACE(4, " {}::{}\n",
2558 func
->second
.func
->cls
->name
, func
->second
.func
->name
);
2562 cinfo
->methodFamilies
.emplace(
2563 std::piecewise_construct
,
2564 std::forward_as_tuple(name
),
2565 std::forward_as_tuple(std::move(funcs
), containsInterceptables
)
2571 void build_abstract_func_families(IndexData
& data
, ClassInfo
* cinfo
) {
2572 std::vector
<SString
> extras
;
2574 // We start by collecting the list of methods shared across all
2575 // subclasses of cinfo (including indirectly). And then add the
2576 // public methods which are not constructors and have no private
2577 // ancestors to the method families of cinfo. Note that this set
2578 // may be larger than the methods declared on cinfo and may also
2579 // be missing methods declared on cinfo. In practice this is the
2580 // set of methods we can depend on having accessible given any
2581 // object which is known to implement cinfo.
2582 auto it
= cinfo
->subclassList
.begin();
2584 if (it
== cinfo
->subclassList
.end()) return;
2585 auto const sub
= *it
++;
2586 assertx(!(sub
->cls
->attrs
& AttrInterface
));
2587 if (sub
== cinfo
|| (sub
->cls
->attrs
& AttrAbstract
)) continue;
2588 for (auto& par
: sub
->methods
) {
2589 if (!par
.second
.hasPrivateAncestor
&&
2590 (par
.second
.attrs
& AttrPublic
) &&
2591 !cinfo
->methodFamilies
.count(par
.first
) &&
2592 !cinfo
->methods
.count(par
.first
)) {
2593 extras
.push_back(par
.first
);
2596 if (!extras
.size()) return;
2600 auto end
= extras
.end();
2601 while (it
!= cinfo
->subclassList
.end()) {
2602 auto const sub
= *it
++;
2603 assertx(!(sub
->cls
->attrs
& AttrInterface
));
2604 if (sub
== cinfo
|| (sub
->cls
->attrs
& AttrAbstract
)) continue;
2605 for (auto nameIt
= extras
.begin(); nameIt
!= end
;) {
2606 auto const meth
= sub
->methods
.find(*nameIt
);
2607 if (meth
== sub
->methods
.end() ||
2608 !(meth
->second
.attrs
& AttrPublic
) ||
2609 meth
->second
.hasPrivateAncestor
) {
2611 if (end
== extras
.begin()) return;
2617 extras
.erase(end
, extras
.end());
2619 if (Trace::moduleEnabled(Trace::hhbbc_index
, 5)) {
2620 FTRACE(5, "Adding extra methods to {}:\n", cinfo
->cls
->name
);
2621 for (auto const DEBUG_ONLY extra
: extras
) {
2622 FTRACE(5, " {}\n", extra
);
2626 hphp_fast_set
<SString
> added
;
2628 for (auto name
: extras
) {
2629 if (define_func_family(data
, cinfo
, name
) &&
2630 (cinfo
->cls
->attrs
& AttrInterface
)) {
2631 added
.emplace(name
);
2635 if (cinfo
->cls
->attrs
& AttrInterface
) {
2636 for (auto& m
: cinfo
->cls
->methods
) {
2637 if (added
.count(m
->name
)) {
2638 cinfo
->methods
.emplace(
2640 MethTabEntry
{ m
.get(), m
->attrs
, false, true }
2648 void define_func_families(IndexData
& index
) {
2649 trace_time
tracer("define_func_families");
2652 index
.allClassInfos
,
2653 [&] (const std::unique_ptr
<ClassInfo
>& cinfo
) {
2654 if (cinfo
->cls
->attrs
& AttrTrait
) return;
2655 FTRACE(4, "Defining func families for {}\n", cinfo
->cls
->name
);
2656 if (!(cinfo
->cls
->attrs
& AttrInterface
)) {
2657 for (auto& kv
: cinfo
->methods
) {
2658 auto const mte
= mteFromElm(kv
);
2660 if (mte
->second
.attrs
& AttrNoOverride
) continue;
2661 if (is_special_method_name(mte
->first
)) continue;
2663 // We need function family for constructor even if it is private,
2664 // as `new static()` may still call a non-private constructor from
2666 if (!mte
->first
->isame(s_construct
.get()) &&
2667 mte
->second
.attrs
& AttrPrivate
) {
2671 define_func_family(index
, cinfo
.get(), mte
->first
, mte
->second
.func
);
2674 if (cinfo
->cls
->attrs
& (AttrInterface
| AttrAbstract
)) {
2675 build_abstract_func_families(index
, cinfo
.get());
2682 * ConflictGraph maintains lists of interfaces that conflict with each other
2683 * due to being implemented by the same class.
2685 struct ConflictGraph
{
2686 void add(const php::Class
* i
, const php::Class
* j
) {
2688 auto& conflicts
= map
[i
];
2689 if (std::find(conflicts
.begin(), conflicts
.end(), j
) != conflicts
.end()) {
2692 conflicts
.push_back(j
);
2695 hphp_hash_map
<const php::Class
*,
2696 std::vector
<const php::Class
*>> map
;
2700 * Trace information about interface conflict sets and the vtables computed
2703 void trace_interfaces(const IndexData
& index
, const ConflictGraph
& cg
) {
2704 // Compute what the vtable for each Class will look like, and build up a list
2705 // of all interfaces.
2707 const ClassInfo
* cinfo
;
2708 std::vector
<const php::Class
*> vtable
;
2710 std::vector
<Cls
> classes
;
2711 std::vector
<const php::Class
*> ifaces
;
2712 size_t total_slots
= 0, empty_slots
= 0;
2713 for (auto& cinfo
: index
.allClassInfos
) {
2714 if (cinfo
->cls
->attrs
& AttrInterface
) {
2715 ifaces
.emplace_back(cinfo
->cls
);
2718 if (cinfo
->cls
->attrs
& (AttrTrait
| AttrEnum
| AttrAbstract
)) continue;
2720 classes
.emplace_back(Cls
{cinfo
.get()});
2721 auto& vtable
= classes
.back().vtable
;
2722 for (auto& pair
: cinfo
->implInterfaces
) {
2723 auto it
= index
.ifaceSlotMap
.find(pair
.second
->cls
);
2724 assert(it
!= end(index
.ifaceSlotMap
));
2725 auto const slot
= it
->second
;
2726 if (slot
>= vtable
.size()) vtable
.resize(slot
+ 1);
2727 vtable
[slot
] = pair
.second
->cls
;
2730 total_slots
+= vtable
.size();
2731 for (auto iface
: vtable
) if (iface
== nullptr) ++empty_slots
;
2735 for (auto const& pair
: index
.ifaceSlotMap
) {
2736 max_slot
= std::max(max_slot
, pair
.second
);
2739 // Sort the list of class vtables so the largest ones come first.
2740 auto class_cmp
= [&](const Cls
& a
, const Cls
& b
) {
2741 return a
.vtable
.size() > b
.vtable
.size();
2743 std::sort(begin(classes
), end(classes
), class_cmp
);
2745 // Sort the list of interfaces so the biggest conflict sets come first.
2746 auto iface_cmp
= [&](const php::Class
* a
, const php::Class
* b
) {
2747 return cg
.map
.at(a
).size() > cg
.map
.at(b
).size();
2749 std::sort(begin(ifaces
), end(ifaces
), iface_cmp
);
2752 folly::format(&out
, "{} interfaces, {} classes\n",
2753 ifaces
.size(), classes
.size());
2755 "{} vtable slots, {} empty vtable slots, max slot {}\n",
2756 total_slots
, empty_slots
, max_slot
);
2757 folly::format(&out
, "\n{:-^80}\n", " interface slots & conflict sets");
2758 for (auto iface
: ifaces
) {
2759 auto cgIt
= cg
.map
.find(iface
);
2760 if (cgIt
== end(cg
.map
)) break;
2761 auto& conflicts
= cgIt
->second
;
2763 folly::format(&out
, "{:>40} {:3} {:2} [", iface
->name
,
2765 folly::get_default(index
.ifaceSlotMap
, iface
));
2767 for (auto conflict
: conflicts
) {
2768 folly::format(&out
, "{}{}", sep
, conflict
->name
);
2771 folly::format(&out
, "]\n");
2774 folly::format(&out
, "\n{:-^80}\n", " class vtables ");
2775 for (auto& item
: classes
) {
2776 if (item
.vtable
.empty()) break;
2778 folly::format(&out
, "{:>30}: [", item
.cinfo
->cls
->name
);
2780 for (auto iface
: item
.vtable
) {
2781 folly::format(&out
, "{}{}", sep
, iface
? iface
->name
->data() : "null");
2784 folly::format(&out
, "]\n");
2787 Trace::traceRelease("%s", out
.c_str());
2791 * Find the lowest Slot that doesn't conflict with anything in the conflict set
2794 Slot
find_min_slot(const php::Class
* iface
,
2795 const IfaceSlotMap
& slots
,
2796 const ConflictGraph
& cg
) {
2797 auto const& conflicts
= cg
.map
.at(iface
);
2798 if (conflicts
.empty()) {
2799 // No conflicts. This is the only interface implemented by the classes that
2804 boost::dynamic_bitset
<> used
;
2806 for (auto& c
: conflicts
) {
2807 auto const it
= slots
.find(c
);
2808 if (it
== slots
.end()) continue;
2809 auto const slot
= it
->second
;
2811 if (used
.size() <= slot
) used
.resize(slot
+ 1);
2815 return used
.any() ? used
.find_first() : used
.size();
2819 * Compute vtable slots for all interfaces. No two interfaces implemented by
2820 * the same class will share the same vtable slot.
2822 void compute_iface_vtables(IndexData
& index
) {
2823 trace_time
tracer("compute interface vtables");
2826 std::vector
<const php::Class
*> ifaces
;
2827 hphp_hash_map
<const php::Class
*, int> iface_uses
;
2829 // Build up the conflict sets.
2830 for (auto& cinfo
: index
.allClassInfos
) {
2831 // Gather interfaces.
2832 if (cinfo
->cls
->attrs
& AttrInterface
) {
2833 ifaces
.emplace_back(cinfo
->cls
);
2834 // Make sure cg.map has an entry for every interface - this simplifies
2835 // some code later on.
2840 // Only worry about classes that can be instantiated. If an abstract class
2841 // has any concrete subclasses, those classes will make sure the right
2842 // entries are in the conflict sets.
2843 if (cinfo
->cls
->attrs
& (AttrTrait
| AttrEnum
| AttrAbstract
)) continue;
2845 for (auto& ipair
: cinfo
->implInterfaces
) {
2846 ++iface_uses
[ipair
.second
->cls
];
2847 for (auto& jpair
: cinfo
->implInterfaces
) {
2848 cg
.add(ipair
.second
->cls
, jpair
.second
->cls
);
2853 if (ifaces
.size() == 0) return;
2855 // Sort interfaces by usage frequencies.
2856 // We assign slots greedily, so sort the interface list so the most
2857 // frequently implemented ones come first.
2858 auto iface_cmp
= [&](const php::Class
* a
, const php::Class
* b
) {
2859 return iface_uses
[a
] > iface_uses
[b
];
2861 std::sort(begin(ifaces
), end(ifaces
), iface_cmp
);
2863 // Assign slots, keeping track of the largest assigned slot and the total
2864 // number of uses for each slot.
2866 hphp_hash_map
<Slot
, int> slot_uses
;
2867 for (auto* iface
: ifaces
) {
2868 auto const slot
= find_min_slot(iface
, index
.ifaceSlotMap
, cg
);
2869 index
.ifaceSlotMap
[iface
] = slot
;
2870 max_slot
= std::max(max_slot
, slot
);
2872 // Interfaces implemented by the same class never share a slot, so normal
2873 // addition is fine here.
2874 slot_uses
[slot
] += iface_uses
[iface
];
2877 // Make sure we have an initialized entry for each slot for the sort below.
2878 for (Slot slot
= 0; slot
< max_slot
; ++slot
) {
2879 assert(slot_uses
.count(slot
));
2882 // Finally, sort and reassign slots so the most frequently used slots come
2883 // first. This slightly reduces the number of wasted vtable vector entries at
2885 auto const slots
= sort_keys_by_value(
2887 [&] (int a
, int b
) { return a
> b
; }
2890 std::vector
<Slot
> slots_permute(max_slot
+ 1, 0);
2891 for (size_t i
= 0; i
<= max_slot
; ++i
) slots_permute
[slots
[i
]] = i
;
2893 // re-map interfaces to permuted slots
2894 for (auto& pair
: index
.ifaceSlotMap
) {
2895 pair
.second
= slots_permute
[pair
.second
];
2898 if (Trace::moduleEnabledRelease(Trace::hhbbc_iface
)) {
2899 trace_interfaces(index
, cg
);
2903 void mark_magic_on_parents(ClassInfo
& cinfo
, ClassInfo
& derived
) {
2905 for (auto& kv
: magicMethodMap
) {
2906 if ((derived
.*kv
.second
.pmem
).thisHas
) {
2907 auto& derivedHas
= (cinfo
.*kv
.second
.pmem
).derivedHas
;
2909 derivedHas
= any
= true;
2914 if (cinfo
.parent
) mark_magic_on_parents(*cinfo
.parent
, derived
);
2915 for (auto iface
: cinfo
.declInterfaces
) {
2916 mark_magic_on_parents(*const_cast<ClassInfo
*>(iface
), derived
);
2920 bool has_magic_method(const ClassInfo
* cinfo
, SString name
) {
2921 if (name
== s_toBoolean
.get()) {
2922 // note that "having" a magic method includes the possibility that
2923 // a parent class has it. This can't happen for the collection
2924 // classes, because they're all final; but for SimpleXMLElement,
2925 // we need to search.
2926 while (cinfo
->parent
) cinfo
= cinfo
->parent
;
2927 return has_magic_bool_conversion(cinfo
->cls
->name
);
2929 return cinfo
->methods
.find(name
) != end(cinfo
->methods
);
2932 void find_magic_methods(IndexData
& index
) {
2933 for (auto& cinfo
: index
.allClassInfos
) {
2935 for (auto& kv
: magicMethodMap
) {
2936 bool const found
= has_magic_method(cinfo
.get(), kv
.first
);
2938 (cinfo
.get()->*kv
.second
.pmem
).thisHas
= found
;
2940 if (any
) mark_magic_on_parents(*cinfo
, *cinfo
);
2944 void find_mocked_classes(IndexData
& index
) {
2945 for (auto& cinfo
: index
.allClassInfos
) {
2946 if (is_mock_class(cinfo
->cls
) && cinfo
->parent
) {
2947 cinfo
->parent
->isMocked
= true;
2948 for (auto c
= cinfo
->parent
; c
; c
= c
->parent
) {
2949 c
->isDerivedMocked
= true;
2955 void mark_no_override_classes(IndexData
& index
) {
2956 for (auto& cinfo
: index
.allClassInfos
) {
2957 // We cleared all the NoOverride flags while building the
2958 // index. Set them as necessary.
2959 if (!(cinfo
->cls
->attrs
& AttrUnique
)) continue;
2960 if (!(cinfo
->cls
->attrs
& AttrInterface
) &&
2961 cinfo
->subclassList
.size() == 1) {
2962 attribute_setter(cinfo
->cls
->attrs
, true, AttrNoOverride
);
2965 for (auto& kv
: magicMethodMap
) {
2966 if (kv
.second
.attrBit
== AttrNone
) continue;
2967 if (!(cinfo
.get()->*kv
.second
.pmem
).derivedHas
) {
2968 FTRACE(2, "Adding no-override of {} to {}\n",
2971 attribute_setter(cinfo
->cls
->attrs
, true, kv
.second
.attrBit
);
2977 void mark_no_override_methods(IndexData
& index
) {
2978 // We removed any AttrNoOverride flags from all methods while adding
2979 // the units to the index. Now start by marking every
2980 // (non-interface, non-special) method as AttrNoOverride.
2981 for (auto& cinfo
: index
.allClassInfos
) {
2982 if (cinfo
->cls
->attrs
& AttrInterface
) continue;
2983 if (!(cinfo
->cls
->attrs
& AttrUnique
)) continue;
2985 for (auto& m
: cinfo
->methods
) {
2986 if (!(is_special_method_name(m
.first
))) {
2987 FTRACE(9, "Pre-setting AttrNoOverride on {}::{}\n",
2988 m
.second
.func
->cls
->name
, m
.first
);
2989 attribute_setter(m
.second
.attrs
, true, AttrNoOverride
);
2990 attribute_setter(m
.second
.func
->attrs
, true, AttrNoOverride
);
2995 // Then run through every ClassInfo, and for each of its parent classes clear
2996 // the AttrNoOverride flag if it has a different Func with the same name.
2997 for (auto& cinfo
: index
.allClassInfos
) {
2998 for (auto& ancestor
: cinfo
->baseList
) {
2999 if (ancestor
== cinfo
.get()) continue;
3001 auto removeNoOverride
= [] (auto it
) {
3002 assertx(it
->second
.attrs
& AttrNoOverride
||
3003 !(it
->second
.func
->attrs
& AttrNoOverride
));
3004 if (it
->second
.attrs
& AttrNoOverride
) {
3005 FTRACE(2, "Removing AttrNoOverride on {}::{}\n",
3006 it
->second
.func
->cls
->name
, it
->first
);
3007 attribute_setter(it
->second
.attrs
, false, AttrNoOverride
);
3008 attribute_setter(it
->second
.func
->attrs
, false, AttrNoOverride
);
3012 for (auto& derivedMethod
: cinfo
->methods
) {
3013 auto const it
= ancestor
->methods
.find(derivedMethod
.first
);
3014 if (it
== end(ancestor
->methods
)) continue;
3015 if (it
->second
.func
!= derivedMethod
.second
.func
) {
3016 removeNoOverride(it
);
3023 template <class T
, class F
>
3024 void mark_unique_entities(ISStringToMany
<T
>& entities
, F marker
) {
3025 for (auto it
= entities
.begin(), end
= entities
.end(); it
!= end
; ) {
3028 while (it
!= end
&& it
->first
->isame(first
->first
)) {
3029 marker(it
++->second
, false);
3032 marker(first
->second
, flag
);
3036 const StaticString
s__Reified("__Reified");
3039 * Emitter adds a 86reifiedinit method to all classes that have reified
3040 * generics. All base classes also need to have this method so that when we
3041 * call parent::86reifeidinit(...), there is a stopping point.
3042 * Since while emitting we do not know whether a base class will have
3043 * reified parents, during JIT time we need to add 86reifiedinit
3044 * unless AttrNoReifiedInit attribute is set. At this phase,
3045 * we set AttrNoReifiedInit attribute on classes do not have any
3046 * reified classes that extend it.
3048 void clean_86reifiedinit_methods(IndexData
& index
) {
3049 trace_time
tracer("clean 86reifiedinit methods");
3050 folly::F14FastSet
<const php::Class
*> needsinit
;
3052 // Find all classes that still need their 86reifiedinit methods
3053 for (auto& cinfo
: index
.allClassInfos
) {
3054 auto ual
= cinfo
->cls
->userAttributes
;
3055 // Each class that has at least one reified generic has an attribute
3056 // __Reified added by the emitter
3057 auto has_reification
= ual
.find(s__Reified
.get()) != ual
.end();
3058 if (!has_reification
) continue;
3059 // Add the base class for this reified class
3060 needsinit
.emplace(cinfo
->baseList
[0]->cls
);
3063 // Add AttrNoReifiedInit to the base classes that do not need this method
3064 for (auto& cinfo
: index
.allClassInfos
) {
3065 if (cinfo
->parent
== nullptr && needsinit
.count(cinfo
->cls
) == 0) {
3066 FTRACE(2, "Adding AttrNoReifiedInit on class {}\n", cinfo
->cls
->name
);
3067 attribute_setter(cinfo
->cls
->attrs
, true, AttrNoReifiedInit
);
3072 //////////////////////////////////////////////////////////////////////
3074 void check_invariants(const ClassInfo
* cinfo
) {
3075 // All the following invariants only apply to classes
3076 if (cinfo
->cls
->attrs
& AttrInterface
) return;
3078 if (!(cinfo
->cls
->attrs
& AttrTrait
)) {
3079 // For non-interface classes, each method in a php class has an
3080 // entry in its ClassInfo method table, and if it's not special,
3081 // AttrNoOverride, or private, an entry in the family table.
3082 for (auto& m
: cinfo
->cls
->methods
) {
3083 auto const it
= cinfo
->methods
.find(m
->name
);
3084 always_assert(it
!= cinfo
->methods
.end());
3085 if (it
->second
.attrs
& (AttrNoOverride
|AttrPrivate
)) continue;
3086 if (is_special_method_name(m
->name
)) continue;
3087 always_assert(cinfo
->methodFamilies
.count(m
->name
));
3091 // The subclassList is non-empty, contains this ClassInfo, and
3092 // contains only unique elements.
3093 always_assert(!cinfo
->subclassList
.empty());
3094 always_assert(std::find(begin(cinfo
->subclassList
),
3095 end(cinfo
->subclassList
),
3096 cinfo
) != end(cinfo
->subclassList
));
3097 auto cpy
= cinfo
->subclassList
;
3098 std::sort(begin(cpy
), end(cpy
));
3100 std::unique(begin(cpy
), end(cpy
)),
3103 always_assert(cpy
.size() == cinfo
->subclassList
.size());
3105 // The baseList is non-empty, and the last element is this class.
3106 always_assert(!cinfo
->baseList
.empty());
3107 always_assert(cinfo
->baseList
.back() == cinfo
);
3109 for (auto& kv
: magicMethodMap
) {
3110 auto& info
= cinfo
->*kv
.second
.pmem
;
3112 // Magic method flags should be consistent with the method table.
3113 always_assert(info
.thisHas
== has_magic_method(cinfo
, kv
.first
));
3115 // Non-'derived' flags (thisHas) about magic methods imply the derived
3117 always_assert(!info
.thisHas
|| info
.derivedHas
);
3120 // Every FuncFamily is non-empty and contain functions with the same
3121 // name (unless its a family of ctors).
3122 for (auto const& mfam
: cinfo
->methodFamilies
) {
3123 always_assert(!mfam
.second
.possibleFuncs()->empty());
3124 auto const name
= mfam
.second
.possibleFuncs()->front()->first
;
3125 for (auto const pf
: mfam
.second
.possibleFuncs()) {
3126 always_assert(pf
->first
->isame(name
));
3131 void check_invariants(IndexData
& data
) {
3134 // Every AttrUnique non-trait class has a unique ClassInfo object,
3135 // or no ClassInfo object in the case that instantiating it would've
3137 for (auto& kv
: data
.classes
) {
3138 auto const name
= kv
.first
;
3139 auto const cls
= kv
.second
;
3140 if (!(cls
->attrs
& AttrUnique
)) continue;
3142 auto const range
= find_range(data
.classInfo
, name
);
3143 if (begin(range
) != end(range
)) {
3144 always_assert(std::next(begin(range
)) == end(range
));
3148 for (auto& cinfo
: data
.allClassInfos
) {
3149 check_invariants(cinfo
.get());
3153 //////////////////////////////////////////////////////////////////////
3155 Type
context_sensitive_return_type(IndexData
& data
,
3156 CallContext callCtx
) {
3157 constexpr auto max_interp_nexting_level
= 2;
3158 static __thread
uint32_t interp_nesting_level
;
3159 auto const finfo
= func_info(data
, callCtx
.callee
);
3160 auto const returnType
= return_with_context(finfo
->returnTy
, callCtx
.context
);
3162 auto checkParam
= [&] (int i
) {
3163 auto const constraint
= finfo
->func
->params
[i
].typeConstraint
;
3164 if (constraint
.hasConstraint() &&
3165 !constraint
.isTypeVar() &&
3166 !constraint
.isTypeConstant()) {
3167 auto ctx
= Context
{
3169 const_cast<php::Func
*>(finfo
->func
),
3172 auto t
= loosen_dvarrayness(
3173 data
.m_index
->lookup_constraint(ctx
, constraint
));
3174 return callCtx
.args
[i
].strictlyMoreRefined(t
);
3176 return callCtx
.args
[i
].strictSubtypeOf(TInitCell
);
3179 // TODO(#3788877): more heuristics here would be useful.
3180 bool const tryContextSensitive
= [&] {
3181 if (finfo
->func
->noContextSensitiveAnalysis
||
3182 finfo
->func
->params
.empty() ||
3183 interp_nesting_level
+ 1 >= max_interp_nexting_level
||
3184 returnType
== TBottom
) {
3188 if (finfo
->retParam
!= NoLocalId
&&
3189 callCtx
.args
.size() > finfo
->retParam
&&
3190 checkParam(finfo
->retParam
)) {
3194 if (!options
.ContextSensitiveInterp
) return false;
3196 if (callCtx
.args
.size() < finfo
->func
->params
.size()) return true;
3197 for (auto i
= 0; i
< finfo
->func
->params
.size(); i
++) {
3198 if (checkParam(i
)) return true;
3203 if (!tryContextSensitive
) {
3207 auto maybe_loosen_staticness
= [&] (const Type
& ty
) {
3208 return returnType
.subtypeOf(BUnc
) ? ty
: loosen_staticness(ty
);
3212 ContextRetTyMap::const_accessor acc
;
3213 if (data
.contextualReturnTypes
.find(acc
, callCtx
)) {
3214 if (data
.frozen
|| acc
->second
== TBottom
|| is_scalar(acc
->second
)) {
3215 return maybe_loosen_staticness(acc
->second
);
3224 auto contextType
= [&] {
3225 ++interp_nesting_level
;
3226 SCOPE_EXIT
{ --interp_nesting_level
; };
3228 auto const calleeCtx
= Context
{
3230 const_cast<php::Func
*>(finfo
->func
),
3234 analyze_func_inline(*data
.m_index
, calleeCtx
,
3235 callCtx
.context
, callCtx
.args
).inferredReturn
;
3236 return return_with_context(ty
, callCtx
.context
);
3239 if (!interp_nesting_level
) {
3241 "Context sensitive type: {}\n"
3242 "Context insensitive type: {}\n",
3243 show(contextType
), show(returnType
));
3246 auto ret
= intersection_of(std::move(returnType
),
3247 std::move(contextType
));
3249 ContextRetTyMap::accessor acc
;
3250 if (data
.contextualReturnTypes
.insert(acc
, callCtx
) ||
3251 ret
.strictSubtypeOf(acc
->second
)) {
3255 if (!interp_nesting_level
) {
3256 ret
= maybe_loosen_staticness(ret
);
3257 FTRACE(3, "Context sensitive result: {}\n", show(ret
));
3263 //////////////////////////////////////////////////////////////////////
3265 PrepKind
func_param_prep(const php::Func
* func
,
3267 if (func
->attrs
& AttrInterceptable
) return PrepKind::Unknown
;
3268 if (paramId
>= func
->params
.size()) {
3269 return PrepKind::Val
;
3271 return func
->params
[paramId
].byRef
? PrepKind::Ref
: PrepKind::Val
;
3274 template<class PossibleFuncRange
>
3275 PrepKind
prep_kind_from_set(PossibleFuncRange range
, uint32_t paramId
) {
3278 * In sinlge-unit mode, the range is not complete. Without konwing all
3279 * possible resolutions, HHBBC cannot deduce anything about by-ref vs by-val.
3280 * So the caller should make sure not calling this in single-unit mode.
3282 assert(RuntimeOption::RepoAuthoritative
);
3284 if (begin(range
) == end(range
)) {
3286 * We can assume it's by value, because either we're calling a function
3287 * that doesn't exist (about to fatal), or we're going to an __call (which
3288 * never takes parameters by reference).
3290 * Or if we've got AllFuncsInterceptable we need to assume someone could
3291 * rename a function to the new name.
3293 return RuntimeOption::EvalJitEnableRenameFunction
?
3294 PrepKind::Unknown
: PrepKind::Val
;
3298 using F
= const php::Func
*;
3299 static F
get(std::pair
<SString
,F
> p
) { return p
.second
; }
3300 static F
get(const MethTabEntryPair
* mte
) { return mte
->second
.func
; }
3303 folly::Optional
<PrepKind
> prep
;
3304 for (auto& item
: range
) {
3305 switch (func_param_prep(FuncFind::get(item
), paramId
)) {
3306 case PrepKind::Unknown
:
3307 return PrepKind::Unknown
;
3309 if (prep
&& *prep
!= PrepKind::Ref
) return PrepKind::Unknown
;
3310 prep
= PrepKind::Ref
;
3313 if (prep
&& *prep
!= PrepKind::Val
) return PrepKind::Unknown
;
3314 prep
= PrepKind::Val
;
3321 template<typename F
> auto
3322 visit_parent_cinfo(const ClassInfo
* cinfo
, F fun
) -> decltype(fun(cinfo
)) {
3323 for (auto ci
= cinfo
; ci
!= nullptr; ci
= ci
->parent
) {
3324 if (auto const ret
= fun(ci
)) return ret
;
3325 if (ci
->cls
->attrs
& AttrNoExpandTrait
) continue;
3326 for (auto ct
: ci
->usedTraits
) {
3327 if (auto const ret
= visit_parent_cinfo(ct
, fun
)) {
3335 PublicSPropEntry
lookup_public_static_impl(
3336 const IndexData
& data
,
3337 const ClassInfo
* cinfo
,
3340 auto const noInfo
= PublicSPropEntry
{TInitGen
, TInitGen
, nullptr, 0, true};
3342 if (data
.allPublicSPropsUnknown
) return noInfo
;
3344 const ClassInfo
* knownCInfo
= nullptr;
3345 auto const knownClsPart
= visit_parent_cinfo(
3347 [&] (const ClassInfo
* ci
) -> const PublicSPropEntry
* {
3348 auto const it
= ci
->publicStaticProps
.find(prop
);
3349 if (it
!= end(ci
->publicStaticProps
)) {
3357 auto const unkPart
= [&]() -> const Type
* {
3358 auto unkIt
= data
.unknownClassSProps
.find(prop
);
3359 if (unkIt
!= end(data
.unknownClassSProps
)) {
3360 return &unkIt
->second
.first
;
3365 if (knownClsPart
== nullptr) {
3369 // NB: Inferred type can be TBottom here if the property is never set to a
3370 // value which can satisfy its type constraint. Such properties can't exist at
3373 if (unkPart
!= nullptr) {
3374 return PublicSPropEntry
{
3376 knownClsPart
->inferredType
,
3379 knownClsPart
->initializerType
,
3385 return *knownClsPart
;
3388 PublicSPropEntry
lookup_public_static_impl(
3389 const IndexData
& data
,
3390 const php::Class
* cls
,
3393 auto const classes
= find_range(data
.classInfo
, cls
->name
);
3394 if (begin(classes
) == end(classes
) ||
3395 std::next(begin(classes
)) != end(classes
)) {
3396 return PublicSPropEntry
{TInitGen
, TInitGen
, nullptr, 0, true};
3398 return lookup_public_static_impl(data
, begin(classes
)->second
, name
);
3401 Type
lookup_public_prop_impl(
3402 const IndexData
& data
,
3403 const ClassInfo
* cinfo
,
3406 // Find a property declared in this class (or a parent) with the same name.
3407 const php::Class
* knownCls
= nullptr;
3408 auto const prop
= visit_parent_cinfo(
3410 [&] (const ClassInfo
* ci
) -> const php::Prop
* {
3411 for (auto const& prop
: ci
->cls
->properties
) {
3412 if (prop
.name
== propName
) {
3421 if (!prop
) return TGen
;
3422 // Make sure its non-static and public. Otherwise its another function's
3424 if (prop
->attrs
& (AttrStatic
| AttrPrivate
| AttrLateInitSoft
)) return TGen
;
3426 // Get a type corresponding to its declared type-hint (if any).
3427 auto ty
= adjust_type_for_prop(
3428 *data
.m_index
, *knownCls
, &prop
->typeConstraint
, TGen
3430 // We might have to include the initial value which might be outside of the
3432 auto initialTy
= loosen_all(from_cell(prop
->val
));
3433 if (!initialTy
.subtypeOf(TUninit
) && (prop
->attrs
& AttrSystemInitialValue
)) {
3439 //////////////////////////////////////////////////////////////////////
3443 //////////////////////////////////////////////////////////////////////
3445 Index::Index(php::Program
* program
,
3446 rebuild
* rebuild_exception
)
3447 : m_data(std::make_unique
<IndexData
>(this))
3449 trace_time
tracer("create index");
3451 m_data
->arrTableBuilder
.reset(new ArrayTypeTable::Builder());
3453 add_system_constants_to_index(*m_data
);
3455 if (rebuild_exception
) {
3456 for (auto& ca
: rebuild_exception
->class_aliases
) {
3457 m_data
->classAliases
.insert(ca
.first
);
3458 m_data
->classAliases
.insert(ca
.second
);
3460 rebuild_exception
->class_aliases
.clear();
3463 for (auto& u
: program
->units
) {
3464 add_unit_to_index(*m_data
, *u
);
3468 NamingEnv env
{program
, *m_data
};
3469 for (auto& u
: program
->units
) {
3470 Trace::Bump bumper
{Trace::hhbbc
, kSystemLibBump
, is_systemlib_part(*u
)};
3471 // iterate by index, because preresolve can add closures to the
3472 // end of u->classes via flatten_traits (which need to be
3473 // visited after they're added).
3474 for (size_t idx
= 0; idx
< u
->classes
.size(); idx
++) {
3475 auto const c
= u
->classes
[idx
].get();
3476 // Classes with no possible resolutions won't get visited in the
3477 // mark_persistent pass; make sure everything starts off with
3478 // the attributes clear.
3479 attrSetter(c
->attrs
, false, AttrUnique
| AttrPersistent
);
3481 // Manually set closure classes to be unique to maintain invariance.
3482 if (is_closure(*c
)) {
3483 attrSetter(c
->attrs
, true, AttrUnique
);
3485 preresolve(env
, c
->name
);
3490 mark_unique_entities(m_data
->typeAliases
,
3491 [&] (const php::TypeAlias
* ta
, bool flag
) {
3495 !m_data
->classInfo
.count(ta
->name
) &&
3496 !m_data
->classAliases
.count(ta
->name
),
3500 // Iterate allClassInfos so that we visit parent classes before
3502 for (auto& cinfo
: m_data
->allClassInfos
) {
3503 auto const set
= [&] {
3504 if (m_data
->classInfo
.count(cinfo
->cls
->name
) != 1 ||
3505 m_data
->typeAliases
.count(cinfo
->cls
->name
) ||
3506 m_data
->classAliases
.count(cinfo
->cls
->name
)) {
3509 if (cinfo
->parent
&& !(cinfo
->parent
->cls
->attrs
& AttrUnique
)) {
3512 for (auto const i
: cinfo
->declInterfaces
) {
3513 if (!(i
->cls
->attrs
& AttrUnique
)) return false;
3515 for (auto const t
: cinfo
->usedTraits
) {
3516 if (!(t
->cls
->attrs
& AttrUnique
)) return false;
3520 attribute_setter(cinfo
->cls
->attrs
, set
, AttrUnique
);
3523 mark_unique_entities(m_data
->funcs
,
3524 [&] (const php::Func
* func
, bool flag
) {
3525 attribute_setter(func
->attrs
, flag
, AttrUnique
);
3528 m_data
->funcInfo
.resize(program
->nextFuncId
);
3530 // Part of the index building routines happens before the various asserted
3531 // index invariants hold. These each may depend on computations from
3532 // previous functions, so be careful changing the order here.
3533 compute_subclass_list(*m_data
);
3534 clean_86reifiedinit_methods(*m_data
); // uses the base class lists
3535 mark_no_override_methods(*m_data
); // uses AttrUnique
3536 find_magic_methods(*m_data
); // uses the subclass lists
3537 find_mocked_classes(*m_data
);
3538 auto const logging
= Trace::moduleEnabledRelease(Trace::hhbbc_time
, 1);
3539 m_data
->compute_iface_vtables
= std::thread([&] {
3540 HphpSessionAndThread _
{Treadmill::SessionKind::HHBBC
};
3542 logging
&& !Trace::moduleEnabledRelease(Trace::hhbbc_time
, 1);
3543 Trace::BumpRelease
bumper(Trace::hhbbc_time
, -1, enable
);
3544 compute_iface_vtables(*m_data
);
3547 define_func_families(*m_data
); // AttrNoOverride, iface_vtables,
3550 check_invariants(*m_data
);
3552 mark_no_override_classes(*m_data
); // uses AttrUnique
3554 if (RuntimeOption::EvalCheckReturnTypeHints
== 3) {
3555 trace_time
tracer("initialize return types");
3556 std::vector
<const php::Func
*> all_funcs
;
3557 all_funcs
.reserve(m_data
->funcs
.size() + m_data
->methods
.size());
3558 for (auto const fn
: m_data
->funcs
) {
3559 all_funcs
.push_back(fn
.second
);
3561 for (auto const fn
: m_data
->methods
) {
3562 all_funcs
.push_back(fn
.second
);
3565 parallel::for_each(all_funcs
, [&] (const php::Func
* f
) {
3566 init_return_type(f
);
3571 // Defined here so IndexData is a complete type for the unique_ptr
3575 //////////////////////////////////////////////////////////////////////
3577 void Index::mark_persistent_classes_and_functions(php::Program
& program
) {
3578 auto persist
= [] (const php::Unit
* unit
) {
3580 unit
->persistent
.load(std::memory_order_relaxed
) &&
3581 unit
->persistent_pseudomain
.load(std::memory_order_relaxed
);
3583 for (auto& unit
: program
.units
) {
3584 auto const persistent
= persist(unit
.get());
3585 for (auto& f
: unit
->funcs
) {
3586 attribute_setter(f
->attrs
,
3587 persistent
&& (f
->attrs
& AttrUnique
),
3591 for (auto& t
: unit
->typeAliases
) {
3592 attribute_setter(t
->attrs
,
3593 persistent
&& (t
->attrs
& AttrUnique
),
3598 auto check_persistent
= [&] (const ClassInfo
& cinfo
) {
3599 if (cinfo
.parent
&& !(cinfo
.parent
->cls
->attrs
& AttrPersistent
)) {
3603 for (auto const intrf
: cinfo
.declInterfaces
) {
3604 if (!(intrf
->cls
->attrs
& AttrPersistent
)) return false;
3610 for (auto& c
: m_data
->allClassInfos
) {
3611 attribute_setter(c
->cls
->attrs
,
3612 (c
->cls
->attrs
& AttrUnique
) &&
3613 (persist(c
->cls
->unit
) ||
3614 c
->cls
->parentName
== s_Closure
.get()) &&
3615 check_persistent(*c
),
3620 void Index::mark_no_bad_redeclare_props(php::Class
& cls
) const {
3622 * Keep a list of properties which have not yet been found to redeclare
3623 * anything inequivalently. Start out by putting everything on the list. Then
3624 * walk up the inheritance chain, removing collisions as we find them.
3626 std::vector
<php::Prop
*> props
;
3627 for (auto& prop
: cls
.properties
) {
3628 if (prop
.attrs
& (AttrStatic
| AttrPrivate
)) {
3629 // Static and private properties never redeclare anything so need not be
3631 attribute_setter(prop
.attrs
, true, AttrNoBadRedeclare
);
3634 attribute_setter(prop
.attrs
, false, AttrNoBadRedeclare
);
3635 props
.emplace_back(&prop
);
3638 auto currentCls
= [&]() -> const ClassInfo
* {
3639 auto const rcls
= resolve_class(&cls
);
3640 if (rcls
.val
.left()) return nullptr;
3641 return rcls
.val
.right();
3643 // If there's one more than one resolution for the class, be conservative and
3644 // we'll treat everything as possibly redeclaring.
3645 if (!currentCls
) props
.clear();
3647 while (!props
.empty()) {
3648 auto const parent
= currentCls
->parent
;
3650 // No parent. We're done, so anything left on the prop list is
3651 // AttrNoBadRedeclare.
3652 for (auto& prop
: props
) {
3653 attribute_setter(prop
->attrs
, true, AttrNoBadRedeclare
);
3658 auto const findParentProp
= [&] (SString name
) -> const php::Prop
* {
3659 for (auto& prop
: parent
->cls
->properties
) {
3660 if (prop
.name
== name
) return &prop
;
3662 for (auto& prop
: parent
->traitProps
) {
3663 if (prop
.name
== name
) return &prop
;
3668 // Remove any properties which collide with the current class.
3670 auto const propRedeclares
= [&] (php::Prop
* prop
) {
3671 auto const pprop
= findParentProp(prop
->name
);
3672 if (!pprop
) return false;
3674 // We found a property being redeclared. Check if the type-hints on
3675 // the two are equivalent.
3676 auto const equiv
= [&] {
3677 auto const& tc1
= prop
->typeConstraint
;
3678 auto const& tc2
= pprop
->typeConstraint
;
3679 // Try the cheap check first, use the index otherwise. Two
3680 // type-constraints are equivalent if all the possible values of one
3681 // satisfies the other, and vice-versa.
3682 if (!tc1
.maybeInequivalentForProp(tc2
)) return true;
3684 satisfies_constraint(
3686 lookup_constraint(Context
{}, tc1
),
3688 ) && satisfies_constraint(
3690 lookup_constraint(Context
{}, tc2
),
3694 // If the property in the parent is static or private, the property in
3695 // the child isn't actually redeclaring anything. Otherwise, if the
3696 // type-hints are equivalent, remove this property from further
3697 // consideration and mark it as AttrNoBadRedeclare.
3698 if ((pprop
->attrs
& (AttrStatic
| AttrPrivate
)) || equiv()) {
3699 attribute_setter(prop
->attrs
, true, AttrNoBadRedeclare
);
3705 std::remove_if(props
.begin(), props
.end(), propRedeclares
),
3709 currentCls
= parent
;
3712 auto const possibleOverride
=
3714 cls
.properties
.begin(),
3715 cls
.properties
.end(),
3716 [&](const php::Prop
& prop
) { return !(prop
.attrs
& AttrNoBadRedeclare
); }
3719 // Mark all resolutions of this class as having any possible bad redeclaration
3720 // props, even if there's not an unique resolution.
3721 for (auto& info
: find_range(m_data
->classInfo
, cls
.name
)) {
3722 auto const cinfo
= info
.second
;
3723 if (cinfo
->cls
!= &cls
) continue;
3724 cinfo
->hasBadRedeclareProp
= possibleOverride
;
3729 * Rewrite the initial values for any AttrSystemInitialValue properties. If the
3730 * properties' type-hint does not admit null values, change the initial value to
3731 * one (if possible) to one that is not null. This is only safe to do so if the
3732 * property is not redeclared in a derived class or if the redeclaration does
3733 * not have a null system provided default value. Otherwise, a property can have
3734 * a null value (even if its type-hint doesn't allow it) without the JIT
3735 * realizing that its possible.
3737 * Note that this ignores any unflattened traits. This is okay because
3738 * properties pulled in from traits which match an already existing property
3739 * can't change the initial value. The runtime will clear AttrNoImplicitNullable
3740 * on any property pulled from the trait if it doesn't match an existing
3743 void Index::rewrite_default_initial_values(php::Program
& program
) const {
3744 trace_time
tracer("rewrite default initial values");
3747 * Use dataflow across the whole program class hierarchy. Start from the
3748 * classes which have no derived classes and flow up the hierarchy. We flow
3749 * the set of properties which have been assigned a null system provided
3750 * default value. If a property with such a null value flows into a class
3751 * which declares a property with the same name (and isn't static or private),
3752 * than that property is forced to be null as well.
3754 using PropSet
= folly::F14FastSet
<SString
>;
3755 using OutState
= folly::F14FastMap
<const ClassInfo
*, PropSet
>;
3756 using Worklist
= folly::F14FastSet
<const ClassInfo
*>;
3759 outStates
.reserve(m_data
->allClassInfos
.size());
3761 // List of Class' still to process this iteration
3762 using WorkList
= std::vector
<const ClassInfo
*>;
3763 using WorkSet
= folly::F14FastSet
<const ClassInfo
*>;
3767 auto const enqueue
= [&] (const ClassInfo
& cls
) {
3768 auto const result
= workSet
.insert(&cls
);
3769 if (!result
.second
) return;
3770 workList
.emplace_back(&cls
);
3773 // Start with all the leaf classes
3774 for (auto const& cinfo
: m_data
->allClassInfos
) {
3775 auto const isLeaf
= [&] {
3776 for (auto const& sub
: cinfo
->subclassList
) {
3777 if (sub
!= cinfo
.get()) return false;
3781 if (isLeaf
) enqueue(*cinfo
);
3784 WorkList oldWorkList
;
3786 while (!workList
.empty()) {
3788 4, "rewrite_default_initial_values round #{}: {} items\n",
3789 iter
, workList
.size()
3793 std::swap(workList
, oldWorkList
);
3796 for (auto const& cinfo
: oldWorkList
) {
3797 // Retrieve the set of properties which are flowing into this Class and
3799 auto inState
= [&] () -> folly::Optional
<PropSet
> {
3801 for (auto const& sub
: cinfo
->subclassList
) {
3802 if (sub
== cinfo
|| sub
->parent
!= cinfo
) continue;
3803 auto const it
= outStates
.find(sub
);
3804 if (it
== outStates
.end()) return folly::none
;
3805 in
.insert(it
->second
.begin(), it
->second
.end());
3809 if (!inState
) continue;
3811 // Modify the in-state depending on the properties declared on this Class
3812 auto const cls
= cinfo
->cls
;
3813 for (auto const& prop
: cls
->properties
) {
3814 if (prop
.attrs
& (AttrStatic
| AttrPrivate
)) {
3815 // Private or static properties can't be redeclared
3816 inState
->erase(prop
.name
);
3819 // Ignore properties which have actual user provided initial values or
3821 if (!(prop
.attrs
& AttrSystemInitialValue
) ||
3822 (prop
.attrs
& AttrLateInit
)) {
3825 // Forced to be null, nothing to do
3826 if (inState
->count(prop
.name
) > 0) continue;
3828 // Its not forced to be null. Find a better default value. If its null
3829 // anyways, force any properties this redeclares to be null as well.
3830 auto const defaultValue
= prop
.typeConstraint
.defaultValue();
3831 if (defaultValue
.m_type
== KindOfNull
) inState
->insert(prop
.name
);
3834 // Push the in-state to the out-state.
3835 auto const result
= outStates
.emplace(std::make_pair(cinfo
, *inState
));
3836 if (result
.second
) {
3837 if (cinfo
->parent
) enqueue(*cinfo
->parent
);
3839 // There shouldn't be cycles in the inheritance tree, so the out state
3840 // of Class', once set, should never change.
3841 assertx(result
.first
->second
== *inState
);
3846 // Now that we've processed all the classes, rewrite the property initial
3847 // values, unless they are forced to be nullable.
3848 for (auto& unit
: program
.units
) {
3849 for (auto& c
: unit
->classes
) {
3850 if (is_closure(*c
)) continue;
3852 auto const out
= [&] () -> folly::Optional
<PropSet
> {
3853 folly::Optional
<PropSet
> props
;
3854 auto const range
= m_data
->classInfo
.equal_range(c
->name
);
3855 for (auto it
= range
.first
; it
!= range
.second
; ++it
) {
3856 if (it
->second
->cls
!= c
.get()) continue;
3857 auto const outStateIt
= outStates
.find(it
->second
);
3858 if (outStateIt
== outStates
.end()) return folly::none
;
3859 if (!props
) props
.emplace();
3860 props
->insert(outStateIt
->second
.begin(), outStateIt
->second
.end());
3865 for (auto& prop
: c
->properties
) {
3866 auto const nullable
= [&] {
3867 if (!(prop
.attrs
& (AttrStatic
| AttrPrivate
))) {
3868 if (!out
|| out
->count(prop
.name
)) return true;
3870 if (!(prop
.attrs
& AttrSystemInitialValue
)) return false;
3871 return prop
.typeConstraint
.defaultValue().m_type
== KindOfNull
;
3874 attribute_setter(prop
.attrs
, !nullable
, AttrNoImplicitNullable
);
3875 if (!(prop
.attrs
& AttrSystemInitialValue
)) continue;
3876 if (prop
.val
.m_type
== KindOfUninit
) {
3877 assertx(prop
.attrs
& AttrLateInit
);
3882 ? make_tv
<KindOfNull
>()
3883 : prop
.typeConstraint
.defaultValue();
3889 bool Index::register_class_alias(SString orig
, SString alias
) const {
3890 auto check
= [&] (SString name
) {
3891 if (m_data
->classAliases
.count(name
)) return true;
3893 auto const classes
= find_range(m_data
->classInfo
, name
);
3894 if (begin(classes
) != end(classes
)) {
3895 return !(begin(classes
)->second
->cls
->attrs
& AttrUnique
);
3897 auto const tas
= find_range(m_data
->typeAliases
, name
);
3898 if (begin(tas
) == end(tas
)) return true;
3899 return !(begin(tas
)->second
->attrs
& AttrUnique
);
3901 if (check(orig
) && check(alias
)) return true;
3902 if (m_data
->ever_frozen
) return false;
3903 std::lock_guard
<std::mutex
> lock
{m_data
->pending_class_aliases_mutex
};
3904 m_data
->pending_class_aliases
.emplace_back(orig
, alias
);
3908 void Index::update_class_aliases() {
3909 if (m_data
->pending_class_aliases
.empty()) return;
3910 FTRACE(1, "Index needs rebuilding due to {} class aliases\n",
3911 m_data
->pending_class_aliases
.size());
3912 throw rebuild
{ std::move(m_data
->pending_class_aliases
) };
3915 const CompactVector
<const php::Class
*>*
3916 Index::lookup_closures(const php::Class
* cls
) const {
3917 auto const it
= m_data
->classClosureMap
.find(cls
);
3918 if (it
!= end(m_data
->classClosureMap
)) {
3924 const hphp_fast_set
<php::Func
*>*
3925 Index::lookup_extra_methods(const php::Class
* cls
) const {
3926 if (cls
->attrs
& AttrNoExpandTrait
) return nullptr;
3927 auto const it
= m_data
->classExtraMethodMap
.find(cls
);
3928 if (it
!= end(m_data
->classExtraMethodMap
)) {
3934 //////////////////////////////////////////////////////////////////////
3936 res::Class
Index::resolve_class(const php::Class
* cls
) const {
3938 ClassInfo
* result
= nullptr;
3940 auto const classes
= find_range(m_data
->classInfo
, cls
->name
);
3941 for (auto it
= begin(classes
); it
!= end(classes
); ++it
) {
3942 auto const cinfo
= it
->second
;
3943 if (cinfo
->cls
== cls
) {
3952 // The function is supposed to return a cinfo if we can uniquely resolve cls.
3953 // In repo mode, if there is only one cinfo, return it.
3954 // In non-repo mode, we don't know all the cinfo's. So "only one cinfo" does
3955 // not mean anything unless it is a built-in and we disable rename/intercept.
3956 if (result
&& (RuntimeOption::RepoAuthoritative
||
3957 (!RuntimeOption::EvalJitEnableRenameFunction
&&
3958 cls
->attrs
& AttrBuiltin
))) {
3959 return res::Class
{ this, result
};
3962 // We know its a class, not an enum or type alias, so return
3964 return res::Class
{ this, cls
->name
.get() };
3967 folly::Optional
<res::Class
> Index::resolve_class(Context ctx
,
3968 SString clsName
) const {
3969 clsName
= normalizeNS(clsName
);
3971 if (ctx
.cls
&& ctx
.cls
->name
->isame(clsName
)) return resolve_class(ctx
.cls
);
3974 * If there's only one preresolved ClassInfo, we can give out a
3975 * specific res::Class for it. (Any other possible resolutions were
3976 * known to fatal, or it was actually unique.)
3978 auto const classes
= find_range(m_data
->classInfo
, clsName
);
3979 for (auto it
= begin(classes
); it
!= end(classes
); ++it
) {
3980 auto const cinfo
= it
->second
;
3981 if (cinfo
->cls
->attrs
& AttrUnique
) {
3983 (std::next(it
) != end(classes
) ||
3984 m_data
->typeAliases
.count(clsName
))) {
3985 std::fprintf(stderr
, "non unique \"unique\" class: %s\n",
3986 cinfo
->cls
->name
->data());
3987 while (++it
!= end(classes
)) {
3988 std::fprintf(stderr
, " and %s\n", it
->second
->cls
->name
->data());
3990 auto const typeAliases
= find_range(m_data
->typeAliases
, clsName
);
3992 for (auto ta
= begin(typeAliases
); ta
!= end(typeAliases
); ++ta
) {
3993 std::fprintf(stderr
, " and type-alias %s\n",
3994 ta
->second
->name
->data());
3998 return res::Class
{ this, cinfo
};
4003 // We refuse to have name-only resolutions of enums, or typeAliases,
4004 // so that all name only resolutions can be treated as objects.
4005 if (!m_data
->enums
.count(clsName
) &&
4006 !m_data
->typeAliases
.count(clsName
)) {
4007 return res::Class
{ this, clsName
};
4013 folly::Optional
<res::Class
> Index::selfCls(const Context
& ctx
) const {
4014 if (!ctx
.cls
|| is_used_trait(*ctx
.cls
)) return folly::none
;
4015 return resolve_class(ctx
.cls
);
4018 folly::Optional
<res::Class
> Index::parentCls(const Context
& ctx
) const {
4019 if (!ctx
.cls
|| !ctx
.cls
->parentName
) return folly::none
;
4020 if (auto const parent
= resolve_class(ctx
.cls
).parent()) return parent
;
4021 return resolve_class(ctx
, ctx
.cls
->parentName
);
4024 Index::ResolvedInfo
<folly::Optional
<res::Class
>>
4025 Index::resolve_type_name(SString inName
) const {
4026 auto const res
= resolve_type_name_internal(inName
);
4032 : folly::make_optional(res::Class
{this, res
.value
})
4036 Index::ResolvedInfo
<Either
<SString
,ClassInfo
*>>
4037 Index::resolve_type_name_internal(SString inName
) const {
4038 folly::Optional
<hphp_fast_set
<const void*>> seen
;
4040 auto nullable
= false;
4043 for (unsigned i
= 0; ; ++i
) {
4044 name
= normalizeNS(name
);
4045 auto const rec_it
= m_data
->records
.find(name
);
4046 if (rec_it
!= m_data
->records
.end()) {
4047 return { AnnotType::Record
, nullable
, nullptr };
4049 auto const classes
= find_range(m_data
->classInfo
, name
);
4050 auto const cls_it
= begin(classes
);
4051 if (cls_it
!= end(classes
)) {
4052 auto const cinfo
= cls_it
->second
;
4053 if (!(cinfo
->cls
->attrs
& AttrUnique
)) {
4054 if (!m_data
->enums
.count(name
) && !m_data
->typeAliases
.count(name
)) {
4057 return { AnnotType::Object
, false, nullptr };
4059 if (!(cinfo
->cls
->attrs
& AttrEnum
)) {
4060 return { AnnotType::Object
, nullable
, cinfo
};
4062 auto const& tc
= cinfo
->cls
->enumBaseTy
;
4063 assert(!tc
.isNullable());
4064 if (tc
.type() != AnnotType::Object
) {
4065 auto const type
= tc
.type() == AnnotType::Mixed
?
4066 AnnotType::ArrayKey
: tc
.type();
4067 return { type
, nullable
, tc
.typeName() };
4069 name
= tc
.typeName();
4071 auto const typeAliases
= find_range(m_data
->typeAliases
, name
);
4072 auto const ta_it
= begin(typeAliases
);
4073 if (ta_it
== end(typeAliases
)) break;
4074 auto const ta
= ta_it
->second
;
4075 if (!(ta
->attrs
& AttrUnique
)) {
4076 return { AnnotType::Object
, false, nullptr };
4078 nullable
= nullable
|| ta
->nullable
;
4079 if (ta
->type
!= AnnotType::Object
) {
4080 return { ta
->type
, nullable
, ta
->value
.get() };
4085 // deal with cycles. Since we don't expect to
4086 // encounter them, just use a counter until we hit a chain length
4087 // of 10, then start tracking the names we resolve.
4091 } else if (i
> 10) {
4092 if (!seen
->insert(name
).second
) {
4093 return { AnnotType::Object
, false, nullptr };
4098 return { AnnotType::Object
, nullable
, name
};
4101 struct Index::ConstraintResolution
{
4102 /* implicit */ ConstraintResolution(Type type
)
4103 : type
{std::move(type
)}
4104 , maybeMixed
{false} {}
4105 ConstraintResolution(folly::Optional
<Type
> type
, bool maybeMixed
)
4106 : type
{std::move(type
)}
4107 , maybeMixed
{maybeMixed
} {}
4109 folly::Optional
<Type
> type
;
4113 Index::ConstraintResolution
Index::resolve_named_type(
4114 const Context
& ctx
, SString name
, const Type
& candidate
) const {
4116 auto const res
= resolve_type_name_internal(name
);
4118 if (res
.nullable
&& candidate
.subtypeOf(BInitNull
)) return TInitNull
;
4120 if (res
.type
== AnnotType::Object
) {
4121 auto resolve
= [&] (const res::Class
& rcls
) -> folly::Optional
<Type
> {
4122 if (!interface_supports_non_objects(rcls
.name()) ||
4123 candidate
.subtypeOrNull(BObj
)) {
4124 return subObj(rcls
);
4127 if (candidate
.subtypeOrNull(BArr
)) {
4128 if (interface_supports_array(rcls
.name())) return TArr
;
4129 } else if (candidate
.subtypeOrNull(BVec
)) {
4130 if (interface_supports_vec(rcls
.name())) return TVec
;
4131 } else if (candidate
.subtypeOrNull(BDict
)) {
4132 if (interface_supports_dict(rcls
.name())) return TDict
;
4133 } else if (candidate
.subtypeOrNull(BKeyset
)) {
4134 if (interface_supports_keyset(rcls
.name())) return TKeyset
;
4135 } else if (candidate
.subtypeOrNull(BStr
)) {
4136 if (interface_supports_string(rcls
.name())) return TStr
;
4137 } else if (candidate
.subtypeOrNull(BInt
)) {
4138 if (interface_supports_int(rcls
.name())) return TInt
;
4139 } else if (candidate
.subtypeOrNull(BDbl
)) {
4140 if (interface_supports_double(rcls
.name())) return TDbl
;
4145 if (res
.value
.isNull()) return ConstraintResolution
{ folly::none
, true };
4147 auto ty
= res
.value
.right() ?
4148 resolve({ this, res
.value
.right() }) :
4149 resolve({ this, res
.value
.left() });
4151 if (ty
&& res
.nullable
) *ty
= opt(std::move(*ty
));
4152 return ConstraintResolution
{ std::move(ty
), false };
4155 return get_type_for_annotated_type(ctx
, res
.type
, res
.nullable
,
4156 res
.value
.left(), candidate
);
4159 std::pair
<res::Class
,php::Class
*>
4160 Index::resolve_closure_class(Context ctx
, int32_t idx
) const {
4161 auto const cls
= ctx
.unit
->classes
[idx
].get();
4162 auto const rcls
= resolve_class(cls
);
4164 // Closure classes must be unique and defined in the unit that uses
4165 // the CreateCl opcode, so resolution must succeed.
4168 "A Closure class ({}) failed to resolve",
4172 return { rcls
, cls
};
4175 res::Class
Index::builtin_class(SString name
) const {
4176 auto const rcls
= resolve_class(Context
{}, name
);
4179 rcls
->val
.right() &&
4180 (rcls
->val
.right()->cls
->attrs
& AttrBuiltin
),
4181 "A builtin class ({}) failed to resolve",
4187 res::Func
Index::resolve_method(Context ctx
,
4189 SString name
) const {
4190 auto name_only
= [&] {
4191 return res::Func
{ this, res::Func::MethodName
{ name
} };
4194 if (!is_specialized_cls(clsType
)) {
4197 auto const dcls
= dcls_of(clsType
);
4198 auto const cinfo
= dcls
.cls
.val
.right();
4199 if (!cinfo
) return name_only();
4201 // Classes may have more method families than methods. Any such
4202 // method families are guaranteed to all be public so we can do this
4203 // lookup as a last gasp before resorting to name_only().
4204 auto const find_extra_method
= [&] {
4205 auto methIt
= cinfo
->methodFamilies
.find(name
);
4206 if (methIt
== end(cinfo
->methodFamilies
)) return name_only();
4207 if (methIt
->second
.possibleFuncs()->size() == 1) {
4208 return res::Func
{ this, methIt
->second
.possibleFuncs()->front() };
4210 // If there was a sole implementer we can resolve to a single method, even
4211 // if the method was not declared on the interface itself.
4212 return res::Func
{ this, &methIt
->second
};
4215 // Interfaces *only* have the extra methods defined for all
4217 if (cinfo
->cls
->attrs
& AttrInterface
) return find_extra_method();
4220 * Whether or not the context class has a private method with the
4221 * same name as the method we're trying to call.
4223 auto const contextMayHavePrivateWithSameName
= folly::lazy([&]() -> bool {
4224 if (!ctx
.cls
) return false;
4225 auto const range
= find_range(m_data
->classInfo
, ctx
.cls
->name
);
4226 if (begin(range
) == end(range
)) {
4227 // This class had no pre-resolved ClassInfos, which means it
4228 // always fatals in any way it could be defined, so it doesn't
4229 // matter what we return here (as all methods in the context
4230 // class are unreachable code).
4233 // Because of traits, each instantiation of the class could have
4234 // different private methods; we need to check them all.
4235 for (auto ctxInfo
: range
) {
4236 auto const iter
= ctxInfo
.second
->methods
.find(name
);
4237 if (iter
!= end(ctxInfo
.second
->methods
) &&
4238 iter
->second
.attrs
& AttrPrivate
&&
4239 iter
->second
.topLevel
) {
4247 * Look up the method in the target class.
4249 auto const methIt
= cinfo
->methods
.find(name
);
4250 if (methIt
== end(cinfo
->methods
)) return find_extra_method();
4251 if (methIt
->second
.attrs
& AttrInterceptable
) return name_only();
4252 auto const ftarget
= methIt
->second
.func
;
4254 // We need to revisit the hasPrivateAncestor code if we start being
4255 // able to look up methods on interfaces (currently they have empty
4257 assert(!(cinfo
->cls
->attrs
& AttrInterface
));
4260 * If our candidate method has a private ancestor, unless it is
4261 * defined on this class, we need to make sure we don't erroneously
4262 * resolve the overriding method if the call is coming from the
4263 * context the defines the private method.
4265 * For now this just gives up if the context and the callee class
4266 * could be related and the context defines a private of the same
4267 * name. (We should actually try to resolve that method, though.)
4269 if (methIt
->second
.hasPrivateAncestor
&&
4271 ctx
.cls
!= ftarget
->cls
) {
4272 if (could_be_related(ctx
.cls
, cinfo
->cls
)) {
4273 if (contextMayHavePrivateWithSameName()) {
4280 * Note: this currently isn't exhaustively checking accessibility,
4281 * except in cases where we must do a little bit of it for
4284 * It is generally ok to resolve a method that won't actually be
4285 * called as long, as we only do so in cases where it will fatal at
4288 * So, in the presence of magic methods, we must handle the fact
4289 * that attempting to call an inaccessible method will instead call
4290 * the magic method, if it exists. Note that if any class derives
4291 * from a class and adds magic methods, it can change still change
4292 * dispatch to call that method instead of fatalling.
4295 // If false, this method is definitely accessible. If true, it may
4296 // or may not be accessible.
4297 auto const couldBeInaccessible
= [&] {
4298 // Public is always accessible.
4299 if (methIt
->second
.attrs
& AttrPublic
) return false;
4300 // An anonymous context won't have access if it wasn't public.
4301 if (!ctx
.cls
) return true;
4302 // If the calling context class is the same as the target class,
4303 // and the method is defined on this class or is protected, it
4304 // must be accessible.
4305 if (ctx
.cls
== cinfo
->cls
&&
4306 (methIt
->second
.topLevel
|| methIt
->second
.attrs
& AttrProtected
)) {
4309 // If the method is private, the above case is the only case where
4310 // we'd know it was accessible.
4311 if (methIt
->second
.attrs
& AttrPrivate
) return true;
4313 * For the protected method case: if the context class must be
4314 * derived from the class that first defined the protected method
4315 * we know it is accessible. First check against the class of the
4316 * method (or cinfo for trait methods).
4318 if (must_be_derived_from(
4320 ftarget
->cls
->attrs
& AttrTrait
? cinfo
->cls
: ftarget
->cls
)) {
4323 if (methIt
->second
.hasAncestor
||
4324 (ftarget
->cls
->attrs
& AttrTrait
&& !methIt
->second
.topLevel
)) {
4325 // Now we have find the first class that defined the method, and
4326 // check if *that* is an ancestor of the context class.
4327 auto parent
= cinfo
->parent
;
4330 auto it
= parent
->methods
.find(name
);
4331 assertx(it
!= parent
->methods
.end());
4332 if (!it
->second
.hasAncestor
&& it
->second
.topLevel
) {
4333 if (must_be_derived_from(ctx
.cls
, parent
->cls
)) return false;
4336 parent
= parent
->parent
;
4340 * On the other hand, if the class that defined the method must be
4341 * derived from the context class, it is going to be accessible as
4342 * long as the context class does not define a private method with
4343 * the same name. (If it did, we'd be calling that private
4344 * method, which currently we don't ever resolve---we've removed
4345 * it from the method table in the classInfo.)
4347 if (must_be_derived_from(cinfo
->cls
, ctx
.cls
)) {
4348 if (!contextMayHavePrivateWithSameName()) {
4352 // Other cases we're not sure about (maybe some non-unique classes
4353 // got in the way). Conservatively return that it might be
4358 auto resolve
= [&] {
4359 create_func_info(*m_data
, ftarget
);
4360 return res::Func
{ this, mteFromIt(methIt
) };
4363 switch (dcls
.type
) {
4365 if (cinfo
->magicCall
.thisHas
) {
4366 if (couldBeInaccessible()) return name_only();
4370 if (cinfo
->magicCall
.derivedHas
) {
4371 if (couldBeInaccessible()) return name_only();
4373 if (methIt
->second
.attrs
& AttrNoOverride
) {
4376 if (!options
.FuncFamilies
) return name_only();
4379 auto const famIt
= cinfo
->methodFamilies
.find(name
);
4380 if (famIt
== end(cinfo
->methodFamilies
)) {
4383 if (famIt
->second
.containsInterceptables()) {
4386 return res::Func
{ this, &famIt
->second
};
4392 folly::Optional
<res::Func
>
4393 Index::resolve_ctor(Context
/*ctx*/, res::Class rcls
, bool exact
) const {
4394 auto const cinfo
= rcls
.val
.right();
4395 if (!cinfo
) return folly::none
;
4396 if (cinfo
->cls
->attrs
& (AttrInterface
|AttrTrait
)) return folly::none
;
4398 auto const cit
= cinfo
->methods
.find(s_construct
.get());
4399 if (cit
== end(cinfo
->methods
)) return folly::none
;
4401 auto const ctor
= mteFromIt(cit
);
4402 if (exact
|| ctor
->second
.attrs
& AttrNoOverride
) {
4403 if (ctor
->second
.attrs
& AttrInterceptable
) return folly::none
;
4404 create_func_info(*m_data
, ctor
->second
.func
);
4405 return res::Func
{ this, ctor
};
4408 if (!options
.FuncFamilies
) return folly::none
;
4410 auto const famIt
= cinfo
->methodFamilies
.find(s_construct
.get());
4411 if (famIt
== end(cinfo
->methodFamilies
)) return folly::none
;
4412 if (famIt
->second
.containsInterceptables()) return folly::none
;
4413 return res::Func
{ this, &famIt
->second
};
4416 template<class FuncRange
>
4418 Index::resolve_func_helper(const FuncRange
& funcs
, SString name
) const {
4419 auto name_only
= [&] (bool renamable
) {
4420 return res::Func
{ this, res::Func::FuncName
{ name
, renamable
} };
4424 if (begin(funcs
) == end(funcs
)) return name_only(false);
4426 auto const func
= begin(funcs
)->second
;
4427 if (func
->attrs
& AttrInterceptable
) return name_only(true);
4429 // multiple resolutions
4430 if (std::next(begin(funcs
)) != end(funcs
)) {
4431 assert(!(func
->attrs
& AttrUnique
));
4432 if (debug
&& any_interceptable_functions()) {
4433 for (auto const DEBUG_ONLY f
: funcs
) {
4434 assertx(!(f
.second
->attrs
& AttrInterceptable
));
4437 return name_only(false);
4440 // single resolution, in whole-program mode, that's it
4441 if (RuntimeOption::RepoAuthoritative
) {
4442 assert(func
->attrs
& AttrUnique
);
4443 return do_resolve(func
);
4446 // single-unit mode, check builtins
4447 if (func
->attrs
& AttrBuiltin
) {
4448 assert(func
->attrs
& AttrUnique
);
4449 return do_resolve(func
);
4452 // single-unit, non-builtin, not renamable
4453 return name_only(false);
4456 res::Func
Index::resolve_func(Context
/*ctx*/, SString name
) const {
4457 name
= normalizeNS(name
);
4458 auto const funcs
= find_range(m_data
->funcs
, name
);
4459 return resolve_func_helper(funcs
, name
);
4463 * Gets a type for the constraint.
4465 * If getSuperType is true, the type could be a super-type of the
4466 * actual type constraint (eg TCell). Otherwise its guaranteed that
4467 * for any t, t.subtypeOf(get_type_for_constraint<false>(ctx, tc, t)
4468 * implies t would pass the constraint.
4470 * The candidate type is used to disambiguate; if we're applying a
4471 * Traversable constraint to a TObj, we should return
4472 * subObj(Traversable). If we're applying it to an Array, we should
4475 template<bool getSuperType
>
4476 Type
Index::get_type_for_constraint(Context ctx
,
4477 const TypeConstraint
& tc
,
4478 const Type
& candidate
) const {
4479 assertx(IMPLIES(!tc
.isCheckable(), tc
.isMixed()));
4483 * Soft hints (@Foo) are not checked.
4485 if (tc
.isSoft()) return TCell
;
4488 auto const res
= get_type_for_annotated_type(
4495 if (res
.type
) return *res
.type
;
4496 // If the type constraint might be mixed, then the value could be
4497 // uninit. Any other type constraint implies TInitCell.
4498 return getSuperType
? (res
.maybeMixed
? TCell
: TInitCell
) : TBottom
;
4501 bool Index::prop_tc_maybe_unenforced(const php::Class
& propCls
,
4502 const TypeConstraint
& tc
) const {
4503 assertx(tc
.validForProp());
4504 if (RuntimeOption::EvalCheckPropTypeHints
<= 2) return true;
4505 if (!tc
.isCheckable()) return true;
4506 if (tc
.isSoft()) return true;
4507 auto const res
= get_type_for_annotated_type(
4508 Context
{ nullptr, nullptr, &propCls
},
4514 return res
.maybeMixed
;
4517 Index::ConstraintResolution
Index::get_type_for_annotated_type(
4518 Context ctx
, AnnotType annot
, bool nullable
,
4519 SString name
, const Type
& candidate
) const {
4521 if (candidate
.subtypeOf(BInitNull
) && nullable
) {
4525 auto mainType
= [&]() -> ConstraintResolution
{
4526 switch (getAnnotMetaType(annot
)) {
4527 case AnnotMetaType::Precise
: {
4528 auto const dt
= getAnnotDataType(annot
);
4531 case KindOfNull
: return TNull
;
4532 case KindOfBoolean
: return TBool
;
4533 case KindOfInt64
: return TInt
;
4534 case KindOfDouble
: return TDbl
;
4535 case KindOfPersistentString
:
4536 case KindOfString
: return TStr
;
4537 case KindOfPersistentVec
:
4538 case KindOfVec
: return TVec
;
4539 case KindOfPersistentDict
:
4540 case KindOfDict
: return TDict
;
4541 case KindOfPersistentKeyset
:
4542 case KindOfKeyset
: return TKeyset
;
4543 case KindOfPersistentShape
:
4544 case KindOfShape
: not_implemented();
4545 case KindOfPersistentArray
:
4546 case KindOfArray
: return TPArr
;
4547 case KindOfResource
: return TRes
;
4548 case KindOfClsMeth
: return TClsMeth
;
4549 case KindOfRecord
: return TRecord
;
4551 return resolve_named_type(ctx
, name
, candidate
);
4556 always_assert_flog(false, "Unexpected DataType");
4561 case AnnotMetaType::Mixed
:
4563 * Here we handle "mixed", typevars, and some other ignored
4564 * typehints (ex. "(function(..): ..)" typehints).
4566 return { TCell
, true };
4567 case AnnotMetaType::Nothing
:
4568 case AnnotMetaType::NoReturn
:
4570 case AnnotMetaType::Nonnull
:
4571 if (candidate
.subtypeOf(BInitNull
)) return TBottom
;
4572 if (!candidate
.couldBe(BInitNull
)) return candidate
;
4573 if (is_opt(candidate
)) return unopt(candidate
);
4575 case AnnotMetaType::This
:
4576 if (auto s
= selfCls(ctx
)) return setctx(subObj(*s
));
4578 case AnnotMetaType::Self
:
4579 if (auto s
= selfCls(ctx
)) return subObj(*s
);
4581 case AnnotMetaType::Parent
:
4582 if (auto p
= parentCls(ctx
)) return subObj(*p
);
4584 case AnnotMetaType::Callable
:
4586 case AnnotMetaType::Number
:
4588 case AnnotMetaType::ArrayKey
:
4589 if (candidate
.subtypeOf(BInt
)) return TInt
;
4590 if (candidate
.subtypeOf(BStr
)) return TStr
;
4592 case AnnotMetaType::VArray
:
4593 assertx(!RuntimeOption::EvalHackArrDVArrs
);
4595 case AnnotMetaType::DArray
:
4596 assertx(!RuntimeOption::EvalHackArrDVArrs
);
4598 case AnnotMetaType::VArrOrDArr
:
4599 assertx(!RuntimeOption::EvalHackArrDVArrs
);
4601 case AnnotMetaType::VecOrDict
:
4602 if (candidate
.subtypeOf(BVec
)) return TVec
;
4603 if (candidate
.subtypeOf(BDict
)) return TDict
;
4605 case AnnotMetaType::ArrayLike
:
4606 if (candidate
.subtypeOf(BVArr
)) return TVArr
;
4607 if (candidate
.subtypeOf(BDArr
)) return TDArr
;
4608 if (candidate
.subtypeOf(BArr
)) return TArr
;
4609 if (candidate
.subtypeOf(BVec
)) return TVec
;
4610 if (candidate
.subtypeOf(BDict
)) return TDict
;
4611 if (candidate
.subtypeOf(BKeyset
)) return TKeyset
;
4614 return ConstraintResolution
{ folly::none
, false };
4617 if (mainType
.type
&& nullable
&& !mainType
.type
->couldBe(BInitNull
)) {
4618 mainType
.type
= opt(*mainType
.type
);
4623 Type
Index::lookup_constraint(Context ctx
,
4624 const TypeConstraint
& tc
,
4625 const Type
& t
) const {
4626 return get_type_for_constraint
<true>(ctx
, tc
, t
);
4629 bool Index::satisfies_constraint(Context ctx
, const Type
& t
,
4630 const TypeConstraint
& tc
) const {
4631 // T45709201: Currently record types in HHBBC are not specialized.
4632 // Therefore, they can never satisfy a constrant.
4633 if (t
.subtypeOf(BOptRecord
)) return false;
4634 auto const tcType
= get_type_for_constraint
<false>(ctx
, tc
, t
);
4635 if (t
.moreRefined(loosen_dvarrayness(tcType
))) {
4636 // For d/varrays, we might satisfy the constraint, but still not want to
4637 // optimize away the type-check (because we'll raise a notice on a d/varray
4638 // mismatch), so do some additional checking here to rule that out.
4639 if (!RuntimeOption::EvalHackArrCompatTypeHintNotices
) return true;
4640 if (!tcType
.subtypeOrNull(BArr
) || tcType
.subtypeOf(BNull
)) return true;
4641 assertx(t
.subtypeOrNull(BArr
));
4642 if (tcType
.subtypeOrNull(BVArr
)) return t
.subtypeOrNull(BVArr
);
4643 if (tcType
.subtypeOrNull(BDArr
)) return t
.subtypeOrNull(BDArr
);
4644 if (tcType
.subtypeOrNull(BPArr
)) return t
.subtypeOrNull(BPArr
);
4649 bool Index::could_have_reified_type(const TypeConstraint
& tc
) const {
4650 if (!tc
.isObject()) return false;
4651 auto const name
= tc
.typeName();
4652 auto const resolved
= resolve_type_name_internal(name
);
4653 if (resolved
.type
!= AnnotType::Object
) return false;
4654 res::Class rcls
{this, resolved
.value
};
4655 return rcls
.couldHaveReifiedGenerics();
4658 folly::Optional
<bool>
4659 Index::supports_async_eager_return(res::Func rfunc
) const {
4660 auto const supportsAER
= [] (const php::Func
* func
) {
4661 // Async functions always support async eager return.
4662 if (func
->isAsync
&& !func
->isGenerator
) return true;
4664 // No other functions support async eager return yet.
4668 return match
<folly::Optional
<bool>>(
4670 [&](res::Func::FuncName
) { return folly::none
; },
4671 [&](res::Func::MethodName
) { return folly::none
; },
4672 [&](FuncInfo
* finfo
) { return supportsAER(finfo
->func
); },
4673 [&](const MethTabEntryPair
* mte
) { return supportsAER(mte
->second
.func
); },
4674 [&](FuncFamily
* fam
) -> folly::Optional
<bool> {
4675 auto ret
= folly::Optional
<bool>{};
4676 for (auto const pf
: fam
->possibleFuncs()) {
4677 // Abstract functions are never called.
4678 if (pf
->second
.attrs
& AttrAbstract
) continue;
4679 auto const val
= supportsAER(pf
->second
.func
);
4680 if (ret
&& *ret
!= val
) return folly::none
;
4687 bool Index::is_effect_free(res::Func rfunc
) const {
4690 [&](res::Func::FuncName
) { return false; },
4691 [&](res::Func::MethodName
) { return false; },
4692 [&](FuncInfo
* finfo
) {
4693 return finfo
->effectFree
;
4695 [&](const MethTabEntryPair
* mte
) {
4696 return func_info(*m_data
, mte
->second
.func
)->effectFree
;
4698 [&](FuncFamily
* fam
) {
4703 bool Index::any_interceptable_functions() const {
4704 return m_data
->any_interceptable_functions
;
4707 const php::Const
* Index::lookup_class_const_ptr(Context ctx
,
4710 bool allow_tconst
) const {
4711 if (rcls
.val
.left()) return nullptr;
4712 auto const cinfo
= rcls
.val
.right();
4714 auto const it
= cinfo
->clsConstants
.find(cnsName
);
4715 if (it
!= end(cinfo
->clsConstants
)) {
4716 if (!it
->second
->val
.hasValue() ||
4717 (!allow_tconst
&& it
->second
->isTypeconst
)) {
4718 // This is an abstract class constant or typeconstant
4721 if (it
->second
->val
.value().m_type
== KindOfUninit
) {
4722 // This is a class constant that needs an 86cinit to run.
4723 // We'll add a dependency to make sure we're re-run if it
4724 // resolves anything.
4725 auto const cinit
= it
->second
->cls
->methods
.back().get();
4726 assert(cinit
->name
== s_86cinit
.get());
4727 add_dependency(*m_data
, cinit
, ctx
, Dep::ClsConst
);
4735 Type
Index::lookup_class_constant(Context ctx
,
4738 bool allow_tconst
) const {
4739 auto const cnst
= lookup_class_const_ptr(ctx
, rcls
, cnsName
, allow_tconst
);
4740 if (!cnst
) return TInitCell
;
4741 return from_cell(cnst
->val
.value());
4744 folly::Optional
<Type
> Index::lookup_constant(Context ctx
,
4745 SString cnsName
) const {
4746 ConstInfoConcurrentMap::const_accessor acc
;
4747 if (!m_data
->constants
.find(acc
, cnsName
)) {
4748 // flag to indicate that the constant isn't in the index yet.
4749 if (options
.HardConstProp
) return folly::none
;
4753 if (acc
->second
.func
&&
4754 !acc
->second
.readonly
&&
4755 !acc
->second
.system
&&
4756 !tv(acc
->second
.type
)) {
4757 // we might refine the type
4758 add_dependency(*m_data
, acc
->second
.func
, ctx
, Dep::ConstVal
);
4761 return acc
->second
.type
;
4764 folly::Optional
<Cell
> Index::lookup_persistent_constant(SString cnsName
) const {
4765 if (!options
.HardConstProp
) return folly::none
;
4766 ConstInfoConcurrentMap::const_accessor acc
;
4767 if (!m_data
->constants
.find(acc
, cnsName
)) return folly::none
;
4768 return tv(acc
->second
.type
);
4771 bool Index::func_depends_on_arg(const php::Func
* func
, int arg
) const {
4772 auto const& finfo
= *func_info(*m_data
, func
);
4773 return arg
>= finfo
.unusedParams
.size() || !finfo
.unusedParams
.test(arg
);
4776 Type
Index::lookup_foldable_return_type(Context ctx
,
4777 const php::Func
* func
,
4779 CompactVector
<Type
> args
) const {
4780 constexpr auto max_interp_nexting_level
= 2;
4781 static __thread
uint32_t interp_nesting_level
;
4782 static __thread Context base_ctx
;
4784 // Don't fold functions when staticness mismatches
4785 if ((func
->attrs
& AttrStatic
) && ctxType
.couldBe(TObj
)) return TTop
;
4786 if (!(func
->attrs
& AttrStatic
) && ctxType
.couldBe(TCls
)) return TTop
;
4788 auto const& finfo
= *func_info(*m_data
, func
);
4789 if (finfo
.effectFree
&& is_scalar(finfo
.returnTy
)) {
4790 return finfo
.returnTy
;
4793 auto const calleeCtx
= CallContext
{
4799 auto showArgs DEBUG_ONLY
= [] (const CompactVector
<Type
>& a
) {
4800 std::string ret
, sep
;
4801 for (auto& arg
: a
) {
4802 folly::format(&ret
, "{}{}", sep
, show(arg
));
4809 ContextRetTyMap::const_accessor acc
;
4810 if (m_data
->foldableReturnTypeMap
.find(acc
, calleeCtx
)) {
4813 "Found foldableReturnType for {}{}{} with args {} (hash: {})\n",
4814 func
->cls
? func
->cls
->name
: empty_string().get(),
4815 func
->cls
? "::" : "",
4817 showArgs(calleeCtx
.args
),
4818 CallContextHashCompare
{}.hash(calleeCtx
));
4820 assertx(is_scalar(acc
->second
));
4828 "MISSING: foldableReturnType for {}{}{} with args {} (hash: {})\n",
4829 func
->cls
? func
->cls
->name
: empty_string().get(),
4830 func
->cls
? "::" : "",
4832 showArgs(calleeCtx
.args
),
4833 CallContextHashCompare
{}.hash(calleeCtx
));
4837 if (!interp_nesting_level
) {
4839 } else if (interp_nesting_level
> max_interp_nexting_level
) {
4840 add_dependency(*m_data
, func
, base_ctx
, Dep::InlineDepthLimit
);
4844 auto const contextType
= [&] {
4845 ++interp_nesting_level
;
4846 SCOPE_EXIT
{ --interp_nesting_level
; };
4848 auto const fa
= analyze_func_inline(
4850 Context
{ func
->unit
, const_cast<php::Func
*>(func
), func
->cls
},
4853 CollectionOpts::EffectFreeOnly
4855 return fa
.effectFree
? fa
.inferredReturn
: TTop
;
4858 if (!is_scalar(contextType
)) {
4862 ContextRetTyMap::accessor acc
;
4863 if (m_data
->foldableReturnTypeMap
.insert(acc
, calleeCtx
)) {
4864 acc
->second
= contextType
;
4866 // someone beat us to it
4867 assertx(acc
->second
== contextType
);
4872 Type
Index::lookup_return_type(Context ctx
, res::Func rfunc
) const {
4875 [&](res::Func::FuncName
) { return TInitCell
; },
4876 [&](res::Func::MethodName
) { return TInitCell
; },
4877 [&](FuncInfo
* finfo
) {
4878 add_dependency(*m_data
, finfo
->func
, ctx
, Dep::ReturnTy
);
4879 return unctx(finfo
->returnTy
);
4881 [&](const MethTabEntryPair
* mte
) {
4882 add_dependency(*m_data
, mte
->second
.func
, ctx
, Dep::ReturnTy
);
4883 auto const finfo
= func_info(*m_data
, mte
->second
.func
);
4884 if (!finfo
->func
) return TInitCell
;
4885 return unctx(finfo
->returnTy
);
4887 [&](FuncFamily
* fam
) {
4889 for (auto const pf
: fam
->possibleFuncs()) {
4890 add_dependency(*m_data
, pf
->second
.func
, ctx
, Dep::ReturnTy
);
4891 auto const finfo
= func_info(*m_data
, pf
->second
.func
);
4892 if (!finfo
->func
) return TInitCell
;
4893 ret
|= unctx(finfo
->returnTy
);
4899 Type
Index::lookup_return_type(Context caller
,
4900 const CompactVector
<Type
>& args
,
4901 const Type
& context
,
4902 res::Func rfunc
) const {
4905 [&](res::Func::FuncName
) {
4906 return lookup_return_type(caller
, rfunc
);
4908 [&](res::Func::MethodName
) {
4909 return lookup_return_type(caller
, rfunc
);
4911 [&](FuncInfo
* finfo
) {
4912 add_dependency(*m_data
, finfo
->func
, caller
, Dep::ReturnTy
);
4913 return context_sensitive_return_type(*m_data
,
4914 { finfo
->func
, args
, context
});
4916 [&](const MethTabEntryPair
* mte
) {
4917 add_dependency(*m_data
, mte
->second
.func
, caller
, Dep::ReturnTy
);
4918 auto const finfo
= func_info(*m_data
, mte
->second
.func
);
4919 if (!finfo
->func
) return TInitCell
;
4920 return context_sensitive_return_type(*m_data
,
4921 { finfo
->func
, args
, context
});
4923 [&] (FuncFamily
* fam
) {
4925 for (auto& pf
: fam
->possibleFuncs()) {
4926 add_dependency(*m_data
, pf
->second
.func
, caller
, Dep::ReturnTy
);
4927 auto const finfo
= func_info(*m_data
, pf
->second
.func
);
4928 if (!finfo
->func
) ret
|= TInitCell
;
4929 else ret
|= return_with_context(finfo
->returnTy
, context
);
4937 Index::lookup_closure_use_vars(const php::Func
* func
,
4939 assert(func
->isClosureBody
);
4941 auto const numUseVars
= closure_num_use_vars(func
);
4942 if (!numUseVars
) return {};
4943 auto const it
= m_data
->closureUseVars
.find(func
->cls
);
4944 if (it
== end(m_data
->closureUseVars
)) {
4945 return CompactVector
<Type
>(numUseVars
, TCell
);
4947 if (move
) return std::move(it
->second
);
4951 Type
Index::lookup_return_type_raw(const php::Func
* f
) const {
4952 auto it
= func_info(*m_data
, f
);
4954 assertx(it
->func
== f
);
4955 return it
->returnTy
;
4960 bool Index::lookup_this_available(const php::Func
* f
) const {
4961 return (f
->attrs
& AttrRequiresThis
) && !f
->isClosureBody
;
4964 PrepKind
Index::lookup_param_prep(Context
/*ctx*/, res::Func rfunc
,
4965 uint32_t paramId
) const {
4966 return match
<PrepKind
>(
4968 [&] (res::Func::FuncName s
) {
4969 if (!RuntimeOption::RepoAuthoritative
|| s
.renamable
) return PrepKind::Unknown
;
4970 return prep_kind_from_set(find_range(m_data
->funcs
, s
.name
), paramId
);
4972 [&] (res::Func::MethodName s
) {
4973 if (!RuntimeOption::RepoAuthoritative
) return PrepKind::Unknown
;
4974 auto const it
= m_data
->method_ref_params_by_name
.find(s
.name
);
4975 if (it
== end(m_data
->method_ref_params_by_name
)) {
4976 // There was no entry, so no method by this name takes a parameter
4978 return PrepKind::Val
;
4981 * If we think it's supposed to be PrepKind::Ref, we still can't be sure
4982 * unless we go through some effort to guarantee that it can't be going
4983 * to an __call function magically (which will never take anything by
4986 if (paramId
< sizeof(it
->second
) * CHAR_BIT
) {
4987 return ((it
->second
>> paramId
) & 1) ?
4988 PrepKind::Unknown
: PrepKind::Val
;
4990 auto const kind
= prep_kind_from_set(
4991 find_range(m_data
->methods
, s
.name
),
4994 return kind
== PrepKind::Ref
? PrepKind::Unknown
: kind
;
4996 [&] (FuncInfo
* finfo
) {
4997 return func_param_prep(finfo
->func
, paramId
);
4999 [&] (const MethTabEntryPair
* mte
) {
5000 return func_param_prep(mte
->second
.func
, paramId
);
5002 [&] (FuncFamily
* fam
) {
5003 assert(RuntimeOption::RepoAuthoritative
);
5004 return prep_kind_from_set(fam
->possibleFuncs(), paramId
);
5010 Index::lookup_private_props(const php::Class
* cls
,
5012 auto it
= m_data
->privatePropInfo
.find(cls
);
5013 if (it
!= end(m_data
->privatePropInfo
)) {
5014 if (move
) return std::move(it
->second
);
5017 return make_unknown_propstate(
5019 [&] (const php::Prop
& prop
) {
5020 return (prop
.attrs
& AttrPrivate
) && !(prop
.attrs
& AttrStatic
);
5026 Index::lookup_private_statics(const php::Class
* cls
,
5028 auto it
= m_data
->privateStaticPropInfo
.find(cls
);
5029 if (it
!= end(m_data
->privateStaticPropInfo
)) {
5030 if (move
) return std::move(it
->second
);
5033 return make_unknown_propstate(
5035 [&] (const php::Prop
& prop
) {
5036 return (prop
.attrs
& AttrPrivate
) && (prop
.attrs
& AttrStatic
);
5041 Type
Index::lookup_public_static(Context ctx
,
5043 const Type
& name
) const {
5044 if (!is_specialized_cls(cls
)) return TInitGen
;
5046 auto const vname
= tv(name
);
5047 if (!vname
|| vname
->m_type
!= KindOfPersistentString
) return TInitGen
;
5048 auto const sname
= vname
->m_data
.pstr
;
5050 if (ctx
.unit
) add_dependency(*m_data
, sname
, ctx
, Dep::PublicSPropName
);
5052 auto const dcls
= dcls_of(cls
);
5053 if (dcls
.cls
.val
.left()) return TInitGen
;
5054 auto const cinfo
= dcls
.cls
.val
.right();
5056 switch (dcls
.type
) {
5059 for (auto const sub
: cinfo
->subclassList
) {
5060 ty
|= lookup_public_static_impl(
5069 return lookup_public_static_impl(
5075 always_assert(false);
5078 Type
Index::lookup_public_static(Context ctx
,
5079 const php::Class
* cls
,
5080 SString name
) const {
5081 if (ctx
.unit
) add_dependency(*m_data
, name
, ctx
, Dep::PublicSPropName
);
5082 return lookup_public_static_impl(*m_data
, cls
, name
).inferredType
;
5085 bool Index::lookup_public_static_immutable(const php::Class
* cls
,
5086 SString name
) const {
5087 return !lookup_public_static_impl(*m_data
, cls
, name
).everModified
;
5090 bool Index::lookup_public_static_maybe_late_init(const Type
& cls
,
5091 const Type
& name
) const {
5092 auto const cinfo
= [&] () -> const ClassInfo
* {
5093 if (!is_specialized_cls(cls
)) {
5096 auto const dcls
= dcls_of(cls
);
5097 switch (dcls
.type
) {
5098 case DCls::Sub
: return nullptr;
5099 case DCls::Exact
: return dcls
.cls
.val
.right();
5103 if (!cinfo
) return true;
5105 auto const vname
= tv(name
);
5106 if (!vname
|| (vname
&& vname
->m_type
!= KindOfPersistentString
)) {
5109 auto const sname
= vname
->m_data
.pstr
;
5111 auto isLateInit
= false;
5114 [&] (const ClassInfo
* ci
) -> bool {
5115 for (auto const& prop
: ci
->cls
->properties
) {
5116 if (prop
.name
== sname
) {
5117 isLateInit
= prop
.attrs
& AttrLateInit
;
5127 Type
Index::lookup_public_prop(const Type
& cls
, const Type
& name
) const {
5128 if (!is_specialized_cls(cls
)) return TGen
;
5130 auto const vname
= tv(name
);
5131 if (!vname
|| vname
->m_type
!= KindOfPersistentString
) return TGen
;
5132 auto const sname
= vname
->m_data
.pstr
;
5134 auto const dcls
= dcls_of(cls
);
5135 if (dcls
.cls
.val
.left()) return TGen
;
5136 auto const cinfo
= dcls
.cls
.val
.right();
5138 switch (dcls
.type
) {
5141 for (auto const sub
: cinfo
->subclassList
) {
5142 ty
|= lookup_public_prop_impl(
5151 return lookup_public_prop_impl(
5157 always_assert(false);
5160 Type
Index::lookup_public_prop(const php::Class
* cls
, SString name
) const {
5161 auto const classes
= find_range(m_data
->classInfo
, cls
->name
);
5162 if (begin(classes
) == end(classes
) ||
5163 std::next(begin(classes
)) != end(classes
)) {
5166 return lookup_public_prop_impl(*m_data
, begin(classes
)->second
, name
);
5169 bool Index::lookup_class_init_might_raise(Context ctx
, res::Class cls
) const {
5170 return cls
.val
.match(
5171 [] (SString
) { return true; },
5172 [&] (ClassInfo
* cinfo
) {
5173 // Check this class and all of its parents for possible inequivalent
5174 // redeclarations or bad initial values.
5176 // Be conservative for now if we have unflattened traits.
5177 if (!cinfo
->traitProps
.empty()) return true;
5178 if (cinfo
->hasBadRedeclareProp
) return true;
5179 if (cinfo
->hasBadInitialPropValues
) {
5180 add_dependency(*m_data
, cinfo
->cls
, ctx
, Dep::PropBadInitialValues
);
5183 cinfo
= cinfo
->parent
;
5190 void Index::join_iface_vtable_thread() const {
5191 if (m_data
->compute_iface_vtables
.joinable()) {
5192 m_data
->compute_iface_vtables
.join();
5197 Index::lookup_iface_vtable_slot(const php::Class
* cls
) const {
5198 return folly::get_default(m_data
->ifaceSlotMap
, cls
, kInvalidSlot
);
5201 //////////////////////////////////////////////////////////////////////
5203 DependencyContext
Index::dependency_context(const Context
& ctx
) const {
5204 return dep_context(*m_data
, ctx
);
5207 void Index::use_class_dependencies(bool f
) {
5208 if (f
!= m_data
->useClassDependencies
) {
5209 m_data
->dependencyMap
.clear();
5210 m_data
->useClassDependencies
= f
;
5214 void Index::init_public_static_prop_types() {
5215 for (auto const& cinfo
: m_data
->allClassInfos
) {
5216 for (auto const& prop
: cinfo
->cls
->properties
) {
5217 if (!(prop
.attrs
& AttrPublic
) || !(prop
.attrs
& AttrStatic
)) {
5222 * If the initializer type is TUninit, it means an 86sinit provides the
5223 * actual initialization type or it is AttrLateInit. So we don't want to
5224 * include the Uninit (which isn't really a user-visible type for the
5225 * property) or by the time we union things in we'll have inferred nothing
5228 * If the property is AttrLateInitSoft, it can be anything because of the
5229 * default value, so give the initial value as TInitGen and don't honor
5230 * the type-constraint, which will keep us from inferring anything.
5232 auto const initial
= [&] {
5233 if (prop
.attrs
& AttrLateInitSoft
) return TInitGen
;
5234 auto const tyRaw
= from_cell(prop
.val
);
5235 if (tyRaw
.subtypeOf(BUninit
)) return TBottom
;
5236 if (prop
.attrs
& AttrSystemInitialValue
) return tyRaw
;
5237 return adjust_type_for_prop(
5238 *this, *cinfo
->cls
, &prop
.typeConstraint
, tyRaw
5242 auto const tc
= (prop
.attrs
& AttrLateInitSoft
)
5244 : &prop
.typeConstraint
;
5246 cinfo
->publicStaticProps
[prop
.name
] =
5249 adjust_type_for_prop(*this, *cinfo
->cls
, tc
, TInitGen
),
5261 void Index::refine_class_constants(
5263 const CompactVector
<std::pair
<size_t, TypedValue
>>& resolved
,
5264 DependencyContextSet
& deps
) {
5265 if (!resolved
.size()) return;
5266 auto& constants
= ctx
.func
->cls
->constants
;
5267 for (auto const& c
: resolved
) {
5268 assertx(c
.first
< constants
.size());
5269 auto& cnst
= constants
[c
.first
];
5270 assertx(cnst
.val
&& cnst
.val
->m_type
== KindOfUninit
);
5271 cnst
.val
= c
.second
;
5273 find_deps(*m_data
, ctx
.func
, Dep::ClsConst
, deps
);
5276 void Index::refine_constants(const FuncAnalysisResult
& fa
,
5277 DependencyContextSet
& deps
) {
5278 auto const func
= fa
.ctx
.func
;
5279 for (auto const& it
: fa
.cnsMap
) {
5280 if (it
.second
.m_type
== kReadOnlyConstant
) {
5281 // this constant was read, but there was nothing mentioning it
5282 // in the index. Should only happen on the first iteration. We
5283 // need to reprocess this func.
5284 assert(fa
.readsUntrackedConstants
);
5285 // if there's already an entry, we don't want to do anything,
5286 // otherwise just insert a dummy entry to indicate that it was
5288 ConstInfoConcurrentMap::accessor acc
;
5289 if (m_data
->constants
.insert(acc
, it
.first
)) {
5290 acc
->second
= ConstInfo
{func
, TInitCell
, false, true};
5295 if (it
.second
.m_type
== kDynamicConstant
|| !is_pseudomain(func
)) {
5296 // two definitions, or a non-pseuodmain definition
5297 ConstInfoConcurrentMap::accessor acc
;
5298 m_data
->constants
.insert(acc
, it
.first
);
5299 auto& c
= acc
->second
;
5308 auto t
= it
.second
.m_type
== KindOfUninit
?
5309 TInitCell
: from_cell(it
.second
);
5311 assertx(t
.equivalentlyRefined(unctx(t
)));
5313 ConstInfoConcurrentMap::accessor acc
;
5314 if (m_data
->constants
.insert(acc
, it
.first
)) {
5315 acc
->second
= ConstInfo
{func
, t
};
5319 if (acc
->second
.system
) continue;
5321 if (acc
->second
.readonly
) {
5322 acc
->second
.func
= func
;
5323 acc
->second
.type
= t
;
5324 acc
->second
.readonly
= false;
5328 if (acc
->second
.func
!= func
) {
5329 acc
->second
.func
= nullptr;
5330 acc
->second
.type
= TInitCell
;
5334 assertx(t
.moreRefined(acc
->second
.type
));
5335 if (!t
.equivalentlyRefined(acc
->second
.type
)) {
5336 acc
->second
.type
= t
;
5337 find_deps(*m_data
, func
, Dep::ConstVal
, deps
);
5340 if (fa
.readsUntrackedConstants
) deps
.emplace(dep_context(*m_data
, fa
.ctx
));
5343 void Index::fixup_return_type(const php::Func
* func
,
5344 Type
& retTy
) const {
5345 if (func
->isGenerator
) {
5346 if (func
->isAsync
) {
5347 // Async generators always return AsyncGenerator object.
5348 retTy
= objExact(builtin_class(s_AsyncGenerator
.get()));
5350 // Non-async generators always return Generator object.
5351 retTy
= objExact(builtin_class(s_Generator
.get()));
5353 } else if (func
->isAsync
) {
5354 // Async functions always return WaitH<T>, where T is the type returned
5356 retTy
= wait_handle(*this, std::move(retTy
));
5360 void Index::init_return_type(const php::Func
* func
) {
5361 if ((func
->attrs
& AttrBuiltin
) || func
->isMemoizeWrapper
) {
5365 auto make_type
= [&] (const TypeConstraint
& tc
) {
5367 (RuntimeOption::EvalThisTypeHintLevel
!= 3 && tc
.isThis())) {
5370 return loosen_dvarrayness(
5374 const_cast<php::Func
*>(func
),
5375 func
->cls
&& func
->cls
->closureContextCls
?
5376 func
->cls
->closureContextCls
: func
->cls
5382 auto const finfo
= create_func_info(*m_data
, func
);
5384 auto tcT
= make_type(func
->retTypeConstraint
);
5385 if (tcT
== TBottom
) return;
5387 if (func
->attrs
& AttrTakesInOutParams
) {
5388 std::vector
<Type
> types
;
5389 types
.emplace_back(intersection_of(TInitCell
, std::move(tcT
)));
5390 for (auto& p
: func
->params
) {
5391 if (!p
.inout
) continue;
5392 auto t
= make_type(p
.typeConstraint
);
5393 if (t
== TBottom
) return;
5394 types
.emplace_back(intersection_of(TInitCell
, std::move(t
)));
5396 tcT
= vec(std::move(types
));
5399 tcT
= to_cell(std::move(tcT
));
5400 if (is_specialized_obj(tcT
)) {
5401 if (dobj_of(tcT
).cls
.couldBeInterfaceOrTrait()) {
5402 tcT
= is_opt(tcT
) ? TOptObj
: TObj
;
5405 tcT
= loosen_all(std::move(tcT
));
5407 FTRACE(4, "Pre-fixup return type for {}{}{}: {}\n",
5408 func
->cls
? func
->cls
->name
->data() : "",
5409 func
->cls
? "::" : "",
5410 func
->name
, show(tcT
));
5411 fixup_return_type(func
, tcT
);
5412 FTRACE(3, "Initial return type for {}{}{}: {}\n",
5413 func
->cls
? func
->cls
->name
->data() : "",
5414 func
->cls
? "::" : "",
5415 func
->name
, show(tcT
));
5416 finfo
->returnTy
= std::move(tcT
);
5419 void Index::refine_return_info(const FuncAnalysisResult
& fa
,
5420 DependencyContextSet
& deps
) {
5421 auto const& t
= fa
.inferredReturn
;
5422 auto const func
= fa
.ctx
.func
;
5423 auto const finfo
= create_func_info(*m_data
, func
);
5425 auto error_loc
= [&] {
5426 return folly::sformat(
5428 func
->unit
->filename
,
5430 folly::to
<std::string
>(func
->cls
->name
->data(), "::") : std::string
{},
5436 if (finfo
->retParam
== NoLocalId
&& fa
.retParam
!= NoLocalId
) {
5437 // This is just a heuristic; it doesn't mean that the value passed
5438 // in was returned, but that the value of the parameter at the
5439 // point of the RetC was returned. We use it to make (heuristic)
5440 // decisions about whether to do inline interps, so we only allow
5441 // it to change once (otherwise later passes might not do the
5442 // inline interp, and get worse results, which could trigger other
5443 // assertions in Index::refine_*).
5444 dep
= Dep::ReturnTy
;
5445 finfo
->retParam
= fa
.retParam
;
5448 auto unusedParams
= ~fa
.usedParams
;
5449 if (finfo
->unusedParams
!= unusedParams
) {
5450 dep
= Dep::ReturnTy
;
5452 (finfo
->unusedParams
| unusedParams
) == unusedParams
,
5453 "Index unusedParams decreased in {}.\n",
5456 finfo
->unusedParams
= unusedParams
;
5459 if (t
.strictlyMoreRefined(finfo
->returnTy
)) {
5460 if (finfo
->returnRefinments
+ 1 < options
.returnTypeRefineLimit
) {
5461 finfo
->returnTy
= t
;
5462 ++finfo
->returnRefinments
;
5463 dep
= is_scalar(t
) ?
5464 Dep::ReturnTy
| Dep::InlineDepthLimit
: Dep::ReturnTy
;
5466 FTRACE(1, "maxed out return type refinements at {}\n", error_loc());
5470 t
.moreRefined(finfo
->returnTy
),
5471 "Index return type invariant violated in {}.\n"
5472 " {} is not at least as refined as {}\n",
5475 show(finfo
->returnTy
)
5480 !finfo
->effectFree
|| fa
.effectFree
,
5481 "Index effectFree changed from true to false in {} {}{}.\n",
5482 func
->unit
->filename
,
5483 func
->cls
? folly::to
<std::string
>(func
->cls
->name
->data(), "::") :
5487 if (finfo
->effectFree
!= fa
.effectFree
) {
5488 finfo
->effectFree
= fa
.effectFree
;
5489 dep
= Dep::InlineDepthLimit
| Dep::ReturnTy
;
5492 if (dep
!= Dep
{}) find_deps(*m_data
, func
, dep
, deps
);
5495 bool Index::refine_closure_use_vars(const php::Class
* cls
,
5496 const CompactVector
<Type
>& vars
) {
5497 assert(is_closure(*cls
));
5499 for (auto i
= uint32_t{0}; i
< vars
.size(); ++i
) {
5501 vars
[i
].equivalentlyRefined(unctx(vars
[i
])),
5502 "Closure cannot have a used var with a context dependent type"
5506 auto& current
= [&] () -> CompactVector
<Type
>& {
5507 std::lock_guard
<std::mutex
> _
{closure_use_vars_mutex
};
5508 return m_data
->closureUseVars
[cls
];
5511 always_assert(current
.empty() || current
.size() == vars
.size());
5512 if (current
.empty()) {
5517 auto changed
= false;
5518 for (auto i
= uint32_t{0}; i
< vars
.size(); ++i
) {
5519 always_assert(vars
[i
].subtypeOf(current
[i
]));
5520 if (vars
[i
].strictSubtypeOf(current
[i
])) {
5522 current
[i
] = vars
[i
];
5529 template<class Container
>
5530 void refine_private_propstate(Container
& cont
,
5531 const php::Class
* cls
,
5532 const PropState
& state
) {
5533 assertx(!is_used_trait(*cls
));
5534 auto* elm
= [&] () -> typename
Container::value_type
* {
5535 std::lock_guard
<std::mutex
> _
{private_propstate_mutex
};
5536 auto it
= cont
.find(cls
);
5537 if (it
== end(cont
)) {
5546 for (auto& kv
: state
) {
5547 auto& target
= elm
->second
[kv
.first
];
5548 assertx(target
.tc
== kv
.second
.tc
);
5550 kv
.second
.ty
.moreRefined(target
.ty
),
5551 "PropState refinement failed on {}::${} -- {} was not a subtype of {}\n",
5557 target
.ty
= kv
.second
.ty
;
5561 void Index::refine_private_props(const php::Class
* cls
,
5562 const PropState
& state
) {
5563 refine_private_propstate(m_data
->privatePropInfo
, cls
, state
);
5566 void Index::refine_private_statics(const php::Class
* cls
,
5567 const PropState
& state
) {
5568 // We can't store context dependent types in private statics since they
5569 // could be accessed using different contexts.
5570 auto cleanedState
= PropState
{};
5571 for (auto const& prop
: state
) {
5572 auto& elem
= cleanedState
[prop
.first
];
5573 elem
.ty
= unctx(prop
.second
.ty
);
5574 elem
.tc
= prop
.second
.tc
;
5577 refine_private_propstate(m_data
->privateStaticPropInfo
, cls
, cleanedState
);
5580 void Index::record_public_static_mutations(const php::Func
& func
,
5581 PublicSPropMutations mutations
) {
5582 if (!mutations
.m_data
) {
5583 m_data
->publicSPropMutations
.erase(&func
);
5586 m_data
->publicSPropMutations
.insert_or_assign(&func
, std::move(mutations
));
5589 void Index::update_static_prop_init_val(const php::Class
* cls
,
5590 SString name
) const {
5591 for (auto& info
: find_range(m_data
->classInfo
, cls
->name
)) {
5592 auto const cinfo
= info
.second
;
5593 if (cinfo
->cls
!= cls
) continue;
5594 auto const it
= cinfo
->publicStaticProps
.find(name
);
5595 if (it
!= cinfo
->publicStaticProps
.end()) {
5596 it
->second
.initialValueResolved
= true;
5601 void Index::refine_public_statics(DependencyContextSet
& deps
) {
5602 trace_time
update("update public statics");
5604 // Union together the mutations for each function, including the functions
5605 // which weren't analyzed this round.
5606 auto nothing_known
= false;
5607 PublicSPropMutations::UnknownMap unknown
;
5608 PublicSPropMutations::KnownMap known
;
5609 for (auto const& mutations
: m_data
->publicSPropMutations
) {
5610 if (!mutations
.second
.m_data
) continue;
5611 if (mutations
.second
.m_data
->m_nothing_known
) {
5612 nothing_known
= true;
5616 for (auto const& kv
: mutations
.second
.m_data
->m_unknown
) {
5617 auto const ret
= unknown
.insert(kv
);
5618 if (!ret
.second
) ret
.first
->second
|= kv
.second
;
5620 for (auto const& kv
: mutations
.second
.m_data
->m_known
) {
5621 auto const ret
= known
.insert(kv
);
5622 if (!ret
.second
) ret
.first
->second
|= kv
.second
;
5626 if (nothing_known
) {
5627 // We cannot go from knowing the types to not knowing the types (this is
5628 // equivalent to widening the types).
5629 always_assert(m_data
->allPublicSPropsUnknown
);
5633 auto const firstRefinement
= m_data
->allPublicSPropsUnknown
;
5634 m_data
->allPublicSPropsUnknown
= false;
5636 if (firstRefinement
) {
5637 // If this is the first refinement, reschedule any dependency which looked
5638 // at the public static property state previously.
5639 always_assert(m_data
->unknownClassSProps
.empty());
5640 for (auto const& dependency
: m_data
->dependencyMap
) {
5641 if (dependency
.first
.tag() != DependencyContextType::PropName
) continue;
5642 for (auto const& kv
: dependency
.second
) {
5643 if (has_dep(kv
.second
, Dep::PublicSPropName
)) deps
.insert(kv
.first
);
5648 // Refine unknown class state
5649 for (auto const& kv
: unknown
) {
5650 // We can't keep context dependent types in public properties.
5651 auto newType
= unctx(kv
.second
);
5652 auto it
= m_data
->unknownClassSProps
.find(kv
.first
);
5653 if (it
== end(m_data
->unknownClassSProps
)) {
5654 // If this is the first refinement, our previous state was effectively
5655 // TGen for everything, so inserting a type into the map can only
5656 // refine. However, if this isn't the first refinement, a name not present
5657 // in the map means that its TBottom, so we shouldn't be inserting
5659 always_assert(firstRefinement
);
5660 m_data
->unknownClassSProps
.emplace(
5662 std::make_pair(std::move(newType
), 0)
5668 * We may only shrink the types we recorded for each property. (If a
5669 * property type ever grows, the interpreter could infer something
5670 * incorrect at some step.)
5672 always_assert(!firstRefinement
);
5674 newType
.subtypeOf(it
->second
.first
),
5675 "Static property index invariant violated for name {}:\n"
5676 " {} was not a subtype of {}",
5679 show(it
->second
.first
)
5682 // Put a limit on the refinements to ensure termination. Since we only ever
5683 // refine types, we can stop at any point and maintain correctness.
5684 if (it
->second
.second
+ 1 < options
.publicSPropRefineLimit
) {
5685 if (newType
.strictSubtypeOf(it
->second
.first
)) {
5686 find_deps(*m_data
, it
->first
, Dep::PublicSPropName
, deps
);
5688 it
->second
.first
= std::move(newType
);
5689 ++it
->second
.second
;
5692 1, "maxed out public static property refinements for name {}\n",
5698 // If we didn't see a mutation among all the functions for a particular name,
5699 // it means the type is TBottom. Iterate through the unknown class state and
5700 // remove any entries which we didn't see a mutation for.
5701 if (!firstRefinement
) {
5702 auto it
= begin(m_data
->unknownClassSProps
);
5703 auto last
= end(m_data
->unknownClassSProps
);
5704 while (it
!= last
) {
5705 auto const unknownIt
= unknown
.find(it
->first
);
5706 if (unknownIt
== end(unknown
)) {
5707 if (unknownIt
->second
!= TBottom
) {
5708 find_deps(*m_data
, unknownIt
->first
, Dep::PublicSPropName
, deps
);
5710 it
= m_data
->unknownClassSProps
.erase(it
);
5717 // Refine known class state
5718 for (auto const& cinfo
: m_data
->allClassInfos
) {
5719 for (auto& kv
: cinfo
->publicStaticProps
) {
5720 auto const newType
= [&] {
5721 auto const it
= known
.find(
5722 PublicSPropMutations::KnownKey
{ cinfo
.get(), kv
.first
}
5724 // If we didn't see a mutation, the type is TBottom.
5725 if (it
== end(known
)) return TBottom
;
5726 // We can't keep context dependent types in public properties.
5727 return adjust_type_for_prop(
5728 *this, *cinfo
->cls
, kv
.second
.tc
, unctx(it
->second
)
5732 if (kv
.second
.initialValueResolved
) {
5733 for (auto& prop
: cinfo
->cls
->properties
) {
5734 if (prop
.name
!= kv
.first
) continue;
5735 kv
.second
.initializerType
= from_cell(prop
.val
);
5736 kv
.second
.initialValueResolved
= false;
5739 assertx(!kv
.second
.initialValueResolved
);
5742 // The type from the indexer doesn't contain the in-class initializer
5743 // types. Add that here.
5744 auto effectiveType
= union_of(newType
, kv
.second
.initializerType
);
5747 * We may only shrink the types we recorded for each property. (If a
5748 * property type ever grows, the interpreter could infer something
5749 * incorrect at some step.)
5752 effectiveType
.subtypeOf(kv
.second
.inferredType
),
5753 "Static property index invariant violated on {}::{}:\n"
5754 " {} is not a subtype of {}",
5755 cinfo
->cls
->name
->data(),
5757 show(effectiveType
),
5758 show(kv
.second
.inferredType
)
5760 always_assert(newType
== TBottom
|| kv
.second
.everModified
);
5762 // Put a limit on the refinements to ensure termination. Since we only
5763 // ever refine types, we can stop at any point and still maintain
5765 if (kv
.second
.refinements
+ 1 < options
.publicSPropRefineLimit
) {
5766 if (effectiveType
.strictSubtypeOf(kv
.second
.inferredType
)) {
5767 find_deps(*m_data
, kv
.first
, Dep::PublicSPropName
, deps
);
5769 kv
.second
.inferredType
= std::move(effectiveType
);
5770 kv
.second
.everModified
= newType
!= TBottom
;
5771 ++kv
.second
.refinements
;
5774 1, "maxed out public static property refinements for {}:{}\n",
5775 cinfo
->cls
->name
->data(),
5783 void Index::refine_bad_initial_prop_values(const php::Class
* cls
,
5785 DependencyContextSet
& deps
) {
5786 assertx(!is_used_trait(*cls
));
5788 for (auto& info
: find_range(m_data
->classInfo
, cls
->name
)) {
5789 auto const cinfo
= info
.second
;
5790 if (cinfo
->cls
!= cls
) continue;
5792 cinfo
->hasBadInitialPropValues
|| !value
,
5793 "Bad initial prop values going from false to true on {}",
5797 if (cinfo
->hasBadInitialPropValues
&& !value
) {
5798 cinfo
->hasBadInitialPropValues
= false;
5799 find_deps(*m_data
, cls
, Dep::PropBadInitialValues
, deps
);
5804 bool Index::frozen() const {
5805 return m_data
->frozen
;
5808 void Index::freeze() {
5809 m_data
->frozen
= true;
5810 m_data
->ever_frozen
= true;
5813 template<typename T
>
5814 void clobber(T
& t
) {
5817 for (auto i
= sizeof(t
); i
--; ) p
[i
] ^= 0xa5;
5824 SCOPE_EXIT { clobber(x); };
5826 void Index::cleanup_for_emit(folly::Baton
<>* done
) {
5827 CLEAR(m_data
->classes
);
5828 CLEAR(m_data
->methods
);
5829 CLEAR(m_data
->method_ref_params_by_name
);
5830 CLEAR(m_data
->funcs
);
5831 CLEAR(m_data
->typeAliases
);
5832 CLEAR(m_data
->classAliases
);
5834 CLEAR(m_data
->classClosureMap
);
5835 CLEAR(m_data
->classExtraMethodMap
);
5838 * allClassInfos, is what's keeping the ClassInfos alive, and Type's
5839 * can still have references to them. In addition, we can still use
5840 * classInfos from lookup_public_static, so we can't clear either
5844 CLEAR(m_data
->dependencyMap
);
5845 CLEAR(m_data
->foldableReturnTypeMap
);
5847 if (done
) done
->wait();
5850 void Index::thaw() {
5851 m_data
->frozen
= false;
5854 std::unique_ptr
<ArrayTypeTable::Builder
>& Index::array_table_builder() const {
5855 return m_data
->arrTableBuilder
;
5858 //////////////////////////////////////////////////////////////////////
5860 res::Func
Index::do_resolve(const php::Func
* f
) const {
5861 auto const finfo
= create_func_info(*m_data
, f
);
5862 return res::Func
{ this, finfo
};
5865 // Return true if we know for sure that one php::Class must derive
5866 // from another at runtime, in all possible instantiations.
5867 bool Index::must_be_derived_from(const php::Class
* cls
,
5868 const php::Class
* parent
) const {
5869 if (cls
== parent
) return true;
5870 auto const clsClasses
= find_range(m_data
->classInfo
, cls
->name
);
5871 auto const parentClasses
= find_range(m_data
->classInfo
, parent
->name
);
5872 for (auto& kvCls
: clsClasses
) {
5873 auto const rCls
= res::Class
{ this, kvCls
.second
};
5874 for (auto& kvPar
: parentClasses
) {
5875 auto const rPar
= res::Class
{ this, kvPar
.second
};
5876 if (!rCls
.mustBeSubtypeOf(rPar
)) return false;
5882 // Return true if any possible definition of one php::Class could
5883 // derive from another at runtime, or vice versa.
5885 Index::could_be_related(const php::Class
* cls
,
5886 const php::Class
* parent
) const {
5887 if (cls
== parent
) return true;
5888 auto const clsClasses
= find_range(m_data
->classInfo
, cls
->name
);
5889 auto const parentClasses
= find_range(m_data
->classInfo
, parent
->name
);
5890 for (auto& kvCls
: clsClasses
) {
5891 auto const rCls
= res::Class
{ this, kvCls
.second
};
5892 for (auto& kvPar
: parentClasses
) {
5893 auto const rPar
= res::Class
{ this, kvPar
.second
};
5894 if (rCls
.couldBe(rPar
)) return true;
5900 //////////////////////////////////////////////////////////////////////
5902 void PublicSPropMutations::merge(const Index
& index
,
5907 // Figure out which class this can affect. If we have a DCls::Sub we have to
5908 // assume it could affect any subclass, so we repeat this merge for all exact
5909 // class types deriving from that base.
5910 if (is_specialized_cls(tcls
)) {
5911 auto const dcls
= dcls_of(tcls
);
5912 if (auto const cinfo
= dcls
.cls
.val
.right()) {
5913 switch (dcls
.type
) {
5915 return merge(index
, ctx
, cinfo
, name
, val
);
5917 for (auto const sub
: cinfo
->subclassList
) {
5918 merge(index
, ctx
, sub
, name
, val
);
5926 merge(index
, ctx
, nullptr, name
, val
);
5929 void PublicSPropMutations::merge(const Index
& index
,
5934 FTRACE(2, "merge_public_static: {} {} {}\n",
5935 cinfo
? cinfo
->cls
->name
->data() : "<unknown>", show(name
), show(val
));
5937 auto get
= [this] () -> Data
& {
5938 if (!m_data
) m_data
= std::make_unique
<Data
>();
5942 auto const vname
= tv(name
);
5943 auto const unknownName
= !vname
||
5944 (vname
&& vname
->m_type
!= KindOfPersistentString
);
5949 * We have a case here where we know neither the class nor the static
5950 * property name. This means we have to pessimize public static property
5951 * types for the entire program.
5953 * We could limit it to pessimizing them by merging the `val' type, but
5954 * instead we just throw everything away---this optimization is not
5955 * expected to be particularly useful on programs that contain any
5956 * instances of this situation.
5960 "NOTE: had to mark everything unknown for public static "
5961 "property types due to dynamic code. -fanalyze-public-statics "
5962 "will not help for this program.\n"
5963 "NOTE: The offending code occured in this context: %s\n",
5966 get().m_nothing_known
= true;
5970 auto const res
= get().m_unknown
.emplace(vname
->m_data
.pstr
, val
);
5971 if (!res
.second
) res
.first
->second
|= val
;
5976 * We don't know the name, but we know something about the class. We need to
5977 * merge the type for every property in the class hierarchy.
5980 visit_parent_cinfo(cinfo
,
5981 [&] (const ClassInfo
* ci
) {
5982 for (auto& kv
: ci
->publicStaticProps
) {
5983 merge(index
, ctx
, cinfo
, sval(kv
.first
), val
);
5991 * Here we know both the ClassInfo and the static property name, but it may
5992 * not actually be on this ClassInfo. In php, you can access base class
5993 * static properties through derived class names, and the access affects the
5994 * property with that name on the most-recently-inherited-from base class.
5996 * If the property is not found as a public property anywhere in the
5997 * hierarchy, we don't want to merge this type. Note we don't have to worry
5998 * about the case that there is a protected property in between, because this
5999 * is a fatal at class declaration time (you can't redeclare a public static
6000 * property with narrower access in a subclass).
6002 auto const affectedInfo
= (
6005 [&] (const ClassInfo
* ci
) ->
6006 folly::Optional
<std::pair
<ClassInfo
*, const TypeConstraint
*>> {
6007 auto const it
= ci
->publicStaticProps
.find(vname
->m_data
.pstr
);
6008 if (it
!= end(ci
->publicStaticProps
)) {
6009 return std::make_pair(
6010 const_cast<ClassInfo
*>(ci
),
6019 if (!affectedInfo
) {
6020 // Either this was a mutation that's going to fatal (property doesn't
6021 // exist), or it's a private static or a protected static. We aren't in
6022 // that business here, so we don't need to record anything.
6026 auto const affectedCInfo
= affectedInfo
->first
;
6027 auto const affectedTC
= affectedInfo
->second
;
6029 auto const adjusted
=
6030 adjust_type_for_prop(index
, *affectedCInfo
->cls
, affectedTC
, val
);
6032 // Merge the property type.
6033 auto const res
= get().m_known
.emplace(
6034 KnownKey
{ affectedCInfo
, vname
->m_data
.pstr
},
6037 if (!res
.second
) res
.first
->second
|= adjusted
;
6040 void PublicSPropMutations::merge(const Index
& index
,
6042 const php::Class
& cls
,
6045 auto range
= find_range(index
.m_data
->classInfo
, cls
.name
);
6046 for (auto const& pair
: range
) {
6047 auto const cinfo
= pair
.second
;
6048 if (cinfo
->cls
!= &cls
) continue;
6049 // Note that this works for both traits and regular classes
6050 for (auto const sub
: cinfo
->subclassList
) {
6051 merge(index
, ctx
, sub
, name
, val
);
6056 //////////////////////////////////////////////////////////////////////