Better type resolution
[hiphop-php.git] / hphp / hhbbc / interp-internal.h
blob1b5c439528ac196184bcb6ec7de7deccbfcb15e8
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 #ifndef incl_HPHP_INTERP_INTERNAL_H_
17 #define incl_HPHP_INTERP_INTERNAL_H_
19 #include <algorithm>
21 #include <folly/Optional.h>
23 #include "hphp/runtime/base/type-string.h"
25 #include "hphp/hhbbc/interp-state.h"
26 #include "hphp/hhbbc/interp.h"
27 #include "hphp/hhbbc/representation.h"
28 #include "hphp/hhbbc/type-system.h"
29 #include "hphp/hhbbc/func-util.h"
31 namespace HPHP { namespace HHBBC {
33 //////////////////////////////////////////////////////////////////////
35 TRACE_SET_MOD(hhbbc);
37 const StaticString s_assert("assert");
38 const StaticString s_set_frame_metadata("HH\\set_frame_metadata");
40 //////////////////////////////////////////////////////////////////////
43 * Interpreter Step State.
45 * This struct gives interpreter functions access to shared state. It's not in
46 * interp-state.h because it's part of the internal implementation of
47 * interpreter routines. The publicized state as results of interpretation are
48 * in that header and interp.h.
50 struct ISS {
51 explicit ISS(Interp& bag,
52 StepFlags& flags,
53 PropagateFn propagate)
54 : index(bag.index)
55 , ctx(bag.ctx)
56 , collect(bag.collect)
57 , blk(*bag.blk)
58 , state(bag.state)
59 , flags(flags)
60 , propagate(propagate)
63 const Index& index;
64 const Context ctx;
65 CollectedInfo& collect;
66 const php::Block& blk;
67 State& state;
68 StepFlags& flags;
69 PropagateFn propagate;
72 void impl_vec(ISS& env, bool reduce, std::vector<Bytecode>&& bcs);
74 //////////////////////////////////////////////////////////////////////
76 namespace interp_step {
79 * An interp_step::in(ISS&, const bc::op&) function exists for every
80 * bytecode. Most are defined in interp.cpp, but some (like FCallBuiltin and
81 * member instructions) are defined elsewhere.
83 #define O(opcode, ...) void in(ISS&, const bc::opcode&);
84 OPCODES
85 #undef O
89 namespace {
91 #ifdef __clang__
92 #pragma clang diagnostic push
93 #pragma clang diagnostic ignored "-Wunused-function"
94 #endif
97 * impl(...)
99 * Utility for chaining one bytecode implementation to a series of a few
100 * others. Use reduce() if you also want to enable strength reduction
101 * (i.e. the bytecode can be replaced by some other bytecode as an
102 * optimization).
104 * The chained-to bytecodes should not take branches. For impl, the
105 * canConstProp flag will only be set if it was set for all the
106 * bytecodes.
109 template<class... Ts>
110 void impl(ISS& env, Ts&&... ts) {
111 impl_vec(env, false, { std::forward<Ts>(ts)... });
115 * Reduce means that (given some situation in the execution state),
116 * a given bytecode could be replaced by some other bytecode
117 * sequence. Ensure that if you call reduce(), it is before any
118 * state-affecting operations (like popC()).
120 * If env.collect.propagate_constants is set, the reduced bytecodes
121 * will have been constant-propagated, and the canConstProp flag will
122 * be clear; otherwise canConstProp will be set as for impl.
124 void reduce(ISS& env, std::vector<Bytecode>&& bcs) {
125 impl_vec(env, true, std::move(bcs));
128 template<class... Bytecodes>
129 void reduce(ISS& env, Bytecodes&&... hhbc) {
130 reduce(env, { std::forward<Bytecodes>(hhbc)... });
133 void nothrow(ISS& env) {
134 FTRACE(2, " nothrow\n");
135 env.flags.wasPEI = false;
138 void unreachable(ISS& env) { env.state.unreachable = true; }
139 void constprop(ISS& env) { env.flags.canConstProp = true; }
141 void jmp_nofallthrough(ISS& env) {
142 env.flags.jmpFlag = StepFlags::JmpFlags::Taken;
144 void jmp_nevertaken(ISS& env) {
145 env.flags.jmpFlag = StepFlags::JmpFlags::Fallthrough;
148 void readUnknownLocals(ISS& env) { env.flags.mayReadLocalSet.set(); }
149 void readAllLocals(ISS& env) { env.flags.mayReadLocalSet.set(); }
151 void modifyLocalStatic(ISS& env, LocalId id, const Type& t) {
152 auto modifyOne = [&] (LocalId lid) {
153 if (is_volatile_local(env.ctx.func, lid)) return;
154 if (env.state.localStaticBindings.size() <= lid) return;
155 if (env.state.localStaticBindings[lid] == LocalStaticBinding::None) return;
156 if (t.subtypeOf(TUninit) && !t.subtypeOf(TBottom)) {
157 // Uninit means we are unbinding.
158 env.state.localStaticBindings[lid] = id == NoLocalId ?
159 LocalStaticBinding::None : LocalStaticBinding::Maybe;
160 return;
162 if (lid >= env.collect.localStaticTypes.size()) {
163 env.collect.localStaticTypes.resize(lid + 1, TBottom);
165 env.collect.localStaticTypes[lid] = t.subtypeOf(TCell) ?
166 union_of(std::move(env.collect.localStaticTypes[lid]), t) :
167 TGen;
169 if (id != NoLocalId) {
170 return modifyOne(id);
172 for (LocalId i = 0; i < env.state.localStaticBindings.size(); i++) {
173 modifyOne(i);
177 void unbindLocalStatic(ISS& env, LocalId id) {
178 modifyLocalStatic(env, id, TUninit);
181 void bindLocalStatic(ISS& env, LocalId id, const Type& t) {
182 if (is_volatile_local(env.ctx.func, id)) return;
183 if (env.state.localStaticBindings.size() <= id) {
184 env.state.localStaticBindings.resize(id + 1);
186 env.state.localStaticBindings[id] = LocalStaticBinding::Bound;
187 modifyLocalStatic(env, id, t);
190 void killLocals(ISS& env) {
191 FTRACE(2, " killLocals\n");
192 readUnknownLocals(env);
193 modifyLocalStatic(env, NoLocalId, TGen);
194 for (auto& l : env.state.locals) l = TGen;
195 for (auto& e : env.state.stack) e.equivLocal = NoLocalId;
196 env.state.equivLocals.clear();
199 void doRet(ISS& env, Type t) {
200 readAllLocals(env);
201 assert(env.state.stack.empty());
202 env.flags.returned = t;
205 void mayUseVV(ISS& env) {
206 env.collect.mayUseVV = true;
209 void specialFunctionEffects(ISS& env, const res::Func& func) {
210 if (func.name()->isame(s_set_frame_metadata.get())) {
212 * HH\set_frame_metadata can write to the caller's frame, but does not
213 * require a VV.
215 readUnknownLocals(env);
216 killLocals(env);
217 return;
220 if (func.name()->isame(s_assert.get())) {
222 * Assert is somewhat special. In the most general case, it can read and
223 * write to the caller's frame (and is marked as such). The first parameter,
224 * if a string, will be evaled and can have arbitrary effects. Luckily this
225 * is forbidden in RepoAuthoritative mode, so we can ignore that here. If
226 * the assert fails, it may execute an arbitrary pre-registered callback
227 * which still might try to write to the assert caller's frame. This can't
228 * happen if calling such frame accessing functions dynamically is
229 * forbidden.
231 if (options.DisallowDynamicVarEnvFuncs) return;
235 * Skip-frame functions won't write or read to the caller's frame, but they
236 * might dynamically call a function which can. So, skip-frame functions kill
237 * our locals unless they can't call such functions.
239 if (func.mightWriteCallerFrame() ||
240 (!options.DisallowDynamicVarEnvFuncs && func.mightBeSkipFrame())) {
241 readUnknownLocals(env);
242 killLocals(env);
243 mayUseVV(env);
244 return;
247 if (func.mightReadCallerFrame()) {
248 readUnknownLocals(env);
249 mayUseVV(env);
250 return;
254 void specialFunctionEffects(ISS& env, ActRec ar) {
255 switch (ar.kind) {
256 case FPIKind::Unknown:
257 // fallthrough
258 case FPIKind::Func:
259 if (!ar.func) {
260 if (!options.DisallowDynamicVarEnvFuncs) {
261 readUnknownLocals(env);
262 killLocals(env);
263 mayUseVV(env);
265 return;
267 case FPIKind::Builtin:
268 specialFunctionEffects(env, *ar.func);
269 if (ar.fallbackFunc) specialFunctionEffects(env, *ar.fallbackFunc);
270 break;
271 case FPIKind::Ctor:
272 case FPIKind::ObjMeth:
273 case FPIKind::ClsMeth:
274 case FPIKind::ObjInvoke:
275 case FPIKind::CallableArr:
277 * Methods cannot read or write to the caller's frame, but they can be
278 * skip-frame (if they're a builtin). So, its possible they'll dynamically
279 * call a function which reads or writes to the caller's frame. If we don't
280 * forbid this, we have to be pessimistic. Imagine something like
281 * Vector::map calling assert.
283 if (!options.DisallowDynamicVarEnvFuncs) {
284 readUnknownLocals(env);
285 killLocals(env);
286 mayUseVV(env);
288 break;
292 //////////////////////////////////////////////////////////////////////
293 // eval stack
295 Type popT(ISS& env) {
296 assert(!env.state.stack.empty());
297 auto const ret = std::move(env.state.stack.back().type);
298 FTRACE(2, " pop: {}\n", show(ret));
299 assert(ret.subtypeOf(TGen));
300 env.state.stack.pop_back();
301 return ret;
304 Type popC(ISS& env) {
305 auto const v = popT(env);
306 assert(v.subtypeOf(TInitCell));
307 return v;
310 Type popV(ISS& env) {
311 auto const v = popT(env);
312 assert(v.subtypeOf(TRef));
313 return v;
316 Type popU(ISS& env) {
317 auto const v = popT(env);
318 assert(v.subtypeOf(TUninit));
319 return v;
322 Type popCU(ISS& env) {
323 auto const v = popT(env);
324 assert(v.subtypeOf(TCell));
325 return v;
328 Type popR(ISS& env) { return popT(env); }
329 Type popF(ISS& env) { return popT(env); }
330 Type popCV(ISS& env) { return popT(env); }
332 void discard(ISS& env, int n) {
333 for (auto i = 0; i < n; ++i) {
334 popT(env);
338 Type& topT(ISS& env, uint32_t idx = 0) {
339 assert(idx < env.state.stack.size());
340 return env.state.stack[env.state.stack.size() - idx - 1].type;
343 Type& topC(ISS& env, uint32_t i = 0) {
344 assert(topT(env, i).subtypeOf(TInitCell));
345 return topT(env, i);
348 Type& topR(ISS& env, uint32_t i = 0) { return topT(env, i); }
350 Type& topV(ISS& env, uint32_t i = 0) {
351 assert(topT(env, i).subtypeOf(TRef));
352 return topT(env, i);
355 void push(ISS& env, Type t, LocalId l = NoLocalId) {
356 FTRACE(2, " push: {}\n", show(t));
357 always_assert(l == NoLocalId || !is_volatile_local(env.ctx.func, l));
358 env.state.stack.push_back(StackElem {std::move(t), l});
361 //////////////////////////////////////////////////////////////////////
362 // fpi
364 void fpiPush(ISS& env, ActRec ar) {
365 FTRACE(2, " fpi+: {}\n", show(ar));
366 env.state.fpiStack.push_back(ar);
369 ActRec fpiPop(ISS& env) {
370 assert(!env.state.fpiStack.empty());
371 auto const ret = env.state.fpiStack.back();
372 FTRACE(2, " fpi-: {}\n", show(ret));
373 env.state.fpiStack.pop_back();
374 return ret;
377 ActRec fpiTop(ISS& env) {
378 assert(!env.state.fpiStack.empty());
379 return env.state.fpiStack.back();
382 PrepKind prepKind(ISS& env, uint32_t paramId) {
383 auto ar = fpiTop(env);
384 if (ar.func && !ar.fallbackFunc) {
385 auto ret = env.index.lookup_param_prep(env.ctx, *ar.func, paramId);
386 assert(ar.kind != FPIKind::Builtin || ret != PrepKind::Unknown);
387 return ret;
389 assert(ar.kind != FPIKind::Builtin);
390 return PrepKind::Unknown;
393 //////////////////////////////////////////////////////////////////////
394 // locals
396 void useLocalStatic(ISS& env, LocalId l) {
397 assert(env.collect.localStaticTypes.size() > l);
398 if (!env.flags.usedLocalStatics) {
399 env.flags.usedLocalStatics = std::make_shared<std::map<LocalId,Type>>();
401 // Ignore the return value, since we only want the first type used,
402 // as that will be the narrowest.
403 env.flags.usedLocalStatics->emplace(l, env.collect.localStaticTypes[l]);
406 void mayReadLocal(ISS& env, uint32_t id) {
407 if (id < env.flags.mayReadLocalSet.size()) {
408 env.flags.mayReadLocalSet.set(id);
412 // Find a local which is equivalent to the given local
413 LocalId findLocEquiv(ISS& env, LocalId l) {
414 if (l >= env.state.equivLocals.size()) return NoLocalId;
415 assert(env.state.equivLocals[l] == NoLocalId ||
416 !is_volatile_local(env.ctx.func, l));
417 return env.state.equivLocals[l];
420 // Determine whether two locals are equivalent
421 bool locsAreEquiv(ISS& env, LocalId l1, LocalId l2) {
422 if (l1 >= env.state.equivLocals.size() ||
423 l2 >= env.state.equivLocals.size() ||
424 env.state.equivLocals[l1] == NoLocalId ||
425 env.state.equivLocals[l2] == NoLocalId) {
426 return false;
429 auto l = l1;
430 while ((l = env.state.equivLocals[l]) != l1) {
431 if (l == l2) return true;
433 return false;
436 void killLocEquiv(State& state, LocalId l) {
437 if (l >= state.equivLocals.size()) return;
438 if (state.equivLocals[l] == NoLocalId) return;
439 auto loc = l;
440 do {
441 loc = state.equivLocals[loc];
442 } while (state.equivLocals[loc] != l);
443 assert(loc != l);
444 if (state.equivLocals[l] == loc) {
445 state.equivLocals[loc] = NoLocalId;
446 } else {
447 state.equivLocals[loc] = state.equivLocals[l];
449 state.equivLocals[l] = NoLocalId;
452 void killLocEquiv(ISS& env, LocalId l) {
453 killLocEquiv(env.state, l);
456 void killAllLocEquiv(ISS& env) {
457 env.state.equivLocals.clear();
460 // Add from to to's equivalency set.
461 void addLocEquiv(ISS& env,
462 LocalId from,
463 LocalId to) {
464 always_assert(!is_volatile_local(env.ctx.func, from));
465 always_assert(!is_volatile_local(env.ctx.func, to));
466 always_assert(from != to && findLocEquiv(env, from) == NoLocalId);
468 auto m = std::max(to, from);
469 if (env.state.equivLocals.size() <= m) {
470 env.state.equivLocals.resize(m + 1, NoLocalId);
473 if (env.state.equivLocals[to] == NoLocalId) {
474 env.state.equivLocals[from] = to;
475 env.state.equivLocals[to] = from;
476 } else {
477 env.state.equivLocals[from] = env.state.equivLocals[to];
478 env.state.equivLocals[to] = from;
482 // Obtain a local which is equivalent to the given stack value
483 LocalId topStkEquiv(ISS& env, uint32_t idx = 0) {
484 assert(idx < env.state.stack.size());
485 return env.state.stack[env.state.stack.size() - idx - 1].equivLocal;
488 // Kill all equivalencies involving the given local to stack values
489 void killStkEquiv(ISS& env, LocalId l) {
490 for (auto& e : env.state.stack) {
491 if (e.equivLocal == l) e.equivLocal = NoLocalId;
495 void killAllStkEquiv(ISS& env) {
496 for (auto& e : env.state.stack) e.equivLocal = NoLocalId;
499 Type locRaw(ISS& env, LocalId l) {
500 mayReadLocal(env, l);
501 auto ret = env.state.locals[l];
502 if (is_volatile_local(env.ctx.func, l)) {
503 always_assert_flog(ret == TGen, "volatile local was not TGen");
505 return ret;
508 void setLocRaw(ISS& env, LocalId l, Type t) {
509 mayReadLocal(env, l);
510 killLocEquiv(env, l);
511 killStkEquiv(env, l);
512 if (is_volatile_local(env.ctx.func, l)) {
513 auto current = env.state.locals[l];
514 always_assert_flog(current == TGen, "volatile local was not TGen");
515 return;
517 modifyLocalStatic(env, l, t);
518 env.state.locals[l] = std::move(t);
521 folly::Optional<Type> staticLocType(ISS& env, LocalId l, const Type& super) {
522 mayReadLocal(env, l);
523 if (env.state.localStaticBindings.size() > l &&
524 env.state.localStaticBindings[l] == LocalStaticBinding::Bound) {
525 assert(env.collect.localStaticTypes.size() > l);
526 auto t = env.collect.localStaticTypes[l];
527 if (t.subtypeOf(super)) {
528 useLocalStatic(env, l);
529 if (t.subtypeOf(TBottom)) t = TInitNull;
530 return std::move(t);
533 return folly::none;
536 // Read a local type in the sense of CGetL. (TUninits turn into
537 // TInitNull, and potentially reffy types return the "inner" type,
538 // which is always a subtype of InitCell.)
539 Type locAsCell(ISS& env, LocalId l) {
540 if (auto s = staticLocType(env, l, TInitCell)) {
541 return std::move(*s);
543 auto t = locRaw(env, l);
544 return !t.subtypeOf(TCell) ? TInitCell :
545 t.subtypeOf(TUninit) ? TInitNull :
546 remove_uninit(std::move(t));
549 // Read a local type, dereferencing refs, but without converting
550 // potential TUninits to TInitNull.
551 Type derefLoc(ISS& env, LocalId l) {
552 if (auto s = staticLocType(env, l, TCell)) {
553 return std::move(*s);
555 auto v = locRaw(env, l);
556 if (v.subtypeOf(TCell)) return v;
557 return v.couldBe(TUninit) ? TCell : TInitCell;
560 bool locCouldBeUninit(ISS& env, LocalId l) {
561 return locRaw(env, l).couldBe(TUninit);
564 bool locCouldBeRef(ISS& env, LocalId l) {
565 return locRaw(env, l).couldBe(TRef);
569 * Update the known type of a local, based on assertions
570 * (VerifyParamType; or IsType/JmpCC), rather than an actual
571 * modification to the local.
573 void refineLoc(ISS& env, LocalId l, Type t) {
574 auto v = locRaw(env, l);
575 if (is_volatile_local(env.ctx.func, l)) {
576 always_assert_flog(v == TGen, "volatile local was not TGen");
577 return;
579 if (v.subtypeOf(TCell)) env.state.locals[l] = std::move(t);
583 * Set a local type in the sense of tvSet. If the local is boxed or
584 * not known to be not boxed, we can't change the type. May be used
585 * to set locals to types that include Uninit.
587 void setLoc(ISS& env, LocalId l, Type t) {
588 killLocEquiv(env, l);
589 killStkEquiv(env, l);
590 modifyLocalStatic(env, l, t);
591 refineLoc(env, l, std::move(t));
594 LocalId findLocal(ISS& env, SString name) {
595 for (auto& l : env.ctx.func->locals) {
596 if (l.name->same(name)) {
597 mayReadLocal(env, l.id);
598 return l.id;
601 return NoLocalId;
604 // Force non-ref locals to TCell. Used when something modifies an
605 // unknown local's value, without changing reffiness.
606 void loseNonRefLocalTypes(ISS& env) {
607 readUnknownLocals(env);
608 FTRACE(2, " loseNonRefLocalTypes\n");
609 for (auto& l : env.state.locals) {
610 if (l.subtypeOf(TCell)) l = TCell;
612 killAllLocEquiv(env);
613 killAllStkEquiv(env);
614 modifyLocalStatic(env, NoLocalId, TCell);
617 void boxUnknownLocal(ISS& env) {
618 readUnknownLocals(env);
619 FTRACE(2, " boxUnknownLocal\n");
620 for (auto& l : env.state.locals) {
621 if (!l.subtypeOf(TRef)) l = TGen;
623 killAllLocEquiv(env);
624 killAllStkEquiv(env);
625 // Don't update the local statics here; this is called both for
626 // boxing and binding, and the effects on local statics are
627 // different.
630 void unsetUnknownLocal(ISS& env) {
631 readUnknownLocals(env);
632 FTRACE(2, " unsetUnknownLocal\n");
633 for (auto& l : env.state.locals) l |= TUninit;
634 killAllLocEquiv(env);
635 killAllStkEquiv(env);
636 unbindLocalStatic(env, NoLocalId);
639 //////////////////////////////////////////////////////////////////////
640 // class-ref slots
642 // Read the specified class-ref slot without discarding the stored value.
643 const Type& peekClsRefSlot(ISS& env, ClsRefSlotId slot) {
644 assert(slot >= 0);
645 always_assert_flog(env.state.clsRefSlots[slot].subtypeOf(TCls),
646 "class-ref slot contained non-TCls");
647 return env.state.clsRefSlots[slot];
650 // Read the specified class-ref slot and discard the stored value.
651 Type takeClsRefSlot(ISS& env, ClsRefSlotId slot) {
652 assert(slot >= 0);
653 auto ret = std::move(env.state.clsRefSlots[slot]);
654 FTRACE(2, " read class-ref: {} -> {}\n", slot, show(ret));
655 always_assert_flog(ret.subtypeOf(TCls), "class-ref slot contained non-TCls");
656 env.state.clsRefSlots[slot] = TCls;
657 return ret;
660 void putClsRefSlot(ISS& env, ClsRefSlotId slot, Type ty) {
661 assert(slot >= 0);
662 always_assert_flog(ty.subtypeOf(TCls),
663 "attempted to set class-ref slot to non-TCls");
664 FTRACE(2, " write class-ref: {} -> {}\n", slot, show(ty));
665 env.state.clsRefSlots[slot] = std::move(ty);
668 //////////////////////////////////////////////////////////////////////
669 // iterators
671 void setIter(ISS& env, IterId iter, Iter iterState) {
672 env.state.iters[iter] = std::move(iterState);
674 void freeIter(ISS& env, IterId iter) {
675 env.state.iters[iter] = UnknownIter {};
678 //////////////////////////////////////////////////////////////////////
679 // $this
681 void setThisAvailable(ISS& env) {
682 FTRACE(2, " setThisAvailable\n");
683 env.state.thisAvailable = true;
686 bool thisAvailable(ISS& env) { return env.state.thisAvailable; }
688 // Returns the type $this would have if it's not null. Generally
689 // you have to check thisIsAvailable() before assuming it can't be
690 // null.
691 folly::Optional<Type> thisType(ISS& env) {
692 if (!env.ctx.cls) return folly::none;
693 return subObj(env.index.resolve_class(env.ctx.cls));
696 folly::Optional<Type> selfCls(ISS& env) {
697 if (!env.ctx.cls) return folly::none;
698 return subCls(env.index.resolve_class(env.ctx.cls));
701 folly::Optional<Type> selfClsExact(ISS& env) {
702 if (!env.ctx.cls) return folly::none;
703 return clsExact(env.index.resolve_class(env.ctx.cls));
706 folly::Optional<Type> parentClsExact(ISS& env) {
707 if (!env.ctx.cls || !env.ctx.cls->parentName) return folly::none;
708 auto parent = env.index.resolve_class(env.ctx.cls).parent();
709 if (!parent) {
710 parent = env.index.resolve_class(env.ctx, env.ctx.cls->parentName);
713 if (parent) {
714 return clsExact(*parent);
716 return folly::none;
719 //////////////////////////////////////////////////////////////////////
720 // properties on $this
723 * Note: we are only tracking control-flow insensitive types for
724 * object properties, because it can be pretty rough to try to track
725 * all cases that could re-enter the VM, run arbitrary code, and
726 * potentially change the type of a property.
728 * Because of this, the various "setter" functions for thisProps
729 * here actually just union the new type into what we already had.
732 Type* thisPropRaw(ISS& env, SString name) {
733 auto& privateProperties = env.collect.props.privateProperties();
734 auto const it = privateProperties.find(name);
735 if (it != end(privateProperties)) {
736 return &it->second;
738 return nullptr;
741 bool isTrackedThisProp(ISS& env, SString name) {
742 return thisPropRaw(env, name);
745 void killThisProps(ISS& env) {
746 FTRACE(2, " killThisProps\n");
747 for (auto& kv : env.collect.props.privateProperties()) {
748 kv.second = TGen;
753 * This function returns a type that includes all the possible types
754 * that could result from reading a property $this->name.
756 * Note that this may include types that the property itself cannot
757 * actually contain, due to the effects of a possible __get function.
759 folly::Optional<Type> thisPropAsCell(ISS& env, SString name) {
760 auto const t = thisPropRaw(env, name);
761 if (!t) return folly::none;
762 if (t->couldBe(TUninit)) {
763 auto const rthis = thisType(env);
764 if (!rthis || dobj_of(*rthis).cls.couldHaveMagicGet()) {
765 return TInitCell;
768 return !t->subtypeOf(TCell) ? TInitCell :
769 t->subtypeOf(TUninit) ? TInitNull :
770 remove_uninit(*t);
774 * Merge a type into the track property types on $this, in the sense
775 * of tvSet (i.e. setting the inner type on possible refs).
777 * Note that all types we see that could go into an object property
778 * have to loosen_statics and loosen_values. This is because the
779 * object could be serialized and then deserialized, losing the
780 * static-ness of a string or array member, and we don't guarantee
781 * deserialization would preserve a constant value object property
782 * type.
784 void mergeThisProp(ISS& env, SString name, Type type) {
785 auto const t = thisPropRaw(env, name);
786 if (!t) return;
787 *t |= loosen_statics(loosen_values(type));
791 * Merge something into each this prop. Usually MapFn will be a
792 * predicate that returns TBottom when some condition doesn't hold.
794 * The types given to the map function are the raw tracked types
795 * (i.e. could be TRef or TUninit).
797 template<class MapFn>
798 void mergeEachThisPropRaw(ISS& env, MapFn fn) {
799 for (auto& kv : env.collect.props.privateProperties()) {
800 mergeThisProp(env, kv.first, fn(kv.second));
804 void unsetThisProp(ISS& env, SString name) {
805 mergeThisProp(env, name, TUninit);
808 void unsetUnknownThisProp(ISS& env) {
809 for (auto& kv : env.collect.props.privateProperties()) {
810 mergeThisProp(env, kv.first, TUninit);
814 void boxThisProp(ISS& env, SString name) {
815 auto const t = thisPropRaw(env, name);
816 if (!t) return;
817 *t |= TRef;
821 * Forces non-ref property types up to TCell. This is used when an
822 * operation affects an unknown property on $this, but can't change
823 * its reffiness. This could only do TInitCell, but we're just
824 * going to gradually get rid of the callsites of this.
826 void loseNonRefThisPropTypes(ISS& env) {
827 FTRACE(2, " loseNonRefThisPropTypes\n");
828 for (auto& kv : env.collect.props.privateProperties()) {
829 if (kv.second.subtypeOf(TCell)) kv.second = TCell;
833 //////////////////////////////////////////////////////////////////////
834 // properties on self::
836 // Similar to $this properties above, we only track control-flow
837 // insensitive types for these.
839 Type* selfPropRaw(ISS& env, SString name) {
840 auto& privateStatics = env.collect.props.privateStatics();
841 auto it = privateStatics.find(name);
842 if (it != end(privateStatics)) {
843 return &it->second;
845 return nullptr;
848 void killSelfProps(ISS& env) {
849 FTRACE(2, " killSelfProps\n");
850 for (auto& kv : env.collect.props.privateStatics()) {
851 kv.second = TGen;
855 void killSelfProp(ISS& env, SString name) {
856 FTRACE(2, " killSelfProp {}\n", name->data());
857 if (auto t = selfPropRaw(env, name)) *t = TGen;
860 // TODO(#3684136): self::$foo can't actually ever be uninit. Right
861 // now uninits may find their way into here though.
862 folly::Optional<Type> selfPropAsCell(ISS& env, SString name) {
863 auto const t = selfPropRaw(env, name);
864 if (!t) return folly::none;
865 return !t->subtypeOf(TCell) ? TInitCell :
866 t->subtypeOf(TUninit) ? TInitNull :
867 remove_uninit(*t);
871 * Merges a type into tracked static properties on self, in the
872 * sense of tvSet (i.e. setting the inner type on possible refs).
874 void mergeSelfProp(ISS& env, SString name, Type type) {
875 auto const t = selfPropRaw(env, name);
876 if (!t) return;
877 *t |= type;
881 * Similar to mergeEachThisPropRaw, but for self props.
883 template<class MapFn>
884 void mergeEachSelfPropRaw(ISS& env, MapFn fn) {
885 for (auto& kv : env.collect.props.privateStatics()) {
886 mergeSelfProp(env, kv.first, fn(kv.second));
890 void boxSelfProp(ISS& env, SString name) {
891 mergeSelfProp(env, name, TRef);
895 * Forces non-ref static properties up to TCell. This is used when
896 * an operation affects an unknown static property on self::, but
897 * can't change its reffiness.
899 * This could only do TInitCell because static properties can never
900 * be unset. We're just going to get rid of the callers of this
901 * function over a few more changes, though.
903 void loseNonRefSelfPropTypes(ISS& env) {
904 FTRACE(2, " loseNonRefSelfPropTypes\n");
905 for (auto& kv : env.collect.props.privateStatics()) {
906 if (kv.second.subtypeOf(TInitCell)) kv.second = TCell;
910 #ifdef __clang__
911 #pragma clang diagnostic pop
912 #endif
915 //////////////////////////////////////////////////////////////////////
919 #endif