void RepoQuery::getTypedValue(int iCol, TypedValue& tv) { const void* blob; size_t size; getBlob(iCol, blob, size); tvWriteUninit(&tv); if (size > 0) { String s = String((const char*)blob, size, CopyString); Variant v = unserialize_from_string(s); if (v.isString()) { v = String(makeStaticString(v.asCStrRef().get())); } else if (v.isArray()) { v = Array(ArrayData::GetScalarArray(v.asCArrRef().get())); } else { // Serialized variants and objects shouldn't ever make it into the repo. assert(!isRefcountedType(v.getType())); } tvAsVariant(&tv) = v; } }
const Variant& APCLocalArray::GetValueRef(const ArrayData* adIn, ssize_t pos) { auto const ad = asApcArray(adIn); auto const sv = ad->m_arr->getValue(pos); if (LIKELY(ad->m_localCache != nullptr)) { assert(unsigned(pos) < ad->m_arr->capacity()); TypedValue* tv = &ad->m_localCache[pos]; if (tv->m_type != KindOfUninit) { return tvAsCVarRef(tv); } } else { static_assert(KindOfUninit == 0, "must be 0 since we use req::calloc"); unsigned cap = ad->m_arr->capacity(); ad->m_localCache = req::calloc_raw_array<TypedValue>(cap); } auto const tv = &ad->m_localCache[pos]; tvAsVariant(tv) = sv->toLocal(); assert(tv->m_type != KindOfUninit); return tvAsCVarRef(tv); }
HOT_FUNC CVarRef SharedMap::getValueRef(ssize_t pos) const { SharedVariant *sv = m_arr->getValue(pos); DataType t = sv->getType(); if (!IS_REFCOUNTED_TYPE(t)) return sv->asCVarRef(); if (LIKELY(m_localCache != nullptr)) { assert(unsigned(pos) < m_arr->arrCap()); TypedValue* tv = &m_localCache[pos]; if (tv->m_type != KindOfUninit) return tvAsCVarRef(tv); } else { static_assert(KindOfUninit == 0, "must be 0 since we use smart_calloc"); unsigned cap = m_arr->arrCap(); m_localCache = (TypedValue*) smart_calloc(cap, sizeof(TypedValue)); } TypedValue* tv = &m_localCache[pos]; tvAsVariant(tv) = sv->toLocal(); assert(tv->m_type != KindOfUninit); return tvAsCVarRef(tv); }
bool tvCoerceParamToStringInPlace(TypedValue* tv) { assert(tvIsPlausible(*tv)); tvUnboxIfNeeded(tv); switch (tv->m_type) { case KindOfArray: return false; case KindOfObject: if (tv->m_data.pobj->hasToString()) { tvAsVariant(tv) = tv->m_data.pobj->invokeToString(); return true; } return false; case KindOfResource: return false; default: break; } tvCastToStringInPlace(tv); return true; }
ArrayData* PackedArray::AppendWithRef(ArrayData* adIn, const Variant& v, bool copy) { assert(checkInvariants(adIn)); auto const ad = copy ? CopyAndResizeIfNeeded(adIn) : ResizeIfNeeded(adIn); if (UNLIKELY(!ad)) { auto const mixed = copy ? ToMixedCopy(adIn) : ToMixed(adIn); // XXX: constness return MixedArray::AppendRef(mixed, const_cast<Variant&>(v), copy); } if (ad->m_pos == ArrayData::invalid_index) { ad->m_pos = ad->m_size; } auto& dst = packedData(ad)[ad->m_size++]; dst.m_type = KindOfNull; tvAsVariant(&dst).setWithRef(v); return ad; }
/* * Helper for empty array -> packed transitions. Creates an array * with one element. The element is transferred into the array (should * already be incref'd). */ ALWAYS_INLINE ArrayLval EmptyArray::MakePackedInl(TypedValue tv) { auto const cap = kPackedSmallSize; auto const ad = static_cast<ArrayData*>( MM().objMalloc(sizeof(ArrayData) + cap * sizeof(TypedValue)) ); assert(cap == CapCode::ceil(cap).code); ad->m_sizeAndPos = 1; // size=1, pos=0 ad->initHeader(CapCode::exact(cap), HeaderKind::Packed, 1); auto const lval = reinterpret_cast<TypedValue*>(ad + 1); lval->m_data = tv.m_data; lval->m_type = tv.m_type; assert(ad->kind() == ArrayData::kPackedKind); assert(ad->m_size == 1); assert(ad->m_pos == 0); assert(ad->hasExactlyOneRef()); assert(PackedArray::checkInvariants(ad)); return { ad, &tvAsVariant(lval) }; }
void objOffsetSet(TypedValue* base, CVarRef offset, TypedValue* val, bool validate /* = true */) { if (validate) { objArrayAccess(base); } static StringData* sd__offsetSet = StringData::GetStaticString("offsetSet"); ObjectData* obj = base->m_data.pobj; if (LIKELY(obj->isInstance())) { Instance* instance = static_cast<Instance*>(obj); const Func* method = instance->methodNamed(sd__offsetSet); ASSERT(method != NULL); TypedValue tvResult; tvWriteUninit(&tvResult); instance->invokeUserMethod(&tvResult, method, CREATE_VECTOR2(offset, tvAsCVarRef(val))); tvRefcountedDecRef(&tvResult); } else { tvAsVariant(base).getArrayAccess() ->o_invoke(sd__offsetSet, CREATE_VECTOR2(offset, tvAsCVarRef(val))); } }
ArrayData* PackedArray::LvalInt(ArrayData* adIn, int64_t k, Variant*& ret, bool copy) { assert(checkInvariants(adIn)); if (LIKELY(size_t(k) < adIn->m_size)) { auto const ad = copy ? Copy(adIn) : adIn; ret = &tvAsVariant(&packedData(ad)[k]); return ad; } // We can stay packed if the index is m_size, and the operation does // the same thing as LvalNew. if (size_t(k) == adIn->m_size) return LvalNew(adIn, ret, copy); // Promote-to-mixed path, we know the key is new and should be using // findForNewInsert but aren't yet TODO(#2606310). auto const mixed = copy ? ToMixedCopy(adIn) : ToMixed(adIn); return mixed->addLvalImpl(k, ret); }
Variant f_constant(const String& name) { if (!name.get()) return uninit_null(); const char *data = name.data(); int len = name.length(); // slice off starting backslash bool hadInitialBackslash = false; if (len > 0 && data[0] == '\\') { data += 1; len -= 1; hadInitialBackslash = true; } char *colon; if ((colon = (char*)memchr(data, ':', len)) && colon[1] == ':') { // class constant int classNameLen = colon - data; char *constantName = colon + 2; Class* cls = getClassByName(data, classNameLen); if (cls) { String cnsName(constantName, data + len - constantName, CopyString); Cell cns = cls->clsCnsGet(cnsName.get()); if (cns.m_type != KindOfUninit) { return cellAsCVarRef(cns); } } raise_warning("Couldn't find constant %s", data); } else { TypedValue* cns; if (hadInitialBackslash) { String s(data, len, CopyString); cns = Unit::loadCns(s.get()); } else { cns = Unit::loadCns(name.get()); } if (cns) return tvAsVariant(cns); } return uninit_null(); }
bool tvCoerceParamToArrayInPlace(TypedValue* tv) { assert(tvIsPlausible(*tv)); tvUnboxIfNeeded(tv); switch (tv->m_type) { case KindOfUninit: case KindOfNull: case KindOfBoolean: case KindOfInt64: case KindOfDouble: case KindOfPersistentString: case KindOfString: case KindOfPersistentVec: case KindOfVec: case KindOfPersistentDict: case KindOfDict: case KindOfPersistentKeyset: case KindOfKeyset: return false; case KindOfPersistentArray: case KindOfArray: return true; case KindOfObject: if (LIKELY(tv->m_data.pobj->isCollection())) { tvAsVariant(tv) = tv->m_data.pobj->toArray(); return true; } return false; case KindOfResource: return false; case KindOfRef: case KindOfClass: break; } not_reached(); }
bool tvCoerceParamToStringInPlace(TypedValue* tv) { assert(tvIsPlausible(*tv)); tvUnboxIfNeeded(tv); switch (tv->m_type) { case KindOfUninit: case KindOfNull: case KindOfBoolean: case KindOfInt64: case KindOfDouble: case KindOfPersistentString: case KindOfString: // In PHP 7 mode handling of null types is stricter if (tv->m_type == KindOfNull && RuntimeOption::PHP7_ScalarTypes) { return false; } tvCastToStringInPlace(tv); return true; case KindOfPersistentArray: case KindOfArray: return false; case KindOfObject: if (tv->m_data.pobj->hasToString()) { tvAsVariant(tv) = tv->m_data.pobj->invokeToString(); return true; } return false; case KindOfResource: return false; case KindOfRef: case KindOfClass: break; } not_reached(); }
CVarRef APCLocalArray::getValueRef(ssize_t pos) const { APCHandle *sv = m_arr->getValue(pos); DataType t = sv->getType(); if (!IS_REFCOUNTED_TYPE(t)) { return APCTypedValue::fromHandle(sv)->asCVarRef(); } if (LIKELY(m_localCache != nullptr)) { assert(unsigned(pos) < m_arr->capacity()); TypedValue* tv = &m_localCache[pos]; if (tv->m_type != KindOfUninit) { return tvAsCVarRef(tv); } } else { static_assert(KindOfUninit == 0, "must be 0 since we use smart_calloc"); unsigned cap = m_arr->capacity(); m_localCache = (TypedValue*) smart_calloc(cap, sizeof(TypedValue)); } TypedValue* tv = &m_localCache[pos]; tvAsVariant(tv) = sv->toLocal(); assert(tv->m_type != KindOfUninit); return tvAsCVarRef(tv); }
ArrayData *VectorArray::setRef(int64 k, CVarRef v, bool copy) { if (UNLIKELY(copy)) { if (inRange(k, m_size) || k == m_size) { VectorArray *a = NEW(VectorArray)(this); a->VectorArray::setRef(k, v, false); return a; } } else { if (inRange(k, m_size)) { tvAsVariant(&m_elems[k]).assignRef(v); return nullptr; } else if (k == m_size) { checkSize(); tvAsUninitializedVariant(&m_elems[k]).constructRefHelper(v); checkInsertIterator((ssize_t)k); m_size++; return nullptr; } } ZendArray *a = escalateToZendArray(); a->updateRef(k, v); return a; }
Array StringData::GetConstants() { // Return an array of all defined constants. assert(s_stringDataMap); Array a(Transl::TargetCache::s_constants); for (StringDataMap::const_iterator it = s_stringDataMap->begin(); it != s_stringDataMap->end(); ++it) { if (it->second) { TypedValue& tv = Transl::TargetCache::handleToRef<TypedValue>(it->second); if (tv.m_type != KindOfUninit) { StrNR key(const_cast<StringData*>(it->first)); a.set(key, tvAsVariant(&tv), true); } else if (tv.m_data.pref) { StrNR key(const_cast<StringData*>(it->first)); ClassInfo::ConstantInfo* ci = (ClassInfo::ConstantInfo*)(void*)tv.m_data.pref; a.set(key, ci->getDeferredValue(), true); } } } return a; }
ArrayData *VectorArray::pop(Variant &value) { if (UNLIKELY(!m_size)) { value.setNull(); return nullptr; } if (UNLIKELY(getCount() > 1)) { value = tvAsCVarRef(&m_elems[m_size - 1]); if (m_size == 1) { return StaticEmptyVectorArray::Get(); } VectorArray *a = NEW(VectorArray)(this, 0, m_size - 1); a->m_pos = (ssize_t)0; return a; } ssize_t pos = m_size - 1; value = tvAsCVarRef(&m_elems[pos]); tvAsVariant(&m_elems[pos]).~Variant(); assert(m_size && pos == m_size - 1L); m_size--; // To match PHP-like semantics, the pop operation resets the array's // internal iterator m_pos = m_size ? (ssize_t)0 : ArrayData::invalid_index; return nullptr; }
Array lookupDefinedConstants(bool categorize /*= false */) { assert(s_stringDataMap); Array usr(RDS::s_constants()); Array sys; for (StringDataMap::const_iterator it = s_stringDataMap->begin(); it != s_stringDataMap->end(); ++it) { if (it->second.bound()) { Array *tbl = (categorize && RDS::isPersistentHandle(it->second.handle())) ? &sys : &usr; auto& tv = *it->second; if (tv.m_type != KindOfUninit) { StrNR key(const_cast<StringData*>(to_sdata(it->first))); tbl->set(key, tvAsVariant(&tv), true); } else if (tv.m_data.pref) { StrNR key(const_cast<StringData*>(to_sdata(it->first))); ClassInfo::ConstantInfo* ci = (ClassInfo::ConstantInfo*)(void*)tv.m_data.pref; auto cns = ci->getDeferredValue(); if (cns.isInitialized()) { tbl->set(key, cns, true); } } } } if (categorize) { Array ret; ret.set(s_user, usr); ret.set(s_Core, sys); return ret; } else { return usr; } }
/* * Creating a single-element mixed array with a integer key. The * value is already incref'd. */ ArrayLval EmptyArray::MakeMixed(int64_t key, TypedValue val) { auto const ad = reqAllocArray(MixedArray::SmallScale); MixedArray::InitSmall(ad, 1/*count*/, 1/*size*/, (key >= 0) ? key + 1 : 0); auto const data = ad->data(); auto const hash = reinterpret_cast<int32_t*>(data + MixedArray::SmallSize); auto const mask = MixedArray::SmallMask; auto h = hash_int64(key); hash[h & mask] = 0; data[0].setIntKey(key, h); auto& lval = data[0].data; lval.m_data = val.m_data; lval.m_type = val.m_type; assert(ad->kind() == ArrayData::kMixedKind); assert(ad->m_size == 1); assert(ad->m_pos == 0); assert(ad->hasExactlyOneRef()); assert(ad->m_scale == MixedArray::SmallScale); assert(ad->m_used == 1); assert(ad->checkInvariants()); return { ad, &tvAsVariant(&lval) }; }
bool objOffsetIsset(TypedValue& tvRef, ObjectData* base, const Variant& offset, bool validate /* = true */) { auto exists = objOffsetExists(base, offset); // Unless we called ArrayObject::offsetExists, there's nothing more to do if (exists != OffsetExistsResult::IssetIfNonNull) { return (int)exists; } // For ArrayObject::offsetExists, we need to check the value at `offset`. // If it's null, then we return false. TypedValue tvResult; tvWriteUninit(&tvResult); // We can't call the offsetGet method on `base` because users aren't // expecting offsetGet to be called for `isset(...)` expressions, so call // the method on the base ArrayObject class. const Func* method = SystemLib::s_ArrayObjectClass->lookupMethod(s_offsetGet.get()); assert(method != nullptr); g_context->invokeFuncFew(&tvResult, method, base, nullptr, 1, offset.asCell()); return !(tvAsVariant(&tvResult).isNull()); }
/* * Helper for creating a single-element mixed array with a string key. * * Note: the key is not already incref'd, but the value must be. */ NEVER_INLINE ArrayLval EmptyArray::MakeMixed(StringData* key, TypedValue val) { auto const ad = reqAllocArray(MixedArray::SmallScale); MixedArray::InitSmall(ad, 1/*count*/, 1/*size*/, 0/*nextIntKey*/); auto const data = ad->data(); auto const hash = reinterpret_cast<int32_t*>(data + MixedArray::SmallSize); auto const khash = key->hash(); auto const mask = MixedArray::SmallMask; hash[khash & mask] = 0; data[0].setStrKey(key, khash); auto& lval = data[0].data; lval.m_data = val.m_data; lval.m_type = val.m_type; assert(ad->m_size == 1); assert(ad->m_pos == 0); assert(ad->m_scale == MixedArray::SmallScale); assert(ad->kind() == ArrayData::kMixedKind); assert(ad->hasExactlyOneRef()); assert(ad->m_used == 1); assert(ad->checkInvariants()); return { ad, &tvAsVariant(&lval) }; }
void c_Continuation::copyContinuationVars(ActRec* fp) { // For functions that contain only named locals, we can copy TVs // right to the local space. static const StringData* thisStr = s_this.get(); bool skipThis; if (fp->hasVarEnv()) { Stats::inc(Stats::Cont_CreateVerySlow); Array definedVariables = fp->getVarEnv()->getDefinedVariables(); skipThis = definedVariables.exists(s_this, true); for (ArrayIter iter(definedVariables); !iter.end(); iter.next()) { if (iter.first().getStringData()->same(s___cont__.get())) { continue; } dupContVar(iter.first().getStringData(), const_cast<TypedValue *>(iter.secondRef().asTypedValue())); } } else { const Func *genFunc = actRec()->m_func; skipThis = genFunc->lookupVarId(thisStr) != kInvalidId; // skip local 0 because that's the old continuation for (Id i = 1; i < genFunc->numNamedLocals(); ++i) { dupContVar(genFunc->localVarName(i), frame_local(fp, i)); } } // If $this is used as a local inside the body and is not provided // by our containing environment, just prefill it here instead of // using InitThisLoc inside the body if (!skipThis && fp->hasThis()) { Id id = actRec()->m_func->lookupVarId(thisStr); if (id != kInvalidId) { tvAsVariant(frame_local(actRec(), id)) = fp->getThis(); } } }
void VectorArray::onSetEvalScalar() { for (uint i = 0; i < m_size; i++) { tvAsVariant(&m_elems[i]).setEvalScalar(); } }
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; }
void TypeConstraint::verifyFail(const Func* func, TypedValue* tv, int id, bool useStrictTypes) const { VMRegAnchor _; std::string name = displayName(func); auto const givenType = describe_actual_type(tv, isHHType()); if (UNLIKELY(!useStrictTypes)) { if (auto dt = underlyingDataType()) { // In non-strict mode we may be able to coerce a type failure. For object // typehints there is no possible coercion in the failure case, but HNI // builtins currently only guard on kind not class so the following wil // generate false positives for objects. if (*dt != KindOfObject) { // HNI conversions implicitly unbox references, this behavior is wrong, // in particular it breaks the way type conversion works for PHP 7 // scalar type hints if (tv->m_type == KindOfRef) { auto inner = tv->m_data.pref->var()->asTypedValue(); if (tvCoerceParamInPlace(inner, *dt)) { tvAsVariant(tv) = tvAsVariant(inner); return; } } else { if (tvCoerceParamInPlace(tv, *dt)) return; } } } } else if (UNLIKELY(!func->unit()->isHHFile() && !RuntimeOption::EnableHipHopSyntax)) { // PHP 7 allows for a widening conversion from Int to Float. We still ban // this in HH files. if (auto dt = underlyingDataType()) { if (*dt == KindOfDouble && tv->m_type == KindOfInt64 && tvCoerceParamToDoubleInPlace(tv)) { return; } } } // Handle return type constraint failures if (id == ReturnId) { std::string msg; if (func->isClosureBody()) { msg = folly::format( "Value returned from {}closure must be of type {}, {} given", func->isAsync() ? "async " : "", name, givenType ).str(); } else { msg = folly::format( "Value returned from {}{} {}() must be of type {}, {} given", func->isAsync() ? "async " : "", func->preClass() ? "method" : "function", func->fullName(), name, givenType ).str(); } if (RuntimeOption::EvalCheckReturnTypeHints >= 2 && !isSoft()) { raise_return_typehint_error(msg); } else { raise_warning_unsampled(msg); } return; } // Handle implicit collection->array conversion for array parameter type // constraints auto c = tvToCell(tv); if (isArray() && !isSoft() && !func->mustBeRef(id) && c->m_type == KindOfObject && c->m_data.pobj->isCollection()) { // To ease migration, the 'array' type constraint will implicitly cast // collections to arrays, provided the type constraint is not soft and // the parameter is not by reference. We raise a notice to let the user // know that there was a type mismatch and that an implicit conversion // was performed. raise_notice( folly::format( "Argument {} to {}() must be of type {}, {} given; argument {} was " "implicitly cast to array", id + 1, func->fullName(), name, givenType, id + 1 ).str() ); tvCastToArrayInPlace(tv); return; } // Handle parameter type constraint failures if (isExtended() && isSoft()) { // Soft extended type hints raise warnings instead of recoverable // errors, to ease migration. raise_warning_unsampled( folly::format( "Argument {} to {}() must be of type {}, {} given", id + 1, func->fullName(), name, givenType ).str() ); } else if (isExtended() && isNullable()) { raise_typehint_error( folly::format( "Argument {} to {}() must be of type {}, {} given", id + 1, func->fullName(), name, givenType ).str() ); } else { auto cls = Unit::lookupClass(m_typeName); if (cls && isInterface(cls)) { raise_typehint_error( folly::format( "Argument {} passed to {}() must implement interface {}, {} given", id + 1, func->fullName(), name, givenType ).str() ); } else { raise_typehint_error( folly::format( "Argument {} passed to {}() must be an instance of {}, {} given", id + 1, func->fullName(), name, givenType ).str() ); } } }
RefData::~RefData() { assert(m_magic == Magic::kMagic); tvAsVariant(&m_tv).~Variant(); }
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; }
/* $Id$ */ #include "zend.h" // builtin-functions has to happen before zend_API since that defines getThis() #include "hphp/runtime/base/builtin-functions.h" #include "zend_API.h" #include "zend_interfaces.h" #include "zend_exceptions.h" #include "hphp/runtime/base/array-init.h" ZEND_API zend_class_entry *zend_ce_traversable; ZEND_API zend_class_entry *zend_ce_aggregate; ZEND_API zend_class_entry *zend_ce_iterator; ZEND_API zend_class_entry *zend_ce_arrayaccess; ZEND_API zend_class_entry *zend_ce_serializable; ZEND_API zval* zend_call_method(zval **object_pp, zend_class_entry *obj_ce, zend_function **fn_proxy, const char *function_name, int function_name_len, zval **retval_ptr_ptr, int param_count, zval* arg1, zval* arg2 TSRMLS_DC) { HPHP::String f_name(function_name, function_name_len, HPHP::CopyString); HPHP::PackedArrayInit paramInit(2); paramInit.append(tvAsVariant(arg1->tv())); paramInit.append(tvAsVariant(arg2->tv())); const HPHP::Array params(paramInit.create()); HPHP::Variant ret = HPHP::vm_call_user_func(f_name, params); auto ref = ret.asRef()->m_data.pref; ref->incRefCount(); *retval_ptr_ptr = ref; return ref; }
ArrayData* EmptyArray::LvalInt(ArrayData*, int64_t k, Variant*& retVar, bool) { auto const ret = k == 0 ? EmptyArray::MakePacked(make_tv<KindOfNull>()) : EmptyArray::MakeMixed(k, make_tv<KindOfNull>()); retVar = &tvAsVariant(ret.second); return ret.first; }
ArrayData* EmptyArray::AppendWithRef(ArrayData*, const Variant& v, bool copy) { auto tv = make_tv<KindOfNull>(); tvAsVariant(&tv).setWithRef(v); return EmptyArray::MakePacked(tv).first; }
ArrayData* EmptyArray::LvalNew(ArrayData*, Variant*& retVar, bool) { auto const ret = EmptyArray::MakePacked(make_tv<KindOfNull>()); retVar = &tvAsVariant(ret.second); return ret.first; }
ArrayData* NameValueTableWrapper::SetRefStr(ArrayData* ad, StringData* k, CVarRef v, bool copy) { auto a = asNVTW(ad); tvAsVariant(a->m_tab->lookupAdd(k)).assignRef(v); return a; }