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 const a
= folly::get_ptr(allows
, fc
);
6400 assertx(a
->process
->contains(index
.bucketIdx
));
6401 if (auto const cls
= folly::get_default(index
.classes
, c
.name
)) {
6402 auto const canon
= canonicalize(*cls
);
6403 if (auto const c2
= canon
.cls()) {
6404 auto const ca
= folly::get_ptr(allows
, c2
);
6406 assertx(ca
->present
->contains(index
.bucketIdx
));
6407 return a
->process
->isSubset(
6408 bytecode
? *ca
->withBC
: *ca
->present
6411 if (auto const f
= canon
.func()) {
6412 auto const fa
= folly::get_ptr(allows
, f
);
6414 assertx(fa
->present
->contains(index
.bucketIdx
));
6415 return a
->process
->isSubset(
6416 bytecode
? *fa
->withBC
: *fa
->present
6419 always_assert(false);
6421 if (auto const ta
= folly::get_ptr(index
.typeAliases
, c
.name
)) {
6422 auto const ua
= folly::get_ptr(allows
, ta
->second
);
6424 assertx(ua
->present
->contains(index
.bucketIdx
));
6425 return a
->process
->isSubset(*ua
->present
);
6427 if (index
.badClasses
.count(c
.name
)) {
6428 auto const ca
= folly::get_ptr(badClassAllows
, c
.name
);
6430 assertx(ca
->present
->contains(index
.bucketIdx
));
6431 return a
->process
->isSubset(*ca
->present
);
6436 bool allowed(FuncClsUnit fc
, Func f
, bool bytecode
) const {
6437 assertx(!fc
.unit());
6438 auto const a
= folly::get_ptr(allows
, fc
);
6440 assertx(a
->process
->contains(index
.bucketIdx
));
6441 if (auto const func
= folly::get_default(index
.funcs
, f
.name
)) {
6442 auto const fa
= folly::get_ptr(allows
, func
);
6444 assertx(fa
->present
->contains(index
.bucketIdx
));
6445 return a
->process
->isSubset(
6446 bytecode
? *fa
->withBC
: *fa
->present
6449 if (index
.badFuncs
.count(f
.name
)) {
6450 auto const fa
= folly::get_ptr(badFuncAllows
, f
.name
);
6452 assertx(fa
->present
->contains(index
.bucketIdx
));
6453 return a
->process
->isSubset(*fa
->present
);
6458 bool allowed(FuncClsUnit fc
, Constant cns
) const {
6459 assertx(!fc
.unit());
6460 auto const a
= folly::get_ptr(allows
, fc
);
6462 assertx(a
->process
->contains(index
.bucketIdx
));
6463 if (auto const c
= folly::get_ptr(index
.constants
, cns
.name
)) {
6464 auto const unit
= folly::get_default(index
.units
, c
->second
->filename
);
6466 auto const ua
= folly::get_ptr(allows
, unit
);
6468 assertx(ua
->present
->contains(index
.bucketIdx
));
6469 return a
->process
->isSubset(*ua
->present
);
6471 if (index
.badConstants
.count(cns
.name
)) {
6472 auto const ca
= folly::get_ptr(badConstantAllows
, cns
.name
);
6474 assertx(ca
->present
->contains(index
.bucketIdx
));
6475 return a
->process
->isSubset(*ca
->present
);
6480 // Return appropriate entity to attribute the dependency to. If
6481 // we're analyzing a function within a class, use the class. If it's
6482 // a top-level function, use that.
6483 FuncClsUnit
context() const {
6484 auto const fc
= fc_from_context(context_for_deps(index
), index
);
6485 if (auto const c
= fc
.cls()) return canonicalize(*c
);
6489 // If a class is a closure, the correct context might actually be a
6491 FuncClsUnit
canonicalize(const php::Class
& cls
) const {
6492 // If this is a closure, the context is the closure's declaring
6493 // class or function.
6494 if (cls
.closureContextCls
) {
6496 folly::get_default(index
.classes
, cls
.closureContextCls
);
6500 if (cls
.closureDeclFunc
) {
6501 auto const f
= folly::get_default(index
.funcs
, cls
.closureDeclFunc
);
6508 const php::Func
* from(MethRef m
) const {
6509 if (auto const cls
= folly::get_default(index
.classes
, m
.cls
)) {
6510 assertx(m
.idx
< cls
->methods
.size());
6511 return cls
->methods
[m
.idx
].get();
6516 const php::Const
* from(ConstIndex cns
) const {
6517 if (auto const cls
= folly::get_default(index
.classes
, cns
.cls
)) {
6518 assertx(cns
.idx
< cls
->constants
.size());
6519 return &cls
->constants
[cns
.idx
];
6524 std::string
display(MethRef m
) const {
6525 if (auto const p
= from(m
)) return func_fullname(*p
);
6529 std::string
display(ConstIndex cns
) const {
6530 if (auto const p
= from(cns
)) {
6531 return folly::sformat("{}::{}", p
->cls
, p
->name
);
6533 return show(cns
, AnalysisIndexAdaptor
{ index
.index
});
6536 static std::string
displayAdded(Type t
) {
6537 auto out
= show(t
- Type::Meta
);
6538 if (!out
.empty()) folly::format(&out
, " of ");
6542 using FuncClsUnitSet
=
6543 hphp_fast_set
<FuncClsUnit
, FuncClsUnitHasher
>;
6544 using FuncClsUnitToType
=
6545 hphp_fast_map
<FuncClsUnit
, Type
, FuncClsUnitHasher
>;
6547 void schedule(const FuncClsUnitSet
* fcs
) {
6548 if (!fcs
|| fcs
->empty()) return;
6549 TinyVector
<FuncClsUnit
, 4> v
;
6550 v
.insert(begin(*fcs
), end(*fcs
));
6554 void schedule(const FuncClsUnitToType
* fcs
, Type t
) {
6555 assertx(!(t
& Type::Meta
));
6556 if (!fcs
|| fcs
->empty()) return;
6557 TinyVector
<FuncClsUnit
, 4> v
;
6558 for (auto const [fc
, t2
] : *fcs
) {
6559 if (t
& t2
) v
.emplace_back(fc
);
6564 void addToWorklist(TinyVector
<FuncClsUnit
, 4>& fcs
) {
6565 if (fcs
.empty()) return;
6567 fcs
.begin(), fcs
.end(),
6568 [] (FuncClsUnit fc1
, FuncClsUnit fc2
) {
6569 if (auto const c1
= fc1
.cls()) {
6570 if (auto const c2
= fc2
.cls()) {
6571 return string_data_lt_type
{}(c1
->name
, c2
->name
);
6575 if (auto const f1
= fc1
.func()) {
6576 if (auto const f2
= fc2
.func()) {
6577 return string_data_lt_func
{}(f1
->name
, f2
->name
);
6581 if (auto const u1
= fc1
.unit()) {
6582 if (auto const u2
= fc2
.unit()) {
6583 return string_data_lt
{}(u1
->filename
, u2
->filename
);
6585 return !fc2
.cls() && !fc2
.func();
6591 for (auto const fc
: fcs
) index
.worklist
.schedule(fc
);
6594 const AnalysisIndex::IndexData
& index
;
6595 AnalysisChangeSet changes
;
6596 hphp_fast_map
<FuncClsUnit
, AnalysisDeps
, FuncClsUnitHasher
> deps
;
6598 hphp_fast_map
<FuncClsUnit
, BucketPresence
, FuncClsUnitHasher
> allows
;
6599 TSStringToOneT
<BucketPresence
> badClassAllows
;
6600 FSStringToOneT
<BucketPresence
> badFuncAllows
;
6601 SStringToOneT
<BucketPresence
> badConstantAllows
;
6603 hphp_fast_map
<const php::Func
*, FuncClsUnitToType
> funcs
;
6604 hphp_fast_map
<const php::Const
*, FuncClsUnitSet
> clsConstants
;
6605 hphp_fast_map
<const php::Constant
*, FuncClsUnitSet
> constants
;
6606 hphp_fast_map
<const php::Class
*, FuncClsUnitSet
> anyClsConstants
;
6609 //////////////////////////////////////////////////////////////////////
6611 using IndexData
= Index::IndexData
;
6613 std::mutex closure_use_vars_mutex
;
6614 std::mutex private_propstate_mutex
;
6616 DependencyContext
make_dep(const php::Func
* func
) {
6617 return DependencyContext
{DependencyContextType::Func
, func
};
6619 DependencyContext
make_dep(const php::Class
* cls
) {
6620 return DependencyContext
{DependencyContextType::Class
, cls
};
6622 DependencyContext
make_dep(const php::Prop
* prop
) {
6623 return DependencyContext
{DependencyContextType::Prop
, prop
};
6625 DependencyContext
make_dep(const FuncFamily
* family
) {
6626 return DependencyContext
{DependencyContextType::FuncFamily
, family
};
6629 DependencyContext
dep_context(IndexData
& data
, const Context
& baseCtx
) {
6630 auto const& ctx
= baseCtx
.forDep();
6631 if (!ctx
.cls
|| !data
.useClassDependencies
) return make_dep(ctx
.func
);
6632 auto const cls
= ctx
.cls
->closureContextCls
6633 ? data
.classes
.at(ctx
.cls
->closureContextCls
)
6635 if (is_used_trait(*cls
)) return make_dep(ctx
.func
);
6636 return make_dep(cls
);
6639 template <typename T
>
6640 void add_dependency(IndexData
& data
,
6644 if (data
.frozen
) return;
6646 auto d
= dep_context(data
, dst
);
6647 DepMap::accessor acc
;
6648 data
.dependencyMap
.insert(acc
, make_dep(src
));
6649 auto& current
= acc
->second
[d
];
6650 current
= current
| newMask
;
6652 // We should only have a return type dependency on func families.
6655 acc
->first
.tag() == DependencyContextType::FuncFamily
,
6656 newMask
== Dep::ReturnTy
6661 template <typename T
>
6662 void find_deps(IndexData
& data
,
6665 DependencyContextSet
& deps
) {
6666 auto const srcDep
= make_dep(src
);
6668 // We should only ever have return type dependencies on func family.
6671 srcDep
.tag() == DependencyContextType::FuncFamily
,
6672 mask
== Dep::ReturnTy
6676 DepMap::const_accessor acc
;
6677 if (data
.dependencyMap
.find(acc
, srcDep
)) {
6678 for (auto const& kv
: acc
->second
) {
6679 if (has_dep(kv
.second
, mask
)) deps
.insert(kv
.first
);
6684 //////////////////////////////////////////////////////////////////////
6686 FuncInfo
* func_info(IndexData
& data
, const php::Func
* f
) {
6687 assertx(f
->idx
< data
.funcInfo
.size());
6688 auto const fi
= &data
.funcInfo
[f
->idx
];
6689 assertx(fi
->func
== f
);
6693 FuncInfo2
& func_info(AnalysisIndex::IndexData
& data
, const php::Func
& f
) {
6694 assertx(f
.idx
< data
.finfosByIdx
.size());
6695 auto const fi
= data
.finfosByIdx
[f
.idx
];
6696 assertx(fi
->func
== &f
);
6700 //////////////////////////////////////////////////////////////////////
6702 // Obtain the php::Func* represented by a MethRef.
6703 const php::Func
* func_from_meth_ref(const IndexData
& index
,
6704 const MethRef
& meth
) {
6705 auto const cls
= index
.classes
.at(meth
.cls
);
6706 assertx(meth
.idx
< cls
->methods
.size());
6707 return cls
->methods
[meth
.idx
].get();
6710 const php::Func
* func_from_meth_ref(const AnalysisIndex::IndexData
& index
,
6711 const MethRef
& meth
) {
6712 if (!index
.deps
->add(AnalysisDeps::Class
{ meth
.cls
})) return nullptr;
6713 auto const cls
= folly::get_default(index
.classes
, meth
.cls
);
6716 !index
.badClasses
.count(meth
.cls
),
6717 "MethRef references non-existent class {}\n",
6722 assertx(meth
.idx
< cls
->methods
.size());
6723 return cls
->methods
[meth
.idx
].get();
6726 //////////////////////////////////////////////////////////////////////
6730 //////////////////////////////////////////////////////////////////////
6732 // Defined here so that AnalysisIndex::IndexData is a complete type.
6734 bool ClassGraph::storeAuxs(AnalysisIndex::IndexData
& i
, bool children
) const {
6737 auto const add
= [&] (AuxClassGraphs
& auxs
) {
6739 if (!auxs
.newWithChildren
.emplace(*this).second
) return false;
6740 auxs
.newNoChildren
.erase(*this);
6743 if (auxs
.newWithChildren
.count(*this)) return false;
6744 return auxs
.newNoChildren
.emplace(*this).second
;
6748 // Get the current context and store this ClassGraph on it's aux
6750 auto const fc
= fc_from_context(context_for_deps(i
), i
);
6751 if (auto const c
= fc
.cls()) {
6752 if (!c
->cinfo
) return false;
6753 if (c
->cinfo
== cinfo2()) return true;
6754 if (add(c
->cinfo
->auxClassGraphs
)) {
6756 2, "{} now stores {} as an auxiliary ClassGraph{}\n",
6758 children
? " (with children)" : ""
6762 } else if (auto const f
= fc
.func()) {
6763 auto& fi
= func_info(i
, *f
);
6764 if (!fi
.auxClassGraphs
) {
6765 fi
.auxClassGraphs
= std::make_unique
<AuxClassGraphs
>();
6767 if (add(*fi
.auxClassGraphs
)) {
6769 2, "{} now stores {} as an auxiliary ClassGraph{}\n",
6771 children
? " (with children)" : ""
6780 bool ClassGraph::onAuxs(AnalysisIndex::IndexData
& i
, bool children
) const {
6781 // Check if this ClassGraph is on the current context's aux set *or*
6782 // if it is implied by another ClassGraph on the aux set (for
6783 // example, if this ClassGraph is a parent of a ClassGraph already
6785 auto const check
= [&] (const AuxClassGraphs
& auxs
, Node
* target
) {
6786 assertx(!target
|| !target
->isMissing());
6788 if (target
== this_
) return true;
6789 // Check for direct membership first
6790 if (auxs
.withChildren
.count(*this)) return true;
6791 if (auxs
.noChildren
.count(*this)) return !children
;
6792 if (this_
->isMissing()) return false;
6794 // Check if any parents of this Node are on the set.
6795 if (!auxs
.withChildren
.empty()) {
6796 auto const a
= forEachParent(
6799 if (target
== &p
) return Action::Stop
;
6800 return auxs
.withChildren
.count(ClassGraph
{ &p
})
6805 if (a
== Action::Stop
) return true;
6808 if (children
) return false;
6810 TLNodeIdxSet visited
;
6811 for (auto const n
: auxs
.noChildren
) {
6812 if (n
.this_
->isMissing()) continue;
6813 if (findParent(*n
.this_
, *this_
, *visited
)) return true;
6815 for (auto const n
: auxs
.withChildren
) {
6816 if (n
.this_
->isMissing()) continue;
6817 if (findParent(*n
.this_
, *this_
, *visited
)) return true;
6819 if (target
&& findParent(*target
, *this_
, *visited
)) return true;
6824 auto const fc
= fc_from_context(context_for_deps(i
), i
);
6825 if (auto const c
= fc
.cls()) {
6826 if (!c
->cinfo
) return false;
6827 if (c
->cinfo
== cinfo2()) return true;
6828 return check(c
->cinfo
->auxClassGraphs
, c
->cinfo
->classGraph
.this_
);
6831 if (auto const f
= fc
.func()) {
6832 auto const& fi
= func_info(i
, *f
);
6833 if (!fi
.auxClassGraphs
) return false;
6834 return check(*fi
.auxClassGraphs
, nullptr);
6840 // Ensure ClassGraph is not missing
6841 bool ClassGraph::ensure() const {
6843 auto const i
= table().index
;
6844 if (!i
) return true;
6845 if (onAuxs(*i
, false)) {
6846 if (i
->frozen
) always_assert(storeAuxs(*i
, false));
6848 } else if (i
->deps
->allowed(AnalysisDeps::Class
{ name() })) {
6849 if (this_
->isMissing()) {
6850 always_assert(i
->deps
->add(AnalysisDeps::Class
{ name() }));
6852 if (!i
->frozen
) return true;
6853 if (!storeAuxs(*i
, false)) {
6854 (void)i
->deps
->add(AnalysisDeps::Class
{ name() });
6858 (void)i
->deps
->add(AnalysisDeps::Class
{ name() });
6863 // Ensure ClassGraph is not missing and has complete child
6865 bool ClassGraph::ensureWithChildren() const {
6867 auto const i
= table().index
;
6868 if (!i
) return true;
6869 if (onAuxs(*i
, true)) {
6870 if (i
->frozen
) always_assert(storeAuxs(*i
, true));
6872 } else if (i
->deps
->allowed(AnalysisDeps::Class
{ name() })) {
6873 if (this_
->isMissing() ||
6874 (!this_
->hasCompleteChildren() && !this_
->isConservative())) {
6875 always_assert(i
->deps
->add(AnalysisDeps::Class
{ name() }));
6877 if (!i
->frozen
) return true;
6878 if (!storeAuxs(*i
, true)) {
6879 (void)i
->deps
->add(AnalysisDeps::Class
{ name() });
6883 (void)i
->deps
->add(AnalysisDeps::Class
{ name() });
6888 // Ensure ClassGraph is not missing and has an associated ClassInfo2
6889 // (strongest condition).
6890 bool ClassGraph::ensureCInfo() const {
6891 auto const i
= table().index
;
6892 return !i
|| i
->deps
->add(AnalysisDeps::Class
{ name() });
6895 bool ClassGraph::allowed(bool children
) const {
6896 auto const i
= table().index
;
6897 return !i
|| onAuxs(*i
, children
) ||
6898 i
->deps
->allowed(AnalysisDeps::Class
{ name() });
6901 //////////////////////////////////////////////////////////////////////
6905 //////////////////////////////////////////////////////////////////////
6907 struct TraitMethod
{
6908 using class_type
= std::pair
<const ClassInfo2
*, const php::Class
*>;
6909 using method_type
= const php::Func
*;
6910 using origin_type
= SString
;
6912 TraitMethod(class_type trait_
, method_type method_
, Attr modifiers_
)
6915 , modifiers(modifiers_
)
6924 using string_type
= LSString
;
6925 using class_type
= TraitMethod::class_type
;
6926 using method_type
= TraitMethod::method_type
;
6927 using origin_type
= TraitMethod::origin_type
;
6929 struct TMIException
: std::exception
{
6930 explicit TMIException(std::string msg
) : msg(msg
) {}
6931 const char* what() const noexcept override
{ return msg
.c_str(); }
6936 // Return the name for the trait class.
6937 static const string_type
clsName(class_type traitCls
) {
6938 return traitCls
.first
->name
;
6941 // Return the name of the trait where the method was originally defined
6942 static origin_type
originalClass(method_type meth
) {
6943 return meth
->originalClass
? meth
->originalClass
: meth
->cls
->name
;
6947 static bool isAbstract(Attr modifiers
) {
6948 return modifiers
& AttrAbstract
;
6951 // Whether to exclude methods with name `methName' when adding.
6952 static bool exclude(string_type methName
) {
6953 return Func::isSpecial(methName
);
6957 static void errorDuplicateMethod(class_type cls
,
6958 string_type methName
,
6959 const std::vector
<const StringData
*>&) {
6960 auto const& m
= cls
.second
->methods
;
6961 if (std::find_if(m
.begin(), m
.end(),
6962 [&] (auto const& f
) {
6963 return f
->name
->same(methName
);
6965 // the duplicate methods will be overridden by the class method.
6968 throw TMIException(folly::sformat("DuplicateMethod: {}", methName
));
6972 using TMIData
= TraitMethodImportData
<TraitMethod
, TMIOps
>;
6974 //////////////////////////////////////////////////////////////////////
6976 template <typename T
, typename R
>
6977 void add_symbol_to_index(R
& map
, T t
, const char* type
) {
6978 auto const name
= t
->name
;
6979 auto const ret
= map
.emplace(name
, std::move(t
));
6982 "More than one {} with the name {} "
6983 "(should have been caught by parser)",
6989 template <typename T
, typename R
, typename E
>
6990 void add_symbol_to_index(R
& map
, T t
, const char* type
, const E
& other
) {
6991 auto const it
= other
.find(t
->name
);
6994 "More than one symbol with the name {} "
6995 "(should have been caught by parser)",
6998 add_symbol_to_index(map
, std::move(t
), type
);
7001 // We want const qualifiers on various index data structures for php
7002 // object pointers, but during index creation time we need to
7003 // manipulate some of their attributes (changing the representation).
7004 // This little wrapper keeps the const_casting out of the main line of
7006 void attribute_setter(const Attr
& attrs
, bool set
, Attr attr
) {
7007 attrSetter(const_cast<Attr
&>(attrs
), set
, attr
);
7010 void add_system_constants_to_index(IndexData
& index
) {
7011 for (auto cnsPair
: Native::getConstants()) {
7012 assertx(cnsPair
.second
.m_type
!= KindOfUninit
);
7013 auto pc
= new php::Constant
{
7018 add_symbol_to_index(index
.constants
, pc
, "constant");
7022 void add_unit_to_index(IndexData
& index
, php::Unit
& unit
) {
7024 index
.units
.emplace(unit
.filename
, &unit
).second
,
7025 "More than one unit with the same name {} "
7026 "(should have been caught by parser)",
7030 for (auto& ta
: unit
.typeAliases
) {
7031 add_symbol_to_index(
7039 for (auto& c
: unit
.constants
) {
7040 add_symbol_to_index(index
.constants
, c
.get(), "constant");
7043 for (auto& m
: unit
.modules
) {
7044 add_symbol_to_index(index
.modules
, m
.get(), "module");
7048 void add_class_to_index(IndexData
& index
, php::Class
& c
) {
7049 if (c
.attrs
& AttrEnum
) {
7050 add_symbol_to_index(index
.enums
, &c
, "enum");
7053 add_symbol_to_index(index
.classes
, &c
, "class", index
.typeAliases
);
7055 for (auto& m
: c
.methods
) {
7056 attribute_setter(m
->attrs
, false, AttrNoOverride
);
7057 m
->idx
= index
.nextFuncId
++;
7061 void add_func_to_index(IndexData
& index
, php::Func
& func
) {
7062 add_symbol_to_index(index
.funcs
, &func
, "function");
7063 func
.idx
= index
.nextFuncId
++;
7066 void add_program_to_index(IndexData
& index
) {
7067 trace_time timer
{"add program to index", index
.sample
};
7068 timer
.ignore_client_stats();
7070 auto& program
= *index
.program
;
7071 for (auto const& u
: program
.units
) {
7072 add_unit_to_index(index
, *u
);
7074 for (auto const& c
: program
.classes
) {
7075 add_class_to_index(index
, *c
);
7076 for (auto const& clo
: c
->closures
) {
7077 add_class_to_index(index
, *clo
);
7080 for (auto const& f
: program
.funcs
) {
7081 add_func_to_index(index
, *f
);
7084 for (auto const& c
: program
.classes
) {
7085 assertx(!c
->closureContextCls
);
7086 for (auto const& clo
: c
->closures
) {
7087 assertx(clo
->closureContextCls
);
7088 auto& s
= index
.classClosureMap
[index
.classes
.at(clo
->closureContextCls
)];
7089 s
.emplace_back(clo
.get());
7093 // All funcs have been assigned indices above. Now for each func we
7094 // initialize a default FuncInfo in the funcInfo vec at the
7095 // appropriate index.
7097 trace_time timer2
{"create func-infos"};
7098 timer2
.ignore_client_stats();
7100 index
.funcInfo
.resize(index
.nextFuncId
);
7102 auto const create
= [&] (const php::Func
& f
) {
7103 assertx(f
.idx
< index
.funcInfo
.size());
7104 auto& fi
= index
.funcInfo
[f
.idx
];
7111 [&] (const std::unique_ptr
<php::Class
>& cls
) {
7112 for (auto const& m
: cls
->methods
) create(*m
);
7113 for (auto const& clo
: cls
->closures
) {
7114 assertx(clo
->methods
.size() == 1);
7115 create(*clo
->methods
[0]);
7122 [&] (const std::unique_ptr
<php::Func
>& func
) { create(*func
); }
7126 //////////////////////////////////////////////////////////////////////
7129 * Lists of interfaces that conflict with each other due to being
7130 * implemented by the same class.
7133 struct InterfaceConflicts
{
7134 SString name
{nullptr};
7135 // The number of classes which implements this interface (used to
7136 // prioritize lower slots for more heavily used interfaces).
7138 TSStringSet conflicts
;
7139 template <typename SerDe
> void serde(SerDe
& sd
) {
7140 sd(name
)(usage
)(conflicts
, string_data_lt_type
{});
7144 void compute_iface_vtables(IndexData
& index
,
7145 std::vector
<InterfaceConflicts
> conflicts
) {
7146 trace_time tracer
{"compute interface vtables"};
7147 tracer
.ignore_client_stats();
7149 if (conflicts
.empty()) return;
7151 // Sort interfaces by usage frequencies. We assign slots greedily,
7152 // so sort the interface list so the most frequently implemented
7157 [&] (const InterfaceConflicts
& a
, const InterfaceConflicts
& b
) {
7158 if (a
.usage
!= b
.usage
) return a
.usage
> b
.usage
;
7159 return string_data_lt_type
{}(a
.name
, b
.name
);
7163 // Assign slots, keeping track of the largest assigned slot and the
7164 // total number of uses for each slot.
7167 hphp_fast_map
<Slot
, int> slotUses
;
7168 boost::dynamic_bitset
<> used
;
7170 for (auto const& iface
: conflicts
) {
7173 // Find the lowest Slot that doesn't conflict with anything in the
7174 // conflict set for iface.
7175 auto const slot
= [&] () -> Slot
{
7176 // No conflicts. This is the only interface implemented by the
7177 // classes that implement it.
7178 if (iface
.conflicts
.empty()) return 0;
7180 for (auto const conflict
: iface
.conflicts
) {
7181 auto const it
= index
.ifaceSlotMap
.find(conflict
);
7182 if (it
== end(index
.ifaceSlotMap
)) continue;
7183 auto const s
= it
->second
;
7184 if (used
.size() <= s
) used
.resize(s
+ 1);
7189 return used
.any() ? used
.find_first() : used
.size();
7193 index
.ifaceSlotMap
.emplace(iface
.name
, slot
).second
7195 maxSlot
= std::max(maxSlot
, slot
);
7196 slotUses
[slot
] += iface
.usage
;
7200 // Make sure we have an initialized entry for each slot for the sort below.
7201 for (Slot slot
= 0; slot
< maxSlot
; ++slot
) {
7202 always_assert(slotUses
.count(slot
));
7206 // Finally, sort and reassign slots so the most frequently used
7207 // slots come first. This slightly reduces the number of wasted
7208 // vtable vector entries at runtime.
7210 auto const slots
= [&] {
7211 std::vector
<std::pair
<Slot
, int>> flattened
{
7212 begin(slotUses
), end(slotUses
)
7217 [&] (auto const& a
, auto const& b
) {
7218 if (a
.second
!= b
.second
) return a
.second
> b
.second
;
7219 return a
.first
< b
.first
;
7222 std::vector
<Slot
> out
;
7223 out
.reserve(flattened
.size());
7224 for (auto const& [slot
, _
] : flattened
) out
.emplace_back(slot
);
7228 std::vector
<Slot
> slotsPermute(maxSlot
+ 1, 0);
7229 for (size_t i
= 0; i
<= maxSlot
; ++i
) slotsPermute
[slots
[i
]] = i
;
7231 // re-map interfaces to permuted slots
7232 for (auto& [cls
, slot
] : index
.ifaceSlotMap
) {
7233 slot
= slotsPermute
[slot
];
7237 //////////////////////////////////////////////////////////////////////
7239 struct CheckClassInfoInvariantsJob
{
7240 static std::string
name() { return "hhbbc-check-cinfo-invariants"; }
7241 static void init(const Config
& config
) {
7242 process_init(config
.o
, config
.gd
, false);
7245 static void fini() { ClassGraph::destroy(); }
7247 static bool run(std::unique_ptr
<ClassInfo2
> cinfo
,
7248 std::unique_ptr
<php::Class
> cls
) {
7249 SCOPE_ASSERT_DETAIL("class") { return cls
->name
->toCppString(); };
7251 always_assert(check(*cls
, false));
7253 auto const check
= [] (const ClassInfo2
* cinfo
,
7254 const php::Class
* cls
) {
7255 // ClassGraph stored in a ClassInfo should not be missing, always
7256 // have the ClassInfo stored, and have complete children
7258 always_assert(cinfo
->classGraph
);
7259 always_assert(!cinfo
->classGraph
.isMissing());
7260 always_assert(cinfo
->classGraph
.name()->tsame(cinfo
->name
));
7261 always_assert(cinfo
->classGraph
.cinfo2() == cinfo
);
7262 if (is_closure_base(cinfo
->name
)) {
7263 // The closure base class is special. We don't store it's
7264 // children information because it's too large.
7265 always_assert(!cinfo
->classGraph
.hasCompleteChildren());
7266 always_assert(cinfo
->classGraph
.isConservative());
7268 always_assert(cinfo
->classGraph
.hasCompleteChildren() ||
7269 cinfo
->classGraph
.isConservative());
7272 // This class and withoutNonRegular should be equivalent when
7273 // ignoring non-regular classes. The withoutNonRegular class
7274 // should be a fixed-point.
7275 if (auto const without
= cinfo
->classGraph
.withoutNonRegular()) {
7276 always_assert(without
.hasCompleteChildren() ||
7277 without
.isConservative());
7278 always_assert(without
.subSubtypeOf(cinfo
->classGraph
, false, false));
7279 always_assert(cinfo
->classGraph
.subSubtypeOf(without
, false, false));
7280 always_assert(without
.withoutNonRegular() == without
);
7281 always_assert(cinfo
->classGraph
.mightBeRegular() ||
7282 cinfo
->classGraph
.mightHaveRegularSubclass());
7283 always_assert(IMPLIES(cinfo
->classGraph
.mightBeRegular(),
7284 without
== cinfo
->classGraph
));
7285 } else if (!is_used_trait(*cls
)) {
7286 always_assert(!cinfo
->classGraph
.mightBeRegular());
7287 always_assert(!cinfo
->classGraph
.mightHaveRegularSubclass());
7290 // AttrNoOverride is a superset of AttrNoOverrideRegular
7292 IMPLIES(!(cls
->attrs
& AttrNoOverrideRegular
),
7293 !(cls
->attrs
& AttrNoOverride
))
7296 // Override attrs and what we know about the subclasses should be in
7298 if (cls
->attrs
& AttrNoOverride
) {
7299 always_assert(!cinfo
->classGraph
.mightHaveRegularSubclass());
7300 always_assert(!cinfo
->classGraph
.mightHaveNonRegularSubclass());
7301 } else if (cls
->attrs
& AttrNoOverrideRegular
) {
7302 always_assert(!cinfo
->classGraph
.mightHaveRegularSubclass());
7303 always_assert(cinfo
->classGraph
.mightHaveNonRegularSubclass());
7306 // Make sure the information stored on the ClassInfo matches that
7307 // which the ClassGraph reports.
7308 if (cls
->attrs
& AttrNoMock
) {
7309 always_assert(!cinfo
->isMocked
);
7310 always_assert(!cinfo
->isSubMocked
);
7312 always_assert(cinfo
->isSubMocked
);
7316 bool(cls
->attrs
& AttrNoExpandTrait
) ==
7317 cinfo
->classGraph
.usedTraits().empty()
7320 for (auto const& [name
, mte
] : cinfo
->methods
) {
7321 // Interface method tables should only contain its own methods.
7322 if (cls
->attrs
& AttrInterface
) {
7323 always_assert(mte
.meth().cls
->tsame(cinfo
->name
));
7326 // AttrNoOverride implies noOverrideRegular
7327 always_assert(IMPLIES(mte
.attrs
& AttrNoOverride
, mte
.noOverrideRegular()));
7329 if (!is_special_method_name(name
)) {
7330 // If the class isn't overridden, none of it's methods can be
7332 always_assert(IMPLIES(cls
->attrs
& AttrNoOverride
,
7333 mte
.attrs
& AttrNoOverride
));
7335 always_assert(!(mte
.attrs
& AttrNoOverride
));
7336 always_assert(!mte
.noOverrideRegular());
7339 if (cinfo
->name
->tsame(s_Closure
.get()) || is_closure_name(cinfo
->name
)) {
7340 always_assert(mte
.attrs
& AttrNoOverride
);
7343 // Don't store method families for special methods.
7344 auto const famIt
= cinfo
->methodFamilies
.find(name
);
7345 if (is_special_method_name(name
)) {
7346 always_assert(famIt
== end(cinfo
->methodFamilies
));
7349 if (famIt
== end(cinfo
->methodFamilies
)) {
7350 always_assert(is_closure_name(cinfo
->name
));
7353 auto const& entry
= famIt
->second
;
7355 // No override methods should always have a single entry.
7356 if (mte
.attrs
& AttrNoOverride
) {
7358 boost::get
<FuncFamilyEntry::BothSingle
>(&entry
.m_meths
) ||
7359 boost::get
<FuncFamilyEntry::SingleAndNone
>(&entry
.m_meths
)
7364 if (cinfo
->isRegularClass
) {
7365 // "all" should only be a func family. It can't be empty,
7366 // because we know there's at least one method in it (the one in
7367 // cinfo->methods). It can't be a single func, because one of
7368 // the methods must be the cinfo->methods method, and we know it
7369 // isn't AttrNoOverride, so there *must* be another method. So,
7370 // it must be a func family.
7372 boost::get
<FuncFamilyEntry::BothFF
>(&entry
.m_meths
) ||
7373 boost::get
<FuncFamilyEntry::FFAndSingle
>(&entry
.m_meths
)
7375 // This is a regular class, so we cannot have an incomplete
7376 // entry (can only happen with interfaces).
7377 always_assert(!entry
.m_regularIncomplete
);
7381 // If the class is marked as having not having bad initial prop
7382 // values, all of it's properties should have
7383 // AttrInitialSatisfiesTC set. Likewise, if it is, at least one
7384 // property should not have it set.
7385 if (!cinfo
->hasBadInitialPropValues
) {
7386 auto const all
= std::all_of(
7387 begin(cls
->properties
),
7388 end(cls
->properties
),
7389 [] (const php::Prop
& p
) {
7390 return p
.attrs
& AttrInitialSatisfiesTC
;
7395 auto const someBad
= std::any_of(
7396 begin(cls
->properties
),
7397 end(cls
->properties
),
7398 [] (const php::Prop
& p
) {
7399 return !(p
.attrs
& AttrInitialSatisfiesTC
);
7402 always_assert(someBad
);
7405 if (is_closure_name(cinfo
->name
)) {
7406 assertx(cinfo
->classGraph
.hasCompleteChildren());
7407 // Closures have no children.
7408 auto const subclasses
= cinfo
->classGraph
.children();
7409 always_assert(subclasses
.size() == 1);
7410 always_assert(subclasses
[0].name()->tsame(cinfo
->name
));
7411 } else if (cinfo
->classGraph
.hasCompleteChildren()) {
7412 // Otherwise the children list is non-empty, contains this
7413 // class, and contains only unique elements.
7414 auto const subclasses
= cinfo
->classGraph
.children();
7419 [&] (ClassGraph g
) { return g
.name()->tsame(cinfo
->name
); }
7420 ) != end(subclasses
)
7422 auto cpy
= subclasses
;
7423 std::sort(begin(cpy
), end(cpy
));
7424 cpy
.erase(std::unique(begin(cpy
), end(cpy
)), end(cpy
));
7425 always_assert(cpy
.size() == subclasses
.size());
7428 // The base list is non-empty, and the last element is this class.
7429 auto const bases
= cinfo
->classGraph
.bases();
7430 always_assert(!bases
.empty());
7431 always_assert(cinfo
->classGraph
== bases
.back());
7432 if (is_closure_base(cinfo
->name
)) {
7433 always_assert(bases
.size() == 1);
7434 } else if (is_closure_name(cinfo
->name
)) {
7435 always_assert(bases
.size() == 2);
7436 always_assert(bases
[0].name()->tsame(s_Closure
.get()));
7439 always_assert(IMPLIES(is_closure(*cls
), cls
->closures
.empty()));
7440 always_assert(cls
->closures
.size() == cinfo
->closures
.size());
7443 check(cinfo
.get(), cls
.get());
7444 for (size_t i
= 0, size
= cls
->closures
.size(); i
< size
; ++i
) {
7445 always_assert(cls
->closures
[i
]->name
->tsame(cinfo
->closures
[i
]->name
));
7446 always_assert(is_closure(*cls
->closures
[i
]));
7447 check(cinfo
->closures
[i
].get(), cls
->closures
[i
].get());
7454 struct CheckFuncFamilyInvariantsJob
{
7455 static std::string
name() { return "hhbbc-check-ff-invariants"; }
7456 static void init(const Config
& config
) {
7457 process_init(config
.o
, config
.gd
, false);
7459 static void fini() {}
7461 static bool run(FuncFamilyGroup group
) {
7462 for (auto const& ff
: group
.m_ffs
) {
7463 // FuncFamily should always have more than one func on it.
7465 ff
->m_regular
.size() +
7466 ff
->m_nonRegularPrivate
.size() +
7467 ff
->m_nonRegular
.size()
7471 // Every method should be sorted in its respective list. We
7472 // should never see a method for the same Class more than once.
7473 TSStringSet classes
;
7474 Optional
<MethRef
> lastReg
;
7475 Optional
<MethRef
> lastPrivate
;
7476 Optional
<MethRef
> lastNonReg
;
7477 for (auto const& meth
: ff
->m_regular
) {
7478 if (lastReg
) always_assert(*lastReg
< meth
);
7480 always_assert(classes
.emplace(meth
.cls
).second
);
7482 for (auto const& meth
: ff
->m_nonRegularPrivate
) {
7483 if (lastPrivate
) always_assert(*lastPrivate
< meth
);
7485 always_assert(classes
.emplace(meth
.cls
).second
);
7487 for (auto const& meth
: ff
->m_nonRegular
) {
7488 if (lastNonReg
) always_assert(*lastNonReg
< meth
);
7490 always_assert(classes
.emplace(meth
.cls
).second
);
7493 always_assert(ff
->m_allStatic
.has_value());
7495 ff
->m_regularStatic
.has_value() ==
7496 (!ff
->m_regular
.empty() || !ff
->m_nonRegularPrivate
.empty())
7503 Job
<CheckClassInfoInvariantsJob
> s_checkCInfoInvariantsJob
;
7504 Job
<CheckFuncFamilyInvariantsJob
> s_checkFuncFamilyInvariantsJob
;
7506 void check_invariants(const IndexData
& index
) {
7509 trace_time trace
{"check-invariants", index
.sample
};
7511 constexpr size_t kCInfoBucketSize
= 3000;
7512 constexpr size_t kFFBucketSize
= 500;
7514 auto cinfoBuckets
= consistently_bucketize(
7516 std::vector
<SString
> roots
;
7517 roots
.reserve(index
.classInfoRefs
.size());
7518 for (auto const& [name
, _
] : index
.classInfoRefs
) {
7519 roots
.emplace_back(name
);
7526 SStringToOneT
<Ref
<FuncFamilyGroup
>> nameToFuncFamilyGroup
;
7528 auto ffBuckets
= consistently_bucketize(
7530 std::vector
<SString
> roots
;
7531 roots
.reserve(index
.funcFamilyRefs
.size());
7532 for (auto const& [_
, ref
] : index
.funcFamilyRefs
) {
7533 auto const name
= makeStaticString(ref
.id().toString());
7534 if (nameToFuncFamilyGroup
.emplace(name
, ref
).second
) {
7535 roots
.emplace_back(name
);
7543 using namespace folly::gen
;
7545 auto const runCInfo
= [&] (std::vector
<SString
> work
) -> coro::Task
<void> {
7546 co_await
coro::co_reschedule_on_current_executor
;
7548 if (work
.empty()) co_return
;
7550 auto inputs
= from(work
)
7551 | map([&] (SString name
) {
7552 return std::make_tuple(
7553 index
.classInfoRefs
.at(name
),
7554 index
.classRefs
.at(name
)
7557 | as
<std::vector
>();
7559 Client::ExecMetadata metadata
{
7560 .job_key
= folly::sformat("check cinfo invariants {}", work
[0])
7562 auto config
= co_await index
.configRef
->getCopy();
7563 auto outputs
= co_await index
.client
->exec(
7564 s_checkCInfoInvariantsJob
,
7569 assertx(outputs
.size() == work
.size());
7574 auto const runFF
= [&] (std::vector
<SString
> work
) -> coro::Task
<void> {
7575 co_await
coro::co_reschedule_on_current_executor
;
7577 if (work
.empty()) co_return
;
7579 auto inputs
= from(work
)
7580 | map([&] (SString name
) {
7581 return std::make_tuple(nameToFuncFamilyGroup
.at(name
));
7583 | as
<std::vector
>();
7585 Client::ExecMetadata metadata
{
7586 .job_key
= folly::sformat("check func-family invariants {}", work
[0])
7588 auto config
= co_await index
.configRef
->getCopy();
7589 auto outputs
= co_await index
.client
->exec(
7590 s_checkFuncFamilyInvariantsJob
,
7595 assertx(outputs
.size() == work
.size());
7600 std::vector
<coro::TaskWithExecutor
<void>> tasks
;
7601 for (auto& work
: cinfoBuckets
) {
7603 runCInfo(std::move(work
)).scheduleOn(index
.executor
->sticky())
7606 for (auto& work
: ffBuckets
) {
7608 runFF(std::move(work
)).scheduleOn(index
.executor
->sticky())
7611 coro::blockingWait(coro::collectAllRange(std::move(tasks
)));
7614 void check_local_invariants(const IndexData
& index
, const ClassInfo
* cinfo
) {
7615 SCOPE_ASSERT_DETAIL("class") { return cinfo
->cls
->name
->toCppString(); };
7617 always_assert(check(*cinfo
->cls
));
7619 // AttrNoOverride is a superset of AttrNoOverrideRegular
7621 IMPLIES(!(cinfo
->cls
->attrs
& AttrNoOverrideRegular
),
7622 !(cinfo
->cls
->attrs
& AttrNoOverride
))
7625 always_assert(cinfo
->classGraph
);
7626 always_assert(!cinfo
->classGraph
.isMissing());
7627 always_assert(cinfo
->classGraph
.name()->tsame(cinfo
->cls
->name
));
7628 always_assert(cinfo
->classGraph
.cinfo() == cinfo
);
7629 if (is_closure_base(cinfo
->cls
->name
)) {
7630 // The closure base class is special. We don't store it's children
7631 // information because it's too large.
7632 always_assert(!cinfo
->classGraph
.hasCompleteChildren());
7633 always_assert(cinfo
->classGraph
.isConservative());
7635 always_assert(cinfo
->classGraph
.hasCompleteChildren() ||
7636 cinfo
->classGraph
.isConservative());
7639 // This class and withoutNonRegular should be equivalent when
7640 // ignoring non-regular classes. The withoutNonRegular class should
7641 // be a fixed-point.
7642 if (auto const without
= cinfo
->classGraph
.withoutNonRegular()) {
7643 always_assert(without
.hasCompleteChildren() ||
7644 without
.isConservative());
7645 always_assert(without
.subSubtypeOf(cinfo
->classGraph
, false, false));
7646 always_assert(cinfo
->classGraph
.subSubtypeOf(without
, false, false));
7647 always_assert(without
.withoutNonRegular() == without
);
7648 always_assert(cinfo
->classGraph
.mightBeRegular() ||
7649 cinfo
->classGraph
.mightHaveRegularSubclass());
7650 always_assert(IMPLIES(cinfo
->classGraph
.mightBeRegular(),
7651 without
== cinfo
->classGraph
));
7652 } else if (!is_used_trait(*cinfo
->cls
)) {
7653 always_assert(!cinfo
->classGraph
.mightBeRegular());
7654 always_assert(!cinfo
->classGraph
.mightHaveRegularSubclass());
7657 // Override attrs and what we know about the subclasses should be in
7659 if (cinfo
->cls
->attrs
& AttrNoOverride
) {
7660 always_assert(!cinfo
->classGraph
.mightHaveRegularSubclass());
7661 always_assert(!cinfo
->classGraph
.mightHaveNonRegularSubclass());
7662 } else if (cinfo
->cls
->attrs
& AttrNoOverrideRegular
) {
7663 always_assert(!cinfo
->classGraph
.mightHaveRegularSubclass());
7664 always_assert(cinfo
->classGraph
.mightHaveNonRegularSubclass());
7667 if (cinfo
->cls
->attrs
& AttrNoMock
) {
7668 always_assert(!cinfo
->isMocked
);
7669 always_assert(!cinfo
->isSubMocked
);
7672 // An AttrNoExpand class shouldn't have any used traits.
7674 bool(cinfo
->cls
->attrs
& AttrNoExpandTrait
) ==
7675 cinfo
->usedTraits
.empty()
7678 for (size_t idx
= 0; idx
< cinfo
->cls
->methods
.size(); ++idx
) {
7679 // Each method in a class has an entry in its ClassInfo method
7681 auto const& m
= cinfo
->cls
->methods
[idx
];
7682 auto const it
= cinfo
->methods
.find(m
->name
);
7683 always_assert(it
!= cinfo
->methods
.end());
7684 always_assert(it
->second
.meth().cls
->tsame(cinfo
->cls
->name
));
7685 always_assert(it
->second
.meth().idx
== idx
);
7687 // Every method (except for constructors and special methods
7688 // should be in the global name-only tables.
7689 auto const nameIt
= index
.methodFamilies
.find(m
->name
);
7690 if (!has_name_only_func_family(m
->name
)) {
7691 always_assert(nameIt
== end(index
.methodFamilies
));
7694 always_assert(nameIt
!= end(index
.methodFamilies
));
7696 auto const& entry
= nameIt
->second
;
7697 // The global name-only tables are never complete.
7698 always_assert(entry
.m_all
.isIncomplete());
7699 always_assert(entry
.m_regular
.isEmpty() || entry
.m_regular
.isIncomplete());
7701 // "all" should always be non-empty and contain this method.
7702 always_assert(!entry
.m_all
.isEmpty());
7703 if (auto const ff
= entry
.m_all
.funcFamily()) {
7704 always_assert(ff
->possibleFuncs().size() > 1);
7705 // The FuncFamily shouldn't have a section for regular results
7706 // if "regular" isn't using it.
7707 if (entry
.m_regular
.func() || entry
.m_regular
.isEmpty()) {
7708 always_assert(!ff
->m_regular
);
7710 // "all" and "regular" always share the same func family.
7711 always_assert(entry
.m_regular
.funcFamily() == ff
);
7714 auto const func
= entry
.m_all
.func();
7715 always_assert(func
);
7716 always_assert(func
== m
.get());
7717 // "regular" is always a subset of "all", so it can either be a
7718 // single func (the same as "all"), or empty.
7719 always_assert(entry
.m_regular
.func() || entry
.m_regular
.isEmpty());
7720 if (auto const func2
= entry
.m_regular
.func()) {
7721 always_assert(func
== func2
);
7725 // If this is a regular class, "regular" should be non-empty and
7726 // contain this method.
7727 if (auto const ff
= entry
.m_regular
.funcFamily()) {
7728 always_assert(ff
->possibleFuncs().size() > 1);
7729 } else if (auto const func
= entry
.m_regular
.func()) {
7730 if (is_regular_class(*cinfo
->cls
)) {
7731 always_assert(func
== m
.get());
7734 always_assert(!is_regular_class(*cinfo
->cls
));
7738 // Interface ClassInfo method table should only contain methods from
7739 // the interface itself.
7740 if (cinfo
->cls
->attrs
& AttrInterface
) {
7741 always_assert(cinfo
->cls
->methods
.size() == cinfo
->methods
.size());
7744 // If a class isn't overridden, it shouldn't have any func families
7745 // (because the method table is sufficient).
7746 if (cinfo
->cls
->attrs
& AttrNoOverride
) {
7747 always_assert(cinfo
->methodFamilies
.empty());
7748 always_assert(cinfo
->methodFamiliesAux
.empty());
7751 // The auxiliary method families map is only used by non-regular
7753 if (is_regular_class(*cinfo
->cls
)) {
7754 always_assert(cinfo
->methodFamiliesAux
.empty());
7757 for (auto const& [name
, mte
] : cinfo
->methods
) {
7758 // Interface method tables should only contain its own methods.
7759 if (cinfo
->cls
->attrs
& AttrInterface
) {
7760 always_assert(mte
.meth().cls
->tsame(cinfo
->cls
->name
));
7762 // Non-interface method tables should not contain any methods
7763 // defined by an interface.
7764 auto const func
= func_from_meth_ref(index
, mte
.meth());
7765 always_assert(!(func
->cls
->attrs
& AttrInterface
));
7768 // AttrNoOverride implies noOverrideRegular
7769 always_assert(IMPLIES(mte
.attrs
& AttrNoOverride
, mte
.noOverrideRegular()));
7771 if (!is_special_method_name(name
)) {
7772 // If the class isn't overridden, none of it's methods can be
7774 always_assert(IMPLIES(cinfo
->cls
->attrs
& AttrNoOverride
,
7775 mte
.attrs
& AttrNoOverride
));
7777 always_assert(!(mte
.attrs
& AttrNoOverride
));
7778 always_assert(!mte
.noOverrideRegular());
7781 if (is_closure_base(*cinfo
->cls
) || is_closure(*cinfo
->cls
)) {
7782 always_assert(mte
.attrs
& AttrNoOverride
);
7785 auto const famIt
= cinfo
->methodFamilies
.find(name
);
7786 // Don't store method families for special methods, or if there's
7788 if (is_special_method_name(name
) || (mte
.attrs
& AttrNoOverride
)) {
7789 always_assert(famIt
== end(cinfo
->methodFamilies
));
7790 always_assert(!cinfo
->methodFamiliesAux
.count(name
));
7793 always_assert(famIt
!= end(cinfo
->methodFamilies
));
7795 auto const& entry
= famIt
->second
;
7797 if (is_regular_class(*cinfo
->cls
)) {
7798 // "all" should only be a func family. It can't be empty,
7799 // because we know there's at least one method in it (the one in
7800 // cinfo->methods). It can't be a single func, because one of
7801 // the methods must be the cinfo->methods method, and we know it
7802 // isn't AttrNoOverride, so there *must* be another method. So,
7803 // it must be a func family.
7804 always_assert(entry
.funcFamily());
7805 // This is a regular class, so we cannot have an incomplete
7806 // entry (can only happen with interfaces).
7807 always_assert(entry
.isComplete());
7809 // This class isn't AttrNoOverride, and since the method is on
7810 // this class, it should at least contain that.
7811 always_assert(!entry
.isEmpty());
7812 // Only interfaces can have incomplete entries.
7814 IMPLIES(entry
.isIncomplete(), cinfo
->cls
->attrs
& AttrInterface
)
7816 // If we got a single func, it should be the func on this
7817 // class. Since this isn't AttrNoOverride, it implies the entry
7818 // should be incomplete.
7819 always_assert(IMPLIES(entry
.func(), entry
.isIncomplete()));
7821 IMPLIES(entry
.func(),
7822 entry
.func() == func_from_meth_ref(index
, mte
.meth()))
7825 // The "aux" entry is optional. If it isn't present, it's the
7826 // same as the normal table.
7827 auto const auxIt
= cinfo
->methodFamiliesAux
.find(name
);
7828 if (auxIt
!= end(cinfo
->methodFamiliesAux
)) {
7829 auto const& aux
= auxIt
->second
;
7831 // We shouldn't store in the aux table if the entry is the
7832 // same or if there's no override.
7833 always_assert(!mte
.noOverrideRegular());
7835 aux
.isIncomplete() ||
7836 aux
.func() != entry
.func() ||
7837 aux
.funcFamily() != entry
.funcFamily()
7840 // Normally the aux should be non-empty and complete. However
7841 // if this class is an interface, they could be.
7843 IMPLIES(aux
.isEmpty(), cinfo
->cls
->attrs
& AttrInterface
)
7846 IMPLIES(aux
.isIncomplete(), cinfo
->cls
->attrs
& AttrInterface
)
7849 // Since we know this was overridden (it wouldn't be in the
7850 // aux table otherwise), it must either be incomplete, or if
7851 // it has a single func, it cannot be the same func as this
7854 aux
.isIncomplete() ||
7855 ((mte
.attrs
& AttrPrivate
) && mte
.topLevel()) ||
7856 aux
.func() != func_from_meth_ref(index
, mte
.meth())
7859 // Aux entry is a subset of the normal entry. If they both
7860 // have a func family or func, they must be the same. If the
7861 // normal entry has a func family, but aux doesn't, that func
7862 // family shouldn't have extra space allocated.
7863 always_assert(IMPLIES(entry
.func(), !aux
.funcFamily()));
7864 always_assert(IMPLIES(entry
.funcFamily() && aux
.funcFamily(),
7865 entry
.funcFamily() == aux
.funcFamily()));
7866 always_assert(IMPLIES(entry
.func() && aux
.func(),
7867 entry
.func() == aux
.func()));
7868 always_assert(IMPLIES(entry
.funcFamily() && !aux
.funcFamily(),
7869 !entry
.funcFamily()->m_regular
));
7874 // "Aux" entries should only exist for methods on this class, and
7875 // with a corresponding methodFamilies entry.
7876 for (auto const& [name
, _
] : cinfo
->methodFamiliesAux
) {
7877 always_assert(cinfo
->methods
.count(name
));
7878 always_assert(cinfo
->methodFamilies
.count(name
));
7881 // We should only have func families for methods declared on this
7882 // class (except for interfaces and abstract classes).
7883 for (auto const& [name
, entry
] : cinfo
->methodFamilies
) {
7884 if (cinfo
->methods
.count(name
)) continue;
7885 // Interfaces and abstract classes can have func families for
7886 // methods not defined on this class.
7887 always_assert(cinfo
->cls
->attrs
& (AttrInterface
|AttrAbstract
));
7888 // We don't expand func families for these.
7889 always_assert(name
!= s_construct
.get() && !is_special_method_name(name
));
7891 // We only expand entries for interfaces and abstract classes if
7892 // it appears in every regular subclass. Therefore it cannot be
7893 // empty and is complete.
7894 always_assert(!entry
.isEmpty());
7895 always_assert(entry
.isComplete());
7896 if (auto const ff
= entry
.funcFamily()) {
7897 always_assert(!ff
->m_regular
);
7898 } else if (auto const func
= entry
.func()) {
7899 always_assert(func
->cls
!= cinfo
->cls
);
7903 // If the class is marked as having not having bad initial prop
7904 // values, all of it's properties should have AttrInitialSatisfiesTC
7905 // set. Likewise, if it is, at least one property should not have it
7907 if (!cinfo
->hasBadInitialPropValues
) {
7908 auto const all
= std::all_of(
7909 begin(cinfo
->cls
->properties
),
7910 end(cinfo
->cls
->properties
),
7911 [] (const php::Prop
& p
) {
7912 return p
.attrs
& AttrInitialSatisfiesTC
;
7917 auto const someBad
= std::any_of(
7918 begin(cinfo
->cls
->properties
),
7919 end(cinfo
->cls
->properties
),
7920 [] (const php::Prop
& p
) {
7921 return !(p
.attrs
& AttrInitialSatisfiesTC
);
7924 always_assert(someBad
);
7927 if (is_closure_name(cinfo
->cls
->name
)) {
7928 assertx(cinfo
->classGraph
.hasCompleteChildren());
7929 // Closures have no children.
7930 auto const subclasses
= cinfo
->classGraph
.children();
7931 always_assert(subclasses
.size() == 1);
7932 always_assert(subclasses
[0].name()->tsame(cinfo
->cls
->name
));
7933 } else if (cinfo
->classGraph
.hasCompleteChildren()) {
7934 // Otherwise the children list is non-empty, contains this
7935 // class, and contains only unique elements.
7936 auto const subclasses
= cinfo
->classGraph
.children();
7941 [&] (ClassGraph g
) { return g
.name()->tsame(cinfo
->cls
->name
); }
7942 ) != end(subclasses
)
7944 auto cpy
= subclasses
;
7945 std::sort(begin(cpy
), end(cpy
));
7946 cpy
.erase(std::unique(begin(cpy
), end(cpy
)), end(cpy
));
7947 always_assert(cpy
.size() == subclasses
.size());
7950 // The base list is non-empty, and the last element is this class.
7951 auto const bases
= cinfo
->classGraph
.bases();
7952 always_assert(!bases
.empty());
7953 always_assert(cinfo
->classGraph
== bases
.back());
7954 if (is_closure_base(cinfo
->cls
->name
)) {
7955 always_assert(bases
.size() == 1);
7956 } else if (is_closure_name(cinfo
->cls
->name
)) {
7957 always_assert(bases
.size() == 2);
7958 always_assert(bases
[0].name()->tsame(s_Closure
.get()));
7962 void check_local_invariants(const IndexData
& data
, const FuncFamily
& ff
) {
7963 // FuncFamily should always have more than one func on it.
7964 always_assert(ff
.possibleFuncs().size() > 1);
7966 SString name
{nullptr};
7967 FuncFamily::PossibleFunc last
{nullptr, false};
7968 for (auto const pf
: ff
.possibleFuncs()) {
7969 // Should only contain methods
7970 always_assert(pf
.ptr()->cls
);
7972 // Every method on the list should have the same name.
7974 name
= pf
.ptr()->name
;
7976 always_assert(name
== pf
.ptr()->name
);
7979 // Verify the list is sorted and doesn't contain any duplicates.
7980 hphp_fast_set
<const php::Func
*> seen
;
7984 if (last
.inRegular() && !pf
.inRegular()) return true;
7985 if (!last
.inRegular() && pf
.inRegular()) return false;
7986 return string_data_lt_type
{}(last
.ptr()->cls
->name
, pf
.ptr()->cls
->name
);
7990 always_assert(seen
.emplace(pf
.ptr()).second
);
7994 if (!ff
.possibleFuncs().front().inRegular() ||
7995 ff
.possibleFuncs().back().inRegular()) {
7996 // If there's no funcs on a regular class, or if all functions are
7997 // on a regular class, we don't need to keep separate information
7998 // for the regular subset (it either doesn't exist, or it's equal to
7999 // the entire list).
8000 always_assert(!ff
.m_regular
);
8004 void check_local_invariants(const IndexData
& data
) {
8007 trace_time timer
{"check-local-invariants"};
8011 [&] (const std::unique_ptr
<ClassInfo
>& cinfo
) {
8012 check_local_invariants(data
, cinfo
.get());
8016 std::vector
<const FuncFamily
*> funcFamilies
;
8017 funcFamilies
.reserve(data
.funcFamilies
.size());
8018 for (auto const& [ff
, _
] : data
.funcFamilies
) {
8019 funcFamilies
.emplace_back(ff
.get());
8023 [&] (const FuncFamily
* ff
) { check_local_invariants(data
, *ff
); }
8027 //////////////////////////////////////////////////////////////////////
8029 Type
adjust_closure_context(const IIndex
& index
, const CallContext
& ctx
) {
8030 if (ctx
.callee
->cls
&& ctx
.callee
->cls
->closureContextCls
) {
8031 auto const withClosureContext
= Context
{
8034 index
.lookup_closure_context(*ctx
.callee
->cls
)
8036 if (auto const s
= selfCls(index
, withClosureContext
)) {
8037 return setctx(toobj(*s
));
8044 Index::ReturnType
context_sensitive_return_type(IndexData
& data
,
8046 CallContext callCtx
,
8047 Index::ReturnType returnType
) {
8048 constexpr auto max_interp_nexting_level
= 2;
8049 static __thread
uint32_t interp_nesting_level
;
8050 auto const finfo
= func_info(data
, callCtx
.callee
);
8052 auto const adjustedCtx
= adjust_closure_context(
8053 IndexAdaptor
{ *data
.m_index
},
8056 returnType
.t
= return_with_context(std::move(returnType
.t
), adjustedCtx
);
8058 auto const checkParam
= [&] (int i
) {
8059 auto const& constraint
= finfo
->func
->params
[i
].typeConstraint
;
8060 if (constraint
.hasConstraint() &&
8061 !constraint
.isTypeVar() &&
8062 !constraint
.isTypeConstant()) {
8063 auto const ctx
= Context
{
8068 return callCtx
.args
[i
].strictlyMoreRefined(
8069 lookup_constraint(IndexAdaptor
{ *data
.m_index
}, ctx
, constraint
).upper
8072 return callCtx
.args
[i
].strictSubtypeOf(TInitCell
);
8075 // TODO(#3788877): more heuristics here would be useful.
8076 auto const tryContextSensitive
= [&] {
8077 if (finfo
->func
->noContextSensitiveAnalysis
||
8078 finfo
->func
->params
.empty() ||
8079 interp_nesting_level
+ 1 >= max_interp_nexting_level
||
8080 returnType
.t
.is(BBottom
)) {
8084 if (finfo
->retParam
!= NoLocalId
&&
8085 callCtx
.args
.size() > finfo
->retParam
&&
8086 checkParam(finfo
->retParam
)) {
8090 if (!options
.ContextSensitiveInterp
) return false;
8092 if (callCtx
.args
.size() < finfo
->func
->params
.size()) return true;
8093 for (auto i
= 0; i
< finfo
->func
->params
.size(); i
++) {
8094 if (checkParam(i
)) return true;
8099 if (!tryContextSensitive
) return returnType
;
8102 ContextRetTyMap::const_accessor acc
;
8103 if (data
.contextualReturnTypes
.find(acc
, callCtx
)) {
8105 acc
->second
.t
.is(BBottom
) ||
8106 is_scalar(acc
->second
.t
)) {
8112 if (data
.frozen
) return returnType
;
8114 auto contextType
= [&] {
8115 ++interp_nesting_level
;
8116 SCOPE_EXIT
{ --interp_nesting_level
; };
8118 auto const func
= finfo
->func
;
8119 auto const wf
= php::WideFunc::cns(func
);
8120 auto const calleeCtx
= AnalysisContext
{
8126 auto fa
= analyze_func_inline(
8127 IndexAdaptor
{ *data
.m_index
},
8132 return Index::ReturnType
{
8133 return_with_context(std::move(fa
.inferredReturn
), adjustedCtx
),
8138 if (!interp_nesting_level
) {
8140 "Context sensitive type: {}\n"
8141 "Context insensitive type: {}\n",
8142 show(contextType
.t
), show(returnType
.t
));
8145 if (!returnType
.t
.subtypeOf(BUnc
)) {
8146 // If the context insensitive return type could be non-static, staticness
8147 // could be a result of temporary context sensitive bytecode optimizations.
8148 contextType
.t
= loosen_staticness(std::move(contextType
.t
));
8151 auto ret
= Index::ReturnType
{
8152 intersection_of(std::move(returnType
.t
), std::move(contextType
.t
)),
8153 returnType
.effectFree
&& contextType
.effectFree
8156 if (!interp_nesting_level
) {
8157 FTRACE(3, "Context sensitive result: {}\n", show(ret
.t
));
8160 ContextRetTyMap::accessor acc
;
8161 if (data
.contextualReturnTypes
.insert(acc
, callCtx
) ||
8162 ret
.t
.strictSubtypeOf(acc
->second
.t
) ||
8163 (ret
.effectFree
&& !acc
->second
.effectFree
)) {
8170 //////////////////////////////////////////////////////////////////////
8172 Index::ReturnType
context_sensitive_return_type(AnalysisIndex::IndexData
& data
,
8173 const CallContext
& callCtx
,
8174 Index::ReturnType returnType
) {
8175 constexpr size_t maxNestingLevel
= 2;
8177 using R
= Index::ReturnType
;
8179 auto const& func
= *callCtx
.callee
;
8181 if (data
.mode
== AnalysisIndex::Mode::Constants
) {
8184 "Skipping inline interp of {} because analyzing constants\n",
8190 auto const& finfo
= func_info(data
, func
);
8191 auto const& caller
= *context_for_deps(data
).func
;
8193 auto const adjustedCtx
= adjust_closure_context(
8194 AnalysisIndexAdaptor
{ data
.index
},
8197 returnType
.t
= return_with_context(std::move(returnType
.t
), adjustedCtx
);
8199 auto const checkParam
= [&] (size_t i
) {
8200 auto const& constraint
= func
.params
[i
].typeConstraint
;
8201 if (constraint
.hasConstraint() &&
8202 !constraint
.isTypeVar() &&
8203 !constraint
.isTypeConstant()) {
8204 return callCtx
.args
[i
].strictlyMoreRefined(
8206 AnalysisIndexAdaptor
{ data
.index
},
8207 Context
{ func
.unit
, &func
, func
.cls
},
8212 return callCtx
.args
[i
].strictSubtypeOf(TInitCell
);
8215 // TODO(#3788877): more heuristics here would be useful.
8216 auto const tryContextSensitive
= [&] {
8217 if (func
.noContextSensitiveAnalysis
||
8218 func
.params
.empty() ||
8219 data
.contextualInterpNestingLevel
+ 1 >= maxNestingLevel
||
8220 returnType
.t
.is(BBottom
)) {
8224 if (data
.deps
->add(func
, AnalysisDeps::RetParam
)) {
8225 if (finfo
.retParam
!= NoLocalId
&&
8226 callCtx
.args
.size() > finfo
.retParam
&&
8227 checkParam(finfo
.retParam
)) {
8232 if (!options
.ContextSensitiveInterp
) return false;
8234 auto const numParams
= func
.params
.size();
8235 if (callCtx
.args
.size() < numParams
) return true;
8236 for (size_t i
= 0; i
< numParams
; ++i
) {
8237 if (checkParam(i
)) return true;
8242 if (!tryContextSensitive
) {
8243 ITRACE_MOD(Trace::hhbbc
, 4, "not trying context sensitive\n");
8247 if (!data
.deps
->add(func
, AnalysisDeps::Bytecode
)) {
8250 "Skipping inline interp of {} because inspecting "
8251 "bytecode is not allowed\n",
8254 return R
{ TInitCell
, false };
8256 if (!func
.rawBlocks
) {
8259 "Skipping inline interp of {} because bytecode not present\n",
8262 return R
{ TInitCell
, false };
8265 auto const contextType
= [&] {
8266 ++data
.contextualInterpNestingLevel
;
8267 SCOPE_EXIT
{ --data
.contextualInterpNestingLevel
; };
8269 auto const wf
= php::WideFunc::cns(&func
);
8270 auto fa
= analyze_func_inline(
8271 AnalysisIndexAdaptor
{ data
.index
},
8276 &context_for_deps(data
)
8282 return_with_context(std::move(fa
.inferredReturn
), std::move(adjustedCtx
)),
8289 "Context sensitive type: {}, context insensitive type: {}\n",
8290 show(contextType
.t
), show(returnType
.t
)
8293 auto const error_context
= [&] {
8294 using namespace folly::gen
;
8295 return folly::sformat(
8296 "{} calling {} (context: {}, args: {})",
8297 func_fullname(caller
),
8298 func_fullname(func
),
8299 show(callCtx
.context
),
8301 | map([] (const Type
& t
) { return show(t
); })
8302 | unsplit
<std::string
>(",")
8307 contextType
.t
.subtypeOf(returnType
.t
),
8308 "Context sensitive return type for {} is {} ",
8309 "which is not at least as refined as context insensitive "
8312 show(contextType
.t
),
8316 contextType
.effectFree
|| !returnType
.effectFree
,
8317 "Context sensitive effect-free for {} is {} ",
8318 "which is not at least as refined as context insensitive "
8321 contextType
.effectFree
,
8322 returnType
.effectFree
8328 //////////////////////////////////////////////////////////////////////
8330 template<typename F
> auto
8331 visit_parent_cinfo(const ClassInfo
* cinfo
, F fun
) -> decltype(fun(cinfo
)) {
8332 for (auto ci
= cinfo
; ci
!= nullptr; ci
= ci
->parent
) {
8333 if (auto const ret
= fun(ci
)) return ret
;
8334 for (auto const trait
: ci
->usedTraits
) {
8335 if (auto const ret
= visit_parent_cinfo(trait
, fun
)) {
8343 //////////////////////////////////////////////////////////////////////
8345 // The type of a public static property, considering only it's initial
8347 Type
initial_type_for_public_sprop(const Index
& index
,
8348 const php::Class
& cls
,
8349 const php::Prop
& prop
) {
8351 * If the initializer type is TUninit, it means an 86sinit provides
8352 * the actual initialization type or it is AttrLateInit. So we don't
8353 * want to include the Uninit (which isn't really a user-visible
8354 * type for the property) or by the time we union things in we'll
8355 * have inferred nothing much.
8357 auto const ty
= from_cell(prop
.val
);
8358 if (ty
.subtypeOf(BUninit
)) return TBottom
;
8359 if (prop
.attrs
& AttrSystemInitialValue
) return ty
;
8360 return adjust_type_for_prop(
8361 IndexAdaptor
{ index
},
8363 &prop
.typeConstraint
,
8368 Type
lookup_public_prop_impl(
8369 const IndexData
& data
,
8370 const ClassInfo
* cinfo
,
8373 // Find a property declared in this class (or a parent) with the same name.
8374 const php::Class
* knownCls
= nullptr;
8375 auto const prop
= visit_parent_cinfo(
8377 [&] (const ClassInfo
* ci
) -> const php::Prop
* {
8378 for (auto const& prop
: ci
->cls
->properties
) {
8379 if (prop
.name
== propName
) {
8388 if (!prop
) return TCell
;
8389 // Make sure its non-static and public. Otherwise its another function's
8391 if (prop
->attrs
& (AttrStatic
| AttrPrivate
)) return TCell
;
8393 // Get a type corresponding to its declared type-hint (if any).
8394 auto ty
= adjust_type_for_prop(
8395 IndexAdaptor
{ *data
.m_index
}, *knownCls
, &prop
->typeConstraint
, TCell
8397 // We might have to include the initial value which might be outside of the
8399 auto initialTy
= loosen_all(from_cell(prop
->val
));
8400 if (!initialTy
.subtypeOf(TUninit
) && (prop
->attrs
& AttrSystemInitialValue
)) {
8406 // Test if the given property (declared in `cls') is accessible in the
8407 // given context (null if we're not in a class).
8408 bool static_is_accessible(const ClassInfo
* clsCtx
,
8409 const ClassInfo
* cls
,
8410 const php::Prop
& prop
) {
8411 assertx(prop
.attrs
& AttrStatic
);
8412 switch (prop
.attrs
& (AttrPublic
|AttrProtected
|AttrPrivate
)) {
8414 // Public is accessible everywhere
8417 // Protected is accessible from both derived classes and parent
8420 (clsCtx
->classGraph
.exactSubtypeOf(cls
->classGraph
, true, true) ||
8421 cls
->classGraph
.exactSubtypeOf(clsCtx
->classGraph
, true, true));
8423 // Private is only accessible from within the declared class
8424 return clsCtx
== cls
;
8426 always_assert(false);
8429 // Return true if the given class can possibly throw when its
8430 // initialized. Initialization can happen when an object of that class
8431 // is instantiated, or (more importantly) when static properties are
8433 bool class_init_might_raise(IndexData
& data
,
8435 const ClassInfo
* cinfo
) {
8436 // Check this class and all of its parents for possible inequivalent
8437 // redeclarations or bad initial values.
8439 // Be conservative for now if we have unflattened traits.
8440 if (!cinfo
->traitProps
.empty()) return true;
8441 if (cinfo
->hasBadRedeclareProp
) return true;
8442 if (cinfo
->hasBadInitialPropValues
) {
8443 add_dependency(data
, cinfo
->cls
, ctx
, Dep::PropBadInitialValues
);
8446 cinfo
= cinfo
->parent
;
8452 * Calculate the effects of applying the given type against the
8453 * type-constraints for the given prop. This includes the subtype
8454 * which will succeed (if any), and if the type-constraint check might
8457 PropMergeResult
prop_tc_effects(const Index
& index
,
8458 const ClassInfo
* ci
,
8459 const php::Prop
& prop
,
8462 assertx(prop
.typeConstraint
.validForProp());
8464 using R
= PropMergeResult
;
8466 // If we're not actually checking property type-hints, everything
8468 if (Cfg::Eval::CheckPropTypeHints
<= 0) return R
{ val
, TriBool::No
};
8470 auto const ctx
= Context
{ nullptr, nullptr, ci
->cls
};
8472 auto const check
= [&] (const TypeConstraint
& tc
, const Type
& t
) {
8473 // If the type as is satisfies the constraint, we won't throw and
8474 // the type is unchanged.
8476 lookup_constraint(IndexAdaptor
{ index
}, ctx
, tc
, t
).lower
)
8478 return R
{ t
, TriBool:: No
};
8480 // Otherwise adjust the type. If we get a Bottom we'll definitely
8481 // throw. We already know the type doesn't completely satisfy the
8482 // constraint, so we'll at least maybe throw.
8484 adjust_type_for_prop(IndexAdaptor
{ index
}, *ctx
.cls
, &tc
, t
);
8485 auto const throws
= yesOrMaybe(adjusted
.subtypeOf(BBottom
));
8486 return R
{ std::move(adjusted
), throws
};
8489 // First check the main type-constraint.
8490 auto result
= check(prop
.typeConstraint
, val
);
8491 // If we're not checking generics upper-bounds, or if we already
8492 // know we'll fail, we're done.
8493 if (!checkUB
|| result
.throws
== TriBool::Yes
) {
8497 // Otherwise check every generic upper-bound. We'll feed the
8498 // narrowed type into each successive round. If we reach the point
8499 // where we'll know we'll definitely fail, just stop.
8500 for (auto const& ub
: prop
.ubs
.m_constraints
) {
8501 auto r
= check(ub
, result
.adjusted
);
8502 result
.throws
&= r
.throws
;
8503 result
.adjusted
= std::move(r
.adjusted
);
8504 if (result
.throws
== TriBool::Yes
) break;
8511 * Lookup data for the static property named `propName', starting from
8512 * the specified class `start'. If `propName' is nullptr, then any
8513 * accessible static property in the class hierarchy is considered. If
8514 * `startOnly' is specified, if the property isn't found in `start',
8515 * it is treated as a lookup failure. Otherwise the lookup continues
8516 * in all parent classes of `start', until a property is found, or
8517 * until all parent classes have been exhausted (`startOnly' is used
8518 * to avoid redundant class hierarchy walks). `clsCtx' is the current
8519 * context, converted to a ClassInfo* (or nullptr if not in a class).
8521 PropLookupResult
lookup_static_impl(IndexData
& data
,
8523 const ClassInfo
* clsCtx
,
8524 const PropertiesInfo
& privateProps
,
8525 const ClassInfo
* start
,
8529 6, "lookup_static_impl: {} {} {}\n",
8530 clsCtx
? clsCtx
->cls
->name
->toCppString() : std::string
{"-"},
8532 propName
? propName
->toCppString() : std::string
{"*"}
8536 auto const type
= [&] (const php::Prop
& prop
,
8537 const ClassInfo
* ci
) {
8538 switch (prop
.attrs
& (AttrPublic
|AttrProtected
|AttrPrivate
)) {
8540 case AttrProtected
: {
8541 if (ctx
.unit
) add_dependency(data
, &prop
, ctx
, Dep::PublicSProp
);
8542 if (!data
.seenPublicSPropMutations
) {
8543 // If we haven't recorded any mutations yet, we need to be
8544 // conservative and consider only the type-hint and initial
8547 adjust_type_for_prop(
8548 IndexAdaptor
{ *data
.m_index
},
8550 &prop
.typeConstraint
,
8553 initial_type_for_public_sprop(*data
.m_index
, *ci
->cls
, prop
)
8556 auto const it
= ci
->publicStaticProps
.find(propName
);
8557 if (it
== end(ci
->publicStaticProps
)) {
8558 // We've recorded mutations, but have information for this
8559 // property. That means there's no mutations so only
8560 // consider the initial value.
8561 return initial_type_for_public_sprop(*data
.m_index
, *ci
->cls
, prop
);
8563 return it
->second
.inferredType
;
8566 assertx(clsCtx
== ci
);
8567 auto const elem
= privateProps
.readPrivateStatic(prop
.name
);
8568 if (!elem
) return TInitCell
;
8569 return remove_uninit(elem
->ty
);
8572 always_assert(false);
8575 auto const initMightRaise
= class_init_might_raise(data
, ctx
, start
);
8577 auto const fromProp
= [&] (const php::Prop
& prop
,
8578 const ClassInfo
* ci
) {
8579 // The property was definitely found. Compute its attributes
8580 // from the prop metadata.
8581 return PropLookupResult
{
8585 yesOrNo(prop
.attrs
& AttrIsConst
),
8586 yesOrNo(prop
.attrs
& AttrIsReadonly
),
8587 yesOrNo(prop
.attrs
& AttrLateInit
),
8588 yesOrNo(prop
.attrs
& AttrInternal
),
8593 auto const notFound
= [&] {
8594 // The property definitely wasn't found.
8595 return PropLookupResult
{
8608 // We don't statically know the prop name. Walk up the hierarchy
8609 // and union the data for any accessible static property.
8610 ITRACE(4, "no prop name, considering all accessible\n");
8611 auto result
= notFound();
8614 [&] (const ClassInfo
* ci
) {
8615 for (auto const& prop
: ci
->cls
->properties
) {
8616 if (!(prop
.attrs
& AttrStatic
) ||
8617 !static_is_accessible(clsCtx
, ci
, prop
)) {
8619 6, "skipping inaccessible {}::${}\n",
8620 ci
->cls
->name
, prop
.name
8624 auto const r
= fromProp(prop
, ci
);
8625 ITRACE(6, "including {}:${} {}\n", ci
->cls
->name
, prop
.name
, show(r
));
8628 // If we're only interested in the starting class, don't walk
8629 // up to the parents.
8636 // We statically know the prop name. Walk up the hierarchy and stop
8637 // at the first matching property and use that data.
8638 assertx(!startOnly
);
8639 auto const result
= visit_parent_cinfo(
8641 [&] (const ClassInfo
* ci
) -> Optional
<PropLookupResult
> {
8642 for (auto const& prop
: ci
->cls
->properties
) {
8643 if (prop
.name
!= propName
) continue;
8644 // We have a matching prop. If its not static or not
8645 // accessible, the access will not succeed.
8646 if (!(prop
.attrs
& AttrStatic
) ||
8647 !static_is_accessible(clsCtx
, ci
, prop
)) {
8649 6, "{}::${} found but inaccessible, stopping\n",
8650 ci
->cls
->name
, propName
8654 // Otherwise its a match
8655 auto const r
= fromProp(prop
, ci
);
8656 ITRACE(6, "found {}:${} {}\n", ci
->cls
->name
, propName
, show(r
));
8659 return std::nullopt
;
8663 // We walked up to all of the base classes and didn't find a
8664 // property with a matching name. The access will fail.
8665 ITRACE(6, "nothing found\n");
8672 * Lookup the static property named `propName', starting from the
8673 * specified class `start'. If an accessible property is found, then
8674 * merge the given type `val' into the already known type for that
8675 * property. If `propName' is nullptr, then any accessible static
8676 * property in the class hierarchy is considered. If `startOnly' is
8677 * specified, if the property isn't found in `start', then the nothing
8678 * is done. Otherwise the lookup continues in all parent classes of
8679 * `start', until a property is found, or until all parent classes
8680 * have been exhausted (`startOnly' is to avoid redundant class
8681 * hierarchy walks). `clsCtx' is the current context, converted to a
8682 * ClassInfo* (or nullptr if not in a class). If `ignoreConst' is
8683 * false, then AttrConst properties will not have their type
8684 * modified. `mergePublic' is a lambda with the logic to merge a type
8685 * for a public property (this is needed to avoid cyclic
8688 template <typename F
>
8689 PropMergeResult
merge_static_type_impl(IndexData
& data
,
8692 PropertiesInfo
& privateProps
,
8693 const ClassInfo
* clsCtx
,
8694 const ClassInfo
* start
,
8699 bool mustBeReadOnly
,
8702 6, "merge_static_type_impl: {} {} {} {}\n",
8703 clsCtx
? clsCtx
->cls
->name
->toCppString() : std::string
{"-"},
8705 propName
? propName
->toCppString() : std::string
{"*"},
8710 assertx(!val
.subtypeOf(BBottom
));
8712 // Perform the actual merge for a given property, returning the
8713 // effects of that merge.
8714 auto const merge
= [&] (const php::Prop
& prop
, const ClassInfo
* ci
) {
8715 // First calculate the effects of the type-constraint.
8716 auto const effects
= prop_tc_effects(*data
.m_index
, ci
, prop
, val
, checkUB
);
8717 // No point in merging if the type-constraint will always fail.
8718 if (effects
.throws
== TriBool::Yes
) {
8720 6, "tc would throw on {}::${} with {}, skipping\n",
8721 ci
->cls
->name
, prop
.name
, show(val
)
8725 assertx(!effects
.adjusted
.subtypeOf(BBottom
));
8728 6, "merging {} into {}::${}\n",
8729 show(effects
), ci
->cls
->name
, prop
.name
8732 switch (prop
.attrs
& (AttrPublic
|AttrProtected
|AttrPrivate
)) {
8735 mergePublic(ci
, prop
, unctx(effects
.adjusted
));
8736 // If the property is internal, accessing it may throw
8737 // TODO(T131951529): we can do better by checking modules here
8738 if ((prop
.attrs
& AttrInternal
) && effects
.throws
== TriBool::No
) {
8739 ITRACE(6, "{}::${} is internal, "
8740 "being pessimistic with regards to throwing\n",
8741 ci
->cls
->name
, prop
.name
);
8742 return PropMergeResult
{
8749 assertx(clsCtx
== ci
);
8750 privateProps
.mergeInPrivateStaticPreAdjusted(
8752 unctx(effects
.adjusted
)
8757 always_assert(false);
8760 // If we don't find a property, then the mutation will definitely
8762 auto const notFound
= [&] {
8763 return PropMergeResult
{
8770 // We don't statically know the prop name. Walk up the hierarchy
8771 // and merge the type for any accessible static property.
8772 ITRACE(6, "no prop name, considering all accessible\n");
8773 auto result
= notFound();
8776 [&] (const ClassInfo
* ci
) {
8777 for (auto const& prop
: ci
->cls
->properties
) {
8778 if (!(prop
.attrs
& AttrStatic
) ||
8779 !static_is_accessible(clsCtx
, ci
, prop
)) {
8781 6, "skipping inaccessible {}::${}\n",
8782 ci
->cls
->name
, prop
.name
8786 if (!ignoreConst
&& (prop
.attrs
& AttrIsConst
)) {
8787 ITRACE(6, "skipping const {}::${}\n", ci
->cls
->name
, prop
.name
);
8790 if (mustBeReadOnly
&& !(prop
.attrs
& AttrIsReadonly
)) {
8791 ITRACE(6, "skipping mutable property that must be readonly {}::${}\n",
8792 ci
->cls
->name
, prop
.name
);
8795 result
|= merge(prop
, ci
);
8803 // We statically know the prop name. Walk up the hierarchy and stop
8804 // at the first matching property and merge the type there.
8805 assertx(!startOnly
);
8806 auto result
= visit_parent_cinfo(
8808 [&] (const ClassInfo
* ci
) -> Optional
<PropMergeResult
> {
8809 for (auto const& prop
: ci
->cls
->properties
) {
8810 if (prop
.name
!= propName
) continue;
8811 // We found a property with the right name, but its
8812 // inaccessible from this context (or not even static). This
8813 // mutation will fail, so we don't need to modify the type.
8814 if (!(prop
.attrs
& AttrStatic
) ||
8815 !static_is_accessible(clsCtx
, ci
, prop
)) {
8817 6, "{}::${} found but inaccessible, stopping\n",
8818 ci
->cls
->name
, propName
8822 // Mutations to AttrConst properties will fail as well, unless
8823 // it we want to override that behavior.
8824 if (!ignoreConst
&& (prop
.attrs
& AttrIsConst
)) {
8826 6, "{}:${} found but const, stopping\n",
8827 ci
->cls
->name
, propName
8831 if (mustBeReadOnly
&& !(prop
.attrs
& AttrIsReadonly
)) {
8833 6, "{}:${} found but is mutable and must be readonly, stopping\n",
8834 ci
->cls
->name
, propName
8838 return merge(prop
, ci
);
8840 return std::nullopt
;
8844 ITRACE(6, "nothing found\n");
8848 // If the mutation won't throw, we still need to check if the class
8849 // initialization can throw. If we might already throw (or
8850 // definitely will throw), this doesn't matter.
8851 if (result
->throws
== TriBool::No
) {
8852 return PropMergeResult
{
8853 std::move(result
->adjusted
),
8854 maybeOrNo(class_init_might_raise(data
, ctx
, start
))
8860 //////////////////////////////////////////////////////////////////////
8863 * Split a group of buckets so that no bucket is larger (including its
8864 * dependencies) than the given max size. The given callable is used
8865 * to obtain the dependencies of bucket item.
8867 * Note: if a single item has dependencies larger than maxSize, you'll
8868 * get a bucket with just that and its dependencies (which will be
8869 * larger than maxSize). This is the only situation where a returned
8870 * bucket will be larger than maxSize.
8872 template <typename GetDeps
>
8873 std::vector
<std::vector
<SString
>>
8874 split_buckets(const std::vector
<std::vector
<SString
>>& items
,
8876 const GetDeps
& getDeps
) {
8877 // Split all of the buckets in parallel
8878 auto rebuckets
= parallel::map(
8880 [&] (const std::vector
<SString
>& bucket
) {
8881 // If there's only one thing in a bucket, there's no point in
8883 if (bucket
.size() <= 1) return singleton_vec(bucket
);
8885 // The splitting algorithm is simple. Iterate over each element
8886 // in the bucket. As long as all the dependencies are less than
8887 // the maximum size, we put it into a new bucket. If we exceed
8888 // the max size, create a new bucket.
8889 std::vector
<std::vector
<SString
>> out
;
8891 out
.back().emplace_back(bucket
[0]);
8893 auto allDeps
= getDeps(bucket
[0]);
8894 for (size_t i
= 1, size
= bucket
.size(); i
< size
; ++i
) {
8895 auto const& d
= getDeps(bucket
[i
]);
8896 allDeps
.insert(begin(d
), end(d
));
8897 auto const newSize
= allDeps
.size() + out
.back().size() + 1;
8898 if (newSize
> maxSize
) {
8902 out
.back().emplace_back(bucket
[i
]);
8908 // Flatten all of the new buckets into a single list of buckets.
8909 std::vector
<std::vector
<SString
>> flattened
;
8910 flattened
.reserve(items
.size());
8911 for (auto& r
: rebuckets
) {
8912 for (auto& b
: r
) flattened
.emplace_back(std::move(b
));
8917 //////////////////////////////////////////////////////////////////////
8920 * For efficiency reasons, we often want to process classes in as few
8921 * passes as possible. However, this is tricky because the algorithms
8922 * are usually naturally iterative. You start at the root classes
8923 * (which may be the top classes in the hierarchy, or leaf classes),
8924 * and flow data down to each of their parents or children. This
8925 * requires N passes, where N is the maximum depth of the class
8926 * hierarchy. N can get large.
8928 * Instead when we process a class, we ensure that all of it's
8929 * dependencies (all the way up to the roots) are also present in the
8930 * job. Since we're doing this in one pass, none of the dependencies
8931 * will have any calculated information, and the job will have to do
8934 * It is not, in general, possible to ensure that each dependency is
8935 * present in exactly one job (because the dependency may be shared by
8936 * lots of classes which are not bucketed together). So, any given
8937 * dependency may end up on multiple jobs and have the same
8938 * information calculated for it. This is fine, as it just results in
8941 * We perform flattening using the following approach:
8943 * - First we Bucketize the root classes (using the standard
8944 * consistent hashing algorithm) into N buckets.
8946 * - We split any buckets which are larger than the specified maximum
8947 * size. This prevents buckets from becoming pathologically large if
8948 * there's many dependencies.
8950 * - For each bucket, find all of the (transitive) dependencies of the
8951 * leaves and add them to that bucket (as dependencies). As stated
8952 * above, the same class may end up in multiple buckets as
8955 * - So far for each bucket (each bucket will map to one job), we have
8956 * a set of input classes (the roots), and all of the dependencies
8959 * - We want results for every class, not just the roots, so the
8960 * dependencies need to become inputs of the first kind in at least
8961 * one bucket. So, for each dependency, in one of the buckets
8962 * they're already present in, we "promote" it to a full input (and
8963 * will receive output for it). This is done by hashing the bucket
8964 * index and class name and picking the bucket that results in the
8965 * lowest hash. In some situations we don't want a dependency to
8966 * ever be promoted, so those will be skipped.
8969 // Single output bucket for assign_hierarchical_work. Each bucket
8970 // contains classes which will be processed and returned as output,
8971 // and a set of dependency classes which will just be used as inputs.
8972 struct HierarchicalWorkBucket
{
8973 std::vector
<SString
> classes
;
8974 std::vector
<SString
> deps
;
8975 std::vector
<SString
> uninstantiable
;
8979 * Assign work for a set of root classes (using the above
8980 * algorithm). The function is named because it's meant for situations
8981 * where we're processing classes in a "hierarchical" manner (either
8982 * from parent class to children, or from leaf class to parents).
8984 * The dependencies for each class is provided by the getDeps
8985 * callable. For the purposes of promoting a class to a full output
8986 * (see above algorithm description), each class must be assigned an
8987 * index. The (optional) index for a class is provided by the getIdx
8988 * callable. If getIdx returns std::nullopt, then that class won't be
8989 * considered for promotion. The given "numClasses" parameter is an
8990 * upper bound on the possible returned indices.
8992 template <typename GetDeps
, typename GetIdx
>
8993 std::vector
<HierarchicalWorkBucket
>
8994 build_hierarchical_work(std::vector
<std::vector
<SString
>>& buckets
,
8996 const GetDeps
& getDeps
,
8997 const GetIdx
& getIdx
) {
8998 struct DepHashState
{
9000 size_t lowestHash
{std::numeric_limits
<size_t>::max()};
9001 size_t lowestBucket
{std::numeric_limits
<size_t>::max()};
9003 std::vector
<DepHashState
> depHashState
{numClasses
};
9005 // For each bucket (which right now just contains the root classes),
9006 // find all the transitive dependencies those root classes need. A
9007 // dependency might end up in multiple buckets (because multiple
9008 // roots in different buckets depend on it). We only want to
9009 // actually perform the flattening for those dependencies in one of
9010 // the buckets. So, we need a tie-breaker. We hash the name of the
9011 // dependency along with the bucket number. The bucket that the
9012 // dependency is present in with the lowest hash is what "wins".
9013 auto const bucketDeps
= parallel::gen(
9015 [&] (size_t bucketIdx
) {
9016 assertx(bucketIdx
< buckets
.size());
9017 auto& bucket
= buckets
[bucketIdx
];
9018 const TSStringSet roots
{begin(bucket
), end(bucket
)};
9020 // Gather up all dependencies for this bucket
9022 for (auto const cls
: bucket
) {
9023 auto const d
= getDeps(cls
).first
;
9024 deps
.insert(begin(*d
), end(*d
));
9027 // Make sure dependencies and roots are disjoint.
9028 for (auto const c
: bucket
) deps
.erase(c
);
9030 // For each dependency, store the bucket with the lowest hash.
9031 for (auto const d
: deps
) {
9032 auto const idx
= getIdx(roots
, bucketIdx
, d
);
9033 if (!idx
.has_value()) continue;
9034 assertx(*idx
< depHashState
.size());
9035 auto& s
= depHashState
[*idx
];
9036 auto const hash
= hash_int64_pair(
9040 std::lock_guard
<std::mutex
> _
{s
.lock
};
9041 if (hash
< s
.lowestHash
) {
9042 s
.lowestHash
= hash
;
9043 s
.lowestBucket
= bucketIdx
;
9044 } else if (hash
== s
.lowestHash
) {
9045 s
.lowestBucket
= std::min(s
.lowestBucket
, bucketIdx
);
9053 // Now for each bucket, "promote" dependencies into a full input
9054 // class. The dependency is promoted in the bucket with the lowest
9055 // hash, which we've already calculated.
9056 assertx(buckets
.size() == bucketDeps
.size());
9057 return parallel::gen(
9059 [&] (size_t bucketIdx
) {
9060 auto& bucket
= buckets
[bucketIdx
];
9061 auto const& deps
= bucketDeps
[bucketIdx
];
9062 const TSStringSet roots
{begin(bucket
), end(bucket
)};
9064 std::vector
<SString
> depOut
;
9065 depOut
.reserve(deps
.size());
9067 for (auto const d
: deps
) {
9068 // Calculate the hash for the dependency for this bucket. If
9069 // the hash equals the already calculated lowest hash, promote
9071 auto const idx
= getIdx(roots
, bucketIdx
, d
);
9072 if (!idx
.has_value()) {
9073 depOut
.emplace_back(d
);
9076 assertx(*idx
< depHashState
.size());
9077 auto const& s
= depHashState
[*idx
];
9078 auto const hash
= hash_int64_pair(
9082 if (hash
== s
.lowestHash
&& bucketIdx
== s
.lowestBucket
) {
9083 bucket
.emplace_back(d
);
9084 } else if (getDeps(d
).second
) {
9085 // Otherwise keep it as a dependency, but only if it's
9086 // actually instantiable.
9087 depOut
.emplace_back(d
);
9091 // Split off any uninstantiable classes in the bucket.
9092 auto const bucketEnd
= std::partition(
9095 [&] (SString cls
) { return getDeps(cls
).second
; }
9097 std::vector
<SString
> uninstantiable
{bucketEnd
, end(bucket
)};
9098 bucket
.erase(bucketEnd
, end(bucket
));
9100 // Keep deterministic ordering. Make sure there's no duplicates.
9101 std::sort(bucket
.begin(), bucket
.end(), string_data_lt_type
{});
9102 std::sort(depOut
.begin(), depOut
.end(), string_data_lt_type
{});
9103 std::sort(uninstantiable
.begin(), uninstantiable
.end(),
9104 string_data_lt_type
{});
9105 assertx(std::adjacent_find(bucket
.begin(), bucket
.end()) == bucket
.end());
9106 assertx(std::adjacent_find(depOut
.begin(), depOut
.end()) == depOut
.end());
9108 std::adjacent_find(uninstantiable
.begin(), uninstantiable
.end()) ==
9109 uninstantiable
.end()
9111 return HierarchicalWorkBucket
{
9114 std::move(uninstantiable
)
9120 template <typename GetDeps
, typename GetIdx
>
9121 std::vector
<HierarchicalWorkBucket
>
9122 assign_hierarchical_work(std::vector
<SString
> roots
,
9126 const GetDeps
& getDeps
,
9127 const GetIdx
& getIdx
) {
9128 // First turn roots into buckets, and split if any exceed the
9130 auto buckets
= split_buckets(
9131 consistently_bucketize(roots
, bucketSize
),
9133 [&] (SString cls
) -> const TSStringSet
& {
9134 auto const [d
, _
] = getDeps(cls
);
9138 return build_hierarchical_work(buckets
, numClasses
, getDeps
, getIdx
);
9141 //////////////////////////////////////////////////////////////////////
9142 // Class flattening:
9145 s___Sealed("__Sealed"),
9146 s___EnableMethodTraitDiamond("__EnableMethodTraitDiamond"),
9147 s___ModuleLevelTrait("__ModuleLevelTrait");
9150 * Extern-worker job to build ClassInfo2s (which involves flattening
9151 * data across the hierarchy) and flattening traits.
9154 static std::string
name() { return "hhbbc-flatten"; }
9155 static void init(const Config
& config
) {
9156 process_init(config
.o
, config
.gd
, false);
9159 static void fini() { ClassGraph::destroy(); }
9162 * Metadata representing results of flattening. This is information
9163 * that the local coordinator (as opposed to later remote jobs) will
9167 // Classes which have been determined to be uninstantiable
9168 // (therefore have no result output data).
9169 TSStringSet uninstantiable
;
9170 // New closures produced from trait flattening. Such new closures
9171 // will require "fixups" in the php::Program data.
9176 template <typename SerDe
> void serde(SerDe
& sd
) {
9177 sd(unit
)(name
)(context
);
9180 std::vector
<NewClosure
> newClosures
;
9181 // Report parents of each class. A class is a parent of another if
9182 // it would appear on a subclass list. The parents of a closure
9183 // are not reported because that's implicit.
9185 std::vector
<SString
> names
;
9186 template <typename SerDe
> void serde(SerDe
& sd
) { sd(names
); }
9188 std::vector
<Parents
> parents
;
9189 // Classes which are interfaces.
9190 TSStringSet interfaces
;
9191 // Classes which have 86init functions. A class can gain a 86init
9192 // from flattening even if it didn't have it before.
9193 TSStringSet with86init
;
9194 // The types used by the type-constraints of input classes and
9196 std::vector
<TSStringSet
> classTypeUses
;
9197 std::vector
<TSStringSet
> funcTypeUses
;
9198 std::vector
<InterfaceConflicts
> interfaceConflicts
;
9199 template <typename SerDe
> void serde(SerDe
& sd
) {
9200 ScopedStringDataIndexer _
;
9201 sd(uninstantiable
, string_data_lt_type
{})
9204 (interfaces
, string_data_lt_type
{})
9205 (with86init
, string_data_lt_type
{})
9206 (classTypeUses
, string_data_lt_type
{})
9207 (funcTypeUses
, string_data_lt_type
{})
9208 (interfaceConflicts
)
9214 * Job returns a list of (potentially modified) php::Class, a list
9215 * of new ClassInfo2, a list of (potentially modified) php::Func,
9216 * and metadata for the entire job. The order of the lists reflects
9217 * the order of the input classes and functions (skipping over
9218 * classes marked as uninstantiable in the metadata).
9220 using Output
= Multi
<
9221 Variadic
<std::unique_ptr
<php::Class
>>,
9222 Variadic
<std::unique_ptr
<php::ClassBytecode
>>,
9223 Variadic
<std::unique_ptr
<ClassInfo2
>>,
9224 Variadic
<std::unique_ptr
<php::Func
>>,
9225 Variadic
<std::unique_ptr
<FuncInfo2
>>,
9226 Variadic
<std::unique_ptr
<MethodsWithoutCInfo
>>,
9231 * Job takes a list of classes which are to be flattened. In
9232 * addition to this, it also takes a list of classes which are
9233 * dependencies of the classes to be flattened. (A class might be
9234 * one of the inputs *and* a dependency, in which case it should
9235 * just be on the input list). It is expected that *all*
9236 * dependencies are provided. All instantiable classes will have
9237 * their type-constraints resolved to their ultimate type, or left
9238 * as unresolved if it refers to a missing/invalid type. The
9239 * provided functions only have their type-constraints updated. The
9240 * provided type-mappings and list of missing types is used for
9241 * type-constraint resolution (if a type isn't in a type mapping and
9242 * isn't a missing type, it is assumed to be a object type).
9244 * Bytecode needs to be provided for every provided class. The
9245 * bytecode must be provided in the same order as the bytecode's
9248 static Output
run(Variadic
<std::unique_ptr
<php::Class
>> classes
,
9249 Variadic
<std::unique_ptr
<php::Class
>> deps
,
9250 Variadic
<std::unique_ptr
<php::ClassBytecode
>> classBytecode
,
9251 Variadic
<std::unique_ptr
<php::Func
>> funcs
,
9252 Variadic
<std::unique_ptr
<php::Class
>> uninstantiable
,
9253 std::vector
<TypeMapping
> typeMappings
,
9254 std::vector
<SString
> missingTypes
) {
9257 for (auto& tc
: typeMappings
) {
9258 auto const name
= tc
.name
;
9259 always_assert(index
.m_typeMappings
.emplace(name
, std::move(tc
)).second
);
9261 for (auto const m
: missingTypes
) {
9262 always_assert(index
.m_missingTypes
.emplace(m
).second
);
9264 typeMappings
.clear();
9265 missingTypes
.clear();
9267 // Bytecode should have been provided for every class provided.
9269 classBytecode
.vals
.size() == (classes
.vals
.size() + deps
.vals
.size())
9271 // Store the provided bytecode in the matching php::Class.
9273 size
= classBytecode
.vals
.size(),
9274 classesSize
= classes
.vals
.size();
9276 auto& cls
= i
< classesSize
9278 : deps
.vals
[i
- classesSize
];
9279 auto& bytecode
= classBytecode
.vals
[i
];
9281 // We shouldn't have closures here. They're children of the
9283 assertx(!cls
->closureContextCls
);
9284 auto const numMethods
= cls
->methods
.size();
9285 auto const numClosures
= cls
->closures
.size();
9286 always_assert(bytecode
->methodBCs
.size() == numMethods
+ numClosures
);
9288 for (size_t j
= 0, bcSize
= bytecode
->methodBCs
.size(); j
< bcSize
; ++j
) {
9289 if (j
< numMethods
) {
9290 cls
->methods
[j
]->rawBlocks
= std::move(bytecode
->methodBCs
[j
].bc
);
9292 assertx(cls
->closures
[j
-numMethods
]->methods
.size() == 1);
9293 cls
->closures
[j
-numMethods
]->methods
[0]->rawBlocks
=
9294 std::move(bytecode
->methodBCs
[j
].bc
);
9298 classBytecode
.vals
.clear();
9300 // Some classes might be dependencies of another. Moreover, some
9301 // classes might share dependencies. Topologically sort all of the
9302 // classes and process them in that order. Information will flow
9303 // from parent classes to their children.
9304 auto const worklist
= prepare(
9307 TSStringToOneT
<php::Class
*> out
;
9308 out
.reserve(classes
.vals
.size() + deps
.vals
.size());
9309 for (auto const& c
: classes
.vals
) {
9310 always_assert(out
.emplace(c
->name
, c
.get()).second
);
9311 for (auto const& clo
: c
->closures
) {
9312 always_assert(out
.emplace(clo
->name
, clo
.get()).second
);
9315 for (auto const& c
: deps
.vals
) {
9316 always_assert(out
.emplace(c
->name
, c
.get()).second
);
9317 for (auto const& clo
: c
->closures
) {
9318 always_assert(out
.emplace(clo
->name
, clo
.get()).second
);
9325 for (auto const& cls
: uninstantiable
.vals
) {
9326 always_assert(index
.m_uninstantiable
.emplace(cls
->name
).second
);
9327 for (auto const& clo
: cls
->closures
) {
9328 always_assert(index
.m_uninstantiable
.emplace(clo
->name
).second
);
9332 std::vector
<const php::Class
*> newClosures
;
9334 for (auto const cls
: worklist
) {
9336 Trace::hhbbc_index
, kSystemLibBump
, is_systemlib_part(cls
->unit
)
9339 ITRACE(2, "flatten class: {}\n", cls
->name
);
9340 Trace::Indent indent
;
9343 SCOPE_EXIT
{ index
.m_ctx
= nullptr; };
9345 auto state
= std::make_unique
<State
>();
9346 // Attempt to make the ClassInfo2 for this class. If we can't,
9347 // it means the class is not instantiable.
9348 auto newInfo
= make_info(index
, *cls
, *state
);
9350 ITRACE(4, "{} is not instantiable\n", cls
->name
);
9351 always_assert(index
.m_uninstantiable
.emplace(cls
->name
).second
);
9354 auto const cinfo
= newInfo
.get();
9356 ITRACE(5, "adding state for class '{}' to local index\n", cls
->name
);
9357 assertx(cinfo
->name
->tsame(cls
->name
));
9359 // We might look up this class when flattening itself, so add it
9360 // to the local index before we start.
9361 always_assert(index
.m_classes
.emplace(cls
->name
, cls
).second
);
9363 index
.m_classInfos
.emplace(cls
->name
, std::move(newInfo
)).second
9366 auto const [stateIt
, stateSuccess
] =
9367 index
.m_states
.emplace(cls
->name
, std::move(state
));
9368 always_assert(stateSuccess
);
9370 auto closureIdx
= cls
->closures
.size();
9371 auto closures
= flatten_traits(index
, *cls
, *cinfo
, *stateIt
->second
);
9373 // Trait flattening may produce new closures, so those need to
9374 // be added to the local index as well.
9375 for (auto& i
: closures
) {
9376 assertx(closureIdx
< cls
->closures
.size());
9377 auto& c
= cls
->closures
[closureIdx
++];
9378 ITRACE(5, "adding state for closure '{}' to local index\n", c
->name
);
9379 assertx(!is_closure(*cls
));
9380 assertx(c
->name
->tsame(i
->name
));
9381 assertx(is_closure(*c
));
9382 assertx(c
->closureContextCls
);
9383 assertx(c
->closures
.empty());
9384 assertx(i
->closures
.empty());
9385 always_assert(index
.m_classes
.emplace(c
->name
, c
.get()).second
);
9386 always_assert(index
.m_classInfos
.emplace(c
->name
, std::move(i
)).second
);
9387 newClosures
.emplace_back(c
.get());
9391 begin(cls
->closures
), end(cls
->closures
),
9392 [] (const std::unique_ptr
<php::Class
>& c1
,
9393 const std::unique_ptr
<php::Class
>& c2
) {
9394 return string_data_lt_type
{}(c1
->name
, c2
->name
);
9398 // We're done with this class. All of it's parents are now
9400 cinfo
->classGraph
.finalizeParents();
9403 // Format the output data and put it in a deterministic order.
9404 Variadic
<std::unique_ptr
<php::Class
>> outClasses
;
9405 Variadic
<std::unique_ptr
<ClassInfo2
>> outInfos
;
9406 Variadic
<std::unique_ptr
<MethodsWithoutCInfo
>> outMethods
;
9408 TSStringSet outNames
;
9410 outClasses
.vals
.reserve(classes
.vals
.size());
9411 outInfos
.vals
.reserve(classes
.vals
.size());
9412 outNames
.reserve(classes
.vals
.size());
9413 outMeta
.parents
.reserve(classes
.vals
.size());
9414 outMeta
.newClosures
.reserve(newClosures
.size());
9416 auto const makeMethodsWithoutCInfo
= [&] (const php::Class
& cls
) {
9417 always_assert(outMeta
.uninstantiable
.emplace(cls
.name
).second
);
9418 // Even though the class is uninstantiable, we still need to
9419 // create FuncInfos for it's methods. These are stored
9420 // separately (there's no ClassInfo to store it in!)
9421 auto methods
= std::make_unique
<MethodsWithoutCInfo
>();
9422 methods
->cls
= cls
.name
;
9423 for (auto const& func
: cls
.methods
) {
9424 methods
->finfos
.emplace_back(make_func_info(index
, *func
));
9426 for (auto const& clo
: cls
.closures
) {
9427 assertx(clo
->methods
.size() == 1);
9428 methods
->closureInvokes
.emplace_back(
9429 make_func_info(index
, *clo
->methods
[0])
9432 outMethods
.vals
.emplace_back(std::move(methods
));
9435 // Do the processing which relies on a fully accessible
9438 TSStringToOneT
<InterfaceConflicts
> ifaceConflicts
;
9439 for (auto& cls
: classes
.vals
) {
9440 assertx(!cls
->closureContextCls
);
9441 auto const cinfoIt
= index
.m_classInfos
.find(cls
->name
);
9442 if (cinfoIt
== end(index
.m_classInfos
)) {
9444 4, "{} discovered to be not instantiable, instead "
9445 "creating MethodsWithoutCInfo for it\n",
9448 always_assert(index
.uninstantiable(cls
->name
));
9449 makeMethodsWithoutCInfo(*cls
);
9452 auto& cinfo
= cinfoIt
->second
;
9454 index
.m_ctx
= cls
.get();
9455 SCOPE_EXIT
{ index
.m_ctx
= nullptr; };
9457 outMeta
.classTypeUses
.emplace_back();
9458 update_type_constraints(index
, *cls
, &outMeta
.classTypeUses
.back());
9459 optimize_properties(index
, *cls
, *cinfo
);
9460 for (auto const& func
: cls
->methods
) {
9461 cinfo
->funcInfos
.emplace_back(make_func_info(index
, *func
));
9464 assertx(cinfo
->closures
.empty());
9465 for (auto& clo
: cls
->closures
) {
9466 auto const it
= index
.m_classInfos
.find(clo
->name
);
9467 always_assert(it
!= end(index
.m_classInfos
));
9468 auto& cloinfo
= it
->second
;
9469 update_type_constraints(index
, *clo
, &outMeta
.classTypeUses
.back());
9470 optimize_properties(index
, *clo
, *cloinfo
);
9471 assertx(clo
->methods
.size() == 1);
9472 cloinfo
->funcInfos
.emplace_back(
9473 make_func_info(index
, *clo
->methods
[0])
9477 outNames
.emplace(cls
->name
);
9479 // Record interface conflicts
9481 // Only consider normal or abstract classes
9483 (AttrInterface
| AttrTrait
| AttrEnum
| AttrEnumClass
)) {
9487 auto const interfaces
= cinfo
->classGraph
.interfaces();
9490 always_assert(IMPLIES(is_closure(*cls
), interfaces
.empty()));
9491 for (auto const& cloinfo
: cinfo
->closures
) {
9492 always_assert(cloinfo
->classGraph
.interfaces().empty());
9496 for (auto const i1
: interfaces
) {
9497 auto& conflicts
= ifaceConflicts
[i1
.name()];
9498 conflicts
.name
= i1
.name();
9500 for (auto const i2
: interfaces
) {
9501 if (i1
== i2
) continue;
9502 conflicts
.conflicts
.emplace(i2
.name());
9507 outMeta
.interfaceConflicts
.reserve(ifaceConflicts
.size());
9508 for (auto& [_
, c
] : ifaceConflicts
) {
9509 outMeta
.interfaceConflicts
.emplace_back(std::move(c
));
9512 begin(outMeta
.interfaceConflicts
),
9513 end(outMeta
.interfaceConflicts
),
9514 [] (auto const& c1
, auto const& c2
) {
9515 return string_data_lt_type
{}(c1
.name
, c2
.name
);
9519 // We don't process classes marked as uninstantiable beforehand,
9520 // except for creating method FuncInfos for them.
9521 for (auto const& cls
: uninstantiable
.vals
) {
9523 4, "{} already known to be not instantiable, creating "
9524 "MethodsWithoutCInfo for it\n",
9527 makeMethodsWithoutCInfo(*cls
);
9530 // Now move the classes out of LocalIndex and into the output. At
9531 // this point, it's not safe to access the LocalIndex unless
9532 // you're sure something hasn't been moved yet.
9533 for (auto& cls
: classes
.vals
) {
9534 auto const name
= cls
->name
;
9536 auto const cinfoIt
= index
.m_classInfos
.find(name
);
9537 if (cinfoIt
== end(index
.m_classInfos
)) {
9538 assertx(outMeta
.uninstantiable
.count(name
));
9541 auto& cinfo
= cinfoIt
->second
;
9543 // Check if this class has a 86*init function (it might have
9544 // already or might have gained one from trait flattening).
9545 auto const has86init
=
9547 begin(cls
->methods
), end(cls
->methods
),
9548 [] (auto const& m
) { return is_86init_func(*m
); }
9551 begin(cinfo
->clsConstants
), end(cinfo
->clsConstants
),
9552 [] (auto const& cns
) {
9553 return cns
.second
.kind
== ConstModifiers::Kind::Type
;
9557 assertx(!is_closure(*cls
));
9558 outMeta
.with86init
.emplace(name
);
9561 index
.m_ctx
= cls
.get();
9562 SCOPE_EXIT
{ index
.m_ctx
= nullptr; };
9564 // For building FuncFamily::StaticInfo, we need to ensure that
9565 // every method has an entry in methodFamilies. Make all of the
9566 // initial entries here (they'll be created assuming this method
9567 // is AttrNoOverride).
9568 for (auto const& [methname
, mte
] : cinfo
->methods
) {
9569 if (is_special_method_name(methname
)) continue;
9570 auto entry
= make_initial_func_family_entry(*cls
, index
.meth(mte
), mte
);
9572 cinfo
->methodFamilies
.emplace(methname
, std::move(entry
)).second
9576 if (!is_closure(*cls
)) {
9577 auto const& state
= index
.m_states
.at(name
);
9578 outMeta
.parents
.emplace_back();
9579 auto& parents
= outMeta
.parents
.back().names
;
9580 parents
.reserve(state
->m_parents
.size());
9581 for (auto const p
: state
->m_parents
) {
9582 parents
.emplace_back(p
->name
);
9586 if (cls
->attrs
& AttrInterface
) {
9587 outMeta
.interfaces
.emplace(name
);
9590 // We always know the subclass status of closures and the
9591 // closure base class.
9592 if (is_closure_base(*cls
)) {
9593 cinfo
->classGraph
.setClosureBase();
9594 } else if (is_closure(*cls
)) {
9595 cinfo
->classGraph
.setComplete();
9598 assertx(cinfo
->closures
.empty());
9599 for (auto& clo
: cls
->closures
) {
9600 auto const it
= index
.m_classInfos
.find(clo
->name
);
9601 always_assert(it
!= end(index
.m_classInfos
));
9602 auto& cloinfo
= it
->second
;
9604 // Closures are always leafs.
9605 cloinfo
->classGraph
.setComplete();
9606 assertx(!cloinfo
->classGraph
.mightHaveRegularSubclass());
9607 assertx(!cloinfo
->classGraph
.mightHaveNonRegularSubclass());
9609 for (auto const& [methname
, mte
] : cloinfo
->methods
) {
9610 if (is_special_method_name(methname
)) continue;
9612 make_initial_func_family_entry(*clo
, index
.meth(mte
), mte
);
9614 cloinfo
->methodFamilies
.emplace(methname
, std::move(entry
)).second
9618 cinfo
->closures
.emplace_back(std::move(cloinfo
));
9621 outClasses
.vals
.emplace_back(std::move(cls
));
9622 outInfos
.vals
.emplace_back(std::move(cinfo
));
9626 begin(newClosures
), end(newClosures
),
9627 [] (const php::Class
* c1
, const php::Class
* c2
) {
9628 return string_data_lt_type
{}(c1
->name
, c2
->name
);
9631 for (auto clo
: newClosures
) {
9632 assertx(clo
->closureContextCls
);
9633 if (!outNames
.count(clo
->closureContextCls
)) continue;
9634 outMeta
.newClosures
.emplace_back(
9635 OutputMeta::NewClosure
{clo
->unit
, clo
->name
, clo
->closureContextCls
}
9639 Variadic
<std::unique_ptr
<FuncInfo2
>> funcInfos
;
9640 funcInfos
.vals
.reserve(funcs
.vals
.size());
9641 for (auto& func
: funcs
.vals
) {
9642 outMeta
.funcTypeUses
.emplace_back();
9643 update_type_constraints(index
, *func
, &outMeta
.funcTypeUses
.back());
9644 funcInfos
.vals
.emplace_back(make_func_info(index
, *func
));
9647 // Provide any updated bytecode back to the caller.
9648 Variadic
<std::unique_ptr
<php::ClassBytecode
>> outBytecode
;
9649 outBytecode
.vals
.reserve(outClasses
.vals
.size());
9650 for (auto& cls
: outClasses
.vals
) {
9651 auto bytecode
= std::make_unique
<php::ClassBytecode
>();
9652 bytecode
->cls
= cls
->name
;
9653 bytecode
->methodBCs
.reserve(cls
->methods
.size());
9654 for (auto& method
: cls
->methods
) {
9655 bytecode
->methodBCs
.emplace_back(
9657 std::move(method
->rawBlocks
)
9660 for (auto& clo
: cls
->closures
) {
9661 assertx(clo
->methods
.size() == 1);
9662 auto& method
= clo
->methods
[0];
9663 bytecode
->methodBCs
.emplace_back(
9665 std::move(method
->rawBlocks
)
9668 outBytecode
.vals
.emplace_back(std::move(bytecode
));
9671 return std::make_tuple(
9672 std::move(outClasses
),
9673 std::move(outBytecode
),
9674 std::move(outInfos
),
9676 std::move(funcInfos
),
9677 std::move(outMethods
),
9684 * State which needs to be propagated from a dependency to a child
9685 * class during flattening, but not required after flattening (so
9686 * doesn't belong in ClassInfo2).
9694 // Maintain order of properties as we inherit them.
9695 CompactVector
<PropTuple
> m_props
;
9696 SStringToOneT
<size_t> m_propIndices
;
9697 CompactVector
<php::Const
> m_traitCns
;
9698 SStringSet m_cnsFromTrait
;
9699 SStringToOneT
<size_t> m_methodIndices
;
9700 CompactVector
<const php::Class
*> m_parents
;
9702 size_t& methodIdx(SString context
, SString cls
, SString name
) {
9703 auto const it
= m_methodIndices
.find(name
);
9705 it
!= m_methodIndices
.end(),
9706 "While processing '{}', "
9707 "tried to access missing method index for '{}::{}'",
9713 size_t methodIdx(SString context
, SString cls
, SString name
) const {
9714 return const_cast<State
*>(this)->methodIdx(context
, cls
, name
);
9719 * LocalIndex is similar to Index, but for this job. It maps names
9720 * to class information needed during flattening. It also verifies
9721 * we don't try to access information about a class until it's
9722 * actually available (which shouldn't happen if our dataflow is
9726 const php::Class
* m_ctx
{nullptr};
9728 TSStringToOneT
<const php::Class
*> m_classes
;
9729 TSStringToOneT
<std::unique_ptr
<ClassInfo2
>> m_classInfos
;
9730 TSStringToOneT
<std::unique_ptr
<State
>> m_states
;
9732 TSStringSet m_uninstantiable
;
9734 TSStringToOneT
<TypeMapping
> m_typeMappings
;
9735 TSStringSet m_missingTypes
;
9737 const php::Class
& cls(SString name
) const {
9738 if (m_ctx
->name
->tsame(name
)) return *m_ctx
;
9739 auto const it
= m_classes
.find(name
);
9741 it
!= m_classes
.end(),
9742 "While processing '{}', tried to access missing class '{}' from index",
9746 assertx(it
->second
);
9750 const ClassInfo2
& classInfo(SString name
) const {
9751 auto const it
= m_classInfos
.find(name
);
9753 it
!= m_classInfos
.end(),
9754 "While processing '{}', tried to access missing class-info for '{}' "
9759 assertx(it
->second
.get());
9763 const State
& state(SString name
) const {
9764 auto const it
= m_states
.find(name
);
9766 it
!= m_states
.end(),
9767 "While processing '{}', tried to access missing flatten state for '{}' "
9772 assertx(it
->second
.get());
9776 bool uninstantiable(SString name
) const {
9777 return m_uninstantiable
.count(name
);
9780 const TypeMapping
* typeMapping(SString name
) const {
9781 return folly::get_ptr(m_typeMappings
, name
);
9784 bool missingType(SString name
) const {
9785 return m_missingTypes
.count(name
);
9788 const php::Func
& meth(const MethRef
& r
) const {
9789 auto const& mcls
= cls(r
.cls
);
9790 assertx(r
.idx
< mcls
.methods
.size());
9791 return *mcls
.methods
[r
.idx
];
9793 const php::Func
& meth(const MethTabEntry
& mte
) const {
9794 return meth(mte
.meth());
9797 const php::Const
& cns(const ConstIndex
& idx
) const {
9798 auto const& c
= cls(idx
.cls
);
9799 assertx(idx
.idx
< c
.constants
.size());
9800 return c
.constants
[idx
.idx
];
9803 size_t methodIdx(SString cls
, SString name
) const {
9804 return state(cls
).methodIdx(m_ctx
->name
, cls
, name
);
9809 * Calculate the order in which the classes should be flattened,
9810 * taking into account dependencies.
9812 static std::vector
<php::Class
*> prepare(
9814 const TSStringToOneT
<php::Class
*>& classes
9816 // We might not have any classes if we're just processing funcs.
9817 if (classes
.empty()) return {};
9819 auto const get
= [&] (SString name
) -> php::Class
& {
9820 auto const it
= classes
.find(name
);
9822 it
!= classes
.end(),
9823 "Tried to access missing class '{}' while calculating flattening order",
9829 auto const forEachDep
= [&] (php::Class
& c
, auto const& f
) {
9830 if (c
.parentName
) f(get(c
.parentName
));
9831 for (auto const i
: c
.interfaceNames
) f(get(i
));
9832 for (auto const e
: c
.includedEnumNames
) f(get(e
));
9833 for (auto const t
: c
.usedTraitNames
) f(get(t
));
9834 for (auto const& clo
: c
.closures
) {
9835 f(const_cast<php::Class
&>(*clo
));
9840 * Perform a standard topological sort:
9842 * - For each class, calculate the number of classes which depend on it.
9844 * - Any class which has a use count of zero is not depended on by
9845 * anyone and goes onto the intitial worklist.
9847 * - For every class on the worklist, push it onto the output
9848 * list, and decrement the use count of all of it's
9851 * - For any class which now has a use count of zero, push it onto
9852 * the worklist and repeat above step until all classes are
9853 * pushed onto the output list.
9855 * - Reverse the list.
9857 * - This does not handle cycles, but we should not encounter any
9858 * here, as such cycles should be detected earlier and not be
9859 * scheduled in a job.
9861 hphp_fast_map
<const php::Class
*, size_t> uses
;
9862 uses
.reserve(classes
.size());
9863 for (auto const& [_
, cls
] : classes
) {
9864 forEachDep(*cls
, [&] (const php::Class
& d
) { ++uses
[&d
]; });
9867 std::vector
<php::Class
*> worklist
;
9868 for (auto const [_
, cls
] : classes
) {
9869 if (!uses
[cls
]) worklist
.emplace_back(cls
);
9871 always_assert(!worklist
.empty());
9875 [] (const php::Class
* c1
, const php::Class
* c2
) {
9876 return string_data_lt_type
{}(c1
->name
, c2
->name
);
9880 std::vector
<php::Class
*> ordered
;
9881 ordered
.reserve(classes
.size());
9883 auto const cls
= worklist
.back();
9884 assertx(!uses
[cls
]);
9885 worklist
.pop_back();
9888 [&] (php::Class
& d
) {
9889 if (!--uses
.at(&d
)) worklist
.emplace_back(&d
);
9892 ordered
.emplace_back(cls
);
9893 } while (!worklist
.empty());
9895 for (auto const& [_
, cls
] : classes
) always_assert(!uses
.at(cls
));
9896 std::reverse(ordered
.begin(), ordered
.end());
9901 * Create a FuncFamilyEntry for the give method. This
9902 * FuncFamilyEntry assumes that the method is AttrNoOverride, and
9903 * hence reflects just this method. The method isn't necessarily
9904 * actually AttrNoOverride, but if not, it will be updated in
9905 * BuildSubclassListJob (that job needs the initial entries).
9907 static FuncFamilyEntry
make_initial_func_family_entry(
9908 const php::Class
& cls
,
9909 const php::Func
& meth
,
9910 const MethTabEntry
& mte
9912 FuncFamilyEntry entry
;
9913 entry
.m_allIncomplete
= false;
9914 entry
.m_regularIncomplete
= false;
9915 entry
.m_privateAncestor
= is_regular_class(cls
) && mte
.hasPrivateAncestor();
9917 FuncFamilyEntry::MethMetadata meta
;
9919 for (size_t i
= 0; i
< meth
.params
.size(); ++i
) {
9920 meta
.m_prepKinds
.emplace_back(func_param_prep(&meth
, i
));
9922 // Any param beyond the size of m_paramPreps is implicitly
9923 // TriBool::No, so we can drop trailing entries which are
9925 while (!meta
.m_prepKinds
.empty()) {
9926 auto& back
= meta
.m_prepKinds
.back();
9927 if (back
.inOut
!= TriBool::No
|| back
.readonly
!= TriBool::No
) break;
9928 meta
.m_prepKinds
.pop_back();
9930 meta
.m_numInOut
= func_num_inout(&meth
);
9931 meta
.m_nonVariadicParams
= numNVArgs(meth
);
9932 meta
.m_coeffectRules
= meth
.coeffectRules
;
9933 meta
.m_requiredCoeffects
= meth
.requiredCoeffects
;
9934 meta
.m_isReadonlyReturn
= meth
.isReadonlyReturn
;
9935 meta
.m_isReadonlyThis
= meth
.isReadonlyThis
;
9936 meta
.m_supportsAER
= func_supports_AER(&meth
);
9937 meta
.m_isReified
= meth
.isReified
;
9938 meta
.m_caresAboutDyncalls
= (dyn_call_error_level(&meth
) > 0);
9939 meta
.m_builtin
= meth
.attrs
& AttrBuiltin
;
9941 if (is_regular_class(cls
)) {
9943 FuncFamilyEntry::BothSingle
{mte
.meth(), std::move(meta
), false};
9944 } else if (bool(mte
.attrs
& AttrPrivate
) && mte
.topLevel()) {
9946 FuncFamilyEntry::BothSingle
{mte
.meth(), std::move(meta
), true};
9949 FuncFamilyEntry::SingleAndNone
{mte
.meth(), std::move(meta
)};
9955 static std::unique_ptr
<ClassInfo2
> make_info(const LocalIndex
& index
,
9958 if (debug
&& (is_closure(cls
) || is_closure_base(cls
))) {
9959 if (is_closure(cls
)) {
9960 always_assert(cls
.parentName
->tsame(s_Closure
.get()));
9962 always_assert(!cls
.parentName
);
9964 always_assert(cls
.interfaceNames
.empty());
9965 always_assert(cls
.includedEnumNames
.empty());
9966 always_assert(cls
.usedTraitNames
.empty());
9967 always_assert(cls
.requirements
.empty());
9968 always_assert(cls
.constants
.empty());
9969 always_assert(cls
.userAttributes
.empty());
9970 always_assert(!(cls
.attrs
& (AttrTrait
| AttrInterface
| AttrAbstract
)));
9973 // Set up some initial values for ClassInfo properties. If this
9974 // class is a leaf (we can't actually determine that yet), these
9975 // will be valid and remain as-is. If not, they'll be updated
9976 // properly when calculating subclass information in another pass.
9977 auto cinfo
= std::make_unique
<ClassInfo2
>();
9978 cinfo
->name
= cls
.name
;
9979 cinfo
->hasConstProp
= cls
.hasConstProp
;
9980 cinfo
->hasReifiedParent
= cls
.hasReifiedGenerics
;
9981 cinfo
->hasReifiedGeneric
= cls
.userAttributes
.count(s___Reified
.get());
9982 cinfo
->subHasReifiedGeneric
= cinfo
->hasReifiedGeneric
;
9983 cinfo
->initialNoReifiedInit
= cls
.attrs
& AttrNoReifiedInit
;
9984 cinfo
->isMockClass
= is_mock_class(&cls
);
9985 cinfo
->isRegularClass
= is_regular_class(cls
);
9987 // Create a ClassGraph for this class. If we decide to not keep
9988 // the ClassInfo, reset the ClassGraph to keep it from ending up
9990 cinfo
->classGraph
= ClassGraph::create(cls
);
9991 auto success
= false;
9992 SCOPE_EXIT
{ if (!success
) cinfo
->classGraph
.reset(); };
9994 // Assume the class isn't overridden. This is true for leafs and
9995 // non-leafs will get updated when we build subclass information.
9996 if (!is_closure_base(cls
)) {
9997 attribute_setter(cls
.attrs
, true, AttrNoOverride
);
9998 attribute_setter(cls
.attrs
, true, AttrNoOverrideRegular
);
10000 attribute_setter(cls
.attrs
, false, AttrNoOverride
);
10001 attribute_setter(cls
.attrs
, false, AttrNoOverrideRegular
);
10004 // Assume this. If not a leaf, will be updated in
10005 // BuildSubclassList job.
10006 attribute_setter(cls
.attrs
, true, AttrNoMock
);
10008 for (auto const& clo
: cls
.closures
) {
10009 if (index
.uninstantiable(clo
->name
)) {
10011 "Making class-info failed for `{}' because "
10012 "its closure `{}' is uninstantiable\n",
10013 cls
.name
, clo
->name
);
10018 if (cls
.parentName
) {
10019 assertx(!is_closure_base(cls
));
10020 assertx(is_closure(cls
) == cls
.parentName
->tsame(s_Closure
.get()));
10022 if (index
.uninstantiable(cls
.parentName
)) {
10024 "Making class-info failed for `{}' because "
10025 "its parent `{}' is uninstantiable\n",
10026 cls
.name
, cls
.parentName
);
10029 auto const& parent
= index
.cls(cls
.parentName
);
10030 auto const& parentInfo
= index
.classInfo(cls
.parentName
);
10032 assertx(!is_closure(parent
));
10033 if (parent
.attrs
& (AttrInterface
| AttrTrait
)) {
10035 "Making class-info failed for `{}' because "
10036 "its parent `{}' is not a class\n",
10037 cls
.name
, cls
.parentName
);
10040 if (!enforce_sealing(*cinfo
, cls
, parent
)) return nullptr;
10042 cinfo
->parent
= cls
.parentName
;
10043 cinfo
->hasConstProp
|= parentInfo
.hasConstProp
;
10044 cinfo
->hasReifiedParent
|= parentInfo
.hasReifiedParent
;
10046 state
.m_parents
.emplace_back(&parent
);
10047 cinfo
->classGraph
.setBase(parentInfo
.classGraph
);
10048 } else if (!cinfo
->hasReifiedGeneric
) {
10049 attribute_setter(cls
.attrs
, true, AttrNoReifiedInit
);
10052 for (auto const iname
: cls
.interfaceNames
) {
10053 assertx(!is_closure(cls
));
10054 assertx(!is_closure_base(cls
));
10055 if (index
.uninstantiable(iname
)) {
10057 "Making class-info failed for `{}' because "
10058 "{} is uninstantiable\n",
10062 auto const& iface
= index
.cls(iname
);
10063 auto const& ifaceInfo
= index
.classInfo(iname
);
10065 assertx(!is_closure(iface
));
10066 if (!(iface
.attrs
& AttrInterface
)) {
10068 "Making class-info failed for `{}' because `{}' "
10069 "is not an interface\n",
10073 if (!enforce_sealing(*cinfo
, cls
, iface
)) return nullptr;
10075 cinfo
->hasReifiedParent
|= ifaceInfo
.hasReifiedParent
;
10077 state
.m_parents
.emplace_back(&iface
);
10078 cinfo
->classGraph
.addParent(ifaceInfo
.classGraph
);
10081 for (auto const ename
: cls
.includedEnumNames
) {
10082 assertx(!is_closure(cls
));
10083 assertx(!is_closure_base(cls
));
10084 if (index
.uninstantiable(ename
)) {
10086 "Making class-info failed for `{}' because "
10087 "{} is uninstantiable\n",
10091 auto const& e
= index
.cls(ename
);
10092 auto const& einfo
= index
.classInfo(ename
);
10094 assertx(!is_closure(e
));
10095 auto const wantAttr
= cls
.attrs
& (AttrEnum
| AttrEnumClass
);
10096 if (!(e
.attrs
& wantAttr
)) {
10098 "Making class-info failed for `{}' because `{}' "
10099 "is not an enum{}\n",
10101 wantAttr
& AttrEnumClass
? " class" : "");
10104 if (!enforce_sealing(*cinfo
, cls
, e
)) return nullptr;
10106 for (auto const iface
: einfo
.classGraph
.declInterfaces()) {
10107 cinfo
->classGraph
.addParent(iface
);
10111 auto const clsHasModuleLevelTrait
=
10112 cls
.userAttributes
.count(s___ModuleLevelTrait
.get());
10113 if (clsHasModuleLevelTrait
&&
10114 (!(cls
.attrs
& AttrTrait
) || (cls
.attrs
& AttrInternal
))) {
10116 "Making class-info failed for `{}' because "
10117 "attribute <<__ModuleLevelTrait>> can only be "
10118 "specified on public traits\n",
10123 for (auto const tname
: cls
.usedTraitNames
) {
10124 assertx(!is_closure(cls
));
10125 assertx(!is_closure_base(cls
));
10126 if (index
.uninstantiable(tname
)) {
10128 "Making class-info failed for `{}' because "
10129 "{} is uninstantiable\n",
10133 auto const& trait
= index
.cls(tname
);
10134 auto const& traitInfo
= index
.classInfo(tname
);
10136 assertx(!is_closure(trait
));
10137 if (!(trait
.attrs
& AttrTrait
)) {
10139 "Making class-info failed for `{}' because `{}' "
10140 "is not a trait\n",
10144 if (!enforce_sealing(*cinfo
, cls
, trait
)) return nullptr;
10146 cinfo
->hasConstProp
|= traitInfo
.hasConstProp
;
10147 cinfo
->hasReifiedParent
|= traitInfo
.hasReifiedParent
;
10149 state
.m_parents
.emplace_back(&trait
);
10150 cinfo
->classGraph
.addParent(traitInfo
.classGraph
);
10153 if (cls
.attrs
& AttrEnum
) {
10154 auto const baseType
= [&] {
10155 auto const& base
= cls
.enumBaseTy
;
10156 if (!base
.isUnresolved()) return base
.type();
10157 auto const tm
= index
.typeMapping(base
.typeName());
10158 if (!tm
) return AnnotType::Unresolved
;
10159 // enums cannot use case types
10160 assertx(!tm
->value
.isUnion());
10161 return tm
->value
.type();
10163 if (!enumSupportsAnnot(baseType
)) {
10165 "Making class-info failed for `{}' because {} "
10166 "is not a valid enum base type\n",
10167 cls
.name
, annotName(baseType
));
10172 if (!build_methods(index
, cls
, *cinfo
, state
)) return nullptr;
10173 if (!build_properties(index
, cls
, *cinfo
, state
)) return nullptr;
10174 if (!build_constants(index
, cls
, *cinfo
, state
)) return nullptr;
10177 begin(state
.m_parents
),
10178 end(state
.m_parents
),
10179 [] (const php::Class
* a
, const php::Class
* b
) {
10180 return string_data_lt_type
{}(a
->name
, b
->name
);
10183 state
.m_parents
.erase(
10184 std::unique(begin(state
.m_parents
), end(state
.m_parents
)),
10185 end(state
.m_parents
)
10189 begin(state
.m_parents
), end(state
.m_parents
),
10190 [] (const php::Class
* c
) { return is_closure(*c
); }
10194 cinfo
->subHasConstProp
= cinfo
->hasConstProp
;
10196 // All methods are originally not overridden (we'll update this as
10197 // necessary later), except for special methods, which are always
10198 // considered to be overridden.
10199 for (auto& [name
, mte
] : cinfo
->methods
) {
10200 assertx(!cinfo
->missingMethods
.count(name
));
10201 if (is_special_method_name(name
)) {
10202 attribute_setter(mte
.attrs
, false, AttrNoOverride
);
10203 mte
.clearNoOverrideRegular();
10205 attribute_setter(mte
.attrs
, true, AttrNoOverride
);
10206 mte
.setNoOverrideRegular();
10210 // We don't calculate subclass information for closures, so make
10211 // sure their initial values are all what they should be.
10212 if (debug
&& (is_closure(cls
) || is_closure_base(cls
))) {
10213 if (is_closure(cls
)) {
10214 always_assert(is_closure_name(cls
.name
));
10215 always_assert(state
.m_parents
.size() == 1);
10216 always_assert(state
.m_parents
[0]->name
->tsame(s_Closure
.get()));
10217 always_assert(!(cls
.attrs
& AttrNoReifiedInit
));
10219 always_assert(state
.m_parents
.empty());
10220 always_assert(cls
.attrs
& AttrNoReifiedInit
);
10222 always_assert(cinfo
->missingMethods
.empty());
10223 always_assert(!cinfo
->hasConstProp
);
10224 always_assert(!cinfo
->subHasConstProp
);
10225 always_assert(!cinfo
->hasReifiedParent
);
10226 always_assert(!cinfo
->hasReifiedGeneric
);
10227 always_assert(!cinfo
->subHasReifiedGeneric
);
10228 always_assert(!cinfo
->initialNoReifiedInit
);
10229 always_assert(!cinfo
->isMockClass
);
10230 always_assert(cinfo
->isRegularClass
);
10231 always_assert(!is_mock_class(&cls
));
10234 ITRACE(2, "new class-info: {}\n", cls
.name
);
10235 if (Trace::moduleEnabled(Trace::hhbbc_index
, 3)) {
10236 if (cinfo
->parent
) {
10237 ITRACE(3, " parent: {}\n", cinfo
->parent
);
10239 auto const cg
= cinfo
->classGraph
;
10240 for (auto const DEBUG_ONLY base
: cg
.bases()) {
10241 ITRACE(3, " base: {}\n", base
.name());
10243 for (auto const DEBUG_ONLY iface
: cls
.interfaceNames
) {
10244 ITRACE(3, " decl implements: {}\n", iface
);
10246 for (auto const DEBUG_ONLY iface
: cg
.interfaces()) {
10247 ITRACE(3, " implements: {}\n", iface
.name());
10249 for (auto const DEBUG_ONLY e
: cls
.includedEnumNames
) {
10250 ITRACE(3, " enum: {}\n", e
);
10252 for (auto const DEBUG_ONLY trait
: cls
.usedTraitNames
) {
10253 ITRACE(3, " uses: {}\n", trait
);
10255 for (auto const& DEBUG_ONLY closure
: cls
.closures
) {
10256 ITRACE(3, " closure: {}\n", closure
->name
);
10260 // We're going to use this ClassInfo.
10265 static bool enforce_sealing(const ClassInfo2
& cinfo
,
10266 const php::Class
& cls
,
10267 const php::Class
& parent
) {
10268 if (is_mock_class(&cls
)) return true;
10269 if (!(parent
.attrs
& AttrSealed
)) return true;
10270 auto const it
= parent
.userAttributes
.find(s___Sealed
.get());
10271 assertx(it
!= parent
.userAttributes
.end());
10272 assertx(tvIsArrayLike(it
->second
));
10273 auto allowed
= false;
10275 it
->second
.m_data
.parr
,
10276 [&] (TypedValue v
) {
10277 assertx(tvIsStringLike(v
));
10278 if (tvAssertStringLike(v
)->tsame(cinfo
.name
)) {
10288 "Making class-info failed for `{}' because "
10289 "`{}' is sealed\n",
10290 cinfo
.name
, parent
.name
10296 static bool build_properties(const LocalIndex
& index
,
10297 const php::Class
& cls
,
10300 if (cls
.parentName
) {
10301 auto const& parentState
= index
.state(cls
.parentName
);
10302 state
.m_props
= parentState
.m_props
;
10303 state
.m_propIndices
= parentState
.m_propIndices
;
10306 for (auto const iface
: cls
.interfaceNames
) {
10307 if (!merge_properties(cinfo
, state
, index
.state(iface
))) {
10311 for (auto const trait
: cls
.usedTraitNames
) {
10312 if (!merge_properties(cinfo
, state
, index
.state(trait
))) {
10316 for (auto const e
: cls
.includedEnumNames
) {
10317 if (!merge_properties(cinfo
, state
, index
.state(e
))) {
10322 if (cls
.attrs
& AttrInterface
) return true;
10324 auto const cannotDefineInternalProperties
=
10325 // public traits cannot define internal properties unless they
10326 // have the __ModuleLevelTrait attribute
10327 ((cls
.attrs
& AttrTrait
) && (cls
.attrs
& AttrPublic
)) &&
10328 !(cls
.userAttributes
.count(s___ModuleLevelTrait
.get()));
10330 for (auto const& p
: cls
.properties
) {
10331 if (cannotDefineInternalProperties
&& (p
.attrs
& AttrInternal
)) {
10333 "Adding property failed for `{}' because property `{}' "
10334 "is internal and public traits cannot define internal properties\n",
10335 cinfo
.name
, p
.name
);
10338 if (!add_property(cinfo
, state
, p
.name
, p
, cinfo
.name
, false)) {
10343 // There's no need to do this work if traits have been flattened
10344 // already, or if the top level class has no traits. In those
10345 // cases, we might be able to rule out some instantiations, but it
10346 // doesn't seem worth it.
10347 if (cls
.attrs
& AttrNoExpandTrait
) return true;
10349 for (auto const traitName
: cls
.usedTraitNames
) {
10350 auto const& trait
= index
.cls(traitName
);
10351 auto const& traitInfo
= index
.classInfo(traitName
);
10352 for (auto const& p
: trait
.properties
) {
10353 if (!add_property(cinfo
, state
, p
.name
, p
, cinfo
.name
, true)) {
10357 for (auto const& p
: traitInfo
.traitProps
) {
10358 if (!add_property(cinfo
, state
, p
.name
, p
, cinfo
.name
, true)) {
10367 static bool add_property(ClassInfo2
& cinfo
,
10370 const php::Prop
& prop
,
10373 auto const [it
, emplaced
] =
10374 state
.m_propIndices
.emplace(name
, state
.m_props
.size());
10376 state
.m_props
.emplace_back(State::PropTuple
{name
, src
, prop
});
10377 if (trait
) cinfo
.traitProps
.emplace_back(prop
);
10380 assertx(it
->second
< state
.m_props
.size());
10381 auto& prevTuple
= state
.m_props
[it
->second
];
10382 auto const& prev
= prevTuple
.prop
;
10383 auto const prevSrc
= prevTuple
.src
;
10385 if (cinfo
.name
->tsame(prevSrc
)) {
10386 if ((prev
.attrs
^ prop
.attrs
) &
10387 (AttrStatic
| AttrPublic
| AttrProtected
| AttrPrivate
) ||
10388 (!(prop
.attrs
& AttrSystemInitialValue
) &&
10389 !(prev
.attrs
& AttrSystemInitialValue
) &&
10390 !Class::compatibleTraitPropInit(prev
.val
, prop
.val
))) {
10392 "Adding property failed for `{}' because "
10393 "two declarations of `{}' at the same level had "
10394 "different attributes\n",
10395 cinfo
.name
, prop
.name
);
10401 if (!(prev
.attrs
& AttrPrivate
)) {
10402 if ((prev
.attrs
^ prop
.attrs
) & AttrStatic
) {
10404 "Adding property failed for `{}' because "
10405 "`{}' was defined both static and non-static\n",
10406 cinfo
.name
, prop
.name
);
10409 if (prop
.attrs
& AttrPrivate
) {
10411 "Adding property failed for `{}' because "
10412 "`{}' was re-declared private\n",
10413 cinfo
.name
, prop
.name
);
10416 if (prop
.attrs
& AttrProtected
&& !(prev
.attrs
& AttrProtected
)) {
10418 "Adding property failed for `{}' because "
10419 "`{}' was redeclared protected from public\n",
10420 cinfo
.name
, prop
.name
);
10425 if (trait
) cinfo
.traitProps
.emplace_back(prop
);
10426 prevTuple
= State::PropTuple
{name
, src
, prop
};
10430 static bool merge_properties(ClassInfo2
& cinfo
,
10432 const State
& src
) {
10433 for (auto const& [name
, src
, prop
] : src
.m_props
) {
10434 if (!add_property(cinfo
, dst
, name
, prop
, src
, false)) {
10441 static bool build_constants(const LocalIndex
& index
,
10445 if (cls
.parentName
) {
10446 cinfo
.clsConstants
= index
.classInfo(cls
.parentName
).clsConstants
;
10447 state
.m_cnsFromTrait
= index
.state(cls
.parentName
).m_cnsFromTrait
;
10450 for (auto const iname
: cls
.interfaceNames
) {
10451 auto const& iface
= index
.classInfo(iname
);
10452 auto const& ifaceState
= index
.state(iname
);
10453 for (auto const& [cnsName
, cnsIdx
] : iface
.clsConstants
) {
10454 auto const added
= add_constant(
10455 index
, cinfo
, state
, cnsName
,
10456 cnsIdx
, ifaceState
.m_cnsFromTrait
.count(cnsName
)
10458 if (!added
) return false;
10462 auto const addShallowConstants
= [&] {
10463 auto const numConstants
= cls
.constants
.size();
10464 for (uint32_t idx
= 0; idx
< numConstants
; ++idx
) {
10465 auto const& cns
= cls
.constants
[idx
];
10466 auto const added
= add_constant(
10467 index
, cinfo
, state
,
10469 ClassInfo2::ConstIndexAndKind
{
10470 ConstIndex
{ cls
.name
, idx
},
10475 if (!added
) return false;
10480 auto const addTraitConstants
= [&] {
10481 for (auto const tname
: cls
.usedTraitNames
) {
10482 auto const& trait
= index
.classInfo(tname
);
10483 for (auto const& [cnsName
, cnsIdx
] : trait
.clsConstants
) {
10484 auto const added
= add_constant(
10485 index
, cinfo
, state
, cnsName
,
10488 if (!added
) return false;
10494 if (Cfg::Eval::TraitConstantInterfaceBehavior
) {
10495 // trait constants must be inserted before constants shallowly
10496 // declared on the class to match the interface semantics
10497 if (!addTraitConstants()) return false;
10498 if (!addShallowConstants()) return false;
10500 if (!addShallowConstants()) return false;
10501 if (!addTraitConstants()) return false;
10504 for (auto const ename
: cls
.includedEnumNames
) {
10505 auto const& e
= index
.classInfo(ename
);
10506 for (auto const& [cnsName
, cnsIdx
] : e
.clsConstants
) {
10507 auto const added
= add_constant(
10508 index
, cinfo
, state
, cnsName
,
10511 if (!added
) return false;
10515 auto const addTraitConst
= [&] (const php::Const
& c
) {
10517 * Only copy in constants that win. Otherwise, in the runtime, if
10518 * we have a constant from an interface implemented by a trait
10519 * that wins over this fromTrait constant, we won't know which
10520 * trait it came from, and therefore won't know which constant
10521 * should win. Dropping losing constants here works because if
10522 * they fatal with constants in declared interfaces, we catch that
10525 auto const& existing
= cinfo
.clsConstants
.find(c
.name
);
10526 if (existing
->second
.idx
.cls
->tsame(c
.cls
)) {
10527 state
.m_traitCns
.emplace_back(c
);
10528 state
.m_traitCns
.back().isFromTrait
= true;
10531 for (auto const tname
: cls
.usedTraitNames
) {
10532 auto const& trait
= index
.cls(tname
);
10533 auto const& traitState
= index
.state(tname
);
10534 for (auto const& c
: trait
.constants
) addTraitConst(c
);
10535 for (auto const& c
: traitState
.m_traitCns
) addTraitConst(c
);
10538 if (cls
.attrs
& (AttrAbstract
| AttrInterface
| AttrTrait
)) return true;
10540 std::vector
<SString
> sortedClsConstants
;
10541 sortedClsConstants
.reserve(cinfo
.clsConstants
.size());
10542 for (auto const& [name
, _
] : cinfo
.clsConstants
) {
10543 sortedClsConstants
.emplace_back(name
);
10546 sortedClsConstants
.begin(),
10547 sortedClsConstants
.end(),
10551 for (auto const name
: sortedClsConstants
) {
10552 auto& cnsIdx
= cinfo
.clsConstants
.find(name
)->second
;
10553 if (cnsIdx
.idx
.cls
->tsame(cls
.name
)) continue;
10555 auto const& cns
= index
.cns(cnsIdx
.idx
);
10556 if (!cns
.isAbstract
|| !cns
.val
) continue;
10558 if (cns
.val
->m_type
== KindOfUninit
) {
10559 auto const& cnsCls
= index
.cls(cnsIdx
.idx
.cls
);
10560 assertx(!cnsCls
.methods
.empty());
10561 assertx(cnsCls
.methods
.back()->name
== s_86cinit
.get());
10562 auto const& cnsCInit
= *cnsCls
.methods
.back();
10564 if (cls
.methods
.empty() ||
10565 cls
.methods
.back()->name
!= s_86cinit
.get()) {
10566 ClonedClosures clonedClosures
;
10567 auto cloned
= clone(
10577 assertx(clonedClosures
.empty());
10578 assertx(cloned
->cls
== &cls
);
10579 cloned
->clsIdx
= cls
.methods
.size();
10580 auto const DEBUG_ONLY emplaced
=
10581 cinfo
.methods
.emplace(cloned
->name
, MethTabEntry
{ *cloned
});
10582 assertx(emplaced
.second
);
10583 cls
.methods
.emplace_back(std::move(cloned
));
10585 auto const DEBUG_ONLY succeeded
=
10586 append_86cinit(cls
.methods
.back().get(), cnsCInit
);
10587 assertx(succeeded
);
10591 // This is similar to trait constant flattening
10593 copy
.cls
= cls
.name
;
10594 copy
.isAbstract
= false;
10595 state
.m_cnsFromTrait
.erase(copy
.name
);
10597 cnsIdx
.idx
.cls
= cls
.name
;
10598 cnsIdx
.idx
.idx
= cls
.constants
.size();
10599 cnsIdx
.kind
= copy
.kind
;
10600 cls
.constants
.emplace_back(std::move(copy
));
10606 static bool add_constant(const LocalIndex
& index
,
10610 const ClassInfo2::ConstIndexAndKind
& cnsIdx
,
10612 auto [it
, emplaced
] = cinfo
.clsConstants
.emplace(name
, cnsIdx
);
10615 always_assert(state
.m_cnsFromTrait
.emplace(name
).second
);
10617 always_assert(!state
.m_cnsFromTrait
.count(name
));
10621 auto& existingIdx
= it
->second
;
10623 // Same constant (from an interface via two different paths) is ok
10624 if (existingIdx
.idx
.cls
->tsame(cnsIdx
.idx
.cls
)) return true;
10626 auto const& existingCnsCls
= index
.cls(existingIdx
.idx
.cls
);
10627 auto const& existing
= index
.cns(existingIdx
.idx
);
10628 auto const& cns
= index
.cns(cnsIdx
.idx
);
10630 if (existing
.kind
!= cns
.kind
) {
10633 "Adding constant failed for `{}' because `{}' was defined by "
10634 "`{}' as a {} and by `{}' as a {}\n",
10638 ConstModifiers::show(cns
.kind
),
10639 existingIdx
.idx
.cls
,
10640 ConstModifiers::show(existing
.kind
)
10645 // Ignore abstract constants
10646 if (cns
.isAbstract
&& !cns
.val
) return true;
10647 // If the existing constant in the map is concrete, then don't
10648 // overwrite it with an incoming abstract constant's default
10649 if (!existing
.isAbstract
&& cns
.isAbstract
) return true;
10651 if (existing
.val
) {
10653 * A constant from a declared interface collides with a constant
10654 * (Excluding constants from interfaces a trait implements).
10656 * Need this check otherwise constants from traits that conflict
10657 * with declared interfaces will silently lose and not conflict
10660 * Type and Context constants can be overridden.
10662 auto const& cnsCls
= index
.cls(cnsIdx
.idx
.cls
);
10663 if (cns
.kind
== ConstModifiers::Kind::Value
&&
10664 !existing
.isAbstract
&&
10665 (existingCnsCls
.attrs
& AttrInterface
) &&
10666 !((cnsCls
.attrs
& AttrInterface
) && fromTrait
)) {
10667 auto const& cls
= index
.cls(cinfo
.name
);
10668 for (auto const iface
: cls
.interfaceNames
) {
10669 if (existingIdx
.idx
.cls
->tsame(iface
)) {
10672 "Adding constant failed for `{}' because "
10673 "`{}' was defined by both `{}' and `{}'\n",
10677 existingIdx
.idx
.cls
10684 // Constants from traits silently lose
10685 if (!Cfg::Eval::TraitConstantInterfaceBehavior
&& fromTrait
) return true;
10687 if ((cnsCls
.attrs
& AttrInterface
||
10688 (Cfg::Eval::TraitConstantInterfaceBehavior
&&
10689 (cnsCls
.attrs
& AttrTrait
))) &&
10690 (existing
.isAbstract
||
10691 cns
.kind
== ConstModifiers::Kind::Type
)) {
10692 // Because existing has val, this covers the case where it is
10693 // abstract with default allow incoming to win. Also, type
10694 // constants from interfaces may be overridden even if they're
10697 // A constant from an interface or from an included enum
10698 // collides with an existing constant.
10699 if (cnsCls
.attrs
& (AttrInterface
| AttrEnum
| AttrEnumClass
) ||
10700 (Cfg::Eval::TraitConstantInterfaceBehavior
&&
10701 (cnsCls
.attrs
& AttrTrait
))) {
10704 "Adding constant failed for `{}' because "
10705 "`{}' was defined by both `{}' and `{}'\n",
10709 existingIdx
.idx
.cls
10717 state
.m_cnsFromTrait
.emplace(name
);
10719 state
.m_cnsFromTrait
.erase(name
);
10721 existingIdx
= cnsIdx
;
10726 * Make a flattened table of the methods on this class.
10728 * Duplicate method names override parent methods, unless the parent
10729 * method is final and the class is not a __MockClass, in which case
10730 * this class definitely would fatal if ever defined.
10732 * Note: we're leaving non-overridden privates in their subclass
10733 * method table, here. This isn't currently "wrong", because calling
10734 * it would be a fatal, but note that resolve_method needs to be
10735 * pretty careful about privates and overriding in general.
10737 static bool build_methods(const LocalIndex
& index
,
10738 const php::Class
& cls
,
10741 // Since interface methods are not inherited, any methods in
10742 // interfaces this class implements are automatically missing.
10743 assertx(cinfo
.methods
.empty());
10744 for (auto const iname
: cls
.interfaceNames
) {
10745 auto const& iface
= index
.classInfo(iname
);
10746 for (auto const& [name
, _
] : iface
.methods
) {
10747 if (is_special_method_name(name
)) continue;
10748 cinfo
.missingMethods
.emplace(name
);
10750 for (auto const name
: iface
.missingMethods
) {
10751 assertx(!is_special_method_name(name
));
10752 cinfo
.missingMethods
.emplace(name
);
10756 // Interface methods are just stubs which return null. They don't
10757 // get inherited by their implementations.
10758 if (cls
.attrs
& AttrInterface
) {
10759 assertx(!cls
.parentName
);
10760 assertx(cls
.usedTraitNames
.empty());
10761 uint32_t idx
= cinfo
.methods
.size();
10763 for (auto const& m
: cls
.methods
) {
10764 auto const res
= cinfo
.methods
.emplace(m
->name
, MethTabEntry
{ *m
});
10765 always_assert(res
.second
);
10766 always_assert(state
.m_methodIndices
.emplace(m
->name
, idx
++).second
);
10767 if (cinfo
.missingMethods
.count(m
->name
)) {
10768 assertx(!res
.first
->second
.firstName());
10769 cinfo
.missingMethods
.erase(m
->name
);
10771 res
.first
->second
.setFirstName();
10773 ITRACE(4, " {}: adding method {}::{}\n",
10774 cls
.name
, cls
.name
, m
->name
);
10779 auto const overridden
= [&] (MethTabEntry
& existing
,
10782 auto const& existingMeth
= index
.meth(existing
);
10783 if (existingMeth
.attrs
& AttrFinal
) {
10784 if (!is_mock_class(&cls
)) {
10787 "Adding methods failed for `{}' because "
10788 "it tried to override final method `{}::{}'\n",
10790 existing
.meth().cls
,
10798 "{}: overriding method {}::{} with {}::{}\n",
10800 existing
.meth().cls
,
10805 if (existingMeth
.attrs
& AttrPrivate
) {
10806 existing
.setHasPrivateAncestor();
10808 existing
.setMeth(meth
);
10809 existing
.attrs
= attrs
;
10810 existing
.setTopLevel();
10814 // If there's a parent, start by copying its methods
10815 if (cls
.parentName
) {
10816 auto const& parentInfo
= index
.classInfo(cls
.parentName
);
10818 assertx(cinfo
.methods
.empty());
10819 cinfo
.missingMethods
.insert(
10820 begin(parentInfo
.missingMethods
),
10821 end(parentInfo
.missingMethods
)
10824 for (auto const& mte
: parentInfo
.methods
) {
10825 // Don't inherit the 86* methods
10826 if (HPHP::Func::isSpecial(mte
.first
)) continue;
10828 auto const emplaced
= cinfo
.methods
.emplace(mte
);
10829 always_assert(emplaced
.second
);
10830 emplaced
.first
->second
.clearTopLevel();
10831 emplaced
.first
->second
.clearFirstName();
10834 state
.m_methodIndices
.emplace(
10836 index
.methodIdx(cls
.parentName
, mte
.first
)
10840 cinfo
.missingMethods
.erase(mte
.first
);
10844 "{}: inheriting method {}::{}\n",
10852 auto idx
= cinfo
.methods
.size();
10853 auto const clsHasModuleLevelTrait
=
10854 cls
.userAttributes
.count(s___ModuleLevelTrait
.get());
10856 // Now add our methods.
10857 for (auto const& m
: cls
.methods
) {
10858 if ((cls
.attrs
& AttrTrait
) &&
10859 (!((cls
.attrs
& AttrInternal
) || clsHasModuleLevelTrait
)) &&
10860 (m
->attrs
& AttrInternal
)) {
10862 "Adding methods failed for `{}' because "
10863 "method `{}' is internal and public traits "
10864 "cannot define internal methods unless they have "
10865 "the <<__ModuleLevelTrait>> attribute\n",
10866 cls
.name
, m
->name
);
10869 auto const emplaced
= cinfo
.methods
.emplace(m
->name
, MethTabEntry
{ *m
});
10870 if (emplaced
.second
) {
10873 "{}: adding method {}::{}\n",
10878 always_assert(state
.m_methodIndices
.emplace(m
->name
, idx
++).second
);
10879 if (cinfo
.missingMethods
.count(m
->name
)) {
10880 assertx(!emplaced
.first
->second
.firstName());
10881 cinfo
.missingMethods
.erase(m
->name
);
10883 emplaced
.first
->second
.setFirstName();
10888 // If the method is already in our table, it shouldn't be
10890 assertx(!cinfo
.missingMethods
.count(m
->name
));
10892 assertx(!emplaced
.first
->second
.firstName());
10894 if ((m
->attrs
& AttrTrait
) && (m
->attrs
& AttrAbstract
)) {
10895 // Abstract methods from traits never override anything.
10898 if (!overridden(emplaced
.first
->second
, MethRef
{ *m
}, m
->attrs
)) {
10903 // If our traits were previously flattened, we're done.
10904 if (cls
.attrs
& AttrNoExpandTrait
) return true;
10908 for (auto const tname
: cls
.usedTraitNames
) {
10909 auto const& tcls
= index
.cls(tname
);
10910 auto const& t
= index
.classInfo(tname
);
10911 std::vector
<std::pair
<SString
, const MethTabEntry
*>>
10912 methods(t
.methods
.size());
10913 for (auto const& [name
, mte
] : t
.methods
) {
10914 if (HPHP::Func::isSpecial(name
)) continue;
10915 auto const idx
= index
.methodIdx(tname
, name
);
10916 assertx(!methods
[idx
].first
);
10917 methods
[idx
] = std::make_pair(name
, &mte
);
10918 if (auto it
= cinfo
.methods
.find(name
);
10919 it
!= end(cinfo
.methods
)) {
10920 it
->second
.clearFirstName();
10924 for (auto const name
: t
.missingMethods
) {
10925 assertx(!is_special_method_name(name
));
10926 if (cinfo
.methods
.count(name
)) continue;
10927 cinfo
.missingMethods
.emplace(name
);
10930 for (auto const& [name
, mte
] : methods
) {
10931 if (!name
) continue;
10932 auto const& meth
= index
.meth(*mte
);
10934 TraitMethod
{ std::make_pair(&t
, &tcls
), &meth
, mte
->attrs
},
10938 for (auto const& clo
: tcls
.closures
) {
10939 auto const invoke
= find_method(clo
.get(), s_invoke
.get());
10941 cinfo
.extraMethods
.emplace(MethRef
{ *invoke
});
10945 auto const traitMethods
= tmid
.finish(
10946 std::make_pair(&cinfo
, &cls
),
10947 cls
.userAttributes
.count(s___EnableMethodTraitDiamond
.get())
10950 // Import the methods.
10951 for (auto const& mdata
: traitMethods
) {
10952 auto const method
= mdata
.tm
.method
;
10953 auto attrs
= mdata
.tm
.modifiers
;
10955 if (attrs
== AttrNone
) {
10956 attrs
= method
->attrs
;
10958 auto const attrMask
=
10959 (Attr
)(AttrPublic
| AttrProtected
| AttrPrivate
|
10960 AttrAbstract
| AttrFinal
);
10961 attrs
= (Attr
)((attrs
& attrMask
) |
10962 (method
->attrs
& ~attrMask
));
10965 auto const emplaced
= cinfo
.methods
.emplace(
10967 MethTabEntry
{ *method
, attrs
}
10969 if (emplaced
.second
) {
10972 "{}: adding trait method {}::{} as {}\n",
10974 method
->cls
->name
, method
->name
, mdata
.name
10977 state
.m_methodIndices
.emplace(mdata
.name
, idx
++).second
10979 cinfo
.missingMethods
.erase(mdata
.name
);
10981 assertx(!cinfo
.missingMethods
.count(mdata
.name
));
10982 if (attrs
& AttrAbstract
) continue;
10983 if (emplaced
.first
->second
.meth().cls
->tsame(cls
.name
)) continue;
10984 if (!overridden(emplaced
.first
->second
, MethRef
{ *method
}, attrs
)) {
10987 state
.methodIdx(index
.m_ctx
->name
, cinfo
.name
, mdata
.name
) = idx
++;
10989 cinfo
.extraMethods
.emplace(MethRef
{ *method
});
10991 } catch (const TMIOps::TMIException
& exn
) {
10994 "Adding methods failed for `{}' importing traits: {}\n",
10995 cls
.name
, exn
.what()
11003 using ClonedClosures
=
11004 hphp_fast_map
<const php::Class
*, std::unique_ptr
<php::Class
>>;
11006 static SString
rename_closure(const php::Class
& closure
,
11007 const php::Class
& newContext
) {
11008 auto n
= closure
.name
->slice();
11009 auto const p
= n
.find(';');
11010 if (p
!= std::string::npos
) n
= n
.subpiece(0, p
);
11011 return makeStaticString(folly::sformat("{};{}", n
, newContext
.name
));
11014 static std::unique_ptr
<php::Class
>
11015 clone_closure(const LocalIndex
& index
,
11016 const php::Class
& closure
,
11017 const php::Class
& newContext
,
11018 bool requiresFromOriginalModule
,
11019 ClonedClosures
& clonedClosures
) {
11020 auto clone
= std::make_unique
<php::Class
>(closure
);
11021 assertx(clone
->closureContextCls
);
11023 clone
->name
= rename_closure(closure
, newContext
);
11024 clone
->closureContextCls
= newContext
.name
;
11025 clone
->unit
= newContext
.unit
;
11027 ITRACE(4, "- cloning closure {} as {} (with context {})\n",
11028 closure
.name
, clone
->name
, newContext
.name
);
11030 for (size_t i
= 0, numMeths
= clone
->methods
.size(); i
< numMeths
; ++i
) {
11031 auto meth
= std::move(clone
->methods
[i
]);
11032 meth
->cls
= clone
.get();
11033 assertx(meth
->clsIdx
== i
);
11034 if (!meth
->originalFilename
) meth
->originalFilename
= meth
->unit
;
11035 if (!meth
->originalUnit
) meth
->originalUnit
= meth
->unit
;
11036 if (!meth
->originalClass
) meth
->originalClass
= closure
.name
;
11037 meth
->requiresFromOriginalModule
= requiresFromOriginalModule
;
11038 meth
->unit
= newContext
.unit
;
11040 clone
->methods
[i
] =
11041 clone_closures(index
, std::move(meth
), requiresFromOriginalModule
, clonedClosures
);
11042 if (!clone
->methods
[i
]) return nullptr;
11048 static std::unique_ptr
<php::Func
>
11049 clone_closures(const LocalIndex
& index
,
11050 std::unique_ptr
<php::Func
> cloned
,
11051 bool requiresFromOriginalModule
,
11052 ClonedClosures
& clonedClosures
) {
11053 if (!cloned
->hasCreateCl
) return cloned
;
11055 auto const onClosure
= [&] (LSString
& closureName
) {
11056 auto const& cls
= index
.cls(closureName
);
11057 assertx(is_closure(cls
));
11059 // CreateCls are allowed to refer to the same closure within the
11060 // same func. If this is a duplicate, use the already cloned
11062 if (auto const it
= clonedClosures
.find(&cls
);
11063 it
!= clonedClosures
.end()) {
11064 closureName
= it
->second
->name
;
11068 // Otherwise clone the closure (which gives it a new name), and
11069 // update the name in the CreateCl to match.
11070 auto closure
= clone_closure(
11073 cloned
->cls
->closureContextCls
11074 ? index
.cls(cloned
->cls
->closureContextCls
)
11076 requiresFromOriginalModule
,
11079 if (!closure
) return false;
11080 closureName
= closure
->name
;
11081 always_assert(clonedClosures
.emplace(&cls
, std::move(closure
)).second
);
11085 auto mf
= php::WideFunc::mut(cloned
.get());
11086 assertx(!mf
.blocks().empty());
11087 for (size_t bid
= 0; bid
< mf
.blocks().size(); bid
++) {
11088 auto const b
= mf
.blocks()[bid
].mutate();
11089 for (size_t ix
= 0; ix
< b
->hhbcs
.size(); ix
++) {
11090 auto& bc
= b
->hhbcs
[ix
];
11092 case Op::CreateCl
: {
11093 if (!onClosure(bc
.CreateCl
.str2
)) return nullptr;
11105 static std::unique_ptr
<php::Func
> clone(const LocalIndex
& index
,
11106 const php::Func
& orig
,
11109 const php::Class
& dstCls
,
11110 ClonedClosures
& clonedClosures
,
11111 bool internal
= false) {
11112 auto cloned
= std::make_unique
<php::Func
>(orig
);
11113 cloned
->name
= name
;
11114 cloned
->attrs
= attrs
;
11115 if (!internal
) cloned
->attrs
|= AttrTrait
;
11116 cloned
->cls
= const_cast<php::Class
*>(&dstCls
);
11117 cloned
->unit
= dstCls
.unit
;
11119 if (!cloned
->originalFilename
) cloned
->originalFilename
= orig
.unit
;
11120 if (!cloned
->originalUnit
) cloned
->originalUnit
= orig
.unit
;
11121 cloned
->originalClass
= orig
.originalClass
11122 ? orig
.originalClass
11124 cloned
->originalModuleName
= orig
.originalModuleName
;
11126 // If the "module level traits" semantics is enabled, whenever HHBBC
11127 // inlines a method from a trait defined in module A into a trait/class
11128 // defined in module B, it sets the requiresFromOriginalModule flag of the
11129 // method to true. This flag causes the originalModuleName field to be
11130 // copied in the HHVM extendedSharedData section of the method, so that
11131 // HHVM is able to resolve correctly the original module of the method.
11132 // Preserving the original module of a method is also needed when a
11133 // method is defined in an internal trait that is used by a module level
11135 const bool requiresFromOriginalModule
= [&] () {
11136 bool copyFromModuleLevelTrait
=
11137 orig
.fromModuleLevelTrait
&& !orig
.requiresFromOriginalModule
&&
11138 orig
.originalModuleName
!= dstCls
.moduleName
;
11139 bool copyFromInternal
=
11140 (orig
.cls
->attrs
& AttrInternal
)
11141 && dstCls
.userAttributes
.count(s___ModuleLevelTrait
.get());;
11143 if (Cfg::Eval::ModuleLevelTraits
&&
11144 (copyFromModuleLevelTrait
|| copyFromInternal
)) {
11147 return orig
.requiresFromOriginalModule
;
11150 cloned
->requiresFromOriginalModule
= requiresFromOriginalModule
;
11152 // cloned method isn't in any method table yet, so trash its
11154 cloned
->clsIdx
= std::numeric_limits
<uint32_t>::max();
11155 return clone_closures(index
, std::move(cloned
), requiresFromOriginalModule
, clonedClosures
);
11158 static bool merge_inits(const LocalIndex
& index
,
11159 const php::Class
& cls
,
11160 const ClassInfo2
& cinfo
,
11162 std::vector
<std::unique_ptr
<php::Func
>>& clones
) {
11163 auto const existing
= [&] () -> const php::Func
* {
11164 for (auto const& m
: cls
.methods
) {
11165 if (m
->name
== name
) return m
.get();
11170 std::unique_ptr
<php::Func
> cloned
;
11172 auto const merge
= [&] (const php::Func
& f
) {
11174 ClonedClosures clonedClosures
;
11185 assertx(clonedClosures
.empty());
11186 if (!cloned
) return false;
11188 ITRACE(4, "- cloning {}::{} as {}::{}\n",
11189 f
.cls
->name
, f
.name
, cls
.name
, name
);
11190 cloned
= clone(index
, f
, f
.name
, f
.attrs
, cls
, clonedClosures
, true);
11191 assertx(clonedClosures
.empty());
11192 return (bool)cloned
;
11196 ITRACE(4, "- appending {}::{} into {}::{}\n",
11197 f
.cls
->name
, f
.name
, cls
.name
, name
);
11198 if (name
== s_86cinit
.get()) return append_86cinit(cloned
.get(), f
);
11199 return append_func(cloned
.get(), f
);
11202 for (auto const tname
: cls
.usedTraitNames
) {
11203 auto const& trait
= index
.classInfo(tname
);
11204 auto const it
= trait
.methods
.find(name
);
11205 if (it
== trait
.methods
.end()) continue;
11206 auto const& meth
= index
.meth(it
->second
);
11207 if (!merge(meth
)) {
11208 ITRACE(4, "merge_inits: failed to merge {}::{}\n",
11209 meth
.cls
->name
, name
);
11215 ITRACE(4, "merge_inits: adding {}::{} to method table\n",
11216 cloned
->cls
->name
, cloned
->name
);
11217 clones
.emplace_back(std::move(cloned
));
11223 static bool merge_xinits(const LocalIndex
& index
,
11224 const php::Class
& cls
,
11225 const ClassInfo2
& cinfo
,
11226 const State
& state
,
11227 std::vector
<std::unique_ptr
<php::Func
>>& clones
) {
11228 auto const merge_one
= [&] (SString name
, Attr attr
) {
11229 auto const unnecessary
= std::all_of(
11230 cinfo
.traitProps
.begin(),
11231 cinfo
.traitProps
.end(),
11232 [&] (const php::Prop
& p
) {
11233 if ((p
.attrs
& (AttrStatic
| AttrLSB
)) != attr
) return true;
11234 if (p
.val
.m_type
!= KindOfUninit
) return true;
11235 if (p
.attrs
& AttrLateInit
) return true;
11239 if (unnecessary
) return true;
11240 return merge_inits(index
, cls
, cinfo
, name
, clones
);
11243 if (!merge_one(s_86pinit
.get(), AttrNone
)) return false;
11244 if (!merge_one(s_86sinit
.get(), AttrStatic
)) return false;
11245 if (!merge_one(s_86linit
.get(), AttrStatic
| AttrLSB
)) return false;
11247 auto const unnecessary
= std::all_of(
11248 state
.m_traitCns
.begin(),
11249 state
.m_traitCns
.end(),
11250 [&] (const php::Const
& c
) {
11251 return !c
.val
|| c
.val
->m_type
!= KindOfUninit
;
11254 if (unnecessary
) return true;
11255 return merge_inits(index
, cls
, cinfo
, s_86cinit
.get(), clones
);
11258 static std::vector
<std::unique_ptr
<ClassInfo2
>>
11259 flatten_traits(const LocalIndex
& index
,
11263 if (cls
.attrs
& AttrNoExpandTrait
) return {};
11264 if (cls
.usedTraitNames
.empty()) {
11265 cls
.attrs
|= AttrNoExpandTrait
;
11269 ITRACE(4, "flatten traits: {}\n", cls
.name
);
11270 Trace::Indent indent
;
11272 assertx(!is_closure(cls
));
11274 auto traitHasConstProp
= cls
.hasConstProp
;
11275 for (auto const tname
: cls
.usedTraitNames
) {
11276 auto const& trait
= index
.cls(tname
);
11277 auto const& tinfo
= index
.classInfo(tname
);
11278 if (!(trait
.attrs
& AttrNoExpandTrait
)) {
11279 ITRACE(4, "Not flattening {} because of {}\n", cls
.name
, trait
.name
);
11282 if (is_noflatten_trait(&trait
)) {
11284 4, "Not flattening {} because {} is annotated with __NoFlatten\n",
11285 cls
.name
, trait
.name
11289 if (tinfo
.hasConstProp
) traitHasConstProp
= true;
11292 std::vector
<std::pair
<SString
, MethTabEntry
*>> toAdd
;
11293 for (auto& [name
, mte
] : cinfo
.methods
) {
11294 if (!mte
.topLevel()) continue;
11295 if (mte
.meth().cls
->tsame(cls
.name
)) continue;
11296 assertx(index
.cls(mte
.meth().cls
).attrs
& AttrTrait
);
11297 toAdd
.emplace_back(name
, &mte
);
11300 if (!toAdd
.empty()) {
11301 assertx(!cinfo
.extraMethods
.empty());
11303 toAdd
.begin(), toAdd
.end(),
11304 [&] (auto const& a
, auto const& b
) {
11306 state
.methodIdx(index
.m_ctx
->name
, cinfo
.name
, a
.first
) <
11307 state
.methodIdx(index
.m_ctx
->name
, cinfo
.name
, b
.first
);
11310 } else if (debug
) {
11311 // When building the ClassInfos, we proactively added all
11312 // closures from usedTraits to the extraMethods map; but now
11313 // we're going to start from the used methods, and deduce which
11314 // closures actually get pulled in. Its possible *none* of the
11315 // methods got used, in which case, we won't need their closures
11316 // either. To be safe, verify that the only things in the map
11318 for (auto const& mte
: cinfo
.extraMethods
) {
11319 auto const& meth
= index
.meth(mte
);
11320 always_assert(meth
.isClosureBody
);
11324 std::vector
<std::unique_ptr
<php::Func
>> clones
;
11325 ClonedClosures clonedClosures
;
11327 for (auto const& [name
, mte
] : toAdd
) {
11328 auto const& meth
= index
.meth(*mte
);
11329 auto cloned
= clone(
11338 ITRACE(4, "Not flattening {} because {}::{} could not be cloned\n",
11339 cls
.name
, mte
->meth().cls
, name
);
11342 assertx(cloned
->attrs
& AttrTrait
);
11343 clones
.emplace_back(std::move(cloned
));
11346 if (!merge_xinits(index
, cls
, cinfo
, state
, clones
)) {
11347 ITRACE(4, "Not flattening {} because we couldn't merge the 86xinits\n",
11352 // We're now committed to flattening.
11353 ITRACE(3, "Flattening {}\n", cls
.name
);
11355 if (traitHasConstProp
) {
11356 assertx(cinfo
.hasConstProp
);
11357 cls
.hasConstProp
= true;
11359 cinfo
.extraMethods
.clear();
11361 for (auto [_
, mte
] : toAdd
) mte
->attrs
|= AttrTrait
;
11363 for (auto& p
: cinfo
.traitProps
) {
11364 ITRACE(4, "- prop {}\n", p
.name
);
11365 cls
.properties
.emplace_back(std::move(p
));
11366 cls
.properties
.back().attrs
|= AttrTrait
;
11368 cinfo
.traitProps
.clear();
11370 for (auto& c
: state
.m_traitCns
) {
11371 ITRACE(4, "- const {}\n", c
.name
);
11373 auto it
= cinfo
.clsConstants
.find(c
.name
);
11374 assertx(it
!= cinfo
.clsConstants
.end());
11375 auto& cnsIdx
= it
->second
;
11378 state
.m_cnsFromTrait
.erase(c
.name
);
11379 cnsIdx
.idx
.cls
= cls
.name
;
11380 cnsIdx
.idx
.idx
= cls
.constants
.size();
11381 cls
.constants
.emplace_back(std::move(c
));
11383 state
.m_traitCns
.clear();
11385 // A class should inherit any declared interfaces of any traits
11386 // that are flattened into it.
11387 for (auto const tname
: cls
.usedTraitNames
) {
11388 auto const& tinfo
= index
.classInfo(tname
);
11389 cinfo
.classGraph
.flattenTraitInto(tinfo
.classGraph
);
11392 // If we flatten the traits into us, they're no longer actual
11394 state
.m_parents
.erase(
11396 begin(state
.m_parents
),
11397 end(state
.m_parents
),
11398 [] (const php::Class
* c
) { return bool(c
->attrs
& AttrTrait
); }
11400 end(state
.m_parents
)
11403 for (auto const tname
: cls
.usedTraitNames
) {
11404 auto const& traitState
= index
.state(tname
);
11405 state
.m_parents
.insert(
11406 end(state
.m_parents
),
11407 begin(traitState
.m_parents
),
11408 end(traitState
.m_parents
)
11412 begin(state
.m_parents
),
11413 end(state
.m_parents
),
11414 [] (const php::Class
* a
, const php::Class
* b
) {
11415 return string_data_lt_type
{}(a
->name
, b
->name
);
11418 state
.m_parents
.erase(
11419 std::unique(begin(state
.m_parents
), end(state
.m_parents
)),
11420 end(state
.m_parents
)
11423 std::vector
<std::unique_ptr
<ClassInfo2
>> newClosures
;
11424 if (!clones
.empty()) {
11425 auto const add
= [&] (std::unique_ptr
<php::Func
> clone
) {
11426 assertx(clone
->cls
== &cls
);
11427 clone
->clsIdx
= cls
.methods
.size();
11429 if (!is_special_method_name(clone
->name
)) {
11430 auto it
= cinfo
.methods
.find(clone
->name
);
11431 assertx(it
!= cinfo
.methods
.end());
11432 assertx(!it
->second
.meth().cls
->tsame(cls
.name
));
11433 it
->second
.setMeth(MethRef
{ cls
.name
, clone
->clsIdx
});
11435 auto const [existing
, emplaced
] =
11436 cinfo
.methods
.emplace(clone
->name
, MethTabEntry
{ *clone
});
11438 assertx(existing
->second
.meth().cls
->tsame(cls
.name
));
11439 if (clone
->name
!= s_86cinit
.get()) {
11440 auto const idx
= existing
->second
.meth().idx
;
11441 clone
->clsIdx
= idx
;
11442 cls
.methods
[idx
] = std::move(clone
);
11445 existing
->second
.setMeth(MethRef
{ cls
.name
, clone
->clsIdx
});
11450 cls
.methods
.emplace_back(std::move(clone
));
11453 auto cinit
= [&] () -> std::unique_ptr
<php::Func
> {
11454 if (cls
.methods
.empty()) return nullptr;
11455 if (cls
.methods
.back()->name
!= s_86cinit
.get()) return nullptr;
11456 auto init
= std::move(cls
.methods
.back());
11457 cls
.methods
.pop_back();
11461 for (auto& clone
: clones
) {
11462 ITRACE(4, "- meth {}\n", clone
->name
);
11463 if (clone
->name
== s_86cinit
.get()) {
11464 cinit
= std::move(clone
);
11467 add(std::move(clone
));
11469 if (cinit
) add(std::move(cinit
));
11471 for (auto& [orig
, clo
] : clonedClosures
) {
11472 ITRACE(4, "- closure {} as {}\n", orig
->name
, clo
->name
);
11473 assertx(is_closure(*orig
));
11474 assertx(is_closure(*clo
));
11475 assertx(clo
->closureContextCls
->tsame(cls
.name
));
11476 assertx(clo
->unit
== cls
.unit
);
11478 assertx(clo
->usedTraitNames
.empty());
11480 auto cloinfo
= make_info(index
, *clo
, cloState
);
11482 assertx(cloState
.m_traitCns
.empty());
11483 assertx(cloState
.m_cnsFromTrait
.empty());
11484 assertx(cloState
.m_parents
.size() == 1);
11485 assertx(cloState
.m_parents
[0]->name
->tsame(s_Closure
.get()));
11487 cls
.closures
.emplace_back(std::move(clo
));
11488 newClosures
.emplace_back(std::move(cloinfo
));
11492 // Flattening methods into traits can turn methods from not "first
11493 // name" to "first name", so recalculate that here.
11494 for (auto& [name
, mte
] : cinfo
.methods
) {
11495 if (mte
.firstName()) continue;
11496 auto const firstName
= [&, name
=name
] {
11497 if (cls
.parentName
) {
11498 auto const& parentInfo
= index
.classInfo(cls
.parentName
);
11499 if (parentInfo
.methods
.count(name
)) return false;
11500 if (parentInfo
.missingMethods
.count(name
)) return false;
11502 for (auto const iname
: cinfo
.classGraph
.interfaces()) {
11503 auto const& iface
= index
.classInfo(iname
.name());
11504 if (iface
.methods
.count(name
)) return false;
11505 if (iface
.missingMethods
.count(name
)) return false;
11509 if (firstName
) mte
.setFirstName();
11513 bool operator()(const PreClass::ClassRequirement
& a
,
11514 const PreClass::ClassRequirement
& b
) const {
11515 return a
.is_same(&b
);
11517 size_t operator()(const PreClass::ClassRequirement
& a
) const {
11521 hphp_fast_set
<PreClass::ClassRequirement
, EqHash
, EqHash
> reqs
{
11522 cls
.requirements
.begin(),
11523 cls
.requirements
.end()
11526 for (auto const tname
: cls
.usedTraitNames
) {
11527 auto const& trait
= index
.cls(tname
);
11528 for (auto const& req
: trait
.requirements
) {
11529 if (reqs
.emplace(req
).second
) cls
.requirements
.emplace_back(req
);
11533 cls
.attrs
|= AttrNoExpandTrait
;
11534 return newClosures
;
11537 static std::unique_ptr
<FuncInfo2
> make_func_info(const LocalIndex
& index
,
11538 const php::Func
& f
) {
11539 auto finfo
= std::make_unique
<FuncInfo2
>();
11540 finfo
->name
= f
.name
;
11544 static bool resolve_one(TypeConstraint
& tc
,
11545 const TypeConstraint
& tv
,
11550 assertx(!tv
.isUnion());
11551 // Whatever it's an alias of isn't valid, so leave unresolved.
11552 if (tv
.isUnresolved()) return false;
11553 if (isProp
&& !propSupportsAnnot(tv
.type())) return false;
11554 auto const value
= [&] () -> SString
{
11555 // Store the first enum encountered during resolution. This
11556 // lets us fixup the type later if needed.
11557 if (firstEnum
) return firstEnum
;
11558 if (tv
.isSubObject()) {
11559 auto clsName
= tv
.clsName();
11565 if (isUnion
) tc
.unresolve();
11566 tc
.resolveType(tv
.type(), tv
.isNullable(), value
);
11567 assertx(IMPLIES(isProp
, tc
.validForProp()));
11568 if (uses
&& value
) uses
->emplace(value
);
11572 // Update a type constraint to it's ultimate type, or leave it as
11573 // unresolved if it resolves to nothing valid. Record the new type
11574 // in case it needs to be fixed up later.
11575 static void update_type_constraint(const LocalIndex
& index
,
11576 TypeConstraint
& tc
,
11578 TSStringSet
* uses
) {
11579 always_assert(IMPLIES(isProp
, tc
.validForProp()));
11581 if (!tc
.isUnresolved()) {
11582 // Any TC already resolved is assumed to be correct.
11584 for (auto& part
: eachTypeConstraintInUnion(tc
)) {
11585 if (auto clsName
= part
.clsName()) {
11586 uses
->emplace(clsName
);
11592 auto const name
= tc
.typeName();
11594 if (tc
.isUnion()) {
11595 // This is a union that contains unresolved names.
11596 not_implemented(); // TODO(T151885113)
11599 // This is an unresolved name that can resolve to either a single type or
11602 // Is this name a type-alias or enum?
11603 if (auto const tm
= index
.typeMapping(name
)) {
11604 if (tm
->value
.isUnion()) {
11606 tc
.flags() & (TypeConstraintFlags::Nullable
11607 | TypeConstraintFlags::TypeVar
11608 | TypeConstraintFlags::Soft
11609 | TypeConstraintFlags::TypeConstant
11610 | TypeConstraintFlags::DisplayNullable
11611 | TypeConstraintFlags::UpperBound
);
11612 std::vector
<TypeConstraint
> members
;
11613 for (auto& tv
: eachTypeConstraintInUnion(tm
->value
)) {
11614 TypeConstraint copy
= tv
;
11615 copy
.addFlags(flags
);
11616 if (!resolve_one(copy
, tv
, tm
->firstEnum
, uses
, isProp
, true)) {
11619 members
.emplace_back(std::move(copy
));
11621 tc
= TypeConstraint::makeUnion(name
, members
);
11625 // This unresolved name resolves to a single type.
11626 assertx(!tm
->value
.isUnion());
11627 resolve_one(tc
, tm
->value
, tm
->firstEnum
, uses
, isProp
, false);
11631 // Not a type-alias or enum. If it's explicitly marked as missing,
11632 // leave it unresolved. Otherwise assume it's an object with that
11634 if (index
.missingType(name
)) return;
11635 tc
.resolveType(AnnotType::SubObject
, tc
.isNullable(), name
);
11636 if (uses
) uses
->emplace(name
);
11639 static void update_type_constraints(const LocalIndex
& index
,
11641 TSStringSet
* uses
) {
11642 for (auto& p
: func
.params
) {
11643 update_type_constraint(index
, p
.typeConstraint
, false, uses
);
11644 for (auto& ub
: p
.upperBounds
.m_constraints
) {
11645 update_type_constraint(index
, ub
, false, uses
);
11648 update_type_constraint(index
, func
.retTypeConstraint
, false, uses
);
11649 for (auto& ub
: func
.returnUBs
.m_constraints
) {
11650 update_type_constraint(index
, ub
, false, uses
);
11654 static void update_type_constraints(const LocalIndex
& index
,
11656 TSStringSet
* uses
) {
11657 if (cls
.attrs
& AttrEnum
) {
11658 update_type_constraint(index
, cls
.enumBaseTy
, false, uses
);
11660 for (auto& meth
: cls
.methods
) update_type_constraints(index
, *meth
, uses
);
11661 for (auto& prop
: cls
.properties
) {
11662 update_type_constraint(index
, prop
.typeConstraint
, true, uses
);
11663 for (auto& ub
: prop
.ubs
.m_constraints
) {
11664 update_type_constraint(index
, ub
, true, uses
);
11670 * Mark any properties in cls that definitely do not redeclare a
11671 * property in the parent with an inequivalent type-hint.
11673 * Rewrite the initial values for any AttrSystemInitialValue
11674 * properties. If the properties' type-hint does not admit null
11675 * values, change the initial value to one that is not null
11676 * (if possible). This is only safe to do so if the property is not
11677 * redeclared in a derived class or if the redeclaration does not
11678 * have a null system provided default value. Otherwise, a property
11679 * can have a null value (even if its type-hint doesn't allow it)
11680 * without the JIT realizing that its possible.
11682 * Note that this ignores any unflattened traits. This is okay
11683 * because properties pulled in from traits which match an already
11684 * existing property can't change the initial value. The runtime
11685 * will clear AttrNoImplicitNullable on any property pulled from the
11686 * trait if it doesn't match an existing property.
11688 static void optimize_properties(const LocalIndex
& index
,
11690 ClassInfo2
& cinfo
) {
11691 assertx(cinfo
.hasBadRedeclareProp
);
11693 auto const isClosure
= is_closure(cls
);
11695 cinfo
.hasBadRedeclareProp
= false;
11696 for (auto& prop
: cls
.properties
) {
11697 assertx(!(prop
.attrs
& AttrNoBadRedeclare
));
11698 assertx(!(prop
.attrs
& AttrNoImplicitNullable
));
11700 auto const noBadRedeclare
= [&] {
11701 // Closures should never have redeclared properties.
11702 if (isClosure
) return true;
11703 // Static and private properties never redeclare anything so
11704 // need not be considered.
11705 if (prop
.attrs
& (AttrStatic
| AttrPrivate
)) return true;
11707 for (auto const base
: cinfo
.classGraph
.bases()) {
11708 if (base
.name()->tsame(cls
.name
)) continue;
11710 auto& baseCInfo
= index
.classInfo(base
.name());
11711 auto& baseCls
= index
.cls(base
.name());
11713 auto const parentProp
= [&] () -> php::Prop
* {
11714 for (auto& p
: baseCls
.properties
) {
11715 if (p
.name
== prop
.name
) return const_cast<php::Prop
*>(&p
);
11717 for (auto& p
: baseCInfo
.traitProps
) {
11718 if (p
.name
== prop
.name
) return const_cast<php::Prop
*>(&p
);
11722 if (!parentProp
) continue;
11723 if (parentProp
->attrs
& (AttrStatic
| AttrPrivate
)) continue;
11725 // This property's type-constraint might not have been
11726 // resolved (if the parent is not on the output list for
11727 // this job), so do so here.
11728 update_type_constraint(
11730 parentProp
->typeConstraint
,
11735 // This check is safe, but conservative. It might miss a few
11736 // rare cases, but it's sufficient and doesn't require class
11738 if (prop
.typeConstraint
.maybeInequivalentForProp(
11739 parentProp
->typeConstraint
11744 for (auto const& ub
: prop
.ubs
.m_constraints
) {
11745 for (auto& pub
: parentProp
->ubs
.m_constraints
) {
11746 update_type_constraint(index
, pub
, true, nullptr);
11747 if (ub
.maybeInequivalentForProp(pub
)) return false;
11755 if (noBadRedeclare
) {
11756 attribute_setter(prop
.attrs
, true, AttrNoBadRedeclare
);
11758 cinfo
.hasBadRedeclareProp
= true;
11761 auto const nullable
= [&] {
11762 if (isClosure
) return true;
11763 if (!(prop
.attrs
& AttrSystemInitialValue
)) return false;
11764 return prop
.typeConstraint
.defaultValue().m_type
== KindOfNull
;
11767 attribute_setter(prop
.attrs
, !nullable
, AttrNoImplicitNullable
);
11768 if (!(prop
.attrs
& AttrSystemInitialValue
)) continue;
11769 if (prop
.val
.m_type
== KindOfUninit
) {
11770 assertx(isClosure
|| bool(prop
.attrs
& AttrLateInit
));
11775 if (nullable
) return make_tv
<KindOfNull
>();
11776 // Give the 86reified_prop a special default value to avoid
11777 // pessimizing the inferred type (we want it to always be a
11778 // vec of a specific size).
11779 if (prop
.name
== s_86reified_prop
.get()) {
11780 return get_default_value_of_reified_list(cls
.userAttributes
);
11782 return prop
.typeConstraint
.defaultValue();
11789 Job
<FlattenJob
> s_flattenJob
;
11792 * For efficiency reasons, we want to do class flattening all in one
11793 * pass. So, we use assign_hierarchical_work (described above) to
11794 * calculate work buckets to allow us to do this.
11796 * - The "root" classes are the leaf classes in the hierarchy. These are
11797 * the buckets which are not dependencies of anything.
11799 * - The dependencies of a class are all of the (transitive) parent
11800 * classes of that class (up to the top classes with no parents).
11802 * - Each job takes two kinds of input. The first is the set of
11803 * classes which are actually to be flattened. These will have the
11804 * flattening results returned as output from the job. The second is
11805 * the set of dependencies that are required to perform flattening
11806 * on the first set of inputs. These will have the same flattening
11807 * algorithm applied to them, but only to obtain intermediate state
11808 * to calculate the output for the first set of inputs. Their
11809 * results will be thrown away.
11811 * - When we run the jobs, we'll take the outputs and turn that into a set of
11812 * updates, which we then apply to the Index data structures. Some
11813 * of these updates require changes to the php::Unit, which we do a
11814 * in separate set of "fixup" jobs at the end.
11817 // Input class metadata to be turned into work buckets.
11818 struct IndexFlattenMetadata
{
11821 // All types mentioned in type-constraints in this class.
11822 std::vector
<SString
> unresolvedTypes
;
11823 size_t idx
; // Index into allCls vector
11824 bool isClosure
{false};
11825 bool uninstantiable
{false};
11827 TSStringToOneT
<ClassMeta
> cls
;
11828 // All classes to be flattened
11829 std::vector
<SString
> allCls
;
11830 // Mapping of units to classes which should be deleted from that
11831 // unit. This is typically from duplicate meth callers. This is
11832 // performed as part of "fixing up" the unit after flattening
11833 // because it's convenient to do so there.
11834 SStringToOneT
<std::vector
<SString
>> unitDeletions
;
11836 // All types mentioned in type-constraints in this func.
11837 std::vector
<SString
> unresolvedTypes
;
11839 FSStringToOneT
<FuncMeta
> func
;
11840 std::vector
<SString
> allFuncs
;
11841 TSStringToOneT
<TypeMapping
> typeMappings
;
11844 //////////////////////////////////////////////////////////////////////
11846 constexpr size_t kNumTypeMappingRounds
= 20;
11849 * Update the type-mappings in the program so they all point to their
11850 * ultimate type. After this step, every type-mapping that still has
11851 * an unresolved type points to an invalid type.
11853 void flatten_type_mappings(IndexData
& index
,
11854 IndexFlattenMetadata
& meta
) {
11855 trace_time tracer
{"flatten type mappings"};
11856 tracer
.ignore_client_stats();
11858 std::vector
<const TypeMapping
*> work
;
11859 work
.reserve(meta
.typeMappings
.size());
11860 for (auto const& [_
, tm
] : meta
.typeMappings
) work
.emplace_back(&tm
);
11862 auto resolved
= parallel::map(
11864 [&] (const TypeMapping
* typeMapping
) {
11865 Optional
<TSStringSet
> seen
;
11866 TypeConstraintFlags flags
=
11867 typeMapping
->value
.flags() & (TypeConstraintFlags::Nullable
11868 | TypeConstraintFlags::TypeVar
11869 | TypeConstraintFlags::Soft
11870 | TypeConstraintFlags::TypeConstant
11871 | TypeConstraintFlags::DisplayNullable
11872 | TypeConstraintFlags::UpperBound
);
11873 auto firstEnum
= typeMapping
->firstEnum
;
11874 auto const isUnion
= typeMapping
->value
.isUnion();
11875 bool anyUnresolved
= false;
11877 auto enumMeta
= folly::get_ptr(meta
.cls
, typeMapping
->name
);
11879 std::vector
<TypeConstraint
> tvu
;
11881 for (auto const& tc
: eachTypeConstraintInUnion(typeMapping
->value
)) {
11882 const auto type
= tc
.type();
11883 const auto value
= tc
.typeName();
11887 if (type
!= AnnotType::Unresolved
) {
11888 // If the type-mapping is already resolved, we mainly take it
11889 // as is. The exception is if it's an enum, in which case we
11890 // validate the underlying base type.
11891 assertx(type
!= AnnotType::SubObject
);
11893 tvu
.emplace_back(tc
);
11896 if (!enumSupportsAnnot(type
)) {
11898 2, "Type-mapping '{}' is invalid because it resolves to "
11899 "invalid enum type {}\n",
11903 tvu
.emplace_back(AnnotType::Unresolved
, tc
.flags(), value
);
11906 tvu
.emplace_back(type
, tc
.flags() | flags
, value
);
11907 anyUnresolved
= true;
11911 std::queue
<LSString
> queue
;
11914 for (size_t rounds
= 0;; ++rounds
) {
11915 if (queue
.empty()) break;
11916 name
= normalizeNS(queue
.back());
11919 if (auto const next
= folly::get_ptr(meta
.typeMappings
, name
)) {
11920 flags
|= next
->value
.flags() & (TypeConstraintFlags::Nullable
11921 | TypeConstraintFlags::TypeVar
11922 | TypeConstraintFlags::Soft
11923 | TypeConstraintFlags::TypeConstant
11924 | TypeConstraintFlags::DisplayNullable
11925 | TypeConstraintFlags::UpperBound
);
11926 auto const nextEnum
= next
->firstEnum
;
11927 if (!curEnum
) curEnum
= nextEnum
;
11928 if (!firstEnum
&& !isUnion
) firstEnum
= curEnum
;
11930 if (enumMeta
&& nextEnum
) {
11931 enumMeta
->deps
.emplace(nextEnum
);
11934 for (auto const& next_tc
: eachTypeConstraintInUnion(next
->value
)) {
11935 auto next_type
= next_tc
.type();
11936 auto next_value
= next_tc
.typeName();
11937 if (next_type
== AnnotType::Unresolved
) {
11938 queue
.push(next_value
);
11941 assertx(next_type
!= AnnotType::SubObject
);
11942 if (curEnum
&& !enumSupportsAnnot(next_type
)) {
11944 2, "Type-mapping '{}' is invalid because it resolves to "
11945 "invalid enum type {}{}\n",
11947 annotName(next_type
),
11948 curEnum
->tsame(typeMapping
->name
)
11949 ? "" : folly::sformat(" (via {})", curEnum
)
11951 tvu
.emplace_back(AnnotType::Unresolved
, tc
.flags() | flags
, name
);
11952 anyUnresolved
= true;
11955 tvu
.emplace_back(next_type
, tc
.flags() | flags
, next_value
);
11957 } else if (index
.classRefs
.count(name
)) {
11960 2, "Type-mapping '{}' is invalid because it resolves to "
11961 "invalid object '{}' for enum type (via {})\n",
11969 curEnum
? AnnotType::Unresolved
: AnnotType::SubObject
,
11970 tc
.flags() | flags
,
11973 if (curEnum
) anyUnresolved
= true;
11977 2, "Type-mapping '{}' is invalid because it involves "
11978 "non-existent type '{}'{}\n",
11981 (curEnum
&& !curEnum
->tsame(typeMapping
->name
))
11982 ? folly::sformat(" (via {})", curEnum
) : ""
11984 tvu
.emplace_back(AnnotType::Unresolved
, tc
.flags() | flags
, name
);
11985 anyUnresolved
= true;
11989 // Deal with cycles. Since we don't expect to encounter them, just
11990 // use a counter until we hit a chain length of kNumTypeMappingRounds,
11991 // then start tracking the names we resolve.
11992 if (rounds
== kNumTypeMappingRounds
) {
11994 seen
->insert(name
);
11995 } else if (rounds
> kNumTypeMappingRounds
) {
11996 if (!seen
->insert(name
).second
) {
11998 2, "Type-mapping '{}' is invalid because it's definition "
11999 "is circular with '{}'\n",
12003 return TypeMapping
{
12006 TypeConstraint
{AnnotType::Unresolved
, flags
, name
},
12012 if (isUnion
&& anyUnresolved
) {
12013 // Unions cannot contain a mix of resolved an unresolved class names so
12014 // if one of the names failed to resolve we must mark all of them as
12016 for (auto& tc
: tvu
) if (tc
.isSubObject()) tc
.unresolve();
12018 assertx(!tvu
.empty());
12019 // If any of the subtypes end up unresolved then the final union will also
12020 // be unresolved. But it's important to try the `makeUnion` anyway because
12021 // it will deal with some of the canonicalizations like `bool`.
12022 auto value
= TypeConstraint::makeUnion(typeMapping
->name
, std::move(tvu
));
12023 return TypeMapping
{ typeMapping
->name
, firstEnum
, value
};
12027 for (auto& after
: resolved
) {
12028 auto const name
= after
.name
;
12029 using namespace folly::gen
;
12031 4, "Type-mapping '{}' flattened to {}{}\n",
12033 after
.value
.debugName(),
12034 (after
.firstEnum
&& !after
.firstEnum
->tsame(name
))
12035 ? folly::sformat(" (via {})", after
.firstEnum
) : ""
12037 if (after
.value
.isUnresolved() && meta
.cls
.count(name
)) {
12038 FTRACE(4, " Marking enum '{}' as uninstantiable\n", name
);
12039 meta
.cls
.at(name
).uninstantiable
= true;
12041 meta
.typeMappings
.at(name
) = std::move(after
);
12045 //////////////////////////////////////////////////////////////////////
12047 struct FlattenClassesWork
{
12048 std::vector
<SString
> classes
;
12049 std::vector
<SString
> deps
;
12050 std::vector
<SString
> funcs
;
12051 std::vector
<SString
> uninstantiable
;
12054 std::vector
<FlattenClassesWork
>
12055 flatten_classes_assign(IndexFlattenMetadata
& meta
) {
12056 trace_time trace
{"flatten classes assign"};
12057 trace
.ignore_client_stats();
12059 // First calculate the classes which *aren't* leafs. A class is a
12060 // leaf if it is not depended on by another class. The sense is
12061 // inverted because we want to default construct the atomics.
12062 std::vector
<std::atomic
<bool>> isNotLeaf(meta
.allCls
.size());
12063 parallel::for_each(
12065 [&] (SString cls
) {
12066 auto const& clsMeta
= meta
.cls
.at(cls
);
12067 for (auto const d
: clsMeta
.deps
) {
12068 auto const it
= meta
.cls
.find(d
);
12069 if (it
== meta
.cls
.end()) continue;
12070 assertx(it
->second
.idx
< isNotLeaf
.size());
12071 isNotLeaf
[it
->second
.idx
] = true;
12076 // Store all of the (transitive) dependencies for every class,
12077 // calculated lazily. LockFreeLazy ensures that multiple classes can
12078 // access this concurrently and safely calculate it on demand.
12081 // Whether this class is instantiable
12082 bool instantiable
{false};
12084 std::vector
<LockFreeLazy
<DepLookup
>> allDeps
{meta
.allCls
.size()};
12086 // Look up all of the transitive dependencies for the given class.
12087 auto const findAllDeps
= [&] (SString cls
,
12088 TSStringSet
& visited
,
12089 auto const& self
) -> const DepLookup
& {
12090 static const DepLookup empty
;
12092 auto const it
= meta
.cls
.find(cls
);
12093 if (it
== meta
.cls
.end()) {
12095 4, "{} is not instantiable because it is missing\n",
12101 // The class exists, so look up it's dependency information.
12102 auto const idx
= it
->second
.idx
;
12103 auto const& deps
= it
->second
.deps
;
12105 // Check for cycles. A class involved in cyclic inheritance is not
12106 // instantiable (and has no dependencies). This needs to be done
12107 // before accessing the LockFreeLazy below, because if we are in a
12108 // cycle, we'll deadlock when we do so.
12109 auto const emplaced
= visited
.emplace(cls
).second
;
12112 4, "{} is not instantiable because it forms a dependency "
12113 "cycle with itself\n", cls
12115 it
->second
.uninstantiable
= true;
12118 SCOPE_EXIT
{ visited
.erase(cls
); };
12120 assertx(idx
< allDeps
.size());
12121 return allDeps
[idx
].get(
12123 // Otherwise get all of the transitive dependencies of it's
12124 // dependencies and combine them.
12126 out
.instantiable
= !it
->second
.uninstantiable
;
12128 for (auto const d
: deps
) {
12129 auto const& lookup
= self(d
, visited
, self
);
12130 if (lookup
.instantiable
|| meta
.cls
.count(d
)) {
12131 out
.deps
.emplace(d
);
12133 out
.deps
.insert(begin(lookup
.deps
), end(lookup
.deps
));
12134 if (lookup
.instantiable
) continue;
12135 // If the dependency is not instantiable, this isn't
12136 // either. Note, however, we still need to preserve the
12137 // already gathered dependencies, since they'll have to be
12138 // placed in some bucket.
12139 if (out
.instantiable
) {
12141 4, "{} is not instantiable because it depends on {}, "
12142 "which is not instantiable\n",
12145 it
->second
.uninstantiable
= true;
12147 out
.instantiable
= false;
12155 constexpr size_t kBucketSize
= 2000;
12156 constexpr size_t kMaxBucketSize
= 30000;
12158 auto assignments
= assign_hierarchical_work(
12160 std::vector
<SString
> l
;
12161 auto const size
= meta
.allCls
.size();
12162 assertx(size
== isNotLeaf
.size());
12164 for (size_t i
= 0; i
< size
; ++i
) {
12165 if (!isNotLeaf
[i
]) l
.emplace_back(meta
.allCls
[i
]);
12169 meta
.allCls
.size(),
12173 TSStringSet visited
;
12174 auto const& lookup
= findAllDeps(c
, visited
, findAllDeps
);
12175 return std::make_pair(&lookup
.deps
, lookup
.instantiable
);
12177 [&] (const TSStringSet
&, size_t, SString c
) -> Optional
<size_t> {
12178 return meta
.cls
.at(c
).idx
;
12182 // Bucketize functions separately
12184 constexpr size_t kFuncBucketSize
= 5000;
12186 auto funcBuckets
= consistently_bucketize(meta
.allFuncs
, kFuncBucketSize
);
12188 std::vector
<FlattenClassesWork
> work
;
12189 // If both the class and func assignments map to a single bucket,
12190 // combine them both together. This is an optimization for things
12191 // like unit tests, where the total amount of work is low and we
12192 // want to run it all in a single job if possible.
12193 if (assignments
.size() == 1 && funcBuckets
.size() == 1) {
12195 FlattenClassesWork
{
12196 std::move(assignments
[0].classes
),
12197 std::move(assignments
[0].deps
),
12198 std::move(funcBuckets
[0]),
12199 std::move(assignments
[0].uninstantiable
)
12203 // Otherwise split the classes and func work.
12204 work
.reserve(assignments
.size() + funcBuckets
.size());
12205 for (auto& assignment
: assignments
) {
12207 FlattenClassesWork
{
12208 std::move(assignment
.classes
),
12209 std::move(assignment
.deps
),
12211 std::move(assignment
.uninstantiable
)
12215 for (auto& bucket
: funcBuckets
) {
12217 FlattenClassesWork
{ {}, {}, std::move(bucket
), {} }
12222 if (Trace::moduleEnabled(Trace::hhbbc_index
, 5)) {
12223 for (size_t i
= 0; i
< work
.size(); ++i
) {
12224 auto const& [classes
, deps
, funcs
, uninstantiable
] = work
[i
];
12225 FTRACE(5, "flatten work item #{}:\n", i
);
12226 FTRACE(5, " classes ({}):\n", classes
.size());
12227 for (auto const DEBUG_ONLY c
: classes
) FTRACE(5, " {}\n", c
);
12228 FTRACE(5, " deps ({}):\n", deps
.size());
12229 for (auto const DEBUG_ONLY d
: deps
) FTRACE(5, " {}\n", d
);
12230 FTRACE(5, " funcs ({}):\n", funcs
.size());
12231 for (auto const DEBUG_ONLY f
: funcs
) FTRACE(5, " {}\n", f
);
12232 FTRACE(5, " uninstantiable classes ({}):\n", uninstantiable
.size());
12233 for (auto const DEBUG_ONLY c
: uninstantiable
) FTRACE(5, " {}\n", c
);
12240 // Metadata used to assign work buckets for building subclasses. This
12241 // is produced from flattening classes. We don't put closures (or
12242 // Closure base class) into here. There's a lot of them, but we can
12243 // predict their results without running build subclass pass on them.
12244 struct SubclassMetadata
{
12245 // Immediate children and parents of class (not transitive!).
12247 std::vector
<SString
> children
;
12248 std::vector
<SString
> parents
;
12249 size_t idx
; // Index into all classes vector.
12251 TSStringToOneT
<Meta
> meta
;
12252 // All classes to be processed
12253 std::vector
<SString
> all
;
12256 // Metadata used to drive the init-types pass. This is produced from
12257 // flattening classes and added to when building subclasses.
12258 struct InitTypesMetadata
{
12260 // Dependencies of the class. A dependency is a class in a
12261 // property/param/return type-hint.
12263 TSStringSet candidateRegOnlyEquivs
;
12266 // Same as ClsMeta, but for the func
12269 // Modifications to make to an unit
12271 std::vector
<SString
> addClass
;
12272 std::vector
<SString
> removeFunc
;
12273 template <typename SerDe
> void serde(SerDe
& sd
) {
12274 sd(addClass
)(removeFunc
);
12277 TSStringToOneT
<ClsMeta
> classes
;
12278 FSStringToOneT
<FuncMeta
> funcs
;
12279 SStringToOneT
<Fixup
> fixups
;
12280 SStringToOneT
<std::vector
<FuncFamilyEntry
>> nameOnlyFF
;
12283 std::tuple
<SubclassMetadata
, InitTypesMetadata
, std::vector
<InterfaceConflicts
>>
12284 flatten_classes(IndexData
& index
, IndexFlattenMetadata meta
) {
12285 trace_time
trace("flatten classes", index
.sample
);
12286 trace
.ignore_client_stats();
12288 using namespace folly::gen
;
12290 struct ClassUpdate
{
12292 UniquePtrRef
<php::Class
> cls
;
12293 UniquePtrRef
<php::ClassBytecode
> bytecode
;
12294 UniquePtrRef
<ClassInfo2
> cinfo
;
12295 SString unitToAddTo
;
12296 TSStringSet typeUses
;
12297 bool isInterface
{false};
12298 bool has86init
{false};
12299 CompactVector
<SString
> parents
;
12301 struct FuncUpdate
{
12303 UniquePtrRef
<php::Func
> func
;
12304 UniquePtrRef
<FuncInfo2
> finfo
;
12305 TSStringSet typeUses
;
12307 struct ClosureUpdate
{
12312 struct MethodUpdate
{
12314 UniquePtrRef
<MethodsWithoutCInfo
> methods
;
12317 boost::variant
<ClassUpdate
, FuncUpdate
, ClosureUpdate
, MethodUpdate
>;
12318 using UpdateVec
= std::vector
<Update
>;
12320 tbb::concurrent_hash_map
<
12322 InterfaceConflicts
,
12323 string_data_hash_tsame
12326 auto const run
= [&] (FlattenClassesWork work
) -> coro::Task
<UpdateVec
> {
12327 co_await
coro::co_reschedule_on_current_executor
;
12329 if (work
.classes
.empty() &&
12330 work
.funcs
.empty() &&
12331 work
.uninstantiable
.empty()) {
12332 assertx(work
.deps
.empty());
12333 co_return UpdateVec
{};
12336 Client::ExecMetadata metadata
{
12337 .job_key
= folly::sformat(
12338 "flatten classes {}",
12339 work
.classes
.empty()
12340 ? (work
.uninstantiable
.empty()
12342 : work
.uninstantiable
[0])
12346 auto classes
= from(work
.classes
)
12347 | map([&] (SString c
) { return index
.classRefs
.at(c
); })
12348 | as
<std::vector
>();
12349 auto deps
= from(work
.deps
)
12350 | map([&] (SString c
) { return index
.classRefs
.at(c
); })
12351 | as
<std::vector
>();
12352 auto bytecode
= (from(work
.classes
) + from(work
.deps
))
12353 | map([&] (SString c
) { return index
.classBytecodeRefs
.at(c
); })
12354 | as
<std::vector
>();
12355 auto funcs
= from(work
.funcs
)
12356 | map([&] (SString f
) { return index
.funcRefs
.at(f
); })
12357 | as
<std::vector
>();
12358 auto uninstantiableRefs
= from(work
.uninstantiable
)
12359 | map([&] (SString c
) { return index
.classRefs
.at(c
); })
12360 | as
<std::vector
>();
12362 // Gather any type-mappings or missing types referenced by these
12363 // classes or funcs.
12364 std::vector
<TypeMapping
> typeMappings
;
12365 std::vector
<SString
> missingTypes
;
12369 auto const addUnresolved
= [&] (SString u
) {
12370 if (!seen
.emplace(u
).second
) return;
12371 if (auto const m
= folly::get_ptr(meta
.typeMappings
, u
)) {
12372 // If the type-mapping maps an enum, and that enum is
12373 // uninstantiable, just treat it as a missing type.
12374 if (m
->firstEnum
&& meta
.cls
.at(m
->firstEnum
).uninstantiable
) {
12375 missingTypes
.emplace_back(u
);
12377 typeMappings
.emplace_back(*m
);
12379 } else if (!index
.classRefs
.count(u
) ||
12380 meta
.cls
.at(u
).uninstantiable
) {
12381 missingTypes
.emplace_back(u
);
12385 auto const addClass
= [&] (SString c
) {
12386 for (auto const u
: meta
.cls
.at(c
).unresolvedTypes
) addUnresolved(u
);
12388 auto const addFunc
= [&] (SString f
) {
12389 for (auto const u
: meta
.func
.at(f
).unresolvedTypes
) addUnresolved(u
);
12392 for (auto const c
: work
.classes
) addClass(c
);
12393 for (auto const d
: work
.deps
) addClass(d
);
12394 for (auto const f
: work
.funcs
) addFunc(f
);
12397 begin(typeMappings
), end(typeMappings
),
12398 [] (const TypeMapping
& a
, const TypeMapping
& b
) {
12399 return string_data_lt_type
{}(a
.name
, b
.name
);
12402 std::sort(begin(missingTypes
), end(missingTypes
), string_data_lt_type
{});
12405 auto [typeMappingsRef
, missingTypesRef
, config
] = co_await
12407 index
.client
->store(std::move(typeMappings
)),
12408 index
.client
->store(std::move(missingTypes
)),
12409 index
.configRef
->getCopy()
12412 auto results
= co_await
12413 index
.client
->exec(
12418 std::move(classes
),
12420 std::move(bytecode
),
12422 std::move(uninstantiableRefs
),
12423 std::move(typeMappingsRef
),
12424 std::move(missingTypesRef
)
12427 std::move(metadata
)
12429 // Every flattening job is a single work-unit, so we should only
12430 // ever get one result for each one.
12431 assertx(results
.size() == 1);
12432 auto& [clsRefs
, bytecodeRefs
, cinfoRefs
, funcRefs
,
12433 finfoRefs
, methodRefs
, classMetaRef
] = results
[0];
12434 assertx(clsRefs
.size() == cinfoRefs
.size());
12435 assertx(clsRefs
.size() == bytecodeRefs
.size());
12436 assertx(funcRefs
.size() == work
.funcs
.size());
12437 assertx(funcRefs
.size() == finfoRefs
.size());
12439 // We need the output metadata, but everything else stays
12441 auto clsMeta
= co_await index
.client
->load(std::move(classMetaRef
));
12442 assertx(methodRefs
.size() == clsMeta
.uninstantiable
.size());
12444 // Create the updates by combining the job output (but skipping
12445 // over uninstantiable classes).
12447 updates
.reserve(work
.classes
.size() * 3);
12449 size_t outputIdx
= 0;
12450 size_t parentIdx
= 0;
12451 size_t methodIdx
= 0;
12452 for (auto const name
: work
.classes
) {
12453 if (clsMeta
.uninstantiable
.count(name
)) {
12454 assertx(methodIdx
< methodRefs
.size());
12455 updates
.emplace_back(
12456 MethodUpdate
{ name
, std::move(methodRefs
[methodIdx
]) }
12461 assertx(outputIdx
< clsRefs
.size());
12462 assertx(outputIdx
< clsMeta
.classTypeUses
.size());
12464 auto const& flattenMeta
= meta
.cls
.at(name
);
12465 updates
.emplace_back(
12468 std::move(clsRefs
[outputIdx
]),
12469 std::move(bytecodeRefs
[outputIdx
]),
12470 std::move(cinfoRefs
[outputIdx
]),
12472 std::move(clsMeta
.classTypeUses
[outputIdx
]),
12473 (bool)clsMeta
.interfaces
.count(name
),
12474 (bool)clsMeta
.with86init
.count(name
)
12478 // Ignore closures. We don't run the build subclass pass for
12479 // closures, so we don't need information for them.
12480 if (!flattenMeta
.isClosure
) {
12481 assertx(parentIdx
< clsMeta
.parents
.size());
12482 auto const& parents
= clsMeta
.parents
[parentIdx
].names
;
12483 auto& update
= boost::get
<ClassUpdate
>(updates
.back());
12484 update
.parents
.insert(
12485 end(update
.parents
), begin(parents
), end(parents
)
12492 assertx(outputIdx
== clsRefs
.size());
12493 assertx(outputIdx
== clsMeta
.classTypeUses
.size());
12495 for (auto const& [unit
, name
, context
] : clsMeta
.newClosures
) {
12496 updates
.emplace_back(ClosureUpdate
{ name
, context
, unit
});
12499 for (auto const name
: work
.uninstantiable
) {
12500 assertx(clsMeta
.uninstantiable
.count(name
));
12501 assertx(methodIdx
< methodRefs
.size());
12502 updates
.emplace_back(
12503 MethodUpdate
{ name
, std::move(methodRefs
[methodIdx
]) }
12507 assertx(methodIdx
== methodRefs
.size());
12509 assertx(work
.funcs
.size() == clsMeta
.funcTypeUses
.size());
12510 for (size_t i
= 0, size
= work
.funcs
.size(); i
< size
; ++i
) {
12511 updates
.emplace_back(
12514 std::move(funcRefs
[i
]),
12515 std::move(finfoRefs
[i
]),
12516 std::move(clsMeta
.funcTypeUses
[i
])
12521 for (auto const& c
: clsMeta
.interfaceConflicts
) {
12522 decltype(ifaceConflicts
)::accessor acc
;
12523 ifaceConflicts
.insert(acc
, c
.name
);
12524 acc
->second
.name
= c
.name
;
12525 acc
->second
.usage
+= c
.usage
;
12526 acc
->second
.conflicts
.insert(begin(c
.conflicts
), end(c
.conflicts
));
12532 // Calculate the grouping of classes into work units for flattening,
12533 // perform the flattening, and gather all updates from the jobs.
12534 auto allUpdates
= [&] {
12535 auto assignments
= flatten_classes_assign(meta
);
12537 trace_time
trace2("flatten classes work", index
.sample
);
12538 return coro::blockingWait(coro::collectAllRange(
12541 | map([&] (FlattenClassesWork w
) {
12542 return run(std::move(w
)).scheduleOn(index
.executor
->sticky());
12544 | as
<std::vector
>()
12548 // Now take the updates and apply them to the Index tables. This
12549 // needs to be done in a single threaded context (per data
12550 // structure). This also gathers up all the fixups needed.
12552 SubclassMetadata subclassMeta
;
12553 InitTypesMetadata initTypesMeta
;
12556 trace_time
trace2("flatten classes update");
12557 trace2
.ignore_client_stats();
12559 parallel::parallel(
12561 for (auto& updates
: allUpdates
) {
12562 for (auto& update
: updates
) {
12563 auto u
= boost::get
<ClassUpdate
>(&update
);
12565 index
.classRefs
.insert_or_assign(
12573 for (auto& updates
: allUpdates
) {
12574 for (auto& update
: updates
) {
12575 auto u
= boost::get
<ClassUpdate
>(&update
);
12578 index
.classInfoRefs
.emplace(
12580 std::move(u
->cinfo
)
12587 for (auto& updates
: allUpdates
) {
12588 for (auto& update
: updates
) {
12589 auto u
= boost::get
<ClassUpdate
>(&update
);
12591 index
.classBytecodeRefs
.insert_or_assign(
12593 std::move(u
->bytecode
)
12599 for (auto& updates
: allUpdates
) {
12600 for (auto& update
: updates
) {
12601 auto u
= boost::get
<FuncUpdate
>(&update
);
12603 index
.funcRefs
.at(u
->name
) = std::move(u
->func
);
12608 for (auto& updates
: allUpdates
) {
12609 for (auto& update
: updates
) {
12610 auto u
= boost::get
<FuncUpdate
>(&update
);
12613 index
.funcInfoRefs
.emplace(
12615 std::move(u
->finfo
)
12622 for (auto& updates
: allUpdates
) {
12623 for (auto& update
: updates
) {
12624 // Keep closure mappings up to date.
12625 auto u
= boost::get
<ClosureUpdate
>(&update
);
12627 initTypesMeta
.fixups
[u
->unit
].addClass
.emplace_back(u
->name
);
12628 assertx(u
->context
);
12630 index
.closureToClass
.emplace(u
->name
, u
->context
).second
12632 index
.classToClosures
[u
->context
].emplace(u
->name
);
12635 for (auto& [unit
, deletions
] : meta
.unitDeletions
) {
12636 initTypesMeta
.fixups
[unit
].removeFunc
= std::move(deletions
);
12640 // A class which didn't have an 86*init function previously
12641 // can gain one due to trait flattening. Update that here.
12642 for (auto const& updates
: allUpdates
) {
12643 for (auto const& update
: updates
) {
12644 auto u
= boost::get
<ClassUpdate
>(&update
);
12645 if (!u
|| !u
->has86init
) continue;
12646 index
.classesWith86Inits
.emplace(u
->name
);
12651 // Build metadata for the next build subclass pass.
12652 auto& all
= subclassMeta
.all
;
12653 auto& meta
= subclassMeta
.meta
;
12654 for (auto& updates
: allUpdates
) {
12655 for (auto& update
: updates
) {
12656 auto u
= boost::get
<ClassUpdate
>(&update
);
12659 // We shouldn't have parents for closures because we
12660 // special case those explicitly.
12661 if (is_closure_name(u
->name
) || is_closure_base(u
->name
)) {
12662 assertx(u
->parents
.empty());
12665 // Otherwise build the children lists from the parents.
12666 all
.emplace_back(u
->name
);
12667 for (auto const p
: u
->parents
) {
12668 meta
[p
].children
.emplace_back(u
->name
);
12670 auto& parents
= meta
[u
->name
].parents
;
12671 assertx(parents
.empty());
12674 begin(u
->parents
), end(u
->parents
)
12679 std::sort(begin(all
), end(all
), string_data_lt_type
{});
12680 // Make sure there's no duplicates:
12681 assertx(std::adjacent_find(begin(all
), end(all
)) == end(all
));
12683 for (size_t i
= 0; i
< all
.size(); ++i
) meta
[all
[i
]].idx
= i
;
12686 for (auto& updates
: allUpdates
) {
12687 for (auto& update
: updates
) {
12688 auto u
= boost::get
<MethodUpdate
>(&update
);
12691 index
.uninstantiableClsMethRefs
.emplace(
12693 std::move(u
->methods
)
12700 for (auto& updates
: allUpdates
) {
12701 for (auto& update
: updates
) {
12702 if (auto const u
= boost::get
<ClassUpdate
>(&update
)) {
12703 auto& meta
= initTypesMeta
.classes
[u
->name
];
12704 assertx(meta
.deps
.empty());
12705 meta
.deps
.insert(begin(u
->typeUses
), end(u
->typeUses
));
12706 } else if (auto const u
= boost::get
<FuncUpdate
>(&update
)) {
12707 auto& meta
= initTypesMeta
.funcs
[u
->name
];
12708 assertx(meta
.deps
.empty());
12709 meta
.deps
.insert(begin(u
->typeUses
), end(u
->typeUses
));
12717 return std::make_tuple(
12718 std::move(subclassMeta
),
12719 std::move(initTypesMeta
),
12721 std::vector
<InterfaceConflicts
> out
;
12722 out
.reserve(ifaceConflicts
.size());
12723 for (auto& [_
, c
] : ifaceConflicts
) out
.emplace_back(std::move(c
));
12729 //////////////////////////////////////////////////////////////////////
12733 * Subclass lists are built in a similar manner as flattening classes,
12734 * except the order is reversed.
12736 * However, there is one complication: the transitive children of each
12737 * class can be huge. In fact, for large hierarchies, they can easily
12738 * be too large to (efficiently) handle in a single job.
12740 * Rather than (always) processing everything in a single pass, we
12741 * might need to use multiple passes to keep the fan-in down. When
12742 * calculating the work buckets, we keep the size of each bucket into
12743 * account and don't allow any bucket to grow too large. If it does,
12744 * we'll just process that bucket, and process any dependencies in the
12747 * This isn't sufficient. A single class have (far) more direct
12748 * children than we want in a single bucket. Multiple passes don't
12749 * help here because there's no intermediate classes to use as an
12750 * output. To fix this, we insert "splits", which serve to "summarize"
12751 * some subset of a class' direct children.
12753 * For example, suppose a class has 10k direct children, and our
12754 * maximum bucket size is 1k. On the first pass we'll process all of
12755 * the children in ~10 different jobs, each one processing 1k of the
12756 * children, and producing a single split node. The second pass will
12757 * actually process the class and take all of the splits as inputs
12758 * (not the actual children). The inputs to the job has been reduced
12759 * from 10k to 10. This is a simplification. In reality a job can
12760 * produce multiple splits, and inputs can be a mix of splits and
12761 * actual classes. In extreme cases, you might need multiple rounds of
12762 * splits before processing the class.
12764 * There is one other difference between this and the flatten classes
12765 * pass. Unlike in flatten classes, every class (except leafs) are
12766 * "roots" here. We do not promote any dependencies. This causes more
12767 * work overall, but it lets us process more classes in parallel.
12771 * Extern-worker job to build ClassInfo2 subclass lists, and calculate
12772 * various properties on the ClassInfo2 from it.
12774 struct BuildSubclassListJob
{
12775 static std::string
name() { return "hhbbc-build-subclass"; }
12776 static void init(const Config
& config
) {
12777 process_init(config
.o
, config
.gd
, false);
12778 ClassGraph::init();
12780 static void fini() { ClassGraph::destroy(); }
12782 // Aggregated data for some group of classes. The data can either
12783 // come from a split node, or inferred from a group of classes.
12785 // Information about all of the methods with a particular name
12786 // between all of the classes in this Data.
12788 // Methods which are present on at least one regular class.
12789 MethRefSet regularMeths
;
12790 // Methods which are only present on non-regular classes, but is
12791 // private on at least one class. These are sometimes treated
12792 // like a regular class.
12793 MethRefSet nonRegularPrivateMeths
;
12794 // Methods which are only present on non-regular classes (and
12795 // never private). These three sets are always disjoint.
12796 MethRefSet nonRegularMeths
;
12798 Optional
<FuncFamily2::StaticInfo
> allStatic
;
12799 Optional
<FuncFamily2::StaticInfo
> regularStatic
;
12801 // Whether all classes in this Data have a method with this
12803 bool complete
{true};
12804 // Whether all regular classes in this Data have a method with
12806 bool regularComplete
{true};
12807 // Whether any of the methods has a private ancestor.
12808 bool privateAncestor
{false};
12810 template <typename SerDe
> void serde(SerDe
& sd
) {
12811 sd(regularMeths
, std::less
<MethRef
>{})
12812 (nonRegularPrivateMeths
, std::less
<MethRef
>{})
12813 (nonRegularMeths
, std::less
<MethRef
>{})
12822 SStringToOneT
<MethInfo
> methods
;
12824 // The name of properties which might have null values even if the
12825 // type-constraint doesn't allow it (due to system provided
12826 // initial values).
12827 SStringSet propsWithImplicitNullable
;
12829 // The classes for whom isMocked would be true due to one of the
12830 // classes making up this Data. The classes in this set may not
12831 // necessarily be also part of this Data.
12832 TSStringSet mockedClasses
;
12834 bool hasConstProp
{false};
12835 bool hasReifiedGeneric
{false};
12837 bool isSubMocked
{false};
12839 // The meaning of these differ depending on whether the ClassInfo
12840 // contains just it's info, or all of it's subclass info.
12841 bool hasRegularClass
{false};
12842 bool hasRegularClassFull
{false};
12844 template <typename SerDe
> void serde(SerDe
& sd
) {
12845 sd(methods
, string_data_lt
{})
12846 (propsWithImplicitNullable
, string_data_lt
{})
12847 (mockedClasses
, string_data_lt_type
{})
12849 (hasReifiedGeneric
)
12852 (hasRegularClassFull
)
12857 // Split node. Used to wrap a Data when summarizing some subset of a
12858 // class' children.
12861 Split(SString name
, SString cls
) : name
{name
}, cls
{cls
} {}
12865 CompactVector
<SString
> children
;
12866 CompactVector
<ClassGraph
> classGraphs
;
12869 template <typename SerDe
> void serde(SerDe
& sd
) {
12870 ScopedStringDataIndexer _
;
12871 ClassGraph::ScopedSerdeState _2
;
12875 (classGraphs
, nullptr)
12881 // Mark a dependency on a class to a split node. Since the splits
12882 // are not actually part of the hierarchy, the relationship between
12883 // classes and splits cannot be inferred otherwise.
12884 struct EdgeToSplit
{
12887 template <typename SerDe
> void serde(SerDe
& sd
) {
12894 // Job output meant to be downloaded and drive the next round.
12895 struct OutputMeta
{
12896 // For every input ClassInfo, the set of func families present in
12897 // that ClassInfo's method family table. If the ClassInfo is used
12898 // as a dep later, these func families need to be provided as
12900 std::vector
<hphp_fast_set
<FuncFamily2::Id
>> funcFamilyDeps
;
12901 // The ids of new (not provided as an input) func families
12902 // produced. The ids are grouped together to become
12903 // FuncFamilyGroups.
12904 std::vector
<std::vector
<FuncFamily2::Id
>> newFuncFamilyIds
;
12905 // Func family entries corresponding to all methods with a
12906 // particular name encountered in this job. Multiple jobs will
12907 // generally produce func family entries for the same name, so
12908 // they must be aggregated together afterwards.
12909 std::vector
<std::pair
<SString
, FuncFamilyEntry
>> nameOnly
;
12910 std::vector
<std::vector
<SString
>> regOnlyEquivCandidates
;
12912 // For every output class, the set of classes which that class has
12913 // inherited class constants from.
12914 TSStringToOneT
<TSStringSet
> cnsBases
;
12916 template <typename SerDe
> void serde(SerDe
& sd
) {
12917 ScopedStringDataIndexer _
;
12918 sd(funcFamilyDeps
, std::less
<FuncFamily2::Id
>{})
12921 (regOnlyEquivCandidates
)
12922 (cnsBases
, string_data_lt_type
{}, string_data_lt_type
{})
12926 using Output
= Multi
<
12927 Variadic
<std::unique_ptr
<ClassInfo2
>>,
12928 Variadic
<std::unique_ptr
<Split
>>,
12929 Variadic
<std::unique_ptr
<php::Class
>>,
12930 Variadic
<FuncFamilyGroup
>,
12931 Variadic
<std::unique_ptr
<ClassInfo2
>>,
12935 // Each job takes the list of classes and splits which should be
12936 // produced, dependency classes and splits (which are not updated),
12937 // edges between classes and splits, and func families (needed by
12938 // dependency classes). Leafs are like deps, except they'll be
12939 // considered as part of calculating the name-only func families
12940 // because its possible for them to be disjoint from classes.
12941 // (normal deps are not considered as their data is guaranteed to
12942 // be included by a class).
12944 run(Variadic
<std::unique_ptr
<ClassInfo2
>> classes
,
12945 Variadic
<std::unique_ptr
<ClassInfo2
>> deps
,
12946 Variadic
<std::unique_ptr
<ClassInfo2
>> leafs
,
12947 Variadic
<std::unique_ptr
<Split
>> splits
,
12948 Variadic
<std::unique_ptr
<Split
>> splitDeps
,
12949 Variadic
<std::unique_ptr
<php::Class
>> phpClasses
,
12950 Variadic
<EdgeToSplit
> edges
,
12951 Variadic
<FuncFamilyGroup
> funcFamilies
) {
12952 // Store mappings of names to classes and edges.
12956 for (auto const& cinfo
: classes
.vals
) {
12957 always_assert(!cinfo
->classGraph
.isMissing());
12958 always_assert(!cinfo
->classGraph
.hasCompleteChildren());
12959 always_assert(!cinfo
->classGraph
.isConservative());
12961 for (auto const& cinfo
: leafs
.vals
) {
12962 always_assert(!cinfo
->classGraph
.isMissing());
12963 always_assert(!cinfo
->classGraph
.hasCompleteChildren());
12964 always_assert(!cinfo
->classGraph
.isConservative());
12966 for (auto const& cinfo
: deps
.vals
) {
12967 always_assert(!cinfo
->classGraph
.isMissing());
12969 for (auto const& split
: splits
.vals
) {
12970 for (auto const child
: split
->classGraphs
) {
12971 always_assert(!child
.isMissing());
12972 always_assert(!child
.hasCompleteChildren());
12973 always_assert(!child
.isConservative());
12976 for (auto const& split
: splitDeps
.vals
) {
12977 for (auto const child
: split
->classGraphs
) {
12978 always_assert(!child
.isMissing());
12979 always_assert(child
.hasCompleteChildren() ||
12980 child
.isConservative());
12985 for (auto& cinfo
: classes
.vals
) {
12986 assertx(!is_closure_name(cinfo
->name
));
12988 index
.classInfos
.emplace(cinfo
->name
, cinfo
.get()).second
12990 index
.top
.emplace(cinfo
->name
);
12992 for (auto& cinfo
: deps
.vals
) {
12993 assertx(!is_closure_name(cinfo
->name
));
12995 index
.classInfos
.emplace(cinfo
->name
, cinfo
.get()).second
12998 for (auto& cinfo
: leafs
.vals
) {
12999 assertx(!is_closure_name(cinfo
->name
));
13001 index
.classInfos
.emplace(cinfo
->name
, cinfo
.get()).second
13004 for (auto& split
: splits
.vals
) {
13006 index
.splits
.emplace(split
->name
, split
.get()).second
13008 index
.top
.emplace(split
->name
);
13010 for (auto& split
: splitDeps
.vals
) {
13011 assertx(split
->children
.empty());
13013 index
.splits
.emplace(split
->name
, split
.get()).second
13016 for (auto& cls
: phpClasses
.vals
) {
13017 assertx(!is_closure(*cls
));
13019 index
.classes
.emplace(cls
->name
, cls
.get()).second
13022 for (auto& group
: funcFamilies
.vals
) {
13023 for (auto& ff
: group
.m_ffs
) {
13024 auto const id
= ff
->m_id
;
13025 // We could have multiple groups which contain the same
13026 // FuncFamily, so don't assert uniqueness here. We'll just
13027 // take the first one we see (they should all be equivalent).
13028 index
.funcFamilies
.emplace(id
, std::move(ff
));
13032 index
.aggregateData
.reserve(index
.top
.size());
13036 // Mark all of the classes (including leafs) as being complete
13037 // since their subclass lists are correct.
13038 for (auto& cinfo
: leafs
.vals
) {
13039 if (cinfo
->classGraph
.hasCompleteChildren()) continue;
13040 cinfo
->classGraph
.setComplete();
13042 for (auto& cinfo
: classes
.vals
) {
13043 if (cinfo
->classGraph
.hasCompleteChildren() ||
13044 cinfo
->classGraph
.isConservative()) {
13047 cinfo
->classGraph
.setComplete();
13049 for (auto& cinfo
: deps
.vals
) {
13050 if (cinfo
->classGraph
.hasCompleteChildren() ||
13051 cinfo
->classGraph
.isConservative()) {
13054 cinfo
->classGraph
.setComplete();
13056 for (auto& split
: splits
.vals
) {
13057 for (auto child
: split
->classGraphs
) {
13058 if (child
.hasCompleteChildren() ||
13059 child
.isConservative()) {
13062 child
.setComplete();
13066 // Store the regular-only equivalent classes in the output
13067 // metadata. This will be used in the init-types pass.
13068 meta
.regOnlyEquivCandidates
.reserve(classes
.vals
.size());
13069 for (auto& cinfo
: classes
.vals
) {
13070 meta
.regOnlyEquivCandidates
.emplace_back();
13071 auto& candidates
= meta
.regOnlyEquivCandidates
.back();
13072 for (auto const g
: cinfo
->classGraph
.candidateRegOnlyEquivs()) {
13073 candidates
.emplace_back(g
.name());
13077 // If there's no classes or splits, this job is doing nothing but
13078 // calculating name only func family entries (so should have at
13079 // least one leaf).
13080 if (!index
.top
.empty()) {
13081 build_children(index
, edges
.vals
);
13082 process_roots(index
, classes
.vals
, splits
.vals
);
13084 assertx(classes
.vals
.empty());
13085 assertx(splits
.vals
.empty());
13086 assertx(index
.splits
.empty());
13087 assertx(index
.funcFamilies
.empty());
13088 assertx(!leafs
.vals
.empty());
13089 assertx(!index
.classInfos
.empty());
13093 make_name_only_method_entries(index
, classes
.vals
, leafs
.vals
);
13095 // Record dependencies for each input class. A func family is a
13096 // dependency of the class if it appears in the method families
13098 meta
.funcFamilyDeps
.reserve(classes
.vals
.size());
13099 for (auto const& cinfo
: classes
.vals
) {
13100 meta
.funcFamilyDeps
.emplace_back();
13101 auto& deps
= meta
.funcFamilyDeps
.back();
13102 for (auto const& [_
, entry
] : cinfo
->methodFamilies
) {
13105 [&] (const FuncFamilyEntry::BothFF
& e
) { deps
.emplace(e
.m_ff
); },
13106 [&] (const FuncFamilyEntry::FFAndSingle
& e
) { deps
.emplace(e
.m_ff
); },
13107 [&] (const FuncFamilyEntry::FFAndNone
& e
) { deps
.emplace(e
.m_ff
); },
13108 [&] (const FuncFamilyEntry::BothSingle
&) {},
13109 [&] (const FuncFamilyEntry::SingleAndNone
&) {},
13110 [&] (const FuncFamilyEntry::None
&) {}
13115 Variadic
<FuncFamilyGroup
> funcFamilyGroups
;
13116 group_func_families(index
, funcFamilyGroups
.vals
, meta
.newFuncFamilyIds
);
13118 auto const addCnsBase
= [&] (const ClassInfo2
& cinfo
) {
13119 auto& bases
= meta
.cnsBases
[cinfo
.name
];
13120 for (auto const& [_
, idx
] : cinfo
.clsConstants
) {
13121 if (!cinfo
.name
->tsame(idx
.idx
.cls
)) bases
.emplace(idx
.idx
.cls
);
13124 for (auto const& cinfo
: classes
.vals
) addCnsBase(*cinfo
);
13125 for (auto const& cinfo
: leafs
.vals
) addCnsBase(*cinfo
);
13127 // We only need to provide php::Class which correspond to a class
13128 // which wasn't a dep.
13129 phpClasses
.vals
.erase(
13131 begin(phpClasses
.vals
),
13132 end(phpClasses
.vals
),
13133 [&] (const std::unique_ptr
<php::Class
>& c
) {
13134 return !index
.top
.count(c
->name
);
13137 end(phpClasses
.vals
)
13140 return std::make_tuple(
13141 std::move(classes
),
13143 std::move(phpClasses
),
13144 std::move(funcFamilyGroups
),
13155 * When building a func family, MethRefSets must be sorted, then
13156 * hashed in order to generate the unique id. Once we do so, we can
13157 * then check if that func family already exists. For large func
13158 * families, this can very expensive and we might have to do this
13159 * (wasted) work multiple times.
13161 * To avoid this, we add a cache before the sorting/hashing
13162 * step. Instead of using a func family id (which is the expensive
13163 * thing to generate), the cache is keyed by the set of methods
13164 * directly. A commutative hash is used so that we don't actually
13165 * have to sort the MethRefSets, and equality is just equality of
13166 * the MethRefSets. Moreover, we make use of hetereogenous lookup to
13167 * avoid having to copy any MethRefSets (again they can be large)
13168 * when doing the lookup.
13171 // What is actually stored in the cache. Keeps a copy of the
13173 struct MethInfoTuple
{
13174 MethRefSet regular
;
13175 MethRefSet nonRegularPrivate
;
13176 MethRefSet nonRegular
;
13178 // Used for lookups. Just has pointers to the MethRefSets, so we
13179 // don't have to do any copying for the lookup.
13180 struct MethInfoTupleProxy
{
13181 const MethRefSet
* regular
;
13182 const MethRefSet
* nonRegularPrivate
;
13183 const MethRefSet
* nonRegular
;
13186 struct MethInfoTupleHasher
{
13187 using is_transparent
= void;
13189 size_t operator()(const MethInfoTuple
& t
) const {
13190 auto const h1
= folly::hash::commutative_hash_combine_range_generic(
13191 0, MethRef::Hash
{}, begin(t
.regular
), end(t
.regular
)
13193 auto const h2
= folly::hash::commutative_hash_combine_range_generic(
13194 0, MethRef::Hash
{}, begin(t
.nonRegularPrivate
), end(t
.nonRegularPrivate
)
13196 auto const h3
= folly::hash::commutative_hash_combine_range_generic(
13197 0, MethRef::Hash
{}, begin(t
.nonRegular
), end(t
.nonRegular
)
13199 return folly::hash::hash_combine(h1
, h2
, h3
);
13201 size_t operator()(const MethInfoTupleProxy
& t
) const {
13202 auto const h1
= folly::hash::commutative_hash_combine_range_generic(
13203 0, MethRef::Hash
{}, begin(*t
.regular
), end(*t
.regular
)
13205 auto const h2
= folly::hash::commutative_hash_combine_range_generic(
13206 0, MethRef::Hash
{}, begin(*t
.nonRegularPrivate
), end(*t
.nonRegularPrivate
)
13208 auto const h3
= folly::hash::commutative_hash_combine_range_generic(
13209 0, MethRef::Hash
{}, begin(*t
.nonRegular
), end(*t
.nonRegular
)
13211 return folly::hash::hash_combine(h1
, h2
, h3
);
13214 struct MethInfoTupleEquals
{
13215 using is_transparent
= void;
13217 bool operator()(const MethInfoTuple
& t1
, const MethInfoTuple
& t2
) const {
13219 t1
.regular
== t2
.regular
&&
13220 t1
.nonRegularPrivate
== t2
.nonRegularPrivate
&&
13221 t1
.nonRegular
== t2
.nonRegular
;
13223 bool operator()(const MethInfoTupleProxy
& t1
,
13224 const MethInfoTuple
& t2
) const {
13226 *t1
.regular
== t2
.regular
&&
13227 *t1
.nonRegularPrivate
== t2
.nonRegularPrivate
&&
13228 *t1
.nonRegular
== t2
.nonRegular
;
13232 struct LocalIndex
{
13233 // All ClassInfos, whether inputs or dependencies.
13234 TSStringToOneT
<ClassInfo2
*> classInfos
;
13235 // All splits, whether inputs or dependencies.
13236 TSStringToOneT
<Split
*> splits
;
13237 // All php::Class, whether inputs or dependencies.
13238 TSStringToOneT
<php::Class
*> classes
;
13240 // ClassInfos and splits which are inputs (IE, we want to
13241 // calculate data for).
13244 // Aggregated data for an input
13245 TSStringToOneT
<Data
> aggregateData
;
13247 // Mapping of input ClassInfos/splits to all of their subclasses
13248 // present in this Job. Some of the children may be splits, which
13249 // means some subset of the children were processed in another
13251 TSStringToOneT
<std::vector
<SString
>> children
;
13253 // The leafs in this job. This isn't necessarily an actual leaf,
13254 // but one whose children haven't been provided in this job
13255 // (because they've already been processed). Classes which are
13256 // leafs have information in their ClassInfo2 which reflect all of
13257 // their subclasses, otherwise just their own information.
13260 // All func families available in this Job, either from inputs, or
13261 // created during processing.
13262 hphp_fast_map
<FuncFamily2::Id
, std::unique_ptr
<FuncFamily2
>> funcFamilies
;
13264 // Above mentioned func family cache. If an entry is present here,
13265 // we know the func family already exists and don't need to do
13266 // expensive sorting/hashing.
13270 MethInfoTupleHasher
,
13271 MethInfoTupleEquals
13274 // funcFamilies contains all func families. If a func family is
13275 // created during processing, it will be inserted here (used to
13276 // determine outputs).
13277 std::vector
<FuncFamily2::Id
> newFuncFamilies
;
13279 php::Class
& cls(SString name
) {
13280 auto const it
= classes
.find(name
);
13281 always_assert(it
!= end(classes
));
13282 return *it
->second
;
13286 // Take all of the func families produced by this job and group them
13287 // together into FuncFamilyGroups. We produce both the
13288 // FuncFamilyGroups themselves, but also the associated ids in each
13289 // group (which will be output as metadata).
13290 static void group_func_families(
13292 std::vector
<FuncFamilyGroup
>& groups
,
13293 std::vector
<std::vector
<FuncFamily2::Id
>>& ids
13295 constexpr size_t kGroupSize
= 5000;
13297 // The grouping algorithm is very simple. First we sort all of the
13298 // func families by size. We then just group adjacent families
13299 // until their total size exceeds some threshold. Once it does, we
13300 // start a new group.
13302 begin(index
.newFuncFamilies
),
13303 end(index
.newFuncFamilies
),
13304 [&] (const FuncFamily2::Id
& id1
, const FuncFamily2::Id
& id2
) {
13305 auto const& ff1
= index
.funcFamilies
.at(id1
);
13306 auto const& ff2
= index
.funcFamilies
.at(id2
);
13308 ff1
->m_regular
.size() +
13309 ff1
->m_nonRegularPrivate
.size() +
13310 ff1
->m_nonRegular
.size();
13312 ff2
->m_regular
.size() +
13313 ff2
->m_nonRegularPrivate
.size() +
13314 ff2
->m_nonRegular
.size();
13315 if (size1
!= size2
) return size1
< size2
;
13320 size_t current
= 0;
13321 for (auto const& id
: index
.newFuncFamilies
) {
13322 auto& ff
= index
.funcFamilies
.at(id
);
13324 ff
->m_regular
.size() +
13325 ff
->m_nonRegularPrivate
.size() +
13326 ff
->m_nonRegular
.size();
13327 if (groups
.empty() || current
> kGroupSize
) {
13328 groups
.emplace_back();
13329 ids
.emplace_back();
13332 groups
.back().m_ffs
.emplace_back(std::move(ff
));
13333 ids
.back().emplace_back(id
);
13338 // Produce a set of name-only func families from the given set of
13339 // roots and leafs. It is assumed that any roots have already been
13340 // processed by process_roots, so that they'll have any appropriate
13341 // method families on them. Only entries which are "first name" are
13343 static std::vector
<std::pair
<SString
, FuncFamilyEntry
>>
13344 make_name_only_method_entries(
13346 const std::vector
<std::unique_ptr
<ClassInfo2
>>& roots
,
13347 const std::vector
<std::unique_ptr
<ClassInfo2
>>& leafs
13349 SStringToOneT
<Data::MethInfo
> infos
;
13351 // Use the already calculated method family and merge
13352 // it's contents into what we already have.
13353 auto const process
= [&] (const ClassInfo2
* cinfo
,
13355 auto const it
= cinfo
->methodFamilies
.find(name
);
13356 always_assert(it
!= end(cinfo
->methodFamilies
));
13357 auto entryInfo
= meth_info_from_func_family_entry(index
, it
->second
);
13359 auto& info
= infos
[name
];
13360 info
.complete
= false;
13361 info
.regularComplete
= false;
13363 for (auto const& meth
: entryInfo
.regularMeths
) {
13364 if (info
.regularMeths
.count(meth
)) continue;
13365 info
.regularMeths
.emplace(meth
);
13366 info
.nonRegularPrivateMeths
.erase(meth
);
13367 info
.nonRegularMeths
.erase(meth
);
13369 for (auto const& meth
: entryInfo
.nonRegularPrivateMeths
) {
13370 if (info
.regularMeths
.count(meth
) ||
13371 info
.nonRegularPrivateMeths
.count(meth
)) {
13374 info
.nonRegularPrivateMeths
.emplace(meth
);
13375 info
.nonRegularMeths
.erase(meth
);
13377 for (auto const& meth
: entryInfo
.nonRegularMeths
) {
13378 if (info
.regularMeths
.count(meth
) ||
13379 info
.nonRegularPrivateMeths
.count(meth
) ||
13380 info
.nonRegularMeths
.count(meth
)) {
13383 info
.nonRegularMeths
.emplace(meth
);
13386 // Merge any StaticInfo entries we have for this method.
13387 if (entryInfo
.allStatic
) {
13388 if (!info
.allStatic
) {
13389 info
.allStatic
= std::move(*entryInfo
.allStatic
);
13391 *info
.allStatic
|= *entryInfo
.allStatic
;
13394 if (entryInfo
.regularStatic
) {
13395 if (!info
.regularStatic
) {
13396 info
.regularStatic
= std::move(*entryInfo
.regularStatic
);
13398 *info
.regularStatic
|= *entryInfo
.regularStatic
;
13403 // First process the roots. These methods might be overridden or
13405 for (auto const& cinfo
: roots
) {
13406 for (auto const& [name
, mte
] : cinfo
->methods
) {
13407 if (!mte
.firstName()) continue;
13408 if (!has_name_only_func_family(name
)) continue;
13409 process(cinfo
.get(), name
);
13413 // Leafs are by definition always AttrNoOverride.
13414 for (auto const& cinfo
: leafs
) {
13415 for (auto const& [name
, mte
] : cinfo
->methods
) {
13416 if (!mte
.firstName()) continue;
13417 if (!has_name_only_func_family(name
)) continue;
13418 process(cinfo
.get(), name
);
13422 // Make the MethInfo order deterministic
13423 std::vector
<SString
> sorted
;
13424 sorted
.reserve(infos
.size());
13425 for (auto const& [name
, _
] : infos
) sorted
.emplace_back(name
);
13426 std::sort(begin(sorted
), end(sorted
), string_data_lt
{});
13428 std::vector
<std::pair
<SString
, FuncFamilyEntry
>> entries
;
13429 entries
.reserve(infos
.size());
13431 // Turn the MethInfos into FuncFamilyEntries
13432 for (auto const name
: sorted
) {
13433 auto& info
= infos
.at(name
);
13434 entries
.emplace_back(
13436 make_method_family_entry(index
, name
, std::move(info
))
13442 // From the information present in the inputs, calculate a mapping
13443 // of classes and splits to their children (which can be other
13444 // classes or split nodes). This is not just direct children, but
13445 // all transitive subclasses.
13446 static void build_children(LocalIndex
& index
,
13447 const std::vector
<EdgeToSplit
>& edges
) {
13448 TSStringToOneT
<TSStringSet
> children
;
13449 // First record direct children. This can be inferred from the
13450 // parents of all present ClassInfos:
13452 // Everything starts out as a leaf.
13453 index
.leafs
.reserve(index
.classInfos
.size());
13454 for (auto const [name
, _
] : index
.classInfos
) {
13455 index
.leafs
.emplace(name
);
13458 auto const onParent
= [&] (SString parent
, const ClassInfo2
* child
) {
13459 // Due to how work is divided, a class might have parents not
13460 // present in this job. Ignore those.
13461 if (!index
.classInfos
.count(parent
)) return;
13462 children
[parent
].emplace(child
->name
);
13463 // If you're a parent, you're not a leaf.
13464 index
.leafs
.erase(parent
);
13467 for (auto const [name
, cinfo
] : index
.classInfos
) {
13468 if (cinfo
->parent
) onParent(cinfo
->parent
, cinfo
);
13469 for (auto const iface
: cinfo
->classGraph
.declInterfaces()) {
13470 onParent(iface
.name(), cinfo
);
13472 for (auto const trait
: cinfo
->classGraph
.usedTraits()) {
13473 onParent(trait
.name(), cinfo
);
13477 // Use the edges provided to the Job to know the mapping from
13478 // ClassInfo to split (it cannot be inferred otherwise).
13479 for (auto const& edge
: edges
) {
13480 SCOPE_ASSERT_DETAIL("Edge not present in job") {
13481 return folly::sformat("{} -> {}", edge
.cls
, edge
.split
);
13483 assertx(index
.classInfos
.count(edge
.cls
));
13484 assertx(index
.splits
.count(edge
.split
));
13485 children
[edge
.cls
].emplace(edge
.split
);
13488 // Every "top" ClassInfo also has itself as a subclass (this
13489 // matches the semantics of the subclass list and simplifies the
13491 for (auto const name
: index
.top
) {
13492 if (auto const split
= folly::get_default(index
.splits
, name
)) {
13493 // Copy the children list out of the split and add it to the
13495 auto& c
= children
[name
];
13496 for (auto const child
: split
->children
) {
13497 assertx(index
.classInfos
.count(child
) ||
13498 index
.splits
.count(child
));
13504 // Calculate the indegree for all children. The indegree for a given node
13505 // differs depending on the top used, so these are calculated separately.
13506 auto const getIndegree
= [&](SString root
) {
13507 TSStringSet visited
;
13508 TSStringSet toExplore
{root
};
13509 TSStringSet toExploreNext
;
13510 TSStringToOneT
<uint32_t> indegree
;
13512 while (!toExplore
.empty()) {
13513 toExploreNext
.clear();
13514 for (auto const child
: toExplore
) {
13515 if (visited
.count(child
)) continue;
13516 visited
.emplace(child
);
13517 auto const it
= children
.find(child
);
13518 // May not exist in children if processed in earlier round.
13519 if (it
== end(children
)) continue;
13520 for (auto const c
: it
->second
) {
13522 toExploreNext
.emplace(c
);
13525 std::swap(toExplore
, toExploreNext
);
13530 // Topological sort the transitive children for each node.
13531 for (auto& [name
, _
] : children
) {
13532 auto indegree
= getIndegree(name
);
13533 std::vector
<SString
> sorted
{name
};
13535 int sortedBegin
= 0;
13536 int sortedEnd
= sorted
.size();
13538 while (sortedBegin
!= sortedEnd
) {
13539 for (int i
= sortedBegin
; i
< sortedEnd
; i
++) {
13540 auto const cls
= sorted
[i
];
13541 auto const it
= children
.find(cls
);
13542 if (it
== end(children
)) continue;
13543 for (auto const c
: it
->second
) {
13545 if (indegree
[c
] == 0) sorted
.emplace_back(c
);
13548 sortedBegin
= sortedEnd
;
13549 sortedEnd
= sorted
.size();
13551 assertx(indegree
.size() + 1 == sorted
.size());
13552 index
.children
[name
] = std::move(sorted
);
13556 static FuncFamily2::StaticInfo
static_info_from_meth_meta(
13557 const FuncFamilyEntry::MethMetadata
& meta
13559 FuncFamily2::StaticInfo info
;
13560 info
.m_numInOut
= meta
.m_numInOut
;
13561 info
.m_requiredCoeffects
= meta
.m_requiredCoeffects
;
13562 info
.m_coeffectRules
= meta
.m_coeffectRules
;
13563 info
.m_paramPreps
= meta
.m_prepKinds
;
13564 info
.m_minNonVariadicParams
= info
.m_maxNonVariadicParams
=
13565 meta
.m_nonVariadicParams
;
13566 info
.m_isReadonlyReturn
= yesOrNo(meta
.m_isReadonlyReturn
);
13567 info
.m_isReadonlyThis
= yesOrNo(meta
.m_isReadonlyThis
);
13568 info
.m_supportsAER
= yesOrNo(meta
.m_supportsAER
);
13569 info
.m_maybeReified
= meta
.m_isReified
;
13570 info
.m_maybeCaresAboutDynCalls
= meta
.m_caresAboutDyncalls
;
13571 info
.m_maybeBuiltin
= meta
.m_builtin
;
13575 // Turn a FuncFamilyEntry into an equivalent Data::MethInfo.
13576 static Data::MethInfo
13577 meth_info_from_func_family_entry(LocalIndex
& index
,
13578 const FuncFamilyEntry
& entry
) {
13579 Data::MethInfo info
;
13580 info
.complete
= !entry
.m_allIncomplete
;
13581 info
.regularComplete
= !entry
.m_regularIncomplete
;
13582 info
.privateAncestor
= entry
.m_privateAncestor
;
13584 auto const getFF
= [&] (const FuncFamily2::Id
& id
)
13585 -> const FuncFamily2
& {
13586 auto const it
= index
.funcFamilies
.find(id
);
13587 always_assert_flog(
13588 it
!= end(index
.funcFamilies
),
13589 "Tried to access non-existent func-family '{}'",
13592 return *it
->second
;
13597 [&] (const FuncFamilyEntry::BothFF
& e
) {
13598 auto const& ff
= getFF(e
.m_ff
);
13599 info
.regularMeths
.insert(
13600 begin(ff
.m_regular
),
13603 info
.nonRegularPrivateMeths
.insert(
13604 begin(ff
.m_nonRegularPrivate
),
13605 end(ff
.m_nonRegularPrivate
)
13607 info
.nonRegularMeths
.insert(
13608 begin(ff
.m_nonRegular
),
13609 end(ff
.m_nonRegular
)
13611 assertx(ff
.m_allStatic
);
13612 assertx(ff
.m_regularStatic
);
13613 info
.allStatic
= ff
.m_allStatic
;
13614 info
.regularStatic
= ff
.m_regularStatic
;
13616 [&] (const FuncFamilyEntry::FFAndSingle
& e
) {
13617 auto const& ff
= getFF(e
.m_ff
);
13618 info
.nonRegularMeths
.insert(
13619 begin(ff
.m_nonRegular
),
13620 end(ff
.m_nonRegular
)
13622 if (e
.m_nonRegularPrivate
) {
13623 assertx(ff
.m_nonRegularPrivate
.size() == 1);
13624 assertx(ff
.m_nonRegularPrivate
[0] == e
.m_regular
);
13625 info
.nonRegularPrivateMeths
.emplace(e
.m_regular
);
13627 assertx(ff
.m_regular
.size() == 1);
13628 assertx(ff
.m_regular
[0] == e
.m_regular
);
13629 info
.regularMeths
.emplace(e
.m_regular
);
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::FFAndNone
& e
) {
13637 auto const& ff
= getFF(e
.m_ff
);
13638 assertx(ff
.m_regular
.empty());
13639 info
.nonRegularMeths
.insert(
13640 begin(ff
.m_nonRegular
),
13641 end(ff
.m_nonRegular
)
13643 assertx(ff
.m_allStatic
);
13644 assertx(!ff
.m_regularStatic
);
13645 info
.allStatic
= ff
.m_allStatic
;
13647 [&] (const FuncFamilyEntry::BothSingle
& e
) {
13648 if (e
.m_nonRegularPrivate
) {
13649 info
.nonRegularPrivateMeths
.emplace(e
.m_all
);
13651 info
.regularMeths
.emplace(e
.m_all
);
13653 info
.allStatic
= info
.regularStatic
=
13654 static_info_from_meth_meta(e
.m_meta
);
13656 [&] (const FuncFamilyEntry::SingleAndNone
& e
) {
13657 info
.nonRegularMeths
.emplace(e
.m_all
);
13658 info
.allStatic
= static_info_from_meth_meta(e
.m_meta
);
13660 [&] (const FuncFamilyEntry::None
&) {
13661 assertx(!info
.complete
);
13668 // Create a Data representing the single ClassInfo or split with the
13670 static Data
build_data(LocalIndex
& index
, SString clsname
) {
13671 // Does this name represent a class?
13672 if (auto const cinfo
= folly::get_default(index
.classInfos
, clsname
)) {
13673 // It's a class. We need to build a Data from what's in the
13674 // ClassInfo. If the ClassInfo hasn't been processed already
13675 // (it's a leaf or its the first round), the data will reflect
13676 // just that class. However if the ClassInfo has been processed
13677 // (it's a dependencies and it's past the first round), it will
13678 // reflect any subclasses of that ClassInfo as well.
13681 // Use the method family table to build initial MethInfos (if
13682 // the ClassInfo hasn't been processed this will be empty).
13683 for (auto const& [name
, entry
] : cinfo
->methodFamilies
) {
13684 data
.methods
.emplace(
13686 meth_info_from_func_family_entry(index
, entry
)
13690 auto const& cls
= index
.cls(cinfo
->name
);
13693 for (auto const& [name
, mte
] : cinfo
->methods
) {
13694 if (is_special_method_name(name
)) continue;
13696 // Every method should have a methodFamilies entry. If this
13697 // method is AttrNoOverride, it shouldn't have a FuncFamily
13698 // associated with it.
13699 auto const it
= cinfo
->methodFamilies
.find(name
);
13700 always_assert(it
!= end(cinfo
->methodFamilies
));
13702 if (mte
.attrs
& AttrNoOverride
) {
13704 boost::get
<FuncFamilyEntry::BothSingle
>(&it
->second
.m_meths
) ||
13705 boost::get
<FuncFamilyEntry::SingleAndNone
>(&it
->second
.m_meths
)
13711 // Create a MethInfo for any missing methods as well.
13712 for (auto const name
: cinfo
->missingMethods
) {
13713 assertx(!cinfo
->methods
.count(name
));
13714 if (data
.methods
.count(name
)) continue;
13715 // The MethInfo will be empty, and be marked as incomplete.
13716 auto& info
= data
.methods
[name
];
13717 info
.complete
= false;
13718 if (cinfo
->isRegularClass
) info
.regularComplete
= false;
13721 data
.hasConstProp
= cinfo
->subHasConstProp
;
13722 data
.hasReifiedGeneric
= cinfo
->subHasReifiedGeneric
;
13724 // If this is a mock class, any direct parent of this class
13725 // should be marked as mocked.
13726 if (cinfo
->isMockClass
) {
13727 for (auto const p
: cinfo
->classGraph
.directParents()) {
13728 data
.mockedClasses
.emplace(p
.name());
13731 data
.isSubMocked
= cinfo
->isMocked
|| cinfo
->isSubMocked
;
13733 data
.hasRegularClass
= cinfo
->isRegularClass
;
13734 data
.hasRegularClassFull
=
13735 data
.hasRegularClass
|| cinfo
->classGraph
.mightHaveRegularSubclass();
13736 if (!data
.hasRegularClass
&& index
.leafs
.count(clsname
)) {
13737 data
.hasRegularClass
= data
.hasRegularClassFull
;
13740 for (auto const& prop
: cls
.properties
) {
13741 if (!(prop
.attrs
& (AttrStatic
|AttrPrivate
|AttrNoImplicitNullable
))) {
13742 data
.propsWithImplicitNullable
.emplace(prop
.name
);
13749 // It doesn't represent a class. It should represent a
13752 // A split cannot be both a root and a dependency due to how we
13753 // set up the buckets.
13754 assertx(!index
.top
.count(clsname
));
13755 auto const split
= folly::get_default(index
.splits
, clsname
);
13756 always_assert(split
!= nullptr);
13757 assertx(split
->children
.empty());
13758 // Split already contains the Data, so nothing to do but return
13760 return split
->data
;
13763 static void update_data(Data
& data
, Data childData
) {
13764 // Combine MethInfos for each method name:
13767 [&] (std::pair
<const SString
, Data::MethInfo
>& p
) {
13768 auto const name
= p
.first
;
13769 auto& info
= p
.second
;
13771 if (auto const childInfo
=
13772 folly::get_ptr(childData
.methods
, name
)) {
13773 // There's a MethInfo with that name in the
13774 // child. "Promote" the MethRefs if they're in a superior
13775 // status in the child.
13776 for (auto const& meth
: childInfo
->regularMeths
) {
13777 if (info
.regularMeths
.count(meth
)) continue;
13778 info
.regularMeths
.emplace(meth
);
13779 info
.nonRegularPrivateMeths
.erase(meth
);
13780 info
.nonRegularMeths
.erase(meth
);
13782 for (auto const& meth
: childInfo
->nonRegularPrivateMeths
) {
13783 if (info
.regularMeths
.count(meth
) ||
13784 info
.nonRegularPrivateMeths
.count(meth
)) {
13787 info
.nonRegularPrivateMeths
.emplace(meth
);
13788 info
.nonRegularMeths
.erase(meth
);
13790 for (auto const& meth
: childInfo
->nonRegularMeths
) {
13791 if (info
.regularMeths
.count(meth
) ||
13792 info
.nonRegularPrivateMeths
.count(meth
) ||
13793 info
.nonRegularMeths
.count(meth
)) {
13796 info
.nonRegularMeths
.emplace(meth
);
13798 info
.complete
&= childInfo
->complete
;
13799 if (childData
.hasRegularClassFull
) {
13800 info
.regularComplete
&= childInfo
->regularComplete
;
13801 info
.privateAncestor
|= childInfo
->privateAncestor
;
13803 assertx(childInfo
->regularComplete
);
13804 assertx(!childInfo
->privateAncestor
);
13807 if (childInfo
->allStatic
) {
13808 if (!info
.allStatic
) {
13809 info
.allStatic
= std::move(*childInfo
->allStatic
);
13811 *info
.allStatic
|= *childInfo
->allStatic
;
13814 if (childInfo
->regularStatic
) {
13815 if (!info
.regularStatic
) {
13816 info
.regularStatic
= std::move(*childInfo
->regularStatic
);
13818 *info
.regularStatic
|= *childInfo
->regularStatic
;
13825 // There's no MethInfo with that name in the child. We might
13826 // still want to keep the MethInfo because it will be needed
13827 // for expanding abstract class/interface method
13828 // families. If the child has a regular class, we can remove
13829 // it (it won't be part of the expansion).
13831 childData
.hasRegularClass
||
13832 !info
.regularComplete
||
13833 info
.privateAncestor
||
13834 is_special_method_name(name
) ||
13835 name
== s_construct
.get();
13839 // Since we drop non-matching method names only if the class has
13840 // a regular class, it introduces an ordering dependency. If the
13841 // first class we encounter has a regular class, everything
13842 // works fine. However, if the first class we encounter does not
13843 // have a regular class, the Data will have its methods. If we
13844 // eventually process a class which does have a regular class,
13845 // we'll never process it's non-matching methods (because we
13846 // iterate over data.methods). They won't end up in data.methods
13847 // whereas they would if a class with regular class was
13848 // processed first. Detect this condition and manually add such
13849 // methods to data.methods.
13850 if (!data
.hasRegularClass
&& childData
.hasRegularClass
) {
13851 for (auto& [name
, info
] : childData
.methods
) {
13852 if (!info
.regularComplete
|| info
.privateAncestor
) continue;
13853 if (is_special_method_name(name
)) continue;
13854 if (name
== s_construct
.get()) continue;
13855 if (data
.methods
.count(name
)) continue;
13856 auto& newInfo
= data
.methods
[name
];
13857 newInfo
.regularMeths
= std::move(info
.regularMeths
);
13858 newInfo
.nonRegularPrivateMeths
=
13859 std::move(info
.nonRegularPrivateMeths
);
13860 newInfo
.nonRegularMeths
= std::move(info
.nonRegularMeths
);
13861 newInfo
.allStatic
= std::move(info
.allStatic
);
13862 newInfo
.regularStatic
= std::move(info
.regularStatic
);
13863 newInfo
.complete
= false;
13864 newInfo
.regularComplete
= true;
13865 newInfo
.privateAncestor
= false;
13869 data
.propsWithImplicitNullable
.insert(
13870 begin(childData
.propsWithImplicitNullable
),
13871 end(childData
.propsWithImplicitNullable
)
13874 data
.mockedClasses
.insert(
13875 begin(childData
.mockedClasses
),
13876 end(childData
.mockedClasses
)
13879 // The rest are booleans which can just be unioned together.
13880 data
.hasConstProp
|= childData
.hasConstProp
;
13881 data
.hasReifiedGeneric
|= childData
.hasReifiedGeneric
;
13882 data
.isSubMocked
|= childData
.isSubMocked
;
13883 data
.hasRegularClass
|= childData
.hasRegularClass
;
13884 data
.hasRegularClassFull
|= childData
.hasRegularClassFull
;
13887 // Obtain a Data for the given class/split named "top".
13888 // @param calculatedAcc: an accumulator passed in to track nodes we process
13889 // while processing children recursively
13890 static Data
aggregate_data(LocalIndex
& index
,
13892 TSStringSet
& calculatedAcc
) {
13893 assertx(index
.top
.contains(top
));
13895 auto const& children
= [&]() -> const std::vector
<SString
>& {
13896 auto const it
= index
.children
.find(top
);
13897 always_assert(it
!= end(index
.children
));
13898 assertx(!it
->second
.empty());
13902 auto const it
= index
.aggregateData
.find(top
);
13903 if (it
!= end(index
.aggregateData
)) {
13904 for (auto const child
: children
) calculatedAcc
.emplace(child
);
13910 // Set of children calculated for current top to ensure we don't
13912 TSStringSet calculatedForTop
;
13914 // For each child of the class/split (for classes this includes
13915 // the top class itself), we create a Data, then union it together
13917 size_t childIdx
= 0;
13918 while (calculatedForTop
.size() < children
.size()) {
13919 auto child
= children
[childIdx
++];
13920 if (calculatedForTop
.contains(child
)) continue;
13921 // Top Splits have no associated data yet.
13922 if (index
.top
.count(child
) && index
.splits
.count(child
)) {
13923 calculatedForTop
.emplace(child
);
13927 auto childData
= [&]() {
13928 if (index
.top
.contains(child
) && !child
->tsame(top
)) {
13929 return aggregate_data(index
, child
, calculatedForTop
);
13931 calculatedForTop
.emplace(child
);
13932 return build_data(index
, child
);
13936 // The first Data has nothing to union with, so just use it as is.
13938 data
= std::move(childData
);
13942 update_data(data
, std::move(childData
));
13945 for (auto const cls
: calculatedForTop
) calculatedAcc
.emplace(cls
);
13946 always_assert(index
.aggregateData
.emplace(top
, data
).second
);
13950 // Obtain a Data for the given class/split named "top".
13951 static Data
aggregate_data(LocalIndex
& index
, SString top
) {
13952 TSStringSet calculated
;
13953 return aggregate_data(index
, top
, calculated
);
13956 // Create (or re-use an existing) FuncFamily for the given MethInfo.
13957 static FuncFamily2::Id
make_func_family(
13960 Data::MethInfo info
13962 // We should have more than one method because otherwise we
13963 // shouldn't be trying to create a FuncFamily for it.
13965 info
.regularMeths
.size() +
13966 info
.nonRegularPrivateMeths
.size() +
13967 info
.nonRegularMeths
.size() > 1
13970 // Before doing the expensive sorting and hashing, see if this
13971 // FuncFamily already exists. If so, just return the id.
13972 if (auto const id
= folly::get_ptr(
13973 index
.funcFamilyCache
,
13974 MethInfoTupleProxy
{
13975 &info
.regularMeths
,
13976 &info
.nonRegularPrivateMeths
,
13977 &info
.nonRegularMeths
13983 // Nothing in the cache. We need to do the expensive step of
13984 // actually creating the FuncFamily.
13986 // First sort the methods so they're in deterministic order.
13987 std::vector
<MethRef
> regular
{
13988 begin(info
.regularMeths
), end(info
.regularMeths
)
13990 std::vector
<MethRef
> nonRegularPrivate
{
13991 begin(info
.nonRegularPrivateMeths
), end(info
.nonRegularPrivateMeths
)
13993 std::vector
<MethRef
> nonRegular
{
13994 begin(info
.nonRegularMeths
), end(info
.nonRegularMeths
)
13996 std::sort(begin(regular
), end(regular
));
13997 std::sort(begin(nonRegularPrivate
), end(nonRegularPrivate
));
13998 std::sort(begin(nonRegular
), end(nonRegular
));
14000 // Create the id by hashing the methods:
14003 auto const size1
= regular
.size();
14004 auto const size2
= nonRegularPrivate
.size();
14005 auto const size3
= nonRegular
.size();
14006 hasher
.update((const char*)&size1
, sizeof(size1
));
14007 hasher
.update((const char*)&size2
, sizeof(size2
));
14008 hasher
.update((const char*)&size3
, sizeof(size3
));
14010 for (auto const& m
: regular
) {
14011 hasher
.update(m
.cls
->data(), m
.cls
->size());
14012 hasher
.update((const char*)&m
.idx
, sizeof(m
.idx
));
14014 for (auto const& m
: nonRegularPrivate
) {
14015 hasher
.update(m
.cls
->data(), m
.cls
->size());
14016 hasher
.update((const char*)&m
.idx
, sizeof(m
.idx
));
14018 for (auto const& m
: nonRegular
) {
14019 hasher
.update(m
.cls
->data(), m
.cls
->size());
14020 hasher
.update((const char*)&m
.idx
, sizeof(m
.idx
));
14022 auto const id
= hasher
.finish();
14024 // See if this id exists already. If so, record it in the cache
14026 if (index
.funcFamilies
.count(id
)) {
14027 index
.funcFamilyCache
.emplace(
14029 std::move(info
.regularMeths
),
14030 std::move(info
.nonRegularPrivateMeths
),
14031 std::move(info
.nonRegularMeths
)
14038 // It's a new id. Create the actual FuncFamily:
14040 regular
.shrink_to_fit();
14041 nonRegularPrivate
.shrink_to_fit();
14042 nonRegular
.shrink_to_fit();
14044 auto ff
= std::make_unique
<FuncFamily2
>();
14047 ff
->m_regular
= std::move(regular
);
14048 ff
->m_nonRegularPrivate
= std::move(nonRegularPrivate
);
14049 ff
->m_nonRegular
= std::move(nonRegular
);
14050 ff
->m_allStatic
= std::move(info
.allStatic
);
14051 ff
->m_regularStatic
= std::move(info
.regularStatic
);
14054 index
.funcFamilies
.emplace(id
, std::move(ff
)).second
14056 index
.newFuncFamilies
.emplace_back(id
);
14057 index
.funcFamilyCache
.emplace(
14059 std::move(info
.regularMeths
),
14060 std::move(info
.nonRegularPrivateMeths
),
14061 std::move(info
.nonRegularMeths
)
14069 // Turn a FuncFamily::StaticInfo into an equivalent
14070 // FuncFamilyEntry::MethMetadata. The StaticInfo must be valid for a
14072 static FuncFamilyEntry::MethMetadata
single_meth_meta_from_static_info(
14073 const FuncFamily2::StaticInfo
& info
14075 assertx(info
.m_numInOut
);
14076 assertx(info
.m_requiredCoeffects
);
14077 assertx(info
.m_coeffectRules
);
14078 assertx(info
.m_minNonVariadicParams
== info
.m_maxNonVariadicParams
);
14079 assertx(info
.m_isReadonlyReturn
!= TriBool::Maybe
);
14080 assertx(info
.m_isReadonlyThis
!= TriBool::Maybe
);
14081 assertx(info
.m_supportsAER
!= TriBool::Maybe
);
14083 FuncFamilyEntry::MethMetadata meta
;
14084 meta
.m_prepKinds
= info
.m_paramPreps
;
14085 meta
.m_coeffectRules
= *info
.m_coeffectRules
;
14086 meta
.m_numInOut
= *info
.m_numInOut
;
14087 meta
.m_requiredCoeffects
= *info
.m_requiredCoeffects
;
14088 meta
.m_nonVariadicParams
= info
.m_minNonVariadicParams
;
14089 meta
.m_isReadonlyReturn
= info
.m_isReadonlyReturn
== TriBool::Yes
;
14090 meta
.m_isReadonlyThis
= info
.m_isReadonlyThis
== TriBool::Yes
;
14091 meta
.m_supportsAER
= info
.m_supportsAER
== TriBool::Yes
;
14092 meta
.m_isReified
= info
.m_maybeReified
;
14093 meta
.m_caresAboutDyncalls
= info
.m_maybeCaresAboutDynCalls
;
14094 meta
.m_builtin
= info
.m_maybeBuiltin
;
14098 // Translate a MethInfo into the appropriate FuncFamilyEntry
14099 static FuncFamilyEntry
make_method_family_entry(
14102 Data::MethInfo info
14104 FuncFamilyEntry entry
;
14105 entry
.m_allIncomplete
= !info
.complete
;
14106 entry
.m_regularIncomplete
= !info
.regularComplete
;
14107 entry
.m_privateAncestor
= info
.privateAncestor
;
14109 if (info
.regularMeths
.size() + info
.nonRegularPrivateMeths
.size() > 1) {
14110 // There's either multiple regularMeths, multiple
14111 // nonRegularPrivateMeths, or one of each (remember they are
14112 // disjoint). In either case, there's more than one method, so
14113 // we need a func family.
14114 assertx(info
.allStatic
);
14115 assertx(info
.regularStatic
);
14116 auto const ff
= make_func_family(index
, name
, std::move(info
));
14117 entry
.m_meths
= FuncFamilyEntry::BothFF
{ff
};
14118 } else if (!info
.regularMeths
.empty() ||
14119 !info
.nonRegularPrivateMeths
.empty()) {
14120 // We know their sum isn't greater than one, so only one of them
14121 // can be non-empty (and the one that is has only a single
14123 assertx(info
.allStatic
);
14124 assertx(info
.regularStatic
);
14125 auto const r
= !info
.regularMeths
.empty()
14126 ? *begin(info
.regularMeths
)
14127 : *begin(info
.nonRegularPrivateMeths
);
14128 if (info
.nonRegularMeths
.empty()) {
14129 // There's only one method and it covers both variants.
14130 entry
.m_meths
= FuncFamilyEntry::BothSingle
{
14132 single_meth_meta_from_static_info(*info
.allStatic
),
14133 info
.regularMeths
.empty()
14136 // nonRegularMeths is non-empty. Since the MethRefSets are
14137 // disjoint, overall there's more than one method so need a
14139 auto const nonRegularPrivate
= info
.regularMeths
.empty();
14140 auto const ff
= make_func_family(index
, name
, std::move(info
));
14141 entry
.m_meths
= FuncFamilyEntry::FFAndSingle
{ff
, r
, nonRegularPrivate
};
14143 } else if (info
.nonRegularMeths
.size() > 1) {
14144 // Both regularMeths and nonRegularPrivateMeths is empty. If
14145 // there's multiple nonRegularMeths, we need a func family for
14146 // the non-regular variant, but the regular variant is empty.
14147 assertx(info
.allStatic
);
14148 assertx(!info
.regularStatic
);
14149 auto const ff
= make_func_family(index
, name
, std::move(info
));
14150 entry
.m_meths
= FuncFamilyEntry::FFAndNone
{ff
};
14151 } else if (!info
.nonRegularMeths
.empty()) {
14152 // There's exactly one nonRegularMeths method (and nothing for
14153 // the regular variant).
14154 assertx(info
.allStatic
);
14155 assertx(!info
.regularStatic
);
14156 entry
.m_meths
= FuncFamilyEntry::SingleAndNone
{
14157 *begin(info
.nonRegularMeths
),
14158 single_meth_meta_from_static_info(*info
.allStatic
)
14161 // No methods at all
14162 assertx(!info
.complete
);
14163 assertx(!info
.allStatic
);
14164 assertx(!info
.regularStatic
);
14165 entry
.m_meths
= FuncFamilyEntry::None
{};
14171 // Calculate the data for each root (those which will we'll provide
14172 // outputs for) and update the ClassInfo or Split as appropriate.
14173 static void process_roots(
14175 const std::vector
<std::unique_ptr
<ClassInfo2
>>& roots
,
14176 const std::vector
<std::unique_ptr
<Split
>>& splits
14178 for (auto const& cinfo
: roots
) {
14179 assertx(index
.top
.count(cinfo
->name
));
14180 // Process the children of this class and build a unified Data
14182 auto data
= aggregate_data(index
, cinfo
->name
);
14184 // These are just copied directly from Data.
14185 cinfo
->subHasConstProp
= data
.hasConstProp
;
14186 cinfo
->subHasReifiedGeneric
= data
.hasReifiedGeneric
;
14188 auto& cls
= index
.cls(cinfo
->name
);
14190 // This class is mocked if its on the mocked classes list.
14191 cinfo
->isMocked
= (bool)data
.mockedClasses
.count(cinfo
->name
);
14192 cinfo
->isSubMocked
= data
.isSubMocked
|| cinfo
->isMocked
;
14193 attribute_setter(cls
.attrs
, !cinfo
->isSubMocked
, AttrNoMock
);
14195 // We can use whether we saw regular/non-regular subclasses to
14196 // infer if this class is overridden.
14197 if (cinfo
->classGraph
.mightHaveRegularSubclass()) {
14198 attribute_setter(cls
.attrs
, false, AttrNoOverrideRegular
);
14199 attribute_setter(cls
.attrs
, false, AttrNoOverride
);
14200 } else if (cinfo
->classGraph
.mightHaveNonRegularSubclass()) {
14201 attribute_setter(cls
.attrs
, true, AttrNoOverrideRegular
);
14202 attribute_setter(cls
.attrs
, false, AttrNoOverride
);
14204 attribute_setter(cls
.attrs
, true, AttrNoOverrideRegular
);
14205 attribute_setter(cls
.attrs
, true, AttrNoOverride
);
14210 cinfo
->initialNoReifiedInit
,
14211 cls
.attrs
& AttrNoReifiedInit
14218 if (cinfo
->initialNoReifiedInit
) return true;
14219 if (cinfo
->parent
) return false;
14220 if (cls
.attrs
& AttrInterface
) return true;
14221 return !data
.hasReifiedGeneric
;
14226 for (auto& [name
, mte
] : cinfo
->methods
) {
14227 if (is_special_method_name(name
)) continue;
14229 // Since this is the first time we're processing this class,
14230 // all of the methods should be marked as AttrNoOverride.
14231 assertx(mte
.attrs
& AttrNoOverride
);
14232 assertx(mte
.noOverrideRegular());
14234 auto& info
= [&, name
=name
] () -> Data::MethInfo
& {
14235 auto it
= data
.methods
.find(name
);
14236 always_assert(it
!= end(data
.methods
));
14240 auto const meth
= mte
.meth();
14242 // Is this method overridden?
14243 auto const noOverride
= [&] {
14244 // An incomplete method family is always overridden because
14245 // the call could fail.
14246 if (!info
.complete
) return false;
14247 // If more than one method then no.
14248 if (info
.regularMeths
.size() +
14249 info
.nonRegularPrivateMeths
.size() +
14250 info
.nonRegularMeths
.size() > 1) {
14253 // NB: All of the below checks all return true. The
14254 // different conditions are just for checking the right
14256 if (info
.regularMeths
.empty()) {
14257 // The (single) method isn't on a regular class. This
14258 // class shouldn't have any regular classes (the set is
14259 // complete so if we did, the method would have been on
14260 // it). The (single) method must be on nonRegularMeths or
14261 // nonRegularPrivateMeths.
14262 assertx(!cinfo
->isRegularClass
);
14263 if (info
.nonRegularPrivateMeths
.empty()) {
14264 assertx(info
.nonRegularMeths
.count(meth
));
14267 assertx(info
.nonRegularMeths
.empty());
14268 assertx(info
.nonRegularPrivateMeths
.count(meth
));
14271 assertx(info
.nonRegularPrivateMeths
.empty());
14272 assertx(info
.nonRegularMeths
.empty());
14273 assertx(info
.regularMeths
.count(meth
));
14277 // Is this method overridden in a regular class? (weaker
14279 auto const noOverrideRegular
= [&] {
14280 // An incomplete method family is always overridden because
14281 // the call could fail.
14282 if (!info
.regularComplete
) return false;
14283 // If more than one method then no. For the purposes of this
14284 // check, non-regular but private methods are included.
14285 if (info
.regularMeths
.size() +
14286 info
.nonRegularPrivateMeths
.size() > 1) {
14289 if (info
.regularMeths
.empty()) {
14290 // The method isn't on a regular class. Like in
14291 // noOverride(), the class shouldn't have any regular
14292 // classes. If nonRegularPrivateMethos is empty, this
14293 // means any possible override is non-regular, so we're
14295 assertx(!cinfo
->isRegularClass
);
14296 if (info
.nonRegularPrivateMeths
.empty()) return true;
14297 return (bool)info
.nonRegularPrivateMeths
.count(meth
);
14299 if (cinfo
->isRegularClass
) {
14300 // If this class is regular, the method on this class
14301 // should be marked as regular.
14302 assertx(info
.regularMeths
.count(meth
));
14305 // We know regularMeths is non-empty, and the size is at
14306 // most one. If this method is the (only) one in
14307 // regularMeths, it's not overridden by anything.
14308 return (bool)info
.regularMeths
.count(meth
);
14311 if (!noOverrideRegular()) {
14312 mte
.clearNoOverrideRegular();
14313 attribute_setter(mte
.attrs
, false, AttrNoOverride
);
14314 } else if (!noOverride()) {
14315 attribute_setter(mte
.attrs
, false, AttrNoOverride
);
14318 auto& entry
= cinfo
->methodFamilies
.at(name
);
14320 boost::get
<FuncFamilyEntry::BothSingle
>(&entry
.m_meths
) ||
14321 boost::get
<FuncFamilyEntry::SingleAndNone
>(&entry
.m_meths
)
14325 if (mte
.attrs
& AttrNoOverride
) {
14326 always_assert(info
.complete
);
14327 always_assert(info
.regularComplete
);
14329 if (cinfo
->isRegularClass
||
14330 cinfo
->classGraph
.mightHaveRegularSubclass()) {
14331 always_assert(info
.regularMeths
.size() == 1);
14332 always_assert(info
.regularMeths
.count(meth
));
14333 always_assert(info
.nonRegularPrivateMeths
.empty());
14334 always_assert(info
.nonRegularMeths
.empty());
14336 // If this class isn't regular, it could still have a
14337 // regular method which it inherited from a (regular)
14338 // parent. There should only be one method across all the
14341 info
.regularMeths
.size() +
14342 info
.nonRegularPrivateMeths
.size() +
14343 info
.nonRegularMeths
.size() == 1
14346 info
.regularMeths
.count(meth
) ||
14347 info
.nonRegularPrivateMeths
.count(meth
) ||
14348 info
.nonRegularMeths
.count(meth
)
14352 if (mte
.hasPrivateAncestor() &&
14353 (cinfo
->isRegularClass
||
14354 cinfo
->classGraph
.mightHaveRegularSubclass())) {
14355 always_assert(info
.privateAncestor
);
14357 always_assert(!info
.privateAncestor
);
14360 always_assert(!(cls
.attrs
& AttrNoOverride
));
14364 // NB: Even if the method is AttrNoOverride, we might need to
14365 // change the FuncFamilyEntry. This class could be non-regular
14366 // and a child class could be regular. Even if the child class
14367 // doesn't override the method, it changes it from non-regular
14369 entry
= make_method_family_entry(index
, name
, std::move(info
));
14371 if (mte
.attrs
& AttrNoOverride
) {
14372 // However, even if the entry changes with AttrNoOverride,
14373 // it can only be these two cases.
14375 boost::get
<FuncFamilyEntry::BothSingle
>(&entry
.m_meths
) ||
14376 boost::get
<FuncFamilyEntry::SingleAndNone
>(&entry
.m_meths
)
14382 * Interfaces can cause monotonicity violations. Suppose we have two
14383 * interfaces: I2 and I2. I1 declares a method named Foo. Every
14384 * class which implements I2 also implements I1 (therefore I2
14385 * implies I1). During analysis, a type is initially Obj<=I1 and we
14386 * resolve a call to Foo using I1's func families. After further
14387 * optimization, we narrow the type to Obj<=I2. Now when we go to
14388 * resolve a call to Foo using I2's func families, we find
14389 * nothing. Foo is declared in I1, not in I2, and interface methods
14390 * are not inherited. We use the fall back name-only tables, which
14391 * might give us a worse type than before. This is a monotonicity
14392 * violation because refining the object type gave us worse
14395 * To avoid this, we expand an interface's (and abstract class'
14396 * which has similar issues) func families to include all methods
14397 * defined by *all* of it's (regular) implementations. So, in the
14398 * example above, we'd expand I2's func families to include Foo,
14399 * since all of I2's implements should define a Foo method (since
14400 * they also all implement I1).
14402 * Any MethInfos which are part of the abstract class/interface
14403 * method table has already been processed above. Any ones which
14404 * haven't are candidates for the above expansion and must also
14405 * be placed in the method families table. Note: we do not just
14406 * restrict this to just abstract classes or interfaces. This
14407 * class may be a child of an abstract class or interfaces and
14408 * we need to propagate these "expanded" methods so they're
14409 * available in the dependency when we actually process the
14410 * abstract class/interface in a later round.
14412 for (auto& [name
, info
] : data
.methods
) {
14413 if (cinfo
->methods
.count(name
)) continue;
14414 assertx(!is_special_method_name(name
));
14415 auto entry
= make_method_family_entry(index
, name
, std::move(info
));
14417 cinfo
->methodFamilies
.emplace(name
, std::move(entry
)).second
14421 for (auto& prop
: cls
.properties
) {
14422 if (bool(prop
.attrs
& AttrNoImplicitNullable
) &&
14423 !(prop
.attrs
& (AttrStatic
| AttrPrivate
))) {
14426 !data
.propsWithImplicitNullable
.count(prop
.name
),
14427 AttrNoImplicitNullable
14431 if (!(prop
.attrs
& AttrSystemInitialValue
)) continue;
14432 if (prop
.val
.m_type
== KindOfUninit
) {
14433 assertx(prop
.attrs
& AttrLateInit
);
14438 if (!(prop
.attrs
& AttrNoImplicitNullable
)) {
14439 return make_tv
<KindOfNull
>();
14441 // Give the 86reified_prop a special default value to
14442 // avoid pessimizing the inferred type (we want it to
14443 // always be a vec of a specific size).
14444 if (prop
.name
== s_86reified_prop
.get()) {
14445 return get_default_value_of_reified_list(cls
.userAttributes
);
14447 return prop
.typeConstraint
.defaultValue();
14452 // Splits just store the data directly. Since this split hasn't
14453 // been processed yet (and no other job should process it), all of
14454 // the fields should be their default settings.
14455 for (auto& split
: splits
) {
14456 assertx(index
.top
.count(split
->name
));
14457 split
->data
= aggregate_data(index
, split
->name
);
14458 // This split inherits all of the splits of their children.
14459 for (auto const child
: split
->children
) {
14460 if (auto const c
= folly::get_default(index
.classInfos
, child
)) {
14461 split
->classGraphs
.emplace_back(c
->classGraph
);
14464 auto const s
= folly::get_default(index
.splits
, child
);
14466 split
->classGraphs
.insert(
14467 end(split
->classGraphs
),
14468 begin(s
->classGraphs
),
14469 end(s
->classGraphs
)
14472 std::sort(begin(split
->classGraphs
), end(split
->classGraphs
));
14473 split
->classGraphs
.erase(
14474 std::unique(begin(split
->classGraphs
), end(split
->classGraphs
)),
14475 end(split
->classGraphs
)
14477 split
->children
.clear();
14482 Job
<BuildSubclassListJob
> s_buildSubclassJob
;
14484 struct SubclassWork
{
14485 TSStringToOneT
<std::unique_ptr
<BuildSubclassListJob::Split
>> allSplits
;
14487 std::vector
<SString
> classes
;
14488 std::vector
<SString
> deps
;
14489 std::vector
<SString
> splits
;
14490 std::vector
<SString
> splitDeps
;
14491 std::vector
<SString
> leafs
;
14492 std::vector
<BuildSubclassListJob::EdgeToSplit
> edges
;
14495 std::vector
<std::vector
<Bucket
>> buckets
;
14499 * Algorithm for assigning work for building subclass lists:
14501 * - Keep track of which classes have been processed and which ones
14502 * have not yet been.
14504 * - Keep looping until all classes have been processed. Each round of
14505 * the algorithm becomes a round of output.
14507 * - Iterate over all classes which haven't been
14508 * processed. Distinguish classes which are eligible for processing
14509 * or not. A class is eligible for processing if its transitive
14510 * dependencies are below the maximum size.
14512 * - Non-eligible classes are ignored and will be processed again next
14513 * round. However, if the class has more eligible direct children
14514 * than the bucket size, the class' children will be turned into
14517 * - Create split nodes. For each class (who we're splitting), use the
14518 * typical consistent hashing algorithm to assign each child to a
14519 * split node. Change the class' child list to contain the split
14520 * nodes instead of the children (this should shrink it
14521 * considerably). Each new split becomes a root.
14523 * - Assign each eligible class to a bucket. Use
14524 * assign_hierachial_work to map each eligible class to a bucket.
14526 * - Update the processed set. Any class which hasn't been processed
14527 * that round should have their dependency set shrunken. Processing
14528 * a class makes its dependency set be empty. So if a class wasn't
14529 * eligible, it should have a dependency which was. Therefore the
14530 * class' transitive dependencies should shrink. It should continue
14531 * to shrink until its eventually becomes eligible. The same happens
14532 * if the class' children are turned into split nodes. Each N
14533 * children is replaced with a single split (with no other
14534 * dependencies), so the class' dependencies should shrink. Thus,
14535 * the algorithm eventually terminates.
14538 // Dependency information for a class or split node.
14540 // Transitive dependencies (children) for this class.
14542 // Any split nodes which are dependencies of this class.
14544 // The number of direct children of this class which will be
14545 // processed this round.
14546 size_t processChildren
{0};
14550 // Given a set of roots, greedily add roots and their children to buckets
14551 // via DFS traversal.
14552 template <typename GetDeps
>
14553 std::vector
<HierarchicalWorkBucket
>
14554 dfs_bucketize(SubclassMetadata
& subclassMeta
,
14555 std::vector
<SString
> roots
,
14556 const TSStringToOneT
<std::vector
<SString
>>& splitImmDeps
,
14557 size_t kMaxBucketSize
,
14558 size_t maxClassIdx
,
14559 bool alwaysCreateNew
,
14560 const TSStringSet
& leafs
,
14561 const TSStringSet
& processed
, // already processed
14562 const GetDeps
& getDeps
) {
14563 TSStringSet visited
;
14564 std::vector
<std::vector
<SString
>> rootsToProcess
;
14565 rootsToProcess
.emplace_back();
14566 std::vector
<size_t> rootsCost
;
14568 auto const depsSize
= [&] (SString cls
) {
14569 return getDeps(cls
, getDeps
).deps
.size();
14574 auto const finishBucket
= [&]() {
14576 rootsToProcess
.emplace_back();
14577 rootsCost
.emplace_back(cost
);
14581 auto const addRoot
= [&](SString c
) {
14582 rootsToProcess
.back().emplace_back(c
);
14583 cost
+= depsSize(c
);
14586 auto const processSubgraph
= [&](SString cls
) {
14587 assertx(!processed
.count(cls
));
14590 for (auto const& child
: getDeps(cls
, getDeps
).deps
) {
14591 if (processed
.count(child
)) continue;
14592 if (visited
.count(child
)) continue;
14593 visited
.insert(child
);
14594 // Leaves use special leaf-promotion logic in assign_hierarchial_work
14595 if (leafs
.count(child
)) continue;
14598 if (cost
< kMaxBucketSize
) return;
14602 // Visit immediate children. Recurse until you find a node that has small
14603 // enough transitive deps.
14604 auto const visitSubgraph
= [&](SString root
, auto const& self
) {
14605 if (processed
.count(root
) || visited
.count(root
)) return false;
14606 if (!depsSize(root
)) return false;
14607 auto progress
= false;
14608 visited
.insert(root
);
14610 assertx(IMPLIES(splitImmDeps
.count(root
), depsSize(root
) <= kMaxBucketSize
));
14611 if (depsSize(root
) <= kMaxBucketSize
) {
14612 processSubgraph(root
);
14615 auto const immChildren
= [&] {
14616 auto const it
= subclassMeta
.meta
.find(root
);
14617 assertx(it
!= end(subclassMeta
.meta
));
14618 return it
->second
.children
;
14620 for (auto const& child
: immChildren
) progress
|= self(child
, self
);
14625 // Sort the roots to keep it deterministic
14627 begin(roots
), end(roots
),
14628 [&] (SString a
, SString b
) {
14629 auto const s1
= getDeps(a
, getDeps
).deps
.size();
14630 auto const s2
= getDeps(b
, getDeps
).deps
.size();
14631 if (s1
!= s2
) return s1
> s2
;
14632 return string_data_lt_type
{}(a
, b
);
14636 auto progress
= false;
14637 for (auto const r
: roots
) {
14638 assertx(depsSize(r
)); // Should never be processing one leaf
14639 progress
|= visitSubgraph(r
, visitSubgraph
);
14644 if (rootsToProcess
.back().empty()) rootsToProcess
.pop_back();
14646 auto const buckets
= parallel::gen(
14647 rootsToProcess
.size(),
14648 [&] (size_t bucketIdx
) {
14650 (rootsCost
[bucketIdx
] + (kMaxBucketSize
/2)) / kMaxBucketSize
;
14651 if (!numBuckets
) numBuckets
= 1;
14652 return consistently_bucketize_by_num_buckets(rootsToProcess
[bucketIdx
],
14653 alwaysCreateNew
? rootsToProcess
[bucketIdx
].size() : numBuckets
);
14657 std::vector
<std::vector
<SString
>> flattened
;
14658 for (auto const& b
: buckets
) {
14659 flattened
.insert(flattened
.end(), b
.begin(), b
.end());
14662 auto const work
= build_hierarchical_work(
14666 auto const& deps
= getDeps(c
, getDeps
).deps
;
14667 return std::make_pair(&deps
, true);
14669 [&] (const TSStringSet
&, size_t, SString c
) -> Optional
<size_t> {
14670 if (!leafs
.count(c
)) return std::nullopt
;
14671 return subclassMeta
.meta
.at(c
).idx
;
14678 // While toProcess is not empty:
14679 // 1. Find transitive dep counts
14680 // 2. For each class, calculate splits, find roots, find rootLeafs
14681 // 3. For rootLeafs, consistently hash to make buckets
14682 // 4. For roots, assign subgraphs to buckets via greedy DFS. If buckets get too big,
14683 // split them via consistent hashing.
14684 SubclassWork
build_subclass_lists_assign(SubclassMetadata subclassMeta
) {
14685 trace_time trace
{"build subclass lists assign"};
14686 trace
.ignore_client_stats();
14688 constexpr size_t kBucketSize
= 2000;
14689 constexpr size_t kMaxBucketSize
= 25000;
14693 auto const maxClassIdx
= subclassMeta
.all
.size();
14695 // A processed class/split is considered processed once it's
14696 // assigned to a bucket in a round. Once considered processed, it
14697 // will have no dependencies.
14698 TSStringSet processed
;
14700 TSStringToOneT
<std::unique_ptr
<DepData
>> splitDeps
;
14701 TSStringToOneT
<std::unique_ptr
<BuildSubclassListJob::Split
>> splitPtrs
;
14703 TSStringToOneT
<std::vector
<SString
>> splitImmDeps
;
14705 // Keep creating rounds until all of the classes are assigned to a
14706 // bucket in a round.
14707 auto toProcess
= std::move(subclassMeta
.all
);
14709 if (debug
) tp
.insert(toProcess
.begin(), toProcess
.end());
14711 for (size_t round
= 0; !toProcess
.empty(); ++round
) {
14712 // If we have this many rounds, something has gone wrong, because
14713 // it should require an astronomical amount of classes.
14714 always_assert_flog(
14716 "Worklist still has {} items after {} rounds. "
14717 "This almost certainly means it's stuck in an infinite loop",
14722 // The dependency information for every class, for just this
14723 // round. The information is calculated lazily and recursively by
14725 std::vector
<LockFreeLazy
<DepData
>> deps
{maxClassIdx
};
14727 auto const findDeps
= [&] (SString cls
,
14728 auto const& self
) -> const DepData
& {
14729 // If it's processed, there's implicitly no dependencies
14730 static DepData empty
;
14731 if (processed
.count(cls
)) return empty
;
14733 // Look up the metadata for this class. If we don't find any,
14734 // assume that it's for a split.
14735 auto const it
= subclassMeta
.meta
.find(cls
);
14736 if (it
== end(subclassMeta
.meta
)) {
14737 auto const it2
= splitDeps
.find(cls
);
14738 always_assert(it2
!= end(splitDeps
));
14739 return *it2
->second
;
14741 auto const& meta
= it
->second
;
14742 auto const idx
= meta
.idx
;
14743 assertx(idx
< deps
.size());
14745 // Now that we have the index into the dependency vector, look
14746 // it up, calculating it if it hasn't been already.
14747 return deps
[idx
].get(
14750 for (auto const c
: meta
.children
) {
14751 // At a minimum, we need the immediate deps in order to
14752 // construct the subclass lists for the parent.
14753 out
.deps
.emplace(c
);
14754 if (splitDeps
.count(c
)) out
.edges
.emplace(c
);
14755 auto const& childDeps
= self(c
, self
);
14756 if (childDeps
.deps
.size() <= kMaxBucketSize
) ++out
.processChildren
;
14757 out
.deps
.insert(begin(childDeps
.deps
), end(childDeps
.deps
));
14764 auto const depsSize
= [&] (SString cls
) {
14765 return findDeps(cls
, findDeps
).deps
.size();
14767 // If this class' children needs to be split into split nodes this
14768 // round. This happens if the number of direct children of this
14769 // class which are eligible for processing exceeds the bucket
14771 auto const willSplitChildren
= [&] (SString cls
) {
14772 return findDeps(cls
, findDeps
).processChildren
> kBucketSize
;
14774 // If this class will be processed this round. A class will be
14775 // processed if it's dependencies are less than the maximum bucket
14777 auto const willProcess
= [&] (SString cls
) {
14778 // NB: Not <=. When calculating splits, a class is included
14779 // among it's own dependencies so we need to leave space for one
14781 return depsSize(cls
) < kMaxBucketSize
;
14784 // Process every remaining class in parallel and assign an action
14787 // This class will be processed this round and is a root.
14788 struct Root
{ SString cls
; };
14789 struct RootLeaf
{ SString cls
; };
14790 struct Child
{ SString cls
; };
14791 // This class' children should be split. The class' child list
14792 // will be replaced with the new child list and splits created.
14795 std::vector
<SString
> children
;
14798 std::unique_ptr
<DepData
> deps
;
14799 std::unique_ptr
<BuildSubclassListJob::Split
> ptr
;
14800 std::vector
<SString
> children
;
14802 std::vector
<Data
> splits
;
14804 using Action
= boost::variant
<Root
, Split
, Child
, RootLeaf
>;
14806 auto const actions
= parallel::map(
14808 [&] (SString cls
) {
14809 auto const& meta
= subclassMeta
.meta
.at(cls
);
14811 if (!willSplitChildren(cls
)) {
14812 if (!meta
.parents
.empty()) return Action
{ Child
{cls
} };
14813 if (meta
.children
.empty()) return Action
{ RootLeaf
{cls
} };
14814 return Action
{ Root
{cls
} };
14817 // Otherwise we're going to split some/all of this class'
14818 // children. Once we process those in this round, this class'
14819 // dependencies should be smaller and be able to be processed.
14822 split
.splits
= [&] {
14823 // Group all of the eligible children into buckets, and
14824 // split the buckets to ensure they remain below the maximum
14826 auto const buckets
= split_buckets(
14828 auto const numChildren
= findDeps(cls
, findDeps
).processChildren
;
14829 auto const numBuckets
=
14830 (numChildren
+ kMaxBucketSize
- 1) / kMaxBucketSize
;
14831 assertx(numBuckets
> 0);
14833 std::vector
<std::vector
<SString
>> buckets
;
14834 buckets
.resize(numBuckets
);
14835 for (auto const child
: meta
.children
) {
14836 if (!willProcess(child
)) continue;
14838 consistent_hash(child
->hashStatic(), numBuckets
);
14839 assertx(idx
< numBuckets
);
14840 buckets
[idx
].emplace_back(child
);
14847 [] (const std::vector
<SString
>& b
) { return b
.empty(); }
14852 assertx(!buckets
.empty());
14856 [&] (SString child
) -> const TSStringSet
& {
14857 return findDeps(child
, findDeps
).deps
;
14860 // Each bucket corresponds to a new split node, which will
14861 // contain the results for the children in that bucket.
14862 auto const numSplits
= buckets
.size();
14864 // Actually make the splits and fill their children list.
14865 std::vector
<Split::Data
> splits
;
14866 splits
.reserve(numSplits
);
14867 for (size_t i
= 0; i
< numSplits
; ++i
) {
14868 // The names of a split node are arbitrary, but must be
14869 // unique and not collide with any actual classes.
14870 auto const name
= makeStaticString(
14871 folly::sformat("{}_{}_split;{}", round
, i
, cls
)
14874 auto deps
= std::make_unique
<DepData
>();
14876 std::make_unique
<BuildSubclassListJob::Split
>(name
, cls
);
14877 std::vector
<SString
> children
;
14879 for (auto const child
: buckets
[i
]) {
14880 split
->children
.emplace_back(child
);
14881 children
.emplace_back(child
);
14882 auto const& childDeps
= findDeps(child
, findDeps
).deps
;
14883 deps
->deps
.insert(begin(childDeps
), end(childDeps
));
14884 deps
->deps
.emplace(child
);
14886 assertx(deps
->deps
.size() <= kMaxBucketSize
);
14889 begin(split
->children
),
14890 end(split
->children
),
14891 string_data_lt_type
{}
14894 splits
.emplace_back(
14899 std::move(children
)
14906 // Create the new children list for this class. The new
14907 // children list are any children which won't be processed,
14908 // and the new splits.
14909 for (auto const child
: meta
.children
) {
14910 if (willProcess(child
)) continue;
14911 split
.children
.emplace_back(child
);
14913 for (auto const& [name
, _
, _2
, _3
] : split
.splits
) {
14914 split
.children
.emplace_back(name
);
14917 return Action
{ std::move(split
) };
14921 assertx(actions
.size() == toProcess
.size());
14922 std::vector
<SString
> roots
;
14923 roots
.reserve(actions
.size());
14924 std::vector
<SString
> rootLeafs
;
14926 for (auto const& action
: actions
) {
14930 assertx(!subclassMeta
.meta
.at(r
.cls
).children
.empty());
14931 roots
.emplace_back(r
.cls
);
14934 assertx(subclassMeta
.meta
.at(r
.cls
).children
.empty());
14935 rootLeafs
.emplace_back(r
.cls
);
14936 leafs
.emplace(r
.cls
);
14939 auto const& meta
= subclassMeta
.meta
.at(n
.cls
);
14940 if (meta
.children
.empty()) leafs
.emplace(n
.cls
);
14942 [&] (const Split
& s
) {
14943 auto& meta
= subclassMeta
.meta
.at(s
.cls
);
14944 meta
.children
= s
.children
;
14945 if (meta
.parents
.empty()) {
14946 roots
.emplace_back(s
.cls
);
14948 auto& splits
= const_cast<std::vector
<Split::Data
>&>(s
.splits
);
14949 for (auto& [name
, deps
, ptr
, children
] : splits
) {
14950 splitImmDeps
.emplace(name
, children
);
14951 roots
.emplace_back(name
);
14952 splitDeps
.emplace(name
, std::move(deps
));
14953 splitPtrs
.emplace(name
, std::move(ptr
));
14959 auto work
= dfs_bucketize(
14971 // Bucketize root leafs.
14972 // These are cheaper since we will only be calculating
14973 // name-only func family entries.
14974 for (auto& b
: consistently_bucketize(rootLeafs
, kMaxBucketSize
)) {
14975 work
.emplace_back(HierarchicalWorkBucket
{ std::move(b
) });
14978 std::vector
<SString
> markProcessed
;
14979 markProcessed
.reserve(actions
.size());
14981 // The output of assign_hierarchical_work is just buckets with the
14982 // names. We need to map those to classes or edge nodes and put
14983 // them in the correct data structure in the output. If there's a
14984 // class dependency on a split node, we also need to record an
14985 // edge between them.
14986 auto const add
= [&] (SString cls
, auto& clsList
,
14987 auto& splitList
, auto& edgeList
) {
14988 auto const it
= splitPtrs
.find(cls
);
14989 if (it
== end(splitPtrs
)) {
14990 clsList
.emplace_back(cls
);
14991 for (auto const s
: findDeps(cls
, findDeps
).edges
) {
14992 edgeList
.emplace_back(BuildSubclassListJob::EdgeToSplit
{cls
, s
});
14995 splitList
.emplace_back(it
->second
->name
);
14999 out
.buckets
.emplace_back();
15000 for (auto const& w
: work
) {
15001 assertx(w
.uninstantiable
.empty());
15002 out
.buckets
.back().emplace_back();
15003 auto& bucket
= out
.buckets
.back().back();
15004 // Separate out any of the "roots" which are actually leafs.
15005 for (auto const cls
: w
.classes
) {
15006 bucket
.cost
+= depsSize(cls
);
15007 markProcessed
.emplace_back(cls
);
15008 if (leafs
.count(cls
)) {
15010 bucket
.leafs
.emplace_back(cls
);
15012 add(cls
, bucket
.classes
, bucket
.splits
, bucket
.edges
);
15015 for (auto const cls
: w
.deps
) {
15016 add(cls
, bucket
.deps
, bucket
.splitDeps
, bucket
.edges
);
15020 begin(bucket
.edges
), end(bucket
.edges
),
15021 [] (const BuildSubclassListJob::EdgeToSplit
& a
,
15022 const BuildSubclassListJob::EdgeToSplit
& b
) {
15023 if (string_data_lt_type
{}(a
.cls
, b
.cls
)) return true;
15024 if (string_data_lt_type
{}(b
.cls
, a
.cls
)) return false;
15025 return string_data_lt_type
{}(a
.split
, b
.split
);
15028 std::sort(begin(bucket
.leafs
), end(bucket
.leafs
), string_data_lt_type
{});
15032 begin(out
.buckets
.back()), end(out
.buckets
.back()),
15033 [] (const SubclassWork::Bucket
& a
,
15034 const SubclassWork::Bucket
& b
) {
15035 return a
.cost
> b
.cost
;
15039 // Update the processed set. We have to defer that until here
15040 // because we'd check it when building the buckets.
15041 processed
.insert(begin(markProcessed
), end(markProcessed
));
15043 auto const before
= toProcess
.size();
15046 begin(toProcess
), end(toProcess
),
15047 [&] (SString c
) { return processed
.count(c
); }
15051 always_assert(toProcess
.size() < before
);
15054 // Keep all split nodes created in the output
15055 for (auto& [name
, p
] : splitPtrs
) out
.allSplits
.emplace(name
, std::move(p
));
15057 // Ensure we create an output for everything exactly once
15059 for (size_t round
= 0; round
< out
.buckets
.size(); ++round
) {
15060 auto const& r
= out
.buckets
[round
];
15061 for (size_t i
= 0; i
< r
.size(); ++i
) {
15062 auto const& bucket
= r
[i
];
15063 for (auto const c
: bucket
.classes
) always_assert(tp
.erase(c
));
15064 for (auto const l
: bucket
.leafs
) always_assert(tp
.erase(l
));
15067 assertx(tp
.empty());
15070 if (Trace::moduleEnabled(Trace::hhbbc_index
, 4)) {
15071 for (size_t round
= 0; round
< out
.buckets
.size(); ++round
) {
15078 auto const& r
= out
.buckets
[round
];
15079 for (size_t i
= 0; i
< r
.size(); ++i
) {
15080 auto const& bucket
= r
[i
];
15081 FTRACE(5, "build subclass lists round #{} work item #{}:\n", round
, i
);
15082 FTRACE(5, " classes ({}):\n", bucket
.classes
.size());
15083 for (auto const DEBUG_ONLY c
: bucket
.classes
) FTRACE(6, " {}\n", c
);
15084 FTRACE(5, " splits ({}):\n", bucket
.splits
.size());
15085 for (auto const DEBUG_ONLY s
: bucket
.splits
) FTRACE(6, " {}\n", s
);
15086 FTRACE(5, " deps ({}):\n", bucket
.deps
.size());
15087 for (auto const DEBUG_ONLY d
: bucket
.deps
) FTRACE(6, " {}\n", d
);
15088 FTRACE(5, " split deps ({}):\n", bucket
.splitDeps
.size());
15089 for (auto const DEBUG_ONLY s
: bucket
.splitDeps
) {
15090 FTRACE(6, " {}\n", s
);
15092 FTRACE(5, " leafs ({}):\n", bucket
.leafs
.size());
15093 for (auto const DEBUG_ONLY c
: bucket
.leafs
) FTRACE(6, " {}\n", c
);
15094 FTRACE(5, " edges ({}):\n", bucket
.edges
.size());
15095 for (DEBUG_ONLY
auto const& e
: bucket
.edges
) {
15096 FTRACE(6, " {} -> {}\n", e
.cls
, e
.split
);
15098 nc
+= bucket
.classes
.size();
15099 ns
+= bucket
.splits
.size();
15100 nd
+= bucket
.deps
.size();
15101 nsd
+= bucket
.splitDeps
.size();
15102 nl
+= bucket
.leafs
.size();
15104 FTRACE(4, "BSL round #{} stats\n"
15111 round
, r
.size(), nc
, ns
, nd
, nsd
, nl
15119 void build_subclass_lists(IndexData
& index
,
15120 SubclassMetadata meta
,
15121 InitTypesMetadata
& initTypesMeta
) {
15122 trace_time tracer
{"build subclass lists", index
.sample
};
15123 tracer
.ignore_client_stats();
15125 using namespace folly::gen
;
15127 // Mapping of splits to their Ref. We only upload a split when we're
15128 // going to run a job which it is part of the output.
15129 TSStringToOneT
<UniquePtrRef
<BuildSubclassListJob::Split
>> splitsToRefs
;
15131 FSStringToOneT
<hphp_fast_set
<FuncFamily2::Id
>> funcFamilyDeps
;
15133 // Use the metadata to assign to rounds and buckets.
15134 auto work
= build_subclass_lists_assign(std::move(meta
));
15136 // We need to defer updates to data structures until after all the
15137 // jobs in a round have completed. Otherwise we could update a ref
15138 // to a class at the same time another thread is reading it.
15141 std::tuple
<SString
, UniquePtrRef
<ClassInfo2
>, UniquePtrRef
<php::Class
>>
15144 std::pair
<SString
, UniquePtrRef
<BuildSubclassListJob::Split
>>
15147 std::pair
<FuncFamily2::Id
, Ref
<FuncFamilyGroup
>>
15150 std::pair
<SString
, hphp_fast_set
<FuncFamily2::Id
>>
15152 std::vector
<std::pair
<SString
, UniquePtrRef
<ClassInfo2
>>> leafs
;
15153 std::vector
<std::pair
<SString
, FuncFamilyEntry
>> nameOnly
;
15154 std::vector
<std::pair
<SString
, SString
>> candidateRegOnlyEquivs
;
15155 TSStringToOneT
<TSStringSet
> cnsBases
;
15158 auto const run
= [&] (SubclassWork::Bucket bucket
, size_t round
)
15159 -> coro::Task
<Updates
> {
15160 co_await
coro::co_reschedule_on_current_executor
;
15162 if (bucket
.classes
.empty() &&
15163 bucket
.splits
.empty() &&
15164 bucket
.leafs
.empty()) {
15165 assertx(bucket
.splitDeps
.empty());
15166 co_return Updates
{};
15169 // We shouldn't get closures or Closure in any of this.
15171 for (auto const c
: bucket
.classes
) {
15172 always_assert(!c
->tsame(s_Closure
.get()));
15173 always_assert(!is_closure_name(c
));
15175 for (auto const c
: bucket
.deps
) {
15176 always_assert(!c
->tsame(s_Closure
.get()));
15177 always_assert(!is_closure_name(c
));
15181 auto classes
= from(bucket
.classes
)
15182 | map([&] (SString c
) { return index
.classInfoRefs
.at(c
); })
15183 | as
<std::vector
>();
15184 auto deps
= from(bucket
.deps
)
15185 | map([&] (SString c
) { return index
.classInfoRefs
.at(c
); })
15186 | as
<std::vector
>();
15187 auto leafs
= from(bucket
.leafs
)
15188 | map([&] (SString c
) { return index
.classInfoRefs
.at(c
); })
15189 | as
<std::vector
>();
15190 auto splits
= from(bucket
.splits
)
15191 | map([&] (SString s
) {
15192 std::unique_ptr
<BuildSubclassListJob::Split
> split
=
15193 std::move(work
.allSplits
.at(s
));
15197 | as
<std::vector
>();
15198 auto splitDeps
= from(bucket
.splitDeps
)
15199 | map([&] (SString s
) { return splitsToRefs
.at(s
); })
15200 | as
<std::vector
>();
15202 (from(bucket
.classes
) + from(bucket
.deps
) + from(bucket
.leafs
))
15203 | map([&] (SString c
) { return index
.classRefs
.at(c
); })
15204 | as
<std::vector
>();
15206 std::vector
<Ref
<FuncFamilyGroup
>> funcFamilies
;
15208 // Provide the func families associated with any dependency
15209 // classes going into this job. We only need to do this after
15210 // the first round because in the first round all dependencies
15211 // are leafs and won't have any func families.
15212 for (auto const c
: bucket
.deps
) {
15213 if (auto const deps
= folly::get_ptr(funcFamilyDeps
, c
)) {
15214 for (auto const& d
: *deps
) {
15215 funcFamilies
.emplace_back(index
.funcFamilyRefs
.at(d
));
15219 // Keep the func families in deterministic order and avoid
15221 std::sort(begin(funcFamilies
), end(funcFamilies
));
15222 funcFamilies
.erase(
15223 std::unique(begin(funcFamilies
), end(funcFamilies
)),
15227 assertx(funcFamilyDeps
.empty());
15228 assertx(index
.funcFamilyRefs
.empty());
15231 // ClassInfos and any dependency splits should already be
15232 // stored. Any splits as output of the job, or edges need to be
15233 // uploaded, however.
15234 auto [splitRefs
, edges
, config
] = co_await
coro::collectAll(
15235 index
.client
->storeMulti(std::move(splits
)),
15236 index
.client
->storeMulti(std::move(bucket
.edges
)),
15237 index
.configRef
->getCopy()
15240 Client::ExecMetadata metadata
{
15241 .job_key
= folly::sformat(
15242 "build subclass list {}",
15244 if (!bucket
.classes
.empty()) return bucket
.classes
[0];
15245 if (!bucket
.splits
.empty()) return bucket
.splits
[0];
15246 assertx(!bucket
.leafs
.empty());
15247 return bucket
.leafs
[0];
15252 auto results
= co_await
15253 index
.client
->exec(
15254 s_buildSubclassJob
,
15258 std::move(classes
),
15261 std::move(splitRefs
),
15262 std::move(splitDeps
),
15263 std::move(phpClasses
),
15265 std::move(funcFamilies
)
15268 std::move(metadata
)
15270 // Every job is a single work-unit, so we should only ever get one
15271 // result for each one.
15272 assertx(results
.size() == 1);
15273 auto& [cinfoRefs
, outSplitRefs
, clsRefs
, ffRefs
, leafRefs
, outMetaRef
]
15275 assertx(cinfoRefs
.size() == bucket
.classes
.size());
15276 assertx(outSplitRefs
.size() == bucket
.splits
.size());
15277 assertx(clsRefs
.size() == bucket
.classes
.size());
15278 assertx(leafRefs
.size() == bucket
.leafs
.size());
15280 auto outMeta
= co_await index
.client
->load(std::move(outMetaRef
));
15281 assertx(outMeta
.newFuncFamilyIds
.size() == ffRefs
.size());
15282 assertx(outMeta
.funcFamilyDeps
.size() == cinfoRefs
.size());
15283 assertx(outMeta
.regOnlyEquivCandidates
.size() == cinfoRefs
.size());
15286 updates
.classes
.reserve(bucket
.classes
.size());
15287 updates
.splits
.reserve(bucket
.splits
.size());
15288 updates
.funcFamilies
.reserve(outMeta
.newFuncFamilyIds
.size());
15289 updates
.funcFamilyDeps
.reserve(outMeta
.funcFamilyDeps
.size());
15290 updates
.nameOnly
.reserve(outMeta
.nameOnly
.size());
15291 updates
.leafs
.reserve(bucket
.leafs
.size());
15293 for (size_t i
= 0, size
= bucket
.classes
.size(); i
< size
; ++i
) {
15294 updates
.classes
.emplace_back(bucket
.classes
[i
], cinfoRefs
[i
], clsRefs
[i
]);
15296 for (size_t i
= 0, size
= bucket
.splits
.size(); i
< size
; ++i
) {
15297 updates
.splits
.emplace_back(bucket
.splits
[i
], outSplitRefs
[i
]);
15299 for (size_t i
= 0, size
= bucket
.leafs
.size(); i
< size
; ++i
) {
15300 updates
.leafs
.emplace_back(bucket
.leafs
[i
], leafRefs
[i
]);
15302 for (size_t i
= 0, size
= outMeta
.newFuncFamilyIds
.size(); i
< size
; ++i
) {
15303 auto const ref
= ffRefs
[i
];
15304 for (auto const& id
: outMeta
.newFuncFamilyIds
[i
]) {
15305 updates
.funcFamilies
.emplace_back(id
, ref
);
15308 for (size_t i
= 0, size
= outMeta
.funcFamilyDeps
.size(); i
< size
; ++i
) {
15309 updates
.funcFamilyDeps
.emplace_back(
15311 std::move(outMeta
.funcFamilyDeps
[i
])
15314 updates
.nameOnly
= std::move(outMeta
.nameOnly
);
15315 for (size_t i
= 0, size
= outMeta
.regOnlyEquivCandidates
.size();
15317 auto const name
= bucket
.classes
[i
];
15318 for (auto const c
: outMeta
.regOnlyEquivCandidates
[i
]) {
15319 updates
.candidateRegOnlyEquivs
.emplace_back(name
, c
);
15322 updates
.cnsBases
= std::move(outMeta
.cnsBases
);
15328 trace_time tracer2
{"build subclass lists work", index
.sample
};
15330 for (size_t roundNum
= 0; roundNum
< work
.buckets
.size(); ++roundNum
) {
15331 auto& round
= work
.buckets
[roundNum
];
15332 // In each round, run all of the work for each bucket
15333 // simultaneously, gathering up updates from each job.
15334 auto const updates
= coro::blockingWait(coro::collectAllRange(
15337 | map([&] (SubclassWork::Bucket
&& b
) {
15338 return run(std::move(b
), roundNum
)
15339 .scheduleOn(index
.executor
->sticky());
15341 | as
<std::vector
>()
15344 // Apply the updates to ClassInfo refs. We can do this
15345 // concurrently because every ClassInfo is already in the map, so
15346 // we can update in place (without mutating the map).
15347 parallel::for_each(
15349 [&] (const Updates
& u
) {
15350 for (auto const& [name
, cinfo
, cls
] : u
.classes
) {
15351 index
.classInfoRefs
.at(name
) = cinfo
;
15352 index
.classRefs
.at(name
) = cls
;
15354 for (auto const& [name
, cinfo
] : u
.leafs
) {
15355 index
.classInfoRefs
.at(name
) = cinfo
;
15360 // However updating splitsToRefs cannot be, because we're mutating
15361 // the map by inserting into it. However there's a relatively
15362 // small number of splits, so this should be fine.
15363 parallel::parallel(
15365 for (auto const& u
: updates
) {
15366 for (auto const& [name
, ref
] : u
.splits
) {
15367 always_assert(splitsToRefs
.emplace(name
, ref
).second
);
15372 for (auto const& u
: updates
) {
15373 for (auto const& [id
, ref
] : u
.funcFamilies
) {
15374 // The same FuncFamily can be grouped into multiple
15375 // different groups. Prefer the group that's smaller and
15376 // if they're the same size, use the one with the lowest
15377 // id to keep determinism.
15378 auto const& [existing
, inserted
] =
15379 index
.funcFamilyRefs
.emplace(id
, ref
);
15380 if (inserted
) continue;
15381 if (existing
->second
.id().m_size
< ref
.id().m_size
) continue;
15382 if (ref
.id().m_size
< existing
->second
.id().m_size
) {
15383 existing
->second
= ref
;
15386 if (existing
->second
.id() <= ref
.id()) continue;
15387 existing
->second
= ref
;
15392 for (auto& u
: updates
) {
15393 for (auto& [name
, ids
] : u
.funcFamilyDeps
) {
15395 funcFamilyDeps
.emplace(name
, std::move(ids
)).second
15401 for (auto& u
: updates
) {
15402 for (auto& [name
, entry
] : u
.nameOnly
) {
15403 initTypesMeta
.nameOnlyFF
[name
].emplace_back(std::move(entry
));
15405 for (auto [name
, candidate
] : u
.candidateRegOnlyEquivs
) {
15406 initTypesMeta
.classes
[name
]
15407 .candidateRegOnlyEquivs
.emplace(candidate
);
15412 for (auto& u
: updates
) {
15413 for (auto& [n
, o
] : u
.cnsBases
) {
15415 index
.classToCnsBases
.emplace(n
, std::move(o
)).second
15424 splitsToRefs
.clear();
15425 funcFamilyDeps
.clear();
15426 work
.buckets
.clear();
15427 work
.allSplits
.clear();
15430 //////////////////////////////////////////////////////////////////////
15433 * Initialize the return-types of functions and methods from their
15434 * type-hints. Also set AttrInitialSatisfiesTC on properties if
15435 * appropriate (which must be done after types are initialized).
15437 struct InitTypesJob
{
15438 static std::string
name() { return "hhbbc-init-types"; }
15439 static void init(const Config
& config
) {
15440 process_init(config
.o
, config
.gd
, false);
15441 ClassGraph::init();
15443 static void fini() { ClassGraph::destroy(); }
15445 using Output
= Multi
<
15446 Variadic
<std::unique_ptr
<php::Class
>>,
15447 Variadic
<std::unique_ptr
<ClassInfo2
>>,
15448 Variadic
<std::unique_ptr
<php::Func
>>,
15449 Variadic
<std::unique_ptr
<FuncInfo2
>>
15451 static Output
run(Variadic
<std::unique_ptr
<php::Class
>> classes
,
15452 Variadic
<std::unique_ptr
<ClassInfo2
>> cinfos
,
15453 Variadic
<std::unique_ptr
<php::Func
>> funcs
,
15454 Variadic
<std::unique_ptr
<FuncInfo2
>> finfos
,
15455 Variadic
<std::unique_ptr
<ClassInfo2
>> cinfoDeps
) {
15458 for (auto const& cls
: classes
.vals
) {
15459 always_assert(index
.classes
.emplace(cls
->name
, cls
.get()).second
);
15460 for (auto const& clo
: cls
->closures
) {
15461 always_assert(index
.classes
.emplace(clo
->name
, clo
.get()).second
);
15465 // All of the classes which might be a regular only equivalent
15466 // have been provided to the job. So, we can now definitely set
15467 // the regular only equivalent (if necessary). We need to do this
15468 // before setting the initial types because we need that
15469 // information to canonicalize.
15470 for (auto const& cinfo
: cinfos
.vals
) {
15471 always_assert(index
.classInfos
.emplace(cinfo
->name
, cinfo
.get()).second
);
15472 cinfo
->classGraph
.setRegOnlyEquivs();
15473 // If this is a regular class, we don't need the "expanded"
15474 // method family information anymore, so clear it here to save
15476 if (cinfo
->isRegularClass
) {
15478 cinfo
->methodFamilies
,
15479 [&] (auto const& e
) { return !cinfo
->methods
.count(e
.first
); }
15483 for (auto const& clo
: cinfo
->closures
) {
15484 always_assert(index
.classInfos
.emplace(clo
->name
, clo
.get()).second
);
15485 clo
->classGraph
.setRegOnlyEquivs();
15488 for (auto const& cinfo
: cinfoDeps
.vals
) {
15489 always_assert(index
.classInfos
.emplace(cinfo
->name
, cinfo
.get()).second
);
15490 cinfo
->classGraph
.setRegOnlyEquivs();
15491 for (auto const& clo
: cinfo
->closures
) {
15492 always_assert(index
.classInfos
.emplace(clo
->name
, clo
.get()).second
);
15493 clo
->classGraph
.setRegOnlyEquivs();
15497 auto const onCls
= [&] (php::Class
& cls
, ClassInfo2
& cinfo
) {
15498 assertx(cls
.name
->tsame(cinfo
.name
));
15499 assertx(cinfo
.funcInfos
.size() == cls
.methods
.size());
15501 unresolve_missing(index
, cls
);
15502 set_bad_initial_prop_values(index
, cls
, cinfo
);
15503 for (size_t i
= 0, size
= cls
.methods
.size(); i
< size
; ++i
) {
15504 auto const& func
= cls
.methods
[i
];
15505 auto& finfo
= cinfo
.funcInfos
[i
];
15506 assertx(func
->name
== finfo
->name
);
15507 assertx(finfo
->returnTy
.is(BInitCell
));
15508 finfo
->returnTy
= initial_return_type(index
, *func
);
15512 assertx(classes
.vals
.size() == cinfos
.vals
.size());
15513 for (size_t i
= 0, size
= classes
.vals
.size(); i
< size
; ++i
) {
15514 auto& cls
= classes
.vals
[i
];
15515 auto& cinfo
= cinfos
.vals
[i
];
15516 onCls(*cls
, *cinfo
);
15518 assertx(cls
->closures
.size() == cinfo
->closures
.size());
15519 for (size_t j
= 0, size2
= cls
->closures
.size(); j
< size2
; ++j
) {
15520 auto& clo
= cls
->closures
[j
];
15521 auto& cloinfo
= cinfo
->closures
[j
];
15522 onCls(*clo
, *cloinfo
);
15526 assertx(funcs
.vals
.size() == finfos
.vals
.size());
15527 for (size_t i
= 0, size
= funcs
.vals
.size(); i
< size
; ++i
) {
15528 auto const& func
= funcs
.vals
[i
];
15529 auto& finfo
= finfos
.vals
[i
];
15530 assertx(func
->name
== finfo
->name
);
15531 assertx(finfo
->returnTy
.is(BInitCell
));
15532 unresolve_missing(index
, *func
);
15533 finfo
->returnTy
= initial_return_type(index
, *func
);
15536 return std::make_tuple(
15537 std::move(classes
),
15546 struct LocalIndex
{
15547 TSStringToOneT
<const ClassInfo2
*> classInfos
;
15548 TSStringToOneT
<const php::Class
*> classes
;
15551 static void unresolve_missing(const LocalIndex
& index
, TypeConstraint
& tc
) {
15552 if (!tc
.isSubObject()) return;
15553 auto const name
= tc
.clsName();
15554 if (index
.classInfos
.count(name
)) return;
15556 4, "Unresolving type-constraint for '{}' because it does not exist\n",
15562 static void unresolve_missing(const LocalIndex
& index
, php::Func
& func
) {
15563 for (auto& p
: func
.params
) {
15564 unresolve_missing(index
, p
.typeConstraint
);
15565 for (auto& ub
: p
.upperBounds
.m_constraints
) unresolve_missing(index
, ub
);
15567 unresolve_missing(index
, func
.retTypeConstraint
);
15568 for (auto& ub
: func
.returnUBs
.m_constraints
) unresolve_missing(index
, ub
);
15571 static void unresolve_missing(const LocalIndex
& index
, php::Class
& cls
) {
15572 if (cls
.attrs
& AttrEnum
) unresolve_missing(index
, cls
.enumBaseTy
);
15573 for (auto& meth
: cls
.methods
) unresolve_missing(index
, *meth
);
15574 for (auto& prop
: cls
.properties
) {
15575 unresolve_missing(index
, prop
.typeConstraint
);
15576 for (auto& ub
: prop
.ubs
.m_constraints
) unresolve_missing(index
, ub
);
15580 static Type
initial_return_type(const LocalIndex
& index
, const php::Func
& f
) {
15582 Trace::hhbbc_index
, kSystemLibBump
, is_systemlib_part(f
.unit
)
15585 auto const ty
= [&] {
15586 // Return type of native functions is calculated differently.
15587 if (f
.isNative
) return native_function_return_type(&f
);
15589 if ((f
.attrs
& AttrBuiltin
) || f
.isMemoizeWrapper
) return TInitCell
;
15591 if (f
.isGenerator
) {
15593 // Async generators always return AsyncGenerator object.
15594 return objExact(res::Class::get(s_AsyncGenerator
.get()));
15596 // Non-async generators always return Generator object.
15597 return objExact(res::Class::get(s_Generator
.get()));
15600 auto const make_type
= [&] (const TypeConstraint
& tc
) {
15601 auto lookup
= type_from_constraint(
15604 [&] (SString name
) -> Optional
<res::Class
> {
15605 if (auto const ci
= folly::get_default(index
.classInfos
, name
)) {
15606 auto const c
= res::Class::get(*ci
);
15607 assertx(c
.isComplete());
15610 return std::nullopt
;
15612 [&] () -> Optional
<Type
> {
15613 if (!f
.cls
) return std::nullopt
;
15614 auto const& cls
= [&] () -> const php::Class
& {
15615 if (!f
.cls
->closureContextCls
) return *f
.cls
;
15617 folly::get_default(index
.classes
, f
.cls
->closureContextCls
);
15618 always_assert_flog(
15620 "When processing return-type for {}, "
15621 "tried to access missing class {}",
15623 f
.cls
->closureContextCls
15627 if (cls
.attrs
& AttrTrait
) return std::nullopt
;
15628 auto const c
= res::Class::get(cls
.name
);
15629 assertx(c
.isComplete());
15630 return subCls(c
, true);
15633 if (lookup
.coerceClassToString
== TriBool::Yes
) {
15634 lookup
.upper
= promote_classish(std::move(lookup
.upper
));
15635 } else if (lookup
.coerceClassToString
== TriBool::Maybe
) {
15636 lookup
.upper
|= TSStr
;
15638 return unctx(std::move(lookup
.upper
));
15641 auto const process
= [&] (const TypeConstraint
& tc
,
15642 const TypeIntersectionConstraint
& ubs
) {
15643 auto ret
= TInitCell
;
15644 ret
= intersection_of(std::move(ret
), make_type(tc
));
15645 for (auto const& ub
: ubs
.m_constraints
) {
15646 ret
= intersection_of(std::move(ret
), make_type(ub
));
15651 auto ret
= process(f
.retTypeConstraint
, f
.returnUBs
);
15652 if (f
.hasInOutArgs
&& !ret
.is(BBottom
)) {
15653 std::vector
<Type
> types
;
15654 types
.reserve(f
.params
.size() + 1);
15655 types
.emplace_back(std::move(ret
));
15656 for (auto const& p
: f
.params
) {
15657 if (!p
.inout
) continue;
15658 auto t
= process(p
.typeConstraint
, p
.upperBounds
);
15659 if (t
.is(BBottom
)) return TBottom
;
15660 types
.emplace_back(std::move(t
));
15662 std::reverse(begin(types
)+1, end(types
));
15663 ret
= vec(std::move(types
));
15667 // Async functions always return WaitH<T>, where T is the type
15668 // returned internally.
15669 return wait_handle(std::move(ret
));
15674 FTRACE(3, "Initial return type for {}: {}\n",
15675 func_fullname(f
), show(ty
));
15679 static void set_bad_initial_prop_values(const LocalIndex
& index
,
15681 ClassInfo2
& cinfo
) {
15683 Trace::hhbbc_index
, kSystemLibBump
, is_systemlib_part(cls
.unit
)
15686 assertx(cinfo
.hasBadInitialPropValues
);
15688 auto const isClosure
= is_closure(cls
);
15690 cinfo
.hasBadInitialPropValues
= false;
15691 for (auto& prop
: cls
.properties
) {
15692 assertx(!(prop
.attrs
& AttrInitialSatisfiesTC
));
15694 // Check whether the property's initial value satisfies it's
15696 auto const initialSatisfies
= [&] {
15697 if (isClosure
) return true;
15698 if (is_used_trait(cls
)) return false;
15700 // Any property with an unresolved type-constraint here might
15701 // fatal when we initialize the class.
15702 if (prop
.typeConstraint
.isUnresolved()) return false;
15703 for (auto const& ub
: prop
.ubs
.m_constraints
) {
15704 if (ub
.isUnresolved()) return false;
15707 if (prop
.attrs
& (AttrSystemInitialValue
| AttrLateInit
)) return true;
15709 auto const initial
= from_cell(prop
.val
);
15710 if (initial
.subtypeOf(BUninit
)) return false;
15712 auto const make_type
= [&] (const TypeConstraint
& tc
) {
15713 auto lookup
= type_from_constraint(
15716 [&] (SString name
) -> Optional
<res::Class
> {
15717 if (auto const ci
= folly::get_default(index
.classInfos
, name
)) {
15718 auto const c
= res::Class::get(*ci
);
15719 assertx(c
.isComplete());
15722 return std::nullopt
;
15724 [&] () -> Optional
<Type
> {
15725 auto const& ctx
= [&] () -> const php::Class
& {
15726 if (!cls
.closureContextCls
) return cls
;
15728 folly::get_default(index
.classes
, cls
.closureContextCls
);
15729 always_assert_flog(
15731 "When processing bad initial prop values for {}, "
15732 "tried to access missing class {}",
15734 cls
.closureContextCls
15738 if (ctx
.attrs
& AttrTrait
) return std::nullopt
;
15739 auto const c
= res::Class::get(ctx
.name
);
15740 assertx(c
.isComplete());
15741 return subCls(c
, true);
15744 return unctx(std::move(lookup
.lower
));
15747 if (!initial
.subtypeOf(make_type(prop
.typeConstraint
))) return false;
15748 for (auto const& ub
: prop
.ubs
.m_constraints
) {
15749 if (!initial
.subtypeOf(make_type(ub
))) return false;
15754 if (initialSatisfies
) {
15755 attribute_setter(prop
.attrs
, true, AttrInitialSatisfiesTC
);
15757 cinfo
.hasBadInitialPropValues
= true;
15764 * "Fixups" a php::Unit by removing specified funcs from it, and
15765 * adding specified classes. This is needed to add closures created
15766 * from trait flattening into their associated units. While we're
15767 * doing this, we also remove redundant meth caller funcs here
15768 * (because it's convenient).
15770 struct UnitFixupJob
{
15771 static std::string
name() { return "hhbbc-unit-fixup"; }
15772 static void init(const Config
& config
) {
15773 process_init(config
.o
, config
.gd
, false);
15775 static void fini() {}
15777 static std::unique_ptr
<php::Unit
> run(std::unique_ptr
<php::Unit
> unit
,
15778 const InitTypesMetadata::Fixup
& fixup
) {
15779 SCOPE_ASSERT_DETAIL("unit") { return unit
->filename
->toCppString(); };
15781 if (!fixup
.removeFunc
.empty()) {
15782 // If we want to remove a func, it should be in this unit.
15783 auto DEBUG_ONLY erased
= false;
15786 begin(unit
->funcs
),
15788 [&] (SString func
) {
15789 // This is a kinda dumb O(N^2) algorithm, but these lists
15790 // are typicaly size 1.
15791 auto const erase
= std::any_of(
15792 begin(fixup
.removeFunc
),
15793 end(fixup
.removeFunc
),
15794 [&] (SString remove
) { return remove
== func
; }
15796 if (erase
) erased
= true;
15805 auto const before
= unit
->classes
.size();
15806 unit
->classes
.insert(
15807 end(unit
->classes
),
15808 begin(fixup
.addClass
),
15809 end(fixup
.addClass
)
15811 // Only sort the newly added classes. The order of the existing
15812 // classes is visible to programs.
15814 begin(unit
->classes
) + before
,
15815 end(unit
->classes
),
15816 string_data_lt_type
{}
15819 std::adjacent_find(
15820 begin(unit
->classes
), end(unit
->classes
),
15821 string_data_tsame
{}) == end(unit
->classes
)
15828 * BuildSubclassListJob produces name-only func family entries. This
15829 * job merges entries for the same name into one.
15831 struct AggregateNameOnlyJob
: public BuildSubclassListJob
{
15832 static std::string
name() { return "hhbbc-aggregate-name-only"; }
15834 struct OutputMeta
{
15835 std::vector
<std::vector
<FuncFamily2::Id
>> newFuncFamilyIds
;
15836 std::vector
<FuncFamilyEntry
> nameOnly
;
15837 template <typename SerDe
> void serde(SerDe
& sd
) {
15838 ScopedStringDataIndexer _
;
15839 sd(newFuncFamilyIds
)
15844 using Output
= Multi
<
15845 Variadic
<FuncFamilyGroup
>,
15850 run(std::vector
<std::pair
<SString
, std::vector
<FuncFamilyEntry
>>> allEntries
,
15851 Variadic
<FuncFamilyGroup
> funcFamilies
) {
15854 for (auto& group
: funcFamilies
.vals
) {
15855 for (auto& ff
: group
.m_ffs
) {
15856 auto const id
= ff
->m_id
;
15857 // We could have multiple groups which contain the same
15858 // FuncFamily, so don't assert uniqueness here. We'll just
15859 // take the first one we see (they should all be equivalent).
15860 index
.funcFamilies
.emplace(id
, std::move(ff
));
15866 for (auto const& [name
, entries
] : allEntries
) {
15867 Data::MethInfo info
;
15868 info
.complete
= false;
15869 info
.regularComplete
= false;
15871 for (auto const& entry
: entries
) {
15872 auto entryInfo
= meth_info_from_func_family_entry(index
, entry
);
15873 for (auto const& meth
: entryInfo
.regularMeths
) {
15874 if (info
.regularMeths
.count(meth
)) continue;
15875 info
.regularMeths
.emplace(meth
);
15876 info
.nonRegularPrivateMeths
.erase(meth
);
15877 info
.nonRegularMeths
.erase(meth
);
15879 for (auto const& meth
: entryInfo
.nonRegularPrivateMeths
) {
15880 if (info
.regularMeths
.count(meth
) ||
15881 info
.nonRegularPrivateMeths
.count(meth
)) {
15884 info
.nonRegularPrivateMeths
.emplace(meth
);
15885 info
.nonRegularMeths
.erase(meth
);
15887 for (auto const& meth
: entryInfo
.nonRegularMeths
) {
15888 if (info
.regularMeths
.count(meth
) ||
15889 info
.nonRegularPrivateMeths
.count(meth
) ||
15890 info
.nonRegularMeths
.count(meth
)) {
15893 info
.nonRegularMeths
.emplace(meth
);
15896 if (entryInfo
.allStatic
) {
15897 if (!info
.allStatic
) {
15898 info
.allStatic
= std::move(*entryInfo
.allStatic
);
15900 *info
.allStatic
|= *entryInfo
.allStatic
;
15903 if (entryInfo
.regularStatic
) {
15904 if (!info
.regularStatic
) {
15905 info
.regularStatic
= std::move(*entryInfo
.regularStatic
);
15907 *info
.regularStatic
|= *entryInfo
.regularStatic
;
15912 meta
.nameOnly
.emplace_back(
15913 make_method_family_entry(index
, name
, std::move(info
))
15917 Variadic
<FuncFamilyGroup
> funcFamilyGroups
;
15918 group_func_families(index
, funcFamilyGroups
.vals
, meta
.newFuncFamilyIds
);
15920 return std::make_tuple(
15921 std::move(funcFamilyGroups
),
15927 Job
<InitTypesJob
> s_initTypesJob
;
15928 Job
<UnitFixupJob
> s_unitFixupJob
;
15929 Job
<AggregateNameOnlyJob
> s_aggregateNameOnlyJob
;
15931 // Initialize return-types, fixup units, and aggregate name-only
15932 // func-families all at once.
15933 void init_types(IndexData
& index
, InitTypesMetadata meta
) {
15934 trace_time tracer
{"init types", index
.sample
};
15936 constexpr size_t kTypesBucketSize
= 2000;
15937 constexpr size_t kFixupsBucketSize
= 3000;
15938 constexpr size_t kAggregateBucketSize
= 3000;
15940 auto typeBuckets
= consistently_bucketize(
15942 // Temporarily suppress case collision logging
15943 auto oldLogLevel
= Cfg::Eval::LogTsameCollisions
;
15944 Cfg::Eval::LogTsameCollisions
= 0;
15945 SCOPE_EXIT
{ Cfg::Eval::LogTsameCollisions
= oldLogLevel
; };
15947 std::vector
<SString
> roots
;
15948 roots
.reserve(meta
.classes
.size() + meta
.funcs
.size());
15949 for (auto const& [name
, _
] : meta
.classes
) {
15950 roots
.emplace_back(name
);
15952 for (auto const& [name
, _
] : meta
.funcs
) {
15953 // A class and a func could have the same name. Avoid
15954 // duplicates. If we do have a name collision it just means
15955 // the func and class will be assigned to the same bucket.
15956 if (meta
.classes
.count(name
)) continue;
15957 roots
.emplace_back(name
);
15964 auto fixupBuckets
= consistently_bucketize(
15966 std::vector
<SString
> sorted
;
15967 sorted
.reserve(meta
.fixups
.size());
15968 for (auto& [unit
, _
] : meta
.fixups
) sorted
.emplace_back(unit
);
15969 std::sort(sorted
.begin(), sorted
.end(), string_data_lt
{});
15975 auto aggregateBuckets
= consistently_bucketize(
15977 std::vector
<SString
> sorted
;
15978 sorted
.reserve(meta
.nameOnlyFF
.size());
15979 for (auto const& [name
, entries
] : meta
.nameOnlyFF
) {
15980 if (entries
.size() <= 1) {
15981 // If there's only one entry for a name, there's nothing to
15982 // aggregate, and can be inserted directly as the final
15985 index
.nameOnlyMethodFamilies
.emplace(name
, entries
[0]).second
15989 // Otherwise insert a dummy entry. This will let us update the
15990 // entry later from multiple threads without having to mutate
15993 index
.nameOnlyMethodFamilies
.emplace(name
, FuncFamilyEntry
{}).second
15995 sorted
.emplace_back(name
);
15997 std::sort(begin(sorted
), end(sorted
), string_data_lt
{});
16000 kAggregateBucketSize
16003 // We want to avoid updating any Index data-structures until after
16004 // all jobs have read their inputs. We use the latch to block tasks
16005 // until all tasks have passed the point of reading their inputs.
16006 CoroLatch typesLatch
{typeBuckets
.size()};
16008 auto const runTypes
= [&] (std::vector
<SString
> work
) -> coro::Task
<void> {
16009 co_await
coro::co_reschedule_on_current_executor
;
16011 if (work
.empty()) {
16012 typesLatch
.count_down();
16016 std::vector
<UniquePtrRef
<php::Class
>> classes
;
16017 std::vector
<UniquePtrRef
<ClassInfo2
>> cinfos
;
16018 std::vector
<UniquePtrRef
<php::Func
>> funcs
;
16019 std::vector
<UniquePtrRef
<FuncInfo2
>> finfos
;
16020 std::vector
<UniquePtrRef
<ClassInfo2
>> cinfoDeps
;
16023 std::vector
<SString
> classNames
;
16024 std::vector
<SString
> funcNames
;
16026 roots
.reserve(work
.size());
16027 classNames
.reserve(work
.size());
16029 for (auto const w
: work
) {
16030 if (meta
.classes
.count(w
)) {
16031 always_assert(roots
.emplace(w
).second
);
16032 classNames
.emplace_back(w
);
16034 if (meta
.funcs
.count(w
)) funcNames
.emplace_back(w
);
16037 // Add a dependency to the job. A class is a dependency if it
16038 // shows up in a class' type-hints, or if it's a potential
16039 // reg-only equivalent.
16040 auto const addDep
= [&] (SString dep
, bool addEquiv
) {
16041 if (!meta
.classes
.count(dep
) || roots
.count(dep
)) return;
16042 cinfoDeps
.emplace_back(index
.classInfoRefs
.at(dep
));
16043 if (!addEquiv
) return;
16044 if (auto const cls
= folly::get_ptr(meta
.classes
, dep
)) {
16045 for (auto const d
: cls
->candidateRegOnlyEquivs
) {
16046 if (!meta
.classes
.count(d
) || roots
.count(d
)) continue;
16047 cinfoDeps
.emplace_back(index
.classInfoRefs
.at(d
));
16052 for (auto const w
: work
) {
16053 if (auto const cls
= folly::get_ptr(meta
.classes
, w
)) {
16054 classes
.emplace_back(index
.classRefs
.at(w
));
16055 cinfos
.emplace_back(index
.classInfoRefs
.at(w
));
16056 for (auto const d
: cls
->deps
) addDep(d
, true);
16057 for (auto const d
: cls
->candidateRegOnlyEquivs
) addDep(d
, false);
16059 // Not else if. A name can correspond to both a class and a
16061 if (auto const func
= folly::get_ptr(meta
.funcs
, w
)) {
16062 funcs
.emplace_back(index
.funcRefs
.at(w
));
16063 finfos
.emplace_back(index
.funcInfoRefs
.at(w
));
16064 for (auto const d
: func
->deps
) addDep(d
, true);
16067 addDep(s_Awaitable
.get(), true);
16068 addDep(s_AsyncGenerator
.get(), true);
16069 addDep(s_Generator
.get(), true);
16071 // Record that we've read our inputs
16072 typesLatch
.count_down();
16074 std::sort(begin(cinfoDeps
), end(cinfoDeps
));
16076 std::unique(begin(cinfoDeps
), end(cinfoDeps
)),
16080 auto config
= co_await index
.configRef
->getCopy();
16082 Client::ExecMetadata metadata
{
16083 .job_key
= folly::sformat("init types {}", work
[0])
16086 auto results
= co_await
16087 index
.client
->exec(
16092 std::move(classes
),
16096 std::move(cinfoDeps
)
16099 std::move(metadata
)
16101 assertx(results
.size() == 1);
16102 auto& [classRefs
, cinfoRefs
, funcRefs
, finfoRefs
] = results
[0];
16103 assertx(classRefs
.size() == classNames
.size());
16104 assertx(cinfoRefs
.size() == classNames
.size());
16105 assertx(funcRefs
.size() == funcNames
.size());
16106 assertx(finfoRefs
.size() == funcNames
.size());
16108 // Wait for all tasks to finish reading from the Index ref tables
16109 // before starting to overwrite them.
16110 co_await typesLatch
.wait();
16112 for (size_t i
= 0, size
= classNames
.size(); i
< size
; ++i
) {
16113 auto const name
= classNames
[i
];
16114 index
.classRefs
.at(name
) = std::move(classRefs
[i
]);
16115 index
.classInfoRefs
.at(name
) = std::move(cinfoRefs
[i
]);
16117 for (size_t i
= 0, size
= funcNames
.size(); i
< size
; ++i
) {
16118 auto const name
= funcNames
[i
];
16119 index
.funcRefs
.at(name
) = std::move(funcRefs
[i
]);
16120 index
.funcInfoRefs
.at(name
) = std::move(finfoRefs
[i
]);
16126 auto const runFixups
= [&] (std::vector
<SString
> units
) -> coro::Task
<void> {
16127 co_await
coro::co_reschedule_on_current_executor
;
16129 if (units
.empty()) co_return
;
16131 std::vector
<InitTypesMetadata::Fixup
> fixups
;
16133 // Gather up the fixups and ensure a deterministic ordering.
16134 fixups
.reserve(units
.size());
16135 for (auto const unit
: units
) {
16136 auto f
= std::move(meta
.fixups
.at(unit
));
16137 assertx(!f
.addClass
.empty() || !f
.removeFunc
.empty());
16138 std::sort(f
.addClass
.begin(), f
.addClass
.end(), string_data_lt_type
{});
16139 std::sort(f
.removeFunc
.begin(), f
.removeFunc
.end(), string_data_lt
{});
16140 fixups
.emplace_back(std::move(f
));
16142 auto fixupRefs
= co_await index
.client
->storeMulti(std::move(fixups
));
16143 assertx(fixupRefs
.size() == units
.size());
16146 std::tuple
<UniquePtrRef
<php::Unit
>, Ref
<InitTypesMetadata::Fixup
>>
16148 inputs
.reserve(units
.size());
16150 for (size_t i
= 0, size
= units
.size(); i
< size
; ++i
) {
16151 inputs
.emplace_back(
16152 index
.unitRefs
.at(units
[i
]),
16153 std::move(fixupRefs
[i
])
16157 Client::ExecMetadata metadata
{
16158 .job_key
= folly::sformat("fixup units {}", units
[0])
16161 auto config
= co_await index
.configRef
->getCopy();
16162 auto outputs
= co_await index
.client
->exec(
16166 std::move(metadata
)
16168 assertx(outputs
.size() == units
.size());
16170 // Every unit is already in the Index table, so we can overwrite
16171 // them without locking.
16172 for (size_t i
= 0, size
= units
.size(); i
< size
; ++i
) {
16173 index
.unitRefs
.at(units
[i
]) = std::move(outputs
[i
]);
16179 struct AggregateUpdates
{
16181 std::pair
<FuncFamily2::Id
, Ref
<FuncFamilyGroup
>>
16185 auto const runAggregate
= [&] (std::vector
<SString
> names
)
16186 -> coro::Task
<AggregateUpdates
> {
16187 co_await
coro::co_reschedule_on_current_executor
;
16189 if (names
.empty()) co_return AggregateUpdates
{};
16191 std::vector
<std::pair
<SString
, std::vector
<FuncFamilyEntry
>>> entries
;
16192 std::vector
<Ref
<FuncFamilyGroup
>> funcFamilies
;
16194 entries
.reserve(names
.size());
16195 // Extract out any func families the entries refer to, so they can
16196 // be provided to the job.
16197 for (auto const n
: names
) {
16198 auto& e
= meta
.nameOnlyFF
.at(n
);
16199 entries
.emplace_back(n
, std::move(e
));
16200 for (auto const& entry
: entries
.back().second
) {
16203 [&] (const FuncFamilyEntry::BothFF
& e
) {
16204 funcFamilies
.emplace_back(index
.funcFamilyRefs
.at(e
.m_ff
));
16206 [&] (const FuncFamilyEntry::FFAndSingle
& e
) {
16207 funcFamilies
.emplace_back(index
.funcFamilyRefs
.at(e
.m_ff
));
16209 [&] (const FuncFamilyEntry::FFAndNone
& e
) {
16210 funcFamilies
.emplace_back(index
.funcFamilyRefs
.at(e
.m_ff
));
16212 [&] (const FuncFamilyEntry::BothSingle
&) {},
16213 [&] (const FuncFamilyEntry::SingleAndNone
&) {},
16214 [&] (const FuncFamilyEntry::None
&) {}
16219 std::sort(begin(funcFamilies
), end(funcFamilies
));
16220 funcFamilies
.erase(
16221 std::unique(begin(funcFamilies
), end(funcFamilies
)),
16225 auto [entriesRef
, config
] = co_await
coro::collectAll(
16226 index
.client
->store(std::move(entries
)),
16227 index
.configRef
->getCopy()
16230 Client::ExecMetadata metadata
{
16231 .job_key
= folly::sformat("aggregate name-only {}", names
[0])
16234 auto results
= co_await
16235 index
.client
->exec(
16236 s_aggregateNameOnlyJob
,
16239 std::make_tuple(std::move(entriesRef
), std::move(funcFamilies
))
16241 std::move(metadata
)
16243 assertx(results
.size() == 1);
16244 auto& [ffRefs
, outMetaRef
] = results
[0];
16246 auto outMeta
= co_await index
.client
->load(std::move(outMetaRef
));
16247 assertx(outMeta
.newFuncFamilyIds
.size() == ffRefs
.size());
16248 assertx(outMeta
.nameOnly
.size() == names
.size());
16250 // Update the dummy entries with the actual result.
16251 for (size_t i
= 0, size
= names
.size(); i
< size
; ++i
) {
16252 auto& old
= index
.nameOnlyMethodFamilies
.at(names
[i
]);
16253 assertx(boost::get
<FuncFamilyEntry::None
>(&old
.m_meths
));
16254 old
= std::move(outMeta
.nameOnly
[i
]);
16257 AggregateUpdates updates
;
16258 updates
.funcFamilies
.reserve(outMeta
.newFuncFamilyIds
.size());
16259 for (size_t i
= 0, size
= outMeta
.newFuncFamilyIds
.size(); i
< size
; ++i
) {
16260 auto const ref
= ffRefs
[i
];
16261 for (auto const& id
: outMeta
.newFuncFamilyIds
[i
]) {
16262 updates
.funcFamilies
.emplace_back(id
, ref
);
16269 auto const runAggregateCombine
= [&] (auto tasks
) -> coro::Task
<void> {
16270 co_await
coro::co_reschedule_on_current_executor
;
16272 auto const updates
= co_await
coro::collectAllRange(std::move(tasks
));
16274 for (auto const& u
: updates
) {
16275 for (auto const& [id
, ref
] : u
.funcFamilies
) {
16276 // The same FuncFamily can be grouped into multiple
16277 // different groups. Prefer the group that's smaller and
16278 // if they're the same size, use the one with the lowest
16279 // id to keep determinism.
16280 auto const& [existing
, inserted
] =
16281 index
.funcFamilyRefs
.emplace(id
, ref
);
16282 if (inserted
) continue;
16283 if (existing
->second
.id().m_size
< ref
.id().m_size
) continue;
16284 if (ref
.id().m_size
< existing
->second
.id().m_size
) {
16285 existing
->second
= ref
;
16288 if (existing
->second
.id() <= ref
.id()) continue;
16289 existing
->second
= ref
;
16296 using namespace folly::gen
;
16298 std::vector
<coro::TaskWithExecutor
<void>> tasks
;
16299 tasks
.reserve(typeBuckets
.size() + fixupBuckets
.size() + 1);
16301 // Temporarily suppress case collision logging
16302 auto oldTypeLogLevel
= Cfg::Eval::LogTsameCollisions
;
16303 Cfg::Eval::LogTsameCollisions
= 0;
16305 Cfg::Eval::LogTsameCollisions
= oldTypeLogLevel
;
16308 for (auto& work
: typeBuckets
) {
16309 tasks
.emplace_back(
16310 runTypes(std::move(work
)).scheduleOn(index
.executor
->sticky())
16313 for (auto& work
: fixupBuckets
) {
16314 tasks
.emplace_back(
16315 runFixups(std::move(work
)).scheduleOn(index
.executor
->sticky())
16318 auto subTasks
= from(aggregateBuckets
)
16320 | map([&] (std::vector
<SString
>&& work
) {
16321 return runAggregate(
16323 ).scheduleOn(index
.executor
->sticky());
16325 | as
<std::vector
>();
16326 tasks
.emplace_back(
16327 runAggregateCombine(
16328 std::move(subTasks
)
16329 ).scheduleOn(index
.executor
->sticky())
16332 coro::blockingWait(coro::collectAllRange(std::move(tasks
)));
16335 //////////////////////////////////////////////////////////////////////
16337 Index::Input::UnitMeta
make_native_unit_meta(IndexData
& index
) {
16338 auto unit
= make_native_unit();
16339 auto const name
= unit
->filename
;
16341 std::vector
<std::pair
<SString
, bool>> constants
;
16342 constants
.reserve(unit
->constants
.size());
16343 for (auto const& cns
: unit
->constants
) {
16344 constants
.emplace_back(cns
->name
, type(cns
->val
) == KindOfUninit
);
16347 auto unitRef
= coro::blockingWait(index
.client
->store(std::move(unit
)));
16348 Index::Input::UnitMeta meta
{ std::move(unitRef
), name
};
16349 meta
.constants
= std::move(constants
);
16353 // Set up the async state, populate the (initial) table of
16354 // extern-worker refs in the Index, and build some metadata needed for
16355 // class flattening.
16356 IndexFlattenMetadata
make_remote(IndexData
& index
,
16358 Index::Input input
,
16359 std::unique_ptr
<TicketExecutor
> executor
,
16360 std::unique_ptr
<Client
> client
,
16361 DisposeCallback dispose
) {
16362 trace_time
tracer("make remote");
16363 tracer
.ignore_client_stats();
16365 assertx(input
.classes
.size() == input
.classBC
.size());
16366 assertx(input
.funcs
.size() == input
.funcBC
.size());
16368 index
.executor
= std::move(executor
);
16369 index
.client
= std::move(client
);
16370 index
.disposeClient
= std::move(dispose
);
16372 // Kick off the storage of the global config. We'll start early so
16373 // it will (hopefully) be done before we need it.
16374 index
.configRef
= std::make_unique
<CoroAsyncValue
<Ref
<Config
>>>(
16375 [&index
, config
= std::move(config
)] () mutable {
16376 return index
.client
->store(std::move(config
));
16378 index
.executor
->sticky()
16381 // Create a fake unit to store native constants and add it as an
16383 input
.units
.emplace_back(make_native_unit_meta(index
));
16385 IndexFlattenMetadata flattenMeta
;
16386 SStringToOneT
<SString
> methCallerUnits
;
16388 flattenMeta
.cls
.reserve(input
.classes
.size());
16389 flattenMeta
.allCls
.reserve(input
.classes
.size());
16390 flattenMeta
.allFuncs
.reserve(input
.funcs
.size());
16392 // Add unit and class information to their appropriate tables. This
16393 // is also where we'll detect duplicate funcs and class names (which
16394 // should be caught earlier during parsing).
16395 for (auto& unit
: input
.units
) {
16396 FTRACE(5, "unit {} -> {}\n", unit
.name
, unit
.unit
.id().toString());
16398 for (auto& typeMapping
: unit
.typeMappings
) {
16399 auto const name
= typeMapping
.name
;
16400 auto const isTypeAlias
= typeMapping
.isTypeAlias
;
16401 always_assert_flog(
16402 flattenMeta
.typeMappings
.emplace(name
, std::move(typeMapping
)).second
,
16403 "Duplicate type-mapping: {}",
16407 always_assert(index
.typeAliasToUnit
.emplace(name
, unit
.name
).second
);
16408 index
.unitsWithTypeAliases
.emplace(unit
.name
);
16412 always_assert_flog(
16413 index
.unitRefs
.emplace(unit
.name
, std::move(unit
.unit
)).second
,
16414 "Duplicate unit: {}",
16418 for (auto const& [cnsName
, hasInit
] : unit
.constants
) {
16419 always_assert_flog(
16420 index
.constantToUnit
.emplace(
16422 std::make_pair(unit
.name
, hasInit
)
16424 "Duplicate constant: {}",
16430 for (auto& cls
: input
.classes
) {
16431 FTRACE(5, "class {} -> {}\n", cls
.name
, cls
.cls
.id().toString());
16432 always_assert_flog(
16433 index
.classRefs
.emplace(cls
.name
, std::move(cls
.cls
)).second
,
16434 "Duplicate class: {}",
16437 always_assert(index
.classToUnit
.emplace(cls
.name
, cls
.unit
).second
);
16439 auto& meta
= flattenMeta
.cls
[cls
.name
];
16440 if (cls
.closureFunc
) {
16441 assertx(cls
.closures
.empty());
16442 index
.funcToClosures
[cls
.closureFunc
].emplace(cls
.name
);
16443 index
.closureToFunc
.emplace(cls
.name
, cls
.closureFunc
);
16444 meta
.isClosure
= true;
16446 index
.classToClosures
[cls
.name
].insert(
16447 begin(cls
.closures
),
16450 for (auto const clo
: cls
.closures
) {
16451 index
.closureToClass
.emplace(clo
, cls
.name
);
16454 meta
.deps
.insert(begin(cls
.dependencies
), end(cls
.dependencies
));
16455 meta
.unresolvedTypes
= std::move(cls
.unresolvedTypes
);
16456 meta
.idx
= flattenMeta
.allCls
.size();
16457 flattenMeta
.allCls
.emplace_back(cls
.name
);
16459 if (cls
.has86init
) index
.classesWith86Inits
.emplace(cls
.name
);
16461 if (cls
.typeMapping
) {
16462 auto const name
= cls
.typeMapping
->name
;
16463 always_assert_flog(
16464 flattenMeta
.typeMappings
.emplace(
16465 name
, std::move(*cls
.typeMapping
)
16467 "Duplicate type-mapping: {}",
16473 // Funcs have an additional wrinkle, however. A func might be a meth
16474 // caller. Meth callers are special in that they might be present
16475 // (with the same name) in multiple units. However only one "wins"
16476 // and is actually emitted in the repo. We detect that here and
16477 // select a winner. The "losing" meth callers will be actually
16478 // removed from their unit after class flattening.
16479 for (auto& func
: input
.funcs
) {
16480 FTRACE(5, "func {} -> {}\n", func
.name
, func
.func
.id().toString());
16482 if (func
.methCaller
) {
16483 // If this meth caller a duplicate of one we've already seen?
16484 auto const [existing
, emplaced
] =
16485 methCallerUnits
.emplace(func
.name
, func
.unit
);
16487 // It is. The duplicate shouldn't be in the same unit,
16489 always_assert_flog(
16490 existing
->second
!= func
.unit
,
16491 "Duplicate meth-caller {} in same unit {}",
16495 // The winner is the one with the unit with the "lesser"
16496 // name. This is completely arbitrary.
16497 if (string_data_lt
{}(func
.unit
, existing
->second
)) {
16498 // This one wins. Schedule the older entry for deletion and
16499 // take over it's position in the map.
16501 4, " meth caller {} from unit {} taking priority over unit {}",
16502 func
.name
, func
.unit
, existing
->second
16504 flattenMeta
.unitDeletions
[existing
->second
].emplace_back(func
.name
);
16505 existing
->second
= func
.unit
;
16506 index
.funcRefs
.at(func
.name
) = std::move(func
.func
);
16507 index
.funcToUnit
.at(func
.name
) = func
.unit
;
16509 // This one loses. Schedule it for deletion.
16510 flattenMeta
.unitDeletions
[func
.unit
].emplace_back(func
.name
);
16514 // It's not. Treat it like anything else.
16517 // If not a meth caller, treat it like anything else.
16518 always_assert_flog(
16519 index
.funcRefs
.emplace(func
.name
, std::move(func
.func
)).second
,
16520 "Duplicate func: {}",
16524 index
.funcToUnit
.emplace(func
.name
, func
.unit
);
16525 if (Constant::nameFromFuncName(func
.name
)) {
16526 index
.constantInitFuncs
.emplace(func
.name
);
16529 auto& meta
= flattenMeta
.func
[func
.name
];
16530 meta
.unresolvedTypes
= std::move(func
.unresolvedTypes
);
16532 flattenMeta
.allFuncs
.emplace_back(func
.name
);
16535 for (auto& bc
: input
.classBC
) {
16536 FTRACE(5, "class bytecode {} -> {}\n", bc
.name
, bc
.bc
.id().toString());
16538 always_assert_flog(
16539 index
.classRefs
.count(bc
.name
),
16540 "Class bytecode for non-existent class {}",
16543 always_assert_flog(
16544 index
.classBytecodeRefs
.emplace(bc
.name
, std::move(bc
.bc
)).second
,
16545 "Duplicate class bytecode: {}",
16550 for (auto& bc
: input
.funcBC
) {
16551 FTRACE(5, "func bytecode {} -> {}\n", bc
.name
, bc
.bc
.id().toString());
16553 always_assert_flog(
16554 index
.funcRefs
.count(bc
.name
),
16555 "Func bytecode for non-existent func {}",
16559 if (bc
.methCaller
) {
16560 // Only record this bytecode if it's associated meth-caller was
16562 auto const it
= methCallerUnits
.find(bc
.name
);
16563 always_assert_flog(
16564 it
!= end(methCallerUnits
),
16565 "Bytecode for func {} is marked as meth-caller, "
16566 "but func is not a meth-caller",
16569 auto const unit
= it
->second
;
16570 if (bc
.unit
!= unit
) {
16573 "Bytecode for meth-caller func {} in unit {} "
16574 "skipped because the meth-caller was dropped\n",
16580 always_assert_flog(
16581 !methCallerUnits
.count(bc
.name
),
16582 "Bytecode for func {} is not marked as meth-caller, "
16583 "but func is a meth-caller",
16588 always_assert_flog(
16589 index
.funcBytecodeRefs
.emplace(bc
.name
, std::move(bc
.bc
)).second
,
16590 "Duplicate func bytecode: {}",
16596 for (auto const& [cns
, unitAndInit
] : index
.constantToUnit
) {
16597 if (!unitAndInit
.second
) continue;
16598 if (is_native_unit(unitAndInit
.first
)) continue;
16599 auto const initName
= Constant::funcNameFromName(cns
);
16600 always_assert_flog(
16601 index
.funcRefs
.count(initName
) > 0,
16602 "Constant {} is marked as having initialization func {}, "
16603 "but it does not exist",
16609 return flattenMeta
;
16612 //////////////////////////////////////////////////////////////////////
16615 * Combines multiple classes/class-infos/funcs/units into a single
16616 * blob. Makes make_local() more efficient, as you can download a
16617 * smaller number of large blobs rather than many small blobs.
16619 struct AggregateJob
{
16620 static std::string
name() { return "hhbbc-aggregate"; }
16621 static void init(const Config
& config
) {
16622 process_init(config
.o
, config
.gd
, false);
16623 ClassGraph::init();
16625 static void fini() { ClassGraph::destroy(); }
16628 std::vector
<std::unique_ptr
<php::Class
>> classes
;
16629 std::vector
<std::unique_ptr
<ClassInfo2
>> classInfos
;
16630 std::vector
<std::unique_ptr
<php::ClassBytecode
>> classBytecode
;
16631 std::vector
<std::unique_ptr
<php::Func
>> funcs
;
16632 std::vector
<std::unique_ptr
<FuncInfo2
>> funcInfos
;
16633 std::vector
<std::unique_ptr
<php::FuncBytecode
>> funcBytecode
;
16634 std::vector
<std::unique_ptr
<php::Unit
>> units
;
16635 std::vector
<FuncFamilyGroup
> funcFamilies
;
16636 std::vector
<std::unique_ptr
<MethodsWithoutCInfo
>> methInfos
;
16638 template <typename SerDe
> void serde(SerDe
& sd
) {
16639 ScopedStringDataIndexer _
;
16640 ClassGraph::ScopedSerdeState _2
;
16654 static Bundle
run(Variadic
<std::unique_ptr
<php::Class
>> classes
,
16655 Variadic
<std::unique_ptr
<ClassInfo2
>> classInfos
,
16656 Variadic
<std::unique_ptr
<php::ClassBytecode
>> classBytecode
,
16657 Variadic
<std::unique_ptr
<php::Func
>> funcs
,
16658 Variadic
<std::unique_ptr
<FuncInfo2
>> funcInfos
,
16659 Variadic
<std::unique_ptr
<php::FuncBytecode
>> funcBytecode
,
16660 Variadic
<std::unique_ptr
<php::Unit
>> units
,
16661 Variadic
<FuncFamilyGroup
> funcFamilies
,
16662 Variadic
<std::unique_ptr
<MethodsWithoutCInfo
>> methInfos
) {
16664 bundle
.classes
.reserve(classes
.vals
.size());
16665 bundle
.classInfos
.reserve(classInfos
.vals
.size());
16666 bundle
.classBytecode
.reserve(classBytecode
.vals
.size());
16667 bundle
.funcs
.reserve(funcs
.vals
.size());
16668 bundle
.funcInfos
.reserve(funcInfos
.vals
.size());
16669 bundle
.funcBytecode
.reserve(funcBytecode
.vals
.size());
16670 bundle
.units
.reserve(units
.vals
.size());
16671 bundle
.funcFamilies
.reserve(funcFamilies
.vals
.size());
16672 bundle
.methInfos
.reserve(methInfos
.vals
.size());
16673 for (auto& c
: classes
.vals
) {
16674 bundle
.classes
.emplace_back(std::move(c
));
16676 for (auto& c
: classInfos
.vals
) {
16677 bundle
.classInfos
.emplace_back(std::move(c
));
16679 for (auto& b
: classBytecode
.vals
) {
16680 bundle
.classBytecode
.emplace_back(std::move(b
));
16682 for (auto& f
: funcs
.vals
) {
16683 bundle
.funcs
.emplace_back(std::move(f
));
16685 for (auto& f
: funcInfos
.vals
) {
16686 bundle
.funcInfos
.emplace_back(std::move(f
));
16688 for (auto& b
: funcBytecode
.vals
) {
16689 bundle
.funcBytecode
.emplace_back(std::move(b
));
16691 for (auto& u
: units
.vals
) {
16692 bundle
.units
.emplace_back(std::move(u
));
16694 for (auto& group
: funcFamilies
.vals
) {
16695 bundle
.funcFamilies
.emplace_back(std::move(group
));
16697 for (auto& m
: methInfos
.vals
) {
16698 bundle
.methInfos
.emplace_back(std::move(m
));
16704 Job
<AggregateJob
> s_aggregateJob
;
16706 void remote_func_info_to_local(IndexData
& index
,
16707 const php::Func
& func
,
16708 FuncInfo2
& rfinfo
) {
16709 assertx(func
.name
== rfinfo
.name
);
16710 auto finfo
= func_info(index
, &func
);
16711 assertx(finfo
->returnTy
.is(BInitCell
));
16712 finfo
->returnTy
= std::move(rfinfo
.returnTy
);
16713 finfo
->returnRefinements
= rfinfo
.returnRefinements
;
16714 finfo
->retParam
= rfinfo
.retParam
;
16715 finfo
->effectFree
= rfinfo
.effectFree
;
16716 finfo
->unusedParams
= rfinfo
.unusedParams
;
16719 // Convert the FuncInfo2s we loaded from extern-worker into their
16720 // equivalent FuncInfos.
16721 void make_func_infos_local(IndexData
& index
,
16722 std::vector
<std::unique_ptr
<FuncInfo2
>> remote
) {
16723 trace_time tracer
{"make func-infos local"};
16724 tracer
.ignore_client_stats();
16726 parallel::for_each(
16728 [&] (const std::unique_ptr
<FuncInfo2
>& rfinfo
) {
16729 auto const it
= index
.funcs
.find(rfinfo
->name
);
16730 always_assert_flog(
16731 it
!= end(index
.funcs
),
16732 "Func-info for {} has no associated php::Func in index",
16735 remote_func_info_to_local(index
, *it
->second
, *rfinfo
);
16740 // Convert the ClassInfo2s we loaded from extern-worker into their
16741 // equivalent ClassInfos (and store it in the Index).
16742 void make_class_infos_local(
16744 std::vector
<std::unique_ptr
<ClassInfo2
>> remote
,
16745 std::vector
<std::unique_ptr
<FuncFamily2
>> funcFamilies
16747 trace_time tracer
{"make class-infos local"};
16748 tracer
.ignore_client_stats();
16750 assertx(index
.allClassInfos
.empty());
16751 assertx(index
.classInfo
.empty());
16753 // First create a ClassInfo for each ClassInfo2. Since a ClassInfo
16754 // can refer to other ClassInfos, we can't do much more at this
16756 auto newCInfos
= parallel::map(
16758 [&] (const std::unique_ptr
<ClassInfo2
>& in
) {
16759 std::vector
<std::unique_ptr
<ClassInfo
>> out
;
16761 auto const make
= [&] (const ClassInfo2
& cinfo
) {
16762 auto c
= std::make_unique
<ClassInfo
>();
16763 auto const it
= index
.classes
.find(cinfo
.name
);
16764 always_assert_flog(
16765 it
!= end(index
.classes
),
16766 "Class-info for {} has no associated php::Class in index",
16769 c
->cls
= it
->second
;
16770 out
.emplace_back(std::move(c
));
16774 for (auto const& clo
: in
->closures
) make(*clo
);
16779 // Build table mapping name to ClassInfo.
16780 for (auto& cinfos
: newCInfos
) {
16781 for (auto& cinfo
: cinfos
) {
16783 index
.classInfo
.emplace(cinfo
->cls
->name
, cinfo
.get()).second
16785 index
.allClassInfos
.emplace_back(std::move(cinfo
));
16789 newCInfos
.shrink_to_fit();
16790 index
.allClassInfos
.shrink_to_fit();
16792 // Set AttrNoOverride to true for all methods. If we determine it's
16793 // actually overridden below, we'll clear it.
16794 parallel::for_each(
16795 index
.program
->classes
,
16796 [&] (std::unique_ptr
<php::Class
>& cls
) {
16797 for (auto& m
: cls
->methods
) {
16798 assertx(!(m
->attrs
& AttrNoOverride
));
16799 if (is_special_method_name(m
->name
)) continue;
16800 attribute_setter(m
->attrs
, true, AttrNoOverride
);
16802 for (auto& clo
: cls
->closures
) {
16803 assertx(clo
->methods
.size() == 1);
16804 auto& m
= clo
->methods
[0];
16805 assertx(!(m
->attrs
& AttrNoOverride
));
16806 assertx(!is_special_method_name(m
->name
));
16807 attribute_setter(m
->attrs
, true, AttrNoOverride
);
16812 auto const get
= [&] (SString name
) {
16813 auto const it
= index
.classInfo
.find(name
);
16814 always_assert_flog(
16815 it
!= end(index
.classInfo
),
16816 "Class-info for {} not found in index",
16823 explicit FFState(std::unique_ptr
<FuncFamily2
> ff
) : m_ff
{std::move(ff
)} {}
16824 std::unique_ptr
<FuncFamily2
> m_ff
;
16825 LockFreeLazyPtrNoDelete
<FuncFamily
> m_notExpanded
;
16826 LockFreeLazyPtrNoDelete
<FuncFamily
> m_expanded
;
16828 FuncFamily
* notExpanded(IndexData
& index
) {
16829 return const_cast<FuncFamily
*>(
16830 &m_notExpanded
.get([&] { return make(index
, false); })
16833 FuncFamily
* expanded(IndexData
& index
) {
16834 return const_cast<FuncFamily
*>(
16835 &m_expanded
.get([&] { return make(index
, true); })
16839 FuncFamily
* make(IndexData
& index
, bool expanded
) const {
16840 FuncFamily::PFuncVec funcs
;
16842 m_ff
->m_regular
.size() +
16845 : (m_ff
->m_nonRegularPrivate
.size() + m_ff
->m_nonRegular
.size())
16849 for (auto const& m
: m_ff
->m_regular
) {
16850 funcs
.emplace_back(func_from_meth_ref(index
, m
), true);
16852 for (auto const& m
: m_ff
->m_nonRegularPrivate
) {
16853 funcs
.emplace_back(func_from_meth_ref(index
, m
), true);
16856 for (auto const& m
: m_ff
->m_nonRegular
) {
16857 funcs
.emplace_back(func_from_meth_ref(index
, m
), false);
16861 auto const extra
= !expanded
&& !m_ff
->m_nonRegular
.empty() &&
16862 (m_ff
->m_regular
.size() + m_ff
->m_nonRegularPrivate
.size()) > 1;
16865 begin(funcs
), end(funcs
),
16866 [] (FuncFamily::PossibleFunc a
, const FuncFamily::PossibleFunc b
) {
16867 if (a
.inRegular() && !b
.inRegular()) return true;
16868 if (!a
.inRegular() && b
.inRegular()) return false;
16869 return string_data_lt_type
{}(a
.ptr()->cls
->name
, b
.ptr()->cls
->name
);
16872 funcs
.shrink_to_fit();
16874 assertx(funcs
.size() > 1);
16876 auto const convert
= [&] (const FuncFamily2::StaticInfo
& in
) {
16877 FuncFamily::StaticInfo out
;
16878 out
.m_numInOut
= in
.m_numInOut
;
16879 out
.m_requiredCoeffects
= in
.m_requiredCoeffects
;
16880 out
.m_coeffectRules
= in
.m_coeffectRules
;
16881 out
.m_paramPreps
= in
.m_paramPreps
;
16882 out
.m_minNonVariadicParams
= in
.m_minNonVariadicParams
;
16883 out
.m_maxNonVariadicParams
= in
.m_maxNonVariadicParams
;
16884 out
.m_isReadonlyReturn
= in
.m_isReadonlyReturn
;
16885 out
.m_isReadonlyThis
= in
.m_isReadonlyThis
;
16886 out
.m_supportsAER
= in
.m_supportsAER
;
16887 out
.m_maybeReified
= in
.m_maybeReified
;
16888 out
.m_maybeCaresAboutDynCalls
= in
.m_maybeCaresAboutDynCalls
;
16889 out
.m_maybeBuiltin
= in
.m_maybeBuiltin
;
16891 auto const it
= index
.funcFamilyStaticInfos
.find(out
);
16892 if (it
!= end(index
.funcFamilyStaticInfos
)) return it
->first
.get();
16893 return index
.funcFamilyStaticInfos
.insert(
16894 std::make_unique
<FuncFamily::StaticInfo
>(std::move(out
)),
16896 ).first
->first
.get();
16899 auto newFuncFamily
=
16900 std::make_unique
<FuncFamily
>(std::move(funcs
), extra
);
16902 always_assert(m_ff
->m_allStatic
);
16903 if (m_ff
->m_regularStatic
) {
16904 const FuncFamily::StaticInfo
* reg
= nullptr;
16905 if (expanded
|| extra
) reg
= convert(*m_ff
->m_regularStatic
);
16906 newFuncFamily
->m_all
.m_static
=
16907 expanded
? reg
: convert(*m_ff
->m_allStatic
);
16909 newFuncFamily
->m_regular
= std::make_unique
<FuncFamily::Info
>();
16910 newFuncFamily
->m_regular
->m_static
= reg
;
16913 newFuncFamily
->m_all
.m_static
= convert(*m_ff
->m_allStatic
);
16916 return index
.funcFamilies
.insert(
16917 std::move(newFuncFamily
),
16919 ).first
->first
.get();
16923 hphp_fast_map
<FuncFamily2::Id
, std::unique_ptr
<FFState
>> ffState
;
16924 for (auto& ff
: funcFamilies
) {
16925 auto const id
= ff
->m_id
;
16929 std::make_unique
<FFState
>(std::move(ff
))
16933 funcFamilies
.clear();
16935 std::mutex extraMethodLock
;
16937 // Now that we can map name to ClassInfo, we can populate the rest
16938 // of the fields in each ClassInfo.
16939 parallel::for_each(
16941 [&] (std::unique_ptr
<ClassInfo2
>& rcinfos
) {
16942 auto const process
= [&] (std::unique_ptr
<ClassInfo2
> rcinfo
) {
16943 auto const cinfo
= get(rcinfo
->name
);
16944 if (rcinfo
->parent
) cinfo
->parent
= get(rcinfo
->parent
);
16946 if (!(cinfo
->cls
->attrs
& AttrNoExpandTrait
)) {
16947 auto const traits
= rcinfo
->classGraph
.usedTraits();
16948 cinfo
->usedTraits
.reserve(traits
.size());
16949 for (auto const trait
: traits
) {
16950 cinfo
->usedTraits
.emplace_back(get(trait
.name()));
16952 cinfo
->usedTraits
.shrink_to_fit();
16954 cinfo
->traitProps
= std::move(rcinfo
->traitProps
);
16956 cinfo
->clsConstants
.reserve(rcinfo
->clsConstants
.size());
16957 for (auto const& [name
, cns
] : rcinfo
->clsConstants
) {
16958 auto const it
= index
.classes
.find(cns
.idx
.cls
);
16959 always_assert_flog(
16960 it
!= end(index
.classes
),
16961 "php::Class for {} not found in index",
16964 cinfo
->clsConstants
.emplace(
16966 ClassInfo::ConstIndex
{ it
->second
, cns
.idx
.idx
}
16970 for (size_t i
= 0, size
= cinfo
->cls
->constants
.size(); i
< size
; ++i
) {
16971 auto const& cns
= cinfo
->cls
->constants
[i
];
16972 if (cns
.kind
!= ConstModifiers::Kind::Value
) continue;
16973 if (!cns
.val
.has_value()) continue;
16974 if (cns
.val
->m_type
!= KindOfUninit
) continue;
16975 if (i
>= cinfo
->clsConstTypes
.size()) {
16976 cinfo
->clsConstTypes
.resize(i
+1, ClsConstInfo
{ TInitCell
, 0 });
16978 cinfo
->clsConstTypes
[i
] = folly::get_default(
16979 rcinfo
->clsConstantInfo
,
16981 ClsConstInfo
{ TInitCell
, 0 }
16984 cinfo
->clsConstTypes
.shrink_to_fit();
16987 std::vector
<std::pair
<SString
, MethTabEntry
>> methods
;
16988 methods
.reserve(cinfo
->methods
.size());
16989 for (auto const& [name
, mte
] : rcinfo
->methods
) {
16990 if (!(mte
.attrs
& AttrNoOverride
)) {
16992 func_from_meth_ref(index
, mte
.meth())->attrs
,
16997 methods
.emplace_back(name
, mte
);
17000 begin(methods
), end(methods
),
17001 [] (auto const& p1
, auto const& p2
) { return p1
.first
< p2
.first
; }
17003 cinfo
->methods
.insert(
17004 folly::sorted_unique
, begin(methods
), end(methods
)
17006 cinfo
->methods
.shrink_to_fit();
17009 cinfo
->hasBadRedeclareProp
= rcinfo
->hasBadRedeclareProp
;
17010 cinfo
->hasBadInitialPropValues
= rcinfo
->hasBadInitialPropValues
;
17011 cinfo
->hasConstProp
= rcinfo
->hasConstProp
;
17012 cinfo
->hasReifiedParent
= rcinfo
->hasReifiedParent
;
17013 cinfo
->subHasConstProp
= rcinfo
->subHasConstProp
;
17014 cinfo
->isMocked
= rcinfo
->isMocked
;
17015 cinfo
->isSubMocked
= rcinfo
->isSubMocked
;
17017 cinfo
->classGraph
= rcinfo
->classGraph
;
17018 cinfo
->classGraph
.setCInfo(*cinfo
);
17020 auto const noOverride
= [&] (SString name
) {
17021 if (auto const mte
= folly::get_ptr(cinfo
->methods
, name
)) {
17022 return bool(mte
->attrs
& AttrNoOverride
);
17027 auto const noOverrideRegular
= [&] (SString name
) {
17028 if (auto const mte
= folly::get_ptr(cinfo
->methods
, name
)) {
17029 return mte
->noOverrideRegular();
17034 std::vector
<std::pair
<SString
, FuncFamilyOrSingle
>> entries
;
17035 std::vector
<std::pair
<SString
, FuncFamilyOrSingle
>> aux
;
17036 for (auto const& [name
, entry
] : rcinfo
->methodFamilies
) {
17037 assertx(!is_special_method_name(name
));
17039 auto expanded
= false;
17040 if (!cinfo
->methods
.count(name
)) {
17041 if (!(cinfo
->cls
->attrs
& (AttrAbstract
|AttrInterface
))) continue;
17042 if (!cinfo
->classGraph
.mightHaveRegularSubclass()) continue;
17043 if (entry
.m_regularIncomplete
|| entry
.m_privateAncestor
) continue;
17044 if (name
== s_construct
.get()) continue;
17046 } else if (noOverride(name
)) {
17052 [&, name
=name
, &entry
=entry
] (const FuncFamilyEntry::BothFF
& e
) {
17053 auto const it
= ffState
.find(e
.m_ff
);
17054 assertx(it
!= end(ffState
));
17055 auto const& state
= it
->second
;
17058 if (state
->m_ff
->m_regular
.empty()) return;
17059 if (state
->m_ff
->m_regular
.size() == 1) {
17060 entries
.emplace_back(
17062 FuncFamilyOrSingle
{
17063 func_from_meth_ref(index
, state
->m_ff
->m_regular
[0]),
17069 entries
.emplace_back(
17071 FuncFamilyOrSingle
{
17072 state
->expanded(index
),
17079 entries
.emplace_back(
17081 FuncFamilyOrSingle
{
17082 state
->notExpanded(index
),
17083 entry
.m_allIncomplete
17087 [&, name
=name
, &entry
=entry
] (const FuncFamilyEntry::FFAndSingle
& e
) {
17089 if (e
.m_nonRegularPrivate
) return;
17090 entries
.emplace_back(
17092 FuncFamilyOrSingle
{
17093 func_from_meth_ref(index
, e
.m_regular
),
17100 auto const it
= ffState
.find(e
.m_ff
);
17101 assertx(it
!= end(ffState
));
17103 entries
.emplace_back(
17105 FuncFamilyOrSingle
{
17106 it
->second
->notExpanded(index
),
17107 entry
.m_allIncomplete
17110 if (noOverrideRegular(name
)) return;
17113 FuncFamilyOrSingle
{
17114 func_from_meth_ref(index
, e
.m_regular
),
17119 [&, name
=name
, &entry
=entry
] (const FuncFamilyEntry::FFAndNone
& e
) {
17120 if (expanded
) return;
17121 auto const it
= ffState
.find(e
.m_ff
);
17122 assertx(it
!= end(ffState
));
17124 entries
.emplace_back(
17126 FuncFamilyOrSingle
{
17127 it
->second
->notExpanded(index
),
17128 entry
.m_allIncomplete
17131 if (!noOverrideRegular(name
)) {
17132 aux
.emplace_back(name
, FuncFamilyOrSingle
{});
17135 [&, name
=name
, &entry
=entry
] (const FuncFamilyEntry::BothSingle
& e
) {
17136 if (expanded
&& e
.m_nonRegularPrivate
) return;
17137 entries
.emplace_back(
17139 FuncFamilyOrSingle
{
17140 func_from_meth_ref(index
, e
.m_all
),
17141 !expanded
&& entry
.m_allIncomplete
17145 [&, name
=name
, &entry
=entry
] (const FuncFamilyEntry::SingleAndNone
& e
) {
17146 if (expanded
) return;
17147 entries
.emplace_back(
17149 FuncFamilyOrSingle
{
17150 func_from_meth_ref(index
, e
.m_all
),
17151 entry
.m_allIncomplete
17154 if (!noOverrideRegular(name
)) {
17155 aux
.emplace_back(name
, FuncFamilyOrSingle
{});
17158 [&, &entry
=entry
] (const FuncFamilyEntry::None
&) {
17159 assertx(entry
.m_allIncomplete
);
17164 // Sort the lists of new entries, so we can insert them into the
17165 // method family maps (which are sorted_vector_maps) in bulk.
17167 begin(entries
), end(entries
),
17168 [] (auto const& p1
, auto const& p2
) { return p1
.first
< p2
.first
; }
17171 begin(aux
), end(aux
),
17172 [] (auto const& p1
, auto const& p2
) { return p1
.first
< p2
.first
; }
17174 if (!entries
.empty()) {
17175 cinfo
->methodFamilies
.insert(
17176 folly::sorted_unique
, begin(entries
), end(entries
)
17179 if (!aux
.empty()) {
17180 cinfo
->methodFamiliesAux
.insert(
17181 folly::sorted_unique
, begin(aux
), end(aux
)
17184 cinfo
->methodFamilies
.shrink_to_fit();
17185 cinfo
->methodFamiliesAux
.shrink_to_fit();
17187 if (!rcinfo
->extraMethods
.empty()) {
17188 // This is rare. Only happens with unflattened traits, so
17189 // taking a lock here is fine.
17190 std::lock_guard
<std::mutex
> _
{extraMethodLock
};
17191 auto& extra
= index
.classExtraMethodMap
[cinfo
->cls
];
17192 for (auto const& meth
: rcinfo
->extraMethods
) {
17193 extra
.emplace(func_from_meth_ref(index
, meth
));
17197 // Build the FuncInfo for every method on this class. The
17198 // FuncInfos already have default types, so update them with the
17199 // type from the FuncInfo2. Any class types here will be
17200 // unresolved (will be resolved later).
17201 assertx(cinfo
->cls
->methods
.size() == rcinfo
->funcInfos
.size());
17202 for (size_t i
= 0, size
= cinfo
->cls
->methods
.size(); i
< size
; ++i
) {
17203 auto& func
= cinfo
->cls
->methods
[i
];
17204 auto& rfi
= rcinfo
->funcInfos
[i
];
17205 remote_func_info_to_local(index
, *func
, *rfi
);
17209 for (auto& clo
: rcinfos
->closures
) process(std::move(clo
));
17210 process(std::move(rcinfos
));
17215 remote
.shrink_to_fit();
17217 for (auto const& [name
, entry
] : index
.nameOnlyMethodFamilies
) {
17220 [&, name
=name
] (const FuncFamilyEntry::BothFF
& e
) {
17221 auto const it
= ffState
.find(e
.m_ff
);
17222 assertx(it
!= end(ffState
));
17224 FuncFamilyOrSingle f
{ it
->second
->notExpanded(index
), true };
17225 index
.methodFamilies
.emplace(
17227 IndexData::MethodFamilyEntry
{ f
, f
}
17230 [&, name
=name
] (const FuncFamilyEntry::FFAndSingle
& e
) {
17231 auto const it
= ffState
.find(e
.m_ff
);
17232 assertx(it
!= end(ffState
));
17234 index
.methodFamilies
.emplace(
17236 IndexData::MethodFamilyEntry
{
17237 FuncFamilyOrSingle
{
17238 it
->second
->notExpanded(index
),
17241 FuncFamilyOrSingle
{
17242 func_from_meth_ref(index
, e
.m_regular
),
17248 [&, name
=name
] (const FuncFamilyEntry::FFAndNone
& e
) {
17249 auto const it
= ffState
.find(e
.m_ff
);
17250 assertx(it
!= end(ffState
));
17252 index
.methodFamilies
.emplace(
17254 IndexData::MethodFamilyEntry
{
17255 FuncFamilyOrSingle
{
17256 it
->second
->notExpanded(index
),
17259 FuncFamilyOrSingle
{}
17263 [&, name
=name
] (const FuncFamilyEntry::BothSingle
& e
) {
17264 FuncFamilyOrSingle f
{ func_from_meth_ref(index
, e
.m_all
), true };
17265 index
.methodFamilies
.emplace(
17267 IndexData::MethodFamilyEntry
{ f
, f
}
17270 [&, name
=name
] (const FuncFamilyEntry::SingleAndNone
& e
) {
17271 index
.methodFamilies
.emplace(
17273 IndexData::MethodFamilyEntry
{
17274 FuncFamilyOrSingle
{
17275 func_from_meth_ref(index
, e
.m_all
),
17278 FuncFamilyOrSingle
{}
17282 [&] (const FuncFamilyEntry::None
&) { always_assert(false); }
17287 decltype(index
.nameOnlyMethodFamilies
){}.swap(index
.nameOnlyMethodFamilies
);
17289 // Now that all of the FuncFamilies have been created, generate the
17290 // back links from FuncInfo to their FuncFamilies.
17291 std::vector
<FuncFamily
*> work
;
17292 work
.reserve(index
.funcFamilies
.size());
17293 for (auto const& kv
: index
.funcFamilies
) work
.emplace_back(kv
.first
.get());
17295 // First calculate the needed capacity for each FuncInfo's family
17296 // list. We use this to presize the family list. This is superior
17297 // just pushing back and then shrinking the vectors, as that can
17298 // excessively fragment the heap.
17299 std::vector
<std::atomic
<size_t>> capacities(index
.nextFuncId
);
17301 parallel::for_each(
17303 [&] (FuncFamily
* ff
) {
17304 for (auto const pf
: ff
->possibleFuncs()) {
17305 ++capacities
[pf
.ptr()->idx
];
17310 parallel::for_each(
17312 [&] (FuncInfo
& fi
) {
17313 if (!fi
.func
) return;
17314 fi
.families
.reserve(capacities
[fi
.func
->idx
]);
17317 capacities
.clear();
17318 capacities
.shrink_to_fit();
17320 // Different threads can touch the same FuncInfo when adding to the
17321 // func family list, so use sharded locking scheme.
17322 std::array
<std::mutex
, 256> locks
;
17323 parallel::for_each(
17325 [&] (FuncFamily
* ff
) {
17326 for (auto const pf
: ff
->possibleFuncs()) {
17327 auto finfo
= func_info(index
, pf
.ptr());
17328 auto& lock
= locks
[pointer_hash
<FuncInfo
>{}(finfo
) % locks
.size()];
17329 std::lock_guard
<std::mutex
> _
{lock
};
17330 finfo
->families
.emplace_back(ff
);
17336 // Switch to "local" mode, in which all calculations are expected to
17337 // be done locally (not using extern-worker). This involves
17338 // downloading everything out of extern-worker and converting it. To
17339 // improve efficiency, we first aggregate many small(er) items into
17340 // larger aggregate blobs in external workers, then download the
17342 void make_local(IndexData
& index
) {
17343 trace_time
tracer("make local", index
.sample
);
17345 using namespace folly::gen
;
17347 // These aren't needed below so we can free them immediately.
17348 decltype(index
.funcToClosures
){}.swap(index
.funcToClosures
);
17349 decltype(index
.classToClosures
){}.swap(index
.classToClosures
);
17350 decltype(index
.classesWith86Inits
){}.swap(index
.classesWith86Inits
);
17351 decltype(index
.classToUnit
){}.swap(index
.classToUnit
);
17352 decltype(index
.funcToUnit
){}.swap(index
.funcToUnit
);
17353 decltype(index
.constantToUnit
){}.swap(index
.constantToUnit
);
17354 decltype(index
.constantInitFuncs
){}.swap(index
.constantInitFuncs
);
17355 decltype(index
.unitsWithTypeAliases
){}.swap(index
.unitsWithTypeAliases
);
17356 decltype(index
.closureToFunc
){}.swap(index
.closureToFunc
);
17357 decltype(index
.closureToClass
){}.swap(index
.closureToClass
);
17358 decltype(index
.classToCnsBases
){}.swap(index
.classToCnsBases
);
17360 // Unlike other cases, we want to bound each bucket to roughly the
17361 // same total byte size (since ultimately we're going to download
17363 auto const usingSubprocess
= index
.client
->usingSubprocess();
17364 // We can be more aggressive in subprocess mode because there's no
17365 // actual aggregation.
17366 auto const sizePerBucket
= usingSubprocess
17371 * We'll use the names of the various items as the items to
17372 * bucketize. This is somewhat problematic because names between
17373 * units/funcs/classes/class-infos can collide (indeed classes and
17374 * class-infos will always collide). Adding to the complication is
17375 * that some of these are case sensitive and some aren't.
17377 * We'll store a case sensitive version of each name exactly *once*,
17378 * using a seen set. Since the hash for a static string (which
17379 * consistently_bucketize() uses) is case insensitive, all case
17380 * sensitive versions of the same name will always hash to the same
17383 * When we obtain the Refs for a corresponding bucket, we'll load
17384 * all items with that given name, using a set to ensure each RefId
17385 * is only used once.
17388 SStringToOneT
<Ref
<FuncFamilyGroup
>> nameToFuncFamilyGroup
;
17389 auto const& [items
, totalSize
] = [&] {
17391 std::vector
<SString
> items
;
17392 size_t totalSize
= 0;
17394 index
.unitRefs
.size() + index
.classRefs
.size() +
17395 index
.classInfoRefs
.size() + index
.funcRefs
.size() +
17396 index
.funcInfoRefs
.size() + index
.funcFamilyRefs
.size() +
17397 index
.uninstantiableClsMethRefs
.size()
17399 for (auto const& [name
, ref
] : index
.unitRefs
) {
17400 totalSize
+= ref
.id().m_size
;
17401 if (!seen
.emplace(name
).second
) continue;
17402 items
.emplace_back(name
);
17404 for (auto const& [name
, ref
] : index
.classRefs
) {
17405 totalSize
+= ref
.id().m_size
;
17406 if (!seen
.emplace(name
).second
) continue;
17407 items
.emplace_back(name
);
17409 for (auto const& [name
, ref
] : index
.classInfoRefs
) {
17410 totalSize
+= ref
.id().m_size
;
17411 if (!seen
.emplace(name
).second
) continue;
17412 items
.emplace_back(name
);
17414 for (auto const& [name
, ref
] : index
.classBytecodeRefs
) {
17415 totalSize
+= ref
.id().m_size
;
17416 if (!seen
.emplace(name
).second
) continue;
17417 items
.emplace_back(name
);
17419 for (auto const& [name
, ref
] : index
.funcRefs
) {
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
.funcInfoRefs
) {
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
.funcBytecodeRefs
) {
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
.uninstantiableClsMethRefs
) {
17435 totalSize
+= ref
.id().m_size
;
17436 if (!seen
.emplace(name
).second
) continue;
17437 items
.emplace_back(name
);
17440 for (auto const& [_
, ref
] : index
.funcFamilyRefs
) {
17441 auto const name
= makeStaticString(ref
.id().toString());
17442 nameToFuncFamilyGroup
.emplace(name
, ref
);
17444 for (auto const& [name
, ref
] : nameToFuncFamilyGroup
) {
17445 totalSize
+= ref
.id().m_size
;
17446 if (!seen
.emplace(name
).second
) continue;
17447 items
.emplace_back(name
);
17449 std::sort(begin(items
), end(items
), string_data_lt
{});
17450 return std::make_pair(items
, totalSize
);
17453 // Back out the number of buckets we want from the total size of
17454 // everything and the target size of a bucket.
17455 auto const numBuckets
= (totalSize
+ sizePerBucket
- 1) / sizePerBucket
;
17456 auto buckets
= consistently_bucketize(
17458 (items
.size() + numBuckets
- 1) / numBuckets
17461 // We're going to be downloading all bytecode, so to avoid wasted
17462 // memory, try to re-use identical bytecode.
17463 Optional
<php::FuncBytecode::Reuser
> reuser
;
17465 php::FuncBytecode::s_reuser
= reuser
.get_pointer();
17466 SCOPE_EXIT
{ php::FuncBytecode::s_reuser
= nullptr; };
17469 auto program
= std::make_unique
<php::Program
>();
17471 // Index stores ClassInfos, not ClassInfo2s, so we need a place to
17472 // store them until we convert it.
17473 std::vector
<std::unique_ptr
<ClassInfo2
>> remoteClassInfos
;
17474 remoteClassInfos
.reserve(index
.classInfoRefs
.size());
17476 std::vector
<std::unique_ptr
<FuncInfo2
>> remoteFuncInfos
;
17477 remoteFuncInfos
.reserve(index
.funcInfoRefs
.size());
17479 std::vector
<std::unique_ptr
<MethodsWithoutCInfo
>> remoteMethInfos
;
17480 remoteMethInfos
.reserve(index
.uninstantiableClsMethRefs
.size());
17482 hphp_fast_set
<FuncFamily2::Id
> remoteFuncFamilyIds
;
17483 std::vector
<std::unique_ptr
<FuncFamily2
>> remoteFuncFamilies
;
17484 remoteFuncFamilies
.reserve(index
.funcFamilyRefs
.size());
17486 auto const run
= [&] (std::vector
<SString
> chunks
) -> coro::Task
<void> {
17487 co_await
coro::co_reschedule_on_current_executor
;
17489 if (chunks
.empty()) co_return
;
17491 std::vector
<UniquePtrRef
<php::Class
>> classes
;
17492 std::vector
<UniquePtrRef
<ClassInfo2
>> classInfos
;
17493 std::vector
<UniquePtrRef
<php::ClassBytecode
>> classBytecode
;
17494 std::vector
<UniquePtrRef
<php::Func
>> funcs
;
17495 std::vector
<UniquePtrRef
<FuncInfo2
>> funcInfos
;
17496 std::vector
<UniquePtrRef
<php::FuncBytecode
>> funcBytecode
;
17497 std::vector
<UniquePtrRef
<php::Unit
>> units
;
17498 std::vector
<Ref
<FuncFamilyGroup
>> funcFamilies
;
17499 std::vector
<UniquePtrRef
<MethodsWithoutCInfo
>> methInfos
;
17501 // Since a name can map to multiple items, and different case
17502 // sensitive version of the same name can appear in the same
17503 // bucket, use a set to make sure any given RefId only included
17504 // once. A set of case insensitive identical names will always
17505 // appear in the same bucket, so there's no need to track
17506 // duplicates globally.
17507 hphp_fast_set
<RefId
, RefId::Hasher
> ids
;
17509 for (auto const name
: chunks
) {
17510 if (auto const r
= folly::get_optional(index
.unitRefs
, name
)) {
17511 if (ids
.emplace(r
->id()).second
) {
17512 units
.emplace_back(*r
);
17515 if (auto const r
= folly::get_optional(index
.classRefs
, name
)) {
17516 auto const bytecode
= index
.classBytecodeRefs
.at(name
);
17517 if (ids
.emplace(r
->id()).second
) {
17518 classes
.emplace_back(*r
);
17519 classBytecode
.emplace_back(bytecode
);
17522 if (auto const r
= folly::get_optional(index
.classInfoRefs
, name
)) {
17523 if (ids
.emplace(r
->id()).second
) {
17524 classInfos
.emplace_back(*r
);
17527 if (auto const r
= folly::get_optional(index
.funcRefs
, name
)) {
17528 auto const bytecode
= index
.funcBytecodeRefs
.at(name
);
17529 if (ids
.emplace(r
->id()).second
) {
17530 funcs
.emplace_back(*r
);
17531 funcBytecode
.emplace_back(bytecode
);
17534 if (auto const r
= folly::get_optional(index
.funcInfoRefs
, name
)) {
17535 if (ids
.emplace(r
->id()).second
) {
17536 funcInfos
.emplace_back(*r
);
17539 if (auto const r
= folly::get_optional(nameToFuncFamilyGroup
, name
)) {
17540 if (ids
.emplace(r
->id()).second
) {
17541 funcFamilies
.emplace_back(*r
);
17544 if (auto const r
= folly::get_optional(index
.uninstantiableClsMethRefs
,
17546 if (ids
.emplace(r
->id()).second
) {
17547 methInfos
.emplace_back(*r
);
17552 AggregateJob::Bundle chunk
;
17553 if (!usingSubprocess
) {
17554 Client::ExecMetadata metadata
{
17555 .job_key
= folly::sformat("aggregate {}", chunks
[0])
17558 // Aggregate the data, which makes downloading it more efficient.
17559 auto config
= co_await index
.configRef
->getCopy();
17560 auto outputs
= co_await index
.client
->exec(
17565 std::move(classes
),
17566 std::move(classInfos
),
17567 std::move(classBytecode
),
17569 std::move(funcInfos
),
17570 std::move(funcBytecode
),
17572 std::move(funcFamilies
),
17573 std::move(methInfos
)
17576 std::move(metadata
)
17578 assertx(outputs
.size() == 1);
17580 // Download the aggregate chunks.
17581 chunk
= co_await index
.client
->load(std::move(outputs
[0]));
17583 // If we're using subprocess mode, we don't need to aggregate
17584 // and we can just download the items directly.
17585 auto [c
, cinfo
, cbc
, f
, finfo
, fbc
, u
, ff
, minfo
] =
17586 co_await
coro::collectAll(
17587 index
.client
->load(std::move(classes
)),
17588 index
.client
->load(std::move(classInfos
)),
17589 index
.client
->load(std::move(classBytecode
)),
17590 index
.client
->load(std::move(funcs
)),
17591 index
.client
->load(std::move(funcInfos
)),
17592 index
.client
->load(std::move(funcBytecode
)),
17593 index
.client
->load(std::move(units
)),
17594 index
.client
->load(std::move(funcFamilies
)),
17595 index
.client
->load(std::move(methInfos
))
17597 chunk
.classes
.insert(
17598 end(chunk
.classes
),
17599 std::make_move_iterator(begin(c
)),
17600 std::make_move_iterator(end(c
))
17602 chunk
.classInfos
.insert(
17603 end(chunk
.classInfos
),
17604 std::make_move_iterator(begin(cinfo
)),
17605 std::make_move_iterator(end(cinfo
))
17607 chunk
.classBytecode
.insert(
17608 end(chunk
.classBytecode
),
17609 std::make_move_iterator(begin(cbc
)),
17610 std::make_move_iterator(end(cbc
))
17612 chunk
.funcs
.insert(
17614 std::make_move_iterator(begin(f
)),
17615 std::make_move_iterator(end(f
))
17617 chunk
.funcInfos
.insert(
17618 end(chunk
.funcInfos
),
17619 std::make_move_iterator(begin(finfo
)),
17620 std::make_move_iterator(end(finfo
))
17622 chunk
.funcBytecode
.insert(
17623 end(chunk
.funcBytecode
),
17624 std::make_move_iterator(begin(fbc
)),
17625 std::make_move_iterator(end(fbc
))
17627 chunk
.units
.insert(
17629 std::make_move_iterator(begin(u
)),
17630 std::make_move_iterator(end(u
))
17632 chunk
.funcFamilies
.insert(
17633 end(chunk
.funcFamilies
),
17634 std::make_move_iterator(begin(ff
)),
17635 std::make_move_iterator(end(ff
))
17637 chunk
.methInfos
.insert(
17638 end(chunk
.methInfos
),
17639 std::make_move_iterator(begin(minfo
)),
17640 std::make_move_iterator(end(minfo
))
17644 always_assert(chunk
.classBytecode
.size() == chunk
.classes
.size());
17645 for (size_t i
= 0, size
= chunk
.classes
.size(); i
< size
; ++i
) {
17646 auto& bc
= chunk
.classBytecode
[i
];
17647 auto& cls
= chunk
.classes
[i
];
17650 for (auto& meth
: cls
->methods
) {
17651 assertx(bcIdx
< bc
->methodBCs
.size());
17652 auto& methBC
= bc
->methodBCs
[bcIdx
++];
17653 always_assert(methBC
.name
== meth
->name
);
17654 always_assert(!meth
->rawBlocks
);
17655 meth
->rawBlocks
= std::move(methBC
.bc
);
17657 for (auto& clo
: cls
->closures
) {
17658 assertx(bcIdx
< bc
->methodBCs
.size());
17659 auto& methBC
= bc
->methodBCs
[bcIdx
++];
17660 assertx(clo
->methods
.size() == 1);
17661 always_assert(methBC
.name
== clo
->methods
[0]->name
);
17662 always_assert(!clo
->methods
[0]->rawBlocks
);
17663 clo
->methods
[0]->rawBlocks
= std::move(methBC
.bc
);
17665 assertx(bcIdx
== bc
->methodBCs
.size());
17667 chunk
.classBytecode
.clear();
17669 always_assert(chunk
.funcBytecode
.size() == chunk
.funcs
.size());
17670 for (size_t i
= 0, size
= chunk
.funcs
.size(); i
< size
; ++i
) {
17671 auto& bytecode
= chunk
.funcBytecode
[i
];
17672 chunk
.funcs
[i
]->rawBlocks
= std::move(bytecode
->bc
);
17674 chunk
.funcBytecode
.clear();
17676 // And add it to our php::Program.
17678 std::scoped_lock
<std::mutex
> _
{lock
};
17679 for (auto& unit
: chunk
.units
) {
17680 // Local execution doesn't need the native unit, so strip it
17682 if (is_native_unit(*unit
)) continue;
17683 program
->units
.emplace_back(std::move(unit
));
17685 for (auto& cls
: chunk
.classes
) {
17686 program
->classes
.emplace_back(std::move(cls
));
17688 for (auto& func
: chunk
.funcs
) {
17689 program
->funcs
.emplace_back(std::move(func
));
17691 remoteClassInfos
.insert(
17692 end(remoteClassInfos
),
17693 std::make_move_iterator(begin(chunk
.classInfos
)),
17694 std::make_move_iterator(end(chunk
.classInfos
))
17696 remoteFuncInfos
.insert(
17697 end(remoteFuncInfos
),
17698 std::make_move_iterator(begin(chunk
.funcInfos
)),
17699 std::make_move_iterator(end(chunk
.funcInfos
))
17701 remoteMethInfos
.insert(
17702 end(remoteMethInfos
),
17703 std::make_move_iterator(begin(chunk
.methInfos
)),
17704 std::make_move_iterator(end(chunk
.methInfos
))
17706 for (auto& group
: chunk
.funcFamilies
) {
17707 for (auto& ff
: group
.m_ffs
) {
17708 if (remoteFuncFamilyIds
.emplace(ff
->m_id
).second
) {
17709 remoteFuncFamilies
.emplace_back(std::move(ff
));
17718 // We're going to load ClassGraphs concurrently.
17719 ClassGraph::initConcurrent();
17722 // Temporarily suppress case collision logging
17723 auto oldTypeLogLevel
= Cfg::Eval::LogTsameCollisions
;
17724 Cfg::Eval::LogTsameCollisions
= 0;
17726 Cfg::Eval::LogTsameCollisions
= oldTypeLogLevel
;
17729 coro::blockingWait(coro::collectAllRange(
17732 | map([&] (std::vector
<SString
> chunks
) {
17733 return run(std::move(chunks
)).scheduleOn(index
.executor
->sticky());
17735 | as
<std::vector
>()
17739 // Deserialization done.
17740 ClassGraph::stopConcurrent();
17742 // We've used any refs we need. Free them now to save memory.
17743 decltype(index
.unitRefs
){}.swap(index
.unitRefs
);
17744 decltype(index
.classRefs
){}.swap(index
.classRefs
);
17745 decltype(index
.funcRefs
){}.swap(index
.funcRefs
);
17746 decltype(index
.classInfoRefs
){}.swap(index
.classInfoRefs
);
17747 decltype(index
.funcInfoRefs
){}.swap(index
.funcInfoRefs
);
17748 decltype(index
.funcFamilyRefs
){}.swap(index
.funcFamilyRefs
);
17749 decltype(index
.classBytecodeRefs
){}.swap(index
.classBytecodeRefs
);
17750 decltype(index
.funcBytecodeRefs
){}.swap(index
.funcBytecodeRefs
);
17751 decltype(index
.uninstantiableClsMethRefs
){}.swap(
17752 index
.uninstantiableClsMethRefs
17755 // Done with any extern-worker stuff at this point:
17756 index
.configRef
.reset();
17760 index
.client
->getStats().toString(
17763 "{:,} units, {:,} classes, {:,} class-infos, {:,} funcs",
17764 program
->units
.size(),
17765 program
->classes
.size(),
17766 remoteClassInfos
.size(),
17767 program
->funcs
.size()
17772 if (index
.sample
) {
17773 index
.client
->getStats().logSample("hhbbc", *index
.sample
);
17776 index
.disposeClient(
17777 std::move(index
.executor
),
17778 std::move(index
.client
)
17780 index
.disposeClient
= decltype(index
.disposeClient
){};
17782 php::FuncBytecode::s_reuser
= nullptr;
17786 nameToFuncFamilyGroup
.clear();
17787 remoteFuncFamilyIds
.clear();
17789 program
->units
.shrink_to_fit();
17790 program
->classes
.shrink_to_fit();
17791 program
->funcs
.shrink_to_fit();
17792 index
.program
= std::move(program
);
17794 // For now we don't require system constants in any extern-worker
17795 // stuff we do. So we can just add it to the Index now.
17796 add_system_constants_to_index(index
);
17798 // Buid Index data structures from the php::Program.
17799 add_program_to_index(index
);
17801 // Convert the FuncInfo2s into FuncInfos.
17802 make_func_infos_local(
17804 std::move(remoteFuncInfos
)
17807 // Convert the ClassInfo2s into ClassInfos.
17808 make_class_infos_local(
17810 std::move(remoteClassInfos
),
17811 std::move(remoteFuncFamilies
)
17814 // Convert any "orphan" FuncInfo2s (those representing methods for a
17815 // class without a ClassInfo).
17816 parallel::for_each(
17818 [&] (std::unique_ptr
<MethodsWithoutCInfo
>& meths
) {
17819 auto const cls
= folly::get_default(index
.classes
, meths
->cls
);
17820 always_assert_flog(
17822 "php::Class for {} not found in index",
17825 assertx(cls
->methods
.size() == meths
->finfos
.size());
17826 for (size_t i
= 0, size
= cls
->methods
.size(); i
< size
; ++i
) {
17827 remote_func_info_to_local(index
, *cls
->methods
[i
], *meths
->finfos
[i
]);
17830 assertx(cls
->closures
.size() == meths
->closureInvokes
.size());
17831 for (size_t i
= 0, size
= cls
->closures
.size(); i
< size
; ++i
) {
17832 auto const& clo
= cls
->closures
[i
];
17833 assertx(clo
->methods
.size() == 1);
17834 remote_func_info_to_local(
17837 *meths
->closureInvokes
[i
]
17843 // Ensure that all classes are unserialized since all local
17844 // processing requires that.
17846 trace_time tracer2
{"unserialize classes"};
17847 tracer2
.ignore_client_stats();
17849 parallel::for_each(
17850 index
.allClassInfos
,
17851 [&] (std::unique_ptr
<ClassInfo
>& cinfo
) {
17852 for (auto& [ty
, _
] : cinfo
->clsConstTypes
) {
17853 ty
= unserialize_classes(
17854 IndexAdaptor
{ *index
.m_index
},
17861 parallel::for_each(
17863 [&] (FuncInfo
& fi
) {
17864 if (!fi
.func
) return;
17865 fi
.returnTy
= unserialize_classes(
17866 IndexAdaptor
{ *index
.m_index
},
17867 std::move(fi
.returnTy
)
17873 //////////////////////////////////////////////////////////////////////
17877 //////////////////////////////////////////////////////////////////////
17879 std::vector
<SString
> Index::Input::makeDeps(const php::Class
& cls
) {
17880 std::vector
<SString
> deps
;
17881 if (cls
.parentName
) deps
.emplace_back(cls
.parentName
);
17882 deps
.insert(deps
.end(), cls
.interfaceNames
.begin(), cls
.interfaceNames
.end());
17883 deps
.insert(deps
.end(), cls
.usedTraitNames
.begin(), cls
.usedTraitNames
.end());
17886 cls
.includedEnumNames
.begin(),
17887 cls
.includedEnumNames
.end()
17892 //////////////////////////////////////////////////////////////////////
17894 Index::Index(Input input
,
17896 std::unique_ptr
<TicketExecutor
> executor
,
17897 std::unique_ptr
<Client
> client
,
17898 DisposeCallback dispose
,
17899 StructuredLogEntry
* sample
)
17900 : m_data
{std::make_unique
<IndexData
>(this)}
17902 trace_time
tracer("create index", sample
);
17903 m_data
->sample
= sample
;
17905 auto flattenMeta
= make_remote(
17909 std::move(executor
),
17914 flatten_type_mappings(*m_data
, flattenMeta
);
17915 auto [subclassMeta
, initTypesMeta
, ifaceConflicts
] =
17916 flatten_classes(*m_data
, std::move(flattenMeta
));
17917 build_subclass_lists(*m_data
, std::move(subclassMeta
), initTypesMeta
);
17918 init_types(*m_data
, std::move(initTypesMeta
));
17919 compute_iface_vtables(*m_data
, std::move(ifaceConflicts
));
17920 check_invariants(*m_data
);
17923 // Defined here so IndexData is a complete type for the unique_ptr
17925 Index::~Index() = default;
17927 Index::Index(Index
&&) = default;
17928 Index
& Index::operator=(Index
&&) = default;
17930 //////////////////////////////////////////////////////////////////////
17932 const php::Program
& Index::program() const {
17933 return *m_data
->program
;
17936 StructuredLogEntry
* Index::sample() const {
17937 return m_data
->sample
;
17940 //////////////////////////////////////////////////////////////////////
17942 TicketExecutor
& Index::executor() const {
17943 return *m_data
->executor
;
17946 Client
& Index::client() const {
17947 return *m_data
->client
;
17950 const CoroAsyncValue
<Ref
<Config
>>& Index::configRef() const {
17951 return *m_data
->configRef
;
17954 //////////////////////////////////////////////////////////////////////
17956 const TSStringSet
& Index::classes_with_86inits() const {
17957 return m_data
->classesWith86Inits
;
17960 const FSStringSet
& Index::constant_init_funcs() const {
17961 return m_data
->constantInitFuncs
;
17964 const SStringSet
& Index::units_with_type_aliases() const {
17965 return m_data
->unitsWithTypeAliases
;
17968 //////////////////////////////////////////////////////////////////////
17970 void Index::make_local() {
17971 HHBBC::make_local(*m_data
);
17972 check_local_invariants(*m_data
);
17975 //////////////////////////////////////////////////////////////////////
17977 const php::Class
* Index::lookup_closure_context(const php::Class
& cls
) const {
17978 if (!cls
.closureContextCls
) return &cls
;
17979 return m_data
->classes
.at(cls
.closureContextCls
);
17982 const php::Unit
* Index::lookup_func_unit(const php::Func
& func
) const {
17983 return m_data
->units
.at(func
.unit
);
17986 const php::Unit
* Index::lookup_func_original_unit(const php::Func
& func
) const {
17987 auto const unit
= func
.originalUnit
? func
.originalUnit
: func
.unit
;
17988 return m_data
->units
.at(unit
);
17991 const php::Unit
* Index::lookup_class_unit(const php::Class
& cls
) const {
17992 return m_data
->units
.at(cls
.unit
);
17995 const php::Class
* Index::lookup_const_class(const php::Const
& cns
) const {
17996 return m_data
->classes
.at(cns
.cls
);
17999 const php::Class
* Index::lookup_class(SString name
) const {
18000 return folly::get_default(m_data
->classes
, name
);
18003 //////////////////////////////////////////////////////////////////////
18005 void Index::for_each_unit_func(const php::Unit
& unit
,
18006 std::function
<void(const php::Func
&)> f
) const {
18007 for (auto const func
: unit
.funcs
) {
18008 f(*m_data
->funcs
.at(func
));
18012 void Index::for_each_unit_func_mutable(php::Unit
& unit
,
18013 std::function
<void(php::Func
&)> f
) {
18014 for (auto const func
: unit
.funcs
) {
18015 f(*m_data
->funcs
.at(func
));
18019 void Index::for_each_unit_class(
18020 const php::Unit
& unit
,
18021 std::function
<void(const php::Class
&)> f
) const {
18022 for (auto const cls
: unit
.classes
) {
18023 f(*m_data
->classes
.at(cls
));
18027 void Index::for_each_unit_class_mutable(php::Unit
& unit
,
18028 std::function
<void(php::Class
&)> f
) {
18029 for (auto const cls
: unit
.classes
) {
18030 f(*m_data
->classes
.at(cls
));
18034 //////////////////////////////////////////////////////////////////////
18036 void Index::preresolve_type_structures() {
18037 trace_time
tracer("pre-resolve type-structures", m_data
->sample
);
18039 // Now that everything has been updated, calculate the invariance
18040 // for each resolved type-structure. For each class constant,
18041 // examine all subclasses and see how the resolved type-structure
18043 parallel::for_each(
18044 m_data
->allClassInfos
,
18045 [&] (std::unique_ptr
<ClassInfo
>& cinfo
) {
18046 if (!cinfo
->classGraph
.hasCompleteChildren()) return;
18048 for (auto& cns
: const_cast<php::Class
*>(cinfo
->cls
)->constants
) {
18049 assertx(cns
.invariance
== php::Const::Invariance::None
);
18050 if (cns
.kind
!= ConstModifiers::Kind::Type
) continue;
18051 if (!cns
.val
.has_value()) continue;
18052 if (!cns
.resolvedTypeStructure
) continue;
18054 auto const checkClassname
=
18055 tvIsString(cns
.resolvedTypeStructure
->get(s_classname
));
18057 // Assume it doesn't change
18058 auto invariance
= php::Const::Invariance::Same
;
18059 for (auto const g
: cinfo
->classGraph
.children()) {
18060 assertx(!g
.isMissing());
18061 assertx(g
.hasCompleteChildren());
18062 auto const s
= g
.cinfo();
18064 assertx(invariance
!= php::Const::Invariance::None
);
18066 IMPLIES(!checkClassname
,
18067 invariance
!= php::Const::Invariance::ClassnamePresent
)
18069 if (s
== cinfo
.get()) continue;
18071 auto const it
= s
->clsConstants
.find(cns
.name
);
18072 assertx(it
!= s
->clsConstants
.end());
18073 if (it
->second
.cls
!= s
->cls
) continue;
18074 auto const& scns
= *it
->second
;
18076 // Overridden in some strange way. Be pessimistic.
18077 if (!scns
.val
.has_value() ||
18078 scns
.kind
!= ConstModifiers::Kind::Type
) {
18079 invariance
= php::Const::Invariance::None
;
18083 // The resolved type structure in this subclass is not the
18085 if (scns
.resolvedTypeStructure
!= cns
.resolvedTypeStructure
) {
18086 if (!scns
.resolvedTypeStructure
) {
18087 // It's not even resolved here, so we can't assume
18089 invariance
= php::Const::Invariance::None
;
18092 // We might still be able to assert that a classname is
18093 // always present, or a resolved type structure at least
18095 if (invariance
== php::Const::Invariance::Same
||
18096 invariance
== php::Const::Invariance::ClassnamePresent
) {
18099 tvIsString(scns
.resolvedTypeStructure
->get(s_classname
)))
18100 ? php::Const::Invariance::ClassnamePresent
18101 : php::Const::Invariance::Present
;
18106 if (invariance
!= php::Const::Invariance::None
) {
18107 cns
.invariance
= invariance
;
18114 //////////////////////////////////////////////////////////////////////
18116 const CompactVector
<const php::Class
*>*
18117 Index::lookup_closures(const php::Class
* cls
) const {
18118 auto const it
= m_data
->classClosureMap
.find(cls
);
18119 if (it
!= end(m_data
->classClosureMap
)) {
18120 return &it
->second
;
18125 const hphp_fast_set
<const php::Func
*>*
18126 Index::lookup_extra_methods(const php::Class
* cls
) const {
18127 if (cls
->attrs
& AttrNoExpandTrait
) return nullptr;
18128 auto const it
= m_data
->classExtraMethodMap
.find(cls
);
18129 if (it
!= end(m_data
->classExtraMethodMap
)) {
18130 return &it
->second
;
18135 //////////////////////////////////////////////////////////////////////
18137 Optional
<res::Class
> Index::resolve_class(SString clsName
) const {
18138 clsName
= normalizeNS(clsName
);
18139 auto const it
= m_data
->classInfo
.find(clsName
);
18140 if (it
== end(m_data
->classInfo
)) return std::nullopt
;
18141 return res::Class::get(*it
->second
);
18144 Optional
<res::Class
> Index::resolve_class(const php::Class
& cls
) const {
18145 return resolve_class(cls
.name
);
18148 const php::TypeAlias
* Index::lookup_type_alias(SString name
) const {
18149 auto const it
= m_data
->typeAliases
.find(name
);
18150 if (it
== m_data
->typeAliases
.end()) return nullptr;
18154 Index::ClassOrTypeAlias
Index::lookup_class_or_type_alias(SString name
) const {
18155 auto const rcls
= resolve_class(name
);
18157 auto const cls
= [&] () -> const php::Class
* {
18158 if (auto const ci
= rcls
->cinfo()) return ci
->cls
;
18159 return m_data
->classes
.at(rcls
->name());
18161 return ClassOrTypeAlias
{cls
, nullptr, true};
18163 if (auto const ta
= lookup_type_alias(name
)) {
18164 return ClassOrTypeAlias
{nullptr, ta
, true};
18166 return ClassOrTypeAlias
{nullptr, nullptr, false};
18169 // Given a DCls, return the most specific res::Func for that DCls. For
18170 // intersections, this will call process/general on every component of
18171 // the intersection and combine the results. For non-intersections, it
18172 // will call process/general on the sole member of the DCls. process
18173 // is called to obtain a res::Func from a ClassInfo. If a ClassInfo
18174 // isn't available, general will be called instead.
18175 template <typename P
, typename G
>
18176 res::Func
Index::rfunc_from_dcls(const DCls
& dcls
,
18179 const G
& general
) const {
18180 if (dcls
.isExact() || dcls
.isSub()) {
18181 // If this isn't an intersection, there's only one cinfo to
18182 // process and we're done.
18183 auto const cinfo
= dcls
.cls().cinfo();
18184 if (!cinfo
) return general(dcls
.containsNonRegular());
18185 return process(cinfo
, dcls
.isExact(), dcls
.containsNonRegular());
18188 if (dcls
.isIsectAndExact()) {
18189 // Even though this has an intersection list, it always must be
18190 // the exact class, so it sufficies to provide that.
18191 auto const e
= dcls
.isectAndExact().first
;
18192 auto const cinfo
= e
.cinfo();
18193 if (!cinfo
) return general(dcls
.containsNonRegular());
18194 return process(cinfo
, true, dcls
.containsNonRegular());
18198 * Otherwise get a res::Func for all members of the intersection and
18199 * combine them together. Since the DCls represents a class which is
18200 * a subtype of every ClassInfo in the list, every res::Func we get
18203 * The relevant res::Func types in order from most general to more
18206 * MethodName -> FuncFamily -> MethodOrMissing -> Method -> Missing
18208 * Since every res::Func in the intersection is true, we take the
18209 * res::Func which is most specific. Two different res::Funcs cannot
18210 * be contradict. For example, we shouldn't get a Method and a
18211 * Missing since one implies there's no func and the other implies
18212 * one specific func. Or two different res::Funcs shouldn't resolve
18213 * to two different methods.
18216 assertx(dcls
.isIsect());
18217 using Func
= res::Func
;
18219 auto missing
= TriBool::Maybe
;
18221 const php::Func
* singleMethod
= nullptr;
18223 auto const DEBUG_ONLY allIncomplete
= !debug
|| std::all_of(
18224 begin(dcls
.isect()), end(dcls
.isect()),
18225 [] (res::Class c
) { return !c
.hasCompleteChildren(); }
18228 for (auto const i
: dcls
.isect()) {
18229 auto const cinfo
= i
.cinfo();
18230 if (!cinfo
) continue;
18232 auto const func
= process(cinfo
, false, dcls
.containsNonRegular());
18235 [&] (Func::MethodName
) {},
18236 [&] (Func::Method m
) {
18237 if (singleMethod
) {
18238 assertx(missing
!= TriBool::Yes
);
18239 assertx(isect
.families
.empty());
18240 if (singleMethod
!= m
.finfo
->func
) {
18241 assertx(allIncomplete
);
18242 singleMethod
= nullptr;
18243 missing
= TriBool::Yes
;
18245 missing
= TriBool::No
;
18247 } else if (missing
!= TriBool::Yes
) {
18248 singleMethod
= m
.finfo
->func
;
18249 isect
.families
.clear();
18250 missing
= TriBool::No
;
18253 [&] (Func::MethodFamily fam
) {
18254 if (missing
== TriBool::Yes
) {
18255 assertx(!singleMethod
);
18256 assertx(isect
.families
.empty());
18259 if (singleMethod
) {
18260 assertx(missing
!= TriBool::Yes
);
18261 assertx(isect
.families
.empty());
18264 assertx(missing
== TriBool::Maybe
);
18265 isect
.families
.emplace_back(fam
.family
);
18266 isect
.regularOnly
|= fam
.regularOnly
;
18268 [&] (Func::MethodOrMissing m
) {
18269 if (singleMethod
) {
18270 assertx(missing
!= TriBool::Yes
);
18271 assertx(isect
.families
.empty());
18272 if (singleMethod
!= m
.finfo
->func
) {
18273 assertx(allIncomplete
);
18274 singleMethod
= nullptr;
18275 missing
= TriBool::Yes
;
18277 } else if (missing
!= TriBool::Yes
) {
18278 singleMethod
= m
.finfo
->func
;
18279 isect
.families
.clear();
18282 [&] (Func::MissingMethod
) {
18283 assertx(IMPLIES(missing
== TriBool::No
, allIncomplete
));
18284 singleMethod
= nullptr;
18285 isect
.families
.clear();
18286 missing
= TriBool::Yes
;
18288 [&] (Func::FuncName
) { always_assert(false); },
18289 [&] (Func::Fun
) { always_assert(false); },
18290 [&] (Func::Fun2
) { always_assert(false); },
18291 [&] (Func::Method2
) { always_assert(false); },
18292 [&] (Func::MethodFamily2
) { always_assert(false); },
18293 [&] (Func::MethodOrMissing2
) { always_assert(false); },
18294 [&] (Func::MissingFunc
) { always_assert(false); },
18295 [&] (const Func::Isect
&) { always_assert(false); },
18296 [&] (const Func::Isect2
&) { always_assert(false); }
18300 // If we got a method, that always wins. Again, every res::Func is
18301 // true, and method is more specific than a FuncFamily, so it is
18303 if (singleMethod
) {
18304 assertx(missing
!= TriBool::Yes
);
18305 // If missing is Maybe, then *every* resolution was to a
18306 // MethodName or MethodOrMissing, so include that fact here by
18307 // using MethodOrMissing.
18308 if (missing
== TriBool::Maybe
) {
18310 Func::MethodOrMissing
{ func_info(*m_data
, singleMethod
) }
18313 return Func
{ Func::Method
{ func_info(*m_data
, singleMethod
) } };
18315 // We only got unresolved classes. If missing is TriBool::Yes, the
18316 // function doesn't exist. Otherwise be pessimistic.
18317 if (isect
.families
.empty()) {
18318 if (missing
== TriBool::Yes
) {
18319 return Func
{ Func::MissingMethod
{ dcls
.smallestCls().name(), name
} };
18321 assertx(missing
== TriBool::Maybe
);
18322 return general(dcls
.containsNonRegular());
18324 // Isect case. Isects always might contain missing funcs.
18325 assertx(missing
== TriBool::Maybe
);
18327 // We could add a FuncFamily multiple times, so remove duplicates.
18328 std::sort(begin(isect
.families
), end(isect
.families
));
18329 isect
.families
.erase(
18330 std::unique(begin(isect
.families
), end(isect
.families
)),
18331 end(isect
.families
)
18333 // If everything simplifies down to a single FuncFamily, just use
18335 if (isect
.families
.size() == 1) {
18336 return Func
{ Func::MethodFamily
{ isect
.families
[0], isect
.regularOnly
} };
18338 return Func
{ std::move(isect
) };
18341 res::Func
Index::resolve_method(Context ctx
,
18342 const Type
& thisType
,
18343 SString name
) const {
18344 assertx(thisType
.subtypeOf(BCls
) || thisType
.subtypeOf(BObj
));
18346 using Func
= res::Func
;
18349 * Without using the class type, try to infer a set of methods
18350 * using just the method name. This will, naturally, not produce
18351 * as precise a set as when using the class type, but it's better
18352 * than nothing. For all of these results, we need to include the
18353 * possibility of the method not existing (we cannot rule that out
18354 * for this situation).
18356 auto const general
= [&] (bool includeNonRegular
, SString maybeCls
) {
18357 assertx(name
!= s_construct
.get());
18359 // We don't produce name-only global func families for special
18360 // methods, so be conservative. We don't call special methods in a
18361 // context where we'd expect to not know the class, so it's not
18362 // worthwhile. The same goes for __invoke and __debuginfo, which
18363 // is corresponds to every closure, and gets too large without
18365 if (!has_name_only_func_family(name
)) {
18366 return Func
{ Func::MethodName
{ maybeCls
, name
} };
18369 // Lookup up the name-only global func families for this name. If
18370 // we don't have one, the method cannot exist because it contains
18371 // every method with that name in the program.
18372 auto const famIt
= m_data
->methodFamilies
.find(name
);
18373 if (famIt
== end(m_data
->methodFamilies
)) {
18374 return Func
{ Func::MissingMethod
{ maybeCls
, name
} };
18377 // The entry exists. Consult the correct data in it, depending on
18378 // whether we're including non-regular classes or not.
18379 auto const& entry
= includeNonRegular
18380 ? famIt
->second
.m_all
18381 : famIt
->second
.m_regular
;
18382 assertx(entry
.isEmpty() || entry
.isIncomplete());
18384 if (auto const ff
= entry
.funcFamily()) {
18385 return Func
{ Func::MethodFamily
{ ff
, !includeNonRegular
} };
18386 } else if (auto const f
= entry
.func()) {
18387 return Func
{ Func::MethodOrMissing
{ func_info(*m_data
, f
) } };
18389 return Func
{ Func::MissingMethod
{ maybeCls
, name
} };
18393 auto const process
= [&] (ClassInfo
* cinfo
,
18395 bool includeNonRegular
) {
18396 assertx(name
!= s_construct
.get());
18398 auto const methIt
= cinfo
->methods
.find(name
);
18399 if (methIt
== end(cinfo
->methods
)) {
18400 // We don't store metadata for special methods, so be
18401 // pessimistic (the lack of a method entry does not mean the
18402 // call might fail at runtme).
18403 if (is_special_method_name(name
)) {
18404 return Func
{ Func::MethodName
{ cinfo
->cls
->name
, name
} };
18406 // We're only considering this class, not it's subclasses. Since
18407 // it doesn't exist here, the resolution will always fail.
18409 return Func
{ Func::MissingMethod
{ cinfo
->cls
->name
, name
} };
18411 // The method isn't present on this class, but it might be in
18412 // the subclasses. In most cases try a general lookup to get a
18413 // slightly better type than nothing.
18414 if (includeNonRegular
||
18415 !(cinfo
->cls
->attrs
& (AttrInterface
|AttrAbstract
))) {
18416 return general(includeNonRegular
, cinfo
->cls
->name
);
18419 // A special case is if we're only considering regular classes,
18420 // and this is an interface or abstract class. For those, we
18421 // "expand" the method families table to include any methods
18422 // defined in *all* regular subclasses. This is needed to
18423 // preserve monotonicity. Check this now.
18424 auto const famIt
= cinfo
->methodFamilies
.find(name
);
18425 // If no entry, treat it pessimistically like the rest of the
18427 if (famIt
== end(cinfo
->methodFamilies
)) {
18428 return general(false, cinfo
->cls
->name
);
18431 // We found an entry. This cannot be empty (remember the method
18432 // is guaranteed to exist on *all* regular subclasses), and must
18433 // be complete (for the same reason). Use it.
18434 auto const& entry
= famIt
->second
;
18435 assertx(!entry
.isEmpty());
18436 assertx(entry
.isComplete());
18437 if (auto const ff
= entry
.funcFamily()) {
18438 return Func
{ Func::MethodFamily
{ ff
, true } };
18439 } else if (auto const func
= entry
.func()) {
18440 return Func
{ Func::Method
{ func_info(*m_data
, func
) } };
18442 always_assert(false);
18445 // The method on this class.
18446 auto const& meth
= methIt
->second
;
18447 auto const ftarget
= func_from_meth_ref(*m_data
, meth
.meth());
18449 // We don't store method family information about special methods
18450 // and they have special inheritance semantics.
18451 if (is_special_method_name(name
)) {
18452 // If we know the class exactly, we can use ftarget.
18453 if (isExact
) return Func
{ Func::Method
{ func_info(*m_data
, ftarget
) } };
18454 // The method isn't overwritten, but they don't inherit, so it
18455 // could be missing.
18456 if (meth
.attrs
& AttrNoOverride
) {
18457 return Func
{ Func::MethodOrMissing
{ func_info(*m_data
, ftarget
) } };
18459 // Otherwise be pessimistic.
18460 return Func
{ Func::MethodName
{ cinfo
->cls
->name
, name
} };
18463 // Private method handling: Private methods have special lookup
18464 // rules. If we're in the context of a particular class, and that
18465 // class defines a private method, an instance of the class will
18466 // always call that private method (even if overridden) in that
18468 assertx(cinfo
->cls
);
18469 if (ctx
.cls
== cinfo
->cls
) {
18470 // The context matches the current class. If we've looked up a
18471 // private method (defined on this class), then that's what
18473 if ((meth
.attrs
& AttrPrivate
) && meth
.topLevel()) {
18474 return Func
{ Func::Method
{ func_info(*m_data
, ftarget
) } };
18476 } else if ((meth
.attrs
& AttrPrivate
) || meth
.hasPrivateAncestor()) {
18477 // Otherwise the context doesn't match the current class. If the
18478 // looked up method is private, or has a private ancestor,
18479 // there's a chance we'll call that method (or
18480 // ancestor). Otherwise there's no private method in the
18481 // inheritance tree we'll call.
18482 auto const ancestor
= [&] () -> const php::Func
* {
18483 if (!ctx
.cls
) return nullptr;
18484 // Look up the ClassInfo corresponding to the context:
18485 auto const it
= m_data
->classInfo
.find(ctx
.cls
->name
);
18486 if (it
== end(m_data
->classInfo
)) return nullptr;
18487 auto const ctxCInfo
= it
->second
;
18488 // Is this context a parent of our class?
18489 if (!cinfo
->classGraph
.exactSubtypeOf(ctxCInfo
->classGraph
,
18494 // It is. See if it defines a private method.
18495 auto const it2
= ctxCInfo
->methods
.find(name
);
18496 if (it2
== end(ctxCInfo
->methods
)) return nullptr;
18497 auto const& mte
= it2
->second
;
18498 // If it defines a private method, use it.
18499 if ((mte
.attrs
& AttrPrivate
) && mte
.topLevel()) {
18500 return func_from_meth_ref(*m_data
, mte
.meth());
18502 // Otherwise do normal lookup.
18506 return Func
{ Func::Method
{ func_info(*m_data
, ancestor
) } };
18509 // If none of the above cases trigger, we still might call a
18510 // private method (in a child class), but the func-family logic
18511 // below will handle that.
18513 // If we're only including regular subclasses, and this class
18514 // itself isn't regular, the result may not necessarily include
18516 if (!includeNonRegular
&& !is_regular_class(*cinfo
->cls
)) {
18517 // We're not including this base class. If we're exactly this
18518 // class, there's no method at all. It will always be missing.
18520 return Func
{ Func::MissingMethod
{ cinfo
->cls
->name
, name
} };
18522 if (meth
.noOverrideRegular()) {
18523 // The method isn't overridden in a subclass, but we can't
18524 // use the base class either. This leaves two cases. Either
18525 // the method isn't overridden because there are no regular
18526 // subclasses (in which case there's no resolution at all), or
18527 // because there's regular subclasses, but they use the same
18528 // method (in which case the result is just ftarget).
18529 if (!cinfo
->classGraph
.mightHaveRegularSubclass()) {
18530 return Func
{ Func::MissingMethod
{ cinfo
->cls
->name
, name
} };
18532 return Func
{ Func::Method
{ func_info(*m_data
, ftarget
) } };
18534 // We can't use the base class (because it's non-regular), but
18535 // the method is overridden by a regular subclass.
18537 // Since this is a non-regular class and we want the result for
18538 // the regular subset, we need to consult the aux table first.
18539 auto const auxIt
= cinfo
->methodFamiliesAux
.find(name
);
18540 if (auxIt
!= end(cinfo
->methodFamiliesAux
)) {
18541 // Found an entry in the aux table. Use whatever it provides.
18542 auto const& aux
= auxIt
->second
;
18543 if (auto const ff
= aux
.funcFamily()) {
18544 return Func
{ Func::MethodFamily
{ ff
, true } };
18545 } else if (auto const f
= aux
.func()) {
18546 return aux
.isComplete()
18547 ? Func
{ Func::Method
{ func_info(*m_data
, f
) } }
18548 : Func
{ Func::MethodOrMissing
{ func_info(*m_data
, f
) } };
18550 return Func
{ Func::MissingMethod
{ cinfo
->cls
->name
, name
} };
18553 // No entry in the aux table. The result is the same as the
18554 // normal table, so fall through and use that.
18555 } else if (isExact
||
18556 meth
.attrs
& AttrNoOverride
||
18557 (!includeNonRegular
&& meth
.noOverrideRegular())) {
18558 // Either we want all classes, or the base class is regular. If
18559 // the method isn't overridden we know it must be just ftarget
18560 // (the override bits include it being missing in a subclass, so
18561 // we know it cannot be missing either).
18562 return Func
{ Func::Method
{ func_info(*m_data
, ftarget
) } };
18565 // Look up the entry in the normal method family table and use
18566 // whatever is there.
18567 auto const famIt
= cinfo
->methodFamilies
.find(name
);
18568 assertx(famIt
!= end(cinfo
->methodFamilies
));
18569 auto const& fam
= famIt
->second
;
18570 assertx(!fam
.isEmpty());
18572 if (auto const ff
= fam
.funcFamily()) {
18573 return Func
{ Func::MethodFamily
{ ff
, !includeNonRegular
} };
18574 } else if (auto const f
= fam
.func()) {
18575 return (!includeNonRegular
|| fam
.isComplete())
18576 ? Func
{ Func::Method
{ func_info(*m_data
, f
) } }
18577 : Func
{ Func::MethodOrMissing
{ func_info(*m_data
, f
) } };
18579 always_assert(false);
18583 auto const isClass
= thisType
.subtypeOf(BCls
);
18584 if (name
== s_construct
.get()) {
18586 return Func
{ Func::MethodName
{ nullptr, s_construct
.get() } };
18588 return resolve_ctor(thisType
);
18592 if (!is_specialized_cls(thisType
)) return general(true, nullptr);
18593 } else if (!is_specialized_obj(thisType
)) {
18594 return general(false, nullptr);
18597 auto const& dcls
= isClass
? dcls_of(thisType
) : dobj_of(thisType
);
18598 return rfunc_from_dcls(
18602 [&] (bool i
) { return general(i
, dcls
.smallestCls().name()); }
18606 res::Func
Index::resolve_ctor(const Type
& obj
) const {
18607 assertx(obj
.subtypeOf(BObj
));
18609 using Func
= res::Func
;
18611 // Can't say anything useful if we don't know the object type.
18612 if (!is_specialized_obj(obj
)) {
18613 return Func
{ Func::MethodName
{ nullptr, s_construct
.get() } };
18616 auto const& dcls
= dobj_of(obj
);
18617 return rfunc_from_dcls(
18620 [&] (ClassInfo
* cinfo
, bool isExact
, bool includeNonRegular
) {
18621 // We're dealing with an object here, which never uses
18622 // non-regular classes.
18623 assertx(!includeNonRegular
);
18625 // See if this class has a ctor.
18626 auto const methIt
= cinfo
->methods
.find(s_construct
.get());
18627 if (methIt
== end(cinfo
->methods
)) {
18628 // There's no ctor on this class. This doesn't mean the ctor
18629 // won't exist at runtime, it might get the default ctor, so
18630 // we have to be conservative.
18632 Func::MethodName
{ cinfo
->cls
->name
, s_construct
.get() }
18636 // We have a ctor, but it might be overridden in a subclass.
18637 auto const& mte
= methIt
->second
;
18638 assertx(!(mte
.attrs
& AttrStatic
));
18639 auto const ftarget
= func_from_meth_ref(*m_data
, mte
.meth());
18640 assertx(!(ftarget
->attrs
& AttrStatic
));
18642 // If this class is known exactly, or we know nothing overrides
18643 // this ctor, we know this ctor is precisely it.
18644 if (isExact
|| mte
.noOverrideRegular()) {
18645 // If this class isn't regular, and doesn't have any regular
18646 // subclasses (or if it's exact), this resolution will always
18648 if (!is_regular_class(*cinfo
->cls
) &&
18649 (isExact
|| !cinfo
->classGraph
.mightHaveRegularSubclass())) {
18651 Func::MissingMethod
{ cinfo
->cls
->name
, s_construct
.get() }
18654 return Func
{ Func::Method
{ func_info(*m_data
, ftarget
) } };
18657 // If this isn't a regular class, we need to check the "aux"
18658 // entry first (which always has priority when only looking at
18659 // the regular subset).
18660 if (!is_regular_class(*cinfo
->cls
)) {
18661 auto const auxIt
= cinfo
->methodFamiliesAux
.find(s_construct
.get());
18662 if (auxIt
!= end(cinfo
->methodFamiliesAux
)) {
18663 auto const& aux
= auxIt
->second
;
18664 if (auto const ff
= aux
.funcFamily()) {
18665 return Func
{ Func::MethodFamily
{ ff
, true } };
18666 } else if (auto const f
= aux
.func()) {
18667 return aux
.isComplete()
18668 ? Func
{ Func::Method
{ func_info(*m_data
, f
) } }
18669 : Func
{ Func::MethodOrMissing
{ func_info(*m_data
, f
) } };
18671 // Ctor doesn't exist in any regular subclasses. This can
18672 // happen with interfaces. The ctor might get the default
18673 // ctor at runtime, so be conservative.
18675 Func::MethodName
{ cinfo
->cls
->name
, s_construct
.get() }
18680 // Otherwise this class is regular (in which case there's just
18681 // method families, or there's no entry in aux, which means the
18682 // regular subset entry is the same as the full entry.
18684 auto const famIt
= cinfo
->methodFamilies
.find(s_construct
.get());
18685 assertx(famIt
!= cinfo
->methodFamilies
.end());
18686 auto const& fam
= famIt
->second
;
18687 assertx(!fam
.isEmpty());
18689 if (auto const ff
= fam
.funcFamily()) {
18690 return Func
{ Func::MethodFamily
{ ff
, true } };
18691 } else if (auto const f
= fam
.func()) {
18692 // Since we're looking at the regular subset, we can assume
18693 // the set is complete, regardless of the flag on fam.
18694 return Func
{ Func::Method
{ func_info(*m_data
, f
) } };
18696 always_assert(false);
18699 [&] (bool includeNonRegular
) {
18700 assertx(!includeNonRegular
);
18702 Func::MethodName
{ dcls
.smallestCls().name(), s_construct
.get() }
18708 res::Func
Index::resolve_func(SString name
) const {
18709 name
= normalizeNS(name
);
18710 auto const it
= m_data
->funcs
.find(name
);
18711 if (it
== end(m_data
->funcs
)) {
18712 return res::Func
{ res::Func::MissingFunc
{ name
} };
18714 auto const func
= it
->second
;
18715 assertx(func
->attrs
& AttrPersistent
);
18716 return res::Func
{ res::Func::Fun
{ func_info(*m_data
, func
) } };
18719 res::Func
Index::resolve_func_or_method(const php::Func
& f
) const {
18720 if (!f
.cls
) return res::Func
{ res::Func::Fun
{ func_info(*m_data
, &f
) } };
18721 return res::Func
{ res::Func::Method
{ func_info(*m_data
, &f
) } };
18724 bool Index::func_depends_on_arg(const php::Func
* func
, size_t arg
) const {
18725 auto const& finfo
= *func_info(*m_data
, func
);
18726 return arg
>= finfo
.unusedParams
.size() || !finfo
.unusedParams
.test(arg
);
18729 // Helper function: Given a DCls, visit every subclass it represents,
18730 // passing it to the given callable. If the callable returns false,
18731 // stop iteration. Return false if any of the classes is unresolved,
18732 // true otherwise. This is used to simplify the below functions which
18733 // need to iterate over all possible subclasses and union the results.
18734 template <typename F
>
18735 bool Index::visit_every_dcls_cls(const DCls
& dcls
, const F
& f
) const {
18736 if (dcls
.isExact()) {
18737 auto const cinfo
= dcls
.cls().cinfo();
18738 if (!cinfo
) return false;
18739 if (dcls
.containsNonRegular() || is_regular_class(*cinfo
->cls
)) {
18743 } else if (dcls
.isSub()) {
18744 auto unresolved
= false;
18745 res::Class::visitEverySub(
18746 std::array
<res::Class
, 1>{dcls
.cls()},
18747 dcls
.containsNonRegular(),
18748 [&] (res::Class c
) {
18749 if (c
.hasCompleteChildren()) {
18750 if (auto const cinfo
= c
.cinfo()) return f(cinfo
);
18756 return !unresolved
;
18757 } else if (dcls
.isIsect()) {
18758 auto const& isect
= dcls
.isect();
18759 assertx(isect
.size() > 1);
18761 auto unresolved
= false;
18762 res::Class::visitEverySub(
18764 dcls
.containsNonRegular(),
18765 [&] (res::Class c
) {
18766 if (c
.hasCompleteChildren()) {
18767 if (auto const cinfo
= c
.cinfo()) return f(cinfo
);
18773 return !unresolved
;
18775 // Even though this has an intersection list, it must be the exact
18776 // class, so it's sufficient to provide that.
18777 assertx(dcls
.isIsectAndExact());
18778 auto const e
= dcls
.isectAndExact().first
;
18779 auto const cinfo
= e
.cinfo();
18780 if (!cinfo
) return false;
18781 if (dcls
.containsNonRegular() || is_regular_class(*cinfo
->cls
)) {
18788 ClsConstLookupResult
Index::lookup_class_constant(Context ctx
,
18790 const Type
& name
) const {
18791 ITRACE(4, "lookup_class_constant: ({}) {}::{}\n",
18792 show(ctx
), show(cls
), show(name
));
18795 using R
= ClsConstLookupResult
;
18797 auto const conservative
= [] {
18798 ITRACE(4, "conservative\n");
18799 return R
{ TInitCell
, TriBool::Maybe
, true };
18802 auto const notFound
= [] {
18803 ITRACE(4, "not found\n");
18804 return R
{ TBottom
, TriBool::No
, false };
18807 if (!is_specialized_cls(cls
)) return conservative();
18809 // We could easily support the case where we don't know the constant
18810 // name, but know the class (like we do for properties), by unioning
18811 // together all possible constants. However it very rarely happens,
18812 // but when it does, the set of constants to union together can be
18813 // huge and it becomes very expensive.
18814 if (!is_specialized_string(name
)) return conservative();
18815 auto const sname
= sval_of(name
);
18817 // If this lookup is safe to cache. Some classes can have a huge
18818 // number of subclasses and unioning together all possible constants
18819 // can become very expensive. We can aleviate some of this expense
18820 // by caching results. We cannot cache a result when we use 86cinit
18821 // analysis since that can change.
18822 auto cachable
= true;
18824 auto const process
= [&] (const ClassInfo
* ci
) {
18825 ITRACE(4, "{}:\n", ci
->cls
->name
);
18828 // Does the constant exist on this class?
18829 auto const it
= ci
->clsConstants
.find(sname
);
18830 if (it
== ci
->clsConstants
.end()) return notFound();
18832 // Is it a value and is it non-abstract (we only deal with
18833 // concrete constants).
18834 auto const& cns
= *it
->second
.get();
18835 if (cns
.kind
!= ConstModifiers::Kind::Value
) return notFound();
18836 if (!cns
.val
.has_value()) return notFound();
18838 auto const cnsIdx
= it
->second
.idx
;
18840 // Determine the constant's value and return it
18841 auto const r
= [&] {
18842 if (cns
.val
->m_type
== KindOfUninit
) {
18843 // Constant is defined by a 86cinit. Use the result from
18844 // analysis and add a dependency. We cannot cache in this
18847 auto const cnsCls
= m_data
->classes
.at(cns
.cls
);
18849 auto const cinit
= cnsCls
->methods
.back().get();
18850 assertx(cinit
->name
== s_86cinit
.get());
18851 add_dependency(*m_data
, cinit
, ctx
, Dep::ClsConst
);
18854 ITRACE(4, "(dynamic)\n");
18855 auto const type
= [&] {
18856 auto const cnsClsCi
= folly::get_default(m_data
->classInfo
, cnsCls
->name
);
18857 if (!cnsClsCi
|| cnsIdx
>= cnsClsCi
->clsConstTypes
.size()) {
18860 return cnsClsCi
->clsConstTypes
[cnsIdx
].type
;
18862 return R
{ type
, TriBool::Yes
, true };
18865 // Fully resolved constant with a known value
18866 auto mightThrow
= bool(ci
->cls
->attrs
& AttrInternal
);
18868 auto const unit
= lookup_class_unit(*ci
->cls
);
18869 auto const moduleName
= unit
->moduleName
;
18870 auto const packageInfo
= unit
->packageInfo
;
18871 if (auto const activeDeployment
= packageInfo
.getActiveDeployment()) {
18872 if (!packageInfo
.moduleInDeployment(
18873 moduleName
, *activeDeployment
, DeployKind::Hard
)) {
18878 return R
{ from_cell(*cns
.val
), TriBool::Yes
, mightThrow
};
18880 ITRACE(4, "-> {}\n", show(r
));
18884 auto const& dcls
= dcls_of(cls
);
18885 if (dcls
.isSub()) {
18886 // Before anything, look up this entry in the cache. We don't
18887 // bother with the cache for the exact case because it's quick and
18888 // there's little point.
18889 auto const cinfo
= dcls
.cls().cinfo();
18890 if (!cinfo
) return conservative();
18891 if (auto const it
=
18892 m_data
->clsConstLookupCache
.find(std::make_pair(cinfo
->cls
, sname
));
18893 it
!= m_data
->clsConstLookupCache
.end()) {
18894 ITRACE(4, "cache hit: {}\n", show(it
->second
));
18899 Optional
<R
> result
;
18900 auto const resolved
= visit_every_dcls_cls(
18902 [&] (const ClassInfo
* cinfo
) {
18903 if (result
) ITRACE(5, "-> {}\n", show(*result
));
18904 auto r
= process(cinfo
);
18906 result
.emplace(std::move(r
));
18913 if (!resolved
) return conservative();
18914 assertx(result
.has_value());
18916 // Save this for future lookups if we can
18917 if (dcls
.isSub() && cachable
) {
18918 auto const cinfo
= dcls
.cls().cinfo();
18920 m_data
->clsConstLookupCache
.emplace(
18921 std::make_pair(cinfo
->cls
, sname
),
18926 ITRACE(4, "-> {}\n", show(*result
));
18930 std::vector
<std::pair
<SString
, ConstIndex
>>
18931 Index::lookup_flattened_class_type_constants(const php::Class
&) const {
18932 // Should never be used with an Index.
18933 always_assert(false);
18936 std::vector
<std::pair
<SString
, ClsConstInfo
>>
18937 Index::lookup_class_constants(const php::Class
& cls
) const {
18938 std::vector
<std::pair
<SString
, ClsConstInfo
>> out
;
18939 out
.reserve(cls
.constants
.size());
18941 auto const cinfo
= folly::get_default(m_data
->classInfo
, cls
.name
);
18942 for (size_t i
= 0, size
= cls
.constants
.size(); i
< size
; ++i
) {
18943 auto const& cns
= cls
.constants
[i
];
18944 if (cns
.kind
!= ConstModifiers::Kind::Value
) continue;
18945 if (!cns
.val
) continue;
18946 if (cns
.val
->m_type
!= KindOfUninit
) {
18947 out
.emplace_back(cns
.name
, ClsConstInfo
{ from_cell(*cns
.val
), 0 });
18948 } else if (cinfo
&& i
< cinfo
->clsConstTypes
.size()) {
18949 out
.emplace_back(cns
.name
, cinfo
->clsConstTypes
[i
]);
18951 out
.emplace_back(cns
.name
, ClsConstInfo
{ TInitCell
, 0 });
18957 ClsTypeConstLookupResult
18958 Index::lookup_class_type_constant(
18961 const ClsTypeConstLookupResolver
& resolver
) const {
18962 ITRACE(4, "lookup_class_type_constant: {}::{}\n", show(cls
), show(name
));
18965 using R
= ClsTypeConstLookupResult
;
18967 auto const conservative
= [] {
18968 ITRACE(4, "conservative\n");
18970 TypeStructureResolution
{ TSDictN
, true },
18976 auto const notFound
= [] {
18977 ITRACE(4, "not found\n");
18979 TypeStructureResolution
{ TBottom
, false },
18985 // Unlike lookup_class_constant, we distinguish abstract from
18986 // not-found, as the runtime sometimes treats them differently.
18987 auto const abstract
= [] {
18988 ITRACE(4, "abstract\n");
18990 TypeStructureResolution
{ TBottom
, false },
18996 if (!is_specialized_cls(cls
)) return conservative();
18998 // As in lookup_class_constant, we could handle this, but it's not
19000 if (!is_specialized_string(name
)) return conservative();
19001 auto const sname
= sval_of(name
);
19003 auto const process
= [&] (const ClassInfo
* ci
) {
19004 ITRACE(4, "{}:\n", ci
->cls
->name
);
19007 // Does the type constant exist on this class?
19008 auto const it
= ci
->clsConstants
.find(sname
);
19009 if (it
== ci
->clsConstants
.end()) return notFound();
19011 // Is it an actual non-abstract type-constant?
19012 auto const& cns
= *it
->second
;
19013 if (cns
.kind
!= ConstModifiers::Kind::Type
) return notFound();
19014 if (!cns
.val
.has_value()) return abstract();
19016 assertx(tvIsDict(*cns
.val
));
19017 ITRACE(4, "({}) {}\n", cns
.cls
, show(dict_val(val(*cns
.val
).parr
)));
19019 // If we've been given a resolver, use it. Otherwise resolve it in
19021 auto resolved
= resolver
19022 ? resolver(cns
, *ci
->cls
)
19023 : resolve_type_structure(IndexAdaptor
{ *this }, cns
, *ci
->cls
);
19025 // The result of resolve_type_structure isn't, in general,
19026 // static. However a type-constant will always be, so force that
19028 assertx(resolved
.type
.is(BBottom
) || resolved
.type
.couldBe(BUnc
));
19029 resolved
.type
&= TUnc
;
19031 std::move(resolved
),
19035 ITRACE(4, "-> {}\n", show(r
));
19039 auto const& dcls
= dcls_of(cls
);
19041 Optional
<R
> result
;
19042 auto const resolved
= visit_every_dcls_cls(
19044 [&] (const ClassInfo
* cinfo
) {
19046 ITRACE(5, "-> {}\n", show(*result
));
19048 auto r
= process(cinfo
);
19050 result
.emplace(std::move(r
));
19057 if (!resolved
) return conservative();
19058 assertx(result
.has_value());
19060 ITRACE(4, "-> {}\n", show(*result
));
19064 ClsTypeConstLookupResult
19065 Index::lookup_class_type_constant(const php::Class
&,
19067 HHBBC::ConstIndex
) const {
19068 // Should never be called with an Index.
19069 always_assert(false);
19072 Type
Index::lookup_constant(Context ctx
, SString cnsName
) const {
19073 auto iter
= m_data
->constants
.find(cnsName
);
19074 if (iter
== end(m_data
->constants
)) return TBottom
;
19076 auto constant
= iter
->second
;
19077 if (type(constant
->val
) != KindOfUninit
) {
19078 return from_cell(constant
->val
);
19081 // Assume a runtime call to Constant::get(), which will invoke
19082 // 86cinit_<cnsName>(). Look up it's return type.
19084 auto const func_name
= Constant::funcNameFromName(cnsName
);
19085 assertx(func_name
&& "func_name will never be nullptr");
19087 auto rfunc
= resolve_func(func_name
);
19088 assertx(rfunc
.exactFunc());
19090 return lookup_return_type(ctx
, nullptr, rfunc
, Dep::ConstVal
).t
;
19094 Index::lookup_foldable_return_type(Context ctx
,
19095 const CallContext
& calleeCtx
) const {
19096 auto const func
= calleeCtx
.callee
;
19097 constexpr auto max_interp_nexting_level
= 2;
19098 static __thread
uint32_t interp_nesting_level
;
19100 using R
= ReturnType
;
19102 auto const ctxType
= adjust_closure_context(
19103 IndexAdaptor
{ *this },
19107 // Don't fold functions when staticness mismatches
19108 if (!func
->isClosureBody
) {
19109 if ((func
->attrs
& AttrStatic
) && ctxType
.couldBe(TObj
)) {
19110 return R
{ TInitCell
, false };
19112 if (!(func
->attrs
& AttrStatic
) && ctxType
.couldBe(TCls
)) {
19113 return R
{ TInitCell
, false };
19117 auto const& finfo
= *func_info(*m_data
, func
);
19118 if (finfo
.effectFree
&& is_scalar(finfo
.returnTy
)) {
19119 return R
{ finfo
.returnTy
, true };
19122 auto showArgs DEBUG_ONLY
= [] (const CompactVector
<Type
>& a
) {
19123 std::string ret
, sep
;
19124 for (auto& arg
: a
) {
19125 folly::format(&ret
, "{}{}", sep
, show(arg
));
19132 ContextRetTyMap::const_accessor acc
;
19133 if (m_data
->foldableReturnTypeMap
.find(acc
, calleeCtx
)) {
19136 "Found foldableReturnType for {}{}{} with args {} (hash: {})\n",
19137 func
->cls
? func
->cls
->name
: staticEmptyString(),
19138 func
->cls
? "::" : "",
19140 showArgs(calleeCtx
.args
),
19141 CallContextHashCompare
{}.hash(calleeCtx
));
19143 assertx(is_scalar(acc
->second
.t
));
19144 assertx(acc
->second
.effectFree
);
19145 return acc
->second
;
19152 "MISSING: foldableReturnType for {}{}{} with args {} (hash: {})\n",
19153 func
->cls
? func
->cls
->name
: staticEmptyString(),
19154 func
->cls
? "::" : "",
19156 showArgs(calleeCtx
.args
),
19157 CallContextHashCompare
{}.hash(calleeCtx
));
19158 return R
{ TInitCell
, false };
19161 if (interp_nesting_level
> max_interp_nexting_level
) {
19162 add_dependency(*m_data
, func
, ctx
, Dep::InlineDepthLimit
);
19163 return R
{ TInitCell
, false };
19166 auto const contextType
= [&] {
19167 ++interp_nesting_level
;
19168 SCOPE_EXIT
{ --interp_nesting_level
; };
19170 auto const wf
= php::WideFunc::cns(func
);
19171 auto const fa
= analyze_func_inline(
19172 IndexAdaptor
{ *this },
19173 AnalysisContext
{ func
->unit
, wf
, func
->cls
, &ctx
.forDep() },
19177 CollectionOpts::EffectFreeOnly
19180 fa
.effectFree
? fa
.inferredReturn
: TInitCell
,
19185 if (!is_scalar(contextType
.t
)) return R
{ TInitCell
, false };
19187 ContextRetTyMap::accessor acc
;
19188 if (m_data
->foldableReturnTypeMap
.insert(acc
, calleeCtx
)) {
19189 acc
->second
= contextType
;
19191 // someone beat us to it
19192 assertx(acc
->second
.t
== contextType
.t
);
19194 return contextType
;
19197 Index::ReturnType
Index::lookup_return_type(Context ctx
,
19198 MethodsInfo
* methods
,
19201 using R
= ReturnType
;
19203 auto const funcFamily
= [&] (FuncFamily
* fam
, bool regularOnly
) {
19204 add_dependency(*m_data
, fam
, ctx
, dep
);
19205 return fam
->infoFor(regularOnly
).m_returnTy
.get(
19207 auto ret
= TBottom
;
19208 auto effectFree
= true;
19209 for (auto const pf
: fam
->possibleFuncs()) {
19210 if (regularOnly
&& !pf
.inRegular()) continue;
19211 auto const finfo
= func_info(*m_data
, pf
.ptr());
19212 if (!finfo
->func
) return R
{ TInitCell
, false };
19213 ret
|= unctx(finfo
->returnTy
);
19214 effectFree
&= finfo
->effectFree
;
19215 if (!ret
.strictSubtypeOf(BInitCell
) && !effectFree
) break;
19217 return R
{ std::move(ret
), effectFree
};
19221 auto const meth
= [&] (const php::Func
* func
) {
19223 if (auto ret
= methods
->lookupReturnType(*func
)) {
19224 return R
{ unctx(std::move(ret
->t
)), ret
->effectFree
};
19227 add_dependency(*m_data
, func
, ctx
, dep
);
19228 auto const finfo
= func_info(*m_data
, func
);
19229 if (!finfo
->func
) return R
{ TInitCell
, false };
19230 return R
{ unctx(finfo
->returnTy
), finfo
->effectFree
};
19235 [&] (res::Func::FuncName
) { return R
{ TInitCell
, false }; },
19236 [&] (res::Func::MethodName
) { return R
{ TInitCell
, false }; },
19237 [&] (res::Func::Fun f
) {
19238 add_dependency(*m_data
, f
.finfo
->func
, ctx
, dep
);
19239 return R
{ unctx(f
.finfo
->returnTy
), f
.finfo
->effectFree
};
19241 [&] (res::Func::Method m
) { return meth(m
.finfo
->func
); },
19242 [&] (res::Func::MethodFamily fam
) {
19243 return funcFamily(fam
.family
, fam
.regularOnly
);
19245 [&] (res::Func::MethodOrMissing m
) { return meth(m
.finfo
->func
); },
19246 [&] (res::Func::MissingFunc
) { return R
{ TBottom
, false }; },
19247 [&] (res::Func::MissingMethod
) { return R
{ TBottom
, false }; },
19248 [&] (const res::Func::Isect
& i
) {
19249 auto ty
= TInitCell
;
19250 auto anyEffectFree
= false;
19251 for (auto const ff
: i
.families
) {
19252 auto const [t
, e
] = funcFamily(ff
, i
.regularOnly
);
19254 if (e
) anyEffectFree
= true;
19256 return R
{ std::move(ty
), anyEffectFree
};
19258 [&] (res::Func::Fun2
) -> R
{ always_assert(false); },
19259 [&] (res::Func::Method2
) -> R
{ always_assert(false); },
19260 [&] (res::Func::MethodFamily2
) -> R
{ always_assert(false); },
19261 [&] (res::Func::MethodOrMissing2
) -> R
{ always_assert(false); },
19262 [&] (res::Func::Isect2
&) -> R
{ always_assert(false); }
19266 Index::ReturnType
Index::lookup_return_type(Context caller
,
19267 MethodsInfo
* methods
,
19268 const CompactVector
<Type
>& args
,
19269 const Type
& context
,
19272 using R
= ReturnType
;
19274 auto const funcFamily
= [&] (FuncFamily
* fam
, bool regularOnly
) {
19275 add_dependency(*m_data
, fam
, caller
, dep
);
19276 auto ret
= fam
->infoFor(regularOnly
).m_returnTy
.get(
19279 auto effectFree
= true;
19280 for (auto const pf
: fam
->possibleFuncs()) {
19281 if (regularOnly
&& !pf
.inRegular()) continue;
19282 auto const finfo
= func_info(*m_data
, pf
.ptr());
19283 if (!finfo
->func
) return R
{ TInitCell
, false };
19284 ty
|= finfo
->returnTy
;
19285 effectFree
&= finfo
->effectFree
;
19286 if (!ty
.strictSubtypeOf(BInitCell
) && !effectFree
) break;
19288 return R
{ std::move(ty
), effectFree
};
19292 return_with_context(std::move(ret
.t
), context
),
19296 auto const meth
= [&] (const php::Func
* func
) {
19297 auto const finfo
= func_info(*m_data
, func
);
19298 if (!finfo
->func
) return R
{ TInitCell
, false };
19300 auto returnType
= [&] {
19302 if (auto ret
= methods
->lookupReturnType(*func
)) {
19306 add_dependency(*m_data
, func
, caller
, dep
);
19307 return R
{ finfo
->returnTy
, finfo
->effectFree
};
19310 return context_sensitive_return_type(
19313 { finfo
->func
, args
, context
},
19314 std::move(returnType
)
19320 [&] (res::Func::FuncName
) {
19321 return lookup_return_type(caller
, methods
, rfunc
, dep
);
19323 [&] (res::Func::MethodName
) {
19324 return lookup_return_type(caller
, methods
, rfunc
, dep
);
19326 [&] (res::Func::Fun f
) {
19327 add_dependency(*m_data
, f
.finfo
->func
, caller
, dep
);
19328 return context_sensitive_return_type(
19331 { f
.finfo
->func
, args
, context
},
19332 R
{ f
.finfo
->returnTy
, f
.finfo
->effectFree
}
19335 [&] (res::Func::Method m
) { return meth(m
.finfo
->func
); },
19336 [&] (res::Func::MethodFamily fam
) {
19337 return funcFamily(fam
.family
, fam
.regularOnly
);
19339 [&] (res::Func::MethodOrMissing m
) { return meth(m
.finfo
->func
); },
19340 [&] (res::Func::MissingFunc
) { return R
{ TBottom
, false }; },
19341 [&] (res::Func::MissingMethod
) { return R
{ TBottom
, false }; },
19342 [&] (const res::Func::Isect
& i
) {
19343 auto ty
= TInitCell
;
19344 auto anyEffectFree
= false;
19345 for (auto const ff
: i
.families
) {
19346 auto const [t
, e
] = funcFamily(ff
, i
.regularOnly
);
19348 if (e
) anyEffectFree
= true;
19350 return R
{ std::move(ty
), anyEffectFree
};
19352 [&] (res::Func::Fun2
) -> R
{ always_assert(false); },
19353 [&] (res::Func::Method2
) -> R
{ always_assert(false); },
19354 [&] (res::Func::MethodFamily2
) -> R
{ always_assert(false); },
19355 [&] (res::Func::MethodOrMissing2
) -> R
{ always_assert(false); },
19356 [&] (res::Func::Isect2
&) -> R
{ always_assert(false); }
19360 std::pair
<Index::ReturnType
, size_t>
19361 Index::lookup_return_type_raw(const php::Func
* f
) const {
19362 auto it
= func_info(*m_data
, f
);
19364 assertx(it
->func
== f
);
19366 ReturnType
{ it
->returnTy
, it
->effectFree
},
19367 it
->returnRefinements
19370 return { ReturnType
{ TInitCell
, false }, 0 };
19373 CompactVector
<Type
>
19374 Index::lookup_closure_use_vars(const php::Func
* func
,
19376 assertx(func
->isClosureBody
);
19378 auto const numUseVars
= closure_num_use_vars(func
);
19379 if (!numUseVars
) return {};
19380 auto const it
= m_data
->closureUseVars
.find(func
->cls
);
19381 if (it
== end(m_data
->closureUseVars
)) {
19382 return CompactVector
<Type
>(numUseVars
, TCell
);
19384 if (move
) return std::move(it
->second
);
19389 Index::lookup_private_props(const php::Class
* cls
,
19391 auto it
= m_data
->privatePropInfo
.find(cls
);
19392 if (it
!= end(m_data
->privatePropInfo
)) {
19393 if (move
) return std::move(it
->second
);
19396 return make_unknown_propstate(
19397 IndexAdaptor
{ *this },
19399 [&] (const php::Prop
& prop
) {
19400 return (prop
.attrs
& AttrPrivate
) && !(prop
.attrs
& AttrStatic
);
19406 Index::lookup_private_statics(const php::Class
* cls
,
19408 auto it
= m_data
->privateStaticPropInfo
.find(cls
);
19409 if (it
!= end(m_data
->privateStaticPropInfo
)) {
19410 if (move
) return std::move(it
->second
);
19413 return make_unknown_propstate(
19414 IndexAdaptor
{ *this },
19416 [&] (const php::Prop
& prop
) {
19417 return (prop
.attrs
& AttrPrivate
) && (prop
.attrs
& AttrStatic
);
19422 PropState
Index::lookup_public_statics(const php::Class
* cls
) const {
19423 auto const cinfo
= [&] () -> const ClassInfo
* {
19424 auto const it
= m_data
->classInfo
.find(cls
->name
);
19425 if (it
== end(m_data
->classInfo
)) return nullptr;
19430 for (auto const& prop
: cls
->properties
) {
19431 if (!(prop
.attrs
& (AttrPublic
|AttrProtected
)) ||
19432 !(prop
.attrs
& AttrStatic
)) {
19436 auto [ty
, everModified
] = [&] {
19437 if (!cinfo
) return std::make_pair(TInitCell
, true);
19439 if (!m_data
->seenPublicSPropMutations
) {
19440 return std::make_pair(
19442 adjust_type_for_prop(
19443 IndexAdaptor
{ *this },
19445 &prop
.typeConstraint
,
19448 initial_type_for_public_sprop(*this, *cls
, prop
)
19454 auto const it
= cinfo
->publicStaticProps
.find(prop
.name
);
19455 if (it
== end(cinfo
->publicStaticProps
)) {
19456 return std::make_pair(
19457 initial_type_for_public_sprop(*this, *cls
, prop
),
19461 return std::make_pair(
19462 it
->second
.inferredType
,
19463 it
->second
.everModified
19470 &prop
.typeConstraint
,
19480 * Entry point for static property lookups from the Index. Return
19481 * metadata about a `cls'::`name' static property access in the given
19484 PropLookupResult
Index::lookup_static(Context ctx
,
19485 const PropertiesInfo
& privateProps
,
19487 const Type
& name
) const {
19488 ITRACE(4, "lookup_static: {} {}::${}\n", show(ctx
), show(cls
), show(name
));
19491 using R
= PropLookupResult
;
19493 // First try to obtain the property name as a static string
19494 auto const sname
= [&] () -> SString
{
19495 // Treat non-string names conservatively, but the caller should be
19497 if (!is_specialized_string(name
)) return nullptr;
19498 return sval_of(name
);
19501 // Conservative result when we can't do any better. The type can be
19502 // anything, and anything might throw.
19503 auto const conservative
= [&] {
19504 ITRACE(4, "conservative\n");
19517 // If we don't know what `cls' is, there's not much we can do.
19518 if (!is_specialized_cls(cls
)) return conservative();
19520 // Turn the context class into a ClassInfo* for convenience.
19521 const ClassInfo
* ctxCls
= nullptr;
19523 // I don't think this can ever fail (we should always be able to
19524 // resolve the class since we're currently processing it). If it
19525 // does, be conservative.
19526 auto const rCtx
= resolve_class(ctx
.cls
->name
);
19527 if (!rCtx
) return conservative();
19528 ctxCls
= rCtx
->cinfo();
19529 if (!ctxCls
) return conservative();
19532 auto const& dcls
= dcls_of(cls
);
19533 auto const start
= dcls
.cls();
19535 Optional
<R
> result
;
19536 auto const resolved
= visit_every_dcls_cls(
19538 [&] (const ClassInfo
* cinfo
) {
19539 auto r
= lookup_static_impl(
19546 dcls
.isSub() && !sname
&& cinfo
!= start
.cinfo()
19548 ITRACE(4, "{} -> {}\n", cinfo
->cls
->name
, show(r
));
19550 result
.emplace(std::move(r
));
19557 if (!resolved
) return conservative();
19558 assertx(result
.has_value());
19560 ITRACE(4, "union -> {}\n", show(*result
));
19564 Type
Index::lookup_public_prop(const Type
& obj
, const Type
& name
) const {
19565 if (!is_specialized_obj(obj
)) return TCell
;
19567 if (!is_specialized_string(name
)) return TCell
;
19568 auto const sname
= sval_of(name
);
19571 auto const resolved
= visit_every_dcls_cls(
19573 [&] (const ClassInfo
* cinfo
) {
19574 ty
|= lookup_public_prop_impl(
19579 return ty
.strictSubtypeOf(TCell
);
19582 if (!resolved
) return TCell
;
19586 Type
Index::lookup_public_prop(const php::Class
* cls
, SString name
) const {
19587 auto const it
= m_data
->classInfo
.find(cls
->name
);
19588 if (it
== end(m_data
->classInfo
)) {
19591 return lookup_public_prop_impl(*m_data
, it
->second
, name
);
19594 bool Index::lookup_class_init_might_raise(Context ctx
, res::Class cls
) const {
19595 if (auto const ci
= cls
.cinfo()) {
19596 return class_init_might_raise(*m_data
, ctx
, ci
);
19597 } else if (cls
.cinfo2()) {
19598 // Not implemented yet
19599 always_assert(false);
19606 Index::lookup_iface_vtable_slot(const php::Class
* cls
) const {
19607 return folly::get_default(m_data
->ifaceSlotMap
, cls
->name
, kInvalidSlot
);
19610 //////////////////////////////////////////////////////////////////////
19613 * Entry point for static property type mutation from the Index. Merge
19614 * `val' into the known type for any accessible `cls'::`name' static
19615 * property. The mutation will be recovered into either
19616 * `publicMutations' or `privateProps' depending on the properties
19617 * found. Mutations to AttrConst properties are ignored, unless
19618 * `ignoreConst' is true.
19620 PropMergeResult
Index::merge_static_type(
19622 PublicSPropMutations
& publicMutations
,
19623 PropertiesInfo
& privateProps
,
19629 bool mustBeReadOnly
) const {
19631 4, "merge_static_type: {} {}::${} {}\n",
19632 show(ctx
), show(cls
), show(name
), show(val
)
19636 assertx(val
.subtypeOf(BInitCell
));
19638 using R
= PropMergeResult
;
19640 // In some cases we might try to merge Bottom if we're in
19641 // unreachable code. This won't affect anything, so just skip out
19643 if (val
.subtypeOf(BBottom
)) return R
{ TBottom
, TriBool::No
};
19645 // Try to turn the given property name into a static string
19646 auto const sname
= [&] () -> SString
{
19647 // Non-string names are treated conservatively here. The caller
19648 // should be checking for these and doing the right thing.
19649 if (!is_specialized_string(name
)) return nullptr;
19650 return sval_of(name
);
19653 // The case where we don't know `cls':
19654 auto const unknownCls
= [&] {
19656 // Very bad case. We don't know `cls' or the property name. This
19657 // mutation can be affecting anything, so merge it into all
19658 // properties (this drops type information for public
19660 ITRACE(4, "unknown class and prop. merging everything\n");
19661 publicMutations
.mergeUnknown(ctx
);
19662 privateProps
.mergeInAllPrivateStatics(
19663 IndexAdaptor
{ *this }, unctx(val
), ignoreConst
, mustBeReadOnly
19666 // Otherwise we don't know `cls', but do know the property
19667 // name. We'll store this mutation separately and union it in to
19668 // any lookup with the same name.
19669 ITRACE(4, "unknown class. merging all props with name {}\n", sname
);
19671 publicMutations
.mergeUnknownClass(sname
, unctx(val
));
19673 // Assume that it could possibly affect any private property with
19675 privateProps
.mergeInPrivateStatic(
19676 IndexAdaptor
{ *this }, sname
, unctx(val
), ignoreConst
, mustBeReadOnly
19680 // To be conservative, say we might throw and be conservative about
19682 return PropMergeResult
{
19683 loosen_likeness(val
),
19688 // check if we can determine the class.
19689 if (!is_specialized_cls(cls
)) return unknownCls();
19691 const ClassInfo
* ctxCls
= nullptr;
19693 auto const rCtx
= resolve_class(ctx
.cls
->name
);
19694 // We should only be not able to resolve our own context if the
19695 // class is not instantiable. In that case, the merge can't
19697 if (!rCtx
) return R
{ TBottom
, TriBool::No
};
19698 ctxCls
= rCtx
->cinfo();
19699 if (!ctxCls
) return unknownCls();
19702 auto const mergePublic
= [&] (const ClassInfo
* ci
,
19703 const php::Prop
& prop
,
19705 publicMutations
.mergeKnown(ci
, prop
, val
);
19708 auto const& dcls
= dcls_of(cls
);
19709 Optional
<res::Class
> start
;
19710 if (dcls
.isExact() || dcls
.isSub()) {
19711 start
= dcls
.cls();
19712 } else if (dcls
.isIsectAndExact()) {
19713 start
= dcls
.isectAndExact().first
;
19716 Optional
<R
> result
;
19717 auto const resolved
= visit_every_dcls_cls(
19719 [&] (const ClassInfo
* cinfo
) {
19720 auto r
= merge_static_type_impl(
19732 dcls
.isSub() && !sname
&& cinfo
!= start
->cinfo()
19734 ITRACE(4, "{} -> {}\n", cinfo
->cls
->name
, show(r
));
19736 result
.emplace(std::move(r
));
19743 if (!resolved
) return unknownCls();
19744 assertx(result
.has_value());
19745 ITRACE(4, "union -> {}\n", show(*result
));
19749 //////////////////////////////////////////////////////////////////////
19751 DependencyContext
Index::dependency_context(const Context
& ctx
) const {
19752 return dep_context(*m_data
, ctx
);
19755 bool Index::using_class_dependencies() const {
19756 return m_data
->useClassDependencies
;
19759 void Index::use_class_dependencies(bool f
) {
19760 if (f
!= m_data
->useClassDependencies
) {
19761 m_data
->dependencyMap
.clear();
19762 m_data
->useClassDependencies
= f
;
19766 void Index::refine_class_constants(const Context
& ctx
,
19767 const ResolvedConstants
& resolved
,
19768 DependencyContextSet
& deps
) {
19769 if (resolved
.empty()) return;
19771 auto changed
= false;
19772 auto const cls
= ctx
.func
->cls
;
19774 auto& constants
= cls
->constants
;
19776 for (auto& c
: resolved
) {
19777 assertx(c
.first
< constants
.size());
19778 auto& cnst
= constants
[c
.first
];
19779 assertx(cnst
.kind
== ConstModifiers::Kind::Value
);
19781 always_assert(cnst
.val
&& type(*cnst
.val
) == KindOfUninit
);
19782 if (auto const val
= tv(c
.second
.type
)) {
19783 assertx(val
->m_type
!= KindOfUninit
);
19785 // Deleting from the types map is too expensive, so just leave
19786 // any entry. We won't look at it if val is set.
19788 } else if (auto const cinfo
=
19789 folly::get_default(m_data
->classInfo
, cls
->name
)) {
19790 auto& old
= [&] () -> ClsConstInfo
& {
19791 if (c
.first
>= cinfo
->clsConstTypes
.size()) {
19792 auto const newSize
= std::max(c
.first
+1, resolved
.back().first
+1);
19793 cinfo
->clsConstTypes
.resize(newSize
, ClsConstInfo
{ TInitCell
, 0 });
19795 return cinfo
->clsConstTypes
[c
.first
];
19798 if (c
.second
.type
.strictlyMoreRefined(old
.type
)) {
19799 always_assert(c
.second
.refinements
> old
.refinements
);
19800 old
= std::move(c
.second
);
19803 always_assert_flog(
19804 c
.second
.type
.moreRefined(old
.type
),
19805 "Class constant type invariant violated for {}::{}\n"
19806 " {} is not at least as refined as {}\n",
19807 ctx
.func
->cls
->name
,
19809 show(c
.second
.type
),
19817 find_deps(*m_data
, ctx
.func
, Dep::ClsConst
, deps
);
19821 void Index::refine_constants(const FuncAnalysisResult
& fa
,
19822 DependencyContextSet
& deps
) {
19823 auto const& func
= fa
.ctx
.func
;
19824 if (func
->cls
) return;
19826 auto const cns_name
= Constant::nameFromFuncName(func
->name
);
19827 if (!cns_name
) return;
19829 auto const cns
= m_data
->constants
.at(cns_name
);
19830 auto const val
= tv(fa
.inferredReturn
);
19832 always_assert_flog(
19833 type(cns
->val
) == KindOfUninit
,
19834 "Constant value invariant violated in {}.\n"
19835 " Value went from {} to {}",
19837 show(from_cell(cns
->val
)),
19838 show(fa
.inferredReturn
)
19843 if (type(cns
->val
) != KindOfUninit
) {
19844 always_assert_flog(
19845 from_cell(cns
->val
) == fa
.inferredReturn
,
19846 "Constant value invariant violated in {}.\n"
19847 " Value went from {} to {}",
19849 show(from_cell(cns
->val
)),
19850 show(fa
.inferredReturn
)
19856 find_deps(*m_data
, func
, Dep::ConstVal
, deps
);
19859 void Index::refine_return_info(const FuncAnalysisResult
& fa
,
19860 DependencyContextSet
& deps
) {
19861 auto const& func
= fa
.ctx
.func
;
19862 auto const finfo
= func_info(*m_data
, func
);
19864 auto const error_loc
= [&] {
19865 return folly::sformat(
19869 ? folly::to
<std::string
>(func
->cls
->name
->data(), "::")
19876 if (finfo
->retParam
== NoLocalId
&& fa
.retParam
!= NoLocalId
) {
19877 // This is just a heuristic; it doesn't mean that the value passed
19878 // in was returned, but that the value of the parameter at the
19879 // point of the RetC was returned. We use it to make (heuristic)
19880 // decisions about whether to do inline interps, so we only allow
19881 // it to change once (otherwise later passes might not do the
19882 // inline interp, and get worse results, which could trigger other
19883 // assertions in Index::refine_*).
19884 dep
= Dep::ReturnTy
;
19885 finfo
->retParam
= fa
.retParam
;
19888 auto unusedParams
= ~fa
.usedParams
;
19889 if (finfo
->unusedParams
!= unusedParams
) {
19890 dep
= Dep::ReturnTy
;
19891 always_assert_flog(
19892 (finfo
->unusedParams
| unusedParams
) == unusedParams
,
19893 "Index unusedParams decreased in {}.\n",
19896 finfo
->unusedParams
= unusedParams
;
19899 auto resetFuncFamilies
= false;
19900 if (fa
.inferredReturn
.strictlyMoreRefined(finfo
->returnTy
)) {
19901 if (finfo
->returnRefinements
< options
.returnTypeRefineLimit
) {
19902 finfo
->returnTy
= fa
.inferredReturn
;
19903 // We've modifed the return type, so reset any cached FuncFamily
19905 resetFuncFamilies
= true;
19906 dep
= is_scalar(fa
.inferredReturn
)
19907 ? Dep::ReturnTy
| Dep::InlineDepthLimit
: Dep::ReturnTy
;
19908 finfo
->returnRefinements
+= fa
.localReturnRefinements
+ 1;
19909 if (finfo
->returnRefinements
> options
.returnTypeRefineLimit
) {
19910 FTRACE(1, "maxed out return type refinements at {}\n", error_loc());
19913 FTRACE(1, "maxed out return type refinements at {}\n", error_loc());
19916 always_assert_flog(
19917 fa
.inferredReturn
.moreRefined(finfo
->returnTy
),
19918 "Index return type invariant violated in {}.\n"
19919 " {} is not at least as refined as {}\n",
19921 show(fa
.inferredReturn
),
19922 show(finfo
->returnTy
)
19926 always_assert_flog(
19927 !finfo
->effectFree
|| fa
.effectFree
,
19928 "Index effectFree changed from true to false in {} {}.\n",
19930 func_fullname(*func
)
19933 if (finfo
->effectFree
!= fa
.effectFree
) {
19934 finfo
->effectFree
= fa
.effectFree
;
19935 dep
= Dep::InlineDepthLimit
| Dep::ReturnTy
;
19938 if (dep
!= Dep
{}) {
19939 find_deps(*m_data
, func
, dep
, deps
);
19940 if (resetFuncFamilies
) {
19941 assertx(has_dep(dep
, Dep::ReturnTy
));
19942 for (auto const ff
: finfo
->families
) {
19943 // Reset the cached return type information for all the
19944 // FuncFamilies this function is a part of. Always reset the
19945 // "all" information, and if there's regular subset
19946 // information, reset that too.
19947 if (!ff
->m_all
.m_returnTy
.reset() &&
19948 (!ff
->m_regular
|| !ff
->m_regular
->m_returnTy
.reset())) {
19951 // Only load the deps for this func family if we're the ones
19952 // who successfully reset. Only one thread needs to do it.
19953 find_deps(*m_data
, ff
, Dep::ReturnTy
, deps
);
19959 bool Index::refine_closure_use_vars(const php::Class
* cls
,
19960 const CompactVector
<Type
>& vars
) {
19961 assertx(is_closure(*cls
));
19963 for (auto i
= uint32_t{0}; i
< vars
.size(); ++i
) {
19964 always_assert_flog(
19965 vars
[i
].equivalentlyRefined(unctx(vars
[i
])),
19966 "Closure cannot have a used var with a context dependent type"
19970 auto& current
= [&] () -> CompactVector
<Type
>& {
19971 std::lock_guard
<std::mutex
> _
{closure_use_vars_mutex
};
19972 return m_data
->closureUseVars
[cls
];
19975 always_assert(current
.empty() || current
.size() == vars
.size());
19976 if (current
.empty()) {
19981 auto changed
= false;
19982 for (auto i
= uint32_t{0}; i
< vars
.size(); ++i
) {
19983 if (vars
[i
].strictSubtypeOf(current
[i
])) {
19985 current
[i
] = vars
[i
];
19987 always_assert_flog(
19988 vars
[i
].moreRefined(current
[i
]),
19989 "Index closure_use_var invariant violated in {}.\n"
19990 " {} is not at least as refined as {}\n",
20001 template<class Container
>
20002 void refine_private_propstate(Container
& cont
,
20003 const php::Class
* cls
,
20004 const PropState
& state
) {
20005 assertx(!is_used_trait(*cls
));
20006 auto* elm
= [&] () -> typename
Container::value_type
* {
20007 std::lock_guard
<std::mutex
> _
{private_propstate_mutex
};
20008 auto it
= cont
.find(cls
);
20009 if (it
== end(cont
)) {
20010 if (!state
.empty()) cont
[cls
] = state
;
20018 for (auto& kv
: state
) {
20019 auto& target
= elm
->second
[kv
.first
];
20020 assertx(target
.tc
== kv
.second
.tc
);
20021 always_assert_flog(
20022 kv
.second
.ty
.moreRefined(target
.ty
),
20023 "PropState refinement failed on {}::${} -- {} was not a subtype of {}\n",
20026 show(kv
.second
.ty
),
20029 target
.ty
= kv
.second
.ty
;
20031 if (kv
.second
.everModified
) {
20032 always_assert_flog(
20033 target
.everModified
,
20034 "PropState refinement failed on {}::${} -- "
20035 "everModified flag went from false to true\n",
20040 target
.everModified
= false;
20045 void Index::refine_private_props(const php::Class
* cls
,
20046 const PropState
& state
) {
20047 refine_private_propstate(m_data
->privatePropInfo
, cls
, state
);
20050 void Index::refine_private_statics(const php::Class
* cls
,
20051 const PropState
& state
) {
20052 // We can't store context dependent types in private statics since they
20053 // could be accessed using different contexts.
20054 auto cleanedState
= PropState
{};
20055 for (auto const& prop
: state
) {
20056 auto& elem
= cleanedState
[prop
.first
];
20057 elem
.ty
= unctx(prop
.second
.ty
);
20058 elem
.tc
= prop
.second
.tc
;
20059 elem
.attrs
= prop
.second
.attrs
;
20060 elem
.everModified
= prop
.second
.everModified
;
20063 refine_private_propstate(m_data
->privateStaticPropInfo
, cls
, cleanedState
);
20066 void Index::record_public_static_mutations(const php::Func
& func
,
20067 PublicSPropMutations mutations
) {
20068 if (!mutations
.m_data
) {
20069 m_data
->publicSPropMutations
.erase(&func
);
20072 m_data
->publicSPropMutations
.insert_or_assign(&func
, std::move(mutations
));
20075 void Index::update_prop_initial_values(const Context
& ctx
,
20076 const ResolvedPropInits
& resolved
,
20077 DependencyContextSet
& deps
) {
20078 auto& props
= const_cast<php::Class
*>(ctx
.cls
)->properties
;
20080 auto changed
= false;
20081 for (auto const& [idx
, info
] : resolved
) {
20082 assertx(idx
< props
.size());
20083 auto& prop
= props
[idx
];
20085 auto const allResolved
= [&] {
20086 if (prop
.typeConstraint
.isUnresolved()) return false;
20087 for (auto const& ub
: prop
.ubs
.m_constraints
) {
20088 if (ub
.isUnresolved()) return false;
20093 if (info
.satisfies
) {
20094 if (!(prop
.attrs
& AttrInitialSatisfiesTC
) && allResolved()) {
20095 attribute_setter(prop
.attrs
, true, AttrInitialSatisfiesTC
);
20099 always_assert_flog(
20100 !(prop
.attrs
& AttrInitialSatisfiesTC
),
20101 "AttrInitialSatisfiesTC invariant violated for {}::{}\n"
20102 " Went from true to false",
20103 ctx
.cls
->name
, prop
.name
20107 always_assert_flog(
20108 IMPLIES(!(prop
.attrs
& AttrDeepInit
), !info
.deepInit
),
20109 "AttrDeepInit invariant violated for {}::{}\n"
20110 " Went from false to true",
20111 ctx
.cls
->name
, prop
.name
20113 attribute_setter(prop
.attrs
, info
.deepInit
, AttrDeepInit
);
20115 if (type(info
.val
) != KindOfUninit
) {
20116 always_assert_flog(
20117 type(prop
.val
) == KindOfUninit
||
20118 from_cell(prop
.val
) == from_cell(info
.val
),
20119 "Property initial value invariant violated for {}::{}\n"
20120 " Value went from {} to {}",
20121 ctx
.cls
->name
, prop
.name
,
20122 show(from_cell(prop
.val
)), show(from_cell(info
.val
))
20124 prop
.val
= info
.val
;
20126 always_assert_flog(
20127 type(prop
.val
) == KindOfUninit
,
20128 "Property initial value invariant violated for {}::{}\n"
20129 " Value went from {} to not set",
20130 ctx
.cls
->name
, prop
.name
,
20131 show(from_cell(prop
.val
))
20135 if (!changed
) return;
20137 auto const it
= m_data
->classInfo
.find(ctx
.cls
->name
);
20138 if (it
== end(m_data
->classInfo
)) return;
20139 auto const cinfo
= it
->second
;
20141 // Both a pinit and a sinit can have resolved property values. When
20142 // analyzing constants we'll process each function separately and
20143 // potentially in different threads. Both will want to inspect the
20144 // property Attrs and the hasBadInitialPropValues. So, if we reach
20145 // here, take a lock to ensure both don't stomp on each other.
20146 static std::array
<std::mutex
, 256> locks
;
20147 auto& lock
= locks
[pointer_hash
<const php::Class
>{}(ctx
.cls
) % locks
.size()];
20148 std::lock_guard
<std::mutex
> _
{lock
};
20150 auto const noBad
= std::all_of(
20151 begin(props
), end(props
),
20152 [] (const php::Prop
& prop
) {
20153 return bool(prop
.attrs
& AttrInitialSatisfiesTC
);
20157 if (cinfo
->hasBadInitialPropValues
) {
20159 cinfo
->hasBadInitialPropValues
= false;
20160 find_deps(*m_data
, ctx
.cls
, Dep::PropBadInitialValues
, deps
);
20163 // If it's false, another thread got here before us and set it to
20165 always_assert(noBad
);
20169 void Index::refine_public_statics(DependencyContextSet
& deps
) {
20170 trace_time
update("update public statics");
20172 // Union together the mutations for each function, including the functions
20173 // which weren't analyzed this round.
20174 auto nothing_known
= false;
20175 PublicSPropMutations::UnknownMap unknown
;
20176 PublicSPropMutations::KnownMap known
;
20177 for (auto const& mutations
: m_data
->publicSPropMutations
) {
20178 if (!mutations
.second
.m_data
) continue;
20179 if (mutations
.second
.m_data
->m_nothing_known
) {
20180 nothing_known
= true;
20184 for (auto const& kv
: mutations
.second
.m_data
->m_unknown
) {
20185 auto const ret
= unknown
.insert(kv
);
20186 if (!ret
.second
) ret
.first
->second
|= kv
.second
;
20188 for (auto const& kv
: mutations
.second
.m_data
->m_known
) {
20189 auto const ret
= known
.insert(kv
);
20190 if (!ret
.second
) ret
.first
->second
|= kv
.second
;
20194 if (nothing_known
) {
20195 // We cannot go from knowing the types to not knowing the types (this is
20196 // equivalent to widening the types).
20197 always_assert(!m_data
->seenPublicSPropMutations
);
20200 m_data
->seenPublicSPropMutations
= true;
20202 // Refine known class state
20203 parallel::for_each(
20204 m_data
->allClassInfos
,
20205 [&] (std::unique_ptr
<ClassInfo
>& cinfo
) {
20206 for (auto const& prop
: cinfo
->cls
->properties
) {
20207 if (!(prop
.attrs
& (AttrPublic
|AttrProtected
)) ||
20208 !(prop
.attrs
& AttrStatic
)) {
20212 auto knownClsType
= [&] {
20213 auto const it
= known
.find(
20214 PublicSPropMutations::KnownKey
{ cinfo
.get(), prop
.name
}
20216 // If we didn't see a mutation, the type is TBottom.
20217 return it
== end(known
) ? TBottom
: it
->second
;
20220 auto unknownClsType
= [&] {
20221 auto const it
= unknown
.find(prop
.name
);
20222 // If we didn't see a mutation, the type is TBottom.
20223 return it
== end(unknown
) ? TBottom
: it
->second
;
20226 // We can't keep context dependent types in public properties.
20227 auto newType
= adjust_type_for_prop(
20228 IndexAdaptor
{ *this },
20230 &prop
.typeConstraint
,
20231 unctx(union_of(std::move(knownClsType
), std::move(unknownClsType
)))
20234 auto& entry
= cinfo
->publicStaticProps
[prop
.name
];
20236 if (!newType
.is(BBottom
)) {
20237 always_assert_flog(
20238 entry
.everModified
,
20239 "Static property index invariant violated on {}::{}:\n"
20240 " everModified flag went from false to true",
20245 entry
.everModified
= false;
20248 // The type from the mutations doesn't contain the in-class
20249 // initializer types. Add that here.
20250 auto effectiveType
= union_of(
20251 std::move(newType
),
20252 initial_type_for_public_sprop(*this, *cinfo
->cls
, prop
)
20256 * We may only shrink the types we recorded for each property. (If a
20257 * property type ever grows, the interpreter could infer something
20258 * incorrect at some step.)
20260 always_assert_flog(
20261 effectiveType
.subtypeOf(entry
.inferredType
),
20262 "Static property index invariant violated on {}::{}:\n"
20263 " {} is not a subtype of {}",
20266 show(effectiveType
),
20267 show(entry
.inferredType
)
20270 // Put a limit on the refinements to ensure termination. Since
20271 // we only ever refine types, we can stop at any point and still
20272 // maintain correctness.
20273 if (effectiveType
.strictSubtypeOf(entry
.inferredType
)) {
20274 if (entry
.refinements
+ 1 < options
.publicSPropRefineLimit
) {
20275 find_deps(*m_data
, &prop
, Dep::PublicSProp
, deps
);
20276 entry
.inferredType
= std::move(effectiveType
);
20277 ++entry
.refinements
;
20280 1, "maxed out public static property refinements for {}:{}\n",
20291 bool Index::frozen() const {
20292 return m_data
->frozen
;
20295 void Index::freeze() {
20296 m_data
->frozen
= true;
20297 m_data
->ever_frozen
= true;
20300 Type
AnalysisIndex::unserialize_type(Type t
) const {
20301 return unserialize_classes(AnalysisIndexAdaptor
{ *this }, std::move(t
));
20305 * Note that these functions run in separate threads, and
20306 * intentionally don't bump Trace::hhbbc_time. If you want to see
20307 * these times, set TRACE=hhbbc_time:1
20311 trace_time _{"clearing " #x}; \
20312 _.ignore_client_stats(); \
20316 void Index::cleanup_for_final() {
20317 trace_time _
{"cleanup for final", m_data
->sample
};
20318 CLEAR(m_data
->dependencyMap
);
20321 void Index::cleanup_post_emit() {
20322 trace_time _
{"cleanup post emit", m_data
->sample
};
20324 std::vector
<std::function
<void()>> clearers
;
20325 #define CLEAR_PARALLEL(x) clearers.push_back([&] CLEAR(x));
20326 CLEAR_PARALLEL(m_data
->classes
);
20327 CLEAR_PARALLEL(m_data
->funcs
);
20328 CLEAR_PARALLEL(m_data
->typeAliases
);
20329 CLEAR_PARALLEL(m_data
->enums
);
20330 CLEAR_PARALLEL(m_data
->constants
);
20331 CLEAR_PARALLEL(m_data
->modules
);
20332 CLEAR_PARALLEL(m_data
->units
);
20334 CLEAR_PARALLEL(m_data
->classClosureMap
);
20335 CLEAR_PARALLEL(m_data
->classExtraMethodMap
);
20337 CLEAR_PARALLEL(m_data
->classInfo
);
20339 CLEAR_PARALLEL(m_data
->privatePropInfo
);
20340 CLEAR_PARALLEL(m_data
->privateStaticPropInfo
);
20341 CLEAR_PARALLEL(m_data
->publicSPropMutations
);
20342 CLEAR_PARALLEL(m_data
->ifaceSlotMap
);
20343 CLEAR_PARALLEL(m_data
->closureUseVars
);
20345 CLEAR_PARALLEL(m_data
->methodFamilies
);
20347 CLEAR_PARALLEL(m_data
->funcFamilies
);
20348 CLEAR_PARALLEL(m_data
->funcFamilyStaticInfos
);
20350 CLEAR_PARALLEL(m_data
->clsConstLookupCache
);
20352 CLEAR_PARALLEL(m_data
->foldableReturnTypeMap
);
20353 CLEAR_PARALLEL(m_data
->contextualReturnTypes
);
20355 parallel::for_each(clearers
, [] (const std::function
<void()>& f
) { f(); });
20358 trace_time t
{"reset funcInfo"};
20359 t
.ignore_client_stats();
20360 parallel::for_each(
20363 u
.returnTy
= TBottom
;
20364 u
.families
.clear();
20367 m_data
->funcInfo
.clear();
20370 // Class-infos and program need to be freed after all Type instances
20371 // are destroyed, as Type::checkInvariants may try to access them.
20374 trace_time t
{"reset allClassInfos"};
20375 t
.ignore_client_stats();
20376 parallel::for_each(m_data
->allClassInfos
, [] (auto& u
) { u
.reset(); });
20377 m_data
->allClassInfos
.clear();
20381 trace_time t
{"reset program"};
20382 t
.ignore_client_stats();
20383 parallel::for_each(m_data
->program
->units
, [] (auto& u
) { u
.reset(); });
20384 parallel::for_each(m_data
->program
->classes
, [] (auto& u
) { u
.reset(); });
20385 parallel::for_each(m_data
->program
->funcs
, [] (auto& f
) { f
.reset(); });
20386 m_data
->program
.reset();
20390 void Index::thaw() {
20391 m_data
->frozen
= false;
20394 //////////////////////////////////////////////////////////////////////
20396 FuncClsUnit
AnalysisWorklist::next() {
20397 if (list
.empty()) return FuncClsUnit
{};
20398 auto n
= list
.front();
20404 void AnalysisWorklist::schedule(FuncClsUnit fc
) {
20405 assertx(IMPLIES(fc
.cls(), !is_closure(*fc
.cls())));
20406 if (!in
.emplace(fc
).second
) return;
20407 ITRACE(2, "scheduling {} onto worklist\n", show(fc
));
20408 list
.emplace_back(fc
);
20411 //////////////////////////////////////////////////////////////////////
20413 bool AnalysisDeps::add(Class c
, bool inTypeCns
) {
20415 ? typeCnsClasses
.emplace(c
.name
).second
20416 : classes
.emplace(c
.name
).second
;
20419 bool AnalysisDeps::add(ConstIndex cns
, bool inTypeCns
) {
20420 // Dependency on class constant implies a dependency on the class as
20422 add(Class
{ cns
.cls
}, inTypeCns
);
20424 ? typeCnsClsConstants
.emplace(cns
).second
20425 : clsConstants
.emplace(cns
).second
;
20428 bool AnalysisDeps::add(Constant cns
) {
20429 // Dependency on top-level constant implies a dependency on the
20430 // 86cinit initialized as well (which may not even exist).
20431 add(Func
{ HPHP::Constant::funcNameFromName(cns
.name
) }, Type::Meta
);
20432 return constants
.emplace(cns
.name
).second
;
20435 bool AnalysisDeps::add(AnyClassConstant any
, bool inTypeCns
) {
20436 // Dependency on class constant implies a dependency on the class as
20438 add(Class
{ any
.name
}, inTypeCns
);
20440 ? typeCnsAnyClsConstants
.emplace(any
.name
).second
20441 : anyClsConstants
.emplace(any
.name
).second
;
20444 AnalysisDeps::Type
AnalysisDeps::add(const php::Func
& f
, Type t
) {
20446 ? add(MethRef
{ f
}, t
)
20447 : add(Func
{ f
.name
}, t
);
20450 AnalysisDeps::Type
AnalysisDeps::add(MethRef m
, Type t
) {
20451 add(Class
{ m
.cls
});
20452 return merge(methods
[m
], t
| Type::Meta
);
20455 AnalysisDeps::Type
AnalysisDeps::add(Func f
, Type t
) {
20456 return merge(funcs
[f
.name
], t
| Type::Meta
);
20459 AnalysisDeps::Type
AnalysisDeps::merge(Type
& o
, Type n
) {
20460 auto const added
= n
- o
;
20465 AnalysisDeps
& AnalysisDeps::operator|=(const AnalysisDeps
& o
) {
20466 clsConstants
.insert(begin(o
.clsConstants
), end(o
.clsConstants
));
20467 classes
.insert(begin(o
.classes
), end(o
.classes
));
20468 constants
.insert(begin(o
.constants
), end(o
.constants
));
20469 anyClsConstants
.insert(begin(o
.anyClsConstants
), end(o
.anyClsConstants
));
20470 typeCnsClasses
.insert(begin(o
.typeCnsClasses
), end(o
.typeCnsClasses
));
20471 typeCnsClsConstants
.insert(
20472 begin(o
.typeCnsClsConstants
),
20473 end(o
.typeCnsClsConstants
)
20475 typeCnsAnyClsConstants
.insert(
20476 begin(o
.typeCnsAnyClsConstants
),
20477 end(o
.typeCnsAnyClsConstants
)
20479 for (auto const [name
, t
] : o
.funcs
) funcs
[name
] |= t
;
20480 for (auto const [meth
, t
] : o
.methods
) methods
[meth
] |= t
;
20484 std::string
show(AnalysisDeps::Type t
) {
20485 using T
= AnalysisDeps::Type
;
20487 auto const add
= [&] (const char* s
) {
20488 folly::format(&out
, "{}{}", out
.empty() ? "" : ",", s
);
20490 if (t
& T::Meta
) add("meta");
20491 if (t
& T::RetType
) add("return type");
20492 if (t
& T::ScalarRetType
) add("scalar return type");
20493 if (t
& T::RetParam
) add("returned param");
20494 if (t
& T::UnusedParams
) add("unused params");
20495 if (t
& T::Bytecode
) add("bytecode");
20499 std::string
show(const AnalysisDeps
& d
) {
20500 using namespace folly::gen
;
20501 auto const toCpp
= [] (SString n
) { return n
->toCppString(); };
20504 if (!d
.classes
.empty()) {
20506 &out
, " classes: {}\n",
20507 from(d
.classes
) | map(toCpp
) | unsplit
<std::string
>(", ")
20510 if (!d
.funcs
.empty()) {
20512 &out
, " funcs: {}\n",
20514 | map([&] (auto const& p
) {
20515 return folly::sformat("{} -> [{}]", toCpp(p
.first
), show(p
.second
));
20517 | unsplit
<std::string
>(", ")
20520 if (!d
.methods
.empty()) {
20522 &out
, " methods: {}\n",
20524 | map([] (auto const& p
) {
20525 return folly::sformat("{} -> [{}]", show(p
.first
), show(p
.second
));
20527 | unsplit
<std::string
>(", ")
20530 if (!d
.clsConstants
.empty()) {
20532 &out
, " class-constants: {}\n",
20533 from(d
.clsConstants
)
20534 | map([] (ConstIndex idx
) { return show(idx
); })
20535 | unsplit
<std::string
>(", ")
20538 if (!d
.anyClsConstants
.empty()) {
20540 &out
, " any class-constants: {}\n",
20541 from(d
.anyClsConstants
) | map(toCpp
) | unsplit
<std::string
>(", ")
20544 if (!d
.typeCnsClasses
.empty()) {
20546 &out
, " type-cns classes: {}\n",
20547 from(d
.typeCnsClasses
) | map(toCpp
) | unsplit
<std::string
>(", ")
20550 if (!d
.typeCnsClsConstants
.empty()) {
20552 &out
, " type-cns class-constants: {}\n",
20553 from(d
.typeCnsClsConstants
)
20554 | map([] (ConstIndex idx
) { return show(idx
); })
20555 | unsplit
<std::string
>(", ")
20558 if (!d
.typeCnsAnyClsConstants
.empty()) {
20560 &out
, " type-cns any class-constants: {}\n",
20561 from(d
.typeCnsAnyClsConstants
) | map(toCpp
) | unsplit
<std::string
>(", ")
20565 if (out
.empty()) out
= " (none)\n";
20569 //////////////////////////////////////////////////////////////////////
20571 void AnalysisChangeSet::changed(ConstIndex idx
) {
20572 clsConstants
.emplace(idx
);
20575 void AnalysisChangeSet::changed(const php::Constant
& c
) {
20576 constants
.emplace(c
.name
);
20579 void AnalysisChangeSet::changed(const php::Func
& f
, Type t
) {
20580 assertx(AnalysisDeps::isValidForChanges(t
));
20582 methods
[MethRef
{ f
}] |= t
;
20584 funcs
[f
.name
] |= t
;
20588 void AnalysisChangeSet::fixed(ConstIndex idx
) {
20589 fixedClsConstants
.emplace(idx
);
20592 void AnalysisChangeSet::fixed(const php::Class
& cls
) {
20593 allClsConstantsFixed
.emplace(cls
.name
);
20596 void AnalysisChangeSet::fixed(const php::Unit
& unit
) {
20597 unitsFixed
.emplace(unit
.filename
);
20600 void AnalysisChangeSet::typeCnsName(const php::Class
& cls
,
20602 if (cls
.name
->tsame(name
.name
)) return;
20603 clsTypeCnsNames
[cls
.name
].emplace(name
.name
);
20606 void AnalysisChangeSet::typeCnsName(const php::Unit
& unit
,
20608 unitTypeCnsNames
[unit
.filename
].emplace(name
.name
);
20611 void AnalysisChangeSet::filter(const TSStringSet
& keepClasses
,
20612 const FSStringSet
& keepFuncs
,
20613 const SStringSet
& keepUnits
,
20614 const SStringSet
& keepConstants
) {
20616 funcs
, [&] (auto const& p
) { return !keepFuncs
.count(p
.first
); }
20619 methods
, [&] (auto const& p
) { return !keepClasses
.count(p
.first
.cls
); }
20622 constants
, [&] (SString s
) { return !keepConstants
.count(s
); }
20625 clsConstants
, [&] (ConstIndex idx
) { return !keepClasses
.count(idx
.cls
); }
20629 [&] (ConstIndex idx
) {
20630 return !keepClasses
.count(idx
.cls
) || allClsConstantsFixed
.count(idx
.cls
);
20634 allClsConstantsFixed
, [&] (SString s
) { return !keepClasses
.count(s
); }
20637 unitsFixed
, [&] (SString s
) { return !keepUnits
.count(s
); }
20640 clsTypeCnsNames
, [&] (auto const& p
) { return !keepClasses
.count(p
.first
); }
20643 unitTypeCnsNames
, [&] (auto const& p
) { return !keepUnits
.count(p
.first
); }
20647 //////////////////////////////////////////////////////////////////////
20651 template <typename V
, typename H
, typename E
, typename C
>
20652 std::vector
<SString
>
20653 map_to_sorted_key_vec(const hphp_fast_map
<SString
, V
, H
, E
>& m
,
20655 std::vector
<SString
> keys
;
20656 keys
.reserve(m
.size());
20657 for (auto const& [k
, _
] : m
) keys
.emplace_back(k
);
20658 std::sort(begin(keys
), end(keys
), c
);
20662 template <typename V
, typename H
, typename E
, typename C
>
20663 std::vector
<V
> map_to_sorted_vec(const hphp_fast_map
<SString
, V
, H
, E
>& m
,
20665 auto const keys
= map_to_sorted_key_vec(m
, c
);
20666 std::vector
<V
> out
;
20667 out
.reserve(keys
.size());
20668 for (auto const k
: keys
) out
.emplace_back(m
.at(k
));
20674 std::vector
<SString
> AnalysisInput::classNames() const {
20675 return map_to_sorted_key_vec(classes
, string_data_lt_type
{});
20678 std::vector
<SString
> AnalysisInput::funcNames() const {
20679 return map_to_sorted_key_vec(funcs
, string_data_lt_func
{});
20682 std::vector
<SString
> AnalysisInput::unitNames() const {
20683 return map_to_sorted_key_vec(units
, string_data_lt
{});
20686 std::vector
<SString
> AnalysisInput::cinfoNames() const {
20687 using namespace folly::gen
;
20688 return from(classNames())
20689 | filter([&] (SString n
) { return (bool)cinfos
.count(n
); })
20690 | as
<std::vector
>();
20693 std::vector
<SString
> AnalysisInput::minfoNames() const {
20694 using namespace folly::gen
;
20695 return from(classNames())
20696 | filter([&] (SString n
) { return (bool)minfos
.count(n
); })
20697 | as
<std::vector
>();
20700 AnalysisInput::Tuple
AnalysisInput::toTuple(Ref
<Meta
> meta
) const {
20702 map_to_sorted_vec(classes
, string_data_lt_type
{}),
20703 map_to_sorted_vec(funcs
, string_data_lt_func
{}),
20704 map_to_sorted_vec(units
, string_data_lt
{}),
20705 map_to_sorted_vec(classBC
, string_data_lt_type
{}),
20706 map_to_sorted_vec(funcBC
, string_data_lt_func
{}),
20707 map_to_sorted_vec(cinfos
, string_data_lt_type
{}),
20708 map_to_sorted_vec(finfos
, string_data_lt_func
{}),
20709 map_to_sorted_vec(minfos
, string_data_lt_type
{}),
20710 map_to_sorted_vec(depClasses
, string_data_lt_type
{}),
20711 map_to_sorted_vec(depFuncs
, string_data_lt_func
{}),
20712 map_to_sorted_vec(depUnits
, string_data_lt
{}),
20717 //////////////////////////////////////////////////////////////////////
20721 // Many BucketSets are identical, so we intern them in the below
20722 // table, which saves a lot of memory.
20724 struct BucketSetHasher
{
20725 size_t operator()(const AnalysisInput::BucketSet
& b
) const {
20730 folly_concurrent_hash_map_simd
<
20731 AnalysisInput::BucketSet
,
20734 > s_bucketSetIntern
;
20736 AnalysisInput::BucketSet s_emptyBucketSet
;
20738 // Likewise, when we serde them, we can refer to them by indices
20739 // rather than encoding the same set multiple times.
20740 struct BucketSetSerdeTable
{
20741 using A
= AnalysisInput
;
20743 hphp_fast_map
<const A::BucketSet
*, size_t> bToIdx
;
20744 std::vector
<const A::BucketSet
*> idxToB
;
20746 void encode(BlobEncoder
& sd
, const A::BucketSet
& b
) {
20747 if (auto const idx
= folly::get_ptr(bToIdx
, &b
)) {
20750 idxToB
.emplace_back(&b
);
20751 bToIdx
.try_emplace(&b
, idxToB
.size());
20755 const AnalysisInput::BucketSet
* decode(BlobDecoder
& sd
) {
20756 auto const idx
= sd
.make
<size_t>();
20758 auto const b
= A::BucketSet::intern(sd
.make
<A::BucketSet
>());
20759 idxToB
.emplace_back(b
);
20762 assertx(idx
<= idxToB
.size());
20763 return idxToB
[idx
-1];
20767 thread_local Optional
<BucketSetSerdeTable
> tl_bucketSetTable
;
20771 bool AnalysisInput::BucketSet::isSubset(const BucketSet
& o
) const {
20774 std::includes(o
.buckets
.begin(), o
.buckets
.end(),
20775 buckets
.begin(), buckets
.end());
20778 bool AnalysisInput::BucketSet::contains(size_t idx
) const {
20779 assertx(idx
< std::numeric_limits
<uint32_t>::max());
20780 return buckets
.contains(idx
);
20783 size_t AnalysisInput::BucketSet::hash() const {
20784 return folly::hash::hash_range(buckets
.begin(), buckets
.end());
20787 bool AnalysisInput::BucketSet::empty() const {
20788 return buckets
.empty();
20791 void AnalysisInput::BucketSet::add(size_t idx
) {
20792 assertx(idx
< std::numeric_limits
<uint32_t>::max());
20793 buckets
.emplace_hint(buckets
.end(), idx
);
20796 void AnalysisInput::BucketSet::clear() {
20800 AnalysisInput::BucketSet
&
20801 AnalysisInput::BucketSet::operator|=(const BucketSet
& o
) {
20802 buckets
.insert(folly::sorted_unique
, begin(o
.buckets
), end(o
.buckets
));
20806 const AnalysisInput::BucketSet
*
20807 AnalysisInput::BucketSet::intern(BucketSet b
) {
20808 if (b
.buckets
.empty()) return &s_emptyBucketSet
;
20809 b
.buckets
.shrink_to_fit();
20810 return &s_bucketSetIntern
.try_emplace(std::move(b
)).first
->first
;
20813 void AnalysisInput::BucketSet::clearIntern() {
20814 s_bucketSetIntern
= decltype(s_bucketSetIntern
){};
20817 std::string
AnalysisInput::BucketSet::toString() const {
20818 using namespace folly::gen
;
20819 if (buckets
.empty()) return "-";
20820 return from(buckets
)
20821 | map([] (uint32_t i
) { return std::to_string(i
); })
20822 | unsplit
<std::string
>(",");
20825 void AnalysisInput::BucketPresence::serdeStart() {
20826 assertx(!tl_bucketSetTable
);
20827 tl_bucketSetTable
.emplace();
20830 void AnalysisInput::BucketPresence::serdeEnd() {
20831 assertx(tl_bucketSetTable
);
20832 tl_bucketSetTable
.reset();
20835 void AnalysisInput::BucketPresence::serde(BlobEncoder
& sd
) {
20836 assertx(tl_bucketSetTable
);
20840 tl_bucketSetTable
->encode(sd
, *present
);
20841 tl_bucketSetTable
->encode(sd
, *withBC
);
20842 tl_bucketSetTable
->encode(sd
, *process
);
20845 void AnalysisInput::BucketPresence::serde(BlobDecoder
& sd
) {
20846 assertx(tl_bucketSetTable
);
20847 present
= tl_bucketSetTable
->decode(sd
);
20848 withBC
= tl_bucketSetTable
->decode(sd
);
20849 process
= tl_bucketSetTable
->decode(sd
);
20855 std::string
show(const AnalysisInput::BucketPresence
& b
) {
20856 return folly::sformat(
20857 "present: {} BC: {} process: {}",
20858 b
.present
->toString(),
20859 b
.withBC
->toString(),
20860 b
.process
->toString()
20864 //////////////////////////////////////////////////////////////////////
20866 struct AnalysisScheduler::Bucket
{
20867 HierarchicalWorkBucket b
;
20870 //////////////////////////////////////////////////////////////////////
20872 AnalysisScheduler::AnalysisScheduler(Index
& index
)
20874 , totalWorkItems
{0}
20875 , lock
{std::make_unique
<std::mutex
>()} {}
20877 AnalysisScheduler::~AnalysisScheduler() {
20878 // Free the BucketSet intern table when the scheduler finishes, as
20879 // it can take a non-trivial amount of memory.
20880 AnalysisInput::BucketSet::clearIntern();
20883 void AnalysisScheduler::registerClass(SString name
) {
20884 // Closures are only scheduled as part of the class or func they're
20886 if (is_closure_name(name
)) return;
20887 FTRACE(5, "AnalysisScheduler: registering class {}\n", name
);
20889 auto const [cState
, emplaced1
] = classState
.try_emplace(name
, name
);
20890 if (!emplaced1
) return;
20894 auto const [tState
, emplaced2
] = traceState
.try_emplace(name
);
20895 if (emplaced2
) traceNames
.emplace_back(name
);
20896 tState
->second
.depStates
.emplace_back(&cState
->second
.depState
);
20898 classNames
.emplace_back(name
);
20899 auto const& closures
=
20900 folly::get_default(index
.m_data
->classToClosures
, name
);
20901 for (auto const clo
: closures
) {
20903 5, "AnalysisScheduler: registering closure {} associated with class {}\n",
20906 always_assert(classState
.try_emplace(clo
, clo
).second
);
20910 void AnalysisScheduler::registerFunc(SString name
) {
20911 FTRACE(5, "AnalysisScheduler: registering func {}\n", name
);
20913 auto const [fState
, emplaced1
] = funcState
.try_emplace(name
, name
);
20914 if (!emplaced1
) return;
20918 funcNames
.emplace_back(name
);
20920 auto const& closures
= folly::get_default(index
.m_data
->funcToClosures
, name
);
20921 for (auto const clo
: closures
) {
20923 5, "AnalysisScheduler: registering closure {} associated with func {}\n",
20926 always_assert(classState
.try_emplace(clo
, clo
).second
);
20929 // If this func is a 86cinit, then register the associated constant
20931 if (auto const cns
= Constant::nameFromFuncName(name
)) {
20932 FTRACE(5, "AnalysisScheduler: registering constant {}\n", cns
);
20933 always_assert(cnsChanged
.try_emplace(cns
).second
);
20934 auto const unit
= index
.m_data
->funcToUnit
.at(name
);
20935 // Modifying a global constant func implies the unit will be
20936 // changed too, so the unit must be registered as well.
20937 registerUnit(unit
);
20938 traceState
.at(unit
).depStates
.emplace_back(&fState
->second
.depState
);
20940 auto const [tState
, emplaced2
] = traceState
.try_emplace(name
);
20941 if (emplaced2
) traceNames
.emplace_back(name
);
20942 tState
->second
.depStates
.emplace_back(&fState
->second
.depState
);
20946 void AnalysisScheduler::registerUnit(SString name
) {
20947 FTRACE(5, "AnalysisScheduler: registering unit {}\n", name
);
20949 auto const [uState
, emplaced1
] = unitState
.try_emplace(name
, name
);
20950 if (!emplaced1
) return;
20954 auto const [tState
, emplaced2
] = traceState
.try_emplace(name
);
20955 if (emplaced2
) traceNames
.emplace_back(name
);
20956 tState
->second
.depStates
.emplace_back(&uState
->second
.depState
);
20958 unitNames
.emplace_back(name
);
20961 // Called when an analysis job reports back its changes. This makes
20962 // any dependencies affected by the change eligible to run in the next
20964 void AnalysisScheduler::recordChanges(const AnalysisOutput
& output
) {
20965 const TSStringSet classes
{begin(output
.classNames
), end(output
.classNames
)};
20966 const FSStringSet funcs
{begin(output
.funcNames
), end(output
.funcNames
)};
20967 const SStringSet units
{begin(output
.unitNames
), end(output
.unitNames
)};
20969 auto const& changed
= output
.meta
.changed
;
20971 // Sanity check that this bucket should actually be modifying the
20973 auto const valid
= [&] (SString name
, DepState::Kind kind
) {
20975 case DepState::Func
:
20976 return funcs
.count(name
) || output
.meta
.removedFuncs
.count(name
);
20977 case DepState::Class
: {
20978 if (!is_closure_name(name
)) return (bool)classes
.count(name
);
20979 auto const ctx
= folly::get_default(index
.m_data
->closureToClass
, name
);
20980 if (ctx
) return (bool)classes
.count(ctx
);
20981 auto const f
= folly::get_default(index
.m_data
->closureToFunc
, name
);
20983 return (bool)funcs
.count(f
);
20985 case DepState::Unit
:
20986 return (bool)units
.count(name
);
20990 for (auto const [name
, type
] : changed
.funcs
) {
20991 FTRACE(4, "AnalysisScheduler: func {} changed ({})\n", name
, show(type
));
20992 auto state
= folly::get_ptr(funcState
, name
);
20993 always_assert_flog(
20995 "Trying to mark un-tracked func {} changed",
20998 always_assert_flog(
20999 valid(name
, DepState::Func
),
21000 "Trying to mark func {} as changed from wrong shard",
21003 assertx(AnalysisDeps::isValidForChanges(type
));
21004 assertx(state
->changed
== Type::None
);
21005 state
->changed
= type
;
21008 for (auto const [meth
, type
] : changed
.methods
) {
21009 FTRACE(4, "AnalysisScheduler: method {} changed ({})\n",
21010 show(meth
), show(type
));
21011 auto state
= folly::get_ptr(classState
, meth
.cls
);
21012 always_assert_flog(
21014 "Trying to mark method for un-tracked class {} changed",
21017 always_assert_flog(
21018 valid(meth
.cls
, DepState::Class
),
21019 "Trying to mark method for class {} as changed from wrong shard",
21022 assertx(AnalysisDeps::isValidForChanges(type
));
21023 auto& t
= state
->methodChanges
.ensure(meth
.idx
);
21024 assertx(t
== Type::None
);
21028 for (auto const cns
: changed
.clsConstants
) {
21029 auto state
= folly::get_ptr(classState
, cns
.cls
);
21030 always_assert_flog(
21032 "Trying to mark constant for un-tracked class {} changed",
21035 always_assert_flog(
21036 valid(cns
.cls
, DepState::Class
),
21037 "Trying to mark constant for class {} as changed from wrong shard",
21041 if (state
->allCnsFixed
) continue;
21042 if (cns
.idx
< state
->cnsFixed
.size() && state
->cnsFixed
[cns
.idx
]) continue;
21043 FTRACE(4, "AnalysisScheduler: class constant {} changed\n", show(cns
));
21044 if (cns
.idx
>= state
->cnsChanges
.size()) {
21045 state
->cnsChanges
.resize(cns
.idx
+1);
21047 assertx(!state
->cnsChanges
[cns
.idx
]);
21048 state
->cnsChanges
[cns
.idx
] = true;
21051 for (auto const cns
: changed
.fixedClsConstants
) {
21052 auto state
= folly::get_ptr(classState
, cns
.cls
);
21053 always_assert_flog(
21055 "Trying to mark constant for un-tracked class {} as fixed",
21058 always_assert_flog(
21059 valid(cns
.cls
, DepState::Class
),
21060 "Trying to mark constant for class {} as fixed from wrong shard",
21064 if (state
->allCnsFixed
) continue;
21065 if (cns
.idx
>= state
->cnsFixed
.size()) {
21066 state
->cnsFixed
.resize(cns
.idx
+1);
21068 if (!state
->cnsFixed
[cns
.idx
]) {
21069 FTRACE(4, "AnalysisScheduler: class constant {} now fixed\n", show(cns
));
21070 state
->cnsFixed
[cns
.idx
] = true;
21074 for (auto const cls
: changed
.allClsConstantsFixed
) {
21075 auto state
= folly::get_ptr(classState
, cls
);
21076 always_assert_flog(
21078 "Trying to mark all constants for un-tracked class {} as fixed",
21081 always_assert_flog(
21082 valid(cls
, DepState::Class
),
21083 "Trying to mark all constants for class {} as fixed from wrong shard",
21086 if (!state
->allCnsFixed
) {
21088 4, "AnalysisScheduler: all class constants for {} now fixed\n",
21091 state
->allCnsFixed
= true;
21092 state
->cnsFixed
.clear();
21096 for (auto const name
: changed
.constants
) {
21097 FTRACE(4, "AnalysisScheduler: constant {} changed\n", name
);
21098 auto state
= folly::get_ptr(cnsChanged
, name
);
21099 always_assert_flog(
21101 "Trying to mark un-tracked constant {} changed",
21104 auto const initName
= Constant::funcNameFromName(name
);
21105 always_assert_flog(
21106 valid(initName
, DepState::Func
),
21107 "Trying to mark constant {} as changed from wrong shard",
21110 assertx(!state
->load(std::memory_order_acquire
));
21111 state
->store(true, std::memory_order_release
);
21114 for (auto const unit
: changed
.unitsFixed
) {
21115 auto state
= folly::get_ptr(unitState
, unit
);
21116 always_assert_flog(
21118 "Trying to mark all type-aliases for un-tracked unit {} as fixed",
21121 always_assert_flog(
21122 valid(unit
, DepState::Unit
),
21123 "Trying to mark all type-aliases for unit {} as fixed from wrong shard",
21126 if (!state
->fixed
) {
21128 4, "AnalysisScheduler: all type-aliases for unit {} now fixed\n",
21131 state
->fixed
= true;
21135 for (auto& [cls
, names
] : changed
.clsTypeCnsNames
) {
21136 auto state
= folly::get_ptr(classState
, cls
);
21137 always_assert_flog(
21139 "Trying to record type constant names "
21140 "for un-tracked class {}",
21143 always_assert_flog(
21144 valid(cls
, DepState::Class
),
21145 "Trying to record type constant names "
21146 "for class {} from wrong shard",
21149 state
->typeCnsNames
= std::move(names
);
21152 for (auto& [unit
, names
] : changed
.unitTypeCnsNames
) {
21153 auto state
= folly::get_ptr(unitState
, unit
);
21154 always_assert_flog(
21156 "Trying to record type constant names "
21157 "for un-tracked unit {}",
21160 always_assert_flog(
21161 valid(unit
, DepState::Unit
),
21162 "Trying to record type constant names "
21163 "for unit {} from wrong shard",
21166 state
->typeCnsNames
= std::move(names
);
21170 // Update the dependencies stored in the scheduler to take into
21171 // account the new set of dependencies reported by an analysis job.
21172 void AnalysisScheduler::updateDepState(AnalysisOutput
& output
) {
21173 for (size_t i
= 0, size
= output
.classNames
.size(); i
< size
; ++i
) {
21174 auto const name
= output
.classNames
[i
];
21175 auto it
= classState
.find(name
);
21176 always_assert_flog(
21177 it
!= end(classState
),
21178 "Trying to set deps for un-tracked class {}",
21181 auto& state
= it
->second
.depState
;
21182 if (is_closure_name(name
)) {
21183 assertx(output
.meta
.classDeps
[i
].empty());
21184 assertx(state
.deps
.empty());
21187 state
.deps
= std::move(output
.meta
.classDeps
[i
]);
21189 for (size_t i
= 0, size
= output
.funcNames
.size(); i
< size
; ++i
) {
21190 auto const name
= output
.funcNames
[i
];
21191 auto it
= funcState
.find(name
);
21192 always_assert_flog(
21193 it
!= end(funcState
),
21194 "Trying to set deps for un-tracked func {}",
21197 auto& state
= it
->second
.depState
;
21198 state
.deps
= std::move(output
.meta
.funcDeps
[i
]);
21200 for (size_t i
= 0, size
= output
.unitNames
.size(); i
< size
; ++i
) {
21201 auto const name
= output
.unitNames
[i
];
21202 auto it
= unitState
.find(name
);
21203 if (it
== end(unitState
)) {
21204 always_assert_flog(
21205 output
.meta
.unitDeps
[i
].empty(),
21206 "Trying to set non-empty deps for un-tracked unit {}",
21211 auto& state
= it
->second
.depState
;
21212 state
.deps
= std::move(output
.meta
.unitDeps
[i
]);
21215 // Remove deps for any removed functions, to avoid them spuriously
21216 // being rescheduled again.
21217 for (auto const name
: output
.meta
.removedFuncs
) {
21218 auto it
= funcState
.find(name
);
21219 always_assert_flog(
21220 it
!= end(funcState
),
21221 "Trying to reset deps for un-tracked func {}",
21224 auto& state
= it
->second
.depState
;
21225 state
.deps
= AnalysisDeps
{};
21228 for (auto& [cls
, bases
] : output
.meta
.cnsBases
) {
21229 auto const state
= folly::get_ptr(classState
, cls
);
21230 always_assert_flog(
21232 "Trying to update cns bases for untracked class {}",
21235 auto old
= folly::get_ptr(index
.m_data
->classToCnsBases
, cls
);
21237 assertx(bases
.empty());
21241 // Class constant base classes should only shrink.
21242 for (auto const b
: bases
) always_assert(old
->contains(b
));
21244 *old
= std::move(bases
);
21248 // Record the output of an analysis job. This means updating the
21249 // various Refs to their new versions, recording new dependencies, and
21250 // recording what has changed (to schedule the next round).
21251 void AnalysisScheduler::record(AnalysisOutput output
) {
21252 auto const numClasses
= output
.classNames
.size();
21253 auto const numCInfos
= output
.cinfoNames
.size();
21254 auto const numMInfos
= output
.minfoNames
.size();
21255 auto const numFuncs
= output
.funcNames
.size();
21256 auto const numUnits
= output
.unitNames
.size();
21257 assertx(numClasses
== output
.classes
.size());
21258 assertx(numClasses
== output
.clsBC
.size());
21259 assertx(numCInfos
== output
.cinfos
.size());
21260 assertx(numMInfos
== output
.minfos
.size());
21261 assertx(numClasses
== output
.meta
.classDeps
.size());
21262 assertx(numFuncs
== output
.funcs
.size());
21263 assertx(numFuncs
== output
.funcBC
.size());
21264 assertx(numFuncs
== output
.finfos
.size());
21265 assertx(numFuncs
== output
.meta
.funcDeps
.size());
21266 assertx(numUnits
== output
.units
.size());
21267 assertx(numUnits
== output
.meta
.unitDeps
.size());
21269 // Update Ref mappings:
21271 for (size_t i
= 0; i
< numUnits
; ++i
) {
21272 auto const name
= output
.unitNames
[i
];
21273 index
.m_data
->unitRefs
.at(name
) = std::move(output
.units
[i
]);
21276 for (size_t i
= 0; i
< numClasses
; ++i
) {
21277 auto const name
= output
.classNames
[i
];
21278 index
.m_data
->classRefs
.at(name
) = std::move(output
.classes
[i
]);
21279 index
.m_data
->classBytecodeRefs
.at(name
) = std::move(output
.clsBC
[i
]);
21281 for (size_t i
= 0; i
< numCInfos
; ++i
) {
21282 auto const name
= output
.cinfoNames
[i
];
21283 index
.m_data
->classInfoRefs
.at(name
) =
21284 output
.cinfos
[i
].cast
<std::unique_ptr
<ClassInfo2
>>();
21286 for (size_t i
= 0; i
< numMInfos
; ++i
) {
21287 auto const name
= output
.minfoNames
[i
];
21288 index
.m_data
->uninstantiableClsMethRefs
.at(name
) =
21289 output
.minfos
[i
].cast
<std::unique_ptr
<MethodsWithoutCInfo
>>();
21291 for (size_t i
= 0; i
< numFuncs
; ++i
) {
21292 auto const name
= output
.funcNames
[i
];
21293 index
.m_data
->funcRefs
.at(name
) = std::move(output
.funcs
[i
]);
21294 index
.m_data
->funcBytecodeRefs
.at(name
) = std::move(output
.funcBC
[i
]);
21295 index
.m_data
->funcInfoRefs
.at(name
) =
21296 output
.finfos
[i
].cast
<std::unique_ptr
<FuncInfo2
>>();
21299 recordChanges(output
);
21300 updateDepState(output
);
21302 // If the analysis job optimized away any 86cinit functions, record
21303 // that here so they can be later removed from our tables.
21304 if (!output
.meta
.removedFuncs
.empty()) {
21305 // This is relatively rare, so a lock is fine.
21306 std::lock_guard
<std::mutex
> _
{*lock
};
21307 funcsToRemove
.insert(
21308 begin(output
.meta
.removedFuncs
),
21309 end(output
.meta
.removedFuncs
)
21314 // Remove metadata for any 86cinit function that an analysis job
21315 // optimized away. This must be done *after* calculating the next
21317 void AnalysisScheduler::removeFuncs() {
21318 if (funcsToRemove
.empty()) return;
21320 TSStringSet traceNamesToRemove
;
21321 for (auto const name
: funcsToRemove
) {
21322 FTRACE(4, "AnalysisScheduler: removing function {}\n", name
);
21324 auto fstate
= folly::get_ptr(funcState
, name
);
21325 always_assert(fstate
);
21327 auto tstate
= [&] {
21328 if (!Constant::nameFromFuncName(name
)) {
21329 return folly::get_ptr(traceState
, name
);
21331 auto const unit
= index
.m_data
->funcToUnit
.at(name
);
21332 return folly::get_ptr(traceState
, unit
);
21334 always_assert(tstate
);
21336 tstate
->depStates
.eraseTail(
21338 tstate
->depStates
.begin(), tstate
->depStates
.end(),
21339 [&] (const DepState
* d
) { return d
== &fstate
->depState
; }
21342 if (tstate
->depStates
.empty()) {
21343 traceState
.erase(name
);
21344 traceNamesToRemove
.emplace(name
);
21347 always_assert(index
.m_data
->funcRefs
.erase(name
));
21348 always_assert(index
.m_data
->funcBytecodeRefs
.erase(name
));
21349 always_assert(index
.m_data
->funcInfoRefs
.erase(name
));
21350 always_assert(index
.m_data
->funcToUnit
.erase(name
));
21351 always_assert(funcState
.erase(name
));
21352 index
.m_data
->funcToClosures
.erase(name
);
21353 if (auto const cns
= Constant::nameFromFuncName(name
)) {
21354 always_assert(index
.m_data
->constantInitFuncs
.erase(name
));
21355 always_assert(cnsChanged
.erase(cns
));
21356 index
.m_data
->constantToUnit
.at(cns
).second
= false;
21362 begin(funcNames
), end(funcNames
),
21363 [&] (SString name
) { return funcsToRemove
.count(name
); }
21368 if (!traceNamesToRemove
.empty()) {
21371 begin(traceNames
), end(traceNames
),
21372 [&] (SString name
) { return traceNamesToRemove
.count(name
); }
21378 funcsToRemove
.clear();
21381 const AnalysisScheduler::TraceState
*
21382 AnalysisScheduler::lookupTrace(DepState::Kind k
, SString n
) const {
21383 auto const s
= folly::get_ptr(traceState
, n
);
21384 if (!s
) return nullptr;
21385 for (auto const d
: s
->depStates
) {
21386 if (d
->kind
!= k
) continue;
21387 if (!d
->name
->tsame(n
)) continue;
21393 // Retrieve the TraceState appropriate for the class with the given
21395 const AnalysisScheduler::TraceState
*
21396 AnalysisScheduler::traceForClass(SString cls
) const {
21397 if (is_closure_name(cls
)) {
21398 if (auto const n
= folly::get_default(index
.m_data
->closureToClass
, cls
)) {
21399 return traceForClass(n
);
21401 if (auto const n
= folly::get_default(index
.m_data
->closureToFunc
, cls
)) {
21402 return traceForFunc(n
);
21405 return lookupTrace(DepState::Class
, cls
);
21408 // Retrieve the TraceState appropriate for the func with the given
21410 const AnalysisScheduler::TraceState
*
21411 AnalysisScheduler::traceForFunc(SString func
) const {
21412 if (auto const cns
= Constant::nameFromFuncName(func
)) {
21413 return traceForConstant(cns
);
21415 return lookupTrace(DepState::Func
, func
);
21418 // Retrieve the TraceState appropriate for the unit with the given
21420 const AnalysisScheduler::TraceState
*
21421 AnalysisScheduler::traceForUnit(SString unit
) const {
21422 return lookupTrace(DepState::Unit
, unit
);
21425 // Retrive the TraceState appropriate for the constant with the given
21427 const AnalysisScheduler::TraceState
*
21428 AnalysisScheduler::traceForConstant(SString cns
) const {
21429 auto const unit
= folly::get_ptr(index
.m_data
->constantToUnit
, cns
);
21430 if (!unit
) return nullptr;
21431 return traceForUnit(unit
->first
);
21434 // Retrieve the TraceState appropriate for the type-alias with the
21436 const AnalysisScheduler::TraceState
*
21437 AnalysisScheduler::traceForTypeAlias(SString typeAlias
) const {
21439 folly::get_default(index
.m_data
->typeAliasToUnit
, typeAlias
);
21440 if (!unit
) return nullptr;
21441 return traceForUnit(unit
);
21444 // Retrieve the TraceState appropriate for the class or type-alias
21445 // with the given name.
21446 const AnalysisScheduler::TraceState
*
21447 AnalysisScheduler::traceForClassOrTypeAlias(SString name
) const {
21448 if (auto const t
= traceForClass(name
)) return t
;
21449 if (auto const t
= traceForTypeAlias(name
)) return t
;
21453 // Maps a DepState to it's associated TraceState.
21454 const AnalysisScheduler::TraceState
*
21455 AnalysisScheduler::traceForDepState(const DepState
& d
) const {
21457 case DepState::Func
: return traceForFunc(d
.name
);
21458 case DepState::Class
: return traceForClass(d
.name
);
21459 case DepState::Unit
: return traceForUnit(d
.name
);
21461 always_assert(false);
21464 Either
<const AnalysisScheduler::ClassState
*,
21465 const AnalysisScheduler::UnitState
*>
21466 AnalysisScheduler::stateForClassOrTypeAlias(SString n
) const {
21467 if (auto const s
= folly::get_ptr(classState
, n
)) return s
;
21468 if (auto const unit
= folly::get_default(index
.m_data
->typeAliasToUnit
, n
)) {
21469 return folly::get_ptr(unitState
, unit
);
21474 AnalysisScheduler::Presence
21475 AnalysisScheduler::presenceOf(const AnalysisInput::BucketPresence
& b1
,
21476 const AnalysisInput::BucketPresence
& b2
) const {
21477 if (&b1
== &b2
) return Presence::Full
;
21478 assertx(b1
.process
);
21479 assertx(b2
.process
);
21480 assertx(b2
.withBC
);
21481 assertx(b2
.present
);
21482 if (b1
.process
->isSubset(*b2
.process
)) return Presence::Full
;
21483 if (b1
.process
->isSubset(*b2
.withBC
)) return Presence::DepWithBytecode
;
21484 if (b1
.process
->isSubset(*b2
.present
)) return Presence::Dep
;
21485 return Presence::None
;
21488 AnalysisScheduler::Presence
21489 AnalysisScheduler::presenceOfClass(const TraceState
& trace
,
21490 SString cls
) const {
21491 if (is_closure_name(cls
)) {
21492 if (auto const n
= folly::get_default(index
.m_data
->closureToClass
, cls
)) {
21493 return presenceOfClass(trace
, n
);
21495 if (auto const n
= folly::get_default(index
.m_data
->closureToFunc
, cls
)) {
21496 return presenceOfFunc(trace
, n
);
21500 if (auto const t
= traceForClass(cls
)) {
21501 return presenceOf(trace
.buckets
, t
->buckets
);
21503 if (auto const b
= folly::get_ptr(untracked
.classes
, cls
)) {
21504 return presenceOf(trace
.buckets
, *b
);
21507 assertx(trace
.buckets
.process
);
21508 return trace
.buckets
.process
->empty() ? Presence::Full
: Presence::None
;
21511 AnalysisScheduler::Presence
21512 AnalysisScheduler::presenceOfClassOrTypeAlias(const TraceState
& trace
,
21513 SString cls
) const {
21514 if (is_closure_name(cls
)) {
21515 if (auto const n
= folly::get_default(index
.m_data
->closureToClass
, cls
)) {
21516 return presenceOfClass(trace
, n
);
21518 if (auto const n
= folly::get_default(index
.m_data
->closureToFunc
, cls
)) {
21519 return presenceOfFunc(trace
, n
);
21523 if (auto const t
= traceForClassOrTypeAlias(cls
)) {
21524 return presenceOf(trace
.buckets
, t
->buckets
);
21526 if (auto const u
= folly::get_default(index
.m_data
->typeAliasToUnit
, cls
)) {
21527 if (auto const b
= folly::get_ptr(untracked
.units
, u
)) {
21528 return presenceOf(trace
.buckets
, *b
);
21530 } else if (auto const b
= folly::get_ptr(untracked
.classes
, cls
)) {
21531 return presenceOf(trace
.buckets
, *b
);
21534 assertx(trace
.buckets
.process
);
21535 return trace
.buckets
.process
->empty() ? Presence::Full
: Presence::None
;
21538 AnalysisScheduler::Presence
21539 AnalysisScheduler::presenceOfFunc(const TraceState
& trace
,
21540 SString func
) const {
21541 if (auto const t
= traceForFunc(func
)) {
21542 return presenceOf(trace
.buckets
, t
->buckets
);
21544 if (auto const b
= folly::get_ptr(untracked
.funcs
, func
)) {
21545 return presenceOf(trace
.buckets
, *b
);
21547 assertx(trace
.buckets
.process
);
21548 return trace
.buckets
.process
->empty() ? Presence::Full
: Presence::None
;
21551 AnalysisScheduler::Presence
21552 AnalysisScheduler::presenceOfConstant(const TraceState
& trace
,
21553 SString cns
) const {
21554 if (auto const trace2
= traceForConstant(cns
)) {
21555 return presenceOf(trace
.buckets
, trace2
->buckets
);
21557 if (auto const b
= folly::get_ptr(untracked
.badConstants
, cns
)) {
21558 return presenceOf(trace
.buckets
, *b
);
21560 assertx(trace
.buckets
.process
);
21561 return trace
.buckets
.process
->empty() ? Presence::Full
: Presence::None
;
21564 // Calculate any classes or functions which should be scheduled to be
21565 // analyzed in the next round.
21566 void AnalysisScheduler::findToSchedule() {
21567 // Check if the given entity (class or function) needs to run again
21568 // due to one of its dependencies changing (or if it previously
21569 // registered a new dependency).
21570 auto const check
= [&] (SString name
, DepState
& d
) {
21571 // The algorithm for these are all similar: Compare the old
21572 // dependencies with the new dependencies. If the dependency is
21573 // new, or if it's not the same as the old, check the
21574 // ClassGroup. If they're in the same ClassGroup, ignore it (this
21575 // entity already incorporated the change inside the analysis
21576 // job). Otherwise schedule this class or func to run.
21578 if (d
.kind
== DepState::Class
&& is_closure_name(name
)) {
21579 assertx(d
.deps
.empty());
21583 auto const trace
= traceForDepState(d
);
21584 always_assert(trace
);
21586 for (auto const cls
: d
.deps
.classes
) {
21587 if (presenceOfClassOrTypeAlias(*trace
, cls
) == Presence::None
) {
21589 4, "AnalysisScheduler: {} new class/type-alias dependency on {},"
21597 for (auto const cls
: d
.deps
.typeCnsClasses
) {
21598 if (presenceOfClassOrTypeAlias(*trace
, cls
) == Presence::None
) {
21600 4, "AnalysisScheduler: {} new class/type-alias dependency "
21601 "(in type-cns) on {}, scheduling\n",
21608 for (auto const cls
: d
.deps
.anyClsConstants
) {
21609 auto const schedule
= [&] {
21610 switch (presenceOfClassOrTypeAlias(*trace
, cls
)) {
21611 case Presence::None
:
21613 case Presence::Dep
:
21614 case Presence::DepWithBytecode
: {
21615 auto const state
= folly::get_ptr(classState
, cls
);
21616 return state
&& state
->cnsChanges
.any();
21618 case Presence::Full
:
21625 4, "AnalysisScheduler: {} new/changed dependency on "
21626 "any class constant for {}, scheduling\n",
21633 for (auto const cls
: d
.deps
.typeCnsAnyClsConstants
) {
21634 if (presenceOfClassOrTypeAlias(*trace
, cls
) == Presence::None
) {
21636 4, "AnalysisScheduler: {} new/changed dependency (in type-cns) on "
21637 "any class constant for {}, scheduling\n",
21644 for (auto const cns
: d
.deps
.typeCnsClsConstants
) {
21645 if (presenceOfClassOrTypeAlias(*trace
, cns
.cls
) == Presence::None
) {
21647 4, "AnalysisScheduler: {} new/changed dependency (in type-cns) on "
21648 "class constant {}, scheduling\n",
21655 for (auto const [meth
, newT
] : d
.deps
.methods
) {
21656 if (newT
== Type::None
) continue;
21658 auto const schedule
= [&, meth
=meth
, newT
=newT
] {
21659 switch (presenceOfClass(*trace
, meth
.cls
)) {
21660 case Presence::None
:
21662 case Presence::Dep
:
21663 if (newT
& Type::Bytecode
) return true;
21665 case Presence::DepWithBytecode
: {
21666 auto const state
= folly::get_ptr(classState
, meth
.cls
);
21667 if (!state
) return false;
21668 auto const changed
=
21669 state
->methodChanges
.get_default(meth
.idx
, Type::None
);
21670 return (bool)(changed
& newT
);
21672 case Presence::Full
:
21679 4, "AnalysisScheduler: {} new/changed dependency on method {},"
21687 for (auto const [func
, newT
] : d
.deps
.funcs
) {
21688 if (newT
== Type::None
) continue;
21690 auto const schedule
= [&, func
=func
, newT
=newT
] {
21691 switch (presenceOfFunc(*trace
, func
)) {
21692 case Presence::None
:
21694 case Presence::Dep
:
21695 if (newT
& Type::Bytecode
) return true;
21697 case Presence::DepWithBytecode
: {
21698 auto const state
= folly::get_ptr(funcState
, func
);
21699 if (!state
) return false;
21700 return (bool)(state
->changed
& newT
);
21702 case Presence::Full
:
21709 4, "AnalysisScheduler: {} new/changed dependency on func {}, "
21717 for (auto const cns
: d
.deps
.clsConstants
) {
21718 auto const schedule
= [&] {
21719 switch (presenceOfClass(*trace
, cns
.cls
)) {
21720 case Presence::None
:
21722 case Presence::Dep
:
21723 case Presence::DepWithBytecode
: {
21724 auto const state
= folly::get_ptr(classState
, cns
.cls
);
21725 if (!state
) return false;
21727 cns
.idx
< state
->cnsChanges
.size() && state
->cnsChanges
[cns
.idx
];
21729 case Presence::Full
:
21736 4, "AnalysisScheduler: {} new/changed dependency on "
21737 "class constant {}, scheduling\n",
21744 for (auto const cns
: d
.deps
.constants
) {
21745 auto const schedule
= [&] {
21746 switch (presenceOfConstant(*trace
, cns
)) {
21747 case Presence::None
:
21749 case Presence::Dep
:
21750 case Presence::DepWithBytecode
: {
21751 auto const changed
= folly::get_ptr(cnsChanged
, cns
);
21752 return changed
&& changed
->load(std::memory_order_acquire
);
21754 case Presence::Full
:
21761 4, "AnalysisScheduler: {} new/changed dependency on "
21762 "constant {}, scheduling\n",
21772 parallel::for_each(
21774 [&] (SString name
) {
21775 assertx(!is_closure_name(name
));
21776 auto& state
= classState
.at(name
).depState
;
21777 state
.toSchedule
= check(name
, state
);
21778 if (state
.toSchedule
) ++totalWorkItems
;
21781 parallel::for_each(
21783 [&] (SString name
) {
21784 auto& state
= funcState
.at(name
).depState
;
21785 state
.toSchedule
= check(name
, state
);
21786 if (state
.toSchedule
) ++totalWorkItems
;
21789 parallel::for_each(
21791 [&] (SString name
) {
21792 auto& state
= unitState
.at(name
).depState
;
21793 state
.toSchedule
= check(name
, state
);
21794 if (state
.toSchedule
) ++totalWorkItems
;
21799 // Reset any recorded changes from analysis jobs, in preparation for
21801 void AnalysisScheduler::resetChanges() {
21802 auto const resetClass
= [&] (SString name
) {
21803 auto& state
= classState
.at(name
);
21805 begin(state
.methodChanges
),
21806 end(state
.methodChanges
),
21809 state
.cnsChanges
.reset();
21812 parallel::for_each(
21814 [&] (SString name
) {
21816 auto const& closures
=
21817 folly::get_default(index
.m_data
->classToClosures
, name
);
21818 for (auto const c
: closures
) resetClass(c
);
21821 parallel::for_each(
21823 [&] (SString name
) {
21824 funcState
.at(name
).changed
= Type::None
;
21825 auto const& closures
=
21826 folly::get_default(index
.m_data
->funcToClosures
, name
);
21827 for (auto const c
: closures
) resetClass(c
);
21828 if (auto const cns
= Constant::nameFromFuncName(name
)) {
21829 cnsChanged
.at(cns
).store(false, std::memory_order_release
);
21835 // Called when all analysis jobs are finished. "Finalize" the changes
21836 // and determine what should run in the next analysis round.
21837 void AnalysisScheduler::recordingDone() {
21843 void AnalysisScheduler::addClassToInput(SString name
,
21844 AnalysisInput
& input
) const {
21845 FTRACE(4, "AnalysisScheduler: adding class {} to input\n", name
);
21847 input
.classes
.emplace(name
, index
.m_data
->classRefs
.at(name
));
21848 input
.classBC
.emplace(name
, index
.m_data
->classBytecodeRefs
.at(name
));
21849 if (auto const ref
= folly::get_ptr(index
.m_data
->classInfoRefs
, name
)) {
21850 input
.cinfos
.emplace(name
, ref
->cast
<AnalysisIndexCInfo
>());
21851 } else if (auto const ref
=
21852 folly::get_ptr(index
.m_data
->uninstantiableClsMethRefs
, name
)) {
21853 input
.minfos
.emplace(name
, ref
->cast
<AnalysisIndexMInfo
>());
21855 input
.meta
.badClasses
.emplace(name
);
21857 if (!input
.m_key
) input
.m_key
= name
;
21860 void AnalysisScheduler::addFuncToInput(SString name
,
21861 AnalysisInput
& input
) const {
21862 FTRACE(4, "AnalysisScheduler: adding func {} to input\n", name
);
21864 input
.funcs
.emplace(name
, index
.m_data
->funcRefs
.at(name
));
21865 input
.funcBC
.emplace(name
, index
.m_data
->funcBytecodeRefs
.at(name
));
21866 input
.finfos
.emplace(
21868 index
.m_data
->funcInfoRefs
.at(name
).cast
<AnalysisIndexFInfo
>()
21870 if (!input
.m_key
) input
.m_key
= name
;
21872 auto const& closures
= folly::get_default(index
.m_data
->funcToClosures
, name
);
21873 for (auto const clo
: closures
) addClassToInput(clo
, input
);
21876 void AnalysisScheduler::addUnitToInput(SString name
,
21877 AnalysisInput
& input
) const {
21878 FTRACE(4, "AnalysisScheduler: adding unit {} to input\n", name
);
21879 input
.units
.emplace(name
, index
.m_data
->unitRefs
.at(name
));
21880 if (!input
.m_key
) input
.m_key
= name
;
21883 void AnalysisScheduler::addDepClassToInput(SString cls
,
21886 AnalysisInput
& input
,
21887 bool fromClosureCtx
) const {
21888 if (auto const unit
=
21889 folly::get_default(index
.m_data
->typeAliasToUnit
, cls
)) {
21890 addDepUnitToInput(unit
, depSrc
, input
);
21894 if (input
.classes
.count(cls
)) return;
21896 auto const badClass
= [&] {
21897 if (input
.meta
.badClasses
.emplace(cls
).second
) {
21898 FTRACE(4, "AnalysisScheduler: adding bad class {} to {} dep inputs\n",
21903 auto const clsRef
= folly::get_ptr(index
.m_data
->classRefs
, cls
);
21905 if (!is_closure_name(cls
)) return badClass();
21906 assertx(!index
.m_data
->closureToFunc
.count(cls
));
21907 auto const ctx
= folly::get_default(index
.m_data
->closureToClass
, cls
);
21908 if (!ctx
) return badClass();
21909 if (fromClosureCtx
) return;
21910 return addDepClassToInput(ctx
, depSrc
, addBytecode
, input
);
21912 assertx(!index
.m_data
->closureToClass
.count(cls
));
21914 if (input
.depClasses
.emplace(cls
, *clsRef
).second
) {
21916 4, "AnalysisScheduler: adding class {} to {} dep inputs\n",
21919 } else if (!addBytecode
|| input
.classBC
.count(cls
)) {
21923 if (auto const r
= folly::get_ptr(index
.m_data
->classInfoRefs
, cls
)) {
21924 input
.cinfos
.emplace(cls
, r
->cast
<AnalysisIndexCInfo
>());
21925 } else if (auto const r
=
21926 folly::get_ptr(index
.m_data
->uninstantiableClsMethRefs
, cls
)) {
21927 input
.minfos
.emplace(cls
, r
->cast
<AnalysisIndexMInfo
>());
21933 if (input
.classBC
.emplace(cls
,
21934 index
.m_data
->classBytecodeRefs
.at(cls
)).second
) {
21936 4, "AnalysisScheduler: adding class {} bytecode to {} dep inputs\n",
21942 addDepUnitToInput(index
.m_data
->classToUnit
.at(cls
), depSrc
, input
);
21944 if (is_closure_name(cls
)) {
21945 auto const f
= folly::get_default(index
.m_data
->closureToFunc
, cls
);
21947 if (fromClosureCtx
) return;
21948 return addDepFuncToInput(
21951 addBytecode
? Type::Bytecode
: Type::Meta
,
21957 void AnalysisScheduler::addDepFuncToInput(SString func
,
21960 AnalysisInput
& input
) const {
21961 assertx(type
!= Type::None
);
21962 if (input
.funcs
.count(func
)) return;
21964 auto const funcRef
= folly::get_ptr(index
.m_data
->funcRefs
, func
);
21966 if (input
.meta
.badFuncs
.emplace(func
).second
) {
21967 FTRACE(4, "AnalysisScheduler: adding bad func {} to {} dep inputs\n",
21973 if (input
.depFuncs
.emplace(func
, *funcRef
).second
) {
21975 4, "AnalysisScheduler: adding func {} to {} dep inputs\n",
21978 } else if (!(type
& Type::Bytecode
) || input
.funcBC
.count(func
)) {
21982 input
.finfos
.emplace(
21984 index
.m_data
->funcInfoRefs
.at(func
).cast
<AnalysisIndexFInfo
>()
21987 if (type
& Type::Bytecode
) {
21988 if (input
.funcBC
.emplace(func
,
21989 index
.m_data
->funcBytecodeRefs
.at(func
)).second
) {
21991 4, "AnalysisScheduler: adding func {} bytecode to {} dep inputs\n",
21997 addDepUnitToInput(index
.m_data
->funcToUnit
.at(func
), depSrc
, input
);
21999 auto const& closures
= folly::get_default(index
.m_data
->funcToClosures
, func
);
22000 for (auto const clo
: closures
) {
22001 addDepClassToInput(clo
, depSrc
, type
& Type::Bytecode
, input
, true);
22005 void AnalysisScheduler::addDepConstantToInput(SString cns
,
22007 AnalysisInput
& input
) const {
22008 auto const unit
= folly::get_ptr(index
.m_data
->constantToUnit
, cns
);
22010 if (input
.meta
.badConstants
.emplace(cns
).second
) {
22011 FTRACE(4, "AnalysisScheduler: adding bad constant {} to {} dep inputs\n",
22017 addDepUnitToInput(unit
->first
, depSrc
, input
);
22018 if (!unit
->second
|| is_native_unit(unit
->first
)) return;
22020 auto const initName
= Constant::funcNameFromName(cns
);
22021 always_assert_flog(
22022 index
.m_data
->funcRefs
.count(initName
),
22023 "Constant {} is missing expected initialization function {}",
22027 addDepFuncToInput(initName
, depSrc
, Type::Meta
, input
);
22030 void AnalysisScheduler::addDepUnitToInput(SString unit
,
22032 AnalysisInput
& input
) const {
22033 if (input
.units
.count(unit
)) return;
22034 if (!input
.depUnits
.emplace(unit
, index
.m_data
->unitRefs
.at(unit
)).second
) {
22037 FTRACE(4, "AnalysisScheduler: adding unit {} to {} dep inputs\n",
22041 void AnalysisScheduler::addTraceDepToInput(const DepState
& d
,
22042 AnalysisInput
& input
) const {
22044 case DepState::Func
:
22045 addDepFuncToInput(d
.name
, d
.name
, Type::Bytecode
, input
);
22047 case DepState::Class
:
22048 addDepClassToInput(d
.name
, d
.name
, true, input
);
22050 case DepState::Unit
:
22051 addDepUnitToInput(d
.name
, d
.name
, input
);
22054 addDepsToInput(d
, input
);
22057 void AnalysisScheduler::addDepsToInput(const DepState
& deps
,
22058 AnalysisInput
& input
) const {
22059 auto const& i
= *index
.m_data
;
22061 switch (deps
.kind
) {
22062 case DepState::Func
:
22064 i
.funcToUnit
.at(deps
.name
),
22069 case DepState::Class
:
22071 i
.classToUnit
.at(deps
.name
),
22075 if (auto const bases
= folly::get_ptr(i
.classToCnsBases
, deps
.name
)) {
22076 for (auto const b
: *bases
) {
22077 addDepClassToInput(b
, deps
.name
, false, input
);
22081 case DepState::Unit
:
22085 stateForClassOrTypeAlias(deps
.name
).match(
22086 [&] (const ClassState
* s
) {
22088 for (auto const t
: s
->typeCnsNames
) {
22089 addDepClassToInput(t
, deps
.name
, false, input
);
22092 [&] (const UnitState
* s
) {
22094 for (auto const t
: s
->typeCnsNames
) {
22095 addDepClassToInput(t
, deps
.name
, false, input
);
22100 if (deps
.deps
.empty()) return;
22101 assertx(IMPLIES(deps
.kind
== DepState::Class
, !is_closure_name(deps
.name
)));
22102 auto const& d
= deps
.deps
;
22104 for (auto const cls
: d
.classes
) {
22105 addDepClassToInput(cls
, deps
.name
, false, input
);
22107 for (auto const cls
: d
.typeCnsClasses
) {
22108 addDepClassToInput(cls
, deps
.name
, false, input
);
22110 for (auto const [meth
, type
] : d
.methods
) {
22111 if (type
!= Type::None
) {
22112 addDepClassToInput(
22115 type
& Type::Bytecode
,
22120 for (auto const [func
, type
] : d
.funcs
) {
22121 if (type
!= Type::None
) addDepFuncToInput(func
, deps
.name
, type
, input
);
22123 for (auto const cns
: d
.clsConstants
) {
22124 addDepClassToInput(cns
.cls
, deps
.name
, false, input
);
22126 for (auto const cns
: d
.typeCnsClsConstants
) {
22127 addDepClassToInput(cns
.cls
, deps
.name
, false, input
);
22129 for (auto const cls
: d
.anyClsConstants
) {
22130 addDepClassToInput(cls
, deps
.name
, false, input
);
22131 if (auto const bases
= folly::get_ptr(i
.classToCnsBases
, cls
)) {
22132 for (auto const b
: *bases
) addDepClassToInput(b
, deps
.name
, false, input
);
22135 for (auto const cls
: d
.typeCnsAnyClsConstants
) {
22136 addDepClassToInput(cls
, deps
.name
, false, input
);
22137 if (auto const bases
= folly::get_ptr(i
.classToCnsBases
, cls
)) {
22138 for (auto const b
: *bases
) addDepClassToInput(b
, deps
.name
, false, input
);
22141 for (auto const cns
: d
.constants
) {
22142 addDepConstantToInput(cns
, deps
.name
, input
);
22146 // For every input in the AnalysisInput, add any associated
22147 // dependencies for those inputs.
22148 void AnalysisScheduler::addAllDepsToInput(AnalysisInput
& input
) const {
22149 for (auto const& [name
, _
] : input
.classes
) {
22150 addDepsToInput(classState
.at(name
).depState
, input
);
22152 for (auto const& [name
, _
] : input
.funcs
) {
22153 addDepsToInput(funcState
.at(name
).depState
, input
);
22155 for (auto const& [name
, _
] : input
.units
) {
22156 addDepsToInput(unitState
.at(name
).depState
, input
);
22159 // These are automatically included everywhere:
22160 for (auto const f
: special_builtins()) {
22161 addDepFuncToInput(f
, f
, Type::Meta
, input
);
22163 // Wait handle information must always be available.
22164 addDepClassToInput(s_Awaitable
.get(), s_Awaitable
.get(), false, input
);
22165 addDepClassToInput(s_Traversable
.get(), s_Traversable
.get(), false, input
);
22168 void AnalysisScheduler::addDepsMeta(const DepState
& deps
,
22169 AnalysisInput
& input
) const {
22170 auto& d
= [&] () -> AnalysisDeps
& {
22171 switch (deps
.kind
) {
22172 case DepState::Func
: return input
.meta
.funcDeps
[deps
.name
];
22173 case DepState::Class
: return input
.meta
.classDeps
[deps
.name
];
22174 case DepState::Unit
: return input
.meta
.unitDeps
[deps
.name
];
22180 // Call F for any dependency which should be included
22181 // transitively. This usually means the dependency has information
22182 // that can change between rounds. This is a subset of a DepState's
22183 // total dependencies.
22184 template <typename F
>
22185 void AnalysisScheduler::onTransitiveDep(SString name
,
22186 const DepState
& state
,
22187 const F
& f
) const {
22188 if (state
.deps
.empty() || workItems() >= 4000000) return;
22189 auto const& d
= state
.deps
;
22190 auto const& i
= *index
.m_data
;
22192 auto const filter
= [&] (SString n
) {
22193 if (!n
->tsame(name
)) f(n
);
22196 auto const onFunc
= [&] (SString n
) {
22197 if (!Constant::nameFromFuncName(n
)) return filter(n
);
22198 auto const u
= folly::get_default(i
.funcToUnit
, n
);
22202 auto const onCls
= [&] (SString n
) {
22203 if (auto const c
= folly::get_default(i
.closureToClass
, n
)) {
22206 if (auto const f
= folly::get_default(i
.closureToFunc
, n
)) {
22212 auto const onClsOrTypeAlias
= [&] (SString n
) {
22213 if (auto const u
= folly::get_default(i
.typeAliasToUnit
, n
)) {
22220 stateForClassOrTypeAlias(state
.name
).match(
22221 [&] (const ClassState
* s
) {
22223 for (auto const t
: s
->typeCnsNames
) onClsOrTypeAlias(t
);
22225 [&] (const UnitState
* s
) {
22227 for (auto const t
: s
->typeCnsNames
) onClsOrTypeAlias(t
);
22230 if (auto const bases
= folly::get_ptr(i
.classToCnsBases
, state
.name
)) {
22231 for (auto const b
: *bases
) onCls(b
);
22234 for (auto const [meth
, type
] : d
.methods
) {
22235 if (type
== Type::None
) continue;
22236 if (!(type
& AnalysisDeps::kValidForChanges
)) continue;
22239 for (auto const [func
, type
] : d
.funcs
) {
22240 if (!(type
& AnalysisDeps::kValidForChanges
)) continue;
22243 for (auto const cls
: d
.anyClsConstants
) {
22244 stateForClassOrTypeAlias(cls
).match(
22245 [&] (const ClassState
* s
) {
22246 if (s
&& !s
->allCnsFixed
) onCls(cls
);
22248 [&] (const UnitState
* s
) {
22249 if (s
&& !s
->fixed
) filter(s
->depState
.name
);
22253 for (auto const cls
: d
.typeCnsAnyClsConstants
) onClsOrTypeAlias(cls
);
22255 for (auto const cns
: d
.clsConstants
) {
22256 stateForClassOrTypeAlias(cns
.cls
).match(
22257 [&] (const ClassState
* s
) {
22258 if (!s
|| s
->allCnsFixed
) return;
22259 if (cns
.idx
>= s
->cnsFixed
.size() || !s
->cnsFixed
[cns
.idx
]) {
22263 [&] (const UnitState
* s
) {
22264 if (s
&& !s
->fixed
) filter(s
->depState
.name
);
22268 for (auto const cns
: d
.typeCnsClsConstants
) onClsOrTypeAlias(cns
.cls
);
22270 for (auto const cns
: d
.constants
) onFunc(Constant::funcNameFromName(cns
));
22272 for (auto const ta
: d
.classes
) {
22273 auto const unit
= folly::get_default(i
.typeAliasToUnit
, ta
);
22274 if (!unit
) continue;
22275 auto const p
= folly::get_ptr(unitState
, unit
);
22276 if (!p
|| p
->fixed
) continue;
22279 for (auto const c
: d
.typeCnsClasses
) onClsOrTypeAlias(c
);
22282 void AnalysisScheduler::addAllDeps(TSStringSet
& out
,
22283 const DepState
& state
) const {
22284 if (state
.deps
.empty()) return;
22285 auto const& d
= state
.deps
;
22286 auto const& i
= *index
.m_data
;
22288 auto const add
= [&] (SString n
) { out
.emplace(n
); };
22290 auto const onFunc
= [&] (SString n
) {
22291 if (!Constant::nameFromFuncName(n
)) return add(n
);
22292 auto const u
= folly::get_default(i
.funcToUnit
, n
);
22296 auto const onCls
= [&] (SString n
) {
22297 if (auto const c
= folly::get_default(i
.closureToClass
, n
)) {
22300 if (auto const f
= folly::get_default(i
.closureToFunc
, n
)) {
22306 auto const onClsOrTypeAlias
= [&] (SString n
) {
22307 if (auto const u
= folly::get_default(i
.typeAliasToUnit
, n
)) {
22314 stateForClassOrTypeAlias(state
.name
).match(
22315 [&] (const ClassState
* s
) {
22317 for (auto const t
: s
->typeCnsNames
) onClsOrTypeAlias(t
);
22319 [&] (const UnitState
* s
) {
22321 for (auto const t
: s
->typeCnsNames
) onClsOrTypeAlias(t
);
22324 if (auto const bases
= folly::get_ptr(i
.classToCnsBases
, state
.name
)) {
22325 for (auto const b
: *bases
) onCls(b
);
22328 for (auto const cls
: d
.classes
) onClsOrTypeAlias(cls
);
22329 for (auto const cls
: d
.typeCnsClasses
) onClsOrTypeAlias(cls
);
22331 for (auto const [meth
, type
] : d
.methods
) {
22332 if (type
!= Type::None
) onCls(meth
.cls
);
22334 for (auto const [func
, type
] : d
.funcs
) {
22335 if (type
!= Type::None
) onFunc(func
);
22338 for (auto const cls
: d
.anyClsConstants
) {
22339 onClsOrTypeAlias(cls
);
22340 if (auto const bases
= folly::get_ptr(i
.classToCnsBases
, cls
)) {
22341 for (auto const b
: *bases
) onCls(b
);
22344 for (auto const cls
: d
.typeCnsAnyClsConstants
) {
22345 onClsOrTypeAlias(cls
);
22346 if (auto const bases
= folly::get_ptr(i
.classToCnsBases
, cls
)) {
22347 for (auto const b
: *bases
) onCls(b
);
22350 for (auto const cns
: d
.clsConstants
) onClsOrTypeAlias(cns
.cls
);
22351 for (auto const cns
: d
.typeCnsClsConstants
) onClsOrTypeAlias(cns
.cls
);
22353 for (auto const cns
: d
.constants
) onFunc(Constant::funcNameFromName(cns
));
22356 // Dump dependencies of any trace classes or functions.
22357 void AnalysisScheduler::maybeDumpTraces() const {
22358 if (!Trace::moduleEnabledRelease(Trace::hhbbc_dump_trace
, 1)) return;
22360 TSStringSet allRoots
;
22361 TSStringSet allTraces
;
22362 TSStringSet allDeps
;
22363 TSStringToOneT
<TSStringSet
> allTraceEdges
;
22364 TSStringToOneT
<TSStringSet
> allDepEdges
;
22366 parallel::for_each(
22368 [&] (SString name
) {
22369 Trace::BumpRelease bumper
{
22370 Trace::hhbbc_dump_trace
,
22372 auto& state
= traceState
.at(name
);
22373 int bump
= std::numeric_limits
<int>::max();
22374 for (auto const d
: state
.depStates
) {
22375 SString cls
= nullptr;
22376 SString func
= nullptr;
22377 SString unit
= nullptr;
22379 case DepState::Func
:
22381 unit
= folly::get_default(index
.m_data
->funcToUnit
, d
->name
);
22383 case DepState::Class
:
22385 unit
= folly::get_default(index
.m_data
->classToUnit
, d
->name
);
22387 case DepState::Unit
:
22391 bump
= std::min(bump
, trace_bump_for(cls
, func
, unit
));
22393 assertx(bump
< std::numeric_limits
<int>::max());
22397 if (!Trace::moduleEnabledRelease(Trace::hhbbc_dump_trace
, 2)) return;
22399 TSStringSet traces
;
22401 TSStringToOneT
<TSStringSet
> traceEdges
;
22402 TSStringToOneT
<TSStringSet
> depEdges
;
22404 std::vector
<SString
> worklist
;
22405 worklist
.emplace_back(name
);
22406 traces
.emplace(name
);
22408 while (!worklist
.empty()) {
22409 auto const from
= worklist
.back();
22410 worklist
.pop_back();
22411 auto const s
= folly::get_ptr(traceState
, from
);
22413 for (auto const d
: s
->depStates
) {
22417 if (from
->tsame(to
)) return;
22418 if (!traceState
.count(to
)) return;
22419 traceEdges
[from
].emplace(to
);
22420 if (!traces
.emplace(to
).second
) return;
22421 worklist
.emplace_back(to
);
22427 for (auto const n
: traces
) {
22428 auto const s
= folly::get_ptr(traceState
, n
);
22430 for (auto const d
: s
->depStates
) {
22431 TSStringSet newDeps
;
22432 addAllDeps(newDeps
, *d
);
22433 for (auto const n2
: newDeps
) {
22434 if (n
->tsame(n2
)) continue;
22435 depEdges
[n
].emplace(n2
);
22437 deps
.insert(begin(newDeps
), end(newDeps
));
22441 std::lock_guard
<std::mutex
> _
{*lock
};
22442 allRoots
.emplace(name
);
22443 allTraces
.insert(begin(traces
), end(traces
));
22444 allDeps
.insert(begin(deps
), end(deps
));
22445 for (auto const& [n
, e
] : traceEdges
) {
22446 allTraceEdges
[n
].insert(begin(e
), end(e
));
22448 for (auto const& [n
, e
] : depEdges
) {
22449 allDepEdges
[n
].insert(begin(e
), end(e
));
22454 if (allRoots
.empty() && allTraces
.empty() && allDeps
.empty()) return;
22457 TSStringToOneT
<size_t> ids
;
22458 auto const id
= [&] (SString n
) {
22459 if (auto const i
= folly::get_ptr(ids
, n
)) return *i
;
22460 ids
.emplace(n
, nextId
);
22464 using namespace folly::gen
;
22466 auto const nodes
= [&] (const TSStringSet
& ns
,
22468 const TSStringSet
* f1
= nullptr,
22469 const TSStringSet
* f2
= nullptr) {
22471 | filter([&] (SString n
) { return !f1
|| !f1
->count(n
); })
22472 | filter([&] (SString n
) { return !f2
|| !f2
->count(n
); })
22473 | orderBy(folly::identity
, string_data_lt_type
{})
22474 | map([&] (SString n
) {
22475 return folly::sformat(
22476 " {} [label=\"{}\" color={}];",
22477 id(n
), folly::cEscape
<std::string
>(n
->slice()), color
22480 | unsplit
<std::string
>("\n");
22483 auto const edges
= [&] (const TSStringToOneT
<TSStringSet
>& es
,
22485 const TSStringToOneT
<TSStringSet
>* f1
= nullptr) {
22486 std::vector
<SString
> fromE
;
22487 for (auto const& [n
, _
] : es
) fromE
.emplace_back(n
);
22488 std::sort(begin(fromE
), end(fromE
), string_data_lt_type
{});
22490 | map([&] (SString n
) {
22491 return folly::sformat(
22492 " {} -> {{{}}} [color={}];",
22495 | filter([&] (SString n2
) {
22496 if (!f1
) return true;
22497 auto const t
= folly::get_ptr(*f1
, n
);
22498 return !t
|| !t
->count(n2
);
22500 | orderBy(folly::identity
, string_data_lt_type
{})
22501 | map([&] (SString n2
) { return folly::sformat("{}", id(n2
)); })
22502 | unsplit
<std::string
>(" "),
22506 | unsplit
<std::string
>("\n");
22509 namespace fs
= std::filesystem
;
22511 auto const path
= [] {
22512 auto const base
= [] {
22513 if (auto const e
= getenv("HHBBC_DUMP_DIR")) return fs::path(e
);
22514 return fs::temp_directory_path();
22516 return fs::weakly_canonical(
22517 base
/ boost::filesystem::unique_path("hhbbc-%%%%%%%%").native()
22521 std::ofstream out
{path
};
22522 out
.exceptions(std::ofstream::failbit
| std::ofstream::badbit
);
22523 out
<< folly::format(
22524 "digraph \"Round #{}\" {{\n"
22532 nodes(allRoots
, "red"),
22533 nodes(allTraces
, "blue", &allRoots
),
22534 nodes(allDeps
, "gray", &allRoots
, &allTraces
),
22535 edges(allTraceEdges
, "blue"),
22536 edges(allDepEdges
, "gray", &allTraceEdges
)
22539 std::cout
<< "Trace dump for round #" << round
22540 << " written to " << path
22544 // First pass: initialize all data in TraceStates.
22545 void AnalysisScheduler::tracePass1() {
22546 untracked
= Untracked
{};
22548 std::atomic
<size_t> idx
{0};
22549 parallel::for_each(
22552 auto& state
= traceState
.at(n
);
22553 state
.eligible
= false;
22554 state
.leaf
.store(true);
22555 state
.covered
.store(false);
22556 state
.trace
.clear();
22557 state
.deps
.clear();
22558 state
.buckets
.present
= nullptr;
22559 state
.buckets
.withBC
= nullptr;
22560 state
.buckets
.process
= nullptr;
22566 // Second pass: Build traces for each entity and mark eligible.
22567 void AnalysisScheduler::tracePass2() {
22568 parallel::for_each(
22570 [&] (SString name
) {
22571 auto& state
= traceState
.at(name
);
22573 // Build the trace up by using the transitive dependencies.
22574 std::vector
<SString
> worklist
;
22575 auto const add
= [&] (SString n
) {
22576 if (!traceState
.count(n
)) return;
22577 if (!state
.trace
.emplace(n
).second
) return;
22578 if (n
->tsame(name
)) return;
22579 worklist
.emplace_back(n
);
22582 worklist
.emplace_back(name
);
22583 while (!worklist
.empty()) {
22584 auto const n
= worklist
.back();
22585 worklist
.pop_back();
22586 auto const s
= folly::get_ptr(traceState
, n
);
22588 for (auto const d
: s
->depStates
) onTransitiveDep(n
, *d
, add
);
22591 // An entity is eligible if any of its components changed, or if
22592 // anything in the trace is eligible.
22593 auto const eligible
= [&] (const TraceState
& t
) {
22594 return std::any_of(
22595 t
.depStates
.begin(), t
.depStates
.end(),
22596 [] (const DepState
* d
) { return d
->toSchedule
; }
22603 begin(state
.trace
), end(state
.trace
),
22605 auto const s
= folly::get_ptr(traceState
, n
);
22606 return s
&& eligible(*s
);
22613 // Third pass: "Expand" every TraceState with it's total dependencies,
22614 // which is a superset of those in the trace.
22615 void AnalysisScheduler::tracePass3() {
22616 parallel::for_each(
22618 [&] (SString name
) {
22619 auto& state
= traceState
.at(name
);
22621 if (!state
.eligible
) return;
22626 auto const s
= folly::get_ptr(traceState
, n
);
22627 return !s
|| !s
->eligible
;
22631 auto const expand
= [&] (SString n
) {
22632 auto const s
= folly::get_ptr(traceState
, n
);
22634 for (auto const d
: s
->depStates
) addAllDeps(state
.deps
, *d
);
22637 state
.deps
= state
.trace
;
22639 for (auto const n
: state
.trace
) expand(n
);
22644 // Fourth pass: Determine leafs for the purpose of scheduling.
22645 std::vector
<SString
> AnalysisScheduler::tracePass4() {
22646 // A leaf here means that nothing else depends on it.
22648 // Mark any TraceState which is on another TraceState's trace as not
22650 parallel::for_each(
22652 [&] (SString name
) {
22653 auto& state
= traceState
.at(name
);
22654 if (!state
.eligible
) return;
22655 for (auto const n
: state
.trace
) {
22656 auto const s
= folly::get_ptr(traceState
, n
);
22657 if (s
) s
->leaf
.store(false);
22662 // Mark "covered" TraceStates. A covered TraceState is processed and
22663 // shouldn't be considered anymore in this function.
22664 std::vector
<SString
> traceLeafs
;
22665 parallel::for_each(
22667 [&] (SString name
) {
22668 auto& state
= traceState
.at(name
);
22669 if (!state
.eligible
|| !state
.leaf
) return;
22671 // Right now a TraceState is covered if it's a leaf, along with
22672 // anything on this TraceState's trace.
22673 state
.covered
.store(true);
22674 for (auto const n
: state
.trace
) {
22675 auto const s
= folly::get_ptr(traceState
, n
);
22676 if (s
) s
->covered
.store(true);
22679 std::lock_guard
<std::mutex
> _
{*lock
};
22680 traceLeafs
.emplace_back(name
);
22684 // Any uncovered TraceState's must now be part of a cycle (or depend
22685 // on a TraceState in a cycle).
22686 std::vector
<SString
> traceInCycle
;
22687 parallel::for_each(
22690 auto const& state
= traceState
.at(n
);
22691 if (!state
.eligible
|| state
.covered
) return;
22692 assertx(!state
.leaf
);
22693 std::lock_guard
<std::mutex
> _
{*lock
};
22694 traceInCycle
.emplace_back(n
);
22698 // Sort all TraceStates in a cycle. This isn't strictly necessary,
22699 // but leads to better results.
22701 begin(traceInCycle
),
22703 [&] (SString n1
, SString n2
) {
22704 auto const& state1
= traceState
.at(n1
);
22705 auto const& state2
= traceState
.at(n2
);
22706 assertx(state1
.eligible
);
22707 assertx(!state1
.covered
);
22708 assertx(!state1
.leaf
);
22709 assertx(state2
.eligible
);
22710 assertx(!state2
.covered
);
22711 assertx(!state2
.leaf
);
22712 if (state1
.trace
.size() > state2
.trace
.size()) return true;
22713 if (state1
.trace
.size() < state2
.trace
.size()) return false;
22714 return string_data_lt_type
{}(n1
, n2
);
22718 // Pick an arbitrary TraceState from the cycle set, declare it a
22719 // leaf by fiat, then mark it and any trace dependencies as being
22720 // covered. This process repeats until all TraceStates are covered.
22721 for (auto const name
: traceInCycle
) {
22722 auto& s
= traceState
.at(name
);
22723 if (s
.covered
) continue;
22725 s
.leaf
.store(true);
22726 s
.covered
.store(true);
22727 for (auto const n
: s
.trace
) {
22728 auto const s2
= folly::get_ptr(traceState
, n
);
22729 if (!s2
|| s2
->covered
) continue;
22730 assertx(!s2
->leaf
);
22731 s2
->covered
.store(true);
22733 traceLeafs
.emplace_back(name
);
22737 // Every TraceState at this point should be covered (or not
22739 parallel::for_each(
22742 auto const s
= folly::get_ptr(traceState
, n
);
22744 always_assert_flog(
22745 s
->covered
|| !s
->eligible
,
22746 "{} is not covered", n
22755 // Fifth pass: Assign leafs and their dependencies to
22756 // buckets. Non-leafs (which must have some TraceState depending on
22757 // them) will be assigned to one of the buckets of the TraceState that
22759 std::vector
<AnalysisScheduler::Bucket
>
22760 AnalysisScheduler::tracePass5(size_t bucketSize
,
22761 size_t maxBucketSize
,
22762 std::vector
<SString
> leafs
) {
22763 using namespace folly::gen
;
22765 auto w
= assign_hierarchical_work(
22771 return std::make_pair(&traceState
.at(n
).deps
, true);
22773 [&] (const TSStringSet
& roots
,
22775 SString n
) -> Optional
<size_t> {
22776 // To determine whether we want to promote this TraceState or
22777 // not, we need to check if it's present in any of the bucket's
22778 // traces. This can be expensive to calculate, so utilize
22779 // caching. Only recalculate the set if we're referring to a
22780 // different bucket than last call.
22781 thread_local Optional
<size_t> last
;
22782 thread_local TSStringSet inTrace
;
22783 if (!last
|| *last
!= bucketIdx
) {
22785 for (auto const root
: roots
) {
22786 auto const& state
= traceState
.at(root
);
22787 inTrace
.insert(begin(state
.trace
), end(state
.trace
));
22792 // If the TraceState is a leaf, or isn't in any of the traces
22793 // for this bucket, we don't want to promote it.
22794 auto const s
= folly::get_ptr(traceState
, n
);
22795 if (!s
|| !s
->eligible
|| s
->leaf
.load() || !inTrace
.count(n
)) {
22796 return std::nullopt
;
22803 // Sanity check that every eligible TraceState belongs to at least
22805 TSStringSet inputs
;
22806 for (auto const& b
: w
) {
22807 inputs
.insert(begin(b
.classes
), end(b
.classes
));
22810 parallel::for_each(
22813 auto const s
= folly::get_ptr(traceState
, n
);
22815 if (!s
->eligible
) return;
22816 always_assert_flog(
22818 "{} should be present in at least one bucket's inputs, but is not",
22825 using H
= HierarchicalWorkBucket
;
22828 | map([] (H
&& b
) { return Bucket
{std::move(b
)}; })
22829 | as
<std::vector
>();
22832 // Sixth pass: Make AnalysisInputs for each bucket.
22833 AnalysisScheduler::InputsAndUntracked
22834 AnalysisScheduler::tracePass6(const std::vector
<Bucket
>& buckets
) {
22835 TSStringToOneConcurrentT
<std::nullptr_t
> seenClasses
;
22836 FSStringToOneConcurrentT
<std::nullptr_t
> seenFuncs
;
22837 SStringToOneConcurrentT
<std::nullptr_t
> seenUnits
;
22838 SStringToOneConcurrentT
<std::nullptr_t
> seenConstants
;
22840 auto outputs
= parallel::gen(
22843 auto const& b
= buckets
[idx
].b
;
22844 assertx(b
.uninstantiable
.empty());
22846 TSStringSet inTrace
;
22848 // Make an AnalysisInput by adding all the appropriate inputs
22849 // for each TraceState and dependencies.
22850 AnalysisInput input
;
22851 input
.meta
.bucketIdx
= idx
;
22853 for (auto const item
: b
.classes
) {
22854 auto const& state
= traceState
.at(item
);
22855 assertx(!state
.depStates
.empty());
22856 for (auto const d
: state
.depStates
) {
22858 case DepState::Func
:
22859 addFuncToInput(d
->name
, input
);
22861 case DepState::Class
:
22862 addClassToInput(d
->name
, input
);
22864 case DepState::Unit
:
22865 addUnitToInput(d
->name
, input
);
22869 if (!d
->toSchedule
) addDepsMeta(*d
, input
);
22872 inTrace
.insert(begin(state
.trace
), end(state
.trace
));
22875 for (auto const item
: b
.deps
) {
22876 auto const s
= folly::get_ptr(traceState
, item
);
22877 if (!s
|| !s
->eligible
|| !inTrace
.count(item
)) continue;
22878 assertx(!s
->depStates
.empty());
22880 for (auto const d
: s
->depStates
) {
22881 addTraceDepToInput(*d
, input
);
22883 if (!d
->toSchedule
) {
22884 addDepsMeta(*d
, input
);
22889 case DepState::Func
:
22890 input
.meta
.processDepFunc
.emplace(d
->name
);
22892 case DepState::Class
:
22893 input
.meta
.processDepCls
.emplace(d
->name
);
22895 case DepState::Unit
:
22896 input
.meta
.processDepUnit
.emplace(d
->name
);
22902 addAllDepsToInput(input
);
22904 InputsAndUntracked out
;
22906 // Record untracked items. These are inputs to this bucket which
22907 // do not have TraceState. They must be dealt with differently
22909 auto const untracked
= [&] (auto const& i1
,
22913 using D1
= std::decay_t
<decltype(i1
)>;
22914 using D2
= std::decay_t
<decltype(i2
)>;
22916 std::vector
<SString
> out
;
22918 if constexpr (!std::is_same_v
<D1
, std::nullptr_t
>) {
22919 for (auto const& [n
, _
] : i1
) {
22920 if (std::invoke(t
, this, n
)) continue;
22921 if (!s
.try_emplace(n
).second
) continue;
22922 out
.emplace_back(n
);
22925 if constexpr (!std::is_same_v
<D2
, std::nullptr_t
>) {
22926 for (auto const n
: i2
) {
22927 if (std::invoke(t
, this, n
)) continue;
22928 if (!s
.try_emplace(n
).second
) continue;
22929 out
.emplace_back(n
);
22935 using A
= AnalysisScheduler
;
22936 out
.untrackedClasses
= untracked(
22938 input
.meta
.badClasses
,
22942 out
.untrackedFuncs
= untracked(
22944 input
.meta
.badFuncs
,
22948 out
.untrackedUnits
= untracked(
22954 out
.badConstants
= untracked(
22956 input
.meta
.badConstants
,
22958 &A::traceForConstant
22961 out
.inputs
.emplace_back(std::move(input
));
22966 // We created untracked data for each bucket. We must now combine
22967 // that into one unified set.
22969 auto const combine
= [&] (auto f
, auto& m
) {
22970 std::vector
<SString
> out
;
22971 for (auto const& output
: outputs
) {
22972 auto& in
= output
.*f
;
22973 out
.insert(end(out
), begin(in
), end(in
));
22975 for (auto const n
: out
) m
.try_emplace(n
);
22979 using I
= InputsAndUntracked
;
22981 parallel::parallel(
22983 combined
.inputs
.reserve(outputs
.size());
22984 for (auto& output
: outputs
) {
22985 assertx(output
.inputs
.size() == 1);
22986 combined
.inputs
.emplace_back(std::move(output
.inputs
[0]));
22990 combined
.untrackedClasses
=
22991 combine(&I::untrackedClasses
, untracked
.classes
);
22994 combined
.untrackedFuncs
=
22995 combine(&I::untrackedFuncs
, untracked
.funcs
);
22998 combined
.untrackedUnits
=
22999 combine(&I::untrackedUnits
, untracked
.units
);
23002 combined
.badConstants
=
23003 combine(&I::badConstants
, untracked
.badConstants
);
23010 // Seventh pass: Calculate BucketSets for each TraceSet. This will
23011 // determine how different items can utilize information from another.
23012 void AnalysisScheduler::tracePass7(InputsAndUntracked
& inputs
) {
23013 using A
= AnalysisInput
;
23015 auto const onClass
= [&] (SString name
,
23016 A::BucketSet
& present
,
23017 A::BucketSet
& withBC
,
23018 A::BucketSet
& process
) {
23019 for (size_t i
= 0, size
= inputs
.inputs
.size(); i
< size
; ++i
) {
23020 auto const& input
= inputs
.inputs
[i
];
23021 if (input
.classes
.count(name
)) {
23022 assertx(input
.classBC
.count(name
));
23026 if (input
.depClasses
.count(name
)) {
23028 if (input
.meta
.classDeps
.count(name
) ||
23029 input
.meta
.processDepCls
.count(name
)) {
23030 assertx(input
.classBC
.count(name
));
23034 if (input
.classBC
.count(name
)) withBC
.add(i
);
23035 if (input
.meta
.badClasses
.count(name
)) present
.add(i
);
23039 auto const onFunc
= [&] (SString name
,
23040 A::BucketSet
& present
,
23041 A::BucketSet
& withBC
,
23042 A::BucketSet
& process
) {
23043 for (size_t i
= 0, size
= inputs
.inputs
.size(); i
< size
; ++i
) {
23044 auto const& input
= inputs
.inputs
[i
];
23045 if (input
.funcs
.count(name
)) {
23046 assertx(input
.funcBC
.count(name
));
23050 if (input
.depFuncs
.count(name
)) {
23052 if (input
.meta
.funcDeps
.count(name
) ||
23053 input
.meta
.processDepFunc
.count(name
)) {
23054 assertx(input
.funcBC
.count(name
));
23058 if (input
.funcBC
.count(name
)) withBC
.add(i
);
23059 if (input
.meta
.badFuncs
.count(name
)) present
.add(i
);
23063 auto const onUnit
= [&] (SString name
,
23064 A::BucketSet
& present
,
23066 A::BucketSet
& process
) {
23067 for (size_t i
= 0, size
= inputs
.inputs
.size(); i
< size
; ++i
) {
23068 auto const& input
= inputs
.inputs
[i
];
23069 if (input
.units
.count(name
)) {
23073 if (input
.depUnits
.count(name
)) {
23075 if (input
.meta
.unitDeps
.count(name
) ||
23076 input
.meta
.processDepUnit
.count(name
)) {
23083 parallel::for_each(
23085 [&] (SString name
) {
23086 auto& state
= traceState
.at(name
);
23087 state
.trace
.clear();
23088 state
.deps
.clear();
23090 A::BucketSet present
;
23091 A::BucketSet withBC
;
23092 A::BucketSet process
;
23095 A::BucketSet temp1
, temp2
, temp3
;
23096 for (auto const d
: state
.depStates
) {
23100 auto& s1
= first
? present
: temp1
;
23101 auto& s2
= first
? withBC
: temp2
;
23102 auto& s3
= first
? process
: temp3
;
23104 case DepState::Func
:
23105 onFunc(d
->name
, s1
, s2
, s3
);
23107 case DepState::Class
:
23108 onClass(d
->name
, s1
, s2
, s3
);
23110 case DepState::Unit
:
23111 onUnit(d
->name
, s1
, s2
, s3
);
23123 assertx(!state
.buckets
.present
);
23124 assertx(!state
.buckets
.withBC
);
23125 assertx(!state
.buckets
.process
);
23127 state
.buckets
.present
= A::BucketSet::intern(std::move(present
));
23128 state
.buckets
.withBC
= A::BucketSet::intern(std::move(withBC
));
23129 state
.buckets
.process
= A::BucketSet::intern(std::move(process
));
23133 // Do the same for untracked items:
23135 auto const addUntracked
= [&] (const std::vector
<SString
>& v
,
23138 parallel::for_each(
23140 [&] (SString name
) {
23141 auto& state
= m
.at(name
);
23143 A::BucketSet present
;
23144 A::BucketSet withBC
;
23145 A::BucketSet process
;
23146 o(name
, present
, withBC
, process
);
23148 assertx(!present
.empty());
23149 assertx(process
.empty());
23151 assertx(!state
.present
);
23152 assertx(!state
.withBC
);
23153 assertx(!state
.process
);
23155 state
.present
= A::BucketSet::intern(std::move(present
));
23156 state
.withBC
= A::BucketSet::intern(std::move(withBC
));
23157 state
.process
= A::BucketSet::intern(std::move(process
));
23161 addUntracked(inputs
.untrackedClasses
, untracked
.classes
, onClass
);
23162 addUntracked(inputs
.untrackedFuncs
, untracked
.funcs
, onFunc
);
23163 addUntracked(inputs
.untrackedUnits
, untracked
.units
, onUnit
);
23165 inputs
.badConstants
,
23166 untracked
.badConstants
,
23168 A::BucketSet
& present
,
23171 for (size_t i
= 0, size
= inputs
.inputs
.size(); i
< size
; ++i
) {
23172 auto const& input
= inputs
.inputs
[i
];
23173 if (input
.meta
.badConstants
.count(name
)) {
23180 inputs
.untrackedClasses
.clear();
23181 inputs
.untrackedFuncs
.clear();
23182 inputs
.untrackedUnits
.clear();
23183 inputs
.badConstants
.clear();
23186 // Eighth pass: Store BucketSets in each AnalysisInput's metadata.
23187 void AnalysisScheduler::tracePass8(std::vector
<Bucket
> buckets
,
23188 std::vector
<AnalysisInput
>& inputs
) {
23192 auto& input
= inputs
[i
];
23193 assertx(i
< buckets
.size());
23194 auto& bucket
= buckets
[i
].b
;
23196 for (auto const item
: bucket
.classes
) {
23197 auto& state
= traceState
.at(item
);
23198 assertx(!state
.depStates
.empty());
23199 assertx(state
.buckets
.present
->contains(i
));
23200 assertx(state
.buckets
.process
->contains(i
));
23202 for (auto const d
: state
.depStates
) {
23204 case DepState::Func
:
23206 input
.meta
.funcBuckets
.emplace(d
->name
, state
.buckets
).second
23209 case DepState::Class
:
23211 input
.meta
.classBuckets
.emplace(d
->name
, state
.buckets
).second
23214 case DepState::Unit
:
23216 input
.meta
.unitBuckets
.emplace(d
->name
, state
.buckets
).second
23223 for (auto const item
: bucket
.deps
) {
23224 auto const s
= folly::get_ptr(traceState
, item
);
23226 assertx(!s
->depStates
.empty());
23227 if (!s
->buckets
.present
->contains(i
)) {
23228 // If this trace state isn't in the bucket, an untracked
23229 // item (with the same name) should be.
23230 auto const b
= [&] () -> const AnalysisInput::BucketPresence
* {
23231 auto const& u
= untracked
;
23232 if (auto const b
= folly::get_ptr(u
.classes
, item
)) return b
;
23233 if (auto const b
= folly::get_ptr(u
.funcs
, item
)) return b
;
23234 if (auto const b
= folly::get_ptr(u
.units
, item
)) return b
;
23235 if (auto const b
= folly::get_ptr(u
.badConstants
, item
)) return b
;
23239 always_assert(b
->present
->contains(i
));
23243 for (auto const d
: s
->depStates
) {
23245 case DepState::Func
:
23247 input
.meta
.funcBuckets
.emplace(d
->name
, s
->buckets
).second
23250 case DepState::Class
:
23252 input
.meta
.classBuckets
.emplace(d
->name
, s
->buckets
).second
23255 case DepState::Unit
:
23257 input
.meta
.unitBuckets
.emplace(d
->name
, s
->buckets
).second
23264 for (auto const& [name
, _
] : input
.depClasses
) {
23265 if (auto const b
= folly::get_ptr(untracked
.classes
, name
)) {
23266 assertx(!traceForClass(name
));
23267 assertx(b
->present
->contains(i
));
23268 always_assert(input
.meta
.classBuckets
.emplace(name
, *b
).second
);
23269 } else if (!input
.meta
.classBuckets
.count(name
)) {
23270 auto const t
= traceForClass(name
);
23271 always_assert_flog(
23272 t
, "{} is on input dep classes, but has no tracking state",
23276 input
.meta
.classBuckets
.emplace(name
, t
->buckets
).second
23278 assertx(t
->buckets
.present
->contains(i
));
23281 for (auto const& [name
, _
] : input
.depFuncs
) {
23282 if (auto const b
= folly::get_ptr(untracked
.funcs
, name
)) {
23283 assertx(!traceForFunc(name
));
23284 assertx(b
->present
->contains(i
));
23285 always_assert(input
.meta
.funcBuckets
.emplace(name
, *b
).second
);
23286 } else if (!input
.meta
.funcBuckets
.count(name
)) {
23287 auto const t
= traceForFunc(name
);
23288 always_assert_flog(
23289 t
, "{} is on input dep funcs, but has no tracking state",
23293 input
.meta
.funcBuckets
.emplace(name
, t
->buckets
).second
23295 assertx(t
->buckets
.present
->contains(i
));
23298 for (auto const& [name
, _
] : input
.depUnits
) {
23299 if (auto const b
= folly::get_ptr(untracked
.units
, name
)) {
23300 assertx(!traceForUnit(name
));
23301 assertx(b
->present
->contains(i
));
23302 always_assert(input
.meta
.unitBuckets
.emplace(name
, *b
).second
);
23303 } else if (!input
.meta
.unitBuckets
.count(name
)) {
23304 auto const t
= traceForUnit(name
);
23305 always_assert_flog(
23306 t
, "{} is on input dep units, but has no tracking state",
23310 input
.meta
.unitBuckets
.emplace(name
, t
->buckets
).second
23312 assertx(t
->buckets
.present
->contains(i
));
23316 for (auto const name
: input
.meta
.badClasses
) {
23317 if (input
.meta
.classBuckets
.count(name
)) continue;
23318 if (auto const b
= folly::get_ptr(untracked
.classes
, name
)) {
23319 assertx(!traceForClass(name
));
23320 assertx(b
->present
->contains(i
));
23321 always_assert(input
.meta
.classBuckets
.emplace(name
, *b
).second
);
23323 auto const t
= traceForClass(name
);
23324 always_assert_flog(
23325 t
, "{} is on input bad classes, but has no tracking state",
23328 input
.meta
.classBuckets
.emplace(name
, t
->buckets
);
23329 assertx(t
->buckets
.present
->contains(i
));
23332 for (auto const name
: input
.meta
.badFuncs
) {
23333 if (auto const b
= folly::get_ptr(untracked
.funcs
, name
)) {
23334 assertx(!traceForFunc(name
));
23335 assertx(b
->present
->contains(i
));
23336 always_assert(input
.meta
.funcBuckets
.emplace(name
, *b
).second
);
23337 } else if (!input
.meta
.funcBuckets
.count(name
)) {
23338 auto const t
= traceForFunc(name
);
23339 always_assert_flog(
23340 t
, "{} is on input bad funcs, but has no tracking state",
23344 input
.meta
.funcBuckets
.emplace(name
, t
->buckets
).second
23348 for (auto const name
: input
.meta
.badConstants
) {
23349 if (auto const b
= folly::get_ptr(untracked
.badConstants
, name
)) {
23350 assertx(!traceForConstant(name
));
23351 assertx(b
->present
->contains(i
));
23352 always_assert(input
.meta
.badConstantBuckets
.emplace(name
, *b
).second
);
23354 auto const t
= traceForConstant(name
);
23355 always_assert_flog(
23356 t
, "{} is on input bad constants, but has no tracking state",
23360 input
.meta
.badConstantBuckets
.emplace(name
, t
->buckets
).second
23362 assertx(t
->buckets
.present
->contains(i
));
23366 bucket
.classes
.clear();
23367 bucket
.deps
.clear();
23373 // Ninth pass: Strictly debug. Check various invariants.
23374 void AnalysisScheduler::tracePass9(const std::vector
<AnalysisInput
>& inputs
) {
23375 if (!debug
) return;
23377 TSStringToOneT
<size_t> classes
;
23378 FSStringToOneT
<size_t> funcs
;
23379 SStringToOneT
<size_t> units
;
23381 // Every class/func/unit on an AnalysisInput should only be one
23383 for (auto const& i
: inputs
) {
23384 for (auto const& [n
, _
] : i
.classes
) {
23385 auto const [it
, e
] = classes
.try_emplace(n
, i
.meta
.bucketIdx
);
23386 always_assert_flog(
23388 "Class {} is in both bucket {} and {}!\n",
23389 n
, it
->second
, i
.meta
.bucketIdx
23392 for (auto const& [n
, _
] : i
.funcs
) {
23393 auto const [it
, e
] = funcs
.try_emplace(n
, i
.meta
.bucketIdx
);
23395 always_assert_flog(
23397 "Func {} is in both bucket {} and {}!\n",
23398 n
, it
->second
, i
.meta
.bucketIdx
23401 for (auto const& [n
, _
] : i
.units
) {
23402 auto const [it
, e
] = units
.try_emplace(n
, i
.meta
.bucketIdx
);
23404 always_assert_flog(
23406 "Unit {} is in both bucket {} and {}!\n",
23407 n
, it
->second
, i
.meta
.bucketIdx
23411 // Dep class/funcs/units can be in multiple AnalysisInputs, but
23412 // can't appear more than once in the same AnalysisInput.
23413 for (auto const& [n
, _
] : i
.depClasses
) {
23414 always_assert_flog(
23415 !i
.classes
.count(n
),
23416 "Class {} is in both classes and depClasses in bucket {}\n",
23417 n
, i
.meta
.bucketIdx
23420 for (auto const& [n
, _
] : i
.depFuncs
) {
23421 always_assert_flog(
23423 "Func {} is in both funcs and depFuncs in bucket {}\n",
23424 n
, i
.meta
.bucketIdx
23427 for (auto const& [n
, _
] : i
.depUnits
) {
23428 always_assert_flog(
23430 "Unit {} is in both units and depUnits in bucket {}\n",
23431 n
, i
.meta
.bucketIdx
23435 // Ditto for "bad" classes or funcs.
23436 for (auto const n
: i
.meta
.badClasses
) {
23437 always_assert_flog(
23438 !i
.classes
.count(n
),
23439 "Class {} is in both classes and badClasses in bucket {}\n",
23440 n
, i
.meta
.bucketIdx
23442 always_assert_flog(
23443 !i
.depClasses
.count(n
),
23444 "Class {} is in both depClasses and badClasses in bucket {}\n",
23445 n
, i
.meta
.bucketIdx
23448 for (auto const n
: i
.meta
.badFuncs
) {
23449 always_assert_flog(
23451 "Func {} is in both funcs and badFuncs in bucket {}\n",
23452 n
, i
.meta
.bucketIdx
23454 always_assert_flog(
23455 !i
.depFuncs
.count(n
),
23456 "Func {} is in both depFuncs and badFuncs in bucket {}\n",
23457 n
, i
.meta
.bucketIdx
23463 // Group the work that needs to run into buckets of the given size.
23464 std::vector
<AnalysisInput
> AnalysisScheduler::schedule(size_t bucketSize
,
23465 size_t maxBucketSize
) {
23466 FTRACE(2, "AnalysisScheduler: scheduling {} items into buckets of "
23467 "size {} (max {})\n",
23468 workItems(), bucketSize
, maxBucketSize
);
23474 auto leafs
= tracePass4();
23475 auto buckets
= tracePass5(bucketSize
, maxBucketSize
, std::move(leafs
));
23476 auto inputs
= tracePass6(buckets
);
23477 tracePass7(inputs
);
23478 tracePass8(std::move(buckets
), inputs
.inputs
);
23479 tracePass9(inputs
.inputs
);
23481 totalWorkItems
.store(0);
23482 FTRACE(2, "AnalysisScheduler: scheduled {} buckets\n", inputs
.inputs
.size());
23484 return std::move(inputs
.inputs
);
23487 //////////////////////////////////////////////////////////////////////
23491 //////////////////////////////////////////////////////////////////////
23493 // If we optimized a top-level constant's value to a scalar, we no
23494 // longer need the associated 86cinit function. This fixes up the
23495 // metadata to remove it.
23496 FSStringSet
strip_unneeded_constant_inits(AnalysisIndex::IndexData
& index
) {
23497 FSStringSet stripped
;
23499 for (auto const name
: index
.outFuncNames
) {
23500 auto const cnsName
= Constant::nameFromFuncName(name
);
23501 if (!cnsName
) continue;
23502 auto const it
= index
.constants
.find(cnsName
);
23503 if (it
== end(index
.constants
)) continue;
23504 auto const& cns
= *it
->second
.first
;
23505 if (type(cns
.val
) == KindOfUninit
) continue;
23506 stripped
.emplace(name
);
23508 if (stripped
.empty()) return stripped
;
23510 for (auto const name
: stripped
) {
23511 index
.deps
->getChanges().remove(*index
.funcs
.at(name
));
23512 index
.funcs
.erase(name
);
23513 index
.finfos
.erase(name
);
23516 index
.outFuncNames
.erase(
23518 begin(index
.outFuncNames
), end(index
.outFuncNames
),
23519 [&] (SString f
) { return stripped
.count(f
); }
23521 end(index
.outFuncNames
)
23524 for (auto& [_
, unit
] : index
.units
) {
23527 begin(unit
->funcs
), end(unit
->funcs
),
23528 [&, unit
=unit
] (SString f
) {
23529 if (!stripped
.count(f
)) return false;
23531 index
.deps
->bucketFor(unit
).process
->contains(index
.bucketIdx
)
23543 // Record the new set of classes which this class has inherited
23544 // constants from. This set can change (always shrink) due to
23545 // optimizations rewriting constants.
23546 TSStringSet
record_cns_bases(const php::Class
& cls
,
23547 const AnalysisIndex::IndexData
& index
) {
23549 if (!cls
.cinfo
) return out
;
23550 for (auto const& [_
, idx
] : cls
.cinfo
->clsConstants
) {
23551 if (!cls
.name
->tsame(idx
.idx
.cls
)) out
.emplace(idx
.idx
.cls
);
23552 if (auto const cnsCls
= folly::get_default(index
.classes
, idx
.idx
.cls
)) {
23553 assertx(idx
.idx
.idx
< cnsCls
->constants
.size());
23554 auto const& cns
= cnsCls
->constants
[idx
.idx
.idx
];
23555 if (!cls
.name
->tsame(cns
.cls
)) out
.emplace(cns
.cls
);
23561 // Mark all "fixed" class constants. A class constant is fixed if it's
23562 // value can't change going forward. This transforms any dependency on
23563 // that constant from a transitive one (that gets put on the trace) to
23564 // a strict dependency (which only goes on the dep list).
23565 void mark_fixed_class_constants(const php::Class
& cls
,
23566 AnalysisIndex::IndexData
& index
) {
23567 auto& changes
= index
.deps
->getChanges();
23570 for (size_t i
= 0, size
= cls
.constants
.size(); i
< size
; ++i
) {
23571 auto const& cns
= cls
.constants
[i
];
23572 auto const fixed
= [&] {
23573 if (!cns
.val
) return true;
23574 if (cns
.kind
== ConstModifiers::Kind::Type
) {
23575 // A type-constant is fixed if it's been resolved.
23576 return cns
.resolvedTypeStructure
&& cns
.contextInsensitive
;
23577 } else if (cns
.kind
== ConstModifiers::Kind::Value
) {
23578 // A normal constant is fixed if it's a scalar.
23579 return type(*cns
.val
) != KindOfUninit
;
23581 // Anything else never changes.
23586 changes
.fixed(ConstIndex
{ cls
.name
, (unsigned int)i
});
23591 if (all
) changes
.fixed(cls
);
23593 // While we're at it, record all the names present in this class'
23594 // type-constants. This will be used in forming dependencies.
23595 for (auto const& [_
, idx
] :
23596 index
.index
.lookup_flattened_class_type_constants(cls
)) {
23597 auto const cinfo
= folly::get_default(index
.cinfos
, idx
.cls
);
23598 if (!cinfo
) continue;
23599 assertx(idx
.idx
< cinfo
->cls
->constants
.size());
23600 auto const& cns
= cinfo
->cls
->constants
[idx
.idx
];
23601 if (cns
.kind
!= ConstModifiers::Kind::Type
) continue;
23602 if (!cns
.val
.has_value()) continue;
23603 auto const ts
= [&] () -> SArray
{
23604 if (cns
.resolvedTypeStructure
&&
23605 (cns
.contextInsensitive
|| cls
.name
->tsame(cns
.cls
))) {
23606 return cns
.resolvedTypeStructure
;
23608 assertx(tvIsDict(*cns
.val
));
23609 return val(*cns
.val
).parr
;
23611 auto const name
= type_structure_name(ts
);
23612 if (!name
) continue;
23613 changes
.typeCnsName(cls
, AnalysisChangeSet::Class
{ name
});
23617 // Mark whether this unit is "fixed". An unit is fixed if all the
23618 // type-aliases in it are resolved.
23619 void mark_fixed_unit(const php::Unit
& unit
,
23620 AnalysisChangeSet
& changes
) {
23622 for (auto const& ta
: unit
.typeAliases
) {
23623 if (!ta
->resolvedTypeStructure
) all
= false;
23624 auto const ts
= [&] {
23625 if (ta
->resolvedTypeStructure
) return ta
->resolvedTypeStructure
;
23626 return ta
->typeStructure
;
23628 auto const name
= type_structure_name(ts
);
23629 if (!name
) continue;
23630 changes
.typeCnsName(unit
, AnalysisChangeSet::Class
{ name
});
23632 if (all
) changes
.fixed(unit
);
23635 //////////////////////////////////////////////////////////////////////
23639 //////////////////////////////////////////////////////////////////////
23641 AnalysisIndex::AnalysisIndex(
23642 AnalysisWorklist
& worklist
,
23643 VU
<php::Class
> classes
,
23644 VU
<php::Func
> funcs
,
23645 VU
<php::Unit
> units
,
23646 VU
<php::ClassBytecode
> clsBC
,
23647 VU
<php::FuncBytecode
> funcBC
,
23648 V
<AnalysisIndexCInfo
> cinfos
,
23649 V
<AnalysisIndexFInfo
> finfos
,
23650 V
<AnalysisIndexMInfo
> minfos
,
23651 VU
<php::Class
> depClasses
,
23652 VU
<php::Func
> depFuncs
,
23653 VU
<php::Unit
> depUnits
,
23654 AnalysisInput::Meta meta
,
23656 ) : m_data
{std::make_unique
<IndexData
>(*this, worklist
, mode
)}
23658 m_data
->bucketIdx
= meta
.bucketIdx
;
23660 m_data
->badClasses
= std::move(meta
.badClasses
);
23661 m_data
->badFuncs
= std::move(meta
.badFuncs
);
23662 m_data
->badConstants
= std::move(meta
.badConstants
);
23664 std::vector
<SString
> depClassNames
;
23666 m_data
->outClassNames
.reserve(classes
.size());
23667 m_data
->allClasses
.reserve(classes
.size() + depClasses
.size());
23668 depClassNames
.reserve(depClasses
.size());
23669 for (auto& cls
: classes
) {
23670 for (auto& clo
: cls
->closures
) {
23672 m_data
->classes
.emplace(clo
->name
, clo
.get()).second
23675 auto const name
= cls
->name
;
23676 always_assert(m_data
->classes
.emplace(name
, cls
.get()).second
);
23677 m_data
->outClassNames
.emplace_back(name
);
23678 m_data
->allClasses
.emplace(name
, std::move(cls
));
23680 for (auto& cls
: depClasses
) {
23681 for (auto& clo
: cls
->closures
) {
23683 m_data
->classes
.emplace(clo
->name
, clo
.get()).second
23686 auto const name
= cls
->name
;
23687 always_assert(m_data
->classes
.emplace(name
, cls
.get()).second
);
23688 depClassNames
.emplace_back(name
);
23689 m_data
->allClasses
.emplace(name
, std::move(cls
));
23692 std::vector
<SString
> depFuncNames
;
23694 m_data
->outFuncNames
.reserve(funcs
.size());
23695 m_data
->allFuncs
.reserve(funcs
.size() + depFuncs
.size());
23696 m_data
->funcs
.reserve(funcs
.size() + depFuncs
.size());
23697 depFuncNames
.reserve(depFuncs
.size());
23698 for (auto& func
: funcs
) {
23699 auto const name
= func
->name
;
23700 always_assert(m_data
->funcs
.emplace(name
, func
.get()).second
);
23701 m_data
->outFuncNames
.emplace_back(name
);
23702 m_data
->allFuncs
.emplace(name
, std::move(func
));
23704 for (auto& func
: depFuncs
) {
23705 auto const name
= func
->name
;
23706 always_assert(m_data
->funcs
.emplace(name
, func
.get()).second
);
23707 depFuncNames
.emplace_back(name
);
23708 m_data
->allFuncs
.emplace(name
, std::move(func
));
23711 auto const assignFInfoIdx
= [&] (php::Func
& func
, FuncInfo2
& finfo
) {
23712 always_assert(func
.idx
== std::numeric_limits
<uint32_t>::max());
23713 always_assert(!finfo
.func
);
23714 finfo
.func
= &func
;
23715 func
.idx
= m_data
->finfosByIdx
.size();
23716 m_data
->finfosByIdx
.emplace_back(&finfo
);
23719 auto const addCInfo
= [&] (ClassInfo2
* cinfo
) {
23720 auto cls
= folly::get_default(m_data
->classes
, cinfo
->name
);
23721 always_assert(cls
);
23722 always_assert(!cls
->cinfo
);
23723 always_assert(!cinfo
->cls
);
23724 cls
->cinfo
= cinfo
;
23727 auto const numMethods
= cls
->methods
.size();
23728 always_assert(cinfo
->funcInfos
.size() == numMethods
);
23729 for (size_t i
= 0; i
< numMethods
; ++i
) {
23730 assignFInfoIdx(*cls
->methods
[i
], *cinfo
->funcInfos
[i
]);
23732 always_assert(m_data
->cinfos
.emplace(cinfo
->name
, cinfo
).second
);
23735 m_data
->allCInfos
.reserve(cinfos
.size());
23736 for (auto& wrapper
: cinfos
) {
23737 for (auto& clo
: wrapper
.ptr
->closures
) addCInfo(clo
.get());
23738 addCInfo(wrapper
.ptr
.get());
23739 auto const name
= wrapper
.ptr
->name
;
23740 m_data
->allCInfos
.emplace(name
, wrapper
.ptr
.release());
23743 m_data
->finfos
.reserve(finfos
.size());
23744 m_data
->allFInfos
.reserve(finfos
.size());
23745 for (auto& wrapper
: finfos
) {
23746 auto func
= folly::get_default(m_data
->funcs
, wrapper
.ptr
->name
);
23747 always_assert(func
);
23748 assignFInfoIdx(*func
, *wrapper
.ptr
);
23749 auto const name
= wrapper
.ptr
->name
;
23750 always_assert(m_data
->finfos
.emplace(name
, wrapper
.ptr
.get()).second
);
23751 m_data
->allFInfos
.emplace(name
, wrapper
.ptr
.release());
23754 m_data
->minfos
.reserve(minfos
.size());
23755 m_data
->allMInfos
.reserve(minfos
.size());
23756 for (auto& wrapper
: minfos
) {
23757 auto const minfo
= wrapper
.ptr
.get();
23758 auto cls
= folly::get_default(m_data
->classes
, minfo
->cls
);
23759 always_assert(cls
);
23760 always_assert(!cls
->cinfo
);
23762 auto const numMethods
= cls
->methods
.size();
23763 always_assert(minfo
->finfos
.size() == numMethods
);
23764 for (size_t i
= 0; i
< numMethods
; ++i
) {
23765 assignFInfoIdx(*cls
->methods
[i
], *minfo
->finfos
[i
]);
23767 auto const numClosures
= cls
->closures
.size();
23768 always_assert(minfo
->closureInvokes
.size() == numClosures
);
23769 for (size_t i
= 0; i
< numClosures
; ++i
) {
23770 auto& clo
= cls
->closures
[i
];
23771 auto& finfo
= minfo
->closureInvokes
[i
];
23772 assertx(clo
->methods
.size() == 1);
23773 assignFInfoIdx(*clo
->methods
[0], *finfo
);
23776 auto const name
= minfo
->cls
;
23777 always_assert(m_data
->minfos
.emplace(name
, minfo
).second
);
23778 m_data
->allMInfos
.emplace(name
, wrapper
.ptr
.release());
23781 for (auto& bc
: clsBC
) {
23782 auto cls
= folly::get_default(m_data
->classes
, bc
->cls
);
23783 always_assert(cls
);
23786 for (auto& meth
: cls
->methods
) {
23787 assertx(idx
< bc
->methodBCs
.size());
23788 auto& methBC
= bc
->methodBCs
[idx
++];
23789 always_assert(methBC
.name
== meth
->name
);
23790 meth
->rawBlocks
= std::move(methBC
.bc
);
23792 for (auto& clo
: cls
->closures
) {
23793 assertx(idx
< bc
->methodBCs
.size());
23794 auto& methBC
= bc
->methodBCs
[idx
++];
23795 assertx(clo
->methods
.size() == 1);
23796 always_assert(methBC
.name
== clo
->methods
[0]->name
);
23797 clo
->methods
[0]->rawBlocks
= std::move(methBC
.bc
);
23799 assertx(idx
== bc
->methodBCs
.size());
23801 for (auto& bc
: funcBC
) {
23802 auto func
= folly::get_default(m_data
->funcs
, bc
->name
);
23803 always_assert(func
);
23804 func
->rawBlocks
= std::move(bc
->bc
);
23807 std::vector
<SString
> depUnitNames
;
23809 m_data
->outUnitNames
.reserve(units
.size());
23810 m_data
->units
.reserve(units
.size() + depUnits
.size());
23811 m_data
->allUnits
.reserve(units
.size() + depUnits
.size());
23812 depUnitNames
.reserve(depUnits
.size());
23814 auto const addUnit
= [&] (php::Unit
* unit
) {
23815 auto const isNative
= is_native_unit(*unit
);
23816 for (auto& cns
: unit
->constants
) {
23818 m_data
->constants
.try_emplace(cns
->name
, cns
.get(), unit
).second
23820 assertx(!m_data
->badConstants
.count(cns
->name
));
23821 if (isNative
&& type(cns
->val
) == KindOfUninit
) {
23822 m_data
->dynamicConstants
.emplace(cns
->name
);
23825 for (auto& typeAlias
: unit
->typeAliases
) {
23827 m_data
->typeAliases
.try_emplace(
23833 assertx(!m_data
->badClasses
.count(typeAlias
->name
));
23835 always_assert(m_data
->units
.emplace(unit
->filename
, unit
).second
);
23837 for (auto& unit
: units
) {
23838 addUnit(unit
.get());
23839 auto const name
= unit
->filename
;
23840 m_data
->outUnitNames
.emplace_back(name
);
23841 m_data
->allUnits
.emplace(name
, std::move(unit
));
23843 for (auto& unit
: depUnits
) {
23844 addUnit(unit
.get());
23845 auto const name
= unit
->filename
;
23846 m_data
->allUnits
.emplace(name
, std::move(unit
));
23847 depUnitNames
.emplace_back(name
);
23850 for (auto const& [_
, cls
] : m_data
->allClasses
) {
23851 if (!cls
->cinfo
) continue;
23852 always_assert(cls
.get() == cls
->cinfo
->cls
);
23854 for (auto const& [_
, cinfo
]: m_data
->allCInfos
) {
23855 always_assert(cinfo
->cls
);
23856 always_assert(cinfo
.get() == cinfo
->cls
->cinfo
);
23858 for (size_t i
= 0, size
= m_data
->finfosByIdx
.size(); i
< size
; ++i
) {
23859 auto finfo
= m_data
->finfosByIdx
[i
];
23860 always_assert(finfo
->func
);
23861 always_assert(finfo
->func
->idx
== i
);
23864 ClassGraph::setAnalysisIndex(*m_data
);
23866 // Use the BucketSet information in the input metadata to set up the
23868 for (auto& [n
, b
] : meta
.classBuckets
) {
23869 if (auto const c
= folly::get_default(m_data
->classes
, n
)) {
23870 m_data
->deps
->restrict(c
, std::move(b
));
23871 } else if (m_data
->badClasses
.count(n
)) {
23872 m_data
->deps
->restrict(DepTracker::Class
{ n
}, std::move(b
));
23875 for (auto& [n
, b
] : meta
.funcBuckets
) {
23876 if (auto const f
= folly::get_default(m_data
->funcs
, n
)) {
23877 m_data
->deps
->restrict(f
, std::move(b
));
23878 } else if (m_data
->badFuncs
.count(n
)) {
23879 m_data
->deps
->restrict(DepTracker::Func
{ n
}, std::move(b
));
23882 for (auto& [n
, b
] : meta
.unitBuckets
) {
23883 if (auto const u
= folly::get_default(m_data
->units
, n
)) {
23884 m_data
->deps
->restrict(u
, std::move(b
));
23887 for (auto& [n
, b
] : meta
.badConstantBuckets
) {
23888 assertx(m_data
->badConstants
.count(n
));
23889 m_data
->deps
->restrict(DepTracker::Constant
{ n
}, std::move(b
));
23892 initialize_worklist(
23894 std::move(depClassNames
),
23895 std::move(depFuncNames
),
23896 std::move(depUnitNames
)
23900 AnalysisIndex::~AnalysisIndex() {
23901 ClassGraph::clearAnalysisIndex();
23904 // Initialize the worklist with the items we know we must
23905 // process. Also add dependency information for the items which *may*
23907 void AnalysisIndex::initialize_worklist(const AnalysisInput::Meta
& meta
,
23908 std::vector
<SString
> depClasses
,
23909 std::vector
<SString
> depFuncs
,
23910 std::vector
<SString
> depUnits
) {
23911 auto const add
= [&] (FuncClsUnit src
, const AnalysisDeps
& deps
) {
23912 for (auto const [name
, t
] : deps
.funcs
) {
23913 m_data
->deps
->preadd(src
, DepTracker::Func
{ name
}, t
);
23915 for (auto const [meth
, t
] : deps
.methods
) {
23916 m_data
->deps
->preadd(src
, meth
, t
);
23918 for (auto const cns
: deps
.clsConstants
) {
23919 m_data
->deps
->preadd(src
, cns
);
23921 for (auto const cls
: deps
.anyClsConstants
) {
23922 m_data
->deps
->preadd(src
, DepTracker::AnyClassConstant
{ cls
});
23924 for (auto const cns
: deps
.constants
) {
23925 m_data
->deps
->preadd(src
, DepTracker::Constant
{ cns
});
23929 for (auto const name
: m_data
->outClassNames
) {
23930 auto const cls
= m_data
->classes
.at(name
);
23931 if (is_closure(*cls
)) {
23932 assertx(!cls
->closureContextCls
);
23933 assertx(cls
->closureDeclFunc
);
23934 assertx(!meta
.classDeps
.count(name
));
23937 assertx(m_data
->deps
->bucketFor(cls
).process
->contains(m_data
->bucketIdx
));
23938 if (auto const deps
= folly::get_ptr(meta
.classDeps
, name
)) {
23941 m_data
->worklist
.schedule(cls
);
23945 for (auto const name
: depClasses
) {
23946 if (auto const deps
= folly::get_ptr(meta
.classDeps
, name
)) {
23948 m_data
->deps
->bucketFor(m_data
->classes
.at(name
))
23949 .process
->contains(m_data
->bucketIdx
)
23951 add(m_data
->classes
.at(name
), *deps
);
23952 } else if (meta
.processDepCls
.count(name
)) {
23954 m_data
->deps
->bucketFor(m_data
->classes
.at(name
))
23955 .process
->contains(m_data
->bucketIdx
)
23957 m_data
->worklist
.schedule(m_data
->classes
.at(name
));
23961 for (auto const name
: m_data
->outFuncNames
) {
23962 auto const func
= m_data
->funcs
.at(name
);
23963 assertx(m_data
->deps
->bucketFor(func
).process
->contains(m_data
->bucketIdx
));
23964 if (auto const deps
= folly::get_ptr(meta
.funcDeps
, name
)) {
23967 m_data
->worklist
.schedule(func
);
23971 for (auto const name
: depFuncs
) {
23972 if (auto const deps
= folly::get_ptr(meta
.funcDeps
, name
)) {
23974 m_data
->deps
->bucketFor(m_data
->funcs
.at(name
))
23975 .process
->contains(m_data
->bucketIdx
)
23977 add(m_data
->funcs
.at(name
), *deps
);
23978 } else if (meta
.processDepFunc
.count(name
)) {
23980 m_data
->deps
->bucketFor(m_data
->funcs
.at(name
))
23981 .process
->contains(m_data
->bucketIdx
)
23983 m_data
->worklist
.schedule(m_data
->funcs
.at(name
));
23987 for (auto const name
: m_data
->outUnitNames
) {
23988 auto const unit
= m_data
->units
.at(name
);
23990 m_data
->deps
->bucketFor(unit
).process
->contains(m_data
->bucketIdx
)
23992 if (auto const deps
= folly::get_ptr(meta
.unitDeps
, name
)) {
23995 m_data
->worklist
.schedule(unit
);
23999 for (auto const name
: depUnits
) {
24000 if (auto const deps
= folly::get_ptr(meta
.unitDeps
, name
)) {
24002 m_data
->deps
->bucketFor(m_data
->units
.at(name
))
24003 .process
->contains(m_data
->bucketIdx
)
24005 add(m_data
->units
.at(name
), *deps
);
24006 } else if (meta
.processDepUnit
.count(name
)) {
24008 m_data
->deps
->bucketFor(m_data
->units
.at(name
))
24009 .process
->contains(m_data
->bucketIdx
)
24011 m_data
->worklist
.schedule(m_data
->units
.at(name
));
24016 void AnalysisIndex::start() { ClassGraph::init(); }
24017 void AnalysisIndex::stop() { ClassGraph::destroy(); }
24019 void AnalysisIndex::push_context(const Context
& ctx
) {
24020 m_data
->contexts
.emplace_back(ctx
);
24023 void AnalysisIndex::pop_context() {
24024 assertx(!m_data
->contexts
.empty());
24025 m_data
->contexts
.pop_back();
24028 bool AnalysisIndex::set_in_type_cns(bool b
) {
24029 auto const was
= m_data
->inTypeCns
;
24030 m_data
->inTypeCns
= b
;
24034 void AnalysisIndex::freeze() {
24035 FTRACE(2, "Freezing index...\n");
24036 assertx(!m_data
->frozen
);
24037 m_data
->frozen
= true;
24039 // We're going to do a final set of analysis to record dependencies,
24040 // so we must re-add the original set of items to the worklist.
24041 assertx(!m_data
->worklist
.next());
24042 for (auto const name
: m_data
->outClassNames
) {
24043 auto const cls
= m_data
->classes
.at(name
);
24044 if (is_closure(*cls
)) continue;
24045 m_data
->deps
->reset(cls
);
24046 m_data
->worklist
.schedule(cls
);
24048 for (auto const name
: m_data
->outFuncNames
) {
24049 auto const func
= m_data
->funcs
.at(name
);
24050 m_data
->deps
->reset(func
);
24051 m_data
->worklist
.schedule(func
);
24053 for (auto const name
: m_data
->outUnitNames
) {
24054 auto const unit
= m_data
->units
.at(name
);
24055 m_data
->deps
->reset(unit
);
24056 m_data
->worklist
.schedule(unit
);
24060 bool AnalysisIndex::frozen() const { return m_data
->frozen
; }
24062 const php::Unit
& AnalysisIndex::lookup_func_unit(const php::Func
& f
) const {
24063 auto const it
= m_data
->units
.find(f
.unit
);
24064 always_assert_flog(
24065 it
!= end(m_data
->units
),
24066 "Attempting to access missing unit {} for func {}",
24067 f
.unit
, func_fullname(f
)
24069 return *it
->second
;
24072 const php::Unit
& AnalysisIndex::lookup_class_unit(const php::Class
& c
) const {
24073 auto const it
= m_data
->units
.find(c
.unit
);
24074 always_assert_flog(
24075 it
!= end(m_data
->units
),
24076 "Attempting to access missing unit {} for class {}",
24079 return *it
->second
;
24082 const php::Unit
& AnalysisIndex::lookup_unit(SString n
) const {
24083 auto const it
= m_data
->units
.find(n
);
24084 always_assert_flog(
24085 it
!= end(m_data
->units
),
24086 "Attempting to access missing unit {}",
24089 return *it
->second
;
24093 AnalysisIndex::lookup_const_class(const php::Const
& cns
) const {
24094 if (!m_data
->deps
->add(AnalysisDeps::Class
{ cns
.cls
})) return nullptr;
24095 return folly::get_default(m_data
->classes
, cns
.cls
);
24098 const php::Class
* AnalysisIndex::lookup_class(SString name
) const {
24099 return folly::get_default(m_data
->classes
, name
);
24103 AnalysisIndex::lookup_closure_context(const php::Class
& cls
) const {
24104 always_assert_flog(
24105 !cls
.closureContextCls
,
24106 "AnalysisIndex does not yet support closure contexts (for {})",
24112 res::Func
AnalysisIndex::resolve_func(SString n
) const {
24113 n
= normalizeNS(n
);
24114 if (m_data
->mode
== Mode::Constants
) {
24115 return res::Func
{ res::Func::FuncName
{ n
} };
24118 if (m_data
->deps
->add(AnalysisDeps::Func
{ n
})) {
24119 if (auto const finfo
= folly::get_default(m_data
->finfos
, n
)) {
24120 return res::Func
{ res::Func::Fun2
{ finfo
} };
24122 if (m_data
->badFuncs
.count(n
)) {
24123 return res::Func
{ res::Func::MissingFunc
{ n
} };
24126 return res::Func
{ res::Func::FuncName
{ n
} };
24129 Optional
<res::Class
> AnalysisIndex::resolve_class(SString n
) const {
24130 n
= normalizeNS(n
);
24131 if (!m_data
->deps
->add(AnalysisDeps::Class
{ n
})) {
24132 // If we can't use information about the class, there's no point
24133 // in checking, and just use the unknown case.
24134 return res::Class::getOrCreate(n
);
24136 if (auto const cinfo
= folly::get_default(m_data
->cinfos
, n
)) {
24137 return res::Class::get(*cinfo
);
24139 if (m_data
->typeAliases
.count(n
)) return std::nullopt
;
24140 // A php::Class should always be accompanied by it's ClassInfo,
24141 // unless if it's uninstantiable. So, if we have a php::Class here,
24142 // we know it's uninstantiable.
24143 if (m_data
->badClasses
.count(n
) || m_data
->classes
.count(n
)) {
24144 return std::nullopt
;
24146 return res::Class::getOrCreate(n
);
24149 Optional
<res::Class
> AnalysisIndex::resolve_class(const php::Class
& cls
) const {
24150 if (!m_data
->deps
->add(AnalysisDeps::Class
{ cls
.name
})) {
24151 return res::Class::getOrCreate(cls
.name
);
24153 if (cls
.cinfo
) return res::Class::get(*cls
.cinfo
);
24154 return std::nullopt
;
24157 res::Func
AnalysisIndex::resolve_func_or_method(const php::Func
& f
) const {
24158 if (!m_data
->deps
->add(f
)) {
24160 return res::Func
{ res::Func::FuncName
{ f
.name
} };
24162 return res::Func
{ res::Func::MethodName
{ f
.cls
->name
, f
.name
} };
24164 if (!f
.cls
) return res::Func
{ res::Func::Fun2
{ &func_info(*m_data
, f
) } };
24165 return res::Func
{ res::Func::Method2
{ &func_info(*m_data
, f
) } };
24168 Type
AnalysisIndex::lookup_constant(SString name
) const {
24169 if (!m_data
->deps
->add(AnalysisDeps::Constant
{ name
})) return TInitCell
;
24171 if (auto const p
= folly::get_ptr(m_data
->constants
, name
)) {
24172 auto const cns
= p
->first
;
24173 if (type(cns
->val
) != KindOfUninit
) return from_cell(cns
->val
);
24174 if (m_data
->dynamicConstants
.count(name
)) return TInitCell
;
24175 auto const fname
= Constant::funcNameFromName(name
);
24176 auto const fit
= m_data
->finfos
.find(fname
);
24177 // We might have the unit present by chance, but without an
24178 // explicit dependence on the constant, we might not have the init
24180 if (fit
== end(m_data
->finfos
)) return TInitCell
;
24181 return unctx(unserialize_type(fit
->second
->returnTy
));
24183 return m_data
->badConstants
.count(name
) ? TBottom
: TInitCell
;
24186 std::vector
<std::pair
<SString
, ConstIndex
>>
24187 AnalysisIndex::lookup_flattened_class_type_constants(
24188 const php::Class
& cls
24190 std::vector
<std::pair
<SString
, ConstIndex
>> out
;
24192 auto const cinfo
= cls
.cinfo
;
24193 if (!cinfo
) return out
;
24195 out
.reserve(cinfo
->clsConstants
.size());
24196 for (auto const& [name
, idx
] : cinfo
->clsConstants
) {
24197 if (idx
.kind
!= ConstModifiers::Kind::Type
) continue;
24198 out
.emplace_back(name
, idx
.idx
);
24201 begin(out
), end(out
),
24202 [] (auto const& p1
, auto const& p2
) {
24203 return string_data_lt
{}(p1
.first
, p2
.first
);
24209 std::vector
<std::pair
<SString
, ClsConstInfo
>>
24210 AnalysisIndex::lookup_class_constants(const php::Class
& cls
) const {
24211 std::vector
<std::pair
<SString
, ClsConstInfo
>> out
;
24212 out
.reserve(cls
.constants
.size());
24213 for (auto const& cns
: cls
.constants
) {
24214 if (cns
.kind
!= ConstModifiers::Kind::Value
) continue;
24215 if (!cns
.val
) continue;
24216 if (cns
.val
->m_type
!= KindOfUninit
) {
24217 out
.emplace_back(cns
.name
, ClsConstInfo
{ from_cell(*cns
.val
), 0 });
24218 } else if (!cls
.cinfo
) {
24219 out
.emplace_back(cns
.name
, ClsConstInfo
{ TInitCell
, 0 });
24221 auto info
= folly::get_default(
24222 cls
.cinfo
->clsConstantInfo
,
24224 ClsConstInfo
{ TInitCell
, 0 }
24226 info
.type
= unserialize_type(std::move(info
.type
));
24227 out
.emplace_back(cns
.name
, std::move(info
));
24233 ClsConstLookupResult
24234 AnalysisIndex::lookup_class_constant(const Type
& cls
,
24235 const Type
& name
) const {
24236 ITRACE(4, "lookup_class_constant: ({}) {}::{}\n",
24237 show(current_context(*m_data
)), show(cls
), show(name
));
24240 AnalysisIndexAdaptor adaptor
{*this};
24241 InTypeCns _2
{adaptor
, false};
24243 using R
= ClsConstLookupResult
;
24245 auto const conservative
= [] {
24246 ITRACE(4, "conservative\n");
24247 return R
{ TInitCell
, TriBool::Maybe
, true };
24250 auto const notFound
= [] {
24251 ITRACE(4, "not found\n");
24252 return R
{ TBottom
, TriBool::No
, false };
24255 if (!is_specialized_cls(cls
)) return conservative();
24257 // We could easily support the case where we don't know the constant
24258 // name, but know the class (like we do for properties), by unioning
24259 // together all possible constants. However it very rarely happens,
24260 // but when it does, the set of constants to union together can be
24261 // huge and it becomes very expensive.
24262 if (!is_specialized_string(name
)) return conservative();
24263 auto const sname
= sval_of(name
);
24265 auto const& dcls
= dcls_of(cls
);
24266 if (dcls
.isExact()) {
24267 auto const rcls
= dcls
.cls();
24268 auto const cinfo
= rcls
.cinfo2();
24269 if (!cinfo
|| !m_data
->deps
->add(AnalysisDeps::Class
{ rcls
.name() })) {
24270 (void)m_data
->deps
->add(AnalysisDeps::AnyClassConstant
{ rcls
.name() });
24271 return conservative();
24274 ITRACE(4, "{}:\n", cinfo
->name
);
24277 auto const idxIt
= cinfo
->clsConstants
.find(sname
);
24278 if (idxIt
== end(cinfo
->clsConstants
)) return notFound();
24279 auto const& idx
= idxIt
->second
;
24281 assertx(!m_data
->badClasses
.count(idx
.idx
.cls
));
24282 if (idx
.kind
!= ConstModifiers::Kind::Value
) return notFound();
24284 if (!m_data
->deps
->add(idx
.idx
)) return conservative();
24286 auto const cnsClsIt
= m_data
->classes
.find(idx
.idx
.cls
);
24287 if (cnsClsIt
== end(m_data
->classes
)) return conservative();
24288 auto const& cnsCls
= cnsClsIt
->second
;
24290 assertx(idx
.idx
.idx
< cnsCls
->constants
.size());
24291 auto const& cns
= cnsCls
->constants
[idx
.idx
.idx
];
24292 assertx(cns
.kind
== ConstModifiers::Kind::Value
);
24294 if (!cns
.val
.has_value()) return notFound();
24296 auto const r
= [&] {
24297 if (type(*cns
.val
) != KindOfUninit
) {
24298 // Fully resolved constant with a known value. We don't need
24299 // to register a dependency on the constant because the value
24300 // will never change.
24301 auto mightThrow
= bool(cinfo
->cls
->attrs
& AttrInternal
);
24303 auto const& unit
= lookup_class_unit(*cinfo
->cls
);
24304 auto const moduleName
= unit
.moduleName
;
24305 auto const packageInfo
= unit
.packageInfo
;
24306 if (auto const activeDeployment
= packageInfo
.getActiveDeployment()) {
24307 if (!packageInfo
.moduleInDeployment(
24308 moduleName
, *activeDeployment
, DeployKind::Hard
)) {
24313 return R
{ from_cell(*cns
.val
), TriBool::Yes
, mightThrow
};
24316 ITRACE(4, "(dynamic)\n");
24317 if (!cnsCls
->cinfo
) return conservative();
24319 folly::get_ptr(cnsCls
->cinfo
->clsConstantInfo
, cns
.name
);
24321 info
? unserialize_type(info
->type
) : TInitCell
,
24326 ITRACE(4, "-> {}\n", show(r
));
24330 // Subclasses not yet implemented
24331 return conservative();
24334 ClsTypeConstLookupResult
24335 AnalysisIndex::lookup_class_type_constant(
24338 const Index::ClsTypeConstLookupResolver
& resolver
24340 ITRACE(4, "lookup_class_type_constant: ({}) {}::{}\n",
24341 show(current_context(*m_data
)), show(cls
), show(name
));
24344 using R
= ClsTypeConstLookupResult
;
24346 auto const conservative
= [&] (SString n
= nullptr) {
24347 ITRACE(4, "conservative\n");
24349 TypeStructureResolution
{ TSDictN
, true },
24355 auto const notFound
= [] {
24356 ITRACE(4, "not found\n");
24358 TypeStructureResolution
{ TBottom
, false },
24364 // Unlike lookup_class_constant, we distinguish abstract from
24365 // not-found, as the runtime sometimes treats them differently.
24366 auto const abstract
= [] {
24367 ITRACE(4, "abstract\n");
24369 TypeStructureResolution
{ TBottom
, false },
24375 if (!is_specialized_cls(cls
)) return conservative();
24377 // As in lookup_class_constant, we could handle this, but it's not
24379 if (!is_specialized_string(name
)) return conservative();
24380 auto const sname
= sval_of(name
);
24382 auto const& dcls
= dcls_of(cls
);
24383 if (dcls
.isExact()) {
24384 auto const rcls
= dcls
.cls();
24385 auto const cinfo
= rcls
.cinfo2();
24386 if (!cinfo
|| !m_data
->deps
->add(AnalysisDeps::Class
{ rcls
.name() })) {
24387 (void)m_data
->deps
->add(AnalysisDeps::AnyClassConstant
{ rcls
.name() });
24388 return conservative(rcls
.name());
24391 ITRACE(4, "{}:\n", cinfo
->name
);
24394 auto const idxIt
= cinfo
->clsConstants
.find(sname
);
24395 if (idxIt
== end(cinfo
->clsConstants
)) return notFound();
24396 auto const& idx
= idxIt
->second
;
24398 assertx(!m_data
->badClasses
.count(idx
.idx
.cls
));
24399 if (idx
.kind
!= ConstModifiers::Kind::Type
) return notFound();
24401 if (!m_data
->deps
->add(idx
.idx
)) return conservative(rcls
.name());
24403 auto const cnsClsIt
= m_data
->classes
.find(idx
.idx
.cls
);
24404 if (cnsClsIt
== end(m_data
->classes
)) return conservative(rcls
.name());
24405 auto const& cnsCls
= cnsClsIt
->second
;
24407 assertx(idx
.idx
.idx
< cnsCls
->constants
.size());
24408 auto const& cns
= cnsCls
->constants
[idx
.idx
.idx
];
24409 assertx(cns
.kind
== ConstModifiers::Kind::Type
);
24410 if (!cns
.val
.has_value()) return abstract();
24411 assertx(tvIsDict(*cns
.val
));
24413 ITRACE(4, "({}) {}\n", cns
.cls
, show(dict_val(val(*cns
.val
).parr
)));
24415 // If we've been given a resolver, use it. Otherwise resolve it in
24417 auto resolved
= resolver
24418 ? resolver(cns
, *cinfo
->cls
)
24419 : resolve_type_structure(
24420 AnalysisIndexAdaptor
{ *this }, cns
, *cinfo
->cls
24423 // The result of resolve_type_structure isn't, in general,
24424 // static. However a type-constant will always be, so force that
24426 assertx(resolved
.type
.is(BBottom
) || resolved
.type
.couldBe(BUnc
));
24427 resolved
.type
&= TUnc
;
24429 std::move(resolved
),
24433 ITRACE(4, "-> {}\n", show(r
));
24437 // Subclasses not yet implemented
24438 return conservative();
24441 ClsTypeConstLookupResult
24442 AnalysisIndex::lookup_class_type_constant(const php::Class
& ctx
,
24444 ConstIndex idx
) const {
24445 ITRACE(4, "lookup_class_type_constant: ({}) {}::{} (from {})\n",
24446 show(current_context(*m_data
)),
24448 show(idx
, AnalysisIndexAdaptor
{ *this }));
24451 assertx(!m_data
->badClasses
.count(idx
.cls
));
24453 using R
= ClsTypeConstLookupResult
;
24455 auto const conservative
= [] {
24456 ITRACE(4, "conservative\n");
24458 TypeStructureResolution
{ TSDictN
, true },
24464 auto const notFound
= [] {
24465 ITRACE(4, "not found\n");
24467 TypeStructureResolution
{ TBottom
, false },
24473 // Unlike lookup_class_constant, we distinguish abstract from
24474 // not-found, as the runtime sometimes treats them differently.
24475 auto const abstract
= [] {
24476 ITRACE(4, "abstract\n");
24478 TypeStructureResolution
{ TBottom
, false },
24484 if (!m_data
->deps
->add(idx
)) return conservative();
24485 auto const cinfo
= folly::get_default(m_data
->cinfos
, idx
.cls
);
24486 if (!cinfo
) return conservative();
24488 assertx(idx
.idx
< cinfo
->cls
->constants
.size());
24489 auto const& cns
= cinfo
->cls
->constants
[idx
.idx
];
24490 if (cns
.kind
!= ConstModifiers::Kind::Type
) return notFound();
24491 if (!cns
.val
.has_value()) return abstract();
24493 assertx(tvIsDict(*cns
.val
));
24495 ITRACE(4, "({}) {}\n", cns
.cls
, show(dict_val(val(*cns
.val
).parr
)));
24497 auto resolved
= resolve_type_structure(
24498 AnalysisIndexAdaptor
{ *this }, cns
, ctx
24501 // The result of resolve_type_structure isn't, in general,
24502 // static. However a type-constant will always be, so force that
24504 assertx(resolved
.type
.is(BBottom
) || resolved
.type
.couldBe(BUnc
));
24505 resolved
.type
&= TUnc
;
24507 std::move(resolved
),
24511 ITRACE(4, "-> {}\n", show(r
));
24515 PropState
AnalysisIndex::lookup_private_props(const php::Class
& cls
) const {
24516 // Private property tracking not yet implemented, so be
24518 return make_unknown_propstate(
24519 AnalysisIndexAdaptor
{ *this },
24521 [&] (const php::Prop
& prop
) {
24522 return (prop
.attrs
& AttrPrivate
) && !(prop
.attrs
& AttrStatic
);
24527 PropState
AnalysisIndex::lookup_private_statics(const php::Class
& cls
) const {
24528 // Private static property tracking not yet implemented, so be
24530 return make_unknown_propstate(
24531 AnalysisIndexAdaptor
{ *this },
24533 [&] (const php::Prop
& prop
) {
24534 return (prop
.attrs
& AttrPrivate
) && (prop
.attrs
& AttrStatic
);
24539 Index::ReturnType
AnalysisIndex::lookup_return_type(MethodsInfo
* methods
,
24540 res::Func rfunc
) const {
24541 using R
= Index::ReturnType
;
24543 auto const meth
= [&] (const FuncInfo2
& finfo
) {
24545 if (auto ret
= methods
->lookupReturnType(*finfo
.func
)) {
24546 return R
{ unctx(std::move(ret
->t
)), ret
->effectFree
};
24549 if (!m_data
->deps
->add(*finfo
.func
, AnalysisDeps::Type::RetType
)) {
24550 return R
{ TInitCell
, false };
24553 unctx(unserialize_type(finfo
.returnTy
)),
24560 [&] (res::Func::FuncName f
) {
24561 (void)m_data
->deps
->add(
24562 AnalysisDeps::Func
{ f
.name
},
24563 AnalysisDeps::Type::RetType
24565 return R
{ TInitCell
, false };
24567 [&] (res::Func::MethodName m
) {
24568 // If we know the name of the class, we can register a
24569 // dependency on it. If not, nothing we can do.
24570 if (m
.cls
) (void)m_data
->deps
->add(AnalysisDeps::Class
{ m
.cls
});
24571 return R
{ TInitCell
, false };
24573 [&] (res::Func::Fun
) -> R
{ always_assert(false); },
24574 [&] (res::Func::Method
) -> R
{ always_assert(false); },
24575 [&] (res::Func::MethodFamily
) -> R
{ always_assert(false); },
24576 [&] (res::Func::MethodOrMissing
) -> R
{ always_assert(false); },
24577 [&] (res::Func::MissingFunc
) { return R
{ TBottom
, false }; },
24578 [&] (res::Func::MissingMethod
) { return R
{ TBottom
, false }; },
24579 [&] (const res::Func::Isect
&) -> R
{ always_assert(false); },
24580 [&] (res::Func::Fun2 f
) {
24581 if (!m_data
->deps
->add(*f
.finfo
->func
, AnalysisDeps::Type::RetType
)) {
24582 return R
{ TInitCell
, false };
24585 unctx(unserialize_type(f
.finfo
->returnTy
)),
24586 f
.finfo
->effectFree
24589 [&] (res::Func::Method2 m
) { return meth(*m
.finfo
); },
24590 [&] (res::Func::MethodFamily2
) -> R
{ always_assert(false); },
24591 [&] (res::Func::MethodOrMissing2 m
) { return meth(*m
.finfo
); },
24592 [&] (const res::Func::Isect2
&) -> R
{ always_assert(false); }
24597 AnalysisIndex::lookup_return_type(MethodsInfo
* methods
,
24598 const CompactVector
<Type
>& args
,
24599 const Type
& context
,
24600 res::Func rfunc
) const {
24601 using R
= Index::ReturnType
;
24603 auto ty
= lookup_return_type(methods
, rfunc
);
24605 auto const contextual
= [&] (const FuncInfo2
& finfo
) {
24606 return context_sensitive_return_type(
24608 { finfo
.func
, args
, context
},
24615 [&] (res::Func::FuncName
) { return ty
; },
24616 [&] (res::Func::MethodName
) { return ty
; },
24617 [&] (res::Func::Fun
) -> R
{ always_assert(false); },
24618 [&] (res::Func::Method
) -> R
{ always_assert(false); },
24619 [&] (res::Func::MethodFamily
) -> R
{ always_assert(false); },
24620 [&] (res::Func::MethodOrMissing
) -> R
{ always_assert(false); },
24621 [&] (res::Func::MissingFunc
) { return R
{ TBottom
, false }; },
24622 [&] (res::Func::MissingMethod
) { return R
{ TBottom
, false }; },
24623 [&] (const res::Func::Isect
&) -> R
{ always_assert(false); },
24624 [&] (res::Func::Fun2 f
) { return contextual(*f
.finfo
); },
24625 [&] (res::Func::Method2 m
) { return contextual(*m
.finfo
); },
24626 [&] (res::Func::MethodFamily2
) -> R
{ always_assert(false); },
24627 [&] (res::Func::MethodOrMissing2 m
) { return contextual(*m
.finfo
); },
24628 [&] (const res::Func::Isect2
&) -> R
{ always_assert(false); }
24633 AnalysisIndex::lookup_foldable_return_type(const CallContext
& calleeCtx
) const {
24634 constexpr size_t maxNestingLevel
= 2;
24636 using R
= Index::ReturnType
;
24638 auto const& func
= *calleeCtx
.callee
;
24640 if (m_data
->mode
== Mode::Constants
) {
24643 "Skipping inline interp of {} because analyzing constants\n",
24644 func_fullname(func
)
24646 return R
{ TInitCell
, false };
24649 auto const ctxType
=
24650 adjust_closure_context(AnalysisIndexAdaptor
{ *this }, calleeCtx
);
24652 // Don't fold functions when staticness mismatches
24653 if (!func
.isClosureBody
) {
24654 if ((func
.attrs
& AttrStatic
) && ctxType
.couldBe(TObj
)) {
24655 return R
{ TInitCell
, false };
24657 if (!(func
.attrs
& AttrStatic
) && ctxType
.couldBe(TCls
)) {
24658 return R
{ TInitCell
, false };
24662 auto const& finfo
= func_info(*m_data
, func
);
24663 // No need to call unserialize_type here. If it's a scalar, there's
24664 // nothing to unserialize anyways.
24665 if (finfo
.effectFree
&& is_scalar(finfo
.returnTy
)) {
24666 return R
{ finfo
.returnTy
, finfo
.effectFree
};
24669 auto const& caller
= *context_for_deps(*m_data
).func
;
24671 if (!m_data
->deps
->add(
24673 AnalysisDeps::Type::ScalarRetType
|
24674 AnalysisDeps::Type::Bytecode
24676 return R
{ TInitCell
, false };
24678 if (m_data
->foldableInterpNestingLevel
> maxNestingLevel
) {
24679 return R
{ TInitCell
, false };
24682 if (!func
.rawBlocks
) {
24685 "Skipping inline interp of {} because bytecode not present\n",
24686 func_fullname(func
)
24688 return R
{ TInitCell
, false };
24691 auto const contextualRet
= [&] () -> Optional
<Type
> {
24692 ++m_data
->foldableInterpNestingLevel
;
24693 SCOPE_EXIT
{ --m_data
->foldableInterpNestingLevel
; };
24695 auto const wf
= php::WideFunc::cns(&func
);
24696 auto const fa
= analyze_func_inline(
24697 AnalysisIndexAdaptor
{ *this },
24702 &context_for_deps(*m_data
)
24707 CollectionOpts::EffectFreeOnly
24709 if (!fa
.effectFree
) return std::nullopt
;
24710 return fa
.inferredReturn
;
24713 if (!contextualRet
) {
24716 "Foldable inline analysis failed due to possible side-effects\n"
24718 return R
{ TInitCell
, false };
24723 "Foldable return type: {}\n",
24724 show(*contextualRet
)
24727 auto const error_context
= [&] {
24728 using namespace folly::gen
;
24729 return folly::sformat(
24730 "{} calling {} (context: {}, args: {})",
24731 func_fullname(caller
),
24732 func_fullname(func
),
24733 show(calleeCtx
.context
),
24734 from(calleeCtx
.args
)
24735 | map([] (const Type
& t
) { return show(t
); })
24736 | unsplit
<std::string
>(",")
24740 auto const insensitive
= unserialize_type(finfo
.returnTy
);
24741 always_assert_flog(
24742 contextualRet
->subtypeOf(insensitive
),
24743 "Context sensitive return type for {} is {} "
24744 "which not at least as refined as context insensitive "
24745 "return type {}\n",
24747 show(*contextualRet
),
24750 if (!is_scalar(*contextualRet
)) return R
{ TInitCell
, false };
24752 return R
{ *contextualRet
, true };
24755 std::pair
<Index::ReturnType
, size_t>
24756 AnalysisIndex::lookup_return_type_raw(const php::Func
& f
) const {
24757 auto const& finfo
= func_info(*m_data
, f
);
24758 return std::make_pair(
24760 unserialize_type(finfo
.returnTy
),
24763 finfo
.returnRefinements
24767 bool AnalysisIndex::func_depends_on_arg(const php::Func
& func
,
24768 size_t arg
) const {
24769 if (!m_data
->deps
->add(func
, AnalysisDeps::Type::UnusedParams
)) return true;
24770 auto const& finfo
= func_info(*m_data
, func
);
24771 return arg
>= finfo
.unusedParams
.size() || !finfo
.unusedParams
.test(arg
);
24775 * Given a DCls, return the most specific res::Func for that DCls. For
24776 * intersections, this will call process/general on every component of
24777 * the intersection and combine the results. process is called to
24778 * obtain a res::Func from a ClassInfo. If a ClassInfo isn't
24779 * available, general will be called instead.
24781 template <typename P
, typename G
>
24782 res::Func
AnalysisIndex::rfunc_from_dcls(const DCls
& dcls
,
24785 const G
& general
) const {
24786 using Func
= res::Func
;
24787 using Class
= res::Class
;
24790 * Combine together multiple res::Funcs together. Since the DCls
24791 * represents a class which is a subtype of every ClassInfo in the
24792 * list, every res::Func we get is true.
24794 * The relevant res::Func types in order from most general to more
24797 * MethodName -> FuncFamily -> MethodOrMissing -> Method -> Missing
24799 * Since every res::Func in the intersection is true, we take the
24800 * res::Func which is most specific. Two different res::Funcs cannot
24801 * be contradict. For example, we shouldn't get a Method and a
24802 * Missing since one implies there's no func and the other implies
24803 * one specific func. Or two different res::Funcs shouldn't resolve
24804 * to two different methods.
24806 auto missing
= TriBool::Maybe
;
24807 Func::Isect2 isect
;
24808 const php::Func
* singleMethod
= nullptr;
24810 auto const onFunc
= [&] (Func func
) {
24813 [&] (Func::MethodName
) {},
24814 [&] (Func::Method
) { always_assert(false); },
24815 [&] (Func::MethodFamily
) { always_assert(false); },
24816 [&] (Func::MethodOrMissing
) { always_assert(false); },
24817 [&] (Func::MissingMethod
) {
24818 assertx(missing
!= TriBool::No
);
24819 singleMethod
= nullptr;
24820 isect
.families
.clear();
24821 missing
= TriBool::Yes
;
24823 [&] (Func::FuncName
) { always_assert(false); },
24824 [&] (Func::Fun
) { always_assert(false); },
24825 [&] (Func::Fun2
) { always_assert(false); },
24826 [&] (Func::Method2 m
) {
24827 assertx(IMPLIES(singleMethod
, singleMethod
== m
.finfo
->func
));
24828 assertx(IMPLIES(singleMethod
, isect
.families
.empty()));
24829 assertx(missing
!= TriBool::Yes
);
24830 if (!singleMethod
) {
24831 singleMethod
= m
.finfo
->func
;
24832 isect
.families
.clear();
24834 missing
= TriBool::No
;
24836 [&] (Func::MethodFamily2
) { always_assert(false); },
24837 [&] (Func::MethodOrMissing2 m
) {
24838 assertx(IMPLIES(singleMethod
, singleMethod
== m
.finfo
->func
));
24839 assertx(IMPLIES(singleMethod
, isect
.families
.empty()));
24840 if (missing
== TriBool::Yes
) {
24841 assertx(!singleMethod
);
24842 assertx(isect
.families
.empty());
24845 if (!singleMethod
) {
24846 singleMethod
= m
.finfo
->func
;
24847 isect
.families
.clear();
24850 [&] (Func::MissingFunc
) { always_assert(false); },
24851 [&] (const Func::Isect
&) { always_assert(false); },
24852 [&] (const Func::Isect2
&) { always_assert(false); }
24856 auto const onClass
= [&] (Class cls
, bool isExact
) {
24857 auto const g
= cls
.graph();
24858 if (!g
.ensureCInfo()) {
24859 onFunc(general(dcls
.containsNonRegular()));
24863 if (auto const cinfo
= g
.cinfo2()) {
24864 onFunc(process(cinfo
, isExact
, dcls
.containsNonRegular()));
24866 // The class doesn't have a ClassInfo present, so we cannot call
24867 // process. We can, however, look at any parents that do have a
24868 // ClassInfo. This won't result in as good results, but it
24869 // preserves monotonicity.
24870 onFunc(general(dcls
.containsNonRegular()));
24871 if (g
.isMissing()) return;
24873 if (!dcls
.containsNonRegular() &&
24874 !g
.mightBeRegular() &&
24875 g
.hasCompleteChildren()) {
24878 Func
{ Func::MissingMethod
{ dcls
.smallestCls().name(), name
} }
24883 hphp_fast_set
<ClassGraph
, ClassGraphHasher
> commonParents
;
24885 for (auto const c
: g
.children()) {
24886 assertx(!c
.isMissing());
24887 if (!c
.mightBeRegular()) continue;
24889 hphp_fast_set
<ClassGraph
, ClassGraphHasher
> newCommon
;
24891 [&] (ClassGraph p
) {
24892 if (first
|| commonParents
.count(p
)) {
24893 newCommon
.emplace(p
);
24899 commonParents
= std::move(newCommon
);
24904 Func
{ Func::MissingMethod
{ dcls
.smallestCls().name(), name
} }
24909 assertx(!commonParents
.empty());
24910 for (auto const p
: commonParents
) {
24911 if (!p
.ensureCInfo()) continue;
24912 if (auto const cinfo
= p
.cinfo2()) {
24913 onFunc(process(cinfo
, false, false));
24920 [&] (ClassGraph p
) {
24921 if (!p
.ensureCInfo()) return true;
24922 if (auto const cinfo
= p
.cinfo2()) {
24923 onFunc(process(cinfo
, false, dcls
.containsNonRegular()));
24932 if (dcls
.isExact() || dcls
.isSub()) {
24933 // If this isn't an intersection, there's only one class to
24934 // process and we're done.
24935 onClass(dcls
.cls(), dcls
.isExact());
24936 } else if (dcls
.isIsect()) {
24937 for (auto const c
: dcls
.isect()) onClass(c
, false);
24939 assertx(dcls
.isIsectAndExact());
24940 auto const [e
, i
] = dcls
.isectAndExact();
24942 for (auto const c
: *i
) onClass(c
, false);
24945 // If we got a method, that always wins. Again, every res::Func is
24946 // true, and method is more specific than a FuncFamily, so it is
24948 if (singleMethod
) {
24949 assertx(missing
!= TriBool::Yes
);
24950 // If missing is Maybe, then *every* resolution was to a
24951 // MethodName or MethodOrMissing, so include that fact here by
24952 // using MethodOrMissing.
24953 if (missing
== TriBool::Maybe
) {
24955 Func::MethodOrMissing2
{ &func_info(*m_data
, *singleMethod
) }
24958 return Func
{ Func::Method2
{ &func_info(*m_data
, *singleMethod
) } };
24960 // We only got unresolved classes. If missing is TriBool::Yes, the
24961 // function doesn't exist. Otherwise be pessimistic.
24962 if (isect
.families
.empty()) {
24963 if (missing
== TriBool::Yes
) {
24964 return Func
{ Func::MissingMethod
{ dcls
.smallestCls().name(), name
} };
24966 assertx(missing
== TriBool::Maybe
);
24967 return general(dcls
.containsNonRegular());
24969 // Isect case. Isects always might contain missing funcs.
24970 assertx(missing
== TriBool::Maybe
);
24972 // We could add a FuncFamily multiple times, so remove duplicates.
24973 std::sort(begin(isect
.families
), end(isect
.families
));
24974 isect
.families
.erase(
24975 std::unique(begin(isect
.families
), end(isect
.families
)),
24976 end(isect
.families
)
24978 // If everything simplifies down to a single FuncFamily, just use
24980 if (isect
.families
.size() == 1) {
24982 Func::MethodFamily2
{ isect
.families
[0], isect
.regularOnly
}
24985 return Func
{ std::move(isect
) };
24988 res::Func
AnalysisIndex::resolve_method(const Type
& thisType
,
24989 SString name
) const {
24990 assertx(thisType
.subtypeOf(BCls
) || thisType
.subtypeOf(BObj
));
24992 using Func
= res::Func
;
24994 auto const general
= [&] (SString maybeCls
, bool) {
24995 assertx(name
!= s_construct
.get());
24996 return Func
{ Func::MethodName
{ maybeCls
, name
} };
24999 if (m_data
->mode
== Mode::Constants
) return general(nullptr, true);
25001 auto const process
= [&] (ClassInfo2
* cinfo
,
25003 bool includeNonRegular
) {
25004 assertx(name
!= s_construct
.get());
25006 auto const meth
= folly::get_ptr(cinfo
->methods
, name
);
25008 // We don't store metadata for special methods, so be pessimistic
25009 // (the lack of a method entry does not mean the call might fail
25011 if (is_special_method_name(name
)) {
25012 return Func
{ Func::MethodName
{ cinfo
->name
, name
} };
25014 // We're only considering this class, not it's subclasses. Since
25015 // it doesn't exist here, the resolution will always fail.
25017 return Func
{ Func::MissingMethod
{ cinfo
->name
, name
} };
25019 // The method isn't present on this class, but it might be in the
25020 // subclasses. In most cases try a general lookup to get a
25021 // slightly better type than nothing.
25022 return general(cinfo
->name
, includeNonRegular
);
25025 if (!m_data
->deps
->add(meth
->meth())) {
25026 return general(cinfo
->name
, includeNonRegular
);
25028 auto const func
= func_from_meth_ref(*m_data
, meth
->meth());
25029 if (!func
) return general(cinfo
->name
, includeNonRegular
);
25031 // We don't store method family information about special methods
25032 // and they have special inheritance semantics.
25033 if (is_special_method_name(name
)) {
25034 // If we know the class exactly, we can use ftarget.
25036 return Func
{ Func::Method2
{ &func_info(*m_data
, *func
) } };
25038 // The method isn't overwritten, but they don't inherit, so it
25039 // could be missing.
25040 if (meth
->attrs
& AttrNoOverride
) {
25041 return Func
{ Func::MethodOrMissing2
{ &func_info(*m_data
, *func
) } };
25043 // Otherwise be pessimistic.
25044 return Func
{ Func::MethodName
{ cinfo
->name
, name
} };
25047 // Private method handling: Private methods have special lookup
25048 // rules. If we're in the context of a particular class, and that
25049 // class defines a private method, an instance of the class will
25050 // always call that private method (even if overridden) in that
25052 assertx(cinfo
->cls
);
25053 auto const& ctx
= current_context(*m_data
);
25054 if (ctx
.cls
== cinfo
->cls
) {
25055 // The context matches the current class. If we've looked up a
25056 // private method (defined on this class), then that's what
25058 if ((meth
->attrs
& AttrPrivate
) && meth
->topLevel()) {
25059 return Func
{ Func::Method2
{ &func_info(*m_data
, *func
) } };
25061 } else if ((meth
->attrs
& AttrPrivate
) || meth
->hasPrivateAncestor()) {
25062 // Otherwise the context doesn't match the current class. If the
25063 // looked up method is private, or has a private ancestor,
25064 // there's a chance we'll call that method (or
25065 // ancestor). Otherwise there's no private method in the
25066 // inheritance tree we'll call.
25067 auto conservative
= false;
25068 auto const ancestor
= [&] () -> const php::Func
* {
25069 if (!ctx
.cls
) return nullptr;
25070 if (!m_data
->deps
->add(AnalysisDeps::Class
{ ctx
.cls
->name
})) {
25071 conservative
= true;
25074 // Look up the ClassInfo corresponding to the context.
25075 auto const ctxCInfo
= ctx
.cls
->cinfo
;
25076 if (!ctxCInfo
) return nullptr;
25077 // Is this context a parent of our class?
25078 if (!cinfo
->classGraph
.isChildOf(ctxCInfo
->classGraph
)) {
25081 // It is. See if it defines a private method.
25082 auto const ctxMeth
= folly::get_ptr(ctxCInfo
->methods
, name
);
25083 if (!ctxMeth
) return nullptr;
25084 // If it defines a private method, use it.
25085 if ((ctxMeth
->attrs
& AttrPrivate
) && ctxMeth
->topLevel()) {
25086 if (!m_data
->deps
->add(ctxMeth
->meth())) {
25087 conservative
= true;
25090 auto const ctxFunc
= func_from_meth_ref(*m_data
, ctxMeth
->meth());
25091 if (!ctxFunc
) conservative
= true;
25094 // Otherwise do normal lookup.
25098 return Func
{ Func::Method2
{ &func_info(*m_data
, *ancestor
) } };
25099 } else if (conservative
) {
25100 return Func
{ Func::MethodName
{ cinfo
->name
, name
} };
25104 // If we're only including regular subclasses, and this class
25105 // itself isn't regular, the result may not necessarily include
25107 if (!includeNonRegular
&& !is_regular_class(*cinfo
->cls
)) {
25108 // We're not including this base class. If we're exactly this
25109 // class, there's no method at all. It will always be missing.
25111 return Func
{ Func::MissingMethod
{ cinfo
->name
, name
} };
25113 if (meth
->noOverrideRegular()) {
25114 // The method isn't overridden in a subclass, but we can't use
25115 // the base class either. This leaves two cases. Either the
25116 // method isn't overridden because there are no regular
25117 // subclasses (in which case there's no resolution at all), or
25118 // because there's regular subclasses, but they use the same
25119 // method (in which case the result is just func).
25120 if (!cinfo
->classGraph
.mightHaveRegularSubclass()) {
25121 return Func
{ Func::MissingMethod
{ cinfo
->name
, name
} };
25123 return Func
{ Func::Method2
{ &func_info(*m_data
, *func
) } };
25125 } else if (isExact
||
25126 meth
->attrs
& AttrNoOverride
||
25127 (!includeNonRegular
&& meth
->noOverrideRegular())) {
25128 // Either we want all classes, or the base class is regular. If
25129 // the method isn't overridden we know it must be just func (the
25130 // override bits include it being missing in a subclass, so we
25131 // know it cannot be missing either).
25132 return Func
{ Func::Method2
{ &func_info(*m_data
, *func
) } };
25135 // Be pessimistic for the rest of cases
25136 return general(cinfo
->name
, includeNonRegular
);
25139 auto const isClass
= thisType
.subtypeOf(BCls
);
25140 if (name
== s_construct
.get()) {
25142 return Func
{ Func::MethodName
{ nullptr, s_construct
.get() } };
25144 return resolve_ctor(thisType
);
25148 if (!is_specialized_cls(thisType
)) return general(nullptr, true);
25149 } else if (!is_specialized_obj(thisType
)) {
25150 return general(nullptr, false);
25153 auto const& dcls
= isClass
? dcls_of(thisType
) : dobj_of(thisType
);
25154 return rfunc_from_dcls(
25158 [&] (bool i
) { return general(dcls
.smallestCls().name(), i
); }
25162 res::Func
AnalysisIndex::resolve_ctor(const Type
& obj
) const {
25163 assertx(obj
.subtypeOf(BObj
));
25165 using Func
= res::Func
;
25167 if (m_data
->mode
== Mode::Constants
) {
25168 return Func
{ Func::MethodName
{ nullptr, s_construct
.get() } };
25171 // Can't say anything useful if we don't know the object type.
25172 if (!is_specialized_obj(obj
)) {
25173 return Func
{ Func::MethodName
{ nullptr, s_construct
.get() } };
25176 auto const& dcls
= dobj_of(obj
);
25177 return rfunc_from_dcls(
25180 [&] (ClassInfo2
* cinfo
, bool isExact
, bool includeNonRegular
) {
25181 // We're dealing with an object here, which never uses
25182 // non-regular classes.
25183 assertx(!includeNonRegular
);
25185 // See if this class has a ctor.
25186 auto const meth
= folly::get_ptr(cinfo
->methods
, s_construct
.get());
25188 // There's no ctor on this class. This doesn't mean the ctor
25189 // won't exist at runtime, it might get the default ctor, so
25190 // we have to be conservative.
25191 return Func
{ Func::MethodName
{ cinfo
->name
, s_construct
.get() } };
25194 if (!m_data
->deps
->add(meth
->meth())) {
25196 Func::MethodName
{ dcls
.smallestCls().name(), s_construct
.get() }
25200 // We have a ctor, but it might be overridden in a subclass.
25201 assertx(!(meth
->attrs
& AttrStatic
));
25202 auto const func
= func_from_meth_ref(*m_data
, meth
->meth());
25204 // Relevant function doesn't exist on the AnalysisIndex. Be
25206 return Func
{ Func::MethodName
{ cinfo
->name
, s_construct
.get() } };
25208 assertx(!(func
->attrs
& AttrStatic
));
25210 // If this class is known exactly, or we know nothing overrides
25211 // this ctor, we know this ctor is precisely it.
25212 if (isExact
|| meth
->noOverrideRegular()) {
25213 // If this class isn't regular, and doesn't have any regular
25214 // subclasses (or if it's exact), this resolution will always
25216 if (!is_regular_class(*cinfo
->cls
) &&
25217 (isExact
|| !cinfo
->classGraph
.mightHaveRegularSubclass())) {
25219 Func::MissingMethod
{ cinfo
->name
, s_construct
.get() }
25222 return Func
{ Func::Method2
{ &func_info(*m_data
, *func
) } };
25225 // Be pessimistic for the rest of cases
25227 Func::MethodName
{ dcls
.smallestCls().name(), s_construct
.get() }
25230 [&] (bool includeNonRegular
) {
25231 assertx(!includeNonRegular
);
25233 Func::MethodName
{ dcls
.smallestCls().name(), s_construct
.get() }
25239 std::pair
<const php::TypeAlias
*, bool>
25240 AnalysisIndex::lookup_type_alias(SString name
) const {
25241 if (!m_data
->deps
->add(AnalysisDeps::Class
{ name
})) {
25242 return std::make_pair(nullptr, true);
25244 if (m_data
->classes
.count(name
)) return std::make_pair(nullptr, false);
25245 if (auto const ta
= folly::get_ptr(m_data
->typeAliases
, name
)) {
25246 return std::make_pair(ta
->first
, true);
25248 return std::make_pair(nullptr, !m_data
->badClasses
.count(name
));
25251 Index::ClassOrTypeAlias
25252 AnalysisIndex::lookup_class_or_type_alias(SString n
) const {
25253 n
= normalizeNS(n
);
25254 if (!m_data
->deps
->add(AnalysisDeps::Class
{ n
})) {
25255 return Index::ClassOrTypeAlias
{nullptr, nullptr, true};
25257 if (auto const cls
= folly::get_default(m_data
->classes
, n
)) {
25258 return Index::ClassOrTypeAlias
{cls
, nullptr, true};
25260 if (auto const ta
= folly::get_ptr(m_data
->typeAliases
, n
)) {
25261 return Index::ClassOrTypeAlias
{nullptr, ta
->first
, true};
25263 return Index::ClassOrTypeAlias
{
25266 !m_data
->badClasses
.count(n
)
25270 PropMergeResult
AnalysisIndex::merge_static_type(
25271 PublicSPropMutations
& publicMutations
,
25272 PropertiesInfo
& privateProps
,
25278 bool mustBeReadOnly
) const {
25279 // Not yet implemented
25280 return PropMergeResult
{ TInitCell
, TriBool::Maybe
};
25283 void AnalysisIndex::refine_constants(const FuncAnalysisResult
& fa
) {
25284 auto const& func
= *fa
.ctx
.func
;
25285 if (func
.cls
) return;
25287 auto const name
= Constant::nameFromFuncName(func
.name
);
25290 auto const cnsPtr
= folly::get_ptr(m_data
->constants
, name
);
25291 always_assert_flog(
25293 "Attempting to refine constant {} "
25294 "which we don't have meta-data for",
25297 auto const cns
= cnsPtr
->first
;
25298 auto const val
= tv(fa
.inferredReturn
);
25300 always_assert_flog(
25301 type(cns
->val
) == KindOfUninit
,
25302 "Constant value invariant violated in {}.\n"
25303 " Value went from {} to {}",
25305 show(from_cell(cns
->val
)),
25306 show(fa
.inferredReturn
)
25311 if (type(cns
->val
) != KindOfUninit
) {
25312 always_assert_flog(
25313 from_cell(cns
->val
) == fa
.inferredReturn
,
25314 "Constant value invariant violated in {}.\n"
25315 " Value went from {} to {}",
25317 show(from_cell(cns
->val
)),
25318 show(fa
.inferredReturn
)
25321 always_assert_flog(
25323 "Attempting to refine constant {} to {} when index is frozen",
25325 show(fa
.inferredReturn
)
25328 m_data
->deps
->update(*cns
);
25332 void AnalysisIndex::refine_class_constants(const FuncAnalysisResult
& fa
) {
25333 auto const resolved
= fa
.resolvedInitializers
.left();
25334 if (!resolved
|| resolved
->empty()) return;
25336 assertx(fa
.ctx
.func
->cls
);
25337 auto& constants
= fa
.ctx
.func
->cls
->constants
;
25339 for (auto const& c
: *resolved
) {
25340 assertx(c
.first
< constants
.size());
25341 auto& cns
= constants
[c
.first
];
25342 assertx(cns
.kind
== ConstModifiers::Kind::Value
);
25343 always_assert(cns
.val
.has_value());
25344 always_assert(type(*cns
.val
) == KindOfUninit
);
25346 auto cinfo
= fa
.ctx
.func
->cls
->cinfo
;
25347 if (auto const val
= tv(c
.second
.type
)) {
25348 assertx(type(*val
) != KindOfUninit
);
25349 always_assert_flog(
25351 "Attempting to refine class constant {}::{} to {} "
25352 "when index is frozen",
25353 fa
.ctx
.func
->cls
->name
,
25355 show(c
.second
.type
)
25358 if (cinfo
) cinfo
->clsConstantInfo
.erase(cns
.name
);
25359 m_data
->deps
->update(
25361 ConstIndex
{ fa
.ctx
.func
->cls
->name
, c
.first
}
25363 } else if (cinfo
) {
25364 auto old
= folly::get_default(
25365 cinfo
->clsConstantInfo
,
25367 ClsConstInfo
{ TInitCell
, 0 }
25369 old
.type
= unserialize_type(std::move(old
.type
));
25371 if (c
.second
.type
.strictlyMoreRefined(old
.type
)) {
25372 always_assert(c
.second
.refinements
> old
.refinements
);
25373 always_assert_flog(
25375 "Attempting to refine class constant {}::{} to {} "
25376 "when index is frozen",
25379 show(c
.second
.type
)
25381 cinfo
->clsConstantInfo
.insert_or_assign(cns
.name
, c
.second
);
25382 m_data
->deps
->update(cns
, ConstIndex
{ cinfo
->name
, c
.first
});
25384 always_assert_flog(
25385 c
.second
.type
.moreRefined(old
.type
),
25386 "Class constant type invariant violated for {}::{}\n"
25387 " {} is not at least as refined as {}\n",
25388 fa
.ctx
.func
->cls
->name
,
25390 show(c
.second
.type
),
25398 void AnalysisIndex::refine_return_info(const FuncAnalysisResult
& fa
) {
25399 auto const& func
= *fa
.ctx
.func
;
25400 auto& finfo
= func_info(*m_data
, func
);
25402 auto const error_loc
= [&] {
25403 return folly::sformat("{} {}", func
.unit
, func_fullname(func
));
25406 auto changes
= AnalysisDeps::Type::None
;
25408 if (finfo
.retParam
== NoLocalId
) {
25409 // This is just a heuristic; it doesn't mean that the value passed
25410 // in was returned, but that the value of the parameter at the
25411 // point of the RetC was returned. We use it to make (heuristic)
25412 // decisions about whether to do inline interps, so we only allow
25413 // it to change once. (otherwise later passes might not do the
25414 // inline interp, and get worse results, which breaks
25416 if (fa
.retParam
!= NoLocalId
) {
25417 finfo
.retParam
= fa
.retParam
;
25418 changes
|= AnalysisDeps::Type::RetParam
;
25421 always_assert_flog(
25422 finfo
.retParam
== fa
.retParam
,
25423 "Index return param invariant violated in {}.\n"
25424 " Went from {} to {}\n",
25431 auto const unusedParams
= ~fa
.usedParams
;
25432 if (finfo
.unusedParams
!= unusedParams
) {
25433 always_assert_flog(
25434 (finfo
.unusedParams
| unusedParams
) == unusedParams
,
25435 "Index unused params decreased in {}.\n",
25438 finfo
.unusedParams
= unusedParams
;
25439 changes
|= AnalysisDeps::Type::UnusedParams
;
25442 auto const oldReturnTy
= unserialize_type(finfo
.returnTy
);
25443 if (fa
.inferredReturn
.strictlyMoreRefined(oldReturnTy
)) {
25444 if (finfo
.returnRefinements
< options
.returnTypeRefineLimit
) {
25445 finfo
.returnTy
= fa
.inferredReturn
;
25446 finfo
.returnRefinements
+= fa
.localReturnRefinements
+ 1;
25447 if (finfo
.returnRefinements
> options
.returnTypeRefineLimit
) {
25448 FTRACE(1, "maxed out return type refinements at {}\n", error_loc());
25450 changes
|= AnalysisDeps::Type::RetType
;
25451 if (is_scalar(finfo
.returnTy
)) {
25452 changes
|= AnalysisDeps::Type::ScalarRetType
;
25455 FTRACE(1, "maxed out return type refinements at {}\n", error_loc());
25458 always_assert_flog(
25459 fa
.inferredReturn
.moreRefined(oldReturnTy
),
25460 "Index return type invariant violated in {}.\n"
25461 " {} is not at least as refined as {}\n",
25463 show(fa
.inferredReturn
),
25468 always_assert_flog(
25469 !finfo
.effectFree
|| fa
.effectFree
,
25470 "Index effect-free invariant violated in {}.\n"
25471 " Went from true to false\n",
25475 if (finfo
.effectFree
!= fa
.effectFree
) {
25476 finfo
.effectFree
= fa
.effectFree
;
25477 changes
|= AnalysisDeps::Type::RetType
;
25480 always_assert_flog(
25481 !m_data
->frozen
|| changes
== AnalysisDeps::Type::None
,
25482 "Attempting to refine return info for {} ({}) "
25483 "when index is frozen",
25488 if (changes
& AnalysisDeps::Type::RetType
) {
25489 if (auto const name
= Constant::nameFromFuncName(func
.name
)) {
25490 auto const cns
= folly::get_ptr(m_data
->constants
, name
);
25491 always_assert_flog(
25493 "Attempting to update constant {} type, but constant is not present!",
25496 m_data
->deps
->update(*cns
->first
);
25499 m_data
->deps
->update(func
, changes
);
25502 void AnalysisIndex::update_prop_initial_values(const FuncAnalysisResult
& fa
) {
25503 auto const resolved
= fa
.resolvedInitializers
.right();
25504 if (!resolved
|| resolved
->empty()) return;
25506 assertx(fa
.ctx
.cls
);
25507 auto& props
= const_cast<php::Class
*>(fa
.ctx
.cls
)->properties
;
25509 auto changed
= false;
25510 for (auto const& [idx
, info
] : *resolved
) {
25511 assertx(idx
< props
.size());
25512 auto& prop
= props
[idx
];
25514 if (info
.satisfies
) {
25515 if (!(prop
.attrs
& AttrInitialSatisfiesTC
)) {
25516 always_assert_flog(
25518 "Attempting to update AttrInitialSatisfiesTC for {}::{} "
25519 "when index is frozen",
25523 attribute_setter(prop
.attrs
, true, AttrInitialSatisfiesTC
);
25527 always_assert_flog(
25528 !(prop
.attrs
& AttrInitialSatisfiesTC
),
25529 "AttrInitialSatisfiesTC invariant violated for {}::{}\n"
25530 " Went from true to false",
25531 fa
.ctx
.cls
->name
, prop
.name
25535 always_assert_flog(
25536 IMPLIES(!(prop
.attrs
& AttrDeepInit
), !info
.deepInit
),
25537 "AttrDeepInit invariant violated for {}::{}\n"
25538 " Went from false to true",
25539 fa
.ctx
.cls
->name
, prop
.name
25541 if (bool(prop
.attrs
& AttrDeepInit
) != info
.deepInit
) {
25542 always_assert_flog(
25544 "Attempting to update AttrDeepInit for {}::{} "
25545 "when index is frozen",
25549 attribute_setter(prop
.attrs
, info
.deepInit
, AttrDeepInit
);
25552 if (type(info
.val
) != KindOfUninit
) {
25553 always_assert_flog(
25555 "Attempting to update property initial value for {}::{} "
25556 "to {} when index is frozen",
25559 show(from_cell(info
.val
))
25561 always_assert_flog(
25562 type(prop
.val
) == KindOfUninit
||
25563 from_cell(prop
.val
) == from_cell(info
.val
),
25564 "Property initial value invariant violated for {}::{}\n"
25565 " Value went from {} to {}",
25566 fa
.ctx
.cls
->name
, prop
.name
,
25567 show(from_cell(prop
.val
)), show(from_cell(info
.val
))
25569 prop
.val
= info
.val
;
25571 always_assert_flog(
25572 type(prop
.val
) == KindOfUninit
,
25573 "Property initial value invariant violated for {}::{}\n"
25574 " Value went from {} to not set",
25575 fa
.ctx
.cls
->name
, prop
.name
,
25576 show(from_cell(prop
.val
))
25580 if (!changed
) return;
25582 auto const cinfo
= fa
.ctx
.cls
->cinfo
;
25583 if (!cinfo
) return;
25585 assertx(cinfo
->hasBadInitialPropValues
);
25586 auto const noBad
= std::all_of(
25587 begin(props
), end(props
),
25588 [] (const php::Prop
& prop
) {
25589 return bool(prop
.attrs
& AttrInitialSatisfiesTC
);
25594 cinfo
->hasBadInitialPropValues
= false;
25598 void AnalysisIndex::update_type_consts(const ClassAnalysis
& analysis
) {
25599 if (analysis
.resolvedTypeConsts
.empty()) return;
25601 always_assert_flog(
25603 "Attempting to update type constants for {} when index is frozen",
25604 analysis
.ctx
.cls
->name
25607 auto const cls
= const_cast<php::Class
*>(analysis
.ctx
.cls
);
25608 auto const cinfo
= cls
->cinfo
;
25609 if (!cinfo
) return;
25611 for (auto const& update
: analysis
.resolvedTypeConsts
) {
25612 auto const srcCls
= folly::get_default(m_data
->classes
, update
.from
.cls
);
25614 assertx(update
.from
.idx
< srcCls
->constants
.size());
25616 auto& newCns
= [&] () -> php::Const
& {
25617 auto& srcCns
= srcCls
->constants
[update
.from
.idx
];
25618 if (srcCls
== cls
) {
25619 assertx(!srcCns
.resolvedTypeStructure
);
25622 cinfo
->clsConstants
[srcCns
.name
] =
25623 ClassInfo2::ConstIndexAndKind
{
25624 ConstIndex
{ cinfo
->name
, (ConstIndex::Idx
)cls
->constants
.size() },
25627 cls
->constants
.emplace_back(srcCns
);
25628 return cls
->constants
.back();
25631 newCns
.resolvedTypeStructure
= update
.resolved
;
25632 newCns
.contextInsensitive
= update
.contextInsensitive
;
25633 newCns
.resolvedLocally
= true;
25637 void AnalysisIndex::update_bytecode(FuncAnalysisResult
& fa
) {
25638 auto func
= php::WideFunc::mut(const_cast<php::Func
*>(fa
.ctx
.func
));
25639 auto const update
= HHBBC::update_bytecode(func
, std::move(fa
.blockUpdates
));
25640 if (update
== UpdateBCResult::None
) return;
25642 always_assert_flog(
25644 "Attempting to update bytecode for {} when index is frozen",
25645 func_fullname(*fa
.ctx
.func
)
25648 if (update
== UpdateBCResult::ChangedAnalyze
||
25649 fa
.ctx
.func
->name
== s_86cinit
.get()) {
25650 ITRACE(2, "Updated bytecode for {} in a way that requires re-analysis\n",
25651 func_fullname(*fa
.ctx
.func
));
25652 m_data
->worklist
.schedule(fc_from_context(fa
.ctx
, *m_data
));
25655 m_data
->deps
->update(*fa
.ctx
.func
, AnalysisDeps::Type::Bytecode
);
25658 void AnalysisIndex::update_type_aliases(const UnitAnalysis
& ua
) {
25659 assertx(ua
.ctx
.unit
);
25660 if (ua
.resolvedTypeAliases
.empty()) return;
25662 always_assert_flog(
25664 "Attempting to update type-aliases for unit {} when index is frozen\n",
25668 auto const& unit
= lookup_unit(ua
.ctx
.unit
);
25669 for (auto const& update
: ua
.resolvedTypeAliases
) {
25670 assertx(update
.idx
< unit
.typeAliases
.size());
25671 auto& ta
= *unit
.typeAliases
[update
.idx
];
25672 ta
.resolvedTypeStructure
= update
.resolved
;
25673 ta
.resolvedLocally
= true;
25677 // Finish using the AnalysisIndex and calculate the output to be
25678 // returned back from the job.
25679 AnalysisIndex::Output
AnalysisIndex::finish() {
25680 Variadic
<std::unique_ptr
<php::Class
>> classes
;
25681 Variadic
<std::unique_ptr
<php::Func
>> funcs
;
25682 Variadic
<std::unique_ptr
<php::Unit
>> units
;
25683 Variadic
<std::unique_ptr
<php::ClassBytecode
>> clsBC
;
25684 Variadic
<std::unique_ptr
<php::FuncBytecode
>> funcBC
;
25685 Variadic
<AnalysisIndexCInfo
> cinfos
;
25686 Variadic
<AnalysisIndexFInfo
> finfos
;
25687 Variadic
<AnalysisIndexMInfo
> minfos
;
25688 AnalysisOutput::Meta meta
;
25690 assertx(m_data
->frozen
);
25692 // Remove any 86cinits that are now unneeded.
25693 meta
.removedFuncs
= strip_unneeded_constant_inits(*m_data
);
25695 for (auto const name
: m_data
->outClassNames
) {
25696 auto const& cls
= m_data
->allClasses
.at(name
);
25697 mark_fixed_class_constants(*cls
, *m_data
);
25698 meta
.cnsBases
[cls
->name
] = record_cns_bases(*cls
, *m_data
);
25699 for (auto const& clo
: cls
->closures
) {
25700 mark_fixed_class_constants(*clo
, *m_data
);
25704 for (auto const name
: m_data
->outUnitNames
) {
25705 auto const& unit
= m_data
->allUnits
.at(name
);
25706 mark_fixed_unit(*unit
, m_data
->deps
->getChanges());
25709 auto const moveNewAuxs
= [&] (AuxClassGraphs
& auxs
) {
25710 auxs
.noChildren
= std::move(auxs
.newNoChildren
);
25711 auxs
.withChildren
= std::move(auxs
.newWithChildren
);
25714 TSStringSet outClasses
;
25716 classes
.vals
.reserve(m_data
->outClassNames
.size());
25717 clsBC
.vals
.reserve(m_data
->outClassNames
.size());
25718 cinfos
.vals
.reserve(m_data
->outClassNames
.size());
25719 meta
.classDeps
.reserve(m_data
->outClassNames
.size());
25720 for (auto const name
: m_data
->outClassNames
) {
25721 auto& cls
= m_data
->allClasses
.at(name
);
25723 outClasses
.emplace(name
);
25725 meta
.classDeps
.emplace_back(m_data
->deps
->take(cls
.get()));
25726 always_assert(IMPLIES(is_closure(*cls
), meta
.classDeps
.back().empty()));
25727 for (auto const& clo
: cls
->closures
) {
25728 assertx(m_data
->deps
->take(clo
.get()).empty());
25729 outClasses
.emplace(clo
->name
);
25732 clsBC
.vals
.emplace_back(
25733 std::make_unique
<php::ClassBytecode
>(cls
->name
)
25735 auto& bc
= *clsBC
.vals
.back();
25736 for (auto& meth
: cls
->methods
) {
25737 bc
.methodBCs
.emplace_back(meth
->name
, std::move(meth
->rawBlocks
));
25739 for (auto& clo
: cls
->closures
) {
25740 assertx(clo
->methods
.size() == 1);
25741 auto& meth
= clo
->methods
[0];
25742 bc
.methodBCs
.emplace_back(meth
->name
, std::move(meth
->rawBlocks
));
25745 if (auto cinfo
= folly::get_ptr(m_data
->allCInfos
, name
)) {
25746 moveNewAuxs(cinfo
->get()->auxClassGraphs
);
25748 AnalysisIndexCInfo acinfo
;
25749 acinfo
.ptr
= decltype(acinfo
.ptr
){cinfo
->release()};
25750 cinfos
.vals
.emplace_back(std::move(acinfo
));
25751 } else if (auto minfo
= folly::get_ptr(m_data
->allMInfos
, name
)) {
25752 AnalysisIndexMInfo aminfo
;
25753 aminfo
.ptr
= decltype(aminfo
.ptr
){minfo
->release()};
25754 minfos
.vals
.emplace_back(std::move(aminfo
));
25757 classes
.vals
.emplace_back(std::move(cls
));
25760 FSStringSet outFuncs
;
25761 outFuncs
.reserve(m_data
->outFuncNames
.size());
25763 funcs
.vals
.reserve(m_data
->outFuncNames
.size());
25764 funcBC
.vals
.reserve(m_data
->outFuncNames
.size());
25765 finfos
.vals
.reserve(m_data
->outFuncNames
.size());
25766 meta
.funcDeps
.reserve(m_data
->outFuncNames
.size());
25767 for (auto const name
: m_data
->outFuncNames
) {
25768 assertx(!meta
.removedFuncs
.count(name
));
25770 auto& func
= m_data
->allFuncs
.at(name
);
25771 auto& finfo
= m_data
->allFInfos
.at(name
);
25773 outFuncs
.emplace(name
);
25774 meta
.funcDeps
.emplace_back(m_data
->deps
->take(func
.get()));
25776 funcBC
.vals
.emplace_back(
25777 std::make_unique
<php::FuncBytecode
>(name
, std::move(func
->rawBlocks
))
25779 funcs
.vals
.emplace_back(std::move(func
));
25781 if (finfo
->auxClassGraphs
) moveNewAuxs(*finfo
->auxClassGraphs
);
25783 AnalysisIndexFInfo afinfo
;
25784 afinfo
.ptr
= decltype(afinfo
.ptr
){finfo
.release()};
25785 finfos
.vals
.emplace_back(std::move(afinfo
));
25788 hphp_fast_set
<php::Unit
*> outUnits
;
25789 outUnits
.reserve(m_data
->outUnitNames
.size());
25791 units
.vals
.reserve(m_data
->outUnitNames
.size());
25792 meta
.unitDeps
.reserve(m_data
->outUnitNames
.size());
25793 for (auto const name
: m_data
->outUnitNames
) {
25794 auto& unit
= m_data
->allUnits
.at(name
);
25795 outUnits
.emplace(unit
.get());
25796 meta
.unitDeps
.emplace_back(m_data
->deps
->take(unit
.get()));
25797 units
.vals
.emplace_back(std::move(unit
));
25800 SStringSet outConstants
;
25801 for (auto const& [_
, p
] : m_data
->constants
) {
25802 if (!outUnits
.count(p
.second
)) continue;
25803 outConstants
.emplace(p
.first
->name
);
25806 const SStringSet outUnitNames
{
25807 begin(m_data
->outUnitNames
),
25808 end(m_data
->outUnitNames
)
25811 meta
.changed
= std::move(m_data
->deps
->getChanges());
25812 meta
.changed
.filter(outClasses
, outFuncs
, outUnitNames
, outConstants
);
25814 return std::make_tuple(
25815 std::move(classes
),
25827 //////////////////////////////////////////////////////////////////////
25829 PublicSPropMutations::PublicSPropMutations(bool enabled
) : m_enabled
{enabled
} {}
25831 PublicSPropMutations::Data
& PublicSPropMutations::get() {
25832 if (!m_data
) m_data
= std::make_unique
<Data
>();
25836 void PublicSPropMutations::mergeKnown(const ClassInfo
* ci
,
25837 const php::Prop
& prop
,
25839 if (!m_enabled
) return;
25840 ITRACE(4, "PublicSPropMutations::mergeKnown: {} {} {}\n",
25841 ci
->cls
->name
->data(), prop
.name
, show(val
));
25843 auto const res
= get().m_known
.emplace(
25844 KnownKey
{ const_cast<ClassInfo
*>(ci
), prop
.name
}, val
25846 if (!res
.second
) res
.first
->second
|= val
;
25849 void PublicSPropMutations::mergeUnknownClass(SString prop
, const Type
& val
) {
25850 if (!m_enabled
) return;
25851 ITRACE(4, "PublicSPropMutations::mergeUnknownClass: {} {}\n",
25854 auto const res
= get().m_unknown
.emplace(prop
, val
);
25855 if (!res
.second
) res
.first
->second
|= val
;
25858 void PublicSPropMutations::mergeUnknown(Context ctx
) {
25859 if (!m_enabled
) return;
25860 ITRACE(4, "PublicSPropMutations::mergeUnknown\n");
25863 * We have a case here where we know neither the class nor the static
25864 * property name. This means we have to pessimize public static property
25865 * types for the entire program.
25867 * We could limit it to pessimizing them by merging the `val' type, but
25868 * instead we just throw everything away---this optimization is not
25869 * expected to be particularly useful on programs that contain any
25870 * instances of this situation.
25874 "NOTE: had to mark everything unknown for public static "
25875 "property types due to dynamic code. -fanalyze-public-statics "
25876 "will not help for this program.\n"
25877 "NOTE: The offending code occured in this context: %s\n",
25880 get().m_nothing_known
= true;
25883 //////////////////////////////////////////////////////////////////////
25885 #define UNIMPLEMENTED always_assert_flog(false, "{} not implemented for AnalysisIndex", __func__)
25887 bool AnalysisIndexAdaptor::frozen() const {
25888 return index
.frozen();
25891 void AnalysisIndexAdaptor::push_context(const Context
& ctx
) const {
25892 index
.push_context(ctx
);
25895 void AnalysisIndexAdaptor::pop_context() const {
25896 index
.pop_context();
25899 bool AnalysisIndexAdaptor::set_in_type_cns(bool b
) const {
25900 return index
.set_in_type_cns(b
);
25903 const php::Unit
* AnalysisIndexAdaptor::lookup_func_unit(const php::Func
& func
) const {
25904 return &index
.lookup_func_unit(func
);
25906 const php::Unit
* AnalysisIndexAdaptor::lookup_class_unit(const php::Class
& cls
) const {
25907 return &index
.lookup_class_unit(cls
);
25909 const php::Class
* AnalysisIndexAdaptor::lookup_const_class(const php::Const
& cns
) const {
25910 return index
.lookup_const_class(cns
);
25912 const php::Class
* AnalysisIndexAdaptor::lookup_closure_context(const php::Class
& cls
) const {
25913 return &index
.lookup_closure_context(cls
);
25915 const php::Class
* AnalysisIndexAdaptor::lookup_class(SString c
) const {
25916 return index
.lookup_class(c
);
25919 const CompactVector
<const php::Class
*>*
25920 AnalysisIndexAdaptor::lookup_closures(const php::Class
*) const {
25924 const hphp_fast_set
<const php::Func
*>*
25925 AnalysisIndexAdaptor::lookup_extra_methods(const php::Class
*) const {
25929 Optional
<res::Class
> AnalysisIndexAdaptor::resolve_class(SString n
) const {
25930 return index
.resolve_class(n
);
25932 Optional
<res::Class
>
25933 AnalysisIndexAdaptor::resolve_class(const php::Class
& c
) const {
25934 return index
.resolve_class(c
);
25936 std::pair
<const php::TypeAlias
*, bool>
25937 AnalysisIndexAdaptor::lookup_type_alias(SString n
) const {
25938 return index
.lookup_type_alias(n
);
25940 Index::ClassOrTypeAlias
25941 AnalysisIndexAdaptor::lookup_class_or_type_alias(SString n
) const {
25942 return index
.lookup_class_or_type_alias(n
);
25945 res::Func
AnalysisIndexAdaptor::resolve_func_or_method(const php::Func
& f
) const {
25946 return index
.resolve_func_or_method(f
);
25948 res::Func
AnalysisIndexAdaptor::resolve_func(SString f
) const {
25949 return index
.resolve_func(f
);
25951 res::Func
AnalysisIndexAdaptor::resolve_method(Context
,
25954 return index
.resolve_method(t
, n
);
25956 res::Func
AnalysisIndexAdaptor::resolve_ctor(const Type
& obj
) const {
25957 return index
.resolve_ctor(obj
);
25960 std::vector
<std::pair
<SString
, ClsConstInfo
>>
25961 AnalysisIndexAdaptor::lookup_class_constants(const php::Class
& cls
) const {
25962 return index
.lookup_class_constants(cls
);
25965 ClsConstLookupResult
25966 AnalysisIndexAdaptor::lookup_class_constant(Context
,
25968 const Type
& name
) const {
25969 return index
.lookup_class_constant(cls
, name
);
25972 ClsTypeConstLookupResult
25973 AnalysisIndexAdaptor::lookup_class_type_constant(
25976 const Index::ClsTypeConstLookupResolver
& resolver
25978 return index
.lookup_class_type_constant(cls
, name
, resolver
);
25981 ClsTypeConstLookupResult
25982 AnalysisIndexAdaptor::lookup_class_type_constant(const php::Class
& ctx
,
25984 ConstIndex idx
) const {
25985 return index
.lookup_class_type_constant(ctx
, n
, idx
);
25988 std::vector
<std::pair
<SString
, ConstIndex
>>
25989 AnalysisIndexAdaptor::lookup_flattened_class_type_constants(
25990 const php::Class
& cls
25992 return index
.lookup_flattened_class_type_constants(cls
);
25995 Type
AnalysisIndexAdaptor::lookup_constant(Context
, SString n
) const {
25996 return index
.lookup_constant(n
);
25998 bool AnalysisIndexAdaptor::func_depends_on_arg(const php::Func
* f
,
25999 size_t arg
) const {
26000 return index
.func_depends_on_arg(*f
, arg
);
26003 AnalysisIndexAdaptor::lookup_foldable_return_type(Context
,
26004 const CallContext
& callee
) const {
26005 return index
.lookup_foldable_return_type(callee
);
26007 Index::ReturnType
AnalysisIndexAdaptor::lookup_return_type(Context
,
26008 MethodsInfo
* methods
,
26011 return index
.lookup_return_type(methods
, func
);
26013 Index::ReturnType
AnalysisIndexAdaptor::lookup_return_type(Context
,
26014 MethodsInfo
* methods
,
26015 const CompactVector
<Type
>& args
,
26016 const Type
& context
,
26019 return index
.lookup_return_type(methods
, args
, context
, func
);
26022 std::pair
<Index::ReturnType
, size_t>
26023 AnalysisIndexAdaptor::lookup_return_type_raw(const php::Func
* f
) const {
26024 return index
.lookup_return_type_raw(*f
);
26026 CompactVector
<Type
>
26027 AnalysisIndexAdaptor::lookup_closure_use_vars(const php::Func
*, bool) const {
26031 PropState
AnalysisIndexAdaptor::lookup_private_props(const php::Class
* cls
,
26033 return index
.lookup_private_props(*cls
);
26035 PropState
AnalysisIndexAdaptor::lookup_private_statics(const php::Class
* cls
,
26037 return index
.lookup_private_statics(*cls
);
26040 PropLookupResult
AnalysisIndexAdaptor::lookup_static(Context
,
26041 const PropertiesInfo
&,
26043 const Type
& name
) const {
26044 // Not implemented yet, be conservative.
26046 auto const sname
= [&] () -> SString
{
26047 // Treat non-string names conservatively, but the caller should be
26049 if (!is_specialized_string(name
)) return nullptr;
26050 return sval_of(name
);
26053 return PropLookupResult
{
26065 Type
AnalysisIndexAdaptor::lookup_public_prop(const Type
&, const Type
&) const {
26070 AnalysisIndexAdaptor::merge_static_type(Context
,
26071 PublicSPropMutations
& publicMutations
,
26072 PropertiesInfo
& privateProps
,
26078 bool mustBeReadOnly
) const {
26079 return index
.merge_static_type(
26091 bool AnalysisIndexAdaptor::using_class_dependencies() const {
26095 #undef UNIMPLEMENTED
26097 //////////////////////////////////////////////////////////////////////
26099 template <typename T
>
26100 void AnalysisIndexParam
<T
>::Deleter::operator()(T
* t
) const {
26104 template <typename T
>
26105 void AnalysisIndexParam
<T
>::serde(BlobEncoder
& sd
) const {
26109 template <typename T
>
26110 void AnalysisIndexParam
<T
>::serde(BlobDecoder
& sd
) {
26114 template struct AnalysisIndexParam
<ClassInfo2
>;
26115 template struct AnalysisIndexParam
<FuncInfo2
>;
26116 template struct AnalysisIndexParam
<MethodsWithoutCInfo
>;
26118 //////////////////////////////////////////////////////////////////////
26122 //////////////////////////////////////////////////////////////////////
26124 MAKE_UNIQUE_PTR_BLOB_SERDE_HELPER(HHBBC::ClassInfo2
);
26125 MAKE_UNIQUE_PTR_BLOB_SERDE_HELPER(HHBBC::FuncInfo2
);
26126 MAKE_UNIQUE_PTR_BLOB_SERDE_HELPER(HHBBC::FuncFamily2
);
26127 MAKE_UNIQUE_PTR_BLOB_SERDE_HELPER(HHBBC::MethodsWithoutCInfo
);
26128 MAKE_UNIQUE_PTR_BLOB_SERDE_HELPER(HHBBC::BuildSubclassListJob::Split
);
26130 //////////////////////////////////////////////////////////////////////