2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
16 #include "hphp/hhbbc/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/optimize.h"
30 #include "hphp/hhbbc/type-ops.h"
32 namespace HPHP
{ namespace HHBBC
{
36 //////////////////////////////////////////////////////////////////////
38 const StaticString
s_stdClass("stdClass");
40 //////////////////////////////////////////////////////////////////////
43 * Note: the couldBe comparisons here with sempty() are asking "can this string
44 * be a non-reference counted empty string". What actually matters is whether
45 * it can be an empty string at all. Currently, all reference counted strings
46 * are TStr, which has no values and may also be non-reference
47 * counted---emptiness isn't separately tracked like it is for arrays, so if
48 * anything happened that could make it reference counted this check will
51 * This means this code is fine for now, but if we implement #3837503
52 * (non-static strings with values in the type system) it will need to change.
55 bool couldBeEmptyish(const Type
& ty
) {
56 return ty
.couldBe(BNull
| BFalse
) || ty
.couldBe(sempty());
59 bool mustBeEmptyish(const Type
& ty
) {
60 return ty
.subtypeOf(BNull
| BFalse
) || ty
.subtypeOf(sempty());
63 bool elemCouldPromoteToArr(const Type
& ty
) { return couldBeEmptyish(ty
); }
64 bool elemMustPromoteToArr(const Type
& ty
) { return mustBeEmptyish(ty
); }
66 bool propCouldPromoteToObj(const Type
& ty
) {
67 return RuntimeOption::EvalPromoteEmptyObject
&& couldBeEmptyish(ty
);
70 bool propMustPromoteToObj(const Type
& ty
) {
71 return RuntimeOption::EvalPromoteEmptyObject
&& mustBeEmptyish(ty
);
74 bool keyCouldBeWeird(const Type
& key
) {
75 return key
.couldBe(BObj
| BArr
| BVec
| BDict
| BKeyset
);
78 bool mustBeArrLike(const Type
& ty
) {
79 return ty
.subtypeOf(BArr
| BVec
| BDict
| BKeyset
);
82 //////////////////////////////////////////////////////////////////////
84 Type
baseLocNameType(const Base
& b
) {
85 return b
.locName
? sval(b
.locName
) : TInitGen
;
88 //////////////////////////////////////////////////////////////////////
93 * Generally type inference needs to know two kinds of things about the base to
94 * handle effects on tracked locations:
96 * - Could the base be a location we're tracking deeper structure on, so the
97 * next operation actually affects something inside of it. For example,
98 * could the base be an object with the same type as $this, or an array in a
101 * - Could the base be something (regardless of type) that is inside one of
102 * the things we're tracking. I.e., the base might be whatever (an array or
103 * a bool or something), but living inside a property inside an object with
104 * the same type as $this, or living inside of an array in the local frame.
106 * The first cases apply because final operations are going to directly affect
107 * the type of these elements. The second case is because vector operations may
108 * change the base at each step if it is a defining instruction.
110 * Note that both of these cases can apply to the same base in some cases: you
111 * might have an object property on $this that could be an object of the type of
114 * The functions below with names "couldBeIn*" detect the second case. The
115 * effects on the tracked location in the second case are handled in the
116 * functions with names "promoteIn*{Prop,Elem,..}". The effects for the first
117 * case are generally handled in the miFinal op functions.
119 * Control flow insensitive vs. control flow sensitive types:
121 * Things are also slightly complicated by the fact that we are analyzing some
122 * control flow insensitve types along side precisely tracked types. For
123 * effects on locals, we perform the type effects of each operation on
124 * base.type, and then allow updateBaseWithType() to make the updates to the
125 * local when we know what its final type will be.
127 * This approach doesn't do as well for possible properties in $this or self::,
128 * because we may see situations where the base could be one of these properties
129 * but we're not sure---perhaps because it came off a property with the same
130 * name on an object with an unknown type (i.e. base.type is InitCell but
131 * couldBeInProp is true). In these situations, we can get away with just
132 * merging Obj=stdClass into the thisProp (because it 'could' promote) instead
133 * of merging the whole InitCell, which possibly lets us leave the type at ?Obj
136 * This is why there's two fairly different mechanisms for handling the effects
137 * of defining ops on base types.
140 //////////////////////////////////////////////////////////////////////
142 bool couldBeThisObj(ISS
& env
, const Base
& b
) {
143 auto const thisTy
= thisTypeFromContext(env
.index
, env
.ctx
);
144 return b
.type
.couldBe(thisTy
? *thisTy
: TObj
);
147 bool mustBeThisObj(ISS
& env
, const Base
& b
) {
148 if (b
.loc
== BaseLoc::This
) return true;
149 if (auto const ty
= thisTypeFromContext(env
.index
, env
.ctx
)) {
150 return b
.type
.subtypeOf(*ty
);
155 bool mustBeInLocal(const Base
& b
) {
156 return b
.loc
== BaseLoc::Local
;
159 bool mustBeInStack(const Base
& b
) {
160 return b
.loc
== BaseLoc::Stack
;
163 bool couldBeInProp(ISS
& env
, const Base
& b
) {
164 if (b
.loc
!= BaseLoc::Prop
) return false;
165 auto const thisTy
= thisTypeFromContext(env
.index
, env
.ctx
);
166 if (!thisTy
) return true;
167 if (!b
.locTy
.couldBe(*thisTy
)) return false;
168 if (b
.locName
) return isTrackedThisProp(env
, b
.locName
);
172 bool couldBeInPrivateStatic(ISS
& env
, const Base
& b
) {
173 if (b
.loc
!= BaseLoc::StaticProp
) return false;
174 auto const selfTy
= selfCls(env
);
175 return !selfTy
|| b
.locTy
.couldBe(*selfTy
);
178 bool couldBeInPublicStatic(const Base
& b
) {
179 return b
.loc
== BaseLoc::StaticProp
;
182 //////////////////////////////////////////////////////////////////////
184 // Base locations that only occur at the start of a minstr sequence.
185 bool isInitialBaseLoc(BaseLoc loc
) {
187 loc
== BaseLoc::Local
||
188 loc
== BaseLoc::Stack
||
189 loc
== BaseLoc::StaticProp
||
190 loc
== BaseLoc::This
||
191 loc
== BaseLoc::Global
;
194 // Base locations that only occur after the start of a minstr sequence.
195 bool isDimBaseLoc(BaseLoc loc
) {
196 return loc
== BaseLoc::Elem
|| loc
== BaseLoc::Prop
;
199 //////////////////////////////////////////////////////////////////////
202 * If the current base is an array-like, update it via *_set, and return true;
203 * otherwise, return false.
205 bool array_do_set(ISS
& env
, const Type
& key
, const Type
& value
) {
206 auto& base
= env
.collect
.mInstrState
.base
.type
;
207 auto const tag
= provTagHere(env
);
208 auto res
= [&] () -> folly::Optional
<std::pair
<Type
,ThrowMode
>> {
209 if (base
.subtypeOf(BArr
)) {
210 return array_set(std::move(base
), key
, value
, tag
);
211 } else if (base
.subtypeOf(BVec
)) {
212 return vec_set(std::move(base
), key
, value
, tag
);
213 } else if (base
.subtypeOf(BDict
)) {
214 return dict_set(std::move(base
), key
, value
, tag
);
215 } else if (base
.subtypeOf(BKeyset
)) {
216 return keyset_set(std::move(base
), key
, value
);
220 if (!res
) return false;
222 switch (res
->second
) {
223 case ThrowMode::None
:
226 case ThrowMode::MaybeMissingElement
:
227 case ThrowMode::MissingElement
:
228 case ThrowMode::MaybeBadKey
:
229 case ThrowMode::BadOperation
:
233 if (res
->first
== TBottom
) {
237 base
= std::move(res
->first
);
242 * If the current base is an array-like, return the best known type
245 folly::Optional
<Type
> array_do_elem(ISS
& env
,
248 auto const& base
= env
.collect
.mInstrState
.base
.type
;
249 auto res
= [&] () -> folly::Optional
<std::pair
<Type
,ThrowMode
>> {
250 if (base
.subtypeOf(BArr
)) return array_elem(base
, key
);
251 if (base
.subtypeOf(BVec
)) return vec_elem(base
, key
);
252 if (base
.subtypeOf(BDict
)) return dict_elem(base
, key
);
253 if (base
.subtypeOf(BKeyset
)) return keyset_elem(base
, key
);
256 if (!res
) return folly::none
;
258 switch (res
->second
) {
259 case ThrowMode::None
:
262 case ThrowMode::MaybeMissingElement
:
263 case ThrowMode::MissingElement
:
266 res
->first
|= TInitNull
;
269 case ThrowMode::MaybeBadKey
:
271 res
->first
|= TInitNull
;
274 case ThrowMode::BadOperation
:
278 if (res
->first
== TBottom
) {
282 return std::move(res
->first
);
286 * If the current base is an array-like, update it via *_newelem, and
287 * return the best known type for the key added; otherwise return folly::none.
289 folly::Optional
<Type
> array_do_newelem(ISS
& env
, const Type
& value
) {
290 auto& base
= env
.collect
.mInstrState
.base
.type
;
291 auto const tag
= provTagHere(env
);
292 auto res
= [&] () -> folly::Optional
<std::pair
<Type
,Type
>> {
293 if (base
.subtypeOf(BArr
)) {
294 return array_newelem(std::move(base
), value
, tag
);
295 } else if (base
.subtypeOf(BVec
)) {
296 return vec_newelem(std::move(base
), value
, tag
);
297 } else if (base
.subtypeOf(BDict
)) {
298 return dict_newelem(std::move(base
), value
, tag
);
299 } else if (base
.subtypeOf(BKeyset
)) {
300 return keyset_newelem(std::move(base
), value
);
304 if (!res
) return folly::none
;
305 base
= std::move(res
->first
);
309 //////////////////////////////////////////////////////////////////////
311 // MInstrs can throw in between each op, so the states of locals
312 // need to be propagated across throw exit edges.
313 void miThrow(ISS
& env
) {
314 if (env
.blk
.throwExit
!= NoBlockId
) {
315 auto const stackLess
= with_throwable_only(env
.index
, env
.state
);
316 env
.propagate(env
.blk
.throwExit
, &stackLess
);
320 //////////////////////////////////////////////////////////////////////
322 void setLocalForBase(ISS
& env
, Type ty
, LocalId firstKeyLoc
) {
323 assert(mustBeInLocal(env
.collect
.mInstrState
.base
));
324 if (env
.collect
.mInstrState
.base
.locLocal
== NoLocalId
) {
325 return loseNonRefLocalTypes(env
);
327 FTRACE(4, " ${} := {}\n",
328 env
.collect
.mInstrState
.base
.locName
329 ? env
.collect
.mInstrState
.base
.locName
->data()
335 env
.collect
.mInstrState
.base
.locLocal
,
341 void setStackForBase(ISS
& env
, Type ty
) {
342 assert(mustBeInStack(env
.collect
.mInstrState
.base
));
344 auto const locSlot
= env
.collect
.mInstrState
.base
.locSlot
;
345 FTRACE(4, " stk[{:02}] := {}\n", locSlot
, show(ty
));
346 assert(locSlot
< env
.state
.stack
.size());
348 auto const& oldTy
= env
.state
.stack
[locSlot
].type
;
349 if (oldTy
.subtypeOf(BInitCell
)) {
350 env
.state
.stack
[locSlot
] = StackElem
{ std::move(ty
), NoLocalId
};
354 void setPrivateStaticForBase(ISS
& env
, Type ty
) {
355 assert(couldBeInPrivateStatic(env
, env
.collect
.mInstrState
.base
));
357 if (auto const name
= env
.collect
.mInstrState
.base
.locName
) {
358 FTRACE(4, " self::${} |= {}\n", name
->data(), show(ty
));
359 mergeSelfProp(env
, name
, std::move(ty
));
362 FTRACE(4, " self::* |= {}\n", show(ty
));
363 mergeEachSelfPropRaw(
365 [&](const Type
& old
){ return old
.subtypeOf(BInitCell
) ? ty
: TBottom
; }
369 void setPublicStaticForBase(ISS
& env
, Type ty
) {
370 auto const& base
= env
.collect
.mInstrState
.base
;
371 assertx(couldBeInPublicStatic(base
));
373 auto const nameTy
= base
.locName
? sval(base
.locName
) : TStr
;
375 4, " public ({})::$({}) |= {}\n",
376 show(base
.locTy
), show(nameTy
), show(ty
)
378 env
.collect
.publicSPropMutations
.merge(
379 env
.index
, env
.ctx
, base
.locTy
, nameTy
, ty
383 // Run backwards through an array chain doing array_set operations
384 // to produce the array type that incorporates the effects of any
385 // intermediate defining dims.
386 Type
currentChainType(ISS
& env
, Type val
) {
387 auto it
= env
.collect
.mInstrState
.arrayChain
.end();
388 auto const tag
= provTagHere(env
);
389 while (it
!= env
.collect
.mInstrState
.arrayChain
.begin()) {
391 if (it
->base
.subtypeOf(BArr
)) {
392 val
= array_set(it
->base
, it
->key
, val
, tag
).first
;
393 } else if (it
->base
.subtypeOf(BVec
)) {
394 val
= vec_set(it
->base
, it
->key
, val
, tag
).first
;
395 if (val
== TBottom
) val
= TVec
;
396 } else if (it
->base
.subtypeOf(BDict
)) {
397 val
= dict_set(it
->base
, it
->key
, val
, tag
).first
;
398 if (val
== TBottom
) val
= TDict
;
400 assert(it
->base
.subtypeOf(BKeyset
));
401 val
= keyset_set(it
->base
, it
->key
, val
).first
;
402 if (val
== TBottom
) val
= TKeyset
;
408 Type
resolveArrayChain(ISS
& env
, Type val
) {
409 static UNUSED
const char prefix
[] = " ";
410 FTRACE(5, "{}chain\n", prefix
, show(val
));
411 auto const tag
= provTagHere(env
);
413 auto arr
= std::move(env
.collect
.mInstrState
.arrayChain
.back().base
);
414 auto key
= std::move(env
.collect
.mInstrState
.arrayChain
.back().key
);
415 env
.collect
.mInstrState
.arrayChain
.pop_back();
416 FTRACE(5, "{} | {} := {} in {}\n", prefix
,
417 show(key
), show(val
), show(arr
));
418 if (arr
.subtypeOf(BVec
)) {
419 val
= vec_set(std::move(arr
), key
, val
, tag
).first
;
420 if (val
== TBottom
) val
= TVec
;
421 } else if (arr
.subtypeOf(BDict
)) {
422 val
= dict_set(std::move(arr
), key
, val
, tag
).first
;
423 if (val
== TBottom
) val
= TDict
;
424 } else if (arr
.subtypeOf(BKeyset
)) {
425 val
= keyset_set(std::move(arr
), key
, val
).first
;
426 if (val
== TBottom
) val
= TKeyset
;
428 assert(arr
.subtypeOf(BArr
));
429 val
= array_set(std::move(arr
), key
, val
, tag
).first
;
431 } while (!env
.collect
.mInstrState
.arrayChain
.empty());
432 FTRACE(5, "{} = {}\n", prefix
, show(val
));
436 void updateBaseWithType(ISS
& env
,
438 LocalId firstKeyLoc
= NoLocalId
) {
439 FTRACE(6, " updateBaseWithType: {}\n", show(ty
));
441 auto const& base
= env
.collect
.mInstrState
.base
;
443 if (mustBeInLocal(base
)) {
444 setLocalForBase(env
, ty
, firstKeyLoc
);
447 if (mustBeInStack(base
)) {
448 return setStackForBase(env
, ty
);
451 if (couldBeInPrivateStatic(env
, base
)) setPrivateStaticForBase(env
, ty
);
452 if (couldBeInPublicStatic(base
)) setPublicStaticForBase(env
, ty
);
455 void startBase(ISS
& env
, Base base
) {
456 auto& oldState
= env
.collect
.mInstrState
;
457 assert(oldState
.base
.loc
== BaseLoc::None
);
458 assert(oldState
.arrayChain
.empty());
459 assert(isInitialBaseLoc(base
.loc
));
460 assert(!base
.type
.subtypeOf(TBottom
));
462 oldState
.noThrow
= !env
.flags
.wasPEI
;
463 oldState
.extraPop
= false;
464 oldState
.base
= std::move(base
);
465 FTRACE(5, " startBase: {}\n", show(*env
.ctx
.func
, oldState
.base
));
468 void endBase(ISS
& env
, bool update
= true, LocalId keyLoc
= NoLocalId
) {
469 auto& state
= env
.collect
.mInstrState
;
470 assert(state
.base
.loc
!= BaseLoc::None
);
472 FTRACE(5, " endBase: {}\n", show(*env
.ctx
.func
, state
.base
));
474 auto const firstKeyLoc
= state
.arrayChain
.empty()
476 : state
.arrayChain
.data()->keyLoc
;
477 auto const& ty
= state
.arrayChain
.empty()
479 : resolveArrayChain(env
, state
.base
.type
);
481 if (update
) updateBaseWithType(env
, ty
, firstKeyLoc
);
482 state
.base
.loc
= BaseLoc::None
;
485 void moveBase(ISS
& env
,
488 LocalId keyLoc
= NoLocalId
) {
489 auto& state
= env
.collect
.mInstrState
;
490 assert(state
.base
.loc
!= BaseLoc::None
);
491 assert(isDimBaseLoc(newBase
.loc
));
492 assert(!state
.base
.type
.subtypeOf(BBottom
));
494 FTRACE(5, " moveBase: {} -> {}\n",
495 show(*env
.ctx
.func
, state
.base
),
496 show(*env
.ctx
.func
, newBase
));
498 if (newBase
.loc
== BaseLoc::Elem
) {
501 loosen_staticness(loosen_values(state
.base
.type
))
505 auto const firstKeyLoc
= state
.arrayChain
.empty()
507 : state
.arrayChain
.data()->keyLoc
;
508 auto const& ty
= state
.arrayChain
.empty()
510 : resolveArrayChain(env
, state
.base
.type
);
512 if (update
) updateBaseWithType(env
, ty
, firstKeyLoc
);
513 state
.base
= std::move(newBase
);
516 void extendArrChain(ISS
& env
, Type key
, Type arr
,
517 Type val
, bool update
= true,
518 LocalId keyLoc
= NoLocalId
) {
519 auto& state
= env
.collect
.mInstrState
;
520 assert(state
.base
.loc
!= BaseLoc::None
);
521 assert(mustBeArrLike(arr
));
522 assert(!state
.base
.type
.subtypeOf(BBottom
));
523 assert(!val
.subtypeOf(BBottom
));
525 state
.arrayChain
.emplace_back(
526 CollectedInfo::MInstrState::ArrayChainEnt
{std::move(arr
), std::move(key
), keyLoc
}
528 state
.base
.type
= std::move(val
);
530 auto const firstKeyLoc
= state
.arrayChain
.data()->keyLoc
;
532 FTRACE(5, " extendArrChain: {}\n", show(*env
.ctx
.func
, state
));
536 currentChainType(env
, state
.base
.type
),
542 //////////////////////////////////////////////////////////////////////
545 * The following promoteBase{Elem,Prop}* functions are used to implement the
546 * 'normal' portion of the effects on base types, which are mostly what are done
547 * by intermediate dims.
549 * The contract with these functions is that they should handle all the effects
550 * on the base type /except/ for the case of the base being an array
551 * subtype---the caller is responsible for that. The reason for this is that for
552 * tracking effects on specialized array types, the final ops generally need to
553 * do completely different things to the array, so this allows reuse of this
554 * shared part of the type transitions. The intermediate routines must handle
555 * array subtypes outside of calls to this as well.
558 void promoteBaseElemU(ISS
& env
) {
559 // We're conservative with unsets on array types for now.
560 env
.collect
.mInstrState
.base
.type
= loosen_all(env
.collect
.mInstrState
.base
.type
);
563 void promoteBasePropD(ISS
& env
, bool isNullsafe
) {
564 auto& ty
= env
.collect
.mInstrState
.base
.type
;
566 // NullSafe (Q) props do not promote an emptyish base to stdClass instance.
567 if (isNullsafe
|| ty
.subtypeOf(BObj
)) return;
569 if (propMustPromoteToObj(ty
)) {
570 ty
= objExact(env
.index
.builtin_class(s_stdClass
.get()));
573 if (propCouldPromoteToObj(ty
)) {
574 ty
= promote_emptyish(ty
, TObj
);
579 void promoteBaseElemD(ISS
& env
) {
580 auto& ty
= env
.collect
.mInstrState
.base
.type
;
582 // When the base is actually a subtype of array, we handle it in the callers
583 // of these functions.
584 if (mustBeArrLike(ty
)) return;
586 if (elemMustPromoteToArr(ty
)) {
591 // Intermediate ElemD operations on strings fatal, unless the string is empty,
592 // which promotes to array. So for any string here we can assume it promoted
593 // to an empty array.
594 if (ty
.subtypeOf(BStr
)) {
599 if (elemCouldPromoteToArr(ty
)) {
600 ty
= promote_emptyish(ty
, some_aempty());
605 * If the base still could be some kind of array (but isn't an array sub-type
606 * which would be handled outside this routine), we need to give up on any
607 * better information here (or track the effects, but we're not doing that
610 ty
= loosen_arrays(ty
);
613 void promoteBaseNewElem(ISS
& env
) {
614 promoteBaseElemD(env
);
615 // Technically we don't need to do TStr case.
618 //////////////////////////////////////////////////////////////////////
620 void handleInPublicStaticElemD(ISS
& env
) {
621 auto const& base
= env
.collect
.mInstrState
.base
;
622 if (!couldBeInPublicStatic(base
)) return;
624 auto const name
= baseLocNameType(base
);
625 auto const ty
= env
.index
.lookup_public_static(env
.ctx
, base
.locTy
, name
);
626 if (elemCouldPromoteToArr(ty
)) {
627 // Might be possible to only merge a TArrE, but for now this is ok.
628 env
.collect
.publicSPropMutations
.merge(
629 env
.index
, env
.ctx
, base
.locTy
, name
, TArr
634 void handleInThisElemD(ISS
& env
) {
635 if (!couldBeInProp(env
, env
.collect
.mInstrState
.base
)) return;
637 if (auto const name
= env
.collect
.mInstrState
.base
.locName
) {
638 auto const ty
= thisPropAsCell(env
, name
);
639 if (ty
&& elemCouldPromoteToArr(*ty
)) {
640 mergeThisProp(env
, name
, TArr
);
645 mergeEachThisPropRaw(env
, [&] (const Type
& t
) {
646 return elemCouldPromoteToArr(t
) ? TArr
: TBottom
;
650 void handleInPublicStaticPropD(ISS
& env
, bool isNullsafe
) {
651 // NullSafe (Q) props do not promote an emptyish base to stdClass instance.
652 if (isNullsafe
) return;
654 auto const& base
= env
.collect
.mInstrState
.base
;
655 if (!couldBeInPublicStatic(base
)) return;
657 auto const name
= baseLocNameType(base
);
658 auto const ty
= env
.index
.lookup_public_static(env
.ctx
, base
.locTy
, name
);
659 if (propCouldPromoteToObj(ty
)) {
660 env
.collect
.publicSPropMutations
.merge(
661 env
.index
, env
.ctx
, base
.locTy
, name
,
662 objExact(env
.index
.builtin_class(s_stdClass
.get()))
667 void handleInThisPropD(ISS
& env
, bool isNullsafe
) {
668 // NullSafe (Q) props do not promote an emptyish base to stdClass instance.
669 if (isNullsafe
) return;
671 if (!couldBeInProp(env
, env
.collect
.mInstrState
.base
)) return;
673 if (auto const name
= env
.collect
.mInstrState
.base
.locName
) {
674 auto const ty
= thisPropAsCell(env
, name
);
675 if (ty
&& propCouldPromoteToObj(*ty
)) {
676 mergeThisProp(env
, name
,
677 objExact(env
.index
.builtin_class(s_stdClass
.get())));
682 if (RuntimeOption::EvalPromoteEmptyObject
) {
683 mergeEachThisPropRaw(env
, [&] (const Type
& t
) {
684 return propCouldPromoteToObj(t
) ? TObj
: TBottom
;
689 // Currently NewElem and Elem InFoo effects don't need to do
690 // anything different from each other.
691 void handleInPublicStaticNewElem(ISS
& env
) { handleInPublicStaticElemD(env
); }
692 void handleInThisNewElem(ISS
& env
) { handleInThisElemD(env
); }
694 void handleInPublicStaticElemU(ISS
& env
) {
695 auto const& base
= env
.collect
.mInstrState
.base
;
696 if (!couldBeInPublicStatic(base
)) return;
699 * Merging InitCell is correct, but very conservative, for now.
701 auto const name
= baseLocNameType(base
);
702 env
.collect
.publicSPropMutations
.merge(
703 env
.index
, env
.ctx
, base
.locTy
, name
, TInitCell
707 //////////////////////////////////////////////////////////////////////
709 // Returns nullptr if it's an unknown key or not a string.
710 SString
mStringKey(const Type
& key
) {
711 auto const v
= tv(key
);
712 return v
&& v
->m_type
== KindOfPersistentString
? v
->m_data
.pstr
: nullptr;
715 template<typename Op
>
716 auto update_mkey(const Op
& op
) { return false; }
718 template<typename Op
>
719 auto update_mkey(Op
& op
) -> decltype(op
.mkey
, true) {
720 switch (op
.mkey
.mcode
) {
721 case MEC
: case MPC
: {
730 template<typename Op
>
731 auto update_discard(const Op
& op
) { return false; }
733 template<typename Op
>
734 auto update_discard(Op
& op
) -> decltype(op
.arg1
, true) {
740 * Return the type of the key, or reduce op, and return folly::none.
741 * Note that when folly::none is returned, there is nothing further to
744 template<typename Op
>
745 folly::Optional
<Type
> key_type_or_fixup(ISS
& env
, Op op
) {
746 if (env
.collect
.mInstrState
.extraPop
) {
747 auto const mkey
= update_mkey(op
);
748 if (update_discard(op
) || mkey
) {
749 env
.collect
.mInstrState
.extraPop
= false;
751 env
.collect
.mInstrState
.extraPop
= true;
755 auto fixup
= [&] (Type ty
, bool isProp
) -> folly::Optional
<Type
> {
756 if (auto const val
= tv(ty
)) {
757 if (isStringType(val
->m_type
)) {
758 op
.mkey
.mcode
= isProp
? MPT
: MET
;
759 op
.mkey
.litstr
= val
->m_data
.pstr
;
763 if (!isProp
&& val
->m_type
== KindOfInt64
) {
765 op
.mkey
.int64
= val
->m_data
.num
;
770 return std::move(ty
);
772 switch (op
.mkey
.mcode
) {
774 return fixup(topC(env
, op
.mkey
.idx
), op
.mkey
.mcode
== MPC
);
776 return fixup(locAsCell(env
, op
.mkey
.local
), op
.mkey
.mcode
== MPL
);
780 return ival(op
.mkey
.int64
);
781 case MET
: case MPT
: case MQT
:
782 return sval(op
.mkey
.litstr
);
787 template<typename Op
>
788 LocalId
key_local(ISS
& env
, Op op
) {
789 switch (op
.mkey
.mcode
) {
791 return topStkLocal(env
, op
.mkey
.idx
);
793 return op
.mkey
.local
;
796 case MET
: case MPT
: case MQT
:
802 //////////////////////////////////////////////////////////////////////
805 Base
miBaseLocal(ISS
& env
, LocalId locBase
, MOpMode mode
) {
806 auto const locName
= env
.ctx
.func
->locals
[locBase
].name
;
807 auto const isDefine
= mode
== MOpMode::Define
;
808 if (mode
== MOpMode::None
||
809 (mode
== MOpMode::Warn
&& !locCouldBeUninit(env
, locBase
))) {
812 // If we're changing the local to define it, we don't need to do an miThrow
813 // yet---the promotions (to array or stdClass) on previously uninitialized
814 // locals happen before raising warnings that could throw, so we can wait
815 // until the first moveBase.
816 return Base
{ isDefine
? locAsCell(env
, locBase
) : derefLoc(env
, locBase
),
823 Base
miBaseSProp(ISS
& env
, Type cls
, const Type
& tprop
) {
824 auto const self
= selfCls(env
);
825 auto const prop
= tv(tprop
);
826 auto const name
= prop
&& prop
->m_type
== KindOfPersistentString
827 ? prop
->m_data
.pstr
: nullptr;
828 if (self
&& cls
.subtypeOf(*self
) && name
) {
829 if (auto ty
= selfPropAsCell(env
, name
)) {
831 Base
{ std::move(*ty
), BaseLoc::StaticProp
, std::move(cls
), name
};
834 auto indexTy
= env
.index
.lookup_public_static(env
.ctx
, cls
, tprop
);
835 if (!indexTy
.subtypeOf(BInitCell
)) indexTy
= TInitCell
;
836 return Base
{ std::move(indexTy
),
842 //////////////////////////////////////////////////////////////////////
845 void miProp(ISS
& env
, bool isNullsafe
, MOpMode mode
, Type key
) {
846 auto const name
= mStringKey(key
);
847 auto const isDefine
= mode
== MOpMode::Define
;
848 auto const isUnset
= mode
== MOpMode::Unset
;
849 // PHP5 doesn't modify an unset local if you unset a property or
850 // array elem on it, but hhvm does (it promotes it to init-null).
851 auto const update
= isDefine
|| isUnset
;
853 * MOpMode::Unset Props doesn't promote "emptyish" things to stdClass, or
854 * affect arrays, however it can define a property on an object base. This
855 * means we don't need any couldBeInFoo logic, but if the base could actually
856 * be $this, and a declared property could be Uninit, we need to merge
859 * We're trying to handle this case correctly as far as the type inference
860 * here is concerned, but the runtime doesn't actually behave this way right
861 * now for declared properties. Note that it never hurts to merge more types
862 * than a thisProp could actually be, so this is fine.
864 * See TODO(#3602740): unset with intermediate dims on previously declared
865 * properties doesn't define them to null.
867 if (isUnset
&& couldBeThisObj(env
, env
.collect
.mInstrState
.base
)) {
869 auto const elem
= thisPropRaw(env
, name
);
870 if (elem
&& elem
->ty
.couldBe(BUninit
)) {
871 mergeThisProp(env
, name
, TInitNull
);
874 mergeEachThisPropRaw(env
, [&] (const Type
& ty
) {
875 return ty
.couldBe(BUninit
) ? TInitNull
: TBottom
;
881 handleInThisPropD(env
, isNullsafe
);
882 handleInPublicStaticPropD(env
, isNullsafe
);
883 promoteBasePropD(env
, isNullsafe
);
886 if (mustBeThisObj(env
, env
.collect
.mInstrState
.base
)) {
887 auto const optThisTy
= thisTypeFromContext(env
.index
, env
.ctx
);
888 auto const thisTy
= optThisTy
? *optThisTy
: TObj
;
890 auto const ty
= [&] {
891 if (auto const propTy
= thisPropAsCell(env
, name
)) return *propTy
;
893 env
.index
.lookup_public_prop(objcls(thisTy
), sval(name
))
897 if (ty
.subtypeOf(BBottom
)) return unreachable(env
);
900 Base
{ ty
, BaseLoc::Prop
, thisTy
, name
},
905 Base
{ TInitCell
, BaseLoc::Prop
, thisTy
},
911 // We know for sure we're going to be in an object property.
912 if (env
.collect
.mInstrState
.base
.type
.subtypeOf(BObj
)) {
913 auto const ty
= to_cell(
914 env
.index
.lookup_public_prop(
915 objcls(env
.collect
.mInstrState
.base
.type
),
916 name
? sval(name
) : TStr
919 if (ty
.subtypeOf(BBottom
)) return unreachable(env
);
923 env
.collect
.mInstrState
.base
.type
,
930 * Otherwise, intermediate props with define can promote a null, false, or ""
931 * to stdClass. Those cases, and others, if it's MOpMode::Define, will set
932 * the base to a null value in tvScratch. The base may also legitimately be
933 * an object and our next base is in an object property. Conservatively treat
934 * all these cases as "possibly" being inside of an object property with
935 * "Prop" with locType TTop.
938 Base
{ TInitCell
, BaseLoc::Prop
, TTop
, name
},
942 void miElem(ISS
& env
, MOpMode mode
, Type key
, LocalId keyLoc
) {
943 auto const isDefine
= mode
== MOpMode::Define
;
944 auto const isUnset
= mode
== MOpMode::Unset
;
945 auto const update
= isDefine
|| isUnset
;
949 * Elem dims with MOpMode::Unset can change a base from a static array into
950 * a reference counted array. It never promotes emptyish types, however.
952 handleInPublicStaticElemU(env
);
953 promoteBaseElemU(env
);
955 if (auto ty
= array_do_elem(env
, false, key
)) {
956 if (ty
->subtypeOf(BBottom
)) return unreachable(env
);
960 std::move(*ty
), BaseLoc::Elem
,
961 env
.collect
.mInstrState
.base
.type
971 handleInThisElemD(env
);
972 handleInPublicStaticElemD(env
);
973 promoteBaseElemD(env
);
976 if (auto ty
= array_do_elem(env
, mode
== MOpMode::None
, key
)) {
977 if (ty
->subtypeOf(BBottom
)) return unreachable(env
);
979 env
, std::move(key
), env
.collect
.mInstrState
.base
.type
,
987 if (env
.collect
.mInstrState
.base
.type
.subtypeOf(BStr
)) {
988 moveBase(env
, Base
{ TStr
, BaseLoc::Elem
}, update
);
993 * Other cases could leave the base as anything (if nothing else, via
994 * ArrayAccess on an object).
996 * The resulting BaseLoc is either inside an array, is the global
997 * init_null_variant, or inside tvScratch. We represent this with the
998 * Elem base location with locType TTop.
1000 moveBase(env
, Base
{ TInitCell
, BaseLoc::Elem
, TTop
}, update
, keyLoc
);
1003 void miNewElem(ISS
& env
) {
1004 handleInThisNewElem(env
);
1005 handleInPublicStaticNewElem(env
);
1006 promoteBaseNewElem(env
);
1008 if (auto kty
= array_do_newelem(env
, TInitNull
)) {
1011 std::move(env
.collect
.mInstrState
.base
.type
),
1014 moveBase(env
, Base
{ TInitCell
, BaseLoc::Elem
, TTop
});
1018 //////////////////////////////////////////////////////////////////////
1021 void miFinalIssetProp(ISS
& env
, int32_t nDiscard
, const Type
& key
) {
1022 auto const name
= mStringKey(key
);
1023 discard(env
, nDiscard
);
1024 if (name
&& mustBeThisObj(env
, env
.collect
.mInstrState
.base
)) {
1025 if (auto const pt
= thisPropAsCell(env
, name
)) {
1026 if (isMaybeLateInitThisProp(env
, name
)) {
1027 // LateInit props can always be maybe unset, except if its never set at
1029 return push(env
, pt
->subtypeOf(BBottom
) ? TFalse
: TBool
);
1031 if (pt
->subtypeOf(BNull
)) return push(env
, TFalse
);
1032 if (!pt
->couldBe(BNull
)) return push(env
, TTrue
);
1038 void miFinalCGetProp(ISS
& env
, int32_t nDiscard
, const Type
& key
) {
1039 auto const name
= mStringKey(key
);
1040 discard(env
, nDiscard
);
1042 auto const ty
= [&] {
1044 if (mustBeThisObj(env
, env
.collect
.mInstrState
.base
)) {
1045 if (auto const t
= thisPropAsCell(env
, name
)) return *t
;
1048 env
.index
.lookup_public_prop(
1049 objcls(env
.collect
.mInstrState
.base
.type
), sval(name
)
1055 if (ty
.subtypeOf(BBottom
)) unreachable(env
);
1059 void miFinalSetProp(ISS
& env
, int32_t nDiscard
, const Type
& key
) {
1060 auto const name
= mStringKey(key
);
1061 auto const t1
= unctx(popC(env
));
1063 auto const finish
= [&](Type ty
) {
1065 discard(env
, nDiscard
);
1066 push(env
, std::move(ty
));
1069 handleInThisPropD(env
, false);
1070 handleInPublicStaticPropD(env
, false);
1071 promoteBasePropD(env
, false);
1073 if (couldBeThisObj(env
, env
.collect
.mInstrState
.base
)) {
1075 mergeEachThisPropRaw(
1078 return propTy
.couldBe(BInitCell
) ? t1
: TBottom
;
1082 mergeThisProp(env
, name
, t1
);
1086 if (env
.collect
.mInstrState
.base
.type
.subtypeOf(BObj
)) {
1087 if (t1
.subtypeOf(BBottom
)) return unreachable(env
);
1090 Base
{ t1
, BaseLoc::Prop
, env
.collect
.mInstrState
.base
.type
, name
}
1095 moveBase(env
, Base
{ TInitCell
, BaseLoc::Prop
, TTop
, name
});
1096 return finish(TInitCell
);
1099 void miFinalSetOpProp(ISS
& env
, int32_t nDiscard
,
1100 SetOpOp subop
, const Type
& key
) {
1101 auto const name
= mStringKey(key
);
1102 auto const rhsTy
= popC(env
);
1104 handleInThisPropD(env
, false);
1105 handleInPublicStaticPropD(env
, false);
1106 promoteBasePropD(env
, false);
1108 auto const lhsTy
= [&] {
1110 if (mustBeThisObj(env
, env
.collect
.mInstrState
.base
)) {
1111 if (auto const t
= thisPropAsCell(env
, name
)) return *t
;
1114 env
.index
.lookup_public_prop(
1115 objcls(env
.collect
.mInstrState
.base
.type
), sval(name
)
1122 auto const resultTy
= env
.collect
.mInstrState
.base
.type
.subtypeOf(TObj
)
1123 ? typeSetOp(subop
, lhsTy
, rhsTy
)
1125 if (couldBeThisObj(env
, env
.collect
.mInstrState
.base
)) {
1127 mergeThisProp(env
, name
, resultTy
);
1129 loseNonRefThisPropTypes(env
);
1134 discard(env
, nDiscard
);
1135 push(env
, resultTy
);
1138 void miFinalIncDecProp(ISS
& env
, int32_t nDiscard
,
1139 IncDecOp subop
, const Type
& key
) {
1140 auto const name
= mStringKey(key
);
1142 handleInThisPropD(env
, false);
1143 handleInPublicStaticPropD(env
, false);
1144 promoteBasePropD(env
, false);
1146 auto const postPropTy
= [&] {
1148 if (mustBeThisObj(env
, env
.collect
.mInstrState
.base
)) {
1149 if (auto const t
= thisPropAsCell(env
, name
)) return *t
;
1152 env
.index
.lookup_public_prop(
1153 objcls(env
.collect
.mInstrState
.base
.type
), sval(name
)
1159 auto const prePropTy
= env
.collect
.mInstrState
.base
.type
.subtypeOf(TObj
)
1160 ? typeIncDec(subop
, postPropTy
)
1163 if (couldBeThisObj(env
, env
.collect
.mInstrState
.base
)) {
1165 mergeThisProp(env
, name
, prePropTy
);
1167 loseNonRefThisPropTypes(env
);
1172 discard(env
, nDiscard
);
1173 push(env
, isPre(subop
) ? prePropTy
: postPropTy
);
1176 void miFinalUnsetProp(ISS
& env
, int32_t nDiscard
, const Type
& key
) {
1177 auto const name
= mStringKey(key
);
1180 * Unset does define intermediate dims but with slightly different
1181 * rules than sets. It only applies to object properties.
1183 * Note that this can't affect self props, because static
1184 * properties can never be unset. It also can't change anything
1185 * about an inner array type.
1187 handleInThisPropD(env
, false);
1189 if (couldBeThisObj(env
, env
.collect
.mInstrState
.base
)) {
1191 unsetThisProp(env
, name
);
1193 unsetUnknownThisProp(env
);
1198 discard(env
, nDiscard
);
1201 //////////////////////////////////////////////////////////////////////
1204 // This is a helper for final defining Elem operations that need to
1205 // handle array chains and frame effects, but don't yet do anything
1206 // better than supplying a single type.
1207 void pessimisticFinalElemD(ISS
& env
, const Type
& key
, const Type
& ty
) {
1208 array_do_set(env
, key
, ty
);
1211 template<typename F
>
1212 void miFinalCGetElem(ISS
& env
, int32_t nDiscard
,
1213 const Type
& key
, bool nullOnMissing
,
1216 if (auto type
= array_do_elem(env
, nullOnMissing
, key
)) {
1217 return std::move(*type
);
1221 discard(env
, nDiscard
);
1222 push(env
, transform(std::move(ty
)));
1225 void miFinalSetElem(ISS
& env
,
1229 auto const t1
= popC(env
);
1231 handleInThisElemD(env
);
1232 handleInPublicStaticElemD(env
);
1234 auto const finish
= [&](Type ty
) {
1235 endBase(env
, true, keyLoc
);
1236 discard(env
, nDiscard
);
1237 push(env
, std::move(ty
));
1240 // Note: we must handle the string-related cases before doing the
1241 // general handleBaseElemD, since operates on strings as if this
1242 // was an intermediate ElemD.
1243 if (env
.collect
.mInstrState
.base
.type
.subtypeOf(sempty())) {
1244 env
.collect
.mInstrState
.base
.type
= some_aempty();
1246 auto& ty
= env
.collect
.mInstrState
.base
.type
;
1247 if (ty
.couldBe(BStr
)) {
1248 // Note here that a string type stays a string (with a changed character,
1249 // and loss of staticness), unless it was the empty string, where it
1250 // becomes an array. Do it conservatively for now:
1252 loosen_staticness(loosen_values(std::move(ty
))),
1256 if (!ty
.subtypeOf(BStr
)) promoteBaseElemD(env
);
1260 * In some unusual cases with illegal keys, SetM pushes null
1261 * instead of the right hand side.
1263 * There are also some special cases for SetM for different base types:
1264 * 1. If the base is a string, SetM pushes a new string with the
1265 * value of the first character of the right hand side converted
1266 * to a string (or something like that).
1267 * 2. If the base is a primitive type, SetM pushes null.
1268 * 3. If the base is an object, and it does not implement ArrayAccess,
1269 * it is still ok to push the right hand side, because it is a
1272 * We push the right hand side on the stack only if the base is an
1273 * array, object or emptyish.
1275 if (array_do_set(env
, key
, t1
)) {
1276 if (env
.state
.unreachable
) return finish(TBottom
);
1277 auto const maybeWeird
=
1278 env
.collect
.mInstrState
.base
.type
.subtypeOf(BArr
) && keyCouldBeWeird(key
);
1279 return finish(maybeWeird
? union_of(t1
, TInitNull
) : t1
);
1282 // ArrayAccess on $this will always push the rhs, even if things
1284 if (mustBeThisObj(env
, env
.collect
.mInstrState
.base
)) return finish(t1
);
1286 auto const isWeird
=
1287 keyCouldBeWeird(key
) ||
1288 (!mustBeEmptyish(env
.collect
.mInstrState
.base
.type
) &&
1289 !env
.collect
.mInstrState
.base
.type
.subtypeOf(BObj
));
1290 finish(isWeird
? TInitCell
: t1
);
1293 void miFinalSetOpElem(ISS
& env
, int32_t nDiscard
,
1294 SetOpOp subop
, const Type
& key
,
1296 auto const rhsTy
= popC(env
);
1297 handleInThisElemD(env
);
1298 handleInPublicStaticElemD(env
);
1299 promoteBaseElemD(env
);
1300 auto const lhsTy
= [&] {
1301 if (auto ty
= array_do_elem(env
, false, key
)) {
1302 if (env
.state
.unreachable
) return TBottom
;
1303 assertx(!ty
->subtypeOf(BBottom
));
1304 return std::move(*ty
);
1308 auto const resultTy
= typeSetOp(subop
, lhsTy
, rhsTy
);
1309 pessimisticFinalElemD(env
, key
, resultTy
);
1310 endBase(env
, true, keyLoc
);
1311 discard(env
, nDiscard
);
1312 push(env
, resultTy
);
1315 void miFinalIncDecElem(ISS
& env
, int32_t nDiscard
,
1316 IncDecOp subop
, const Type
& key
,
1318 handleInThisElemD(env
);
1319 handleInPublicStaticElemD(env
);
1320 promoteBaseElemD(env
);
1321 auto const postTy
= [&] {
1322 if (auto ty
= array_do_elem(env
, false, key
)) {
1323 if (env
.state
.unreachable
) return TBottom
;
1324 assertx(!ty
->subtypeOf(BBottom
));
1325 return std::move(*ty
);
1329 auto const preTy
= typeIncDec(subop
, postTy
);
1330 pessimisticFinalElemD(env
, key
, preTy
);
1331 endBase(env
, true, keyLoc
);
1332 discard(env
, nDiscard
);
1333 push(env
, isPre(subop
) ? preTy
: postTy
);
1336 void miFinalUnsetElem(ISS
& env
, int32_t nDiscard
, const Type
&) {
1337 handleInPublicStaticElemU(env
);
1338 promoteBaseElemU(env
);
1339 // We don't handle inner-array types with unset yet.
1340 always_assert(env
.collect
.mInstrState
.arrayChain
.empty());
1341 auto const& ty
= env
.collect
.mInstrState
.base
.type
;
1342 always_assert(!ty
.strictSubtypeOf(TArr
) && !ty
.strictSubtypeOf(TVec
) &&
1343 !ty
.strictSubtypeOf(TDict
) && !ty
.strictSubtypeOf(TKeyset
));
1345 discard(env
, nDiscard
);
1348 //////////////////////////////////////////////////////////////////////
1349 // Final new elem ops
1351 // This is a helper for final defining Elem operations that need to handle
1352 // array chains and frame effects, but don't yet do anything better than
1353 // supplying a single type.
1354 void pessimisticFinalNewElem(ISS
& env
, const Type
& type
) {
1355 array_do_newelem(env
, type
);
1358 void miFinalSetNewElem(ISS
& env
, int32_t nDiscard
) {
1359 auto const t1
= popC(env
);
1361 handleInThisNewElem(env
);
1362 handleInPublicStaticNewElem(env
);
1363 promoteBaseNewElem(env
);
1365 auto const finish
= [&](Type ty
) {
1367 discard(env
, nDiscard
);
1368 push(env
, std::move(ty
));
1371 if (array_do_newelem(env
, t1
)) {
1375 // ArrayAccess on $this will always push the rhs.
1376 if (mustBeThisObj(env
, env
.collect
.mInstrState
.base
)) return finish(t1
);
1378 // TODO(#3343813): we should push the type of the rhs when we can;
1379 // SetM for a new elem still has some weird cases where it pushes
1380 // null instead to handle. (E.g. if the base is a number.)
1384 void miFinalSetOpNewElem(ISS
& env
, int32_t nDiscard
) {
1386 handleInThisNewElem(env
);
1387 handleInPublicStaticNewElem(env
);
1388 promoteBaseNewElem(env
);
1389 pessimisticFinalNewElem(env
, TInitCell
);
1391 discard(env
, nDiscard
);
1392 push(env
, TInitCell
);
1395 void miFinalIncDecNewElem(ISS
& env
, int32_t nDiscard
) {
1396 handleInThisNewElem(env
);
1397 handleInPublicStaticNewElem(env
);
1398 promoteBaseNewElem(env
);
1399 pessimisticFinalNewElem(env
, TInitCell
);
1401 discard(env
, nDiscard
);
1402 push(env
, TInitCell
);
1407 namespace interp_step
{
1409 //////////////////////////////////////////////////////////////////////
1412 void in(ISS
& env
, const bc::BaseGC
& op
) {
1414 startBase(env
, Base
{TInitCell
, BaseLoc::Global
});
1417 void in(ISS
& env
, const bc::BaseGL
& op
) {
1418 locAsCell(env
, op
.loc1
);
1419 startBase(env
, Base
{TInitCell
, BaseLoc::Global
});
1422 void in(ISS
& env
, const bc::BaseSC
& op
) {
1423 auto cls
= topC(env
, op
.arg2
);
1424 auto prop
= topC(env
, op
.arg1
);
1426 auto const vname
= tv(prop
);
1427 if (op
.subop3
== MOpMode::Define
|| op
.subop3
== MOpMode::Unset
||
1428 op
.subop3
== MOpMode::InOut
) {
1429 if (vname
&& vname
->m_type
== KindOfPersistentString
&&
1430 canSkipMergeOnConstProp(env
, cls
, vname
->m_data
.pstr
)) {
1435 auto newBase
= miBaseSProp(env
, std::move(cls
), prop
);
1436 if (newBase
.type
.subtypeOf(BBottom
)) return unreachable(env
);
1437 startBase(env
, std::move(newBase
));
1440 void in(ISS
& env
, const bc::BaseL
& op
) {
1441 auto newBase
= miBaseLocal(env
, op
.loc1
, op
.subop2
);
1442 if (newBase
.type
.subtypeOf(BBottom
)) return unreachable(env
);
1443 startBase(env
, std::move(newBase
));
1446 void in(ISS
& env
, const bc::BaseC
& op
) {
1447 assert(op
.arg1
< env
.state
.stack
.size());
1448 auto ty
= topC(env
, op
.arg1
);
1449 if (ty
.subtypeOf(BBottom
)) return unreachable(env
);
1459 (uint32_t)env
.state
.stack
.size() - op
.arg1
- 1
1464 void in(ISS
& env
, const bc::BaseH
&) {
1465 auto const ty
= thisTypeNonNull(env
);
1466 if (ty
.subtypeOf(BBottom
)) return unreachable(env
);
1468 startBase(env
, Base
{ty
, BaseLoc::This
});
1471 //////////////////////////////////////////////////////////////////////
1472 // Intermediate operations
1474 void in(ISS
& env
, const bc::Dim
& op
) {
1475 auto const key
= key_type_or_fixup(env
, op
);
1477 auto const keyLoc
= key_local(env
, op
);
1478 if (mcodeIsProp(op
.mkey
.mcode
)) {
1479 miProp(env
, op
.mkey
.mcode
== MQT
, op
.subop1
, *key
);
1480 } else if (mcodeIsElem(op
.mkey
.mcode
)) {
1481 miElem(env
, op
.subop1
, *key
, keyLoc
);
1485 if (env
.flags
.wasPEI
) env
.collect
.mInstrState
.noThrow
= false;
1486 if (env
.collect
.mInstrState
.noThrow
&&
1487 (op
.subop1
== MOpMode::None
|| op
.subop1
== MOpMode::Warn
) &&
1489 is_scalar(env
.collect
.mInstrState
.base
.type
)) {
1490 for (int i
= 0; ; i
++) {
1491 auto const last
= last_op(env
, i
);
1493 if (isMemberBaseOp(last
->op
)) {
1494 auto const base
= *last
;
1496 auto const reuseStack
=
1499 case Op::BaseGC
: return base
.BaseGC
.arg1
== 0;
1500 case Op::BaseC
: return base
.BaseC
.arg1
== 0;
1501 default: return false;
1504 assertx(!env
.collect
.mInstrState
.extraPop
|| reuseStack
);
1505 auto const extraPop
= !reuseStack
|| env
.collect
.mInstrState
.extraPop
;
1506 env
.collect
.mInstrState
.clear();
1507 if (reuseStack
) reduce(env
, bc::PopC
{});
1508 auto const v
= tv(env
.collect
.mInstrState
.base
.type
);
1510 reduce(env
, gen_constant(*v
), bc::BaseC
{ 0, op
.subop1
});
1511 env
.collect
.mInstrState
.extraPop
= extraPop
;
1514 if (!isMemberDimOp(last
->op
)) break;
1519 //////////////////////////////////////////////////////////////////////
1522 const StaticString
s_classname("classname");
1523 const StaticString
s_type_structure("HH\\type_structure");
1524 const StaticString
s_type_structure_classname("HH\\type_structure_classname");
1526 void in(ISS
& env
, const bc::QueryM
& op
) {
1527 auto const key
= key_type_or_fixup(env
, op
);
1529 auto const nDiscard
= op
.arg1
;
1531 if (mcodeIsProp(op
.mkey
.mcode
)) {
1532 // We don't currently do anything different for nullsafe query ops.
1533 switch (op
.subop2
) {
1534 case QueryMOp::CGet
:
1535 case QueryMOp::CGetQuiet
:
1536 miFinalCGetProp(env
, nDiscard
, *key
);
1538 case QueryMOp::Isset
:
1539 miFinalIssetProp(env
, nDiscard
, *key
);
1541 case QueryMOp::Empty
:
1542 discard(env
, nDiscard
);
1545 case QueryMOp::InOut
:
1546 always_assert(false);
1548 } else if (mcodeIsElem(op
.mkey
.mcode
)) {
1549 switch (op
.subop2
) {
1550 case QueryMOp::InOut
:
1551 case QueryMOp::CGet
:
1552 miFinalCGetElem(env
, nDiscard
, *key
, false,
1553 [](Type t
) { return t
; });
1555 case QueryMOp::CGetQuiet
:
1556 miFinalCGetElem(env
, nDiscard
, *key
, true,
1557 [](Type t
) { return t
; });
1559 case QueryMOp::Isset
:
1560 miFinalCGetElem(env
, nDiscard
, *key
, true,
1562 return t
.subtypeOf(BInitNull
) ? TFalse
:
1563 !t
.couldBe(BInitNull
) ? TTrue
: TBool
;
1566 case QueryMOp::Empty
:
1567 miFinalCGetElem(env
, nDiscard
, *key
, true,
1569 auto const e
= emptiness(t
);
1571 e
== Emptiness::Empty
? TTrue
:
1572 e
== Emptiness::NonEmpty
? TFalse
: TBool
;
1576 if (!env
.flags
.wasPEI
&&
1577 env
.collect
.mInstrState
.noThrow
&&
1578 is_scalar(topC(env
))) {
1579 for (int i
= 0; ; i
++) {
1580 auto const last
= last_op(env
, i
);
1582 if (isMemberBaseOp(last
->op
)) {
1583 auto const v
= tv(topC(env
));
1586 env
.collect
.mInstrState
.clear();
1587 BytecodeVec bcs
{nDiscard
, bc::PopC
{}};
1588 bcs
.push_back(gen_constant(*v
));
1589 return reduce(env
, std::move(bcs
));
1591 if (!isMemberDimOp(last
->op
)) break;
1595 // Try to detect type_structure(cls_name, cns_name)['classname'] and
1596 // reduce this to type_structure_classname(cls_name, cns_name)
1597 if (op
.subop2
== QueryMOp::CGet
&&
1599 op
.mkey
.mcode
== MemberCode::MET
&&
1600 op
.mkey
.litstr
->isame(s_classname
.get())) {
1601 if (auto const last
= last_op(env
, 0)) {
1602 if (last
->op
== Op::BaseC
) {
1603 if (auto const prev
= last_op(env
, 1)) {
1604 if (prev
->op
== Op::FCallBuiltin
&&
1605 prev
->FCallBuiltin
.str4
->isame(s_type_structure
.get()) &&
1606 prev
->FCallBuiltin
.arg1
== 2) {
1607 auto const params
= prev
->FCallBuiltin
.arg1
;
1608 auto const passed_params
= prev
->FCallBuiltin
.arg2
;
1609 rewind(env
, op
); // querym
1610 rewind(env
, 2); // basec + fcallbuiltin
1611 env
.collect
.mInstrState
.clear();
1618 s_type_structure_classname
.get()
1627 // QueryMNewElem will always throw without doing any work.
1628 discard(env
, nDiscard
);
1629 push(env
, TInitCell
);
1632 endBase(env
, false);
1635 void in(ISS
& env
, const bc::SetM
& op
) {
1636 auto const key
= key_type_or_fixup(env
, op
);
1638 auto const keyLoc
= key_local(env
, op
);
1639 if (mcodeIsProp(op
.mkey
.mcode
)) {
1640 miFinalSetProp(env
, op
.arg1
, *key
);
1641 } else if (mcodeIsElem(op
.mkey
.mcode
)) {
1642 miFinalSetElem(env
, op
.arg1
, *key
, keyLoc
);
1644 miFinalSetNewElem(env
, op
.arg1
);
1648 void in(ISS
& env
, const bc::SetRangeM
& op
) {
1649 auto const count
= popC(env
);
1650 auto const value
= popC(env
);
1651 auto const offset
= popC(env
);
1653 auto& base
= env
.collect
.mInstrState
.base
.type
;
1654 if (base
.couldBe(BStr
)) {
1655 base
= loosen_staticness(loosen_values(std::move(base
)));
1658 endBase(env
, true, NoLocalId
);
1659 discard(env
, op
.arg1
);
1662 void in(ISS
& env
, const bc::IncDecM
& op
) {
1663 auto const key
= key_type_or_fixup(env
, op
);
1665 auto const keyLoc
= key_local(env
, op
);
1666 if (mcodeIsProp(op
.mkey
.mcode
)) {
1667 miFinalIncDecProp(env
, op
.arg1
, op
.subop2
, *key
);
1668 } else if (mcodeIsElem(op
.mkey
.mcode
)) {
1669 miFinalIncDecElem(env
, op
.arg1
, op
.subop2
, *key
, keyLoc
);
1671 miFinalIncDecNewElem(env
, op
.arg1
);
1675 void in(ISS
& env
, const bc::SetOpM
& op
) {
1676 auto const key
= key_type_or_fixup(env
, op
);
1678 auto const keyLoc
= key_local(env
, op
);
1679 if (mcodeIsProp(op
.mkey
.mcode
)) {
1680 miFinalSetOpProp(env
, op
.arg1
, op
.subop2
, *key
);
1681 } else if (mcodeIsElem(op
.mkey
.mcode
)) {
1682 miFinalSetOpElem(env
, op
.arg1
, op
.subop2
, *key
, keyLoc
);
1684 miFinalSetOpNewElem(env
, op
.arg1
);
1688 void in(ISS
& env
, const bc::UnsetM
& op
) {
1689 auto const key
= key_type_or_fixup(env
, op
);
1691 if (mcodeIsProp(op
.mkey
.mcode
)) {
1692 miFinalUnsetProp(env
, op
.arg1
, *key
);
1694 assert(mcodeIsElem(op
.mkey
.mcode
));
1695 miFinalUnsetElem(env
, op
.arg1
, *key
);
1701 //////////////////////////////////////////////////////////////////////