Skip to content

Commit 5c5a284

Browse files
committed
[Bounds-Safety] Add sized_by, counted_by_or_null & sized_by_or_null
The attributes `sized_by`, `counted_by_or_null` and `sized_by_or_null` have been added as variants on `counted_by`, each with slightly different semantics. `sized_by` takes a byte size parameter instead of an element count, allowing pointees with unknown size. The `counted_by_or_null` and `sized_by_or_null` variants are equivalent to their base variants, except the pointer can be null regardless of count/size value. If the pointer is null the size is effectively 0. rdar://125400354
1 parent 5a02a9a commit 5c5a284

30 files changed

+2150
-31
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,12 @@ Attribute Changes in Clang
451451
size_t count;
452452
};
453453
454+
- The attributes ``sized_by``, ``counted_by_or_null`` and ``sized_by_or_null```
455+
have been added as variants on ``counted_by``, each with slightly different semantics.
456+
``sized_by`` takes a byte size parameter instead of an element count, allowing pointees
457+
with unknown size. The ``counted_by_or_null`` and ``sized_by_or_null`` variants are equivalent
458+
to their base variants, except the pointer can be null regardless of count/size value.
459+
If the pointer is null the size is effectively 0.
454460

455461
Improvements to Clang's diagnostics
456462
-----------------------------------

clang/include/clang/Basic/Attr.td

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2265,6 +2265,36 @@ def CountedBy : DeclOrTypeAttr {
22652265
let LangOpts = [COnly];
22662266
}
22672267

2268+
def CountedByOrNull : DeclOrTypeAttr {
2269+
let Spellings = [Clang<"counted_by_or_null">];
2270+
let Subjects = SubjectList<[Field], ErrorDiag>;
2271+
let Args = [ExprArgument<"Count">, IntArgument<"NestedLevel", 1>];
2272+
let LateParsed = LateAttrParseExperimentalExt;
2273+
let ParseArgumentsAsUnevaluated = 1;
2274+
let Documentation = [CountedByDocs];
2275+
let LangOpts = [COnly];
2276+
}
2277+
2278+
def SizedBy : DeclOrTypeAttr {
2279+
let Spellings = [Clang<"sized_by">];
2280+
let Subjects = SubjectList<[Field], ErrorDiag>;
2281+
let Args = [ExprArgument<"Size">, IntArgument<"NestedLevel", 1>];
2282+
let LateParsed = LateAttrParseExperimentalExt;
2283+
let ParseArgumentsAsUnevaluated = 1;
2284+
let Documentation = [CountedByDocs];
2285+
let LangOpts = [COnly];
2286+
}
2287+
2288+
def SizedByOrNull : DeclOrTypeAttr {
2289+
let Spellings = [Clang<"sized_by_or_null">];
2290+
let Subjects = SubjectList<[Field], ErrorDiag>;
2291+
let Args = [ExprArgument<"Size">, IntArgument<"NestedLevel", 1>];
2292+
let LateParsed = LateAttrParseExperimentalExt;
2293+
let ParseArgumentsAsUnevaluated = 1;
2294+
let Documentation = [CountedByDocs];
2295+
let LangOpts = [COnly];
2296+
}
2297+
22682298
// This is a marker used to indicate that an __unsafe_unretained qualifier was
22692299
// ignored because ARC is not enabled. The usual representation for this
22702300
// qualifier is as an ObjCOwnership attribute with Kind == "none".

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6543,27 +6543,27 @@ def warn_superclass_variable_sized_type_not_at_end : Warning<
65436543
" in superclass %3">, InGroup<ObjCFlexibleArray>;
65446544

65456545
def err_flexible_array_count_not_in_same_struct : Error<
6546-
"'counted_by' field %0 isn't within the same struct as the flexible array">;
6546+
"'%select{counted_by|sized_by|counted_by_or_null|sized_by_or_null}1' field %0 isn't within the same struct as the flexible array">;
65476547
def err_counted_by_attr_not_on_ptr_or_flexible_array_member : Error<
6548-
"'counted_by' only applies to pointers or C99 flexible array members">;
6548+
"'%select{counted_by|sized_by|counted_by_or_null|sized_by_or_null}0' only applies to pointers%select{ or C99 flexible array members|||}0">;
65496549
def err_counted_by_attr_on_array_not_flexible_array_member : Error<
65506550
"'counted_by' on arrays only applies to C99 flexible array members">;
65516551
def err_counted_by_attr_refer_to_itself : Error<
65526552
"'counted_by' cannot refer to the flexible array member %0">;
65536553
def err_counted_by_must_be_in_structure : Error<
6554-
"field %0 in 'counted_by' not inside structure">;
6554+
"field %0 in '%select{counted_by|sized_by|counted_by_or_null|sized_by_or_null}1' not inside structure">;
65556555
def err_counted_by_attr_argument_not_integer : Error<
6556-
"'counted_by' requires a non-boolean integer type argument">;
6556+
"'%select{counted_by|sized_by|counted_by_or_null|sized_by_or_null}0' requires a non-boolean integer type argument">;
65576557
def err_counted_by_attr_only_support_simple_decl_reference : Error<
6558-
"'counted_by' argument must be a simple declaration reference">;
6558+
"'%select{counted_by|sized_by|counted_by_or_null|sized_by_or_null}0' argument must be a simple declaration reference">;
65596559
def err_counted_by_attr_in_union : Error<
6560-
"'counted_by' cannot be applied to a union member">;
6560+
"'%select{counted_by|sized_by|counted_by_or_null|sized_by_or_null}0' cannot be applied to a union member">;
65616561
def err_counted_by_attr_refer_to_union : Error<
6562-
"'counted_by' argument cannot refer to a union member">;
6562+
"'%select{counted_by|sized_by|counted_by_or_null|sized_by_or_null}0' argument cannot refer to a union member">;
65636563
def note_flexible_array_counted_by_attr_field : Note<
65646564
"field %0 declared here">;
65656565
def err_counted_by_attr_pointee_unknown_size : Error<
6566-
"'counted_by' %select{cannot|should not}3 be applied to %select{"
6566+
"'%select{counted_by|counted_by_or_null}4' %select{cannot|should not}3 be applied to %select{"
65676567
"a pointer with pointee|" // pointer
65686568
"an array with element}0" // array
65696569
" of unknown size because %1 is %select{"

clang/include/clang/Sema/Sema.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11432,7 +11432,9 @@ class Sema final : public SemaBase {
1143211432
SourceLocation AttrLoc);
1143311433

1143411434
QualType BuildCountAttributedArrayOrPointerType(QualType WrappedTy,
11435-
Expr *CountExpr);
11435+
Expr *CountExpr,
11436+
bool CountInBytes,
11437+
bool OrNull);
1143611438

1143711439
QualType BuildAddressSpaceAttr(QualType &T, LangAS ASIdx, Expr *AddrSpace,
1143811440
SourceLocation AttrLoc);

clang/lib/AST/TypePrinter.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1905,6 +1905,9 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
19051905
break;
19061906

19071907
case attr::CountedBy:
1908+
case attr::CountedByOrNull:
1909+
case attr::SizedBy:
1910+
case attr::SizedByOrNull:
19081911
case attr::LifetimeBound:
19091912
case attr::TypeNonNull:
19101913
case attr::TypeNullable:

clang/lib/Parse/ParseDecl.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -667,7 +667,10 @@ void Parser::ParseGNUAttributeArgs(
667667
ParseAttributeWithTypeArg(*AttrName, AttrNameLoc, Attrs, ScopeName,
668668
ScopeLoc, Form);
669669
return;
670-
} else if (AttrKind == ParsedAttr::AT_CountedBy) {
670+
} else if (AttrKind == ParsedAttr::AT_CountedBy ||
671+
AttrKind == ParsedAttr::AT_CountedByOrNull ||
672+
AttrKind == ParsedAttr::AT_SizedBy ||
673+
AttrKind == ParsedAttr::AT_SizedByOrNull) {
671674
ParseBoundsAttribute(*AttrName, AttrNameLoc, Attrs, ScopeName, ScopeLoc,
672675
Form);
673676
return;
@@ -4835,7 +4838,7 @@ static void DiagnoseCountAttributedTypeInUnnamedAnon(ParsingDeclSpec &DS,
48354838
if (!RD->containsDecl(DD.getDecl())) {
48364839
P.Diag(VD->getBeginLoc(),
48374840
diag::err_flexible_array_count_not_in_same_struct)
4838-
<< DD.getDecl();
4841+
<< DD.getDecl() << CAT->getKind();
48394842
P.Diag(DD.getDecl()->getBeginLoc(),
48404843
diag::note_flexible_array_counted_by_attr_field)
48414844
<< DD.getDecl();

clang/lib/Sema/SemaDeclAttr.cpp

Lines changed: 54 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8328,22 +8328,33 @@ enum class CountedByInvalidPointeeTypeKind {
83288328
VALID,
83298329
};
83308330

8331-
static bool CheckCountedByAttrOnField(
8332-
Sema &S, FieldDecl *FD, Expr *E,
8333-
llvm::SmallVectorImpl<TypeCoupledDeclRefInfo> &Decls) {
8331+
static bool
8332+
CheckCountedByAttrOnField(Sema &S, FieldDecl *FD, Expr *E,
8333+
llvm::SmallVectorImpl<TypeCoupledDeclRefInfo> &Decls,
8334+
bool CountInBytes, bool OrNull) {
83348335
// Check the context the attribute is used in
83358336

8337+
unsigned Kind = CountInBytes;
8338+
if (OrNull)
8339+
Kind += 2;
8340+
83368341
if (FD->getParent()->isUnion()) {
83378342
S.Diag(FD->getBeginLoc(), diag::err_counted_by_attr_in_union)
8338-
<< FD->getSourceRange();
8343+
<< Kind << FD->getSourceRange();
83398344
return true;
83408345
}
83418346

83428347
const auto FieldTy = FD->getType();
8348+
if (FieldTy->isArrayType() && (CountInBytes || OrNull)) {
8349+
S.Diag(FD->getBeginLoc(),
8350+
diag::err_counted_by_attr_not_on_ptr_or_flexible_array_member)
8351+
<< Kind << FD->getLocation();
8352+
return true;
8353+
}
83438354
if (!FieldTy->isArrayType() && !FieldTy->isPointerType()) {
83448355
S.Diag(FD->getBeginLoc(),
83458356
diag::err_counted_by_attr_not_on_ptr_or_flexible_array_member)
8346-
<< FD->getLocation();
8357+
<< Kind << FD->getLocation();
83478358
return true;
83488359
}
83498360

@@ -8354,7 +8365,7 @@ static bool CheckCountedByAttrOnField(
83548365
StrictFlexArraysLevel, true)) {
83558366
S.Diag(FD->getBeginLoc(),
83568367
diag::err_counted_by_attr_on_array_not_flexible_array_member)
8357-
<< FD->getLocation();
8368+
<< Kind << FD->getLocation();
83588369
return true;
83598370
}
83608371

@@ -8394,29 +8405,30 @@ static bool CheckCountedByAttrOnField(
83948405
InvalidTypeKind = CountedByInvalidPointeeTypeKind::FLEXIBLE_ARRAY_MEMBER;
83958406
}
83968407

8397-
if (InvalidTypeKind != CountedByInvalidPointeeTypeKind::VALID) {
8408+
if (InvalidTypeKind != CountedByInvalidPointeeTypeKind::VALID &&
8409+
!CountInBytes) {
83988410
unsigned DiagID = ShouldWarn
83998411
? diag::warn_counted_by_attr_elt_type_unknown_size
84008412
: diag::err_counted_by_attr_pointee_unknown_size;
84018413
S.Diag(FD->getBeginLoc(), DiagID)
84028414
<< SelectPtrOrArr << PointeeTy << (int)InvalidTypeKind
8403-
<< (ShouldWarn ? 1 : 0) << FD->getSourceRange();
8415+
<< (ShouldWarn ? 1 : 0) << OrNull << FD->getSourceRange();
84048416
return true;
84058417
}
84068418

84078419
// Check the expression
84088420

84098421
if (!E->getType()->isIntegerType() || E->getType()->isBooleanType()) {
84108422
S.Diag(E->getBeginLoc(), diag::err_counted_by_attr_argument_not_integer)
8411-
<< E->getSourceRange();
8423+
<< Kind << E->getSourceRange();
84128424
return true;
84138425
}
84148426

84158427
auto *DRE = dyn_cast<DeclRefExpr>(E);
84168428
if (!DRE) {
84178429
S.Diag(E->getBeginLoc(),
84188430
diag::err_counted_by_attr_only_support_simple_decl_reference)
8419-
<< E->getSourceRange();
8431+
<< Kind << E->getSourceRange();
84208432
return true;
84218433
}
84228434

@@ -8427,7 +8439,7 @@ static bool CheckCountedByAttrOnField(
84278439
}
84288440
if (!CountFD) {
84298441
S.Diag(E->getBeginLoc(), diag::err_counted_by_must_be_in_structure)
8430-
<< CountDecl << E->getSourceRange();
8442+
<< CountDecl << Kind << E->getSourceRange();
84318443

84328444
S.Diag(CountDecl->getBeginLoc(),
84338445
diag::note_flexible_array_counted_by_attr_field)
@@ -8438,7 +8450,7 @@ static bool CheckCountedByAttrOnField(
84388450
if (FD->getParent() != CountFD->getParent()) {
84398451
if (CountFD->getParent()->isUnion()) {
84408452
S.Diag(CountFD->getBeginLoc(), diag::err_counted_by_attr_refer_to_union)
8441-
<< CountFD->getSourceRange();
8453+
<< Kind << CountFD->getSourceRange();
84428454
return true;
84438455
}
84448456
// Whether CountRD is an anonymous struct is not determined at this
@@ -8450,7 +8462,7 @@ static bool CheckCountedByAttrOnField(
84508462
if (RD != CountRD) {
84518463
S.Diag(E->getBeginLoc(),
84528464
diag::err_flexible_array_count_not_in_same_struct)
8453-
<< CountFD << E->getSourceRange();
8465+
<< CountFD << Kind << E->getSourceRange();
84548466
S.Diag(CountFD->getBeginLoc(),
84558467
diag::note_flexible_array_counted_by_attr_field)
84568468
<< CountFD << CountFD->getSourceRange();
@@ -8470,12 +8482,35 @@ static void handleCountedByAttrField(Sema &S, Decl *D, const ParsedAttr &AL) {
84708482
if (!CountExpr)
84718483
return;
84728484

8485+
bool CountInBytes;
8486+
bool OrNull;
8487+
switch (AL.getKind()) {
8488+
case ParsedAttr::AT_CountedBy:
8489+
CountInBytes = false;
8490+
OrNull = false;
8491+
break;
8492+
case ParsedAttr::AT_CountedByOrNull:
8493+
CountInBytes = false;
8494+
OrNull = true;
8495+
break;
8496+
case ParsedAttr::AT_SizedBy:
8497+
CountInBytes = true;
8498+
OrNull = false;
8499+
break;
8500+
case ParsedAttr::AT_SizedByOrNull:
8501+
CountInBytes = true;
8502+
OrNull = true;
8503+
break;
8504+
default:
8505+
llvm_unreachable("unexpected counted_by family attribute");
8506+
}
8507+
84738508
llvm::SmallVector<TypeCoupledDeclRefInfo, 1> Decls;
8474-
if (CheckCountedByAttrOnField(S, FD, CountExpr, Decls))
8509+
if (CheckCountedByAttrOnField(S, FD, CountExpr, Decls, CountInBytes, OrNull))
84758510
return;
84768511

8477-
QualType CAT =
8478-
S.BuildCountAttributedArrayOrPointerType(FD->getType(), CountExpr);
8512+
QualType CAT = S.BuildCountAttributedArrayOrPointerType(
8513+
FD->getType(), CountExpr, CountInBytes, OrNull);
84798514
FD->setType(CAT);
84808515
}
84818516

@@ -9502,6 +9537,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
95029537
break;
95039538

95049539
case ParsedAttr::AT_CountedBy:
9540+
case ParsedAttr::AT_CountedByOrNull:
9541+
case ParsedAttr::AT_SizedBy:
9542+
case ParsedAttr::AT_SizedByOrNull:
95059543
handleCountedByAttrField(S, D, AL);
95069544
break;
95079545

clang/lib/Sema/SemaType.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9350,15 +9350,17 @@ BuildTypeCoupledDecls(Expr *E,
93509350
}
93519351

93529352
QualType Sema::BuildCountAttributedArrayOrPointerType(QualType WrappedTy,
9353-
Expr *CountExpr) {
9353+
Expr *CountExpr,
9354+
bool CountInBytes,
9355+
bool OrNull) {
93549356
assert(WrappedTy->isIncompleteArrayType() || WrappedTy->isPointerType());
93559357

93569358
llvm::SmallVector<TypeCoupledDeclRefInfo, 1> Decls;
93579359
BuildTypeCoupledDecls(CountExpr, Decls);
93589360
/// When the resulting expression is invalid, we still create the AST using
93599361
/// the original count expression for the sake of AST dump.
9360-
return Context.getCountAttributedType(
9361-
WrappedTy, CountExpr, /*CountInBytes*/ false, /*OrNull*/ false, Decls);
9362+
return Context.getCountAttributedType(WrappedTy, CountExpr, CountInBytes,
9363+
OrNull, Decls);
93629364
}
93639365

93649366
/// getDecltypeForExpr - Given an expr, will return the decltype for

clang/lib/Sema/TreeTransform.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7336,7 +7336,8 @@ QualType TreeTransform<Derived>::TransformCountAttributedType(
73367336
if (getDerived().AlwaysRebuild() || InnerTy != OldTy->desugar() ||
73377337
OldCount != NewCount) {
73387338
// Currently, CountAttributedType can only wrap incomplete array types.
7339-
Result = SemaRef.BuildCountAttributedArrayOrPointerType(InnerTy, NewCount);
7339+
Result = SemaRef.BuildCountAttributedArrayOrPointerType(
7340+
InnerTy, NewCount, OldTy->isCountInBytes(), OldTy->isOrNull());
73407341
}
73417342

73427343
TLB.push<CountAttributedTypeLoc>(Result);
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// RUN: %clang_cc1 -fexperimental-late-parse-attributes %s -ast-dump | FileCheck %s
2+
3+
#define __counted_by_or_null(f) __attribute__((counted_by_or_null(f)))
4+
5+
struct size_known {
6+
int field;
7+
};
8+
9+
//==============================================================================
10+
// __counted_by_or_null on struct member pointer in decl attribute position
11+
//==============================================================================
12+
13+
struct on_member_pointer_complete_ty {
14+
struct size_known *buf __counted_by_or_null(count);
15+
int count;
16+
};
17+
// CHECK-LABEL: struct on_member_pointer_complete_ty definition
18+
// CHECK-NEXT: |-FieldDecl {{.*}} buf 'struct size_known * __counted_by_or_null(count)':'struct size_known *'
19+
// CHECK-NEXT: `-FieldDecl {{.*}} referenced count 'int'
20+
21+
struct on_pointer_anon_count {
22+
struct size_known *buf __counted_by_or_null(count);
23+
struct {
24+
int count;
25+
};
26+
};
27+
28+
// CHECK-LABEL: struct on_pointer_anon_count definition
29+
// CHECK-NEXT: |-FieldDecl {{.*}} buf 'struct size_known * __counted_by_or_null(count)':'struct size_known *'
30+
// CHECK-NEXT: |-RecordDecl {{.*}} struct definition
31+
// CHECK-NEXT: | `-FieldDecl {{.*}} count 'int'
32+
// CHECK-NEXT: |-FieldDecl {{.*}} implicit 'struct on_pointer_anon_count::(anonymous at {{.*}})'
33+
// CHECK-NEXT: `-IndirectFieldDecl {{.*}} implicit referenced count 'int'
34+
// CHECK-NEXT: |-Field {{.*}} '' 'struct on_pointer_anon_count::(anonymous at {{.*}})'
35+
// CHECK-NEXT: `-Field {{.*}} 'count' 'int'
36+
37+
//==============================================================================
38+
// __counted_by_or_null on struct member pointer in type attribute position
39+
//==============================================================================
40+
// TODO: Correctly parse counted_by_or_null as a type attribute. Currently it is parsed
41+
// as a declaration attribute and is **not** late parsed resulting in the `count`
42+
// field being unavailable.
43+
//
44+
// See `clang/test/Sema/attr-counted-by-late-parsed-struct-ptrs.c` for test
45+
// cases.

0 commit comments

Comments
 (0)