https://github.com/rapidsna updated 
https://github.com/llvm/llvm-project/pull/224846

>From b11b1745e1f15b3d771c7c78ef2322c7875c0de0 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 fca68f292f667..205d68b0979fe 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 7539e000d03f9..0d615824ecc5b 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 5becfc9fae152..6e8a75bdf2bd5 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 75041c801b6ff..2afe0812dcf4f 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 2796ac2929f46..b6641fa538ae2 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,

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to