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/type-ops.h"
31 namespace HPHP
{ namespace HHBBC
{
35 //////////////////////////////////////////////////////////////////////
37 const StaticString
s_stdClass("stdClass");
39 //////////////////////////////////////////////////////////////////////
42 * Note: the couldBe comparisons here with sempty() are asking "can this string
43 * be a non-reference counted empty string". What actually matters is whether
44 * it can be an empty string at all. Currently, all reference counted strings
45 * are TStr, which has no values and may also be non-reference
46 * counted---emptiness isn't separately tracked like it is for arrays, so if
47 * anything happened that could make it reference counted this check will
50 * This means this code is fine for now, but if we implement #3837503
51 * (non-static strings with values in the type system) it will need to change.
54 bool couldBeEmptyish(Type ty
) {
55 return ty
.couldBe(TNull
) ||
56 ty
.couldBe(sempty()) ||
60 bool mustBeEmptyish(Type ty
) {
61 return ty
.subtypeOf(TNull
) ||
62 ty
.subtypeOf(sempty()) ||
66 bool elemCouldPromoteToArr(Type ty
) { return couldBeEmptyish(ty
); }
67 bool elemMustPromoteToArr(Type ty
) { return mustBeEmptyish(ty
); }
69 bool propCouldPromoteToObj(Type ty
) {
70 return RuntimeOption::EvalPromoteEmptyObject
&& couldBeEmptyish(ty
);
73 bool propMustPromoteToObj(Type ty
) {
74 return RuntimeOption::EvalPromoteEmptyObject
&& mustBeEmptyish(ty
);
77 bool keyCouldBeWeird(Type key
) {
78 return key
.couldBe(TObj
) || key
.couldBe(TArr
) || key
.couldBe(TVec
) ||
79 key
.couldBe(TDict
) || key
.couldBe(TKeyset
);
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
94 * the base to handle effects on tracked locations:
96 * - Could the base be a location we're tracking deeper structure
97 * on, so the next operation actually affects something inside
98 * of it. For example, could the base be an object with the
99 * same type as $this, or an array in a local variable.
101 * - Could the base be something (regardless of type) that is
102 * inside one of the things we're tracking. I.e., the base
103 * might be whatever (an array or a bool or something), but
104 * living inside a property inside an object with the same type
105 * as $this, or living inside of an array in the local frame.
107 * The first cases apply because final operations are going to
108 * directly affect the type of these elements. The second case is
109 * because vector operations may change the base at each step if it
110 * is a defining instruction.
112 * Note that both of these cases can apply to the same base in some
113 * cases: you might have an object property on $this that could be
114 * an object of the type of $this.
116 * The functions below with names "couldBeIn*" detect the second
117 * case. The effects on the tracked location in the second case are
118 * handled in the functions with names "handleIn*{Prop,Elem,..}".
119 * The effects for the first case are generally handled in the
120 * miFinal op functions.
122 * Control flow insensitive vs. control flow sensitive types:
124 * Things are also slightly complicated by the fact that we are
125 * analyzing some control flow insensitve types along side precisely
126 * tracked types. For effects on locals, we perform the type
127 * effects of each operation on base.type, and then allow moveBase()
128 * to make the updates to the local when we know what its final type
131 * This approach doesn't do as well for possible properties in $this
132 * or self::, because we may see situations where the base could be
133 * one of these properties but we're not sure---perhaps because it
134 * came off a property with the same name on an object with an
135 * unknown type (i.e. base.type is InitCell but couldBeInThis is
136 * true). In these situations, we can get away with just merging
137 * Obj=stdClass into the thisProp (because it 'could' promote)
138 * instead of merging the whole InitCell, which possibly lets us
139 * leave the type at ?Obj in some cases.
141 * This is why there's two fairly different mechanisms for handling
142 * the effects of defining ops on base types.
145 //////////////////////////////////////////////////////////////////////
147 bool couldBeThisObj(ISS
& env
, const Base
& b
) {
148 if (b
.loc
== BaseLoc::Fataled
) return false;
149 auto const thisTy
= thisType(env
);
150 return b
.type
.couldBe(thisTy
? *thisTy
: TObj
);
153 bool mustBeThisObj(ISS
& env
, const Base
& b
) {
154 if (b
.loc
== BaseLoc::FrameThis
) return true;
155 if (auto const ty
= thisType(env
)) return b
.type
.subtypeOf(*ty
);
159 bool mustBeInFrame(const Base
& b
) {
160 return b
.loc
== BaseLoc::Frame
;
163 bool couldBeInThis(ISS
& env
, const Base
& b
) {
164 if (b
.loc
!= BaseLoc::PostProp
) return false;
165 auto const thisTy
= thisType(env
);
166 if (!thisTy
) return true;
167 if (!b
.locTy
.couldBe(*thisTy
)) return false;
169 return isTrackedThisProp(env
, b
.locName
);
174 bool couldBeInSelf(ISS
& env
, const Base
& b
) {
175 if (b
.loc
!= BaseLoc::StaticObjProp
) return false;
176 auto const selfTy
= selfCls(env
);
177 return !selfTy
|| b
.locTy
.couldBe(*selfTy
);
180 bool couldBeInPublicStatic(ISS
& env
, const Base
& b
) {
181 return b
.loc
== BaseLoc::StaticObjProp
;
184 //////////////////////////////////////////////////////////////////////
186 template<typename R
, typename B
, typename
... T
>
187 typename
std::enable_if
<
188 !std::is_same
<R
, Type
>::value
,
189 folly::Optional
<Type
>
190 >::type
hack_array_op(
192 R
opV(B
, const T
&...),
193 R
opD(B
, const T
&...),
194 R
opK(B
, const T
&...),
196 auto const base
= env
.state
.base
.type
;
197 if (!base
.subtypeOf(TVec
) && !base
.subtypeOf(TDict
) &&
198 !base
.subtypeOf(TKeyset
)) {
202 base
.subtypeOf(TVec
) ? opV(base
, args
...) :
203 base
.subtypeOf(TDict
) ? opD(base
, args
...) :
205 // TODO: we cannot support unreachable() in the middle of a minstr sequence
206 // right now as it causes problems in the verifier when we fall out of the
207 // fault funclet before the minstr is complete.
209 // if (res.first == TBottom) {
212 if (res
.second
) nothrow(env
);
213 if (res
.first
!= TBottom
) return res
.first
;
215 base
.subtypeOf(TVec
) ? TVec
:
216 base
.subtypeOf(TDict
) ? TDict
:
219 template<typename R
, typename B
, typename
... T
>
220 typename
std::enable_if
<
221 std::is_same
<R
, Type
>::value
,
222 folly::Optional
<Type
>
223 >::type
hack_array_op(
225 R
opV(B
, const T
&...),
226 R
opD(B
, const T
&...),
227 R
opK(B
, const T
&...),
229 auto const base
= env
.state
.base
.type
;
230 if (base
.subtypeOf(TVec
)) return opV(base
, args
...);
231 if (base
.subtypeOf(TDict
)) return opD(base
, args
...);
232 if (base
.subtypeOf(TKeyset
)) return opK(base
, args
...);
235 #define hack_array_do(env, op, ...) \
236 hack_array_op(env, vec_ ## op, dict_ ## op, keyset_ ## op, ##__VA_ARGS__)
238 //////////////////////////////////////////////////////////////////////
240 void handleInThisPropD(ISS
& env
, bool isNullsafe
) {
241 // NullSafe (Q) props do not promote an emptyish base to stdClass instance.
242 if (isNullsafe
) return;
244 if (!couldBeInThis(env
, env
.state
.base
)) return;
246 if (auto const name
= env
.state
.base
.locName
) {
247 auto const ty
= thisPropAsCell(env
, name
);
248 if (ty
&& propCouldPromoteToObj(*ty
)) {
249 mergeThisProp(env
, name
,
250 objExact(env
.index
.builtin_class(s_stdClass
.get())));
255 if (RuntimeOption::EvalPromoteEmptyObject
) {
256 mergeEachThisPropRaw(env
, [&] (Type t
) {
257 return propCouldPromoteToObj(t
) ? TObj
: TBottom
;
262 void handleInSelfPropD(ISS
& env
, bool isNullsafe
) {
263 // NullSafe (Q) props do not promote an emptyish base to stdClass instance.
264 if (isNullsafe
) return;
266 if (!couldBeInSelf(env
, env
.state
.base
)) return;
268 if (auto const name
= env
.state
.base
.locName
) {
269 auto const ty
= selfPropAsCell(env
, name
);
270 if (ty
&& propCouldPromoteToObj(*ty
)) {
271 mergeSelfProp(env
, name
,
272 objExact(env
.index
.builtin_class(s_stdClass
.get())));
277 loseNonRefSelfPropTypes(env
);
280 void handleInPublicStaticPropD(ISS
& env
, bool isNullsafe
) {
281 // NullSafe (Q) props do not promote an emptyish base to stdClass instance.
282 if (isNullsafe
) return;
284 if (!couldBeInPublicStatic(env
, env
.state
.base
)) return;
286 auto const indexer
= env
.collect
.publicStatics
;
287 if (!indexer
) return;
289 auto const name
= baseLocNameType(env
.state
.base
);
290 auto const ty
= env
.index
.lookup_public_static(env
.state
.base
.locTy
, name
);
291 if (propCouldPromoteToObj(ty
)) {
292 indexer
->merge(env
.ctx
, env
.state
.base
.locTy
, name
,
293 objExact(env
.index
.builtin_class(s_stdClass
.get())));
297 void handleInThisElemD(ISS
& env
) {
298 if (!couldBeInThis(env
, env
.state
.base
)) return;
300 if (auto const name
= env
.state
.base
.locName
) {
301 auto const ty
= thisPropAsCell(env
, name
);
302 if (ty
&& elemCouldPromoteToArr(*ty
)) {
303 mergeThisProp(env
, name
, TArr
);
308 mergeEachThisPropRaw(env
, [&] (Type t
) {
309 return elemCouldPromoteToArr(t
) ? TArr
: TBottom
;
313 void handleInSelfElemD(ISS
& env
) {
314 if (!couldBeInSelf(env
, env
.state
.base
)) return;
316 if (auto const name
= env
.state
.base
.locName
) {
317 if (auto const ty
= selfPropAsCell(env
, name
)) {
318 if (elemCouldPromoteToArr(*ty
)) {
319 mergeSelfProp(env
, name
, TArr
);
321 mergeSelfProp(env
, name
, loosen_statics(*ty
));
325 loseNonRefSelfPropTypes(env
);
328 void handleInPublicStaticElemD(ISS
& env
) {
329 if (!couldBeInPublicStatic(env
, env
.state
.base
)) return;
331 auto const indexer
= env
.collect
.publicStatics
;
332 if (!indexer
) return;
334 auto const name
= baseLocNameType(env
.state
.base
);
335 auto const ty
= env
.index
.lookup_public_static(env
.state
.base
.locTy
, name
);
336 if (elemCouldPromoteToArr(ty
)) {
337 // Might be possible to only merge a TArrE, but for now this is ok.
338 indexer
->merge(env
.ctx
, env
.state
.base
.locTy
, name
, TArr
);
342 // Currently NewElem and Elem InFoo effects don't need to do
343 // anything different from each other.
344 void handleInThisNewElem(ISS
& env
) { handleInThisElemD(env
); }
345 void handleInSelfNewElem(ISS
& env
) { handleInSelfElemD(env
); }
346 void handleInPublicStaticNewElem(ISS
& env
) { handleInPublicStaticElemD(env
); }
348 void handleInSelfElemU(ISS
& env
) {
349 if (!couldBeInSelf(env
, env
.state
.base
)) return;
351 if (auto const name
= env
.state
.base
.locName
) {
352 auto const ty
= selfPropAsCell(env
, name
);
353 if (ty
) mergeSelfProp(env
, name
, loosen_statics(*ty
));
355 mergeEachSelfPropRaw(env
, loosen_statics
);
359 void handleInPublicStaticElemU(ISS
& env
) {
360 if (!couldBeInPublicStatic(env
, env
.state
.base
)) return;
362 auto const indexer
= env
.collect
.publicStatics
;
363 if (!indexer
) return;
366 * We need to ensure that the type could become non-static, but since we're
367 * never going to see anything specialized from lookup_public_static the
368 * first time we're running with collect.publicStatics, we can't do much
369 * right now since we don't have a type for the union of all counted types.
371 * Merging InitCell is correct, but very conservative, for now.
373 auto const name
= baseLocNameType(env
.state
.base
);
374 indexer
->merge(env
.ctx
, env
.state
.base
.locTy
, name
, TInitCell
);
377 //////////////////////////////////////////////////////////////////////
379 // MInstrs can throw in between each op, so the states of locals
380 // need to be propagated across factored exit edges.
381 void miThrow(ISS
& env
) {
382 for (auto& factored
: env
.blk
.factoredExits
) {
383 env
.propagate(*factored
, without_stacks(env
.state
));
387 //////////////////////////////////////////////////////////////////////
389 void setLocalForBase(ISS
& env
, Type ty
) {
390 assert(mustBeInFrame(env
.state
.base
) ||
391 env
.state
.base
.loc
== BaseLoc::LocalArrChain
);
392 if (!env
.state
.base
.local
) return loseNonRefLocalTypes(env
);
393 setLoc(env
, env
.state
.base
.local
, ty
);
394 FTRACE(4, " ${} := {}\n",
395 env
.state
.base
.locName
? env
.state
.base
.locName
->data() : "$<unnamed>",
400 // Run backwards through an array chain doing array_set operations
401 // to produce the array type that incorporates the effects of any
402 // intermediate defining dims.
403 Type
currentChainType(ISS
& env
, Type val
) {
404 auto it
= env
.state
.arrayChain
.rbegin();
405 for (; it
!= env
.state
.arrayChain
.rend(); ++it
) {
406 if (it
->first
.subtypeOf(TArr
)) {
407 val
= array_set(it
->first
, it
->second
, val
);
408 } else if (it
->first
.subtypeOf(TVec
)) {
409 val
= vec_set(it
->first
, it
->second
, val
).first
;
410 if (val
== TBottom
) val
= TVec
;
411 } else if (it
->first
.subtypeOf(TDict
)) {
412 val
= dict_set(it
->first
, it
->second
, val
).first
;
413 if (val
== TBottom
) val
= TDict
;
415 assert(it
->first
.subtypeOf(TKeyset
));
416 val
= keyset_set(it
->first
, it
->second
, val
).first
;
417 if (val
== TBottom
) val
= TKeyset
;
423 Type
resolveArrayChain(ISS
& env
, Type val
) {
424 static UNUSED
const char prefix
[] = " ";
425 FTRACE(5, "{}chain\n", prefix
, show(val
));
427 auto arr
= std::move(env
.state
.arrayChain
.back().first
);
428 auto key
= std::move(env
.state
.arrayChain
.back().second
);
429 env
.state
.arrayChain
.pop_back();
430 FTRACE(5, "{} | {} := {} in {}\n", prefix
,
431 show(key
), show(val
), show(arr
));
432 if (arr
.subtypeOf(TVec
)) {
433 val
= vec_set(std::move(arr
), key
, val
).first
;
434 if (val
== TBottom
) val
= TVec
;
435 } else if (arr
.subtypeOf(TDict
)) {
436 val
= dict_set(std::move(arr
), key
, val
).first
;
437 if (val
== TBottom
) val
= TDict
;
438 } else if (arr
.subtypeOf(TKeyset
)) {
439 val
= keyset_set(std::move(arr
), key
, val
).first
;
440 if (val
== TBottom
) val
= TKeyset
;
442 assert(arr
.subtypeOf(TArr
));
443 val
= array_set(std::move(arr
), key
, val
);
445 } while (!env
.state
.arrayChain
.empty());
446 FTRACE(5, "{} = {}\n", prefix
, show(val
));
450 void moveBase(ISS
& env
, folly::Optional
<Base
> base
) {
451 SCOPE_EXIT
{ if (base
) env
.state
.base
= *base
; };
453 // Note: these miThrows probably can be left out if base is folly::none
454 // (i.e. we're on the last dim).
456 if (!env
.state
.arrayChain
.empty()) {
457 auto const continueChain
= base
&& base
->loc
== BaseLoc::LocalArrChain
;
460 * We have a chain in progress, but it's not done. We still need to
461 * update the type of the local for new minstrs, and for the exception
462 * edge of old minstrs.
464 setLocalForBase(env
, currentChainType(env
, base
->type
));
467 setLocalForBase(env
, resolveArrayChain(env
, env
.state
.base
.type
));
473 if (mustBeInFrame(env
.state
.base
)) {
474 setLocalForBase(env
, env
.state
.base
.type
);
479 //////////////////////////////////////////////////////////////////////
482 * The following handleBase{Elem,Prop}* functions are used to implement the
483 * 'normal' portion of the effects on base types, which are mostly what are
484 * done by intermediate dims. (Contrast with the handleInXXX{Elem,Prop}
485 * functions, which handle the effects on the type of the thing that's
486 * /containing/ the base.)
488 * The contract with these functions is that they should handle all the effects
489 * on the base type /except/ for the case of the base being a subtype of
490 * TArr---the caller is responsible for that. The reason for this is that for
491 * tracking effects on specialized array types (e.g. LocalArrChain), the final
492 * ops generally need to do completely different things to the array, so this
493 * allows reuse of this shared part of the type transitions. The
494 * miIntermediate routines must handle subtypes of TArr outside of calls to
498 void handleBaseElemU(ISS
& env
) {
499 auto& ty
= env
.state
.base
.type
;
500 if (ty
.couldBe(TArr
)) {
501 // We're conservative with unsets on array types for now.
502 ty
= union_of(ty
, TArr
);
504 if (ty
.couldBe(TVec
)) {
505 // Unset on a vec might turn it into a dict.
506 ty
= union_of(ty
, union_of(TVec
, TDict
));
508 if (ty
.couldBe(TDict
)) {
509 ty
= union_of(ty
, TDict
);
511 if (ty
.couldBe(TKeyset
)) {
512 ty
= union_of(ty
, TKeyset
);
514 if (ty
.couldBe(TSStr
)) {
515 ty
= loosen_statics(env
.state
.base
.type
);
519 void handleBasePropD(ISS
& env
, bool isNullsafe
) {
520 // NullSafe (Q) props do not promote an emptyish base to stdClass instance.
521 if (isNullsafe
) return;
523 auto& ty
= env
.state
.base
.type
;
524 if (ty
.subtypeOf(TObj
)) return;
525 if (propMustPromoteToObj(ty
)) {
526 ty
= objExact(env
.index
.builtin_class(s_stdClass
.get()));
529 if (propCouldPromoteToObj(ty
)) {
530 ty
= promote_emptyish(ty
, TObj
);
535 void handleBaseElemD(ISS
& env
) {
536 auto& ty
= env
.state
.base
.type
;
538 // When the base is actually a subtype of array, we handle it in the callers
539 // of these functions.
540 if (ty
.subtypeOf(TArr
) || ty
.subtypeOf(TVec
) || ty
.subtypeOf(TDict
) ||
541 ty
.subtypeOf(TKeyset
)) {
545 if (elemMustPromoteToArr(ty
)) {
546 ty
= counted_aempty();
549 // Intermediate ElemD operations on strings fatal, unless the
550 // string is empty, which promotes to array. So for any string
551 // here we can assume it promoted to an empty array.
552 if (ty
.subtypeOf(TStr
)) {
553 ty
= counted_aempty();
556 if (elemCouldPromoteToArr(ty
)) {
557 ty
= promote_emptyish(ty
, counted_aempty());
561 * If the base still couldBe some kind of array (but isn't a subtype of TArr,
562 * which would be handled outside this routine), we need to give up on any
563 * information better than TArr here (or track the effects, but we're not
566 if (ty
.couldBe(TArr
)) {
567 ty
= union_of(ty
, TArr
);
569 if (ty
.couldBe(TVec
)) {
570 ty
= union_of(ty
, TVec
);
572 if (ty
.couldBe(TDict
)) {
573 ty
= union_of(ty
, TDict
);
575 if (ty
.couldBe(TKeyset
)) {
576 ty
= union_of(ty
, TKeyset
);
580 void handleBaseNewElem(ISS
& env
) {
581 handleBaseElemD(env
);
582 // Technically we don't need to do TStr case.
585 //////////////////////////////////////////////////////////////////////
587 // Returns nullptr if it's an unknown key or not a string.
588 SString
mStringKey(Type key
) {
589 auto const v
= tv(key
);
590 return v
&& v
->m_type
== KindOfPersistentString
? v
->m_data
.pstr
: nullptr;
593 Type
key_type(ISS
& env
, MKey mkey
) {
594 switch (mkey
.mcode
) {
598 return locAsCell(env
, mkey
.local
);
600 return topC(env
, mkey
.idx
);
602 return ival(mkey
.int64
);
603 case MET
: case MPT
: case MQT
:
604 return sval(mkey
.litstr
);
609 //////////////////////////////////////////////////////////////////////
612 Base
miBaseLoc(ISS
& env
, borrowed_ptr
<php::Local
> locBase
, bool isDefine
) {
614 return Base
{ derefLoc(env
, locBase
),
621 // We're changing the local to define it, but we don't need to do an miThrow
622 // yet---the promotions (to array or stdClass) on previously uninitialized
623 // locals happen before raising warnings that could throw, so we can wait
624 // until the first moveBase.
625 return Base
{ locAsCell(env
, locBase
),
632 Base
miBaseSProp(ISS
& env
, Type cls
, Type tprop
) {
633 auto const self
= selfCls(env
);
634 auto const prop
= tv(tprop
);
635 auto const name
= prop
&& prop
->m_type
== KindOfPersistentString
636 ? prop
->m_data
.pstr
: nullptr;
637 if (self
&& cls
.subtypeOf(*self
) && name
) {
638 if (auto const ty
= selfPropAsCell(env
, prop
->m_data
.pstr
)) {
639 return Base
{ *ty
, BaseLoc::StaticObjProp
, cls
, name
};
642 auto const indexTy
= env
.index
.lookup_public_static(cls
, tprop
);
643 if (indexTy
.subtypeOf(TInitCell
)) {
644 return Base
{ indexTy
, BaseLoc::StaticObjProp
, cls
, name
};
646 return Base
{ TInitCell
, BaseLoc::StaticObjProp
, cls
, name
};
649 //////////////////////////////////////////////////////////////////////
652 void miProp(ISS
& env
, bool isNullsafe
, MOpMode mode
, Type key
) {
653 auto const name
= mStringKey(key
);
654 auto const isDefine
= mode
== MOpMode::Define
;
655 auto const isUnset
= mode
== MOpMode::Unset
;
658 * MOpMode::Unset Props doesn't promote "emptyish" things to stdClass, or
659 * affect arrays, however it can define a property on an object base. This
660 * means we don't need any couldBeInFoo logic, but if the base could actually
661 * be $this, and a declared property could be Uninit, we need to merge
664 * We're trying to handle this case correctly as far as the type inference
665 * here is concerned, but the runtime doesn't actually behave this way right
666 * now for declared properties. Note that it never hurts to merge more types
667 * than a thisProp could actually be, so this is fine.
669 * See TODO(#3602740): unset with intermediate dims on previously declared
670 * properties doesn't define them to null.
672 if (isUnset
&& couldBeThisObj(env
, env
.state
.base
)) {
674 auto const ty
= thisPropRaw(env
, name
);
675 if (ty
&& ty
->couldBe(TUninit
)) {
676 mergeThisProp(env
, name
, TInitNull
);
679 mergeEachThisPropRaw(env
, [&] (Type ty
) {
680 return ty
.couldBe(TUninit
) ? TInitNull
: TBottom
;
686 handleInThisPropD(env
, isNullsafe
);
687 handleInSelfPropD(env
, isNullsafe
);
688 handleInPublicStaticPropD(env
, isNullsafe
);
689 handleBasePropD(env
, isNullsafe
);
692 if (mustBeThisObj(env
, env
.state
.base
)) {
693 auto const optThisTy
= thisType(env
);
694 auto const thisTy
= optThisTy
? *optThisTy
: TObj
;
696 auto const propTy
= thisPropAsCell(env
, name
);
697 moveBase(env
, Base
{ propTy
? *propTy
: TInitCell
,
702 moveBase(env
, Base
{ TInitCell
, BaseLoc::PostProp
, thisTy
});
707 // We know for sure we're going to be in an object property.
708 if (env
.state
.base
.type
.subtypeOf(TObj
)) {
709 moveBase(env
, Base
{ TInitCell
,
717 * Otherwise, intermediate props with define can promote a null, false, or ""
718 * to stdClass. Those cases, and others, if it's MOpMode::Define, will set
719 * the base to a null value in tvScratch. The base may also legitimately be
720 * an object and our next base is in an object property.
722 * If we know for sure we're promoting to stdClass, we can put the locType
723 * pointing at that. Otherwise we conservatively treat all these cases as
724 * "possibly" being inside of an object property with "PostProp" with locType
727 auto const newBaseLocTy
=
728 isDefine
&& !isNullsafe
&& propMustPromoteToObj(env
.state
.base
.type
)
729 ? objExact(env
.index
.builtin_class(s_stdClass
.get()))
732 moveBase(env
, Base
{ TInitCell
, BaseLoc::PostProp
, newBaseLocTy
, name
});
735 void miElem(ISS
& env
, MOpMode mode
, Type key
) {
736 auto const isDefine
= mode
== MOpMode::Define
;
737 auto const isUnset
= mode
== MOpMode::Unset
;
740 * Elem dims with MOpMode::Unset can change a base from a static array into a
741 * reference counted array. It never promotes emptyish types, however.
743 * We only need to handle this for self props, because we don't track
744 * static-ness on this props. The similar effect on local bases is handled
748 handleInSelfElemU(env
);
749 handleInPublicStaticElemU(env
);
750 handleBaseElemU(env
);
753 auto const isvec
= env
.state
.base
.type
.subtypeOf(TVec
);
754 auto const isdict
= env
.state
.base
.type
.subtypeOf(TDict
);
755 auto const iskeyset
= env
.state
.base
.type
.subtypeOf(TKeyset
);
757 handleInThisElemD(env
);
758 handleInSelfElemD(env
);
759 handleInPublicStaticElemD(env
);
760 handleBaseElemD(env
);
762 auto const couldDoChain
=
763 (mustBeInFrame(env
.state
.base
) ||
764 env
.state
.base
.loc
== BaseLoc::LocalArrChain
) &&
765 (env
.state
.base
.type
.subtypeOf(TArr
) || isvec
|| isdict
|| iskeyset
);
768 env
.state
.arrayChain
.emplace_back(env
.state
.base
.type
, key
);
770 if (auto ty
= hack_array_do(env
, elem
, key
)) {
773 return array_elem(env
.state
.base
.type
, key
);
775 moveBase(env
, Base
{ ty
,
776 BaseLoc::LocalArrChain
,
778 env
.state
.base
.locName
,
779 env
.state
.base
.local
});
784 if (env
.state
.base
.type
.subtypeOf(TArr
)) {
785 moveBase(env
, Base
{ array_elem(env
.state
.base
.type
, key
),
787 env
.state
.base
.type
});
790 if (auto ty
= hack_array_do(env
, elem
, key
)) {
791 moveBase(env
, Base
{ *ty
,
793 env
.state
.base
.type
});
796 if (env
.state
.base
.type
.subtypeOf(TStr
)) {
797 moveBase(env
, Base
{ TStr
, BaseLoc::PostElem
});
802 * Other cases could leave the base as anything (if nothing else, via
803 * ArrayAccess on an object).
805 * The resulting BaseLoc is either inside an array, is the global
806 * init_null_variant, or inside tvScratch. We represent this with the
807 * PostElem base location with locType TTop.
809 moveBase(env
, Base
{ TInitCell
, BaseLoc::PostElem
, TTop
});
812 void miNewElem(ISS
& env
) {
813 handleInThisNewElem(env
);
814 handleInSelfNewElem(env
);
815 handleInPublicStaticNewElem(env
);
816 handleBaseNewElem(env
);
818 auto const isvec
= env
.state
.base
.type
.subtypeOf(TVec
);
819 auto const isdict
= env
.state
.base
.type
.subtypeOf(TDict
);
820 auto const iskeyset
= env
.state
.base
.type
.subtypeOf(TKeyset
);
821 auto const couldDoChain
=
822 (mustBeInFrame(env
.state
.base
) ||
823 env
.state
.base
.loc
== BaseLoc::LocalArrChain
) &&
824 (env
.state
.base
.type
.subtypeOf(TArr
) || isvec
|| isdict
|| iskeyset
);
826 auto par
= [&] () -> std::pair
<Type
, Type
> {
827 auto ty
= hack_array_do(env
, newelem
, TInitNull
);
828 if (ty
) return {*ty
, TInt
};
829 return array_newelem_key(env
.state
.base
.type
, TInitNull
);
831 env
.state
.arrayChain
.push_back(par
);
832 moveBase(env
, Base
{ TInitNull
,
833 BaseLoc::LocalArrChain
,
835 env
.state
.base
.locName
,
836 env
.state
.base
.local
});
840 if (env
.state
.base
.type
.subtypeOf(TArr
) || isvec
|| isdict
|| iskeyset
) {
841 moveBase(env
, Base
{ TInitNull
, BaseLoc::PostElem
, env
.state
.base
.type
});
845 moveBase(env
, Base
{ TInitCell
, BaseLoc::PostElem
, TTop
});
848 //////////////////////////////////////////////////////////////////////
851 void miFinalIssetProp(ISS
& env
, int32_t nDiscard
, Type key
) {
852 auto const name
= mStringKey(key
);
853 discard(env
, nDiscard
);
854 if (name
&& mustBeThisObj(env
, env
.state
.base
)) {
855 if (auto const pt
= thisPropAsCell(env
, name
)) {
856 if (pt
->subtypeOf(TNull
)) return push(env
, TFalse
);
857 if (!pt
->couldBe(TNull
)) return push(env
, TTrue
);
863 void miFinalCGetProp(ISS
& env
, int32_t nDiscard
, Type key
) {
864 auto const name
= mStringKey(key
);
865 discard(env
, nDiscard
);
866 if (name
&& mustBeThisObj(env
, env
.state
.base
)) {
867 if (auto const t
= thisPropAsCell(env
, name
)) {
868 return push(env
, *t
);
871 push(env
, TInitCell
);
874 void miFinalVGetProp(ISS
& env
, int32_t nDiscard
, Type key
, bool isNullsafe
) {
875 auto const name
= mStringKey(key
);
876 discard(env
, nDiscard
);
877 handleInThisPropD(env
, isNullsafe
);
878 handleInSelfPropD(env
, isNullsafe
);
879 handleInPublicStaticPropD(env
, isNullsafe
);
880 handleBasePropD(env
, isNullsafe
);
881 if (couldBeThisObj(env
, env
.state
.base
)) {
883 boxThisProp(env
, name
);
891 void miFinalSetProp(ISS
& env
, int32_t nDiscard
, Type key
) {
892 auto const name
= mStringKey(key
);
893 auto const t1
= popC(env
);
894 auto const nullsafe
= false;
896 discard(env
, nDiscard
);
897 handleInThisPropD(env
, nullsafe
);
898 handleInSelfPropD(env
, nullsafe
);
899 handleInPublicStaticPropD(env
, nullsafe
);
900 handleBasePropD(env
, nullsafe
);
902 auto const resultTy
= env
.state
.base
.type
.subtypeOf(TObj
) ? t1
: TInitCell
;
904 if (couldBeThisObj(env
, env
.state
.base
)) {
906 mergeEachThisPropRaw(env
, [&] (Type propTy
) -> Type
{
907 if (propTy
.couldBe(TInitCell
)) {
908 return union_of(std::move(propTy
), t1
);
915 mergeThisProp(env
, name
, t1
);
923 void miFinalSetOpProp(ISS
& env
, int32_t nDiscard
, SetOpOp subop
, Type key
) {
924 auto const name
= mStringKey(key
);
925 auto const rhsTy
= popC(env
);
927 discard(env
, nDiscard
);
928 auto const isNullsafe
= false;
929 handleInThisPropD(env
, isNullsafe
);
930 handleInSelfPropD(env
, isNullsafe
);
931 handleInPublicStaticPropD(env
, isNullsafe
);
932 handleBasePropD(env
, isNullsafe
);
934 auto resultTy
= TInitCell
;
936 if (couldBeThisObj(env
, env
.state
.base
)) {
937 if (name
&& mustBeThisObj(env
, env
.state
.base
)) {
938 if (auto const lhsTy
= thisPropAsCell(env
, name
)) {
939 resultTy
= typeSetOp(subop
, *lhsTy
, rhsTy
);
944 mergeThisProp(env
, name
, resultTy
);
946 loseNonRefThisPropTypes(env
);
953 void miFinalIncDecProp(ISS
& env
, int32_t nDiscard
, IncDecOp subop
, Type key
) {
954 auto const name
= mStringKey(key
);
955 discard(env
, nDiscard
);
956 auto const isNullsafe
= false;
957 handleInThisPropD(env
, isNullsafe
);
958 handleInSelfPropD(env
, isNullsafe
);
959 handleInPublicStaticPropD(env
, isNullsafe
);
960 handleBasePropD(env
, isNullsafe
);
962 auto prePropTy
= TInitCell
;
963 auto postPropTy
= TInitCell
;
965 if (couldBeThisObj(env
, env
.state
.base
)) {
966 if (name
&& mustBeThisObj(env
, env
.state
.base
)) {
967 if (auto const propTy
= thisPropAsCell(env
, name
)) {
968 prePropTy
= typeIncDec(subop
, *propTy
);
969 postPropTy
= *propTy
;
974 mergeThisProp(env
, name
, prePropTy
);
976 loseNonRefThisPropTypes(env
);
979 push(env
, isPre(subop
) ? prePropTy
: postPropTy
);
982 void miFinalBindProp(ISS
& env
, int32_t nDiscard
, Type key
) {
983 auto const name
= mStringKey(key
);
985 discard(env
, nDiscard
);
986 auto const isNullsafe
= false;
987 handleInThisPropD(env
, isNullsafe
);
988 handleInSelfPropD(env
, isNullsafe
);
989 handleInPublicStaticPropD(env
, isNullsafe
);
990 handleBasePropD(env
, isNullsafe
);
991 if (couldBeThisObj(env
, env
.state
.base
)) {
993 boxThisProp(env
, name
);
1001 void miFinalUnsetProp(ISS
& env
, int32_t nDiscard
, Type key
) {
1002 auto const name
= mStringKey(key
);
1003 discard(env
, nDiscard
);
1006 * Unset does define intermediate dims but with slightly different
1007 * rules than sets. It only applies to object properties.
1009 * Note that this can't affect self props, because static
1010 * properties can never be unset. It also can't change anything
1011 * about an inner array type.
1013 auto const isNullsafe
= false;
1014 handleInThisPropD(env
, isNullsafe
);
1016 if (couldBeThisObj(env
, env
.state
.base
)) {
1018 unsetThisProp(env
, name
);
1020 unsetUnknownThisProp(env
);
1025 //////////////////////////////////////////////////////////////////////
1028 // This is a helper for final defining Elem operations that need to
1029 // handle array chains and frame effects, but don't yet do anything
1030 // better than supplying a single type.
1031 void pessimisticFinalElemD(ISS
& env
, Type key
, Type ty
) {
1032 if (mustBeInFrame(env
.state
.base
)) {
1033 if (env
.state
.base
.type
.subtypeOf(TArr
)) {
1034 env
.state
.base
.type
= array_set(env
.state
.base
.type
, key
, ty
);
1037 if (auto res
= hack_array_do(env
, set
, key
, ty
)) {
1038 env
.state
.base
.type
= *res
;
1042 auto const isvec
= env
.state
.base
.type
.subtypeOf(TVec
);
1043 auto const isdict
= env
.state
.base
.type
.subtypeOf(TDict
);
1044 auto const iskeyset
= env
.state
.base
.type
.subtypeOf(TKeyset
);
1045 if (env
.state
.base
.loc
== BaseLoc::LocalArrChain
) {
1046 if (env
.state
.base
.type
.subtypeOf(TArr
) || isvec
|| isdict
|| iskeyset
) {
1047 env
.state
.arrayChain
.emplace_back(env
.state
.base
.type
, key
);
1048 env
.state
.base
.type
= ty
;
1053 void miFinalCGetElem(ISS
& env
, int32_t nDiscard
, Type key
) {
1054 auto const ty
= [&] {
1055 if (env
.state
.base
.type
.subtypeOf(TArr
)) {
1056 return array_elem(env
.state
.base
.type
, key
);
1058 if (auto ty
= hack_array_do(env
, elem
, key
)) {
1063 discard(env
, nDiscard
);
1067 void miFinalVGetElem(ISS
& env
, int32_t nDiscard
, Type key
) {
1068 discard(env
, nDiscard
);
1069 handleInThisElemD(env
);
1070 handleInSelfElemD(env
);
1071 handleInPublicStaticElemD(env
);
1072 handleBaseElemD(env
);
1073 auto const isvec
= env
.state
.base
.type
.subtypeOf(TVec
);
1074 auto const isdict
= env
.state
.base
.type
.subtypeOf(TDict
);
1075 auto const iskeyset
= env
.state
.base
.type
.subtypeOf(TKeyset
);
1076 pessimisticFinalElemD(env
, key
, TInitGen
);
1077 if (isvec
|| isdict
|| iskeyset
) {
1085 void miFinalSetElem(ISS
& env
, int32_t nDiscard
, Type key
) {
1086 auto const t1
= popC(env
);
1087 discard(env
, nDiscard
);
1089 handleInThisElemD(env
);
1090 handleInSelfElemD(env
);
1091 handleInPublicStaticElemD(env
);
1093 // Note: we must handle the string-related cases before doing the
1094 // general handleBaseElemD, since operates on strings as if this
1095 // was an intermediate ElemD.
1096 if (env
.state
.base
.type
.subtypeOf(sempty())) {
1097 env
.state
.base
.type
= counted_aempty();
1099 auto& ty
= env
.state
.base
.type
;
1100 if (ty
.couldBe(TStr
)) {
1101 // Note here that a string type stays a string (with a changed character,
1102 // and loss of staticness), unless it was the empty string, where it
1103 // becomes an array. Do it conservatively for now:
1104 ty
= union_of(loosen_statics(ty
), counted_aempty());
1106 if (!ty
.subtypeOf(TStr
)) {
1107 handleBaseElemD(env
);
1112 * In some unusual cases with illegal keys, SetM pushes null
1113 * instead of the right hand side.
1115 * There are also some special cases for SetM for different base types:
1116 * 1. If the base is a string, SetM pushes a new string with the
1117 * value of the first character of the right hand side converted
1118 * to a string (or something like that).
1119 * 2. If the base is a primitive type, SetM pushes null.
1120 * 3. If the base is an object, and it does not implement ArrayAccess,
1121 * it is still ok to push the right hand side, because it is a
1124 * We push the right hand side on the stack only if the base is an
1125 * array, object or emptyish.
1127 auto const isvec
= env
.state
.base
.type
.subtypeOf(TVec
);
1128 auto const isdict
= env
.state
.base
.type
.subtypeOf(TDict
);
1129 auto const iskeyset
= env
.state
.base
.type
.subtypeOf(TKeyset
);
1130 auto const isWeird
= keyCouldBeWeird(key
) ||
1131 (!env
.state
.base
.type
.subtypeOf(TArr
) &&
1132 !env
.state
.base
.type
.subtypeOf(TObj
) &&
1133 !mustBeEmptyish(env
.state
.base
.type
) &&
1137 auto isSuitableHackKey
= [&](Type key
) {
1138 if (isvec
) return key
.couldBe(TInt
);
1139 if (isdict
) return key
.couldBe(TInt
) || key
.couldBe(TStr
);
1143 if (mustBeInFrame(env
.state
.base
)) {
1144 if (env
.state
.base
.type
.subtypeOf(TArr
)) {
1145 env
.state
.base
.type
= array_set(env
.state
.base
.type
, key
, t1
);
1146 push(env
, isWeird
? TInitCell
: t1
);
1149 if (isvec
|| isdict
|| iskeyset
) {
1150 auto ty
= hack_array_do(env
, set
, key
, t1
);
1152 env
.state
.base
.type
= *ty
;
1153 // Vec, Dict, and Keysets throw on weird keys
1154 if (!isSuitableHackKey(key
)) {
1156 return push(env
, TBottom
);
1158 return push(env
, t1
);
1161 if (env
.state
.base
.loc
== BaseLoc::LocalArrChain
) {
1162 if (env
.state
.base
.type
.subtypeOf(TArr
)) {
1163 env
.state
.arrayChain
.emplace_back(env
.state
.base
.type
, key
);
1164 env
.state
.base
.type
= t1
;
1165 push(env
, isWeird
? TInitCell
: t1
);
1168 if (isvec
|| isdict
|| iskeyset
) {
1169 env
.state
.arrayChain
.emplace_back(env
.state
.base
.type
, key
);
1170 env
.state
.base
.type
= t1
;
1176 // ArrayAccess on $this will always push the rhs, even if things
1178 if (mustBeThisObj(env
, env
.state
.base
)) return push(env
, t1
);
1180 push(env
, isWeird
? TInitCell
: t1
);
1183 void miFinalSetOpElem(ISS
& env
, int32_t nDiscard
, SetOpOp subop
, Type key
) {
1184 auto const rhsTy
= popC(env
);
1185 discard(env
, nDiscard
);
1186 handleInThisElemD(env
);
1187 handleInSelfElemD(env
);
1188 handleInPublicStaticElemD(env
);
1189 handleBaseElemD(env
);
1190 auto const lhsTy
= [&] {
1191 if (env
.state
.base
.type
.subtypeOf(TArr
) && !keyCouldBeWeird(key
)) {
1192 return array_elem(env
.state
.base
.type
, key
);
1194 if (auto ty
= hack_array_do(env
, elem
, key
)) {
1199 auto const resultTy
= typeSetOp(subop
, lhsTy
, rhsTy
);
1200 pessimisticFinalElemD(env
, key
, resultTy
);
1201 push(env
, resultTy
);
1204 void miFinalIncDecElem(ISS
& env
, int32_t nDiscard
, IncDecOp subop
, Type key
) {
1205 discard(env
, nDiscard
);
1206 handleInThisElemD(env
);
1207 handleInSelfElemD(env
);
1208 handleInPublicStaticElemD(env
);
1209 handleBaseElemD(env
);
1210 auto const postTy
= [&] {
1211 if (env
.state
.base
.type
.subtypeOf(TArr
) && !keyCouldBeWeird(key
)) {
1212 return array_elem(env
.state
.base
.type
, key
);
1214 if (auto ty
= hack_array_do(env
, elem
, key
)) return *ty
;
1217 auto const preTy
= typeIncDec(subop
, postTy
);
1218 pessimisticFinalElemD(env
, key
, typeIncDec(subop
, preTy
));
1219 push(env
, isPre(subop
) ? preTy
: postTy
);
1222 void miFinalBindElem(ISS
& env
, int32_t nDiscard
, Type key
) {
1224 discard(env
, nDiscard
);
1225 handleInThisElemD(env
);
1226 handleInSelfElemD(env
);
1227 handleInPublicStaticElemD(env
);
1228 handleBaseElemD(env
);
1229 auto const isvec
= env
.state
.base
.type
.subtypeOf(TVec
);
1230 auto const isdict
= env
.state
.base
.type
.subtypeOf(TDict
);
1231 auto const iskeyset
= env
.state
.base
.type
.subtypeOf(TKeyset
);
1232 pessimisticFinalElemD(env
, key
, TInitGen
);
1233 if (isvec
|| isdict
|| iskeyset
) {
1241 void miFinalUnsetElem(ISS
& env
, int32_t nDiscard
, Type key
) {
1242 discard(env
, nDiscard
);
1243 handleInSelfElemU(env
);
1244 handleInPublicStaticElemU(env
);
1245 handleBaseElemU(env
);
1246 // We don't handle inner-array types with unset yet.
1247 always_assert(env
.state
.base
.loc
!= BaseLoc::LocalArrChain
);
1248 if (mustBeInFrame(env
.state
.base
)) {
1249 always_assert(!env
.state
.base
.type
.strictSubtypeOf(TArr
));
1253 //////////////////////////////////////////////////////////////////////
1254 // Final new elem ops
1256 // This is a helper for final defining Elem operations that need to handle
1257 // array chains and frame effects, but don't yet do anything better than
1258 // supplying a single type.
1259 void pessimisticFinalNewElem(ISS
& env
, Type ty
) {
1260 if (mustBeInFrame(env
.state
.base
)) {
1261 if (env
.state
.base
.type
.subtypeOf(TArr
)) {
1262 env
.state
.base
.type
= array_newelem(env
.state
.base
.type
, ty
);
1265 if (auto res
= hack_array_do(env
, newelem
, ty
)) {
1266 env
.state
.base
.type
= *res
;
1270 if (env
.state
.base
.loc
== BaseLoc::LocalArrChain
) {
1271 if (env
.state
.base
.type
.subtypeOf(TArr
)) {
1272 env
.state
.base
.type
= array_newelem(env
.state
.base
.type
, ty
);
1275 if (auto res
= hack_array_do(env
, newelem
, ty
)) {
1276 env
.state
.base
.type
= *res
;
1282 void miFinalVGetNewElem(ISS
& env
, int32_t nDiscard
) {
1283 discard(env
, nDiscard
);
1284 handleInThisNewElem(env
);
1285 handleInSelfNewElem(env
);
1286 handleInPublicStaticNewElem(env
);
1287 handleBaseNewElem(env
);
1288 auto const isvec
= env
.state
.base
.type
.subtypeOf(TVec
);
1289 auto const isdict
= env
.state
.base
.type
.subtypeOf(TDict
);
1290 auto const iskeyset
= env
.state
.base
.type
.subtypeOf(TKeyset
);
1291 pessimisticFinalNewElem(env
, TInitGen
);
1292 if (isvec
|| isdict
|| iskeyset
) {
1300 void miFinalSetNewElem(ISS
& env
, int32_t nDiscard
) {
1301 auto const t1
= popC(env
);
1302 discard(env
, nDiscard
);
1303 handleInThisNewElem(env
);
1304 handleInSelfNewElem(env
);
1305 handleInPublicStaticNewElem(env
);
1306 handleBaseNewElem(env
);
1308 if (mustBeInFrame(env
.state
.base
)) {
1309 if (env
.state
.base
.type
.subtypeOf(TArr
)) {
1310 env
.state
.base
.type
= array_newelem(env
.state
.base
.type
, t1
);
1314 if (auto ty
= hack_array_do(env
, newelem
, t1
)) {
1315 env
.state
.base
.type
= *ty
;
1320 if (env
.state
.base
.loc
== BaseLoc::LocalArrChain
) {
1321 if (env
.state
.base
.type
.subtypeOf(TArr
)) {
1322 env
.state
.base
.type
= array_newelem(env
.state
.base
.type
, t1
);
1326 if (auto ty
= hack_array_do(env
, newelem
, t1
)) {
1327 env
.state
.base
.type
= *ty
;
1333 // ArrayAccess on $this will always push the rhs.
1334 if (mustBeThisObj(env
, env
.state
.base
)) return push(env
, t1
);
1336 // TODO(#3343813): we should push the type of the rhs when we can;
1337 // SetM for a new elem still has some weird cases where it pushes
1338 // null instead to handle. (E.g. if the base is a number.)
1339 push(env
, TInitCell
);
1342 void miFinalSetOpNewElem(ISS
& env
, int32_t nDiscard
) {
1344 discard(env
, nDiscard
);
1345 handleInThisNewElem(env
);
1346 handleInSelfNewElem(env
);
1347 handleInPublicStaticNewElem(env
);
1348 handleBaseNewElem(env
);
1349 pessimisticFinalNewElem(env
, TInitCell
);
1350 push(env
, TInitCell
);
1353 void miFinalIncDecNewElem(ISS
& env
, int32_t nDiscard
) {
1354 discard(env
, nDiscard
);
1355 handleInThisNewElem(env
);
1356 handleInSelfNewElem(env
);
1357 handleInPublicStaticNewElem(env
);
1358 handleBaseNewElem(env
);
1359 pessimisticFinalNewElem(env
, TInitCell
);
1360 push(env
, TInitCell
);
1363 void miFinalBindNewElem(ISS
& env
, int32_t nDiscard
) {
1365 discard(env
, nDiscard
);
1366 handleInThisNewElem(env
);
1367 handleInSelfNewElem(env
);
1368 handleInPublicStaticNewElem(env
);
1369 handleBaseNewElem(env
);
1370 auto const isvec
= env
.state
.base
.type
.subtypeOf(TVec
);
1371 auto const isdict
= env
.state
.base
.type
.subtypeOf(TDict
);
1372 auto const iskeyset
= env
.state
.base
.type
.subtypeOf(TKeyset
);
1373 pessimisticFinalNewElem(env
, TInitGen
);
1374 if (isvec
|| isdict
|| iskeyset
) {
1382 void miFinalSetWithRef(ISS
& env
) {
1383 auto const isvec
= env
.state
.base
.type
.subtypeOf(TVec
);
1384 auto const isdict
= env
.state
.base
.type
.subtypeOf(TDict
);
1385 auto const iskeyset
= env
.state
.base
.type
.subtypeOf(TKeyset
);
1386 moveBase(env
, folly::none
);
1387 if (!isvec
&& !isdict
&& !iskeyset
) {
1394 //////////////////////////////////////////////////////////////////////
1396 void miBaseSImpl(ISS
& env
, bool hasRhs
, Type prop
) {
1397 auto rhs
= hasRhs
? popT(env
) : TTop
;
1398 auto const cls
= popA(env
);
1399 env
.state
.base
= miBaseSProp(env
, cls
, prop
);
1400 if (hasRhs
) push(env
, rhs
);
1403 //////////////////////////////////////////////////////////////////////
1405 template<typename A
, typename B
>
1406 void mergePaths(ISS
& env
, A a
, B b
) {
1407 auto const start
= env
.state
;
1409 auto const aState
= env
.state
;
1412 merge_into(env
.state
, aState
);
1413 assert(env
.flags
.wasPEI
);
1414 assert(!env
.flags
.canConstProp
);
1418 * Helpers to set the MOpMode immediate of a bytecode struct, whether it's
1419 * subop2 for Base* opcodes or subop1 for a Dim. All users of these functions
1420 * start with the flags set to Warn.
1422 template<typename BC
>
1423 void setMOpMode(BC
& op
, MOpMode mode
) {
1424 assert(op
.subop2
== MOpMode::Warn
);
1428 void setMOpMode(bc::Dim
& op
, MOpMode mode
) {
1429 assert(op
.subop1
== MOpMode::Warn
);
1433 folly::Optional
<MOpMode
> fpassMode(ISS
& env
, int32_t arg
) {
1434 switch (prepKind(env
, arg
)) {
1435 case PrepKind::Unknown
: return folly::none
;
1436 case PrepKind::Val
: return MOpMode::Warn
;
1437 case PrepKind::Ref
: return MOpMode::Define
;
1439 always_assert(false);
1444 namespace interp_step
{
1446 //////////////////////////////////////////////////////////////////////
1449 void in(ISS
& env
, const bc::BaseNC
& op
) {
1450 assert(env
.state
.arrayChain
.empty());
1452 readUnknownLocals(env
);
1454 env
.state
.base
= Base
{TInitCell
, BaseLoc::Frame
};
1457 void in(ISS
& env
, const bc::BaseNL
& op
) {
1458 assert(env
.state
.arrayChain
.empty());
1459 locAsCell(env
, op
.loc1
);
1460 readUnknownLocals(env
);
1462 env
.state
.base
= Base
{TInitCell
, BaseLoc::Frame
};
1465 void in(ISS
& env
, const bc::BaseGC
& op
) {
1466 assert(env
.state
.arrayChain
.empty());
1468 env
.state
.base
= Base
{TInitCell
, BaseLoc::Global
};
1471 void in(ISS
& env
, const bc::BaseGL
& op
) {
1472 assert(env
.state
.arrayChain
.empty());
1473 locAsCell(env
, op
.loc1
);
1474 env
.state
.base
= Base
{TInitCell
, BaseLoc::Global
};
1477 void in(ISS
& env
, const bc::BaseSC
& op
) {
1478 assert(env
.state
.arrayChain
.empty());
1479 auto const prop
= topC(env
, op
.arg1
);
1480 miBaseSImpl(env
, op
.arg2
== 1, prop
);
1483 void in(ISS
& env
, const bc::BaseSL
& op
) {
1484 assert(env
.state
.arrayChain
.empty());
1485 auto const prop
= locAsCell(env
, op
.loc1
);
1486 miBaseSImpl(env
, op
.arg2
== 1, prop
);
1489 void in(ISS
& env
, const bc::BaseL
& op
) {
1490 assert(env
.state
.arrayChain
.empty());
1491 env
.state
.base
= miBaseLoc(env
, op
.loc1
, op
.subop2
== MOpMode::Define
);
1494 void in(ISS
& env
, const bc::BaseC
& op
) {
1495 assert(env
.state
.arrayChain
.empty());
1496 env
.state
.base
= Base
{topC(env
, op
.arg1
), BaseLoc::EvalStack
};
1499 void in(ISS
& env
, const bc::BaseR
& op
) {
1500 assert(env
.state
.arrayChain
.empty());
1501 auto const ty
= topR(env
, op
.arg1
);
1502 env
.state
.base
= Base
{ty
.subtypeOf(TInitCell
) ? ty
: TInitCell
,
1503 BaseLoc::EvalStack
};
1506 void in(ISS
& env
, const bc::BaseH
& op
) {
1507 assert(env
.state
.arrayChain
.empty());
1508 auto const ty
= thisType(env
);
1509 env
.state
.base
= Base
{ty
? *ty
: TObj
, BaseLoc::FrameThis
};
1512 template<typename BC
>
1513 static void fpassImpl(ISS
& env
, int32_t arg
, BC op
) {
1514 if (auto const mode
= fpassMode(env
, arg
)) {
1515 setMOpMode(op
, *mode
);
1516 return reduce(env
, op
);
1521 [&] { in(env
, op
); },
1523 setMOpMode(op
, MOpMode::Define
);
1529 void in(ISS
& env
, const bc::FPassBaseNC
& op
) {
1530 fpassImpl(env
, op
.arg1
, bc::BaseNC
{op
.arg2
, MOpMode::Warn
});
1533 void in(ISS
& env
, const bc::FPassBaseNL
& op
) {
1534 fpassImpl(env
, op
.arg1
, bc::BaseNL
{op
.loc2
, MOpMode::Warn
});
1537 void in(ISS
& env
, const bc::FPassBaseGC
& op
) {
1538 fpassImpl(env
, op
.arg1
, bc::BaseGC
{op
.arg2
, MOpMode::Warn
});
1541 void in(ISS
& env
, const bc::FPassBaseGL
& op
) {
1542 fpassImpl(env
, op
.arg1
, bc::BaseGL
{op
.loc2
, MOpMode::Warn
});
1545 void in(ISS
& env
, const bc::FPassBaseL
& op
) {
1546 fpassImpl(env
, op
.arg1
, bc::BaseL
{op
.loc2
, MOpMode::Warn
});
1549 //////////////////////////////////////////////////////////////////////
1550 // Intermediate operations
1552 void in(ISS
& env
, const bc::Dim
& op
) {
1553 auto const key
= key_type(env
, op
.mkey
);
1554 if (mcodeIsProp(op
.mkey
.mcode
)) {
1555 miProp(env
, op
.mkey
.mcode
== MQT
, op
.subop1
, key
);
1556 } else if (mcodeIsElem(op
.mkey
.mcode
)) {
1557 miElem(env
, op
.subop1
, key
);
1563 void in(ISS
& env
, const bc::FPassDim
& op
) {
1564 fpassImpl(env
, op
.arg1
, bc::Dim
{MOpMode::Warn
, op
.mkey
});
1567 //////////////////////////////////////////////////////////////////////
1570 void in(ISS
& env
, const bc::QueryM
& op
) {
1571 auto const key
= key_type(env
, op
.mkey
);
1572 auto const nDiscard
= op
.arg1
;
1574 if (mcodeIsProp(op
.mkey
.mcode
)) {
1575 // We don't currently do anything different for nullsafe query ops.
1576 switch (op
.subop2
) {
1577 case QueryMOp::CGet
:
1578 case QueryMOp::CGetQuiet
:
1579 return miFinalCGetProp(env
, nDiscard
, key
);
1580 case QueryMOp::Isset
:
1581 return miFinalIssetProp(env
, nDiscard
, key
);
1582 case QueryMOp::Empty
:
1583 discard(env
, nDiscard
);
1587 } else if (mcodeIsElem(op
.mkey
.mcode
)) {
1588 switch (op
.subop2
) {
1589 case QueryMOp::CGet
:
1590 case QueryMOp::CGetQuiet
:
1591 return miFinalCGetElem(env
, nDiscard
, key
);
1592 case QueryMOp::Isset
:
1593 case QueryMOp::Empty
:
1594 discard(env
, nDiscard
);
1599 // QueryMNewElem will always throw without doing any work.
1600 discard(env
, op
.arg1
);
1601 push(env
, TInitCell
);
1605 void in(ISS
& env
, const bc::VGetM
& op
) {
1606 auto const key
= key_type(env
, op
.mkey
);
1607 if (mcodeIsProp(op
.mkey
.mcode
)) {
1608 miFinalVGetProp(env
, op
.arg1
, key
, op
.mkey
.mcode
== MQT
);
1609 } else if (mcodeIsElem(op
.mkey
.mcode
)) {
1610 miFinalVGetElem(env
, op
.arg1
, key
);
1612 miFinalVGetNewElem(env
, op
.arg1
);
1614 moveBase(env
, folly::none
);
1617 void in(ISS
& env
, const bc::SetM
& op
) {
1618 auto const key
= key_type(env
, op
.mkey
);
1619 if (mcodeIsProp(op
.mkey
.mcode
)) {
1620 miFinalSetProp(env
, op
.arg1
, key
);
1621 } else if (mcodeIsElem(op
.mkey
.mcode
)) {
1622 miFinalSetElem(env
, op
.arg1
, key
);
1624 miFinalSetNewElem(env
, op
.arg1
);
1626 moveBase(env
, folly::none
);
1629 void in(ISS
& env
, const bc::IncDecM
& op
) {
1630 auto const key
= key_type(env
, op
.mkey
);
1631 if (mcodeIsProp(op
.mkey
.mcode
)) {
1632 miFinalIncDecProp(env
, op
.arg1
, op
.subop2
, key
);
1633 } else if (mcodeIsElem(op
.mkey
.mcode
)) {
1634 miFinalIncDecElem(env
, op
.arg1
, op
.subop2
, key
);
1636 miFinalIncDecNewElem(env
, op
.arg1
);
1638 moveBase(env
, folly::none
);
1641 void in(ISS
& env
, const bc::SetOpM
& op
) {
1642 auto const key
= key_type(env
, op
.mkey
);
1643 if (mcodeIsProp(op
.mkey
.mcode
)) {
1644 miFinalSetOpProp(env
, op
.arg1
, op
.subop2
, key
);
1645 } else if (mcodeIsElem(op
.mkey
.mcode
)) {
1646 miFinalSetOpElem(env
, op
.arg1
, op
.subop2
, key
);
1648 miFinalSetOpNewElem(env
, op
.arg1
);
1650 moveBase(env
, folly::none
);
1653 void in(ISS
& env
, const bc::BindM
& op
) {
1654 auto const key
= key_type(env
, op
.mkey
);
1655 if (mcodeIsProp(op
.mkey
.mcode
)) {
1656 miFinalBindProp(env
, op
.arg1
, key
);
1657 } else if (mcodeIsElem(op
.mkey
.mcode
)) {
1658 miFinalBindElem(env
, op
.arg1
, key
);
1660 miFinalBindNewElem(env
, op
.arg1
);
1662 moveBase(env
, folly::none
);
1665 void in(ISS
& env
, const bc::UnsetM
& op
) {
1666 auto const key
= key_type(env
, op
.mkey
);
1667 if (mcodeIsProp(op
.mkey
.mcode
)) {
1668 miFinalUnsetProp(env
, op
.arg1
, key
);
1670 assert(mcodeIsElem(op
.mkey
.mcode
));
1671 miFinalUnsetElem(env
, op
.arg1
, key
);
1673 moveBase(env
, folly::none
);
1676 void in(ISS
& env
, const bc::SetWithRefLML
& op
) {
1677 locAsCell(env
, op
.loc1
);
1678 locAsCell(env
, op
.loc2
);
1679 miFinalSetWithRef(env
);
1682 void in(ISS
& env
, const bc::SetWithRefRML
& op
) {
1683 locAsCell(env
, op
.loc1
);
1685 miFinalSetWithRef(env
);
1688 void in(ISS
& env
, const bc::FPassM
& op
) {
1689 auto const cget
= bc::QueryM
{op
.arg2
, QueryMOp::CGet
, op
.mkey
};
1690 auto const vget
= bc::VGetM
{op
.arg2
, op
.mkey
};
1692 if (auto const mode
= fpassMode(env
, op
.arg1
)) {
1693 return mode
== MOpMode::Warn
? reduce(env
, cget
, bc::FPassC
{op
.arg1
})
1694 : reduce(env
, vget
, bc::FPassVNop
{op
.arg1
});
1699 [&] { in(env
, cget
); },
1700 [&] { in(env
, vget
); }
1706 //////////////////////////////////////////////////////////////////////