[llvm-branch-commits] [clang] [BoundsSafety] Handle the counted_by family as a type attribute (PR #233582)
Yeoul Na via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Wed Oct 21 11:46:04 PDT 2026
https://github.com/rapidsna updated https://github.com/llvm/llvm-project/pull/233582
>From 90e03eac21072fba55ffb7de10271cdb4e53252b Mon Sep 17 00:00:00 2001
From: Yeoul Na <yeoul_na at apple.com>
Date: Fri, 18 Sep 2026 00:28:21 -0700
Subject: [PATCH 1/3] [BoundsSafety] Handle the counted_by family as a type
attribute
counted_by / sized_by (and their _or_null variants) were handled as a
declaration-position attribute through handleCountedByAttrField.
Build the type during type construction instead and properly handle as a
type-position attribute. Building the node in type position also allows
the new way of late-parsing for type attributes: creates a type node
in place and fills it in once late parsing is done, which will be
enabled in the follow-up commits.
Assisted-by: Opus 5.5
---
.../clang/Basic/DiagnosticSemaKinds.td | 2 +
clang/include/clang/Sema/Sema.h | 18 ++-
clang/lib/Sema/SemaBoundsSafety.cpp | 149 ++++--------------
clang/lib/Sema/SemaDecl.cpp | 19 +++
clang/lib/Sema/SemaDeclAttr.cpp | 48 +++---
clang/lib/Sema/SemaType.cpp | 110 ++++++++++++-
.../AST/attr-counted-by-or-null-struct-ptrs.c | 23 ---
clang/test/AST/attr-counted-by-struct-ptrs.c | 26 ---
.../AST/attr-sized-by-or-null-struct-ptrs.c | 24 ---
clang/test/AST/attr-sized-by-struct-ptrs.c | 24 ---
...ruct-ptrs-completable-incomplete-pointee.c | 13 +-
.../attr-counted-by-or-null-struct-ptrs.c | 16 +-
clang/test/Sema/attr-counted-by-return-type.c | 53 +++++++
...ruct-ptrs-completable-incomplete-pointee.c | 9 +-
clang/test/Sema/attr-counted-by-struct-ptrs.c | 16 +-
.../Sema/attr-sized-by-or-null-struct-ptrs.c | 16 +-
clang/test/Sema/attr-sized-by-struct-ptrs.c | 16 +-
17 files changed, 307 insertions(+), 275 deletions(-)
create mode 100644 clang/test/Sema/attr-counted-by-return-type.c
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index a23667f1fb1c2c..d796386cf8a174 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -7281,6 +7281,8 @@ def note_counted_by_counter_field : Note<
def err_counted_by_on_nested_pointer : Error<
"'%select{counted_by|sized_by|counted_by_or_null|sized_by_or_null}0' attribute on nested pointer type is not allowed">;
+def err_counted_by_on_return_type : Error<
+ "'%select{counted_by|sized_by|counted_by_or_null|sized_by_or_null}0' attribute cannot be applied to a function return type">;
let CategoryName = "ARC Semantic Issue" in {
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 9082042c48502d..9a3810efabd133 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -2556,14 +2556,26 @@ class Sema final : public SemaBase {
bool UpdateFlags = true);
/// Check a counted_by-family attribute in type position and initialize
- /// \p Flags from \p AttrKind. \p PointerNestLevel counts the pointer, array
- /// and function declarator chunks enclosing the attribute.
+ /// \p Flags from \p AttrKind. \p PointerNestLevel counts the pointer and
+ /// array declarator chunks enclosing the attribute, up to the nearest
+ /// function chunk. \p OnReturnType is true if a function chunk encloses it,
+ /// i.e. the attribute is in a function's return type.
///
/// \returns false after diagnosing an attribute that must be dropped (see
/// ValidateBoundsAttrTypeShape).
bool ValidateBoundsAttrTypeForTypePosition(
QualType Ty, AttributeCommonInfo::Kind AttrKind, SourceLocation AttrLoc,
- SourceRange AttrRange, unsigned PointerNestLevel, BoundsAttrFlags &Flags);
+ SourceRange AttrRange, unsigned PointerNestLevel, bool OnReturnType,
+ BoundsAttrFlags &Flags);
+
+ /// Check where a counted_by-family attribute of kind \p Kind sits in its
+ /// declarator, as described by \p PointerNestLevel and \p OnReturnType (see
+ /// ValidateBoundsAttrTypeForTypePosition).
+ ///
+ /// \returns false after diagnosing an attribute in an unsupported position.
+ bool ValidateBoundsAttrPosition(BoundsAttributedType::BoundsAttrKind Kind,
+ SourceLocation AttrLoc, SourceRange AttrRange,
+ unsigned PointerNestLevel, bool OnReturnType);
/// Check if applying the specified attribute variant from the "counted by"
/// family of attributes to FieldDecl \p FD is semantically valid. If
diff --git a/clang/lib/Sema/SemaBoundsSafety.cpp b/clang/lib/Sema/SemaBoundsSafety.cpp
index cebc680297cd9c..a4bb0548a58b98 100644
--- a/clang/lib/Sema/SemaBoundsSafety.cpp
+++ b/clang/lib/Sema/SemaBoundsSafety.cpp
@@ -213,15 +213,18 @@ bool Sema::ValidateBoundsAttrTypeShape(QualType Ty, SourceLocation AttrLoc,
return true;
}
-bool Sema::ValidateBoundsAttrTypeForTypePosition(
- QualType Ty, AttributeCommonInfo::Kind AttrKind, SourceLocation AttrLoc,
- SourceRange AttrRange, unsigned PointerNestLevel, BoundsAttrFlags &Flags) {
- Flags = BoundsAttrFlags::get(AttrKind);
-
- if (!ValidateBoundsAttrTypeShape(Ty, AttrLoc, AttrRange, Flags,
- /*AttrSpelling=*/{}, /*AllowRedecl=*/false,
- /*AttrArg=*/nullptr, /*UpdateFlags=*/false))
+bool Sema::ValidateBoundsAttrPosition(BoundsAttributedType::BoundsAttrKind Kind,
+ SourceLocation AttrLoc,
+ SourceRange AttrRange,
+ unsigned PointerNestLevel,
+ bool OnReturnType) {
+ // FIXME: Support function return types, with a count that refers to the
+ // function's parameters. Their nesting is then checked below, relative to
+ // the return type.
+ if (OnReturnType) {
+ Diag(AttrLoc, diag::err_counted_by_on_return_type) << Kind << AttrRange;
return false;
+ }
// Currently, only attributes at the outermost level of the declared type
// are supported.
@@ -230,14 +233,31 @@ bool Sema::ValidateBoundsAttrTypeForTypePosition(
// void f(int *__counted_by(*len) *buf, int *len);
// See https://github.com/llvm/llvm-project/issues/166411.
if (PointerNestLevel > 0) {
- Diag(AttrLoc, diag::err_counted_by_on_nested_pointer)
- << getBoundsAttrKind(Flags);
+ Diag(AttrLoc, diag::err_counted_by_on_nested_pointer) << Kind;
return false;
}
return true;
}
+bool Sema::ValidateBoundsAttrTypeForTypePosition(
+ QualType Ty, AttributeCommonInfo::Kind AttrKind, SourceLocation AttrLoc,
+ SourceRange AttrRange, unsigned PointerNestLevel, bool OnReturnType,
+ BoundsAttrFlags &Flags) {
+ Flags = BoundsAttrFlags::get(AttrKind);
+
+ if (!ValidateBoundsAttrTypeShape(Ty, AttrLoc, AttrRange, Flags,
+ /*AttrSpelling=*/{}, /*AllowRedecl=*/false,
+ /*AttrArg=*/nullptr, /*UpdateFlags=*/false))
+ return false;
+
+ if (!ValidateBoundsAttrPosition(getBoundsAttrKind(Flags), AttrLoc, AttrRange,
+ PointerNestLevel, OnReturnType))
+ return false;
+
+ return true;
+}
+
bool Sema::CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes,
bool OrNull) {
// Check the context the attribute is used in
@@ -250,20 +270,7 @@ bool Sema::CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes,
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;
- }
-
+ const QualType FieldTy = FD->getType();
LangOptions::StrictFlexArraysLevelKind StrictFlexArraysLevel =
LangOptions::StrictFlexArraysLevelKind::IncompleteOnly;
if (FieldTy->isArrayType() &&
@@ -275,98 +282,7 @@ bool Sema::CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes,
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 (!CountInBytes && PointeeTy->isAlwaysIncompleteType()) {
- // 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 count;
- // 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` in strict C mode) the attribute is disallowed by this method
- // because we know the type can never be completed so there's no reason
- // to allow it.
- //
- // Exception: void has an implicit size of 1 byte for pointer arithmetic
- // (following GNU convention). Therefore, counted_by on void* is allowed
- // and behaves equivalently to sized_by (treating the count as bytes).
- bool IsVoidPtr = PointeeTy->isVoidType();
- if (IsVoidPtr) {
- // Emit a warning that this is a GNU extension.
- Diag(FD->getBeginLoc(), diag::ext_gnu_counted_by_void_ptr) << Kind;
- Diag(FD->getBeginLoc(), diag::note_gnu_counted_by_void_ptr_use_sized_by)
- << Kind;
- assert(InvalidTypeKind == CountedByInvalidPointeeTypeKind::VALID);
- } else {
- InvalidTypeKind = CountedByInvalidPointeeTypeKind::INCOMPLETE;
- }
- } else if (PointeeTy->isSizelessType()) {
- InvalidTypeKind = CountedByInvalidPointeeTypeKind::SIZELESS;
- } else if (PointeeTy->isFunctionType()) {
- InvalidTypeKind = CountedByInvalidPointeeTypeKind::FUNCTION;
- } else if (!CountInBytes &&
- 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
-
+ // Validate the expression type
if (!E->getType()->isIntegerType() || E->getType()->isBooleanType()) {
Diag(E->getBeginLoc(), diag::err_count_attr_argument_not_integer)
<< Kind << E->getSourceRange();
@@ -381,6 +297,7 @@ bool Sema::CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes,
return true;
}
+ // Validate count field references
auto *CountDecl = DRE->getDecl();
FieldDecl *CountFD = dyn_cast<FieldDecl>(CountDecl);
if (auto *IFD = dyn_cast<IndirectFieldDecl>(CountDecl)) {
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 21e279ec2000c3..157216a50f5555 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -20243,6 +20243,25 @@ void Sema::ActOnFields(Scope *S, SourceLocation RecLoc, Decl *EnclosingDecl,
}
}
+ if (!getLangOpts().ExperimentalLateParseAttributes) {
+ // Perform FieldDecl-dependent validation for counted_by family attributes.
+ for (auto *D : Fields) {
+ FieldDecl *FD = cast<FieldDecl>(D);
+ if (auto *CAT = FD->getType()->getAs<CountAttributedType>()) {
+ if (CheckCountedByAttrOnField(FD, CAT->getCountExpr(),
+ CAT->isCountInBytes(), CAT->isOrNull())) {
+ // Rejected. Strip the CountAttributedType so the field keeps its
+ // plain wrapped type. The pre-refactor eager path built the type only
+ // after this check passed, so on failure no CAT ever existed.
+ QualType Wrapped = CAT->desugar();
+ FD->setType(Wrapped);
+ FD->setTypeSourceInfo(
+ Context.getTrivialTypeSourceInfo(Wrapped, FD->getLocation()));
+ }
+ }
+ }
+ }
+
// Verify that all the fields are okay.
SmallVector<FieldDecl*, 32> RecFields;
const FieldDecl *PreviousField = nullptr;
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 33b680c4d93d4c..717fa57691d502 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -7109,41 +7109,35 @@ static void handleNoPFPAttrField(Sema &S, Decl *D, const ParsedAttr &AL) {
D->addAttr(NoFieldProtectionAttr::Create(S.Context, AL));
}
+// Only reached for a counted_by-family attribute that
+// -fexperimental-late-parse-attributes late-parses as a declaration attribute.
+// This will be removed once late-parsing for type attribute is fully switched
+// over to the new mechanism so it's properly supported.
+// Every other one is a type attribute, handled by HandleCountedByAttrOnType.
static void handleCountedByAttrField(Sema &S, Decl *D, const ParsedAttr &AL) {
+ // Already applied, or diagnosed, as a type attribute.
+ if (AL.isUsedAsTypeAttr())
+ return;
+
auto *CountExpr = AL.getArgAsExpr(0);
if (!CountExpr)
return;
- bool CountInBytes;
- bool OrNull;
- switch (AL.getKind()) {
- case ParsedAttr::AT_CountedBy:
- CountInBytes = false;
- OrNull = false;
- break;
- case ParsedAttr::AT_CountedByOrNull:
- CountInBytes = false;
- OrNull = true;
- break;
- case ParsedAttr::AT_SizedBy:
- CountInBytes = true;
- OrNull = false;
- break;
- case ParsedAttr::AT_SizedByOrNull:
- CountInBytes = true;
- OrNull = true;
- break;
- default:
- llvm_unreachable("unexpected counted_by family attribute");
- }
-
- FieldDecl *FD = cast<FieldDecl>(D);
- if (S.CheckCountedByAttrOnField(FD, CountExpr, CountInBytes, OrNull))
+ auto *FD = cast<FieldDecl>(D);
+ Sema::BoundsAttrFlags Flags = Sema::BoundsAttrFlags::get(AL.getKind());
+ if (!S.ValidateBoundsAttrTypeShape(FD->getType(), AL.getLoc(), AL.getRange(),
+ Flags, /*AttrSpelling=*/{},
+ /*AllowRedecl=*/false, /*AttrArg=*/nullptr,
+ /*UpdateFlags=*/false))
+ return;
+ if (S.CheckCountedByAttrOnField(FD, CountExpr, Flags.CountInBytes,
+ Flags.OrNull))
return;
QualType CAT = S.BuildCountAttributedArrayOrPointerType(
- FD->getType(), CountExpr, CountInBytes, OrNull);
- FD->setType(CAT);
+ FD->getType(), CountExpr, Flags.CountInBytes, Flags.OrNull);
+ if (!CAT.isNull())
+ FD->setType(CAT);
}
static void handleFunctionReturnThunksAttr(Sema &S, Decl *D,
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index af0403e73480c0..76325c9be203ef 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -6455,6 +6455,7 @@ GetTypeSourceInfoForDeclarator(TypeProcessingState &State,
break;
}
+ case TypeLoc::CountAttributed:
case TypeLoc::Adjusted:
case TypeLoc::BTFTagAttributed: {
CurrTL = CurrTL.getNextTypeLoc().getUnqualifiedLoc();
@@ -9072,6 +9073,85 @@ static void HandleHLSLParamModifierAttr(TypeProcessingState &State,
}
}
+/// Return true if the type built at \p chunkIndex is part of a function's
+/// return type, i.e. a function chunk wraps it at any level. In both
+/// `int *__counted_by(n) (*fp)(void)` and `int *__counted_by(n) *(*fp)(void)`,
+/// the attributed pointer is in the return type of the function `fp` points to.
+///
+/// Chunk 0 is the one closest to the identifier, which is the outermost part of
+/// the type, so the type built by chunk i is wrapped by chunk i - 1. A
+/// parameter has its own declarator, so it is never part of \p D's chunks.
+static bool isOnFunctionReturnType(const Declarator &D, unsigned chunkIndex) {
+ assert(chunkIndex <= D.getNumTypeObjects());
+ for (unsigned i = chunkIndex; i != 0; --i)
+ if (D.getTypeObject(i - 1).Kind == DeclaratorChunk::Function)
+ return true;
+ return false;
+}
+
+/// Return the number of pointer and array chunks that wrap the type built at
+/// \p chunkIndex, up to the nearest function chunk. The level is relative to
+/// the type the attribute belongs to: the declared type, or the return type of
+/// the nearest enclosing function.
+///
+/// In `int *__counted_by(n) *pp`, the attributed pointer is chunk 1, wrapped by
+/// the pointer at chunk 0, so its level is 1. In
+/// `int *__counted_by(n) *(*fp)(void)`, the attributed pointer is chunk 4,
+/// wrapped by the pointer at chunk 3 and then the function at chunk 2, so its
+/// level is 1, relative to the return type.
+static unsigned getPointerNestLevel(const Declarator &D, unsigned chunkIndex) {
+ assert(chunkIndex <= D.getNumTypeObjects());
+ unsigned Level = 0;
+ for (unsigned i = chunkIndex; i != 0; --i) {
+ switch (D.getTypeObject(i - 1).Kind) {
+ case DeclaratorChunk::Function:
+ return Level;
+ case DeclaratorChunk::Pointer:
+ case DeclaratorChunk::Array:
+ ++Level;
+ break;
+ default:
+ break;
+ }
+ }
+ return Level;
+}
+
+static void HandleCountedByAttrOnType(TypeProcessingState &State,
+ QualType &CurType, ParsedAttr &Attr) {
+ Sema &S = State.getSema();
+
+ // This attribute is only supported in C.
+ if (!Attr.diagnoseLangOpts(S)) {
+ Attr.setInvalid();
+ return;
+ }
+
+ auto *CountExpr = Attr.getArgAsExpr(0);
+ if (!CountExpr)
+ return;
+
+ const Declarator &D = State.getDeclarator();
+ unsigned chunkIndex = State.getCurrentChunkIndex();
+
+ Sema::BoundsAttrFlags Flags;
+ if (!S.ValidateBoundsAttrTypeForTypePosition(
+ CurType, Attr.getKind(), Attr.getLoc(), Attr.getRange(),
+ getPointerNestLevel(D, chunkIndex),
+ isOnFunctionReturnType(D, chunkIndex), Flags)) {
+ Attr.setInvalid();
+ return;
+ }
+
+ QualType NewType = S.BuildCountAttributedArrayOrPointerType(
+ CurType, CountExpr, Flags.CountInBytes, Flags.OrNull);
+ if (NewType.isNull()) {
+ Attr.setInvalid();
+ return;
+ }
+ CurType = NewType;
+}
+
bool Sema::ProcessLateParsedTypeAttr(LateParsedTypeAttrInfo &LA, QualType &QT) {
// A DeclSpec attribute runs this once per declarator, as in
// `IP __counted_by(n) a, b;`. Reuse one node, as the eager path does, so no
@@ -9085,7 +9165,8 @@ bool Sema::ProcessLateParsedTypeAttr(LateParsedTypeAttrInfo &LA, QualType &QT) {
BoundsAttrFlags Flags;
if (!ValidateBoundsAttrTypeForTypePosition(QT, LA.Kind, LA.NameLoc,
SourceRange(LA.NameLoc),
- /*PointerNestLevel=*/0, Flags))
+ /*PointerNestLevel=*/0,
+ /*OnReturnType=*/false, Flags))
return false;
// The argument isn't parsed yet, so build the type without a count.
@@ -9335,6 +9416,19 @@ static void processTypeAttrs(TypeProcessingState &state, QualType &type,
break;
}
+ case ParsedAttr::AT_CountedBy:
+ case ParsedAttr::AT_CountedByOrNull:
+ case ParsedAttr::AT_SizedBy:
+ case ParsedAttr::AT_SizedByOrNull: {
+ // XXX: The late-parsing path hasn't been switched to the new mechanism to
+ // properly support type-position attributes yet.
+ if (!state.getSema().getLangOpts().ExperimentalLateParseAttributes) {
+ HandleCountedByAttrOnType(state, type, attr);
+ attr.setUsedAsTypeAttr();
+ }
+ break;
+ }
+
MS_TYPE_ATTRS_CASELIST:
if (!handleMSPointerTypeQualifierAttr(state, attr, type))
attr.setUsedAsTypeAttr();
@@ -10106,7 +10200,19 @@ QualType Sema::BuildCountAttributedArrayOrPointerType(QualType WrappedTy,
Expr *CountExpr,
bool CountInBytes,
bool OrNull) {
- assert(WrappedTy->isIncompleteArrayType() || WrappedTy->isPointerType());
+ // Accept any array or pointer type here. For arrays, validation that it's
+ // a flexible array member is deferred until CheckCountedByAttrOnField.
+ assert(WrappedTy->isArrayType() || WrappedTy->isPointerType());
+
+ // Reject non-DeclRefExpr early to avoid cast failure in
+ // BuildTypeCoupledDecls.
+ if (!isa<DeclRefExpr>(CountExpr)) {
+ unsigned Kind = getBoundsAttrKind(BoundsAttrFlags{CountInBytes, OrNull});
+ Diag(CountExpr->getBeginLoc(),
+ diag::err_count_attr_only_support_simple_decl_reference)
+ << Kind << CountExpr->getSourceRange();
+ return QualType();
+ }
llvm::SmallVector<TypeCoupledDeclRefInfo, 1> Decls;
BuildTypeCoupledDecls(CountExpr, Decls);
diff --git a/clang/test/AST/attr-counted-by-or-null-struct-ptrs.c b/clang/test/AST/attr-counted-by-or-null-struct-ptrs.c
index d42547003f0b38..ca603402f0a345 100644
--- a/clang/test/AST/attr-counted-by-or-null-struct-ptrs.c
+++ b/clang/test/AST/attr-counted-by-or-null-struct-ptrs.c
@@ -56,29 +56,6 @@ struct on_member_pointer_complete_ty_ty_pos {
struct size_known *__counted_by_or_null(count) buf;
};
-// TODO: This should be forbidden but isn't due to counted_by_or_null being treated as a
-// declaration attribute. The attribute ends up on the outer most pointer
-// (allowed by sema) even though syntactically its supposed to be on the inner
-// pointer (would not allowed by sema due to pointee being a function type).
-// CHECK-LABEL: RecordDecl {{.+}} struct on_member_pointer_fn_ptr_ty_ty_pos_inner definition
-// CHECK-NEXT: |-FieldDecl {{.+}} referenced count 'int'
-// CHECK-NEXT: `-FieldDecl {{.+}} fn_ptr 'void (** __counted_by_or_null(count))(void)':'void (**)(void)'
-struct on_member_pointer_fn_ptr_ty_ty_pos_inner {
- int count;
- void (* __counted_by_or_null(count) * fn_ptr)(void);
-};
-
-// FIXME: The generated AST here is wrong. The attribute should be on the inner
-// pointer.
-// CHECK-LABEL: RecordDecl {{.+}} struct on_nested_pointer_inner definition
-// CHECK-NEXT: |-FieldDecl {{.+}} referenced count 'int'
-// CHECK-NEXT: `-FieldDecl {{.+}} buf 'struct size_known ** __counted_by_or_null(count)':'struct size_known **'
-struct on_nested_pointer_inner {
- int count;
- // TODO: This should be disallowed because in the `-fbounds-safety` model
- // `__counted_by_or_null` can only be nested when used in function parameters.
- struct size_known *__counted_by_or_null(count) *buf;
-};
// CHECK-LABEL: RecordDecl {{.+}} struct on_nested_pointer_outer definition
// CHECK-NEXT: |-FieldDecl {{.+}} referenced count 'int'
diff --git a/clang/test/AST/attr-counted-by-struct-ptrs.c b/clang/test/AST/attr-counted-by-struct-ptrs.c
index afef9c8c3b95d2..414a7007c7b49d 100644
--- a/clang/test/AST/attr-counted-by-struct-ptrs.c
+++ b/clang/test/AST/attr-counted-by-struct-ptrs.c
@@ -45,8 +45,6 @@ struct on_pointer_anon_count {
//==============================================================================
// __counted_by on struct member pointer in type attribute position
//==============================================================================
-// TODO: Correctly parse counted_by as a type attribute. Currently it is parsed
-// as a declaration attribute
// CHECK-LABEL: RecordDecl {{.+}} struct on_member_pointer_complete_ty_ty_pos definition
// CHECK-NEXT: |-FieldDecl {{.+}} referenced count 'int'
@@ -56,30 +54,6 @@ struct on_member_pointer_complete_ty_ty_pos {
struct size_known *__counted_by(count) buf;
};
-// TODO: This should be forbidden but isn't due to counted_by being treated as a
-// declaration attribute. The attribute ends up on the outer most pointer
-// (allowed by sema) even though syntactically its supposed to be on the inner
-// pointer (would not allowed by sema due to pointee being a function type).
-// CHECK-LABEL: RecordDecl {{.+}} struct on_member_pointer_fn_ptr_ty_ty_pos_inner definition
-// CHECK-NEXT: |-FieldDecl {{.+}} referenced count 'int'
-// CHECK-NEXT: `-FieldDecl {{.+}} fn_ptr 'void (** __counted_by(count))(void)':'void (**)(void)'
-struct on_member_pointer_fn_ptr_ty_ty_pos_inner {
- int count;
- void (* __counted_by(count) * fn_ptr)(void);
-};
-
-// FIXME: The generated AST here is wrong. The attribute should be on the inner
-// pointer.
-// CHECK-LABEL: RecordDecl {{.+}} struct on_nested_pointer_inner definition
-// CHECK-NEXT: |-FieldDecl {{.+}} referenced count 'int'
-// CHECK-NEXT: `-FieldDecl {{.+}} buf 'struct size_known ** __counted_by(count)':'struct size_known **'
-struct on_nested_pointer_inner {
- int count;
- // TODO: This should be disallowed because in the `-fbounds-safety` model
- // `__counted_by` can only be nested when used in function parameters.
- struct size_known *__counted_by(count) *buf;
-};
-
// CHECK-LABEL: RecordDecl {{.+}} struct on_nested_pointer_outer definition
// CHECK-NEXT: |-FieldDecl {{.+}} referenced count 'int'
// CHECK-NEXT: `-FieldDecl {{.+}} buf 'struct size_known ** __counted_by(count)':'struct size_known **'
diff --git a/clang/test/AST/attr-sized-by-or-null-struct-ptrs.c b/clang/test/AST/attr-sized-by-or-null-struct-ptrs.c
index 7273280e4b60c0..2592d0fd5cb4e5 100644
--- a/clang/test/AST/attr-sized-by-or-null-struct-ptrs.c
+++ b/clang/test/AST/attr-sized-by-or-null-struct-ptrs.c
@@ -56,30 +56,6 @@ struct on_member_pointer_complete_ty_ty_pos {
struct size_known *__sized_by_or_null(count) buf;
};
-// TODO: This should be forbidden but isn't due to sized_by_or_null being treated as a
-// declaration attribute. The attribute ends up on the outer most pointer
-// (allowed by sema) even though syntactically its supposed to be on the inner
-// pointer (would not allowed by sema due to pointee being a function type).
-// CHECK-LABEL: RecordDecl {{.+}} struct on_member_pointer_fn_ptr_ty_ty_pos_inner definition
-// CHECK-NEXT: |-FieldDecl {{.+}} referenced count 'int'
-// CHECK-NEXT: `-FieldDecl {{.+}} fn_ptr 'void (** __sized_by_or_null(count))(void)':'void (**)(void)'
-struct on_member_pointer_fn_ptr_ty_ty_pos_inner {
- int count;
- void (* __sized_by_or_null(count) * fn_ptr)(void);
-};
-
-// FIXME: The generated AST here is wrong. The attribute should be on the inner
-// pointer.
-// CHECK-LABEL: RecordDecl {{.+}} struct on_nested_pointer_inner definition
-// CHECK-NEXT: |-FieldDecl {{.+}} referenced count 'int'
-// CHECK-NEXT: `-FieldDecl {{.+}} buf 'struct size_known ** __sized_by_or_null(count)':'struct size_known **'
-struct on_nested_pointer_inner {
- int count;
- // TODO: This should be disallowed because in the `-fbounds-safety` model
- // `__sized_by_or_null` can only be nested when used in function parameters.
- struct size_known *__sized_by_or_null(count) *buf;
-};
-
// CHECK-LABEL: RecordDecl {{.+}} struct on_nested_pointer_outer definition
// CHECK-NEXT: |-FieldDecl {{.+}} referenced count 'int'
// CHECK-NEXT: `-FieldDecl {{.+}} buf 'struct size_known ** __sized_by_or_null(count)':'struct size_known **'
diff --git a/clang/test/AST/attr-sized-by-struct-ptrs.c b/clang/test/AST/attr-sized-by-struct-ptrs.c
index 738eaf8cbf36b2..4d7797fa823956 100644
--- a/clang/test/AST/attr-sized-by-struct-ptrs.c
+++ b/clang/test/AST/attr-sized-by-struct-ptrs.c
@@ -56,30 +56,6 @@ struct on_member_pointer_complete_ty_ty_pos {
struct size_known *__sized_by(count) buf;
};
-// TODO: This should be forbidden but isn't due to sized_by being treated as a
-// declaration attribute. The attribute ends up on the outer most pointer
-// (allowed by sema) even though syntactically its supposed to be on the inner
-// pointer (would not allowed by sema due to pointee being a function type).
-// CHECK-LABEL: RecordDecl {{.+}} struct on_member_pointer_fn_ptr_ty_ty_pos_inner definition
-// CHECK-NEXT: |-FieldDecl {{.+}} referenced count 'int'
-// CHECK-NEXT: `-FieldDecl {{.+}} fn_ptr 'void (** __sized_by(count))(void)':'void (**)(void)'
-struct on_member_pointer_fn_ptr_ty_ty_pos_inner {
- int count;
- void (* __sized_by(count) * fn_ptr)(void);
-};
-
-// FIXME: The generated AST here is wrong. The attribute should be on the inner
-// pointer.
-// CHECK-LABEL: RecordDecl {{.+}} struct on_nested_pointer_inner definition
-// CHECK-NEXT: |-FieldDecl {{.+}} referenced count 'int'
-// CHECK-NEXT: `-FieldDecl {{.+}} buf 'struct size_known ** __sized_by(count)':'struct size_known **'
-struct on_nested_pointer_inner {
- int count;
- // TODO: This should be disallowed because in the `-fbounds-safety` model
- // `__sized_by` can only be nested when used in function parameters.
- struct size_known *__sized_by(count) *buf;
-};
-
// CHECK-LABEL: RecordDecl {{.+}} struct on_nested_pointer_outer definition
// CHECK-NEXT: |-FieldDecl {{.+}} referenced count 'int'
// CHECK-NEXT: `-FieldDecl {{.+}} buf 'struct size_known ** __sized_by(count)':'struct size_known **'
diff --git a/clang/test/Sema/attr-counted-by-or-null-struct-ptrs-completable-incomplete-pointee.c b/clang/test/Sema/attr-counted-by-or-null-struct-ptrs-completable-incomplete-pointee.c
index cff5a14c70b993..955e781b5679b3 100644
--- a/clang/test/Sema/attr-counted-by-or-null-struct-ptrs-completable-incomplete-pointee.c
+++ b/clang/test/Sema/attr-counted-by-or-null-struct-ptrs-completable-incomplete-pointee.c
@@ -1,5 +1,5 @@
-// RUN: %clang_cc1 -fsyntax-only -verify %s
-// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -verify %s
+// RUN: %clang_cc1 -fsyntax-only -verify=expected,immediate %s
+// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -verify=expected,late %s
#define __counted_by_or_null(f) __attribute__((counted_by_or_null(f)))
@@ -616,10 +616,11 @@ struct IncompleteTy3;
struct CBBufFAMofCountedByPtrs {
int size;
- // TODO: This is misleading. The attribute is written in the type position
- // but clang currently doesn't treat it like that and it gets treated as
- // an attribute on the array, rather than on the element type.
- // expected-error at +1{{'counted_by_or_null' only applies to pointers; did you mean to use 'counted_by'?}}
+ // The attribute is written on the element type, so it's a nested pointer.
+ // With -fexperimental-late-parse-attributes it's still treated as an
+ // attribute on the array until late parsing handles type attributes.
+ // late-error at +2{{'counted_by_or_null' only applies to pointers; did you mean to use 'counted_by'?}}
+ // immediate-error at +1{{'counted_by_or_null' attribute on nested pointer type is not allowed}}
struct IncompleteTy3* __counted_by_or_null(size) arr[];
};
diff --git a/clang/test/Sema/attr-counted-by-or-null-struct-ptrs.c b/clang/test/Sema/attr-counted-by-or-null-struct-ptrs.c
index 0fd739ca7d4c34..1bd625b0329cc6 100644
--- a/clang/test/Sema/attr-counted-by-or-null-struct-ptrs.c
+++ b/clang/test/Sema/attr-counted-by-or-null-struct-ptrs.c
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -fsyntax-only -Wpointer-arith -verify %s
+// RUN: %clang_cc1 -fsyntax-only -Wpointer-arith -verify=expected,immediate %s
// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -Wpointer-arith -verify %s
#define __counted_by_or_null(f) __attribute__((counted_by_or_null(f)))
@@ -158,10 +158,13 @@ struct on_member_pointer_fn_ptr_ty_ty_pos {
fn_ptr_ty __counted_by_or_null(count) fn_ptr;
};
-// TODO: This should be forbidden but isn't due to counted_by_or_null being treated
-// as a declaration attribute.
+// Forbidden: the pointee is a function type. With
+// -fexperimental-late-parse-attributes it's still treated as a declaration
+// attribute on the outermost pointer until late parsing handles type
+// attributes.
struct on_member_pointer_fn_ptr_ty_ty_pos_inner {
int count;
+ // immediate-error at +1{{'counted_by_or_null' cannot be applied to a pointer with pointee of unknown size because 'void (void)' is a function type}}
void (* __counted_by_or_null(count) * fn_ptr)(void);
};
@@ -181,9 +184,12 @@ struct on_member_pointer_struct_with_annotated_vla_ty_pos {
};
struct on_nested_pointer_inner {
- // TODO: This should be disallowed because in the `-fbounds-safety` model
- // `__counted_by_or_null` can only be nested when used in function parameters.
+ // Nested bounds attributes are not allowed. With
+ // -fexperimental-late-parse-attributes it's still treated as a declaration
+ // attribute on the outermost pointer until late parsing handles type
+ // attributes.
int count;
+ // immediate-error at +1{{'counted_by_or_null' attribute on nested pointer type is not allowed}}
struct size_known *__counted_by_or_null(count) *buf;
};
diff --git a/clang/test/Sema/attr-counted-by-return-type.c b/clang/test/Sema/attr-counted-by-return-type.c
new file mode 100644
index 00000000000000..6b9fe33a76853d
--- /dev/null
+++ b/clang/test/Sema/attr-counted-by-return-type.c
@@ -0,0 +1,53 @@
+// RUN: %clang_cc1 -fblocks -fsyntax-only -verify %s
+
+// counted_by-family attributes aren't supported on function return types,
+// including the return type of the function a function-pointer or
+// block-pointer member points to. They're rejected with a dedicated diagnostic.
+// They used to be reported as a nested pointer, because the function chunk
+// counted as a level of pointer nesting.
+
+#define __counted_by(f) __attribute__((counted_by(f)))
+#define __sized_by(f) __attribute__((sized_by(f)))
+#define __counted_by_or_null(f) __attribute__((counted_by_or_null(f)))
+#define __sized_by_or_null(f) __attribute__((sized_by_or_null(f)))
+
+extern int g;
+
+// expected-error at +1{{'counted_by' attribute cannot be applied to a function return type}}
+int *__counted_by(g) fn(void);
+// expected-error at +1{{'sized_by' attribute cannot be applied to a function return type}}
+char *__sized_by(4) fn_const(void);
+
+struct fn_ptr_return {
+ int n;
+ // expected-error at +1{{'counted_by' attribute cannot be applied to a function return type}}
+ int *__counted_by(n) (*fp)(void);
+};
+
+struct block_ptr_return {
+ int n;
+ // expected-error at +1{{'counted_by_or_null' attribute cannot be applied to a function return type}}
+ int *__counted_by_or_null(n) (^bp)(void);
+};
+
+struct fn_ptr_array_return {
+ int n;
+ // expected-error at +1{{'sized_by_or_null' attribute cannot be applied to a function return type}}
+ char *__sized_by_or_null(n) (*fpa[2])(void);
+};
+
+// A counted pointer nested anywhere in a return type is rejected as being on
+// the return type. Its nesting is measured relative to the return type.
+struct fn_ptr_return_nested {
+ int n;
+ // expected-error at +1{{'counted_by' attribute cannot be applied to a function return type}}
+ int *__counted_by(n) *(*fp)(void);
+};
+
+// Nesting stops at the function: the pointer to the function and the array
+// holding it don't count toward the return type's nesting.
+struct fn_ptr_array_nested_return {
+ int n;
+ // expected-error at +1{{'counted_by' attribute cannot be applied to a function return type}}
+ int *__counted_by(n) *(*fpa[2])(void);
+};
diff --git a/clang/test/Sema/attr-counted-by-struct-ptrs-completable-incomplete-pointee.c b/clang/test/Sema/attr-counted-by-struct-ptrs-completable-incomplete-pointee.c
index d28a2086b51b88..073c039b3810a4 100644
--- a/clang/test/Sema/attr-counted-by-struct-ptrs-completable-incomplete-pointee.c
+++ b/clang/test/Sema/attr-counted-by-struct-ptrs-completable-incomplete-pointee.c
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -fsyntax-only -verify %s
+// RUN: %clang_cc1 -fsyntax-only -verify=expected,immediate %s
// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -verify %s
#define __counted_by(f) __attribute__((counted_by(f)))
@@ -616,9 +616,10 @@ struct IncompleteTy3;
struct CBBufFAMofCountedByPtrs {
int size;
- // TODO: This is misleading. The attribute is written in the type position
- // but clang currently doesn't treat it like that and it gets treated as
- // an attribute on the array, rather than on the element type.
+ // The attribute is written on the element type, so it's a nested pointer.
+ // With -fexperimental-late-parse-attributes it's still treated as an
+ // attribute on the array until late parsing handles type attributes.
+ // immediate-error at +1{{'counted_by' attribute on nested pointer type is not allowed}}
struct IncompleteTy3* __counted_by(size) arr[];
};
diff --git a/clang/test/Sema/attr-counted-by-struct-ptrs.c b/clang/test/Sema/attr-counted-by-struct-ptrs.c
index a42f3895695a32..cfa9d10dba78b0 100644
--- a/clang/test/Sema/attr-counted-by-struct-ptrs.c
+++ b/clang/test/Sema/attr-counted-by-struct-ptrs.c
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -fsyntax-only -Wpointer-arith -verify %s
+// RUN: %clang_cc1 -fsyntax-only -Wpointer-arith -verify=expected,immediate %s
// RUN: %clang_cc1 -fsyntax-only -Wpointer-arith -fexperimental-late-parse-attributes %s -verify
#define __counted_by(f) __attribute__((counted_by(f)))
@@ -157,10 +157,13 @@ struct on_member_pointer_fn_ptr_ty_ty_pos {
fn_ptr_ty __counted_by(count) fn_ptr;
};
-// TODO: This should be forbidden but isn't due to counted_by being treated
-// as a declaration attribute.
+// Forbidden: the pointee is a function type. With
+// -fexperimental-late-parse-attributes it's still treated as a declaration
+// attribute on the outermost pointer until late parsing handles type
+// attributes.
struct on_member_pointer_fn_ptr_ty_ty_pos_inner {
int count;
+ // immediate-error at +1{{'counted_by' cannot be applied to a pointer with pointee of unknown size because 'void (void)' is a function type}}
void (* __counted_by(count) * fn_ptr)(void);
};
@@ -180,9 +183,12 @@ struct on_member_pointer_struct_with_annotated_vla_ty_pos {
};
struct on_nested_pointer_inner {
- // TODO: This should be disallowed because in the `-fbounds-safety` model
- // `__counted_by` can only be nested when used in function parameters.
+ // Nested bounds attributes are not allowed. With
+ // -fexperimental-late-parse-attributes it's still treated as a declaration
+ // attribute on the outermost pointer until late parsing handles type
+ // attributes.
int count;
+ // immediate-error at +1{{'counted_by' attribute on nested pointer type is not allowed}}
struct size_known *__counted_by(count) *buf;
};
diff --git a/clang/test/Sema/attr-sized-by-or-null-struct-ptrs.c b/clang/test/Sema/attr-sized-by-or-null-struct-ptrs.c
index 82819e5cf43fff..201d34c4d20676 100644
--- a/clang/test/Sema/attr-sized-by-or-null-struct-ptrs.c
+++ b/clang/test/Sema/attr-sized-by-or-null-struct-ptrs.c
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -fsyntax-only -verify %s
+// RUN: %clang_cc1 -fsyntax-only -verify=expected,immediate %s
// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -verify %s
#define __sized_by_or_null(f) __attribute__((sized_by_or_null(f)))
@@ -149,10 +149,13 @@ struct on_member_pointer_fn_ptr_ty_ty_pos {
fn_ptr_ty __sized_by_or_null(size) fn_ptr;
};
-// TODO: This should be forbidden but isn't due to sized_by_or_null being treated
-// as a declaration attribute.
+// Forbidden: the pointee is a function type. With
+// -fexperimental-late-parse-attributes it's still treated as a declaration
+// attribute on the outermost pointer until late parsing handles type
+// attributes.
struct on_member_pointer_fn_ptr_ty_ty_pos_inner {
int size;
+ // immediate-error at +1{{'sized_by_or_null' cannot be applied to a pointer with pointee of unknown size because 'void (void)' is a function type}}
void (* __sized_by_or_null(size) * fn_ptr)(void);
};
@@ -167,9 +170,12 @@ struct on_member_pointer_struct_with_annotated_vla_ty_pos {
};
struct on_nested_pointer_inner {
- // TODO: This should be disallowed because in the `-fbounds-safety` model
- // `__sized_by_or_null` can only be nested when used in function parameters.
+ // Nested bounds attributes are not allowed. With
+ // -fexperimental-late-parse-attributes it's still treated as a declaration
+ // attribute on the outermost pointer until late parsing handles type
+ // attributes.
int size;
+ // immediate-error at +1{{'sized_by_or_null' attribute on nested pointer type is not allowed}}
struct size_known *__sized_by_or_null(size) *buf;
};
diff --git a/clang/test/Sema/attr-sized-by-struct-ptrs.c b/clang/test/Sema/attr-sized-by-struct-ptrs.c
index 6dc76b8bc7081e..adfa8d82f7d906 100644
--- a/clang/test/Sema/attr-sized-by-struct-ptrs.c
+++ b/clang/test/Sema/attr-sized-by-struct-ptrs.c
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -fsyntax-only -verify %s
+// RUN: %clang_cc1 -fsyntax-only -verify=expected,immediate %s
// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -verify %s
#define __sized_by(f) __attribute__((sized_by(f)))
@@ -149,10 +149,13 @@ struct on_member_pointer_fn_ptr_ty_ty_pos {
fn_ptr_ty __sized_by(size) fn_ptr;
};
-// TODO: This should be forbidden but isn't due to sized_by being treated
-// as a declaration attribute.
+// Forbidden: the pointee is a function type. With
+// -fexperimental-late-parse-attributes it's still treated as a declaration
+// attribute on the outermost pointer until late parsing handles type
+// attributes.
struct on_member_pointer_fn_ptr_ty_ty_pos_inner {
int size;
+ // immediate-error at +1{{'sized_by' cannot be applied to a pointer with pointee of unknown size because 'void (void)' is a function type}}
void (* __sized_by(size) * fn_ptr)(void);
};
@@ -167,9 +170,12 @@ struct on_member_pointer_struct_with_annotated_vla_ty_pos {
};
struct on_nested_pointer_inner {
- // TODO: This should be disallowed because in the `-fbounds-safety` model
- // `__sized_by` can only be nested when used in function parameters.
+ // Nested bounds attributes are not allowed. With
+ // -fexperimental-late-parse-attributes it's still treated as a declaration
+ // attribute on the outermost pointer until late parsing handles type
+ // attributes.
int size;
+ // immediate-error at +1{{'sized_by' attribute on nested pointer type is not allowed}}
struct size_known *__sized_by(size) *buf;
};
>From efbe6027d791eab27256d0b87c37d37b60e2fd09 Mon Sep 17 00:00:00 2001
From: Yeoul Na <yeoul_na at apple.com>
Date: Tue, 20 Oct 2026 10:49:52 -0700
Subject: [PATCH 2/3] [BoundsSafety] Reject a second bounds attribute on the
same pointer
A pointer with two counted_by-family attributes, as in
`int *__counted_by(n) __sized_by(m) p;`, got one CountAttributedType stacked on
another. Reject the second attribute with
err_counted_by_on_bounds_attributed_type, which names both attributes.
Assisted-by: Opus 5.5
---
.../clang/Basic/DiagnosticSemaKinds.td | 4 ++
clang/lib/Sema/SemaBoundsSafety.cpp | 13 +++++
.../attr-counted-by-multiple-on-pointer.c | 58 +++++++++++++++++++
3 files changed, 75 insertions(+)
create mode 100644 clang/test/Sema/attr-counted-by-multiple-on-pointer.c
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index d796386cf8a174..197c81bb45d53f 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -7283,6 +7283,10 @@ def err_counted_by_on_nested_pointer : Error<
"'%select{counted_by|sized_by|counted_by_or_null|sized_by_or_null}0' attribute on nested pointer type is not allowed">;
def err_counted_by_on_return_type : Error<
"'%select{counted_by|sized_by|counted_by_or_null|sized_by_or_null}0' attribute cannot be applied to a function return type">;
+def err_counted_by_on_bounds_attributed_type : Error<
+ "'%select{counted_by|sized_by|counted_by_or_null|sized_by_or_null}0' attribute "
+ "cannot be applied to a pointer that already has a "
+ "'%select{counted_by|sized_by|counted_by_or_null|sized_by_or_null}1' attribute">;
let CategoryName = "ARC Semantic Issue" in {
diff --git a/clang/lib/Sema/SemaBoundsSafety.cpp b/clang/lib/Sema/SemaBoundsSafety.cpp
index a4bb0548a58b98..b187410bb6956f 100644
--- a/clang/lib/Sema/SemaBoundsSafety.cpp
+++ b/clang/lib/Sema/SemaBoundsSafety.cpp
@@ -255,6 +255,19 @@ bool Sema::ValidateBoundsAttrTypeForTypePosition(
PointerNestLevel, OnReturnType))
return false;
+ // A pointer takes only one bounds attribute. This sees one applied eagerly
+ // or late, since a late-parsed one is an incomplete CountAttributedType until
+ // its count is parsed.
+ //
+ // TODO: Converge with the -fbounds-safety attribute model checks in
+ // checkBoundsAttrTypeConflictsAndMisc.
+ if (!getLangOpts().hasBoundsSafetyAttributes())
+ if (const auto *CATy = Ty->getAs<CountAttributedType>()) {
+ Diag(AttrLoc, diag::err_counted_by_on_bounds_attributed_type)
+ << getBoundsAttrKind(Flags) << CATy->getKind() << AttrRange;
+ return false;
+ }
+
return true;
}
diff --git a/clang/test/Sema/attr-counted-by-multiple-on-pointer.c b/clang/test/Sema/attr-counted-by-multiple-on-pointer.c
new file mode 100644
index 00000000000000..be588c15bd1648
--- /dev/null
+++ b/clang/test/Sema/attr-counted-by-multiple-on-pointer.c
@@ -0,0 +1,58 @@
+// RUN: %clang_cc1 -fsyntax-only -verify %s
+
+// A pointer can have only one counted_by-family attribute. A second one used
+// to stack another CountAttributedType on the first.
+
+#define __counted_by(f) __attribute__((counted_by(f)))
+#define __counted_by_or_null(f) __attribute__((counted_by_or_null(f)))
+#define __sized_by(f) __attribute__((sized_by(f)))
+#define __sized_by_or_null(f) __attribute__((sized_by_or_null(f)))
+
+typedef int *IP;
+
+struct same_attr {
+ int n;
+ int m;
+ // expected-error at +1{{'counted_by' attribute cannot be applied to a pointer that already has a 'counted_by' attribute}}
+ int *__counted_by(n) __counted_by(m) p;
+};
+
+struct same_count {
+ int n;
+ // expected-error at +1{{'counted_by' attribute cannot be applied to a pointer that already has a 'counted_by' attribute}}
+ int *__counted_by(n) __counted_by(n) p;
+};
+
+struct different_attrs {
+ int n;
+ int m;
+ // expected-error at +1{{'sized_by' attribute cannot be applied to a pointer that already has a 'counted_by' attribute}}
+ int *__counted_by(n) __sized_by(m) p;
+ // expected-error at +1{{'counted_by_or_null' attribute cannot be applied to a pointer that already has a 'sized_by_or_null' attribute}}
+ int *__sized_by_or_null(n) __counted_by_or_null(m) q;
+};
+
+// One in declaration-specifier position, shared by both declarators, is
+// diagnosed once.
+struct shared_decl_spec {
+ int n;
+ int m;
+ // expected-error at +1{{'sized_by' attribute cannot be applied to a pointer that already has a 'counted_by' attribute}}
+ IP __counted_by(n) __sized_by(m) a, b;
+};
+
+// One in declaration-specifier position and one in the declarator.
+struct decl_spec_and_declarator {
+ int n;
+ int m;
+ // expected-error at +1{{'sized_by' attribute cannot be applied to a pointer that already has a 'counted_by' attribute}}
+ IP __counted_by(n) a __sized_by(m);
+};
+
+// One in the pointer chunk and one after the declarator.
+struct chunk_and_after_declarator {
+ int n;
+ int m;
+ // expected-error at +1{{'sized_by' attribute cannot be applied to a pointer that already has a 'counted_by' attribute}}
+ int *__counted_by(n) p __sized_by(m);
+};
>From 8513c826de45e9171bcb72689ccdea6b2c291709 Mon Sep 17 00:00:00 2001
From: Yeoul Na <yeoul_na at apple.com>
Date: Tue, 20 Oct 2026 18:00:11 -0700
Subject: [PATCH 3/3] Fix test failures in -fexperimental-bounds-safety
---
clang/lib/Sema/SemaBoundsSafety.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/clang/lib/Sema/SemaBoundsSafety.cpp b/clang/lib/Sema/SemaBoundsSafety.cpp
index b187410bb6956f..c4477c228a08ac 100644
--- a/clang/lib/Sema/SemaBoundsSafety.cpp
+++ b/clang/lib/Sema/SemaBoundsSafety.cpp
@@ -164,7 +164,7 @@ bool Sema::ValidateBoundsAttrTypeShape(QualType Ty, SourceLocation AttrLoc,
// Exception: void has an implicit size of 1 byte for pointer arithmetic
// (following GNU convention). Therefore, counted_by on void* is allowed
// and behaves equivalently to sized_by (treating the count as bytes).
- if (PointeeTy->isVoidType() && !getLangOpts().hasBoundsSafetyAttributes()) {
+ if (PointeeTy->isVoidType()) {
// Emit a warning that this is a GNU extension.
Diag(AttrLoc, diag::ext_gnu_counted_by_void_ptr) << Kind;
Diag(AttrLoc, diag::note_gnu_counted_by_void_ptr_use_sized_by) << Kind;
@@ -180,7 +180,7 @@ bool Sema::ValidateBoundsAttrTypeShape(QualType Ty, SourceLocation AttrLoc,
InvalidTypeKind = CountedByInvalidPointeeTypeKind::FUNCTION;
} else if (!Flags.CountInBytes &&
PointeeTy->isStructureTypeWithFlexibleArrayMember()) {
- if (Ty->isArrayType()) {
+ if (!getLangOpts().BoundsSafety && Ty->isArrayType()) {
// 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
More information about the llvm-branch-commits
mailing list