bool TypeConstraint::checkTypeAliasNonObj(const TypedValue* tv) const { assert(tv->m_type != KindOfObject); // this checks when tv is not an object assert(!isSelf() && !isParent()); auto p = getTypeAliasOrClassWithAutoload(m_namedEntity, m_typeName); auto td = p.first; auto c = p.second; // Common case is that we actually find the alias: if (td) { if (td->nullable && tv->m_type == KindOfNull) return true; return td->any || equivDataTypes(td->kind, tv->m_type); } // Otherwise, this isn't a proper type alias, but it *might* be a // first-class enum. Check if the type is an enum and check the // constraint if it is. We only need to do this when the underlying // type is not an object, since only int and string can be enums. if (c && isEnum(c)) { auto dt = c->enumBaseTy(); // For an enum, if the underlying type is mixed, we still require // it is either an int or a string! if (dt) { return equivDataTypes(*dt, tv->m_type); } else { return IS_INT_TYPE(tv->m_type) || IS_STRING_TYPE(tv->m_type); } } return false; }
bool TypeConstraint::checkPrimitive(DataType dt) const { assert(m_type.m_dt != KindOfObject); assert(dt != KindOfRef); if (nullable() && IS_NULL_TYPE(dt)) return true; return equivDataTypes(m_type.m_dt, dt); }
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); }
bool TypeConstraint::checkTypedefNonObj(const TypedValue* tv) const { assert(tv->m_type != KindOfObject); // this checks when tv is not an object assert(!isSelf() && !isParent()); auto const td = getTypedefWithAutoload(m_namedEntity, m_typeName); if (!td) return false; if (td->nullable && IS_NULL_TYPE(tv->m_type)) return true; return td->kind == KindOfAny || equivDataTypes(td->kind, tv->m_type); }
bool TypeConstraint::checkTypeAliasNonObj(const TypedValue* tv) const { assert(tv->m_type != KindOfObject); assert(isObject()); auto p = getTypeAliasOrClassWithAutoload(m_namedEntity, m_typeName); auto td = p.first; auto c = p.second; // Common case is that we actually find the alias: if (td) { if (td->nullable && tv->m_type == KindOfNull) return true; auto result = annotCompat(tv->m_type, td->type, td->klass ? td->klass->name() : nullptr); 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: break; } assert(result == AnnotAction::ObjectCheck); assert(td->type == AnnotType::Object); // Fall through to the check below, since this could be a type // alias to an enum type c = td->klass; } // Otherwise, this isn't a proper type alias, but it *might* be a // first-class enum. Check if the type is an enum and check the // constraint if it is. We only need to do this when the underlying // type is not an object, since only int and string can be enums. if (c && isEnum(c)) { auto dt = c->enumBaseTy(); // For an enum, if the underlying type is mixed, we still require // it is either an int or a string! if (dt) { return equivDataTypes(*dt, tv->m_type); } else { return isIntType(tv->m_type) || isStringType(tv->m_type); } } return false; }
void IRTranslator::translateLtGtOp(const NormalizedInstruction& i) { auto const op = i.op(); assert(op == Op::Lt || op == Op::Lte || op == Op::Gt || op == Op::Gte); auto leftType = m_hhbcTrans.topType(1, DataTypeGeneric); auto rightType = m_hhbcTrans.topType(0, DataTypeGeneric); bool ok = equivDataTypes(leftType.toDataType(), rightType.toDataType()) && leftType.subtypeOfAny(Type::Null, Type::Bool, Type::Int); HHIR_UNIMPLEMENTED_WHEN(!ok, LtGtOp); switch (op) { case Op::Lt : HHIR_EMIT(Lt); case Op::Lte : HHIR_EMIT(Lte); case Op::Gt : HHIR_EMIT(Gt); case Op::Gte : HHIR_EMIT(Gte); default : HHIR_UNIMPLEMENTED(LtGtOp); } }
bool TypeConstraint::check(const 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 (nullable() && IS_NULL_TYPE(tv->m_type)) return true; if (tv->m_type == KindOfObject) { if (!isObjectOrTypedef()) 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()) Class::profileInstanceOf(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) { Class::profileInstanceOf(c->preClass()->name()); } if (c && tv->m_data.pobj->instanceof(c)) { return true; } return !selfOrParentOrCallable && checkTypedefObj(tv); } if (isObjectOrTypedef()) { 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() && checkTypedefNonObj(tv); } return equivDataTypes(m_type.m_dt, tv->m_type); }
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 (isArrayKey()) { return IS_INT_TYPE(tv->m_type) || IS_STRING_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 (isProfileRequest()) 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 HHVM_FN(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 (isProfileRequest() && c) { InstanceBits::profile(c->preClass()->name()); } if (c && tv->m_data.pobj->instanceof(c)) { return true; } return !selfOrParentOrCallable && checkTypeAliasObj(tv); } if (isObjectOrTypeAlias()) { do { switch (tv->m_type) { case KindOfInt64: if (interface_supports_int(m_typeName)) { return true; } continue; case KindOfDouble: if (interface_supports_double(m_typeName)) { return true; } continue; case KindOfStaticString: case KindOfString: if (interface_supports_string(m_typeName)) { return true; } continue; case KindOfArray: if (interface_supports_array(m_typeName)) { return true; } continue; case KindOfUninit: case KindOfNull: case KindOfBoolean: case KindOfObject: case KindOfResource: continue; case KindOfRef: case KindOfClass: break; } not_reached(); } while (0); if (isCallable()) { return HHVM_FN(is_callable)(tvAsCVarRef(tv)); } return isPrecise() && checkTypeAliasNonObj(tv); } return m_type.dt && equivDataTypes(*m_type.dt, tv->m_type); }