[clang] [BoundsSafety] support counted_by family on function parameters (PR #230538)

Mohammed Ashraf via cfe-commits cfe-commits at lists.llvm.org
Fri Oct 9 11:55:38 PDT 2026


https://github.com/Holo-xy updated https://github.com/llvm/llvm-project/pull/230538

>From e09b97477991c2e09832586040d584173dcbe877 Mon Sep 17 00:00:00 2001
From: Holo-xy <mohammedashraf4599 at gmail.com>
Date: Wed, 30 Sep 2026 18:58:29 +0300
Subject: [PATCH 1/9] [clang][bounds-safety][nfc] add shared bounds attribute
 validation

introduce shared type-shape and nesting validation for counted_by,
sized_by and their nullable variants. keep declaration-specific checks
separate so eager and late parsing can share the validation as support
expands to parameters, return types, globals and locals.

add attribute flags, declarator nesting support and a disabled
bounds-safety attribute-mode hook for later integration. the helpers
have no active callers, so existing behavior is unchanged.

co-authored-by: rapidsna <yeoul_na at apple.com>
---
 .../clang/Basic/DiagnosticSemaKinds.td        |   3 +
 clang/include/clang/Basic/LangOptions.h       |   6 +
 clang/include/clang/Sema/Sema.h               |  40 +++++
 clang/lib/Sema/SemaBoundsSafety.cpp           | 145 ++++++++++++++++++
 clang/lib/Sema/SemaType.cpp                   |  26 ++++
 5 files changed, 220 insertions(+)

diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 9208aba1445d7e..868e4d138e89e3 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -7269,6 +7269,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..eedb3b85555f46 100644
--- a/clang/include/clang/Basic/LangOptions.h
+++ b/clang/include/clang/Basic/LangOptions.h
@@ -710,6 +710,12 @@ class LangOptions : public LangOptionsBase {
     return ConvergentFunctions;
   }
 
+  /// Return true if bounds attributes follow the -fbounds-safety programming
+  /// model, including its attributes-only mode.
+  ///
+  /// Always false until that model is implemented upstream.
+  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 85ebe0cfdba1e8..a63709529f4706 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -2493,6 +2493,46 @@ class Sema final : public SemaBase {
   /// Implementations are in SemaBoundsSafety.cpp
   ///@{
 public:
+  struct BoundsAttrFlags {
+    bool CountInBytes = false;
+    bool OrNull = false;
+    bool IsEndedBy = false;
+
+    /// Return the flags for a bounds attribute of kind \p K.
+    static BoundsAttrFlags get(AttributeCommonInfo::Kind K);
+  };
+  static BoundsAttributedType::BoundsAttrKind
+  getBoundsAttrKind(const BoundsAttrFlags &);
+
+  /// Check the type shape for counted_by, sized_by and their _or_null variants.
+  /// The caller checks declaration context, count expressions, flexible array
+  /// member eligibility and nesting.
+  ///
+  /// \p Flags selects the attribute variant. For void pointers, counted_by is
+  /// treated as a byte count outside the -fbounds-safety attribute model. Set
+  /// \c Flags.CountInBytes in this case if \p UpdateFlags is true.
+  /// \p AttrSpelling, \p AllowRedecl and \p AttrArg are reserved for that model
+  /// (see LangOptions::hasBoundsSafetyAttributes).
+  ///
+  /// \returns true if the shape is valid, or false after diagnosing a rejected
+  /// attribute. Rejection may produce a warning rather than an error.
+  bool ValidateBoundsAttrTypeShape(QualType Ty, SourceLocation AttrLoc,
+                                   SourceRange AttrRange,
+                                   BoundsAttrFlags &Flags,
+                                   StringRef AttrSpelling = {},
+                                   bool AllowRedecl = false,
+                                   Expr *AttrArg = nullptr,
+                                   bool UpdateFlags = true);
+
+  /// Check a counted_by-family attribute in type position and initialize
+  /// \p Flags from \p AttrKind. \p PointerNestLevel counts the pointer, array
+  /// and function declarator chunks enclosing the attribute.
+  ///
+  /// \returns true if valid, or false after diagnosing a rejected attribute.
+  bool ValidateBoundsAttrTypeForTypePosition(
+      QualType Ty, AttributeCommonInfo::Kind AttrKind, SourceLocation AttrLoc,
+      SourceRange AttrRange, unsigned PointerNestLevel, BoundsAttrFlags &Flags);
+
   /// 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..dda72a3414f524 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) {
+  // Clang does not yet implement __ended_by (see BoundsSafety.md).
+  assert(!Flags.IsEndedBy && "ended_by is not implemented");
+  return getCountAttrKind(Flags.CountInBytes, Flags.OrNull);
+}
+
+Sema::BoundsAttrFlags Sema::BoundsAttrFlags::get(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,123 @@ enum class CountedByInvalidPointeeTypeKind {
   VALID,
 };
 
+/// Check type conflicts under the -fbounds-safety attribute model.
+/// Return std::nullopt if validation should continue with the shared checks.
+static std::optional<bool> checkBoundsAttrTypeConflictsAndMisc(
+    Sema &S, QualType Ty, SourceLocation AttrLoc,
+    const Sema::BoundsAttrFlags &Flags, StringRef AttrSpelling,
+    bool AllowRedecl, Expr *AttrArg) {
+  // The -fbounds-safety model's checks for conflicting attributes, atomic
+  // pointers and array shapes are not upstream yet.
+  return std::nullopt;
+}
+
+bool Sema::ValidateBoundsAttrTypeShape(QualType Ty, SourceLocation AttrLoc,
+                                       SourceRange AttrRange,
+                                       BoundsAttrFlags &Flags,
+                                       StringRef AttrSpelling, bool AllowRedecl,
+                                       Expr *AttrArg, bool UpdateFlags) {
+  if (getLangOpts().hasBoundsSafetyAttributes())
+    if (std::optional<bool> Result = checkBoundsAttrTypeConflictsAndMisc(
+            *this, Ty, AttrLoc, Flags, AttrSpelling, AllowRedecl, AttrArg))
+      return *Result;
+
+  BoundsAttributedType::BoundsAttrKind Kind = getBoundsAttrKind(Flags);
+
+  // counted_by and sized_by require a 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 only support counted_by outside the -fbounds-safety attribute model.
+  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;
+  }
+
+  // Check the pointee or element type.
+  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()) {
+    // GNU void pointer arithmetic treats each element as one byte, so allow
+    // counted_by on void pointers outside the -fbounds-safety attribute model.
+    if (PointeeTy->isVoidType() && !getLangOpts().hasBoundsSafetyAttributes()) {
+      Diag(AttrLoc, diag::ext_gnu_counted_by_void_ptr) << Kind;
+      Diag(AttrLoc, diag::note_gnu_counted_by_void_ptr_use_sized_by) << Kind;
+      if (UpdateFlags)
+        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) {
+      // Warn for compatibility with Linux kernel code using counted_by on a FAM
+      // of structs with FAMs. Computing these bounds requires traversing the
+      // elements at runtime.
+      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::ValidateBoundsAttrTypeForTypePosition(
+    QualType Ty, AttributeCommonInfo::Kind AttrKind, SourceLocation AttrLoc,
+    SourceRange AttrRange, unsigned PointerNestLevel, BoundsAttrFlags &Flags) {
+  Flags = BoundsAttrFlags::get(AttrKind);
+
+  // Preserve the counted_by kind for the GNU void pointer extension to match
+  // the field path. The count is still interpreted as a byte size.
+  if (!ValidateBoundsAttrTypeShape(Ty, AttrLoc, AttrRange, Flags,
+                                   /*AttrSpelling=*/{}, /*AllowRedecl=*/false,
+                                   /*AttrArg=*/nullptr, /*UpdateFlags=*/false))
+    return false;
+
+  // Currently, only attributes at the outermost level of the declared type
+  // are supported.
+  //
+  // FIXME: Support indirect parameters such as:
+  //   void f(int *__counted_by(*len) *buf, int *len);
+  // See https://github.com/llvm/llvm-project/issues/166411.
+  if (PointerNestLevel > 0) {
+    Diag(AttrLoc, diag::err_counted_by_on_nested_pointer)
+        << getBoundsAttrKind(Flags);
+    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 037583575d19fe..45819bde15afdd 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -9061,6 +9061,32 @@ static void HandleHLSLParamModifierAttr(TypeProcessingState &State,
   }
 }
 
+/// Return the number of pointer, array and function chunks before
+/// \p chunkIndex for counted_by validation.
+///
+/// Chunks are ordered from the identifier out. In
+/// `int *__counted_by(n) *pp`, the attributed pointer is chunk 1, enclosed by
+/// the pointer at chunk 0, so its nesting level is 1.
+[[maybe_unused]] static unsigned getPointerNestLevel(TypeProcessingState &state,
+                                                     unsigned chunkIndex) {
+  unsigned pointerNestLevel = 0;
+  const auto &stateDeclarator = state.getDeclarator();
+  assert(chunkIndex <= stateDeclarator.getNumTypeObjects());
+  for (unsigned i = 0; i < chunkIndex; ++i) {
+    const DeclaratorChunk &TypeObject = stateDeclarator.getTypeObject(i);
+    switch (TypeObject.Kind) {
+    case DeclaratorChunk::Function:
+    case DeclaratorChunk::Array:
+    case DeclaratorChunk::Pointer:
+      pointerNestLevel++;
+      break;
+    default:
+      break;
+    }
+  }
+  return pointerNestLevel;
+}
+
 static void processTypeAttrs(TypeProcessingState &state, QualType &type,
                              TypeAttrLocation TAL,
                              const ParsedAttributesView &attrs,

>From ce07a3afc018cc9f8e0c99ed4ddef582a115c428 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 2/9] [BoundsSafety][NFC] Build an incomplete
 CountAttributedType for a late-parsed attribute

Add Sema::ProcessLateParsedTypeAttr. It validates a late-parsed
counted_by-family attribute in type position and wraps the type in a
CountAttributedType whose count is filled in once the enclosing record
is complete.

LateParsedTypeAttribute is owned by the Parser, which Sema cannot depend
on. Sema therefore reads the attribute's kind, location and type slot
through a callback.

No functional change: nothing creates late-parsed type attributes yet,
so ProcessLateParsedTypeAttr is never invoked. The next commit wires it
up.

Assisted-by: Opus 5.5
---
 clang/include/clang/Parse/Parser.h | 15 +++++++++++---
 clang/include/clang/Sema/Sema.h    | 20 ++++++++++++++++---
 clang/lib/Parse/ParseDecl.cpp      | 10 ++++++++++
 clang/lib/Parse/Parser.cpp         |  3 +++
 clang/lib/Sema/SemaType.cpp        | 32 ++++++++++++++++++++++++++++++
 5 files changed, 74 insertions(+), 6 deletions(-)

diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index d29d3f0ac84a15..101cfe2bcde745 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -234,7 +234,7 @@ 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
+  /// `Sema::ProcessLateParsedTypeAttr` and completed once the
   /// enclosing scope makes the argument parseable. Null if type construction
   /// rejected the attribute.
   ///
@@ -242,9 +242,13 @@ struct LateParsedTypeAttribute : public LateParsedAttribute {
   /// attribute this is; Sema dispatches on the concrete kind when completing.
   BoundsAttributedType *TypeToComplete = nullptr;
 
+  /// The attribute kind, resolved when the GNU attribute was parsed.
+  ParsedAttr::Kind AttrKind;
+
   explicit LateParsedTypeAttribute(Parser *P, IdentifierInfo &Name,
-                                   SourceLocation Loc)
-      : LateParsedAttribute(P, Name, Loc, Kind::Type) {}
+                                   SourceLocation Loc,
+                                   ParsedAttr::Kind AttrKind)
+      : LateParsedAttribute(P, Name, Loc, Kind::Type), AttrKind(AttrKind) {}
 
   void ParseLexedAttributes() override;
 
@@ -8162,6 +8166,11 @@ class Parser : public CodeCompletionHandler {
 
   static void LateTemplateParserCallback(void *P, LateParsedTemplate &LPT);
 
+  /// Implements Sema::GetLateParsedTypeAttrCB.
+  static BoundsAttributedType **
+  GetLateParsedTypeAttrCallback(LateParsedAttribute *LA, ParsedAttr::Kind &Kind,
+                                SourceLocation &Loc);
+
   /// 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 a63709529f4706..2fbf49c5c82ebb 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -1354,6 +1354,20 @@ class Sema final : public SemaBase {
     OpaqueParser = P;
   }
 
+  /// Callback into the parser for the fields of a late-parsed type attribute,
+  /// which Sema cannot see. Returns the slot holding the type built for \p LA
+  /// and sets \p Kind and \p Loc, or returns null if \p LA is not a type
+  /// attribute.
+  typedef BoundsAttributedType **
+  GetLateParsedTypeAttrCB(LateParsedAttribute *LA, ParsedAttr::Kind &Kind,
+                          SourceLocation &Loc);
+  GetLateParsedTypeAttrCB *GetLateParsedTypeAttrCallback = nullptr;
+
+  /// Validate the late-parsed type attribute \p LA and wrap \p type in a
+  /// CountAttributedType with no count yet, recorded on \p LA for later
+  /// completion. Ignores other attributes. Returns false if \p LA is invalid.
+  bool ProcessLateParsedTypeAttr(LateParsedAttribute *LA, QualType &type);
+
   /// Callback to the parser to parse a type expressed as a string.
   std::function<TypeResult(StringRef, StringRef, SourceLocation)>
       ParseTypeFromStringCallback;
@@ -2570,9 +2584,9 @@ class Sema final : public SemaBase {
   }
 
   /// 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.
+  /// built for it by ProcessLateParsedTypeAttr, 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);
 
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index 6ab03e8a7fc4a1..da5a6d880a71c0 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -4940,6 +4940,16 @@ void Parser::CompleteLateParsedTypeAttributes(
   LateTypeAttrs.clear();
 }
 
+BoundsAttributedType **Parser::GetLateParsedTypeAttrCallback(
+    LateParsedAttribute *LA, ParsedAttr::Kind &Kind, SourceLocation &Loc) {
+  auto *LTA = dyn_cast<LateParsedTypeAttribute>(LA);
+  if (!LTA)
+    return nullptr;
+  Kind = LTA->AttrKind;
+  Loc = LTA->AttrNameLoc;
+  return &LTA->TypeToComplete;
+}
+
 void LateParsedTypeAttribute::ParseInto(ParsedAttributes &OutAttrs) {
   // Delegate to the Parser that created this attribute
   Self->ParseLexedTypeAttribute(*this, OutAttrs);
diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp
index e35666b5d6420a..a0ca7c7156b30b 100644
--- a/clang/lib/Parse/Parser.cpp
+++ b/clang/lib/Parse/Parser.cpp
@@ -592,6 +592,9 @@ void Parser::Initialize() {
   }
 
   Actions.Initialize();
+  // Let Sema read late-parsed type attributes, which only the Parser can see.
+  Actions.GetLateParsedTypeAttrCallback =
+      &Parser::GetLateParsedTypeAttrCallback;
 
   // Prime the lexer look-ahead.
   ConsumeToken();
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 45819bde15afdd..ba9d0f14da5f58 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -9087,6 +9087,38 @@ static void HandleHLSLParamModifierAttr(TypeProcessingState &State,
   return pointerNestLevel;
 }
 
+bool Sema::ProcessLateParsedTypeAttr(LateParsedAttribute *LA, QualType &type) {
+  assert(GetLateParsedTypeAttrCallback);
+  ParsedAttr::Kind AttrKind;
+  SourceLocation AttrNameLoc;
+  BoundsAttributedType **TypeToComplete =
+      GetLateParsedTypeAttrCallback(LA, AttrKind, AttrNameLoc);
+  if (!TypeToComplete)
+    return true;
+
+  // A DeclSpec attribute runs this once per declarator, as in
+  // `IP __counted_by(n) a, b;`. Reuse one node, as the eager path does, so no
+  // node is left without a count.
+  if (*TypeToComplete) {
+    type = QualType(*TypeToComplete, 0);
+    return true;
+  }
+
+  // Nesting is checked once the enclosing declarator chunks wrap the node.
+  BoundsAttrFlags Flags;
+  if (!ValidateBoundsAttrTypeForTypePosition(type, AttrKind, AttrNameLoc,
+                                             SourceRange(AttrNameLoc),
+                                             /*PointerNestLevel=*/0, Flags))
+    return false;
+
+  // The argument isn't parsed yet, so build the type without a count.
+  auto *CATy = getASTContext().getIncompleteCountAttributedType(
+      type, Flags.CountInBytes, Flags.OrNull);
+  type = QualType(CATy, 0);
+  *TypeToComplete = CATy;
+  return true;
+}
+
 static void processTypeAttrs(TypeProcessingState &state, QualType &type,
                              TypeAttrLocation TAL,
                              const ParsedAttributesView &attrs,

>From 9b1884f6e6f7f0ea368c675d289e1d55450c8184 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 3/9] [BoundsSafety][NFC] Add late-parsed type attribute lists
 to declarators

This is groundwork for late-parsing type attributes in every type
position where an ordinary type attribute can appear. Ordinary type
attributes are kept on the DeclSpec, on each DeclaratorChunk and on the
Declarator. Give late-parsed type attributes the same three homes, with
nothing producing or consuming them yet:

  - Move CachedTokens and LateParsedAttrList earlier in DeclSpec.h so
    that DeclSpec, Declarator and DeclaratorChunk can hold one. Add
    LateParsedAttrList::forTypeAttrs() to create those lists.
  - 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. The former passes it to
    ParseDeclarationSpecifiers, the latter to ParseGNUAttributes.

No functional change: no caller passes a list yet, so the lists stay
empty. The next commit populates them.
---
 clang/include/clang/Parse/Parser.h  | 23 ++++---
 clang/include/clang/Sema/DeclSpec.h | 94 +++++++++++++++++++----------
 clang/lib/Parse/ParseDecl.cpp       | 14 +++--
 3 files changed, 86 insertions(+), 45 deletions(-)

diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index 101cfe2bcde745..7207bd972a4976 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -1958,10 +1958,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
@@ -1972,10 +1974,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
@@ -2683,7 +2687,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 66a6098a75ae9b..3868da094ffef9 100644
--- a/clang/include/clang/Sema/DeclSpec.h
+++ b/clang/include/clang/Sema/DeclSpec.h
@@ -213,6 +213,42 @@ 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) {}
+
+    /// A list for the late-parsed type attributes of a declaration specifier,
+    /// declarator or declarator chunk.
+    static LateParsedAttrList forTypeAttrs() {
+      return LateParsedAttrList(/*PSoon=*/true,
+                                /*LateAttrParseExperimentalExtOnly=*/true,
+                                /*LateAttrParseTypeAttrOnly=*/true);
+    }
+
+    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,
@@ -407,6 +443,9 @@ class DeclSpec {
   // attributes.
   ParsedAttributes Attrs;
 
+  // late attributes
+  LateParsedAttrList LateParsedAttrs;
+
   // Scope specifier for the type spec, if applicable.
   CXXScopeSpec TypeScope;
 
@@ -483,7 +522,8 @@ 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(LateParsedAttrList::forTypeAttrs()),
+        writtenBS(), ObjCQualifiers(nullptr) {}
 
   // storage-class-specifier
   SCS getStorageClassSpec() const { return (SCS)StorageClassSpec; }
@@ -883,6 +923,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);
   }
@@ -1255,40 +1300,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(LateParsedAttrList::forTypeAttrs()) {}
 
   enum {
     Pointer, Reference, Array, Function, BlockPointer, MemberPointer, Paren, Pipe
@@ -1306,6 +1323,7 @@ struct DeclaratorChunk {
   }
 
   ParsedAttributesView AttrList;
+  LateParsedAttrList LateAttrList;
 
   struct PointerTypeInfo {
     /// The type qualifiers: const/volatile/restrict/unaligned/atomic.
@@ -2018,6 +2036,8 @@ class Declarator {
   /// corresponding constructor parameter.
   const ParsedAttributesView &DeclarationAttrs;
 
+  LateParsedAttrList LateParsedAttrs;
+
   /// The asm label, if specified.
   Expr *AsmLabel;
 
@@ -2083,7 +2103,8 @@ class Declarator {
         Redeclaration(false), Extension(false), ObjCIvar(false),
         ObjCWeakProperty(false), InlineStorageUsed(false),
         HasInitializer(false), Attrs(DS.getAttributePool().getFactory()),
-        DeclarationAttrs(DeclarationAttrs), AsmLabel(nullptr),
+        DeclarationAttrs(DeclarationAttrs),
+        LateParsedAttrs(LateParsedAttrList::forTypeAttrs()), AsmLabel(nullptr),
         TrailingRequiresClause(nullptr),
         InventedTemplateParameterList(nullptr) {
     assert(llvm::all_of(DeclarationAttrs,
@@ -2172,6 +2193,7 @@ class Declarator {
       DeclTypeInfo[i].destroy();
     DeclTypeInfo.clear();
     Attrs.clear();
+    LateParsedAttrs.clear();
     AsmLabel = nullptr;
     InlineStorageUsed = false;
     HasInitializer = false;
@@ -2406,13 +2428,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
@@ -2742,6 +2767,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 da5a6d880a71c0..7e85d32366d83b 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -2753,12 +2753,17 @@ Decl *Parser::ParseDeclarationAfterDeclaratorAndAttributes(
 
 void Parser::ParseSpecifierQualifierList(
     DeclSpec &DS, ImplicitTypenameContext AllowImplicitTypename,
-    AccessSpecifier AS, DeclSpecContext DSC) {
+    AccessSpecifier AS, DeclSpecContext DSC, LateParsedAttrList *LateAttrs) {
   ParsedTemplateInfo TemplateInfo;
+
+  assert((!LateAttrs ||
+          llvm::none_of(*LateAttrs, llvm::IsaPred<LateParsedTypeAttribute>)) &&
+         "caller's late-attribute list must not hold type attributes yet");
+
   /// 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.
@@ -6283,7 +6288,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);
@@ -6445,7 +6451,7 @@ void Parser::ParseTypeQualifierListOpt(
       // recovery is graceful.
       if (AttrReqs & AR_GNUAttributesParsed ||
           AttrReqs & AR_GNUAttributesParsedAndRejected) {
-        ParseGNUAttributes(DS.getAttributes());
+        ParseGNUAttributes(DS.getAttributes(), LateAttrs);
         continue; // do *not* consume the next token!
       }
       // otherwise, FALL THROUGH!

>From 7ff07ab1265fad0433279915948a6734bdfbb624 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 4/9] [BoundsSafety] Handle the counted_by family as a type
 attribute

counted_by / sized_by (and their _or_null variants) were handled as a
declaration-position attribute through handleCountedByAttrField.

Build the type during type construction instead and properly handle as a
type-position attribute. Building the node in type position also allows
the new way of late-parsing for type attributes: creates a type node
in place and fills it in once late parsing is done, which will be
enabled in the follow-up commits.

Assisted-by: Opus 4.8
---
 clang/lib/Sema/SemaBoundsSafety.cpp | 109 +---------------------------
 clang/lib/Sema/SemaDecl.cpp         |  19 +++++
 clang/lib/Sema/SemaDeclAttr.cpp     |  48 ++++++------
 clang/lib/Sema/SemaType.cpp         |  66 ++++++++++++++++-
 4 files changed, 106 insertions(+), 136 deletions(-)

diff --git a/clang/lib/Sema/SemaBoundsSafety.cpp b/clang/lib/Sema/SemaBoundsSafety.cpp
index dda72a3414f524..7e983a947b293d 100644
--- a/clang/lib/Sema/SemaBoundsSafety.cpp
+++ b/clang/lib/Sema/SemaBoundsSafety.cpp
@@ -206,20 +206,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() &&
@@ -231,98 +218,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();
@@ -337,6 +233,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)) {
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index db5e66cb96c3ec..2ca6ecb99c3365 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -20191,6 +20191,25 @@ 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.
+          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..dbf0971f0acab7 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -7069,41 +7069,35 @@ static void handleNoPFPAttrField(Sema &S, Decl *D, const ParsedAttr &AL) {
   D->addAttr(NoFieldProtectionAttr::Create(S.Context, AL));
 }
 
+// Only reached for a counted_by-family attribute that
+// -fexperimental-late-parse-attributes late-parses as a declaration attribute.
+// This will be removed once late-parsing for type attribute is fully switched
+// over to the new mechanism so it's properly supported.
+// Every other one is a type attribute, handled by HandleCountedByAttrOnType.
 static void handleCountedByAttrField(Sema &S, Decl *D, const ParsedAttr &AL) {
+  // Already applied, or diagnosed, as a type attribute.
+  if (AL.isUsedAsTypeAttr())
+    return;
+
   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))
+  auto *FD = cast<FieldDecl>(D);
+  Sema::BoundsAttrFlags Flags = Sema::BoundsAttrFlags::get(AL.getKind());
+  if (!S.ValidateBoundsAttrTypeShape(FD->getType(), AL.getLoc(), AL.getRange(),
+                                     Flags, /*AttrSpelling=*/{},
+                                     /*AllowRedecl=*/false, /*AttrArg=*/nullptr,
+                                     /*UpdateFlags=*/false))
+    return;
+  if (S.CheckCountedByAttrOnField(FD, CountExpr, Flags.CountInBytes,
+                                  Flags.OrNull))
     return;
 
   QualType CAT = S.BuildCountAttributedArrayOrPointerType(
-      FD->getType(), CountExpr, CountInBytes, OrNull);
-  FD->setType(CAT);
+      FD->getType(), CountExpr, Flags.CountInBytes, Flags.OrNull);
+  if (!CAT.isNull())
+    FD->setType(CAT);
 }
 
 static void handleFunctionReturnThunksAttr(Sema &S, Decl *D,
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index ba9d0f14da5f58..6ce17f392ac207 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -6444,6 +6444,7 @@ GetTypeSourceInfoForDeclarator(TypeProcessingState &State,
         break;
       }
 
+      case TypeLoc::CountAttributed:
       case TypeLoc::Adjusted:
       case TypeLoc::BTFTagAttributed: {
         CurrTL = CurrTL.getNextTypeLoc().getUnqualifiedLoc();
@@ -9067,8 +9068,8 @@ static void HandleHLSLParamModifierAttr(TypeProcessingState &State,
 /// Chunks are ordered from the identifier out. In
 /// `int *__counted_by(n) *pp`, the attributed pointer is chunk 1, enclosed by
 /// the pointer at chunk 0, so its nesting level is 1.
-[[maybe_unused]] static unsigned getPointerNestLevel(TypeProcessingState &state,
-                                                     unsigned chunkIndex) {
+static unsigned getPointerNestLevel(TypeProcessingState &state,
+                                    unsigned chunkIndex) {
   unsigned pointerNestLevel = 0;
   const auto &stateDeclarator = state.getDeclarator();
   assert(chunkIndex <= stateDeclarator.getNumTypeObjects());
@@ -9087,6 +9088,40 @@ static void HandleHLSLParamModifierAttr(TypeProcessingState &State,
   return pointerNestLevel;
 }
 
+static void HandleCountedByAttrOnType(TypeProcessingState &State,
+                                      QualType &CurType, ParsedAttr &Attr) {
+  Sema &S = State.getSema();
+
+  // This attribute is only supported in C.
+  if (!Attr.diagnoseLangOpts(S)) {
+    Attr.setInvalid();
+    return;
+  }
+
+  auto *CountExpr = Attr.getArgAsExpr(0);
+  if (!CountExpr)
+    return;
+
+  unsigned chunkIndex = State.getCurrentChunkIndex();
+  unsigned pointerNestLevel = getPointerNestLevel(State, chunkIndex);
+
+  Sema::BoundsAttrFlags Flags;
+  if (!S.ValidateBoundsAttrTypeForTypePosition(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::ProcessLateParsedTypeAttr(LateParsedAttribute *LA, QualType &type) {
   assert(GetLateParsedTypeAttrCallback);
   ParsedAttr::Kind AttrKind;
@@ -9358,6 +9393,19 @@ static void processTypeAttrs(TypeProcessingState &state, QualType &type,
       break;
     }
 
+    case ParsedAttr::AT_CountedBy:
+    case ParsedAttr::AT_CountedByOrNull:
+    case ParsedAttr::AT_SizedBy:
+    case ParsedAttr::AT_SizedByOrNull: {
+      // XXX: The late-parsing path hasn't been switched to the new mechanism to
+      // properly support type-position attributes yet.
+      if (!state.getSema().getLangOpts().ExperimentalLateParseAttributes) {
+        HandleCountedByAttrOnType(state, type, attr);
+        attr.setUsedAsTypeAttr();
+      }
+      break;
+    }
+
     MS_TYPE_ATTRS_CASELIST:
       if (!handleMSPointerTypeQualifierAttr(state, attr, type))
         attr.setUsedAsTypeAttr();
@@ -10129,7 +10177,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 = getBoundsAttrKind(BoundsAttrFlags{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 cd4e84776a73f774b573f4b6809e2e2fb46b0aaa Mon Sep 17 00:00:00 2001
From: Yeoul Na <yeoul_na at apple.com>
Date: Wed, 7 Oct 2026 15:43:39 -0700
Subject: [PATCH 5/9] Update tests

---
 .../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 -----------------
 ...ruct-ptrs-completable-incomplete-pointee.c | 13 +++++-----
 .../attr-counted-by-or-null-struct-ptrs.c     | 16 ++++++++----
 ...ruct-ptrs-completable-incomplete-pointee.c |  9 ++++---
 clang/test/Sema/attr-counted-by-struct-ptrs.c | 16 ++++++++----
 .../Sema/attr-sized-by-or-null-struct-ptrs.c  | 16 ++++++++----
 clang/test/Sema/attr-sized-by-struct-ptrs.c   | 16 ++++++++----
 10 files changed, 56 insertions(+), 127 deletions(-)

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-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..955e781b5679b3 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
@@ -1,5 +1,5 @@
-// RUN: %clang_cc1 -fsyntax-only -verify %s
-// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -verify %s
+// RUN: %clang_cc1 -fsyntax-only -verify=expected,immediate %s
+// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -verify=expected,late %s
 
 #define __counted_by_or_null(f)  __attribute__((counted_by_or_null(f)))
 
@@ -616,10 +616,11 @@ 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'?}}
+  // The attribute is written on the element type, so it's a nested pointer.
+  // With -fexperimental-late-parse-attributes it's still treated as an
+  // attribute on the array until late parsing handles type attributes.
+  // late-error at +2{{'counted_by_or_null' only applies to pointers; did you mean to use 'counted_by'?}}
+  // immediate-error at +1{{'counted_by_or_null' attribute on nested pointer type is not allowed}}
   struct IncompleteTy3* __counted_by_or_null(size) arr[];
 };
 
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..1bd625b0329cc6 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
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -fsyntax-only -Wpointer-arith -verify %s
+// RUN: %clang_cc1 -fsyntax-only -Wpointer-arith -verify=expected,immediate %s
 // RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -Wpointer-arith -verify %s
 
 #define __counted_by_or_null(f)  __attribute__((counted_by_or_null(f)))
@@ -158,10 +158,13 @@ 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.
+// Forbidden: the pointee is a function type. With
+// -fexperimental-late-parse-attributes it's still treated as a declaration
+// attribute on the outermost pointer until late parsing handles type
+// attributes.
 struct on_member_pointer_fn_ptr_ty_ty_pos_inner {
   int count;
+  // immediate-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);
 };
 
@@ -181,9 +184,12 @@ 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.
+  // Nested bounds attributes are not allowed. With
+  // -fexperimental-late-parse-attributes it's still treated as a declaration
+  // attribute on the outermost pointer until late parsing handles type
+  // attributes.
   int count;
+  // immediate-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..073c039b3810a4 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
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -fsyntax-only -verify %s
+// RUN: %clang_cc1 -fsyntax-only -verify=expected,immediate %s
 // RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -verify %s
 
 #define __counted_by(f)  __attribute__((counted_by(f)))
@@ -616,9 +616,10 @@ 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.
+  // The attribute is written on the element type, so it's a nested pointer.
+  // With -fexperimental-late-parse-attributes it's still treated as an
+  // attribute on the array until late parsing handles type attributes.
+  // immediate-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..cfa9d10dba78b0 100644
--- a/clang/test/Sema/attr-counted-by-struct-ptrs.c
+++ b/clang/test/Sema/attr-counted-by-struct-ptrs.c
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -fsyntax-only -Wpointer-arith -verify %s
+// RUN: %clang_cc1 -fsyntax-only -Wpointer-arith -verify=expected,immediate %s
 // RUN: %clang_cc1 -fsyntax-only -Wpointer-arith -fexperimental-late-parse-attributes %s -verify
 
 #define __counted_by(f)  __attribute__((counted_by(f)))
@@ -157,10 +157,13 @@ 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.
+// Forbidden: the pointee is a function type. With
+// -fexperimental-late-parse-attributes it's still treated as a declaration
+// attribute on the outermost pointer until late parsing handles type
+// attributes.
 struct on_member_pointer_fn_ptr_ty_ty_pos_inner {
   int count;
+  // immediate-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);
 };
 
@@ -180,9 +183,12 @@ 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.
+  // Nested bounds attributes are not allowed. With
+  // -fexperimental-late-parse-attributes it's still treated as a declaration
+  // attribute on the outermost pointer until late parsing handles type
+  // attributes.
   int count;
+  // immediate-error at +1{{'counted_by' attribute on nested pointer type is not allowed}}
   struct size_known *__counted_by(count) *buf;
 };
 
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..201d34c4d20676 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
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -fsyntax-only -verify %s
+// RUN: %clang_cc1 -fsyntax-only -verify=expected,immediate %s
 // RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -verify %s
 
 #define __sized_by_or_null(f)  __attribute__((sized_by_or_null(f)))
@@ -149,10 +149,13 @@ 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.
+// Forbidden: the pointee is a function type. With
+// -fexperimental-late-parse-attributes it's still treated as a declaration
+// attribute on the outermost pointer until late parsing handles type
+// attributes.
 struct on_member_pointer_fn_ptr_ty_ty_pos_inner {
   int size;
+  // immediate-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);
 };
 
@@ -167,9 +170,12 @@ 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.
+  // Nested bounds attributes are not allowed. With
+  // -fexperimental-late-parse-attributes it's still treated as a declaration
+  // attribute on the outermost pointer until late parsing handles type
+  // attributes.
   int size;
+  // immediate-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..adfa8d82f7d906 100644
--- a/clang/test/Sema/attr-sized-by-struct-ptrs.c
+++ b/clang/test/Sema/attr-sized-by-struct-ptrs.c
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -fsyntax-only -verify %s
+// RUN: %clang_cc1 -fsyntax-only -verify=expected,immediate %s
 // RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -verify %s
 
 #define __sized_by(f)  __attribute__((sized_by(f)))
@@ -149,10 +149,13 @@ 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.
+// Forbidden: the pointee is a function type. With
+// -fexperimental-late-parse-attributes it's still treated as a declaration
+// attribute on the outermost pointer until late parsing handles type
+// attributes.
 struct on_member_pointer_fn_ptr_ty_ty_pos_inner {
   int size;
+  // immediate-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);
 };
 
@@ -167,9 +170,12 @@ 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.
+  // Nested bounds attributes are not allowed. With
+  // -fexperimental-late-parse-attributes it's still treated as a declaration
+  // attribute on the outermost pointer until late parsing handles type
+  // attributes.
   int size;
+  // immediate-error at +1{{'sized_by' attribute on nested pointer type is not allowed}}
   struct size_known *__sized_by(size) *buf;
 };
 

>From 13adcb6a9f464f8af2a6ae38448c545899e16500 Mon Sep 17 00:00:00 2001
From: Yeoul Na <yeoul_na at apple.com>
Date: Wed, 7 Oct 2026 17:33:35 -0700
Subject: [PATCH 6/9] Remove deprecated declaration-position counted_by
 attribute handler

---
 clang/lib/Sema/SemaDeclAttr.cpp | 38 ---------------------------------
 clang/lib/Sema/SemaType.cpp     | 11 +++-------
 2 files changed, 3 insertions(+), 46 deletions(-)

diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index dbf0971f0acab7..533aafa938e661 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -7069,37 +7069,6 @@ static void handleNoPFPAttrField(Sema &S, Decl *D, const ParsedAttr &AL) {
   D->addAttr(NoFieldProtectionAttr::Create(S.Context, AL));
 }
 
-// Only reached for a counted_by-family attribute that
-// -fexperimental-late-parse-attributes late-parses as a declaration attribute.
-// This will be removed once late-parsing for type attribute is fully switched
-// over to the new mechanism so it's properly supported.
-// Every other one is a type attribute, handled by HandleCountedByAttrOnType.
-static void handleCountedByAttrField(Sema &S, Decl *D, const ParsedAttr &AL) {
-  // Already applied, or diagnosed, as a type attribute.
-  if (AL.isUsedAsTypeAttr())
-    return;
-
-  auto *CountExpr = AL.getArgAsExpr(0);
-  if (!CountExpr)
-    return;
-
-  auto *FD = cast<FieldDecl>(D);
-  Sema::BoundsAttrFlags Flags = Sema::BoundsAttrFlags::get(AL.getKind());
-  if (!S.ValidateBoundsAttrTypeShape(FD->getType(), AL.getLoc(), AL.getRange(),
-                                     Flags, /*AttrSpelling=*/{},
-                                     /*AllowRedecl=*/false, /*AttrArg=*/nullptr,
-                                     /*UpdateFlags=*/false))
-    return;
-  if (S.CheckCountedByAttrOnField(FD, CountExpr, Flags.CountInBytes,
-                                  Flags.OrNull))
-    return;
-
-  QualType CAT = S.BuildCountAttributedArrayOrPointerType(
-      FD->getType(), CountExpr, Flags.CountInBytes, Flags.OrNull);
-  if (!CAT.isNull())
-    FD->setType(CAT);
-}
-
 static void handleFunctionReturnThunksAttr(Sema &S, Decl *D,
                                            const ParsedAttr &AL) {
   StringRef KindStr;
@@ -8194,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 6ce17f392ac207..5935b832222b9f 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -9396,15 +9396,10 @@ static void processTypeAttrs(TypeProcessingState &state, QualType &type,
     case ParsedAttr::AT_CountedBy:
     case ParsedAttr::AT_CountedByOrNull:
     case ParsedAttr::AT_SizedBy:
-    case ParsedAttr::AT_SizedByOrNull: {
-      // XXX: The late-parsing path hasn't been switched to the new mechanism to
-      // properly support type-position attributes yet.
-      if (!state.getSema().getLangOpts().ExperimentalLateParseAttributes) {
-        HandleCountedByAttrOnType(state, type, attr);
-        attr.setUsedAsTypeAttr();
-      }
+    case ParsedAttr::AT_SizedByOrNull:
+      HandleCountedByAttrOnType(state, type, attr);
+      attr.setUsedAsTypeAttr();
       break;
-    }
 
     MS_TYPE_ATTRS_CASELIST:
       if (!handleMSPointerTypeQualifierAttr(state, attr, type))

>From 2cdb5677859def10492903ab1c0dbe67e7e42071 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 7/9] [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                 | 208 ++++++++++++++++--
 clang/lib/Sema/SemaType.cpp                   |  60 +++++
 .../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 |  42 ++--
 .../attr-counted-by-or-null-struct-ptrs.c     |  22 +-
 ...ruct-ptrs-completable-incomplete-pointee.c |  35 ++-
 clang/test/Sema/attr-counted-by-struct-ptrs.c |  26 +--
 .../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  |  22 +-
 clang/test/Sema/attr-sized-by-struct-ptrs.c   |  20 +-
 16 files changed, 417 insertions(+), 322 deletions(-)

diff --git a/clang/include/clang/AST/TypeBase.h b/clang/include/clang/AST/TypeBase.h
index 6373794bb69f54..57d7e575e992ce 100644
--- a/clang/include/clang/AST/TypeBase.h
+++ b/clang/include/clang/AST/TypeBase.h
@@ -3458,7 +3458,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 7207bd972a4976..532c8dfaf50256 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -297,6 +297,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;
@@ -2251,6 +2253,8 @@ class Parser : public CodeCompletionHandler {
       ParsedAttributes Attrs(AttrFactory);
       ParseGNUAttributes(Attrs, LateAttrs, &D);
       D.takeAttributesAppending(Attrs);
+      if (LateAttrs)
+        Parser::TakeTypeAttrsAppendingFrom(D.getLateAttributes(), *LateAttrs);
     }
   }
 
@@ -8176,6 +8180,13 @@ class Parser : public CodeCompletionHandler {
   GetLateParsedTypeAttrCallback(LateParsedAttribute *LA, ParsedAttr::Kind &Kind,
                                 SourceLocation &Loc);
 
+  /// 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 2fbf49c5c82ebb..873fca46b132fe 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;
@@ -2547,22 +2549,27 @@ class Sema final : public SemaBase {
       QualType Ty, AttributeCommonInfo::Kind AttrKind, SourceLocation AttrLoc,
       SourceRange AttrRange, unsigned PointerNestLevel, BoundsAttrFlags &Flags);
 
-  /// 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 7e85d32366d83b..cc57efbda796c7 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,15 +185,26 @@ bool Parser::ParseSingleGNUAttribute(ParsedAttributes &Attrs,
     return false;
   }
 
+  ParsedAttr::Kind AttrKind = ParsedAttr::getParsedKind(
+      AttrName, nullptr, ParsedAttr::Form::GNU().getSyntax());
+
   bool LateParse = false;
+  // Whether to late-parse the attribute as a type attribute, completed by the
+  // enclosing record, rather than as a declaration attribute.
+  bool LateParseAsTypeAttr = false;
   if (!LateAttrs)
     LateParse = false;
   else if (LateAttrs->lateAttrParseExperimentalExtOnly()) {
     // The caller requested that this attribute **only** be late
     // parsed for `LateAttrParseExperimentalExt` attributes. This will
     // only be late parsed if the experimental language option is enabled.
+    // A list for type attributes only late-parses type attributes; nothing
+    // reads declaration attributes back from it.
+    bool IsTypeAttr = IsAttributeTypeAttr(AttrKind);
     LateParse = getLangOpts().ExperimentalLateParseAttributes &&
-                IsAttributeLateParsedExperimentalExt(*AttrName);
+                IsAttributeLateParsedExperimentalExt(*AttrName) &&
+                (IsTypeAttr || !LateAttrs->lateAttrParseTypeAttrOnly());
+    LateParseAsTypeAttr = LateParse && IsTypeAttr;
   } else {
     // The caller did not restrict late parsing to only
     // `LateAttrParseExperimentalExt` attributes so late parse
@@ -193,10 +222,27 @@ bool Parser::ParseSingleGNUAttribute(ParsedAttributes &Attrs,
   }
 
   // Handle attributes with arguments that require late parsing.
-  LateParsedAttribute *LA =
-      new LateParsedAttribute(this, *AttrName, AttrNameLoc);
+  LateParsedAttribute *LA = nullptr;
+  if (LateParseAsTypeAttr) {
+    // Only C records (ParseStructUnionBody) late-parse type attributes. Late
+    // parsing for type attributes isn't properly supported in C++ yet.
+    assert(CurRecordLateParsedTypeAttrs && !getLangOpts().CPlusPlus &&
+           "type attributes are only late-parsed in C records");
+    LA = new LateParsedTypeAttribute(this, *AttrName, AttrNameLoc, AttrKind);
+  } 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 (LateParseAsTypeAttr)
+    CurRecordLateParsedTypeAttrs->push_back(cast<LateParsedTypeAttribute>(LA));
+
   // Attributes in a class are parsed at the end of the class, along
   // with other late-parsed declarations.
   if (!ClassStack.empty() && !LateAttrs->parseSoon())
@@ -3174,10 +3220,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);
     }
   }
 }
@@ -3250,12 +3297,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);
 }
@@ -3495,6 +3536,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);
@@ -4027,7 +4072,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);
@@ -4786,8 +4830,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.
@@ -4823,6 +4879,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);
@@ -4851,6 +4909,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))
@@ -4901,9 +4984,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);
 }
@@ -4991,6 +5071,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)) {
@@ -5097,15 +5185,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());
 }
@@ -6611,6 +6732,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());
@@ -6638,26 +6761,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);
@@ -6708,6 +6854,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),
@@ -7221,9 +7369,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.
@@ -7278,7 +7435,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);
 
