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