https://github.com/rapidsna updated https://github.com/llvm/llvm-project/pull/224558
>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 1/2] [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 03fc536e4ec6e..8fb985423c4a4 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 3dcd330c52c83..609684523573c 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, >From f92108a1dcf0a535fdc07755af196475702d746b Mon Sep 17 00:00:00 2001 From: Yeoul Na <[email protected]> Date: Thu, 17 Sep 2026 21:12:37 -0700 Subject: [PATCH 2/2] [BoundsSafety][NFC] Add the Sema/Parser bridge for late-parsed type attributes A late-parsed bounds attribute has to build its type when the attribute is seen, but its argument isn't parseable until the enclosing record is complete. Building that type needs the Parser (which owns the cached tokens) and Sema (which owns type construction) to meet: - Sema::ActOnLateParsedTypeAttr validates a counted_by-family attribute for the type position and, if valid, wraps the type in a CountAttributedType whose count is not yet known, handing the node back for completion. - Parser::ProcessLateParsedTypeAttrCallback is the Parser-side entry point, registered on Sema so Sema can call back without including Parser.h (the same pattern as LateTemplateParserCallback). It reuses an already-built node so several declarators sharing one attribute share one type. No functional change: nothing records late-parsed type attributes yet, so the callback is never invoked. The next commit wires it up. --- clang/include/clang/Parse/Parser.h | 14 +++++++++++++ clang/include/clang/Sema/Sema.h | 21 ++++++++++++++++++++ clang/lib/Parse/ParseDecl.cpp | 32 ++++++++++++++++++++++++++++++ clang/lib/Parse/Parser.cpp | 5 +++++ clang/lib/Sema/SemaType.cpp | 21 ++++++++++++++++++++ 5 files changed, 93 insertions(+) diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h index ce97ad25bcd16..ae7c345ba6ce3 100644 --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -8153,6 +8153,20 @@ class Parser : public CodeCompletionHandler { static void LateTemplateParserCallback(void *P, LateParsedTemplate &LPT); + /// Validate \p LA as a late-parsed type attribute and, if valid, wrap \p type + /// in a \c CountAttributedType whose count expression is not yet known, + /// recording the node on \p LA so it can be completed later. + /// + /// \p LA is downcast to \c LateParsedTypeAttribute; if the cast fails the + /// attribute is not applicable here and the function returns \c true to skip. + /// \p pointerNestLevel is the number of pointer/array/function declarator + /// chunks that precede the current chunk (see \c getPointerNestLevel). + /// Returns \c true on success and \c false if the attribute is invalid for + /// \p type. + static bool ProcessLateParsedTypeAttrCallback(LateParsedAttribute *LA, + QualType &type, + unsigned pointerNestLevel); + /// We've parsed something that could plausibly be intended to be a template /// name (\p LHS) followed by a '<' token, and the following code can't /// possibly be an expression. Determine if this is likely to be a template-id diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 8fb985423c4a4..aa18627776da8 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -1354,6 +1354,27 @@ class Sema final : public SemaBase { OpaqueParser = P; } + /// Callback to the parser to interact with late-parsed type attributes. This + /// allows Sema to call back into Parser without including Parser.h. + /// + /// Processes a single late-parsed type attribute: validates the attribute + /// kind/type and wraps \p type in a CountAttributedType whose count is not + /// yet known, if appropriate. Returns false if the attribute is invalid. + typedef bool ProcessLateParsedTypeAttrCB(LateParsedAttribute *LA, + QualType &type, + unsigned pointerNestLevel); + ProcessLateParsedTypeAttrCB *ProcessLateParsedTypeAttrCallback = nullptr; + + /// Called from the Parser's ProcessLateParsedTypeAttrCallback to validate a + /// counted_by-family attribute type and, if valid, wrap \p type in a + /// CountAttributedType whose count expression is not yet known. Returns false + /// if the attribute should be dropped, otherwise sets \p BATy to the node the + /// caller must complete once the argument is parseable. + bool ActOnLateParsedTypeAttr(ParsedAttr::Kind AttrKind, + SourceLocation AttrNameLoc, QualType &type, + unsigned pointerNestLevel, + BoundsAttributedType **BATy); + /// Callback to the parser to parse a type expressed as a string. std::function<TypeResult(StringRef, StringRef, SourceLocation)> ParseTypeFromStringCallback; diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp index de97611e8bcae..0d205df0c2a48 100644 --- a/clang/lib/Parse/ParseDecl.cpp +++ b/clang/lib/Parse/ParseDecl.cpp @@ -4896,6 +4896,38 @@ void Parser::ParseLexedTypeAttribute(LateParsedTypeAttribute &LA, OutAttrs.takeAllAppendingFrom(Attrs); } +bool Parser::ProcessLateParsedTypeAttrCallback(LateParsedAttribute *LA, + QualType &type, + unsigned pointerNestLevel) { + auto *LTA = dyn_cast_if_present<LateParsedTypeAttribute>(LA); + if (!LTA) + return true; + + // One attribute yields one type node, even when several declarators share it. + // A declaration-specifier-position attribute lives on the DeclSpec, whose + // late-attribute list ConvertDeclSpecToType walks once per declarator, so + // this callback runs N times for `IP __counted_by(n) a, b;`. Building a fresh + // (deliberately un-uniqued) node each time would leave every node but the + // last orphaned with a null count, so reuse the node instead. This matches + // the eager path, where getCountAttributedType uniques on the count + // expression and all declarators likewise share one node. + if (LTA->TypeToComplete) { + type = QualType(LTA->TypeToComplete, 0); + return true; + } + + ParsedAttr::Kind AttrKind = ParsedAttr::getParsedKind( + <A->AttrName, nullptr, ParsedAttr::Form::GNU().getSyntax()); + // Sema cannot see LateParsedTypeAttribute's definition, so it hands the node + // back and we record it here for the completion pass to fill in. + BoundsAttributedType *BATy = nullptr; + if (!LTA->Self->Actions.ActOnLateParsedTypeAttr( + AttrKind, LTA->AttrNameLoc, type, pointerNestLevel, &BATy)) + return false; + LTA->TypeToComplete = BATy; + return true; +} + void Parser::CompleteLateParsedTypeAttributes( SmallVectorImpl<LateParsedTypeAttribute *> &LateTypeAttrs) { for (LateParsedTypeAttribute *RawLTA : LateTypeAttrs) { diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp index c86ed6b2ea3f9..2d1fcffcf5e4e 100644 --- a/clang/lib/Parse/Parser.cpp +++ b/clang/lib/Parse/Parser.cpp @@ -592,6 +592,11 @@ void Parser::Initialize() { } Actions.Initialize(); + // Register the callback so Sema can call back into the Parser to handle + // late-parsed type attributes (e.g. counted_by on struct fields), which + // may be processed at any point during parsing via ActOnFields. + Actions.ProcessLateParsedTypeAttrCallback = + &Parser::ProcessLateParsedTypeAttrCallback; // Prime the lexer look-ahead. ConsumeToken(); diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp index 609684523573c..7f7c7ae6e3075 100644 --- a/clang/lib/Sema/SemaType.cpp +++ b/clang/lib/Sema/SemaType.cpp @@ -9126,6 +9126,27 @@ static bool validateBoundsAttrTypeForTypePosition( return true; } +bool Sema::ActOnLateParsedTypeAttr(ParsedAttr::Kind AttrKind, + SourceLocation AttrNameLoc, QualType &type, + unsigned pointerNestLevel, + BoundsAttributedType **BATy) { + BoundsAttrFlags Flags; + if (!validateBoundsAttrTypeForTypePosition(*this, type, AttrKind, AttrNameLoc, + SourceRange(AttrNameLoc), + pointerNestLevel, Flags)) + return false; + + // The argument hasn't been parsed yet, so build the type without it and hand + // the node back for completion. Because enclosing types refer to it by + // pointer, filling the argument in later leaves them untouched — no rebuild + // of the type chain and no TypeLoc re-emission. + auto *CATy = getASTContext().getIncompleteCountAttributedType( + type, Flags.CountInBytes, Flags.OrNull); + type = QualType(CATy, 0); + *BATy = CATy; + 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