@@ -7289,6 +7446,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 5935b832222b9f..12164f5e43138a 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -409,6 +409,13 @@ processTypeAttrs(TypeProcessingState &state, QualType &type,
                  TypeAttrLocation TAL, const ParsedAttributesView &attrs,
                  CUDAFunctionTarget CFT = CUDAFunctionTarget::HostDevice);
 
+static bool processLateTypeAttrs(TypeProcessingState &state, QualType &type,
+                                 const LateParsedAttrList &LateAttrs);
+
+static void
+BuildTypeCoupledDecls(Expr *E,
+                      llvm::SmallVectorImpl<TypeCoupledDeclRefInfo> &Decls);
+
 static bool handleFunctionTypeAttr(TypeProcessingState &state, ParsedAttr &attr,
                                    QualType &type, CUDAFunctionTarget CFT);
 
@@ -1516,6 +1523,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.
@@ -4738,6 +4748,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)
@@ -5505,6 +5541,13 @@ 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(state, T, DeclType.LateAttrList);
+
     if (DeclType.Kind != DeclaratorChunk::Paren) {
       if (ExpectNoDerefChunk && !IsNoDerefableChunk(DeclType))
         S.Diag(DeclType.Loc, diag::warn_noderef_on_non_pointer_or_array);
@@ -5685,6 +5728,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);
 
