[llvm-branch-commits] [clang] [BoundsSafety][NFC] Thread a late-parsed attribute list through declarators (PR #224559)

Yeoul Na via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Sat Sep 19 11:10:32 PDT 2026


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

>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 1/7] [BoundsSafety][NFC] Add counted_by type-shape validation
 helper

Introduce the single "is this type valid for a counted_by-family attribute in
type position" leaf that both the eager type-attribute path and the
late-parsed path will call:

  - Sema::ValidateBoundsAttrTypeShape holds the type-shape checks -- pointer
    or flexible array member, void and function pointee, pointee that is a
    struct with a flexible array member -- and their diagnostics.
  - validateBoundsAttrTypeForTypePosition wraps it for type position and adds
    the nested-pointer rejection, reported with the new
    err_counted_by_on_nested_pointer diagnostic.

Supporting pieces: Sema::BoundsAttrFlags and Sema::getBoundsAttrKind,
getCountAttrKind, getPointerNestLevel, the CountedByInvalidPointeeTypeKind
enum, and LangOptions::hasBoundsSafetyAttributes(), a stub returning false so
the shared leaf can gate its -fbounds-safety-only branches with the same
predicate used downstream.

Unused at this point -- nothing invokes it yet -- so this is NFC. The callers
are added in the following commits.
---
 .../clang/Basic/DiagnosticSemaKinds.td        |   3 +
 clang/include/clang/Basic/LangOptions.h       |   7 ++
 clang/include/clang/Sema/Sema.h               |  24 ++++
 clang/lib/Sema/SemaBoundsSafety.cpp           | 112 ++++++++++++++++++
 clang/lib/Sema/SemaType.cpp                   |  83 +++++++++++++
 5 files changed, 229 insertions(+)

diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index fca68f292f667..205d68b0979fe 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -7266,6 +7266,9 @@ def err_builtin_counted_by_ref_invalid_use : Error<
   "value returned by '__builtin_counted_by_ref' cannot be used in "
   "%select{an array subscript|a binary}0 expression">;
 
