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 +----------------------------------------------------------------------+
17 #include "hphp/runtime/vm/verifier/check.h"
19 #include "hphp/runtime/base/repo-auth-type-codec.h"
20 #include "hphp/runtime/base/mixed-array.h"
21 #include "hphp/runtime/vm/native.h"
23 #include "hphp/runtime/vm/verifier/cfg.h"
24 #include "hphp/runtime/vm/verifier/util.h"
25 #include "hphp/runtime/vm/verifier/pretty.h"
26 #include <folly/Range.h>
28 #include <boost/dynamic_bitset.hpp>
41 * State for one entry on the FPI stack which corresponds to one call-site
45 Offset fpush
; // offset of fpush (can get num_params from this)
46 int stkmin
; // stklen before FPush
47 int next
; // next expected param number
48 bool operator==(const FpiState
& s
) const {
49 return fpush
== s
.fpush
&& stkmin
== s
.stkmin
&& next
== s
.next
;
51 bool operator!=(const FpiState
& s
) const {
57 * Facts about a Func's current frame at various program points
60 FlavorDesc
* stk
; // Evaluation stack.
61 FpiState
* fpi
; // FPI stack.
62 bool* iters
; // defined/not-defined state of each iter var.
63 boost::dynamic_bitset
<> clsRefSlots
; // init state of class-ref slots
64 int stklen
; // length of evaluation stack.
65 int fpilen
; // length of FPI stack.
66 bool mbr_live
; // liveness of member base register
67 folly::Optional
<MOpMode
> mbr_mode
; // mode of member base register
71 * Facts about a specific block.
74 State state_in
; // state at the start of the block
83 FuncChecker(const Func
* func
, ErrorMode mode
);
88 struct unknown_length
: std::runtime_error
{
90 : std::runtime_error("Unknown instruction length")
94 bool checkEdge(Block
* b
, const State
& cur
, Block
* t
);
95 bool checkSuccEdges(Block
* b
, State
* cur
);
96 bool checkOffset(const char* name
, Offset o
, const char* regionName
,
97 Offset base
, Offset past
, bool check_instrs
= true);
98 bool checkRegion(const char* name
, Offset b
, Offset p
,
99 const char* regionName
, Offset base
, Offset past
,
100 bool check_instrs
= true);
101 bool checkSection(bool main
, const char* name
, Offset base
, Offset past
);
102 bool checkImmediates(const char* name
, PC instr
);
103 bool checkImmVec(PC
& pc
, size_t elemSize
);
104 #define ARGTYPE(name, type) bool checkImm##name(PC& pc, PC instr);
105 #define ARGTYPEVEC(name, type) ARGTYPE(name, type)
109 bool checkOp(State
*, PC
, Op
, Block
*);
110 template<typename Subop
> bool checkImmOAImpl(PC
& pc
, PC instr
);
111 bool checkMemberKey(State
* cur
, PC
, Op
);
112 bool checkInputs(State
* cur
, PC
, Block
* b
);
113 bool checkOutputs(State
* cur
, PC
, Block
* b
);
114 bool checkSig(PC pc
, int len
, const FlavorDesc
* args
, const FlavorDesc
* sig
);
115 bool checkEHStack(const EHEnt
&, Block
* b
);
116 bool checkTerminal(State
* cur
, PC pc
);
117 bool checkFpi(State
* cur
, PC pc
, Block
* b
);
118 bool checkIter(State
* cur
, PC pc
);
119 bool checkClsRefSlots(State
* cur
, PC pc
);
120 bool checkIterBreak(State
* cur
, PC pc
);
121 bool checkLocal(PC pc
, int val
);
122 bool checkString(PC pc
, Id id
);
123 void reportStkUnderflow(Block
*, const State
& cur
, PC
);
124 void reportStkOverflow(Block
*, const State
& cur
, PC
);
125 void reportStkMismatch(Block
* b
, Block
* target
, const State
& cur
);
126 void reportEscapeEdge(Block
* b
, Block
* s
);
127 std::string
stateToString(const State
& cur
) const;
128 std::string
sigToString(int len
, const FlavorDesc
* sig
) const;
129 std::string
stkToString(int len
, const FlavorDesc
* args
) const;
130 std::string
fpiToString(const FpiState
&) const;
131 std::string
iterToString(const State
& cur
) const;
132 std::string
slotsToString(const boost::dynamic_bitset
<>&) const;
133 void copyState(State
* to
, const State
* from
);
134 void initState(State
* s
);
135 const FlavorDesc
* sig(PC pc
);
136 Offset
offset(PC pc
) const { return pc
- unit()->entry(); }
137 PC
at(Offset off
) const { return unit()->at(off
); }
138 int maxStack() const { return m_func
->maxStackCells(); }
139 int maxFpi() const { return m_func
->fpitab().size(); }
140 int numIters() const { return m_func
->numIterators(); }
141 int numLocals() const { return m_func
->numLocals(); }
142 int numParams() const { return m_func
->numParams(); }
143 int numClsRefSlots() const { return m_func
->numClsRefSlots(); }
144 const Unit
* unit() const { return m_func
->unit(); }
145 folly::Range
<const IterKindId
*> iterBreakIds(PC pc
) const;
148 template<class... Args
>
149 void error(const char* fmt
, Args
&&... args
) {
155 std::forward
<Args
>(args
)...
158 template<class... Args
>
159 void ferror(Args
&&... args
) {
160 verify_error(unit(), m_func
, m_errmode
== kThrow
, "%s",
161 folly::sformat(std::forward
<Args
>(args
)...).c_str());
166 BlockInfo
* m_info
; // one per block
167 const Func
* const m_func
;
171 FlavorDesc
* m_tmp_sig
;
174 bool checkNativeFunc(const Func
* func
, ErrorMode mode
) {
175 auto const funcname
= func
->displayName();
176 auto const pc
= func
->preClass();
177 auto const clsname
= pc
? pc
->name() : nullptr;
178 auto const& info
= Native::GetBuiltinFunction(funcname
, clsname
,
181 if (func
->builtinFuncPtr() == Native::unimplementedWrapper
) return true;
183 if (func
->isAsync()) {
184 verify_error(func
->unit(), func
, mode
== kThrow
,
185 "<<__Native>> function %s%s%s is declared async; <<__Native>> functions "
186 "can return Awaitable<T>, but can not be declared async.\n",
187 clsname
? clsname
->data() : "",
194 auto const& tc
= func
->returnTypeConstraint();
195 auto const message
= Native::checkTypeFunc(info
.sig
, tc
, func
);
198 auto const tstr
= info
.sig
.toString(clsname
? clsname
->data() : nullptr,
201 verify_error(func
->unit(), func
, mode
== kThrow
,
202 "<<__Native>> function %s%s%s does not match C++ function "
203 "signature (%s): %s\n",
204 clsname
? clsname
->data() : "",
216 bool checkFunc(const Func
* func
, ErrorMode mode
) {
217 if (mode
== kVerbose
) {
218 func
->prettyPrint(std::cout
);
219 if (func
->cls() || !func
->preClass()) {
220 printf(" FuncId %d\n", func
->getFuncId());
224 FuncChecker
v(func
, mode
);
225 return v
.checkOffsets() &&
229 FuncChecker::FuncChecker(const Func
* f
, ErrorMode mode
)
232 , m_instrs(m_arena
, f
->past() - f
->base() + 1)
236 // Needs to be a sorted map so we can divide funcs into contiguous sections.
237 using SectionMap
= std::map
<Offset
,Offset
>;
240 * Return the start offset of the nearest enclosing section. Caller must
241 * ensure that off is at least within the entire func's bytecode region.
243 Offset
findSection(SectionMap
& sections
, Offset off
) {
244 assert(!sections
.empty());
245 SectionMap::iterator i
= sections
.upper_bound(off
);
251 * Make sure all offsets are in-bounds. Offset 0 is unit->m_bc,
252 * for all functions. Jump instructions use an Offset relative to
253 * the start of the jump instruction.
255 bool FuncChecker::checkOffsets() {
257 assert(unit()->bclen() >= 0);
258 PC bc
= unit()->entry();
259 Offset base
= m_func
->base();
260 Offset past
= m_func
->past();
261 checkRegion("func", base
, past
, "unit", 0, unit()->bclen(), false);
262 // find instruction boundaries and make sure no branches escape
264 for (auto& eh
: m_func
->ehtab()) {
265 if (eh
.m_type
== EHEnt::Type::Fault
) {
266 ok
&= checkOffset("fault funclet", eh
.m_handler
, "func bytecode", base
,
268 sections
[eh
.m_handler
] = 0;
271 Offset funclets
= !sections
.empty() ? sections
.begin()->first
: past
;
272 sections
[base
] = funclets
; // primary body
273 // Get instruction boundaries and check branches within primary body
274 // and each faultlet.
275 for (auto i
= sections
.begin(), end
= sections
.end(); i
!= end
;) {
276 Offset section_base
= i
->first
; ++i
;
277 Offset section_past
= i
== end
? past
: i
->first
;
278 sections
[section_base
] = section_past
;
279 ok
&= checkSection(section_base
== base
,
280 section_base
== base
? "primary body" : "funclet body",
281 section_base
, section_past
);
283 // DV entry points must be in the primary function body
284 for (auto& param
: m_func
->params()) {
285 if (param
.hasDefaultValue()) {
286 ok
&= checkOffset("dv-entry", param
.funcletOff
, "func body", base
,
290 // Every FPI region must be contained within one section, either the
291 // primary body or one fault funclet
292 for (auto& fpi
: m_func
->fpitab()) {
293 Offset fpi_base
= fpiBase(fpi
, bc
);
294 Offset fpi_past
= fpiPast(fpi
, bc
);
295 if (checkRegion("fpi", fpi_base
, fpi_past
, "func", base
, past
)) {
296 // FPI is within whole func, but we also need to check within the section
297 Offset section_base
= findSection(sections
, fpi_base
);
298 Offset section_past
= sections
[section_base
];
299 ok
&= checkRegion("fpi", fpi_base
, fpi_past
,
300 section_base
== base
? "func body" : "funclet",
301 section_base
, section_past
);
306 // check EH regions and targets
307 for (auto& eh
: m_func
->ehtab()) {
308 if (eh
.m_type
== EHEnt::Type::Fault
) {
309 ok
&= checkOffset("fault", eh
.m_handler
, "funclets", funclets
, past
);
316 * Scan instructions in the given section to find valid instruction
317 * boundaries, and check that branches a) land on valid boundaries,
318 * b) do not escape the section.
320 bool FuncChecker::checkSection(bool is_main
, const char* name
, Offset base
,
323 typedef std::list
<PC
> BranchList
;
325 PC bc
= unit()->entry();
326 // Find instruction boundaries and branch instructions.
327 for (InstrRange
i(at(base
), at(past
)); !i
.empty();) {
328 auto pc
= i
.popFront();
329 auto const op
= peek_op(pc
);
330 if (!checkImmediates(name
, pc
)) {
331 ferror("checkImmediates failed for {} @ {}\n",
332 opcodeToName(op
), offset(pc
));
335 m_instrs
.set(offset(pc
) - m_func
->base());
337 instrJumpTarget(bc
, offset(pc
)) != InvalidAbsoluteOffset
) {
338 if (op
== OpSwitch
&& getImm(pc
, 0).u_IVA
== int(SwitchKind::Bounded
)) {
339 int64_t switchBase
= getImm(pc
, 1).u_I64A
;
340 int32_t len
= getImmVector(pc
).size();
342 error("Bounded switch must have a vector of length > 2 [%d:%d]\n",
345 if (switchBase
> std::numeric_limits
<int64_t>::max() - len
+ 2) {
346 error("Overflow in Switch bounds [%d:%d]\n", base
, past
);
348 } else if (op
== Op::SSwitch
) {
349 foreachSSwitchString(pc
, [&](Id id
) {
350 ok
&= checkString(pc
, id
);
353 branches
.push_back(pc
);
356 if (offset(pc
+ instrLen(pc
)) != past
) {
357 error("Last instruction in %s at %d overflows [%d:%d]\n",
358 name
, offset(pc
), base
, past
);
361 if ((instrFlags(op
) & TF
) == 0) {
362 error("Last instruction in %s is not terminal %d:%s\n",
363 name
, offset(pc
), instrToString(pc
, unit()).c_str());
366 if (isRet(pc
) && !is_main
) {
367 error("Ret* may not appear in %s\n", name
);
369 } else if (op
== Op::Unwind
&& is_main
) {
370 error("Unwind may not appear in %s\n", name
);
376 // Check each branch target lands on a valid instruction boundary
377 // within this region.
378 for (auto branch
: branches
) {
379 if (isSwitch(peek_op(branch
))) {
380 foreachSwitchTarget(branch
, [&](Offset o
) {
381 ok
&= checkOffset("switch target", offset(branch
+ o
),
385 Offset target
= instrJumpTarget(bc
, offset(branch
));
386 ok
&= checkOffset("branch target", target
, name
, base
, past
);
387 if (peek_op(branch
) == Op::JmpNS
&& target
== offset(branch
)) {
388 error("JmpNS may not have zero offset in %s\n", name
);
396 Id
decodeId(PC
* ppc
) {
402 Offset
decodeOffset(PC
* ppc
) {
403 Offset offset
= *(Offset
*)*ppc
;
404 *ppc
+= sizeof(Offset
);
408 bool FuncChecker::checkLocal(PC pc
, int k
) {
409 if (k
< 0 || k
>= numLocals()) {
410 error("invalid local variable id %d at Offset %d\n",
417 bool FuncChecker::checkString(PC
/*pc*/, Id id
) {
418 return unit()->isLitstrId(id
);
421 bool FuncChecker::checkImmVec(PC
& pc
, size_t elemSize
) {
422 auto const len
= decode_raw
<int32_t>(pc
);
424 error("invalid length of immediate vector %d at Offset %d\n",
426 throw unknown_length
{};
429 pc
+= len
* elemSize
;
433 bool FuncChecker::checkImmBLA(PC
& pc
, PC
const /*instr*/) {
434 return checkImmVec(pc
, sizeof(Offset
));
437 bool FuncChecker::checkImmSLA(PC
& pc
, PC
const /*instr*/) {
438 return checkImmVec(pc
, sizeof(Id
) + sizeof(Offset
));
441 bool FuncChecker::checkImmILA(PC
& pc
, PC
const /*instr*/) {
442 auto ids
= iterBreakIds(pc
);
443 if (ids
.size() < 1) {
444 error("invalid length of immediate vector %lu at Offset %d\n",
445 ids
.size(), offset(pc
));
449 for (auto& iter
: ids
) {
450 if (iter
.kind
< KindOfIter
|| iter
.kind
> KindOfCIter
) {
451 error("invalid iterator kind %d in iter-vec at offset %d\n",
452 iter
.kind
, offset(pc
));
455 if (iter
.id
< 0 || iter
.id
>= numIters()) {
456 error("invalid iterator variable id %d at %d\n",
457 iter
.id
, offset(pc
));
462 return checkImmVec(pc
, sizeof(IterKindId
)) && ok
;
465 bool FuncChecker::checkImmIVA(PC
& pc
, PC
const instr
) {
466 auto const k
= decode_iva(pc
);
467 if (peek_op(instr
) == Op::ConcatN
) {
468 return k
>= 2 && k
<= kMaxConcatN
;
474 bool FuncChecker::checkImmI64A(PC
& pc
, PC
const /*instr*/) {
475 pc
+= sizeof(int64_t);
479 bool FuncChecker::checkImmLA(PC
& pc
, PC
const instr
) {
481 auto const k
= decode_iva(pc
);
482 ok
&= checkLocal(pc
, k
);
483 if (peek_op(instr
) == Op::VerifyParamType
) {
484 if (k
>= numParams()) {
485 error("invalid parameter id %d at %d\n", k
, offset(instr
));
493 bool FuncChecker::checkImmIA(PC
& pc
, PC
const instr
) {
494 auto const k
= decode_iva(pc
);
495 if (k
>= numIters()) {
496 error("invalid iterator variable id %d at %d\n", k
, offset(instr
));
502 bool FuncChecker::checkImmCAR(PC
& pc
, PC
const instr
) {
503 auto const slot
= decode_iva(pc
);
504 if (slot
>= numClsRefSlots()) {
505 error("invalid class-ref slot %d at %d\n", slot
, offset(instr
));
511 bool FuncChecker::checkImmCAW(PC
& pc
, PC
const instr
) {
512 auto const slot
= decode_iva(pc
);
513 if (slot
>= numClsRefSlots()) {
514 error("invalid class-ref slot %d at %d\n", slot
, offset(instr
));
520 bool FuncChecker::checkImmDA(PC
& pc
, PC
const /*instr*/) {
521 pc
+= sizeof(double);
525 bool FuncChecker::checkImmSA(PC
& pc
, PC
const /*instr*/) {
526 auto const id
= decodeId(&pc
);
527 return checkString(pc
, id
);
530 bool FuncChecker::checkImmAA(PC
& pc
, PC
const /*instr*/) {
531 auto const id
= decodeId(&pc
);
532 if (id
< 0 || id
>= (Id
)unit()->numArrays()) {
533 error("invalid array id %d\n", id
);
539 bool FuncChecker::checkImmRATA(PC
& pc
, PC
const /*instr*/) {
540 // Nothing to check at the moment.
541 pc
+= encodedRATSize(pc
);
545 bool FuncChecker::checkImmBA(PC
& pc
, PC
const instr
) {
546 // we check branch offsets in checkSection(). ignore here.
547 assert(instrJumpTarget(unit()->entry(), offset(instr
)) !=
548 InvalidAbsoluteOffset
);
549 pc
+= sizeof(Offset
);
553 bool FuncChecker::checkImmVSA(PC
& pc
, PC
const /*instr*/) {
554 auto const len
= decode_raw
<int32_t>(pc
);
555 if (len
< 1 || len
> MixedArray::MaxStructMakeSize
) {
556 error("invalid length of immedate VSA vector %d at offset %d\n",
558 throw unknown_length
{};
562 for (int i
= 0; i
< len
; i
++) {
563 auto const id
= decodeId(&pc
);
564 ok
&= checkString(pc
, id
);
569 template <typename Subop
>
570 bool FuncChecker::checkImmOAImpl(PC
& pc
, PC
const /*instr*/) {
571 auto const subop
= decode_oa
<Subop
>(pc
);
572 if (!subopValid(subop
)) {
573 ferror("Invalid subop {}\n", size_t(subop
));
579 bool FuncChecker::checkImmKA(PC
& pc
, PC
const /*instr*/) {
580 auto const mcode
= decode_raw
<MemberCode
>(pc
);
581 if (mcode
< 0 || mcode
>= NumMemberCodes
) {
582 ferror("Invalid MemberCode {}\n", uint8_t{mcode
});
590 case MEL
: case MPL
: {
591 auto const loc
= decode_iva(pc
);
592 ok
&= checkLocal(pc
, loc
);
599 pc
+= sizeof(int64_t);
601 case MET
: case MPT
: case MQT
:
602 auto const id
= decode_raw
<Id
>(pc
);
603 ok
&= checkString(pc
, id
);
610 bool FuncChecker::checkImmLAR(PC
& pc
, PC
const instr
) {
612 auto const range
= decodeLocalRange(pc
);
613 for (auto i
= uint32_t{0}; i
< range
.restCount
+1; ++i
) {
614 ok
&= checkLocal(instr
, range
.first
+ i
);
619 folly::Range
<const IterKindId
*> FuncChecker::iterBreakIds(PC pc
) const {
620 auto vec
= reinterpret_cast<const int32_t*>(pc
);
621 auto itervec
= reinterpret_cast<const IterKindId
*>(vec
+ 1);
622 return {itervec
, itervec
+ vec
[0]};
626 * Check instruction and its immediates. Returns false if we can't continue
627 * because we don't know the length of this instruction or one of the
628 * immediates was invalid.
630 bool FuncChecker::checkImmediates(const char* name
, PC
const instr
) {
633 auto const op
= decode_op(pc
);
634 if (!isValidOpcode(op
)) {
635 error("Invalid opcode %d in section %s at offset %d\n",
636 size_t(op
), name
, offset(instr
));
643 #define checkImmOA(ty) checkImmOAImpl<ty>
644 #define ONE(a) ok &= checkImm##a(pc, instr)
645 #define TWO(a, b) ONE(a); ok &= checkImm##b(pc, instr)
646 #define THREE(a, b, c) TWO(a, b); ok &= checkImm##c(pc, instr)
647 #define FOUR(a, b, c, d) THREE(a, b, c); ok &= checkImm##d(pc, instr)
648 #define O(name, imm, in, out, flags) case Op::name: imm; break;
658 } catch (const unknown_length
&) {
662 auto const expectPC
= instr
+ instrLen(instr
);
663 if (pc
!= expectPC
) {
664 ferror("PC after decoding {} at {} is {} instead of expected {}\n",
665 opcodeToName(op
), instr
, pc
, expectPC
);
671 static const char* stkflav(FlavorDesc f
) {
673 case NOV
: return "N";
679 case CRV
: return "C|R";
680 case CUV
: return "C|U";
681 case CVV
: return "C|V";
682 case CVUV
: return "C|V|U";
687 static bool checkArg(FlavorDesc expect
, FlavorDesc check
) {
688 if (expect
== check
) return true;
691 case CVV
: return check
== CV
|| check
== VV
;
692 case CRV
: return check
== CV
|| check
== RV
;
693 case CUV
: return check
== CV
|| check
== UV
;
694 case CVUV
: return check
== CV
|| check
== VV
|| check
== UV
|| check
== CUV
;
695 default: return false;
699 bool FuncChecker::checkSig(PC pc
, int len
, const FlavorDesc
* args
,
700 const FlavorDesc
* sig
) {
701 for (int i
= 0; i
< len
; ++i
) {
702 if (!checkArg(sig
[i
], args
[i
])) {
703 error("flavor mismatch at %d, got %s expected %s\n",
704 offset(pc
), stkToString(len
, args
).c_str(),
705 sigToString(len
, sig
).c_str());
712 const FlavorDesc
* FuncChecker::sig(PC pc
) {
713 static const FlavorDesc inputSigs
[][4] = {
719 #define ONE(a) { a },
720 #define TWO(a,b) { b, a },
721 #define THREE(a,b,c) { c, b, a },
722 #define FOUR(a,b,c,d) { d, c, b, a },
724 #define F_MFINAL { },
725 #define C_MFINAL { },
726 #define V_MFINAL { },
727 #define O(name, imm, pop, push, flags) pop
744 switch (peek_op(pc
)) {
751 for (int i
= 0, n
= instrNumPops(pc
); i
< n
; ++i
) {
758 for (int i
= 0, n
= instrNumPops(pc
); i
< n
; ++i
) {
759 m_tmp_sig
[i
] = i
== n
- 1 ? CV
: CRV
;
763 for (int i
= 0, n
= instrNumPops(pc
); i
< n
; ++i
) {
764 m_tmp_sig
[i
] = i
== n
- 1 ? VV
: CRV
;
767 case Op::FCall
: // ONE(IVA), FMANY, ONE(RV)
768 case Op::FCallD
: // THREE(IVA,SA,SA), FMANY, ONE(RV)
769 case Op::FCallAwait
: // THREE(IVA,SA,SA), FMANY, ONE(CV)
770 case Op::FCallUnpack
: // ONE(IVA), FMANY, ONE(RV)
771 case Op::FCallArray
: // NA, ONE(FV), ONE(RV)
772 for (int i
= 0, n
= instrNumPops(pc
); i
< n
; ++i
) {
776 case Op::FCallBuiltin
: //TWO(IVA, SA), CVUMANY, ONE(RV)
777 for (int i
= 0, n
= instrNumPops(pc
); i
< n
; ++i
) {
781 case Op::CreateCl
: // TWO(IVA,SA), CVUMANY, ONE(CV)
782 for (int i
= 0, n
= instrNumPops(pc
); i
< n
; ++i
) {
786 case Op::NewPackedArray
: // ONE(IVA), CMANY, ONE(CV)
787 case Op::NewStructArray
: // ONE(VSA), SMANY, ONE(CV)
788 case Op::NewVecArray
: // ONE(IVA), CMANY, ONE(CV)
789 case Op::NewKeysetArray
: // ONE(IVA), CMANY, ONE(CV)
790 case Op::ConcatN
: // ONE(IVA), CMANY, ONE(CV)
791 for (int i
= 0, n
= instrNumPops(pc
); i
< n
; ++i
) {
796 return &inputSigs
[size_t(peek_op(pc
))][0];
800 bool FuncChecker::checkMemberKey(State
* cur
, PC pc
, Op op
) {
803 if(RuntimeOption::RepoAuthoritative
) { //if the key mcode is ET, MT or QT
804 LitstrTable::get().setReading(); //we need to read from the LitstrTable
810 case Op::QueryM
: //THREE(IVA, OA, KA)
814 key
= decode_member_key(pc
, unit());
819 case Op::VGetM
: //TWO(IVA, KA)
822 key
= decode_member_key(pc
, unit());
824 case Op::FPassM
: //THREE(IVA, IVA, KA)
825 case Op::SetWithRefLML
:
826 case Op::SetWithRefRML
:
832 always_assert(false);
836 if ((key
.mcode
== MemberCode::MEC
|| key
.mcode
== MemberCode::MPC
) &&
837 key
.iva
+ 1 > cur
->stklen
) {
838 error("Member Key %s in op %s has stack offset greater than stack"
839 " depth %d\n", show(key
).c_str(), opcodeToName(op
), cur
->stklen
);
846 bool FuncChecker::checkInputs(State
* cur
, PC pc
, Block
* b
) {
847 StackTransInfo info
= instrStackTransInfo(pc
);
848 int min
= cur
->fpilen
> 0 ? cur
->fpi
[cur
->fpilen
- 1].stkmin
: 0;
849 if (info
.numPops
> 0 && cur
->stklen
- info
.numPops
< min
) {
850 reportStkUnderflow(b
, *cur
, pc
);
854 cur
->stklen
-= info
.numPops
;
855 auto ok
= checkSig(pc
, info
.numPops
, &cur
->stk
[cur
->stklen
], sig(pc
));
856 auto const op
= peek_op(pc
);
857 auto const need_live
= isMemberDimOp(op
) || isMemberFinalOp(op
);
858 auto const live_ok
= need_live
|| isTypeAssert(op
);
859 if ((need_live
&& !cur
->mbr_live
) || (cur
->mbr_live
&& !live_ok
)) {
860 error("Member base register %s coming into %s\n",
861 cur
->mbr_live
? "live" : "dead", opcodeToName(op
));
865 if (cur
->mbr_live
&& isMemberOp(op
)) {
866 folly::Optional
<MOpMode
> op_mode
;
867 if (op
== Op::QueryM
) {
871 op_mode
= getQueryMOpMode(decode_oa
<QueryMOp
>(new_pc
));
872 } else if (isMemberFinalOp(op
)) {
873 op_mode
= finalMemberOpMode(op
);
874 } else if (op
== Op::Dim
) {
877 op_mode
= decode_oa
<MOpMode
>(new_pc
);
880 if(cur
->mbr_mode
&& cur
->mbr_mode
!= op_mode
){
881 error("Member base register mode at %s is %s when it should be %s\n",
883 op_mode
? subopToName(op_mode
.value()) : "Unknown",
884 subopToName(cur
->mbr_mode
.value()));
888 cur
->mbr_mode
= op_mode
;
891 if (cur
->fpilen
> 0 && isJmp(op
)) {
892 auto offset
= getImm(pc
, 0).u_BA
;
894 error("FPI contains backwards jump at %s\n",
902 bool FuncChecker::checkTerminal(State
* cur
, PC pc
) {
903 if (isRet(pc
) || peek_op(pc
) == Op::Unwind
) {
904 if (cur
->stklen
!= 0) {
905 error("stack depth must equal 0 after Ret* and Unwind; got %d\n",
909 if (cur
->clsRefSlots
.any()) {
910 ferror("all class-ref slots must be uninitialized after Ret* and Unwind; "
911 "got {}\n", slotsToString(cur
->clsRefSlots
));
918 bool FuncChecker::checkFpi(State
* cur
, PC pc
, Block
* /*b*/) {
919 if (cur
->fpilen
<= 0) {
920 error("%s", "cannot access empty FPI stack\n");
924 FpiState
& fpi
= cur
->fpi
[cur
->fpilen
- 1];
925 auto const op
= peek_op(pc
);
927 if (isFCallStar(op
)) {
929 int call_params
= op
== Op::FCallArray
? 1 : getImmIva(pc
);
930 int push_params
= getImmIva(at(fpi
.fpush
));
931 if (call_params
!= push_params
) {
932 error("FCall* param_count (%d) doesn't match FPush* (%d)\n",
933 call_params
, push_params
);
936 if (fpi
.next
!= push_params
) {
937 error("wrong # of params were passed; got %d expected %d\n",
938 fpi
.next
, push_params
);
941 if (cur
->stklen
!= fpi
.stkmin
) {
942 error("%s", "FCall didn't consume the proper param count\n");
947 int param_id
= getImmIva(pc
);
948 int push_params
= getImmIva(at(fpi
.fpush
));
949 if (param_id
>= push_params
) {
950 error("param_id %d out of range [0:%d)\n", param_id
,
954 if (param_id
!= fpi
.next
) {
955 error("FPass* out of order; got id %d expected %d\n",
959 if (isMemberBaseOp(op
) || isMemberDimOp(op
)) {
960 // The argument isn't pushed until the final member operation. Skip the
964 // we have already popped FPass's input, but not pushed the output, so this
965 // check doesn't count the F result of this FPass, but does count the
967 if (cur
->stklen
!= fpi
.stkmin
+ param_id
) {
968 error("Stack depth incorrect after FPass; got %d expected %d\n",
969 cur
->stklen
, fpi
.stkmin
+ param_id
);
978 // Check that the initialization state of the iterator referenced by
979 // the current iterator instruction is valid. For *IterInit and DecodeCufIter,
980 // it must not already be initialized; for *IterNext and *IterFree, it must
982 bool FuncChecker::checkIter(State
* cur
, PC
const pc
) {
984 int id
= getImmIva(pc
);
986 auto op
= peek_op(pc
);
987 if (op
== Op::IterInit
|| op
== Op::IterInitK
||
988 op
== Op::WIterInit
|| op
== Op::WIterInitK
||
989 op
== Op::MIterInit
|| op
== Op::MIterInitK
||
990 op
== Op::DecodeCufIter
) {
991 if (cur
->iters
[id
]) {
993 "IterInit* or MIterInit* <%d> trying to double-initialize\n", id
);
997 if (!cur
->iters
[id
]) {
998 // TODO(#2608280): we produce incorrect bytecode for iterators still
999 //error("Cannot access un-initialized iter %d\n", id);
1002 if (op
== Op::IterFree
||
1003 op
== Op::MIterFree
||
1004 op
== Op::CIterFree
) {
1005 cur
->iters
[id
] = false;
1011 std::array
<int32_t, 4> getReadClsRefSlots(PC pc
) {
1012 std::array
<int32_t, 4> ret
= { -1, -1, -1, -1 };
1015 auto const op
= peek_op(pc
);
1016 auto const numImmeds
= numImmediates(op
);
1017 for (int i
= 0; i
< numImmeds
; ++i
) {
1018 if (immType(op
, i
) == ArgType::CAR
) {
1019 assertx(index
< ret
.size());
1020 ret
[index
++] = getImm(pc
, i
).u_CAR
;
1026 std::array
<int32_t, 4> getWrittenClsRefSlots(PC pc
) {
1027 std::array
<int32_t, 4> ret
= { -1, -1, -1, -1 };
1030 auto const op
= peek_op(pc
);
1031 auto const numImmeds
= numImmediates(op
);
1032 for (int i
= 0; i
< numImmeds
; ++i
) {
1033 if (immType(op
, i
) == ArgType::CAW
) {
1034 assertx(index
< ret
.size());
1035 ret
[index
++] = getImm(pc
, i
).u_CAW
;
1041 bool FuncChecker::checkClsRefSlots(State
* cur
, PC
const pc
) {
1044 auto const op
= peek_op(pc
);
1046 for (auto const read
: getReadClsRefSlots(pc
)) {
1047 if (read
< 0) continue;
1048 if (!cur
->clsRefSlots
[read
]) {
1049 ferror("{} trying to read from uninitialized class-ref slot {} at {}\n",
1050 opcodeToName(op
), read
, offset(pc
));
1053 cur
->clsRefSlots
[read
] = false;
1055 for (auto const write
: getWrittenClsRefSlots(pc
)) {
1056 if (write
< 0) continue;
1057 if (cur
->clsRefSlots
[write
]) {
1059 "{} trying to write to already initialized class-ref slot {} at {}\n",
1060 opcodeToName(op
), write
, offset(pc
)
1064 cur
->clsRefSlots
[write
] = true;
1070 bool FuncChecker::checkOp(State
* cur
, PC pc
, Op op
, Block
* b
) {
1074 case Op::CreateCl
: {
1075 auto id
= getImm(pc
, 0).u_IVA
;
1076 if (op
== Op::CreateCl
) id
= getImm(pc
, 1).u_IVA
;
1077 if (id
>= unit()->preclasses().size()) {
1078 ferror("{} references nonexistent class ({})\n", opcodeToName(op
), id
);
1084 auto id
= getImm(pc
, 0).u_IVA
;
1085 if (id
>= unit()->funcs().size()) {
1086 ferror("{} references nonexistent function ({})\n",
1087 opcodeToName(op
), id
);
1091 ferror("Cannot DefFunc main\n");
1096 case Op::DefTypeAlias
: {
1097 auto id
= getImm(pc
, 0).u_IVA
;
1098 if (id
>= unit()->typeAliases().size()) {
1099 ferror("{} references nonexistent type alias ({})\n",
1100 opcodeToName(op
), id
);
1105 case Op::GetMemoKeyL
:
1108 if (!m_func
->isMemoizeWrapper()) {
1109 ferror("{} can only appear within memoize wrappers\n",
1115 case Op::ColFromArray
: {
1118 auto colType
= decode_oa
<CollectionType
>(new_pc
);
1119 if (colType
== CollectionType::Pair
) {
1120 ferror("Immediate of {} must not be a pair\n", opcodeToName(op
));
1125 case Op::AssertRATL
:
1126 case Op::AssertRATStk
: {
1128 ferror("{} cannot appear at the end of a block\n", opcodeToName(op
));
1131 if (op
== Op::AssertRATL
) break;
1138 case Op::FPassBaseNC
:
1139 case Op::FPassBaseGC
: {
1140 auto const stackIdx
= getImm(pc
, 0).u_IVA
;
1141 if (stackIdx
>= cur
->stklen
) {
1142 ferror("{} indexes ({}) past end of stack ({})\n", opcodeToName(op
),
1143 stackIdx
, cur
->stklen
);
1154 // Check that each of the iterators provided as arguments to IterBreak
1155 // are currently initialized.
1156 bool FuncChecker::checkIterBreak(State
* cur
, PC pc
) {
1157 pc
+= encoded_op_size(Op::IterBreak
); // skip opcode
1158 decode_raw
<Offset
>(pc
); // skip target offset
1159 for (auto& iter
: iterBreakIds(pc
)) {
1160 if (!cur
->iters
[iter
.id
]) {
1161 // TODO(#2608280): we produce incorrect bytecode for iterators still
1162 //error("Cannot access un-initialized iter %d\n", iter.id);
1165 // IterBreak has no fall-through path, so don't change iter.id's current
1166 // state; instead it will be done in checkSuccEdges.
1171 bool FuncChecker::checkOutputs(State
* cur
, PC pc
, Block
* b
) {
1172 static const FlavorDesc outputSigs
[][4] = {
1177 #define ONE(a) { a },
1178 #define TWO(a,b) { a, b },
1179 #define THREE(a,b,c) { a, b, c },
1180 #define FOUR(a,b,c,d) { a, b, c, d },
1181 #define INS_1(a) { a },
1182 #define O(name, imm, pop, push, flags) push
1196 auto const op
= peek_op(pc
);
1197 StackTransInfo info
= instrStackTransInfo(pc
);
1198 if (info
.kind
== StackTransInfo::Kind::InsertMid
) {
1199 int index
= cur
->stklen
- info
.pos
- 1;
1201 reportStkUnderflow(b
, *cur
, pc
);
1204 memmove(&cur
->stk
[index
+ 1], &cur
->stk
[index
],
1205 (info
.pos
+ 1) * sizeof(*cur
->stk
));
1206 cur
->stk
[index
] = outputSigs
[size_t(op
)][0];
1209 int pushes
= info
.numPushes
;
1210 if (cur
->stklen
+ pushes
> maxStack()) reportStkOverflow(b
, *cur
, pc
);
1211 FlavorDesc
*outs
= &cur
->stk
[cur
->stklen
];
1212 cur
->stklen
+= pushes
;
1213 if (op
== Op::BaseSC
|| op
== Op::BaseSL
) {
1214 if (pushes
== 1) outs
[0] = outs
[1];
1216 for (int i
= 0; i
< pushes
; ++i
) {
1217 outs
[i
] = outputSigs
[size_t(op
)][i
];
1221 if (cur
->fpilen
>= maxFpi()) {
1222 error("%s", "more FPush* instructions than FPI regions\n");
1225 FpiState
& fpi
= cur
->fpi
[cur
->fpilen
];
1227 fpi
.fpush
= offset(pc
);
1229 fpi
.stkmin
= cur
->stklen
;
1233 if (isMemberBaseOp(op
)) {
1234 cur
->mbr_live
= true;
1235 if (op
== Op::BaseNC
|| op
== Op::BaseNL
|| op
== Op::BaseGC
||
1236 op
== Op::BaseGL
|| op
== Op::BaseL
) {
1240 cur
->mbr_mode
= decode_oa
<MOpMode
>(new_pc
);
1242 } else if (isMemberFinalOp(op
)) {
1243 cur
->mbr_live
= false;
1244 cur
->mbr_mode
.clear();
1247 if (cur
->fpilen
> 0 && (op
== Op::RetC
|| op
== Op::RetV
||
1248 op
== Op::Unwind
|| op
== Op::Throw
)) {
1249 error("%s instruction encountered inside of FPI region\n",
1257 std::string
FuncChecker::stkToString(int len
, const FlavorDesc
* args
) const {
1258 std::stringstream out
;
1260 for (int i
= 0; i
< len
; ++i
) {
1261 out
<< stkflav(args
[i
]);
1267 std::string
FuncChecker::sigToString(int len
, const FlavorDesc
* sig
) const {
1268 std::stringstream out
;
1270 for (int i
= 0; i
< len
; ++i
) {
1271 out
<< stkflav(sig
[i
]);
1277 std::string
FuncChecker::iterToString(const State
& cur
) const {
1280 std::stringstream out
;
1282 for (int i
= 0; i
< n
; ++i
) {
1283 out
<< (cur
.iters
[i
] ? '1' : '0');
1290 FuncChecker::slotsToString(const boost::dynamic_bitset
<>& slots
) const {
1291 std::ostringstream oss
;
1296 std::string
FuncChecker::stateToString(const State
& cur
) const {
1297 return folly::sformat(
1300 stkToString(cur
.stklen
, cur
.stk
),
1301 slotsToString(cur
.clsRefSlots
)
1305 std::string
FuncChecker::fpiToString(const FpiState
& fpi
) const {
1306 std::stringstream out
;
1307 out
<< '(' << fpi
.fpush
<< ':' << fpi
.stkmin
<< ',' << fpi
.next
<< ')';
1311 void FuncChecker::initState(State
* s
) {
1312 s
->stk
= new (m_arena
) FlavorDesc
[maxStack()];
1313 s
->fpi
= new (m_arena
) FpiState
[maxFpi()];
1314 s
->iters
= new (m_arena
) bool[numIters()];
1315 for (int i
= 0, n
= numIters(); i
< n
; ++i
) s
->iters
[i
] = false;
1316 s
->clsRefSlots
.resize(numClsRefSlots());
1319 s
->mbr_live
= false;
1320 s
->mbr_mode
.clear();
1323 void FuncChecker::copyState(State
* to
, const State
* from
) {
1325 if (!to
->stk
) initState(to
);
1326 memcpy(to
->stk
, from
->stk
, from
->stklen
* sizeof(*to
->stk
));
1327 memcpy(to
->fpi
, from
->fpi
, from
->fpilen
* sizeof(*to
->fpi
));
1328 memcpy(to
->iters
, from
->iters
, numIters() * sizeof(*to
->iters
));
1329 to
->clsRefSlots
= from
->clsRefSlots
;
1330 to
->stklen
= from
->stklen
;
1331 to
->fpilen
= from
->fpilen
;
1332 to
->mbr_live
= from
->mbr_live
;
1333 to
->mbr_mode
= from
->mbr_mode
;
1337 * Verify stack depth along all control flow paths
1339 bool FuncChecker::checkFlow() {
1341 GraphBuilder
builder(m_arena
, m_func
);
1342 m_graph
= builder
.build();
1343 m_info
= new (m_arena
) BlockInfo
[m_graph
->block_count
];
1344 memset(m_info
, 0, sizeof(BlockInfo
) * m_graph
->block_count
);
1345 m_tmp_sig
= new (m_arena
) FlavorDesc
[maxStack()];
1349 // initialize state at all entry points
1350 for (BlockPtrRange i
= entryBlocks(m_graph
); !i
.empty();) {
1351 ok
&= checkEdge(0, cur
, i
.popFront());
1353 for (Block
* b
= m_graph
->first_rpo
; b
; b
= b
->next_rpo
) {
1354 copyState(&cur
, &m_info
[b
->id
].state_in
);
1355 if (m_errmode
== kVerbose
) {
1356 std::cout
<< blockToString(b
, m_graph
, unit()) << std::endl
;
1358 for (InstrRange i
= blockInstrs(b
); !i
.empty(); ) {
1359 PC pc
= i
.popFront();
1360 if (m_errmode
== kVerbose
) {
1361 std::cout
<< " " << std::setw(5) << offset(pc
) << ":" <<
1362 stateToString(cur
) << " " <<
1363 instrToString(pc
, unit()) << std::endl
;
1365 auto const op
= peek_op(pc
);
1366 if (isMemberFinalOp(op
)) ok
&= checkMemberKey(&cur
, pc
, op
);
1367 ok
&= checkOp(&cur
, pc
, op
, b
);
1368 ok
&= checkInputs(&cur
, pc
, b
);
1369 auto const flags
= instrFlags(op
);
1370 if (flags
& TF
) ok
&= checkTerminal(&cur
, pc
);
1371 if (flags
& FF
) ok
&= checkFpi(&cur
, pc
, b
);
1372 if (isIter(pc
)) ok
&= checkIter(&cur
, pc
);
1373 if (Op(*pc
) == Op::IterBreak
) ok
&= checkIterBreak(&cur
, pc
);
1374 ok
&= checkClsRefSlots(&cur
, pc
);
1375 ok
&= checkOutputs(&cur
, pc
, b
);
1377 ok
&= checkSuccEdges(b
, &cur
);
1379 // Make sure eval stack is correct at start of each try region
1380 for (auto& handler
: m_func
->ehtab()) {
1381 if (handler
.m_type
== EHEnt::Type::Catch
) {
1382 ok
&= checkEHStack(handler
, builder
.at(handler
.m_base
));
1388 bool FuncChecker::checkSuccEdges(Block
* b
, State
* cur
) {
1390 // Reachable catch blocks and fault funclets have an empty stack and
1391 // non-initialized class-ref slots.
1393 int save_stklen
= cur
->stklen
;
1394 int save_fpilen
= cur
->fpilen
;
1395 auto save_slots
= cur
->clsRefSlots
;
1398 cur
->clsRefSlots
.reset();
1399 ok
&= checkEdge(b
, *cur
, b
->exn
);
1400 cur
->stklen
= save_stklen
;
1401 cur
->fpilen
= save_fpilen
;
1402 cur
->clsRefSlots
= std::move(save_slots
);
1404 if (isIter(b
->last
) && numSuccBlocks(b
) == 2) {
1405 // IterInit* and IterNext*, Both implicitly free their iterator variable
1406 // on the loop-exit path. Compute the iterator state on the "taken" path;
1407 // the fall-through path has the opposite state.
1408 int id
= getImmIva(b
->last
);
1409 auto const last_op
= peek_op(b
->last
);
1411 (last_op
== OpIterNext
|| last_op
== OpIterNextK
||
1412 last_op
== OpMIterNext
|| last_op
== OpMIterNextK
||
1413 last_op
== OpWIterNext
|| last_op
== OpWIterNextK
);
1414 bool save
= cur
->iters
[id
];
1415 cur
->iters
[id
] = taken_state
;
1416 if (m_errmode
== kVerbose
) {
1417 std::cout
<< " " << stateToString(*cur
) <<
1418 " -> B" << b
->succs
[1]->id
<< std::endl
;
1420 ok
&= checkEdge(b
, *cur
, b
->succs
[1]);
1421 cur
->iters
[id
] = !taken_state
;
1422 if (m_errmode
== kVerbose
) {
1423 std::cout
<< " " << stateToString(*cur
) <<
1424 " -> B" << b
->succs
[0]->id
<< std::endl
;
1426 ok
&= checkEdge(b
, *cur
, b
->succs
[0]);
1427 cur
->iters
[id
] = save
;
1428 } else if (Op(*b
->last
) == Op::IterBreak
) {
1429 auto pc
= b
->last
+ encoded_op_size(Op::IterBreak
);
1430 decode_raw
<Offset
>(pc
);
1431 for (auto& iter
: iterBreakIds(pc
)) {
1432 cur
->iters
[iter
.id
] = false;
1434 ok
&= checkEdge(b
, *cur
, b
->succs
[0]);
1436 // Other branch instructions send the same state to all successors.
1437 if (m_errmode
== kVerbose
) {
1438 std::cout
<< " " << stateToString(*cur
) << std::endl
;
1440 for (BlockPtrRange i
= succBlocks(b
); !i
.empty(); ) {
1441 ok
&= checkEdge(b
, *cur
, i
.popFront());
1444 if (cur
->mbr_live
) {
1445 // MBR must not be live across control flow edges.
1446 error("Member base register live at end of B%d\n", b
->id
);
1453 bool FuncChecker::checkEHStack(const EHEnt
& /*handler*/, Block
* b
) {
1454 const State
& state
= m_info
[b
->id
].state_in
;
1455 if (!state
.stk
) return true; // ignore unreachable block
1456 if (state
.fpilen
!= 0) {
1457 error("EH region starts with non-empty FPI stack at B%d\n",
1465 * Check the edge b->t, given the current state at the end of b.
1466 * If this is the first edge ->t we've seen, copy the state to t.
1467 * Otherwise, require the state exactly match.
1469 bool FuncChecker::checkEdge(Block
* b
, const State
& cur
, Block
*t
) {
1470 State
& state
= m_info
[t
->id
].state_in
;
1472 copyState(&state
, &cur
);
1476 if (state
.stklen
!= cur
.stklen
) {
1477 reportStkMismatch(b
, t
, cur
);
1480 for (int i
= 0, n
= cur
.stklen
; i
< n
; i
++) {
1481 if (state
.stk
[i
] != cur
.stk
[i
]) {
1482 error("mismatch on edge B%d->B%d, current %s target %s\n",
1483 b
->id
, t
->id
, stkToString(n
, cur
.stk
).c_str(),
1484 stkToString(n
, state
.stk
).c_str());
1489 if (state
.fpilen
!= cur
.fpilen
) {
1490 error("FPI stack depth mismatch on edge B%d->B%d, "
1491 "current %d target %d\n", b
->id
, t
->id
, cur
.fpilen
, state
.fpilen
);
1494 for (int i
= 0, n
= cur
.fpilen
; i
< n
; i
++) {
1495 if (state
.fpi
[i
] != cur
.fpi
[i
]) {
1496 error("FPI mismatch on edge B%d->B%d, current %s target %s\n",
1497 b
->id
, t
->id
, fpiToString(cur
.fpi
[i
]).c_str(),
1498 fpiToString(state
.fpi
[i
]).c_str());
1502 // Check iterator variable state.
1503 if (false /* TODO(#1097182): Iterator verification disabled */) {
1504 for (int i
= 0, n
= numIters(); i
< n
; i
++) {
1505 if (state
.iters
[i
] != cur
.iters
[i
]) {
1506 error("mismatched iterator state on edge B%d->B%d, "
1507 "current %s target %s\n", b
->id
, t
->id
,
1508 iterToString(cur
).c_str(), iterToString(state
).c_str());
1514 // Check class-ref slot state.
1515 if (state
.clsRefSlots
!= cur
.clsRefSlots
) {
1517 "mismatched class-ref state on edge B{}->B{}, current {} target {}\n",
1519 slotsToString(cur
.clsRefSlots
),
1520 slotsToString(state
.clsRefSlots
)
1528 void FuncChecker::reportStkUnderflow(Block
*, const State
& cur
, PC pc
) {
1529 int min
= cur
.fpilen
> 0 ? cur
.fpi
[cur
.fpilen
- 1].stkmin
: 0;
1530 error("Rule2: Stack underflow at PC %d, min depth %d\n",
1534 void FuncChecker::reportStkOverflow(Block
*, const State
& /*cur*/, PC pc
) {
1535 error("Rule2: Stack overflow at PC %d\n", offset(pc
));
1538 void FuncChecker::reportStkMismatch(Block
* b
, Block
* t
, const State
& cur
) {
1539 const State
& st
= m_info
[t
->id
].state_in
;
1540 error("Rule1: Stack mismatch on edge B%d->B%d; depth %d->%d\n",
1541 b
->id
, t
->id
, cur
.stklen
, st
.stklen
);
1544 void FuncChecker::reportEscapeEdge(Block
* b
, Block
* s
) {
1545 error("Edge from B%d to offset %d escapes function\n",
1546 b
->id
, offset(s
->start
));
1550 * Check that the offset is within the region and it lands on an exact
1551 * instruction start.
1553 bool FuncChecker::checkOffset(const char* name
, Offset off
,
1554 const char* regionName
, Offset base
,
1555 Offset past
, bool check_instrs
) {
1556 assert(past
>= base
);
1557 if (off
< base
|| off
>= past
) {
1558 error("Offset %s %d is outside region %s %d:%d\n",
1559 name
, off
, regionName
, base
, past
);
1562 if (check_instrs
&& !m_instrs
.get(off
- m_func
->base())) {
1563 error("label %s %d is not on a valid instruction boundary\n",
1571 * Check that the given inner region is valid, within the outer region, and
1572 * the inner region boundaries are exact instructions.
1574 bool FuncChecker::checkRegion(const char* name
, Offset b
, Offset p
,
1575 const char* regionName
, Offset base
,
1576 Offset past
, bool check_instrs
) {
1577 assert(past
>= base
);
1579 error("region %s %d:%d has negative length\n",
1583 if (b
< base
|| p
> past
) {
1584 error("region %s %d:%d is not inside region %s %d:%d\n",
1585 name
, b
, p
, regionName
, base
, past
);
1587 } else if (check_instrs
&&
1588 (!m_instrs
.get(b
- m_func
->base()) ||
1589 (p
< past
&& !m_instrs
.get(p
- m_func
->base())))) {
1590 error("region %s %d:%d boundaries are inbetween instructions\n",
1597 }} // HPHP::Verifier