bool FuncChecker::checkOp()

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;
}