+def err_counted_by_on_nested_pointer : Error<
+  "'%select{counted_by|sized_by|counted_by_or_null|sized_by_or_null}0' attribute on nested pointer type is not allowed">;
+
 let CategoryName = "ARC Semantic Issue" in {
 
 // ARC-mode diagnostics.
diff --git a/clang/include/clang/Basic/LangOptions.h b/clang/include/clang/Basic/LangOptions.h
index 7539e000d03f9..0d615824ecc5b 100644
--- a/clang/include/clang/Basic/LangOptions.h
+++ b/clang/include/clang/Basic/LangOptions.h
@@ -710,6 +710,13 @@ class LangOptions : public LangOptionsBase {
     return ConvergentFunctions;
   }
 
+  /// Returns true when the -fbounds-safety attribute programming model is in
+  /// effect. There is no attributes-only mode on this base, so this is always
+  /// false; it exists so the shared Sema::ValidateBoundsAttrTypeShape leaf can
+  /// gate its -fbounds-safety-only branches with the same predicate used
+  /// downstream (where those branches carry the extra diagnostics).
+  bool hasBoundsSafetyAttributes() const { return false; }
+
   /// Return true if atomicrmw operations targeting allocations in private
   /// memory are undefined.
   bool threadPrivateMemoryAtomicsAreUndefined() const {
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 5becfc9fae152..6e8a75bdf2bd5 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -2493,6 +2493,30 @@ class Sema final : public SemaBase {
   /// Implementations are in SemaBoundsSafety.cpp
   ///@{
 public:
+  struct BoundsAttrFlags {
+    bool CountInBytes = false;
+    bool OrNull = false;
+    bool IsEndedBy = false;
+  };
+  static BoundsAttrFlags getBoundsAttrFlags(AttributeCommonInfo::Kind K);
+  static BoundsAttributedType::BoundsAttrKind
+  getBoundsAttrKind(const BoundsAttrFlags &);
+
+  /// Validates that a type is eligible for an "externally counted" bounds
+  /// attribute (counted_by/sized_by and their _or_null variants).
+  ///
+  /// \p Flags selects the attribute variant. \returns true if the type is
+  /// valid, false on error (diagnostics emitted). For `void *__counted_by(n)`
+  /// it warns that the count is treated as a byte size and sets
+  /// \p Flags.CountInBytes; callers that want to preserve a counted_by node
+  /// pass a scratch copy (see validateBoundsAttrTypeForTypePosition).
+  bool ValidateBoundsAttrTypeShape(QualType Ty, SourceLocation AttrLoc,
+                                   SourceRange AttrRange,
+                                   BoundsAttrFlags &Flags,
+                                   StringRef AttrSpelling = {},
+                                   bool AllowRedecl = false,
+                                   Expr *AttrArg = nullptr);
+
   /// Check if applying the specified attribute variant from the "counted by"
   /// family of attributes to FieldDecl \p FD is semantically valid. If
   /// semantically invalid diagnostics will be emitted explaining the problems.
diff --git a/clang/lib/Sema/SemaBoundsSafety.cpp b/clang/lib/Sema/SemaBoundsSafety.cpp
index 75041c801b6ff..2afe0812dcf4f 100644
--- a/clang/lib/Sema/SemaBoundsSafety.cpp
+++ b/clang/lib/Sema/SemaBoundsSafety.cpp
@@ -26,6 +26,34 @@ static CountAttributedType::BoundsAttrKind getCountAttrKind(bool CountInBytes,
                 : CountAttributedType::CountedBy;
 }
 
+BoundsAttributedType::BoundsAttrKind
+Sema::getBoundsAttrKind(const BoundsAttrFlags &Flags) {
+  // `ended_by` (Flags.IsEndedBy) has no home on this base; the field exists for
+  // struct parity with the downstream API but is never set here.
+  return getCountAttrKind(Flags.CountInBytes, Flags.OrNull);
+}
+
+Sema::BoundsAttrFlags Sema::getBoundsAttrFlags(AttributeCommonInfo::Kind K) {
+  BoundsAttrFlags Flags;
+  switch (K) {
+  case ParsedAttr::AT_SizedBy:
+    Flags.CountInBytes = true;
+    break;
+  case ParsedAttr::AT_SizedByOrNull:
+    Flags.CountInBytes = true;
+    Flags.OrNull = true;
+    break;
+  case ParsedAttr::AT_CountedBy:
+    break;
+  case ParsedAttr::AT_CountedByOrNull:
+    Flags.OrNull = true;
+    break;
+  default:
+    llvm_unreachable("unexpected bounds attribute kind");
+  }
+  return Flags;
+}
+
 static const RecordDecl *GetEnclosingNamedOrTopAnonRecord(const FieldDecl *FD) {
   const auto *RD = FD->getParent();
   // An unnamed struct is treated as anonymous struct at this point.
@@ -49,6 +77,90 @@ enum class CountedByInvalidPointeeTypeKind {
   VALID,
 };
 
+bool Sema::ValidateBoundsAttrTypeShape(QualType Ty, SourceLocation AttrLoc,
+                                       SourceRange AttrRange,
+                                       BoundsAttrFlags &Flags,
+                                       StringRef AttrSpelling, bool AllowRedecl,
+                                       Expr *AttrArg) {
+  // The downstream leaf runs a `hasBoundsSafetyAttributes()`-gated
+  // `checkBoundsAttrTypeConflictsAndMisc` preamble and an `ended_by` early
+  // path; both depend on machinery (`DynamicRangePointerType`,
+  // `ValueTerminatedType`, the `err_bounds_safety_*` diagnostics) that does not
+  // exist here, so they are the omitted bounds-safety arms. The rest matches.
+  BoundsAttributedType::BoundsAttrKind Kind = getBoundsAttrKind(Flags);
+
+  // counted_by/sized_by: must be pointer or array.
+  if (!Ty->isPointerType() && !Ty->isArrayType()) {
+    Diag(AttrLoc, diag::err_count_attr_not_on_ptr_or_flexible_array_member)
+        << Kind << 0;
+    return false;
+  }
+
+  // Arrays with sized_by or _or_null variants are not allowed under the
+  // non -fbounds-safety path; emit the "did you mean to use 'counted_by'" hint.
+  if (!getLangOpts().hasBoundsSafetyAttributes() && Ty->isArrayType() &&
+      (Flags.CountInBytes || Flags.OrNull)) {
+    Diag(AttrLoc, diag::err_count_attr_not_on_ptr_or_flexible_array_member)
+        << Kind << /*suggest counted_by*/ 1;
+    return false;
+  }
+
+  // Pointee/element type validation.
+  QualType PointeeTy;
+  int SelectPtrOrArr;
+  if (Ty->isPointerType()) {
+    PointeeTy = Ty->getPointeeType();
+    SelectPtrOrArr = 0;
+  } else {
+    const ArrayType *AT = getASTContext().getAsArrayType(Ty);
+    PointeeTy = AT->getElementType();
+    SelectPtrOrArr = 1;
+  }
+
+  auto InvalidTypeKind = CountedByInvalidPointeeTypeKind::VALID;
+  bool ShouldWarn = false;
+  if (!Flags.CountInBytes && PointeeTy->isAlwaysIncompleteType()) {
+    // Exception: void has an implicit size of 1 byte for pointer arithmetic
+    // (following GNU convention). Therefore, counted_by on void* is allowed
+    // and behaves equivalently to sized_by (treating the count as bytes).
+    if (PointeeTy->isVoidType() && !getLangOpts().hasBoundsSafetyAttributes()) {
+      // Emit a warning that this is a GNU extension.
+      Diag(AttrLoc, diag::ext_gnu_counted_by_void_ptr) << Kind;
+      Diag(AttrLoc, diag::note_gnu_counted_by_void_ptr_use_sized_by) << Kind;
+      Flags.CountInBytes = true;
+      return true;
+    }
+    InvalidTypeKind = CountedByInvalidPointeeTypeKind::INCOMPLETE;
+  } else if (PointeeTy->isSizelessType()) {
+    InvalidTypeKind = CountedByInvalidPointeeTypeKind::SIZELESS;
+  } else if (PointeeTy->isFunctionType()) {
+    InvalidTypeKind = CountedByInvalidPointeeTypeKind::FUNCTION;
+  } else if (!Flags.CountInBytes &&
+             PointeeTy->isStructureTypeWithFlexibleArrayMember()) {
+    if (Ty->isArrayType() && !getLangOpts().BoundsSafety) {
+      // This is a workaround for the Linux kernel that has already adopted
+      // `counted_by` on a FAM where the pointee is a struct with a FAM. This
+      // should be an error because computing the bounds of the array cannot
+      // be done correctly without manually traversing every struct object in
+      // the array at runtime. To allow the code to be built this error is
+      // downgraded to a warning.
+      ShouldWarn = true;
+    }
+    InvalidTypeKind = CountedByInvalidPointeeTypeKind::FLEXIBLE_ARRAY_MEMBER;
+  }
+
+  if (InvalidTypeKind != CountedByInvalidPointeeTypeKind::VALID) {
+    unsigned DiagID = ShouldWarn
+                          ? diag::warn_counted_by_attr_elt_type_unknown_size
+                          : diag::err_counted_by_attr_pointee_unknown_size;
+    Diag(AttrLoc, DiagID) << SelectPtrOrArr << PointeeTy << (int)InvalidTypeKind
+                          << (ShouldWarn ? 1 : 0) << Kind << AttrRange;
+    return false;
+  }
+
+  return true;
+}
+
 bool Sema::CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes,
                                      bool OrNull) {
   // Check the context the attribute is used in
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 2796ac2929f46..b6641fa538ae2 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -9043,6 +9043,89 @@ static void HandleHLSLParamModifierAttr(TypeProcessingState &State,
   }
 }
 
+static CountAttributedType::BoundsAttrKind getCountAttrKind(bool CountInBytes,
+                                                            bool OrNull) {
+  if (CountInBytes)
+    return OrNull ? CountAttributedType::SizedByOrNull
+                  : CountAttributedType::SizedBy;
+  return OrNull ? CountAttributedType::CountedByOrNull
+                : CountAttributedType::CountedBy;
+}
+
+/// Calculate the pointer nesting level for counted_by attribute validation.
+/// Counts the number of pointer/array/function declarator chunks before the
+/// specified chunk index.
+///
+/// For example, given \c "int * __counted_by(n) *pp" the declarator chunks
+/// are (outermost first): [0]=Pointer(\c int **), [1]=Pointer(\c int *).
+/// When processing the inner pointer at \p chunkIndex=1, one Pointer chunk
+/// precedes it, so the function returns 1.
+///
+/// \param state The type processing state
+/// \param chunkIndex The index of the current declarator chunk
+/// \return The number of pointer/array/function chunks before chunkIndex
+static unsigned getPointerNestLevel(TypeProcessingState &state,
+                                    unsigned chunkIndex) {
+  unsigned pointerNestLevel = 0;
+  const auto &stateDeclarator = state.getDeclarator();
+  assert(chunkIndex <= stateDeclarator.getNumTypeObjects());
+  // DeclChunks are ordered identifier out. Index 0 is the outer most type
+  // object. Find outer pointer, array or function.
+  for (unsigned i = 0; i < chunkIndex; ++i) {
+    auto TypeObject = stateDeclarator.getTypeObject(i);
+    switch (TypeObject.Kind) {
+    case DeclaratorChunk::Function:
+    case DeclaratorChunk::Array:
+    case DeclaratorChunk::Pointer:
+      pointerNestLevel++;
+      break;
+    default:
+      break;
+    }
+  }
+  return pointerNestLevel;
+}
+
+/// The single "is this type valid for a counted_by-family attribute in type
+/// position" leaf, shared by the eager path (HandleCountedByAttrOnType) and the
+/// late-parsed path (Sema::ActOnLateParsedTypeAttr).
+///
+/// Delegates the type-shape checks to Sema::ValidateBoundsAttrTypeShape so
+/// there is exactly one copy of those diagnostics, and adds the nested-pointer
+/// rejection that only applies in type position.
+///
+/// \p Flags is set from \p AttrKind and returned to the caller for building the
+/// type.
+static bool validateBoundsAttrTypeForTypePosition(
+    Sema &S, QualType Ty, ParsedAttr::Kind AttrKind, SourceLocation AttrLoc,
+    SourceRange AttrRange, unsigned PointerNestLevel,
+    Sema::BoundsAttrFlags &Flags) {
+  Flags = Sema::getBoundsAttrFlags(AttrKind);
+
+  // ValidateBoundsAttrTypeShape may rewrite Flags.CountInBytes: for
+  // `void *__counted_by(n)` it warns "treated as 'sized_by'" and sets
+  // CountInBytes so the -fbounds-safety path builds a SizedBy node. The
+  // pre-existing non-BoundsSafety field path discards that rewrite and builds a
+  // CountedBy node, so absorb it in a scratch copy to keep this
+  // diagnostics-only.
+  //
+  // FIXME: Reconcile with the -fbounds-safety path, which honors the rewrite.
+  Sema::BoundsAttrFlags Scratch = Flags;
+  if (!S.ValidateBoundsAttrTypeShape(Ty, AttrLoc, AttrRange, Scratch))
+    return false;
+
+  // A counted_by-family attribute has to end up at the outermost level of the
+  // declared type; a nested one would be buried where the bounds cannot be
+  // maintained.
+  if (PointerNestLevel > 0) {
+    S.Diag(AttrLoc, diag::err_counted_by_on_nested_pointer)
+        << Sema::getBoundsAttrKind(Flags);
+    return false;
+  }
+
+  return true;
+}
+
 static void processTypeAttrs(TypeProcessingState &state, QualType &type,
                              TypeAttrLocation TAL,
                              const ParsedAttributesView &attrs,

>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 2/7] [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 e9bab81b095fd..ce97ad25bcd16 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 6e8a75bdf2bd5..8533f2823ed14 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 ffd9bd33c7501..b145560508e95 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 5976f5a7ccdea..9a49ce16447cc 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 b6641fa538ae2..e1701b1feaed2 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 3/7] 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 9a49ce16447cc..848390205b983 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 4/7] 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 8533f2823ed14..8fb985423c4a4 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 848390205b983..e3b0eda1af670 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 e1701b1feaed2..609684523573c 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 5/7] 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 e3b0eda1af670..de97611e8bcae 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 6/7] [BoundsSafety][NFC] Add the Sema/Parser bridge for
 late-parsed type attributes

