
These are identified by misc-include-cleaner. I've filtered out those that break builds. Also, I'm staying away from llvm-config.h, config.h, and Compiler.h, which likely cause platform- or compiler-specific build failures.
399 lines
15 KiB
C++
399 lines
15 KiB
C++
//===-- 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/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
|