https://github.com/rapidsna created
https://github.com/llvm/llvm-project/pull/224846
Introduce the single "is this type valid for a counted_by-family attribute in
type position" leaf that both the eager type-attribute path and the
late-parsed path will call:
- Sema::ValidateBoundsAttrTypeShape holds the type-shape checks -- pointer
or flexible array member, void and function pointee, pointee that is a
struct with a flexible array member -- and their diagnostics.
- validateBoundsAttrTypeForTypePosition wraps it for type position and adds
the nested-pointer rejection, reported with the new
err_counted_by_on_nested_pointer diagnostic.
Supporting pieces: Sema::BoundsAttrFlags and Sema::getBoundsAttrKind,
getCountAttrKind, getPointerNestLevel, the CountedByInvalidPointeeTypeKind
enum, and LangOptions::hasBoundsSafetyAttributes(), a stub returning false so
the shared leaf can gate its -fbounds-safety-only branches with the same
predicate used downstream.
Unused at this point -- nothing invokes it yet -- so this is NFC. The callers
are added in the following commits.
Assisted-by: Opus 4.8
---
Replaces #224557, which GitHub closed as "merged" during a stack reorder. No
merge took place -- a force-push made this branch's commit reachable from the
PR's base branch, so GitHub flipped the label. The commit never reached `main`.
Review history from the original PR remains on #224557.
>From 962a0a41854457b07ec7da0b847709065d734f4b Mon Sep 17 00:00:00 2001
From: Yeoul Na <[email protected]>
Date: Thu, 10 Sep 2026 07:30:15 -0700
Subject: [PATCH] [BoundsSafety][NFC] Add counted_by type-shape validation
helper
Introduce the single "is this type valid for a counted_by-family attribute in
type position" leaf that both the eager type-attribute path and the
late-parsed path will call:
- Sema::ValidateBoundsAttrTypeShape holds the type-shape checks -- pointer
or flexible array member, void and function pointee, pointee that is a
struct with a flexible array member -- and their diagnostics.
- validateBoundsAttrTypeForTypePosition wraps it for type position and adds
the nested-pointer rejection, reported with the new
err_counted_by_on_nested_pointer diagnostic.
Supporting pieces: Sema::BoundsAttrFlags and Sema::getBoundsAttrKind,
getCountAttrKind, getPointerNestLevel, the CountedByInvalidPointeeTypeKind
enum, and LangOptions::hasBoundsSafetyAttributes(), a stub returning false so
the shared leaf can gate its -fbounds-safety-only branches with the same
predicate used downstream.
Unused at this point -- nothing invokes it yet -- so this is NFC. The callers
are added in the following commits.
---
.../clang/Basic/DiagnosticSemaKinds.td | 3 +
clang/include/clang/Basic/LangOptions.h | 7 ++
clang/include/clang/Sema/Sema.h | 24 ++++
clang/lib/Sema/SemaBoundsSafety.cpp | 112 ++++++++++++++++++
clang/lib/Sema/SemaType.cpp | 83 +++++++++++++
5 files changed, 229 insertions(+)
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td
b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index fca68f292f6671..205d68b0979fe1 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -7266,6 +7266,9 @@ def err_builtin_counted_by_ref_invalid_use : Error<
"value returned by '__builtin_counted_by_ref' cannot be used in "
"%select{an array subscript|a binary}0 expression">;
+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">;
+
let CategoryName = "ARC Semantic Issue" in {
// ARC-mode diagnostics.
diff --git a/clang/include/clang/Basic/LangOptions.h
b/clang/include/clang/Basic/LangOptions.h
index 7539e000d03f98..0d615824ecc5b4 100644
--- a/clang/include/clang/Basic/LangOptions.h
+++ b/clang/include/clang/Basic/LangOptions.h
@@ -710,6 +710,13 @@ class LangOptions : public LangOptionsBase {
return ConvergentFunctions;
}
+ /// Returns true when the -fbounds-safety attribute programming model is in
+ /// effect. There is no attributes-only mode on this base, so this is always
+ /// false; it exists so the shared Sema::ValidateBoundsAttrTypeShape leaf can
+ /// gate its -fbounds-safety-only branches with the same predicate used
+ /// downstream (where those branches carry the extra diagnostics).
+ bool hasBoundsSafetyAttributes() const { return false; }
+
/// Return true if atomicrmw operations targeting allocations in private
/// memory are undefined.
bool threadPrivateMemoryAtomicsAreUndefined() const {
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 03fc536e4ec6e0..8fb985423c4a4f 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -2493,6 +2493,30 @@ class Sema final : public SemaBase {
/// Implementations are in SemaBoundsSafety.cpp
///@{
public:
+ struct BoundsAttrFlags {
+ bool CountInBytes = false;
+ bool OrNull = false;
+ bool IsEndedBy = false;
+ };
+ static BoundsAttrFlags getBoundsAttrFlags(AttributeCommonInfo::Kind K);
+ static BoundsAttributedType::BoundsAttrKind
+ getBoundsAttrKind(const BoundsAttrFlags &);
+
+ /// Validates that a type is eligible for an "externally counted" bounds
+ /// attribute (counted_by/sized_by and their _or_null variants).
+ ///
+ /// \p Flags selects the attribute variant. \returns true if the type is
+ /// valid, false on error (diagnostics emitted). For `void *__counted_by(n)`
+ /// it warns that the count is treated as a byte size and sets
+ /// \p Flags.CountInBytes; callers that want to preserve a counted_by node
+ /// pass a scratch copy (see validateBoundsAttrTypeForTypePosition).
+ bool ValidateBoundsAttrTypeShape(QualType Ty, SourceLocation AttrLoc,
+ SourceRange AttrRange,
+ BoundsAttrFlags &Flags,
+ StringRef AttrSpelling = {},
+ bool AllowRedecl = false,
+ Expr *AttrArg = nullptr);
+
/// Check if applying the specified attribute variant from the "counted by"
/// family of attributes to FieldDecl \p FD is semantically valid. If
/// semantically invalid diagnostics will be emitted explaining the problems.
diff --git a/clang/lib/Sema/SemaBoundsSafety.cpp
b/clang/lib/Sema/SemaBoundsSafety.cpp
index 75041c801b6ff8..2afe0812dcf4fe 100644
--- a/clang/lib/Sema/SemaBoundsSafety.cpp
+++ b/clang/lib/Sema/SemaBoundsSafety.cpp
@@ -26,6 +26,34 @@ static CountAttributedType::BoundsAttrKind
getCountAttrKind(bool CountInBytes,
: CountAttributedType::CountedBy;
}
+BoundsAttributedType::BoundsAttrKind
+Sema::getBoundsAttrKind(const BoundsAttrFlags &Flags) {
+ // `ended_by` (Flags.IsEndedBy) has no home on this base; the field exists
for
+ // struct parity with the downstream API but is never set here.
+ return getCountAttrKind(Flags.CountInBytes, Flags.OrNull);
+}
+
+Sema::BoundsAttrFlags Sema::getBoundsAttrFlags(AttributeCommonInfo::Kind K) {
+ BoundsAttrFlags Flags;
+ switch (K) {
+ case ParsedAttr::AT_SizedBy:
+ Flags.CountInBytes = true;
+ break;
+ case ParsedAttr::AT_SizedByOrNull:
+ Flags.CountInBytes = true;
+ Flags.OrNull = true;
+ break;
+ case ParsedAttr::AT_CountedBy:
+ break;
+ case ParsedAttr::AT_CountedByOrNull:
+ Flags.OrNull = true;
+ break;
+ default:
+ llvm_unreachable("unexpected bounds attribute kind");
+ }
+ return Flags;
+}
+
static const RecordDecl *GetEnclosingNamedOrTopAnonRecord(const FieldDecl *FD)
{
const auto *RD = FD->getParent();
// An unnamed struct is treated as anonymous struct at this point.
@@ -49,6 +77,90 @@ enum class CountedByInvalidPointeeTypeKind {
VALID,
};
+bool Sema::ValidateBoundsAttrTypeShape(QualType Ty, SourceLocation AttrLoc,
+ SourceRange AttrRange,
+ BoundsAttrFlags &Flags,
+ StringRef AttrSpelling, bool
AllowRedecl,
+ Expr *AttrArg) {
+ // The downstream leaf runs a `hasBoundsSafetyAttributes()`-gated
+ // `checkBoundsAttrTypeConflictsAndMisc` preamble and an `ended_by` early
+ // path; both depend on machinery (`DynamicRangePointerType`,
+ // `ValueTerminatedType`, the `err_bounds_safety_*` diagnostics) that does
not
+ // exist here, so they are the omitted bounds-safety arms. The rest matches.
+ BoundsAttributedType::BoundsAttrKind Kind = getBoundsAttrKind(Flags);
+
+ // counted_by/sized_by: must be pointer or array.
+ if (!Ty->isPointerType() && !Ty->isArrayType()) {
+ Diag(AttrLoc, diag::err_count_attr_not_on_ptr_or_flexible_array_member)
+ << Kind << 0;
+ return false;
+ }
+
+ // Arrays with sized_by or _or_null variants are not allowed under the
+ // non -fbounds-safety path; emit the "did you mean to use 'counted_by'"
hint.
+ if (!getLangOpts().hasBoundsSafetyAttributes() && Ty->isArrayType() &&
+ (Flags.CountInBytes || Flags.OrNull)) {
+ Diag(AttrLoc, diag::err_count_attr_not_on_ptr_or_flexible_array_member)
+ << Kind << /*suggest counted_by*/ 1;
+ return false;
+ }
+
+ // Pointee/element type validation.
+ QualType PointeeTy;
+ int SelectPtrOrArr;
+ if (Ty->isPointerType()) {
+ PointeeTy = Ty->getPointeeType();
+ SelectPtrOrArr = 0;
+ } else {
+ const ArrayType *AT = getASTContext().getAsArrayType(Ty);
+ PointeeTy = AT->getElementType();
+ SelectPtrOrArr = 1;
+ }
+
+ auto InvalidTypeKind = CountedByInvalidPointeeTypeKind::VALID;
+ bool ShouldWarn = false;
+ if (!Flags.CountInBytes && PointeeTy->isAlwaysIncompleteType()) {
+ // 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())
{
+ // 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;
+ Flags.CountInBytes = true;
+ return true;
+ }
+ InvalidTypeKind = CountedByInvalidPointeeTypeKind::INCOMPLETE;
+ } else if (PointeeTy->isSizelessType()) {
+ InvalidTypeKind = CountedByInvalidPointeeTypeKind::SIZELESS;
+ } else if (PointeeTy->isFunctionType()) {
+ InvalidTypeKind = CountedByInvalidPointeeTypeKind::FUNCTION;
+ } else if (!Flags.CountInBytes &&
+ PointeeTy->isStructureTypeWithFlexibleArrayMember()) {
+ if (Ty->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(AttrLoc, DiagID) << SelectPtrOrArr << PointeeTy <<
(int)InvalidTypeKind
+ << (ShouldWarn ? 1 : 0) << Kind << AttrRange;
+ return false;
+ }
+
+ return true;
+}
+
bool Sema::CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes,
bool OrNull) {
// Check the context the attribute is used in
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 3dcd330c52c837..609684523573cd 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -9043,6 +9043,89 @@ static void
HandleHLSLParamModifierAttr(TypeProcessingState &State,
}
}
+static CountAttributedType::BoundsAttrKind getCountAttrKind(bool CountInBytes,
+ bool OrNull) {
+ if (CountInBytes)
+ return OrNull ? CountAttributedType::SizedByOrNull
+ : CountAttributedType::SizedBy;
+ return OrNull ? CountAttributedType::CountedByOrNull
+ : CountAttributedType::CountedBy;
+}
+
+/// Calculate the pointer nesting level for counted_by attribute validation.
+/// Counts the number of pointer/array/function declarator chunks before the
+/// specified chunk index.
+///
+/// For example, given \c "int * __counted_by(n) *pp" the declarator chunks
+/// are (outermost first): [0]=Pointer(\c int **), [1]=Pointer(\c int *).
+/// When processing the inner pointer at \p chunkIndex=1, one Pointer chunk
+/// precedes it, so the function returns 1.
+///
+/// \param state The type processing state
+/// \param chunkIndex The index of the current declarator chunk
+/// \return The number of pointer/array/function chunks before chunkIndex
+static unsigned getPointerNestLevel(TypeProcessingState &state,
+ unsigned chunkIndex) {
+ unsigned pointerNestLevel = 0;
+ const auto &stateDeclarator = state.getDeclarator();
+ assert(chunkIndex <= stateDeclarator.getNumTypeObjects());
+ // DeclChunks are ordered identifier out. Index 0 is the outer most type
+ // object. Find outer pointer, array or function.
+ for (unsigned i = 0; i < chunkIndex; ++i) {
+ auto TypeObject = stateDeclarator.getTypeObject(i);
+ switch (TypeObject.Kind) {
+ case DeclaratorChunk::Function:
+ case DeclaratorChunk::Array:
+ case DeclaratorChunk::Pointer:
+ pointerNestLevel++;
+ break;
+ default:
+ break;
+ }
+ }
+ return pointerNestLevel;
+}
+
+/// The single "is this type valid for a counted_by-family attribute in type
+/// position" leaf, shared by the eager path (HandleCountedByAttrOnType) and
the
+/// late-parsed path (Sema::ActOnLateParsedTypeAttr).
+///
+/// Delegates the type-shape checks to Sema::ValidateBoundsAttrTypeShape so
+/// there is exactly one copy of those diagnostics, and adds the nested-pointer
+/// rejection that only applies in type position.
+///
+/// \p Flags is set from \p AttrKind and returned to the caller for building
the
+/// type.
+static bool validateBoundsAttrTypeForTypePosition(
+ Sema &S, QualType Ty, ParsedAttr::Kind AttrKind, SourceLocation AttrLoc,
+ SourceRange AttrRange, unsigned PointerNestLevel,
+ Sema::BoundsAttrFlags &Flags) {
+ Flags = Sema::getBoundsAttrFlags(AttrKind);
+
+ // ValidateBoundsAttrTypeShape may rewrite Flags.CountInBytes: for
+ // `void *__counted_by(n)` it warns "treated as 'sized_by'" and sets
+ // CountInBytes so the -fbounds-safety path builds a SizedBy node. The
+ // pre-existing non-BoundsSafety field path discards that rewrite and builds
a
+ // CountedBy node, so absorb it in a scratch copy to keep this
+ // diagnostics-only.
+ //
+ // FIXME: Reconcile with the -fbounds-safety path, which honors the rewrite.
+ Sema::BoundsAttrFlags Scratch = Flags;
+ if (!S.ValidateBoundsAttrTypeShape(Ty, AttrLoc, AttrRange, Scratch))
+ return false;
+
+ // A counted_by-family attribute has to end up at the outermost level of the
+ // declared type; a nested one would be buried where the bounds cannot be
+ // maintained.
+ if (PointerNestLevel > 0) {
+ S.Diag(AttrLoc, diag::err_counted_by_on_nested_pointer)
+ << Sema::getBoundsAttrKind(Flags);
+ return false;
+ }
+
+ return true;
+}
+
static void processTypeAttrs(TypeProcessingState &state, QualType &type,
TypeAttrLocation TAL,
const ParsedAttributesView &attrs,
_______________________________________________
llvm-branch-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits