void TypeConstraint::init() { if (UNLIKELY(s_typeNamesToTypes.empty())) { const struct Pair { const StringData* name; Type type; } pairs[] = { { makeStaticString("HH\\bool"), { KindOfBoolean, MetaType::Precise }}, { makeStaticString("HH\\int"), { KindOfInt64, MetaType::Precise }}, { makeStaticString("HH\\float"), { KindOfDouble, MetaType::Precise }}, { makeStaticString("HH\\string"), { KindOfString, MetaType::Precise }}, { makeStaticString("array"), { KindOfArray, MetaType::Precise }}, { makeStaticString("HH\\resource"), { KindOfResource, MetaType::Precise }}, { makeStaticString("HH\\num"), { KindOfDouble, MetaType::Number }}, { makeStaticString("self"), { KindOfObject, MetaType::Self }}, { makeStaticString("parent"), { KindOfObject, MetaType::Parent }}, { makeStaticString("callable"), { KindOfObject, MetaType::Callable }}, }; for (unsigned i = 0; i < sizeof(pairs) / sizeof(Pair); ++i) { s_typeNamesToTypes[pairs[i].name] = pairs[i].type; } } if (isTypeVar()) { // We kept the type variable type constraint to correctly check child // classes implementing abstract methods or interfaces. m_type.dt = KindOfInvalid; m_type.metatype = MetaType::Precise; return; } if (m_typeName == nullptr) { m_type.dt = KindOfInvalid; m_type.metatype = MetaType::Precise; return; } Type dtype; TRACE(5, "TypeConstraint: this %p type %s, nullable %d\n", this, m_typeName->data(), isNullable()); auto const mptr = folly::get_ptr(s_typeNamesToTypes, m_typeName); if (mptr) dtype = *mptr; if (!mptr || !(isHHType() || dtype.dt == KindOfArray || dtype.metatype == MetaType::Parent || dtype.metatype == MetaType::Self || dtype.metatype == MetaType::Callable)) { TRACE(5, "TypeConstraint: this %p no such type %s, treating as object\n", this, m_typeName->data()); m_type = { KindOfObject, MetaType::Precise }; m_namedEntity = Unit::GetNamedEntity(m_typeName); TRACE(5, "TypeConstraint: NamedEntity: %p\n", m_namedEntity); return; } m_type = dtype; assert(m_type.dt != KindOfStaticString); assert(IMPLIES(isParent(), m_type.dt == KindOfObject)); assert(IMPLIES(isSelf(), m_type.dt == KindOfObject)); assert(IMPLIES(isCallable(), m_type.dt == KindOfObject)); }
bool TypeConstraint::checkPrimitive(DataType dt) const { assert(m_type.dt != KindOfObject); assert(dt != KindOfRef); if (isNullable() && IS_NULL_TYPE(dt)) return true; return equivDataTypes(m_type.dt, dt); }
void TypeConstraint::verifyFail(const Func* func, int paramNum, const TypedValue* tv) const { Transl::VMRegAnchor _; std::ostringstream fname; fname << func->fullName()->data() << "()"; const StringData* tn = typeName(); if (isSelf()) { selfToTypeName(func, &tn); } else if (isParent()) { parentToTypeName(func, &tn); } auto const givenType = describe_actual_type(tv); if (isExtended()) { // Extended type hints raise warnings instead of recoverable // errors for now, to ease migration (we used to not check these // at all at runtime). assert( (isSoft() || isNullable()) && "Only nullable and soft extended type hints are currently implemented"); raise_debugging( "Argument %d to %s must be of type %s, %s given", paramNum + 1, fname.str().c_str(), fullName().c_str(), givenType); } else { raise_recoverable_error( "Argument %d passed to %s must be an instance of %s, %s given", paramNum + 1, fname.str().c_str(), tn->data(), givenType); } }
bool TypeConstraint::checkPrimitive(DataType dt) const { assert(m_type.dt != KindOfObject); assert(dt != KindOfRef); if (isNullable() && dt == KindOfNull) return true; if (isNumber()) { return IS_INT_TYPE(dt) || IS_DOUBLE_TYPE(dt); } return equivDataTypes(m_type.dt, dt); }
void CMAny::calcLastPos(CMStateSet& toSet) const { // If we are an epsilon node, then the last pos is an empty set if (isNullable()) toSet.zeroBits(); // Otherwise, its just the one bit of our position else toSet.setBit(fPosition); }
set<int> SyntaxSymbolAttributeCache::getFirst(const int *begin, const int *end) { set<int> r; for (; begin != end; ++begin) { auto &set = getFirst(*begin); r.insert(set.begin(), set.end()); if (!isNullable(*begin)) break; } return r; }
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() ); } }
Value* ColumnTinyint::createEmptyDefaultVal() const { if (isNullable()) { return new ValueTinyint( ); } else { return new ValueTinyint( 0 ); } }
Value* ColumnInteger::createEmptyDefaultVal() const { if (isNullable()) { return new ValueInteger( ); } else { return new ValueInteger( 0 ); } }
inline void CMLeaf::calcLastPos(CMStateSet& toSet) const { // If we are an epsilon node, then the last pos is an empty set if (isNullable()) { toSet.zeroBits(); return; } // Otherwise, its just the one bit of our position toSet.setBit(fPosition); }
std::string TypeConstraint::displayName(const Func* func /*= nullptr*/) const { const StringData* tn = typeName(); std::string name; if (isSoft()) { name += '@'; } if (isNullable() && isExtended()) { name += '?'; } if (func && isSelf()) { selfToTypeName(func, &tn); name += tn->data(); } else if (func && isParent()) { parentToTypeName(func, &tn); name += tn->data(); } else { const char* str = tn->data(); auto len = tn->size(); if (len > 3 && tolower(str[0]) == 'h' && tolower(str[1]) == 'h' && str[2] == '\\') { bool strip = false; const char* stripped = str + 3; switch (len - 3) { case 3: strip = (!strcasecmp(stripped, "int") || !strcasecmp(stripped, "num")); break; case 4: strip = !strcasecmp(stripped, "bool"); break; case 5: strip = !strcasecmp(stripped, "float"); break; case 6: strip = !strcasecmp(stripped, "string"); break; case 8: strip = (!strcasecmp(stripped, "resource") || !strcasecmp(stripped, "noreturn") || !strcasecmp(stripped, "arraykey")); break; default: break; } if (strip) { str = stripped; } } name += str; } return name; }
DataTypePtr FunctionCoalesce::getReturnTypeImpl(const DataTypes & arguments) const { /// Skip all NULL arguments. If any argument is non-Nullable, skip all next arguments. DataTypes filtered_args; filtered_args.reserve(arguments.size()); for (const auto & arg : arguments) { if (arg->onlyNull()) continue; filtered_args.push_back(arg); if (!arg->isNullable()) break; } DataTypes new_args; for (size_t i = 0; i < filtered_args.size(); ++i) { bool is_last = i + 1 == filtered_args.size(); if (is_last) { new_args.push_back(filtered_args[i]); } else { new_args.push_back(std::make_shared<DataTypeUInt8>()); new_args.push_back(removeNullable(filtered_args[i])); } } if (new_args.empty()) return std::make_shared<DataTypeNullable>(std::make_shared<DataTypeNothing>()); if (new_args.size() == 1) return new_args.front(); auto res = FunctionMultiIf{context}.getReturnTypeImpl(new_args); /// if last argument is not nullable, result should be also not nullable if (!new_args.back()->isNullable() && res->isNullable()) res = removeNullable(res); return res; }
void TypeConstraint::init() { if (isTypeVar()) { // We kept the type variable type constraint to correctly check child // classes implementing abstract methods or interfaces. m_type.dt = folly::none; m_type.metatype = MetaType::Precise; return; } if (m_typeName == nullptr) { m_type.dt = folly::none; m_type.metatype = MetaType::Precise; return; } Type dtype; TRACE(5, "TypeConstraint: this %p type %s, nullable %d\n", this, m_typeName->data(), isNullable()); auto const mptr = typeNameToType(m_typeName); if (mptr) dtype = *mptr; if (!mptr || !(isHHType() || dtype.dt == KindOfArray || dtype.dt == KindOfBoolean || dtype.dt == KindOfString || dtype.dt == KindOfInt64 || dtype.dt == KindOfDouble || dtype.dt == KindOfResource || dtype.metatype == MetaType::ArrayKey || dtype.metatype == MetaType::Number || dtype.metatype == MetaType::Parent || dtype.metatype == MetaType::Self || dtype.metatype == MetaType::Callable)) { TRACE(5, "TypeConstraint: this %p no such type %s, treating as object\n", this, m_typeName->data()); m_type = { KindOfObject, MetaType::Precise }; m_namedEntity = NamedEntity::get(m_typeName); TRACE(5, "TypeConstraint: NamedEntity: %p\n", m_namedEntity); return; } m_type = dtype; assert(m_type.dt != KindOfStaticString); assert(IMPLIES(isParent(), m_type.dt == KindOfObject)); assert(IMPLIES(isSelf(), m_type.dt == KindOfObject)); assert(IMPLIES(isCallable(), m_type.dt == KindOfObject)); }
void TypeConstraint::init() { if (m_typeName == nullptr || isTypeVar() || isTypeConstant()) { m_type = Type::Mixed; return; } TRACE(5, "TypeConstraint: this %p type %s, nullable %d\n", this, m_typeName->data(), isNullable()); auto const mptr = nameToAnnotType(m_typeName); if (mptr) { m_type = *mptr; assert(getAnnotDataType(m_type) != KindOfPersistentString); return; } TRACE(5, "TypeConstraint: this %p no such type %s, treating as object\n", this, m_typeName->data()); m_type = Type::Object; m_namedEntity = NamedEntity::get(m_typeName); TRACE(5, "TypeConstraint: NamedEntity: %p\n", m_namedEntity.get()); }
bool SyntaxSymbolAttributeCache::isNullable(int sym) { if (!isNonTerm(sym)) return false; if (m_nullable.count(sym) > 0) return m_nullable[sym]; bool &b = m_nullable[sym]; int pbegin, pend; getNonTermProductRange(sym, pbegin, pend); for (int pid = pbegin; pid < pend; ++pid) { auto &body = getProductBody(pid); bool nullable = true; for (auto i : body) { if (!isNullable(i)) { nullable = false; break; } } if (nullable) { b = true; break; } } return b; }
void TypeConstraint::verifyFail(const Func* func, int paramNum, const TypedValue* tv) const { Transl::VMRegAnchor _; std::ostringstream fname; fname << func->fullName()->data() << "()"; const StringData* tn = typeName(); if (isSelf()) { selfToTypeName(func, &tn); } else if (isParent()) { parentToTypeName(func, &tn); } auto const givenType = describe_actual_type(tv); if (isExtended()) { if (isSoft()) { // Soft type hints raise warnings instead of recoverable // errors by design, to ease migration. raise_warning( "Argument %d passed to %s must be of type %s, %s given", paramNum + 1, fname.str().c_str(), fullName().c_str(), givenType); } else if (isNullable()) { // This error message is slightly different from the normal case // (fullName() vs tn) raise_recoverable_error( "Argument %d passed to %s must be of type %s, %s given", paramNum + 1, fname.str().c_str(), fullName().c_str(), givenType); } else { assert(false && "Only nullable and soft extended type hints are currently implemented"); } } else { raise_recoverable_error( "Argument %d passed to %s must be an instance of %s, %s given", paramNum + 1, fname.str().c_str(), tn->data(), givenType); } }
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 TypeConstraint::check(TypedValue* tv, const Func* func) const { assert(hasConstraint() && !isTypeVar() && !isMixed() && !isTypeConstant()); // This is part of the interpreter runtime; perf matters. if (tv->m_type == KindOfRef) { tv = tv->m_data.pref->tv(); } if (isNullable() && tv->m_type == KindOfNull) { return true; } if (tv->m_type == KindOfObject) { // Perfect match seems common enough to be worth skipping the hash // table lookup. const Class *c = nullptr; if (isObject()) { if (m_typeName->isame(tv->m_data.pobj->getVMClass()->name())) { if (isProfileRequest()) InstanceBits::profile(m_typeName); return true; } // We can't save the Class* since it moves around from request // to request. assert(m_namedEntity); c = Unit::lookupClass(m_namedEntity); } else { switch (metaType()) { case MetaType::Self: selfToClass(func, &c); break; case MetaType::Parent: parentToClass(func, &c); break; case MetaType::Callable: return is_callable(tvAsCVarRef(tv)); case MetaType::Precise: case MetaType::Number: case MetaType::ArrayKey: case MetaType::Dict: case MetaType::Vec: return false; case MetaType::Mixed: // We assert'd at the top of this function that the // metatype cannot be Mixed not_reached(); } } if (isProfileRequest() && c) { InstanceBits::profile(c->preClass()->name()); } if (c && tv->m_data.pobj->instanceof(c)) { return true; } return isObject() && checkTypeAliasObj(tv->m_data.pobj->getVMClass()); } auto const result = annotCompat(tv->m_type, m_type, m_typeName); switch (result) { case AnnotAction::Pass: return true; case AnnotAction::Fail: return false; case AnnotAction::CallableCheck: return is_callable(tvAsCVarRef(tv)); case AnnotAction::DictCheck: return tv->m_data.parr->isDict(); case AnnotAction::VecCheck: return tv->m_data.parr->isVecArray(); case AnnotAction::ObjectCheck: assert(isObject()); return checkTypeAliasNonObj(tv); } not_reached(); }
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() ); } } }
Def* IdentityAnalyzer::do_cknull(UnaryStmt* instr) { if (!isNullable(type(instr->value_in()))) return identity(instr); return def_; }
bool TypeConstraint::check(TypedValue* tv, const Func* func) const { assert(hasConstraint()); // This is part of the interpreter runtime; perf matters. if (tv->m_type == KindOfRef) { tv = tv->m_data.pref->tv(); } if (isNullable() && tv->m_type == KindOfNull) return true; if (isNumber()) { return IS_INT_TYPE(tv->m_type) || IS_DOUBLE_TYPE(tv->m_type); } if (tv->m_type == KindOfObject) { if (!isObjectOrTypeAlias()) return false; // Perfect match seems common enough to be worth skipping the hash // table lookup. if (m_typeName->isame(tv->m_data.pobj->getVMClass()->name())) { if (shouldProfile()) InstanceBits::profile(m_typeName); return true; } const Class *c = nullptr; const bool selfOrParentOrCallable = isSelf() || isParent() || isCallable(); if (selfOrParentOrCallable) { if (isSelf()) { selfToClass(func, &c); } else if (isParent()) { parentToClass(func, &c); } else { assert(isCallable()); return f_is_callable(tvAsCVarRef(tv)); } } else { // We can't save the Class* since it moves around from request // to request. assert(m_namedEntity); c = Unit::lookupClass(m_namedEntity); } if (shouldProfile() && c) { InstanceBits::profile(c->preClass()->name()); } if (c && tv->m_data.pobj->instanceof(c)) { return true; } return !selfOrParentOrCallable && checkTypeAliasObj(tv); } if (isObjectOrTypeAlias()) { switch (tv->m_type) { case KindOfArray: if (interface_supports_array(m_typeName)) { return true; } break; case KindOfString: case KindOfStaticString: if (interface_supports_string(m_typeName)) { return true; } break; case KindOfInt64: if (interface_supports_int(m_typeName)) { return true; } break; case KindOfDouble: if (interface_supports_double(m_typeName)) { return true; } break; default: break; } if (isCallable()) { return f_is_callable(tvAsCVarRef(tv)); } return isPrecise() && checkTypeAliasNonObj(tv); } return equivDataTypes(m_type.dt, tv->m_type); }