[llvm-branch-commits] [clang] [BoundsSafety][test] Add late-parsed counted_by type-attribute coverage (PR #224561)
Yeoul Na via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Sat Sep 19 11:13:22 PDT 2026
https://github.com/rapidsna updated https://github.com/llvm/llvm-project/pull/224561
>From b11b1745e1f15b3d771c7c78ef2322c7875c0de0 Mon Sep 17 00:00:00 2001
From: Yeoul Na <yeoul_na at apple.com>
Date: Thu, 10 Sep 2026 07:30:15 -0700
Subject: [PATCH 01/10] [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 5becfc9fae152f..6e8a75bdf2bd5f 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 2796ac2929f460..b6641fa538ae24 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 dd63dcc05cd0980d1ab178903516d7a629e47ad2 Mon Sep 17 00:00:00 2001
From: Yeoul Na <yeoul_na at apple.com>
Date: Thu, 10 Sep 2026 07:07:26 -0700
Subject: [PATCH 02/10] [BoundsSafety][NFC] Add the count-refill logic for
late-parsed bounds attributes
Introduce the machinery that fills in a late-parsed bounds attribute's
argument once the enclosing record is complete, without wiring it up yet.
Nothing creates an incomplete CountAttributedType or records one for
completion at this point, so this is inert -- the functions are unused and
behavior is unchanged. Activation follows in the next commit.
---
clang/include/clang/Parse/Parser.h | 15 +++++++++
clang/include/clang/Sema/Sema.h | 7 ++++
clang/lib/AST/Decl.cpp | 8 ++++-
clang/lib/Parse/ParseDecl.cpp | 48 ++++++++++++++++++++++++++
clang/lib/Sema/SemaType.cpp | 54 ++++++++++++++++++++++++++++++
5 files changed, 131 insertions(+), 1 deletion(-)
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index e9bab81b095fd4..ce97ad25bcd16d 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -232,6 +232,16 @@ struct LateParsedAttribute : public LateParsedDeclaration {
/// is replaced with a concrete type (e.g., CountAttributedType).
struct LateParsedTypeAttribute : public LateParsedAttribute {
+ /// The type built for this attribute during type construction, still missing
+ /// the argument that hasn't been parsed yet. Filled in by
+ /// `Parser::ProcessLateParsedTypeAttrCallback` and completed once the
+ /// enclosing scope makes the argument parseable. Null if type construction
+ /// rejected the attribute.
+ ///
+ /// Held as the base class so the parser stays agnostic about which bounds
+ /// attribute this is; Sema dispatches on the concrete kind when completing.
+ BoundsAttributedType *TypeToComplete = nullptr;
+
explicit LateParsedTypeAttribute(Parser *P, IdentifierInfo &Name,
SourceLocation Loc)
: LateParsedAttribute(P, Name, Loc, Kind::Type) {}
@@ -1524,6 +1534,11 @@ class Parser : public CodeCompletionHandler {
void ParseLexedTypeAttribute(LateParsedTypeAttribute &LA,
ParsedAttributes &OutAttrs);
+ /// Complete every late-parsed type attribute queued for the record whose body
+ /// just closed. Consumes and clears \p LateTypeAttrs.
+ void CompleteLateParsedTypeAttributes(
+ SmallVectorImpl<LateParsedTypeAttribute *> &LateTypeAttrs);
+
/// Parse cached tokens for a late-parsed attribute and return the parsed
/// attributes. Shared implementation used by both ParseLexedAttribute and
/// ParseLexedTypeAttribute.
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 6e8a75bdf2bd5f..8533f2823ed141 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -2538,6 +2538,13 @@ class Sema final : public SemaBase {
bool CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes,
bool OrNull);
+ /// Supply the parsed argument of a late-parsed bounds attribute to the type
+ /// built for it by ActOnLateParsedTypeAttr, and run the checks that need the
+ /// owning declaration. \p FD is the field the type belongs to. Returns false
+ /// if the attribute was rejected.
+ bool ActOnLateParsedTypeAttrArgument(BoundsAttributedType *BATy,
+ FieldDecl *FD, Expr *Arg);
+
/// Perform Bounds Safety Semantic checks for assigning to a `__counted_by` or
/// `__counted_by_or_null` pointer type \param LHSTy.
///
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index ffd9bd33c7501c..b145560508e956 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -4922,7 +4922,13 @@ const FieldDecl *FieldDecl::findCountedByField() const {
if (!CAT)
return nullptr;
- const auto *CountDRE = cast<DeclRefExpr>(CAT->getCountExpr());
+ // A late-parsed attribute whose argument was rejected keeps the node with the
+ // raw argument as its count (see Sema::ActOnLateParsedTypeAttrArgument). That
+ // argument may not be a simple declaration reference (e.g. it may be an error
+ // expression or a `sizeof`), in which case it refers to no field.
+ const auto *CountDRE = dyn_cast<DeclRefExpr>(CAT->getCountExpr());
+ if (!CountDRE)
+ return nullptr;
const auto *CountDecl = CountDRE->getDecl();
if (const auto *IFD = dyn_cast<IndirectFieldDecl>(CountDecl))
CountDecl = IFD->getAnonField();
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index 5976f5a7ccdea0..9a49ce16447cc5 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -4896,6 +4896,54 @@ void Parser::ParseLexedTypeAttribute(LateParsedTypeAttribute &LA,
OutAttrs.takeAllAppendingFrom(Attrs);
}
+void Parser::CompleteLateParsedTypeAttributes(
+ SmallVectorImpl<LateParsedTypeAttribute *> &LateTypeAttrs) {
+ for (LateParsedTypeAttribute *LTA : LateTypeAttrs) {
+ // Read these out before parsing, which destroys the attribute. The type is
+ // null if construction rejected the attribute, in which case the diagnostic
+ // has already been emitted and there is nothing to complete.
+ BoundsAttributedType *BATy = LTA->TypeToComplete;
+ // Rejected during construction (already diagnosed); the cached tokens are
+ // self-contained, so there is nothing to drain — just discard it.
+ if (!BATy) {
+ delete LTA;
+ continue;
+ }
+ // The fields were
+ // attached in ParseStructDeclaration as each declarator was completed; more
+ // than one appears when several declarators share a
+ // declaration-specifier-position attribute.
+ SmallVector<Decl *, 2> Fields(LTA->Decls);
+
+ AttributeFactory AF;
+ ParsedAttributes Attrs(AF);
+ ParseLexedTypeAttribute(*LTA, Attrs);
+ delete LTA;
+
+ // An unparseable argument leaves no attribute behind; already diagnosed.
+ if (Attrs.empty())
+ continue;
+ assert(Attrs.size() == 1);
+
+ Expr *Arg = Attrs[0].getArgAsExpr(0);
+ assert(Arg);
+
+ // No field means the attribute never reached a field declarator (for
+ // instance the type was rejected during construction, which unwraps the
+ // node and leaves it unreferenced), so nothing is left to complete.
+ bool Valid = !Fields.empty();
+ for (Decl *FD : Fields)
+ Valid &= Actions.ActOnLateParsedTypeAttrArgument(
+ BATy, cast<FieldDecl>(FD), Arg);
+
+ if (Valid)
+ Attrs[0].setUsedAsTypeAttr();
+ else
+ Attrs[0].setInvalid();
+ }
+ LateTypeAttrs.clear();
+}
+
void LateParsedTypeAttribute::ParseInto(ParsedAttributes &OutAttrs) {
// Delegate to the Parser that created this attribute
Self->ParseLexedTypeAttribute(*this, OutAttrs);
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index b6641fa538ae24..e1701b1feaed2c 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -10091,6 +10091,60 @@ BuildTypeCoupledDecls(Expr *E,
Decls.push_back(TypeCoupledDeclRefInfo(CountDecl, /*IsDref*/ false));
}
+bool Sema::ActOnLateParsedTypeAttrArgument(BoundsAttributedType *BATy,
+ FieldDecl *FD, Expr *Arg) {
+ assert(Arg);
+
+ // Only the counted_by family exists so far.
+ auto *CATy = cast<CountAttributedType>(BATy);
+
+ // A nested counted_by (buried under a pointer or array) was diagnosed and
+ // dropped to its wrapped type while the declarator was built, orphaning this
+ // node -- it is no longer part of the field's type. getAs finds only a
+ // top-level (through-sugar) CountAttributedType, so when it can't find this
+ // node the node was dropped: skip it, leaving the field as-is. This matches
+ // the eager path, which drops the attribute for a nested counted_by.
+ if (FD->getType()->getAs<CountAttributedType>() != CATy)
+ return false;
+
+ // Rejected: complete the node in place with the raw argument and no coupled
+ // decls. The argument isn't a valid count reference, so there are none --
+ // and BuildTypeCoupledDecls would assert on a non-DeclRefExpr. Mark the field
+ // invalid; consumers bail on a non-DeclRefExpr count. Guarded so shared
+ // declarators (`IP __counted_by(n) a, b;`) only complete the node once.
+ auto Reject = [&]() -> bool {
+ if (!CATy->getCountExpr())
+ Context.completeCountAttributedType(CATy, Arg, {});
+ FD->setInvalidDecl();
+ return false;
+ };
+
+ // A failed parse was already diagnosed; skip the checks (they would only add
+ // noise) and recover the node directly.
+ if (Arg->containsErrors())
+ return Reject();
+
+ // Rejected (diagnostic emitted by the check): a bad count expression, or a
+ // valid reference in an invalid position (union member, non-flexible array,
+ // cross-struct count).
+ if (CheckCountedByAttrOnField(FD, Arg, CATy->isCountInBytes(),
+ CATy->isOrNull()))
+ return Reject();
+
+ // Valid: the argument is a simple declaration reference, so it's safe to
+ // derive the coupled decls. Several declarators can share one node when the
+ // attribute was written in declaration-specifier position
+ // (`IP __counted_by(n) a, b;`), so this runs once per field; completion is
+ // idempotent -- the first field supplies the count, the rest only need the
+ // decl-context check above.
+ llvm::SmallVector<TypeCoupledDeclRefInfo, 1> Decls;
+ BuildTypeCoupledDecls(Arg, Decls);
+ if (!CATy->getCountExpr())
+ Context.completeCountAttributedType(CATy, Arg, Decls);
+
+ return true;
+}
+
QualType Sema::BuildCountAttributedArrayOrPointerType(QualType WrappedTy,
Expr *CountExpr,
bool CountInBytes,
>From ce106ecd4911650b5a92b3dfad756151ee5fc936 Mon Sep 17 00:00:00 2001
From: Yeoul Na <yeoul_na at apple.com>
Date: Fri, 18 Sep 2026 09:54:36 -0700
Subject: [PATCH 03/10] Use unique_ptr and remove raw deletes; assertions
instead of bail out
---
clang/lib/Parse/ParseDecl.cpp | 31 ++++++++++---------------------
1 file changed, 10 insertions(+), 21 deletions(-)
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index 9a49ce16447cc5..848390205b983d 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -4898,27 +4898,18 @@ void Parser::ParseLexedTypeAttribute(LateParsedTypeAttribute &LA,
void Parser::CompleteLateParsedTypeAttributes(
SmallVectorImpl<LateParsedTypeAttribute *> &LateTypeAttrs) {
- for (LateParsedTypeAttribute *LTA : LateTypeAttrs) {
- // Read these out before parsing, which destroys the attribute. The type is
- // null if construction rejected the attribute, in which case the diagnostic
- // has already been emitted and there is nothing to complete.
+ for (LateParsedTypeAttribute *RawLTA : LateTypeAttrs) {
+ std::unique_ptr<LateParsedTypeAttribute> LTA(RawLTA);
+
BoundsAttributedType *BATy = LTA->TypeToComplete;
- // Rejected during construction (already diagnosed); the cached tokens are
- // self-contained, so there is nothing to drain — just discard it.
- if (!BATy) {
- delete LTA;
+ if (!BATy)
continue;
- }
- // The fields were
- // attached in ParseStructDeclaration as each declarator was completed; more
- // than one appears when several declarators share a
- // declaration-specifier-position attribute.
- SmallVector<Decl *, 2> Fields(LTA->Decls);
+
+ ArrayRef<Decl *> Fields = LTA->Decls;
AttributeFactory AF;
ParsedAttributes Attrs(AF);
ParseLexedTypeAttribute(*LTA, Attrs);
- delete LTA;
// An unparseable argument leaves no attribute behind; already diagnosed.
if (Attrs.empty())
@@ -4928,13 +4919,11 @@ void Parser::CompleteLateParsedTypeAttributes(
Expr *Arg = Attrs[0].getArgAsExpr(0);
assert(Arg);
- // No field means the attribute never reached a field declarator (for
- // instance the type was rejected during construction, which unwraps the
- // node and leaves it unreferenced), so nothing is left to complete.
- bool Valid = !Fields.empty();
+ bool Valid = true;
+ assert(!Fields.empty());
for (Decl *FD : Fields)
- Valid &= Actions.ActOnLateParsedTypeAttrArgument(
- BATy, cast<FieldDecl>(FD), Arg);
+ Valid &= Actions.ActOnLateParsedTypeAttrArgument(BATy, cast<FieldDecl>(FD),
+ Arg);
if (Valid)
Attrs[0].setUsedAsTypeAttr();
>From 36eefb9ce3555a2b8d860eaf07352ef44d082774 Mon Sep 17 00:00:00 2001
From: Yeoul Na <yeoul_na at apple.com>
Date: Sat, 19 Sep 2026 07:49:07 -0700
Subject: [PATCH 04/10] Record rejected nodes explicitly and skip; trim wordy
comments
---
clang/include/clang/Sema/Sema.h | 15 +++++++++++++++
clang/lib/Parse/ParseDecl.cpp | 2 +-
clang/lib/Sema/SemaType.cpp | 30 +++++-------------------------
3 files changed, 21 insertions(+), 26 deletions(-)
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 8533f2823ed141..8fb985423c4a4f 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -2538,6 +2538,21 @@ class Sema final : public SemaBase {
bool CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes,
bool OrNull);
+ /// Late-parsed bounds types dropped while their declarator was built. The
+ /// attribute has already been diagnosed and its node is no longer part of
+ /// any type, so the completion pass must skip it rather than parse its
+ /// argument and complete it.
+ llvm::SmallPtrSet<const BoundsAttributedType *, 4>
+ RejectedLateParsedBoundsTypes;
+
+ void markLateParsedBoundsTypeRejected(const BoundsAttributedType *BATy) {
+ RejectedLateParsedBoundsTypes.insert(BATy);
+ }
+
+ bool isLateParsedBoundsTypeRejected(const BoundsAttributedType *BATy) const {
+ return RejectedLateParsedBoundsTypes.contains(BATy);
+ }
+
/// Supply the parsed argument of a late-parsed bounds attribute to the type
/// built for it by ActOnLateParsedTypeAttr, and run the checks that need the
/// owning declaration. \p FD is the field the type belongs to. Returns false
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index 848390205b983d..e3b0eda1af6702 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -4902,7 +4902,7 @@ void Parser::CompleteLateParsedTypeAttributes(
std::unique_ptr<LateParsedTypeAttribute> LTA(RawLTA);
BoundsAttributedType *BATy = LTA->TypeToComplete;
- if (!BATy)
+ if (!BATy || Actions.isLateParsedBoundsTypeRejected(BATy))
continue;
ArrayRef<Decl *> Fields = LTA->Decls;
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index e1701b1feaed2c..609684523573cd 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -10098,47 +10098,27 @@ bool Sema::ActOnLateParsedTypeAttrArgument(BoundsAttributedType *BATy,
// Only the counted_by family exists so far.
auto *CATy = cast<CountAttributedType>(BATy);
- // A nested counted_by (buried under a pointer or array) was diagnosed and
- // dropped to its wrapped type while the declarator was built, orphaning this
- // node -- it is no longer part of the field's type. getAs finds only a
- // top-level (through-sugar) CountAttributedType, so when it can't find this
- // node the node was dropped: skip it, leaving the field as-is. This matches
- // the eager path, which drops the attribute for a nested counted_by.
- if (FD->getType()->getAs<CountAttributedType>() != CATy)
- return false;
-
- // Rejected: complete the node in place with the raw argument and no coupled
- // decls. The argument isn't a valid count reference, so there are none --
- // and BuildTypeCoupledDecls would assert on a non-DeclRefExpr. Mark the field
- // invalid; consumers bail on a non-DeclRefExpr count. Guarded so shared
- // declarators (`IP __counted_by(n) a, b;`) only complete the node once.
auto Reject = [&]() -> bool {
+ // Guarded so shared declarators (`IP __counted_by(n) a, b;`) only complete
+ // the node once.
if (!CATy->getCountExpr())
Context.completeCountAttributedType(CATy, Arg, {});
FD->setInvalidDecl();
return false;
};
- // A failed parse was already diagnosed; skip the checks (they would only add
- // noise) and recover the node directly.
if (Arg->containsErrors())
return Reject();
- // Rejected (diagnostic emitted by the check): a bad count expression, or a
- // valid reference in an invalid position (union member, non-flexible array,
- // cross-struct count).
if (CheckCountedByAttrOnField(FD, Arg, CATy->isCountInBytes(),
CATy->isOrNull()))
return Reject();
- // Valid: the argument is a simple declaration reference, so it's safe to
- // derive the coupled decls. Several declarators can share one node when the
- // attribute was written in declaration-specifier position
- // (`IP __counted_by(n) a, b;`), so this runs once per field; completion is
- // idempotent -- the first field supplies the count, the rest only need the
- // decl-context check above.
llvm::SmallVector<TypeCoupledDeclRefInfo, 1> Decls;
BuildTypeCoupledDecls(Arg, Decls);
+ // Several declarators can share one node when the attribute was written in
+ // declaration-specifier position (`IP __counted_by(n) a, b;`), so this runs
+ // once per field
if (!CATy->getCountExpr())
Context.completeCountAttributedType(CATy, Arg, Decls);
>From 59c60604b4cb61fbf5b8e32c1f2d8d98442ed341 Mon Sep 17 00:00:00 2001
From: Yeoul Na <yeoul_na at apple.com>
Date: Sat, 19 Sep 2026 10:49:09 -0700
Subject: [PATCH 05/10] clang format
---
clang/lib/Parse/ParseDecl.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index e3b0eda1af6702..de97611e8bcaef 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -4922,8 +4922,8 @@ void Parser::CompleteLateParsedTypeAttributes(
bool Valid = true;
assert(!Fields.empty());
for (Decl *FD : Fields)
- Valid &= Actions.ActOnLateParsedTypeAttrArgument(BATy, cast<FieldDecl>(FD),
- Arg);
+ Valid &= Actions.ActOnLateParsedTypeAttrArgument(
+ BATy, cast<FieldDecl>(FD), Arg);
if (Valid)
Attrs[0].setUsedAsTypeAttr();
>From 201c99b5658cd699fe31e32bf95fa6f6e685f9a7 Mon Sep 17 00:00:00 2001
From: Yeoul Na <yeoul_na at apple.com>
Date: Thu, 17 Sep 2026 21:12:37 -0700
Subject: [PATCH 06/10] [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 ce97ad25bcd16d..ae7c345ba6ce3e 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 8fb985423c4a4f..aa18627776da8a 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 de97611e8bcaef..0d205df0c2a486 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 c86ed6b2ea3f98..2d1fcffcf5e4e4 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 609684523573cd..7f7c7ae6e3075f 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,
>From 126740d88cf7ee00a957eb5599d47cb874e33bbf Mon Sep 17 00:00:00 2001
From: Yeoul Na <yeoul_na at apple.com>
Date: Thu, 17 Sep 2026 21:38:11 -0700
Subject: [PATCH 07/10] [BoundsSafety][NFC] Thread a late-parsed attribute list
through declarators
A late-parsed type attribute is written in the middle of a declarator, so the
list it lands in has to travel with the declarator pieces until the enclosing
record can supply its argument. Add that storage and plumbing, with nothing
producing or consuming it yet:
- Move CachedTokens and LateParsedAttrList earlier in DeclSpec.h so DeclSpec,
Declarator and DeclaratorChunk can hold one.
- Give DeclSpec, Declarator and DeclaratorChunk a LateParsedAttrList, and let
Declarator::AddTypeInfo carry one onto the chunk it appends.
- Give ParseSpecifierQualifierList and ParseTypeQualifierListOpt an optional
LateParsedAttrList parameter, passed down to ParseDeclarationSpecifiers.
No functional change: the lists stay empty and no caller passes one. The next
commit populates them.
---
clang/include/clang/Parse/Parser.h | 23 +++++---
clang/include/clang/Sema/DeclSpec.h | 87 ++++++++++++++++++-----------
clang/lib/Parse/ParseDecl.cpp | 15 ++++-
3 files changed, 80 insertions(+), 45 deletions(-)
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index ae7c345ba6ce3e..6d0affa3c88225 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -1953,10 +1953,12 @@ class Parser : public CodeCompletionHandler {
DeclSpec &DS, AccessSpecifier AS, DeclSpecContext DSContext,
LateParsedAttrList *LateAttrs = nullptr);
- void ParseSpecifierQualifierList(
- DeclSpec &DS, AccessSpecifier AS = AS_none,
- DeclSpecContext DSC = DeclSpecContext::DSC_normal) {
- ParseSpecifierQualifierList(DS, getImplicitTypenameContext(DSC), AS, DSC);
+ void
+ ParseSpecifierQualifierList(DeclSpec &DS, AccessSpecifier AS = AS_none,
+ DeclSpecContext DSC = DeclSpecContext::DSC_normal,
+ LateParsedAttrList *LateAttrs = nullptr) {
+ ParseSpecifierQualifierList(DS, getImplicitTypenameContext(DSC), AS, DSC,
+ LateAttrs);
}
/// ParseSpecifierQualifierList
@@ -1967,10 +1969,12 @@ class Parser : public CodeCompletionHandler {
/// [GNU] attributes specifier-qualifier-list[opt]
/// \endverbatim
///
- void ParseSpecifierQualifierList(
- DeclSpec &DS, ImplicitTypenameContext AllowImplicitTypename,
- AccessSpecifier AS = AS_none,
- DeclSpecContext DSC = DeclSpecContext::DSC_normal);
+ void
+ ParseSpecifierQualifierList(DeclSpec &DS,
+ ImplicitTypenameContext AllowImplicitTypename,
+ AccessSpecifier AS = AS_none,
+ DeclSpecContext DSC = DeclSpecContext::DSC_normal,
+ LateParsedAttrList *LateAttrs = nullptr);
/// ParseEnumSpecifier
/// \verbatim
@@ -2678,7 +2682,8 @@ class Parser : public CodeCompletionHandler {
void ParseTypeQualifierListOpt(
DeclSpec &DS, unsigned AttrReqs = AR_AllAttributesParsed,
bool AtomicOrPtrauthAllowed = true, bool IdentifierRequired = false,
- llvm::function_ref<void()> CodeCompletionHandler = {});
+ llvm::function_ref<void()> CodeCompletionHandler = {},
+ LateParsedAttrList *LateAttrs = nullptr);
/// ParseDirectDeclarator
/// \verbatim
diff --git a/clang/include/clang/Sema/DeclSpec.h b/clang/include/clang/Sema/DeclSpec.h
index e6dc6831d893f0..9b1f3b244515db 100644
--- a/clang/include/clang/Sema/DeclSpec.h
+++ b/clang/include/clang/Sema/DeclSpec.h
@@ -213,6 +213,34 @@ namespace clang {
unsigned location_size() const { return Builder.getBuffer().second; }
};
+ /// A set of tokens that has been cached for later parsing.
+ typedef SmallVector<Token, 4> CachedTokens;
+
+ // A list of late-parsed attributes. Used by ParseGNUAttributes.
+ class LateParsedAttrList : public SmallVector<LateParsedAttribute *, 2> {
+ public:
+ LateParsedAttrList(bool PSoon = false,
+ bool LateAttrParseExperimentalExtOnly = false,
+ bool LateAttrParseTypeAttrOnly = false)
+ : ParseSoon(PSoon),
+ LateAttrParseExperimentalExtOnly(LateAttrParseExperimentalExtOnly),
+ LateAttrParseTypeAttrOnly(LateAttrParseTypeAttrOnly) {}
+
+ bool parseSoon() const { return ParseSoon; }
+ /// returns true iff the attribute to be parsed should only be late parsed
+ /// if it is annotated with `LateAttrParseExperimentalExt`
+ bool lateAttrParseExperimentalExtOnly() const {
+ return LateAttrParseExperimentalExtOnly;
+ }
+
+ bool lateAttrParseTypeAttrOnly() const { return LateAttrParseTypeAttrOnly; }
+
+ private:
+ bool ParseSoon; // Are we planning to parse these shortly after creation?
+ bool LateAttrParseExperimentalExtOnly;
+ bool LateAttrParseTypeAttrOnly;
+ };
+
/// Captures information about "declaration specifiers".
///
/// "Declaration specifiers" encompasses storage-class-specifiers,
@@ -404,6 +432,9 @@ class DeclSpec {
// attributes.
ParsedAttributes Attrs;
+ // late attributes
+ LateParsedAttrList LateParsedAttrs;
+
// Scope specifier for the type spec, if applicable.
CXXScopeSpec TypeScope;
@@ -480,7 +511,9 @@ class DeclSpec {
FS_virtual_specified(false), FS_noreturn_specified(false),
FriendSpecifiedFirst(false), ConstexprSpecifier(static_cast<unsigned>(
ConstexprSpecKind::Unspecified)),
- Attrs(attrFactory), writtenBS(), ObjCQualifiers(nullptr) {}
+ Attrs(attrFactory), LateParsedAttrs(true, true, true), writtenBS(),
+
+ ObjCQualifiers(nullptr) {}
// storage-class-specifier
SCS getStorageClassSpec() const { return (SCS)StorageClassSpec; }
@@ -880,6 +913,11 @@ class DeclSpec {
ParsedAttributes &getAttributes() { return Attrs; }
const ParsedAttributes &getAttributes() const { return Attrs; }
+ LateParsedAttrList &getLateAttributes() { return LateParsedAttrs; }
+ const LateParsedAttrList &getLateAttributes() const {
+ return LateParsedAttrs;
+ }
+
void takeAttributesAppendingingFrom(ParsedAttributes &attrs) {
Attrs.takeAllAppendingFrom(attrs);
}
@@ -1252,40 +1290,12 @@ class UnqualifiedId {
SourceLocation getEndLoc() const LLVM_READONLY { return EndLocation; }
};
-/// A set of tokens that has been cached for later parsing.
-typedef SmallVector<Token, 4> CachedTokens;
-
-// A list of late-parsed attributes. Used by ParseGNUAttributes.
-class LateParsedAttrList : public SmallVector<LateParsedAttribute *, 2> {
-public:
- LateParsedAttrList(bool PSoon = false,
- bool LateAttrParseExperimentalExtOnly = false,
- bool LateAttrParseTypeAttrOnly = false)
- : ParseSoon(PSoon),
- LateAttrParseExperimentalExtOnly(LateAttrParseExperimentalExtOnly),
- LateAttrParseTypeAttrOnly(LateAttrParseTypeAttrOnly) {}
-
- bool parseSoon() const { return ParseSoon; }
- /// returns true iff the attribute to be parsed should only be late parsed
- /// if it is annotated with `LateAttrParseExperimentalExt`
- bool lateAttrParseExperimentalExtOnly() const {
- return LateAttrParseExperimentalExtOnly;
- }
-
- bool lateAttrParseTypeAttrOnly() const { return LateAttrParseTypeAttrOnly; }
-
-private:
- bool ParseSoon; // Are we planning to parse these shortly after creation?
- bool LateAttrParseExperimentalExtOnly;
- bool LateAttrParseTypeAttrOnly;
-};
-
/// One instance of this struct is used for each type in a
/// declarator that is parsed.
///
/// This is intended to be a small value object.
struct DeclaratorChunk {
- DeclaratorChunk() {};
+ DeclaratorChunk() : LateAttrList(true, true, true) {};
enum {
Pointer, Reference, Array, Function, BlockPointer, MemberPointer, Paren, Pipe
@@ -1303,6 +1313,7 @@ struct DeclaratorChunk {
}
ParsedAttributesView AttrList;
+ LateParsedAttrList LateAttrList;
struct PointerTypeInfo {
/// The type qualifiers: const/volatile/restrict/unaligned/atomic.
@@ -2015,6 +2026,8 @@ class Declarator {
/// corresponding constructor parameter.
const ParsedAttributesView &DeclarationAttrs;
+ LateParsedAttrList LateParsedAttrs;
+
/// The asm label, if specified.
Expr *AsmLabel;
@@ -2080,8 +2093,8 @@ class Declarator {
Redeclaration(false), Extension(false), ObjCIvar(false),
ObjCWeakProperty(false), InlineStorageUsed(false),
HasInitializer(false), Attrs(DS.getAttributePool().getFactory()),
- DeclarationAttrs(DeclarationAttrs), AsmLabel(nullptr),
- TrailingRequiresClause(nullptr),
+ DeclarationAttrs(DeclarationAttrs), LateParsedAttrs(true, true, true),
+ AsmLabel(nullptr), TrailingRequiresClause(nullptr),
InventedTemplateParameterList(nullptr) {
assert(llvm::all_of(DeclarationAttrs,
[](const ParsedAttr &AL) {
@@ -2403,13 +2416,16 @@ class Declarator {
/// This function takes attrs by R-Value reference because it takes ownership
/// of those attributes from the parameter.
void AddTypeInfo(const DeclaratorChunk &TI, ParsedAttributes &&attrs,
- SourceLocation EndLoc) {
+ SourceLocation EndLoc,
+ const LateParsedAttrList &LateAttrs = {}) {
DeclTypeInfo.push_back(TI);
DeclTypeInfo.back().getAttrs().prepend(attrs.begin(), attrs.end());
getAttributePool().takeAllFrom(attrs.getPool());
if (!EndLoc.isInvalid())
SetRangeEnd(EndLoc);
+
+ DeclTypeInfo.back().LateAttrList.append(LateAttrs);
}
/// AddTypeInfo - Add a chunk to this declarator. Also extend the range to
@@ -2739,6 +2755,11 @@ class Declarator {
return DeclarationAttrs;
}
+ LateParsedAttrList &getLateAttributes() { return LateParsedAttrs; }
+ const LateParsedAttrList &getLateAttributes() const {
+ return LateParsedAttrs;
+ }
+
/// hasAttributes - do we contain any attributes?
bool hasAttributes() const {
if (!getAttributes().empty() || !getDeclarationAttributes().empty() ||
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index 0d205df0c2a486..9dbfc7ec8b3931 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -2753,12 +2753,20 @@ Decl *Parser::ParseDeclarationAfterDeclaratorAndAttributes(
void Parser::ParseSpecifierQualifierList(
DeclSpec &DS, ImplicitTypenameContext AllowImplicitTypename,
- AccessSpecifier AS, DeclSpecContext DSC) {
+ AccessSpecifier AS, DeclSpecContext DSC, LateParsedAttrList *LateAttrs) {
ParsedTemplateInfo TemplateInfo;
+
+ if (LateAttrs)
+ assert(!std::any_of(LateAttrs->begin(), LateAttrs->end(),
+ [&](const LateParsedAttribute *LA) {
+ return isa<LateParsedTypeAttribute>(LA);
+ }) &&
+ "Late type attribute carried over");
+
/// specifier-qualifier-list is a subset of declaration-specifiers. Just
/// parse declaration-specifiers and complain about extra stuff.
/// TODO: diagnose attribute-specifiers and alignment-specifiers.
- ParseDeclarationSpecifiers(DS, TemplateInfo, AS, DSC, nullptr,
+ ParseDeclarationSpecifiers(DS, TemplateInfo, AS, DSC, LateAttrs,
AllowImplicitTypename);
// Validate declspec for type-name.
@@ -6285,7 +6293,8 @@ bool Parser::isConstructorDeclarator(bool IsUnqualified, bool DeductionGuide,
void Parser::ParseTypeQualifierListOpt(
DeclSpec &DS, unsigned AttrReqs, bool AtomicOrPtrauthAllowed,
- bool IdentifierRequired, llvm::function_ref<void()> CodeCompletionHandler) {
+ bool IdentifierRequired, llvm::function_ref<void()> CodeCompletionHandler,
+ LateParsedAttrList *LateAttrs) {
if ((AttrReqs & AR_CXX11AttributesParsed) &&
isAllowedCXX11AttributeSpecifier()) {
ParsedAttributes Attrs(AttrFactory);
>From 53a7f75f9afada6400c1e55d089791fbdee06258 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 08/10] [BoundsSafety] Handle the counted_by family as a type
attribute
counted_by / sized_by (and their _or_null variants) were handled in only one
way: a declaration-position attribute went through handleCountedByAttrField,
which validated it and then patched the field afterwards with
FieldDecl::setType. There was no type-position handling at all.
Build the type during type construction instead, from a single handler that
serves both positions:
- Add HandleCountedByAttrOnType and dispatch the counted_by family to it
from processTypeAttrs, going through the shared
validateBoundsAttrTypeForTypePosition leaf.
- Remove handleCountedByAttrField. Its FieldDecl-based type-shape checks in
Sema::CheckCountedByAttrOnField are superseded by
Sema::ValidateBoundsAttrTypeShape, added in the previous commit and now
reached from the type path, and are deleted; no diagnostic is dropped. The
checks that genuinely need the FieldDecl (union member, non-flexible
array, cross-struct count) stay in CheckCountedByAttrOnField and run from
ActOnFields.
- BuildCountAttributedArrayOrPointerType accepts any array type -- the
flexible-array-member check is deferred to CheckCountedByAttrOnField --
and rejects a non-DeclRefExpr count up front, where the diagnostic can
still be attributed to the count expression.
Building the node in type position is also what lets the next commit hand out
an incomplete node and fill its count in later.
Test expectations are updated in the next commit, which completes the change;
this commit on its own leaves them stale.
---
clang/lib/Sema/SemaBoundsSafety.cpp | 112 +---------------------------
clang/lib/Sema/SemaDecl.cpp | 23 +++++-
clang/lib/Sema/SemaDeclAttr.cpp | 44 -----------
clang/lib/Sema/SemaType.cpp | 70 ++++++++++++++++-
4 files changed, 94 insertions(+), 155 deletions(-)
diff --git a/clang/lib/Sema/SemaBoundsSafety.cpp b/clang/lib/Sema/SemaBoundsSafety.cpp
index 2afe0812dcf4fe..d92718b332ce61 100644
--- a/clang/lib/Sema/SemaBoundsSafety.cpp
+++ b/clang/lib/Sema/SemaBoundsSafety.cpp
@@ -173,20 +173,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() &&
@@ -198,98 +185,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();
@@ -304,6 +200,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)) {
@@ -325,9 +222,6 @@ bool Sema::CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes,
<< Kind << CountFD->getSourceRange();
return true;
}
- // Whether CountRD is an anonymous struct is not determined at this
- // point. Thus, an additional diagnostic in case it's not anonymous struct
- // is done later in `Parser::ParseStructDeclaration`.
auto *RD = GetEnclosingNamedOrTopAnonRecord(FD);
auto *CountRD = GetEnclosingNamedOrTopAnonRecord(CountFD);
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index e52f99b4cd98db..8584d5492f68f2 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -17400,7 +17400,7 @@ void Sema::ActOnFinishDelayedAttribute(Scope *S, Decl *D,
// Always attach attributes to the underlying decl.
if (TemplateDecl *TD = dyn_cast<TemplateDecl>(D))
D = TD->getTemplatedDecl();
- ProcessDeclAttributeList(S, D, Attrs);
+ ProcessDeclAttributeList(S, D, Attrs, ProcessDeclAttributeOptions());
ProcessAPINotes(D);
if (CXXMethodDecl *Method = dyn_cast_or_null<CXXMethodDecl>(D))
@@ -20186,6 +20186,27 @@ 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; leaving
+ // it here would flow an invalid CAT downstream. Mirrors the late path
+ // in Sema::ActOnLateParsedTypeAttrArgument.
+ 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 eb4a8c2ab9ae01..533aafa938e661 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -7069,43 +7069,6 @@ static void handleNoPFPAttrField(Sema &S, Decl *D, const ParsedAttr &AL) {
D->addAttr(NoFieldProtectionAttr::Create(S.Context, AL));
}
-static void handleCountedByAttrField(Sema &S, Decl *D, const ParsedAttr &AL) {
- 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))
- return;
-
- QualType CAT = S.BuildCountAttributedArrayOrPointerType(
- FD->getType(), CountExpr, CountInBytes, OrNull);
- FD->setType(CAT);
-}
-
static void handleFunctionReturnThunksAttr(Sema &S, Decl *D,
const ParsedAttr &AL) {
StringRef KindStr;
@@ -8200,13 +8163,6 @@ ProcessDeclAttribute(Sema &S, Decl *D, const ParsedAttr &AL,
handleAvailableOnlyInDefaultEvalMethod(S, D, AL);
break;
- case ParsedAttr::AT_CountedBy:
- case ParsedAttr::AT_CountedByOrNull:
- case ParsedAttr::AT_SizedBy:
- case ParsedAttr::AT_SizedByOrNull:
- handleCountedByAttrField(S, D, AL);
- break;
-
case ParsedAttr::AT_NoFieldProtection:
handleNoPFPAttrField(S, D, AL);
break;
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 7f7c7ae6e3075f..3a8b15b5b4eeee 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -6426,6 +6426,7 @@ GetTypeSourceInfoForDeclarator(TypeProcessingState &State,
break;
}
+ case TypeLoc::CountAttributed:
case TypeLoc::Adjusted:
case TypeLoc::BTFTagAttributed: {
CurrTL = CurrTL.getNextTypeLoc().getUnqualifiedLoc();
@@ -9126,6 +9127,53 @@ static bool validateBoundsAttrTypeForTypePosition(
return true;
}
+static void HandleCountedByAttrOnType(TypeProcessingState &State,
+ QualType &CurType, ParsedAttr &Attr) {
+ Sema &S = State.getSema();
+
+ // This attribute is only supported in C.
+ // FIXME: we should implement checkCommonAttributeFeatures() in SemaAttr.cpp
+ // such that it handles type attributes, and then call that from
+ // processTypeAttrs() instead of one-off checks like this.
+ if (!Attr.diagnoseLangOpts(S)) {
+ Attr.setInvalid();
+ return;
+ }
+
+ auto *CountExpr = Attr.getArgAsExpr(0);
+ if (!CountExpr)
+ return;
+
+ // This is a mechanism to prevent nested count pointer types in the contexts
+ // where late parsing isn't allowed: currently that is any context other than
+ // struct fields. In the context where late parsing is allowed, the level
+ // check will be done once the whole context is constructed.
+ unsigned chunkIndex = State.getCurrentChunkIndex();
+ unsigned pointerNestLevel = 0;
+
+ // Only calculate pointer nest level if we're processing a declarator chunk.
+ // For DeclSpec attributes, the declarator hasn't been constructed yet.
+ if (chunkIndex > 0) {
+ pointerNestLevel = getPointerNestLevel(State, chunkIndex);
+ }
+
+ Sema::BoundsAttrFlags Flags;
+ if (!validateBoundsAttrTypeForTypePosition(S, CurType, Attr.getKind(),
+ Attr.getLoc(), Attr.getRange(),
+ pointerNestLevel, Flags)) {
+ Attr.setInvalid();
+ return;
+ }
+
+ QualType NewType = S.BuildCountAttributedArrayOrPointerType(
+ CurType, CountExpr, Flags.CountInBytes, Flags.OrNull);
+ if (NewType.isNull()) {
+ Attr.setInvalid();
+ return;
+ }
+ CurType = NewType;
+}
+
bool Sema::ActOnLateParsedTypeAttr(ParsedAttr::Kind AttrKind,
SourceLocation AttrNameLoc, QualType &type,
unsigned pointerNestLevel,
@@ -9379,6 +9427,14 @@ static void processTypeAttrs(TypeProcessingState &state, QualType &type,
break;
}
+ case ParsedAttr::AT_CountedBy:
+ case ParsedAttr::AT_CountedByOrNull:
+ case ParsedAttr::AT_SizedBy:
+ case ParsedAttr::AT_SizedByOrNull:
+ HandleCountedByAttrOnType(state, type, attr);
+ attr.setUsedAsTypeAttr();
+ break;
+
MS_TYPE_ATTRS_CASELIST:
if (!handleMSPointerTypeQualifierAttr(state, attr, type))
attr.setUsedAsTypeAttr();
@@ -10150,7 +10206,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 = getCountAttrKind(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);
>From ba8b2326f4cf670744b8f5a4ba1c0efb6c2f651d Mon Sep 17 00:00:00 2001
From: Yeoul Na <yeoul_na at apple.com>
Date: Thu, 10 Sep 2026 07:40:37 -0700
Subject: [PATCH 09/10] [BoundsSafety] Create incomplete counted_by types and
wire up the refill
Activate late parsing for the counted_by family (counted_by / sized_by and
their _or_null variants) in type-attribute position, under
-fexperimental-late-parse-attributes, on top of the type-attribute handling,
the validation helper and the refill machinery added in the previous commits.
When such an attribute is seen during type construction and its argument
can't be resolved yet, build the CountAttributedType immediately with
getIncompleteCountAttributedType and record it against the enclosing record;
its count expression is filled in at the closing brace via the refill logic.
Because enclosing types refer to the node by pointer, completing it in place
leaves the type chain untouched -- no rebuild, no TypeLoc re-emission.
- Sema::ActOnLateParsedTypeAttr builds the incomplete node; the parser
callback stores it on the LateParsedTypeAttribute and records the
attribute in the record currently being parsed.
Parser::CompleteLateParsedTypeAttributes drains that list at the closing
brace; a nested anonymous record hands its pending attributes up to the
enclosing record whose scope makes the argument visible.
- A nested counted_by is diagnosed in the declarator-chunk loop, where the
wrap would otherwise bury the CountAttributedType.
- Free-function parameters have no enclosing record to complete them, so
late parsing is gated to record members; parameters fall back to eager
handling instead of leaving a null-count node to crash on PCH round-trip.
Existing counted_by/sized_by tests are updated to the resolved-in-place AST
and diagnostics, including the expectations left stale by the previous commit.
---
clang/include/clang/AST/TypeBase.h | 2 +-
clang/include/clang/Parse/Parser.h | 11 +
clang/include/clang/Sema/Sema.h | 39 ++--
clang/lib/Parse/ParseDecl.cpp | 203 +++++++++++++++---
clang/lib/Sema/SemaType.cpp | 65 ++++++
.../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 ---
.../attr-counted-by-late-parsed-struct-ptrs.c | 79 +++----
.../Sema/attr-counted-by-or-null-last-field.c | 4 +-
...unted-by-or-null-late-parsed-struct-ptrs.c | 64 ++----
...ruct-ptrs-completable-incomplete-pointee.c | 37 ++--
.../attr-counted-by-or-null-struct-ptrs.c | 14 +-
...ruct-ptrs-completable-incomplete-pointee.c | 32 ++-
clang/test/Sema/attr-counted-by-struct-ptrs.c | 18 +-
.../attr-sized-by-late-parsed-struct-ptrs.c | 52 +----
...sized-by-or-null-late-parsed-struct-ptrs.c | 53 +----
.../Sema/attr-sized-by-or-null-struct-ptrs.c | 14 +-
clang/test/Sema/attr-sized-by-struct-ptrs.c | 12 +-
20 files changed, 409 insertions(+), 387 deletions(-)
diff --git a/clang/include/clang/AST/TypeBase.h b/clang/include/clang/AST/TypeBase.h
index 28f102fdaf534c..454829ff58b43b 100644
--- a/clang/include/clang/AST/TypeBase.h
+++ b/clang/include/clang/AST/TypeBase.h
@@ -3451,7 +3451,7 @@ class BoundsAttributedType : public Type, public llvm::FoldingSetNode {
QualType WrappedTy;
protected:
- ArrayRef<TypeCoupledDeclRefInfo> Decls; // stored in trailing objects
+ ArrayRef<TypeCoupledDeclRefInfo> Decls; // allocated in the ASTContext
BoundsAttributedType(TypeClass TC, QualType Wrapped, QualType Canon);
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index 6d0affa3c88225..fdf0414e46aaaf 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -292,6 +292,8 @@ class Parser : public CodeCompletionHandler {
friend class PoisonSEHIdentifiersRAIIObject;
friend class ParenBraceBracketBalancer;
friend class BalancedDelimiterTracker;
+ friend struct LateParsedAttribute;
+ friend struct LateParsedTypeAttribute;
Parser(Preprocessor &PP, Sema &Actions, bool SkipFunctionBodies);
~Parser() override;
@@ -2246,6 +2248,8 @@ class Parser : public CodeCompletionHandler {
ParsedAttributes Attrs(AttrFactory);
ParseGNUAttributes(Attrs, LateAttrs, &D);
D.takeAttributesAppending(Attrs);
+ if (LateAttrs)
+ Parser::TakeTypeAttrsAppendingFrom(D.getLateAttributes(), *LateAttrs);
}
}
@@ -8172,6 +8176,13 @@ class Parser : public CodeCompletionHandler {
QualType &type,
unsigned pointerNestLevel);
+ /// The late-parsed type attributes of the record currently being parsed, so a
+ /// nested anonymous record can hand its unresolved attributes to the
+ /// enclosing record whose scope makes their arguments visible. Null outside a
+ /// record body.
+ SmallVectorImpl<LateParsedTypeAttribute *> *CurRecordLateParsedTypeAttrs =
+ nullptr;
+
/// 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 aa18627776da8a..b641ed85ad41f2 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -142,6 +142,8 @@ class InitializationKind;
class InitializationSequence;
class InitializedEntity;
enum class LangAS : unsigned int;
+struct LateParsedAttribute;
+struct LateParsedTypeAttribute;
class LocalInstantiationScope;
class LookupResult;
class MangleNumberingContext;
@@ -2538,22 +2540,27 @@ class Sema final : public SemaBase {
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.
- ///
- /// \param FD The FieldDecl to apply the attribute to
- /// \param E The count expression on the attribute
- /// \param CountInBytes If true the attribute is from the "sized_by" family of
- /// attributes. If the false the attribute is from
- /// "counted_by" family of attributes.
- /// \param OrNull If true the attribute is from the "_or_null" suffixed family
- /// of attributes. If false the attribute does not have the
- /// suffix.
- ///
- /// Together \p CountInBytes and \p OrNull decide the attribute variant. E.g.
- /// \p CountInBytes and \p OrNull both being true indicates the
- /// `counted_by_or_null` attribute.
+ /// Perform semantic validation on a FieldDecl with a "counted_by" family
+ /// attribute. This is called after the attribute has been attached to the
+ /// field's type (as a CountAttributedType) to validate the attribute is
+ /// correctly applied.
+ ///
+ /// This performs declaration-level checks that require the FieldDecl to
+ /// exist, complementing the type-level checks performed in
+ /// HandleCountedByAttrOnType during type processing. Specifically, this
+ /// validates:
+ /// - Field is not in a union
+ /// - For array fields, the field is a flexible array member
+ /// - Count expression is an integer type (not bool)
+ /// - Count expression references a field in the same struct
+ /// - Count field is not in a union
+ ///
+ /// \param FD The FieldDecl with the attribute
+ /// \param E The count expression from the attribute
+ /// \param CountInBytes If true the attribute is from the "sized_by" family.
+ /// If false the attribute is from the "counted_by"
+ /// family.
+ /// \param OrNull If true the attribute has the "_or_null" suffix.
///
/// \returns false iff semantically valid.
bool CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes,
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index 9dbfc7ec8b3931..d0c4f4d17b1eb1 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -34,6 +34,7 @@
#include "llvm/ADT/ScopeExit.h"
#include "llvm/ADT/SmallSet.h"
#include "llvm/ADT/StringSwitch.h"
+#include "llvm/Support/SaveAndRestore.h"
#include <optional>
using namespace clang;
@@ -118,6 +119,23 @@ static bool IsAttributeArgsParsedInFunctionScope(const IdentifierInfo &II) {
#undef CLANG_ATTR_PARSE_ARGS_IN_FUNCTION_SCOPE_LIST
}
+/// returns true iff the attribute appertains to a type (a TYPE_ATTR or
+/// DECL_OR_TYPE_ATTR in `Attr.td`).
+static bool IsAttributeTypeAttr(ParsedAttr::Kind Kind) {
+ switch (Kind) {
+#define ATTR(NAME)
+#define DECL_OR_TYPE_ATTR(NAME) case ParsedAttr::AT_##NAME:
+#define TYPE_ATTR(NAME) case ParsedAttr::AT_##NAME:
+#include "clang/Basic/AttrList.inc"
+ return true;
+ default:
+ return false;
+#undef DECL_OR_TYPE_ATTR
+#undef TYPE_ATTR
+#undef ATTR
+ }
+}
+
/// Check if the a start and end source location expand to the same macro.
static bool FindLocsWithCommonFileID(Preprocessor &PP, SourceLocation StartLoc,
SourceLocation EndLoc) {
@@ -167,6 +185,9 @@ bool Parser::ParseSingleGNUAttribute(ParsedAttributes &Attrs,
return false;
}
+ ParsedAttr::Kind AttrKind = ParsedAttr::getParsedKind(
+ AttrName, nullptr, ParsedAttr::Form::GNU().getSyntax());
+
bool LateParse = false;
if (!LateAttrs)
LateParse = false;
@@ -175,7 +196,9 @@ bool Parser::ParseSingleGNUAttribute(ParsedAttributes &Attrs,
// parsed for `LateAttrParseExperimentalExt` attributes. This will
// only be late parsed if the experimental language option is enabled.
LateParse = getLangOpts().ExperimentalLateParseAttributes &&
- IsAttributeLateParsedExperimentalExt(*AttrName);
+ IsAttributeLateParsedExperimentalExt(*AttrName) &&
+ (IsAttributeTypeAttr(AttrKind) ||
+ !LateAttrs->lateAttrParseTypeAttrOnly());
} else {
// The caller did not restrict late parsing to only
// `LateAttrParseExperimentalExt` attributes so late parse
@@ -193,10 +216,24 @@ bool Parser::ParseSingleGNUAttribute(ParsedAttributes &Attrs,
}
// Handle attributes with arguments that require late parsing.
- LateParsedAttribute *LA =
- new LateParsedAttribute(this, *AttrName, AttrNameLoc);
+ // Late parsing for type attributes isn't properly supported in C++ yet.
+ LateParsedAttribute *LA = nullptr;
+ if (IsAttributeTypeAttr(AttrKind) && !getLangOpts().CPlusPlus)
+ LA = new LateParsedTypeAttribute(this, *AttrName, AttrNameLoc);
+ else
+ LA = new LateParsedAttribute(this, *AttrName, AttrNameLoc);
+
LateAttrs->push_back(LA);
+ // Record type attributes against the record currently being parsed, whose
+ // closing brace is when their arguments become resolvable. `LateAttrs` can't
+ // serve here: for a declarator-position attribute it is a transient local
+ // that is drained into a DeclaratorChunk, and for a decl-spec-position one
+ // TakeTypeAttrsAppendingFrom moves the entry into the DeclSpec.
+ if (auto *LTA = dyn_cast<LateParsedTypeAttribute>(LA);
+ LTA && CurRecordLateParsedTypeAttrs)
+ CurRecordLateParsedTypeAttrs->push_back(LTA);
+
// Attributes in a class are parsed at the end of the class, along
// with other late-parsed declarations.
if (!ClassStack.empty() && !LateAttrs->parseSoon())
@@ -3177,10 +3214,11 @@ void Parser::DistributeCLateParsedAttrs(Decl *Dcl,
if (!LateAttrs)
return;
+ // Attach `Decl *` to each `LateParsedAttribute *`.
if (Dcl) {
- for (auto *LateAttr : *LateAttrs) {
- if (LateAttr->Decls.empty())
- LateAttr->addDecl(Dcl);
+ for (auto *LA : *LateAttrs) {
+ if (LA->Decls.empty())
+ LA->addDecl(Dcl);
}
}
}
@@ -3253,12 +3291,6 @@ void Parser::ParseBoundsAttribute(IdentifierInfo &AttrName,
ArgExprs.push_back(ArgExpr.get());
Parens.consumeClose();
- ASTContext &Ctx = Actions.getASTContext();
-
- ArgExprs.push_back(IntegerLiteral::Create(
- Ctx, llvm::APInt(Ctx.getTypeSize(Ctx.getSizeType()), 0),
- Ctx.getSizeType(), SourceLocation()));
-
Attrs.addNew(&AttrName, SourceRange(AttrNameLoc, Parens.getCloseLocation()),
AttributeScopeInfo(), ArgExprs.data(), ArgExprs.size(), Form);
}
@@ -3498,6 +3530,10 @@ void Parser::ParseDeclarationSpecifiers(
DS.takeAttributesAppendingingFrom(attrs);
}
+ if (LateAttrs) {
+ Parser::TakeTypeAttrsAppendingFrom(DS.getLateAttributes(), *LateAttrs);
+ }
+
// If this is not a declaration specifier token, we're done reading decl
// specifiers. First verify that DeclSpec's are consistent.
DS.Finish(Actions, Policy);
@@ -4030,7 +4066,6 @@ void Parser::ParseDeclarationSpecifiers(
case tok::kw___declspec:
ParseAttributes(PAKM_GNU | PAKM_Declspec, DS.getAttributes(), LateAttrs);
continue;
-
// Microsoft single token adornments.
case tok::kw___forceinline: {
isInvalid = DS.setFunctionSpecForceInline(Loc, PrevSpec, DiagID);
@@ -4782,8 +4817,20 @@ void Parser::ParseStructDeclaration(
ParsedAttributes Attrs(AttrFactory);
MaybeParseCXX11Attributes(Attrs);
+ // Late-parsed type attributes written in declaration-specifier position (e.g.
+ // `IP __counted_by(n) a, b;` where `IP` is a pointer typedef) belong to every
+ // declarator in this declaration, so remember where this declaration's
+ // entries start before the specifier list is parsed. Indices, not iterators:
+ // the side list is a SmallVector and only ever grows within a record body.
+ unsigned DeclSpecMark =
+ CurRecordLateParsedTypeAttrs ? CurRecordLateParsedTypeAttrs->size() : 0;
+
// Parse the common specifier-qualifiers-list piece.
- ParseSpecifierQualifierList(DS);
+ ParseSpecifierQualifierList(DS, AS_none, DeclSpecContext::DSC_normal,
+ LateFieldAttrs);
+
+ unsigned AfterDeclSpecMark =
+ CurRecordLateParsedTypeAttrs ? CurRecordLateParsedTypeAttrs->size() : 0;
// If there are no declarators, this is a free-standing declaration
// specifier. Let the actions module cope with it.
@@ -4819,6 +4866,8 @@ void Parser::ParseStructDeclaration(
/// struct-declarator: declarator
/// struct-declarator: declarator[opt] ':' constant-expression
+ unsigned DeclMark =
+ CurRecordLateParsedTypeAttrs ? CurRecordLateParsedTypeAttrs->size() : 0;
if (Tok.isNot(tok::colon)) {
// Don't parse FOO:BAR as if it were a typo for FOO::BAR.
ColonProtectionRAIIObject X(*this);
@@ -4847,6 +4896,31 @@ void Parser::ParseStructDeclaration(
if (Field)
DistributeCLateParsedAttrs(Field, LateFieldAttrs);
+ // Record the field each pending late-parsed type attribute belongs to, in
+ // the base class's coupled-decl list. The callback above ran
+ // GetTypeForDeclarator, so the attribute's type node exists by now; pairing
+ // it with the field here means the completion pass at the closing brace
+ // needs no search -- which matters because a bounds type may sit nested
+ // inside the field's type, where it cannot be recovered by inspecting the
+ // field's top-level type.
+ //
+ // Two ranges apply: attributes from the shared declaration-specifier (every
+ // declarator in this declaration gets appended), and those from this
+ // declarator alone.
+ if (auto *FD = dyn_cast_if_present<FieldDecl>(Field);
+ FD && CurRecordLateParsedTypeAttrs) {
+ unsigned Size = CurRecordLateParsedTypeAttrs->size();
+ assert(Size >= DeclMark && DeclMark >= AfterDeclSpecMark &&
+ AfterDeclSpecMark >= DeclSpecMark &&
+ "late-parsed type attribute list must only grow");
+ auto Attach = [&](unsigned First, unsigned Last) {
+ for (unsigned I = First; I != Last; ++I)
+ (*CurRecordLateParsedTypeAttrs)[I]->addDecl(FD);
+ };
+ Attach(DeclSpecMark, AfterDeclSpecMark);
+ Attach(DeclMark, Size);
+ }
+
// If we don't have a comma, it is either the end of the list (a ';')
// or an error, bail out.
if (!TryConsumeToken(tok::comma, CommaLoc))
@@ -4897,9 +4971,6 @@ ParsedAttributes Parser::ParseLexedAttributeTokens(LateParsedAttribute &LPA) {
void Parser::ParseLexedTypeAttribute(LateParsedTypeAttribute &LA,
ParsedAttributes &OutAttrs) {
- assert(LA.Decls.size() <= 1 &&
- "late field attribute expects to have at most one declaration.");
-
ParsedAttributes Attrs = ParseLexedAttributeTokens(LA);
OutAttrs.takeAllAppendingFrom(Attrs);
}
@@ -5009,6 +5080,14 @@ void Parser::ParseStructUnionBody(SourceLocation RecordLoc,
LateParsedAttrList LateFieldAttrs(/*PSoon=*/true,
/*LateAttrParseExperimentalExtOnly=*/true);
+ // Pending late-parsed type attributes for this record, populated as its
+ // fields are parsed and drained at the closing brace. Exposed to nested
+ // bodies so an anonymous nested record can hand its own up to us;
+ // `Enclosing.get()` is our caller's list, or null for the outermost record.
+ SmallVector<LateParsedTypeAttribute *, 2> LateTypeAttrs;
+ llvm::SaveAndRestore<SmallVectorImpl<LateParsedTypeAttribute *> *> Enclosing(
+ CurRecordLateParsedTypeAttrs, &LateTypeAttrs);
+
// While we still have something to read, read the declarations in the struct.
while (!tryParseMisplacedModuleImport() && Tok.isNot(tok::r_brace) &&
Tok.isNot(tok::eof)) {
@@ -5115,15 +5194,48 @@ void Parser::ParseStructUnionBody(SourceLocation RecordLoc,
ParsedAttributes attrs(AttrFactory);
// If attributes exist after struct contents, parse them.
MaybeParseGNUAttributes(attrs, &LateFieldAttrs);
-
SmallVector<Decl *, 32> FieldDecls(TagDecl->fields());
Actions.ActOnFields(getCurScope(), RecordLoc, TagDecl, FieldDecls,
T.getOpenLocation(), T.getCloseLocation(), attrs);
// Late parse field attributes if necessary.
+ //
+ // Late-parsed type attributes are owned by CompleteLateParsedTypeAttributes
+ // via the record's side list, which parses their tokens and deletes them.
+ // They are only in this generic list to be routed to type construction; if
+ // any remain (e.g. a type attribute that wasn't moved into a DeclSpec /
+ // DeclaratorChunk), drop them here so ParseLexedAttributeList doesn't parse
+ // and free them a second time.
+ llvm::erase_if(LateFieldAttrs, [](LateParsedAttribute *LA) {
+ return isa<LateParsedTypeAttribute>(LA);
+ });
ParseLexedAttributeList(LateFieldAttrs, /*D=*/nullptr, /*EnterScope=*/false,
/*OnDefinition=*/false);
+
+ // Resolve late-parsed type attributes while this record's fields are still in
+ // scope. A truly anonymous record can't do that yet — its count may live in
+ // the enclosing record and only becomes visible once its members are
+ // flattened in — so it hands its pending attributes up instead.
+ //
+ // `isAnonymousStructOrUnion()` isn't set until the enclosing context sees
+ // whether a declarator follows, which happens after we return. Determine it
+ // the way the parser can: no tag name and no declarator after the body. Any
+ // attribute-specifiers between `}` and the `;`/declarator are skipped with a
+ // reverting tentative parse, so a trailing `[[...]]` / `__attribute__` etc.
+ // doesn't defeat the check.
+ if (getLangOpts().ExperimentalLateParseAttributes && !LateTypeAttrs.empty()) {
+ bool IsAnonymous = false;
+ if (!TagDecl->getIdentifier()) {
+ TentativeParsingAction TPA(*this);
+ IsAnonymous = TrySkipAttributes() && Tok.is(tok::semi);
+ TPA.Revert();
+ }
+ if (IsAnonymous && Enclosing.get())
+ llvm::append_range(*Enclosing.get(), LateTypeAttrs);
+ else
+ CompleteLateParsedTypeAttributes(LateTypeAttrs);
+ }
StructScope.Exit();
Actions.ActOnTagFinishDefinition(getCurScope(), TagDecl, T.getRange());
}
@@ -6456,7 +6568,9 @@ void Parser::ParseTypeQualifierListOpt(
// recovery is graceful.
if (AttrReqs & AR_GNUAttributesParsed ||
AttrReqs & AR_GNUAttributesParsedAndRejected) {
- ParseGNUAttributes(DS.getAttributes());
+
+ // FIXME: Late parse only when some flag is set.
+ ParseGNUAttributes(DS.getAttributes(), LateAttrs);
continue; // do *not* consume the next token!
}
// otherwise, FALL THROUGH!
@@ -6616,6 +6730,8 @@ void Parser::ParseDeclaratorInternal(Declarator &D,
DeclSpec DS(AttrFactory);
ParseTypeQualifierListOpt(DS);
+ assert(DS.getLateAttributes().empty());
+
D.AddTypeInfo(
DeclaratorChunk::getPipe(DS.getTypeQualifiers(), DS.getPipeLoc()),
std::move(DS.getAttributes()), SourceLocation());
@@ -6643,26 +6759,49 @@ void Parser::ParseDeclaratorInternal(Declarator &D,
((D.getContext() != DeclaratorContext::CXXNew)
? AR_GNUAttributesParsed
: AR_GNUAttributesParsedAndRejected);
+
+ // Late-parsed type attributes apply to members and function parameters,
+ // not variables. Completion is driven by the enclosing record
+ // (CompleteLateParsedTypeAttributes), so only late-parse when there is one:
+ // a free-function prototype (e.g. `void f(int *__counted_by(n), int n)`)
+ // has no record to complete into, and late-parsing there would leave a
+ // CountAttributedType with a null count in the AST. Such parameters fall
+ // back to eager handling instead. A function-pointer parameter inside a
+ // struct field is still late-parsed, since that record completes it.
+ bool LateParsingContext =
+ (D.getContext() == DeclaratorContext::Member ||
+ D.getContext() == DeclaratorContext::Prototype) &&
+ CurRecordLateParsedTypeAttrs != nullptr;
+
+ // No guard on ExperimentalLateParseAttributes is needed here;
+ // DS.getLateAttributes() already initializes with
+ // LateAttrParseExperimentalExtOnly.
+ LateParsedAttrList *LateAttrs =
+ LateParsingContext ? &DS.getLateAttributes() : nullptr;
+
ParseTypeQualifierListOpt(DS, Reqs, /*AtomicOrPtrauthAllowed=*/true,
- !D.mayOmitIdentifier());
+ !D.mayOmitIdentifier(), {}, LateAttrs);
D.ExtendWithDeclSpec(DS);
// Recursively parse the declarator.
Actions.runWithSufficientStackSpace(
D.getBeginLoc(), [&] { ParseDeclaratorInternal(D, DirectDeclParser); });
- if (Kind == tok::star)
+ if (Kind == tok::star) {
// Remember that we parsed a pointer type, and remember the type-quals.
D.AddTypeInfo(DeclaratorChunk::getPointer(
DS.getTypeQualifiers(), Loc, DS.getConstSpecLoc(),
DS.getVolatileSpecLoc(), DS.getRestrictSpecLoc(),
DS.getAtomicSpecLoc(), DS.getUnalignedSpecLoc(),
DS.getOverflowBehaviorLoc(), DS.isWrapSpecified()),
- std::move(DS.getAttributes()), SourceLocation());
- else
+ std::move(DS.getAttributes()), SourceLocation(),
+ std::move(DS.getLateAttributes()));
+ } else {
+ assert(DS.getLateAttributes().empty());
// Remember that we parsed a Block type, and remember the type-quals.
D.AddTypeInfo(
DeclaratorChunk::getBlockPointer(DS.getTypeQualifiers(), Loc),
std::move(DS.getAttributes()), SourceLocation());
+ }
} else {
// Is a reference
DeclSpec DS(AttrFactory);
@@ -6713,6 +6852,8 @@ void Parser::ParseDeclaratorInternal(Declarator &D,
}
}
+ assert(DS.getLateAttributes().empty());
+
// Remember that we parsed a reference type.
D.AddTypeInfo(DeclaratorChunk::getReference(DS.getTypeQualifiers(), Loc,
Kind == tok::amp),
@@ -7226,9 +7367,18 @@ void Parser::ParseParenDeclarator(Declarator &D) {
// sort of paren this is.
//
ParsedAttributes attrs(AttrFactory);
+ LateParsedAttrList LateAttrs(true, true, true);
bool RequiresArg = false;
if (Tok.is(tok::kw___attribute)) {
- ParseGNUAttributes(attrs);
+ // Only late-parse type attributes when there is an enclosing record to
+ // complete the CountAttributedType (see ParseDeclaratorInternal). A
+ // grouping paren at file scope, e.g. `IP (__counted_by(n) x)` where `IP` is
+ // a pointer typedef, has no record to complete into, so late-parsing there
+ // would leave a CountAttributedType with a null count in the AST; parse
+ // eagerly instead.
+ LateParsedAttrList *LA =
+ CurRecordLateParsedTypeAttrs ? &LateAttrs : nullptr;
+ ParseGNUAttributes(attrs, LA);
// We require that the argument list (if this is a non-grouping paren) be
// present even if the attribute list was empty.
@@ -7283,7 +7433,7 @@ void Parser::ParseParenDeclarator(Declarator &D) {
T.consumeClose();
D.AddTypeInfo(
DeclaratorChunk::getParen(T.getOpenLocation(), T.getCloseLocation()),
- std::move(attrs), T.getCloseLocation());
+ std::move(attrs), T.getCloseLocation(), LateAttrs);
D.setGroupingParens(hadGroupingParens);
@@ -7294,6 +7444,9 @@ void Parser::ParseParenDeclarator(Declarator &D) {
return;
}
+ assert(LateAttrs.empty() &&
+ "Late parsed type attribute on FirstParamAttr is dropped");
+
// Okay, if this wasn't a grouping paren, it must be the start of a function
// argument list. Recognize that this declarator will never have an
// identifier (and remember where it would have been), then call into
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 3a8b15b5b4eeee..6834e731744364 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -409,6 +409,14 @@ processTypeAttrs(TypeProcessingState &state, QualType &type,
TypeAttrLocation TAL, const ParsedAttributesView &attrs,
CUDAFunctionTarget CFT = CUDAFunctionTarget::HostDevice);
+static bool processLateTypeAttrs(TypeProcessingState &state, QualType &type,
+ const LateParsedAttrList &LateAttrs,
+ unsigned chunkIndex = 0);
+
+static void
+BuildTypeCoupledDecls(Expr *E,
+ llvm::SmallVectorImpl<TypeCoupledDeclRefInfo> &Decls);
+
static bool handleFunctionTypeAttr(TypeProcessingState &state, ParsedAttr &attr,
QualType &type, CUDAFunctionTarget CFT);
@@ -1510,6 +1518,9 @@ static QualType ConvertDeclSpecToType(TypeProcessingState &state) {
// are never distributed.
processTypeAttrs(state, Result, TAL_DeclSpec, SlidingAttrs);
processTypeAttrs(state, Result, TAL_DeclSpec, DS.getAttributes());
+
+ // Process the late attributes that appeared after the type name
+ processLateTypeAttrs(state, Result, DS.getLateAttributes());
}
// Apply const/volatile/restrict qualifiers to T.
@@ -4720,6 +4731,32 @@ static TypeSourceInfo *GetFullTypeForDeclarator(TypeProcessingState &state,
state.setCurrentChunkIndex(chunkIndex);
DeclaratorChunk &DeclType = D.getTypeObject(chunkIndex);
IsQualifiedFunction &= DeclType.Kind == DeclaratorChunk::Paren;
+
+ // A counted_by-family attribute has to end up at the outermost level of the
+ // declared type. `int *__counted_by(n) *p` would bury the
+ // CountAttributedType under another pointer, where the bounds can't be
+ // maintained, so diagnose as soon as a chunk is about to wrap one. Only
+ // reachable for late-parsed attributes, since the eager path applies the
+ // attribute after the declarator is built.
+ if (DeclType.Kind == DeclaratorChunk::Pointer ||
+ DeclType.Kind == DeclaratorChunk::Array) {
+ if (const auto *CATy = T->getAs<CountAttributedType>()) {
+ // A counted_by-family attribute buried under another pointer or array
+ // can't maintain its bounds. Diagnose it and drop it to its wrapped
+ // type -- matching the eager path, which drops the attribute rather
+ // than applying it. Because this fires just before the enclosing chunk
+ // wraps the node, replacing T here needs no enclosing-type rebuild.
+ //
+ // Record the rejection so the completion pass skips this node without
+ // parsing its argument, which would otherwise diagnose the argument of
+ // an attribute that has already been rejected.
+ S.Diag(DeclType.Loc, diag::err_counted_by_on_nested_pointer)
+ << CATy->getKind();
+ S.markLateParsedBoundsTypeRejected(CATy);
+ T = CATy->desugar();
+ }
+ }
+
switch (DeclType.Kind) {
case DeclaratorChunk::Paren:
if (i == 0)
@@ -5487,6 +5524,14 @@ static TypeSourceInfo *GetFullTypeForDeclarator(TypeProcessingState &state,
processTypeAttrs(state, T, TAL_DeclChunk, DeclType.getAttrs(),
S.CUDA().IdentifyTarget(D.getAttributes()));
+ // The pointer-nest-level check for late-parsed attributes is intentionally
+ // deferred: at this point the enclosing chunks have not all been applied,
+ // so it is done once the whole declarator is built, when a Pointer/Array
+ // chunk is seen wrapping a CountAttributedType (see the
+ // err_counted_by_on_nested_pointer diagnostic above). processLateTypeAttrs
+ // therefore passes pointerNestLevel == 0 here.
+ processLateTypeAttrs(state, T, DeclType.LateAttrList, chunkIndex);
+
if (DeclType.Kind != DeclaratorChunk::Paren) {
if (ExpectNoDerefChunk && !IsNoDerefableChunk(DeclType))
S.Diag(DeclType.Loc, diag::warn_noderef_on_non_pointer_or_array);
@@ -5667,6 +5712,8 @@ static TypeSourceInfo *GetFullTypeForDeclarator(TypeProcessingState &state,
processTypeAttrs(state, T, TAL_DeclName, NonSlidingAttrs);
processTypeAttrs(state, T, TAL_DeclName, D.getAttributes());
+ processLateTypeAttrs(state, T, D.getLateAttributes());
+
// Diagnose any ignored type attributes.
state.diagnoseIgnoredTypeAttrs(T);
@@ -9194,6 +9241,24 @@ bool Sema::ActOnLateParsedTypeAttr(ParsedAttr::Kind AttrKind,
*BATy = CATy;
return true;
}
+static bool processLateTypeAttrs(TypeProcessingState &state, QualType &type,
+ const LateParsedAttrList &LateAttrs,
+ unsigned chunkIndex) {
+
+ if (LateAttrs.empty())
+ return true;
+
+ Sema &S = state.getSema();
+ unsigned pointerNestLevel = 0;
+
+ assert(S.ProcessLateParsedTypeAttrCallback);
+
+ for (auto *LA : LateAttrs)
+ if (!S.ProcessLateParsedTypeAttrCallback(LA, type, pointerNestLevel))
+ return false;
+
+ return true;
+}
static void processTypeAttrs(TypeProcessingState &state, QualType &type,
TypeAttrLocation TAL,
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-late-parsed-struct-ptrs.c b/clang/test/Sema/attr-counted-by-late-parsed-struct-ptrs.c
index 443ccbbae66dbb..554bcfbd8d5c71 100644
--- a/clang/test/Sema/attr-counted-by-late-parsed-struct-ptrs.c
+++ b/clang/test/Sema/attr-counted-by-late-parsed-struct-ptrs.c
@@ -29,9 +29,8 @@ struct on_member_pointer_const_incomplete_ty {
};
struct on_member_pointer_void_ty {
- // expected-warning at +2{{'counted_by' on a pointer to void is a GNU extension, treated as 'sized_by'}}
- // expected-note at +1{{use '__sized_by' to suppress this warning}}
- void* buf __counted_by(count);
+ // expected-warning at +1{{'counted_by' on a pointer to void is a GNU extension, treated as 'sized_by'}}
+ void* buf __counted_by(count); // expected-note{{use '__sized_by' to suppress this warning}}
int count;
};
@@ -88,9 +87,7 @@ struct on_member_pointer_struct_with_annotated_vla {
};
struct on_pointer_anon_buf {
- // TODO: Support referring to parent scope
struct {
- // expected-error at +1{{use of undeclared identifier 'count'}}
struct size_known *buf __counted_by(count);
};
int count;
@@ -106,131 +103,113 @@ 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 and is **not** late parsed resulting in the `count`
-// field being unavailable.
struct on_member_pointer_complete_ty_ty_pos {
- // TODO: Allow this
- // expected-error at +1{{use of undeclared identifier 'count'}}
struct size_known *__counted_by(count) buf;
int count;
};
struct on_member_pointer_incomplete_ty_ty_pos {
- // TODO: Allow this
- // expected-error at +1{{use of undeclared identifier 'count'}}
struct size_unknown * __counted_by(count) buf;
int count;
};
struct on_member_pointer_const_incomplete_ty_ty_pos {
- // TODO: Allow this
- // expected-error at +1{{use of undeclared identifier 'count'}}
const struct size_unknown * __counted_by(count) buf;
int count;
};
struct on_member_pointer_void_ty_ty_pos {
- // TODO: This should fail because the attribute is
- // on a pointer with the pointee being an incomplete type.
- // expected-error at +1{{use of undeclared identifier 'count'}}
- void *__counted_by(count) buf;
+ // expected-warning at +1{{'counted_by' on a pointer to void is a GNU extension, treated as 'sized_by'}}
+ void *__counted_by(count) buf; // expected-note{{use '__sized_by' to suppress this warning}}
int count;
};
// -
struct on_member_pointer_fn_ptr_ty_pos {
- // TODO: buffer of `count` function pointers should be allowed
- // but fails because this isn't late parsed.
- // expected-error at +1{{use of undeclared identifier 'count'}}
void (** __counted_by(count) fn_ptr)(void);
int count;
};
struct on_member_pointer_fn_ptr_ty_ptr_ty_pos {
- // TODO: buffer of `count` function pointers should be allowed
- // but fails because this isn't late parsed.
- // expected-error at +1{{use of undeclared identifier 'count'}}
fn_ptr_ty* __counted_by(count) fn_ptr;
int count;
};
struct on_member_pointer_fn_ty_ty_pos {
- // TODO: This should fail because the attribute is
- // on a pointer with the pointee being a function type.
- // expected-error at +1{{use of undeclared identifier 'count'}}
+ // expected-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);
int count;
};
struct on_member_pointer_fn_ptr_ty_ty_pos {
- // TODO: buffer of `count` function pointers should be allowed
- // expected-error at +1{{use of undeclared identifier 'count'}}
void (** __counted_by(count) fn_ptr)(void);
int count;
};
struct on_member_pointer_fn_ptr_ty_typedef_ty_pos {
- // TODO: This should fail because the attribute is
- // on a pointer with the pointee being a function type.
- // expected-error at +1{{use of undeclared identifier 'count'}}
+ // expected-error at +1{{'counted_by' cannot be applied to a pointer with pointee of unknown size because 'void (void)' is a function type}}
fn_ptr_ty __counted_by(count) fn_ptr;
int count;
};
struct on_member_pointer_fn_ptr_ty_ty_pos_inner {
- // TODO: This should fail because the attribute is
- // on a pointer with the pointee being a function type.
- // expected-error at +1{{use of undeclared identifier 'count'}}
+ // expected-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);
int count;
};
+struct on_member_ptr_ptr_fn_ptr_ty_ty_pos_inner {
+ // expected-error at +1{{'counted_by' attribute on nested pointer type is not allowed}}
+ void (**__counted_by(count) * fn_ptr)(void);
+ int count;
+};
+
struct on_member_pointer_struct_with_vla_ty_pos {
- // TODO: This should fail because the attribute is
- // on a pointer with the pointee being a struct type with a VLA.
- // expected-error at +1{{use of undeclared identifier 'count'}}
+ // expected-error at +1{{'counted_by' cannot be applied to a pointer with pointee of unknown size because 'struct has_unannotated_vla' is a struct type with a flexible array member}}
struct has_unannotated_vla *__counted_by(count) objects;
int count;
};
struct on_member_pointer_struct_with_annotated_vla_ty_pos {
- // TODO: This should fail because the attribute is
- // on a pointer with the pointee being a struct type with a VLA.
- // expected-error at +1{{use of undeclared identifier 'count'}}
+ // expected-error at +1{{'counted_by' cannot be applied to a pointer with pointee of unknown size because 'struct has_annotated_vla' is a struct type with a flexible array member}}
struct has_annotated_vla* __counted_by(count) objects;
int count;
};
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.
- // expected-error at +1{{use of undeclared identifier 'count'}}
+ // expected-error at +1{{'counted_by' attribute on nested pointer type is not allowed}}
struct size_known *__counted_by(count) *buf;
int count;
};
struct on_nested_pointer_outer {
- // TODO: Allow this
- // expected-error at +1{{use of undeclared identifier 'count'}}
struct size_known **__counted_by(count) buf;
int count;
};
+struct on_nested_pointer_array_inner {
+ // expected-error at +1{{'counted_by' attribute on nested pointer type is not allowed}}
+ struct size_known *__counted_by(count) arr[10];
+ int count;
+};
+
+struct on_nested_pointer_flexible_array_inner {
+ // expected-error at +2{{flexible array member 'arr' with type 'struct size_known *[]' is not at the end of struct}}
+ // expected-error at +1{{'counted_by' attribute on nested pointer type is not allowed}}
+ struct size_known *__counted_by(count) arr[];
+ int count; // expected-note{{next field declaration is here}}
+};
+
struct on_pointer_anon_buf_ty_pos {
struct {
- // TODO: Support referring to parent scope
- // expected-error at +1{{use of undeclared identifier 'count'}}
struct size_known * __counted_by(count) buf;
};
int count;
};
struct on_pointer_anon_count_ty_pos {
- // TODO: Allow this
- // expected-error at +1{{use of undeclared identifier 'count'}}
struct size_known *__counted_by(count) buf;
struct {
int count;
diff --git a/clang/test/Sema/attr-counted-by-or-null-last-field.c b/clang/test/Sema/attr-counted-by-or-null-last-field.c
index d0c50a733acef5..9a1cae59f52829 100644
--- a/clang/test/Sema/attr-counted-by-or-null-last-field.c
+++ b/clang/test/Sema/attr-counted-by-or-null-last-field.c
@@ -128,9 +128,7 @@ struct on_member_ptr_incomplete_const_ty_ty_pos {
struct on_member_ptr_void_ty_ty_pos {
int count;
- // expected-warning at +2{{'counted_by_or_null' on a pointer to void is a GNU extension, treated as 'sized_by_or_null'}}
- // expected-note at +1{{use '__sized_by_or_null' to suppress this warning}}
- void * ptr __counted_by_or_null(count);
+ void * ptr __counted_by_or_null(count); // expected-warning{{'counted_by_or_null' on a pointer to void is a GNU extension, treated as 'sized_by_or_null'}} expected-note{{use '__sized_by_or_null' to suppress this warning}}
};
typedef void(fn_ty)(int);
diff --git a/clang/test/Sema/attr-counted-by-or-null-late-parsed-struct-ptrs.c b/clang/test/Sema/attr-counted-by-or-null-late-parsed-struct-ptrs.c
index 233b729f87ccd0..9f91f66b6e1c47 100644
--- a/clang/test/Sema/attr-counted-by-or-null-late-parsed-struct-ptrs.c
+++ b/clang/test/Sema/attr-counted-by-or-null-late-parsed-struct-ptrs.c
@@ -30,9 +30,7 @@ struct on_member_pointer_const_incomplete_ty {
};
struct on_member_pointer_void_ty {
- // expected-warning at +2{{'counted_by_or_null' on a pointer to void is a GNU extension, treated as 'sized_by_or_null'}}
- // expected-note at +1{{use '__sized_by_or_null' to suppress this warning}}
- void* buf __counted_by_or_null(count);
+ void* buf __counted_by_or_null(count); // expected-warning{{'counted_by_or_null' on a pointer to void is a GNU extension, treated as 'sized_by_or_null'}} expected-note{{use '__sized_by_or_null' to suppress this warning}}
int count;
};
@@ -89,9 +87,7 @@ struct on_member_pointer_struct_with_annotated_vla {
};
struct on_pointer_anon_buf {
- // TODO: Support referring to parent scope
struct {
- // expected-error at +1{{use of undeclared identifier 'count'}}
struct size_known *buf __counted_by_or_null(count);
};
int count;
@@ -107,131 +103,99 @@ struct on_pointer_anon_count {
//==============================================================================
// __counted_by_or_null on struct member pointer in type attribute position
//==============================================================================
-// TODO: Correctly parse counted_by_or_null as a type attribute. Currently it is parsed
-// as a declaration attribute and is **not** late parsed resulting in the `count`
-// field being unavailable.
struct on_member_pointer_complete_ty_ty_pos {
- // TODO: Allow this
- // expected-error at +1{{use of undeclared identifier 'count'}}
struct size_known *__counted_by_or_null(count) buf;
int count;
};
struct on_member_pointer_incomplete_ty_ty_pos {
- // TODO: Allow this
- // expected-error at +1{{use of undeclared identifier 'count'}}
struct size_unknown * __counted_by_or_null(count) buf;
int count;
};
struct on_member_pointer_const_incomplete_ty_ty_pos {
- // TODO: Allow this
- // expected-error at +1{{use of undeclared identifier 'count'}}
const struct size_unknown * __counted_by_or_null(count) buf;
int count;
};
struct on_member_pointer_void_ty_ty_pos {
- // TODO: This should fail because the attribute is
- // on a pointer with the pointee being an incomplete type.
- // expected-error at +1{{use of undeclared identifier 'count'}}
- void *__counted_by_or_null(count) buf;
+ void *__counted_by_or_null(count) buf; // expected-warning{{'counted_by_or_null' on a pointer to void is a GNU extension, treated as 'sized_by_or_null'}} expected-note{{use '__sized_by_or_null' to suppress this warning}}
int count;
};
// -
struct on_member_pointer_fn_ptr_ty_pos {
- // TODO: buffer of `count` function pointers should be allowed
- // but fails because this isn't late parsed.
- // expected-error at +1{{use of undeclared identifier 'count'}}
void (** __counted_by_or_null(count) fn_ptr)(void);
int count;
};
struct on_member_pointer_fn_ptr_ty_ptr_ty_pos {
- // TODO: buffer of `count` function pointers should be allowed
- // but fails because this isn't late parsed.
- // expected-error at +1{{use of undeclared identifier 'count'}}
fn_ptr_ty* __counted_by_or_null(count) fn_ptr;
int count;
};
struct on_member_pointer_fn_ty_ty_pos {
- // TODO: This should fail because the attribute is
- // on a pointer with the pointee being a function type.
- // expected-error at +1{{use of undeclared identifier 'count'}}
+ // expected-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);
int count;
};
struct on_member_pointer_fn_ptr_ty_ty_pos {
- // TODO: buffer of `count` function pointers should be allowed
- // expected-error at +1{{use of undeclared identifier 'count'}}
void (** __counted_by_or_null(count) fn_ptr)(void);
int count;
};
struct on_member_pointer_fn_ptr_ty_typedef_ty_pos {
- // TODO: This should fail because the attribute is
- // on a pointer with the pointee being a function type.
- // expected-error at +1{{use of undeclared identifier 'count'}}
+ // expected-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}}
fn_ptr_ty __counted_by_or_null(count) fn_ptr;
int count;
};
struct on_member_pointer_fn_ptr_ty_ty_pos_inner {
- // TODO: This should fail because the attribute is
- // on a pointer with the pointee being a function type.
- // expected-error at +1{{use of undeclared identifier 'count'}}
+ // expected-error at +1{{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);
int count;
};
+struct on_member_pointer_fn_ptr_ty_ty_ty_pos_inner {
+ // expected-error at +1{{'counted_by_or_null' attribute on nested pointer type is not allowed}}
+ void (** __counted_by_or_null(count) * fn_ptr)(void);
+ int count;
+};
+
struct on_member_pointer_struct_with_vla_ty_pos {
- // TODO: This should fail because the attribute is
- // on a pointer with the pointee being a struct type with a VLA.
- // expected-error at +1{{use of undeclared identifier 'count'}}
+ // expected-error at +1{{cannot be applied to a pointer with pointee of unknown size because 'struct has_unannotated_vla' is a struct type with a flexible array member}}
struct has_unannotated_vla *__counted_by_or_null(count) objects;
int count;
};
struct on_member_pointer_struct_with_annotated_vla_ty_pos {
- // TODO: This should fail because the attribute is
- // on a pointer with the pointee being a struct type with a VLA.
- // expected-error at +1{{use of undeclared identifier 'count'}}
+ // expected-error at +1{{'counted_by_or_null' cannot be applied to a pointer with pointee of unknown size because 'struct has_annotated_vla' is a struct type with a flexible array member}}
struct has_annotated_vla* __counted_by_or_null(count) objects;
int count;
};
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.
- // expected-error at +1{{use of undeclared identifier 'count'}}
+ // expected-error at +1{{'counted_by_or_null' attribute on nested pointer type is not allowed}}
struct size_known *__counted_by_or_null(count) *buf;
int count;
};
struct on_nested_pointer_outer {
- // TODO: Allow this
- // expected-error at +1{{use of undeclared identifier 'count'}}
struct size_known **__counted_by_or_null(count) buf;
int count;
};
struct on_pointer_anon_buf_ty_pos {
struct {
- // TODO: Support referring to parent scope
- // expected-error at +1{{use of undeclared identifier 'count'}}
struct size_known * __counted_by_or_null(count) buf;
};
int count;
};
struct on_pointer_anon_count_ty_pos {
- // TODO: Allow this
- // expected-error at +1{{use of undeclared identifier 'count'}}
struct size_known *__counted_by_or_null(count) buf;
struct {
int count;
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..4d4a6e81e3766b 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
@@ -17,7 +17,7 @@
// expected-note at +1 24{{forward declaration of 'struct IncompleteTy'}}
struct IncompleteTy; // expected-note 27{{consider providing a complete definition for 'struct IncompleteTy'}}
-typedef struct IncompleteTy Incomplete_t;
+typedef struct IncompleteTy Incomplete_t;
struct CBBufDeclPos {
int count;
@@ -75,7 +75,7 @@ void test_CBBufDeclPos(struct CBBufDeclPos* ptr) {
void* tmp3 = implicit_full_init.buf;
// expected-error at +1{{cannot use 'implicit_full_init.buf_typedef' with '__counted_by_or_null' attributed type 'Incomplete_t * __counted_by_or_null(count)' (aka 'struct IncompleteTy *') because the pointee type 'Incomplete_t' (aka 'struct IncompleteTy') is incomplete}}
void* tmp4 = implicit_full_init.buf_typedef;
-
+
struct CBBufDeclPos explicit_non_desig_init = {
0,
// expected-error at +1{{cannot initialize 'CBBufDeclPos::buf' with '__counted_by_or_null' attributed type 'struct IncompleteTy * __counted_by_or_null(count)' (aka 'struct IncompleteTy *') because the pointee type 'struct IncompleteTy' is incomplete}}
@@ -113,7 +113,7 @@ void test_CBBufDeclPos(struct CBBufDeclPos* ptr) {
uninit.buf_typedef++; // // expected-error{{arithmetic on a pointer to an incomplete type 'Incomplete_t' (aka 'struct IncompleteTy')}}
++uninit.buf_typedef; // expected-error{{arithmetic on a pointer to an incomplete type 'Incomplete_t' (aka 'struct IncompleteTy')}}
uninit.buf_typedef -= 1; // expected-error{{arithmetic on a pointer to an incomplete type 'Incomplete_t' (aka 'struct IncompleteTy')}}
-
+
uninit.buf--; // expected-error{{arithmetic on a pointer to an incomplete type 'struct IncompleteTy'}}
--uninit.buf; // expected-error{{arithmetic on a pointer to an incomplete type 'struct IncompleteTy'}}
uninit.buf -= 1; // expected-error{{arithmetic on a pointer to an incomplete type 'struct IncompleteTy'}}
@@ -139,16 +139,16 @@ void test_CBBufDeclPos(struct CBBufDeclPos* ptr) {
// ## Use of fields in expressions
// ===========================================================================
// expected-error at +2{{cannot use 'uninit.buf' with '__counted_by_or_null' attributed type 'struct IncompleteTy * __counted_by_or_null(count)' (aka 'struct IncompleteTy *') because the pointee type 'struct IncompleteTy' is incomplete}}
- void* addr =
+ void* addr =
((char*) uninit.buf ) + 1;
// expected-error at +2{{cannot use 'uninit.buf_typedef' with '__counted_by_or_null' attributed type 'Incomplete_t * __counted_by_or_null(count)' (aka 'struct IncompleteTy *') because the pointee type 'Incomplete_t' (aka 'struct IncompleteTy') is incomplete}}
- void* addr_typedef =
+ void* addr_typedef =
((char*) uninit.buf_typedef ) + 1;
// expected-error at +2{{cannot use 'ptr->buf' with '__counted_by_or_null' attributed type 'struct IncompleteTy * __counted_by_or_null(count)' (aka 'struct IncompleteTy *') because the pointee type 'struct IncompleteTy' is incomplete}}
- void* addr_ptr =
+ void* addr_ptr =
((char*) ptr->buf ) + 1;
// expected-error at +2{{cannot use 'ptr->buf_typedef' with '__counted_by_or_null' attributed type 'Incomplete_t * __counted_by_or_null(count)' (aka 'struct IncompleteTy *') because the pointee type 'Incomplete_t' (aka 'struct IncompleteTy') is incomplete}}
- void* addr_ptr_typedef =
+ void* addr_ptr_typedef =
((char*) ptr->buf_typedef ) + 1;
@@ -289,7 +289,7 @@ void test_CBBufDeclPos_completed(struct CBBufDeclPos* ptr) {
};
struct CBBufDeclPos implicit_full_init = {0};
-
+
struct CBBufDeclPos explicit_non_desig_init = {
0,
0x0,
@@ -384,10 +384,10 @@ void use_CBBufTyPos(struct CBBufTyPos* ptr) {
// Use
// expected-error at +2{{cannot use 'ptr->buf' with '__counted_by_or_null' attributed type 'struct IncompleteTy2 * __counted_by_or_null(count)' (aka 'struct IncompleteTy2 *') because the pointee type 'struct IncompleteTy2' is incomplete}}
- void* addr =
+ void* addr =
((char*) ptr->buf ) + 1;
// expected-error at +2{{cannot use 'ptr->buf_typedef' with '__counted_by_or_null' attributed type 'Incomplete_ty2 * __counted_by_or_null(count)' (aka 'struct IncompleteTy2 *') because the pointee type 'Incomplete_ty2' (aka 'struct IncompleteTy2') is incomplete}}
- void* addr_typedef =
+ void* addr_typedef =
((char*) ptr->buf_typedef ) + 1;
// expected-error at +1{{cannot use 'ptr->buf' with '__counted_by_or_null' attributed type 'struct IncompleteTy2 * __counted_by_or_null(count)' (aka 'struct IncompleteTy2 *') because the pointee type 'struct IncompleteTy2' is incomplete}}
@@ -458,10 +458,10 @@ void use_CBBufUnionTyPos(struct CBBufUnionTyPos* ptr) {
// Use
// expected-error at +2{{cannot use 'ptr->buf' with '__counted_by_or_null' attributed type 'union IncompleteUnionTy * __counted_by_or_null(count)' (aka 'union IncompleteUnionTy *') because the pointee type 'union IncompleteUnionTy' is incomplete}}
- void* addr =
+ void* addr =
((char*) ptr->buf ) + 1;
// expected-error at +2{{cannot use 'ptr->buf_typedef' with '__counted_by_or_null' attributed type 'IncompleteUnion_ty * __counted_by_or_null(count)' (aka 'union IncompleteUnionTy *') because the pointee type 'IncompleteUnion_ty' (aka 'union IncompleteUnionTy') is incomplete}}
- void* addr_typedef =
+ void* addr_typedef =
((char*) ptr->buf_typedef ) + 1;
// expected-error at +1{{cannot use 'ptr->buf' with '__counted_by_or_null' attributed type 'union IncompleteUnionTy * __counted_by_or_null(count)' (aka 'union IncompleteUnionTy *') because the pointee type 'union IncompleteUnionTy' is incomplete}}
@@ -532,10 +532,10 @@ void use_CBBufEnumTyPos(struct CBBufEnumTyPos* ptr) {
// Use
// expected-error at +2{{cannot use 'ptr->buf' with '__counted_by_or_null' attributed type 'enum IncompleteEnumTy * __counted_by_or_null(count)' (aka 'enum IncompleteEnumTy *') because the pointee type 'enum IncompleteEnumTy' is incomplete}}
- void* addr =
+ void* addr =
((char*) ptr->buf ) + 1;
// expected-error at +2{{cannot use 'ptr->buf_typedef' with '__counted_by_or_null' attributed type 'IncompleteEnum_ty * __counted_by_or_null(count)' (aka 'enum IncompleteEnumTy *') because the pointee type 'IncompleteEnum_ty' (aka 'enum IncompleteEnumTy') is incomplete}}
- void* addr_typedef =
+ void* addr_typedef =
((char*) ptr->buf_typedef ) + 1;
// expected-error at +1{{cannot use 'ptr->buf' with '__counted_by_or_null' attributed type 'enum IncompleteEnumTy * __counted_by_or_null(count)' (aka 'enum IncompleteEnumTy *') because the pointee type 'enum IncompleteEnumTy' is incomplete}}
@@ -616,16 +616,13 @@ 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'?}}
+ // expected-error at +1{{'counted_by_or_null' attribute on nested pointer type is not allowed}}
struct IncompleteTy3* __counted_by_or_null(size) arr[];
};
void arr_of_counted_by_ptr(struct CBBufFAMofCountedByPtrs* ptr) {
- // TODO: Should be disallowed once parsing attributes in the type position
- // works.
+ // TODO: Diagnostic should appear here once `__counted_by_or_null` is allowed on
+ // nested pointers.
ptr->arr[0] = 0x0;
void* addr = ((char*) ptr->arr[0]) + 1;
}
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..e8ab29cd80181f 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
@@ -105,8 +105,6 @@ struct on_pointer_anon_count {
//==============================================================================
// __counted_by_or_null on struct member pointer in type attribute position
//==============================================================================
-// TODO: Correctly parse counted_by_or_null as a type attribute. Currently it is parsed
-// as a declaration attribute
struct on_member_pointer_complete_ty_ty_pos {
int count;
@@ -158,13 +156,18 @@ 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.
struct on_member_pointer_fn_ptr_ty_ty_pos_inner {
int count;
+ // expected-error at +1{{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);
};
+struct on_member_pointer_fn_ptr_ty_ty_ty_pos_inner {
+ int count;
+ // expected-error at +1{{'counted_by_or_null' attribute on nested pointer type is not allowed}}
+ void (** __counted_by_or_null(count) * fn_ptr)(void);
+};
+
struct on_member_pointer_struct_with_vla_ty_pos {
int count;
// expected-error at +1{{'counted_by_or_null' cannot be applied to a pointer with pointee of unknown size because 'struct has_unannotated_vla' is a struct type with a flexible array member}}
@@ -181,9 +184,8 @@ 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.
int count;
+ // expected-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-struct-ptrs-completable-incomplete-pointee.c b/clang/test/Sema/attr-counted-by-struct-ptrs-completable-incomplete-pointee.c
index d28a2086b51b88..f9afe558d0e13b 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
@@ -17,7 +17,7 @@
// expected-note at +1 24{{forward declaration of 'struct IncompleteTy'}}
struct IncompleteTy; // expected-note 27{{consider providing a complete definition for 'struct IncompleteTy'}}
-typedef struct IncompleteTy Incomplete_t;
+typedef struct IncompleteTy Incomplete_t;
struct CBBufDeclPos {
int count;
@@ -75,7 +75,7 @@ void test_CBBufDeclPos(struct CBBufDeclPos* ptr) {
void* tmp3 = implicit_full_init.buf;
// expected-error at +1{{cannot use 'implicit_full_init.buf_typedef' with '__counted_by' attributed type 'Incomplete_t * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'Incomplete_t' (aka 'struct IncompleteTy') is incomplete}}
void* tmp4 = implicit_full_init.buf_typedef;
-
+
struct CBBufDeclPos explicit_non_desig_init = {
0,
// expected-error at +1{{cannot initialize 'CBBufDeclPos::buf' with '__counted_by' attributed type 'struct IncompleteTy * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'struct IncompleteTy' is incomplete}}
@@ -113,7 +113,7 @@ void test_CBBufDeclPos(struct CBBufDeclPos* ptr) {
uninit.buf_typedef++; // // expected-error{{arithmetic on a pointer to an incomplete type 'Incomplete_t' (aka 'struct IncompleteTy')}}
++uninit.buf_typedef; // expected-error{{arithmetic on a pointer to an incomplete type 'Incomplete_t' (aka 'struct IncompleteTy')}}
uninit.buf_typedef -= 1; // expected-error{{arithmetic on a pointer to an incomplete type 'Incomplete_t' (aka 'struct IncompleteTy')}}
-
+
uninit.buf--; // expected-error{{arithmetic on a pointer to an incomplete type 'struct IncompleteTy'}}
--uninit.buf; // expected-error{{arithmetic on a pointer to an incomplete type 'struct IncompleteTy'}}
uninit.buf -= 1; // expected-error{{arithmetic on a pointer to an incomplete type 'struct IncompleteTy'}}
@@ -139,16 +139,16 @@ void test_CBBufDeclPos(struct CBBufDeclPos* ptr) {
// ## Use of fields in expressions
// ===========================================================================
// expected-error at +2{{cannot use 'uninit.buf' with '__counted_by' attributed type 'struct IncompleteTy * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'struct IncompleteTy' is incomplete}}
- void* addr =
+ void* addr =
((char*) uninit.buf ) + 1;
// expected-error at +2{{cannot use 'uninit.buf_typedef' with '__counted_by' attributed type 'Incomplete_t * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'Incomplete_t' (aka 'struct IncompleteTy') is incomplete}}
- void* addr_typedef =
+ void* addr_typedef =
((char*) uninit.buf_typedef ) + 1;
// expected-error at +2{{cannot use 'ptr->buf' with '__counted_by' attributed type 'struct IncompleteTy * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'struct IncompleteTy' is incomplete}}
- void* addr_ptr =
+ void* addr_ptr =
((char*) ptr->buf ) + 1;
// expected-error at +2{{cannot use 'ptr->buf_typedef' with '__counted_by' attributed type 'Incomplete_t * __counted_by(count)' (aka 'struct IncompleteTy *') because the pointee type 'Incomplete_t' (aka 'struct IncompleteTy') is incomplete}}
- void* addr_ptr_typedef =
+ void* addr_ptr_typedef =
((char*) ptr->buf_typedef ) + 1;
@@ -289,7 +289,7 @@ void test_CBBufDeclPos_completed(struct CBBufDeclPos* ptr) {
};
struct CBBufDeclPos implicit_full_init = {0};
-
+
struct CBBufDeclPos explicit_non_desig_init = {
0,
0x0,
@@ -384,10 +384,10 @@ void use_CBBufTyPos(struct CBBufTyPos* ptr) {
// Use
// expected-error at +2{{cannot use 'ptr->buf' with '__counted_by' attributed type 'struct IncompleteTy2 * __counted_by(count)' (aka 'struct IncompleteTy2 *') because the pointee type 'struct IncompleteTy2' is incomplete}}
- void* addr =
+ void* addr =
((char*) ptr->buf ) + 1;
// expected-error at +2{{cannot use 'ptr->buf_typedef' with '__counted_by' attributed type 'Incomplete_ty2 * __counted_by(count)' (aka 'struct IncompleteTy2 *') because the pointee type 'Incomplete_ty2' (aka 'struct IncompleteTy2') is incomplete}}
- void* addr_typedef =
+ void* addr_typedef =
((char*) ptr->buf_typedef ) + 1;
// expected-error at +1{{cannot use 'ptr->buf' with '__counted_by' attributed type 'struct IncompleteTy2 * __counted_by(count)' (aka 'struct IncompleteTy2 *') because the pointee type 'struct IncompleteTy2' is incomplete}}
@@ -458,10 +458,10 @@ void use_CBBufUnionTyPos(struct CBBufUnionTyPos* ptr) {
// Use
// expected-error at +2{{cannot use 'ptr->buf' with '__counted_by' attributed type 'union IncompleteUnionTy * __counted_by(count)' (aka 'union IncompleteUnionTy *') because the pointee type 'union IncompleteUnionTy' is incomplete}}
- void* addr =
+ void* addr =
((char*) ptr->buf ) + 1;
// expected-error at +2{{cannot use 'ptr->buf_typedef' with '__counted_by' attributed type 'IncompleteUnion_ty * __counted_by(count)' (aka 'union IncompleteUnionTy *') because the pointee type 'IncompleteUnion_ty' (aka 'union IncompleteUnionTy') is incomplete}}
- void* addr_typedef =
+ void* addr_typedef =
((char*) ptr->buf_typedef ) + 1;
// expected-error at +1{{cannot use 'ptr->buf' with '__counted_by' attributed type 'union IncompleteUnionTy * __counted_by(count)' (aka 'union IncompleteUnionTy *') because the pointee type 'union IncompleteUnionTy' is incomplete}}
@@ -532,10 +532,10 @@ void use_CBBufEnumTyPos(struct CBBufEnumTyPos* ptr) {
// Use
// expected-error at +2{{cannot use 'ptr->buf' with '__counted_by' attributed type 'enum IncompleteEnumTy * __counted_by(count)' (aka 'enum IncompleteEnumTy *') because the pointee type 'enum IncompleteEnumTy' is incomplete}}
- void* addr =
+ void* addr =
((char*) ptr->buf ) + 1;
// expected-error at +2{{cannot use 'ptr->buf_typedef' with '__counted_by' attributed type 'IncompleteEnum_ty * __counted_by(count)' (aka 'enum IncompleteEnumTy *') because the pointee type 'IncompleteEnum_ty' (aka 'enum IncompleteEnumTy') is incomplete}}
- void* addr_typedef =
+ void* addr_typedef =
((char*) ptr->buf_typedef ) + 1;
// expected-error at +1{{cannot use 'ptr->buf' with '__counted_by' attributed type 'enum IncompleteEnumTy * __counted_by(count)' (aka 'enum IncompleteEnumTy *') because the pointee type 'enum IncompleteEnumTy' is incomplete}}
@@ -616,9 +616,7 @@ 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' 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..f8957ca8b4eba4 100644
--- a/clang/test/Sema/attr-counted-by-struct-ptrs.c
+++ b/clang/test/Sema/attr-counted-by-struct-ptrs.c
@@ -104,8 +104,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
struct on_member_pointer_complete_ty_ty_pos {
int count;
@@ -157,11 +155,16 @@ 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.
struct on_member_pointer_fn_ptr_ty_ty_pos_inner {
int count;
- void (* __counted_by(count) * fn_ptr)(void);
+ // expected-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); // FIXME
+};
+
+struct on_member_pointer_fn_ptr_ty_ty_ty_pos_inner {
+ int count;
+ // expected-error at +1{{'counted_by' attribute on nested pointer type is not allowed}}
+ void (** __counted_by(count) * fn_ptr)(void);
};
struct on_member_pointer_struct_with_vla_ty_pos {
@@ -180,10 +183,9 @@ 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.
int count;
- struct size_known *__counted_by(count) *buf;
+ // expected-error at +1{{'counted_by' attribute on nested pointer type is not allowed}}
+ struct size_known *__counted_by(count) *buf; // FIXME
};
struct on_nested_pointer_outer {
diff --git a/clang/test/Sema/attr-sized-by-late-parsed-struct-ptrs.c b/clang/test/Sema/attr-sized-by-late-parsed-struct-ptrs.c
index cfdf3407332c91..054fa0fec51e1a 100644
--- a/clang/test/Sema/attr-sized-by-late-parsed-struct-ptrs.c
+++ b/clang/test/Sema/attr-sized-by-late-parsed-struct-ptrs.c
@@ -82,9 +82,7 @@ struct on_member_pointer_struct_with_annotated_vla {
};
struct on_pointer_anon_buf {
- // TODO: Support referring to parent scope
struct {
- // expected-error at +1{{use of undeclared identifier 'size'}}
struct size_known *buf __sized_by(size);
};
int size;
@@ -100,35 +98,23 @@ struct on_pointer_anon_count {
//==============================================================================
// __sized_by on struct member pointer in type attribute position
//==============================================================================
-// TODO: Correctly parse sized_by as a type attribute. Currently it is parsed
-// as a declaration attribute and is **not** late parsed resulting in the `size`
-// field being unavailable.
struct on_member_pointer_complete_ty_ty_pos {
- // TODO: Allow this
- // expected-error at +1{{use of undeclared identifier 'size'}}
struct size_known *__sized_by(size) buf;
int size;
};
struct on_member_pointer_incomplete_ty_ty_pos {
- // TODO: Allow this
- // expected-error at +1{{use of undeclared identifier 'size'}}
struct size_unknown * __sized_by(size) buf;
int size;
};
struct on_member_pointer_const_incomplete_ty_ty_pos {
- // TODO: Allow this
- // expected-error at +1{{use of undeclared identifier 'size'}}
const struct size_unknown * __sized_by(size) buf;
int size;
};
struct on_member_pointer_void_ty_ty_pos {
- // TODO: This should fail because the attribute is
- // on a pointer with the pointee being an incomplete type.
- // expected-error at +1{{use of undeclared identifier 'size'}}
void *__sized_by(size) buf;
int size;
};
@@ -136,91 +122,73 @@ struct on_member_pointer_void_ty_ty_pos {
// -
struct on_member_pointer_fn_ptr_ty_pos {
- // TODO: buffer of `size` function pointers should be allowed
- // but fails because this isn't late parsed.
- // expected-error at +1{{use of undeclared identifier 'size'}}
void (** __sized_by(size) fn_ptr)(void);
int size;
};
struct on_member_pointer_fn_ptr_ty_ptr_ty_pos {
- // TODO: buffer of `size` function pointers should be allowed
- // but fails because this isn't late parsed.
- // expected-error at +1{{use of undeclared identifier 'size'}}
fn_ptr_ty* __sized_by(size) fn_ptr;
int size;
};
struct on_member_pointer_fn_ty_ty_pos {
- // TODO: This should fail because the attribute is
- // on a pointer with the pointee being a function type.
- // expected-error at +1{{use of undeclared identifier 'size'}}
+ // expected-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);
int size;
};
struct on_member_pointer_fn_ptr_ty_ty_pos {
- // TODO: buffer of `size` function pointers should be allowed
- // expected-error at +1{{use of undeclared identifier 'size'}}
void (** __sized_by(size) fn_ptr)(void);
int size;
};
struct on_member_pointer_fn_ptr_ty_typedef_ty_pos {
- // TODO: This should be allowed with sized_by.
- // expected-error at +1{{use of undeclared identifier 'size'}}
+ // expected-error at +1{{'sized_by' cannot be applied to a pointer with pointee of unknown size because 'void (void)' is a function type}}
fn_ptr_ty __sized_by(size) fn_ptr;
int size;
};
struct on_member_pointer_fn_ptr_ty_ty_pos_inner {
- // TODO: This should be allowed with sized_by.
- // expected-error at +1{{use of undeclared identifier 'size'}}
+ // expected-error at +1{{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);
int size;
};
+struct on_member_pointer_fn_ptr_ty_ty_ty_pos_inner {
+ // expected-error at +1{{'sized_by' attribute on nested pointer type is not allowed}}
+ void (** __sized_by(size) * fn_ptr)(void);
+ int size;
+};
+
struct on_member_pointer_struct_with_vla_ty_pos {
- // TODO: This should be allowed with sized_by.
- // expected-error at +1{{use of undeclared identifier 'size'}}
struct has_unannotated_vla *__sized_by(size) objects;
int size;
};
struct on_member_pointer_struct_with_annotated_vla_ty_pos {
- // TODO: This should be allowed with sized_by.
- // expected-error at +1{{use of undeclared identifier 'size'}}
struct has_annotated_vla* __sized_by(size) objects;
int size;
};
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.
- // expected-error at +1{{use of undeclared identifier 'size'}}
+ // expected-error at +1{{'sized_by' attribute on nested pointer type is not allowed}}
struct size_known *__sized_by(size) *buf;
int size;
};
struct on_nested_pointer_outer {
- // TODO: Allow this
- // expected-error at +1{{use of undeclared identifier 'size'}}
struct size_known **__sized_by(size) buf;
int size;
};
struct on_pointer_anon_buf_ty_pos {
struct {
- // TODO: Support referring to parent scope
- // expected-error at +1{{use of undeclared identifier 'size'}}
struct size_known * __sized_by(size) buf;
};
int size;
};
struct on_pointer_anon_count_ty_pos {
- // TODO: Allow this
- // expected-error at +1{{use of undeclared identifier 'size'}}
struct size_known *__sized_by(size) buf;
struct {
int size;
diff --git a/clang/test/Sema/attr-sized-by-or-null-late-parsed-struct-ptrs.c b/clang/test/Sema/attr-sized-by-or-null-late-parsed-struct-ptrs.c
index b726a9b005b9b5..50d5f57ded0233 100644
--- a/clang/test/Sema/attr-sized-by-or-null-late-parsed-struct-ptrs.c
+++ b/clang/test/Sema/attr-sized-by-or-null-late-parsed-struct-ptrs.c
@@ -82,9 +82,7 @@ struct on_member_pointer_struct_with_annotated_vla {
};
struct on_pointer_anon_buf {
- // TODO: Support referring to parent scope
struct {
- // expected-error at +1{{use of undeclared identifier 'size'}}
struct size_known *buf __sized_by_or_null(size);
};
int size;
@@ -100,35 +98,23 @@ struct on_pointer_anon_count {
//==============================================================================
// __sized_by_or_null on struct member pointer in type attribute position
//==============================================================================
-// TODO: Correctly parse sized_by_or_null as a type attribute. Currently it is parsed
-// as a declaration attribute and is **not** late parsed resulting in the `size`
-// field being unavailable.
struct on_member_pointer_complete_ty_ty_pos {
- // TODO: Allow this
- // expected-error at +1{{use of undeclared identifier 'size'}}
struct size_known *__sized_by_or_null(size) buf;
int size;
};
struct on_member_pointer_incomplete_ty_ty_pos {
- // TODO: Allow this
- // expected-error at +1{{use of undeclared identifier 'size'}}
struct size_unknown * __sized_by_or_null(size) buf;
int size;
};
struct on_member_pointer_const_incomplete_ty_ty_pos {
- // TODO: Allow this
- // expected-error at +1{{use of undeclared identifier 'size'}}
const struct size_unknown * __sized_by_or_null(size) buf;
int size;
};
struct on_member_pointer_void_ty_ty_pos {
- // TODO: This should fail because the attribute is
- // on a pointer with the pointee being an incomplete type.
- // expected-error at +1{{use of undeclared identifier 'size'}}
void *__sized_by_or_null(size) buf;
int size;
};
@@ -136,91 +122,74 @@ struct on_member_pointer_void_ty_ty_pos {
// -
struct on_member_pointer_fn_ptr_ty_pos {
- // TODO: buffer of `size` function pointers should be allowed
- // but fails because this isn't late parsed.
- // expected-error at +1{{use of undeclared identifier 'size'}}
void (** __sized_by_or_null(size) fn_ptr)(void);
int size;
};
struct on_member_pointer_fn_ptr_ty_ptr_ty_pos {
- // TODO: buffer of `size` function pointers should be allowed
- // but fails because this isn't late parsed.
- // expected-error at +1{{use of undeclared identifier 'size'}}
fn_ptr_ty* __sized_by_or_null(size) fn_ptr;
int size;
};
struct on_member_pointer_fn_ty_ty_pos {
- // TODO: This should fail because the attribute is
- // on a pointer with the pointee being a function type.
- // expected-error at +1{{use of undeclared identifier 'size'}}
+ // TODO: Improve diagnostics (Issue #167368).
+ // expected-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);
int size;
};
struct on_member_pointer_fn_ptr_ty_ty_pos {
- // TODO: buffer of `size` function pointers should be allowed
- // expected-error at +1{{use of undeclared identifier 'size'}}
void (** __sized_by_or_null(size) fn_ptr)(void);
int size;
};
struct on_member_pointer_fn_ptr_ty_typedef_ty_pos {
- // TODO: This should be allowed with sized_by_or_null.
- // expected-error at +1{{use of undeclared identifier 'size'}}
+ // expected-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}}
fn_ptr_ty __sized_by_or_null(size) fn_ptr;
int size;
};
struct on_member_pointer_fn_ptr_ty_ty_pos_inner {
- // TODO: This should be allowed with sized_by_or_null.
- // expected-error at +1{{use of undeclared identifier 'size'}}
+ // expected-error at +1{{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);
int size;
};
+struct on_member_pointer_fn_ptr_ty_ty_ty_pos_inner {
+ // expected-error at +1{{'sized_by_or_null' attribute on nested pointer type is not allowed}}
+ void (** __sized_by_or_null(size) * fn_ptr)(void);
+ int size;
+};
+
struct on_member_pointer_struct_with_vla_ty_pos {
- // TODO: This should be allowed with sized_by_or_null.
- // expected-error at +1{{use of undeclared identifier 'size'}}
struct has_unannotated_vla *__sized_by_or_null(size) objects;
int size;
};
struct on_member_pointer_struct_with_annotated_vla_ty_pos {
- // TODO: This should be allowed with sized_by_or_null.
- // expected-error at +1{{use of undeclared identifier 'size'}}
struct has_annotated_vla* __sized_by_or_null(size) objects;
int size;
};
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.
- // expected-error at +1{{use of undeclared identifier 'size'}}
+ // expected-error at +1{{'sized_by_or_null' attribute on nested pointer type is not allowed}}
struct size_known *__sized_by_or_null(size) *buf;
int size;
};
struct on_nested_pointer_outer {
- // TODO: Allow this
- // expected-error at +1{{use of undeclared identifier 'size'}}
struct size_known **__sized_by_or_null(size) buf;
int size;
};
struct on_pointer_anon_buf_ty_pos {
struct {
- // TODO: Support referring to parent scope
- // expected-error at +1{{use of undeclared identifier 'size'}}
struct size_known * __sized_by_or_null(size) buf;
};
int size;
};
struct on_pointer_anon_count_ty_pos {
- // TODO: Allow this
- // expected-error at +1{{use of undeclared identifier 'size'}}
struct size_known *__sized_by_or_null(size) buf;
struct {
int size;
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..69ce8c5a67c700 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
@@ -100,8 +100,6 @@ struct on_pointer_anon_size {
//==============================================================================
// __sized_by_or_null on struct member pointer in type attribute position
//==============================================================================
-// TODO: Correctly parse sized_by_or_null as a type attribute. Currently it is parsed
-// as a declaration attribute
struct on_member_pointer_complete_ty_ty_pos {
int size;
@@ -149,13 +147,18 @@ 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.
struct on_member_pointer_fn_ptr_ty_ty_pos_inner {
int size;
+ // expected-error at +1{{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);
};
+struct on_member_pointer_fn_ptr_ty_ty_ty_pos_inner {
+ int size;
+ // expected-error at +1{{'sized_by_or_null' attribute on nested pointer type is not allowed}}
+ void (** __sized_by_or_null(size) * fn_ptr)(void);
+};
+
struct on_member_pointer_struct_with_vla_ty_pos {
int size;
struct has_unannotated_vla *__sized_by_or_null(size) objects;
@@ -167,9 +170,8 @@ 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.
int size;
+ // expected-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..d56598f012fc2e 100644
--- a/clang/test/Sema/attr-sized-by-struct-ptrs.c
+++ b/clang/test/Sema/attr-sized-by-struct-ptrs.c
@@ -149,13 +149,18 @@ 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.
struct on_member_pointer_fn_ptr_ty_ty_pos_inner {
int size;
+ // expected-error at +1{{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);
};
+struct on_member_pointer_fn_ptr_ty_ty_ty_pos_inner {
+ int size;
+ // expected-error at +1{{'sized_by' attribute on nested pointer type is not allowed}}
+ void (** __sized_by(size) * fn_ptr)(void);
+};
+
struct on_member_pointer_struct_with_vla_ty_pos {
int size;
struct has_unannotated_vla *__sized_by(size) objects;
@@ -167,9 +172,8 @@ 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.
int size;
+ // expected-error at +1{{'sized_by' attribute on nested pointer type is not allowed}}
struct size_known *__sized_by(size) *buf;
};
>From f18dc81e199e026442a29198e79bd508af59b20b Mon Sep 17 00:00:00 2001
From: Yeoul Na <yeoul_na at apple.com>
Date: Wed, 9 Sep 2026 13:02:47 -0700
Subject: [PATCH 10/10] [BoundsSafety][test] Add late-parsed counted_by
type-attribute coverage
New tests exercising the late-parse fill-in mechanism:
- Sema/attr-counted-by-weird-type-positions{,-late-parsed}.c: counted_by
in assorted type positions, nested pointers, and rejection cases.
- Sema/attr-bounds-safety-function-ptr-param.c: attributes on
function-pointer-typed members.
- Modules/ and PCH/ bounds-safety-attributed-type-late-parsed: the
resolved type round-trips through serialization.
- Sema/attr-counted-by-late-parsed-regressions.c: guards against the
double-free on a nested-record decl-spec attribute and the null-count
escape on a free-function parameter.
---
.../AST/attr-counted-by-eager-invalid-strip.c | 27 ++
...-counted-by-late-parsed-invalid-recovery.c | 46 ++
.../attr-counted-by-late-parsed-struct-ptrs.c | 19 +
...ounds-safety-attributed-type-late-parsed.c | 111 +++++
...ounds-safety-attributed-type-late-parsed.h | 58 +++
...ounds-safety-attributed-type-late-parsed.c | 69 +++
.../attr-bounds-safety-function-ptr-param.c | 173 +++++++
.../attr-counted-by-late-parsed-regressions.c | 104 ++++
...nted-by-weird-type-positions-late-parsed.c | 456 ++++++++++++++++++
.../attr-counted-by-weird-type-positions.c | 454 +++++++++++++++++
10 files changed, 1517 insertions(+)
create mode 100644 clang/test/AST/attr-counted-by-eager-invalid-strip.c
create mode 100644 clang/test/AST/attr-counted-by-late-parsed-invalid-recovery.c
create mode 100644 clang/test/Modules/bounds-safety-attributed-type-late-parsed.c
create mode 100644 clang/test/PCH/Inputs/bounds-safety-attributed-type-late-parsed.h
create mode 100644 clang/test/PCH/bounds-safety-attributed-type-late-parsed.c
create mode 100644 clang/test/Sema/attr-bounds-safety-function-ptr-param.c
create mode 100644 clang/test/Sema/attr-counted-by-late-parsed-regressions.c
create mode 100644 clang/test/Sema/attr-counted-by-weird-type-positions-late-parsed.c
create mode 100644 clang/test/Sema/attr-counted-by-weird-type-positions.c
diff --git a/clang/test/AST/attr-counted-by-eager-invalid-strip.c b/clang/test/AST/attr-counted-by-eager-invalid-strip.c
new file mode 100644
index 00000000000000..51cd306aa1970f
--- /dev/null
+++ b/clang/test/AST/attr-counted-by-eager-invalid-strip.c
@@ -0,0 +1,27 @@
+// RUN: %clang_cc1 -verify %s -ast-dump | FileCheck %s
+
+// On the eager (non -fexperimental-late-parse-attributes) path a
+// counted_by-family CountAttributedType is built during type processing, before
+// the FieldDecl-dependent checks run in ActOnFields. When those checks reject
+// the attribute, the CountAttributedType must be stripped so the field keeps
+// its plain wrapped type -- matching the pre-refactor behavior, which built the
+// type only after the check passed. A surviving bogus CountAttributedType would
+// otherwise flow downstream. This test pins the stripped type; the diagnostics
+// themselves are covered elsewhere.
+
+#define __counted_by(f) __attribute__((counted_by(f)))
+
+union invalid_union_member {
+ int n;
+ int *__counted_by(n) p; // expected-error {{'counted_by' cannot be applied to a union member}}
+};
+// CHECK-LABEL: union invalid_union_member definition
+// CHECK: FieldDecl {{.*}} p 'int *'{{$}}
+
+struct invalid_non_fam_array {
+ int n;
+ int arr[10] __counted_by(n); // expected-error {{'counted_by' on arrays only applies to C99 flexible array members}}
+ int last;
+};
+// CHECK-LABEL: struct invalid_non_fam_array definition
+// CHECK: FieldDecl {{.*}} arr 'int[10]'{{$}}
diff --git a/clang/test/AST/attr-counted-by-late-parsed-invalid-recovery.c b/clang/test/AST/attr-counted-by-late-parsed-invalid-recovery.c
new file mode 100644
index 00000000000000..776f8422ad0fcf
--- /dev/null
+++ b/clang/test/AST/attr-counted-by-late-parsed-invalid-recovery.c
@@ -0,0 +1,46 @@
+// RUN: %clang_cc1 -fexperimental-late-parse-attributes -verify %s -ast-dump | FileCheck %s
+
+// On the late-parsed path the CountAttributedType is built before its count
+// argument is parsed, so a rejected argument is only discovered at completion,
+// when the node is already embedded in the field's type. Rather than strip a
+// (possibly nested) node -- which would force the enclosing types to be rebuilt
+// -- the node is kept and completed in place with the raw argument, and the
+// field is marked invalid. Consumers bail on such a count (see
+// FieldDecl::findCountedByField). This applies whether the argument is unusable
+// (a parse failure, or a non-declaration-reference such as `sizeof(...)`) or is
+// a valid reference in an invalid position (a union member).
+//
+// A nested counted_by is the exception: it is diagnosed and dropped while the
+// declarator is built -- before the enclosing pointer/array wraps the node, so
+// the drop needs no rebuild -- leaving the field its plain wrapped type, exactly
+// as on the eager path.
+
+#define __counted_by(f) __attribute__((counted_by(f)))
+
+// Non-declaration-reference argument: the node is kept with the raw argument as
+// its count and the field is marked invalid.
+struct bad_count_expr {
+ int n;
+ int *__counted_by(sizeof(int)) p; // expected-error {{'counted_by' argument must be a simple declaration reference}}
+};
+// CHECK-LABEL: struct bad_count_expr definition
+// CHECK: FieldDecl {{.*}} invalid p 'int * __counted_by(sizeof(int))':'int *'
+
+// Valid reference in an invalid position: also kept with the raw argument and
+// the field marked invalid.
+union valid_ref_bad_position {
+ int n;
+ int *__counted_by(n) p; // expected-error {{'counted_by' cannot be applied to a union member}}
+};
+// CHECK-LABEL: union valid_ref_bad_position definition
+// CHECK: FieldDecl {{.*}} invalid p 'int * __counted_by(n)':'int *'
+
+// Nested under another pointer: diagnosed and dropped while the declarator is
+// built, so the field keeps its plain wrapped type (no CountAttributedType) and
+// stays valid -- exactly as on the eager path.
+struct nested_under_pointer {
+ int n;
+ int *__counted_by(n) *pp; // expected-error {{'counted_by' attribute on nested pointer type is not allowed}}
+};
+// CHECK-LABEL: struct nested_under_pointer definition
+// CHECK: FieldDecl {{.*}} pp 'int **'{{$}}
diff --git a/clang/test/AST/attr-counted-by-late-parsed-struct-ptrs.c b/clang/test/AST/attr-counted-by-late-parsed-struct-ptrs.c
index f9772db8b65543..9d43523bf31802 100644
--- a/clang/test/AST/attr-counted-by-late-parsed-struct-ptrs.c
+++ b/clang/test/AST/attr-counted-by-late-parsed-struct-ptrs.c
@@ -43,3 +43,22 @@ struct on_pointer_anon_count {
//
// See `clang/test/Sema/attr-counted-by-late-parsed-struct-ptrs.c` for test
// cases.
+
+//==============================================================================
+// A declaration-specifier-position attribute shared by several declarators
+//==============================================================================
+// All declarators share one CountAttributedType, so every field must print a
+// resolved count. Previously only the last one did: each declarator built its
+// own (un-uniqued) node and only the last was ever completed, leaving the
+// earlier fields with an empty '__counted_by()'.
+
+typedef int *ptr_ty;
+
+struct shared_declspec_attr {
+ int count;
+ ptr_ty __counted_by(count) a, b;
+};
+// CHECK-LABEL: struct shared_declspec_attr definition
+// CHECK-NEXT: |-FieldDecl {{.*}} referenced count 'int'
+// CHECK-NEXT: |-FieldDecl {{.*}} a 'ptr_ty __counted_by(count)':'int *'
+// CHECK-NEXT: `-FieldDecl {{.*}} b 'ptr_ty __counted_by(count)':'int *'
diff --git a/clang/test/Modules/bounds-safety-attributed-type-late-parsed.c b/clang/test/Modules/bounds-safety-attributed-type-late-parsed.c
new file mode 100644
index 00000000000000..58df0761f2022b
--- /dev/null
+++ b/clang/test/Modules/bounds-safety-attributed-type-late-parsed.c
@@ -0,0 +1,111 @@
+// Test serialization of late-parsed bounds-safety attributes via Modules
+// This verifies that LateParsedAttrType is transformed to CountAttributedType
+// before serialization and remains as CountAttributedType after deserialization.
+
+// RUN: rm -rf %t
+// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fmodules -fmodules-cache-path=%t -verify %s
+// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fmodules -fmodules-cache-path=%t -ast-dump-all %s | FileCheck %s
+// expected-no-diagnostics
+
+#pragma clang module build bounds_safety_late_parsed
+module bounds_safety_late_parsed {}
+#pragma clang module contents
+#pragma clang module begin bounds_safety_late_parsed
+
+// Test where counted_by references a field declared later
+struct LateRefPointer {
+ int *__attribute__((counted_by(count))) buf;
+ int count;
+};
+
+// Test with sized_by referencing later field
+struct LateRefSized {
+ int *__attribute__((sized_by(size))) data;
+ int size;
+};
+
+// Test with counted_by_or_null referencing later field
+struct LateRefCountedByOrNull {
+ int *__attribute__((counted_by_or_null(count))) buf;
+ int count;
+};
+
+// Test with sized_by_or_null referencing later field
+struct LateRefSizedByOrNull {
+ int *__attribute__((sized_by_or_null(size))) data;
+ int size;
+};
+
+// Test with nested struct
+struct LateRefNested {
+ struct Inner {
+ int value;
+ } *__attribute__((counted_by(n))) items;
+ int n;
+};
+
+// Test with multiple late-parsed attributes
+struct MultipleLateRefs {
+ int *__attribute__((counted_by(count1))) buf1;
+ int *__attribute__((sized_by(count2))) buf2;
+ int *__attribute__((counted_by_or_null(count3))) buf3;
+ int *__attribute__((sized_by_or_null(count4))) buf4;
+ int count1;
+ int count2;
+ int count3;
+ int count4;
+};
+
+#pragma clang module end
+#pragma clang module endbuild
+
+#pragma clang module import bounds_safety_late_parsed
+
+struct LateRefPointer *p1;
+struct LateRefSized *p2;
+struct LateRefCountedByOrNull *p3;
+struct LateRefSizedByOrNull *p4;
+struct LateRefNested *p5;
+struct MultipleLateRefs *p6;
+
+// CHECK: RecordDecl {{.*}} imported in bounds_safety_late_parsed <undeserialized declarations> struct LateRefPointer definition
+// CHECK-NEXT: |-FieldDecl {{.*}} imported in bounds_safety_late_parsed buf 'int * __counted_by(count)':'int *'
+// CHECK-NEXT: `-FieldDecl {{.*}} imported in bounds_safety_late_parsed referenced count 'int'
+
+// CHECK: RecordDecl {{.*}} imported in bounds_safety_late_parsed <undeserialized declarations> struct LateRefSized definition
+// CHECK-NEXT: |-FieldDecl {{.*}} imported in bounds_safety_late_parsed data 'int * __sized_by(size)':'int *'
+// CHECK-NEXT: `-FieldDecl {{.*}} imported in bounds_safety_late_parsed referenced size 'int'
+
+// CHECK: RecordDecl {{.*}} imported in bounds_safety_late_parsed <undeserialized declarations> struct LateRefCountedByOrNull definition
+// CHECK-NEXT: |-FieldDecl {{.*}} imported in bounds_safety_late_parsed buf 'int * __counted_by_or_null(count)':'int *'
+// CHECK-NEXT: `-FieldDecl {{.*}} imported in bounds_safety_late_parsed referenced count 'int'
+
+// CHECK: RecordDecl {{.*}} imported in bounds_safety_late_parsed <undeserialized declarations> struct LateRefSizedByOrNull definition
+// CHECK-NEXT: |-FieldDecl {{.*}} imported in bounds_safety_late_parsed data 'int * __sized_by_or_null(size)':'int *'
+// CHECK-NEXT: `-FieldDecl {{.*}} imported in bounds_safety_late_parsed referenced size 'int'
+
+// CHECK: RecordDecl {{.*}} imported in bounds_safety_late_parsed <undeserialized declarations> struct LateRefNested definition
+// CHECK: |-FieldDecl {{.*}} imported in bounds_safety_late_parsed items 'struct Inner * __counted_by(n)':'struct Inner *'
+// CHECK: `-FieldDecl {{.*}} imported in bounds_safety_late_parsed referenced n 'int'
+
+// CHECK: RecordDecl {{.*}} imported in bounds_safety_late_parsed <undeserialized declarations> struct MultipleLateRefs definition
+// CHECK-NEXT: |-FieldDecl {{.*}} imported in bounds_safety_late_parsed buf1 'int * __counted_by(count1)':'int *'
+// CHECK-NEXT: |-FieldDecl {{.*}} imported in bounds_safety_late_parsed buf2 'int * __sized_by(count2)':'int *'
+// CHECK-NEXT: |-FieldDecl {{.*}} imported in bounds_safety_late_parsed buf3 'int * __counted_by_or_null(count3)':'int *'
+// CHECK-NEXT: |-FieldDecl {{.*}} imported in bounds_safety_late_parsed buf4 'int * __sized_by_or_null(count4)':'int *'
+// CHECK-NEXT: |-FieldDecl {{.*}} imported in bounds_safety_late_parsed referenced count1 'int'
+// CHECK-NEXT: |-FieldDecl {{.*}} imported in bounds_safety_late_parsed referenced count2 'int'
+// CHECK-NEXT: |-FieldDecl {{.*}} imported in bounds_safety_late_parsed referenced count3 'int'
+// CHECK-NEXT: `-FieldDecl {{.*}} imported in bounds_safety_late_parsed referenced count4 'int'
+
+// Verify that LateParsedAttrType does not appear in the AST dump
+// CHECK-NOT: LateParsedAttr
+
+// Verify the import and variable declarations
+// CHECK: ImportDecl {{.*}} implicit bounds_safety_late_parsed
+// CHECK: VarDecl {{.*}} p1 'struct LateRefPointer *'
+// CHECK: VarDecl {{.*}} p2 'struct LateRefSized *'
+// CHECK: VarDecl {{.*}} p3 'struct LateRefCountedByOrNull *'
+// CHECK: VarDecl {{.*}} p4 'struct LateRefSizedByOrNull *'
+// CHECK: VarDecl {{.*}} p5 'struct LateRefNested *'
+// CHECK: VarDecl {{.*}} p6 'struct MultipleLateRefs *'
diff --git a/clang/test/PCH/Inputs/bounds-safety-attributed-type-late-parsed.h b/clang/test/PCH/Inputs/bounds-safety-attributed-type-late-parsed.h
new file mode 100644
index 00000000000000..c0751408045c7b
--- /dev/null
+++ b/clang/test/PCH/Inputs/bounds-safety-attributed-type-late-parsed.h
@@ -0,0 +1,58 @@
+// Header for testing late-parsed bounds-safety attributes serialization
+
+#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)))
+
+// Test where counted_by references a field declared later
+struct LateRefPointer {
+ int *__counted_by(count) buf;
+ int count;
+};
+
+// Test with sized_by referencing later field
+struct LateRefSized {
+ int *__sized_by(size) data;
+ int size;
+};
+
+// Test with counted_by_or_null referencing later field
+struct LateRefCountedByOrNull {
+ int *__counted_by_or_null(count) buf;
+ int count;
+};
+
+// Test with sized_by_or_null referencing later field
+struct LateRefSizedByOrNull {
+ int *__sized_by_or_null(size) data;
+ int size;
+};
+
+// Test with nested struct
+struct LateRefNested {
+ struct Inner {
+ int value;
+ } *__counted_by(n) items;
+ int n;
+};
+
+// Test with multiple late-parsed attributes
+struct MultipleLateRefs {
+ int *__counted_by(count1) buf1;
+ int *__sized_by(count2) buf2;
+ int *__counted_by_or_null(count3) buf3;
+ int *__sized_by_or_null(count4) buf4;
+ int count1;
+ int count2;
+ int count3;
+ int count4;
+};
+
+// Test with anonymous struct/union
+struct LateRefAnon {
+ int *__counted_by(count) buf;
+ struct {
+ int count;
+ };
+};
diff --git a/clang/test/PCH/bounds-safety-attributed-type-late-parsed.c b/clang/test/PCH/bounds-safety-attributed-type-late-parsed.c
new file mode 100644
index 00000000000000..1d77bbe13927c1
--- /dev/null
+++ b/clang/test/PCH/bounds-safety-attributed-type-late-parsed.c
@@ -0,0 +1,69 @@
+// Test serialization of late-parsed bounds-safety attributes via PCH
+// This verifies that LateParsedAttrType is transformed to CountAttributedType
+// before serialization and remains as CountAttributedType after deserialization.
+
+// RUN: %clang_cc1 -fexperimental-late-parse-attributes -include %S/Inputs/bounds-safety-attributed-type-late-parsed.h -fsyntax-only -verify %s
+
+// Test with pch.
+// RUN: %clang_cc1 -fexperimental-late-parse-attributes -emit-pch -o %t %S/Inputs/bounds-safety-attributed-type-late-parsed.h
+// RUN: %clang_cc1 -fexperimental-late-parse-attributes -include-pch %t -fsyntax-only -verify %s
+// RUN: %clang_cc1 -fexperimental-late-parse-attributes -include-pch %t -ast-print %s | FileCheck %s --check-prefix PRINT
+// RUN: %clang_cc1 -fexperimental-late-parse-attributes -include-pch %t -ast-dump-all %s | FileCheck %s --check-prefix DUMP
+// expected-no-diagnostics
+
+// PRINT: struct LateRefPointer {
+// PRINT-NEXT: int * __counted_by(count)buf;
+// PRINT-NEXT: int count;
+// PRINT-NEXT: };
+
+// PRINT: struct LateRefSized {
+// PRINT-NEXT: int * __sized_by(size)data;
+// PRINT-NEXT: int size;
+// PRINT-NEXT: };
+
+// PRINT: struct LateRefCountedByOrNull {
+// PRINT-NEXT: int * __counted_by_or_null(count)buf;
+// PRINT-NEXT: int count;
+// PRINT-NEXT: };
+
+// PRINT: struct LateRefSizedByOrNull {
+// PRINT-NEXT: int * __sized_by_or_null(size)data;
+// PRINT-NEXT: int size;
+// PRINT-NEXT: };
+
+// DUMP: RecordDecl {{.*}} imported <undeserialized declarations> struct LateRefPointer definition
+// DUMP-NEXT: |-FieldDecl {{.*}} imported buf 'int * __counted_by(count)':'int *'
+// DUMP-NEXT: `-FieldDecl {{.*}} imported referenced count 'int'
+
+// DUMP: RecordDecl {{.*}} imported <undeserialized declarations> struct LateRefSized definition
+// DUMP-NEXT: |-FieldDecl {{.*}} imported data 'int * __sized_by(size)':'int *'
+// DUMP-NEXT: `-FieldDecl {{.*}} imported referenced size 'int'
+
+// DUMP: RecordDecl {{.*}} imported <undeserialized declarations> struct LateRefCountedByOrNull definition
+// DUMP-NEXT: |-FieldDecl {{.*}} imported buf 'int * __counted_by_or_null(count)':'int *'
+// DUMP-NEXT: `-FieldDecl {{.*}} imported referenced count 'int'
+
+// DUMP: RecordDecl {{.*}} imported <undeserialized declarations> struct LateRefSizedByOrNull definition
+// DUMP-NEXT: |-FieldDecl {{.*}} imported data 'int * __sized_by_or_null(size)':'int *'
+// DUMP-NEXT: `-FieldDecl {{.*}} imported referenced size 'int'
+
+// DUMP: RecordDecl {{.*}} imported <undeserialized declarations> struct LateRefNested definition
+// DUMP: |-FieldDecl {{.*}} imported items 'struct Inner * __counted_by(n)':'struct Inner *'
+// DUMP: `-FieldDecl {{.*}} imported referenced n 'int'
+
+// DUMP: RecordDecl {{.*}} imported <undeserialized declarations> struct MultipleLateRefs definition
+// DUMP-NEXT: |-FieldDecl {{.*}} imported buf1 'int * __counted_by(count1)':'int *'
+// DUMP-NEXT: |-FieldDecl {{.*}} imported buf2 'int * __sized_by(count2)':'int *'
+// DUMP-NEXT: |-FieldDecl {{.*}} imported buf3 'int * __counted_by_or_null(count3)':'int *'
+// DUMP-NEXT: |-FieldDecl {{.*}} imported buf4 'int * __sized_by_or_null(count4)':'int *'
+// DUMP-NEXT: |-FieldDecl {{.*}} imported referenced count1 'int'
+// DUMP-NEXT: |-FieldDecl {{.*}} imported referenced count2 'int'
+// DUMP-NEXT: |-FieldDecl {{.*}} imported referenced count3 'int'
+// DUMP-NEXT: `-FieldDecl {{.*}} imported referenced count4 'int'
+
+// DUMP: RecordDecl {{.*}} imported <undeserialized declarations> struct LateRefAnon definition
+// DUMP-NEXT: |-FieldDecl {{.*}} imported buf 'int * __counted_by(count)':'int *'
+// DUMP: `-IndirectFieldDecl {{.*}} imported implicit referenced count 'int'
+
+// Verify that LateParsedAttrType does not appear in the AST dump
+// DUMP-NOT: LateParsedAttr
diff --git a/clang/test/Sema/attr-bounds-safety-function-ptr-param.c b/clang/test/Sema/attr-bounds-safety-function-ptr-param.c
new file mode 100644
index 00000000000000..091220e3139587
--- /dev/null
+++ b/clang/test/Sema/attr-bounds-safety-function-ptr-param.c
@@ -0,0 +1,173 @@
+// XFAIL: *
+// FIXME: https://github.com/llvm/llvm-project/issues/166454
+
+// RUN: %clang_cc1 -fsyntax-only -verify %s
+// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -verify %s
+
+#define __counted_by(N) __attribute__((counted_by(N)))
+#define __counted_by_or_null(N) __attribute__((counted_by_or_null(N)))
+#define __sized_by(N) __attribute__((sized_by(N)))
+#define __sized_by_or_null(N) __attribute__((sized_by_or_null(N)))
+
+//==============================================================================
+// Test bounds safety attributes on function pointer parameters
+//==============================================================================
+
+struct counted_by_function_pointer_param {
+ // expected-error at +1{{'counted_by' attribute cannot be applied to a parameter in a function pointer type}}
+ int (*callback)(int *__counted_by(len));
+ int len;
+};
+
+struct counted_by_or_null_function_pointer_param {
+ // expected-error at +1{{'counted_by_or_null' attribute cannot be applied to a parameter in a function pointer type}}
+ int (*callback)(int *__counted_by_or_null(len));
+ int len;
+};
+
+struct sized_by_function_pointer_param {
+ // expected-error at +1{{'sized_by' attribute cannot be applied to a parameter in a function pointer type}}
+ int (*callback)(char *__sized_by(len));
+ int len;
+};
+
+struct sized_by_or_null_function_pointer_param {
+ // expected-error at +1{{'sized_by_or_null' attribute cannot be applied to a parameter in a function pointer type}}
+ int (*callback)(char *__sized_by_or_null(len));
+ int len;
+};
+
+//==============================================================================
+// Test multiple parameters with bounds safety attributes
+//==============================================================================
+
+struct multiple_params_with_bounds_safety {
+ // expected-error at +1{{'counted_by' attribute cannot be applied to a parameter in a function pointer type}}
+ int (*multi_callback)(int *__counted_by(len1), char *data, int len1);
+ int len1;
+};
+
+struct mixed_bounds_safety_params {
+ // expected-error at +2{{'counted_by' attribute cannot be applied to a parameter in a function pointer type}}
+ // expected-error at +1{{'sized_by_or_null' attribute cannot be applied to a parameter in a function pointer type}}
+ int (*mixed_callback)(int *__counted_by(count), char *__sized_by_or_null(size), int count, int size);
+ int count;
+ int size;
+};
+
+//==============================================================================
+// Test cases that do not require late parsing (count field defined before use)
+//==============================================================================
+
+struct counted_by_no_late_parse {
+ int len;
+ // expected-error at +1{{'counted_by' attribute cannot be applied to a parameter in a function pointer type}}
+ int (*callback)(int *__counted_by(len));
+};
+
+struct counted_by_or_null_no_late_parse {
+ int len;
+ // expected-error at +1{{'counted_by_or_null' attribute cannot be applied to a parameter in a function pointer type}}
+ int (*callback)(int *__counted_by_or_null(len));
+};
+
+struct sized_by_no_late_parse {
+ int len;
+ // expected-error at +1{{'sized_by' attribute cannot be applied to a parameter in a function pointer type}}
+ int (*callback)(char *__sized_by(len));
+};
+
+struct sized_by_or_null_no_late_parse {
+ int len;
+ // expected-error at +1{{'sized_by_or_null' attribute cannot be applied to a parameter in a function pointer type}}
+ int (*callback)(char *__sized_by_or_null(len));
+};
+
+//==============================================================================
+// Test nested function pointer types
+//==============================================================================
+
+struct nested_function_pointer_with_bounds_safety {
+ // expected-error at +1{{'counted_by' attribute cannot be applied to a parameter in a function pointer type}}
+ int (*outer_callback)(int (*inner)(int *__counted_by(len)), int len);
+ int len;
+};
+
+//==============================================================================
+// Test struct members with anonymous structs/unions (no late parsing needed)
+//==============================================================================
+
+struct with_anonymous_struct_no_late_parse {
+ int len;
+ // expected-error at +1{{'counted_by' attribute cannot be applied to a parameter in a function pointer type}}
+ int (*callback)(int *__counted_by(len));
+};
+
+struct with_anonymous_union_no_late_parse {
+ union {
+ int len;
+ float f_len;
+ };
+ // expected-error at +1{{'counted_by_or_null' attribute cannot be applied to a parameter in a function pointer type}}
+ int (*callback)(int *__counted_by_or_null(len));
+};
+
+//==============================================================================
+// Test with different parameter positions
+//==============================================================================
+
+struct first_param_bounds_safety_no_late_parse {
+ int count;
+ // expected-error at +1{{'counted_by' attribute cannot be applied to a parameter in a function pointer type}}
+ int (*callback)(int *__counted_by(count), void *data, int extra);
+};
+
+struct middle_param_bounds_safety_no_late_parse {
+ int size;
+ // expected-error at +1{{'sized_by' attribute cannot be applied to a parameter in a function pointer type}}
+ int (*callback)(void *prefix, char *__sized_by(size), int suffix);
+};
+
+struct last_param_bounds_safety_no_late_parse {
+ int len;
+ // expected-error at +1{{'counted_by_or_null' attribute cannot be applied to a parameter in a function pointer type}}
+ int (*callback)(int a, float b, int *__counted_by_or_null(len));
+};
+
+//==============================================================================
+// Test with const and volatile qualifiers
+//==============================================================================
+
+struct const_param_bounds_safety_no_late_parse {
+ int count;
+ // expected-error at +1{{'counted_by' attribute cannot be applied to a parameter in a function pointer type}}
+ int (*callback)(const int *__counted_by(count));
+};
+
+struct volatile_param_bounds_safety_no_late_parse {
+ int size;
+ // expected-error at +1{{'sized_by_or_null' attribute cannot be applied to a parameter in a function pointer type}}
+ int (*callback)(volatile char *__sized_by_or_null(size));
+};
+
+struct const_volatile_param_bounds_safety_no_late_parse {
+ int len;
+ // expected-error at +1{{'counted_by_or_null' attribute cannot be applied to a parameter in a function pointer type}}
+ int (*callback)(const volatile int *__counted_by_or_null(len));
+};
+
+//==============================================================================
+// Test with multiple function pointers in same struct
+//==============================================================================
+
+struct multiple_function_pointers_no_late_parse {
+ int len1, len2, size1, size2;
+ // expected-error at +1{{'counted_by' attribute cannot be applied to a parameter in a function pointer type}}
+ int (*callback1)(int *__counted_by(len1));
+ // expected-error at +1{{'counted_by_or_null' attribute cannot be applied to a parameter in a function pointer type}}
+ int (*callback2)(int *__counted_by_or_null(len2));
+ // expected-error at +1{{'sized_by' attribute cannot be applied to a parameter in a function pointer type}}
+ void (*callback3)(char *__sized_by(size1));
+ // expected-error at +1{{'sized_by_or_null' attribute cannot be applied to a parameter in a function pointer type}}
+ void (*callback4)(char *__sized_by_or_null(size2));
+};
diff --git a/clang/test/Sema/attr-counted-by-late-parsed-regressions.c b/clang/test/Sema/attr-counted-by-late-parsed-regressions.c
new file mode 100644
index 00000000000000..99c196f4232b5b
--- /dev/null
+++ b/clang/test/Sema/attr-counted-by-late-parsed-regressions.c
@@ -0,0 +1,104 @@
+// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -verify %s
+
+#define __counted_by(N) __attribute__((counted_by(N)))
+
+// A late-parsed type attribute in declarator-specifier position after a nested
+// record definition used to be registered both in the enclosing record's
+// field-attribute list and in its late-parsed-type-attribute list, so its
+// cached tokens were parsed and freed twice -- an assertion / use-after-free.
+// The point of this test is that it no longer crashes.
+//
+// FIXME: 'counted_by' on a non-pointer (here struct-typed) field should be
+// diagnosed as "only applies to pointers or C99 flexible array members"; the
+// late path currently accepts it silently. That missing diagnostic is a
+// separate issue from the double-free guarded here.
+struct nested_record_declspec_attr {
+ struct inner1 {
+ int x;
+ int *p;
+ } __counted_by(x) f;
+ int y;
+};
+
+// A 'counted_by' type attribute on a free-function parameter has no enclosing
+// record to complete it. Late-parsing it left a CountAttributedType with a
+// null count expression in the AST, which crashed on serialization / PCH
+// round-trip. Parameters now fall back to eager handling, so the attribute is
+// resolved (or rejected) immediately instead of escaping unfinished.
+
+// Forward reference: eager handling can't see 'n' yet, so it is diagnosed
+// rather than silently building a null-count type.
+void fwd_ref_param(int *__counted_by(n) p, // expected-error {{use of undeclared identifier 'n'}}
+ int n);
+
+// FIXME: counted_by on a function parameter isn't supported yet; the eager
+// decl-attribute path rejects it. What matters for this regression is that it
+// is diagnosed here, not left as an unfinished type for a later crash.
+void bwd_ref_param(int n,
+ int *__counted_by(n) p); // expected-error {{'counted_by' attribute only applies to non-static data members}}
+
+// A declaration-specifier-position attribute is shared by every declarator in
+// the declaration, and ConvertDeclSpecToType walks the DeclSpec's late-attribute
+// list once per declarator. Building a fresh (deliberately un-uniqued)
+// CountAttributedType on each walk left every field but the last holding a node
+// whose count expression was never filled in -- silently, with no diagnostic --
+// and any use of such a field then tripped FieldDecl::findCountedByField's
+// unconditional cast<DeclRefExpr>. The attribute now reuses one node for all
+// declarators, matching the eager path where uniquing has the same effect.
+typedef int *shared_ptr_ty;
+
+struct shared_declspec_attr {
+ int n;
+ shared_ptr_ty __counted_by(n) a, b, c;
+};
+
+// Exercising the *earlier* declarators is the point: 'c' was always fine.
+void use_shared_declspec_attr(struct shared_declspec_attr *s) {
+ (void)__builtin_counted_by_ref(s->a);
+ (void)__builtin_counted_by_ref(s->b);
+ (void)__builtin_counted_by_ref(s->c);
+}
+
+// The same, with the count declared after the fields, so the attribute really is
+// late parsed rather than resolved eagerly.
+struct shared_declspec_attr_fwd {
+ shared_ptr_ty __counted_by(n) a, b;
+ int n;
+};
+
+void use_shared_declspec_attr_fwd(struct shared_declspec_attr_fwd *s) {
+ (void)__builtin_counted_by_ref(s->a);
+}
+
+// Each field is still checked in its own declaration context, so a shared
+// attribute reports once per field rather than once per attribute.
+union shared_declspec_attr_in_union {
+ int n;
+ // expected-error at +2 {{'counted_by' cannot be applied to a union member}}
+ // expected-error at +1 {{'counted_by' cannot be applied to a union member}}
+ shared_ptr_ty __counted_by(n) a, b;
+};
+
+// A grouping-paren declarator whose base type is already a pointer/array (via a
+// typedef) used to late-parse the attribute even at file scope, where there is
+// no enclosing record to complete it -- leaving a CountAttributedType with a
+// null count expression in the AST (and skipping the "non-static data members"
+// diagnostic entirely). ParseParenDeclarator now late-parses only inside a
+// record, so at file scope the attribute is handled eagerly and rejected.
+typedef int *ptr_ty;
+typedef int arr_ty[4];
+int global_count;
+ptr_ty (__counted_by(global_count) file_ptr); // expected-error {{'counted_by' attribute only applies to non-static data members}}
+arr_ty (__counted_by(global_count) file_arr); // expected-error {{'counted_by' attribute only applies to non-static data members}}
+
+// A nested counted_by is diagnosed and dropped while the declarator is built,
+// which orphans the incomplete CountAttributedType. The completion pass used to
+// notice that only after re-parsing the argument, so a bad count name produced a
+// second, cascading diagnostic for an attribute that was already rejected. The
+// rejection is now recorded when the node is dropped, so the argument is never
+// re-parsed.
+struct nested_with_unresolvable_count {
+ int n;
+ // expected-error at +1 {{'counted_by' attribute on nested pointer type is not allowed}}
+ int *__counted_by(does_not_exist) *pp;
+};
diff --git a/clang/test/Sema/attr-counted-by-weird-type-positions-late-parsed.c b/clang/test/Sema/attr-counted-by-weird-type-positions-late-parsed.c
new file mode 100644
index 00000000000000..0728c8623c2028
--- /dev/null
+++ b/clang/test/Sema/attr-counted-by-weird-type-positions-late-parsed.c
@@ -0,0 +1,456 @@
+// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -verify %s
+
+#define __counted_by(f) __attribute__((counted_by(f)))
+
+// ============================================================================
+// SIMPLE POINTER: int *buf
+// ============================================================================
+
+// Position: after *, before identifier
+// Applies to `int *`.
+struct ptr_after_star {
+ int *__counted_by(count) buf;
+ int count;
+};
+
+// Position: before type specifier
+// Applies to the top-level type.
+struct ptr_before_type {
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ __counted_by(count) int *buf;
+ int count;
+};
+
+// Position: after type, before *
+// Applies to `int`.
+struct ptr_after_type {
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ int __counted_by(count) *buf;
+ int count;
+};
+
+// Position: after identifier
+// Applies to the top-level type.
+struct ptr_after_ident {
+ int *buf __counted_by(count);
+ int count;
+};
+
+// ============================================================================
+// TYPEDEF POINTER: ptr_to_int_t buf
+// ============================================================================
+
+typedef int * ptr_to_int_t;
+
+// Position: after typedef name, before identifier
+// Applies to `ptr_to_int_t`.
+struct typedef_after_type {
+ ptr_to_int_t __counted_by(count) buf;
+ int count;
+};
+
+// Position: before typedef name
+// Applies to the top-level type.
+struct typedef_before_type {
+ __counted_by(count) ptr_to_int_t buf;
+ int count;
+};
+
+// Position: after identifier
+// Applies to the top-level type.
+struct typedef_after_ident {
+ ptr_to_int_t buf __counted_by(count);
+ int count;
+};
+
+// ============================================================================
+// POINTER TO ARRAY: int (*buf)[4]
+// ============================================================================
+
+// Position: after type, before (*...)
+// Applies to `int`.
+struct ptr_to_arr_after_type {
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ int __counted_by(count) (* buf)[4];
+ int count;
+};
+
+// Position: before type
+// Applies to the top-level type.
+struct ptr_to_arr_before_type {
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ __counted_by(count) int (* buf)[4];
+ int count;
+};
+
+// Position: after *, before identifier (inside parens)
+// Applies to `int (*)[4]`.
+struct ptr_to_arr_after_star {
+ int (* __counted_by(count) buf)[4];
+ int count;
+};
+
+// Position: after identifier, before ) (inside parens)
+// Invalid position - causes parse error
+struct ptr_to_arr_after_ident {
+ int (*buf __counted_by(count))[4]; // Invalid position
+ // expected-error at -1{{expected ')'}}
+ // expected-note at -2{{to match this '('}}
+ int count;
+};
+
+// Position: after [4]
+// Applies to the top-level type.
+struct ptr_to_arr_after_brackets {
+ int (* buf)[4] __counted_by(count);
+ int count;
+};
+
+// Position: after (, before *
+struct ptr_to_arr_after_lparen {
+ // expected-error at +1{{'counted_by' attribute on nested pointer type is not allowed}}
+ int (__counted_by(count) *buf)[4];
+ int count;
+};
+
+// Position: inside [4]
+struct ptr_to_arr_inside_brackets {
+ int (* buf)[4 __counted_by(count)]; // Invalid syntax
+ // expected-error at -1{{expected ']'}}
+ // expected-note at -2{{to match this '['}}
+ int count;
+};
+
+// Position: before [4]
+struct ptr_to_arr_before_brackets {
+ // expected-error at +1{{expected ';' at end of declaration list}}
+ int (* buf) __counted_by(count) [4]; // Invalid syntax
+ int count;
+};
+
+// Position: double parens, after ((, before *
+struct ptr_to_arr_double_paren1 {
+ // expected-error at +1{{'counted_by' attribute on nested pointer type is not allowed}}
+ int ((__counted_by(count) * buf))[4];
+ int count;
+};
+
+// Position: double parens, after *, before identifier
+struct ptr_to_arr_double_paren2 {
+ int ((* __counted_by(count) buf))[4];
+ int count;
+};
+
+// ============================================================================
+// POINTER TO ARRAY WITH QUALIFIERS
+// ============================================================================
+
+// const pointer
+struct ptr_to_arr_const_ptr1 {
+ int (* const __counted_by(count) buf)[4];
+ int count;
+};
+
+struct ptr_to_arr_const_ptr2 {
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ int __counted_by(count) (* const buf)[4];
+ int count;
+};
+
+// pointer to const
+struct ptr_to_arr_ptr_to_const {
+ const int (* __counted_by(count) buf)[4];
+ int count;
+};
+
+struct ptr_to_arr_ptr_to_const2 {
+ int const (* __counted_by(count) buf)[4];
+ int count;
+};
+
+// restrict pointer
+struct ptr_to_arr_restrict1 {
+ int (* __restrict __counted_by(count) buf)[4];
+ int count;
+};
+
+struct ptr_to_arr_restrict2 {
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ int __counted_by(count) (* __restrict buf)[4];
+ int count;
+};
+
+// ============================================================================
+// POINTER TO MULTI-DIMENSIONAL ARRAY: int (*buf)[4][8]
+// ============================================================================
+
+struct ptr_to_multidim_arr_after_type {
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ int __counted_by(count) (* buf)[4][8];
+ int count;
+};
+
+struct ptr_to_multidim_arr_after_star {
+ int (* __counted_by(count) buf)[4][8];
+ int count;
+};
+
+struct ptr_to_multidim_arr_middle {
+ // expected-error at +1{{expected ';' at end of declaration list}}
+ int (* buf)[4] __counted_by(count) [8]; // Invalid position
+ int count;
+};
+
+struct ptr_to_multidim_arr_after_all {
+ int (* buf)[4][8] __counted_by(count);
+ // This doesn't trigger an error - the attribute applies to the pointer
+ int count;
+};
+
+// ============================================================================
+// ARRAY OF POINTERS TO ARRAY: int (*buf[10])[4]
+// ============================================================================
+
+struct arr_of_ptr_to_arr_after_type {
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ int __counted_by(count) (* buf[10])[4];
+ int count;
+};
+
+struct arr_of_ptr_to_arr_after_star {
+ // expected-error at +1{{'counted_by' attribute on nested pointer type is not allowed}}
+ int (* __counted_by(count) buf[10])[4];
+ int count;
+};
+
+struct arr_of_ptr_to_arr_middle {
+ // expected-error at +2{{'counted_by' on arrays only applies to C99 flexible array members}}
+ // expected-error at +1{{expected ';' at end of declaration list}}
+ int (* buf[10]) __counted_by(count) [4]; // Invalid position
+ int count;
+};
+
+struct arr_of_ptr_to_arr_inside_first_brackets {
+ int (* buf __counted_by(count) [10])[4];
+ // expected-error at -1{{expected ')'}}
+ // expected-note at -2{{to match this '('}}
+ int count;
+};
+
+// ============================================================================
+// TYPEDEF ARRAY: arr4_t *buf where arr4_t is int[4]
+// ============================================================================
+
+typedef int arr4_t[4];
+
+struct typedef_arr_before_type {
+ // expected-error at +1{{'counted_by' attribute on nested pointer type is not allowed}}
+ __counted_by(count) arr4_t * buf;
+ int count;
+};
+
+struct typedef_arr_after_type {
+ // expected-error at +1{{'counted_by' attribute on nested pointer type is not allowed}}
+ arr4_t __counted_by(count) * buf;
+ int count;
+};
+
+struct typedef_arr_after_star {
+ arr4_t * __counted_by(count) buf;
+ int count;
+};
+
+// ============================================================================
+// FUNCTION POINTER: int (*buf)(void)
+// ============================================================================
+
+// Position: after *, before identifier
+struct fptr_after_star {
+ // expected-error at +1{{'counted_by' cannot be applied to a pointer with pointee of unknown size because 'int (void)' is a function type}}
+ int (* __counted_by(count) buf)(void);
+ int count;
+};
+
+// Position: after (, before *
+struct fptr_after_lparen {
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ int (__counted_by(count) *buf)(void);
+ int count;
+};
+
+// ============================================================================
+// _ATOMIC POINTER VARIATIONS
+// ============================================================================
+
+// _Atomic(int *) - atomic pointer type
+struct atomic_ptr_type {
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ _Atomic(int *) __counted_by(count) buf;
+ int count;
+};
+
+// Attribute inside _Atomic (likely invalid)
+struct atomic_ptr_attr_inside {
+ // expected-error at +1{{use of undeclared identifier 'count'}}
+ _Atomic(int *__counted_by(count)) buf;
+ int count;
+};
+
+struct atomic_ptr_attr_inside_no_forward_ref {
+ int count;
+ // FIXME: should not be allowed
+ _Atomic(int *__counted_by(count)) buf;
+};
+
+struct atomic_ptr_attr_after {
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ __counted_by(count) _Atomic(int *) buf;
+ int count;
+};
+
+struct atomic_ptr_attr_after_no_forward_ref {
+ int count;
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ __counted_by(count) _Atomic(int *) buf;
+};
+
+// _Atomic int * - could be atomic int or atomic pointer
+struct atomic_ambiguous {
+ _Atomic int * __counted_by(count) buf;
+ int count;
+};
+
+// int *_Atomic - atomic pointer (unambiguous)
+struct atomic_ptr_unambiguous1 {
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ int *_Atomic __counted_by(count) buf;
+ int count;
+};
+
+// __counted_by before _Atomic
+struct atomic_ptr_attr_before_atomic1 {
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ int *__counted_by(count) _Atomic buf;
+ int count;
+};
+
+// __counted_by before * _Atomic
+struct atomic_ptr_attr_before_atomic2 {
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ int __counted_by(count) * _Atomic buf;
+ int count;
+};
+
+// _Atomic before type
+struct atomic_ptr_atomic_first1 {
+ _Atomic int *__counted_by(count) buf;
+ int count;
+};
+
+// _Atomic before type, attribute after *
+struct atomic_ptr_atomic_first2 {
+ _Atomic int * __counted_by(count) buf;
+ int count;
+};
+
+// __counted_by at the end
+struct atomic_ptr_attr_at_end1 {
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ int *_Atomic buf __counted_by(count);
+ int count;
+};
+
+// __counted_by at the end with space
+struct atomic_ptr_attr_at_end2 {
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ int * _Atomic buf __counted_by(count);
+ int count;
+};
+
+// ============================================================================
+// _ATOMIC POINTER TO ARRAY
+// ============================================================================
+
+struct atomic_ptr_to_arr1 {
+ _Atomic int (* __counted_by(count) buf)[4];
+ int count;
+};
+
+struct atomic_ptr_to_arr2 {
+ // expected-error at +3{{expected a type}}
+ // expected-error at +2{{use of undeclared identifier 'count'}}
+ // expected-error at +1{{expected member name or ';' after declaration specifiers}}
+ int _Atomic (* __counted_by(count) buf)[4];
+ int count;
+};
+
+struct atomic_ptr_to_arr3 {
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ int (* _Atomic __counted_by(count) buf)[4];
+ int count;
+};
+
+// ============================================================================
+// ATOMIC WITH CONST/VOLATILE/RESTRICT QUALIFIERS
+// ============================================================================
+
+// const _Atomic pointer
+struct atomic_const_ptr1 {
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ int * const _Atomic __counted_by(count) buf;
+ int count;
+};
+
+struct atomic_const_ptr2 {
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ int * _Atomic const __counted_by(count) buf;
+ int count;
+};
+
+struct atomic_const_ptr3 {
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ const int * _Atomic __counted_by(count) buf;
+ int count;
+};
+
+// volatile _Atomic pointer
+struct atomic_volatile_ptr1 {
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ int * volatile _Atomic __counted_by(count) buf;
+ int count;
+};
+
+struct atomic_volatile_ptr2 {
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ int * _Atomic volatile __counted_by(count) buf;
+ int count;
+};
+
+// restrict _Atomic pointer
+struct atomic_restrict_ptr1 {
+ // expected-error at +2{{restrict requires a pointer or reference ('_Atomic(int *)' is invalid)}}
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ int * __restrict _Atomic __counted_by(count) buf;
+ int count;
+};
+
+struct atomic_restrict_ptr2 {
+ // expected-error at +2{{restrict requires a pointer or reference ('_Atomic(int *)' is invalid)}}
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ int * _Atomic __restrict __counted_by(count) buf;
+ int count;
+};
+
+// Combined qualifiers
+struct atomic_const_volatile_ptr {
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ int * const volatile _Atomic __counted_by(count) buf;
+ int count;
+};
+
+struct atomic_all_qualifiers {
+ // expected-error at +2{{restrict requires a pointer or reference ('_Atomic(int *)' is invalid)}}
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ int * const volatile __restrict _Atomic __counted_by(count) buf;
+ int count;
+};
diff --git a/clang/test/Sema/attr-counted-by-weird-type-positions.c b/clang/test/Sema/attr-counted-by-weird-type-positions.c
new file mode 100644
index 00000000000000..2668ab1e18346f
--- /dev/null
+++ b/clang/test/Sema/attr-counted-by-weird-type-positions.c
@@ -0,0 +1,454 @@
+// RUN: %clang_cc1 -fsyntax-only -verify %s
+
+#define __counted_by(f) __attribute__((counted_by(f)))
+
+// ============================================================================
+// SIMPLE POINTER: int *buf
+// ============================================================================
+
+// Position: after *, before identifier
+// Applies to `int *`.
+struct ptr_after_star {
+ int count;
+ int *__counted_by(count) buf;
+};
+
+// Position: before type specifier
+// Applies to the top-level type.
+struct ptr_before_type {
+ int count;
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ __counted_by(count) int *buf;
+};
+
+// Position: after type, before *
+// Applies to `int`.
+struct ptr_after_type {
+ int count;
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ int __counted_by(count) *buf;
+};
+
+// Position: after identifier
+// Applies to the top-level type.
+struct ptr_after_ident {
+ int count;
+ int *buf __counted_by(count);
+};
+
+// ============================================================================
+// TYPEDEF POINTER: ptr_to_int_t buf
+// ============================================================================
+
+typedef int * ptr_to_int_t;
+
+// Position: after typedef name, before identifier
+// Applies to `ptr_to_int_t`.
+struct typedef_after_type {
+ int count;
+ ptr_to_int_t __counted_by(count) buf;
+};
+
+// Position: before typedef name
+// Applies to the top-level type.
+struct typedef_before_type {
+ int count;
+ __counted_by(count) ptr_to_int_t buf;
+};
+
+// Position: after identifier
+// Applies to the top-level type.
+struct typedef_after_ident {
+ int count;
+ ptr_to_int_t buf __counted_by(count);
+};
+
+// ============================================================================
+// POINTER TO ARRAY: int (*buf)[4]
+// ============================================================================
+
+// Position: after type, before (*...)
+// Applies to `int`.
+struct ptr_to_arr_after_type {
+ int count;
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ int __counted_by(count) (* buf)[4];
+};
+
+// Position: before type
+// Applies to the top-level type.
+struct ptr_to_arr_before_type {
+ int count;
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ __counted_by(count) int (* buf)[4];
+};
+
+// Position: after *, before identifier (inside parens)
+// Applies to `int (*)[4]`.
+struct ptr_to_arr_after_star {
+ int count;
+ int (* __counted_by(count) buf)[4];
+};
+
+// Position: after identifier, before ) (inside parens)
+// Invalid position - causes parse error
+struct ptr_to_arr_after_ident {
+ int count;
+ int (*buf __counted_by(count))[4]; // Invalid position
+ // expected-error at -1{{expected ')'}}
+ // expected-note at -2{{to match this '('}}
+};
+
+// Position: after [4]
+// Applies to the top-level type.
+struct ptr_to_arr_after_brackets {
+ int count;
+ int (* buf)[4] __counted_by(count);
+};
+
+// Position: after (, before *
+struct ptr_to_arr_after_lparen {
+ int count;
+ // expected-error at +1{{'counted_by' attribute on nested pointer type is not allowed}}
+ int (__counted_by(count) *buf)[4];
+};
+
+// Position: inside [4]
+struct ptr_to_arr_inside_brackets {
+ int count;
+ int (* buf)[4 __counted_by(count)]; // Invalid syntax
+ // expected-error at -1{{expected ']'}}
+ // expected-note at -2{{to match this '['}}
+};
+
+// Position: before [4]
+struct ptr_to_arr_before_brackets {
+ int count;
+ // expected-error at +1{{expected ';' at end of declaration list}}
+ int (* buf) __counted_by(count) [4]; // Invalid syntax
+};
+
+// Position: double parens, after ((, before *
+struct ptr_to_arr_double_paren1 {
+ int count;
+ // expected-error at +1{{'counted_by' attribute on nested pointer type is not allowed}}
+ int ((__counted_by(count) * buf))[4];
+};
+
+// Position: double parens, after *, before identifier
+struct ptr_to_arr_double_paren2 {
+ int count;
+ int ((* __counted_by(count) buf))[4];
+};
+
+// ============================================================================
+// POINTER TO ARRAY WITH QUALIFIERS
+// ============================================================================
+
+// const pointer
+struct ptr_to_arr_const_ptr1 {
+ int count;
+ int (* const __counted_by(count) buf)[4];
+};
+
+struct ptr_to_arr_const_ptr2 {
+ int count;
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ int __counted_by(count) (* const buf)[4];
+};
+
+// pointer to const
+struct ptr_to_arr_ptr_to_const {
+ int count;
+ const int (* __counted_by(count) buf)[4];
+};
+
+struct ptr_to_arr_ptr_to_const2 {
+ int count;
+ int const (* __counted_by(count) buf)[4];
+};
+
+// restrict pointer
+struct ptr_to_arr_restrict1 {
+ int count;
+ int (* __restrict __counted_by(count) buf)[4];
+};
+
+struct ptr_to_arr_restrict2 {
+ int count;
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ int __counted_by(count) (* __restrict buf)[4];
+};
+
+// ============================================================================
+// POINTER TO MULTI-DIMENSIONAL ARRAY: int (*buf)[4][8]
+// ============================================================================
+
+struct ptr_to_multidim_arr_after_type {
+ int count;
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ int __counted_by(count) (* buf)[4][8];
+};
+
+struct ptr_to_multidim_arr_after_star {
+ int count;
+ int (* __counted_by(count) buf)[4][8];
+};
+
+struct ptr_to_multidim_arr_middle {
+ int count;
+ // expected-error at +1{{expected ';' at end of declaration list}}
+ int (* buf)[4] __counted_by(count) [8]; // Invalid position
+};
+
+struct ptr_to_multidim_arr_after_all {
+ int count;
+ int (* buf)[4][8] __counted_by(count);
+ // This doesn't trigger an error - the attribute applies to the pointer
+};
+
+// ============================================================================
+// ARRAY OF POINTERS TO ARRAY: int (*buf[10])[4]
+// ============================================================================
+
+struct arr_of_ptr_to_arr_after_type {
+ int count;
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ int __counted_by(count) (* buf[10])[4];
+};
+
+struct arr_of_ptr_to_arr_after_star {
+ int count;
+ // expected-error at +1{{'counted_by' attribute on nested pointer type is not allowed}}
+ int (* __counted_by(count) buf[10])[4];
+};
+
+struct arr_of_ptr_to_arr_middle {
+ int count;
+ // expected-error at +2{{'counted_by' on arrays only applies to C99 flexible array members}}
+ // expected-error at +1{{expected ';' at end of declaration list}}
+ int (* buf[10]) __counted_by(count) [4]; // Invalid position
+};
+
+struct arr_of_ptr_to_arr_inside_first_brackets {
+ int count;
+ int (* buf __counted_by(count) [10])[4];
+ // expected-error at -1{{expected ')'}}
+ // expected-note at -2{{to match this '('}}
+};
+
+// ============================================================================
+// TYPEDEF ARRAY: arr4_t *buf where arr4_t is int[4]
+// ============================================================================
+
+typedef int arr4_t[4];
+
+struct typedef_arr_before_type {
+ int count;
+ // expected-error at +1{{'counted_by' attribute on nested pointer type is not allowed}}
+ __counted_by(count) arr4_t * buf;
+};
+
+struct typedef_arr_after_type {
+ int count;
+ // expected-error at +1{{'counted_by' attribute on nested pointer type is not allowed}}
+ arr4_t __counted_by(count) * buf;
+};
+
+struct typedef_arr_after_star {
+ int count;
+ arr4_t * __counted_by(count) buf;
+};
+
+// ============================================================================
+// FUNCTION POINTER: int (*buf)(void)
+// ============================================================================
+
+// Position: after *, before identifier
+struct fptr_after_star {
+ int count;
+ // expected-error at +1{{'counted_by' cannot be applied to a pointer with pointee of unknown size because 'int (void)' is a function type}}
+ int (* __counted_by(count) buf)(void);
+};
+
+// Position: after (, before *
+struct fptr_after_lparen {
+ int count;
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ int (__counted_by(count) *buf)(void);
+};
+
+// ============================================================================
+// _ATOMIC POINTER VARIATIONS
+// ============================================================================
+
+// _Atomic(int *) - atomic pointer type
+struct atomic_ptr_type {
+ int count;
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ _Atomic(int *) __counted_by(count) buf;
+};
+
+// Attribute inside _Atomic (likely invalid)
+struct atomic_ptr_attr_inside {
+ int count;
+ _Atomic(int *__counted_by(count)) buf;
+};
+
+struct atomic_ptr_attr_inside_no_forward_ref {
+ int count;
+ // FIXME: should not be allowed
+ _Atomic(int *__counted_by(count)) buf;
+};
+
+struct atomic_ptr_attr_after {
+ int count;
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ __counted_by(count) _Atomic(int *) buf;
+};
+
+struct atomic_ptr_attr_after_no_forward_ref {
+ int count;
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ __counted_by(count) _Atomic(int *) buf;
+};
+
+// _Atomic int * - could be atomic int or atomic pointer
+struct atomic_ambiguous {
+ int count;
+ _Atomic int * __counted_by(count) buf;
+};
+
+// int *_Atomic - atomic pointer (unambiguous)
+struct atomic_ptr_unambiguous1 {
+ int count;
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ int *_Atomic __counted_by(count) buf;
+};
+
+// __counted_by before _Atomic
+struct atomic_ptr_attr_before_atomic1 {
+ int count;
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ int *__counted_by(count) _Atomic buf;
+};
+
+// __counted_by before * _Atomic
+struct atomic_ptr_attr_before_atomic2 {
+ int count;
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ int __counted_by(count) * _Atomic buf;
+};
+
+// _Atomic before type
+struct atomic_ptr_atomic_first1 {
+ int count;
+ _Atomic int *__counted_by(count) buf;
+};
+
+// _Atomic before type, attribute after *
+struct atomic_ptr_atomic_first2 {
+ int count;
+ _Atomic int * __counted_by(count) buf;
+};
+
+// __counted_by at the end
+struct atomic_ptr_attr_at_end1 {
+ int count;
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ int *_Atomic buf __counted_by(count);
+};
+
+// __counted_by at the end with space
+struct atomic_ptr_attr_at_end2 {
+ int count;
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ int * _Atomic buf __counted_by(count);
+};
+
+// ============================================================================
+// _ATOMIC POINTER TO ARRAY
+// ============================================================================
+
+struct atomic_ptr_to_arr1 {
+ int count;
+ _Atomic int (* __counted_by(count) buf)[4];
+};
+
+struct atomic_ptr_to_arr2 {
+ int count;
+ // expected-error at +2{{expected a type}}
+ // expected-error at +1{{expected member name or ';' after declaration specifiers}}
+ int _Atomic (* __counted_by(count) buf)[4];
+};
+
+struct atomic_ptr_to_arr3 {
+ int count;
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ int (* _Atomic __counted_by(count) buf)[4];
+};
+
+// ============================================================================
+// ATOMIC WITH CONST/VOLATILE/RESTRICT QUALIFIERS
+// ============================================================================
+
+// const _Atomic pointer
+struct atomic_const_ptr1 {
+ int count;
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ int * const _Atomic __counted_by(count) buf;
+};
+
+struct atomic_const_ptr2 {
+ int count;
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ int * _Atomic const __counted_by(count) buf;
+};
+
+struct atomic_const_ptr3 {
+ int count;
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ const int * _Atomic __counted_by(count) buf;
+};
+
+// volatile _Atomic pointer
+struct atomic_volatile_ptr1 {
+ int count;
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ int * volatile _Atomic __counted_by(count) buf;
+};
+
+struct atomic_volatile_ptr2 {
+ int count;
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ int * _Atomic volatile __counted_by(count) buf;
+};
+
+// restrict _Atomic pointer
+struct atomic_restrict_ptr1 {
+ int count;
+ // expected-error at +2{{restrict requires a pointer or reference ('_Atomic(int *)' is invalid)}}
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ int * __restrict _Atomic __counted_by(count) buf;
+};
+
+struct atomic_restrict_ptr2 {
+ int count;
+ // expected-error at +2{{restrict requires a pointer or reference ('_Atomic(int *)' is invalid)}}
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ int * _Atomic __restrict __counted_by(count) buf;
+};
+
+// Combined qualifiers
+struct atomic_const_volatile_ptr {
+ int count;
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ int * const volatile _Atomic __counted_by(count) buf;
+};
+
+struct atomic_all_qualifiers {
+ int count;
+ // expected-error at +2{{restrict requires a pointer or reference ('_Atomic(int *)' is invalid)}}
+ // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+ int * const volatile __restrict _Atomic __counted_by(count) buf;
+};
More information about the llvm-branch-commits
mailing list