Variant f_func_get_arg(int arg_num) { if (hhvm) { CallerFrame cf; ActRec* ar = cf(); if (ar == NULL || arg_num < 0 || arg_num >= ar->numArgs()) { return false; } const int numParams = ar->m_func->numParams(); 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; } else { throw FatalErrorException("bad HPHP code generation"); } }
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; }
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; }
Array createBacktrace(const BacktraceArgs& btArgs) { auto bt = Array::Create(); // If there is a parser frame, put it at the beginning of the backtrace. if (btArgs.m_parserFrame) { bt.append( make_map_array( s_file, btArgs.m_parserFrame->filename, s_line, btArgs.m_parserFrame->lineNumber ) ); } VMRegAnchor _; // If there are no VM frames, we're done. if (!rds::header() || !vmfp()) return bt; int depth = 0; ActRec* fp = nullptr; Offset pc = 0; // Get the fp and pc of the top frame (possibly skipping one frame). if (btArgs.m_skipTop) { fp = getPrevActRec(vmfp(), &pc); // We skipped over the only VM frame, we're done. if (!fp) return bt; } else { fp = vmfp(); auto const unit = fp->func()->unit(); assert(unit); pc = unit->offsetOf(vmpc()); } // Handle the top frame. if (btArgs.m_withSelf) { // Builtins don't have a file and line number. if (!fp->func()->isBuiltin()) { auto const unit = fp->func()->unit(); assert(unit); auto const filename = fp->func()->filename(); ArrayInit frame(btArgs.m_parserFrame ? 4 : 2, ArrayInit::Map{}); frame.set(s_file, Variant{const_cast<StringData*>(filename)}); frame.set(s_line, unit->getLineNumber(pc)); if (btArgs.m_parserFrame) { frame.set(s_function, s_include); frame.set(s_args, Array::Create(btArgs.m_parserFrame->filename)); } bt.append(frame.toVariant()); depth++; } } // Handle the subsequent VM frames. Offset prevPc = 0; for (auto prevFp = getPrevActRec(fp, &prevPc); fp != nullptr && (btArgs.m_limit == 0 || depth < btArgs.m_limit); fp = prevFp, pc = prevPc, prevFp = getPrevActRec(fp, &prevPc)) { // Do not capture frame for HPHP only functions. if (fp->func()->isNoInjection()) continue; ArrayInit frame(7, ArrayInit::Map{}); auto const curUnit = fp->func()->unit(); auto const curOp = *reinterpret_cast<const Op*>(curUnit->at(pc)); auto const isReturning = curOp == Op::RetC || curOp == Op::RetV || curOp == Op::CreateCont || curOp == Op::Await || fp->localsDecRefd(); // Builtins and generators don't have a file and line number if (prevFp && !prevFp->func()->isBuiltin()) { auto const prevUnit = prevFp->func()->unit(); auto prevFile = prevUnit->filepath(); if (prevFp->func()->originalFilename()) { prevFile = prevFp->func()->originalFilename(); } assert(prevFile); frame.set(s_file, Variant{const_cast<StringData*>(prevFile)}); // In the normal method case, the "saved pc" for line number printing is // pointing at the cell conversion (Unbox/Pop) instruction, not the call // itself. For multi-line calls, this instruction is associated with the // subsequent line which results in an off-by-n. We're subtracting one // in order to look up the line associated with the FCall/FCallArray // instruction. Exception handling and the other opcodes (ex. BoxR) // already do the right thing. The emitter associates object access with // the subsequent expression and this would be difficult to modify. auto const opAtPrevPc = *reinterpret_cast<const Op*>(prevUnit->at(prevPc)); Offset pcAdjust = 0; if (opAtPrevPc == Op::PopR || opAtPrevPc == Op::UnboxR || opAtPrevPc == Op::UnboxRNop) { pcAdjust = 1; } frame.set(s_line, prevFp->func()->unit()->getLineNumber(prevPc - pcAdjust)); } // Check for include. String funcname{const_cast<StringData*>(fp->func()->name())}; if (fp->func()->isClosureBody()) { // Strip the file hash from the closure name. String fullName{const_cast<StringData*>(fp->func()->baseCls()->name())}; funcname = fullName.substr(0, fullName.find(';')); } // Check for pseudomain. if (funcname.empty()) { if (!prevFp && !btArgs.m_withPseudoMain) continue; else if (!prevFp) funcname = s_main; else funcname = s_include; } frame.set(s_function, funcname); if (!funcname.same(s_include)) { // Closures have an m_this but they aren't in object context. auto ctx = arGetContextClass(fp); if (ctx != nullptr && !fp->func()->isClosureBody()) { frame.set(s_class, Variant{const_cast<StringData*>(ctx->name())}); if (fp->hasThis() && !isReturning) { if (btArgs.m_withThis) { frame.set(s_object, Object(fp->getThis())); } frame.set(s_type, s_arrow); } else { frame.set(s_type, s_double_colon); } } } bool const mayUseVV = fp->func()->attrs() & AttrMayUseVV; auto const withNames = btArgs.m_withArgNames; auto const withValues = btArgs.m_withArgValues; if (!btArgs.m_withArgNames && !btArgs.m_withArgValues) { // do nothing } else if (funcname.same(s_include)) { if (depth != 0) { auto filepath = const_cast<StringData*>(curUnit->filepath()); frame.set(s_args, make_packed_array(filepath)); } } else if (!RuntimeOption::EnableArgsInBacktraces || isReturning) { // Provide an empty 'args' array to be consistent with hphpc. frame.set(s_args, empty_array()); } else { auto args = Array::Create(); auto const nparams = fp->func()->numNonVariadicParams(); auto const nargs = fp->numArgs(); auto const nformals = std::min<int>(nparams, nargs); if (UNLIKELY(mayUseVV) && UNLIKELY(fp->hasVarEnv() && fp->getVarEnv()->getFP() != fp)) { // VarEnv is attached to eval or debugger frame, other than the current // frame. Access locals thru VarEnv. auto varEnv = fp->getVarEnv(); auto func = fp->func(); for (int i = 0; i < nformals; i++) { auto const argname = func->localVarName(i); auto const tv = varEnv->lookup(argname); Variant val; if (tv != nullptr) { // the variable hasn't been unset val = withValues ? tvAsVariant(tv) : ""; } if (withNames) { args.set(String(const_cast<StringData*>(argname)), val); } else { args.append(val); } } } else { for (int i = 0; i < nformals; i++) { Variant val = withValues ? tvAsVariant(frame_local(fp, i)) : ""; if (withNames) { auto const argname = fp->func()->localVarName(i); args.set(String(const_cast<StringData*>(argname)), val); } else { args.append(val); } } } // Builtin extra args are not stored in varenv. if (UNLIKELY(mayUseVV) && nargs > nparams && fp->hasExtraArgs()) { for (int i = nparams; i < nargs; i++) { auto arg = fp->getExtraArg(i - nparams); args.append(tvAsVariant(arg)); } } frame.set(s_args, args); } if (btArgs.m_withMetadata && !isReturning) { if (UNLIKELY(mayUseVV) && UNLIKELY(fp->hasVarEnv())) { auto tv = fp->getVarEnv()->lookup(s_86metadata.get()); if (tv != nullptr && tv->m_type != KindOfUninit) { frame.set(s_metadata, tvAsVariant(tv)); } } else { auto local = fp->func()->lookupVarId(s_86metadata.get()); if (local != kInvalidId) { auto tv = frame_local(fp, local); if (tv->m_type != KindOfUninit) { frame.set(s_metadata, tvAsVariant(tv)); } } } } bt.append(frame.toVariant()); depth++; } return bt; }
Array createBacktrace(const BacktraceArgs& btArgs) { Array bt = Array::Create(); // If there is a parser frame, put it at the beginning of // the backtrace if (btArgs.m_parserFrame) { bt.append( make_map_array( s_file, btArgs.m_parserFrame->filename, s_line, btArgs.m_parserFrame->lineNumber ) ); } VMRegAnchor _; if (!vmfp()) { // If there are no VM frames, we're done return bt; } int depth = 0; ActRec* fp = nullptr; Offset pc = 0; // Get the fp and pc of the top frame (possibly skipping one frame) { if (btArgs.m_skipTop) { fp = g_context->getPrevVMState(vmfp(), &pc); if (!fp) { // We skipped over the only VM frame, we're done return bt; } } else { fp = vmfp(); Unit *unit = vmfp()->m_func->unit(); assert(unit); pc = unit->offsetOf(vmpc()); } // Handle the top frame if (btArgs.m_withSelf) { // Builtins don't have a file and line number if (!fp->m_func->isBuiltin()) { Unit* unit = fp->m_func->unit(); assert(unit); const char* filename = fp->m_func->filename()->data(); Offset off = pc; ArrayInit frame(btArgs.m_parserFrame ? 4 : 2, ArrayInit::Map{}); frame.set(s_file, filename); frame.set(s_line, unit->getLineNumber(off)); if (btArgs.m_parserFrame) { frame.set(s_function, s_include); frame.set(s_args, Array::Create(btArgs.m_parserFrame->filename)); } bt.append(frame.toVariant()); depth++; } } } // Handle the subsequent VM frames Offset prevPc = 0; for (ActRec* prevFp = g_context->getPrevVMState(fp, &prevPc); fp != nullptr && (btArgs.m_limit == 0 || depth < btArgs.m_limit); fp = prevFp, pc = prevPc, prevFp = g_context->getPrevVMState(fp, &prevPc)) { // do not capture frame for HPHP only functions if (fp->m_func->isNoInjection()) { continue; } ArrayInit frame(7, ArrayInit::Map{}); auto const curUnit = fp->m_func->unit(); auto const curOp = *reinterpret_cast<const Op*>(curUnit->at(pc)); auto const isReturning = curOp == Op::RetC || curOp == Op::RetV || curOp == Op::CreateCont || curOp == Op::Await || fp->localsDecRefd(); // Builtins and generators don't have a file and line number if (prevFp && !prevFp->m_func->isBuiltin() && !fp->resumed()) { auto const prevUnit = prevFp->m_func->unit(); auto prevFile = prevUnit->filepath(); if (prevFp->m_func->originalFilename()) { prevFile = prevFp->m_func->originalFilename(); } assert(prevFile); frame.set(s_file, const_cast<StringData*>(prevFile)); // In the normal method case, the "saved pc" for line number printing is // pointing at the cell conversion (Unbox/Pop) instruction, not the call // itself. For multi-line calls, this instruction is associated with the // subsequent line which results in an off-by-n. We're subtracting one // in order to look up the line associated with the FCall/FCallArray // instruction. Exception handling and the other opcodes (ex. BoxR) // already do the right thing. The emitter associates object access with // the subsequent expression and this would be difficult to modify. auto const opAtPrevPc = *reinterpret_cast<const Op*>(prevUnit->at(prevPc)); Offset pcAdjust = 0; if (opAtPrevPc == OpPopR || opAtPrevPc == OpUnboxR) { pcAdjust = 1; } frame.set(s_line, prevFp->m_func->unit()->getLineNumber(prevPc - pcAdjust)); } // check for include String funcname = const_cast<StringData*>(fp->m_func->name()); if (fp->m_func->isClosureBody()) { static StringData* s_closure_label = makeStaticString("{closure}"); funcname = s_closure_label; } // check for pseudomain if (funcname.empty()) { if (!prevFp) continue; funcname = s_include; } frame.set(s_function, funcname); if (!funcname.same(s_include)) { // Closures have an m_this but they aren't in object context Class* ctx = arGetContextClass(fp); if (ctx != nullptr && !fp->m_func->isClosureBody()) { frame.set(s_class, ctx->name()->data()); if (fp->hasThis() && !isReturning) { if (btArgs.m_withThis) { frame.set(s_object, Object(fp->getThis())); } frame.set(s_type, "->"); } else { frame.set(s_type, "::"); } } } Array args = Array::Create(); if (btArgs.m_ignoreArgs) { // do nothing } else if (funcname.same(s_include)) { if (depth) { args.append(const_cast<StringData*>(curUnit->filepath())); frame.set(s_args, args); } } else if (!RuntimeOption::EnableArgsInBacktraces || isReturning) { // Provide an empty 'args' array to be consistent with hphpc frame.set(s_args, args); } else { const int nparams = fp->m_func->numNonVariadicParams(); int nargs = fp->numArgs(); int nformals = std::min(nparams, nargs); if (UNLIKELY(fp->hasVarEnv() && fp->getVarEnv()->getFP() != fp)) { // VarEnv is attached to eval or debugger frame, other than the current // frame. Access locals thru VarEnv. auto varEnv = fp->getVarEnv(); auto func = fp->func(); for (int i = 0; i < nformals; i++) { TypedValue *arg = varEnv->lookup(func->localVarName(i)); args.append(tvAsVariant(arg)); } } else { for (int i = 0; i < nformals; i++) { TypedValue *arg = frame_local(fp, i); args.append(tvAsVariant(arg)); } } /* builtin extra args are not stored in varenv */ if (nargs > nparams && fp->hasExtraArgs()) { for (int i = nparams; i < nargs; i++) { TypedValue *arg = fp->getExtraArg(i - nparams); args.append(tvAsVariant(arg)); } } frame.set(s_args, args); } bt.append(frame.toVariant()); depth++; } return bt; }