Teach hhbbc to optimize away LockObj.
[hiphop-php.git] / hphp / hhbbc / index.cpp
blob7f0dd92867037ad3059f36681d9cfa99d403c00a
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
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"
18 #include <algorithm>
19 #include <cstdio>
20 #include <cstdlib>
21 #include <iterator>
22 #include <map>
23 #include <memory>
24 #include <mutex>
25 #include <unordered_map>
26 #include <unordered_set>
27 #include <utility>
28 #include <vector>
30 #include <boost/dynamic_bitset.hpp>
32 #include <tbb/concurrent_hash_map.h>
33 #include <tbb/concurrent_unordered_map.h>
35 #include <folly/Format.h>
36 #include <folly/Hash.h>
37 #include <folly/Lazy.h>
38 #include <folly/MapUtil.h>
39 #include <folly/Memory.h>
40 #include <folly/Optional.h>
41 #include <folly/Range.h>
42 #include <folly/String.h>
43 #include <folly/concurrency/ConcurrentHashMap.h>
45 #include "hphp/runtime/base/runtime-option.h"
46 #include "hphp/runtime/base/tv-comparisons.h"
48 #include "hphp/runtime/vm/native.h"
49 #include "hphp/runtime/vm/preclass-emitter.h"
50 #include "hphp/runtime/vm/runtime.h"
51 #include "hphp/runtime/vm/trait-method-import-data.h"
52 #include "hphp/runtime/vm/unit-util.h"
54 #include "hphp/hhbbc/type-builtins.h"
55 #include "hphp/hhbbc/type-system.h"
56 #include "hphp/hhbbc/representation.h"
57 #include "hphp/hhbbc/unit-util.h"
58 #include "hphp/hhbbc/class-util.h"
59 #include "hphp/hhbbc/context.h"
60 #include "hphp/hhbbc/func-util.h"
61 #include "hphp/hhbbc/options-util.h"
62 #include "hphp/hhbbc/parallel.h"
63 #include "hphp/hhbbc/analyze.h"
65 #include "hphp/util/algorithm.h"
66 #include "hphp/util/assertions.h"
67 #include "hphp/util/match.h"
69 namespace HPHP { namespace HHBBC {
71 TRACE_SET_MOD(hhbbc_index);
73 //////////////////////////////////////////////////////////////////////
75 namespace {
77 //////////////////////////////////////////////////////////////////////
79 const StaticString s_construct("__construct");
80 const StaticString s_call("__call");
81 const StaticString s_get("__get");
82 const StaticString s_set("__set");
83 const StaticString s_isset("__isset");
84 const StaticString s_unset("__unset");
85 const StaticString s_toBoolean("__toBoolean");
86 const StaticString s_invoke("__invoke");
87 const StaticString s_Closure("Closure");
88 const StaticString s_AsyncGenerator("HH\\AsyncGenerator");
89 const StaticString s_Generator("Generator");
91 //////////////////////////////////////////////////////////////////////
94 * One-to-many case insensitive map, where the keys are static strings
95 * and the values are some kind of pointer.
97 template<class T> using ISStringToMany =
98 std::unordered_multimap<
99 SString,
101 string_data_hash,
102 string_data_isame
106 * One-to-one case insensitive map, where the keys are static strings
107 * and the values are some T.
109 template<class T> using ISStringToOneT =
110 hphp_hash_map<
111 SString,
113 string_data_hash,
114 string_data_isame
118 * One-to-one case insensitive map, where the keys are static strings
119 * and the values are some T.
121 * Elements are not stable under insert/erase.
123 template<class T> using ISStringToOneFastT =
124 hphp_fast_map<
125 SString,
127 string_data_hash,
128 string_data_isame
132 * One-to-one case insensitive map, where the keys are static strings
133 * and the values are some kind of pointer.
135 template<class T> using ISStringToOne = ISStringToOneT<T*>;
137 template<class MultiMap>
138 folly::Range<typename MultiMap::const_iterator>
139 find_range(const MultiMap& map, typename MultiMap::key_type key) {
140 auto const pair = map.equal_range(key);
141 return folly::range(pair.first, pair.second);
144 // Like find_range, but copy them into a temporary buffer instead of
145 // returning iterators, so you can still mutate the underlying
146 // multimap.
147 template<class MultiMap>
148 std::vector<typename MultiMap::value_type>
149 copy_range(const MultiMap& map, typename MultiMap::key_type key) {
150 auto range = find_range(map, key);
151 return std::vector<typename MultiMap::value_type>(begin(range), end(range));
154 //////////////////////////////////////////////////////////////////////
156 enum class Dep : uintptr_t {
157 /* This dependency should trigger when the return type changes */
158 ReturnTy = (1u << 0),
159 /* This dependency should trigger when a DefCns is resolved */
160 ConstVal = (1u << 1),
161 /* This dependency should trigger when a class constant is resolved */
162 ClsConst = (1u << 2),
163 /* This dependency should trigger when the bad initial prop value bit for a
164 * class changes */
165 PropBadInitialValues = (1u << 3),
166 /* This dependency should trigger when a public static property with a
167 * particular name changes */
168 PublicSPropName = (1u << 4),
169 /* This dependency means that we refused to do inline analysis on
170 * this function due to inline analysis depth. The dependency will
171 * trigger if the target function becomes effect-free, or gets a
172 * literal return value.
174 InlineDepthLimit = (1u << 5),
177 Dep operator|(Dep a, Dep b) {
178 return static_cast<Dep>(
179 static_cast<uintptr_t>(a) | static_cast<uintptr_t>(b)
183 bool has_dep(Dep m, Dep t) {
184 return static_cast<uintptr_t>(m) & static_cast<uintptr_t>(t);
188 * Maps functions to contexts that depend on information about that
189 * function, with information about the type of dependency.
191 using DepMap =
192 tbb::concurrent_hash_map<
193 DependencyContext,
194 std::map<DependencyContext,Dep,DependencyContextLess>,
195 DependencyContextHashCompare
198 //////////////////////////////////////////////////////////////////////
201 * Each ClassInfo has a table of public static properties with these entries.
202 * The `initializerType' is for use during refine_public_statics, and
203 * inferredType will always be a supertype of initializerType.
205 struct PublicSPropEntry {
206 Type inferredType;
207 Type initializerType;
208 const TypeConstraint* tc;
209 uint32_t refinements;
210 bool everModified;
212 * This flag is set during analysis to indicate that we resolved the
213 * intial value (and updated it on the php::Class). This doesn't
214 * need to be atomic, because only one thread can resolve the value
215 * (the one processing the 86sinit), and it's been joined by the
216 * time we read the flag in refine_public_statics.
218 bool initialValueResolved;
222 * Entries in the ClassInfo method table need to track some additional
223 * information.
225 * The reason for this is that we need to record attributes of the
226 * class hierarchy.
228 struct MethTabEntry {
229 MethTabEntry(const php::Func* func, Attr a, bool hpa, bool tl) :
230 func(func), attrs(a), hasPrivateAncestor(hpa), topLevel(tl) {}
231 const php::Func* func = nullptr;
232 // A method could be imported from a trait, and its attributes changed
233 Attr attrs {};
234 bool hasAncestor = false;
235 bool hasPrivateAncestor = false;
236 // This method came from the ClassInfo that owns the MethTabEntry,
237 // or one of its used traits.
238 bool topLevel = false;
239 uint32_t idx = 0;
244 struct res::Func::MethTabEntryPair :
245 ISStringToOneT<MethTabEntry>::value_type {};
247 namespace {
249 using MethTabEntryPair = res::Func::MethTabEntryPair;
251 inline MethTabEntryPair* mteFromElm(
252 ISStringToOneT<MethTabEntry>::value_type& elm) {
253 return static_cast<MethTabEntryPair*>(&elm);
256 inline const MethTabEntryPair* mteFromElm(
257 const ISStringToOneT<MethTabEntry>::value_type& elm) {
258 return static_cast<const MethTabEntryPair*>(&elm);
261 inline MethTabEntryPair* mteFromIt(ISStringToOneT<MethTabEntry>::iterator it) {
262 return static_cast<MethTabEntryPair*>(&*it);
265 struct CallContextHashCompare {
266 bool equal(const CallContext& a, const CallContext& b) const {
267 return a == b;
270 size_t hash(const CallContext& c) const {
271 auto ret = folly::hash::hash_combine(
272 c.callee,
273 c.args.size(),
274 c.context.hash()
276 for (auto& t : c.args) {
277 ret = folly::hash::hash_combine(ret, t.hash());
279 return ret;
283 using ContextRetTyMap = tbb::concurrent_hash_map<
284 CallContext,
285 Type,
286 CallContextHashCompare
289 //////////////////////////////////////////////////////////////////////
291 template<class Filter>
292 PropState make_unknown_propstate(const php::Class* cls,
293 Filter filter) {
294 auto ret = PropState{};
295 for (auto& prop : cls->properties) {
296 if (filter(prop)) {
297 ret[prop.name].ty = TGen;
300 return ret;
306 * Currently inferred information about a PHP function.
308 * Nothing in this structure can ever be untrue. The way the
309 * algorithm works, whatever is in here must be factual (even if it is
310 * not complete information), because we may deduce other facts based
311 * on it.
313 struct res::Func::FuncInfo {
314 const php::Func* func = nullptr;
316 * The best-known return type of the function, if we have any
317 * information. May be TBottom if the function is known to never
318 * return (e.g. always throws).
320 Type returnTy = TInitCell;
323 * If the function always returns the same parameter, this will be
324 * set to its id; otherwise it will be NoLocalId.
326 LocalId retParam{NoLocalId};
329 * The number of times we've refined returnTy.
331 uint32_t returnRefinments{0};
334 * Whether the function is effectFree.
336 bool effectFree{false};
339 * Bitset representing which parameters definitely don't affect the
340 * result of the function, assuming it produces one. Note that
341 * VerifyParamType does not count as a use in this context.
343 std::bitset<64> unusedParams;
346 namespace {
348 //////////////////////////////////////////////////////////////////////
351 * Known information about a particular constant:
352 * - if system is true, it's a system constant and other definitions
353 * will be ignored.
354 * - for non-system constants, if func is non-null it's the unique
355 * pseudomain defining the constant; otherwise there was more than
356 * one definition, or a non-pseudomain definition, and the type will
357 * be TInitCell
358 * - readonly is true if we've only seen uses of the constant, and no
359 * definitions (this could change during the first pass, but not after
360 * that).
363 struct ConstInfo {
364 const php::Func* func;
365 Type type;
366 bool system;
367 bool readonly;
370 using FuncFamily = res::Func::FuncFamily;
371 using FuncInfo = res::Func::FuncInfo;
372 using MethTabEntryPair = res::Func::MethTabEntryPair;
374 //////////////////////////////////////////////////////////////////////
378 //////////////////////////////////////////////////////////////////////
381 * Sometimes function resolution can't determine which function
382 * something will call, but can restrict it to a family of functions.
384 * For example, if you want to call an abstract function on a base
385 * class with all unique derived classes, we will resolve the function
386 * to a FuncFamily that contains references to all the possible
387 * overriding-functions.
389 * Carefully pack it into 8 bytes, so that hphp_fast_map will use
390 * F14VectorMap.
392 struct res::Func::FuncFamily {
393 using PFuncVec = CompactVector<const MethTabEntryPair*>;
394 static_assert(sizeof(PFuncVec) == sizeof(uintptr_t),
395 "CompactVector must be layout compatible with a pointer");
397 struct Holder {
398 Holder(const Holder& o) : bits{o.bits} {}
399 explicit Holder(PFuncVec&& o) : v{std::move(o)} {}
400 explicit Holder(uintptr_t b) : bits{b & ~1} {}
401 Holder& operator=(const Holder&) = delete;
402 ~Holder() {}
403 const PFuncVec* operator->() const { return &v; }
404 uintptr_t val() const { return bits; }
405 friend auto begin(const Holder& h) { return h->begin(); }
406 friend auto end(const Holder& h) { return h->end(); }
407 private:
408 union {
409 uintptr_t bits;
410 PFuncVec v;
414 FuncFamily(PFuncVec&& v, bool containsInterceptables)
415 : m_raw{Holder{std::move(v)}.val()} {
416 if (containsInterceptables) m_raw |= 1;
418 FuncFamily(FuncFamily&& o) noexcept : m_raw(o.m_raw) {
419 o.m_raw = 0;
421 ~FuncFamily() {
422 Holder{m_raw & ~1}->~PFuncVec();
424 FuncFamily& operator=(const FuncFamily&) = delete;
426 bool containsInterceptables() const { return m_raw & 1; };
427 const Holder possibleFuncs() const {
428 return Holder{m_raw & ~1};
430 private:
431 uintptr_t m_raw;
434 //////////////////////////////////////////////////////////////////////
437 * Known information about a particular possible instantiation of a
438 * PHP class. The php::Class will be marked AttrUnique if there is a
439 * unique ClassInfo with the same name, and no interfering class_aliases.
441 struct ClassInfo {
443 * A pointer to the underlying php::Class that we're storing
444 * information about.
446 const php::Class* cls = nullptr;
449 * The info for the parent of this Class.
451 ClassInfo* parent = nullptr;
454 * A vector of the declared interfaces class info structures. This is in
455 * declaration order mirroring the php::Class interfaceNames vector, and does
456 * not include inherited interfaces.
458 CompactVector<const ClassInfo*> declInterfaces;
461 * A (case-insensitive) map from interface names supported by this class to
462 * their ClassInfo structures, flattened across the hierarchy.
464 ISStringToOneT<const ClassInfo*> implInterfaces;
467 * A (case-sensitive) map from class constant name to the php::Const
468 * that it came from. This map is flattened across the inheritance
469 * hierarchy.
471 hphp_fast_map<SString,const php::Const*> clsConstants;
474 * A vector of the used traits, in class order, mirroring the
475 * php::Class usedTraitNames vector.
477 CompactVector<const ClassInfo*> usedTraits;
480 * A list of extra properties supplied by this class's used traits.
482 CompactVector<php::Prop> traitProps;
485 * A (case-insensitive) map from class method names to the php::Func
486 * associated with it. This map is flattened across the inheritance
487 * hierarchy.
489 ISStringToOneT<MethTabEntry> methods;
492 * A (case-insensitive) map from class method names to associated
493 * FuncFamily objects that group the set of possibly-overriding
494 * methods.
496 * Note that this does not currently encode anything for interface
497 * methods.
499 * Invariant: methods on this class with AttrNoOverride or
500 * AttrPrivate will not have an entry in this map.
502 ISStringToOneFastT<FuncFamily> methodFamilies;
505 * Subclasses of this class, including this class itself.
507 * For interfaces, this is the list of instantiable classes that
508 * implement this interface.
510 * For traits, this is the list of classes that use the trait where
511 * the trait wasn't flattened into the class (including the trait
512 * itself).
514 * Note, unlike baseList, the order of the elements in this vector
515 * is unspecified.
517 CompactVector<ClassInfo*> subclassList;
520 * A vector of ClassInfo that encodes the inheritance hierarchy,
521 * unless this ClassInfo represents an interface.
523 * This is the list of base classes for this class in inheritance
524 * order.
526 CompactVector<ClassInfo*> baseList;
529 * Property types for public static properties, declared on this exact class
530 * (i.e. not flattened in the hierarchy).
532 * These maps always have an entry for each public static property declared
533 * in this class, so it can also be used to check if this class declares a
534 * public static property of a given name.
536 * Note: the effective type we can assume a given static property may hold is
537 * not just the value in these maps. To handle mutations of public statics
538 * where the name is known, but not which class was affected, these always
539 * need to be unioned with values from IndexData::unknownClassSProps.
541 hphp_hash_map<SString,PublicSPropEntry> publicStaticProps;
544 * Flags to track if this class is mocked, or if any of its dervied classes
545 * are mocked.
547 bool isMocked{false};
548 bool isDerivedMocked{false};
551 * Track if this class has a property which might redeclare a property in a
552 * parent class with an inequivalent type-hint.
554 bool hasBadRedeclareProp{true};
557 * Track if this class has any properties with initial values that might
558 * violate their type-hints.
560 bool hasBadInitialPropValues{true};
563 * Track if this class has any const props (including inherited ones).
565 bool hasConstProp{false};
568 * Track if any derived classes (including this one) have any const props.
570 bool derivedHasConstProp{false};
573 * Flags about the existence of various magic methods, or whether
574 * any derived classes may have those methods. The non-derived
575 * flags imply the derived flags, even if the class is final, so you
576 * don't need to check both in those situations.
578 struct MagicFnInfo {
579 bool thisHas{false};
580 bool derivedHas{false};
582 MagicFnInfo
583 magicCall,
584 magicGet,
585 magicSet,
586 magicIsset,
587 magicUnset,
588 magicBool;
591 using MagicMapInfo = struct {
592 ClassInfo::MagicFnInfo ClassInfo::*pmem;
593 Attr attrBit;
596 const std::vector<std::pair<SString,MagicMapInfo>> magicMethodMap {
597 { s_call.get(), { &ClassInfo::magicCall, AttrNone } },
598 { s_toBoolean.get(), { &ClassInfo::magicBool, AttrNone } },
599 { s_get.get(), { &ClassInfo::magicGet, AttrNoOverrideMagicGet } },
600 { s_set.get(), { &ClassInfo::magicSet, AttrNoOverrideMagicSet } },
601 { s_isset.get(), { &ClassInfo::magicIsset, AttrNoOverrideMagicIsset } },
602 { s_unset.get(), { &ClassInfo::magicUnset, AttrNoOverrideMagicUnset } }
605 //////////////////////////////////////////////////////////////////////
607 namespace res {
609 Class::Class(const Index* idx,
610 Either<SString,ClassInfo*> val)
611 : index(idx)
612 , val(val)
615 // Class type operations here are very conservative for now.
617 bool Class::same(const Class& o) const {
618 return val == o.val;
621 template <bool returnTrueOnMaybe>
622 bool Class::subtypeOfImpl(const Class& o) const {
623 auto s1 = val.left();
624 auto s2 = o.val.left();
625 if (s1 || s2) return returnTrueOnMaybe || s1 == s2;
626 auto c1 = val.right();
627 auto c2 = o.val.right();
629 // If c2 is an interface, see if c1 declared it.
630 if (c2->cls->attrs & AttrInterface) {
631 if (c1->implInterfaces.count(c2->cls->name)) {
632 return true;
634 return false;
637 // Otherwise check for direct inheritance.
638 if (c1->baseList.size() >= c2->baseList.size()) {
639 return c1->baseList[c2->baseList.size() - 1] == c2;
641 return false;
644 bool Class::mustBeSubtypeOf(const Class& o) const {
645 return subtypeOfImpl<false>(o);
648 bool Class::maybeSubtypeOf(const Class& o) const {
649 return subtypeOfImpl<true>(o);
652 bool Class::couldBe(const Class& o) const {
653 // If either types are not unique return true
654 if (val.left() || o.val.left()) return true;
656 auto c1 = val.right();
657 auto c2 = o.val.right();
658 // if one or the other is an interface return true for now.
659 // TODO(#3621433): better interface stuff
660 if (c1->cls->attrs & AttrInterface || c2->cls->attrs & AttrInterface) {
661 return true;
664 // Both types are unique classes so they "could be" if they are in an
665 // inheritance relationship
666 if (c1->baseList.size() >= c2->baseList.size()) {
667 return c1->baseList[c2->baseList.size() - 1] == c2;
668 } else {
669 return c2->baseList[c1->baseList.size() - 1] == c1;
673 SString Class::name() const {
674 return val.match(
675 [] (SString s) { return s; },
676 [] (ClassInfo* ci) { return ci->cls->name.get(); }
680 bool Class::couldBeInterfaceOrTrait() const {
681 return val.match(
682 [] (SString) { return true; },
683 [] (ClassInfo* cinfo) {
684 return (cinfo->cls->attrs & (AttrInterface | AttrTrait));
689 bool Class::couldBeInterface() const {
690 return val.match(
691 [] (SString) { return true; },
692 [] (ClassInfo* cinfo) {
693 return cinfo->cls->attrs & AttrInterface;
698 bool Class::couldBeOverriden() const {
699 return val.match(
700 [] (SString) { return true; },
701 [] (ClassInfo* cinfo) {
702 return !(cinfo->cls->attrs & AttrNoOverride);
707 bool Class::couldHaveMagicGet() const {
708 return val.match(
709 [] (SString) { return true; },
710 [] (ClassInfo* cinfo) {
711 return cinfo->magicGet.derivedHas;
716 bool Class::couldHaveMagicBool() const {
717 return val.match(
718 [] (SString) { return true; },
719 [] (ClassInfo* cinfo) {
720 return cinfo->magicBool.derivedHas;
725 bool Class::couldHaveMockedDerivedClass() const {
726 return val.match(
727 [] (SString) { return true;},
728 [] (ClassInfo* cinfo) {
729 return cinfo->isDerivedMocked;
734 bool Class::couldBeMocked() const {
735 return val.match(
736 [] (SString) { return true;},
737 [] (ClassInfo* cinfo) {
738 return cinfo->isMocked;
743 bool Class::couldHaveReifiedGenerics() const {
744 return val.match(
745 [] (SString) { return true; },
746 [] (ClassInfo* cinfo) {
747 return cinfo->cls->hasReifiedGenerics;
752 bool Class::mightCareAboutDynConstructs() const {
753 if (RuntimeOption::EvalForbidDynamicCalls > 0) {
754 return val.match(
755 [] (SString) { return true; },
756 [] (ClassInfo* cinfo) {
757 return !(cinfo->cls->attrs & AttrDynamicallyConstructible);
761 return false;
764 bool Class::couldHaveConstProp() const {
765 return val.match(
766 [] (SString) { return true; },
767 [] (ClassInfo* cinfo) { return cinfo->hasConstProp; }
771 bool Class::derivedCouldHaveConstProp() const {
772 return val.match(
773 [] (SString) { return true; },
774 [] (ClassInfo* cinfo) { return cinfo->derivedHasConstProp; }
778 folly::Optional<Class> Class::commonAncestor(const Class& o) const {
779 if (val.left() || o.val.left()) return folly::none;
780 auto const c1 = val.right();
781 auto const c2 = o.val.right();
782 // Walk the arrays of base classes until they match. For common ancestors
783 // to exist they must be on both sides of the baseList at the same positions
784 ClassInfo* ancestor = nullptr;
785 auto it1 = c1->baseList.begin();
786 auto it2 = c2->baseList.begin();
787 while (it1 != c1->baseList.end() && it2 != c2->baseList.end()) {
788 if (*it1 != *it2) break;
789 ancestor = *it1;
790 ++it1; ++it2;
792 if (ancestor == nullptr) {
793 return folly::none;
795 return res::Class { index, ancestor };
798 folly::Optional<res::Class> Class::parent() const {
799 if (!val.right()) return folly::none;
800 auto parent = val.right()->parent;
801 if (!parent) return folly::none;
802 return res::Class { index, parent };
805 const php::Class* Class::cls() const {
806 return val.right() ? val.right()->cls : nullptr;
809 std::string show(const Class& c) {
810 return c.val.match(
811 [] (SString s) -> std::string {
812 return s->data();
814 [] (ClassInfo* cinfo) {
815 return folly::sformat("{}*", cinfo->cls->name);
820 Func::Func(const Index* idx, Rep val)
821 : index(idx)
822 , val(val)
825 bool Func::same(const Func& o) const {
827 * TODO(#3666699): function name case sensitivity here shouldn't
828 * break equality.
830 return val == o.val;
833 SString Func::name() const {
834 return match<SString>(
835 val,
836 [&] (FuncName s) { return s.name; },
837 [&] (MethodName s) { return s.name; },
838 [&] (FuncInfo* fi) { return fi->func->name; },
839 [&] (const MethTabEntryPair* mte) { return mte->first; },
840 [&] (FuncFamily* fa) -> SString {
841 auto const name = fa->possibleFuncs()->front()->first;
842 if (debug) {
843 for (DEBUG_ONLY auto const f : fa->possibleFuncs()) {
844 assert(f->first->isame(name));
847 return name;
852 const php::Func* Func::exactFunc() const {
853 using Ret = const php::Func*;
854 return match<Ret>(
855 val,
856 [&](FuncName) { return Ret{}; },
857 [&](MethodName) { return Ret{}; },
858 [&](FuncInfo* fi) { return fi->func; },
859 [&](const MethTabEntryPair* mte) { return mte->second.func; },
860 [&](FuncFamily* /*fa*/) { return Ret{}; }
864 bool Func::cantBeMagicCall() const {
865 return match<bool>(
866 val,
867 [&](FuncName) { return true; },
868 [&](MethodName) { return false; },
869 [&](FuncInfo*) { return true; },
870 [&](const MethTabEntryPair*) { return true; },
871 [&](FuncFamily*) { return true; }
875 bool Func::isFoldable() const {
876 return match<bool>(val,
877 [&](FuncName) { return false; },
878 [&](MethodName) { return false; },
879 [&](FuncInfo* fi) {
880 return fi->func->attrs & AttrIsFoldable;
882 [&](const MethTabEntryPair* mte) {
883 return mte->second.func->attrs & AttrIsFoldable;
885 [&](FuncFamily* fa) {
886 return false;
890 bool Func::couldHaveReifiedGenerics() const {
891 return match<bool>(
892 val,
893 [&](FuncName s) { return true; },
894 [&](MethodName) { return true; },
895 [&](FuncInfo* fi) { return fi->func->isReified; },
896 [&](const MethTabEntryPair* mte) {
897 return mte->second.func->isReified;
899 [&](FuncFamily* fa) {
900 for (auto const pf : fa->possibleFuncs()) {
901 if (pf->second.func->isReified) return true;
903 return false;
907 bool Func::mightCareAboutDynCalls() const {
908 if (RuntimeOption::EvalNoticeOnBuiltinDynamicCalls && mightBeBuiltin()) {
909 return true;
911 if (RuntimeOption::EvalForbidDynamicCalls > 0) {
912 auto const res = match<bool>(
913 val,
914 [&](FuncName) { return true; },
915 [&](MethodName) { return true; },
916 [&](FuncInfo* fi) {
917 return !(fi->func->attrs & AttrDynamicallyCallable);
919 [&](const MethTabEntryPair* mte) {
920 return !(mte->second.func->attrs & AttrDynamicallyCallable);
922 [&](FuncFamily* fa) {
923 for (auto const pf : fa->possibleFuncs()) {
924 if (!(pf->second.func->attrs & AttrDynamicallyCallable)) return true;
926 return false;
929 if (res) return true;
931 return false;
934 bool Func::mightBeBuiltin() const {
935 return match<bool>(
936 val,
937 // Builtins are always uniquely resolvable unless renaming is
938 // involved.
939 [&](FuncName s) { return s.renamable; },
940 [&](MethodName) { return true; },
941 [&](FuncInfo* fi) { return fi->func->attrs & AttrBuiltin; },
942 [&](const MethTabEntryPair* mte) {
943 return mte->second.func->attrs & AttrBuiltin;
945 [&](FuncFamily* fa) {
946 for (auto const pf : fa->possibleFuncs()) {
947 if (pf->second.func->attrs & AttrBuiltin) return true;
949 return false;
954 std::string show(const Func& f) {
955 auto ret = f.name()->toCppString();
956 match<void>(f.val,
957 [&](Func::FuncName s) { if (s.renamable) ret += '?'; },
958 [&](Func::MethodName) {},
959 [&](FuncInfo* /*fi*/) { ret += "*"; },
960 [&](const MethTabEntryPair* /*mte*/) { ret += "*"; },
961 [&](FuncFamily* /*fa*/) { ret += "+"; });
962 return ret;
967 //////////////////////////////////////////////////////////////////////
969 using IfaceSlotMap = hphp_hash_map<const php::Class*, Slot>;
970 using ConstInfoConcurrentMap =
971 tbb::concurrent_hash_map<SString, ConstInfo, StringDataHashCompare>;
973 struct Index::IndexData {
974 explicit IndexData(Index* index) : m_index{index} {}
975 IndexData(const IndexData&) = delete;
976 IndexData& operator=(const IndexData&) = delete;
977 ~IndexData() {
978 if (compute_iface_vtables.joinable()) {
979 compute_iface_vtables.join();
983 Index* m_index;
985 bool frozen{false};
986 bool ever_frozen{false};
987 bool any_interceptable_functions{false};
989 std::unique_ptr<ArrayTypeTable::Builder> arrTableBuilder;
991 ISStringToMany<const php::Class> classes;
992 ISStringToMany<const php::Func> methods;
993 ISStringToOneT<uint64_t> method_ref_params_by_name;
994 ISStringToMany<const php::Func> funcs;
995 ISStringToMany<const php::TypeAlias> typeAliases;
996 ISStringToMany<const php::Class> enums;
997 ConstInfoConcurrentMap constants;
998 ISStringToMany<const php::Record> records;
999 hphp_fast_set<SString, string_data_hash, string_data_isame> classAliases;
1001 // Map from each class to all the closures that are allocated in
1002 // functions of that class.
1003 hphp_hash_map<
1004 const php::Class*,
1005 CompactVector<const php::Class*>
1006 > classClosureMap;
1008 hphp_hash_map<
1009 const php::Class*,
1010 hphp_fast_set<php::Func*>
1011 > classExtraMethodMap;
1014 * Map from each class name to ClassInfo objects for all
1015 * not-known-to-be-impossible resolutions of the class at runtime.
1017 * If the class is unique, there will only be one resolution.
1018 * Otherwise there will be one for each possible path through the
1019 * inheritance hierarchy, potentially excluding cases that we know
1020 * would definitely fatal when defined.
1022 ISStringToMany<ClassInfo> classInfo;
1025 * All the ClassInfos, sorted topologically (ie all the parents,
1026 * interfaces and traits used by the ClassInfo at index K will have
1027 * indices less than K). This mostly drops out of the way ClassInfos
1028 * are created; it would be hard to create the ClassInfos for the
1029 * php::Class X (or even know how many to create) without knowing
1030 * all the ClassInfos that were created for X's dependencies.
1032 std::vector<std::unique_ptr<ClassInfo>> allClassInfos;
1034 std::vector<FuncInfo> funcInfo;
1036 // Private instance and static property types are stored separately
1037 // from ClassInfo, because you don't need to resolve a class to get
1038 // at them.
1039 hphp_hash_map<
1040 const php::Class*,
1041 PropState
1042 > privatePropInfo;
1043 hphp_hash_map<
1044 const php::Class*,
1045 PropState
1046 > privateStaticPropInfo;
1049 * Public static property information:
1052 // If this is true, we don't know anything about public static properties and
1053 // must be pessimistic. We start in this state (before we've analyzed any
1054 // mutations) and remain in it if we see a mutation where both the name and
1055 // class are unknown.
1056 bool allPublicSPropsUnknown{true};
1058 // Best known types for public static properties where we knew the name, but
1059 // not the class. The type we're allowed to assume for a public static
1060 // property is the union of the ClassInfo-specific type with the unknown class
1061 // type that's stored here. The second value is the number of times the type
1062 // has been refined.
1063 hphp_hash_map<SString, std::pair<Type, uint32_t>> unknownClassSProps;
1065 // The set of gathered public static property mutations for each function. The
1066 // inferred types for the public static properties is the union of all these
1067 // mutations. If a function is not analyzed in a particular analysis round,
1068 // its mutations are left unchanged from the previous round.
1069 folly::ConcurrentHashMap<const php::Func*,
1070 PublicSPropMutations> publicSPropMutations;
1073 * Map from interfaces to their assigned vtable slots, computed in
1074 * compute_iface_vtables().
1076 IfaceSlotMap ifaceSlotMap;
1078 hphp_hash_map<
1079 const php::Class*,
1080 CompactVector<Type>
1081 > closureUseVars;
1083 bool useClassDependencies{};
1084 DepMap dependencyMap;
1087 * If a function is effect-free when called with a particular set of
1088 * literal arguments, and produces a literal result, there will be
1089 * an entry here representing the type.
1091 * The map isn't just an optimization; we can't call
1092 * analyze_func_inline during the optimization phase, because the
1093 * bytecode could be modified while we do so.
1095 ContextRetTyMap foldableReturnTypeMap;
1098 * Call-context sensitive return types are cached here. This is not
1099 * an optimization.
1101 * The reason we need to retain this information about the
1102 * calling-context-sensitive return types is that once the Index is
1103 * frozen (during the final optimization pass), calls to
1104 * lookup_return_type with a CallContext can't look at the bytecode
1105 * bodies of functions other than the calling function. So we need
1106 * to know what we determined the last time we were alloewd to do
1107 * that so we can return it again.
1109 ContextRetTyMap contextualReturnTypes{};
1112 * Vector of class aliases that need to be added to the index when
1113 * its safe to do so (see update_class_aliases).
1115 std::vector<std::pair<SString, SString>> pending_class_aliases;
1116 std::mutex pending_class_aliases_mutex;
1118 std::thread compute_iface_vtables;
1121 //////////////////////////////////////////////////////////////////////
1123 namespace {
1125 //////////////////////////////////////////////////////////////////////
1127 using IndexData = Index::IndexData;
1129 std::mutex closure_use_vars_mutex;
1130 std::mutex private_propstate_mutex;
1132 DependencyContext make_dep(const php::Func* func) {
1133 return DependencyContext{DependencyContextType::Func, func};
1135 DependencyContext make_dep(const php::Class* cls) {
1136 return DependencyContext{DependencyContextType::Class, cls};
1138 DependencyContext make_dep(SString name) {
1139 return DependencyContext{DependencyContextType::PropName, name};
1142 DependencyContext dep_context(IndexData& data, const Context& ctx) {
1143 if (!ctx.cls || !data.useClassDependencies) return make_dep(ctx.func);
1144 auto const cls = ctx.cls->closureContextCls ?
1145 ctx.cls->closureContextCls : ctx.cls;
1146 if (is_used_trait(*cls)) return make_dep(ctx.func);
1147 return make_dep(cls);
1150 template <typename T>
1151 void add_dependency(IndexData& data,
1152 T src,
1153 const Context& dst,
1154 Dep newMask) {
1155 if (data.frozen) return;
1157 auto d = dep_context(data, dst);
1158 DepMap::accessor acc;
1159 data.dependencyMap.insert(acc, make_dep(src));
1160 auto& current = acc->second[d];
1161 current = current | newMask;
1164 std::mutex func_info_mutex;
1166 FuncInfo* create_func_info(IndexData& data, const php::Func* f) {
1167 auto fi = &data.funcInfo[f->idx];
1168 if (UNLIKELY(fi->func == nullptr)) {
1169 if (f->nativeInfo) {
1170 std::lock_guard<std::mutex> g{func_info_mutex};
1171 if (fi->func) {
1172 assert(fi->func == f);
1173 return fi;
1175 // We'd infer this anyway when we look at the bytecode body
1176 // (NativeImpl) for the HNI function, but just initializing it
1177 // here saves on whole-program iterations.
1178 fi->returnTy = native_function_return_type(f);
1180 fi->func = f;
1183 assert(fi->func == f);
1184 return fi;
1187 FuncInfo* func_info(IndexData& data, const php::Func* f) {
1188 auto const fi = &data.funcInfo[f->idx];
1189 return fi;
1192 template <typename T>
1193 void find_deps(IndexData& data,
1194 T src,
1195 Dep mask,
1196 DependencyContextSet& deps) {
1197 DepMap::const_accessor acc;
1198 if (data.dependencyMap.find(acc, make_dep(src))) {
1199 for (auto& kv : acc->second) {
1200 if (has_dep(kv.second, mask)) deps.insert(kv.first);
1205 struct TraitMethod {
1206 using class_type = const ClassInfo*;
1207 using method_type = const php::Func*;
1209 TraitMethod(class_type trait_, method_type method_, Attr modifiers_)
1210 : trait(trait_)
1211 , method(method_)
1212 , modifiers(modifiers_)
1215 class_type trait;
1216 method_type method;
1217 Attr modifiers;
1220 struct TMIOps {
1221 using string_type = LSString;
1222 using class_type = TraitMethod::class_type;
1223 using method_type = TraitMethod::method_type;
1225 struct TMIException : std::exception {
1226 explicit TMIException(std::string msg) : msg(msg) {}
1227 const char* what() const noexcept override { return msg.c_str(); }
1228 private:
1229 std::string msg;
1232 // Return the name for the trait class.
1233 static const string_type clsName(class_type traitCls) {
1234 return traitCls->cls->name;
1237 // Return the name for the trait method.
1238 static const string_type methName(method_type meth) {
1239 return meth->name;
1242 // Is-a methods.
1243 static bool isTrait(class_type traitCls) {
1244 return traitCls->cls->attrs & AttrTrait;
1246 static bool isAbstract(Attr modifiers) {
1247 return modifiers & AttrAbstract;
1250 static bool isAsync(method_type meth) {
1251 return meth->isAsync;
1253 static bool isStatic(method_type meth) {
1254 return meth->attrs & AttrStatic;
1256 static bool isFinal(method_type meth) {
1257 return meth->attrs & AttrFinal;
1260 // Whether to exclude methods with name `methName' when adding.
1261 static bool exclude(string_type methName) {
1262 return Func::isSpecial(methName);
1265 // TraitMethod constructor.
1266 static TraitMethod traitMethod(class_type traitCls,
1267 method_type traitMeth,
1268 const PreClass::TraitAliasRule& rule) {
1269 return TraitMethod { traitCls, traitMeth, rule.modifiers() };
1272 // Register a trait alias once the trait class is found.
1273 static void addTraitAlias(const ClassInfo* /*cls*/,
1274 const PreClass::TraitAliasRule& /*rule*/,
1275 class_type /*traitCls*/) {
1276 // purely a runtime thing... nothing to do
1279 // Trait class/method finders.
1280 static class_type findSingleTraitWithMethod(class_type cls,
1281 string_type origMethName) {
1282 class_type traitCls = nullptr;
1284 for (auto const t : cls->usedTraits) {
1285 // Note: m_methods includes methods from parents/traits recursively.
1286 if (t->methods.count(origMethName)) {
1287 if (traitCls != nullptr) {
1288 return nullptr;
1290 traitCls = t;
1293 return traitCls;
1296 static class_type findTraitClass(class_type cls,
1297 string_type traitName) {
1298 for (auto const t : cls->usedTraits) {
1299 if (traitName->isame(t->cls->name)) return t;
1301 return nullptr;
1304 static method_type findTraitMethod(class_type traitCls,
1305 string_type origMethName) {
1306 auto it = traitCls->methods.find(origMethName);
1307 if (it == traitCls->methods.end()) return nullptr;
1308 return it->second.func;
1311 // Errors.
1312 static void errorUnknownMethod(string_type methName) {
1313 throw TMIException(folly::sformat("Unknown method '{}'", methName));
1315 static void errorUnknownTrait(string_type traitName) {
1316 throw TMIException(folly::sformat("Unknown trait '{}'", traitName));
1318 static void errorDuplicateMethod(class_type cls,
1319 string_type methName,
1320 const std::list<TraitMethod>&) {
1321 auto const& m = cls->cls->methods;
1322 if (std::find_if(m.begin(), m.end(),
1323 [&] (auto const& f) {
1324 return f->name->isame(methName);
1325 }) != m.end()) {
1326 // the duplicate methods will be overridden by the class method.
1327 return;
1329 throw TMIException(folly::sformat("DuplicateMethod: {}", methName));
1331 static void errorInconsistentInsteadOf(class_type cls,
1332 string_type methName) {
1333 throw TMIException(folly::sformat("InconsistentInsteadOf: {} {}",
1334 methName, cls->cls->name));
1336 static void errorMultiplyExcluded(string_type traitName,
1337 string_type methName) {
1338 throw TMIException(folly::sformat("MultiplyExcluded: {}::{}",
1339 traitName, methName));
1341 static void errorInconsistentAttr(string_type traitName,
1342 string_type methName,
1343 const char* attr) {
1344 throw TMIException(folly::sformat(
1345 "Redeclaration of trait method '{}::{}' is inconsistent about '{}'",
1346 traitName, methName, attr
1349 static void errorRedeclaredNotFinal(string_type traitName,
1350 string_type methName) {
1351 throw TMIException(folly::sformat(
1352 "Redeclaration of final trait method '{}::{}' must also be final",
1353 traitName, methName
1359 using TMIData = TraitMethodImportData<TraitMethod,
1360 TMIOps>;
1362 struct BuildClsInfo {
1363 IndexData& index;
1364 ClassInfo* rleaf;
1365 hphp_hash_map<SString, std::pair<php::Prop, const ClassInfo*>,
1366 string_data_hash, string_data_same> pbuilder;
1370 * Make a flattened table of the constants on this class.
1372 bool build_class_constants(BuildClsInfo& info,
1373 const ClassInfo* rparent,
1374 bool fromTrait) {
1375 auto const removeNoOverride = [&] (const php::Const* c) {
1376 // During hhbbc/parse, all constants are pre-set to NoOverride
1377 FTRACE(2, "Removing NoOverride on {}::{}\n", c->cls->name, c->name);
1378 const_cast<php::Const*>(c)->isNoOverride = false;
1380 for (auto& c : rparent->cls->constants) {
1381 auto& cptr = info.rleaf->clsConstants[c.name];
1382 if (!cptr) {
1383 cptr = &c;
1384 continue;
1387 // Same constant (from an interface via two different paths) is ok
1388 if (cptr->cls == rparent->cls) continue;
1390 if (cptr->isTypeconst != c.isTypeconst) {
1391 ITRACE(2,
1392 "build_cls_info_rec failed for `{}' because `{}' was defined by "
1393 "`{}' as a {}constant and by `{}' as a {}constant\n",
1394 info.rleaf->cls->name, c.name,
1395 rparent->cls->name, c.isTypeconst ? "type " : "",
1396 cptr->cls->name, cptr->isTypeconst ? "type " : "");
1397 return false;
1400 // Ignore abstract constants
1401 if (!c.val) continue;
1403 if (cptr->val) {
1404 // Constants from interfaces implemented by traits silently lose
1405 if (fromTrait) {
1406 removeNoOverride(&c);
1407 continue;
1410 // A constant from an interface collides with an existing constant.
1411 if (rparent->cls->attrs & AttrInterface) {
1412 ITRACE(2,
1413 "build_cls_info_rec failed for `{}' because "
1414 "`{}' was defined by both `{}' and `{}'\n",
1415 info.rleaf->cls->name, c.name,
1416 rparent->cls->name, cptr->cls->name);
1417 return false;
1421 removeNoOverride(cptr);
1422 cptr = &c;
1424 return true;
1427 bool build_class_properties(BuildClsInfo& info,
1428 const ClassInfo* rparent) {
1429 // There's no need to do this work if traits have been flattened
1430 // already, or if the top level class has no traits. In those
1431 // cases, we might be able to rule out some ClassInfo
1432 // instantiations, but it doesn't seem worth it.
1433 if (info.rleaf->cls->attrs & AttrNoExpandTrait) return true;
1434 if (info.rleaf->usedTraits.empty()) return true;
1436 auto addProp = [&] (const php::Prop& p, bool add) {
1437 auto ent = std::make_pair(p, rparent);
1438 auto res = info.pbuilder.emplace(p.name, ent);
1439 if (res.second) {
1440 if (add) info.rleaf->traitProps.push_back(p);
1441 return true;
1443 auto& prevProp = res.first->second.first;
1444 if (rparent == res.first->second.second) {
1445 assertx(rparent == info.rleaf);
1446 if ((prevProp.attrs ^ p.attrs) &
1447 (AttrStatic | AttrPublic | AttrProtected | AttrPrivate) ||
1448 (!(p.attrs & AttrSystemInitialValue) &&
1449 !(prevProp.attrs & AttrSystemInitialValue) &&
1450 !Class::compatibleTraitPropInit(prevProp.val, p.val))) {
1451 ITRACE(2,
1452 "build_class_properties failed for `{}' because "
1453 "two declarations of `{}' at the same level had "
1454 "different attributes\n",
1455 info.rleaf->cls->name, p.name);
1456 return false;
1458 return true;
1461 if (!(prevProp.attrs & AttrPrivate)) {
1462 if ((prevProp.attrs ^ p.attrs) & AttrStatic) {
1463 ITRACE(2,
1464 "build_class_properties failed for `{}' because "
1465 "`{}' was defined both static and non-static\n",
1466 info.rleaf->cls->name, p.name);
1467 return false;
1469 if (p.attrs & AttrPrivate) {
1470 ITRACE(2,
1471 "build_class_properties failed for `{}' because "
1472 "`{}' was re-declared private\n",
1473 info.rleaf->cls->name, p.name);
1474 return false;
1476 if (p.attrs & AttrProtected && !(prevProp.attrs & AttrProtected)) {
1477 ITRACE(2,
1478 "build_class_properties failed for `{}' because "
1479 "`{}' was redeclared protected from public\n",
1480 info.rleaf->cls->name, p.name);
1481 return false;
1484 if (add && res.first->second.second != rparent) {
1485 info.rleaf->traitProps.push_back(p);
1487 res.first->second = ent;
1488 return true;
1491 for (auto const& p : rparent->cls->properties) {
1492 if (!addProp(p, false)) return false;
1495 if (rparent == info.rleaf) {
1496 for (auto t : rparent->usedTraits) {
1497 for (auto const& p : t->cls->properties) {
1498 if (!addProp(p, true)) return false;
1500 for (auto const& p : t->traitProps) {
1501 if (!addProp(p, true)) return false;
1504 } else {
1505 for (auto const& p : rparent->traitProps) {
1506 if (!addProp(p, false)) return false;
1510 return true;
1514 * Make a flattened table of the methods on this class.
1516 * Duplicate method names override parent methods, unless the parent method
1517 * is final and the class is not a __MockClass, in which case this class
1518 * definitely would fatal if ever defined.
1520 * Note: we're leaving non-overridden privates in their subclass method
1521 * table, here. This isn't currently "wrong", because calling it would be a
1522 * fatal, but note that resolve_method needs to be pretty careful about
1523 * privates and overriding in general.
1525 bool build_class_methods(BuildClsInfo& info) {
1527 auto methodOverride = [&] (auto& it,
1528 const php::Func* meth,
1529 Attr attrs,
1530 SString name) {
1531 if (it->second.func->attrs & AttrFinal) {
1532 if (!is_mock_class(info.rleaf->cls)) {
1533 ITRACE(2,
1534 "build_class_methods failed for `{}' because "
1535 "it tried to override final method `{}::{}'\n",
1536 info.rleaf->cls->name,
1537 it->second.func->cls->name, name);
1538 return false;
1541 ITRACE(9,
1542 " {}: overriding method {}::{} with {}::{}\n",
1543 info.rleaf->cls->name,
1544 it->second.func->cls->name, it->second.func->name,
1545 meth->cls->name, name);
1546 if (it->second.func->attrs & AttrPrivate) {
1547 it->second.hasPrivateAncestor = true;
1549 it->second.func = meth;
1550 it->second.attrs = attrs;
1551 it->second.hasAncestor = true;
1552 it->second.topLevel = true;
1553 if (it->first != name) {
1554 auto mte = it->second;
1555 info.rleaf->methods.erase(it);
1556 it = info.rleaf->methods.emplace(name, mte).first;
1558 return true;
1561 // If there's a parent, start by copying its methods
1562 if (auto const rparent = info.rleaf->parent) {
1563 for (auto& mte : rparent->methods) {
1564 // don't inherit the 86* methods.
1565 if (HPHP::Func::isSpecial(mte.first)) continue;
1566 auto const res = info.rleaf->methods.emplace(mte.first, mte.second);
1567 assertx(res.second);
1568 res.first->second.topLevel = false;
1569 ITRACE(9,
1570 " {}: inheriting method {}::{}\n",
1571 info.rleaf->cls->name,
1572 rparent->cls->name, mte.first);
1573 continue;
1577 uint32_t idx = info.rleaf->methods.size();
1579 // Now add our methods.
1580 for (auto& m : info.rleaf->cls->methods) {
1581 auto res = info.rleaf->methods.emplace(
1582 m->name,
1583 MethTabEntry { m.get(), m->attrs, false, true }
1585 if (res.second) {
1586 res.first->second.idx = idx++;
1587 ITRACE(9,
1588 " {}: adding method {}::{}\n",
1589 info.rleaf->cls->name,
1590 info.rleaf->cls->name, m->name);
1591 continue;
1593 if (m->attrs & AttrTrait && m->attrs & AttrAbstract) {
1594 // abstract methods from traits never override anything.
1595 continue;
1597 if (!methodOverride(res.first, m.get(), m->attrs, m->name)) return false;
1600 // If our traits were previously flattened, we're done.
1601 if (info.rleaf->cls->attrs & AttrNoExpandTrait) return true;
1603 try {
1604 TMIData tmid;
1605 for (auto const t : info.rleaf->usedTraits) {
1606 std::vector<const MethTabEntryPair*> methods(t->methods.size());
1607 for (auto& m : t->methods) {
1608 if (HPHP::Func::isSpecial(m.first)) continue;
1609 assertx(!methods[m.second.idx]);
1610 methods[m.second.idx] = mteFromElm(m);
1612 for (auto const m : methods) {
1613 if (!m) continue;
1614 TraitMethod traitMethod { t, m->second.func, m->second.attrs };
1615 tmid.add(traitMethod, m->first);
1617 for (auto const c : info.index.classClosureMap[t->cls]) {
1618 auto const invoke = find_method(c, s_invoke.get());
1619 assertx(invoke);
1620 info.index.classExtraMethodMap[info.rleaf->cls].insert(invoke);
1624 for (auto const& precRule : info.rleaf->cls->traitPrecRules) {
1625 tmid.applyPrecRule(precRule, info.rleaf);
1627 auto const& aliasRules = info.rleaf->cls->traitAliasRules;
1628 tmid.applyAliasRules(aliasRules.begin(), aliasRules.end(), info.rleaf);
1629 auto traitMethods = tmid.finish(info.rleaf);
1630 // Import the methods.
1631 for (auto const& mdata : traitMethods) {
1632 auto const method = mdata.tm.method;
1633 auto attrs = mdata.tm.modifiers;
1634 if (attrs == AttrNone) {
1635 attrs = method->attrs;
1636 } else {
1637 Attr attrMask = (Attr)(AttrPublic | AttrProtected | AttrPrivate |
1638 AttrAbstract | AttrFinal);
1639 attrs = (Attr)((attrs & attrMask) |
1640 (method->attrs & ~attrMask));
1642 auto res = info.rleaf->methods.emplace(
1643 mdata.name,
1644 MethTabEntry { method, attrs, false, true }
1646 if (res.second) {
1647 res.first->second.idx = idx++;
1648 ITRACE(9,
1649 " {}: adding trait method {}::{} as {}\n",
1650 info.rleaf->cls->name,
1651 method->cls->name, method->name, mdata.name);
1652 } else {
1653 if (attrs & AttrAbstract) continue;
1654 if (res.first->second.func->cls == info.rleaf->cls) continue;
1655 if (!methodOverride(res.first, method, attrs, mdata.name)) {
1656 return false;
1658 res.first->second.idx = idx++;
1660 info.index.classExtraMethodMap[info.rleaf->cls].insert(
1661 const_cast<php::Func*>(method));
1663 } catch (TMIOps::TMIException& ex) {
1664 ITRACE(2,
1665 "build_class_methods failed for `{}' importing traits: {}\n",
1666 info.rleaf->cls->name, ex.what());
1667 return false;
1670 return true;
1673 bool enforce_in_maybe_sealed_parent_whitelist(
1674 const ClassInfo* cls,
1675 const ClassInfo* parent);
1677 bool build_cls_info_rec(BuildClsInfo& info,
1678 const ClassInfo* rparent,
1679 bool fromTrait) {
1680 if (!rparent) return true;
1681 if (!enforce_in_maybe_sealed_parent_whitelist(rparent, rparent->parent)) {
1682 return false;
1684 if (!build_cls_info_rec(info, rparent->parent, false)) {
1685 return false;
1688 for (auto const iface : rparent->declInterfaces) {
1689 if (!enforce_in_maybe_sealed_parent_whitelist(rparent, iface)) {
1690 return false;
1692 if (!build_cls_info_rec(info, iface, fromTrait)) {
1693 return false;
1697 for (auto const trait : rparent->usedTraits) {
1698 if (!enforce_in_maybe_sealed_parent_whitelist(rparent, trait)) {
1699 return false;
1701 if (!build_cls_info_rec(info, trait, true)) return false;
1704 if (rparent->cls->attrs & AttrInterface) {
1706 * Make a flattened table of all the interfaces implemented by the class.
1708 info.rleaf->implInterfaces[rparent->cls->name] = rparent;
1709 } else {
1710 if (!fromTrait &&
1711 !build_class_properties(info, rparent)) {
1712 return false;
1715 // We don't need a method table for interfaces, and rather than
1716 // building the table recursively from scratch we just use the
1717 // parent's already constructed method table, and this class's
1718 // local method table (and traits if necessary).
1719 if (rparent == info.rleaf) {
1720 if (!build_class_methods(info)) return false;
1724 if (!build_class_constants(info, rparent, fromTrait)) return false;
1726 return true;
1729 const StaticString s___Sealed("__Sealed");
1730 bool enforce_in_maybe_sealed_parent_whitelist(
1731 const ClassInfo* cls,
1732 const ClassInfo* parent) {
1733 // if our parent isn't sealed, then we're fine.
1734 if (!parent || !(parent->cls->attrs & AttrSealed)) return true;
1735 const UserAttributeMap& parent_attrs = parent->cls->userAttributes;
1736 assert(parent_attrs.find(s___Sealed.get()) != parent_attrs.end());
1737 const auto& parent_sealed_attr = parent_attrs.find(s___Sealed.get())->second;
1738 bool in_sealed_whitelist = false;
1739 IterateV(parent_sealed_attr.m_data.parr,
1740 [&in_sealed_whitelist, cls](TypedValue v) -> bool {
1741 if (v.m_data.pstr->same(cls->cls->name)) {
1742 in_sealed_whitelist = true;
1743 return true;
1745 return false;
1747 return in_sealed_whitelist;
1751 * Note: a cyclic inheritance chain will blow this up, but right now
1752 * we'll never get here in that case because hphpc currently just
1753 * modifies classes not to have that situation. TODO(#3649211).
1755 * This function return false if we are certain instantiating cinfo
1756 * would be a fatal at runtime.
1758 bool build_cls_info(IndexData& index, ClassInfo* cinfo) {
1759 auto info = BuildClsInfo{ index, cinfo };
1760 if (!build_cls_info_rec(info, cinfo, false)) return false;
1761 return true;
1764 //////////////////////////////////////////////////////////////////////
1766 void add_system_constants_to_index(IndexData& index) {
1767 for (auto cnsPair : Native::getConstants()) {
1768 assertx(cnsPair.second.m_type != KindOfUninit ||
1769 cnsPair.second.dynamic());
1770 auto t = cnsPair.second.dynamic() ?
1771 TInitCell : from_cell(cnsPair.second);
1773 ConstInfoConcurrentMap::accessor acc;
1774 if (index.constants.insert(acc, cnsPair.first)) {
1775 acc->second.func = nullptr;
1776 acc->second.type = t;
1777 acc->second.system = true;
1778 acc->second.readonly = false;
1783 //////////////////////////////////////////////////////////////////////
1785 struct ClassInfoData {
1786 // Map from name to classes that directly use that name (as parent,
1787 // interface or trait).
1788 hphp_hash_map<SString,
1789 CompactVector<const php::Class*>,
1790 string_data_hash,
1791 string_data_isame> classUsers;
1792 // Map from php::Class to number of dependencies, used in
1793 // conjunction with classUsers above.
1794 hphp_hash_map<const php::Class*, uint32_t> classDepCounts;
1796 uint32_t cqFront{};
1797 uint32_t cqBack{};
1798 std::vector<const php::Class*> classQueue;
1799 bool hasPseudoCycles{};
1802 // We want const qualifiers on various index data structures for php
1803 // object pointers, but during index creation time we need to
1804 // manipulate some of their attributes (changing the representation).
1805 // This little wrapper keeps the const_casting out of the main line of
1806 // code below.
1807 void attribute_setter(const Attr& attrs, bool set, Attr attr) {
1808 attrSetter(const_cast<Attr&>(attrs), set, attr);
1811 void add_unit_to_index(IndexData& index, const php::Unit& unit) {
1812 hphp_hash_map<
1813 const php::Class*,
1814 hphp_hash_set<const php::Class*>
1815 > closureMap;
1817 for (auto& c : unit.classes) {
1818 auto const attrsToRemove =
1819 AttrUnique |
1820 AttrPersistent |
1821 AttrNoOverride |
1822 AttrNoOverrideMagicGet |
1823 AttrNoOverrideMagicSet |
1824 AttrNoOverrideMagicIsset |
1825 AttrNoOverrideMagicUnset;
1826 attribute_setter(c->attrs, false, attrsToRemove);
1828 // Manually set closure classes to be unique to maintain invariance.
1829 if (is_closure(*c)) {
1830 attrSetter(c->attrs, true, AttrUnique);
1833 if (c->attrs & AttrEnum) {
1834 index.enums.emplace(c->name, c.get());
1838 * A class can be defined with the same name as a builtin in the
1839 * repo. Any such attempts will fatal at runtime, so we can safely
1840 * ignore any such definitions. This ensures that names referring
1841 * to builtins are always fully resolvable.
1843 auto const classes = find_range(index.classes, c->name);
1844 if (classes.begin() != classes.end()) {
1845 if (c->attrs & AttrBuiltin) {
1846 index.classes.erase(classes.begin(), classes.end());
1847 } else if (classes.begin()->second->attrs & AttrBuiltin) {
1848 assertx(std::next(classes.begin()) == classes.end());
1849 continue;
1852 index.classes.emplace(c->name, c.get());
1854 for (auto& m : c->methods) {
1855 attribute_setter(m->attrs, false, AttrNoOverride);
1856 index.methods.insert({m->name, m.get()});
1857 if (m->attrs & AttrInterceptable) {
1858 index.any_interceptable_functions = true;
1861 if (RuntimeOption::RepoAuthoritative) {
1862 uint64_t refs = 0, cur = 1;
1863 bool anyByRef = false;
1864 for (auto& p : m->params) {
1865 if (p.byRef) {
1866 refs |= cur;
1867 anyByRef = true;
1869 // It doesn't matter that we lose parameters beyond the 64th,
1870 // for those, we'll conservatively check everything anyway.
1871 cur <<= 1;
1873 if (anyByRef) {
1874 // Multiple methods with the same name will be combined in the same
1875 // cell, thus we use |=. This only makes sense in WholeProgram mode
1876 // since we use this field to check that no functions uses its n-th
1877 // parameter byref, which requires global knowledge.
1878 index.method_ref_params_by_name[m->name] |= refs;
1883 if (c->closureContextCls) {
1884 closureMap[c->closureContextCls].insert(c.get());
1888 if (!closureMap.empty()) {
1889 for (auto const& c1 : closureMap) {
1890 auto& s = index.classClosureMap[c1.first];
1891 for (auto const& c2 : c1.second) {
1892 s.push_back(c2);
1897 for (auto& f : unit.funcs) {
1899 * A function can be defined with the same name as a builtin in the
1900 * repo. Any such attempts will fatal at runtime, so we can safely ignore
1901 * any such definitions. This ensures that names referring to builtins are
1902 * always fully resolvable.
1904 auto const funcs = index.funcs.equal_range(f->name);
1905 if (funcs.first != funcs.second) {
1906 if (f->attrs & AttrIsMethCaller) {
1907 // meth_caller has builtin attr and can have duplicates definitions
1908 assertx(std::next(funcs.first) == funcs.second);
1909 assertx(funcs.first->second->attrs & AttrIsMethCaller);
1910 continue;
1913 auto const& old_func = funcs.first->second;
1914 // If there is a builtin, it will always be the first (and only) func on
1915 // the list.
1916 if (old_func->attrs & AttrBuiltin) {
1917 always_assert(!(f->attrs & AttrBuiltin));
1918 continue;
1920 if (f->attrs & AttrBuiltin) index.funcs.erase(funcs.first, funcs.second);
1922 if (f->attrs & AttrInterceptable) index.any_interceptable_functions = true;
1923 index.funcs.insert({f->name, f.get()});
1926 for (auto& ta : unit.typeAliases) {
1927 index.typeAliases.insert({ta->name, ta.get()});
1930 for (auto& rec : unit.records) {
1931 index.records.insert({rec->name, rec.get()});
1934 for (auto& ca : unit.classAliases) {
1935 index.classAliases.insert(ca.first);
1936 index.classAliases.insert(ca.second);
1940 struct NamingEnv {
1941 NamingEnv(php::Program* program, IndexData& index, ClassInfoData& cid) :
1942 program{program}, index{index}, cid{cid} {}
1944 struct Define;
1946 ClassInfo* try_lookup(SString name) const {
1947 auto const range = index.classInfo.equal_range(name);
1948 // We're resolving in topological order; we shouldn't be here
1949 // unless we know there's at least one resolution of this class.
1950 assertx(range.first != range.second);
1951 // Common case will be exactly one resolution. Lets avoid the
1952 // copy_range, and iteration for that case.
1953 if (std::next(range.first) == range.second) {
1954 return range.first->second;
1956 auto const it = names.find(name);
1957 if (it != end(names)) return it->second;
1958 return nullptr;
1961 ClassInfo* lookup(SString name) const {
1962 auto const ret = try_lookup(name);
1963 assertx(ret);
1964 return ret;
1967 php::Program* program;
1968 IndexData& index;
1969 ClassInfoData& cid;
1970 std::unordered_multimap<
1971 const php::Class*,
1972 ClassInfo*,
1973 pointer_hash<php::Class>> resolved;
1974 private:
1975 ISStringToOne<ClassInfo> names;
1978 struct NamingEnv::Define {
1979 explicit Define(NamingEnv& env, SString n, ClassInfo* ci,
1980 const php::Class* cls)
1981 : env(env), n(n) {
1982 ITRACE(2, "defining {} for {}\n", n, cls->name);
1983 always_assert(!env.names.count(n));
1984 env.names[n] = ci;
1986 ~Define() {
1987 env.names.erase(n);
1990 Define(const Define&) = delete;
1991 Define& operator=(const Define&) = delete;
1993 private:
1994 Trace::Indent indent;
1995 NamingEnv& env;
1996 SString n;
1999 using ClonedClosureMap = hphp_hash_map<
2000 php::Class*,
2001 std::pair<std::unique_ptr<php::Class>, uint32_t>
2004 std::unique_ptr<php::Func> clone_meth_helper(
2005 php::Class* newContext,
2006 const php::Func* origMeth,
2007 std::unique_ptr<php::Func> cloneMeth,
2008 std::atomic<uint32_t>& nextFuncId,
2009 uint32_t& nextClass,
2010 ClonedClosureMap& clonedClosures);
2012 std::unique_ptr<php::Class> clone_closure(php::Class* newContext,
2013 php::Class* cls,
2014 std::atomic<uint32_t>& nextFuncId,
2015 uint32_t& nextClass,
2016 ClonedClosureMap& clonedClosures) {
2017 auto clone = std::make_unique<php::Class>(*cls);
2018 assertx(clone->closureContextCls);
2019 clone->closureContextCls = newContext;
2020 clone->unit = newContext->unit;
2021 auto i = 0;
2022 for (auto& cloneMeth : clone->methods) {
2023 cloneMeth = clone_meth_helper(clone.get(),
2024 cls->methods[i++].get(),
2025 std::move(cloneMeth),
2026 nextFuncId,
2027 nextClass,
2028 clonedClosures);
2029 if (!cloneMeth) return nullptr;
2031 return clone;
2034 std::unique_ptr<php::Func> clone_meth_helper(
2035 php::Class* newContext,
2036 const php::Func* origMeth,
2037 std::unique_ptr<php::Func> cloneMeth,
2038 std::atomic<uint32_t>& nextFuncId,
2039 uint32_t& nextClass,
2040 ClonedClosureMap& clonedClosures) {
2042 cloneMeth->cls = newContext;
2043 cloneMeth->idx = nextFuncId.fetch_add(1, std::memory_order_relaxed);
2044 if (!cloneMeth->originalFilename) {
2045 cloneMeth->originalFilename = origMeth->unit->filename;
2047 if (!cloneMeth->originalUnit) {
2048 cloneMeth->originalUnit = origMeth->unit;
2050 cloneMeth->unit = newContext->unit;
2052 auto const recordClosure = [&] (uint32_t* clsId) {
2053 auto const cls = origMeth->unit->classes[*clsId].get();
2054 auto& elm = clonedClosures[cls];
2055 if (!elm.first) {
2056 elm.first = clone_closure(newContext->closureContextCls ?
2057 newContext->closureContextCls : newContext,
2058 cls, nextFuncId, nextClass, clonedClosures);
2059 if (!elm.first) return false;
2060 elm.second = nextClass++;
2062 *clsId = elm.second;
2063 return true;
2066 hphp_fast_map<size_t, hphp_fast_map<size_t, uint32_t>> updates;
2067 for (size_t bid = 0; bid < cloneMeth->blocks.size(); bid++) {
2068 auto const b = cloneMeth->blocks[bid].get();
2069 for (size_t ix = 0; ix < b->hhbcs.size(); ix++) {
2070 auto const& bc = b->hhbcs[ix];
2071 switch (bc.op) {
2072 case Op::CreateCl: {
2073 auto clsId = bc.CreateCl.arg2;
2074 if (!recordClosure(&clsId)) return nullptr;
2075 updates[bid][ix] = clsId;
2076 break;
2078 case Op::DefCls:
2079 case Op::DefClsNop:
2080 return nullptr;
2081 default:
2082 break;
2087 for (auto elm : updates) {
2088 auto& cblk = cloneMeth->blocks[elm.first];
2089 auto const blk = cblk.mutate();
2090 for (auto const& ix : elm.second) {
2091 blk->hhbcs[ix.first].CreateCl.arg2 = ix.second;
2095 return cloneMeth;
2098 std::unique_ptr<php::Func> clone_meth(php::Class* newContext,
2099 const php::Func* origMeth,
2100 SString name,
2101 Attr attrs,
2102 std::atomic<uint32_t>& nextFuncId,
2103 uint32_t& nextClass,
2104 ClonedClosureMap& clonedClosures) {
2106 auto cloneMeth = std::make_unique<php::Func>(*origMeth);
2107 cloneMeth->name = name;
2108 cloneMeth->attrs = attrs | AttrTrait;
2109 return clone_meth_helper(newContext, origMeth, std::move(cloneMeth),
2110 nextFuncId, nextClass, clonedClosures);
2113 bool merge_xinits(Attr attr,
2114 std::vector<std::unique_ptr<php::Func>>& clones,
2115 ClassInfo* cinfo,
2116 std::atomic<uint32_t>& nextFuncId,
2117 uint32_t& nextClass,
2118 ClonedClosureMap& clonedClosures) {
2119 auto const cls = const_cast<php::Class*>(cinfo->cls);
2120 auto const xinitName = [&]() {
2121 switch (attr) {
2122 case AttrNone : return s_86pinit.get();
2123 case AttrStatic: return s_86sinit.get();
2124 case AttrLSB : return s_86linit.get();
2125 default: always_assert(false);
2127 }();
2129 auto const xinitMatch = [&](Attr prop_attrs) {
2130 auto mask = AttrStatic | AttrLSB;
2131 switch (attr) {
2132 case AttrNone: return (prop_attrs & mask) == AttrNone;
2133 case AttrStatic: return (prop_attrs & mask) == AttrStatic;
2134 case AttrLSB: return (prop_attrs & mask) == mask;
2135 default: always_assert(false);
2139 auto const needsXinit = [&] {
2140 for (auto const& p : cinfo->traitProps) {
2141 if (xinitMatch(p.attrs) &&
2142 p.val.m_type == KindOfUninit &&
2143 !(p.attrs & AttrLateInit)) {
2144 ITRACE(5, "merge_xinits: {}: Needs merge for {}{}prop `{}'\n",
2145 cls->name, attr & AttrStatic ? "static " : "",
2146 attr & AttrLSB ? "lsb " : "", p.name);
2147 return true;
2150 return false;
2151 }();
2153 if (!needsXinit) return true;
2155 std::unique_ptr<php::Func> empty;
2156 auto& xinit = [&] () -> std::unique_ptr<php::Func>& {
2157 for (auto& m : cls->methods) {
2158 if (m->name == xinitName) return m;
2160 return empty;
2161 }();
2163 auto merge_one = [&] (const php::Func* func) {
2164 if (!xinit) {
2165 ITRACE(5, " - cloning {}::{} as {}::{}\n",
2166 func->cls->name, func->name, cls->name, xinitName);
2167 xinit = clone_meth(cls, func, func->name, func->attrs, nextFuncId,
2168 nextClass, clonedClosures);
2169 return xinit != nullptr;
2172 ITRACE(5, " - appending {}::{} into {}::{}\n",
2173 func->cls->name, func->name, cls->name, xinitName);
2174 return append_func(xinit.get(), *func);
2177 for (auto t : cinfo->usedTraits) {
2178 auto it = t->methods.find(xinitName);
2179 if (it != t->methods.end()) {
2180 if (!merge_one(it->second.func)) {
2181 ITRACE(5, "merge_xinits: failed to merge {}::{}\n",
2182 it->second.func->cls->name, it->second.func->name);
2183 return false;
2188 assertx(xinit);
2189 if (empty) {
2190 ITRACE(5, "merge_xinits: adding {}::{} to method table\n",
2191 xinit->cls->name, xinit->name);
2192 assertx(&empty == &xinit);
2193 DEBUG_ONLY auto res = cinfo->methods.emplace(
2194 xinit->name,
2195 MethTabEntry { xinit.get(), xinit->attrs, false, true }
2197 assertx(res.second);
2198 clones.push_back(std::move(xinit));
2201 return true;
2204 void rename_closure(NamingEnv& env, php::Class* cls) {
2205 auto n = cls->name->slice();
2206 auto const p = n.find(';');
2207 if (p != std::string::npos) {
2208 n = n.subpiece(0, p);
2210 auto const newName = makeStaticString(NewAnonymousClassName(n));
2211 assertx(!env.index.classes.count(newName));
2212 cls->name = newName;
2213 env.index.classes.emplace(newName, cls);
2216 void preresolve(NamingEnv& env, const php::Class* cls);
2218 void flatten_traits(NamingEnv& env, ClassInfo* cinfo) {
2219 bool hasConstProp = false;
2220 for (auto t : cinfo->usedTraits) {
2221 if (t->usedTraits.size() && !(t->cls->attrs & AttrNoExpandTrait)) {
2222 ITRACE(5, "Not flattening {} because of {}\n",
2223 cinfo->cls->name, t->cls->name);
2224 return;
2226 if (t->cls->attrs & AttrHasConstProps) hasConstProp = true;
2228 auto const cls = const_cast<php::Class*>(cinfo->cls);
2229 if (hasConstProp) cls->attrs |= AttrHasConstProps;
2230 std::vector<MethTabEntryPair*> methodsToAdd;
2231 for (auto& ent : cinfo->methods) {
2232 if (!ent.second.topLevel || ent.second.func->cls == cinfo->cls) {
2233 continue;
2235 always_assert(ent.second.func->cls->attrs & AttrTrait);
2236 methodsToAdd.push_back(mteFromElm(ent));
2239 auto const it = env.index.classExtraMethodMap.find(cinfo->cls);
2241 if (!methodsToAdd.empty()) {
2242 assertx(it != env.index.classExtraMethodMap.end());
2243 std::sort(begin(methodsToAdd), end(methodsToAdd),
2244 [] (const MethTabEntryPair* a, const MethTabEntryPair* b) {
2245 return a->second.idx < b->second.idx;
2247 } else if (debug && it != env.index.classExtraMethodMap.end()) {
2248 // When building the ClassInfos, we proactively added all closures
2249 // from usedTraits to classExtraMethodMap; but now we're going to
2250 // start from the used methods, and deduce which closures actually
2251 // get pulled in. Its possible *none* of the methods got used, in
2252 // which case, we won't need their closures either. To be safe,
2253 // verify that the only things in classExtraMethodMap are
2254 // closures.
2255 for (DEBUG_ONLY auto const f : it->second) {
2256 assertx(f->isClosureBody);
2260 std::vector<std::unique_ptr<php::Func>> clones;
2261 ClonedClosureMap clonedClosures;
2262 uint32_t nextClassId = cls->unit->classes.size();
2263 for (auto const ent : methodsToAdd) {
2264 auto clone = clone_meth(cls, ent->second.func, ent->first,
2265 ent->second.attrs, env.program->nextFuncId,
2266 nextClassId, clonedClosures);
2267 if (!clone) {
2268 ITRACE(5, "Not flattening {} because {}::{} could not be cloned\n",
2269 cls->name, ent->second.func->cls->name, ent->first);
2270 return;
2273 clone->attrs |= AttrTrait;
2274 ent->second.attrs |= AttrTrait;
2275 ent->second.func = clone.get();
2276 clones.push_back(std::move(clone));
2279 if (cinfo->traitProps.size()) {
2280 if (!merge_xinits(AttrNone, clones, cinfo,
2281 env.program->nextFuncId, nextClassId, clonedClosures) ||
2282 !merge_xinits(AttrStatic, clones, cinfo,
2283 env.program->nextFuncId, nextClassId, clonedClosures) ||
2284 !merge_xinits(AttrLSB, clones, cinfo,
2285 env.program->nextFuncId, nextClassId, clonedClosures)) {
2286 ITRACE(5, "Not flattening {} because we couldn't merge the 86xinits\n",
2287 cls->name);
2288 return;
2292 // We're now committed to flattening.
2293 ITRACE(3, "Flattening {}\n", cls->name);
2294 if (it != env.index.classExtraMethodMap.end()) it->second.clear();
2295 for (auto const& p : cinfo->traitProps) {
2296 ITRACE(5, " - prop {}\n", p.name);
2297 cls->properties.push_back(p);
2298 cls->properties.back().attrs |= AttrTrait;
2300 cinfo->traitProps.clear();
2302 if (clones.size()) {
2303 auto cinit = cls->methods.size() &&
2304 cls->methods.back()->name == s_86cinit.get() ?
2305 std::move(cls->methods.back()) : nullptr;
2306 if (cinit) cls->methods.pop_back();
2307 for (auto& clone : clones) {
2308 ITRACE(5, " - meth {}\n", clone->name);
2309 cinfo->methods.find(clone->name)->second.func = clone.get();
2310 cls->methods.push_back(std::move(clone));
2312 if (cinit) cls->methods.push_back(std::move(cinit));
2314 if (clonedClosures.size()) {
2315 auto& classClosures = env.index.classClosureMap[cls];
2316 cls->unit->classes.resize(nextClassId);
2317 for (auto& ent : clonedClosures) {
2318 auto const clo = ent.second.first.get();
2319 rename_closure(env, clo);
2320 ITRACE(5, " - closure {} as {}\n", ent.first->name, clo->name);
2321 assertx(clo->closureContextCls == cls);
2322 assertx(clo->unit == cls->unit);
2323 classClosures.push_back(clo);
2325 cls->unit->classes[ent.second.second] = std::move(ent.second.first);
2326 preresolve(env, clo);
2331 struct EqHash {
2332 bool operator()(const PreClass::ClassRequirement& a,
2333 const PreClass::ClassRequirement& b) const {
2334 return a.is_same(&b);
2336 size_t operator()(const PreClass::ClassRequirement& a) const {
2337 return a.hash();
2341 hphp_hash_set<PreClass::ClassRequirement, EqHash, EqHash> reqs;
2343 for (auto const t : cinfo->usedTraits) {
2344 for (auto const& req : t->cls->requirements) {
2345 if (reqs.empty()) {
2346 for (auto const& r : cls->requirements) {
2347 reqs.insert(r);
2350 if (reqs.insert(req).second) cls->requirements.push_back(req);
2354 cls->attrs |= AttrNoExpandTrait;
2357 void resolve_combinations(NamingEnv& env,
2358 const php::Class* cls) {
2360 auto resolve_one = [&] (SString name) {
2361 if (env.try_lookup(name)) return true;
2362 auto const range = copy_range(env.index.classInfo, name);
2363 assertx(range.size() > 1);
2364 for (auto& kv : range) {
2365 NamingEnv::Define def{env, name, kv.second, cls};
2366 resolve_combinations(env, cls);
2368 return false;
2371 // Recurse with all combinations of bases and interfaces in the
2372 // naming environment.
2373 if (cls->parentName) {
2374 if (!resolve_one(cls->parentName)) return;
2376 for (auto& iname : cls->interfaceNames) {
2377 if (!resolve_one(iname)) return;
2379 for (auto& tname : cls->usedTraitNames) {
2380 if (!resolve_one(tname)) return;
2383 // Everything is defined in the naming environment here. (We
2384 // returned early if something didn't exist.)
2386 auto cinfo = std::make_unique<ClassInfo>();
2387 cinfo->cls = cls;
2388 if (cls->parentName) {
2389 cinfo->parent = env.lookup(cls->parentName);
2390 cinfo->baseList = cinfo->parent->baseList;
2391 if (cinfo->parent->cls->attrs & (AttrInterface | AttrTrait)) {
2392 ITRACE(2,
2393 "Resolve combinations failed for `{}' because "
2394 "its parent `{}' is not a class\n",
2395 cls->name, cls->parentName);
2396 return;
2399 cinfo->baseList.push_back(cinfo.get());
2401 for (auto& iname : cls->interfaceNames) {
2402 auto const iface = env.lookup(iname);
2403 if (!(iface->cls->attrs & AttrInterface)) {
2404 ITRACE(2,
2405 "Resolve combinations failed for `{}' because `{}' "
2406 "is not an interface\n",
2407 cls->name, iname);
2408 return;
2410 cinfo->declInterfaces.push_back(iface);
2413 for (auto& tname : cls->usedTraitNames) {
2414 auto const trait = env.lookup(tname);
2415 if (!(trait->cls->attrs & AttrTrait)) {
2416 ITRACE(2,
2417 "Resolve combinations failed for `{}' because `{}' "
2418 "is not a trait\n",
2419 cls->name, tname);
2420 return;
2422 cinfo->usedTraits.push_back(trait);
2425 if (!build_cls_info(env.index, cinfo.get())) return;
2427 ITRACE(2, " resolved: {}\n", cls->name);
2428 if (Trace::moduleEnabled(Trace::hhbbc_index, 3)) {
2429 for (auto const DEBUG_ONLY& iface : cinfo->implInterfaces) {
2430 ITRACE(3, " implements: {}\n", iface.second->cls->name);
2432 for (auto const DEBUG_ONLY& trait : cinfo->usedTraits) {
2433 ITRACE(3, " uses: {}\n", trait->cls->name);
2436 cinfo->baseList.shrink_to_fit();
2437 env.resolved.emplace(cls, cinfo.get());
2438 env.index.classInfo.emplace(cls->name, cinfo.get());
2439 env.index.allClassInfos.push_back(std::move(cinfo));
2442 void preresolve(NamingEnv& env, const php::Class* cls) {
2443 assertx(!env.resolved.count(cls));
2445 ITRACE(2, "preresolve: {}:{}\n", cls->name, (void*)cls);
2447 Trace::Indent indent;
2448 if (debug) {
2449 if (cls->parentName) {
2450 assertx(env.index.classInfo.count(cls->parentName));
2452 for (DEBUG_ONLY auto& i : cls->interfaceNames) {
2453 assertx(env.index.classInfo.count(i));
2455 for (DEBUG_ONLY auto& t : cls->usedTraitNames) {
2456 assertx(env.index.classInfo.count(t));
2459 resolve_combinations(env, cls);
2462 ITRACE(3, "preresolve: {}:{} ({} resolutions)\n",
2463 cls->name, (void*)cls, env.resolved.count(cls));
2465 auto const range = find_range(env.resolved, cls);
2466 if (begin(range) != end(range)) {
2467 auto const& users = env.cid.classUsers[cls->name];
2468 for (auto const cu : users) {
2469 auto const it = env.cid.classDepCounts.find(cu);
2470 if (it == env.cid.classDepCounts.end()) {
2471 assertx(env.cid.hasPseudoCycles);
2472 continue;
2474 auto& depCount = it->second;
2475 assertx(depCount);
2476 if (!--depCount) {
2477 env.cid.classDepCounts.erase(it);
2478 ITRACE(5, " enqueue: {}:{}\n", cu->name, (void*)cu);
2479 env.cid.classQueue[env.cid.cqBack++] = cu;
2480 } else {
2481 ITRACE(6, " depcount: {}:{} = {}\n", cu->name, (void*)cu, depCount);
2484 if (options.FlattenTraits &&
2485 !(cls->attrs & AttrNoExpandTrait) &&
2486 !cls->usedTraitNames.empty() &&
2487 std::next(begin(range)) == end(range) &&
2488 env.index.classes.count(cls->name) == 1) {
2489 Trace::Indent indent;
2490 auto const cinfo = begin(range)->second;
2491 assertx(!cinfo->usedTraits.empty());
2492 flatten_traits(env, cinfo);
2497 void compute_subclass_list_rec(IndexData& index,
2498 ClassInfo* cinfo,
2499 ClassInfo* csub) {
2500 for (auto const ctrait : csub->usedTraits) {
2501 auto const ct = const_cast<ClassInfo*>(ctrait);
2502 ct->subclassList.push_back(cinfo);
2503 compute_subclass_list_rec(index, cinfo, ct);
2507 void compute_subclass_list(IndexData& index) {
2508 trace_time _("compute subclass list");
2509 auto fixupTraits = false;
2510 for (auto& cinfo : index.allClassInfos) {
2511 if (cinfo->cls->attrs & AttrInterface) continue;
2512 for (auto& cparent : cinfo->baseList) {
2513 cparent->subclassList.push_back(cinfo.get());
2515 if (!(cinfo->cls->attrs & AttrNoExpandTrait) &&
2516 cinfo->usedTraits.size()) {
2517 fixupTraits = true;
2518 compute_subclass_list_rec(index, cinfo.get(), cinfo.get());
2520 // Also add instantiable classes to their interface's subclassLists
2521 if (cinfo->cls->attrs & (AttrTrait | AttrEnum | AttrAbstract)) continue;
2522 for (auto& ipair : cinfo->implInterfaces) {
2523 auto impl = const_cast<ClassInfo*>(ipair.second);
2524 impl->subclassList.push_back(cinfo.get());
2528 for (auto& cinfo : index.allClassInfos) {
2529 auto& sub = cinfo->subclassList;
2530 if (fixupTraits && cinfo->cls->attrs & AttrTrait) {
2531 // traits can be reached by multiple paths, so we need to uniquify
2532 // their subclassLists.
2533 std::sort(begin(sub), end(sub));
2534 sub.erase(
2535 std::unique(begin(sub), end(sub)),
2536 end(sub)
2539 sub.shrink_to_fit();
2543 bool define_func_family(IndexData& index, ClassInfo* cinfo,
2544 SString name, const php::Func* func = nullptr) {
2545 FuncFamily::PFuncVec funcs{};
2546 auto containsInterceptables = false;
2547 for (auto const cleaf : cinfo->subclassList) {
2548 auto const leafFn = [&] () -> const MethTabEntryPair* {
2549 auto const leafFnIt = cleaf->methods.find(name);
2550 if (leafFnIt == end(cleaf->methods)) return nullptr;
2551 return mteFromIt(leafFnIt);
2552 }();
2553 if (!leafFn) continue;
2554 if (leafFn->second.func->attrs & AttrInterceptable) {
2555 containsInterceptables = true;
2557 funcs.push_back(leafFn);
2560 if (funcs.empty()) return false;
2562 std::sort(begin(funcs), end(funcs),
2563 [&] (const MethTabEntryPair* a, const MethTabEntryPair* b) {
2564 // We want a canonical order for the family. Putting the
2565 // one corresponding to cinfo first makes sense, because
2566 // the first one is used as the name for FCall hint, after
2567 // that, sort by name so that different case spellings
2568 // come in the same order.
2569 if (a->second.func == b->second.func) return false;
2570 if (func) {
2571 if (b->second.func == func) return false;
2572 if (a->second.func == func) return true;
2574 if (auto d = a->first->compare(b->first)) {
2575 if (!func) {
2576 if (b->first == name) return false;
2577 if (a->first == name) return true;
2579 return d < 0;
2581 return std::less<const void*>{}(a->second.func, b->second.func);
2583 funcs.erase(
2584 std::unique(begin(funcs), end(funcs),
2585 [] (const MethTabEntryPair* a, const MethTabEntryPair* b) {
2586 return a->second.func == b->second.func;
2588 end(funcs)
2591 funcs.shrink_to_fit();
2593 if (Trace::moduleEnabled(Trace::hhbbc_index, 4)) {
2594 FTRACE(4, "define_func_family: {}::{}:\n",
2595 cinfo->cls->name, name);
2596 for (auto const DEBUG_ONLY func : funcs) {
2597 FTRACE(4, " {}::{}\n",
2598 func->second.func->cls->name, func->second.func->name);
2602 cinfo->methodFamilies.emplace(
2603 std::piecewise_construct,
2604 std::forward_as_tuple(name),
2605 std::forward_as_tuple(std::move(funcs), containsInterceptables)
2608 return true;
2611 void build_abstract_func_families(IndexData& data, ClassInfo* cinfo) {
2612 std::vector<SString> extras;
2614 // We start by collecting the list of methods shared across all
2615 // subclasses of cinfo (including indirectly). And then add the
2616 // public methods which are not constructors and have no private
2617 // ancestors to the method families of cinfo. Note that this set
2618 // may be larger than the methods declared on cinfo and may also
2619 // be missing methods declared on cinfo. In practice this is the
2620 // set of methods we can depend on having accessible given any
2621 // object which is known to implement cinfo.
2622 auto it = cinfo->subclassList.begin();
2623 while (true) {
2624 if (it == cinfo->subclassList.end()) return;
2625 auto const sub = *it++;
2626 assertx(!(sub->cls->attrs & AttrInterface));
2627 if (sub == cinfo || (sub->cls->attrs & AttrAbstract)) continue;
2628 for (auto& par : sub->methods) {
2629 if (!par.second.hasPrivateAncestor &&
2630 (par.second.attrs & AttrPublic) &&
2631 !cinfo->methodFamilies.count(par.first) &&
2632 !cinfo->methods.count(par.first)) {
2633 extras.push_back(par.first);
2636 if (!extras.size()) return;
2637 break;
2640 auto end = extras.end();
2641 while (it != cinfo->subclassList.end()) {
2642 auto const sub = *it++;
2643 assertx(!(sub->cls->attrs & AttrInterface));
2644 if (sub == cinfo || (sub->cls->attrs & AttrAbstract)) continue;
2645 for (auto nameIt = extras.begin(); nameIt != end;) {
2646 auto const meth = sub->methods.find(*nameIt);
2647 if (meth == sub->methods.end() ||
2648 !(meth->second.attrs & AttrPublic) ||
2649 meth->second.hasPrivateAncestor) {
2650 *nameIt = *--end;
2651 if (end == extras.begin()) return;
2652 } else {
2653 ++nameIt;
2657 extras.erase(end, extras.end());
2659 if (Trace::moduleEnabled(Trace::hhbbc_index, 5)) {
2660 FTRACE(5, "Adding extra methods to {}:\n", cinfo->cls->name);
2661 for (auto const DEBUG_ONLY extra : extras) {
2662 FTRACE(5, " {}\n", extra);
2666 hphp_fast_set<SString> added;
2668 for (auto name : extras) {
2669 if (define_func_family(data, cinfo, name) &&
2670 (cinfo->cls->attrs & AttrInterface)) {
2671 added.emplace(name);
2675 if (cinfo->cls->attrs & AttrInterface) {
2676 for (auto& m : cinfo->cls->methods) {
2677 if (added.count(m->name)) {
2678 cinfo->methods.emplace(
2679 m->name,
2680 MethTabEntry { m.get(), m->attrs, false, true }
2685 return;
2688 void define_func_families(IndexData& index) {
2689 trace_time tracer("define_func_families");
2691 parallel::for_each(
2692 index.allClassInfos,
2693 [&] (const std::unique_ptr<ClassInfo>& cinfo) {
2694 if (cinfo->cls->attrs & AttrTrait) return;
2695 FTRACE(4, "Defining func families for {}\n", cinfo->cls->name);
2696 if (!(cinfo->cls->attrs & AttrInterface)) {
2697 for (auto& kv : cinfo->methods) {
2698 auto const mte = mteFromElm(kv);
2700 if (mte->second.attrs & AttrNoOverride) continue;
2701 if (is_special_method_name(mte->first)) continue;
2703 // We need function family for constructor even if it is private,
2704 // as `new static()` may still call a non-private constructor from
2705 // subclass.
2706 if (!mte->first->isame(s_construct.get()) &&
2707 mte->second.attrs & AttrPrivate) {
2708 continue;
2711 define_func_family(index, cinfo.get(), mte->first, mte->second.func);
2714 if (cinfo->cls->attrs & (AttrInterface | AttrAbstract)) {
2715 build_abstract_func_families(index, cinfo.get());
2722 * ConflictGraph maintains lists of interfaces that conflict with each other
2723 * due to being implemented by the same class.
2725 struct ConflictGraph {
2726 void add(const php::Class* i, const php::Class* j) {
2727 if (i == j) return;
2728 auto& conflicts = map[i];
2729 if (std::find(conflicts.begin(), conflicts.end(), j) != conflicts.end()) {
2730 return;
2732 conflicts.push_back(j);
2735 hphp_hash_map<const php::Class*,
2736 std::vector<const php::Class*>> map;
2740 * Trace information about interface conflict sets and the vtables computed
2741 * from them.
2743 void trace_interfaces(const IndexData& index, const ConflictGraph& cg) {
2744 // Compute what the vtable for each Class will look like, and build up a list
2745 // of all interfaces.
2746 struct Cls {
2747 const ClassInfo* cinfo;
2748 std::vector<const php::Class*> vtable;
2750 std::vector<Cls> classes;
2751 std::vector<const php::Class*> ifaces;
2752 size_t total_slots = 0, empty_slots = 0;
2753 for (auto& cinfo : index.allClassInfos) {
2754 if (cinfo->cls->attrs & AttrInterface) {
2755 ifaces.emplace_back(cinfo->cls);
2756 continue;
2758 if (cinfo->cls->attrs & (AttrTrait | AttrEnum | AttrAbstract)) continue;
2760 classes.emplace_back(Cls{cinfo.get()});
2761 auto& vtable = classes.back().vtable;
2762 for (auto& pair : cinfo->implInterfaces) {
2763 auto it = index.ifaceSlotMap.find(pair.second->cls);
2764 assert(it != end(index.ifaceSlotMap));
2765 auto const slot = it->second;
2766 if (slot >= vtable.size()) vtable.resize(slot + 1);
2767 vtable[slot] = pair.second->cls;
2770 total_slots += vtable.size();
2771 for (auto iface : vtable) if (iface == nullptr) ++empty_slots;
2774 Slot max_slot = 0;
2775 for (auto const& pair : index.ifaceSlotMap) {
2776 max_slot = std::max(max_slot, pair.second);
2779 // Sort the list of class vtables so the largest ones come first.
2780 auto class_cmp = [&](const Cls& a, const Cls& b) {
2781 return a.vtable.size() > b.vtable.size();
2783 std::sort(begin(classes), end(classes), class_cmp);
2785 // Sort the list of interfaces so the biggest conflict sets come first.
2786 auto iface_cmp = [&](const php::Class* a, const php::Class* b) {
2787 return cg.map.at(a).size() > cg.map.at(b).size();
2789 std::sort(begin(ifaces), end(ifaces), iface_cmp);
2791 std::string out;
2792 folly::format(&out, "{} interfaces, {} classes\n",
2793 ifaces.size(), classes.size());
2794 folly::format(&out,
2795 "{} vtable slots, {} empty vtable slots, max slot {}\n",
2796 total_slots, empty_slots, max_slot);
2797 folly::format(&out, "\n{:-^80}\n", " interface slots & conflict sets");
2798 for (auto iface : ifaces) {
2799 auto cgIt = cg.map.find(iface);
2800 if (cgIt == end(cg.map)) break;
2801 auto& conflicts = cgIt->second;
2803 folly::format(&out, "{:>40} {:3} {:2} [", iface->name,
2804 conflicts.size(),
2805 folly::get_default(index.ifaceSlotMap, iface));
2806 auto sep = "";
2807 for (auto conflict : conflicts) {
2808 folly::format(&out, "{}{}", sep, conflict->name);
2809 sep = ", ";
2811 folly::format(&out, "]\n");
2814 folly::format(&out, "\n{:-^80}\n", " class vtables ");
2815 for (auto& item : classes) {
2816 if (item.vtable.empty()) break;
2818 folly::format(&out, "{:>30}: [", item.cinfo->cls->name);
2819 auto sep = "";
2820 for (auto iface : item.vtable) {
2821 folly::format(&out, "{}{}", sep, iface ? iface->name->data() : "null");
2822 sep = ", ";
2824 folly::format(&out, "]\n");
2827 Trace::traceRelease("%s", out.c_str());
2831 * Find the lowest Slot that doesn't conflict with anything in the conflict set
2832 * for iface.
2834 Slot find_min_slot(const php::Class* iface,
2835 const IfaceSlotMap& slots,
2836 const ConflictGraph& cg) {
2837 auto const& conflicts = cg.map.at(iface);
2838 if (conflicts.empty()) {
2839 // No conflicts. This is the only interface implemented by the classes that
2840 // implement it.
2841 return 0;
2844 boost::dynamic_bitset<> used;
2846 for (auto& c : conflicts) {
2847 auto const it = slots.find(c);
2848 if (it == slots.end()) continue;
2849 auto const slot = it->second;
2851 if (used.size() <= slot) used.resize(slot + 1);
2852 used.set(slot);
2854 used.flip();
2855 return used.any() ? used.find_first() : used.size();
2859 * Compute vtable slots for all interfaces. No two interfaces implemented by
2860 * the same class will share the same vtable slot.
2862 void compute_iface_vtables(IndexData& index) {
2863 trace_time tracer("compute interface vtables");
2865 ConflictGraph cg;
2866 std::vector<const php::Class*> ifaces;
2867 hphp_hash_map<const php::Class*, int> iface_uses;
2869 // Build up the conflict sets.
2870 for (auto& cinfo : index.allClassInfos) {
2871 // Gather interfaces.
2872 if (cinfo->cls->attrs & AttrInterface) {
2873 ifaces.emplace_back(cinfo->cls);
2874 // Make sure cg.map has an entry for every interface - this simplifies
2875 // some code later on.
2876 cg.map[cinfo->cls];
2877 continue;
2880 // Only worry about classes that can be instantiated. If an abstract class
2881 // has any concrete subclasses, those classes will make sure the right
2882 // entries are in the conflict sets.
2883 if (cinfo->cls->attrs & (AttrTrait | AttrEnum | AttrAbstract)) continue;
2885 for (auto& ipair : cinfo->implInterfaces) {
2886 ++iface_uses[ipair.second->cls];
2887 for (auto& jpair : cinfo->implInterfaces) {
2888 cg.add(ipair.second->cls, jpair.second->cls);
2893 if (ifaces.size() == 0) return;
2895 // Sort interfaces by usage frequencies.
2896 // We assign slots greedily, so sort the interface list so the most
2897 // frequently implemented ones come first.
2898 auto iface_cmp = [&](const php::Class* a, const php::Class* b) {
2899 return iface_uses[a] > iface_uses[b];
2901 std::sort(begin(ifaces), end(ifaces), iface_cmp);
2903 // Assign slots, keeping track of the largest assigned slot and the total
2904 // number of uses for each slot.
2905 Slot max_slot = 0;
2906 hphp_hash_map<Slot, int> slot_uses;
2907 for (auto* iface : ifaces) {
2908 auto const slot = find_min_slot(iface, index.ifaceSlotMap, cg);
2909 index.ifaceSlotMap[iface] = slot;
2910 max_slot = std::max(max_slot, slot);
2912 // Interfaces implemented by the same class never share a slot, so normal
2913 // addition is fine here.
2914 slot_uses[slot] += iface_uses[iface];
2917 // Make sure we have an initialized entry for each slot for the sort below.
2918 for (Slot slot = 0; slot < max_slot; ++slot) {
2919 assert(slot_uses.count(slot));
2922 // Finally, sort and reassign slots so the most frequently used slots come
2923 // first. This slightly reduces the number of wasted vtable vector entries at
2924 // runtime.
2925 auto const slots = sort_keys_by_value(
2926 slot_uses,
2927 [&] (int a, int b) { return a > b; }
2930 std::vector<Slot> slots_permute(max_slot + 1, 0);
2931 for (size_t i = 0; i <= max_slot; ++i) slots_permute[slots[i]] = i;
2933 // re-map interfaces to permuted slots
2934 for (auto& pair : index.ifaceSlotMap) {
2935 pair.second = slots_permute[pair.second];
2938 if (Trace::moduleEnabledRelease(Trace::hhbbc_iface)) {
2939 trace_interfaces(index, cg);
2943 void mark_magic_on_parents(ClassInfo& cinfo, ClassInfo& derived) {
2944 auto any = false;
2945 for (auto& kv : magicMethodMap) {
2946 if ((derived.*kv.second.pmem).thisHas) {
2947 auto& derivedHas = (cinfo.*kv.second.pmem).derivedHas;
2948 if (!derivedHas) {
2949 derivedHas = any = true;
2953 if (!any) return;
2954 if (cinfo.parent) mark_magic_on_parents(*cinfo.parent, derived);
2955 for (auto iface : cinfo.declInterfaces) {
2956 mark_magic_on_parents(*const_cast<ClassInfo*>(iface), derived);
2960 bool has_magic_method(const ClassInfo* cinfo, SString name) {
2961 if (name == s_toBoolean.get()) {
2962 // note that "having" a magic method includes the possibility that
2963 // a parent class has it. This can't happen for the collection
2964 // classes, because they're all final; but for SimpleXMLElement,
2965 // we need to search.
2966 while (cinfo->parent) cinfo = cinfo->parent;
2967 return has_magic_bool_conversion(cinfo->cls->name);
2969 return cinfo->methods.find(name) != end(cinfo->methods);
2972 void find_magic_methods(IndexData& index) {
2973 for (auto& cinfo : index.allClassInfos) {
2974 bool any = false;
2975 for (auto& kv : magicMethodMap) {
2976 bool const found = has_magic_method(cinfo.get(), kv.first);
2977 any = any || found;
2978 (cinfo.get()->*kv.second.pmem).thisHas = found;
2980 if (any) mark_magic_on_parents(*cinfo, *cinfo);
2984 void find_mocked_classes(IndexData& index) {
2985 for (auto& cinfo : index.allClassInfos) {
2986 if (is_mock_class(cinfo->cls) && cinfo->parent) {
2987 cinfo->parent->isMocked = true;
2988 for (auto c = cinfo->parent; c; c = c->parent) {
2989 c->isDerivedMocked = true;
2995 void mark_const_props(IndexData& index) {
2996 for (auto& cinfo : index.allClassInfos) {
2997 auto const hasConstProp = [&]() {
2998 if (cinfo->cls->attrs & AttrHasConstProps) return true;
2999 if (cinfo->parent && cinfo->parent->hasConstProp) return true;
3000 if (!(cinfo->cls->attrs & AttrNoExpandTrait)) {
3001 for (auto t : cinfo->usedTraits) {
3002 if (t->cls->attrs & AttrHasConstProps) return true;
3005 return false;
3006 }();
3007 if (hasConstProp) {
3008 cinfo->hasConstProp = true;
3009 for (auto c = cinfo.get(); c; c = c->parent) {
3010 if (c->derivedHasConstProp) break;
3011 c->derivedHasConstProp = true;
3017 void mark_no_override_classes(IndexData& index) {
3018 for (auto& cinfo : index.allClassInfos) {
3019 // We cleared all the NoOverride flags while building the
3020 // index. Set them as necessary.
3021 if (!(cinfo->cls->attrs & AttrUnique)) continue;
3022 if (!(cinfo->cls->attrs & AttrInterface) &&
3023 cinfo->subclassList.size() == 1) {
3024 attribute_setter(cinfo->cls->attrs, true, AttrNoOverride);
3027 for (auto& kv : magicMethodMap) {
3028 if (kv.second.attrBit == AttrNone) continue;
3029 if (!(cinfo.get()->*kv.second.pmem).derivedHas) {
3030 FTRACE(2, "Adding no-override of {} to {}\n",
3031 kv.first->data(),
3032 cinfo->cls->name);
3033 attribute_setter(cinfo->cls->attrs, true, kv.second.attrBit);
3039 void mark_no_override_methods(IndexData& index) {
3040 // We removed any AttrNoOverride flags from all methods while adding
3041 // the units to the index. Now start by marking every
3042 // (non-interface, non-special) method as AttrNoOverride.
3043 for (auto& cinfo : index.allClassInfos) {
3044 if (cinfo->cls->attrs & AttrInterface) continue;
3045 if (!(cinfo->cls->attrs & AttrUnique)) continue;
3047 for (auto& m : cinfo->methods) {
3048 if (!(is_special_method_name(m.first))) {
3049 FTRACE(9, "Pre-setting AttrNoOverride on {}::{}\n",
3050 m.second.func->cls->name, m.first);
3051 attribute_setter(m.second.attrs, true, AttrNoOverride);
3052 attribute_setter(m.second.func->attrs, true, AttrNoOverride);
3057 // Then run through every ClassInfo, and for each of its parent classes clear
3058 // the AttrNoOverride flag if it has a different Func with the same name.
3059 for (auto& cinfo : index.allClassInfos) {
3060 for (auto& ancestor : cinfo->baseList) {
3061 if (ancestor == cinfo.get()) continue;
3063 auto removeNoOverride = [] (auto it) {
3064 assertx(it->second.attrs & AttrNoOverride ||
3065 !(it->second.func->attrs & AttrNoOverride));
3066 if (it->second.attrs & AttrNoOverride) {
3067 FTRACE(2, "Removing AttrNoOverride on {}::{}\n",
3068 it->second.func->cls->name, it->first);
3069 attribute_setter(it->second.attrs, false, AttrNoOverride);
3070 attribute_setter(it->second.func->attrs, false, AttrNoOverride);
3074 for (auto& derivedMethod : cinfo->methods) {
3075 auto const it = ancestor->methods.find(derivedMethod.first);
3076 if (it == end(ancestor->methods)) continue;
3077 if (it->second.func != derivedMethod.second.func) {
3078 removeNoOverride(it);
3085 template <class T, class F>
3086 void mark_unique_entities(ISStringToMany<T>& entities, F marker) {
3087 for (auto it = entities.begin(), end = entities.end(); it != end; ) {
3088 auto first = it++;
3089 auto flag = true;
3090 while (it != end && it->first->isame(first->first)) {
3091 marker(it++->second, false);
3092 flag = false;
3094 marker(first->second, flag);
3098 const StaticString s__Reified("__Reified");
3101 * Emitter adds a 86reifiedinit method to all classes that have reified
3102 * generics. All base classes also need to have this method so that when we
3103 * call parent::86reifeidinit(...), there is a stopping point.
3104 * Since while emitting we do not know whether a base class will have
3105 * reified parents, during JIT time we need to add 86reifiedinit
3106 * unless AttrNoReifiedInit attribute is set. At this phase,
3107 * we set AttrNoReifiedInit attribute on classes do not have any
3108 * reified classes that extend it.
3110 void clean_86reifiedinit_methods(IndexData& index) {
3111 trace_time tracer("clean 86reifiedinit methods");
3112 folly::F14FastSet<const php::Class*> needsinit;
3114 // Find all classes that still need their 86reifiedinit methods
3115 for (auto& cinfo : index.allClassInfos) {
3116 auto ual = cinfo->cls->userAttributes;
3117 // Each class that has at least one reified generic has an attribute
3118 // __Reified added by the emitter
3119 auto has_reification = ual.find(s__Reified.get()) != ual.end();
3120 if (!has_reification) continue;
3121 // Add the base class for this reified class
3122 needsinit.emplace(cinfo->baseList[0]->cls);
3125 // Add AttrNoReifiedInit to the base classes that do not need this method
3126 for (auto& cinfo : index.allClassInfos) {
3127 if (cinfo->parent == nullptr && needsinit.count(cinfo->cls) == 0) {
3128 FTRACE(2, "Adding AttrNoReifiedInit on class {}\n", cinfo->cls->name);
3129 attribute_setter(cinfo->cls->attrs, true, AttrNoReifiedInit);
3134 //////////////////////////////////////////////////////////////////////
3136 void check_invariants(const ClassInfo* cinfo) {
3137 // All the following invariants only apply to classes
3138 if (cinfo->cls->attrs & AttrInterface) return;
3140 if (!(cinfo->cls->attrs & AttrTrait)) {
3141 // For non-interface classes, each method in a php class has an
3142 // entry in its ClassInfo method table, and if it's not special,
3143 // AttrNoOverride, or private, an entry in the family table.
3144 for (auto& m : cinfo->cls->methods) {
3145 auto const it = cinfo->methods.find(m->name);
3146 always_assert(it != cinfo->methods.end());
3147 if (it->second.attrs & (AttrNoOverride|AttrPrivate)) continue;
3148 if (is_special_method_name(m->name)) continue;
3149 always_assert(cinfo->methodFamilies.count(m->name));
3153 // The subclassList is non-empty, contains this ClassInfo, and
3154 // contains only unique elements.
3155 always_assert(!cinfo->subclassList.empty());
3156 always_assert(std::find(begin(cinfo->subclassList),
3157 end(cinfo->subclassList),
3158 cinfo) != end(cinfo->subclassList));
3159 auto cpy = cinfo->subclassList;
3160 std::sort(begin(cpy), end(cpy));
3161 cpy.erase(
3162 std::unique(begin(cpy), end(cpy)),
3163 end(cpy)
3165 always_assert(cpy.size() == cinfo->subclassList.size());
3167 // The baseList is non-empty, and the last element is this class.
3168 always_assert(!cinfo->baseList.empty());
3169 always_assert(cinfo->baseList.back() == cinfo);
3171 for (auto& kv : magicMethodMap) {
3172 auto& info = cinfo->*kv.second.pmem;
3174 // Magic method flags should be consistent with the method table.
3175 always_assert(info.thisHas == has_magic_method(cinfo, kv.first));
3177 // Non-'derived' flags (thisHas) about magic methods imply the derived
3178 // ones.
3179 always_assert(!info.thisHas || info.derivedHas);
3182 // Every FuncFamily is non-empty and contain functions with the same
3183 // name (unless its a family of ctors).
3184 for (auto const& mfam: cinfo->methodFamilies) {
3185 always_assert(!mfam.second.possibleFuncs()->empty());
3186 auto const name = mfam.second.possibleFuncs()->front()->first;
3187 for (auto const pf : mfam.second.possibleFuncs()) {
3188 always_assert(pf->first->isame(name));
3193 void check_invariants(IndexData& data) {
3194 if (!debug) return;
3196 // Every AttrUnique non-trait class has a unique ClassInfo object,
3197 // or no ClassInfo object in the case that instantiating it would've
3198 // fataled.
3199 for (auto& kv : data.classes) {
3200 auto const name = kv.first;
3201 auto const cls = kv.second;
3202 if (!(cls->attrs & AttrUnique)) continue;
3204 auto const range = find_range(data.classInfo, name);
3205 if (begin(range) != end(range)) {
3206 always_assert(std::next(begin(range)) == end(range));
3210 for (auto& cinfo : data.allClassInfos) {
3211 check_invariants(cinfo.get());
3215 //////////////////////////////////////////////////////////////////////
3217 Type context_sensitive_return_type(IndexData& data,
3218 CallContext callCtx) {
3219 constexpr auto max_interp_nexting_level = 2;
3220 static __thread uint32_t interp_nesting_level;
3221 auto const finfo = func_info(data, callCtx.callee);
3222 auto const returnType = return_with_context(finfo->returnTy, callCtx.context);
3224 auto checkParam = [&] (int i) {
3225 auto const constraint = finfo->func->params[i].typeConstraint;
3226 if (constraint.hasConstraint() &&
3227 !constraint.isTypeVar() &&
3228 !constraint.isTypeConstant()) {
3229 auto ctx = Context {
3230 finfo->func->unit,
3231 const_cast<php::Func*>(finfo->func),
3232 finfo->func->cls
3234 auto t = loosen_dvarrayness(
3235 data.m_index->lookup_constraint(ctx, constraint));
3236 return callCtx.args[i].strictlyMoreRefined(t);
3238 return callCtx.args[i].strictSubtypeOf(TInitCell);
3241 // TODO(#3788877): more heuristics here would be useful.
3242 bool const tryContextSensitive = [&] {
3243 if (finfo->func->noContextSensitiveAnalysis ||
3244 finfo->func->params.empty() ||
3245 interp_nesting_level + 1 >= max_interp_nexting_level ||
3246 returnType == TBottom) {
3247 return false;
3250 if (finfo->retParam != NoLocalId &&
3251 callCtx.args.size() > finfo->retParam &&
3252 checkParam(finfo->retParam)) {
3253 return true;
3256 if (!options.ContextSensitiveInterp) return false;
3258 if (callCtx.args.size() < finfo->func->params.size()) return true;
3259 for (auto i = 0; i < finfo->func->params.size(); i++) {
3260 if (checkParam(i)) return true;
3262 return false;
3263 }();
3265 if (!tryContextSensitive) {
3266 return returnType;
3269 auto maybe_loosen_staticness = [&] (const Type& ty) {
3270 return returnType.subtypeOf(BUnc) ? ty : loosen_staticness(ty);
3274 ContextRetTyMap::const_accessor acc;
3275 if (data.contextualReturnTypes.find(acc, callCtx)) {
3276 if (data.frozen || acc->second == TBottom || is_scalar(acc->second)) {
3277 return maybe_loosen_staticness(acc->second);
3282 if (data.frozen) {
3283 return returnType;
3286 auto contextType = [&] {
3287 ++interp_nesting_level;
3288 SCOPE_EXIT { --interp_nesting_level; };
3290 auto const calleeCtx = Context {
3291 finfo->func->unit,
3292 const_cast<php::Func*>(finfo->func),
3293 finfo->func->cls
3295 auto const ty =
3296 analyze_func_inline(*data.m_index, calleeCtx,
3297 callCtx.context, callCtx.args).inferredReturn;
3298 return return_with_context(ty, callCtx.context);
3299 }();
3301 if (!interp_nesting_level) {
3302 FTRACE(3,
3303 "Context sensitive type: {}\n"
3304 "Context insensitive type: {}\n",
3305 show(contextType), show(returnType));
3308 auto ret = intersection_of(std::move(returnType),
3309 std::move(contextType));
3311 ContextRetTyMap::accessor acc;
3312 if (data.contextualReturnTypes.insert(acc, callCtx) ||
3313 ret.strictSubtypeOf(acc->second)) {
3314 acc->second = ret;
3317 if (!interp_nesting_level) {
3318 ret = maybe_loosen_staticness(ret);
3319 FTRACE(3, "Context sensitive result: {}\n", show(ret));
3322 return ret;
3325 //////////////////////////////////////////////////////////////////////
3327 PrepKind func_param_prep(const php::Func* func,
3328 uint32_t paramId) {
3329 if (func->attrs & AttrInterceptable) return PrepKind::Unknown;
3330 if (paramId >= func->params.size()) {
3331 return PrepKind::Val;
3333 return func->params[paramId].byRef ? PrepKind::Ref : PrepKind::Val;
3336 template<class PossibleFuncRange>
3337 PrepKind prep_kind_from_set(PossibleFuncRange range, uint32_t paramId) {
3340 * In sinlge-unit mode, the range is not complete. Without konwing all
3341 * possible resolutions, HHBBC cannot deduce anything about by-ref vs by-val.
3342 * So the caller should make sure not calling this in single-unit mode.
3344 assert(RuntimeOption::RepoAuthoritative);
3346 if (begin(range) == end(range)) {
3348 * We can assume it's by value, because either we're calling a function
3349 * that doesn't exist (about to fatal), or we're going to an __call (which
3350 * never takes parameters by reference).
3352 * Or if we've got AllFuncsInterceptable we need to assume someone could
3353 * rename a function to the new name.
3355 return RuntimeOption::EvalJitEnableRenameFunction ?
3356 PrepKind::Unknown : PrepKind::Val;
3359 struct FuncFind {
3360 using F = const php::Func*;
3361 static F get(std::pair<SString,F> p) { return p.second; }
3362 static F get(const MethTabEntryPair* mte) { return mte->second.func; }
3365 folly::Optional<PrepKind> prep;
3366 for (auto& item : range) {
3367 switch (func_param_prep(FuncFind::get(item), paramId)) {
3368 case PrepKind::Unknown:
3369 return PrepKind::Unknown;
3370 case PrepKind::Ref:
3371 if (prep && *prep != PrepKind::Ref) return PrepKind::Unknown;
3372 prep = PrepKind::Ref;
3373 break;
3374 case PrepKind::Val:
3375 if (prep && *prep != PrepKind::Val) return PrepKind::Unknown;
3376 prep = PrepKind::Val;
3377 break;
3380 return *prep;
3383 template<typename F> auto
3384 visit_parent_cinfo(const ClassInfo* cinfo, F fun) -> decltype(fun(cinfo)) {
3385 for (auto ci = cinfo; ci != nullptr; ci = ci->parent) {
3386 if (auto const ret = fun(ci)) return ret;
3387 if (ci->cls->attrs & AttrNoExpandTrait) continue;
3388 for (auto ct : ci->usedTraits) {
3389 if (auto const ret = visit_parent_cinfo(ct, fun)) {
3390 return ret;
3394 return {};
3397 PublicSPropEntry lookup_public_static_impl(
3398 const IndexData& data,
3399 const ClassInfo* cinfo,
3400 SString prop
3402 auto const noInfo = PublicSPropEntry{TInitGen, TInitGen, nullptr, 0, true};
3404 if (data.allPublicSPropsUnknown) return noInfo;
3406 const ClassInfo* knownCInfo = nullptr;
3407 auto const knownClsPart = visit_parent_cinfo(
3408 cinfo,
3409 [&] (const ClassInfo* ci) -> const PublicSPropEntry* {
3410 auto const it = ci->publicStaticProps.find(prop);
3411 if (it != end(ci->publicStaticProps)) {
3412 knownCInfo = ci;
3413 return &it->second;
3415 return nullptr;
3419 auto const unkPart = [&]() -> const Type* {
3420 auto unkIt = data.unknownClassSProps.find(prop);
3421 if (unkIt != end(data.unknownClassSProps)) {
3422 return &unkIt->second.first;
3424 return nullptr;
3425 }();
3427 if (knownClsPart == nullptr) {
3428 return noInfo;
3431 // NB: Inferred type can be TBottom here if the property is never set to a
3432 // value which can satisfy its type constraint. Such properties can't exist at
3433 // runtime.
3435 if (unkPart != nullptr) {
3436 return PublicSPropEntry {
3437 union_of(
3438 knownClsPart->inferredType,
3439 *unkPart
3441 knownClsPart->initializerType,
3442 nullptr,
3444 true
3447 return *knownClsPart;
3450 PublicSPropEntry lookup_public_static_impl(
3451 const IndexData& data,
3452 const php::Class* cls,
3453 SString name
3455 auto const classes = find_range(data.classInfo, cls->name);
3456 if (begin(classes) == end(classes) ||
3457 std::next(begin(classes)) != end(classes)) {
3458 return PublicSPropEntry{TInitGen, TInitGen, nullptr, 0, true};
3460 return lookup_public_static_impl(data, begin(classes)->second, name);
3463 Type lookup_public_prop_impl(
3464 const IndexData& data,
3465 const ClassInfo* cinfo,
3466 SString propName
3468 // Find a property declared in this class (or a parent) with the same name.
3469 const php::Class* knownCls = nullptr;
3470 auto const prop = visit_parent_cinfo(
3471 cinfo,
3472 [&] (const ClassInfo* ci) -> const php::Prop* {
3473 for (auto const& prop : ci->cls->properties) {
3474 if (prop.name == propName) {
3475 knownCls = ci->cls;
3476 return &prop;
3479 return nullptr;
3483 if (!prop) return TGen;
3484 // Make sure its non-static and public. Otherwise its another function's
3485 // problem.
3486 if (prop->attrs & (AttrStatic | AttrPrivate | AttrLateInitSoft)) return TGen;
3488 // Get a type corresponding to its declared type-hint (if any).
3489 auto ty = adjust_type_for_prop(
3490 *data.m_index, *knownCls, &prop->typeConstraint, TGen
3492 // We might have to include the initial value which might be outside of the
3493 // type-hint.
3494 auto initialTy = loosen_all(from_cell(prop->val));
3495 if (!initialTy.subtypeOf(TUninit) && (prop->attrs & AttrSystemInitialValue)) {
3496 ty |= initialTy;
3498 return ty;
3501 //////////////////////////////////////////////////////////////////////
3505 //////////////////////////////////////////////////////////////////////
3507 Index::Index(php::Program* program,
3508 rebuild* rebuild_exception)
3509 : m_data(std::make_unique<IndexData>(this))
3511 trace_time tracer("create index");
3513 m_data->arrTableBuilder.reset(new ArrayTypeTable::Builder());
3515 add_system_constants_to_index(*m_data);
3517 if (rebuild_exception) {
3518 for (auto& ca : rebuild_exception->class_aliases) {
3519 m_data->classAliases.insert(ca.first);
3520 m_data->classAliases.insert(ca.second);
3522 rebuild_exception->class_aliases.clear();
3526 trace_time trace_add_units("add units to index");
3527 for (auto& u : program->units) {
3528 add_unit_to_index(*m_data, *u);
3532 ClassInfoData cid;
3534 trace_time build_class_info_data("build classinfo data");
3535 for (auto const &elm : m_data->classes) {
3536 auto const c = elm.second;
3537 auto const addUser = [&] (SString cName) {
3538 cid.classUsers[cName].push_back(c);
3539 auto const count = m_data->classes.count(cName);
3540 cid.classDepCounts[c] += count ? count : 1;
3542 if (c->parentName) {
3543 addUser(c->parentName);
3545 for (auto& i : c->interfaceNames) {
3546 addUser(i);
3548 for (auto& t : c->usedTraitNames) {
3549 addUser(t);
3551 if (!cid.classDepCounts.count(c)) {
3552 FTRACE(5, "Adding no-dep class {}:{} to classQueue\n",
3553 c->name, (void*)c);
3554 // make sure that closure is first, because we end up calling
3555 // preresolve directly on closures created by trait
3556 // flattening, which assumes all dependencies are satisfied.
3557 if (cid.classQueue.size() && c->name == s_Closure.get()) {
3558 cid.classQueue.push_back(cid.classQueue[0]);
3559 cid.classQueue[0] = c;
3560 } else {
3561 cid.classQueue.push_back(c);
3563 } else {
3564 FTRACE(6, "Class {}:{} has {} deps\n",
3565 c->name, (void*)c, cid.classDepCounts[c]);
3569 cid.cqBack = cid.classQueue.size();
3570 cid.classQueue.resize(m_data->classes.size());
3574 trace_time preresolve_classes("preresolve classes");
3576 auto canResolve = [&] (const php::Class* cls) {
3577 if (cls->parentName && !m_data->classInfo.count(cls->parentName)) {
3578 return false;
3580 for (auto& i : cls->interfaceNames) {
3581 if (!m_data->classInfo.count(i)) return false;
3583 for (auto& t : cls->usedTraitNames) {
3584 if (!m_data->classInfo.count(t)) return false;
3586 return true;
3589 NamingEnv env{program, *m_data, cid};
3590 while (true) {
3591 auto const ix = cid.cqFront++;
3592 if (ix == cid.cqBack) {
3593 // we've consumed everything where all dependencies are
3594 // satisfied. There may still be some pseudo-cycles that can
3595 // be broken though.
3597 // eg if A extends B and B' extends A', we'll resolve B and
3598 // A', and then end up here, since both A and B' still have
3599 // one dependency. But both A and B' can be resolved at this
3600 // point
3601 for (auto it = cid.classDepCounts.begin();
3602 it != cid.classDepCounts.end();
3604 if (canResolve(it->first)) {
3605 FTRACE(2, "Breaking pseudo-cycle for class {}:{}\n",
3606 it->first->name, (void*)it->first);
3607 cid.classQueue[cid.cqBack++] = it->first;
3608 it = cid.classDepCounts.erase(it);
3609 cid.hasPseudoCycles = true;
3610 } else {
3611 ++it;
3614 if (ix == cid.cqBack) {
3615 break;
3618 auto const c = cid.classQueue[ix];
3619 Trace::Bump bumper{
3620 Trace::hhbbc_index, kSystemLibBump, is_systemlib_part(*c->unit)
3622 preresolve(env, c);
3626 mark_unique_entities(m_data->typeAliases,
3627 [&] (const php::TypeAlias* ta, bool flag) {
3628 attribute_setter(
3629 ta->attrs,
3630 flag &&
3631 !m_data->classInfo.count(ta->name) &&
3632 !m_data->classAliases.count(ta->name),
3633 AttrUnique);
3636 // Iterate allClassInfos so that we visit parent classes before
3637 // child classes.
3638 for (auto& cinfo : m_data->allClassInfos) {
3639 auto const set = [&] {
3640 if (m_data->classInfo.count(cinfo->cls->name) != 1 ||
3641 m_data->typeAliases.count(cinfo->cls->name) ||
3642 m_data->classAliases.count(cinfo->cls->name)) {
3643 return false;
3645 if (cinfo->parent && !(cinfo->parent->cls->attrs & AttrUnique)) {
3646 return false;
3648 for (auto const i : cinfo->declInterfaces) {
3649 if (!(i->cls->attrs & AttrUnique)) return false;
3651 for (auto const t : cinfo->usedTraits) {
3652 if (!(t->cls->attrs & AttrUnique)) return false;
3654 return true;
3655 }();
3656 attribute_setter(cinfo->cls->attrs, set, AttrUnique);
3659 mark_unique_entities(m_data->funcs,
3660 [&] (const php::Func* func, bool flag) {
3661 attribute_setter(func->attrs, flag, AttrUnique);
3664 m_data->funcInfo.resize(program->nextFuncId);
3666 // Part of the index building routines happens before the various asserted
3667 // index invariants hold. These each may depend on computations from
3668 // previous functions, so be careful changing the order here.
3669 compute_subclass_list(*m_data);
3670 clean_86reifiedinit_methods(*m_data); // uses the base class lists
3671 mark_no_override_methods(*m_data); // uses AttrUnique
3672 find_magic_methods(*m_data); // uses the subclass lists
3673 find_mocked_classes(*m_data);
3674 mark_const_props(*m_data);
3675 auto const logging = Trace::moduleEnabledRelease(Trace::hhbbc_time, 1);
3676 m_data->compute_iface_vtables = std::thread([&] {
3677 HphpSessionAndThread _{Treadmill::SessionKind::HHBBC};
3678 auto const enable =
3679 logging && !Trace::moduleEnabledRelease(Trace::hhbbc_time, 1);
3680 Trace::BumpRelease bumper(Trace::hhbbc_time, -1, enable);
3681 compute_iface_vtables(*m_data);
3684 define_func_families(*m_data); // AttrNoOverride, iface_vtables,
3685 // subclass_list
3687 check_invariants(*m_data);
3689 mark_no_override_classes(*m_data); // uses AttrUnique
3691 if (RuntimeOption::EvalCheckReturnTypeHints == 3) {
3692 trace_time tracer("initialize return types");
3693 std::vector<const php::Func*> all_funcs;
3694 all_funcs.reserve(m_data->funcs.size() + m_data->methods.size());
3695 for (auto const fn : m_data->funcs) {
3696 all_funcs.push_back(fn.second);
3698 for (auto const fn : m_data->methods) {
3699 all_funcs.push_back(fn.second);
3702 parallel::for_each(all_funcs, [&] (const php::Func* f) {
3703 init_return_type(f);
3708 // Defined here so IndexData is a complete type for the unique_ptr
3709 // destructor.
3710 Index::~Index() {}
3712 //////////////////////////////////////////////////////////////////////
3714 void Index::mark_persistent_classes_and_functions(php::Program& program) {
3715 auto persist = [] (const php::Unit* unit) {
3716 return
3717 unit->persistent.load(std::memory_order_relaxed) &&
3718 unit->persistent_pseudomain.load(std::memory_order_relaxed);
3720 for (auto& unit : program.units) {
3721 auto const persistent = persist(unit.get());
3722 for (auto& f : unit->funcs) {
3723 attribute_setter(f->attrs,
3724 persistent && (f->attrs & AttrUnique),
3725 AttrPersistent);
3728 for (auto& t : unit->typeAliases) {
3729 attribute_setter(t->attrs,
3730 persistent && (t->attrs & AttrUnique),
3731 AttrPersistent);
3735 auto check_persistent = [&] (const ClassInfo& cinfo) {
3736 if (cinfo.parent && !(cinfo.parent->cls->attrs & AttrPersistent)) {
3737 return false;
3740 for (auto const intrf : cinfo.declInterfaces) {
3741 if (!(intrf->cls->attrs & AttrPersistent)) return false;
3744 return true;
3747 for (auto& c : m_data->allClassInfos) {
3748 attribute_setter(c->cls->attrs,
3749 (c->cls->attrs & AttrUnique) &&
3750 (persist(c->cls->unit) ||
3751 c->cls->parentName == s_Closure.get()) &&
3752 check_persistent(*c),
3753 AttrPersistent);
3757 void Index::mark_no_bad_redeclare_props(php::Class& cls) const {
3759 * Keep a list of properties which have not yet been found to redeclare
3760 * anything inequivalently. Start out by putting everything on the list. Then
3761 * walk up the inheritance chain, removing collisions as we find them.
3763 std::vector<php::Prop*> props;
3764 for (auto& prop : cls.properties) {
3765 if (prop.attrs & (AttrStatic | AttrPrivate)) {
3766 // Static and private properties never redeclare anything so need not be
3767 // considered.
3768 attribute_setter(prop.attrs, true, AttrNoBadRedeclare);
3769 continue;
3771 attribute_setter(prop.attrs, false, AttrNoBadRedeclare);
3772 props.emplace_back(&prop);
3775 auto currentCls = [&]() -> const ClassInfo* {
3776 auto const rcls = resolve_class(&cls);
3777 if (rcls.val.left()) return nullptr;
3778 return rcls.val.right();
3779 }();
3780 // If there's one more than one resolution for the class, be conservative and
3781 // we'll treat everything as possibly redeclaring.
3782 if (!currentCls) props.clear();
3784 while (!props.empty()) {
3785 auto const parent = currentCls->parent;
3786 if (!parent) {
3787 // No parent. We're done, so anything left on the prop list is
3788 // AttrNoBadRedeclare.
3789 for (auto& prop : props) {
3790 attribute_setter(prop->attrs, true, AttrNoBadRedeclare);
3792 break;
3795 auto const findParentProp = [&] (SString name) -> const php::Prop* {
3796 for (auto& prop : parent->cls->properties) {
3797 if (prop.name == name) return &prop;
3799 for (auto& prop : parent->traitProps) {
3800 if (prop.name == name) return &prop;
3802 return nullptr;
3805 // Remove any properties which collide with the current class.
3807 auto const propRedeclares = [&] (php::Prop* prop) {
3808 auto const pprop = findParentProp(prop->name);
3809 if (!pprop) return false;
3811 // We found a property being redeclared. Check if the type-hints on
3812 // the two are equivalent.
3813 auto const equiv = [&] {
3814 auto const& tc1 = prop->typeConstraint;
3815 auto const& tc2 = pprop->typeConstraint;
3816 // Try the cheap check first, use the index otherwise. Two
3817 // type-constraints are equivalent if all the possible values of one
3818 // satisfies the other, and vice-versa.
3819 if (!tc1.maybeInequivalentForProp(tc2)) return true;
3820 return
3821 satisfies_constraint(
3822 Context{},
3823 lookup_constraint(Context{}, tc1),
3825 ) && satisfies_constraint(
3826 Context{},
3827 lookup_constraint(Context{}, tc2),
3831 // If the property in the parent is static or private, the property in
3832 // the child isn't actually redeclaring anything. Otherwise, if the
3833 // type-hints are equivalent, remove this property from further
3834 // consideration and mark it as AttrNoBadRedeclare.
3835 if ((pprop->attrs & (AttrStatic | AttrPrivate)) || equiv()) {
3836 attribute_setter(prop->attrs, true, AttrNoBadRedeclare);
3838 return true;
3841 props.erase(
3842 std::remove_if(props.begin(), props.end(), propRedeclares),
3843 props.end()
3846 currentCls = parent;
3849 auto const possibleOverride =
3850 std::any_of(
3851 cls.properties.begin(),
3852 cls.properties.end(),
3853 [&](const php::Prop& prop) { return !(prop.attrs & AttrNoBadRedeclare); }
3856 // Mark all resolutions of this class as having any possible bad redeclaration
3857 // props, even if there's not an unique resolution.
3858 for (auto& info : find_range(m_data->classInfo, cls.name)) {
3859 auto const cinfo = info.second;
3860 if (cinfo->cls != &cls) continue;
3861 cinfo->hasBadRedeclareProp = possibleOverride;
3866 * Rewrite the initial values for any AttrSystemInitialValue properties. If the
3867 * properties' type-hint does not admit null values, change the initial value to
3868 * one (if possible) to one that is not null. This is only safe to do so if the
3869 * property is not redeclared in a derived class or if the redeclaration does
3870 * not have a null system provided default value. Otherwise, a property can have
3871 * a null value (even if its type-hint doesn't allow it) without the JIT
3872 * realizing that its possible.
3874 * Note that this ignores any unflattened traits. This is okay because
3875 * properties pulled in from traits which match an already existing property
3876 * can't change the initial value. The runtime will clear AttrNoImplicitNullable
3877 * on any property pulled from the trait if it doesn't match an existing
3878 * property.
3880 void Index::rewrite_default_initial_values(php::Program& program) const {
3881 trace_time tracer("rewrite default initial values");
3884 * Use dataflow across the whole program class hierarchy. Start from the
3885 * classes which have no derived classes and flow up the hierarchy. We flow
3886 * the set of properties which have been assigned a null system provided
3887 * default value. If a property with such a null value flows into a class
3888 * which declares a property with the same name (and isn't static or private),
3889 * than that property is forced to be null as well.
3891 using PropSet = folly::F14FastSet<SString>;
3892 using OutState = folly::F14FastMap<const ClassInfo*, PropSet>;
3893 using Worklist = folly::F14FastSet<const ClassInfo*>;
3895 OutState outStates;
3896 outStates.reserve(m_data->allClassInfos.size());
3898 // List of Class' still to process this iteration
3899 using WorkList = std::vector<const ClassInfo*>;
3900 using WorkSet = folly::F14FastSet<const ClassInfo*>;
3902 WorkList workList;
3903 WorkSet workSet;
3904 auto const enqueue = [&] (const ClassInfo& cls) {
3905 auto const result = workSet.insert(&cls);
3906 if (!result.second) return;
3907 workList.emplace_back(&cls);
3910 // Start with all the leaf classes
3911 for (auto const& cinfo : m_data->allClassInfos) {
3912 auto const isLeaf = [&] {
3913 for (auto const& sub : cinfo->subclassList) {
3914 if (sub != cinfo.get()) return false;
3916 return true;
3917 }();
3918 if (isLeaf) enqueue(*cinfo);
3921 WorkList oldWorkList;
3922 int iter = 1;
3923 while (!workList.empty()) {
3924 FTRACE(
3925 4, "rewrite_default_initial_values round #{}: {} items\n",
3926 iter, workList.size()
3928 ++iter;
3930 std::swap(workList, oldWorkList);
3931 workList.clear();
3932 workSet.clear();
3933 for (auto const& cinfo : oldWorkList) {
3934 // Retrieve the set of properties which are flowing into this Class and
3935 // have to be null.
3936 auto inState = [&] () -> folly::Optional<PropSet> {
3937 PropSet in;
3938 for (auto const& sub : cinfo->subclassList) {
3939 if (sub == cinfo || sub->parent != cinfo) continue;
3940 auto const it = outStates.find(sub);
3941 if (it == outStates.end()) return folly::none;
3942 in.insert(it->second.begin(), it->second.end());
3944 return in;
3945 }();
3946 if (!inState) continue;
3948 // Modify the in-state depending on the properties declared on this Class
3949 auto const cls = cinfo->cls;
3950 for (auto const& prop : cls->properties) {
3951 if (prop.attrs & (AttrStatic | AttrPrivate)) {
3952 // Private or static properties can't be redeclared
3953 inState->erase(prop.name);
3954 continue;
3956 // Ignore properties which have actual user provided initial values or
3957 // are LateInit.
3958 if (!(prop.attrs & AttrSystemInitialValue) ||
3959 (prop.attrs & AttrLateInit)) {
3960 continue;
3962 // Forced to be null, nothing to do
3963 if (inState->count(prop.name) > 0) continue;
3965 // Its not forced to be null. Find a better default value. If its null
3966 // anyways, force any properties this redeclares to be null as well.
3967 auto const defaultValue = prop.typeConstraint.defaultValue();
3968 if (defaultValue.m_type == KindOfNull) inState->insert(prop.name);
3971 // Push the in-state to the out-state.
3972 auto const result = outStates.emplace(std::make_pair(cinfo, *inState));
3973 if (result.second) {
3974 if (cinfo->parent) enqueue(*cinfo->parent);
3975 } else {
3976 // There shouldn't be cycles in the inheritance tree, so the out state
3977 // of Class', once set, should never change.
3978 assertx(result.first->second == *inState);
3983 // Now that we've processed all the classes, rewrite the property initial
3984 // values, unless they are forced to be nullable.
3985 for (auto& unit : program.units) {
3986 for (auto& c : unit->classes) {
3987 if (is_closure(*c)) continue;
3989 auto const out = [&] () -> folly::Optional<PropSet> {
3990 folly::Optional<PropSet> props;
3991 auto const range = m_data->classInfo.equal_range(c->name);
3992 for (auto it = range.first; it != range.second; ++it) {
3993 if (it->second->cls != c.get()) continue;
3994 auto const outStateIt = outStates.find(it->second);
3995 if (outStateIt == outStates.end()) return folly::none;
3996 if (!props) props.emplace();
3997 props->insert(outStateIt->second.begin(), outStateIt->second.end());
3999 return props;
4000 }();
4002 for (auto& prop : c->properties) {
4003 auto const nullable = [&] {
4004 if (!(prop.attrs & (AttrStatic | AttrPrivate))) {
4005 if (!out || out->count(prop.name)) return true;
4007 if (!(prop.attrs & AttrSystemInitialValue)) return false;
4008 return prop.typeConstraint.defaultValue().m_type == KindOfNull;
4009 }();
4011 attribute_setter(prop.attrs, !nullable, AttrNoImplicitNullable);
4012 if (!(prop.attrs & AttrSystemInitialValue)) continue;
4013 if (prop.val.m_type == KindOfUninit) {
4014 assertx(prop.attrs & AttrLateInit);
4015 continue;
4018 prop.val = nullable
4019 ? make_tv<KindOfNull>()
4020 : prop.typeConstraint.defaultValue();
4026 bool Index::register_class_alias(SString orig, SString alias) const {
4027 auto check = [&] (SString name) {
4028 if (m_data->classAliases.count(name)) return true;
4030 auto const classes = find_range(m_data->classInfo, name);
4031 if (begin(classes) != end(classes)) {
4032 return !(begin(classes)->second->cls->attrs & AttrUnique);
4034 auto const tas = find_range(m_data->typeAliases, name);
4035 if (begin(tas) == end(tas)) return true;
4036 return !(begin(tas)->second->attrs & AttrUnique);
4038 if (check(orig) && check(alias)) return true;
4039 if (m_data->ever_frozen) return false;
4040 std::lock_guard<std::mutex> lock{m_data->pending_class_aliases_mutex};
4041 m_data->pending_class_aliases.emplace_back(orig, alias);
4042 return true;
4045 void Index::update_class_aliases() {
4046 if (m_data->pending_class_aliases.empty()) return;
4047 FTRACE(1, "Index needs rebuilding due to {} class aliases\n",
4048 m_data->pending_class_aliases.size());
4049 throw rebuild { std::move(m_data->pending_class_aliases) };
4052 const CompactVector<const php::Class*>*
4053 Index::lookup_closures(const php::Class* cls) const {
4054 auto const it = m_data->classClosureMap.find(cls);
4055 if (it != end(m_data->classClosureMap)) {
4056 return &it->second;
4058 return nullptr;
4061 const hphp_fast_set<php::Func*>*
4062 Index::lookup_extra_methods(const php::Class* cls) const {
4063 if (cls->attrs & AttrNoExpandTrait) return nullptr;
4064 auto const it = m_data->classExtraMethodMap.find(cls);
4065 if (it != end(m_data->classExtraMethodMap)) {
4066 return &it->second;
4068 return nullptr;
4071 //////////////////////////////////////////////////////////////////////
4073 res::Class Index::resolve_class(const php::Class* cls) const {
4075 ClassInfo* result = nullptr;
4077 auto const classes = find_range(m_data->classInfo, cls->name);
4078 for (auto it = begin(classes); it != end(classes); ++it) {
4079 auto const cinfo = it->second;
4080 if (cinfo->cls == cls) {
4081 if (result) {
4082 result = nullptr;
4083 break;
4085 result = cinfo;
4089 // The function is supposed to return a cinfo if we can uniquely resolve cls.
4090 // In repo mode, if there is only one cinfo, return it.
4091 // In non-repo mode, we don't know all the cinfo's. So "only one cinfo" does
4092 // not mean anything unless it is a built-in and we disable rename/intercept.
4093 if (result && (RuntimeOption::RepoAuthoritative ||
4094 (!RuntimeOption::EvalJitEnableRenameFunction &&
4095 cls->attrs & AttrBuiltin))) {
4096 return res::Class { this, result };
4099 // We know its a class, not an enum or type alias, so return
4100 // by name
4101 return res::Class { this, cls->name.get() };
4104 folly::Optional<res::Class> Index::resolve_class(Context ctx,
4105 SString clsName) const {
4106 clsName = normalizeNS(clsName);
4108 if (ctx.cls && ctx.cls->name->isame(clsName)) return resolve_class(ctx.cls);
4111 * If there's only one preresolved ClassInfo, we can give out a
4112 * specific res::Class for it. (Any other possible resolutions were
4113 * known to fatal, or it was actually unique.)
4115 auto const classes = find_range(m_data->classInfo, clsName);
4116 for (auto it = begin(classes); it != end(classes); ++it) {
4117 auto const cinfo = it->second;
4118 if (cinfo->cls->attrs & AttrUnique) {
4119 if (debug &&
4120 (std::next(it) != end(classes) ||
4121 m_data->typeAliases.count(clsName))) {
4122 std::fprintf(stderr, "non unique \"unique\" class: %s\n",
4123 cinfo->cls->name->data());
4124 while (++it != end(classes)) {
4125 std::fprintf(stderr, " and %s\n", it->second->cls->name->data());
4127 auto const typeAliases = find_range(m_data->typeAliases, clsName);
4129 for (auto ta = begin(typeAliases); ta != end(typeAliases); ++ta) {
4130 std::fprintf(stderr, " and type-alias %s\n",
4131 ta->second->name->data());
4133 always_assert(0);
4135 return res::Class { this, cinfo };
4137 break;
4140 // We refuse to have name-only resolutions of enums, or typeAliases,
4141 // so that all name only resolutions can be treated as objects.
4142 if (!m_data->enums.count(clsName) &&
4143 !m_data->typeAliases.count(clsName)) {
4144 return res::Class { this, clsName };
4147 return folly::none;
4150 folly::Optional<res::Class> Index::selfCls(const Context& ctx) const {
4151 if (!ctx.cls || is_used_trait(*ctx.cls)) return folly::none;
4152 return resolve_class(ctx.cls);
4155 folly::Optional<res::Class> Index::parentCls(const Context& ctx) const {
4156 if (!ctx.cls || !ctx.cls->parentName) return folly::none;
4157 if (auto const parent = resolve_class(ctx.cls).parent()) return parent;
4158 return resolve_class(ctx, ctx.cls->parentName);
4161 Index::ResolvedInfo<folly::Optional<res::Class>>
4162 Index::resolve_type_name(SString inName) const {
4163 auto const res = resolve_type_name_internal(inName);
4164 return {
4165 res.type,
4166 res.nullable,
4167 res.value.isNull()
4168 ? folly::none
4169 : folly::make_optional(res::Class{this, res.value})
4173 Index::ResolvedInfo<Either<SString,ClassInfo*>>
4174 Index::resolve_type_name_internal(SString inName) const {
4175 folly::Optional<hphp_fast_set<const void*>> seen;
4177 auto nullable = false;
4178 auto name = inName;
4180 for (unsigned i = 0; ; ++i) {
4181 name = normalizeNS(name);
4182 auto const rec_it = m_data->records.find(name);
4183 if (rec_it != m_data->records.end()) {
4184 return { AnnotType::Record, nullable, nullptr };
4186 auto const classes = find_range(m_data->classInfo, name);
4187 auto const cls_it = begin(classes);
4188 if (cls_it != end(classes)) {
4189 auto const cinfo = cls_it->second;
4190 if (!(cinfo->cls->attrs & AttrUnique)) {
4191 if (!m_data->enums.count(name) && !m_data->typeAliases.count(name)) {
4192 break;
4194 return { AnnotType::Object, false, nullptr };
4196 if (!(cinfo->cls->attrs & AttrEnum)) {
4197 return { AnnotType::Object, nullable, cinfo };
4199 auto const& tc = cinfo->cls->enumBaseTy;
4200 assert(!tc.isNullable());
4201 if (tc.type() != AnnotType::Object) {
4202 auto const type = tc.type() == AnnotType::Mixed ?
4203 AnnotType::ArrayKey : tc.type();
4204 return { type, nullable, tc.typeName() };
4206 name = tc.typeName();
4207 } else {
4208 auto const typeAliases = find_range(m_data->typeAliases, name);
4209 auto const ta_it = begin(typeAliases);
4210 if (ta_it == end(typeAliases)) break;
4211 auto const ta = ta_it->second;
4212 if (!(ta->attrs & AttrUnique)) {
4213 return { AnnotType::Object, false, nullptr };
4215 nullable = nullable || ta->nullable;
4216 if (ta->type != AnnotType::Object) {
4217 return { ta->type, nullable, ta->value.get() };
4219 name = ta->value;
4222 // deal with cycles. Since we don't expect to
4223 // encounter them, just use a counter until we hit a chain length
4224 // of 10, then start tracking the names we resolve.
4225 if (i == 10) {
4226 seen.emplace();
4227 seen->insert(name);
4228 } else if (i > 10) {
4229 if (!seen->insert(name).second) {
4230 return { AnnotType::Object, false, nullptr };
4235 return { AnnotType::Object, nullable, name };
4238 struct Index::ConstraintResolution {
4239 /* implicit */ ConstraintResolution(Type type)
4240 : type{std::move(type)}
4241 , maybeMixed{false} {}
4242 ConstraintResolution(folly::Optional<Type> type, bool maybeMixed)
4243 : type{std::move(type)}
4244 , maybeMixed{maybeMixed} {}
4246 folly::Optional<Type> type;
4247 bool maybeMixed;
4250 Index::ConstraintResolution Index::resolve_named_type(
4251 const Context& ctx, SString name, const Type& candidate) const {
4253 auto const res = resolve_type_name_internal(name);
4255 if (res.nullable && candidate.subtypeOf(BInitNull)) return TInitNull;
4257 if (res.type == AnnotType::Object) {
4258 auto resolve = [&] (const res::Class& rcls) -> folly::Optional<Type> {
4259 if (!interface_supports_non_objects(rcls.name()) ||
4260 candidate.subtypeOrNull(BObj)) {
4261 return subObj(rcls);
4264 if (candidate.subtypeOrNull(BArr)) {
4265 if (interface_supports_array(rcls.name())) return TArr;
4266 } else if (candidate.subtypeOrNull(BVec)) {
4267 if (interface_supports_vec(rcls.name())) return TVec;
4268 } else if (candidate.subtypeOrNull(BDict)) {
4269 if (interface_supports_dict(rcls.name())) return TDict;
4270 } else if (candidate.subtypeOrNull(BKeyset)) {
4271 if (interface_supports_keyset(rcls.name())) return TKeyset;
4272 } else if (candidate.subtypeOrNull(BStr)) {
4273 if (interface_supports_string(rcls.name())) return TStr;
4274 } else if (candidate.subtypeOrNull(BInt)) {
4275 if (interface_supports_int(rcls.name())) return TInt;
4276 } else if (candidate.subtypeOrNull(BDbl)) {
4277 if (interface_supports_double(rcls.name())) return TDbl;
4279 return folly::none;
4282 if (res.value.isNull()) return ConstraintResolution{ folly::none, true };
4284 auto ty = res.value.right() ?
4285 resolve({ this, res.value.right() }) :
4286 resolve({ this, res.value.left() });
4288 if (ty && res.nullable) *ty = opt(std::move(*ty));
4289 return ConstraintResolution{ std::move(ty), false };
4292 return get_type_for_annotated_type(ctx, res.type, res.nullable,
4293 res.value.left(), candidate);
4296 std::pair<res::Class,php::Class*>
4297 Index::resolve_closure_class(Context ctx, int32_t idx) const {
4298 auto const cls = ctx.unit->classes[idx].get();
4299 auto const rcls = resolve_class(cls);
4301 // Closure classes must be unique and defined in the unit that uses
4302 // the CreateCl opcode, so resolution must succeed.
4303 always_assert_flog(
4304 rcls.resolved(),
4305 "A Closure class ({}) failed to resolve",
4306 cls->name
4309 return { rcls, cls };
4312 res::Class Index::builtin_class(SString name) const {
4313 auto const rcls = resolve_class(Context {}, name);
4314 always_assert_flog(
4315 rcls.hasValue() &&
4316 rcls->val.right() &&
4317 (rcls->val.right()->cls->attrs & AttrBuiltin),
4318 "A builtin class ({}) failed to resolve",
4319 name->data()
4321 return *rcls;
4324 res::Func Index::resolve_method(Context ctx,
4325 Type clsType,
4326 SString name) const {
4327 auto name_only = [&] {
4328 return res::Func { this, res::Func::MethodName { name } };
4331 if (!is_specialized_cls(clsType)) {
4332 return name_only();
4334 auto const dcls = dcls_of(clsType);
4335 auto const cinfo = dcls.cls.val.right();
4336 if (!cinfo) return name_only();
4338 // Classes may have more method families than methods. Any such
4339 // method families are guaranteed to all be public so we can do this
4340 // lookup as a last gasp before resorting to name_only().
4341 auto const find_extra_method = [&] {
4342 auto methIt = cinfo->methodFamilies.find(name);
4343 if (methIt == end(cinfo->methodFamilies)) return name_only();
4344 if (methIt->second.possibleFuncs()->size() == 1) {
4345 return res::Func { this, methIt->second.possibleFuncs()->front() };
4347 // If there was a sole implementer we can resolve to a single method, even
4348 // if the method was not declared on the interface itself.
4349 return res::Func { this, &methIt->second };
4352 // Interfaces *only* have the extra methods defined for all
4353 // subclasses
4354 if (cinfo->cls->attrs & AttrInterface) return find_extra_method();
4357 * Whether or not the context class has a private method with the
4358 * same name as the method we're trying to call.
4360 auto const contextMayHavePrivateWithSameName = folly::lazy([&]() -> bool {
4361 if (!ctx.cls) return false;
4362 auto const range = find_range(m_data->classInfo, ctx.cls->name);
4363 if (begin(range) == end(range)) {
4364 // This class had no pre-resolved ClassInfos, which means it
4365 // always fatals in any way it could be defined, so it doesn't
4366 // matter what we return here (as all methods in the context
4367 // class are unreachable code).
4368 return true;
4370 // Because of traits, each instantiation of the class could have
4371 // different private methods; we need to check them all.
4372 for (auto ctxInfo : range) {
4373 auto const iter = ctxInfo.second->methods.find(name);
4374 if (iter != end(ctxInfo.second->methods) &&
4375 iter->second.attrs & AttrPrivate &&
4376 iter->second.topLevel) {
4377 return true;
4380 return false;
4384 * Look up the method in the target class.
4386 auto const methIt = cinfo->methods.find(name);
4387 if (methIt == end(cinfo->methods)) return find_extra_method();
4388 if (methIt->second.attrs & AttrInterceptable) return name_only();
4389 auto const ftarget = methIt->second.func;
4391 // We need to revisit the hasPrivateAncestor code if we start being
4392 // able to look up methods on interfaces (currently they have empty
4393 // method tables).
4394 assert(!(cinfo->cls->attrs & AttrInterface));
4397 * If our candidate method has a private ancestor, unless it is
4398 * defined on this class, we need to make sure we don't erroneously
4399 * resolve the overriding method if the call is coming from the
4400 * context the defines the private method.
4402 * For now this just gives up if the context and the callee class
4403 * could be related and the context defines a private of the same
4404 * name. (We should actually try to resolve that method, though.)
4406 if (methIt->second.hasPrivateAncestor &&
4407 ctx.cls &&
4408 ctx.cls != ftarget->cls) {
4409 if (could_be_related(ctx.cls, cinfo->cls)) {
4410 if (contextMayHavePrivateWithSameName()) {
4411 return name_only();
4417 * Note: this currently isn't exhaustively checking accessibility,
4418 * except in cases where we must do a little bit of it for
4419 * correctness.
4421 * It is generally ok to resolve a method that won't actually be
4422 * called as long, as we only do so in cases where it will fatal at
4423 * runtime.
4425 * So, in the presence of magic methods, we must handle the fact
4426 * that attempting to call an inaccessible method will instead call
4427 * the magic method, if it exists. Note that if any class derives
4428 * from a class and adds magic methods, it can change still change
4429 * dispatch to call that method instead of fatalling.
4432 // If false, this method is definitely accessible. If true, it may
4433 // or may not be accessible.
4434 auto const couldBeInaccessible = [&] {
4435 // Public is always accessible.
4436 if (methIt->second.attrs & AttrPublic) return false;
4437 // An anonymous context won't have access if it wasn't public.
4438 if (!ctx.cls) return true;
4439 // If the calling context class is the same as the target class,
4440 // and the method is defined on this class or is protected, it
4441 // must be accessible.
4442 if (ctx.cls == cinfo->cls &&
4443 (methIt->second.topLevel || methIt->second.attrs & AttrProtected)) {
4444 return false;
4446 // If the method is private, the above case is the only case where
4447 // we'd know it was accessible.
4448 if (methIt->second.attrs & AttrPrivate) return true;
4450 * For the protected method case: if the context class must be
4451 * derived from the class that first defined the protected method
4452 * we know it is accessible. First check against the class of the
4453 * method (or cinfo for trait methods).
4455 if (must_be_derived_from(
4456 ctx.cls,
4457 ftarget->cls->attrs & AttrTrait ? cinfo->cls : ftarget->cls)) {
4458 return false;
4460 if (methIt->second.hasAncestor ||
4461 (ftarget->cls->attrs & AttrTrait && !methIt->second.topLevel)) {
4462 // Now we have find the first class that defined the method, and
4463 // check if *that* is an ancestor of the context class.
4464 auto parent = cinfo->parent;
4465 while (true) {
4466 assertx(parent);
4467 auto it = parent->methods.find(name);
4468 assertx(it != parent->methods.end());
4469 if (!it->second.hasAncestor && it->second.topLevel) {
4470 if (must_be_derived_from(ctx.cls, parent->cls)) return false;
4471 break;
4473 parent = parent->parent;
4477 * On the other hand, if the class that defined the method must be
4478 * derived from the context class, it is going to be accessible as
4479 * long as the context class does not define a private method with
4480 * the same name. (If it did, we'd be calling that private
4481 * method, which currently we don't ever resolve---we've removed
4482 * it from the method table in the classInfo.)
4484 if (must_be_derived_from(cinfo->cls, ctx.cls)) {
4485 if (!contextMayHavePrivateWithSameName()) {
4486 return false;
4489 // Other cases we're not sure about (maybe some non-unique classes
4490 // got in the way). Conservatively return that it might be
4491 // inaccessible.
4492 return true;
4495 auto resolve = [&] {
4496 create_func_info(*m_data, ftarget);
4497 return res::Func { this, mteFromIt(methIt) };
4500 switch (dcls.type) {
4501 case DCls::Exact:
4502 if (cinfo->magicCall.thisHas) {
4503 if (couldBeInaccessible()) return name_only();
4505 return resolve();
4506 case DCls::Sub:
4507 if (cinfo->magicCall.derivedHas) {
4508 if (couldBeInaccessible()) return name_only();
4510 if (methIt->second.attrs & AttrNoOverride) {
4511 return resolve();
4513 if (!options.FuncFamilies) return name_only();
4516 auto const famIt = cinfo->methodFamilies.find(name);
4517 if (famIt == end(cinfo->methodFamilies)) {
4518 return name_only();
4520 if (famIt->second.containsInterceptables()) {
4521 return name_only();
4523 return res::Func { this, &famIt->second };
4526 not_reached();
4529 folly::Optional<res::Func>
4530 Index::resolve_ctor(Context /*ctx*/, res::Class rcls, bool exact) const {
4531 auto const cinfo = rcls.val.right();
4532 if (!cinfo) return folly::none;
4533 if (cinfo->cls->attrs & (AttrInterface|AttrTrait)) return folly::none;
4535 auto const cit = cinfo->methods.find(s_construct.get());
4536 if (cit == end(cinfo->methods)) return folly::none;
4538 auto const ctor = mteFromIt(cit);
4539 if (exact || ctor->second.attrs & AttrNoOverride) {
4540 if (ctor->second.attrs & AttrInterceptable) return folly::none;
4541 create_func_info(*m_data, ctor->second.func);
4542 return res::Func { this, ctor };
4545 if (!options.FuncFamilies) return folly::none;
4547 auto const famIt = cinfo->methodFamilies.find(s_construct.get());
4548 if (famIt == end(cinfo->methodFamilies)) return folly::none;
4549 if (famIt->second.containsInterceptables()) return folly::none;
4550 return res::Func { this, &famIt->second };
4553 template<class FuncRange>
4554 res::Func
4555 Index::resolve_func_helper(const FuncRange& funcs, SString name) const {
4556 auto name_only = [&] (bool renamable) {
4557 return res::Func { this, res::Func::FuncName { name, renamable } };
4560 // no resolution
4561 if (begin(funcs) == end(funcs)) return name_only(false);
4563 auto const func = begin(funcs)->second;
4564 if (func->attrs & AttrInterceptable) return name_only(true);
4566 // multiple resolutions
4567 if (std::next(begin(funcs)) != end(funcs)) {
4568 assert(!(func->attrs & AttrUnique));
4569 if (debug && any_interceptable_functions()) {
4570 for (auto const DEBUG_ONLY f : funcs) {
4571 assertx(!(f.second->attrs & AttrInterceptable));
4574 return name_only(false);
4577 // single resolution, in whole-program mode, that's it
4578 if (RuntimeOption::RepoAuthoritative) {
4579 assert(func->attrs & AttrUnique);
4580 return do_resolve(func);
4583 // single-unit mode, check builtins
4584 if (func->attrs & AttrBuiltin) {
4585 assert(func->attrs & AttrUnique);
4586 return do_resolve(func);
4589 // single-unit, non-builtin, not renamable
4590 return name_only(false);
4593 res::Func Index::resolve_func(Context /*ctx*/, SString name) const {
4594 name = normalizeNS(name);
4595 auto const funcs = find_range(m_data->funcs, name);
4596 return resolve_func_helper(funcs, name);
4600 * Gets a type for the constraint.
4602 * If getSuperType is true, the type could be a super-type of the
4603 * actual type constraint (eg TCell). Otherwise its guaranteed that
4604 * for any t, t.subtypeOf(get_type_for_constraint<false>(ctx, tc, t)
4605 * implies t would pass the constraint.
4607 * The candidate type is used to disambiguate; if we're applying a
4608 * Traversable constraint to a TObj, we should return
4609 * subObj(Traversable). If we're applying it to an Array, we should
4610 * return Array.
4612 template<bool getSuperType>
4613 Type Index::get_type_for_constraint(Context ctx,
4614 const TypeConstraint& tc,
4615 const Type& candidate) const {
4616 assertx(IMPLIES(!tc.isCheckable(), tc.isMixed()));
4618 if (getSuperType) {
4620 * Soft hints (@Foo) are not checked.
4622 if (tc.isSoft()) return TCell;
4625 auto const res = get_type_for_annotated_type(
4626 ctx,
4627 tc.type(),
4628 tc.isNullable(),
4629 tc.typeName(),
4630 candidate
4632 if (res.type) return *res.type;
4633 // If the type constraint might be mixed, then the value could be
4634 // uninit. Any other type constraint implies TInitCell.
4635 return getSuperType ? (res.maybeMixed ? TCell : TInitCell) : TBottom;
4638 bool Index::prop_tc_maybe_unenforced(const php::Class& propCls,
4639 const TypeConstraint& tc) const {
4640 assertx(tc.validForProp());
4641 if (RuntimeOption::EvalCheckPropTypeHints <= 2) return true;
4642 if (!tc.isCheckable()) return true;
4643 if (tc.isSoft()) return true;
4644 auto const res = get_type_for_annotated_type(
4645 Context { nullptr, nullptr, &propCls },
4646 tc.type(),
4647 tc.isNullable(),
4648 tc.typeName(),
4649 TGen
4651 return res.maybeMixed;
4654 Index::ConstraintResolution Index::get_type_for_annotated_type(
4655 Context ctx, AnnotType annot, bool nullable,
4656 SString name, const Type& candidate) const {
4658 if (candidate.subtypeOf(BInitNull) && nullable) {
4659 return TInitNull;
4662 auto mainType = [&]() -> ConstraintResolution {
4663 switch (getAnnotMetaType(annot)) {
4664 case AnnotMetaType::Precise: {
4665 auto const dt = getAnnotDataType(annot);
4667 switch (dt) {
4668 case KindOfNull: return TNull;
4669 case KindOfBoolean: return TBool;
4670 case KindOfInt64: return TInt;
4671 case KindOfDouble: return TDbl;
4672 case KindOfPersistentString:
4673 case KindOfString: return TStr;
4674 case KindOfPersistentVec:
4675 case KindOfVec: return TVec;
4676 case KindOfPersistentDict:
4677 case KindOfDict: return TDict;
4678 case KindOfPersistentKeyset:
4679 case KindOfKeyset: return TKeyset;
4680 case KindOfPersistentShape:
4681 case KindOfShape: not_implemented();
4682 case KindOfPersistentArray:
4683 case KindOfArray: return TPArr;
4684 case KindOfResource: return TRes;
4685 case KindOfClsMeth: return TClsMeth;
4686 case KindOfRecord: return TRecord;
4687 case KindOfObject:
4688 return resolve_named_type(ctx, name, candidate);
4689 case KindOfUninit:
4690 case KindOfRef:
4691 case KindOfFunc:
4692 case KindOfClass:
4693 always_assert_flog(false, "Unexpected DataType");
4694 break;
4696 break;
4698 case AnnotMetaType::Mixed:
4700 * Here we handle "mixed", typevars, and some other ignored
4701 * typehints (ex. "(function(..): ..)" typehints).
4703 return { TCell, true };
4704 case AnnotMetaType::Nothing:
4705 case AnnotMetaType::NoReturn:
4706 return TBottom;
4707 case AnnotMetaType::Nonnull:
4708 if (candidate.subtypeOf(BInitNull)) return TBottom;
4709 if (!candidate.couldBe(BInitNull)) return candidate;
4710 if (is_opt(candidate)) return unopt(candidate);
4711 break;
4712 case AnnotMetaType::This:
4713 if (auto s = selfCls(ctx)) return setctx(subObj(*s));
4714 break;
4715 case AnnotMetaType::Self:
4716 if (auto s = selfCls(ctx)) return subObj(*s);
4717 break;
4718 case AnnotMetaType::Parent:
4719 if (auto p = parentCls(ctx)) return subObj(*p);
4720 break;
4721 case AnnotMetaType::Callable:
4722 break;
4723 case AnnotMetaType::Number:
4724 return TNum;
4725 case AnnotMetaType::ArrayKey:
4726 if (candidate.subtypeOf(BInt)) return TInt;
4727 if (candidate.subtypeOf(BStr)) return TStr;
4728 return TArrKey;
4729 case AnnotMetaType::VArray:
4730 assertx(!RuntimeOption::EvalHackArrDVArrs);
4731 return TVArr;
4732 case AnnotMetaType::DArray:
4733 assertx(!RuntimeOption::EvalHackArrDVArrs);
4734 return TDArr;
4735 case AnnotMetaType::VArrOrDArr:
4736 assertx(!RuntimeOption::EvalHackArrDVArrs);
4737 return TArr;
4738 case AnnotMetaType::VecOrDict:
4739 if (candidate.subtypeOf(BVec)) return TVec;
4740 if (candidate.subtypeOf(BDict)) return TDict;
4741 break;
4742 case AnnotMetaType::ArrayLike:
4743 if (candidate.subtypeOf(BVArr)) return TVArr;
4744 if (candidate.subtypeOf(BDArr)) return TDArr;
4745 if (candidate.subtypeOf(BArr)) return TArr;
4746 if (candidate.subtypeOf(BVec)) return TVec;
4747 if (candidate.subtypeOf(BDict)) return TDict;
4748 if (candidate.subtypeOf(BKeyset)) return TKeyset;
4749 break;
4751 return ConstraintResolution{ folly::none, false };
4752 }();
4754 if (mainType.type && nullable && !mainType.type->couldBe(BInitNull)) {
4755 mainType.type = opt(*mainType.type);
4757 return mainType;
4760 Type Index::lookup_constraint(Context ctx,
4761 const TypeConstraint& tc,
4762 const Type& t) const {
4763 return get_type_for_constraint<true>(ctx, tc, t);
4766 bool Index::satisfies_constraint(Context ctx, const Type& t,
4767 const TypeConstraint& tc) const {
4768 // T45709201: Currently record types in HHBBC are not specialized.
4769 // Therefore, they can never satisfy a constrant.
4770 if (t.subtypeOf(BOptRecord)) return false;
4771 auto const tcType = get_type_for_constraint<false>(ctx, tc, t);
4772 if (t.moreRefined(loosen_dvarrayness(tcType))) {
4773 // For d/varrays, we might satisfy the constraint, but still not want to
4774 // optimize away the type-check (because we'll raise a notice on a d/varray
4775 // mismatch), so do some additional checking here to rule that out.
4776 if (!RuntimeOption::EvalHackArrCompatTypeHintNotices) return true;
4777 if (!tcType.subtypeOrNull(BArr) || tcType.subtypeOf(BNull)) return true;
4778 assertx(t.subtypeOrNull(BArr));
4779 if (tcType.subtypeOrNull(BVArr)) return t.subtypeOrNull(BVArr);
4780 if (tcType.subtypeOrNull(BDArr)) return t.subtypeOrNull(BDArr);
4781 if (tcType.subtypeOrNull(BPArr)) return t.subtypeOrNull(BPArr);
4783 return false;
4786 bool Index::could_have_reified_type(const TypeConstraint& tc) const {
4787 if (!tc.isObject()) return false;
4788 auto const name = tc.typeName();
4789 auto const resolved = resolve_type_name_internal(name);
4790 if (resolved.type != AnnotType::Object) return false;
4791 res::Class rcls{this, resolved.value};
4792 return rcls.couldHaveReifiedGenerics();
4795 folly::Optional<bool>
4796 Index::supports_async_eager_return(res::Func rfunc) const {
4797 auto const supportsAER = [] (const php::Func* func) {
4798 // Async functions always support async eager return.
4799 if (func->isAsync && !func->isGenerator) return true;
4801 // No other functions support async eager return yet.
4802 return false;
4805 return match<folly::Optional<bool>>(
4806 rfunc.val,
4807 [&](res::Func::FuncName) { return folly::none; },
4808 [&](res::Func::MethodName) { return folly::none; },
4809 [&](FuncInfo* finfo) { return supportsAER(finfo->func); },
4810 [&](const MethTabEntryPair* mte) { return supportsAER(mte->second.func); },
4811 [&](FuncFamily* fam) -> folly::Optional<bool> {
4812 auto ret = folly::Optional<bool>{};
4813 for (auto const pf : fam->possibleFuncs()) {
4814 // Abstract functions are never called.
4815 if (pf->second.attrs & AttrAbstract) continue;
4816 auto const val = supportsAER(pf->second.func);
4817 if (ret && *ret != val) return folly::none;
4818 ret = val;
4820 return ret;
4824 bool Index::is_effect_free(res::Func rfunc) const {
4825 return match<bool>(
4826 rfunc.val,
4827 [&](res::Func::FuncName) { return false; },
4828 [&](res::Func::MethodName) { return false; },
4829 [&](FuncInfo* finfo) {
4830 return finfo->effectFree;
4832 [&](const MethTabEntryPair* mte) {
4833 return func_info(*m_data, mte->second.func)->effectFree;
4835 [&](FuncFamily* fam) {
4836 return false;
4840 bool Index::any_interceptable_functions() const {
4841 return m_data->any_interceptable_functions;
4844 const php::Const* Index::lookup_class_const_ptr(Context ctx,
4845 res::Class rcls,
4846 SString cnsName,
4847 bool allow_tconst) const {
4848 if (rcls.val.left()) return nullptr;
4849 auto const cinfo = rcls.val.right();
4851 auto const it = cinfo->clsConstants.find(cnsName);
4852 if (it != end(cinfo->clsConstants)) {
4853 if (!it->second->val.hasValue() ||
4854 (!allow_tconst && it->second->isTypeconst)) {
4855 // This is an abstract class constant or typeconstant
4856 return nullptr;
4858 if (it->second->val.value().m_type == KindOfUninit) {
4859 // This is a class constant that needs an 86cinit to run.
4860 // We'll add a dependency to make sure we're re-run if it
4861 // resolves anything.
4862 auto const cinit = it->second->cls->methods.back().get();
4863 assert(cinit->name == s_86cinit.get());
4864 add_dependency(*m_data, cinit, ctx, Dep::ClsConst);
4865 return nullptr;
4867 return it->second;
4869 return nullptr;
4872 Type Index::lookup_class_constant(Context ctx,
4873 res::Class rcls,
4874 SString cnsName,
4875 bool allow_tconst) const {
4876 auto const cnst = lookup_class_const_ptr(ctx, rcls, cnsName, allow_tconst);
4877 if (!cnst) return TInitCell;
4878 return from_cell(cnst->val.value());
4881 folly::Optional<Type> Index::lookup_constant(Context ctx,
4882 SString cnsName) const {
4883 ConstInfoConcurrentMap::const_accessor acc;
4884 if (!m_data->constants.find(acc, cnsName)) {
4885 // flag to indicate that the constant isn't in the index yet.
4886 if (options.HardConstProp) return folly::none;
4887 return TInitCell;
4890 if (acc->second.func &&
4891 !acc->second.readonly &&
4892 !acc->second.system &&
4893 !tv(acc->second.type)) {
4894 // we might refine the type
4895 add_dependency(*m_data, acc->second.func, ctx, Dep::ConstVal);
4898 return acc->second.type;
4901 folly::Optional<Cell> Index::lookup_persistent_constant(SString cnsName) const {
4902 if (!options.HardConstProp) return folly::none;
4903 ConstInfoConcurrentMap::const_accessor acc;
4904 if (!m_data->constants.find(acc, cnsName)) return folly::none;
4905 return tv(acc->second.type);
4908 bool Index::func_depends_on_arg(const php::Func* func, int arg) const {
4909 auto const& finfo = *func_info(*m_data, func);
4910 return arg >= finfo.unusedParams.size() || !finfo.unusedParams.test(arg);
4913 Type Index::lookup_foldable_return_type(Context ctx,
4914 const php::Func* func,
4915 Type ctxType,
4916 CompactVector<Type> args) const {
4917 constexpr auto max_interp_nexting_level = 2;
4918 static __thread uint32_t interp_nesting_level;
4919 static __thread Context base_ctx;
4921 // Don't fold functions when staticness mismatches
4922 if ((func->attrs & AttrStatic) && ctxType.couldBe(TObj)) return TTop;
4923 if (!(func->attrs & AttrStatic) && ctxType.couldBe(TCls)) return TTop;
4925 auto const& finfo = *func_info(*m_data, func);
4926 if (finfo.effectFree && is_scalar(finfo.returnTy)) {
4927 return finfo.returnTy;
4930 auto const calleeCtx = CallContext {
4931 func,
4932 std::move(args),
4933 std::move(ctxType)
4936 auto showArgs DEBUG_ONLY = [] (const CompactVector<Type>& a) {
4937 std::string ret, sep;
4938 for (auto& arg : a) {
4939 folly::format(&ret, "{}{}", sep, show(arg));
4940 sep = ",";
4942 return ret;
4946 ContextRetTyMap::const_accessor acc;
4947 if (m_data->foldableReturnTypeMap.find(acc, calleeCtx)) {
4948 FTRACE_MOD(
4949 Trace::hhbbc, 4,
4950 "Found foldableReturnType for {}{}{} with args {} (hash: {})\n",
4951 func->cls ? func->cls->name : empty_string().get(),
4952 func->cls ? "::" : "",
4953 func->name,
4954 showArgs(calleeCtx.args),
4955 CallContextHashCompare{}.hash(calleeCtx));
4957 assertx(is_scalar(acc->second));
4958 return acc->second;
4962 if (frozen()) {
4963 FTRACE_MOD(
4964 Trace::hhbbc, 4,
4965 "MISSING: foldableReturnType for {}{}{} with args {} (hash: {})\n",
4966 func->cls ? func->cls->name : empty_string().get(),
4967 func->cls ? "::" : "",
4968 func->name,
4969 showArgs(calleeCtx.args),
4970 CallContextHashCompare{}.hash(calleeCtx));
4971 return TTop;
4974 if (!interp_nesting_level) {
4975 base_ctx = ctx;
4976 } else if (interp_nesting_level > max_interp_nexting_level) {
4977 add_dependency(*m_data, func, base_ctx, Dep::InlineDepthLimit);
4978 return TTop;
4981 auto const contextType = [&] {
4982 ++interp_nesting_level;
4983 SCOPE_EXIT { --interp_nesting_level; };
4985 auto const fa = analyze_func_inline(
4986 *this,
4987 Context { func->unit, const_cast<php::Func*>(func), func->cls },
4988 calleeCtx.context,
4989 calleeCtx.args,
4990 CollectionOpts::EffectFreeOnly
4992 return fa.effectFree ? fa.inferredReturn : TTop;
4993 }();
4995 if (!is_scalar(contextType)) {
4996 return TTop;
4999 ContextRetTyMap::accessor acc;
5000 if (m_data->foldableReturnTypeMap.insert(acc, calleeCtx)) {
5001 acc->second = contextType;
5002 } else {
5003 // someone beat us to it
5004 assertx(acc->second == contextType);
5006 return contextType;
5009 Type Index::lookup_return_type(Context ctx, res::Func rfunc) const {
5010 return match<Type>(
5011 rfunc.val,
5012 [&](res::Func::FuncName) { return TInitCell; },
5013 [&](res::Func::MethodName) { return TInitCell; },
5014 [&](FuncInfo* finfo) {
5015 add_dependency(*m_data, finfo->func, ctx, Dep::ReturnTy);
5016 return unctx(finfo->returnTy);
5018 [&](const MethTabEntryPair* mte) {
5019 add_dependency(*m_data, mte->second.func, ctx, Dep::ReturnTy);
5020 auto const finfo = func_info(*m_data, mte->second.func);
5021 if (!finfo->func) return TInitCell;
5022 return unctx(finfo->returnTy);
5024 [&](FuncFamily* fam) {
5025 auto ret = TBottom;
5026 for (auto const pf : fam->possibleFuncs()) {
5027 add_dependency(*m_data, pf->second.func, ctx, Dep::ReturnTy);
5028 auto const finfo = func_info(*m_data, pf->second.func);
5029 if (!finfo->func) return TInitCell;
5030 ret |= unctx(finfo->returnTy);
5032 return ret;
5036 Type Index::lookup_return_type(Context caller,
5037 const CompactVector<Type>& args,
5038 const Type& context,
5039 res::Func rfunc) const {
5040 return match<Type>(
5041 rfunc.val,
5042 [&](res::Func::FuncName) {
5043 return lookup_return_type(caller, rfunc);
5045 [&](res::Func::MethodName) {
5046 return lookup_return_type(caller, rfunc);
5048 [&](FuncInfo* finfo) {
5049 add_dependency(*m_data, finfo->func, caller, Dep::ReturnTy);
5050 return context_sensitive_return_type(*m_data,
5051 { finfo->func, args, context });
5053 [&](const MethTabEntryPair* mte) {
5054 add_dependency(*m_data, mte->second.func, caller, Dep::ReturnTy);
5055 auto const finfo = func_info(*m_data, mte->second.func);
5056 if (!finfo->func) return TInitCell;
5057 return context_sensitive_return_type(*m_data,
5058 { finfo->func, args, context });
5060 [&] (FuncFamily* fam) {
5061 auto ret = TBottom;
5062 for (auto& pf : fam->possibleFuncs()) {
5063 add_dependency(*m_data, pf->second.func, caller, Dep::ReturnTy);
5064 auto const finfo = func_info(*m_data, pf->second.func);
5065 if (!finfo->func) ret |= TInitCell;
5066 else ret |= return_with_context(finfo->returnTy, context);
5068 return ret;
5073 CompactVector<Type>
5074 Index::lookup_closure_use_vars(const php::Func* func,
5075 bool move) const {
5076 assert(func->isClosureBody);
5078 auto const numUseVars = closure_num_use_vars(func);
5079 if (!numUseVars) return {};
5080 auto const it = m_data->closureUseVars.find(func->cls);
5081 if (it == end(m_data->closureUseVars)) {
5082 return CompactVector<Type>(numUseVars, TCell);
5084 if (move) return std::move(it->second);
5085 return it->second;
5088 Type Index::lookup_return_type_raw(const php::Func* f) const {
5089 auto it = func_info(*m_data, f);
5090 if (it->func) {
5091 assertx(it->func == f);
5092 return it->returnTy;
5094 return TInitCell;
5097 bool Index::lookup_this_available(const php::Func* f) const {
5098 return (f->attrs & AttrRequiresThis) && !f->isClosureBody;
5101 PrepKind Index::lookup_param_prep(Context /*ctx*/, res::Func rfunc,
5102 uint32_t paramId) const {
5103 return match<PrepKind>(
5104 rfunc.val,
5105 [&] (res::Func::FuncName s) {
5106 if (!RuntimeOption::RepoAuthoritative || s.renamable) return PrepKind::Unknown;
5107 return prep_kind_from_set(find_range(m_data->funcs, s.name), paramId);
5109 [&] (res::Func::MethodName s) {
5110 if (!RuntimeOption::RepoAuthoritative) return PrepKind::Unknown;
5111 auto const it = m_data->method_ref_params_by_name.find(s.name);
5112 if (it == end(m_data->method_ref_params_by_name)) {
5113 // There was no entry, so no method by this name takes a parameter
5114 // by reference.
5115 return PrepKind::Val;
5118 * If we think it's supposed to be PrepKind::Ref, we still can't be sure
5119 * unless we go through some effort to guarantee that it can't be going
5120 * to an __call function magically (which will never take anything by
5121 * ref).
5123 if (paramId < sizeof(it->second) * CHAR_BIT) {
5124 return ((it->second >> paramId) & 1) ?
5125 PrepKind::Unknown : PrepKind::Val;
5127 auto const kind = prep_kind_from_set(
5128 find_range(m_data->methods, s.name),
5129 paramId
5131 return kind == PrepKind::Ref ? PrepKind::Unknown : kind;
5133 [&] (FuncInfo* finfo) {
5134 return func_param_prep(finfo->func, paramId);
5136 [&] (const MethTabEntryPair* mte) {
5137 return func_param_prep(mte->second.func, paramId);
5139 [&] (FuncFamily* fam) {
5140 assert(RuntimeOption::RepoAuthoritative);
5141 return prep_kind_from_set(fam->possibleFuncs(), paramId);
5146 PropState
5147 Index::lookup_private_props(const php::Class* cls,
5148 bool move) const {
5149 auto it = m_data->privatePropInfo.find(cls);
5150 if (it != end(m_data->privatePropInfo)) {
5151 if (move) return std::move(it->second);
5152 return it->second;
5154 return make_unknown_propstate(
5155 cls,
5156 [&] (const php::Prop& prop) {
5157 return (prop.attrs & AttrPrivate) && !(prop.attrs & AttrStatic);
5162 PropState
5163 Index::lookup_private_statics(const php::Class* cls,
5164 bool move) const {
5165 auto it = m_data->privateStaticPropInfo.find(cls);
5166 if (it != end(m_data->privateStaticPropInfo)) {
5167 if (move) return std::move(it->second);
5168 return it->second;
5170 return make_unknown_propstate(
5171 cls,
5172 [&] (const php::Prop& prop) {
5173 return (prop.attrs & AttrPrivate) && (prop.attrs & AttrStatic);
5178 Type Index::lookup_public_static(Context ctx,
5179 const Type& cls,
5180 const Type& name) const {
5181 if (!is_specialized_cls(cls)) return TInitGen;
5183 auto const vname = tv(name);
5184 if (!vname || vname->m_type != KindOfPersistentString) return TInitGen;
5185 auto const sname = vname->m_data.pstr;
5187 if (ctx.unit) add_dependency(*m_data, sname, ctx, Dep::PublicSPropName);
5189 auto const dcls = dcls_of(cls);
5190 if (dcls.cls.val.left()) return TInitGen;
5191 auto const cinfo = dcls.cls.val.right();
5193 switch (dcls.type) {
5194 case DCls::Sub: {
5195 auto ty = TBottom;
5196 for (auto const sub : cinfo->subclassList) {
5197 ty |= lookup_public_static_impl(
5198 *m_data,
5199 sub,
5200 sname
5201 ).inferredType;
5203 return ty;
5205 case DCls::Exact:
5206 return lookup_public_static_impl(
5207 *m_data,
5208 cinfo,
5209 sname
5210 ).inferredType;
5212 always_assert(false);
5215 Type Index::lookup_public_static(Context ctx,
5216 const php::Class* cls,
5217 SString name) const {
5218 if (ctx.unit) add_dependency(*m_data, name, ctx, Dep::PublicSPropName);
5219 return lookup_public_static_impl(*m_data, cls, name).inferredType;
5222 bool Index::lookup_public_static_immutable(const php::Class* cls,
5223 SString name) const {
5224 return !lookup_public_static_impl(*m_data, cls, name).everModified;
5227 bool Index::lookup_public_static_maybe_late_init(const Type& cls,
5228 const Type& name) const {
5229 auto const cinfo = [&] () -> const ClassInfo* {
5230 if (!is_specialized_cls(cls)) {
5231 return nullptr;
5233 auto const dcls = dcls_of(cls);
5234 switch (dcls.type) {
5235 case DCls::Sub: return nullptr;
5236 case DCls::Exact: return dcls.cls.val.right();
5238 not_reached();
5239 }();
5240 if (!cinfo) return true;
5242 auto const vname = tv(name);
5243 if (!vname || (vname && vname->m_type != KindOfPersistentString)) {
5244 return true;
5246 auto const sname = vname->m_data.pstr;
5248 auto isLateInit = false;
5249 visit_parent_cinfo(
5250 cinfo,
5251 [&] (const ClassInfo* ci) -> bool {
5252 for (auto const& prop : ci->cls->properties) {
5253 if (prop.name == sname) {
5254 isLateInit = prop.attrs & AttrLateInit;
5255 return true;
5258 return false;
5261 return isLateInit;
5264 Type Index::lookup_public_prop(const Type& cls, const Type& name) const {
5265 if (!is_specialized_cls(cls)) return TGen;
5267 auto const vname = tv(name);
5268 if (!vname || vname->m_type != KindOfPersistentString) return TGen;
5269 auto const sname = vname->m_data.pstr;
5271 auto const dcls = dcls_of(cls);
5272 if (dcls.cls.val.left()) return TGen;
5273 auto const cinfo = dcls.cls.val.right();
5275 switch (dcls.type) {
5276 case DCls::Sub: {
5277 auto ty = TBottom;
5278 for (auto const sub : cinfo->subclassList) {
5279 ty |= lookup_public_prop_impl(
5280 *m_data,
5281 sub,
5282 sname
5285 return ty;
5287 case DCls::Exact:
5288 return lookup_public_prop_impl(
5289 *m_data,
5290 cinfo,
5291 sname
5294 always_assert(false);
5297 Type Index::lookup_public_prop(const php::Class* cls, SString name) const {
5298 auto const classes = find_range(m_data->classInfo, cls->name);
5299 if (begin(classes) == end(classes) ||
5300 std::next(begin(classes)) != end(classes)) {
5301 return TGen;
5303 return lookup_public_prop_impl(*m_data, begin(classes)->second, name);
5306 bool Index::lookup_class_init_might_raise(Context ctx, res::Class cls) const {
5307 return cls.val.match(
5308 [] (SString) { return true; },
5309 [&] (ClassInfo* cinfo) {
5310 // Check this class and all of its parents for possible inequivalent
5311 // redeclarations or bad initial values.
5312 do {
5313 // Be conservative for now if we have unflattened traits.
5314 if (!cinfo->traitProps.empty()) return true;
5315 if (cinfo->hasBadRedeclareProp) return true;
5316 if (cinfo->hasBadInitialPropValues) {
5317 add_dependency(*m_data, cinfo->cls, ctx, Dep::PropBadInitialValues);
5318 return true;
5320 cinfo = cinfo->parent;
5321 } while (cinfo);
5322 return false;
5327 void Index::join_iface_vtable_thread() const {
5328 if (m_data->compute_iface_vtables.joinable()) {
5329 m_data->compute_iface_vtables.join();
5333 Slot
5334 Index::lookup_iface_vtable_slot(const php::Class* cls) const {
5335 return folly::get_default(m_data->ifaceSlotMap, cls, kInvalidSlot);
5338 //////////////////////////////////////////////////////////////////////
5340 DependencyContext Index::dependency_context(const Context& ctx) const {
5341 return dep_context(*m_data, ctx);
5344 void Index::use_class_dependencies(bool f) {
5345 if (f != m_data->useClassDependencies) {
5346 m_data->dependencyMap.clear();
5347 m_data->useClassDependencies = f;
5351 void Index::init_public_static_prop_types() {
5352 for (auto const& cinfo : m_data->allClassInfos) {
5353 for (auto const& prop : cinfo->cls->properties) {
5354 if (!(prop.attrs & AttrPublic) || !(prop.attrs & AttrStatic)) {
5355 continue;
5359 * If the initializer type is TUninit, it means an 86sinit provides the
5360 * actual initialization type or it is AttrLateInit. So we don't want to
5361 * include the Uninit (which isn't really a user-visible type for the
5362 * property) or by the time we union things in we'll have inferred nothing
5363 * much.
5365 * If the property is AttrLateInitSoft, it can be anything because of the
5366 * default value, so give the initial value as TInitGen and don't honor
5367 * the type-constraint, which will keep us from inferring anything.
5369 auto const initial = [&] {
5370 if (prop.attrs & AttrLateInitSoft) return TInitGen;
5371 auto const tyRaw = from_cell(prop.val);
5372 if (tyRaw.subtypeOf(BUninit)) return TBottom;
5373 if (prop.attrs & AttrSystemInitialValue) return tyRaw;
5374 return adjust_type_for_prop(
5375 *this, *cinfo->cls, &prop.typeConstraint, tyRaw
5377 }();
5379 auto const tc = (prop.attrs & AttrLateInitSoft)
5380 ? nullptr
5381 : &prop.typeConstraint;
5383 cinfo->publicStaticProps[prop.name] =
5384 PublicSPropEntry {
5385 union_of(
5386 adjust_type_for_prop(*this, *cinfo->cls, tc, TInitGen),
5387 initial
5389 initial,
5392 true
5398 void Index::refine_class_constants(
5399 const Context& ctx,
5400 const CompactVector<std::pair<size_t, TypedValue>>& resolved,
5401 DependencyContextSet& deps) {
5402 if (!resolved.size()) return;
5403 auto& constants = ctx.func->cls->constants;
5404 for (auto const& c : resolved) {
5405 assertx(c.first < constants.size());
5406 auto& cnst = constants[c.first];
5407 assertx(cnst.val && cnst.val->m_type == KindOfUninit);
5408 cnst.val = c.second;
5410 find_deps(*m_data, ctx.func, Dep::ClsConst, deps);
5413 void Index::refine_constants(const FuncAnalysisResult& fa,
5414 DependencyContextSet& deps) {
5415 auto const func = fa.ctx.func;
5416 for (auto const& it : fa.cnsMap) {
5417 if (it.second.m_type == kReadOnlyConstant) {
5418 // this constant was read, but there was nothing mentioning it
5419 // in the index. Should only happen on the first iteration. We
5420 // need to reprocess this func.
5421 assert(fa.readsUntrackedConstants);
5422 // if there's already an entry, we don't want to do anything,
5423 // otherwise just insert a dummy entry to indicate that it was
5424 // read.
5425 ConstInfoConcurrentMap::accessor acc;
5426 if (m_data->constants.insert(acc, it.first)) {
5427 acc->second = ConstInfo {func, TInitCell, false, true};
5429 continue;
5432 if (it.second.m_type == kDynamicConstant || !is_pseudomain(func)) {
5433 // two definitions, or a non-pseuodmain definition
5434 ConstInfoConcurrentMap::accessor acc;
5435 m_data->constants.insert(acc, it.first);
5436 auto& c = acc->second;
5437 if (!c.system) {
5438 c.func = nullptr;
5439 c.type = TInitCell;
5440 c.readonly = false;
5442 continue;
5445 auto t = it.second.m_type == KindOfUninit ?
5446 TInitCell : from_cell(it.second);
5448 assertx(t.equivalentlyRefined(unctx(t)));
5450 ConstInfoConcurrentMap::accessor acc;
5451 if (m_data->constants.insert(acc, it.first)) {
5452 acc->second = ConstInfo {func, t};
5453 continue;
5456 if (acc->second.system) continue;
5458 if (acc->second.readonly) {
5459 acc->second.func = func;
5460 acc->second.type = t;
5461 acc->second.readonly = false;
5462 continue;
5465 if (acc->second.func != func) {
5466 acc->second.func = nullptr;
5467 acc->second.type = TInitCell;
5468 continue;
5471 assertx(t.moreRefined(acc->second.type));
5472 if (!t.equivalentlyRefined(acc->second.type)) {
5473 acc->second.type = t;
5474 find_deps(*m_data, func, Dep::ConstVal, deps);
5477 if (fa.readsUntrackedConstants) deps.emplace(dep_context(*m_data, fa.ctx));
5480 void Index::fixup_return_type(const php::Func* func,
5481 Type& retTy) const {
5482 if (func->isGenerator) {
5483 if (func->isAsync) {
5484 // Async generators always return AsyncGenerator object.
5485 retTy = objExact(builtin_class(s_AsyncGenerator.get()));
5486 } else {
5487 // Non-async generators always return Generator object.
5488 retTy = objExact(builtin_class(s_Generator.get()));
5490 } else if (func->isAsync) {
5491 // Async functions always return WaitH<T>, where T is the type returned
5492 // internally.
5493 retTy = wait_handle(*this, std::move(retTy));
5497 void Index::init_return_type(const php::Func* func) {
5498 if ((func->attrs & AttrBuiltin) || func->isMemoizeWrapper) {
5499 return;
5502 auto make_type = [&] (const TypeConstraint& tc) {
5503 if (tc.isSoft() ||
5504 (RuntimeOption::EvalThisTypeHintLevel != 3 && tc.isThis())) {
5505 return TBottom;
5507 return loosen_dvarrayness(
5508 lookup_constraint(
5509 Context {
5510 func->unit,
5511 const_cast<php::Func*>(func),
5512 func->cls && func->cls->closureContextCls ?
5513 func->cls->closureContextCls : func->cls
5519 auto const finfo = create_func_info(*m_data, func);
5521 auto tcT = make_type(func->retTypeConstraint);
5522 if (tcT == TBottom) return;
5524 if (func->attrs & AttrTakesInOutParams) {
5525 std::vector<Type> types;
5526 types.emplace_back(intersection_of(TInitCell, std::move(tcT)));
5527 for (auto& p : func->params) {
5528 if (!p.inout) continue;
5529 auto t = make_type(p.typeConstraint);
5530 if (t == TBottom) return;
5531 types.emplace_back(intersection_of(TInitCell, std::move(t)));
5533 tcT = vec(std::move(types));
5536 tcT = to_cell(std::move(tcT));
5537 if (is_specialized_obj(tcT)) {
5538 if (dobj_of(tcT).cls.couldBeInterfaceOrTrait()) {
5539 tcT = is_opt(tcT) ? TOptObj : TObj;
5541 } else {
5542 tcT = loosen_all(std::move(tcT));
5544 FTRACE(4, "Pre-fixup return type for {}{}{}: {}\n",
5545 func->cls ? func->cls->name->data() : "",
5546 func->cls ? "::" : "",
5547 func->name, show(tcT));
5548 fixup_return_type(func, tcT);
5549 FTRACE(3, "Initial return type for {}{}{}: {}\n",
5550 func->cls ? func->cls->name->data() : "",
5551 func->cls ? "::" : "",
5552 func->name, show(tcT));
5553 finfo->returnTy = std::move(tcT);
5556 void Index::refine_return_info(const FuncAnalysisResult& fa,
5557 DependencyContextSet& deps) {
5558 auto const& t = fa.inferredReturn;
5559 auto const func = fa.ctx.func;
5560 auto const finfo = create_func_info(*m_data, func);
5562 auto error_loc = [&] {
5563 return folly::sformat(
5564 "{} {}{}",
5565 func->unit->filename,
5566 func->cls ?
5567 folly::to<std::string>(func->cls->name->data(), "::") : std::string{},
5568 func->name
5572 auto dep = Dep{};
5573 if (finfo->retParam == NoLocalId && fa.retParam != NoLocalId) {
5574 // This is just a heuristic; it doesn't mean that the value passed
5575 // in was returned, but that the value of the parameter at the
5576 // point of the RetC was returned. We use it to make (heuristic)
5577 // decisions about whether to do inline interps, so we only allow
5578 // it to change once (otherwise later passes might not do the
5579 // inline interp, and get worse results, which could trigger other
5580 // assertions in Index::refine_*).
5581 dep = Dep::ReturnTy;
5582 finfo->retParam = fa.retParam;
5585 auto unusedParams = ~fa.usedParams;
5586 if (finfo->unusedParams != unusedParams) {
5587 dep = Dep::ReturnTy;
5588 always_assert_flog(
5589 (finfo->unusedParams | unusedParams) == unusedParams,
5590 "Index unusedParams decreased in {}.\n",
5591 error_loc()
5593 finfo->unusedParams = unusedParams;
5596 if (t.strictlyMoreRefined(finfo->returnTy)) {
5597 if (finfo->returnRefinments + 1 < options.returnTypeRefineLimit) {
5598 finfo->returnTy = t;
5599 ++finfo->returnRefinments;
5600 dep = is_scalar(t) ?
5601 Dep::ReturnTy | Dep::InlineDepthLimit : Dep::ReturnTy;
5602 } else {
5603 FTRACE(1, "maxed out return type refinements at {}\n", error_loc());
5605 } else {
5606 always_assert_flog(
5607 t.moreRefined(finfo->returnTy),
5608 "Index return type invariant violated in {}.\n"
5609 " {} is not at least as refined as {}\n",
5610 error_loc(),
5611 show(t),
5612 show(finfo->returnTy)
5616 always_assert_flog(
5617 !finfo->effectFree || fa.effectFree,
5618 "Index effectFree changed from true to false in {} {}{}.\n",
5619 func->unit->filename,
5620 func->cls ? folly::to<std::string>(func->cls->name->data(), "::") :
5621 std::string{},
5622 func->name);
5624 if (finfo->effectFree != fa.effectFree) {
5625 finfo->effectFree = fa.effectFree;
5626 dep = Dep::InlineDepthLimit | Dep::ReturnTy;
5629 if (dep != Dep{}) find_deps(*m_data, func, dep, deps);
5632 bool Index::refine_closure_use_vars(const php::Class* cls,
5633 const CompactVector<Type>& vars) {
5634 assert(is_closure(*cls));
5636 for (auto i = uint32_t{0}; i < vars.size(); ++i) {
5637 always_assert_flog(
5638 vars[i].equivalentlyRefined(unctx(vars[i])),
5639 "Closure cannot have a used var with a context dependent type"
5643 auto& current = [&] () -> CompactVector<Type>& {
5644 std::lock_guard<std::mutex> _{closure_use_vars_mutex};
5645 return m_data->closureUseVars[cls];
5646 }();
5648 always_assert(current.empty() || current.size() == vars.size());
5649 if (current.empty()) {
5650 current = vars;
5651 return true;
5654 auto changed = false;
5655 for (auto i = uint32_t{0}; i < vars.size(); ++i) {
5656 always_assert(vars[i].subtypeOf(current[i]));
5657 if (vars[i].strictSubtypeOf(current[i])) {
5658 changed = true;
5659 current[i] = vars[i];
5663 return changed;
5666 template<class Container>
5667 void refine_private_propstate(Container& cont,
5668 const php::Class* cls,
5669 const PropState& state) {
5670 assertx(!is_used_trait(*cls));
5671 auto* elm = [&] () -> typename Container::value_type* {
5672 std::lock_guard<std::mutex> _{private_propstate_mutex};
5673 auto it = cont.find(cls);
5674 if (it == end(cont)) {
5675 cont[cls] = state;
5676 return nullptr;
5678 return &*it;
5679 }();
5681 if (!elm) return;
5683 for (auto& kv : state) {
5684 auto& target = elm->second[kv.first];
5685 assertx(target.tc == kv.second.tc);
5686 always_assert_flog(
5687 kv.second.ty.moreRefined(target.ty),
5688 "PropState refinement failed on {}::${} -- {} was not a subtype of {}\n",
5689 cls->name->data(),
5690 kv.first->data(),
5691 show(kv.second.ty),
5692 show(target.ty)
5694 target.ty = kv.second.ty;
5698 void Index::refine_private_props(const php::Class* cls,
5699 const PropState& state) {
5700 refine_private_propstate(m_data->privatePropInfo, cls, state);
5703 void Index::refine_private_statics(const php::Class* cls,
5704 const PropState& state) {
5705 // We can't store context dependent types in private statics since they
5706 // could be accessed using different contexts.
5707 auto cleanedState = PropState{};
5708 for (auto const& prop : state) {
5709 auto& elem = cleanedState[prop.first];
5710 elem.ty = unctx(prop.second.ty);
5711 elem.tc = prop.second.tc;
5714 refine_private_propstate(m_data->privateStaticPropInfo, cls, cleanedState);
5717 void Index::record_public_static_mutations(const php::Func& func,
5718 PublicSPropMutations mutations) {
5719 if (!mutations.m_data) {
5720 m_data->publicSPropMutations.erase(&func);
5721 return;
5723 m_data->publicSPropMutations.insert_or_assign(&func, std::move(mutations));
5726 void Index::update_static_prop_init_val(const php::Class* cls,
5727 SString name) const {
5728 for (auto& info : find_range(m_data->classInfo, cls->name)) {
5729 auto const cinfo = info.second;
5730 if (cinfo->cls != cls) continue;
5731 auto const it = cinfo->publicStaticProps.find(name);
5732 if (it != cinfo->publicStaticProps.end()) {
5733 it->second.initialValueResolved = true;
5738 void Index::refine_public_statics(DependencyContextSet& deps) {
5739 trace_time update("update public statics");
5741 // Union together the mutations for each function, including the functions
5742 // which weren't analyzed this round.
5743 auto nothing_known = false;
5744 PublicSPropMutations::UnknownMap unknown;
5745 PublicSPropMutations::KnownMap known;
5746 for (auto const& mutations : m_data->publicSPropMutations) {
5747 if (!mutations.second.m_data) continue;
5748 if (mutations.second.m_data->m_nothing_known) {
5749 nothing_known = true;
5750 break;
5753 for (auto const& kv : mutations.second.m_data->m_unknown) {
5754 auto const ret = unknown.insert(kv);
5755 if (!ret.second) ret.first->second |= kv.second;
5757 for (auto const& kv : mutations.second.m_data->m_known) {
5758 auto const ret = known.insert(kv);
5759 if (!ret.second) ret.first->second |= kv.second;
5763 if (nothing_known) {
5764 // We cannot go from knowing the types to not knowing the types (this is
5765 // equivalent to widening the types).
5766 always_assert(m_data->allPublicSPropsUnknown);
5767 return;
5770 auto const firstRefinement = m_data->allPublicSPropsUnknown;
5771 m_data->allPublicSPropsUnknown = false;
5773 if (firstRefinement) {
5774 // If this is the first refinement, reschedule any dependency which looked
5775 // at the public static property state previously.
5776 always_assert(m_data->unknownClassSProps.empty());
5777 for (auto const& dependency : m_data->dependencyMap) {
5778 if (dependency.first.tag() != DependencyContextType::PropName) continue;
5779 for (auto const& kv : dependency.second) {
5780 if (has_dep(kv.second, Dep::PublicSPropName)) deps.insert(kv.first);
5785 // Refine unknown class state
5786 for (auto const& kv : unknown) {
5787 // We can't keep context dependent types in public properties.
5788 auto newType = unctx(kv.second);
5789 auto it = m_data->unknownClassSProps.find(kv.first);
5790 if (it == end(m_data->unknownClassSProps)) {
5791 // If this is the first refinement, our previous state was effectively
5792 // TGen for everything, so inserting a type into the map can only
5793 // refine. However, if this isn't the first refinement, a name not present
5794 // in the map means that its TBottom, so we shouldn't be inserting
5795 // anything.
5796 always_assert(firstRefinement);
5797 m_data->unknownClassSProps.emplace(
5798 kv.first,
5799 std::make_pair(std::move(newType), 0)
5801 continue;
5805 * We may only shrink the types we recorded for each property. (If a
5806 * property type ever grows, the interpreter could infer something
5807 * incorrect at some step.)
5809 always_assert(!firstRefinement);
5810 always_assert_flog(
5811 newType.subtypeOf(it->second.first),
5812 "Static property index invariant violated for name {}:\n"
5813 " {} was not a subtype of {}",
5814 kv.first->data(),
5815 show(newType),
5816 show(it->second.first)
5819 // Put a limit on the refinements to ensure termination. Since we only ever
5820 // refine types, we can stop at any point and maintain correctness.
5821 if (it->second.second + 1 < options.publicSPropRefineLimit) {
5822 if (newType.strictSubtypeOf(it->second.first)) {
5823 find_deps(*m_data, it->first, Dep::PublicSPropName, deps);
5825 it->second.first = std::move(newType);
5826 ++it->second.second;
5827 } else {
5828 FTRACE(
5829 1, "maxed out public static property refinements for name {}\n",
5830 kv.first->data()
5835 // If we didn't see a mutation among all the functions for a particular name,
5836 // it means the type is TBottom. Iterate through the unknown class state and
5837 // remove any entries which we didn't see a mutation for.
5838 if (!firstRefinement) {
5839 auto it = begin(m_data->unknownClassSProps);
5840 auto last = end(m_data->unknownClassSProps);
5841 while (it != last) {
5842 auto const unknownIt = unknown.find(it->first);
5843 if (unknownIt == end(unknown)) {
5844 if (unknownIt->second != TBottom) {
5845 find_deps(*m_data, unknownIt->first, Dep::PublicSPropName, deps);
5847 it = m_data->unknownClassSProps.erase(it);
5848 } else {
5849 ++it;
5854 // Refine known class state
5855 for (auto const& cinfo : m_data->allClassInfos) {
5856 for (auto& kv : cinfo->publicStaticProps) {
5857 auto const newType = [&] {
5858 auto const it = known.find(
5859 PublicSPropMutations::KnownKey { cinfo.get(), kv.first }
5861 // If we didn't see a mutation, the type is TBottom.
5862 if (it == end(known)) return TBottom;
5863 // We can't keep context dependent types in public properties.
5864 return adjust_type_for_prop(
5865 *this, *cinfo->cls, kv.second.tc, unctx(it->second)
5867 }();
5869 if (kv.second.initialValueResolved) {
5870 for (auto& prop : cinfo->cls->properties) {
5871 if (prop.name != kv.first) continue;
5872 kv.second.initializerType = from_cell(prop.val);
5873 kv.second.initialValueResolved = false;
5874 break;
5876 assertx(!kv.second.initialValueResolved);
5879 // The type from the indexer doesn't contain the in-class initializer
5880 // types. Add that here.
5881 auto effectiveType = union_of(newType, kv.second.initializerType);
5884 * We may only shrink the types we recorded for each property. (If a
5885 * property type ever grows, the interpreter could infer something
5886 * incorrect at some step.)
5888 always_assert_flog(
5889 effectiveType.subtypeOf(kv.second.inferredType),
5890 "Static property index invariant violated on {}::{}:\n"
5891 " {} is not a subtype of {}",
5892 cinfo->cls->name->data(),
5893 kv.first->data(),
5894 show(effectiveType),
5895 show(kv.second.inferredType)
5897 always_assert(newType == TBottom || kv.second.everModified);
5899 // Put a limit on the refinements to ensure termination. Since we only
5900 // ever refine types, we can stop at any point and still maintain
5901 // correctness.
5902 if (kv.second.refinements + 1 < options.publicSPropRefineLimit) {
5903 if (effectiveType.strictSubtypeOf(kv.second.inferredType)) {
5904 find_deps(*m_data, kv.first, Dep::PublicSPropName, deps);
5906 kv.second.inferredType = std::move(effectiveType);
5907 kv.second.everModified = newType != TBottom;
5908 ++kv.second.refinements;
5909 } else {
5910 FTRACE(
5911 1, "maxed out public static property refinements for {}:{}\n",
5912 cinfo->cls->name->data(),
5913 kv.first->data()
5920 void Index::refine_bad_initial_prop_values(const php::Class* cls,
5921 bool value,
5922 DependencyContextSet& deps) {
5923 assertx(!is_used_trait(*cls));
5925 for (auto& info : find_range(m_data->classInfo, cls->name)) {
5926 auto const cinfo = info.second;
5927 if (cinfo->cls != cls) continue;
5928 always_assert_flog(
5929 cinfo->hasBadInitialPropValues || !value,
5930 "Bad initial prop values going from false to true on {}",
5931 cls->name->data()
5934 if (cinfo->hasBadInitialPropValues && !value) {
5935 cinfo->hasBadInitialPropValues = false;
5936 find_deps(*m_data, cls, Dep::PropBadInitialValues, deps);
5941 bool Index::frozen() const {
5942 return m_data->frozen;
5945 void Index::freeze() {
5946 m_data->frozen = true;
5947 m_data->ever_frozen = true;
5951 * Note that these functions run in separate threads, and
5952 * intentionally don't bump Trace::hhbbc_time. If you want to see
5953 * these times, set TRACE=hhbbc_time:1
5955 #define CLEAR(x) \
5957 trace_time _{"Clearing " #x}; \
5958 (x).clear(); \
5961 void Index::cleanup_for_final() {
5962 trace_time _{"cleanup_for_final"};
5963 CLEAR(m_data->dependencyMap);
5967 void Index::cleanup_post_emit() {
5968 trace_time _{"cleanup_post_emit"};
5970 trace_time t{"Reset allClassInfos"};
5971 parallel::for_each(m_data->allClassInfos, [] (auto& u) { u.reset(); });
5973 std::vector<std::function<void()>> clearers;
5974 #define CLEAR_PARALLEL(x) clearers.push_back([&] CLEAR(x));
5975 CLEAR_PARALLEL(m_data->classes);
5976 CLEAR_PARALLEL(m_data->methods);
5977 CLEAR_PARALLEL(m_data->method_ref_params_by_name);
5978 CLEAR_PARALLEL(m_data->funcs);
5979 CLEAR_PARALLEL(m_data->typeAliases);
5980 CLEAR_PARALLEL(m_data->enums);
5981 CLEAR_PARALLEL(m_data->constants);
5982 CLEAR_PARALLEL(m_data->records);
5983 CLEAR_PARALLEL(m_data->classAliases);
5985 CLEAR_PARALLEL(m_data->classClosureMap);
5986 CLEAR_PARALLEL(m_data->classExtraMethodMap);
5988 CLEAR_PARALLEL(m_data->allClassInfos);
5989 CLEAR_PARALLEL(m_data->classInfo);
5990 CLEAR_PARALLEL(m_data->funcInfo);
5992 CLEAR_PARALLEL(m_data->privatePropInfo);
5993 CLEAR_PARALLEL(m_data->privateStaticPropInfo);
5994 CLEAR_PARALLEL(m_data->unknownClassSProps);
5995 CLEAR_PARALLEL(m_data->publicSPropMutations);
5996 CLEAR_PARALLEL(m_data->ifaceSlotMap);
5997 CLEAR_PARALLEL(m_data->closureUseVars);
5999 CLEAR_PARALLEL(m_data->foldableReturnTypeMap);
6000 CLEAR_PARALLEL(m_data->contextualReturnTypes);
6002 parallel::for_each(clearers, [] (const std::function<void()>& f) { f(); });
6005 void Index::thaw() {
6006 m_data->frozen = false;
6009 std::unique_ptr<ArrayTypeTable::Builder>& Index::array_table_builder() const {
6010 return m_data->arrTableBuilder;
6013 //////////////////////////////////////////////////////////////////////
6015 res::Func Index::do_resolve(const php::Func* f) const {
6016 auto const finfo = create_func_info(*m_data, f);
6017 return res::Func { this, finfo };
6020 // Return true if we know for sure that one php::Class must derive
6021 // from another at runtime, in all possible instantiations.
6022 bool Index::must_be_derived_from(const php::Class* cls,
6023 const php::Class* parent) const {
6024 if (cls == parent) return true;
6025 auto const clsClasses = find_range(m_data->classInfo, cls->name);
6026 auto const parentClasses = find_range(m_data->classInfo, parent->name);
6027 for (auto& kvCls : clsClasses) {
6028 auto const rCls = res::Class { this, kvCls.second };
6029 for (auto& kvPar : parentClasses) {
6030 auto const rPar = res::Class { this, kvPar.second };
6031 if (!rCls.mustBeSubtypeOf(rPar)) return false;
6034 return true;
6037 // Return true if any possible definition of one php::Class could
6038 // derive from another at runtime, or vice versa.
6039 bool
6040 Index::could_be_related(const php::Class* cls,
6041 const php::Class* parent) const {
6042 if (cls == parent) return true;
6043 auto const clsClasses = find_range(m_data->classInfo, cls->name);
6044 auto const parentClasses = find_range(m_data->classInfo, parent->name);
6045 for (auto& kvCls : clsClasses) {
6046 auto const rCls = res::Class { this, kvCls.second };
6047 for (auto& kvPar : parentClasses) {
6048 auto const rPar = res::Class { this, kvPar.second };
6049 if (rCls.couldBe(rPar)) return true;
6052 return false;
6055 //////////////////////////////////////////////////////////////////////
6057 void PublicSPropMutations::merge(const Index& index,
6058 Context ctx,
6059 const Type& tcls,
6060 const Type& name,
6061 const Type& val) {
6062 // Figure out which class this can affect. If we have a DCls::Sub we have to
6063 // assume it could affect any subclass, so we repeat this merge for all exact
6064 // class types deriving from that base.
6065 if (is_specialized_cls(tcls)) {
6066 auto const dcls = dcls_of(tcls);
6067 if (auto const cinfo = dcls.cls.val.right()) {
6068 switch (dcls.type) {
6069 case DCls::Exact:
6070 return merge(index, ctx, cinfo, name, val);
6071 case DCls::Sub:
6072 for (auto const sub : cinfo->subclassList) {
6073 merge(index, ctx, sub, name, val);
6075 return;
6077 not_reached();
6081 merge(index, ctx, nullptr, name, val);
6084 void PublicSPropMutations::merge(const Index& index,
6085 Context ctx,
6086 ClassInfo* cinfo,
6087 const Type& name,
6088 const Type& val) {
6089 FTRACE(2, "merge_public_static: {} {} {}\n",
6090 cinfo ? cinfo->cls->name->data() : "<unknown>", show(name), show(val));
6092 auto get = [this] () -> Data& {
6093 if (!m_data) m_data = std::make_unique<Data>();
6094 return *m_data;
6097 auto const vname = tv(name);
6098 auto const unknownName = !vname ||
6099 (vname && vname->m_type != KindOfPersistentString);
6101 if (!cinfo) {
6102 if (unknownName) {
6104 * We have a case here where we know neither the class nor the static
6105 * property name. This means we have to pessimize public static property
6106 * types for the entire program.
6108 * We could limit it to pessimizing them by merging the `val' type, but
6109 * instead we just throw everything away---this optimization is not
6110 * expected to be particularly useful on programs that contain any
6111 * instances of this situation.
6113 std::fprintf(
6114 stderr,
6115 "NOTE: had to mark everything unknown for public static "
6116 "property types due to dynamic code. -fanalyze-public-statics "
6117 "will not help for this program.\n"
6118 "NOTE: The offending code occured in this context: %s\n",
6119 show(ctx).c_str()
6121 get().m_nothing_known = true;
6122 return;
6125 auto const res = get().m_unknown.emplace(vname->m_data.pstr, val);
6126 if (!res.second) res.first->second |= val;
6127 return;
6131 * We don't know the name, but we know something about the class. We need to
6132 * merge the type for every property in the class hierarchy.
6134 if (unknownName) {
6135 visit_parent_cinfo(cinfo,
6136 [&] (const ClassInfo* ci) {
6137 for (auto& kv : ci->publicStaticProps) {
6138 merge(index, ctx, cinfo, sval(kv.first), val);
6140 return false;
6142 return;
6146 * Here we know both the ClassInfo and the static property name, but it may
6147 * not actually be on this ClassInfo. In php, you can access base class
6148 * static properties through derived class names, and the access affects the
6149 * property with that name on the most-recently-inherited-from base class.
6151 * If the property is not found as a public property anywhere in the
6152 * hierarchy, we don't want to merge this type. Note we don't have to worry
6153 * about the case that there is a protected property in between, because this
6154 * is a fatal at class declaration time (you can't redeclare a public static
6155 * property with narrower access in a subclass).
6157 auto const affectedInfo = (
6158 visit_parent_cinfo(
6159 cinfo,
6160 [&] (const ClassInfo* ci) ->
6161 folly::Optional<std::pair<ClassInfo*, const TypeConstraint*>> {
6162 auto const it = ci->publicStaticProps.find(vname->m_data.pstr);
6163 if (it != end(ci->publicStaticProps)) {
6164 return std::make_pair(
6165 const_cast<ClassInfo*>(ci),
6166 it->second.tc
6169 return folly::none;
6174 if (!affectedInfo) {
6175 // Either this was a mutation that's going to fatal (property doesn't
6176 // exist), or it's a private static or a protected static. We aren't in
6177 // that business here, so we don't need to record anything.
6178 return;
6181 auto const affectedCInfo = affectedInfo->first;
6182 auto const affectedTC = affectedInfo->second;
6184 auto const adjusted =
6185 adjust_type_for_prop(index, *affectedCInfo->cls, affectedTC, val);
6187 // Merge the property type.
6188 auto const res = get().m_known.emplace(
6189 KnownKey { affectedCInfo, vname->m_data.pstr },
6190 adjusted
6192 if (!res.second) res.first->second |= adjusted;
6195 void PublicSPropMutations::merge(const Index& index,
6196 Context ctx,
6197 const php::Class& cls,
6198 const Type& name,
6199 const Type& val) {
6200 auto range = find_range(index.m_data->classInfo, cls.name);
6201 for (auto const& pair : range) {
6202 auto const cinfo = pair.second;
6203 if (cinfo->cls != &cls) continue;
6204 // Note that this works for both traits and regular classes
6205 for (auto const sub : cinfo->subclassList) {
6206 merge(index, ctx, sub, name, val);
6211 //////////////////////////////////////////////////////////////////////