2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
16 #include "hphp/hhbbc/interp.h"
23 #include <folly/Optional.h>
24 #include <folly/Format.h>
26 #include "hphp/util/trace.h"
28 #include "hphp/hhbbc/interp-internal.h"
29 #include "hphp/hhbbc/optimize.h"
30 #include "hphp/hhbbc/type-ops.h"
32 namespace HPHP
{ namespace HHBBC
{
36 //////////////////////////////////////////////////////////////////////
38 // Represents the "effectful"-ness of a particular member instruction
39 // operation. This includes operations which are visible outside of
40 // the function (writing to a static property), but also potential
41 // throwing. Many different aspects of a member instruction operation
42 // can have effects, so we combine the effects from the various
43 // portions and use the final result to determine how to mark the
47 SideEffect
, // Cannot throw, but has some side-effect
48 Throws
, // Might throw an exception
49 AlwaysThrows
// Always throws an exception
52 // Combine two effects, in the sense that either of them might happen
53 // (therefore AlwaysThrows becomes just Throws if mixed with something
55 Effects
unionEffects(Effects e1
, Effects e2
) {
56 if (e1
== e2
) return e1
;
57 if (e1
== Effects::Throws
|| e2
== Effects::Throws
||
58 e1
== Effects::AlwaysThrows
|| e2
== Effects::AlwaysThrows
) {
59 return Effects::Throws
;
61 return Effects::SideEffect
;
64 // There's no good default for Effects when you want to union them
65 // together. Instead you want to start with the first Effect you
66 // see. This keeps the Effect from taking a value until you union one
68 using OptEffects
= folly::Optional
<Effects
>;
70 OptEffects
unionEffects(OptEffects e1
, Effects e2
) {
72 return unionEffects(*e1
, e2
);
75 //////////////////////////////////////////////////////////////////////
80 * Generally type inference needs to know two kinds of things about the base to
81 * handle effects on tracked locations:
83 * - Could the base be a location we're tracking deeper structure on, so the
84 * next operation actually affects something inside of it. For example,
85 * could the base be an object with the same type as $this, or an array in a
88 * - Could the base be something (regardless of type) that is inside one of
89 * the things we're tracking. I.e., the base might be whatever (an array or
90 * a bool or something), but living inside a property inside an object with
91 * the same type as $this, or living inside of an array in the local frame.
93 * The first cases apply because final operations are going to directly affect
94 * the type of these elements. The second case is because member operations may
95 * change the base at each step if it is a defining instruction.
97 * Note that both of these cases can apply to the same base in some cases: you
98 * might have an object property on $this that could be an object of the type of
102 //////////////////////////////////////////////////////////////////////
104 bool couldBeThisObj(ISS
& env
, const Base
& b
) {
105 if (b
.loc
== BaseLoc::This
) return true;
106 auto const thisTy
= thisTypeFromContext(env
.index
, env
.ctx
);
107 return b
.type
.couldBe(thisTy
? *thisTy
: TObj
);
110 bool mustBeThisObj(ISS
& env
, const Base
& b
) {
111 if (b
.loc
== BaseLoc::This
) return true;
112 if (auto const ty
= thisTypeFromContext(env
.index
, env
.ctx
)) {
113 return b
.type
.subtypeOf(*ty
);
118 bool mustBeInLocal(const Base
& b
) {
119 return b
.loc
== BaseLoc::Local
;
122 bool mustBeInStack(const Base
& b
) {
123 return b
.loc
== BaseLoc::Stack
;
126 bool mustBeInStatic(const Base
& b
) {
127 return b
.loc
== BaseLoc::StaticProp
;
130 //////////////////////////////////////////////////////////////////////
132 // Base locations that only occur at the start of a minstr sequence.
133 bool isInitialBaseLoc(BaseLoc loc
) {
135 loc
== BaseLoc::Local
||
136 loc
== BaseLoc::Stack
||
137 loc
== BaseLoc::StaticProp
||
138 loc
== BaseLoc::This
||
139 loc
== BaseLoc::Global
;
142 // Base locations that only occur after the start of a minstr sequence.
143 bool isDimBaseLoc(BaseLoc loc
) {
144 return loc
== BaseLoc::Elem
|| loc
== BaseLoc::Prop
;
147 //////////////////////////////////////////////////////////////////////
150 * Update the current base via array_like_set, and return whether the
151 * operation can possibly throw.
153 TriBool
array_do_set(ISS
& env
,
156 auto& base
= env
.collect
.mInstrState
.base
.type
;
157 assertx(base
.couldBe(BArrLike
));
159 // array_like_set requires the key to be a TArrKey already. If it's
160 // not guaranteed to be, we assume we could throw.
161 if (!key
.couldBe(BArrKey
)) return TriBool::Yes
;
162 auto const validKey
= key
.subtypeOf(BArrKey
);
164 auto set
= array_like_set(
166 validKey
? key
: intersection_of(key
, TArrKey
),
169 // Check for the presence of TArrLike rather than Bottom, since the
170 // base may have contained more than just TArrLike to begin with.
171 if (!set
.first
.couldBe(BArrLike
)) {
176 base
= std::move(set
.first
);
177 return maybeOrNo(set
.second
|| !validKey
);
181 * Look up the specified key via array_like_elem and return the
182 * associated value, along with whether the lookup can throw and if
183 * the value is definitely present.
185 * If excludeKeyset is true, we remove any TKeyset types from the
186 * array before doing the lookup. This is useful for the cases where a
187 * keyset would fatal, but other types wouldn't.
194 ElemResult
array_do_elem(ISS
& env
, const Type
& key
,
195 bool excludeKeyset
= false) {
196 auto const& base
= env
.collect
.mInstrState
.base
.type
;
197 assertx(base
.couldBe(BArrLike
));
199 // If the key can't possibly be good, or the array is entirely a
200 // keyset (and we exclude keysets), we'll always throw.
201 if (!key
.couldBe(BArrKey
)) return ElemResult
{ TBottom
, TriBool::Yes
, false };
202 if (excludeKeyset
&& base
.subtypeAmong(BKeyset
, BArrLike
)) {
203 return ElemResult
{ TBottom
, TriBool::Yes
, false };
206 // Otherwise remove the problematic parts of the key or value. If we
207 // have to remove anything, assume we could possibly throw.
208 auto const validKey
= key
.subtypeOf(BArrKey
);
209 auto const validArr
= !excludeKeyset
|| base
.subtypeAmong(BKVish
, BArrLike
);
210 auto r
= array_like_elem(
211 validArr
? base
: intersection_of(base
, TKVish
),
212 validKey
? key
: intersection_of(key
, TArrKey
)
217 maybeOrNo(!validKey
|| !validArr
),
223 * Update the current base via array_like_newelem, and return whether
224 * the newelem can throw.
226 TriBool
array_do_newelem(ISS
& env
, const Type
& value
) {
227 auto& base
= env
.collect
.mInstrState
.base
.type
;
228 assertx(base
.couldBe(BArrLike
));
230 auto update
= array_like_newelem(base
, value
);
231 // Check for the presence of TArrLike rather than Bottom, since the
232 // base may have contained more than just TArrLike to begin with.
233 if (!update
.first
.couldBe(BArrLike
)) {
234 assertx(update
.second
);
238 base
= std::move(update
.first
);
239 return maybeOrNo(update
.second
);
242 //////////////////////////////////////////////////////////////////////
244 void setLocalForBase(ISS
& env
, Type ty
, LocalId firstKeyLoc
) {
245 assertx(mustBeInLocal(env
.collect
.mInstrState
.base
));
246 if (env
.collect
.mInstrState
.base
.locLocal
== NoLocalId
) {
247 return killLocals(env
);
249 FTRACE(4, " ${} := {}\n",
250 env
.collect
.mInstrState
.base
.locName
251 ? env
.collect
.mInstrState
.base
.locName
->data()
257 env
.collect
.mInstrState
.base
.locLocal
,
263 void setStackForBase(ISS
& env
, Type ty
) {
264 assertx(mustBeInStack(env
.collect
.mInstrState
.base
));
266 auto const locSlot
= env
.collect
.mInstrState
.base
.locSlot
;
267 FTRACE(4, " stk[{:02}] := {}\n", locSlot
, show(ty
));
268 assertx(locSlot
< env
.state
.stack
.size());
270 env
.state
.stack
[locSlot
] = StackElem
{ std::move(ty
), NoLocalId
};
273 void setStaticForBase(ISS
& env
, Type ty
) {
274 auto const& base
= env
.collect
.mInstrState
.base
;
275 assertx(mustBeInStatic(base
));
277 auto const nameTy
= base
.locName
? sval(base
.locName
) : TStr
;
279 4, " ({})::$({}) |= {}\n",
280 show(base
.locTy
), show(nameTy
), show(ty
)
283 env
.index
.merge_static_type(
285 env
.collect
.publicSPropMutations
,
289 remove_uninit(std::move(ty
))
293 // Run backwards through an array chain doing array_set operations
294 // to produce the array type that incorporates the effects of any
295 // intermediate defining dims.
296 Type
currentChainType(ISS
& env
, Type val
) {
297 auto it
= env
.collect
.mInstrState
.arrayChain
.end();
298 while (it
!= env
.collect
.mInstrState
.arrayChain
.begin()) {
299 if (val
.is(BBottom
)) break;
301 assertx(it
->base
.subtypeOf(BArrLike
));
302 assertx(it
->key
.subtypeOf(BArrKey
));
303 val
= array_like_set(it
->base
, it
->key
, val
).first
;
308 Type
resolveArrayChain(ISS
& env
, Type val
) {
309 static UNUSED
const char prefix
[] = " ";
310 FTRACE(5, "{}chain {}\n", prefix
, show(val
));
312 if (val
.is(BBottom
)) {
313 // If val is Bottom, the update isn't actually going to happen,
314 // so we don't need to unwind the chain.
315 env
.collect
.mInstrState
.arrayChain
.clear();
318 auto arr
= std::move(env
.collect
.mInstrState
.arrayChain
.back().base
);
319 auto key
= std::move(env
.collect
.mInstrState
.arrayChain
.back().key
);
320 env
.collect
.mInstrState
.arrayChain
.pop_back();
321 FTRACE(5, "{} | {} := {} in {}\n", prefix
,
322 show(key
), show(val
), show(arr
));
323 assertx(arr
.subtypeOf(BArrLike
));
324 assertx(key
.subtypeOf(BArrKey
));
325 val
= array_like_set(std::move(arr
), key
, val
).first
;
326 } while (!env
.collect
.mInstrState
.arrayChain
.empty());
327 FTRACE(5, "{} = {}\n", prefix
, show(val
));
331 // Returns true if the base update can be considered "effect-free"
332 // (IE, updating a static prop base is a side-effect because its
333 // visible elsewhere. Updating a local or stack slot is not).
334 bool updateBaseWithType(ISS
& env
,
336 LocalId firstKeyLoc
= NoLocalId
) {
337 FTRACE(6, " updateBaseWithType: {}\n", show(ty
));
339 if (ty
.subtypeOf(BBottom
)) return true;
341 auto const& base
= env
.collect
.mInstrState
.base
;
343 if (mustBeInLocal(base
)) {
344 setLocalForBase(env
, ty
, firstKeyLoc
);
345 // If we're speculating, a local update is considered a
347 return !any(env
.collect
.opts
& CollectionOpts::Speculating
);
349 if (mustBeInStack(base
)) {
350 setStackForBase(env
, ty
);
353 if (mustBeInStatic(base
)) {
354 setStaticForBase(env
, ty
);
361 // Whether its worthwhile to refine the base's type based on knowing
362 // that certain types would have fatalled (and therefore the base can
363 // no longer be those types after the op). This is only worthwhile if
364 // the base is flow sensitive (IE, a local or stack slot). This is
365 // just an optimization so its always legal to say no.
366 bool shouldRefineBase(ISS
& env
) {
367 auto const& base
= env
.collect
.mInstrState
.base
;
368 return mustBeInLocal(base
) || mustBeInStack(base
);
371 void startBase(ISS
& env
, Base base
) {
372 auto& oldState
= env
.collect
.mInstrState
;
373 assertx(oldState
.base
.loc
== BaseLoc::None
);
374 assertx(oldState
.arrayChain
.empty());
375 assertx(isInitialBaseLoc(base
.loc
));
376 assertx(!base
.type
.subtypeOf(TBottom
));
378 oldState
.effectFree
= env
.flags
.effectFree
;
379 oldState
.extraPop
= false;
380 oldState
.base
= std::move(base
);
381 FTRACE(5, " startBase: {}\n", show(*env
.ctx
.func
, oldState
.base
));
384 // Return true if the base is updated and that update is considered
385 // "effect-free" (see updateBaseWithType).
386 bool endBase(ISS
& env
, bool update
= true, LocalId keyLoc
= NoLocalId
) {
387 auto& state
= env
.collect
.mInstrState
;
388 assertx(state
.base
.loc
!= BaseLoc::None
);
390 FTRACE(5, " endBase: {}\n", show(*env
.ctx
.func
, state
.base
));
392 auto const firstKeyLoc
= state
.arrayChain
.empty()
394 : state
.arrayChain
.data()->keyLoc
;
395 auto const& ty
= state
.arrayChain
.empty()
397 : resolveArrayChain(env
, state
.base
.type
);
399 auto const effectFree
= update
400 ? updateBaseWithType(env
, ty
, firstKeyLoc
)
402 state
.base
.loc
= BaseLoc::None
;
406 // Return true if the base is updated and that update is considered
407 // "effect-free" (see updateBaseWithType).
408 bool moveBase(ISS
& env
,
411 LocalId keyLoc
= NoLocalId
) {
412 auto& state
= env
.collect
.mInstrState
;
413 assertx(state
.base
.loc
!= BaseLoc::None
);
414 assertx(isDimBaseLoc(newBase
.loc
));
415 assertx(!state
.base
.type
.subtypeOf(BBottom
));
417 FTRACE(5, " moveBase: {} -> {}\n",
418 show(*env
.ctx
.func
, state
.base
),
419 show(*env
.ctx
.func
, newBase
));
421 auto const firstKeyLoc
= state
.arrayChain
.empty()
423 : state
.arrayChain
.data()->keyLoc
;
424 auto const& ty
= state
.arrayChain
.empty()
426 : resolveArrayChain(env
, state
.base
.type
);
428 auto const effectFree
= update
429 ? updateBaseWithType(env
, ty
, firstKeyLoc
)
431 state
.base
= std::move(newBase
);
435 // Return true if the base is updated and that update is considered
436 // "effect-free" (see updateBaseWithType).
437 bool extendArrChain(ISS
& env
, Type key
, Type arr
,
438 Type val
, LocalId keyLoc
= NoLocalId
) {
439 auto& state
= env
.collect
.mInstrState
;
440 assertx(state
.base
.loc
!= BaseLoc::None
);
441 // NB: The various array operation functions can accept arbitrary
442 // types as long as they contain TArrLike. However we still do not
443 // allow putting anything but TArrLike in the chain, since (in
444 // general) its not clear how to deal with the other types when
445 // resolving the chain. Currently the only case where we set up
446 // array chains is ElemD or ElemU. For ElemD, most of the common
447 // other types just fatal, so we can simply remove them (including
448 // TInitNull). For ElemU we do the same thing, but less types
450 assertx(arr
.subtypeOf(BArrLike
));
451 assertx(key
.subtypeOf(BArrKey
));
452 assertx(!state
.base
.type
.subtypeOf(BBottom
));
453 assertx(!val
.subtypeOf(BBottom
));
454 assertx(!key
.subtypeOf(BBottom
));
456 state
.arrayChain
.emplace_back(
457 CollectedInfo::MInstrState::ArrayChainEnt
{
463 state
.base
.type
= std::move(val
);
465 auto const firstKeyLoc
= state
.arrayChain
.data()->keyLoc
;
467 FTRACE(5, " extendArrChain: {}\n", show(*env
.ctx
.func
, state
));
468 return updateBaseWithType(
470 currentChainType(env
, state
.base
.type
),
475 //////////////////////////////////////////////////////////////////////
477 // Returns nullptr if it's an unknown key or not a string.
478 SString
mStringKey(const Type
& key
) {
479 auto const v
= tv(key
);
480 return v
&& v
->m_type
== KindOfPersistentString
? v
->m_data
.pstr
: nullptr;
483 template<typename Op
>
484 auto update_mkey(const Op
& op
) { return false; }
486 template<typename Op
>
487 auto update_mkey(Op
& op
) -> decltype(op
.mkey
, true) {
488 switch (op
.mkey
.mcode
) {
489 case MEC
: case MPC
: {
498 template<typename Op
>
499 auto update_discard(const Op
& op
) { return false; }
501 template<typename Op
>
502 auto update_discard(Op
& op
) -> decltype(op
.arg1
, true) {
508 * Return the type of the key and whether any promotions happened, or
509 * reduce op and return folly::none. Note that when folly::none is
510 * returned, there is nothing further to do.
512 template<typename Op
>
513 folly::Optional
<std::pair
<Type
,Promotion
>> key_type_or_fixup(ISS
& env
, Op op
) {
514 if (env
.collect
.mInstrState
.extraPop
) {
515 auto const mkey
= update_mkey(op
);
516 if (update_discard(op
) || mkey
) {
517 env
.collect
.mInstrState
.extraPop
= false;
519 env
.collect
.mInstrState
.extraPop
= true;
523 auto const fixup
= [&] (Type ty
, bool isProp
, bool couldBeUninit
)
524 -> folly::Optional
<std::pair
<Type
,Promotion
>> {
526 // Handle any classlike key promotions
527 auto promoted
= promote_classlike_to_key(std::move(ty
));
528 // We could also promote and potentially throw if we had an uninit
530 if (couldBeUninit
) promoted
.second
= Promotion::YesMightThrow
;
531 // If we might throw, we don't want to const prop the key
532 if (promoted
.second
== Promotion::YesMightThrow
) return promoted
;
534 if (auto const val
= tv(promoted
.first
)) {
535 if (isStringType(val
->m_type
)) {
536 op
.mkey
.mcode
= isProp
? MPT
: MET
;
537 op
.mkey
.litstr
= val
->m_data
.pstr
;
541 if (!isProp
&& val
->m_type
== KindOfInt64
) {
543 op
.mkey
.int64
= val
->m_data
.num
;
550 switch (op
.mkey
.mcode
) {
552 return fixup(topC(env
, op
.mkey
.idx
), op
.mkey
.mcode
== MPC
, false);
553 case MEL
: case MPL
: {
554 auto couldBeUninit
= true;
555 if (!peekLocCouldBeUninit(env
, op
.mkey
.local
.id
)) {
556 couldBeUninit
= false;
557 auto const minLocEquiv
= findMinLocEquiv(env
, op
.mkey
.local
.id
, false);
558 if (minLocEquiv
!= NoLocalId
) {
559 op
.mkey
.local
= NamedLocal
{ kInvalidLocalName
, minLocEquiv
};
565 locAsCell(env
, op
.mkey
.local
.id
),
566 op
.mkey
.mcode
== MPL
,
571 return std::make_pair(TBottom
, Promotion::No
);
573 return std::make_pair(ival(op
.mkey
.int64
), Promotion::No
);
574 case MET
: case MPT
: case MQT
:
575 return std::make_pair(sval(op
.mkey
.litstr
), Promotion::No
);
580 template<typename Op
>
581 LocalId
key_local(ISS
& env
, Op op
) {
582 switch (op
.mkey
.mcode
) {
584 return topStkLocal(env
, op
.mkey
.idx
);
586 return op
.mkey
.local
.id
;
589 case MET
: case MPT
: case MQT
:
595 //////////////////////////////////////////////////////////////////////
597 // Handle the promotions that can happen to the base for ElemU or
598 // ElemD operations (including mutating final operations). Return
599 // whether any promotion happened.
600 Promotion
handleElemUDBasePromos(ISS
& env
) {
601 auto& base
= env
.collect
.mInstrState
.base
.type
;
603 std::tie(base
, promotion
) = promote_clsmeth_to_vecish(std::move(base
));
607 //////////////////////////////////////////////////////////////////////
609 // Helper function for ending the base when we know this member
610 // instruction will always throw.
611 Effects
endUnreachableBase(ISS
& env
, Promotion basePromo
,
612 LocalId keyLoc
= NoLocalId
) {
613 // If we promoted the base, we still need to reflect that
614 if (basePromo
!= Promotion::No
) endBase(env
, true, keyLoc
);
615 return Effects::AlwaysThrows
;
618 // Helper function for ending the base. Takes the current aggregated
619 // effects up until this point, and whether the base underwent any
620 // promotions. Returns an updated aggregated effects, taking into
621 // account any effects from the base write-back. The given lambda will
622 // be called after the base is updated (this is needed because you
623 // don't want to modify the stack until after the base write back has
625 template <typename F
>
626 Effects
endBaseWithEffects(ISS
& env
, Effects effects
, bool update
,
629 LocalId keyLoc
= NoLocalId
) {
630 if (effects
== Effects::AlwaysThrows
) {
631 auto const e
= endUnreachableBase(env
, basePromo
, keyLoc
);
635 // End the base. We request a base update if the caller requested,
636 // or if a base promotion happened (if the base promoted, we need to
637 // record the new type into it).
638 auto const effectFree
=
639 endBase(env
, update
|| basePromo
!= Promotion::No
, keyLoc
);
641 // If we might throw because of base promotion, it doesn't matter
642 // what other effects are.
643 if (basePromo
== Promotion::YesMightThrow
) return Effects::Throws
;
644 // Special case: if we don't have any other effects, but writing to
645 // the base is side-effectful, then we can be nothrow (but not
647 if (!effectFree
&& effects
== Effects::None
) return Effects::SideEffect
;
651 //////////////////////////////////////////////////////////////////////
653 // Helper function for Elem (not ElemD or ElemU) operations. Luckily
654 // the logic for the Dim portion, and the final operation are
655 // identical, so we can treat them as the same. Return the elem type
656 // and what effects the access has.
657 std::pair
<Type
, Effects
> elemHelper(ISS
& env
, MOpMode mode
, Type key
) {
658 assertx(mode
== MOpMode::None
||
659 mode
== MOpMode::Warn
||
660 mode
== MOpMode::InOut
);
662 auto& base
= env
.collect
.mInstrState
.base
.type
;
663 assertx(!base
.is(BBottom
));
665 // If we're using MOpMode::InOut, then the base has to be a ArrLike
667 auto inOutFail
= false;
668 if (mode
== MOpMode::InOut
) {
669 if (!base
.couldBe(BArrLike
)) return { TBottom
, Effects::AlwaysThrows
};
670 if (!base
.subtypeOf(BArrLike
)) inOutFail
= true;
673 auto const warnsWithNull
=
674 BTrue
| BNum
| BRes
| BFunc
| BRFunc
| BRClsMeth
|
675 (!RO::EvalIsCompatibleClsMethType
? BClsMeth
: BBottom
);
676 auto const justNull
= BNull
| BFalse
;
677 auto const DEBUG_ONLY handled
=
678 warnsWithNull
| justNull
| BClsMeth
|
679 BStr
| BCls
| BLazyCls
| BObj
| BRecord
| BArrLike
;
681 // Can't static assert because warnsWithNull depends on
683 assertx(handled
== BCell
);
688 // These emit a warning and push InitNull
689 if (base
.couldBe(warnsWithNull
)) {
690 effects
= unionEffects(effects
, Effects::Throws
);
693 // These silently push InitNull
694 if (base
.couldBe(justNull
)) {
695 effects
= unionEffects(
697 inOutFail
? Effects::Throws
: Effects::None
701 // Strings will return a static string (a character from itself or
702 // an empty string). Class-likes will convert to its equivalent string
704 if (base
.couldBe(BStr
| BCls
| BLazyCls
)) {
705 auto const isNoThrow
=
707 mode
!= MOpMode::Warn
&&
708 key
.subtypeOf(BArrKey
) &&
709 (!base
.couldBe(BCls
| BLazyCls
) ||
710 !RuntimeOption::EvalRaiseClassConversionWarning
);
711 effects
= unionEffects(
713 isNoThrow
? Effects::None
: Effects::Throws
717 // With the right setting, ClsMeth will behave like an equivalent
718 // varray/vec (but will not actually promote). This is the same as a
719 // 2 element vec/varray containing static strings.
720 if (RO::EvalIsCompatibleClsMethType
&& base
.couldBe(BClsMeth
)) {
721 auto const isNoThrow
=
723 mode
== MOpMode::None
&&
724 !RuntimeOption::EvalRaiseClsMethConversionWarning
;
725 effects
= unionEffects(
727 isNoThrow
? Effects::None
: Effects::Throws
731 // These can throw and push anything.
732 if (base
.couldBe(BObj
| BRecord
)) {
733 effects
= unionEffects(effects
, Effects::Throws
);
736 // If it's an array, we can determine the exact element.
737 if (base
.couldBe(BArrLike
)) {
738 auto elem
= array_do_elem(env
, key
);
739 if (elem
.throws
== TriBool::Yes
) {
741 effects
= unionEffects(effects
, Effects::AlwaysThrows
);
743 auto mightThrow
= elem
.throws
!= TriBool::No
;
744 // If the mode is MOpMode::None, we'll use TInitNull if the key
745 // is missing. Otherwise, we'll throw.
747 if (mode
== MOpMode::None
) {
748 elem
.elem
|= TInitNull
;
754 if (elem
.elem
.is(BBottom
)) {
755 // Key definitely doesn't exist. We'll always throw. Note that
756 // this can't happen if mode is MOpMode::None because we added
757 // TInitNull to the type above.
758 effects
= unionEffects(effects
, Effects::AlwaysThrows
);
760 ty
|= std::move(elem
.elem
);
761 effects
= unionEffects(
763 !mightThrow
&& !inOutFail
? Effects::None
: Effects::Throws
769 assertx(effects
.has_value());
770 return { std::move(ty
), *effects
};
773 // Helper function for SetOpElem/IncDecElem final operations. Reads
774 // the element in the base given by the key, performs the operation
775 // using `op', then writes the new value back to the base. Returns the
776 // aggregates effects of the entire operation.
777 template <typename F
>
778 Effects
setOpElemHelper(ISS
& env
, int32_t nDiscard
, const Type
& key
,
779 LocalId keyLoc
, F op
) {
780 // Before anything else, the base might promote to a different type
781 auto const promo
= handleElemUDBasePromos(env
);
783 auto& base
= env
.collect
.mInstrState
.base
.type
;
784 assertx(!base
.is(BBottom
));
786 auto const throws
= BNull
| BFalse
| BStr
;
788 BTrue
| BNum
| BRes
| BFunc
| BRFunc
|
789 BCls
| BLazyCls
| BClsMeth
| BRClsMeth
;
790 auto const handled
= throws
| null
| BArrLike
| BObj
| BRecord
;
792 static_assert(handled
== BCell
);
795 auto pushed
= TBottom
;
796 auto refine
= BBottom
;
800 if (base
.couldBe(throws
)) {
801 effects
= unionEffects(effects
, Effects::AlwaysThrows
);
804 // Raises a warning and pushes null
805 if (base
.couldBe(null
)) {
806 effects
= unionEffects(effects
, Effects::Throws
);
809 // Objects can throw and push anything
810 if (base
.couldBe(BObj
| BRecord
)) {
811 effects
= unionEffects(effects
, Effects::Throws
);
814 // Records can throw and push anything. Since we might be mutating
815 // the record, record an update.
816 if (base
.couldBe(BRecord
)) {
817 effects
= unionEffects(effects
, Effects::Throws
);
821 if (base
.couldBe(BArrLike
)) {
822 auto const unreachable
= [&] {
823 effects
= unionEffects(effects
, Effects::AlwaysThrows
);
828 // For the array portion, we can analyze the effects precisely
829 auto elem
= array_do_elem(env
, key
, true);
830 if (elem
.elem
.is(BBottom
) || elem
.throws
== TriBool::Yes
) {
831 // Element doesn't exist or key is bad. Always throws.
836 // Keysets will throw, so we can assume the base does not
837 // contain them afterwards.
838 if (base
.couldBe(BKeyset
)) base
= remove_keyset(std::move(base
));
840 // We'll have already have COWed the array if the setop throws,
841 // so we need to manually remove staticness and force a base
843 base
= loosen_array_staticness(std::move(base
));
847 auto [toSet
, toPush
, opEffects
] = op(std::move(elem
.elem
));
848 if (opEffects
== Effects::AlwaysThrows
) {
849 // The op will always throw. We've already COWed the base, so we
850 // still need to update the base type.
855 // Write the element back into the array
856 auto const set
= array_do_set(env
, key
, toSet
);
857 if (set
== TriBool::Yes
) {
858 // The set will always throw. In theory this can happen if we do
859 // something like read a string out of a keyset, turn it into
860 // something that's not an array key, then try to write it back.
865 auto const maybeThrows
=
867 elem
.throws
== TriBool::Maybe
||
868 set
== TriBool::Maybe
;
869 effects
= unionEffects(
871 maybeThrows
? Effects::Throws
: opEffects
873 pushed
|= std::move(toPush
);
877 // Refine the base and remove bits that will always throw
878 if (refine
&& shouldRefineBase(env
)) {
879 base
= remove_bits(std::move(base
), refine
);
883 assertx(effects
.has_value());
885 // We have to update the base even if we'll always throw to account
886 // for potential COWing of the array (it could be in a static for
888 if (*effects
== Effects::AlwaysThrows
&& update
) {
889 assertx(pushed
.is(BBottom
));
890 endBase(env
, true, keyLoc
);
891 discard(env
, nDiscard
);
893 return Effects::AlwaysThrows
;
896 return endBaseWithEffects(
897 env
, *effects
, update
, promo
,
899 discard(env
, nDiscard
);
900 push(env
, std::move(pushed
));
906 // Helper function for SetOpNewElem/IncDecNewElemm final
907 // operations. These all either throw or push null.
908 Effects
setOpNewElemHelper(ISS
& env
, int32_t nDiscard
) {
909 auto& base
= env
.collect
.mInstrState
.base
.type
;
910 assertx(!base
.is(BBottom
));
912 auto const alwaysThrow
=
913 BNull
| BFalse
| BStr
| BArrLike
| BObj
| BClsMeth
| BRecord
;
915 BTrue
| BNum
| BRes
| BRFunc
| BFunc
| BRClsMeth
| BCls
| BLazyCls
;
916 auto const handled
= alwaysThrow
| null
;
918 static_assert(handled
== BCell
);
921 auto pushed
= TBottom
;
922 auto refine
= BBottom
;
925 // These always throw
926 if (base
.couldBe(alwaysThrow
)) {
927 effects
= unionEffects(effects
, Effects::AlwaysThrows
);
928 refine
|= alwaysThrow
;
930 // These raise a warning and push InitNull
931 if (base
.couldBe(null
)) {
932 effects
= unionEffects(effects
, Effects::Throws
);
936 // Refine the base and remove bits that will always throw
937 if (refine
&& shouldRefineBase(env
)) {
938 base
= remove_bits(std::move(base
), refine
);
942 assertx(effects
.has_value());
944 return endBaseWithEffects(
945 env
, *effects
, update
, Promotion::No
,
947 discard(env
, nDiscard
);
948 push(env
, std::move(pushed
));
953 //////////////////////////////////////////////////////////////////////
956 Effects
miProp(ISS
& env
, bool, MOpMode mode
, Type key
, ReadOnlyOp op
) {
957 auto const name
= mStringKey(key
);
958 auto const isDefine
= mode
== MOpMode::Define
;
959 auto const isUnset
= mode
== MOpMode::Unset
;
960 // PHP5 doesn't modify an unset local if you unset a property or
961 // array elem on it, but hhvm does (it promotes it to init-null).
962 auto const update
= isDefine
|| isUnset
;
964 * MOpMode::Unset Props doesn't promote "emptyish" things to stdClass, or
965 * affect arrays, however it can define a property on an object base. This
966 * means we don't need any couldBeInFoo logic, but if the base could actually
967 * be $this, and a declared property could be Uninit, we need to merge
970 * We're trying to handle this case correctly as far as the type inference
971 * here is concerned, but the runtime doesn't actually behave this way right
972 * now for declared properties. Note that it never hurts to merge more types
973 * than a thisProp could actually be, so this is fine.
975 * See TODO(#3602740): unset with intermediate dims on previously declared
976 * properties doesn't define them to null.
978 if (isUnset
&& couldBeThisObj(env
, env
.collect
.mInstrState
.base
)) {
980 auto const elem
= thisPropRaw(env
, name
);
981 if (elem
&& elem
->ty
.couldBe(BUninit
)) {
982 mergeThisProp(env
, name
, TInitNull
);
985 mergeEachThisPropRaw(env
, [&] (const Type
& ty
) {
986 return ty
.couldBe(BUninit
) ? TInitNull
: TBottom
;
991 if (mustBeThisObj(env
, env
.collect
.mInstrState
.base
)) {
992 auto const optThisTy
= thisTypeFromContext(env
.index
, env
.ctx
);
993 auto const thisTy
= optThisTy
? *optThisTy
: TObj
;
995 auto const elem
= thisPropRaw(env
, name
);
996 if (elem
&& elem
->attrs
& AttrIsReadOnly
&& op
== ReadOnlyOp::Mutable
) {
997 return Effects::AlwaysThrows
;
1000 auto const ty
= [&] {
1002 if (elem
) return elem
->ty
;
1004 if (auto const propTy
= thisPropAsCell(env
, name
)) return *propTy
;
1007 env
.index
.lookup_public_prop(objcls(thisTy
), sval(name
));
1008 return update
? raw
: to_cell(raw
);
1011 if (ty
.subtypeOf(BBottom
)) return Effects::AlwaysThrows
;
1014 Base
{ ty
, BaseLoc::Prop
, thisTy
, name
},
1019 Base
{ TInitCell
, BaseLoc::Prop
, thisTy
},
1022 return Effects::Throws
;
1025 // We know for sure we're going to be in an object property.
1026 if (env
.collect
.mInstrState
.base
.type
.subtypeOf(BObj
)) {
1028 env
.index
.lookup_public_prop(
1029 objcls(env
.collect
.mInstrState
.base
.type
),
1030 name
? sval(name
) : TStr
1032 auto const ty
= update
? raw
: to_cell(raw
);
1033 if (ty
.subtypeOf(BBottom
)) return Effects::AlwaysThrows
;
1037 env
.collect
.mInstrState
.base
.type
,
1040 return Effects::Throws
;
1044 * Otherwise, intermediate props with define can promote a null, false, or ""
1045 * to stdClass. Those cases, and others, if it's MOpMode::Define, will set
1046 * the base to a null value in tvScratch. The base may also legitimately be
1047 * an object and our next base is in an object property. Conservatively treat
1048 * all these cases as "possibly" being inside of an object property with
1049 * "Prop" with locType TCell.
1052 Base
{ TInitCell
, BaseLoc::Prop
, TCell
, name
},
1054 return Effects::Throws
;
1057 Effects
miElem(ISS
& env
, MOpMode mode
, Type key
, LocalId keyLoc
) {
1058 auto const isDefine
= mode
== MOpMode::Define
;
1059 auto const isUnset
= mode
== MOpMode::Unset
;
1061 if (!isDefine
&& !isUnset
) {
1062 // An Elem operation which doesn't mutate the base at all
1063 auto [elem
, effects
] = elemHelper(env
, mode
, std::move(key
));
1064 if (effects
!= Effects::AlwaysThrows
) {
1065 // Since this Dim is not mutating the base, we don't need to use
1066 // an array chain here.
1068 env
, Base
{ std::move(elem
), BaseLoc::Elem
}, false, keyLoc
1074 // ElemD or ElemU. The base might mutate here. First handle any base
1076 auto const promo
= handleElemUDBasePromos(env
);
1078 auto& base
= env
.collect
.mInstrState
.base
.type
;
1079 assertx(!base
.is(BBottom
));
1081 // These are similar to endBaseWithEffects, but moves the base or
1082 // extends the array chain instead.
1083 auto const move
= [&] (Type ty
, bool update
, Effects effects
) {
1084 if (effects
== Effects::AlwaysThrows
) {
1085 return endUnreachableBase(env
, promo
, keyLoc
);
1087 auto const effectFree
= moveBase(
1089 Base
{ std::move(ty
), BaseLoc::Elem
},
1090 update
|| promo
!= Promotion::No
,
1093 if (promo
== Promotion::YesMightThrow
) return Effects::Throws
;
1094 if (!effectFree
&& effects
== Effects::None
) return Effects::SideEffect
;
1097 auto const extend
= [&] (Type ty
, Effects effects
) {
1098 assertx(effects
!= Effects::AlwaysThrows
);
1099 if (!key
.subtypeOf(BArrKey
)) {
1100 assertx(effects
== Effects::Throws
);
1101 key
= intersection_of(std::move(key
), TArrKey
);
1102 assertx(!key
.is(BBottom
));
1104 auto const effectFree
=
1105 extendArrChain(env
, std::move(key
), base
, std::move(ty
), keyLoc
);
1106 if (promo
== Promotion::YesMightThrow
) return Effects::Throws
;
1107 if (!effectFree
&& effects
== Effects::None
) return Effects::SideEffect
;
1112 // ElemU. These types either always throw, or set the base to
1114 auto const alwaysThrows
=
1115 BCls
| BLazyCls
| BFunc
| BRFunc
| BStr
| BClsMeth
|
1116 BRClsMeth
| BRecord
;
1117 auto const movesToUninit
= BPrim
| BRes
;
1118 auto const handled
=
1119 alwaysThrows
| movesToUninit
| BObj
| BArrLike
;
1121 static_assert(handled
== BCell
);
1123 // It is quite unfortunate that InitNull doesn't always throw, as
1124 // this means we cannot use an array chain with a nullable array
1125 // (which is common).
1126 if (base
.couldBe(BArrLike
) && base
.subtypeOf(alwaysThrows
| BArrLike
)) {
1127 auto elem
= array_do_elem(env
, key
, true);
1128 if (elem
.throws
== TriBool::Yes
) {
1129 // We'll always throw
1130 return endUnreachableBase(env
, promo
, keyLoc
);
1132 if (!elem
.present
) elem
.elem
|= TInitNull
;
1133 auto const maybeAlwaysThrows
= base
.couldBe(alwaysThrows
);
1134 // Keysets will throw, so we can assume the base does not
1135 // contain them afterwards.
1136 if (base
.couldBe(BKeyset
)) base
= remove_keyset(std::move(base
));
1137 base
= loosen_array_staticness(std::move(base
)); // Handle COW
1138 // We can safely remove the always throws bits, since if we get
1139 // to the next op, it means they weren't present.
1140 if (maybeAlwaysThrows
) base
= remove_bits(std::move(base
), alwaysThrows
);
1142 std::move(elem
.elem
),
1143 (maybeAlwaysThrows
|| elem
.throws
== TriBool::Maybe
)
1144 ? Effects::Throws
: Effects::None
1150 auto update
= false;
1151 auto refine
= BBottom
;
1153 // These always raise an error
1154 if (base
.couldBe(alwaysThrows
)) {
1155 effects
= unionEffects(effects
, Effects::AlwaysThrows
);
1156 refine
|= alwaysThrows
;
1158 // These silently set the base to uninit
1159 if (base
.couldBe(movesToUninit
)) {
1160 effects
= unionEffects(effects
, Effects::None
);
1163 // Objects can throw and retrieve anything
1164 if (base
.couldBe(BObj
)) {
1165 effects
= unionEffects(effects
, Effects::Throws
);
1168 // For arrays we can do a precise lookup
1169 if (base
.couldBe(BArrLike
)) {
1170 auto elem
= array_do_elem(env
, key
, true);
1171 if (elem
.throws
== TriBool::Yes
) {
1172 // Bad key or the element never exists. We'll always throw.
1173 effects
= unionEffects(effects
, Effects::AlwaysThrows
);
1176 if (!elem
.present
) elem
.elem
|= TInitNull
;
1177 // Keysets will throw, so we can assume the base does not
1178 // contain them afterwards.
1179 if (base
.couldBe(BKeyset
)) base
= remove_keyset(std::move(base
));
1180 // Since we're not using an array chain here, we need to
1181 // pessimize the array (since we won't be able to track
1182 // further changes to its inner structure).
1183 base
= loosen_array_staticness(loosen_array_values(std::move(base
)));
1185 effects
= unionEffects(
1187 elem
.throws
== TriBool::Maybe
1191 ty
|= std::move(elem
.elem
);
1195 // Refine the base and remove bits that will always throw
1196 if (refine
&& shouldRefineBase(env
)) {
1197 base
= remove_bits(std::move(base
), refine
);
1201 assertx(effects
.has_value());
1203 return move(std::move(ty
), update
, *effects
);
1207 // ElemD. These types either always throw, or emit a warning and
1209 auto const alwaysThrows
= BNull
| BFalse
| BStr
;
1210 auto const warnsAndNull
=
1211 BTrue
| BNum
| BRes
| BFunc
| BRFunc
| BCls
| BLazyCls
|
1212 BClsMeth
| BRClsMeth
;
1213 auto const handled
=
1214 alwaysThrows
| warnsAndNull
| BObj
| BRecord
| BArrLike
;
1216 static_assert(handled
== BCell
);
1218 // As a special case, if we just have an array, we can extend an
1219 // array chain and track the modifications to the inner array
1220 // structure. We also allow the types which always throw since
1221 // they don't affect the structure (if we get one at runtime,
1222 // we'll just throw without modifying anything).
1223 if (base
.couldBe(BArrLike
) && base
.subtypeOf(alwaysThrows
| BArrLike
)) {
1224 auto elem
= array_do_elem(env
, key
, true);
1225 if (elem
.elem
.is(BBottom
) || elem
.throws
== TriBool::Yes
) {
1226 // We'll always throw
1227 return endUnreachableBase(env
, promo
, keyLoc
);
1229 auto const maybeAlwaysThrows
= base
.couldBe(alwaysThrows
);
1230 // Keysets will throw, so we can assume the base does not
1231 // contain them afterwards.
1232 if (base
.couldBe(BKeyset
)) base
= remove_keyset(std::move(base
));
1233 base
= loosen_array_staticness(std::move(base
)); // Handle COW
1234 // We can safely remove the always throws bits, since if we get
1235 // to the next op, it means they weren't present.
1236 if (maybeAlwaysThrows
) base
= remove_bits(std::move(base
), alwaysThrows
);
1237 auto const mightThrow
=
1238 maybeAlwaysThrows
||
1240 elem
.throws
== TriBool::Maybe
;
1242 std::move(elem
.elem
),
1243 mightThrow
? Effects::Throws
: Effects::None
1249 auto update
= false;
1250 auto refine
= BBottom
;
1252 // These always raise an error
1253 if (base
.couldBe(alwaysThrows
)) {
1254 effects
= unionEffects(effects
, Effects::AlwaysThrows
);
1255 refine
|= alwaysThrows
;
1257 // These emit a warning and push InitNull
1258 if (base
.couldBe(warnsAndNull
)) {
1259 effects
= unionEffects(effects
, Effects::Throws
);
1262 // Objects can throw and push anything
1263 if (base
.couldBe(BObj
)) {
1264 effects
= unionEffects(effects
, Effects::Throws
);
1267 // Records can throw and push anything as well. Since we might be
1268 // mutating the record, we need to perform an update.
1269 if (base
.couldBe(BRecord
)) {
1270 effects
= unionEffects(effects
, Effects::Throws
);
1274 // For arrays we can lookup the specific element
1275 if (base
.couldBe(BArrLike
)) {
1276 auto elem
= array_do_elem(env
, key
, true);
1277 if (elem
.elem
.is(BBottom
) || elem
.throws
== TriBool::Yes
) {
1278 // Bad key or the element never exists. We'll always throw.
1279 effects
= unionEffects(effects
, Effects::AlwaysThrows
);
1282 // Keysets will throw, so we can assume the base does not
1283 // contain them afterwards.
1284 if (base
.couldBe(BKeyset
)) base
= remove_keyset(std::move(base
));
1285 // Since we're not using an array chain here, we need to
1286 // pessimize the array (since we won't be able to track
1287 // further changes to its inner structure).
1288 base
= loosen_array_staticness(loosen_array_values(std::move(base
)));
1290 effects
= unionEffects(
1292 (!elem
.present
|| elem
.throws
== TriBool::Maybe
)
1296 ty
|= std::move(elem
.elem
);
1300 // Refine the base and remove bits that will always throw
1301 if (refine
&& shouldRefineBase(env
)) {
1302 base
= remove_bits(std::move(base
), refine
);
1306 assertx(effects
.has_value());
1308 return move(std::move(ty
), update
, *effects
);
1312 Effects
miNewElem(ISS
& env
) {
1313 // NewElem. These all either throw or raise a warning and set the
1315 auto& base
= env
.collect
.mInstrState
.base
.type
;
1316 assertx(!base
.is(BBottom
));
1318 auto const alwaysThrows
=
1319 BNull
| BFalse
| BArrLike
| BObj
| BClsMeth
| BRecord
;
1321 BTrue
| BNum
| BRes
| BRFunc
| BFunc
|
1322 BRClsMeth
| BCls
| BLazyCls
;
1323 auto const handled
= alwaysThrows
| uninit
| BStr
;
1325 static_assert(handled
== BCell
);
1328 auto newBase
= TBottom
;
1329 auto update
= false;
1330 auto refine
= BBottom
;
1332 if (base
.couldBe(alwaysThrows
)) {
1333 effects
= unionEffects(effects
, Effects::AlwaysThrows
);
1334 refine
|= alwaysThrows
;
1336 if (base
.couldBe(uninit
)) {
1337 effects
= unionEffects(effects
, Effects::Throws
);
1340 if (base
.couldBe(BStr
)) {
1341 if (is_specialized_string(base
) && sval_of(base
)->empty()) {
1342 effects
= unionEffects(effects
, Effects::AlwaysThrows
);
1345 effects
= unionEffects(effects
, Effects::Throws
);
1350 // Refine the base and remove bits that will always throw
1351 if (refine
&& shouldRefineBase(env
)) {
1352 base
= remove_bits(std::move(base
), refine
);
1356 assertx(effects
.has_value());
1357 assertx(*effects
== Effects::Throws
|| *effects
== Effects::AlwaysThrows
);
1358 if (*effects
!= Effects::AlwaysThrows
) {
1359 moveBase(env
, Base
{ std::move(newBase
), BaseLoc::Elem
}, update
);
1364 //////////////////////////////////////////////////////////////////////
1367 Effects
miFinalIssetProp(ISS
& env
, int32_t nDiscard
, const Type
& key
) {
1368 auto const name
= mStringKey(key
);
1369 discard(env
, nDiscard
);
1371 if (name
&& mustBeThisObj(env
, env
.collect
.mInstrState
.base
)) {
1372 if (auto const pt
= thisPropAsCell(env
, name
)) {
1373 if (isMaybeThisPropAttr(env
, name
, AttrLateInit
)) {
1374 // LateInit props can always be maybe unset, except if its never set at
1376 push(env
, pt
->subtypeOf(BBottom
) ? TFalse
: TBool
);
1377 } else if (pt
->subtypeOf(BNull
)) {
1379 } else if (!pt
->couldBe(BNull
)) {
1384 return Effects::None
;
1389 return Effects::Throws
;
1392 Effects
miFinalCGetProp(ISS
& env
, int32_t nDiscard
, const Type
& key
,
1393 bool quiet
, bool mustBeMutable
) {
1394 auto const name
= mStringKey(key
);
1395 discard(env
, nDiscard
);
1397 auto const& base
= env
.collect
.mInstrState
.base
.type
;
1400 if (mustBeThisObj(env
, env
.collect
.mInstrState
.base
)) {
1401 if (auto const t
= thisPropAsCell(env
, name
)) {
1403 if (mustBeMutable
&& isMaybeThisPropAttr(env
, name
, AttrIsReadOnly
)) {
1404 return Effects::Throws
;
1406 if (t
->subtypeOf(BBottom
)) return Effects::AlwaysThrows
;
1407 if (isMaybeThisPropAttr(env
, name
, AttrLateInit
)) return Effects::Throws
;
1408 if (quiet
) return Effects::None
;
1409 auto const elem
= thisPropRaw(env
, name
);
1411 return elem
->ty
.couldBe(BUninit
) ? Effects::Throws
: Effects::None
;
1415 if (!base
.couldBe(BObj
)) return TInitNull
;
1417 env
.index
.lookup_public_prop(
1418 objcls(base
.subtypeOf(BObj
) ? base
: intersection_of(base
, TObj
)),
1422 if (!base
.subtypeOf(BObj
)) t
= opt(std::move(t
));
1426 ty
.subtypeOf(BBottom
) ? Effects::AlwaysThrows
: Effects::Throws
;
1427 push(env
, std::move(ty
));
1431 push(env
, TInitCell
);
1432 return Effects::Throws
;
1435 Effects
miFinalSetProp(ISS
& env
, int32_t nDiscard
, const Type
& key
, ReadOnlyOp op
) {
1436 auto const name
= mStringKey(key
);
1437 auto const t1
= unctx(popC(env
));
1439 auto const finish
= [&](Type ty
) {
1441 discard(env
, nDiscard
);
1442 push(env
, std::move(ty
));
1443 return Effects::Throws
;
1446 auto const alwaysThrows
= [&] {
1447 discard(env
, nDiscard
);
1449 return Effects::AlwaysThrows
;
1452 if (op
== ReadOnlyOp::ReadOnly
&& !isMaybeThisPropAttr(env
, name
, AttrIsReadOnly
)) {
1453 return alwaysThrows();
1456 if (couldBeThisObj(env
, env
.collect
.mInstrState
.base
)) {
1458 mergeEachThisPropRaw(
1461 return propTy
.couldBe(BInitCell
) ? t1
: TBottom
;
1465 mergeThisProp(env
, name
, t1
);
1469 if (env
.collect
.mInstrState
.base
.type
.subtypeOf(BObj
)) {
1470 if (t1
.subtypeOf(BBottom
)) return alwaysThrows();
1473 Base
{ t1
, BaseLoc::Prop
, env
.collect
.mInstrState
.base
.type
, name
}
1478 moveBase(env
, Base
{ TInitCell
, BaseLoc::Prop
, TCell
, name
});
1479 return finish(TInitCell
);
1482 Effects
miFinalSetOpProp(ISS
& env
, int32_t nDiscard
,
1483 SetOpOp subop
, const Type
& key
) {
1484 auto const name
= mStringKey(key
);
1485 auto const rhsTy
= popC(env
);
1487 auto const& base
= env
.collect
.mInstrState
.base
.type
;
1489 if (!base
.couldBe(BObj
)) {
1491 discard(env
, nDiscard
);
1492 push(env
, TInitNull
);
1493 return Effects::Throws
;
1498 if (mustBeThisObj(env
, env
.collect
.mInstrState
.base
)) {
1499 if (auto const t
= thisPropAsCell(env
, name
)) return *t
;
1502 env
.index
.lookup_public_prop(
1503 objcls(base
.subtypeOf(BObj
) ? base
: intersection_of(base
, TObj
)),
1510 if (lhsTy
.subtypeOf(BBottom
)) {
1511 discard(env
, nDiscard
);
1513 return Effects::AlwaysThrows
;
1515 if (!base
.subtypeOf(BObj
)) lhsTy
= opt(std::move(lhsTy
));
1517 auto const resultTy
= base
.subtypeOf(BObj
)
1518 ? typeSetOp(subop
, lhsTy
, rhsTy
)
1521 if (resultTy
.subtypeOf(BBottom
)) {
1522 discard(env
, nDiscard
);
1524 return Effects::AlwaysThrows
;
1527 if (couldBeThisObj(env
, env
.collect
.mInstrState
.base
)) {
1529 mergeThisProp(env
, name
, resultTy
);
1536 discard(env
, nDiscard
);
1537 push(env
, resultTy
);
1538 return Effects::Throws
;
1541 Effects
miFinalIncDecProp(ISS
& env
, int32_t nDiscard
,
1542 IncDecOp subop
, const Type
& key
) {
1543 auto const name
= mStringKey(key
);
1545 auto const& base
= env
.collect
.mInstrState
.base
.type
;
1547 if (!base
.couldBe(BObj
)) {
1549 discard(env
, nDiscard
);
1550 push(env
, TInitNull
);
1551 return Effects::Throws
;
1554 auto postPropTy
= [&] {
1556 if (mustBeThisObj(env
, env
.collect
.mInstrState
.base
)) {
1557 if (auto const t
= thisPropAsCell(env
, name
)) return *t
;
1560 env
.index
.lookup_public_prop(
1561 objcls(base
.subtypeOf(BObj
) ? base
: intersection_of(base
, TObj
)),
1568 if (postPropTy
.subtypeOf(BBottom
)) {
1569 discard(env
, nDiscard
);
1571 return Effects::AlwaysThrows
;
1573 if (!base
.subtypeOf(BObj
)) postPropTy
= opt(std::move(postPropTy
));
1575 auto const prePropTy
= base
.subtypeOf(TObj
)
1576 ? typeIncDec(subop
, postPropTy
)
1578 if (prePropTy
.subtypeOf(BBottom
)) {
1579 discard(env
, nDiscard
);
1581 return Effects::AlwaysThrows
;
1584 if (couldBeThisObj(env
, env
.collect
.mInstrState
.base
)) {
1586 mergeThisProp(env
, name
, prePropTy
);
1593 discard(env
, nDiscard
);
1594 push(env
, isPre(subop
) ? prePropTy
: postPropTy
);
1595 return Effects::Throws
;
1598 Effects
miFinalUnsetProp(ISS
& env
, int32_t nDiscard
, const Type
& key
) {
1599 if (couldBeThisObj(env
, env
.collect
.mInstrState
.base
)) {
1600 if (auto const name
= mStringKey(key
)) {
1601 unsetThisProp(env
, name
);
1603 unsetUnknownThisProp(env
);
1608 discard(env
, nDiscard
);
1609 return Effects::Throws
;
1612 //////////////////////////////////////////////////////////////////////
1615 Effects
miFinalCGetElem(ISS
& env
, int32_t nDiscard
,
1616 const Type
& key
, MOpMode mode
) {
1617 auto [elem
, effects
] = elemHelper(env
, mode
, key
);
1618 discard(env
, nDiscard
);
1619 push(env
, std::move(elem
));
1623 Effects
miFinalIssetElem(ISS
& env
,
1626 auto& base
= env
.collect
.mInstrState
.base
.type
;
1627 assertx(!base
.is(BBottom
));
1629 auto const pushesFalse
=
1630 BNull
| BBool
| BNum
| BRes
| BFunc
| BRFunc
| BRClsMeth
|
1631 (!RO::EvalIsCompatibleClsMethType
? BClsMeth
: BBottom
);
1632 auto const handled
= pushesFalse
| BArrLike
;
1635 auto pushed
= TBottom
;
1637 if (base
.couldBe(pushesFalse
)) {
1638 effects
= unionEffects(effects
, Effects::None
);
1641 if (base
.couldBe(BArrLike
)) {
1642 auto const elem
= array_do_elem(env
, key
);
1643 if (elem
.throws
== TriBool::Yes
) {
1644 effects
= unionEffects(effects
, Effects::AlwaysThrows
);
1646 effects
= unionEffects(
1648 elem
.throws
== TriBool::Maybe
1653 if (elem
.elem
.subtypeOf(BNull
)) {
1655 } else if (elem
.present
&& !elem
.elem
.couldBe(BNull
)) {
1663 if (!base
.subtypeOf(handled
)) {
1664 effects
= unionEffects(effects
, Effects::Throws
);
1668 assertx(effects
.has_value());
1669 discard(env
, nDiscard
);
1670 push(env
, std::move(pushed
));
1674 Effects
miFinalSetElem(ISS
& env
,
1678 auto const rhs
= popC(env
);
1680 // First handle base promotions
1681 auto const promo
= handleElemUDBasePromos(env
);
1683 auto& base
= env
.collect
.mInstrState
.base
.type
;
1684 assertx(!base
.is(BBottom
));
1686 auto const alwaysThrows
= BNull
| BFalse
;
1687 auto const pushesNull
=
1688 BTrue
| BNum
| BRes
| BFunc
| BRFunc
|
1689 BCls
| BLazyCls
| BClsMeth
| BRClsMeth
;
1690 auto const handled
=
1691 alwaysThrows
| pushesNull
| BObj
| BRecord
| BStr
| BArrLike
;
1693 static_assert(handled
== BCell
);
1696 auto pushed
= TBottom
;
1697 auto update
= false;
1698 auto refine
= BBottom
;
1700 if (base
.couldBe(pushesNull
)) {
1701 // These emit a warning and push null
1702 effects
= unionEffects(effects
, Effects::Throws
);
1703 pushed
|= TInitNull
;
1705 if (base
.couldBe(alwaysThrows
)) {
1706 // These always raise a fatal
1707 effects
= unionEffects(effects
, Effects::AlwaysThrows
);
1708 refine
|= alwaysThrows
;
1710 if (base
.couldBe(BStr
)) {
1711 // String is a special case here. It will throw on empty strings,
1712 // and otherwise can return null or a static string (and possibly
1713 // warn). We don't bother predicting the effects of the set on the
1714 // string here, so be pessimistic and forget what we know about
1715 // the base in regards to staticness and values.
1716 if (is_specialized_string(base
) && sval_of(base
)->empty()) {
1717 effects
= unionEffects(effects
, Effects::AlwaysThrows
);
1720 effects
= unionEffects(effects
, Effects::Throws
);
1721 base
= loosen_string_staticness(loosen_string_values(std::move(base
)));
1726 if (base
.couldBe(BObj
)) {
1727 // Objects can throw but otherwise don't affect the base and push
1729 effects
= unionEffects(effects
, Effects::Throws
);
1732 if (base
.couldBe(BRecord
)) {
1733 // Records can throw but otherwise don't affect the base and push
1734 // the rhs. Record an update since we're potentially mutating it.
1735 effects
= unionEffects(effects
, Effects::Throws
);
1739 if (base
.couldBe(BArrLike
)) {
1740 // Arrays will set the value and push the right hande side of the
1741 // assignment (keysets will always fatal because you can't do a
1743 auto const doesThrow
= array_do_set(env
, key
, rhs
);
1744 if (doesThrow
== TriBool::Yes
) {
1745 effects
= unionEffects(effects
, Effects::AlwaysThrows
);
1748 effects
= unionEffects(
1750 doesThrow
== TriBool::No
? Effects::None
: Effects::Throws
1757 // Refine the base and remove bits that will always throw
1758 if (refine
&& shouldRefineBase(env
)) {
1759 base
= remove_bits(std::move(base
), refine
);
1763 assertx(effects
.has_value());
1765 return endBaseWithEffects(
1766 env
, *effects
, update
, promo
,
1768 discard(env
, nDiscard
);
1769 push(env
, std::move(pushed
));
1775 Effects
miFinalSetOpElem(ISS
& env
, int32_t nDiscard
,
1776 SetOpOp subop
, const Type
& key
,
1778 auto const rhsTy
= popC(env
);
1779 return setOpElemHelper(
1780 env
, nDiscard
, key
, keyLoc
,
1781 [&] (const Type
& lhsTy
) {
1782 auto const result
= typeSetOp(subop
, lhsTy
, rhsTy
);
1783 return std::make_tuple(result
, result
, Effects::Throws
);
1788 Effects
miFinalIncDecElem(ISS
& env
, int32_t nDiscard
,
1789 IncDecOp subop
, const Type
& key
,
1791 return setOpElemHelper(
1792 env
, nDiscard
, key
, keyLoc
,
1793 [&] (const Type
& before
) {
1794 auto const after
= typeIncDec(subop
, before
);
1795 return std::make_tuple(
1797 isPre(subop
) ? after
: before
,
1798 before
.subtypeOf(BNum
) ? Effects::None
: Effects::Throws
1804 Effects
miFinalUnsetElem(ISS
& env
, int32_t nDiscard
, const Type
& key
) {
1805 // First handle base promotions
1806 auto const promo
= handleElemUDBasePromos(env
);
1808 auto& base
= env
.collect
.mInstrState
.base
.type
;
1809 assertx(!base
.is(BBottom
));
1811 auto const doesNothing
= BNull
| BBool
| BNum
| BRes
;
1812 auto const alwaysThrows
=
1813 BFunc
| BRFunc
| BCls
| BLazyCls
| BStr
| BRecord
| BClsMeth
| BRClsMeth
;
1814 auto const handled
= doesNothing
| alwaysThrows
| BArrLike
| BObj
;
1816 static_assert(handled
== BCell
);
1819 auto update
= false;
1820 auto refine
= BBottom
;
1822 // These silently does nothing
1823 if (base
.couldBe(doesNothing
)) {
1824 effects
= unionEffects(effects
, Effects::None
);
1826 // These always raise an error
1827 if (base
.couldBe(alwaysThrows
)) {
1828 effects
= unionEffects(effects
, Effects::AlwaysThrows
);
1829 refine
|= alwaysThrows
;
1831 // Objects can throw but otherwise do not affect the base
1832 if (base
.couldBe(BObj
)) {
1833 effects
= unionEffects(effects
, Effects::Throws
);
1835 if (base
.couldBe(BArrLike
)) {
1836 // Unset doesn't throw for a missing element on dicts, keysets, or
1837 // darrays, only if the key is invalid. Vecs and varrays silently do
1838 // nothing for string keys, but can throw with int keys.
1839 if (!key
.couldBe(BArrKey
)) {
1840 effects
= unionEffects(effects
, Effects::AlwaysThrows
);
1843 auto e
= key
.subtypeOf(BArrKey
) ? Effects::None
: Effects::Throws
;
1844 if (base
.couldBe(BVec
) && key
.couldBe(BInt
)) e
= Effects::Throws
;
1845 effects
= unionEffects(effects
, e
);
1847 // We purposefully do not model the effects of unset on array
1848 // structure. This lets us assume that if we have array structure,
1849 // we also have no tombstones. Pessimize the base which drops array
1850 // structure and also remove emptiness information.
1851 if (!base
.subtypeAmong(BVec
, BArrLike
) || key
.couldBe(BInt
)) {
1852 base
= loosen_array_staticness(loosen_array_values(std::move(base
)));
1853 base
= loosen_emptiness(std::move(base
));
1859 // Refine the base and remove bits that will always throw
1860 if (refine
&& shouldRefineBase(env
)) {
1861 base
= remove_bits(std::move(base
), refine
);
1865 assertx(effects
.has_value());
1867 return endBaseWithEffects(
1868 env
, *effects
, update
, promo
,
1869 [&] { discard(env
, nDiscard
); }
1873 //////////////////////////////////////////////////////////////////////
1874 // Final new elem ops
1876 Effects
miFinalSetNewElem(ISS
& env
, int32_t nDiscard
) {
1877 auto const rhs
= popC(env
);
1879 // First handle base promotions
1880 auto const promo
= handleElemUDBasePromos(env
);
1882 auto& base
= env
.collect
.mInstrState
.base
.type
;
1883 assertx(!base
.is(BBottom
));
1885 auto const pushesNull
=
1886 BTrue
| BNum
| BRes
| BFunc
| BRFunc
| BCls
|
1887 BLazyCls
| BClsMeth
| BRClsMeth
;
1888 auto const alwaysThrows
= BNull
| BStr
| BRecord
| BFalse
;
1889 auto const handled
= pushesNull
| alwaysThrows
| BObj
| BArrLike
;
1891 static_assert(handled
== BCell
);
1894 auto pushed
= TBottom
;
1895 auto update
= false;
1896 auto refine
= BBottom
;
1898 if (base
.couldBe(pushesNull
)) {
1899 // These emit a warning and push null
1900 effects
= unionEffects(effects
, Effects::Throws
);
1901 pushed
|= TInitNull
;
1903 if (base
.couldBe(alwaysThrows
)) {
1904 // These always raise a fatal
1905 effects
= unionEffects(effects
, Effects::AlwaysThrows
);
1906 refine
|= alwaysThrows
;
1908 if (base
.couldBe(BObj
)) {
1909 // Objects can throw but otherwise don't affect the base and push
1911 effects
= unionEffects(effects
, Effects::Throws
);
1914 if (base
.couldBe(BArrLike
)) {
1915 // Arrays will add a new element and push the right hand side of the
1917 auto const doesThrow
= array_do_newelem(env
, rhs
);
1918 if (doesThrow
== TriBool::Yes
) {
1919 effects
= unionEffects(effects
, Effects::AlwaysThrows
);
1922 effects
= unionEffects(
1924 doesThrow
== TriBool::No
? Effects::None
: Effects::Throws
1931 // Refine the base and remove bits that will always throw
1932 if (refine
&& shouldRefineBase(env
)) {
1933 base
= remove_bits(std::move(base
), refine
);
1937 assertx(effects
.has_value());
1939 return endBaseWithEffects(
1940 env
, *effects
, update
, promo
,
1942 discard(env
, nDiscard
);
1943 push(env
, std::move(pushed
));
1948 Effects
miFinalSetOpNewElem(ISS
& env
, int32_t nDiscard
) {
1950 return setOpNewElemHelper(env
, nDiscard
);
1953 Effects
miFinalIncDecNewElem(ISS
& env
, int32_t nDiscard
) {
1954 return setOpNewElemHelper(env
, nDiscard
);
1957 //////////////////////////////////////////////////////////////////////
1959 // Translate the aggregated effects of the instruction into the
1960 // appropriate interp-state actions. Returns true if the instruction
1961 // is totally effect-free.
1962 bool handleEffects(ISS
& env
, Effects effects
, Promotion keyPromotion
) {
1963 auto const effectFree
= [&]{
1966 if (keyPromotion
== Promotion::YesMightThrow
) return false;
1969 case Effects::SideEffect
:
1970 if (keyPromotion
!= Promotion::YesMightThrow
) nothrow(env
);
1972 case Effects::Throws
:
1974 case Effects::AlwaysThrows
:
1978 always_assert(false);
1981 if (env
.flags
.wasPEI
&& env
.blk
.throwExit
!= NoBlockId
) {
1982 // Minstr opcodes may throw after side-effects.
1983 assertx(!effectFree
);
1984 auto const state
= with_throwable_only(env
.index
, env
.state
);
1985 env
.propagate(env
.blk
.throwExit
, &state
);
1993 namespace interp_step
{
1995 //////////////////////////////////////////////////////////////////////
1998 void in(ISS
& env
, const bc::BaseGC
& op
) {
1999 startBase(env
, Base
{TInitCell
, BaseLoc::Global
});
2002 void in(ISS
& env
, const bc::BaseGL
& op
) {
2003 mayReadLocal(env
, op
.loc1
);
2004 startBase(env
, Base
{TInitCell
, BaseLoc::Global
});
2007 void in(ISS
& env
, const bc::BaseSC
& op
) {
2008 auto tcls
= topC(env
, op
.arg2
);
2009 auto const tname
= topC(env
, op
.arg1
);
2011 // We'll raise an error if its not a class
2012 if (!tcls
.couldBe(BCls
)) return unreachable(env
);
2014 // Lookup what we know about the property
2015 auto lookup
= env
.index
.lookup_static(
2022 // If we definitely didn't find anything, we'll definitely throw
2023 if (lookup
.found
== TriBool::No
|| lookup
.ty
.subtypeOf(BBottom
)) {
2024 return unreachable(env
);
2027 // Whether we might potentially throw because of AttrConst
2028 auto mightConstThrow
= false;
2029 switch (op
.subop3
) {
2030 case MOpMode::Define
:
2031 case MOpMode::Unset
:
2032 case MOpMode::InOut
:
2033 // If its definitely const, we'll always throw. Otherwise we'll
2034 // potentially throw if there's a chance its AttrConst.
2035 if (lookup
.isConst
== TriBool::Yes
) return unreachable(env
);
2036 mightConstThrow
= lookup
.isConst
== TriBool::Maybe
;
2040 // These don't mutate the base, so AttrConst does not apply
2044 // Whether we might potentially throw because of AttrIsReadOnly
2045 if (op
.subop4
== ReadOnlyOp::Mutable
&& lookup
.readOnly
== TriBool::Yes
) {
2046 return unreachable(env
);
2048 auto const mightReadOnlyThrow
=
2049 (op
.subop4
== ReadOnlyOp::Mutable
&& lookup
.readOnly
== TriBool::Maybe
);
2051 // Loading the base from a static property can be considered
2052 // effect_free if there's no possibility of throwing. This requires
2053 // a definitely found, non-AttrLateInit property with normal class
2054 // initialization, and both the class and name have to be the normal
2056 if (lookup
.found
== TriBool::Yes
&&
2057 lookup
.lateInit
== TriBool::No
&&
2058 !lookup
.classInitMightRaise
&&
2060 !mightReadOnlyThrow
&&
2061 tcls
.subtypeOf(BCls
) &&
2062 tname
.subtypeOf(BStr
)) {
2064 // If we're not mutating the base, and the base is a constant,
2065 // turn it into a BaseC with the appropriate constant on the
2067 if (op
.subop3
== MOpMode::Warn
|| op
.subop3
== MOpMode::None
) {
2068 if (auto const v
= tv(lookup
.ty
)) {
2069 reduce(env
, gen_constant(*v
), bc::BaseC
{ 0, op
.subop3
});
2070 env
.collect
.mInstrState
.extraPop
= true;
2081 std::move(lookup
.ty
),
2082 BaseLoc::StaticProp
,
2089 void in(ISS
& env
, const bc::BaseL
& op
) {
2090 auto ty
= peekLocRaw(env
, op
.nloc1
.id
);
2092 // An Uninit local base can raise a notice.
2093 if (!ty
.couldBe(BUninit
)) {
2094 // If we're not mutating the base, and the base is a constant,
2095 // turn it into a BaseC with the appropriate constant on the
2097 if (op
.subop2
== MOpMode::Warn
|| op
.subop2
== MOpMode::None
) {
2098 if (auto const v
= tv(ty
)) {
2099 reduce(env
, gen_constant(*v
), bc::BaseC
{ 0, op
.subop2
});
2100 env
.collect
.mInstrState
.extraPop
= true;
2104 // Try to find an equivalent local to use instead
2105 auto const minLocEquiv
= findMinLocEquiv(env
, op
.nloc1
.id
, false);
2106 if (minLocEquiv
!= NoLocalId
) {
2110 NamedLocal
{ kInvalidLocalName
, minLocEquiv
},
2118 } else if (op
.subop2
!= MOpMode::Warn
) {
2119 // The local could be Uninit, but we won't warn about it anyways.
2123 mayReadLocal(env
, op
.nloc1
.id
);
2124 if (ty
.subtypeOf(BBottom
)) return unreachable(env
);
2132 op
.nloc1
.name
!= kInvalidLocalName
2133 ? env
.ctx
.func
->locals
[op
.nloc1
.name
].name
2140 void in(ISS
& env
, const bc::BaseC
& op
) {
2141 assertx(op
.arg1
< env
.state
.stack
.size());
2142 auto ty
= topC(env
, op
.arg1
);
2143 if (ty
.subtypeOf(BBottom
)) return unreachable(env
);
2153 (uint32_t)env
.state
.stack
.size() - op
.arg1
- 1
2158 void in(ISS
& env
, const bc::BaseH
&) {
2159 auto const ty
= thisTypeNonNull(env
);
2160 if (ty
.subtypeOf(BBottom
)) return unreachable(env
);
2162 startBase(env
, Base
{ty
, BaseLoc::This
});
2165 //////////////////////////////////////////////////////////////////////
2166 // Intermediate operations
2168 void in(ISS
& env
, const bc::Dim
& op
) {
2169 auto key
= key_type_or_fixup(env
, op
);
2172 auto const effects
= [&] {
2173 if (mcodeIsProp(op
.mkey
.mcode
)) {
2175 env
, op
.mkey
.mcode
== MQT
, op
.subop1
, std::move(key
->first
), op
.mkey
.rop
2177 } else if (mcodeIsElem(op
.mkey
.mcode
)) {
2178 return miElem(env
, op
.subop1
, std::move(key
->first
), key_local(env
, op
));
2180 return miNewElem(env
);
2184 if (effects
!= Effects::None
) {
2185 env
.collect
.mInstrState
.effectFree
= false;
2187 if (!handleEffects(env
, effects
, key
->second
)) return;
2189 // This instruction must be effect free
2190 assertx(env
.flags
.effectFree
);
2191 assertx(!env
.state
.unreachable
);
2193 // If the base is a constant, and we're not mutating the base, and
2194 // if the entire minstr sequence up until now has been effect-free,
2195 // we can remove the entire sequence up until now. We replace it
2196 // with just a BaseC on the constant pushed onto the stack.
2197 if ((op
.subop1
== MOpMode::None
|| op
.subop1
== MOpMode::Warn
) &&
2198 env
.collect
.mInstrState
.effectFree
&&
2200 is_scalar(env
.collect
.mInstrState
.base
.type
)) {
2201 // Find the base instruction which started the sequence.
2202 for (int i
= 0; ; i
++) {
2203 auto const last
= last_op(env
, i
);
2205 if (isMemberBaseOp(last
->op
)) {
2206 auto const base
= *last
;
2208 // We'll need to push the constant onto the stack. If the
2209 // sequence originally started with a BaseC (or BaseGC)
2210 // instruction, we can just pop off the original value and
2211 // replace it with the constant. This leaves all offsets the
2212 // same. If not, we push the constant and set extraPop, which
2213 // makes us increment all of the offsets when we reprocess
2215 auto const reuseStack
=
2218 case Op::BaseGC
: return base
.BaseGC
.arg1
== 0;
2219 case Op::BaseC
: return base
.BaseC
.arg1
== 0;
2220 default: return false;
2223 assertx(!env
.collect
.mInstrState
.extraPop
|| reuseStack
);
2224 auto const extraPop
= !reuseStack
|| env
.collect
.mInstrState
.extraPop
;
2225 env
.collect
.mInstrState
.clear();
2226 if (reuseStack
) reduce(env
, bc::PopC
{});
2227 auto const v
= tv(env
.collect
.mInstrState
.base
.type
);
2229 reduce(env
, gen_constant(*v
), bc::BaseC
{ 0, op
.subop1
});
2230 env
.collect
.mInstrState
.extraPop
= extraPop
;
2233 if (!isMemberDimOp(last
->op
)) break;
2238 //////////////////////////////////////////////////////////////////////
2241 const StaticString
s_classname("classname");
2242 const StaticString
s_type_structure("HH\\type_structure");
2243 const StaticString
s_type_structure_classname("HH\\type_structure_classname");
2245 void in(ISS
& env
, const bc::QueryM
& op
) {
2246 auto const key
= key_type_or_fixup(env
, op
);
2248 auto const nDiscard
= op
.arg1
;
2250 auto const effects
= [&] {
2251 if (mcodeIsProp(op
.mkey
.mcode
)) {
2252 // We don't currently do anything different for nullsafe query ops.
2253 switch (op
.subop2
) {
2254 case QueryMOp::CGet
:
2255 case QueryMOp::CGetQuiet
:
2256 return miFinalCGetProp(env
, nDiscard
, key
->first
,
2257 op
.subop2
== QueryMOp::CGetQuiet
,
2258 op
.mkey
.rop
== ReadOnlyOp::Mutable
);
2259 case QueryMOp::Isset
:
2260 return miFinalIssetProp(env
, nDiscard
, key
->first
);
2261 case QueryMOp::InOut
:
2262 always_assert(false);
2264 always_assert(false);
2265 } else if (mcodeIsElem(op
.mkey
.mcode
)) {
2266 switch (op
.subop2
) {
2267 case QueryMOp::InOut
:
2268 case QueryMOp::CGet
:
2269 case QueryMOp::CGetQuiet
:
2270 return miFinalCGetElem(
2271 env
, nDiscard
, key
->first
, getQueryMOpMode(op
.subop2
)
2273 case QueryMOp::Isset
:
2274 return miFinalIssetElem(env
, nDiscard
, key
->first
);
2276 always_assert(false);
2278 // QueryMNewElem will always throw without doing any work.
2279 discard(env
, nDiscard
);
2281 return Effects::AlwaysThrows
;
2285 // Try to detect type_structure(cls_name, cns_name)['classname'] and
2286 // reduce this to type_structure_classname(cls_name, cns_name)
2287 if (mcodeIsElem(op
.mkey
.mcode
) &&
2288 op
.subop2
== QueryMOp::CGet
&&
2290 op
.mkey
.mcode
== MemberCode::MET
&&
2291 op
.mkey
.litstr
->isame(s_classname
.get())) {
2292 if (auto const last
= last_op(env
, 0)) {
2293 if (last
->op
== Op::BaseC
) {
2294 if (auto const prev
= last_op(env
, 1)) {
2295 if (prev
->op
== Op::FCallFuncD
&&
2296 prev
->FCallFuncD
.str2
->isame(s_type_structure
.get()) &&
2297 prev
->FCallFuncD
.fca
.numArgs() == 2) {
2298 auto const params
= prev
->FCallFuncD
.fca
.numArgs();
2299 rewind(env
, op
); // querym
2300 rewind(env
, 2); // basec + fcallfuncd
2301 env
.collect
.mInstrState
.clear();
2306 s_type_structure_classname
.get()
2315 if (effects
!= Effects::None
) env
.collect
.mInstrState
.effectFree
= false;
2317 // For the QueryM ops, its our responsibility to call endBase()
2318 // (unless we'll always throw).
2320 if (!handleEffects(env
, effects
, key
->second
)) {
2321 if (effects
!= Effects::AlwaysThrows
) endBase(env
, false);
2325 assertx(env
.flags
.effectFree
);
2326 assertx(!env
.state
.unreachable
);
2328 // If the QueryM produced a constant without any possible
2329 // side-ffects, we can replace the entire thing with the constant.
2330 if (env
.collect
.mInstrState
.effectFree
&& is_scalar(topC(env
))) {
2331 for (int i
= 0; ; i
++) {
2332 auto const last
= last_op(env
, i
);
2334 if (isMemberBaseOp(last
->op
)) {
2335 auto const v
= tv(topC(env
));
2338 env
.collect
.mInstrState
.clear();
2339 BytecodeVec bcs
{nDiscard
, bc::PopC
{}};
2340 bcs
.push_back(gen_constant(*v
));
2341 return reduce(env
, std::move(bcs
));
2343 if (!isMemberDimOp(last
->op
)) break;
2346 endBase(env
, false);
2349 void in(ISS
& env
, const bc::SetM
& op
) {
2350 auto const key
= key_type_or_fixup(env
, op
);
2353 auto const effects
= [&] {
2354 if (mcodeIsProp(op
.mkey
.mcode
)) {
2355 return miFinalSetProp(env
, op
.arg1
, key
->first
, op
.mkey
.rop
);
2356 } else if (mcodeIsElem(op
.mkey
.mcode
)) {
2357 return miFinalSetElem(env
, op
.arg1
, key
->first
, key_local(env
, op
));
2359 return miFinalSetNewElem(env
, op
.arg1
);
2362 handleEffects(env
, effects
, key
->second
);
2365 void in(ISS
& env
, const bc::SetRangeM
& op
) {
2369 discard(env
, op
.arg1
);
2370 auto& base
= env
.collect
.mInstrState
.base
.type
;
2371 if (!base
.couldBe(BStr
)) return unreachable(env
);
2372 base
= loosen_staticness(loosen_values(std::move(base
)));
2376 void in(ISS
& env
, const bc::IncDecM
& op
) {
2377 auto const key
= key_type_or_fixup(env
, op
);
2380 auto const effects
= [&] {
2381 if (mcodeIsProp(op
.mkey
.mcode
)) {
2382 return miFinalIncDecProp(env
, op
.arg1
, op
.subop2
, key
->first
);
2383 } else if (mcodeIsElem(op
.mkey
.mcode
)) {
2384 return miFinalIncDecElem(
2385 env
, op
.arg1
, op
.subop2
, key
->first
, key_local(env
, op
)
2388 return miFinalIncDecNewElem(env
, op
.arg1
);
2391 handleEffects(env
, effects
, key
->second
);
2394 void in(ISS
& env
, const bc::SetOpM
& op
) {
2395 auto const key
= key_type_or_fixup(env
, op
);
2398 auto const effects
= [&] {
2399 if (mcodeIsProp(op
.mkey
.mcode
)) {
2400 return miFinalSetOpProp(env
, op
.arg1
, op
.subop2
, key
->first
);
2401 } else if (mcodeIsElem(op
.mkey
.mcode
)) {
2402 return miFinalSetOpElem(
2403 env
, op
.arg1
, op
.subop2
, key
->first
, key_local(env
, op
)
2406 return miFinalSetOpNewElem(env
, op
.arg1
);
2409 handleEffects(env
, effects
, key
->second
);
2412 void in(ISS
& env
, const bc::UnsetM
& op
) {
2413 auto const key
= key_type_or_fixup(env
, op
);
2416 auto const effects
= [&] {
2417 if (mcodeIsProp(op
.mkey
.mcode
)) {
2418 return miFinalUnsetProp(env
, op
.arg1
, key
->first
);
2420 assertx(mcodeIsElem(op
.mkey
.mcode
));
2421 return miFinalUnsetElem(env
, op
.arg1
, key
->first
);
2424 handleEffects(env
, effects
, key
->second
);
2429 //////////////////////////////////////////////////////////////////////