comment out unused parameters
[hiphop-php.git] / hphp / runtime / vm / verifier / check-func.cpp
blobb2d6dbd6ee2bcc33b7fa19340b31e8f698556265
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
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>
30 #include <cstdio>
31 #include <iomanip>
32 #include <iostream>
33 #include <limits>
34 #include <list>
35 #include <stdexcept>
37 namespace HPHP {
38 namespace Verifier {
40 /**
41 * State for one entry on the FPI stack which corresponds to one call-site
42 * in progress.
44 struct FpiState {
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 {
52 return !(*this == s);
56 /**
57 * Facts about a Func's current frame at various program points
59 struct State {
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
70 /**
71 * Facts about a specific block.
73 struct BlockInfo {
74 State state_in; // state at the start of the block
77 struct IterKindId {
78 IterKind kind;
79 Id id;
82 struct FuncChecker {
83 FuncChecker(const Func* func, ErrorMode mode);
84 bool checkOffsets();
85 bool checkFlow();
87 private:
88 struct unknown_length : std::runtime_error {
89 unknown_length()
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)
106 ARGTYPES
107 #undef ARGTYPE
108 #undef ARGTYPEVEC
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;
147 private:
148 template<class... Args>
149 void error(const char* fmt, Args&&... args) {
150 verify_error(
151 unit(),
152 m_func,
153 m_errmode == kThrow,
154 fmt,
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());
164 private:
165 Arena m_arena;
166 BlockInfo* m_info; // one per block
167 const Func* const m_func;
168 Graph* m_graph;
169 Bits m_instrs;
170 ErrorMode m_errmode;
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,
179 func->isStatic());
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() : "",
188 clsname ? "::" : "",
189 funcname->data()
191 return false;
194 auto const& tc = func->returnTypeConstraint();
195 auto const message = Native::checkTypeFunc(info.sig, tc, func);
197 if (message) {
198 auto const tstr = info.sig.toString(clsname ? clsname->data() : nullptr,
199 funcname->data());
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() : "",
205 clsname ? "::" : "",
206 funcname->data(),
207 tstr.c_str(),
208 message
210 return false;
213 return true;
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());
222 printFPI(func);
224 FuncChecker v(func, mode);
225 return v.checkOffsets() &&
226 v.checkFlow();
229 FuncChecker::FuncChecker(const Func* f, ErrorMode mode)
230 : m_func(f)
231 , m_graph(0)
232 , m_instrs(m_arena, f->past() - f->base() + 1)
233 , m_errmode(mode) {
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);
246 --i;
247 return i->first;
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() {
256 bool ok = true;
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
263 SectionMap sections;
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,
267 past, false);
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,
287 funclets);
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);
302 } else {
303 ok = false;
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);
312 return ok;
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,
321 Offset past) {
322 bool ok = true;
323 typedef std::list<PC> BranchList;
324 BranchList branches;
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));
333 return false;
335 m_instrs.set(offset(pc) - m_func->base());
336 if (isSwitch(op) ||
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();
341 if (len <= 2) {
342 error("Bounded switch must have a vector of length > 2 [%d:%d]\n",
343 base, past);
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);
355 if (i.empty()) {
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);
359 ok = false;
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());
364 ok = false;
365 } else {
366 if (isRet(pc) && !is_main) {
367 error("Ret* may not appear in %s\n", name);
368 ok = false;
369 } else if (op == Op::Unwind && is_main) {
370 error("Unwind may not appear in %s\n", name);
371 ok = false;
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),
382 name, base, past);
384 } else {
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);
389 ok = false;
393 return ok;
396 Id decodeId(PC* ppc) {
397 Id id = *(Id*)*ppc;
398 *ppc += sizeof(Id);
399 return id;
402 Offset decodeOffset(PC* ppc) {
403 Offset offset = *(Offset*)*ppc;
404 *ppc += sizeof(Offset);
405 return 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",
411 k, offset(pc));
412 return false;
414 return true;
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);
423 if (len < 1) {
424 error("invalid length of immediate vector %d at Offset %d\n",
425 len, offset(pc));
426 throw unknown_length{};
429 pc += len * elemSize;
430 return true;
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));
446 return false;
448 auto ok = true;
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));
453 ok = false;
455 if (iter.id < 0 || iter.id >= numIters()) {
456 error("invalid iterator variable id %d at %d\n",
457 iter.id, offset(pc));
458 ok = false;
461 // now skip the vec
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;
471 return true;
474 bool FuncChecker::checkImmI64A(PC& pc, PC const /*instr*/) {
475 pc += sizeof(int64_t);
476 return true;
479 bool FuncChecker::checkImmLA(PC& pc, PC const instr) {
480 auto ok = true;
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));
486 ok = false;
490 return ok;
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));
497 return false;
499 return true;
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));
506 return false;
508 return true;
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));
515 return false;
517 return true;
520 bool FuncChecker::checkImmDA(PC& pc, PC const /*instr*/) {
521 pc += sizeof(double);
522 return true;
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);
534 return false;
536 return true;
539 bool FuncChecker::checkImmRATA(PC& pc, PC const /*instr*/) {
540 // Nothing to check at the moment.
541 pc += encodedRATSize(pc);
542 return true;
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);
550 return true;
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",
557 len, offset(pc));
558 throw unknown_length{};
561 auto ok = true;
562 for (int i = 0; i < len; i++) {
563 auto const id = decodeId(&pc);
564 ok &= checkString(pc, id);
566 return ok;
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));
574 return false;
576 return true;
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});
583 return false;
586 auto ok = true;
587 switch (mcode) {
588 case MW:
589 break;
590 case MEL: case MPL: {
591 auto const loc = decode_iva(pc);
592 ok &= checkLocal(pc, loc);
593 break;
595 case MEC: case MPC:
596 decode_iva(pc);
597 break;
598 case MEI:
599 pc += sizeof(int64_t);
600 break;
601 case MET: case MPT: case MQT:
602 auto const id = decode_raw<Id>(pc);
603 ok &= checkString(pc, id);
604 break;
607 return ok;
610 bool FuncChecker::checkImmLAR(PC& pc, PC const instr) {
611 auto ok = true;
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);
616 return ok;
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) {
631 auto pc = 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));
637 return false;
639 bool ok = true;
640 try {
641 switch (op) {
642 #define NA
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;
649 OPCODES
650 #undef NA
651 #undef checkOA
652 #undef ONE
653 #undef TWO
654 #undef THREE
655 #undef FOUR
656 #undef O
658 } catch (const unknown_length&) {
659 return false;
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);
666 return false;
668 return ok;
671 static const char* stkflav(FlavorDesc f) {
672 switch (f) {
673 case NOV: return "N";
674 case CV: return "C";
675 case VV: return "V";
676 case RV: return "R";
677 case FV: return "F";
678 case UV: return "U";
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";
684 not_reached();
687 static bool checkArg(FlavorDesc expect, FlavorDesc check) {
688 if (expect == check) return true;
690 switch (expect) {
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());
706 return false;
709 return true;
712 const FlavorDesc* FuncChecker::sig(PC pc) {
713 static const FlavorDesc inputSigs[][4] = {
714 #define NOV { },
715 #define FMANY { },
716 #define CVUMANY { },
717 #define CMANY { },
718 #define SMANY { },
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 },
723 #define MFINAL { },
724 #define F_MFINAL { },
725 #define C_MFINAL { },
726 #define V_MFINAL { },
727 #define O(name, imm, pop, push, flags) pop
728 OPCODES
729 #undef O
730 #undef MFINAL
731 #undef F_MFINAL
732 #undef C_MFINAL
733 #undef V_MFINAL
734 #undef FMANY
735 #undef CVUMANY
736 #undef CMANY
737 #undef SMANY
738 #undef FOUR
739 #undef THREE
740 #undef TWO
741 #undef ONE
742 #undef NOV
744 switch (peek_op(pc)) {
745 case Op::QueryM:
746 case Op::VGetM:
747 case Op::FPassM:
748 case Op::IncDecM:
749 case Op::UnsetM:
750 case Op::MemoGet:
751 for (int i = 0, n = instrNumPops(pc); i < n; ++i) {
752 m_tmp_sig[i] = CRV;
754 return m_tmp_sig;
755 case Op::SetM:
756 case Op::SetOpM:
757 case Op::MemoSet:
758 for (int i = 0, n = instrNumPops(pc); i < n; ++i) {
759 m_tmp_sig[i] = i == n - 1 ? CV : CRV;
761 return m_tmp_sig;
762 case Op::BindM:
763 for (int i = 0, n = instrNumPops(pc); i < n; ++i) {
764 m_tmp_sig[i] = i == n - 1 ? VV : CRV;
766 return m_tmp_sig;
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) {
773 m_tmp_sig[i] = FV;
775 return m_tmp_sig;
776 case Op::FCallBuiltin: //TWO(IVA, SA), CVUMANY, ONE(RV)
777 for (int i = 0, n = instrNumPops(pc); i < n; ++i) {
778 m_tmp_sig[i] = CVUV;
780 return m_tmp_sig;
781 case Op::CreateCl: // TWO(IVA,SA), CVUMANY, ONE(CV)
782 for (int i = 0, n = instrNumPops(pc); i < n; ++i) {
783 m_tmp_sig[i] = CVUV;
785 return m_tmp_sig;
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) {
792 m_tmp_sig[i] = CV;
794 return m_tmp_sig;
795 default:
796 return &inputSigs[size_t(peek_op(pc))][0];
800 bool FuncChecker::checkMemberKey(State* cur, PC pc, Op op) {
801 MemberKey key;
803 if(RuntimeOption::RepoAuthoritative) { //if the key mcode is ET, MT or QT
804 LitstrTable::get().setReading(); //we need to read from the LitstrTable
807 switch(op){
808 case Op::IncDecM:
809 case Op::SetOpM:
810 case Op::QueryM: //THREE(IVA, OA, KA)
811 decode_op(pc);
812 decode_iva(pc);
813 decode_byte(pc);
814 key = decode_member_key(pc, unit());
815 break;
816 case Op::BindM:
817 case Op::UnsetM:
818 case Op::SetM:
819 case Op::VGetM: //TWO(IVA, KA)
820 decode_op(pc);
821 decode_iva(pc);
822 key = decode_member_key(pc, unit());
823 break;
824 case Op::FPassM: //THREE(IVA, IVA, KA)
825 case Op::SetWithRefLML:
826 case Op::SetWithRefRML:
827 case Op::MemoGet:
828 case Op::MemoSet:
829 return true;
831 default:
832 always_assert(false);
833 return 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);
840 return false;
843 return true;
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);
851 cur->stklen = 0;
852 return false;
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));
862 ok = false;
865 if (cur->mbr_live && isMemberOp(op)) {
866 folly::Optional<MOpMode> op_mode;
867 if (op == Op::QueryM) {
868 auto new_pc = pc;
869 decode_op(new_pc);
870 decode_iva(new_pc);
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) {
875 auto new_pc = pc;
876 decode_op(new_pc);
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",
882 opcodeToName(op),
883 op_mode ? subopToName(op_mode.value()) : "Unknown",
884 subopToName(cur->mbr_mode.value()));
885 ok = false;
888 cur->mbr_mode = op_mode;
891 if (cur->fpilen > 0 && isJmp(op)) {
892 auto offset = getImm(pc, 0).u_BA;
893 if(offset < 0){
894 error("FPI contains backwards jump at %s\n",
895 opcodeToName(op));
899 return ok;
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",
906 cur->stklen);
907 return false;
909 if (cur->clsRefSlots.any()) {
910 ferror("all class-ref slots must be uninitialized after Ret* and Unwind; "
911 "got {}\n", slotsToString(cur->clsRefSlots));
912 return false;
915 return true;
918 bool FuncChecker::checkFpi(State* cur, PC pc, Block* /*b*/) {
919 if (cur->fpilen <= 0) {
920 error("%s", "cannot access empty FPI stack\n");
921 return false;
923 bool ok = true;
924 FpiState& fpi = cur->fpi[cur->fpilen - 1];
925 auto const op = peek_op(pc);
927 if (isFCallStar(op)) {
928 --cur->fpilen;
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);
934 ok = false;
936 if (fpi.next != push_params) {
937 error("wrong # of params were passed; got %d expected %d\n",
938 fpi.next, push_params);
939 ok = false;
941 if (cur->stklen != fpi.stkmin) {
942 error("%s", "FCall didn't consume the proper param count\n");
943 ok = false;
945 } else {
946 // FPass*
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,
951 push_params);
952 return false;
954 if (param_id != fpi.next) {
955 error("FPass* out of order; got id %d expected %d\n",
956 param_id, fpi.next);
957 ok = false;
959 if (isMemberBaseOp(op) || isMemberDimOp(op)) {
960 // The argument isn't pushed until the final member operation. Skip the
961 // last two checks.
962 return ok;
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
966 // previous FPass*s.
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);
970 ok = false;
972 fpi.next++;
975 return ok;
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
981 // be initialized.
982 bool FuncChecker::checkIter(State* cur, PC const pc) {
983 assert(isIter(pc));
984 int id = getImmIva(pc);
985 bool ok = true;
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]) {
992 error(
993 "IterInit* or MIterInit* <%d> trying to double-initialize\n", id);
994 ok = false;
996 } else {
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);
1000 //ok = false;
1002 if (op == Op::IterFree ||
1003 op == Op::MIterFree ||
1004 op == Op::CIterFree) {
1005 cur->iters[id] = false;
1008 return ok;
1011 std::array<int32_t, 4> getReadClsRefSlots(PC pc) {
1012 std::array<int32_t, 4> ret = { -1, -1, -1, -1 };
1013 size_t index = 0;
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;
1023 return ret;
1026 std::array<int32_t, 4> getWrittenClsRefSlots(PC pc) {
1027 std::array<int32_t, 4> ret = { -1, -1, -1, -1 };
1028 size_t index = 0;
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;
1038 return ret;
1041 bool FuncChecker::checkClsRefSlots(State* cur, PC const pc) {
1042 bool ok = true;
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));
1051 ok = false;
1053 cur->clsRefSlots[read] = false;
1055 for (auto const write : getWrittenClsRefSlots(pc)) {
1056 if (write < 0) continue;
1057 if (cur->clsRefSlots[write]) {
1058 ferror(
1059 "{} trying to write to already initialized class-ref slot {} at {}\n",
1060 opcodeToName(op), write, offset(pc)
1062 ok = false;
1064 cur->clsRefSlots[write] = true;
1067 return ok;
1070 bool FuncChecker::checkOp(State* cur, PC pc, Op op, Block* b) {
1071 switch (op) {
1072 case Op::DefCls:
1073 case Op::DefClsNop:
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);
1079 return false;
1081 break;
1083 case Op::DefFunc: {
1084 auto id = getImm(pc, 0).u_IVA;
1085 if (id >= unit()->funcs().size()) {
1086 ferror("{} references nonexistent function ({})\n",
1087 opcodeToName(op), id);
1088 return false;
1090 if (id == 0) {
1091 ferror("Cannot DefFunc main\n");
1092 return false;
1094 break;
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);
1101 return false;
1103 break;
1105 case Op::GetMemoKeyL:
1106 case Op::MemoGet:
1107 case Op::MemoSet:
1108 if (!m_func->isMemoizeWrapper()) {
1109 ferror("{} can only appear within memoize wrappers\n",
1110 opcodeToName(op));
1111 return false;
1113 break;
1114 case Op::NewCol:
1115 case Op::ColFromArray: {
1116 auto new_pc = pc;
1117 decode_op(new_pc);
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));
1121 return false;
1123 break;
1125 case Op::AssertRATL:
1126 case Op::AssertRATStk: {
1127 if (pc == b->last){
1128 ferror("{} cannot appear at the end of a block\n", opcodeToName(op));
1129 return false;
1131 if (op == Op::AssertRATL) break;
1133 case Op::BaseNC:
1134 case Op::BaseGC:
1135 case Op::BaseSC:
1136 case Op::BaseC:
1137 case Op::BaseR:
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);
1144 return false;
1146 break;
1148 default:
1149 break;
1151 return true;
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);
1163 //return false;
1165 // IterBreak has no fall-through path, so don't change iter.id's current
1166 // state; instead it will be done in checkSuccEdges.
1168 return true;
1171 bool FuncChecker::checkOutputs(State* cur, PC pc, Block* b) {
1172 static const FlavorDesc outputSigs[][4] = {
1173 #define NOV { },
1174 #define FMANY { },
1175 #define CMANY { },
1176 #define SMANY { },
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
1183 OPCODES
1184 #undef O
1185 #undef FMANY
1186 #undef CMANY
1187 #undef SMANY
1188 #undef INS_1
1189 #undef FOUR
1190 #undef THREE
1191 #undef TWO
1192 #undef ONE
1193 #undef NOV
1195 bool ok = true;
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;
1200 if (index < 0) {
1201 reportStkUnderflow(b, *cur, pc);
1202 return false;
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];
1207 cur->stklen++;
1208 } else {
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];
1215 } else {
1216 for (int i = 0; i < pushes; ++i) {
1217 outs[i] = outputSigs[size_t(op)][i];
1220 if (isFPush(op)) {
1221 if (cur->fpilen >= maxFpi()) {
1222 error("%s", "more FPush* instructions than FPI regions\n");
1223 return false;
1225 FpiState& fpi = cur->fpi[cur->fpilen];
1226 cur->fpilen++;
1227 fpi.fpush = offset(pc);
1228 fpi.next = 0;
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) {
1237 auto new_pc = pc;
1238 decode_op(new_pc);
1239 decode_iva(new_pc);
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",
1250 opcodeToName(op));
1251 ok = false;
1254 return ok;
1257 std::string FuncChecker::stkToString(int len, const FlavorDesc* args) const {
1258 std::stringstream out;
1259 out << '[';
1260 for (int i = 0; i < len; ++i) {
1261 out << stkflav(args[i]);
1263 out << ']';
1264 return out.str();
1267 std::string FuncChecker::sigToString(int len, const FlavorDesc* sig) const {
1268 std::stringstream out;
1269 out << '[';
1270 for (int i = 0; i < len; ++i) {
1271 out << stkflav(sig[i]);
1273 out << ']';
1274 return out.str();
1277 std::string FuncChecker::iterToString(const State& cur) const {
1278 int n = numIters();
1279 if (!n) return "";
1280 std::stringstream out;
1281 out << '[';
1282 for (int i = 0; i < n; ++i) {
1283 out << (cur.iters[i] ? '1' : '0');
1285 out << ']';
1286 return out.str();
1289 std::string
1290 FuncChecker::slotsToString(const boost::dynamic_bitset<>& slots) const {
1291 std::ostringstream oss;
1292 oss << slots;
1293 return oss.str();
1296 std::string FuncChecker::stateToString(const State& cur) const {
1297 return folly::sformat(
1298 "{}{}[{}]",
1299 iterToString(cur),
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 << ')';
1308 return out.str();
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());
1317 s->stklen = 0;
1318 s->fpilen = 0;
1319 s->mbr_live = false;
1320 s->mbr_mode.clear();
1323 void FuncChecker::copyState(State* to, const State* from) {
1324 assert(from->stk);
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() {
1340 bool ok = true;
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()];
1346 sortRpo(m_graph);
1347 State cur;
1348 initState(&cur);
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));
1385 return ok;
1388 bool FuncChecker::checkSuccEdges(Block* b, State* cur) {
1389 bool ok = true;
1390 // Reachable catch blocks and fault funclets have an empty stack and
1391 // non-initialized class-ref slots.
1392 if (b->exn) {
1393 int save_stklen = cur->stklen;
1394 int save_fpilen = cur->fpilen;
1395 auto save_slots = cur->clsRefSlots;
1396 cur->stklen = 0;
1397 cur->fpilen = 0;
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);
1410 bool taken_state =
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]);
1435 } else {
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);
1447 ok = false;
1450 return ok;
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",
1458 b->id);
1459 return false;
1461 return true;
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;
1471 if (!state.stk) {
1472 copyState(&state, &cur);
1473 return true;
1475 // Check stack.
1476 if (state.stklen != cur.stklen) {
1477 reportStkMismatch(b, t, cur);
1478 return false;
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());
1485 return false;
1488 // Check FPI stack.
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);
1492 return false;
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());
1499 return false;
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());
1509 return false;
1514 // Check class-ref slot state.
1515 if (state.clsRefSlots != cur.clsRefSlots) {
1516 ferror(
1517 "mismatched class-ref state on edge B{}->B{}, current {} target {}\n",
1518 b->id, t->id,
1519 slotsToString(cur.clsRefSlots),
1520 slotsToString(state.clsRefSlots)
1522 return false;
1525 return true;
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",
1531 offset(pc), min);
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);
1560 return false;
1562 if (check_instrs && !m_instrs.get(off - m_func->base())) {
1563 error("label %s %d is not on a valid instruction boundary\n",
1564 name, off);
1565 return false;
1567 return true;
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);
1578 if (p < b) {
1579 error("region %s %d:%d has negative length\n",
1580 name, b, p);
1581 return false;
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);
1586 return false;
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",
1591 name, b, p);
1592 return false;
1594 return true;
1597 }} // HPHP::Verifier