ArrayData* PackedArray::SetInt(ArrayData* adIn, int64_t k, const Variant& v, bool copy) { assert(checkInvariants(adIn)); // Right now SetInt is used for the AddInt entry point also. This // first branch is the only thing we'd be able to omit if we were // doing AddInt. if (size_t(k) < adIn->m_size) { auto const ad = copy ? Copy(adIn) : adIn; auto& dst = *tvToCell(&packedData(ad)[k]); cellSet(*v.asCell(), dst); // TODO(#3888164): we should restructure things so we don't have to // check KindOfUninit here. if (UNLIKELY(dst.m_type == KindOfUninit)) { dst.m_type = KindOfNull; } return ad; } // Setting the int at the size of the array can keep it in packed // mode---it's the same as an append. if (size_t(k) == adIn->m_size) return Append(adIn, v, copy); // On the promote-to-mixed path, we can use addVal since we know the // key can't exist. auto const mixed = copy ? ToMixedCopy(adIn) : ToMixed(adIn); return mixed->addVal(k, v); }
Object c_AwaitAllWaitHandle::FromMixedArray(const MixedArray* dependencies) { auto const start = dependencies->data(); auto const stop = start + dependencies->iterLimit(); return createAAWH<const MixedArray::Elm*>(start, stop, mixedArrayNext, [](const MixedArray::Elm* elm) { return tvToCell(&elm->data); }); }
ArrayData* GlobalsArray::SetStr(ArrayData* ad, StringData* k, Cell v, bool copy) { auto a = asGlobals(ad); cellSet(v, *tvToCell(a->m_tab->lookupAdd(k))); return a; }
ArrayData* NameValueTableWrapper::SetStr(ArrayData* ad, StringData* k, Cell v, bool copy) { auto a = asNVTW(ad); cellSet(v, *tvToCell(a->m_tab->lookupAdd(k))); return a; }
Object c_AwaitAllWaitHandle::FromPackedArray(const ArrayData* dependencies) { auto const start = packedData(dependencies); auto const stop = start + dependencies->getSize(); return createAAWH<const TypedValue*>(start, stop, [](const TypedValue* tv, UNUSED const TypedValue* limit) { return tv + 1; }, [](const TypedValue* tv) { return tvToCell(tv); }); }
bool objOffsetEmpty(TypedValue& tvRef, ObjectData* base, const Variant& offset, bool validate /* = true */) { if (objOffsetExists(base, offset) == OffsetExistsResult::DoesNotExist) { return true; } TypedValue* result = objOffsetGet(tvRef, base, offset, false); assert(result); return !cellToBool(*tvToCell(result)); }
bool objOffsetEmpty(TypedValue& tvRef, ObjectData* base, CVarRef offset, bool validate /* = true */) { if (!objOffsetExists(base, offset)) { return true; } TypedValue* result = objOffsetGet(tvRef, base, offset, false); assert(result); return !cellToBool(*tvToCell(result)); }
void TypeConstraint::verifyFail(const Func* func, int paramNum, TypedValue* tv) const { JIT::VMRegAnchor _; const StringData* tn = typeName(); if (isSelf()) { selfToTypeName(func, &tn); } else if (isParent()) { parentToTypeName(func, &tn); } auto const givenType = describe_actual_type(tv); auto c = tvToCell(tv); if (isArray() && !isSoft() && !func->mustBeRef(paramNum) && 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", paramNum + 1, func->fullName()->data(), fullName(), givenType, paramNum + 1 ).str() ); tvCastToArrayInPlace(tv); return; } if (isExtended() && isSoft()) { // Soft extended type hints raise warnings instead of recoverable // errors, to ease migration. raise_debugging( "Argument %d to %s() must be of type %s, %s given", paramNum + 1, func->fullName()->data(), fullName().c_str(), givenType); } else if (isExtended() && isNullable()) { raise_typehint_error( folly::format( "Argument {} to {}() must be of type {}, {} given", paramNum + 1, func->fullName()->data(), fullName(), givenType ).str() ); } else { raise_typehint_error( folly::format( "Argument {} passed to {}() must be an instance of {}, {} given", paramNum + 1, func->fullName()->data(), tn->data(), givenType ).str() ); } }
ALWAYS_INLINE TypedValue& getDefaultIfNullCell(TypedValue* tv, TypedValue& def) { if (UNLIKELY(nullptr == tv)) { // refcount is already correct since def was never decrefed return def; } tvRefcountedDecRef(&def); TypedValue* ret = tvToCell(tv); tvRefcountedIncRef(ret); return *ret; }
bool BaseVector::OffsetIsset(ObjectData* obj, TypedValue* key) { assert(key->m_type != KindOfRef); auto vec = static_cast<BaseVector*>(obj); TypedValue* result; if (key->m_type == KindOfInt64) { result = vec->get(key->m_data.num); } else { throwBadKeyType(); result = nullptr; } return result ? !cellIsNull(tvToCell(result)) : false; }
NEVER_INLINE APCHandle::Pair APCObject::ConstructSlow(ObjectData* objectData, ClassOrName name) { Array odProps; objectData->o_getArray(odProps); auto const propCount = odProps.size(); auto size = sizeof(APCObject) + sizeof(Prop) * propCount; auto const apcObj = new (malloc_huge(size)) APCObject(name, propCount); if (!propCount) return {apcObj->getHandle(), size}; auto prop = apcObj->props(); for (ArrayIter it(odProps); !it.end(); it.next(), ++prop) { Variant key(it.first()); assert(key.isString()); auto const rval = it.secondRval(); if (!isNullType(tvToCell(rval).type())) { auto val = APCHandle::Create(VarNR(rval.tv()), false, APCHandleLevel::Inner, true); prop->val = val.handle; size += val.size; } else { prop->val = nullptr; } const String& keySD = key.asCStrRef(); if (!keySD.empty() && *keySD.data() == '\0') { int32_t subLen = keySD.find('\0', 1) + 1; String cls = keySD.substr(1, subLen - 2); if (cls.size() == 1 && cls[0] == '*') { // Protected. prop->ctx = nullptr; } else { // Private. auto* ctx = Unit::lookupClass(cls.get()); if (ctx && ctx->attrs() & AttrUnique) { prop->ctx = ctx; } else { prop->ctx = makeStaticString(cls.get()); } } prop->name = makeStaticString(keySD.substr(subLen)); } else { prop->ctx = nullptr; prop->name = makeStaticString(keySD.get()); } } assert(prop == apcObj->props() + propCount); return {apcObj->getHandle(), size}; }
ArrayData* StructArray::SetStr( ArrayData* ad, StringData* k, Cell v, bool copy ) { auto structArray = asStructArray(ad); auto shape = structArray->shape(); auto result = structArray; auto offset = shape->offsetFor(k); bool isNewProperty = offset == PropertyTable::kInvalidOffset; auto convertToMixedAndAdd = [&]() { auto mixed = copy ? ToMixedCopy(structArray) : ToMixed(structArray); return mixed->addValNoAsserts(k, v); }; if (isNewProperty) { StringData* staticKey; // We don't support adding non-static strings yet. if (k->isStatic()) { staticKey = k; } else { staticKey = lookupStaticString(k); if (!staticKey) return convertToMixedAndAdd(); } auto newShape = shape->transition(staticKey); if (!newShape) return convertToMixedAndAdd(); result = copy ? CopyAndResizeIfNeeded(structArray, newShape) : ResizeIfNeeded(structArray, newShape); offset = result->shape()->offsetFor(staticKey); assert(offset != PropertyTable::kInvalidOffset); TypedValue* dst = &result->data()[offset]; // TODO(#3888164): we should restructure things so we don't have to // check KindOfUninit here. if (UNLIKELY(v.m_type == KindOfUninit)) v = make_tv<KindOfNull>(); cellDup(v, *dst); return result; } if (copy) { result = asStructArray(Copy(structArray)); } assert(offset != PropertyTable::kInvalidOffset); TypedValue* dst = &result->data()[offset]; if (UNLIKELY(v.m_type == KindOfUninit)) v = make_tv<KindOfNull>(); cellSet(v, *tvToCell(dst)); return result; }
ALWAYS_INLINE TypedValue& getDefaultIfNullCell(TypedValue* tv, TypedValue& def) { if (UNLIKELY(nullptr == tv)) { // DecRef of def is done unconditionally by the IR, since there's // a good chance it will be paired with an IncRef and optimized // away. So we need to IncRef here if it is being returned. tvRefcountedIncRef(&def); return def; } TypedValue* ret = tvToCell(tv); tvRefcountedIncRef(ret); return *ret; }
void objOffsetSet(ObjectData* base, const Variant& offset, TypedValue* val, bool validate /* = true */) { if (validate) { objArrayAccess(base); } assert(!base->isCollection()); const Func* method = base->methodNamed(s_offsetSet.get()); assert(method != nullptr); TypedValue tvResult; tvWriteUninit(&tvResult); TypedValue args[2] = { *offset.asCell(), *tvToCell(val) }; g_context->invokeFuncFew(&tvResult, method, base, nullptr, 2, args); tvRefcountedDecRef(&tvResult); }
Object c_AwaitAllWaitHandle::FromPackedArray(const ArrayData* dependencies) { auto const start = reinterpret_cast<const TypedValue*>(dependencies + 1); auto const stop = start + dependencies->getSize(); auto ctx_idx = std::numeric_limits<context_idx_t>::max(); int32_t cnt = 0; for (auto iter = start; iter < stop; ++iter) { prepareChild(tvToCell(iter), ctx_idx, cnt); } if (!cnt) return returnEmpty(); auto result = Alloc(cnt); auto next = &result->m_children[cnt]; for (auto iter = start; iter < stop; ++iter) { addChild(tvToCell(iter), next); } assert(next == &result->m_children[0]); result->initialize(ctx_idx); return Object{std::move(result)}; }
bool objOffsetEmpty( ObjectData* base, TypedValue offset, bool validate /* = true */ ) { if (objOffsetExists(base, offset) == OffsetExistsResult::DoesNotExist) { return true; } auto value = objOffsetGet(base, offset, false); auto result = !cellToBool(*tvToCell(&value)); tvRefcountedDecRef(value); return result; }
static const char* describe_actual_type(const TypedValue* tv, bool isHHType) { tv = tvToCell(tv); switch (tv->m_type) { case KindOfUninit: return "undefined variable"; case KindOfNull: return "null"; case KindOfBoolean: return "bool"; case KindOfInt64: return "int"; case KindOfDouble: return isHHType ? "float" : "double"; case KindOfStaticString: case KindOfString: return "string"; case KindOfArray: return "array"; case KindOfObject: return tv->m_data.pobj->o_getClassName().c_str(); case KindOfResource: return tv->m_data.pres->o_getClassName().c_str(); default: assert(false); } not_reached(); }
void objOffsetSet( ObjectData* base, TypedValue offset, TypedValue* val, bool validate /* = true */ ) { if (validate) { objArrayAccess(base); } assertx(!base->isCollection()); assertx(offset.m_type != KindOfRef); auto const method = base->methodNamed(s_offsetSet.get()); assert(method != nullptr); TypedValue args[2] = { offset, *tvToCell(val) }; g_context->invokeMethodV(base, method, folly::range(args)); }
Object c_AwaitAllWaitHandle::Create(Iter iter) { auto ctx_idx = std::numeric_limits<context_idx_t>::max(); uint32_t cnt = 0; auto toCell = convert ? [](TypedValue tv) { return tvToCell(tv); } : [](TypedValue tv) { return tvAssertCell(tv); }; iter([&](TypedValue v) { prepareChild(toCell(v), ctx_idx, cnt); }); if (!cnt) { return Object{returnEmpty()}; } auto result = Alloc(cnt); auto next = &result->m_children[cnt]; uint32_t idx = cnt - 1; iter([&](TypedValue v) { addChild(toCell(v), next, idx); }); assert(next == &result->m_children[0]); result->initialize(ctx_idx); return Object{std::move(result)}; }
void Generator::done(TypedValue tv) { assert(getState() == State::Running); cellSetNull(m_key); cellSet(*tvToCell(&tv), m_value); setState(State::Done); }
bool tvSame(TypedValue tv1, TypedValue tv2) { assert(tvIsPlausible(tv1)); assert(tvIsPlausible(tv2)); return cellSame(*tvToCell(&tv1), *tvToCell(&tv2)); }
TypedValue* ArrayData::nvGetCell(int64 k) const { TypedValue* tv = (TypedValue*)&get(k, false); return LIKELY(tv != (TypedValue*)&null_variant) ? tvToCell(tv) : nvGetNotFound(k); }
TypedValue* ArrayData::nvGetCell(const StringData* key) const { TypedValue* tv = (TypedValue*)&get(key, false); return LIKELY(tv != (TypedValue*)&null_variant) ? tvToCell(tv) : nvGetNotFound(key); }
TypedValue* NameValueTable::set(const StringData* name, const TypedValue* val) { TypedValue* target = findTypedValue(name); tvSet(*tvToCell(val), *target); return target; }
void TypeConstraint::verifyFail(const Func* func, TypedValue* tv, int id) const { VMRegAnchor _; std::string name = displayName(func); auto const givenType = describe_actual_type(tv, isHHType()); // 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()->data(), name, givenType ).str(); } if (RuntimeOption::EvalCheckReturnTypeHints >= 2 && !isSoft() && (!func->isClosureBody() || !RuntimeOption::EvalSoftClosureReturnTypeHints)) { raise_return_typehint_error(msg); } else { raise_debugging(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()->data(), 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_debugging( folly::format( "Argument {} to {}() must be of type {}, {} given", id + 1, func->fullName()->data(), name, givenType ).str() ); } else if (isExtended() && isNullable()) { raise_typehint_error( folly::format( "Argument {} to {}() must be of type {}, {} given", id + 1, func->fullName()->data(), 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()->data(), name, givenType ).str() ); } else { raise_typehint_error( folly::format( "Argument {} passed to {}() must be an instance of {}, {} given", id + 1, func->fullName()->data(), name, givenType ).str() ); } } }
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() ); } } }
bool tvSame(const TypedValue* tv1, const TypedValue* tv2) { assert(tvIsPlausible(tv1)); assert(tvIsPlausible(tv2)); return cellSame(tvToCell(tv1), tvToCell(tv2)); }