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