blob: 1b356eec6b78aaed77eb7232dcc0783833ada647 [file] [log] [blame] [edit]
//===-- SemaBoundsSafety.cpp - Bounds Safety specific routines-*- C++ -*---===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
/// \file
/// This file declares semantic analysis functions specific to `-fbounds-safety`
/// (Bounds Safety) and also its attributes when used without `-fbounds-safety`
/// (e.g. `counted_by`)
///
//===----------------------------------------------------------------------===//
#include "clang/Basic/SourceManager.h"
#include "clang/Lex/Lexer.h"
#include "clang/Sema/Initialization.h"
#include "clang/Sema/Sema.h"
namespace clang {
static CountAttributedType::DynamicCountPointerKind
getCountAttrKind(bool CountInBytes, bool OrNull) {
if (CountInBytes)
return OrNull ? CountAttributedType::SizedByOrNull
: CountAttributedType::SizedBy;
return OrNull ? CountAttributedType::CountedByOrNull
: CountAttributedType::CountedBy;
}
static const RecordDecl *GetEnclosingNamedOrTopAnonRecord(const FieldDecl *FD) {
const auto *RD = FD->getParent();
// An unnamed struct is anonymous struct only if it's not instantiated.
// However, the struct may not be fully processed yet to determine
// whether it's anonymous or not. In that case, this function treats it as
// an anonymous struct and tries to find a named parent.
while (RD && (RD->isAnonymousStructOrUnion() ||
(!RD->isCompleteDefinition() && RD->getName().empty()))) {
const auto *Parent = dyn_cast<RecordDecl>(RD->getParent());
if (!Parent)
break;
RD = Parent;
}
return RD;
}
enum class CountedByInvalidPointeeTypeKind {
INCOMPLETE,
SIZELESS,
FUNCTION,
FLEXIBLE_ARRAY_MEMBER,
VALID,
};
bool Sema::CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes,
bool OrNull) {
// Check the context the attribute is used in
unsigned Kind = getCountAttrKind(CountInBytes, OrNull);
if (FD->getParent()->isUnion()) {
Diag(FD->getBeginLoc(), diag::err_count_attr_in_union)
<< Kind << FD->getSourceRange();
return true;
}
const auto FieldTy = FD->getType();
if (FieldTy->isArrayType() && (CountInBytes || OrNull)) {
Diag(FD->getBeginLoc(),
diag::err_count_attr_not_on_ptr_or_flexible_array_member)
<< Kind << FD->getLocation() << /* suggest counted_by */ 1;
return true;
}
if (!FieldTy->isArrayType() && !FieldTy->isPointerType()) {
Diag(FD->getBeginLoc(),
diag::err_count_attr_not_on_ptr_or_flexible_array_member)
<< Kind << FD->getLocation() << /* do not suggest counted_by */ 0;
return true;
}
LangOptions::StrictFlexArraysLevelKind StrictFlexArraysLevel =
LangOptions::StrictFlexArraysLevelKind::IncompleteOnly;
if (FieldTy->isArrayType() &&
!Decl::isFlexibleArrayMemberLike(getASTContext(), FD, FieldTy,
StrictFlexArraysLevel, true)) {
Diag(FD->getBeginLoc(),
diag::err_counted_by_attr_on_array_not_flexible_array_member)
<< Kind << FD->getLocation();
return true;
}
CountedByInvalidPointeeTypeKind InvalidTypeKind =
CountedByInvalidPointeeTypeKind::VALID;
QualType PointeeTy;
int SelectPtrOrArr = 0;
if (FieldTy->isPointerType()) {
PointeeTy = FieldTy->getPointeeType();
SelectPtrOrArr = 0;
} else {
assert(FieldTy->isArrayType());
const ArrayType *AT = getASTContext().getAsArrayType(FieldTy);
PointeeTy = AT->getElementType();
SelectPtrOrArr = 1;
}
// Note: The `Decl::isFlexibleArrayMemberLike` check earlier on means
// only `PointeeTy->isStructureTypeWithFlexibleArrayMember()` is reachable
// when `FieldTy->isArrayType()`.
bool ShouldWarn = false;
if (PointeeTy->isAlwaysIncompleteType() && !CountInBytes) {
// In general using `counted_by` or `counted_by_or_null` on
// pointers where the pointee is an incomplete type are problematic. This is
// because it isn't possible to compute the pointer's bounds without knowing
// the pointee type size. At the same time it is common to forward declare
// types in header files.
//
// E.g.:
//
// struct Handle;
// struct Wrapper {
// size_t size;
// struct Handle* __counted_by(count) handles;
// }
//
// To allow the above code pattern but still prevent the pointee type from
// being incomplete in places where bounds checks are needed the following
// scheme is used:
//
// * When the pointee type might not always be an incomplete type (i.e.
// a type that is currently incomplete but might be completed later
// on in the translation unit) the attribute is allowed by this method
// but later uses of the FieldDecl are checked that the pointee type
// is complete see `BoundsSafetyCheckAssignmentToCountAttrPtr`,
// `BoundsSafetyCheckInitialization`, and
// `BoundsSafetyCheckUseOfCountAttrPtr`
//
// * When the pointee type is always an incomplete type (e.g.
// `void`) the attribute is disallowed by this method because we know the
// type can never be completed so there's no reason to allow it.
InvalidTypeKind = CountedByInvalidPointeeTypeKind::INCOMPLETE;
} else if (PointeeTy->isSizelessType()) {
InvalidTypeKind = CountedByInvalidPointeeTypeKind::SIZELESS;
} else if (PointeeTy->isFunctionType()) {
InvalidTypeKind = CountedByInvalidPointeeTypeKind::FUNCTION;
} else if (PointeeTy->isStructureTypeWithFlexibleArrayMember()) {
if (FieldTy->isArrayType() && !getLangOpts().BoundsSafety) {
// This is a workaround for the Linux kernel that has already adopted
// `counted_by` on a FAM where the pointee is a struct with a FAM. This
// should be an error because computing the bounds of the array cannot be
// done correctly without manually traversing every struct object in the
// array at runtime. To allow the code to be built this error is
// downgraded to a warning.
ShouldWarn = true;
}
InvalidTypeKind = CountedByInvalidPointeeTypeKind::FLEXIBLE_ARRAY_MEMBER;
}
if (InvalidTypeKind != CountedByInvalidPointeeTypeKind::VALID) {
unsigned DiagID = ShouldWarn
? diag::warn_counted_by_attr_elt_type_unknown_size
: diag::err_counted_by_attr_pointee_unknown_size;
Diag(FD->getBeginLoc(), DiagID)
<< SelectPtrOrArr << PointeeTy << (int)InvalidTypeKind
<< (ShouldWarn ? 1 : 0) << Kind << FD->getSourceRange();
return true;
}
// Check the expression
if (!E->getType()->isIntegerType() || E->getType()->isBooleanType()) {
Diag(E->getBeginLoc(), diag::err_count_attr_argument_not_integer)
<< Kind << E->getSourceRange();
return true;
}
auto *DRE = dyn_cast<DeclRefExpr>(E);
if (!DRE) {
Diag(E->getBeginLoc(),
diag::err_count_attr_only_support_simple_decl_reference)
<< Kind << E->getSourceRange();
return true;
}
auto *CountDecl = DRE->getDecl();
FieldDecl *CountFD = dyn_cast<FieldDecl>(CountDecl);
if (auto *IFD = dyn_cast<IndirectFieldDecl>(CountDecl)) {
CountFD = IFD->getAnonField();
}
if (!CountFD) {
Diag(E->getBeginLoc(), diag::err_count_attr_must_be_in_structure)
<< CountDecl << Kind << E->getSourceRange();
Diag(CountDecl->getBeginLoc(),
diag::note_flexible_array_counted_by_attr_field)
<< CountDecl << CountDecl->getSourceRange();
return true;
}
if (FD->getParent() != CountFD->getParent()) {
if (CountFD->getParent()->isUnion()) {
Diag(CountFD->getBeginLoc(), diag::err_count_attr_refer_to_union)
<< Kind << CountFD->getSourceRange();
return true;
}
// Whether CountRD is an anonymous struct is not determined at this
// point. Thus, an additional diagnostic in case it's not anonymous struct
// is done later in `Parser::ParseStructDeclaration`.
auto *RD = GetEnclosingNamedOrTopAnonRecord(FD);
auto *CountRD = GetEnclosingNamedOrTopAnonRecord(CountFD);
if (RD != CountRD) {
Diag(E->getBeginLoc(), diag::err_count_attr_param_not_in_same_struct)
<< CountFD << Kind << FieldTy->isArrayType() << E->getSourceRange();
Diag(CountFD->getBeginLoc(),
diag::note_flexible_array_counted_by_attr_field)
<< CountFD << CountFD->getSourceRange();
return true;
}
}
return false;
}
static void EmitIncompleteCountedByPointeeNotes(Sema &S,
const CountAttributedType *CATy,
NamedDecl *IncompleteTyDecl) {
assert(IncompleteTyDecl == nullptr || isa<TypeDecl>(IncompleteTyDecl));
if (IncompleteTyDecl) {
// Suggest completing the pointee type if its a named typed (i.e.
// IncompleteTyDecl isn't nullptr). Suggest this first as it is more likely
// to be the correct fix.
//
// Note the `IncompleteTyDecl` type is the underlying type which might not
// be the same as `CATy->getPointeeType()` which could be a typedef.
//
// The diagnostic printed will be at the location of the underlying type but
// the diagnostic text will print the type of `CATy->getPointeeType()` which
// could be a typedef name rather than the underlying type. This is ok
// though because the diagnostic will print the underlying type name too.
S.Diag(IncompleteTyDecl->getBeginLoc(),
diag::note_counted_by_consider_completing_pointee_ty)
<< CATy->getPointeeType();
}
// Suggest using __sized_by(_or_null) instead of __counted_by(_or_null) as
// __sized_by(_or_null) doesn't have the complete type restriction.
//
// We use the source range of the expression on the CountAttributedType as an
// approximation for the source range of the attribute. This isn't quite right
// but isn't easy to fix right now.
//
// TODO: Implement logic to find the relevant TypeLoc for the attribute and
// get the SourceRange from that (#113582).
//
// TODO: We should emit a fix-it here.
SourceRange AttrSrcRange = CATy->getCountExpr()->getSourceRange();
S.Diag(AttrSrcRange.getBegin(), diag::note_counted_by_consider_using_sized_by)
<< CATy->isOrNull() << AttrSrcRange;
}
static std::tuple<const CountAttributedType *, QualType>
GetCountedByAttrOnIncompletePointee(QualType Ty, NamedDecl **ND) {
auto *CATy = Ty->getAs<CountAttributedType>();
// Incomplete pointee type is only a problem for
// counted_by/counted_by_or_null
if (!CATy || CATy->isCountInBytes())
return {};
auto PointeeTy = CATy->getPointeeType();
if (PointeeTy.isNull()) {
// Reachable if `CountAttributedType` wraps an IncompleteArrayType
return {};
}
if (!PointeeTy->isIncompleteType(ND))
return {};
return {CATy, PointeeTy};
}
/// Perform Checks for assigning to a `__counted_by` or
/// `__counted_by_or_null` pointer type \param LHSTy where the pointee type
/// is incomplete which is invalid.
///
/// \param S The Sema instance.
/// \param LHSTy The type being assigned to. Checks will only be performed if
/// the type is a `counted_by` or `counted_by_or_null ` pointer.
/// \param RHSExpr The expression being assigned from.
/// \param Action The type assignment being performed
/// \param Loc The SourceLocation to use for error diagnostics
/// \param Assignee The ValueDecl being assigned. This is used to compute
/// the name of the assignee. If the assignee isn't known this can
/// be set to nullptr.
/// \param ShowFullyQualifiedAssigneeName If set to true when using \p
/// Assignee to compute the name of the assignee use the fully
/// qualified name, otherwise use the unqualified name.
///
/// \returns True iff no diagnostic where emitted, false otherwise.
static bool CheckAssignmentToCountAttrPtrWithIncompletePointeeTy(
Sema &S, QualType LHSTy, Expr *RHSExpr, AssignmentAction Action,
SourceLocation Loc, const ValueDecl *Assignee,
bool ShowFullyQualifiedAssigneeName) {
NamedDecl *IncompleteTyDecl = nullptr;
auto [CATy, PointeeTy] =
GetCountedByAttrOnIncompletePointee(LHSTy, &IncompleteTyDecl);
if (!CATy)
return true;
std::string AssigneeStr;
if (Assignee) {
if (ShowFullyQualifiedAssigneeName) {
AssigneeStr = Assignee->getQualifiedNameAsString();
} else {
AssigneeStr = Assignee->getNameAsString();
}
}
S.Diag(Loc, diag::err_counted_by_on_incomplete_type_on_assign)
<< static_cast<int>(Action) << AssigneeStr << (AssigneeStr.size() > 0)
<< isa<ImplicitValueInitExpr>(RHSExpr) << LHSTy
<< CATy->getAttributeName(/*WithMacroPrefix=*/true) << PointeeTy
<< CATy->isOrNull() << RHSExpr->getSourceRange();
EmitIncompleteCountedByPointeeNotes(S, CATy, IncompleteTyDecl);
return false; // check failed
}
bool Sema::BoundsSafetyCheckAssignmentToCountAttrPtr(
QualType LHSTy, Expr *RHSExpr, AssignmentAction Action, SourceLocation Loc,
const ValueDecl *Assignee, bool ShowFullyQualifiedAssigneeName) {
return CheckAssignmentToCountAttrPtrWithIncompletePointeeTy(
*this, LHSTy, RHSExpr, Action, Loc, Assignee,
ShowFullyQualifiedAssigneeName);
}
bool Sema::BoundsSafetyCheckInitialization(const InitializedEntity &Entity,
const InitializationKind &Kind,
AssignmentAction Action,
QualType LHSType, Expr *RHSExpr) {
auto SL = Kind.getLocation();
// Note: We don't call `BoundsSafetyCheckAssignmentToCountAttrPtr` here
// because we need conditionalize what is checked. In downstream
// Clang `counted_by` is supported on variable definitions and in that
// implementation an error diagnostic will be emitted on the variable
// definition if the pointee is an incomplete type. To avoid warning about the
// same problem twice (once when the variable is defined, once when Sema
// checks the initializer) we skip checking the initializer if it's a
// variable.
if (Action == AssignmentAction::Initializing &&
Entity.getKind() != InitializedEntity::EK_Variable) {
if (!CheckAssignmentToCountAttrPtrWithIncompletePointeeTy(
*this, LHSType, RHSExpr, Action, SL,
dyn_cast_or_null<ValueDecl>(Entity.getDecl()),
/*ShowFullQualifiedAssigneeName=*/true)) {
return false;
}
}
return true;
}
bool Sema::BoundsSafetyCheckUseOfCountAttrPtr(const Expr *E) {
QualType T = E->getType();
if (!T->isPointerType())
return true;
NamedDecl *IncompleteTyDecl = nullptr;
auto [CATy, PointeeTy] =
GetCountedByAttrOnIncompletePointee(T, &IncompleteTyDecl);
if (!CATy)
return true;
// Generate a string for the diagnostic that describes the "use".
// The string is specialized for direct calls to produce a better
// diagnostic.
SmallString<64> UseStr;
bool IsDirectCall = false;
if (const auto *CE = dyn_cast<CallExpr>(E->IgnoreParens())) {
if (const auto *FD = CE->getDirectCallee()) {
UseStr = FD->getName();
IsDirectCall = true;
}
}
if (!IsDirectCall) {
llvm::raw_svector_ostream SS(UseStr);
E->printPretty(SS, nullptr, getPrintingPolicy());
}
Diag(E->getBeginLoc(), diag::err_counted_by_on_incomplete_type_on_use)
<< IsDirectCall << UseStr << T << PointeeTy
<< CATy->getAttributeName(/*WithMacroPrefix=*/true) << CATy->isOrNull()
<< E->getSourceRange();
EmitIncompleteCountedByPointeeNotes(*this, CATy, IncompleteTyDecl);
return false;
}
} // namespace clang