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
