c_Continuation::~c_Continuation() { ActRec* ar = actRec(); if (ar->hasVarEnv()) { ar->getVarEnv()->detach(ar); } else { frame_free_locals_inl(ar, ar->m_func->numLocals()); } }
Variant f_get_called_class() { EagerCallerFrame cf; ActRec* ar = cf(); if (ar) { if (ar->hasThis()) return Variant(ar->getThis()->o_getClassName()); if (ar->hasClass()) return Variant(ar->getClass()->preClass()->name()); } return Variant(false); }
Variant f_func_get_args() { EagerCallerFrame cf; ActRec* ar = cf.actRecForArgs(); if (ar && ar->hasVarEnv() && ar->getVarEnv()->isGlobalScope()) { raise_warning( "func_get_args(): Called from the global scope - no function context" ); return false; } return hhvm_get_frame_args(ar); }
const Func* StaticMethodCache::lookup(RDS::Handle handle, const NamedEntity *ne, const StringData* clsName, const StringData* methName) { StaticMethodCache* thiz = static_cast<StaticMethodCache*> (handleToPtr(handle)); Stats::inc(Stats::TgtCache_StaticMethodMiss); Stats::inc(Stats::TgtCache_StaticMethodHit, -1); TRACE(1, "miss %s :: %s caller %p\n", clsName->data(), methName->data(), __builtin_return_address(0)); Transl::VMRegAnchor _; // needed for lookupClsMethod. ActRec* ar = reinterpret_cast<ActRec*>(vmsp() - kNumActRecCells); const Func* f; VMExecutionContext* ec = g_vmContext; const Class* cls = Unit::loadClass(ne, clsName); if (UNLIKELY(!cls)) { raise_error(Strings::UNKNOWN_CLASS, clsName->data()); } LookupResult res = ec->lookupClsMethod(f, cls, methName, nullptr, // there may be an active this, // but we can just fall through // in that case. arGetContextClass(ec->getFP()), false /*raise*/); if (LIKELY(res == LookupResult::MethodFoundNoThis && !f->isAbstract() && f->isStatic())) { f->validate(); TRACE(1, "fill %s :: %s -> %p\n", clsName->data(), methName->data(), f); // Do the | here instead of on every call. thiz->m_cls = (Class*)(uintptr_t(cls) | 1); thiz->m_func = f; ar->setClass(const_cast<Class*>(cls)); return f; } assert(res != LookupResult::MethodFoundWithThis); // Not possible: no this. // We've already sync'ed regs; this is some hard case, we might as well // just let the interpreter handle this entirely. assert(toOp(*vmpc()) == OpFPushClsMethodD); Stats::inc(Stats::Instr_InterpOneFPushClsMethodD); Stats::inc(Stats::Instr_TC, -1); ec->opFPushClsMethodD(); // Return whatever func the instruction produced; if nothing was // possible we'll either have fataled or thrown. assert(ar->m_func); ar->m_func->validate(); // Don't update the cache; this case was too scary to memoize. TRACE(1, "unfillable miss %s :: %s -> %p\n", clsName->data(), methName->data(), ar->m_func); // Indicate to the caller that there is no work to do. return nullptr; }
c_Continuation::~c_Continuation() { ActRec* ar = actRec(); if (ar->hasVarEnv()) { ar->getVarEnv()->detach(ar); } else { // Free locals, but don't trigger the EventHook for FunctionExit // since the continuation function has already been exited. We // don't want redundant calls. frame_free_locals_inl_no_hook<false>(ar, ar->m_func->numLocals()); } }
void c_Continuation::dupContVar(const StringData* name, TypedValue* src) { ActRec *fp = actRec(); Id destId = fp->m_func->lookupVarId(name); if (destId != kInvalidId) { // Copy the value of the local to the cont object. tvDupFlattenVars(src, frame_local(fp, destId)); } else { if (!fp->hasVarEnv()) { fp->setVarEnv(VarEnv::createLocal(fp)); } fp->getVarEnv()->setWithRef(name, src); } }
void phpDebuggerStepIn() { // If this is called in the middle of a flow command we short-circuit the // other commands auto& req_data = RID(); req_data.setDebuggerStepIn(true); req_data.setDebuggerStepOut(StepOutState::NONE); req_data.setDebuggerNext(false); // Ensure the flow filter is fresh auto flow_filter = getFlowFilter(); flow_filter->clear(); // Check if the site is valid. VMRegAnchor _; ActRec* fp = vmfp(); PC pc = vmpc(); if (fp == nullptr || pc == nullptr) { TRACE(5, "Could not grab stack or program counter\n"); return; } // Try to get needed context info. Bail if we can't const Func* func = fp->func(); const Unit* unit = func != nullptr ? func->unit() : nullptr; if (func == nullptr || func == nullptr) { TRACE(5, "Could not grab the current unit or function\n"); return; } // We use line1 here because it works better than line0 in our // bytecode-source mapping. int line; SourceLoc source_loc; if (unit->getSourceLoc(unit->offsetOf(pc), source_loc)) { line = source_loc.line1; } else { TRACE(5, "Could not grab the current line number\n"); return; } TRACE(3, "Prepare location filter for %s:%d, unit %p:\n", unit->filepath()->data(), line, unit); // Get offset ranges for the whole line. OffsetRangeVec ranges; if (!unit->getOffsetRanges(line, ranges)) { ranges.clear(); } flow_filter->addRanges(unit, ranges); }
int64 f_func_num_args() { if (hhvm) { CallerFrame cf; ActRec* ar = cf(); if (ar == NULL) { return -1; } return ar->numArgs(); } else { // we shouldn't be here, since code generation will inline this function ASSERT(false); return -1; } }
int64_t f_func_num_args() { EagerCallerFrame cf; ActRec* ar = cf.actRecForArgs(); if (ar == NULL) { return -1; } if (ar->hasVarEnv() && ar->getVarEnv()->isGlobalScope()) { raise_warning( "func_num_args(): Called from the global scope - no function context" ); return -1; } return ar->numArgs(); }
int DebuggerProxy::getStackDepth() { TRACE(2, "DebuggerProxy::getStackDepth\n"); int depth = 0; VMExecutionContext* context = g_vmContext; ActRec *fp = context->getFP(); if (!fp) return 0; ActRec *prev = fp->arGetSfp(); while (fp != prev) { fp = prev; prev = fp->arGetSfp(); depth++; } return depth; }
c_Generator::~c_Generator() { if (LIKELY(getState() == State::Done)) { return; } assert(getState() != State::Running); tvRefcountedDecRef(m_key); tvRefcountedDecRef(m_value); // Free locals, but don't trigger the EventHook for FunctionReturn since // the generator has already been exited. We don't want redundant calls. ActRec* ar = actRec(); frame_free_locals_inl_no_hook<false>(ar, ar->func()->numLocals()); }
int64_t HHVM_FUNCTION(func_num_args) { EagerCallerFrame cf; ActRec* ar = cf.actRecForArgs(); if (ar == nullptr) { return -1; } if (ar->func()->isPseudoMain()) { raise_warning( "func_num_args(): Called from the global scope - no function context" ); return -1; } return ar->numArgs(); }
void c_Continuation::dupContVar(const StringData* name, TypedValue* src) { ActRec *fp = actRec(); Id destId = fp->m_func->lookupVarId(name); if (destId != kInvalidId) { // Copy the value of the local to the cont object. tvDupFlattenVars(src, frame_local(fp, destId)); } else { if (!fp->hasVarEnv()) { // This VarEnv may potentially outlive the most recently stack-allocated // VarEnv, so we need to heap allocate it. fp->setVarEnv(VarEnv::createLocalOnHeap(fp)); } fp->getVarEnv()->setWithRef(name, src); } }
ALWAYS_INLINE static int64_t func_num_args_impl() { EagerCallerFrame cf; ActRec* ar = cf.actRecForArgs(); if (ar == nullptr) { return -1; } if (ar->func()->isPseudoMain()) { raise_warning( "func_num_args(): Called from the global scope - no function context" ); return -1; } return ar->numArgs(); }
Variant f_get_called_class() { CallerFrame cf; ActRec* ar = cf(); if (ar == NULL) { return Variant(false); } if (ar->hasThis()) { ObjectData* obj = ar->getThis(); return obj->o_getClassName(); } else if (ar->hasClass()) { return ar->getClass()->preClass()->name()->data(); } else { return Variant(false); } }
ALWAYS_INLINE static Variant func_get_arg_impl(int arg_num) { CallerFrame cf; ActRec* ar = cf.actRecForArgs(); if (ar == nullptr) { return false; } if (ar->func()->isPseudoMain()) { raise_warning( "func_get_arg(): Called from the global scope - no function context" ); return false; } if (arg_num < 0) { raise_warning( "func_get_arg(): The argument number should be >= 0" ); return false; } if (arg_num >= ar->numArgs()) { raise_warning( "func_get_arg(): Argument %d not passed to function", arg_num ); return false; } const int numParams = ar->m_func->numNonVariadicParams(); if (arg_num < numParams) { // Formal parameter. Value is on the stack. TypedValue* loc = (TypedValue*)(uintptr_t(ar) - (arg_num + 1) * sizeof(TypedValue)); return tvAsVariant(loc); } const int numArgs = ar->numArgs(); const int extraArgs = numArgs - numParams; // Not a formal parameter. Value is potentially in the // ExtraArgs/VarEnv. const int extraArgNum = arg_num - numParams; if (extraArgNum < extraArgs) { return tvAsVariant(ar->getExtraArg(extraArgNum)); } return false; }
Variant HHVM_FUNCTION(get_called_class) { EagerCallerFrame cf; ActRec* ar = cf(); if (ar) { if (ar->hasThis()) { return Variant(ar->getThis()->getClassName()); } if (ar->hasClass()) { return Variant(ar->getClass()->preClass()->name(), Variant::StaticStrInit{}); } } raise_warning("get_called_class() called from outside a class"); return Variant(false); }
c_Continuation::~c_Continuation() { ActRec* ar = actRec(); // The first local is the object itself, and it wasn't increffed at creation // time (see createContinuation()). Overwrite its type to exempt it from // refcounting here. TypedValue* contLocal = frame_local(ar, 0); assert(contLocal->m_data.pobj == this); contLocal->m_type = KindOfNull; if (ar->hasVarEnv()) { ar->getVarEnv()->detach(ar); } else { frame_free_locals_inl(ar, m_vmFunc->numLocals()); } }
File* GlobStreamWrapper::open(const String& filename, const String& mode, int options, CVarRef context) { // Can't open a glob as a file, it's meant to be opened as a directory // if the function was called via FCallBuiltin, we'll get a bogus name as // the stack frame will be wrong ActRec* ar = g_vmContext->getStackFrame(); const char* fn = (ar != nullptr) ? ar->func()->name()->data() : "OPTIMIZED_BUILTIN"; raise_warning("%s(%s): failed to open stream: " "wrapper does not support stream open", fn, filename.data()); return nullptr; }
c_Continuation *c_Continuation::clone() { const Func *origFunc = m_origFunc; const Func *genFunc = actRec()->m_func; ActRec *fp = g_vmContext->getFP(); c_Continuation* cont = origFunc->isMethod() ? g_vmContext->createContMeth(origFunc, genFunc, fp->getThisOrClass()) : g_vmContext->createContFunc(origFunc, genFunc); cont->copyContinuationVars(actRec()); cont->o_subclassData.u16 = o_subclassData.u16; cont->m_label = m_label; cont->m_index = m_index; cont->m_key = m_key; cont->m_value = m_value; return cont; }
const Func* StaticMethodCache::lookupIR(RDS::Handle handle, const NamedEntity *ne, const StringData* clsName, const StringData* methName, TypedValue* vmfp, TypedValue* vmsp) { StaticMethodCache* thiz = static_cast<StaticMethodCache*> (handleToPtr(handle)); Stats::inc(Stats::TgtCache_StaticMethodMiss); Stats::inc(Stats::TgtCache_StaticMethodHit, -1); TRACE(1, "miss %s :: %s caller %p\n", clsName->data(), methName->data(), __builtin_return_address(0)); ActRec* ar = reinterpret_cast<ActRec*>(vmsp - kNumActRecCells); const Func* f; VMExecutionContext* ec = g_vmContext; const Class* cls = Unit::loadClass(ne, clsName); if (UNLIKELY(!cls)) { raise_error(Strings::UNKNOWN_CLASS, clsName->data()); } LookupResult res = ec->lookupClsMethod(f, cls, methName, nullptr, // there may be an active this, // but we can just fall through // in that case. arGetContextClass((ActRec*)vmfp), false /*raise*/); if (LIKELY(res == LookupResult::MethodFoundNoThis && !f->isAbstract() && f->isStatic())) { f->validate(); TRACE(1, "fill %s :: %s -> %p\n", clsName->data(), methName->data(), f); // Do the | here instead of on every call. thiz->m_cls = (Class*)(uintptr_t(cls) | 1); thiz->m_func = f; ar->setClass(const_cast<Class*>(cls)); return f; } assert(res != LookupResult::MethodFoundWithThis); // Not possible: no this. // Indicate to the IR that it should take even slower path return nullptr; }
c_Continuation *c_Continuation::Clone(ObjectData* obj) { auto thiz = static_cast<c_Continuation*>(obj); const Func *origFunc = thiz->m_origFunc; const Func *genFunc = thiz->actRec()->m_func; ActRec *fp = g_vmContext->getFP(); c_Continuation* cont = origFunc->isMethod() ? g_vmContext->createContMeth(origFunc, genFunc, fp->getThisOrClass()) : g_vmContext->createContFunc(origFunc, genFunc); cont->copyContinuationVars(thiz->actRec()); cont->o_subclassData.u16 = thiz->o_subclassData.u16; cont->m_label = thiz->m_label; cont->m_index = thiz->m_index; cont->m_key = thiz->m_key; cont->m_value = thiz->m_value; return cont; }
// stack trace helper static ProfileStackTrace getStackTrace() { ProfileStackTrace trace; if (g_context.isNull()) return trace; VMRegAnchor _; ActRec *fp = vmfp(); if (!fp) return trace; PC pc = vmpc(); const Func *f = fp->m_func; Unit *u = f->unit(); Offset off = pc - u->entry(); for (;;) { trace.push_back({ f, off, fp->resumed() }); fp = g_context->getPrevVMStateUNSAFE(fp, &off); if (!fp) break; f = fp->m_func; } return trace; }
// stack trace helper static ProfileStackTrace getStackTrace() { ProfileStackTrace trace; if (g_context.isNull()) return trace; JIT::VMRegAnchor _; ActRec *fp = g_context->getFP(); if (!fp) return trace; PC pc = g_context->getPC(); const Func *f = fp->m_func; Unit *u = f->unit(); Offset off = pc - u->entry(); for (;;) { trace.push_back({ f, off, fp->inGenerator() }); fp = g_context->getPrevVMState(fp, &off); if (!fp) break; f = fp->m_func; } return trace; }
void TranslatorX64::fCallArrayHelper(const Offset pcOff, const Offset pcNext) { DECLARE_FRAME_POINTER(framePtr); ActRec* fp = (ActRec*)framePtr->m_savedRbp; VMExecutionContext *ec = g_vmContext; ec->m_fp = fp; ec->m_stack.top() = sp; ec->m_pc = fp->unit()->at(pcOff); PC pc = fp->unit()->at(pcNext); tl_regState = VMRegState::CLEAN; bool runFunc = ec->doFCallArray(pc); sp = ec->m_stack.top(); tl_regState = VMRegState::DIRTY; if (!runFunc) return; ec->m_fp->m_savedRip = framePtr->m_savedRip; // smash our return and frame pointer chain framePtr->m_savedRip = (uint64_t)ec->m_fp->m_func->getFuncBody(); framePtr->m_savedRbp = (uint64_t)ec->m_fp; }
Variant f_get_called_class() { if (hhvm) { CallerFrame cf; ActRec* ar = cf(); if (ar == NULL) { return Variant(false); } if (ar->hasThis()) { ObjectData* obj = ar->getThis(); return obj->o_getClassName(); } else if (ar->hasClass()) { return ar->getClass()->preClass()->name()->data(); } else { return Variant(false); } } else { CStrRef cls = FrameInjection::GetStaticClassName( ThreadInfo::s_threadInfo.getNoCheck()); return cls.size() ? Variant(cls.get()) : Variant(false); } }
void Injection::execute() const { if (m_builtin) { ASSERT(m_callback); // Execute function in runtime m_callback(m_arg); return; } // Execute php code piece TypedValue retval; VarEnv *varEnv = NULL; ActRec *cfpSave = NULL; ObjectData *this_ = NULL; Class *cls = NULL; ActRec *fp = g_vmContext->getFP(); if (fp) { if (!fp->hasVarEnv()) { fp->m_varEnv = VarEnv::createLazyAttach(fp); } varEnv = fp->m_varEnv; cfpSave = varEnv->getCfp(); if (fp->hasThis()) { this_ = fp->getThis(); } else if (fp->hasClass()) { cls = fp->getClass(); } } // Note: For now we don't merge analysis code's class and function. // Later we might decide to do so g_vmContext->invokeFunc(&retval, m_unit->getMain(), Array::Create(), this_, cls, varEnv, NULL, NULL); if (varEnv) { varEnv->setCfp(cfpSave); } }
void XDebugProfiler::enableTracing(const String& filename, int64_t opts) { assert(!m_tracingEnabled); // Attempt to open the passed filename. php5 xdebug doesn't enable tracing // if we cannot open the file, so we need to open it now as opposed to when we // actually do the writing in order to ensure we handle this case. We keep the // file handle open in order to ensure we can still write on tracing stop FILE* file; if (opts & k_XDEBUG_TRACE_APPEND) { file = fopen(filename.data(), "a"); } else { file = fopen(filename.data(), "w"); } // If file is null, opening the passed filename failed. php5 xdebug doesn't // do anything in this case, but we should probably notify the user if (file == nullptr) { raise_warning("xdebug profiler failed to open tracing file %s for writing.", filename.data()); return; } m_tracingEnabled = true; m_tracingStartIdx = m_nextFrameIdx; m_tracingFilename = filename; m_tracingFile = file; m_tracingOpts = opts; // If we're not at the top level, need to grab the call sites for each frame // on the stack. VMRegAnchor _; Offset offset; ActRec* fp = vmfp(); while ((fp = g_context->getPrevVMState(fp, &offset)) != nullptr) { FrameData frame; frame.func = fp->func(); frame.line = fp->unit()->getLineNumber(offset); m_tracingStartFrameData.push_back(frame); } }
zval* ZendExecutionStack::getArg(int i) { auto& stack = getStack(); auto& entry = stack.m_stack.back(); switch (entry.mode) { case ZendStackMode::HHVM_STACK: { ActRec* ar = (ActRec*)entry.value; const int numNonVaradic = ar->m_func->numNonVariadicParams(); TypedValue* arg; if (i < numNonVaradic) { arg = (TypedValue*)ar - i - 1; } else if (i < ar->numArgs()) { arg = ar->getExtraArg(i - numNonVaradic); } else { if (!stack.m_nullArg) { stack.m_nullArg = RefData::Make(make_tv<KindOfNull>()); } return stack.m_nullArg; } zBoxAndProxy(arg); return arg->m_data.pref; } case ZendStackMode::SIDE_STACK: { // Zend puts the number of args as the last thing on the stack int numargs = uintptr_t(entry.value); assert(numargs < 4096); assert(i < numargs); zval* zv = (zval*) stack.m_stack[stack.m_stack.size() - 1 - numargs + i].value; zv->assertValid(); return zv; } } not_reached(); return nullptr; }
bool EventHook::RunInterceptHandler(ActRec* ar) { const Func* func = ar->func(); if (LIKELY(func->maybeIntercepted() == 0)) return true; // Intercept only original generator / async function calls, not resumption. if (ar->resumed()) return true; Variant* h = get_intercept_handler(func->fullNameStr(), &func->maybeIntercepted()); if (!h) return true; /* * In production mode, only functions that we have assumed can be * intercepted during static analysis should actually be * intercepted. */ if (RuntimeOption::RepoAuthoritative && !RuntimeOption::EvalJitEnableRenameFunction) { if (!(func->attrs() & AttrInterceptable)) { raise_error("fb_intercept was used on a non-interceptable function (%s) " "in RepoAuthoritative mode", func->fullName()->data()); } } VMRegAnchor _; PC savePc = vmpc(); Variant doneFlag = true; Variant called_on; if (ar->hasThis()) { called_on = Variant(ar->getThis()); } else if (ar->hasClass()) { // For static methods, give handler the name of called class called_on = Variant(const_cast<StringData*>(ar->getClass()->name())); } Variant intArgs = PackedArrayInit(5) .append(VarNR(ar->func()->fullName())) .append(called_on) .append(get_frame_args_with_ref(ar)) .append(h->asCArrRef()[1]) .appendRef(doneFlag) .toArray(); Variant ret = vm_call_user_func(h->asCArrRef()[0], intArgs); if (doneFlag.toBoolean()) { Offset pcOff; ActRec* outer = g_context->getPrevVMState(ar, &pcOff); frame_free_locals_inl_no_hook<true>(ar, ar->func()->numLocals()); Stack& stack = vmStack(); stack.top() = (Cell*)(ar + 1); cellDup(*ret.asCell(), *stack.allocTV()); vmfp() = outer; vmpc() = outer ? outer->func()->unit()->at(pcOff) : nullptr; return false; } vmfp() = ar; vmpc() = savePc; return true; }