void Func::prettyPrint(std::ostream& out) const { if (isPseudoMain()) { out << "Pseudo-main"; } else if (preClass() != NULL) { out << "Method "; if (m_attrs & AttrStatic) { out << "static "; } if (m_attrs & AttrPublic) { out << "public "; } if (m_attrs & AttrProtected) { out << "protected "; } if (m_attrs & AttrPrivate) { out << "private "; } if (m_attrs & AttrAbstract) { out << "abstract "; } if (m_attrs & AttrFinal) { out << "final "; } out << preClass()->name()->data() << "::" << m_name->data(); } else { out << "Function " << m_name->data(); } out << " at " << base(); if (shared()->m_id != -1) { out << " (ID " << shared()->m_id << ")"; } out << std::endl; const ParamInfoVec& params = shared()->m_params; for (uint i = 0; i < params.size(); ++i) { if (params[i].funcletOff() != InvalidAbsoluteOffset) { out << " DV for parameter " << i << " at " << params[i].funcletOff() << " = " << params[i].phpCode()->data() << std::endl; } } const EHEntVec& ehtab = shared()->m_ehtab; for (EHEntVec::const_iterator it = ehtab.begin(); it != ehtab.end(); ++it) { bool catcher = it->m_ehtype == EHEnt::EHType_Catch; out << " EH " << (catcher ? "Catch" : "Fault") << " for " << it->m_base << ":" << it->m_past; if (it->m_parentIndex != -1) { out << " outer EH " << it->m_parentIndex; } if (it->m_iterId != -1) { out << " iterId " << it->m_iterId; } if (catcher) { out << std::endl; for (EHEnt::CatchVec::const_iterator it2 = it->m_catches.begin(); it2 != it->m_catches.end(); ++it2) { out << " Handle " << m_unit->lookupLitstrId(it2->first)->data() << " at " << it2->second; } } else { out << " to " << it->m_fault; } if (it->m_parentIndex != -1) { out << " parentIndex " << it->m_parentIndex; } out << std::endl; } }
void Func::init(int numParams, bool isGenerator) { // For methods, we defer setting the full name until m_cls is initialized m_maybeIntercepted = -1; if (!preClass()) { setNewFuncId(); setFullName(); } else { m_fullName = 0; } if (isSpecial(m_name)) { /* * i) We dont want these compiler generated functions to * appear in backtraces. * * ii) 86sinit and 86pinit construct NameValueTableWrappers * on the stack. So we MUST NOT allow those to leak into * the backtrace (since the backtrace will outlive the * variables). */ m_attrs = m_attrs | AttrNoInjection; } #ifdef DEBUG m_magic = kMagic; #endif assert(m_name); initPrologues(numParams, isGenerator); }
void cgInstanceOfIfaceVtable(IRLS& env, const IRInstruction* inst) { auto const iface = inst->extra<InstanceOfIfaceVtable>()->cls; auto const slot = iface->preClass()->ifaceVtableSlot(); auto const dst = dstLoc(env, inst, 0).reg(); auto const rcls = srcLoc(env, inst, 0).reg(); auto& v = vmain(env); auto const sf = v.makeReg(); emitCmpVecLen(v, sf, static_cast<int32_t>(slot), rcls[Class::vtableVecLenOff()]); cond( v, CC_A, sf, dst, [&] (Vout& v) { auto const vtableVec = v.makeReg(); emitLdLowPtr(v, rcls[Class::vtableVecOff()], vtableVec, sizeof(LowPtr<Class::VtableVecSlot>)); auto const ifaceOff = slot * sizeof(Class::VtableVecSlot) + offsetof(Class::VtableVecSlot, iface); auto const sf = v.makeReg(); emitCmpLowPtr<Class>(v, sf, iface, vtableVec[ifaceOff]); auto tmp = v.makeReg(); v << setcc{CC_E, sf, tmp}; return tmp; }, [&] (Vout& v) { return v.cns(false); } ); }
void Func::init(int numParams) { // For methods, we defer setting the full name until m_cls is initialized m_maybeIntercepted = s_interceptsEnabled ? -1 : 0; if (!preClass()) { setNewFuncId(); setFullName(); } else { m_fullName = 0; } #ifdef DEBUG m_magic = kMagic; #endif ASSERT(m_name); initPrologues(numParams); }
void cgCall(IRLS& env, const IRInstruction* inst) { auto const sp = srcLoc(env, inst, 0).reg(); auto const fp = srcLoc(env, inst, 1).reg(); auto const extra = inst->extra<Call>(); auto const callee = extra->callee; auto const argc = extra->numParams; auto& v = vmain(env); auto& vc = vcold(env); auto const catchBlock = label(env, inst->taken()); auto const calleeSP = sp[cellsToBytes(extra->spOffset.offset)]; auto const calleeAR = calleeSP + cellsToBytes(argc); v << store{fp, calleeAR + AROFF(m_sfp)}; v << storeli{safe_cast<int32_t>(extra->after), calleeAR + AROFF(m_soff)}; if (extra->fcallAwait) { // This clobbers any flags that might have already been set on the callee // AR (e.g., by SpillFrame), but this is okay because there should never be // any conflicts; see the documentation in act-rec.h. auto const imm = static_cast<int32_t>( ActRec::encodeNumArgsAndFlags(argc, ActRec::Flags::IsFCallAwait) ); v << storeli{imm, calleeAR + AROFF(m_numArgsAndFlags)}; } auto const isNativeImplCall = callee && callee->builtinFuncPtr() && !callee->nativeFuncPtr() && argc == callee->numParams(); if (isNativeImplCall) { // The assumption here is that for builtins, the generated func contains // only a single opcode (NativeImpl), and there are no non-argument locals. if (do_assert) { assertx(argc == callee->numLocals()); assertx(callee->numIterators() == 0); auto addr = callee->getEntry(); while (peek_op(addr) == Op::AssertRATL) { addr += instrLen(addr); } assertx(peek_op(addr) == Op::NativeImpl); assertx(addr + instrLen(addr) == callee->unit()->entry() + callee->past()); } v << store{v.cns(mcg->ustubs().retHelper), calleeAR + AROFF(m_savedRip)}; if (callee->attrs() & AttrMayUseVV) { v << storeqi{0, calleeAR + AROFF(m_invName)}; } v << lea{calleeAR, rvmfp()}; emitCheckSurpriseFlagsEnter(v, vc, fp, Fixup(0, argc), catchBlock); auto const builtinFuncPtr = callee->builtinFuncPtr(); TRACE(2, "Calling builtin preClass %p func %p\n", callee->preClass(), builtinFuncPtr); // We sometimes call this while curFunc() isn't really the builtin, so make // sure to record the sync point as if we are inside the builtin. if (FixupMap::eagerRecord(callee)) { auto const syncSP = v.makeReg(); v << lea{calleeSP, syncSP}; emitEagerSyncPoint(v, callee->getEntry(), rvmtl(), rvmfp(), syncSP); } // Call the native implementation. This will free the locals for us in the // normal case. In the case where an exception is thrown, the VM unwinder // will handle it for us. auto const done = v.makeBlock(); v << vinvoke{CallSpec::direct(builtinFuncPtr), v.makeVcallArgs({{rvmfp()}}), v.makeTuple({}), {done, catchBlock}, Fixup(0, argc)}; env.catch_calls[inst->taken()] = CatchCall::CPP; v = done; // The native implementation already put the return value on the stack for // us, and handled cleaning up the arguments. We have to update the frame // pointer and the stack pointer, and load the return value into the return // register so the trace we are returning to has it where it expects. // TODO(#1273094): We should probably modify the actual builtins to return // values via registers using the C ABI and do a reg-to-reg move. loadTV(v, inst->dst(), dstLoc(env, inst, 0), rvmfp()[AROFF(m_r)], true); v << load{rvmfp()[AROFF(m_sfp)], rvmfp()}; emitRB(v, Trace::RBTypeFuncExit, callee->fullName()->data()); return; } v << lea{calleeAR, rvmfp()}; if (RuntimeOption::EvalHHIRGenerateAsserts) { v << syncvmsp{v.cns(0x42)}; constexpr uint64_t kUninitializedRIP = 0xba5eba11acc01ade; emitImmStoreq(v, kUninitializedRIP, rvmfp()[AROFF(m_savedRip)]); } // Emit a smashable call that initially calls a recyclable service request // stub. The stub and the eventual targets take rvmfp() as an argument, // pointing to the callee ActRec. auto const target = callee ? mcg->ustubs().immutableBindCallStub : mcg->ustubs().bindCallStub; auto const done = v.makeBlock(); v << callphp{target, php_call_regs(), {{done, catchBlock}}}; env.catch_calls[inst->taken()] = CatchCall::PHP; v = done; auto const dst = dstLoc(env, inst, 0); v << defvmret{dst.reg(0), dst.reg(1)}; }
void Func::getFuncInfo(ClassInfo::MethodInfo* mi) const { ASSERT(mi); if (info() != NULL) { // Very large operator=() invocation. *mi = *info(); // Deep copy the vectors of mi-owned pointers. cloneMembers(mi->parameters); cloneMembers(mi->staticVariables); } else { // hphpc and hphpi set the ClassInfo::VariableArguments attribute if the // method contains a call to func_get_arg, func_get_args, or func_num_args. // We don't do this in the VM currently and hopefully we never will need to. int attr = 0; if (m_attrs & AttrReference) attr |= ClassInfo::IsReference; if (m_attrs & AttrAbstract) attr |= ClassInfo::IsAbstract; if (m_attrs & AttrFinal) attr |= ClassInfo::IsFinal; if (m_attrs & AttrProtected) attr |= ClassInfo::IsProtected; if (m_attrs & AttrPrivate) attr |= ClassInfo::IsPrivate; if (m_attrs & AttrStatic) attr |= ClassInfo::IsStatic; if (!(attr & ClassInfo::IsProtected || attr & ClassInfo::IsPrivate)) { attr |= ClassInfo::IsPublic; } if (preClass() && (!strcasecmp(m_name->data(), "__construct") || (!(preClass()->attrs() & AttrTrait) && !strcasecmp(m_name->data(), preClass()->name()->data()) && !preClass()->hasMethod(String("__construct").get())))) { attr |= ClassInfo::IsConstructor; } if (attr == 0) attr = ClassInfo::IsNothing; mi->attribute = (ClassInfo::Attribute)attr; mi->name = m_name->data(); mi->file = m_unit->filepath()->data(); mi->line1 = line1(); mi->line2 = line2(); if (docComment() && !docComment()->empty()) { mi->docComment = docComment()->data(); } // Get the parameter info for (unsigned i = 0; i < unsigned(m_numParams); ++i) { ClassInfo::ParameterInfo* pi = new ClassInfo::ParameterInfo; attr = 0; if (byRef(i)) { attr |= ClassInfo::IsReference; } if (attr == 0) { attr = ClassInfo::IsNothing; } const ParamInfoVec& params = shared()->m_params; const ParamInfo& fpi = params[i]; pi->attribute = (ClassInfo::Attribute)attr; pi->name = shared()->m_localNames[i]->data(); if (params.size() <= i || !fpi.hasDefaultValue()) { pi->value = NULL; pi->valueText = ""; } else { if (fpi.hasScalarDefaultValue()) { // Most of the time the default value is scalar, so we can // avoid evaling in the common case pi->value = strdup(f_serialize( tvAsVariant((TypedValue*)&fpi.defaultValue())).get()->data()); } else { // Eval PHP code to get default value, and serialize the result. Note // that access of undefined class constants can cause the eval() to // fatal. Zend lets such fatals propagate, so don't bother catching // exceptions here. CVarRef v = g_vmContext->getEvaledArg(fpi.phpCode()); pi->value = strdup(f_serialize(v).get()->data()); } // This is a raw char*, but its lifetime should be at least as long // as the the Func*. At this writing, it's a merged anon string // owned by ParamInfo. pi->valueText = fpi.phpCode()->data(); } pi->type = fpi.typeConstraint().exists() ? fpi.typeConstraint().typeName()->data() : ""; mi->parameters.push_back(pi); } // XXX ConstantInfo is abused to store static variable metadata, and // although ConstantInfo::callbacks provides a mechanism for registering // callbacks, it does not pass enough information through for the callback // functions to know the function context whence the callbacks came. // Furthermore, the callback mechanism isn't employed in a fashion that // would allow repeated introspection to reflect updated values. // Supporting introspection of static variable values will require // different plumbing than currently exists in ConstantInfo. const SVInfoVec& staticVars = shared()->m_staticVars; for (SVInfoVec::const_iterator it = staticVars.begin(); it != staticVars.end(); ++it) { ClassInfo::ConstantInfo* ci = new ClassInfo::ConstantInfo; ci->name = *(String*)(&(*it).name); if ((*it).phpCode != NULL) { ci->valueLen = (*it).phpCode->size(); ci->valueText = (*it).phpCode->data(); } else { ci->valueLen = 0; ci->valueText = ""; } mi->staticVariables.push_back(ci); } } }