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_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); }
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); } }
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(); }
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 Variant func_get_arg_impl(int arg_num) { CallerFrame cf; ActRec* ar = cf.actRecForArgs(); if (ar == nullptr) { return false; } if (ar->hasVarEnv() && ar->getVarEnv()->isGlobalScope()) { 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; }
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()); } }
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; }
bool UrlFile::open(const String& input_url, const String& mode) { String url = input_url; const char* modestr = mode.c_str(); if (strchr(modestr, '+') || strchr(modestr, 'a') || strchr(modestr, 'w')) { std::string msg = "cannot open a url stream for write/append operation: "; msg += url.c_str(); m_error = msg; return false; } HttpClient http(m_timeout, m_maxRedirect); auto ctx = this->getStreamContext(); if (ctx) { http.setStreamContextOptions(ctx->getOptions()); } m_response.clear(); if (!m_proxyHost.empty()) { http.proxy(m_proxyHost, m_proxyPort, m_proxyUsername, m_proxyPassword); } HeaderMap *pHeaders = nullptr; HeaderMap requestHeaders; if (!m_headers.empty()) { pHeaders = &requestHeaders; for (ArrayIter iter(m_headers); iter; ++iter) { requestHeaders[std::string(iter.first().toString().data())]. push_back(iter.second().toString().data()); } } Variant user = f_parse_url(url, k_PHP_URL_USER); if (user.isString()) { Variant pass = f_parse_url(url, k_PHP_URL_PASS); http.auth(user.toString().c_str(), pass.toString().c_str()); url = HHVM_FN(preg_replace)( s_remove_user_pass_pattern, s_remove_user_pass_replace, url, 1 ).toString(); } int code; std::vector<String> responseHeaders; if (m_get) { code = http.get(url.c_str(), m_response, pHeaders, &responseHeaders); } else { code = http.request(m_method, url.c_str(), m_postData.data(), m_postData.size(), m_response, pHeaders, &responseHeaders); } m_responseHeaders.reset(); for (unsigned int i = 0; i < responseHeaders.size(); i++) { m_responseHeaders.append(responseHeaders[i]); } VMRegAnchor vra; ActRec* fp = vmfp(); while (fp->skipFrame()) { fp = g_context->getPrevVMState(fp); } auto id = fp->func()->lookupVarId(s_http_response_header.get()); if (id != kInvalidId) { auto tvTo = frame_local(fp, id); Variant varFrom(m_responseHeaders); const auto tvFrom(varFrom.asTypedValue()); if (tvTo->m_type == KindOfRef) { tvTo = tvTo->m_data.pref->tv(); } tvDup(*tvFrom, *tvTo); } else if ((fp->func()->attrs() & AttrMayUseVV) && fp->hasVarEnv()) { fp->getVarEnv()->set(s_http_response_header.get(), Variant(m_responseHeaders).asTypedValue()); } /* * If code == 0, Curl failed to connect; per PHP5, ignore_errors just means * to not worry if we get an http resonse code that isn't between 200 and 400, * but we shouldn't ignore other errors. * all status codes in the 2xx range are defined by the specification as * successful; * all status codes in the 3xx range are for redirection, and so also should * never fail. */ if ((code >= 200 && code < 400) || (m_ignoreErrors && code != 0)) { setName(url.toCppString()); m_data = const_cast<char*>(m_response.data()); m_len = m_response.size(); return true; } else { m_error = http.getLastError().c_str(); return false; } }
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; }