@@ -9154,6 +9199,21 @@ bool Sema::ProcessLateParsedTypeAttr(LateParsedAttribute *LA, QualType &type) {
   return true;
 }
 
+static bool processLateTypeAttrs(TypeProcessingState &state, QualType &type,
+                                 const LateParsedAttrList &LateAttrs) {
+
+  if (LateAttrs.empty())
+    return true;
+
+  Sema &S = state.getSema();
+
+  for (auto *LA : LateAttrs)
+    if (!S.ProcessLateParsedTypeAttr(LA, type))
+      return false;
+
+  return true;
+}
+
 static void processTypeAttrs(TypeProcessingState &state, QualType &type,
                              TypeAttrLocation TAL,
                              const ParsedAttributesView &attrs,
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 955e781b5679b3..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
@@ -1,5 +1,5 @@
-// RUN: %clang_cc1 -fsyntax-only -verify=expected,immediate %s
-// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -verify=expected,late %s
+// RUN: %clang_cc1 -fsyntax-only -verify %s
+// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -verify %s
 
 #define __counted_by_or_null(f)  __attribute__((counted_by_or_null(f)))
 
@@ -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,17 +616,13 @@ struct IncompleteTy3;
 
 struct CBBufFAMofCountedByPtrs {
   int size;
-  // The attribute is written on the element type, so it's a nested pointer.
-  // With -fexperimental-late-parse-attributes it's still treated as an
-  // attribute on the array until late parsing handles type attributes.
-  // late-error at +2{{'counted_by_or_null' only applies to pointers; did you mean to use 'counted_by'?}}
-  // immediate-error at +1{{'counted_by_or_null' attribute on nested pointer type is not allowed}}
+  // 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 1bd625b0329cc6..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
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -fsyntax-only -Wpointer-arith -verify=expected,immediate %s
+// RUN: %clang_cc1 -fsyntax-only -Wpointer-arith -verify %s
 // RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -Wpointer-arith -verify %s
 
 #define __counted_by_or_null(f)  __attribute__((counted_by_or_null(f)))
@@ -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,16 +156,18 @@ struct on_member_pointer_fn_ptr_ty_ty_pos {
   fn_ptr_ty __counted_by_or_null(count) fn_ptr;
 };
 
-// Forbidden: the pointee is a function type. With
-// -fexperimental-late-parse-attributes it's still treated as a declaration
-// attribute on the outermost pointer until late parsing handles type
-// attributes.
 struct on_member_pointer_fn_ptr_ty_ty_pos_inner {
   int count;
-  // immediate-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}}
+  // 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}}
@@ -184,12 +184,8 @@ struct on_member_pointer_struct_with_annotated_vla_ty_pos {
 };
 
 struct on_nested_pointer_inner {
-  // Nested bounds attributes are not allowed. With
-  // -fexperimental-late-parse-attributes it's still treated as a declaration
-  // attribute on the outermost pointer until late parsing handles type
-  // attributes.
   int count;
-  // immediate-error at +1{{'counted_by_or_null' attribute on nested pointer type is not allowed}}
+  // 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 073c039b3810a4..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
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -fsyntax-only -verify=expected,immediate %s
+// RUN: %clang_cc1 -fsyntax-only -verify %s
 // RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -verify %s
 
 #define __counted_by(f)  __attribute__((counted_by(f)))
@@ -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,10 +616,7 @@ struct IncompleteTy3;
 
 struct CBBufFAMofCountedByPtrs {
   int size;
-  // The attribute is written on the element type, so it's a nested pointer.
-  // With -fexperimental-late-parse-attributes it's still treated as an
-  // attribute on the array until late parsing handles type attributes.
-  // immediate-error at +1{{'counted_by' attribute on nested pointer type is not allowed}}
+  // 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 cfa9d10dba78b0..f8957ca8b4eba4 100644
--- a/clang/test/Sema/attr-counted-by-struct-ptrs.c
+++ b/clang/test/Sema/attr-counted-by-struct-ptrs.c
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -fsyntax-only -Wpointer-arith -verify=expected,immediate %s
+// RUN: %clang_cc1 -fsyntax-only -Wpointer-arith -verify %s
 // RUN: %clang_cc1 -fsyntax-only -Wpointer-arith -fexperimental-late-parse-attributes %s -verify
 
 #define __counted_by(f)  __attribute__((counted_by(f)))
@@ -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,14 +155,16 @@ struct on_member_pointer_fn_ptr_ty_ty_pos {
   fn_ptr_ty __counted_by(count) fn_ptr;
 };
 
-// Forbidden: the pointee is a function type. With
-// -fexperimental-late-parse-attributes it's still treated as a declaration
-// attribute on the outermost pointer until late parsing handles type
-// attributes.
 struct on_member_pointer_fn_ptr_ty_ty_pos_inner {
   int count;
-  // immediate-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);
+  // 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 {
@@ -183,13 +183,9 @@ struct on_member_pointer_struct_with_annotated_vla_ty_pos {
 };
 
 struct on_nested_pointer_inner {
-  // Nested bounds attributes are not allowed. With
-  // -fexperimental-late-parse-attributes it's still treated as a declaration
-  // attribute on the outermost pointer until late parsing handles type
-  // attributes.
   int count;
-  // immediate-error at +1{{'counted_by' attribute on nested pointer type is not allowed}}
-  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 201d34c4d20676..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
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -fsyntax-only -verify=expected,immediate %s
+// RUN: %clang_cc1 -fsyntax-only -verify %s
 // RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -verify %s
 
 #define __sized_by_or_null(f)  __attribute__((sized_by_or_null(f)))
@@ -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,16 +147,18 @@ struct on_member_pointer_fn_ptr_ty_ty_pos {
   fn_ptr_ty __sized_by_or_null(size) fn_ptr;
 };
 
-// Forbidden: the pointee is a function type. With
-// -fexperimental-late-parse-attributes it's still treated as a declaration
-// attribute on the outermost pointer until late parsing handles type
-// attributes.
 struct on_member_pointer_fn_ptr_ty_ty_pos_inner {
   int size;
-  // immediate-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}}
+  // 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;
@@ -170,12 +170,8 @@ struct on_member_pointer_struct_with_annotated_vla_ty_pos {
 };
 
 struct on_nested_pointer_inner {
-  // Nested bounds attributes are not allowed. With
-  // -fexperimental-late-parse-attributes it's still treated as a declaration
-  // attribute on the outermost pointer until late parsing handles type
-  // attributes.
   int size;
-  // immediate-error at +1{{'sized_by_or_null' attribute on nested pointer type is not allowed}}
+  // 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 adfa8d82f7d906..d56598f012fc2e 100644
--- a/clang/test/Sema/attr-sized-by-struct-ptrs.c
+++ b/clang/test/Sema/attr-sized-by-struct-ptrs.c
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -fsyntax-only -verify=expected,immediate %s
+// RUN: %clang_cc1 -fsyntax-only -verify %s
 // RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -verify %s
 
 #define __sized_by(f)  __attribute__((sized_by(f)))
@@ -149,16 +149,18 @@ struct on_member_pointer_fn_ptr_ty_ty_pos {
   fn_ptr_ty __sized_by(size) fn_ptr;
 };
 
-// Forbidden: the pointee is a function type. With
-// -fexperimental-late-parse-attributes it's still treated as a declaration
-// attribute on the outermost pointer until late parsing handles type
-// attributes.
 struct on_member_pointer_fn_ptr_ty_ty_pos_inner {
   int size;
-  // immediate-error at +1{{'sized_by' cannot be applied to a pointer with pointee of unknown size because 'void (void)' is a function type}}
+  // 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;
@@ -170,12 +172,8 @@ struct on_member_pointer_struct_with_annotated_vla_ty_pos {
 };
 
 struct on_nested_pointer_inner {
-  // Nested bounds attributes are not allowed. With
-  // -fexperimental-late-parse-attributes it's still treated as a declaration
-  // attribute on the outermost pointer until late parsing handles type
-  // attributes.
   int size;
-  // immediate-error at +1{{'sized_by' attribute on nested pointer type is not allowed}}
+  // expected-error at +1{{'sized_by' attribute on nested pointer type is not allowed}}
   struct size_known *__sized_by(size) *buf;
 };
 

>From e5732b687b21c5be7d025cc0e9148420a265d708 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 8/9] [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;
+};

>From 5db6c3a8200e09985107cd99f98db16c07f67bae Mon Sep 17 00:00:00 2001
From: Holo-xy <mohammedashraf4599 at gmail.com>
Date: Fri, 9 Oct 2026 17:45:12 +0300
Subject: [PATCH 9/9] [BoundsSafety] Support the counted_by family on function
 parameters

Allow counted_by, sized_by and their _or_null variants on function
parameters. The count is parsed where it is written, also with
-fexperimental-late-parse-attributes, so it names a parameter declared
before the annotated one.

  - The count must name a parameter of the function, or of an enclosing
    function declarator.
  - One pointer level below a parameter may have a count, as for an out
    parameter, and a parameter's count may dereference a parameter:
    `int *__counted_by(*len) *buf`.
  - An array parameter's count moves to the pointer it adjusts to. An
    array with an explicit size cannot also have a count.
  - A pointer may have only one count.
  - Redeclarations must repeat the counts on their parameters' pointers,
    except for functions declared in system headers.
  - Counts on K&R parameter declarations are dropped.

The parameters of a function-pointer field are parsed with the
callback, not with the record, so their counts do not resolve against
the record's fields.

Only a declarator's own count is checked for nesting, so a count that
comes through __typeof__ is no longer rejected as nested. This also
fixes an assertion on `__typeof__(s->p) (**x)` for a counted field.

CodeGen does not use parameter counts yet: they bound neither
-fsanitize=array-bounds nor __builtin_dynamic_object_size.
---
 clang/include/clang/Basic/Attr.td             |   8 +-
 .../clang/Basic/DiagnosticSemaKinds.td        |   8 +
 clang/include/clang/Sema/Sema.h               |  35 +++-
 clang/lib/Parse/ParseDecl.cpp                 |  20 +-
 clang/lib/Sema/SemaBoundsSafety.cpp           | 172 +++++++++++++++++-
 clang/lib/Sema/SemaDecl.cpp                   |  39 ++++
 clang/lib/Sema/SemaType.cpp                   | 118 +++++++++---
 .../AST/attr-counted-by-function-params.c     |  66 +++++++
 .../CodeGen/attr-counted-by-function-params.c |  25 +++
 .../attr-counted-by-function-params-redecl.c  | 117 ++++++++++++
 .../Sema/attr-counted-by-function-params.c    | 123 +++++++++++++
 .../attr-counted-by-late-parsed-regressions.c |   7 +-
 12 files changed, 692 insertions(+), 46 deletions(-)
 create mode 100644 clang/test/AST/attr-counted-by-function-params.c
 create mode 100644 clang/test/CodeGen/attr-counted-by-function-params.c
 create mode 100644 clang/test/Sema/attr-counted-by-function-params-redecl.c
 create mode 100644 clang/test/Sema/attr-counted-by-function-params.c

diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 4ef0bd2b5ca7c1..284488d5681902 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -2712,7 +2712,7 @@ def TypeNullUnspecified : TypeAttr {
 
 def CountedBy : DeclOrTypeAttr {
   let Spellings = [Clang<"counted_by">];
-  let Subjects = SubjectList<[Field], ErrorDiag>;
+  let Subjects = SubjectList<[Field, ParmVar], ErrorDiag>;
   let Args = [ExprArgument<"Count">, IntArgument<"NestedLevel", 1>];
   let LateParsed = LateAttrParseExperimentalExt;
   let ParseArgumentsAsUnevaluated = 1;
@@ -2722,7 +2722,7 @@ def CountedBy : DeclOrTypeAttr {
 
 def CountedByOrNull : DeclOrTypeAttr {
   let Spellings = [Clang<"counted_by_or_null">];
-  let Subjects = SubjectList<[Field], ErrorDiag>;
+  let Subjects = SubjectList<[Field, ParmVar], ErrorDiag>;
   let Args = [ExprArgument<"Count">, IntArgument<"NestedLevel", 1>];
   let LateParsed = LateAttrParseExperimentalExt;
   let ParseArgumentsAsUnevaluated = 1;
@@ -2745,7 +2745,7 @@ def PointerFieldProtection : DeclOrTypeAttr {
 
 def SizedBy : DeclOrTypeAttr {
   let Spellings = [Clang<"sized_by">];
-  let Subjects = SubjectList<[Field], ErrorDiag>;
+  let Subjects = SubjectList<[Field, ParmVar], ErrorDiag>;
   let Args = [ExprArgument<"Size">, IntArgument<"NestedLevel", 1>];
   let LateParsed = LateAttrParseExperimentalExt;
   let ParseArgumentsAsUnevaluated = 1;
@@ -2755,7 +2755,7 @@ def SizedBy : DeclOrTypeAttr {
 
 def SizedByOrNull : DeclOrTypeAttr {
   let Spellings = [Clang<"sized_by_or_null">];
-  let Subjects = SubjectList<[Field], ErrorDiag>;
+  let Subjects = SubjectList<[Field, ParmVar], ErrorDiag>;
   let Args = [ExprArgument<"Size">, IntArgument<"NestedLevel", 1>];
   let LateParsed = LateAttrParseExperimentalExt;
   let ParseArgumentsAsUnevaluated = 1;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 868e4d138e89e3..8f81fddbbe15b6 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -7203,6 +7203,14 @@ def err_counted_by_attr_refer_to_itself : Error<
   "'counted_by' cannot refer to the flexible array member %0">;
 def err_count_attr_must_be_in_structure : Error<
   "field %0 in '%select{counted_by|sized_by|counted_by_or_null|sized_by_or_null}1' not inside structure">;
+def err_count_attr_refer_to_non_param : Error<
+  "count expression in function declaration may only reference function parameters">;
+def err_count_attr_on_sized_array_param : Error<
+  "'%select{counted_by|sized_by|counted_by_or_null|sized_by_or_null}0' cannot be applied to an array parameter with an explicit size">;
+def err_count_attr_more_than_one : Error<
+  "pointer cannot have more than one count attribute">;
+def err_count_attr_conflicting_redeclaration : Error<
+  "conflicting '%select{counted_by|sized_by|counted_by_or_null|sized_by_or_null}0' attribute with the previous function declaration">;
 def err_count_attr_argument_not_integer : Error<
   "'%select{counted_by|sized_by|counted_by_or_null|sized_by_or_null}0' requires a non-boolean integer type argument">;
 def err_count_attr_only_support_simple_decl_reference : Error<
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 873fca46b132fe..c06ca2e18166bd 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -2520,6 +2520,12 @@ class Sema final : public SemaBase {
   static BoundsAttributedType::BoundsAttrKind
   getBoundsAttrKind(const BoundsAttrFlags &);
 
+  /// The reference that the count \p E of a counted_by-family attribute is
+  /// made of: \p E itself, or the operand of `*` in \p E, which sets \p IsDeref
+  /// (a parameter's count may be a dereferenced parameter, `*len`). Null if
+  /// \p E is neither.
+  static DeclRefExpr *getCountDeclRef(Expr *E, bool &IsDeref);
+
   /// Check the type shape for counted_by, sized_by and their _or_null variants.
   /// The caller checks declaration context, count expressions, flexible array
   /// member eligibility and nesting.
@@ -2542,12 +2548,14 @@ class Sema final : public SemaBase {
 
   /// Check a counted_by-family attribute in type position and initialize
   /// \p Flags from \p AttrKind. \p PointerNestLevel counts the pointer, array
-  /// and function declarator chunks enclosing the attribute.
+  /// and function declarator chunks enclosing the attribute. \p IsParameter is
+  /// set for a count in a parameter's declarator.
   ///
   /// \returns true if valid, or false after diagnosing a rejected attribute.
   bool ValidateBoundsAttrTypeForTypePosition(
       QualType Ty, AttributeCommonInfo::Kind AttrKind, SourceLocation AttrLoc,
-      SourceRange AttrRange, unsigned PointerNestLevel, BoundsAttrFlags &Flags);
+      SourceRange AttrRange, unsigned PointerNestLevel, BoundsAttrFlags &Flags,
+      bool IsParameter);
 
   /// Perform semantic validation on a FieldDecl with a "counted_by" family
   /// attribute. This is called after the attribute has been attached to the
@@ -2597,6 +2605,29 @@ class Sema final : public SemaBase {
   bool ActOnLateParsedTypeAttrArgument(BoundsAttributedType *BATy,
                                        FieldDecl *FD, Expr *Arg);
 
+  /// Check the count \p E of a counted_by-family attribute on \p ParamTy, the
+  /// declared type of a parameter or the pointer it points to: it must be a
+  /// non-boolean integer naming a parameter or dereferencing one, \p ParamTy
+  /// may not already have a count, and an array parameter may not also have a
+  /// size.
+  ///
+  /// \returns false iff semantically valid.
+  bool CheckCountedByAttrOnParam(QualType ParamTy, Expr *E, bool CountInBytes,
+                                 bool OrNull);
+
+  /// A parameter declared as an array adjusts to a pointer to its element type.
+  /// Move the valid count \p CATy on the array to that pointer.
+  void AdjustCountedArrayParamType(ParmVarDecl *PVD,
+                                   const CountAttributedType *CATy);
+
+  /// Diagnose a redeclaration \p New of \p Old whose parameters differ from
+  /// \p Old's in a count, on a parameter's own pointer or on one it reaches
+  /// through pointers alone.
+  ///
+  /// \returns true iff a difference was diagnosed.
+  bool CheckCountAttributedRedeclaration(const FunctionDecl *New,
+                                         const FunctionDecl *Old);
+
   /// 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/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index cc57efbda796c7..ed76b6d524afb2 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -6768,8 +6768,8 @@ void Parser::ParseDeclaratorInternal(Declarator &D,
     // 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.
+    // back to eager handling instead, as do those of a function-pointer field:
+    // ParseFunctionDeclarator hides the record from the parameter clause.
     bool LateParsingContext =
         (D.getContext() == DeclaratorContext::Member ||
          D.getContext() == DeclaratorContext::Prototype) &&
@@ -7563,10 +7563,18 @@ void Parser::ParseFunctionDeclarator(Declarator &D,
     MaybeParseCXX11Attributes(FnAttrs);
     ProhibitAttributes(FnAttrs);
   } else {
-    if (Tok.isNot(tok::r_paren))
-      ParseParameterDeclarationClause(D, FirstArgAttrs, ParamInfo, EllipsisLoc);
-    else if (RequiresArg)
-      Diag(Tok, diag::err_argument_required_after_attribute);
+    {
+      // A parameter's count names parameters, not the fields of an enclosing
+      // record, so it is not late-parsed with them: parameters are parsed
+      // eagerly.
+      llvm::SaveAndRestore<SmallVectorImpl<LateParsedTypeAttribute *> *> Record(
+          CurRecordLateParsedTypeAttrs, nullptr);
+      if (Tok.isNot(tok::r_paren))
+        ParseParameterDeclarationClause(D, FirstArgAttrs, ParamInfo,
+                                        EllipsisLoc);
+      else if (RequiresArg)
+        Diag(Tok, diag::err_argument_required_after_attribute);
+    }
 
     // OpenCL disallows functions without a prototype, but it doesn't enforce
     // strict prototypes as in C23 because it allows a function definition to
diff --git a/clang/lib/Sema/SemaBoundsSafety.cpp b/clang/lib/Sema/SemaBoundsSafety.cpp
index 7e983a947b293d..3bd8ba78c6f8a1 100644
--- a/clang/lib/Sema/SemaBoundsSafety.cpp
+++ b/clang/lib/Sema/SemaBoundsSafety.cpp
@@ -54,6 +54,16 @@ Sema::BoundsAttrFlags Sema::BoundsAttrFlags::get(AttributeCommonInfo::Kind K) {
   return Flags;
 }
 
+DeclRefExpr *Sema::getCountDeclRef(Expr *E, bool &IsDeref) {
+  IsDeref = false;
+  if (auto *UO = dyn_cast<UnaryOperator>(E);
+      UO && UO->getOpcode() == UO_Deref) {
+    E = UO->getSubExpr()->IgnoreImpCasts();
+    IsDeref = true;
+  }
+  return dyn_cast<DeclRefExpr>(E);
+}
+
 static const RecordDecl *GetEnclosingNamedOrTopAnonRecord(const FieldDecl *FD) {
   const auto *RD = FD->getParent();
   // An unnamed struct is treated as anonymous struct at this point.
@@ -169,9 +179,15 @@ bool Sema::ValidateBoundsAttrTypeShape(QualType Ty, SourceLocation AttrLoc,
 
 bool Sema::ValidateBoundsAttrTypeForTypePosition(
     QualType Ty, AttributeCommonInfo::Kind AttrKind, SourceLocation AttrLoc,
-    SourceRange AttrRange, unsigned PointerNestLevel, BoundsAttrFlags &Flags) {
+    SourceRange AttrRange, unsigned PointerNestLevel, BoundsAttrFlags &Flags,
+    bool IsParameter) {
   Flags = BoundsAttrFlags::get(AttrKind);
 
+  // An array parameter adjusts to a pointer, which is what the attribute
+  // describes. Whether the array may have a size is checked on the parameter.
+  if (IsParameter && Ty->isArrayType())
+    Ty = getASTContext().getArrayDecayedType(Ty);
+
   // Preserve the counted_by kind for the GNU void pointer extension to match
   // the field path. The count is still interpreted as a byte size.
   if (!ValidateBoundsAttrTypeShape(Ty, AttrLoc, AttrRange, Flags,
@@ -180,11 +196,8 @@ bool Sema::ValidateBoundsAttrTypeForTypePosition(
     return false;
 
   // Currently, only attributes at the outermost level of the declared type
-  // are supported.
-  //
-  // FIXME: Support indirect parameters such as:
-  //   void f(int *__counted_by(*len) *buf, int *len);
-  // See https://github.com/llvm/llvm-project/issues/166411.
+  // are supported. The caller passes zero for the pointer that a parameter
+  // points to, as for an out parameter: `int *__counted_by(*len) *buf`.
   if (PointerNestLevel > 0) {
     Diag(AttrLoc, diag::err_counted_by_on_nested_pointer)
         << getBoundsAttrKind(Flags);
@@ -273,6 +286,153 @@ bool Sema::CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes,
   return false;
 }
 
+bool Sema::CheckCountedByAttrOnParam(QualType ParamTy, Expr *E,
+                                     bool CountInBytes, bool OrNull) {
+  // An invalid count was already diagnosed.
+  if (E->containsErrors())
+    return true;
+
+  // A second count would replace the first, or hide it.
+  if (ParamTy->getAs<CountAttributedType>()) {
+    Diag(E->getBeginLoc(), diag::err_count_attr_more_than_one);
+    return true;
+  }
+
+  // An array parameter adjusts to a pointer, which the count then describes.
+  // An array with an explicit size is not supported: in the -fbounds-safety
+  // programming model, its size becomes that pointer's count.
+  unsigned Kind = getCountAttrKind(CountInBytes, OrNull);
+  if (ParamTy->isArrayType() && !ParamTy->isIncompleteArrayType()) {
+    Diag(E->getBeginLoc(), diag::err_count_attr_on_sized_array_param) << Kind;
+    return true;
+  }
+
+  if (!E->getType()->isIntegerType() || E->getType()->isBooleanType()) {
+    Diag(E->getBeginLoc(), diag::err_count_attr_argument_not_integer)
+        << Kind << E->getSourceRange();
+    return true;
+  }
+
+  bool IsDeref;
+  auto *DRE = getCountDeclRef(E, IsDeref);
+  if (!DRE) {
+    Diag(E->getBeginLoc(),
+         diag::err_count_attr_only_support_simple_decl_reference)
+        << Kind << E->getSourceRange();
+    return true;
+  }
+
+  // The parameters of an enclosing function declarator are in scope too.
+  if (!isa<ParmVarDecl>(DRE->getDecl())) {
+    Diag(E->getBeginLoc(), diag::err_count_attr_refer_to_non_param)
+        << E->getSourceRange();
+    return true;
+  }
+  return false;
+}
+
+void Sema::AdjustCountedArrayParamType(ParmVarDecl *PVD,
+                                       const CountAttributedType *CATy) {
+  if (!CATy->desugar()->isArrayType())
+    return;
+  // The parameter's type is already adjusted, with the qualifiers written
+  // outside the count moved to the element (C99 6.7.3p8), but the decayed
+  // pointer does not carry the count. Put it back on the pointer.
+  PVD->setType(Context.getCountAttributedType(
+      PVD->getType(), CATy->getCountExpr(), CATy->isCountInBytes(),
+      CATy->isOrNull(), CATy->getCoupledDecls()));
+}
+
+/// Whether the count \p E in a declaration of \p FD names a parameter of a
+/// function, block or method whose body encloses that declaration.
+static bool namesEnclosingParam(Expr *E, const FunctionDecl *FD) {
+  bool IsDeref;
+  const DeclRefExpr *DRE = Sema::getCountDeclRef(E, IsDeref);
+  const auto *PVD = DRE ? dyn_cast<ParmVarDecl>(DRE->getDecl()) : nullptr;
+  if (!PVD)
+    return false;
+  // The parameters of a prototype written in that body, such as a typedef's,
+  // have the same context, so check that this is one of the body's own.
+  const DeclContext *DC = PVD->getDeclContext();
+  ArrayRef<ParmVarDecl *> Params;
+  if (const auto *F = dyn_cast<FunctionDecl>(DC))
+    Params = F->parameters();
+  else if (const auto *B = dyn_cast<BlockDecl>(DC))
+    Params = B->parameters();
+  else if (const auto *M = dyn_cast<ObjCMethodDecl>(DC))
+    Params = M->parameters();
+  return llvm::is_contained(Params, PVD) &&
+         DC->LexicallyEncloses(FD->getLexicalDeclContext());
+}
+
+/// Whether \p New and \p Old, counts in two declarations \p NewFD and \p OldFD
+/// of a function, name the same thing. A parameter compares by its position
+/// and type, so the counts of both declarations can name their own parameters,
+/// or those of the typedef they were declared with, if those have the same
+/// type, qualifiers included. A parameter of an enclosing body is not in
+/// either function's parameter list, so it compares by identity.
+static bool isSameCount(const ASTContext &Ctx, Expr *New, Expr *Old,
+                        const FunctionDecl *NewFD, const FunctionDecl *OldFD) {
+  if (!namesEnclosingParam(New, NewFD) && !namesEnclosingParam(Old, OldFD))
+    return Ctx.hasSameExpr(New, Old);
+  bool NewIsDeref, OldIsDeref;
+  const DeclRefExpr *NewDRE = Sema::getCountDeclRef(New, NewIsDeref);
+  const DeclRefExpr *OldDRE = Sema::getCountDeclRef(Old, OldIsDeref);
+  return NewDRE && OldDRE && NewIsDeref == OldIsDeref &&
+         NewDRE->getDecl() == OldDRE->getDecl();
+}
+
+/// Of \p New and \p Old, the types of one parameter in two declarations
+/// \p NewFD and \p OldFD of a function, the count at the outermost pointer
+/// level where they differ, or null if they agree. Only pointers are followed,
+/// so counts below an _Atomic pointer or in a callback's parameters are not
+/// compared.
+static const CountAttributedType *
+findConflictingCount(const ASTContext &Ctx, QualType New, QualType Old,
+                     const FunctionDecl *NewFD, const FunctionDecl *OldFD) {
+  for (; New->isPointerType() && Old->isPointerType();
+       New = New->getPointeeType(), Old = Old->getPointeeType()) {
+    const auto *NewCATy = New->getAs<CountAttributedType>();
+    const auto *OldCATy = Old->getAs<CountAttributedType>();
+    if (!NewCATy && !OldCATy)
+      continue;
+    if (!NewCATy || !OldCATy)
+      return NewCATy ? NewCATy : OldCATy;
+
+    if (NewCATy->isOrNull() != OldCATy->isOrNull())
+      return NewCATy;
+    // A count of one-byte elements is a size.
+    if (NewCATy->isCountInBytes() != OldCATy->isCountInBytes()) {
+      QualType Pointee = NewCATy->getPointeeType();
+      if (!Pointee->isVoidType() && (Pointee->isIncompleteType() ||
+                                     !Ctx.getTypeSizeInChars(Pointee).isOne()))
+        return NewCATy;
+    }
+    assert(NewCATy->getCountExpr() && OldCATy->getCountExpr());
+    if (!isSameCount(Ctx, NewCATy->getCountExpr(), OldCATy->getCountExpr(),
+                     NewFD, OldFD))
+      return NewCATy;
+  }
+  return nullptr;
+}
+
+bool Sema::CheckCountAttributedRedeclaration(const FunctionDecl *New,
+                                             const FunctionDecl *Old) {
+  for (unsigned I = 0, E = std::min(New->getNumParams(), Old->getNumParams());
+       I != E; ++I) {
+    const ParmVarDecl *NewParam = New->getParamDecl(I);
+    if (const CountAttributedType *CATy =
+            findConflictingCount(Context, NewParam->getType(),
+                                 Old->getParamDecl(I)->getType(), New, Old)) {
+      Diag(NewParam->getBeginLoc(),
+           diag::err_count_attr_conflicting_redeclaration)
+          << CATy->getKind();
+      return true;
+    }
+  }
+  return false;
+}
+
 static void EmitIncompleteCountedByPointeeNotes(Sema &S,
                                                 const CountAttributedType *CATy,
                                                 NamedDecl *IncompleteTyDecl) {
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 2ca6ecb99c3365..f3f3b7207d6e24 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -4057,6 +4057,16 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD, Scope *S,
     return true;
   }
 
+  // Counts are part of the function's interface, so a redeclaration has to
+  // repeat those on its parameters' pointers. Functions declared in system
+  // headers are exempt: a program may redeclare them with counts.
+  if (!SourceMgr.isInSystemHeader(Old->getLocation()) &&
+      !SourceMgr.isInSystemHeader(New->getLocation()) &&
+      CheckCountAttributedRedeclaration(New, Old)) {
+    Diag(OldLocation, PrevDiag) << Old << Old->getType();
+    return true;
+  }
+
   QualType OldQTypeForComparison = OldQType;
   if (Context.hasAnyFunctionEffects()) {
     const auto OldFX = Old->getFunctionEffects();
@@ -15893,6 +15903,27 @@ static void CheckExplicitObjectParameter(Sema &S, ParmVarDecl *P,
     LSI->ExplicitObjectParameter = P;
 }
 
+/// The count written on \p T itself: a CountAttributedType under at most the
+/// parentheses, type attributes and macro qualifiers written with it, which
+/// Type::getAsAdjusted also looks through. Null if there is none.
+static const CountAttributedType *getWrittenCountAttributedType(QualType T) {
+  const Type *Ty = T.getTypePtr();
+  while (true) {
+    if (const auto *CATy = dyn_cast<CountAttributedType>(Ty))
+      return CATy;
+    if (const auto *A = dyn_cast<AttributedType>(Ty))
+      Ty = A->getModifiedType().getTypePtr();
+    else if (const auto *A = dyn_cast<BTFTagAttributedType>(Ty))
+      Ty = A->getWrappedType().getTypePtr();
+    else if (const auto *P = dyn_cast<ParenType>(Ty))
+      Ty = P->getInnerType().getTypePtr();
+    else if (const auto *M = dyn_cast<MacroQualifiedType>(Ty))
+      Ty = M->getUnderlyingType().getTypePtr();
+    else
+      return nullptr;
+  }
+}
+
 Decl *Sema::ActOnParamDeclarator(Scope *S, Declarator &D,
                                  SourceLocation ExplicitThisLoc) {
   const DeclSpec &DS = D.getDeclSpec();
@@ -16015,6 +16046,14 @@ Decl *Sema::ActOnParamDeclarator(Scope *S, Declarator &D,
 
   ProcessDeclAttributes(S, New, D);
 
+  // A count written on an array parameter, also under other type attributes
+  // written with it, moves to the pointer the parameter adjusts to; it was
+  // checked where it was applied. One that comes with the array's type through
+  // a typedef or `__typeof__` belongs to another declaration and stays behind.
+  if (const CountAttributedType *CATy =
+          getWrittenCountAttributedType(TInfo->getType()))
+    AdjustCountedArrayParamType(New, CATy);
+
   if (D.getDeclSpec().isModulePrivateSpecified())
     Diag(New->getLocation(), diag::err_module_private_local)
         << 1 << New << SourceRange(D.getDeclSpec().getModulePrivateSpecLoc())
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 12164f5e43138a..7bfed54b59f467 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -181,6 +181,13 @@ static void diagnoseBadTypeAttribute(Sema &S, const ParsedAttr &attr,
   case ParsedAttr::AT_TypeNullableResult:                                      \
   case ParsedAttr::AT_TypeNullUnspecified
 
+// Bounds attributes of the counted_by family.
+#define COUNTED_BY_TYPE_ATTRS_CASELIST                                         \
+  case ParsedAttr::AT_CountedBy:                                               \
+  case ParsedAttr::AT_CountedByOrNull:                                         \
+  case ParsedAttr::AT_SizedBy:                                                 \
+  case ParsedAttr::AT_SizedByOrNull
+
 namespace {
   /// An object which stores processing state for the entire
   /// GetTypeForDeclarator process.
@@ -4364,6 +4371,39 @@ static bool shouldHaveNullability(QualType T) {
              T->getCanonicalTypeInternal());
 }
 
+/// Whether the count on declarator chunk \p ChunkIndex of \p D (on its
+/// declaration specifiers if \p ChunkIndex is the number of chunks) is on the
+/// pointer that a parameter's own pointer or array points to, as for an out
+/// parameter: `int *__counted_by(*len) *buf`. Only that one level may have a
+/// count, so the parameter's own type may not have one too.
+static bool isIndirectParamCount(const Declarator &D, unsigned ChunkIndex) {
+  if (D.getContext() != DeclaratorContext::Prototype)
+    return false;
+
+  auto IsCount = [](const ParsedAttr &A) {
+    switch (A.getKind()) {
+    COUNTED_BY_TYPE_ATTRS_CASELIST:
+      return true;
+    default:
+      return false;
+    }
+  };
+  if (llvm::any_of(D.getAttributes(), IsCount))
+    return false;
+
+  unsigned Levels = 0;
+  for (unsigned I = 0; I != ChunkIndex; ++I) {
+    const DeclaratorChunk &Chunk = D.getTypeObject(I);
+    if (Chunk.isParen())
+      continue;
+    if ((Chunk.Kind != DeclaratorChunk::Pointer &&
+         Chunk.Kind != DeclaratorChunk::Array) ||
+        ++Levels > 1 || llvm::any_of(Chunk.getAttrs(), IsCount))
+      return false;
+  }
+  return Levels == 1;
+}
+
 static TypeSourceInfo *GetFullTypeForDeclarator(TypeProcessingState &state,
                                                 QualType declSpecType,
                                                 TypeSourceInfo *TInfo) {
@@ -4752,12 +4792,22 @@ static TypeSourceInfo *GetFullTypeForDeclarator(TypeProcessingState &state,
     // 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.
+    // maintained, so diagnose as soon as a chunk is about to wrap one. The
+    // eager path diagnoses the nesting where it applies the attribute. The
+    // pointer that a parameter points to may have a count, as for an out
+    // parameter.
+    //
+    // Only this declarator's own count is checked. A late-parsed count is
+    // applied after the other attributes in its position, so at most the
+    // parentheses of Paren chunks sit over it. One reached through other sugar,
+    // such as `__typeof__(p)` or a typedef of it, belongs to another
+    // declaration and was checked there.
     if (DeclType.Kind == DeclaratorChunk::Pointer ||
         DeclType.Kind == DeclaratorChunk::Array) {
-      if (const auto *CATy = T->getAs<CountAttributedType>()) {
+      const auto *CATy =
+          dyn_cast<CountAttributedType>(T.IgnoreParens().getTypePtr());
+      if (CATy && !(CATy->desugar()->isPointerType() &&
+                    isIndirectParamCount(D, chunkIndex + 1))) {
         // 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
@@ -9147,13 +9197,36 @@ static void HandleCountedByAttrOnType(TypeProcessingState &State,
   if (!CountExpr)
     return;
 
+  // Counts are not supported on K&R parameter declarations, which C23
+  // removed; drop the count.
+  const Declarator &D = State.getDeclarator();
+  if (D.getContext() == DeclaratorContext::KNRTypeList)
+    return;
+
   unsigned chunkIndex = State.getCurrentChunkIndex();
   unsigned pointerNestLevel = getPointerNestLevel(State, chunkIndex);
 
+  // A parameter may point to a counted pointer, as an out parameter does:
+  // `int *__counted_by(*len) *buf`. That count is nested, but allowed.
+  if (pointerNestLevel == 1 && CurType->isPointerType() &&
+      isIndirectParamCount(D, chunkIndex))
+    pointerNestLevel = 0;
+
+  bool IsParameter = D.getContext() == DeclaratorContext::Prototype;
+
   Sema::BoundsAttrFlags Flags;
-  if (!S.ValidateBoundsAttrTypeForTypePosition(CurType, Attr.getKind(),
-                                               Attr.getLoc(), Attr.getRange(),
-                                               pointerNestLevel, Flags)) {
+  if (!S.ValidateBoundsAttrTypeForTypePosition(
+          CurType, Attr.getKind(), Attr.getLoc(), Attr.getRange(),
+          pointerNestLevel, Flags, IsParameter)) {
+    Attr.setInvalid();
+    return;
+  }
+
+  // Unlike a field's count, a parameter's needs no declaration to be checked,
+  // so a rejected one is not applied.
+  if (IsParameter &&
+      S.CheckCountedByAttrOnParam(CurType, CountExpr, Flags.CountInBytes,
+                                  Flags.OrNull)) {
     Attr.setInvalid();
     return;
   }
@@ -9185,10 +9258,11 @@ bool Sema::ProcessLateParsedTypeAttr(LateParsedAttribute *LA, QualType &type) {
   }
 
   // Nesting is checked once the enclosing declarator chunks wrap the node.
+  // Parameters are not late-parsed.
   BoundsAttrFlags Flags;
-  if (!ValidateBoundsAttrTypeForTypePosition(type, AttrKind, AttrNameLoc,
-                                             SourceRange(AttrNameLoc),
-                                             /*PointerNestLevel=*/0, Flags))
+  if (!ValidateBoundsAttrTypeForTypePosition(
+          type, AttrKind, AttrNameLoc, SourceRange(AttrNameLoc),
+          /*PointerNestLevel=*/0, Flags, /*IsParameter=*/false))
     return false;
 
   // The argument isn't parsed yet, so build the type without a count.
@@ -9453,10 +9527,7 @@ static void processTypeAttrs(TypeProcessingState &state, QualType &type,
       break;
     }
 
-    case ParsedAttr::AT_CountedBy:
-    case ParsedAttr::AT_CountedByOrNull:
-    case ParsedAttr::AT_SizedBy:
-    case ParsedAttr::AT_SizedByOrNull:
+    COUNTED_BY_TYPE_ATTRS_CASELIST:
       HandleCountedByAttrOnType(state, type, attr);
       attr.setUsedAsTypeAttr();
       break;
@@ -10189,9 +10260,10 @@ QualType Sema::BuildTypeofExprType(Expr *E, TypeOfKind Kind) {
 static void
 BuildTypeCoupledDecls(Expr *E,
                       llvm::SmallVectorImpl<TypeCoupledDeclRefInfo> &Decls) {
-  // Currently, 'counted_by' only allows direct DeclRefExpr to FieldDecl.
-  auto *CountDecl = cast<DeclRefExpr>(E)->getDecl();
-  Decls.push_back(TypeCoupledDeclRefInfo(CountDecl, /*IsDref*/ false));
+  bool IsDeref;
+  DeclRefExpr *DRE = Sema::getCountDeclRef(E, IsDeref);
+  assert(DRE && "count is not a reference");
+  Decls.push_back(TypeCoupledDeclRefInfo(DRE->getDecl(), IsDeref));
 }
 
 bool Sema::ActOnLateParsedTypeAttrArgument(BoundsAttributedType *BATy,
@@ -10232,13 +10304,15 @@ QualType Sema::BuildCountAttributedArrayOrPointerType(QualType WrappedTy,
                                                       Expr *CountExpr,
                                                       bool CountInBytes,
                                                       bool OrNull) {
-  // Accept any array or pointer type here. For arrays, validation that it's
-  // a flexible array member is deferred until CheckCountedByAttrOnField.
+  // Accept any array or pointer type here. For a field's array, validation that
+  // it's a flexible array member is deferred until CheckCountedByAttrOnField;
+  // a parameter's array is checked by CheckCountedByAttrOnParam.
   assert(WrappedTy->isArrayType() || WrappedTy->isPointerType());
 
-  // Reject non-DeclRefExpr early to avoid cast failure in
-  // BuildTypeCoupledDecls.
-  if (!isa<DeclRefExpr>(CountExpr)) {
+  // Reject anything but a reference early, for BuildTypeCoupledDecls. The
+  // checks of the positions that do not allow a dereferenced one reject it.
+  bool IsDeref;
+  if (!getCountDeclRef(CountExpr, IsDeref)) {
     unsigned Kind = getBoundsAttrKind(BoundsAttrFlags{CountInBytes, OrNull});
     Diag(CountExpr->getBeginLoc(),
          diag::err_count_attr_only_support_simple_decl_reference)
diff --git a/clang/test/AST/attr-counted-by-function-params.c b/clang/test/AST/attr-counted-by-function-params.c
new file mode 100644
index 00000000000000..9f2b19975fd133
--- /dev/null
+++ b/clang/test/AST/attr-counted-by-function-params.c
@@ -0,0 +1,66 @@
+// RUN: %clang_cc1 -verify %s -ast-dump | FileCheck %s
+// RUN: %clang_cc1 -fexperimental-late-parse-attributes -verify %s -ast-dump | FileCheck %s
+
+#define __counted_by(f)  __attribute__((counted_by(f)))
+#define __sized_by_or_null(f)  __attribute__((sized_by_or_null(f)))
+
+int global_count;
+
+// CHECK: FunctionDecl {{.*}} back_ref 'void (int, int * __counted_by(count))'
+// CHECK-NEXT: ParmVarDecl {{.*}} count 'int'
+// CHECK-NEXT: ParmVarDecl {{.*}} buf 'int * __counted_by(count)':'int *'
+void back_ref(int count, int *__counted_by(count) buf);
+
+// An array parameter's count moves to the pointer it adjusts to.
+// CHECK: FunctionDecl {{.*}} array 'void (int, int * __sized_by_or_null(count))'
+// CHECK-NEXT: ParmVarDecl {{.*}} count 'int'
+// CHECK-NEXT: ParmVarDecl {{.*}} arr 'int * __sized_by_or_null(count)':'int *'
+void array(int count, int arr[] __sized_by_or_null(count));
+
+// The array's qualifiers apply to its element (C99 6.7.3p8), also when they are
+// outside the count, as when it is written in the declaration specifiers.
+typedef int int_array[];
+// CHECK: FunctionDecl {{.*}} array_qualified 'void (int, const int * __counted_by(count))'
+// CHECK-NEXT: ParmVarDecl {{.*}} count 'int'
+// CHECK-NEXT: ParmVarDecl {{.*}} arr 'const int * __counted_by(count)':'const int *'
+void array_qualified(int count, const int_array __counted_by(count) arr);
+
+// A type attribute written after the count does not hide it.
+// CHECK: FunctionDecl {{.*}} array_attr_after 'void (int, int * __counted_by(count))'
+void array_attr_after(int count,
+                      int arr[] __counted_by(count) __attribute__((btf_type_tag("t"))));
+// Nor does one that comes from a macro.
+#define __noderef __attribute__((noderef))
+// CHECK: FunctionDecl {{.*}} array_macro_attr_after 'void (int, int * __counted_by(count))'
+void array_macro_attr_after(int count, int arr[] __counted_by(count) __noderef);
+
+// A count that comes with the array's type through '__typeof__' names a field,
+// so it does not move to the pointer the parameter adjusts to.
+struct fam { int n; int arr[] __counted_by(n); };
+// CHECK: FunctionDecl {{.*}} typeof_fam 'void (int *)'
+void typeof_fam(__typeof__(((struct fam *)0)->arr) arr);
+
+// A second count is rejected, so the first stays on the pointer.
+// CHECK: FunctionDecl {{.*}} repeated_array 'void (int, int, int * __counted_by(n))'
+// expected-error at +1{{pointer cannot have more than one count attribute}}
+void repeated_array(int n, int m, int arr[] __counted_by(n) __counted_by(m));
+
+// An out parameter's count, here a dereferenced parameter, is on the pointer
+// that the parameter points to.
+// CHECK: FunctionDecl {{.*}} out 'void (int *, int * __counted_by(*len)*)'
+void out(int *len, int *__counted_by(*len) *buf);
+
+// A rejected count is not applied, and the function stays valid.
+// CHECK: FunctionDecl {{.*}} not_a_param 'void (int *)'
+// expected-error at +1{{count expression in function declaration may only reference function parameters}}
+void not_a_param(int *__counted_by(global_count) buf);
+
+// A nested count is dropped; the parameter's own count stays.
+// CHECK: FunctionDecl {{.*}} both_counted 'void (int, int, int ** __counted_by(m))'
+// expected-error at +1{{'counted_by' attribute on nested pointer type is not allowed}}
+void both_counted(int n, int m, int *__counted_by(n) *__counted_by(m) buf);
+
+// A count on a K&R parameter declaration is dropped.
+// CHECK: FunctionDecl {{.*}} knr 'void (int, int *)'
+// expected-warning at +1{{a function definition without a prototype is deprecated}}
+void knr(count, buf) int count; int *__counted_by(count) buf; {}
diff --git a/clang/test/CodeGen/attr-counted-by-function-params.c b/clang/test/CodeGen/attr-counted-by-function-params.c
new file mode 100644
index 00000000000000..ae7bc6aaf8f3fc
--- /dev/null
+++ b/clang/test/CodeGen/attr-counted-by-function-params.c
@@ -0,0 +1,25 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fsanitize=array-bounds -emit-llvm -o - %s | FileCheck %s
+
+// Test that counted_by on a function parameter is consumed entirely by Sema:
+// unlike a field's count, it bounds neither -fsanitize=array-bounds nor
+// __builtin_dynamic_object_size.
+
+#define __counted_by(f)  __attribute__((counted_by(f)))
+
+// CHECK-LABEL: define dso_local i32 @subscript(
+// CHECK-NOT:     __ubsan_handle_out_of_bounds
+// CHECK:         ret i32
+//
+// Verify: indexing a counted parameter is not bounds checked
+int subscript(int count, int *__counted_by(count) buf, int i) {
+  return buf[i];
+}
+
+// CHECK-LABEL: define dso_local i64 @bdos(
+// CHECK-NOT:     counted_by.load
+// CHECK:         call i64 @llvm.objectsize.i64.p0(
+//
+// Verify: the object size is not computed from the count
+unsigned long bdos(int count, int *__counted_by(count) buf) {
+  return __builtin_dynamic_object_size(buf, 0);
+}
diff --git a/clang/test/Sema/attr-counted-by-function-params-redecl.c b/clang/test/Sema/attr-counted-by-function-params-redecl.c
new file mode 100644
index 00000000000000..65ceb817b5b77a
--- /dev/null
+++ b/clang/test/Sema/attr-counted-by-function-params-redecl.c
@@ -0,0 +1,117 @@
+// RUN: %clang_cc1 -fsyntax-only -Wno-deprecated-non-prototype -verify %s
+// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -Wno-deprecated-non-prototype -verify %s
+
+// Counts are part of a function's interface, so a redeclaration has to repeat
+// them, on a parameter's own pointer and on those it reaches through pointers
+// alone. Each count names an earlier parameter, so it resolves with or without
+// late parsing.
+
+#define __counted_by(f)  __attribute__((counted_by(f)))
+#define __counted_by_or_null(f)  __attribute__((counted_by_or_null(f)))
+#define __sized_by(f)  __attribute__((sized_by(f)))
+
+// A redeclaration or definition may rename the parameters.
+void same(int n, int *__counted_by(n) p);
+void same(int k, int *__counted_by(k) q) { (void)q; }
+void same_out(int *len, int *__counted_by(*len) *buf);
+void same_out(int *size, int *__counted_by(*size) *buf);
+
+// Counting one-byte elements is counting bytes.
+void bytes(int n, char *__counted_by(n) p);
+void bytes(int n, char *__sized_by(n) p);
+void void_bytes(int n, void *__counted_by(n) p);
+void void_bytes(int n, void *__sized_by(n) p);
+
+// A function without a prototype has no parameters to compare.
+void no_proto(int n, int *__counted_by(n) p);
+void no_proto();
+
+// expected-note at +1{{previous declaration is here}}
+void add(int n, int *p);
+// expected-error at +1{{conflicting 'counted_by' attribute with the previous function declaration}}
+void add(int n, int *__counted_by(n) p);
+
+// expected-note at +1{{previous declaration is here}}
+void drop(int n, int *__counted_by(n) p);
+// expected-error at +1{{conflicting 'counted_by' attribute with the previous function declaration}}
+void drop(int n, int *p);
+
+// A count compares the parameter it names by position and type, qualifiers
+// included.
+// expected-note at +1{{previous declaration is here}}
+void qualified_count(int n, int *__counted_by(n) p);
+// expected-error at +1{{conflicting 'counted_by' attribute with the previous function declaration}}
+void qualified_count(const int n, int *__counted_by(n) p);
+
+// expected-note at +1{{previous declaration is here}}
+void other_param(int n, int m, int *__counted_by(n) p);
+// expected-error at +1{{conflicting 'counted_by' attribute with the previous function declaration}}
+void other_param(int n, int m, int *__counted_by(m) p);
+
+// expected-note at +1{{previous declaration is here}}
+void kind(int n, int *__counted_by(n) p);
+// expected-error at +1{{conflicting 'sized_by' attribute with the previous function declaration}}
+void kind(int n, int *__sized_by(n) p);
+
+// expected-note at +1{{previous declaration is here}}
+void or_null(int n, int *__counted_by(n) p);
+// expected-error at +1{{conflicting 'counted_by_or_null' attribute with the previous function declaration}}
+void or_null(int n, int *__counted_by_or_null(n) p);
+
+// The size of an incomplete element is unknown.
+struct incomplete;
+// expected-note at +1{{previous declaration is here}}
+void incomplete_bytes(int n, struct incomplete *__counted_by(n) p);
+// expected-error at +1{{conflicting 'sized_by' attribute with the previous function declaration}}
+void incomplete_bytes(int n, struct incomplete *__sized_by(n) p);
+
+// expected-note at +1{{previous declaration is here}}
+void out_drop(int n, int *__counted_by(n) *buf);
+// expected-error at +1{{conflicting 'counted_by' attribute with the previous function declaration}}
+void out_drop(int n, int **buf);
+
+// expected-note at +1{{previous declaration is here}}
+void out_deref(int n, int *len, int *__counted_by(*len) *buf);
+// expected-error at +1{{conflicting 'counted_by' attribute with the previous function declaration}}
+void out_deref(int n, int *len, int *__counted_by(n) *buf);
+
+// A K&R definition drops its counts.
+// expected-note at +1{{previous declaration is here}}
+void knr(int n, int *__counted_by(n) p);
+// expected-error at +1{{conflicting 'counted_by' attribute with the previous function declaration}}
+void knr(n, p) int n; int *__counted_by(n) p; {}
+
+// A count naming a parameter of an enclosing function compares as that
+// parameter, not by its position.
+// expected-note at +1{{previous declaration is here}}
+void captured(int n, int *__counted_by(n) p);
+void outer(int c) {
+  // expected-error at +1{{conflicting 'counted_by' attribute with the previous function declaration}}
+  void captured(int n, int *__counted_by(c) p);
+  void captured_twice(int n, int *__counted_by(c) p);
+  void captured_twice(int k, int *__counted_by(c) q);
+}
+
+// A function declared with a typedef or '__typeof__' has its own parameters,
+// but its counts name the typedef's, which compare by position as well.
+typedef void fn_type(int n, int *__counted_by(n) p);
+fn_type from_typedef;
+void from_typedef(int n, int *__counted_by(n) p);
+void from_typeof_source(int n, int *__counted_by(n) p);
+__typeof__(from_typeof_source) from_typeof;
+void from_typeof(int n, int *__counted_by(n) p);
+void typedef_in_body(int c) {
+  typedef void local_fn_type(int n, int *__counted_by(n) p);
+  local_fn_type local_from_typedef;
+  void local_from_typedef(int n, int *__counted_by(n) p);
+}
+
+// Functions declared in system headers may be redeclared with counts.
+
+# 1 "system.h" 1 3
+void sys_add(int n, int *p);
+void sys_drop(int n, int *__counted_by(n) p);
+# 115 "attr-counted-by-function-params-redecl.c" 2
+
+void sys_add(int n, int *__counted_by(n) p);
+void sys_drop(int n, int *p);
diff --git a/clang/test/Sema/attr-counted-by-function-params.c b/clang/test/Sema/attr-counted-by-function-params.c
new file mode 100644
index 00000000000000..44bccade927418
--- /dev/null
+++ b/clang/test/Sema/attr-counted-by-function-params.c
@@ -0,0 +1,123 @@
+// RUN: %clang_cc1 -fsyntax-only -verify %s
+// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -verify %s
+
+// The counted_by family on a function parameter. The count is parsed where it
+// is written, also with late parsing, so it names a parameter declared before
+// the annotated one.
+
+#define __counted_by(f)  __attribute__((counted_by(f)))
+#define __counted_by_or_null(f)  __attribute__((counted_by_or_null(f)))
+#define __sized_by(f)  __attribute__((sized_by(f)))
+#define __sized_by_or_null(f)  __attribute__((sized_by_or_null(f)))
+
+typedef int *int_ptr;
+int global_count;
+
+//==============================================================================
+// Valid: the count is declared before the annotated pointer
+//==============================================================================
+
+void back_ref(int count, int *__counted_by(count) buf);
+void back_ref_or_null(int count, int *__counted_by_or_null(count) buf);
+void back_ref_sized(int count, void *__sized_by(count) buf);
+void back_ref_sized_or_null(int count, void *__sized_by_or_null(count) buf);
+
+// Two pointers may share a count, but a pointer has only one.
+void two_buffers(int count, int *__counted_by(count) a,
+                 int *__counted_by(count) b);
+// expected-error at +1{{pointer cannot have more than one count attribute}}
+void repeated(int n, int m, int *buf __counted_by(n) __counted_by(m));
+
+// Written after the declarator, or in the declaration specifiers.
+void trailing(int count, int *buf __counted_by(count));
+void declspec(int count, int_ptr __counted_by(count) buf);
+
+// The C23 spelling is checked the same way.
+void c23_spelling(int count, int *[[clang::counted_by(count)]] buf);
+
+int sum(int count, int *__counted_by(count) buf) {
+  int total = 0;
+  for (int i = 0; i < count; ++i)
+    total += buf[i];
+  return total;
+}
+
+//==============================================================================
+// The count is parsed where it is written
+//==============================================================================
+
+// expected-error at +1{{use of undeclared identifier 'count'}}
+void fwd_ref(int *__counted_by(count) buf, int count);
+
+// A callback field's count names the callback's parameters, not the record's
+// fields, so it is not late-parsed with them.
+struct callback_field {
+  int n;
+  void (*cb)(int len, int *__counted_by(len) p);
+  // expected-error at +1{{use of undeclared identifier 'n'}}
+  void (*cb_field)(int *__counted_by(n) p);
+};
+
+//==============================================================================
+// What the count may name
+//==============================================================================
+
+// A parameter of the function, or of an enclosing function declarator.
+void inner(void (*cb)(int len, int *__counted_by(len) p));
+void outer(int n, void (*cb)(int *__counted_by(n) p));
+
+// expected-error at +1{{count expression in function declaration may only reference function parameters}}
+void not_a_param(int *__counted_by(global_count) buf);
+// expected-error at +1{{'counted_by' requires a non-boolean integer type argument}}
+void not_an_integer(float count, int *__counted_by(count) buf);
+// expected-error at +1{{'counted_by' argument must be a simple declaration reference}}
+void not_a_reference(int count, int *__counted_by(count + 1) buf);
+
+//==============================================================================
+// Array parameters
+//==============================================================================
+
+// An array without a size adjusts to a pointer that carries the count, so each
+// kind is allowed, although a flexible array member takes only counted_by.
+void array(int count, int arr[] __counted_by(count));
+void array_sized_or_null(int count, int arr[] __sized_by_or_null(count));
+// expected-error at +1{{'counted_by' cannot be applied to an array parameter with an explicit size}}
+void array_with_size(int count, int arr[10] __counted_by(count));
+
+// A type attribute written after the count does not hide it, so the count
+// still moves to the pointer.
+void array_attr_after(int count,
+                      int arr[] [[clang::counted_by(count),
+                                  clang::annotate_type("x")]]);
+void array_attr_after(int count, int *__counted_by(count) arr);
+
+//==============================================================================
+// Indirect parameters
+//==============================================================================
+
+// A parameter may point to a counted pointer, as an out parameter does, and a
+// parameter's count may dereference a parameter.
+void out(int *len, int *__counted_by(*len) *buf);
+void out_count(int count, int *__counted_by(count) *buf);
+void out_array(int count, int *__counted_by(count) buf[]);
+void own_deref(int *count, int *__counted_by(*count) buf);
+
+// Only one level down, and not below a pointer that has a count itself.
+// expected-error at +1{{'counted_by' attribute on nested pointer type is not allowed}}
+void two_levels(int count, int *__counted_by(count) **buf);
+// expected-error at +1{{'counted_by' attribute on nested pointer type is not allowed}}
+void both_counted(int n, int m, int *__counted_by(n) *__counted_by(m) buf);
+
+// A callback's return type is below the callback's pointer.
+// expected-error at +1{{'counted_by' attribute on nested pointer type is not allowed}}
+void cb_return(int len, int *__counted_by(len) (*cb)(void));
+
+// A count that comes with another declaration's type, through '__typeof__', was
+// checked where it was written, so it is not nested here.
+void typeof_param(int count, int *__counted_by(count) buf,
+                  __typeof__(buf) **out, __typeof__(buf) (**out_paren));
+struct with_count { int n; int *__counted_by(n) p; };
+void typeof_field(struct with_count *s) {
+  __typeof__(s->p) (**x);
+  (void)x;
+}
diff --git a/clang/test/Sema/attr-counted-by-late-parsed-regressions.c b/clang/test/Sema/attr-counted-by-late-parsed-regressions.c
index 99c196f4232b5b..19f766bf6d78de 100644
--- a/clang/test/Sema/attr-counted-by-late-parsed-regressions.c
+++ b/clang/test/Sema/attr-counted-by-late-parsed-regressions.c
@@ -30,12 +30,7 @@ struct nested_record_declspec_attr {
 // 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}}
+void bwd_ref_param(int n, int *__counted_by(n) p);
 
 // A declaration-specifier-position attribute is shared by every declarator in
 // the declaration, and ConvertDeclSpecToType walks the DeclSpec's late-attribute



More information about the cfe-commits mailing list