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/Format.h>
25 #include "hphp/util/trace.h"
27 #include "hphp/hhbbc/interp-internal.h"
28 #include "hphp/hhbbc/optimize.h"
29 #include "hphp/hhbbc/type-ops.h"
31 namespace HPHP
{ namespace HHBBC
{
35 //////////////////////////////////////////////////////////////////////
37 // Represents the "effectful"-ness of a particular member instruction
38 // operation. This includes operations which are visible outside of
39 // the function (writing to a static property), but also potential
40 // throwing. Many different aspects of a member instruction operation
41 // can have effects, so we combine the effects from the various
42 // portions and use the final result to determine how to mark the
46 SideEffect
, // Cannot throw, but has some side-effect
47 Throws
, // Might throw an exception
48 AlwaysThrows
// Always throws an exception
51 // Combine two effects, in the sense that either of them might happen
52 // (therefore AlwaysThrows becomes just Throws if mixed with something
54 Effects
unionEffects(Effects e1
, Effects e2
) {
55 if (e1
== e2
) return e1
;
56 if (e1
== Effects::Throws
|| e2
== Effects::Throws
||
57 e1
== Effects::AlwaysThrows
|| e2
== Effects::AlwaysThrows
) {
58 return Effects::Throws
;
60 return Effects::SideEffect
;
63 // There's no good default for Effects when you want to union them
64 // together. Instead you want to start with the first Effect you
65 // see. This keeps the Effect from taking a value until you union one
67 using OptEffects
= Optional
<Effects
>;
69 OptEffects
unionEffects(OptEffects e1
, Effects e2
) {
71 return unionEffects(*e1
, e2
);
74 //////////////////////////////////////////////////////////////////////
79 * Generally type inference needs to know two kinds of things about the base to
80 * handle effects on tracked locations:
82 * - Could the base be a location we're tracking deeper structure on, so the
83 * next operation actually affects something inside of it. For example,
84 * could the base be an object with the same type as $this, or an array in a
87 * - Could the base be something (regardless of type) that is inside one of
88 * the things we're tracking. I.e., the base might be whatever (an array or
89 * a bool or something), but living inside a property inside an object with
90 * the same type as $this, or living inside of an array in the local frame.
92 * The first cases apply because final operations are going to directly affect
93 * the type of these elements. The second case is because member operations may
94 * change the base at each step if it is a defining instruction.
96 * Note that both of these cases can apply to the same base in some cases: you
97 * might have an object property on $this that could be an object of the type of
101 //////////////////////////////////////////////////////////////////////
103 bool couldBeThisObj(ISS
& env
, const Base
& b
) {
104 if (b
.loc
== BaseLoc::This
) return true;
105 auto const thisTy
= thisTypeFromContext(env
.index
, env
.ctx
);
106 return b
.type
.couldBe(thisTy
? *thisTy
: TObj
);
109 bool mustBeThisObj(ISS
& env
, const Base
& b
) {
110 if (b
.loc
== BaseLoc::This
) return true;
111 if (auto const ty
= thisTypeFromContext(env
.index
, env
.ctx
)) {
112 return b
.type
.subtypeOf(*ty
);
117 bool mustBeInLocal(const Base
& b
) {
118 return b
.loc
== BaseLoc::Local
;
121 bool mustBeInStack(const Base
& b
) {
122 return b
.loc
== BaseLoc::Stack
;
125 bool mustBeInStatic(const Base
& b
) {
126 return b
.loc
== BaseLoc::StaticProp
;
129 //////////////////////////////////////////////////////////////////////
131 // Base locations that only occur at the start of a minstr sequence.
132 bool isInitialBaseLoc(BaseLoc loc
) {
134 loc
== BaseLoc::Local
||
135 loc
== BaseLoc::Stack
||
136 loc
== BaseLoc::StaticProp
||
137 loc
== BaseLoc::This
||
138 loc
== BaseLoc::Global
;
141 // Base locations that only occur after the start of a minstr sequence.
142 bool isDimBaseLoc(BaseLoc loc
) {
143 return loc
== BaseLoc::Elem
|| loc
== BaseLoc::Prop
;
146 //////////////////////////////////////////////////////////////////////
149 * Update the current base via array_like_set, and return whether the
150 * operation can possibly throw.
152 TriBool
array_do_set(ISS
& env
,
155 auto& base
= env
.collect
.mInstrState
.base
.type
;
156 assertx(base
.couldBe(BArrLike
));
158 // array_like_set requires the key to be a TArrKey already. If it's
159 // not guaranteed to be, we assume we could throw.
160 if (!key
.couldBe(BArrKey
)) return TriBool::Yes
;
161 auto const validKey
= key
.subtypeOf(BArrKey
);
163 auto set
= array_like_set(
165 validKey
? key
: intersection_of(key
, TArrKey
),
168 // Check for the presence of TArrLike rather than Bottom, since the
169 // base may have contained more than just TArrLike to begin with.
170 if (!set
.first
.couldBe(BArrLike
)) {
175 base
= std::move(set
.first
);
176 return maybeOrNo(set
.second
|| !validKey
);
180 * Look up the specified key via array_like_elem and return the
181 * associated value, along with whether the lookup can throw and if
182 * the value is definitely present.
184 * If excludeKeyset is true, we remove any TKeyset types from the
185 * array before doing the lookup. This is useful for the cases where a
186 * keyset would fatal, but other types wouldn't.
193 ElemResult
array_do_elem(ISS
& env
, const Type
& key
,
194 bool excludeKeyset
= false) {
195 auto const& base
= env
.collect
.mInstrState
.base
.type
;
196 assertx(base
.couldBe(BArrLike
));
198 // If the key can't possibly be good, or the array is entirely a
199 // keyset (and we exclude keysets), we'll always throw.
200 if (!key
.couldBe(BArrKey
)) return ElemResult
{ TBottom
, TriBool::Yes
, false };
201 if (excludeKeyset
&& base
.subtypeAmong(BKeyset
, BArrLike
)) {
202 return ElemResult
{ TBottom
, TriBool::Yes
, false };
205 // Otherwise remove the problematic parts of the key or value. If we
206 // have to remove anything, assume we could possibly throw.
207 auto const validKey
= key
.subtypeOf(BArrKey
);
208 auto const validArr
= !excludeKeyset
|| base
.subtypeAmong(BKVish
, BArrLike
);
209 auto r
= array_like_elem(
210 validArr
? base
: intersection_of(base
, TKVish
),
211 validKey
? key
: intersection_of(key
, TArrKey
)
216 maybeOrNo(!validKey
|| !validArr
),
222 * Update the current base via array_like_newelem, and return whether
223 * the newelem can throw.
225 TriBool
array_do_newelem(ISS
& env
, const Type
& value
) {
226 auto& base
= env
.collect
.mInstrState
.base
.type
;
227 assertx(base
.couldBe(BArrLike
));
229 auto update
= array_like_newelem(base
, value
);
230 // Check for the presence of TArrLike rather than Bottom, since the
231 // base may have contained more than just TArrLike to begin with.
232 if (!update
.first
.couldBe(BArrLike
)) {
233 assertx(update
.second
);
237 base
= std::move(update
.first
);
238 return maybeOrNo(update
.second
);
241 //////////////////////////////////////////////////////////////////////
243 void setLocalForBase(ISS
& env
, Type ty
, LocalId firstKeyLoc
) {
244 assertx(mustBeInLocal(env
.collect
.mInstrState
.base
));
245 if (env
.collect
.mInstrState
.base
.locLocal
== NoLocalId
) {
246 return killLocals(env
);
248 FTRACE(4, " ${} := {}\n",
249 env
.collect
.mInstrState
.base
.locName
250 ? env
.collect
.mInstrState
.base
.locName
->data()
256 env
.collect
.mInstrState
.base
.locLocal
,
262 void setStackForBase(ISS
& env
, Type ty
) {
263 assertx(mustBeInStack(env
.collect
.mInstrState
.base
));
265 auto const locSlot
= env
.collect
.mInstrState
.base
.locSlot
;
266 FTRACE(4, " stk[{:02}] := {}\n", locSlot
, show(ty
));
267 assertx(locSlot
< env
.state
.stack
.size());
270 env
.undo
->onStackWrite(locSlot
, std::move(env
.state
.stack
[locSlot
].type
));
272 env
.state
.stack
[locSlot
] = StackElem
{ std::move(ty
), NoLocalId
};
275 void setStaticForBase(ISS
& env
, Type ty
) {
276 auto const& base
= env
.collect
.mInstrState
.base
;
277 assertx(mustBeInStatic(base
));
279 auto const nameTy
= base
.locName
? sval(base
.locName
) : TStr
;
281 4, " ({})::$({}) |= {}\n",
282 show(base
.locTy
), show(nameTy
), show(ty
)
285 env
.index
.merge_static_type(
287 env
.collect
.publicSPropMutations
,
291 remove_uninit(std::move(ty
))
295 // Run backwards through an array chain doing array_set operations
296 // to produce the array type that incorporates the effects of any
297 // intermediate defining dims.
298 Type
currentChainType(ISS
& env
, Type val
) {
299 auto it
= env
.collect
.mInstrState
.arrayChain
.end();
300 while (it
!= env
.collect
.mInstrState
.arrayChain
.begin()) {
301 if (val
.is(BBottom
)) break;
303 assertx(it
->base
.subtypeOf(BArrLike
));
304 assertx(it
->key
.subtypeOf(BArrKey
));
305 val
= array_like_set(it
->base
, it
->key
, val
).first
;
310 Type
resolveArrayChain(ISS
& env
, Type val
) {
311 static UNUSED
const char prefix
[] = " ";
312 FTRACE(5, "{}chain {}\n", prefix
, show(val
));
314 if (val
.is(BBottom
)) {
315 // If val is Bottom, the update isn't actually going to happen,
316 // so we don't need to unwind the chain.
317 env
.collect
.mInstrState
.arrayChain
.clear();
320 auto arr
= std::move(env
.collect
.mInstrState
.arrayChain
.back().base
);
321 auto key
= std::move(env
.collect
.mInstrState
.arrayChain
.back().key
);
322 env
.collect
.mInstrState
.arrayChain
.pop_back();
323 FTRACE(5, "{} | {} := {} in {}\n", prefix
,
324 show(key
), show(val
), show(arr
));
325 assertx(arr
.subtypeOf(BArrLike
));
326 assertx(key
.subtypeOf(BArrKey
));
327 val
= array_like_set(std::move(arr
), key
, val
).first
;
328 } while (!env
.collect
.mInstrState
.arrayChain
.empty());
329 FTRACE(5, "{} = {}\n", prefix
, show(val
));
333 // Returns true if the base update can be considered "effect-free"
334 // (IE, updating a static prop base is a side-effect because its
335 // visible elsewhere. Updating a local or stack slot is not).
336 bool updateBaseWithType(ISS
& env
,
338 LocalId firstKeyLoc
= NoLocalId
) {
339 FTRACE(6, " updateBaseWithType: {}\n", show(ty
));
341 if (ty
.subtypeOf(BBottom
)) return true;
343 auto const& base
= env
.collect
.mInstrState
.base
;
345 if (mustBeInLocal(base
)) {
346 setLocalForBase(env
, ty
, firstKeyLoc
);
347 // If we're speculating, a local update is considered a
349 return !any(env
.collect
.opts
& CollectionOpts::Speculating
);
351 if (mustBeInStack(base
)) {
352 setStackForBase(env
, ty
);
355 if (mustBeInStatic(base
)) {
356 setStaticForBase(env
, ty
);
363 // Whether its worthwhile to refine the base's type based on knowing
364 // that certain types would have fatalled (and therefore the base can
365 // no longer be those types after the op). This is only worthwhile if
366 // the base is flow sensitive (IE, a local or stack slot). This is
367 // just an optimization so its always legal to say no.
368 bool shouldRefineBase(ISS
& env
) {
369 auto const& base
= env
.collect
.mInstrState
.base
;
370 return mustBeInLocal(base
) || mustBeInStack(base
);
373 void startBase(ISS
& env
, Base base
) {
374 auto& oldState
= env
.collect
.mInstrState
;
375 assertx(oldState
.base
.loc
== BaseLoc::None
);
376 assertx(oldState
.arrayChain
.empty());
377 assertx(isInitialBaseLoc(base
.loc
));
378 assertx(!base
.type
.subtypeOf(TBottom
));
380 oldState
.effectFree
= env
.flags
.effectFree
;
381 oldState
.extraPop
= false;
382 oldState
.base
= std::move(base
);
383 FTRACE(5, " startBase: {}\n", show(*env
.ctx
.func
, oldState
.base
));
386 // Return true if the base is updated and that update is considered
387 // "effect-free" (see updateBaseWithType).
388 bool endBase(ISS
& env
, bool update
= true, LocalId keyLoc
= NoLocalId
) {
389 auto& state
= env
.collect
.mInstrState
;
390 assertx(state
.base
.loc
!= BaseLoc::None
);
392 FTRACE(5, " endBase: {}\n", show(*env
.ctx
.func
, state
.base
));
394 auto const firstKeyLoc
= state
.arrayChain
.empty()
396 : state
.arrayChain
.data()->keyLoc
;
397 auto const& ty
= state
.arrayChain
.empty()
399 : resolveArrayChain(env
, state
.base
.type
);
401 auto const effectFree
= update
402 ? updateBaseWithType(env
, ty
, firstKeyLoc
)
404 state
.base
.loc
= BaseLoc::None
;
408 // Return true if the base is updated and that update is considered
409 // "effect-free" (see updateBaseWithType).
410 bool moveBase(ISS
& env
,
413 LocalId keyLoc
= NoLocalId
) {
414 auto& state
= env
.collect
.mInstrState
;
415 assertx(state
.base
.loc
!= BaseLoc::None
);
416 assertx(isDimBaseLoc(newBase
.loc
));
417 assertx(!state
.base
.type
.subtypeOf(BBottom
));
419 FTRACE(5, " moveBase: {} -> {}\n",
420 show(*env
.ctx
.func
, state
.base
),
421 show(*env
.ctx
.func
, newBase
));
423 auto const firstKeyLoc
= state
.arrayChain
.empty()
425 : state
.arrayChain
.data()->keyLoc
;
426 auto const& ty
= state
.arrayChain
.empty()
428 : resolveArrayChain(env
, state
.base
.type
);
430 auto const effectFree
= update
431 ? updateBaseWithType(env
, ty
, firstKeyLoc
)
433 state
.base
= std::move(newBase
);
437 // Return true if the base is updated and that update is considered
438 // "effect-free" (see updateBaseWithType).
439 bool extendArrChain(ISS
& env
, Type key
, Type arr
,
440 Type val
, LocalId keyLoc
= NoLocalId
) {
441 auto& state
= env
.collect
.mInstrState
;
442 assertx(state
.base
.loc
!= BaseLoc::None
);
443 // NB: The various array operation functions can accept arbitrary
444 // types as long as they contain TArrLike. However we still do not
445 // allow putting anything but TArrLike in the chain, since (in
446 // general) its not clear how to deal with the other types when
447 // resolving the chain. Currently the only case where we set up
448 // array chains is ElemD or ElemU. For ElemD, most of the common
449 // other types just fatal, so we can simply remove them (including
450 // TInitNull). For ElemU we do the same thing, but less types
452 assertx(arr
.subtypeOf(BArrLike
));
453 assertx(key
.subtypeOf(BArrKey
));
454 assertx(!state
.base
.type
.subtypeOf(BBottom
));
455 assertx(!val
.subtypeOf(BBottom
));
456 assertx(!key
.subtypeOf(BBottom
));
458 state
.arrayChain
.emplace_back(
459 CollectedInfo::MInstrState::ArrayChainEnt
{
465 state
.base
.type
= std::move(val
);
467 auto const firstKeyLoc
= state
.arrayChain
.data()->keyLoc
;
469 FTRACE(5, " extendArrChain: {}\n", show(*env
.ctx
.func
, state
));
470 return updateBaseWithType(
472 currentChainType(env
, state
.base
.type
),
477 //////////////////////////////////////////////////////////////////////
479 // Returns nullptr if it's an unknown key or not a string.
480 SString
mStringKey(const Type
& key
) {
481 auto const v
= tv(key
);
482 return v
&& v
->m_type
== KindOfPersistentString
? v
->m_data
.pstr
: nullptr;
485 template<typename Op
>
486 auto update_mkey(const Op
& op
) { return false; }
488 template<typename Op
>
489 auto update_mkey(Op
& op
) -> decltype(op
.mkey
, true) {
490 switch (op
.mkey
.mcode
) {
491 case MEC
: case MPC
: {
500 template<typename Op
>
501 auto update_discard(const Op
& op
) { return false; }
503 template<typename Op
>
504 auto update_discard(Op
& op
) -> decltype(op
.arg1
, true) {
510 * Return the type of the key and whether any promotions happened, or
511 * reduce op and return std::nullopt. Note that when std::nullopt is
512 * returned, there is nothing further to do.
514 template<typename Op
>
515 Optional
<std::pair
<Type
,Promotion
>> key_type_or_fixup(ISS
& env
, Op op
) {
516 if (env
.collect
.mInstrState
.extraPop
) {
517 auto const mkey
= update_mkey(op
);
518 if (update_discard(op
) || mkey
) {
519 env
.collect
.mInstrState
.extraPop
= false;
521 env
.collect
.mInstrState
.extraPop
= true;
525 auto const fixup
= [&] (Type ty
, bool isProp
, bool couldBeUninit
)
526 -> Optional
<std::pair
<Type
,Promotion
>> {
528 // Handle any classlike key promotions
529 auto promoted
= promote_classlike_to_key(std::move(ty
));
530 // We could also promote and potentially throw if we had an uninit
532 if (couldBeUninit
) promoted
.second
= Promotion::YesMightThrow
;
533 // If we might throw, we don't want to const prop the key
534 if (promoted
.second
== Promotion::YesMightThrow
) return promoted
;
536 if (auto const val
= tv(promoted
.first
)) {
537 if (isStringType(val
->m_type
)) {
538 op
.mkey
.mcode
= isProp
? MPT
: MET
;
539 op
.mkey
.litstr
= val
->m_data
.pstr
;
543 if (!isProp
&& val
->m_type
== KindOfInt64
) {
545 op
.mkey
.int64
= val
->m_data
.num
;
552 switch (op
.mkey
.mcode
) {
554 return fixup(topC(env
, op
.mkey
.idx
), op
.mkey
.mcode
== MPC
, false);
555 case MEL
: case MPL
: {
556 auto couldBeUninit
= true;
557 if (!peekLocCouldBeUninit(env
, op
.mkey
.local
.id
)) {
558 couldBeUninit
= false;
559 auto const minLocEquiv
= findMinLocEquiv(env
, op
.mkey
.local
.id
, false);
560 if (minLocEquiv
!= NoLocalId
) {
561 op
.mkey
.local
= NamedLocal
{ kInvalidLocalName
, minLocEquiv
};
567 locAsCell(env
, op
.mkey
.local
.id
),
568 op
.mkey
.mcode
== MPL
,
573 return std::make_pair(TBottom
, Promotion::No
);
575 return std::make_pair(ival(op
.mkey
.int64
), Promotion::No
);
576 case MET
: case MPT
: case MQT
:
577 return std::make_pair(sval(op
.mkey
.litstr
), Promotion::No
);
582 template<typename Op
>
583 LocalId
key_local(ISS
& env
, Op op
) {
584 switch (op
.mkey
.mcode
) {
586 return topStkLocal(env
, op
.mkey
.idx
);
588 return op
.mkey
.local
.id
;
591 case MET
: case MPT
: case MQT
:
597 //////////////////////////////////////////////////////////////////////
599 // Helper function for ending the base when we know this member
600 // instruction will always throw.
601 Effects
endUnreachableBase(ISS
& env
, Promotion basePromo
,
602 LocalId keyLoc
= NoLocalId
) {
603 // If we promoted the base, we still need to reflect that
604 if (basePromo
!= Promotion::No
) endBase(env
, true, keyLoc
);
605 return Effects::AlwaysThrows
;
608 // Helper function for ending the base. Takes the current aggregated
609 // effects up until this point, and whether the base underwent any
610 // promotions. Returns an updated aggregated effects, taking into
611 // account any effects from the base write-back. The given lambda will
612 // be called after the base is updated (this is needed because you
613 // don't want to modify the stack until after the base write back has
615 template <typename F
>
616 Effects
endBaseWithEffects(ISS
& env
, Effects effects
, bool update
,
619 LocalId keyLoc
= NoLocalId
) {
620 if (effects
== Effects::AlwaysThrows
) {
621 auto const e
= endUnreachableBase(env
, basePromo
, keyLoc
);
625 // End the base. We request a base update if the caller requested,
626 // or if a base promotion happened (if the base promoted, we need to
627 // record the new type into it).
628 auto const effectFree
=
629 endBase(env
, update
|| basePromo
!= Promotion::No
, keyLoc
);
631 // If we might throw because of base promotion, it doesn't matter
632 // what other effects are.
633 if (basePromo
== Promotion::YesMightThrow
) return Effects::Throws
;
634 // Special case: if we don't have any other effects, but writing to
635 // the base is side-effectful, then we can be nothrow (but not
637 if (!effectFree
&& effects
== Effects::None
) return Effects::SideEffect
;
641 //////////////////////////////////////////////////////////////////////
643 // Helper function for Elem (not ElemD or ElemU) operations. Luckily
644 // the logic for the Dim portion, and the final operation are
645 // identical, so we can treat them as the same. Return the elem type
646 // and what effects the access has.
647 std::pair
<Type
, Effects
> elemHelper(ISS
& env
, MOpMode mode
, Type key
) {
648 assertx(mode
== MOpMode::None
||
649 mode
== MOpMode::Warn
||
650 mode
== MOpMode::InOut
);
652 auto& base
= env
.collect
.mInstrState
.base
.type
;
653 assertx(!base
.is(BBottom
));
655 // If we're using MOpMode::InOut, then the base has to be a ArrLike
657 auto inOutFail
= false;
658 if (mode
== MOpMode::InOut
) {
659 if (!base
.couldBe(BArrLike
)) return { TBottom
, Effects::AlwaysThrows
};
660 if (!base
.subtypeOf(BArrLike
)) inOutFail
= true;
663 auto const warnsWithNull
=
664 BTrue
| BNum
| BRes
| BFunc
| BRFunc
| BRClsMeth
| BClsMeth
;
665 auto const justNull
= BNull
| BFalse
;
666 auto const DEBUG_ONLY handled
=
667 warnsWithNull
| justNull
| BClsMeth
|
668 BStr
| BCls
| BLazyCls
| BObj
| BRecord
| BArrLike
;
670 static_assert(handled
== BCell
);
675 // These emit a warning and push InitNull
676 if (base
.couldBe(warnsWithNull
)) {
677 effects
= unionEffects(effects
, Effects::Throws
);
680 // These silently push InitNull
681 if (base
.couldBe(justNull
)) {
682 effects
= unionEffects(
684 inOutFail
? Effects::Throws
: Effects::None
688 // Strings will return a static string (a character from itself or
689 // an empty string). Class-likes will convert to its equivalent string
691 if (base
.couldBe(BStr
| BCls
| BLazyCls
)) {
692 auto const isNoThrow
=
694 mode
!= MOpMode::Warn
&&
695 key
.subtypeOf(BArrKey
) &&
696 (!base
.couldBe(BCls
| BLazyCls
) || !RO::EvalRaiseClassConversionWarning
);
697 effects
= unionEffects(
699 isNoThrow
? Effects::None
: Effects::Throws
703 // These can throw and push anything.
704 if (base
.couldBe(BObj
| BRecord
)) {
705 effects
= unionEffects(effects
, Effects::Throws
);
708 // If it's an array, we can determine the exact element.
709 if (base
.couldBe(BArrLike
)) {
710 auto elem
= array_do_elem(env
, key
);
711 if (elem
.throws
== TriBool::Yes
) {
713 effects
= unionEffects(effects
, Effects::AlwaysThrows
);
715 auto mightThrow
= elem
.throws
!= TriBool::No
;
716 // If the mode is MOpMode::None, we'll use TInitNull if the key
717 // is missing. Otherwise, we'll throw.
719 if (mode
== MOpMode::None
) {
720 elem
.elem
|= TInitNull
;
726 if (elem
.elem
.is(BBottom
)) {
727 // Key definitely doesn't exist. We'll always throw. Note that
728 // this can't happen if mode is MOpMode::None because we added
729 // TInitNull to the type above.
730 effects
= unionEffects(effects
, Effects::AlwaysThrows
);
732 ty
|= std::move(elem
.elem
);
733 effects
= unionEffects(
735 !mightThrow
&& !inOutFail
? Effects::None
: Effects::Throws
741 assertx(effects
.has_value());
742 return { std::move(ty
), *effects
};
745 // Helper function for SetOpElem/IncDecElem final operations. Reads
746 // the element in the base given by the key, performs the operation
747 // using `op', then writes the new value back to the base. Returns the
748 // aggregates effects of the entire operation.
749 template <typename F
>
750 Effects
setOpElemHelper(ISS
& env
, int32_t nDiscard
, const Type
& key
,
751 LocalId keyLoc
, F op
) {
753 auto& base
= env
.collect
.mInstrState
.base
.type
;
754 assertx(!base
.is(BBottom
));
756 auto const throws
= BNull
| BFalse
| BStr
;
758 BTrue
| BNum
| BRes
| BFunc
| BRFunc
|
759 BCls
| BLazyCls
| BClsMeth
| BRClsMeth
;
760 auto const handled
= throws
| null
| BArrLike
| BObj
| BRecord
;
762 static_assert(handled
== BCell
);
765 auto pushed
= TBottom
;
766 auto refine
= BBottom
;
770 if (base
.couldBe(throws
)) {
771 effects
= unionEffects(effects
, Effects::AlwaysThrows
);
774 // Raises a warning and pushes null
775 if (base
.couldBe(null
)) {
776 effects
= unionEffects(effects
, Effects::Throws
);
779 // Objects can throw and push anything
780 if (base
.couldBe(BObj
| BRecord
)) {
781 effects
= unionEffects(effects
, Effects::Throws
);
784 // Records can throw and push anything. Since we might be mutating
785 // the record, record an update.
786 if (base
.couldBe(BRecord
)) {
787 effects
= unionEffects(effects
, Effects::Throws
);
791 if (base
.couldBe(BArrLike
)) {
792 auto const unreachable
= [&] {
793 effects
= unionEffects(effects
, Effects::AlwaysThrows
);
798 // For the array portion, we can analyze the effects precisely
799 auto elem
= array_do_elem(env
, key
, true);
800 if (elem
.elem
.is(BBottom
) || elem
.throws
== TriBool::Yes
) {
801 // Element doesn't exist or key is bad. Always throws.
806 // Keysets will throw, so we can assume the base does not
807 // contain them afterwards.
808 if (base
.couldBe(BKeyset
)) base
= remove_keyset(std::move(base
));
810 // We'll have already have COWed the array if the setop throws,
811 // so we need to manually remove staticness and force a base
813 base
= loosen_array_staticness(std::move(base
));
817 auto [toSet
, toPush
, opEffects
] = op(std::move(elem
.elem
));
818 if (opEffects
== Effects::AlwaysThrows
) {
819 // The op will always throw. We've already COWed the base, so we
820 // still need to update the base type.
825 // Write the element back into the array
826 auto const set
= array_do_set(env
, key
, toSet
);
827 if (set
== TriBool::Yes
) {
828 // The set will always throw. In theory this can happen if we do
829 // something like read a string out of a keyset, turn it into
830 // something that's not an array key, then try to write it back.
835 auto const maybeThrows
=
837 elem
.throws
== TriBool::Maybe
||
838 set
== TriBool::Maybe
;
839 effects
= unionEffects(
841 maybeThrows
? Effects::Throws
: opEffects
843 pushed
|= std::move(toPush
);
847 // Refine the base and remove bits that will always throw
848 if (refine
&& shouldRefineBase(env
)) {
849 base
= remove_bits(std::move(base
), refine
);
853 assertx(effects
.has_value());
855 // We have to update the base even if we'll always throw to account
856 // for potential COWing of the array (it could be in a static for
858 if (*effects
== Effects::AlwaysThrows
&& update
) {
859 assertx(pushed
.is(BBottom
));
860 endBase(env
, true, keyLoc
);
861 discard(env
, nDiscard
);
863 return Effects::AlwaysThrows
;
866 return endBaseWithEffects(
867 env
, *effects
, update
, Promotion::No
,
869 discard(env
, nDiscard
);
870 push(env
, std::move(pushed
));
876 // Helper function for SetOpNewElem/IncDecNewElemm final
877 // operations. These all either throw or push null.
878 Effects
setOpNewElemHelper(ISS
& env
, int32_t nDiscard
) {
879 auto& base
= env
.collect
.mInstrState
.base
.type
;
880 assertx(!base
.is(BBottom
));
882 auto const alwaysThrow
=
883 BNull
| BFalse
| BStr
| BArrLike
| BObj
| BClsMeth
| BRecord
;
885 BTrue
| BNum
| BRes
| BRFunc
| BFunc
| BRClsMeth
| BCls
| BLazyCls
;
886 auto const handled
= alwaysThrow
| null
;
888 static_assert(handled
== BCell
);
891 auto pushed
= TBottom
;
892 auto refine
= BBottom
;
895 // These always throw
896 if (base
.couldBe(alwaysThrow
)) {
897 effects
= unionEffects(effects
, Effects::AlwaysThrows
);
898 refine
|= alwaysThrow
;
900 // These raise a warning and push InitNull
901 if (base
.couldBe(null
)) {
902 effects
= unionEffects(effects
, Effects::Throws
);
906 // Refine the base and remove bits that will always throw
907 if (refine
&& shouldRefineBase(env
)) {
908 base
= remove_bits(std::move(base
), refine
);
912 assertx(effects
.has_value());
914 return endBaseWithEffects(
915 env
, *effects
, update
, Promotion::No
,
917 discard(env
, nDiscard
);
918 push(env
, std::move(pushed
));
923 //////////////////////////////////////////////////////////////////////
926 Effects
miProp(ISS
& env
, MOpMode mode
, Type key
, ReadonlyOp op
) {
927 auto const name
= mStringKey(key
);
928 auto const isDefine
= mode
== MOpMode::Define
;
929 auto const isUnset
= mode
== MOpMode::Unset
;
930 // PHP5 doesn't modify an unset local if you unset a property or
931 // array elem on it, but hhvm does (it promotes it to init-null).
932 auto const update
= isDefine
|| isUnset
;
934 * MOpMode::Unset Props doesn't promote "emptyish" things to stdClass, or
935 * affect arrays, however it can define a property on an object base. This
936 * means we don't need any couldBeInFoo logic, but if the base could actually
937 * be $this, and a declared property could be Uninit, we need to merge
940 * We're trying to handle this case correctly as far as the type inference
941 * here is concerned, but the runtime doesn't actually behave this way right
942 * now for declared properties. Note that it never hurts to merge more types
943 * than a thisProp could actually be, so this is fine.
945 * See TODO(#3602740): unset with intermediate dims on previously declared
946 * properties doesn't define them to null.
948 if (isUnset
&& couldBeThisObj(env
, env
.collect
.mInstrState
.base
)) {
950 if (auto const elem
= thisPropType(env
, name
)) {
951 if (elem
->couldBe(BUninit
)) mergeThisProp(env
, name
, TInitNull
);
954 mergeEachThisPropRaw(env
, [&] (const Type
& ty
) {
955 return ty
.couldBe(BUninit
) ? TInitNull
: TBottom
;
960 if (mustBeThisObj(env
, env
.collect
.mInstrState
.base
)) {
961 auto const optThisTy
= thisTypeFromContext(env
.index
, env
.ctx
);
962 auto const thisTy
= optThisTy
? *optThisTy
: TObj
;
964 if (checkReadonlyOp(ReadonlyOp::Mutable
, op
) &&
965 isDefinitelyThisPropAttr(env
, name
, AttrIsReadonly
)) {
966 return Effects::AlwaysThrows
;
969 auto const [ty
, effects
] = [&] () -> std::pair
<Type
, Effects
> {
971 if (auto const elem
= thisPropType(env
, name
)) {
972 return { *elem
, Effects::Throws
};
974 } else if (auto const propTy
= thisPropAsCell(env
, name
)) {
975 if (propTy
->subtypeOf(BBottom
)) {
976 return { TBottom
, Effects::AlwaysThrows
};
978 if (checkReadonlyOp(ReadonlyOp::Mutable
, op
) &&
979 isMaybeThisPropAttr(env
, name
, AttrIsReadonly
)) {
980 return { *propTy
, Effects::Throws
};
982 if (isMaybeThisPropAttr(env
, name
, AttrLateInit
)) {
983 return { *propTy
, Effects::Throws
};
985 if (mode
== MOpMode::None
) {
986 return { *propTy
, Effects::None
};
988 auto const elem
= thisPropType(env
, name
);
989 assertx(elem
.has_value());
992 elem
->couldBe(BUninit
) ? Effects::Throws
: Effects::None
996 env
.index
.lookup_public_prop(objcls(thisTy
), sval(name
));
997 return { update
? raw
: to_cell(raw
), Effects::Throws
};
1000 if (ty
.subtypeOf(BBottom
)) return Effects::AlwaysThrows
;
1003 Base
{ ty
, BaseLoc::Prop
, thisTy
, name
},
1009 Base
{ TInitCell
, BaseLoc::Prop
, thisTy
},
1011 return Effects::Throws
;
1015 // We know for sure we're going to be in an object property.
1016 if (env
.collect
.mInstrState
.base
.type
.subtypeOf(BObj
)) {
1018 env
.index
.lookup_public_prop(
1019 objcls(env
.collect
.mInstrState
.base
.type
),
1020 name
? sval(name
) : TStr
1022 auto const ty
= update
? raw
: to_cell(raw
);
1023 if (ty
.subtypeOf(BBottom
)) return Effects::AlwaysThrows
;
1027 env
.collect
.mInstrState
.base
.type
,
1030 return Effects::Throws
;
1034 * Otherwise, intermediate props with define can promote a null, false, or ""
1035 * to stdClass. Those cases, and others, if it's MOpMode::Define, will set
1036 * the base to a null value in tvScratch. The base may also legitimately be
1037 * an object and our next base is in an object property. Conservatively treat
1038 * all these cases as "possibly" being inside of an object property with
1039 * "Prop" with locType TCell.
1042 Base
{ TInitCell
, BaseLoc::Prop
, TCell
, name
},
1044 return Effects::Throws
;
1047 Effects
miElem(ISS
& env
, MOpMode mode
, Type key
, LocalId keyLoc
) {
1048 auto const isDefine
= mode
== MOpMode::Define
;
1049 auto const isUnset
= mode
== MOpMode::Unset
;
1051 if (!isDefine
&& !isUnset
) {
1052 // An Elem operation which doesn't mutate the base at all
1053 auto [elem
, effects
] = elemHelper(env
, mode
, std::move(key
));
1054 if (effects
!= Effects::AlwaysThrows
) {
1055 // Since this Dim is not mutating the base, we don't need to use
1056 // an array chain here.
1058 env
, Base
{ std::move(elem
), BaseLoc::Elem
}, false, keyLoc
1064 auto& base
= env
.collect
.mInstrState
.base
.type
;
1065 assertx(!base
.is(BBottom
));
1067 // These are similar to endBaseWithEffects, but moves the base or
1068 // extends the array chain instead.
1069 auto const move
= [&] (Type ty
, bool update
, Effects effects
) {
1070 if (effects
== Effects::AlwaysThrows
) {
1071 return endUnreachableBase(env
, Promotion::No
, keyLoc
);
1073 auto const effectFree
= moveBase(
1075 Base
{ std::move(ty
), BaseLoc::Elem
},
1079 if (!effectFree
&& effects
== Effects::None
) return Effects::SideEffect
;
1082 auto const extend
= [&] (Type ty
, Effects effects
) {
1083 assertx(effects
!= Effects::AlwaysThrows
);
1084 if (!key
.subtypeOf(BArrKey
)) {
1085 assertx(effects
== Effects::Throws
);
1086 key
= intersection_of(std::move(key
), TArrKey
);
1087 assertx(!key
.is(BBottom
));
1089 auto const effectFree
=
1090 extendArrChain(env
, std::move(key
), base
, std::move(ty
), keyLoc
);
1091 if (!effectFree
&& effects
== Effects::None
) return Effects::SideEffect
;
1096 // ElemU. These types either always throw, or set the base to
1098 auto const alwaysThrows
=
1099 BCls
| BLazyCls
| BFunc
| BRFunc
| BStr
| BClsMeth
|
1100 BRClsMeth
| BRecord
;
1101 auto const movesToUninit
= BPrim
| BRes
;
1102 auto const handled
=
1103 alwaysThrows
| movesToUninit
| BObj
| BArrLike
;
1105 static_assert(handled
== BCell
);
1107 // It is quite unfortunate that InitNull doesn't always throw, as
1108 // this means we cannot use an array chain with a nullable array
1109 // (which is common).
1110 if (base
.couldBe(BArrLike
) && base
.subtypeOf(alwaysThrows
| BArrLike
)) {
1111 auto elem
= array_do_elem(env
, key
, true);
1112 if (elem
.throws
== TriBool::Yes
) {
1113 // We'll always throw
1114 return endUnreachableBase(env
, Promotion::No
, keyLoc
);
1116 if (!elem
.present
) elem
.elem
|= TInitNull
;
1117 auto const maybeAlwaysThrows
= base
.couldBe(alwaysThrows
);
1118 // Keysets will throw, so we can assume the base does not
1119 // contain them afterwards.
1120 if (base
.couldBe(BKeyset
)) base
= remove_keyset(std::move(base
));
1121 base
= loosen_array_staticness(std::move(base
)); // Handle COW
1122 // We can safely remove the always throws bits, since if we get
1123 // to the next op, it means they weren't present.
1124 if (maybeAlwaysThrows
) base
= remove_bits(std::move(base
), alwaysThrows
);
1126 std::move(elem
.elem
),
1127 (maybeAlwaysThrows
|| elem
.throws
== TriBool::Maybe
)
1128 ? Effects::Throws
: Effects::None
1134 auto update
= false;
1135 auto refine
= BBottom
;
1137 // These always raise an error
1138 if (base
.couldBe(alwaysThrows
)) {
1139 effects
= unionEffects(effects
, Effects::AlwaysThrows
);
1140 refine
|= alwaysThrows
;
1142 // These silently set the base to uninit
1143 if (base
.couldBe(movesToUninit
)) {
1144 effects
= unionEffects(effects
, Effects::None
);
1147 // Objects can throw and retrieve anything
1148 if (base
.couldBe(BObj
)) {
1149 effects
= unionEffects(effects
, Effects::Throws
);
1152 // For arrays we can do a precise lookup
1153 if (base
.couldBe(BArrLike
)) {
1154 auto elem
= array_do_elem(env
, key
, true);
1155 if (elem
.throws
== TriBool::Yes
) {
1156 // Bad key or the element never exists. We'll always throw.
1157 effects
= unionEffects(effects
, Effects::AlwaysThrows
);
1160 if (!elem
.present
) elem
.elem
|= TInitNull
;
1161 // Keysets will throw, so we can assume the base does not
1162 // contain them afterwards.
1163 if (base
.couldBe(BKeyset
)) base
= remove_keyset(std::move(base
));
1164 // Since we're not using an array chain here, we need to
1165 // pessimize the array (since we won't be able to track
1166 // further changes to its inner structure).
1167 base
= loosen_array_staticness(loosen_array_values(std::move(base
)));
1169 effects
= unionEffects(
1171 elem
.throws
== TriBool::Maybe
1175 ty
|= std::move(elem
.elem
);
1179 // Refine the base and remove bits that will always throw
1180 if (refine
&& shouldRefineBase(env
)) {
1181 base
= remove_bits(std::move(base
), refine
);
1185 assertx(effects
.has_value());
1187 return move(std::move(ty
), update
, *effects
);
1191 // ElemD. These types either always throw, or emit a warning and
1193 auto const alwaysThrows
= BNull
| BFalse
| BStr
;
1194 auto const warnsAndNull
=
1195 BTrue
| BNum
| BRes
| BFunc
| BRFunc
| BCls
| BLazyCls
|
1196 BClsMeth
| BRClsMeth
;
1197 auto const handled
=
1198 alwaysThrows
| warnsAndNull
| BObj
| BRecord
| BArrLike
;
1200 static_assert(handled
== BCell
);
1202 // As a special case, if we just have an array, we can extend an
1203 // array chain and track the modifications to the inner array
1204 // structure. We also allow the types which always throw since
1205 // they don't affect the structure (if we get one at runtime,
1206 // we'll just throw without modifying anything).
1207 if (base
.couldBe(BArrLike
) && base
.subtypeOf(alwaysThrows
| BArrLike
)) {
1208 auto elem
= array_do_elem(env
, key
, true);
1209 if (elem
.elem
.is(BBottom
) || elem
.throws
== TriBool::Yes
) {
1210 // We'll always throw
1211 return endUnreachableBase(env
, Promotion::No
, keyLoc
);
1213 auto const maybeAlwaysThrows
= base
.couldBe(alwaysThrows
);
1214 // Keysets will throw, so we can assume the base does not
1215 // contain them afterwards.
1216 if (base
.couldBe(BKeyset
)) base
= remove_keyset(std::move(base
));
1217 base
= loosen_array_staticness(std::move(base
)); // Handle COW
1218 // We can safely remove the always throws bits, since if we get
1219 // to the next op, it means they weren't present.
1220 if (maybeAlwaysThrows
) base
= remove_bits(std::move(base
), alwaysThrows
);
1221 auto const mightThrow
=
1222 maybeAlwaysThrows
||
1224 elem
.throws
== TriBool::Maybe
;
1226 std::move(elem
.elem
),
1227 mightThrow
? Effects::Throws
: Effects::None
1233 auto update
= false;
1234 auto refine
= BBottom
;
1236 // These always raise an error
1237 if (base
.couldBe(alwaysThrows
)) {
1238 effects
= unionEffects(effects
, Effects::AlwaysThrows
);
1239 refine
|= alwaysThrows
;
1241 // These emit a warning and push InitNull
1242 if (base
.couldBe(warnsAndNull
)) {
1243 effects
= unionEffects(effects
, Effects::Throws
);
1246 // Objects can throw and push anything
1247 if (base
.couldBe(BObj
)) {
1248 effects
= unionEffects(effects
, Effects::Throws
);
1251 // Records can throw and push anything as well. Since we might be
1252 // mutating the record, we need to perform an update.
1253 if (base
.couldBe(BRecord
)) {
1254 effects
= unionEffects(effects
, Effects::Throws
);
1258 // For arrays we can lookup the specific element
1259 if (base
.couldBe(BArrLike
)) {
1260 auto elem
= array_do_elem(env
, key
, true);
1261 if (elem
.elem
.is(BBottom
) || elem
.throws
== TriBool::Yes
) {
1262 // Bad key or the element never exists. We'll always throw.
1263 effects
= unionEffects(effects
, Effects::AlwaysThrows
);
1266 // Keysets will throw, so we can assume the base does not
1267 // contain them afterwards.
1268 if (base
.couldBe(BKeyset
)) base
= remove_keyset(std::move(base
));
1269 // Since we're not using an array chain here, we need to
1270 // pessimize the array (since we won't be able to track
1271 // further changes to its inner structure).
1272 base
= loosen_array_staticness(loosen_array_values(std::move(base
)));
1274 effects
= unionEffects(
1276 (!elem
.present
|| elem
.throws
== TriBool::Maybe
)
1280 ty
|= std::move(elem
.elem
);
1284 // Refine the base and remove bits that will always throw
1285 if (refine
&& shouldRefineBase(env
)) {
1286 base
= remove_bits(std::move(base
), refine
);
1290 assertx(effects
.has_value());
1292 return move(std::move(ty
), update
, *effects
);
1296 Effects
miNewElem(ISS
& env
) {
1297 // NewElem. These all either throw or raise a warning and set the
1299 auto& base
= env
.collect
.mInstrState
.base
.type
;
1300 assertx(!base
.is(BBottom
));
1302 auto const alwaysThrows
=
1303 BNull
| BFalse
| BArrLike
| BObj
| BClsMeth
| BRecord
;
1305 BTrue
| BNum
| BRes
| BRFunc
| BFunc
|
1306 BRClsMeth
| BCls
| BLazyCls
;
1307 auto const handled
= alwaysThrows
| uninit
| BStr
;
1309 static_assert(handled
== BCell
);
1312 auto newBase
= TBottom
;
1313 auto update
= false;
1314 auto refine
= BBottom
;
1316 if (base
.couldBe(alwaysThrows
)) {
1317 effects
= unionEffects(effects
, Effects::AlwaysThrows
);
1318 refine
|= alwaysThrows
;
1320 if (base
.couldBe(uninit
)) {
1321 effects
= unionEffects(effects
, Effects::Throws
);
1324 if (base
.couldBe(BStr
)) {
1325 if (is_specialized_string(base
) && sval_of(base
)->empty()) {
1326 effects
= unionEffects(effects
, Effects::AlwaysThrows
);
1329 effects
= unionEffects(effects
, Effects::Throws
);
1334 // Refine the base and remove bits that will always throw
1335 if (refine
&& shouldRefineBase(env
)) {
1336 base
= remove_bits(std::move(base
), refine
);
1340 assertx(effects
.has_value());
1341 assertx(*effects
== Effects::Throws
|| *effects
== Effects::AlwaysThrows
);
1342 if (*effects
!= Effects::AlwaysThrows
) {
1343 moveBase(env
, Base
{ std::move(newBase
), BaseLoc::Elem
}, update
);
1348 //////////////////////////////////////////////////////////////////////
1351 Effects
miFinalIssetProp(ISS
& env
, int32_t nDiscard
, const Type
& key
) {
1352 auto const name
= mStringKey(key
);
1353 discard(env
, nDiscard
);
1355 if (name
&& mustBeThisObj(env
, env
.collect
.mInstrState
.base
)) {
1356 if (auto const pt
= thisPropAsCell(env
, name
)) {
1357 if (isMaybeThisPropAttr(env
, name
, AttrLateInit
)) {
1358 // LateInit props can always be maybe unset, except if its never set at
1360 push(env
, pt
->subtypeOf(BBottom
) ? TFalse
: TBool
);
1361 } else if (pt
->subtypeOf(BNull
)) {
1363 } else if (!pt
->couldBe(BNull
)) {
1368 return Effects::None
;
1373 return Effects::Throws
;
1376 Effects
miFinalCGetProp(ISS
& env
, int32_t nDiscard
, const Type
& key
,
1377 bool quiet
, bool mustBeMutable
) {
1378 assertx(!mustBeMutable
|| RO::EvalEnableReadonlyPropertyEnforcement
);
1379 auto const name
= mStringKey(key
);
1380 discard(env
, nDiscard
);
1382 auto const& base
= env
.collect
.mInstrState
.base
.type
;
1385 if (mustBeThisObj(env
, env
.collect
.mInstrState
.base
)) {
1386 if (auto t
= thisPropAsCell(env
, name
)) {
1387 if (t
->subtypeOf(BBottom
)) {
1389 return Effects::AlwaysThrows
;
1391 push(env
, std::move(*t
));
1392 if (mustBeMutable
&&
1393 isMaybeThisPropAttr(env
, name
, AttrIsReadonly
)) {
1394 return Effects::Throws
;
1396 if (isMaybeThisPropAttr(env
, name
, AttrLateInit
)) {
1397 return Effects::Throws
;
1399 if (quiet
) return Effects::None
;
1400 auto const elem
= thisPropType(env
, name
);
1401 assertx(elem
.has_value());
1402 return elem
->couldBe(BUninit
) ? Effects::Throws
: Effects::None
;
1406 if (!base
.couldBe(BObj
)) return TInitNull
;
1408 env
.index
.lookup_public_prop(
1409 objcls(base
.subtypeOf(BObj
) ? base
: intersection_of(base
, TObj
)),
1413 if (!base
.subtypeOf(BObj
)) t
= opt(std::move(t
));
1417 ty
.subtypeOf(BBottom
) ? Effects::AlwaysThrows
: Effects::Throws
;
1418 push(env
, std::move(ty
));
1422 push(env
, TInitCell
);
1423 return Effects::Throws
;
1426 Effects
miFinalSetProp(ISS
& env
, int32_t nDiscard
, const Type
& key
, ReadonlyOp op
) {
1427 auto const name
= mStringKey(key
);
1428 auto const t1
= unctx(popC(env
));
1430 auto const finish
= [&](Type ty
) {
1432 discard(env
, nDiscard
);
1433 push(env
, std::move(ty
));
1434 return Effects::Throws
;
1437 auto const alwaysThrows
= [&] {
1438 discard(env
, nDiscard
);
1440 return Effects::AlwaysThrows
;
1443 if (checkReadonlyOp(ReadonlyOp::Readonly
, op
) &&
1444 !isMaybeThisPropAttr(env
, name
, AttrIsReadonly
)) {
1445 return alwaysThrows();
1448 if (couldBeThisObj(env
, env
.collect
.mInstrState
.base
)) {
1450 mergeEachThisPropRaw(
1452 [&] (const Type
& propTy
) {
1453 return propTy
.couldBe(BInitCell
) ? t1
: TBottom
;
1457 mergeThisProp(env
, name
, t1
);
1461 if (env
.collect
.mInstrState
.base
.type
.subtypeOf(BObj
)) {
1462 if (t1
.subtypeOf(BBottom
)) return alwaysThrows();
1465 Base
{ t1
, BaseLoc::Prop
, env
.collect
.mInstrState
.base
.type
, name
}
1470 moveBase(env
, Base
{ TInitCell
, BaseLoc::Prop
, TCell
, name
});
1471 return finish(TInitCell
);
1474 Effects
miFinalSetOpProp(ISS
& env
, int32_t nDiscard
,
1475 SetOpOp subop
, const Type
& key
) {
1476 auto const name
= mStringKey(key
);
1477 auto const rhsTy
= popC(env
);
1479 auto const& base
= env
.collect
.mInstrState
.base
.type
;
1481 if (!base
.couldBe(BObj
)) {
1483 discard(env
, nDiscard
);
1484 push(env
, TInitNull
);
1485 return Effects::Throws
;
1490 if (mustBeThisObj(env
, env
.collect
.mInstrState
.base
)) {
1491 if (auto const t
= thisPropAsCell(env
, name
)) return *t
;
1494 env
.index
.lookup_public_prop(
1495 objcls(base
.subtypeOf(BObj
) ? base
: intersection_of(base
, TObj
)),
1502 if (lhsTy
.subtypeOf(BBottom
)) {
1503 discard(env
, nDiscard
);
1505 return Effects::AlwaysThrows
;
1507 if (!base
.subtypeOf(BObj
)) lhsTy
= opt(std::move(lhsTy
));
1509 auto const resultTy
= base
.subtypeOf(BObj
)
1510 ? typeSetOp(subop
, lhsTy
, rhsTy
)
1513 if (resultTy
.subtypeOf(BBottom
)) {
1514 discard(env
, nDiscard
);
1516 return Effects::AlwaysThrows
;
1519 if (couldBeThisObj(env
, env
.collect
.mInstrState
.base
)) {
1521 mergeThisProp(env
, name
, resultTy
);
1528 discard(env
, nDiscard
);
1529 push(env
, resultTy
);
1530 return Effects::Throws
;
1533 Effects
miFinalIncDecProp(ISS
& env
, int32_t nDiscard
,
1534 IncDecOp subop
, const Type
& key
) {
1535 auto const name
= mStringKey(key
);
1537 auto const& base
= env
.collect
.mInstrState
.base
.type
;
1539 if (!base
.couldBe(BObj
)) {
1541 discard(env
, nDiscard
);
1542 push(env
, TInitNull
);
1543 return Effects::Throws
;
1546 auto postPropTy
= [&] {
1548 if (mustBeThisObj(env
, env
.collect
.mInstrState
.base
)) {
1549 if (auto const t
= thisPropAsCell(env
, name
)) return *t
;
1552 env
.index
.lookup_public_prop(
1553 objcls(base
.subtypeOf(BObj
) ? base
: intersection_of(base
, TObj
)),
1560 if (postPropTy
.subtypeOf(BBottom
)) {
1561 discard(env
, nDiscard
);
1563 return Effects::AlwaysThrows
;
1565 if (!base
.subtypeOf(BObj
)) postPropTy
= opt(std::move(postPropTy
));
1567 auto const prePropTy
= base
.subtypeOf(TObj
)
1568 ? typeIncDec(subop
, postPropTy
)
1570 if (prePropTy
.subtypeOf(BBottom
)) {
1571 discard(env
, nDiscard
);
1573 return Effects::AlwaysThrows
;
1576 if (couldBeThisObj(env
, env
.collect
.mInstrState
.base
)) {
1578 mergeThisProp(env
, name
, prePropTy
);
1585 discard(env
, nDiscard
);
1586 push(env
, isPre(subop
) ? prePropTy
: postPropTy
);
1587 return Effects::Throws
;
1590 Effects
miFinalUnsetProp(ISS
& env
, int32_t nDiscard
, const Type
& key
) {
1591 if (couldBeThisObj(env
, env
.collect
.mInstrState
.base
)) {
1592 if (auto const name
= mStringKey(key
)) {
1593 unsetThisProp(env
, name
);
1595 unsetUnknownThisProp(env
);
1600 discard(env
, nDiscard
);
1601 return Effects::Throws
;
1604 //////////////////////////////////////////////////////////////////////
1607 Effects
miFinalCGetElem(ISS
& env
, int32_t nDiscard
,
1608 const Type
& key
, MOpMode mode
) {
1609 auto [elem
, effects
] = elemHelper(env
, mode
, key
);
1610 discard(env
, nDiscard
);
1611 push(env
, std::move(elem
));
1615 Effects
miFinalIssetElem(ISS
& env
,
1618 auto& base
= env
.collect
.mInstrState
.base
.type
;
1619 assertx(!base
.is(BBottom
));
1621 auto const pushesFalse
=
1622 BNull
| BBool
| BNum
| BRes
| BFunc
| BRFunc
| BRClsMeth
| BClsMeth
;
1623 auto const handled
= pushesFalse
| BArrLike
;
1626 auto pushed
= TBottom
;
1628 if (base
.couldBe(pushesFalse
)) {
1629 effects
= unionEffects(effects
, Effects::None
);
1632 if (base
.couldBe(BArrLike
)) {
1633 auto const elem
= array_do_elem(env
, key
);
1634 if (elem
.throws
== TriBool::Yes
) {
1635 effects
= unionEffects(effects
, Effects::AlwaysThrows
);
1637 effects
= unionEffects(
1639 elem
.throws
== TriBool::Maybe
1644 if (elem
.elem
.subtypeOf(BNull
)) {
1646 } else if (elem
.present
&& !elem
.elem
.couldBe(BNull
)) {
1654 if (!base
.subtypeOf(handled
)) {
1655 effects
= unionEffects(effects
, Effects::Throws
);
1659 assertx(effects
.has_value());
1660 discard(env
, nDiscard
);
1661 push(env
, std::move(pushed
));
1665 Effects
miFinalSetElem(ISS
& env
,
1669 auto const rhs
= popC(env
);
1671 auto& base
= env
.collect
.mInstrState
.base
.type
;
1672 assertx(!base
.is(BBottom
));
1674 auto const alwaysThrows
= BNull
| BFalse
;
1675 auto const pushesNull
=
1676 BTrue
| BNum
| BRes
| BFunc
| BRFunc
|
1677 BCls
| BLazyCls
| BClsMeth
| BRClsMeth
;
1678 auto const handled
=
1679 alwaysThrows
| pushesNull
| BObj
| BRecord
| BStr
| BArrLike
;
1681 static_assert(handled
== BCell
);
1684 auto pushed
= TBottom
;
1685 auto update
= false;
1686 auto refine
= BBottom
;
1688 if (base
.couldBe(pushesNull
)) {
1689 // These emit a warning and push null
1690 effects
= unionEffects(effects
, Effects::Throws
);
1691 pushed
|= TInitNull
;
1693 if (base
.couldBe(alwaysThrows
)) {
1694 // These always raise a fatal
1695 effects
= unionEffects(effects
, Effects::AlwaysThrows
);
1696 refine
|= alwaysThrows
;
1698 if (base
.couldBe(BStr
)) {
1699 // String is a special case here. It will throw on empty strings,
1700 // and otherwise can return null or a static string (and possibly
1701 // warn). We don't bother predicting the effects of the set on the
1702 // string here, so be pessimistic and forget what we know about
1703 // the base in regards to staticness and values.
1704 if (is_specialized_string(base
) && sval_of(base
)->empty()) {
1705 effects
= unionEffects(effects
, Effects::AlwaysThrows
);
1708 effects
= unionEffects(effects
, Effects::Throws
);
1709 base
= loosen_string_staticness(loosen_string_values(std::move(base
)));
1714 if (base
.couldBe(BObj
)) {
1715 // Objects can throw but otherwise don't affect the base and push
1717 effects
= unionEffects(effects
, Effects::Throws
);
1720 if (base
.couldBe(BRecord
)) {
1721 // Records can throw but otherwise don't affect the base and push
1722 // the rhs. Record an update since we're potentially mutating it.
1723 effects
= unionEffects(effects
, Effects::Throws
);
1727 if (base
.couldBe(BArrLike
)) {
1728 // Arrays will set the value and push the right hande side of the
1729 // assignment (keysets will always fatal because you can't do a
1731 auto const doesThrow
= array_do_set(env
, key
, rhs
);
1732 if (doesThrow
== TriBool::Yes
) {
1733 effects
= unionEffects(effects
, Effects::AlwaysThrows
);
1736 effects
= unionEffects(
1738 doesThrow
== TriBool::No
? Effects::None
: Effects::Throws
1745 // Refine the base and remove bits that will always throw
1746 if (refine
&& shouldRefineBase(env
)) {
1747 base
= remove_bits(std::move(base
), refine
);
1751 assertx(effects
.has_value());
1753 return endBaseWithEffects(
1754 env
, *effects
, update
, Promotion::No
,
1756 discard(env
, nDiscard
);
1757 push(env
, std::move(pushed
));
1763 Effects
miFinalSetOpElem(ISS
& env
, int32_t nDiscard
,
1764 SetOpOp subop
, const Type
& key
,
1766 auto const rhsTy
= popC(env
);
1767 return setOpElemHelper(
1768 env
, nDiscard
, key
, keyLoc
,
1769 [&] (const Type
& lhsTy
) {
1770 auto const result
= typeSetOp(subop
, lhsTy
, rhsTy
);
1771 return std::make_tuple(result
, result
, Effects::Throws
);
1776 Effects
miFinalIncDecElem(ISS
& env
, int32_t nDiscard
,
1777 IncDecOp subop
, const Type
& key
,
1779 return setOpElemHelper(
1780 env
, nDiscard
, key
, keyLoc
,
1781 [&] (const Type
& before
) {
1782 auto const after
= typeIncDec(subop
, before
);
1783 return std::make_tuple(
1785 isPre(subop
) ? after
: before
,
1786 before
.subtypeOf(BNum
) ? Effects::None
: Effects::Throws
1792 Effects
miFinalUnsetElem(ISS
& env
, int32_t nDiscard
, const Type
& key
) {
1794 auto& base
= env
.collect
.mInstrState
.base
.type
;
1795 assertx(!base
.is(BBottom
));
1797 auto const doesNothing
= BNull
| BBool
| BNum
| BRes
;
1798 auto const alwaysThrows
=
1799 BFunc
| BRFunc
| BCls
| BLazyCls
| BStr
| BRecord
| BClsMeth
| BRClsMeth
;
1800 auto const handled
= doesNothing
| alwaysThrows
| BArrLike
| BObj
;
1802 static_assert(handled
== BCell
);
1805 auto update
= false;
1806 auto refine
= BBottom
;
1808 // These silently does nothing
1809 if (base
.couldBe(doesNothing
)) {
1810 effects
= unionEffects(effects
, Effects::None
);
1812 // These always raise an error
1813 if (base
.couldBe(alwaysThrows
)) {
1814 effects
= unionEffects(effects
, Effects::AlwaysThrows
);
1815 refine
|= alwaysThrows
;
1817 // Objects can throw but otherwise do not affect the base
1818 if (base
.couldBe(BObj
)) {
1819 effects
= unionEffects(effects
, Effects::Throws
);
1821 if (base
.couldBe(BArrLike
)) {
1822 // Unset doesn't throw for a missing element on dicts, keysets, or
1823 // darrays, only if the key is invalid. Vecs and varrays silently do
1824 // nothing for string keys, but can throw with int keys.
1825 if (!key
.couldBe(BArrKey
)) {
1826 effects
= unionEffects(effects
, Effects::AlwaysThrows
);
1829 auto e
= key
.subtypeOf(BArrKey
) ? Effects::None
: Effects::Throws
;
1830 if (base
.couldBe(BVec
) && key
.couldBe(BInt
)) e
= Effects::Throws
;
1831 effects
= unionEffects(effects
, e
);
1833 // We purposefully do not model the effects of unset on array
1834 // structure. This lets us assume that if we have array structure,
1835 // we also have no tombstones. Pessimize the base which drops array
1836 // structure and also remove emptiness information.
1837 if (!base
.subtypeAmong(BVec
, BArrLike
) || key
.couldBe(BInt
)) {
1838 base
= loosen_array_staticness(loosen_array_values(std::move(base
)));
1839 base
= loosen_emptiness(std::move(base
));
1845 // Refine the base and remove bits that will always throw
1846 if (refine
&& shouldRefineBase(env
)) {
1847 base
= remove_bits(std::move(base
), refine
);
1851 assertx(effects
.has_value());
1853 return endBaseWithEffects(
1854 env
, *effects
, update
, Promotion::No
,
1855 [&] { discard(env
, nDiscard
); }
1859 //////////////////////////////////////////////////////////////////////
1860 // Final new elem ops
1862 Effects
miFinalSetNewElem(ISS
& env
, int32_t nDiscard
) {
1863 auto const rhs
= popC(env
);
1865 auto& base
= env
.collect
.mInstrState
.base
.type
;
1866 assertx(!base
.is(BBottom
));
1868 auto const pushesNull
=
1869 BTrue
| BNum
| BRes
| BFunc
| BRFunc
| BCls
|
1870 BLazyCls
| BClsMeth
| BRClsMeth
;
1871 auto const alwaysThrows
= BNull
| BStr
| BRecord
| BFalse
;
1872 auto const handled
= pushesNull
| alwaysThrows
| BObj
| BArrLike
;
1874 static_assert(handled
== BCell
);
1877 auto pushed
= TBottom
;
1878 auto update
= false;
1879 auto refine
= BBottom
;
1881 if (base
.couldBe(pushesNull
)) {
1882 // These emit a warning and push null
1883 effects
= unionEffects(effects
, Effects::Throws
);
1884 pushed
|= TInitNull
;
1886 if (base
.couldBe(alwaysThrows
)) {
1887 // These always raise a fatal
1888 effects
= unionEffects(effects
, Effects::AlwaysThrows
);
1889 refine
|= alwaysThrows
;
1891 if (base
.couldBe(BObj
)) {
1892 // Objects can throw but otherwise don't affect the base and push
1894 effects
= unionEffects(effects
, Effects::Throws
);
1897 if (base
.couldBe(BArrLike
)) {
1898 // Arrays will add a new element and push the right hand side of the
1900 auto const doesThrow
= array_do_newelem(env
, rhs
);
1901 if (doesThrow
== TriBool::Yes
) {
1902 effects
= unionEffects(effects
, Effects::AlwaysThrows
);
1905 effects
= unionEffects(
1907 doesThrow
== TriBool::No
? Effects::None
: Effects::Throws
1914 // Refine the base and remove bits that will always throw
1915 if (refine
&& shouldRefineBase(env
)) {
1916 base
= remove_bits(std::move(base
), refine
);
1920 assertx(effects
.has_value());
1922 return endBaseWithEffects(
1923 env
, *effects
, update
, Promotion::No
,
1925 discard(env
, nDiscard
);
1926 push(env
, std::move(pushed
));
1931 Effects
miFinalSetOpNewElem(ISS
& env
, int32_t nDiscard
) {
1933 return setOpNewElemHelper(env
, nDiscard
);
1936 Effects
miFinalIncDecNewElem(ISS
& env
, int32_t nDiscard
) {
1937 return setOpNewElemHelper(env
, nDiscard
);
1940 //////////////////////////////////////////////////////////////////////
1942 // Translate the aggregated effects of the instruction into the
1943 // appropriate interp-state actions. Returns true if the instruction
1944 // is totally effect-free.
1945 bool handleEffects(ISS
& env
, Effects effects
, Promotion keyPromotion
) {
1946 auto const effectFree
= [&]{
1949 if (keyPromotion
== Promotion::YesMightThrow
) return false;
1952 case Effects::SideEffect
:
1953 if (keyPromotion
!= Promotion::YesMightThrow
) nothrow(env
);
1955 case Effects::Throws
:
1957 case Effects::AlwaysThrows
:
1961 always_assert(false);
1964 if (env
.flags
.wasPEI
&& env
.blk
.throwExit
!= NoBlockId
) {
1965 // Minstr opcodes may throw after side-effects.
1966 assertx(!effectFree
);
1967 auto const state
= with_throwable_only(env
.index
, env
.state
);
1968 env
.propagate(env
.blk
.throwExit
, &state
);
1976 namespace interp_step
{
1978 //////////////////////////////////////////////////////////////////////
1981 void in(ISS
& env
, const bc::BaseGC
& op
) {
1982 startBase(env
, Base
{TInitCell
, BaseLoc::Global
});
1985 void in(ISS
& env
, const bc::BaseGL
& op
) {
1986 mayReadLocal(env
, op
.loc1
);
1987 startBase(env
, Base
{TInitCell
, BaseLoc::Global
});
1990 void in(ISS
& env
, const bc::BaseSC
& op
) {
1991 auto tcls
= topC(env
, op
.arg2
);
1992 auto const tname
= topC(env
, op
.arg1
);
1994 // We'll raise an error if its not a class
1995 if (!tcls
.couldBe(BCls
)) return unreachable(env
);
1997 // Lookup what we know about the property
1998 auto lookup
= env
.index
.lookup_static(
2005 // If we definitely didn't find anything, we'll definitely throw
2006 if (lookup
.found
== TriBool::No
|| lookup
.ty
.subtypeOf(BBottom
)) {
2007 return unreachable(env
);
2010 // Whether we might potentially throw because of AttrConst
2011 auto mightConstThrow
= false;
2012 switch (op
.subop3
) {
2013 case MOpMode::Define
:
2014 case MOpMode::Unset
:
2015 case MOpMode::InOut
:
2016 // If its definitely const, we'll always throw. Otherwise we'll
2017 // potentially throw if there's a chance its AttrConst.
2018 if (lookup
.isConst
== TriBool::Yes
) return unreachable(env
);
2019 mightConstThrow
= lookup
.isConst
== TriBool::Maybe
;
2023 // These don't mutate the base, so AttrConst does not apply
2027 // Whether we might potentially throw because of AttrIsReadonly
2028 if (checkReadonlyOp(ReadonlyOp::Mutable
, op
.subop4
) &&
2029 lookup
.readOnly
== TriBool::Yes
) {
2030 return unreachable(env
);
2032 if (checkReadonlyOp(ReadonlyOp::CheckROCOW
, op
.subop4
) &&
2033 lookup
.readOnly
== TriBool::No
) {
2034 return unreachable(env
);
2037 auto mightNotBeCOW
=
2038 lookup
.ty
.couldBe(BCounted
) && !lookup
.ty
.subtypeOf(BArrLike
);
2040 auto const mightMutableThrow
=
2041 op
.subop4
== ReadonlyOp::Mutable
&& lookup
.readOnly
== TriBool::Maybe
;
2042 auto const mightROCOWThrow
= op
.subop4
== ReadonlyOp::CheckROCOW
&& mightNotBeCOW
;
2043 auto const mightMutROCOWThrow
= op
.subop4
== ReadonlyOp::CheckMutROCOW
&&
2044 lookup
.readOnly
== TriBool::Maybe
&& mightNotBeCOW
;
2046 auto const mightReadOnlyThrow
= checkReadonlyOp() &&
2047 (mightMutableThrow
|| mightROCOWThrow
|| mightMutROCOWThrow
);
2049 // Loading the base from a static property can be considered
2050 // effect_free if there's no possibility of throwing. This requires
2051 // a definitely found, non-AttrLateInit property with normal class
2052 // initialization, and both the class and name have to be the normal
2054 if (lookup
.found
== TriBool::Yes
&&
2055 lookup
.lateInit
== TriBool::No
&&
2056 !lookup
.classInitMightRaise
&&
2058 !mightReadOnlyThrow
&&
2059 tcls
.subtypeOf(BCls
) &&
2060 tname
.subtypeOf(BStr
)) {
2062 // If we're not mutating the base, and the base is a constant,
2063 // turn it into a BaseC with the appropriate constant on the
2065 if (op
.subop3
== MOpMode::Warn
|| op
.subop3
== MOpMode::None
) {
2066 if (auto const v
= tv(lookup
.ty
)) {
2067 reduce(env
, gen_constant(*v
), bc::BaseC
{ 0, op
.subop3
});
2068 env
.collect
.mInstrState
.extraPop
= true;
2079 std::move(lookup
.ty
),
2080 BaseLoc::StaticProp
,
2087 void in(ISS
& env
, const bc::BaseL
& op
) {
2088 auto ty
= peekLocRaw(env
, op
.nloc1
.id
);
2089 auto throws
= false;
2091 if (checkReadonlyOp(ReadonlyOp::CheckROCOW
, op
.subop3
)) {
2092 if (ty
.couldBe(BCounted
) && !ty
.subtypeOf(BArrLike
)) throws
= true;
2095 // An Uninit local base can raise a notice.
2096 if (!ty
.couldBe(BUninit
)) {
2097 // If we're not mutating the base, and the base is a constant,
2098 // turn it into a BaseC with the appropriate constant on the
2100 if (op
.subop2
== MOpMode::Warn
|| op
.subop2
== MOpMode::None
) {
2101 if (auto const v
= tv(ty
)) {
2102 reduce(env
, gen_constant(*v
), bc::BaseC
{ 0, op
.subop2
});
2103 env
.collect
.mInstrState
.extraPop
= true;
2107 // Try to find an equivalent local to use instead
2108 auto const minLocEquiv
= findMinLocEquiv(env
, op
.nloc1
.id
, false);
2109 if (minLocEquiv
!= NoLocalId
) {
2113 NamedLocal
{ kInvalidLocalName
, minLocEquiv
},
2121 if (!throws
) effect_free(env
);
2122 } else if (op
.subop2
!= MOpMode::Warn
) {
2123 // The local could be Uninit, but we won't warn about it anyways.
2124 if (!throws
) effect_free(env
);
2127 mayReadLocal(env
, op
.nloc1
.id
);
2128 if (ty
.subtypeOf(BBottom
)) return unreachable(env
);
2136 op
.nloc1
.name
!= kInvalidLocalName
2137 ? env
.ctx
.func
->locals
[op
.nloc1
.name
].name
2144 void in(ISS
& env
, const bc::BaseC
& op
) {
2145 assertx(op
.arg1
< env
.state
.stack
.size());
2146 auto ty
= topC(env
, op
.arg1
);
2147 if (ty
.subtypeOf(BBottom
)) return unreachable(env
);
2157 (uint32_t)env
.state
.stack
.size() - op
.arg1
- 1
2162 void in(ISS
& env
, const bc::BaseH
&) {
2163 auto const ty
= thisTypeNonNull(env
);
2164 if (ty
.subtypeOf(BBottom
)) return unreachable(env
);
2166 startBase(env
, Base
{ty
, BaseLoc::This
});
2169 //////////////////////////////////////////////////////////////////////
2170 // Intermediate operations
2172 void in(ISS
& env
, const bc::Dim
& op
) {
2173 auto key
= key_type_or_fixup(env
, op
);
2176 auto const effects
= [&] {
2177 if (mcodeIsProp(op
.mkey
.mcode
)) {
2178 return miProp(env
, op
.subop1
, std::move(key
->first
), op
.mkey
.rop
);
2179 } else if (mcodeIsElem(op
.mkey
.mcode
)) {
2180 return miElem(env
, op
.subop1
, std::move(key
->first
), key_local(env
, op
));
2182 return miNewElem(env
);
2186 if (effects
!= Effects::None
) {
2187 env
.collect
.mInstrState
.effectFree
= false;
2189 if (!handleEffects(env
, effects
, key
->second
)) return;
2191 // This instruction must be effect free
2192 assertx(env
.flags
.effectFree
);
2193 assertx(!env
.state
.unreachable
);
2195 // If the base is a constant, and we're not mutating the base, and
2196 // if the entire minstr sequence up until now has been effect-free,
2197 // we can remove the entire sequence up until now. We replace it
2198 // with just a BaseC on the constant pushed onto the stack.
2199 if ((op
.subop1
== MOpMode::None
|| op
.subop1
== MOpMode::Warn
) &&
2200 env
.collect
.mInstrState
.effectFree
&&
2202 is_scalar(env
.collect
.mInstrState
.base
.type
)) {
2203 // Find the base instruction which started the sequence.
2204 for (int i
= 0; ; i
++) {
2205 auto const last
= last_op(env
, i
);
2207 if (isMemberBaseOp(last
->op
)) {
2208 auto const base
= *last
;
2210 // We'll need to push the constant onto the stack. If the
2211 // sequence originally started with a BaseC (or BaseGC)
2212 // instruction, we can just pop off the original value and
2213 // replace it with the constant. This leaves all offsets the
2214 // same. If not, we push the constant and set extraPop, which
2215 // makes us increment all of the offsets when we reprocess
2217 auto const reuseStack
=
2220 case Op::BaseGC
: return base
.BaseGC
.arg1
== 0;
2221 case Op::BaseC
: return base
.BaseC
.arg1
== 0;
2222 default: return false;
2225 assertx(!env
.collect
.mInstrState
.extraPop
|| reuseStack
);
2226 auto const extraPop
= !reuseStack
|| env
.collect
.mInstrState
.extraPop
;
2227 env
.collect
.mInstrState
.clear();
2228 if (reuseStack
) reduce(env
, bc::PopC
{});
2229 auto const v
= tv(env
.collect
.mInstrState
.base
.type
);
2231 reduce(env
, gen_constant(*v
), bc::BaseC
{ 0, op
.subop1
});
2232 env
.collect
.mInstrState
.extraPop
= extraPop
;
2235 if (!isMemberDimOp(last
->op
)) break;
2240 //////////////////////////////////////////////////////////////////////
2243 const StaticString
s_classname("classname");
2244 const StaticString
s_type_structure("HH\\type_structure");
2245 const StaticString
s_type_structure_classname("HH\\type_structure_classname");
2247 void in(ISS
& env
, const bc::QueryM
& op
) {
2248 auto const key
= key_type_or_fixup(env
, op
);
2250 auto const nDiscard
= op
.arg1
;
2252 auto const effects
= [&] {
2253 if (mcodeIsProp(op
.mkey
.mcode
)) {
2254 // We don't currently do anything different for nullsafe query ops.
2255 switch (op
.subop2
) {
2256 case QueryMOp::CGet
:
2257 case QueryMOp::CGetQuiet
: {
2258 auto const check
= checkReadonlyOp(ReadonlyOp::Mutable
, op
.mkey
.rop
);
2259 return miFinalCGetProp(env
, nDiscard
, key
->first
,
2260 op
.subop2
== QueryMOp::CGetQuiet
, check
);
2262 case QueryMOp::Isset
:
2263 return miFinalIssetProp(env
, nDiscard
, key
->first
);
2264 case QueryMOp::InOut
:
2265 always_assert(false);
2267 always_assert(false);
2268 } else if (mcodeIsElem(op
.mkey
.mcode
)) {
2269 switch (op
.subop2
) {
2270 case QueryMOp::InOut
:
2271 case QueryMOp::CGet
:
2272 case QueryMOp::CGetQuiet
:
2273 return miFinalCGetElem(
2274 env
, nDiscard
, key
->first
, getQueryMOpMode(op
.subop2
)
2276 case QueryMOp::Isset
:
2277 return miFinalIssetElem(env
, nDiscard
, key
->first
);
2279 always_assert(false);
2281 // QueryMNewElem will always throw without doing any work.
2282 discard(env
, nDiscard
);
2284 return Effects::AlwaysThrows
;
2288 // Try to detect type_structure(cls_name, cns_name)['classname'] and
2289 // reduce this to type_structure_classname(cls_name, cns_name)
2290 if (mcodeIsElem(op
.mkey
.mcode
) &&
2291 op
.subop2
== QueryMOp::CGet
&&
2293 op
.mkey
.mcode
== MemberCode::MET
&&
2294 op
.mkey
.litstr
->isame(s_classname
.get())) {
2295 if (auto const last
= last_op(env
, 0)) {
2296 if (last
->op
== Op::BaseC
) {
2297 if (auto const prev
= last_op(env
, 1)) {
2298 if (prev
->op
== Op::FCallFuncD
&&
2299 prev
->FCallFuncD
.str2
->isame(s_type_structure
.get()) &&
2300 prev
->FCallFuncD
.fca
.numArgs() == 2) {
2301 auto const params
= prev
->FCallFuncD
.fca
.numArgs();
2302 rewind(env
, op
); // querym
2303 rewind(env
, 2); // basec + fcallfuncd
2304 env
.collect
.mInstrState
.clear();
2309 s_type_structure_classname
.get()
2318 if (effects
!= Effects::None
) env
.collect
.mInstrState
.effectFree
= false;
2320 // For the QueryM ops, its our responsibility to call endBase()
2321 // (unless we'll always throw).
2323 if (!handleEffects(env
, effects
, key
->second
)) {
2324 if (effects
!= Effects::AlwaysThrows
) endBase(env
, false);
2328 assertx(env
.flags
.effectFree
);
2329 assertx(!env
.state
.unreachable
);
2331 // If the QueryM produced a constant without any possible
2332 // side-ffects, we can replace the entire thing with the constant.
2333 if (env
.collect
.mInstrState
.effectFree
&& is_scalar(topC(env
))) {
2334 for (int i
= 0; ; i
++) {
2335 auto const last
= last_op(env
, i
);
2337 if (isMemberBaseOp(last
->op
)) {
2338 auto const v
= tv(topC(env
));
2341 env
.collect
.mInstrState
.clear();
2342 BytecodeVec bcs
{nDiscard
, bc::PopC
{}};
2343 bcs
.push_back(gen_constant(*v
));
2344 return reduce(env
, std::move(bcs
));
2346 if (!isMemberDimOp(last
->op
)) break;
2349 endBase(env
, false);
2352 void in(ISS
& env
, const bc::SetM
& op
) {
2353 auto const key
= key_type_or_fixup(env
, op
);
2356 auto const effects
= [&] {
2357 if (mcodeIsProp(op
.mkey
.mcode
)) {
2358 return miFinalSetProp(env
, op
.arg1
, key
->first
, op
.mkey
.rop
);
2359 } else if (mcodeIsElem(op
.mkey
.mcode
)) {
2360 return miFinalSetElem(env
, op
.arg1
, key
->first
, key_local(env
, op
));
2362 return miFinalSetNewElem(env
, op
.arg1
);
2365 handleEffects(env
, effects
, key
->second
);
2368 void in(ISS
& env
, const bc::SetRangeM
& op
) {
2372 discard(env
, op
.arg1
);
2373 auto& base
= env
.collect
.mInstrState
.base
.type
;
2374 if (!base
.couldBe(BStr
)) return unreachable(env
);
2375 base
= loosen_staticness(loosen_values(std::move(base
)));
2379 void in(ISS
& env
, const bc::IncDecM
& op
) {
2380 auto const key
= key_type_or_fixup(env
, op
);
2383 auto const effects
= [&] {
2384 if (mcodeIsProp(op
.mkey
.mcode
)) {
2385 return miFinalIncDecProp(env
, op
.arg1
, op
.subop2
, key
->first
);
2386 } else if (mcodeIsElem(op
.mkey
.mcode
)) {
2387 return miFinalIncDecElem(
2388 env
, op
.arg1
, op
.subop2
, key
->first
, key_local(env
, op
)
2391 return miFinalIncDecNewElem(env
, op
.arg1
);
2394 handleEffects(env
, effects
, key
->second
);
2397 void in(ISS
& env
, const bc::SetOpM
& op
) {
2398 auto const key
= key_type_or_fixup(env
, op
);
2401 auto const effects
= [&] {
2402 if (mcodeIsProp(op
.mkey
.mcode
)) {
2403 return miFinalSetOpProp(env
, op
.arg1
, op
.subop2
, key
->first
);
2404 } else if (mcodeIsElem(op
.mkey
.mcode
)) {
2405 return miFinalSetOpElem(
2406 env
, op
.arg1
, op
.subop2
, key
->first
, key_local(env
, op
)
2409 return miFinalSetOpNewElem(env
, op
.arg1
);
2412 handleEffects(env
, effects
, key
->second
);
2415 void in(ISS
& env
, const bc::UnsetM
& op
) {
2416 auto const key
= key_type_or_fixup(env
, op
);
2419 auto const effects
= [&] {
2420 if (mcodeIsProp(op
.mkey
.mcode
)) {
2421 return miFinalUnsetProp(env
, op
.arg1
, key
->first
);
2423 assertx(mcodeIsElem(op
.mkey
.mcode
));
2424 return miFinalUnsetElem(env
, op
.arg1
, key
->first
);
2427 handleEffects(env
, effects
, key
->second
);
2432 //////////////////////////////////////////////////////////////////////