in hphp/runtime/vm/verifier/check-func.cpp [1145:1478]
bool FuncChecker::checkOp(State* cur, PC pc, Op op, Block* b, PC prev_pc) {
switch (op) {
case Op::BreakTraceHint:
if (cur->mbr_live) {
// Special case for unreachable code. hhbbc generates
// BreakTraceHint; String; Fatal
auto const str = pc + instrLen(pc);
if (peek_op(str) != Op::String) break;
auto const fatal = str + instrLen(str);
if (peek_op(fatal) != Op::Fatal) break;
cur->mbr_live = false;
}
break;
case Op::InitProp:
case Op::CheckProp: {
auto fname = m_func->name->toCppString();
if (fname.compare("86pinit") != 0 &&
fname.compare("86sinit") != 0 &&
fname.compare("86linit") != 0) {
ferror("{} cannot appear in {} function\n", opcodeToName(op), fname);
return false;
}
if (!LitstrTable::canRead()) {
// Unfortunately in order to check if the property name itself is
// valid we need to be able to read from the Litstr table, which we
// cannot do while verifying an optimized repo in hhbbc.
if (!checkString(pc, getImm(pc, 0).u_SA)) return false;
break;
}
auto const prop = m_func->ue().lookupLitstr(getImm(pc, 0).u_SA);
if (!m_func->pce() || !m_func->pce()->hasProp(prop)){
ferror("{} references non-existent property {}\n",
opcodeToName(op), prop);
return false;
}
break;
}
case Op::This:
case Op::CheckThis: {
cur->guaranteedThis = true;
break;
}
case Op::BareThis: {
auto new_pc = pc;
decode_op(new_pc);
if (decode_oa<BareThisOp>(new_pc) != BareThisOp::NeverNull) break;
}
case Op::BaseH: {
// In HHBBC we can track the $this across loads into locals and pushes
// onto the stack. If the types of those equivalent locations are refined
// we may end up knowing that $this is never null. The verifier doesn't
// currently support this type of sophisticated tracking and it's doubtful
// there would be much value in adding it.
if (!RuntimeOption::RepoAuthoritative && !cur->guaranteedThis) {
ferror("{} required that $this be guaranteed to be non-null\n",
opcodeToName(op));
return false;
}
break;
}
case Op::CreateCl: {
auto const id = getImm(pc, 1).u_IVA;
if (id >= unit()->numPreClasses()) {
ferror("CreateCl must reference a closure defined in the same "
"unit\n");
return false;
}
auto const preCls = unit()->pce(id);
if (preCls->parentName()->toCppString() != std::string("Closure")) {
ferror("CreateCl references non-closure class {} ({})\n",
preCls->name(), id);
return false;
}
auto const numBound = getImm(pc, 0).u_IVA;
auto const invoke = preCls->lookupMethod(s_invoke.get());
if (invoke && numBound != preCls->numProperties()) {
ferror("CreateCl bound Closure {} with {} params instead of {}\n",
preCls->name(), numBound, preCls->numProperties());
return false;
}
if (!m_func->pce() || (m_func->attrs & AttrStatic)) {
if (!(invoke->attrs & AttrStatic)) {
ferror("CreateCl bound Closure {} without AttrStatic in a {}\n",
preCls->name(), m_func->pce() ? "static method" : "function");
return false;
}
}
break;
}
#define O(name) \
case Op::name: { \
auto const id = getImm(pc, 0).u_AA; \
if (!checkArray(pc, id)) { \
ferror("{} references array data that is not a \n", \
#name, #name); \
return false; \
} \
if (!LitarrayTable::canRead()) { \
break; \
} \
auto const dt = unit()->lookupArray(id)->toDataType(); \
if (dt != KindOf##name) { \
ferror("{} references array data that is a {}\n", #name, dt); \
return false; \
} \
break; \
}
O(Keyset)
O(Dict)
O(Vec)
#undef O
case Op::GetMemoKeyL: {
auto const name = folly::to<std::string>(
m_func && m_func->pce() ? m_func->pce()->name()->data() : "",
m_func && m_func->pce() ? "::" : "",
m_func ? m_func->name->data() : "");
if (!m_func->isMemoizeWrapper) {
ferror("GetMemoKeyL can only appear within memoize wrappers\n");
return false;
}
break;
}
case Op::MemoGet:
case Op::MemoGetEager:
case Op::MemoSet:
case Op::MemoSetEager:
if (!m_func->isMemoizeWrapper) {
ferror("{} can only appear within memoize wrappers\n",
opcodeToName(op));
return false;
}
if (op == Op::MemoGetEager || op == Op::MemoSetEager) {
if (!m_func->isAsync || m_func->isGenerator) {
ferror("{} can only appear within async functions\n",
opcodeToName(op));
return false;
}
}
break;
case Op::NewCol:
case Op::ColFromArray: {
auto new_pc = pc;
decode_op(new_pc);
auto colType = decode_oa<CollectionType>(new_pc);
if (colType == CollectionType::Pair) {
ferror("Immediate of {} must not be a pair\n", opcodeToName(op));
return false;
}
break;
}
case Op::AssertRATL:
case Op::AssertRATStk: {
if (pc == b->last){
ferror("{} cannot appear at the end of a block\n", opcodeToName(op));
return false;
}
if (op == Op::AssertRATL) break;
}
case Op::BaseGC:
case Op::BaseC: {
auto const stackIdx = getImm(pc, 0).u_IVA;
if (stackIdx >= cur->stklen) {
ferror("{} indexes ({}) past end of stack ({})\n", opcodeToName(op),
stackIdx, cur->stklen);
return false;
}
break;
}
case Op::BaseSC: {
auto const keyIdx = getImm(pc, 0).u_IVA;
auto const clsIdx = getImm(pc, 1).u_IVA;
if (keyIdx >= cur->stklen) {
ferror("{} indexes key index ({}) past end of stack ({})\n",
opcodeToName(op), keyIdx, cur->stklen);
return false;
}
if (clsIdx >= cur->stklen) {
ferror("{} indexes class index ({}) past end of stack ({})\n",
opcodeToName(op), clsIdx, cur->stklen);
return false;
}
break;
}
case Op::CreateCont:
case Op::YieldK:
case Op::Yield:
if (!m_func->isGenerator) {
ferror("{} may only appear in a generator\n", opcodeToName(op));
return false;
}
break;
case Op::ContEnter: // Only in non-static generator methods
case Op::ContRaise: {
auto cls = m_func->pce();
if ((m_func->attrs & AttrStatic) || !cls ||
(cls->name()->toCppString() != std::string("Generator") &&
cls->name()->toCppString() != std::string("HH\\AsyncGenerator"))) {
ferror("{} may only appear in non-static methods of the "
"[Async]Generator class\n", opcodeToName(op));
return false;
}
break;
}
case Op::AwaitAll: {
auto const& range = getImm(pc, 0).u_LAR;
if (range.count == 0) {
ferror("{} must have a non-empty local range\n", opcodeToName(op));
return false;
}
// Fall-through
}
case Op::Await: {
if (!m_func->isAsync) {
ferror("{} may only appear in an async function\n", opcodeToName(op));
return false;
}
if (cur->stklen != instrNumPops(pc)) {
ferror("{} may not be used with non-empty stack\n", opcodeToName(op));
return false;
}
break;
}
case Op::Silence: {
auto new_pc = pc;
decode_op(new_pc);
auto const local = decode_iva(new_pc);
if (m_func->localNameMap()[local]) {
ferror("Cannot store error reporting value in named local\n");
return false;
}
auto const silence = decode_oa<SilenceOp>(new_pc);
if (local + 1 > cur->silences.size()) cur->silences.resize(local + 1);
if (silence == SilenceOp::End && !cur->silences[local]) {
ferror("Silence ended on local variable {} with no start\n", local);
return false;
}
cur->silences[local] = silence == SilenceOp::Start ? 1 : 0;
break;
}
case Op::Fatal:
// fatals don't do exception handling, and the silence state is reset
// by the runtime.
cur->silences.clear();
break;
case Op::NewVec:
case Op::NewKeysetArray: {
auto new_pc = pc;
decode_op(new_pc);
auto const elems = decode_iva(new_pc);
static_assert(std::is_unsigned<decltype(elems)>::value,
"IVA should be unsigned");
if (elems == 0 || elems > ArrayData::MaxElemsOnStack) {
ferror("{} has an illegal element count\n", opcodeToName(op));
return false;
}
break;
}
case Op::RetCSuspended:
if (!m_func->isAsync || m_func->isGenerator) {
ferror("{} can only appear within async functions\n",
opcodeToName(op));
return false;
}
break;
case Op::FCallClsMethodD:
case Op::FCallClsMethodSD:
case Op::FCallCtor:
case Op::FCallFuncD:
case Op::FCallObjMethodD: {
auto const fca = getImm(pc, 0).u_FCA;
if (prev_pc && fca.hasGenerics()) {
auto const prev_op = peek_op(prev_pc);
if (prev_op == Op::Vec) {
auto const id = getImm(prev_pc, 0).u_AA;
if (!checkArray(prev_pc, id)) {
ferror("Generics passed to {} don't exist\n", opcodeToName(op));
return false;
}
if (!LitarrayTable::canRead()) {
break;
}
auto const arr = unit()->lookupArray(id);
if (doesTypeStructureContainTUnresolved(arr)) {
ferror("Generics passed to {} contain unresolved generics. "
"Call CombineAndResolveTypeStruct to resolve them\n",
opcodeToName(op));
return false;
}
}
}
break;
}
case Op::SetS: {
auto new_pc = pc;
decode_op(new_pc);
auto const rop = decode_oa<ReadonlyOp>(new_pc);
if (rop != ReadonlyOp::Readonly && rop != ReadonlyOp::Any) {
return readOnlyImmNotSupported(rop, op);
}
break;
}
case Op::CGetS: {
auto new_pc = pc;
decode_op(new_pc);
auto const rop = decode_oa<ReadonlyOp>(new_pc);
if (rop != ReadonlyOp::Mutable && rop != ReadonlyOp::Any) {
return readOnlyImmNotSupported(rop, op);
}
break;
}
default:
break;
}
if (op != Op::Silence && !isTypeAssert(op)) {
for (auto const local : localIds(op, pc)) {
if (cur->silences.size() > local && cur->silences[local]) {
ferror("{} at PC {} affected a local variable ({}) which was reserved "
"for storing the error reporting level\n",
opcodeToName(op), offset(pc), local);
return false;
}
}
}
return true;
}