ReadOnly -> ::Readonly
[hiphop-php.git] / hphp / hhbbc / interp-minstr.cpp
blob0a99c9840a9f60852890e73f1572b000fe4b3e38
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
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"
18 #include <vector>
19 #include <algorithm>
20 #include <string>
21 #include <utility>
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 {
33 namespace {
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
43 // bytecode.
44 enum class Effects {
45 None, // Effect-free
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
53 // else).
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
66 // into it.
67 using OptEffects = Optional<Effects>;
69 OptEffects unionEffects(OptEffects e1, Effects e2) {
70 if (!e1) return e2;
71 return unionEffects(*e1, e2);
74 //////////////////////////////////////////////////////////////////////
77 * A note about bases.
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
85 * local variable.
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
98 * $this.
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);
114 return false;
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) {
133 return
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,
153 const Type& key,
154 const Type& value) {
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(
164 base,
165 validKey ? key : intersection_of(key, TArrKey),
166 value
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)) {
171 assertx(set.second);
172 return TriBool::Yes;
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.
188 struct ElemResult {
189 Type elem;
190 TriBool throws;
191 bool present;
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)
214 return ElemResult {
215 std::move(r.first),
216 maybeOrNo(!validKey || !validArr),
217 r.second
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);
234 return TriBool::Yes;
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()
251 : "$<unnamed>",
252 show(ty)
254 setLoc(
255 env,
256 env.collect.mInstrState.base.locLocal,
257 std::move(ty),
258 firstKeyLoc
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());
269 if (env.undo) {
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;
280 FTRACE(
281 4, " ({})::$({}) |= {}\n",
282 show(base.locTy), show(nameTy), show(ty)
285 env.index.merge_static_type(
286 env.ctx,
287 env.collect.publicSPropMutations,
288 env.collect.props,
289 base.locTy,
290 nameTy,
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;
302 --it;
303 assertx(it->base.subtypeOf(BArrLike));
304 assertx(it->key.subtypeOf(BArrKey));
305 val = array_like_set(it->base, it->key, val).first;
307 return val;
310 Type resolveArrayChain(ISS& env, Type val) {
311 static UNUSED const char prefix[] = " ";
312 FTRACE(5, "{}chain {}\n", prefix, show(val));
313 do {
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();
318 break;
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));
330 return 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,
337 const Type& ty,
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
348 // side-effect.
349 return !any(env.collect.opts & CollectionOpts::Speculating);
351 if (mustBeInStack(base)) {
352 setStackForBase(env, ty);
353 return true;
355 if (mustBeInStatic(base)) {
356 setStaticForBase(env, ty);
357 return false;
360 return true;
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()
395 ? keyLoc
396 : state.arrayChain.data()->keyLoc;
397 auto const& ty = state.arrayChain.empty()
398 ? state.base.type
399 : resolveArrayChain(env, state.base.type);
401 auto const effectFree = update
402 ? updateBaseWithType(env, ty, firstKeyLoc)
403 : true;
404 state.base.loc = BaseLoc::None;
405 return effectFree;
408 // Return true if the base is updated and that update is considered
409 // "effect-free" (see updateBaseWithType).
410 bool moveBase(ISS& env,
411 Base newBase,
412 bool update = true,
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()
424 ? keyLoc
425 : state.arrayChain.data()->keyLoc;
426 auto const& ty = state.arrayChain.empty()
427 ? state.base.type
428 : resolveArrayChain(env, state.base.type);
430 auto const effectFree = update
431 ? updateBaseWithType(env, ty, firstKeyLoc)
432 : true;
433 state.base = std::move(newBase);
434 return effectFree;
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
451 // qualify.
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{
460 std::move(arr),
461 std::move(key),
462 keyLoc
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(
471 env,
472 currentChainType(env, state.base.type),
473 firstKeyLoc
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: {
492 op.mkey.idx++;
493 return true;
495 default:
496 return false;
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) {
505 op.arg1++;
506 return 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;
520 reduce(env, op);
521 env.collect.mInstrState.extraPop = true;
522 return std::nullopt;
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
531 // local.
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;
540 reduce(env, op);
541 return std::nullopt;
543 if (!isProp && val->m_type == KindOfInt64) {
544 op.mkey.mcode = MEI;
545 op.mkey.int64 = val->m_data.num;
546 reduce(env, op);
547 return std::nullopt;
550 return promoted;
552 switch (op.mkey.mcode) {
553 case MEC: case MPC:
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 };
562 reduce(env, op);
563 return std::nullopt;
566 return fixup(
567 locAsCell(env, op.mkey.local.id),
568 op.mkey.mcode == MPL,
569 couldBeUninit
572 case MW:
573 return std::make_pair(TBottom, Promotion::No);
574 case MEI:
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);
579 not_reached();
582 template<typename Op>
583 LocalId key_local(ISS& env, Op op) {
584 switch (op.mkey.mcode) {
585 case MEC: case MPC:
586 return topStkLocal(env, op.mkey.idx);
587 case MEL: case MPL:
588 return op.mkey.local.id;
589 case MW:
590 case MEI:
591 case MET: case MPT: case MQT:
592 return NoLocalId;
594 not_reached();
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
614 // occurred).
615 template <typename F>
616 Effects endBaseWithEffects(ISS& env, Effects effects, bool update,
617 Promotion basePromo,
618 const F& finish,
619 LocalId keyLoc = NoLocalId) {
620 if (effects == Effects::AlwaysThrows) {
621 auto const e = endUnreachableBase(env, basePromo, keyLoc);
622 finish();
623 return e;
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);
630 finish();
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
636 // effect_free).
637 if (!effectFree && effects == Effects::None) return Effects::SideEffect;
638 return effects;
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
656 // or we'll throw.
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);
672 OptEffects effects;
673 auto ty = TBottom;
675 // These emit a warning and push InitNull
676 if (base.couldBe(warnsWithNull)) {
677 effects = unionEffects(effects, Effects::Throws);
678 ty |= TInitNull;
680 // These silently push InitNull
681 if (base.couldBe(justNull)) {
682 effects = unionEffects(
683 effects,
684 inOutFail ? Effects::Throws : Effects::None
686 ty |= TInitNull;
688 // Strings will return a static string (a character from itself or
689 // an empty string). Class-likes will convert to its equivalent string
690 // first.
691 if (base.couldBe(BStr | BCls | BLazyCls)) {
692 auto const isNoThrow =
693 !inOutFail &&
694 mode != MOpMode::Warn &&
695 key.subtypeOf(BArrKey) &&
696 (!base.couldBe(BCls | BLazyCls) || !RO::EvalRaiseClassConversionWarning);
697 effects = unionEffects(
698 effects,
699 isNoThrow ? Effects::None : Effects::Throws
701 ty |= TSStr;
703 // These can throw and push anything.
704 if (base.couldBe(BObj | BRecord)) {
705 effects = unionEffects(effects, Effects::Throws);
706 ty |= TInitCell;
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) {
712 // Key is bad
713 effects = unionEffects(effects, Effects::AlwaysThrows);
714 } else {
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.
718 if (!elem.present) {
719 if (mode == MOpMode::None) {
720 elem.elem |= TInitNull;
721 } else {
722 mightThrow = true;
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);
731 } else {
732 ty |= std::move(elem.elem);
733 effects = unionEffects(
734 effects,
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;
757 auto const null =
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);
764 OptEffects effects;
765 auto pushed = TBottom;
766 auto refine = BBottom;
767 auto update = false;
769 // Always throws
770 if (base.couldBe(throws)) {
771 effects = unionEffects(effects, Effects::AlwaysThrows);
772 refine |= throws;
774 // Raises a warning and pushes null
775 if (base.couldBe(null)) {
776 effects = unionEffects(effects, Effects::Throws);
777 pushed |= TInitNull;
779 // Objects can throw and push anything
780 if (base.couldBe(BObj | BRecord)) {
781 effects = unionEffects(effects, Effects::Throws);
782 pushed |= TInitCell;
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);
788 pushed |= TInitCell;
789 update = true;
791 if (base.couldBe(BArrLike)) {
792 auto const unreachable = [&] {
793 effects = unionEffects(effects, Effects::AlwaysThrows);
794 refine |= BArrLike;
797 [&] {
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.
802 unreachable();
803 return;
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
812 // update.
813 base = loosen_array_staticness(std::move(base));
814 update = true;
816 // Perform the op
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.
821 unreachable();
822 return;
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.
831 unreachable();
832 return;
835 auto const maybeThrows =
836 !elem.present ||
837 elem.throws == TriBool::Maybe ||
838 set == TriBool::Maybe;
839 effects = unionEffects(
840 effects,
841 maybeThrows ? Effects::Throws : opEffects
843 pushed |= std::move(toPush);
844 }();
847 // Refine the base and remove bits that will always throw
848 if (refine && shouldRefineBase(env)) {
849 base = remove_bits(std::move(base), refine);
850 update = true;
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
857 // example).
858 if (*effects == Effects::AlwaysThrows && update) {
859 assertx(pushed.is(BBottom));
860 endBase(env, true, keyLoc);
861 discard(env, nDiscard);
862 push(env, TBottom);
863 return Effects::AlwaysThrows;
866 return endBaseWithEffects(
867 env, *effects, update, Promotion::No,
868 [&] {
869 discard(env, nDiscard);
870 push(env, std::move(pushed));
872 keyLoc
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;
884 auto const null =
885 BTrue | BNum | BRes | BRFunc | BFunc | BRClsMeth | BCls | BLazyCls;
886 auto const handled = alwaysThrow | null;
888 static_assert(handled == BCell);
890 OptEffects effects;
891 auto pushed = TBottom;
892 auto refine = BBottom;
893 auto update = false;
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);
903 pushed |= TInitNull;
906 // Refine the base and remove bits that will always throw
907 if (refine && shouldRefineBase(env)) {
908 base = remove_bits(std::move(base), refine);
909 update = true;
912 assertx(effects.has_value());
914 return endBaseWithEffects(
915 env, *effects, update, Promotion::No,
916 [&] {
917 discard(env, nDiscard);
918 push(env, std::move(pushed));
923 //////////////////////////////////////////////////////////////////////
924 // intermediate ops
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
938 * InitNull.
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)) {
949 if (name) {
950 if (auto const elem = thisPropType(env, name)) {
951 if (elem->couldBe(BUninit)) mergeThisProp(env, name, TInitNull);
953 } else {
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;
963 if (name) {
964 if (checkReadonlyOp(ReadonlyOp::Mutable, op) &&
965 isDefinitelyThisPropAttr(env, name, AttrIsReadonly)) {
966 return Effects::AlwaysThrows;
969 auto const [ty, effects] = [&] () -> std::pair<Type, Effects> {
970 if (update) {
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());
990 return {
991 *propTy,
992 elem->couldBe(BUninit) ? Effects::Throws : Effects::None
995 auto const raw =
996 env.index.lookup_public_prop(objcls(thisTy), sval(name));
997 return { update ? raw : to_cell(raw), Effects::Throws };
998 }();
1000 if (ty.subtypeOf(BBottom)) return Effects::AlwaysThrows;
1001 moveBase(
1002 env,
1003 Base { ty, BaseLoc::Prop, thisTy, name },
1004 update
1006 return effects;
1007 } else {
1008 moveBase(env,
1009 Base { TInitCell, BaseLoc::Prop, thisTy },
1010 update);
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)) {
1017 auto const raw =
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;
1024 moveBase(env,
1025 Base { ty,
1026 BaseLoc::Prop,
1027 env.collect.mInstrState.base.type,
1028 name },
1029 update);
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.
1041 moveBase(env,
1042 Base { TInitCell, BaseLoc::Prop, TCell, name },
1043 update);
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.
1057 moveBase(
1058 env, Base { std::move(elem), BaseLoc::Elem }, false, keyLoc
1061 return effects;
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(
1074 env,
1075 Base { std::move(ty), BaseLoc::Elem },
1076 update,
1077 keyLoc
1079 if (!effectFree && effects == Effects::None) return Effects::SideEffect;
1080 return effects;
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;
1092 return effects;
1095 if (isUnset) {
1096 // ElemU. These types either always throw, or set the base to
1097 // Uninit.
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);
1125 return extend(
1126 std::move(elem.elem),
1127 (maybeAlwaysThrows || elem.throws == TriBool::Maybe)
1128 ? Effects::Throws : Effects::None
1132 OptEffects effects;
1133 auto ty = TBottom;
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);
1145 ty |= TUninit;
1147 // Objects can throw and retrieve anything
1148 if (base.couldBe(BObj)) {
1149 effects = unionEffects(effects, Effects::Throws);
1150 ty |= TInitCell;
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);
1158 refine |= BArrLike;
1159 } else {
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)));
1168 update = true;
1169 effects = unionEffects(
1170 effects,
1171 elem.throws == TriBool::Maybe
1172 ? Effects::Throws
1173 : Effects::None
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);
1182 update = true;
1185 assertx(effects.has_value());
1187 return move(std::move(ty), update, *effects);
1188 } else {
1189 assertx(isDefine);
1191 // ElemD. These types either always throw, or emit a warning and
1192 // push InitNull.
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 ||
1223 !elem.present ||
1224 elem.throws == TriBool::Maybe;
1225 return extend(
1226 std::move(elem.elem),
1227 mightThrow ? Effects::Throws : Effects::None
1231 OptEffects effects;
1232 auto ty = TBottom;
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);
1244 ty |= TInitNull;
1246 // Objects can throw and push anything
1247 if (base.couldBe(BObj)) {
1248 effects = unionEffects(effects, Effects::Throws);
1249 ty |= TInitCell;
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);
1255 ty |= TInitCell;
1256 update = true;
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);
1264 refine |= BArrLike;
1265 } else {
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)));
1273 update = true;
1274 effects = unionEffects(
1275 effects,
1276 (!elem.present || elem.throws == TriBool::Maybe)
1277 ? Effects::Throws
1278 : Effects::None
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);
1287 update = true;
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
1298 // base to Uninit.
1299 auto& base = env.collect.mInstrState.base.type;
1300 assertx(!base.is(BBottom));
1302 auto const alwaysThrows =
1303 BNull | BFalse | BArrLike | BObj | BClsMeth | BRecord;
1304 auto const uninit =
1305 BTrue | BNum | BRes | BRFunc | BFunc |
1306 BRClsMeth | BCls | BLazyCls;
1307 auto const handled = alwaysThrows | uninit | BStr;
1309 static_assert(handled == BCell);
1311 OptEffects effects;
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);
1322 newBase |= TUninit;
1324 if (base.couldBe(BStr)) {
1325 if (is_specialized_string(base) && sval_of(base)->empty()) {
1326 effects = unionEffects(effects, Effects::AlwaysThrows);
1327 refine |= BStr;
1328 } else {
1329 effects = unionEffects(effects, Effects::Throws);
1330 newBase |= TUninit;
1334 // Refine the base and remove bits that will always throw
1335 if (refine && shouldRefineBase(env)) {
1336 base = remove_bits(std::move(base), refine);
1337 update = true;
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);
1345 return *effects;
1348 //////////////////////////////////////////////////////////////////////
1349 // final prop ops
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
1359 // all.
1360 push(env, pt->subtypeOf(BBottom) ? TFalse : TBool);
1361 } else if (pt->subtypeOf(BNull)) {
1362 push(env, TFalse);
1363 } else if (!pt->couldBe(BNull)) {
1364 push(env, TTrue);
1365 } else {
1366 push(env, TBool);
1368 return Effects::None;
1372 push(env, TBool);
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;
1384 if (name) {
1385 if (mustBeThisObj(env, env.collect.mInstrState.base)) {
1386 if (auto t = thisPropAsCell(env, name)) {
1387 if (t->subtypeOf(BBottom)) {
1388 push(env, TBottom);
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;
1405 auto ty = [&] {
1406 if (!base.couldBe(BObj)) return TInitNull;
1407 auto t = to_cell(
1408 env.index.lookup_public_prop(
1409 objcls(base.subtypeOf(BObj) ? base : intersection_of(base, TObj)),
1410 sval(name)
1413 if (!base.subtypeOf(BObj)) t = opt(std::move(t));
1414 return t;
1415 }();
1416 auto const e =
1417 ty.subtypeOf(BBottom) ? Effects::AlwaysThrows : Effects::Throws;
1418 push(env, std::move(ty));
1419 return e;
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) {
1431 endBase(env);
1432 discard(env, nDiscard);
1433 push(env, std::move(ty));
1434 return Effects::Throws;
1437 auto const alwaysThrows = [&] {
1438 discard(env, nDiscard);
1439 push(env, TBottom);
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)) {
1449 if (!name) {
1450 mergeEachThisPropRaw(
1451 env,
1452 [&] (const Type& propTy) {
1453 return propTy.couldBe(BInitCell) ? t1 : TBottom;
1456 } else {
1457 mergeThisProp(env, name, t1);
1461 if (env.collect.mInstrState.base.type.subtypeOf(BObj)) {
1462 if (t1.subtypeOf(BBottom)) return alwaysThrows();
1463 moveBase(
1464 env,
1465 Base { t1, BaseLoc::Prop, env.collect.mInstrState.base.type, name }
1467 return finish(t1);
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)) {
1482 endBase(env);
1483 discard(env, nDiscard);
1484 push(env, TInitNull);
1485 return Effects::Throws;
1488 auto lhsTy = [&] {
1489 if (name) {
1490 if (mustBeThisObj(env, env.collect.mInstrState.base)) {
1491 if (auto const t = thisPropAsCell(env, name)) return *t;
1493 return to_cell(
1494 env.index.lookup_public_prop(
1495 objcls(base.subtypeOf(BObj) ? base : intersection_of(base, TObj)),
1496 sval(name)
1500 return TInitCell;
1501 }();
1502 if (lhsTy.subtypeOf(BBottom)) {
1503 discard(env, nDiscard);
1504 push(env, TBottom);
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)
1511 : TInitCell;
1513 if (resultTy.subtypeOf(BBottom)) {
1514 discard(env, nDiscard);
1515 push(env, TBottom);
1516 return Effects::AlwaysThrows;
1519 if (couldBeThisObj(env, env.collect.mInstrState.base)) {
1520 if (name) {
1521 mergeThisProp(env, name, resultTy);
1522 } else {
1523 killThisProps(env);
1527 endBase(env);
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)) {
1540 endBase(env);
1541 discard(env, nDiscard);
1542 push(env, TInitNull);
1543 return Effects::Throws;
1546 auto postPropTy = [&] {
1547 if (name) {
1548 if (mustBeThisObj(env, env.collect.mInstrState.base)) {
1549 if (auto const t = thisPropAsCell(env, name)) return *t;
1551 return to_cell(
1552 env.index.lookup_public_prop(
1553 objcls(base.subtypeOf(BObj) ? base : intersection_of(base, TObj)),
1554 sval(name)
1558 return TInitCell;
1559 }();
1560 if (postPropTy.subtypeOf(BBottom)) {
1561 discard(env, nDiscard);
1562 push(env, TBottom);
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)
1569 : TInitCell;
1570 if (prePropTy.subtypeOf(BBottom)) {
1571 discard(env, nDiscard);
1572 push(env, TBottom);
1573 return Effects::AlwaysThrows;
1576 if (couldBeThisObj(env, env.collect.mInstrState.base)) {
1577 if (name) {
1578 mergeThisProp(env, name, prePropTy);
1579 } else {
1580 killThisProps(env);
1584 endBase(env);
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);
1594 } else {
1595 unsetUnknownThisProp(env);
1599 endBase(env);
1600 discard(env, nDiscard);
1601 return Effects::Throws;
1604 //////////////////////////////////////////////////////////////////////
1605 // Final elem ops
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));
1612 return effects;
1615 Effects miFinalIssetElem(ISS& env,
1616 int32_t nDiscard,
1617 const Type& key) {
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;
1625 OptEffects effects;
1626 auto pushed = TBottom;
1628 if (base.couldBe(pushesFalse)) {
1629 effects = unionEffects(effects, Effects::None);
1630 pushed |= TFalse;
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);
1636 } else {
1637 effects = unionEffects(
1638 effects,
1639 elem.throws == TriBool::Maybe
1640 ? Effects::Throws
1641 : Effects::None
1644 if (elem.elem.subtypeOf(BNull)) {
1645 pushed |= TFalse;
1646 } else if (elem.present && !elem.elem.couldBe(BNull)) {
1647 pushed |= TTrue;
1648 } else {
1649 pushed |= TBool;
1654 if (!base.subtypeOf(handled)) {
1655 effects = unionEffects(effects, Effects::Throws);
1656 pushed |= TBool;
1659 assertx(effects.has_value());
1660 discard(env, nDiscard);
1661 push(env, std::move(pushed));
1662 return *effects;
1665 Effects miFinalSetElem(ISS& env,
1666 int32_t nDiscard,
1667 const Type& key,
1668 LocalId keyLoc) {
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);
1683 OptEffects effects;
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);
1706 refine |= BStr;
1707 } else {
1708 effects = unionEffects(effects, Effects::Throws);
1709 base = loosen_string_staticness(loosen_string_values(std::move(base)));
1710 update = true;
1711 pushed |= TOptSStr;
1714 if (base.couldBe(BObj)) {
1715 // Objects can throw but otherwise don't affect the base and push
1716 // the rhs.
1717 effects = unionEffects(effects, Effects::Throws);
1718 pushed |= rhs;
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);
1724 pushed |= rhs;
1725 update = true;
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
1730 // set on them).
1731 auto const doesThrow = array_do_set(env, key, rhs);
1732 if (doesThrow == TriBool::Yes) {
1733 effects = unionEffects(effects, Effects::AlwaysThrows);
1734 refine |= BArrLike;
1735 } else {
1736 effects = unionEffects(
1737 effects,
1738 doesThrow == TriBool::No ? Effects::None : Effects::Throws
1740 pushed |= rhs;
1741 update = true;
1745 // Refine the base and remove bits that will always throw
1746 if (refine && shouldRefineBase(env)) {
1747 base = remove_bits(std::move(base), refine);
1748 update = true;
1751 assertx(effects.has_value());
1753 return endBaseWithEffects(
1754 env, *effects, update, Promotion::No,
1755 [&] {
1756 discard(env, nDiscard);
1757 push(env, std::move(pushed));
1759 keyLoc
1763 Effects miFinalSetOpElem(ISS& env, int32_t nDiscard,
1764 SetOpOp subop, const Type& key,
1765 LocalId keyLoc) {
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,
1778 LocalId keyLoc) {
1779 return setOpElemHelper(
1780 env, nDiscard, key, keyLoc,
1781 [&] (const Type& before) {
1782 auto const after = typeIncDec(subop, before);
1783 return std::make_tuple(
1784 after,
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);
1804 OptEffects effects;
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);
1827 refine |= BArrLike;
1828 } else {
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));
1840 update = true;
1845 // Refine the base and remove bits that will always throw
1846 if (refine && shouldRefineBase(env)) {
1847 base = remove_bits(std::move(base), refine);
1848 update = true;
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);
1876 OptEffects effects;
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
1893 // the rhs.
1894 effects = unionEffects(effects, Effects::Throws);
1895 pushed |= rhs;
1897 if (base.couldBe(BArrLike)) {
1898 // Arrays will add a new element and push the right hand side of the
1899 // assignment.
1900 auto const doesThrow = array_do_newelem(env, rhs);
1901 if (doesThrow == TriBool::Yes) {
1902 effects = unionEffects(effects, Effects::AlwaysThrows);
1903 refine |= BArrLike;
1904 } else {
1905 effects = unionEffects(
1906 effects,
1907 doesThrow == TriBool::No ? Effects::None : Effects::Throws
1909 pushed |= rhs;
1910 update = true;
1914 // Refine the base and remove bits that will always throw
1915 if (refine && shouldRefineBase(env)) {
1916 base = remove_bits(std::move(base), refine);
1917 update = true;
1920 assertx(effects.has_value());
1922 return endBaseWithEffects(
1923 env, *effects, update, Promotion::No,
1924 [&] {
1925 discard(env, nDiscard);
1926 push(env, std::move(pushed));
1931 Effects miFinalSetOpNewElem(ISS& env, int32_t nDiscard) {
1932 popC(env);
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 = [&]{
1947 switch (effects) {
1948 case Effects::None:
1949 if (keyPromotion == Promotion::YesMightThrow) return false;
1950 effect_free(env);
1951 return true;
1952 case Effects::SideEffect:
1953 if (keyPromotion != Promotion::YesMightThrow) nothrow(env);
1954 return false;
1955 case Effects::Throws:
1956 return false;
1957 case Effects::AlwaysThrows:
1958 unreachable(env);
1959 return false;
1961 always_assert(false);
1962 }();
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);
1971 return effectFree;
1976 namespace interp_step {
1978 //////////////////////////////////////////////////////////////////////
1979 // Base operations
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(
1999 env.ctx,
2000 env.collect.props,
2001 tcls,
2002 tname
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;
2020 break;
2021 case MOpMode::None:
2022 case MOpMode::Warn:
2023 // These don't mutate the base, so AttrConst does not apply
2024 break;
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
2053 // types.
2054 if (lookup.found == TriBool::Yes &&
2055 lookup.lateInit == TriBool::No &&
2056 !lookup.classInitMightRaise &&
2057 !mightConstThrow &&
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
2064 // stack.
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;
2069 return;
2073 effect_free(env);
2076 return startBase(
2077 env,
2078 Base {
2079 std::move(lookup.ty),
2080 BaseLoc::StaticProp,
2081 std::move(tcls),
2082 lookup.name
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
2099 // stack.
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;
2104 return;
2107 // Try to find an equivalent local to use instead
2108 auto const minLocEquiv = findMinLocEquiv(env, op.nloc1.id, false);
2109 if (minLocEquiv != NoLocalId) {
2110 return reduce(
2111 env,
2112 bc::BaseL {
2113 NamedLocal { kInvalidLocalName, minLocEquiv },
2114 op.subop2,
2115 op.subop3,
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);
2130 startBase(
2131 env,
2132 Base {
2133 std::move(ty),
2134 BaseLoc::Local,
2135 TBottom,
2136 op.nloc1.name != kInvalidLocalName
2137 ? env.ctx.func->locals[op.nloc1.name].name
2138 : nullptr,
2139 op.nloc1.id
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);
2148 effect_free(env);
2149 startBase(
2150 env,
2151 Base {
2152 std::move(ty),
2153 BaseLoc::Stack,
2154 TBottom,
2155 SString{},
2156 NoLocalId,
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);
2165 effect_free(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);
2174 if (!key) return;
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));
2181 } else {
2182 return miNewElem(env);
2184 }();
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 &&
2201 will_reduce(env) &&
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);
2206 if (!last) break;
2207 if (isMemberBaseOp(last->op)) {
2208 auto const base = *last;
2209 rewind(env, i + 1);
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
2216 // them.
2217 auto const reuseStack =
2218 [&] {
2219 switch (base.op) {
2220 case Op::BaseGC: return base.BaseGC.arg1 == 0;
2221 case Op::BaseC: return base.BaseC.arg1 == 0;
2222 default: return false;
2224 }();
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);
2230 assertx(v);
2231 reduce(env, gen_constant(*v), bc::BaseC { 0, op.subop1 });
2232 env.collect.mInstrState.extraPop = extraPop;
2233 return;
2235 if (!isMemberDimOp(last->op)) break;
2240 //////////////////////////////////////////////////////////////////////
2241 // Final operations
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);
2249 if (!key) return;
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);
2280 } else {
2281 // QueryMNewElem will always throw without doing any work.
2282 discard(env, nDiscard);
2283 push(env, TBottom);
2284 return Effects::AlwaysThrows;
2286 }();
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 &&
2292 nDiscard == 1 &&
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();
2305 return reduce(
2306 env,
2307 bc::FCallFuncD {
2308 FCallArgs(params),
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);
2325 return;
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);
2336 if (!last) break;
2337 if (isMemberBaseOp(last->op)) {
2338 auto const v = tv(topC(env));
2339 rewind(env, op);
2340 rewind(env, i + 1);
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);
2354 if (!key) return;
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));
2361 } else {
2362 return miFinalSetNewElem(env, op.arg1);
2364 }();
2365 handleEffects(env, effects, key->second);
2368 void in(ISS& env, const bc::SetRangeM& op) {
2369 popC(env);
2370 popC(env);
2371 popC(env);
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)));
2376 endBase(env);
2379 void in(ISS& env, const bc::IncDecM& op) {
2380 auto const key = key_type_or_fixup(env, op);
2381 if (!key) return;
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)
2390 } else {
2391 return miFinalIncDecNewElem(env, op.arg1);
2393 }();
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);
2399 if (!key) return;
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)
2408 } else {
2409 return miFinalSetOpNewElem(env, op.arg1);
2411 }();
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);
2417 if (!key) return;
2419 auto const effects = [&] {
2420 if (mcodeIsProp(op.mkey.mcode)) {
2421 return miFinalUnsetProp(env, op.arg1, key->first);
2422 } else {
2423 assertx(mcodeIsElem(op.mkey.mcode));
2424 return miFinalUnsetElem(env, op.arg1, key->first);
2426 }();
2427 handleEffects(env, effects, key->second);
2432 //////////////////////////////////////////////////////////////////////