2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
16 #ifndef incl_HPHP_INTERP_INTERNAL_H_
17 #define incl_HPHP_INTERP_INTERNAL_H_
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 //////////////////////////////////////////////////////////////////////
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.
51 explicit ISS(Interp
& bag
,
53 PropagateFn propagate
)
56 , collect(bag
.collect
)
60 , propagate(propagate
)
65 CollectedInfo
& collect
;
66 const php::Block
& blk
;
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&);
92 #pragma clang diagnostic push
93 #pragma clang diagnostic ignored "-Wunused-function"
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
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
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
;
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
) :
169 if (id
!= NoLocalId
) {
170 return modifyOne(id
);
172 for (LocalId i
= 0; i
< env
.state
.localStaticBindings
.size(); 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
) {
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
215 readUnknownLocals(env
);
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
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
);
247 if (func
.mightReadCallerFrame()) {
248 readUnknownLocals(env
);
254 void specialFunctionEffects(ISS
& env
, ActRec ar
) {
256 case FPIKind::Unknown
:
260 if (!options
.DisallowDynamicVarEnvFuncs
) {
261 readUnknownLocals(env
);
267 case FPIKind::Builtin
:
268 specialFunctionEffects(env
, *ar
.func
);
269 if (ar
.fallbackFunc
) specialFunctionEffects(env
, *ar
.fallbackFunc
);
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
);
292 //////////////////////////////////////////////////////////////////////
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();
304 Type
popC(ISS
& env
) {
305 auto const v
= popT(env
);
306 assert(v
.subtypeOf(TInitCell
));
310 Type
popV(ISS
& env
) {
311 auto const v
= popT(env
);
312 assert(v
.subtypeOf(TRef
));
316 Type
popU(ISS
& env
) {
317 auto const v
= popT(env
);
318 assert(v
.subtypeOf(TUninit
));
322 Type
popCU(ISS
& env
) {
323 auto const v
= popT(env
);
324 assert(v
.subtypeOf(TCell
));
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
) {
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
));
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
));
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 //////////////////////////////////////////////////////////////////////
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();
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
);
389 assert(ar
.kind
!= FPIKind::Builtin
);
390 return PrepKind::Unknown
;
393 //////////////////////////////////////////////////////////////////////
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
) {
430 while ((l
= env
.state
.equivLocals
[l
]) != l1
) {
431 if (l
== l2
) return true;
436 void killLocEquiv(State
& state
, LocalId l
) {
437 if (l
>= state
.equivLocals
.size()) return;
438 if (state
.equivLocals
[l
] == NoLocalId
) return;
441 loc
= state
.equivLocals
[loc
];
442 } while (state
.equivLocals
[loc
] != l
);
444 if (state
.equivLocals
[l
] == loc
) {
445 state
.equivLocals
[loc
] = NoLocalId
;
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
,
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
;
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");
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");
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
;
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");
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
);
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
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 //////////////////////////////////////////////////////////////////////
642 // Read the specified class-ref slot without discarding the stored value.
643 const Type
& peekClsRefSlot(ISS
& env
, ClsRefSlotId slot
) {
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
) {
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
;
660 void putClsRefSlot(ISS
& env
, ClsRefSlotId slot
, Type ty
) {
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 //////////////////////////////////////////////////////////////////////
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 //////////////////////////////////////////////////////////////////////
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
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();
710 parent
= env
.index
.resolve_class(env
.ctx
, env
.ctx
.cls
->parentName
);
714 return clsExact(*parent
);
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
)) {
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()) {
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()) {
768 return !t
->subtypeOf(TCell
) ? TInitCell
:
769 t
->subtypeOf(TUninit
) ? TInitNull
:
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
784 void mergeThisProp(ISS
& env
, SString name
, Type type
) {
785 auto const t
= thisPropRaw(env
, name
);
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
);
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
)) {
848 void killSelfProps(ISS
& env
) {
849 FTRACE(2, " killSelfProps\n");
850 for (auto& kv
: env
.collect
.props
.privateStatics()) {
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
:
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
);
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
;
911 #pragma clang diagnostic pop
915 //////////////////////////////////////////////////////////////////////