2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-2014 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/interp.h"
23 #include <folly/Optional.h>
24 #include <folly/Format.h>
26 #include "hphp/util/trace.h"
28 #include "hphp/hhbbc/interp-internal.h"
29 #include "hphp/hhbbc/type-ops.h"
31 namespace HPHP
{ namespace HHBBC
{
35 //////////////////////////////////////////////////////////////////////
37 const StaticString
s_stdClass("stdClass");
39 //////////////////////////////////////////////////////////////////////
42 * Note: the couldBe comparisons here with sempty() are asking "can this string
43 * be a non-reference counted empty string". What actually matters is whether
44 * it can be an empty string at all. Currently, all reference counted strings
45 * are TStr, which has no values and may also be non-reference
46 * counted---emptiness isn't separately tracked like it is for arrays, so if
47 * anything happened that could make it reference counted this check will
50 * This means this code is fine for now, but if we implement #3837503
51 * (non-static strings with values in the type system) it will need to change.
54 bool couldBeEmptyish(Type ty
) {
55 return ty
.couldBe(TNull
) ||
56 ty
.couldBe(sempty()) ||
60 bool mustBeEmptyish(Type ty
) {
61 return ty
.subtypeOf(TNull
) ||
62 ty
.subtypeOf(sempty()) ||
66 bool elemCouldPromoteToArr(Type ty
) { return couldBeEmptyish(ty
); }
67 bool propCouldPromoteToObj(Type ty
) { return couldBeEmptyish(ty
); }
68 bool elemMustPromoteToArr(Type ty
) { return mustBeEmptyish(ty
); }
69 bool propMustPromoteToObj(Type ty
) { return mustBeEmptyish(ty
); }
71 //////////////////////////////////////////////////////////////////////
74 * Tag indicating what sort of thing contains the current base.
76 * The base is always the unboxed version of the type, and its
77 * location could be inside of a Ref. So, for example, a base with
78 * BaseLoc::Frame could be located inside of a Ref that is pointed
79 * to by the Frame. (We may want to distinguish these two cases at
80 * some point if we start trying to track real information about
85 * Base is in a number of possible places after an Elem op. It
86 * cannot possibly be in an object property (although it certainly
87 * may alias one). See miElem for details. Not all post-elem ops
88 * use this location (see LocalArrChain).
90 * If it is definitely in an array, the locTy in the Base will be
96 * Base is in possible locations after a Prop op. This means it
97 * possibly lives in a property on an object, but possibly not
98 * (e.g. it could be a null in tvScratch). See miProp for
101 * If it is definitely known to be a property in an object, the
102 * locTy in the Base will be a subtype of TObj.
107 * Known to be a static property on an object. This is only
108 * possible as an initial base.
113 * The base is inside of a local that contains a specialized array
114 * type, and the arrayChain is non-empty.
116 * When the location is set to this, the chain will continue as
117 * long as we keep staying inside specialized array types. If it
118 * moves to something like a ?Arr type, we must leave the chain
119 * when the base moves.
124 * Known to be contained in the current frame as a local, as the
125 * frame $this, by the evaluation stack, or inside $GLOBALS. Only
126 * possible as initial bases.
134 * If we've execute an operation that's known to fatal, we use
145 * We also need to track effects of intermediate dims on the type
146 * of the base. So we have a type, name, and possibly associated
147 * local for the base's container.
149 * For StaticObjProp, locName this is the name of the property if
150 * known, or nullptr, and locTy is the type of the class
151 * containing the static property.
153 * Similarly, if loc is PostProp, locName is the name of the
154 * property if it was known, and locTy gives as much information
155 * about the object type it is in. (If we actually *know* it is
156 * in an object, locTy will be a subtype of TObj.)
160 borrowed_ptr
<php::Local
> local
;
163 std::string
base_string(const Base
& b
) {
164 auto const locStr
= [&]() -> const char* {
166 case BaseLoc::PostElem
: return "PostElem";
167 case BaseLoc::PostProp
: return "PostProp";
168 case BaseLoc::StaticObjProp
: return "StaticObjProp";
169 case BaseLoc::LocalArrChain
: return "ArrChain";
170 case BaseLoc::Frame
: return "Frame";
171 case BaseLoc::FrameThis
: return "FrameThis";
172 case BaseLoc::EvalStack
: return "EvalStack";
173 case BaseLoc::Global
: return "Global";
174 case BaseLoc::Fataled
: return "Fataled";
178 return folly::format(
179 "{: <32} ({: <14} {: <8} @ {})",
183 b
.locName
? b
.locName
->data() : "?"
187 Type
baseLocNameType(const Base
& b
) {
188 return b
.locName
? sval(b
.locName
) : TInitGen
;
192 explicit MIS(ISS
& env
,
193 const MInstrInfo
* info
,
198 , stackIdx(info
->valCount() + numVecPops(mvec
))
202 * Return the current MElem. Only valid after the first base has
205 const MElem
& melem() const {
206 assert(mInd
< mvec
.mcodes
.size());
207 return mvec
.mcodes
[mInd
];
211 * Return the current MemberCode. Only valid after the first base
212 * has been processed.
214 MemberCode
mcode() const { return melem().mcode
; }
216 const MInstrInfo
& info
;
220 * The current base. Updated as we move through the vector
226 * Chains of operations on array elements will all affect the type
227 * of something further back in the member instruction. Currently
228 * this is just used for locals. This vector tracks the base,key
229 * type pair that was used at each stage. See resolveArrayChain.
231 std::vector
<std::pair
<Type
,Type
>> arrayChain
;
234 * Current index in mcodes vector (or 0 if we still haven't done
240 * One above next stack slot to read, going forward in the mvec
241 * (deeper to higher on stack).
246 //////////////////////////////////////////////////////////////////////
249 * A note about bases.
251 * Generally type inference needs to know two kinds of things about
252 * the base to handle effects on tracked locations:
254 * - Could the base be a location we're tracking deeper structure
255 * on, so the next operation actually affects something inside
256 * of it. For example, could the base be an object with the
257 * same type as $this, or an array in a local variable.
259 * - Could the base be something (regardless of type) that is
260 * inside one of the things we're tracking. I.e., the base
261 * might be whatever (an array or a bool or something), but
262 * living inside a property inside an object with the same type
263 * as $this, or living inside of an array in the local frame.
265 * The first cases apply because final operations are going to
266 * directly affect the type of these elements. The second case is
267 * because vector operations may change the base at each step if it
268 * is a defining instruction.
270 * Note that both of these cases can apply to the same base in some
271 * cases: you might have an object property on $this that could be
272 * an object of the type of $this.
274 * The functions below with names "couldBeIn*" detect the second
275 * case. The effects on the tracked location in the second case are
276 * handled in the functions with names "handleIn*{Prop,Elem,..}".
277 * The effects for the first case are generally handled in the
278 * miFinal op functions.
280 * Control flow insensitive vs. control flow sensitive types:
282 * Things are also slightly complicated by the fact that we are
283 * analyzing some control flow insensitve types along side precisely
284 * tracked types. For effects on locals, we perform the type
285 * effects of each operation on base.type, and then allow moveBase()
286 * to make the updates to the local when we know what its final type
289 * This approach doesn't do as well for possible properties in $this
290 * or self::, because we may see situations where the base could be
291 * one of these properties but we're not sure---perhaps because it
292 * came off a property with the same name on an object with an
293 * unknown type (i.e. base.type is InitCell but couldBeInThis is
294 * true). In these situations, we can get away with just merging
295 * Obj=stdClass into the thisProp (because it 'could' promote)
296 * instead of merging the whole InitCell, which possibly lets us
297 * leave the type at ?Obj in some cases.
299 * This is why there's two fairly different mechanisms for handling
300 * the effects of defining ops on base types.
303 //////////////////////////////////////////////////////////////////////
305 bool couldBeThisObj(ISS
& env
, const Base
& b
) {
306 if (b
.loc
== BaseLoc::Fataled
) return false;
307 auto const thisTy
= thisType(env
);
308 return b
.type
.couldBe(thisTy
? *thisTy
: TObj
);
311 bool mustBeThisObj(ISS
& env
, const Base
& b
) {
312 if (b
.loc
== BaseLoc::FrameThis
) return true;
313 if (auto const ty
= thisType(env
)) return b
.type
.subtypeOf(*ty
);
317 bool mustBeInFrame(const Base
& b
) {
318 return b
.loc
== BaseLoc::Frame
;
321 bool couldBeInThis(ISS
& env
, const Base
& b
) {
322 if (b
.loc
!= BaseLoc::PostProp
) return false;
323 auto const thisTy
= thisType(env
);
324 if (!thisTy
) return true;
325 if (!b
.locTy
.couldBe(*thisTy
)) return false;
327 return isTrackedThisProp(env
, b
.locName
);
332 bool couldBeInSelf(ISS
& env
, const Base
& b
) {
333 if (b
.loc
!= BaseLoc::StaticObjProp
) return false;
334 auto const selfTy
= selfCls(env
);
335 return !selfTy
|| b
.locTy
.couldBe(*selfTy
);
338 bool couldBeInPublicStatic(ISS
& env
, const Base
& b
) {
339 return b
.loc
== BaseLoc::StaticObjProp
;
342 //////////////////////////////////////////////////////////////////////
344 void handleInThisPropD(MIS
& env
) {
345 // NullSafe (Q) props do not promote an emptyish base to stdClass instance.
346 if (env
.mvec
.mcodes
[env
.mInd
].mcode
== MQT
) {
349 if (!couldBeInThis(env
, env
.base
)) return;
351 if (auto const name
= env
.base
.locName
) {
352 auto const ty
= thisPropAsCell(env
, name
);
353 if (ty
&& propCouldPromoteToObj(*ty
)) {
354 mergeThisProp(env
, name
,
355 objExact(env
.index
.builtin_class(s_stdClass
.get())));
360 mergeEachThisPropRaw(env
, [&] (Type t
) {
361 return propCouldPromoteToObj(t
) ? TObj
: TBottom
;
365 void handleInSelfPropD(MIS
& env
) {
366 // NullSafe (Q) props do not promote an emptyish base to stdClass instance.
367 if (env
.mvec
.mcodes
[env
.mInd
].mcode
== MQT
) {
371 if (!couldBeInSelf(env
, env
.base
)) return;
373 if (auto const name
= env
.base
.locName
) {
374 auto const ty
= selfPropAsCell(env
, name
);
375 if (ty
&& propCouldPromoteToObj(*ty
)) {
376 mergeSelfProp(env
, name
,
377 objExact(env
.index
.builtin_class(s_stdClass
.get())));
382 loseNonRefSelfPropTypes(env
);
385 void handleInPublicStaticPropD(MIS
& env
) {
386 // NullSafe (Q) props do not promote an emptyish base to stdClass instance.
387 if (env
.mvec
.mcodes
[env
.mInd
].mcode
== MQT
) {
391 if (!couldBeInPublicStatic(env
, env
.base
)) return;
393 auto const indexer
= env
.collect
.publicStatics
;
394 if (!indexer
) return;
396 auto const name
= baseLocNameType(env
.base
);
397 auto const ty
= env
.index
.lookup_public_static(env
.base
.locTy
, name
);
398 if (propCouldPromoteToObj(ty
)) {
399 indexer
->merge(env
.ctx
, env
.base
.locTy
, name
,
400 objExact(env
.index
.builtin_class(s_stdClass
.get())));
404 void handleInThisElemD(MIS
& env
) {
405 if (!couldBeInThis(env
, env
.base
)) return;
407 if (auto const name
= env
.base
.locName
) {
408 auto const ty
= thisPropAsCell(env
, name
);
409 if (ty
&& elemCouldPromoteToArr(*ty
)) {
410 mergeThisProp(env
, name
, TArr
);
415 mergeEachThisPropRaw(env
, [&] (Type t
) {
416 return elemCouldPromoteToArr(t
) ? TArr
: TBottom
;
420 void handleInSelfElemD(MIS
& env
) {
421 if (!couldBeInSelf(env
, env
.base
)) return;
423 if (auto const name
= env
.base
.locName
) {
424 if (auto const ty
= selfPropAsCell(env
, name
)) {
425 if (elemCouldPromoteToArr(*ty
)) {
426 mergeSelfProp(env
, name
, TArr
);
428 mergeSelfProp(env
, name
, loosen_statics(*ty
));
432 loseNonRefSelfPropTypes(env
);
435 void handleInPublicStaticElemD(MIS
& env
) {
436 if (!couldBeInPublicStatic(env
, env
.base
)) return;
438 auto const indexer
= env
.collect
.publicStatics
;
439 if (!indexer
) return;
441 auto const name
= baseLocNameType(env
.base
);
442 auto const ty
= env
.index
.lookup_public_static(env
.base
.locTy
, name
);
443 if (elemCouldPromoteToArr(ty
)) {
444 // Might be possible to only merge a TArrE, but for now this is ok.
445 indexer
->merge(env
.ctx
, env
.base
.locTy
, name
, TArr
);
449 // Currently NewElem and Elem InFoo effects don't need to do
450 // anything different from each other.
451 void handleInThisNewElem(MIS
& env
) { handleInThisElemD(env
); }
452 void handleInSelfNewElem(MIS
& env
) { handleInSelfElemD(env
); }
453 void handleInPublicStaticNewElem(MIS
& env
) { handleInPublicStaticElemD(env
); }
455 void handleInSelfElemU(MIS
& env
) {
456 if (!couldBeInSelf(env
, env
.base
)) return;
458 if (auto const name
= env
.base
.locName
) {
459 auto const ty
= selfPropAsCell(env
, name
);
460 if (ty
) mergeSelfProp(env
, name
, loosen_statics(*ty
));
462 mergeEachSelfPropRaw(env
, loosen_statics
);
466 void handleInPublicStaticElemU(MIS
& env
) {
467 if (!couldBeInPublicStatic(env
, env
.base
)) return;
469 auto const indexer
= env
.collect
.publicStatics
;
470 if (!indexer
) return;
473 * We need to ensure that the type could become non-static, but since we're
474 * never going to see anything specialized from lookup_public_static the
475 * first time we're running with collect.publicStatics, we can't do much
476 * right now since we don't have a type for the union of all counted types.
478 * Merging InitCell is correct, but very conservative, for now.
480 auto const name
= baseLocNameType(env
.base
);
481 indexer
->merge(env
.ctx
, env
.base
.locTy
, name
, TInitCell
);
484 //////////////////////////////////////////////////////////////////////
486 // MInstrs can throw in between each op, so the states of locals
487 // need to be propagated across factored exit edges.
488 void miThrow(MIS
& env
) {
489 for (auto& factored
: env
.blk
.factoredExits
) {
490 env
.propagate(*factored
, without_stacks(env
.state
));
494 //////////////////////////////////////////////////////////////////////
496 void setLocalForBase(MIS
& env
, Type ty
) {
497 assert(mustBeInFrame(env
.base
) ||
498 env
.base
.loc
== BaseLoc::LocalArrChain
);
499 if (!env
.base
.local
) return loseNonRefLocalTypes(env
);
500 setLoc(env
, env
.base
.local
, ty
);
501 FTRACE(4, " ${} := {}\n",
502 env
.base
.locName
? env
.base
.locName
->data() : "$<unnamed>",
507 // Run backwards through an array chain doing array_set operations
508 // to produce the array type that incorporates the effects of any
509 // intermediate defining dims.
510 Type
currentChainType(MIS
& env
, Type val
) {
511 auto it
= env
.arrayChain
.rbegin();
512 for (; it
!= env
.arrayChain
.rend(); ++it
) {
513 val
= array_set(it
->first
, it
->second
, val
);
518 Type
resolveArrayChain(MIS
& env
, Type val
) {
519 static UNUSED
const char prefix
[] = " ";
520 FTRACE(5, "{}chain\n", prefix
, show(val
));
522 auto arr
= std::move(env
.arrayChain
.back().first
);
523 auto key
= std::move(env
.arrayChain
.back().second
);
524 assert(arr
.subtypeOf(TArr
));
525 env
.arrayChain
.pop_back();
526 FTRACE(5, "{} | {} := {} in {}\n", prefix
,
527 show(key
), show(val
), show(arr
));
528 val
= array_set(std::move(arr
), key
, val
);
529 } while (!env
.arrayChain
.empty());
530 FTRACE(5, "{} = {}\n", prefix
, show(val
));
534 void moveBase(MIS
& env
, folly::Optional
<Base
> base
) {
535 SCOPE_EXIT
{ if (base
) env
.base
= *base
; };
537 // Note: these miThrows probably can be left out if base is
538 // folly::none (i.e. we're on the last dim).
540 auto const& ty
= env
.base
.type
;
541 if (mustBeInFrame(env
.base
)) {
542 setLocalForBase(env
, ty
);
547 if (!env
.arrayChain
.empty()) {
548 auto const continueChain
= base
&& base
->loc
== BaseLoc::LocalArrChain
;
549 if (!continueChain
) {
550 setLocalForBase(env
, resolveArrayChain(env
, env
.base
.type
));
555 * We have a chain in progress, but it's not done. But we still
556 * need to throw any type effects on the local across factored
559 setLocalForBase(env
, currentChainType(env
, env
.base
.type
));
564 //////////////////////////////////////////////////////////////////////
567 * The following handleBase{Elem,Prop}* functions are used to implement the
568 * 'normal' portion of the effects on base types, which are mostly what are
569 * done by intermediate dims. (Contrast with the handleInXXX{Elem,Prop}
570 * functions, which handle the effects on the type of the thing that's
571 * /containing/ the base.)
573 * The contract with these functions is that they should handle all the effects
574 * on the base type /except/ for the case of the base being a subtype of
575 * TArr---the caller is responsible for that. The reason for this is that for
576 * tracking effects on specialized array types (e.g. LocalArrChain), the final
577 * ops generally need to do completely different things to the array, so this
578 * allows reuse of this shared part of the type transitions. The
579 * miIntermediate routines must handle subtypes of TArr outside of calls to
583 void handleBaseElemU(MIS
& env
) {
584 auto& ty
= env
.base
.type
;
585 if (ty
.couldBe(TArr
)) {
586 // We're conservative with unsets on array types for now.
587 ty
= union_of(ty
, TArr
);
589 if (ty
.couldBe(TSStr
)) {
590 ty
= loosen_statics(env
.base
.type
);
594 void handleBasePropD(MIS
& env
) {
595 // NullSafe (Q) props do not promote an emptyish base to stdClass instance.
596 if (env
.mvec
.mcodes
[env
.mInd
].mcode
== MQT
) {
599 auto& ty
= env
.base
.type
;
600 if (ty
.subtypeOf(TObj
)) return;
601 if (propMustPromoteToObj(ty
)) {
602 ty
= objExact(env
.index
.builtin_class(s_stdClass
.get()));
605 if (propCouldPromoteToObj(ty
)) {
606 ty
= promote_emptyish(ty
, TObj
);
611 void handleBaseElemD(MIS
& env
) {
612 auto& ty
= env
.base
.type
;
614 // When the base is actually a subtype of array, we handle it in the callers
615 // of these functions.
616 if (ty
.subtypeOf(TArr
)) return;
618 if (elemMustPromoteToArr(ty
)) {
619 ty
= counted_aempty();
622 // Intermediate ElemD operations on strings fatal, unless the
623 // string is empty, which promotes to array. So for any string
624 // here we can assume it promoted to an empty array.
625 if (ty
.subtypeOf(TStr
)) {
626 ty
= counted_aempty();
629 if (elemCouldPromoteToArr(ty
)) {
630 ty
= promote_emptyish(ty
, counted_aempty());
634 * If the base still couldBe some kind of array (but isn't a subtype of TArr,
635 * which would be handled outside this routine), we need to give up on any
636 * information better than TArr here (or track the effects, but we're not
639 if (ty
.couldBe(TArr
)) {
640 ty
= union_of(ty
, TArr
);
644 void handleBaseNewElem(MIS
& env
) {
645 handleBaseElemD(env
);
646 // Technically we don't need to do TStr case.
649 //////////////////////////////////////////////////////////////////////
651 Type
mcodeKey(MIS
& env
) {
652 auto const melem
= env
.mvec
.mcodes
[env
.mInd
];
653 switch (melem
.mcode
) {
654 case MPC
: return topC(env
, --env
.stackIdx
);
655 case MPL
: return locAsCell(env
, melem
.immLoc
);
656 case MPT
: return sval(melem
.immStr
);
657 case MQT
: return sval(melem
.immStr
);
658 case MEC
: return topC(env
, --env
.stackIdx
);
659 case MET
: return sval(melem
.immStr
);
660 case MEL
: return locAsCell(env
, melem
.immLoc
);
661 case MEI
: return ival(melem
.immInt
);
664 case InvalidMemberCode
:
671 // Returns nullptr if it's an unknown key or not a string.
672 SString
mcodeStringKey(MIS
& env
) {
673 auto const v
= tv(mcodeKey(env
));
674 return v
&& v
->m_type
== KindOfStaticString
? v
->m_data
.pstr
: nullptr;
677 void miPop(MIS
& env
) {
678 auto& mvec
= env
.mvec
;
679 if (mvec
.lcode
== LSL
|| mvec
.lcode
== LSC
) {
680 assert(env
.stackIdx
== env
.info
.valCount() + 1 /* clsref */);
682 assert(env
.stackIdx
== env
.info
.valCount());
684 for (auto i
= uint32_t{0}; i
< numVecPops(mvec
); ++i
) popT(env
);
687 //////////////////////////////////////////////////////////////////////
690 Base
miBaseLoc(MIS
& env
) {
691 bool const isDefine
= env
.info
.getAttr(env
.mvec
.lcode
) & MIA_define
;
692 auto const locBase
= env
.mvec
.locBase
;
695 return Base
{ derefLoc(env
, locBase
),
702 // We're changing the local to define it, but we don't need to do
703 // an miThrow yet---the promotions (to array or stdClass) on
704 // previously uninitialized locals happen before raising warnings
705 // that could throw, so we can wait until the first moveBase.
706 ensureInit(env
, locBase
);
707 return Base
{ locAsCell(env
, locBase
),
714 Base
miBaseSProp(MIS
& env
, Type cls
, Type tprop
) {
715 auto const self
= selfCls(env
);
716 auto const prop
= tv(tprop
);
717 auto const name
= prop
&& prop
->m_type
== KindOfStaticString
718 ? prop
->m_data
.pstr
: nullptr;
719 if (self
&& cls
.subtypeOf(*self
) && name
) {
720 if (auto const ty
= selfPropAsCell(env
, prop
->m_data
.pstr
)) {
721 return Base
{ *ty
, BaseLoc::StaticObjProp
, cls
, name
};
724 auto const indexTy
= env
.index
.lookup_public_static(cls
, tprop
);
725 if (indexTy
.subtypeOf(TInitCell
)) {
726 return Base
{ indexTy
, BaseLoc::StaticObjProp
, cls
, name
};
728 return Base
{ TInitCell
, BaseLoc::StaticObjProp
, cls
, name
};
731 Base
miBase(MIS
& env
) {
732 auto& mvec
= env
.mvec
;
733 switch (mvec
.lcode
) {
735 return miBaseLoc(env
);
737 return Base
{ topC(env
, --env
.stackIdx
), BaseLoc::EvalStack
};
740 auto const t
= topR(env
, --env
.stackIdx
);
741 return Base
{ t
.subtypeOf(TInitCell
) ? t
: TInitCell
,
742 BaseLoc::EvalStack
};
746 auto const ty
= thisType(env
);
747 return Base
{ ty
? *ty
: TObj
, BaseLoc::FrameThis
};
750 locAsCell(env
, env
.mvec
.locBase
);
751 return Base
{ TInitCell
, BaseLoc::Global
};
753 topC(env
, --env
.stackIdx
);
754 return Base
{ TInitCell
, BaseLoc::Global
};
756 // The first moveBase() on these unknown local bases will cause
757 // a env.loseNonRefLocalTypes.
759 return Base
{ TInitCell
, BaseLoc::Frame
};
761 topC(env
, --env
.stackIdx
);
762 return Base
{ TInitCell
, BaseLoc::Frame
};
766 auto const cls
= topA(env
, env
.info
.valCount());
767 auto const prop
= locAsCell(env
, env
.mvec
.locBase
);
768 return miBaseSProp(env
, cls
, prop
);
772 auto const cls
= topA(env
, env
.info
.valCount());
773 auto const prop
= topC(env
, --env
.stackIdx
);
774 return miBaseSProp(env
, cls
, prop
);
777 case InvalidLocationCode
:
783 //////////////////////////////////////////////////////////////////////
786 void miProp(MIS
& env
) {
787 auto const name
= mcodeStringKey(env
);
788 auto const isDefine
= env
.info
.getAttr(env
.mcode()) & MIA_define
;
789 auto const isUnset
= env
.info
.getAttr(env
.mcode()) & MIA_unset
;
790 auto const isNullsafe
= (env
.mvec
.mcodes
[env
.mInd
].mcode
== MQT
);
793 * MIA_unset Props doesn't promote "emptyish" things to stdClass,
794 * or affect arrays, however it can define a property on an object
795 * base. This means we don't need any couldBeInFoo logic, but if
796 * the base could actually be $this, and a declared property could
797 * be Uninit, we need to merge InitNull.
799 * We're trying to handle this case correctly as far as the type
800 * inference here is concerned, but the runtime doesn't actually
801 * behave this way right now for declared properties. Note that
802 * it never hurts to merge more types than a thisProp could
803 * actually be, so this is fine.
805 * See TODO(#3602740): unset with intermediate dims on previously
806 * declared properties doesn't define them to null.
808 if (isUnset
&& couldBeThisObj(env
, env
.base
)) {
810 auto const ty
= thisPropRaw(env
, name
);
811 if (ty
&& ty
->couldBe(TUninit
)) {
812 mergeThisProp(env
, name
, TInitNull
);
815 mergeEachThisPropRaw(env
, [&] (Type ty
) {
816 return ty
.couldBe(TUninit
) ? TInitNull
: TBottom
;
822 handleInThisPropD(env
);
823 handleInSelfPropD(env
);
824 handleInPublicStaticPropD(env
);
825 handleBasePropD(env
);
828 if (mustBeThisObj(env
, env
.base
)) {
829 auto const optThisTy
= thisType(env
);
830 auto const thisTy
= optThisTy
? *optThisTy
: TObj
;
832 auto const propTy
= thisPropAsCell(env
, name
);
833 moveBase(env
, Base
{ propTy
? *propTy
: TInitCell
,
838 moveBase(env
, Base
{ TInitCell
, BaseLoc::PostProp
, thisTy
});
843 // We know for sure we're going to be in an object property.
844 if (env
.base
.type
.subtypeOf(TObj
)) {
845 moveBase(env
, Base
{ TInitCell
,
853 * Otherwise, intermediate props with define can promote a null, false, or ""
854 * to stdClass. Those cases, and others, if it's not MIA_define, will set
855 * the base to a null value in tvScratch. The base may also legitimately be
856 * an object and our next base is in an object property.
858 * If we know for sure we're promoting to stdClass, we can put the locType
859 * pointing at that. Otherwise we conservatively treat all these cases as
860 * "possibly" being inside of an object property with "PostProp" with locType
863 auto const newBaseLocTy
=
864 isDefine
&& !isNullsafe
&& propMustPromoteToObj(env
.base
.type
)
865 ? objExact(env
.index
.builtin_class(s_stdClass
.get()))
868 moveBase(env
, Base
{ TInitCell
, BaseLoc::PostProp
, newBaseLocTy
, name
});
871 void miElem(MIS
& env
) {
872 auto const key
= mcodeKey(env
);
873 bool const isDefine
= env
.info
.getAttr(env
.mcode()) & MIA_define
;
874 bool const isUnset
= env
.info
.getAttr(env
.mcode()) & MIA_unset
;
877 * Elem dims with MIA_unset can change a base from a static array
878 * into a reference counted array. It never promotes emptyish
881 * We only need to handle this for self props, because we don't
882 * track static-ness on this props. The similar effect on local
883 * bases is handled in miBase.
886 handleInSelfElemU(env
);
887 handleInPublicStaticElemU(env
);
888 handleBaseElemU(env
);
892 handleInThisElemD(env
);
893 handleInSelfElemD(env
);
894 handleInPublicStaticElemD(env
);
895 handleBaseElemD(env
);
897 auto const couldDoChain
=
898 mustBeInFrame(env
.base
) || env
.base
.loc
== BaseLoc::LocalArrChain
;
899 if (couldDoChain
&& env
.base
.type
.subtypeOf(TArr
)) {
900 env
.arrayChain
.emplace_back(env
.base
.type
, key
);
901 moveBase(env
, Base
{ array_elem(env
.base
.type
, key
),
902 BaseLoc::LocalArrChain
,
910 if (env
.base
.type
.subtypeOf(TArr
)) {
911 moveBase(env
, Base
{ array_elem(env
.base
.type
, key
),
916 if (env
.base
.type
.subtypeOf(TStr
)) {
917 moveBase(env
, Base
{ TStr
, BaseLoc::PostElem
});
922 * Other cases could leave the base as anything (if nothing else,
923 * via ArrayAccess on an object).
925 * The resulting BaseLoc is either inside an array, is the global
926 * init_null_variant, or inside tvScratch. We represent this with
927 * the PostElem base location with locType TTop.
929 moveBase(env
, Base
{ TInitCell
, BaseLoc::PostElem
, TTop
});
932 void miNewElem(MIS
& env
) {
933 if (!env
.info
.newElem()) {
934 moveBase(env
, Base
{ TInitCell
, BaseLoc::Fataled
});
938 handleInThisNewElem(env
);
939 handleInSelfNewElem(env
);
940 handleInPublicStaticNewElem(env
);
941 handleBaseNewElem(env
);
943 auto const couldDoChain
=
944 mustBeInFrame(env
.base
) || env
.base
.loc
== BaseLoc::LocalArrChain
;
945 if (couldDoChain
&& env
.base
.type
.subtypeOf(TArr
)) {
946 env
.arrayChain
.push_back(
947 array_newelem_key(env
.base
.type
, TInitNull
));
948 moveBase(env
, Base
{ TInitNull
,
949 BaseLoc::LocalArrChain
,
956 if (env
.base
.type
.subtypeOf(TArr
)) {
957 moveBase(env
, Base
{ TInitNull
, BaseLoc::PostElem
, env
.base
.type
});
961 moveBase(env
, Base
{ TInitCell
, BaseLoc::PostElem
, TTop
});
964 void miIntermediate(MIS
& env
) {
965 if (mcodeIsProp(env
.mcode())) return miProp(env
);
966 if (mcodeIsElem(env
.mcode())) return miElem(env
);
967 return miNewElem(env
);
970 //////////////////////////////////////////////////////////////////////
973 void miFinalIssetProp(MIS
& env
) {
974 auto const name
= mcodeStringKey(env
);
976 if (name
&& mustBeThisObj(env
, env
.base
)) {
977 if (auto const pt
= thisPropAsCell(env
, name
)) {
978 if (pt
->subtypeOf(TNull
)) return push(env
, TFalse
);
979 if (!pt
->couldBe(TNull
)) return push(env
, TTrue
);
985 void miFinalCGetProp(MIS
& env
) {
986 auto const name
= mcodeStringKey(env
);
988 if (name
&& mustBeThisObj(env
, env
.base
)) {
989 if (auto const t
= thisPropAsCell(env
, name
)) {
990 return push(env
, *t
);
993 push(env
, TInitCell
);
996 void miFinalVGetProp(MIS
& env
) {
997 auto const name
= mcodeStringKey(env
);
999 handleInThisPropD(env
);
1000 handleInSelfPropD(env
);
1001 handleInPublicStaticPropD(env
);
1002 handleBasePropD(env
);
1003 if (couldBeThisObj(env
, env
.base
)) {
1005 boxThisProp(env
, name
);
1013 void miFinalSetProp(MIS
& env
) {
1014 auto const name
= mcodeStringKey(env
);
1015 auto const t1
= popC(env
);
1018 handleInThisPropD(env
);
1019 handleInSelfPropD(env
);
1020 handleInPublicStaticPropD(env
);
1021 handleBasePropD(env
);
1023 auto const resultTy
= env
.base
.type
.subtypeOf(TObj
) ? t1
: TInitCell
;
1025 if (couldBeThisObj(env
, env
.base
)) {
1027 mergeEachThisPropRaw(env
, [&] (Type propTy
) -> Type
{
1028 if (propTy
.couldBe(TInitCell
)) {
1029 return union_of(std::move(propTy
), t1
);
1033 push(env
, resultTy
);
1036 mergeThisProp(env
, name
, t1
);
1037 push(env
, resultTy
);
1041 push(env
, resultTy
);
1044 void miFinalSetOpProp(MIS
& env
, SetOpOp subop
) {
1045 auto const name
= mcodeStringKey(env
);
1046 auto const rhsTy
= popC(env
);
1049 handleInThisPropD(env
);
1050 handleInSelfPropD(env
);
1051 handleInPublicStaticPropD(env
);
1052 handleBasePropD(env
);
1054 auto resultTy
= TInitCell
;
1056 if (couldBeThisObj(env
, env
.base
)) {
1057 if (name
&& mustBeThisObj(env
, env
.base
)) {
1058 if (auto const lhsTy
= thisPropAsCell(env
, name
)) {
1059 resultTy
= typeSetOp(subop
, *lhsTy
, rhsTy
);
1064 mergeThisProp(env
, name
, resultTy
);
1066 loseNonRefThisPropTypes(env
);
1070 push(env
, resultTy
);
1073 void miFinalIncDecProp(MIS
& env
) {
1074 auto const name
= mcodeStringKey(env
);
1076 handleInThisPropD(env
);
1077 handleInSelfPropD(env
);
1078 handleInPublicStaticPropD(env
);
1079 handleBasePropD(env
);
1080 if (couldBeThisObj(env
, env
.base
)) {
1082 mergeThisProp(env
, name
, TInitCell
);
1084 loseNonRefThisPropTypes(env
);
1087 push(env
, TInitCell
);
1090 void miFinalBindProp(MIS
& env
) {
1091 auto const name
= mcodeStringKey(env
);
1094 handleInThisPropD(env
);
1095 handleInSelfPropD(env
);
1096 handleInPublicStaticPropD(env
);
1097 handleBasePropD(env
);
1098 if (couldBeThisObj(env
, env
.base
)) {
1100 boxThisProp(env
, name
);
1108 void miFinalUnsetProp(MIS
& env
) {
1109 auto const name
= mcodeStringKey(env
);
1113 * Unset does define intermediate dims but with slightly different
1114 * rules than sets. It only applies to object properties.
1116 * Note that this can't affect self props, because static
1117 * properties can never be unset. It also can't change anything
1118 * about an inner array type.
1120 handleInThisPropD(env
);
1122 if (couldBeThisObj(env
, env
.base
)) {
1124 unsetThisProp(env
, name
);
1126 unsetUnknownThisProp(env
);
1131 //////////////////////////////////////////////////////////////////////
1134 // This is a helper for final defining Elem operations that need to
1135 // handle array chains and frame effects, but don't yet do anything
1136 // better than supplying a single type.
1137 void pessimisticFinalElemD(MIS
& env
, Type key
, Type ty
) {
1138 if (mustBeInFrame(env
.base
) && env
.base
.type
.subtypeOf(TArr
)) {
1139 env
.base
.type
= array_set(env
.base
.type
, key
, ty
);
1142 if (env
.base
.loc
== BaseLoc::LocalArrChain
) {
1143 if (env
.base
.type
.subtypeOf(TArr
)) {
1144 env
.arrayChain
.emplace_back(env
.base
.type
, key
);
1150 void miFinalCGetElem(MIS
& env
) {
1151 auto const key
= mcodeKey(env
);
1153 env
.base
.type
.subtypeOf(TArr
)
1154 ? array_elem(env
.base
.type
, key
)
1160 void miFinalVGetElem(MIS
& env
) {
1161 auto const key
= mcodeKey(env
);
1163 handleInThisElemD(env
);
1164 handleInSelfElemD(env
);
1165 handleInPublicStaticElemD(env
);
1166 handleBaseElemD(env
);
1167 pessimisticFinalElemD(env
, key
, TInitGen
);
1171 void miFinalSetElem(MIS
& env
) {
1172 auto const key
= mcodeKey(env
);
1173 auto const t1
= popC(env
);
1176 handleInThisElemD(env
);
1177 handleInSelfElemD(env
);
1178 handleInPublicStaticElemD(env
);
1180 // Note: we must handle the string-related cases before doing the
1181 // general handleBaseElemD, since operates on strings as if this
1182 // was an intermediate ElemD.
1183 if (env
.base
.type
.subtypeOf(sempty())) {
1184 env
.base
.type
= counted_aempty();
1186 auto& ty
= env
.base
.type
;
1187 if (ty
.couldBe(TStr
)) {
1188 // Note here that a string type stays a string (with a changed character,
1189 // and loss of staticness), unless it was the empty string, where it
1190 // becomes an array. Do it conservatively for now:
1191 ty
= union_of(loosen_statics(ty
), counted_aempty());
1193 if (!ty
.subtypeOf(TStr
)) {
1194 handleBaseElemD(env
);
1199 * In some unusual cases with illegal keys, SetM pushes null
1200 * instead of the right hand side.
1202 * There are also some special cases for SetM for different base types:
1203 * 1. If the base is a string, SetM pushes a new string with the
1204 * value of the first character of the right hand side converted
1205 * to a string (or something like that).
1206 * 2. If the base is a primitive type, SetM pushes null.
1207 * 3. If the base is an object, and it does not implement ArrayAccess,
1208 * it is still ok to push the right hand side, because it is a
1211 * We push the right hand side on the stack only if the base is an
1212 * array, object or emptyish.
1214 auto const isWeird
= key
.couldBe(TObj
) ||
1215 key
.couldBe(TArr
) ||
1216 (!env
.base
.type
.subtypeOf(TArr
) &&
1217 !env
.base
.type
.subtypeOf(TObj
) &&
1218 !mustBeEmptyish(env
.base
.type
));
1220 if (mustBeInFrame(env
.base
) && env
.base
.type
.subtypeOf(TArr
)) {
1221 env
.base
.type
= array_set(env
.base
.type
, key
, t1
);
1222 push(env
, isWeird
? TInitCell
: t1
);
1225 if (env
.base
.loc
== BaseLoc::LocalArrChain
) {
1226 if (env
.base
.type
.subtypeOf(TArr
)) {
1227 env
.arrayChain
.emplace_back(env
.base
.type
, key
);
1229 push(env
, isWeird
? TInitCell
: t1
);
1234 // ArrayAccess on $this will always push the rhs, even if things
1236 if (mustBeThisObj(env
, env
.base
)) return push(env
, t1
);
1238 push(env
, isWeird
? TInitCell
: t1
);
1241 void miFinalSetOpElem(MIS
& env
) {
1242 auto const key
= mcodeKey(env
);
1245 handleInThisElemD(env
);
1246 handleInSelfElemD(env
);
1247 handleInPublicStaticElemD(env
);
1248 handleBaseElemD(env
);
1249 pessimisticFinalElemD(env
, key
, TInitCell
);
1250 push(env
, TInitCell
);
1253 void miFinalIncDecElem(MIS
& env
) {
1254 auto const key
= mcodeKey(env
);
1256 handleInThisElemD(env
);
1257 handleInSelfElemD(env
);
1258 handleInPublicStaticElemD(env
);
1259 handleBaseElemD(env
);
1260 pessimisticFinalElemD(env
, key
, TInitCell
);
1261 push(env
, TInitCell
);
1264 void miFinalBindElem(MIS
& env
) {
1265 auto const key
= mcodeKey(env
);
1268 handleInThisElemD(env
);
1269 handleInSelfElemD(env
);
1270 handleInPublicStaticElemD(env
);
1271 handleBaseElemD(env
);
1272 pessimisticFinalElemD(env
, key
, TInitGen
);
1276 void miFinalUnsetElem(MIS
& env
) {
1279 handleInSelfElemU(env
);
1280 handleInPublicStaticElemU(env
);
1281 handleBaseElemU(env
);
1282 // We don't handle inner-array types with unset yet.
1283 always_assert(env
.base
.loc
!= BaseLoc::LocalArrChain
);
1284 if (mustBeInFrame(env
.base
)) {
1285 always_assert(!env
.base
.type
.strictSubtypeOf(TArr
));
1289 //////////////////////////////////////////////////////////////////////
1290 // Final new elem ops
1292 // This is a helper for final defining Elem operations that need to
1293 // handle array chains and frame effects, but don't yet do anything
1294 // better than supplying a single type.
1295 void pessimisticFinalNewElem(MIS
& env
, Type ty
) {
1296 if (mustBeInFrame(env
.base
) && env
.base
.type
.subtypeOf(TArr
)) {
1297 env
.base
.type
= array_newelem(env
.base
.type
, ty
);
1300 if (env
.base
.loc
== BaseLoc::LocalArrChain
&&
1301 env
.base
.type
.subtypeOf(TArr
)) {
1302 env
.base
.type
= array_newelem(env
.base
.type
, ty
);
1307 void miFinalVGetNewElem(MIS
& env
) {
1309 handleInThisNewElem(env
);
1310 handleInSelfNewElem(env
);
1311 handleInPublicStaticNewElem(env
);
1312 handleBaseNewElem(env
);
1313 pessimisticFinalNewElem(env
, TInitGen
);
1317 void miFinalSetNewElem(MIS
& env
) {
1318 auto const t1
= popC(env
);
1320 handleInThisNewElem(env
);
1321 handleInSelfNewElem(env
);
1322 handleInPublicStaticNewElem(env
);
1323 handleBaseNewElem(env
);
1325 if (mustBeInFrame(env
.base
) && env
.base
.type
.subtypeOf(TArr
)) {
1326 env
.base
.type
= array_newelem(env
.base
.type
, t1
);
1330 if (env
.base
.loc
== BaseLoc::LocalArrChain
&&
1331 env
.base
.type
.subtypeOf(TArr
)) {
1332 env
.base
.type
= array_newelem(env
.base
.type
, t1
);
1337 // ArrayAccess on $this will always push the rhs.
1338 if (mustBeThisObj(env
, env
.base
)) return push(env
, t1
);
1340 // TODO(#3343813): we should push the type of the rhs when we can;
1341 // SetM for a new elem still has some weird cases where it pushes
1342 // null instead to handle. (E.g. if the base is a number.)
1343 push(env
, TInitCell
);
1346 void miFinalSetOpNewElem(MIS
& env
) {
1349 handleInThisNewElem(env
);
1350 handleInSelfNewElem(env
);
1351 handleInPublicStaticNewElem(env
);
1352 handleBaseNewElem(env
);
1353 pessimisticFinalNewElem(env
, TInitCell
);
1354 push(env
, TInitCell
);
1357 void miFinalIncDecNewElem(MIS
& env
) {
1359 handleInThisNewElem(env
);
1360 handleInSelfNewElem(env
);
1361 handleInPublicStaticNewElem(env
);
1362 handleBaseNewElem(env
);
1363 pessimisticFinalNewElem(env
, TInitCell
);
1364 push(env
, TInitCell
);
1367 void miFinalBindNewElem(MIS
& env
) {
1370 handleInThisNewElem(env
);
1371 handleInSelfNewElem(env
);
1372 handleInPublicStaticNewElem(env
);
1373 handleBaseNewElem(env
);
1374 pessimisticFinalNewElem(env
, TInitGen
);
1378 //////////////////////////////////////////////////////////////////////
1380 void miFinal(MIS
& env
, const bc::EmptyM
& op
) {
1381 // Same thing for now for props and elems, regardless of the base.
1382 // MW would be a fatal.
1388 void miFinal(MIS
& env
, const bc::IssetM
& op
) {
1389 if (mcodeIsProp(env
.mcode())) return miFinalIssetProp(env
);
1390 // Elem case (MW would be a fatal):
1391 if (env
.mcode() != MemberCode::MW
) mcodeKey(env
);
1396 void miFinal(MIS
& env
, const bc::CGetM
& op
) {
1397 if (mcodeIsProp(env
.mcode())) return miFinalCGetProp(env
);
1398 if (env
.mcode() == MemberCode::MW
) {
1401 push(env
, TInitCell
);
1404 return miFinalCGetElem(env
);
1407 void miFinal(MIS
& env
, const bc::VGetM
& op
) {
1408 if (mcodeIsElem(env
.mcode())) return miFinalVGetElem(env
);
1409 if (mcodeIsProp(env
.mcode())) return miFinalVGetProp(env
);
1410 return miFinalVGetNewElem(env
);
1413 void miFinal(MIS
& env
, const bc::SetM
& op
) {
1414 if (mcodeIsElem(env
.mcode())) return miFinalSetElem(env
);
1415 if (mcodeIsProp(env
.mcode())) return miFinalSetProp(env
);
1416 return miFinalSetNewElem(env
);
1419 void miFinal(MIS
& env
, const bc::SetOpM
& op
) {
1420 if (mcodeIsElem(env
.mcode())) return miFinalSetOpElem(env
);
1421 if (mcodeIsProp(env
.mcode())) return miFinalSetOpProp(env
, op
.subop
);
1422 return miFinalSetOpNewElem(env
);
1425 void miFinal(MIS
& env
, const bc::IncDecM
& op
) {
1426 if (mcodeIsElem(env
.mcode())) return miFinalIncDecElem(env
);
1427 if (mcodeIsProp(env
.mcode())) return miFinalIncDecProp(env
);
1428 return miFinalIncDecNewElem(env
);
1431 void miFinal(MIS
& env
, const bc::BindM
& op
) {
1432 if (mcodeIsElem(env
.mcode())) return miFinalBindElem(env
);
1433 if (mcodeIsProp(env
.mcode())) return miFinalBindProp(env
);
1434 return miFinalBindNewElem(env
);
1437 void miFinal(MIS
& env
, const bc::UnsetM
& op
) {
1438 if (mcodeIsElem(env
.mcode())) return miFinalUnsetElem(env
);
1439 if (mcodeIsProp(env
.mcode())) return miFinalUnsetProp(env
);
1440 // The MW case is a fatal.
1444 void miFinal(MIS
& env
, const bc::SetWithRefLM
& op
) {
1445 moveBase(env
, folly::none
);
1449 if (env
.mcode() != MemberCode::MW
) mcodeKey(env
);
1453 void miFinal(MIS
& env
, const bc::SetWithRefRM
& op
) {
1454 moveBase(env
, folly::none
);
1458 if (env
.mcode() != MemberCode::MW
) mcodeKey(env
);
1463 //////////////////////////////////////////////////////////////////////
1466 void miImpl(ISS
& baseEnv
, const Op
& op
,
1467 const MInstrInfo
& info
, const MVector
& mvec
) {
1468 MIS env
{ baseEnv
, &info
, mvec
};
1469 env
.base
= miBase(env
);
1470 FTRACE(3, " base: {}\n", base_string(env
.base
));
1472 for (; env
.mInd
< mvec
.mcodes
.size() - 1; ++env
.mInd
) {
1473 miIntermediate(env
);
1474 FTRACE(3, " base: {}\n", base_string(env
.base
));
1479 // If the final operation was either a define, a NewElem, or an
1480 // unset, there may be pending effects in local array chains or on
1481 // local types, so we need to move the base one last time. The
1482 // other cases should have no effects here.
1483 moveBase(env
, folly::none
);
1486 //////////////////////////////////////////////////////////////////////
1490 #define MII(m, ...) \
1491 void minstr(ISS& env, const bc::m##M& op) { \
1492 miImpl(env, op, getMInstrInfo(Op::m##M), op.mvec); \
1497 //////////////////////////////////////////////////////////////////////