2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
16 #include "hphp/hhbbc/interp.h"
23 #include <folly/Optional.h>
25 #include "hphp/util/trace.h"
26 #include "hphp/runtime/base/array-init.h"
27 #include "hphp/runtime/base/collections.h"
28 #include "hphp/runtime/base/static-string-table.h"
29 #include "hphp/runtime/base/tv-arith.h"
30 #include "hphp/runtime/base/tv-comparisons.h"
31 #include "hphp/runtime/base/tv-conversions.h"
32 #include "hphp/runtime/vm/runtime.h"
33 #include "hphp/runtime/vm/unit-util.h"
35 #include "hphp/runtime/ext/hh/ext_hh.h"
37 #include "hphp/hhbbc/analyze.h"
38 #include "hphp/hhbbc/bc.h"
39 #include "hphp/hhbbc/cfg.h"
40 #include "hphp/hhbbc/class-util.h"
41 #include "hphp/hhbbc/eval-cell.h"
42 #include "hphp/hhbbc/index.h"
43 #include "hphp/hhbbc/interp-state.h"
44 #include "hphp/hhbbc/optimize.h"
45 #include "hphp/hhbbc/representation.h"
46 #include "hphp/hhbbc/type-builtins.h"
47 #include "hphp/hhbbc/type-ops.h"
48 #include "hphp/hhbbc/type-system.h"
49 #include "hphp/hhbbc/unit-util.h"
51 #include "hphp/hhbbc/interp-internal.h"
53 namespace HPHP
{ namespace HHBBC
{
55 //////////////////////////////////////////////////////////////////////
59 const StaticString
s_Throwable("__SystemLib\\Throwable");
60 const StaticString
s_empty("");
61 const StaticString
s_construct("__construct");
62 const StaticString
s_86ctor("86ctor");
63 const StaticString
s_PHP_Incomplete_Class("__PHP_Incomplete_Class");
64 const StaticString
s_IMemoizeParam("HH\\IMemoizeParam");
65 const StaticString
s_getInstanceKey("getInstanceKey");
66 const StaticString
s_Closure("Closure");
67 const StaticString
s_byRefWarn("Only variables should be passed by reference");
68 const StaticString
s_byRefError("Only variables can be passed by reference");
69 const StaticString
s_trigger_error("trigger_error");
72 //////////////////////////////////////////////////////////////////////
74 void impl_vec(ISS
& env
, bool reduce
, std::vector
<Bytecode
>&& bcs
) {
75 std::vector
<Bytecode
> currentReduction
;
76 if (!options
.StrengthReduce
) reduce
= false;
78 for (auto it
= begin(bcs
); it
!= end(bcs
); ++it
) {
79 assert(env
.flags
.jmpFlag
== StepFlags::JmpFlags::Either
&&
80 "you can't use impl with branching opcodes before last position");
82 auto const wasPEI
= env
.flags
.wasPEI
;
83 auto const canConstProp
= env
.flags
.canConstProp
;
85 FTRACE(3, " (impl {}\n", show(env
.ctx
.func
, *it
));
86 env
.flags
.wasPEI
= true;
87 env
.flags
.canConstProp
= false;
88 env
.flags
.strengthReduced
= folly::none
;
89 default_dispatch(env
, *it
);
91 if (env
.flags
.strengthReduced
) {
92 if (instrFlags(env
.flags
.strengthReduced
->back().op
) & TF
) {
96 std::move(begin(*env
.flags
.strengthReduced
),
97 end(*env
.flags
.strengthReduced
),
98 std::back_inserter(currentReduction
));
101 if (instrFlags(it
->op
) & TF
) {
105 if (env
.flags
.canConstProp
&&
106 env
.collect
.propagate_constants
&&
107 env
.collect
.propagate_constants(*it
, env
.state
, currentReduction
)) {
108 env
.flags
.canConstProp
= false;
109 env
.flags
.wasPEI
= false;
111 currentReduction
.push_back(std::move(*it
));
116 // If any of the opcodes in the impl list said they could throw,
117 // then the whole thing could throw.
118 env
.flags
.wasPEI
= env
.flags
.wasPEI
|| wasPEI
;
119 env
.flags
.canConstProp
= env
.flags
.canConstProp
&& canConstProp
;
120 if (env
.state
.unreachable
) break;
124 env
.flags
.strengthReduced
= std::move(currentReduction
);
126 env
.flags
.strengthReduced
= folly::none
;
130 namespace interp_step
{
132 void in(ISS
& env
, const bc::Nop
&) { nothrow(env
); }
133 void in(ISS
& env
, const bc::DiscardClsRef
& op
) {
135 takeClsRefSlot(env
, op
.slot
);
137 void in(ISS
& env
, const bc::PopC
&) { nothrow(env
); popC(env
); }
138 void in(ISS
& env
, const bc::PopV
&) { nothrow(env
); popV(env
); }
139 void in(ISS
& env
, const bc::PopU
&) { nothrow(env
); popU(env
); }
140 void in(ISS
& env
, const bc::PopR
&) {
141 auto t
= topT(env
, 0);
142 if (t
.subtypeOf(TCell
)) {
143 return reduce(env
, bc::UnboxRNop
{}, bc::PopC
{});
149 void in(ISS
& env
, const bc::EntryNop
&) { nothrow(env
); }
151 void in(ISS
& env
, const bc::Dup
& op
) {
153 auto val
= popC(env
);
155 push(env
, std::move(val
));
158 void in(ISS
& env
, const bc::AssertRATL
& op
) {
159 mayReadLocal(env
, op
.loc1
);
163 void in(ISS
& env
, const bc::AssertRATStk
&) {
167 void in(ISS
& env
, const bc::BreakTraceHint
&) { nothrow(env
); }
169 void in(ISS
& env
, const bc::Box
&) {
175 void in(ISS
& env
, const bc::BoxR
&) {
177 if (topR(env
).subtypeOf(TRef
)) {
178 return reduce(env
, bc::BoxRNop
{});
184 void in(ISS
& env
, const bc::Unbox
&) {
187 push(env
, TInitCell
);
190 void in(ISS
& env
, const bc::UnboxR
&) {
191 auto const t
= topR(env
);
192 if (t
.subtypeOf(TInitCell
)) return reduce(env
, bc::UnboxRNop
{});
195 push(env
, TInitCell
);
198 void in(ISS
& env
, const bc::RGetCNop
&) { nothrow(env
); }
200 void in(ISS
& env
, const bc::CGetCUNop
&) {
202 auto const t
= popCU(env
);
203 push(env
, remove_uninit(t
));
206 void in(ISS
& env
, const bc::UGetCUNop
&) {
212 void in(ISS
& env
, const bc::UnboxRNop
&) {
216 if (!t
.subtypeOf(TInitCell
)) t
= TInitCell
;
217 push(env
, std::move(t
));
220 void in(ISS
& env
, const bc::BoxRNop
&) {
223 if (!t
.subtypeOf(TRef
)) t
= TRef
;
224 push(env
, std::move(t
));
227 void in(ISS
& env
, const bc::Null
&) { nothrow(env
); push(env
, TInitNull
); }
228 void in(ISS
& env
, const bc::NullUninit
&){ nothrow(env
); push(env
, TUninit
); }
229 void in(ISS
& env
, const bc::True
&) { nothrow(env
); push(env
, TTrue
); }
230 void in(ISS
& env
, const bc::False
&) { nothrow(env
); push(env
, TFalse
); }
232 void in(ISS
& env
, const bc::Int
& op
) {
234 push(env
, ival(op
.arg1
));
237 void in(ISS
& env
, const bc::Double
& op
) {
239 push(env
, dval(op
.dbl1
));
242 void in(ISS
& env
, const bc::String
& op
) {
244 push(env
, sval(op
.str1
));
247 void in(ISS
& env
, const bc::Array
& op
) {
248 assert(op
.arr1
->isPHPArray());
250 push(env
, aval(op
.arr1
));
253 void in(ISS
& env
, const bc::Vec
& op
) {
254 assert(op
.arr1
->isVecArray());
256 push(env
, vec_val(op
.arr1
));
259 void in(ISS
& env
, const bc::Dict
& op
) {
260 assert(op
.arr1
->isDict());
262 push(env
, dict_val(op
.arr1
));
265 void in(ISS
& env
, const bc::Keyset
& op
) {
266 assert(op
.arr1
->isKeyset());
268 push(env
, keyset_val(op
.arr1
));
271 void in(ISS
& env
, const bc::NewArray
& op
) {
272 push(env
, op
.arg1
== 0 ? aempty() : counted_aempty());
275 void in(ISS
& env
, const bc::NewDictArray
& op
) {
276 push(env
, op
.arg1
== 0 ? dict_empty() : counted_dict_empty());
279 void in(ISS
& env
, const bc::NewMixedArray
& op
) {
280 push(env
, op
.arg1
== 0 ? aempty() : counted_aempty());
283 void in(ISS
& env
, const bc::NewPackedArray
& op
) {
284 auto elems
= std::vector
<Type
>{};
285 elems
.reserve(op
.arg1
);
286 for (auto i
= uint32_t{0}; i
< op
.arg1
; ++i
) {
287 elems
.push_back(std::move(topC(env
, op
.arg1
- i
- 1)));
289 discard(env
, op
.arg1
);
290 push(env
, arr_packed(std::move(elems
)));
294 void in(ISS
& env
, const bc::NewStructArray
& op
) {
295 auto map
= MapElems
{};
296 for (auto it
= op
.keys
.end(); it
!= op
.keys
.begin(); ) {
297 map
.emplace_front(make_tv
<KindOfPersistentString
>(*--it
), popC(env
));
299 push(env
, arr_map(std::move(map
)));
303 void in(ISS
& env
, const bc::NewVecArray
& op
) {
304 auto elems
= std::vector
<Type
>{};
305 elems
.reserve(op
.arg1
);
306 for (auto i
= uint32_t{0}; i
< op
.arg1
; ++i
) {
307 elems
.push_back(std::move(topC(env
, op
.arg1
- i
- 1)));
309 discard(env
, op
.arg1
);
311 push(env
, vec(std::move(elems
)));
314 void in(ISS
& env
, const bc::NewKeysetArray
& op
) {
316 auto map
= MapElems
{};
319 for (auto i
= uint32_t{0}; i
< op
.arg1
; ++i
) {
322 auto const k
= disect_strict_key(t
);
323 if (auto const v
= k
.tv()) {
324 map
.emplace_front(*v
, k
.type
);
332 push(env
, keyset_map(std::move(map
)));
335 push(env
, keyset_n(ty
));
339 void in(ISS
& env
, const bc::NewLikeArrayL
& op
) {
340 locAsCell(env
, op
.loc1
);
341 push(env
, counted_aempty());
344 void in(ISS
& env
, const bc::AddElemC
& op
) {
345 auto const v
= popC(env
);
346 auto const k
= popC(env
);
348 auto const outTy
= [&] (Type ty
) -> folly::Optional
<Type
> {
349 if (ty
.subtypeOf(TArr
)) {
350 return array_set(std::move(ty
), k
, v
);
352 if (ty
.subtypeOf(TDict
)) {
353 return dict_set(std::move(ty
), k
, v
).first
;
359 return push(env
, union_of(TArr
, TDict
));
362 if (outTy
->subtypeOf(TBottom
)) {
365 if (env
.collect
.trackConstantArrays
) constprop(env
);
367 push(env
, std::move(*outTy
));
370 void in(ISS
& env
, const bc::AddElemV
& op
) {
371 popV(env
); popC(env
);
372 auto const ty
= popC(env
);
374 ty
.subtypeOf(TArr
) ? TArr
375 : ty
.subtypeOf(TDict
) ? TDict
376 : union_of(TArr
, TDict
);
380 void in(ISS
& env
, const bc::AddNewElemC
&) {
383 auto const outTy
= [&] (Type ty
) -> folly::Optional
<Type
> {
384 if (ty
.subtypeOf(TArr
)) {
385 return array_newelem(std::move(ty
), std::move(v
));
391 return push(env
, TInitCell
);
394 if (outTy
->subtypeOf(TBottom
)) {
397 if (env
.collect
.trackConstantArrays
) constprop(env
);
399 push(env
, std::move(*outTy
));
402 void in(ISS
& env
, const bc::AddNewElemV
&) {
408 void in(ISS
& env
, const bc::NewCol
& op
) {
409 auto const type
= static_cast<CollectionType
>(op
.arg1
);
410 auto const name
= collections::typeToString(type
);
411 push(env
, objExact(env
.index
.builtin_class(name
)));
414 void in(ISS
& env
, const bc::NewPair
& op
) {
415 popC(env
); popC(env
);
416 auto const name
= collections::typeToString(CollectionType::Pair
);
417 push(env
, objExact(env
.index
.builtin_class(name
)));
420 void in(ISS
& env
, const bc::ColFromArray
& op
) {
422 auto const type
= static_cast<CollectionType
>(op
.arg1
);
423 auto const name
= collections::typeToString(type
);
424 push(env
, objExact(env
.index
.builtin_class(name
)));
427 void doCns(ISS
& env
, SString str
) {
428 auto t
= env
.index
.lookup_constant(env
.ctx
, str
);
430 // There's no entry for this constant in the index. It must be
431 // the first iteration, so we'll add a dummy entry to make sure
432 // there /is/ something next time around.
434 val
.m_type
= kReadOnlyConstant
;
435 env
.collect
.cnsMap
.emplace(str
, val
);
437 // make sure we're re-analyzed
438 env
.collect
.readsUntrackedConstants
= true;
439 } else if (t
->strictSubtypeOf(TInitCell
)) {
443 push(env
, std::move(*t
));
446 void in(ISS
& env
, const bc::Cns
& op
) { doCns(env
, op
.str1
); }
447 void in(ISS
& env
, const bc::CnsE
& op
) { doCns(env
, op
.str1
); }
448 void in(ISS
& env
, const bc::CnsU
&) { push(env
, TInitCell
); }
450 void in(ISS
& env
, const bc::ClsCns
& op
) {
451 auto const& t1
= peekClsRefSlot(env
, op
.slot
);
452 if (is_specialized_cls(t1
)) {
453 auto const dcls
= dcls_of(t1
);
454 if (dcls
.type
== DCls::Exact
) {
455 return reduce(env
, bc::DiscardClsRef
{ op
.slot
},
456 bc::ClsCnsD
{ op
.str1
, dcls
.cls
.name() });
459 takeClsRefSlot(env
, op
.slot
);
460 push(env
, TInitCell
);
463 void in(ISS
& env
, const bc::ClsCnsD
& op
) {
464 if (auto const rcls
= env
.index
.resolve_class(env
.ctx
, op
.str2
)) {
465 auto t
= env
.index
.lookup_class_constant(env
.ctx
, *rcls
, op
.str1
);
466 if (options
.HardConstProp
) constprop(env
);
467 push(env
, std::move(t
));
470 push(env
, TInitCell
);
473 void in(ISS
& env
, const bc::File
&) { nothrow(env
); push(env
, TSStr
); }
474 void in(ISS
& env
, const bc::Dir
&) { nothrow(env
); push(env
, TSStr
); }
475 void in(ISS
& env
, const bc::Method
&) { nothrow(env
); push(env
, TSStr
); }
477 void in(ISS
& env
, const bc::ClsRefName
& op
) {
479 takeClsRefSlot(env
, op
.slot
);
483 void in(ISS
& env
, const bc::Concat
& op
) {
484 auto const t1
= popC(env
);
485 auto const t2
= popC(env
);
486 auto const v1
= tv(t1
);
487 auto const v2
= tv(t2
);
489 auto to_string_is_safe
= [] (const Cell
& cell
) {
491 isStringType(cell
.m_type
) ||
492 cell
.m_type
== KindOfNull
||
493 cell
.m_type
== KindOfBoolean
||
494 cell
.m_type
== KindOfInt64
||
495 cell
.m_type
== KindOfDouble
;
497 if (to_string_is_safe(*v1
) && to_string_is_safe(*v2
)) {
499 auto const cell
= eval_cell([&] {
500 auto s
= StringData::Make(
501 tvAsCVarRef(&*v2
).toString().get(),
502 tvAsCVarRef(&*v1
).toString().get());
503 return make_tv
<KindOfString
>(s
);
505 return push(env
, cell
? *cell
: TInitCell
);
508 // Not nothrow even if both are strings: can throw for strings
509 // that are too large.
513 void in(ISS
& env
, const bc::ConcatN
& op
) {
518 for (auto i
= 0; i
< n
; ++i
) {
524 template<class Op
, class Fun
>
525 void arithImpl(ISS
& env
, const Op
& op
, Fun fun
) {
527 auto const t1
= popC(env
);
528 auto const t2
= popC(env
);
529 push(env
, fun(t2
, t1
));
532 void in(ISS
& env
, const bc::Add
& op
) { arithImpl(env
, op
, typeAdd
); }
533 void in(ISS
& env
, const bc::Sub
& op
) { arithImpl(env
, op
, typeSub
); }
534 void in(ISS
& env
, const bc::Mul
& op
) { arithImpl(env
, op
, typeMul
); }
535 void in(ISS
& env
, const bc::Div
& op
) { arithImpl(env
, op
, typeDiv
); }
536 void in(ISS
& env
, const bc::Mod
& op
) { arithImpl(env
, op
, typeMod
); }
537 void in(ISS
& env
, const bc::Pow
& op
) { arithImpl(env
, op
, typePow
); }
538 void in(ISS
& env
, const bc::BitAnd
& op
) { arithImpl(env
, op
, typeBitAnd
); }
539 void in(ISS
& env
, const bc::BitOr
& op
) { arithImpl(env
, op
, typeBitOr
); }
540 void in(ISS
& env
, const bc::BitXor
& op
) { arithImpl(env
, op
, typeBitXor
); }
541 void in(ISS
& env
, const bc::AddO
& op
) { arithImpl(env
, op
, typeAddO
); }
542 void in(ISS
& env
, const bc::SubO
& op
) { arithImpl(env
, op
, typeSubO
); }
543 void in(ISS
& env
, const bc::MulO
& op
) { arithImpl(env
, op
, typeMulO
); }
544 void in(ISS
& env
, const bc::Shl
& op
) { arithImpl(env
, op
, typeShl
); }
545 void in(ISS
& env
, const bc::Shr
& op
) { arithImpl(env
, op
, typeShr
); }
547 void in(ISS
& env
, const bc::BitNot
& op
) {
548 auto const t
= popC(env
);
549 auto const v
= tv(t
);
552 auto cell
= eval_cell([&] {
557 if (cell
) return push(env
, std::move(*cell
));
559 push(env
, TInitCell
);
564 bool couldBeHackArr(Type t
) {
565 return t
.couldBe(TVec
) || t
.couldBe(TDict
) || t
.couldBe(TKeyset
);
570 template<bool Negate
>
571 void sameImpl(ISS
& env
) {
572 auto const t1
= popC(env
);
573 auto const t2
= popC(env
);
574 auto const v1
= tv(t1
);
575 auto const v2
= tv(t2
);
577 auto const mightWarn
= [&]{
578 // EvalHackArrCompatNotices will notice on === and !== between PHP arrays
580 if (!RuntimeOption::EvalHackArrCompatNotices
) return false;
581 if (t1
.couldBe(TArr
) && couldBeHackArr(t2
)) return true;
582 if (couldBeHackArr(t1
) && t2
.couldBe(TArr
)) return true;
591 if (auto r
= eval_cell_value([&]{ return cellSame(*v2
, *v1
); })) {
592 return push(env
, r
!= Negate
? TTrue
: TFalse
);
595 push(env
, Negate
? typeNSame(t1
, t2
) : typeSame(t1
, t2
));
598 void in(ISS
& env
, const bc::Same
&) { sameImpl
<false>(env
); }
599 void in(ISS
& env
, const bc::NSame
&) { sameImpl
<true>(env
); }
602 void binOpBoolImpl(ISS
& env
, Fun fun
) {
603 auto const t1
= popC(env
);
604 auto const t2
= popC(env
);
605 auto const v1
= tv(t1
);
606 auto const v2
= tv(t2
);
608 if (auto r
= eval_cell_value([&]{ return fun(*v2
, *v1
); })) {
610 return push(env
, *r
? TTrue
: TFalse
);
613 // TODO_4: evaluate when these can throw, non-constant type stuff.
618 void binOpInt64Impl(ISS
& env
, Fun fun
) {
619 auto const t1
= popC(env
);
620 auto const t2
= popC(env
);
621 auto const v1
= tv(t1
);
622 auto const v2
= tv(t2
);
624 if (auto r
= eval_cell_value([&]{ return ival(fun(*v2
, *v1
)); })) {
626 return push(env
, std::move(*r
));
629 // TODO_4: evaluate when these can throw, non-constant type stuff.
633 void in(ISS
& env
, const bc::Eq
&) {
634 binOpBoolImpl(env
, [&] (Cell c1
, Cell c2
) { return cellEqual(c1
, c2
); });
636 void in(ISS
& env
, const bc::Neq
&) {
637 binOpBoolImpl(env
, [&] (Cell c1
, Cell c2
) { return !cellEqual(c1
, c2
); });
639 void in(ISS
& env
, const bc::Lt
&) {
640 binOpBoolImpl(env
, [&] (Cell c1
, Cell c2
) { return cellLess(c1
, c2
); });
642 void in(ISS
& env
, const bc::Gt
&) {
643 binOpBoolImpl(env
, [&] (Cell c1
, Cell c2
) { return cellGreater(c1
, c2
); });
645 void in(ISS
& env
, const bc::Lte
&) { binOpBoolImpl(env
, cellLessOrEqual
); }
646 void in(ISS
& env
, const bc::Gte
&) { binOpBoolImpl(env
, cellGreaterOrEqual
); }
648 void in(ISS
& env
, const bc::Cmp
&) {
649 binOpInt64Impl(env
, [&] (Cell c1
, Cell c2
) { return cellCompare(c1
, c2
); });
652 void in(ISS
& env
, const bc::Xor
&) {
653 binOpBoolImpl(env
, [&] (Cell c1
, Cell c2
) {
654 return cellToBool(c1
) ^ cellToBool(c2
);
658 void castBoolImpl(ISS
& env
, bool negate
) {
662 auto const t
= popC(env
);
663 auto const v
= tv(t
);
665 auto cell
= eval_cell([&] {
666 return make_tv
<KindOfBoolean
>(cellToBool(*v
) != negate
);
668 always_assert_flog(!!cell
, "cellToBool should never throw");
669 return push(env
, std::move(*cell
));
672 if (t
.subtypeOf(TArrE
)) return push(env
, negate
? TTrue
: TFalse
);
673 if (t
.subtypeOf(TArrN
)) return push(env
, negate
? TFalse
: TTrue
);
678 void in(ISS
& env
, const bc::Not
&) {
679 castBoolImpl(env
, true);
682 void in(ISS
& env
, const bc::CastBool
&) {
683 auto const t
= topC(env
);
684 if (t
.subtypeOf(TBool
)) return reduce(env
, bc::Nop
{});
685 castBoolImpl(env
, false);
688 void in(ISS
& env
, const bc::CastInt
&) {
690 auto const t
= topC(env
);
691 if (t
.subtypeOf(TInt
)) return reduce(env
, bc::Nop
{});
693 // Objects can raise a warning about converting to int.
694 if (!t
.couldBe(TObj
)) nothrow(env
);
695 if (auto const v
= tv(t
)) {
696 auto cell
= eval_cell([&] {
697 return make_tv
<KindOfInt64
>(cellToInt(*v
));
699 if (cell
) return push(env
, std::move(*cell
));
704 void castImpl(ISS
& env
, Type target
, void(*fn
)(TypedValue
*)) {
705 auto const t
= topC(env
);
706 if (t
.subtypeOf(target
)) return reduce(env
, bc::Nop
{});
709 if (auto val
= tv(t
)) {
710 if (auto result
= eval_cell([&] { fn(&*val
); return *val
; })) {
716 push(env
, std::move(target
));
719 void in(ISS
& env
, const bc::CastDouble
&) {
720 castImpl(env
, TDbl
, tvCastToDoubleInPlace
);
723 void in(ISS
& env
, const bc::CastString
&) {
724 castImpl(env
, TStr
, tvCastToStringInPlace
);
727 void in(ISS
& env
, const bc::CastArray
&) {
728 castImpl(env
, TArr
, tvCastToArrayInPlace
);
731 void in(ISS
& env
, const bc::CastObject
&) { castImpl(env
, TObj
, nullptr); }
733 void in(ISS
& env
, const bc::CastDict
&) {
734 castImpl(env
, TDict
, tvCastToDictInPlace
);
737 void in(ISS
& env
, const bc::CastVec
&) {
738 castImpl(env
, TVec
, tvCastToVecInPlace
);
741 void in(ISS
& env
, const bc::CastKeyset
&) {
742 castImpl(env
, TKeyset
, tvCastToKeysetInPlace
);
745 void in(ISS
& env
, const bc::Print
& op
) { popC(env
); push(env
, ival(1)); }
747 void in(ISS
& env
, const bc::Clone
& op
) {
748 auto val
= popC(env
);
749 if (!val
.subtypeOf(TObj
)) {
750 val
= is_opt(val
) ? unopt(std::move(val
)) : TObj
;
752 push(env
, std::move(val
));
755 void in(ISS
& env
, const bc::Exit
&) { popC(env
); push(env
, TInitNull
); }
756 void in(ISS
& env
, const bc::Fatal
&) { popC(env
); }
758 void in(ISS
& env
, const bc::JmpNS
&) {
759 always_assert(0 && "blocks should not contain JmpNS instructions");
762 void in(ISS
& env
, const bc::Jmp
&) {
763 always_assert(0 && "blocks should not contain Jmp instructions");
766 template<bool Negate
, class JmpOp
>
767 void jmpImpl(ISS
& env
, const JmpOp
& op
) {
769 auto const t1
= popC(env
);
770 auto const v1
= tv(t1
);
772 auto const taken
= !cellToBool(*v1
) != Negate
;
774 jmp_nofallthrough(env
);
775 env
.propagate(op
.target
, env
.state
);
781 if (next_real_block(*env
.ctx
.func
, env
.blk
.fallthrough
) ==
782 next_real_block(*env
.ctx
.func
, op
.target
)) {
786 env
.propagate(op
.target
, env
.state
);
789 void in(ISS
& env
, const bc::JmpNZ
& op
) { jmpImpl
<true>(env
, op
); }
790 void in(ISS
& env
, const bc::JmpZ
& op
) { jmpImpl
<false>(env
, op
); }
792 template<class JmpOp
>
793 void group(ISS
& env
, const bc::IsTypeL
& istype
, const JmpOp
& jmp
) {
794 if (istype
.subop2
== IsTypeOp::Scalar
) return impl(env
, istype
, jmp
);
796 auto const loc
= derefLoc(env
, istype
.loc1
);
797 auto const testTy
= type_of_istype(istype
.subop2
);
798 if (loc
.subtypeOf(testTy
) || !loc
.couldBe(testTy
)) {
799 return impl(env
, istype
, jmp
);
802 if (!locCouldBeUninit(env
, istype
.loc1
)) nothrow(env
);
804 auto const negate
= jmp
.op
== Op::JmpNZ
;
805 auto const was_true
= [&] {
807 if (testTy
.subtypeOf(TNull
)) return TInitNull
;
808 auto const unopted
= unopt(loc
);
809 if (unopted
.subtypeOf(testTy
)) return unopted
;
813 auto const was_false
= [&] {
815 auto const unopted
= unopt(loc
);
816 if (testTy
.subtypeOf(TNull
)) return unopted
;
817 if (unopted
.subtypeOf(testTy
)) return TInitNull
;
822 refineLoc(env
, istype
.loc1
, negate
? was_true
: was_false
);
823 env
.propagate(jmp
.target
, env
.state
);
824 refineLoc(env
, istype
.loc1
, negate
? was_false
: was_true
);
829 // If the current function is a memoize wrapper, return the inferred return type
830 // of the function being wrapped.
831 Type
memoizeImplRetType(ISS
& env
) {
832 always_assert(env
.ctx
.func
->isMemoizeWrapper
);
834 // Lookup the wrapped function. This should always resolve to a precise
835 // function but we don't rely on it.
836 auto const memo_impl_func
= [&]{
837 if (env
.ctx
.func
->cls
) {
838 auto const clsTy
= selfClsExact(env
);
839 return env
.index
.resolve_method(
841 clsTy
? *clsTy
: TCls
,
842 memoize_impl_name(env
.ctx
.func
)
845 return env
.index
.resolve_func(env
.ctx
, memoize_impl_name(env
.ctx
.func
));
848 // Infer the return type of the wrapped function, taking into account the
849 // types of the parameters for context sensitive types.
850 auto const numArgs
= env
.ctx
.func
->params
.size();
851 std::vector
<Type
> args
{numArgs
};
852 for (auto i
= LocalId
{0}; i
< numArgs
; ++i
) {
853 args
[i
] = locAsCell(env
, i
);
856 auto retTy
= env
.index
.lookup_return_type(
857 CallContext
{ env
.ctx
, args
},
860 // Regardless of anything we know the return type will be an InitCell (this is
861 // a requirement of memoize functions).
862 if (!retTy
.subtypeOf(TInitCell
)) return TInitCell
;
867 * Propagate a more specific type to the taken/fall-through branches of a jmp
868 * operation when the jmp is done because of a type test. Given a type `valTy`,
869 * being tested against the type `testTy`, propagate `failTy` to the branch
870 * representing test failure, and `testTy` to the branch representing test
873 template<class JmpOp
>
874 void typeTestPropagate(ISS
& env
, Type valTy
, Type testTy
,
875 Type failTy
, const JmpOp
& jmp
) {
877 auto const takenOnSuccess
= jmp
.op
== Op::JmpNZ
;
879 if (valTy
.subtypeOf(testTy
) || failTy
.subtypeOf(TBottom
)) {
880 push(env
, std::move(valTy
));
881 if (takenOnSuccess
) {
882 jmp_nofallthrough(env
);
883 env
.propagate(jmp
.target
, env
.state
);
889 if (!valTy
.couldBe(testTy
)) {
891 if (takenOnSuccess
) {
894 jmp_nofallthrough(env
);
895 env
.propagate(jmp
.target
, env
.state
);
900 push(env
, std::move(takenOnSuccess
? testTy
: failTy
));
901 env
.propagate(jmp
.target
, env
.state
);
903 push(env
, std::move(takenOnSuccess
? failTy
: testTy
));
908 // If we duplicate a value, and then test its type and Jmp based on that result,
909 // we can narrow the type of the top of the stack. Only do this for null checks
910 // right now (because its useful in memoize wrappers).
911 template<class JmpOp
>
912 void group(ISS
& env
, const bc::Dup
& dup
,
913 const bc::IsTypeC
& istype
, const JmpOp
& jmp
) {
914 if (istype
.subop1
!= IsTypeOp::Scalar
) {
915 auto const testTy
= type_of_istype(istype
.subop1
);
916 if (testTy
.subtypeOf(TNull
)) {
917 auto const valTy
= popC(env
);
919 env
, valTy
, TInitNull
, is_opt(valTy
) ? unopt(valTy
) : valTy
, jmp
924 impl(env
, dup
, istype
, jmp
);
927 // If we do an IsUninit check and then Jmp based on the check, one branch will
928 // be the original type minus the Uninit, and the other will be
929 // Uninit. (IsUninit does not pop the value).
930 template<class JmpOp
>
931 void group(ISS
& env
, const bc::IsUninit
&, const JmpOp
& jmp
) {
932 auto const valTy
= popCU(env
);
933 typeTestPropagate(env
, valTy
, TUninit
, remove_uninit(valTy
), jmp
);
936 // A MemoGet, followed by an IsUninit, followed by a Jmp, can have the type of
937 // the stack inferred very well. The IsUninit success path will be Uninit and
938 // the failure path will be the inferred return type of the wrapped
939 // function. This has to be done as a group and not via individual interp()
940 // calls is because of limitations in HHBBC's type-system. The type that MemoGet
941 // pushes is the inferred return type of the wrapper function with Uninit added
942 // in. Unfortunately HHBBC's type-system cannot exactly represent this
943 // combination, so it gets forced to Cell. By analyzing this triplet as a group,
944 // we can avoid this loss of type precision.
945 template <class JmpOp
>
946 void group(ISS
& env
, const bc::MemoGet
& get
,
947 const bc::IsUninit
& isuninit
, const JmpOp
& jmp
) {
949 typeTestPropagate(env
, popCU(env
), TUninit
, memoizeImplRetType(env
), jmp
);
952 template<class JmpOp
>
953 void group(ISS
& env
, const bc::CGetL
& cgetl
, const JmpOp
& jmp
) {
954 auto const loc
= derefLoc(env
, cgetl
.loc1
);
955 if (tv(loc
)) return impl(env
, cgetl
, jmp
);
957 if (!locCouldBeUninit(env
, cgetl
.loc1
)) nothrow(env
);
959 auto const negate
= jmp
.op
== Op::JmpNZ
;
960 auto const converted_true
= [&]() -> const Type
{
961 if (is_opt(loc
)) return unopt(loc
);
962 if (loc
.subtypeOf(TBool
)) return TTrue
;
965 auto const converted_false
= [&]() -> const Type
{
966 if (!could_have_magic_bool_conversion(loc
) && loc
.subtypeOf(TOptObj
)) {
969 if (loc
.subtypeOf(TInt
)) return ival(0);
970 if (loc
.subtypeOf(TBool
)) return TFalse
;
971 if (loc
.subtypeOf(TDbl
)) return dval(0);
972 // Can't tell if any of the other ?primitives are going to be
973 // null based on this, so leave those types alone. E.g. a Str
974 // might contain "" and be falsey, or an array or collection
979 refineLoc(env
, cgetl
.loc1
, negate
? converted_true
: converted_false
);
980 env
.propagate(jmp
.target
, env
.state
);
981 refineLoc(env
, cgetl
.loc1
, negate
? converted_false
: converted_true
);
984 template<class JmpOp
>
986 const bc::CGetL
& cgetl
,
987 const bc::InstanceOfD
& inst
,
989 auto bail
= [&] { impl(env
, cgetl
, inst
, jmp
); };
991 if (interface_supports_non_objects(inst
.str1
)) return bail();
992 auto const rcls
= env
.index
.resolve_class(env
.ctx
, inst
.str1
);
993 if (!rcls
) return bail();
995 auto const instTy
= subObj(*rcls
);
996 auto const loc
= derefLoc(env
, cgetl
.loc1
);
997 if (loc
.subtypeOf(instTy
) || !loc
.couldBe(instTy
)) {
1001 auto const negate
= jmp
.op
== Op::JmpNZ
;
1002 auto const was_true
= instTy
;
1003 auto const was_false
= loc
;
1004 refineLoc(env
, cgetl
.loc1
, negate
? was_true
: was_false
);
1005 env
.propagate(jmp
.target
, env
.state
);
1006 refineLoc(env
, cgetl
.loc1
, negate
? was_false
: was_true
);
1009 void group(ISS
& env
,
1010 const bc::CGetL
& cgetl
,
1011 const bc::FPushObjMethodD
& fpush
) {
1012 auto const obj
= locAsCell(env
, cgetl
.loc1
);
1013 impl(env
, cgetl
, fpush
);
1014 if (!is_specialized_obj(obj
)) {
1015 refineLoc(env
, cgetl
.loc1
,
1016 fpush
.subop3
== ObjMethodOp::NullThrows
? TObj
: TOptObj
);
1017 } else if (is_opt(obj
) && fpush
.subop3
== ObjMethodOp::NullThrows
) {
1018 refineLoc(env
, cgetl
.loc1
, unopt(obj
));
1022 void in(ISS
& env
, const bc::Switch
& op
) {
1024 forEachTakenEdge(op
, [&] (BlockId id
) {
1025 env
.propagate(id
, env
.state
);
1029 void in(ISS
& env
, const bc::SSwitch
& op
) {
1031 forEachTakenEdge(op
, [&] (BlockId id
) {
1032 env
.propagate(id
, env
.state
);
1036 void in(ISS
& env
, const bc::RetC
& op
) { doRet(env
, popC(env
)); }
1037 void in(ISS
& env
, const bc::RetV
& op
) { doRet(env
, popV(env
)); }
1038 void in(ISS
& env
, const bc::Unwind
& op
) {}
1039 void in(ISS
& env
, const bc::Throw
& op
) { popC(env
); }
1041 void in(ISS
& env
, const bc::Catch
&) {
1043 return push(env
, subObj(env
.index
.builtin_class(s_Throwable
.get())));
1046 void in(ISS
& env
, const bc::NativeImpl
&) {
1050 if (is_collection_method_returning_this(env
.ctx
.cls
, env
.ctx
.func
)) {
1051 assert(env
.ctx
.func
->attrs
& AttrParamCoerceModeNull
);
1052 assert(!(env
.ctx
.func
->attrs
& AttrReference
));
1053 auto const resCls
= env
.index
.builtin_class(env
.ctx
.cls
->name
);
1054 // Can still return null if parameter coercion fails
1055 return doRet(env
, union_of(objExact(resCls
), TInitNull
));
1058 if (env
.ctx
.func
->nativeInfo
) {
1059 return doRet(env
, native_function_return_type(env
.ctx
.func
));
1061 doRet(env
, TInitGen
);
1064 void in(ISS
& env
, const bc::CGetL
& op
) {
1065 LocalId equivLocal
= NoLocalId
;
1066 // If the local could be Uninit or a Ref, don't record equality because the
1067 // value on the stack won't the same as in the local.
1068 if (!locCouldBeUninit(env
, op
.loc1
)) {
1071 if (!locCouldBeRef(env
, op
.loc1
) &&
1072 !is_volatile_local(env
.ctx
.func
, op
.loc1
)) {
1073 equivLocal
= op
.loc1
;
1076 push(env
, locAsCell(env
, op
.loc1
), equivLocal
);
1079 void in(ISS
& env
, const bc::CGetQuietL
& op
) {
1082 push(env
, locAsCell(env
, op
.loc1
));
1085 void in(ISS
& env
, const bc::CUGetL
& op
) {
1086 auto ty
= locRaw(env
, op
.loc1
);
1087 if (ty
.subtypeOf(TUninit
)) {
1088 return reduce(env
, bc::NullUninit
{});
1091 if (!ty
.couldBe(TUninit
)) constprop(env
);
1092 if (!ty
.subtypeOf(TCell
)) ty
= TCell
;
1093 push(env
, std::move(ty
));
1096 void in(ISS
& env
, const bc::PushL
& op
) {
1097 impl(env
, bc::CGetL
{ op
.loc1
}, bc::UnsetL
{ op
.loc1
});
1100 void in(ISS
& env
, const bc::CGetL2
& op
) {
1101 // Can't constprop yet because of no INS_1 support in bc.h
1102 if (!locCouldBeUninit(env
, op
.loc1
)) nothrow(env
);
1103 auto loc
= locAsCell(env
, op
.loc1
);
1104 auto top
= popT(env
);
1105 push(env
, std::move(loc
));
1106 push(env
, std::move(top
));
1111 template <typename Op
> void common_cgetn(ISS
& env
) {
1112 auto const t1
= topC(env
);
1113 auto const v1
= tv(t1
);
1114 if (v1
&& v1
->m_type
== KindOfPersistentString
) {
1115 auto const loc
= findLocal(env
, v1
->m_data
.pstr
);
1116 if (loc
!= NoLocalId
) {
1117 return reduce(env
, bc::PopC
{}, Op
{ loc
});
1120 readUnknownLocals(env
);
1122 popC(env
); // conversion to string can throw
1123 push(env
, TInitCell
);
1128 void in(ISS
& env
, const bc::CGetN
&) { common_cgetn
<bc::CGetL
>(env
); }
1129 void in(ISS
& env
, const bc::CGetQuietN
&) { common_cgetn
<bc::CGetQuietL
>(env
); }
1131 void in(ISS
& env
, const bc::CGetG
&) { popC(env
); push(env
, TInitCell
); }
1132 void in(ISS
& env
, const bc::CGetQuietG
&) { popC(env
); push(env
, TInitCell
); }
1134 void in(ISS
& env
, const bc::CGetS
& op
) {
1135 auto const tcls
= takeClsRefSlot(env
, op
.slot
);
1136 auto const tname
= popC(env
);
1137 auto const vname
= tv(tname
);
1138 auto const self
= selfCls(env
);
1140 if (vname
&& vname
->m_type
== KindOfPersistentString
&&
1141 self
&& tcls
.subtypeOf(*self
)) {
1142 if (auto ty
= selfPropAsCell(env
, vname
->m_data
.pstr
)) {
1143 // Only nothrow when we know it's a private declared property
1144 // (and thus accessible here).
1147 // We can only constprop here if we know for sure this is exactly the
1148 // correct class. The reason for this is that you could have a LSB class
1149 // attempting to access a private static in a derived class with the same
1150 // name as a private static in this class, which is supposed to fatal at
1151 // runtime (for an example see test/quick/static_sprop2.php).
1152 auto const selfExact
= selfClsExact(env
);
1153 if (selfExact
&& tcls
.subtypeOf(*selfExact
)) {
1157 return push(env
, std::move(*ty
));
1161 auto indexTy
= env
.index
.lookup_public_static(tcls
, tname
);
1162 if (indexTy
.subtypeOf(TInitCell
)) {
1164 * Constant propagation here can change when we invoke autoload, so it's
1165 * considered HardConstProp. It's safe not to check anything about private
1166 * or protected static properties, because you can't override a public
1167 * static property with a private or protected one---if the index gave us
1168 * back a constant type, it's because it found a public static and it must
1169 * be the property this would have read dynamically.
1171 if (options
.HardConstProp
) constprop(env
);
1172 return push(env
, std::move(indexTy
));
1175 push(env
, TInitCell
);
1178 void in(ISS
& env
, const bc::VGetL
& op
) {
1180 setLocRaw(env
, op
.loc1
, TRef
);
1184 void in(ISS
& env
, const bc::VGetN
&) {
1185 auto const t1
= topC(env
);
1186 auto const v1
= tv(t1
);
1187 if (v1
&& v1
->m_type
== KindOfPersistentString
) {
1188 auto const loc
= findLocal(env
, v1
->m_data
.pstr
);
1189 if (loc
!= NoLocalId
) {
1190 return reduce(env
, bc::PopC
{},
1194 modifyLocalStatic(env
, NoLocalId
, TRef
);
1196 boxUnknownLocal(env
);
1201 void in(ISS
& env
, const bc::VGetG
&) { popC(env
); push(env
, TRef
); }
1203 void in(ISS
& env
, const bc::VGetS
& op
) {
1204 auto const tcls
= takeClsRefSlot(env
, op
.slot
);
1205 auto const tname
= popC(env
);
1206 auto const vname
= tv(tname
);
1207 auto const self
= selfCls(env
);
1209 if (!self
|| tcls
.couldBe(*self
)) {
1210 if (vname
&& vname
->m_type
== KindOfPersistentString
) {
1211 boxSelfProp(env
, vname
->m_data
.pstr
);
1217 if (auto c
= env
.collect
.publicStatics
) {
1218 c
->merge(env
.ctx
, tcls
, tname
, TRef
);
1224 void clsRefGetImpl(ISS
& env
, Type t1
, ClsRefSlotId slot
) {
1226 if (t1
.subtypeOf(TObj
)) {
1230 auto const v1
= tv(t1
);
1231 if (v1
&& v1
->m_type
== KindOfPersistentString
) {
1232 if (auto const rcls
= env
.index
.resolve_class(env
.ctx
, v1
->m_data
.pstr
)) {
1233 return clsExact(*rcls
);
1238 putClsRefSlot(env
, slot
, std::move(cls
));
1241 void in(ISS
& env
, const bc::ClsRefGetL
& op
) {
1242 clsRefGetImpl(env
, locAsCell(env
, op
.loc1
), op
.slot
);
1244 void in(ISS
& env
, const bc::ClsRefGetC
& op
) {
1245 clsRefGetImpl(env
, popC(env
), op
.slot
);
1248 void in(ISS
& env
, const bc::AKExists
& op
) {
1249 auto const t1
= popC(env
);
1250 auto const t2
= popC(env
);
1251 auto const t1Ok
= t1
.subtypeOf(TObj
) || t1
.subtypeOf(TArr
);
1252 auto const t2Ok
= t2
.subtypeOf(TInt
) || t2
.subtypeOf(TNull
) ||
1254 if (t1Ok
&& t2Ok
) nothrow(env
);
1258 void in(ISS
& env
, const bc::GetMemoKeyL
& op
) {
1259 always_assert(env
.ctx
.func
->isMemoizeWrapper
);
1261 auto const tyIMemoizeParam
=
1262 subObj(env
.index
.builtin_class(s_IMemoizeParam
.get()));
1264 auto const inTy
= locAsCell(env
, op
.loc1
);
1266 // If the local could be uninit, we might raise a warning (as
1267 // usual). Converting an object to a memo key might invoke PHP code if it has
1268 // the IMemoizeParam interface, and if it doesn't, we'll throw.
1269 if (!locCouldBeUninit(env
, op
.loc1
) && !inTy
.couldBe(TObj
)) {
1270 nothrow(env
); constprop(env
);
1273 // If type constraints are being enforced and the local being turned into a
1274 // memo key is a parameter, then we can possibly using the type constraint to
1275 // perform a more efficient memoization scheme. Note that this all needs to
1276 // stay in sync with the interpreter and JIT.
1277 using MK
= MemoKeyConstraint
;
1278 auto const mkc
= [&]{
1279 if (!options
.HardTypeHints
) return MK::None
;
1280 if (op
.loc1
>= env
.ctx
.func
->params
.size()) return MK::None
;
1281 return memoKeyConstraintFromTC(
1282 env
.ctx
.func
->params
[op
.loc1
].typeConstraint
1288 // Always null, so the key can always just be 0
1289 always_assert(inTy
.subtypeOf(TNull
));
1290 return push(env
, ival(0));
1292 // Always an int, so the key is always an identity mapping
1293 always_assert(inTy
.subtypeOf(TInt
));
1294 return reduce(env
, bc::CGetL
{ op
.loc1
});
1296 // Always a bool, so the key is the bool cast to an int
1297 always_assert(inTy
.subtypeOf(TBool
));
1298 return reduce(env
, bc::CGetL
{ op
.loc1
}, bc::CastInt
{});
1300 // Always a string, so the key is always an identity mapping
1301 always_assert(inTy
.subtypeOf(TStr
));
1302 return reduce(env
, bc::CGetL
{ op
.loc1
});
1304 // Either an int or string, so the key can be an identity mapping
1305 return reduce(env
, bc::CGetL
{ op
.loc1
});
1308 // A nullable string or int. For strings the key will always be 0 or the
1309 // string. For ints the key will be the int or a static string. We can't
1310 // reduce either without introducing control flow.
1311 return push(env
, union_of(TInt
, TStr
));
1312 case MK::BoolOrNull
:
1313 // A nullable bool. The key will always be an int (null will be 2), but we
1314 // can't reduce that without introducing control flow.
1315 return push(env
, TInt
);
1320 // No type constraint, or one that isn't usuable. Use the generic memoization
1321 // scheme which can handle any type:
1323 // Integer keys are always mapped to themselves
1324 if (inTy
.subtypeOf(TInt
)) return reduce(env
, bc::CGetL
{ op
.loc1
});
1326 if (inTy
.subtypeOf(tyIMemoizeParam
)) {
1329 bc::CGetL
{ op
.loc1
},
1330 bc::FPushObjMethodD
{
1332 s_getInstanceKey
.get(),
1333 ObjMethodOp::NullThrows
,
1341 // A memo key can be an integer if the input might be an integer, and is a
1342 // string otherwise. Booleans are always static strings.
1344 if (auto const val
= tv(inTy
)) {
1345 auto const key
= eval_cell(
1346 [&]{ return HHVM_FN(serialize_memoize_param
)(*val
); }
1348 if (key
) return *key
;
1350 if (inTy
.subtypeOf(TBool
)) return TSStr
;
1351 if (inTy
.couldBe(TInt
)) return union_of(TInt
, TStr
);
1354 push(env
, std::move(keyTy
));
1357 void in(ISS
& env
, const bc::IssetL
& op
) {
1360 auto const loc
= locAsCell(env
, op
.loc1
);
1361 if (loc
.subtypeOf(TNull
)) return push(env
, TFalse
);
1362 if (!loc
.couldBe(TNull
)) return push(env
, TTrue
);
1366 void in(ISS
& env
, const bc::EmptyL
& op
) {
1369 if (!locCouldBeUninit(env
, op
.loc1
)) {
1370 return impl(env
, bc::CGetL
{ op
.loc1
}, bc::Not
{});
1372 locAsCell(env
, op
.loc1
); // read the local
1376 void in(ISS
& env
, const bc::EmptyS
& op
) {
1377 takeClsRefSlot(env
, op
.slot
);
1382 void in(ISS
& env
, const bc::IssetS
& op
) {
1383 auto const tcls
= takeClsRefSlot(env
, op
.slot
);
1384 auto const tname
= popC(env
);
1385 auto const vname
= tv(tname
);
1386 auto const self
= selfCls(env
);
1388 if (self
&& tcls
.subtypeOf(*self
) &&
1389 vname
&& vname
->m_type
== KindOfPersistentString
) {
1390 if (auto const t
= selfPropAsCell(env
, vname
->m_data
.pstr
)) {
1391 if (t
->subtypeOf(TNull
)) { constprop(env
); return push(env
, TFalse
); }
1392 if (!t
->couldBe(TNull
)) { constprop(env
); return push(env
, TTrue
); }
1396 auto const indexTy
= env
.index
.lookup_public_static(tcls
, tname
);
1397 if (indexTy
.subtypeOf(TInitCell
)) {
1398 // See the comments in CGetS about constprop for public statics.
1399 if (options
.HardConstProp
) constprop(env
);
1400 if (indexTy
.subtypeOf(TNull
)) { return push(env
, TFalse
); }
1401 if (!indexTy
.couldBe(TNull
)) { return push(env
, TTrue
); }
1407 template<class ReduceOp
>
1408 void issetEmptyNImpl(ISS
& env
) {
1409 auto const t1
= topC(env
);
1410 auto const v1
= tv(t1
);
1411 if (v1
&& v1
->m_type
== KindOfPersistentString
) {
1412 auto const loc
= findLocal(env
, v1
->m_data
.pstr
);
1413 if (loc
!= NoLocalId
) {
1414 return reduce(env
, bc::PopC
{}, ReduceOp
{ loc
});
1416 // Can't push true in the non env.findLocal case unless we know
1417 // whether this function can have a VarEnv.
1419 readUnknownLocals(env
);
1425 void in(ISS
& env
, const bc::IssetN
&) { issetEmptyNImpl
<bc::IssetL
>(env
); }
1426 void in(ISS
& env
, const bc::EmptyN
&) { issetEmptyNImpl
<bc::EmptyL
>(env
); }
1427 void in(ISS
& env
, const bc::EmptyG
&) { popC(env
); push(env
, TBool
); }
1428 void in(ISS
& env
, const bc::IssetG
&) { popC(env
); push(env
, TBool
); }
1430 void isTypeImpl(ISS
& env
, const Type
& locOrCell
, const Type
& test
) {
1432 if (locOrCell
.subtypeOf(test
)) return push(env
, TTrue
);
1433 if (!locOrCell
.couldBe(test
)) return push(env
, TFalse
);
1437 void isTypeObj(ISS
& env
, const Type
& ty
) {
1438 if (!ty
.couldBe(TObj
)) return push(env
, TFalse
);
1439 if (ty
.subtypeOf(TObj
)) {
1440 auto const incompl
= objExact(
1441 env
.index
.builtin_class(s_PHP_Incomplete_Class
.get()));
1442 if (!ty
.couldBe(incompl
)) return push(env
, TTrue
);
1443 if (ty
.subtypeOf(incompl
)) return push(env
, TFalse
);
1449 void isTypeLImpl(ISS
& env
, const Op
& op
) {
1450 if (!locCouldBeUninit(env
, op
.loc1
)) { nothrow(env
); constprop(env
); }
1451 auto const loc
= locAsCell(env
, op
.loc1
);
1452 switch (op
.subop2
) {
1453 case IsTypeOp::Scalar
: return push(env
, TBool
);
1454 case IsTypeOp::Obj
: return isTypeObj(env
, loc
);
1455 default: return isTypeImpl(env
, loc
, type_of_istype(op
.subop2
));
1460 void isTypeCImpl(ISS
& env
, const Op
& op
) {
1462 auto const t1
= popC(env
);
1463 switch (op
.subop1
) {
1464 case IsTypeOp::Scalar
: return push(env
, TBool
);
1465 case IsTypeOp::Obj
: return isTypeObj(env
, t1
);
1466 default: return isTypeImpl(env
, t1
, type_of_istype(op
.subop1
));
1470 void in(ISS
& env
, const bc::IsTypeC
& op
) { isTypeCImpl(env
, op
); }
1471 void in(ISS
& env
, const bc::IsTypeL
& op
) { isTypeLImpl(env
, op
); }
1473 void in(ISS
& env
, const bc::IsUninit
& op
) {
1475 push(env
, popCU(env
));
1476 isTypeImpl(env
, topT(env
), TUninit
);
1479 void in(ISS
& env
, const bc::MaybeMemoType
& op
) {
1480 always_assert(env
.ctx
.func
->isMemoizeWrapper
);
1483 auto const memoTy
= memoizeImplRetType(env
);
1484 auto const ty
= popC(env
);
1485 push(env
, ty
.couldBe(memoTy
) ? TTrue
: TFalse
);
1488 void in(ISS
& env
, const bc::IsMemoType
& op
) {
1489 always_assert(env
.ctx
.func
->isMemoizeWrapper
);
1492 auto const memoTy
= memoizeImplRetType(env
);
1493 auto const ty
= popC(env
);
1494 push(env
, memoTy
.subtypeOf(ty
) ? TTrue
: TFalse
);
1497 void in(ISS
& env
, const bc::InstanceOfD
& op
) {
1498 auto const t1
= popC(env
);
1499 // Note: InstanceOfD can do autoload if the type might be a type
1500 // alias, so it's not nothrow unless we know it's an object type.
1501 if (auto const rcls
= env
.index
.resolve_class(env
.ctx
, op
.str1
)) {
1503 if (!interface_supports_non_objects(rcls
->name())) {
1504 isTypeImpl(env
, t1
, subObj(*rcls
));
1511 void in(ISS
& env
, const bc::InstanceOf
& op
) {
1512 auto const t1
= topC(env
);
1513 auto const v1
= tv(t1
);
1514 if (v1
&& v1
->m_type
== KindOfPersistentString
) {
1515 return reduce(env
, bc::PopC
{},
1516 bc::InstanceOfD
{ v1
->m_data
.pstr
});
1519 if (t1
.subtypeOf(TObj
) && is_specialized_obj(t1
)) {
1520 auto const dobj
= dobj_of(t1
);
1521 switch (dobj
.type
) {
1525 return reduce(env
, bc::PopC
{},
1526 bc::InstanceOfD
{ dobj
.cls
.name() });
1535 void in(ISS
& env
, const bc::SetL
& op
) {
1537 auto equivLoc
= topStkEquiv(env
);
1538 // If the local could be a Ref, don't record equality because the stack
1539 // element and the local won't actually have the same type.
1540 if (!locCouldBeRef(env
, op
.loc1
) &&
1541 !is_volatile_local(env
.ctx
.func
, op
.loc1
)) {
1542 if (equivLoc
!= NoLocalId
) {
1543 if (equivLoc
== op
.loc1
||
1544 locsAreEquiv(env
, equivLoc
, op
.loc1
)) {
1545 return reduce(env
, bc::Nop
{});
1551 auto val
= popC(env
);
1552 setLoc(env
, op
.loc1
, val
);
1553 if (equivLoc
!= op
.loc1
&& equivLoc
!= NoLocalId
) {
1554 addLocEquiv(env
, op
.loc1
, equivLoc
);
1556 push(env
, std::move(val
), equivLoc
);
1559 void in(ISS
& env
, const bc::SetN
&) {
1560 // This isn't trivial to strength reduce, without a "flip two top
1561 // elements of stack" opcode.
1562 auto t1
= popC(env
);
1563 auto const t2
= popC(env
);
1564 auto const v2
= tv(t2
);
1565 // TODO(#3653110): could nothrow if t2 can't be an Obj or Res
1567 auto const knownLoc
= v2
&& v2
->m_type
== KindOfPersistentString
1568 ? findLocal(env
, v2
->m_data
.pstr
)
1570 if (knownLoc
!= NoLocalId
) {
1571 setLoc(env
, knownLoc
, t1
);
1573 // We could be changing the value of any local, but we won't
1574 // change whether or not they are boxed or initialized.
1575 loseNonRefLocalTypes(env
);
1578 push(env
, std::move(t1
));
1581 void in(ISS
& env
, const bc::SetG
&) {
1582 auto t1
= popC(env
);
1584 push(env
, std::move(t1
));
1587 void in(ISS
& env
, const bc::SetS
& op
) {
1588 auto const t1
= popC(env
);
1589 auto const tcls
= takeClsRefSlot(env
, op
.slot
);
1590 auto const tname
= popC(env
);
1591 auto const vname
= tv(tname
);
1592 auto const self
= selfCls(env
);
1594 if (!self
|| tcls
.couldBe(*self
)) {
1595 if (vname
&& vname
->m_type
== KindOfPersistentString
) {
1597 mergeSelfProp(env
, vname
->m_data
.pstr
, t1
);
1599 mergeEachSelfPropRaw(env
, [&] (Type
) { return t1
; });
1603 if (auto c
= env
.collect
.publicStatics
) {
1604 c
->merge(env
.ctx
, tcls
, tname
, t1
);
1607 push(env
, std::move(t1
));
1610 void in(ISS
& env
, const bc::SetOpL
& op
) {
1611 auto const t1
= popC(env
);
1612 auto const v1
= tv(t1
);
1613 auto const loc
= locAsCell(env
, op
.loc1
);
1614 auto const locVal
= tv(loc
);
1616 // Can't constprop at this eval_cell, because of the effects on
1618 auto resultTy
= eval_cell([&] {
1621 setopBody(&c
, op
.subop2
, &rhs
);
1624 if (!resultTy
) resultTy
= TInitCell
;
1626 // We may have inferred a TSStr or TSArr with a value here, but
1627 // at runtime it will not be static. For now just throw that
1628 // away. TODO(#3696042): should be able to loosen_statics here.
1629 if (resultTy
->subtypeOf(TStr
)) resultTy
= TStr
;
1630 else if (resultTy
->subtypeOf(TArr
)) resultTy
= TArr
;
1632 setLoc(env
, op
.loc1
, *resultTy
);
1633 push(env
, *resultTy
);
1637 auto resultTy
= typeSetOp(op
.subop2
, loc
, t1
);
1638 setLoc(env
, op
.loc1
, resultTy
);
1639 push(env
, std::move(resultTy
));
1642 void in(ISS
& env
, const bc::SetOpN
&) {
1645 loseNonRefLocalTypes(env
);
1647 push(env
, TInitCell
);
1650 void in(ISS
& env
, const bc::SetOpG
&) {
1651 popC(env
); popC(env
);
1652 push(env
, TInitCell
);
1655 void in(ISS
& env
, const bc::SetOpS
& op
) {
1657 auto const tcls
= takeClsRefSlot(env
, op
.slot
);
1658 auto const tname
= popC(env
);
1659 auto const vname
= tv(tname
);
1660 auto const self
= selfCls(env
);
1662 if (!self
|| tcls
.couldBe(*self
)) {
1663 if (vname
&& vname
->m_type
== KindOfPersistentString
) {
1664 mergeSelfProp(env
, vname
->m_data
.pstr
, TInitCell
);
1666 loseNonRefSelfPropTypes(env
);
1670 if (auto c
= env
.collect
.publicStatics
) {
1671 c
->merge(env
.ctx
, tcls
, tname
, TInitCell
);
1674 push(env
, TInitCell
);
1677 void in(ISS
& env
, const bc::IncDecL
& op
) {
1678 auto loc
= locAsCell(env
, op
.loc1
);
1679 auto newT
= typeIncDec(op
.subop2
, loc
);
1680 auto const pre
= isPre(op
.subop2
);
1682 // If it's a non-numeric string, this may cause it to exceed the max length.
1683 if (!locCouldBeUninit(env
, op
.loc1
) &&
1684 !loc
.couldBe(TStr
)) {
1688 if (!pre
) push(env
, std::move(loc
));
1689 setLoc(env
, op
.loc1
, newT
);
1690 if (pre
) push(env
, std::move(newT
));
1693 void in(ISS
& env
, const bc::IncDecN
& op
) {
1694 auto const t1
= topC(env
);
1695 auto const v1
= tv(t1
);
1696 auto const knownLoc
= v1
&& v1
->m_type
== KindOfPersistentString
1697 ? findLocal(env
, v1
->m_data
.pstr
)
1699 if (knownLoc
!= NoLocalId
) {
1700 return reduce(env
, bc::PopC
{},
1701 bc::IncDecL
{ knownLoc
, op
.subop1
});
1704 loseNonRefLocalTypes(env
);
1706 push(env
, TInitCell
);
1709 void in(ISS
& env
, const bc::IncDecG
&) { popC(env
); push(env
, TInitCell
); }
1711 void in(ISS
& env
, const bc::IncDecS
& op
) {
1712 auto const tcls
= takeClsRefSlot(env
, op
.slot
);
1713 auto const tname
= popC(env
);
1714 auto const vname
= tv(tname
);
1715 auto const self
= selfCls(env
);
1717 if (!self
|| tcls
.couldBe(*self
)) {
1718 if (vname
&& vname
->m_type
== KindOfPersistentString
) {
1719 mergeSelfProp(env
, vname
->m_data
.pstr
, TInitCell
);
1721 loseNonRefSelfPropTypes(env
);
1725 if (auto c
= env
.collect
.publicStatics
) {
1726 c
->merge(env
.ctx
, tcls
, tname
, TInitCell
);
1729 push(env
, TInitCell
);
1732 void in(ISS
& env
, const bc::BindL
& op
) {
1733 // If the op.loc1 was bound to a local static, its going to be
1734 // unbound from it. If the thing its being bound /to/ is a local
1735 // static, we've already marked it as modified via the VGetL, so
1736 // there's nothing more to track.
1737 // Unbind it before any updates.
1738 modifyLocalStatic(env
, op
.loc1
, TUninit
);
1740 auto t1
= popV(env
);
1741 setLocRaw(env
, op
.loc1
, t1
);
1742 push(env
, std::move(t1
));
1745 void in(ISS
& env
, const bc::BindN
&) {
1746 // TODO(#3653110): could nothrow if t2 can't be an Obj or Res
1747 auto t1
= popV(env
);
1748 auto const t2
= popC(env
);
1749 auto const v2
= tv(t2
);
1750 auto const knownLoc
= v2
&& v2
->m_type
== KindOfPersistentString
1751 ? findLocal(env
, v2
->m_data
.pstr
)
1753 unbindLocalStatic(env
, knownLoc
);
1754 if (knownLoc
!= NoLocalId
) {
1755 setLocRaw(env
, knownLoc
, t1
);
1757 boxUnknownLocal(env
);
1760 push(env
, std::move(t1
));
1763 void in(ISS
& env
, const bc::BindG
&) {
1764 auto t1
= popV(env
);
1766 push(env
, std::move(t1
));
1769 void in(ISS
& env
, const bc::BindS
& op
) {
1771 auto const tcls
= takeClsRefSlot(env
, op
.slot
);
1772 auto const tname
= popC(env
);
1773 auto const vname
= tv(tname
);
1774 auto const self
= selfCls(env
);
1776 if (!self
|| tcls
.couldBe(*self
)) {
1777 if (vname
&& vname
->m_type
== KindOfPersistentString
) {
1778 boxSelfProp(env
, vname
->m_data
.pstr
);
1784 if (auto c
= env
.collect
.publicStatics
) {
1785 c
->merge(env
.ctx
, tcls
, tname
, TRef
);
1791 void in(ISS
& env
, const bc::UnsetL
& op
) {
1793 setLocRaw(env
, op
.loc1
, TUninit
);
1796 void in(ISS
& env
, const bc::UnsetN
& op
) {
1797 auto const t1
= topC(env
);
1798 auto const v1
= tv(t1
);
1799 if (v1
&& v1
->m_type
== KindOfPersistentString
) {
1800 auto const loc
= findLocal(env
, v1
->m_data
.pstr
);
1801 if (loc
!= NoLocalId
) {
1802 return reduce(env
, bc::PopC
{},
1803 bc::UnsetL
{ loc
});
1807 if (!t1
.couldBe(TObj
) && !t1
.couldBe(TRes
)) nothrow(env
);
1808 unsetUnknownLocal(env
);
1812 void in(ISS
& env
, const bc::UnsetG
& op
) {
1813 auto const t1
= popC(env
);
1814 if (!t1
.couldBe(TObj
) && !t1
.couldBe(TRes
)) nothrow(env
);
1817 void in(ISS
& env
, const bc::FPushFuncD
& op
) {
1818 auto const rfunc
= env
.index
.resolve_func(env
.ctx
, op
.str2
);
1819 if (auto const func
= rfunc
.exactFunc()) {
1820 if (can_emit_builtin(func
, op
.arg1
, op
.has_unpack
)) {
1821 fpiPush(env
, ActRec
{ FPIKind::Builtin
, folly::none
, rfunc
});
1822 return reduce(env
, bc::Nop
{});
1825 fpiPush(env
, ActRec
{ FPIKind::Func
, folly::none
, rfunc
});
1828 void in(ISS
& env
, const bc::FPushFunc
& op
) {
1829 auto const t1
= topC(env
);
1830 auto const v1
= tv(t1
);
1831 if (v1
&& v1
->m_type
== KindOfPersistentString
) {
1832 auto const name
= normalizeNS(v1
->m_data
.pstr
);
1833 // FPushFuncD doesn't support class-method pair strings yet.
1834 if (isNSNormalized(name
) && notClassMethodPair(name
)) {
1835 auto const rfunc
= env
.index
.resolve_func(env
.ctx
, name
);
1836 // Don't turn dynamic calls to caller frame affecting functions into
1837 // static calls, because they might fatal (whereas the static one won't).
1838 if (!rfunc
.mightAccessCallerFrame()) {
1839 return reduce(env
, bc::PopC
{},
1840 bc::FPushFuncD
{ op
.arg1
, name
, op
.has_unpack
});
1845 if (t1
.subtypeOf(TObj
)) return fpiPush(env
, ActRec
{ FPIKind::ObjInvoke
});
1846 if (t1
.subtypeOf(TArr
)) return fpiPush(env
, ActRec
{ FPIKind::CallableArr
});
1847 if (t1
.subtypeOf(TStr
)) return fpiPush(env
, ActRec
{ FPIKind::Func
});
1848 fpiPush(env
, ActRec
{ FPIKind::Unknown
});
1851 void in(ISS
& env
, const bc::FPushFuncU
& op
) {
1852 auto const rfuncPair
=
1853 env
.index
.resolve_func_fallback(env
.ctx
, op
.str2
, op
.str3
);
1854 if (options
.ElideAutoloadInvokes
&& !rfuncPair
.second
) {
1857 bc::FPushFuncD
{ op
.arg1
, rfuncPair
.first
.name(), op
.has_unpack
}
1862 ActRec
{ FPIKind::Func
, folly::none
, rfuncPair
.first
, rfuncPair
.second
}
1866 void in(ISS
& env
, const bc::FPushObjMethodD
& op
) {
1867 auto t1
= popC(env
);
1868 if (is_opt(t1
) && op
.subop3
== ObjMethodOp::NullThrows
) {
1871 auto const clsTy
= objcls(t1
);
1872 auto const rcls
= [&]() -> folly::Optional
<res::Class
> {
1873 if (is_specialized_cls(clsTy
)) return dcls_of(clsTy
).cls
;
1877 fpiPush(env
, ActRec
{
1880 env
.index
.resolve_method(env
.ctx
, clsTy
, op
.str2
)
1884 void in(ISS
& env
, const bc::FPushObjMethod
& op
) {
1885 auto const t1
= topC(env
);
1886 auto const v1
= tv(t1
);
1887 if (v1
&& v1
->m_type
== KindOfPersistentString
) {
1891 bc::FPushObjMethodD
{ op
.arg1
, v1
->m_data
.pstr
, op
.subop2
, op
.has_unpack
}
1896 fpiPush(env
, ActRec
{ FPIKind::ObjMeth
});
1899 void in(ISS
& env
, const bc::FPushClsMethodD
& op
) {
1900 auto const rcls
= env
.index
.resolve_class(env
.ctx
, op
.str3
);
1901 auto const rfun
= env
.index
.resolve_method(
1903 rcls
? clsExact(*rcls
) : TCls
,
1906 fpiPush(env
, ActRec
{ FPIKind::ClsMeth
, rcls
, rfun
});
1909 void in(ISS
& env
, const bc::FPushClsMethod
& op
) {
1910 auto const t1
= takeClsRefSlot(env
, op
.slot
);
1911 auto const t2
= popC(env
);
1912 auto const v2
= tv(t2
);
1914 folly::Optional
<res::Func
> rfunc
;
1915 if (v2
&& v2
->m_type
== KindOfPersistentString
) {
1916 rfunc
= env
.index
.resolve_method(env
.ctx
, t1
, v2
->m_data
.pstr
);
1918 folly::Optional
<res::Class
> rcls
;
1919 if (is_specialized_cls(t1
)) rcls
= dcls_of(t1
).cls
;
1920 fpiPush(env
, ActRec
{ FPIKind::ClsMeth
, rcls
, rfunc
});
1923 void in(ISS
& env
, const bc::FPushClsMethodF
& op
) {
1924 // The difference with FPushClsMethod is what ends up on the
1925 // ActRec (late-bound class), which we currently aren't tracking.
1926 impl(env
, bc::FPushClsMethod
{ op
.arg1
, op
.slot
, op
.has_unpack
});
1929 void ctorHelper(ISS
& env
, SString name
) {
1930 auto const rcls
= env
.index
.resolve_class(env
.ctx
, name
);
1931 push(env
, rcls
? objExact(*rcls
) : TObj
);
1933 rcls
? env
.index
.resolve_ctor(env
.ctx
, *rcls
) : folly::none
;
1934 fpiPush(env
, ActRec
{ FPIKind::Ctor
, rcls
, rfunc
});
1937 void in(ISS
& env
, const bc::FPushCtorD
& op
) {
1938 ctorHelper(env
, op
.str2
);
1941 void in(ISS
& env
, const bc::FPushCtorI
& op
) {
1942 auto const name
= env
.ctx
.unit
->classes
[op
.arg2
]->name
;
1943 ctorHelper(env
, name
);
1946 void in(ISS
& env
, const bc::FPushCtor
& op
) {
1947 auto const& t1
= peekClsRefSlot(env
, op
.slot
);
1948 if (is_specialized_cls(t1
)) {
1949 auto const dcls
= dcls_of(t1
);
1950 if (dcls
.type
== DCls::Exact
) {
1951 return reduce(env
, bc::DiscardClsRef
{ op
.slot
},
1952 bc::FPushCtorD
{ op
.arg1
, dcls
.cls
.name(), op
.has_unpack
});
1955 takeClsRefSlot(env
, op
.slot
);
1957 fpiPush(env
, ActRec
{ FPIKind::Ctor
});
1960 void in(ISS
& env
, const bc::FPushCufIter
&) {
1962 fpiPush(env
, ActRec
{ FPIKind::Unknown
});
1965 void in(ISS
& env
, const bc::FPushCuf
&) {
1967 fpiPush(env
, ActRec
{ FPIKind::Unknown
});
1969 void in(ISS
& env
, const bc::FPushCufF
&) {
1971 fpiPush(env
, ActRec
{ FPIKind::Unknown
});
1974 void in(ISS
& env
, const bc::FPushCufSafe
&) {
1975 auto t1
= popC(env
);
1977 push(env
, std::move(t1
));
1978 fpiPush(env
, ActRec
{ FPIKind::Unknown
});
1982 void in(ISS
& env
, const bc::FPassL
& op
) {
1983 switch (prepKind(env
, op
.arg1
)) {
1984 case PrepKind::Unknown
:
1985 if (!locCouldBeUninit(env
, op
.loc2
)) nothrow(env
);
1986 // This might box the local, we can't tell. Note: if the local
1987 // is already TRef, we could try to leave it alone, but not for
1989 setLocRaw(env
, op
.loc2
, TGen
);
1990 return push(env
, TInitGen
);
1992 return reduce_fpass_arg(env
, bc::CGetL
{ op
.loc2
}, op
.arg1
, false);
1994 return reduce_fpass_arg(env
, bc::VGetL
{ op
.loc2
}, op
.arg1
, true);
1998 void in(ISS
& env
, const bc::FPassN
& op
) {
1999 switch (prepKind(env
, op
.arg1
)) {
2000 case PrepKind::Unknown
:
2001 // This could change the type of any local.
2005 return push(env
, TInitGen
);
2006 case PrepKind::Val
: return reduce_fpass_arg(env
,
2010 case PrepKind::Ref
: return reduce_fpass_arg(env
,
2017 void in(ISS
& env
, const bc::FPassG
& op
) {
2018 switch (prepKind(env
, op
.arg1
)) {
2019 case PrepKind::Unknown
: popC(env
); return push(env
, TInitGen
);
2020 case PrepKind::Val
: return reduce_fpass_arg(env
,
2024 case PrepKind::Ref
: return reduce_fpass_arg(env
,
2031 void in(ISS
& env
, const bc::FPassS
& op
) {
2032 switch (prepKind(env
, op
.arg1
)) {
2033 case PrepKind::Unknown
:
2035 auto tcls
= takeClsRefSlot(env
, op
.slot
);
2036 auto const self
= selfCls(env
);
2037 auto const tname
= popC(env
);
2038 auto const vname
= tv(tname
);
2039 if (!self
|| tcls
.couldBe(*self
)) {
2040 if (vname
&& vname
->m_type
== KindOfPersistentString
) {
2041 // May or may not be boxing it, depending on the refiness.
2042 mergeSelfProp(env
, vname
->m_data
.pstr
, TInitGen
);
2047 if (auto c
= env
.collect
.publicStatics
) {
2048 c
->merge(env
.ctx
, std::move(tcls
), tname
, TInitGen
);
2051 return push(env
, TInitGen
);
2053 return reduce_fpass_arg(env
, bc::CGetS
{ op
.slot
}, op
.arg1
, false);
2055 return reduce_fpass_arg(env
, bc::VGetS
{ op
.slot
}, op
.arg1
, true);
2059 void in(ISS
& env
, const bc::FPassV
& op
) {
2061 switch (prepKind(env
, op
.arg1
)) {
2062 case PrepKind::Unknown
:
2064 return push(env
, TInitGen
);
2066 return reduce_fpass_arg(env
, bc::Unbox
{}, op
.arg1
, false);
2068 return reduce_fpass_arg(env
, bc::Nop
{}, op
.arg1
, true);
2072 void in(ISS
& env
, const bc::FPassR
& op
) {
2074 if (fpiTop(env
).kind
== FPIKind::Builtin
) {
2075 switch (prepKind(env
, op
.arg1
)) {
2076 case PrepKind::Unknown
:
2079 return reduce(env
, bc::UnboxR
{});
2081 return reduce(env
, bc::BoxR
{});
2085 auto const t1
= topT(env
);
2086 if (t1
.subtypeOf(TCell
)) {
2087 return reduce_fpass_arg(env
, bc::UnboxRNop
{}, op
.arg1
, false);
2090 // If it's known to be a ref, this behaves like FPassV, except we need to do
2091 // it slightly differently to keep stack flavors correct.
2092 if (t1
.subtypeOf(TRef
)) {
2093 switch (prepKind(env
, op
.arg1
)) {
2094 case PrepKind::Unknown
:
2096 return push(env
, TInitGen
);
2098 return reduce_fpass_arg(env
, bc::UnboxR
{}, op
.arg1
, false);
2100 return reduce_fpass_arg(env
, bc::BoxRNop
{}, op
.arg1
, true);
2105 // Here we don't know if it is going to be a cell or a ref.
2106 switch (prepKind(env
, op
.arg1
)) {
2107 case PrepKind::Unknown
: popR(env
); return push(env
, TInitGen
);
2108 case PrepKind::Val
: popR(env
); return push(env
, TInitCell
);
2109 case PrepKind::Ref
: popR(env
); return push(env
, TRef
);
2113 void in(ISS
& env
, const bc::FPassVNop
&) {
2114 push(env
, popV(env
));
2115 if (fpiTop(env
).kind
== FPIKind::Builtin
) {
2116 return reduce(env
, bc::Nop
{});
2121 void in(ISS
& env
, const bc::FPassC
& op
) {
2122 if (fpiTop(env
).kind
== FPIKind::Builtin
) {
2123 return reduce(env
, bc::Nop
{});
2128 void fpassCXHelper(ISS
& env
, int param
, bool error
) {
2129 auto const& fpi
= fpiTop(env
);
2130 if (fpi
.kind
== FPIKind::Builtin
) {
2131 switch (prepKind(env
, param
)) {
2132 case PrepKind::Unknown
:
2136 auto const& params
= fpi
.func
->exactFunc()->params
;
2137 if (param
>= params
.size() || params
[param
].mustBeRef
) {
2140 bc::String
{ s_byRefError
.get() },
2141 bc::Fatal
{ FatalOp::Runtime
});
2144 bc::String
{ s_byRefWarn
.get() },
2145 bc::Int
{ (int)ErrorMode::STRICT
},
2146 bc::FCallBuiltin
{ 2, 2, s_trigger_error
.get() },
2153 return reduce(env
, bc::Nop
{});
2157 switch (prepKind(env
, param
)) {
2158 case PrepKind::Unknown
: return;
2159 case PrepKind::Val
: return reduce(env
, bc::FPassC
{ param
});
2160 case PrepKind::Ref
: /* will warn/fatal at runtime */ return;
2164 void in(ISS
& env
, const bc::FPassCW
& op
) {
2165 fpassCXHelper(env
, op
.arg1
, false);
2168 void in(ISS
& env
, const bc::FPassCE
& op
) {
2169 fpassCXHelper(env
, op
.arg1
, true);
2172 void pushCallReturnType(ISS
& env
, Type
&& ty
) {
2173 if (ty
== TBottom
) {
2174 // The callee function never returns. It might throw, or loop forever.
2177 return push(env
, std::move(ty
));
2180 const StaticString s_defined
{ "defined" };
2181 const StaticString s_function_exists
{ "function_exists" };
2183 void fcallKnownImpl(ISS
& env
, uint32_t numArgs
) {
2184 auto const ar
= fpiPop(env
);
2185 always_assert(ar
.func
.hasValue());
2186 specialFunctionEffects(env
, ar
);
2188 if (ar
.func
->name()->isame(s_function_exists
.get())) {
2189 handle_function_exists(env
, numArgs
, false);
2192 std::vector
<Type
> args(numArgs
);
2193 for (auto i
= uint32_t{0}; i
< numArgs
; ++i
) {
2194 args
[numArgs
- i
- 1] = popF(env
);
2197 if (options
.HardConstProp
&&
2199 ar
.func
->name()->isame(s_defined
.get())) {
2200 // If someone calls defined('foo') they probably want foo to be
2201 // defined normally; ie not a persistent constant.
2202 if (auto const v
= tv(args
[0])) {
2203 if (isStringType(v
->m_type
) &&
2204 !env
.index
.lookup_constant(env
.ctx
, v
->m_data
.pstr
)) {
2205 env
.collect
.cnsMap
[v
->m_data
.pstr
].m_type
= kDynamicConstant
;
2210 auto ty
= env
.index
.lookup_return_type(
2211 CallContext
{ env
.ctx
, args
},
2214 if (!ar
.fallbackFunc
) {
2215 pushCallReturnType(env
, std::move(ty
));
2218 auto ty2
= env
.index
.lookup_return_type(
2219 CallContext
{ env
.ctx
, args
},
2222 pushCallReturnType(env
, union_of(std::move(ty
), std::move(ty2
)));
2225 void in(ISS
& env
, const bc::FCall
& op
) {
2226 auto const ar
= fpiTop(env
);
2227 if (ar
.func
&& !ar
.fallbackFunc
) {
2229 case FPIKind::Unknown
:
2230 case FPIKind::CallableArr
:
2231 case FPIKind::ObjInvoke
:
2234 // Don't turn dynamic calls into static calls with functions that can
2235 // potentially touch the caller's frame. Such functions will fatal if
2236 // called dynamically and we want to preserve that behavior.
2237 if (!ar
.func
->mightAccessCallerFrame()) {
2240 bc::FCallD
{ op
.arg1
, s_empty
.get(), ar
.func
->name() }
2244 case FPIKind::Builtin
:
2245 return finish_builtin(env
, ar
.func
->exactFunc(), op
.arg1
, false);
2248 * Need to be wary of old-style ctors. We could get into the situation
2249 * where we're constructing class D extends B, and B has an old-style
2250 * ctor but D::B also exists. (So in this case we'll skip the
2251 * fcallKnownImpl stuff.)
2253 if (!ar
.func
->name()->isame(s_construct
.get()) &&
2254 !ar
.func
->name()->isame(s_86ctor
.get())) {
2258 case FPIKind::ObjMeth
:
2259 case FPIKind::ClsMeth
:
2260 if (ar
.cls
.hasValue() && ar
.func
->cantBeMagicCall()) {
2263 bc::FCallD
{ op
.arg1
, ar
.cls
->name(), ar
.func
->name() }
2267 // If we didn't return a reduce above, we still can compute a
2268 // partially-known FCall effect with our res::Func.
2269 return fcallKnownImpl(env
, op
.arg1
);
2273 for (auto i
= uint32_t{0}; i
< op
.arg1
; ++i
) popF(env
);
2275 specialFunctionEffects(env
, ar
);
2276 push(env
, TInitGen
);
2279 void in(ISS
& env
, const bc::FCallD
& op
) {
2280 auto const ar
= fpiTop(env
);
2281 if (ar
.kind
== FPIKind::Builtin
) {
2282 return finish_builtin(env
, ar
.func
->exactFunc(), op
.arg1
, false);
2284 if (ar
.func
) return fcallKnownImpl(env
, op
.arg1
);
2285 specialFunctionEffects(env
, ar
);
2286 for (auto i
= uint32_t{0}; i
< op
.arg1
; ++i
) popF(env
);
2287 push(env
, TInitGen
);
2290 void in(ISS
& env
, const bc::FCallAwait
& op
) {
2291 in(env
, bc::FCallD
{ op
.arg1
, op
.str2
, op
.str3
});
2292 in(env
, bc::UnboxRNop
{ });
2293 in(env
, bc::Await
{ });
2295 env
.flags
.wasPEI
= true;
2296 env
.flags
.canConstProp
= false;
2299 void fcallArrayImpl(ISS
& env
, int arg
) {
2300 auto const ar
= fpiTop(env
);
2301 if (ar
.kind
== FPIKind::Builtin
) {
2302 return finish_builtin(env
, ar
.func
->exactFunc(), arg
, true);
2305 for (auto i
= uint32_t{0}; i
< arg
; ++i
) { popF(env
); }
2307 specialFunctionEffects(env
, ar
);
2309 auto ty
= env
.index
.lookup_return_type(env
.ctx
, *ar
.func
);
2310 if (!ar
.fallbackFunc
) {
2311 pushCallReturnType(env
, std::move(ty
));
2314 auto ty2
= env
.index
.lookup_return_type(env
.ctx
, *ar
.fallbackFunc
);
2315 pushCallReturnType(env
, union_of(std::move(ty
), std::move(ty2
)));
2318 return push(env
, TInitGen
);
2321 void in(ISS
& env
, const bc::FCallArray
& op
) {
2322 fcallArrayImpl(env
, 1);
2325 void in(ISS
& env
, const bc::FCallUnpack
& op
) {
2326 fcallArrayImpl(env
, op
.arg1
);
2329 void in(ISS
& env
, const bc::CufSafeArray
&) {
2330 popR(env
); popC(env
); popC(env
);
2334 void in(ISS
& env
, const bc::CufSafeReturn
&) {
2335 popR(env
); popC(env
); popC(env
);
2336 push(env
, TInitCell
);
2339 void in(ISS
& env
, const bc::DecodeCufIter
& op
) {
2341 env
.propagate(op
.target
, env
.state
); // before iter is modifed
2344 void in(ISS
& env
, const bc::IterInit
& op
) {
2345 auto const t1
= popC(env
);
2346 // Take the branch before setting locals if the iter is already
2347 // empty, but after popping. Similar for the other IterInits
2349 freeIter(env
, op
.iter1
);
2350 env
.propagate(op
.target
, env
.state
);
2351 if (t1
.subtypeOf(TArrE
)) {
2353 jmp_nofallthrough(env
);
2356 auto ity
= iter_types(t1
);
2357 setLoc(env
, op
.loc3
, ity
.second
);
2358 setIter(env
, op
.iter1
, TrackedIter
{ std::move(ity
) });
2361 void in(ISS
& env
, const bc::MIterInit
& op
) {
2363 env
.propagate(op
.target
, env
.state
);
2364 unbindLocalStatic(env
, op
.loc3
);
2365 setLocRaw(env
, op
.loc3
, TRef
);
2368 void in(ISS
& env
, const bc::IterInitK
& op
) {
2369 auto const t1
= popC(env
);
2370 freeIter(env
, op
.iter1
);
2371 env
.propagate(op
.target
, env
.state
);
2372 if (t1
.subtypeOf(TArrE
)) {
2374 jmp_nofallthrough(env
);
2377 auto ity
= iter_types(t1
);
2378 setLoc(env
, op
.loc3
, ity
.second
);
2379 setLoc(env
, op
.loc4
, ity
.first
);
2380 setIter(env
, op
.iter1
, TrackedIter
{ std::move(ity
) });
2383 void in(ISS
& env
, const bc::MIterInitK
& op
) {
2385 env
.propagate(op
.target
, env
.state
);
2386 unbindLocalStatic(env
, op
.loc3
);
2387 setLocRaw(env
, op
.loc3
, TRef
);
2388 setLoc(env
, op
.loc4
, TInitCell
);
2391 void in(ISS
& env
, const bc::WIterInit
& op
) {
2393 env
.propagate(op
.target
, env
.state
);
2394 // WIter* instructions may leave the value locals as either refs
2395 // or cells, depending whether the rhs of the assignment was a
2397 setLocRaw(env
, op
.loc3
, TInitGen
);
2400 void in(ISS
& env
, const bc::WIterInitK
& op
) {
2402 env
.propagate(op
.target
, env
.state
);
2403 setLocRaw(env
, op
.loc3
, TInitGen
);
2404 setLoc(env
, op
.loc4
, TInitCell
);
2407 void in(ISS
& env
, const bc::IterNext
& op
) {
2408 auto const curLoc3
= locRaw(env
, op
.loc3
);
2411 env
.state
.iters
[op
.iter1
],
2413 setLoc(env
, op
.loc3
, TInitCell
);
2415 [&] (const TrackedIter
& ti
) {
2416 setLoc(env
, op
.loc3
, ti
.kv
.second
);
2419 env
.propagate(op
.target
, env
.state
);
2421 freeIter(env
, op
.iter1
);
2422 if (curLoc3
.subtypeOf(TInitCell
)) setLocRaw(env
, op
.loc3
, curLoc3
);
2425 void in(ISS
& env
, const bc::MIterNext
& op
) {
2426 env
.propagate(op
.target
, env
.state
);
2427 unbindLocalStatic(env
, op
.loc3
);
2428 setLocRaw(env
, op
.loc3
, TRef
);
2431 void in(ISS
& env
, const bc::IterNextK
& op
) {
2432 auto const curLoc3
= locRaw(env
, op
.loc3
);
2433 auto const curLoc4
= locRaw(env
, op
.loc4
);
2436 env
.state
.iters
[op
.iter1
],
2438 setLoc(env
, op
.loc3
, TInitCell
);
2439 setLoc(env
, op
.loc4
, TInitCell
);
2441 [&] (const TrackedIter
& ti
) {
2442 setLoc(env
, op
.loc3
, ti
.kv
.second
);
2443 setLoc(env
, op
.loc4
, ti
.kv
.first
);
2446 env
.propagate(op
.target
, env
.state
);
2448 freeIter(env
, op
.iter1
);
2449 if (curLoc3
.subtypeOf(TInitCell
)) setLocRaw(env
, op
.loc3
, curLoc3
);
2450 if (curLoc4
.subtypeOf(TInitCell
)) setLocRaw(env
, op
.loc4
, curLoc4
);
2453 void in(ISS
& env
, const bc::MIterNextK
& op
) {
2454 env
.propagate(op
.target
, env
.state
);
2455 unbindLocalStatic(env
, op
.loc3
);
2456 setLocRaw(env
, op
.loc3
, TRef
);
2457 setLoc(env
, op
.loc4
, TInitCell
);
2460 void in(ISS
& env
, const bc::WIterNext
& op
) {
2461 env
.propagate(op
.target
, env
.state
);
2462 setLocRaw(env
, op
.loc3
, TInitGen
);
2465 void in(ISS
& env
, const bc::WIterNextK
& op
) {
2466 env
.propagate(op
.target
, env
.state
);
2467 setLocRaw(env
, op
.loc3
, TInitGen
);
2468 setLoc(env
, op
.loc4
, TInitCell
);
2471 void in(ISS
& env
, const bc::IterFree
& op
) {
2473 freeIter(env
, op
.iter1
);
2475 void in(ISS
& env
, const bc::MIterFree
& op
) {
2477 freeIter(env
, op
.iter1
);
2479 void in(ISS
& env
, const bc::CIterFree
& op
) {
2481 freeIter(env
, op
.iter1
);
2484 void in(ISS
& env
, const bc::IterBreak
& op
) {
2485 for (auto& kv
: op
.iterTab
) freeIter(env
, kv
.second
);
2486 env
.propagate(op
.target
, env
.state
);
2490 * Any include/require (or eval) op kills all locals, and private properties.
2492 * We don't need to do anything for collect.publicStatics because we'll analyze
2493 * the included pseudo-main separately and see any effects it may have on
2496 void inclOpImpl(ISS
& env
) {
2502 push(env
, TInitCell
);
2505 void in(ISS
& env
, const bc::Incl
&) { inclOpImpl(env
); }
2506 void in(ISS
& env
, const bc::InclOnce
&) { inclOpImpl(env
); }
2507 void in(ISS
& env
, const bc::Req
&) { inclOpImpl(env
); }
2508 void in(ISS
& env
, const bc::ReqOnce
&) { inclOpImpl(env
); }
2509 void in(ISS
& env
, const bc::ReqDoc
&) { inclOpImpl(env
); }
2510 void in(ISS
& env
, const bc::Eval
&) { inclOpImpl(env
); }
2512 void in(ISS
& env
, const bc::DefFunc
&) {}
2513 void in(ISS
& env
, const bc::DefCls
&) {}
2514 void in(ISS
& env
, const bc::DefClsNop
&) {}
2515 void in(ISS
& env
, const bc::AliasCls
&) {
2520 void in(ISS
& env
, const bc::DefCns
& op
) {
2521 auto const t
= popC(env
);
2522 if (options
.HardConstProp
) {
2523 auto const v
= tv(t
);
2524 auto const val
= v
&& tvAsCVarRef(&*v
).isAllowedAsConstantValue() ?
2525 *v
: make_tv
<KindOfUninit
>();
2526 auto const res
= env
.collect
.cnsMap
.emplace(op
.str1
, val
);
2528 if (res
.first
->second
.m_type
== kReadOnlyConstant
) {
2529 // we only saw a read of this constant
2530 res
.first
->second
= val
;
2532 // more than one definition in this function
2533 res
.first
->second
.m_type
= kDynamicConstant
;
2540 void in(ISS
& env
, const bc::DefTypeAlias
&) {}
2542 void in(ISS
& env
, const bc::This
&) {
2543 if (thisAvailable(env
)) {
2544 return reduce(env
, bc::BareThis
{ BareThisOp::NeverNull
});
2546 auto const ty
= thisType(env
);
2547 push(env
, ty
? *ty
: TObj
);
2548 setThisAvailable(env
);
2551 void in(ISS
& env
, const bc::LateBoundCls
& op
) {
2552 auto const ty
= selfCls(env
);
2553 putClsRefSlot(env
, op
.slot
, ty
? *ty
: TCls
);
2556 void in(ISS
& env
, const bc::CheckThis
&) {
2557 if (thisAvailable(env
)) {
2558 reduce(env
, bc::Nop
{});
2560 setThisAvailable(env
);
2563 void in(ISS
& env
, const bc::BareThis
& op
) {
2564 if (thisAvailable(env
)) {
2565 if (op
.subop1
!= BareThisOp::NeverNull
) {
2566 return reduce(env
, bc::BareThis
{ BareThisOp::NeverNull
});
2570 auto const ty
= thisType(env
);
2571 switch (op
.subop1
) {
2572 case BareThisOp::Notice
:
2574 case BareThisOp::NoNotice
:
2577 case BareThisOp::NeverNull
:
2579 setThisAvailable(env
);
2580 return push(env
, ty
? *ty
: TObj
);
2583 push(env
, ty
? opt(*ty
) : TOptObj
);
2586 void in(ISS
& env
, const bc::InitThisLoc
& op
) {
2587 setLocRaw(env
, op
.loc1
, TCell
);
2590 folly::Optional
<Cell
> staticLocHelper(ISS
& env
, LocalId l
, Type init
) {
2591 unbindLocalStatic(env
, l
);
2592 setLocRaw(env
, l
, TRef
);
2593 bindLocalStatic(env
, l
, std::move(init
));
2594 if (!env
.ctx
.func
->isClosureBody
&&
2595 env
.collect
.localStaticTypes
.size() > l
) {
2596 auto t
= env
.collect
.localStaticTypes
[l
];
2597 if (auto v
= tv(t
)) {
2598 useLocalStatic(env
, l
);
2599 setLocRaw(env
, l
, t
);
2606 void in(ISS
& env
, const bc::StaticLoc
& op
) {
2607 if (auto const v
= staticLocHelper(env
, op
.loc1
, TBottom
)) {
2610 bc::SetL
{ op
.loc1
}, bc::PopC
{},
2616 void in(ISS
& env
, const bc::StaticLocInit
& op
) {
2617 if (staticLocHelper(env
, op
.loc1
, topC(env
))) {
2618 return reduce(env
, bc::SetL
{ op
.loc1
}, bc::PopC
{});
2624 * Amongst other things, we use this to mark units non-persistent.
2626 void in(ISS
& env
, const bc::OODeclExists
& op
) {
2627 auto flag
= popC(env
);
2628 auto name
= popC(env
);
2630 if (!name
.strictSubtypeOf(TStr
)) return TBool
;
2631 auto const v
= tv(name
);
2632 if (!v
) return TBool
;
2633 auto rcls
= env
.index
.resolve_class(env
.ctx
, v
->m_data
.pstr
);
2634 if (!rcls
|| !rcls
->cls()) return TBool
;
2635 auto const mayExist
= [&] () -> bool {
2636 switch (op
.subop1
) {
2637 case OODeclExistsOp::Class
:
2638 return !(rcls
->cls()->attrs
& (AttrInterface
| AttrTrait
));
2639 case OODeclExistsOp::Interface
:
2640 return rcls
->cls()->attrs
& AttrInterface
;
2641 case OODeclExistsOp::Trait
:
2642 return rcls
->cls()->attrs
& AttrTrait
;
2646 auto unit
= rcls
->cls()->unit
;
2647 auto canConstProp
= [&] {
2648 // Its generally not safe to constprop this, because of
2649 // autoload. We're safe if its part of systemlib, or a
2650 // superclass of the current context.
2651 if (is_systemlib_part(*unit
)) return true;
2652 if (!env
.ctx
.cls
) return false;
2653 auto thisClass
= env
.index
.resolve_class(env
.ctx
.cls
);
2654 return thisClass
.subtypeOf(*rcls
);
2656 if (canConstProp()) {
2658 return mayExist
? TTrue
: TFalse
;
2660 unit
->persistent
.store(false, std::memory_order_relaxed
);
2661 // At this point, if it mayExist, we still don't know that it
2662 // *does* exist, but if not we know that it either doesn't
2663 // exist, or it doesn't have the right type.
2664 return mayExist
? TBool
: TFalse
;
2668 void in(ISS
& env
, const bc::VerifyParamType
& op
) {
2669 locAsCell(env
, op
.loc1
);
2670 if (!options
.HardTypeHints
) return;
2673 * In HardTypeHints mode, we assume that if this opcode doesn't
2674 * throw, the parameter was of the specified type (although it may
2675 * have been a Ref if the parameter was by reference).
2677 * The env.setLoc here handles dealing with a parameter that was
2678 * already known to be a reference.
2680 * NB: VerifyParamType of a reference parameter can kill any
2681 * references if it re-enters, even if Option::HardTypeHints is
2684 auto const constraint
= env
.ctx
.func
->params
[op
.loc1
].typeConstraint
;
2685 if (constraint
.hasConstraint() && !constraint
.isTypeVar() &&
2686 !constraint
.isTypeConstant()) {
2687 FTRACE(2, " {}\n", constraint
.fullName());
2688 auto t
= env
.index
.lookup_constraint(env
.ctx
, constraint
);
2689 if (t
.subtypeOf(TBottom
)) unreachable(env
);
2690 setLoc(env
, op
.loc1
, std::move(t
));
2694 void in(ISS
& env
, const bc::VerifyRetTypeV
& op
) {}
2696 void in(ISS
& env
, const bc::VerifyRetTypeC
& op
) {
2697 auto const constraint
= env
.ctx
.func
->retTypeConstraint
;
2698 auto const stackT
= topC(env
);
2700 // If there is no return type constraint, or if the return type
2701 // constraint is a typevar, or if the top of stack is the same
2702 // or a subtype of the type constraint, then this is a no-op.
2703 if (env
.index
.satisfies_constraint(env
.ctx
, stackT
, constraint
)) {
2704 reduce(env
, bc::Nop
{});
2708 // If HardReturnTypeHints is false OR if the constraint is soft,
2709 // then there are no optimizations we can safely do here, so
2710 // just leave the top of stack as is.
2711 if (!options
.HardReturnTypeHints
|| constraint
.isSoft()) {
2715 // If we reach here, then HardReturnTypeHints is true AND the constraint
2716 // is not soft. We can safely assume that either VerifyRetTypeC will
2717 // throw or it will produce a value whose type is compatible with the
2718 // return type constraint.
2720 remove_uninit(env
.index
.lookup_constraint(env
.ctx
, constraint
));
2722 if (tcT
.subtypeOf(TBottom
)) {
2727 // Below we compute retT, which is a rough conservative approximate of the
2728 // intersection of stackT and tcT.
2729 // TODO(4441939): We could do better if we had an intersect_of() function
2730 // that provided a formal way to compute the intersection of two Types.
2732 // If tcT could be an interface or trait, we upcast it to TObj/TOptObj.
2733 // Why? Because we want uphold the invariant that we only refine return
2734 // types and never widen them, and if we allow tcT to be an interface then
2735 // it's possible for violations of this invariant to arise. For an example,
2736 // see "hphp/test/slow/hhbbc/return-type-opt-bug.php".
2737 // Note: It's safe to use TObj/TOptObj because lookup_constraint() only
2738 // returns classes or interfaces or traits (it never returns something that
2739 // could be an enum or type alias) and it never returns anything that could
2740 // be a "magic" interface that supports non-objects. (For traits the return
2741 // typehint will always throw at run time, so it's safe to use TObj/TOptObj.)
2742 if (is_specialized_obj(tcT
) && dobj_of(tcT
).cls
.couldBeInterfaceOrTrait()) {
2743 tcT
= is_opt(tcT
) ? TOptObj
: TObj
;
2745 // If stackT is a subtype of tcT, use stackT. Otherwise, if tc is an opt
2746 // type and stackT cannot be InitNull, then we can safely use unopt(tcT).
2747 // In all other cases, use tcT.
2748 auto retT
= stackT
.subtypeOf(tcT
) ? stackT
:
2749 is_opt(tcT
) && !stackT
.couldBe(TInitNull
) ? unopt(tcT
) :
2752 // Update the top of stack with the rough conservative approximate of the
2753 // intersection of stackT and tcT
2755 push(env
, std::move(retT
));
2758 // These only occur in traits, so we don't need to do better than
2760 void in(ISS
& env
, const bc::Self
& op
) {
2761 auto self
= selfClsExact(env
);
2762 putClsRefSlot(env
, op
.slot
, self
? *self
: TCls
);
2765 void in(ISS
& env
, const bc::Parent
& op
) {
2766 auto parent
= parentClsExact(env
);
2767 putClsRefSlot(env
, op
.slot
, parent
? *parent
: TCls
);
2770 void in(ISS
& env
, const bc::CreateCl
& op
) {
2771 auto const nargs
= op
.arg1
;
2772 auto const clsPair
= env
.index
.resolve_closure_class(env
.ctx
, op
.arg2
);
2775 * Every closure should have a unique allocation site, but we may see it
2776 * multiple times in a given round of analyzing this function. Each time we
2777 * may have more information about the used variables; the types should only
2778 * possibly grow. If it's already there we need to merge the used vars in
2779 * with what we saw last time.
2782 std::vector
<Type
> usedVars(nargs
);
2783 for (auto i
= uint32_t{0}; i
< nargs
; ++i
) {
2784 usedVars
[nargs
- i
- 1] = popT(env
);
2786 merge_closure_use_vars_into(
2787 env
.collect
.closureUseTypes
,
2793 // Closure classes can be cloned and rescoped at runtime, so it's not safe to
2794 // assert the exact type of closure objects. The best we can do is assert
2795 // that it's a subclass of Closure.
2796 auto const closure
= env
.index
.resolve_class(env
.ctx
, s_Closure
.get());
2797 always_assert(closure
.hasValue());
2799 return push(env
, subObj(*closure
));
2802 void in(ISS
& env
, const bc::CreateCont
& op
) {
2803 // First resume is always next() which pushes null.
2804 push(env
, TInitNull
);
2807 void in(ISS
& env
, const bc::ContEnter
&) { popC(env
); push(env
, TInitCell
); }
2808 void in(ISS
& env
, const bc::ContRaise
&) { popC(env
); push(env
, TInitCell
); }
2810 void in(ISS
& env
, const bc::Yield
&) {
2812 push(env
, TInitCell
);
2815 void in(ISS
& env
, const bc::YieldK
&) {
2818 push(env
, TInitCell
);
2821 void in(ISS
& env
, const bc::ContAssignDelegate
&) {
2825 void in(ISS
& env
, const bc::ContEnterDelegate
&) {
2829 void in(ISS
& env
, const bc::YieldFromDelegate
&) {
2830 push(env
, TInitCell
);
2833 void in(ISS
& env
, const bc::ContUnsetDelegate
&) {}
2835 void in(ISS
& env
, const bc::ContCheck
&) {}
2836 void in(ISS
& env
, const bc::ContValid
&) { push(env
, TBool
); }
2837 void in(ISS
& env
, const bc::ContStarted
&) { push(env
, TBool
); }
2838 void in(ISS
& env
, const bc::ContKey
&) { push(env
, TInitCell
); }
2839 void in(ISS
& env
, const bc::ContCurrent
&) { push(env
, TInitCell
); }
2840 void in(ISS
& env
, const bc::ContGetReturn
&) { push(env
, TInitCell
); }
2842 void pushTypeFromWH(ISS
& env
, const Type t
) {
2843 if (!t
.couldBe(TObj
)) {
2844 // These opcodes require an object descending from WaitHandle.
2845 // Exceptions will be thrown for any non-object.
2850 // If we aren't even sure this is a wait handle, there's nothing we can
2851 // infer here. (This can happen if a user declares a class with a
2852 // getWaitHandle method that returns non-WaitHandle garbage.)
2853 if (!t
.subtypeOf(TObj
) || !is_specialized_wait_handle(t
)) {
2854 return push(env
, TInitCell
);
2857 auto inner
= wait_handle_inner(t
);
2858 if (inner
.subtypeOf(TBottom
)) {
2859 // If it's a WaitH<Bottom>, we know it's going to throw an exception, and
2860 // the fallthrough code is not reachable.
2866 push(env
, std::move(inner
));
2869 void in(ISS
& env
, const bc::WHResult
&) {
2870 pushTypeFromWH(env
, popC(env
));
2873 void in(ISS
& env
, const bc::Await
&) {
2874 pushTypeFromWH(env
, popC(env
));
2877 void in(ISS
& env
, const bc::IncStat
&) {}
2879 void in(ISS
& env
, const bc::Idx
&) {
2880 popC(env
); popC(env
); popC(env
);
2881 push(env
, TInitCell
);
2884 void in(ISS
& env
, const bc::ArrayIdx
&) {
2885 popC(env
); popC(env
); popC(env
);
2886 push(env
, TInitCell
);
2889 void in(ISS
& env
, const bc::CheckProp
&) {
2890 if (env
.ctx
.cls
->attrs
& AttrNoOverride
) {
2891 return reduce(env
, bc::False
{});
2897 void in(ISS
& env
, const bc::InitProp
& op
) {
2898 auto const t
= topC(env
);
2899 switch (op
.subop2
) {
2900 case InitPropOp::Static
:
2901 mergeSelfProp(env
, op
.str1
, t
);
2902 if (auto c
= env
.collect
.publicStatics
) {
2903 auto const cls
= selfClsExact(env
);
2904 always_assert(!!cls
);
2905 c
->merge(env
.ctx
, *cls
, sval(op
.str1
), t
);
2908 case InitPropOp::NonStatic
:
2909 mergeThisProp(env
, op
.str1
, t
);
2912 if (auto const v
= tv(t
)) {
2913 for (auto& prop
: env
.ctx
.func
->cls
->properties
) {
2914 if (prop
.name
== op
.str1
) {
2915 ITRACE(1, "InitProp: {} = {}\n", op
.str1
, show(t
));
2917 if (op
.subop2
== InitPropOp::Static
&&
2918 !env
.collect
.publicStatics
&&
2919 !env
.index
.frozen()) {
2920 env
.index
.fixup_public_static(env
.ctx
.func
->cls
, prop
.name
, t
);
2922 return reduce(env
, bc::PopC
{});
2929 void in(ISS
& env
, const bc::Silence
& op
) {
2931 switch (op
.subop2
) {
2932 case SilenceOp::Start
:
2933 setLoc(env
, op
.loc1
, TInt
);
2935 case SilenceOp::End
:
2940 void in(ISS
& emv
, const bc::VarEnvDynCall
&) {}
2944 //////////////////////////////////////////////////////////////////////
2946 void dispatch(ISS
& env
, const Bytecode
& op
) {
2947 #define O(opcode, ...) case Op::opcode: interp_step::in(env, op.opcode); return;
2948 switch (op
.op
) { OPCODES
}
2953 //////////////////////////////////////////////////////////////////////
2955 template<class Iterator
, class... Args
>
2956 void group(ISS
& env
, Iterator
& it
, Args
&&... args
) {
2957 FTRACE(2, " {}\n", [&]() -> std::string
{
2958 auto ret
= std::string
{};
2959 for (auto i
= size_t{0}; i
< sizeof...(Args
); ++i
) {
2960 ret
+= " " + show(env
.ctx
.func
, it
[i
]);
2961 if (i
!= sizeof...(Args
) - 1) ret
+= ';';
2965 it
+= sizeof...(Args
);
2966 return interp_step::group(env
, std::forward
<Args
>(args
)...);
2969 template<class Iterator
>
2970 void interpStep(ISS
& env
, Iterator
& it
, Iterator stop
) {
2972 * During the analysis phase, we analyze some common bytecode
2973 * patterns involving conditional jumps as groups to be able to
2974 * add additional information to the type environment depending on
2975 * whether the branch is taken or not.
2977 auto const o1
= it
->op
;
2978 auto const o2
= it
+ 1 != stop
? it
[1].op
: Op::Nop
;
2979 auto const o3
= it
+ 1 != stop
&&
2980 it
+ 2 != stop
? it
[2].op
: Op::Nop
;
2985 case Op::JmpZ
: return group(env
, it
, it
[0].CGetL
, it
[1].JmpZ
);
2986 case Op::JmpNZ
: return group(env
, it
, it
[0].CGetL
, it
[1].JmpNZ
);
2987 case Op::InstanceOfD
:
2990 return group(env
, it
, it
[0].CGetL
, it
[1].InstanceOfD
, it
[2].JmpZ
);
2992 return group(env
, it
, it
[0].CGetL
, it
[1].InstanceOfD
, it
[2].JmpNZ
);
2996 case Op::FPushObjMethodD
:
2997 return group(env
, it
, it
[0].CGetL
, it
[1].FPushObjMethodD
);
3003 case Op::JmpZ
: return group(env
, it
, it
[0].IsTypeL
, it
[1].JmpZ
);
3004 case Op::JmpNZ
: return group(env
, it
, it
[0].IsTypeL
, it
[1].JmpNZ
);
3010 case Op::JmpZ
: return group(env
, it
, it
[0].IsUninit
, it
[1].JmpZ
);
3011 case Op::JmpNZ
: return group(env
, it
, it
[0].IsUninit
, it
[1].JmpNZ
);
3020 return group(env
, it
, it
[0].Dup
, it
[1].IsTypeC
, it
[2].JmpZ
);
3022 return group(env
, it
, it
[0].Dup
, it
[1].IsTypeC
, it
[2].JmpNZ
);
3034 return group(env
, it
, it
[0].MemoGet
, it
[1].IsUninit
, it
[2].JmpZ
);
3036 return group(env
, it
, it
[0].MemoGet
, it
[1].IsUninit
, it
[2].JmpNZ
);
3046 FTRACE(2, " {}\n", show(env
.ctx
.func
, *it
));
3047 dispatch(env
, *it
++);
3050 template<class Iterator
>
3051 StepFlags
interpOps(Interp
& interp
,
3052 Iterator
& iter
, Iterator stop
,
3053 PropagateFn propagate
) {
3054 auto flags
= StepFlags
{};
3055 ISS env
{ interp
, flags
, propagate
};
3057 // If there are factored edges, make a copy of the state (except
3058 // stacks) in case we need to propagate across factored exits (if
3060 auto const stateBefore
= interp
.blk
->factoredExits
.empty()
3062 : without_stacks(interp
.state
);
3064 auto const numPushed
= iter
->numPush();
3065 interpStep(env
, iter
, stop
);
3067 auto outputs_constant
= [&] {
3068 auto elems
= &interp
.state
.stack
.back();
3069 for (auto i
= size_t{0}; i
< numPushed
; ++i
, --elems
) {
3070 if (!tv(elems
->type
)) return false;
3075 if (options
.ConstantProp
&& flags
.canConstProp
&& outputs_constant()) {
3076 auto elems
= &interp
.state
.stack
.back();
3077 for (auto i
= size_t{0}; i
< numPushed
; ++i
, --elems
) {
3078 auto& ty
= elems
->type
;
3079 ty
= from_cell(*tv(ty
));
3082 FTRACE(2, " nothrow (due to constprop)\n");
3083 flags
.wasPEI
= false;
3088 FTRACE(2, " PEI.\n");
3089 for (auto factored
: interp
.blk
->factoredExits
) {
3090 propagate(factored
, stateBefore
);
3096 //////////////////////////////////////////////////////////////////////
3098 RunFlags
run(Interp
& interp
, PropagateFn propagate
) {
3100 FTRACE(2, "out {}\n",
3101 state_string(*interp
.ctx
.func
, interp
.state
, interp
.collect
));
3104 auto ret
= RunFlags
{};
3105 auto const stop
= end(interp
.blk
->hhbcs
);
3106 auto iter
= begin(interp
.blk
->hhbcs
);
3107 while (iter
!= stop
) {
3108 auto const flags
= interpOps(interp
, iter
, stop
, propagate
);
3109 if (flags
.usedLocalStatics
) {
3110 if (!ret
.usedLocalStatics
) {
3111 ret
.usedLocalStatics
= std::move(flags
.usedLocalStatics
);
3113 for (auto& elm
: *flags
.usedLocalStatics
) {
3114 ret
.usedLocalStatics
->insert(std::move(elm
));
3119 if (interp
.state
.unreachable
) {
3120 FTRACE(2, " <bytecode fallthrough is unreachable>\n");
3124 switch (flags
.jmpFlag
) {
3125 case StepFlags::JmpFlags::Taken
:
3126 FTRACE(2, " <took branch; no fallthrough>\n");
3128 case StepFlags::JmpFlags::Fallthrough
:
3129 case StepFlags::JmpFlags::Either
:
3132 if (flags
.returned
) {
3133 FTRACE(2, " returned {}\n", show(*flags
.returned
));
3134 always_assert(iter
== stop
);
3135 always_assert(interp
.blk
->fallthrough
== NoBlockId
);
3136 ret
.returned
= flags
.returned
;
3141 FTRACE(2, " <end block>\n");
3142 if (interp
.blk
->fallthrough
!= NoBlockId
) {
3143 propagate(interp
.blk
->fallthrough
, interp
.state
);
3148 StepFlags
step(Interp
& interp
, const Bytecode
& op
) {
3149 auto flags
= StepFlags
{};
3150 auto noop
= [] (BlockId
, const State
&) {};
3151 ISS env
{ interp
, flags
, noop
};
3156 void default_dispatch(ISS
& env
, const Bytecode
& op
) {
3160 //////////////////////////////////////////////////////////////////////