Skip to content

Commit 176bf50

Browse files
authored
[clang][NFC] Move Bounds Safety Sema code to SemaBoundsSafety.cpp (#99330)
This patch adds a new `SemaBoundsSafety.cpp` source file and moves the existing `CheckCountedByAttrOnField` function and related helper functions and types from `SemaDeclAttr.cpp` into the new source file. The `CheckCountedByAttrOnField` function is now a method on the Sema class and now has doxygen comments. The goal behind this refactor is to clearly separate the `-fbounds-safety` Sema code from everything else. Although `counted_by(_or_null)` and `sized_by(_or_null)` attributes have a meaning outside of `-fbounds-safety` it seems reasonable to also have the Sema logic live in `SemaBoundsSafety.cpp` since the intention is that the attributes will have the same semantics (but not necessarily the same enforcement). As `-fbounds-safety` is upstreamed additional Sema checks will be added to `SemaBoundsSafety.cpp`. rdar://131777237
1 parent 123c036 commit 176bf50

File tree

4 files changed

+233
-176
lines changed

4 files changed

+233
-176
lines changed

clang/include/clang/Sema/Sema.h

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15051,6 +15051,44 @@ class Sema final : public SemaBase {
1505115051
void ProcessAPINotes(Decl *D);
1505215052

1505315053
///@}
15054+
15055+
//
15056+
//
15057+
// -------------------------------------------------------------------------
15058+
//
15059+
//
15060+
15061+
/// \name Bounds Safety
15062+
/// Implementations are in SemaBoundsSafety.cpp
15063+
///@{
15064+
public:
15065+
/// Check if applying the specified attribute variant from the "counted by"
15066+
/// family of attributes to FieldDecl \p FD is semantically valid. If
15067+
/// semantically invalid diagnostics will be emitted explaining the problems.
15068+
///
15069+
/// \param FD The FieldDecl to apply the attribute to
15070+
/// \param E The count expression on the attribute
15071+
/// \param[out] Decls If the attribute is semantically valid \p Decls
15072+
/// is populated with TypeCoupledDeclRefInfo objects, each
15073+
/// describing Decls referred to in \p E.
15074+
/// \param CountInBytes If true the attribute is from the "sized_by" family of
15075+
/// attributes. If the false the attribute is from
15076+
/// "counted_by" family of attributes.
15077+
/// \param OrNull If true the attribute is from the "_or_null" suffixed family
15078+
/// of attributes. If false the attribute does not have the
15079+
/// suffix.
15080+
///
15081+
/// Together \p CountInBytes and \p OrNull decide the attribute variant. E.g.
15082+
/// \p CountInBytes and \p OrNull both being true indicates the
15083+
/// `counted_by_or_null` attribute.
15084+
///
15085+
/// \returns false iff semantically valid.
15086+
bool CheckCountedByAttrOnField(
15087+
FieldDecl *FD, Expr *E,
15088+
llvm::SmallVectorImpl<TypeCoupledDeclRefInfo> &Decls, bool CountInBytes,
15089+
bool OrNull);
15090+
15091+
///@}
1505415092
};
1505515093

1505615094
DeductionFailureInfo

clang/lib/Sema/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ add_clang_library(clangSema
3636
SemaAvailability.cpp
3737
SemaBPF.cpp
3838
SemaBase.cpp
39+
SemaBoundsSafety.cpp
3940
SemaCXXScopeSpec.cpp
4041
SemaCast.cpp
4142
SemaChecking.cpp

clang/lib/Sema/SemaBoundsSafety.cpp

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
//===-- SemaBoundsSafety.cpp - Bounds Safety specific routines-*- C++ -*---===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
/// \file
9+
/// This file declares semantic analysis functions specific to `-fbounds-safety`
10+
/// (Bounds Safety) and also its attributes when used without `-fbounds-safety`
11+
/// (e.g. `counted_by`)
12+
///
13+
//===----------------------------------------------------------------------===//
14+
#include "clang/Sema/Sema.h"
15+
16+
namespace clang {
17+
18+
static CountAttributedType::DynamicCountPointerKind
19+
getCountAttrKind(bool CountInBytes, bool OrNull) {
20+
if (CountInBytes)
21+
return OrNull ? CountAttributedType::SizedByOrNull
22+
: CountAttributedType::SizedBy;
23+
return OrNull ? CountAttributedType::CountedByOrNull
24+
: CountAttributedType::CountedBy;
25+
}
26+
27+
static const RecordDecl *GetEnclosingNamedOrTopAnonRecord(const FieldDecl *FD) {
28+
const auto *RD = FD->getParent();
29+
// An unnamed struct is anonymous struct only if it's not instantiated.
30+
// However, the struct may not be fully processed yet to determine
31+
// whether it's anonymous or not. In that case, this function treats it as
32+
// an anonymous struct and tries to find a named parent.
33+
while (RD && (RD->isAnonymousStructOrUnion() ||
34+
(!RD->isCompleteDefinition() && RD->getName().empty()))) {
35+
const auto *Parent = dyn_cast<RecordDecl>(RD->getParent());
36+
if (!Parent)
37+
break;
38+
RD = Parent;
39+
}
40+
return RD;
41+
}
42+
43+
enum class CountedByInvalidPointeeTypeKind {
44+
INCOMPLETE,
45+
SIZELESS,
46+
FUNCTION,
47+
FLEXIBLE_ARRAY_MEMBER,
48+
VALID,
49+
};
50+
51+
bool Sema::CheckCountedByAttrOnField(
52+
FieldDecl *FD, Expr *E,
53+
llvm::SmallVectorImpl<TypeCoupledDeclRefInfo> &Decls, bool CountInBytes,
54+
bool OrNull) {
55+
// Check the context the attribute is used in
56+
57+
unsigned Kind = getCountAttrKind(CountInBytes, OrNull);
58+
59+
if (FD->getParent()->isUnion()) {
60+
Diag(FD->getBeginLoc(), diag::err_count_attr_in_union)
61+
<< Kind << FD->getSourceRange();
62+
return true;
63+
}
64+
65+
const auto FieldTy = FD->getType();
66+
if (FieldTy->isArrayType() && (CountInBytes || OrNull)) {
67+
Diag(FD->getBeginLoc(),
68+
diag::err_count_attr_not_on_ptr_or_flexible_array_member)
69+
<< Kind << FD->getLocation() << /* suggest counted_by */ 1;
70+
return true;
71+
}
72+
if (!FieldTy->isArrayType() && !FieldTy->isPointerType()) {
73+
Diag(FD->getBeginLoc(),
74+
diag::err_count_attr_not_on_ptr_or_flexible_array_member)
75+
<< Kind << FD->getLocation() << /* do not suggest counted_by */ 0;
76+
return true;
77+
}
78+
79+
LangOptions::StrictFlexArraysLevelKind StrictFlexArraysLevel =
80+
LangOptions::StrictFlexArraysLevelKind::IncompleteOnly;
81+
if (FieldTy->isArrayType() &&
82+
!Decl::isFlexibleArrayMemberLike(getASTContext(), FD, FieldTy,
83+
StrictFlexArraysLevel, true)) {
84+
Diag(FD->getBeginLoc(),
85+
diag::err_counted_by_attr_on_array_not_flexible_array_member)
86+
<< Kind << FD->getLocation();
87+
return true;
88+
}
89+
90+
CountedByInvalidPointeeTypeKind InvalidTypeKind =
91+
CountedByInvalidPointeeTypeKind::VALID;
92+
QualType PointeeTy;
93+
int SelectPtrOrArr = 0;
94+
if (FieldTy->isPointerType()) {
95+
PointeeTy = FieldTy->getPointeeType();
96+
SelectPtrOrArr = 0;
97+
} else {
98+
assert(FieldTy->isArrayType());
99+
const ArrayType *AT = getASTContext().getAsArrayType(FieldTy);
100+
PointeeTy = AT->getElementType();
101+
SelectPtrOrArr = 1;
102+
}
103+
// Note: The `Decl::isFlexibleArrayMemberLike` check earlier on means
104+
// only `PointeeTy->isStructureTypeWithFlexibleArrayMember()` is reachable
105+
// when `FieldTy->isArrayType()`.
106+
bool ShouldWarn = false;
107+
if (PointeeTy->isIncompleteType() && !CountInBytes) {
108+
InvalidTypeKind = CountedByInvalidPointeeTypeKind::INCOMPLETE;
109+
} else if (PointeeTy->isSizelessType()) {
110+
InvalidTypeKind = CountedByInvalidPointeeTypeKind::SIZELESS;
111+
} else if (PointeeTy->isFunctionType()) {
112+
InvalidTypeKind = CountedByInvalidPointeeTypeKind::FUNCTION;
113+
} else if (PointeeTy->isStructureTypeWithFlexibleArrayMember()) {
114+
if (FieldTy->isArrayType() && !getLangOpts().BoundsSafety) {
115+
// This is a workaround for the Linux kernel that has already adopted
116+
// `counted_by` on a FAM where the pointee is a struct with a FAM. This
117+
// should be an error because computing the bounds of the array cannot be
118+
// done correctly without manually traversing every struct object in the
119+
// array at runtime. To allow the code to be built this error is
120+
// downgraded to a warning.
121+
ShouldWarn = true;
122+
}
123+
InvalidTypeKind = CountedByInvalidPointeeTypeKind::FLEXIBLE_ARRAY_MEMBER;
124+
}
125+
126+
if (InvalidTypeKind != CountedByInvalidPointeeTypeKind::VALID) {
127+
unsigned DiagID = ShouldWarn
128+
? diag::warn_counted_by_attr_elt_type_unknown_size
129+
: diag::err_counted_by_attr_pointee_unknown_size;
130+
Diag(FD->getBeginLoc(), DiagID)
131+
<< SelectPtrOrArr << PointeeTy << (int)InvalidTypeKind
132+
<< (ShouldWarn ? 1 : 0) << Kind << FD->getSourceRange();
133+
return true;
134+
}
135+
136+
// Check the expression
137+
138+
if (!E->getType()->isIntegerType() || E->getType()->isBooleanType()) {
139+
Diag(E->getBeginLoc(), diag::err_count_attr_argument_not_integer)
140+
<< Kind << E->getSourceRange();
141+
return true;
142+
}
143+
144+
auto *DRE = dyn_cast<DeclRefExpr>(E);
145+
if (!DRE) {
146+
Diag(E->getBeginLoc(),
147+
diag::err_count_attr_only_support_simple_decl_reference)
148+
<< Kind << E->getSourceRange();
149+
return true;
150+
}
151+
152+
auto *CountDecl = DRE->getDecl();
153+
FieldDecl *CountFD = dyn_cast<FieldDecl>(CountDecl);
154+
if (auto *IFD = dyn_cast<IndirectFieldDecl>(CountDecl)) {
155+
CountFD = IFD->getAnonField();
156+
}
157+
if (!CountFD) {
158+
Diag(E->getBeginLoc(), diag::err_count_attr_must_be_in_structure)
159+
<< CountDecl << Kind << E->getSourceRange();
160+
161+
Diag(CountDecl->getBeginLoc(),
162+
diag::note_flexible_array_counted_by_attr_field)
163+
<< CountDecl << CountDecl->getSourceRange();
164+
return true;
165+
}
166+
167+
if (FD->getParent() != CountFD->getParent()) {
168+
if (CountFD->getParent()->isUnion()) {
169+
Diag(CountFD->getBeginLoc(), diag::err_count_attr_refer_to_union)
170+
<< Kind << CountFD->getSourceRange();
171+
return true;
172+
}
173+
// Whether CountRD is an anonymous struct is not determined at this
174+
// point. Thus, an additional diagnostic in case it's not anonymous struct
175+
// is done later in `Parser::ParseStructDeclaration`.
176+
auto *RD = GetEnclosingNamedOrTopAnonRecord(FD);
177+
auto *CountRD = GetEnclosingNamedOrTopAnonRecord(CountFD);
178+
179+
if (RD != CountRD) {
180+
Diag(E->getBeginLoc(), diag::err_count_attr_param_not_in_same_struct)
181+
<< CountFD << Kind << FieldTy->isArrayType() << E->getSourceRange();
182+
Diag(CountFD->getBeginLoc(),
183+
diag::note_flexible_array_counted_by_attr_field)
184+
<< CountFD << CountFD->getSourceRange();
185+
return true;
186+
}
187+
}
188+
189+
Decls.push_back(TypeCoupledDeclRefInfo(CountFD, /*IsDref*/ false));
190+
return false;
191+
}
192+
193+
} // namespace clang

0 commit comments

Comments
 (0)