A late-parsed bounds attribute has to build its type when the attribute is
seen, but its argument isn't parseable until the enclosing record is complete.
Building that type needs the Parser (which owns the cached tokens) and Sema
(which owns type construction) to meet:

  - Sema::ActOnLateParsedTypeAttr validates a counted_by-family attribute for
    the type position and, if valid, wraps the type in a CountAttributedType
    whose count is not yet known, handing the node back for completion.

  - Parser::ProcessLateParsedTypeAttrCallback is the Parser-side entry point,
    registered on Sema so Sema can call back without including Parser.h (the
    same pattern as LateTemplateParserCallback). It reuses an already-built
    node so several declarators sharing one attribute share one type.

No functional change: nothing records late-parsed type attributes yet, so the
callback is never invoked. The next commit wires it up.
---
 clang/include/clang/Parse/Parser.h | 14 +++++++++++++
 clang/include/clang/Sema/Sema.h    | 21 ++++++++++++++++++++
 clang/lib/Parse/ParseDecl.cpp      | 32 ++++++++++++++++++++++++++++++
 clang/lib/Parse/Parser.cpp         |  5 +++++
 clang/lib/Sema/SemaType.cpp        | 21 ++++++++++++++++++++
 5 files changed, 93 insertions(+)

diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index ce97ad25bcd16..ae7c345ba6ce3 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -8153,6 +8153,20 @@ class Parser : public CodeCompletionHandler {
 
   static void LateTemplateParserCallback(void *P, LateParsedTemplate &LPT);
 
+  /// Validate \p LA as a late-parsed type attribute and, if valid, wrap \p type
+  /// in a \c CountAttributedType whose count expression is not yet known,
+  /// recording the node on \p LA so it can be completed later.
+  ///
+  /// \p LA is downcast to \c LateParsedTypeAttribute; if the cast fails the
+  /// attribute is not applicable here and the function returns \c true to skip.
+  /// \p pointerNestLevel is the number of pointer/array/function declarator
+  /// chunks that precede the current chunk (see \c getPointerNestLevel).
+  /// Returns \c true on success and \c false if the attribute is invalid for
+  /// \p type.
+  static bool ProcessLateParsedTypeAttrCallback(LateParsedAttribute *LA,
+                                                QualType &type,
+                                                unsigned pointerNestLevel);
+
   /// We've parsed something that could plausibly be intended to be a template
   /// name (\p LHS) followed by a '<' token, and the following code can't
   /// possibly be an expression. Determine if this is likely to be a template-id
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 8fb985423c4a4..aa18627776da8 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -1354,6 +1354,27 @@ class Sema final : public SemaBase {
     OpaqueParser = P;
   }
 
+  /// Callback to the parser to interact with late-parsed type attributes. This
+  /// allows Sema to call back into Parser without including Parser.h.
+  ///
+  /// Processes a single late-parsed type attribute: validates the attribute
+  /// kind/type and wraps \p type in a CountAttributedType whose count is not
+  /// yet known, if appropriate. Returns false if the attribute is invalid.
+  typedef bool ProcessLateParsedTypeAttrCB(LateParsedAttribute *LA,
+                                           QualType &type,
+                                           unsigned pointerNestLevel);
+  ProcessLateParsedTypeAttrCB *ProcessLateParsedTypeAttrCallback = nullptr;
+
+  /// Called from the Parser's ProcessLateParsedTypeAttrCallback to validate a
+  /// counted_by-family attribute type and, if valid, wrap \p type in a
+  /// CountAttributedType whose count expression is not yet known. Returns false
+  /// if the attribute should be dropped, otherwise sets \p BATy to the node the
+  /// caller must complete once the argument is parseable.
+  bool ActOnLateParsedTypeAttr(ParsedAttr::Kind AttrKind,
+                               SourceLocation AttrNameLoc, QualType &type,
+                               unsigned pointerNestLevel,
+                               BoundsAttributedType **BATy);
+
   /// Callback to the parser to parse a type expressed as a string.
   std::function<TypeResult(StringRef, StringRef, SourceLocation)>
       ParseTypeFromStringCallback;
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index de97611e8bcae..0d205df0c2a48 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -4896,6 +4896,38 @@ void Parser::ParseLexedTypeAttribute(LateParsedTypeAttribute &LA,
   OutAttrs.takeAllAppendingFrom(Attrs);
 }
 
+bool Parser::ProcessLateParsedTypeAttrCallback(LateParsedAttribute *LA,
+                                               QualType &type,
+                                               unsigned pointerNestLevel) {
+  auto *LTA = dyn_cast_if_present<LateParsedTypeAttribute>(LA);
+  if (!LTA)
+    return true;
+
+  // One attribute yields one type node, even when several declarators share it.
+  // A declaration-specifier-position attribute lives on the DeclSpec, whose
+  // late-attribute list ConvertDeclSpecToType walks once per declarator, so
+  // this callback runs N times for `IP __counted_by(n) a, b;`. Building a fresh
+  // (deliberately un-uniqued) node each time would leave every node but the
+  // last orphaned with a null count, so reuse the node instead. This matches
+  // the eager path, where getCountAttributedType uniques on the count
+  // expression and all declarators likewise share one node.
+  if (LTA->TypeToComplete) {
+    type = QualType(LTA->TypeToComplete, 0);
+    return true;
+  }
+
+  ParsedAttr::Kind AttrKind = ParsedAttr::getParsedKind(
+      &LTA->AttrName, nullptr, ParsedAttr::Form::GNU().getSyntax());
+  // Sema cannot see LateParsedTypeAttribute's definition, so it hands the node
+  // back and we record it here for the completion pass to fill in.
+  BoundsAttributedType *BATy = nullptr;
+  if (!LTA->Self->Actions.ActOnLateParsedTypeAttr(
+          AttrKind, LTA->AttrNameLoc, type, pointerNestLevel, &BATy))
+    return false;
+  LTA->TypeToComplete = BATy;
+  return true;
+}
+
 void Parser::CompleteLateParsedTypeAttributes(
     SmallVectorImpl<LateParsedTypeAttribute *> &LateTypeAttrs) {
   for (LateParsedTypeAttribute *RawLTA : LateTypeAttrs) {
diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp
index c86ed6b2ea3f9..2d1fcffcf5e4e 100644
--- a/clang/lib/Parse/Parser.cpp
+++ b/clang/lib/Parse/Parser.cpp
@@ -592,6 +592,11 @@ void Parser::Initialize() {
   }
 
   Actions.Initialize();
+  // Register the callback so Sema can call back into the Parser to handle
+  // late-parsed type attributes (e.g. counted_by on struct fields), which
+  // may be processed at any point during parsing via ActOnFields.
+  Actions.ProcessLateParsedTypeAttrCallback =
+      &Parser::ProcessLateParsedTypeAttrCallback;
 
   // Prime the lexer look-ahead.
   ConsumeToken();
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 609684523573c..7f7c7ae6e3075 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -9126,6 +9126,27 @@ static bool validateBoundsAttrTypeForTypePosition(
   return true;
 }
 
+bool Sema::ActOnLateParsedTypeAttr(ParsedAttr::Kind AttrKind,
+                                   SourceLocation AttrNameLoc, QualType &type,
+                                   unsigned pointerNestLevel,
+                                   BoundsAttributedType **BATy) {
+  BoundsAttrFlags Flags;
+  if (!validateBoundsAttrTypeForTypePosition(*this, type, AttrKind, AttrNameLoc,
+                                             SourceRange(AttrNameLoc),
+                                             pointerNestLevel, Flags))
+    return false;
+
+  // The argument hasn't been parsed yet, so build the type without it and hand
+  // the node back for completion. Because enclosing types refer to it by
+  // pointer, filling the argument in later leaves them untouched — no rebuild
+  // of the type chain and no TypeLoc re-emission.
+  auto *CATy = getASTContext().getIncompleteCountAttributedType(
+      type, Flags.CountInBytes, Flags.OrNull);
+  type = QualType(CATy, 0);
+  *BATy = CATy;
+  return true;
+}
+
 static void processTypeAttrs(TypeProcessingState &state, QualType &type,
                              TypeAttrLocation TAL,
                              const ParsedAttributesView &attrs,

>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 7/7] [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 ae7c345ba6ce3..6d0affa3c8822 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 e6dc6831d893f..9b1f3b244515d 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 0d205df0c2a48..9dbfc7ec8b393 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);



More information about the llvm-branch-commits mailing list