2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
16 #include "hphp/hhbbc/index.h"
25 #include <unordered_map>
29 #include <boost/dynamic_bitset.hpp>
30 #include <boost/filesystem.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/Range.h>
41 #include <folly/SharedMutex.h>
42 #include <folly/String.h>
43 #include <folly/concurrency/ConcurrentHashMap.h>
45 #include "hphp/runtime/base/array-iterator.h"
46 #include "hphp/runtime/base/runtime-option.h"
47 #include "hphp/runtime/base/tv-comparisons.h"
48 #include "hphp/runtime/base/type-structure-helpers-defs.h"
50 #include "hphp/runtime/vm/native.h"
51 #include "hphp/runtime/vm/preclass-emitter.h"
52 #include "hphp/runtime/vm/runtime.h"
53 #include "hphp/runtime/vm/trait-method-import-data.h"
54 #include "hphp/runtime/vm/unit-util.h"
56 #include "hphp/hhbbc/analyze.h"
57 #include "hphp/hhbbc/class-util.h"
58 #include "hphp/hhbbc/context.h"
59 #include "hphp/hhbbc/func-util.h"
60 #include "hphp/hhbbc/optimize.h"
61 #include "hphp/hhbbc/options.h"
62 #include "hphp/hhbbc/options-util.h"
63 #include "hphp/hhbbc/parallel.h"
64 #include "hphp/hhbbc/representation.h"
65 #include "hphp/hhbbc/type-builtins.h"
66 #include "hphp/hhbbc/type-structure.h"
67 #include "hphp/hhbbc/type-system.h"
68 #include "hphp/hhbbc/unit-util.h"
69 #include "hphp/hhbbc/wide-func.h"
71 #include "hphp/util/assertions.h"
72 #include "hphp/util/bitset-utils.h"
73 #include "hphp/util/check-size.h"
74 #include "hphp/util/configs/eval.h"
75 #include "hphp/util/hash-set.h"
76 #include "hphp/util/lock-free-lazy.h"
77 #include "hphp/util/match.h"
79 #include "hphp/zend/zend-string.h"
84 TRACE_SET_MOD(hhbbc_index
);
86 //////////////////////////////////////////////////////////////////////
88 using namespace extern_worker
;
89 namespace coro
= folly::coro
;
91 //////////////////////////////////////////////////////////////////////
96 struct ClassGraphHasher
;
98 struct AuxClassGraphs
;
100 //////////////////////////////////////////////////////////////////////
104 //////////////////////////////////////////////////////////////////////
106 const StaticString
s_construct("__construct");
107 const StaticString
s_toBoolean("__toBoolean");
108 const StaticString
s_invoke("__invoke");
109 const StaticString
s_Closure("Closure");
110 const StaticString
s_AsyncGenerator("HH\\AsyncGenerator");
111 const StaticString
s_Generator("Generator");
112 const StaticString
s_Awaitable("HH\\Awaitable");
113 const StaticString
s_TrivialHHVMBuiltinWrapper("TrivialHHVMBuiltinWrapper");
114 const StaticString
s_Traversable("HH\\Traversable");
116 //////////////////////////////////////////////////////////////////////
118 // HHBBC consumes a LOT of memory, so we keep representation types small.
119 static_assert(CheckSize
<php::Block
, 24>(), "");
120 static_assert(CheckSize
<php::Local
, use_lowptr
? 12 : 16>(), "");
121 static_assert(CheckSize
<php::Param
, use_lowptr
? 64 : 96>(), "");
122 static_assert(CheckSize
<php::Func
, use_lowptr
? 184 : 232>(), "");
124 // Likewise, we also keep the bytecode and immediate types small.
125 static_assert(CheckSize
<Bytecode
, use_lowptr
? 32 : 40>(), "");
126 static_assert(CheckSize
<MKey
, 16>(), "");
127 static_assert(CheckSize
<IterArgs
, 16>(), "");
128 static_assert(CheckSize
<FCallArgs
, 8>(), "");
129 static_assert(CheckSize
<RepoAuthType
, 8>(), "");
131 //////////////////////////////////////////////////////////////////////
133 Dep
operator|(Dep a
, Dep b
) {
134 return static_cast<Dep
>(
135 static_cast<uintptr_t>(a
) | static_cast<uintptr_t>(b
)
139 bool has_dep(Dep m
, Dep t
) {
140 return static_cast<uintptr_t>(m
) & static_cast<uintptr_t>(t
);
144 * Maps functions to contexts that depend on information about that
145 * function, with information about the type of dependency.
148 tbb::concurrent_hash_map
<
153 DependencyContextHash
,
154 DependencyContextEquals
156 DependencyContextHashCompare
159 //////////////////////////////////////////////////////////////////////
162 * Each ClassInfo has a table of public static properties with these entries.
164 struct PublicSPropEntry
{
165 Type inferredType
{TInitCell
};
166 uint32_t refinements
{0};
167 bool everModified
{true};
170 //////////////////////////////////////////////////////////////////////
172 * Entries in the ClassInfo method table need to track some additional
175 * The reason for this is that we need to record attributes of the
178 * We store a lot of these, so we go to some effort to keep it as
181 struct MethTabEntry
{
183 : MethTabEntry
{MethRef
{}, Attr
{}} {}
184 explicit MethTabEntry(const php::Func
& f
)
185 : MethTabEntry
{MethRef
{f
}, f
.attrs
} {}
186 MethTabEntry(const php::Func
& f
, Attr a
)
187 : MethTabEntry
{MethRef
{f
}, a
} {}
188 MethTabEntry(MethRef meth
, Attr a
)
189 : cls
{TopLevel
, meth
.cls
}
193 MethRef
meth() const { return MethRef
{cls
.ptr(), clsIdx
}; }
194 void setMeth(MethRef m
) { cls
.set(cls
.tag(), m
.cls
); clsIdx
= m
.idx
; }
196 // There's a private method further up the class hierarchy with the
198 bool hasPrivateAncestor() const { return cls
.tag() & HasPrivateAncestor
; }
199 // This method came from the ClassInfo that owns the MethTabEntry,
200 // or one of its used traits.
201 bool topLevel() const { return cls
.tag() & TopLevel
; }
202 // This method isn't overridden by methods in any regular classes.
203 bool noOverrideRegular() const { return cls
.tag() & NoOverrideRegular
; }
204 // First appearance of a method with this name in the hierarchy.
205 bool firstName() const { return cls
.tag() & FirstName
; }
207 void setHasPrivateAncestor() {
208 cls
.set(Bits(cls
.tag() | HasPrivateAncestor
), cls
.ptr());
211 cls
.set(Bits(cls
.tag() | TopLevel
), cls
.ptr());
213 void setNoOverrideRegular() {
214 cls
.set(Bits(cls
.tag() | NoOverrideRegular
), cls
.ptr());
216 void setFirstName() {
217 cls
.set(Bits(cls
.tag() | FirstName
), cls
.ptr());
220 void clearHasPrivateAncestor() {
221 cls
.set(Bits(cls
.tag() & ~HasPrivateAncestor
), cls
.ptr());
223 void clearTopLevel() {
224 cls
.set(Bits(cls
.tag() & ~TopLevel
), cls
.ptr());
226 void clearNoOverrideRegular() {
227 cls
.set(Bits(cls
.tag() & ~NoOverrideRegular
), cls
.ptr());
229 void clearFirstName() {
230 cls
.set(Bits(cls
.tag() & ~FirstName
), cls
.ptr());
234 // Logically, a MethTabEntry stores a MethRef. However doing so
235 // makes the MethTabEntry larger, due to alignment. So instead we
236 // store the MethRef fields (which lets the clsIdx and attrs share
237 // the same 64-bits). Moreover, we can store the special bits in the
239 enum Bits
: uint8_t {
240 HasPrivateAncestor
= (1u << 0),
241 TopLevel
= (1u << 1),
242 NoOverrideRegular
= (1u << 2),
243 FirstName
= (1u << 3)
245 CompactTaggedPtr
<const StringData
, Bits
> cls
;
249 // A method could be imported from a trait, and its attributes
253 template <typename SerDe
> void serde(SerDe
& sd
) {
254 if constexpr (SerDe::deserializing
) {
257 sd(clsname
)(clsIdx
)(attrs
)(bits
);
258 cls
.set(bits
, clsname
);
260 sd(cls
.ptr())(clsIdx
)(attrs
)(cls
.tag());
265 // Don't indeliberably make this larger
266 static_assert(CheckSize
<MethTabEntry
, 16>(), "");
268 //////////////////////////////////////////////////////////////////////
270 using ContextRetTyMap
= tbb::concurrent_hash_map
<
273 CallContextHashCompare
276 //////////////////////////////////////////////////////////////////////
278 template<typename Filter
>
279 PropState
make_unknown_propstate(const IIndex
& index
,
280 const php::Class
& cls
,
282 auto ret
= PropState
{};
283 for (auto& prop
: cls
.properties
) {
285 auto& elem
= ret
[prop
.name
];
286 elem
.ty
= adjust_type_for_prop(
289 &prop
.typeConstraint
,
292 if (prop
.attrs
& AttrSystemInitialValue
) {
293 auto initial
= loosen_all(from_cell(prop
.val
));
294 if (!initial
.subtypeOf(BUninit
)) elem
.ty
|= initial
;
296 elem
.tc
= &prop
.typeConstraint
;
297 elem
.attrs
= prop
.attrs
;
298 elem
.everModified
= true;
305 //////////////////////////////////////////////////////////////////////
307 bool func_supports_AER(const php::Func
* func
) {
308 // Async functions always support async eager return, and no other
309 // functions support it yet.
310 return func
->isAsync
&& !func
->isGenerator
;
313 uint32_t func_num_inout(const php::Func
* func
) {
314 if (!func
->hasInOutArgs
) return 0;
316 for (auto& p
: func
->params
) count
+= p
.inout
;
320 PrepKind
func_param_prep(const php::Func
* f
, uint32_t paramId
) {
321 auto const sz
= f
->params
.size();
322 if (paramId
>= sz
) return PrepKind
{TriBool::No
, TriBool::No
};
324 kind
.inOut
= yesOrNo(f
->params
[paramId
].inout
);
325 kind
.readonly
= yesOrNo(f
->params
[paramId
].readonly
);
329 uint32_t numNVArgs(const php::Func
& f
) {
330 uint32_t cnt
= f
.params
.size();
331 return cnt
&& f
.params
[cnt
- 1].isVariadic
? cnt
- 1 : cnt
;
334 //////////////////////////////////////////////////////////////////////
338 //////////////////////////////////////////////////////////////////////
340 std::string
show(const ConstIndex
& idx
, const IIndex
& index
) {
341 if (auto const cls
= index
.lookup_class(idx
.cls
)) {
342 assertx(idx
.idx
< cls
->constants
.size());
343 return folly::sformat("{}::{}", idx
.cls
, cls
->constants
[idx
.idx
].name
);
348 //////////////////////////////////////////////////////////////////////
351 * Currently inferred information about a PHP function.
353 * Nothing in this structure can ever be untrue. The way the
354 * algorithm works, whatever is in here must be factual (even if it is
355 * not complete information), because we may deduce other facts based
358 struct res::Func::FuncInfo
{
359 const php::Func
* func
= nullptr;
361 * The best-known return type of the function, if we have any
362 * information. May be TBottom if the function is known to never
363 * return (e.g. always throws).
365 Type returnTy
= TInitCell
;
368 * If the function always returns the same parameter, this will be
369 * set to its id; otherwise it will be NoLocalId.
371 LocalId retParam
{NoLocalId
};
374 * The number of times we've refined returnTy.
376 uint32_t returnRefinements
{0};
379 * Whether the function is effectFree.
381 bool effectFree
{false};
384 * Bitset representing which parameters definitely don't affect the
385 * result of the function, assuming it produces one. Note that
386 * the parameter type verification does not count as a use in this context.
388 std::bitset
<64> unusedParams
;
391 * List of all func families this function belongs to.
393 CompactVector
<FuncFamily
*> families
;
397 * Currently inferred information about a Hack function.
399 * Nothing in this structure can ever be untrue. The way the algorithm
400 * works, whatever is in here must be factual (even if it is not
401 * complete information), because we may deduce other facts based on
404 * This class mirrors the FuncInfo struct, but is produced and used by
405 * remote workers. As needed, this struct will gain more and more of
406 * FuncInfo's fields (but stored in a more remote worker friendly
407 * way). Local calculations will continue to use FuncInfo. Once
408 * everything is converted to use remote workers, this struct will
409 * subsume FuncInfo entirely (and be renamed).
413 * Name of this function. If this is a top level function, this will
414 * be an unique identifier. For methods, it will just be the method
420 * The php::Func representing this function. This field is not
421 * serialized. If you wish to make use of it, it must be fixed up
422 * manually after deserialization.
424 const php::Func
* func
= nullptr;
427 * The best-known return type of the function, if we have any
428 * information. May be TBottom if the function is known to never
429 * return (e.g. always throws).
431 Type returnTy
= TInitCell
;
434 * If the function always returns the same parameter, this will be
435 * set to its id; otherwise it will be NoLocalId.
437 LocalId retParam
{NoLocalId
};
440 * The number of times we've refined returnTy.
442 uint32_t returnRefinements
{0};
445 * Whether this function is effect-free.
447 bool effectFree
{false};
450 * Bitset representing which parameters definitely don't affect the
451 * result of the function, assuming it produces one. Note that
452 * the parameter type verification does not count as a use in this context.
454 std::bitset
<64> unusedParams
;
457 * If we utilize a ClassGraph while resolving types, we store it
458 * here. This ensures that that ClassGraph will always be available
459 * again. This is only used for top-level functions. If this
460 * function is a method, it will instead be stored in the
463 * This is wrapped in a std::unique_ptr because needing this for a
464 * function is very rare and we want to avoid making FuncInfo2 any
465 * larger than necessary. It also helps solve a circular dependency
466 * between the types (since std::unique_ptr does not require a
467 * complete type at declaration).
469 std::unique_ptr
<AuxClassGraphs
> auxClassGraphs
;
471 template <typename SerDe
> void serde(SerDe
&);
474 //////////////////////////////////////////////////////////////////////
477 * FuncInfos2 for the methods of a class which does not have a
478 * ClassInfo2 (because it's uninstantiable). Even if a class doesn't
479 * have a ClassInfo2, we might still need FuncInfo2 for it's
480 * methods. These are stored in here.
482 struct MethodsWithoutCInfo
{
484 // Same order as the methods vector on the associated php::Class.
485 CompactVector
<std::unique_ptr
<FuncInfo2
>> finfos
;
486 // __invoke methods on any closures declared in this class. Same
487 // order as closures vector on the associated php::Class.
488 CompactVector
<std::unique_ptr
<FuncInfo2
>> closureInvokes
;
489 template <typename SerDe
> void serde(SerDe
& sd
) {
497 //////////////////////////////////////////////////////////////////////
499 using FuncFamily
= res::Func::FuncFamily
;
500 using FuncInfo
= res::Func::FuncInfo
;
502 //////////////////////////////////////////////////////////////////////
505 * Sometimes function resolution can't determine which function
506 * something will call, but can restrict it to a family of functions.
508 * For example, if you want to call an abstract function on a base
509 * class with all unique derived classes, we will resolve the function
510 * to a FuncFamily that contains references to all the possible
511 * overriding-functions.
513 * In general, a FuncFamily can contain functions which are used by a
514 * regular class or not. In some contexts, we only care about the
515 * subset which are used by a regular class, and in some contexts we
516 * care about them all. To save memory, we use a single FuncFamily for
517 * both cases. The users of the FuncFamily must skip over which funcs
518 * it does not care about.
520 * Since we cache information related to the func list, if the "all"
521 * case and the "regular-only" case are potentially different, we
522 * allocated space for both possibilities. If we determine they'll
523 * always be the same, we do not. For example, if the possible func
524 * list only contains methods on regular classes, the distinction is
527 struct res::Func::FuncFamily
{
528 // A PossibleFunc is a php::Func* with an additional bit that
529 // indicates whether that func is present on a regular class or
530 // not. This lets us skip over that func if we only care about the
531 // regular subset of the list.
532 struct PossibleFunc
{
533 PossibleFunc(const php::Func
* f
, bool r
) : m_func
{r
, f
} {}
534 const php::Func
* ptr() const { return m_func
.ptr(); }
535 bool inRegular() const { return (bool)m_func
.tag(); }
536 bool operator==(const PossibleFunc
& o
) const { return m_func
== o
.m_func
; }
538 CompactTaggedPtr
<const php::Func
, uint8_t> m_func
;
540 using PFuncVec
= CompactVector
<PossibleFunc
>;
542 // We have a lot of FuncFamilies, and most of them have the same
543 // "static" information (doesn't change as a result of
544 // analysis). So, we store unique groups of static info separately
545 // and FuncFamilies point to the same ones.
547 Optional
<uint32_t> m_numInOut
;
548 Optional
<RuntimeCoeffects
> m_requiredCoeffects
;
549 Optional
<CompactVector
<CoeffectRule
>> m_coeffectRules
;
550 PrepKindVec m_paramPreps
;
551 uint32_t m_minNonVariadicParams
;
552 uint32_t m_maxNonVariadicParams
;
553 TriBool m_isReadonlyReturn
;
554 TriBool m_isReadonlyThis
;
555 TriBool m_supportsAER
;
556 bool m_maybeReified
: 1;
557 bool m_maybeCaresAboutDynCalls
: 1;
558 bool m_maybeBuiltin
: 1;
560 bool operator==(const StaticInfo
& o
) const;
564 // State in the FuncFamily which might vary depending on whether
565 // we're considering the regular subset or not.
567 LockFreeLazy
<Index::ReturnType
> m_returnTy
;
568 const StaticInfo
* m_static
{nullptr};
571 FuncFamily(PFuncVec
&& v
, bool add
) : m_v
{std::move(v
)}
572 { if (add
) m_regular
= std::make_unique
<Info
>(); }
574 FuncFamily(FuncFamily
&&) = delete;
575 FuncFamily(const FuncFamily
&) = delete;
576 FuncFamily
& operator=(FuncFamily
&&) = delete;
577 FuncFamily
& operator=(const FuncFamily
&) = delete;
579 const PFuncVec
& possibleFuncs() const {
583 Info
& infoFor(bool regularOnly
) {
584 if (regularOnly
&& m_regular
) return *m_regular
;
587 const Info
& infoFor(bool regularOnly
) const {
588 if (regularOnly
&& m_regular
) return *m_regular
;
593 // Only allocated if we determined the distinction is relevant. If
594 // this is nullptr, m_all can be used for both cases.
595 std::unique_ptr
<Info
> m_regular
;
599 bool FuncFamily::StaticInfo::operator==(const FuncFamily::StaticInfo
& o
) const {
601 std::tie(m_numInOut
, m_requiredCoeffects
, m_coeffectRules
,
602 m_paramPreps
, m_minNonVariadicParams
,
603 m_maxNonVariadicParams
,
604 m_isReadonlyReturn
, m_isReadonlyThis
, m_supportsAER
,
605 m_maybeReified
, m_maybeCaresAboutDynCalls
,
607 std::tie(o
.m_numInOut
, o
.m_requiredCoeffects
, o
.m_coeffectRules
,
608 o
.m_paramPreps
, o
.m_minNonVariadicParams
,
609 o
.m_maxNonVariadicParams
,
610 o
.m_isReadonlyReturn
, o
.m_isReadonlyThis
, o
.m_supportsAER
,
611 o
.m_maybeReified
, o
.m_maybeCaresAboutDynCalls
,
615 size_t FuncFamily::StaticInfo::hash() const {
616 auto hash
= folly::hash::hash_combine(
619 m_minNonVariadicParams
,
620 m_maxNonVariadicParams
,
625 m_maybeCaresAboutDynCalls
,
628 hash
= folly::hash::hash_range(
629 m_paramPreps
.begin(),
633 if (m_coeffectRules
) {
634 hash
= folly::hash::hash_range(
635 m_coeffectRules
->begin(),
636 m_coeffectRules
->end(),
643 //////////////////////////////////////////////////////////////////////
647 struct PFuncVecHasher
{
648 size_t operator()(const FuncFamily::PFuncVec
& v
) const {
649 return folly::hash::hash_range(
653 [] (FuncFamily::PossibleFunc pf
) {
654 return hash_int64_pair(
655 pointer_hash
<const php::Func
>{}(pf
.ptr()),
662 struct FuncFamilyPtrHasher
{
663 using is_transparent
= void;
664 size_t operator()(const std::unique_ptr
<FuncFamily
>& ff
) const {
665 return PFuncVecHasher
{}(ff
->possibleFuncs());
667 size_t operator()(const FuncFamily::PFuncVec
& pf
) const {
668 return PFuncVecHasher
{}(pf
);
671 struct FuncFamilyPtrEquals
{
672 using is_transparent
= void;
673 bool operator()(const std::unique_ptr
<FuncFamily
>& a
,
674 const std::unique_ptr
<FuncFamily
>& b
) const {
675 return a
->possibleFuncs() == b
->possibleFuncs();
677 bool operator()(const FuncFamily::PFuncVec
& pf
,
678 const std::unique_ptr
<FuncFamily
>& ff
) const {
679 return pf
== ff
->possibleFuncs();
683 struct FFStaticInfoPtrHasher
{
684 using is_transparent
= void;
685 size_t operator()(const std::unique_ptr
<FuncFamily::StaticInfo
>& i
) const {
688 size_t operator()(const FuncFamily::StaticInfo
& i
) const {
692 struct FFStaticInfoPtrEquals
{
693 using is_transparent
= void;
694 bool operator()(const std::unique_ptr
<FuncFamily::StaticInfo
>& a
,
695 const std::unique_ptr
<FuncFamily::StaticInfo
>& b
) const {
698 bool operator()(const FuncFamily::StaticInfo
& a
,
699 const std::unique_ptr
<FuncFamily::StaticInfo
>& b
) const {
704 //////////////////////////////////////////////////////////////////////
709 * Sometimes function resolution can't determine which exact function
710 * something will call, but can restrict it to a family of functions.
712 * For example, if you want to call a function on a base class, we
713 * will resolve the function to a func family that contains references
714 * to all the possible overriding-functions.
716 * In general, a func family can contain functions which are used by a
717 * regular class or not. In some contexts, we only care about the
718 * subset which are used by a regular class, and in some contexts we
719 * care about them all. To save memory, we use a single func family
720 * for both cases. The users of the func family should only consult
721 * the subset they care about.
723 * Besides the possible functions themselves, information in common
724 * about the functions is cached. For example, return type. This
725 * avoids having to iterate over potentially very large sets of
728 * This class mirrors the FuncFamily struct but is produced and used
729 * by remote workers. Once everything is converted to use remote
730 * workers, this struct will replace FuncFamily entirely (and be
734 // Func families don't have any inherent name, but it's convenient
735 // to have an unique id to refer to each one. We produce a SHA1 hash
736 // of all of the methods in the func family.
740 // All methods in a func family should have the same name. However,
741 // multiple func families may have the same name (so this is not an
742 // unique identifier).
744 // Methods used by a regular classes
745 std::vector
<MethRef
> m_regular
;
746 // Methods used exclusively by non-regular classes, but as a private
747 // method. In some situations, these are treated as if it was on
749 std::vector
<MethRef
> m_nonRegularPrivate
;
750 // Methods used exclusively by non-regular classes
751 std::vector
<MethRef
> m_nonRegular
;
753 // Information about the group of methods relevant to analysis which
754 // doesn't change (hence "static").
756 Optional
<uint32_t> m_numInOut
;
757 Optional
<RuntimeCoeffects
> m_requiredCoeffects
;
758 Optional
<CompactVector
<CoeffectRule
>> m_coeffectRules
;
759 PrepKindVec m_paramPreps
;
760 uint32_t m_minNonVariadicParams
;
761 uint32_t m_maxNonVariadicParams
;
762 TriBool m_isReadonlyReturn
;
763 TriBool m_isReadonlyThis
;
764 TriBool m_supportsAER
;
766 bool m_maybeCaresAboutDynCalls
;
769 StaticInfo
& operator|=(const StaticInfo
& o
) {
770 if (m_numInOut
!= o
.m_numInOut
) {
773 if (m_requiredCoeffects
!= o
.m_requiredCoeffects
) {
774 m_requiredCoeffects
.reset();
776 if (m_coeffectRules
!= o
.m_coeffectRules
) {
777 m_coeffectRules
.reset();
780 if (o
.m_paramPreps
.size() > m_paramPreps
.size()) {
782 o
.m_paramPreps
.size(),
783 PrepKind
{TriBool::No
, TriBool::No
}
786 for (size_t i
= 0; i
< o
.m_paramPreps
.size(); ++i
) {
787 m_paramPreps
[i
].inOut
|= o
.m_paramPreps
[i
].inOut
;
788 m_paramPreps
[i
].readonly
|= o
.m_paramPreps
[i
].readonly
;
790 for (size_t i
= o
.m_paramPreps
.size(); i
< m_paramPreps
.size(); ++i
) {
791 m_paramPreps
[i
].inOut
|= TriBool::No
;
792 m_paramPreps
[i
].readonly
|= TriBool::No
;
795 m_minNonVariadicParams
=
796 std::min(m_minNonVariadicParams
, o
.m_minNonVariadicParams
);
797 m_maxNonVariadicParams
=
798 std::max(m_maxNonVariadicParams
, o
.m_maxNonVariadicParams
);
799 m_isReadonlyReturn
|= o
.m_isReadonlyReturn
;
800 m_isReadonlyThis
|= o
.m_isReadonlyThis
;
801 m_supportsAER
|= o
.m_supportsAER
;
802 m_maybeReified
|= o
.m_maybeReified
;
803 m_maybeCaresAboutDynCalls
|= o
.m_maybeCaresAboutDynCalls
;
804 m_maybeBuiltin
|= o
.m_maybeBuiltin
;
809 template <typename SerDe
> void serde(SerDe
& sd
) {
811 (m_requiredCoeffects
)
814 (m_minNonVariadicParams
)
815 (m_maxNonVariadicParams
)
820 (m_maybeCaresAboutDynCalls
)
825 Optional
<StaticInfo
> m_allStatic
;
826 Optional
<StaticInfo
> m_regularStatic
;
828 const StaticInfo
& infoFor(bool regular
) const {
830 assertx(m_regularStatic
.has_value());
831 return *m_regularStatic
;
836 template <typename SerDe
> void serde(SerDe
& sd
) {
840 (m_nonRegularPrivate
)
850 // Func families are (usually) very small, but we have a lot of
851 // them. To reduce remote worker overhead, we bundle func families
852 // together into one blob.
853 struct FuncFamilyGroup
{
854 std::vector
<std::unique_ptr
<FuncFamily2
>> m_ffs
;
855 template <typename SerDe
> void serde(SerDe
& sd
) {
856 // Multiple func families may reuse the same class name, so we
857 // want to de-dup strings.
858 ScopedStringDataIndexer _
;
863 //////////////////////////////////////////////////////////////////////
866 * A method family table entry in a compact format. Can represent a
867 * FuncFamily, a single php::Func, or emptiness. This represents the
868 * possible resolutions of a call to a method with same name. It also
869 * stores whether the entry is "complete" or "incomplete". An
870 * incomplete entry means the possible resolutions includes the
871 * possibility of the method not existing. A complete entry guarantees
872 * it has to be one of the methods. This is (right now) irrelevant for
873 * FuncFamily, but matters for php::Func, as it determines whether you
874 * can fold away the call (if it's incomplete, the call might fatal).
876 * We create a lot of these, so we use some trickery to keep it
879 struct FuncFamilyOrSingle
{
880 FuncFamilyOrSingle() : m_ptr
{Type::Empty
, nullptr} {}
881 explicit FuncFamilyOrSingle(FuncFamily
* ff
, bool incomplete
)
882 : m_ptr
{incomplete
? Type::FuncFamilyIncomplete
: Type::FuncFamily
, ff
} {}
883 FuncFamilyOrSingle(const php::Func
* f
, bool incomplete
)
884 : m_ptr
{incomplete
? Type::SingleIncomplete
: Type::Single
, (void*)f
} {}
886 // If this represents a FuncFamily, return it (or nullptr
888 FuncFamily
* funcFamily() const {
890 (m_ptr
.tag() == Type::FuncFamily
||
891 m_ptr
.tag() == Type::FuncFamilyIncomplete
)
892 ? (FuncFamily
*)m_ptr
.ptr()
896 // If this represents a single php::Func, return it (or nullptr
898 const php::Func
* func() const {
900 (m_ptr
.tag() == Type::Single
|| m_ptr
.tag() == Type::SingleIncomplete
)
901 ? (const php::Func
*)m_ptr
.ptr()
905 // Return true if this entry represents nothing at all (for example,
906 // if the method is guaranteed to not exist).
907 bool isEmpty() const { return m_ptr
.tag() == Type::Empty
; }
909 // NB: empty entries are neither incomplete nor complete. Check
910 // isEmpty() first if that matters.
912 // Return true if this resolution includes the possibility of no
914 bool isIncomplete() const {
916 m_ptr
.tag() == Type::FuncFamilyIncomplete
||
917 m_ptr
.tag() == Type::SingleIncomplete
;
919 // Return true if the method would resolve to exactly one of the
921 bool isComplete() const {
923 m_ptr
.tag() == Type::FuncFamily
||
924 m_ptr
.tag() == Type::Single
;
928 enum class Type
: uint8_t {
931 FuncFamilyIncomplete
,
935 CompactTaggedPtr
<void, Type
> m_ptr
;
938 std::string
show(const FuncFamilyOrSingle
& fam
) {
939 if (auto const ff
= fam
.funcFamily()) {
940 auto const f
= ff
->possibleFuncs().front().ptr();
941 return folly::sformat(
942 "func-family {}::{}{}",
943 f
->cls
->name
, f
->name
,
944 fam
.isIncomplete() ? " (incomplete)" : ""
947 if (auto const f
= fam
.func()) {
948 return folly::sformat(
950 f
->cls
->name
, f
->name
,
951 fam
.isIncomplete() ? " (incomplete)" : ""
958 * A method family table entry. Each entry encodes the possible
959 * resolutions of a method call on a particular class. The reason why
960 * this isn't just a func family is because we don't want to create a
961 * func family when there's only one possible method involved (this is
962 * common and if we did we'd create way more func
963 * families). Furthermore, we really want information for two
964 * different resolutions. One resolution is when we're only
965 * considering regular classes, and the other is when considering all
966 * classes. One of these resolutions can correspond to a func family
967 * and the other may not. This struct encodes all the possible cases
970 struct FuncFamilyEntry
{
971 // The equivalent of FuncFamily::StaticInfo, but only relevant for a
972 // single method (so doesn't have a FuncFamily where the StaticInfo
973 // can live). This can be derived from the method directly, but by
974 // storing it here, we don't need to send the actual methods to the
976 struct MethMetadata
{
977 MethMetadata() : m_requiredCoeffects
{RuntimeCoeffects::none()} {}
979 PrepKindVec m_prepKinds
;
980 CompactVector
<CoeffectRule
> m_coeffectRules
;
982 uint32_t m_nonVariadicParams
;
983 RuntimeCoeffects m_requiredCoeffects
;
984 bool m_isReadonlyReturn
: 1;
985 bool m_isReadonlyThis
: 1;
986 bool m_supportsAER
: 1;
987 bool m_isReified
: 1;
988 bool m_caresAboutDyncalls
: 1;
991 template <typename SerDe
> void serde(SerDe
& sd
) {
995 (m_nonVariadicParams
)
996 (m_requiredCoeffects
)
998 SERDE_BITFIELD(m_isReadonlyReturn
, sd
);
999 SERDE_BITFIELD(m_isReadonlyThis
, sd
);
1000 SERDE_BITFIELD(m_supportsAER
, sd
);
1001 SERDE_BITFIELD(m_isReified
, sd
);
1002 SERDE_BITFIELD(m_caresAboutDyncalls
, sd
);
1003 SERDE_BITFIELD(m_builtin
, sd
);
1007 // Both "regular" and "all" resolutions map to a func family. This
1008 // must always be the same func family because the func family
1009 // stores the information necessary for both cases.
1011 FuncFamily2::Id m_ff
;
1012 template <typename SerDe
> void serde(SerDe
& sd
) {
1016 // The "all" resolution maps to a func family but the "regular"
1017 // resolution maps to a single method.
1018 struct FFAndSingle
{
1019 FuncFamily2::Id m_ff
;
1021 // If true, m_regular is actually non-regular, but a private
1022 // method (which is sometimes treated as regular).
1023 bool m_nonRegularPrivate
;
1024 template <typename SerDe
> void serde(SerDe
& sd
) {
1025 sd(m_ff
)(m_regular
)(m_nonRegularPrivate
);
1028 // The "all" resolution maps to a func family but the "regular"
1029 // resolution maps to nothing (for example, there's no regular
1030 // classes with that method).
1032 FuncFamily2::Id m_ff
;
1033 template <typename SerDe
> void serde(SerDe
& sd
) {
1037 // Both the "all" and "regular" resolutions map to (the same) single
1041 MethMetadata m_meta
;
1042 // If true, m_all is actually non-regular, but a private method
1043 // (which is sometimes treated as regular).
1044 bool m_nonRegularPrivate
;
1045 template <typename SerDe
> void serde(SerDe
& sd
) {
1046 sd(m_all
)(m_meta
)(m_nonRegularPrivate
);
1049 // The "all" resolution maps to a single method but the "regular"
1050 // resolution maps to nothing.
1051 struct SingleAndNone
{
1053 MethMetadata m_meta
;
1054 template <typename SerDe
> void serde(SerDe
& sd
) {
1058 // No resolutions at all.
1060 template <typename SerDe
> void serde(SerDe
&) {}
1064 BothFF
, FFAndSingle
, FFAndNone
, BothSingle
, SingleAndNone
, None
1066 // A resolution is "incomplete" if there's a subclass which does not
1067 // contain any method with that name (not even inheriting it). If a
1068 // resolution is incomplete, it means besides the set of resolved
1069 // methods, the call might also error due to missing method. This
1070 // distinction is only important in a few limited circumstances.
1071 bool m_allIncomplete
{true};
1072 bool m_regularIncomplete
{true};
1073 // Whether any method in the resolution overrides a private
1074 // method. This is only of interest when building func families.
1075 bool m_privateAncestor
{false};
1077 template <typename SerDe
> void serde(SerDe
& sd
) {
1078 if constexpr (SerDe::deserializing
) {
1079 m_meths
= [&] () -> decltype(m_meths
) {
1083 case 0: return sd
.template make
<BothFF
>();
1084 case 1: return sd
.template make
<FFAndSingle
>();
1085 case 2: return sd
.template make
<FFAndNone
>();
1086 case 3: return sd
.template make
<BothSingle
>();
1087 case 4: return sd
.template make
<SingleAndNone
>();
1088 case 5: return sd
.template make
<None
>();
1089 default: always_assert(false);
1095 [&] (const BothFF
& e
) { sd(uint8_t(0))(e
); },
1096 [&] (const FFAndSingle
& e
) { sd(uint8_t(1))(e
); },
1097 [&] (const FFAndNone
& e
) { sd(uint8_t(2))(e
); },
1098 [&] (const BothSingle
& e
) { sd(uint8_t(3))(e
); },
1099 [&] (const SingleAndNone
& e
) { sd(uint8_t(4))(e
); },
1100 [&] (const None
& e
) { sd(uint8_t(5))(e
); }
1105 (m_regularIncomplete
)
1111 //////////////////////////////////////////////////////////////////////
1115 //////////////////////////////////////////////////////////////////////
1118 * ClassGraph is an abstraction for representing a subset of the
1119 * complete class hierarchy in a program. Every instance of ClassGraph
1120 * "points" into the hierarchy at a specific class. From an instance,
1121 * all (transitive) parents are known, and all (transitive) children
1124 * Using a ClassGraph, one can obtain relevant information about the
1125 * class hierarchy, or perform operations between two classes (union
1126 * or intersection, for example), without having the ClassInfo
1129 * One advantage of ClassGraph is that it is self
1130 * contained. Serializing an instance of ClassGraph will serialize all
1131 * information needed about the hierarchy. This means that an
1132 * extern-worker job does not need to retrieve additional metadata
1136 // Default constructed instances are not usable for anything (needed
1137 // for serialization). Use the below factory functions to actually
1139 ClassGraph() = default;
1141 // A ClassGraph is falsey if it has been default-constructed.
1142 explicit operator bool() const { return this_
; }
1144 // Retrieve the name of this class.
1145 SString
name() const;
1147 // Retrieve an optional ClassInfo/ClassInfo2 associated with this
1148 // class. A ClassGraph is not guaranteed to have a
1149 // ClassInfo/ClassInfo2, but if it does, this can be used to save a
1150 // name -> info lookup.
1151 ClassInfo
* cinfo() const;
1152 ClassInfo2
* cinfo2() const;
1154 // Return a class equivalent to this one without considering
1155 // non-regular classes. This might be this class, or a subclass.
1156 ClassGraph
withoutNonRegular() const;
1158 // Whether the class might be a regular or non-regular class. This
1159 // check is precise if isMissing() is false.
1160 bool mightBeRegular() const;
1161 bool mightBeNonRegular() const;
1163 // Whether this class might have any regular or non-regular
1164 // subclasses (not including this class itself). This check is
1165 // precise if hasCompleteChildren() or isConservative() is true.
1166 bool mightHaveRegularSubclass() const;
1167 bool mightHaveNonRegularSubclass() const;
1169 // A "missing" ClassGraph is a special variant which represents a
1170 // class about which nothing is known. The class might not even
1171 // exist. The only valid thing to do with such a class is query its
1172 // name. The "raw" variant ignores any permission checks.
1173 bool isMissing() const;
1174 bool isMissingRaw() const;
1176 // A class might or might not have complete knowledge about its
1177 // children. If this returns true, you can perform any of the below
1178 // queries on it. If not, you can only perform queries related to
1179 // the parents (non-missing classes always have complete parent
1180 // information). The "raw" variant ignores any permission checks.
1181 bool hasCompleteChildren() const;
1182 bool hasCompleteChildrenRaw() const;
1184 // A conservative class is one which will never have complete
1185 // children. This generally means that the class has too many
1186 // subclasses to efficiently represent. We treat such classes
1187 // conservatively, except in a few cases. The "raw" variant ignores
1188 // any permission checks.
1189 bool isConservative() const;
1190 bool isConservativeRaw() const;
1192 // Whether this class is an interface, a trait, an enum, or an
1193 // abstract class. It is invalid to check these if isMissing() is
1195 bool isInterface() const;
1196 bool isTrait() const;
1197 bool isEnum() const;
1198 bool isAbstract() const;
1200 // Retrieve the immediate base class of this class. If this class
1201 // doesn't have an immediate base, or if isMissing() is true, a
1202 // falsey ClassGraph is returned.
1203 ClassGraph
base() const;
1205 // Retrieve the "topmost" base class of this class. This is the base
1206 // class which does not have a base class.
1207 ClassGraph
topBase() const;
1209 // Retrieve all base classes of this class, including this
1210 // class. The ordering is from top-most class to this one (so this
1211 // one will be the last element). The returned classes may not have
1212 // complete child info, even if this class does.
1213 std::vector
<ClassGraph
> bases() const;
1215 // Retrieve all interfaces implemented by this class, either
1216 // directly, or by transitive parents. This includes this class if
1217 // it is an interface. The ordering is alphabetical. Like bases(),
1218 // the returned classes may not have complete child info, even if
1220 std::vector
<ClassGraph
> interfaces() const;
1222 // Walk over all parents of this class, calling the supplied
1223 // callable with each parent. If the callable returns true, that
1224 // parent's parents will then be visited. If false, then they will
1226 template <typename F
> void walkParents(const F
&) const;
1228 // Retrieve all children of this class (including this class
1229 // itself). This is only valid to call if hasCompleteChildren() is
1230 // true. NB: Being on a class' children list does not necessarily
1231 // mean that it has a "is-a" relationship. Namely, classes on a
1232 // trait's children list are not instances of the trait itself.
1233 std::vector
<ClassGraph
> children() const;
1235 // Retrieve the interfaces implemented by this class directly.
1236 std::vector
<ClassGraph
> declInterfaces() const {
1237 return directParents(FlagInterface
);
1239 // Retrieve the set of non-flattened traits used by this class
1240 // directly. Any traits flattened into this class will not appear.
1241 std::vector
<ClassGraph
> usedTraits() const {
1242 return directParents(FlagTrait
);
1245 // Retrieve the direct parents of this class (all of the classes for
1246 // which this class is a direct child).
1247 std::vector
<ClassGraph
> directParents() const;
1249 // Returns true if this class is a child of the other class.
1250 bool isChildOf(ClassGraph
) const;
1252 // Retrieve the set of classes which *might* be equivalent to this
1253 // class when ignoring non-regular classes. This does not include
1254 // subclasses of this class.
1255 std::vector
<ClassGraph
> candidateRegOnlyEquivs() const;
1258 bool exactSubtypeOfExact(ClassGraph
, bool nonRegL
, bool nonRegR
) const;
1259 bool exactSubtypeOf(ClassGraph
, bool nonRegL
, bool nonRegR
) const;
1260 bool subSubtypeOf(ClassGraph
, bool nonRegL
, bool nonRegR
) const;
1263 bool exactCouldBeExact(ClassGraph
, bool nonRegL
, bool nonRegR
) const;
1264 bool exactCouldBe(ClassGraph
, bool nonRegL
, bool nonRegR
) const;
1265 bool subCouldBe(ClassGraph
, bool nonRegL
, bool nonRegR
) const;
1267 // "Ensures" that this ClassGraph will be present (with the
1268 // requested information) for subsequent analysis rounds. If these
1269 // functions return false, you must treat this ClassGraph as if it's
1271 [[nodiscard
]] bool ensure() const;
1272 [[nodiscard
]] bool ensureWithChildren() const;
1273 [[nodiscard
]] bool ensureCInfo() const;
1275 // Check if you're allowed to use this ClassGraph's knowledge (and
1276 // child info if specified). If this returns false, you must treat
1277 // the ClassGraph as if it was missing.
1278 bool allowed(bool children
) const;
1280 // Used when building ClassGraphs initially.
1281 void setClosureBase();
1283 void setBase(ClassGraph
);
1284 void addParent(ClassGraph
);
1285 void flattenTraitInto(ClassGraph
);
1286 void setCInfo(ClassInfo
&);
1287 void setRegOnlyEquivs() const;
1288 void finalizeParents();
1291 // ClassGraphs are ordered by their name alphabetically.
1292 bool operator==(ClassGraph h
) const { return this_
== h
.this_
; }
1293 bool operator!=(ClassGraph h
) const { return this_
!= h
.this_
; }
1294 bool operator<(ClassGraph h
) const;
1296 // Create a new ClassGraph corresponding to the given php::Class.
1297 // This function will assert if a ClassGraph with the php::Class'
1298 // name already exists.
1299 static ClassGraph
create(const php::Class
&);
1300 // Retrieve a ClassGraph with the given name, asserting if one
1302 static ClassGraph
get(SString
);
1303 // Retrieve a ClassGraph with the given name. If one doesn't already
1304 // existing, one will be created as if by calling getMissing().
1305 static ClassGraph
getOrCreate(SString
);
1306 // Retrieve a "missing" ClassGraph with the given name, asserting if
1307 // a non-missing one already exists. This is mainly used for tests.
1308 static ClassGraph
getMissing(SString
);
1310 // Before using ClassGraph, it must be initialized (particularly
1311 // before deserializing any). initConcurrent() must be used if any
1312 // deserialization with be performed in multiple threads
1315 static void initConcurrent();
1316 // If initConcurrent() was used, this must be used when
1317 // deserialization is done and perform querying any ClassGraphs.
1318 static void stopConcurrent();
1319 // Called to clean up memory used by ClassGraph framework.
1320 static void destroy();
1322 // Set/clear an AnalysisIndex to use for calls to ensure() (and
1324 static void setAnalysisIndex(AnalysisIndex::IndexData
&);
1325 static void clearAnalysisIndex();
1327 template <typename SerDe
, typename T
> void serde(SerDe
&, T
, bool = false);
1329 // When serializing multiple ClassGraphs, this can be declared
1330 // before serializing any of them, to allow for the serialization to
1331 // share common state and take up less space.
1332 struct ScopedSerdeState
;
1334 friend struct ClassGraphHasher
;
1341 using NodeSet
= hphp_fast_set
<Node
*>;
1342 template <typename T
> using NodeMap
= hphp_fast_map
<Node
*, T
>;
1343 using NodeVec
= TinyVector
<Node
*, 4>;
1346 struct TLNodeIdxSet
;
1350 template <typename
> struct ParentTracker
;
1352 enum Flags
: std::uint16_t {
1354 FlagInterface
= (1 << 0),
1355 FlagTrait
= (1 << 1),
1356 FlagAbstract
= (1 << 2),
1357 FlagEnum
= (1 << 3),
1358 FlagCInfo2
= (1 << 4),
1359 FlagRegSub
= (1 << 5),
1360 FlagNonRegSub
= (1 << 6),
1361 FlagWait
= (1 << 7),
1362 FlagChildren
= (1 << 8),
1363 FlagConservative
= (1 << 9),
1364 FlagMissing
= (1 << 10)
1367 // These are only set at runtime, so shouldn't be serialized in a
1369 static constexpr Flags kSerializable
=
1370 (Flags
)~(FlagCInfo2
| FlagRegSub
| FlagNonRegSub
|
1371 FlagWait
| FlagChildren
| FlagConservative
);
1373 // Iterating through parents or children can result in one of three
1374 // different outcomes:
1376 Continue
, // Keep iterating into any children/parents
1377 Stop
, // Stop iteration entirely
1378 Skip
// Continue iteration, but skip over any children/parents of
1382 std::vector
<ClassGraph
> directParents(Flags
) const;
1384 bool storeAuxs(AnalysisIndex::IndexData
&, bool) const;
1385 bool onAuxs(AnalysisIndex::IndexData
&, bool) const;
1387 static Table
& table();
1389 static NodeVec
combine(const NodeVec
&, const NodeVec
&,
1390 bool, bool, bool, bool);
1391 static NodeVec
intersect(const NodeVec
&, const NodeVec
&,
1393 static NodeVec
removeNonReg(const NodeVec
&);
1394 static bool couldBeIsect(const NodeVec
&, const NodeVec
&, bool, bool);
1395 static NodeVec
canonicalize(const NodeSet
&, bool);
1397 template <typename F
>
1398 static void enumerateIsectMembers(const NodeVec
&, bool,
1399 const F
&, bool = false);
1401 static std::pair
<NodeSet
, NodeSet
> calcSubclassOfSplit(Node
&);
1402 static NodeSet
calcSubclassOf(Node
&);
1403 static Node
* calcRegOnlyEquiv(Node
&, const NodeSet
&);
1405 // Rank nodes, optionally taking into account permission
1407 template <bool> static bool betterNode(Node
*, Node
*);
1409 template <typename F
>
1410 static Action
forEachParent(Node
&, const F
&, NodeIdxSet
&);
1411 template <typename F
>
1412 static Action
forEachParent(Node
&, const F
&);
1413 template <typename F
>
1414 static Action
forEachParentImpl(Node
&, const F
&, NodeIdxSet
*, bool);
1416 template <typename F
>
1417 static Action
forEachChild(Node
&, const F
&, NodeIdxSet
&);
1418 template <typename F
>
1419 static Action
forEachChild(Node
&, const F
&);
1420 template <typename F
>
1421 static Action
forEachChildImpl(Node
&, const F
&, NodeIdxSet
*, bool);
1423 template <typename F
, typename F2
, typename T
>
1424 static T
foldParents(Node
& n
, const F
& f
, const F2
& f2
, NodeMap
<T
>& m
) {
1425 return foldParentsImpl(n
, f
, f2
, m
, true);
1427 template <typename F
, typename F2
, typename T
>
1428 static T
foldParentsImpl(Node
&, const F
&, const F2
&, NodeMap
<T
>&, bool);
1430 static bool findParent(Node
&, Node
&, NodeIdxSet
&);
1431 static bool findParent(Node
&, Node
&);
1433 static NodeSet
allParents(Node
&);
1435 struct LockedSerdeImpl
;
1436 struct UnlockedSerdeImpl
;
1438 template <typename SerDe
> static void encodeName(SerDe
&, SString
);
1439 template <typename SerDe
> static SString
decodeName(SerDe
&);
1441 template <typename SerDe
, typename Impl
, typename T
>
1442 void serdeImpl(SerDe
&, const Impl
&, T
, bool);
1444 template <typename SerDe
, typename Impl
>
1445 static void deserBlock(SerDe
&, const Impl
&);
1446 template <typename SerDe
> static size_t serDownward(SerDe
&, Node
&);
1447 template <typename SerDe
> static bool serUpward(SerDe
&, Node
&);
1449 template <typename Impl
>
1450 static std::pair
<Flags
, Optional
<size_t>> setCompleteImpl(const Impl
&, Node
&);
1452 template <typename Impl
>
1453 static void setConservative(const Impl
&, Node
&, bool, bool);
1455 static std::unique_ptr
<Table
> g_table
;
1456 static __thread SerdeState
* tl_serde_state
;
1458 friend struct res::Class
;
1460 explicit ClassGraph(Node
* n
) : this_
{n
} {}
1462 Node
* this_
{nullptr};
1465 std::unique_ptr
<ClassGraph::Table
> ClassGraph::g_table
{nullptr};
1466 __thread
ClassGraph::SerdeState
* ClassGraph::tl_serde_state
{nullptr};
1468 struct ClassGraphHasher
{
1469 size_t operator()(ClassGraph g
) const {
1470 return pointer_hash
<ClassGraph::Node
>{}(g
.this_
);
1474 // Node on the graph:
1475 struct ClassGraph::Node
{
1476 // The flags are stored along with any ClassInfo to save memory.
1477 using CIAndFlags
= CompactTaggedPtr
<void, Flags
>;
1479 SString name
{nullptr};
1480 // Atomic because they might be manipulated from multiple threads
1481 // during deserialization.
1482 std::atomic
<CIAndFlags::Opaque
> ci
{CIAndFlags
{}.getOpaque()};
1483 // Direct (not transitive) parents and children of this node.
1484 CompactVector
<Node
*> parents
;
1485 CompactVector
<Node
*> children
;
1487 // This information is lazily cached (and is not serialized).
1488 struct NonRegularInfo
{
1490 Node
* regOnlyEquiv
{nullptr};
1492 LockFreeLazyPtr
<NonRegularInfo
> nonRegInfo
;
1493 LockFreeLazyPtr
<NonRegularInfo
> nonRegInfoDisallow
;
1495 // Unique sequential id assigned to every node. Used for NodeIdxSet.
1496 using Idx
= uint32_t;
1499 Flags
flags() const { return CIAndFlags
{ci
.load()}.tag(); }
1500 ClassInfo
* cinfo() const {
1501 CIAndFlags cif
{ci
.load()};
1502 if (cif
.tag() & FlagCInfo2
) return nullptr;
1503 return (ClassInfo
*)cif
.ptr();
1505 ClassInfo2
* cinfo2() const {
1506 CIAndFlags cif
{ci
.load()};
1507 if (!(cif
.tag() & FlagCInfo2
)) return nullptr;
1508 return (ClassInfo2
*)cif
.ptr();
1510 void* rawPtr() const {
1511 CIAndFlags cif
{ci
.load()};
1515 bool isBase() const { return !(flags() & (FlagInterface
| FlagTrait
)); }
1516 bool isRegular() const {
1517 return !(flags() & (FlagInterface
| FlagTrait
| FlagAbstract
| FlagEnum
));
1519 bool isTrait() const { return flags() & FlagTrait
; }
1520 bool isInterface() const { return flags() & FlagInterface
; }
1521 bool isEnum() const { return flags() & FlagEnum
; }
1522 bool isAbstract() const { return flags() & FlagAbstract
; }
1523 bool isMissing() const { return flags() & FlagMissing
; }
1524 bool hasCompleteChildren() const { return flags() & FlagChildren
; }
1525 bool isConservative() const { return flags() & FlagConservative
; }
1527 bool hasRegularSubclass() const { return flags() & FlagRegSub
; }
1528 bool hasNonRegularSubclass() const { return flags() & FlagNonRegSub
; }
1530 const NonRegularInfo
& nonRegularInfo();
1532 // NB: These aren't thread-safe, so don't use them during concurrent
1534 void setFlags(Flags f
, Flags r
= FlagNone
) {
1535 CIAndFlags old
{ci
.load()};
1536 old
.set((Flags
)((old
.tag() | f
) & ~r
), old
.ptr());
1537 ci
.store(old
.getOpaque());
1539 void setCInfo(ClassInfo
& c
) {
1540 CIAndFlags old
{ci
.load()};
1541 old
.set((Flags
)(old
.tag() & ~FlagCInfo2
), &c
);
1542 ci
.store(old
.getOpaque());
1544 void setCInfo(ClassInfo2
& c
) {
1545 CIAndFlags old
{ci
.load()};
1546 old
.set((Flags
)(old
.tag() | FlagCInfo2
), &c
);
1547 ci
.store(old
.getOpaque());
1551 bool operator()(const Node
* a
, const Node
* b
) const {
1554 return string_data_lt_type
{}(a
->name
, b
->name
);
1559 // Efficient set of ClassGraph::Nodes.
1560 struct ClassGraph::NodeIdxSet
{
1564 if (n
.idx
>= set
.universe_size()) {
1565 set
.resize(folly::nextPowTwo(n
.idx
+1));
1567 return set
.insert(&n
);
1569 void erase(Node
& n
) {
1570 if (n
.idx
< set
.universe_size()) {
1574 void clear() { set
.clear(); }
1575 bool empty() { return set
.empty(); }
1578 Node::Idx
operator()(const Node
* n
) const {
1579 assertx(n
->idx
> 0);
1583 sparse_id_set
<Node::Idx
, const Node
*, Extract
> set
;
1586 // Thread-local NodeIdxSet which automatically clears itself
1587 // afterwards (thus avoids memory allocation).
1588 struct ClassGraph::TLNodeIdxSet
{
1589 TLNodeIdxSet() : set
{get()} { assertx(set
.empty()); }
1593 sets().emplace_back(std::move(set
));
1596 NodeIdxSet
& operator*() { return set
; }
1597 NodeIdxSet
* operator->() { return &set
; }
1601 static NodeIdxSet
get() {
1603 if (s
.empty()) return {};
1604 auto set
= std::move(s
.back());
1609 static std::vector
<NodeIdxSet
>& sets() {
1610 static thread_local
std::vector
<NodeIdxSet
> s
;
1615 struct ClassGraph::Table
{
1616 // Node map to ensure pointer stability.
1617 TSStringToOneNodeT
<Node
> nodes
;
1618 // Mapping of one node equivalent to another when only considering
1619 // regular subclasses. No entry if the mapping is an identity or to
1620 // a subclass (which is common). Stored separately to save memory
1622 hphp_fast_map
<Node
*, Node
*> regOnlyEquivs
;
1623 hphp_fast_map
<Node
*, size_t> completeSizeCache
;
1624 AnalysisIndex::IndexData
* index
{nullptr};
1626 mutable folly::SharedMutex table
;
1627 std::array
<std::mutex
, 2048> nodes
;
1628 mutable folly::SharedMutex equivs
;
1629 mutable folly::SharedMutex sizes
;
1631 // If present, we're doing concurrent deserialization.
1632 Optional
<Locking
> locking
;
1635 ClassGraph::NodeIdxSet::NodeIdxSet()
1636 : set
{folly::nextPowTwo((Node::Idx
)(table().nodes
.size() + 1))}
1638 assertx(!table().locking
);
1641 struct ClassGraph::SerdeState
{
1642 Optional
<NodeIdxSet
> upward
;
1643 Optional
<NodeIdxSet
> downward
;
1645 std::vector
<SString
> strings
;
1646 std::vector
<SString
> newStrings
;
1647 SStringToOneT
<size_t> strToIdx
;
1650 struct ClassGraph::ScopedSerdeState
{
1651 ScopedSerdeState() {
1652 // If there's no SerdeState active, make one active, otherwise do
1654 if (tl_serde_state
) return;
1656 tl_serde_state
= s
.get_pointer();
1659 ~ScopedSerdeState() {
1660 if (!s
.has_value()) return;
1661 assertx(tl_serde_state
== s
.get_pointer());
1662 tl_serde_state
= nullptr;
1665 ScopedSerdeState(const ScopedSerdeState
&) = delete;
1666 ScopedSerdeState(ScopedSerdeState
&&) = delete;
1667 ScopedSerdeState
& operator=(const ScopedSerdeState
&) = delete;
1668 ScopedSerdeState
& operator=(ScopedSerdeState
&&) = delete;
1670 Optional
<SerdeState
> s
;
1674 * When operating over ClassGraph nodes, we often need compact sets of
1675 * them. This is easy to do with bitsets, but we don't have a fixed
1676 * upper-size of the sets. We could always use dynamic_bitset, but
1677 * this can be inefficient. Instead we templatize the algorithm over
1678 * the bitset, and use a fixed size std::bitset for the common case
1679 * and dynamic_bitset for the (rare) exceptions.
1681 struct ClassGraph::SmallBitset
{
1682 explicit SmallBitset(size_t limit
) {
1683 assertx(limit
<= bits
.size());
1685 SmallBitset(size_t limit
, size_t idx
) {
1686 assertx(limit
<= bits
.size());
1687 assertx(idx
< limit
);
1690 SmallBitset
& operator|=(const SmallBitset
& o
) {
1694 SmallBitset
& operator&=(const SmallBitset
& o
) {
1698 SmallBitset
& operator-=(size_t idx
) {
1699 assertx(idx
< bits
.size());
1703 SmallBitset
& flip(size_t limit
) {
1704 assertx(limit
<= bits
.size());
1706 auto const offset
= bits
.size() - limit
;
1711 bool test(size_t idx
) const {
1712 assertx(idx
< bits
.size());
1715 bool any() const { return bits
.any(); }
1716 bool none() const { return bits
.none(); }
1717 bool all(size_t limit
) const {
1722 size_t first() const { return bitset_find_first(bits
); }
1723 size_t next(size_t prev
) const { return bitset_find_next(bits
, prev
); }
1724 bool operator==(const SmallBitset
& o
) const { return bits
== o
.bits
; }
1726 static constexpr size_t kMaxSize
= 64;
1727 using B
= std::bitset
<kMaxSize
>;
1731 size_t operator()(const SmallBitset
& b
) const {
1732 return std::hash
<B
>{}(b
.bits
);
1737 struct ClassGraph::LargeBitset
{
1738 explicit LargeBitset(size_t limit
): bits
{limit
} {}
1739 LargeBitset(size_t limit
, size_t idx
): bits
{limit
} {
1740 assertx(idx
< limit
);
1743 LargeBitset
& operator|=(const LargeBitset
& o
) {
1747 LargeBitset
& operator&=(const LargeBitset
& o
) {
1751 LargeBitset
& operator-=(size_t idx
) {
1752 assertx(idx
< bits
.size());
1756 LargeBitset
& flip(size_t limit
) {
1757 assertx(limit
== bits
.size());
1761 bool test(size_t idx
) const {
1762 assertx(idx
< bits
.size());
1765 bool any() const { return bits
.any(); }
1766 bool none() const { return bits
.none(); }
1767 bool all(size_t) const { return bits
.all(); }
1768 size_t first() const { return bits
.find_first(); }
1769 size_t next(size_t prev
) const { return bits
.find_next(prev
); }
1770 bool operator==(const LargeBitset
& o
) const { return bits
== o
.bits
; }
1772 boost::dynamic_bitset
<> bits
;
1775 size_t operator()(const LargeBitset
& b
) const {
1776 return std::hash
<boost::dynamic_bitset
<>>{}(b
.bits
);
1781 // Helper class (parameterized over bitset type) to track Nodes and
1783 template <typename Set
>
1784 struct ClassGraph::ParentTracker
{
1785 // Heads is the universe of nodes which are tracked.
1786 template <typename T
>
1787 explicit ParentTracker(const T
& heads
, const NodeSet
* ignore
= nullptr)
1788 : count
{heads
.size()}
1792 toNode
.insert(heads
.begin(), heads
.end());
1793 indices
.reserve(count
);
1794 for (size_t i
= 0; i
< count
; ++i
) indices
[toNode
[i
]] = i
;
1798 // Intersect this node and it's parents with the current valid ones
1799 // and return true if any remain.
1800 bool operator()(Node
& n
) {
1804 // Return true if this node and it's parents contain all nodes being
1806 bool all(Node
& n
) { return set(n
).all(count
); }
1808 // Return all nodes left in the valid set.
1809 NodeSet
nodes() const {
1811 for (auto i
= valid
.first(); i
< count
; i
= valid
.next(i
)) {
1812 s
.emplace(toNode
[i
]);
1819 explicit Wrapper(size_t limit
): s
{limit
} {}
1820 Wrapper(size_t limit
, size_t b
): s
{limit
, b
} {}
1821 explicit operator bool() const { return stop
; }
1822 Wrapper
& operator|=(const Wrapper
& o
) {
1832 if (n
.isMissing()) {
1833 auto const idx
= folly::get_ptr(indices
, &n
);
1834 if (!idx
) return Set
{count
};
1835 assertx(*idx
< count
);
1836 return Set
{count
, *idx
};
1839 auto wrapper
= foldParents(
1842 if (ignore
&& ignore
->count(&p
)) {
1847 auto const idx
= folly::get_ptr(indices
, &p
);
1848 if (!idx
) return Wrapper
{count
};
1849 assertx(*idx
< count
);
1850 return Wrapper
{count
, *idx
};
1852 [&] { return Wrapper
{count
}; },
1860 NodeMap
<size_t> indices
;
1861 NodeMap
<Wrapper
> nodeToParents
;
1863 const NodeSet
* ignore
;
1866 // Abstractions for whether we're deserializing concurrently or
1867 // not. This separates out the locking logic and let's us avoid any
1868 // runtime checks (except one) during deserializing to see if we need
1871 // Non-concurrent implementation. This just modifies the fields
1872 // without any locking.
1873 struct ClassGraph::UnlockedSerdeImpl
{
1874 std::pair
<Node
*, bool> create(SString name
) const {
1876 auto& n
= t
.nodes
[name
];
1878 assertx(n
.name
->tsame(name
));
1879 return std::make_pair(&n
, false);
1882 assertx(t
.nodes
.size() < std::numeric_limits
<Node::Idx
>::max());
1883 n
.idx
= t
.nodes
.size();
1885 return std::make_pair(&n
, true);
1887 Node
& get(SString name
) const {
1888 auto n
= folly::get_ptr(table().nodes
, name
);
1891 "Attempting to retrieve missing ClassGraph node '{}'",
1894 assertx(n
->name
->tsame(name
));
1897 void setEquiv(Node
& n
, Node
& e
) const {
1898 auto const [it
, s
] = table().regOnlyEquivs
.emplace(&n
, &e
);
1899 always_assert(s
|| it
->second
== &e
);
1901 size_t getCompleteSize(Node
& n
) const {
1902 auto const size
= folly::get_default(table().completeSizeCache
, &n
);
1903 always_assert(size
> 1);
1906 void setCompleteSize(Node
& n
, size_t size
) const {
1908 auto const [it
, s
] = table().completeSizeCache
.emplace(&n
, size
);
1909 always_assert(s
|| it
->second
== size
);
1911 template <typename F
> void lock(Node
&, const F
& f
) const { f(); }
1912 template <typename F
> void forEachChild(Node
& n
, const F
& f
) const {
1913 for (auto const c
: n
.children
) f(*c
);
1915 void signal(Node
& n
, Flags f
) const {
1916 assertx(n
.flags() == FlagNone
);
1917 assertx(!(f
& FlagWait
));
1920 void setCInfo(Node
& n
, ClassInfo
& cinfo
) const { n
.setCInfo(cinfo
); }
1921 void setCInfo(Node
& n
, ClassInfo2
& cinfo
) const { n
.setCInfo(cinfo
); }
1922 void updateFlags(Node
& n
, Flags add
, Flags remove
= FlagNone
) const {
1923 if (add
== FlagNone
&& remove
== FlagNone
) return;
1924 auto const oldFlags
= n
.flags();
1925 auto const newFlags
= (Flags
)((oldFlags
| add
) & ~remove
);
1926 if (newFlags
== oldFlags
) return;
1927 n
.ci
.store(Node::CIAndFlags
{newFlags
, n
.rawPtr()}.getOpaque());
1931 // Concurrent implementation. The node table is guarded by a RWLock
1932 // and the individual nodes are guarded by an array of locks. The hash
1933 // of the node's address determines the lock to use. In addition,
1934 // FlagWait is used to tell threads that the node is being created and
1935 // one must wait for the flag to be reset.
1936 struct ClassGraph::LockedSerdeImpl
{
1937 std::pair
<Node
*, bool> create(SString name
) const {
1938 // Called when we find an existing node. We cannot safely return
1939 // this node until FlagWait is cleared.
1940 auto const wait
= [&] (Node
& n
) {
1941 assertx(n
.name
->tsame(name
));
1943 Node::CIAndFlags f
{n
.ci
.load()};
1944 if (!(f
.tag() & FlagWait
)) return std::make_pair(&n
, false);
1945 // Still set, wait for it to change and then check again.
1946 n
.ci
.wait(f
.getOpaque());
1952 // First access the table with a read lock and see if the node
1955 auto const n
= [&] {
1956 std::shared_lock _
{t
.locking
->table
};
1957 return folly::get_ptr(t
.nodes
, name
);
1959 // It already exists, wait on FlagWait and then return.
1960 if (n
) return wait(*n
);
1963 // It didn't, we need to create it:
1964 std::unique_lock _
{t
.locking
->table
};
1965 // We now have exlusive access to the table. Check for the node
1966 // again, as someone else might have created it in the meantime.
1967 if (auto const n
= folly::get_ptr(t
.nodes
, name
)) {
1968 // If someone did, wait on FlagWait and then return. Drop the
1969 // write lock before waiting to avoid deadlock.
1974 // Node still isn't present. Create one now.
1975 auto [it
, emplaced
] = t
.nodes
.try_emplace(name
);
1976 always_assert(emplaced
);
1977 auto& n
= it
->second
;
1979 assertx(!(n
.flags() & FlagWait
));
1981 assertx(t
.nodes
.size() < std::numeric_limits
<Node::Idx
>::max());
1982 n
.idx
= t
.nodes
.size();
1984 // Set FlagWait, this will ensure that any other thread who
1985 // retrieves this node (after we drop the write lock) will block
1986 // until we're done deserializing it and it's children.
1987 n
.setFlags(FlagWait
);
1988 return std::make_pair(&n
, true);
1990 Node
& get(SString name
) const {
1992 std::shared_lock _
{t
.locking
->table
};
1993 auto n
= folly::get_ptr(t
.nodes
, name
);
1996 "Attempting to retrieve missing ClassGraph node '{}'",
1999 assertx(n
->name
->tsame(name
));
2000 // FlagWait shouldn't be set here because we shouldn't call get()
2001 // until a node and all of it's dependents are created.
2002 assertx(!(n
->flags() & FlagWait
));
2005 void setEquiv(Node
& n
, Node
& e
) const {
2008 std::shared_lock _
{t
.locking
->equivs
};
2009 if (auto const old
= folly::get_default(t
.regOnlyEquivs
, &n
)) {
2014 std::unique_lock _
{t
.locking
->equivs
};
2015 auto const [it
, s
] = t
.regOnlyEquivs
.emplace(&n
, &e
);
2016 always_assert(s
|| it
->second
== &e
);
2018 size_t getCompleteSize(Node
& n
) const {
2020 std::shared_lock _
{t
.locking
->sizes
};
2021 auto const size
= folly::get_default(t
.completeSizeCache
, &n
);
2022 always_assert(size
> 1);
2025 void setCompleteSize(Node
& n
, size_t size
) const {
2028 std::unique_lock _
{t
.locking
->sizes
};
2029 auto const [it
, s
] = t
.completeSizeCache
.emplace(&n
, size
);
2030 always_assert(s
|| it
->second
== size
);
2032 // Lock a node by using the lock it hashes to and execute f() while
2033 // holding the lock.
2034 template <typename F
> void lock(Node
& n
, const F
& f
) const {
2036 auto& lock
= t
.locking
->nodes
[
2037 pointer_hash
<Node
>{}(&n
) % t
.locking
->nodes
.size()
2040 SCOPE_EXIT
{ lock
.unlock(); };
2043 template <typename F
> void forEachChild(Node
& n
, const F
& f
) const {
2044 CompactVector
<Node
*> children
;
2045 lock(n
, [&] { children
= n
.children
; });
2046 for (auto const c
: children
) f(*c
);
2048 // Signal that a node (and all of it's dependents) is done being
2049 // deserialized. This clears FlagWait and wakes up any threads
2050 // waiting on the flag. In addition, it sets the node's flags to the
2052 void signal(Node
& n
, Flags other
) const {
2053 assertx(!(other
& FlagWait
));
2055 auto old
= n
.ci
.load();
2056 Node::CIAndFlags f
{old
};
2057 assertx(f
.tag() == FlagWait
);
2058 // Use CAS to set the flag
2059 f
.set(other
, f
.ptr());
2060 if (n
.ci
.compare_exchange_strong(old
, f
.getOpaque())) break;
2062 // Wake up any other threads.
2065 // Set a ClassInfo2 on this node, using CAS to detect concurrent
2067 void setCInfo(Node
& n
, ClassInfo
& c
) const {
2069 auto old
= n
.ci
.load();
2070 Node::CIAndFlags f
{old
};
2071 f
.set((Flags
)(f
.tag() & ~FlagCInfo2
), &c
);
2072 if (n
.ci
.compare_exchange_strong(old
, f
.getOpaque())) break;
2075 void setCInfo(Node
& n
, ClassInfo2
& c
) const {
2077 auto old
= n
.ci
.load();
2078 Node::CIAndFlags f
{old
};
2079 f
.set((Flags
)(f
.tag() | FlagCInfo2
), &c
);
2080 if (n
.ci
.compare_exchange_strong(old
, f
.getOpaque())) break;
2083 void updateFlags(Node
& n
, Flags add
, Flags remove
= FlagNone
) const {
2084 if (add
== FlagNone
&& remove
== FlagNone
) return;
2086 auto old
= n
.ci
.load();
2087 Node::CIAndFlags f
{old
};
2088 auto const oldFlags
= (Flags
)f
.tag();
2089 auto const newFlags
= (Flags
)((oldFlags
| add
) & ~remove
);
2090 if (newFlags
== oldFlags
) break;
2091 f
.set(newFlags
, f
.ptr());
2092 if (n
.ci
.compare_exchange_strong(old
, f
.getOpaque())) break;
2097 SString
ClassGraph::name() const {
2102 ClassInfo
* ClassGraph::cinfo() const {
2104 return this_
->cinfo();
2107 ClassInfo2
* ClassGraph::cinfo2() const {
2109 return this_
->cinfo2();
2112 bool ClassGraph::mightBeRegular() const {
2114 return isMissing() || this_
->isRegular();
2117 bool ClassGraph::mightBeNonRegular() const {
2119 return isMissing() || !this_
->isRegular();
2122 bool ClassGraph::mightHaveRegularSubclass() const {
2124 if (this_
->hasCompleteChildren() || this_
->isConservative()) {
2125 return this_
->hasRegularSubclass() || !allowed(true);
2130 bool ClassGraph::mightHaveNonRegularSubclass() const {
2132 if (this_
->hasCompleteChildren() || this_
->isConservative()) {
2133 return this_
->hasNonRegularSubclass() || !allowed(true);
2138 bool ClassGraph::isMissing() const {
2140 return this_
->isMissing() || !allowed(false);
2143 bool ClassGraph::isMissingRaw() const {
2145 return this_
->isMissing();
2148 bool ClassGraph::hasCompleteChildren() const {
2150 return !this_
->isMissing() && this_
->hasCompleteChildren() && allowed(true);
2153 bool ClassGraph::hasCompleteChildrenRaw() const {
2155 return !this_
->isMissing() && this_
->hasCompleteChildren();
2158 bool ClassGraph::isConservative() const {
2160 return !this_
->isMissing() && this_
->isConservative() && allowed(true);
2163 bool ClassGraph::isConservativeRaw() const {
2165 return !this_
->isMissing() && this_
->isConservative();
2168 bool ClassGraph::isInterface() const {
2170 assertx(!isMissing());
2171 return this_
->isInterface();
2174 bool ClassGraph::isTrait() const {
2176 assertx(!isMissing());
2177 return this_
->isTrait();
2180 bool ClassGraph::isEnum() const {
2182 assertx(!isMissing());
2183 return this_
->isEnum();
2186 bool ClassGraph::isAbstract() const {
2188 assertx(!isMissing());
2189 return this_
->isAbstract();
2192 void ClassGraph::setComplete() {
2193 assertx(!table().locking
);
2195 assertx(!this_
->isMissing());
2196 assertx(!this_
->hasCompleteChildren());
2197 assertx(!this_
->isConservative());
2198 setCompleteImpl(UnlockedSerdeImpl
{}, *this_
);
2201 void ClassGraph::setClosureBase() {
2202 assertx(!table().locking
);
2204 assertx(!this_
->isMissing());
2205 assertx(!this_
->hasCompleteChildren());
2206 assertx(!this_
->isConservative());
2207 assertx(is_closure_base(this_
->name
));
2208 this_
->children
.clear();
2209 this_
->setFlags((Flags
)(FlagConservative
| FlagRegSub
));
2212 void ClassGraph::setBase(ClassGraph b
) {
2213 assertx(!table().locking
);
2216 assertx(!isMissing());
2217 assertx(!b
.isMissing());
2218 assertx(b
.this_
->isBase());
2219 this_
->parents
.emplace_back(b
.this_
);
2220 if (!b
.this_
->isConservative()) {
2221 b
.this_
->children
.emplace_back(this_
);
2225 void ClassGraph::addParent(ClassGraph p
) {
2226 assertx(!table().locking
);
2229 assertx(!isMissing());
2230 assertx(!p
.isMissing());
2231 assertx(!p
.this_
->isBase());
2232 this_
->parents
.emplace_back(p
.this_
);
2233 if (!p
.this_
->isConservative()) {
2234 p
.this_
->children
.emplace_back(this_
);
2238 void ClassGraph::flattenTraitInto(ClassGraph t
) {
2239 assertx(!table().locking
);
2242 assertx(t
.this_
->isTrait());
2243 assertx(!isMissing());
2244 assertx(!t
.isMissing());
2245 assertx(!t
.isConservative());
2246 assertx(!t
.hasCompleteChildren());
2248 // Remove this trait as a parent, and move all of it's parents into
2250 always_assert(this_
->parents
.erase(t
.this_
));
2251 always_assert(t
.this_
->children
.erase(this_
));
2252 for (auto const p
: t
.this_
->parents
) {
2253 this_
->parents
.emplace_back(p
);
2254 p
->children
.emplace_back(this_
);
2258 // Indicates that all possible parents has been added to this
2259 // node. Puts the parents list in canonical order.
2260 void ClassGraph::finalizeParents() {
2261 assertx(!table().locking
);
2263 assertx(!this_
->isMissing());
2264 assertx(!this_
->isConservative());
2265 assertx(!this_
->hasCompleteChildren());
2266 std::sort(begin(this_
->parents
), end(this_
->parents
), Node::Compare
{});
2269 void ClassGraph::reset() {
2270 assertx(!table().locking
);
2272 assertx(this_
->children
.empty());
2273 assertx(!this_
->isMissing());
2274 assertx(!this_
->isConservative());
2275 assertx(!this_
->hasCompleteChildren());
2276 for (auto const parent
: this_
->parents
) {
2277 if (parent
->isConservative()) continue;
2278 always_assert(parent
->children
.erase(this_
));
2280 this_
->parents
.clear();
2281 this_
->ci
.store(Node::CIAndFlags::Opaque
{});
2285 void ClassGraph::setCInfo(ClassInfo
& ci
) {
2286 assertx(!table().locking
);
2288 assertx(!this_
->isMissing());
2289 assertx(this_
->hasCompleteChildren() || this_
->isConservative());
2290 this_
->setCInfo(ci
);
2293 bool ClassGraph::operator<(ClassGraph h
) const {
2294 return string_data_lt_type
{}(this_
->name
, h
.this_
->name
);
2297 ClassGraph
ClassGraph::withoutNonRegular() const {
2298 assertx(!table().locking
);
2300 if (!ensure() || this_
->isMissing() || this_
->isRegular()) {
2303 return ClassGraph
{ this_
->nonRegularInfo().regOnlyEquiv
};
2306 ClassGraph
ClassGraph::base() const {
2307 assertx(!table().locking
);
2309 if (ensure() && !this_
->isMissing()) {
2310 for (auto const p
: this_
->parents
) {
2311 if (p
->isBase()) return ClassGraph
{ p
};
2314 return ClassGraph
{ nullptr };
2317 ClassGraph
ClassGraph::topBase() const {
2318 assertx(!table().locking
);
2320 assertx(!isMissing());
2322 always_assert(ensure());
2324 auto current
= this_
;
2328 auto const& parents
= current
->parents
;
2330 // There should be at most one base on the parent list. Verify
2331 // this in debug builds.
2332 for (auto const p
: parents
) {
2341 return ClassGraph
{ last
};
2344 std::vector
<ClassGraph
> ClassGraph::bases() const {
2345 assertx(!table().locking
);
2347 assertx(!isMissing());
2349 always_assert(ensure());
2351 std::vector
<ClassGraph
> out
;
2352 auto current
= this_
;
2354 out
.emplace_back(ClassGraph
{ current
});
2355 auto const& parents
= current
->parents
;
2357 // There should be at most one base on the parent list. Verify
2358 // this in debug builds.
2359 for (auto const p
: parents
) {
2368 std::reverse(begin(out
), end(out
));
2372 std::vector
<ClassGraph
> ClassGraph::interfaces() const {
2373 assertx(!table().locking
);
2375 assertx(!isMissing());
2377 always_assert(ensure());
2379 std::vector
<ClassGraph
> out
;
2383 if (p
.isInterface()) out
.emplace_back(ClassGraph
{ &p
});
2384 return Action::Continue
;
2387 std::sort(begin(out
), end(out
));
2391 template <typename F
>
2392 void ClassGraph::walkParents(const F
& f
) const {
2393 assertx(!table().locking
);
2395 assertx(!isMissing());
2397 always_assert(ensure());
2402 return !f(ClassGraph
{ &p
})
2409 std::vector
<ClassGraph
> ClassGraph::children() const {
2410 assertx(!table().locking
);
2412 assertx(!isMissing());
2413 assertx(hasCompleteChildren());
2415 always_assert(ensureWithChildren());
2417 std::vector
<ClassGraph
> out
;
2418 // If this_ is a trait, then forEachChild won't walk the list. Use
2419 // forEachChildImpl with the right params to prevent this.
2420 TLNodeIdxSet visited
;
2424 out
.emplace_back(ClassGraph
{ &c
});
2425 return Action::Continue
;
2430 std::sort(begin(out
), end(out
));
2434 std::vector
<ClassGraph
> ClassGraph::directParents(Flags flags
) const {
2435 assertx(!table().locking
);
2437 assertx(!isMissing());
2439 always_assert(ensure());
2441 std::vector
<ClassGraph
> out
;
2442 out
.reserve(this_
->parents
.size());
2443 for (auto const p
: this_
->parents
) {
2444 if (!(p
->flags() & flags
)) continue;
2445 out
.emplace_back(ClassGraph
{ p
});
2450 std::vector
<ClassGraph
> ClassGraph::directParents() const {
2451 assertx(!table().locking
);
2453 assertx(!isMissing());
2455 always_assert(ensure());
2457 std::vector
<ClassGraph
> out
;
2458 out
.reserve(this_
->parents
.size());
2459 for (auto const p
: this_
->parents
) out
.emplace_back(ClassGraph
{ p
});
2463 bool ClassGraph::isChildOf(ClassGraph o
) const {
2464 assertx(!table().locking
);
2467 assertx(!isMissing());
2468 assertx(!o
.isMissing());
2469 always_assert(ensure());
2470 if (this_
== o
.this_
) return true;
2471 // Nothing is a child of a trait except itself and we know they're
2473 if (o
.this_
->isTrait()) return false;
2474 return findParent(*this_
, *o
.this_
);
2477 std::vector
<ClassGraph
> ClassGraph::candidateRegOnlyEquivs() const {
2478 assertx(!table().locking
);
2480 assertx(!this_
->isMissing());
2482 if (this_
->isRegular() || this_
->isConservative()) return {};
2483 assertx(this_
->hasCompleteChildren());
2484 auto const nonParents
= calcSubclassOfSplit(*this_
).first
;
2485 if (nonParents
.empty()) return {};
2486 if (nonParents
.size() == 1) return { ClassGraph
{ *nonParents
.begin() } };
2488 auto heads
= nonParents
;
2490 // Remove any nodes which are reachable from another node. Such
2491 // nodes are redundant.
2493 TLNodeIdxSet visited
;
2494 for (auto const n
: nonParents
) {
2495 if (!heads
.count(n
)) continue;
2499 if (&p
== n
) return Action::Continue
;
2500 if (!nonParents
.count(&p
)) return Action::Continue
;
2501 if (!heads
.count(&p
)) return Action::Skip
;
2503 return Action::Continue
;
2511 // Remove any nodes which have a (regular) child which does not have
2512 // this_ a parent. Such a node can't be an equivalent because it
2513 // contains more regular nodes than this_. Note that we might not
2514 // have complete children information for all nodes, so this check
2515 // is conservative. We only remove nodes which are definitely not
2520 auto const action
= forEachChild(
2523 if (!c
.isRegular()) return Action::Continue
;
2524 if (!findParent(c
, *this_
)) return Action::Stop
;
2525 return Action::Skip
;
2528 return action
== Action::Stop
;
2532 std::vector
<ClassGraph
> out
;
2533 out
.reserve(heads
.size());
2534 for (auto const n
: heads
) out
.emplace_back(ClassGraph
{ n
});
2535 std::sort(begin(out
), end(out
));
2539 void ClassGraph::setRegOnlyEquivs() const {
2540 assertx(!table().locking
);
2542 assertx(!this_
->isMissing());
2543 if (this_
->isRegular() || this_
->isConservative()) return;
2544 assertx(this_
->hasCompleteChildren());
2545 auto const equiv
= calcRegOnlyEquiv(*this_
, calcSubclassOf(*this_
));
2546 if (!equiv
|| equiv
== this_
|| findParent(*equiv
, *this_
)) return;
2547 auto const [it
, s
] = table().regOnlyEquivs
.emplace(this_
, equiv
);
2548 always_assert(s
|| it
->second
== equiv
);
2551 const ClassGraph::Node::NonRegularInfo
&
2552 ClassGraph::Node::nonRegularInfo() {
2553 assertx(!table().locking
);
2554 assertx(!isMissing());
2555 assertx(!isRegular());
2556 auto const allowed
= ClassGraph
{this}.ensureWithChildren();
2557 auto const& i
= nonRegInfo
.get(
2559 auto info
= std::make_unique
<NonRegularInfo
>();
2560 info
->subclassOf
= calcSubclassOf(*this);
2561 info
->regOnlyEquiv
= calcRegOnlyEquiv(*this, info
->subclassOf
);
2562 return info
.release();
2565 if (allowed
|| !hasCompleteChildren() || isTrait()) return i
;
2566 // We're not allowed to use this Node's information, so instead use
2567 // more pessimistic info.
2568 return nonRegInfoDisallow
.get(
2570 auto info
= std::make_unique
<NonRegularInfo
>();
2571 info
->subclassOf
= allParents(*this);
2572 info
->regOnlyEquiv
= calcRegOnlyEquiv(*this, info
->subclassOf
);
2573 return info
.release();
2578 bool ClassGraph::exactSubtypeOfExact(ClassGraph o
,
2580 bool nonRegR
) const {
2581 assertx(!table().locking
);
2585 auto const missing1
= !ensure() || this_
->isMissing();
2586 auto const missing2
= !o
.ensure() || o
.this_
->isMissing();
2588 // Two exact classes are only subtypes of another if they're the
2589 // same. One additional complication is if the class isn't regular
2590 // and we're not considering non-regular classes. In that case, the
2591 // class is actually Bottom, and we need to apply the rules of
2592 // subtyping to Bottom (Bottom is a subtype of everything, but
2593 // nothing is a subtype of it).
2595 // Missing classes are only definitely a subtype if it's the same
2596 // node and the lhs can become bottom or the rhs cannot.
2597 return (this_
== o
.this_
) && (!nonRegL
|| nonRegR
);
2598 } else if (!nonRegL
&& !this_
->isRegular()) {
2599 // Lhs is a bottom, so a subtype of everything.
2601 } else if (missing2
|| (!nonRegR
&& !o
.this_
->isRegular())) {
2602 // Lhs is not a bottom, check if the rhs is.
2605 // Neither is bottom, check for equality.
2606 return this_
== o
.this_
;
2610 bool ClassGraph::exactSubtypeOf(ClassGraph o
,
2612 bool nonRegR
) const {
2613 assertx(!table().locking
);
2617 auto const missing1
= !ensure() || this_
->isMissing();
2618 auto const missing2
= !o
.ensure() || o
.this_
->isMissing();
2620 // If we want to exclude non-regular classes on either side, and the
2621 // lhs is not regular, there's no subtype relation. If nonRegL is
2622 // false, then lhs is just a bottom (and bottom is a subtype of
2623 // everything), and if nonRegularR is false, then the rhs does not
2624 // contain any non-regular classes, so lhs is guaranteed to not be
2627 // If the lhs side is missing, it's identical to
2628 // exactSubtypeOfExact.
2629 return (this_
== o
.this_
) && (!nonRegL
|| nonRegR
);
2630 } else if ((!nonRegL
|| !nonRegR
) && !this_
->isRegular()) {
2632 } else if (this_
== o
.this_
) {
2634 } else if (missing2
|| o
.this_
->isTrait()) {
2635 // The lhs is not missing, so cannot be a subtype of a missing
2636 // class. Nothing is a subtype of a trait.
2639 // Otherwise try to find the rhs node among the parents of the lhs
2641 return findParent(*this_
, *o
.this_
);
2645 bool ClassGraph::subSubtypeOf(ClassGraph o
, bool nonRegL
, bool nonRegR
) const {
2646 assertx(!table().locking
);
2650 auto const missing1
= !ensure() || this_
->isMissing();
2651 auto const missing2
= !o
.ensure() || o
.this_
->isMissing();
2653 if (nonRegL
&& !nonRegR
) {
2654 if (missing1
|| !this_
->isRegular()) return false;
2655 if (this_
->hasNonRegularSubclass()) return false;
2658 // If this_ must be part of the lhs, it's equivalent to
2659 // exactSubtypeOf. Otherwise if exactSubtypeOf returns true for the
2660 // conservative case, then it must always be true, so we don't need
2661 // to look at children.
2662 if (nonRegL
|| missing1
|| this_
->isRegular()) {
2663 return exactSubtypeOf(o
, nonRegL
, nonRegR
);
2665 if (exactSubtypeOf(o
, true, true)) return true;
2667 // this_ is not regular and will not be part of the lhs. We need to
2668 // look at the regular children of this_ and check whether they're
2669 // all subtypes of the rhs.
2671 // Traits have no children for the purposes of this test.
2672 if (this_
->isTrait() || missing2
|| o
.this_
->isTrait()) {
2675 return this_
->nonRegularInfo().subclassOf
.count(o
.this_
);
2678 bool ClassGraph::exactCouldBeExact(ClassGraph o
,
2680 bool nonRegR
) const {
2681 assertx(!table().locking
);
2685 auto const missing
= !ensure() || this_
->isMissing();
2687 // Two exact classes can only be each other if they're the same
2688 // class. The only complication is if the class isn't regular and
2689 // we're not considering non-regular classes. In that case, the
2690 // class is actually Bottom, a Bottom can never could-be anything
2691 // (not even itself).
2692 if (this_
!= o
.this_
) return false;
2693 if (missing
|| this_
->isRegular()) return true;
2694 return nonRegL
&& nonRegR
;
2697 bool ClassGraph::exactCouldBe(ClassGraph o
, bool nonRegL
, bool nonRegR
) const {
2698 assertx(!table().locking
);
2702 auto const missing1
= !ensure() || this_
->isMissing();
2703 auto const missing2
= !o
.ensure() || o
.this_
->isMissing();
2705 // exactCouldBe is almost identical to exactSubtypeOf, except the
2706 // case of the lhs being bottom is treated differently (bottom in
2707 // exactSubtypeOf implies true, but here it implies false).
2709 if (missing2
) return true;
2710 if ((!nonRegL
|| !nonRegR
) &&
2711 !o
.this_
->isRegular() &&
2712 !o
.this_
->hasRegularSubclass()) {
2715 return !o
.this_
->hasCompleteChildren();
2716 } else if ((!nonRegL
|| !nonRegR
) && !this_
->isRegular()) {
2718 } else if (this_
== o
.this_
) {
2720 } else if (missing2
|| o
.this_
->isTrait()) {
2723 return findParent(*this_
, *o
.this_
);
2727 bool ClassGraph::subCouldBe(ClassGraph o
, bool nonRegL
, bool nonRegR
) const {
2728 assertx(!table().locking
);
2732 auto const missing1
= !ensure() || this_
->isMissing();
2733 auto const missing2
= !o
.ensure() || o
.this_
->isMissing();
2735 auto const regOnly
= !nonRegL
|| !nonRegR
;
2737 if (exactCouldBe(o
, nonRegL
, nonRegR
) ||
2738 o
.exactCouldBe(*this, nonRegR
, nonRegL
)) {
2740 } else if (missing1
&& missing2
) {
2742 } else if ((!missing1
&& this_
->isTrait()) ||
2743 (!missing2
&& o
.this_
->isTrait())) {
2745 } else if (regOnly
) {
2746 if (!missing1
&& !this_
->isRegular() && !this_
->hasRegularSubclass()) {
2749 if (!missing2
&& !o
.this_
->isRegular() && !o
.this_
->hasRegularSubclass()) {
2755 auto right
= o
.this_
;
2756 if (betterNode
<true>(right
, left
)) std::swap(left
, right
);
2758 if (!ClassGraph
{left
}.ensureWithChildren() ||
2759 !left
->hasCompleteChildren()) {
2762 if (right
->isMissing()) return false;
2764 TLNodeIdxSet visited
;
2765 auto const action
= forEachChild(
2768 if (regOnly
&& !c
.isRegular()) return Action::Continue
;
2769 return findParent(c
, *right
, *visited
)
2770 ? Action::Stop
: Action::Continue
;
2773 return action
== Action::Stop
;
2776 // Calculate all the classes which this class could be a subclass
2777 // of. This is only necessary if the class is non-regular (for regular
2778 // classes the subtype check is trivial). This not only speeds up
2779 // subtypeOf checks, but it is used when calculating reg-only
2780 // equivalent classes. The results are split into the nodes which
2781 // aren't parents of this class, and the ones which are not.
2782 std::pair
<ClassGraph::NodeSet
, ClassGraph::NodeSet
>
2783 ClassGraph::calcSubclassOfSplit(Node
& n
) {
2784 assertx(!table().locking
);
2785 assertx(!n
.isMissing());
2786 assertx(!n
.isRegular());
2788 // Traits cannot be a subclass of anything but itself, and if we
2789 // don't know all of the children, we have to be pessimistic and
2790 // report only the parents.
2791 if (n
.isTrait()) return std::make_pair(NodeSet
{}, NodeSet
{});
2792 if (!n
.hasCompleteChildren()) {
2793 return std::make_pair(NodeSet
{}, allParents(n
));
2796 // Find the first regular child of this non-regular class.
2797 Node
* first
= nullptr;
2801 if (!c
.isRegular()) return Action::Continue
;
2803 // n has complete children, so all children must as well.
2804 assertx(c
.hasCompleteChildren());
2806 return Action::Stop
;
2810 if (!first
) return std::make_pair(NodeSet
{}, NodeSet
{});
2812 auto parents
= allParents(n
);
2814 // Given the regular class we found, gather all of it's
2815 // children. This is the initial set of candidates.
2820 // Ignore parents since we already know about this.
2821 if (parents
.count(&p
)) return Action::Skip
;
2822 candidates
.emplace_back(&p
);
2823 return Action::Continue
;
2826 if (candidates
.empty()) return std::make_pair(NodeSet
{}, std::move(parents
));
2828 // Then use ParentTracker to remove any nodes which are not parents
2829 // of the other children.
2830 auto const run
= [&] (auto tracker
) {
2834 if (!c
.isRegular()) return Action::Continue
;
2835 return tracker(c
) ? Action::Skip
: Action::Stop
;
2838 auto nodes
= tracker
.nodes();
2839 return std::make_pair(std::move(nodes
), std::move(parents
));
2842 if (candidates
.size() <= SmallBitset::kMaxSize
) {
2843 return run(ParentTracker
<SmallBitset
>{candidates
, &parents
});
2845 return run(ParentTracker
<LargeBitset
>{candidates
, &parents
});
2849 ClassGraph::NodeSet
ClassGraph::calcSubclassOf(Node
& n
) {
2850 assertx(!table().locking
);
2851 assertx(!n
.isMissing());
2852 assertx(!n
.isRegular());
2853 auto [nonParents
, parents
] = calcSubclassOfSplit(n
);
2854 nonParents
.insert(begin(parents
), end(parents
));
2858 // Calculate the "best" Node which is equivalent to this Node when
2859 // considering only regular children. We canonicalize this Node to the
2860 // equivalent Node where appropriate. Nullptr is returned if the
2861 // "equivalent" Node is actually Bottom (because there are no regular
2862 // children). subclassOf gives the set of "candidate" Nodes.
2863 ClassGraph::Node
* ClassGraph::calcRegOnlyEquiv(Node
& base
,
2864 const NodeSet
& subclassOf
) {
2865 assertx(!table().locking
);
2866 assertx(!base
.isMissing());
2867 assertx(!base
.isRegular());
2869 if (subclassOf
.empty()) return nullptr;
2870 if (base
.isTrait()) return nullptr;
2871 if (base
.hasCompleteChildren() || base
.isConservative()) {
2872 if (!base
.hasRegularSubclass()) return nullptr;
2874 if (subclassOf
.size() == 1) {
2875 assertx(subclassOf
.count(&base
));
2879 // If we recorded an equivalent when deserializing, just use that.
2880 if (auto const e
= folly::get_default(table().regOnlyEquivs
, &base
)) {
2881 assertx(e
->hasCompleteChildren());
2884 // Otherwise calculate it.
2886 auto heads
= subclassOf
;
2888 // Remove any nodes which are reachable from another node. Such
2889 // nodes are redundant.
2891 TLNodeIdxSet visited
;
2892 for (auto const n
: subclassOf
) {
2893 if (!heads
.count(n
)) continue;
2897 if (&p
== n
) return Action::Continue
;
2898 if (!subclassOf
.count(&p
)) return Action::Continue
;
2899 if (!heads
.count(&p
)) return Action::Skip
;
2901 return Action::Continue
;
2908 if (heads
.size() == 1) return *heads
.begin();
2910 assertx(base
.hasCompleteChildren());
2912 // Find the best node among the remaining candidates.
2913 auto const run
= [&] (auto tracker
) {
2914 // A node is only a valid candidate (and thus sufficient) if all
2915 // of it's children are subclasses of the "base" Node.
2916 auto const isSufficient
= [&] (Node
* n
) {
2917 if (n
== &base
) return true;
2918 if (findParent(*n
, base
)) return true;
2919 if (!n
->hasCompleteChildren()) return false;
2920 auto const action
= forEachChild(
2923 if (!c
.isRegular()) return Action::Continue
;
2924 return tracker
.all(c
) ? Action::Skip
: Action::Stop
;
2927 return action
!= Action::Stop
;
2930 Node
* best
= nullptr;
2931 for (auto const n
: heads
) {
2932 if (!isSufficient(n
)) continue;
2933 if (!best
|| betterNode
<false>(n
, best
)) best
= n
;
2939 if (heads
.size() <= SmallBitset::kMaxSize
) {
2940 return run(ParentTracker
<SmallBitset
>{heads
});
2942 return run(ParentTracker
<LargeBitset
>{heads
});
2946 // Somewhat arbitrarily ranks two Nodes in a consistent manner.
2947 template <bool Allow
>
2948 bool ClassGraph::betterNode(Node
* n1
, Node
* n2
) {
2949 if (n1
== n2
) return false;
2951 // Non-missing nodes are always better. Two missing nodes are ranked
2952 // according to name. If Allow is true and we're not allowed to use
2953 // the Node, treat it as equivalent to isMissing() being true.
2954 auto const missing1
=
2955 n1
->isMissing() || (Allow
&& !ClassGraph
{n1
}.allowed(false));
2956 auto const missing2
=
2957 n2
->isMissing() || (Allow
&& !ClassGraph
{n2
}.allowed(false));
2959 if (!missing2
) return false;
2960 return string_data_lt_type
{}(n1
->name
, n2
->name
);
2961 } else if (missing2
) {
2965 auto const complete1
=
2966 n1
->hasCompleteChildren() && (!Allow
|| ClassGraph
{n1
}.allowed(true));
2967 auto const complete2
=
2968 n2
->hasCompleteChildren() && (!Allow
|| ClassGraph
{n2
}.allowed(true));
2970 // Nodes with complete children are better than those that don't.
2972 if (complete2
) return false;
2973 return string_data_lt_type
{}(n1
->name
, n2
->name
);
2974 } else if (!complete2
) {
2978 // Choose the one with the least (immediate) children. Calculating
2979 // the full subclass list would be better, but more
2980 // expensive. Traits are always considered to have no children.
2981 auto const s1
= n1
->isTrait() ? 0 : n1
->children
.size();
2982 auto const s2
= n2
->isTrait() ? 0 : n2
->children
.size();
2983 if (s1
!= s2
) return s1
< s2
;
2985 // Otherwise rank them acccording to what flags they have and then
2987 auto const weight
= [] (const Node
* n
) {
2988 auto const f
= n
->flags();
2989 if (f
& FlagAbstract
) return 1;
2990 if (f
& FlagInterface
) return 2;
2991 if (f
& FlagTrait
) return 3;
2992 if (f
& FlagEnum
) return 4;
2995 auto const w1
= weight(n1
);
2996 auto const w2
= weight(n2
);
2997 if (w1
!= w2
) return w1
< w2
;
2998 return string_data_lt_type
{}(n1
->name
, n2
->name
);
3001 // Union together two sets of intersected classes.
3002 ClassGraph::NodeVec
ClassGraph::combine(const NodeVec
& lhs
,
3008 assertx(!table().locking
);
3009 assertx(!lhs
.empty());
3010 assertx(!rhs
.empty());
3011 assertx(IMPLIES(!isSubL
, lhs
.size() == 1));
3012 assertx(IMPLIES(!isSubR
, rhs
.size() == 1));
3014 // Combine two sets, keeping only the nodes that are in both
3015 // sets. An empty set is equivalent to Top (not Bottom), thus
3016 // everything in the other set goes in.
3018 auto const combine
= [&] (const NodeSet
& s1
, const NodeSet
& s2
) {
3020 always_assert(!s2
.empty());
3021 combined
.insert(begin(s2
), end(s2
));
3022 } else if (s2
.empty()) {
3023 combined
.insert(begin(s1
), end(s1
));
3024 } else if (s1
.size() < s2
.size()) {
3025 for (auto const e
: s1
) {
3026 if (s2
.count(e
)) combined
.emplace(e
);
3029 for (auto const e
: s2
) {
3030 if (s1
.count(e
)) combined
.emplace(e
);
3036 * (A&B) | (C&D) = (A|C) & (A|D) & (B|C) & (B|D)
3038 * (this generalizes to arbitrary size lists). So to union together
3039 * two intersection sets, we need to calculate the union of
3040 * individual classes pair-wise, then intersect them together.
3042 auto const process
= [&] (Node
* l
, Node
* r
) {
3043 auto allowedL
= ClassGraph
{l
}.ensure();
3044 auto allowedR
= ClassGraph
{r
}.ensure();
3046 // Order the l and r to cut down on the number of cases to deal
3048 auto const flip
= [&] {
3049 if (!allowedL
|| l
->isMissing()) return false;
3050 if (!allowedR
|| r
->isMissing()) return true;
3051 if (nonRegL
|| l
->isRegular()) return false;
3052 if (nonRegR
|| r
->isRegular()) return true;
3053 if (isSubL
) return false;
3058 std::swap(allowedL
, allowedR
);
3060 auto const flipSubL
= flip
? isSubR
: isSubL
;
3061 auto const flipSubR
= flip
? isSubL
: isSubR
;
3062 auto const flipNonRegL
= flip
? nonRegR
: nonRegL
;
3063 auto const flipNonRegR
= flip
? nonRegL
: nonRegR
;
3065 // This logic handles the unioning of two classes. If two classes
3066 // don't have a common parent, their union if Top, which is
3067 // dropped from the intersection list. For regular classes, we can
3068 // just get the parent list. For non-regular classes, we need to
3070 if (!allowedL
|| l
->isMissing()) {
3071 if (l
== r
) combined
.emplace(l
);
3072 } else if (flipNonRegL
|| l
->isRegular()) {
3073 if (flipNonRegR
|| r
->isRegular()) {
3074 combine(allParents(*l
), allParents(*r
));
3075 } else if (flipSubR
) {
3076 combine(allParents(*l
), r
->nonRegularInfo().subclassOf
);
3078 combine(allParents(*l
), {});
3080 } else if (flipSubL
) {
3082 combine(l
->nonRegularInfo().subclassOf
,
3083 r
->nonRegularInfo().subclassOf
);
3085 combine(l
->nonRegularInfo().subclassOf
, {});
3088 always_assert(false);
3092 for (auto const l
: lhs
) {
3093 for (auto const r
: rhs
) process(l
, r
);
3096 // The resultant list is not in canonical form, so canonicalize it.
3097 return canonicalize(combined
, nonRegL
|| nonRegR
);
3100 // Intersect two sets of intersected classes together. This seems like
3101 // you would just combine the lists, but the intersection of the two
3102 // might have more specific classes in common.
3103 ClassGraph::NodeVec
ClassGraph::intersect(const NodeVec
& lhs
,
3108 assertx(!table().locking
);
3109 assertx(!lhs
.empty());
3110 assertx(!rhs
.empty());
3112 // Combine the two lists together.
3114 combined
.reserve(lhs
.size() + rhs
.size());
3116 lhs
.begin(), lhs
.end(),
3117 rhs
.begin(), rhs
.end(),
3118 std::back_inserter(combined
),
3122 // Build the "heads". These are the nodes which could potentially be
3123 // part of the new intersection list, but don't need to be
3124 // calculated below. We leave them out to reduce the amount of work.
3126 for (auto const n
: combined
) {
3127 auto const allowed
= ClassGraph
{n
}.ensure();
3128 if (!allowed
|| n
->isMissing()) {
3130 } else if ((nonRegL
&& nonRegR
) || n
->isRegular()) {
3131 auto const p
= allParents(*n
);
3132 heads
.insert(p
.begin(), p
.end());
3133 } else if (!n
->hasRegularSubclass()) {
3137 auto const& s
= n
->nonRegularInfo().subclassOf
;
3138 heads
.insert(s
.begin(), s
.end());
3142 // Then enumerate over all of the classes in the combined set and
3143 // track which parents they *all* have in common.
3145 Optional
<ParentTracker
<SmallBitset
>> small
;
3146 Optional
<ParentTracker
<LargeBitset
>> large
;
3149 enumerateIsectMembers(
3153 if (n
.isMissing() || !n
.hasCompleteChildren()) {
3154 if (nonRegL
&& nonRegR
) nonRegOut
= true;
3155 } else if (!n
.isRegular()) {
3156 if (!nonRegL
|| !nonRegR
) {
3157 assertx(!ClassGraph
{&n
}.allowed(true));
3161 } else if (!nonRegOut
&& nonRegL
&& nonRegR
) {
3162 if (n
.hasNonRegularSubclass()) nonRegOut
= true;
3165 // If we already created a tracker, use it.
3171 // Otherwise this is the first time. Find the initial set of
3172 // candidates (all of the parents of this node minus the
3173 // heads) and set up the tracker.
3174 auto common
= n
.isMissing() ? NodeSet
{&n
} : allParents(n
);
3175 folly::erase_if(common
, [&] (Node
* c
) { return heads
.count(c
); });
3176 if (common
.size() <= SmallBitset::kMaxSize
) {
3177 small
.emplace(common
, &heads
);
3179 large
.emplace(common
, &heads
);
3181 return !common
.empty();
3185 assertx(IMPLIES(!nonRegL
|| !nonRegR
, !nonRegOut
));
3187 // At this point the tracker only contains all of the parents which
3188 // are in common. These nodes, plus the heads, only need to be
3191 auto s
= small
->nodes();
3192 s
.insert(heads
.begin(), heads
.end());
3193 return canonicalize(s
, nonRegOut
);
3195 auto s
= large
->nodes();
3196 s
.insert(heads
.begin(), heads
.end());
3197 return canonicalize(s
, nonRegOut
);
3203 // Given a set of intersected nodes, return an equivalent set with all
3204 // of the non-regular classes removed.
3205 ClassGraph::NodeVec
ClassGraph::removeNonReg(const NodeVec
& v
) {
3206 assertx(!table().locking
);
3207 assertx(!v
.empty());
3209 // We can just treat this as an intersection:
3212 for (auto const n
: v
) {
3213 auto const allowed
= ClassGraph
{n
}.ensure();
3214 if (!allowed
|| n
->isMissing() || n
->isRegular()) {
3215 lhs
.emplace_back(n
);
3216 } else if (auto const e
= n
->nonRegularInfo().regOnlyEquiv
) {
3217 lhs
.emplace_back(e
);
3222 if (lhs
.size() <= 1) return lhs
;
3224 std::sort(lhs
.begin(), lhs
.end(), betterNode
<false>);
3225 rhs
.emplace_back(lhs
.back());
3228 auto nonRegOut
= false;
3229 SCOPE_EXIT
{ assertx(!nonRegOut
); };
3230 return intersect(lhs
, rhs
, false, false, nonRegOut
);
3233 // Given two lists of intersected classes, return true if the
3234 // intersection of the two lists might be non-empty.
3235 bool ClassGraph::couldBeIsect(const NodeVec
& lhs
,
3239 assertx(!table().locking
);
3240 assertx(!lhs
.empty());
3241 assertx(!rhs
.empty());
3244 combined
.reserve(lhs
.size() + rhs
.size());
3246 lhs
.begin(), lhs
.end(),
3247 rhs
.begin(), rhs
.end(),
3248 std::back_inserter(combined
),
3252 auto couldBe
= false;
3253 enumerateIsectMembers(
3264 // Call the provided callback for each Node in the given intersection
3265 // list. If "all" is true, all Nodes are provided. Otherwise, only the
3266 // "top most" children are provided (the children which meet the
3267 // criteria and don't have any parents which do).
3268 template <typename F
>
3269 void ClassGraph::enumerateIsectMembers(const NodeVec
& nodes
,
3273 assertx(!table().locking
);
3275 if (nodes
.empty()) return;
3277 if (table().index
) {
3278 for (auto const n
: nodes
) (void)ClassGraph
{n
}.ensure();
3281 // Find the "best" node to start with.
3282 auto head
= *std::min_element(nodes
.begin(), nodes
.end(), betterNode
<true>);
3283 if (!ClassGraph
{head
}.ensureWithChildren() ||
3284 head
->isMissing() ||
3285 !head
->hasCompleteChildren()) {
3286 // If the best node is missing or doesn't have complete children,
3287 // they all don't, so just supply the input list.
3288 for (auto const n
: nodes
) {
3291 !n
->hasCompleteChildren() ||
3292 !ClassGraph
{n
}.allowed(true)
3299 // Otherwise report Nodes which are children of all of the Nodes.
3300 auto const run
= [&] (auto tracker
) {
3304 if (nonReg
|| c
.isRegular()) {
3305 if (tracker
.all(c
)) {
3306 if (!f(c
)) return Action::Stop
;
3307 return all
? Action::Continue
: Action::Skip
;
3310 return Action::Continue
;
3315 if (nodes
.size() <= SmallBitset::kMaxSize
) {
3316 return run(ParentTracker
<SmallBitset
>{nodes
});
3318 return run(ParentTracker
<LargeBitset
>{nodes
});
3322 // "Canonicalize" a set of intersected classes by removing redundant
3323 // Nodes and putting them in a deterministic order.
3324 ClassGraph::NodeVec
ClassGraph::canonicalize(const NodeSet
& nodes
,
3327 if (nodes
.size() <= 1) {
3329 out
.insert(begin(nodes
), end(nodes
));
3333 // Remove redundant nodes. A node is redundant if it is reachable
3334 // via another node. In that case, this node is implied by the other
3335 // and can be removed.
3338 TLNodeIdxSet visited
;
3339 for (auto const n
: nodes
) {
3340 if (!heads
.count(n
)) continue;
3341 auto const allowed
= ClassGraph
{n
}.ensure();
3342 if (!allowed
|| n
->isMissing()) continue;
3346 if (&p
== n
) return Action::Continue
;
3347 if (!nodes
.count(&p
)) return Action::Continue
;
3348 if (!heads
.count(&p
)) return Action::Skip
;
3350 return Action::Continue
;
3358 // A node can be redundant with another even they aren't reachable
3359 // via each other. This can only happen if we're considering only
3360 // regular nodes. If a class is a super type of another, it's
3361 // redundant, as the other class implies this class. If the classes
3362 // are both super types of each other, they're equivalent, and we
3363 // keep the "best" one.
3365 for (auto const n1
: heads
) {
3366 auto const isSuperType
= [&] {
3367 for (auto const n2
: heads
) {
3368 if (n1
== n2
) continue;
3369 if (!ClassGraph
{n2
}.subSubtypeOf(ClassGraph
{n1
}, false, false)) {
3372 if (!ClassGraph
{n1
}.subSubtypeOf(ClassGraph
{n2
}, false, false)) {
3375 if (betterNode
<true>(n2
, n1
)) return true;
3379 if (!isSuperType
) out
.emplace_back(n1
);
3382 out
.insert(begin(heads
), end(heads
));
3385 // Finally sort them according to how good they are.
3386 std::sort(out
.begin(), out
.end(), betterNode
<false>);
3390 template <typename F
>
3391 ClassGraph::Action
ClassGraph::forEachParent(Node
& n
, const F
& f
, NodeIdxSet
& v
) {
3392 return forEachParentImpl(n
, f
, &v
, true);
3395 template <typename F
>
3396 ClassGraph::Action
ClassGraph::forEachParent(Node
& n
, const F
& f
) {
3398 return forEachParentImpl(n
, f
, &*v
, true);
3401 template <typename F
>
3402 ClassGraph::Action
ClassGraph::forEachParentImpl(Node
& n
,
3406 assertx(!n
.isMissing());
3407 if (v
&& !v
->add(n
)) return Action::Skip
;
3408 if (start
|| !n
.isTrait()) {
3409 auto const action
= f(n
);
3410 if (action
!= Action::Continue
) return action
;
3412 for (auto const parent
: n
.parents
) {
3413 if (forEachParentImpl(*parent
, f
, v
, false) == Action::Stop
) {
3414 return Action::Stop
;
3417 return Action::Continue
;
3420 template <typename F
, typename F2
, typename T
>
3421 T
ClassGraph::foldParentsImpl(Node
& n
,
3426 assertx(!n
.isMissing());
3427 if (auto const t
= folly::get_ptr(m
, &n
)) return *t
;
3428 T t
= (start
|| !n
.isTrait()) ? f(n
) : f2();
3429 for (auto const parent
: n
.parents
) {
3431 t
|= foldParentsImpl(*parent
, f
, f2
, m
, false);
3433 m
.insert_or_assign(&n
, t
);
3437 bool ClassGraph::findParent(Node
& n1
, Node
& n2
) {
3438 TLNodeIdxSet visited
;
3439 return findParent(n1
, n2
, *visited
);
3442 bool ClassGraph::findParent(Node
& start
, Node
& target
, NodeIdxSet
& visited
) {
3443 assertx(!start
.isMissing());
3445 static thread_local hphp_fast_map
<std::pair
<Node
*, Node
*>, bool> cache
;
3446 if (auto const r
= folly::get_ptr(cache
, std::make_pair(&start
, &target
))) {
3450 auto const action
= forEachParent(
3452 [&] (Node
& p
) { return (&p
== &target
) ? Action::Stop
: Action::Continue
; },
3457 std::make_pair(&start
, &target
),
3458 action
== Action::Stop
3460 return action
== Action::Stop
;
3463 ClassGraph::NodeSet
ClassGraph::allParents(Node
& n
) {
3464 assertx(!n
.isMissing());
3470 return Action::Continue
;
3476 template <typename F
>
3477 ClassGraph::Action
ClassGraph::forEachChild(Node
& n
, const F
& f
, NodeIdxSet
& v
) {
3478 return forEachChildImpl(n
, f
, &v
, true);
3481 template <typename F
>
3482 ClassGraph::Action
ClassGraph::forEachChild(Node
& n
, const F
& f
) {
3484 return forEachChildImpl(n
, f
, &*v
, true);
3487 template <typename F
>
3489 ClassGraph::forEachChildImpl(Node
& n
, const F
& f
, NodeIdxSet
* v
, bool start
) {
3490 assertx(!n
.isMissing());
3491 if (v
&& !v
->add(n
)) return Action::Skip
;
3492 auto const action
= f(n
);
3493 if (action
!= Action::Continue
) return action
;
3494 if (start
&& n
.isTrait()) return action
;
3495 for (auto const child
: n
.children
) {
3496 if (forEachChildImpl(*child
, f
, v
, false) == Action::Stop
) {
3497 return Action::Stop
;
3500 return Action::Continue
;
3503 ClassGraph
ClassGraph::create(const php::Class
& cls
) {
3504 assertx(!table().locking
);
3506 auto const& [n
, emplaced
] = table().nodes
.try_emplace(cls
.name
);
3509 "Attempting to create already existing ClassGraph node '{}'",
3512 assertx(!n
->second
.name
);
3513 assertx(n
->second
.flags() == FlagNone
);
3514 assertx(!n
->second
.cinfo());
3515 assertx(!n
->second
.cinfo2());
3516 n
->second
.name
= cls
.name
;
3517 n
->second
.idx
= table().nodes
.size();
3520 if (cls
.attrs
& AttrInterface
) f
= Flags(f
| FlagInterface
);
3521 if (cls
.attrs
& AttrTrait
) f
= Flags(f
| FlagTrait
);
3522 if (cls
.attrs
& AttrAbstract
) f
= Flags(f
| FlagAbstract
);
3523 if (cls
.attrs
& (AttrEnum
| AttrEnumClass
)) f
= Flags(f
| FlagEnum
);
3524 n
->second
.setFlags(f
);
3526 return ClassGraph
{ &n
->second
};
3529 ClassGraph
ClassGraph::get(SString name
) {
3530 assertx(!table().locking
);
3531 auto n
= folly::get_ptr(table().nodes
, name
);
3534 "Attempting to retrieve missing ClassGraph node '{}'",
3537 assertx(n
->name
->tsame(name
));
3538 return ClassGraph
{ n
};
3541 ClassGraph
ClassGraph::getOrCreate(SString name
) {
3542 assertx(!table().locking
);
3544 auto const& [n
, emplaced
] = table().nodes
.try_emplace(name
);
3546 assertx(!n
->second
.name
);
3547 assertx(n
->second
.flags() == FlagNone
);
3548 assertx(!n
->second
.cinfo());
3549 assertx(!n
->second
.cinfo2());
3550 n
->second
.name
= name
;
3551 n
->second
.idx
= table().nodes
.size();
3552 n
->second
.setFlags(FlagMissing
);
3554 assertx(n
->second
.name
->tsame(name
));
3556 return ClassGraph
{ &n
->second
};
3559 ClassGraph
ClassGraph::getMissing(SString name
) {
3560 assertx(!table().locking
);
3562 auto const& [n
, emplaced
] = table().nodes
.try_emplace(name
);
3564 assertx(!n
->second
.name
);
3565 assertx(n
->second
.flags() == FlagNone
);
3566 assertx(!n
->second
.cinfo());
3567 assertx(!n
->second
.cinfo2());
3568 n
->second
.name
= name
;
3569 n
->second
.idx
= table().nodes
.size();
3570 n
->second
.setFlags(FlagMissing
);
3572 assertx(n
->second
.name
->tsame(name
));
3573 assertx(n
->second
.flags() == FlagMissing
);
3575 return ClassGraph
{ &n
->second
};
3578 void ClassGraph::init() {
3579 always_assert(!g_table
);
3580 g_table
= std::make_unique
<Table
>();
3583 void ClassGraph::initConcurrent() {
3584 always_assert(!g_table
);
3585 g_table
= std::make_unique
<Table
>();
3586 g_table
->locking
.emplace();
3589 void ClassGraph::stopConcurrent() {
3590 always_assert(g_table
);
3591 g_table
->locking
.reset();
3594 void ClassGraph::destroy() {
3595 assertx(IMPLIES(g_table
, !g_table
->index
));
3599 ClassGraph::Table
& ClassGraph::table() {
3602 "Attempting to access ClassGraph node table when one isn't active!"
3607 void ClassGraph::setAnalysisIndex(AnalysisIndex::IndexData
& index
) {
3608 assertx(!table().locking
);
3609 assertx(!table().index
);
3610 table().index
= &index
;
3613 void ClassGraph::clearAnalysisIndex() {
3614 assertx(!table().locking
);
3615 assertx(table().index
);
3616 table().index
= nullptr;
3619 // Set the FlagComplete/FlagConservative and FlagRegSub/FlagNonRegSub
3620 // flags in this class and it's children. Return the
3621 // FlagRegSub/FlagNonRegSub flags from the children, along with the
3622 // count of the subclass list from that child. If std::nullopt is
3623 // given as the count, we've exceeded the limit we want to track.
3624 template <typename Impl
>
3625 std::pair
<ClassGraph::Flags
, Optional
<size_t>>
3626 ClassGraph::setCompleteImpl(const Impl
& impl
, Node
& n
) {
3627 assertx(!n
.isMissing());
3629 // Conservative nodes don't have children. However, they're
3630 // guaranteed to have their FlagRegSub and FlagNonRegSub flags set
3631 // properly, so we don't need to.
3632 if (n
.hasCompleteChildren() || n
.isConservative()) {
3634 if (n
.isRegular() || n
.hasRegularSubclass()) {
3635 f
= (Flags
)(f
| FlagRegSub
);
3637 if (!n
.isRegular() || n
.hasNonRegularSubclass()) {
3638 f
= (Flags
)(f
| FlagNonRegSub
);
3640 if (n
.hasCompleteChildren()) {
3641 // If we know all the children, the list should be in canonical
3657 return std::make_pair(
3659 n
.children
.empty() ? 1 : impl
.getCompleteSize(n
)
3662 return std::make_pair(f
, std::nullopt
);
3665 // Otherwise aggregate the flags and counts from the children.
3666 auto flags
= FlagNone
;
3667 Optional
<size_t> count
;
3670 if (!n
.children
.empty()) {
3674 auto const [f
, c
] = setCompleteImpl(impl
, child
);
3675 flags
= (Flags
)(flags
| f
);
3687 if (!count
|| *count
> options
.preciseSubclassLimit
) {
3688 // The child is conservative, or we've exceeded the subclass list
3689 // limit. Mark this node as being conservative.
3690 setConservative(impl
, n
, flags
& FlagRegSub
, flags
& FlagNonRegSub
);
3693 // Didn't have complete children, but now does. Update the flags.
3694 if (!n
.children
.empty()) impl
.setCompleteSize(n
, *count
);
3697 [&] { std::sort(begin(n
.children
), end(n
.children
), Node::Compare
{}); }
3699 impl
.updateFlags(n
, (Flags
)(flags
| FlagChildren
));
3702 if (n
.isRegular()) flags
= (Flags
)(flags
| FlagRegSub
);
3703 if (!n
.isRegular()) flags
= (Flags
)(flags
| FlagNonRegSub
);
3704 return std::make_pair(flags
, count
);
3707 // Make a node conservative (does not have any child information).
3708 template <typename Impl
>
3709 void ClassGraph::setConservative(const Impl
& impl
,
3713 assertx(!n
.isMissing());
3714 assertx(!n
.hasCompleteChildren());
3716 if (n
.isConservative()) {
3717 assertx(n
.hasRegularSubclass() == regSub
);
3718 assertx(n
.hasNonRegularSubclass() == nonRegSub
);
3722 assertx(!n
.hasRegularSubclass());
3723 assertx(!n
.hasNonRegularSubclass());
3725 auto f
= FlagConservative
;
3726 if (regSub
) f
= (Flags
)(f
| FlagRegSub
);
3727 if (nonRegSub
) f
= (Flags
)(f
| FlagNonRegSub
);
3729 impl
.updateFlags(n
, f
);
3730 impl
.lock(n
, [&] { n
.children
.clear(); });
3733 template <typename SerDe
, typename T
>
3734 void ClassGraph::serde(SerDe
& sd
, T cinfo
, bool ignoreChildren
) {
3735 // Serialization/deserialization entry point. If we're operating
3736 // concurrently, use one Impl, otherwise, use the other.
3737 if (SerDe::deserializing
&& table().locking
) {
3738 serdeImpl(sd
, LockedSerdeImpl
{}, cinfo
, ignoreChildren
);
3740 serdeImpl(sd
, UnlockedSerdeImpl
{}, cinfo
, ignoreChildren
);
3744 template <typename SerDe
, typename Impl
, typename T
>
3745 void ClassGraph::serdeImpl(SerDe
& sd
,
3748 bool ignoreChildren
) {
3749 // Allocate SerdeState if someone else hasn't already.
3754 if constexpr (SerDe::deserializing
) {
3757 // First ensure that all nodes reachable by this node are
3759 sd
.readWithLazyCount([&] { deserBlock(sd
, impl
); });
3761 // Then obtain a pointer to the node that ClassGraph points
3763 if (auto const name
= decodeName(sd
)) {
3764 this_
= &impl
.get(name
);
3766 // If this node was marked as having complete children (and
3767 // we're not ignoring that), mark this node and all of it's
3768 // transitive children as also having complete children.
3771 if (flags
& FlagChildren
) {
3772 assertx(flags
== FlagChildren
);
3773 assertx(!this_
->isConservative());
3774 if (!this_
->hasCompleteChildren()) {
3775 setCompleteImpl(impl
, *this_
);
3776 assertx(this_
->hasCompleteChildren());
3778 } else if (flags
& FlagConservative
) {
3779 assertx(flags
== (flags
& (FlagConservative
|
3780 FlagRegSub
| FlagNonRegSub
)));
3785 flags
& FlagNonRegSub
3788 assertx(flags
== FlagNone
);
3791 // If this node isn't regular, and we've recorded an equivalent
3792 // node for it, make sure that the equivalent is
3793 // hasCompleteChildren(), as that's an invariant.
3794 if (auto const equiv
= decodeName(sd
)) {
3795 auto const equivNode
= &impl
.get(equiv
);
3796 assertx(!equivNode
->isRegular());
3797 if (!equivNode
->hasCompleteChildren()) {
3798 assertx(!equivNode
->isConservative());
3799 setCompleteImpl(impl
, *equivNode
);
3800 assertx(equivNode
->hasCompleteChildren());
3802 impl
.setEquiv(*this_
, *equivNode
);
3805 if constexpr (!std::is_null_pointer_v
<T
>) {
3807 assertx(!this_
->isMissing());
3808 impl
.setCInfo(*this_
, *cinfo
);
3817 if (!tl_serde_state
->upward
) tl_serde_state
->upward
.emplace();
3818 if (!tl_serde_state
->downward
) tl_serde_state
->downward
.emplace();
3820 // Serialize all of the nodes reachable by this node (parents,
3821 // children, and parents of children) and encode how many.
3824 if (!this_
) return 0;
3825 // Only encode children if requested.
3826 auto count
= ignoreChildren
3827 ? serUpward(sd
, *this_
)
3828 : serDownward(sd
, *this_
);
3829 if (ignoreChildren
) return count
;
3831 folly::get_default(table().regOnlyEquivs
, this_
)) {
3832 assertx(!this_
->isRegular());
3833 assertx(e
->hasCompleteChildren());
3834 count
+= serDownward(sd
, *e
);
3839 // Encode the "entry-point" into the graph represented by this
3842 encodeName(sd
, this_
->name
);
3843 // Record whether this node has complete children, so we can
3844 // reconstruct that when deserializing.
3845 assertx(IMPLIES(hasCompleteChildren(), !isConservative()));
3846 assertx(IMPLIES(isConservative(), this_
->children
.empty()));
3847 assertx(IMPLIES(!hasCompleteChildren() && !isConservative(),
3848 !this_
->hasRegularSubclass() &&
3849 !this_
->hasNonRegularSubclass()));
3851 auto mask
= (Flags
)(FlagChildren
| FlagConservative
);
3852 if (this_
->isConservative()) {
3853 mask
= (Flags
)(mask
| FlagRegSub
| FlagNonRegSub
);
3855 if (ignoreChildren
) mask
= (Flags
)(mask
& ~FlagChildren
);
3856 sd((Flags
)(this_
->flags() & mask
));
3858 // If this Node isn't regular and has an equivalent node, record
3860 if (!ignoreChildren
&& !this_
->isMissing() && !this_
->isRegular()) {
3862 folly::get_default(table().regOnlyEquivs
, this_
)) {
3863 assertx(e
->hasCompleteChildren());
3864 encodeName(sd
, e
->name
);
3866 encodeName(sd
, nullptr);
3869 encodeName(sd
, nullptr);
3872 encodeName(sd
, nullptr);
3877 // When serializing, we write this out last. When deserializing,
3878 // we read it first.
3879 assertx(tl_serde_state
);
3880 sd(tl_serde_state
->newStrings
);
3881 tl_serde_state
->strings
.insert(
3882 end(tl_serde_state
->strings
),
3883 begin(tl_serde_state
->newStrings
),
3884 end(tl_serde_state
->newStrings
)
3886 tl_serde_state
->newStrings
.clear();
3891 // Serialize a string using the string table.
3892 template <typename SerDe
>
3893 void ClassGraph::encodeName(SerDe
& sd
, SString s
) {
3894 assertx(tl_serde_state
);
3895 if (auto const idx
= folly::get_ptr(tl_serde_state
->strToIdx
, s
)) {
3900 tl_serde_state
->strings
.size() + tl_serde_state
->newStrings
.size();
3901 tl_serde_state
->newStrings
.emplace_back(s
);
3902 tl_serde_state
->strToIdx
.emplace(s
, idx
);
3906 // Deserialize a string using the string table.
3907 template <typename SerDe
>
3908 SString
ClassGraph::decodeName(SerDe
& sd
) {
3909 assertx(tl_serde_state
);
3912 always_assert(idx
< tl_serde_state
->strings
.size());
3913 return tl_serde_state
->strings
[idx
];
3916 // Deserialize a node, along with any other nodes it depends on.
3917 template <typename SerDe
, typename Impl
>
3918 void ClassGraph::deserBlock(SerDe
& sd
, const Impl
& impl
) {
3919 // First get the name for this node.
3920 auto const name
= decodeName(sd
);
3925 // These flags are never encoded and only exist at runtime.
3926 assertx((flags
& kSerializable
) == flags
);
3927 assertx(IMPLIES(flags
& FlagMissing
, flags
== FlagMissing
));
3929 // Try to create it:
3930 auto const [node
, created
] = impl
.create(name
);
3931 if (created
|| node
->isMissing()) {
3932 // If this is the first time we've seen this node, deserialize any
3936 assertx(!node
->hasCompleteChildren());
3937 assertx(!node
->isConservative());
3939 // Either it already existed and we got an existing Node, or we
3940 // created it. Even if it already existed, we still need to process
3941 // it below as if it was new, because this might have additional
3942 // flags to add to the Node.
3944 // Deserialize dependent nodes.
3945 sd
.readWithLazyCount([&] { deserBlock(sd
, impl
); });
3947 // At this point all dependent nodes are guaranteed to exist.
3949 // Read the parent links. The children links are not encoded as
3950 // they can be inferred from the parent links.
3951 CompactVector
<Node
*> parents
;
3955 parents
.reserve(size
);
3956 for (size_t i
= 0; i
< size
; ++i
) {
3957 // This should always succeed because all dependents
3959 parents
.emplace_back(&impl
.get(decodeName(sd
)));
3961 parents
.shrink_to_fit();
3964 // If this is a "missing" node, it shouldn't have any links
3965 // (because we shouldn't know anything about it).
3966 assertx(IMPLIES(flags
& FlagMissing
, parents
.empty()));
3967 assertx(std::is_sorted(begin(parents
), end(parents
), Node::Compare
{}));
3969 // For each parent, register this node as a child. Lock the
3970 // appropriate node if we're concurrent deserializing.
3971 for (auto const parent
: parents
) {
3975 if (parent
->hasCompleteChildren() ||
3976 parent
->isConservative()) {
3979 parent
->children
.emplace_back(node
);
3986 [&, node
=node
] { node
->parents
= std::move(parents
); }
3992 // If we created this node, we need to clear FlagWait and
3993 // simultaneously set the node's flags to what we decoded.
3994 impl
.signal(*node
, flags
);
3995 } else if (!(flags
& FlagMissing
)) {
3996 impl
.updateFlags(*node
, flags
, FlagMissing
);
3999 // Otherwise skip over the dependent nodes.
4000 always_assert(flags
== FlagMissing
||
4001 flags
== (node
->flags() & kSerializable
));
4006 // Walk downward through a node's children until we hit a leaf. At
4007 // that point, we call serUpward on the leaf, which will serialize it
4008 // and all of it's parents (which should include all nodes traversed
4009 // here). Return the number of nodes serialized.
4010 template <typename SerDe
>
4011 size_t ClassGraph::serDownward(SerDe
& sd
, Node
& n
) {
4012 assertx(!table().locking
);
4013 assertx(tl_serde_state
);
4014 if (!tl_serde_state
->downward
->add(n
)) return 0;
4016 if (n
.children
.empty() || !n
.hasCompleteChildren()) {
4017 return serUpward(sd
, n
);
4019 assertx(!n
.isConservative());
4020 assertx(std::is_sorted(begin(n
.children
), end(n
.children
), Node::Compare
{}));
4023 for (auto const child
: n
.children
) {
4024 count
+= serDownward(sd
, *child
);
4029 // Serialize the given node, along with all of it's parents. Return
4030 // true if anything was serialized.
4031 template <typename SerDe
>
4032 bool ClassGraph::serUpward(SerDe
& sd
, Node
& n
) {
4033 assertx(!table().locking
);
4034 assertx(tl_serde_state
);
4035 // If we've already serialized this node, no need to serialize it
4037 if (!tl_serde_state
->upward
->add(n
)) return false;
4040 assertx(IMPLIES(n
.isMissing(), n
.parents
.empty()));
4041 assertx(IMPLIES(n
.isMissing(), n
.children
.empty()));
4042 assertx(IMPLIES(n
.isMissing(), n
.flags() == FlagMissing
));
4043 assertx(IMPLIES(n
.isConservative(), n
.children
.empty()));
4044 assertx(IMPLIES(!n
.hasCompleteChildren() && !n
.isConservative(),
4045 !n
.hasRegularSubclass()));
4046 assertx(IMPLIES(!n
.hasCompleteChildren() && !n
.isConservative(),
4047 !n
.hasNonRegularSubclass()));
4048 assertx(std::is_sorted(begin(n
.parents
), end(n
.parents
), Node::Compare
{}));
4050 encodeName(sd
, n
.name
);
4051 // Shouldn't have any FlagWait when serializing.
4052 assertx(!(n
.flags() & FlagWait
));
4053 sd((Flags
)(n
.flags() & kSerializable
));
4057 // Recursively serialize all parents of this node. This ensures
4058 // that when deserializing, the parents will be available before
4059 // deserializing this node.
4063 for (auto const parent
: n
.parents
) {
4064 count
+= serUpward(sd
, *parent
);
4070 // Record the names of the parents, to restore the links when
4072 sd(n
.parents
.size());
4073 for (auto const p
: n
.parents
) {
4075 encodeName(sd
, p
->name
);
4083 //////////////////////////////////////////////////////////////////////
4085 // Storage for the auxiliary ClassGraphs a class or func may need.
4086 struct AuxClassGraphs
{
4087 // Nodes for which we don't need children.
4088 hphp_fast_set
<ClassGraph
, ClassGraphHasher
> noChildren
;
4089 // Nodes for which we have and need children.
4090 hphp_fast_set
<ClassGraph
, ClassGraphHasher
> withChildren
;
4092 // Equivalent to the above, but Nodes added within the current
4093 // worker. These are not serialized, but will replace noChildren and
4094 // withChildren when the worker is finishing.
4095 hphp_fast_set
<ClassGraph
, ClassGraphHasher
> newNoChildren
;
4096 hphp_fast_set
<ClassGraph
, ClassGraphHasher
> newWithChildren
;
4098 template <typename SerDe
> void serde(SerDe
& sd
) {
4099 sd(noChildren
, std::less
<>{}, nullptr, true)
4100 (withChildren
, std::less
<>{}, nullptr, false)
4102 // newNoChildren and newWithChildren deliberately not serialized.
4106 //////////////////////////////////////////////////////////////////////
4108 template <typename SerDe
> void FuncInfo2::serde(SerDe
& sd
) {
4109 ScopedStringDataIndexer _
;
4110 ClassGraph::ScopedSerdeState _2
;
4119 if constexpr (SerDe::deserializing
) {
4123 auxClassGraphs
= std::make_unique
<AuxClassGraphs
>(
4124 sd
.template make
<AuxClassGraphs
>()
4128 sd((bool)auxClassGraphs
);
4129 if (auxClassGraphs
) sd(*auxClassGraphs
);
4133 //////////////////////////////////////////////////////////////////////
4136 * Known information about an instantiatiable class.
4140 * A pointer to the underlying php::Class that we're storing
4141 * information about.
4143 const php::Class
* cls
= nullptr;
4146 * The info for the parent of this Class.
4148 ClassInfo
* parent
= nullptr;
4151 const php::Const
& operator*() const {
4152 return cls
->constants
[idx
];
4154 const php::Const
* operator->() const {
4157 const php::Const
* get() const {
4158 return &cls
->constants
[idx
];
4160 const php::Class
* cls
;
4165 * A (case-sensitive) map from class constant name to the php::Class* and
4166 * index into the constants vector that it came from. This map is flattened
4167 * across the inheritance hierarchy. Use a vector_map for stable iteration.
4169 hphp_vector_map
<SString
, ConstIndex
> clsConstants
;
4172 * Inferred class constant types for the constants declared on this
4173 * class. The order mirrors the order in the php::Class constants
4174 * vector. If the vector is smaller than a constant's index, that
4175 * constant's type is implicitly TInitCell.
4177 CompactVector
<ClsConstInfo
> clsConstTypes
;
4180 * The traits used by this class, which *haven't* been flattened
4181 * into it. If the class is AttrNoExpandTrait, this will always be
4184 CompactVector
<const ClassInfo
*> usedTraits
;
4187 * A list of extra properties supplied by this class's used traits.
4189 CompactVector
<php::Prop
> traitProps
;
4192 * A (case-sensitive) map from class method names to the php::Func
4193 * associated with it. This map is flattened across the inheritance
4194 * hierarchy. There's a lot of these, so we use a sorted_vector_map
4195 * to minimize wasted space.
4197 folly::sorted_vector_map
<SString
, MethTabEntry
> methods
;
4200 * A (case-sensitive) map from class method names to associated
4201 * FuncFamilyOrSingle objects that represent the set of
4202 * possibly-overriding methods.
4204 * In addition to the set of methods, a bit is also set indicating
4205 * whether the set of "complete" or not. A complete set means the
4206 * ultimate method will definitely be one in the set. An incomplete
4207 * set means that the ultimate method will either be one in the set,
4208 * or won't resolve to anything (a missing function).
4210 * We do not encode "special" methods in these, as their semantics
4211 * are special and it's not useful.
4213 * For every method present in this ClassInfo's method table, there
4214 * will be an entry in methodFamilies. For regular classes, this
4215 * suffices for looking up information for both all subclasses and
4216 * the regular subset. For non-regular classes, the results for
4217 * "all" and the regular subset may differ. In that case, there is a
4218 * separate "aux" table containing the results for the regular
4219 * subset. If there is no associated entry in the aux table, the
4220 * result is the same as the entry in the normal table (this is a
4221 * common case and saves on memory). For regular classes the aux
4222 * table is always empty.
4224 * If a method is marked as AttrNoOverride, it will not have an
4225 * entry in these maps. If a method is marked as noOverrideRegular,
4226 * it will not have an entry in the aux map (if it would have
4227 * otherwise). In either case, the resolved method is assumed to be
4228 * the same method in this ClassInfo's method table.
4230 * The above is true for all class types. For abstract classes and
4231 * interfaces, however, there may be more entries here than present
4232 * in the methods table. These correspond to methods implemented by
4233 * *all* regular subclasses of the abstract class/interface. For
4234 * that reason, they will only be present in the regular variant of
4235 * the map. This is needed to preserve monotonicity (see comment in
4236 * BuildSubclassListJob::process_roots).
4238 folly::sorted_vector_map
<SString
, FuncFamilyOrSingle
> methodFamilies
;
4239 folly::sorted_vector_map
<SString
, FuncFamilyOrSingle
> methodFamiliesAux
;
4241 ClassGraph classGraph
;
4244 * Property types for public static properties, declared on this exact class
4245 * (i.e. not flattened in the hierarchy).
4247 SStringToOneT
<PublicSPropEntry
> publicStaticProps
;
4250 * Flags to track if this class is mocked, or if any of its derived classes
4253 bool isMocked
{false};
4254 bool isSubMocked
{false};
4257 * Track if this class has a property which might redeclare a property in a
4258 * parent class with an inequivalent type-hint.
4260 bool hasBadRedeclareProp
{true};
4263 * Track if this class has any properties with initial values that might
4264 * violate their type-hints.
4266 bool hasBadInitialPropValues
{true};
4269 * Track if this class has any const props (including inherited ones).
4271 bool hasConstProp
{false};
4274 * Track if any derived classes (including this one) have any const props.
4276 bool subHasConstProp
{false};
4279 * Track if this class has a reified parent.
4281 bool hasReifiedParent
{false};
4285 * Known information about an instantiable class.
4287 * This class mirrors the ClassInfo struct, but is produced and used
4288 * by remote workers. As needed, this struct will gain more and more
4289 * of ClassInfo's fields (but stored in a more remote worker friendly
4290 * way). Local calculations will continue to use ClassInfo. Once
4291 * everything is converted to use remote workers, this struct will
4292 * subsume ClassInfo entirely (and be renamed).
4296 * The name of the underlying php::Class that this ClassInfo
4299 LSString name
{nullptr};
4302 * The name of the parent of this class (or nullptr if none).
4304 LSString parent
{nullptr};
4307 * php::Class associated with this ClassInfo. Must be set manually
4310 const php::Class
* cls
{nullptr};
4313 * A (case-sensitive) map from class constant name to the ConstIndex
4314 * representing the constant. This map is flattened across the
4315 * inheritance hierarchy.
4317 struct ConstIndexAndKind
{
4319 // Store the kind here as well, so we don't need to potentially
4320 // find another class to determine it.
4321 ConstModifiers::Kind kind
;
4322 template <typename SerDe
> void serde(SerDe
& sd
) {
4326 SStringToOneT
<ConstIndexAndKind
> clsConstants
;
4329 * Inferred information about a class constant declared on this
4330 * class (not flattened).
4332 SStringToOneT
<ClsConstInfo
> clsConstantInfo
;
4335 * A list of extra properties supplied by this class's used traits.
4337 CompactVector
<php::Prop
> traitProps
;
4340 * A (case-sensitive) map from class method names to the
4341 * MethTabEntry associated with it. This map is flattened across the
4342 * inheritance hierarchy. MethTabEntry represents the php::Func,
4343 * along with some metadata specific to the method on this specific
4346 SStringToOneT
<MethTabEntry
> methods
;
4349 * In most situations, methods are inherited from a parent class, so
4350 * if a class declares a method, all of its subclasses are
4351 * guaranteed to possess it. An exception to this is with
4352 * interfaces, whose methods are not inherited.
4354 * However, when building func-families and other method data, its
4355 * useful to know all of the methods declared by all parents of a
4356 * class. Most of those methods will be on the method table for this
4357 * ClassInfo, but if any aren't, they'll be added to this set.
4359 SStringSet missingMethods
;
4362 * ClassGraph representing this class. This ClassGraph is guaranteed
4363 * to have hasCompleteChildren() be true (except if this is the
4364 * Closure base class).
4366 ClassGraph classGraph
;
4369 * Set of "extra" methods for this class. These are methods which
4370 * aren't formally part of this class, but must be analyzed along
4371 * with the class' methods. Examples are unflattened trait methods,
4372 * and the invoke methods of their associated closures.
4374 MethRefSet extraMethods
;
4377 * A vector of the closures class-infos declared in this class. Such
4378 * closures are stored in their declaring class, not as a top-level
4381 CompactVector
<std::unique_ptr
<ClassInfo2
>> closures
;
4384 * A (case-sensitive) map from method names to associated
4385 * FuncFamilyEntry objects that represent the set of
4386 * possibly-overriding methods.
4388 * We do not encode "special" methods in these, as their semantics
4389 * are special and it's not useful.
4391 * For every method present in this ClassInfo's method table, there
4392 * will be an entry in methodFamilies (unless if AttrNoOverride, see
4393 * below). The FuncFamilyEntry will provide the set of
4394 * possibly-overriding methods, for both the regular class subset
4397 * If a method is marked as AttrNoOverride, it will not have an
4398 * entry in this map. The resolved method is assumed to be the same
4399 * method in this ClassInfo's method table. If a method is marked as
4400 * noOverrideRegular, it will have an entry in this map, but can be
4401 * treated as AttrNoOverride if you're only considering regular
4404 * There may be more entries in methodFamilies than in the
4405 * ClassInfo's method table. For classes which aren't abstract and
4406 * aren't interfaces, this is an artifact of how the table is
4407 * created and can be ignored. For abstract classes and interfaces,
4408 * however, these extra entries correspond to methods implemented by
4409 * *all* regular subclasses of the abstract class/interface. In this
4410 * situation, only the data in the FuncFamilyEntry corresponding to
4411 * the regular subset is meaningful. This is needed to preserve
4412 * monotonicity (see comment in
4413 * BuildSubclassListJob::process_roots).
4415 SStringToOneT
<FuncFamilyEntry
> methodFamilies
;
4418 * FuncInfo2s for the methods declared on this class (not
4419 * flattened). This is in the same order as the methods vector on
4420 * the associated php::Class.
4422 CompactVector
<std::unique_ptr
<FuncInfo2
>> funcInfos
;
4425 * If we utilize a ClassGraph while resolving types, we store it
4426 * here. This ensures that that ClassGraph will always be available
4429 AuxClassGraphs auxClassGraphs
;
4432 * Track if this class has a property which might redeclare a property in a
4433 * parent class with an inequivalent type-hint.
4435 bool hasBadRedeclareProp
{true};
4438 * Track if this class has any properties with initial values that might
4439 * violate their type-hints.
4441 bool hasBadInitialPropValues
{true};
4444 * Track if this class has any const props (including inherited ones).
4446 bool hasConstProp
{false};
4449 * Track if any derived classes (including this one) have any const props.
4451 bool subHasConstProp
{false};
4454 * Track if this class has a reified parent.
4456 bool hasReifiedParent
{false};
4459 * Whether this class (or any derived classes) has a __Reified
4462 bool hasReifiedGeneric
{false};
4463 bool subHasReifiedGeneric
{false};
4466 * Initial AttrNoReifiedInit setting of attrs (which might be
4469 bool initialNoReifiedInit
{false};
4472 * Whether is_mock_class() is true for this class.
4474 bool isMockClass
{false};
4477 * Whether this class is mocked (has a direct subclass for which
4478 * is_mock_class() is true).
4480 bool isMocked
{false};
4483 * Whether isMocked is true for this class, or any of its
4486 bool isSubMocked
{false};
4489 * Whether is_regular_class() is true for this class.
4491 bool isRegularClass
{false};
4493 template <typename SerDe
> void serde(SerDe
& sd
) {
4494 ScopedStringDataIndexer _
;
4495 ClassGraph::ScopedSerdeState _2
;
4498 (clsConstants
, string_data_lt
{})
4499 (clsConstantInfo
, string_data_lt
{})
4501 (methods
, string_data_lt
{})
4502 (missingMethods
, string_data_lt
{})
4504 (extraMethods
, std::less
<MethRef
>{})
4506 (methodFamilies
, string_data_lt
{})
4509 (hasBadRedeclareProp
)
4510 (hasBadInitialPropValues
)
4515 (subHasReifiedGeneric
)
4516 (initialNoReifiedInit
)
4525 //////////////////////////////////////////////////////////////////////
4529 Class::Class(ClassGraph g
): opaque
{g
.this_
} {
4533 ClassGraph
Class::graph() const {
4534 assertx(opaque
.left());
4535 return ClassGraph
{ (ClassGraph::Node
*)opaque
.left() };
4538 bool Class::isSerialized() const {
4539 return opaque
.right();
4542 ClassInfo
* Class::cinfo() const {
4543 return graph().cinfo();
4546 ClassInfo2
* Class::cinfo2() const {
4547 return graph().cinfo2();
4550 bool Class::same(const Class
& o
) const {
4551 return graph() == o
.graph();
4554 bool Class::exactSubtypeOfExact(const Class
& o
,
4556 bool nonRegR
) const {
4557 return graph().exactSubtypeOfExact(o
.graph(), nonRegL
, nonRegR
);
4560 bool Class::exactSubtypeOf(const Class
& o
, bool nonRegL
, bool nonRegR
) const {
4561 return graph().exactSubtypeOf(o
.graph(), nonRegL
, nonRegR
);
4564 bool Class::subSubtypeOf(const Class
& o
, bool nonRegL
, bool nonRegR
) const {
4565 return graph().subSubtypeOf(o
.graph(), nonRegL
, nonRegR
);
4568 bool Class::exactCouldBeExact(const Class
& o
,
4570 bool nonRegR
) const {
4571 return graph().exactCouldBeExact(o
.graph(), nonRegL
, nonRegR
);
4574 bool Class::exactCouldBe(const Class
& o
, bool nonRegL
, bool nonRegR
) const {
4575 return graph().exactCouldBe(o
.graph(), nonRegL
, nonRegR
);
4578 bool Class::subCouldBe(const Class
& o
, bool nonRegL
, bool nonRegR
) const {
4579 return graph().subCouldBe(o
.graph(), nonRegL
, nonRegR
);
4582 SString
Class::name() const {
4583 if (opaque
.left()) {
4584 return graph().name();
4586 assertx(opaque
.right());
4587 return opaque
.right();
4591 Optional
<res::Class
> Class::withoutNonRegular() const {
4592 if (auto const g
= graph().withoutNonRegular()) {
4595 return std::nullopt
;
4599 bool Class::mightBeRegular() const { return graph().mightBeRegular(); }
4600 bool Class::mightBeNonRegular() const { return graph().mightBeNonRegular(); }
4602 bool Class::couldBeOverridden() const {
4603 if (!graph().isMissing() && graph().isTrait()) return false;
4605 graph().mightHaveRegularSubclass() ||
4606 graph().mightHaveNonRegularSubclass();
4609 bool Class::couldBeOverriddenByRegular() const {
4610 if (!graph().isMissing() && graph().isTrait()) return false;
4611 return graph().mightHaveRegularSubclass();
4614 bool Class::mightContainNonRegular() const {
4615 return graph().mightBeNonRegular() || graph().mightHaveNonRegularSubclass();
4618 bool Class::couldHaveMagicBool() const {
4619 auto const g
= graph();
4620 if (!g
.ensure()) return true;
4621 if (g
.isMissing()) return true;
4622 if (!g
.isInterface()) return has_magic_bool_conversion(g
.topBase().name());
4623 if (!g
.ensureWithChildren()) return true;
4624 if (!g
.hasCompleteChildren()) return true;
4625 for (auto const c
: g
.children()) {
4626 if (has_magic_bool_conversion(c
.topBase().name())) return true;
4631 bool Class::couldHaveMockedSubClass() const {
4632 if (!graph().ensureCInfo()) {
4634 } else if (auto const ci
= cinfo()) {
4635 return ci
->isSubMocked
;
4636 } else if (auto const ci
= cinfo2()) {
4637 return ci
->isSubMocked
;
4643 bool Class::couldBeMocked() const {
4644 if (!graph().ensureCInfo()) {
4646 } else if (auto const ci
= cinfo()) {
4647 return ci
->isMocked
;
4648 } else if (auto const ci
= cinfo2()) {
4649 return ci
->isMocked
;
4655 bool Class::couldHaveReifiedGenerics() const {
4656 if (!graph().ensureCInfo()) {
4658 } else if (auto const ci
= cinfo()) {
4659 return ci
->cls
->hasReifiedGenerics
;
4660 } else if (auto const ci
= cinfo2()) {
4661 return ci
->hasReifiedGeneric
;
4667 bool Class::mustHaveReifiedGenerics() const {
4668 if (!graph().ensureCInfo()) {
4670 } else if (auto const ci
= cinfo()) {
4671 return ci
->cls
->hasReifiedGenerics
;
4672 } else if (auto const ci
= cinfo2()) {
4673 return ci
->hasReifiedGeneric
;
4679 bool Class::couldHaveReifiedParent() const {
4680 if (!graph().ensureCInfo()) {
4682 } else if (auto const ci
= cinfo()) {
4683 return ci
->hasReifiedParent
;
4684 } else if (auto const ci
= cinfo2()) {
4685 return ci
->hasReifiedParent
;
4691 bool Class::mustHaveReifiedParent() const {
4692 if (!graph().ensureCInfo()) {
4694 } else if (auto const ci
= cinfo()) {
4695 return ci
->hasReifiedParent
;
4696 } else if (auto const ci
= cinfo2()) {
4697 return ci
->hasReifiedParent
;
4703 bool Class::mightCareAboutDynConstructs() const {
4704 if (!Cfg::Eval::ForbidDynamicConstructs
) return false;
4705 if (!graph().ensureCInfo()) {
4707 } else if (auto const ci
= cinfo()) {
4708 return !(ci
->cls
->attrs
& AttrDynamicallyConstructible
);
4709 } else if (auto const ci
= cinfo2()) {
4710 return !ci
->cls
|| !(ci
->cls
->attrs
& AttrDynamicallyConstructible
);
4716 bool Class::mightCareAboutDynamicallyReferenced() const {
4717 if (Cfg::Eval::DynamicallyReferencedNoticeSampleRate
== 0) return false;
4718 if (!graph().ensureCInfo()) {
4720 } else if (auto const ci
= cinfo()) {
4721 return !(ci
->cls
->attrs
& AttrDynamicallyReferenced
);
4722 } else if (auto const ci
= cinfo2()) {
4723 return !ci
->cls
|| !(ci
->cls
->attrs
& AttrDynamicallyReferenced
);
4729 bool Class::couldHaveConstProp() const {
4730 if (!graph().ensureCInfo()) {
4732 } else if (auto const ci
= cinfo()) {
4733 return ci
->hasConstProp
;
4734 } else if (auto const ci
= cinfo2()) {
4735 return ci
->hasConstProp
;
4741 bool Class::subCouldHaveConstProp() const {
4742 if (!graph().ensureCInfo()) {
4744 } else if (auto const ci
= cinfo()) {
4745 return ci
->subHasConstProp
;
4746 } else if (auto const ci
= cinfo2()) {
4747 return ci
->subHasConstProp
;
4753 Optional
<res::Class
> Class::parent() const {
4754 if (auto const p
= graph().base()) {
4757 return std::nullopt
;
4761 const php::Class
* Class::cls() const {
4762 if (!graph().ensureCInfo()) {
4764 } else if (auto const ci
= cinfo()) {
4766 } else if (auto const ci
= cinfo2()) {
4773 bool Class::hasCompleteChildren() const {
4774 return graph().hasCompleteChildren();
4777 bool Class::isComplete() const {
4779 !graph().isMissing() &&
4780 (graph().hasCompleteChildren() || graph().isConservative());
4784 Class::forEachSubclass(const std::function
<void(SString
, Attr
)>& f
) const {
4785 auto const g
= graph();
4786 if (!g
.ensureWithChildren()) return false;
4787 if (g
.isMissing() || !g
.hasCompleteChildren()) return false;
4788 for (auto const c
: g
.children()) {
4789 auto const attrs
= [&] {
4790 if (c
.isInterface()) return AttrInterface
;
4791 if (c
.isTrait()) return AttrTrait
;
4792 if (c
.isEnum()) return AttrEnum
;
4793 if (c
.isAbstract()) return AttrAbstract
;
4801 std::string
show(const Class
& c
) {
4802 if (auto const n
= c
.opaque
.right()) {
4803 return folly::sformat("?\"{}\"", n
);
4806 auto const g
= c
.graph();
4807 if (g
.isMissingRaw()) return folly::sformat("\"{}\"", g
.name());
4808 if (!g
.hasCompleteChildrenRaw()) {
4809 if (g
.isConservativeRaw()) {
4810 return folly::sformat(
4812 (c
.cinfo() || c
.cinfo2()) ? "" : "-",
4816 return folly::sformat("!{}", g
.name());
4818 if (!c
.cinfo() && !c
.cinfo2()) return folly::sformat("-{}", g
.name());
4819 return g
.name()->toCppString();
4822 // Call the given callable for every class which is a subclass of
4823 // *all* the classes in the range. If the range includes nothing but
4824 // unresolved classes, they will be passed, as-is, to the callable. If
4825 // the range includes a mix of resolved and unresolved classes, the
4826 // unresolved classes will be used to narrow the classes passed to the
4827 // callable, but the unresolved classes themself will not be passed to
4828 // the callable. If the callable returns false, iteration is
4829 // stopped. If includeNonRegular is true, non-regular subclasses are
4830 // visited (normally they are skipped).
4831 template <typename F
>
4832 void Class::visitEverySub(folly::Range
<const Class
*> classes
,
4833 bool includeNonRegular
,
4835 if (classes
.size() == 1) {
4836 auto const n
= classes
[0].graph().this_
;
4837 if (!ClassGraph
{n
}.ensureWithChildren() ||
4839 !n
->hasCompleteChildren()) {
4840 f(Class
{ClassGraph
{ n
}});
4843 ClassGraph::forEachChild(
4845 [&] (ClassGraph::Node
& c
) {
4846 assertx(!c
.isMissing());
4847 if (includeNonRegular
|| c
.isRegular()) {
4848 if (!f(Class
{ClassGraph
{ &c
}})) return ClassGraph::Action::Stop
;
4850 return ClassGraph::Action::Continue
;
4856 ClassGraph::NodeVec v
;
4857 v
.reserve(classes
.size());
4858 for (auto const c
: classes
) v
.emplace_back(c
.graph().this_
);
4860 ClassGraph::enumerateIsectMembers(
4863 [&] (ClassGraph::Node
& c
) { return f(Class
{ClassGraph
{ &c
}}); },
4868 Class::ClassVec
Class::combine(folly::Range
<const Class
*> classes1
,
4869 folly::Range
<const Class
*> classes2
,
4874 ClassGraph::NodeVec v1
;
4875 ClassGraph::NodeVec v2
;
4876 v1
.reserve(classes1
.size());
4877 v2
.reserve(classes2
.size());
4878 for (auto const c
: classes1
) v1
.emplace_back(c
.graph().this_
);
4879 for (auto const c
: classes2
) v2
.emplace_back(c
.graph().this_
);
4881 ClassGraph::combine(v1
, v2
, isSub1
, isSub2
, nonRegular1
, nonRegular2
);
4883 out
.reserve(i
.size());
4884 for (auto const c
: i
) out
.emplace_back(Class
{ClassGraph
{ c
}});
4888 Class::ClassVec
Class::removeNonRegular(folly::Range
<const Class
*> classes
) {
4889 ClassGraph::NodeVec v
;
4890 v
.reserve(classes
.size());
4891 for (auto const c
: classes
) v
.emplace_back(c
.graph().this_
);
4892 auto const i
= ClassGraph::removeNonReg(v
);
4894 out
.reserve(i
.size());
4895 for (auto const c
: i
) out
.emplace_back(Class
{ClassGraph
{ c
}});
4899 Class::ClassVec
Class::intersect(folly::Range
<const Class
*> classes1
,
4900 folly::Range
<const Class
*> classes2
,
4903 bool& nonRegularOut
) {
4904 ClassGraph::NodeVec v1
;
4905 ClassGraph::NodeVec v2
;
4906 v1
.reserve(classes1
.size());
4907 v2
.reserve(classes2
.size());
4908 for (auto const c
: classes1
) v1
.emplace_back(c
.graph().this_
);
4909 for (auto const c
: classes2
) v2
.emplace_back(c
.graph().this_
);
4911 ClassGraph::intersect(v1
, v2
, nonRegular1
, nonRegular2
, nonRegularOut
);
4913 out
.reserve(i
.size());
4914 for (auto const c
: i
) out
.emplace_back(Class
{ClassGraph
{ c
}});
4918 bool Class::couldBeIsect(folly::Range
<const Class
*> classes1
,
4919 folly::Range
<const Class
*> classes2
,
4922 ClassGraph::NodeVec v1
;
4923 ClassGraph::NodeVec v2
;
4924 v1
.reserve(classes1
.size());
4925 v2
.reserve(classes2
.size());
4926 for (auto const c
: classes1
) v1
.emplace_back(c
.graph().this_
);
4927 for (auto const c
: classes2
) v2
.emplace_back(c
.graph().this_
);
4928 return ClassGraph::couldBeIsect(v1
, v2
, nonRegular1
, nonRegular2
);
4931 Optional
<Class
> Class::unserialize(const IIndex
& index
) const {
4932 if (opaque
.left()) return *this;
4933 return index
.resolve_class(opaque
.right());
4936 Class
Class::get(SString name
) {
4937 return Class
{ ClassGraph::get(name
) };
4940 Class
Class::get(const ClassInfo
& cinfo
) {
4941 assertx(cinfo
.classGraph
);
4942 return Class
{ cinfo
.classGraph
};
4945 Class
Class::get(const ClassInfo2
& cinfo
) {
4946 assertx(cinfo
.classGraph
);
4947 return Class
{ cinfo
.classGraph
};
4950 Class
Class::getOrCreate(SString name
) {
4951 return Class
{ ClassGraph::getOrCreate(name
) };
4954 Class
Class::getUnresolved(SString name
) {
4955 return Class
{ ClassGraph::getMissing(name
) };
4958 void Class::makeConservativeForTest() {
4959 auto n
= graph().this_
;
4960 n
->setFlags(ClassGraph::FlagConservative
, ClassGraph::FlagChildren
);
4961 n
->children
.clear();
4965 bool Class::isMissingDebug() const {
4966 return graph().isMissingRaw();
4970 void Class::serde(BlobEncoder
& sd
) const {
4974 : ClassGraph::get(opaque
.right()),
4979 Class
Class::makeForSerde(BlobDecoder
& sd
) {
4983 // Make the class start out as serialized.
4984 return Class
{ g
.name() };
4987 //////////////////////////////////////////////////////////////////////
4993 std::string
Func::name() const {
4994 return match
<std::string
>(
4996 [] (FuncName s
) { return s
.name
->toCppString(); },
4998 if (s
.cls
) return folly::sformat("{}::{}", s
.cls
, s
.name
);
4999 return folly::sformat("???::{}", s
.name
);
5001 [] (Fun f
) { return f
.finfo
->func
->name
->toCppString(); },
5002 [] (Fun2 f
) { return f
.finfo
->name
->toCppString(); },
5003 [] (Method m
) { return func_fullname(*m
.finfo
->func
); },
5004 [] (Method2 m
) { return func_fullname(*m
.finfo
->func
); },
5005 [] (MethodFamily fam
) {
5006 return folly::sformat(
5008 fam
.family
->possibleFuncs().front().ptr()->name
5011 [] (MethodFamily2 fam
) {
5012 return folly::sformat(
5018 [] (MethodOrMissing m
) { return func_fullname(*m
.finfo
->func
); },
5019 [] (MethodOrMissing2 m
) { return func_fullname(*m
.finfo
->func
); },
5020 [] (MissingFunc m
) { return m
.name
->toCppString(); },
5021 [] (MissingMethod m
) {
5022 if (m
.cls
) return folly::sformat("{}::{}", m
.cls
, m
.name
);
5023 return folly::sformat("???::{}", m
.name
);
5025 [] (const Isect
& i
) {
5026 assertx(i
.families
.size() > 1);
5027 return func_fullname(*i
.families
[0]->possibleFuncs().front().ptr());
5029 [] (const Isect2
& i
) {
5030 assertx(i
.families
.size() > 1);
5031 using namespace folly::gen
;
5032 return folly::sformat(
5035 | map([] (const FuncFamily2
* ff
) { return ff
->m_id
.toString(); })
5036 | unsplit
<std::string
>("&"),
5037 i
.families
[0]->m_name
5043 const php::Func
* Func::exactFunc() const {
5044 using Ret
= const php::Func
*;
5047 [] (FuncName
) { return Ret
{}; },
5048 [] (MethodName
) { return Ret
{}; },
5049 [] (Fun f
) { return f
.finfo
->func
; },
5050 [] (Fun2 f
) { return f
.finfo
->func
; },
5051 [] (Method m
) { return m
.finfo
->func
; },
5052 [] (Method2 m
) { return m
.finfo
->func
; },
5053 [] (MethodFamily
) { return Ret
{}; },
5054 [] (MethodFamily2
) { return Ret
{}; },
5055 [] (MethodOrMissing
) { return Ret
{}; },
5056 [] (MethodOrMissing2
) { return Ret
{}; },
5057 [] (MissingFunc
) { return Ret
{}; },
5058 [] (MissingMethod
) { return Ret
{}; },
5059 [] (const Isect
&) { return Ret
{}; },
5060 [] (const Isect2
&) { return Ret
{}; }
5064 TriBool
Func::exists() const {
5065 return match
<TriBool
>(
5067 [] (FuncName
) { return TriBool::Maybe
; },
5068 [] (MethodName
) { return TriBool::Maybe
; },
5069 [] (Fun
) { return TriBool::Yes
; },
5070 [] (Fun2
) { return TriBool::Yes
; },
5071 [] (Method
) { return TriBool::Yes
; },
5072 [] (Method2
) { return TriBool::Yes
; },
5073 [] (MethodFamily
) { return TriBool::Maybe
; },
5074 [] (MethodFamily2
) { return TriBool::Maybe
; },
5075 [] (MethodOrMissing
) { return TriBool::Maybe
; },
5076 [] (MethodOrMissing2
) { return TriBool::Maybe
; },
5077 [] (MissingFunc
) { return TriBool::No
; },
5078 [] (MissingMethod
) { return TriBool::No
; },
5079 [] (const Isect
&) { return TriBool::Maybe
; },
5080 [] (const Isect2
&) { return TriBool::Maybe
; }
5084 bool Func::isFoldable() const {
5087 [] (FuncName
) { return false; },
5088 [] (MethodName
) { return false; },
5090 return f
.finfo
->func
->attrs
& AttrIsFoldable
;
5093 return f
.finfo
->func
->attrs
& AttrIsFoldable
;
5095 [] (Method m
) { return m
.finfo
->func
->attrs
& AttrIsFoldable
; },
5096 [] (Method2 m
) { return m
.finfo
->func
->attrs
& AttrIsFoldable
; },
5097 [] (MethodFamily
) { return false; },
5098 [] (MethodFamily2
) { return false; },
5099 [] (MethodOrMissing
) { return false; },
5100 [] (MethodOrMissing2
){ return false; },
5101 [] (MissingFunc
) { return false; },
5102 [] (MissingMethod
) { return false; },
5103 [] (const Isect
&) { return false; },
5104 [] (const Isect2
&) { return false; }
5108 bool Func::couldHaveReifiedGenerics() const {
5111 [] (FuncName s
) { return true; },
5112 [] (MethodName
) { return true; },
5113 [] (Fun f
) { return f
.finfo
->func
->isReified
; },
5114 [] (Fun2 f
) { return f
.finfo
->func
->isReified
; },
5115 [] (Method m
) { return m
.finfo
->func
->isReified
; },
5116 [] (Method2 m
) { return m
.finfo
->func
->isReified
; },
5117 [] (MethodFamily fa
) {
5118 return fa
.family
->infoFor(fa
.regularOnly
).m_static
->m_maybeReified
;
5120 [] (MethodFamily2 fa
) {
5121 return fa
.family
->infoFor(fa
.regularOnly
).m_maybeReified
;
5123 [] (MethodOrMissing m
) { return m
.finfo
->func
->isReified
; },
5124 [] (MethodOrMissing2 m
) { return m
.finfo
->func
->isReified
; },
5125 [] (MissingFunc
) { return false; },
5126 [] (MissingMethod
) { return false; },
5127 [] (const Isect
& i
) {
5128 for (auto const ff
: i
.families
) {
5129 if (!ff
->infoFor(i
.regularOnly
).m_static
->m_maybeReified
) return false;
5133 [] (const Isect2
& i
) {
5134 for (auto const ff
: i
.families
) {
5135 if (!ff
->infoFor(i
.regularOnly
).m_maybeReified
) return false;
5142 bool Func::mightCareAboutDynCalls() const {
5143 if (Cfg::Eval::NoticeOnBuiltinDynamicCalls
&& mightBeBuiltin()) {
5146 auto const mightCareAboutFuncs
=
5147 Cfg::Eval::ForbidDynamicCallsToFunc
> 0;
5148 auto const mightCareAboutInstMeth
=
5149 Cfg::Eval::ForbidDynamicCallsToInstMeth
> 0;
5150 auto const mightCareAboutClsMeth
=
5151 Cfg::Eval::ForbidDynamicCallsToClsMeth
> 0;
5155 [&] (FuncName
) { return mightCareAboutFuncs
; },
5157 return mightCareAboutClsMeth
|| mightCareAboutInstMeth
;
5160 return dyn_call_error_level(f
.finfo
->func
) > 0;
5163 return dyn_call_error_level(f
.finfo
->func
) > 0;
5165 [&] (Method m
) { return dyn_call_error_level(m
.finfo
->func
) > 0; },
5166 [&] (Method2 m
) { return dyn_call_error_level(m
.finfo
->func
) > 0; },
5167 [&] (MethodFamily fa
) {
5169 fa
.family
->infoFor(fa
.regularOnly
).m_static
->m_maybeCaresAboutDynCalls
;
5171 [&] (MethodFamily2 fa
) {
5173 fa
.family
->infoFor(fa
.regularOnly
).m_maybeCaresAboutDynCalls
;
5175 [&] (MethodOrMissing m
) { return dyn_call_error_level(m
.finfo
->func
) > 0; },
5176 [&] (MethodOrMissing2 m
) { return dyn_call_error_level(m
.finfo
->func
) > 0; },
5177 [&] (MissingFunc m
) { return false; },
5178 [&] (MissingMethod m
) { return false; },
5179 [&] (const Isect
& i
) {
5180 for (auto const ff
: i
.families
) {
5181 if (!ff
->infoFor(i
.regularOnly
).m_static
->m_maybeCaresAboutDynCalls
) {
5187 [&] (const Isect2
& i
) {
5188 for (auto const ff
: i
.families
) {
5189 if (!ff
->infoFor(i
.regularOnly
).m_maybeCaresAboutDynCalls
) {
5198 bool Func::mightBeBuiltin() const {
5201 [] (FuncName s
) { return true; },
5202 [] (MethodName
) { return true; },
5203 [] (Fun f
) { return f
.finfo
->func
->attrs
& AttrBuiltin
; },
5204 [] (Fun2 f
) { return f
.finfo
->func
->attrs
& AttrBuiltin
; },
5205 [] (Method m
) { return m
.finfo
->func
->attrs
& AttrBuiltin
; },
5206 [] (Method2 m
) { return m
.finfo
->func
->attrs
& AttrBuiltin
; },
5207 [] (MethodFamily fa
) {
5208 return fa
.family
->infoFor(fa
.regularOnly
).m_static
->m_maybeBuiltin
;
5210 [] (MethodFamily2 fa
) {
5211 return fa
.family
->infoFor(fa
.regularOnly
).m_maybeBuiltin
;
5213 [] (MethodOrMissing m
) { return m
.finfo
->func
->attrs
& AttrBuiltin
; },
5214 [] (MethodOrMissing2 m
) { return m
.finfo
->func
->attrs
& AttrBuiltin
; },
5215 [] (MissingFunc m
) { return false; },
5216 [] (MissingMethod m
) { return false; },
5217 [] (const Isect
& i
) {
5218 for (auto const ff
: i
.families
) {
5219 if (!ff
->infoFor(i
.regularOnly
).m_static
->m_maybeBuiltin
) return false;
5223 [] (const Isect2
& i
) {
5224 for (auto const ff
: i
.families
) {
5225 if (!ff
->infoFor(i
.regularOnly
).m_maybeBuiltin
) return false;
5232 uint32_t Func::minNonVariadicParams() const {
5233 return match
<uint32_t>(
5235 [] (FuncName
) { return 0; },
5236 [] (MethodName
) { return 0; },
5237 [] (Fun f
) { return numNVArgs(*f
.finfo
->func
); },
5238 [] (Fun2 f
) { return numNVArgs(*f
.finfo
->func
); },
5239 [] (Method m
) { return numNVArgs(*m
.finfo
->func
); },
5240 [] (Method2 m
) { return numNVArgs(*m
.finfo
->func
); },
5241 [] (MethodFamily fa
) {
5243 fa
.family
->infoFor(fa
.regularOnly
).m_static
->m_minNonVariadicParams
;
5245 [&] (MethodFamily2 fa
) {
5246 return fa
.family
->infoFor(fa
.regularOnly
).m_minNonVariadicParams
;
5248 [] (MethodOrMissing m
) { return numNVArgs(*m
.finfo
->func
); },
5249 [] (MethodOrMissing2 m
) { return numNVArgs(*m
.finfo
->func
); },
5250 [] (MissingFunc
) { return 0; },
5251 [] (MissingMethod
) { return 0; },
5252 [] (const Isect
& i
) {
5254 for (auto const ff
: i
.families
) {
5257 ff
->infoFor(i
.regularOnly
).m_static
->m_minNonVariadicParams
5262 [] (const Isect2
& i
) {
5264 for (auto const ff
: i
.families
) {
5267 ff
->infoFor(i
.regularOnly
).m_minNonVariadicParams
5275 uint32_t Func::maxNonVariadicParams() const {
5276 return match
<uint32_t>(
5278 [] (FuncName
) { return std::numeric_limits
<uint32_t>::max(); },
5279 [] (MethodName
) { return std::numeric_limits
<uint32_t>::max(); },
5280 [] (Fun f
) { return numNVArgs(*f
.finfo
->func
); },
5281 [] (Fun2 f
) { return numNVArgs(*f
.finfo
->func
); },
5282 [] (Method m
) { return numNVArgs(*m
.finfo
->func
); },
5283 [] (Method2 m
) { return numNVArgs(*m
.finfo
->func
); },
5284 [] (MethodFamily fa
) {
5286 fa
.family
->infoFor(fa
.regularOnly
).m_static
->m_maxNonVariadicParams
;
5288 [] (MethodFamily2 fa
) {
5289 return fa
.family
->infoFor(fa
.regularOnly
).m_maxNonVariadicParams
;
5291 [] (MethodOrMissing m
) { return numNVArgs(*m
.finfo
->func
); },
5292 [] (MethodOrMissing2 m
) { return numNVArgs(*m
.finfo
->func
); },
5293 [] (MissingFunc
) { return 0; },
5294 [] (MissingMethod
) { return 0; },
5295 [] (const Isect
& i
) {
5296 auto nv
= std::numeric_limits
<uint32_t>::max();
5297 for (auto const ff
: i
.families
) {
5300 ff
->infoFor(i
.regularOnly
).m_static
->m_maxNonVariadicParams
5305 [] (const Isect2
& i
) {
5306 auto nv
= std::numeric_limits
<uint32_t>::max();
5307 for (auto const ff
: i
.families
) {
5310 ff
->infoFor(i
.regularOnly
).m_maxNonVariadicParams
5318 const RuntimeCoeffects
* Func::requiredCoeffects() const {
5319 return match
<const RuntimeCoeffects
*>(
5321 [] (FuncName
) { return nullptr; },
5322 [] (MethodName
) { return nullptr; },
5323 [] (Fun f
) { return &f
.finfo
->func
->requiredCoeffects
; },
5324 [] (Fun2 f
) { return &f
.finfo
->func
->requiredCoeffects
; },
5325 [] (Method m
) { return &m
.finfo
->func
->requiredCoeffects
; },
5326 [] (Method2 m
) { return &m
.finfo
->func
->requiredCoeffects
; },
5327 [] (MethodFamily fa
) {
5328 return fa
.family
->infoFor(fa
.regularOnly
)
5329 .m_static
->m_requiredCoeffects
.get_pointer();
5331 [] (MethodFamily2 fa
) {
5333 fa
.family
->infoFor(fa
.regularOnly
).m_requiredCoeffects
.get_pointer();
5335 [] (MethodOrMissing m
) { return &m
.finfo
->func
->requiredCoeffects
; },
5336 [] (MethodOrMissing2 m
) { return &m
.finfo
->func
->requiredCoeffects
; },
5337 [] (MissingFunc
) { return nullptr; },
5338 [] (MissingMethod
) { return nullptr; },
5339 [] (const Isect
& i
) {
5340 const RuntimeCoeffects
* coeffects
= nullptr;
5341 for (auto const ff
: i
.families
) {
5342 auto const& info
= *ff
->infoFor(i
.regularOnly
).m_static
;
5343 if (!info
.m_requiredCoeffects
) continue;
5344 assertx(IMPLIES(coeffects
, *coeffects
== *info
.m_requiredCoeffects
));
5345 if (!coeffects
) coeffects
= info
.m_requiredCoeffects
.get_pointer();
5349 [] (const Isect2
& i
) {
5350 const RuntimeCoeffects
* coeffects
= nullptr;
5351 for (auto const ff
: i
.families
) {
5352 auto const& info
= ff
->infoFor(i
.regularOnly
);
5353 if (!info
.m_requiredCoeffects
) continue;
5354 assertx(IMPLIES(coeffects
, *coeffects
== *info
.m_requiredCoeffects
));
5355 if (!coeffects
) coeffects
= info
.m_requiredCoeffects
.get_pointer();
5362 const CompactVector
<CoeffectRule
>* Func::coeffectRules() const {
5363 return match
<const CompactVector
<CoeffectRule
>*>(
5365 [] (FuncName
) { return nullptr; },
5366 [] (MethodName
) { return nullptr; },
5367 [] (Fun f
) { return &f
.finfo
->func
->coeffectRules
; },
5368 [] (Fun2 f
) { return &f
.finfo
->func
->coeffectRules
; },
5369 [] (Method m
) { return &m
.finfo
->func
->coeffectRules
; },
5370 [] (Method2 m
) { return &m
.finfo
->func
->coeffectRules
; },
5371 [] (MethodFamily fa
) {
5372 return fa
.family
->infoFor(fa
.regularOnly
)
5373 .m_static
->m_coeffectRules
.get_pointer();
5375 [] (MethodFamily2 fa
) {
5376 return fa
.family
->infoFor(fa
.regularOnly
).m_coeffectRules
.get_pointer();
5378 [] (MethodOrMissing m
) { return &m
.finfo
->func
->coeffectRules
; },
5379 [] (MethodOrMissing2 m
) { return &m
.finfo
->func
->coeffectRules
; },
5380 [] (MissingFunc
) { return nullptr; },
5381 [] (MissingMethod
) { return nullptr; },
5382 [] (const Isect
& i
) {
5383 const CompactVector
<CoeffectRule
>* coeffects
= nullptr;
5384 for (auto const ff
: i
.families
) {
5385 auto const& info
= *ff
->infoFor(i
.regularOnly
).m_static
;
5386 if (!info
.m_coeffectRules
) continue;
5390 std::is_permutation(
5393 begin(*info
.m_coeffectRules
),
5394 end(*info
.m_coeffectRules
)
5398 if (!coeffects
) coeffects
= info
.m_coeffectRules
.get_pointer();
5402 [] (const Isect2
& i
) {
5403 const CompactVector
<CoeffectRule
>* coeffects
= nullptr;
5404 for (auto const ff
: i
.families
) {
5405 auto const& info
= ff
->infoFor(i
.regularOnly
);
5406 if (!info
.m_coeffectRules
) continue;
5410 std::is_permutation(
5413 begin(*info
.m_coeffectRules
),
5414 end(*info
.m_coeffectRules
)
5418 if (!coeffects
) coeffects
= info
.m_coeffectRules
.get_pointer();
5425 TriBool
Func::supportsAsyncEagerReturn() const {
5426 return match
<TriBool
>(
5428 [] (FuncName
) { return TriBool::Maybe
; },
5429 [] (MethodName
) { return TriBool::Maybe
; },
5430 [] (Fun f
) { return yesOrNo(func_supports_AER(f
.finfo
->func
)); },
5431 [] (Fun2 f
) { return yesOrNo(func_supports_AER(f
.finfo
->func
)); },
5432 [] (Method m
) { return yesOrNo(func_supports_AER(m
.finfo
->func
)); },
5433 [] (Method2 m
) { return yesOrNo(func_supports_AER(m
.finfo
->func
)); },
5434 [] (MethodFamily fam
) {
5435 return fam
.family
->infoFor(fam
.regularOnly
).m_static
->m_supportsAER
;
5437 [] (MethodFamily2 fam
) {
5438 return fam
.family
->infoFor(fam
.regularOnly
).m_supportsAER
;
5440 [] (MethodOrMissing m
) {
5441 return yesOrNo(func_supports_AER(m
.finfo
->func
));
5443 [] (MethodOrMissing2 m
) {
5444 return yesOrNo(func_supports_AER(m
.finfo
->func
));
5446 [] (MissingFunc
) { return TriBool::No
; },
5447 [] (MissingMethod
) { return TriBool::No
; },
5448 [] (const Isect
& i
) {
5449 auto aer
= TriBool::Maybe
;
5450 for (auto const ff
: i
.families
) {
5451 auto const& info
= *ff
->infoFor(i
.regularOnly
).m_static
;
5452 if (info
.m_supportsAER
== TriBool::Maybe
) continue;
5453 assertx(IMPLIES(aer
!= TriBool::Maybe
, aer
== info
.m_supportsAER
));
5454 if (aer
== TriBool::Maybe
) aer
= info
.m_supportsAER
;
5458 [] (const Isect2
& i
) {
5459 auto aer
= TriBool::Maybe
;
5460 for (auto const ff
: i
.families
) {
5461 auto const& info
= ff
->infoFor(i
.regularOnly
);
5462 if (info
.m_supportsAER
== TriBool::Maybe
) continue;
5463 assertx(IMPLIES(aer
!= TriBool::Maybe
, aer
== info
.m_supportsAER
));
5464 if (aer
== TriBool::Maybe
) aer
= info
.m_supportsAER
;
5471 Optional
<uint32_t> Func::lookupNumInoutParams() const {
5472 return match
<Optional
<uint32_t>>(
5474 [] (FuncName s
) -> Optional
<uint32_t> { return std::nullopt
; },
5475 [] (MethodName s
) -> Optional
<uint32_t> { return std::nullopt
; },
5476 [] (Fun f
) { return func_num_inout(f
.finfo
->func
); },
5477 [] (Fun2 f
) { return func_num_inout(f
.finfo
->func
); },
5478 [] (Method m
) { return func_num_inout(m
.finfo
->func
); },
5479 [] (Method2 m
) { return func_num_inout(m
.finfo
->func
); },
5480 [] (MethodFamily fam
) {
5481 return fam
.family
->infoFor(fam
.regularOnly
).m_static
->m_numInOut
;
5483 [] (MethodFamily2 fam
) {
5484 return fam
.family
->infoFor(fam
.regularOnly
).m_numInOut
;
5486 [] (MethodOrMissing m
) { return func_num_inout(m
.finfo
->func
); },
5487 [] (MethodOrMissing2 m
) { return func_num_inout(m
.finfo
->func
); },
5488 [] (MissingFunc
) { return 0; },
5489 [] (MissingMethod
) { return 0; },
5490 [] (const Isect
& i
) {
5491 Optional
<uint32_t> numInOut
;
5492 for (auto const ff
: i
.families
) {
5493 auto const& info
= *ff
->infoFor(i
.regularOnly
).m_static
;
5494 if (!info
.m_numInOut
) continue;
5495 assertx(IMPLIES(numInOut
, *numInOut
== *info
.m_numInOut
));
5496 if (!numInOut
) numInOut
= info
.m_numInOut
;
5500 [] (const Isect2
& i
) {
5501 Optional
<uint32_t> numInOut
;
5502 for (auto const ff
: i
.families
) {
5503 auto const& info
= ff
->infoFor(i
.regularOnly
);
5504 if (!info
.m_numInOut
) continue;
5505 assertx(IMPLIES(numInOut
, *numInOut
== *info
.m_numInOut
));
5506 if (!numInOut
) numInOut
= info
.m_numInOut
;
5513 PrepKind
Func::lookupParamPrep(uint32_t paramId
) const {
5514 auto const fromFuncFamily
= [&] (FuncFamily
* ff
, bool regularOnly
) {
5515 auto const& info
= *ff
->infoFor(regularOnly
).m_static
;
5516 if (paramId
>= info
.m_paramPreps
.size()) {
5517 return PrepKind
{TriBool::No
, TriBool::No
};
5519 return info
.m_paramPreps
[paramId
];
5521 auto const fromFuncFamily2
= [&] (const FuncFamily2
* ff
, bool regularOnly
) {
5522 auto const& info
= ff
->infoFor(regularOnly
);
5523 if (paramId
>= info
.m_paramPreps
.size()) {
5524 return PrepKind
{TriBool::No
, TriBool::No
};
5526 return info
.m_paramPreps
[paramId
];
5529 return match
<PrepKind
>(
5531 [&] (FuncName s
) { return PrepKind
{TriBool::Maybe
, TriBool::Maybe
}; },
5532 [&] (MethodName s
) { return PrepKind
{TriBool::Maybe
, TriBool::Maybe
}; },
5533 [&] (Fun f
) { return func_param_prep(f
.finfo
->func
, paramId
); },
5534 [&] (Fun2 f
) { return func_param_prep(f
.finfo
->func
, paramId
); },
5535 [&] (Method m
) { return func_param_prep(m
.finfo
->func
, paramId
); },
5536 [&] (Method2 m
) { return func_param_prep(m
.finfo
->func
, paramId
); },
5537 [&] (MethodFamily f
) { return fromFuncFamily(f
.family
, f
.regularOnly
); },
5538 [&] (MethodFamily2 f
) { return fromFuncFamily2(f
.family
, f
.regularOnly
); },
5539 [&] (MethodOrMissing m
) { return func_param_prep(m
.finfo
->func
, paramId
); },
5540 [&] (MethodOrMissing2 m
) { return func_param_prep(m
.finfo
->func
, paramId
); },
5541 [&] (MissingFunc
) { return PrepKind
{TriBool::No
, TriBool::Yes
}; },
5542 [&] (MissingMethod
) { return PrepKind
{TriBool::No
, TriBool::Yes
}; },
5543 [&] (const Isect
& i
) {
5544 auto inOut
= TriBool::Maybe
;
5545 auto readonly
= TriBool::Maybe
;
5547 for (auto const ff
: i
.families
) {
5548 auto const prepKind
= fromFuncFamily(ff
, i
.regularOnly
);
5549 if (prepKind
.inOut
!= TriBool::Maybe
) {
5550 assertx(IMPLIES(inOut
!= TriBool::Maybe
, inOut
== prepKind
.inOut
));
5551 if (inOut
== TriBool::Maybe
) inOut
= prepKind
.inOut
;
5554 if (prepKind
.readonly
!= TriBool::Maybe
) {
5556 IMPLIES(readonly
!= TriBool::Maybe
, readonly
== prepKind
.readonly
)
5558 if (readonly
== TriBool::Maybe
) readonly
= prepKind
.readonly
;
5562 return PrepKind
{inOut
, readonly
};
5564 [&] (const Isect2
& i
) {
5565 auto inOut
= TriBool::Maybe
;
5566 auto readonly
= TriBool::Maybe
;
5568 for (auto const ff
: i
.families
) {
5569 auto const prepKind
= fromFuncFamily2(ff
, i
.regularOnly
);
5570 if (prepKind
.inOut
!= TriBool::Maybe
) {
5571 assertx(IMPLIES(inOut
!= TriBool::Maybe
, inOut
== prepKind
.inOut
));
5572 if (inOut
== TriBool::Maybe
) inOut
= prepKind
.inOut
;
5575 if (prepKind
.readonly
!= TriBool::Maybe
) {
5577 IMPLIES(readonly
!= TriBool::Maybe
, readonly
== prepKind
.readonly
)
5579 if (readonly
== TriBool::Maybe
) readonly
= prepKind
.readonly
;
5583 return PrepKind
{inOut
, readonly
};
5588 TriBool
Func::lookupReturnReadonly() const {
5589 return match
<TriBool
>(
5591 [] (FuncName
) { return TriBool::Maybe
; },
5592 [] (MethodName
) { return TriBool::Maybe
; },
5593 [] (Fun f
) { return yesOrNo(f
.finfo
->func
->isReadonlyReturn
); },
5594 [] (Fun2 f
) { return yesOrNo(f
.finfo
->func
->isReadonlyReturn
); },
5595 [] (Method m
) { return yesOrNo(m
.finfo
->func
->isReadonlyReturn
); },
5596 [] (Method2 m
) { return yesOrNo(m
.finfo
->func
->isReadonlyReturn
); },
5597 [] (MethodFamily fam
) {
5598 return fam
.family
->infoFor(fam
.regularOnly
).m_static
->m_isReadonlyReturn
;
5600 [] (MethodFamily2 fam
) {
5601 return fam
.family
->infoFor(fam
.regularOnly
).m_isReadonlyReturn
;
5603 [] (MethodOrMissing m
) { return yesOrNo(m
.finfo
->func
->isReadonlyReturn
); },
5604 [] (MethodOrMissing2 m
) { return yesOrNo(m
.finfo
->func
->isReadonlyReturn
); },
5605 [] (MissingFunc
) { return TriBool::No
; },
5606 [] (MissingMethod
) { return TriBool::No
; },
5607 [] (const Isect
& i
) {
5608 auto readOnly
= TriBool::Maybe
;
5609 for (auto const ff
: i
.families
) {
5610 auto const& info
= *ff
->infoFor(i
.regularOnly
).m_static
;
5611 if (info
.m_isReadonlyReturn
== TriBool::Maybe
) continue;
5612 assertx(IMPLIES(readOnly
!= TriBool::Maybe
,
5613 readOnly
== info
.m_isReadonlyReturn
));
5614 if (readOnly
== TriBool::Maybe
) readOnly
= info
.m_isReadonlyReturn
;
5618 [] (const Isect2
& i
) {
5619 auto readOnly
= TriBool::Maybe
;
5620 for (auto const ff
: i
.families
) {
5621 auto const& info
= ff
->infoFor(i
.regularOnly
);
5622 if (info
.m_isReadonlyReturn
== TriBool::Maybe
) continue;
5623 assertx(IMPLIES(readOnly
!= TriBool::Maybe
,
5624 readOnly
== info
.m_isReadonlyReturn
));
5625 if (readOnly
== TriBool::Maybe
) readOnly
= info
.m_isReadonlyReturn
;
5632 TriBool
Func::lookupReadonlyThis() const {
5633 return match
<TriBool
>(
5635 [] (FuncName s
) { return TriBool::Maybe
; },
5636 [] (MethodName s
) { return TriBool::Maybe
; },
5637 [] (Fun f
) { return yesOrNo(f
.finfo
->func
->isReadonlyThis
); },
5638 [] (Fun2 f
) { return yesOrNo(f
.finfo
->func
->isReadonlyThis
); },
5639 [] (Method m
) { return yesOrNo(m
.finfo
->func
->isReadonlyThis
); },
5640 [] (Method2 m
) { return yesOrNo(m
.finfo
->func
->isReadonlyThis
); },
5641 [] (MethodFamily fam
) {
5642 return fam
.family
->infoFor(fam
.regularOnly
).m_static
->m_isReadonlyThis
;
5644 [] (MethodFamily2 fam
) {
5645 return fam
.family
->infoFor(fam
.regularOnly
).m_isReadonlyThis
;
5647 [] (MethodOrMissing m
) { return yesOrNo(m
.finfo
->func
->isReadonlyThis
); },
5648 [] (MethodOrMissing2 m
) { return yesOrNo(m
.finfo
->func
->isReadonlyThis
); },
5649 [] (MissingFunc
) { return TriBool::No
; },
5650 [] (MissingMethod
) { return TriBool::No
; },
5651 [] (const Isect
& i
) {
5652 auto readOnly
= TriBool::Maybe
;
5653 for (auto const ff
: i
.families
) {
5654 auto const& info
= *ff
->infoFor(i
.regularOnly
).m_static
;
5655 if (info
.m_isReadonlyThis
== TriBool::Maybe
) continue;
5656 assertx(IMPLIES(readOnly
!= TriBool::Maybe
,
5657 readOnly
== info
.m_isReadonlyThis
));
5658 if (readOnly
== TriBool::Maybe
) readOnly
= info
.m_isReadonlyThis
;
5662 [] (const Isect2
& i
) {
5663 auto readOnly
= TriBool::Maybe
;
5664 for (auto const ff
: i
.families
) {
5665 auto const& info
= ff
->infoFor(i
.regularOnly
);
5666 if (info
.m_isReadonlyThis
== TriBool::Maybe
) continue;
5667 assertx(IMPLIES(readOnly
!= TriBool::Maybe
,
5668 readOnly
== info
.m_isReadonlyThis
));
5669 if (readOnly
== TriBool::Maybe
) readOnly
= info
.m_isReadonlyThis
;
5676 Optional
<SString
> Func::triviallyWrappedFunc() const {
5677 auto const check
= [](const php::Func
* func
) -> Optional
<SString
> {
5678 auto const it
= func
->userAttributes
.find(s_TrivialHHVMBuiltinWrapper
.get());
5679 if (it
== func
->userAttributes
.end()) return std::nullopt
;
5680 assertx(tvIsVec(it
->second
));
5681 auto const args
= it
->second
.m_data
.parr
;
5682 if (args
->size() != 1) return std::nullopt
;
5683 auto const wrappedFunc
= args
->at(int64_t{0});
5684 if (!tvIsString(wrappedFunc
)) return std::nullopt
;
5685 assertx(wrappedFunc
.m_data
.pstr
->isStatic());
5686 return wrappedFunc
.m_data
.pstr
;
5688 return match
<Optional
<SString
>>(
5690 [] (Func::FuncName
) { return std::nullopt
; },
5691 [] (Func::MethodName
) { return std::nullopt
; },
5692 [&] (Func::Fun f
) { return check(f
.finfo
->func
); },
5693 [&] (Func::Fun2 f
) { return check(f
.finfo
->func
); },
5694 [] (Func::Method
) { return std::nullopt
; },
5695 [] (Func::Method2
) { return std::nullopt
; },
5696 [] (Func::MethodFamily
) { return std::nullopt
; },
5697 [] (Func::MethodFamily2
) { return std::nullopt
; },
5698 [] (Func::MethodOrMissing
) { return std::nullopt
; },
5699 [] (Func::MethodOrMissing2
) { return std::nullopt
; },
5700 [] (Func::MissingFunc
) { return std::nullopt
; },
5701 [] (Func::MissingMethod
) { return std::nullopt
; },
5702 [] (const Func::Isect
&) { return std::nullopt
; },
5703 [] (const Func::Isect2
&) { return std::nullopt
; }
5707 std::string
show(const Func
& f
) {
5708 auto ret
= f
.name();
5711 [&] (Func::FuncName
) {},
5712 [&] (Func::MethodName
) {},
5713 [&] (Func::Fun
) { ret
+= "*"; },
5714 [&] (Func::Fun2
) { ret
+= "*"; },
5715 [&] (Func::Method
) { ret
+= "*"; },
5716 [&] (Func::Method2
) { ret
+= "*"; },
5717 [&] (Func::MethodFamily
) { ret
+= "+"; },
5718 [&] (Func::MethodFamily2
) { ret
+= "+"; },
5719 [&] (Func::MethodOrMissing
) { ret
+= "-"; },
5720 [&] (Func::MethodOrMissing2
) { ret
+= "-"; },
5721 [&] (Func::MissingFunc
) { ret
+= "!"; },
5722 [&] (Func::MissingMethod
) { ret
+= "!"; },
5723 [&] (const Func::Isect
&) { ret
+= "&"; },
5724 [&] (const Func::Isect2
&) { ret
+= "&"; }
5731 //////////////////////////////////////////////////////////////////////
5733 struct Index::IndexData
{
5734 explicit IndexData(Index
* index
) : m_index
{index
} {}
5735 IndexData(const IndexData
&) = delete;
5736 IndexData
& operator=(const IndexData
&) = delete;
5741 bool ever_frozen
{false};
5743 // If non-nullptr, log information about each pass into it.
5744 StructuredLogEntry
* sample
;
5747 std::unique_ptr
<TicketExecutor
> executor
;
5748 std::unique_ptr
<Client
> client
;
5749 DisposeCallback disposeClient
;
5751 // Global configeration, stored in extern-worker.
5752 std::unique_ptr
<CoroAsyncValue
<Ref
<Config
>>> configRef
;
5754 // Maps unit/class/func name to the extern-worker ref representing
5755 // php::Program data for that. Any associated bytecode is stored
5757 SStringToOneT
<UniquePtrRef
<php::Unit
>> unitRefs
;
5758 TSStringToOneT
<UniquePtrRef
<php::Class
>> classRefs
;
5759 FSStringToOneT
<UniquePtrRef
<php::Func
>> funcRefs
;
5761 // Maps class name to the extern-worker ref representing the class's
5762 // associated ClassInfo2. Only has entries for instantiable classes.
5763 TSStringToOneT
<UniquePtrRef
<ClassInfo2
>> classInfoRefs
;
5765 // Maps func name (global functions, not methods) to the
5766 // extern-worked ref representing the func's associated
5767 // FuncInfo2. The FuncInfo2 for methods are stored in their parent
5769 FSStringToOneT
<UniquePtrRef
<FuncInfo2
>> funcInfoRefs
;
5771 // Maps class/func names to the extern-worker ref representing the
5772 // bytecode for that class or (global) function. The bytecode of all
5773 // of a class' methods are stored together.
5774 TSStringToOneT
<UniquePtrRef
<php::ClassBytecode
>> classBytecodeRefs
;
5775 FSStringToOneT
<UniquePtrRef
<php::FuncBytecode
>> funcBytecodeRefs
;
5777 // Uninstantiable classes do not have ClassInfo2s, but their methods
5778 // still have FuncInfo2s. Since we don't have a ClassInfo2 to store
5779 // them on, we do it separately.
5780 TSStringToOneT
<UniquePtrRef
<MethodsWithoutCInfo
>> uninstantiableClsMethRefs
;
5782 // Func family entries representing all methods with a particular
5784 SStringToOneT
<FuncFamilyEntry
> nameOnlyMethodFamilies
;
5786 // Maps func-family ids to the func family group which contains the
5787 // func family with that id.
5788 hphp_fast_map
<FuncFamily2::Id
, Ref
<FuncFamilyGroup
>> funcFamilyRefs
;
5790 // Maps of functions and classes to the names of closures defined
5792 TSStringToOneT
<TSStringSet
> classToClosures
;
5793 FSStringToOneT
<TSStringSet
> funcToClosures
;
5795 // Maps entities to the unit they were declared in.
5796 TSStringToOneT
<SString
> classToUnit
;
5797 FSStringToOneT
<SString
> funcToUnit
;
5798 TSStringToOneT
<SString
> typeAliasToUnit
;
5799 // If bool is true, then the constant is "dynamic" and has an
5800 // associated 86cinit function.
5801 SStringToOneT
<std::pair
<SString
, bool>> constantToUnit
;
5803 // Maps a closure to it's declaring class or function.
5804 TSStringToOneT
<SString
> closureToClass
;
5805 TSStringToOneT
<SString
> closureToFunc
;
5807 // Maps a class to the classes which it has inherited class
5809 TSStringToOneT
<TSStringSet
> classToCnsBases
;
5811 // All the classes that have a 86*init function.
5812 TSStringSet classesWith86Inits
;
5813 // All the 86cinit functions for "dynamic" top-level constants.
5814 FSStringSet constantInitFuncs
;
5815 // All the units that have type-aliases within them.
5816 SStringSet unitsWithTypeAliases
;
5818 std::unique_ptr
<php::Program
> program
;
5820 TSStringToOneT
<php::Class
*> classes
;
5821 FSStringToOneT
<php::Func
*> funcs
;
5822 TSStringToOneT
<php::TypeAlias
*> typeAliases
;
5823 TSStringToOneT
<php::Class
*> enums
;
5824 SStringToOneT
<php::Constant
*> constants
;
5825 SStringToOneT
<php::Module
*> modules
;
5826 SStringToOneT
<php::Unit
*> units
;
5829 * Func families representing methods with a particular name (across
5832 struct MethodFamilyEntry
{
5833 FuncFamilyOrSingle m_all
;
5834 FuncFamilyOrSingle m_regular
;
5836 SStringToOneT
<MethodFamilyEntry
> methodFamilies
;
5838 // Map from each class to all the closures that are allocated in
5839 // functions of that class.
5842 CompactVector
<const php::Class
*>
5847 hphp_fast_set
<const php::Func
*>
5848 > classExtraMethodMap
;
5851 * Map from each class name to ClassInfo objects if one exists.
5853 * It may not exists if we would fatal when defining the class. That could
5854 * happen for if the inheritance is bad or __Sealed or other things.
5856 TSStringToOneT
<ClassInfo
*> classInfo
;
5859 * All the ClassInfos, stored in no particular order.
5861 std::vector
<std::unique_ptr
<ClassInfo
>> allClassInfos
;
5863 std::vector
<FuncInfo
> funcInfo
;
5864 std::atomic
<uint32_t> nextFuncId
{};
5866 // Private instance and static property types are stored separately
5867 // from ClassInfo, because you don't need to resolve a class to get
5876 > privateStaticPropInfo
;
5879 * Public static property information:
5882 // If this is true, we've seen mutations to public static
5883 // properties. Once this is true, it's no longer legal to report a
5884 // pessimistic static property set (unknown class and
5885 // property). Doing so is a monotonicity violation.
5886 bool seenPublicSPropMutations
{false};
5888 // The set of gathered public static property mutations for each function. The
5889 // inferred types for the public static properties is the union of all these
5890 // mutations. If a function is not analyzed in a particular analysis round,
5891 // its mutations are left unchanged from the previous round.
5892 folly_concurrent_hash_map_simd
<
5894 PublicSPropMutations
,
5895 pointer_hash
<const php::Func
>> publicSPropMutations
;
5897 // All FuncFamilies. These are stored globally so we can avoid
5898 // generating duplicates.
5899 folly_concurrent_hash_map_simd
<
5900 std::unique_ptr
<FuncFamily
>,
5902 FuncFamilyPtrHasher
,
5906 folly_concurrent_hash_map_simd
<
5907 std::unique_ptr
<FuncFamily::StaticInfo
>,
5909 FFStaticInfoPtrHasher
,
5910 FFStaticInfoPtrEquals
5911 > funcFamilyStaticInfos
;
5914 * Map from interfaces to their assigned vtable slots, computed in
5915 * compute_iface_vtables().
5917 TSStringToOneT
<Slot
> ifaceSlotMap
;
5924 struct ClsConstTypesHasher
{
5925 bool operator()(const std::pair
<const php::Class
*, SString
>& k
) const {
5926 return folly::hash::hash_combine(
5927 pointer_hash
<php::Class
>{}(k
.first
),
5928 pointer_hash
<StringData
>{}(k
.second
)
5932 struct ClsConstTypesEquals
{
5933 bool operator()(const std::pair
<const php::Class
*, SString
>& a
,
5934 const std::pair
<const php::Class
*, SString
>& b
) const {
5935 return a
.first
== b
.first
&& a
.second
== b
.second
;
5939 // Cache for lookup_class_constant
5940 folly_concurrent_hash_map_simd
<
5941 std::pair
<const php::Class
*, SString
>,
5942 ClsConstLookupResult
,
5943 ClsConstTypesHasher
,
5945 > clsConstLookupCache
;
5947 bool useClassDependencies
{};
5948 DepMap dependencyMap
;
5951 * If a function is effect-free when called with a particular set of
5952 * literal arguments, and produces a literal result, there will be
5953 * an entry here representing the type.
5955 * The map isn't just an optimization; we can't call
5956 * analyze_func_inline during the optimization phase, because the
5957 * bytecode could be modified while we do so.
5959 ContextRetTyMap foldableReturnTypeMap
;
5962 * Call-context sensitive return types are cached here. This is not
5965 * The reason we need to retain this information about the
5966 * calling-context-sensitive return types is that once the Index is
5967 * frozen (during the final optimization pass), calls to
5968 * lookup_return_type with a CallContext can't look at the bytecode
5969 * bodies of functions other than the calling function. So we need
5970 * to know what we determined the last time we were allowed to do
5971 * that so we can return it again.
5973 ContextRetTyMap contextualReturnTypes
{};
5976 //////////////////////////////////////////////////////////////////////
5978 namespace { struct DepTracker
; };
5980 struct AnalysisIndex::IndexData
{
5981 IndexData(AnalysisIndex
& index
,
5982 AnalysisWorklist
& worklist
,
5985 , worklist
{worklist
}
5986 , deps
{std::make_unique
<DepTracker
>(*this)}
5989 IndexData(const IndexData
&) = delete;
5990 IndexData
& operator=(const IndexData
&) = delete;
5992 AnalysisIndex
& index
;
5993 AnalysisWorklist
& worklist
;
5994 std::unique_ptr
<DepTracker
> deps
;
5996 // The names of classes/funcs/units which will be in the output of
5998 std::vector
<SString
> outClassNames
;
5999 std::vector
<SString
> outFuncNames
;
6000 std::vector
<SString
> outUnitNames
;
6002 // Maps names to various data-structures. This works for both normal
6003 // classes and closures.
6004 TSStringToOneT
<php::Class
*> classes
;
6005 TSStringToOneT
<ClassInfo2
*> cinfos
;
6006 TSStringToOneT
<MethodsWithoutCInfo
*> minfos
;
6008 FSStringToOneT
<php::Func
*> funcs
;
6009 FSStringToOneT
<FuncInfo2
*> finfos
;
6011 SStringToOneT
<php::Unit
*> units
;
6013 SStringToOneT
<std::pair
<php::Constant
*, php::Unit
*>> constants
;
6014 TSStringToOneT
<std::pair
<php::TypeAlias
*, php::Unit
*>> typeAliases
;
6016 std::vector
<FuncInfo2
*> finfosByIdx
;
6018 // "Owns" the actual data structures. Closures will not be on these
6019 // maps, as they are owned by another Class. Generally look ups
6020 // should use the other maps above.
6021 TSStringToOneT
<std::unique_ptr
<php::Class
>> allClasses
;
6022 TSStringToOneT
<std::unique_ptr
<ClassInfo2
>> allCInfos
;
6023 TSStringToOneT
<std::unique_ptr
<MethodsWithoutCInfo
>> allMInfos
;
6024 FSStringToOneT
<std::unique_ptr
<php::Func
>> allFuncs
;
6025 FSStringToOneT
<std::unique_ptr
<FuncInfo2
>> allFInfos
;
6026 SStringToOneT
<std::unique_ptr
<php::Unit
>> allUnits
;
6028 // Anything on these lists is known to definitely not exist.
6029 TSStringSet badClasses
;
6030 FSStringSet badFuncs
;
6031 SStringSet badConstants
;
6033 SStringSet dynamicConstants
;
6035 // AnalysisIndex maintains a stack of the contexts being analyzed
6036 // (we can have multiple because of inline interp).
6037 std::vector
<Context
> contexts
;
6039 // If we're currently resolving class type constants. This changes
6040 // how some of the dependencies are treated.
6041 bool inTypeCns
{false};
6043 size_t foldableInterpNestingLevel
{0};
6044 size_t contextualInterpNestingLevel
{0};
6046 // The bucket id which AnalysisScheduler assigned to this worker.
6049 // Once the index is frozen, no further updates to it are allowed
6050 // (will assert). We only gather dependencies when the index is
6057 //////////////////////////////////////////////////////////////////////
6061 //////////////////////////////////////////////////////////////////////
6063 // Obtain the current (most recently pushed) context. This corresponds
6064 // to the context currently being analyzed.
6065 const Context
& current_context(const AnalysisIndex::IndexData
& index
) {
6067 !index
.contexts
.empty(),
6068 "Accessing current context without any contexts active"
6070 return index
.contexts
.back();
6073 // Obtain the context to use for the purposes of dependency
6074 // tracking. This is the first context pushed. This differs from
6075 // current_context() because of inline interp. If we're inline
6076 // interp-ing a function, we still want to attribute the dependencies
6077 // to the context which started the inline interp.
6078 const Context
& context_for_deps(const AnalysisIndex::IndexData
& index
) {
6080 !index
.contexts
.empty(),
6081 "Accessing dependency context without any contexts active"
6083 return index
.contexts
.front();
6086 //////////////////////////////////////////////////////////////////////
6088 FuncClsUnit
fc_from_context(const Context
& ctx
,
6089 const AnalysisIndex::IndexData
& index
) {
6090 if (ctx
.cls
) return ctx
.cls
;
6091 if (ctx
.func
) return ctx
.func
;
6093 return &index
.index
.lookup_unit(ctx
.unit
);
6096 //////////////////////////////////////////////////////////////////////
6098 // Record the dependencies of all classes and functions being
6099 // processed with an AnalysisIndex. These dependencies will ultimately
6100 // be reported back to the master AnalysisScheduler, but will also
6101 // drive the worklist on the local analysis job.
6103 explicit DepTracker(const AnalysisIndex::IndexData
& index
)
6106 using Type
= AnalysisDeps::Type
;
6107 using Func
= AnalysisDeps::Func
;
6108 using Class
= AnalysisDeps::Class
;
6109 using Constant
= AnalysisDeps::Constant
;
6110 using AnyClassConstant
= AnalysisDeps::AnyClassConstant
;
6112 // Register dependencies on various entities to the current
6113 // dependency context.
6115 [[nodiscard
]] bool add(Class c
) {
6116 auto const fc
= context();
6117 auto const a
= allowed(fc
, c
, false);
6118 if (!index
.frozen
) return a
;
6119 if (auto const c2
= fc
.cls()) {
6120 if (c2
->name
->tsame(c
.name
)) return a
;
6123 if (d
.add(c
, index
.inTypeCns
)) {
6124 FTRACE(2, "{} now depends on class {}{}\n",
6125 HHBBC::show(fc
), c
.name
,
6126 index
.inTypeCns
? " (in type-cns)" : "");
6128 // Class either exists or not and won't change within the job, so
6129 // nothing to record for worklist.
6133 [[nodiscard
]] bool add(const php::Func
& f
, Type t
= Type::None
) {
6134 auto const fc
= context();
6135 assertx(!fc
.unit());
6136 auto const a
= f
.cls
6137 ? allowed(fc
, Class
{ f
.cls
->name
}, t
& Type::Bytecode
)
6138 : allowed(fc
, Func
{ f
.name
}, t
& Type::Bytecode
);
6140 if (auto const c
= fc
.cls()) {
6141 if (f
.cls
&& c
->name
->tsame(f
.cls
->name
)) return a
;
6142 } else if (auto const f2
= fc
.func()) {
6143 if (!f
.cls
&& f2
->name
->fsame(f
.name
)) return a
;
6146 if (auto const added
= deps
[fc
].add(f
, t
)) {
6148 2, "{} now depends on {}{} {}\n",
6149 HHBBC::show(fc
), displayAdded(added
),
6150 f
.cls
? "method" : "func",
6157 // Record dependency for worklist if anything can change within
6159 t
&= AnalysisDeps::kValidForChanges
;
6160 if (t
== Type::None
) return true;
6166 [[nodiscard
]] bool add(MethRef m
, Type t
= Type::None
) {
6167 auto const fc
= context();
6168 assertx(!fc
.unit());
6169 auto const a
= allowed(fc
, Class
{ m
.cls
}, t
& Type::Bytecode
);
6171 if (auto const c
= fc
.cls()) {
6172 if (c
->name
->tsame(m
.cls
)) return a
;
6174 if (auto const added
= deps
[fc
].add(m
, t
)) {
6175 FTRACE(2, "{} now depends on {}method {}\n",
6176 HHBBC::show(fc
), displayAdded(added
), display(m
));
6181 // Record dependency for worklist if anything can change within
6183 t
&= AnalysisDeps::kValidForChanges
;
6184 if (t
== Type::None
) return true;
6185 if (auto const p
= from(m
)) funcs
[p
][fc
] |= t
;
6190 [[nodiscard
]] bool add(Func f
, Type t
= Type::None
) {
6191 auto const fc
= context();
6192 assertx(!fc
.unit());
6193 auto const a
= allowed(fc
, f
, t
& Type::Bytecode
);
6195 if (auto const f2
= fc
.func()) {
6196 if (f2
->name
->fsame(f
.name
)) return a
;
6198 if (auto const added
= deps
[fc
].add(f
, t
)) {
6199 FTRACE(2, "{} now depends on {}func {}\n",
6200 HHBBC::show(fc
), displayAdded(added
), f
.name
);
6205 // Record dependency for worklist if anything can change within
6207 t
&= AnalysisDeps::kValidForChanges
;
6208 if (t
== Type::None
) return true;
6209 if (auto const p
= folly::get_default(index
.funcs
, f
.name
)) {
6216 [[nodiscard
]] bool add(ConstIndex cns
) {
6217 auto const fc
= context();
6218 auto const a
= allowed(fc
, Class
{ cns
.cls
}, false);
6220 if (auto const c
= fc
.cls()) {
6221 if (c
->name
->tsame(cns
.cls
)) return a
;
6223 if (deps
[fc
].add(cns
, index
.inTypeCns
)) {
6224 FTRACE(2, "{} now depends on class constant {}{}\n",
6225 HHBBC::show(fc
), display(cns
),
6226 index
.inTypeCns
? " (in type-cns)" : "");
6230 } else if (auto const p
= from(cns
)) {
6231 clsConstants
[p
].emplace(fc
);
6236 [[nodiscard
]] bool add(AnyClassConstant cns
) {
6237 auto const fc
= context();
6238 auto const a
= allowed(fc
, Class
{ cns
.name
}, false);
6240 if (auto const c
= fc
.cls()) {
6241 if (c
->name
->tsame(cns
.name
)) return a
;
6243 if (deps
[fc
].add(cns
, index
.inTypeCns
)) {
6244 FTRACE(2, "{} now depends on any class constant from {}{}\n",
6245 HHBBC::show(fc
), cns
.name
,
6246 index
.inTypeCns
? " (in type-cns)" : "");
6250 } else if (auto const cls
= folly::get_default(index
.classes
, cns
.name
)) {
6251 anyClsConstants
[cls
].emplace(fc
);
6256 [[nodiscard
]] bool add(Constant cns
) {
6257 auto const fc
= context();
6258 assertx(!fc
.unit());
6259 auto const a
= allowed(fc
, cns
);
6261 if (deps
[fc
].add(cns
)) {
6262 FTRACE(2, "{} now depends on constant {}\n", HHBBC::show(fc
), cns
.name
);
6266 } else if (auto const p
= folly::get_ptr(index
.constants
, cns
.name
)) {
6267 constants
[p
->first
].emplace(fc
);
6272 bool allowed(Class c
) const { return allowed(context(), c
, false); }
6274 // Mark that the given entity has changed in some way. This not only
6275 // results in the change being reported back to the
6276 // AnalysisScheduler, but will reschedule any work locally which has
6279 void update(const php::Func
& f
, Type t
) {
6280 if (t
== Type::None
) return;
6281 assertx(AnalysisDeps::isValidForChanges(t
));
6282 assertx(!index
.frozen
);
6284 2, "{} {} {} changed, scheduling\n",
6285 f
.cls
? "method" : "func",
6289 changes
.changed(f
, t
);
6290 schedule(folly::get_ptr(funcs
, &f
), t
);
6293 void update(const php::Const
& cns
, ConstIndex idx
) {
6294 assertx(!index
.frozen
);
6295 FTRACE(2, "constant {}::{} changed, scheduling\n", idx
.cls
, cns
.name
);
6296 changes
.changed(idx
);
6297 schedule(folly::get_ptr(clsConstants
, &cns
));
6298 if (auto const p
= folly::get_default(index
.classes
, idx
.cls
)) {
6299 schedule(folly::get_ptr(anyClsConstants
, p
));
6303 void update(const php::Constant
& cns
) {
6304 assertx(!index
.frozen
);
6305 FTRACE(2, "constant {} changed, scheduling\n", cns
.name
);
6306 changes
.changed(cns
);
6307 schedule(folly::get_ptr(constants
, &cns
));
6310 // Add pre-known dependencies directly.
6311 void preadd(FuncClsUnit fc
, Func f
, Type t
) {
6312 assertx(!index
.frozen
);
6313 assertx(!fc
.unit());
6314 if (t
== Type::None
) return;
6315 auto const p
= folly::get_default(index
.funcs
, f
.name
);
6320 void preadd(FuncClsUnit fc
, MethRef m
, Type t
) {
6321 assertx(!index
.frozen
);
6322 assertx(!fc
.unit());
6323 if (t
== Type::None
) return;
6324 if (auto const p
= from(m
)) funcs
[p
][fc
] |= t
;
6327 void preadd(FuncClsUnit fc
, ConstIndex cns
) {
6328 assertx(!index
.frozen
);
6329 if (auto const p
= from(cns
)) clsConstants
[p
].emplace(fc
);
6332 void preadd(FuncClsUnit fc
, AnyClassConstant cns
) {
6333 assertx(!index
.frozen
);
6334 auto const p
= folly::get_default(index
.classes
, cns
.name
);
6335 if (p
) anyClsConstants
[p
].emplace(fc
);
6338 void preadd(FuncClsUnit fc
, Constant cns
) {
6339 assertx(!index
.frozen
);
6340 assertx(!fc
.unit());
6341 auto const p
= folly::get_ptr(index
.constants
, cns
.name
);
6343 constants
[p
->first
].emplace(fc
);
6346 // Add bucket presence information for the given entities, which
6347 // will be used to perform permission checks.
6348 using BucketPresence
= AnalysisInput::BucketPresence
;
6350 void restrict(FuncClsUnit fc
, BucketPresence b
) {
6351 assertx(b
.present
->contains(index
.bucketIdx
));
6352 always_assert(allows
.emplace(fc
, std::move(b
)).second
);
6355 void restrict(Class c
, BucketPresence b
) {
6356 assertx(b
.present
->contains(index
.bucketIdx
));
6357 assertx(index
.badClasses
.count(c
.name
));
6358 always_assert(badClassAllows
.emplace(c
.name
, std::move(b
)).second
);
6361 void restrict(Func f
, BucketPresence b
) {
6362 assertx(b
.present
->contains(index
.bucketIdx
));
6363 assertx(index
.badFuncs
.count(f
.name
));
6364 always_assert(badFuncAllows
.emplace(f
.name
, std::move(b
)).second
);
6367 void restrict(Constant cns
, BucketPresence b
) {
6368 assertx(b
.present
->contains(index
.bucketIdx
));
6369 assertx(index
.badConstants
.count(cns
.name
));
6370 always_assert(badConstantAllows
.emplace(cns
.name
, std::move(b
)).second
);
6373 const BucketPresence
& bucketFor(FuncClsUnit fc
) const {
6374 auto const b
= folly::get_ptr(allows
, fc
);
6375 always_assert_flog(b
, "Unable to get bucket for {}", show(fc
));
6379 void reset(FuncClsUnit fc
) { deps
.erase(fc
); }
6381 AnalysisDeps
take(FuncClsUnit fc
) {
6382 auto it
= deps
.find(fc
);
6383 if (it
== end(deps
)) return AnalysisDeps
{};
6384 return std::move(it
->second
);
6387 AnalysisChangeSet
& getChanges() { return changes
; }
6388 const AnalysisChangeSet
& getChanges() const { return changes
; }
6391 // NB: One entity is allowed to use another entity's information if
6392 // the user's bucket presence is a subset of the usee's bucket
6393 // presence. This means the the usee is present in *all* the buckets
6394 // that the user is in, and therefore all workers will come to the
6397 bool allowed(FuncClsUnit fc
, Class c
, bool bytecode
) const {
6398 auto& cache
= bytecode
? allowCacheBC
: allowCache
;
6399 if (auto const b
= folly::get_ptr(cache
, fc
, c
.name
)) return *b
;
6401 auto const a
= folly::get_ptr(allows
, fc
);
6403 assertx(a
->process
->contains(index
.bucketIdx
));
6405 auto const b
= [&] {
6406 if (auto const cls
= folly::get_default(index
.classes
, c
.name
)) {
6407 auto const canon
= canonicalize(*cls
);
6408 if (auto const c2
= canon
.cls()) {
6409 auto const ca
= folly::get_ptr(allows
, c2
);
6411 assertx(ca
->present
->contains(index
.bucketIdx
));
6412 return a
->process
->isSubset(
6413 bytecode
? *ca
->withBC
: *ca
->present
6416 if (auto const f
= canon
.func()) {
6417 auto const fa
= folly::get_ptr(allows
, f
);
6419 assertx(fa
->present
->contains(index
.bucketIdx
));
6420 return a
->process
->isSubset(
6421 bytecode
? *fa
->withBC
: *fa
->present
6424 always_assert(false);
6426 if (auto const ta
= folly::get_ptr(index
.typeAliases
, c
.name
)) {
6427 auto const ua
= folly::get_ptr(allows
, ta
->second
);
6429 assertx(ua
->present
->contains(index
.bucketIdx
));
6430 return a
->process
->isSubset(*ua
->present
);
6432 if (index
.badClasses
.count(c
.name
)) {
6433 auto const ca
= folly::get_ptr(badClassAllows
, c
.name
);
6435 assertx(ca
->present
->contains(index
.bucketIdx
));
6436 return a
->process
->isSubset(*ca
->present
);
6440 cache
[fc
][c
.name
] = b
;
6444 bool allowed(FuncClsUnit fc
, Func f
, bool bytecode
) const {
6445 assertx(!fc
.unit());
6446 auto const a
= folly::get_ptr(allows
, fc
);
6448 assertx(a
->process
->contains(index
.bucketIdx
));
6449 if (auto const func
= folly::get_default(index
.funcs
, f
.name
)) {
6450 auto const fa
= folly::get_ptr(allows
, func
);
6452 assertx(fa
->present
->contains(index
.bucketIdx
));
6453 return a
->process
->isSubset(
6454 bytecode
? *fa
->withBC
: *fa
->present
6457 if (index
.badFuncs
.count(f
.name
)) {
6458 auto const fa
= folly::get_ptr(badFuncAllows
, f
.name
);
6460 assertx(fa
->present
->contains(index
.bucketIdx
));
6461 return a
->process
->isSubset(*fa
->present
);
6466 bool allowed(FuncClsUnit fc
, Constant cns
) const {
6467 assertx(!fc
.unit());
6468 auto const a
= folly::get_ptr(allows
, fc
);
6470 assertx(a
->process
->contains(index
.bucketIdx
));
6471 if (auto const c
= folly::get_ptr(index
.constants
, cns
.name
)) {
6472 auto const unit
= folly::get_default(index
.units
, c
->second
->filename
);
6474 auto const ua
= folly::get_ptr(allows
, unit
);
6476 assertx(ua
->present
->contains(index
.bucketIdx
));
6477 return a
->process
->isSubset(*ua
->present
);
6479 if (index
.badConstants
.count(cns
.name
)) {
6480 auto const ca
= folly::get_ptr(badConstantAllows
, cns
.name
);
6482 assertx(ca
->present
->contains(index
.bucketIdx
));
6483 return a
->process
->isSubset(*ca
->present
);
6488 // Return appropriate entity to attribute the dependency to. If
6489 // we're analyzing a function within a class, use the class. If it's
6490 // a top-level function, use that.
6491 FuncClsUnit
context() const {
6492 auto const fc
= fc_from_context(context_for_deps(index
), index
);
6493 if (auto const c
= fc
.cls()) return canonicalize(*c
);
6497 // If a class is a closure, the correct context might actually be a
6499 FuncClsUnit
canonicalize(const php::Class
& cls
) const {
6500 // If this is a closure, the context is the closure's declaring
6501 // class or function.
6502 if (cls
.closureContextCls
) {
6504 folly::get_default(index
.classes
, cls
.closureContextCls
);
6508 if (cls
.closureDeclFunc
) {
6509 auto const f
= folly::get_default(index
.funcs
, cls
.closureDeclFunc
);
6516 const php::Func
* from(MethRef m
) const {
6517 if (auto const cls
= folly::get_default(index
.classes
, m
.cls
)) {
6518 assertx(m
.idx
< cls
->methods
.size());
6519 return cls
->methods
[m
.idx
].get();
6524 const php::Const
* from(ConstIndex cns
) const {
6525 if (auto const cls
= folly::get_default(index
.classes
, cns
.cls
)) {
6526 assertx(cns
.idx
< cls
->constants
.size());
6527 return &cls
->constants
[cns
.idx
];
6532 std::string
display(MethRef m
) const {
6533 if (auto const p
= from(m
)) return func_fullname(*p
);
6537 std::string
display(ConstIndex cns
) const {
6538 if (auto const p
= from(cns
)) {
6539 return folly::sformat("{}::{}", p
->cls
, p
->name
);
6541 return show(cns
, AnalysisIndexAdaptor
{ index
.index
});
6544 static std::string
displayAdded(Type t
) {
6545 auto out
= show(t
- Type::Meta
);
6546 if (!out
.empty()) folly::format(&out
, " of ");
6550 using FuncClsUnitSet
=
6551 hphp_fast_set
<FuncClsUnit
, FuncClsUnitHasher
>;
6552 using FuncClsUnitToType
=
6553 hphp_fast_map
<FuncClsUnit
, Type
, FuncClsUnitHasher
>;
6555 void schedule(const FuncClsUnitSet
* fcs
) {
6556 if (!fcs
|| fcs
->empty()) return;
6557 TinyVector
<FuncClsUnit
, 4> v
;
6558 v
.insert(begin(*fcs
), end(*fcs
));
6562 void schedule(const FuncClsUnitToType
* fcs
, Type t
) {
6563 assertx(!(t
& Type::Meta
));
6564 if (!fcs
|| fcs
->empty()) return;
6565 TinyVector
<FuncClsUnit
, 4> v
;
6566 for (auto const [fc
, t2
] : *fcs
) {
6567 if (t
& t2
) v
.emplace_back(fc
);
6572 void addToWorklist(TinyVector
<FuncClsUnit
, 4>& fcs
) {
6573 if (fcs
.empty()) return;
6575 fcs
.begin(), fcs
.end(),
6576 [] (FuncClsUnit fc1
, FuncClsUnit fc2
) {
6577 if (auto const c1
= fc1
.cls()) {
6578 if (auto const c2
= fc2
.cls()) {
6579 return string_data_lt_type
{}(c1
->name
, c2
->name
);
6583 if (auto const f1
= fc1
.func()) {
6584 if (auto const f2
= fc2
.func()) {
6585 return string_data_lt_func
{}(f1
->name
, f2
->name
);
6589 if (auto const u1
= fc1
.unit()) {
6590 if (auto const u2
= fc2
.unit()) {
6591 return string_data_lt
{}(u1
->filename
, u2
->filename
);
6593 return !fc2
.cls() && !fc2
.func();
6599 for (auto const fc
: fcs
) index
.worklist
.schedule(fc
);
6602 const AnalysisIndex::IndexData
& index
;
6603 AnalysisChangeSet changes
;
6604 hphp_fast_map
<FuncClsUnit
, AnalysisDeps
, FuncClsUnitHasher
> deps
;
6606 hphp_fast_map
<FuncClsUnit
, BucketPresence
, FuncClsUnitHasher
> allows
;
6607 TSStringToOneT
<BucketPresence
> badClassAllows
;
6608 FSStringToOneT
<BucketPresence
> badFuncAllows
;
6609 SStringToOneT
<BucketPresence
> badConstantAllows
;
6611 hphp_fast_map
<const php::Func
*, FuncClsUnitToType
> funcs
;
6612 hphp_fast_map
<const php::Const
*, FuncClsUnitSet
> clsConstants
;
6613 hphp_fast_map
<const php::Constant
*, FuncClsUnitSet
> constants
;
6614 hphp_fast_map
<const php::Class
*, FuncClsUnitSet
> anyClsConstants
;
6616 // We do a lot of permission checks on the same items, so cache the
6618 mutable hphp_fast_map
<FuncClsUnit
, TSStringToOneT
<bool>,
6619 FuncClsUnitHasher
> allowCache
;
6620 mutable hphp_fast_map
<FuncClsUnit
, TSStringToOneT
<bool>,
6621 FuncClsUnitHasher
> allowCacheBC
;
6624 //////////////////////////////////////////////////////////////////////
6626 using IndexData
= Index::IndexData
;
6628 std::mutex closure_use_vars_mutex
;
6629 std::mutex private_propstate_mutex
;
6631 DependencyContext
make_dep(const php::Func
* func
) {
6632 return DependencyContext
{DependencyContextType::Func
, func
};
6634 DependencyContext
make_dep(const php::Class
* cls
) {
6635 return DependencyContext
{DependencyContextType::Class
, cls
};
6637 DependencyContext
make_dep(const php::Prop
* prop
) {
6638 return DependencyContext
{DependencyContextType::Prop
, prop
};
6640 DependencyContext
make_dep(const FuncFamily
* family
) {
6641 return DependencyContext
{DependencyContextType::FuncFamily
, family
};
6644 DependencyContext
dep_context(IndexData
& data
, const Context
& baseCtx
) {
6645 auto const& ctx
= baseCtx
.forDep();
6646 if (!ctx
.cls
|| !data
.useClassDependencies
) return make_dep(ctx
.func
);
6647 auto const cls
= ctx
.cls
->closureContextCls
6648 ? data
.classes
.at(ctx
.cls
->closureContextCls
)
6650 if (is_used_trait(*cls
)) return make_dep(ctx
.func
);
6651 return make_dep(cls
);
6654 template <typename T
>
6655 void add_dependency(IndexData
& data
,
6659 if (data
.frozen
) return;
6661 auto d
= dep_context(data
, dst
);
6662 DepMap::accessor acc
;
6663 data
.dependencyMap
.insert(acc
, make_dep(src
));
6664 auto& current
= acc
->second
[d
];
6665 current
= current
| newMask
;
6667 // We should only have a return type dependency on func families.
6670 acc
->first
.tag() == DependencyContextType::FuncFamily
,
6671 newMask
== Dep::ReturnTy
6676 template <typename T
>
6677 void find_deps(IndexData
& data
,
6680 DependencyContextSet
& deps
) {
6681 auto const srcDep
= make_dep(src
);
6683 // We should only ever have return type dependencies on func family.
6686 srcDep
.tag() == DependencyContextType::FuncFamily
,
6687 mask
== Dep::ReturnTy
6691 DepMap::const_accessor acc
;
6692 if (data
.dependencyMap
.find(acc
, srcDep
)) {
6693 for (auto const& kv
: acc
->second
) {
6694 if (has_dep(kv
.second
, mask
)) deps
.insert(kv
.first
);
6699 //////////////////////////////////////////////////////////////////////
6701 FuncInfo
* func_info(IndexData
& data
, const php::Func
* f
) {
6702 assertx(f
->idx
< data
.funcInfo
.size());
6703 auto const fi
= &data
.funcInfo
[f
->idx
];
6704 assertx(fi
->func
== f
);
6708 FuncInfo2
& func_info(AnalysisIndex::IndexData
& data
, const php::Func
& f
) {
6709 assertx(f
.idx
< data
.finfosByIdx
.size());
6710 auto const fi
= data
.finfosByIdx
[f
.idx
];
6711 assertx(fi
->func
== &f
);
6715 //////////////////////////////////////////////////////////////////////
6717 // Obtain the php::Func* represented by a MethRef.
6718 const php::Func
* func_from_meth_ref(const IndexData
& index
,
6719 const MethRef
& meth
) {
6720 auto const cls
= index
.classes
.at(meth
.cls
);
6721 assertx(meth
.idx
< cls
->methods
.size());
6722 return cls
->methods
[meth
.idx
].get();
6725 const php::Func
* func_from_meth_ref(const AnalysisIndex::IndexData
& index
,
6726 const MethRef
& meth
) {
6727 if (!index
.deps
->add(AnalysisDeps::Class
{ meth
.cls
})) return nullptr;
6728 auto const cls
= folly::get_default(index
.classes
, meth
.cls
);
6731 !index
.badClasses
.count(meth
.cls
),
6732 "MethRef references non-existent class {}\n",
6737 assertx(meth
.idx
< cls
->methods
.size());
6738 return cls
->methods
[meth
.idx
].get();
6741 //////////////////////////////////////////////////////////////////////
6745 //////////////////////////////////////////////////////////////////////
6747 // Defined here so that AnalysisIndex::IndexData is a complete type.
6749 bool ClassGraph::storeAuxs(AnalysisIndex::IndexData
& i
, bool children
) const {
6752 auto const add
= [&] (AuxClassGraphs
& auxs
) {
6754 if (!auxs
.newWithChildren
.emplace(*this).second
) return false;
6755 auxs
.newNoChildren
.erase(*this);
6758 if (auxs
.newWithChildren
.count(*this)) return false;
6759 return auxs
.newNoChildren
.emplace(*this).second
;
6763 // Get the current context and store this ClassGraph on it's aux
6765 auto const fc
= fc_from_context(context_for_deps(i
), i
);
6766 if (auto const c
= fc
.cls()) {
6767 if (!c
->cinfo
) return false;
6768 if (c
->cinfo
== cinfo2()) return true;
6769 if (add(c
->cinfo
->auxClassGraphs
)) {
6771 2, "{} now stores {} as an auxiliary ClassGraph{}\n",
6773 children
? " (with children)" : ""
6777 } else if (auto const f
= fc
.func()) {
6778 auto& fi
= func_info(i
, *f
);
6779 if (!fi
.auxClassGraphs
) {
6780 fi
.auxClassGraphs
= std::make_unique
<AuxClassGraphs
>();
6782 if (add(*fi
.auxClassGraphs
)) {
6784 2, "{} now stores {} as an auxiliary ClassGraph{}\n",
6786 children
? " (with children)" : ""
6795 bool ClassGraph::onAuxs(AnalysisIndex::IndexData
& i
, bool children
) const {
6796 // Check if this ClassGraph is on the current context's aux set *or*
6797 // if it is implied by another ClassGraph on the aux set (for
6798 // example, if this ClassGraph is a parent of a ClassGraph already
6800 auto const check
= [&] (const AuxClassGraphs
& auxs
, Node
* target
) {
6801 assertx(!target
|| !target
->isMissing());
6803 if (target
== this_
) return true;
6804 // Check for direct membership first
6805 if (auxs
.withChildren
.count(*this)) return true;
6806 if (auxs
.noChildren
.count(*this)) return !children
;
6807 if (this_
->isMissing()) return false;
6809 // Check if any parents of this Node are on the set.
6810 if (!auxs
.withChildren
.empty()) {
6811 auto const a
= forEachParent(
6814 if (target
== &p
) return Action::Stop
;
6815 return auxs
.withChildren
.count(ClassGraph
{ &p
})
6820 if (a
== Action::Stop
) return true;
6823 if (children
) return false;
6825 TLNodeIdxSet visited
;
6826 for (auto const n
: auxs
.noChildren
) {
6827 if (n
.this_
->isMissing()) continue;
6828 if (findParent(*n
.this_
, *this_
, *visited
)) return true;
6830 for (auto const n
: auxs
.withChildren
) {
6831 if (n
.this_
->isMissing()) continue;
6832 if (findParent(*n
.this_
, *this_
, *visited
)) return true;
6834 if (target
&& findParent(*target
, *this_
, *visited
)) return true;
6839 auto const fc
= fc_from_context(context_for_deps(i
), i
);
6840 if (auto const c
= fc
.cls()) {
6841 if (!c
->cinfo
) return false;
6842 if (c
->cinfo
== cinfo2()) return true;
6843 return check(c
->cinfo
->auxClassGraphs
, c
->cinfo
->classGraph
.this_
);
6846 if (auto const f
= fc
.func()) {
6847 auto const& fi
= func_info(i
, *f
);
6848 if (!fi
.auxClassGraphs
) return false;
6849 return check(*fi
.auxClassGraphs
, nullptr);
6855 // Ensure ClassGraph is not missing
6856 bool ClassGraph::ensure() const {
6858 auto const i
= table().index
;
6859 if (!i
) return true;
6860 if (onAuxs(*i
, false)) {
6861 if (i
->frozen
) always_assert(storeAuxs(*i
, false));
6863 } else if (i
->deps
->allowed(AnalysisDeps::Class
{ name() })) {
6864 if (this_
->isMissing()) {
6865 always_assert(i
->deps
->add(AnalysisDeps::Class
{ name() }));
6867 if (!i
->frozen
) return true;
6868 if (!storeAuxs(*i
, false)) {
6869 (void)i
->deps
->add(AnalysisDeps::Class
{ name() });
6873 (void)i
->deps
->add(AnalysisDeps::Class
{ name() });
6878 // Ensure ClassGraph is not missing and has complete child
6880 bool ClassGraph::ensureWithChildren() const {
6882 auto const i
= table().index
;
6883 if (!i
) return true;
6884 if (onAuxs(*i
, true)) {
6885 if (i
->frozen
) always_assert(storeAuxs(*i
, true));
6887 } else if (i
->deps
->allowed(AnalysisDeps::Class
{ name() })) {
6888 if (this_
->isMissing() ||
6889 (!this_
->hasCompleteChildren() && !this_
->isConservative())) {
6890 always_assert(i
->deps
->add(AnalysisDeps::Class
{ name() }));
6892 if (!i
->frozen
) return true;
6893 if (!storeAuxs(*i
, true)) {
6894 (void)i
->deps
->add(AnalysisDeps::Class
{ name() });
6898 (void)i
->deps
->add(AnalysisDeps::Class
{ name() });
6903 // Ensure ClassGraph is not missing and has an associated ClassInfo2
6904 // (strongest condition).
6905 bool ClassGraph::ensureCInfo() const {
6906 auto const i
= table().index
;
6907 return !i
|| i
->deps
->add(AnalysisDeps::Class
{ name() });
6910 bool ClassGraph::allowed(bool children
) const {
6911 auto const i
= table().index
;
6912 return !i
|| onAuxs(*i
, children
) ||
6913 i
->deps
->allowed(AnalysisDeps::Class
{ name() });
6916 //////////////////////////////////////////////////////////////////////
6920 //////////////////////////////////////////////////////////////////////
6922 struct TraitMethod
{
6923 using class_type
= std::pair
<const ClassInfo2
*, const php::Class
*>;
6924 using method_type
= const php::Func
*;
6925 using origin_type
= SString
;
6927 TraitMethod(class_type trait_
, method_type method_
, Attr modifiers_
)
6930 , modifiers(modifiers_
)
6939 using string_type
= LSString
;
6940 using class_type
= TraitMethod::class_type
;
6941 using method_type
= TraitMethod::method_type
;
6942 using origin_type
= TraitMethod::origin_type
;
6944 struct TMIException
: std::exception
{
6945 explicit TMIException(std::string msg
) : msg(msg
) {}
6946 const char* what() const noexcept override
{ return msg
.c_str(); }
6951 // Return the name for the trait class.
6952 static const string_type
clsName(class_type traitCls
) {
6953 return traitCls
.first
->name
;
6956 // Return the name of the trait where the method was originally defined
6957 static origin_type
originalClass(method_type meth
) {
6958 return meth
->originalClass
? meth
->originalClass
: meth
->cls
->name
;
6962 static bool isAbstract(Attr modifiers
) {
6963 return modifiers
& AttrAbstract
;
6966 // Whether to exclude methods with name `methName' when adding.
6967 static bool exclude(string_type methName
) {
6968 return Func::isSpecial(methName
);
6972 static void errorDuplicateMethod(class_type cls
,
6973 string_type methName
,
6974 const std::vector
<const StringData
*>&) {
6975 auto const& m
= cls
.second
->methods
;
6976 if (std::find_if(m
.begin(), m
.end(),
6977 [&] (auto const& f
) {
6978 return f
->name
->same(methName
);
6980 // the duplicate methods will be overridden by the class method.
6983 throw TMIException(folly::sformat("DuplicateMethod: {}", methName
));
6987 using TMIData
= TraitMethodImportData
<TraitMethod
, TMIOps
>;
6989 //////////////////////////////////////////////////////////////////////
6991 template <typename T
, typename R
>
6992 void add_symbol_to_index(R
& map
, T t
, const char* type
) {
6993 auto const name
= t
->name
;
6994 auto const ret
= map
.emplace(name
, std::move(t
));
6997 "More than one {} with the name {} "
6998 "(should have been caught by parser)",
7004 template <typename T
, typename R
, typename E
>
7005 void add_symbol_to_index(R
& map
, T t
, const char* type
, const E
& other
) {
7006 auto const it
= other
.find(t
->name
);
7009 "More than one symbol with the name {} "
7010 "(should have been caught by parser)",
7013 add_symbol_to_index(map
, std::move(t
), type
);
7016 // We want const qualifiers on various index data structures for php
7017 // object pointers, but during index creation time we need to
7018 // manipulate some of their attributes (changing the representation).
7019 // This little wrapper keeps the const_casting out of the main line of
7021 void attribute_setter(const Attr
& attrs
, bool set
, Attr attr
) {
7022 attrSetter(const_cast<Attr
&>(attrs
), set
, attr
);
7025 void add_system_constants_to_index(IndexData
& index
) {
7026 for (auto cnsPair
: Native::getConstants()) {
7027 assertx(cnsPair
.second
.m_type
!= KindOfUninit
);
7028 auto pc
= new php::Constant
{
7033 add_symbol_to_index(index
.constants
, pc
, "constant");
7037 void add_unit_to_index(IndexData
& index
, php::Unit
& unit
) {
7039 index
.units
.emplace(unit
.filename
, &unit
).second
,
7040 "More than one unit with the same name {} "
7041 "(should have been caught by parser)",
7045 for (auto& ta
: unit
.typeAliases
) {
7046 add_symbol_to_index(
7054 for (auto& c
: unit
.constants
) {
7055 add_symbol_to_index(index
.constants
, c
.get(), "constant");
7058 for (auto& m
: unit
.modules
) {
7059 add_symbol_to_index(index
.modules
, m
.get(), "module");
7063 void add_class_to_index(IndexData
& index
, php::Class
& c
) {
7064 if (c
.attrs
& AttrEnum
) {
7065 add_symbol_to_index(index
.enums
, &c
, "enum");
7068 add_symbol_to_index(index
.classes
, &c
, "class", index
.typeAliases
);
7070 for (auto& m
: c
.methods
) {
7071 attribute_setter(m
->attrs
, false, AttrNoOverride
);
7072 m
->idx
= index
.nextFuncId
++;
7076 void add_func_to_index(IndexData
& index
, php::Func
& func
) {
7077 add_symbol_to_index(index
.funcs
, &func
, "function");
7078 func
.idx
= index
.nextFuncId
++;
7081 void add_program_to_index(IndexData
& index
) {
7082 trace_time timer
{"add program to index", index
.sample
};
7083 timer
.ignore_client_stats();
7085 auto& program
= *index
.program
;
7086 for (auto const& u
: program
.units
) {
7087 add_unit_to_index(index
, *u
);
7089 for (auto const& c
: program
.classes
) {
7090 add_class_to_index(index
, *c
);
7091 for (auto const& clo
: c
->closures
) {
7092 add_class_to_index(index
, *clo
);
7095 for (auto const& f
: program
.funcs
) {
7096 add_func_to_index(index
, *f
);
7099 for (auto const& c
: program
.classes
) {
7100 assertx(!c
->closureContextCls
);
7101 for (auto const& clo
: c
->closures
) {
7102 assertx(clo
->closureContextCls
);
7103 auto& s
= index
.classClosureMap
[index
.classes
.at(clo
->closureContextCls
)];
7104 s
.emplace_back(clo
.get());
7108 // All funcs have been assigned indices above. Now for each func we
7109 // initialize a default FuncInfo in the funcInfo vec at the
7110 // appropriate index.
7112 trace_time timer2
{"create func-infos"};
7113 timer2
.ignore_client_stats();
7115 index
.funcInfo
.resize(index
.nextFuncId
);
7117 auto const create
= [&] (const php::Func
& f
) {
7118 assertx(f
.idx
< index
.funcInfo
.size());
7119 auto& fi
= index
.funcInfo
[f
.idx
];
7126 [&] (const std::unique_ptr
<php::Class
>& cls
) {
7127 for (auto const& m
: cls
->methods
) create(*m
);
7128 for (auto const& clo
: cls
->closures
) {
7129 assertx(clo
->methods
.size() == 1);
7130 create(*clo
->methods
[0]);
7137 [&] (const std::unique_ptr
<php::Func
>& func
) { create(*func
); }
7141 //////////////////////////////////////////////////////////////////////
7144 * Lists of interfaces that conflict with each other due to being
7145 * implemented by the same class.
7148 struct InterfaceConflicts
{
7149 SString name
{nullptr};
7150 // The number of classes which implements this interface (used to
7151 // prioritize lower slots for more heavily used interfaces).
7153 TSStringSet conflicts
;
7154 template <typename SerDe
> void serde(SerDe
& sd
) {
7155 sd(name
)(usage
)(conflicts
, string_data_lt_type
{});
7159 void compute_iface_vtables(IndexData
& index
,
7160 std::vector
<InterfaceConflicts
> conflicts
) {
7161 trace_time tracer
{"compute interface vtables"};
7162 tracer
.ignore_client_stats();
7164 if (conflicts
.empty()) return;
7166 // Sort interfaces by usage frequencies. We assign slots greedily,
7167 // so sort the interface list so the most frequently implemented
7172 [&] (const InterfaceConflicts
& a
, const InterfaceConflicts
& b
) {
7173 if (a
.usage
!= b
.usage
) return a
.usage
> b
.usage
;
7174 return string_data_lt_type
{}(a
.name
, b
.name
);
7178 // Assign slots, keeping track of the largest assigned slot and the
7179 // total number of uses for each slot.
7182 hphp_fast_map
<Slot
, int> slotUses
;
7183 boost::dynamic_bitset
<> used
;
7185 for (auto const& iface
: conflicts
) {
7188 // Find the lowest Slot that doesn't conflict with anything in the
7189 // conflict set for iface.
7190 auto const slot
= [&] () -> Slot
{
7191 // No conflicts. This is the only interface implemented by the
7192 // classes that implement it.
7193 if (iface
.conflicts
.empty()) return 0;
7195 for (auto const conflict
: iface
.conflicts
) {
7196 auto const it
= index
.ifaceSlotMap
.find(conflict
);
7197 if (it
== end(index
.ifaceSlotMap
)) continue;
7198 auto const s
= it
->second
;
7199 if (used
.size() <= s
) used
.resize(s
+ 1);
7204 return used
.any() ? used
.find_first() : used
.size();
7208 index
.ifaceSlotMap
.emplace(iface
.name
, slot
).second
7210 maxSlot
= std::max(maxSlot
, slot
);
7211 slotUses
[slot
] += iface
.usage
;
7215 // Make sure we have an initialized entry for each slot for the sort below.
7216 for (Slot slot
= 0; slot
< maxSlot
; ++slot
) {
7217 always_assert(slotUses
.count(slot
));
7221 // Finally, sort and reassign slots so the most frequently used
7222 // slots come first. This slightly reduces the number of wasted
7223 // vtable vector entries at runtime.
7225 auto const slots
= [&] {
7226 std::vector
<std::pair
<Slot
, int>> flattened
{
7227 begin(slotUses
), end(slotUses
)
7232 [&] (auto const& a
, auto const& b
) {
7233 if (a
.second
!= b
.second
) return a
.second
> b
.second
;
7234 return a
.first
< b
.first
;
7237 std::vector
<Slot
> out
;
7238 out
.reserve(flattened
.size());
7239 for (auto const& [slot
, _
] : flattened
) out
.emplace_back(slot
);
7243 std::vector
<Slot
> slotsPermute(maxSlot
+ 1, 0);
7244 for (size_t i
= 0; i
<= maxSlot
; ++i
) slotsPermute
[slots
[i
]] = i
;
7246 // re-map interfaces to permuted slots
7247 for (auto& [cls
, slot
] : index
.ifaceSlotMap
) {
7248 slot
= slotsPermute
[slot
];
7252 //////////////////////////////////////////////////////////////////////
7254 struct CheckClassInfoInvariantsJob
{
7255 static std::string
name() { return "hhbbc-check-cinfo-invariants"; }
7256 static void init(const Config
& config
) {
7257 process_init(config
.o
, config
.gd
, false);
7260 static void fini() { ClassGraph::destroy(); }
7262 static bool run(std::unique_ptr
<ClassInfo2
> cinfo
,
7263 std::unique_ptr
<php::Class
> cls
) {
7264 SCOPE_ASSERT_DETAIL("class") { return cls
->name
->toCppString(); };
7266 always_assert(check(*cls
, false));
7268 auto const check
= [] (const ClassInfo2
* cinfo
,
7269 const php::Class
* cls
) {
7270 // ClassGraph stored in a ClassInfo should not be missing, always
7271 // have the ClassInfo stored, and have complete children
7273 always_assert(cinfo
->classGraph
);
7274 always_assert(!cinfo
->classGraph
.isMissing());
7275 always_assert(cinfo
->classGraph
.name()->tsame(cinfo
->name
));
7276 always_assert(cinfo
->classGraph
.cinfo2() == cinfo
);
7277 if (is_closure_base(cinfo
->name
)) {
7278 // The closure base class is special. We don't store it's
7279 // children information because it's too large.
7280 always_assert(!cinfo
->classGraph
.hasCompleteChildren());
7281 always_assert(cinfo
->classGraph
.isConservative());
7283 always_assert(cinfo
->classGraph
.hasCompleteChildren() ||
7284 cinfo
->classGraph
.isConservative());
7287 // This class and withoutNonRegular should be equivalent when
7288 // ignoring non-regular classes. The withoutNonRegular class
7289 // should be a fixed-point.
7290 if (auto const without
= cinfo
->classGraph
.withoutNonRegular()) {
7291 always_assert(without
.hasCompleteChildren() ||
7292 without
.isConservative());
7293 always_assert(without
.subSubtypeOf(cinfo
->classGraph
, false, false));
7294 always_assert(cinfo
->classGraph
.subSubtypeOf(without
, false, false));
7295 always_assert(without
.withoutNonRegular() == without
);
7296 always_assert(cinfo
->classGraph
.mightBeRegular() ||
7297 cinfo
->classGraph
.mightHaveRegularSubclass());
7298 always_assert(IMPLIES(cinfo
->classGraph
.mightBeRegular(),
7299 without
== cinfo
->classGraph
));
7300 } else if (!is_used_trait(*cls
)) {
7301 always_assert(!cinfo
->classGraph
.mightBeRegular());
7302 always_assert(!cinfo
->classGraph
.mightHaveRegularSubclass());
7305 // AttrNoOverride is a superset of AttrNoOverrideRegular
7307 IMPLIES(!(cls
->attrs
& AttrNoOverrideRegular
),
7308 !(cls
->attrs
& AttrNoOverride
))
7311 // Override attrs and what we know about the subclasses should be in
7313 if (cls
->attrs
& AttrNoOverride
) {
7314 always_assert(!cinfo
->classGraph
.mightHaveRegularSubclass());
7315 always_assert(!cinfo
->classGraph
.mightHaveNonRegularSubclass());
7316 } else if (cls
->attrs
& AttrNoOverrideRegular
) {
7317 always_assert(!cinfo
->classGraph
.mightHaveRegularSubclass());
7318 always_assert(cinfo
->classGraph
.mightHaveNonRegularSubclass());
7321 // Make sure the information stored on the ClassInfo matches that
7322 // which the ClassGraph reports.
7323 if (cls
->attrs
& AttrNoMock
) {
7324 always_assert(!cinfo
->isMocked
);
7325 always_assert(!cinfo
->isSubMocked
);
7327 always_assert(cinfo
->isSubMocked
);
7331 bool(cls
->attrs
& AttrNoExpandTrait
) ==
7332 cinfo
->classGraph
.usedTraits().empty()
7335 for (auto const& [name
, mte
] : cinfo
->methods
) {
7336 // Interface method tables should only contain its own methods.
7337 if (cls
->attrs
& AttrInterface
) {
7338 always_assert(mte
.meth().cls
->tsame(cinfo
->name
));
7341 // AttrNoOverride implies noOverrideRegular
7342 always_assert(IMPLIES(mte
.attrs
& AttrNoOverride
, mte
.noOverrideRegular()));
7344 if (!is_special_method_name(name
)) {
7345 // If the class isn't overridden, none of it's methods can be
7347 always_assert(IMPLIES(cls
->attrs
& AttrNoOverride
,
7348 mte
.attrs
& AttrNoOverride
));
7350 always_assert(!(mte
.attrs
& AttrNoOverride
));
7351 always_assert(!mte
.noOverrideRegular());
7354 if (cinfo
->name
->tsame(s_Closure
.get()) || is_closure_name(cinfo
->name
)) {
7355 always_assert(mte
.attrs
& AttrNoOverride
);
7358 // Don't store method families for special methods.
7359 auto const famIt
= cinfo
->methodFamilies
.find(name
);
7360 if (is_special_method_name(name
)) {
7361 always_assert(famIt
== end(cinfo
->methodFamilies
));
7364 if (famIt
== end(cinfo
->methodFamilies
)) {
7365 always_assert(is_closure_name(cinfo
->name
));
7368 auto const& entry
= famIt
->second
;
7370 // No override methods should always have a single entry.
7371 if (mte
.attrs
& AttrNoOverride
) {
7373 boost::get
<FuncFamilyEntry::BothSingle
>(&entry
.m_meths
) ||
7374 boost::get
<FuncFamilyEntry::SingleAndNone
>(&entry
.m_meths
)
7379 if (cinfo
->isRegularClass
) {
7380 // "all" should only be a func family. It can't be empty,
7381 // because we know there's at least one method in it (the one in
7382 // cinfo->methods). It can't be a single func, because one of
7383 // the methods must be the cinfo->methods method, and we know it
7384 // isn't AttrNoOverride, so there *must* be another method. So,
7385 // it must be a func family.
7387 boost::get
<FuncFamilyEntry::BothFF
>(&entry
.m_meths
) ||
7388 boost::get
<FuncFamilyEntry::FFAndSingle
>(&entry
.m_meths
)
7390 // This is a regular class, so we cannot have an incomplete
7391 // entry (can only happen with interfaces).
7392 always_assert(!entry
.m_regularIncomplete
);
7396 // If the class is marked as having not having bad initial prop
7397 // values, all of it's properties should have
7398 // AttrInitialSatisfiesTC set. Likewise, if it is, at least one
7399 // property should not have it set.
7400 if (!cinfo
->hasBadInitialPropValues
) {
7401 auto const all
= std::all_of(
7402 begin(cls
->properties
),
7403 end(cls
->properties
),
7404 [] (const php::Prop
& p
) {
7405 return p
.attrs
& AttrInitialSatisfiesTC
;
7410 auto const someBad
= std::any_of(
7411 begin(cls
->properties
),
7412 end(cls
->properties
),
7413 [] (const php::Prop
& p
) {
7414 return !(p
.attrs
& AttrInitialSatisfiesTC
);
7417 always_assert(someBad
);
7420 if (is_closure_name(cinfo
->name
)) {
7421 assertx(cinfo
->classGraph
.hasCompleteChildren());
7422 // Closures have no children.
7423 auto const subclasses
= cinfo
->classGraph
.children();
7424 always_assert(subclasses
.size() == 1);
7425 always_assert(subclasses
[0].name()->tsame(cinfo
->name
));
7426 } else if (cinfo
->classGraph
.hasCompleteChildren()) {
7427 // Otherwise the children list is non-empty, contains this
7428 // class, and contains only unique elements.
7429 auto const subclasses
= cinfo
->classGraph
.children();
7434 [&] (ClassGraph g
) { return g
.name()->tsame(cinfo
->name
); }
7435 ) != end(subclasses
)
7437 auto cpy
= subclasses
;
7438 std::sort(begin(cpy
), end(cpy
));
7439 cpy
.erase(std::unique(begin(cpy
), end(cpy
)), end(cpy
));
7440 always_assert(cpy
.size() == subclasses
.size());
7443 // The base list is non-empty, and the last element is this class.
7444 auto const bases
= cinfo
->classGraph
.bases();
7445 always_assert(!bases
.empty());
7446 always_assert(cinfo
->classGraph
== bases
.back());
7447 if (is_closure_base(cinfo
->name
)) {
7448 always_assert(bases
.size() == 1);
7449 } else if (is_closure_name(cinfo
->name
)) {
7450 always_assert(bases
.size() == 2);
7451 always_assert(bases
[0].name()->tsame(s_Closure
.get()));
7454 always_assert(IMPLIES(is_closure(*cls
), cls
->closures
.empty()));
7455 always_assert(cls
->closures
.size() == cinfo
->closures
.size());
7458 check(cinfo
.get(), cls
.get());
7459 for (size_t i
= 0, size
= cls
->closures
.size(); i
< size
; ++i
) {
7460 always_assert(cls
->closures
[i
]->name
->tsame(cinfo
->closures
[i
]->name
));
7461 always_assert(is_closure(*cls
->closures
[i
]));
7462 check(cinfo
->closures
[i
].get(), cls
->closures
[i
].get());
7469 struct CheckFuncFamilyInvariantsJob
{
7470 static std::string
name() { return "hhbbc-check-ff-invariants"; }
7471 static void init(const Config
& config
) {
7472 process_init(config
.o
, config
.gd
, false);
7474 static void fini() {}
7476 static bool run(FuncFamilyGroup group
) {
7477 for (auto const& ff
: group
.m_ffs
) {
7478 // FuncFamily should always have more than one func on it.
7480 ff
->m_regular
.size() +
7481 ff
->m_nonRegularPrivate
.size() +
7482 ff
->m_nonRegular
.size()
7486 // Every method should be sorted in its respective list. We
7487 // should never see a method for the same Class more than once.
7488 TSStringSet classes
;
7489 Optional
<MethRef
> lastReg
;
7490 Optional
<MethRef
> lastPrivate
;
7491 Optional
<MethRef
> lastNonReg
;
7492 for (auto const& meth
: ff
->m_regular
) {
7493 if (lastReg
) always_assert(*lastReg
< meth
);
7495 always_assert(classes
.emplace(meth
.cls
).second
);
7497 for (auto const& meth
: ff
->m_nonRegularPrivate
) {
7498 if (lastPrivate
) always_assert(*lastPrivate
< meth
);
7500 always_assert(classes
.emplace(meth
.cls
).second
);
7502 for (auto const& meth
: ff
->m_nonRegular
) {
7503 if (lastNonReg
) always_assert(*lastNonReg
< meth
);
7505 always_assert(classes
.emplace(meth
.cls
).second
);
7508 always_assert(ff
->m_allStatic
.has_value());
7510 ff
->m_regularStatic
.has_value() ==
7511 (!ff
->m_regular
.empty() || !ff
->m_nonRegularPrivate
.empty())
7518 Job
<CheckClassInfoInvariantsJob
> s_checkCInfoInvariantsJob
;
7519 Job
<CheckFuncFamilyInvariantsJob
> s_checkFuncFamilyInvariantsJob
;
7521 void check_invariants(const IndexData
& index
) {
7524 trace_time trace
{"check-invariants", index
.sample
};
7526 constexpr size_t kCInfoBucketSize
= 3000;
7527 constexpr size_t kFFBucketSize
= 500;
7529 auto cinfoBuckets
= consistently_bucketize(
7531 std::vector
<SString
> roots
;
7532 roots
.reserve(index
.classInfoRefs
.size());
7533 for (auto const& [name
, _
] : index
.classInfoRefs
) {
7534 roots
.emplace_back(name
);
7541 SStringToOneT
<Ref
<FuncFamilyGroup
>> nameToFuncFamilyGroup
;
7543 auto ffBuckets
= consistently_bucketize(
7545 std::vector
<SString
> roots
;
7546 roots
.reserve(index
.funcFamilyRefs
.size());
7547 for (auto const& [_
, ref
] : index
.funcFamilyRefs
) {
7548 auto const name
= makeStaticString(ref
.id().toString());
7549 if (nameToFuncFamilyGroup
.emplace(name
, ref
).second
) {
7550 roots
.emplace_back(name
);
7558 using namespace folly::gen
;
7560 auto const runCInfo
= [&] (std::vector
<SString
> work
) -> coro::Task
<void> {
7561 co_await
coro::co_reschedule_on_current_executor
;
7563 if (work
.empty()) co_return
;
7565 auto inputs
= from(work
)
7566 | map([&] (SString name
) {
7567 return std::make_tuple(
7568 index
.classInfoRefs
.at(name
),
7569 index
.classRefs
.at(name
)
7572 | as
<std::vector
>();
7574 Client::ExecMetadata metadata
{
7575 .job_key
= folly::sformat("check cinfo invariants {}", work
[0])
7577 auto config
= co_await index
.configRef
->getCopy();
7578 auto outputs
= co_await index
.client
->exec(
7579 s_checkCInfoInvariantsJob
,
7584 assertx(outputs
.size() == work
.size());
7589 auto const runFF
= [&] (std::vector
<SString
> work
) -> coro::Task
<void> {
7590 co_await
coro::co_reschedule_on_current_executor
;
7592 if (work
.empty()) co_return
;
7594 auto inputs
= from(work
)
7595 | map([&] (SString name
) {
7596 return std::make_tuple(nameToFuncFamilyGroup
.at(name
));
7598 | as
<std::vector
>();
7600 Client::ExecMetadata metadata
{
7601 .job_key
= folly::sformat("check func-family invariants {}", work
[0])
7603 auto config
= co_await index
.configRef
->getCopy();
7604 auto outputs
= co_await index
.client
->exec(
7605 s_checkFuncFamilyInvariantsJob
,
7610 assertx(outputs
.size() == work
.size());
7615 std::vector
<coro::TaskWithExecutor
<void>> tasks
;
7616 for (auto& work
: cinfoBuckets
) {
7618 runCInfo(std::move(work
)).scheduleOn(index
.executor
->sticky())
7621 for (auto& work
: ffBuckets
) {
7623 runFF(std::move(work
)).scheduleOn(index
.executor
->sticky())
7626 coro::blockingWait(coro::collectAllRange(std::move(tasks
)));
7629 void check_local_invariants(const IndexData
& index
, const ClassInfo
* cinfo
) {
7630 SCOPE_ASSERT_DETAIL("class") { return cinfo
->cls
->name
->toCppString(); };
7632 always_assert(check(*cinfo
->cls
));
7634 // AttrNoOverride is a superset of AttrNoOverrideRegular
7636 IMPLIES(!(cinfo
->cls
->attrs
& AttrNoOverrideRegular
),
7637 !(cinfo
->cls
->attrs
& AttrNoOverride
))
7640 always_assert(cinfo
->classGraph
);
7641 always_assert(!cinfo
->classGraph
.isMissing());
7642 always_assert(cinfo
->classGraph
.name()->tsame(cinfo
->cls
->name
));
7643 always_assert(cinfo
->classGraph
.cinfo() == cinfo
);
7644 if (is_closure_base(cinfo
->cls
->name
)) {
7645 // The closure base class is special. We don't store it's children
7646 // information because it's too large.
7647 always_assert(!cinfo
->classGraph
.hasCompleteChildren());
7648 always_assert(cinfo
->classGraph
.isConservative());
7650 always_assert(cinfo
->classGraph
.hasCompleteChildren() ||
7651 cinfo
->classGraph
.isConservative());
7654 // This class and withoutNonRegular should be equivalent when
7655 // ignoring non-regular classes. The withoutNonRegular class should
7656 // be a fixed-point.
7657 if (auto const without
= cinfo
->classGraph
.withoutNonRegular()) {
7658 always_assert(without
.hasCompleteChildren() ||
7659 without
.isConservative());
7660 always_assert(without
.subSubtypeOf(cinfo
->classGraph
, false, false));
7661 always_assert(cinfo
->classGraph
.subSubtypeOf(without
, false, false));
7662 always_assert(without
.withoutNonRegular() == without
);
7663 always_assert(cinfo
->classGraph
.mightBeRegular() ||
7664 cinfo
->classGraph
.mightHaveRegularSubclass());
7665 always_assert(IMPLIES(cinfo
->classGraph
.mightBeRegular(),
7666 without
== cinfo
->classGraph
));
7667 } else if (!is_used_trait(*cinfo
->cls
)) {
7668 always_assert(!cinfo
->classGraph
.mightBeRegular());
7669 always_assert(!cinfo
->classGraph
.mightHaveRegularSubclass());
7672 // Override attrs and what we know about the subclasses should be in
7674 if (cinfo
->cls
->attrs
& AttrNoOverride
) {
7675 always_assert(!cinfo
->classGraph
.mightHaveRegularSubclass());
7676 always_assert(!cinfo
->classGraph
.mightHaveNonRegularSubclass());
7677 } else if (cinfo
->cls
->attrs
& AttrNoOverrideRegular
) {
7678 always_assert(!cinfo
->classGraph
.mightHaveRegularSubclass());
7679 always_assert(cinfo
->classGraph
.mightHaveNonRegularSubclass());
7682 if (cinfo
->cls
->attrs
& AttrNoMock
) {
7683 always_assert(!cinfo
->isMocked
);
7684 always_assert(!cinfo
->isSubMocked
);
7687 // An AttrNoExpand class shouldn't have any used traits.
7689 bool(cinfo
->cls
->attrs
& AttrNoExpandTrait
) ==
7690 cinfo
->usedTraits
.empty()
7693 for (size_t idx
= 0; idx
< cinfo
->cls
->methods
.size(); ++idx
) {
7694 // Each method in a class has an entry in its ClassInfo method
7696 auto const& m
= cinfo
->cls
->methods
[idx
];
7697 auto const it
= cinfo
->methods
.find(m
->name
);
7698 always_assert(it
!= cinfo
->methods
.end());
7699 always_assert(it
->second
.meth().cls
->tsame(cinfo
->cls
->name
));
7700 always_assert(it
->second
.meth().idx
== idx
);
7702 // Every method (except for constructors and special methods
7703 // should be in the global name-only tables.
7704 auto const nameIt
= index
.methodFamilies
.find(m
->name
);
7705 if (!has_name_only_func_family(m
->name
)) {
7706 always_assert(nameIt
== end(index
.methodFamilies
));
7709 always_assert(nameIt
!= end(index
.methodFamilies
));
7711 auto const& entry
= nameIt
->second
;
7712 // The global name-only tables are never complete.
7713 always_assert(entry
.m_all
.isIncomplete());
7714 always_assert(entry
.m_regular
.isEmpty() || entry
.m_regular
.isIncomplete());
7716 // "all" should always be non-empty and contain this method.
7717 always_assert(!entry
.m_all
.isEmpty());
7718 if (auto const ff
= entry
.m_all
.funcFamily()) {
7719 always_assert(ff
->possibleFuncs().size() > 1);
7720 // The FuncFamily shouldn't have a section for regular results
7721 // if "regular" isn't using it.
7722 if (entry
.m_regular
.func() || entry
.m_regular
.isEmpty()) {
7723 always_assert(!ff
->m_regular
);
7725 // "all" and "regular" always share the same func family.
7726 always_assert(entry
.m_regular
.funcFamily() == ff
);
7729 auto const func
= entry
.m_all
.func();
7730 always_assert(func
);
7731 always_assert(func
== m
.get());
7732 // "regular" is always a subset of "all", so it can either be a
7733 // single func (the same as "all"), or empty.
7734 always_assert(entry
.m_regular
.func() || entry
.m_regular
.isEmpty());
7735 if (auto const func2
= entry
.m_regular
.func()) {
7736 always_assert(func
== func2
);
7740 // If this is a regular class, "regular" should be non-empty and
7741 // contain this method.
7742 if (auto const ff
= entry
.m_regular
.funcFamily()) {
7743 always_assert(ff
->possibleFuncs().size() > 1);
7744 } else if (auto const func
= entry
.m_regular
.func()) {
7745 if (is_regular_class(*cinfo
->cls
)) {
7746 always_assert(func
== m
.get());
7749 always_assert(!is_regular_class(*cinfo
->cls
));
7753 // Interface ClassInfo method table should only contain methods from
7754 // the interface itself.
7755 if (cinfo
->cls
->attrs
& AttrInterface
) {
7756 always_assert(cinfo
->cls
->methods
.size() == cinfo
->methods
.size());
7759 // If a class isn't overridden, it shouldn't have any func families
7760 // (because the method table is sufficient).
7761 if (cinfo
->cls
->attrs
& AttrNoOverride
) {
7762 always_assert(cinfo
->methodFamilies
.empty());
7763 always_assert(cinfo
->methodFamiliesAux
.empty());
7766 // The auxiliary method families map is only used by non-regular
7768 if (is_regular_class(*cinfo
->cls
)) {
7769 always_assert(cinfo
->methodFamiliesAux
.empty());
7772 for (auto const& [name
, mte
] : cinfo
->methods
) {
7773 // Interface method tables should only contain its own methods.
7774 if (cinfo
->cls
->attrs
& AttrInterface
) {
7775 always_assert(mte
.meth().cls
->tsame(cinfo
->cls
->name
));
7777 // Non-interface method tables should not contain any methods
7778 // defined by an interface.
7779 auto const func
= func_from_meth_ref(index
, mte
.meth());
7780 always_assert(!(func
->cls
->attrs
& AttrInterface
));
7783 // AttrNoOverride implies noOverrideRegular
7784 always_assert(IMPLIES(mte
.attrs
& AttrNoOverride
, mte
.noOverrideRegular()));
7786 if (!is_special_method_name(name
)) {
7787 // If the class isn't overridden, none of it's methods can be
7789 always_assert(IMPLIES(cinfo
->cls
->attrs
& AttrNoOverride
,
7790 mte
.attrs
& AttrNoOverride
));
7792 always_assert(!(mte
.attrs
& AttrNoOverride
));
7793 always_assert(!mte
.noOverrideRegular());
7796 if (is_closure_base(*cinfo
->cls
) || is_closure(*cinfo
->cls
)) {
7797 always_assert(mte
.attrs
& AttrNoOverride
);
7800 auto const famIt
= cinfo
->methodFamilies
.find(name
);
7801 // Don't store method families for special methods, or if there's
7803 if (is_special_method_name(name
) || (mte
.attrs
& AttrNoOverride
)) {
7804 always_assert(famIt
== end(cinfo
->methodFamilies
));
7805 always_assert(!cinfo
->methodFamiliesAux
.count(name
));
7808 always_assert(famIt
!= end(cinfo
->methodFamilies
));
7810 auto const& entry
= famIt
->second
;
7812 if (is_regular_class(*cinfo
->cls
)) {
7813 // "all" should only be a func family. It can't be empty,
7814 // because we know there's at least one method in it (the one in
7815 // cinfo->methods). It can't be a single func, because one of
7816 // the methods must be the cinfo->methods method, and we know it
7817 // isn't AttrNoOverride, so there *must* be another method. So,
7818 // it must be a func family.
7819 always_assert(entry
.funcFamily());
7820 // This is a regular class, so we cannot have an incomplete
7821 // entry (can only happen with interfaces).
7822 always_assert(entry
.isComplete());
7824 // This class isn't AttrNoOverride, and since the method is on
7825 // this class, it should at least contain that.
7826 always_assert(!entry
.isEmpty());
7827 // Only interfaces can have incomplete entries.
7829 IMPLIES(entry
.isIncomplete(), cinfo
->cls
->attrs
& AttrInterface
)
7831 // If we got a single func, it should be the func on this
7832 // class. Since this isn't AttrNoOverride, it implies the entry
7833 // should be incomplete.
7834 always_assert(IMPLIES(entry
.func(), entry
.isIncomplete()));
7836 IMPLIES(entry
.func(),
7837 entry
.func() == func_from_meth_ref(index
, mte
.meth()))
7840 // The "aux" entry is optional. If it isn't present, it's the
7841 // same as the normal table.
7842 auto const auxIt
= cinfo
->methodFamiliesAux
.find(name
);
7843 if (auxIt
!= end(cinfo
->methodFamiliesAux
)) {
7844 auto const& aux
= auxIt
->second
;
7846 // We shouldn't store in the aux table if the entry is the
7847 // same or if there's no override.
7848 always_assert(!mte
.noOverrideRegular());
7850 aux
.isIncomplete() ||
7851 aux
.func() != entry
.func() ||
7852 aux
.funcFamily() != entry
.funcFamily()
7855 // Normally the aux should be non-empty and complete. However
7856 // if this class is an interface, they could be.
7858 IMPLIES(aux
.isEmpty(), cinfo
->cls
->attrs
& AttrInterface
)
7861 IMPLIES(aux
.isIncomplete(), cinfo
->cls
->attrs
& AttrInterface
)
7864 // Since we know this was overridden (it wouldn't be in the
7865 // aux table otherwise), it must either be incomplete, or if
7866 // it has a single func, it cannot be the same func as this
7869 aux
.isIncomplete() ||
7870 ((mte
.attrs
& AttrPrivate
) && mte
.topLevel()) ||
7871 aux
.func() != func_from_meth_ref(index
, mte
.meth())
7874 // Aux entry is a subset of the normal entry. If they both
7875 // have a func family or func, they must be the same. If the
7876 // normal entry has a func family, but aux doesn't, that func
7877 // family shouldn't have extra space allocated.
7878 always_assert(IMPLIES(entry
.func(), !aux
.funcFamily()));
7879 always_assert(IMPLIES(entry
.funcFamily() && aux
.funcFamily(),
7880 entry
.funcFamily() == aux
.funcFamily()));
7881 always_assert(IMPLIES(entry
.func() && aux
.func(),
7882 entry
.func() == aux
.func()));
7883 always_assert(IMPLIES(entry
.funcFamily() && !aux
.funcFamily(),
7884 !entry
.funcFamily()->m_regular
));
7889 // "Aux" entries should only exist for methods on this class, and
7890 // with a corresponding methodFamilies entry.
7891 for (auto const& [name
, _
] : cinfo
->methodFamiliesAux
) {
7892 always_assert(cinfo
->methods
.count(name
));
7893 always_assert(cinfo
->methodFamilies
.count(name
));
7896 // We should only have func families for methods declared on this
7897 // class (except for interfaces and abstract classes).
7898 for (auto const& [name
, entry
] : cinfo
->methodFamilies
) {
7899 if (cinfo
->methods
.count(name
)) continue;
7900 // Interfaces and abstract classes can have func families for
7901 // methods not defined on this class.
7902 always_assert(cinfo
->cls
->attrs
& (AttrInterface
|AttrAbstract
));
7903 // We don't expand func families for these.
7904 always_assert(name
!= s_construct
.get() && !is_special_method_name(name
));
7906 // We only expand entries for interfaces and abstract classes if
7907 // it appears in every regular subclass. Therefore it cannot be
7908 // empty and is complete.
7909 always_assert(!entry
.isEmpty());
7910 always_assert(entry
.isComplete());
7911 if (auto const ff
= entry
.funcFamily()) {
7912 always_assert(!ff
->m_regular
);
7913 } else if (auto const func
= entry
.func()) {
7914 always_assert(func
->cls
!= cinfo
->cls
);
7918 // If the class is marked as having not having bad initial prop
7919 // values, all of it's properties should have AttrInitialSatisfiesTC
7920 // set. Likewise, if it is, at least one property should not have it
7922 if (!cinfo
->hasBadInitialPropValues
) {
7923 auto const all
= std::all_of(
7924 begin(cinfo
->cls
->properties
),
7925 end(cinfo
->cls
->properties
),
7926 [] (const php::Prop
& p
) {
7927 return p
.attrs
& AttrInitialSatisfiesTC
;
7932 auto const someBad
= std::any_of(
7933 begin(cinfo
->cls
->properties
),
7934 end(cinfo
->cls
->properties
),
7935 [] (const php::Prop
& p
) {
7936 return !(p
.attrs
& AttrInitialSatisfiesTC
);
7939 always_assert(someBad
);
7942 if (is_closure_name(cinfo
->cls
->name
)) {
7943 assertx(cinfo
->classGraph
.hasCompleteChildren());
7944 // Closures have no children.
7945 auto const subclasses
= cinfo
->classGraph
.children();
7946 always_assert(subclasses
.size() == 1);
7947 always_assert(subclasses
[0].name()->tsame(cinfo
->cls
->name
));
7948 } else if (cinfo
->classGraph
.hasCompleteChildren()) {
7949 // Otherwise the children list is non-empty, contains this
7950 // class, and contains only unique elements.
7951 auto const subclasses
= cinfo
->classGraph
.children();
7956 [&] (ClassGraph g
) { return g
.name()->tsame(cinfo
->cls
->name
); }
7957 ) != end(subclasses
)
7959 auto cpy
= subclasses
;
7960 std::sort(begin(cpy
), end(cpy
));
7961 cpy
.erase(std::unique(begin(cpy
), end(cpy
)), end(cpy
));
7962 always_assert(cpy
.size() == subclasses
.size());
7965 // The base list is non-empty, and the last element is this class.
7966 auto const bases
= cinfo
->classGraph
.bases();
7967 always_assert(!bases
.empty());
7968 always_assert(cinfo
->classGraph
== bases
.back());
7969 if (is_closure_base(cinfo
->cls
->name
)) {
7970 always_assert(bases
.size() == 1);
7971 } else if (is_closure_name(cinfo
->cls
->name
)) {
7972 always_assert(bases
.size() == 2);
7973 always_assert(bases
[0].name()->tsame(s_Closure
.get()));
7977 void check_local_invariants(const IndexData
& data
, const FuncFamily
& ff
) {
7978 // FuncFamily should always have more than one func on it.
7979 always_assert(ff
.possibleFuncs().size() > 1);
7981 SString name
{nullptr};
7982 FuncFamily::PossibleFunc last
{nullptr, false};
7983 for (auto const pf
: ff
.possibleFuncs()) {
7984 // Should only contain methods
7985 always_assert(pf
.ptr()->cls
);
7987 // Every method on the list should have the same name.
7989 name
= pf
.ptr()->name
;
7991 always_assert(name
== pf
.ptr()->name
);
7994 // Verify the list is sorted and doesn't contain any duplicates.
7995 hphp_fast_set
<const php::Func
*> seen
;
7999 if (last
.inRegular() && !pf
.inRegular()) return true;
8000 if (!last
.inRegular() && pf
.inRegular()) return false;
8001 return string_data_lt_type
{}(last
.ptr()->cls
->name
, pf
.ptr()->cls
->name
);
8005 always_assert(seen
.emplace(pf
.ptr()).second
);
8009 if (!ff
.possibleFuncs().front().inRegular() ||
8010 ff
.possibleFuncs().back().inRegular()) {
8011 // If there's no funcs on a regular class, or if all functions are
8012 // on a regular class, we don't need to keep separate information
8013 // for the regular subset (it either doesn't exist, or it's equal to
8014 // the entire list).
8015 always_assert(!ff
.m_regular
);
8019 void check_local_invariants(const IndexData
& data
) {
8022 trace_time timer
{"check-local-invariants"};
8026 [&] (const std::unique_ptr
<ClassInfo
>& cinfo
) {
8027 check_local_invariants(data
, cinfo
.get());
8031 std::vector
<const FuncFamily
*> funcFamilies
;
8032 funcFamilies
.reserve(data
.funcFamilies
.size());
8033 for (auto const& [ff
, _
] : data
.funcFamilies
) {
8034 funcFamilies
.emplace_back(ff
.get());
8038 [&] (const FuncFamily
* ff
) { check_local_invariants(data
, *ff
); }
8042 //////////////////////////////////////////////////////////////////////
8044 Type
adjust_closure_context(const IIndex
& index
, const CallContext
& ctx
) {
8045 if (ctx
.callee
->cls
&& ctx
.callee
->cls
->closureContextCls
) {
8046 auto const withClosureContext
= Context
{
8049 index
.lookup_closure_context(*ctx
.callee
->cls
)
8051 if (auto const s
= selfCls(index
, withClosureContext
)) {
8052 return setctx(toobj(*s
));
8059 Index::ReturnType
context_sensitive_return_type(IndexData
& data
,
8061 CallContext callCtx
,
8062 Index::ReturnType returnType
) {
8063 constexpr auto max_interp_nexting_level
= 2;
8064 static __thread
uint32_t interp_nesting_level
;
8065 auto const finfo
= func_info(data
, callCtx
.callee
);
8067 auto const adjustedCtx
= adjust_closure_context(
8068 IndexAdaptor
{ *data
.m_index
},
8071 returnType
.t
= return_with_context(std::move(returnType
.t
), adjustedCtx
);
8073 auto const checkParam
= [&] (int i
) {
8074 auto const& constraint
= finfo
->func
->params
[i
].typeConstraint
;
8075 if (constraint
.hasConstraint() &&
8076 !constraint
.isTypeVar() &&
8077 !constraint
.isTypeConstant()) {
8078 auto const ctx
= Context
{
8083 return callCtx
.args
[i
].strictlyMoreRefined(
8084 lookup_constraint(IndexAdaptor
{ *data
.m_index
}, ctx
, constraint
).upper
8087 return callCtx
.args
[i
].strictSubtypeOf(TInitCell
);
8090 // TODO(#3788877): more heuristics here would be useful.
8091 auto const tryContextSensitive
= [&] {
8092 if (finfo
->func
->noContextSensitiveAnalysis
||
8093 finfo
->func
->params
.empty() ||
8094 interp_nesting_level
+ 1 >= max_interp_nexting_level
||
8095 returnType
.t
.is(BBottom
)) {
8099 if (finfo
->retParam
!= NoLocalId
&&
8100 callCtx
.args
.size() > finfo
->retParam
&&
8101 checkParam(finfo
->retParam
)) {
8105 if (!options
.ContextSensitiveInterp
) return false;
8107 if (callCtx
.args
.size() < finfo
->func
->params
.size()) return true;
8108 for (auto i
= 0; i
< finfo
->func
->params
.size(); i
++) {
8109 if (checkParam(i
)) return true;
8114 if (!tryContextSensitive
) return returnType
;
8117 ContextRetTyMap::const_accessor acc
;
8118 if (data
.contextualReturnTypes
.find(acc
, callCtx
)) {
8120 acc
->second
.t
.is(BBottom
) ||
8121 is_scalar(acc
->second
.t
)) {
8127 if (data
.frozen
) return returnType
;
8129 auto contextType
= [&] {
8130 ++interp_nesting_level
;
8131 SCOPE_EXIT
{ --interp_nesting_level
; };
8133 auto const func
= finfo
->func
;
8134 auto const wf
= php::WideFunc::cns(func
);
8135 auto const calleeCtx
= AnalysisContext
{
8141 auto fa
= analyze_func_inline(
8142 IndexAdaptor
{ *data
.m_index
},
8147 return Index::ReturnType
{
8148 return_with_context(std::move(fa
.inferredReturn
), adjustedCtx
),
8153 if (!interp_nesting_level
) {
8155 "Context sensitive type: {}\n"
8156 "Context insensitive type: {}\n",
8157 show(contextType
.t
), show(returnType
.t
));
8160 if (!returnType
.t
.subtypeOf(BUnc
)) {
8161 // If the context insensitive return type could be non-static, staticness
8162 // could be a result of temporary context sensitive bytecode optimizations.
8163 contextType
.t
= loosen_staticness(std::move(contextType
.t
));
8166 auto ret
= Index::ReturnType
{
8167 intersection_of(std::move(returnType
.t
), std::move(contextType
.t
)),
8168 returnType
.effectFree
&& contextType
.effectFree
8171 if (!interp_nesting_level
) {
8172 FTRACE(3, "Context sensitive result: {}\n", show(ret
.t
));
8175 ContextRetTyMap::accessor acc
;
8176 if (data
.contextualReturnTypes
.insert(acc
, callCtx
) ||
8177 ret
.t
.strictSubtypeOf(acc
->second
.t
) ||
8178 (ret
.effectFree
&& !acc
->second
.effectFree
)) {
8185 //////////////////////////////////////////////////////////////////////
8187 Index::ReturnType
context_sensitive_return_type(AnalysisIndex::IndexData
& data
,
8188 const CallContext
& callCtx
,
8189 Index::ReturnType returnType
) {
8190 constexpr size_t maxNestingLevel
= 2;
8192 using R
= Index::ReturnType
;
8194 auto const& func
= *callCtx
.callee
;
8196 if (data
.mode
== AnalysisIndex::Mode::Constants
) {
8199 "Skipping inline interp of {} because analyzing constants\n",
8205 auto const& finfo
= func_info(data
, func
);
8206 auto const& caller
= *context_for_deps(data
).func
;
8208 auto const adjustedCtx
= adjust_closure_context(
8209 AnalysisIndexAdaptor
{ data
.index
},
8212 returnType
.t
= return_with_context(std::move(returnType
.t
), adjustedCtx
);
8214 auto const checkParam
= [&] (size_t i
) {
8215 auto const& constraint
= func
.params
[i
].typeConstraint
;
8216 if (constraint
.hasConstraint() &&
8217 !constraint
.isTypeVar() &&
8218 !constraint
.isTypeConstant()) {
8219 return callCtx
.args
[i
].strictlyMoreRefined(
8221 AnalysisIndexAdaptor
{ data
.index
},
8222 Context
{ func
.unit
, &func
, func
.cls
},
8227 return callCtx
.args
[i
].strictSubtypeOf(TInitCell
);
8230 // TODO(#3788877): more heuristics here would be useful.
8231 auto const tryContextSensitive
= [&] {
8232 if (func
.noContextSensitiveAnalysis
||
8233 func
.params
.empty() ||
8234 data
.contextualInterpNestingLevel
+ 1 >= maxNestingLevel
||
8235 returnType
.t
.is(BBottom
)) {
8239 if (data
.deps
->add(func
, AnalysisDeps::RetParam
)) {
8240 if (finfo
.retParam
!= NoLocalId
&&
8241 callCtx
.args
.size() > finfo
.retParam
&&
8242 checkParam(finfo
.retParam
)) {
8247 if (!options
.ContextSensitiveInterp
) return false;
8249 auto const numParams
= func
.params
.size();
8250 if (callCtx
.args
.size() < numParams
) return true;
8251 for (size_t i
= 0; i
< numParams
; ++i
) {
8252 if (checkParam(i
)) return true;
8257 if (!tryContextSensitive
) {
8258 ITRACE_MOD(Trace::hhbbc
, 4, "not trying context sensitive\n");
8262 if (!data
.deps
->add(func
, AnalysisDeps::Bytecode
)) {
8265 "Skipping inline interp of {} because inspecting "
8266 "bytecode is not allowed\n",
8269 return R
{ TInitCell
, false };
8271 if (!func
.rawBlocks
) {
8274 "Skipping inline interp of {} because bytecode not present\n",
8277 return R
{ TInitCell
, false };
8280 auto const contextType
= [&] {
8281 ++data
.contextualInterpNestingLevel
;
8282 SCOPE_EXIT
{ --data
.contextualInterpNestingLevel
; };
8284 auto const wf
= php::WideFunc::cns(&func
);
8285 auto fa
= analyze_func_inline(
8286 AnalysisIndexAdaptor
{ data
.index
},
8291 &context_for_deps(data
)
8297 return_with_context(std::move(fa
.inferredReturn
), std::move(adjustedCtx
)),
8304 "Context sensitive type: {}, context insensitive type: {}\n",
8305 show(contextType
.t
), show(returnType
.t
)
8308 auto const error_context
= [&] {
8309 using namespace folly::gen
;
8310 return folly::sformat(
8311 "{} calling {} (context: {}, args: {})",
8312 func_fullname(caller
),
8313 func_fullname(func
),
8314 show(callCtx
.context
),
8316 | map([] (const Type
& t
) { return show(t
); })
8317 | unsplit
<std::string
>(",")
8322 contextType
.t
.subtypeOf(returnType
.t
),
8323 "Context sensitive return type for {} is {} ",
8324 "which is not at least as refined as context insensitive "
8327 show(contextType
.t
),
8331 contextType
.effectFree
|| !returnType
.effectFree
,
8332 "Context sensitive effect-free for {} is {} ",
8333 "which is not at least as refined as context insensitive "
8336 contextType
.effectFree
,
8337 returnType
.effectFree
8343 //////////////////////////////////////////////////////////////////////
8345 template<typename F
> auto
8346 visit_parent_cinfo(const ClassInfo
* cinfo
, F fun
) -> decltype(fun(cinfo
)) {
8347 for (auto ci
= cinfo
; ci
!= nullptr; ci
= ci
->parent
) {
8348 if (auto const ret
= fun(ci
)) return ret
;
8349 for (auto const trait
: ci
->usedTraits
) {
8350 if (auto const ret
= visit_parent_cinfo(trait
, fun
)) {
8358 //////////////////////////////////////////////////////////////////////
8360 // The type of a public static property, considering only it's initial
8362 Type
initial_type_for_public_sprop(const Index
& index
,
8363 const php::Class
& cls
,
8364 const php::Prop
& prop
) {
8366 * If the initializer type is TUninit, it means an 86sinit provides
8367 * the actual initialization type or it is AttrLateInit. So we don't
8368 * want to include the Uninit (which isn't really a user-visible
8369 * type for the property) or by the time we union things in we'll
8370 * have inferred nothing much.
8372 auto const ty
= from_cell(prop
.val
);
8373 if (ty
.subtypeOf(BUninit
)) return TBottom
;
8374 if (prop
.attrs
& AttrSystemInitialValue
) return ty
;
8375 return adjust_type_for_prop(
8376 IndexAdaptor
{ index
},
8378 &prop
.typeConstraint
,
8383 Type
lookup_public_prop_impl(
8384 const IndexData
& data
,
8385 const ClassInfo
* cinfo
,
8388 // Find a property declared in this class (or a parent) with the same name.
8389 const php::Class
* knownCls
= nullptr;
8390 auto const prop
= visit_parent_cinfo(
8392 [&] (const ClassInfo
* ci
) -> const php::Prop
* {
8393 for (auto const& prop
: ci
->cls
->properties
) {
8394 if (prop
.name
== propName
) {
8403 if (!prop
) return TCell
;
8404 // Make sure its non-static and public. Otherwise its another function's
8406 if (prop
->attrs
& (AttrStatic
| AttrPrivate
)) return TCell
;
8408 // Get a type corresponding to its declared type-hint (if any).
8409 auto ty
= adjust_type_for_prop(
8410 IndexAdaptor
{ *data
.m_index
}, *knownCls
, &prop
->typeConstraint
, TCell
8412 // We might have to include the initial value which might be outside of the
8414 auto initialTy
= loosen_all(from_cell(prop
->val
));
8415 if (!initialTy
.subtypeOf(TUninit
) && (prop
->attrs
& AttrSystemInitialValue
)) {
8421 // Test if the given property (declared in `cls') is accessible in the
8422 // given context (null if we're not in a class).
8423 bool static_is_accessible(const ClassInfo
* clsCtx
,
8424 const ClassInfo
* cls
,
8425 const php::Prop
& prop
) {
8426 assertx(prop
.attrs
& AttrStatic
);
8427 switch (prop
.attrs
& (AttrPublic
|AttrProtected
|AttrPrivate
)) {
8429 // Public is accessible everywhere
8432 // Protected is accessible from both derived classes and parent
8435 (clsCtx
->classGraph
.exactSubtypeOf(cls
->classGraph
, true, true) ||
8436 cls
->classGraph
.exactSubtypeOf(clsCtx
->classGraph
, true, true));
8438 // Private is only accessible from within the declared class
8439 return clsCtx
== cls
;
8441 always_assert(false);
8444 // Return true if the given class can possibly throw when its
8445 // initialized. Initialization can happen when an object of that class
8446 // is instantiated, or (more importantly) when static properties are
8448 bool class_init_might_raise(IndexData
& data
,
8450 const ClassInfo
* cinfo
) {
8451 // Check this class and all of its parents for possible inequivalent
8452 // redeclarations or bad initial values.
8454 // Be conservative for now if we have unflattened traits.
8455 if (!cinfo
->traitProps
.empty()) return true;
8456 if (cinfo
->hasBadRedeclareProp
) return true;
8457 if (cinfo
->hasBadInitialPropValues
) {
8458 add_dependency(data
, cinfo
->cls
, ctx
, Dep::PropBadInitialValues
);
8461 cinfo
= cinfo
->parent
;
8467 * Calculate the effects of applying the given type against the
8468 * type-constraints for the given prop. This includes the subtype
8469 * which will succeed (if any), and if the type-constraint check might
8472 PropMergeResult
prop_tc_effects(const Index
& index
,
8473 const ClassInfo
* ci
,
8474 const php::Prop
& prop
,
8477 assertx(prop
.typeConstraint
.validForProp());
8479 using R
= PropMergeResult
;
8481 // If we're not actually checking property type-hints, everything
8483 if (Cfg::Eval::CheckPropTypeHints
<= 0) return R
{ val
, TriBool::No
};
8485 auto const ctx
= Context
{ nullptr, nullptr, ci
->cls
};
8487 auto const check
= [&] (const TypeConstraint
& tc
, const Type
& t
) {
8488 // If the type as is satisfies the constraint, we won't throw and
8489 // the type is unchanged.
8491 lookup_constraint(IndexAdaptor
{ index
}, ctx
, tc
, t
).lower
)
8493 return R
{ t
, TriBool:: No
};
8495 // Otherwise adjust the type. If we get a Bottom we'll definitely
8496 // throw. We already know the type doesn't completely satisfy the
8497 // constraint, so we'll at least maybe throw.
8499 adjust_type_for_prop(IndexAdaptor
{ index
}, *ctx
.cls
, &tc
, t
);
8500 auto const throws
= yesOrMaybe(adjusted
.subtypeOf(BBottom
));
8501 return R
{ std::move(adjusted
), throws
};
8504 // First check the main type-constraint.
8505 auto result
= check(prop
.typeConstraint
, val
);
8506 // If we're not checking generics upper-bounds, or if we already
8507 // know we'll fail, we're done.
8508 if (!checkUB
|| result
.throws
== TriBool::Yes
) {
8512 // Otherwise check every generic upper-bound. We'll feed the
8513 // narrowed type into each successive round. If we reach the point
8514 // where we'll know we'll definitely fail, just stop.
8515 for (auto const& ub
: prop
.ubs
.m_constraints
) {
8516 auto r
= check(ub
, result
.adjusted
);
8517 result
.throws
&= r
.throws
;
8518 result
.adjusted
= std::move(r
.adjusted
);
8519 if (result
.throws
== TriBool::Yes
) break;
8526 * Lookup data for the static property named `propName', starting from
8527 * the specified class `start'. If `propName' is nullptr, then any
8528 * accessible static property in the class hierarchy is considered. If
8529 * `startOnly' is specified, if the property isn't found in `start',
8530 * it is treated as a lookup failure. Otherwise the lookup continues
8531 * in all parent classes of `start', until a property is found, or
8532 * until all parent classes have been exhausted (`startOnly' is used
8533 * to avoid redundant class hierarchy walks). `clsCtx' is the current
8534 * context, converted to a ClassInfo* (or nullptr if not in a class).
8536 PropLookupResult
lookup_static_impl(IndexData
& data
,
8538 const ClassInfo
* clsCtx
,
8539 const PropertiesInfo
& privateProps
,
8540 const ClassInfo
* start
,
8544 6, "lookup_static_impl: {} {} {}\n",
8545 clsCtx
? clsCtx
->cls
->name
->toCppString() : std::string
{"-"},
8547 propName
? propName
->toCppString() : std::string
{"*"}
8551 auto const type
= [&] (const php::Prop
& prop
,
8552 const ClassInfo
* ci
) {
8553 switch (prop
.attrs
& (AttrPublic
|AttrProtected
|AttrPrivate
)) {
8555 case AttrProtected
: {
8556 if (ctx
.unit
) add_dependency(data
, &prop
, ctx
, Dep::PublicSProp
);
8557 if (!data
.seenPublicSPropMutations
) {
8558 // If we haven't recorded any mutations yet, we need to be
8559 // conservative and consider only the type-hint and initial
8562 adjust_type_for_prop(
8563 IndexAdaptor
{ *data
.m_index
},
8565 &prop
.typeConstraint
,
8568 initial_type_for_public_sprop(*data
.m_index
, *ci
->cls
, prop
)
8571 auto const it
= ci
->publicStaticProps
.find(propName
);
8572 if (it
== end(ci
->publicStaticProps
)) {
8573 // We've recorded mutations, but have information for this
8574 // property. That means there's no mutations so only
8575 // consider the initial value.
8576 return initial_type_for_public_sprop(*data
.m_index
, *ci
->cls
, prop
);
8578 return it
->second
.inferredType
;
8581 assertx(clsCtx
== ci
);
8582 auto const elem
= privateProps
.readPrivateStatic(prop
.name
);
8583 if (!elem
) return TInitCell
;
8584 return remove_uninit(elem
->ty
);
8587 always_assert(false);
8590 auto const initMightRaise
= class_init_might_raise(data
, ctx
, start
);
8592 auto const fromProp
= [&] (const php::Prop
& prop
,
8593 const ClassInfo
* ci
) {
8594 // The property was definitely found. Compute its attributes
8595 // from the prop metadata.
8596 return PropLookupResult
{
8600 yesOrNo(prop
.attrs
& AttrIsConst
),
8601 yesOrNo(prop
.attrs
& AttrIsReadonly
),
8602 yesOrNo(prop
.attrs
& AttrLateInit
),
8603 yesOrNo(prop
.attrs
& AttrInternal
),
8608 auto const notFound
= [&] {
8609 // The property definitely wasn't found.
8610 return PropLookupResult
{
8623 // We don't statically know the prop name. Walk up the hierarchy
8624 // and union the data for any accessible static property.
8625 ITRACE(4, "no prop name, considering all accessible\n");
8626 auto result
= notFound();
8629 [&] (const ClassInfo
* ci
) {
8630 for (auto const& prop
: ci
->cls
->properties
) {
8631 if (!(prop
.attrs
& AttrStatic
) ||
8632 !static_is_accessible(clsCtx
, ci
, prop
)) {
8634 6, "skipping inaccessible {}::${}\n",
8635 ci
->cls
->name
, prop
.name
8639 auto const r
= fromProp(prop
, ci
);
8640 ITRACE(6, "including {}:${} {}\n", ci
->cls
->name
, prop
.name
, show(r
));
8643 // If we're only interested in the starting class, don't walk
8644 // up to the parents.
8651 // We statically know the prop name. Walk up the hierarchy and stop
8652 // at the first matching property and use that data.
8653 assertx(!startOnly
);
8654 auto const result
= visit_parent_cinfo(
8656 [&] (const ClassInfo
* ci
) -> Optional
<PropLookupResult
> {
8657 for (auto const& prop
: ci
->cls
->properties
) {
8658 if (prop
.name
!= propName
) continue;
8659 // We have a matching prop. If its not static or not
8660 // accessible, the access will not succeed.
8661 if (!(prop
.attrs
& AttrStatic
) ||
8662 !static_is_accessible(clsCtx
, ci
, prop
)) {
8664 6, "{}::${} found but inaccessible, stopping\n",
8665 ci
->cls
->name
, propName
8669 // Otherwise its a match
8670 auto const r
= fromProp(prop
, ci
);
8671 ITRACE(6, "found {}:${} {}\n", ci
->cls
->name
, propName
, show(r
));
8674 return std::nullopt
;
8678 // We walked up to all of the base classes and didn't find a
8679 // property with a matching name. The access will fail.
8680 ITRACE(6, "nothing found\n");
8687 * Lookup the static property named `propName', starting from the
8688 * specified class `start'. If an accessible property is found, then
8689 * merge the given type `val' into the already known type for that
8690 * property. If `propName' is nullptr, then any accessible static
8691 * property in the class hierarchy is considered. If `startOnly' is
8692 * specified, if the property isn't found in `start', then the nothing
8693 * is done. Otherwise the lookup continues in all parent classes of
8694 * `start', until a property is found, or until all parent classes
8695 * have been exhausted (`startOnly' is to avoid redundant class
8696 * hierarchy walks). `clsCtx' is the current context, converted to a
8697 * ClassInfo* (or nullptr if not in a class). If `ignoreConst' is
8698 * false, then AttrConst properties will not have their type
8699 * modified. `mergePublic' is a lambda with the logic to merge a type
8700 * for a public property (this is needed to avoid cyclic
8703 template <typename F
>
8704 PropMergeResult
merge_static_type_impl(IndexData
& data
,
8707 PropertiesInfo
& privateProps
,
8708 const ClassInfo
* clsCtx
,
8709 const ClassInfo
* start
,
8714 bool mustBeReadOnly
,
8717 6, "merge_static_type_impl: {} {} {} {}\n",
8718 clsCtx
? clsCtx
->cls
->name
->toCppString() : std::string
{"-"},
8720 propName
? propName
->toCppString() : std::string
{"*"},
8725 assertx(!val
.subtypeOf(BBottom
));
8727 // Perform the actual merge for a given property, returning the
8728 // effects of that merge.
8729 auto const merge
= [&] (const php::Prop
& prop
, const ClassInfo
* ci
) {
8730 // First calculate the effects of the type-constraint.
8731 auto const effects
= prop_tc_effects(*data
.m_index
, ci
, prop
, val
, checkUB
);
8732 // No point in merging if the type-constraint will always fail.
8733 if (effects
.throws
== TriBool::Yes
) {
8735 6, "tc would throw on {}::${} with {}, skipping\n",
8736 ci
->cls
->name
, prop
.name
, show(val
)
8740 assertx(!effects
.adjusted
.subtypeOf(BBottom
));
8743 6, "merging {} into {}::${}\n",
8744 show(effects
), ci
->cls
->name
, prop
.name
8747 switch (prop
.attrs
& (AttrPublic
|AttrProtected
|AttrPrivate
)) {
8750 mergePublic(ci
, prop
, unctx(effects
.adjusted
));
8751 // If the property is internal, accessing it may throw
8752 // TODO(T131951529): we can do better by checking modules here
8753 if ((prop
.attrs
& AttrInternal
) && effects
.throws
== TriBool::No
) {
8754 ITRACE(6, "{}::${} is internal, "
8755 "being pessimistic with regards to throwing\n",
8756 ci
->cls
->name
, prop
.name
);
8757 return PropMergeResult
{
8764 assertx(clsCtx
== ci
);
8765 privateProps
.mergeInPrivateStaticPreAdjusted(
8767 unctx(effects
.adjusted
)
8772 always_assert(false);
8775 // If we don't find a property, then the mutation will definitely
8777 auto const notFound
= [&] {
8778 return PropMergeResult
{
8785 // We don't statically know the prop name. Walk up the hierarchy
8786 // and merge the type for any accessible static property.
8787 ITRACE(6, "no prop name, considering all accessible\n");
8788 auto result
= notFound();
8791 [&] (const ClassInfo
* ci
) {
8792 for (auto const& prop
: ci
->cls
->properties
) {
8793 if (!(prop
.attrs
& AttrStatic
) ||
8794 !static_is_accessible(clsCtx
, ci
, prop
)) {
8796 6, "skipping inaccessible {}::${}\n",
8797 ci
->cls
->name
, prop
.name
8801 if (!ignoreConst
&& (prop
.attrs
& AttrIsConst
)) {
8802 ITRACE(6, "skipping const {}::${}\n", ci
->cls
->name
, prop
.name
);
8805 if (mustBeReadOnly
&& !(prop
.attrs
& AttrIsReadonly
)) {
8806 ITRACE(6, "skipping mutable property that must be readonly {}::${}\n",
8807 ci
->cls
->name
, prop
.name
);
8810 result
|= merge(prop
, ci
);
8818 // We statically know the prop name. Walk up the hierarchy and stop
8819 // at the first matching property and merge the type there.
8820 assertx(!startOnly
);
8821 auto result
= visit_parent_cinfo(
8823 [&] (const ClassInfo
* ci
) -> Optional
<PropMergeResult
> {
8824 for (auto const& prop
: ci
->cls
->properties
) {
8825 if (prop
.name
!= propName
) continue;
8826 // We found a property with the right name, but its
8827 // inaccessible from this context (or not even static). This
8828 // mutation will fail, so we don't need to modify the type.
8829 if (!(prop
.attrs
& AttrStatic
) ||
8830 !static_is_accessible(clsCtx
, ci
, prop
)) {
8832 6, "{}::${} found but inaccessible, stopping\n",
8833 ci
->cls
->name
, propName
8837 // Mutations to AttrConst properties will fail as well, unless
8838 // it we want to override that behavior.
8839 if (!ignoreConst
&& (prop
.attrs
& AttrIsConst
)) {
8841 6, "{}:${} found but const, stopping\n",
8842 ci
->cls
->name
, propName
8846 if (mustBeReadOnly
&& !(prop
.attrs
& AttrIsReadonly
)) {
8848 6, "{}:${} found but is mutable and must be readonly, stopping\n",
8849 ci
->cls
->name
, propName
8853 return merge(prop
, ci
);
8855 return std::nullopt
;
8859 ITRACE(6, "nothing found\n");
8863 // If the mutation won't throw, we still need to check if the class
8864 // initialization can throw. If we might already throw (or
8865 // definitely will throw), this doesn't matter.
8866 if (result
->throws
== TriBool::No
) {
8867 return PropMergeResult
{
8868 std::move(result
->adjusted
),
8869 maybeOrNo(class_init_might_raise(data
, ctx
, start
))
8875 //////////////////////////////////////////////////////////////////////
8878 * Split a group of buckets so that no bucket is larger (including its
8879 * dependencies) than the given max size. The given callable is used
8880 * to obtain the dependencies of bucket item.
8882 * Note: if a single item has dependencies larger than maxSize, you'll
8883 * get a bucket with just that and its dependencies (which will be
8884 * larger than maxSize). This is the only situation where a returned
8885 * bucket will be larger than maxSize.
8887 template <typename GetDeps
>
8888 std::vector
<std::vector
<SString
>>
8889 split_buckets(const std::vector
<std::vector
<SString
>>& items
,
8891 const GetDeps
& getDeps
) {
8892 // Split all of the buckets in parallel
8893 auto rebuckets
= parallel::map(
8895 [&] (const std::vector
<SString
>& bucket
) {
8896 // If there's only one thing in a bucket, there's no point in
8898 if (bucket
.size() <= 1) return singleton_vec(bucket
);
8900 // The splitting algorithm is simple. Iterate over each element
8901 // in the bucket. As long as all the dependencies are less than
8902 // the maximum size, we put it into a new bucket. If we exceed
8903 // the max size, create a new bucket.
8904 std::vector
<std::vector
<SString
>> out
;
8906 out
.back().emplace_back(bucket
[0]);
8908 auto allDeps
= getDeps(bucket
[0]);
8909 for (size_t i
= 1, size
= bucket
.size(); i
< size
; ++i
) {
8910 auto const& d
= getDeps(bucket
[i
]);
8911 allDeps
.insert(begin(d
), end(d
));
8912 auto const newSize
= allDeps
.size() + out
.back().size() + 1;
8913 if (newSize
> maxSize
) {
8917 out
.back().emplace_back(bucket
[i
]);
8923 // Flatten all of the new buckets into a single list of buckets.
8924 std::vector
<std::vector
<SString
>> flattened
;
8925 flattened
.reserve(items
.size());
8926 for (auto& r
: rebuckets
) {
8927 for (auto& b
: r
) flattened
.emplace_back(std::move(b
));
8932 //////////////////////////////////////////////////////////////////////
8935 * For efficiency reasons, we often want to process classes in as few
8936 * passes as possible. However, this is tricky because the algorithms
8937 * are usually naturally iterative. You start at the root classes
8938 * (which may be the top classes in the hierarchy, or leaf classes),
8939 * and flow data down to each of their parents or children. This
8940 * requires N passes, where N is the maximum depth of the class
8941 * hierarchy. N can get large.
8943 * Instead when we process a class, we ensure that all of it's
8944 * dependencies (all the way up to the roots) are also present in the
8945 * job. Since we're doing this in one pass, none of the dependencies
8946 * will have any calculated information, and the job will have to do
8949 * It is not, in general, possible to ensure that each dependency is
8950 * present in exactly one job (because the dependency may be shared by
8951 * lots of classes which are not bucketed together). So, any given
8952 * dependency may end up on multiple jobs and have the same
8953 * information calculated for it. This is fine, as it just results in
8956 * We perform flattening using the following approach:
8958 * - First we Bucketize the root classes (using the standard
8959 * consistent hashing algorithm) into N buckets.
8961 * - We split any buckets which are larger than the specified maximum
8962 * size. This prevents buckets from becoming pathologically large if
8963 * there's many dependencies.
8965 * - For each bucket, find all of the (transitive) dependencies of the
8966 * leaves and add them to that bucket (as dependencies). As stated
8967 * above, the same class may end up in multiple buckets as
8970 * - So far for each bucket (each bucket will map to one job), we have
8971 * a set of input classes (the roots), and all of the dependencies
8974 * - We want results for every class, not just the roots, so the
8975 * dependencies need to become inputs of the first kind in at least
8976 * one bucket. So, for each dependency, in one of the buckets
8977 * they're already present in, we "promote" it to a full input (and
8978 * will receive output for it). This is done by hashing the bucket
8979 * index and class name and picking the bucket that results in the
8980 * lowest hash. In some situations we don't want a dependency to
8981 * ever be promoted, so those will be skipped.
8984 // Single output bucket for assign_hierarchical_work. Each bucket
8985 // contains classes which will be processed and returned as output,
8986 // and a set of dependency classes which will just be used as inputs.
8987 struct HierarchicalWorkBucket
{
8988 std::vector
<SString
> classes
;
8989 std::vector
<SString
> deps
;
8990 std::vector
<SString
> uninstantiable
;
8994 * Assign work for a set of root classes (using the above
8995 * algorithm). The function is named because it's meant for situations
8996 * where we're processing classes in a "hierarchical" manner (either
8997 * from parent class to children, or from leaf class to parents).
8999 * The dependencies for each class is provided by the getDeps
9000 * callable. For the purposes of promoting a class to a full output
9001 * (see above algorithm description), each class must be assigned an
9002 * index. The (optional) index for a class is provided by the getIdx
9003 * callable. If getIdx returns std::nullopt, then that class won't be
9004 * considered for promotion. The given "numClasses" parameter is an
9005 * upper bound on the possible returned indices.
9007 template <typename GetDeps
, typename GetIdx
>
9008 std::vector
<HierarchicalWorkBucket
>
9009 build_hierarchical_work(std::vector
<std::vector
<SString
>>& buckets
,
9011 const GetDeps
& getDeps
,
9012 const GetIdx
& getIdx
) {
9013 struct DepHashState
{
9015 size_t lowestHash
{std::numeric_limits
<size_t>::max()};
9016 size_t lowestBucket
{std::numeric_limits
<size_t>::max()};
9018 std::vector
<DepHashState
> depHashState
{numClasses
};
9020 // For each bucket (which right now just contains the root classes),
9021 // find all the transitive dependencies those root classes need. A
9022 // dependency might end up in multiple buckets (because multiple
9023 // roots in different buckets depend on it). We only want to
9024 // actually perform the flattening for those dependencies in one of
9025 // the buckets. So, we need a tie-breaker. We hash the name of the
9026 // dependency along with the bucket number. The bucket that the
9027 // dependency is present in with the lowest hash is what "wins".
9028 auto const bucketDeps
= parallel::gen(
9030 [&] (size_t bucketIdx
) {
9031 assertx(bucketIdx
< buckets
.size());
9032 auto& bucket
= buckets
[bucketIdx
];
9033 const TSStringSet roots
{begin(bucket
), end(bucket
)};
9035 // Gather up all dependencies for this bucket
9037 for (auto const cls
: bucket
) {
9038 auto const d
= getDeps(cls
).first
;
9039 deps
.insert(begin(*d
), end(*d
));
9042 // Make sure dependencies and roots are disjoint.
9043 for (auto const c
: bucket
) deps
.erase(c
);
9045 // For each dependency, store the bucket with the lowest hash.
9046 for (auto const d
: deps
) {
9047 auto const idx
= getIdx(roots
, bucketIdx
, d
);
9048 if (!idx
.has_value()) continue;
9049 assertx(*idx
< depHashState
.size());
9050 auto& s
= depHashState
[*idx
];
9051 auto const hash
= hash_int64_pair(
9055 std::lock_guard
<std::mutex
> _
{s
.lock
};
9056 if (hash
< s
.lowestHash
) {
9057 s
.lowestHash
= hash
;
9058 s
.lowestBucket
= bucketIdx
;
9059 } else if (hash
== s
.lowestHash
) {
9060 s
.lowestBucket
= std::min(s
.lowestBucket
, bucketIdx
);
9068 // Now for each bucket, "promote" dependencies into a full input
9069 // class. The dependency is promoted in the bucket with the lowest
9070 // hash, which we've already calculated.
9071 assertx(buckets
.size() == bucketDeps
.size());
9072 return parallel::gen(
9074 [&] (size_t bucketIdx
) {
9075 auto& bucket
= buckets
[bucketIdx
];
9076 auto const& deps
= bucketDeps
[bucketIdx
];
9077 const TSStringSet roots
{begin(bucket
), end(bucket
)};
9079 std::vector
<SString
> depOut
;
9080 depOut
.reserve(deps
.size());
9082 for (auto const d
: deps
) {
9083 // Calculate the hash for the dependency for this bucket. If
9084 // the hash equals the already calculated lowest hash, promote
9086 auto const idx
= getIdx(roots
, bucketIdx
, d
);
9087 if (!idx
.has_value()) {
9088 depOut
.emplace_back(d
);
9091 assertx(*idx
< depHashState
.size());
9092 auto const& s
= depHashState
[*idx
];
9093 auto const hash
= hash_int64_pair(
9097 if (hash
== s
.lowestHash
&& bucketIdx
== s
.lowestBucket
) {
9098 bucket
.emplace_back(d
);
9099 } else if (getDeps(d
).second
) {
9100 // Otherwise keep it as a dependency, but only if it's
9101 // actually instantiable.
9102 depOut
.emplace_back(d
);
9106 // Split off any uninstantiable classes in the bucket.
9107 auto const bucketEnd
= std::partition(
9110 [&] (SString cls
) { return getDeps(cls
).second
; }
9112 std::vector
<SString
> uninstantiable
{bucketEnd
, end(bucket
)};
9113 bucket
.erase(bucketEnd
, end(bucket
));
9115 // Keep deterministic ordering. Make sure there's no duplicates.
9116 std::sort(bucket
.begin(), bucket
.end(), string_data_lt_type
{});
9117 std::sort(depOut
.begin(), depOut
.end(), string_data_lt_type
{});
9118 std::sort(uninstantiable
.begin(), uninstantiable
.end(),
9119 string_data_lt_type
{});
9120 assertx(std::adjacent_find(bucket
.begin(), bucket
.end()) == bucket
.end());
9121 assertx(std::adjacent_find(depOut
.begin(), depOut
.end()) == depOut
.end());
9123 std::adjacent_find(uninstantiable
.begin(), uninstantiable
.end()) ==
9124 uninstantiable
.end()
9127 bucket
.shrink_to_fit();
9128 depOut
.shrink_to_fit();
9129 uninstantiable
.shrink_to_fit();
9131 return HierarchicalWorkBucket
{
9134 std::move(uninstantiable
)
9140 template <typename GetDeps
, typename GetIdx
>
9141 std::vector
<HierarchicalWorkBucket
>
9142 assign_hierarchical_work(std::vector
<SString
> roots
,
9146 const GetDeps
& getDeps
,
9147 const GetIdx
& getIdx
) {
9148 // First turn roots into buckets, and split if any exceed the
9150 auto buckets
= split_buckets(
9151 consistently_bucketize(roots
, bucketSize
),
9153 [&] (SString cls
) -> const TSStringSet
& {
9154 auto const [d
, _
] = getDeps(cls
);
9158 return build_hierarchical_work(buckets
, numClasses
, getDeps
, getIdx
);
9161 //////////////////////////////////////////////////////////////////////
9162 // Class flattening:
9165 s___Sealed("__Sealed"),
9166 s___EnableMethodTraitDiamond("__EnableMethodTraitDiamond"),
9167 s___ModuleLevelTrait("__ModuleLevelTrait");
9170 * Extern-worker job to build ClassInfo2s (which involves flattening
9171 * data across the hierarchy) and flattening traits.
9174 static std::string
name() { return "hhbbc-flatten"; }
9175 static void init(const Config
& config
) {
9176 process_init(config
.o
, config
.gd
, false);
9179 static void fini() { ClassGraph::destroy(); }
9182 * Metadata representing results of flattening. This is information
9183 * that the local coordinator (as opposed to later remote jobs) will
9187 // Classes which have been determined to be uninstantiable
9188 // (therefore have no result output data).
9189 TSStringSet uninstantiable
;
9190 // New closures produced from trait flattening. Such new closures
9191 // will require "fixups" in the php::Program data.
9196 template <typename SerDe
> void serde(SerDe
& sd
) {
9197 sd(unit
)(name
)(context
);
9200 std::vector
<NewClosure
> newClosures
;
9201 // Report parents of each class. A class is a parent of another if
9202 // it would appear on a subclass list. The parents of a closure
9203 // are not reported because that's implicit.
9205 std::vector
<SString
> names
;
9206 template <typename SerDe
> void serde(SerDe
& sd
) { sd(names
); }
9208 std::vector
<Parents
> parents
;
9209 // Classes which are interfaces.
9210 TSStringSet interfaces
;
9211 // Classes which have 86init functions. A class can gain a 86init
9212 // from flattening even if it didn't have it before.
9213 TSStringSet with86init
;
9214 // The types used by the type-constraints of input classes and
9216 std::vector
<TSStringSet
> classTypeUses
;
9217 std::vector
<TSStringSet
> funcTypeUses
;
9218 std::vector
<InterfaceConflicts
> interfaceConflicts
;
9219 template <typename SerDe
> void serde(SerDe
& sd
) {
9220 ScopedStringDataIndexer _
;
9221 sd(uninstantiable
, string_data_lt_type
{})
9224 (interfaces
, string_data_lt_type
{})
9225 (with86init
, string_data_lt_type
{})
9226 (classTypeUses
, string_data_lt_type
{})
9227 (funcTypeUses
, string_data_lt_type
{})
9228 (interfaceConflicts
)
9234 * Job returns a list of (potentially modified) php::Class, a list
9235 * of new ClassInfo2, a list of (potentially modified) php::Func,
9236 * and metadata for the entire job. The order of the lists reflects
9237 * the order of the input classes and functions (skipping over
9238 * classes marked as uninstantiable in the metadata).
9240 using Output
= Multi
<
9241 Variadic
<std::unique_ptr
<php::Class
>>,
9242 Variadic
<std::unique_ptr
<php::ClassBytecode
>>,
9243 Variadic
<std::unique_ptr
<ClassInfo2
>>,
9244 Variadic
<std::unique_ptr
<php::Func
>>,
9245 Variadic
<std::unique_ptr
<FuncInfo2
>>,
9246 Variadic
<std::unique_ptr
<MethodsWithoutCInfo
>>,
9251 * Job takes a list of classes which are to be flattened. In
9252 * addition to this, it also takes a list of classes which are
9253 * dependencies of the classes to be flattened. (A class might be
9254 * one of the inputs *and* a dependency, in which case it should
9255 * just be on the input list). It is expected that *all*
9256 * dependencies are provided. All instantiable classes will have
9257 * their type-constraints resolved to their ultimate type, or left
9258 * as unresolved if it refers to a missing/invalid type. The
9259 * provided functions only have their type-constraints updated. The
9260 * provided type-mappings and list of missing types is used for
9261 * type-constraint resolution (if a type isn't in a type mapping and
9262 * isn't a missing type, it is assumed to be a object type).
9264 * Bytecode needs to be provided for every provided class. The
9265 * bytecode must be provided in the same order as the bytecode's
9268 static Output
run(Variadic
<std::unique_ptr
<php::Class
>> classes
,
9269 Variadic
<std::unique_ptr
<php::Class
>> deps
,
9270 Variadic
<std::unique_ptr
<php::ClassBytecode
>> classBytecode
,
9271 Variadic
<std::unique_ptr
<php::Func
>> funcs
,
9272 Variadic
<std::unique_ptr
<php::Class
>> uninstantiable
,
9273 std::vector
<TypeMapping
> typeMappings
,
9274 std::vector
<SString
> missingTypes
) {
9277 for (auto& tc
: typeMappings
) {
9278 auto const name
= tc
.name
;
9279 always_assert(index
.m_typeMappings
.emplace(name
, std::move(tc
)).second
);
9281 for (auto const m
: missingTypes
) {
9282 always_assert(index
.m_missingTypes
.emplace(m
).second
);
9284 typeMappings
.clear();
9285 missingTypes
.clear();
9287 // Bytecode should have been provided for every class provided.
9289 classBytecode
.vals
.size() == (classes
.vals
.size() + deps
.vals
.size())
9291 // Store the provided bytecode in the matching php::Class.
9293 size
= classBytecode
.vals
.size(),
9294 classesSize
= classes
.vals
.size();
9296 auto& cls
= i
< classesSize
9298 : deps
.vals
[i
- classesSize
];
9299 auto& bytecode
= classBytecode
.vals
[i
];
9301 // We shouldn't have closures here. They're children of the
9303 assertx(!cls
->closureContextCls
);
9304 auto const numMethods
= cls
->methods
.size();
9305 auto const numClosures
= cls
->closures
.size();
9306 always_assert(bytecode
->methodBCs
.size() == numMethods
+ numClosures
);
9308 for (size_t j
= 0, bcSize
= bytecode
->methodBCs
.size(); j
< bcSize
; ++j
) {
9309 if (j
< numMethods
) {
9310 cls
->methods
[j
]->rawBlocks
= std::move(bytecode
->methodBCs
[j
].bc
);
9312 assertx(cls
->closures
[j
-numMethods
]->methods
.size() == 1);
9313 cls
->closures
[j
-numMethods
]->methods
[0]->rawBlocks
=
9314 std::move(bytecode
->methodBCs
[j
].bc
);
9318 classBytecode
.vals
.clear();
9320 // Some classes might be dependencies of another. Moreover, some
9321 // classes might share dependencies. Topologically sort all of the
9322 // classes and process them in that order. Information will flow
9323 // from parent classes to their children.
9324 auto const worklist
= prepare(
9327 TSStringToOneT
<php::Class
*> out
;
9328 out
.reserve(classes
.vals
.size() + deps
.vals
.size());
9329 for (auto const& c
: classes
.vals
) {
9330 always_assert(out
.emplace(c
->name
, c
.get()).second
);
9331 for (auto const& clo
: c
->closures
) {
9332 always_assert(out
.emplace(clo
->name
, clo
.get()).second
);
9335 for (auto const& c
: deps
.vals
) {
9336 always_assert(out
.emplace(c
->name
, c
.get()).second
);
9337 for (auto const& clo
: c
->closures
) {
9338 always_assert(out
.emplace(clo
->name
, clo
.get()).second
);
9345 for (auto const& cls
: uninstantiable
.vals
) {
9346 always_assert(index
.m_uninstantiable
.emplace(cls
->name
).second
);
9347 for (auto const& clo
: cls
->closures
) {
9348 always_assert(index
.m_uninstantiable
.emplace(clo
->name
).second
);
9352 std::vector
<const php::Class
*> newClosures
;
9354 for (auto const cls
: worklist
) {
9356 Trace::hhbbc_index
, kSystemLibBump
, is_systemlib_part(cls
->unit
)
9359 ITRACE(2, "flatten class: {}\n", cls
->name
);
9360 Trace::Indent indent
;
9363 SCOPE_EXIT
{ index
.m_ctx
= nullptr; };
9365 auto state
= std::make_unique
<State
>();
9366 // Attempt to make the ClassInfo2 for this class. If we can't,
9367 // it means the class is not instantiable.
9368 auto newInfo
= make_info(index
, *cls
, *state
);
9370 ITRACE(4, "{} is not instantiable\n", cls
->name
);
9371 always_assert(index
.m_uninstantiable
.emplace(cls
->name
).second
);
9374 auto const cinfo
= newInfo
.get();
9376 ITRACE(5, "adding state for class '{}' to local index\n", cls
->name
);
9377 assertx(cinfo
->name
->tsame(cls
->name
));
9379 // We might look up this class when flattening itself, so add it
9380 // to the local index before we start.
9381 always_assert(index
.m_classes
.emplace(cls
->name
, cls
).second
);
9383 index
.m_classInfos
.emplace(cls
->name
, std::move(newInfo
)).second
9386 auto const [stateIt
, stateSuccess
] =
9387 index
.m_states
.emplace(cls
->name
, std::move(state
));
9388 always_assert(stateSuccess
);
9390 auto closureIdx
= cls
->closures
.size();
9391 auto closures
= flatten_traits(index
, *cls
, *cinfo
, *stateIt
->second
);
9393 // Trait flattening may produce new closures, so those need to
9394 // be added to the local index as well.
9395 for (auto& i
: closures
) {
9396 assertx(closureIdx
< cls
->closures
.size());
9397 auto& c
= cls
->closures
[closureIdx
++];
9398 ITRACE(5, "adding state for closure '{}' to local index\n", c
->name
);
9399 assertx(!is_closure(*cls
));
9400 assertx(c
->name
->tsame(i
->name
));
9401 assertx(is_closure(*c
));
9402 assertx(c
->closureContextCls
);
9403 assertx(c
->closures
.empty());
9404 assertx(i
->closures
.empty());
9405 always_assert(index
.m_classes
.emplace(c
->name
, c
.get()).second
);
9406 always_assert(index
.m_classInfos
.emplace(c
->name
, std::move(i
)).second
);
9407 newClosures
.emplace_back(c
.get());
9411 begin(cls
->closures
), end(cls
->closures
),
9412 [] (const std::unique_ptr
<php::Class
>& c1
,
9413 const std::unique_ptr
<php::Class
>& c2
) {
9414 return string_data_lt_type
{}(c1
->name
, c2
->name
);
9418 // We're done with this class. All of it's parents are now
9420 cinfo
->classGraph
.finalizeParents();
9423 // Format the output data and put it in a deterministic order.
9424 Variadic
<std::unique_ptr
<php::Class
>> outClasses
;
9425 Variadic
<std::unique_ptr
<ClassInfo2
>> outInfos
;
9426 Variadic
<std::unique_ptr
<MethodsWithoutCInfo
>> outMethods
;
9428 TSStringSet outNames
;
9430 outClasses
.vals
.reserve(classes
.vals
.size());
9431 outInfos
.vals
.reserve(classes
.vals
.size());
9432 outNames
.reserve(classes
.vals
.size());
9433 outMeta
.parents
.reserve(classes
.vals
.size());
9434 outMeta
.newClosures
.reserve(newClosures
.size());
9436 auto const makeMethodsWithoutCInfo
= [&] (const php::Class
& cls
) {
9437 always_assert(outMeta
.uninstantiable
.emplace(cls
.name
).second
);
9438 // Even though the class is uninstantiable, we still need to
9439 // create FuncInfos for it's methods. These are stored
9440 // separately (there's no ClassInfo to store it in!)
9441 auto methods
= std::make_unique
<MethodsWithoutCInfo
>();
9442 methods
->cls
= cls
.name
;
9443 for (auto const& func
: cls
.methods
) {
9444 methods
->finfos
.emplace_back(make_func_info(index
, *func
));
9446 for (auto const& clo
: cls
.closures
) {
9447 assertx(clo
->methods
.size() == 1);
9448 methods
->closureInvokes
.emplace_back(
9449 make_func_info(index
, *clo
->methods
[0])
9452 outMethods
.vals
.emplace_back(std::move(methods
));
9455 // Do the processing which relies on a fully accessible
9458 TSStringToOneT
<InterfaceConflicts
> ifaceConflicts
;
9459 for (auto& cls
: classes
.vals
) {
9460 assertx(!cls
->closureContextCls
);
9461 auto const cinfoIt
= index
.m_classInfos
.find(cls
->name
);
9462 if (cinfoIt
== end(index
.m_classInfos
)) {
9464 4, "{} discovered to be not instantiable, instead "
9465 "creating MethodsWithoutCInfo for it\n",
9468 always_assert(index
.uninstantiable(cls
->name
));
9469 makeMethodsWithoutCInfo(*cls
);
9472 auto& cinfo
= cinfoIt
->second
;
9474 index
.m_ctx
= cls
.get();
9475 SCOPE_EXIT
{ index
.m_ctx
= nullptr; };
9477 outMeta
.classTypeUses
.emplace_back();
9478 update_type_constraints(index
, *cls
, &outMeta
.classTypeUses
.back());
9479 optimize_properties(index
, *cls
, *cinfo
);
9480 for (auto const& func
: cls
->methods
) {
9481 cinfo
->funcInfos
.emplace_back(make_func_info(index
, *func
));
9484 assertx(cinfo
->closures
.empty());
9485 for (auto& clo
: cls
->closures
) {
9486 auto const it
= index
.m_classInfos
.find(clo
->name
);
9487 always_assert(it
!= end(index
.m_classInfos
));
9488 auto& cloinfo
= it
->second
;
9489 update_type_constraints(index
, *clo
, &outMeta
.classTypeUses
.back());
9490 optimize_properties(index
, *clo
, *cloinfo
);
9491 assertx(clo
->methods
.size() == 1);
9492 cloinfo
->funcInfos
.emplace_back(
9493 make_func_info(index
, *clo
->methods
[0])
9497 outNames
.emplace(cls
->name
);
9499 // Record interface conflicts
9501 // Only consider normal or abstract classes
9503 (AttrInterface
| AttrTrait
| AttrEnum
| AttrEnumClass
)) {
9507 auto const interfaces
= cinfo
->classGraph
.interfaces();
9510 always_assert(IMPLIES(is_closure(*cls
), interfaces
.empty()));
9511 for (auto const& cloinfo
: cinfo
->closures
) {
9512 always_assert(cloinfo
->classGraph
.interfaces().empty());
9516 for (auto const i1
: interfaces
) {
9517 auto& conflicts
= ifaceConflicts
[i1
.name()];
9518 conflicts
.name
= i1
.name();
9520 for (auto const i2
: interfaces
) {
9521 if (i1
== i2
) continue;
9522 conflicts
.conflicts
.emplace(i2
.name());
9527 outMeta
.interfaceConflicts
.reserve(ifaceConflicts
.size());
9528 for (auto& [_
, c
] : ifaceConflicts
) {
9529 outMeta
.interfaceConflicts
.emplace_back(std::move(c
));
9532 begin(outMeta
.interfaceConflicts
),
9533 end(outMeta
.interfaceConflicts
),
9534 [] (auto const& c1
, auto const& c2
) {
9535 return string_data_lt_type
{}(c1
.name
, c2
.name
);
9539 // We don't process classes marked as uninstantiable beforehand,
9540 // except for creating method FuncInfos for them.
9541 for (auto const& cls
: uninstantiable
.vals
) {
9543 4, "{} already known to be not instantiable, creating "
9544 "MethodsWithoutCInfo for it\n",
9547 makeMethodsWithoutCInfo(*cls
);
9550 // Now move the classes out of LocalIndex and into the output. At
9551 // this point, it's not safe to access the LocalIndex unless
9552 // you're sure something hasn't been moved yet.
9553 for (auto& cls
: classes
.vals
) {
9554 auto const name
= cls
->name
;
9556 auto const cinfoIt
= index
.m_classInfos
.find(name
);
9557 if (cinfoIt
== end(index
.m_classInfos
)) {
9558 assertx(outMeta
.uninstantiable
.count(name
));
9561 auto& cinfo
= cinfoIt
->second
;
9563 // Check if this class has a 86*init function (it might have
9564 // already or might have gained one from trait flattening).
9565 auto const has86init
=
9567 begin(cls
->methods
), end(cls
->methods
),
9568 [] (auto const& m
) { return is_86init_func(*m
); }
9571 begin(cinfo
->clsConstants
), end(cinfo
->clsConstants
),
9572 [] (auto const& cns
) {
9573 return cns
.second
.kind
== ConstModifiers::Kind::Type
;
9577 assertx(!is_closure(*cls
));
9578 outMeta
.with86init
.emplace(name
);
9581 index
.m_ctx
= cls
.get();
9582 SCOPE_EXIT
{ index
.m_ctx
= nullptr; };
9584 // For building FuncFamily::StaticInfo, we need to ensure that
9585 // every method has an entry in methodFamilies. Make all of the
9586 // initial entries here (they'll be created assuming this method
9587 // is AttrNoOverride).
9588 for (auto const& [methname
, mte
] : cinfo
->methods
) {
9589 if (is_special_method_name(methname
)) continue;
9590 auto entry
= make_initial_func_family_entry(*cls
, index
.meth(mte
), mte
);
9592 cinfo
->methodFamilies
.emplace(methname
, std::move(entry
)).second
9596 if (!is_closure(*cls
)) {
9597 auto const& state
= index
.m_states
.at(name
);
9598 outMeta
.parents
.emplace_back();
9599 auto& parents
= outMeta
.parents
.back().names
;
9600 parents
.reserve(state
->m_parents
.size());
9601 for (auto const p
: state
->m_parents
) {
9602 parents
.emplace_back(p
->name
);
9606 if (cls
->attrs
& AttrInterface
) {
9607 outMeta
.interfaces
.emplace(name
);
9610 // We always know the subclass status of closures and the
9611 // closure base class.
9612 if (is_closure_base(*cls
)) {
9613 cinfo
->classGraph
.setClosureBase();
9614 } else if (is_closure(*cls
)) {
9615 cinfo
->classGraph
.setComplete();
9618 assertx(cinfo
->closures
.empty());
9619 for (auto& clo
: cls
->closures
) {
9620 auto const it
= index
.m_classInfos
.find(clo
->name
);
9621 always_assert(it
!= end(index
.m_classInfos
));
9622 auto& cloinfo
= it
->second
;
9624 // Closures are always leafs.
9625 cloinfo
->classGraph
.setComplete();
9626 assertx(!cloinfo
->classGraph
.mightHaveRegularSubclass());
9627 assertx(!cloinfo
->classGraph
.mightHaveNonRegularSubclass());
9629 for (auto const& [methname
, mte
] : cloinfo
->methods
) {
9630 if (is_special_method_name(methname
)) continue;
9632 make_initial_func_family_entry(*clo
, index
.meth(mte
), mte
);
9634 cloinfo
->methodFamilies
.emplace(methname
, std::move(entry
)).second
9638 cinfo
->closures
.emplace_back(std::move(cloinfo
));
9641 outClasses
.vals
.emplace_back(std::move(cls
));
9642 outInfos
.vals
.emplace_back(std::move(cinfo
));
9646 begin(newClosures
), end(newClosures
),
9647 [] (const php::Class
* c1
, const php::Class
* c2
) {
9648 return string_data_lt_type
{}(c1
->name
, c2
->name
);
9651 for (auto clo
: newClosures
) {
9652 assertx(clo
->closureContextCls
);
9653 if (!outNames
.count(clo
->closureContextCls
)) continue;
9654 outMeta
.newClosures
.emplace_back(
9655 OutputMeta::NewClosure
{clo
->unit
, clo
->name
, clo
->closureContextCls
}
9659 Variadic
<std::unique_ptr
<FuncInfo2
>> funcInfos
;
9660 funcInfos
.vals
.reserve(funcs
.vals
.size());
9661 for (auto& func
: funcs
.vals
) {
9662 outMeta
.funcTypeUses
.emplace_back();
9663 update_type_constraints(index
, *func
, &outMeta
.funcTypeUses
.back());
9664 funcInfos
.vals
.emplace_back(make_func_info(index
, *func
));
9667 // Provide any updated bytecode back to the caller.
9668 Variadic
<std::unique_ptr
<php::ClassBytecode
>> outBytecode
;
9669 outBytecode
.vals
.reserve(outClasses
.vals
.size());
9670 for (auto& cls
: outClasses
.vals
) {
9671 auto bytecode
= std::make_unique
<php::ClassBytecode
>();
9672 bytecode
->cls
= cls
->name
;
9673 bytecode
->methodBCs
.reserve(cls
->methods
.size());
9674 for (auto& method
: cls
->methods
) {
9675 bytecode
->methodBCs
.emplace_back(
9677 std::move(method
->rawBlocks
)
9680 for (auto& clo
: cls
->closures
) {
9681 assertx(clo
->methods
.size() == 1);
9682 auto& method
= clo
->methods
[0];
9683 bytecode
->methodBCs
.emplace_back(
9685 std::move(method
->rawBlocks
)
9688 outBytecode
.vals
.emplace_back(std::move(bytecode
));
9691 return std::make_tuple(
9692 std::move(outClasses
),
9693 std::move(outBytecode
),
9694 std::move(outInfos
),
9696 std::move(funcInfos
),
9697 std::move(outMethods
),
9704 * State which needs to be propagated from a dependency to a child
9705 * class during flattening, but not required after flattening (so
9706 * doesn't belong in ClassInfo2).
9714 // Maintain order of properties as we inherit them.
9715 CompactVector
<PropTuple
> m_props
;
9716 SStringToOneT
<size_t> m_propIndices
;
9717 CompactVector
<php::Const
> m_traitCns
;
9718 SStringSet m_cnsFromTrait
;
9719 SStringToOneT
<size_t> m_methodIndices
;
9720 CompactVector
<const php::Class
*> m_parents
;
9722 size_t& methodIdx(SString context
, SString cls
, SString name
) {
9723 auto const it
= m_methodIndices
.find(name
);
9725 it
!= m_methodIndices
.end(),
9726 "While processing '{}', "
9727 "tried to access missing method index for '{}::{}'",
9733 size_t methodIdx(SString context
, SString cls
, SString name
) const {
9734 return const_cast<State
*>(this)->methodIdx(context
, cls
, name
);
9739 * LocalIndex is similar to Index, but for this job. It maps names
9740 * to class information needed during flattening. It also verifies
9741 * we don't try to access information about a class until it's
9742 * actually available (which shouldn't happen if our dataflow is
9746 const php::Class
* m_ctx
{nullptr};
9748 TSStringToOneT
<const php::Class
*> m_classes
;
9749 TSStringToOneT
<std::unique_ptr
<ClassInfo2
>> m_classInfos
;
9750 TSStringToOneT
<std::unique_ptr
<State
>> m_states
;
9752 TSStringSet m_uninstantiable
;
9754 TSStringToOneT
<TypeMapping
> m_typeMappings
;
9755 TSStringSet m_missingTypes
;
9757 const php::Class
& cls(SString name
) const {
9758 if (m_ctx
->name
->tsame(name
)) return *m_ctx
;
9759 auto const it
= m_classes
.find(name
);
9761 it
!= m_classes
.end(),
9762 "While processing '{}', tried to access missing class '{}' from index",
9766 assertx(it
->second
);
9770 const ClassInfo2
& classInfo(SString name
) const {
9771 auto const it
= m_classInfos
.find(name
);
9773 it
!= m_classInfos
.end(),
9774 "While processing '{}', tried to access missing class-info for '{}' "
9779 assertx(it
->second
.get());
9783 const State
& state(SString name
) const {
9784 auto const it
= m_states
.find(name
);
9786 it
!= m_states
.end(),
9787 "While processing '{}', tried to access missing flatten state for '{}' "
9792 assertx(it
->second
.get());
9796 bool uninstantiable(SString name
) const {
9797 return m_uninstantiable
.count(name
);
9800 const TypeMapping
* typeMapping(SString name
) const {
9801 return folly::get_ptr(m_typeMappings
, name
);
9804 bool missingType(SString name
) const {
9805 return m_missingTypes
.count(name
);
9808 const php::Func
& meth(const MethRef
& r
) const {
9809 auto const& mcls
= cls(r
.cls
);
9810 assertx(r
.idx
< mcls
.methods
.size());
9811 return *mcls
.methods
[r
.idx
];
9813 const php::Func
& meth(const MethTabEntry
& mte
) const {
9814 return meth(mte
.meth());
9817 const php::Const
& cns(const ConstIndex
& idx
) const {
9818 auto const& c
= cls(idx
.cls
);
9819 assertx(idx
.idx
< c
.constants
.size());
9820 return c
.constants
[idx
.idx
];
9823 size_t methodIdx(SString cls
, SString name
) const {
9824 return state(cls
).methodIdx(m_ctx
->name
, cls
, name
);
9829 * Calculate the order in which the classes should be flattened,
9830 * taking into account dependencies.
9832 static std::vector
<php::Class
*> prepare(
9834 const TSStringToOneT
<php::Class
*>& classes
9836 // We might not have any classes if we're just processing funcs.
9837 if (classes
.empty()) return {};
9839 auto const get
= [&] (SString name
) -> php::Class
& {
9840 auto const it
= classes
.find(name
);
9842 it
!= classes
.end(),
9843 "Tried to access missing class '{}' while calculating flattening order",
9849 auto const forEachDep
= [&] (php::Class
& c
, auto const& f
) {
9850 if (c
.parentName
) f(get(c
.parentName
));
9851 for (auto const i
: c
.interfaceNames
) f(get(i
));
9852 for (auto const e
: c
.includedEnumNames
) f(get(e
));
9853 for (auto const t
: c
.usedTraitNames
) f(get(t
));
9854 for (auto const& clo
: c
.closures
) {
9855 f(const_cast<php::Class
&>(*clo
));
9860 * Perform a standard topological sort:
9862 * - For each class, calculate the number of classes which depend on it.
9864 * - Any class which has a use count of zero is not depended on by
9865 * anyone and goes onto the intitial worklist.
9867 * - For every class on the worklist, push it onto the output
9868 * list, and decrement the use count of all of it's
9871 * - For any class which now has a use count of zero, push it onto
9872 * the worklist and repeat above step until all classes are
9873 * pushed onto the output list.
9875 * - Reverse the list.
9877 * - This does not handle cycles, but we should not encounter any
9878 * here, as such cycles should be detected earlier and not be
9879 * scheduled in a job.
9881 hphp_fast_map
<const php::Class
*, size_t> uses
;
9882 uses
.reserve(classes
.size());
9883 for (auto const& [_
, cls
] : classes
) {
9884 forEachDep(*cls
, [&] (const php::Class
& d
) { ++uses
[&d
]; });
9887 std::vector
<php::Class
*> worklist
;
9888 for (auto const [_
, cls
] : classes
) {
9889 if (!uses
[cls
]) worklist
.emplace_back(cls
);
9891 always_assert(!worklist
.empty());
9895 [] (const php::Class
* c1
, const php::Class
* c2
) {
9896 return string_data_lt_type
{}(c1
->name
, c2
->name
);
9900 std::vector
<php::Class
*> ordered
;
9901 ordered
.reserve(classes
.size());
9903 auto const cls
= worklist
.back();
9904 assertx(!uses
[cls
]);
9905 worklist
.pop_back();
9908 [&] (php::Class
& d
) {
9909 if (!--uses
.at(&d
)) worklist
.emplace_back(&d
);
9912 ordered
.emplace_back(cls
);
9913 } while (!worklist
.empty());
9915 for (auto const& [_
, cls
] : classes
) always_assert(!uses
.at(cls
));
9916 std::reverse(ordered
.begin(), ordered
.end());
9921 * Create a FuncFamilyEntry for the give method. This
9922 * FuncFamilyEntry assumes that the method is AttrNoOverride, and
9923 * hence reflects just this method. The method isn't necessarily
9924 * actually AttrNoOverride, but if not, it will be updated in
9925 * BuildSubclassListJob (that job needs the initial entries).
9927 static FuncFamilyEntry
make_initial_func_family_entry(
9928 const php::Class
& cls
,
9929 const php::Func
& meth
,
9930 const MethTabEntry
& mte
9932 FuncFamilyEntry entry
;
9933 entry
.m_allIncomplete
= false;
9934 entry
.m_regularIncomplete
= false;
9935 entry
.m_privateAncestor
= is_regular_class(cls
) && mte
.hasPrivateAncestor();
9937 FuncFamilyEntry::MethMetadata meta
;
9939 for (size_t i
= 0; i
< meth
.params
.size(); ++i
) {
9940 meta
.m_prepKinds
.emplace_back(func_param_prep(&meth
, i
));
9942 // Any param beyond the size of m_paramPreps is implicitly
9943 // TriBool::No, so we can drop trailing entries which are
9945 while (!meta
.m_prepKinds
.empty()) {
9946 auto& back
= meta
.m_prepKinds
.back();
9947 if (back
.inOut
!= TriBool::No
|| back
.readonly
!= TriBool::No
) break;
9948 meta
.m_prepKinds
.pop_back();
9950 meta
.m_numInOut
= func_num_inout(&meth
);
9951 meta
.m_nonVariadicParams
= numNVArgs(meth
);
9952 meta
.m_coeffectRules
= meth
.coeffectRules
;
9953 meta
.m_requiredCoeffects
= meth
.requiredCoeffects
;
9954 meta
.m_isReadonlyReturn
= meth
.isReadonlyReturn
;
9955 meta
.m_isReadonlyThis
= meth
.isReadonlyThis
;
9956 meta
.m_supportsAER
= func_supports_AER(&meth
);
9957 meta
.m_isReified
= meth
.isReified
;
9958 meta
.m_caresAboutDyncalls
= (dyn_call_error_level(&meth
) > 0);
9959 meta
.m_builtin
= meth
.attrs
& AttrBuiltin
;
9961 if (is_regular_class(cls
)) {
9963 FuncFamilyEntry::BothSingle
{mte
.meth(), std::move(meta
), false};
9964 } else if (bool(mte
.attrs
& AttrPrivate
) && mte
.topLevel()) {
9966 FuncFamilyEntry::BothSingle
{mte
.meth(), std::move(meta
), true};
9969 FuncFamilyEntry::SingleAndNone
{mte
.meth(), std::move(meta
)};
9975 static std::unique_ptr
<ClassInfo2
> make_info(const LocalIndex
& index
,
9978 if (debug
&& (is_closure(cls
) || is_closure_base(cls
))) {
9979 if (is_closure(cls
)) {
9980 always_assert(cls
.parentName
->tsame(s_Closure
.get()));
9982 always_assert(!cls
.parentName
);
9984 always_assert(cls
.interfaceNames
.empty());
9985 always_assert(cls
.includedEnumNames
.empty());
9986 always_assert(cls
.usedTraitNames
.empty());
9987 always_assert(cls
.requirements
.empty());
9988 always_assert(cls
.constants
.empty());
9989 always_assert(cls
.userAttributes
.empty());
9990 always_assert(!(cls
.attrs
& (AttrTrait
| AttrInterface
| AttrAbstract
)));
9993 // Set up some initial values for ClassInfo properties. If this
9994 // class is a leaf (we can't actually determine that yet), these
9995 // will be valid and remain as-is. If not, they'll be updated
9996 // properly when calculating subclass information in another pass.
9997 auto cinfo
= std::make_unique
<ClassInfo2
>();
9998 cinfo
->name
= cls
.name
;
9999 cinfo
->hasConstProp
= cls
.hasConstProp
;
10000 cinfo
->hasReifiedParent
= cls
.hasReifiedGenerics
;
10001 cinfo
->hasReifiedGeneric
= cls
.userAttributes
.count(s___Reified
.get());
10002 cinfo
->subHasReifiedGeneric
= cinfo
->hasReifiedGeneric
;
10003 cinfo
->initialNoReifiedInit
= cls
.attrs
& AttrNoReifiedInit
;
10004 cinfo
->isMockClass
= is_mock_class(&cls
);
10005 cinfo
->isRegularClass
= is_regular_class(cls
);
10007 // Create a ClassGraph for this class. If we decide to not keep
10008 // the ClassInfo, reset the ClassGraph to keep it from ending up
10010 cinfo
->classGraph
= ClassGraph::create(cls
);
10011 auto success
= false;
10012 SCOPE_EXIT
{ if (!success
) cinfo
->classGraph
.reset(); };
10014 // Assume the class isn't overridden. This is true for leafs and
10015 // non-leafs will get updated when we build subclass information.
10016 if (!is_closure_base(cls
)) {
10017 attribute_setter(cls
.attrs
, true, AttrNoOverride
);
10018 attribute_setter(cls
.attrs
, true, AttrNoOverrideRegular
);
10020 attribute_setter(cls
.attrs
, false, AttrNoOverride
);
10021 attribute_setter(cls
.attrs
, false, AttrNoOverrideRegular
);
10024 // Assume this. If not a leaf, will be updated in
10025 // BuildSubclassList job.
10026 attribute_setter(cls
.attrs
, true, AttrNoMock
);
10028 for (auto const& clo
: cls
.closures
) {
10029 if (index
.uninstantiable(clo
->name
)) {
10031 "Making class-info failed for `{}' because "
10032 "its closure `{}' is uninstantiable\n",
10033 cls
.name
, clo
->name
);
10038 if (cls
.parentName
) {
10039 assertx(!is_closure_base(cls
));
10040 assertx(is_closure(cls
) == cls
.parentName
->tsame(s_Closure
.get()));
10042 if (index
.uninstantiable(cls
.parentName
)) {
10044 "Making class-info failed for `{}' because "
10045 "its parent `{}' is uninstantiable\n",
10046 cls
.name
, cls
.parentName
);
10049 auto const& parent
= index
.cls(cls
.parentName
);
10050 auto const& parentInfo
= index
.classInfo(cls
.parentName
);
10052 assertx(!is_closure(parent
));
10053 if (parent
.attrs
& (AttrInterface
| AttrTrait
)) {
10055 "Making class-info failed for `{}' because "
10056 "its parent `{}' is not a class\n",
10057 cls
.name
, cls
.parentName
);
10060 if (!enforce_sealing(*cinfo
, cls
, parent
)) return nullptr;
10062 cinfo
->parent
= cls
.parentName
;
10063 cinfo
->hasConstProp
|= parentInfo
.hasConstProp
;
10064 cinfo
->hasReifiedParent
|= parentInfo
.hasReifiedParent
;
10066 state
.m_parents
.emplace_back(&parent
);
10067 cinfo
->classGraph
.setBase(parentInfo
.classGraph
);
10068 } else if (!cinfo
->hasReifiedGeneric
) {
10069 attribute_setter(cls
.attrs
, true, AttrNoReifiedInit
);
10072 for (auto const iname
: cls
.interfaceNames
) {
10073 assertx(!is_closure(cls
));
10074 assertx(!is_closure_base(cls
));
10075 if (index
.uninstantiable(iname
)) {
10077 "Making class-info failed for `{}' because "
10078 "{} is uninstantiable\n",
10082 auto const& iface
= index
.cls(iname
);
10083 auto const& ifaceInfo
= index
.classInfo(iname
);
10085 assertx(!is_closure(iface
));
10086 if (!(iface
.attrs
& AttrInterface
)) {
10088 "Making class-info failed for `{}' because `{}' "
10089 "is not an interface\n",
10093 if (!enforce_sealing(*cinfo
, cls
, iface
)) return nullptr;
10095 cinfo
->hasReifiedParent
|= ifaceInfo
.hasReifiedParent
;
10097 state
.m_parents
.emplace_back(&iface
);
10098 cinfo
->classGraph
.addParent(ifaceInfo
.classGraph
);
10101 for (auto const ename
: cls
.includedEnumNames
) {
10102 assertx(!is_closure(cls
));
10103 assertx(!is_closure_base(cls
));
10104 if (index
.uninstantiable(ename
)) {
10106 "Making class-info failed for `{}' because "
10107 "{} is uninstantiable\n",
10111 auto const& e
= index
.cls(ename
);
10112 auto const& einfo
= index
.classInfo(ename
);
10114 assertx(!is_closure(e
));
10115 auto const wantAttr
= cls
.attrs
& (AttrEnum
| AttrEnumClass
);
10116 if (!(e
.attrs
& wantAttr
)) {
10118 "Making class-info failed for `{}' because `{}' "
10119 "is not an enum{}\n",
10121 wantAttr
& AttrEnumClass
? " class" : "");
10124 if (!enforce_sealing(*cinfo
, cls
, e
)) return nullptr;
10126 for (auto const iface
: einfo
.classGraph
.declInterfaces()) {
10127 cinfo
->classGraph
.addParent(iface
);
10131 auto const clsHasModuleLevelTrait
=
10132 cls
.userAttributes
.count(s___ModuleLevelTrait
.get());
10133 if (clsHasModuleLevelTrait
&&
10134 (!(cls
.attrs
& AttrTrait
) || (cls
.attrs
& AttrInternal
))) {
10136 "Making class-info failed for `{}' because "
10137 "attribute <<__ModuleLevelTrait>> can only be "
10138 "specified on public traits\n",
10143 for (auto const tname
: cls
.usedTraitNames
) {
10144 assertx(!is_closure(cls
));
10145 assertx(!is_closure_base(cls
));
10146 if (index
.uninstantiable(tname
)) {
10148 "Making class-info failed for `{}' because "
10149 "{} is uninstantiable\n",
10153 auto const& trait
= index
.cls(tname
);
10154 auto const& traitInfo
= index
.classInfo(tname
);
10156 assertx(!is_closure(trait
));
10157 if (!(trait
.attrs
& AttrTrait
)) {
10159 "Making class-info failed for `{}' because `{}' "
10160 "is not a trait\n",
10164 if (!enforce_sealing(*cinfo
, cls
, trait
)) return nullptr;
10166 cinfo
->hasConstProp
|= traitInfo
.hasConstProp
;
10167 cinfo
->hasReifiedParent
|= traitInfo
.hasReifiedParent
;
10169 state
.m_parents
.emplace_back(&trait
);
10170 cinfo
->classGraph
.addParent(traitInfo
.classGraph
);
10173 if (cls
.attrs
& AttrEnum
) {
10174 auto const baseType
= [&] {
10175 auto const& base
= cls
.enumBaseTy
;
10176 if (!base
.isUnresolved()) return base
.type();
10177 auto const tm
= index
.typeMapping(base
.typeName());
10178 if (!tm
) return AnnotType::Unresolved
;
10179 // enums cannot use case types
10180 assertx(!tm
->value
.isUnion());
10181 return tm
->value
.type();
10183 if (!enumSupportsAnnot(baseType
)) {
10185 "Making class-info failed for `{}' because {} "
10186 "is not a valid enum base type\n",
10187 cls
.name
, annotName(baseType
));
10192 if (!build_methods(index
, cls
, *cinfo
, state
)) return nullptr;
10193 if (!build_properties(index
, cls
, *cinfo
, state
)) return nullptr;
10194 if (!build_constants(index
, cls
, *cinfo
, state
)) return nullptr;
10197 begin(state
.m_parents
),
10198 end(state
.m_parents
),
10199 [] (const php::Class
* a
, const php::Class
* b
) {
10200 return string_data_lt_type
{}(a
->name
, b
->name
);
10203 state
.m_parents
.erase(
10204 std::unique(begin(state
.m_parents
), end(state
.m_parents
)),
10205 end(state
.m_parents
)
10209 begin(state
.m_parents
), end(state
.m_parents
),
10210 [] (const php::Class
* c
) { return is_closure(*c
); }
10214 cinfo
->subHasConstProp
= cinfo
->hasConstProp
;
10216 // All methods are originally not overridden (we'll update this as
10217 // necessary later), except for special methods, which are always
10218 // considered to be overridden.
10219 for (auto& [name
, mte
] : cinfo
->methods
) {
10220 assertx(!cinfo
->missingMethods
.count(name
));
10221 if (is_special_method_name(name
)) {
10222 attribute_setter(mte
.attrs
, false, AttrNoOverride
);
10223 mte
.clearNoOverrideRegular();
10225 attribute_setter(mte
.attrs
, true, AttrNoOverride
);
10226 mte
.setNoOverrideRegular();
10230 // We don't calculate subclass information for closures, so make
10231 // sure their initial values are all what they should be.
10232 if (debug
&& (is_closure(cls
) || is_closure_base(cls
))) {
10233 if (is_closure(cls
)) {
10234 always_assert(is_closure_name(cls
.name
));
10235 always_assert(state
.m_parents
.size() == 1);
10236 always_assert(state
.m_parents
[0]->name
->tsame(s_Closure
.get()));
10237 always_assert(!(cls
.attrs
& AttrNoReifiedInit
));
10239 always_assert(state
.m_parents
.empty());
10240 always_assert(cls
.attrs
& AttrNoReifiedInit
);
10242 always_assert(cinfo
->missingMethods
.empty());
10243 always_assert(!cinfo
->hasConstProp
);
10244 always_assert(!cinfo
->subHasConstProp
);
10245 always_assert(!cinfo
->hasReifiedParent
);
10246 always_assert(!cinfo
->hasReifiedGeneric
);
10247 always_assert(!cinfo
->subHasReifiedGeneric
);
10248 always_assert(!cinfo
->initialNoReifiedInit
);
10249 always_assert(!cinfo
->isMockClass
);
10250 always_assert(cinfo
->isRegularClass
);
10251 always_assert(!is_mock_class(&cls
));
10254 ITRACE(2, "new class-info: {}\n", cls
.name
);
10255 if (Trace::moduleEnabled(Trace::hhbbc_index
, 3)) {
10256 if (cinfo
->parent
) {
10257 ITRACE(3, " parent: {}\n", cinfo
->parent
);
10259 auto const cg
= cinfo
->classGraph
;
10260 for (auto const DEBUG_ONLY base
: cg
.bases()) {
10261 ITRACE(3, " base: {}\n", base
.name());
10263 for (auto const DEBUG_ONLY iface
: cls
.interfaceNames
) {
10264 ITRACE(3, " decl implements: {}\n", iface
);
10266 for (auto const DEBUG_ONLY iface
: cg
.interfaces()) {
10267 ITRACE(3, " implements: {}\n", iface
.name());
10269 for (auto const DEBUG_ONLY e
: cls
.includedEnumNames
) {
10270 ITRACE(3, " enum: {}\n", e
);
10272 for (auto const DEBUG_ONLY trait
: cls
.usedTraitNames
) {
10273 ITRACE(3, " uses: {}\n", trait
);
10275 for (auto const& DEBUG_ONLY closure
: cls
.closures
) {
10276 ITRACE(3, " closure: {}\n", closure
->name
);
10280 // We're going to use this ClassInfo.
10285 static bool enforce_sealing(const ClassInfo2
& cinfo
,
10286 const php::Class
& cls
,
10287 const php::Class
& parent
) {
10288 if (is_mock_class(&cls
)) return true;
10289 if (!(parent
.attrs
& AttrSealed
)) return true;
10290 auto const it
= parent
.userAttributes
.find(s___Sealed
.get());
10291 assertx(it
!= parent
.userAttributes
.end());
10292 assertx(tvIsArrayLike(it
->second
));
10293 auto allowed
= false;
10295 it
->second
.m_data
.parr
,
10296 [&] (TypedValue v
) {
10297 assertx(tvIsStringLike(v
));
10298 if (tvAssertStringLike(v
)->tsame(cinfo
.name
)) {
10308 "Making class-info failed for `{}' because "
10309 "`{}' is sealed\n",
10310 cinfo
.name
, parent
.name
10316 static bool build_properties(const LocalIndex
& index
,
10317 const php::Class
& cls
,
10320 if (cls
.parentName
) {
10321 auto const& parentState
= index
.state(cls
.parentName
);
10322 state
.m_props
= parentState
.m_props
;
10323 state
.m_propIndices
= parentState
.m_propIndices
;
10326 for (auto const iface
: cls
.interfaceNames
) {
10327 if (!merge_properties(cinfo
, state
, index
.state(iface
))) {
10331 for (auto const trait
: cls
.usedTraitNames
) {
10332 if (!merge_properties(cinfo
, state
, index
.state(trait
))) {
10336 for (auto const e
: cls
.includedEnumNames
) {
10337 if (!merge_properties(cinfo
, state
, index
.state(e
))) {
10342 if (cls
.attrs
& AttrInterface
) return true;
10344 auto const cannotDefineInternalProperties
=
10345 // public traits cannot define internal properties unless they
10346 // have the __ModuleLevelTrait attribute
10347 ((cls
.attrs
& AttrTrait
) && (cls
.attrs
& AttrPublic
)) &&
10348 !(cls
.userAttributes
.count(s___ModuleLevelTrait
.get()));
10350 for (auto const& p
: cls
.properties
) {
10351 if (cannotDefineInternalProperties
&& (p
.attrs
& AttrInternal
)) {
10353 "Adding property failed for `{}' because property `{}' "
10354 "is internal and public traits cannot define internal properties\n",
10355 cinfo
.name
, p
.name
);
10358 if (!add_property(cinfo
, state
, p
.name
, p
, cinfo
.name
, false)) {
10363 // There's no need to do this work if traits have been flattened
10364 // already, or if the top level class has no traits. In those
10365 // cases, we might be able to rule out some instantiations, but it
10366 // doesn't seem worth it.
10367 if (cls
.attrs
& AttrNoExpandTrait
) return true;
10369 for (auto const traitName
: cls
.usedTraitNames
) {
10370 auto const& trait
= index
.cls(traitName
);
10371 auto const& traitInfo
= index
.classInfo(traitName
);
10372 for (auto const& p
: trait
.properties
) {
10373 if (!add_property(cinfo
, state
, p
.name
, p
, cinfo
.name
, true)) {
10377 for (auto const& p
: traitInfo
.traitProps
) {
10378 if (!add_property(cinfo
, state
, p
.name
, p
, cinfo
.name
, true)) {
10387 static bool add_property(ClassInfo2
& cinfo
,
10390 const php::Prop
& prop
,
10393 auto const [it
, emplaced
] =
10394 state
.m_propIndices
.emplace(name
, state
.m_props
.size());
10396 state
.m_props
.emplace_back(State::PropTuple
{name
, src
, prop
});
10397 if (trait
) cinfo
.traitProps
.emplace_back(prop
);
10400 assertx(it
->second
< state
.m_props
.size());
10401 auto& prevTuple
= state
.m_props
[it
->second
];
10402 auto const& prev
= prevTuple
.prop
;
10403 auto const prevSrc
= prevTuple
.src
;
10405 if (cinfo
.name
->tsame(prevSrc
)) {
10406 if ((prev
.attrs
^ prop
.attrs
) &
10407 (AttrStatic
| AttrPublic
| AttrProtected
| AttrPrivate
) ||
10408 (!(prop
.attrs
& AttrSystemInitialValue
) &&
10409 !(prev
.attrs
& AttrSystemInitialValue
) &&
10410 !Class::compatibleTraitPropInit(prev
.val
, prop
.val
))) {
10412 "Adding property failed for `{}' because "
10413 "two declarations of `{}' at the same level had "
10414 "different attributes\n",
10415 cinfo
.name
, prop
.name
);
10421 if (!(prev
.attrs
& AttrPrivate
)) {
10422 if ((prev
.attrs
^ prop
.attrs
) & AttrStatic
) {
10424 "Adding property failed for `{}' because "
10425 "`{}' was defined both static and non-static\n",
10426 cinfo
.name
, prop
.name
);
10429 if (prop
.attrs
& AttrPrivate
) {
10431 "Adding property failed for `{}' because "
10432 "`{}' was re-declared private\n",
10433 cinfo
.name
, prop
.name
);
10436 if (prop
.attrs
& AttrProtected
&& !(prev
.attrs
& AttrProtected
)) {
10438 "Adding property failed for `{}' because "
10439 "`{}' was redeclared protected from public\n",
10440 cinfo
.name
, prop
.name
);
10445 if (trait
) cinfo
.traitProps
.emplace_back(prop
);
10446 prevTuple
= State::PropTuple
{name
, src
, prop
};
10450 static bool merge_properties(ClassInfo2
& cinfo
,
10452 const State
& src
) {
10453 for (auto const& [name
, src
, prop
] : src
.m_props
) {
10454 if (!add_property(cinfo
, dst
, name
, prop
, src
, false)) {
10461 static bool build_constants(const LocalIndex
& index
,
10465 if (cls
.parentName
) {
10466 cinfo
.clsConstants
= index
.classInfo(cls
.parentName
).clsConstants
;
10467 state
.m_cnsFromTrait
= index
.state(cls
.parentName
).m_cnsFromTrait
;
10470 for (auto const iname
: cls
.interfaceNames
) {
10471 auto const& iface
= index
.classInfo(iname
);
10472 auto const& ifaceState
= index
.state(iname
);
10473 for (auto const& [cnsName
, cnsIdx
] : iface
.clsConstants
) {
10474 auto const added
= add_constant(
10475 index
, cinfo
, state
, cnsName
,
10476 cnsIdx
, ifaceState
.m_cnsFromTrait
.count(cnsName
)
10478 if (!added
) return false;
10482 auto const addShallowConstants
= [&] {
10483 auto const numConstants
= cls
.constants
.size();
10484 for (uint32_t idx
= 0; idx
< numConstants
; ++idx
) {
10485 auto const& cns
= cls
.constants
[idx
];
10486 auto const added
= add_constant(
10487 index
, cinfo
, state
,
10489 ClassInfo2::ConstIndexAndKind
{
10490 ConstIndex
{ cls
.name
, idx
},
10495 if (!added
) return false;
10500 auto const addTraitConstants
= [&] {
10501 for (auto const tname
: cls
.usedTraitNames
) {
10502 auto const& trait
= index
.classInfo(tname
);
10503 for (auto const& [cnsName
, cnsIdx
] : trait
.clsConstants
) {
10504 auto const added
= add_constant(
10505 index
, cinfo
, state
, cnsName
,
10508 if (!added
) return false;
10514 if (Cfg::Eval::TraitConstantInterfaceBehavior
) {
10515 // trait constants must be inserted before constants shallowly
10516 // declared on the class to match the interface semantics
10517 if (!addTraitConstants()) return false;
10518 if (!addShallowConstants()) return false;
10520 if (!addShallowConstants()) return false;
10521 if (!addTraitConstants()) return false;
10524 for (auto const ename
: cls
.includedEnumNames
) {
10525 auto const& e
= index
.classInfo(ename
);
10526 for (auto const& [cnsName
, cnsIdx
] : e
.clsConstants
) {
10527 auto const added
= add_constant(
10528 index
, cinfo
, state
, cnsName
,
10531 if (!added
) return false;
10535 auto const addTraitConst
= [&] (const php::Const
& c
) {
10537 * Only copy in constants that win. Otherwise, in the runtime, if
10538 * we have a constant from an interface implemented by a trait
10539 * that wins over this fromTrait constant, we won't know which
10540 * trait it came from, and therefore won't know which constant
10541 * should win. Dropping losing constants here works because if
10542 * they fatal with constants in declared interfaces, we catch that
10545 auto const& existing
= cinfo
.clsConstants
.find(c
.name
);
10546 if (existing
->second
.idx
.cls
->tsame(c
.cls
)) {
10547 state
.m_traitCns
.emplace_back(c
);
10548 state
.m_traitCns
.back().isFromTrait
= true;
10551 for (auto const tname
: cls
.usedTraitNames
) {
10552 auto const& trait
= index
.cls(tname
);
10553 auto const& traitState
= index
.state(tname
);
10554 for (auto const& c
: trait
.constants
) addTraitConst(c
);
10555 for (auto const& c
: traitState
.m_traitCns
) addTraitConst(c
);
10558 if (cls
.attrs
& (AttrAbstract
| AttrInterface
| AttrTrait
)) return true;
10560 std::vector
<SString
> sortedClsConstants
;
10561 sortedClsConstants
.reserve(cinfo
.clsConstants
.size());
10562 for (auto const& [name
, _
] : cinfo
.clsConstants
) {
10563 sortedClsConstants
.emplace_back(name
);
10566 sortedClsConstants
.begin(),
10567 sortedClsConstants
.end(),
10571 for (auto const name
: sortedClsConstants
) {
10572 auto& cnsIdx
= cinfo
.clsConstants
.find(name
)->second
;
10573 if (cnsIdx
.idx
.cls
->tsame(cls
.name
)) continue;
10575 auto const& cns
= index
.cns(cnsIdx
.idx
);
10576 if (!cns
.isAbstract
|| !cns
.val
) continue;
10578 if (cns
.val
->m_type
== KindOfUninit
) {
10579 auto const& cnsCls
= index
.cls(cnsIdx
.idx
.cls
);
10580 assertx(!cnsCls
.methods
.empty());
10581 assertx(cnsCls
.methods
.back()->name
== s_86cinit
.get());
10582 auto const& cnsCInit
= *cnsCls
.methods
.back();
10584 if (cls
.methods
.empty() ||
10585 cls
.methods
.back()->name
!= s_86cinit
.get()) {
10586 ClonedClosures clonedClosures
;
10587 auto cloned
= clone(
10597 assertx(clonedClosures
.empty());
10598 assertx(cloned
->cls
== &cls
);
10599 cloned
->clsIdx
= cls
.methods
.size();
10600 auto const DEBUG_ONLY emplaced
=
10601 cinfo
.methods
.emplace(cloned
->name
, MethTabEntry
{ *cloned
});
10602 assertx(emplaced
.second
);
10603 cls
.methods
.emplace_back(std::move(cloned
));
10605 auto const DEBUG_ONLY succeeded
=
10606 append_86cinit(cls
.methods
.back().get(), cnsCInit
);
10607 assertx(succeeded
);
10611 // This is similar to trait constant flattening
10613 copy
.cls
= cls
.name
;
10614 copy
.isAbstract
= false;
10615 state
.m_cnsFromTrait
.erase(copy
.name
);
10617 cnsIdx
.idx
.cls
= cls
.name
;
10618 cnsIdx
.idx
.idx
= cls
.constants
.size();
10619 cnsIdx
.kind
= copy
.kind
;
10620 cls
.constants
.emplace_back(std::move(copy
));
10626 static bool add_constant(const LocalIndex
& index
,
10630 const ClassInfo2::ConstIndexAndKind
& cnsIdx
,
10632 auto [it
, emplaced
] = cinfo
.clsConstants
.emplace(name
, cnsIdx
);
10635 always_assert(state
.m_cnsFromTrait
.emplace(name
).second
);
10637 always_assert(!state
.m_cnsFromTrait
.count(name
));
10641 auto& existingIdx
= it
->second
;
10643 // Same constant (from an interface via two different paths) is ok
10644 if (existingIdx
.idx
.cls
->tsame(cnsIdx
.idx
.cls
)) return true;
10646 auto const& existingCnsCls
= index
.cls(existingIdx
.idx
.cls
);
10647 auto const& existing
= index
.cns(existingIdx
.idx
);
10648 auto const& cns
= index
.cns(cnsIdx
.idx
);
10650 if (existing
.kind
!= cns
.kind
) {
10653 "Adding constant failed for `{}' because `{}' was defined by "
10654 "`{}' as a {} and by `{}' as a {}\n",
10658 ConstModifiers::show(cns
.kind
),
10659 existingIdx
.idx
.cls
,
10660 ConstModifiers::show(existing
.kind
)
10665 // Ignore abstract constants
10666 if (cns
.isAbstract
&& !cns
.val
) return true;
10667 // If the existing constant in the map is concrete, then don't
10668 // overwrite it with an incoming abstract constant's default
10669 if (!existing
.isAbstract
&& cns
.isAbstract
) return true;
10671 if (existing
.val
) {
10673 * A constant from a declared interface collides with a constant
10674 * (Excluding constants from interfaces a trait implements).
10676 * Need this check otherwise constants from traits that conflict
10677 * with declared interfaces will silently lose and not conflict
10680 * Type and Context constants can be overridden.
10682 auto const& cnsCls
= index
.cls(cnsIdx
.idx
.cls
);
10683 if (cns
.kind
== ConstModifiers::Kind::Value
&&
10684 !existing
.isAbstract
&&
10685 (existingCnsCls
.attrs
& AttrInterface
) &&
10686 !((cnsCls
.attrs
& AttrInterface
) && fromTrait
)) {
10687 auto const& cls
= index
.cls(cinfo
.name
);
10688 for (auto const iface
: cls
.interfaceNames
) {
10689 if (existingIdx
.idx
.cls
->tsame(iface
)) {
10692 "Adding constant failed for `{}' because "
10693 "`{}' was defined by both `{}' and `{}'\n",
10697 existingIdx
.idx
.cls
10704 // Constants from traits silently lose
10705 if (!Cfg::Eval::TraitConstantInterfaceBehavior
&& fromTrait
) return true;
10707 if ((cnsCls
.attrs
& AttrInterface
||
10708 (Cfg::Eval::TraitConstantInterfaceBehavior
&&
10709 (cnsCls
.attrs
& AttrTrait
))) &&
10710 (existing
.isAbstract
||
10711 cns
.kind
== ConstModifiers::Kind::Type
)) {
10712 // Because existing has val, this covers the case where it is
10713 // abstract with default allow incoming to win. Also, type
10714 // constants from interfaces may be overridden even if they're
10717 // A constant from an interface or from an included enum
10718 // collides with an existing constant.
10719 if (cnsCls
.attrs
& (AttrInterface
| AttrEnum
| AttrEnumClass
) ||
10720 (Cfg::Eval::TraitConstantInterfaceBehavior
&&
10721 (cnsCls
.attrs
& AttrTrait
))) {
10724 "Adding constant failed for `{}' because "
10725 "`{}' was defined by both `{}' and `{}'\n",
10729 existingIdx
.idx
.cls
10737 state
.m_cnsFromTrait
.emplace(name
);
10739 state
.m_cnsFromTrait
.erase(name
);
10741 existingIdx
= cnsIdx
;
10746 * Make a flattened table of the methods on this class.
10748 * Duplicate method names override parent methods, unless the parent
10749 * method is final and the class is not a __MockClass, in which case
10750 * this class definitely would fatal if ever defined.
10752 * Note: we're leaving non-overridden privates in their subclass
10753 * method table, here. This isn't currently "wrong", because calling
10754 * it would be a fatal, but note that resolve_method needs to be
10755 * pretty careful about privates and overriding in general.
10757 static bool build_methods(const LocalIndex
& index
,
10758 const php::Class
& cls
,
10761 // Since interface methods are not inherited, any methods in
10762 // interfaces this class implements are automatically missing.
10763 assertx(cinfo
.methods
.empty());
10764 for (auto const iname
: cls
.interfaceNames
) {
10765 auto const& iface
= index
.classInfo(iname
);
10766 for (auto const& [name
, _
] : iface
.methods
) {
10767 if (is_special_method_name(name
)) continue;
10768 cinfo
.missingMethods
.emplace(name
);
10770 for (auto const name
: iface
.missingMethods
) {
10771 assertx(!is_special_method_name(name
));
10772 cinfo
.missingMethods
.emplace(name
);
10776 // Interface methods are just stubs which return null. They don't
10777 // get inherited by their implementations.
10778 if (cls
.attrs
& AttrInterface
) {
10779 assertx(!cls
.parentName
);
10780 assertx(cls
.usedTraitNames
.empty());
10781 uint32_t idx
= cinfo
.methods
.size();
10783 for (auto const& m
: cls
.methods
) {
10784 auto const res
= cinfo
.methods
.emplace(m
->name
, MethTabEntry
{ *m
});
10785 always_assert(res
.second
);
10786 always_assert(state
.m_methodIndices
.emplace(m
->name
, idx
++).second
);
10787 if (cinfo
.missingMethods
.count(m
->name
)) {
10788 assertx(!res
.first
->second
.firstName());
10789 cinfo
.missingMethods
.erase(m
->name
);
10791 res
.first
->second
.setFirstName();
10793 ITRACE(4, " {}: adding method {}::{}\n",
10794 cls
.name
, cls
.name
, m
->name
);
10799 auto const overridden
= [&] (MethTabEntry
& existing
,
10802 auto const& existingMeth
= index
.meth(existing
);
10803 if (existingMeth
.attrs
& AttrFinal
) {
10804 if (!is_mock_class(&cls
)) {
10807 "Adding methods failed for `{}' because "
10808 "it tried to override final method `{}::{}'\n",
10810 existing
.meth().cls
,
10818 "{}: overriding method {}::{} with {}::{}\n",
10820 existing
.meth().cls
,
10825 if (existingMeth
.attrs
& AttrPrivate
) {
10826 existing
.setHasPrivateAncestor();
10828 existing
.setMeth(meth
);
10829 existing
.attrs
= attrs
;
10830 existing
.setTopLevel();
10834 // If there's a parent, start by copying its methods
10835 if (cls
.parentName
) {
10836 auto const& parentInfo
= index
.classInfo(cls
.parentName
);
10838 assertx(cinfo
.methods
.empty());
10839 cinfo
.missingMethods
.insert(
10840 begin(parentInfo
.missingMethods
),
10841 end(parentInfo
.missingMethods
)
10844 for (auto const& mte
: parentInfo
.methods
) {
10845 // Don't inherit the 86* methods
10846 if (HPHP::Func::isSpecial(mte
.first
)) continue;
10848 auto const emplaced
= cinfo
.methods
.emplace(mte
);
10849 always_assert(emplaced
.second
);
10850 emplaced
.first
->second
.clearTopLevel();
10851 emplaced
.first
->second
.clearFirstName();
10854 state
.m_methodIndices
.emplace(
10856 index
.methodIdx(cls
.parentName
, mte
.first
)
10860 cinfo
.missingMethods
.erase(mte
.first
);
10864 "{}: inheriting method {}::{}\n",
10872 auto idx
= cinfo
.methods
.size();
10873 auto const clsHasModuleLevelTrait
=
10874 cls
.userAttributes
.count(s___ModuleLevelTrait
.get());
10876 // Now add our methods.
10877 for (auto const& m
: cls
.methods
) {
10878 if ((cls
.attrs
& AttrTrait
) &&
10879 (!((cls
.attrs
& AttrInternal
) || clsHasModuleLevelTrait
)) &&
10880 (m
->attrs
& AttrInternal
)) {
10882 "Adding methods failed for `{}' because "
10883 "method `{}' is internal and public traits "
10884 "cannot define internal methods unless they have "
10885 "the <<__ModuleLevelTrait>> attribute\n",
10886 cls
.name
, m
->name
);
10889 auto const emplaced
= cinfo
.methods
.emplace(m
->name
, MethTabEntry
{ *m
});
10890 if (emplaced
.second
) {
10893 "{}: adding method {}::{}\n",
10898 always_assert(state
.m_methodIndices
.emplace(m
->name
, idx
++).second
);
10899 if (cinfo
.missingMethods
.count(m
->name
)) {
10900 assertx(!emplaced
.first
->second
.firstName());
10901 cinfo
.missingMethods
.erase(m
->name
);
10903 emplaced
.first
->second
.setFirstName();
10908 // If the method is already in our table, it shouldn't be
10910 assertx(!cinfo
.missingMethods
.count(m
->name
));
10912 assertx(!emplaced
.first
->second
.firstName());
10914 if ((m
->attrs
& AttrTrait
) && (m
->attrs
& AttrAbstract
)) {
10915 // Abstract methods from traits never override anything.
10918 if (!overridden(emplaced
.first
->second
, MethRef
{ *m
}, m
->attrs
)) {
10923 // If our traits were previously flattened, we're done.
10924 if (cls
.attrs
& AttrNoExpandTrait
) return true;
10928 for (auto const tname
: cls
.usedTraitNames
) {
10929 auto const& tcls
= index
.cls(tname
);
10930 auto const& t
= index
.classInfo(tname
);
10931 std::vector
<std::pair
<SString
, const MethTabEntry
*>>
10932 methods(t
.methods
.size());
10933 for (auto const& [name
, mte
] : t
.methods
) {
10934 if (HPHP::Func::isSpecial(name
)) continue;
10935 auto const idx
= index
.methodIdx(tname
, name
);
10936 assertx(!methods
[idx
].first
);
10937 methods
[idx
] = std::make_pair(name
, &mte
);
10938 if (auto it
= cinfo
.methods
.find(name
);
10939 it
!= end(cinfo
.methods
)) {
10940 it
->second
.clearFirstName();
10944 for (auto const name
: t
.missingMethods
) {
10945 assertx(!is_special_method_name(name
));
10946 if (cinfo
.methods
.count(name
)) continue;
10947 cinfo
.missingMethods
.emplace(name
);
10950 for (auto const& [name
, mte
] : methods
) {
10951 if (!name
) continue;
10952 auto const& meth
= index
.meth(*mte
);
10954 TraitMethod
{ std::make_pair(&t
, &tcls
), &meth
, mte
->attrs
},
10958 for (auto const& clo
: tcls
.closures
) {
10959 auto const invoke
= find_method(clo
.get(), s_invoke
.get());
10961 cinfo
.extraMethods
.emplace(MethRef
{ *invoke
});
10965 auto const traitMethods
= tmid
.finish(
10966 std::make_pair(&cinfo
, &cls
),
10967 cls
.userAttributes
.count(s___EnableMethodTraitDiamond
.get())
10970 // Import the methods.
10971 for (auto const& mdata
: traitMethods
) {
10972 auto const method
= mdata
.tm
.method
;
10973 auto attrs
= mdata
.tm
.modifiers
;
10975 if (attrs
== AttrNone
) {
10976 attrs
= method
->attrs
;
10978 auto const attrMask
=
10979 (Attr
)(AttrPublic
| AttrProtected
| AttrPrivate
|
10980 AttrAbstract
| AttrFinal
);
10981 attrs
= (Attr
)((attrs
& attrMask
) |
10982 (method
->attrs
& ~attrMask
));
10985 auto const emplaced
= cinfo
.methods
.emplace(
10987 MethTabEntry
{ *method
, attrs
}
10989 if (emplaced
.second
) {
10992 "{}: adding trait method {}::{} as {}\n",
10994 method
->cls
->name
, method
->name
, mdata
.name
10997 state
.m_methodIndices
.emplace(mdata
.name
, idx
++).second
10999 cinfo
.missingMethods
.erase(mdata
.name
);
11001 assertx(!cinfo
.missingMethods
.count(mdata
.name
));
11002 if (attrs
& AttrAbstract
) continue;
11003 if (emplaced
.first
->second
.meth().cls
->tsame(cls
.name
)) continue;
11004 if (!overridden(emplaced
.first
->second
, MethRef
{ *method
}, attrs
)) {
11007 state
.methodIdx(index
.m_ctx
->name
, cinfo
.name
, mdata
.name
) = idx
++;
11009 cinfo
.extraMethods
.emplace(MethRef
{ *method
});
11011 } catch (const TMIOps::TMIException
& exn
) {
11014 "Adding methods failed for `{}' importing traits: {}\n",
11015 cls
.name
, exn
.what()
11023 using ClonedClosures
=
11024 hphp_fast_map
<const php::Class
*, std::unique_ptr
<php::Class
>>;
11026 static SString
rename_closure(const php::Class
& closure
,
11027 const php::Class
& newContext
) {
11028 auto n
= closure
.name
->slice();
11029 auto const p
= n
.find(';');
11030 if (p
!= std::string::npos
) n
= n
.subpiece(0, p
);
11031 return makeStaticString(folly::sformat("{};{}", n
, newContext
.name
));
11034 static std::unique_ptr
<php::Class
>
11035 clone_closure(const LocalIndex
& index
,
11036 const php::Class
& closure
,
11037 const php::Class
& newContext
,
11038 bool requiresFromOriginalModule
,
11039 ClonedClosures
& clonedClosures
) {
11040 auto clone
= std::make_unique
<php::Class
>(closure
);
11041 assertx(clone
->closureContextCls
);
11043 clone
->name
= rename_closure(closure
, newContext
);
11044 clone
->closureContextCls
= newContext
.name
;
11045 clone
->unit
= newContext
.unit
;
11047 ITRACE(4, "- cloning closure {} as {} (with context {})\n",
11048 closure
.name
, clone
->name
, newContext
.name
);
11050 for (size_t i
= 0, numMeths
= clone
->methods
.size(); i
< numMeths
; ++i
) {
11051 auto meth
= std::move(clone
->methods
[i
]);
11052 meth
->cls
= clone
.get();
11053 assertx(meth
->clsIdx
== i
);
11054 if (!meth
->originalFilename
) meth
->originalFilename
= meth
->unit
;
11055 if (!meth
->originalUnit
) meth
->originalUnit
= meth
->unit
;
11056 if (!meth
->originalClass
) meth
->originalClass
= closure
.name
;
11057 meth
->requiresFromOriginalModule
= requiresFromOriginalModule
;
11058 meth
->unit
= newContext
.unit
;
11060 clone
->methods
[i
] =
11061 clone_closures(index
, std::move(meth
), requiresFromOriginalModule
, clonedClosures
);
11062 if (!clone
->methods
[i
]) return nullptr;
11068 static std::unique_ptr
<php::Func
>
11069 clone_closures(const LocalIndex
& index
,
11070 std::unique_ptr
<php::Func
> cloned
,
11071 bool requiresFromOriginalModule
,
11072 ClonedClosures
& clonedClosures
) {
11073 if (!cloned
->hasCreateCl
) return cloned
;
11075 auto const onClosure
= [&] (LSString
& closureName
) {
11076 auto const& cls
= index
.cls(closureName
);
11077 assertx(is_closure(cls
));
11079 // CreateCls are allowed to refer to the same closure within the
11080 // same func. If this is a duplicate, use the already cloned
11082 if (auto const it
= clonedClosures
.find(&cls
);
11083 it
!= clonedClosures
.end()) {
11084 closureName
= it
->second
->name
;
11088 // Otherwise clone the closure (which gives it a new name), and
11089 // update the name in the CreateCl to match.
11090 auto closure
= clone_closure(
11093 cloned
->cls
->closureContextCls
11094 ? index
.cls(cloned
->cls
->closureContextCls
)
11096 requiresFromOriginalModule
,
11099 if (!closure
) return false;
11100 closureName
= closure
->name
;
11101 always_assert(clonedClosures
.emplace(&cls
, std::move(closure
)).second
);
11105 auto mf
= php::WideFunc::mut(cloned
.get());
11106 assertx(!mf
.blocks().empty());
11107 for (size_t bid
= 0; bid
< mf
.blocks().size(); bid
++) {
11108 auto const b
= mf
.blocks()[bid
].mutate();
11109 for (size_t ix
= 0; ix
< b
->hhbcs
.size(); ix
++) {
11110 auto& bc
= b
->hhbcs
[ix
];
11112 case Op::CreateCl
: {
11113 if (!onClosure(bc
.CreateCl
.str2
)) return nullptr;
11125 static std::unique_ptr
<php::Func
> clone(const LocalIndex
& index
,
11126 const php::Func
& orig
,
11129 const php::Class
& dstCls
,
11130 ClonedClosures
& clonedClosures
,
11131 bool internal
= false) {
11132 auto cloned
= std::make_unique
<php::Func
>(orig
);
11133 cloned
->name
= name
;
11134 cloned
->attrs
= attrs
;
11135 if (!internal
) cloned
->attrs
|= AttrTrait
;
11136 cloned
->cls
= const_cast<php::Class
*>(&dstCls
);
11137 cloned
->unit
= dstCls
.unit
;
11139 if (!cloned
->originalFilename
) cloned
->originalFilename
= orig
.unit
;
11140 if (!cloned
->originalUnit
) cloned
->originalUnit
= orig
.unit
;
11141 cloned
->originalClass
= orig
.originalClass
11142 ? orig
.originalClass
11144 cloned
->originalModuleName
= orig
.originalModuleName
;
11146 // If the "module level traits" semantics is enabled, whenever HHBBC
11147 // inlines a method from a trait defined in module A into a trait/class
11148 // defined in module B, it sets the requiresFromOriginalModule flag of the
11149 // method to true. This flag causes the originalModuleName field to be
11150 // copied in the HHVM extendedSharedData section of the method, so that
11151 // HHVM is able to resolve correctly the original module of the method.
11152 // Preserving the original module of a method is also needed when a
11153 // method is defined in an internal trait that is used by a module level
11155 const bool requiresFromOriginalModule
= [&] () {
11156 bool copyFromModuleLevelTrait
=
11157 orig
.fromModuleLevelTrait
&& !orig
.requiresFromOriginalModule
&&
11158 orig
.originalModuleName
!= dstCls
.moduleName
;
11159 bool copyFromInternal
=
11160 (orig
.cls
->attrs
& AttrInternal
)
11161 && dstCls
.userAttributes
.count(s___ModuleLevelTrait
.get());
11163 if (Cfg::Eval::ModuleLevelTraits
&&
11164 (copyFromModuleLevelTrait
|| copyFromInternal
)) {
11167 return orig
.requiresFromOriginalModule
;
11170 cloned
->requiresFromOriginalModule
= requiresFromOriginalModule
;
11172 // cloned method isn't in any method table yet, so trash its
11174 cloned
->clsIdx
= std::numeric_limits
<uint32_t>::max();
11175 return clone_closures(index
, std::move(cloned
), requiresFromOriginalModule
, clonedClosures
);
11178 static bool merge_inits(const LocalIndex
& index
,
11179 const php::Class
& cls
,
11180 const ClassInfo2
& cinfo
,
11182 std::vector
<std::unique_ptr
<php::Func
>>& clones
) {
11183 auto const existing
= [&] () -> const php::Func
* {
11184 for (auto const& m
: cls
.methods
) {
11185 if (m
->name
== name
) return m
.get();
11190 std::unique_ptr
<php::Func
> cloned
;
11192 auto const merge
= [&] (const php::Func
& f
) {
11194 ClonedClosures clonedClosures
;
11205 assertx(clonedClosures
.empty());
11206 if (!cloned
) return false;
11208 ITRACE(4, "- cloning {}::{} as {}::{}\n",
11209 f
.cls
->name
, f
.name
, cls
.name
, name
);
11210 cloned
= clone(index
, f
, f
.name
, f
.attrs
, cls
, clonedClosures
, true);
11211 assertx(clonedClosures
.empty());
11212 return (bool)cloned
;
11216 ITRACE(4, "- appending {}::{} into {}::{}\n",
11217 f
.cls
->name
, f
.name
, cls
.name
, name
);
11218 if (name
== s_86cinit
.get()) return append_86cinit(cloned
.get(), f
);
11219 return append_func(cloned
.get(), f
);
11222 for (auto const tname
: cls
.usedTraitNames
) {
11223 auto const& trait
= index
.classInfo(tname
);
11224 auto const it
= trait
.methods
.find(name
);
11225 if (it
== trait
.methods
.end()) continue;
11226 auto const& meth
= index
.meth(it
->second
);
11227 if (!merge(meth
)) {
11228 ITRACE(4, "merge_inits: failed to merge {}::{}\n",
11229 meth
.cls
->name
, name
);
11235 ITRACE(4, "merge_inits: adding {}::{} to method table\n",
11236 cloned
->cls
->name
, cloned
->name
);
11237 clones
.emplace_back(std::move(cloned
));
11243 static bool merge_xinits(const LocalIndex
& index
,
11244 const php::Class
& cls
,
11245 const ClassInfo2
& cinfo
,
11246 const State
& state
,
11247 std::vector
<std::unique_ptr
<php::Func
>>& clones
) {
11248 auto const merge_one
= [&] (SString name
, Attr attr
) {
11249 auto const unnecessary
= std::all_of(
11250 cinfo
.traitProps
.begin(),
11251 cinfo
.traitProps
.end(),
11252 [&] (const php::Prop
& p
) {
11253 if ((p
.attrs
& (AttrStatic
| AttrLSB
)) != attr
) return true;
11254 if (p
.val
.m_type
!= KindOfUninit
) return true;
11255 if (p
.attrs
& AttrLateInit
) return true;
11259 if (unnecessary
) return true;
11260 return merge_inits(index
, cls
, cinfo
, name
, clones
);
11263 if (!merge_one(s_86pinit
.get(), AttrNone
)) return false;
11264 if (!merge_one(s_86sinit
.get(), AttrStatic
)) return false;
11265 if (!merge_one(s_86linit
.get(), AttrStatic
| AttrLSB
)) return false;
11267 auto const unnecessary
= std::all_of(
11268 state
.m_traitCns
.begin(),
11269 state
.m_traitCns
.end(),
11270 [&] (const php::Const
& c
) {
11271 return !c
.val
|| c
.val
->m_type
!= KindOfUninit
;
11274 if (unnecessary
) return true;
11275 return merge_inits(index
, cls
, cinfo
, s_86cinit
.get(), clones
);
11278 static std::vector
<std::unique_ptr
<ClassInfo2
>>
11279 flatten_traits(const LocalIndex
& index
,
11283 if (cls
.attrs
& AttrNoExpandTrait
) return {};
11284 if (cls
.usedTraitNames
.empty()) {
11285 cls
.attrs
|= AttrNoExpandTrait
;
11289 ITRACE(4, "flatten traits: {}\n", cls
.name
);
11290 Trace::Indent indent
;
11292 assertx(!is_closure(cls
));
11294 auto traitHasConstProp
= cls
.hasConstProp
;
11295 for (auto const tname
: cls
.usedTraitNames
) {
11296 auto const& trait
= index
.cls(tname
);
11297 auto const& tinfo
= index
.classInfo(tname
);
11298 if (!(trait
.attrs
& AttrNoExpandTrait
)) {
11299 ITRACE(4, "Not flattening {} because of {}\n", cls
.name
, trait
.name
);
11302 if (is_noflatten_trait(&trait
)) {
11304 4, "Not flattening {} because {} is annotated with __NoFlatten\n",
11305 cls
.name
, trait
.name
11309 if (tinfo
.hasConstProp
) traitHasConstProp
= true;
11312 std::vector
<std::pair
<SString
, MethTabEntry
*>> toAdd
;
11313 for (auto& [name
, mte
] : cinfo
.methods
) {
11314 if (!mte
.topLevel()) continue;
11315 if (mte
.meth().cls
->tsame(cls
.name
)) continue;
11316 assertx(index
.cls(mte
.meth().cls
).attrs
& AttrTrait
);
11317 toAdd
.emplace_back(name
, &mte
);
11320 if (!toAdd
.empty()) {
11321 assertx(!cinfo
.extraMethods
.empty());
11323 toAdd
.begin(), toAdd
.end(),
11324 [&] (auto const& a
, auto const& b
) {
11326 state
.methodIdx(index
.m_ctx
->name
, cinfo
.name
, a
.first
) <
11327 state
.methodIdx(index
.m_ctx
->name
, cinfo
.name
, b
.first
);
11330 } else if (debug
) {
11331 // When building the ClassInfos, we proactively added all
11332 // closures from usedTraits to the extraMethods map; but now
11333 // we're going to start from the used methods, and deduce which
11334 // closures actually get pulled in. Its possible *none* of the
11335 // methods got used, in which case, we won't need their closures
11336 // either. To be safe, verify that the only things in the map
11338 for (auto const& mte
: cinfo
.extraMethods
) {
11339 auto const& meth
= index
.meth(mte
);
11340 always_assert(meth
.isClosureBody
);
11344 std::vector
<std::unique_ptr
<php::Func
>> clones
;
11345 ClonedClosures clonedClosures
;
11347 for (auto const& [name
, mte
] : toAdd
) {
11348 auto const& meth
= index
.meth(*mte
);
11349 auto cloned
= clone(
11358 ITRACE(4, "Not flattening {} because {}::{} could not be cloned\n",
11359 cls
.name
, mte
->meth().cls
, name
);
11362 assertx(cloned
->attrs
& AttrTrait
);
11363 clones
.emplace_back(std::move(cloned
));
11366 if (!merge_xinits(index
, cls
, cinfo
, state
, clones
)) {
11367 ITRACE(4, "Not flattening {} because we couldn't merge the 86xinits\n",
11372 // We're now committed to flattening.
11373 ITRACE(3, "Flattening {}\n", cls
.name
);
11375 if (traitHasConstProp
) {
11376 assertx(cinfo
.hasConstProp
);
11377 cls
.hasConstProp
= true;
11379 cinfo
.extraMethods
.clear();
11381 for (auto [_
, mte
] : toAdd
) mte
->attrs
|= AttrTrait
;
11383 for (auto& p
: cinfo
.traitProps
) {
11384 ITRACE(4, "- prop {}\n", p
.name
);
11385 cls
.properties
.emplace_back(std::move(p
));
11386 cls
.properties
.back().attrs
|= AttrTrait
;
11388 cinfo
.traitProps
.clear();
11390 for (auto& c
: state
.m_traitCns
) {
11391 ITRACE(4, "- const {}\n", c
.name
);
11393 auto it
= cinfo
.clsConstants
.find(c
.name
);
11394 assertx(it
!= cinfo
.clsConstants
.end());
11395 auto& cnsIdx
= it
->second
;
11398 state
.m_cnsFromTrait
.erase(c
.name
);
11399 cnsIdx
.idx
.cls
= cls
.name
;
11400 cnsIdx
.idx
.idx
= cls
.constants
.size();
11401 cls
.constants
.emplace_back(std::move(c
));
11403 state
.m_traitCns
.clear();
11405 // A class should inherit any declared interfaces of any traits
11406 // that are flattened into it.
11407 for (auto const tname
: cls
.usedTraitNames
) {
11408 auto const& tinfo
= index
.classInfo(tname
);
11409 cinfo
.classGraph
.flattenTraitInto(tinfo
.classGraph
);
11412 // If we flatten the traits into us, they're no longer actual
11414 state
.m_parents
.erase(
11416 begin(state
.m_parents
),
11417 end(state
.m_parents
),
11418 [] (const php::Class
* c
) { return bool(c
->attrs
& AttrTrait
); }
11420 end(state
.m_parents
)
11423 for (auto const tname
: cls
.usedTraitNames
) {
11424 auto const& traitState
= index
.state(tname
);
11425 state
.m_parents
.insert(
11426 end(state
.m_parents
),
11427 begin(traitState
.m_parents
),
11428 end(traitState
.m_parents
)
11432 begin(state
.m_parents
),
11433 end(state
.m_parents
),
11434 [] (const php::Class
* a
, const php::Class
* b
) {
11435 return string_data_lt_type
{}(a
->name
, b
->name
);
11438 state
.m_parents
.erase(
11439 std::unique(begin(state
.m_parents
), end(state
.m_parents
)),
11440 end(state
.m_parents
)
11443 std::vector
<std::unique_ptr
<ClassInfo2
>> newClosures
;
11444 if (!clones
.empty()) {
11445 auto const add
= [&] (std::unique_ptr
<php::Func
> clone
) {
11446 assertx(clone
->cls
== &cls
);
11447 clone
->clsIdx
= cls
.methods
.size();
11449 if (!is_special_method_name(clone
->name
)) {
11450 auto it
= cinfo
.methods
.find(clone
->name
);
11451 assertx(it
!= cinfo
.methods
.end());
11452 assertx(!it
->second
.meth().cls
->tsame(cls
.name
));
11453 it
->second
.setMeth(MethRef
{ cls
.name
, clone
->clsIdx
});
11455 auto const [existing
, emplaced
] =
11456 cinfo
.methods
.emplace(clone
->name
, MethTabEntry
{ *clone
});
11458 assertx(existing
->second
.meth().cls
->tsame(cls
.name
));
11459 if (clone
->name
!= s_86cinit
.get()) {
11460 auto const idx
= existing
->second
.meth().idx
;
11461 clone
->clsIdx
= idx
;
11462 cls
.methods
[idx
] = std::move(clone
);
11465 existing
->second
.setMeth(MethRef
{ cls
.name
, clone
->clsIdx
});
11470 cls
.methods
.emplace_back(std::move(clone
));
11473 auto cinit
= [&] () -> std::unique_ptr
<php::Func
> {
11474 if (cls
.methods
.empty()) return nullptr;
11475 if (cls
.methods
.back()->name
!= s_86cinit
.get()) return nullptr;
11476 auto init
= std::move(cls
.methods
.back());
11477 cls
.methods
.pop_back();
11481 for (auto& clone
: clones
) {
11482 ITRACE(4, "- meth {}\n", clone
->name
);
11483 if (clone
->name
== s_86cinit
.get()) {
11484 cinit
= std::move(clone
);
11487 add(std::move(clone
));
11489 if (cinit
) add(std::move(cinit
));
11491 for (auto& [orig
, clo
] : clonedClosures
) {
11492 ITRACE(4, "- closure {} as {}\n", orig
->name
, clo
->name
);
11493 assertx(is_closure(*orig
));
11494 assertx(is_closure(*clo
));
11495 assertx(clo
->closureContextCls
->tsame(cls
.name
));
11496 assertx(clo
->unit
== cls
.unit
);
11498 assertx(clo
->usedTraitNames
.empty());
11500 auto cloinfo
= make_info(index
, *clo
, cloState
);
11502 assertx(cloState
.m_traitCns
.empty());
11503 assertx(cloState
.m_cnsFromTrait
.empty());
11504 assertx(cloState
.m_parents
.size() == 1);
11505 assertx(cloState
.m_parents
[0]->name
->tsame(s_Closure
.get()));
11507 cls
.closures
.emplace_back(std::move(clo
));
11508 newClosures
.emplace_back(std::move(cloinfo
));
11512 // Flattening methods into traits can turn methods from not "first
11513 // name" to "first name", so recalculate that here.
11514 for (auto& [name
, mte
] : cinfo
.methods
) {
11515 if (mte
.firstName()) continue;
11516 auto const firstName
= [&, name
=name
] {
11517 if (cls
.parentName
) {
11518 auto const& parentInfo
= index
.classInfo(cls
.parentName
);
11519 if (parentInfo
.methods
.count(name
)) return false;
11520 if (parentInfo
.missingMethods
.count(name
)) return false;
11522 for (auto const iname
: cinfo
.classGraph
.interfaces()) {
11523 auto const& iface
= index
.classInfo(iname
.name());
11524 if (iface
.methods
.count(name
)) return false;
11525 if (iface
.missingMethods
.count(name
)) return false;
11529 if (firstName
) mte
.setFirstName();
11533 bool operator()(const PreClass::ClassRequirement
& a
,
11534 const PreClass::ClassRequirement
& b
) const {
11535 return a
.is_same(&b
);
11537 size_t operator()(const PreClass::ClassRequirement
& a
) const {
11541 hphp_fast_set
<PreClass::ClassRequirement
, EqHash
, EqHash
> reqs
{
11542 cls
.requirements
.begin(),
11543 cls
.requirements
.end()
11546 for (auto const tname
: cls
.usedTraitNames
) {
11547 auto const& trait
= index
.cls(tname
);
11548 for (auto const& req
: trait
.requirements
) {
11549 if (reqs
.emplace(req
).second
) cls
.requirements
.emplace_back(req
);
11553 cls
.attrs
|= AttrNoExpandTrait
;
11554 return newClosures
;
11557 static std::unique_ptr
<FuncInfo2
> make_func_info(const LocalIndex
& index
,
11558 const php::Func
& f
) {
11559 auto finfo
= std::make_unique
<FuncInfo2
>();
11560 finfo
->name
= f
.name
;
11564 static bool resolve_one(TypeConstraint
& tc
,
11565 const TypeConstraint
& tv
,
11570 assertx(!tv
.isUnion());
11571 // Whatever it's an alias of isn't valid, so leave unresolved.
11572 if (tv
.isUnresolved()) return false;
11573 if (isProp
&& !propSupportsAnnot(tv
.type())) return false;
11574 auto const value
= [&] () -> SString
{
11575 // Store the first enum encountered during resolution. This
11576 // lets us fixup the type later if needed.
11577 if (firstEnum
) return firstEnum
;
11578 if (tv
.isSubObject()) {
11579 auto clsName
= tv
.clsName();
11585 if (isUnion
) tc
.unresolve();
11586 tc
.resolveType(tv
.type(), tv
.isNullable(), value
);
11587 assertx(IMPLIES(isProp
, tc
.validForProp()));
11588 if (uses
&& value
) uses
->emplace(value
);
11592 // Update a type constraint to it's ultimate type, or leave it as
11593 // unresolved if it resolves to nothing valid. Record the new type
11594 // in case it needs to be fixed up later.
11595 static void update_type_constraint(const LocalIndex
& index
,
11596 TypeConstraint
& tc
,
11598 TSStringSet
* uses
) {
11599 always_assert(IMPLIES(isProp
, tc
.validForProp()));
11601 if (!tc
.isUnresolved()) {
11602 // Any TC already resolved is assumed to be correct.
11604 for (auto& part
: eachTypeConstraintInUnion(tc
)) {
11605 if (auto clsName
= part
.clsName()) {
11606 uses
->emplace(clsName
);
11612 auto const name
= tc
.typeName();
11614 if (tc
.isUnion()) {
11615 // This is a union that contains unresolved names.
11616 not_implemented(); // TODO(T151885113)
11619 // This is an unresolved name that can resolve to either a single type or
11622 // Is this name a type-alias or enum?
11623 if (auto const tm
= index
.typeMapping(name
)) {
11624 if (tm
->value
.isUnion()) {
11626 tc
.flags() & (TypeConstraintFlags::Nullable
11627 | TypeConstraintFlags::TypeVar
11628 | TypeConstraintFlags::Soft
11629 | TypeConstraintFlags::TypeConstant
11630 | TypeConstraintFlags::DisplayNullable
11631 | TypeConstraintFlags::UpperBound
);
11632 std::vector
<TypeConstraint
> members
;
11633 for (auto& tv
: eachTypeConstraintInUnion(tm
->value
)) {
11634 TypeConstraint copy
= tv
;
11635 copy
.addFlags(flags
);
11636 if (!resolve_one(copy
, tv
, tm
->firstEnum
, uses
, isProp
, true)) {
11639 members
.emplace_back(std::move(copy
));
11641 tc
= TypeConstraint::makeUnion(name
, members
);
11645 // This unresolved name resolves to a single type.
11646 assertx(!tm
->value
.isUnion());
11647 resolve_one(tc
, tm
->value
, tm
->firstEnum
, uses
, isProp
, false);
11651 // Not a type-alias or enum. If it's explicitly marked as missing,
11652 // leave it unresolved. Otherwise assume it's an object with that
11654 if (index
.missingType(name
)) return;
11655 tc
.resolveType(AnnotType::SubObject
, tc
.isNullable(), name
);
11656 if (uses
) uses
->emplace(name
);
11659 static void update_type_constraints(const LocalIndex
& index
,
11661 TSStringSet
* uses
) {
11662 for (auto& p
: func
.params
) {
11663 update_type_constraint(index
, p
.typeConstraint
, false, uses
);
11664 for (auto& ub
: p
.upperBounds
.m_constraints
) {
11665 update_type_constraint(index
, ub
, false, uses
);
11668 update_type_constraint(index
, func
.retTypeConstraint
, false, uses
);
11669 for (auto& ub
: func
.returnUBs
.m_constraints
) {
11670 update_type_constraint(index
, ub
, false, uses
);
11674 static void update_type_constraints(const LocalIndex
& index
,
11676 TSStringSet
* uses
) {
11677 if (cls
.attrs
& AttrEnum
) {
11678 update_type_constraint(index
, cls
.enumBaseTy
, false, uses
);
11680 for (auto& meth
: cls
.methods
) update_type_constraints(index
, *meth
, uses
);
11681 for (auto& prop
: cls
.properties
) {
11682 update_type_constraint(index
, prop
.typeConstraint
, true, uses
);
11683 for (auto& ub
: prop
.ubs
.m_constraints
) {
11684 update_type_constraint(index
, ub
, true, uses
);
11690 * Mark any properties in cls that definitely do not redeclare a
11691 * property in the parent with an inequivalent type-hint.
11693 * Rewrite the initial values for any AttrSystemInitialValue
11694 * properties. If the properties' type-hint does not admit null
11695 * values, change the initial value to one that is not null
11696 * (if possible). This is only safe to do so if the property is not
11697 * redeclared in a derived class or if the redeclaration does not
11698 * have a null system provided default value. Otherwise, a property
11699 * can have a null value (even if its type-hint doesn't allow it)
11700 * without the JIT realizing that its possible.
11702 * Note that this ignores any unflattened traits. This is okay
11703 * because properties pulled in from traits which match an already
11704 * existing property can't change the initial value. The runtime
11705 * will clear AttrNoImplicitNullable on any property pulled from the
11706 * trait if it doesn't match an existing property.
11708 static void optimize_properties(const LocalIndex
& index
,
11710 ClassInfo2
& cinfo
) {
11711 assertx(cinfo
.hasBadRedeclareProp
);
11713 auto const isClosure
= is_closure(cls
);
11715 cinfo
.hasBadRedeclareProp
= false;
11716 for (auto& prop
: cls
.properties
) {
11717 assertx(!(prop
.attrs
& AttrNoBadRedeclare
));
11718 assertx(!(prop
.attrs
& AttrNoImplicitNullable
));
11720 auto const noBadRedeclare
= [&] {
11721 // Closures should never have redeclared properties.
11722 if (isClosure
) return true;
11723 // Static and private properties never redeclare anything so
11724 // need not be considered.
11725 if (prop
.attrs
& (AttrStatic
| AttrPrivate
)) return true;
11727 for (auto const base
: cinfo
.classGraph
.bases()) {
11728 if (base
.name()->tsame(cls
.name
)) continue;
11730 auto& baseCInfo
= index
.classInfo(base
.name());
11731 auto& baseCls
= index
.cls(base
.name());
11733 auto const parentProp
= [&] () -> php::Prop
* {
11734 for (auto& p
: baseCls
.properties
) {
11735 if (p
.name
== prop
.name
) return const_cast<php::Prop
*>(&p
);
11737 for (auto& p
: baseCInfo
.traitProps
) {
11738 if (p
.name
== prop
.name
) return const_cast<php::Prop
*>(&p
);
11742 if (!parentProp
) continue;
11743 if (parentProp
->attrs
& (AttrStatic
| AttrPrivate
)) continue;
11745 // This property's type-constraint might not have been
11746 // resolved (if the parent is not on the output list for
11747 // this job), so do so here.
11748 update_type_constraint(
11750 parentProp
->typeConstraint
,
11755 // This check is safe, but conservative. It might miss a few
11756 // rare cases, but it's sufficient and doesn't require class
11758 if (prop
.typeConstraint
.maybeInequivalentForProp(
11759 parentProp
->typeConstraint
11764 for (auto const& ub
: prop
.ubs
.m_constraints
) {
11765 for (auto& pub
: parentProp
->ubs
.m_constraints
) {
11766 update_type_constraint(index
, pub
, true, nullptr);
11767 if (ub
.maybeInequivalentForProp(pub
)) return false;
11775 if (noBadRedeclare
) {
11776 attribute_setter(prop
.attrs
, true, AttrNoBadRedeclare
);
11778 cinfo
.hasBadRedeclareProp
= true;
11781 auto const nullable
= [&] {
11782 if (isClosure
) return true;
11783 if (!(prop
.attrs
& AttrSystemInitialValue
)) return false;
11784 return prop
.typeConstraint
.defaultValue().m_type
== KindOfNull
;
11787 attribute_setter(prop
.attrs
, !nullable
, AttrNoImplicitNullable
);
11788 if (!(prop
.attrs
& AttrSystemInitialValue
)) continue;
11789 if (prop
.val
.m_type
== KindOfUninit
) {
11790 assertx(isClosure
|| bool(prop
.attrs
& AttrLateInit
));
11795 if (nullable
) return make_tv
<KindOfNull
>();
11796 // Give the 86reified_prop a special default value to avoid
11797 // pessimizing the inferred type (we want it to always be a
11798 // vec of a specific size).
11799 if (prop
.name
== s_86reified_prop
.get()) {
11800 return get_default_value_of_reified_list(cls
.userAttributes
);
11802 return prop
.typeConstraint
.defaultValue();
11809 Job
<FlattenJob
> s_flattenJob
;
11812 * For efficiency reasons, we want to do class flattening all in one
11813 * pass. So, we use assign_hierarchical_work (described above) to
11814 * calculate work buckets to allow us to do this.
11816 * - The "root" classes are the leaf classes in the hierarchy. These are
11817 * the buckets which are not dependencies of anything.
11819 * - The dependencies of a class are all of the (transitive) parent
11820 * classes of that class (up to the top classes with no parents).
11822 * - Each job takes two kinds of input. The first is the set of
11823 * classes which are actually to be flattened. These will have the
11824 * flattening results returned as output from the job. The second is
11825 * the set of dependencies that are required to perform flattening
11826 * on the first set of inputs. These will have the same flattening
11827 * algorithm applied to them, but only to obtain intermediate state
11828 * to calculate the output for the first set of inputs. Their
11829 * results will be thrown away.
11831 * - When we run the jobs, we'll take the outputs and turn that into a set of
11832 * updates, which we then apply to the Index data structures. Some
11833 * of these updates require changes to the php::Unit, which we do a
11834 * in separate set of "fixup" jobs at the end.
11837 // Input class metadata to be turned into work buckets.
11838 struct IndexFlattenMetadata
{
11841 // All types mentioned in type-constraints in this class.
11842 std::vector
<SString
> unresolvedTypes
;
11843 size_t idx
; // Index into allCls vector
11844 bool isClosure
{false};
11845 bool uninstantiable
{false};
11847 TSStringToOneT
<ClassMeta
> cls
;
11848 // All classes to be flattened
11849 std::vector
<SString
> allCls
;
11850 // Mapping of units to classes which should be deleted from that
11851 // unit. This is typically from duplicate meth callers. This is
11852 // performed as part of "fixing up" the unit after flattening
11853 // because it's convenient to do so there.
11854 SStringToOneT
<std::vector
<SString
>> unitDeletions
;
11856 // All types mentioned in type-constraints in this func.
11857 std::vector
<SString
> unresolvedTypes
;
11859 FSStringToOneT
<FuncMeta
> func
;
11860 std::vector
<SString
> allFuncs
;
11861 TSStringToOneT
<TypeMapping
> typeMappings
;
11864 //////////////////////////////////////////////////////////////////////
11866 constexpr size_t kNumTypeMappingRounds
= 20;
11869 * Update the type-mappings in the program so they all point to their
11870 * ultimate type. After this step, every type-mapping that still has
11871 * an unresolved type points to an invalid type.
11873 void flatten_type_mappings(IndexData
& index
,
11874 IndexFlattenMetadata
& meta
) {
11875 trace_time tracer
{"flatten type mappings"};
11876 tracer
.ignore_client_stats();
11878 std::vector
<const TypeMapping
*> work
;
11879 work
.reserve(meta
.typeMappings
.size());
11880 for (auto const& [_
, tm
] : meta
.typeMappings
) work
.emplace_back(&tm
);
11882 auto resolved
= parallel::map(
11884 [&] (const TypeMapping
* typeMapping
) {
11885 Optional
<TSStringSet
> seen
;
11886 TypeConstraintFlags flags
=
11887 typeMapping
->value
.flags() & (TypeConstraintFlags::Nullable
11888 | TypeConstraintFlags::TypeVar
11889 | TypeConstraintFlags::Soft
11890 | TypeConstraintFlags::TypeConstant
11891 | TypeConstraintFlags::DisplayNullable
11892 | TypeConstraintFlags::UpperBound
);
11893 auto firstEnum
= typeMapping
->firstEnum
;
11894 auto const isUnion
= typeMapping
->value
.isUnion();
11895 bool anyUnresolved
= false;
11897 auto enumMeta
= folly::get_ptr(meta
.cls
, typeMapping
->name
);
11899 std::vector
<TypeConstraint
> tvu
;
11901 for (auto const& tc
: eachTypeConstraintInUnion(typeMapping
->value
)) {
11902 const auto type
= tc
.type();
11903 const auto value
= tc
.typeName();
11907 if (type
!= AnnotType::Unresolved
) {
11908 // If the type-mapping is already resolved, we mainly take it
11909 // as is. The exception is if it's an enum, in which case we
11910 // validate the underlying base type.
11911 assertx(type
!= AnnotType::SubObject
);
11913 tvu
.emplace_back(tc
);
11916 if (!enumSupportsAnnot(type
)) {
11918 2, "Type-mapping '{}' is invalid because it resolves to "
11919 "invalid enum type {}\n",
11923 tvu
.emplace_back(AnnotType::Unresolved
, tc
.flags(), value
);
11926 tvu
.emplace_back(type
, tc
.flags() | flags
, value
);
11927 anyUnresolved
= true;
11931 std::queue
<LSString
> queue
;
11934 for (size_t rounds
= 0;; ++rounds
) {
11935 if (queue
.empty()) break;
11936 name
= normalizeNS(queue
.front());
11939 if (auto const next
= folly::get_ptr(meta
.typeMappings
, name
)) {
11940 flags
|= next
->value
.flags() & (TypeConstraintFlags::Nullable
11941 | TypeConstraintFlags::TypeVar
11942 | TypeConstraintFlags::Soft
11943 | TypeConstraintFlags::TypeConstant
11944 | TypeConstraintFlags::DisplayNullable
11945 | TypeConstraintFlags::UpperBound
);
11946 auto const nextEnum
= next
->firstEnum
;
11947 if (!curEnum
) curEnum
= nextEnum
;
11948 if (!firstEnum
&& !isUnion
) firstEnum
= curEnum
;
11950 if (enumMeta
&& nextEnum
) {
11951 enumMeta
->deps
.emplace(nextEnum
);
11954 for (auto const& next_tc
: eachTypeConstraintInUnion(next
->value
)) {
11955 auto next_type
= next_tc
.type();
11956 auto next_value
= next_tc
.typeName();
11957 if (next_type
== AnnotType::Unresolved
) {
11958 queue
.push(next_value
);
11961 assertx(next_type
!= AnnotType::SubObject
);
11962 if (curEnum
&& !enumSupportsAnnot(next_type
)) {
11964 2, "Type-mapping '{}' is invalid because it resolves to "
11965 "invalid enum type {}{}\n",
11967 annotName(next_type
),
11968 curEnum
->tsame(typeMapping
->name
)
11969 ? "" : folly::sformat(" (via {})", curEnum
)
11971 tvu
.emplace_back(AnnotType::Unresolved
, tc
.flags() | flags
, name
);
11972 anyUnresolved
= true;
11975 tvu
.emplace_back(next_type
, tc
.flags() | flags
, next_value
);
11977 } else if (index
.classRefs
.count(name
)) {
11980 2, "Type-mapping '{}' is invalid because it resolves to "
11981 "invalid object '{}' for enum type (via {})\n",
11989 curEnum
? AnnotType::Unresolved
: AnnotType::SubObject
,
11990 tc
.flags() | flags
,
11993 if (curEnum
) anyUnresolved
= true;
11997 2, "Type-mapping '{}' is invalid because it involves "
11998 "non-existent type '{}'{}\n",
12001 (curEnum
&& !curEnum
->tsame(typeMapping
->name
))
12002 ? folly::sformat(" (via {})", curEnum
) : ""
12004 tvu
.emplace_back(AnnotType::Unresolved
, tc
.flags() | flags
, name
);
12005 anyUnresolved
= true;
12009 // Deal with cycles. Since we don't expect to encounter them, just
12010 // use a counter until we hit a chain length of kNumTypeMappingRounds,
12011 // then start tracking the names we resolve.
12012 if (rounds
== kNumTypeMappingRounds
) {
12014 seen
->insert(name
);
12015 } else if (rounds
> kNumTypeMappingRounds
) {
12016 if (!seen
->insert(name
).second
) {
12018 2, "Type-mapping '{}' is invalid because it's definition "
12019 "is circular with '{}'\n",
12023 return TypeMapping
{
12026 TypeConstraint
{AnnotType::Unresolved
, flags
, name
},
12032 if (isUnion
&& anyUnresolved
) {
12033 // Unions cannot contain a mix of resolved an unresolved class names so
12034 // if one of the names failed to resolve we must mark all of them as
12036 for (auto& tc
: tvu
) if (tc
.isSubObject()) tc
.unresolve();
12038 assertx(!tvu
.empty());
12039 // If any of the subtypes end up unresolved then the final union will also
12040 // be unresolved. But it's important to try the `makeUnion` anyway because
12041 // it will deal with some of the canonicalizations like `bool`.
12042 auto value
= TypeConstraint::makeUnion(typeMapping
->name
, std::move(tvu
));
12043 return TypeMapping
{ typeMapping
->name
, firstEnum
, value
};
12047 for (auto& after
: resolved
) {
12048 auto const name
= after
.name
;
12049 using namespace folly::gen
;
12051 4, "Type-mapping '{}' flattened to {}{}\n",
12053 after
.value
.debugName(),
12054 (after
.firstEnum
&& !after
.firstEnum
->tsame(name
))
12055 ? folly::sformat(" (via {})", after
.firstEnum
) : ""
12057 if (after
.value
.isUnresolved() && meta
.cls
.count(name
)) {
12058 FTRACE(4, " Marking enum '{}' as uninstantiable\n", name
);
12059 meta
.cls
.at(name
).uninstantiable
= true;
12061 meta
.typeMappings
.at(name
) = std::move(after
);
12065 //////////////////////////////////////////////////////////////////////
12067 struct FlattenClassesWork
{
12068 std::vector
<SString
> classes
;
12069 std::vector
<SString
> deps
;
12070 std::vector
<SString
> funcs
;
12071 std::vector
<SString
> uninstantiable
;
12074 std::vector
<FlattenClassesWork
>
12075 flatten_classes_assign(IndexFlattenMetadata
& meta
) {
12076 trace_time trace
{"flatten classes assign"};
12077 trace
.ignore_client_stats();
12079 // First calculate the classes which *aren't* leafs. A class is a
12080 // leaf if it is not depended on by another class. The sense is
12081 // inverted because we want to default construct the atomics.
12082 std::vector
<std::atomic
<bool>> isNotLeaf(meta
.allCls
.size());
12083 parallel::for_each(
12085 [&] (SString cls
) {
12086 auto const& clsMeta
= meta
.cls
.at(cls
);
12087 for (auto const d
: clsMeta
.deps
) {
12088 auto const it
= meta
.cls
.find(d
);
12089 if (it
== meta
.cls
.end()) continue;
12090 assertx(it
->second
.idx
< isNotLeaf
.size());
12091 isNotLeaf
[it
->second
.idx
] = true;
12096 // Store all of the (transitive) dependencies for every class,
12097 // calculated lazily. LockFreeLazy ensures that multiple classes can
12098 // access this concurrently and safely calculate it on demand.
12101 // Whether this class is instantiable
12102 bool instantiable
{false};
12104 std::vector
<LockFreeLazy
<DepLookup
>> allDeps
{meta
.allCls
.size()};
12106 // Look up all of the transitive dependencies for the given class.
12107 auto const findAllDeps
= [&] (SString cls
,
12108 TSStringSet
& visited
,
12109 auto const& self
) -> const DepLookup
& {
12110 static const DepLookup empty
;
12112 auto const it
= meta
.cls
.find(cls
);
12113 if (it
== meta
.cls
.end()) {
12115 4, "{} is not instantiable because it is missing\n",
12121 // The class exists, so look up it's dependency information.
12122 auto const idx
= it
->second
.idx
;
12123 auto const& deps
= it
->second
.deps
;
12125 // Check for cycles. A class involved in cyclic inheritance is not
12126 // instantiable (and has no dependencies). This needs to be done
12127 // before accessing the LockFreeLazy below, because if we are in a
12128 // cycle, we'll deadlock when we do so.
12129 auto const emplaced
= visited
.emplace(cls
).second
;
12132 4, "{} is not instantiable because it forms a dependency "
12133 "cycle with itself\n", cls
12135 it
->second
.uninstantiable
= true;
12138 SCOPE_EXIT
{ visited
.erase(cls
); };
12140 assertx(idx
< allDeps
.size());
12141 return allDeps
[idx
].get(
12143 // Otherwise get all of the transitive dependencies of it's
12144 // dependencies and combine them.
12146 out
.instantiable
= !it
->second
.uninstantiable
;
12148 for (auto const d
: deps
) {
12149 auto const& lookup
= self(d
, visited
, self
);
12150 if (lookup
.instantiable
|| meta
.cls
.count(d
)) {
12151 out
.deps
.emplace(d
);
12153 out
.deps
.insert(begin(lookup
.deps
), end(lookup
.deps
));
12154 if (lookup
.instantiable
) continue;
12155 // If the dependency is not instantiable, this isn't
12156 // either. Note, however, we still need to preserve the
12157 // already gathered dependencies, since they'll have to be
12158 // placed in some bucket.
12159 if (out
.instantiable
) {
12161 4, "{} is not instantiable because it depends on {}, "
12162 "which is not instantiable\n",
12165 it
->second
.uninstantiable
= true;
12167 out
.instantiable
= false;
12175 constexpr size_t kBucketSize
= 2000;
12176 constexpr size_t kMaxBucketSize
= 30000;
12178 auto assignments
= assign_hierarchical_work(
12180 std::vector
<SString
> l
;
12181 auto const size
= meta
.allCls
.size();
12182 assertx(size
== isNotLeaf
.size());
12184 for (size_t i
= 0; i
< size
; ++i
) {
12185 if (!isNotLeaf
[i
]) l
.emplace_back(meta
.allCls
[i
]);
12189 meta
.allCls
.size(),
12193 TSStringSet visited
;
12194 auto const& lookup
= findAllDeps(c
, visited
, findAllDeps
);
12195 return std::make_pair(&lookup
.deps
, lookup
.instantiable
);
12197 [&] (const TSStringSet
&, size_t, SString c
) -> Optional
<size_t> {
12198 return meta
.cls
.at(c
).idx
;
12202 // Bucketize functions separately
12204 constexpr size_t kFuncBucketSize
= 5000;
12206 auto funcBuckets
= consistently_bucketize(meta
.allFuncs
, kFuncBucketSize
);
12208 std::vector
<FlattenClassesWork
> work
;
12209 // If both the class and func assignments map to a single bucket,
12210 // combine them both together. This is an optimization for things
12211 // like unit tests, where the total amount of work is low and we
12212 // want to run it all in a single job if possible.
12213 if (assignments
.size() == 1 && funcBuckets
.size() == 1) {
12215 FlattenClassesWork
{
12216 std::move(assignments
[0].classes
),
12217 std::move(assignments
[0].deps
),
12218 std::move(funcBuckets
[0]),
12219 std::move(assignments
[0].uninstantiable
)
12223 // Otherwise split the classes and func work.
12224 work
.reserve(assignments
.size() + funcBuckets
.size());
12225 for (auto& assignment
: assignments
) {
12227 FlattenClassesWork
{
12228 std::move(assignment
.classes
),
12229 std::move(assignment
.deps
),
12231 std::move(assignment
.uninstantiable
)
12235 for (auto& bucket
: funcBuckets
) {
12237 FlattenClassesWork
{ {}, {}, std::move(bucket
), {} }
12242 if (Trace::moduleEnabled(Trace::hhbbc_index
, 5)) {
12243 for (size_t i
= 0; i
< work
.size(); ++i
) {
12244 auto const& [classes
, deps
, funcs
, uninstantiable
] = work
[i
];
12245 FTRACE(5, "flatten work item #{}:\n", i
);
12246 FTRACE(5, " classes ({}):\n", classes
.size());
12247 for (auto const DEBUG_ONLY c
: classes
) FTRACE(5, " {}\n", c
);
12248 FTRACE(5, " deps ({}):\n", deps
.size());
12249 for (auto const DEBUG_ONLY d
: deps
) FTRACE(5, " {}\n", d
);
12250 FTRACE(5, " funcs ({}):\n", funcs
.size());
12251 for (auto const DEBUG_ONLY f
: funcs
) FTRACE(5, " {}\n", f
);
12252 FTRACE(5, " uninstantiable classes ({}):\n", uninstantiable
.size());
12253 for (auto const DEBUG_ONLY c
: uninstantiable
) FTRACE(5, " {}\n", c
);
12260 // Metadata used to assign work buckets for building subclasses. This
12261 // is produced from flattening classes. We don't put closures (or
12262 // Closure base class) into here. There's a lot of them, but we can
12263 // predict their results without running build subclass pass on them.
12264 struct SubclassMetadata
{
12265 // Immediate children and parents of class (not transitive!).
12267 std::vector
<SString
> children
;
12268 std::vector
<SString
> parents
;
12269 size_t idx
; // Index into all classes vector.
12271 TSStringToOneT
<Meta
> meta
;
12272 // All classes to be processed
12273 std::vector
<SString
> all
;
12276 // Metadata used to drive the init-types pass. This is produced from
12277 // flattening classes and added to when building subclasses.
12278 struct InitTypesMetadata
{
12280 // Dependencies of the class. A dependency is a class in a
12281 // property/param/return type-hint.
12283 TSStringSet candidateRegOnlyEquivs
;
12286 // Same as ClsMeta, but for the func
12289 // Modifications to make to an unit
12291 std::vector
<SString
> addClass
;
12292 std::vector
<SString
> removeFunc
;
12293 template <typename SerDe
> void serde(SerDe
& sd
) {
12294 sd(addClass
)(removeFunc
);
12297 TSStringToOneT
<ClsMeta
> classes
;
12298 FSStringToOneT
<FuncMeta
> funcs
;
12299 SStringToOneT
<Fixup
> fixups
;
12300 SStringToOneT
<std::vector
<FuncFamilyEntry
>> nameOnlyFF
;
12303 std::tuple
<SubclassMetadata
, InitTypesMetadata
, std::vector
<InterfaceConflicts
>>
12304 flatten_classes(IndexData
& index
, IndexFlattenMetadata meta
) {
12305 trace_time
trace("flatten classes", index
.sample
);
12306 trace
.ignore_client_stats();
12308 using namespace folly::gen
;
12310 struct ClassUpdate
{
12312 UniquePtrRef
<php::Class
> cls
;
12313 UniquePtrRef
<php::ClassBytecode
> bytecode
;
12314 UniquePtrRef
<ClassInfo2
> cinfo
;
12315 SString unitToAddTo
;
12316 TSStringSet typeUses
;
12317 bool isInterface
{false};
12318 bool has86init
{false};
12319 CompactVector
<SString
> parents
;
12321 struct FuncUpdate
{
12323 UniquePtrRef
<php::Func
> func
;
12324 UniquePtrRef
<FuncInfo2
> finfo
;
12325 TSStringSet typeUses
;
12327 struct ClosureUpdate
{
12332 struct MethodUpdate
{
12334 UniquePtrRef
<MethodsWithoutCInfo
> methods
;
12337 boost::variant
<ClassUpdate
, FuncUpdate
, ClosureUpdate
, MethodUpdate
>;
12338 using UpdateVec
= std::vector
<Update
>;
12340 tbb::concurrent_hash_map
<
12342 InterfaceConflicts
,
12343 string_data_hash_tsame
12346 auto const run
= [&] (FlattenClassesWork work
) -> coro::Task
<UpdateVec
> {
12347 co_await
coro::co_reschedule_on_current_executor
;
12349 if (work
.classes
.empty() &&
12350 work
.funcs
.empty() &&
12351 work
.uninstantiable
.empty()) {
12352 assertx(work
.deps
.empty());
12353 co_return UpdateVec
{};
12356 Client::ExecMetadata metadata
{
12357 .job_key
= folly::sformat(
12358 "flatten classes {}",
12359 work
.classes
.empty()
12360 ? (work
.uninstantiable
.empty()
12362 : work
.uninstantiable
[0])
12366 auto classes
= from(work
.classes
)
12367 | map([&] (SString c
) { return index
.classRefs
.at(c
); })
12368 | as
<std::vector
>();
12369 auto deps
= from(work
.deps
)
12370 | map([&] (SString c
) { return index
.classRefs
.at(c
); })
12371 | as
<std::vector
>();
12372 auto bytecode
= (from(work
.classes
) + from(work
.deps
))
12373 | map([&] (SString c
) { return index
.classBytecodeRefs
.at(c
); })
12374 | as
<std::vector
>();
12375 auto funcs
= from(work
.funcs
)
12376 | map([&] (SString f
) { return index
.funcRefs
.at(f
); })
12377 | as
<std::vector
>();
12378 auto uninstantiableRefs
= from(work
.uninstantiable
)
12379 | map([&] (SString c
) { return index
.classRefs
.at(c
); })
12380 | as
<std::vector
>();
12382 // Gather any type-mappings or missing types referenced by these
12383 // classes or funcs.
12384 std::vector
<TypeMapping
> typeMappings
;
12385 std::vector
<SString
> missingTypes
;
12389 auto const addUnresolved
= [&] (SString u
) {
12390 if (!seen
.emplace(u
).second
) return;
12391 if (auto const m
= folly::get_ptr(meta
.typeMappings
, u
)) {
12392 // If the type-mapping maps an enum, and that enum is
12393 // uninstantiable, just treat it as a missing type.
12394 if (m
->firstEnum
&& meta
.cls
.at(m
->firstEnum
).uninstantiable
) {
12395 missingTypes
.emplace_back(u
);
12397 typeMappings
.emplace_back(*m
);
12399 } else if (!index
.classRefs
.count(u
) ||
12400 meta
.cls
.at(u
).uninstantiable
) {
12401 missingTypes
.emplace_back(u
);
12405 auto const addClass
= [&] (SString c
) {
12406 for (auto const u
: meta
.cls
.at(c
).unresolvedTypes
) addUnresolved(u
);
12408 auto const addFunc
= [&] (SString f
) {
12409 for (auto const u
: meta
.func
.at(f
).unresolvedTypes
) addUnresolved(u
);
12412 for (auto const c
: work
.classes
) addClass(c
);
12413 for (auto const d
: work
.deps
) addClass(d
);
12414 for (auto const f
: work
.funcs
) addFunc(f
);
12417 begin(typeMappings
), end(typeMappings
),
12418 [] (const TypeMapping
& a
, const TypeMapping
& b
) {
12419 return string_data_lt_type
{}(a
.name
, b
.name
);
12422 std::sort(begin(missingTypes
), end(missingTypes
), string_data_lt_type
{});
12425 auto [typeMappingsRef
, missingTypesRef
, config
] = co_await
12427 index
.client
->store(std::move(typeMappings
)),
12428 index
.client
->store(std::move(missingTypes
)),
12429 index
.configRef
->getCopy()
12432 auto results
= co_await
12433 index
.client
->exec(
12438 std::move(classes
),
12440 std::move(bytecode
),
12442 std::move(uninstantiableRefs
),
12443 std::move(typeMappingsRef
),
12444 std::move(missingTypesRef
)
12447 std::move(metadata
)
12449 // Every flattening job is a single work-unit, so we should only
12450 // ever get one result for each one.
12451 assertx(results
.size() == 1);
12452 auto& [clsRefs
, bytecodeRefs
, cinfoRefs
, funcRefs
,
12453 finfoRefs
, methodRefs
, classMetaRef
] = results
[0];
12454 assertx(clsRefs
.size() == cinfoRefs
.size());
12455 assertx(clsRefs
.size() == bytecodeRefs
.size());
12456 assertx(funcRefs
.size() == work
.funcs
.size());
12457 assertx(funcRefs
.size() == finfoRefs
.size());
12459 // We need the output metadata, but everything else stays
12461 auto clsMeta
= co_await index
.client
->load(std::move(classMetaRef
));
12462 assertx(methodRefs
.size() == clsMeta
.uninstantiable
.size());
12464 // Create the updates by combining the job output (but skipping
12465 // over uninstantiable classes).
12467 updates
.reserve(work
.classes
.size() * 3);
12469 size_t outputIdx
= 0;
12470 size_t parentIdx
= 0;
12471 size_t methodIdx
= 0;
12472 for (auto const name
: work
.classes
) {
12473 if (clsMeta
.uninstantiable
.count(name
)) {
12474 assertx(methodIdx
< methodRefs
.size());
12475 updates
.emplace_back(
12476 MethodUpdate
{ name
, std::move(methodRefs
[methodIdx
]) }
12481 assertx(outputIdx
< clsRefs
.size());
12482 assertx(outputIdx
< clsMeta
.classTypeUses
.size());
12484 auto const& flattenMeta
= meta
.cls
.at(name
);
12485 updates
.emplace_back(
12488 std::move(clsRefs
[outputIdx
]),
12489 std::move(bytecodeRefs
[outputIdx
]),
12490 std::move(cinfoRefs
[outputIdx
]),
12492 std::move(clsMeta
.classTypeUses
[outputIdx
]),
12493 (bool)clsMeta
.interfaces
.count(name
),
12494 (bool)clsMeta
.with86init
.count(name
)
12498 // Ignore closures. We don't run the build subclass pass for
12499 // closures, so we don't need information for them.
12500 if (!flattenMeta
.isClosure
) {
12501 assertx(parentIdx
< clsMeta
.parents
.size());
12502 auto const& parents
= clsMeta
.parents
[parentIdx
].names
;
12503 auto& update
= boost::get
<ClassUpdate
>(updates
.back());
12504 update
.parents
.insert(
12505 end(update
.parents
), begin(parents
), end(parents
)
12512 assertx(outputIdx
== clsRefs
.size());
12513 assertx(outputIdx
== clsMeta
.classTypeUses
.size());
12515 for (auto const& [unit
, name
, context
] : clsMeta
.newClosures
) {
12516 updates
.emplace_back(ClosureUpdate
{ name
, context
, unit
});
12519 for (auto const name
: work
.uninstantiable
) {
12520 assertx(clsMeta
.uninstantiable
.count(name
));
12521 assertx(methodIdx
< methodRefs
.size());
12522 updates
.emplace_back(
12523 MethodUpdate
{ name
, std::move(methodRefs
[methodIdx
]) }
12527 assertx(methodIdx
== methodRefs
.size());
12529 assertx(work
.funcs
.size() == clsMeta
.funcTypeUses
.size());
12530 for (size_t i
= 0, size
= work
.funcs
.size(); i
< size
; ++i
) {
12531 updates
.emplace_back(
12534 std::move(funcRefs
[i
]),
12535 std::move(finfoRefs
[i
]),
12536 std::move(clsMeta
.funcTypeUses
[i
])
12541 for (auto const& c
: clsMeta
.interfaceConflicts
) {
12542 decltype(ifaceConflicts
)::accessor acc
;
12543 ifaceConflicts
.insert(acc
, c
.name
);
12544 acc
->second
.name
= c
.name
;
12545 acc
->second
.usage
+= c
.usage
;
12546 acc
->second
.conflicts
.insert(begin(c
.conflicts
), end(c
.conflicts
));
12552 // Calculate the grouping of classes into work units for flattening,
12553 // perform the flattening, and gather all updates from the jobs.
12554 auto allUpdates
= [&] {
12555 auto assignments
= flatten_classes_assign(meta
);
12557 trace_time
trace2("flatten classes work", index
.sample
);
12558 return coro::blockingWait(coro::collectAllRange(
12561 | map([&] (FlattenClassesWork w
) {
12562 return run(std::move(w
)).scheduleOn(index
.executor
->sticky());
12564 | as
<std::vector
>()
12568 // Now take the updates and apply them to the Index tables. This
12569 // needs to be done in a single threaded context (per data
12570 // structure). This also gathers up all the fixups needed.
12572 SubclassMetadata subclassMeta
;
12573 InitTypesMetadata initTypesMeta
;
12576 trace_time
trace2("flatten classes update");
12577 trace2
.ignore_client_stats();
12579 parallel::parallel(
12581 for (auto& updates
: allUpdates
) {
12582 for (auto& update
: updates
) {
12583 auto u
= boost::get
<ClassUpdate
>(&update
);
12585 index
.classRefs
.insert_or_assign(
12593 for (auto& updates
: allUpdates
) {
12594 for (auto& update
: updates
) {
12595 auto u
= boost::get
<ClassUpdate
>(&update
);
12598 index
.classInfoRefs
.emplace(
12600 std::move(u
->cinfo
)
12607 for (auto& updates
: allUpdates
) {
12608 for (auto& update
: updates
) {
12609 auto u
= boost::get
<ClassUpdate
>(&update
);
12611 index
.classBytecodeRefs
.insert_or_assign(
12613 std::move(u
->bytecode
)
12619 for (auto& updates
: allUpdates
) {
12620 for (auto& update
: updates
) {
12621 auto u
= boost::get
<FuncUpdate
>(&update
);
12623 index
.funcRefs
.at(u
->name
) = std::move(u
->func
);
12628 for (auto& updates
: allUpdates
) {
12629 for (auto& update
: updates
) {
12630 auto u
= boost::get
<FuncUpdate
>(&update
);
12633 index
.funcInfoRefs
.emplace(
12635 std::move(u
->finfo
)
12642 for (auto& updates
: allUpdates
) {
12643 for (auto& update
: updates
) {
12644 // Keep closure mappings up to date.
12645 auto u
= boost::get
<ClosureUpdate
>(&update
);
12647 initTypesMeta
.fixups
[u
->unit
].addClass
.emplace_back(u
->name
);
12648 assertx(u
->context
);
12650 index
.closureToClass
.emplace(u
->name
, u
->context
).second
12652 index
.classToClosures
[u
->context
].emplace(u
->name
);
12655 for (auto& [unit
, deletions
] : meta
.unitDeletions
) {
12656 initTypesMeta
.fixups
[unit
].removeFunc
= std::move(deletions
);
12660 // A class which didn't have an 86*init function previously
12661 // can gain one due to trait flattening. Update that here.
12662 for (auto const& updates
: allUpdates
) {
12663 for (auto const& update
: updates
) {
12664 auto u
= boost::get
<ClassUpdate
>(&update
);
12665 if (!u
|| !u
->has86init
) continue;
12666 index
.classesWith86Inits
.emplace(u
->name
);
12671 // Build metadata for the next build subclass pass.
12672 auto& all
= subclassMeta
.all
;
12673 auto& meta
= subclassMeta
.meta
;
12674 for (auto& updates
: allUpdates
) {
12675 for (auto& update
: updates
) {
12676 auto u
= boost::get
<ClassUpdate
>(&update
);
12679 // We shouldn't have parents for closures because we
12680 // special case those explicitly.
12681 if (is_closure_name(u
->name
) || is_closure_base(u
->name
)) {
12682 assertx(u
->parents
.empty());
12685 // Otherwise build the children lists from the parents.
12686 all
.emplace_back(u
->name
);
12687 for (auto const p
: u
->parents
) {
12688 meta
[p
].children
.emplace_back(u
->name
);
12690 auto& parents
= meta
[u
->name
].parents
;
12691 assertx(parents
.empty());
12694 begin(u
->parents
), end(u
->parents
)
12699 std::sort(begin(all
), end(all
), string_data_lt_type
{});
12700 // Make sure there's no duplicates:
12701 assertx(std::adjacent_find(begin(all
), end(all
)) == end(all
));
12703 for (size_t i
= 0; i
< all
.size(); ++i
) meta
[all
[i
]].idx
= i
;
12706 for (auto& updates
: allUpdates
) {
12707 for (auto& update
: updates
) {
12708 auto u
= boost::get
<MethodUpdate
>(&update
);
12711 index
.uninstantiableClsMethRefs
.emplace(
12713 std::move(u
->methods
)
12720 for (auto& updates
: allUpdates
) {
12721 for (auto& update
: updates
) {
12722 if (auto const u
= boost::get
<ClassUpdate
>(&update
)) {
12723 auto& meta
= initTypesMeta
.classes
[u
->name
];
12724 assertx(meta
.deps
.empty());
12725 meta
.deps
.insert(begin(u
->typeUses
), end(u
->typeUses
));
12726 } else if (auto const u
= boost::get
<FuncUpdate
>(&update
)) {
12727 auto& meta
= initTypesMeta
.funcs
[u
->name
];
12728 assertx(meta
.deps
.empty());
12729 meta
.deps
.insert(begin(u
->typeUses
), end(u
->typeUses
));
12737 return std::make_tuple(
12738 std::move(subclassMeta
),
12739 std::move(initTypesMeta
),
12741 std::vector
<InterfaceConflicts
> out
;
12742 out
.reserve(ifaceConflicts
.size());
12743 for (auto& [_
, c
] : ifaceConflicts
) out
.emplace_back(std::move(c
));
12749 //////////////////////////////////////////////////////////////////////
12753 * Subclass lists are built in a similar manner as flattening classes,
12754 * except the order is reversed.
12756 * However, there is one complication: the transitive children of each
12757 * class can be huge. In fact, for large hierarchies, they can easily
12758 * be too large to (efficiently) handle in a single job.
12760 * Rather than (always) processing everything in a single pass, we
12761 * might need to use multiple passes to keep the fan-in down. When
12762 * calculating the work buckets, we keep the size of each bucket into
12763 * account and don't allow any bucket to grow too large. If it does,
12764 * we'll just process that bucket, and process any dependencies in the
12767 * This isn't sufficient. A single class have (far) more direct
12768 * children than we want in a single bucket. Multiple passes don't
12769 * help here because there's no intermediate classes to use as an
12770 * output. To fix this, we insert "splits", which serve to "summarize"
12771 * some subset of a class' direct children.
12773 * For example, suppose a class has 10k direct children, and our
12774 * maximum bucket size is 1k. On the first pass we'll process all of
12775 * the children in ~10 different jobs, each one processing 1k of the
12776 * children, and producing a single split node. The second pass will
12777 * actually process the class and take all of the splits as inputs
12778 * (not the actual children). The inputs to the job has been reduced
12779 * from 10k to 10. This is a simplification. In reality a job can
12780 * produce multiple splits, and inputs can be a mix of splits and
12781 * actual classes. In extreme cases, you might need multiple rounds of
12782 * splits before processing the class.
12784 * There is one other difference between this and the flatten classes
12785 * pass. Unlike in flatten classes, every class (except leafs) are
12786 * "roots" here. We do not promote any dependencies. This causes more
12787 * work overall, but it lets us process more classes in parallel.
12791 * Extern-worker job to build ClassInfo2 subclass lists, and calculate
12792 * various properties on the ClassInfo2 from it.
12794 struct BuildSubclassListJob
{
12795 static std::string
name() { return "hhbbc-build-subclass"; }
12796 static void init(const Config
& config
) {
12797 process_init(config
.o
, config
.gd
, false);
12798 ClassGraph::init();
12800 static void fini() { ClassGraph::destroy(); }
12802 // Aggregated data for some group of classes. The data can either
12803 // come from a split node, or inferred from a group of classes.
12805 // Information about all of the methods with a particular name
12806 // between all of the classes in this Data.
12808 // Methods which are present on at least one regular class.
12809 MethRefSet regularMeths
;
12810 // Methods which are only present on non-regular classes, but is
12811 // private on at least one class. These are sometimes treated
12812 // like a regular class.
12813 MethRefSet nonRegularPrivateMeths
;
12814 // Methods which are only present on non-regular classes (and
12815 // never private). These three sets are always disjoint.
12816 MethRefSet nonRegularMeths
;
12818 Optional
<FuncFamily2::StaticInfo
> allStatic
;
12819 Optional
<FuncFamily2::StaticInfo
> regularStatic
;
12821 // Whether all classes in this Data have a method with this
12823 bool complete
{true};
12824 // Whether all regular classes in this Data have a method with
12826 bool regularComplete
{true};
12827 // Whether any of the methods has a private ancestor.
12828 bool privateAncestor
{false};
12830 template <typename SerDe
> void serde(SerDe
& sd
) {
12831 sd(regularMeths
, std::less
<MethRef
>{})
12832 (nonRegularPrivateMeths
, std::less
<MethRef
>{})
12833 (nonRegularMeths
, std::less
<MethRef
>{})
12842 SStringToOneT
<MethInfo
> methods
;
12844 // The name of properties which might have null values even if the
12845 // type-constraint doesn't allow it (due to system provided
12846 // initial values).
12847 SStringSet propsWithImplicitNullable
;
12849 // The classes for whom isMocked would be true due to one of the
12850 // classes making up this Data. The classes in this set may not
12851 // necessarily be also part of this Data.
12852 TSStringSet mockedClasses
;
12854 bool hasConstProp
{false};
12855 bool hasReifiedGeneric
{false};
12857 bool isSubMocked
{false};
12859 // The meaning of these differ depending on whether the ClassInfo
12860 // contains just it's info, or all of it's subclass info.
12861 bool hasRegularClass
{false};
12862 bool hasRegularClassFull
{false};
12864 template <typename SerDe
> void serde(SerDe
& sd
) {
12865 sd(methods
, string_data_lt
{})
12866 (propsWithImplicitNullable
, string_data_lt
{})
12867 (mockedClasses
, string_data_lt_type
{})
12869 (hasReifiedGeneric
)
12872 (hasRegularClassFull
)
12877 // Split node. Used to wrap a Data when summarizing some subset of a
12878 // class' children.
12881 Split(SString name
, SString cls
) : name
{name
}, cls
{cls
} {}
12885 CompactVector
<SString
> children
;
12886 CompactVector
<ClassGraph
> classGraphs
;
12889 template <typename SerDe
> void serde(SerDe
& sd
) {
12890 ScopedStringDataIndexer _
;
12891 ClassGraph::ScopedSerdeState _2
;
12895 (classGraphs
, nullptr)
12901 // Mark a dependency on a class to a split node. Since the splits
12902 // are not actually part of the hierarchy, the relationship between
12903 // classes and splits cannot be inferred otherwise.
12904 struct EdgeToSplit
{
12907 template <typename SerDe
> void serde(SerDe
& sd
) {
12914 // Job output meant to be downloaded and drive the next round.
12915 struct OutputMeta
{
12916 // For every input ClassInfo, the set of func families present in
12917 // that ClassInfo's method family table. If the ClassInfo is used
12918 // as a dep later, these func families need to be provided as
12920 std::vector
<hphp_fast_set
<FuncFamily2::Id
>> funcFamilyDeps
;
12921 // The ids of new (not provided as an input) func families
12922 // produced. The ids are grouped together to become
12923 // FuncFamilyGroups.
12924 std::vector
<std::vector
<FuncFamily2::Id
>> newFuncFamilyIds
;
12925 // Func family entries corresponding to all methods with a
12926 // particular name encountered in this job. Multiple jobs will
12927 // generally produce func family entries for the same name, so
12928 // they must be aggregated together afterwards.
12929 std::vector
<std::pair
<SString
, FuncFamilyEntry
>> nameOnly
;
12930 std::vector
<std::vector
<SString
>> regOnlyEquivCandidates
;
12932 // For every output class, the set of classes which that class has
12933 // inherited class constants from.
12934 TSStringToOneT
<TSStringSet
> cnsBases
;
12936 template <typename SerDe
> void serde(SerDe
& sd
) {
12937 ScopedStringDataIndexer _
;
12938 sd(funcFamilyDeps
, std::less
<FuncFamily2::Id
>{})
12941 (regOnlyEquivCandidates
)
12942 (cnsBases
, string_data_lt_type
{}, string_data_lt_type
{})
12946 using Output
= Multi
<
12947 Variadic
<std::unique_ptr
<ClassInfo2
>>,
12948 Variadic
<std::unique_ptr
<Split
>>,
12949 Variadic
<std::unique_ptr
<php::Class
>>,
12950 Variadic
<FuncFamilyGroup
>,
12951 Variadic
<std::unique_ptr
<ClassInfo2
>>,
12955 // Each job takes the list of classes and splits which should be
12956 // produced, dependency classes and splits (which are not updated),
12957 // edges between classes and splits, and func families (needed by
12958 // dependency classes). Leafs are like deps, except they'll be
12959 // considered as part of calculating the name-only func families
12960 // because its possible for them to be disjoint from classes.
12961 // (normal deps are not considered as their data is guaranteed to
12962 // be included by a class).
12964 run(Variadic
<std::unique_ptr
<ClassInfo2
>> classes
,
12965 Variadic
<std::unique_ptr
<ClassInfo2
>> deps
,
12966 Variadic
<std::unique_ptr
<ClassInfo2
>> leafs
,
12967 Variadic
<std::unique_ptr
<Split
>> splits
,
12968 Variadic
<std::unique_ptr
<Split
>> splitDeps
,
12969 Variadic
<std::unique_ptr
<php::Class
>> phpClasses
,
12970 Variadic
<EdgeToSplit
> edges
,
12971 Variadic
<FuncFamilyGroup
> funcFamilies
) {
12972 // Store mappings of names to classes and edges.
12976 for (auto const& cinfo
: classes
.vals
) {
12977 always_assert(!cinfo
->classGraph
.isMissing());
12978 always_assert(!cinfo
->classGraph
.hasCompleteChildren());
12979 always_assert(!cinfo
->classGraph
.isConservative());
12981 for (auto const& cinfo
: leafs
.vals
) {
12982 always_assert(!cinfo
->classGraph
.isMissing());
12983 always_assert(!cinfo
->classGraph
.hasCompleteChildren());
12984 always_assert(!cinfo
->classGraph
.isConservative());
12986 for (auto const& cinfo
: deps
.vals
) {
12987 always_assert(!cinfo
->classGraph
.isMissing());
12989 for (auto const& split
: splits
.vals
) {
12990 for (auto const child
: split
->classGraphs
) {
12991 always_assert(!child
.isMissing());
12992 always_assert(!child
.hasCompleteChildren());
12993 always_assert(!child
.isConservative());
12996 for (auto const& split
: splitDeps
.vals
) {
12997 for (auto const child
: split
->classGraphs
) {
12998 always_assert(!child
.isMissing());
12999 always_assert(child
.hasCompleteChildren() ||
13000 child
.isConservative());
13005 for (auto& cinfo
: classes
.vals
) {
13006 assertx(!is_closure_name(cinfo
->name
));
13008 index
.classInfos
.emplace(cinfo
->name
, cinfo
.get()).second
13010 index
.top
.emplace(cinfo
->name
);
13012 for (auto& cinfo
: deps
.vals
) {
13013 assertx(!is_closure_name(cinfo
->name
));
13015 index
.classInfos
.emplace(cinfo
->name
, cinfo
.get()).second
13018 for (auto& cinfo
: leafs
.vals
) {
13019 assertx(!is_closure_name(cinfo
->name
));
13021 index
.classInfos
.emplace(cinfo
->name
, cinfo
.get()).second
13024 for (auto& split
: splits
.vals
) {
13026 index
.splits
.emplace(split
->name
, split
.get()).second
13028 index
.top
.emplace(split
->name
);
13030 for (auto& split
: splitDeps
.vals
) {
13031 assertx(split
->children
.empty());
13033 index
.splits
.emplace(split
->name
, split
.get()).second
13036 for (auto& cls
: phpClasses
.vals
) {
13037 assertx(!is_closure(*cls
));
13039 index
.classes
.emplace(cls
->name
, cls
.get()).second
13042 for (auto& group
: funcFamilies
.vals
) {
13043 for (auto& ff
: group
.m_ffs
) {
13044 auto const id
= ff
->m_id
;
13045 // We could have multiple groups which contain the same
13046 // FuncFamily, so don't assert uniqueness here. We'll just
13047 // take the first one we see (they should all be equivalent).
13048 index
.funcFamilies
.emplace(id
, std::move(ff
));
13052 index
.aggregateData
.reserve(index
.top
.size());
13056 // Mark all of the classes (including leafs) as being complete
13057 // since their subclass lists are correct.
13058 for (auto& cinfo
: leafs
.vals
) {
13059 if (cinfo
->classGraph
.hasCompleteChildren()) continue;
13060 cinfo
->classGraph
.setComplete();
13062 for (auto& cinfo
: classes
.vals
) {
13063 if (cinfo
->classGraph
.hasCompleteChildren() ||
13064 cinfo
->classGraph
.isConservative()) {
13067 cinfo
->classGraph
.setComplete();
13069 for (auto& cinfo
: deps
.vals
) {
13070 if (cinfo
->classGraph
.hasCompleteChildren() ||
13071 cinfo
->classGraph
.isConservative()) {
13074 cinfo
->classGraph
.setComplete();
13076 for (auto& split
: splits
.vals
) {
13077 for (auto child
: split
->classGraphs
) {
13078 if (child
.hasCompleteChildren() ||
13079 child
.isConservative()) {
13082 child
.setComplete();
13086 // Store the regular-only equivalent classes in the output
13087 // metadata. This will be used in the init-types pass.
13088 meta
.regOnlyEquivCandidates
.reserve(classes
.vals
.size());
13089 for (auto& cinfo
: classes
.vals
) {
13090 meta
.regOnlyEquivCandidates
.emplace_back();
13091 auto& candidates
= meta
.regOnlyEquivCandidates
.back();
13092 for (auto const g
: cinfo
->classGraph
.candidateRegOnlyEquivs()) {
13093 candidates
.emplace_back(g
.name());
13097 // If there's no classes or splits, this job is doing nothing but
13098 // calculating name only func family entries (so should have at
13099 // least one leaf).
13100 if (!index
.top
.empty()) {
13101 build_children(index
, edges
.vals
);
13102 process_roots(index
, classes
.vals
, splits
.vals
);
13104 assertx(classes
.vals
.empty());
13105 assertx(splits
.vals
.empty());
13106 assertx(index
.splits
.empty());
13107 assertx(index
.funcFamilies
.empty());
13108 assertx(!leafs
.vals
.empty());
13109 assertx(!index
.classInfos
.empty());
13113 make_name_only_method_entries(index
, classes
.vals
, leafs
.vals
);
13115 // Record dependencies for each input class. A func family is a
13116 // dependency of the class if it appears in the method families
13118 meta
.funcFamilyDeps
.reserve(classes
.vals
.size());
13119 for (auto const& cinfo
: classes
.vals
) {
13120 meta
.funcFamilyDeps
.emplace_back();
13121 auto& deps
= meta
.funcFamilyDeps
.back();
13122 for (auto const& [_
, entry
] : cinfo
->methodFamilies
) {
13125 [&] (const FuncFamilyEntry::BothFF
& e
) { deps
.emplace(e
.m_ff
); },
13126 [&] (const FuncFamilyEntry::FFAndSingle
& e
) { deps
.emplace(e
.m_ff
); },
13127 [&] (const FuncFamilyEntry::FFAndNone
& e
) { deps
.emplace(e
.m_ff
); },
13128 [&] (const FuncFamilyEntry::BothSingle
&) {},
13129 [&] (const FuncFamilyEntry::SingleAndNone
&) {},
13130 [&] (const FuncFamilyEntry::None
&) {}
13135 Variadic
<FuncFamilyGroup
> funcFamilyGroups
;
13136 group_func_families(index
, funcFamilyGroups
.vals
, meta
.newFuncFamilyIds
);
13138 auto const addCnsBase
= [&] (const ClassInfo2
& cinfo
) {
13139 auto& bases
= meta
.cnsBases
[cinfo
.name
];
13140 for (auto const& [_
, idx
] : cinfo
.clsConstants
) {
13141 if (!cinfo
.name
->tsame(idx
.idx
.cls
)) bases
.emplace(idx
.idx
.cls
);
13144 for (auto const& cinfo
: classes
.vals
) addCnsBase(*cinfo
);
13145 for (auto const& cinfo
: leafs
.vals
) addCnsBase(*cinfo
);
13147 // We only need to provide php::Class which correspond to a class
13148 // which wasn't a dep.
13149 phpClasses
.vals
.erase(
13151 begin(phpClasses
.vals
),
13152 end(phpClasses
.vals
),
13153 [&] (const std::unique_ptr
<php::Class
>& c
) {
13154 return !index
.top
.count(c
->name
);
13157 end(phpClasses
.vals
)
13160 return std::make_tuple(
13161 std::move(classes
),
13163 std::move(phpClasses
),
13164 std::move(funcFamilyGroups
),
13175 * When building a func family, MethRefSets must be sorted, then
13176 * hashed in order to generate the unique id. Once we do so, we can
13177 * then check if that func family already exists. For large func
13178 * families, this can very expensive and we might have to do this
13179 * (wasted) work multiple times.
13181 * To avoid this, we add a cache before the sorting/hashing
13182 * step. Instead of using a func family id (which is the expensive
13183 * thing to generate), the cache is keyed by the set of methods
13184 * directly. A commutative hash is used so that we don't actually
13185 * have to sort the MethRefSets, and equality is just equality of
13186 * the MethRefSets. Moreover, we make use of hetereogenous lookup to
13187 * avoid having to copy any MethRefSets (again they can be large)
13188 * when doing the lookup.
13191 // What is actually stored in the cache. Keeps a copy of the
13193 struct MethInfoTuple
{
13194 MethRefSet regular
;
13195 MethRefSet nonRegularPrivate
;
13196 MethRefSet nonRegular
;
13198 // Used for lookups. Just has pointers to the MethRefSets, so we
13199 // don't have to do any copying for the lookup.
13200 struct MethInfoTupleProxy
{
13201 const MethRefSet
* regular
;
13202 const MethRefSet
* nonRegularPrivate
;
13203 const MethRefSet
* nonRegular
;
13206 struct MethInfoTupleHasher
{
13207 using is_transparent
= void;
13209 size_t operator()(const MethInfoTuple
& t
) const {
13210 auto const h1
= folly::hash::commutative_hash_combine_range_generic(
13211 0, MethRef::Hash
{}, begin(t
.regular
), end(t
.regular
)
13213 auto const h2
= folly::hash::commutative_hash_combine_range_generic(
13214 0, MethRef::Hash
{}, begin(t
.nonRegularPrivate
), end(t
.nonRegularPrivate
)
13216 auto const h3
= folly::hash::commutative_hash_combine_range_generic(
13217 0, MethRef::Hash
{}, begin(t
.nonRegular
), end(t
.nonRegular
)
13219 return folly::hash::hash_combine(h1
, h2
, h3
);
13221 size_t operator()(const MethInfoTupleProxy
& t
) const {
13222 auto const h1
= folly::hash::commutative_hash_combine_range_generic(
13223 0, MethRef::Hash
{}, begin(*t
.regular
), end(*t
.regular
)
13225 auto const h2
= folly::hash::commutative_hash_combine_range_generic(
13226 0, MethRef::Hash
{}, begin(*t
.nonRegularPrivate
), end(*t
.nonRegularPrivate
)
13228 auto const h3
= folly::hash::commutative_hash_combine_range_generic(
13229 0, MethRef::Hash
{}, begin(*t
.nonRegular
), end(*t
.nonRegular
)
13231 return folly::hash::hash_combine(h1
, h2
, h3
);
13234 struct MethInfoTupleEquals
{
13235 using is_transparent
= void;
13237 bool operator()(const MethInfoTuple
& t1
, const MethInfoTuple
& t2
) const {
13239 t1
.regular
== t2
.regular
&&
13240 t1
.nonRegularPrivate
== t2
.nonRegularPrivate
&&
13241 t1
.nonRegular
== t2
.nonRegular
;
13243 bool operator()(const MethInfoTupleProxy
& t1
,
13244 const MethInfoTuple
& t2
) const {
13246 *t1
.regular
== t2
.regular
&&
13247 *t1
.nonRegularPrivate
== t2
.nonRegularPrivate
&&
13248 *t1
.nonRegular
== t2
.nonRegular
;
13252 struct LocalIndex
{
13253 // All ClassInfos, whether inputs or dependencies.
13254 TSStringToOneT
<ClassInfo2
*> classInfos
;
13255 // All splits, whether inputs or dependencies.
13256 TSStringToOneT
<Split
*> splits
;
13257 // All php::Class, whether inputs or dependencies.
13258 TSStringToOneT
<php::Class
*> classes
;
13260 // ClassInfos and splits which are inputs (IE, we want to
13261 // calculate data for).
13264 // Aggregated data for an input
13265 TSStringToOneT
<Data
> aggregateData
;
13267 // Mapping of input ClassInfos/splits to all of their subclasses
13268 // present in this Job. Some of the children may be splits, which
13269 // means some subset of the children were processed in another
13271 TSStringToOneT
<std::vector
<SString
>> children
;
13273 // The leafs in this job. This isn't necessarily an actual leaf,
13274 // but one whose children haven't been provided in this job
13275 // (because they've already been processed). Classes which are
13276 // leafs have information in their ClassInfo2 which reflect all of
13277 // their subclasses, otherwise just their own information.
13280 // All func families available in this Job, either from inputs, or
13281 // created during processing.
13282 hphp_fast_map
<FuncFamily2::Id
, std::unique_ptr
<FuncFamily2
>> funcFamilies
;
13284 // Above mentioned func family cache. If an entry is present here,
13285 // we know the func family already exists and don't need to do
13286 // expensive sorting/hashing.
13290 MethInfoTupleHasher
,
13291 MethInfoTupleEquals
13294 // funcFamilies contains all func families. If a func family is
13295 // created during processing, it will be inserted here (used to
13296 // determine outputs).
13297 std::vector
<FuncFamily2::Id
> newFuncFamilies
;
13299 php::Class
& cls(SString name
) {
13300 auto const it
= classes
.find(name
);
13301 always_assert(it
!= end(classes
));
13302 return *it
->second
;
13306 // Take all of the func families produced by this job and group them
13307 // together into FuncFamilyGroups. We produce both the
13308 // FuncFamilyGroups themselves, but also the associated ids in each
13309 // group (which will be output as metadata).
13310 static void group_func_families(
13312 std::vector
<FuncFamilyGroup
>& groups
,
13313 std::vector
<std::vector
<FuncFamily2::Id
>>& ids
13315 constexpr size_t kGroupSize
= 5000;
13317 // The grouping algorithm is very simple. First we sort all of the
13318 // func families by size. We then just group adjacent families
13319 // until their total size exceeds some threshold. Once it does, we
13320 // start a new group.
13322 begin(index
.newFuncFamilies
),
13323 end(index
.newFuncFamilies
),
13324 [&] (const FuncFamily2::Id
& id1
, const FuncFamily2::Id
& id2
) {
13325 auto const& ff1
= index
.funcFamilies
.at(id1
);
13326 auto const& ff2
= index
.funcFamilies
.at(id2
);
13328 ff1
->m_regular
.size() +
13329 ff1
->m_nonRegularPrivate
.size() +
13330 ff1
->m_nonRegular
.size();
13332 ff2
->m_regular
.size() +
13333 ff2
->m_nonRegularPrivate
.size() +
13334 ff2
->m_nonRegular
.size();
13335 if (size1
!= size2
) return size1
< size2
;
13340 size_t current
= 0;
13341 for (auto const& id
: index
.newFuncFamilies
) {
13342 auto& ff
= index
.funcFamilies
.at(id
);
13344 ff
->m_regular
.size() +
13345 ff
->m_nonRegularPrivate
.size() +
13346 ff
->m_nonRegular
.size();
13347 if (groups
.empty() || current
> kGroupSize
) {
13348 groups
.emplace_back();
13349 ids
.emplace_back();
13352 groups
.back().m_ffs
.emplace_back(std::move(ff
));
13353 ids
.back().emplace_back(id
);
13358 // Produce a set of name-only func families from the given set of
13359 // roots and leafs. It is assumed that any roots have already been
13360 // processed by process_roots, so that they'll have any appropriate
13361 // method families on them. Only entries which are "first name" are
13363 static std::vector
<std::pair
<SString
, FuncFamilyEntry
>>
13364 make_name_only_method_entries(
13366 const std::vector
<std::unique_ptr
<ClassInfo2
>>& roots
,
13367 const std::vector
<std::unique_ptr
<ClassInfo2
>>& leafs
13369 SStringToOneT
<Data::MethInfo
> infos
;
13371 // Use the already calculated method family and merge
13372 // it's contents into what we already have.
13373 auto const process
= [&] (const ClassInfo2
* cinfo
,
13375 auto const it
= cinfo
->methodFamilies
.find(name
);
13376 always_assert(it
!= end(cinfo
->methodFamilies
));
13377 auto entryInfo
= meth_info_from_func_family_entry(index
, it
->second
);
13379 auto& info
= infos
[name
];
13380 info
.complete
= false;
13381 info
.regularComplete
= false;
13383 for (auto const& meth
: entryInfo
.regularMeths
) {
13384 if (info
.regularMeths
.count(meth
)) continue;
13385 info
.regularMeths
.emplace(meth
);
13386 info
.nonRegularPrivateMeths
.erase(meth
);
13387 info
.nonRegularMeths
.erase(meth
);
13389 for (auto const& meth
: entryInfo
.nonRegularPrivateMeths
) {
13390 if (info
.regularMeths
.count(meth
) ||
13391 info
.nonRegularPrivateMeths
.count(meth
)) {
13394 info
.nonRegularPrivateMeths
.emplace(meth
);
13395 info
.nonRegularMeths
.erase(meth
);
13397 for (auto const& meth
: entryInfo
.nonRegularMeths
) {
13398 if (info
.regularMeths
.count(meth
) ||
13399 info
.nonRegularPrivateMeths
.count(meth
) ||
13400 info
.nonRegularMeths
.count(meth
)) {
13403 info
.nonRegularMeths
.emplace(meth
);
13406 // Merge any StaticInfo entries we have for this method.
13407 if (entryInfo
.allStatic
) {
13408 if (!info
.allStatic
) {
13409 info
.allStatic
= std::move(*entryInfo
.allStatic
);
13411 *info
.allStatic
|= *entryInfo
.allStatic
;
13414 if (entryInfo
.regularStatic
) {
13415 if (!info
.regularStatic
) {
13416 info
.regularStatic
= std::move(*entryInfo
.regularStatic
);
13418 *info
.regularStatic
|= *entryInfo
.regularStatic
;
13423 // First process the roots. These methods might be overridden or
13425 for (auto const& cinfo
: roots
) {
13426 for (auto const& [name
, mte
] : cinfo
->methods
) {
13427 if (!mte
.firstName()) continue;
13428 if (!has_name_only_func_family(name
)) continue;
13429 process(cinfo
.get(), name
);
13433 // Leafs are by definition always AttrNoOverride.
13434 for (auto const& cinfo
: leafs
) {
13435 for (auto const& [name
, mte
] : cinfo
->methods
) {
13436 if (!mte
.firstName()) continue;
13437 if (!has_name_only_func_family(name
)) continue;
13438 process(cinfo
.get(), name
);
13442 // Make the MethInfo order deterministic
13443 std::vector
<SString
> sorted
;
13444 sorted
.reserve(infos
.size());
13445 for (auto const& [name
, _
] : infos
) sorted
.emplace_back(name
);
13446 std::sort(begin(sorted
), end(sorted
), string_data_lt
{});
13448 std::vector
<std::pair
<SString
, FuncFamilyEntry
>> entries
;
13449 entries
.reserve(infos
.size());
13451 // Turn the MethInfos into FuncFamilyEntries
13452 for (auto const name
: sorted
) {
13453 auto& info
= infos
.at(name
);
13454 entries
.emplace_back(
13456 make_method_family_entry(index
, name
, std::move(info
))
13462 // From the information present in the inputs, calculate a mapping
13463 // of classes and splits to their children (which can be other
13464 // classes or split nodes). This is not just direct children, but
13465 // all transitive subclasses.
13466 static void build_children(LocalIndex
& index
,
13467 const std::vector
<EdgeToSplit
>& edges
) {
13468 TSStringToOneT
<TSStringSet
> children
;
13469 // First record direct children. This can be inferred from the
13470 // parents of all present ClassInfos:
13472 // Everything starts out as a leaf.
13473 index
.leafs
.reserve(index
.classInfos
.size());
13474 for (auto const [name
, _
] : index
.classInfos
) {
13475 index
.leafs
.emplace(name
);
13478 auto const onParent
= [&] (SString parent
, const ClassInfo2
* child
) {
13479 // Due to how work is divided, a class might have parents not
13480 // present in this job. Ignore those.
13481 if (!index
.classInfos
.count(parent
)) return;
13482 children
[parent
].emplace(child
->name
);
13483 // If you're a parent, you're not a leaf.
13484 index
.leafs
.erase(parent
);
13487 for (auto const [name
, cinfo
] : index
.classInfos
) {
13488 if (cinfo
->parent
) onParent(cinfo
->parent
, cinfo
);
13489 for (auto const iface
: cinfo
->classGraph
.declInterfaces()) {
13490 onParent(iface
.name(), cinfo
);
13492 for (auto const trait
: cinfo
->classGraph
.usedTraits()) {
13493 onParent(trait
.name(), cinfo
);
13497 // Use the edges provided to the Job to know the mapping from
13498 // ClassInfo to split (it cannot be inferred otherwise).
13499 for (auto const& edge
: edges
) {
13500 SCOPE_ASSERT_DETAIL("Edge not present in job") {
13501 return folly::sformat("{} -> {}", edge
.cls
, edge
.split
);
13503 assertx(index
.classInfos
.count(edge
.cls
));
13504 assertx(index
.splits
.count(edge
.split
));
13505 children
[edge
.cls
].emplace(edge
.split
);
13508 // Every "top" ClassInfo also has itself as a subclass (this
13509 // matches the semantics of the subclass list and simplifies the
13511 for (auto const name
: index
.top
) {
13512 if (auto const split
= folly::get_default(index
.splits
, name
)) {
13513 // Copy the children list out of the split and add it to the
13515 auto& c
= children
[name
];
13516 for (auto const child
: split
->children
) {
13517 assertx(index
.classInfos
.count(child
) ||
13518 index
.splits
.count(child
));
13524 // Calculate the indegree for all children. The indegree for a given node
13525 // differs depending on the top used, so these are calculated separately.
13526 auto const getIndegree
= [&](SString root
) {
13527 TSStringSet visited
;
13528 TSStringSet toExplore
{root
};
13529 TSStringSet toExploreNext
;
13530 TSStringToOneT
<uint32_t> indegree
;
13532 while (!toExplore
.empty()) {
13533 toExploreNext
.clear();
13534 for (auto const child
: toExplore
) {
13535 if (visited
.count(child
)) continue;
13536 visited
.emplace(child
);
13537 auto const it
= children
.find(child
);
13538 // May not exist in children if processed in earlier round.
13539 if (it
== end(children
)) continue;
13540 for (auto const c
: it
->second
) {
13542 toExploreNext
.emplace(c
);
13545 std::swap(toExplore
, toExploreNext
);
13550 // Topological sort the transitive children for each node.
13551 for (auto& [name
, _
] : children
) {
13552 auto indegree
= getIndegree(name
);
13553 std::vector
<SString
> sorted
{name
};
13555 int sortedBegin
= 0;
13556 int sortedEnd
= sorted
.size();
13558 while (sortedBegin
!= sortedEnd
) {
13559 for (int i
= sortedBegin
; i
< sortedEnd
; i
++) {
13560 auto const cls
= sorted
[i
];
13561 auto const it
= children
.find(cls
);
13562 if (it
== end(children
)) continue;
13563 for (auto const c
: it
->second
) {
13565 if (indegree
[c
] == 0) sorted
.emplace_back(c
);
13568 sortedBegin
= sortedEnd
;
13569 sortedEnd
= sorted
.size();
13571 assertx(indegree
.size() + 1 == sorted
.size());
13572 index
.children
[name
] = std::move(sorted
);
13576 static FuncFamily2::StaticInfo
static_info_from_meth_meta(
13577 const FuncFamilyEntry::MethMetadata
& meta
13579 FuncFamily2::StaticInfo info
;
13580 info
.m_numInOut
= meta
.m_numInOut
;
13581 info
.m_requiredCoeffects
= meta
.m_requiredCoeffects
;
13582 info
.m_coeffectRules
= meta
.m_coeffectRules
;
13583 info
.m_paramPreps
= meta
.m_prepKinds
;
13584 info
.m_minNonVariadicParams
= info
.m_maxNonVariadicParams
=
13585 meta
.m_nonVariadicParams
;
13586 info
.m_isReadonlyReturn
= yesOrNo(meta
.m_isReadonlyReturn
);
13587 info
.m_isReadonlyThis
= yesOrNo(meta
.m_isReadonlyThis
);
13588 info
.m_supportsAER
= yesOrNo(meta
.m_supportsAER
);
13589 info
.m_maybeReified
= meta
.m_isReified
;
13590 info
.m_maybeCaresAboutDynCalls
= meta
.m_caresAboutDyncalls
;
13591 info
.m_maybeBuiltin
= meta
.m_builtin
;
13595 // Turn a FuncFamilyEntry into an equivalent Data::MethInfo.
13596 static Data::MethInfo
13597 meth_info_from_func_family_entry(LocalIndex
& index
,
13598 const FuncFamilyEntry
& entry
) {
13599 Data::MethInfo info
;
13600 info
.complete
= !entry
.m_allIncomplete
;
13601 info
.regularComplete
= !entry
.m_regularIncomplete
;
13602 info
.privateAncestor
= entry
.m_privateAncestor
;
13604 auto const getFF
= [&] (const FuncFamily2::Id
& id
)
13605 -> const FuncFamily2
& {
13606 auto const it
= index
.funcFamilies
.find(id
);
13607 always_assert_flog(
13608 it
!= end(index
.funcFamilies
),
13609 "Tried to access non-existent func-family '{}'",
13612 return *it
->second
;
13617 [&] (const FuncFamilyEntry::BothFF
& e
) {
13618 auto const& ff
= getFF(e
.m_ff
);
13619 info
.regularMeths
.insert(
13620 begin(ff
.m_regular
),
13623 info
.nonRegularPrivateMeths
.insert(
13624 begin(ff
.m_nonRegularPrivate
),
13625 end(ff
.m_nonRegularPrivate
)
13627 info
.nonRegularMeths
.insert(
13628 begin(ff
.m_nonRegular
),
13629 end(ff
.m_nonRegular
)
13631 assertx(ff
.m_allStatic
);
13632 assertx(ff
.m_regularStatic
);
13633 info
.allStatic
= ff
.m_allStatic
;
13634 info
.regularStatic
= ff
.m_regularStatic
;
13636 [&] (const FuncFamilyEntry::FFAndSingle
& e
) {
13637 auto const& ff
= getFF(e
.m_ff
);
13638 info
.nonRegularMeths
.insert(
13639 begin(ff
.m_nonRegular
),
13640 end(ff
.m_nonRegular
)
13642 if (e
.m_nonRegularPrivate
) {
13643 assertx(ff
.m_nonRegularPrivate
.size() == 1);
13644 assertx(ff
.m_nonRegularPrivate
[0] == e
.m_regular
);
13645 info
.nonRegularPrivateMeths
.emplace(e
.m_regular
);
13647 assertx(ff
.m_regular
.size() == 1);
13648 assertx(ff
.m_regular
[0] == e
.m_regular
);
13649 info
.regularMeths
.emplace(e
.m_regular
);
13651 assertx(ff
.m_allStatic
);
13652 assertx(ff
.m_regularStatic
);
13653 info
.allStatic
= ff
.m_allStatic
;
13654 info
.regularStatic
= ff
.m_regularStatic
;
13656 [&] (const FuncFamilyEntry::FFAndNone
& e
) {
13657 auto const& ff
= getFF(e
.m_ff
);
13658 assertx(ff
.m_regular
.empty());
13659 info
.nonRegularMeths
.insert(
13660 begin(ff
.m_nonRegular
),
13661 end(ff
.m_nonRegular
)
13663 assertx(ff
.m_allStatic
);
13664 assertx(!ff
.m_regularStatic
);
13665 info
.allStatic
= ff
.m_allStatic
;
13667 [&] (const FuncFamilyEntry::BothSingle
& e
) {
13668 if (e
.m_nonRegularPrivate
) {
13669 info
.nonRegularPrivateMeths
.emplace(e
.m_all
);
13671 info
.regularMeths
.emplace(e
.m_all
);
13673 info
.allStatic
= info
.regularStatic
=
13674 static_info_from_meth_meta(e
.m_meta
);
13676 [&] (const FuncFamilyEntry::SingleAndNone
& e
) {
13677 info
.nonRegularMeths
.emplace(e
.m_all
);
13678 info
.allStatic
= static_info_from_meth_meta(e
.m_meta
);
13680 [&] (const FuncFamilyEntry::None
&) {
13681 assertx(!info
.complete
);
13688 // Create a Data representing the single ClassInfo or split with the
13690 static Data
build_data(LocalIndex
& index
, SString clsname
) {
13691 // Does this name represent a class?
13692 if (auto const cinfo
= folly::get_default(index
.classInfos
, clsname
)) {
13693 // It's a class. We need to build a Data from what's in the
13694 // ClassInfo. If the ClassInfo hasn't been processed already
13695 // (it's a leaf or its the first round), the data will reflect
13696 // just that class. However if the ClassInfo has been processed
13697 // (it's a dependencies and it's past the first round), it will
13698 // reflect any subclasses of that ClassInfo as well.
13701 // Use the method family table to build initial MethInfos (if
13702 // the ClassInfo hasn't been processed this will be empty).
13703 for (auto const& [name
, entry
] : cinfo
->methodFamilies
) {
13704 data
.methods
.emplace(
13706 meth_info_from_func_family_entry(index
, entry
)
13710 auto const& cls
= index
.cls(cinfo
->name
);
13713 for (auto const& [name
, mte
] : cinfo
->methods
) {
13714 if (is_special_method_name(name
)) continue;
13716 // Every method should have a methodFamilies entry. If this
13717 // method is AttrNoOverride, it shouldn't have a FuncFamily
13718 // associated with it.
13719 auto const it
= cinfo
->methodFamilies
.find(name
);
13720 always_assert(it
!= end(cinfo
->methodFamilies
));
13722 if (mte
.attrs
& AttrNoOverride
) {
13724 boost::get
<FuncFamilyEntry::BothSingle
>(&it
->second
.m_meths
) ||
13725 boost::get
<FuncFamilyEntry::SingleAndNone
>(&it
->second
.m_meths
)
13731 // Create a MethInfo for any missing methods as well.
13732 for (auto const name
: cinfo
->missingMethods
) {
13733 assertx(!cinfo
->methods
.count(name
));
13734 if (data
.methods
.count(name
)) continue;
13735 // The MethInfo will be empty, and be marked as incomplete.
13736 auto& info
= data
.methods
[name
];
13737 info
.complete
= false;
13738 if (cinfo
->isRegularClass
) info
.regularComplete
= false;
13741 data
.hasConstProp
= cinfo
->subHasConstProp
;
13742 data
.hasReifiedGeneric
= cinfo
->subHasReifiedGeneric
;
13744 // If this is a mock class, any direct parent of this class
13745 // should be marked as mocked.
13746 if (cinfo
->isMockClass
) {
13747 for (auto const p
: cinfo
->classGraph
.directParents()) {
13748 data
.mockedClasses
.emplace(p
.name());
13751 data
.isSubMocked
= cinfo
->isMocked
|| cinfo
->isSubMocked
;
13753 data
.hasRegularClass
= cinfo
->isRegularClass
;
13754 data
.hasRegularClassFull
=
13755 data
.hasRegularClass
|| cinfo
->classGraph
.mightHaveRegularSubclass();
13756 if (!data
.hasRegularClass
&& index
.leafs
.count(clsname
)) {
13757 data
.hasRegularClass
= data
.hasRegularClassFull
;
13760 for (auto const& prop
: cls
.properties
) {
13761 if (!(prop
.attrs
& (AttrStatic
|AttrPrivate
|AttrNoImplicitNullable
))) {
13762 data
.propsWithImplicitNullable
.emplace(prop
.name
);
13769 // It doesn't represent a class. It should represent a
13772 // A split cannot be both a root and a dependency due to how we
13773 // set up the buckets.
13774 assertx(!index
.top
.count(clsname
));
13775 auto const split
= folly::get_default(index
.splits
, clsname
);
13776 always_assert(split
!= nullptr);
13777 assertx(split
->children
.empty());
13778 // Split already contains the Data, so nothing to do but return
13780 return split
->data
;
13783 static void update_data(Data
& data
, Data childData
) {
13784 // Combine MethInfos for each method name:
13787 [&] (std::pair
<const SString
, Data::MethInfo
>& p
) {
13788 auto const name
= p
.first
;
13789 auto& info
= p
.second
;
13791 if (auto const childInfo
=
13792 folly::get_ptr(childData
.methods
, name
)) {
13793 // There's a MethInfo with that name in the
13794 // child. "Promote" the MethRefs if they're in a superior
13795 // status in the child.
13796 for (auto const& meth
: childInfo
->regularMeths
) {
13797 if (info
.regularMeths
.count(meth
)) continue;
13798 info
.regularMeths
.emplace(meth
);
13799 info
.nonRegularPrivateMeths
.erase(meth
);
13800 info
.nonRegularMeths
.erase(meth
);
13802 for (auto const& meth
: childInfo
->nonRegularPrivateMeths
) {
13803 if (info
.regularMeths
.count(meth
) ||
13804 info
.nonRegularPrivateMeths
.count(meth
)) {
13807 info
.nonRegularPrivateMeths
.emplace(meth
);
13808 info
.nonRegularMeths
.erase(meth
);
13810 for (auto const& meth
: childInfo
->nonRegularMeths
) {
13811 if (info
.regularMeths
.count(meth
) ||
13812 info
.nonRegularPrivateMeths
.count(meth
) ||
13813 info
.nonRegularMeths
.count(meth
)) {
13816 info
.nonRegularMeths
.emplace(meth
);
13818 info
.complete
&= childInfo
->complete
;
13819 if (childData
.hasRegularClassFull
) {
13820 info
.regularComplete
&= childInfo
->regularComplete
;
13821 info
.privateAncestor
|= childInfo
->privateAncestor
;
13823 assertx(childInfo
->regularComplete
);
13824 assertx(!childInfo
->privateAncestor
);
13827 if (childInfo
->allStatic
) {
13828 if (!info
.allStatic
) {
13829 info
.allStatic
= std::move(*childInfo
->allStatic
);
13831 *info
.allStatic
|= *childInfo
->allStatic
;
13834 if (childInfo
->regularStatic
) {
13835 if (!info
.regularStatic
) {
13836 info
.regularStatic
= std::move(*childInfo
->regularStatic
);
13838 *info
.regularStatic
|= *childInfo
->regularStatic
;
13845 // There's no MethInfo with that name in the child. We might
13846 // still want to keep the MethInfo because it will be needed
13847 // for expanding abstract class/interface method
13848 // families. If the child has a regular class, we can remove
13849 // it (it won't be part of the expansion).
13851 childData
.hasRegularClass
||
13852 !info
.regularComplete
||
13853 info
.privateAncestor
||
13854 is_special_method_name(name
) ||
13855 name
== s_construct
.get();
13859 // Since we drop non-matching method names only if the class has
13860 // a regular class, it introduces an ordering dependency. If the
13861 // first class we encounter has a regular class, everything
13862 // works fine. However, if the first class we encounter does not
13863 // have a regular class, the Data will have its methods. If we
13864 // eventually process a class which does have a regular class,
13865 // we'll never process it's non-matching methods (because we
13866 // iterate over data.methods). They won't end up in data.methods
13867 // whereas they would if a class with regular class was
13868 // processed first. Detect this condition and manually add such
13869 // methods to data.methods.
13870 if (!data
.hasRegularClass
&& childData
.hasRegularClass
) {
13871 for (auto& [name
, info
] : childData
.methods
) {
13872 if (!info
.regularComplete
|| info
.privateAncestor
) continue;
13873 if (is_special_method_name(name
)) continue;
13874 if (name
== s_construct
.get()) continue;
13875 if (data
.methods
.count(name
)) continue;
13876 auto& newInfo
= data
.methods
[name
];
13877 newInfo
.regularMeths
= std::move(info
.regularMeths
);
13878 newInfo
.nonRegularPrivateMeths
=
13879 std::move(info
.nonRegularPrivateMeths
);
13880 newInfo
.nonRegularMeths
= std::move(info
.nonRegularMeths
);
13881 newInfo
.allStatic
= std::move(info
.allStatic
);
13882 newInfo
.regularStatic
= std::move(info
.regularStatic
);
13883 newInfo
.complete
= false;
13884 newInfo
.regularComplete
= true;
13885 newInfo
.privateAncestor
= false;
13889 data
.propsWithImplicitNullable
.insert(
13890 begin(childData
.propsWithImplicitNullable
),
13891 end(childData
.propsWithImplicitNullable
)
13894 data
.mockedClasses
.insert(
13895 begin(childData
.mockedClasses
),
13896 end(childData
.mockedClasses
)
13899 // The rest are booleans which can just be unioned together.
13900 data
.hasConstProp
|= childData
.hasConstProp
;
13901 data
.hasReifiedGeneric
|= childData
.hasReifiedGeneric
;
13902 data
.isSubMocked
|= childData
.isSubMocked
;
13903 data
.hasRegularClass
|= childData
.hasRegularClass
;
13904 data
.hasRegularClassFull
|= childData
.hasRegularClassFull
;
13907 // Obtain a Data for the given class/split named "top".
13908 // @param calculatedAcc: an accumulator passed in to track nodes we process
13909 // while processing children recursively
13910 static Data
aggregate_data(LocalIndex
& index
,
13912 TSStringSet
& calculatedAcc
) {
13913 assertx(index
.top
.contains(top
));
13915 auto const& children
= [&]() -> const std::vector
<SString
>& {
13916 auto const it
= index
.children
.find(top
);
13917 always_assert(it
!= end(index
.children
));
13918 assertx(!it
->second
.empty());
13922 auto const it
= index
.aggregateData
.find(top
);
13923 if (it
!= end(index
.aggregateData
)) {
13924 for (auto const child
: children
) calculatedAcc
.emplace(child
);
13930 // Set of children calculated for current top to ensure we don't
13932 TSStringSet calculatedForTop
;
13934 // For each child of the class/split (for classes this includes
13935 // the top class itself), we create a Data, then union it together
13937 size_t childIdx
= 0;
13938 while (calculatedForTop
.size() < children
.size()) {
13939 auto child
= children
[childIdx
++];
13940 if (calculatedForTop
.contains(child
)) continue;
13941 // Top Splits have no associated data yet.
13942 if (index
.top
.count(child
) && index
.splits
.count(child
)) {
13943 calculatedForTop
.emplace(child
);
13947 auto childData
= [&]() {
13948 if (index
.top
.contains(child
) && !child
->tsame(top
)) {
13949 return aggregate_data(index
, child
, calculatedForTop
);
13951 calculatedForTop
.emplace(child
);
13952 return build_data(index
, child
);
13956 // The first Data has nothing to union with, so just use it as is.
13958 data
= std::move(childData
);
13962 update_data(data
, std::move(childData
));
13965 for (auto const cls
: calculatedForTop
) calculatedAcc
.emplace(cls
);
13966 always_assert(index
.aggregateData
.emplace(top
, data
).second
);
13970 // Obtain a Data for the given class/split named "top".
13971 static Data
aggregate_data(LocalIndex
& index
, SString top
) {
13972 TSStringSet calculated
;
13973 return aggregate_data(index
, top
, calculated
);
13976 // Create (or re-use an existing) FuncFamily for the given MethInfo.
13977 static FuncFamily2::Id
make_func_family(
13980 Data::MethInfo info
13982 // We should have more than one method because otherwise we
13983 // shouldn't be trying to create a FuncFamily for it.
13985 info
.regularMeths
.size() +
13986 info
.nonRegularPrivateMeths
.size() +
13987 info
.nonRegularMeths
.size() > 1
13990 // Before doing the expensive sorting and hashing, see if this
13991 // FuncFamily already exists. If so, just return the id.
13992 if (auto const id
= folly::get_ptr(
13993 index
.funcFamilyCache
,
13994 MethInfoTupleProxy
{
13995 &info
.regularMeths
,
13996 &info
.nonRegularPrivateMeths
,
13997 &info
.nonRegularMeths
14003 // Nothing in the cache. We need to do the expensive step of
14004 // actually creating the FuncFamily.
14006 // First sort the methods so they're in deterministic order.
14007 std::vector
<MethRef
> regular
{
14008 begin(info
.regularMeths
), end(info
.regularMeths
)
14010 std::vector
<MethRef
> nonRegularPrivate
{
14011 begin(info
.nonRegularPrivateMeths
), end(info
.nonRegularPrivateMeths
)
14013 std::vector
<MethRef
> nonRegular
{
14014 begin(info
.nonRegularMeths
), end(info
.nonRegularMeths
)
14016 std::sort(begin(regular
), end(regular
));
14017 std::sort(begin(nonRegularPrivate
), end(nonRegularPrivate
));
14018 std::sort(begin(nonRegular
), end(nonRegular
));
14020 // Create the id by hashing the methods:
14023 auto const size1
= regular
.size();
14024 auto const size2
= nonRegularPrivate
.size();
14025 auto const size3
= nonRegular
.size();
14026 hasher
.update((const char*)&size1
, sizeof(size1
));
14027 hasher
.update((const char*)&size2
, sizeof(size2
));
14028 hasher
.update((const char*)&size3
, sizeof(size3
));
14030 for (auto const& m
: regular
) {
14031 hasher
.update(m
.cls
->data(), m
.cls
->size());
14032 hasher
.update((const char*)&m
.idx
, sizeof(m
.idx
));
14034 for (auto const& m
: nonRegularPrivate
) {
14035 hasher
.update(m
.cls
->data(), m
.cls
->size());
14036 hasher
.update((const char*)&m
.idx
, sizeof(m
.idx
));
14038 for (auto const& m
: nonRegular
) {
14039 hasher
.update(m
.cls
->data(), m
.cls
->size());
14040 hasher
.update((const char*)&m
.idx
, sizeof(m
.idx
));
14042 auto const id
= hasher
.finish();
14044 // See if this id exists already. If so, record it in the cache
14046 if (index
.funcFamilies
.count(id
)) {
14047 index
.funcFamilyCache
.emplace(
14049 std::move(info
.regularMeths
),
14050 std::move(info
.nonRegularPrivateMeths
),
14051 std::move(info
.nonRegularMeths
)
14058 // It's a new id. Create the actual FuncFamily:
14060 regular
.shrink_to_fit();
14061 nonRegularPrivate
.shrink_to_fit();
14062 nonRegular
.shrink_to_fit();
14064 auto ff
= std::make_unique
<FuncFamily2
>();
14067 ff
->m_regular
= std::move(regular
);
14068 ff
->m_nonRegularPrivate
= std::move(nonRegularPrivate
);
14069 ff
->m_nonRegular
= std::move(nonRegular
);
14070 ff
->m_allStatic
= std::move(info
.allStatic
);
14071 ff
->m_regularStatic
= std::move(info
.regularStatic
);
14074 index
.funcFamilies
.emplace(id
, std::move(ff
)).second
14076 index
.newFuncFamilies
.emplace_back(id
);
14077 index
.funcFamilyCache
.emplace(
14079 std::move(info
.regularMeths
),
14080 std::move(info
.nonRegularPrivateMeths
),
14081 std::move(info
.nonRegularMeths
)
14089 // Turn a FuncFamily::StaticInfo into an equivalent
14090 // FuncFamilyEntry::MethMetadata. The StaticInfo must be valid for a
14092 static FuncFamilyEntry::MethMetadata
single_meth_meta_from_static_info(
14093 const FuncFamily2::StaticInfo
& info
14095 assertx(info
.m_numInOut
);
14096 assertx(info
.m_requiredCoeffects
);
14097 assertx(info
.m_coeffectRules
);
14098 assertx(info
.m_minNonVariadicParams
== info
.m_maxNonVariadicParams
);
14099 assertx(info
.m_isReadonlyReturn
!= TriBool::Maybe
);
14100 assertx(info
.m_isReadonlyThis
!= TriBool::Maybe
);
14101 assertx(info
.m_supportsAER
!= TriBool::Maybe
);
14103 FuncFamilyEntry::MethMetadata meta
;
14104 meta
.m_prepKinds
= info
.m_paramPreps
;
14105 meta
.m_coeffectRules
= *info
.m_coeffectRules
;
14106 meta
.m_numInOut
= *info
.m_numInOut
;
14107 meta
.m_requiredCoeffects
= *info
.m_requiredCoeffects
;
14108 meta
.m_nonVariadicParams
= info
.m_minNonVariadicParams
;
14109 meta
.m_isReadonlyReturn
= info
.m_isReadonlyReturn
== TriBool::Yes
;
14110 meta
.m_isReadonlyThis
= info
.m_isReadonlyThis
== TriBool::Yes
;
14111 meta
.m_supportsAER
= info
.m_supportsAER
== TriBool::Yes
;
14112 meta
.m_isReified
= info
.m_maybeReified
;
14113 meta
.m_caresAboutDyncalls
= info
.m_maybeCaresAboutDynCalls
;
14114 meta
.m_builtin
= info
.m_maybeBuiltin
;
14118 // Translate a MethInfo into the appropriate FuncFamilyEntry
14119 static FuncFamilyEntry
make_method_family_entry(
14122 Data::MethInfo info
14124 FuncFamilyEntry entry
;
14125 entry
.m_allIncomplete
= !info
.complete
;
14126 entry
.m_regularIncomplete
= !info
.regularComplete
;
14127 entry
.m_privateAncestor
= info
.privateAncestor
;
14129 if (info
.regularMeths
.size() + info
.nonRegularPrivateMeths
.size() > 1) {
14130 // There's either multiple regularMeths, multiple
14131 // nonRegularPrivateMeths, or one of each (remember they are
14132 // disjoint). In either case, there's more than one method, so
14133 // we need a func family.
14134 assertx(info
.allStatic
);
14135 assertx(info
.regularStatic
);
14136 auto const ff
= make_func_family(index
, name
, std::move(info
));
14137 entry
.m_meths
= FuncFamilyEntry::BothFF
{ff
};
14138 } else if (!info
.regularMeths
.empty() ||
14139 !info
.nonRegularPrivateMeths
.empty()) {
14140 // We know their sum isn't greater than one, so only one of them
14141 // can be non-empty (and the one that is has only a single
14143 assertx(info
.allStatic
);
14144 assertx(info
.regularStatic
);
14145 auto const r
= !info
.regularMeths
.empty()
14146 ? *begin(info
.regularMeths
)
14147 : *begin(info
.nonRegularPrivateMeths
);
14148 if (info
.nonRegularMeths
.empty()) {
14149 // There's only one method and it covers both variants.
14150 entry
.m_meths
= FuncFamilyEntry::BothSingle
{
14152 single_meth_meta_from_static_info(*info
.allStatic
),
14153 info
.regularMeths
.empty()
14156 // nonRegularMeths is non-empty. Since the MethRefSets are
14157 // disjoint, overall there's more than one method so need a
14159 auto const nonRegularPrivate
= info
.regularMeths
.empty();
14160 auto const ff
= make_func_family(index
, name
, std::move(info
));
14161 entry
.m_meths
= FuncFamilyEntry::FFAndSingle
{ff
, r
, nonRegularPrivate
};
14163 } else if (info
.nonRegularMeths
.size() > 1) {
14164 // Both regularMeths and nonRegularPrivateMeths is empty. If
14165 // there's multiple nonRegularMeths, we need a func family for
14166 // the non-regular variant, but the regular variant is empty.
14167 assertx(info
.allStatic
);
14168 assertx(!info
.regularStatic
);
14169 auto const ff
= make_func_family(index
, name
, std::move(info
));
14170 entry
.m_meths
= FuncFamilyEntry::FFAndNone
{ff
};
14171 } else if (!info
.nonRegularMeths
.empty()) {
14172 // There's exactly one nonRegularMeths method (and nothing for
14173 // the regular variant).
14174 assertx(info
.allStatic
);
14175 assertx(!info
.regularStatic
);
14176 entry
.m_meths
= FuncFamilyEntry::SingleAndNone
{
14177 *begin(info
.nonRegularMeths
),
14178 single_meth_meta_from_static_info(*info
.allStatic
)
14181 // No methods at all
14182 assertx(!info
.complete
);
14183 assertx(!info
.allStatic
);
14184 assertx(!info
.regularStatic
);
14185 entry
.m_meths
= FuncFamilyEntry::None
{};
14191 // Calculate the data for each root (those which will we'll provide
14192 // outputs for) and update the ClassInfo or Split as appropriate.
14193 static void process_roots(
14195 const std::vector
<std::unique_ptr
<ClassInfo2
>>& roots
,
14196 const std::vector
<std::unique_ptr
<Split
>>& splits
14198 for (auto const& cinfo
: roots
) {
14199 assertx(index
.top
.count(cinfo
->name
));
14200 // Process the children of this class and build a unified Data
14202 auto data
= aggregate_data(index
, cinfo
->name
);
14204 // These are just copied directly from Data.
14205 cinfo
->subHasConstProp
= data
.hasConstProp
;
14206 cinfo
->subHasReifiedGeneric
= data
.hasReifiedGeneric
;
14208 auto& cls
= index
.cls(cinfo
->name
);
14210 // This class is mocked if its on the mocked classes list.
14211 cinfo
->isMocked
= (bool)data
.mockedClasses
.count(cinfo
->name
);
14212 cinfo
->isSubMocked
= data
.isSubMocked
|| cinfo
->isMocked
;
14213 attribute_setter(cls
.attrs
, !cinfo
->isSubMocked
, AttrNoMock
);
14215 // We can use whether we saw regular/non-regular subclasses to
14216 // infer if this class is overridden.
14217 if (cinfo
->classGraph
.mightHaveRegularSubclass()) {
14218 attribute_setter(cls
.attrs
, false, AttrNoOverrideRegular
);
14219 attribute_setter(cls
.attrs
, false, AttrNoOverride
);
14220 } else if (cinfo
->classGraph
.mightHaveNonRegularSubclass()) {
14221 attribute_setter(cls
.attrs
, true, AttrNoOverrideRegular
);
14222 attribute_setter(cls
.attrs
, false, AttrNoOverride
);
14224 attribute_setter(cls
.attrs
, true, AttrNoOverrideRegular
);
14225 attribute_setter(cls
.attrs
, true, AttrNoOverride
);
14230 cinfo
->initialNoReifiedInit
,
14231 cls
.attrs
& AttrNoReifiedInit
14238 if (cinfo
->initialNoReifiedInit
) return true;
14239 if (cinfo
->parent
) return false;
14240 if (cls
.attrs
& AttrInterface
) return true;
14241 return !data
.hasReifiedGeneric
;
14246 for (auto& [name
, mte
] : cinfo
->methods
) {
14247 if (is_special_method_name(name
)) continue;
14249 // Since this is the first time we're processing this class,
14250 // all of the methods should be marked as AttrNoOverride.
14251 assertx(mte
.attrs
& AttrNoOverride
);
14252 assertx(mte
.noOverrideRegular());
14254 auto& info
= [&, name
=name
] () -> Data::MethInfo
& {
14255 auto it
= data
.methods
.find(name
);
14256 always_assert(it
!= end(data
.methods
));
14260 auto const meth
= mte
.meth();
14262 // Is this method overridden?
14263 auto const noOverride
= [&] {
14264 // An incomplete method family is always overridden because
14265 // the call could fail.
14266 if (!info
.complete
) return false;
14267 // If more than one method then no.
14268 if (info
.regularMeths
.size() +
14269 info
.nonRegularPrivateMeths
.size() +
14270 info
.nonRegularMeths
.size() > 1) {
14273 // NB: All of the below checks all return true. The
14274 // different conditions are just for checking the right
14276 if (info
.regularMeths
.empty()) {
14277 // The (single) method isn't on a regular class. This
14278 // class shouldn't have any regular classes (the set is
14279 // complete so if we did, the method would have been on
14280 // it). The (single) method must be on nonRegularMeths or
14281 // nonRegularPrivateMeths.
14282 assertx(!cinfo
->isRegularClass
);
14283 if (info
.nonRegularPrivateMeths
.empty()) {
14284 assertx(info
.nonRegularMeths
.count(meth
));
14287 assertx(info
.nonRegularMeths
.empty());
14288 assertx(info
.nonRegularPrivateMeths
.count(meth
));
14291 assertx(info
.nonRegularPrivateMeths
.empty());
14292 assertx(info
.nonRegularMeths
.empty());
14293 assertx(info
.regularMeths
.count(meth
));
14297 // Is this method overridden in a regular class? (weaker
14299 auto const noOverrideRegular
= [&] {
14300 // An incomplete method family is always overridden because
14301 // the call could fail.
14302 if (!info
.regularComplete
) return false;
14303 // If more than one method then no. For the purposes of this
14304 // check, non-regular but private methods are included.
14305 if (info
.regularMeths
.size() +
14306 info
.nonRegularPrivateMeths
.size() > 1) {
14309 if (info
.regularMeths
.empty()) {
14310 // The method isn't on a regular class. Like in
14311 // noOverride(), the class shouldn't have any regular
14312 // classes. If nonRegularPrivateMethos is empty, this
14313 // means any possible override is non-regular, so we're
14315 assertx(!cinfo
->isRegularClass
);
14316 if (info
.nonRegularPrivateMeths
.empty()) return true;
14317 return (bool)info
.nonRegularPrivateMeths
.count(meth
);
14319 if (cinfo
->isRegularClass
) {
14320 // If this class is regular, the method on this class
14321 // should be marked as regular.
14322 assertx(info
.regularMeths
.count(meth
));
14325 // We know regularMeths is non-empty, and the size is at
14326 // most one. If this method is the (only) one in
14327 // regularMeths, it's not overridden by anything.
14328 return (bool)info
.regularMeths
.count(meth
);
14331 if (!noOverrideRegular()) {
14332 mte
.clearNoOverrideRegular();
14333 attribute_setter(mte
.attrs
, false, AttrNoOverride
);
14334 } else if (!noOverride()) {
14335 attribute_setter(mte
.attrs
, false, AttrNoOverride
);
14338 auto& entry
= cinfo
->methodFamilies
.at(name
);
14340 boost::get
<FuncFamilyEntry::BothSingle
>(&entry
.m_meths
) ||
14341 boost::get
<FuncFamilyEntry::SingleAndNone
>(&entry
.m_meths
)
14345 if (mte
.attrs
& AttrNoOverride
) {
14346 always_assert(info
.complete
);
14347 always_assert(info
.regularComplete
);
14349 if (cinfo
->isRegularClass
||
14350 cinfo
->classGraph
.mightHaveRegularSubclass()) {
14351 always_assert(info
.regularMeths
.size() == 1);
14352 always_assert(info
.regularMeths
.count(meth
));
14353 always_assert(info
.nonRegularPrivateMeths
.empty());
14354 always_assert(info
.nonRegularMeths
.empty());
14356 // If this class isn't regular, it could still have a
14357 // regular method which it inherited from a (regular)
14358 // parent. There should only be one method across all the
14361 info
.regularMeths
.size() +
14362 info
.nonRegularPrivateMeths
.size() +
14363 info
.nonRegularMeths
.size() == 1
14366 info
.regularMeths
.count(meth
) ||
14367 info
.nonRegularPrivateMeths
.count(meth
) ||
14368 info
.nonRegularMeths
.count(meth
)
14372 if (mte
.hasPrivateAncestor() &&
14373 (cinfo
->isRegularClass
||
14374 cinfo
->classGraph
.mightHaveRegularSubclass())) {
14375 always_assert(info
.privateAncestor
);
14377 always_assert(!info
.privateAncestor
);
14380 always_assert(!(cls
.attrs
& AttrNoOverride
));
14384 // NB: Even if the method is AttrNoOverride, we might need to
14385 // change the FuncFamilyEntry. This class could be non-regular
14386 // and a child class could be regular. Even if the child class
14387 // doesn't override the method, it changes it from non-regular
14389 entry
= make_method_family_entry(index
, name
, std::move(info
));
14391 if (mte
.attrs
& AttrNoOverride
) {
14392 // However, even if the entry changes with AttrNoOverride,
14393 // it can only be these two cases.
14395 boost::get
<FuncFamilyEntry::BothSingle
>(&entry
.m_meths
) ||
14396 boost::get
<FuncFamilyEntry::SingleAndNone
>(&entry
.m_meths
)
14402 * Interfaces can cause monotonicity violations. Suppose we have two
14403 * interfaces: I2 and I2. I1 declares a method named Foo. Every
14404 * class which implements I2 also implements I1 (therefore I2
14405 * implies I1). During analysis, a type is initially Obj<=I1 and we
14406 * resolve a call to Foo using I1's func families. After further
14407 * optimization, we narrow the type to Obj<=I2. Now when we go to
14408 * resolve a call to Foo using I2's func families, we find
14409 * nothing. Foo is declared in I1, not in I2, and interface methods
14410 * are not inherited. We use the fall back name-only tables, which
14411 * might give us a worse type than before. This is a monotonicity
14412 * violation because refining the object type gave us worse
14415 * To avoid this, we expand an interface's (and abstract class'
14416 * which has similar issues) func families to include all methods
14417 * defined by *all* of it's (regular) implementations. So, in the
14418 * example above, we'd expand I2's func families to include Foo,
14419 * since all of I2's implements should define a Foo method (since
14420 * they also all implement I1).
14422 * Any MethInfos which are part of the abstract class/interface
14423 * method table has already been processed above. Any ones which
14424 * haven't are candidates for the above expansion and must also
14425 * be placed in the method families table. Note: we do not just
14426 * restrict this to just abstract classes or interfaces. This
14427 * class may be a child of an abstract class or interfaces and
14428 * we need to propagate these "expanded" methods so they're
14429 * available in the dependency when we actually process the
14430 * abstract class/interface in a later round.
14432 for (auto& [name
, info
] : data
.methods
) {
14433 if (cinfo
->methods
.count(name
)) continue;
14434 assertx(!is_special_method_name(name
));
14435 auto entry
= make_method_family_entry(index
, name
, std::move(info
));
14437 cinfo
->methodFamilies
.emplace(name
, std::move(entry
)).second
14441 for (auto& prop
: cls
.properties
) {
14442 if (bool(prop
.attrs
& AttrNoImplicitNullable
) &&
14443 !(prop
.attrs
& (AttrStatic
| AttrPrivate
))) {
14446 !data
.propsWithImplicitNullable
.count(prop
.name
),
14447 AttrNoImplicitNullable
14451 if (!(prop
.attrs
& AttrSystemInitialValue
)) continue;
14452 if (prop
.val
.m_type
== KindOfUninit
) {
14453 assertx(prop
.attrs
& AttrLateInit
);
14458 if (!(prop
.attrs
& AttrNoImplicitNullable
)) {
14459 return make_tv
<KindOfNull
>();
14461 // Give the 86reified_prop a special default value to
14462 // avoid pessimizing the inferred type (we want it to
14463 // always be a vec of a specific size).
14464 if (prop
.name
== s_86reified_prop
.get()) {
14465 return get_default_value_of_reified_list(cls
.userAttributes
);
14467 return prop
.typeConstraint
.defaultValue();
14472 // Splits just store the data directly. Since this split hasn't
14473 // been processed yet (and no other job should process it), all of
14474 // the fields should be their default settings.
14475 for (auto& split
: splits
) {
14476 assertx(index
.top
.count(split
->name
));
14477 split
->data
= aggregate_data(index
, split
->name
);
14478 // This split inherits all of the splits of their children.
14479 for (auto const child
: split
->children
) {
14480 if (auto const c
= folly::get_default(index
.classInfos
, child
)) {
14481 split
->classGraphs
.emplace_back(c
->classGraph
);
14484 auto const s
= folly::get_default(index
.splits
, child
);
14486 split
->classGraphs
.insert(
14487 end(split
->classGraphs
),
14488 begin(s
->classGraphs
),
14489 end(s
->classGraphs
)
14492 std::sort(begin(split
->classGraphs
), end(split
->classGraphs
));
14493 split
->classGraphs
.erase(
14494 std::unique(begin(split
->classGraphs
), end(split
->classGraphs
)),
14495 end(split
->classGraphs
)
14497 split
->children
.clear();
14502 Job
<BuildSubclassListJob
> s_buildSubclassJob
;
14504 struct SubclassWork
{
14505 TSStringToOneT
<std::unique_ptr
<BuildSubclassListJob::Split
>> allSplits
;
14507 std::vector
<SString
> classes
;
14508 std::vector
<SString
> deps
;
14509 std::vector
<SString
> splits
;
14510 std::vector
<SString
> splitDeps
;
14511 std::vector
<SString
> leafs
;
14512 std::vector
<BuildSubclassListJob::EdgeToSplit
> edges
;
14515 std::vector
<std::vector
<Bucket
>> buckets
;
14519 * Algorithm for assigning work for building subclass lists:
14521 * - Keep track of which classes have been processed and which ones
14522 * have not yet been.
14524 * - Keep looping until all classes have been processed. Each round of
14525 * the algorithm becomes a round of output.
14527 * - Iterate over all classes which haven't been
14528 * processed. Distinguish classes which are eligible for processing
14529 * or not. A class is eligible for processing if its transitive
14530 * dependencies are below the maximum size.
14532 * - Non-eligible classes are ignored and will be processed again next
14533 * round. However, if the class has more eligible direct children
14534 * than the bucket size, the class' children will be turned into
14537 * - Create split nodes. For each class (who we're splitting), use the
14538 * typical consistent hashing algorithm to assign each child to a
14539 * split node. Change the class' child list to contain the split
14540 * nodes instead of the children (this should shrink it
14541 * considerably). Each new split becomes a root.
14543 * - Assign each eligible class to a bucket. Use
14544 * assign_hierachial_work to map each eligible class to a bucket.
14546 * - Update the processed set. Any class which hasn't been processed
14547 * that round should have their dependency set shrunken. Processing
14548 * a class makes its dependency set be empty. So if a class wasn't
14549 * eligible, it should have a dependency which was. Therefore the
14550 * class' transitive dependencies should shrink. It should continue
14551 * to shrink until its eventually becomes eligible. The same happens
14552 * if the class' children are turned into split nodes. Each N
14553 * children is replaced with a single split (with no other
14554 * dependencies), so the class' dependencies should shrink. Thus,
14555 * the algorithm eventually terminates.
14558 // Dependency information for a class or split node.
14560 // Transitive dependencies (children) for this class.
14562 // Any split nodes which are dependencies of this class.
14564 // The number of direct children of this class which will be
14565 // processed this round.
14566 size_t processChildren
{0};
14570 // Given a set of roots, greedily add roots and their children to buckets
14571 // via DFS traversal.
14572 template <typename GetDeps
>
14573 std::vector
<HierarchicalWorkBucket
>
14574 dfs_bucketize(SubclassMetadata
& subclassMeta
,
14575 std::vector
<SString
> roots
,
14576 const TSStringToOneT
<std::vector
<SString
>>& splitImmDeps
,
14577 size_t kMaxBucketSize
,
14578 size_t maxClassIdx
,
14579 bool alwaysCreateNew
,
14580 const TSStringSet
& leafs
,
14581 const TSStringSet
& processed
, // already processed
14582 const GetDeps
& getDeps
) {
14583 TSStringSet visited
;
14584 std::vector
<std::vector
<SString
>> rootsToProcess
;
14585 rootsToProcess
.emplace_back();
14586 std::vector
<size_t> rootsCost
;
14588 auto const depsSize
= [&] (SString cls
) {
14589 return getDeps(cls
, getDeps
).deps
.size();
14594 auto const finishBucket
= [&]() {
14596 rootsToProcess
.emplace_back();
14597 rootsCost
.emplace_back(cost
);
14601 auto const addRoot
= [&](SString c
) {
14602 rootsToProcess
.back().emplace_back(c
);
14603 cost
+= depsSize(c
);
14606 auto const processSubgraph
= [&](SString cls
) {
14607 assertx(!processed
.count(cls
));
14610 for (auto const& child
: getDeps(cls
, getDeps
).deps
) {
14611 if (processed
.count(child
)) continue;
14612 if (visited
.count(child
)) continue;
14613 visited
.insert(child
);
14614 // Leaves use special leaf-promotion logic in assign_hierarchial_work
14615 if (leafs
.count(child
)) continue;
14618 if (cost
< kMaxBucketSize
) return;
14622 // Visit immediate children. Recurse until you find a node that has small
14623 // enough transitive deps.
14624 auto const visitSubgraph
= [&](SString root
, auto const& self
) {
14625 if (processed
.count(root
) || visited
.count(root
)) return false;
14626 if (!depsSize(root
)) return false;
14627 auto progress
= false;
14628 visited
.insert(root
);
14630 assertx(IMPLIES(splitImmDeps
.count(root
), depsSize(root
) <= kMaxBucketSize
));
14631 if (depsSize(root
) <= kMaxBucketSize
) {
14632 processSubgraph(root
);
14635 auto const immChildren
= [&] {
14636 auto const it
= subclassMeta
.meta
.find(root
);
14637 assertx(it
!= end(subclassMeta
.meta
));
14638 return it
->second
.children
;
14640 for (auto const& child
: immChildren
) progress
|= self(child
, self
);
14645 // Sort the roots to keep it deterministic
14647 begin(roots
), end(roots
),
14648 [&] (SString a
, SString b
) {
14649 auto const s1
= getDeps(a
, getDeps
).deps
.size();
14650 auto const s2
= getDeps(b
, getDeps
).deps
.size();
14651 if (s1
!= s2
) return s1
> s2
;
14652 return string_data_lt_type
{}(a
, b
);
14656 auto progress
= false;
14657 for (auto const r
: roots
) {
14658 assertx(depsSize(r
)); // Should never be processing one leaf
14659 progress
|= visitSubgraph(r
, visitSubgraph
);
14664 if (rootsToProcess
.back().empty()) rootsToProcess
.pop_back();
14666 auto const buckets
= parallel::gen(
14667 rootsToProcess
.size(),
14668 [&] (size_t bucketIdx
) {
14670 (rootsCost
[bucketIdx
] + (kMaxBucketSize
/2)) / kMaxBucketSize
;
14671 if (!numBuckets
) numBuckets
= 1;
14672 return consistently_bucketize_by_num_buckets(rootsToProcess
[bucketIdx
],
14673 alwaysCreateNew
? rootsToProcess
[bucketIdx
].size() : numBuckets
);
14677 std::vector
<std::vector
<SString
>> flattened
;
14678 for (auto const& b
: buckets
) {
14679 flattened
.insert(flattened
.end(), b
.begin(), b
.end());
14682 auto const work
= build_hierarchical_work(
14686 auto const& deps
= getDeps(c
, getDeps
).deps
;
14687 return std::make_pair(&deps
, true);
14689 [&] (const TSStringSet
&, size_t, SString c
) -> Optional
<size_t> {
14690 if (!leafs
.count(c
)) return std::nullopt
;
14691 return subclassMeta
.meta
.at(c
).idx
;
14698 // While toProcess is not empty:
14699 // 1. Find transitive dep counts
14700 // 2. For each class, calculate splits, find roots, find rootLeafs
14701 // 3. For rootLeafs, consistently hash to make buckets
14702 // 4. For roots, assign subgraphs to buckets via greedy DFS. If buckets get too big,
14703 // split them via consistent hashing.
14704 SubclassWork
build_subclass_lists_assign(SubclassMetadata subclassMeta
) {
14705 trace_time trace
{"build subclass lists assign"};
14706 trace
.ignore_client_stats();
14708 constexpr size_t kBucketSize
= 2000;
14709 constexpr size_t kMaxBucketSize
= 25000;
14713 auto const maxClassIdx
= subclassMeta
.all
.size();
14715 // A processed class/split is considered processed once it's
14716 // assigned to a bucket in a round. Once considered processed, it
14717 // will have no dependencies.
14718 TSStringSet processed
;
14720 TSStringToOneT
<std::unique_ptr
<DepData
>> splitDeps
;
14721 TSStringToOneT
<std::unique_ptr
<BuildSubclassListJob::Split
>> splitPtrs
;
14723 TSStringToOneT
<std::vector
<SString
>> splitImmDeps
;
14725 // Keep creating rounds until all of the classes are assigned to a
14726 // bucket in a round.
14727 auto toProcess
= std::move(subclassMeta
.all
);
14729 if (debug
) tp
.insert(toProcess
.begin(), toProcess
.end());
14731 for (size_t round
= 0; !toProcess
.empty(); ++round
) {
14732 // If we have this many rounds, something has gone wrong, because
14733 // it should require an astronomical amount of classes.
14734 always_assert_flog(
14736 "Worklist still has {} items after {} rounds. "
14737 "This almost certainly means it's stuck in an infinite loop",
14742 // The dependency information for every class, for just this
14743 // round. The information is calculated lazily and recursively by
14745 std::vector
<LockFreeLazy
<DepData
>> deps
{maxClassIdx
};
14747 auto const findDeps
= [&] (SString cls
,
14748 auto const& self
) -> const DepData
& {
14749 // If it's processed, there's implicitly no dependencies
14750 static DepData empty
;
14751 if (processed
.count(cls
)) return empty
;
14753 // Look up the metadata for this class. If we don't find any,
14754 // assume that it's for a split.
14755 auto const it
= subclassMeta
.meta
.find(cls
);
14756 if (it
== end(subclassMeta
.meta
)) {
14757 auto const it2
= splitDeps
.find(cls
);
14758 always_assert(it2
!= end(splitDeps
));
14759 return *it2
->second
;
14761 auto const& meta
= it
->second
;
14762 auto const idx
= meta
.idx
;
14763 assertx(idx
< deps
.size());
14765 // Now that we have the index into the dependency vector, look
14766 // it up, calculating it if it hasn't been already.
14767 return deps
[idx
].get(
14770 for (auto const c
: meta
.children
) {
14771 // At a minimum, we need the immediate deps in order to
14772 // construct the subclass lists for the parent.
14773 out
.deps
.emplace(c
);
14774 if (splitDeps
.count(c
)) out
.edges
.emplace(c
);
14775 auto const& childDeps
= self(c
, self
);
14776 if (childDeps
.deps
.size() <= kMaxBucketSize
) ++out
.processChildren
;
14777 out
.deps
.insert(begin(childDeps
.deps
), end(childDeps
.deps
));
14784 auto const depsSize
= [&] (SString cls
) {
14785 return findDeps(cls
, findDeps
).deps
.size();
14787 // If this class' children needs to be split into split nodes this
14788 // round. This happens if the number of direct children of this
14789 // class which are eligible for processing exceeds the bucket
14791 auto const willSplitChildren
= [&] (SString cls
) {
14792 return findDeps(cls
, findDeps
).processChildren
> kBucketSize
;
14794 // If this class will be processed this round. A class will be
14795 // processed if it's dependencies are less than the maximum bucket
14797 auto const willProcess
= [&] (SString cls
) {
14798 // NB: Not <=. When calculating splits, a class is included
14799 // among it's own dependencies so we need to leave space for one
14801 return depsSize(cls
) < kMaxBucketSize
;
14804 // Process every remaining class in parallel and assign an action
14807 // This class will be processed this round and is a root.
14808 struct Root
{ SString cls
; };
14809 struct RootLeaf
{ SString cls
; };
14810 struct Child
{ SString cls
; };
14811 // This class' children should be split. The class' child list
14812 // will be replaced with the new child list and splits created.
14815 std::vector
<SString
> children
;
14818 std::unique_ptr
<DepData
> deps
;
14819 std::unique_ptr
<BuildSubclassListJob::Split
> ptr
;
14820 std::vector
<SString
> children
;
14822 std::vector
<Data
> splits
;
14824 using Action
= boost::variant
<Root
, Split
, Child
, RootLeaf
>;
14826 auto const actions
= parallel::map(
14828 [&] (SString cls
) {
14829 auto const& meta
= subclassMeta
.meta
.at(cls
);
14831 if (!willSplitChildren(cls
)) {
14832 if (!meta
.parents
.empty()) return Action
{ Child
{cls
} };
14833 if (meta
.children
.empty()) return Action
{ RootLeaf
{cls
} };
14834 return Action
{ Root
{cls
} };
14837 // Otherwise we're going to split some/all of this class'
14838 // children. Once we process those in this round, this class'
14839 // dependencies should be smaller and be able to be processed.
14842 split
.splits
= [&] {
14843 // Group all of the eligible children into buckets, and
14844 // split the buckets to ensure they remain below the maximum
14846 auto const buckets
= split_buckets(
14848 auto const numChildren
= findDeps(cls
, findDeps
).processChildren
;
14849 auto const numBuckets
=
14850 (numChildren
+ kMaxBucketSize
- 1) / kMaxBucketSize
;
14851 assertx(numBuckets
> 0);
14853 std::vector
<std::vector
<SString
>> buckets
;
14854 buckets
.resize(numBuckets
);
14855 for (auto const child
: meta
.children
) {
14856 if (!willProcess(child
)) continue;
14858 consistent_hash(child
->hashStatic(), numBuckets
);
14859 assertx(idx
< numBuckets
);
14860 buckets
[idx
].emplace_back(child
);
14867 [] (const std::vector
<SString
>& b
) { return b
.empty(); }
14872 assertx(!buckets
.empty());
14876 [&] (SString child
) -> const TSStringSet
& {
14877 return findDeps(child
, findDeps
).deps
;
14880 // Each bucket corresponds to a new split node, which will
14881 // contain the results for the children in that bucket.
14882 auto const numSplits
= buckets
.size();
14884 // Actually make the splits and fill their children list.
14885 std::vector
<Split::Data
> splits
;
14886 splits
.reserve(numSplits
);
14887 for (size_t i
= 0; i
< numSplits
; ++i
) {
14888 // The names of a split node are arbitrary, but must be
14889 // unique and not collide with any actual classes.
14890 auto const name
= makeStaticString(
14891 folly::sformat("{}_{}_split;{}", round
, i
, cls
)
14894 auto deps
= std::make_unique
<DepData
>();
14896 std::make_unique
<BuildSubclassListJob::Split
>(name
, cls
);
14897 std::vector
<SString
> children
;
14899 for (auto const child
: buckets
[i
]) {
14900 split
->children
.emplace_back(child
);
14901 children
.emplace_back(child
);
14902 auto const& childDeps
= findDeps(child
, findDeps
).deps
;
14903 deps
->deps
.insert(begin(childDeps
), end(childDeps
));
14904 deps
->deps
.emplace(child
);
14906 assertx(deps
->deps
.size() <= kMaxBucketSize
);
14909 begin(split
->children
),
14910 end(split
->children
),
14911 string_data_lt_type
{}
14914 splits
.emplace_back(
14919 std::move(children
)
14926 // Create the new children list for this class. The new
14927 // children list are any children which won't be processed,
14928 // and the new splits.
14929 for (auto const child
: meta
.children
) {
14930 if (willProcess(child
)) continue;
14931 split
.children
.emplace_back(child
);
14933 for (auto const& [name
, _
, _2
, _3
] : split
.splits
) {
14934 split
.children
.emplace_back(name
);
14937 return Action
{ std::move(split
) };
14941 assertx(actions
.size() == toProcess
.size());
14942 std::vector
<SString
> roots
;
14943 roots
.reserve(actions
.size());
14944 std::vector
<SString
> rootLeafs
;
14946 for (auto const& action
: actions
) {
14950 assertx(!subclassMeta
.meta
.at(r
.cls
).children
.empty());
14951 roots
.emplace_back(r
.cls
);
14954 assertx(subclassMeta
.meta
.at(r
.cls
).children
.empty());
14955 rootLeafs
.emplace_back(r
.cls
);
14956 leafs
.emplace(r
.cls
);
14959 auto const& meta
= subclassMeta
.meta
.at(n
.cls
);
14960 if (meta
.children
.empty()) leafs
.emplace(n
.cls
);
14962 [&] (const Split
& s
) {
14963 auto& meta
= subclassMeta
.meta
.at(s
.cls
);
14964 meta
.children
= s
.children
;
14965 if (meta
.parents
.empty()) {
14966 roots
.emplace_back(s
.cls
);
14968 auto& splits
= const_cast<std::vector
<Split::Data
>&>(s
.splits
);
14969 for (auto& [name
, deps
, ptr
, children
] : splits
) {
14970 splitImmDeps
.emplace(name
, children
);
14971 roots
.emplace_back(name
);
14972 splitDeps
.emplace(name
, std::move(deps
));
14973 splitPtrs
.emplace(name
, std::move(ptr
));
14979 auto work
= dfs_bucketize(
14991 // Bucketize root leafs.
14992 // These are cheaper since we will only be calculating
14993 // name-only func family entries.
14994 for (auto& b
: consistently_bucketize(rootLeafs
, kMaxBucketSize
)) {
14995 work
.emplace_back(HierarchicalWorkBucket
{ std::move(b
) });
14998 std::vector
<SString
> markProcessed
;
14999 markProcessed
.reserve(actions
.size());
15001 // The output of assign_hierarchical_work is just buckets with the
15002 // names. We need to map those to classes or edge nodes and put
15003 // them in the correct data structure in the output. If there's a
15004 // class dependency on a split node, we also need to record an
15005 // edge between them.
15006 auto const add
= [&] (SString cls
, auto& clsList
,
15007 auto& splitList
, auto& edgeList
) {
15008 auto const it
= splitPtrs
.find(cls
);
15009 if (it
== end(splitPtrs
)) {
15010 clsList
.emplace_back(cls
);
15011 for (auto const s
: findDeps(cls
, findDeps
).edges
) {
15012 edgeList
.emplace_back(BuildSubclassListJob::EdgeToSplit
{cls
, s
});
15015 splitList
.emplace_back(it
->second
->name
);
15019 out
.buckets
.emplace_back();
15020 for (auto const& w
: work
) {
15021 assertx(w
.uninstantiable
.empty());
15022 out
.buckets
.back().emplace_back();
15023 auto& bucket
= out
.buckets
.back().back();
15024 // Separate out any of the "roots" which are actually leafs.
15025 for (auto const cls
: w
.classes
) {
15026 bucket
.cost
+= depsSize(cls
);
15027 markProcessed
.emplace_back(cls
);
15028 if (leafs
.count(cls
)) {
15030 bucket
.leafs
.emplace_back(cls
);
15032 add(cls
, bucket
.classes
, bucket
.splits
, bucket
.edges
);
15035 for (auto const cls
: w
.deps
) {
15036 add(cls
, bucket
.deps
, bucket
.splitDeps
, bucket
.edges
);
15040 begin(bucket
.edges
), end(bucket
.edges
),
15041 [] (const BuildSubclassListJob::EdgeToSplit
& a
,
15042 const BuildSubclassListJob::EdgeToSplit
& b
) {
15043 if (string_data_lt_type
{}(a
.cls
, b
.cls
)) return true;
15044 if (string_data_lt_type
{}(b
.cls
, a
.cls
)) return false;
15045 return string_data_lt_type
{}(a
.split
, b
.split
);
15048 std::sort(begin(bucket
.leafs
), end(bucket
.leafs
), string_data_lt_type
{});
15052 begin(out
.buckets
.back()), end(out
.buckets
.back()),
15053 [] (const SubclassWork::Bucket
& a
,
15054 const SubclassWork::Bucket
& b
) {
15055 return a
.cost
> b
.cost
;
15059 // Update the processed set. We have to defer that until here
15060 // because we'd check it when building the buckets.
15061 processed
.insert(begin(markProcessed
), end(markProcessed
));
15063 auto const before
= toProcess
.size();
15066 begin(toProcess
), end(toProcess
),
15067 [&] (SString c
) { return processed
.count(c
); }
15071 always_assert(toProcess
.size() < before
);
15074 // Keep all split nodes created in the output
15075 for (auto& [name
, p
] : splitPtrs
) out
.allSplits
.emplace(name
, std::move(p
));
15077 // Ensure we create an output for everything exactly once
15079 for (size_t round
= 0; round
< out
.buckets
.size(); ++round
) {
15080 auto const& r
= out
.buckets
[round
];
15081 for (size_t i
= 0; i
< r
.size(); ++i
) {
15082 auto const& bucket
= r
[i
];
15083 for (auto const c
: bucket
.classes
) always_assert(tp
.erase(c
));
15084 for (auto const l
: bucket
.leafs
) always_assert(tp
.erase(l
));
15087 assertx(tp
.empty());
15090 if (Trace::moduleEnabled(Trace::hhbbc_index
, 4)) {
15091 for (size_t round
= 0; round
< out
.buckets
.size(); ++round
) {
15098 auto const& r
= out
.buckets
[round
];
15099 for (size_t i
= 0; i
< r
.size(); ++i
) {
15100 auto const& bucket
= r
[i
];
15101 FTRACE(5, "build subclass lists round #{} work item #{}:\n", round
, i
);
15102 FTRACE(5, " classes ({}):\n", bucket
.classes
.size());
15103 for (auto const DEBUG_ONLY c
: bucket
.classes
) FTRACE(6, " {}\n", c
);
15104 FTRACE(5, " splits ({}):\n", bucket
.splits
.size());
15105 for (auto const DEBUG_ONLY s
: bucket
.splits
) FTRACE(6, " {}\n", s
);
15106 FTRACE(5, " deps ({}):\n", bucket
.deps
.size());
15107 for (auto const DEBUG_ONLY d
: bucket
.deps
) FTRACE(6, " {}\n", d
);
15108 FTRACE(5, " split deps ({}):\n", bucket
.splitDeps
.size());
15109 for (auto const DEBUG_ONLY s
: bucket
.splitDeps
) {
15110 FTRACE(6, " {}\n", s
);
15112 FTRACE(5, " leafs ({}):\n", bucket
.leafs
.size());
15113 for (auto const DEBUG_ONLY c
: bucket
.leafs
) FTRACE(6, " {}\n", c
);
15114 FTRACE(5, " edges ({}):\n", bucket
.edges
.size());
15115 for (DEBUG_ONLY
auto const& e
: bucket
.edges
) {
15116 FTRACE(6, " {} -> {}\n", e
.cls
, e
.split
);
15118 nc
+= bucket
.classes
.size();
15119 ns
+= bucket
.splits
.size();
15120 nd
+= bucket
.deps
.size();
15121 nsd
+= bucket
.splitDeps
.size();
15122 nl
+= bucket
.leafs
.size();
15124 FTRACE(4, "BSL round #{} stats\n"
15131 round
, r
.size(), nc
, ns
, nd
, nsd
, nl
15139 void build_subclass_lists(IndexData
& index
,
15140 SubclassMetadata meta
,
15141 InitTypesMetadata
& initTypesMeta
) {
15142 trace_time tracer
{"build subclass lists", index
.sample
};
15143 tracer
.ignore_client_stats();
15145 using namespace folly::gen
;
15147 // Mapping of splits to their Ref. We only upload a split when we're
15148 // going to run a job which it is part of the output.
15149 TSStringToOneT
<UniquePtrRef
<BuildSubclassListJob::Split
>> splitsToRefs
;
15151 FSStringToOneT
<hphp_fast_set
<FuncFamily2::Id
>> funcFamilyDeps
;
15153 // Use the metadata to assign to rounds and buckets.
15154 auto work
= build_subclass_lists_assign(std::move(meta
));
15156 // We need to defer updates to data structures until after all the
15157 // jobs in a round have completed. Otherwise we could update a ref
15158 // to a class at the same time another thread is reading it.
15161 std::tuple
<SString
, UniquePtrRef
<ClassInfo2
>, UniquePtrRef
<php::Class
>>
15164 std::pair
<SString
, UniquePtrRef
<BuildSubclassListJob::Split
>>
15167 std::pair
<FuncFamily2::Id
, Ref
<FuncFamilyGroup
>>
15170 std::pair
<SString
, hphp_fast_set
<FuncFamily2::Id
>>
15172 std::vector
<std::pair
<SString
, UniquePtrRef
<ClassInfo2
>>> leafs
;
15173 std::vector
<std::pair
<SString
, FuncFamilyEntry
>> nameOnly
;
15174 std::vector
<std::pair
<SString
, SString
>> candidateRegOnlyEquivs
;
15175 TSStringToOneT
<TSStringSet
> cnsBases
;
15178 auto const run
= [&] (SubclassWork::Bucket bucket
, size_t round
)
15179 -> coro::Task
<Updates
> {
15180 co_await
coro::co_reschedule_on_current_executor
;
15182 if (bucket
.classes
.empty() &&
15183 bucket
.splits
.empty() &&
15184 bucket
.leafs
.empty()) {
15185 assertx(bucket
.splitDeps
.empty());
15186 co_return Updates
{};
15189 // We shouldn't get closures or Closure in any of this.
15191 for (auto const c
: bucket
.classes
) {
15192 always_assert(!c
->tsame(s_Closure
.get()));
15193 always_assert(!is_closure_name(c
));
15195 for (auto const c
: bucket
.deps
) {
15196 always_assert(!c
->tsame(s_Closure
.get()));
15197 always_assert(!is_closure_name(c
));
15201 auto classes
= from(bucket
.classes
)
15202 | map([&] (SString c
) { return index
.classInfoRefs
.at(c
); })
15203 | as
<std::vector
>();
15204 auto deps
= from(bucket
.deps
)
15205 | map([&] (SString c
) { return index
.classInfoRefs
.at(c
); })
15206 | as
<std::vector
>();
15207 auto leafs
= from(bucket
.leafs
)
15208 | map([&] (SString c
) { return index
.classInfoRefs
.at(c
); })
15209 | as
<std::vector
>();
15210 auto splits
= from(bucket
.splits
)
15211 | map([&] (SString s
) {
15212 std::unique_ptr
<BuildSubclassListJob::Split
> split
=
15213 std::move(work
.allSplits
.at(s
));
15217 | as
<std::vector
>();
15218 auto splitDeps
= from(bucket
.splitDeps
)
15219 | map([&] (SString s
) { return splitsToRefs
.at(s
); })
15220 | as
<std::vector
>();
15222 (from(bucket
.classes
) + from(bucket
.deps
) + from(bucket
.leafs
))
15223 | map([&] (SString c
) { return index
.classRefs
.at(c
); })
15224 | as
<std::vector
>();
15226 std::vector
<Ref
<FuncFamilyGroup
>> funcFamilies
;
15228 // Provide the func families associated with any dependency
15229 // classes going into this job. We only need to do this after
15230 // the first round because in the first round all dependencies
15231 // are leafs and won't have any func families.
15232 for (auto const c
: bucket
.deps
) {
15233 if (auto const deps
= folly::get_ptr(funcFamilyDeps
, c
)) {
15234 for (auto const& d
: *deps
) {
15235 funcFamilies
.emplace_back(index
.funcFamilyRefs
.at(d
));
15239 // Keep the func families in deterministic order and avoid
15241 std::sort(begin(funcFamilies
), end(funcFamilies
));
15242 funcFamilies
.erase(
15243 std::unique(begin(funcFamilies
), end(funcFamilies
)),
15247 assertx(funcFamilyDeps
.empty());
15248 assertx(index
.funcFamilyRefs
.empty());
15251 // ClassInfos and any dependency splits should already be
15252 // stored. Any splits as output of the job, or edges need to be
15253 // uploaded, however.
15254 auto [splitRefs
, edges
, config
] = co_await
coro::collectAll(
15255 index
.client
->storeMulti(std::move(splits
)),
15256 index
.client
->storeMulti(std::move(bucket
.edges
)),
15257 index
.configRef
->getCopy()
15260 Client::ExecMetadata metadata
{
15261 .job_key
= folly::sformat(
15262 "build subclass list {}",
15264 if (!bucket
.classes
.empty()) return bucket
.classes
[0];
15265 if (!bucket
.splits
.empty()) return bucket
.splits
[0];
15266 assertx(!bucket
.leafs
.empty());
15267 return bucket
.leafs
[0];
15272 auto results
= co_await
15273 index
.client
->exec(
15274 s_buildSubclassJob
,
15278 std::move(classes
),
15281 std::move(splitRefs
),
15282 std::move(splitDeps
),
15283 std::move(phpClasses
),
15285 std::move(funcFamilies
)
15288 std::move(metadata
)
15290 // Every job is a single work-unit, so we should only ever get one
15291 // result for each one.
15292 assertx(results
.size() == 1);
15293 auto& [cinfoRefs
, outSplitRefs
, clsRefs
, ffRefs
, leafRefs
, outMetaRef
]
15295 assertx(cinfoRefs
.size() == bucket
.classes
.size());
15296 assertx(outSplitRefs
.size() == bucket
.splits
.size());
15297 assertx(clsRefs
.size() == bucket
.classes
.size());
15298 assertx(leafRefs
.size() == bucket
.leafs
.size());
15300 auto outMeta
= co_await index
.client
->load(std::move(outMetaRef
));
15301 assertx(outMeta
.newFuncFamilyIds
.size() == ffRefs
.size());
15302 assertx(outMeta
.funcFamilyDeps
.size() == cinfoRefs
.size());
15303 assertx(outMeta
.regOnlyEquivCandidates
.size() == cinfoRefs
.size());
15306 updates
.classes
.reserve(bucket
.classes
.size());
15307 updates
.splits
.reserve(bucket
.splits
.size());
15308 updates
.funcFamilies
.reserve(outMeta
.newFuncFamilyIds
.size());
15309 updates
.funcFamilyDeps
.reserve(outMeta
.funcFamilyDeps
.size());
15310 updates
.nameOnly
.reserve(outMeta
.nameOnly
.size());
15311 updates
.leafs
.reserve(bucket
.leafs
.size());
15313 for (size_t i
= 0, size
= bucket
.classes
.size(); i
< size
; ++i
) {
15314 updates
.classes
.emplace_back(bucket
.classes
[i
], cinfoRefs
[i
], clsRefs
[i
]);
15316 for (size_t i
= 0, size
= bucket
.splits
.size(); i
< size
; ++i
) {
15317 updates
.splits
.emplace_back(bucket
.splits
[i
], outSplitRefs
[i
]);
15319 for (size_t i
= 0, size
= bucket
.leafs
.size(); i
< size
; ++i
) {
15320 updates
.leafs
.emplace_back(bucket
.leafs
[i
], leafRefs
[i
]);
15322 for (size_t i
= 0, size
= outMeta
.newFuncFamilyIds
.size(); i
< size
; ++i
) {
15323 auto const ref
= ffRefs
[i
];
15324 for (auto const& id
: outMeta
.newFuncFamilyIds
[i
]) {
15325 updates
.funcFamilies
.emplace_back(id
, ref
);
15328 for (size_t i
= 0, size
= outMeta
.funcFamilyDeps
.size(); i
< size
; ++i
) {
15329 updates
.funcFamilyDeps
.emplace_back(
15331 std::move(outMeta
.funcFamilyDeps
[i
])
15334 updates
.nameOnly
= std::move(outMeta
.nameOnly
);
15335 for (size_t i
= 0, size
= outMeta
.regOnlyEquivCandidates
.size();
15337 auto const name
= bucket
.classes
[i
];
15338 for (auto const c
: outMeta
.regOnlyEquivCandidates
[i
]) {
15339 updates
.candidateRegOnlyEquivs
.emplace_back(name
, c
);
15342 updates
.cnsBases
= std::move(outMeta
.cnsBases
);
15348 trace_time tracer2
{"build subclass lists work", index
.sample
};
15350 for (size_t roundNum
= 0; roundNum
< work
.buckets
.size(); ++roundNum
) {
15351 auto& round
= work
.buckets
[roundNum
];
15352 // In each round, run all of the work for each bucket
15353 // simultaneously, gathering up updates from each job.
15354 auto const updates
= coro::blockingWait(coro::collectAllRange(
15357 | map([&] (SubclassWork::Bucket
&& b
) {
15358 return run(std::move(b
), roundNum
)
15359 .scheduleOn(index
.executor
->sticky());
15361 | as
<std::vector
>()
15364 // Apply the updates to ClassInfo refs. We can do this
15365 // concurrently because every ClassInfo is already in the map, so
15366 // we can update in place (without mutating the map).
15367 parallel::for_each(
15369 [&] (const Updates
& u
) {
15370 for (auto const& [name
, cinfo
, cls
] : u
.classes
) {
15371 index
.classInfoRefs
.at(name
) = cinfo
;
15372 index
.classRefs
.at(name
) = cls
;
15374 for (auto const& [name
, cinfo
] : u
.leafs
) {
15375 index
.classInfoRefs
.at(name
) = cinfo
;
15380 // However updating splitsToRefs cannot be, because we're mutating
15381 // the map by inserting into it. However there's a relatively
15382 // small number of splits, so this should be fine.
15383 parallel::parallel(
15385 for (auto const& u
: updates
) {
15386 for (auto const& [name
, ref
] : u
.splits
) {
15387 always_assert(splitsToRefs
.emplace(name
, ref
).second
);
15392 for (auto const& u
: updates
) {
15393 for (auto const& [id
, ref
] : u
.funcFamilies
) {
15394 // The same FuncFamily can be grouped into multiple
15395 // different groups. Prefer the group that's smaller and
15396 // if they're the same size, use the one with the lowest
15397 // id to keep determinism.
15398 auto const& [existing
, inserted
] =
15399 index
.funcFamilyRefs
.emplace(id
, ref
);
15400 if (inserted
) continue;
15401 if (existing
->second
.id().m_size
< ref
.id().m_size
) continue;
15402 if (ref
.id().m_size
< existing
->second
.id().m_size
) {
15403 existing
->second
= ref
;
15406 if (existing
->second
.id() <= ref
.id()) continue;
15407 existing
->second
= ref
;
15412 for (auto& u
: updates
) {
15413 for (auto& [name
, ids
] : u
.funcFamilyDeps
) {
15415 funcFamilyDeps
.emplace(name
, std::move(ids
)).second
15421 for (auto& u
: updates
) {
15422 for (auto& [name
, entry
] : u
.nameOnly
) {
15423 initTypesMeta
.nameOnlyFF
[name
].emplace_back(std::move(entry
));
15425 for (auto [name
, candidate
] : u
.candidateRegOnlyEquivs
) {
15426 initTypesMeta
.classes
[name
]
15427 .candidateRegOnlyEquivs
.emplace(candidate
);
15432 for (auto& u
: updates
) {
15433 for (auto& [n
, o
] : u
.cnsBases
) {
15435 index
.classToCnsBases
.emplace(n
, std::move(o
)).second
15444 splitsToRefs
.clear();
15445 funcFamilyDeps
.clear();
15446 work
.buckets
.clear();
15447 work
.allSplits
.clear();
15450 //////////////////////////////////////////////////////////////////////
15453 * Initialize the return-types of functions and methods from their
15454 * type-hints. Also set AttrInitialSatisfiesTC on properties if
15455 * appropriate (which must be done after types are initialized).
15457 struct InitTypesJob
{
15458 static std::string
name() { return "hhbbc-init-types"; }
15459 static void init(const Config
& config
) {
15460 process_init(config
.o
, config
.gd
, false);
15461 ClassGraph::init();
15463 static void fini() { ClassGraph::destroy(); }
15465 using Output
= Multi
<
15466 Variadic
<std::unique_ptr
<php::Class
>>,
15467 Variadic
<std::unique_ptr
<ClassInfo2
>>,
15468 Variadic
<std::unique_ptr
<php::Func
>>,
15469 Variadic
<std::unique_ptr
<FuncInfo2
>>
15471 static Output
run(Variadic
<std::unique_ptr
<php::Class
>> classes
,
15472 Variadic
<std::unique_ptr
<ClassInfo2
>> cinfos
,
15473 Variadic
<std::unique_ptr
<php::Func
>> funcs
,
15474 Variadic
<std::unique_ptr
<FuncInfo2
>> finfos
,
15475 Variadic
<std::unique_ptr
<ClassInfo2
>> cinfoDeps
) {
15478 for (auto const& cls
: classes
.vals
) {
15479 always_assert(index
.classes
.emplace(cls
->name
, cls
.get()).second
);
15480 for (auto const& clo
: cls
->closures
) {
15481 always_assert(index
.classes
.emplace(clo
->name
, clo
.get()).second
);
15485 // All of the classes which might be a regular only equivalent
15486 // have been provided to the job. So, we can now definitely set
15487 // the regular only equivalent (if necessary). We need to do this
15488 // before setting the initial types because we need that
15489 // information to canonicalize.
15490 for (auto const& cinfo
: cinfos
.vals
) {
15491 always_assert(index
.classInfos
.emplace(cinfo
->name
, cinfo
.get()).second
);
15492 cinfo
->classGraph
.setRegOnlyEquivs();
15493 // If this is a regular class, we don't need the "expanded"
15494 // method family information anymore, so clear it here to save
15496 if (cinfo
->isRegularClass
) {
15498 cinfo
->methodFamilies
,
15499 [&] (auto const& e
) { return !cinfo
->methods
.count(e
.first
); }
15503 for (auto const& clo
: cinfo
->closures
) {
15504 always_assert(index
.classInfos
.emplace(clo
->name
, clo
.get()).second
);
15505 clo
->classGraph
.setRegOnlyEquivs();
15508 for (auto const& cinfo
: cinfoDeps
.vals
) {
15509 always_assert(index
.classInfos
.emplace(cinfo
->name
, cinfo
.get()).second
);
15510 cinfo
->classGraph
.setRegOnlyEquivs();
15511 for (auto const& clo
: cinfo
->closures
) {
15512 always_assert(index
.classInfos
.emplace(clo
->name
, clo
.get()).second
);
15513 clo
->classGraph
.setRegOnlyEquivs();
15517 auto const onCls
= [&] (php::Class
& cls
, ClassInfo2
& cinfo
) {
15518 assertx(cls
.name
->tsame(cinfo
.name
));
15519 assertx(cinfo
.funcInfos
.size() == cls
.methods
.size());
15521 unresolve_missing(index
, cls
);
15522 set_bad_initial_prop_values(index
, cls
, cinfo
);
15523 for (size_t i
= 0, size
= cls
.methods
.size(); i
< size
; ++i
) {
15524 auto const& func
= cls
.methods
[i
];
15525 auto& finfo
= cinfo
.funcInfos
[i
];
15526 assertx(func
->name
== finfo
->name
);
15527 assertx(finfo
->returnTy
.is(BInitCell
));
15528 finfo
->returnTy
= initial_return_type(index
, *func
);
15532 assertx(classes
.vals
.size() == cinfos
.vals
.size());
15533 for (size_t i
= 0, size
= classes
.vals
.size(); i
< size
; ++i
) {
15534 auto& cls
= classes
.vals
[i
];
15535 auto& cinfo
= cinfos
.vals
[i
];
15536 onCls(*cls
, *cinfo
);
15538 assertx(cls
->closures
.size() == cinfo
->closures
.size());
15539 for (size_t j
= 0, size2
= cls
->closures
.size(); j
< size2
; ++j
) {
15540 auto& clo
= cls
->closures
[j
];
15541 auto& cloinfo
= cinfo
->closures
[j
];
15542 onCls(*clo
, *cloinfo
);
15546 assertx(funcs
.vals
.size() == finfos
.vals
.size());
15547 for (size_t i
= 0, size
= funcs
.vals
.size(); i
< size
; ++i
) {
15548 auto const& func
= funcs
.vals
[i
];
15549 auto& finfo
= finfos
.vals
[i
];
15550 assertx(func
->name
== finfo
->name
);
15551 assertx(finfo
->returnTy
.is(BInitCell
));
15552 unresolve_missing(index
, *func
);
15553 finfo
->returnTy
= initial_return_type(index
, *func
);
15556 return std::make_tuple(
15557 std::move(classes
),
15566 struct LocalIndex
{
15567 TSStringToOneT
<const ClassInfo2
*> classInfos
;
15568 TSStringToOneT
<const php::Class
*> classes
;
15571 static void unresolve_missing(const LocalIndex
& index
, TypeConstraint
& tc
) {
15572 if (!tc
.isSubObject()) return;
15573 auto const name
= tc
.clsName();
15574 if (index
.classInfos
.count(name
)) return;
15576 4, "Unresolving type-constraint for '{}' because it does not exist\n",
15582 static void unresolve_missing(const LocalIndex
& index
, php::Func
& func
) {
15583 for (auto& p
: func
.params
) {
15584 unresolve_missing(index
, p
.typeConstraint
);
15585 for (auto& ub
: p
.upperBounds
.m_constraints
) unresolve_missing(index
, ub
);
15587 unresolve_missing(index
, func
.retTypeConstraint
);
15588 for (auto& ub
: func
.returnUBs
.m_constraints
) unresolve_missing(index
, ub
);
15591 static void unresolve_missing(const LocalIndex
& index
, php::Class
& cls
) {
15592 if (cls
.attrs
& AttrEnum
) unresolve_missing(index
, cls
.enumBaseTy
);
15593 for (auto& meth
: cls
.methods
) unresolve_missing(index
, *meth
);
15594 for (auto& prop
: cls
.properties
) {
15595 unresolve_missing(index
, prop
.typeConstraint
);
15596 for (auto& ub
: prop
.ubs
.m_constraints
) unresolve_missing(index
, ub
);
15600 static Type
initial_return_type(const LocalIndex
& index
, const php::Func
& f
) {
15602 Trace::hhbbc_index
, kSystemLibBump
, is_systemlib_part(f
.unit
)
15605 auto const ty
= [&] {
15606 // Return type of native functions is calculated differently.
15607 if (f
.isNative
) return native_function_return_type(&f
);
15609 if ((f
.attrs
& AttrBuiltin
) || f
.isMemoizeWrapper
) return TInitCell
;
15611 if (f
.isGenerator
) {
15613 // Async generators always return AsyncGenerator object.
15614 return objExact(res::Class::get(s_AsyncGenerator
.get()));
15616 // Non-async generators always return Generator object.
15617 return objExact(res::Class::get(s_Generator
.get()));
15620 auto const make_type
= [&] (const TypeConstraint
& tc
) {
15621 auto lookup
= type_from_constraint(
15624 [&] (SString name
) -> Optional
<res::Class
> {
15625 if (auto const ci
= folly::get_default(index
.classInfos
, name
)) {
15626 auto const c
= res::Class::get(*ci
);
15627 assertx(c
.isComplete());
15630 return std::nullopt
;
15632 [&] () -> Optional
<Type
> {
15633 if (!f
.cls
) return std::nullopt
;
15634 auto const& cls
= [&] () -> const php::Class
& {
15635 if (!f
.cls
->closureContextCls
) return *f
.cls
;
15637 folly::get_default(index
.classes
, f
.cls
->closureContextCls
);
15638 always_assert_flog(
15640 "When processing return-type for {}, "
15641 "tried to access missing class {}",
15643 f
.cls
->closureContextCls
15647 if (cls
.attrs
& AttrTrait
) return std::nullopt
;
15648 auto const c
= res::Class::get(cls
.name
);
15649 assertx(c
.isComplete());
15650 return subCls(c
, true);
15653 if (lookup
.coerceClassToString
== TriBool::Yes
) {
15654 lookup
.upper
= promote_classish(std::move(lookup
.upper
));
15655 } else if (lookup
.coerceClassToString
== TriBool::Maybe
) {
15656 lookup
.upper
|= TSStr
;
15658 return unctx(std::move(lookup
.upper
));
15661 auto const process
= [&] (const TypeConstraint
& tc
,
15662 const TypeIntersectionConstraint
& ubs
) {
15663 auto ret
= TInitCell
;
15664 ret
= intersection_of(std::move(ret
), make_type(tc
));
15665 for (auto const& ub
: ubs
.m_constraints
) {
15666 ret
= intersection_of(std::move(ret
), make_type(ub
));
15671 auto ret
= process(f
.retTypeConstraint
, f
.returnUBs
);
15672 if (f
.hasInOutArgs
&& !ret
.is(BBottom
)) {
15673 std::vector
<Type
> types
;
15674 types
.reserve(f
.params
.size() + 1);
15675 types
.emplace_back(std::move(ret
));
15676 for (auto const& p
: f
.params
) {
15677 if (!p
.inout
) continue;
15678 auto t
= process(p
.typeConstraint
, p
.upperBounds
);
15679 if (t
.is(BBottom
)) return TBottom
;
15680 types
.emplace_back(std::move(t
));
15682 std::reverse(begin(types
)+1, end(types
));
15683 ret
= vec(std::move(types
));
15687 // Async functions always return WaitH<T>, where T is the type
15688 // returned internally.
15689 return wait_handle(std::move(ret
));
15694 FTRACE(3, "Initial return type for {}: {}\n",
15695 func_fullname(f
), show(ty
));
15699 static void set_bad_initial_prop_values(const LocalIndex
& index
,
15701 ClassInfo2
& cinfo
) {
15703 Trace::hhbbc_index
, kSystemLibBump
, is_systemlib_part(cls
.unit
)
15706 assertx(cinfo
.hasBadInitialPropValues
);
15708 auto const isClosure
= is_closure(cls
);
15710 cinfo
.hasBadInitialPropValues
= false;
15711 for (auto& prop
: cls
.properties
) {
15712 assertx(!(prop
.attrs
& AttrInitialSatisfiesTC
));
15714 // Check whether the property's initial value satisfies it's
15716 auto const initialSatisfies
= [&] {
15717 if (isClosure
) return true;
15718 if (is_used_trait(cls
)) return false;
15720 // Any property with an unresolved type-constraint here might
15721 // fatal when we initialize the class.
15722 if (prop
.typeConstraint
.isUnresolved()) return false;
15723 for (auto const& ub
: prop
.ubs
.m_constraints
) {
15724 if (ub
.isUnresolved()) return false;
15727 if (prop
.attrs
& (AttrSystemInitialValue
| AttrLateInit
)) return true;
15729 auto const initial
= from_cell(prop
.val
);
15730 if (initial
.subtypeOf(BUninit
)) return false;
15732 auto const make_type
= [&] (const TypeConstraint
& tc
) {
15733 auto lookup
= type_from_constraint(
15736 [&] (SString name
) -> Optional
<res::Class
> {
15737 if (auto const ci
= folly::get_default(index
.classInfos
, name
)) {
15738 auto const c
= res::Class::get(*ci
);
15739 assertx(c
.isComplete());
15742 return std::nullopt
;
15744 [&] () -> Optional
<Type
> {
15745 auto const& ctx
= [&] () -> const php::Class
& {
15746 if (!cls
.closureContextCls
) return cls
;
15748 folly::get_default(index
.classes
, cls
.closureContextCls
);
15749 always_assert_flog(
15751 "When processing bad initial prop values for {}, "
15752 "tried to access missing class {}",
15754 cls
.closureContextCls
15758 if (ctx
.attrs
& AttrTrait
) return std::nullopt
;
15759 auto const c
= res::Class::get(ctx
.name
);
15760 assertx(c
.isComplete());
15761 return subCls(c
, true);
15764 return unctx(std::move(lookup
.lower
));
15767 if (!initial
.subtypeOf(make_type(prop
.typeConstraint
))) return false;
15768 for (auto const& ub
: prop
.ubs
.m_constraints
) {
15769 if (!initial
.subtypeOf(make_type(ub
))) return false;
15774 if (initialSatisfies
) {
15775 attribute_setter(prop
.attrs
, true, AttrInitialSatisfiesTC
);
15777 cinfo
.hasBadInitialPropValues
= true;
15784 * "Fixups" a php::Unit by removing specified funcs from it, and
15785 * adding specified classes. This is needed to add closures created
15786 * from trait flattening into their associated units. While we're
15787 * doing this, we also remove redundant meth caller funcs here
15788 * (because it's convenient).
15790 struct UnitFixupJob
{
15791 static std::string
name() { return "hhbbc-unit-fixup"; }
15792 static void init(const Config
& config
) {
15793 process_init(config
.o
, config
.gd
, false);
15795 static void fini() {}
15797 static std::unique_ptr
<php::Unit
> run(std::unique_ptr
<php::Unit
> unit
,
15798 const InitTypesMetadata::Fixup
& fixup
) {
15799 SCOPE_ASSERT_DETAIL("unit") { return unit
->filename
->toCppString(); };
15801 if (!fixup
.removeFunc
.empty()) {
15802 // If we want to remove a func, it should be in this unit.
15803 auto DEBUG_ONLY erased
= false;
15806 begin(unit
->funcs
),
15808 [&] (SString func
) {
15809 // This is a kinda dumb O(N^2) algorithm, but these lists
15810 // are typicaly size 1.
15811 auto const erase
= std::any_of(
15812 begin(fixup
.removeFunc
),
15813 end(fixup
.removeFunc
),
15814 [&] (SString remove
) { return remove
== func
; }
15816 if (erase
) erased
= true;
15825 auto const before
= unit
->classes
.size();
15826 unit
->classes
.insert(
15827 end(unit
->classes
),
15828 begin(fixup
.addClass
),
15829 end(fixup
.addClass
)
15831 // Only sort the newly added classes. The order of the existing
15832 // classes is visible to programs.
15834 begin(unit
->classes
) + before
,
15835 end(unit
->classes
),
15836 string_data_lt_type
{}
15839 std::adjacent_find(
15840 begin(unit
->classes
), end(unit
->classes
),
15841 string_data_tsame
{}) == end(unit
->classes
)
15848 * BuildSubclassListJob produces name-only func family entries. This
15849 * job merges entries for the same name into one.
15851 struct AggregateNameOnlyJob
: public BuildSubclassListJob
{
15852 static std::string
name() { return "hhbbc-aggregate-name-only"; }
15854 struct OutputMeta
{
15855 std::vector
<std::vector
<FuncFamily2::Id
>> newFuncFamilyIds
;
15856 std::vector
<FuncFamilyEntry
> nameOnly
;
15857 template <typename SerDe
> void serde(SerDe
& sd
) {
15858 ScopedStringDataIndexer _
;
15859 sd(newFuncFamilyIds
)
15864 using Output
= Multi
<
15865 Variadic
<FuncFamilyGroup
>,
15870 run(std::vector
<std::pair
<SString
, std::vector
<FuncFamilyEntry
>>> allEntries
,
15871 Variadic
<FuncFamilyGroup
> funcFamilies
) {
15874 for (auto& group
: funcFamilies
.vals
) {
15875 for (auto& ff
: group
.m_ffs
) {
15876 auto const id
= ff
->m_id
;
15877 // We could have multiple groups which contain the same
15878 // FuncFamily, so don't assert uniqueness here. We'll just
15879 // take the first one we see (they should all be equivalent).
15880 index
.funcFamilies
.emplace(id
, std::move(ff
));
15886 for (auto const& [name
, entries
] : allEntries
) {
15887 Data::MethInfo info
;
15888 info
.complete
= false;
15889 info
.regularComplete
= false;
15891 for (auto const& entry
: entries
) {
15892 auto entryInfo
= meth_info_from_func_family_entry(index
, entry
);
15893 for (auto const& meth
: entryInfo
.regularMeths
) {
15894 if (info
.regularMeths
.count(meth
)) continue;
15895 info
.regularMeths
.emplace(meth
);
15896 info
.nonRegularPrivateMeths
.erase(meth
);
15897 info
.nonRegularMeths
.erase(meth
);
15899 for (auto const& meth
: entryInfo
.nonRegularPrivateMeths
) {
15900 if (info
.regularMeths
.count(meth
) ||
15901 info
.nonRegularPrivateMeths
.count(meth
)) {
15904 info
.nonRegularPrivateMeths
.emplace(meth
);
15905 info
.nonRegularMeths
.erase(meth
);
15907 for (auto const& meth
: entryInfo
.nonRegularMeths
) {
15908 if (info
.regularMeths
.count(meth
) ||
15909 info
.nonRegularPrivateMeths
.count(meth
) ||
15910 info
.nonRegularMeths
.count(meth
)) {
15913 info
.nonRegularMeths
.emplace(meth
);
15916 if (entryInfo
.allStatic
) {
15917 if (!info
.allStatic
) {
15918 info
.allStatic
= std::move(*entryInfo
.allStatic
);
15920 *info
.allStatic
|= *entryInfo
.allStatic
;
15923 if (entryInfo
.regularStatic
) {
15924 if (!info
.regularStatic
) {
15925 info
.regularStatic
= std::move(*entryInfo
.regularStatic
);
15927 *info
.regularStatic
|= *entryInfo
.regularStatic
;
15932 meta
.nameOnly
.emplace_back(
15933 make_method_family_entry(index
, name
, std::move(info
))
15937 Variadic
<FuncFamilyGroup
> funcFamilyGroups
;
15938 group_func_families(index
, funcFamilyGroups
.vals
, meta
.newFuncFamilyIds
);
15940 return std::make_tuple(
15941 std::move(funcFamilyGroups
),
15947 Job
<InitTypesJob
> s_initTypesJob
;
15948 Job
<UnitFixupJob
> s_unitFixupJob
;
15949 Job
<AggregateNameOnlyJob
> s_aggregateNameOnlyJob
;
15951 // Initialize return-types, fixup units, and aggregate name-only
15952 // func-families all at once.
15953 void init_types(IndexData
& index
, InitTypesMetadata meta
) {
15954 trace_time tracer
{"init types", index
.sample
};
15956 constexpr size_t kTypesBucketSize
= 2000;
15957 constexpr size_t kFixupsBucketSize
= 3000;
15958 constexpr size_t kAggregateBucketSize
= 3000;
15960 auto typeBuckets
= consistently_bucketize(
15962 // Temporarily suppress case collision logging
15963 auto oldLogLevel
= Cfg::Eval::LogTsameCollisions
;
15964 Cfg::Eval::LogTsameCollisions
= 0;
15965 SCOPE_EXIT
{ Cfg::Eval::LogTsameCollisions
= oldLogLevel
; };
15967 std::vector
<SString
> roots
;
15968 roots
.reserve(meta
.classes
.size() + meta
.funcs
.size());
15969 for (auto const& [name
, _
] : meta
.classes
) {
15970 roots
.emplace_back(name
);
15972 for (auto const& [name
, _
] : meta
.funcs
) {
15973 // A class and a func could have the same name. Avoid
15974 // duplicates. If we do have a name collision it just means
15975 // the func and class will be assigned to the same bucket.
15976 if (meta
.classes
.count(name
)) continue;
15977 roots
.emplace_back(name
);
15984 auto fixupBuckets
= consistently_bucketize(
15986 std::vector
<SString
> sorted
;
15987 sorted
.reserve(meta
.fixups
.size());
15988 for (auto& [unit
, _
] : meta
.fixups
) sorted
.emplace_back(unit
);
15989 std::sort(sorted
.begin(), sorted
.end(), string_data_lt
{});
15995 auto aggregateBuckets
= consistently_bucketize(
15997 std::vector
<SString
> sorted
;
15998 sorted
.reserve(meta
.nameOnlyFF
.size());
15999 for (auto const& [name
, entries
] : meta
.nameOnlyFF
) {
16000 if (entries
.size() <= 1) {
16001 // If there's only one entry for a name, there's nothing to
16002 // aggregate, and can be inserted directly as the final
16005 index
.nameOnlyMethodFamilies
.emplace(name
, entries
[0]).second
16009 // Otherwise insert a dummy entry. This will let us update the
16010 // entry later from multiple threads without having to mutate
16013 index
.nameOnlyMethodFamilies
.emplace(name
, FuncFamilyEntry
{}).second
16015 sorted
.emplace_back(name
);
16017 std::sort(begin(sorted
), end(sorted
), string_data_lt
{});
16020 kAggregateBucketSize
16023 // We want to avoid updating any Index data-structures until after
16024 // all jobs have read their inputs. We use the latch to block tasks
16025 // until all tasks have passed the point of reading their inputs.
16026 CoroLatch typesLatch
{typeBuckets
.size()};
16028 auto const runTypes
= [&] (std::vector
<SString
> work
) -> coro::Task
<void> {
16029 co_await
coro::co_reschedule_on_current_executor
;
16031 if (work
.empty()) {
16032 typesLatch
.count_down();
16036 std::vector
<UniquePtrRef
<php::Class
>> classes
;
16037 std::vector
<UniquePtrRef
<ClassInfo2
>> cinfos
;
16038 std::vector
<UniquePtrRef
<php::Func
>> funcs
;
16039 std::vector
<UniquePtrRef
<FuncInfo2
>> finfos
;
16040 std::vector
<UniquePtrRef
<ClassInfo2
>> cinfoDeps
;
16043 std::vector
<SString
> classNames
;
16044 std::vector
<SString
> funcNames
;
16046 roots
.reserve(work
.size());
16047 classNames
.reserve(work
.size());
16049 for (auto const w
: work
) {
16050 if (meta
.classes
.count(w
)) {
16051 always_assert(roots
.emplace(w
).second
);
16052 classNames
.emplace_back(w
);
16054 if (meta
.funcs
.count(w
)) funcNames
.emplace_back(w
);
16057 // Add a dependency to the job. A class is a dependency if it
16058 // shows up in a class' type-hints, or if it's a potential
16059 // reg-only equivalent.
16060 auto const addDep
= [&] (SString dep
, bool addEquiv
) {
16061 if (!meta
.classes
.count(dep
) || roots
.count(dep
)) return;
16062 cinfoDeps
.emplace_back(index
.classInfoRefs
.at(dep
));
16063 if (!addEquiv
) return;
16064 if (auto const cls
= folly::get_ptr(meta
.classes
, dep
)) {
16065 for (auto const d
: cls
->candidateRegOnlyEquivs
) {
16066 if (!meta
.classes
.count(d
) || roots
.count(d
)) continue;
16067 cinfoDeps
.emplace_back(index
.classInfoRefs
.at(d
));
16072 for (auto const w
: work
) {
16073 if (auto const cls
= folly::get_ptr(meta
.classes
, w
)) {
16074 classes
.emplace_back(index
.classRefs
.at(w
));
16075 cinfos
.emplace_back(index
.classInfoRefs
.at(w
));
16076 for (auto const d
: cls
->deps
) addDep(d
, true);
16077 for (auto const d
: cls
->candidateRegOnlyEquivs
) addDep(d
, false);
16079 // Not else if. A name can correspond to both a class and a
16081 if (auto const func
= folly::get_ptr(meta
.funcs
, w
)) {
16082 funcs
.emplace_back(index
.funcRefs
.at(w
));
16083 finfos
.emplace_back(index
.funcInfoRefs
.at(w
));
16084 for (auto const d
: func
->deps
) addDep(d
, true);
16087 addDep(s_Awaitable
.get(), true);
16088 addDep(s_AsyncGenerator
.get(), true);
16089 addDep(s_Generator
.get(), true);
16091 // Record that we've read our inputs
16092 typesLatch
.count_down();
16094 std::sort(begin(cinfoDeps
), end(cinfoDeps
));
16096 std::unique(begin(cinfoDeps
), end(cinfoDeps
)),
16100 auto config
= co_await index
.configRef
->getCopy();
16102 Client::ExecMetadata metadata
{
16103 .job_key
= folly::sformat("init types {}", work
[0])
16106 auto results
= co_await
16107 index
.client
->exec(
16112 std::move(classes
),
16116 std::move(cinfoDeps
)
16119 std::move(metadata
)
16121 assertx(results
.size() == 1);
16122 auto& [classRefs
, cinfoRefs
, funcRefs
, finfoRefs
] = results
[0];
16123 assertx(classRefs
.size() == classNames
.size());
16124 assertx(cinfoRefs
.size() == classNames
.size());
16125 assertx(funcRefs
.size() == funcNames
.size());
16126 assertx(finfoRefs
.size() == funcNames
.size());
16128 // Wait for all tasks to finish reading from the Index ref tables
16129 // before starting to overwrite them.
16130 co_await typesLatch
.wait();
16132 for (size_t i
= 0, size
= classNames
.size(); i
< size
; ++i
) {
16133 auto const name
= classNames
[i
];
16134 index
.classRefs
.at(name
) = std::move(classRefs
[i
]);
16135 index
.classInfoRefs
.at(name
) = std::move(cinfoRefs
[i
]);
16137 for (size_t i
= 0, size
= funcNames
.size(); i
< size
; ++i
) {
16138 auto const name
= funcNames
[i
];
16139 index
.funcRefs
.at(name
) = std::move(funcRefs
[i
]);
16140 index
.funcInfoRefs
.at(name
) = std::move(finfoRefs
[i
]);
16146 auto const runFixups
= [&] (std::vector
<SString
> units
) -> coro::Task
<void> {
16147 co_await
coro::co_reschedule_on_current_executor
;
16149 if (units
.empty()) co_return
;
16151 std::vector
<InitTypesMetadata::Fixup
> fixups
;
16153 // Gather up the fixups and ensure a deterministic ordering.
16154 fixups
.reserve(units
.size());
16155 for (auto const unit
: units
) {
16156 auto f
= std::move(meta
.fixups
.at(unit
));
16157 assertx(!f
.addClass
.empty() || !f
.removeFunc
.empty());
16158 std::sort(f
.addClass
.begin(), f
.addClass
.end(), string_data_lt_type
{});
16159 std::sort(f
.removeFunc
.begin(), f
.removeFunc
.end(), string_data_lt
{});
16160 fixups
.emplace_back(std::move(f
));
16162 auto fixupRefs
= co_await index
.client
->storeMulti(std::move(fixups
));
16163 assertx(fixupRefs
.size() == units
.size());
16166 std::tuple
<UniquePtrRef
<php::Unit
>, Ref
<InitTypesMetadata::Fixup
>>
16168 inputs
.reserve(units
.size());
16170 for (size_t i
= 0, size
= units
.size(); i
< size
; ++i
) {
16171 inputs
.emplace_back(
16172 index
.unitRefs
.at(units
[i
]),
16173 std::move(fixupRefs
[i
])
16177 Client::ExecMetadata metadata
{
16178 .job_key
= folly::sformat("fixup units {}", units
[0])
16181 auto config
= co_await index
.configRef
->getCopy();
16182 auto outputs
= co_await index
.client
->exec(
16186 std::move(metadata
)
16188 assertx(outputs
.size() == units
.size());
16190 // Every unit is already in the Index table, so we can overwrite
16191 // them without locking.
16192 for (size_t i
= 0, size
= units
.size(); i
< size
; ++i
) {
16193 index
.unitRefs
.at(units
[i
]) = std::move(outputs
[i
]);
16199 struct AggregateUpdates
{
16201 std::pair
<FuncFamily2::Id
, Ref
<FuncFamilyGroup
>>
16205 auto const runAggregate
= [&] (std::vector
<SString
> names
)
16206 -> coro::Task
<AggregateUpdates
> {
16207 co_await
coro::co_reschedule_on_current_executor
;
16209 if (names
.empty()) co_return AggregateUpdates
{};
16211 std::vector
<std::pair
<SString
, std::vector
<FuncFamilyEntry
>>> entries
;
16212 std::vector
<Ref
<FuncFamilyGroup
>> funcFamilies
;
16214 entries
.reserve(names
.size());
16215 // Extract out any func families the entries refer to, so they can
16216 // be provided to the job.
16217 for (auto const n
: names
) {
16218 auto& e
= meta
.nameOnlyFF
.at(n
);
16219 entries
.emplace_back(n
, std::move(e
));
16220 for (auto const& entry
: entries
.back().second
) {
16223 [&] (const FuncFamilyEntry::BothFF
& e
) {
16224 funcFamilies
.emplace_back(index
.funcFamilyRefs
.at(e
.m_ff
));
16226 [&] (const FuncFamilyEntry::FFAndSingle
& e
) {
16227 funcFamilies
.emplace_back(index
.funcFamilyRefs
.at(e
.m_ff
));
16229 [&] (const FuncFamilyEntry::FFAndNone
& e
) {
16230 funcFamilies
.emplace_back(index
.funcFamilyRefs
.at(e
.m_ff
));
16232 [&] (const FuncFamilyEntry::BothSingle
&) {},
16233 [&] (const FuncFamilyEntry::SingleAndNone
&) {},
16234 [&] (const FuncFamilyEntry::None
&) {}
16239 std::sort(begin(funcFamilies
), end(funcFamilies
));
16240 funcFamilies
.erase(
16241 std::unique(begin(funcFamilies
), end(funcFamilies
)),
16245 auto [entriesRef
, config
] = co_await
coro::collectAll(
16246 index
.client
->store(std::move(entries
)),
16247 index
.configRef
->getCopy()
16250 Client::ExecMetadata metadata
{
16251 .job_key
= folly::sformat("aggregate name-only {}", names
[0])
16254 auto results
= co_await
16255 index
.client
->exec(
16256 s_aggregateNameOnlyJob
,
16259 std::make_tuple(std::move(entriesRef
), std::move(funcFamilies
))
16261 std::move(metadata
)
16263 assertx(results
.size() == 1);
16264 auto& [ffRefs
, outMetaRef
] = results
[0];
16266 auto outMeta
= co_await index
.client
->load(std::move(outMetaRef
));
16267 assertx(outMeta
.newFuncFamilyIds
.size() == ffRefs
.size());
16268 assertx(outMeta
.nameOnly
.size() == names
.size());
16270 // Update the dummy entries with the actual result.
16271 for (size_t i
= 0, size
= names
.size(); i
< size
; ++i
) {
16272 auto& old
= index
.nameOnlyMethodFamilies
.at(names
[i
]);
16273 assertx(boost::get
<FuncFamilyEntry::None
>(&old
.m_meths
));
16274 old
= std::move(outMeta
.nameOnly
[i
]);
16277 AggregateUpdates updates
;
16278 updates
.funcFamilies
.reserve(outMeta
.newFuncFamilyIds
.size());
16279 for (size_t i
= 0, size
= outMeta
.newFuncFamilyIds
.size(); i
< size
; ++i
) {
16280 auto const ref
= ffRefs
[i
];
16281 for (auto const& id
: outMeta
.newFuncFamilyIds
[i
]) {
16282 updates
.funcFamilies
.emplace_back(id
, ref
);
16289 auto const runAggregateCombine
= [&] (auto tasks
) -> coro::Task
<void> {
16290 co_await
coro::co_reschedule_on_current_executor
;
16292 auto const updates
= co_await
coro::collectAllRange(std::move(tasks
));
16294 for (auto const& u
: updates
) {
16295 for (auto const& [id
, ref
] : u
.funcFamilies
) {
16296 // The same FuncFamily can be grouped into multiple
16297 // different groups. Prefer the group that's smaller and
16298 // if they're the same size, use the one with the lowest
16299 // id to keep determinism.
16300 auto const& [existing
, inserted
] =
16301 index
.funcFamilyRefs
.emplace(id
, ref
);
16302 if (inserted
) continue;
16303 if (existing
->second
.id().m_size
< ref
.id().m_size
) continue;
16304 if (ref
.id().m_size
< existing
->second
.id().m_size
) {
16305 existing
->second
= ref
;
16308 if (existing
->second
.id() <= ref
.id()) continue;
16309 existing
->second
= ref
;
16316 using namespace folly::gen
;
16318 std::vector
<coro::TaskWithExecutor
<void>> tasks
;
16319 tasks
.reserve(typeBuckets
.size() + fixupBuckets
.size() + 1);
16321 // Temporarily suppress case collision logging
16322 auto oldTypeLogLevel
= Cfg::Eval::LogTsameCollisions
;
16323 Cfg::Eval::LogTsameCollisions
= 0;
16325 Cfg::Eval::LogTsameCollisions
= oldTypeLogLevel
;
16328 for (auto& work
: typeBuckets
) {
16329 tasks
.emplace_back(
16330 runTypes(std::move(work
)).scheduleOn(index
.executor
->sticky())
16333 for (auto& work
: fixupBuckets
) {
16334 tasks
.emplace_back(
16335 runFixups(std::move(work
)).scheduleOn(index
.executor
->sticky())
16338 auto subTasks
= from(aggregateBuckets
)
16340 | map([&] (std::vector
<SString
>&& work
) {
16341 return runAggregate(
16343 ).scheduleOn(index
.executor
->sticky());
16345 | as
<std::vector
>();
16346 tasks
.emplace_back(
16347 runAggregateCombine(
16348 std::move(subTasks
)
16349 ).scheduleOn(index
.executor
->sticky())
16352 coro::blockingWait(coro::collectAllRange(std::move(tasks
)));
16355 //////////////////////////////////////////////////////////////////////
16357 Index::Input::UnitMeta
make_native_unit_meta(IndexData
& index
) {
16358 auto unit
= make_native_unit();
16359 auto const name
= unit
->filename
;
16361 std::vector
<std::pair
<SString
, bool>> constants
;
16362 constants
.reserve(unit
->constants
.size());
16363 for (auto const& cns
: unit
->constants
) {
16364 constants
.emplace_back(cns
->name
, type(cns
->val
) == KindOfUninit
);
16367 auto unitRef
= coro::blockingWait(index
.client
->store(std::move(unit
)));
16368 Index::Input::UnitMeta meta
{ std::move(unitRef
), name
};
16369 meta
.constants
= std::move(constants
);
16373 // Set up the async state, populate the (initial) table of
16374 // extern-worker refs in the Index, and build some metadata needed for
16375 // class flattening.
16376 IndexFlattenMetadata
make_remote(IndexData
& index
,
16378 Index::Input input
,
16379 std::unique_ptr
<TicketExecutor
> executor
,
16380 std::unique_ptr
<Client
> client
,
16381 DisposeCallback dispose
) {
16382 trace_time
tracer("make remote");
16383 tracer
.ignore_client_stats();
16385 assertx(input
.classes
.size() == input
.classBC
.size());
16386 assertx(input
.funcs
.size() == input
.funcBC
.size());
16388 index
.executor
= std::move(executor
);
16389 index
.client
= std::move(client
);
16390 index
.disposeClient
= std::move(dispose
);
16392 // Kick off the storage of the global config. We'll start early so
16393 // it will (hopefully) be done before we need it.
16394 index
.configRef
= std::make_unique
<CoroAsyncValue
<Ref
<Config
>>>(
16395 [&index
, config
= std::move(config
)] () mutable {
16396 return index
.client
->store(std::move(config
));
16398 index
.executor
->sticky()
16401 // Create a fake unit to store native constants and add it as an
16403 input
.units
.emplace_back(make_native_unit_meta(index
));
16405 IndexFlattenMetadata flattenMeta
;
16406 SStringToOneT
<SString
> methCallerUnits
;
16408 flattenMeta
.cls
.reserve(input
.classes
.size());
16409 flattenMeta
.allCls
.reserve(input
.classes
.size());
16410 flattenMeta
.allFuncs
.reserve(input
.funcs
.size());
16412 // Add unit and class information to their appropriate tables. This
16413 // is also where we'll detect duplicate funcs and class names (which
16414 // should be caught earlier during parsing).
16415 for (auto& unit
: input
.units
) {
16416 FTRACE(5, "unit {} -> {}\n", unit
.name
, unit
.unit
.id().toString());
16418 for (auto& typeMapping
: unit
.typeMappings
) {
16419 auto const name
= typeMapping
.name
;
16420 auto const isTypeAlias
= typeMapping
.isTypeAlias
;
16421 always_assert_flog(
16422 flattenMeta
.typeMappings
.emplace(name
, std::move(typeMapping
)).second
,
16423 "Duplicate type-mapping: {}",
16427 always_assert(index
.typeAliasToUnit
.emplace(name
, unit
.name
).second
);
16428 index
.unitsWithTypeAliases
.emplace(unit
.name
);
16432 always_assert_flog(
16433 index
.unitRefs
.emplace(unit
.name
, std::move(unit
.unit
)).second
,
16434 "Duplicate unit: {}",
16438 for (auto const& [cnsName
, hasInit
] : unit
.constants
) {
16439 always_assert_flog(
16440 index
.constantToUnit
.emplace(
16442 std::make_pair(unit
.name
, hasInit
)
16444 "Duplicate constant: {}",
16450 for (auto& cls
: input
.classes
) {
16451 FTRACE(5, "class {} -> {}\n", cls
.name
, cls
.cls
.id().toString());
16452 always_assert_flog(
16453 index
.classRefs
.emplace(cls
.name
, std::move(cls
.cls
)).second
,
16454 "Duplicate class: {}",
16457 always_assert(index
.classToUnit
.emplace(cls
.name
, cls
.unit
).second
);
16459 auto& meta
= flattenMeta
.cls
[cls
.name
];
16460 if (cls
.closureFunc
) {
16461 assertx(cls
.closures
.empty());
16462 index
.funcToClosures
[cls
.closureFunc
].emplace(cls
.name
);
16463 index
.closureToFunc
.emplace(cls
.name
, cls
.closureFunc
);
16464 meta
.isClosure
= true;
16466 index
.classToClosures
[cls
.name
].insert(
16467 begin(cls
.closures
),
16470 for (auto const clo
: cls
.closures
) {
16471 index
.closureToClass
.emplace(clo
, cls
.name
);
16474 meta
.deps
.insert(begin(cls
.dependencies
), end(cls
.dependencies
));
16475 meta
.unresolvedTypes
= std::move(cls
.unresolvedTypes
);
16476 meta
.idx
= flattenMeta
.allCls
.size();
16477 flattenMeta
.allCls
.emplace_back(cls
.name
);
16479 if (cls
.has86init
) index
.classesWith86Inits
.emplace(cls
.name
);
16481 if (cls
.typeMapping
) {
16482 auto const name
= cls
.typeMapping
->name
;
16483 always_assert_flog(
16484 flattenMeta
.typeMappings
.emplace(
16485 name
, std::move(*cls
.typeMapping
)
16487 "Duplicate type-mapping: {}",
16493 // Funcs have an additional wrinkle, however. A func might be a meth
16494 // caller. Meth callers are special in that they might be present
16495 // (with the same name) in multiple units. However only one "wins"
16496 // and is actually emitted in the repo. We detect that here and
16497 // select a winner. The "losing" meth callers will be actually
16498 // removed from their unit after class flattening.
16499 for (auto& func
: input
.funcs
) {
16500 FTRACE(5, "func {} -> {}\n", func
.name
, func
.func
.id().toString());
16502 if (func
.methCaller
) {
16503 // If this meth caller a duplicate of one we've already seen?
16504 auto const [existing
, emplaced
] =
16505 methCallerUnits
.emplace(func
.name
, func
.unit
);
16507 // It is. The duplicate shouldn't be in the same unit,
16509 always_assert_flog(
16510 existing
->second
!= func
.unit
,
16511 "Duplicate meth-caller {} in same unit {}",
16515 // The winner is the one with the unit with the "lesser"
16516 // name. This is completely arbitrary.
16517 if (string_data_lt
{}(func
.unit
, existing
->second
)) {
16518 // This one wins. Schedule the older entry for deletion and
16519 // take over it's position in the map.
16521 4, " meth caller {} from unit {} taking priority over unit {}",
16522 func
.name
, func
.unit
, existing
->second
16524 flattenMeta
.unitDeletions
[existing
->second
].emplace_back(func
.name
);
16525 existing
->second
= func
.unit
;
16526 index
.funcRefs
.at(func
.name
) = std::move(func
.func
);
16527 index
.funcToUnit
.at(func
.name
) = func
.unit
;
16529 // This one loses. Schedule it for deletion.
16530 flattenMeta
.unitDeletions
[func
.unit
].emplace_back(func
.name
);
16534 // It's not. Treat it like anything else.
16537 // If not a meth caller, treat it like anything else.
16538 always_assert_flog(
16539 index
.funcRefs
.emplace(func
.name
, std::move(func
.func
)).second
,
16540 "Duplicate func: {}",
16544 index
.funcToUnit
.emplace(func
.name
, func
.unit
);
16545 if (Constant::nameFromFuncName(func
.name
)) {
16546 index
.constantInitFuncs
.emplace(func
.name
);
16549 auto& meta
= flattenMeta
.func
[func
.name
];
16550 meta
.unresolvedTypes
= std::move(func
.unresolvedTypes
);
16552 flattenMeta
.allFuncs
.emplace_back(func
.name
);
16555 for (auto& bc
: input
.classBC
) {
16556 FTRACE(5, "class bytecode {} -> {}\n", bc
.name
, bc
.bc
.id().toString());
16558 always_assert_flog(
16559 index
.classRefs
.count(bc
.name
),
16560 "Class bytecode for non-existent class {}",
16563 always_assert_flog(
16564 index
.classBytecodeRefs
.emplace(bc
.name
, std::move(bc
.bc
)).second
,
16565 "Duplicate class bytecode: {}",
16570 for (auto& bc
: input
.funcBC
) {
16571 FTRACE(5, "func bytecode {} -> {}\n", bc
.name
, bc
.bc
.id().toString());
16573 always_assert_flog(
16574 index
.funcRefs
.count(bc
.name
),
16575 "Func bytecode for non-existent func {}",
16579 if (bc
.methCaller
) {
16580 // Only record this bytecode if it's associated meth-caller was
16582 auto const it
= methCallerUnits
.find(bc
.name
);
16583 always_assert_flog(
16584 it
!= end(methCallerUnits
),
16585 "Bytecode for func {} is marked as meth-caller, "
16586 "but func is not a meth-caller",
16589 auto const unit
= it
->second
;
16590 if (bc
.unit
!= unit
) {
16593 "Bytecode for meth-caller func {} in unit {} "
16594 "skipped because the meth-caller was dropped\n",
16600 always_assert_flog(
16601 !methCallerUnits
.count(bc
.name
),
16602 "Bytecode for func {} is not marked as meth-caller, "
16603 "but func is a meth-caller",
16608 always_assert_flog(
16609 index
.funcBytecodeRefs
.emplace(bc
.name
, std::move(bc
.bc
)).second
,
16610 "Duplicate func bytecode: {}",
16616 for (auto const& [cns
, unitAndInit
] : index
.constantToUnit
) {
16617 if (!unitAndInit
.second
) continue;
16618 if (is_native_unit(unitAndInit
.first
)) continue;
16619 auto const initName
= Constant::funcNameFromName(cns
);
16620 always_assert_flog(
16621 index
.funcRefs
.count(initName
) > 0,
16622 "Constant {} is marked as having initialization func {}, "
16623 "but it does not exist",
16629 return flattenMeta
;
16632 //////////////////////////////////////////////////////////////////////
16635 * Combines multiple classes/class-infos/funcs/units into a single
16636 * blob. Makes make_local() more efficient, as you can download a
16637 * smaller number of large blobs rather than many small blobs.
16639 struct AggregateJob
{
16640 static std::string
name() { return "hhbbc-aggregate"; }
16641 static void init(const Config
& config
) {
16642 process_init(config
.o
, config
.gd
, false);
16643 ClassGraph::init();
16645 static void fini() { ClassGraph::destroy(); }
16648 std::vector
<std::unique_ptr
<php::Class
>> classes
;
16649 std::vector
<std::unique_ptr
<ClassInfo2
>> classInfos
;
16650 std::vector
<std::unique_ptr
<php::ClassBytecode
>> classBytecode
;
16651 std::vector
<std::unique_ptr
<php::Func
>> funcs
;
16652 std::vector
<std::unique_ptr
<FuncInfo2
>> funcInfos
;
16653 std::vector
<std::unique_ptr
<php::FuncBytecode
>> funcBytecode
;
16654 std::vector
<std::unique_ptr
<php::Unit
>> units
;
16655 std::vector
<FuncFamilyGroup
> funcFamilies
;
16656 std::vector
<std::unique_ptr
<MethodsWithoutCInfo
>> methInfos
;
16658 template <typename SerDe
> void serde(SerDe
& sd
) {
16659 ScopedStringDataIndexer _
;
16660 ClassGraph::ScopedSerdeState _2
;
16674 static Bundle
run(Variadic
<std::unique_ptr
<php::Class
>> classes
,
16675 Variadic
<std::unique_ptr
<ClassInfo2
>> classInfos
,
16676 Variadic
<std::unique_ptr
<php::ClassBytecode
>> classBytecode
,
16677 Variadic
<std::unique_ptr
<php::Func
>> funcs
,
16678 Variadic
<std::unique_ptr
<FuncInfo2
>> funcInfos
,
16679 Variadic
<std::unique_ptr
<php::FuncBytecode
>> funcBytecode
,
16680 Variadic
<std::unique_ptr
<php::Unit
>> units
,
16681 Variadic
<FuncFamilyGroup
> funcFamilies
,
16682 Variadic
<std::unique_ptr
<MethodsWithoutCInfo
>> methInfos
) {
16684 bundle
.classes
.reserve(classes
.vals
.size());
16685 bundle
.classInfos
.reserve(classInfos
.vals
.size());
16686 bundle
.classBytecode
.reserve(classBytecode
.vals
.size());
16687 bundle
.funcs
.reserve(funcs
.vals
.size());
16688 bundle
.funcInfos
.reserve(funcInfos
.vals
.size());
16689 bundle
.funcBytecode
.reserve(funcBytecode
.vals
.size());
16690 bundle
.units
.reserve(units
.vals
.size());
16691 bundle
.funcFamilies
.reserve(funcFamilies
.vals
.size());
16692 bundle
.methInfos
.reserve(methInfos
.vals
.size());
16693 for (auto& c
: classes
.vals
) {
16694 bundle
.classes
.emplace_back(std::move(c
));
16696 for (auto& c
: classInfos
.vals
) {
16697 bundle
.classInfos
.emplace_back(std::move(c
));
16699 for (auto& b
: classBytecode
.vals
) {
16700 bundle
.classBytecode
.emplace_back(std::move(b
));
16702 for (auto& f
: funcs
.vals
) {
16703 bundle
.funcs
.emplace_back(std::move(f
));
16705 for (auto& f
: funcInfos
.vals
) {
16706 bundle
.funcInfos
.emplace_back(std::move(f
));
16708 for (auto& b
: funcBytecode
.vals
) {
16709 bundle
.funcBytecode
.emplace_back(std::move(b
));
16711 for (auto& u
: units
.vals
) {
16712 bundle
.units
.emplace_back(std::move(u
));
16714 for (auto& group
: funcFamilies
.vals
) {
16715 bundle
.funcFamilies
.emplace_back(std::move(group
));
16717 for (auto& m
: methInfos
.vals
) {
16718 bundle
.methInfos
.emplace_back(std::move(m
));
16724 Job
<AggregateJob
> s_aggregateJob
;
16726 void remote_func_info_to_local(IndexData
& index
,
16727 const php::Func
& func
,
16728 FuncInfo2
& rfinfo
) {
16729 assertx(func
.name
== rfinfo
.name
);
16730 auto finfo
= func_info(index
, &func
);
16731 assertx(finfo
->returnTy
.is(BInitCell
));
16732 finfo
->returnTy
= std::move(rfinfo
.returnTy
);
16733 finfo
->returnRefinements
= rfinfo
.returnRefinements
;
16734 finfo
->retParam
= rfinfo
.retParam
;
16735 finfo
->effectFree
= rfinfo
.effectFree
;
16736 finfo
->unusedParams
= rfinfo
.unusedParams
;
16739 // Convert the FuncInfo2s we loaded from extern-worker into their
16740 // equivalent FuncInfos.
16741 void make_func_infos_local(IndexData
& index
,
16742 std::vector
<std::unique_ptr
<FuncInfo2
>> remote
) {
16743 trace_time tracer
{"make func-infos local"};
16744 tracer
.ignore_client_stats();
16746 parallel::for_each(
16748 [&] (const std::unique_ptr
<FuncInfo2
>& rfinfo
) {
16749 auto const it
= index
.funcs
.find(rfinfo
->name
);
16750 always_assert_flog(
16751 it
!= end(index
.funcs
),
16752 "Func-info for {} has no associated php::Func in index",
16755 remote_func_info_to_local(index
, *it
->second
, *rfinfo
);
16760 // Convert the ClassInfo2s we loaded from extern-worker into their
16761 // equivalent ClassInfos (and store it in the Index).
16762 void make_class_infos_local(
16764 std::vector
<std::unique_ptr
<ClassInfo2
>> remote
,
16765 std::vector
<std::unique_ptr
<FuncFamily2
>> funcFamilies
16767 trace_time tracer
{"make class-infos local"};
16768 tracer
.ignore_client_stats();
16770 assertx(index
.allClassInfos
.empty());
16771 assertx(index
.classInfo
.empty());
16773 // First create a ClassInfo for each ClassInfo2. Since a ClassInfo
16774 // can refer to other ClassInfos, we can't do much more at this
16776 auto newCInfos
= parallel::map(
16778 [&] (const std::unique_ptr
<ClassInfo2
>& in
) {
16779 std::vector
<std::unique_ptr
<ClassInfo
>> out
;
16781 auto const make
= [&] (const ClassInfo2
& cinfo
) {
16782 auto c
= std::make_unique
<ClassInfo
>();
16783 auto const it
= index
.classes
.find(cinfo
.name
);
16784 always_assert_flog(
16785 it
!= end(index
.classes
),
16786 "Class-info for {} has no associated php::Class in index",
16789 c
->cls
= it
->second
;
16790 out
.emplace_back(std::move(c
));
16794 for (auto const& clo
: in
->closures
) make(*clo
);
16799 // Build table mapping name to ClassInfo.
16800 for (auto& cinfos
: newCInfos
) {
16801 for (auto& cinfo
: cinfos
) {
16803 index
.classInfo
.emplace(cinfo
->cls
->name
, cinfo
.get()).second
16805 index
.allClassInfos
.emplace_back(std::move(cinfo
));
16809 newCInfos
.shrink_to_fit();
16810 index
.allClassInfos
.shrink_to_fit();
16812 // Set AttrNoOverride to true for all methods. If we determine it's
16813 // actually overridden below, we'll clear it.
16814 parallel::for_each(
16815 index
.program
->classes
,
16816 [&] (std::unique_ptr
<php::Class
>& cls
) {
16817 for (auto& m
: cls
->methods
) {
16818 assertx(!(m
->attrs
& AttrNoOverride
));
16819 if (is_special_method_name(m
->name
)) continue;
16820 attribute_setter(m
->attrs
, true, AttrNoOverride
);
16822 for (auto& clo
: cls
->closures
) {
16823 assertx(clo
->methods
.size() == 1);
16824 auto& m
= clo
->methods
[0];
16825 assertx(!(m
->attrs
& AttrNoOverride
));
16826 assertx(!is_special_method_name(m
->name
));
16827 attribute_setter(m
->attrs
, true, AttrNoOverride
);
16832 auto const get
= [&] (SString name
) {
16833 auto const it
= index
.classInfo
.find(name
);
16834 always_assert_flog(
16835 it
!= end(index
.classInfo
),
16836 "Class-info for {} not found in index",
16843 explicit FFState(std::unique_ptr
<FuncFamily2
> ff
) : m_ff
{std::move(ff
)} {}
16844 std::unique_ptr
<FuncFamily2
> m_ff
;
16845 LockFreeLazyPtrNoDelete
<FuncFamily
> m_notExpanded
;
16846 LockFreeLazyPtrNoDelete
<FuncFamily
> m_expanded
;
16848 FuncFamily
* notExpanded(IndexData
& index
) {
16849 return const_cast<FuncFamily
*>(
16850 &m_notExpanded
.get([&] { return make(index
, false); })
16853 FuncFamily
* expanded(IndexData
& index
) {
16854 return const_cast<FuncFamily
*>(
16855 &m_expanded
.get([&] { return make(index
, true); })
16859 FuncFamily
* make(IndexData
& index
, bool expanded
) const {
16860 FuncFamily::PFuncVec funcs
;
16862 m_ff
->m_regular
.size() +
16865 : (m_ff
->m_nonRegularPrivate
.size() + m_ff
->m_nonRegular
.size())
16869 for (auto const& m
: m_ff
->m_regular
) {
16870 funcs
.emplace_back(func_from_meth_ref(index
, m
), true);
16872 for (auto const& m
: m_ff
->m_nonRegularPrivate
) {
16873 funcs
.emplace_back(func_from_meth_ref(index
, m
), true);
16876 for (auto const& m
: m_ff
->m_nonRegular
) {
16877 funcs
.emplace_back(func_from_meth_ref(index
, m
), false);
16881 auto const extra
= !expanded
&& !m_ff
->m_nonRegular
.empty() &&
16882 (m_ff
->m_regular
.size() + m_ff
->m_nonRegularPrivate
.size()) > 1;
16885 begin(funcs
), end(funcs
),
16886 [] (FuncFamily::PossibleFunc a
, const FuncFamily::PossibleFunc b
) {
16887 if (a
.inRegular() && !b
.inRegular()) return true;
16888 if (!a
.inRegular() && b
.inRegular()) return false;
16889 return string_data_lt_type
{}(a
.ptr()->cls
->name
, b
.ptr()->cls
->name
);
16892 funcs
.shrink_to_fit();
16894 assertx(funcs
.size() > 1);
16896 auto const convert
= [&] (const FuncFamily2::StaticInfo
& in
) {
16897 FuncFamily::StaticInfo out
;
16898 out
.m_numInOut
= in
.m_numInOut
;
16899 out
.m_requiredCoeffects
= in
.m_requiredCoeffects
;
16900 out
.m_coeffectRules
= in
.m_coeffectRules
;
16901 out
.m_paramPreps
= in
.m_paramPreps
;
16902 out
.m_minNonVariadicParams
= in
.m_minNonVariadicParams
;
16903 out
.m_maxNonVariadicParams
= in
.m_maxNonVariadicParams
;
16904 out
.m_isReadonlyReturn
= in
.m_isReadonlyReturn
;
16905 out
.m_isReadonlyThis
= in
.m_isReadonlyThis
;
16906 out
.m_supportsAER
= in
.m_supportsAER
;
16907 out
.m_maybeReified
= in
.m_maybeReified
;
16908 out
.m_maybeCaresAboutDynCalls
= in
.m_maybeCaresAboutDynCalls
;
16909 out
.m_maybeBuiltin
= in
.m_maybeBuiltin
;
16911 auto const it
= index
.funcFamilyStaticInfos
.find(out
);
16912 if (it
!= end(index
.funcFamilyStaticInfos
)) return it
->first
.get();
16913 return index
.funcFamilyStaticInfos
.insert(
16914 std::make_unique
<FuncFamily::StaticInfo
>(std::move(out
)),
16916 ).first
->first
.get();
16919 auto newFuncFamily
=
16920 std::make_unique
<FuncFamily
>(std::move(funcs
), extra
);
16922 always_assert(m_ff
->m_allStatic
);
16923 if (m_ff
->m_regularStatic
) {
16924 const FuncFamily::StaticInfo
* reg
= nullptr;
16925 if (expanded
|| extra
) reg
= convert(*m_ff
->m_regularStatic
);
16926 newFuncFamily
->m_all
.m_static
=
16927 expanded
? reg
: convert(*m_ff
->m_allStatic
);
16929 newFuncFamily
->m_regular
= std::make_unique
<FuncFamily::Info
>();
16930 newFuncFamily
->m_regular
->m_static
= reg
;
16933 newFuncFamily
->m_all
.m_static
= convert(*m_ff
->m_allStatic
);
16936 return index
.funcFamilies
.insert(
16937 std::move(newFuncFamily
),
16939 ).first
->first
.get();
16943 hphp_fast_map
<FuncFamily2::Id
, std::unique_ptr
<FFState
>> ffState
;
16944 for (auto& ff
: funcFamilies
) {
16945 auto const id
= ff
->m_id
;
16949 std::make_unique
<FFState
>(std::move(ff
))
16953 funcFamilies
.clear();
16955 std::mutex extraMethodLock
;
16957 // Now that we can map name to ClassInfo, we can populate the rest
16958 // of the fields in each ClassInfo.
16959 parallel::for_each(
16961 [&] (std::unique_ptr
<ClassInfo2
>& rcinfos
) {
16962 auto const process
= [&] (std::unique_ptr
<ClassInfo2
> rcinfo
) {
16963 auto const cinfo
= get(rcinfo
->name
);
16964 if (rcinfo
->parent
) cinfo
->parent
= get(rcinfo
->parent
);
16966 if (!(cinfo
->cls
->attrs
& AttrNoExpandTrait
)) {
16967 auto const traits
= rcinfo
->classGraph
.usedTraits();
16968 cinfo
->usedTraits
.reserve(traits
.size());
16969 for (auto const trait
: traits
) {
16970 cinfo
->usedTraits
.emplace_back(get(trait
.name()));
16972 cinfo
->usedTraits
.shrink_to_fit();
16974 cinfo
->traitProps
= std::move(rcinfo
->traitProps
);
16976 cinfo
->clsConstants
.reserve(rcinfo
->clsConstants
.size());
16977 for (auto const& [name
, cns
] : rcinfo
->clsConstants
) {
16978 auto const it
= index
.classes
.find(cns
.idx
.cls
);
16979 always_assert_flog(
16980 it
!= end(index
.classes
),
16981 "php::Class for {} not found in index",
16984 cinfo
->clsConstants
.emplace(
16986 ClassInfo::ConstIndex
{ it
->second
, cns
.idx
.idx
}
16990 for (size_t i
= 0, size
= cinfo
->cls
->constants
.size(); i
< size
; ++i
) {
16991 auto const& cns
= cinfo
->cls
->constants
[i
];
16992 if (cns
.kind
!= ConstModifiers::Kind::Value
) continue;
16993 if (!cns
.val
.has_value()) continue;
16994 if (cns
.val
->m_type
!= KindOfUninit
) continue;
16995 if (i
>= cinfo
->clsConstTypes
.size()) {
16996 cinfo
->clsConstTypes
.resize(i
+1, ClsConstInfo
{ TInitCell
, 0 });
16998 cinfo
->clsConstTypes
[i
] = folly::get_default(
16999 rcinfo
->clsConstantInfo
,
17001 ClsConstInfo
{ TInitCell
, 0 }
17004 cinfo
->clsConstTypes
.shrink_to_fit();
17007 std::vector
<std::pair
<SString
, MethTabEntry
>> methods
;
17008 methods
.reserve(cinfo
->methods
.size());
17009 for (auto const& [name
, mte
] : rcinfo
->methods
) {
17010 if (!(mte
.attrs
& AttrNoOverride
)) {
17012 func_from_meth_ref(index
, mte
.meth())->attrs
,
17017 methods
.emplace_back(name
, mte
);
17020 begin(methods
), end(methods
),
17021 [] (auto const& p1
, auto const& p2
) { return p1
.first
< p2
.first
; }
17023 cinfo
->methods
.insert(
17024 folly::sorted_unique
, begin(methods
), end(methods
)
17026 cinfo
->methods
.shrink_to_fit();
17029 cinfo
->hasBadRedeclareProp
= rcinfo
->hasBadRedeclareProp
;
17030 cinfo
->hasBadInitialPropValues
= rcinfo
->hasBadInitialPropValues
;
17031 cinfo
->hasConstProp
= rcinfo
->hasConstProp
;
17032 cinfo
->hasReifiedParent
= rcinfo
->hasReifiedParent
;
17033 cinfo
->subHasConstProp
= rcinfo
->subHasConstProp
;
17034 cinfo
->isMocked
= rcinfo
->isMocked
;
17035 cinfo
->isSubMocked
= rcinfo
->isSubMocked
;
17037 cinfo
->classGraph
= rcinfo
->classGraph
;
17038 cinfo
->classGraph
.setCInfo(*cinfo
);
17040 auto const noOverride
= [&] (SString name
) {
17041 if (auto const mte
= folly::get_ptr(cinfo
->methods
, name
)) {
17042 return bool(mte
->attrs
& AttrNoOverride
);
17047 auto const noOverrideRegular
= [&] (SString name
) {
17048 if (auto const mte
= folly::get_ptr(cinfo
->methods
, name
)) {
17049 return mte
->noOverrideRegular();
17054 std::vector
<std::pair
<SString
, FuncFamilyOrSingle
>> entries
;
17055 std::vector
<std::pair
<SString
, FuncFamilyOrSingle
>> aux
;
17056 for (auto const& [name
, entry
] : rcinfo
->methodFamilies
) {
17057 assertx(!is_special_method_name(name
));
17059 auto expanded
= false;
17060 if (!cinfo
->methods
.count(name
)) {
17061 if (!(cinfo
->cls
->attrs
& (AttrAbstract
|AttrInterface
))) continue;
17062 if (!cinfo
->classGraph
.mightHaveRegularSubclass()) continue;
17063 if (entry
.m_regularIncomplete
|| entry
.m_privateAncestor
) continue;
17064 if (name
== s_construct
.get()) continue;
17066 } else if (noOverride(name
)) {
17072 [&, name
=name
, &entry
=entry
] (const FuncFamilyEntry::BothFF
& e
) {
17073 auto const it
= ffState
.find(e
.m_ff
);
17074 assertx(it
!= end(ffState
));
17075 auto const& state
= it
->second
;
17078 if (state
->m_ff
->m_regular
.empty()) return;
17079 if (state
->m_ff
->m_regular
.size() == 1) {
17080 entries
.emplace_back(
17082 FuncFamilyOrSingle
{
17083 func_from_meth_ref(index
, state
->m_ff
->m_regular
[0]),
17089 entries
.emplace_back(
17091 FuncFamilyOrSingle
{
17092 state
->expanded(index
),
17099 entries
.emplace_back(
17101 FuncFamilyOrSingle
{
17102 state
->notExpanded(index
),
17103 entry
.m_allIncomplete
17107 [&, name
=name
, &entry
=entry
] (const FuncFamilyEntry::FFAndSingle
& e
) {
17109 if (e
.m_nonRegularPrivate
) return;
17110 entries
.emplace_back(
17112 FuncFamilyOrSingle
{
17113 func_from_meth_ref(index
, e
.m_regular
),
17120 auto const it
= ffState
.find(e
.m_ff
);
17121 assertx(it
!= end(ffState
));
17123 entries
.emplace_back(
17125 FuncFamilyOrSingle
{
17126 it
->second
->notExpanded(index
),
17127 entry
.m_allIncomplete
17130 if (noOverrideRegular(name
)) return;
17133 FuncFamilyOrSingle
{
17134 func_from_meth_ref(index
, e
.m_regular
),
17139 [&, name
=name
, &entry
=entry
] (const FuncFamilyEntry::FFAndNone
& e
) {
17140 if (expanded
) return;
17141 auto const it
= ffState
.find(e
.m_ff
);
17142 assertx(it
!= end(ffState
));
17144 entries
.emplace_back(
17146 FuncFamilyOrSingle
{
17147 it
->second
->notExpanded(index
),
17148 entry
.m_allIncomplete
17151 if (!noOverrideRegular(name
)) {
17152 aux
.emplace_back(name
, FuncFamilyOrSingle
{});
17155 [&, name
=name
, &entry
=entry
] (const FuncFamilyEntry::BothSingle
& e
) {
17156 if (expanded
&& e
.m_nonRegularPrivate
) return;
17157 entries
.emplace_back(
17159 FuncFamilyOrSingle
{
17160 func_from_meth_ref(index
, e
.m_all
),
17161 !expanded
&& entry
.m_allIncomplete
17165 [&, name
=name
, &entry
=entry
] (const FuncFamilyEntry::SingleAndNone
& e
) {
17166 if (expanded
) return;
17167 entries
.emplace_back(
17169 FuncFamilyOrSingle
{
17170 func_from_meth_ref(index
, e
.m_all
),
17171 entry
.m_allIncomplete
17174 if (!noOverrideRegular(name
)) {
17175 aux
.emplace_back(name
, FuncFamilyOrSingle
{});
17178 [&, &entry
=entry
] (const FuncFamilyEntry::None
&) {
17179 assertx(entry
.m_allIncomplete
);
17184 // Sort the lists of new entries, so we can insert them into the
17185 // method family maps (which are sorted_vector_maps) in bulk.
17187 begin(entries
), end(entries
),
17188 [] (auto const& p1
, auto const& p2
) { return p1
.first
< p2
.first
; }
17191 begin(aux
), end(aux
),
17192 [] (auto const& p1
, auto const& p2
) { return p1
.first
< p2
.first
; }
17194 if (!entries
.empty()) {
17195 cinfo
->methodFamilies
.insert(
17196 folly::sorted_unique
, begin(entries
), end(entries
)
17199 if (!aux
.empty()) {
17200 cinfo
->methodFamiliesAux
.insert(
17201 folly::sorted_unique
, begin(aux
), end(aux
)
17204 cinfo
->methodFamilies
.shrink_to_fit();
17205 cinfo
->methodFamiliesAux
.shrink_to_fit();
17207 if (!rcinfo
->extraMethods
.empty()) {
17208 // This is rare. Only happens with unflattened traits, so
17209 // taking a lock here is fine.
17210 std::lock_guard
<std::mutex
> _
{extraMethodLock
};
17211 auto& extra
= index
.classExtraMethodMap
[cinfo
->cls
];
17212 for (auto const& meth
: rcinfo
->extraMethods
) {
17213 extra
.emplace(func_from_meth_ref(index
, meth
));
17217 // Build the FuncInfo for every method on this class. The
17218 // FuncInfos already have default types, so update them with the
17219 // type from the FuncInfo2. Any class types here will be
17220 // unresolved (will be resolved later).
17221 assertx(cinfo
->cls
->methods
.size() == rcinfo
->funcInfos
.size());
17222 for (size_t i
= 0, size
= cinfo
->cls
->methods
.size(); i
< size
; ++i
) {
17223 auto& func
= cinfo
->cls
->methods
[i
];
17224 auto& rfi
= rcinfo
->funcInfos
[i
];
17225 remote_func_info_to_local(index
, *func
, *rfi
);
17229 for (auto& clo
: rcinfos
->closures
) process(std::move(clo
));
17230 process(std::move(rcinfos
));
17235 remote
.shrink_to_fit();
17237 for (auto const& [name
, entry
] : index
.nameOnlyMethodFamilies
) {
17240 [&, name
=name
] (const FuncFamilyEntry::BothFF
& e
) {
17241 auto const it
= ffState
.find(e
.m_ff
);
17242 assertx(it
!= end(ffState
));
17244 FuncFamilyOrSingle f
{ it
->second
->notExpanded(index
), true };
17245 index
.methodFamilies
.emplace(
17247 IndexData::MethodFamilyEntry
{ f
, f
}
17250 [&, name
=name
] (const FuncFamilyEntry::FFAndSingle
& e
) {
17251 auto const it
= ffState
.find(e
.m_ff
);
17252 assertx(it
!= end(ffState
));
17254 index
.methodFamilies
.emplace(
17256 IndexData::MethodFamilyEntry
{
17257 FuncFamilyOrSingle
{
17258 it
->second
->notExpanded(index
),
17261 FuncFamilyOrSingle
{
17262 func_from_meth_ref(index
, e
.m_regular
),
17268 [&, name
=name
] (const FuncFamilyEntry::FFAndNone
& e
) {
17269 auto const it
= ffState
.find(e
.m_ff
);
17270 assertx(it
!= end(ffState
));
17272 index
.methodFamilies
.emplace(
17274 IndexData::MethodFamilyEntry
{
17275 FuncFamilyOrSingle
{
17276 it
->second
->notExpanded(index
),
17279 FuncFamilyOrSingle
{}
17283 [&, name
=name
] (const FuncFamilyEntry::BothSingle
& e
) {
17284 FuncFamilyOrSingle f
{ func_from_meth_ref(index
, e
.m_all
), true };
17285 index
.methodFamilies
.emplace(
17287 IndexData::MethodFamilyEntry
{ f
, f
}
17290 [&, name
=name
] (const FuncFamilyEntry::SingleAndNone
& e
) {
17291 index
.methodFamilies
.emplace(
17293 IndexData::MethodFamilyEntry
{
17294 FuncFamilyOrSingle
{
17295 func_from_meth_ref(index
, e
.m_all
),
17298 FuncFamilyOrSingle
{}
17302 [&] (const FuncFamilyEntry::None
&) { always_assert(false); }
17307 decltype(index
.nameOnlyMethodFamilies
){}.swap(index
.nameOnlyMethodFamilies
);
17309 // Now that all of the FuncFamilies have been created, generate the
17310 // back links from FuncInfo to their FuncFamilies.
17311 std::vector
<FuncFamily
*> work
;
17312 work
.reserve(index
.funcFamilies
.size());
17313 for (auto const& kv
: index
.funcFamilies
) work
.emplace_back(kv
.first
.get());
17315 // First calculate the needed capacity for each FuncInfo's family
17316 // list. We use this to presize the family list. This is superior
17317 // just pushing back and then shrinking the vectors, as that can
17318 // excessively fragment the heap.
17319 std::vector
<std::atomic
<size_t>> capacities(index
.nextFuncId
);
17321 parallel::for_each(
17323 [&] (FuncFamily
* ff
) {
17324 for (auto const pf
: ff
->possibleFuncs()) {
17325 ++capacities
[pf
.ptr()->idx
];
17330 parallel::for_each(
17332 [&] (FuncInfo
& fi
) {
17333 if (!fi
.func
) return;
17334 fi
.families
.reserve(capacities
[fi
.func
->idx
]);
17337 capacities
.clear();
17338 capacities
.shrink_to_fit();
17340 // Different threads can touch the same FuncInfo when adding to the
17341 // func family list, so use sharded locking scheme.
17342 std::array
<std::mutex
, 256> locks
;
17343 parallel::for_each(
17345 [&] (FuncFamily
* ff
) {
17346 for (auto const pf
: ff
->possibleFuncs()) {
17347 auto finfo
= func_info(index
, pf
.ptr());
17348 auto& lock
= locks
[pointer_hash
<FuncInfo
>{}(finfo
) % locks
.size()];
17349 std::lock_guard
<std::mutex
> _
{lock
};
17350 finfo
->families
.emplace_back(ff
);
17356 // Switch to "local" mode, in which all calculations are expected to
17357 // be done locally (not using extern-worker). This involves
17358 // downloading everything out of extern-worker and converting it. To
17359 // improve efficiency, we first aggregate many small(er) items into
17360 // larger aggregate blobs in external workers, then download the
17362 void make_local(IndexData
& index
) {
17363 trace_time
tracer("make local", index
.sample
);
17365 using namespace folly::gen
;
17367 // These aren't needed below so we can free them immediately.
17368 decltype(index
.funcToClosures
){}.swap(index
.funcToClosures
);
17369 decltype(index
.classToClosures
){}.swap(index
.classToClosures
);
17370 decltype(index
.classesWith86Inits
){}.swap(index
.classesWith86Inits
);
17371 decltype(index
.classToUnit
){}.swap(index
.classToUnit
);
17372 decltype(index
.funcToUnit
){}.swap(index
.funcToUnit
);
17373 decltype(index
.constantToUnit
){}.swap(index
.constantToUnit
);
17374 decltype(index
.constantInitFuncs
){}.swap(index
.constantInitFuncs
);
17375 decltype(index
.unitsWithTypeAliases
){}.swap(index
.unitsWithTypeAliases
);
17376 decltype(index
.closureToFunc
){}.swap(index
.closureToFunc
);
17377 decltype(index
.closureToClass
){}.swap(index
.closureToClass
);
17378 decltype(index
.classToCnsBases
){}.swap(index
.classToCnsBases
);
17380 // Unlike other cases, we want to bound each bucket to roughly the
17381 // same total byte size (since ultimately we're going to download
17383 auto const usingSubprocess
= index
.client
->usingSubprocess();
17384 // We can be more aggressive in subprocess mode because there's no
17385 // actual aggregation.
17386 auto const sizePerBucket
= usingSubprocess
17391 * We'll use the names of the various items as the items to
17392 * bucketize. This is somewhat problematic because names between
17393 * units/funcs/classes/class-infos can collide (indeed classes and
17394 * class-infos will always collide). Adding to the complication is
17395 * that some of these are case sensitive and some aren't.
17397 * We'll store a case sensitive version of each name exactly *once*,
17398 * using a seen set. Since the hash for a static string (which
17399 * consistently_bucketize() uses) is case insensitive, all case
17400 * sensitive versions of the same name will always hash to the same
17403 * When we obtain the Refs for a corresponding bucket, we'll load
17404 * all items with that given name, using a set to ensure each RefId
17405 * is only used once.
17408 SStringToOneT
<Ref
<FuncFamilyGroup
>> nameToFuncFamilyGroup
;
17409 auto const& [items
, totalSize
] = [&] {
17411 std::vector
<SString
> items
;
17412 size_t totalSize
= 0;
17414 index
.unitRefs
.size() + index
.classRefs
.size() +
17415 index
.classInfoRefs
.size() + index
.funcRefs
.size() +
17416 index
.funcInfoRefs
.size() + index
.funcFamilyRefs
.size() +
17417 index
.uninstantiableClsMethRefs
.size()
17419 for (auto const& [name
, ref
] : index
.unitRefs
) {
17420 totalSize
+= ref
.id().m_size
;
17421 if (!seen
.emplace(name
).second
) continue;
17422 items
.emplace_back(name
);
17424 for (auto const& [name
, ref
] : index
.classRefs
) {
17425 totalSize
+= ref
.id().m_size
;
17426 if (!seen
.emplace(name
).second
) continue;
17427 items
.emplace_back(name
);
17429 for (auto const& [name
, ref
] : index
.classInfoRefs
) {
17430 totalSize
+= ref
.id().m_size
;
17431 if (!seen
.emplace(name
).second
) continue;
17432 items
.emplace_back(name
);
17434 for (auto const& [name
, ref
] : index
.classBytecodeRefs
) {
17435 totalSize
+= ref
.id().m_size
;
17436 if (!seen
.emplace(name
).second
) continue;
17437 items
.emplace_back(name
);
17439 for (auto const& [name
, ref
] : index
.funcRefs
) {
17440 totalSize
+= ref
.id().m_size
;
17441 if (!seen
.emplace(name
).second
) continue;
17442 items
.emplace_back(name
);
17444 for (auto const& [name
, ref
] : index
.funcInfoRefs
) {
17445 totalSize
+= ref
.id().m_size
;
17446 if (!seen
.emplace(name
).second
) continue;
17447 items
.emplace_back(name
);
17449 for (auto const& [name
, ref
] : index
.funcBytecodeRefs
) {
17450 totalSize
+= ref
.id().m_size
;
17451 if (!seen
.emplace(name
).second
) continue;
17452 items
.emplace_back(name
);
17454 for (auto const& [name
, ref
] : index
.uninstantiableClsMethRefs
) {
17455 totalSize
+= ref
.id().m_size
;
17456 if (!seen
.emplace(name
).second
) continue;
17457 items
.emplace_back(name
);
17460 for (auto const& [_
, ref
] : index
.funcFamilyRefs
) {
17461 auto const name
= makeStaticString(ref
.id().toString());
17462 nameToFuncFamilyGroup
.emplace(name
, ref
);
17464 for (auto const& [name
, ref
] : nameToFuncFamilyGroup
) {
17465 totalSize
+= ref
.id().m_size
;
17466 if (!seen
.emplace(name
).second
) continue;
17467 items
.emplace_back(name
);
17469 std::sort(begin(items
), end(items
), string_data_lt
{});
17470 return std::make_pair(items
, totalSize
);
17473 // Back out the number of buckets we want from the total size of
17474 // everything and the target size of a bucket.
17475 auto const numBuckets
= (totalSize
+ sizePerBucket
- 1) / sizePerBucket
;
17476 auto buckets
= consistently_bucketize(
17478 (items
.size() + numBuckets
- 1) / numBuckets
17481 // We're going to be downloading all bytecode, so to avoid wasted
17482 // memory, try to re-use identical bytecode.
17483 Optional
<php::FuncBytecode::Reuser
> reuser
;
17485 php::FuncBytecode::s_reuser
= reuser
.get_pointer();
17486 SCOPE_EXIT
{ php::FuncBytecode::s_reuser
= nullptr; };
17489 auto program
= std::make_unique
<php::Program
>();
17491 // Index stores ClassInfos, not ClassInfo2s, so we need a place to
17492 // store them until we convert it.
17493 std::vector
<std::unique_ptr
<ClassInfo2
>> remoteClassInfos
;
17494 remoteClassInfos
.reserve(index
.classInfoRefs
.size());
17496 std::vector
<std::unique_ptr
<FuncInfo2
>> remoteFuncInfos
;
17497 remoteFuncInfos
.reserve(index
.funcInfoRefs
.size());
17499 std::vector
<std::unique_ptr
<MethodsWithoutCInfo
>> remoteMethInfos
;
17500 remoteMethInfos
.reserve(index
.uninstantiableClsMethRefs
.size());
17502 hphp_fast_set
<FuncFamily2::Id
> remoteFuncFamilyIds
;
17503 std::vector
<std::unique_ptr
<FuncFamily2
>> remoteFuncFamilies
;
17504 remoteFuncFamilies
.reserve(index
.funcFamilyRefs
.size());
17506 auto const run
= [&] (std::vector
<SString
> chunks
) -> coro::Task
<void> {
17507 co_await
coro::co_reschedule_on_current_executor
;
17509 if (chunks
.empty()) co_return
;
17511 std::vector
<UniquePtrRef
<php::Class
>> classes
;
17512 std::vector
<UniquePtrRef
<ClassInfo2
>> classInfos
;
17513 std::vector
<UniquePtrRef
<php::ClassBytecode
>> classBytecode
;
17514 std::vector
<UniquePtrRef
<php::Func
>> funcs
;
17515 std::vector
<UniquePtrRef
<FuncInfo2
>> funcInfos
;
17516 std::vector
<UniquePtrRef
<php::FuncBytecode
>> funcBytecode
;
17517 std::vector
<UniquePtrRef
<php::Unit
>> units
;
17518 std::vector
<Ref
<FuncFamilyGroup
>> funcFamilies
;
17519 std::vector
<UniquePtrRef
<MethodsWithoutCInfo
>> methInfos
;
17521 // Since a name can map to multiple items, and different case
17522 // sensitive version of the same name can appear in the same
17523 // bucket, use a set to make sure any given RefId only included
17524 // once. A set of case insensitive identical names will always
17525 // appear in the same bucket, so there's no need to track
17526 // duplicates globally.
17527 hphp_fast_set
<RefId
, RefId::Hasher
> ids
;
17529 for (auto const name
: chunks
) {
17530 if (auto const r
= folly::get_optional(index
.unitRefs
, name
)) {
17531 if (ids
.emplace(r
->id()).second
) {
17532 units
.emplace_back(*r
);
17535 if (auto const r
= folly::get_optional(index
.classRefs
, name
)) {
17536 auto const bytecode
= index
.classBytecodeRefs
.at(name
);
17537 if (ids
.emplace(r
->id()).second
) {
17538 classes
.emplace_back(*r
);
17539 classBytecode
.emplace_back(bytecode
);
17542 if (auto const r
= folly::get_optional(index
.classInfoRefs
, name
)) {
17543 if (ids
.emplace(r
->id()).second
) {
17544 classInfos
.emplace_back(*r
);
17547 if (auto const r
= folly::get_optional(index
.funcRefs
, name
)) {
17548 auto const bytecode
= index
.funcBytecodeRefs
.at(name
);
17549 if (ids
.emplace(r
->id()).second
) {
17550 funcs
.emplace_back(*r
);
17551 funcBytecode
.emplace_back(bytecode
);
17554 if (auto const r
= folly::get_optional(index
.funcInfoRefs
, name
)) {
17555 if (ids
.emplace(r
->id()).second
) {
17556 funcInfos
.emplace_back(*r
);
17559 if (auto const r
= folly::get_optional(nameToFuncFamilyGroup
, name
)) {
17560 if (ids
.emplace(r
->id()).second
) {
17561 funcFamilies
.emplace_back(*r
);
17564 if (auto const r
= folly::get_optional(index
.uninstantiableClsMethRefs
,
17566 if (ids
.emplace(r
->id()).second
) {
17567 methInfos
.emplace_back(*r
);
17572 AggregateJob::Bundle chunk
;
17573 if (!usingSubprocess
) {
17574 Client::ExecMetadata metadata
{
17575 .job_key
= folly::sformat("aggregate {}", chunks
[0])
17578 // Aggregate the data, which makes downloading it more efficient.
17579 auto config
= co_await index
.configRef
->getCopy();
17580 auto outputs
= co_await index
.client
->exec(
17585 std::move(classes
),
17586 std::move(classInfos
),
17587 std::move(classBytecode
),
17589 std::move(funcInfos
),
17590 std::move(funcBytecode
),
17592 std::move(funcFamilies
),
17593 std::move(methInfos
)
17596 std::move(metadata
)
17598 assertx(outputs
.size() == 1);
17600 // Download the aggregate chunks.
17601 chunk
= co_await index
.client
->load(std::move(outputs
[0]));
17603 // If we're using subprocess mode, we don't need to aggregate
17604 // and we can just download the items directly.
17605 auto [c
, cinfo
, cbc
, f
, finfo
, fbc
, u
, ff
, minfo
] =
17606 co_await
coro::collectAll(
17607 index
.client
->load(std::move(classes
)),
17608 index
.client
->load(std::move(classInfos
)),
17609 index
.client
->load(std::move(classBytecode
)),
17610 index
.client
->load(std::move(funcs
)),
17611 index
.client
->load(std::move(funcInfos
)),
17612 index
.client
->load(std::move(funcBytecode
)),
17613 index
.client
->load(std::move(units
)),
17614 index
.client
->load(std::move(funcFamilies
)),
17615 index
.client
->load(std::move(methInfos
))
17617 chunk
.classes
.insert(
17618 end(chunk
.classes
),
17619 std::make_move_iterator(begin(c
)),
17620 std::make_move_iterator(end(c
))
17622 chunk
.classInfos
.insert(
17623 end(chunk
.classInfos
),
17624 std::make_move_iterator(begin(cinfo
)),
17625 std::make_move_iterator(end(cinfo
))
17627 chunk
.classBytecode
.insert(
17628 end(chunk
.classBytecode
),
17629 std::make_move_iterator(begin(cbc
)),
17630 std::make_move_iterator(end(cbc
))
17632 chunk
.funcs
.insert(
17634 std::make_move_iterator(begin(f
)),
17635 std::make_move_iterator(end(f
))
17637 chunk
.funcInfos
.insert(
17638 end(chunk
.funcInfos
),
17639 std::make_move_iterator(begin(finfo
)),
17640 std::make_move_iterator(end(finfo
))
17642 chunk
.funcBytecode
.insert(
17643 end(chunk
.funcBytecode
),
17644 std::make_move_iterator(begin(fbc
)),
17645 std::make_move_iterator(end(fbc
))
17647 chunk
.units
.insert(
17649 std::make_move_iterator(begin(u
)),
17650 std::make_move_iterator(end(u
))
17652 chunk
.funcFamilies
.insert(
17653 end(chunk
.funcFamilies
),
17654 std::make_move_iterator(begin(ff
)),
17655 std::make_move_iterator(end(ff
))
17657 chunk
.methInfos
.insert(
17658 end(chunk
.methInfos
),
17659 std::make_move_iterator(begin(minfo
)),
17660 std::make_move_iterator(end(minfo
))
17664 always_assert(chunk
.classBytecode
.size() == chunk
.classes
.size());
17665 for (size_t i
= 0, size
= chunk
.classes
.size(); i
< size
; ++i
) {
17666 auto& bc
= chunk
.classBytecode
[i
];
17667 auto& cls
= chunk
.classes
[i
];
17670 for (auto& meth
: cls
->methods
) {
17671 assertx(bcIdx
< bc
->methodBCs
.size());
17672 auto& methBC
= bc
->methodBCs
[bcIdx
++];
17673 always_assert(methBC
.name
== meth
->name
);
17674 always_assert(!meth
->rawBlocks
);
17675 meth
->rawBlocks
= std::move(methBC
.bc
);
17677 for (auto& clo
: cls
->closures
) {
17678 assertx(bcIdx
< bc
->methodBCs
.size());
17679 auto& methBC
= bc
->methodBCs
[bcIdx
++];
17680 assertx(clo
->methods
.size() == 1);
17681 always_assert(methBC
.name
== clo
->methods
[0]->name
);
17682 always_assert(!clo
->methods
[0]->rawBlocks
);
17683 clo
->methods
[0]->rawBlocks
= std::move(methBC
.bc
);
17685 assertx(bcIdx
== bc
->methodBCs
.size());
17687 chunk
.classBytecode
.clear();
17689 always_assert(chunk
.funcBytecode
.size() == chunk
.funcs
.size());
17690 for (size_t i
= 0, size
= chunk
.funcs
.size(); i
< size
; ++i
) {
17691 auto& bytecode
= chunk
.funcBytecode
[i
];
17692 chunk
.funcs
[i
]->rawBlocks
= std::move(bytecode
->bc
);
17694 chunk
.funcBytecode
.clear();
17696 // And add it to our php::Program.
17698 std::scoped_lock
<std::mutex
> _
{lock
};
17699 for (auto& unit
: chunk
.units
) {
17700 // Local execution doesn't need the native unit, so strip it
17702 if (is_native_unit(*unit
)) continue;
17703 program
->units
.emplace_back(std::move(unit
));
17705 for (auto& cls
: chunk
.classes
) {
17706 program
->classes
.emplace_back(std::move(cls
));
17708 for (auto& func
: chunk
.funcs
) {
17709 program
->funcs
.emplace_back(std::move(func
));
17711 remoteClassInfos
.insert(
17712 end(remoteClassInfos
),
17713 std::make_move_iterator(begin(chunk
.classInfos
)),
17714 std::make_move_iterator(end(chunk
.classInfos
))
17716 remoteFuncInfos
.insert(
17717 end(remoteFuncInfos
),
17718 std::make_move_iterator(begin(chunk
.funcInfos
)),
17719 std::make_move_iterator(end(chunk
.funcInfos
))
17721 remoteMethInfos
.insert(
17722 end(remoteMethInfos
),
17723 std::make_move_iterator(begin(chunk
.methInfos
)),
17724 std::make_move_iterator(end(chunk
.methInfos
))
17726 for (auto& group
: chunk
.funcFamilies
) {
17727 for (auto& ff
: group
.m_ffs
) {
17728 if (remoteFuncFamilyIds
.emplace(ff
->m_id
).second
) {
17729 remoteFuncFamilies
.emplace_back(std::move(ff
));
17738 // We're going to load ClassGraphs concurrently.
17739 ClassGraph::initConcurrent();
17742 // Temporarily suppress case collision logging
17743 auto oldTypeLogLevel
= Cfg::Eval::LogTsameCollisions
;
17744 Cfg::Eval::LogTsameCollisions
= 0;
17746 Cfg::Eval::LogTsameCollisions
= oldTypeLogLevel
;
17749 coro::blockingWait(coro::collectAllRange(
17752 | map([&] (std::vector
<SString
> chunks
) {
17753 return run(std::move(chunks
)).scheduleOn(index
.executor
->sticky());
17755 | as
<std::vector
>()
17759 // Deserialization done.
17760 ClassGraph::stopConcurrent();
17762 // We've used any refs we need. Free them now to save memory.
17763 decltype(index
.unitRefs
){}.swap(index
.unitRefs
);
17764 decltype(index
.classRefs
){}.swap(index
.classRefs
);
17765 decltype(index
.funcRefs
){}.swap(index
.funcRefs
);
17766 decltype(index
.classInfoRefs
){}.swap(index
.classInfoRefs
);
17767 decltype(index
.funcInfoRefs
){}.swap(index
.funcInfoRefs
);
17768 decltype(index
.funcFamilyRefs
){}.swap(index
.funcFamilyRefs
);
17769 decltype(index
.classBytecodeRefs
){}.swap(index
.classBytecodeRefs
);
17770 decltype(index
.funcBytecodeRefs
){}.swap(index
.funcBytecodeRefs
);
17771 decltype(index
.uninstantiableClsMethRefs
){}.swap(
17772 index
.uninstantiableClsMethRefs
17775 // Done with any extern-worker stuff at this point:
17776 index
.configRef
.reset();
17780 index
.client
->getStats().toString(
17783 "{:,} units, {:,} classes, {:,} class-infos, {:,} funcs",
17784 program
->units
.size(),
17785 program
->classes
.size(),
17786 remoteClassInfos
.size(),
17787 program
->funcs
.size()
17792 if (index
.sample
) {
17793 index
.client
->getStats().logSample("hhbbc", *index
.sample
);
17796 index
.disposeClient(
17797 std::move(index
.executor
),
17798 std::move(index
.client
)
17800 index
.disposeClient
= decltype(index
.disposeClient
){};
17802 php::FuncBytecode::s_reuser
= nullptr;
17806 nameToFuncFamilyGroup
.clear();
17807 remoteFuncFamilyIds
.clear();
17809 program
->units
.shrink_to_fit();
17810 program
->classes
.shrink_to_fit();
17811 program
->funcs
.shrink_to_fit();
17812 index
.program
= std::move(program
);
17814 // For now we don't require system constants in any extern-worker
17815 // stuff we do. So we can just add it to the Index now.
17816 add_system_constants_to_index(index
);
17818 // Buid Index data structures from the php::Program.
17819 add_program_to_index(index
);
17821 // Convert the FuncInfo2s into FuncInfos.
17822 make_func_infos_local(
17824 std::move(remoteFuncInfos
)
17827 // Convert the ClassInfo2s into ClassInfos.
17828 make_class_infos_local(
17830 std::move(remoteClassInfos
),
17831 std::move(remoteFuncFamilies
)
17834 // Convert any "orphan" FuncInfo2s (those representing methods for a
17835 // class without a ClassInfo).
17836 parallel::for_each(
17838 [&] (std::unique_ptr
<MethodsWithoutCInfo
>& meths
) {
17839 auto const cls
= folly::get_default(index
.classes
, meths
->cls
);
17840 always_assert_flog(
17842 "php::Class for {} not found in index",
17845 assertx(cls
->methods
.size() == meths
->finfos
.size());
17846 for (size_t i
= 0, size
= cls
->methods
.size(); i
< size
; ++i
) {
17847 remote_func_info_to_local(index
, *cls
->methods
[i
], *meths
->finfos
[i
]);
17850 assertx(cls
->closures
.size() == meths
->closureInvokes
.size());
17851 for (size_t i
= 0, size
= cls
->closures
.size(); i
< size
; ++i
) {
17852 auto const& clo
= cls
->closures
[i
];
17853 assertx(clo
->methods
.size() == 1);
17854 remote_func_info_to_local(
17857 *meths
->closureInvokes
[i
]
17863 // Ensure that all classes are unserialized since all local
17864 // processing requires that.
17866 trace_time tracer2
{"unserialize classes"};
17867 tracer2
.ignore_client_stats();
17869 parallel::for_each(
17870 index
.allClassInfos
,
17871 [&] (std::unique_ptr
<ClassInfo
>& cinfo
) {
17872 for (auto& [ty
, _
] : cinfo
->clsConstTypes
) {
17873 ty
= unserialize_classes(
17874 IndexAdaptor
{ *index
.m_index
},
17881 parallel::for_each(
17883 [&] (FuncInfo
& fi
) {
17884 if (!fi
.func
) return;
17885 fi
.returnTy
= unserialize_classes(
17886 IndexAdaptor
{ *index
.m_index
},
17887 std::move(fi
.returnTy
)
17893 //////////////////////////////////////////////////////////////////////
17897 //////////////////////////////////////////////////////////////////////
17899 std::vector
<SString
> Index::Input::makeDeps(const php::Class
& cls
) {
17900 std::vector
<SString
> deps
;
17901 if (cls
.parentName
) deps
.emplace_back(cls
.parentName
);
17902 deps
.insert(deps
.end(), cls
.interfaceNames
.begin(), cls
.interfaceNames
.end());
17903 deps
.insert(deps
.end(), cls
.usedTraitNames
.begin(), cls
.usedTraitNames
.end());
17906 cls
.includedEnumNames
.begin(),
17907 cls
.includedEnumNames
.end()
17912 //////////////////////////////////////////////////////////////////////
17914 Index::Index(Input input
,
17916 std::unique_ptr
<TicketExecutor
> executor
,
17917 std::unique_ptr
<Client
> client
,
17918 DisposeCallback dispose
,
17919 StructuredLogEntry
* sample
)
17920 : m_data
{std::make_unique
<IndexData
>(this)}
17922 trace_time
tracer("create index", sample
);
17923 m_data
->sample
= sample
;
17925 auto flattenMeta
= make_remote(
17929 std::move(executor
),
17934 flatten_type_mappings(*m_data
, flattenMeta
);
17935 auto [subclassMeta
, initTypesMeta
, ifaceConflicts
] =
17936 flatten_classes(*m_data
, std::move(flattenMeta
));
17937 build_subclass_lists(*m_data
, std::move(subclassMeta
), initTypesMeta
);
17938 init_types(*m_data
, std::move(initTypesMeta
));
17939 compute_iface_vtables(*m_data
, std::move(ifaceConflicts
));
17940 check_invariants(*m_data
);
17943 // Defined here so IndexData is a complete type for the unique_ptr
17945 Index::~Index() = default;
17947 Index::Index(Index
&&) = default;
17948 Index
& Index::operator=(Index
&&) = default;
17950 //////////////////////////////////////////////////////////////////////
17952 const php::Program
& Index::program() const {
17953 return *m_data
->program
;
17956 StructuredLogEntry
* Index::sample() const {
17957 return m_data
->sample
;
17960 //////////////////////////////////////////////////////////////////////
17962 TicketExecutor
& Index::executor() const {
17963 return *m_data
->executor
;
17966 Client
& Index::client() const {
17967 return *m_data
->client
;
17970 const CoroAsyncValue
<Ref
<Config
>>& Index::configRef() const {
17971 return *m_data
->configRef
;
17974 //////////////////////////////////////////////////////////////////////
17976 const TSStringSet
& Index::classes_with_86inits() const {
17977 return m_data
->classesWith86Inits
;
17980 const FSStringSet
& Index::constant_init_funcs() const {
17981 return m_data
->constantInitFuncs
;
17984 const SStringSet
& Index::units_with_type_aliases() const {
17985 return m_data
->unitsWithTypeAliases
;
17988 //////////////////////////////////////////////////////////////////////
17990 void Index::make_local() {
17991 HHBBC::make_local(*m_data
);
17992 check_local_invariants(*m_data
);
17995 //////////////////////////////////////////////////////////////////////
17997 const php::Class
* Index::lookup_closure_context(const php::Class
& cls
) const {
17998 if (!cls
.closureContextCls
) return &cls
;
17999 return m_data
->classes
.at(cls
.closureContextCls
);
18002 const php::Unit
* Index::lookup_func_unit(const php::Func
& func
) const {
18003 return m_data
->units
.at(func
.unit
);
18006 const php::Unit
* Index::lookup_func_original_unit(const php::Func
& func
) const {
18007 auto const unit
= func
.originalUnit
? func
.originalUnit
: func
.unit
;
18008 return m_data
->units
.at(unit
);
18011 const php::Unit
* Index::lookup_class_unit(const php::Class
& cls
) const {
18012 return m_data
->units
.at(cls
.unit
);
18015 const php::Class
* Index::lookup_const_class(const php::Const
& cns
) const {
18016 return m_data
->classes
.at(cns
.cls
);
18019 const php::Class
* Index::lookup_class(SString name
) const {
18020 return folly::get_default(m_data
->classes
, name
);
18023 //////////////////////////////////////////////////////////////////////
18025 void Index::for_each_unit_func(const php::Unit
& unit
,
18026 std::function
<void(const php::Func
&)> f
) const {
18027 for (auto const func
: unit
.funcs
) {
18028 f(*m_data
->funcs
.at(func
));
18032 void Index::for_each_unit_func_mutable(php::Unit
& unit
,
18033 std::function
<void(php::Func
&)> f
) {
18034 for (auto const func
: unit
.funcs
) {
18035 f(*m_data
->funcs
.at(func
));
18039 void Index::for_each_unit_class(
18040 const php::Unit
& unit
,
18041 std::function
<void(const php::Class
&)> f
) const {
18042 for (auto const cls
: unit
.classes
) {
18043 f(*m_data
->classes
.at(cls
));
18047 void Index::for_each_unit_class_mutable(php::Unit
& unit
,
18048 std::function
<void(php::Class
&)> f
) {
18049 for (auto const cls
: unit
.classes
) {
18050 f(*m_data
->classes
.at(cls
));
18054 //////////////////////////////////////////////////////////////////////
18056 void Index::preresolve_type_structures() {
18057 trace_time
tracer("pre-resolve type-structures", m_data
->sample
);
18059 // Now that everything has been updated, calculate the invariance
18060 // for each resolved type-structure. For each class constant,
18061 // examine all subclasses and see how the resolved type-structure
18063 parallel::for_each(
18064 m_data
->allClassInfos
,
18065 [&] (std::unique_ptr
<ClassInfo
>& cinfo
) {
18066 if (!cinfo
->classGraph
.hasCompleteChildren()) return;
18068 for (auto& cns
: const_cast<php::Class
*>(cinfo
->cls
)->constants
) {
18069 assertx(cns
.invariance
== php::Const::Invariance::None
);
18070 if (cns
.kind
!= ConstModifiers::Kind::Type
) continue;
18071 if (!cns
.val
.has_value()) continue;
18072 if (!cns
.resolvedTypeStructure
) continue;
18074 auto const checkClassname
=
18075 tvIsString(cns
.resolvedTypeStructure
->get(s_classname
));
18077 // Assume it doesn't change
18078 auto invariance
= php::Const::Invariance::Same
;
18079 for (auto const g
: cinfo
->classGraph
.children()) {
18080 assertx(!g
.isMissing());
18081 assertx(g
.hasCompleteChildren());
18082 auto const s
= g
.cinfo();
18084 assertx(invariance
!= php::Const::Invariance::None
);
18086 IMPLIES(!checkClassname
,
18087 invariance
!= php::Const::Invariance::ClassnamePresent
)
18089 if (s
== cinfo
.get()) continue;
18091 auto const it
= s
->clsConstants
.find(cns
.name
);
18092 assertx(it
!= s
->clsConstants
.end());
18093 if (it
->second
.cls
!= s
->cls
) continue;
18094 auto const& scns
= *it
->second
;
18096 // Overridden in some strange way. Be pessimistic.
18097 if (!scns
.val
.has_value() ||
18098 scns
.kind
!= ConstModifiers::Kind::Type
) {
18099 invariance
= php::Const::Invariance::None
;
18103 // The resolved type structure in this subclass is not the
18105 if (scns
.resolvedTypeStructure
!= cns
.resolvedTypeStructure
) {
18106 if (!scns
.resolvedTypeStructure
) {
18107 // It's not even resolved here, so we can't assume
18109 invariance
= php::Const::Invariance::None
;
18112 // We might still be able to assert that a classname is
18113 // always present, or a resolved type structure at least
18115 if (invariance
== php::Const::Invariance::Same
||
18116 invariance
== php::Const::Invariance::ClassnamePresent
) {
18119 tvIsString(scns
.resolvedTypeStructure
->get(s_classname
)))
18120 ? php::Const::Invariance::ClassnamePresent
18121 : php::Const::Invariance::Present
;
18126 if (invariance
!= php::Const::Invariance::None
) {
18127 cns
.invariance
= invariance
;
18134 //////////////////////////////////////////////////////////////////////
18136 const CompactVector
<const php::Class
*>*
18137 Index::lookup_closures(const php::Class
* cls
) const {
18138 auto const it
= m_data
->classClosureMap
.find(cls
);
18139 if (it
!= end(m_data
->classClosureMap
)) {
18140 return &it
->second
;
18145 const hphp_fast_set
<const php::Func
*>*
18146 Index::lookup_extra_methods(const php::Class
* cls
) const {
18147 if (cls
->attrs
& AttrNoExpandTrait
) return nullptr;
18148 auto const it
= m_data
->classExtraMethodMap
.find(cls
);
18149 if (it
!= end(m_data
->classExtraMethodMap
)) {
18150 return &it
->second
;
18155 //////////////////////////////////////////////////////////////////////
18157 Optional
<res::Class
> Index::resolve_class(SString clsName
) const {
18158 clsName
= normalizeNS(clsName
);
18159 auto const it
= m_data
->classInfo
.find(clsName
);
18160 if (it
== end(m_data
->classInfo
)) return std::nullopt
;
18161 return res::Class::get(*it
->second
);
18164 Optional
<res::Class
> Index::resolve_class(const php::Class
& cls
) const {
18165 return resolve_class(cls
.name
);
18168 const php::TypeAlias
* Index::lookup_type_alias(SString name
) const {
18169 auto const it
= m_data
->typeAliases
.find(name
);
18170 if (it
== m_data
->typeAliases
.end()) return nullptr;
18174 Index::ClassOrTypeAlias
Index::lookup_class_or_type_alias(SString name
) const {
18175 auto const rcls
= resolve_class(name
);
18177 auto const cls
= [&] () -> const php::Class
* {
18178 if (auto const ci
= rcls
->cinfo()) return ci
->cls
;
18179 return m_data
->classes
.at(rcls
->name());
18181 return ClassOrTypeAlias
{cls
, nullptr, true};
18183 if (auto const ta
= lookup_type_alias(name
)) {
18184 return ClassOrTypeAlias
{nullptr, ta
, true};
18186 return ClassOrTypeAlias
{nullptr, nullptr, false};
18189 // Given a DCls, return the most specific res::Func for that DCls. For
18190 // intersections, this will call process/general on every component of
18191 // the intersection and combine the results. For non-intersections, it
18192 // will call process/general on the sole member of the DCls. process
18193 // is called to obtain a res::Func from a ClassInfo. If a ClassInfo
18194 // isn't available, general will be called instead.
18195 template <typename P
, typename G
>
18196 res::Func
Index::rfunc_from_dcls(const DCls
& dcls
,
18199 const G
& general
) const {
18200 if (dcls
.isExact() || dcls
.isSub()) {
18201 // If this isn't an intersection, there's only one cinfo to
18202 // process and we're done.
18203 auto const cinfo
= dcls
.cls().cinfo();
18204 if (!cinfo
) return general(dcls
.containsNonRegular());
18205 return process(cinfo
, dcls
.isExact(), dcls
.containsNonRegular());
18208 if (dcls
.isIsectAndExact()) {
18209 // Even though this has an intersection list, it always must be
18210 // the exact class, so it sufficies to provide that.
18211 auto const e
= dcls
.isectAndExact().first
;
18212 auto const cinfo
= e
.cinfo();
18213 if (!cinfo
) return general(dcls
.containsNonRegular());
18214 return process(cinfo
, true, dcls
.containsNonRegular());
18218 * Otherwise get a res::Func for all members of the intersection and
18219 * combine them together. Since the DCls represents a class which is
18220 * a subtype of every ClassInfo in the list, every res::Func we get
18223 * The relevant res::Func types in order from most general to more
18226 * MethodName -> FuncFamily -> MethodOrMissing -> Method -> Missing
18228 * Since every res::Func in the intersection is true, we take the
18229 * res::Func which is most specific. Two different res::Funcs cannot
18230 * be contradict. For example, we shouldn't get a Method and a
18231 * Missing since one implies there's no func and the other implies
18232 * one specific func. Or two different res::Funcs shouldn't resolve
18233 * to two different methods.
18236 assertx(dcls
.isIsect());
18237 using Func
= res::Func
;
18239 auto missing
= TriBool::Maybe
;
18241 const php::Func
* singleMethod
= nullptr;
18243 auto const DEBUG_ONLY allIncomplete
= !debug
|| std::all_of(
18244 begin(dcls
.isect()), end(dcls
.isect()),
18245 [] (res::Class c
) { return !c
.hasCompleteChildren(); }
18248 for (auto const i
: dcls
.isect()) {
18249 auto const cinfo
= i
.cinfo();
18250 if (!cinfo
) continue;
18252 auto const func
= process(cinfo
, false, dcls
.containsNonRegular());
18255 [&] (Func::MethodName
) {},
18256 [&] (Func::Method m
) {
18257 if (singleMethod
) {
18258 assertx(missing
!= TriBool::Yes
);
18259 assertx(isect
.families
.empty());
18260 if (singleMethod
!= m
.finfo
->func
) {
18261 assertx(allIncomplete
);
18262 singleMethod
= nullptr;
18263 missing
= TriBool::Yes
;
18265 missing
= TriBool::No
;
18267 } else if (missing
!= TriBool::Yes
) {
18268 singleMethod
= m
.finfo
->func
;
18269 isect
.families
.clear();
18270 missing
= TriBool::No
;
18273 [&] (Func::MethodFamily fam
) {
18274 if (missing
== TriBool::Yes
) {
18275 assertx(!singleMethod
);
18276 assertx(isect
.families
.empty());
18279 if (singleMethod
) {
18280 assertx(missing
!= TriBool::Yes
);
18281 assertx(isect
.families
.empty());
18284 assertx(missing
== TriBool::Maybe
);
18285 isect
.families
.emplace_back(fam
.family
);
18286 isect
.regularOnly
|= fam
.regularOnly
;
18288 [&] (Func::MethodOrMissing m
) {
18289 if (singleMethod
) {
18290 assertx(missing
!= TriBool::Yes
);
18291 assertx(isect
.families
.empty());
18292 if (singleMethod
!= m
.finfo
->func
) {
18293 assertx(allIncomplete
);
18294 singleMethod
= nullptr;
18295 missing
= TriBool::Yes
;
18297 } else if (missing
!= TriBool::Yes
) {
18298 singleMethod
= m
.finfo
->func
;
18299 isect
.families
.clear();
18302 [&] (Func::MissingMethod
) {
18303 assertx(IMPLIES(missing
== TriBool::No
, allIncomplete
));
18304 singleMethod
= nullptr;
18305 isect
.families
.clear();
18306 missing
= TriBool::Yes
;
18308 [&] (Func::FuncName
) { always_assert(false); },
18309 [&] (Func::Fun
) { always_assert(false); },
18310 [&] (Func::Fun2
) { always_assert(false); },
18311 [&] (Func::Method2
) { always_assert(false); },
18312 [&] (Func::MethodFamily2
) { always_assert(false); },
18313 [&] (Func::MethodOrMissing2
) { always_assert(false); },
18314 [&] (Func::MissingFunc
) { always_assert(false); },
18315 [&] (const Func::Isect
&) { always_assert(false); },
18316 [&] (const Func::Isect2
&) { always_assert(false); }
18320 // If we got a method, that always wins. Again, every res::Func is
18321 // true, and method is more specific than a FuncFamily, so it is
18323 if (singleMethod
) {
18324 assertx(missing
!= TriBool::Yes
);
18325 // If missing is Maybe, then *every* resolution was to a
18326 // MethodName or MethodOrMissing, so include that fact here by
18327 // using MethodOrMissing.
18328 if (missing
== TriBool::Maybe
) {
18330 Func::MethodOrMissing
{ func_info(*m_data
, singleMethod
) }
18333 return Func
{ Func::Method
{ func_info(*m_data
, singleMethod
) } };
18335 // We only got unresolved classes. If missing is TriBool::Yes, the
18336 // function doesn't exist. Otherwise be pessimistic.
18337 if (isect
.families
.empty()) {
18338 if (missing
== TriBool::Yes
) {
18339 return Func
{ Func::MissingMethod
{ dcls
.smallestCls().name(), name
} };
18341 assertx(missing
== TriBool::Maybe
);
18342 return general(dcls
.containsNonRegular());
18344 // Isect case. Isects always might contain missing funcs.
18345 assertx(missing
== TriBool::Maybe
);
18347 // We could add a FuncFamily multiple times, so remove duplicates.
18348 std::sort(begin(isect
.families
), end(isect
.families
));
18349 isect
.families
.erase(
18350 std::unique(begin(isect
.families
), end(isect
.families
)),
18351 end(isect
.families
)
18353 // If everything simplifies down to a single FuncFamily, just use
18355 if (isect
.families
.size() == 1) {
18356 return Func
{ Func::MethodFamily
{ isect
.families
[0], isect
.regularOnly
} };
18358 return Func
{ std::move(isect
) };
18361 res::Func
Index::resolve_method(Context ctx
,
18362 const Type
& thisType
,
18363 SString name
) const {
18364 assertx(thisType
.subtypeOf(BCls
) || thisType
.subtypeOf(BObj
));
18366 using Func
= res::Func
;
18369 * Without using the class type, try to infer a set of methods
18370 * using just the method name. This will, naturally, not produce
18371 * as precise a set as when using the class type, but it's better
18372 * than nothing. For all of these results, we need to include the
18373 * possibility of the method not existing (we cannot rule that out
18374 * for this situation).
18376 auto const general
= [&] (bool includeNonRegular
, SString maybeCls
) {
18377 assertx(name
!= s_construct
.get());
18379 // We don't produce name-only global func families for special
18380 // methods, so be conservative. We don't call special methods in a
18381 // context where we'd expect to not know the class, so it's not
18382 // worthwhile. The same goes for __invoke and __debuginfo, which
18383 // is corresponds to every closure, and gets too large without
18385 if (!has_name_only_func_family(name
)) {
18386 return Func
{ Func::MethodName
{ maybeCls
, name
} };
18389 // Lookup up the name-only global func families for this name. If
18390 // we don't have one, the method cannot exist because it contains
18391 // every method with that name in the program.
18392 auto const famIt
= m_data
->methodFamilies
.find(name
);
18393 if (famIt
== end(m_data
->methodFamilies
)) {
18394 return Func
{ Func::MissingMethod
{ maybeCls
, name
} };
18397 // The entry exists. Consult the correct data in it, depending on
18398 // whether we're including non-regular classes or not.
18399 auto const& entry
= includeNonRegular
18400 ? famIt
->second
.m_all
18401 : famIt
->second
.m_regular
;
18402 assertx(entry
.isEmpty() || entry
.isIncomplete());
18404 if (auto const ff
= entry
.funcFamily()) {
18405 return Func
{ Func::MethodFamily
{ ff
, !includeNonRegular
} };
18406 } else if (auto const f
= entry
.func()) {
18407 return Func
{ Func::MethodOrMissing
{ func_info(*m_data
, f
) } };
18409 return Func
{ Func::MissingMethod
{ maybeCls
, name
} };
18413 auto const process
= [&] (ClassInfo
* cinfo
,
18415 bool includeNonRegular
) {
18416 assertx(name
!= s_construct
.get());
18418 auto const methIt
= cinfo
->methods
.find(name
);
18419 if (methIt
== end(cinfo
->methods
)) {
18420 // We don't store metadata for special methods, so be
18421 // pessimistic (the lack of a method entry does not mean the
18422 // call might fail at runtme).
18423 if (is_special_method_name(name
)) {
18424 return Func
{ Func::MethodName
{ cinfo
->cls
->name
, name
} };
18426 // We're only considering this class, not it's subclasses. Since
18427 // it doesn't exist here, the resolution will always fail.
18429 return Func
{ Func::MissingMethod
{ cinfo
->cls
->name
, name
} };
18431 // The method isn't present on this class, but it might be in
18432 // the subclasses. In most cases try a general lookup to get a
18433 // slightly better type than nothing.
18434 if (includeNonRegular
||
18435 !(cinfo
->cls
->attrs
& (AttrInterface
|AttrAbstract
))) {
18436 return general(includeNonRegular
, cinfo
->cls
->name
);
18439 // A special case is if we're only considering regular classes,
18440 // and this is an interface or abstract class. For those, we
18441 // "expand" the method families table to include any methods
18442 // defined in *all* regular subclasses. This is needed to
18443 // preserve monotonicity. Check this now.
18444 auto const famIt
= cinfo
->methodFamilies
.find(name
);
18445 // If no entry, treat it pessimistically like the rest of the
18447 if (famIt
== end(cinfo
->methodFamilies
)) {
18448 return general(false, cinfo
->cls
->name
);
18451 // We found an entry. This cannot be empty (remember the method
18452 // is guaranteed to exist on *all* regular subclasses), and must
18453 // be complete (for the same reason). Use it.
18454 auto const& entry
= famIt
->second
;
18455 assertx(!entry
.isEmpty());
18456 assertx(entry
.isComplete());
18457 if (auto const ff
= entry
.funcFamily()) {
18458 return Func
{ Func::MethodFamily
{ ff
, true } };
18459 } else if (auto const func
= entry
.func()) {
18460 return Func
{ Func::Method
{ func_info(*m_data
, func
) } };
18462 always_assert(false);
18465 // The method on this class.
18466 auto const& meth
= methIt
->second
;
18467 auto const ftarget
= func_from_meth_ref(*m_data
, meth
.meth());
18469 // We don't store method family information about special methods
18470 // and they have special inheritance semantics.
18471 if (is_special_method_name(name
)) {
18472 // If we know the class exactly, we can use ftarget.
18473 if (isExact
) return Func
{ Func::Method
{ func_info(*m_data
, ftarget
) } };
18474 // The method isn't overwritten, but they don't inherit, so it
18475 // could be missing.
18476 if (meth
.attrs
& AttrNoOverride
) {
18477 return Func
{ Func::MethodOrMissing
{ func_info(*m_data
, ftarget
) } };
18479 // Otherwise be pessimistic.
18480 return Func
{ Func::MethodName
{ cinfo
->cls
->name
, name
} };
18483 // Private method handling: Private methods have special lookup
18484 // rules. If we're in the context of a particular class, and that
18485 // class defines a private method, an instance of the class will
18486 // always call that private method (even if overridden) in that
18488 assertx(cinfo
->cls
);
18489 if (ctx
.cls
== cinfo
->cls
) {
18490 // The context matches the current class. If we've looked up a
18491 // private method (defined on this class), then that's what
18493 if ((meth
.attrs
& AttrPrivate
) && meth
.topLevel()) {
18494 return Func
{ Func::Method
{ func_info(*m_data
, ftarget
) } };
18496 } else if ((meth
.attrs
& AttrPrivate
) || meth
.hasPrivateAncestor()) {
18497 // Otherwise the context doesn't match the current class. If the
18498 // looked up method is private, or has a private ancestor,
18499 // there's a chance we'll call that method (or
18500 // ancestor). Otherwise there's no private method in the
18501 // inheritance tree we'll call.
18502 auto const ancestor
= [&] () -> const php::Func
* {
18503 if (!ctx
.cls
) return nullptr;
18504 // Look up the ClassInfo corresponding to the context:
18505 auto const it
= m_data
->classInfo
.find(ctx
.cls
->name
);
18506 if (it
== end(m_data
->classInfo
)) return nullptr;
18507 auto const ctxCInfo
= it
->second
;
18508 // Is this context a parent of our class?
18509 if (!cinfo
->classGraph
.exactSubtypeOf(ctxCInfo
->classGraph
,
18514 // It is. See if it defines a private method.
18515 auto const it2
= ctxCInfo
->methods
.find(name
);
18516 if (it2
== end(ctxCInfo
->methods
)) return nullptr;
18517 auto const& mte
= it2
->second
;
18518 // If it defines a private method, use it.
18519 if ((mte
.attrs
& AttrPrivate
) && mte
.topLevel()) {
18520 return func_from_meth_ref(*m_data
, mte
.meth());
18522 // Otherwise do normal lookup.
18526 return Func
{ Func::Method
{ func_info(*m_data
, ancestor
) } };
18529 // If none of the above cases trigger, we still might call a
18530 // private method (in a child class), but the func-family logic
18531 // below will handle that.
18533 // If we're only including regular subclasses, and this class
18534 // itself isn't regular, the result may not necessarily include
18536 if (!includeNonRegular
&& !is_regular_class(*cinfo
->cls
)) {
18537 // We're not including this base class. If we're exactly this
18538 // class, there's no method at all. It will always be missing.
18540 return Func
{ Func::MissingMethod
{ cinfo
->cls
->name
, name
} };
18542 if (meth
.noOverrideRegular()) {
18543 // The method isn't overridden in a subclass, but we can't
18544 // use the base class either. This leaves two cases. Either
18545 // the method isn't overridden because there are no regular
18546 // subclasses (in which case there's no resolution at all), or
18547 // because there's regular subclasses, but they use the same
18548 // method (in which case the result is just ftarget).
18549 if (!cinfo
->classGraph
.mightHaveRegularSubclass()) {
18550 return Func
{ Func::MissingMethod
{ cinfo
->cls
->name
, name
} };
18552 return Func
{ Func::Method
{ func_info(*m_data
, ftarget
) } };
18554 // We can't use the base class (because it's non-regular), but
18555 // the method is overridden by a regular subclass.
18557 // Since this is a non-regular class and we want the result for
18558 // the regular subset, we need to consult the aux table first.
18559 auto const auxIt
= cinfo
->methodFamiliesAux
.find(name
);
18560 if (auxIt
!= end(cinfo
->methodFamiliesAux
)) {
18561 // Found an entry in the aux table. Use whatever it provides.
18562 auto const& aux
= auxIt
->second
;
18563 if (auto const ff
= aux
.funcFamily()) {
18564 return Func
{ Func::MethodFamily
{ ff
, true } };
18565 } else if (auto const f
= aux
.func()) {
18566 return aux
.isComplete()
18567 ? Func
{ Func::Method
{ func_info(*m_data
, f
) } }
18568 : Func
{ Func::MethodOrMissing
{ func_info(*m_data
, f
) } };
18570 return Func
{ Func::MissingMethod
{ cinfo
->cls
->name
, name
} };
18573 // No entry in the aux table. The result is the same as the
18574 // normal table, so fall through and use that.
18575 } else if (isExact
||
18576 meth
.attrs
& AttrNoOverride
||
18577 (!includeNonRegular
&& meth
.noOverrideRegular())) {
18578 // Either we want all classes, or the base class is regular. If
18579 // the method isn't overridden we know it must be just ftarget
18580 // (the override bits include it being missing in a subclass, so
18581 // we know it cannot be missing either).
18582 return Func
{ Func::Method
{ func_info(*m_data
, ftarget
) } };
18585 // Look up the entry in the normal method family table and use
18586 // whatever is there.
18587 auto const famIt
= cinfo
->methodFamilies
.find(name
);
18588 assertx(famIt
!= end(cinfo
->methodFamilies
));
18589 auto const& fam
= famIt
->second
;
18590 assertx(!fam
.isEmpty());
18592 if (auto const ff
= fam
.funcFamily()) {
18593 return Func
{ Func::MethodFamily
{ ff
, !includeNonRegular
} };
18594 } else if (auto const f
= fam
.func()) {
18595 return (!includeNonRegular
|| fam
.isComplete())
18596 ? Func
{ Func::Method
{ func_info(*m_data
, f
) } }
18597 : Func
{ Func::MethodOrMissing
{ func_info(*m_data
, f
) } };
18599 always_assert(false);
18603 auto const isClass
= thisType
.subtypeOf(BCls
);
18604 if (name
== s_construct
.get()) {
18606 return Func
{ Func::MethodName
{ nullptr, s_construct
.get() } };
18608 return resolve_ctor(thisType
);
18612 if (!is_specialized_cls(thisType
)) return general(true, nullptr);
18613 } else if (!is_specialized_obj(thisType
)) {
18614 return general(false, nullptr);
18617 auto const& dcls
= isClass
? dcls_of(thisType
) : dobj_of(thisType
);
18618 return rfunc_from_dcls(
18622 [&] (bool i
) { return general(i
, dcls
.smallestCls().name()); }
18626 res::Func
Index::resolve_ctor(const Type
& obj
) const {
18627 assertx(obj
.subtypeOf(BObj
));
18629 using Func
= res::Func
;
18631 // Can't say anything useful if we don't know the object type.
18632 if (!is_specialized_obj(obj
)) {
18633 return Func
{ Func::MethodName
{ nullptr, s_construct
.get() } };
18636 auto const& dcls
= dobj_of(obj
);
18637 return rfunc_from_dcls(
18640 [&] (ClassInfo
* cinfo
, bool isExact
, bool includeNonRegular
) {
18641 // We're dealing with an object here, which never uses
18642 // non-regular classes.
18643 assertx(!includeNonRegular
);
18645 // See if this class has a ctor.
18646 auto const methIt
= cinfo
->methods
.find(s_construct
.get());
18647 if (methIt
== end(cinfo
->methods
)) {
18648 // There's no ctor on this class. This doesn't mean the ctor
18649 // won't exist at runtime, it might get the default ctor, so
18650 // we have to be conservative.
18652 Func::MethodName
{ cinfo
->cls
->name
, s_construct
.get() }
18656 // We have a ctor, but it might be overridden in a subclass.
18657 auto const& mte
= methIt
->second
;
18658 assertx(!(mte
.attrs
& AttrStatic
));
18659 auto const ftarget
= func_from_meth_ref(*m_data
, mte
.meth());
18660 assertx(!(ftarget
->attrs
& AttrStatic
));
18662 // If this class is known exactly, or we know nothing overrides
18663 // this ctor, we know this ctor is precisely it.
18664 if (isExact
|| mte
.noOverrideRegular()) {
18665 // If this class isn't regular, and doesn't have any regular
18666 // subclasses (or if it's exact), this resolution will always
18668 if (!is_regular_class(*cinfo
->cls
) &&
18669 (isExact
|| !cinfo
->classGraph
.mightHaveRegularSubclass())) {
18671 Func::MissingMethod
{ cinfo
->cls
->name
, s_construct
.get() }
18674 return Func
{ Func::Method
{ func_info(*m_data
, ftarget
) } };
18677 // If this isn't a regular class, we need to check the "aux"
18678 // entry first (which always has priority when only looking at
18679 // the regular subset).
18680 if (!is_regular_class(*cinfo
->cls
)) {
18681 auto const auxIt
= cinfo
->methodFamiliesAux
.find(s_construct
.get());
18682 if (auxIt
!= end(cinfo
->methodFamiliesAux
)) {
18683 auto const& aux
= auxIt
->second
;
18684 if (auto const ff
= aux
.funcFamily()) {
18685 return Func
{ Func::MethodFamily
{ ff
, true } };
18686 } else if (auto const f
= aux
.func()) {
18687 return aux
.isComplete()
18688 ? Func
{ Func::Method
{ func_info(*m_data
, f
) } }
18689 : Func
{ Func::MethodOrMissing
{ func_info(*m_data
, f
) } };
18691 // Ctor doesn't exist in any regular subclasses. This can
18692 // happen with interfaces. The ctor might get the default
18693 // ctor at runtime, so be conservative.
18695 Func::MethodName
{ cinfo
->cls
->name
, s_construct
.get() }
18700 // Otherwise this class is regular (in which case there's just
18701 // method families, or there's no entry in aux, which means the
18702 // regular subset entry is the same as the full entry.
18704 auto const famIt
= cinfo
->methodFamilies
.find(s_construct
.get());
18705 assertx(famIt
!= cinfo
->methodFamilies
.end());
18706 auto const& fam
= famIt
->second
;
18707 assertx(!fam
.isEmpty());
18709 if (auto const ff
= fam
.funcFamily()) {
18710 return Func
{ Func::MethodFamily
{ ff
, true } };
18711 } else if (auto const f
= fam
.func()) {
18712 // Since we're looking at the regular subset, we can assume
18713 // the set is complete, regardless of the flag on fam.
18714 return Func
{ Func::Method
{ func_info(*m_data
, f
) } };
18716 always_assert(false);
18719 [&] (bool includeNonRegular
) {
18720 assertx(!includeNonRegular
);
18722 Func::MethodName
{ dcls
.smallestCls().name(), s_construct
.get() }
18728 res::Func
Index::resolve_func(SString name
) const {
18729 name
= normalizeNS(name
);
18730 auto const it
= m_data
->funcs
.find(name
);
18731 if (it
== end(m_data
->funcs
)) {
18732 return res::Func
{ res::Func::MissingFunc
{ name
} };
18734 auto const func
= it
->second
;
18735 assertx(func
->attrs
& AttrPersistent
);
18736 return res::Func
{ res::Func::Fun
{ func_info(*m_data
, func
) } };
18739 res::Func
Index::resolve_func_or_method(const php::Func
& f
) const {
18740 if (!f
.cls
) return res::Func
{ res::Func::Fun
{ func_info(*m_data
, &f
) } };
18741 return res::Func
{ res::Func::Method
{ func_info(*m_data
, &f
) } };
18744 bool Index::func_depends_on_arg(const php::Func
* func
, size_t arg
) const {
18745 auto const& finfo
= *func_info(*m_data
, func
);
18746 return arg
>= finfo
.unusedParams
.size() || !finfo
.unusedParams
.test(arg
);
18749 // Helper function: Given a DCls, visit every subclass it represents,
18750 // passing it to the given callable. If the callable returns false,
18751 // stop iteration. Return false if any of the classes is unresolved,
18752 // true otherwise. This is used to simplify the below functions which
18753 // need to iterate over all possible subclasses and union the results.
18754 template <typename F
>
18755 bool Index::visit_every_dcls_cls(const DCls
& dcls
, const F
& f
) const {
18756 if (dcls
.isExact()) {
18757 auto const cinfo
= dcls
.cls().cinfo();
18758 if (!cinfo
) return false;
18759 if (dcls
.containsNonRegular() || is_regular_class(*cinfo
->cls
)) {
18763 } else if (dcls
.isSub()) {
18764 auto unresolved
= false;
18765 res::Class::visitEverySub(
18766 std::array
<res::Class
, 1>{dcls
.cls()},
18767 dcls
.containsNonRegular(),
18768 [&] (res::Class c
) {
18769 if (c
.hasCompleteChildren()) {
18770 if (auto const cinfo
= c
.cinfo()) return f(cinfo
);
18776 return !unresolved
;
18777 } else if (dcls
.isIsect()) {
18778 auto const& isect
= dcls
.isect();
18779 assertx(isect
.size() > 1);
18781 auto unresolved
= false;
18782 res::Class::visitEverySub(
18784 dcls
.containsNonRegular(),
18785 [&] (res::Class c
) {
18786 if (c
.hasCompleteChildren()) {
18787 if (auto const cinfo
= c
.cinfo()) return f(cinfo
);
18793 return !unresolved
;
18795 // Even though this has an intersection list, it must be the exact
18796 // class, so it's sufficient to provide that.
18797 assertx(dcls
.isIsectAndExact());
18798 auto const e
= dcls
.isectAndExact().first
;
18799 auto const cinfo
= e
.cinfo();
18800 if (!cinfo
) return false;
18801 if (dcls
.containsNonRegular() || is_regular_class(*cinfo
->cls
)) {
18808 ClsConstLookupResult
Index::lookup_class_constant(Context ctx
,
18810 const Type
& name
) const {
18811 ITRACE(4, "lookup_class_constant: ({}) {}::{}\n",
18812 show(ctx
), show(cls
), show(name
));
18815 using R
= ClsConstLookupResult
;
18817 auto const conservative
= [] {
18818 ITRACE(4, "conservative\n");
18819 return R
{ TInitCell
, TriBool::Maybe
, true };
18822 auto const notFound
= [] {
18823 ITRACE(4, "not found\n");
18824 return R
{ TBottom
, TriBool::No
, false };
18827 if (!is_specialized_cls(cls
)) return conservative();
18829 // We could easily support the case where we don't know the constant
18830 // name, but know the class (like we do for properties), by unioning
18831 // together all possible constants. However it very rarely happens,
18832 // but when it does, the set of constants to union together can be
18833 // huge and it becomes very expensive.
18834 if (!is_specialized_string(name
)) return conservative();
18835 auto const sname
= sval_of(name
);
18837 // If this lookup is safe to cache. Some classes can have a huge
18838 // number of subclasses and unioning together all possible constants
18839 // can become very expensive. We can aleviate some of this expense
18840 // by caching results. We cannot cache a result when we use 86cinit
18841 // analysis since that can change.
18842 auto cachable
= true;
18844 auto const process
= [&] (const ClassInfo
* ci
) {
18845 ITRACE(4, "{}:\n", ci
->cls
->name
);
18848 // Does the constant exist on this class?
18849 auto const it
= ci
->clsConstants
.find(sname
);
18850 if (it
== ci
->clsConstants
.end()) return notFound();
18852 // Is it a value and is it non-abstract (we only deal with
18853 // concrete constants).
18854 auto const& cns
= *it
->second
.get();
18855 if (cns
.kind
!= ConstModifiers::Kind::Value
) return notFound();
18856 if (!cns
.val
.has_value()) return notFound();
18858 auto const cnsIdx
= it
->second
.idx
;
18860 // Determine the constant's value and return it
18861 auto const r
= [&] {
18862 if (cns
.val
->m_type
== KindOfUninit
) {
18863 // Constant is defined by a 86cinit. Use the result from
18864 // analysis and add a dependency. We cannot cache in this
18867 auto const cnsCls
= m_data
->classes
.at(cns
.cls
);
18869 auto const cinit
= cnsCls
->methods
.back().get();
18870 assertx(cinit
->name
== s_86cinit
.get());
18871 add_dependency(*m_data
, cinit
, ctx
, Dep::ClsConst
);
18874 ITRACE(4, "(dynamic)\n");
18875 auto const type
= [&] {
18876 auto const cnsClsCi
= folly::get_default(m_data
->classInfo
, cnsCls
->name
);
18877 if (!cnsClsCi
|| cnsIdx
>= cnsClsCi
->clsConstTypes
.size()) {
18880 return cnsClsCi
->clsConstTypes
[cnsIdx
].type
;
18882 return R
{ type
, TriBool::Yes
, true };
18885 // Fully resolved constant with a known value
18886 auto mightThrow
= bool(ci
->cls
->attrs
& AttrInternal
);
18888 auto const unit
= lookup_class_unit(*ci
->cls
);
18889 auto const moduleName
= unit
->moduleName
;
18890 auto const packageInfo
= unit
->packageInfo
;
18891 if (auto const activeDeployment
= packageInfo
.getActiveDeployment()) {
18892 if (!packageInfo
.moduleInDeployment(
18893 moduleName
, *activeDeployment
, DeployKind::Hard
)) {
18898 return R
{ from_cell(*cns
.val
), TriBool::Yes
, mightThrow
};
18900 ITRACE(4, "-> {}\n", show(r
));
18904 auto const& dcls
= dcls_of(cls
);
18905 if (dcls
.isSub()) {
18906 // Before anything, look up this entry in the cache. We don't
18907 // bother with the cache for the exact case because it's quick and
18908 // there's little point.
18909 auto const cinfo
= dcls
.cls().cinfo();
18910 if (!cinfo
) return conservative();
18911 if (auto const it
=
18912 m_data
->clsConstLookupCache
.find(std::make_pair(cinfo
->cls
, sname
));
18913 it
!= m_data
->clsConstLookupCache
.end()) {
18914 ITRACE(4, "cache hit: {}\n", show(it
->second
));
18919 Optional
<R
> result
;
18920 auto const resolved
= visit_every_dcls_cls(
18922 [&] (const ClassInfo
* cinfo
) {
18923 if (result
) ITRACE(5, "-> {}\n", show(*result
));
18924 auto r
= process(cinfo
);
18926 result
.emplace(std::move(r
));
18933 if (!resolved
) return conservative();
18934 assertx(result
.has_value());
18936 // Save this for future lookups if we can
18937 if (dcls
.isSub() && cachable
) {
18938 auto const cinfo
= dcls
.cls().cinfo();
18940 m_data
->clsConstLookupCache
.emplace(
18941 std::make_pair(cinfo
->cls
, sname
),
18946 ITRACE(4, "-> {}\n", show(*result
));
18950 std::vector
<std::pair
<SString
, ConstIndex
>>
18951 Index::lookup_flattened_class_type_constants(const php::Class
&) const {
18952 // Should never be used with an Index.
18953 always_assert(false);
18956 std::vector
<std::pair
<SString
, ClsConstInfo
>>
18957 Index::lookup_class_constants(const php::Class
& cls
) const {
18958 std::vector
<std::pair
<SString
, ClsConstInfo
>> out
;
18959 out
.reserve(cls
.constants
.size());
18961 auto const cinfo
= folly::get_default(m_data
->classInfo
, cls
.name
);
18962 for (size_t i
= 0, size
= cls
.constants
.size(); i
< size
; ++i
) {
18963 auto const& cns
= cls
.constants
[i
];
18964 if (cns
.kind
!= ConstModifiers::Kind::Value
) continue;
18965 if (!cns
.val
) continue;
18966 if (cns
.val
->m_type
!= KindOfUninit
) {
18967 out
.emplace_back(cns
.name
, ClsConstInfo
{ from_cell(*cns
.val
), 0 });
18968 } else if (cinfo
&& i
< cinfo
->clsConstTypes
.size()) {
18969 out
.emplace_back(cns
.name
, cinfo
->clsConstTypes
[i
]);
18971 out
.emplace_back(cns
.name
, ClsConstInfo
{ TInitCell
, 0 });
18977 ClsTypeConstLookupResult
18978 Index::lookup_class_type_constant(
18981 const ClsTypeConstLookupResolver
& resolver
) const {
18982 ITRACE(4, "lookup_class_type_constant: {}::{}\n", show(cls
), show(name
));
18985 using R
= ClsTypeConstLookupResult
;
18987 auto const conservative
= [] {
18988 ITRACE(4, "conservative\n");
18990 TypeStructureResolution
{ TSDictN
, true },
18996 auto const notFound
= [] {
18997 ITRACE(4, "not found\n");
18999 TypeStructureResolution
{ TBottom
, false },
19005 // Unlike lookup_class_constant, we distinguish abstract from
19006 // not-found, as the runtime sometimes treats them differently.
19007 auto const abstract
= [] {
19008 ITRACE(4, "abstract\n");
19010 TypeStructureResolution
{ TBottom
, false },
19016 if (!is_specialized_cls(cls
)) return conservative();
19018 // As in lookup_class_constant, we could handle this, but it's not
19020 if (!is_specialized_string(name
)) return conservative();
19021 auto const sname
= sval_of(name
);
19023 auto const process
= [&] (const ClassInfo
* ci
) {
19024 ITRACE(4, "{}:\n", ci
->cls
->name
);
19027 // Does the type constant exist on this class?
19028 auto const it
= ci
->clsConstants
.find(sname
);
19029 if (it
== ci
->clsConstants
.end()) return notFound();
19031 // Is it an actual non-abstract type-constant?
19032 auto const& cns
= *it
->second
;
19033 if (cns
.kind
!= ConstModifiers::Kind::Type
) return notFound();
19034 if (!cns
.val
.has_value()) return abstract();
19036 assertx(tvIsDict(*cns
.val
));
19037 ITRACE(4, "({}) {}\n", cns
.cls
, show(dict_val(val(*cns
.val
).parr
)));
19039 // If we've been given a resolver, use it. Otherwise resolve it in
19041 auto resolved
= resolver
19042 ? resolver(cns
, *ci
->cls
)
19043 : resolve_type_structure(IndexAdaptor
{ *this }, cns
, *ci
->cls
);
19045 // The result of resolve_type_structure isn't, in general,
19046 // static. However a type-constant will always be, so force that
19048 assertx(resolved
.type
.is(BBottom
) || resolved
.type
.couldBe(BUnc
));
19049 resolved
.type
&= TUnc
;
19051 std::move(resolved
),
19055 ITRACE(4, "-> {}\n", show(r
));
19059 auto const& dcls
= dcls_of(cls
);
19061 Optional
<R
> result
;
19062 auto const resolved
= visit_every_dcls_cls(
19064 [&] (const ClassInfo
* cinfo
) {
19066 ITRACE(5, "-> {}\n", show(*result
));
19068 auto r
= process(cinfo
);
19070 result
.emplace(std::move(r
));
19077 if (!resolved
) return conservative();
19078 assertx(result
.has_value());
19080 ITRACE(4, "-> {}\n", show(*result
));
19084 ClsTypeConstLookupResult
19085 Index::lookup_class_type_constant(const php::Class
&,
19087 HHBBC::ConstIndex
) const {
19088 // Should never be called with an Index.
19089 always_assert(false);
19092 Type
Index::lookup_constant(Context ctx
, SString cnsName
) const {
19093 auto iter
= m_data
->constants
.find(cnsName
);
19094 if (iter
== end(m_data
->constants
)) return TBottom
;
19096 auto constant
= iter
->second
;
19097 if (type(constant
->val
) != KindOfUninit
) {
19098 return from_cell(constant
->val
);
19101 // Assume a runtime call to Constant::get(), which will invoke
19102 // 86cinit_<cnsName>(). Look up it's return type.
19104 auto const func_name
= Constant::funcNameFromName(cnsName
);
19105 assertx(func_name
&& "func_name will never be nullptr");
19107 auto rfunc
= resolve_func(func_name
);
19108 assertx(rfunc
.exactFunc());
19110 return lookup_return_type(ctx
, nullptr, rfunc
, Dep::ConstVal
).t
;
19114 Index::lookup_foldable_return_type(Context ctx
,
19115 const CallContext
& calleeCtx
) const {
19116 auto const func
= calleeCtx
.callee
;
19117 constexpr auto max_interp_nexting_level
= 2;
19118 static __thread
uint32_t interp_nesting_level
;
19120 using R
= ReturnType
;
19122 auto const ctxType
= adjust_closure_context(
19123 IndexAdaptor
{ *this },
19127 // Don't fold functions when staticness mismatches
19128 if (!func
->isClosureBody
) {
19129 if ((func
->attrs
& AttrStatic
) && ctxType
.couldBe(TObj
)) {
19130 return R
{ TInitCell
, false };
19132 if (!(func
->attrs
& AttrStatic
) && ctxType
.couldBe(TCls
)) {
19133 return R
{ TInitCell
, false };
19137 auto const& finfo
= *func_info(*m_data
, func
);
19138 if (finfo
.effectFree
&& is_scalar(finfo
.returnTy
)) {
19139 return R
{ finfo
.returnTy
, true };
19142 auto showArgs DEBUG_ONLY
= [] (const CompactVector
<Type
>& a
) {
19143 std::string ret
, sep
;
19144 for (auto& arg
: a
) {
19145 folly::format(&ret
, "{}{}", sep
, show(arg
));
19152 ContextRetTyMap::const_accessor acc
;
19153 if (m_data
->foldableReturnTypeMap
.find(acc
, calleeCtx
)) {
19156 "Found foldableReturnType for {}{}{} with args {} (hash: {})\n",
19157 func
->cls
? func
->cls
->name
: staticEmptyString(),
19158 func
->cls
? "::" : "",
19160 showArgs(calleeCtx
.args
),
19161 CallContextHashCompare
{}.hash(calleeCtx
));
19163 assertx(is_scalar(acc
->second
.t
));
19164 assertx(acc
->second
.effectFree
);
19165 return acc
->second
;
19172 "MISSING: foldableReturnType for {}{}{} with args {} (hash: {})\n",
19173 func
->cls
? func
->cls
->name
: staticEmptyString(),
19174 func
->cls
? "::" : "",
19176 showArgs(calleeCtx
.args
),
19177 CallContextHashCompare
{}.hash(calleeCtx
));
19178 return R
{ TInitCell
, false };
19181 if (interp_nesting_level
> max_interp_nexting_level
) {
19182 add_dependency(*m_data
, func
, ctx
, Dep::InlineDepthLimit
);
19183 return R
{ TInitCell
, false };
19186 auto const contextType
= [&] {
19187 ++interp_nesting_level
;
19188 SCOPE_EXIT
{ --interp_nesting_level
; };
19190 auto const wf
= php::WideFunc::cns(func
);
19191 auto const fa
= analyze_func_inline(
19192 IndexAdaptor
{ *this },
19193 AnalysisContext
{ func
->unit
, wf
, func
->cls
, &ctx
.forDep() },
19197 CollectionOpts::EffectFreeOnly
19200 fa
.effectFree
? fa
.inferredReturn
: TInitCell
,
19205 if (!is_scalar(contextType
.t
)) return R
{ TInitCell
, false };
19207 ContextRetTyMap::accessor acc
;
19208 if (m_data
->foldableReturnTypeMap
.insert(acc
, calleeCtx
)) {
19209 acc
->second
= contextType
;
19211 // someone beat us to it
19212 assertx(acc
->second
.t
== contextType
.t
);
19214 return contextType
;
19217 Index::ReturnType
Index::lookup_return_type(Context ctx
,
19218 MethodsInfo
* methods
,
19221 using R
= ReturnType
;
19223 auto const funcFamily
= [&] (FuncFamily
* fam
, bool regularOnly
) {
19224 add_dependency(*m_data
, fam
, ctx
, dep
);
19225 return fam
->infoFor(regularOnly
).m_returnTy
.get(
19227 auto ret
= TBottom
;
19228 auto effectFree
= true;
19229 for (auto const pf
: fam
->possibleFuncs()) {
19230 if (regularOnly
&& !pf
.inRegular()) continue;
19231 auto const finfo
= func_info(*m_data
, pf
.ptr());
19232 if (!finfo
->func
) return R
{ TInitCell
, false };
19233 ret
|= unctx(finfo
->returnTy
);
19234 effectFree
&= finfo
->effectFree
;
19235 if (!ret
.strictSubtypeOf(BInitCell
) && !effectFree
) break;
19237 return R
{ std::move(ret
), effectFree
};
19241 auto const meth
= [&] (const php::Func
* func
) {
19243 if (auto ret
= methods
->lookupReturnType(*func
)) {
19244 return R
{ unctx(std::move(ret
->t
)), ret
->effectFree
};
19247 add_dependency(*m_data
, func
, ctx
, dep
);
19248 auto const finfo
= func_info(*m_data
, func
);
19249 if (!finfo
->func
) return R
{ TInitCell
, false };
19250 return R
{ unctx(finfo
->returnTy
), finfo
->effectFree
};
19255 [&] (res::Func::FuncName
) { return R
{ TInitCell
, false }; },
19256 [&] (res::Func::MethodName
) { return R
{ TInitCell
, false }; },
19257 [&] (res::Func::Fun f
) {
19258 add_dependency(*m_data
, f
.finfo
->func
, ctx
, dep
);
19259 return R
{ unctx(f
.finfo
->returnTy
), f
.finfo
->effectFree
};
19261 [&] (res::Func::Method m
) { return meth(m
.finfo
->func
); },
19262 [&] (res::Func::MethodFamily fam
) {
19263 return funcFamily(fam
.family
, fam
.regularOnly
);
19265 [&] (res::Func::MethodOrMissing m
) { return meth(m
.finfo
->func
); },
19266 [&] (res::Func::MissingFunc
) { return R
{ TBottom
, false }; },
19267 [&] (res::Func::MissingMethod
) { return R
{ TBottom
, false }; },
19268 [&] (const res::Func::Isect
& i
) {
19269 auto ty
= TInitCell
;
19270 auto anyEffectFree
= false;
19271 for (auto const ff
: i
.families
) {
19272 auto const [t
, e
] = funcFamily(ff
, i
.regularOnly
);
19274 if (e
) anyEffectFree
= true;
19276 return R
{ std::move(ty
), anyEffectFree
};
19278 [&] (res::Func::Fun2
) -> R
{ always_assert(false); },
19279 [&] (res::Func::Method2
) -> R
{ always_assert(false); },
19280 [&] (res::Func::MethodFamily2
) -> R
{ always_assert(false); },
19281 [&] (res::Func::MethodOrMissing2
) -> R
{ always_assert(false); },
19282 [&] (res::Func::Isect2
&) -> R
{ always_assert(false); }
19286 Index::ReturnType
Index::lookup_return_type(Context caller
,
19287 MethodsInfo
* methods
,
19288 const CompactVector
<Type
>& args
,
19289 const Type
& context
,
19292 using R
= ReturnType
;
19294 auto const funcFamily
= [&] (FuncFamily
* fam
, bool regularOnly
) {
19295 add_dependency(*m_data
, fam
, caller
, dep
);
19296 auto ret
= fam
->infoFor(regularOnly
).m_returnTy
.get(
19299 auto effectFree
= true;
19300 for (auto const pf
: fam
->possibleFuncs()) {
19301 if (regularOnly
&& !pf
.inRegular()) continue;
19302 auto const finfo
= func_info(*m_data
, pf
.ptr());
19303 if (!finfo
->func
) return R
{ TInitCell
, false };
19304 ty
|= finfo
->returnTy
;
19305 effectFree
&= finfo
->effectFree
;
19306 if (!ty
.strictSubtypeOf(BInitCell
) && !effectFree
) break;
19308 return R
{ std::move(ty
), effectFree
};
19312 return_with_context(std::move(ret
.t
), context
),
19316 auto const meth
= [&] (const php::Func
* func
) {
19317 auto const finfo
= func_info(*m_data
, func
);
19318 if (!finfo
->func
) return R
{ TInitCell
, false };
19320 auto returnType
= [&] {
19322 if (auto ret
= methods
->lookupReturnType(*func
)) {
19326 add_dependency(*m_data
, func
, caller
, dep
);
19327 return R
{ finfo
->returnTy
, finfo
->effectFree
};
19330 return context_sensitive_return_type(
19333 { finfo
->func
, args
, context
},
19334 std::move(returnType
)
19340 [&] (res::Func::FuncName
) {
19341 return lookup_return_type(caller
, methods
, rfunc
, dep
);
19343 [&] (res::Func::MethodName
) {
19344 return lookup_return_type(caller
, methods
, rfunc
, dep
);
19346 [&] (res::Func::Fun f
) {
19347 add_dependency(*m_data
, f
.finfo
->func
, caller
, dep
);
19348 return context_sensitive_return_type(
19351 { f
.finfo
->func
, args
, context
},
19352 R
{ f
.finfo
->returnTy
, f
.finfo
->effectFree
}
19355 [&] (res::Func::Method m
) { return meth(m
.finfo
->func
); },
19356 [&] (res::Func::MethodFamily fam
) {
19357 return funcFamily(fam
.family
, fam
.regularOnly
);
19359 [&] (res::Func::MethodOrMissing m
) { return meth(m
.finfo
->func
); },
19360 [&] (res::Func::MissingFunc
) { return R
{ TBottom
, false }; },
19361 [&] (res::Func::MissingMethod
) { return R
{ TBottom
, false }; },
19362 [&] (const res::Func::Isect
& i
) {
19363 auto ty
= TInitCell
;
19364 auto anyEffectFree
= false;
19365 for (auto const ff
: i
.families
) {
19366 auto const [t
, e
] = funcFamily(ff
, i
.regularOnly
);
19368 if (e
) anyEffectFree
= true;
19370 return R
{ std::move(ty
), anyEffectFree
};
19372 [&] (res::Func::Fun2
) -> R
{ always_assert(false); },
19373 [&] (res::Func::Method2
) -> R
{ always_assert(false); },
19374 [&] (res::Func::MethodFamily2
) -> R
{ always_assert(false); },
19375 [&] (res::Func::MethodOrMissing2
) -> R
{ always_assert(false); },
19376 [&] (res::Func::Isect2
&) -> R
{ always_assert(false); }
19380 std::pair
<Index::ReturnType
, size_t>
19381 Index::lookup_return_type_raw(const php::Func
* f
) const {
19382 auto it
= func_info(*m_data
, f
);
19384 assertx(it
->func
== f
);
19386 ReturnType
{ it
->returnTy
, it
->effectFree
},
19387 it
->returnRefinements
19390 return { ReturnType
{ TInitCell
, false }, 0 };
19393 CompactVector
<Type
>
19394 Index::lookup_closure_use_vars(const php::Func
* func
,
19396 assertx(func
->isClosureBody
);
19398 auto const numUseVars
= closure_num_use_vars(func
);
19399 if (!numUseVars
) return {};
19400 auto const it
= m_data
->closureUseVars
.find(func
->cls
);
19401 if (it
== end(m_data
->closureUseVars
)) {
19402 return CompactVector
<Type
>(numUseVars
, TCell
);
19404 if (move
) return std::move(it
->second
);
19409 Index::lookup_private_props(const php::Class
* cls
,
19411 auto it
= m_data
->privatePropInfo
.find(cls
);
19412 if (it
!= end(m_data
->privatePropInfo
)) {
19413 if (move
) return std::move(it
->second
);
19416 return make_unknown_propstate(
19417 IndexAdaptor
{ *this },
19419 [&] (const php::Prop
& prop
) {
19420 return (prop
.attrs
& AttrPrivate
) && !(prop
.attrs
& AttrStatic
);
19426 Index::lookup_private_statics(const php::Class
* cls
,
19428 auto it
= m_data
->privateStaticPropInfo
.find(cls
);
19429 if (it
!= end(m_data
->privateStaticPropInfo
)) {
19430 if (move
) return std::move(it
->second
);
19433 return make_unknown_propstate(
19434 IndexAdaptor
{ *this },
19436 [&] (const php::Prop
& prop
) {
19437 return (prop
.attrs
& AttrPrivate
) && (prop
.attrs
& AttrStatic
);
19442 PropState
Index::lookup_public_statics(const php::Class
* cls
) const {
19443 auto const cinfo
= [&] () -> const ClassInfo
* {
19444 auto const it
= m_data
->classInfo
.find(cls
->name
);
19445 if (it
== end(m_data
->classInfo
)) return nullptr;
19450 for (auto const& prop
: cls
->properties
) {
19451 if (!(prop
.attrs
& (AttrPublic
|AttrProtected
)) ||
19452 !(prop
.attrs
& AttrStatic
)) {
19456 auto [ty
, everModified
] = [&] {
19457 if (!cinfo
) return std::make_pair(TInitCell
, true);
19459 if (!m_data
->seenPublicSPropMutations
) {
19460 return std::make_pair(
19462 adjust_type_for_prop(
19463 IndexAdaptor
{ *this },
19465 &prop
.typeConstraint
,
19468 initial_type_for_public_sprop(*this, *cls
, prop
)
19474 auto const it
= cinfo
->publicStaticProps
.find(prop
.name
);
19475 if (it
== end(cinfo
->publicStaticProps
)) {
19476 return std::make_pair(
19477 initial_type_for_public_sprop(*this, *cls
, prop
),
19481 return std::make_pair(
19482 it
->second
.inferredType
,
19483 it
->second
.everModified
19490 &prop
.typeConstraint
,
19500 * Entry point for static property lookups from the Index. Return
19501 * metadata about a `cls'::`name' static property access in the given
19504 PropLookupResult
Index::lookup_static(Context ctx
,
19505 const PropertiesInfo
& privateProps
,
19507 const Type
& name
) const {
19508 ITRACE(4, "lookup_static: {} {}::${}\n", show(ctx
), show(cls
), show(name
));
19511 using R
= PropLookupResult
;
19513 // First try to obtain the property name as a static string
19514 auto const sname
= [&] () -> SString
{
19515 // Treat non-string names conservatively, but the caller should be
19517 if (!is_specialized_string(name
)) return nullptr;
19518 return sval_of(name
);
19521 // Conservative result when we can't do any better. The type can be
19522 // anything, and anything might throw.
19523 auto const conservative
= [&] {
19524 ITRACE(4, "conservative\n");
19537 // If we don't know what `cls' is, there's not much we can do.
19538 if (!is_specialized_cls(cls
)) return conservative();
19540 // Turn the context class into a ClassInfo* for convenience.
19541 const ClassInfo
* ctxCls
= nullptr;
19543 // I don't think this can ever fail (we should always be able to
19544 // resolve the class since we're currently processing it). If it
19545 // does, be conservative.
19546 auto const rCtx
= resolve_class(ctx
.cls
->name
);
19547 if (!rCtx
) return conservative();
19548 ctxCls
= rCtx
->cinfo();
19549 if (!ctxCls
) return conservative();
19552 auto const& dcls
= dcls_of(cls
);
19553 auto const start
= dcls
.cls();
19555 Optional
<R
> result
;
19556 auto const resolved
= visit_every_dcls_cls(
19558 [&] (const ClassInfo
* cinfo
) {
19559 auto r
= lookup_static_impl(
19566 dcls
.isSub() && !sname
&& cinfo
!= start
.cinfo()
19568 ITRACE(4, "{} -> {}\n", cinfo
->cls
->name
, show(r
));
19570 result
.emplace(std::move(r
));
19577 if (!resolved
) return conservative();
19578 assertx(result
.has_value());
19580 ITRACE(4, "union -> {}\n", show(*result
));
19584 Type
Index::lookup_public_prop(const Type
& obj
, const Type
& name
) const {
19585 if (!is_specialized_obj(obj
)) return TCell
;
19587 if (!is_specialized_string(name
)) return TCell
;
19588 auto const sname
= sval_of(name
);
19591 auto const resolved
= visit_every_dcls_cls(
19593 [&] (const ClassInfo
* cinfo
) {
19594 ty
|= lookup_public_prop_impl(
19599 return ty
.strictSubtypeOf(TCell
);
19602 if (!resolved
) return TCell
;
19606 Type
Index::lookup_public_prop(const php::Class
* cls
, SString name
) const {
19607 auto const it
= m_data
->classInfo
.find(cls
->name
);
19608 if (it
== end(m_data
->classInfo
)) {
19611 return lookup_public_prop_impl(*m_data
, it
->second
, name
);
19614 bool Index::lookup_class_init_might_raise(Context ctx
, res::Class cls
) const {
19615 if (auto const ci
= cls
.cinfo()) {
19616 return class_init_might_raise(*m_data
, ctx
, ci
);
19617 } else if (cls
.cinfo2()) {
19618 // Not implemented yet
19619 always_assert(false);
19626 Index::lookup_iface_vtable_slot(const php::Class
* cls
) const {
19627 return folly::get_default(m_data
->ifaceSlotMap
, cls
->name
, kInvalidSlot
);
19630 //////////////////////////////////////////////////////////////////////
19633 * Entry point for static property type mutation from the Index. Merge
19634 * `val' into the known type for any accessible `cls'::`name' static
19635 * property. The mutation will be recovered into either
19636 * `publicMutations' or `privateProps' depending on the properties
19637 * found. Mutations to AttrConst properties are ignored, unless
19638 * `ignoreConst' is true.
19640 PropMergeResult
Index::merge_static_type(
19642 PublicSPropMutations
& publicMutations
,
19643 PropertiesInfo
& privateProps
,
19649 bool mustBeReadOnly
) const {
19651 4, "merge_static_type: {} {}::${} {}\n",
19652 show(ctx
), show(cls
), show(name
), show(val
)
19656 assertx(val
.subtypeOf(BInitCell
));
19658 using R
= PropMergeResult
;
19660 // In some cases we might try to merge Bottom if we're in
19661 // unreachable code. This won't affect anything, so just skip out
19663 if (val
.subtypeOf(BBottom
)) return R
{ TBottom
, TriBool::No
};
19665 // Try to turn the given property name into a static string
19666 auto const sname
= [&] () -> SString
{
19667 // Non-string names are treated conservatively here. The caller
19668 // should be checking for these and doing the right thing.
19669 if (!is_specialized_string(name
)) return nullptr;
19670 return sval_of(name
);
19673 // The case where we don't know `cls':
19674 auto const unknownCls
= [&] {
19676 // Very bad case. We don't know `cls' or the property name. This
19677 // mutation can be affecting anything, so merge it into all
19678 // properties (this drops type information for public
19680 ITRACE(4, "unknown class and prop. merging everything\n");
19681 publicMutations
.mergeUnknown(ctx
);
19682 privateProps
.mergeInAllPrivateStatics(
19683 IndexAdaptor
{ *this }, unctx(val
), ignoreConst
, mustBeReadOnly
19686 // Otherwise we don't know `cls', but do know the property
19687 // name. We'll store this mutation separately and union it in to
19688 // any lookup with the same name.
19689 ITRACE(4, "unknown class. merging all props with name {}\n", sname
);
19691 publicMutations
.mergeUnknownClass(sname
, unctx(val
));
19693 // Assume that it could possibly affect any private property with
19695 privateProps
.mergeInPrivateStatic(
19696 IndexAdaptor
{ *this }, sname
, unctx(val
), ignoreConst
, mustBeReadOnly
19700 // To be conservative, say we might throw and be conservative about
19702 return PropMergeResult
{
19703 loosen_likeness(val
),
19708 // check if we can determine the class.
19709 if (!is_specialized_cls(cls
)) return unknownCls();
19711 const ClassInfo
* ctxCls
= nullptr;
19713 auto const rCtx
= resolve_class(ctx
.cls
->name
);
19714 // We should only be not able to resolve our own context if the
19715 // class is not instantiable. In that case, the merge can't
19717 if (!rCtx
) return R
{ TBottom
, TriBool::No
};
19718 ctxCls
= rCtx
->cinfo();
19719 if (!ctxCls
) return unknownCls();
19722 auto const mergePublic
= [&] (const ClassInfo
* ci
,
19723 const php::Prop
& prop
,
19725 publicMutations
.mergeKnown(ci
, prop
, val
);
19728 auto const& dcls
= dcls_of(cls
);
19729 Optional
<res::Class
> start
;
19730 if (dcls
.isExact() || dcls
.isSub()) {
19731 start
= dcls
.cls();
19732 } else if (dcls
.isIsectAndExact()) {
19733 start
= dcls
.isectAndExact().first
;
19736 Optional
<R
> result
;
19737 auto const resolved
= visit_every_dcls_cls(
19739 [&] (const ClassInfo
* cinfo
) {
19740 auto r
= merge_static_type_impl(
19752 dcls
.isSub() && !sname
&& cinfo
!= start
->cinfo()
19754 ITRACE(4, "{} -> {}\n", cinfo
->cls
->name
, show(r
));
19756 result
.emplace(std::move(r
));
19763 if (!resolved
) return unknownCls();
19764 assertx(result
.has_value());
19765 ITRACE(4, "union -> {}\n", show(*result
));
19769 //////////////////////////////////////////////////////////////////////
19771 DependencyContext
Index::dependency_context(const Context
& ctx
) const {
19772 return dep_context(*m_data
, ctx
);
19775 bool Index::using_class_dependencies() const {
19776 return m_data
->useClassDependencies
;
19779 void Index::use_class_dependencies(bool f
) {
19780 if (f
!= m_data
->useClassDependencies
) {
19781 m_data
->dependencyMap
.clear();
19782 m_data
->useClassDependencies
= f
;
19786 void Index::refine_class_constants(const Context
& ctx
,
19787 const ResolvedConstants
& resolved
,
19788 DependencyContextSet
& deps
) {
19789 if (resolved
.empty()) return;
19791 auto changed
= false;
19792 auto const cls
= ctx
.func
->cls
;
19794 auto& constants
= cls
->constants
;
19796 for (auto& c
: resolved
) {
19797 assertx(c
.first
< constants
.size());
19798 auto& cnst
= constants
[c
.first
];
19799 assertx(cnst
.kind
== ConstModifiers::Kind::Value
);
19801 always_assert(cnst
.val
&& type(*cnst
.val
) == KindOfUninit
);
19802 if (auto const val
= tv(c
.second
.type
)) {
19803 assertx(val
->m_type
!= KindOfUninit
);
19805 // Deleting from the types map is too expensive, so just leave
19806 // any entry. We won't look at it if val is set.
19808 } else if (auto const cinfo
=
19809 folly::get_default(m_data
->classInfo
, cls
->name
)) {
19810 auto& old
= [&] () -> ClsConstInfo
& {
19811 if (c
.first
>= cinfo
->clsConstTypes
.size()) {
19812 auto const newSize
= std::max(c
.first
+1, resolved
.back().first
+1);
19813 cinfo
->clsConstTypes
.resize(newSize
, ClsConstInfo
{ TInitCell
, 0 });
19815 return cinfo
->clsConstTypes
[c
.first
];
19818 if (c
.second
.type
.strictlyMoreRefined(old
.type
)) {
19819 always_assert(c
.second
.refinements
> old
.refinements
);
19820 old
= std::move(c
.second
);
19823 always_assert_flog(
19824 c
.second
.type
.moreRefined(old
.type
),
19825 "Class constant type invariant violated for {}::{}\n"
19826 " {} is not at least as refined as {}\n",
19827 ctx
.func
->cls
->name
,
19829 show(c
.second
.type
),
19837 find_deps(*m_data
, ctx
.func
, Dep::ClsConst
, deps
);
19841 void Index::refine_constants(const FuncAnalysisResult
& fa
,
19842 DependencyContextSet
& deps
) {
19843 auto const& func
= fa
.ctx
.func
;
19844 if (func
->cls
) return;
19846 auto const cns_name
= Constant::nameFromFuncName(func
->name
);
19847 if (!cns_name
) return;
19849 auto const cns
= m_data
->constants
.at(cns_name
);
19850 auto const val
= tv(fa
.inferredReturn
);
19852 always_assert_flog(
19853 type(cns
->val
) == KindOfUninit
,
19854 "Constant value invariant violated in {}.\n"
19855 " Value went from {} to {}",
19857 show(from_cell(cns
->val
)),
19858 show(fa
.inferredReturn
)
19863 if (type(cns
->val
) != KindOfUninit
) {
19864 always_assert_flog(
19865 from_cell(cns
->val
) == fa
.inferredReturn
,
19866 "Constant value invariant violated in {}.\n"
19867 " Value went from {} to {}",
19869 show(from_cell(cns
->val
)),
19870 show(fa
.inferredReturn
)
19876 find_deps(*m_data
, func
, Dep::ConstVal
, deps
);
19879 void Index::refine_return_info(const FuncAnalysisResult
& fa
,
19880 DependencyContextSet
& deps
) {
19881 auto const& func
= fa
.ctx
.func
;
19882 auto const finfo
= func_info(*m_data
, func
);
19884 auto const error_loc
= [&] {
19885 return folly::sformat(
19889 ? folly::to
<std::string
>(func
->cls
->name
->data(), "::")
19896 if (finfo
->retParam
== NoLocalId
&& fa
.retParam
!= NoLocalId
) {
19897 // This is just a heuristic; it doesn't mean that the value passed
19898 // in was returned, but that the value of the parameter at the
19899 // point of the RetC was returned. We use it to make (heuristic)
19900 // decisions about whether to do inline interps, so we only allow
19901 // it to change once (otherwise later passes might not do the
19902 // inline interp, and get worse results, which could trigger other
19903 // assertions in Index::refine_*).
19904 dep
= Dep::ReturnTy
;
19905 finfo
->retParam
= fa
.retParam
;
19908 auto unusedParams
= ~fa
.usedParams
;
19909 if (finfo
->unusedParams
!= unusedParams
) {
19910 dep
= Dep::ReturnTy
;
19911 always_assert_flog(
19912 (finfo
->unusedParams
| unusedParams
) == unusedParams
,
19913 "Index unusedParams decreased in {}.\n",
19916 finfo
->unusedParams
= unusedParams
;
19919 auto resetFuncFamilies
= false;
19920 if (fa
.inferredReturn
.strictlyMoreRefined(finfo
->returnTy
)) {
19921 if (finfo
->returnRefinements
< options
.returnTypeRefineLimit
) {
19922 finfo
->returnTy
= fa
.inferredReturn
;
19923 // We've modifed the return type, so reset any cached FuncFamily
19925 resetFuncFamilies
= true;
19926 dep
= is_scalar(fa
.inferredReturn
)
19927 ? Dep::ReturnTy
| Dep::InlineDepthLimit
: Dep::ReturnTy
;
19928 finfo
->returnRefinements
+= fa
.localReturnRefinements
+ 1;
19929 if (finfo
->returnRefinements
> options
.returnTypeRefineLimit
) {
19930 FTRACE(1, "maxed out return type refinements at {}\n", error_loc());
19933 FTRACE(1, "maxed out return type refinements at {}\n", error_loc());
19936 always_assert_flog(
19937 fa
.inferredReturn
.moreRefined(finfo
->returnTy
),
19938 "Index return type invariant violated in {}.\n"
19939 " {} is not at least as refined as {}\n",
19941 show(fa
.inferredReturn
),
19942 show(finfo
->returnTy
)
19946 always_assert_flog(
19947 !finfo
->effectFree
|| fa
.effectFree
,
19948 "Index effectFree changed from true to false in {} {}.\n",
19950 func_fullname(*func
)
19953 if (finfo
->effectFree
!= fa
.effectFree
) {
19954 finfo
->effectFree
= fa
.effectFree
;
19955 dep
= Dep::InlineDepthLimit
| Dep::ReturnTy
;
19958 if (dep
!= Dep
{}) {
19959 find_deps(*m_data
, func
, dep
, deps
);
19960 if (resetFuncFamilies
) {
19961 assertx(has_dep(dep
, Dep::ReturnTy
));
19962 for (auto const ff
: finfo
->families
) {
19963 // Reset the cached return type information for all the
19964 // FuncFamilies this function is a part of. Always reset the
19965 // "all" information, and if there's regular subset
19966 // information, reset that too.
19967 if (!ff
->m_all
.m_returnTy
.reset() &&
19968 (!ff
->m_regular
|| !ff
->m_regular
->m_returnTy
.reset())) {
19971 // Only load the deps for this func family if we're the ones
19972 // who successfully reset. Only one thread needs to do it.
19973 find_deps(*m_data
, ff
, Dep::ReturnTy
, deps
);
19979 bool Index::refine_closure_use_vars(const php::Class
* cls
,
19980 const CompactVector
<Type
>& vars
) {
19981 assertx(is_closure(*cls
));
19983 for (auto i
= uint32_t{0}; i
< vars
.size(); ++i
) {
19984 always_assert_flog(
19985 vars
[i
].equivalentlyRefined(unctx(vars
[i
])),
19986 "Closure cannot have a used var with a context dependent type"
19990 auto& current
= [&] () -> CompactVector
<Type
>& {
19991 std::lock_guard
<std::mutex
> _
{closure_use_vars_mutex
};
19992 return m_data
->closureUseVars
[cls
];
19995 always_assert(current
.empty() || current
.size() == vars
.size());
19996 if (current
.empty()) {
20001 auto changed
= false;
20002 for (auto i
= uint32_t{0}; i
< vars
.size(); ++i
) {
20003 if (vars
[i
].strictSubtypeOf(current
[i
])) {
20005 current
[i
] = vars
[i
];
20007 always_assert_flog(
20008 vars
[i
].moreRefined(current
[i
]),
20009 "Index closure_use_var invariant violated in {}.\n"
20010 " {} is not at least as refined as {}\n",
20021 template<class Container
>
20022 void refine_private_propstate(Container
& cont
,
20023 const php::Class
* cls
,
20024 const PropState
& state
) {
20025 assertx(!is_used_trait(*cls
));
20026 auto* elm
= [&] () -> typename
Container::value_type
* {
20027 std::lock_guard
<std::mutex
> _
{private_propstate_mutex
};
20028 auto it
= cont
.find(cls
);
20029 if (it
== end(cont
)) {
20030 if (!state
.empty()) cont
[cls
] = state
;
20038 for (auto& kv
: state
) {
20039 auto& target
= elm
->second
[kv
.first
];
20040 assertx(target
.tc
== kv
.second
.tc
);
20041 always_assert_flog(
20042 kv
.second
.ty
.moreRefined(target
.ty
),
20043 "PropState refinement failed on {}::${} -- {} was not a subtype of {}\n",
20046 show(kv
.second
.ty
),
20049 target
.ty
= kv
.second
.ty
;
20051 if (kv
.second
.everModified
) {
20052 always_assert_flog(
20053 target
.everModified
,
20054 "PropState refinement failed on {}::${} -- "
20055 "everModified flag went from false to true\n",
20060 target
.everModified
= false;
20065 void Index::refine_private_props(const php::Class
* cls
,
20066 const PropState
& state
) {
20067 refine_private_propstate(m_data
->privatePropInfo
, cls
, state
);
20070 void Index::refine_private_statics(const php::Class
* cls
,
20071 const PropState
& state
) {
20072 // We can't store context dependent types in private statics since they
20073 // could be accessed using different contexts.
20074 auto cleanedState
= PropState
{};
20075 for (auto const& prop
: state
) {
20076 auto& elem
= cleanedState
[prop
.first
];
20077 elem
.ty
= unctx(prop
.second
.ty
);
20078 elem
.tc
= prop
.second
.tc
;
20079 elem
.attrs
= prop
.second
.attrs
;
20080 elem
.everModified
= prop
.second
.everModified
;
20083 refine_private_propstate(m_data
->privateStaticPropInfo
, cls
, cleanedState
);
20086 void Index::record_public_static_mutations(const php::Func
& func
,
20087 PublicSPropMutations mutations
) {
20088 if (!mutations
.m_data
) {
20089 m_data
->publicSPropMutations
.erase(&func
);
20092 m_data
->publicSPropMutations
.insert_or_assign(&func
, std::move(mutations
));
20095 void Index::update_prop_initial_values(const Context
& ctx
,
20096 const ResolvedPropInits
& resolved
,
20097 DependencyContextSet
& deps
) {
20098 auto& props
= const_cast<php::Class
*>(ctx
.cls
)->properties
;
20100 auto changed
= false;
20101 for (auto const& [idx
, info
] : resolved
) {
20102 assertx(idx
< props
.size());
20103 auto& prop
= props
[idx
];
20105 auto const allResolved
= [&] {
20106 if (prop
.typeConstraint
.isUnresolved()) return false;
20107 for (auto const& ub
: prop
.ubs
.m_constraints
) {
20108 if (ub
.isUnresolved()) return false;
20113 if (info
.satisfies
) {
20114 if (!(prop
.attrs
& AttrInitialSatisfiesTC
) && allResolved()) {
20115 attribute_setter(prop
.attrs
, true, AttrInitialSatisfiesTC
);
20119 always_assert_flog(
20120 !(prop
.attrs
& AttrInitialSatisfiesTC
),
20121 "AttrInitialSatisfiesTC invariant violated for {}::{}\n"
20122 " Went from true to false",
20123 ctx
.cls
->name
, prop
.name
20127 always_assert_flog(
20128 IMPLIES(!(prop
.attrs
& AttrDeepInit
), !info
.deepInit
),
20129 "AttrDeepInit invariant violated for {}::{}\n"
20130 " Went from false to true",
20131 ctx
.cls
->name
, prop
.name
20133 attribute_setter(prop
.attrs
, info
.deepInit
, AttrDeepInit
);
20135 if (type(info
.val
) != KindOfUninit
) {
20136 always_assert_flog(
20137 type(prop
.val
) == KindOfUninit
||
20138 from_cell(prop
.val
) == from_cell(info
.val
),
20139 "Property initial value invariant violated for {}::{}\n"
20140 " Value went from {} to {}",
20141 ctx
.cls
->name
, prop
.name
,
20142 show(from_cell(prop
.val
)), show(from_cell(info
.val
))
20144 prop
.val
= info
.val
;
20146 always_assert_flog(
20147 type(prop
.val
) == KindOfUninit
,
20148 "Property initial value invariant violated for {}::{}\n"
20149 " Value went from {} to not set",
20150 ctx
.cls
->name
, prop
.name
,
20151 show(from_cell(prop
.val
))
20155 if (!changed
) return;
20157 auto const it
= m_data
->classInfo
.find(ctx
.cls
->name
);
20158 if (it
== end(m_data
->classInfo
)) return;
20159 auto const cinfo
= it
->second
;
20161 // Both a pinit and a sinit can have resolved property values. When
20162 // analyzing constants we'll process each function separately and
20163 // potentially in different threads. Both will want to inspect the
20164 // property Attrs and the hasBadInitialPropValues. So, if we reach
20165 // here, take a lock to ensure both don't stomp on each other.
20166 static std::array
<std::mutex
, 256> locks
;
20167 auto& lock
= locks
[pointer_hash
<const php::Class
>{}(ctx
.cls
) % locks
.size()];
20168 std::lock_guard
<std::mutex
> _
{lock
};
20170 auto const noBad
= std::all_of(
20171 begin(props
), end(props
),
20172 [] (const php::Prop
& prop
) {
20173 return bool(prop
.attrs
& AttrInitialSatisfiesTC
);
20177 if (cinfo
->hasBadInitialPropValues
) {
20179 cinfo
->hasBadInitialPropValues
= false;
20180 find_deps(*m_data
, ctx
.cls
, Dep::PropBadInitialValues
, deps
);
20183 // If it's false, another thread got here before us and set it to
20185 always_assert(noBad
);
20189 void Index::refine_public_statics(DependencyContextSet
& deps
) {
20190 trace_time
update("update public statics");
20192 // Union together the mutations for each function, including the functions
20193 // which weren't analyzed this round.
20194 auto nothing_known
= false;
20195 PublicSPropMutations::UnknownMap unknown
;
20196 PublicSPropMutations::KnownMap known
;
20197 for (auto const& mutations
: m_data
->publicSPropMutations
) {
20198 if (!mutations
.second
.m_data
) continue;
20199 if (mutations
.second
.m_data
->m_nothing_known
) {
20200 nothing_known
= true;
20204 for (auto const& kv
: mutations
.second
.m_data
->m_unknown
) {
20205 auto const ret
= unknown
.insert(kv
);
20206 if (!ret
.second
) ret
.first
->second
|= kv
.second
;
20208 for (auto const& kv
: mutations
.second
.m_data
->m_known
) {
20209 auto const ret
= known
.insert(kv
);
20210 if (!ret
.second
) ret
.first
->second
|= kv
.second
;
20214 if (nothing_known
) {
20215 // We cannot go from knowing the types to not knowing the types (this is
20216 // equivalent to widening the types).
20217 always_assert(!m_data
->seenPublicSPropMutations
);
20220 m_data
->seenPublicSPropMutations
= true;
20222 // Refine known class state
20223 parallel::for_each(
20224 m_data
->allClassInfos
,
20225 [&] (std::unique_ptr
<ClassInfo
>& cinfo
) {
20226 for (auto const& prop
: cinfo
->cls
->properties
) {
20227 if (!(prop
.attrs
& (AttrPublic
|AttrProtected
)) ||
20228 !(prop
.attrs
& AttrStatic
)) {
20232 auto knownClsType
= [&] {
20233 auto const it
= known
.find(
20234 PublicSPropMutations::KnownKey
{ cinfo
.get(), prop
.name
}
20236 // If we didn't see a mutation, the type is TBottom.
20237 return it
== end(known
) ? TBottom
: it
->second
;
20240 auto unknownClsType
= [&] {
20241 auto const it
= unknown
.find(prop
.name
);
20242 // If we didn't see a mutation, the type is TBottom.
20243 return it
== end(unknown
) ? TBottom
: it
->second
;
20246 // We can't keep context dependent types in public properties.
20247 auto newType
= adjust_type_for_prop(
20248 IndexAdaptor
{ *this },
20250 &prop
.typeConstraint
,
20251 unctx(union_of(std::move(knownClsType
), std::move(unknownClsType
)))
20254 auto& entry
= cinfo
->publicStaticProps
[prop
.name
];
20256 if (!newType
.is(BBottom
)) {
20257 always_assert_flog(
20258 entry
.everModified
,
20259 "Static property index invariant violated on {}::{}:\n"
20260 " everModified flag went from false to true",
20265 entry
.everModified
= false;
20268 // The type from the mutations doesn't contain the in-class
20269 // initializer types. Add that here.
20270 auto effectiveType
= union_of(
20271 std::move(newType
),
20272 initial_type_for_public_sprop(*this, *cinfo
->cls
, prop
)
20276 * We may only shrink the types we recorded for each property. (If a
20277 * property type ever grows, the interpreter could infer something
20278 * incorrect at some step.)
20280 always_assert_flog(
20281 effectiveType
.subtypeOf(entry
.inferredType
),
20282 "Static property index invariant violated on {}::{}:\n"
20283 " {} is not a subtype of {}",
20286 show(effectiveType
),
20287 show(entry
.inferredType
)
20290 // Put a limit on the refinements to ensure termination. Since
20291 // we only ever refine types, we can stop at any point and still
20292 // maintain correctness.
20293 if (effectiveType
.strictSubtypeOf(entry
.inferredType
)) {
20294 if (entry
.refinements
+ 1 < options
.publicSPropRefineLimit
) {
20295 find_deps(*m_data
, &prop
, Dep::PublicSProp
, deps
);
20296 entry
.inferredType
= std::move(effectiveType
);
20297 ++entry
.refinements
;
20300 1, "maxed out public static property refinements for {}:{}\n",
20311 bool Index::frozen() const {
20312 return m_data
->frozen
;
20315 void Index::freeze() {
20316 m_data
->frozen
= true;
20317 m_data
->ever_frozen
= true;
20320 Type
AnalysisIndex::unserialize_type(Type t
) const {
20321 return unserialize_classes(AnalysisIndexAdaptor
{ *this }, std::move(t
));
20325 * Note that these functions run in separate threads, and
20326 * intentionally don't bump Trace::hhbbc_time. If you want to see
20327 * these times, set TRACE=hhbbc_time:1
20331 trace_time _{"clearing " #x}; \
20332 _.ignore_client_stats(); \
20336 void Index::cleanup_for_final() {
20337 trace_time _
{"cleanup for final", m_data
->sample
};
20338 CLEAR(m_data
->dependencyMap
);
20341 void Index::cleanup_post_emit() {
20342 trace_time _
{"cleanup post emit", m_data
->sample
};
20344 std::vector
<std::function
<void()>> clearers
;
20345 #define CLEAR_PARALLEL(x) clearers.push_back([&] CLEAR(x));
20346 CLEAR_PARALLEL(m_data
->classes
);
20347 CLEAR_PARALLEL(m_data
->funcs
);
20348 CLEAR_PARALLEL(m_data
->typeAliases
);
20349 CLEAR_PARALLEL(m_data
->enums
);
20350 CLEAR_PARALLEL(m_data
->constants
);
20351 CLEAR_PARALLEL(m_data
->modules
);
20352 CLEAR_PARALLEL(m_data
->units
);
20354 CLEAR_PARALLEL(m_data
->classClosureMap
);
20355 CLEAR_PARALLEL(m_data
->classExtraMethodMap
);
20357 CLEAR_PARALLEL(m_data
->classInfo
);
20359 CLEAR_PARALLEL(m_data
->privatePropInfo
);
20360 CLEAR_PARALLEL(m_data
->privateStaticPropInfo
);
20361 CLEAR_PARALLEL(m_data
->publicSPropMutations
);
20362 CLEAR_PARALLEL(m_data
->ifaceSlotMap
);
20363 CLEAR_PARALLEL(m_data
->closureUseVars
);
20365 CLEAR_PARALLEL(m_data
->methodFamilies
);
20367 CLEAR_PARALLEL(m_data
->funcFamilies
);
20368 CLEAR_PARALLEL(m_data
->funcFamilyStaticInfos
);
20370 CLEAR_PARALLEL(m_data
->clsConstLookupCache
);
20372 CLEAR_PARALLEL(m_data
->foldableReturnTypeMap
);
20373 CLEAR_PARALLEL(m_data
->contextualReturnTypes
);
20375 parallel::for_each(clearers
, [] (const std::function
<void()>& f
) { f(); });
20378 trace_time t
{"reset funcInfo"};
20379 t
.ignore_client_stats();
20380 parallel::for_each(
20383 u
.returnTy
= TBottom
;
20384 u
.families
.clear();
20387 m_data
->funcInfo
.clear();
20390 // Class-infos and program need to be freed after all Type instances
20391 // are destroyed, as Type::checkInvariants may try to access them.
20394 trace_time t
{"reset allClassInfos"};
20395 t
.ignore_client_stats();
20396 parallel::for_each(m_data
->allClassInfos
, [] (auto& u
) { u
.reset(); });
20397 m_data
->allClassInfos
.clear();
20401 trace_time t
{"reset program"};
20402 t
.ignore_client_stats();
20403 parallel::for_each(m_data
->program
->units
, [] (auto& u
) { u
.reset(); });
20404 parallel::for_each(m_data
->program
->classes
, [] (auto& u
) { u
.reset(); });
20405 parallel::for_each(m_data
->program
->funcs
, [] (auto& f
) { f
.reset(); });
20406 m_data
->program
.reset();
20410 void Index::thaw() {
20411 m_data
->frozen
= false;
20414 //////////////////////////////////////////////////////////////////////
20416 FuncClsUnit
AnalysisWorklist::next() {
20417 if (list
.empty()) return FuncClsUnit
{};
20418 auto n
= list
.front();
20424 void AnalysisWorklist::schedule(FuncClsUnit fc
) {
20425 assertx(IMPLIES(fc
.cls(), !is_closure(*fc
.cls())));
20426 if (!in
.emplace(fc
).second
) return;
20427 ITRACE(2, "scheduling {} onto worklist\n", show(fc
));
20428 list
.emplace_back(fc
);
20431 //////////////////////////////////////////////////////////////////////
20433 bool AnalysisDeps::add(Class c
, bool inTypeCns
) {
20435 ? typeCnsClasses
.emplace(c
.name
).second
20436 : classes
.emplace(c
.name
).second
;
20439 bool AnalysisDeps::add(ConstIndex cns
, bool inTypeCns
) {
20440 // Dependency on class constant implies a dependency on the class as
20442 add(Class
{ cns
.cls
}, inTypeCns
);
20444 ? typeCnsClsConstants
.emplace(cns
).second
20445 : clsConstants
.emplace(cns
).second
;
20448 bool AnalysisDeps::add(Constant cns
) {
20449 // Dependency on top-level constant implies a dependency on the
20450 // 86cinit initialized as well (which may not even exist).
20451 add(Func
{ HPHP::Constant::funcNameFromName(cns
.name
) }, Type::Meta
);
20452 return constants
.emplace(cns
.name
).second
;
20455 bool AnalysisDeps::add(AnyClassConstant any
, bool inTypeCns
) {
20456 // Dependency on class constant implies a dependency on the class as
20458 add(Class
{ any
.name
}, inTypeCns
);
20460 ? typeCnsAnyClsConstants
.emplace(any
.name
).second
20461 : anyClsConstants
.emplace(any
.name
).second
;
20464 AnalysisDeps::Type
AnalysisDeps::add(const php::Func
& f
, Type t
) {
20466 ? add(MethRef
{ f
}, t
)
20467 : add(Func
{ f
.name
}, t
);
20470 AnalysisDeps::Type
AnalysisDeps::add(MethRef m
, Type t
) {
20471 add(Class
{ m
.cls
});
20472 return merge(methods
[m
], t
| Type::Meta
);
20475 AnalysisDeps::Type
AnalysisDeps::add(Func f
, Type t
) {
20476 return merge(funcs
[f
.name
], t
| Type::Meta
);
20479 AnalysisDeps::Type
AnalysisDeps::merge(Type
& o
, Type n
) {
20480 auto const added
= n
- o
;
20485 AnalysisDeps
& AnalysisDeps::operator|=(const AnalysisDeps
& o
) {
20486 clsConstants
.insert(begin(o
.clsConstants
), end(o
.clsConstants
));
20487 classes
.insert(begin(o
.classes
), end(o
.classes
));
20488 constants
.insert(begin(o
.constants
), end(o
.constants
));
20489 anyClsConstants
.insert(begin(o
.anyClsConstants
), end(o
.anyClsConstants
));
20490 typeCnsClasses
.insert(begin(o
.typeCnsClasses
), end(o
.typeCnsClasses
));
20491 typeCnsClsConstants
.insert(
20492 begin(o
.typeCnsClsConstants
),
20493 end(o
.typeCnsClsConstants
)
20495 typeCnsAnyClsConstants
.insert(
20496 begin(o
.typeCnsAnyClsConstants
),
20497 end(o
.typeCnsAnyClsConstants
)
20499 for (auto const [name
, t
] : o
.funcs
) funcs
[name
] |= t
;
20500 for (auto const [meth
, t
] : o
.methods
) methods
[meth
] |= t
;
20504 std::string
show(AnalysisDeps::Type t
) {
20505 using T
= AnalysisDeps::Type
;
20507 auto const add
= [&] (const char* s
) {
20508 folly::format(&out
, "{}{}", out
.empty() ? "" : ",", s
);
20510 if (t
& T::Meta
) add("meta");
20511 if (t
& T::RetType
) add("return type");
20512 if (t
& T::ScalarRetType
) add("scalar return type");
20513 if (t
& T::RetParam
) add("returned param");
20514 if (t
& T::UnusedParams
) add("unused params");
20515 if (t
& T::Bytecode
) add("bytecode");
20519 std::string
show(const AnalysisDeps
& d
) {
20520 using namespace folly::gen
;
20521 auto const toCpp
= [] (SString n
) { return n
->toCppString(); };
20524 if (!d
.classes
.empty()) {
20526 &out
, " classes: {}\n",
20527 from(d
.classes
) | map(toCpp
) | unsplit
<std::string
>(", ")
20530 if (!d
.funcs
.empty()) {
20532 &out
, " funcs: {}\n",
20534 | map([&] (auto const& p
) {
20535 return folly::sformat("{} -> [{}]", toCpp(p
.first
), show(p
.second
));
20537 | unsplit
<std::string
>(", ")
20540 if (!d
.methods
.empty()) {
20542 &out
, " methods: {}\n",
20544 | map([] (auto const& p
) {
20545 return folly::sformat("{} -> [{}]", show(p
.first
), show(p
.second
));
20547 | unsplit
<std::string
>(", ")
20550 if (!d
.clsConstants
.empty()) {
20552 &out
, " class-constants: {}\n",
20553 from(d
.clsConstants
)
20554 | map([] (ConstIndex idx
) { return show(idx
); })
20555 | unsplit
<std::string
>(", ")
20558 if (!d
.anyClsConstants
.empty()) {
20560 &out
, " any class-constants: {}\n",
20561 from(d
.anyClsConstants
) | map(toCpp
) | unsplit
<std::string
>(", ")
20564 if (!d
.typeCnsClasses
.empty()) {
20566 &out
, " type-cns classes: {}\n",
20567 from(d
.typeCnsClasses
) | map(toCpp
) | unsplit
<std::string
>(", ")
20570 if (!d
.typeCnsClsConstants
.empty()) {
20572 &out
, " type-cns class-constants: {}\n",
20573 from(d
.typeCnsClsConstants
)
20574 | map([] (ConstIndex idx
) { return show(idx
); })
20575 | unsplit
<std::string
>(", ")
20578 if (!d
.typeCnsAnyClsConstants
.empty()) {
20580 &out
, " type-cns any class-constants: {}\n",
20581 from(d
.typeCnsAnyClsConstants
) | map(toCpp
) | unsplit
<std::string
>(", ")
20585 if (out
.empty()) out
= " (none)\n";
20589 //////////////////////////////////////////////////////////////////////
20591 void AnalysisChangeSet::changed(ConstIndex idx
) {
20592 clsConstants
.emplace(idx
);
20595 void AnalysisChangeSet::changed(const php::Constant
& c
) {
20596 constants
.emplace(c
.name
);
20599 void AnalysisChangeSet::changed(const php::Func
& f
, Type t
) {
20600 assertx(AnalysisDeps::isValidForChanges(t
));
20602 methods
[MethRef
{ f
}] |= t
;
20604 funcs
[f
.name
] |= t
;
20608 void AnalysisChangeSet::fixed(ConstIndex idx
) {
20609 fixedClsConstants
.emplace(idx
);
20612 void AnalysisChangeSet::fixed(const php::Class
& cls
) {
20613 allClsConstantsFixed
.emplace(cls
.name
);
20616 void AnalysisChangeSet::fixed(const php::Unit
& unit
) {
20617 unitsFixed
.emplace(unit
.filename
);
20620 void AnalysisChangeSet::typeCnsName(const php::Class
& cls
,
20622 if (cls
.name
->tsame(name
.name
)) return;
20623 clsTypeCnsNames
[cls
.name
].emplace(name
.name
);
20626 void AnalysisChangeSet::typeCnsName(const php::Unit
& unit
,
20628 unitTypeCnsNames
[unit
.filename
].emplace(name
.name
);
20631 void AnalysisChangeSet::filter(const TSStringSet
& keepClasses
,
20632 const FSStringSet
& keepFuncs
,
20633 const SStringSet
& keepUnits
,
20634 const SStringSet
& keepConstants
) {
20636 funcs
, [&] (auto const& p
) { return !keepFuncs
.count(p
.first
); }
20639 methods
, [&] (auto const& p
) { return !keepClasses
.count(p
.first
.cls
); }
20642 constants
, [&] (SString s
) { return !keepConstants
.count(s
); }
20645 clsConstants
, [&] (ConstIndex idx
) { return !keepClasses
.count(idx
.cls
); }
20649 [&] (ConstIndex idx
) {
20650 return !keepClasses
.count(idx
.cls
) || allClsConstantsFixed
.count(idx
.cls
);
20654 allClsConstantsFixed
, [&] (SString s
) { return !keepClasses
.count(s
); }
20657 unitsFixed
, [&] (SString s
) { return !keepUnits
.count(s
); }
20660 clsTypeCnsNames
, [&] (auto const& p
) { return !keepClasses
.count(p
.first
); }
20663 unitTypeCnsNames
, [&] (auto const& p
) { return !keepUnits
.count(p
.first
); }
20667 //////////////////////////////////////////////////////////////////////
20671 template <typename H
, typename V
, typename E
, typename F
, typename C
>
20672 std::vector
<SString
>
20673 map_to_sorted_key_vec(
20674 const hphp_fast_map
<SString
, V
, H
, E
>& m
,
20678 std::vector
<SString
> keys
;
20679 keys
.reserve(m
.size());
20680 for (auto const& [k
, v
] : m
) {
20681 if (!f(v
)) continue;
20682 keys
.emplace_back(k
);
20684 std::sort(begin(keys
), end(keys
), c
);
20685 keys
.shrink_to_fit();
20691 std::vector
<SString
> AnalysisInput::classNames() const {
20692 return map_to_sorted_key_vec(
20694 [] (Kind k
) { return any(k
& Kind::Rep
); },
20695 string_data_lt_type
{}
20699 std::vector
<SString
> AnalysisInput::funcNames() const {
20700 return map_to_sorted_key_vec(
20702 [] (Kind k
) { return any(k
& Kind::Rep
); },
20703 string_data_lt_func
{}
20707 std::vector
<SString
> AnalysisInput::unitNames() const {
20708 return map_to_sorted_key_vec(
20710 [] (Kind k
) { return any(k
& Kind::Rep
); },
20715 std::vector
<SString
> AnalysisInput::cinfoNames() const {
20716 return map_to_sorted_key_vec(
20718 [] (Kind k
) { return any(k
& Kind::Rep
) && any(k
& Kind::Info
); },
20719 string_data_lt_type
{}
20723 std::vector
<SString
> AnalysisInput::minfoNames() const {
20724 return map_to_sorted_key_vec(
20726 [] (Kind k
) { return any(k
& Kind::Rep
) && any(k
& Kind::MInfo
); },
20727 string_data_lt_type
{}
20731 AnalysisInput::Tuple
AnalysisInput::toTuple(Ref
<Meta
> meta
) {
20732 auto const sort
= [] (auto const& m
, auto const& c
) {
20733 return map_to_sorted_key_vec(m
, [] (Kind
) { return true; }, c
);
20736 UniquePtrRefVec
<php::Class
> outClasses
;
20737 UniquePtrRefVec
<php::ClassBytecode
> outClassBC
;
20738 RefVec
<AnalysisIndexCInfo
> outCInfos
;
20739 RefVec
<AnalysisIndexMInfo
> outMInfos
;
20740 UniquePtrRefVec
<php::Class
> outDepClasses
;
20742 for (auto const n
: sort(classes
, string_data_lt_type
{})) {
20743 auto const k
= classes
.at(n
);
20744 if (any(k
& Kind::Rep
)) {
20745 outClasses
.emplace_back(index
->classRefs
.at(n
));
20747 if (any(k
& Kind::Bytecode
)) {
20748 outClassBC
.emplace_back(index
->classBytecodeRefs
.at(n
));
20750 if (any(k
& Kind::Info
)) {
20751 outCInfos
.emplace_back(
20752 index
->classInfoRefs
.at(n
).cast
<AnalysisIndexCInfo
>()
20755 if (any(k
& Kind::MInfo
)) {
20756 outMInfos
.emplace_back(
20757 index
->uninstantiableClsMethRefs
.at(n
).cast
<AnalysisIndexMInfo
>()
20760 if (any(k
& Kind::Dep
)) {
20761 outDepClasses
.emplace_back(index
->classRefs
.at(n
));
20764 decltype(classes
){}.swap(classes
);
20765 outClasses
.shrink_to_fit();
20766 outClassBC
.shrink_to_fit();
20767 outCInfos
.shrink_to_fit();
20768 outMInfos
.shrink_to_fit();
20769 outDepClasses
.shrink_to_fit();
20771 UniquePtrRefVec
<php::Func
> outFuncs
;
20772 UniquePtrRefVec
<php::FuncBytecode
> outFuncBC
;
20773 RefVec
<AnalysisIndexFInfo
> outFInfos
;
20774 UniquePtrRefVec
<php::Func
> outDepFuncs
;
20776 for (auto const n
: sort(funcs
, string_data_lt_func
{})) {
20777 auto const k
= funcs
.at(n
);
20778 if (any(k
& Kind::Rep
)) {
20779 outFuncs
.emplace_back(index
->funcRefs
.at(n
));
20781 if (any(k
& Kind::Bytecode
)) {
20782 outFuncBC
.emplace_back(index
->funcBytecodeRefs
.at(n
));
20784 if (any(k
& Kind::Info
)) {
20785 outFInfos
.emplace_back(
20786 index
->funcInfoRefs
.at(n
).cast
<AnalysisIndexFInfo
>()
20789 if (any(k
& Kind::Dep
)) {
20790 outDepFuncs
.emplace_back(index
->funcRefs
.at(n
));
20793 decltype(funcs
){}.swap(funcs
);
20794 outFuncs
.shrink_to_fit();
20795 outFuncBC
.shrink_to_fit();
20796 outFInfos
.shrink_to_fit();
20797 outDepFuncs
.shrink_to_fit();
20799 UniquePtrRefVec
<php::Unit
> outUnits
;
20800 UniquePtrRefVec
<php::Unit
> outDepUnits
;
20802 for (auto const n
: sort(units
, string_data_lt
{})) {
20803 auto const k
= units
.at(n
);
20804 if (any(k
& Kind::Rep
)) {
20805 outUnits
.emplace_back(index
->unitRefs
.at(n
));
20807 if (any(k
& Kind::Dep
)) {
20808 outDepUnits
.emplace_back(index
->unitRefs
.at(n
));
20811 decltype(units
){}.swap(units
);
20812 outUnits
.shrink_to_fit();
20813 outDepUnits
.shrink_to_fit();
20816 std::move(outClasses
),
20817 std::move(outFuncs
),
20818 std::move(outUnits
),
20819 std::move(outClassBC
),
20820 std::move(outFuncBC
),
20821 std::move(outCInfos
),
20822 std::move(outFInfos
),
20823 std::move(outMInfos
),
20824 std::move(outDepClasses
),
20825 std::move(outDepFuncs
),
20826 std::move(outDepUnits
),
20831 //////////////////////////////////////////////////////////////////////
20835 // Many BucketSets are identical, so we intern them in the below
20836 // table, which saves a lot of memory.
20838 struct BucketSetHasher
{
20839 size_t operator()(const AnalysisInput::BucketSet
& b
) const {
20844 folly_concurrent_hash_map_simd
<
20845 AnalysisInput::BucketSet
,
20848 > s_bucketSetIntern
;
20850 AnalysisInput::BucketSet s_emptyBucketSet
;
20852 // Likewise, when we serde them, we can refer to them by indices
20853 // rather than encoding the same set multiple times.
20854 struct BucketSetSerdeTable
{
20855 using A
= AnalysisInput
;
20857 hphp_fast_map
<const A::BucketSet
*, size_t> bToIdx
;
20858 std::vector
<const A::BucketSet
*> idxToB
;
20860 void encode(BlobEncoder
& sd
, const A::BucketSet
& b
) {
20861 if (auto const idx
= folly::get_ptr(bToIdx
, &b
)) {
20864 idxToB
.emplace_back(&b
);
20865 bToIdx
.try_emplace(&b
, idxToB
.size());
20869 const AnalysisInput::BucketSet
* decode(BlobDecoder
& sd
) {
20870 auto const idx
= sd
.make
<size_t>();
20872 auto const b
= A::BucketSet::intern(sd
.make
<A::BucketSet
>());
20873 idxToB
.emplace_back(b
);
20876 assertx(idx
<= idxToB
.size());
20877 return idxToB
[idx
-1];
20881 thread_local Optional
<BucketSetSerdeTable
> tl_bucketSetTable
;
20885 bool AnalysisInput::BucketSet::isSubset(const BucketSet
& o
) const {
20888 std::includes(o
.buckets
.begin(), o
.buckets
.end(),
20889 buckets
.begin(), buckets
.end());
20892 bool AnalysisInput::BucketSet::contains(size_t idx
) const {
20893 assertx(idx
< std::numeric_limits
<uint32_t>::max());
20894 return buckets
.contains(idx
);
20897 size_t AnalysisInput::BucketSet::hash() const {
20898 return folly::hash::hash_range(buckets
.begin(), buckets
.end());
20901 bool AnalysisInput::BucketSet::empty() const {
20902 return buckets
.empty();
20905 void AnalysisInput::BucketSet::add(size_t idx
) {
20906 assertx(idx
< std::numeric_limits
<uint32_t>::max());
20907 buckets
.emplace_hint(buckets
.end(), idx
);
20910 void AnalysisInput::BucketSet::clear() {
20914 AnalysisInput::BucketSet
&
20915 AnalysisInput::BucketSet::operator|=(const BucketSet
& o
) {
20916 buckets
.insert(folly::sorted_unique
, begin(o
.buckets
), end(o
.buckets
));
20920 const AnalysisInput::BucketSet
*
20921 AnalysisInput::BucketSet::intern(BucketSet b
) {
20922 if (b
.buckets
.empty()) return &s_emptyBucketSet
;
20923 b
.buckets
.shrink_to_fit();
20924 return &s_bucketSetIntern
.try_emplace(std::move(b
)).first
->first
;
20927 void AnalysisInput::BucketSet::clearIntern() {
20928 s_bucketSetIntern
= decltype(s_bucketSetIntern
){};
20931 std::string
AnalysisInput::BucketSet::toString() const {
20932 using namespace folly::gen
;
20933 if (buckets
.empty()) return "-";
20934 return from(buckets
)
20935 | map([] (uint32_t i
) { return std::to_string(i
); })
20936 | unsplit
<std::string
>(",");
20939 void AnalysisInput::BucketPresence::serdeStart() {
20940 assertx(!tl_bucketSetTable
);
20941 tl_bucketSetTable
.emplace();
20944 void AnalysisInput::BucketPresence::serdeEnd() {
20945 assertx(tl_bucketSetTable
);
20946 tl_bucketSetTable
.reset();
20949 void AnalysisInput::BucketPresence::serde(BlobEncoder
& sd
) {
20950 assertx(tl_bucketSetTable
);
20954 tl_bucketSetTable
->encode(sd
, *present
);
20955 tl_bucketSetTable
->encode(sd
, *withBC
);
20956 tl_bucketSetTable
->encode(sd
, *process
);
20959 void AnalysisInput::BucketPresence::serde(BlobDecoder
& sd
) {
20960 assertx(tl_bucketSetTable
);
20961 present
= tl_bucketSetTable
->decode(sd
);
20962 withBC
= tl_bucketSetTable
->decode(sd
);
20963 process
= tl_bucketSetTable
->decode(sd
);
20969 std::string
show(const AnalysisInput::BucketPresence
& b
) {
20970 return folly::sformat(
20971 "present: {} BC: {} process: {}",
20972 b
.present
->toString(),
20973 b
.withBC
->toString(),
20974 b
.process
->toString()
20978 //////////////////////////////////////////////////////////////////////
20980 struct AnalysisScheduler::Bucket
{
20981 HierarchicalWorkBucket b
;
20984 //////////////////////////////////////////////////////////////////////
20986 AnalysisScheduler::AnalysisScheduler(Index
& index
)
20988 , totalWorkItems
{0}
20989 , lock
{std::make_unique
<std::mutex
>()} {}
20991 AnalysisScheduler::~AnalysisScheduler() {
20992 // Free the BucketSet intern table when the scheduler finishes, as
20993 // it can take a non-trivial amount of memory.
20994 AnalysisInput::BucketSet::clearIntern();
20997 void AnalysisScheduler::registerClass(SString name
) {
20998 // Closures are only scheduled as part of the class or func they're
21000 if (is_closure_name(name
)) return;
21001 FTRACE(5, "AnalysisScheduler: registering class {}\n", name
);
21003 auto const [cState
, emplaced1
] = classState
.try_emplace(name
, name
);
21004 if (!emplaced1
) return;
21008 auto const [tState
, emplaced2
] = traceState
.try_emplace(name
);
21009 if (emplaced2
) traceNames
.emplace_back(name
);
21010 tState
->second
.depStates
.emplace_back(&cState
->second
.depState
);
21012 classNames
.emplace_back(name
);
21013 auto const& closures
=
21014 folly::get_default(index
.m_data
->classToClosures
, name
);
21015 for (auto const clo
: closures
) {
21017 5, "AnalysisScheduler: registering closure {} associated with class {}\n",
21020 always_assert(classState
.try_emplace(clo
, clo
).second
);
21024 void AnalysisScheduler::registerFunc(SString name
) {
21025 FTRACE(5, "AnalysisScheduler: registering func {}\n", name
);
21027 auto const [fState
, emplaced1
] = funcState
.try_emplace(name
, name
);
21028 if (!emplaced1
) return;
21032 funcNames
.emplace_back(name
);
21034 auto const& closures
= folly::get_default(index
.m_data
->funcToClosures
, name
);
21035 for (auto const clo
: closures
) {
21037 5, "AnalysisScheduler: registering closure {} associated with func {}\n",
21040 always_assert(classState
.try_emplace(clo
, clo
).second
);
21043 // If this func is a 86cinit, then register the associated constant
21045 if (auto const cns
= Constant::nameFromFuncName(name
)) {
21046 FTRACE(5, "AnalysisScheduler: registering constant {}\n", cns
);
21047 always_assert(cnsChanged
.try_emplace(cns
).second
);
21048 auto const unit
= index
.m_data
->funcToUnit
.at(name
);
21049 // Modifying a global constant func implies the unit will be
21050 // changed too, so the unit must be registered as well.
21051 registerUnit(unit
);
21052 traceState
.at(unit
).depStates
.emplace_back(&fState
->second
.depState
);
21054 auto const [tState
, emplaced2
] = traceState
.try_emplace(name
);
21055 if (emplaced2
) traceNames
.emplace_back(name
);
21056 tState
->second
.depStates
.emplace_back(&fState
->second
.depState
);
21060 void AnalysisScheduler::registerUnit(SString name
) {
21061 FTRACE(5, "AnalysisScheduler: registering unit {}\n", name
);
21063 auto const [uState
, emplaced1
] = unitState
.try_emplace(name
, name
);
21064 if (!emplaced1
) return;
21068 auto const [tState
, emplaced2
] = traceState
.try_emplace(name
);
21069 if (emplaced2
) traceNames
.emplace_back(name
);
21070 tState
->second
.depStates
.emplace_back(&uState
->second
.depState
);
21072 unitNames
.emplace_back(name
);
21075 // Called when an analysis job reports back its changes. This makes
21076 // any dependencies affected by the change eligible to run in the next
21078 void AnalysisScheduler::recordChanges(const AnalysisOutput
& output
) {
21079 const TSStringSet classes
{begin(output
.classNames
), end(output
.classNames
)};
21080 const FSStringSet funcs
{begin(output
.funcNames
), end(output
.funcNames
)};
21081 const SStringSet units
{begin(output
.unitNames
), end(output
.unitNames
)};
21083 auto const& changed
= output
.meta
.changed
;
21085 // Sanity check that this bucket should actually be modifying the
21087 auto const valid
= [&] (SString name
, DepState::Kind kind
) {
21089 case DepState::Func
:
21090 return funcs
.count(name
) || output
.meta
.removedFuncs
.count(name
);
21091 case DepState::Class
: {
21092 if (!is_closure_name(name
)) return (bool)classes
.count(name
);
21093 auto const ctx
= folly::get_default(index
.m_data
->closureToClass
, name
);
21094 if (ctx
) return (bool)classes
.count(ctx
);
21095 auto const f
= folly::get_default(index
.m_data
->closureToFunc
, name
);
21097 return (bool)funcs
.count(f
);
21099 case DepState::Unit
:
21100 return (bool)units
.count(name
);
21104 for (auto const [name
, type
] : changed
.funcs
) {
21105 FTRACE(4, "AnalysisScheduler: func {} changed ({})\n", name
, show(type
));
21106 auto state
= folly::get_ptr(funcState
, name
);
21107 always_assert_flog(
21109 "Trying to mark un-tracked func {} changed",
21112 always_assert_flog(
21113 valid(name
, DepState::Func
),
21114 "Trying to mark func {} as changed from wrong shard",
21117 assertx(AnalysisDeps::isValidForChanges(type
));
21118 assertx(state
->changed
== Type::None
);
21119 state
->changed
= type
;
21122 for (auto const [meth
, type
] : changed
.methods
) {
21123 FTRACE(4, "AnalysisScheduler: method {} changed ({})\n",
21124 show(meth
), show(type
));
21125 auto state
= folly::get_ptr(classState
, meth
.cls
);
21126 always_assert_flog(
21128 "Trying to mark method for un-tracked class {} changed",
21131 always_assert_flog(
21132 valid(meth
.cls
, DepState::Class
),
21133 "Trying to mark method for class {} as changed from wrong shard",
21136 assertx(AnalysisDeps::isValidForChanges(type
));
21137 auto& t
= state
->methodChanges
.ensure(meth
.idx
);
21138 assertx(t
== Type::None
);
21142 for (auto const cns
: changed
.clsConstants
) {
21143 auto state
= folly::get_ptr(classState
, cns
.cls
);
21144 always_assert_flog(
21146 "Trying to mark constant for un-tracked class {} changed",
21149 always_assert_flog(
21150 valid(cns
.cls
, DepState::Class
),
21151 "Trying to mark constant for class {} as changed from wrong shard",
21155 if (state
->allCnsFixed
) continue;
21156 if (cns
.idx
< state
->cnsFixed
.size() && state
->cnsFixed
[cns
.idx
]) continue;
21157 FTRACE(4, "AnalysisScheduler: class constant {} changed\n", show(cns
));
21158 if (cns
.idx
>= state
->cnsChanges
.size()) {
21159 state
->cnsChanges
.resize(cns
.idx
+1);
21161 assertx(!state
->cnsChanges
[cns
.idx
]);
21162 state
->cnsChanges
[cns
.idx
] = true;
21165 for (auto const cns
: changed
.fixedClsConstants
) {
21166 auto state
= folly::get_ptr(classState
, cns
.cls
);
21167 always_assert_flog(
21169 "Trying to mark constant for un-tracked class {} as fixed",
21172 always_assert_flog(
21173 valid(cns
.cls
, DepState::Class
),
21174 "Trying to mark constant for class {} as fixed from wrong shard",
21178 if (state
->allCnsFixed
) continue;
21179 if (cns
.idx
>= state
->cnsFixed
.size()) {
21180 state
->cnsFixed
.resize(cns
.idx
+1);
21182 if (!state
->cnsFixed
[cns
.idx
]) {
21183 FTRACE(4, "AnalysisScheduler: class constant {} now fixed\n", show(cns
));
21184 state
->cnsFixed
[cns
.idx
] = true;
21188 for (auto const cls
: changed
.allClsConstantsFixed
) {
21189 auto state
= folly::get_ptr(classState
, cls
);
21190 always_assert_flog(
21192 "Trying to mark all constants for un-tracked class {} as fixed",
21195 always_assert_flog(
21196 valid(cls
, DepState::Class
),
21197 "Trying to mark all constants for class {} as fixed from wrong shard",
21200 if (!state
->allCnsFixed
) {
21202 4, "AnalysisScheduler: all class constants for {} now fixed\n",
21205 state
->allCnsFixed
= true;
21206 state
->cnsFixed
.clear();
21210 for (auto const name
: changed
.constants
) {
21211 FTRACE(4, "AnalysisScheduler: constant {} changed\n", name
);
21212 auto state
= folly::get_ptr(cnsChanged
, name
);
21213 always_assert_flog(
21215 "Trying to mark un-tracked constant {} changed",
21218 auto const initName
= Constant::funcNameFromName(name
);
21219 always_assert_flog(
21220 valid(initName
, DepState::Func
),
21221 "Trying to mark constant {} as changed from wrong shard",
21224 assertx(!state
->load(std::memory_order_acquire
));
21225 state
->store(true, std::memory_order_release
);
21228 for (auto const unit
: changed
.unitsFixed
) {
21229 auto state
= folly::get_ptr(unitState
, unit
);
21230 always_assert_flog(
21232 "Trying to mark all type-aliases for un-tracked unit {} as fixed",
21235 always_assert_flog(
21236 valid(unit
, DepState::Unit
),
21237 "Trying to mark all type-aliases for unit {} as fixed from wrong shard",
21240 if (!state
->fixed
) {
21242 4, "AnalysisScheduler: all type-aliases for unit {} now fixed\n",
21245 state
->fixed
= true;
21249 for (auto& [cls
, names
] : changed
.clsTypeCnsNames
) {
21250 auto state
= folly::get_ptr(classState
, cls
);
21251 always_assert_flog(
21253 "Trying to record type constant names "
21254 "for un-tracked class {}",
21257 always_assert_flog(
21258 valid(cls
, DepState::Class
),
21259 "Trying to record type constant names "
21260 "for class {} from wrong shard",
21263 state
->typeCnsNames
= std::move(names
);
21266 for (auto& [unit
, names
] : changed
.unitTypeCnsNames
) {
21267 auto state
= folly::get_ptr(unitState
, unit
);
21268 always_assert_flog(
21270 "Trying to record type constant names "
21271 "for un-tracked unit {}",
21274 always_assert_flog(
21275 valid(unit
, DepState::Unit
),
21276 "Trying to record type constant names "
21277 "for unit {} from wrong shard",
21280 state
->typeCnsNames
= std::move(names
);
21284 // Update the dependencies stored in the scheduler to take into
21285 // account the new set of dependencies reported by an analysis job.
21286 void AnalysisScheduler::updateDepState(AnalysisOutput
& output
) {
21287 for (size_t i
= 0, size
= output
.classNames
.size(); i
< size
; ++i
) {
21288 auto const name
= output
.classNames
[i
];
21289 auto it
= classState
.find(name
);
21290 always_assert_flog(
21291 it
!= end(classState
),
21292 "Trying to set deps for un-tracked class {}",
21295 auto& state
= it
->second
.depState
;
21296 if (is_closure_name(name
)) {
21297 assertx(output
.meta
.classDeps
[i
].empty());
21298 assertx(state
.deps
.empty());
21301 state
.deps
= std::move(output
.meta
.classDeps
[i
]);
21303 for (size_t i
= 0, size
= output
.funcNames
.size(); i
< size
; ++i
) {
21304 auto const name
= output
.funcNames
[i
];
21305 auto it
= funcState
.find(name
);
21306 always_assert_flog(
21307 it
!= end(funcState
),
21308 "Trying to set deps for un-tracked func {}",
21311 auto& state
= it
->second
.depState
;
21312 state
.deps
= std::move(output
.meta
.funcDeps
[i
]);
21314 for (size_t i
= 0, size
= output
.unitNames
.size(); i
< size
; ++i
) {
21315 auto const name
= output
.unitNames
[i
];
21316 auto it
= unitState
.find(name
);
21317 if (it
== end(unitState
)) {
21318 always_assert_flog(
21319 output
.meta
.unitDeps
[i
].empty(),
21320 "Trying to set non-empty deps for un-tracked unit {}",
21325 auto& state
= it
->second
.depState
;
21326 state
.deps
= std::move(output
.meta
.unitDeps
[i
]);
21329 // Remove deps for any removed functions, to avoid them spuriously
21330 // being rescheduled again.
21331 for (auto const name
: output
.meta
.removedFuncs
) {
21332 auto it
= funcState
.find(name
);
21333 always_assert_flog(
21334 it
!= end(funcState
),
21335 "Trying to reset deps for un-tracked func {}",
21338 auto& state
= it
->second
.depState
;
21339 state
.deps
= AnalysisDeps
{};
21342 for (auto& [cls
, bases
] : output
.meta
.cnsBases
) {
21343 auto const state
= folly::get_ptr(classState
, cls
);
21344 always_assert_flog(
21346 "Trying to update cns bases for untracked class {}",
21349 auto old
= folly::get_ptr(index
.m_data
->classToCnsBases
, cls
);
21351 assertx(bases
.empty());
21355 // Class constant base classes should only shrink.
21356 for (auto const b
: bases
) always_assert(old
->contains(b
));
21358 *old
= std::move(bases
);
21362 // Record the output of an analysis job. This means updating the
21363 // various Refs to their new versions, recording new dependencies, and
21364 // recording what has changed (to schedule the next round).
21365 void AnalysisScheduler::record(AnalysisOutput output
) {
21366 auto const numClasses
= output
.classNames
.size();
21367 auto const numCInfos
= output
.cinfoNames
.size();
21368 auto const numMInfos
= output
.minfoNames
.size();
21369 auto const numFuncs
= output
.funcNames
.size();
21370 auto const numUnits
= output
.unitNames
.size();
21371 assertx(numClasses
== output
.classes
.size());
21372 assertx(numClasses
== output
.clsBC
.size());
21373 assertx(numCInfos
== output
.cinfos
.size());
21374 assertx(numMInfos
== output
.minfos
.size());
21375 assertx(numClasses
== output
.meta
.classDeps
.size());
21376 assertx(numFuncs
== output
.funcs
.size());
21377 assertx(numFuncs
== output
.funcBC
.size());
21378 assertx(numFuncs
== output
.finfos
.size());
21379 assertx(numFuncs
== output
.meta
.funcDeps
.size());
21380 assertx(numUnits
== output
.units
.size());
21381 assertx(numUnits
== output
.meta
.unitDeps
.size());
21383 // Update Ref mappings:
21385 for (size_t i
= 0; i
< numUnits
; ++i
) {
21386 auto const name
= output
.unitNames
[i
];
21387 index
.m_data
->unitRefs
.at(name
) = std::move(output
.units
[i
]);
21390 for (size_t i
= 0; i
< numClasses
; ++i
) {
21391 auto const name
= output
.classNames
[i
];
21392 index
.m_data
->classRefs
.at(name
) = std::move(output
.classes
[i
]);
21393 index
.m_data
->classBytecodeRefs
.at(name
) = std::move(output
.clsBC
[i
]);
21395 for (size_t i
= 0; i
< numCInfos
; ++i
) {
21396 auto const name
= output
.cinfoNames
[i
];
21397 index
.m_data
->classInfoRefs
.at(name
) =
21398 output
.cinfos
[i
].cast
<std::unique_ptr
<ClassInfo2
>>();
21400 for (size_t i
= 0; i
< numMInfos
; ++i
) {
21401 auto const name
= output
.minfoNames
[i
];
21402 index
.m_data
->uninstantiableClsMethRefs
.at(name
) =
21403 output
.minfos
[i
].cast
<std::unique_ptr
<MethodsWithoutCInfo
>>();
21405 for (size_t i
= 0; i
< numFuncs
; ++i
) {
21406 auto const name
= output
.funcNames
[i
];
21407 index
.m_data
->funcRefs
.at(name
) = std::move(output
.funcs
[i
]);
21408 index
.m_data
->funcBytecodeRefs
.at(name
) = std::move(output
.funcBC
[i
]);
21409 index
.m_data
->funcInfoRefs
.at(name
) =
21410 output
.finfos
[i
].cast
<std::unique_ptr
<FuncInfo2
>>();
21413 recordChanges(output
);
21414 updateDepState(output
);
21416 // If the analysis job optimized away any 86cinit functions, record
21417 // that here so they can be later removed from our tables.
21418 if (!output
.meta
.removedFuncs
.empty()) {
21419 // This is relatively rare, so a lock is fine.
21420 std::lock_guard
<std::mutex
> _
{*lock
};
21421 funcsToRemove
.insert(
21422 begin(output
.meta
.removedFuncs
),
21423 end(output
.meta
.removedFuncs
)
21428 // Remove metadata for any 86cinit function that an analysis job
21429 // optimized away. This must be done *after* calculating the next
21431 void AnalysisScheduler::removeFuncs() {
21432 if (funcsToRemove
.empty()) return;
21434 TSStringSet traceNamesToRemove
;
21435 for (auto const name
: funcsToRemove
) {
21436 FTRACE(4, "AnalysisScheduler: removing function {}\n", name
);
21438 auto fstate
= folly::get_ptr(funcState
, name
);
21439 always_assert(fstate
);
21441 auto tstate
= [&] {
21442 if (!Constant::nameFromFuncName(name
)) {
21443 return folly::get_ptr(traceState
, name
);
21445 auto const unit
= index
.m_data
->funcToUnit
.at(name
);
21446 return folly::get_ptr(traceState
, unit
);
21448 always_assert(tstate
);
21450 tstate
->depStates
.eraseTail(
21452 tstate
->depStates
.begin(), tstate
->depStates
.end(),
21453 [&] (const DepState
* d
) { return d
== &fstate
->depState
; }
21456 if (tstate
->depStates
.empty()) {
21457 traceState
.erase(name
);
21458 traceNamesToRemove
.emplace(name
);
21461 always_assert(index
.m_data
->funcRefs
.erase(name
));
21462 always_assert(index
.m_data
->funcBytecodeRefs
.erase(name
));
21463 always_assert(index
.m_data
->funcInfoRefs
.erase(name
));
21464 always_assert(index
.m_data
->funcToUnit
.erase(name
));
21465 always_assert(funcState
.erase(name
));
21466 index
.m_data
->funcToClosures
.erase(name
);
21467 if (auto const cns
= Constant::nameFromFuncName(name
)) {
21468 always_assert(index
.m_data
->constantInitFuncs
.erase(name
));
21469 always_assert(cnsChanged
.erase(cns
));
21470 index
.m_data
->constantToUnit
.at(cns
).second
= false;
21476 begin(funcNames
), end(funcNames
),
21477 [&] (SString name
) { return funcsToRemove
.count(name
); }
21482 if (!traceNamesToRemove
.empty()) {
21485 begin(traceNames
), end(traceNames
),
21486 [&] (SString name
) { return traceNamesToRemove
.count(name
); }
21492 funcsToRemove
.clear();
21495 const AnalysisScheduler::TraceState
*
21496 AnalysisScheduler::lookupTrace(DepState::Kind k
, SString n
) const {
21497 auto const s
= folly::get_ptr(traceState
, n
);
21498 if (!s
) return nullptr;
21499 for (auto const d
: s
->depStates
) {
21500 if (d
->kind
!= k
) continue;
21501 if (!d
->name
->tsame(n
)) continue;
21507 // Retrieve the TraceState appropriate for the class with the given
21509 const AnalysisScheduler::TraceState
*
21510 AnalysisScheduler::traceForClass(SString cls
) const {
21511 if (is_closure_name(cls
)) {
21512 if (auto const n
= folly::get_default(index
.m_data
->closureToClass
, cls
)) {
21513 return traceForClass(n
);
21515 if (auto const n
= folly::get_default(index
.m_data
->closureToFunc
, cls
)) {
21516 return traceForFunc(n
);
21519 return lookupTrace(DepState::Class
, cls
);
21522 // Retrieve the TraceState appropriate for the func with the given
21524 const AnalysisScheduler::TraceState
*
21525 AnalysisScheduler::traceForFunc(SString func
) const {
21526 if (auto const cns
= Constant::nameFromFuncName(func
)) {
21527 return traceForConstant(cns
);
21529 return lookupTrace(DepState::Func
, func
);
21532 // Retrieve the TraceState appropriate for the unit with the given
21534 const AnalysisScheduler::TraceState
*
21535 AnalysisScheduler::traceForUnit(SString unit
) const {
21536 return lookupTrace(DepState::Unit
, unit
);
21539 // Retrive the TraceState appropriate for the constant with the given
21541 const AnalysisScheduler::TraceState
*
21542 AnalysisScheduler::traceForConstant(SString cns
) const {
21543 auto const unit
= folly::get_ptr(index
.m_data
->constantToUnit
, cns
);
21544 if (!unit
) return nullptr;
21545 return traceForUnit(unit
->first
);
21548 // Retrieve the TraceState appropriate for the type-alias with the
21550 const AnalysisScheduler::TraceState
*
21551 AnalysisScheduler::traceForTypeAlias(SString typeAlias
) const {
21553 folly::get_default(index
.m_data
->typeAliasToUnit
, typeAlias
);
21554 if (!unit
) return nullptr;
21555 return traceForUnit(unit
);
21558 // Retrieve the TraceState appropriate for the class or type-alias
21559 // with the given name.
21560 const AnalysisScheduler::TraceState
*
21561 AnalysisScheduler::traceForClassOrTypeAlias(SString name
) const {
21562 if (auto const t
= traceForClass(name
)) return t
;
21563 if (auto const t
= traceForTypeAlias(name
)) return t
;
21567 // Maps a DepState to it's associated TraceState.
21568 const AnalysisScheduler::TraceState
*
21569 AnalysisScheduler::traceForDepState(const DepState
& d
) const {
21571 case DepState::Func
: return traceForFunc(d
.name
);
21572 case DepState::Class
: return traceForClass(d
.name
);
21573 case DepState::Unit
: return traceForUnit(d
.name
);
21575 always_assert(false);
21578 Either
<const AnalysisScheduler::ClassState
*,
21579 const AnalysisScheduler::UnitState
*>
21580 AnalysisScheduler::stateForClassOrTypeAlias(SString n
) const {
21581 if (auto const s
= folly::get_ptr(classState
, n
)) return s
;
21582 if (auto const unit
= folly::get_default(index
.m_data
->typeAliasToUnit
, n
)) {
21583 return folly::get_ptr(unitState
, unit
);
21588 AnalysisScheduler::Presence
21589 AnalysisScheduler::presenceOf(const AnalysisInput::BucketPresence
& b1
,
21590 const AnalysisInput::BucketPresence
& b2
) const {
21591 if (&b1
== &b2
) return Presence::Full
;
21592 assertx(b1
.process
);
21593 assertx(b2
.process
);
21594 assertx(b2
.withBC
);
21595 assertx(b2
.present
);
21596 if (b1
.process
->isSubset(*b2
.process
)) return Presence::Full
;
21597 if (b1
.process
->isSubset(*b2
.withBC
)) return Presence::DepWithBytecode
;
21598 if (b1
.process
->isSubset(*b2
.present
)) return Presence::Dep
;
21599 return Presence::None
;
21602 AnalysisScheduler::Presence
21603 AnalysisScheduler::presenceOfClass(const TraceState
& trace
,
21604 SString cls
) const {
21605 if (is_closure_name(cls
)) {
21606 if (auto const n
= folly::get_default(index
.m_data
->closureToClass
, cls
)) {
21607 return presenceOfClass(trace
, n
);
21609 if (auto const n
= folly::get_default(index
.m_data
->closureToFunc
, cls
)) {
21610 return presenceOfFunc(trace
, n
);
21614 if (auto const t
= traceForClass(cls
)) {
21615 return presenceOf(trace
.buckets
, t
->buckets
);
21617 if (auto const b
= folly::get_ptr(untracked
.classes
, cls
)) {
21618 return presenceOf(trace
.buckets
, *b
);
21621 assertx(trace
.buckets
.process
);
21622 return trace
.buckets
.process
->empty() ? Presence::Full
: Presence::None
;
21625 AnalysisScheduler::Presence
21626 AnalysisScheduler::presenceOfClassOrTypeAlias(const TraceState
& trace
,
21627 SString cls
) const {
21628 if (is_closure_name(cls
)) {
21629 if (auto const n
= folly::get_default(index
.m_data
->closureToClass
, cls
)) {
21630 return presenceOfClass(trace
, n
);
21632 if (auto const n
= folly::get_default(index
.m_data
->closureToFunc
, cls
)) {
21633 return presenceOfFunc(trace
, n
);
21637 if (auto const t
= traceForClassOrTypeAlias(cls
)) {
21638 return presenceOf(trace
.buckets
, t
->buckets
);
21640 if (auto const u
= folly::get_default(index
.m_data
->typeAliasToUnit
, cls
)) {
21641 if (auto const b
= folly::get_ptr(untracked
.units
, u
)) {
21642 return presenceOf(trace
.buckets
, *b
);
21644 } else if (auto const b
= folly::get_ptr(untracked
.classes
, cls
)) {
21645 return presenceOf(trace
.buckets
, *b
);
21648 assertx(trace
.buckets
.process
);
21649 return trace
.buckets
.process
->empty() ? Presence::Full
: Presence::None
;
21652 AnalysisScheduler::Presence
21653 AnalysisScheduler::presenceOfFunc(const TraceState
& trace
,
21654 SString func
) const {
21655 if (auto const t
= traceForFunc(func
)) {
21656 return presenceOf(trace
.buckets
, t
->buckets
);
21658 if (auto const b
= folly::get_ptr(untracked
.funcs
, func
)) {
21659 return presenceOf(trace
.buckets
, *b
);
21661 assertx(trace
.buckets
.process
);
21662 return trace
.buckets
.process
->empty() ? Presence::Full
: Presence::None
;
21665 AnalysisScheduler::Presence
21666 AnalysisScheduler::presenceOfConstant(const TraceState
& trace
,
21667 SString cns
) const {
21668 if (auto const trace2
= traceForConstant(cns
)) {
21669 return presenceOf(trace
.buckets
, trace2
->buckets
);
21671 if (auto const b
= folly::get_ptr(untracked
.badConstants
, cns
)) {
21672 return presenceOf(trace
.buckets
, *b
);
21674 assertx(trace
.buckets
.process
);
21675 return trace
.buckets
.process
->empty() ? Presence::Full
: Presence::None
;
21678 // Calculate any classes or functions which should be scheduled to be
21679 // analyzed in the next round.
21680 void AnalysisScheduler::findToSchedule() {
21681 // Check if the given entity (class or function) needs to run again
21682 // due to one of its dependencies changing (or if it previously
21683 // registered a new dependency).
21684 auto const check
= [&] (SString name
, DepState
& d
) {
21685 // The algorithm for these are all similar: Compare the old
21686 // dependencies with the new dependencies. If the dependency is
21687 // new, or if it's not the same as the old, check the
21688 // ClassGroup. If they're in the same ClassGroup, ignore it (this
21689 // entity already incorporated the change inside the analysis
21690 // job). Otherwise schedule this class or func to run.
21692 if (d
.kind
== DepState::Class
&& is_closure_name(name
)) {
21693 assertx(d
.deps
.empty());
21697 auto const trace
= traceForDepState(d
);
21698 always_assert(trace
);
21700 for (auto const cls
: d
.deps
.classes
) {
21701 if (presenceOfClassOrTypeAlias(*trace
, cls
) == Presence::None
) {
21703 4, "AnalysisScheduler: {} new class/type-alias dependency on {},"
21711 for (auto const cls
: d
.deps
.typeCnsClasses
) {
21712 if (presenceOfClassOrTypeAlias(*trace
, cls
) == Presence::None
) {
21714 4, "AnalysisScheduler: {} new class/type-alias dependency "
21715 "(in type-cns) on {}, scheduling\n",
21722 for (auto const cls
: d
.deps
.anyClsConstants
) {
21723 auto const schedule
= [&] {
21724 switch (presenceOfClassOrTypeAlias(*trace
, cls
)) {
21725 case Presence::None
:
21727 case Presence::Dep
:
21728 case Presence::DepWithBytecode
: {
21729 auto const state
= folly::get_ptr(classState
, cls
);
21730 return state
&& state
->cnsChanges
.any();
21732 case Presence::Full
:
21739 4, "AnalysisScheduler: {} new/changed dependency on "
21740 "any class constant for {}, scheduling\n",
21747 for (auto const cls
: d
.deps
.typeCnsAnyClsConstants
) {
21748 if (presenceOfClassOrTypeAlias(*trace
, cls
) == Presence::None
) {
21750 4, "AnalysisScheduler: {} new/changed dependency (in type-cns) on "
21751 "any class constant for {}, scheduling\n",
21758 for (auto const cns
: d
.deps
.typeCnsClsConstants
) {
21759 if (presenceOfClassOrTypeAlias(*trace
, cns
.cls
) == Presence::None
) {
21761 4, "AnalysisScheduler: {} new/changed dependency (in type-cns) on "
21762 "class constant {}, scheduling\n",
21769 for (auto const [meth
, newT
] : d
.deps
.methods
) {
21770 if (newT
== Type::None
) continue;
21772 auto const schedule
= [&, meth
=meth
, newT
=newT
] {
21773 switch (presenceOfClass(*trace
, meth
.cls
)) {
21774 case Presence::None
:
21776 case Presence::Dep
:
21777 if (newT
& Type::Bytecode
) return true;
21779 case Presence::DepWithBytecode
: {
21780 auto const state
= folly::get_ptr(classState
, meth
.cls
);
21781 if (!state
) return false;
21782 auto const changed
=
21783 state
->methodChanges
.get_default(meth
.idx
, Type::None
);
21784 return (bool)(changed
& newT
);
21786 case Presence::Full
:
21793 4, "AnalysisScheduler: {} new/changed dependency on method {},"
21801 for (auto const [func
, newT
] : d
.deps
.funcs
) {
21802 if (newT
== Type::None
) continue;
21804 auto const schedule
= [&, func
=func
, newT
=newT
] {
21805 switch (presenceOfFunc(*trace
, func
)) {
21806 case Presence::None
:
21808 case Presence::Dep
:
21809 if (newT
& Type::Bytecode
) return true;
21811 case Presence::DepWithBytecode
: {
21812 auto const state
= folly::get_ptr(funcState
, func
);
21813 if (!state
) return false;
21814 return (bool)(state
->changed
& newT
);
21816 case Presence::Full
:
21823 4, "AnalysisScheduler: {} new/changed dependency on func {}, "
21831 for (auto const cns
: d
.deps
.clsConstants
) {
21832 auto const schedule
= [&] {
21833 switch (presenceOfClass(*trace
, cns
.cls
)) {
21834 case Presence::None
:
21836 case Presence::Dep
:
21837 case Presence::DepWithBytecode
: {
21838 auto const state
= folly::get_ptr(classState
, cns
.cls
);
21839 if (!state
) return false;
21841 cns
.idx
< state
->cnsChanges
.size() && state
->cnsChanges
[cns
.idx
];
21843 case Presence::Full
:
21850 4, "AnalysisScheduler: {} new/changed dependency on "
21851 "class constant {}, scheduling\n",
21858 for (auto const cns
: d
.deps
.constants
) {
21859 auto const schedule
= [&] {
21860 switch (presenceOfConstant(*trace
, cns
)) {
21861 case Presence::None
:
21863 case Presence::Dep
:
21864 case Presence::DepWithBytecode
: {
21865 auto const changed
= folly::get_ptr(cnsChanged
, cns
);
21866 return changed
&& changed
->load(std::memory_order_acquire
);
21868 case Presence::Full
:
21875 4, "AnalysisScheduler: {} new/changed dependency on "
21876 "constant {}, scheduling\n",
21886 parallel::for_each(
21888 [&] (SString name
) {
21889 assertx(!is_closure_name(name
));
21890 auto& state
= classState
.at(name
).depState
;
21891 state
.toSchedule
= check(name
, state
);
21892 if (state
.toSchedule
) ++totalWorkItems
;
21895 parallel::for_each(
21897 [&] (SString name
) {
21898 auto& state
= funcState
.at(name
).depState
;
21899 state
.toSchedule
= check(name
, state
);
21900 if (state
.toSchedule
) ++totalWorkItems
;
21903 parallel::for_each(
21905 [&] (SString name
) {
21906 auto& state
= unitState
.at(name
).depState
;
21907 state
.toSchedule
= check(name
, state
);
21908 if (state
.toSchedule
) ++totalWorkItems
;
21913 // Reset any recorded changes from analysis jobs, in preparation for
21915 void AnalysisScheduler::resetChanges() {
21916 auto const resetClass
= [&] (SString name
) {
21917 auto& state
= classState
.at(name
);
21919 begin(state
.methodChanges
),
21920 end(state
.methodChanges
),
21923 state
.cnsChanges
.reset();
21926 parallel::for_each(
21928 [&] (SString name
) {
21930 auto const& closures
=
21931 folly::get_default(index
.m_data
->classToClosures
, name
);
21932 for (auto const c
: closures
) resetClass(c
);
21935 parallel::for_each(
21937 [&] (SString name
) {
21938 funcState
.at(name
).changed
= Type::None
;
21939 auto const& closures
=
21940 folly::get_default(index
.m_data
->funcToClosures
, name
);
21941 for (auto const c
: closures
) resetClass(c
);
21942 if (auto const cns
= Constant::nameFromFuncName(name
)) {
21943 cnsChanged
.at(cns
).store(false, std::memory_order_release
);
21949 // Called when all analysis jobs are finished. "Finalize" the changes
21950 // and determine what should run in the next analysis round.
21951 void AnalysisScheduler::recordingDone() {
21957 void AnalysisScheduler::addClassToInput(SString name
,
21958 AnalysisInput
& input
) const {
21959 FTRACE(4, "AnalysisScheduler: adding class {} to input\n", name
);
21961 using K
= AnalysisInput::Kind
;
21962 auto k
= K::Rep
| K::Bytecode
;
21963 if (index
.m_data
->classInfoRefs
.count(name
)) {
21965 } else if (index
.m_data
->uninstantiableClsMethRefs
.count(name
)) {
21968 input
.meta
.badClasses
.emplace(name
);
21970 input
.classes
[name
] |= k
;
21972 if (!input
.m_key
) input
.m_key
= name
;
21975 void AnalysisScheduler::addFuncToInput(SString name
,
21976 AnalysisInput
& input
) const {
21977 FTRACE(4, "AnalysisScheduler: adding func {} to input\n", name
);
21979 using K
= AnalysisInput::Kind
;
21980 input
.funcs
[name
] |= (K::Rep
| K::Bytecode
| K::Info
);
21981 if (!input
.m_key
) input
.m_key
= name
;
21983 auto const& closures
= folly::get_default(index
.m_data
->funcToClosures
, name
);
21984 for (auto const clo
: closures
) addClassToInput(clo
, input
);
21987 void AnalysisScheduler::addUnitToInput(SString name
,
21988 AnalysisInput
& input
) const {
21989 FTRACE(4, "AnalysisScheduler: adding unit {} to input\n", name
);
21990 using K
= AnalysisInput::Kind
;
21991 input
.units
[name
] |= K::Rep
;
21992 if (!input
.m_key
) input
.m_key
= name
;
21995 void AnalysisScheduler::addDepClassToInput(SString cls
,
21998 AnalysisInput
& input
,
21999 bool fromClosureCtx
) const {
22000 using K
= AnalysisInput::Kind
;
22002 if (auto const unit
=
22003 folly::get_default(index
.m_data
->typeAliasToUnit
, cls
)) {
22004 addDepUnitToInput(unit
, depSrc
, input
);
22008 auto const old
= folly::get_default(input
.classes
, cls
);
22009 if (any(old
& K::Rep
)) return;
22011 auto const badClass
= [&] {
22012 if (input
.meta
.badClasses
.emplace(cls
).second
) {
22013 FTRACE(4, "AnalysisScheduler: adding bad class {} to {} dep inputs\n",
22018 auto const clsRef
= folly::get_ptr(index
.m_data
->classRefs
, cls
);
22020 if (!is_closure_name(cls
)) return badClass();
22021 assertx(!index
.m_data
->closureToFunc
.count(cls
));
22022 auto const ctx
= folly::get_default(index
.m_data
->closureToClass
, cls
);
22023 if (!ctx
) return badClass();
22024 if (fromClosureCtx
) return;
22025 return addDepClassToInput(ctx
, depSrc
, addBytecode
, input
);
22027 assertx(!index
.m_data
->closureToClass
.count(cls
));
22029 if (any(old
& K::Dep
)) {
22030 if (!addBytecode
|| any(old
& K::Bytecode
)) return;
22033 4, "AnalysisScheduler: adding class {} to {} dep inputs\n",
22036 input
.classes
[cls
] |= K::Dep
;
22039 if (auto const r
= folly::get_ptr(index
.m_data
->classInfoRefs
, cls
)) {
22040 input
.classes
[cls
] |= K::Info
;
22041 } else if (auto const r
=
22042 folly::get_ptr(index
.m_data
->uninstantiableClsMethRefs
, cls
)) {
22043 input
.classes
[cls
] |= K::MInfo
;
22048 if (addBytecode
&& !any(old
& K::Bytecode
)) {
22050 4, "AnalysisScheduler: adding class {} bytecode to {} dep inputs\n",
22053 input
.classes
[cls
] |= K::Bytecode
;
22056 addDepUnitToInput(index
.m_data
->classToUnit
.at(cls
), depSrc
, input
);
22058 if (is_closure_name(cls
)) {
22059 auto const f
= folly::get_default(index
.m_data
->closureToFunc
, cls
);
22061 if (fromClosureCtx
) return;
22062 return addDepFuncToInput(
22065 addBytecode
? Type::Bytecode
: Type::Meta
,
22071 void AnalysisScheduler::addDepFuncToInput(SString func
,
22074 AnalysisInput
& input
) const {
22075 assertx(type
!= Type::None
);
22077 using K
= AnalysisInput::Kind
;
22079 auto const old
= folly::get_default(input
.funcs
, func
);
22080 if (any(old
& K::Rep
)) return;
22082 auto const funcRef
= folly::get_ptr(index
.m_data
->funcRefs
, func
);
22084 if (input
.meta
.badFuncs
.emplace(func
).second
) {
22085 FTRACE(4, "AnalysisScheduler: adding bad func {} to {} dep inputs\n",
22091 if (any(old
& K::Dep
)) {
22092 if (!(type
& Type::Bytecode
) || any(old
& K::Bytecode
)) return;
22095 4, "AnalysisScheduler: adding func {} to {} dep inputs\n",
22098 input
.funcs
[func
] |= K::Dep
;
22101 input
.funcs
[func
] |= K::Info
;
22102 if ((type
& Type::Bytecode
) && !any(old
& K::Bytecode
)) {
22104 4, "AnalysisScheduler: adding func {} bytecode to {} dep inputs\n",
22107 input
.funcs
[func
] |= K::Bytecode
;
22110 addDepUnitToInput(index
.m_data
->funcToUnit
.at(func
), depSrc
, input
);
22112 auto const& closures
= folly::get_default(index
.m_data
->funcToClosures
, func
);
22113 for (auto const clo
: closures
) {
22114 addDepClassToInput(clo
, depSrc
, type
& Type::Bytecode
, input
, true);
22118 void AnalysisScheduler::addDepConstantToInput(SString cns
,
22120 AnalysisInput
& input
) const {
22121 auto const unit
= folly::get_ptr(index
.m_data
->constantToUnit
, cns
);
22123 if (input
.meta
.badConstants
.emplace(cns
).second
) {
22124 FTRACE(4, "AnalysisScheduler: adding bad constant {} to {} dep inputs\n",
22130 addDepUnitToInput(unit
->first
, depSrc
, input
);
22131 if (!unit
->second
|| is_native_unit(unit
->first
)) return;
22133 auto const initName
= Constant::funcNameFromName(cns
);
22134 always_assert_flog(
22135 index
.m_data
->funcRefs
.count(initName
),
22136 "Constant {} is missing expected initialization function {}",
22140 addDepFuncToInput(initName
, depSrc
, Type::Meta
, input
);
22143 void AnalysisScheduler::addDepUnitToInput(SString unit
,
22145 AnalysisInput
& input
) const {
22146 using K
= AnalysisInput::Kind
;
22147 auto const old
= folly::get_default(input
.units
, unit
);
22148 if (any(old
& (K::Rep
| K::Dep
))) return;
22149 FTRACE(4, "AnalysisScheduler: adding unit {} to {} dep inputs\n",
22151 input
.units
[unit
] |= K::Dep
;
22154 void AnalysisScheduler::addTraceDepToInput(const DepState
& d
,
22155 AnalysisInput
& input
) const {
22157 case DepState::Func
:
22158 addDepFuncToInput(d
.name
, d
.name
, Type::Bytecode
, input
);
22160 case DepState::Class
:
22161 addDepClassToInput(d
.name
, d
.name
, true, input
);
22163 case DepState::Unit
:
22164 addDepUnitToInput(d
.name
, d
.name
, input
);
22167 addDepsToInput(d
, input
);
22170 void AnalysisScheduler::addDepsToInput(const DepState
& deps
,
22171 AnalysisInput
& input
) const {
22172 auto const& i
= *index
.m_data
;
22174 switch (deps
.kind
) {
22175 case DepState::Func
:
22177 i
.funcToUnit
.at(deps
.name
),
22182 case DepState::Class
:
22184 i
.classToUnit
.at(deps
.name
),
22188 if (auto const bases
= folly::get_ptr(i
.classToCnsBases
, deps
.name
)) {
22189 for (auto const b
: *bases
) {
22190 addDepClassToInput(b
, deps
.name
, false, input
);
22194 case DepState::Unit
:
22198 stateForClassOrTypeAlias(deps
.name
).match(
22199 [&] (const ClassState
* s
) {
22201 for (auto const t
: s
->typeCnsNames
) {
22202 addDepClassToInput(t
, deps
.name
, false, input
);
22205 [&] (const UnitState
* s
) {
22207 for (auto const t
: s
->typeCnsNames
) {
22208 addDepClassToInput(t
, deps
.name
, false, input
);
22213 if (deps
.deps
.empty()) return;
22214 assertx(IMPLIES(deps
.kind
== DepState::Class
, !is_closure_name(deps
.name
)));
22215 auto const& d
= deps
.deps
;
22217 for (auto const cls
: d
.classes
) {
22218 addDepClassToInput(cls
, deps
.name
, false, input
);
22220 for (auto const cls
: d
.typeCnsClasses
) {
22221 addDepClassToInput(cls
, deps
.name
, false, input
);
22223 for (auto const [meth
, type
] : d
.methods
) {
22224 if (type
!= Type::None
) {
22225 addDepClassToInput(
22228 type
& Type::Bytecode
,
22233 for (auto const [func
, type
] : d
.funcs
) {
22234 if (type
!= Type::None
) addDepFuncToInput(func
, deps
.name
, type
, input
);
22236 for (auto const cns
: d
.clsConstants
) {
22237 addDepClassToInput(cns
.cls
, deps
.name
, false, input
);
22239 for (auto const cns
: d
.typeCnsClsConstants
) {
22240 addDepClassToInput(cns
.cls
, deps
.name
, false, input
);
22242 for (auto const cls
: d
.anyClsConstants
) {
22243 addDepClassToInput(cls
, deps
.name
, false, input
);
22244 if (auto const bases
= folly::get_ptr(i
.classToCnsBases
, cls
)) {
22245 for (auto const b
: *bases
) addDepClassToInput(b
, deps
.name
, false, input
);
22248 for (auto const cls
: d
.typeCnsAnyClsConstants
) {
22249 addDepClassToInput(cls
, deps
.name
, false, input
);
22250 if (auto const bases
= folly::get_ptr(i
.classToCnsBases
, cls
)) {
22251 for (auto const b
: *bases
) addDepClassToInput(b
, deps
.name
, false, input
);
22254 for (auto const cns
: d
.constants
) {
22255 addDepConstantToInput(cns
, deps
.name
, input
);
22259 // For every input in the AnalysisInput, add any associated
22260 // dependencies for those inputs.
22261 void AnalysisScheduler::addAllDepsToInput(AnalysisInput
& input
) const {
22262 using K
= AnalysisInput::Kind
;
22264 auto const names
= [] (auto const& m
) {
22265 using namespace folly::gen
;
22267 | map([] (auto const& p
) { return p
.first
; })
22268 | as
<std::vector
>();
22271 // We can't just iterate over the containers because addDepsToInput
22272 // may add elements to them.
22273 for (auto const name
: names(input
.classes
)) {
22274 auto const k
= input
.classes
.at(name
);
22275 if (!any(k
& K::Rep
)) continue;
22276 addDepsToInput(classState
.at(name
).depState
, input
);
22278 for (auto const name
: names(input
.funcs
)) {
22279 auto const k
= input
.funcs
.at(name
);
22280 if (!any(k
& K::Rep
)) continue;
22281 addDepsToInput(funcState
.at(name
).depState
, input
);
22283 for (auto const name
: names(input
.units
)) {
22284 auto const k
= input
.units
.at(name
);
22285 if (!any(k
& K::Rep
)) continue;
22286 addDepsToInput(unitState
.at(name
).depState
, input
);
22289 // These are automatically included everywhere:
22290 for (auto const f
: special_builtins()) {
22291 addDepFuncToInput(f
, f
, Type::Meta
, input
);
22293 // Wait handle information must always be available.
22294 addDepClassToInput(s_Awaitable
.get(), s_Awaitable
.get(), false, input
);
22295 addDepClassToInput(s_Traversable
.get(), s_Traversable
.get(), false, input
);
22298 void AnalysisScheduler::addDepsMeta(const DepState
& deps
,
22299 AnalysisInput
& input
) const {
22300 auto& d
= [&] () -> AnalysisDeps
& {
22301 switch (deps
.kind
) {
22302 case DepState::Func
: return input
.meta
.funcDeps
[deps
.name
];
22303 case DepState::Class
: return input
.meta
.classDeps
[deps
.name
];
22304 case DepState::Unit
: return input
.meta
.unitDeps
[deps
.name
];
22310 // Call F for any dependency which should be included
22311 // transitively. This usually means the dependency has information
22312 // that can change between rounds. This is a subset of a DepState's
22313 // total dependencies.
22314 template <typename F
>
22315 void AnalysisScheduler::onTransitiveDep(SString name
,
22316 const DepState
& state
,
22317 const F
& f
) const {
22318 if (state
.deps
.empty()) return;
22319 auto const& d
= state
.deps
;
22320 auto const& i
= *index
.m_data
;
22322 auto const filter
= [&] (SString n
) {
22323 if (!n
->tsame(name
)) f(n
);
22326 auto const onFunc
= [&] (SString n
) {
22327 if (!Constant::nameFromFuncName(n
)) return filter(n
);
22328 auto const u
= folly::get_default(i
.funcToUnit
, n
);
22332 auto const onCls
= [&] (SString n
) {
22333 if (auto const c
= folly::get_default(i
.closureToClass
, n
)) {
22336 if (auto const f
= folly::get_default(i
.closureToFunc
, n
)) {
22342 auto const onClsOrTypeAlias
= [&] (SString n
) {
22343 if (auto const u
= folly::get_default(i
.typeAliasToUnit
, n
)) {
22350 stateForClassOrTypeAlias(state
.name
).match(
22351 [&] (const ClassState
* s
) {
22353 for (auto const t
: s
->typeCnsNames
) onClsOrTypeAlias(t
);
22355 [&] (const UnitState
* s
) {
22357 for (auto const t
: s
->typeCnsNames
) onClsOrTypeAlias(t
);
22360 if (auto const bases
= folly::get_ptr(i
.classToCnsBases
, state
.name
)) {
22361 for (auto const b
: *bases
) onCls(b
);
22364 for (auto const [meth
, type
] : d
.methods
) {
22365 if (type
== Type::None
) continue;
22366 if (!(type
& AnalysisDeps::kValidForChanges
)) continue;
22369 for (auto const [func
, type
] : d
.funcs
) {
22370 if (!(type
& AnalysisDeps::kValidForChanges
)) continue;
22373 for (auto const cls
: d
.anyClsConstants
) {
22374 stateForClassOrTypeAlias(cls
).match(
22375 [&] (const ClassState
* s
) {
22376 if (s
&& !s
->allCnsFixed
) onCls(cls
);
22378 [&] (const UnitState
* s
) {
22379 if (s
&& !s
->fixed
) filter(s
->depState
.name
);
22383 for (auto const cls
: d
.typeCnsAnyClsConstants
) onClsOrTypeAlias(cls
);
22385 for (auto const cns
: d
.clsConstants
) {
22386 stateForClassOrTypeAlias(cns
.cls
).match(
22387 [&] (const ClassState
* s
) {
22388 if (!s
|| s
->allCnsFixed
) return;
22389 if (cns
.idx
>= s
->cnsFixed
.size() || !s
->cnsFixed
[cns
.idx
]) {
22393 [&] (const UnitState
* s
) {
22394 if (s
&& !s
->fixed
) filter(s
->depState
.name
);
22398 for (auto const cns
: d
.typeCnsClsConstants
) onClsOrTypeAlias(cns
.cls
);
22400 for (auto const cns
: d
.constants
) onFunc(Constant::funcNameFromName(cns
));
22402 for (auto const ta
: d
.classes
) {
22403 auto const unit
= folly::get_default(i
.typeAliasToUnit
, ta
);
22404 if (!unit
) continue;
22405 auto const p
= folly::get_ptr(unitState
, unit
);
22406 if (!p
|| p
->fixed
) continue;
22409 for (auto const c
: d
.typeCnsClasses
) onClsOrTypeAlias(c
);
22412 void AnalysisScheduler::addAllDeps(TSStringSet
& out
,
22413 const DepState
& state
) const {
22414 if (state
.deps
.empty()) return;
22415 auto const& d
= state
.deps
;
22416 auto const& i
= *index
.m_data
;
22418 auto const add
= [&] (SString n
) { out
.emplace(n
); };
22420 auto const onFunc
= [&] (SString n
) {
22421 if (!Constant::nameFromFuncName(n
)) return add(n
);
22422 auto const u
= folly::get_default(i
.funcToUnit
, n
);
22426 auto const onCls
= [&] (SString n
) {
22427 if (auto const c
= folly::get_default(i
.closureToClass
, n
)) {
22430 if (auto const f
= folly::get_default(i
.closureToFunc
, n
)) {
22436 auto const onClsOrTypeAlias
= [&] (SString n
) {
22437 if (auto const u
= folly::get_default(i
.typeAliasToUnit
, n
)) {
22444 stateForClassOrTypeAlias(state
.name
).match(
22445 [&] (const ClassState
* s
) {
22447 for (auto const t
: s
->typeCnsNames
) onClsOrTypeAlias(t
);
22449 [&] (const UnitState
* s
) {
22451 for (auto const t
: s
->typeCnsNames
) onClsOrTypeAlias(t
);
22454 if (auto const bases
= folly::get_ptr(i
.classToCnsBases
, state
.name
)) {
22455 for (auto const b
: *bases
) onCls(b
);
22458 for (auto const cls
: d
.classes
) onClsOrTypeAlias(cls
);
22459 for (auto const cls
: d
.typeCnsClasses
) onClsOrTypeAlias(cls
);
22461 for (auto const [meth
, type
] : d
.methods
) {
22462 if (type
!= Type::None
) onCls(meth
.cls
);
22464 for (auto const [func
, type
] : d
.funcs
) {
22465 if (type
!= Type::None
) onFunc(func
);
22468 for (auto const cls
: d
.anyClsConstants
) {
22469 onClsOrTypeAlias(cls
);
22470 if (auto const bases
= folly::get_ptr(i
.classToCnsBases
, cls
)) {
22471 for (auto const b
: *bases
) onCls(b
);
22474 for (auto const cls
: d
.typeCnsAnyClsConstants
) {
22475 onClsOrTypeAlias(cls
);
22476 if (auto const bases
= folly::get_ptr(i
.classToCnsBases
, cls
)) {
22477 for (auto const b
: *bases
) onCls(b
);
22480 for (auto const cns
: d
.clsConstants
) onClsOrTypeAlias(cns
.cls
);
22481 for (auto const cns
: d
.typeCnsClsConstants
) onClsOrTypeAlias(cns
.cls
);
22483 for (auto const cns
: d
.constants
) onFunc(Constant::funcNameFromName(cns
));
22486 // Dump dependencies of any trace classes or functions.
22487 void AnalysisScheduler::maybeDumpTraces() const {
22488 if (!Trace::moduleEnabledRelease(Trace::hhbbc_dump_trace
, 1)) return;
22490 TSStringSet allRoots
;
22491 TSStringSet allTraces
;
22492 TSStringSet allDeps
;
22493 TSStringToOneT
<TSStringSet
> allTraceEdges
;
22494 TSStringToOneT
<TSStringSet
> allDepEdges
;
22496 parallel::for_each(
22498 [&] (SString name
) {
22499 Trace::BumpRelease bumper
{
22500 Trace::hhbbc_dump_trace
,
22502 auto& state
= traceState
.at(name
);
22503 int bump
= std::numeric_limits
<int>::max();
22504 for (auto const d
: state
.depStates
) {
22505 SString cls
= nullptr;
22506 SString func
= nullptr;
22507 SString unit
= nullptr;
22509 case DepState::Func
:
22511 unit
= folly::get_default(index
.m_data
->funcToUnit
, d
->name
);
22513 case DepState::Class
:
22515 unit
= folly::get_default(index
.m_data
->classToUnit
, d
->name
);
22517 case DepState::Unit
:
22521 bump
= std::min(bump
, trace_bump_for(cls
, func
, unit
));
22523 assertx(bump
< std::numeric_limits
<int>::max());
22527 if (!Trace::moduleEnabledRelease(Trace::hhbbc_dump_trace
, 2)) return;
22529 TSStringSet traces
;
22531 TSStringToOneT
<TSStringSet
> traceEdges
;
22532 TSStringToOneT
<TSStringSet
> depEdges
;
22534 std::vector
<SString
> worklist
;
22535 worklist
.emplace_back(name
);
22536 traces
.emplace(name
);
22538 while (!worklist
.empty()) {
22539 auto const from
= worklist
.back();
22540 worklist
.pop_back();
22541 auto const s
= folly::get_ptr(traceState
, from
);
22543 for (auto const d
: s
->depStates
) {
22547 if (from
->tsame(to
)) return;
22548 if (!traceState
.count(to
)) return;
22549 traceEdges
[from
].emplace(to
);
22550 if (!traces
.emplace(to
).second
) return;
22551 worklist
.emplace_back(to
);
22557 for (auto const n
: traces
) {
22558 auto const s
= folly::get_ptr(traceState
, n
);
22560 for (auto const d
: s
->depStates
) {
22561 TSStringSet newDeps
;
22562 addAllDeps(newDeps
, *d
);
22563 for (auto const n2
: newDeps
) {
22564 if (n
->tsame(n2
)) continue;
22565 depEdges
[n
].emplace(n2
);
22567 deps
.insert(begin(newDeps
), end(newDeps
));
22571 std::lock_guard
<std::mutex
> _
{*lock
};
22572 allRoots
.emplace(name
);
22573 allTraces
.insert(begin(traces
), end(traces
));
22574 allDeps
.insert(begin(deps
), end(deps
));
22575 for (auto const& [n
, e
] : traceEdges
) {
22576 allTraceEdges
[n
].insert(begin(e
), end(e
));
22578 for (auto const& [n
, e
] : depEdges
) {
22579 allDepEdges
[n
].insert(begin(e
), end(e
));
22584 if (allRoots
.empty() && allTraces
.empty() && allDeps
.empty()) return;
22587 TSStringToOneT
<size_t> ids
;
22588 auto const id
= [&] (SString n
) {
22589 if (auto const i
= folly::get_ptr(ids
, n
)) return *i
;
22590 ids
.emplace(n
, nextId
);
22594 using namespace folly::gen
;
22596 auto const nodes
= [&] (const TSStringSet
& ns
,
22598 const TSStringSet
* f1
= nullptr,
22599 const TSStringSet
* f2
= nullptr) {
22601 | filter([&] (SString n
) { return !f1
|| !f1
->count(n
); })
22602 | filter([&] (SString n
) { return !f2
|| !f2
->count(n
); })
22603 | orderBy(folly::identity
, string_data_lt_type
{})
22604 | map([&] (SString n
) {
22605 return folly::sformat(
22606 " {} [label=\"{}\" color={}];",
22607 id(n
), folly::cEscape
<std::string
>(n
->slice()), color
22610 | unsplit
<std::string
>("\n");
22613 auto const edges
= [&] (const TSStringToOneT
<TSStringSet
>& es
,
22615 const TSStringToOneT
<TSStringSet
>* f1
= nullptr) {
22616 std::vector
<SString
> fromE
;
22617 for (auto const& [n
, _
] : es
) fromE
.emplace_back(n
);
22618 std::sort(begin(fromE
), end(fromE
), string_data_lt_type
{});
22620 | map([&] (SString n
) {
22621 return folly::sformat(
22622 " {} -> {{{}}} [color={}];",
22625 | filter([&] (SString n2
) {
22626 if (!f1
) return true;
22627 auto const t
= folly::get_ptr(*f1
, n
);
22628 return !t
|| !t
->count(n2
);
22630 | orderBy(folly::identity
, string_data_lt_type
{})
22631 | map([&] (SString n2
) { return folly::sformat("{}", id(n2
)); })
22632 | unsplit
<std::string
>(" "),
22636 | unsplit
<std::string
>("\n");
22639 namespace fs
= std::filesystem
;
22641 auto const path
= [] {
22642 auto const base
= [] {
22643 if (auto const e
= getenv("HHBBC_DUMP_DIR")) return fs::path(e
);
22644 return fs::temp_directory_path();
22646 return fs::weakly_canonical(
22647 base
/ boost::filesystem::unique_path("hhbbc-%%%%%%%%").native()
22651 std::ofstream out
{path
};
22652 out
.exceptions(std::ofstream::failbit
| std::ofstream::badbit
);
22653 out
<< folly::format(
22654 "digraph \"Round #{}\" {{\n"
22662 nodes(allRoots
, "red"),
22663 nodes(allTraces
, "blue", &allRoots
),
22664 nodes(allDeps
, "gray", &allRoots
, &allTraces
),
22665 edges(allTraceEdges
, "blue"),
22666 edges(allDepEdges
, "gray", &allTraceEdges
)
22669 std::cout
<< "Trace dump for round #" << round
22670 << " written to " << path
22674 // First pass: initialize all data in TraceStates.
22675 void AnalysisScheduler::tracePass1() {
22676 untracked
= Untracked
{};
22678 std::atomic
<size_t> idx
{0};
22679 parallel::for_each(
22682 auto& state
= traceState
.at(n
);
22683 state
.eligible
= false;
22684 state
.leaf
.store(true);
22685 state
.covered
.store(false);
22686 state
.trace
.clear();
22687 state
.deps
.clear();
22688 state
.buckets
.present
= nullptr;
22689 state
.buckets
.withBC
= nullptr;
22690 state
.buckets
.process
= nullptr;
22696 // Second pass: Build traces for each entity and mark eligible.
22697 void AnalysisScheduler::tracePass2() {
22698 parallel::for_each(
22700 [&] (SString name
) {
22701 auto& state
= traceState
.at(name
);
22703 // Build the trace up by using the transitive dependencies.
22704 std::vector
<SString
> worklist
;
22705 auto const add
= [&] (SString n
) {
22706 if (!traceState
.count(n
)) return;
22707 if (!state
.trace
.emplace(n
).second
) return;
22708 if (n
->tsame(name
)) return;
22709 worklist
.emplace_back(n
);
22712 worklist
.emplace_back(name
);
22713 while (!worklist
.empty()) {
22714 auto const n
= worklist
.back();
22715 worklist
.pop_back();
22716 auto const s
= folly::get_ptr(traceState
, n
);
22718 for (auto const d
: s
->depStates
) onTransitiveDep(n
, *d
, add
);
22721 // An entity is eligible if any of its components changed, or if
22722 // anything in the trace is eligible.
22723 auto const eligible
= [&] (const TraceState
& t
) {
22724 return std::any_of(
22725 t
.depStates
.begin(), t
.depStates
.end(),
22726 [] (const DepState
* d
) { return d
->toSchedule
; }
22733 begin(state
.trace
), end(state
.trace
),
22735 auto const s
= folly::get_ptr(traceState
, n
);
22736 return s
&& eligible(*s
);
22743 // Third pass: "Expand" every TraceState with it's total dependencies,
22744 // which is a superset of those in the trace.
22745 void AnalysisScheduler::tracePass3() {
22746 parallel::for_each(
22748 [&] (SString name
) {
22749 auto& state
= traceState
.at(name
);
22751 if (!state
.eligible
) return;
22756 auto const s
= folly::get_ptr(traceState
, n
);
22757 return !s
|| !s
->eligible
;
22761 auto const expand
= [&] (SString n
) {
22762 auto const s
= folly::get_ptr(traceState
, n
);
22764 for (auto const d
: s
->depStates
) addAllDeps(state
.deps
, *d
);
22767 state
.deps
= state
.trace
;
22769 for (auto const n
: state
.trace
) expand(n
);
22774 // Fourth pass: Determine leafs for the purpose of scheduling.
22775 std::vector
<SString
> AnalysisScheduler::tracePass4() {
22776 // A leaf here means that nothing else depends on it.
22778 // Mark any TraceState which is on another TraceState's trace as not
22780 parallel::for_each(
22782 [&] (SString name
) {
22783 auto& state
= traceState
.at(name
);
22784 if (!state
.eligible
) return;
22785 for (auto const n
: state
.trace
) {
22786 auto const s
= folly::get_ptr(traceState
, n
);
22787 if (s
) s
->leaf
.store(false);
22792 // Mark "covered" TraceStates. A covered TraceState is processed and
22793 // shouldn't be considered anymore in this function.
22794 std::vector
<SString
> traceLeafs
;
22795 parallel::for_each(
22797 [&] (SString name
) {
22798 auto& state
= traceState
.at(name
);
22799 if (!state
.eligible
|| !state
.leaf
) return;
22801 // Right now a TraceState is covered if it's a leaf, along with
22802 // anything on this TraceState's trace.
22803 state
.covered
.store(true);
22804 for (auto const n
: state
.trace
) {
22805 auto const s
= folly::get_ptr(traceState
, n
);
22806 if (s
) s
->covered
.store(true);
22809 std::lock_guard
<std::mutex
> _
{*lock
};
22810 traceLeafs
.emplace_back(name
);
22814 // Any uncovered TraceState's must now be part of a cycle (or depend
22815 // on a TraceState in a cycle).
22816 std::vector
<SString
> traceInCycle
;
22817 parallel::for_each(
22820 auto const& state
= traceState
.at(n
);
22821 if (!state
.eligible
|| state
.covered
) return;
22822 assertx(!state
.leaf
);
22823 std::lock_guard
<std::mutex
> _
{*lock
};
22824 traceInCycle
.emplace_back(n
);
22828 // Sort all TraceStates in a cycle. This isn't strictly necessary,
22829 // but leads to better results.
22831 begin(traceInCycle
),
22833 [&] (SString n1
, SString n2
) {
22834 auto const& state1
= traceState
.at(n1
);
22835 auto const& state2
= traceState
.at(n2
);
22836 assertx(state1
.eligible
);
22837 assertx(!state1
.covered
);
22838 assertx(!state1
.leaf
);
22839 assertx(state2
.eligible
);
22840 assertx(!state2
.covered
);
22841 assertx(!state2
.leaf
);
22842 if (state1
.trace
.size() > state2
.trace
.size()) return true;
22843 if (state1
.trace
.size() < state2
.trace
.size()) return false;
22844 return string_data_lt_type
{}(n1
, n2
);
22848 // Pick an arbitrary TraceState from the cycle set, declare it a
22849 // leaf by fiat, then mark it and any trace dependencies as being
22850 // covered. This process repeats until all TraceStates are covered.
22851 for (auto const name
: traceInCycle
) {
22852 auto& s
= traceState
.at(name
);
22853 if (s
.covered
) continue;
22855 s
.leaf
.store(true);
22856 s
.covered
.store(true);
22857 for (auto const n
: s
.trace
) {
22858 auto const s2
= folly::get_ptr(traceState
, n
);
22859 if (!s2
|| s2
->covered
) continue;
22860 assertx(!s2
->leaf
);
22861 s2
->covered
.store(true);
22863 traceLeafs
.emplace_back(name
);
22867 // Every TraceState at this point should be covered (or not
22869 parallel::for_each(
22872 auto const s
= folly::get_ptr(traceState
, n
);
22874 always_assert_flog(
22875 s
->covered
|| !s
->eligible
,
22876 "{} is not covered", n
22885 // Fifth pass: Assign leafs and their dependencies to
22886 // buckets. Non-leafs (which must have some TraceState depending on
22887 // them) will be assigned to one of the buckets of the TraceState that
22889 std::vector
<AnalysisScheduler::Bucket
>
22890 AnalysisScheduler::tracePass5(size_t bucketSize
,
22891 size_t maxBucketSize
,
22892 std::vector
<SString
> leafs
) {
22893 using namespace folly::gen
;
22895 auto w
= assign_hierarchical_work(
22901 return std::make_pair(&traceState
.at(n
).deps
, true);
22903 [&] (const TSStringSet
& roots
,
22905 SString n
) -> Optional
<size_t> {
22906 // To determine whether we want to promote this TraceState or
22907 // not, we need to check if it's present in any of the bucket's
22908 // traces. This can be expensive to calculate, so utilize
22909 // caching. Only recalculate the set if we're referring to a
22910 // different bucket than last call.
22911 thread_local Optional
<size_t> last
;
22912 thread_local TSStringSet inTrace
;
22913 if (!last
|| *last
!= bucketIdx
) {
22915 for (auto const root
: roots
) {
22916 auto const& state
= traceState
.at(root
);
22917 inTrace
.insert(begin(state
.trace
), end(state
.trace
));
22922 // If the TraceState is a leaf, or isn't in any of the traces
22923 // for this bucket, we don't want to promote it.
22924 auto const s
= folly::get_ptr(traceState
, n
);
22925 if (!s
|| !s
->eligible
|| s
->leaf
.load() || !inTrace
.count(n
)) {
22926 return std::nullopt
;
22933 // Sanity check that every eligible TraceState belongs to at least
22935 TSStringSet inputs
;
22936 for (auto const& b
: w
) {
22937 inputs
.insert(begin(b
.classes
), end(b
.classes
));
22940 parallel::for_each(
22943 auto const s
= folly::get_ptr(traceState
, n
);
22945 if (!s
->eligible
) return;
22946 always_assert_flog(
22948 "{} should be present in at least one bucket's inputs, but is not",
22955 using H
= HierarchicalWorkBucket
;
22958 | map([] (H
&& b
) { return Bucket
{std::move(b
)}; })
22959 | as
<std::vector
>();
22962 // Sixth pass: Make AnalysisInputs for each bucket.
22963 AnalysisScheduler::InputsAndUntracked
22964 AnalysisScheduler::tracePass6(const std::vector
<Bucket
>& buckets
) {
22965 TSStringToOneConcurrentT
<std::nullptr_t
> seenClasses
;
22966 FSStringToOneConcurrentT
<std::nullptr_t
> seenFuncs
;
22967 SStringToOneConcurrentT
<std::nullptr_t
> seenUnits
;
22968 SStringToOneConcurrentT
<std::nullptr_t
> seenConstants
;
22970 auto outputs
= parallel::gen(
22973 auto const& b
= buckets
[idx
].b
;
22974 assertx(b
.uninstantiable
.empty());
22976 TSStringSet inTrace
;
22978 // Make an AnalysisInput by adding all the appropriate inputs
22979 // for each TraceState and dependencies.
22980 AnalysisInput input
;
22981 input
.index
= index
.m_data
.get();
22982 input
.meta
.bucketIdx
= idx
;
22984 for (auto const item
: b
.classes
) {
22985 auto const& state
= traceState
.at(item
);
22986 assertx(!state
.depStates
.empty());
22987 for (auto const d
: state
.depStates
) {
22989 case DepState::Func
:
22990 addFuncToInput(d
->name
, input
);
22992 case DepState::Class
:
22993 addClassToInput(d
->name
, input
);
22995 case DepState::Unit
:
22996 addUnitToInput(d
->name
, input
);
23000 if (!d
->toSchedule
) addDepsMeta(*d
, input
);
23003 inTrace
.insert(begin(state
.trace
), end(state
.trace
));
23006 for (auto const item
: b
.deps
) {
23007 auto const s
= folly::get_ptr(traceState
, item
);
23008 if (!s
|| !s
->eligible
|| !inTrace
.count(item
)) continue;
23009 assertx(!s
->depStates
.empty());
23011 for (auto const d
: s
->depStates
) {
23012 addTraceDepToInput(*d
, input
);
23014 if (!d
->toSchedule
) {
23015 addDepsMeta(*d
, input
);
23020 case DepState::Func
:
23021 input
.meta
.processDepFunc
.emplace(d
->name
);
23023 case DepState::Class
:
23024 input
.meta
.processDepCls
.emplace(d
->name
);
23026 case DepState::Unit
:
23027 input
.meta
.processDepUnit
.emplace(d
->name
);
23033 addAllDepsToInput(input
);
23035 InputsAndUntracked out
;
23037 using K
= AnalysisInput::Kind
;
23039 // Record untracked items. These are inputs to this bucket which
23040 // do not have TraceState. They must be dealt with differently
23042 auto const untracked
= [&] (auto const& i1
,
23046 using D1
= std::decay_t
<decltype(i1
)>;
23047 using D2
= std::decay_t
<decltype(i2
)>;
23049 std::vector
<SString
> out
;
23051 if constexpr (!std::is_same_v
<D1
, std::nullptr_t
>) {
23052 for (auto const& [n
, k
] : i1
) {
23053 if (!any(k
& K::Dep
)) continue;
23054 if (std::invoke(t
, this, n
)) continue;
23055 if (!s
.try_emplace(n
).second
) continue;
23056 out
.emplace_back(n
);
23059 if constexpr (!std::is_same_v
<D2
, std::nullptr_t
>) {
23060 for (auto const n
: i2
) {
23061 if (std::invoke(t
, this, n
)) continue;
23062 if (!s
.try_emplace(n
).second
) continue;
23063 out
.emplace_back(n
);
23069 using A
= AnalysisScheduler
;
23070 out
.untrackedClasses
= untracked(
23072 input
.meta
.badClasses
,
23076 out
.untrackedFuncs
= untracked(
23078 input
.meta
.badFuncs
,
23082 out
.untrackedUnits
= untracked(
23088 out
.badConstants
= untracked(
23090 input
.meta
.badConstants
,
23092 &A::traceForConstant
23095 out
.inputs
.emplace_back(std::move(input
));
23100 // We created untracked data for each bucket. We must now combine
23101 // that into one unified set.
23103 auto const combine
= [&] (auto f
, auto& m
) {
23104 std::vector
<SString
> out
;
23105 for (auto const& output
: outputs
) {
23106 auto& in
= output
.*f
;
23107 out
.insert(end(out
), begin(in
), end(in
));
23109 for (auto const n
: out
) m
.try_emplace(n
);
23113 using I
= InputsAndUntracked
;
23115 parallel::parallel(
23117 combined
.inputs
.reserve(outputs
.size());
23118 for (auto& output
: outputs
) {
23119 assertx(output
.inputs
.size() == 1);
23120 combined
.inputs
.emplace_back(std::move(output
.inputs
[0]));
23124 combined
.untrackedClasses
=
23125 combine(&I::untrackedClasses
, untracked
.classes
);
23128 combined
.untrackedFuncs
=
23129 combine(&I::untrackedFuncs
, untracked
.funcs
);
23132 combined
.untrackedUnits
=
23133 combine(&I::untrackedUnits
, untracked
.units
);
23136 combined
.badConstants
=
23137 combine(&I::badConstants
, untracked
.badConstants
);
23141 combined
.inputs
.shrink_to_fit();
23142 combined
.untrackedClasses
.shrink_to_fit();
23143 combined
.untrackedFuncs
.shrink_to_fit();
23144 combined
.untrackedUnits
.shrink_to_fit();
23145 combined
.badConstants
.shrink_to_fit();
23149 // Seventh pass: Calculate BucketSets for each TraceSet. This will
23150 // determine how different items can utilize information from another.
23151 void AnalysisScheduler::tracePass7(InputsAndUntracked
& inputs
) {
23152 using A
= AnalysisInput
;
23155 auto const onClass
= [&] (SString name
,
23156 A::BucketSet
& present
,
23157 A::BucketSet
& withBC
,
23158 A::BucketSet
& process
) {
23159 for (size_t i
= 0, size
= inputs
.inputs
.size(); i
< size
; ++i
) {
23160 auto const& input
= inputs
.inputs
[i
];
23161 auto const k
= folly::get_default(input
.classes
, name
);
23162 if (any(k
& K::Rep
)) {
23163 assertx(any(k
& K::Bytecode
));
23167 if (any(k
& K::Dep
)) {
23169 if (input
.meta
.classDeps
.count(name
) ||
23170 input
.meta
.processDepCls
.count(name
)) {
23171 assertx(any(k
& K::Bytecode
));
23175 if (any(k
& K::Bytecode
)) withBC
.add(i
);
23176 if (input
.meta
.badClasses
.count(name
)) present
.add(i
);
23180 auto const onFunc
= [&] (SString name
,
23181 A::BucketSet
& present
,
23182 A::BucketSet
& withBC
,
23183 A::BucketSet
& process
) {
23184 for (size_t i
= 0, size
= inputs
.inputs
.size(); i
< size
; ++i
) {
23185 auto const& input
= inputs
.inputs
[i
];
23186 auto const k
= folly::get_default(input
.funcs
, name
);
23187 if (any(k
& K::Rep
)) {
23188 assertx(any(k
& K::Bytecode
));
23192 if (any(k
& K::Dep
)) {
23194 if (input
.meta
.funcDeps
.count(name
) ||
23195 input
.meta
.processDepFunc
.count(name
)) {
23196 assertx(any(k
& K::Bytecode
));
23200 if (any(k
& K::Bytecode
)) withBC
.add(i
);
23201 if (input
.meta
.badFuncs
.count(name
)) present
.add(i
);
23205 auto const onUnit
= [&] (SString name
,
23206 A::BucketSet
& present
,
23208 A::BucketSet
& process
) {
23209 for (size_t i
= 0, size
= inputs
.inputs
.size(); i
< size
; ++i
) {
23210 auto const& input
= inputs
.inputs
[i
];
23211 auto const k
= folly::get_default(input
.units
, name
);
23212 if (any(k
& K::Rep
)) {
23216 if (any(k
& K::Dep
)) {
23218 if (input
.meta
.unitDeps
.count(name
) ||
23219 input
.meta
.processDepUnit
.count(name
)) {
23226 parallel::for_each(
23228 [&] (SString name
) {
23229 auto& state
= traceState
.at(name
);
23230 state
.trace
.clear();
23231 state
.deps
.clear();
23233 A::BucketSet present
;
23234 A::BucketSet withBC
;
23235 A::BucketSet process
;
23238 A::BucketSet temp1
, temp2
, temp3
;
23239 for (auto const d
: state
.depStates
) {
23243 auto& s1
= first
? present
: temp1
;
23244 auto& s2
= first
? withBC
: temp2
;
23245 auto& s3
= first
? process
: temp3
;
23247 case DepState::Func
:
23248 onFunc(d
->name
, s1
, s2
, s3
);
23250 case DepState::Class
:
23251 onClass(d
->name
, s1
, s2
, s3
);
23253 case DepState::Unit
:
23254 onUnit(d
->name
, s1
, s2
, s3
);
23266 assertx(!state
.buckets
.present
);
23267 assertx(!state
.buckets
.withBC
);
23268 assertx(!state
.buckets
.process
);
23270 state
.buckets
.present
= A::BucketSet::intern(std::move(present
));
23271 state
.buckets
.withBC
= A::BucketSet::intern(std::move(withBC
));
23272 state
.buckets
.process
= A::BucketSet::intern(std::move(process
));
23276 // Do the same for untracked items:
23278 auto const addUntracked
= [&] (const std::vector
<SString
>& v
,
23281 parallel::for_each(
23283 [&] (SString name
) {
23284 auto& state
= m
.at(name
);
23286 A::BucketSet present
;
23287 A::BucketSet withBC
;
23288 A::BucketSet process
;
23289 o(name
, present
, withBC
, process
);
23291 assertx(!present
.empty());
23292 assertx(process
.empty());
23294 assertx(!state
.present
);
23295 assertx(!state
.withBC
);
23296 assertx(!state
.process
);
23298 state
.present
= A::BucketSet::intern(std::move(present
));
23299 state
.withBC
= A::BucketSet::intern(std::move(withBC
));
23300 state
.process
= A::BucketSet::intern(std::move(process
));
23304 addUntracked(inputs
.untrackedClasses
, untracked
.classes
, onClass
);
23305 addUntracked(inputs
.untrackedFuncs
, untracked
.funcs
, onFunc
);
23306 addUntracked(inputs
.untrackedUnits
, untracked
.units
, onUnit
);
23308 inputs
.badConstants
,
23309 untracked
.badConstants
,
23311 A::BucketSet
& present
,
23314 for (size_t i
= 0, size
= inputs
.inputs
.size(); i
< size
; ++i
) {
23315 auto const& input
= inputs
.inputs
[i
];
23316 if (input
.meta
.badConstants
.count(name
)) {
23323 decltype(inputs
.untrackedClasses
){}.swap(inputs
.untrackedClasses
);
23324 decltype(inputs
.untrackedFuncs
){}.swap(inputs
.untrackedFuncs
);
23325 decltype(inputs
.untrackedUnits
){}.swap(inputs
.untrackedUnits
);
23326 decltype(inputs
.badConstants
){}.swap(inputs
.badConstants
);
23329 // Eighth pass: Store BucketSets in each AnalysisInput's metadata.
23330 void AnalysisScheduler::tracePass8(std::vector
<Bucket
> buckets
,
23331 std::vector
<AnalysisInput
>& inputs
) {
23335 auto& input
= inputs
[i
];
23336 assertx(i
< buckets
.size());
23337 auto& bucket
= buckets
[i
].b
;
23339 TSStringSet seenClasses
;
23340 FSStringSet seenFuncs
;
23341 SStringSet seenUnits
;
23342 SStringSet seenBadConstants
;
23344 for (auto const item
: bucket
.classes
) {
23345 auto& state
= traceState
.at(item
);
23346 assertx(!state
.depStates
.empty());
23347 assertx(state
.buckets
.present
->contains(i
));
23348 assertx(state
.buckets
.process
->contains(i
));
23350 for (auto const d
: state
.depStates
) {
23352 case DepState::Func
:
23353 always_assert(seenFuncs
.emplace(d
->name
).second
);
23354 input
.meta
.funcBuckets
.emplace_back(d
->name
, state
.buckets
);
23356 case DepState::Class
:
23357 always_assert(seenClasses
.emplace(d
->name
).second
);
23358 input
.meta
.classBuckets
.emplace_back(d
->name
, state
.buckets
);
23360 case DepState::Unit
:
23361 always_assert(seenUnits
.emplace(d
->name
).second
);
23362 input
.meta
.unitBuckets
.emplace_back(d
->name
, state
.buckets
);
23368 for (auto const item
: bucket
.deps
) {
23369 auto const s
= folly::get_ptr(traceState
, item
);
23371 assertx(!s
->depStates
.empty());
23372 if (!s
->buckets
.present
->contains(i
)) {
23373 // If this trace state isn't in the bucket, an untracked
23374 // item (with the same name) should be.
23375 auto const b
= [&] () -> const AnalysisInput::BucketPresence
* {
23376 auto const& u
= untracked
;
23377 if (auto const b
= folly::get_ptr(u
.classes
, item
)) return b
;
23378 if (auto const b
= folly::get_ptr(u
.funcs
, item
)) return b
;
23379 if (auto const b
= folly::get_ptr(u
.units
, item
)) return b
;
23380 if (auto const b
= folly::get_ptr(u
.badConstants
, item
)) return b
;
23384 always_assert(b
->present
->contains(i
));
23388 for (auto const d
: s
->depStates
) {
23390 case DepState::Func
:
23391 always_assert(seenFuncs
.emplace(d
->name
).second
);
23392 input
.meta
.funcBuckets
.emplace_back(d
->name
, s
->buckets
);
23394 case DepState::Class
:
23395 always_assert(seenClasses
.emplace(d
->name
).second
);
23396 input
.meta
.classBuckets
.emplace_back(d
->name
, s
->buckets
);
23398 case DepState::Unit
:
23399 always_assert(seenUnits
.emplace(d
->name
).second
);
23400 input
.meta
.unitBuckets
.emplace_back(d
->name
, s
->buckets
);
23406 using K
= AnalysisInput::Kind
;
23408 for (auto const& [name
, k
] : input
.classes
) {
23409 if (!any(k
& K::Dep
)) continue;
23410 if (auto const b
= folly::get_ptr(untracked
.classes
, name
)) {
23411 assertx(!traceForClass(name
));
23412 assertx(b
->present
->contains(i
));
23413 always_assert(seenClasses
.emplace(name
).second
);
23414 input
.meta
.classBuckets
.emplace_back(name
, *b
);
23415 } else if (!seenClasses
.count(name
)) {
23416 auto const t
= traceForClass(name
);
23417 always_assert_flog(
23418 t
, "{} is on input dep classes, but has no tracking state",
23421 input
.meta
.classBuckets
.emplace_back(name
, t
->buckets
);
23422 assertx(t
->buckets
.present
->contains(i
));
23425 for (auto const& [name
, k
] : input
.funcs
) {
23426 if (!any(k
& K::Dep
)) continue;
23427 if (auto const b
= folly::get_ptr(untracked
.funcs
, name
)) {
23428 assertx(!traceForFunc(name
));
23429 assertx(b
->present
->contains(i
));
23430 always_assert(seenFuncs
.emplace(name
).second
);
23431 input
.meta
.funcBuckets
.emplace_back(name
, *b
);
23432 } else if (!seenFuncs
.count(name
)) {
23433 auto const t
= traceForFunc(name
);
23434 always_assert_flog(
23435 t
, "{} is on input dep funcs, but has no tracking state",
23438 input
.meta
.funcBuckets
.emplace_back(name
, t
->buckets
);
23439 assertx(t
->buckets
.present
->contains(i
));
23442 for (auto const& [name
, k
] : input
.units
) {
23443 if (!any(k
& K::Dep
)) continue;
23444 if (auto const b
= folly::get_ptr(untracked
.units
, name
)) {
23445 assertx(!traceForUnit(name
));
23446 assertx(b
->present
->contains(i
));
23447 always_assert(seenUnits
.emplace(name
).second
);
23448 input
.meta
.unitBuckets
.emplace_back(name
, *b
);
23449 } else if (!seenUnits
.count(name
)) {
23450 auto const t
= traceForUnit(name
);
23451 always_assert_flog(
23452 t
, "{} is on input dep units, but has no tracking state",
23455 input
.meta
.unitBuckets
.emplace_back(name
, t
->buckets
);
23456 assertx(t
->buckets
.present
->contains(i
));
23460 for (auto const name
: input
.meta
.badClasses
) {
23461 if (seenClasses
.count(name
)) continue;
23462 if (auto const b
= folly::get_ptr(untracked
.classes
, name
)) {
23463 assertx(!traceForClass(name
));
23464 assertx(b
->present
->contains(i
));
23465 always_assert(seenClasses
.emplace(name
).second
);
23466 input
.meta
.classBuckets
.emplace_back(name
, *b
);
23468 auto const t
= traceForClass(name
);
23469 always_assert_flog(
23470 t
, "{} is on input bad classes, but has no tracking state",
23473 input
.meta
.classBuckets
.emplace_back(name
, t
->buckets
);
23474 assertx(t
->buckets
.present
->contains(i
));
23477 for (auto const name
: input
.meta
.badFuncs
) {
23478 if (auto const b
= folly::get_ptr(untracked
.funcs
, name
)) {
23479 assertx(!traceForFunc(name
));
23480 assertx(b
->present
->contains(i
));
23481 always_assert(seenFuncs
.emplace(name
).second
);
23482 input
.meta
.funcBuckets
.emplace_back(name
, *b
);
23483 } else if (!seenFuncs
.count(name
)) {
23484 auto const t
= traceForFunc(name
);
23485 always_assert_flog(
23486 t
, "{} is on input bad funcs, but has no tracking state",
23489 input
.meta
.funcBuckets
.emplace_back(name
, t
->buckets
);
23492 for (auto const name
: input
.meta
.badConstants
) {
23493 if (auto const b
= folly::get_ptr(untracked
.badConstants
, name
)) {
23494 assertx(!traceForConstant(name
));
23495 assertx(b
->present
->contains(i
));
23496 always_assert(seenBadConstants
.emplace(name
).second
);
23497 input
.meta
.badConstantBuckets
.emplace_back(name
, *b
);
23499 auto const t
= traceForConstant(name
);
23500 always_assert_flog(
23501 t
, "{} is on input bad constants, but has no tracking state",
23504 always_assert(seenBadConstants
.emplace(name
).second
);
23505 input
.meta
.badConstantBuckets
.emplace_back(name
, t
->buckets
);
23506 assertx(t
->buckets
.present
->contains(i
));
23511 begin(input
.meta
.classBuckets
),
23512 end(input
.meta
.classBuckets
),
23513 [] (auto const& p1
, auto const& p2
) {
23514 return string_data_lt_type
{}(p1
.first
, p2
.first
);
23518 begin(input
.meta
.funcBuckets
),
23519 end(input
.meta
.funcBuckets
),
23520 [] (auto const& p1
, auto const& p2
) {
23521 return string_data_lt_func
{}(p1
.first
, p2
.first
);
23525 begin(input
.meta
.unitBuckets
),
23526 end(input
.meta
.unitBuckets
),
23527 [] (auto const& p1
, auto const& p2
) {
23528 return string_data_lt
{}(p1
.first
, p2
.first
);
23532 begin(input
.meta
.badConstantBuckets
),
23533 end(input
.meta
.badConstantBuckets
),
23534 [] (auto const& p1
, auto const& p2
) {
23535 return string_data_lt
{}(p1
.first
, p2
.first
);
23539 input
.meta
.classBuckets
.shrink_to_fit();
23540 input
.meta
.funcBuckets
.shrink_to_fit();
23541 input
.meta
.unitBuckets
.shrink_to_fit();
23542 input
.meta
.badConstantBuckets
.shrink_to_fit();
23543 decltype(bucket
.classes
){}.swap(bucket
.classes
);
23544 decltype(bucket
.deps
){}.swap(bucket
.deps
);
23551 // Ninth pass: Strictly debug. Check various invariants.
23552 void AnalysisScheduler::tracePass9(const std::vector
<AnalysisInput
>& inputs
) {
23553 if (!debug
) return;
23555 TSStringToOneT
<size_t> classes
;
23556 FSStringToOneT
<size_t> funcs
;
23557 SStringToOneT
<size_t> units
;
23559 using K
= AnalysisInput::Kind
;
23561 // Every class/func/unit on an AnalysisInput should only be one
23563 for (auto const& i
: inputs
) {
23564 for (auto const& [n
, k
] : i
.classes
) {
23565 if (!any(k
& K::Rep
)) continue;
23566 auto const [it
, e
] = classes
.try_emplace(n
, i
.meta
.bucketIdx
);
23567 always_assert_flog(
23569 "Class {} is in both bucket {} and {}!\n",
23570 n
, it
->second
, i
.meta
.bucketIdx
23573 for (auto const& [n
, k
] : i
.funcs
) {
23574 if (!any(k
& K::Rep
)) continue;
23575 auto const [it
, e
] = funcs
.try_emplace(n
, i
.meta
.bucketIdx
);
23577 always_assert_flog(
23579 "Func {} is in both bucket {} and {}!\n",
23580 n
, it
->second
, i
.meta
.bucketIdx
23583 for (auto const& [n
, k
] : i
.units
) {
23584 if (!any(k
& K::Rep
)) continue;
23585 auto const [it
, e
] = units
.try_emplace(n
, i
.meta
.bucketIdx
);
23587 always_assert_flog(
23589 "Unit {} is in both bucket {} and {}!\n",
23590 n
, it
->second
, i
.meta
.bucketIdx
23594 // Dep class/funcs/units can be in multiple AnalysisInputs, but
23595 // can't appear more than once in the same AnalysisInput.
23596 for (auto const& [n
, k
] : i
.classes
) {
23597 if (!any(k
& K::Dep
)) continue;
23598 always_assert_flog(
23599 !any(folly::get_default(i
.classes
, n
) & K::Rep
),
23600 "Class {} is in both classes and depClasses in bucket {}\n",
23601 n
, i
.meta
.bucketIdx
23604 for (auto const& [n
, k
] : i
.funcs
) {
23605 if (!any(k
& K::Dep
)) continue;
23606 always_assert_flog(
23607 !any(folly::get_default(i
.funcs
, n
) & K::Rep
),
23608 "Func {} is in both funcs and depFuncs in bucket {}\n",
23609 n
, i
.meta
.bucketIdx
23612 for (auto const& [n
, k
] : i
.units
) {
23613 if (!any(k
& K::Dep
)) continue;
23614 always_assert_flog(
23615 !any(folly::get_default(i
.units
, n
) & K::Rep
),
23616 "Unit {} is in both units and depUnits in bucket {}\n",
23617 n
, i
.meta
.bucketIdx
23621 // Ditto for "bad" classes or funcs.
23622 for (auto const n
: i
.meta
.badClasses
) {
23623 always_assert_flog(
23624 !any(folly::get_default(i
.classes
, n
) & K::Rep
),
23625 "Class {} is in both classes and badClasses in bucket {}\n",
23626 n
, i
.meta
.bucketIdx
23628 always_assert_flog(
23629 !any(folly::get_default(i
.classes
, n
) & K::Dep
),
23630 "Class {} is in both depClasses and badClasses in bucket {}\n",
23631 n
, i
.meta
.bucketIdx
23634 for (auto const n
: i
.meta
.badFuncs
) {
23635 always_assert_flog(
23636 !any(folly::get_default(i
.funcs
, n
) & K::Rep
),
23637 "Func {} is in both funcs and badFuncs in bucket {}\n",
23638 n
, i
.meta
.bucketIdx
23640 always_assert_flog(
23641 !any(folly::get_default(i
.funcs
, n
) & K::Dep
),
23642 "Func {} is in both depFuncs and badFuncs in bucket {}\n",
23643 n
, i
.meta
.bucketIdx
23649 // Group the work that needs to run into buckets of the given size.
23650 std::vector
<AnalysisInput
> AnalysisScheduler::schedule(size_t bucketSize
,
23651 size_t maxBucketSize
) {
23652 FTRACE(2, "AnalysisScheduler: scheduling {} items into buckets of "
23653 "size {} (max {})\n",
23654 workItems(), bucketSize
, maxBucketSize
);
23660 auto leafs
= tracePass4();
23661 auto buckets
= tracePass5(bucketSize
, maxBucketSize
, std::move(leafs
));
23662 auto inputs
= tracePass6(buckets
);
23663 tracePass7(inputs
);
23664 tracePass8(std::move(buckets
), inputs
.inputs
);
23665 tracePass9(inputs
.inputs
);
23667 totalWorkItems
.store(0);
23668 FTRACE(2, "AnalysisScheduler: scheduled {} buckets\n", inputs
.inputs
.size());
23670 return std::move(inputs
.inputs
);
23673 //////////////////////////////////////////////////////////////////////
23677 //////////////////////////////////////////////////////////////////////
23679 // If we optimized a top-level constant's value to a scalar, we no
23680 // longer need the associated 86cinit function. This fixes up the
23681 // metadata to remove it.
23682 FSStringSet
strip_unneeded_constant_inits(AnalysisIndex::IndexData
& index
) {
23683 FSStringSet stripped
;
23685 for (auto const name
: index
.outFuncNames
) {
23686 auto const cnsName
= Constant::nameFromFuncName(name
);
23687 if (!cnsName
) continue;
23688 auto const it
= index
.constants
.find(cnsName
);
23689 if (it
== end(index
.constants
)) continue;
23690 auto const& cns
= *it
->second
.first
;
23691 if (type(cns
.val
) == KindOfUninit
) continue;
23692 stripped
.emplace(name
);
23694 if (stripped
.empty()) return stripped
;
23696 for (auto const name
: stripped
) {
23697 index
.deps
->getChanges().remove(*index
.funcs
.at(name
));
23698 index
.funcs
.erase(name
);
23699 index
.finfos
.erase(name
);
23702 index
.outFuncNames
.erase(
23704 begin(index
.outFuncNames
), end(index
.outFuncNames
),
23705 [&] (SString f
) { return stripped
.count(f
); }
23707 end(index
.outFuncNames
)
23710 for (auto& [_
, unit
] : index
.units
) {
23713 begin(unit
->funcs
), end(unit
->funcs
),
23714 [&, unit
=unit
] (SString f
) {
23715 if (!stripped
.count(f
)) return false;
23717 index
.deps
->bucketFor(unit
).process
->contains(index
.bucketIdx
)
23729 // Record the new set of classes which this class has inherited
23730 // constants from. This set can change (always shrink) due to
23731 // optimizations rewriting constants.
23732 TSStringSet
record_cns_bases(const php::Class
& cls
,
23733 const AnalysisIndex::IndexData
& index
) {
23735 if (!cls
.cinfo
) return out
;
23736 for (auto const& [_
, idx
] : cls
.cinfo
->clsConstants
) {
23737 if (!cls
.name
->tsame(idx
.idx
.cls
)) out
.emplace(idx
.idx
.cls
);
23738 if (auto const cnsCls
= folly::get_default(index
.classes
, idx
.idx
.cls
)) {
23739 assertx(idx
.idx
.idx
< cnsCls
->constants
.size());
23740 auto const& cns
= cnsCls
->constants
[idx
.idx
.idx
];
23741 if (!cls
.name
->tsame(cns
.cls
)) out
.emplace(cns
.cls
);
23747 // Mark all "fixed" class constants. A class constant is fixed if it's
23748 // value can't change going forward. This transforms any dependency on
23749 // that constant from a transitive one (that gets put on the trace) to
23750 // a strict dependency (which only goes on the dep list).
23751 void mark_fixed_class_constants(const php::Class
& cls
,
23752 AnalysisIndex::IndexData
& index
) {
23753 auto& changes
= index
.deps
->getChanges();
23756 for (size_t i
= 0, size
= cls
.constants
.size(); i
< size
; ++i
) {
23757 auto const& cns
= cls
.constants
[i
];
23758 auto const fixed
= [&] {
23759 if (!cns
.val
) return true;
23760 if (cns
.kind
== ConstModifiers::Kind::Type
) {
23761 // A type-constant is fixed if it's been resolved.
23762 return cns
.resolvedTypeStructure
&& cns
.contextInsensitive
;
23763 } else if (cns
.kind
== ConstModifiers::Kind::Value
) {
23764 // A normal constant is fixed if it's a scalar.
23765 return type(*cns
.val
) != KindOfUninit
;
23767 // Anything else never changes.
23772 changes
.fixed(ConstIndex
{ cls
.name
, (unsigned int)i
});
23777 if (all
) changes
.fixed(cls
);
23779 // While we're at it, record all the names present in this class'
23780 // type-constants. This will be used in forming dependencies.
23781 for (auto const& [_
, idx
] :
23782 index
.index
.lookup_flattened_class_type_constants(cls
)) {
23783 auto const cinfo
= folly::get_default(index
.cinfos
, idx
.cls
);
23784 if (!cinfo
) continue;
23785 assertx(idx
.idx
< cinfo
->cls
->constants
.size());
23786 auto const& cns
= cinfo
->cls
->constants
[idx
.idx
];
23787 if (cns
.kind
!= ConstModifiers::Kind::Type
) continue;
23788 if (!cns
.val
.has_value()) continue;
23789 auto const ts
= [&] () -> SArray
{
23790 if (cns
.resolvedTypeStructure
&&
23791 (cns
.contextInsensitive
|| cls
.name
->tsame(cns
.cls
))) {
23792 return cns
.resolvedTypeStructure
;
23794 assertx(tvIsDict(*cns
.val
));
23795 return val(*cns
.val
).parr
;
23797 auto const name
= type_structure_name(ts
);
23798 if (!name
) continue;
23799 changes
.typeCnsName(cls
, AnalysisChangeSet::Class
{ name
});
23803 // Mark whether this unit is "fixed". An unit is fixed if all the
23804 // type-aliases in it are resolved.
23805 void mark_fixed_unit(const php::Unit
& unit
,
23806 AnalysisChangeSet
& changes
) {
23808 for (auto const& ta
: unit
.typeAliases
) {
23809 if (!ta
->resolvedTypeStructure
) all
= false;
23810 auto const ts
= [&] {
23811 if (ta
->resolvedTypeStructure
) return ta
->resolvedTypeStructure
;
23812 return ta
->typeStructure
;
23814 auto const name
= type_structure_name(ts
);
23815 if (!name
) continue;
23816 changes
.typeCnsName(unit
, AnalysisChangeSet::Class
{ name
});
23818 if (all
) changes
.fixed(unit
);
23821 //////////////////////////////////////////////////////////////////////
23825 //////////////////////////////////////////////////////////////////////
23827 AnalysisIndex::AnalysisIndex(
23828 AnalysisWorklist
& worklist
,
23829 VU
<php::Class
> classes
,
23830 VU
<php::Func
> funcs
,
23831 VU
<php::Unit
> units
,
23832 VU
<php::ClassBytecode
> clsBC
,
23833 VU
<php::FuncBytecode
> funcBC
,
23834 V
<AnalysisIndexCInfo
> cinfos
,
23835 V
<AnalysisIndexFInfo
> finfos
,
23836 V
<AnalysisIndexMInfo
> minfos
,
23837 VU
<php::Class
> depClasses
,
23838 VU
<php::Func
> depFuncs
,
23839 VU
<php::Unit
> depUnits
,
23840 AnalysisInput::Meta meta
,
23842 ) : m_data
{std::make_unique
<IndexData
>(*this, worklist
, mode
)}
23844 m_data
->bucketIdx
= meta
.bucketIdx
;
23846 m_data
->badClasses
= std::move(meta
.badClasses
);
23847 m_data
->badFuncs
= std::move(meta
.badFuncs
);
23848 m_data
->badConstants
= std::move(meta
.badConstants
);
23850 std::vector
<SString
> depClassNames
;
23852 m_data
->outClassNames
.reserve(classes
.size());
23853 m_data
->allClasses
.reserve(classes
.size() + depClasses
.size());
23854 depClassNames
.reserve(depClasses
.size());
23855 for (auto& cls
: classes
) {
23856 for (auto& clo
: cls
->closures
) {
23858 m_data
->classes
.emplace(clo
->name
, clo
.get()).second
23861 auto const name
= cls
->name
;
23862 always_assert(m_data
->classes
.emplace(name
, cls
.get()).second
);
23863 m_data
->outClassNames
.emplace_back(name
);
23864 m_data
->allClasses
.emplace(name
, std::move(cls
));
23866 for (auto& cls
: depClasses
) {
23867 for (auto& clo
: cls
->closures
) {
23869 m_data
->classes
.emplace(clo
->name
, clo
.get()).second
23872 auto const name
= cls
->name
;
23873 always_assert(m_data
->classes
.emplace(name
, cls
.get()).second
);
23874 depClassNames
.emplace_back(name
);
23875 m_data
->allClasses
.emplace(name
, std::move(cls
));
23878 std::vector
<SString
> depFuncNames
;
23880 m_data
->outFuncNames
.reserve(funcs
.size());
23881 m_data
->allFuncs
.reserve(funcs
.size() + depFuncs
.size());
23882 m_data
->funcs
.reserve(funcs
.size() + depFuncs
.size());
23883 depFuncNames
.reserve(depFuncs
.size());
23884 for (auto& func
: funcs
) {
23885 auto const name
= func
->name
;
23886 always_assert(m_data
->funcs
.emplace(name
, func
.get()).second
);
23887 m_data
->outFuncNames
.emplace_back(name
);
23888 m_data
->allFuncs
.emplace(name
, std::move(func
));
23890 for (auto& func
: depFuncs
) {
23891 auto const name
= func
->name
;
23892 always_assert(m_data
->funcs
.emplace(name
, func
.get()).second
);
23893 depFuncNames
.emplace_back(name
);
23894 m_data
->allFuncs
.emplace(name
, std::move(func
));
23897 auto const assignFInfoIdx
= [&] (php::Func
& func
, FuncInfo2
& finfo
) {
23898 always_assert(func
.idx
== std::numeric_limits
<uint32_t>::max());
23899 always_assert(!finfo
.func
);
23900 finfo
.func
= &func
;
23901 func
.idx
= m_data
->finfosByIdx
.size();
23902 m_data
->finfosByIdx
.emplace_back(&finfo
);
23905 auto const addCInfo
= [&] (ClassInfo2
* cinfo
) {
23906 auto cls
= folly::get_default(m_data
->classes
, cinfo
->name
);
23907 always_assert(cls
);
23908 always_assert(!cls
->cinfo
);
23909 always_assert(!cinfo
->cls
);
23910 cls
->cinfo
= cinfo
;
23913 auto const numMethods
= cls
->methods
.size();
23914 always_assert(cinfo
->funcInfos
.size() == numMethods
);
23915 for (size_t i
= 0; i
< numMethods
; ++i
) {
23916 assignFInfoIdx(*cls
->methods
[i
], *cinfo
->funcInfos
[i
]);
23918 always_assert(m_data
->cinfos
.emplace(cinfo
->name
, cinfo
).second
);
23921 m_data
->allCInfos
.reserve(cinfos
.size());
23922 for (auto& wrapper
: cinfos
) {
23923 for (auto& clo
: wrapper
.ptr
->closures
) addCInfo(clo
.get());
23924 addCInfo(wrapper
.ptr
.get());
23925 auto const name
= wrapper
.ptr
->name
;
23926 m_data
->allCInfos
.emplace(name
, wrapper
.ptr
.release());
23929 m_data
->finfos
.reserve(finfos
.size());
23930 m_data
->allFInfos
.reserve(finfos
.size());
23931 for (auto& wrapper
: finfos
) {
23932 auto func
= folly::get_default(m_data
->funcs
, wrapper
.ptr
->name
);
23933 always_assert(func
);
23934 assignFInfoIdx(*func
, *wrapper
.ptr
);
23935 auto const name
= wrapper
.ptr
->name
;
23936 always_assert(m_data
->finfos
.emplace(name
, wrapper
.ptr
.get()).second
);
23937 m_data
->allFInfos
.emplace(name
, wrapper
.ptr
.release());
23940 m_data
->minfos
.reserve(minfos
.size());
23941 m_data
->allMInfos
.reserve(minfos
.size());
23942 for (auto& wrapper
: minfos
) {
23943 auto const minfo
= wrapper
.ptr
.get();
23944 auto cls
= folly::get_default(m_data
->classes
, minfo
->cls
);
23945 always_assert(cls
);
23946 always_assert(!cls
->cinfo
);
23948 auto const numMethods
= cls
->methods
.size();
23949 always_assert(minfo
->finfos
.size() == numMethods
);
23950 for (size_t i
= 0; i
< numMethods
; ++i
) {
23951 assignFInfoIdx(*cls
->methods
[i
], *minfo
->finfos
[i
]);
23953 auto const numClosures
= cls
->closures
.size();
23954 always_assert(minfo
->closureInvokes
.size() == numClosures
);
23955 for (size_t i
= 0; i
< numClosures
; ++i
) {
23956 auto& clo
= cls
->closures
[i
];
23957 auto& finfo
= minfo
->closureInvokes
[i
];
23958 assertx(clo
->methods
.size() == 1);
23959 assignFInfoIdx(*clo
->methods
[0], *finfo
);
23962 auto const name
= minfo
->cls
;
23963 always_assert(m_data
->minfos
.emplace(name
, minfo
).second
);
23964 m_data
->allMInfos
.emplace(name
, wrapper
.ptr
.release());
23967 for (auto& bc
: clsBC
) {
23968 auto cls
= folly::get_default(m_data
->classes
, bc
->cls
);
23969 always_assert(cls
);
23972 for (auto& meth
: cls
->methods
) {
23973 assertx(idx
< bc
->methodBCs
.size());
23974 auto& methBC
= bc
->methodBCs
[idx
++];
23975 always_assert(methBC
.name
== meth
->name
);
23976 meth
->rawBlocks
= std::move(methBC
.bc
);
23978 for (auto& clo
: cls
->closures
) {
23979 assertx(idx
< bc
->methodBCs
.size());
23980 auto& methBC
= bc
->methodBCs
[idx
++];
23981 assertx(clo
->methods
.size() == 1);
23982 always_assert(methBC
.name
== clo
->methods
[0]->name
);
23983 clo
->methods
[0]->rawBlocks
= std::move(methBC
.bc
);
23985 assertx(idx
== bc
->methodBCs
.size());
23987 for (auto& bc
: funcBC
) {
23988 auto func
= folly::get_default(m_data
->funcs
, bc
->name
);
23989 always_assert(func
);
23990 func
->rawBlocks
= std::move(bc
->bc
);
23993 std::vector
<SString
> depUnitNames
;
23995 m_data
->outUnitNames
.reserve(units
.size());
23996 m_data
->units
.reserve(units
.size() + depUnits
.size());
23997 m_data
->allUnits
.reserve(units
.size() + depUnits
.size());
23998 depUnitNames
.reserve(depUnits
.size());
24000 auto const addUnit
= [&] (php::Unit
* unit
) {
24001 auto const isNative
= is_native_unit(*unit
);
24002 for (auto& cns
: unit
->constants
) {
24004 m_data
->constants
.try_emplace(cns
->name
, cns
.get(), unit
).second
24006 assertx(!m_data
->badConstants
.count(cns
->name
));
24007 if (isNative
&& type(cns
->val
) == KindOfUninit
) {
24008 m_data
->dynamicConstants
.emplace(cns
->name
);
24011 for (auto& typeAlias
: unit
->typeAliases
) {
24013 m_data
->typeAliases
.try_emplace(
24019 assertx(!m_data
->badClasses
.count(typeAlias
->name
));
24021 always_assert(m_data
->units
.emplace(unit
->filename
, unit
).second
);
24023 for (auto& unit
: units
) {
24024 addUnit(unit
.get());
24025 auto const name
= unit
->filename
;
24026 m_data
->outUnitNames
.emplace_back(name
);
24027 m_data
->allUnits
.emplace(name
, std::move(unit
));
24029 for (auto& unit
: depUnits
) {
24030 addUnit(unit
.get());
24031 auto const name
= unit
->filename
;
24032 m_data
->allUnits
.emplace(name
, std::move(unit
));
24033 depUnitNames
.emplace_back(name
);
24036 for (auto const& [_
, cls
] : m_data
->allClasses
) {
24037 if (!cls
->cinfo
) continue;
24038 always_assert(cls
.get() == cls
->cinfo
->cls
);
24040 for (auto const& [_
, cinfo
]: m_data
->allCInfos
) {
24041 always_assert(cinfo
->cls
);
24042 always_assert(cinfo
.get() == cinfo
->cls
->cinfo
);
24044 for (size_t i
= 0, size
= m_data
->finfosByIdx
.size(); i
< size
; ++i
) {
24045 auto finfo
= m_data
->finfosByIdx
[i
];
24046 always_assert(finfo
->func
);
24047 always_assert(finfo
->func
->idx
== i
);
24050 ClassGraph::setAnalysisIndex(*m_data
);
24052 // Use the BucketSet information in the input metadata to set up the
24054 for (auto& [n
, b
] : meta
.classBuckets
) {
24055 if (auto const c
= folly::get_default(m_data
->classes
, n
)) {
24056 m_data
->deps
->restrict(c
, std::move(b
));
24057 } else if (m_data
->badClasses
.count(n
)) {
24058 m_data
->deps
->restrict(DepTracker::Class
{ n
}, std::move(b
));
24061 for (auto& [n
, b
] : meta
.funcBuckets
) {
24062 if (auto const f
= folly::get_default(m_data
->funcs
, n
)) {
24063 m_data
->deps
->restrict(f
, std::move(b
));
24064 } else if (m_data
->badFuncs
.count(n
)) {
24065 m_data
->deps
->restrict(DepTracker::Func
{ n
}, std::move(b
));
24068 for (auto& [n
, b
] : meta
.unitBuckets
) {
24069 if (auto const u
= folly::get_default(m_data
->units
, n
)) {
24070 m_data
->deps
->restrict(u
, std::move(b
));
24073 for (auto& [n
, b
] : meta
.badConstantBuckets
) {
24074 assertx(m_data
->badConstants
.count(n
));
24075 m_data
->deps
->restrict(DepTracker::Constant
{ n
}, std::move(b
));
24078 initialize_worklist(
24080 std::move(depClassNames
),
24081 std::move(depFuncNames
),
24082 std::move(depUnitNames
)
24086 AnalysisIndex::~AnalysisIndex() {
24087 ClassGraph::clearAnalysisIndex();
24090 // Initialize the worklist with the items we know we must
24091 // process. Also add dependency information for the items which *may*
24093 void AnalysisIndex::initialize_worklist(const AnalysisInput::Meta
& meta
,
24094 std::vector
<SString
> depClasses
,
24095 std::vector
<SString
> depFuncs
,
24096 std::vector
<SString
> depUnits
) {
24097 auto const add
= [&] (FuncClsUnit src
, const AnalysisDeps
& deps
) {
24098 for (auto const [name
, t
] : deps
.funcs
) {
24099 m_data
->deps
->preadd(src
, DepTracker::Func
{ name
}, t
);
24101 for (auto const [meth
, t
] : deps
.methods
) {
24102 m_data
->deps
->preadd(src
, meth
, t
);
24104 for (auto const cns
: deps
.clsConstants
) {
24105 m_data
->deps
->preadd(src
, cns
);
24107 for (auto const cls
: deps
.anyClsConstants
) {
24108 m_data
->deps
->preadd(src
, DepTracker::AnyClassConstant
{ cls
});
24110 for (auto const cns
: deps
.constants
) {
24111 m_data
->deps
->preadd(src
, DepTracker::Constant
{ cns
});
24115 for (auto const name
: m_data
->outClassNames
) {
24116 auto const cls
= m_data
->classes
.at(name
);
24117 if (is_closure(*cls
)) {
24118 assertx(!cls
->closureContextCls
);
24119 assertx(cls
->closureDeclFunc
);
24120 assertx(!meta
.classDeps
.count(name
));
24123 assertx(m_data
->deps
->bucketFor(cls
).process
->contains(m_data
->bucketIdx
));
24124 if (auto const deps
= folly::get_ptr(meta
.classDeps
, name
)) {
24127 m_data
->worklist
.schedule(cls
);
24131 for (auto const name
: depClasses
) {
24132 if (auto const deps
= folly::get_ptr(meta
.classDeps
, name
)) {
24134 m_data
->deps
->bucketFor(m_data
->classes
.at(name
))
24135 .process
->contains(m_data
->bucketIdx
)
24137 add(m_data
->classes
.at(name
), *deps
);
24138 } else if (meta
.processDepCls
.count(name
)) {
24140 m_data
->deps
->bucketFor(m_data
->classes
.at(name
))
24141 .process
->contains(m_data
->bucketIdx
)
24143 m_data
->worklist
.schedule(m_data
->classes
.at(name
));
24147 for (auto const name
: m_data
->outFuncNames
) {
24148 auto const func
= m_data
->funcs
.at(name
);
24149 assertx(m_data
->deps
->bucketFor(func
).process
->contains(m_data
->bucketIdx
));
24150 if (auto const deps
= folly::get_ptr(meta
.funcDeps
, name
)) {
24153 m_data
->worklist
.schedule(func
);
24157 for (auto const name
: depFuncs
) {
24158 if (auto const deps
= folly::get_ptr(meta
.funcDeps
, name
)) {
24160 m_data
->deps
->bucketFor(m_data
->funcs
.at(name
))
24161 .process
->contains(m_data
->bucketIdx
)
24163 add(m_data
->funcs
.at(name
), *deps
);
24164 } else if (meta
.processDepFunc
.count(name
)) {
24166 m_data
->deps
->bucketFor(m_data
->funcs
.at(name
))
24167 .process
->contains(m_data
->bucketIdx
)
24169 m_data
->worklist
.schedule(m_data
->funcs
.at(name
));
24173 for (auto const name
: m_data
->outUnitNames
) {
24174 auto const unit
= m_data
->units
.at(name
);
24176 m_data
->deps
->bucketFor(unit
).process
->contains(m_data
->bucketIdx
)
24178 if (auto const deps
= folly::get_ptr(meta
.unitDeps
, name
)) {
24181 m_data
->worklist
.schedule(unit
);
24185 for (auto const name
: depUnits
) {
24186 if (auto const deps
= folly::get_ptr(meta
.unitDeps
, name
)) {
24188 m_data
->deps
->bucketFor(m_data
->units
.at(name
))
24189 .process
->contains(m_data
->bucketIdx
)
24191 add(m_data
->units
.at(name
), *deps
);
24192 } else if (meta
.processDepUnit
.count(name
)) {
24194 m_data
->deps
->bucketFor(m_data
->units
.at(name
))
24195 .process
->contains(m_data
->bucketIdx
)
24197 m_data
->worklist
.schedule(m_data
->units
.at(name
));
24202 void AnalysisIndex::start() { ClassGraph::init(); }
24203 void AnalysisIndex::stop() { ClassGraph::destroy(); }
24205 void AnalysisIndex::push_context(const Context
& ctx
) {
24206 m_data
->contexts
.emplace_back(ctx
);
24209 void AnalysisIndex::pop_context() {
24210 assertx(!m_data
->contexts
.empty());
24211 m_data
->contexts
.pop_back();
24214 bool AnalysisIndex::set_in_type_cns(bool b
) {
24215 auto const was
= m_data
->inTypeCns
;
24216 m_data
->inTypeCns
= b
;
24220 void AnalysisIndex::freeze() {
24221 FTRACE(2, "Freezing index...\n");
24222 assertx(!m_data
->frozen
);
24223 m_data
->frozen
= true;
24225 // We're going to do a final set of analysis to record dependencies,
24226 // so we must re-add the original set of items to the worklist.
24227 assertx(!m_data
->worklist
.next());
24228 for (auto const name
: m_data
->outClassNames
) {
24229 auto const cls
= m_data
->classes
.at(name
);
24230 if (is_closure(*cls
)) continue;
24231 m_data
->deps
->reset(cls
);
24232 m_data
->worklist
.schedule(cls
);
24234 for (auto const name
: m_data
->outFuncNames
) {
24235 auto const func
= m_data
->funcs
.at(name
);
24236 m_data
->deps
->reset(func
);
24237 m_data
->worklist
.schedule(func
);
24239 for (auto const name
: m_data
->outUnitNames
) {
24240 auto const unit
= m_data
->units
.at(name
);
24241 m_data
->deps
->reset(unit
);
24242 m_data
->worklist
.schedule(unit
);
24246 bool AnalysisIndex::frozen() const { return m_data
->frozen
; }
24248 const php::Unit
& AnalysisIndex::lookup_func_unit(const php::Func
& f
) const {
24249 auto const it
= m_data
->units
.find(f
.unit
);
24250 always_assert_flog(
24251 it
!= end(m_data
->units
),
24252 "Attempting to access missing unit {} for func {}",
24253 f
.unit
, func_fullname(f
)
24255 return *it
->second
;
24258 const php::Unit
& AnalysisIndex::lookup_class_unit(const php::Class
& c
) const {
24259 auto const it
= m_data
->units
.find(c
.unit
);
24260 always_assert_flog(
24261 it
!= end(m_data
->units
),
24262 "Attempting to access missing unit {} for class {}",
24265 return *it
->second
;
24268 const php::Unit
& AnalysisIndex::lookup_unit(SString n
) const {
24269 auto const it
= m_data
->units
.find(n
);
24270 always_assert_flog(
24271 it
!= end(m_data
->units
),
24272 "Attempting to access missing unit {}",
24275 return *it
->second
;
24279 AnalysisIndex::lookup_const_class(const php::Const
& cns
) const {
24280 if (!m_data
->deps
->add(AnalysisDeps::Class
{ cns
.cls
})) return nullptr;
24281 return folly::get_default(m_data
->classes
, cns
.cls
);
24284 const php::Class
* AnalysisIndex::lookup_class(SString name
) const {
24285 return folly::get_default(m_data
->classes
, name
);
24289 AnalysisIndex::lookup_closure_context(const php::Class
& cls
) const {
24290 always_assert_flog(
24291 !cls
.closureContextCls
,
24292 "AnalysisIndex does not yet support closure contexts (for {})",
24298 res::Func
AnalysisIndex::resolve_func(SString n
) const {
24299 n
= normalizeNS(n
);
24300 if (m_data
->mode
== Mode::Constants
) {
24301 return res::Func
{ res::Func::FuncName
{ n
} };
24304 if (m_data
->deps
->add(AnalysisDeps::Func
{ n
})) {
24305 if (auto const finfo
= folly::get_default(m_data
->finfos
, n
)) {
24306 return res::Func
{ res::Func::Fun2
{ finfo
} };
24308 if (m_data
->badFuncs
.count(n
)) {
24309 return res::Func
{ res::Func::MissingFunc
{ n
} };
24312 return res::Func
{ res::Func::FuncName
{ n
} };
24315 Optional
<res::Class
> AnalysisIndex::resolve_class(SString n
) const {
24316 n
= normalizeNS(n
);
24317 if (!m_data
->deps
->add(AnalysisDeps::Class
{ n
})) {
24318 // If we can't use information about the class, there's no point
24319 // in checking, and just use the unknown case.
24320 return res::Class::getOrCreate(n
);
24322 if (auto const cinfo
= folly::get_default(m_data
->cinfos
, n
)) {
24323 return res::Class::get(*cinfo
);
24325 if (m_data
->typeAliases
.count(n
)) return std::nullopt
;
24326 // A php::Class should always be accompanied by it's ClassInfo,
24327 // unless if it's uninstantiable. So, if we have a php::Class here,
24328 // we know it's uninstantiable.
24329 if (m_data
->badClasses
.count(n
) || m_data
->classes
.count(n
)) {
24330 return std::nullopt
;
24332 return res::Class::getOrCreate(n
);
24335 Optional
<res::Class
> AnalysisIndex::resolve_class(const php::Class
& cls
) const {
24336 if (!m_data
->deps
->add(AnalysisDeps::Class
{ cls
.name
})) {
24337 return res::Class::getOrCreate(cls
.name
);
24339 if (cls
.cinfo
) return res::Class::get(*cls
.cinfo
);
24340 return std::nullopt
;
24343 res::Func
AnalysisIndex::resolve_func_or_method(const php::Func
& f
) const {
24344 if (!m_data
->deps
->add(f
)) {
24346 return res::Func
{ res::Func::FuncName
{ f
.name
} };
24348 return res::Func
{ res::Func::MethodName
{ f
.cls
->name
, f
.name
} };
24350 if (!f
.cls
) return res::Func
{ res::Func::Fun2
{ &func_info(*m_data
, f
) } };
24351 return res::Func
{ res::Func::Method2
{ &func_info(*m_data
, f
) } };
24354 Type
AnalysisIndex::lookup_constant(SString name
) const {
24355 if (!m_data
->deps
->add(AnalysisDeps::Constant
{ name
})) return TInitCell
;
24357 if (auto const p
= folly::get_ptr(m_data
->constants
, name
)) {
24358 auto const cns
= p
->first
;
24359 if (type(cns
->val
) != KindOfUninit
) return from_cell(cns
->val
);
24360 if (m_data
->dynamicConstants
.count(name
)) return TInitCell
;
24361 auto const fname
= Constant::funcNameFromName(name
);
24362 auto const fit
= m_data
->finfos
.find(fname
);
24363 // We might have the unit present by chance, but without an
24364 // explicit dependence on the constant, we might not have the init
24366 if (fit
== end(m_data
->finfos
)) return TInitCell
;
24367 return unctx(unserialize_type(fit
->second
->returnTy
));
24369 return m_data
->badConstants
.count(name
) ? TBottom
: TInitCell
;
24372 std::vector
<std::pair
<SString
, ConstIndex
>>
24373 AnalysisIndex::lookup_flattened_class_type_constants(
24374 const php::Class
& cls
24376 std::vector
<std::pair
<SString
, ConstIndex
>> out
;
24378 auto const cinfo
= cls
.cinfo
;
24379 if (!cinfo
) return out
;
24381 out
.reserve(cinfo
->clsConstants
.size());
24382 for (auto const& [name
, idx
] : cinfo
->clsConstants
) {
24383 if (idx
.kind
!= ConstModifiers::Kind::Type
) continue;
24384 out
.emplace_back(name
, idx
.idx
);
24387 begin(out
), end(out
),
24388 [] (auto const& p1
, auto const& p2
) {
24389 return string_data_lt
{}(p1
.first
, p2
.first
);
24395 std::vector
<std::pair
<SString
, ClsConstInfo
>>
24396 AnalysisIndex::lookup_class_constants(const php::Class
& cls
) const {
24397 std::vector
<std::pair
<SString
, ClsConstInfo
>> out
;
24398 out
.reserve(cls
.constants
.size());
24399 for (auto const& cns
: cls
.constants
) {
24400 if (cns
.kind
!= ConstModifiers::Kind::Value
) continue;
24401 if (!cns
.val
) continue;
24402 if (cns
.val
->m_type
!= KindOfUninit
) {
24403 out
.emplace_back(cns
.name
, ClsConstInfo
{ from_cell(*cns
.val
), 0 });
24404 } else if (!cls
.cinfo
) {
24405 out
.emplace_back(cns
.name
, ClsConstInfo
{ TInitCell
, 0 });
24407 auto info
= folly::get_default(
24408 cls
.cinfo
->clsConstantInfo
,
24410 ClsConstInfo
{ TInitCell
, 0 }
24412 info
.type
= unserialize_type(std::move(info
.type
));
24413 out
.emplace_back(cns
.name
, std::move(info
));
24419 ClsConstLookupResult
24420 AnalysisIndex::lookup_class_constant(const Type
& cls
,
24421 const Type
& name
) const {
24422 ITRACE(4, "lookup_class_constant: ({}) {}::{}\n",
24423 show(current_context(*m_data
)), show(cls
), show(name
));
24426 AnalysisIndexAdaptor adaptor
{*this};
24427 InTypeCns _2
{adaptor
, false};
24429 using R
= ClsConstLookupResult
;
24431 auto const conservative
= [] {
24432 ITRACE(4, "conservative\n");
24433 return R
{ TInitCell
, TriBool::Maybe
, true };
24436 auto const notFound
= [] {
24437 ITRACE(4, "not found\n");
24438 return R
{ TBottom
, TriBool::No
, false };
24441 if (!is_specialized_cls(cls
)) return conservative();
24443 // We could easily support the case where we don't know the constant
24444 // name, but know the class (like we do for properties), by unioning
24445 // together all possible constants. However it very rarely happens,
24446 // but when it does, the set of constants to union together can be
24447 // huge and it becomes very expensive.
24448 if (!is_specialized_string(name
)) return conservative();
24449 auto const sname
= sval_of(name
);
24451 auto const& dcls
= dcls_of(cls
);
24452 if (dcls
.isExact()) {
24453 auto const rcls
= dcls
.cls();
24454 auto const cinfo
= rcls
.cinfo2();
24455 if (!cinfo
|| !m_data
->deps
->add(AnalysisDeps::Class
{ rcls
.name() })) {
24456 (void)m_data
->deps
->add(AnalysisDeps::AnyClassConstant
{ rcls
.name() });
24457 return conservative();
24460 ITRACE(4, "{}:\n", cinfo
->name
);
24463 auto const idxIt
= cinfo
->clsConstants
.find(sname
);
24464 if (idxIt
== end(cinfo
->clsConstants
)) return notFound();
24465 auto const& idx
= idxIt
->second
;
24467 assertx(!m_data
->badClasses
.count(idx
.idx
.cls
));
24468 if (idx
.kind
!= ConstModifiers::Kind::Value
) return notFound();
24470 if (!m_data
->deps
->add(idx
.idx
)) return conservative();
24472 auto const cnsClsIt
= m_data
->classes
.find(idx
.idx
.cls
);
24473 if (cnsClsIt
== end(m_data
->classes
)) return conservative();
24474 auto const& cnsCls
= cnsClsIt
->second
;
24476 assertx(idx
.idx
.idx
< cnsCls
->constants
.size());
24477 auto const& cns
= cnsCls
->constants
[idx
.idx
.idx
];
24478 assertx(cns
.kind
== ConstModifiers::Kind::Value
);
24480 if (!cns
.val
.has_value()) return notFound();
24482 auto const r
= [&] {
24483 if (type(*cns
.val
) != KindOfUninit
) {
24484 // Fully resolved constant with a known value. We don't need
24485 // to register a dependency on the constant because the value
24486 // will never change.
24487 auto mightThrow
= bool(cinfo
->cls
->attrs
& AttrInternal
);
24489 auto const& unit
= lookup_class_unit(*cinfo
->cls
);
24490 auto const moduleName
= unit
.moduleName
;
24491 auto const packageInfo
= unit
.packageInfo
;
24492 if (auto const activeDeployment
= packageInfo
.getActiveDeployment()) {
24493 if (!packageInfo
.moduleInDeployment(
24494 moduleName
, *activeDeployment
, DeployKind::Hard
)) {
24499 return R
{ from_cell(*cns
.val
), TriBool::Yes
, mightThrow
};
24502 ITRACE(4, "(dynamic)\n");
24503 if (!cnsCls
->cinfo
) return conservative();
24505 folly::get_ptr(cnsCls
->cinfo
->clsConstantInfo
, cns
.name
);
24507 info
? unserialize_type(info
->type
) : TInitCell
,
24512 ITRACE(4, "-> {}\n", show(r
));
24516 // Subclasses not yet implemented
24517 return conservative();
24520 ClsTypeConstLookupResult
24521 AnalysisIndex::lookup_class_type_constant(
24524 const Index::ClsTypeConstLookupResolver
& resolver
24526 ITRACE(4, "lookup_class_type_constant: ({}) {}::{}\n",
24527 show(current_context(*m_data
)), show(cls
), show(name
));
24530 using R
= ClsTypeConstLookupResult
;
24532 auto const conservative
= [&] (SString n
= nullptr) {
24533 ITRACE(4, "conservative\n");
24535 TypeStructureResolution
{ TSDictN
, true },
24541 auto const notFound
= [] {
24542 ITRACE(4, "not found\n");
24544 TypeStructureResolution
{ TBottom
, false },
24550 // Unlike lookup_class_constant, we distinguish abstract from
24551 // not-found, as the runtime sometimes treats them differently.
24552 auto const abstract
= [] {
24553 ITRACE(4, "abstract\n");
24555 TypeStructureResolution
{ TBottom
, false },
24561 if (!is_specialized_cls(cls
)) return conservative();
24563 // As in lookup_class_constant, we could handle this, but it's not
24565 if (!is_specialized_string(name
)) return conservative();
24566 auto const sname
= sval_of(name
);
24568 auto const& dcls
= dcls_of(cls
);
24569 if (dcls
.isExact()) {
24570 auto const rcls
= dcls
.cls();
24571 auto const cinfo
= rcls
.cinfo2();
24572 if (!cinfo
|| !m_data
->deps
->add(AnalysisDeps::Class
{ rcls
.name() })) {
24573 (void)m_data
->deps
->add(AnalysisDeps::AnyClassConstant
{ rcls
.name() });
24574 return conservative(rcls
.name());
24577 ITRACE(4, "{}:\n", cinfo
->name
);
24580 auto const idxIt
= cinfo
->clsConstants
.find(sname
);
24581 if (idxIt
== end(cinfo
->clsConstants
)) return notFound();
24582 auto const& idx
= idxIt
->second
;
24584 assertx(!m_data
->badClasses
.count(idx
.idx
.cls
));
24585 if (idx
.kind
!= ConstModifiers::Kind::Type
) return notFound();
24587 if (!m_data
->deps
->add(idx
.idx
)) return conservative(rcls
.name());
24589 auto const cnsClsIt
= m_data
->classes
.find(idx
.idx
.cls
);
24590 if (cnsClsIt
== end(m_data
->classes
)) return conservative(rcls
.name());
24591 auto const& cnsCls
= cnsClsIt
->second
;
24593 assertx(idx
.idx
.idx
< cnsCls
->constants
.size());
24594 auto const& cns
= cnsCls
->constants
[idx
.idx
.idx
];
24595 assertx(cns
.kind
== ConstModifiers::Kind::Type
);
24596 if (!cns
.val
.has_value()) return abstract();
24597 assertx(tvIsDict(*cns
.val
));
24599 ITRACE(4, "({}) {}\n", cns
.cls
, show(dict_val(val(*cns
.val
).parr
)));
24601 // If we've been given a resolver, use it. Otherwise resolve it in
24603 auto resolved
= resolver
24604 ? resolver(cns
, *cinfo
->cls
)
24605 : resolve_type_structure(
24606 AnalysisIndexAdaptor
{ *this }, cns
, *cinfo
->cls
24609 // The result of resolve_type_structure isn't, in general,
24610 // static. However a type-constant will always be, so force that
24612 assertx(resolved
.type
.is(BBottom
) || resolved
.type
.couldBe(BUnc
));
24613 resolved
.type
&= TUnc
;
24615 std::move(resolved
),
24619 ITRACE(4, "-> {}\n", show(r
));
24623 // Subclasses not yet implemented
24624 return conservative();
24627 ClsTypeConstLookupResult
24628 AnalysisIndex::lookup_class_type_constant(const php::Class
& ctx
,
24630 ConstIndex idx
) const {
24631 ITRACE(4, "lookup_class_type_constant: ({}) {}::{} (from {})\n",
24632 show(current_context(*m_data
)),
24634 show(idx
, AnalysisIndexAdaptor
{ *this }));
24637 assertx(!m_data
->badClasses
.count(idx
.cls
));
24639 using R
= ClsTypeConstLookupResult
;
24641 auto const conservative
= [] {
24642 ITRACE(4, "conservative\n");
24644 TypeStructureResolution
{ TSDictN
, true },
24650 auto const notFound
= [] {
24651 ITRACE(4, "not found\n");
24653 TypeStructureResolution
{ TBottom
, false },
24659 // Unlike lookup_class_constant, we distinguish abstract from
24660 // not-found, as the runtime sometimes treats them differently.
24661 auto const abstract
= [] {
24662 ITRACE(4, "abstract\n");
24664 TypeStructureResolution
{ TBottom
, false },
24670 if (!m_data
->deps
->add(idx
)) return conservative();
24671 auto const cinfo
= folly::get_default(m_data
->cinfos
, idx
.cls
);
24672 if (!cinfo
) return conservative();
24674 assertx(idx
.idx
< cinfo
->cls
->constants
.size());
24675 auto const& cns
= cinfo
->cls
->constants
[idx
.idx
];
24676 if (cns
.kind
!= ConstModifiers::Kind::Type
) return notFound();
24677 if (!cns
.val
.has_value()) return abstract();
24679 assertx(tvIsDict(*cns
.val
));
24681 ITRACE(4, "({}) {}\n", cns
.cls
, show(dict_val(val(*cns
.val
).parr
)));
24683 auto resolved
= resolve_type_structure(
24684 AnalysisIndexAdaptor
{ *this }, cns
, ctx
24687 // The result of resolve_type_structure isn't, in general,
24688 // static. However a type-constant will always be, so force that
24690 assertx(resolved
.type
.is(BBottom
) || resolved
.type
.couldBe(BUnc
));
24691 resolved
.type
&= TUnc
;
24693 std::move(resolved
),
24697 ITRACE(4, "-> {}\n", show(r
));
24701 PropState
AnalysisIndex::lookup_private_props(const php::Class
& cls
) const {
24702 // Private property tracking not yet implemented, so be
24704 return make_unknown_propstate(
24705 AnalysisIndexAdaptor
{ *this },
24707 [&] (const php::Prop
& prop
) {
24708 return (prop
.attrs
& AttrPrivate
) && !(prop
.attrs
& AttrStatic
);
24713 PropState
AnalysisIndex::lookup_private_statics(const php::Class
& cls
) const {
24714 // Private static property tracking not yet implemented, so be
24716 return make_unknown_propstate(
24717 AnalysisIndexAdaptor
{ *this },
24719 [&] (const php::Prop
& prop
) {
24720 return (prop
.attrs
& AttrPrivate
) && (prop
.attrs
& AttrStatic
);
24725 Index::ReturnType
AnalysisIndex::lookup_return_type(MethodsInfo
* methods
,
24726 res::Func rfunc
) const {
24727 using R
= Index::ReturnType
;
24729 auto const meth
= [&] (const FuncInfo2
& finfo
) {
24731 if (auto ret
= methods
->lookupReturnType(*finfo
.func
)) {
24732 return R
{ unctx(std::move(ret
->t
)), ret
->effectFree
};
24735 if (!m_data
->deps
->add(*finfo
.func
, AnalysisDeps::Type::RetType
)) {
24736 return R
{ TInitCell
, false };
24739 unctx(unserialize_type(finfo
.returnTy
)),
24746 [&] (res::Func::FuncName f
) {
24747 (void)m_data
->deps
->add(
24748 AnalysisDeps::Func
{ f
.name
},
24749 AnalysisDeps::Type::RetType
24751 return R
{ TInitCell
, false };
24753 [&] (res::Func::MethodName m
) {
24754 // If we know the name of the class, we can register a
24755 // dependency on it. If not, nothing we can do.
24756 if (m
.cls
) (void)m_data
->deps
->add(AnalysisDeps::Class
{ m
.cls
});
24757 return R
{ TInitCell
, false };
24759 [&] (res::Func::Fun
) -> R
{ always_assert(false); },
24760 [&] (res::Func::Method
) -> R
{ always_assert(false); },
24761 [&] (res::Func::MethodFamily
) -> R
{ always_assert(false); },
24762 [&] (res::Func::MethodOrMissing
) -> R
{ always_assert(false); },
24763 [&] (res::Func::MissingFunc
) { return R
{ TBottom
, false }; },
24764 [&] (res::Func::MissingMethod
) { return R
{ TBottom
, false }; },
24765 [&] (const res::Func::Isect
&) -> R
{ always_assert(false); },
24766 [&] (res::Func::Fun2 f
) {
24767 if (!m_data
->deps
->add(*f
.finfo
->func
, AnalysisDeps::Type::RetType
)) {
24768 return R
{ TInitCell
, false };
24771 unctx(unserialize_type(f
.finfo
->returnTy
)),
24772 f
.finfo
->effectFree
24775 [&] (res::Func::Method2 m
) { return meth(*m
.finfo
); },
24776 [&] (res::Func::MethodFamily2
) -> R
{ always_assert(false); },
24777 [&] (res::Func::MethodOrMissing2 m
) { return meth(*m
.finfo
); },
24778 [&] (const res::Func::Isect2
&) -> R
{ always_assert(false); }
24783 AnalysisIndex::lookup_return_type(MethodsInfo
* methods
,
24784 const CompactVector
<Type
>& args
,
24785 const Type
& context
,
24786 res::Func rfunc
) const {
24787 using R
= Index::ReturnType
;
24789 auto ty
= lookup_return_type(methods
, rfunc
);
24791 auto const contextual
= [&] (const FuncInfo2
& finfo
) {
24792 return context_sensitive_return_type(
24794 { finfo
.func
, args
, context
},
24801 [&] (res::Func::FuncName
) { return ty
; },
24802 [&] (res::Func::MethodName
) { return ty
; },
24803 [&] (res::Func::Fun
) -> R
{ always_assert(false); },
24804 [&] (res::Func::Method
) -> R
{ always_assert(false); },
24805 [&] (res::Func::MethodFamily
) -> R
{ always_assert(false); },
24806 [&] (res::Func::MethodOrMissing
) -> R
{ always_assert(false); },
24807 [&] (res::Func::MissingFunc
) { return R
{ TBottom
, false }; },
24808 [&] (res::Func::MissingMethod
) { return R
{ TBottom
, false }; },
24809 [&] (const res::Func::Isect
&) -> R
{ always_assert(false); },
24810 [&] (res::Func::Fun2 f
) { return contextual(*f
.finfo
); },
24811 [&] (res::Func::Method2 m
) { return contextual(*m
.finfo
); },
24812 [&] (res::Func::MethodFamily2
) -> R
{ always_assert(false); },
24813 [&] (res::Func::MethodOrMissing2 m
) { return contextual(*m
.finfo
); },
24814 [&] (const res::Func::Isect2
&) -> R
{ always_assert(false); }
24819 AnalysisIndex::lookup_foldable_return_type(const CallContext
& calleeCtx
) const {
24820 constexpr size_t maxNestingLevel
= 2;
24822 using R
= Index::ReturnType
;
24824 auto const& func
= *calleeCtx
.callee
;
24826 if (m_data
->mode
== Mode::Constants
) {
24829 "Skipping inline interp of {} because analyzing constants\n",
24830 func_fullname(func
)
24832 return R
{ TInitCell
, false };
24835 auto const ctxType
=
24836 adjust_closure_context(AnalysisIndexAdaptor
{ *this }, calleeCtx
);
24838 // Don't fold functions when staticness mismatches
24839 if (!func
.isClosureBody
) {
24840 if ((func
.attrs
& AttrStatic
) && ctxType
.couldBe(TObj
)) {
24841 return R
{ TInitCell
, false };
24843 if (!(func
.attrs
& AttrStatic
) && ctxType
.couldBe(TCls
)) {
24844 return R
{ TInitCell
, false };
24848 auto const& finfo
= func_info(*m_data
, func
);
24849 // No need to call unserialize_type here. If it's a scalar, there's
24850 // nothing to unserialize anyways.
24851 if (finfo
.effectFree
&& is_scalar(finfo
.returnTy
)) {
24852 return R
{ finfo
.returnTy
, finfo
.effectFree
};
24855 auto const& caller
= *context_for_deps(*m_data
).func
;
24857 if (!m_data
->deps
->add(
24859 AnalysisDeps::Type::ScalarRetType
|
24860 AnalysisDeps::Type::Bytecode
24862 return R
{ TInitCell
, false };
24864 if (m_data
->foldableInterpNestingLevel
> maxNestingLevel
) {
24865 return R
{ TInitCell
, false };
24868 if (!func
.rawBlocks
) {
24871 "Skipping inline interp of {} because bytecode not present\n",
24872 func_fullname(func
)
24874 return R
{ TInitCell
, false };
24877 auto const contextualRet
= [&] () -> Optional
<Type
> {
24878 ++m_data
->foldableInterpNestingLevel
;
24879 SCOPE_EXIT
{ --m_data
->foldableInterpNestingLevel
; };
24881 auto const wf
= php::WideFunc::cns(&func
);
24882 auto const fa
= analyze_func_inline(
24883 AnalysisIndexAdaptor
{ *this },
24888 &context_for_deps(*m_data
)
24893 CollectionOpts::EffectFreeOnly
24895 if (!fa
.effectFree
) return std::nullopt
;
24896 return fa
.inferredReturn
;
24899 if (!contextualRet
) {
24902 "Foldable inline analysis failed due to possible side-effects\n"
24904 return R
{ TInitCell
, false };
24909 "Foldable return type: {}\n",
24910 show(*contextualRet
)
24913 auto const error_context
= [&] {
24914 using namespace folly::gen
;
24915 return folly::sformat(
24916 "{} calling {} (context: {}, args: {})",
24917 func_fullname(caller
),
24918 func_fullname(func
),
24919 show(calleeCtx
.context
),
24920 from(calleeCtx
.args
)
24921 | map([] (const Type
& t
) { return show(t
); })
24922 | unsplit
<std::string
>(",")
24926 auto const insensitive
= unserialize_type(finfo
.returnTy
);
24927 always_assert_flog(
24928 contextualRet
->subtypeOf(insensitive
),
24929 "Context sensitive return type for {} is {} "
24930 "which not at least as refined as context insensitive "
24931 "return type {}\n",
24933 show(*contextualRet
),
24936 if (!is_scalar(*contextualRet
)) return R
{ TInitCell
, false };
24938 return R
{ *contextualRet
, true };
24941 std::pair
<Index::ReturnType
, size_t>
24942 AnalysisIndex::lookup_return_type_raw(const php::Func
& f
) const {
24943 auto const& finfo
= func_info(*m_data
, f
);
24944 return std::make_pair(
24946 unserialize_type(finfo
.returnTy
),
24949 finfo
.returnRefinements
24953 bool AnalysisIndex::func_depends_on_arg(const php::Func
& func
,
24954 size_t arg
) const {
24955 if (!m_data
->deps
->add(func
, AnalysisDeps::Type::UnusedParams
)) return true;
24956 auto const& finfo
= func_info(*m_data
, func
);
24957 return arg
>= finfo
.unusedParams
.size() || !finfo
.unusedParams
.test(arg
);
24961 * Given a DCls, return the most specific res::Func for that DCls. For
24962 * intersections, this will call process/general on every component of
24963 * the intersection and combine the results. process is called to
24964 * obtain a res::Func from a ClassInfo. If a ClassInfo isn't
24965 * available, general will be called instead.
24967 template <typename P
, typename G
>
24968 res::Func
AnalysisIndex::rfunc_from_dcls(const DCls
& dcls
,
24971 const G
& general
) const {
24972 using Func
= res::Func
;
24973 using Class
= res::Class
;
24976 * Combine together multiple res::Funcs together. Since the DCls
24977 * represents a class which is a subtype of every ClassInfo in the
24978 * list, every res::Func we get is true.
24980 * The relevant res::Func types in order from most general to more
24983 * MethodName -> FuncFamily -> MethodOrMissing -> Method -> Missing
24985 * Since every res::Func in the intersection is true, we take the
24986 * res::Func which is most specific. Two different res::Funcs cannot
24987 * be contradict. For example, we shouldn't get a Method and a
24988 * Missing since one implies there's no func and the other implies
24989 * one specific func. Or two different res::Funcs shouldn't resolve
24990 * to two different methods.
24992 auto missing
= TriBool::Maybe
;
24993 Func::Isect2 isect
;
24994 const php::Func
* singleMethod
= nullptr;
24996 auto const onFunc
= [&] (Func func
) {
24999 [&] (Func::MethodName
) {},
25000 [&] (Func::Method
) { always_assert(false); },
25001 [&] (Func::MethodFamily
) { always_assert(false); },
25002 [&] (Func::MethodOrMissing
) { always_assert(false); },
25003 [&] (Func::MissingMethod
) {
25004 assertx(missing
!= TriBool::No
);
25005 singleMethod
= nullptr;
25006 isect
.families
.clear();
25007 missing
= TriBool::Yes
;
25009 [&] (Func::FuncName
) { always_assert(false); },
25010 [&] (Func::Fun
) { always_assert(false); },
25011 [&] (Func::Fun2
) { always_assert(false); },
25012 [&] (Func::Method2 m
) {
25013 assertx(IMPLIES(singleMethod
, singleMethod
== m
.finfo
->func
));
25014 assertx(IMPLIES(singleMethod
, isect
.families
.empty()));
25015 assertx(missing
!= TriBool::Yes
);
25016 if (!singleMethod
) {
25017 singleMethod
= m
.finfo
->func
;
25018 isect
.families
.clear();
25020 missing
= TriBool::No
;
25022 [&] (Func::MethodFamily2
) { always_assert(false); },
25023 [&] (Func::MethodOrMissing2 m
) {
25024 assertx(IMPLIES(singleMethod
, singleMethod
== m
.finfo
->func
));
25025 assertx(IMPLIES(singleMethod
, isect
.families
.empty()));
25026 if (missing
== TriBool::Yes
) {
25027 assertx(!singleMethod
);
25028 assertx(isect
.families
.empty());
25031 if (!singleMethod
) {
25032 singleMethod
= m
.finfo
->func
;
25033 isect
.families
.clear();
25036 [&] (Func::MissingFunc
) { always_assert(false); },
25037 [&] (const Func::Isect
&) { always_assert(false); },
25038 [&] (const Func::Isect2
&) { always_assert(false); }
25042 auto const onClass
= [&] (Class cls
, bool isExact
) {
25043 auto const g
= cls
.graph();
25044 if (!g
.ensureCInfo()) {
25045 onFunc(general(dcls
.containsNonRegular()));
25049 if (auto const cinfo
= g
.cinfo2()) {
25050 onFunc(process(cinfo
, isExact
, dcls
.containsNonRegular()));
25052 // The class doesn't have a ClassInfo present, so we cannot call
25053 // process. We can, however, look at any parents that do have a
25054 // ClassInfo. This won't result in as good results, but it
25055 // preserves monotonicity.
25056 onFunc(general(dcls
.containsNonRegular()));
25057 if (g
.isMissing()) return;
25059 if (!dcls
.containsNonRegular() &&
25060 !g
.mightBeRegular() &&
25061 g
.hasCompleteChildren()) {
25064 Func
{ Func::MissingMethod
{ dcls
.smallestCls().name(), name
} }
25069 hphp_fast_set
<ClassGraph
, ClassGraphHasher
> commonParents
;
25071 for (auto const c
: g
.children()) {
25072 assertx(!c
.isMissing());
25073 if (!c
.mightBeRegular()) continue;
25075 hphp_fast_set
<ClassGraph
, ClassGraphHasher
> newCommon
;
25077 [&] (ClassGraph p
) {
25078 if (first
|| commonParents
.count(p
)) {
25079 newCommon
.emplace(p
);
25085 commonParents
= std::move(newCommon
);
25090 Func
{ Func::MissingMethod
{ dcls
.smallestCls().name(), name
} }
25095 assertx(!commonParents
.empty());
25096 for (auto const p
: commonParents
) {
25097 if (!p
.ensureCInfo()) continue;
25098 if (auto const cinfo
= p
.cinfo2()) {
25099 onFunc(process(cinfo
, false, false));
25106 [&] (ClassGraph p
) {
25107 if (!p
.ensureCInfo()) return true;
25108 if (auto const cinfo
= p
.cinfo2()) {
25109 onFunc(process(cinfo
, false, dcls
.containsNonRegular()));
25118 if (dcls
.isExact() || dcls
.isSub()) {
25119 // If this isn't an intersection, there's only one class to
25120 // process and we're done.
25121 onClass(dcls
.cls(), dcls
.isExact());
25122 } else if (dcls
.isIsect()) {
25123 for (auto const c
: dcls
.isect()) onClass(c
, false);
25125 assertx(dcls
.isIsectAndExact());
25126 auto const [e
, i
] = dcls
.isectAndExact();
25128 for (auto const c
: *i
) onClass(c
, false);
25131 // If we got a method, that always wins. Again, every res::Func is
25132 // true, and method is more specific than a FuncFamily, so it is
25134 if (singleMethod
) {
25135 assertx(missing
!= TriBool::Yes
);
25136 // If missing is Maybe, then *every* resolution was to a
25137 // MethodName or MethodOrMissing, so include that fact here by
25138 // using MethodOrMissing.
25139 if (missing
== TriBool::Maybe
) {
25141 Func::MethodOrMissing2
{ &func_info(*m_data
, *singleMethod
) }
25144 return Func
{ Func::Method2
{ &func_info(*m_data
, *singleMethod
) } };
25146 // We only got unresolved classes. If missing is TriBool::Yes, the
25147 // function doesn't exist. Otherwise be pessimistic.
25148 if (isect
.families
.empty()) {
25149 if (missing
== TriBool::Yes
) {
25150 return Func
{ Func::MissingMethod
{ dcls
.smallestCls().name(), name
} };
25152 assertx(missing
== TriBool::Maybe
);
25153 return general(dcls
.containsNonRegular());
25155 // Isect case. Isects always might contain missing funcs.
25156 assertx(missing
== TriBool::Maybe
);
25158 // We could add a FuncFamily multiple times, so remove duplicates.
25159 std::sort(begin(isect
.families
), end(isect
.families
));
25160 isect
.families
.erase(
25161 std::unique(begin(isect
.families
), end(isect
.families
)),
25162 end(isect
.families
)
25164 // If everything simplifies down to a single FuncFamily, just use
25166 if (isect
.families
.size() == 1) {
25168 Func::MethodFamily2
{ isect
.families
[0], isect
.regularOnly
}
25171 return Func
{ std::move(isect
) };
25174 res::Func
AnalysisIndex::resolve_method(const Type
& thisType
,
25175 SString name
) const {
25176 assertx(thisType
.subtypeOf(BCls
) || thisType
.subtypeOf(BObj
));
25178 using Func
= res::Func
;
25180 auto const general
= [&] (SString maybeCls
, bool) {
25181 assertx(name
!= s_construct
.get());
25182 return Func
{ Func::MethodName
{ maybeCls
, name
} };
25185 if (m_data
->mode
== Mode::Constants
) return general(nullptr, true);
25187 auto const process
= [&] (ClassInfo2
* cinfo
,
25189 bool includeNonRegular
) {
25190 assertx(name
!= s_construct
.get());
25192 auto const meth
= folly::get_ptr(cinfo
->methods
, name
);
25194 // We don't store metadata for special methods, so be pessimistic
25195 // (the lack of a method entry does not mean the call might fail
25197 if (is_special_method_name(name
)) {
25198 return Func
{ Func::MethodName
{ cinfo
->name
, name
} };
25200 // We're only considering this class, not it's subclasses. Since
25201 // it doesn't exist here, the resolution will always fail.
25203 return Func
{ Func::MissingMethod
{ cinfo
->name
, name
} };
25205 // The method isn't present on this class, but it might be in the
25206 // subclasses. In most cases try a general lookup to get a
25207 // slightly better type than nothing.
25208 return general(cinfo
->name
, includeNonRegular
);
25211 if (!m_data
->deps
->add(meth
->meth())) {
25212 return general(cinfo
->name
, includeNonRegular
);
25214 auto const func
= func_from_meth_ref(*m_data
, meth
->meth());
25215 if (!func
) return general(cinfo
->name
, includeNonRegular
);
25217 // We don't store method family information about special methods
25218 // and they have special inheritance semantics.
25219 if (is_special_method_name(name
)) {
25220 // If we know the class exactly, we can use ftarget.
25222 return Func
{ Func::Method2
{ &func_info(*m_data
, *func
) } };
25224 // The method isn't overwritten, but they don't inherit, so it
25225 // could be missing.
25226 if (meth
->attrs
& AttrNoOverride
) {
25227 return Func
{ Func::MethodOrMissing2
{ &func_info(*m_data
, *func
) } };
25229 // Otherwise be pessimistic.
25230 return Func
{ Func::MethodName
{ cinfo
->name
, name
} };
25233 // Private method handling: Private methods have special lookup
25234 // rules. If we're in the context of a particular class, and that
25235 // class defines a private method, an instance of the class will
25236 // always call that private method (even if overridden) in that
25238 assertx(cinfo
->cls
);
25239 auto const& ctx
= current_context(*m_data
);
25240 if (ctx
.cls
== cinfo
->cls
) {
25241 // The context matches the current class. If we've looked up a
25242 // private method (defined on this class), then that's what
25244 if ((meth
->attrs
& AttrPrivate
) && meth
->topLevel()) {
25245 return Func
{ Func::Method2
{ &func_info(*m_data
, *func
) } };
25247 } else if ((meth
->attrs
& AttrPrivate
) || meth
->hasPrivateAncestor()) {
25248 // Otherwise the context doesn't match the current class. If the
25249 // looked up method is private, or has a private ancestor,
25250 // there's a chance we'll call that method (or
25251 // ancestor). Otherwise there's no private method in the
25252 // inheritance tree we'll call.
25253 auto conservative
= false;
25254 auto const ancestor
= [&] () -> const php::Func
* {
25255 if (!ctx
.cls
) return nullptr;
25256 if (!m_data
->deps
->add(AnalysisDeps::Class
{ ctx
.cls
->name
})) {
25257 conservative
= true;
25260 // Look up the ClassInfo corresponding to the context.
25261 auto const ctxCInfo
= ctx
.cls
->cinfo
;
25262 if (!ctxCInfo
) return nullptr;
25263 // Is this context a parent of our class?
25264 if (!cinfo
->classGraph
.isChildOf(ctxCInfo
->classGraph
)) {
25267 // It is. See if it defines a private method.
25268 auto const ctxMeth
= folly::get_ptr(ctxCInfo
->methods
, name
);
25269 if (!ctxMeth
) return nullptr;
25270 // If it defines a private method, use it.
25271 if ((ctxMeth
->attrs
& AttrPrivate
) && ctxMeth
->topLevel()) {
25272 if (!m_data
->deps
->add(ctxMeth
->meth())) {
25273 conservative
= true;
25276 auto const ctxFunc
= func_from_meth_ref(*m_data
, ctxMeth
->meth());
25277 if (!ctxFunc
) conservative
= true;
25280 // Otherwise do normal lookup.
25284 return Func
{ Func::Method2
{ &func_info(*m_data
, *ancestor
) } };
25285 } else if (conservative
) {
25286 return Func
{ Func::MethodName
{ cinfo
->name
, name
} };
25290 // If we're only including regular subclasses, and this class
25291 // itself isn't regular, the result may not necessarily include
25293 if (!includeNonRegular
&& !is_regular_class(*cinfo
->cls
)) {
25294 // We're not including this base class. If we're exactly this
25295 // class, there's no method at all. It will always be missing.
25297 return Func
{ Func::MissingMethod
{ cinfo
->name
, name
} };
25299 if (meth
->noOverrideRegular()) {
25300 // The method isn't overridden in a subclass, but we can't use
25301 // the base class either. This leaves two cases. Either the
25302 // method isn't overridden because there are no regular
25303 // subclasses (in which case there's no resolution at all), or
25304 // because there's regular subclasses, but they use the same
25305 // method (in which case the result is just func).
25306 if (!cinfo
->classGraph
.mightHaveRegularSubclass()) {
25307 return Func
{ Func::MissingMethod
{ cinfo
->name
, name
} };
25309 return Func
{ Func::Method2
{ &func_info(*m_data
, *func
) } };
25311 } else if (isExact
||
25312 meth
->attrs
& AttrNoOverride
||
25313 (!includeNonRegular
&& meth
->noOverrideRegular())) {
25314 // Either we want all classes, or the base class is regular. If
25315 // the method isn't overridden we know it must be just func (the
25316 // override bits include it being missing in a subclass, so we
25317 // know it cannot be missing either).
25318 return Func
{ Func::Method2
{ &func_info(*m_data
, *func
) } };
25321 // Be pessimistic for the rest of cases
25322 return general(cinfo
->name
, includeNonRegular
);
25325 auto const isClass
= thisType
.subtypeOf(BCls
);
25326 if (name
== s_construct
.get()) {
25328 return Func
{ Func::MethodName
{ nullptr, s_construct
.get() } };
25330 return resolve_ctor(thisType
);
25334 if (!is_specialized_cls(thisType
)) return general(nullptr, true);
25335 } else if (!is_specialized_obj(thisType
)) {
25336 return general(nullptr, false);
25339 auto const& dcls
= isClass
? dcls_of(thisType
) : dobj_of(thisType
);
25340 return rfunc_from_dcls(
25344 [&] (bool i
) { return general(dcls
.smallestCls().name(), i
); }
25348 res::Func
AnalysisIndex::resolve_ctor(const Type
& obj
) const {
25349 assertx(obj
.subtypeOf(BObj
));
25351 using Func
= res::Func
;
25353 if (m_data
->mode
== Mode::Constants
) {
25354 return Func
{ Func::MethodName
{ nullptr, s_construct
.get() } };
25357 // Can't say anything useful if we don't know the object type.
25358 if (!is_specialized_obj(obj
)) {
25359 return Func
{ Func::MethodName
{ nullptr, s_construct
.get() } };
25362 auto const& dcls
= dobj_of(obj
);
25363 return rfunc_from_dcls(
25366 [&] (ClassInfo2
* cinfo
, bool isExact
, bool includeNonRegular
) {
25367 // We're dealing with an object here, which never uses
25368 // non-regular classes.
25369 assertx(!includeNonRegular
);
25371 // See if this class has a ctor.
25372 auto const meth
= folly::get_ptr(cinfo
->methods
, s_construct
.get());
25374 // There's no ctor on this class. This doesn't mean the ctor
25375 // won't exist at runtime, it might get the default ctor, so
25376 // we have to be conservative.
25377 return Func
{ Func::MethodName
{ cinfo
->name
, s_construct
.get() } };
25380 if (!m_data
->deps
->add(meth
->meth())) {
25382 Func::MethodName
{ dcls
.smallestCls().name(), s_construct
.get() }
25386 // We have a ctor, but it might be overridden in a subclass.
25387 assertx(!(meth
->attrs
& AttrStatic
));
25388 auto const func
= func_from_meth_ref(*m_data
, meth
->meth());
25390 // Relevant function doesn't exist on the AnalysisIndex. Be
25392 return Func
{ Func::MethodName
{ cinfo
->name
, s_construct
.get() } };
25394 assertx(!(func
->attrs
& AttrStatic
));
25396 // If this class is known exactly, or we know nothing overrides
25397 // this ctor, we know this ctor is precisely it.
25398 if (isExact
|| meth
->noOverrideRegular()) {
25399 // If this class isn't regular, and doesn't have any regular
25400 // subclasses (or if it's exact), this resolution will always
25402 if (!is_regular_class(*cinfo
->cls
) &&
25403 (isExact
|| !cinfo
->classGraph
.mightHaveRegularSubclass())) {
25405 Func::MissingMethod
{ cinfo
->name
, s_construct
.get() }
25408 return Func
{ Func::Method2
{ &func_info(*m_data
, *func
) } };
25411 // Be pessimistic for the rest of cases
25413 Func::MethodName
{ dcls
.smallestCls().name(), s_construct
.get() }
25416 [&] (bool includeNonRegular
) {
25417 assertx(!includeNonRegular
);
25419 Func::MethodName
{ dcls
.smallestCls().name(), s_construct
.get() }
25425 std::pair
<const php::TypeAlias
*, bool>
25426 AnalysisIndex::lookup_type_alias(SString name
) const {
25427 if (!m_data
->deps
->add(AnalysisDeps::Class
{ name
})) {
25428 return std::make_pair(nullptr, true);
25430 if (m_data
->classes
.count(name
)) return std::make_pair(nullptr, false);
25431 if (auto const ta
= folly::get_ptr(m_data
->typeAliases
, name
)) {
25432 return std::make_pair(ta
->first
, true);
25434 return std::make_pair(nullptr, !m_data
->badClasses
.count(name
));
25437 Index::ClassOrTypeAlias
25438 AnalysisIndex::lookup_class_or_type_alias(SString n
) const {
25439 n
= normalizeNS(n
);
25440 if (!m_data
->deps
->add(AnalysisDeps::Class
{ n
})) {
25441 return Index::ClassOrTypeAlias
{nullptr, nullptr, true};
25443 if (auto const cls
= folly::get_default(m_data
->classes
, n
)) {
25444 return Index::ClassOrTypeAlias
{cls
, nullptr, true};
25446 if (auto const ta
= folly::get_ptr(m_data
->typeAliases
, n
)) {
25447 return Index::ClassOrTypeAlias
{nullptr, ta
->first
, true};
25449 return Index::ClassOrTypeAlias
{
25452 !m_data
->badClasses
.count(n
)
25456 PropMergeResult
AnalysisIndex::merge_static_type(
25457 PublicSPropMutations
& publicMutations
,
25458 PropertiesInfo
& privateProps
,
25464 bool mustBeReadOnly
) const {
25465 // Not yet implemented
25466 return PropMergeResult
{ TInitCell
, TriBool::Maybe
};
25469 void AnalysisIndex::refine_constants(const FuncAnalysisResult
& fa
) {
25470 auto const& func
= *fa
.ctx
.func
;
25471 if (func
.cls
) return;
25473 auto const name
= Constant::nameFromFuncName(func
.name
);
25476 auto const cnsPtr
= folly::get_ptr(m_data
->constants
, name
);
25477 always_assert_flog(
25479 "Attempting to refine constant {} "
25480 "which we don't have meta-data for",
25483 auto const cns
= cnsPtr
->first
;
25484 auto const val
= tv(fa
.inferredReturn
);
25486 always_assert_flog(
25487 type(cns
->val
) == KindOfUninit
,
25488 "Constant value invariant violated in {}.\n"
25489 " Value went from {} to {}",
25491 show(from_cell(cns
->val
)),
25492 show(fa
.inferredReturn
)
25497 if (type(cns
->val
) != KindOfUninit
) {
25498 always_assert_flog(
25499 from_cell(cns
->val
) == fa
.inferredReturn
,
25500 "Constant value invariant violated in {}.\n"
25501 " Value went from {} to {}",
25503 show(from_cell(cns
->val
)),
25504 show(fa
.inferredReturn
)
25507 always_assert_flog(
25509 "Attempting to refine constant {} to {} when index is frozen",
25511 show(fa
.inferredReturn
)
25514 m_data
->deps
->update(*cns
);
25518 void AnalysisIndex::refine_class_constants(const FuncAnalysisResult
& fa
) {
25519 auto const resolved
= fa
.resolvedInitializers
.left();
25520 if (!resolved
|| resolved
->empty()) return;
25522 assertx(fa
.ctx
.func
->cls
);
25523 auto& constants
= fa
.ctx
.func
->cls
->constants
;
25525 for (auto const& c
: *resolved
) {
25526 assertx(c
.first
< constants
.size());
25527 auto& cns
= constants
[c
.first
];
25528 assertx(cns
.kind
== ConstModifiers::Kind::Value
);
25529 always_assert(cns
.val
.has_value());
25530 always_assert(type(*cns
.val
) == KindOfUninit
);
25532 auto cinfo
= fa
.ctx
.func
->cls
->cinfo
;
25533 if (auto const val
= tv(c
.second
.type
)) {
25534 assertx(type(*val
) != KindOfUninit
);
25535 always_assert_flog(
25537 "Attempting to refine class constant {}::{} to {} "
25538 "when index is frozen",
25539 fa
.ctx
.func
->cls
->name
,
25541 show(c
.second
.type
)
25544 if (cinfo
) cinfo
->clsConstantInfo
.erase(cns
.name
);
25545 m_data
->deps
->update(
25547 ConstIndex
{ fa
.ctx
.func
->cls
->name
, c
.first
}
25549 } else if (cinfo
) {
25550 auto old
= folly::get_default(
25551 cinfo
->clsConstantInfo
,
25553 ClsConstInfo
{ TInitCell
, 0 }
25555 old
.type
= unserialize_type(std::move(old
.type
));
25557 if (c
.second
.type
.strictlyMoreRefined(old
.type
)) {
25558 always_assert(c
.second
.refinements
> old
.refinements
);
25559 always_assert_flog(
25561 "Attempting to refine class constant {}::{} to {} "
25562 "when index is frozen",
25565 show(c
.second
.type
)
25567 cinfo
->clsConstantInfo
.insert_or_assign(cns
.name
, c
.second
);
25568 m_data
->deps
->update(cns
, ConstIndex
{ cinfo
->name
, c
.first
});
25570 always_assert_flog(
25571 c
.second
.type
.moreRefined(old
.type
),
25572 "Class constant type invariant violated for {}::{}\n"
25573 " {} is not at least as refined as {}\n",
25574 fa
.ctx
.func
->cls
->name
,
25576 show(c
.second
.type
),
25584 void AnalysisIndex::refine_return_info(const FuncAnalysisResult
& fa
) {
25585 auto const& func
= *fa
.ctx
.func
;
25586 auto& finfo
= func_info(*m_data
, func
);
25588 auto const error_loc
= [&] {
25589 return folly::sformat("{} {}", func
.unit
, func_fullname(func
));
25592 auto changes
= AnalysisDeps::Type::None
;
25594 if (finfo
.retParam
== NoLocalId
) {
25595 // This is just a heuristic; it doesn't mean that the value passed
25596 // in was returned, but that the value of the parameter at the
25597 // point of the RetC was returned. We use it to make (heuristic)
25598 // decisions about whether to do inline interps, so we only allow
25599 // it to change once. (otherwise later passes might not do the
25600 // inline interp, and get worse results, which breaks
25602 if (fa
.retParam
!= NoLocalId
) {
25603 finfo
.retParam
= fa
.retParam
;
25604 changes
|= AnalysisDeps::Type::RetParam
;
25607 always_assert_flog(
25608 finfo
.retParam
== fa
.retParam
,
25609 "Index return param invariant violated in {}.\n"
25610 " Went from {} to {}\n",
25617 auto const unusedParams
= ~fa
.usedParams
;
25618 if (finfo
.unusedParams
!= unusedParams
) {
25619 always_assert_flog(
25620 (finfo
.unusedParams
| unusedParams
) == unusedParams
,
25621 "Index unused params decreased in {}.\n",
25624 finfo
.unusedParams
= unusedParams
;
25625 changes
|= AnalysisDeps::Type::UnusedParams
;
25628 auto const oldReturnTy
= unserialize_type(finfo
.returnTy
);
25629 if (fa
.inferredReturn
.strictlyMoreRefined(oldReturnTy
)) {
25630 if (finfo
.returnRefinements
< options
.returnTypeRefineLimit
) {
25631 finfo
.returnTy
= fa
.inferredReturn
;
25632 finfo
.returnRefinements
+= fa
.localReturnRefinements
+ 1;
25633 if (finfo
.returnRefinements
> options
.returnTypeRefineLimit
) {
25634 FTRACE(1, "maxed out return type refinements at {}\n", error_loc());
25636 changes
|= AnalysisDeps::Type::RetType
;
25637 if (is_scalar(finfo
.returnTy
)) {
25638 changes
|= AnalysisDeps::Type::ScalarRetType
;
25641 FTRACE(1, "maxed out return type refinements at {}\n", error_loc());
25644 always_assert_flog(
25645 fa
.inferredReturn
.moreRefined(oldReturnTy
),
25646 "Index return type invariant violated in {}.\n"
25647 " {} is not at least as refined as {}\n",
25649 show(fa
.inferredReturn
),
25654 always_assert_flog(
25655 !finfo
.effectFree
|| fa
.effectFree
,
25656 "Index effect-free invariant violated in {}.\n"
25657 " Went from true to false\n",
25661 if (finfo
.effectFree
!= fa
.effectFree
) {
25662 finfo
.effectFree
= fa
.effectFree
;
25663 changes
|= AnalysisDeps::Type::RetType
;
25666 always_assert_flog(
25667 !m_data
->frozen
|| changes
== AnalysisDeps::Type::None
,
25668 "Attempting to refine return info for {} ({}) "
25669 "when index is frozen",
25674 if (changes
& AnalysisDeps::Type::RetType
) {
25675 if (auto const name
= Constant::nameFromFuncName(func
.name
)) {
25676 auto const cns
= folly::get_ptr(m_data
->constants
, name
);
25677 always_assert_flog(
25679 "Attempting to update constant {} type, but constant is not present!",
25682 m_data
->deps
->update(*cns
->first
);
25685 m_data
->deps
->update(func
, changes
);
25688 void AnalysisIndex::update_prop_initial_values(const FuncAnalysisResult
& fa
) {
25689 auto const resolved
= fa
.resolvedInitializers
.right();
25690 if (!resolved
|| resolved
->empty()) return;
25692 assertx(fa
.ctx
.cls
);
25693 auto& props
= const_cast<php::Class
*>(fa
.ctx
.cls
)->properties
;
25695 auto changed
= false;
25696 for (auto const& [idx
, info
] : *resolved
) {
25697 assertx(idx
< props
.size());
25698 auto& prop
= props
[idx
];
25700 if (info
.satisfies
) {
25701 if (!(prop
.attrs
& AttrInitialSatisfiesTC
)) {
25702 always_assert_flog(
25704 "Attempting to update AttrInitialSatisfiesTC for {}::{} "
25705 "when index is frozen",
25709 attribute_setter(prop
.attrs
, true, AttrInitialSatisfiesTC
);
25713 always_assert_flog(
25714 !(prop
.attrs
& AttrInitialSatisfiesTC
),
25715 "AttrInitialSatisfiesTC invariant violated for {}::{}\n"
25716 " Went from true to false",
25717 fa
.ctx
.cls
->name
, prop
.name
25721 always_assert_flog(
25722 IMPLIES(!(prop
.attrs
& AttrDeepInit
), !info
.deepInit
),
25723 "AttrDeepInit invariant violated for {}::{}\n"
25724 " Went from false to true",
25725 fa
.ctx
.cls
->name
, prop
.name
25727 if (bool(prop
.attrs
& AttrDeepInit
) != info
.deepInit
) {
25728 always_assert_flog(
25730 "Attempting to update AttrDeepInit for {}::{} "
25731 "when index is frozen",
25735 attribute_setter(prop
.attrs
, info
.deepInit
, AttrDeepInit
);
25738 if (type(info
.val
) != KindOfUninit
) {
25739 always_assert_flog(
25741 "Attempting to update property initial value for {}::{} "
25742 "to {} when index is frozen",
25745 show(from_cell(info
.val
))
25747 always_assert_flog(
25748 type(prop
.val
) == KindOfUninit
||
25749 from_cell(prop
.val
) == from_cell(info
.val
),
25750 "Property initial value invariant violated for {}::{}\n"
25751 " Value went from {} to {}",
25752 fa
.ctx
.cls
->name
, prop
.name
,
25753 show(from_cell(prop
.val
)), show(from_cell(info
.val
))
25755 prop
.val
= info
.val
;
25757 always_assert_flog(
25758 type(prop
.val
) == KindOfUninit
,
25759 "Property initial value invariant violated for {}::{}\n"
25760 " Value went from {} to not set",
25761 fa
.ctx
.cls
->name
, prop
.name
,
25762 show(from_cell(prop
.val
))
25766 if (!changed
) return;
25768 auto const cinfo
= fa
.ctx
.cls
->cinfo
;
25769 if (!cinfo
) return;
25771 assertx(cinfo
->hasBadInitialPropValues
);
25772 auto const noBad
= std::all_of(
25773 begin(props
), end(props
),
25774 [] (const php::Prop
& prop
) {
25775 return bool(prop
.attrs
& AttrInitialSatisfiesTC
);
25780 cinfo
->hasBadInitialPropValues
= false;
25784 void AnalysisIndex::update_type_consts(const ClassAnalysis
& analysis
) {
25785 if (analysis
.resolvedTypeConsts
.empty()) return;
25787 always_assert_flog(
25789 "Attempting to update type constants for {} when index is frozen",
25790 analysis
.ctx
.cls
->name
25793 auto const cls
= const_cast<php::Class
*>(analysis
.ctx
.cls
);
25794 auto const cinfo
= cls
->cinfo
;
25795 if (!cinfo
) return;
25797 for (auto const& update
: analysis
.resolvedTypeConsts
) {
25798 auto const srcCls
= folly::get_default(m_data
->classes
, update
.from
.cls
);
25800 assertx(update
.from
.idx
< srcCls
->constants
.size());
25802 auto& newCns
= [&] () -> php::Const
& {
25803 auto& srcCns
= srcCls
->constants
[update
.from
.idx
];
25804 if (srcCls
== cls
) {
25805 assertx(!srcCns
.resolvedTypeStructure
);
25808 cinfo
->clsConstants
[srcCns
.name
] =
25809 ClassInfo2::ConstIndexAndKind
{
25810 ConstIndex
{ cinfo
->name
, (ConstIndex::Idx
)cls
->constants
.size() },
25813 cls
->constants
.emplace_back(srcCns
);
25814 return cls
->constants
.back();
25817 newCns
.resolvedTypeStructure
= update
.resolved
;
25818 newCns
.contextInsensitive
= update
.contextInsensitive
;
25819 newCns
.resolvedLocally
= true;
25823 void AnalysisIndex::update_bytecode(FuncAnalysisResult
& fa
) {
25824 auto func
= php::WideFunc::mut(const_cast<php::Func
*>(fa
.ctx
.func
));
25825 auto const update
= HHBBC::update_bytecode(func
, std::move(fa
.blockUpdates
));
25826 if (update
== UpdateBCResult::None
) return;
25828 always_assert_flog(
25830 "Attempting to update bytecode for {} when index is frozen",
25831 func_fullname(*fa
.ctx
.func
)
25834 if (update
== UpdateBCResult::ChangedAnalyze
||
25835 fa
.ctx
.func
->name
== s_86cinit
.get()) {
25836 ITRACE(2, "Updated bytecode for {} in a way that requires re-analysis\n",
25837 func_fullname(*fa
.ctx
.func
));
25838 m_data
->worklist
.schedule(fc_from_context(fa
.ctx
, *m_data
));
25841 m_data
->deps
->update(*fa
.ctx
.func
, AnalysisDeps::Type::Bytecode
);
25844 void AnalysisIndex::update_type_aliases(const UnitAnalysis
& ua
) {
25845 assertx(ua
.ctx
.unit
);
25846 if (ua
.resolvedTypeAliases
.empty()) return;
25848 always_assert_flog(
25850 "Attempting to update type-aliases for unit {} when index is frozen\n",
25854 auto const& unit
= lookup_unit(ua
.ctx
.unit
);
25855 for (auto const& update
: ua
.resolvedTypeAliases
) {
25856 assertx(update
.idx
< unit
.typeAliases
.size());
25857 auto& ta
= *unit
.typeAliases
[update
.idx
];
25858 ta
.resolvedTypeStructure
= update
.resolved
;
25859 ta
.resolvedLocally
= true;
25863 // Finish using the AnalysisIndex and calculate the output to be
25864 // returned back from the job.
25865 AnalysisIndex::Output
AnalysisIndex::finish() {
25866 Variadic
<std::unique_ptr
<php::Class
>> classes
;
25867 Variadic
<std::unique_ptr
<php::Func
>> funcs
;
25868 Variadic
<std::unique_ptr
<php::Unit
>> units
;
25869 Variadic
<std::unique_ptr
<php::ClassBytecode
>> clsBC
;
25870 Variadic
<std::unique_ptr
<php::FuncBytecode
>> funcBC
;
25871 Variadic
<AnalysisIndexCInfo
> cinfos
;
25872 Variadic
<AnalysisIndexFInfo
> finfos
;
25873 Variadic
<AnalysisIndexMInfo
> minfos
;
25874 AnalysisOutput::Meta meta
;
25876 assertx(m_data
->frozen
);
25878 // Remove any 86cinits that are now unneeded.
25879 meta
.removedFuncs
= strip_unneeded_constant_inits(*m_data
);
25881 for (auto const name
: m_data
->outClassNames
) {
25882 auto const& cls
= m_data
->allClasses
.at(name
);
25883 mark_fixed_class_constants(*cls
, *m_data
);
25884 meta
.cnsBases
[cls
->name
] = record_cns_bases(*cls
, *m_data
);
25885 for (auto const& clo
: cls
->closures
) {
25886 mark_fixed_class_constants(*clo
, *m_data
);
25890 for (auto const name
: m_data
->outUnitNames
) {
25891 auto const& unit
= m_data
->allUnits
.at(name
);
25892 mark_fixed_unit(*unit
, m_data
->deps
->getChanges());
25895 auto const moveNewAuxs
= [&] (AuxClassGraphs
& auxs
) {
25896 auxs
.noChildren
= std::move(auxs
.newNoChildren
);
25897 auxs
.withChildren
= std::move(auxs
.newWithChildren
);
25900 TSStringSet outClasses
;
25902 classes
.vals
.reserve(m_data
->outClassNames
.size());
25903 clsBC
.vals
.reserve(m_data
->outClassNames
.size());
25904 cinfos
.vals
.reserve(m_data
->outClassNames
.size());
25905 meta
.classDeps
.reserve(m_data
->outClassNames
.size());
25906 for (auto const name
: m_data
->outClassNames
) {
25907 auto& cls
= m_data
->allClasses
.at(name
);
25909 outClasses
.emplace(name
);
25911 meta
.classDeps
.emplace_back(m_data
->deps
->take(cls
.get()));
25912 always_assert(IMPLIES(is_closure(*cls
), meta
.classDeps
.back().empty()));
25913 for (auto const& clo
: cls
->closures
) {
25914 assertx(m_data
->deps
->take(clo
.get()).empty());
25915 outClasses
.emplace(clo
->name
);
25918 clsBC
.vals
.emplace_back(
25919 std::make_unique
<php::ClassBytecode
>(cls
->name
)
25921 auto& bc
= *clsBC
.vals
.back();
25922 for (auto& meth
: cls
->methods
) {
25923 bc
.methodBCs
.emplace_back(meth
->name
, std::move(meth
->rawBlocks
));
25925 for (auto& clo
: cls
->closures
) {
25926 assertx(clo
->methods
.size() == 1);
25927 auto& meth
= clo
->methods
[0];
25928 bc
.methodBCs
.emplace_back(meth
->name
, std::move(meth
->rawBlocks
));
25931 if (auto cinfo
= folly::get_ptr(m_data
->allCInfos
, name
)) {
25932 moveNewAuxs(cinfo
->get()->auxClassGraphs
);
25934 AnalysisIndexCInfo acinfo
;
25935 acinfo
.ptr
= decltype(acinfo
.ptr
){cinfo
->release()};
25936 cinfos
.vals
.emplace_back(std::move(acinfo
));
25937 } else if (auto minfo
= folly::get_ptr(m_data
->allMInfos
, name
)) {
25938 AnalysisIndexMInfo aminfo
;
25939 aminfo
.ptr
= decltype(aminfo
.ptr
){minfo
->release()};
25940 minfos
.vals
.emplace_back(std::move(aminfo
));
25943 classes
.vals
.emplace_back(std::move(cls
));
25946 FSStringSet outFuncs
;
25947 outFuncs
.reserve(m_data
->outFuncNames
.size());
25949 funcs
.vals
.reserve(m_data
->outFuncNames
.size());
25950 funcBC
.vals
.reserve(m_data
->outFuncNames
.size());
25951 finfos
.vals
.reserve(m_data
->outFuncNames
.size());
25952 meta
.funcDeps
.reserve(m_data
->outFuncNames
.size());
25953 for (auto const name
: m_data
->outFuncNames
) {
25954 assertx(!meta
.removedFuncs
.count(name
));
25956 auto& func
= m_data
->allFuncs
.at(name
);
25957 auto& finfo
= m_data
->allFInfos
.at(name
);
25959 outFuncs
.emplace(name
);
25960 meta
.funcDeps
.emplace_back(m_data
->deps
->take(func
.get()));
25962 funcBC
.vals
.emplace_back(
25963 std::make_unique
<php::FuncBytecode
>(name
, std::move(func
->rawBlocks
))
25965 funcs
.vals
.emplace_back(std::move(func
));
25967 if (finfo
->auxClassGraphs
) moveNewAuxs(*finfo
->auxClassGraphs
);
25969 AnalysisIndexFInfo afinfo
;
25970 afinfo
.ptr
= decltype(afinfo
.ptr
){finfo
.release()};
25971 finfos
.vals
.emplace_back(std::move(afinfo
));
25974 hphp_fast_set
<php::Unit
*> outUnits
;
25975 outUnits
.reserve(m_data
->outUnitNames
.size());
25977 units
.vals
.reserve(m_data
->outUnitNames
.size());
25978 meta
.unitDeps
.reserve(m_data
->outUnitNames
.size());
25979 for (auto const name
: m_data
->outUnitNames
) {
25980 auto& unit
= m_data
->allUnits
.at(name
);
25981 outUnits
.emplace(unit
.get());
25982 meta
.unitDeps
.emplace_back(m_data
->deps
->take(unit
.get()));
25983 units
.vals
.emplace_back(std::move(unit
));
25986 SStringSet outConstants
;
25987 for (auto const& [_
, p
] : m_data
->constants
) {
25988 if (!outUnits
.count(p
.second
)) continue;
25989 outConstants
.emplace(p
.first
->name
);
25992 const SStringSet outUnitNames
{
25993 begin(m_data
->outUnitNames
),
25994 end(m_data
->outUnitNames
)
25997 meta
.changed
= std::move(m_data
->deps
->getChanges());
25998 meta
.changed
.filter(outClasses
, outFuncs
, outUnitNames
, outConstants
);
26000 return std::make_tuple(
26001 std::move(classes
),
26013 //////////////////////////////////////////////////////////////////////
26015 PublicSPropMutations::PublicSPropMutations(bool enabled
) : m_enabled
{enabled
} {}
26017 PublicSPropMutations::Data
& PublicSPropMutations::get() {
26018 if (!m_data
) m_data
= std::make_unique
<Data
>();
26022 void PublicSPropMutations::mergeKnown(const ClassInfo
* ci
,
26023 const php::Prop
& prop
,
26025 if (!m_enabled
) return;
26026 ITRACE(4, "PublicSPropMutations::mergeKnown: {} {} {}\n",
26027 ci
->cls
->name
->data(), prop
.name
, show(val
));
26029 auto const res
= get().m_known
.emplace(
26030 KnownKey
{ const_cast<ClassInfo
*>(ci
), prop
.name
}, val
26032 if (!res
.second
) res
.first
->second
|= val
;
26035 void PublicSPropMutations::mergeUnknownClass(SString prop
, const Type
& val
) {
26036 if (!m_enabled
) return;
26037 ITRACE(4, "PublicSPropMutations::mergeUnknownClass: {} {}\n",
26040 auto const res
= get().m_unknown
.emplace(prop
, val
);
26041 if (!res
.second
) res
.first
->second
|= val
;
26044 void PublicSPropMutations::mergeUnknown(Context ctx
) {
26045 if (!m_enabled
) return;
26046 ITRACE(4, "PublicSPropMutations::mergeUnknown\n");
26049 * We have a case here where we know neither the class nor the static
26050 * property name. This means we have to pessimize public static property
26051 * types for the entire program.
26053 * We could limit it to pessimizing them by merging the `val' type, but
26054 * instead we just throw everything away---this optimization is not
26055 * expected to be particularly useful on programs that contain any
26056 * instances of this situation.
26060 "NOTE: had to mark everything unknown for public static "
26061 "property types due to dynamic code. -fanalyze-public-statics "
26062 "will not help for this program.\n"
26063 "NOTE: The offending code occured in this context: %s\n",
26066 get().m_nothing_known
= true;
26069 //////////////////////////////////////////////////////////////////////
26071 #define UNIMPLEMENTED always_assert_flog(false, "{} not implemented for AnalysisIndex", __func__)
26073 bool AnalysisIndexAdaptor::frozen() const {
26074 return index
.frozen();
26077 void AnalysisIndexAdaptor::push_context(const Context
& ctx
) const {
26078 index
.push_context(ctx
);
26081 void AnalysisIndexAdaptor::pop_context() const {
26082 index
.pop_context();
26085 bool AnalysisIndexAdaptor::set_in_type_cns(bool b
) const {
26086 return index
.set_in_type_cns(b
);
26089 const php::Unit
* AnalysisIndexAdaptor::lookup_func_unit(const php::Func
& func
) const {
26090 return &index
.lookup_func_unit(func
);
26092 const php::Unit
* AnalysisIndexAdaptor::lookup_class_unit(const php::Class
& cls
) const {
26093 return &index
.lookup_class_unit(cls
);
26095 const php::Class
* AnalysisIndexAdaptor::lookup_const_class(const php::Const
& cns
) const {
26096 return index
.lookup_const_class(cns
);
26098 const php::Class
* AnalysisIndexAdaptor::lookup_closure_context(const php::Class
& cls
) const {
26099 return &index
.lookup_closure_context(cls
);
26101 const php::Class
* AnalysisIndexAdaptor::lookup_class(SString c
) const {
26102 return index
.lookup_class(c
);
26105 const CompactVector
<const php::Class
*>*
26106 AnalysisIndexAdaptor::lookup_closures(const php::Class
*) const {
26110 const hphp_fast_set
<const php::Func
*>*
26111 AnalysisIndexAdaptor::lookup_extra_methods(const php::Class
*) const {
26115 Optional
<res::Class
> AnalysisIndexAdaptor::resolve_class(SString n
) const {
26116 return index
.resolve_class(n
);
26118 Optional
<res::Class
>
26119 AnalysisIndexAdaptor::resolve_class(const php::Class
& c
) const {
26120 return index
.resolve_class(c
);
26122 std::pair
<const php::TypeAlias
*, bool>
26123 AnalysisIndexAdaptor::lookup_type_alias(SString n
) const {
26124 return index
.lookup_type_alias(n
);
26126 Index::ClassOrTypeAlias
26127 AnalysisIndexAdaptor::lookup_class_or_type_alias(SString n
) const {
26128 return index
.lookup_class_or_type_alias(n
);
26131 res::Func
AnalysisIndexAdaptor::resolve_func_or_method(const php::Func
& f
) const {
26132 return index
.resolve_func_or_method(f
);
26134 res::Func
AnalysisIndexAdaptor::resolve_func(SString f
) const {
26135 return index
.resolve_func(f
);
26137 res::Func
AnalysisIndexAdaptor::resolve_method(Context
,
26140 return index
.resolve_method(t
, n
);
26142 res::Func
AnalysisIndexAdaptor::resolve_ctor(const Type
& obj
) const {
26143 return index
.resolve_ctor(obj
);
26146 std::vector
<std::pair
<SString
, ClsConstInfo
>>
26147 AnalysisIndexAdaptor::lookup_class_constants(const php::Class
& cls
) const {
26148 return index
.lookup_class_constants(cls
);
26151 ClsConstLookupResult
26152 AnalysisIndexAdaptor::lookup_class_constant(Context
,
26154 const Type
& name
) const {
26155 return index
.lookup_class_constant(cls
, name
);
26158 ClsTypeConstLookupResult
26159 AnalysisIndexAdaptor::lookup_class_type_constant(
26162 const Index::ClsTypeConstLookupResolver
& resolver
26164 return index
.lookup_class_type_constant(cls
, name
, resolver
);
26167 ClsTypeConstLookupResult
26168 AnalysisIndexAdaptor::lookup_class_type_constant(const php::Class
& ctx
,
26170 ConstIndex idx
) const {
26171 return index
.lookup_class_type_constant(ctx
, n
, idx
);
26174 std::vector
<std::pair
<SString
, ConstIndex
>>
26175 AnalysisIndexAdaptor::lookup_flattened_class_type_constants(
26176 const php::Class
& cls
26178 return index
.lookup_flattened_class_type_constants(cls
);
26181 Type
AnalysisIndexAdaptor::lookup_constant(Context
, SString n
) const {
26182 return index
.lookup_constant(n
);
26184 bool AnalysisIndexAdaptor::func_depends_on_arg(const php::Func
* f
,
26185 size_t arg
) const {
26186 return index
.func_depends_on_arg(*f
, arg
);
26189 AnalysisIndexAdaptor::lookup_foldable_return_type(Context
,
26190 const CallContext
& callee
) const {
26191 return index
.lookup_foldable_return_type(callee
);
26193 Index::ReturnType
AnalysisIndexAdaptor::lookup_return_type(Context
,
26194 MethodsInfo
* methods
,
26197 return index
.lookup_return_type(methods
, func
);
26199 Index::ReturnType
AnalysisIndexAdaptor::lookup_return_type(Context
,
26200 MethodsInfo
* methods
,
26201 const CompactVector
<Type
>& args
,
26202 const Type
& context
,
26205 return index
.lookup_return_type(methods
, args
, context
, func
);
26208 std::pair
<Index::ReturnType
, size_t>
26209 AnalysisIndexAdaptor::lookup_return_type_raw(const php::Func
* f
) const {
26210 return index
.lookup_return_type_raw(*f
);
26212 CompactVector
<Type
>
26213 AnalysisIndexAdaptor::lookup_closure_use_vars(const php::Func
*, bool) const {
26217 PropState
AnalysisIndexAdaptor::lookup_private_props(const php::Class
* cls
,
26219 return index
.lookup_private_props(*cls
);
26221 PropState
AnalysisIndexAdaptor::lookup_private_statics(const php::Class
* cls
,
26223 return index
.lookup_private_statics(*cls
);
26226 PropLookupResult
AnalysisIndexAdaptor::lookup_static(Context
,
26227 const PropertiesInfo
&,
26229 const Type
& name
) const {
26230 // Not implemented yet, be conservative.
26232 auto const sname
= [&] () -> SString
{
26233 // Treat non-string names conservatively, but the caller should be
26235 if (!is_specialized_string(name
)) return nullptr;
26236 return sval_of(name
);
26239 return PropLookupResult
{
26251 Type
AnalysisIndexAdaptor::lookup_public_prop(const Type
&, const Type
&) const {
26256 AnalysisIndexAdaptor::merge_static_type(Context
,
26257 PublicSPropMutations
& publicMutations
,
26258 PropertiesInfo
& privateProps
,
26264 bool mustBeReadOnly
) const {
26265 return index
.merge_static_type(
26277 bool AnalysisIndexAdaptor::using_class_dependencies() const {
26281 #undef UNIMPLEMENTED
26283 //////////////////////////////////////////////////////////////////////
26285 template <typename T
>
26286 void AnalysisIndexParam
<T
>::Deleter::operator()(T
* t
) const {
26290 template <typename T
>
26291 void AnalysisIndexParam
<T
>::serde(BlobEncoder
& sd
) const {
26295 template <typename T
>
26296 void AnalysisIndexParam
<T
>::serde(BlobDecoder
& sd
) {
26300 template struct AnalysisIndexParam
<ClassInfo2
>;
26301 template struct AnalysisIndexParam
<FuncInfo2
>;
26302 template struct AnalysisIndexParam
<MethodsWithoutCInfo
>;
26304 //////////////////////////////////////////////////////////////////////
26308 //////////////////////////////////////////////////////////////////////
26310 MAKE_UNIQUE_PTR_BLOB_SERDE_HELPER(HHBBC::ClassInfo2
);
26311 MAKE_UNIQUE_PTR_BLOB_SERDE_HELPER(HHBBC::FuncInfo2
);
26312 MAKE_UNIQUE_PTR_BLOB_SERDE_HELPER(HHBBC::FuncFamily2
);
26313 MAKE_UNIQUE_PTR_BLOB_SERDE_HELPER(HHBBC::MethodsWithoutCInfo
);
26314 MAKE_UNIQUE_PTR_BLOB_SERDE_HELPER(HHBBC::BuildSubclassListJob::Split
);
26316 //////////////////////////////////////////////////////////////////////