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 res
= [&] () -> folly::Optional
<std::pair
<Type
,ThrowMode
>> {
208 if (base
.subtypeOf(BArr
)) return array_set(std::move(base
), key
, value
);
209 if (base
.subtypeOf(BVec
)) return vec_set(std::move(base
), key
, value
);
210 if (base
.subtypeOf(BDict
)) return dict_set(std::move(base
), key
, value
);
211 if (base
.subtypeOf(BKeyset
)) return keyset_set(std::move(base
), key
, value
);
214 if (!res
) return false;
216 switch (res
->second
) {
217 case ThrowMode::None
:
220 case ThrowMode::MaybeMissingElement
:
221 case ThrowMode::MissingElement
:
222 case ThrowMode::MaybeBadKey
:
223 case ThrowMode::BadOperation
:
227 if (res
->first
== TBottom
) {
231 base
= std::move(res
->first
);
236 * If the current base is an array-like, return the best known type
239 folly::Optional
<Type
> array_do_elem(ISS
& env
,
242 auto const& base
= env
.collect
.mInstrState
.base
.type
;
243 auto res
= [&] () -> folly::Optional
<std::pair
<Type
,ThrowMode
>> {
244 if (base
.subtypeOf(BArr
)) return array_elem(base
, key
);
245 if (base
.subtypeOf(BVec
)) return vec_elem(base
, key
);
246 if (base
.subtypeOf(BDict
)) return dict_elem(base
, key
);
247 if (base
.subtypeOf(BKeyset
)) return keyset_elem(base
, key
);
250 if (!res
) return folly::none
;
252 switch (res
->second
) {
253 case ThrowMode::None
:
256 case ThrowMode::MaybeMissingElement
:
257 case ThrowMode::MissingElement
:
260 res
->first
|= TInitNull
;
263 case ThrowMode::MaybeBadKey
:
265 res
->first
|= TInitNull
;
268 case ThrowMode::BadOperation
:
272 if (res
->first
== TBottom
) {
276 return std::move(res
->first
);
280 * If the current base is an array-like, update it via *_newelem, and
281 * return the best known type for the key added; otherwise return folly::none.
283 folly::Optional
<Type
> array_do_newelem(ISS
& env
, const Type
& value
) {
284 auto& base
= env
.collect
.mInstrState
.base
.type
;
285 auto res
= [&] () -> folly::Optional
<std::pair
<Type
,Type
>> {
286 if (base
.subtypeOf(BArr
)) return array_newelem(std::move(base
), value
);
287 if (base
.subtypeOf(BVec
)) return vec_newelem(std::move(base
), value
);
288 if (base
.subtypeOf(BDict
)) return dict_newelem(std::move(base
), value
);
289 if (base
.subtypeOf(BKeyset
)) return keyset_newelem(std::move(base
), value
);
292 if (!res
) return folly::none
;
293 base
= std::move(res
->first
);
297 //////////////////////////////////////////////////////////////////////
299 // MInstrs can throw in between each op, so the states of locals
300 // need to be propagated across throw exit edges.
301 void miThrow(ISS
& env
) {
302 if (env
.blk
.throwExit
!= NoBlockId
) {
303 auto const stackLess
= with_throwable_only(env
.index
, env
.state
);
304 env
.propagate(env
.blk
.throwExit
, &stackLess
);
308 //////////////////////////////////////////////////////////////////////
310 void setLocalForBase(ISS
& env
, Type ty
, LocalId firstKeyLoc
) {
311 assert(mustBeInLocal(env
.collect
.mInstrState
.base
));
312 if (env
.collect
.mInstrState
.base
.locLocal
== NoLocalId
) {
313 return loseNonRefLocalTypes(env
);
315 FTRACE(4, " ${} := {}\n",
316 env
.collect
.mInstrState
.base
.locName
317 ? env
.collect
.mInstrState
.base
.locName
->data()
323 env
.collect
.mInstrState
.base
.locLocal
,
329 void setStackForBase(ISS
& env
, Type ty
) {
330 assert(mustBeInStack(env
.collect
.mInstrState
.base
));
332 auto const locSlot
= env
.collect
.mInstrState
.base
.locSlot
;
333 FTRACE(4, " stk[{:02}] := {}\n", locSlot
, show(ty
));
334 assert(locSlot
< env
.state
.stack
.size());
336 auto const& oldTy
= env
.state
.stack
[locSlot
].type
;
337 if (oldTy
.subtypeOf(BInitCell
)) {
338 env
.state
.stack
[locSlot
] = StackElem
{ std::move(ty
), NoLocalId
};
342 void setPrivateStaticForBase(ISS
& env
, Type ty
) {
343 assert(couldBeInPrivateStatic(env
, env
.collect
.mInstrState
.base
));
345 if (auto const name
= env
.collect
.mInstrState
.base
.locName
) {
346 FTRACE(4, " self::${} |= {}\n", name
->data(), show(ty
));
347 mergeSelfProp(env
, name
, std::move(ty
));
350 FTRACE(4, " self::* |= {}\n", show(ty
));
351 mergeEachSelfPropRaw(
353 [&](const Type
& old
){ return old
.subtypeOf(BInitCell
) ? ty
: TBottom
; }
357 void setPublicStaticForBase(ISS
& env
, Type ty
) {
358 auto const& base
= env
.collect
.mInstrState
.base
;
359 assertx(couldBeInPublicStatic(base
));
361 auto const nameTy
= base
.locName
? sval(base
.locName
) : TStr
;
363 4, " public ({})::$({}) |= {}\n",
364 show(base
.locTy
), show(nameTy
), show(ty
)
366 env
.collect
.publicSPropMutations
.merge(
367 env
.index
, env
.ctx
, base
.locTy
, nameTy
, ty
371 // Run backwards through an array chain doing array_set operations
372 // to produce the array type that incorporates the effects of any
373 // intermediate defining dims.
374 Type
currentChainType(ISS
& env
, Type val
) {
375 auto it
= env
.collect
.mInstrState
.arrayChain
.end();
376 while (it
!= env
.collect
.mInstrState
.arrayChain
.begin()) {
378 if (it
->base
.subtypeOf(BArr
)) {
379 val
= array_set(it
->base
, it
->key
, val
).first
;
380 } else if (it
->base
.subtypeOf(BVec
)) {
381 val
= vec_set(it
->base
, it
->key
, val
).first
;
382 if (val
== TBottom
) val
= TVec
;
383 } else if (it
->base
.subtypeOf(BDict
)) {
384 val
= dict_set(it
->base
, it
->key
, val
).first
;
385 if (val
== TBottom
) val
= TDict
;
387 assert(it
->base
.subtypeOf(BKeyset
));
388 val
= keyset_set(it
->base
, it
->key
, val
).first
;
389 if (val
== TBottom
) val
= TKeyset
;
395 Type
resolveArrayChain(ISS
& env
, Type val
) {
396 static UNUSED
const char prefix
[] = " ";
397 FTRACE(5, "{}chain\n", prefix
, show(val
));
399 auto arr
= std::move(env
.collect
.mInstrState
.arrayChain
.back().base
);
400 auto key
= std::move(env
.collect
.mInstrState
.arrayChain
.back().key
);
401 env
.collect
.mInstrState
.arrayChain
.pop_back();
402 FTRACE(5, "{} | {} := {} in {}\n", prefix
,
403 show(key
), show(val
), show(arr
));
404 if (arr
.subtypeOf(BVec
)) {
405 val
= vec_set(std::move(arr
), key
, val
).first
;
406 if (val
== TBottom
) val
= TVec
;
407 } else if (arr
.subtypeOf(BDict
)) {
408 val
= dict_set(std::move(arr
), key
, val
).first
;
409 if (val
== TBottom
) val
= TDict
;
410 } else if (arr
.subtypeOf(BKeyset
)) {
411 val
= keyset_set(std::move(arr
), key
, val
).first
;
412 if (val
== TBottom
) val
= TKeyset
;
414 assert(arr
.subtypeOf(BArr
));
415 val
= array_set(std::move(arr
), key
, val
).first
;
417 } while (!env
.collect
.mInstrState
.arrayChain
.empty());
418 FTRACE(5, "{} = {}\n", prefix
, show(val
));
422 void updateBaseWithType(ISS
& env
,
424 LocalId firstKeyLoc
= NoLocalId
) {
425 FTRACE(6, " updateBaseWithType: {}\n", show(ty
));
427 auto const& base
= env
.collect
.mInstrState
.base
;
429 if (mustBeInLocal(base
)) {
430 setLocalForBase(env
, ty
, firstKeyLoc
);
433 if (mustBeInStack(base
)) {
434 return setStackForBase(env
, ty
);
437 if (couldBeInPrivateStatic(env
, base
)) setPrivateStaticForBase(env
, ty
);
438 if (couldBeInPublicStatic(base
)) setPublicStaticForBase(env
, ty
);
441 void startBase(ISS
& env
, Base base
) {
442 auto& oldState
= env
.collect
.mInstrState
;
443 assert(oldState
.base
.loc
== BaseLoc::None
);
444 assert(oldState
.arrayChain
.empty());
445 assert(isInitialBaseLoc(base
.loc
));
446 assert(!base
.type
.subtypeOf(TBottom
));
448 oldState
.noThrow
= !env
.flags
.wasPEI
;
449 oldState
.extraPop
= false;
450 oldState
.base
= std::move(base
);
451 FTRACE(5, " startBase: {}\n", show(*env
.ctx
.func
, oldState
.base
));
454 void endBase(ISS
& env
, bool update
= true, LocalId keyLoc
= NoLocalId
) {
455 auto& state
= env
.collect
.mInstrState
;
456 assert(state
.base
.loc
!= BaseLoc::None
);
458 FTRACE(5, " endBase: {}\n", show(*env
.ctx
.func
, state
.base
));
460 auto const firstKeyLoc
= state
.arrayChain
.empty()
462 : state
.arrayChain
.data()->keyLoc
;
463 auto const& ty
= state
.arrayChain
.empty()
465 : resolveArrayChain(env
, state
.base
.type
);
467 if (update
) updateBaseWithType(env
, ty
, firstKeyLoc
);
468 state
.base
.loc
= BaseLoc::None
;
471 void moveBase(ISS
& env
,
474 LocalId keyLoc
= NoLocalId
) {
475 auto& state
= env
.collect
.mInstrState
;
476 assert(state
.base
.loc
!= BaseLoc::None
);
477 assert(isDimBaseLoc(newBase
.loc
));
478 assert(!state
.base
.type
.subtypeOf(BBottom
));
480 FTRACE(5, " moveBase: {} -> {}\n",
481 show(*env
.ctx
.func
, state
.base
),
482 show(*env
.ctx
.func
, newBase
));
484 if (newBase
.loc
== BaseLoc::Elem
) {
487 loosen_staticness(loosen_values(state
.base
.type
))
491 auto const firstKeyLoc
= state
.arrayChain
.empty()
493 : state
.arrayChain
.data()->keyLoc
;
494 auto const& ty
= state
.arrayChain
.empty()
496 : resolveArrayChain(env
, state
.base
.type
);
498 if (update
) updateBaseWithType(env
, ty
, firstKeyLoc
);
499 state
.base
= std::move(newBase
);
502 void extendArrChain(ISS
& env
, Type key
, Type arr
,
503 Type val
, bool update
= true,
504 LocalId keyLoc
= NoLocalId
) {
505 auto& state
= env
.collect
.mInstrState
;
506 assert(state
.base
.loc
!= BaseLoc::None
);
507 assert(mustBeArrLike(arr
));
508 assert(!state
.base
.type
.subtypeOf(BBottom
));
509 assert(!val
.subtypeOf(BBottom
));
511 state
.arrayChain
.emplace_back(
512 CollectedInfo::MInstrState::ArrayChainEnt
{std::move(arr
), std::move(key
), keyLoc
}
514 state
.base
.type
= std::move(val
);
516 auto const firstKeyLoc
= state
.arrayChain
.data()->keyLoc
;
518 FTRACE(5, " extendArrChain: {}\n", show(*env
.ctx
.func
, state
));
522 currentChainType(env
, state
.base
.type
),
528 //////////////////////////////////////////////////////////////////////
531 * The following promoteBase{Elem,Prop}* functions are used to implement the
532 * 'normal' portion of the effects on base types, which are mostly what are done
533 * by intermediate dims.
535 * The contract with these functions is that they should handle all the effects
536 * on the base type /except/ for the case of the base being an array
537 * subtype---the caller is responsible for that. The reason for this is that for
538 * tracking effects on specialized array types, the final ops generally need to
539 * do completely different things to the array, so this allows reuse of this
540 * shared part of the type transitions. The intermediate routines must handle
541 * array subtypes outside of calls to this as well.
544 void promoteBaseElemU(ISS
& env
) {
545 // We're conservative with unsets on array types for now.
546 env
.collect
.mInstrState
.base
.type
= loosen_all(env
.collect
.mInstrState
.base
.type
);
549 void promoteBasePropD(ISS
& env
, bool isNullsafe
) {
550 auto& ty
= env
.collect
.mInstrState
.base
.type
;
552 // NullSafe (Q) props do not promote an emptyish base to stdClass instance.
553 if (isNullsafe
|| ty
.subtypeOf(BObj
)) return;
555 if (propMustPromoteToObj(ty
)) {
556 ty
= objExact(env
.index
.builtin_class(s_stdClass
.get()));
559 if (propCouldPromoteToObj(ty
)) {
560 ty
= promote_emptyish(ty
, TObj
);
565 void promoteBaseElemD(ISS
& env
) {
566 auto& ty
= env
.collect
.mInstrState
.base
.type
;
568 // When the base is actually a subtype of array, we handle it in the callers
569 // of these functions.
570 if (mustBeArrLike(ty
)) return;
572 if (elemMustPromoteToArr(ty
)) {
577 // Intermediate ElemD operations on strings fatal, unless the string is empty,
578 // which promotes to array. So for any string here we can assume it promoted
579 // to an empty array.
580 if (ty
.subtypeOf(BStr
)) {
585 if (elemCouldPromoteToArr(ty
)) {
586 ty
= promote_emptyish(ty
, some_aempty());
591 * If the base still could be some kind of array (but isn't an array sub-type
592 * which would be handled outside this routine), we need to give up on any
593 * better information here (or track the effects, but we're not doing that
596 ty
= loosen_arrays(ty
);
599 void promoteBaseNewElem(ISS
& env
) {
600 promoteBaseElemD(env
);
601 // Technically we don't need to do TStr case.
604 //////////////////////////////////////////////////////////////////////
606 void handleInPublicStaticElemD(ISS
& env
) {
607 auto const& base
= env
.collect
.mInstrState
.base
;
608 if (!couldBeInPublicStatic(base
)) return;
610 auto const name
= baseLocNameType(base
);
611 auto const ty
= env
.index
.lookup_public_static(env
.ctx
, base
.locTy
, name
);
612 if (elemCouldPromoteToArr(ty
)) {
613 // Might be possible to only merge a TArrE, but for now this is ok.
614 env
.collect
.publicSPropMutations
.merge(
615 env
.index
, env
.ctx
, base
.locTy
, name
, TArr
620 void handleInThisElemD(ISS
& env
) {
621 if (!couldBeInProp(env
, env
.collect
.mInstrState
.base
)) return;
623 if (auto const name
= env
.collect
.mInstrState
.base
.locName
) {
624 auto const ty
= thisPropAsCell(env
, name
);
625 if (ty
&& elemCouldPromoteToArr(*ty
)) {
626 mergeThisProp(env
, name
, TArr
);
631 mergeEachThisPropRaw(env
, [&] (const Type
& t
) {
632 return elemCouldPromoteToArr(t
) ? TArr
: TBottom
;
636 void handleInPublicStaticPropD(ISS
& env
, bool isNullsafe
) {
637 // NullSafe (Q) props do not promote an emptyish base to stdClass instance.
638 if (isNullsafe
) return;
640 auto const& base
= env
.collect
.mInstrState
.base
;
641 if (!couldBeInPublicStatic(base
)) return;
643 auto const name
= baseLocNameType(base
);
644 auto const ty
= env
.index
.lookup_public_static(env
.ctx
, base
.locTy
, name
);
645 if (propCouldPromoteToObj(ty
)) {
646 env
.collect
.publicSPropMutations
.merge(
647 env
.index
, env
.ctx
, base
.locTy
, name
,
648 objExact(env
.index
.builtin_class(s_stdClass
.get()))
653 void handleInThisPropD(ISS
& env
, bool isNullsafe
) {
654 // NullSafe (Q) props do not promote an emptyish base to stdClass instance.
655 if (isNullsafe
) return;
657 if (!couldBeInProp(env
, env
.collect
.mInstrState
.base
)) return;
659 if (auto const name
= env
.collect
.mInstrState
.base
.locName
) {
660 auto const ty
= thisPropAsCell(env
, name
);
661 if (ty
&& propCouldPromoteToObj(*ty
)) {
662 mergeThisProp(env
, name
,
663 objExact(env
.index
.builtin_class(s_stdClass
.get())));
668 if (RuntimeOption::EvalPromoteEmptyObject
) {
669 mergeEachThisPropRaw(env
, [&] (const Type
& t
) {
670 return propCouldPromoteToObj(t
) ? TObj
: TBottom
;
675 // Currently NewElem and Elem InFoo effects don't need to do
676 // anything different from each other.
677 void handleInPublicStaticNewElem(ISS
& env
) { handleInPublicStaticElemD(env
); }
678 void handleInThisNewElem(ISS
& env
) { handleInThisElemD(env
); }
680 void handleInPublicStaticElemU(ISS
& env
) {
681 auto const& base
= env
.collect
.mInstrState
.base
;
682 if (!couldBeInPublicStatic(base
)) return;
685 * Merging InitCell is correct, but very conservative, for now.
687 auto const name
= baseLocNameType(base
);
688 env
.collect
.publicSPropMutations
.merge(
689 env
.index
, env
.ctx
, base
.locTy
, name
, TInitCell
693 //////////////////////////////////////////////////////////////////////
695 // Returns nullptr if it's an unknown key or not a string.
696 SString
mStringKey(const Type
& key
) {
697 auto const v
= tv(key
);
698 return v
&& v
->m_type
== KindOfPersistentString
? v
->m_data
.pstr
: nullptr;
701 template<typename Op
>
702 auto update_mkey(const Op
& op
) { return false; }
704 template<typename Op
>
705 auto update_mkey(Op
& op
) -> decltype(op
.mkey
, true) {
706 switch (op
.mkey
.mcode
) {
707 case MEC
: case MPC
: {
716 template<typename Op
>
717 auto update_discard(const Op
& op
) { return false; }
719 template<typename Op
>
720 auto update_discard(Op
& op
) -> decltype(op
.arg1
, true) {
726 * Return the type of the key, or reduce op, and return folly::none.
727 * Note that when folly::none is returned, there is nothing further to
730 template<typename Op
>
731 folly::Optional
<Type
> key_type_or_fixup(ISS
& env
, Op op
) {
732 if (env
.collect
.mInstrState
.extraPop
) {
733 auto const mkey
= update_mkey(op
);
734 if (update_discard(op
) || mkey
) {
735 env
.collect
.mInstrState
.extraPop
= false;
737 env
.collect
.mInstrState
.extraPop
= true;
741 auto fixup
= [&] (Type ty
, bool isProp
) -> folly::Optional
<Type
> {
742 if (auto const val
= tv(ty
)) {
743 if (isStringType(val
->m_type
)) {
744 op
.mkey
.mcode
= isProp
? MPT
: MET
;
745 op
.mkey
.litstr
= val
->m_data
.pstr
;
749 if (!isProp
&& val
->m_type
== KindOfInt64
) {
751 op
.mkey
.int64
= val
->m_data
.num
;
756 return std::move(ty
);
758 switch (op
.mkey
.mcode
) {
760 return fixup(topC(env
, op
.mkey
.idx
), op
.mkey
.mcode
== MPC
);
762 return fixup(locAsCell(env
, op
.mkey
.local
), op
.mkey
.mcode
== MPL
);
766 return ival(op
.mkey
.int64
);
767 case MET
: case MPT
: case MQT
:
768 return sval(op
.mkey
.litstr
);
773 template<typename Op
>
774 LocalId
key_local(ISS
& env
, Op op
) {
775 switch (op
.mkey
.mcode
) {
777 return topStkLocal(env
, op
.mkey
.idx
);
779 return op
.mkey
.local
;
782 case MET
: case MPT
: case MQT
:
788 //////////////////////////////////////////////////////////////////////
791 Base
miBaseLocal(ISS
& env
, LocalId locBase
, MOpMode mode
) {
792 auto const locName
= env
.ctx
.func
->locals
[locBase
].name
;
793 auto const isDefine
= mode
== MOpMode::Define
;
794 if (mode
== MOpMode::None
||
795 (mode
== MOpMode::Warn
&& !locCouldBeUninit(env
, locBase
))) {
798 // If we're changing the local to define it, we don't need to do an miThrow
799 // yet---the promotions (to array or stdClass) on previously uninitialized
800 // locals happen before raising warnings that could throw, so we can wait
801 // until the first moveBase.
802 return Base
{ isDefine
? locAsCell(env
, locBase
) : derefLoc(env
, locBase
),
809 Base
miBaseSProp(ISS
& env
, Type cls
, const Type
& tprop
) {
810 auto const self
= selfCls(env
);
811 auto const prop
= tv(tprop
);
812 auto const name
= prop
&& prop
->m_type
== KindOfPersistentString
813 ? prop
->m_data
.pstr
: nullptr;
814 if (self
&& cls
.subtypeOf(*self
) && name
) {
815 if (auto ty
= selfPropAsCell(env
, name
)) {
817 Base
{ std::move(*ty
), BaseLoc::StaticProp
, std::move(cls
), name
};
820 auto indexTy
= env
.index
.lookup_public_static(env
.ctx
, cls
, tprop
);
821 if (!indexTy
.subtypeOf(BInitCell
)) indexTy
= TInitCell
;
822 return Base
{ std::move(indexTy
),
828 //////////////////////////////////////////////////////////////////////
831 void miProp(ISS
& env
, bool isNullsafe
, MOpMode mode
, Type key
) {
832 auto const name
= mStringKey(key
);
833 auto const isDefine
= mode
== MOpMode::Define
;
834 auto const isUnset
= mode
== MOpMode::Unset
;
835 // PHP5 doesn't modify an unset local if you unset a property or
836 // array elem on it, but hhvm does (it promotes it to init-null).
837 auto const update
= isDefine
|| isUnset
;
839 * MOpMode::Unset Props doesn't promote "emptyish" things to stdClass, or
840 * affect arrays, however it can define a property on an object base. This
841 * means we don't need any couldBeInFoo logic, but if the base could actually
842 * be $this, and a declared property could be Uninit, we need to merge
845 * We're trying to handle this case correctly as far as the type inference
846 * here is concerned, but the runtime doesn't actually behave this way right
847 * now for declared properties. Note that it never hurts to merge more types
848 * than a thisProp could actually be, so this is fine.
850 * See TODO(#3602740): unset with intermediate dims on previously declared
851 * properties doesn't define them to null.
853 if (isUnset
&& couldBeThisObj(env
, env
.collect
.mInstrState
.base
)) {
855 auto const elem
= thisPropRaw(env
, name
);
856 if (elem
&& elem
->ty
.couldBe(BUninit
)) {
857 mergeThisProp(env
, name
, TInitNull
);
860 mergeEachThisPropRaw(env
, [&] (const Type
& ty
) {
861 return ty
.couldBe(BUninit
) ? TInitNull
: TBottom
;
867 handleInThisPropD(env
, isNullsafe
);
868 handleInPublicStaticPropD(env
, isNullsafe
);
869 promoteBasePropD(env
, isNullsafe
);
872 if (mustBeThisObj(env
, env
.collect
.mInstrState
.base
)) {
873 auto const optThisTy
= thisTypeFromContext(env
.index
, env
.ctx
);
874 auto const thisTy
= optThisTy
? *optThisTy
: TObj
;
876 auto const ty
= [&] {
877 if (auto const propTy
= thisPropAsCell(env
, name
)) return *propTy
;
879 env
.index
.lookup_public_prop(objcls(thisTy
), sval(name
))
883 if (ty
.subtypeOf(BBottom
)) return unreachable(env
);
886 Base
{ ty
, BaseLoc::Prop
, thisTy
, name
},
891 Base
{ TInitCell
, BaseLoc::Prop
, thisTy
},
897 // We know for sure we're going to be in an object property.
898 if (env
.collect
.mInstrState
.base
.type
.subtypeOf(BObj
)) {
899 auto const ty
= to_cell(
900 env
.index
.lookup_public_prop(
901 objcls(env
.collect
.mInstrState
.base
.type
),
902 name
? sval(name
) : TStr
905 if (ty
.subtypeOf(BBottom
)) return unreachable(env
);
909 env
.collect
.mInstrState
.base
.type
,
916 * Otherwise, intermediate props with define can promote a null, false, or ""
917 * to stdClass. Those cases, and others, if it's MOpMode::Define, will set
918 * the base to a null value in tvScratch. The base may also legitimately be
919 * an object and our next base is in an object property. Conservatively treat
920 * all these cases as "possibly" being inside of an object property with
921 * "Prop" with locType TTop.
924 Base
{ TInitCell
, BaseLoc::Prop
, TTop
, name
},
928 void miElem(ISS
& env
, MOpMode mode
, Type key
, LocalId keyLoc
) {
929 auto const isDefine
= mode
== MOpMode::Define
;
930 auto const isUnset
= mode
== MOpMode::Unset
;
931 auto const update
= isDefine
|| isUnset
;
935 * Elem dims with MOpMode::Unset can change a base from a static array into
936 * a reference counted array. It never promotes emptyish types, however.
938 handleInPublicStaticElemU(env
);
939 promoteBaseElemU(env
);
941 if (auto ty
= array_do_elem(env
, false, key
)) {
942 if (ty
->subtypeOf(BBottom
)) return unreachable(env
);
946 std::move(*ty
), BaseLoc::Elem
,
947 env
.collect
.mInstrState
.base
.type
957 handleInThisElemD(env
);
958 handleInPublicStaticElemD(env
);
959 promoteBaseElemD(env
);
962 if (auto ty
= array_do_elem(env
, mode
== MOpMode::None
, key
)) {
963 if (ty
->subtypeOf(BBottom
)) return unreachable(env
);
965 env
, std::move(key
), env
.collect
.mInstrState
.base
.type
,
973 if (env
.collect
.mInstrState
.base
.type
.subtypeOf(BStr
)) {
974 moveBase(env
, Base
{ TStr
, BaseLoc::Elem
}, update
);
979 * Other cases could leave the base as anything (if nothing else, via
980 * ArrayAccess on an object).
982 * The resulting BaseLoc is either inside an array, is the global
983 * init_null_variant, or inside tvScratch. We represent this with the
984 * Elem base location with locType TTop.
986 moveBase(env
, Base
{ TInitCell
, BaseLoc::Elem
, TTop
}, update
, keyLoc
);
989 void miNewElem(ISS
& env
) {
990 handleInThisNewElem(env
);
991 handleInPublicStaticNewElem(env
);
992 promoteBaseNewElem(env
);
994 if (auto kty
= array_do_newelem(env
, TInitNull
)) {
997 std::move(env
.collect
.mInstrState
.base
.type
),
1000 moveBase(env
, Base
{ TInitCell
, BaseLoc::Elem
, TTop
});
1004 //////////////////////////////////////////////////////////////////////
1007 void miFinalIssetProp(ISS
& env
, int32_t nDiscard
, const Type
& key
) {
1008 auto const name
= mStringKey(key
);
1009 discard(env
, nDiscard
);
1010 if (name
&& mustBeThisObj(env
, env
.collect
.mInstrState
.base
)) {
1011 if (auto const pt
= thisPropAsCell(env
, name
)) {
1012 if (isMaybeLateInitThisProp(env
, name
)) {
1013 // LateInit props can always be maybe unset, except if its never set at
1015 return push(env
, pt
->subtypeOf(BBottom
) ? TFalse
: TBool
);
1017 if (pt
->subtypeOf(BNull
)) return push(env
, TFalse
);
1018 if (!pt
->couldBe(BNull
)) return push(env
, TTrue
);
1024 void miFinalCGetProp(ISS
& env
, int32_t nDiscard
, const Type
& key
) {
1025 auto const name
= mStringKey(key
);
1026 discard(env
, nDiscard
);
1028 auto const ty
= [&] {
1030 if (mustBeThisObj(env
, env
.collect
.mInstrState
.base
)) {
1031 if (auto const t
= thisPropAsCell(env
, name
)) return *t
;
1034 env
.index
.lookup_public_prop(
1035 objcls(env
.collect
.mInstrState
.base
.type
), sval(name
)
1041 if (ty
.subtypeOf(BBottom
)) unreachable(env
);
1045 void miFinalSetProp(ISS
& env
, int32_t nDiscard
, const Type
& key
) {
1046 auto const name
= mStringKey(key
);
1047 auto const t1
= unctx(popC(env
));
1049 auto const finish
= [&](Type ty
) {
1051 discard(env
, nDiscard
);
1052 push(env
, std::move(ty
));
1055 handleInThisPropD(env
, false);
1056 handleInPublicStaticPropD(env
, false);
1057 promoteBasePropD(env
, false);
1059 if (couldBeThisObj(env
, env
.collect
.mInstrState
.base
)) {
1061 mergeEachThisPropRaw(
1064 return propTy
.couldBe(BInitCell
) ? t1
: TBottom
;
1068 mergeThisProp(env
, name
, t1
);
1072 if (env
.collect
.mInstrState
.base
.type
.subtypeOf(BObj
)) {
1073 if (t1
.subtypeOf(BBottom
)) return unreachable(env
);
1076 Base
{ t1
, BaseLoc::Prop
, env
.collect
.mInstrState
.base
.type
, name
}
1081 moveBase(env
, Base
{ TInitCell
, BaseLoc::Prop
, TTop
, name
});
1082 return finish(TInitCell
);
1085 void miFinalSetOpProp(ISS
& env
, int32_t nDiscard
,
1086 SetOpOp subop
, const Type
& key
) {
1087 auto const name
= mStringKey(key
);
1088 auto const rhsTy
= popC(env
);
1090 handleInThisPropD(env
, false);
1091 handleInPublicStaticPropD(env
, false);
1092 promoteBasePropD(env
, false);
1094 auto const lhsTy
= [&] {
1096 if (mustBeThisObj(env
, env
.collect
.mInstrState
.base
)) {
1097 if (auto const t
= thisPropAsCell(env
, name
)) return *t
;
1100 env
.index
.lookup_public_prop(
1101 objcls(env
.collect
.mInstrState
.base
.type
), sval(name
)
1108 auto const resultTy
= env
.collect
.mInstrState
.base
.type
.subtypeOf(TObj
)
1109 ? typeSetOp(subop
, lhsTy
, rhsTy
)
1111 if (couldBeThisObj(env
, env
.collect
.mInstrState
.base
)) {
1113 mergeThisProp(env
, name
, resultTy
);
1115 loseNonRefThisPropTypes(env
);
1120 discard(env
, nDiscard
);
1121 push(env
, resultTy
);
1124 void miFinalIncDecProp(ISS
& env
, int32_t nDiscard
,
1125 IncDecOp subop
, const Type
& key
) {
1126 auto const name
= mStringKey(key
);
1128 handleInThisPropD(env
, false);
1129 handleInPublicStaticPropD(env
, false);
1130 promoteBasePropD(env
, false);
1132 auto const postPropTy
= [&] {
1134 if (mustBeThisObj(env
, env
.collect
.mInstrState
.base
)) {
1135 if (auto const t
= thisPropAsCell(env
, name
)) return *t
;
1138 env
.index
.lookup_public_prop(
1139 objcls(env
.collect
.mInstrState
.base
.type
), sval(name
)
1145 auto const prePropTy
= env
.collect
.mInstrState
.base
.type
.subtypeOf(TObj
)
1146 ? typeIncDec(subop
, postPropTy
)
1149 if (couldBeThisObj(env
, env
.collect
.mInstrState
.base
)) {
1151 mergeThisProp(env
, name
, prePropTy
);
1153 loseNonRefThisPropTypes(env
);
1158 discard(env
, nDiscard
);
1159 push(env
, isPre(subop
) ? prePropTy
: postPropTy
);
1162 void miFinalUnsetProp(ISS
& env
, int32_t nDiscard
, const Type
& key
) {
1163 auto const name
= mStringKey(key
);
1166 * Unset does define intermediate dims but with slightly different
1167 * rules than sets. It only applies to object properties.
1169 * Note that this can't affect self props, because static
1170 * properties can never be unset. It also can't change anything
1171 * about an inner array type.
1173 handleInThisPropD(env
, false);
1175 if (couldBeThisObj(env
, env
.collect
.mInstrState
.base
)) {
1177 unsetThisProp(env
, name
);
1179 unsetUnknownThisProp(env
);
1184 discard(env
, nDiscard
);
1187 //////////////////////////////////////////////////////////////////////
1190 // This is a helper for final defining Elem operations that need to
1191 // handle array chains and frame effects, but don't yet do anything
1192 // better than supplying a single type.
1193 void pessimisticFinalElemD(ISS
& env
, const Type
& key
, const Type
& ty
) {
1194 array_do_set(env
, key
, ty
);
1197 template<typename F
>
1198 void miFinalCGetElem(ISS
& env
, int32_t nDiscard
,
1199 const Type
& key
, bool nullOnMissing
,
1202 if (auto type
= array_do_elem(env
, nullOnMissing
, key
)) {
1203 return std::move(*type
);
1207 discard(env
, nDiscard
);
1208 push(env
, transform(std::move(ty
)));
1211 void miFinalSetElem(ISS
& env
,
1215 auto const t1
= popC(env
);
1217 handleInThisElemD(env
);
1218 handleInPublicStaticElemD(env
);
1220 auto const finish
= [&](Type ty
) {
1221 endBase(env
, true, keyLoc
);
1222 discard(env
, nDiscard
);
1223 push(env
, std::move(ty
));
1226 // Note: we must handle the string-related cases before doing the
1227 // general handleBaseElemD, since operates on strings as if this
1228 // was an intermediate ElemD.
1229 if (env
.collect
.mInstrState
.base
.type
.subtypeOf(sempty())) {
1230 env
.collect
.mInstrState
.base
.type
= some_aempty();
1232 auto& ty
= env
.collect
.mInstrState
.base
.type
;
1233 if (ty
.couldBe(BStr
)) {
1234 // Note here that a string type stays a string (with a changed character,
1235 // and loss of staticness), unless it was the empty string, where it
1236 // becomes an array. Do it conservatively for now:
1238 loosen_staticness(loosen_values(std::move(ty
))),
1242 if (!ty
.subtypeOf(BStr
)) promoteBaseElemD(env
);
1246 * In some unusual cases with illegal keys, SetM pushes null
1247 * instead of the right hand side.
1249 * There are also some special cases for SetM for different base types:
1250 * 1. If the base is a string, SetM pushes a new string with the
1251 * value of the first character of the right hand side converted
1252 * to a string (or something like that).
1253 * 2. If the base is a primitive type, SetM pushes null.
1254 * 3. If the base is an object, and it does not implement ArrayAccess,
1255 * it is still ok to push the right hand side, because it is a
1258 * We push the right hand side on the stack only if the base is an
1259 * array, object or emptyish.
1261 if (array_do_set(env
, key
, t1
)) {
1262 if (env
.state
.unreachable
) return finish(TBottom
);
1263 auto const maybeWeird
=
1264 env
.collect
.mInstrState
.base
.type
.subtypeOf(BArr
) && keyCouldBeWeird(key
);
1265 return finish(maybeWeird
? union_of(t1
, TInitNull
) : t1
);
1268 // ArrayAccess on $this will always push the rhs, even if things
1270 if (mustBeThisObj(env
, env
.collect
.mInstrState
.base
)) return finish(t1
);
1272 auto const isWeird
=
1273 keyCouldBeWeird(key
) ||
1274 (!mustBeEmptyish(env
.collect
.mInstrState
.base
.type
) &&
1275 !env
.collect
.mInstrState
.base
.type
.subtypeOf(BObj
));
1276 finish(isWeird
? TInitCell
: t1
);
1279 void miFinalSetOpElem(ISS
& env
, int32_t nDiscard
,
1280 SetOpOp subop
, const Type
& key
,
1282 auto const rhsTy
= popC(env
);
1283 handleInThisElemD(env
);
1284 handleInPublicStaticElemD(env
);
1285 promoteBaseElemD(env
);
1286 auto const lhsTy
= [&] {
1287 if (auto ty
= array_do_elem(env
, false, key
)) {
1288 if (env
.state
.unreachable
) return TBottom
;
1289 assertx(!ty
->subtypeOf(BBottom
));
1290 return std::move(*ty
);
1294 auto const resultTy
= typeSetOp(subop
, lhsTy
, rhsTy
);
1295 pessimisticFinalElemD(env
, key
, resultTy
);
1296 endBase(env
, true, keyLoc
);
1297 discard(env
, nDiscard
);
1298 push(env
, resultTy
);
1301 void miFinalIncDecElem(ISS
& env
, int32_t nDiscard
,
1302 IncDecOp subop
, const Type
& key
,
1304 handleInThisElemD(env
);
1305 handleInPublicStaticElemD(env
);
1306 promoteBaseElemD(env
);
1307 auto const postTy
= [&] {
1308 if (auto ty
= array_do_elem(env
, false, key
)) {
1309 if (env
.state
.unreachable
) return TBottom
;
1310 assertx(!ty
->subtypeOf(BBottom
));
1311 return std::move(*ty
);
1315 auto const preTy
= typeIncDec(subop
, postTy
);
1316 pessimisticFinalElemD(env
, key
, preTy
);
1317 endBase(env
, true, keyLoc
);
1318 discard(env
, nDiscard
);
1319 push(env
, isPre(subop
) ? preTy
: postTy
);
1322 void miFinalUnsetElem(ISS
& env
, int32_t nDiscard
, const Type
&) {
1323 handleInPublicStaticElemU(env
);
1324 promoteBaseElemU(env
);
1325 // We don't handle inner-array types with unset yet.
1326 always_assert(env
.collect
.mInstrState
.arrayChain
.empty());
1327 auto const& ty
= env
.collect
.mInstrState
.base
.type
;
1328 always_assert(!ty
.strictSubtypeOf(TArr
) && !ty
.strictSubtypeOf(TVec
) &&
1329 !ty
.strictSubtypeOf(TDict
) && !ty
.strictSubtypeOf(TKeyset
));
1331 discard(env
, nDiscard
);
1334 //////////////////////////////////////////////////////////////////////
1335 // Final new elem ops
1337 // This is a helper for final defining Elem operations that need to handle
1338 // array chains and frame effects, but don't yet do anything better than
1339 // supplying a single type.
1340 void pessimisticFinalNewElem(ISS
& env
, const Type
& type
) {
1341 array_do_newelem(env
, type
);
1344 void miFinalSetNewElem(ISS
& env
, int32_t nDiscard
) {
1345 auto const t1
= popC(env
);
1347 handleInThisNewElem(env
);
1348 handleInPublicStaticNewElem(env
);
1349 promoteBaseNewElem(env
);
1351 auto const finish
= [&](Type ty
) {
1353 discard(env
, nDiscard
);
1354 push(env
, std::move(ty
));
1357 if (array_do_newelem(env
, t1
)) {
1361 // ArrayAccess on $this will always push the rhs.
1362 if (mustBeThisObj(env
, env
.collect
.mInstrState
.base
)) return finish(t1
);
1364 // TODO(#3343813): we should push the type of the rhs when we can;
1365 // SetM for a new elem still has some weird cases where it pushes
1366 // null instead to handle. (E.g. if the base is a number.)
1370 void miFinalSetOpNewElem(ISS
& env
, int32_t nDiscard
) {
1372 handleInThisNewElem(env
);
1373 handleInPublicStaticNewElem(env
);
1374 promoteBaseNewElem(env
);
1375 pessimisticFinalNewElem(env
, TInitCell
);
1377 discard(env
, nDiscard
);
1378 push(env
, TInitCell
);
1381 void miFinalIncDecNewElem(ISS
& env
, int32_t nDiscard
) {
1382 handleInThisNewElem(env
);
1383 handleInPublicStaticNewElem(env
);
1384 promoteBaseNewElem(env
);
1385 pessimisticFinalNewElem(env
, TInitCell
);
1387 discard(env
, nDiscard
);
1388 push(env
, TInitCell
);
1393 namespace interp_step
{
1395 //////////////////////////////////////////////////////////////////////
1398 void in(ISS
& env
, const bc::BaseGC
& op
) {
1400 startBase(env
, Base
{TInitCell
, BaseLoc::Global
});
1403 void in(ISS
& env
, const bc::BaseGL
& op
) {
1404 locAsCell(env
, op
.loc1
);
1405 startBase(env
, Base
{TInitCell
, BaseLoc::Global
});
1408 void in(ISS
& env
, const bc::BaseSC
& op
) {
1409 auto prop
= topC(env
, op
.arg1
);
1410 auto cls
= takeClsRefSlot(env
, op
.slot
);
1411 auto newBase
= miBaseSProp(env
, std::move(cls
), prop
);
1412 if (newBase
.type
.subtypeOf(BBottom
)) return unreachable(env
);
1413 startBase(env
, std::move(newBase
));
1416 void in(ISS
& env
, const bc::BaseL
& op
) {
1417 auto newBase
= miBaseLocal(env
, op
.loc1
, op
.subop2
);
1418 if (newBase
.type
.subtypeOf(BBottom
)) return unreachable(env
);
1419 startBase(env
, std::move(newBase
));
1422 void in(ISS
& env
, const bc::BaseC
& op
) {
1423 assert(op
.arg1
< env
.state
.stack
.size());
1424 auto ty
= topC(env
, op
.arg1
);
1425 if (ty
.subtypeOf(BBottom
)) return unreachable(env
);
1435 (uint32_t)env
.state
.stack
.size() - op
.arg1
- 1
1440 void in(ISS
& env
, const bc::BaseH
&) {
1441 auto const ty
= thisTypeNonNull(env
);
1442 if (ty
.subtypeOf(BBottom
)) return unreachable(env
);
1444 startBase(env
, Base
{ty
, BaseLoc::This
});
1447 //////////////////////////////////////////////////////////////////////
1448 // Intermediate operations
1450 void in(ISS
& env
, const bc::Dim
& op
) {
1451 auto const key
= key_type_or_fixup(env
, op
);
1453 auto const keyLoc
= key_local(env
, op
);
1454 if (mcodeIsProp(op
.mkey
.mcode
)) {
1455 miProp(env
, op
.mkey
.mcode
== MQT
, op
.subop1
, *key
);
1456 } else if (mcodeIsElem(op
.mkey
.mcode
)) {
1457 miElem(env
, op
.subop1
, *key
, keyLoc
);
1461 if (env
.flags
.wasPEI
) env
.collect
.mInstrState
.noThrow
= false;
1462 if (env
.collect
.mInstrState
.noThrow
&&
1463 (op
.subop1
== MOpMode::None
|| op
.subop1
== MOpMode::Warn
) &&
1465 is_scalar(env
.collect
.mInstrState
.base
.type
)) {
1466 for (int i
= 0; ; i
++) {
1467 auto const last
= last_op(env
, i
);
1469 if (isMemberBaseOp(last
->op
)) {
1470 auto const base
= *last
;
1472 auto const reuseStack
=
1475 case Op::BaseGC
: return base
.BaseGC
.arg1
== 0;
1476 case Op::BaseSC
: return base
.BaseSC
.arg1
== 0;
1477 case Op::BaseC
: return base
.BaseC
.arg1
== 0;
1478 default: return false;
1481 assertx(!env
.collect
.mInstrState
.extraPop
|| reuseStack
);
1482 auto const extraPop
= !reuseStack
|| env
.collect
.mInstrState
.extraPop
;
1483 env
.collect
.mInstrState
.clear();
1484 if (reuseStack
) reduce(env
, bc::PopC
{});
1485 auto const v
= tv(env
.collect
.mInstrState
.base
.type
);
1487 reduce(env
, gen_constant(*v
), bc::BaseC
{ 0, op
.subop1
});
1488 env
.collect
.mInstrState
.extraPop
= extraPop
;
1491 if (!isMemberDimOp(last
->op
)) break;
1496 //////////////////////////////////////////////////////////////////////
1499 const StaticString
s_classname("classname");
1500 const StaticString
s_type_structure("HH\\type_structure");
1501 const StaticString
s_type_structure_classname("HH\\type_structure_classname");
1503 void in(ISS
& env
, const bc::QueryM
& op
) {
1504 auto const key
= key_type_or_fixup(env
, op
);
1506 auto const nDiscard
= op
.arg1
;
1508 if (mcodeIsProp(op
.mkey
.mcode
)) {
1509 // We don't currently do anything different for nullsafe query ops.
1510 switch (op
.subop2
) {
1511 case QueryMOp::CGet
:
1512 case QueryMOp::CGetQuiet
:
1513 miFinalCGetProp(env
, nDiscard
, *key
);
1515 case QueryMOp::Isset
:
1516 miFinalIssetProp(env
, nDiscard
, *key
);
1518 case QueryMOp::Empty
:
1519 discard(env
, nDiscard
);
1522 case QueryMOp::InOut
:
1523 always_assert(false);
1525 } else if (mcodeIsElem(op
.mkey
.mcode
)) {
1526 switch (op
.subop2
) {
1527 case QueryMOp::InOut
:
1528 case QueryMOp::CGet
:
1529 miFinalCGetElem(env
, nDiscard
, *key
, false,
1530 [](Type t
) { return t
; });
1532 case QueryMOp::CGetQuiet
:
1533 miFinalCGetElem(env
, nDiscard
, *key
, true,
1534 [](Type t
) { return t
; });
1536 case QueryMOp::Isset
:
1537 miFinalCGetElem(env
, nDiscard
, *key
, true,
1539 return t
.subtypeOf(BInitNull
) ? TFalse
:
1540 !t
.couldBe(BInitNull
) ? TTrue
: TBool
;
1543 case QueryMOp::Empty
:
1544 miFinalCGetElem(env
, nDiscard
, *key
, true,
1546 auto const e
= emptiness(t
);
1548 e
== Emptiness::Empty
? TTrue
:
1549 e
== Emptiness::NonEmpty
? TFalse
: TBool
;
1553 if (!env
.flags
.wasPEI
&&
1554 env
.collect
.mInstrState
.noThrow
&&
1555 is_scalar(topC(env
))) {
1556 for (int i
= 0; ; i
++) {
1557 auto const last
= last_op(env
, i
);
1559 if (isMemberBaseOp(last
->op
)) {
1560 auto const v
= tv(topC(env
));
1563 env
.collect
.mInstrState
.clear();
1564 BytecodeVec bcs
{nDiscard
, bc::PopC
{}};
1565 bcs
.push_back(gen_constant(*v
));
1566 return reduce(env
, std::move(bcs
));
1568 if (!isMemberDimOp(last
->op
)) break;
1572 // Try to detect type_structure(cls_name, cns_name)['classname'] and
1573 // reduce this to type_structure_classname(cls_name, cns_name)
1574 if (op
.subop2
== QueryMOp::CGet
&&
1576 op
.mkey
.mcode
== MemberCode::MET
&&
1577 op
.mkey
.litstr
->isame(s_classname
.get())) {
1578 if (auto const last
= last_op(env
, 0)) {
1579 if (last
->op
== Op::BaseC
) {
1580 if (auto const prev
= last_op(env
, 1)) {
1581 if (prev
->op
== Op::FCallBuiltin
&&
1582 prev
->FCallBuiltin
.str3
->isame(s_type_structure
.get()) &&
1583 prev
->FCallBuiltin
.arg1
== 2) {
1584 auto const params
= prev
->FCallBuiltin
.arg1
;
1585 auto const passed_params
= prev
->FCallBuiltin
.arg2
;
1586 rewind(env
, op
); // querym
1587 rewind(env
, 2); // basec + fcallbuiltin
1588 env
.collect
.mInstrState
.clear();
1594 s_type_structure_classname
.get()
1603 // QueryMNewElem will always throw without doing any work.
1604 discard(env
, nDiscard
);
1605 push(env
, TInitCell
);
1608 endBase(env
, false);
1611 void in(ISS
& env
, const bc::SetM
& op
) {
1612 auto const key
= key_type_or_fixup(env
, op
);
1614 auto const keyLoc
= key_local(env
, op
);
1615 if (mcodeIsProp(op
.mkey
.mcode
)) {
1616 miFinalSetProp(env
, op
.arg1
, *key
);
1617 } else if (mcodeIsElem(op
.mkey
.mcode
)) {
1618 miFinalSetElem(env
, op
.arg1
, *key
, keyLoc
);
1620 miFinalSetNewElem(env
, op
.arg1
);
1624 void in(ISS
& env
, const bc::SetRangeM
& op
) {
1625 auto const count
= popC(env
);
1626 auto const value
= popC(env
);
1627 auto const offset
= popC(env
);
1629 auto& base
= env
.collect
.mInstrState
.base
.type
;
1630 if (base
.couldBe(BStr
)) {
1631 base
= loosen_staticness(loosen_values(std::move(base
)));
1634 endBase(env
, true, NoLocalId
);
1635 discard(env
, op
.arg1
);
1638 void in(ISS
& env
, const bc::IncDecM
& op
) {
1639 auto const key
= key_type_or_fixup(env
, op
);
1641 auto const keyLoc
= key_local(env
, op
);
1642 if (mcodeIsProp(op
.mkey
.mcode
)) {
1643 miFinalIncDecProp(env
, op
.arg1
, op
.subop2
, *key
);
1644 } else if (mcodeIsElem(op
.mkey
.mcode
)) {
1645 miFinalIncDecElem(env
, op
.arg1
, op
.subop2
, *key
, keyLoc
);
1647 miFinalIncDecNewElem(env
, op
.arg1
);
1651 void in(ISS
& env
, const bc::SetOpM
& op
) {
1652 auto const key
= key_type_or_fixup(env
, op
);
1654 auto const keyLoc
= key_local(env
, op
);
1655 if (mcodeIsProp(op
.mkey
.mcode
)) {
1656 miFinalSetOpProp(env
, op
.arg1
, op
.subop2
, *key
);
1657 } else if (mcodeIsElem(op
.mkey
.mcode
)) {
1658 miFinalSetOpElem(env
, op
.arg1
, op
.subop2
, *key
, keyLoc
);
1660 miFinalSetOpNewElem(env
, op
.arg1
);
1664 void in(ISS
& env
, const bc::UnsetM
& op
) {
1665 auto const key
= key_type_or_fixup(env
, op
);
1667 if (mcodeIsProp(op
.mkey
.mcode
)) {
1668 miFinalUnsetProp(env
, op
.arg1
, *key
);
1670 assert(mcodeIsElem(op
.mkey
.mcode
));
1671 miFinalUnsetElem(env
, op
.arg1
, *key
);
1677 //////////////////////////////////////////////////////////////////////