[llvm-branch-commits] [clang] [BoundsSafety] Activate late parsing in type-attribute position (PR #233583)

Yeoul Na via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Wed Oct 21 11:46:06 PDT 2026


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

>From 3d0cb49ef83d91417c30eb26ccc692af3c7ea4e1 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 1/5] 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 717fa57691d50..f8d648b32c031 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -7109,37 +7109,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;
@@ -8247,13 +8216,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 76325c9be203e..e9a091777917e 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -9419,15 +9419,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 d2b0cee2e5c1d308ae94015c6c0afd59958edb13 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 2/5] [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.
---
 clang/include/clang/AST/TypeBase.h            |   2 +-
 clang/include/clang/Parse/Parser.h            |  11 +
 clang/include/clang/Sema/Sema.h               |  33 +--
 clang/lib/Parse/ParseDecl.cpp                 | 209 ++++++++++++++++--
 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 +-
 ...warn-thread-safety-late-parsed-decl-spec.c |  48 ++++
 17 files changed, 464 insertions(+), 318 deletions(-)
 create mode 100644 clang/test/Sema/warn-thread-safety-late-parsed-decl-spec.c

diff --git a/clang/include/clang/AST/TypeBase.h b/clang/include/clang/AST/TypeBase.h
index 6373794bb69f5..57d7e575e992c 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 8fff5631c8357..7bd6ebe88e6c5 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -291,6 +291,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;
@@ -2250,6 +2252,8 @@ class Parser : public CodeCompletionHandler {
       ParsedAttributes Attrs(AttrFactory);
       ParseGNUAttributes(Attrs, LateAttrs, &D);
       D.takeAttributesAppending(Attrs);
+      if (LateAttrs)
+        Parser::TakeTypeAttrsAppendingFrom(D.getLateAttributes(), *LateAttrs);
     }
   }
 
@@ -8169,6 +8173,13 @@ class Parser : public CodeCompletionHandler {
 
   static void LateTemplateParserCallback(void *P, LateParsedTemplate &LPT);
 
+  /// 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 9a3810efabd13..f444c0d34691e 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;
@@ -2577,22 +2579,21 @@ class Sema final : public SemaBase {
                                   SourceLocation AttrLoc, SourceRange AttrRange,
                                   unsigned PointerNestLevel, bool OnReturnType);
 
-  /// 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.
+  ///
+  /// \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 b673679a8f56d..1a54e2695ca9c 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,8 +222,17 @@ 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);
+  }
+
 
   // Keep the innermost prototype's parameters available for a late-parsed
   // attribute that is parsed in that prototype's scope, such as one describing
@@ -218,6 +256,12 @@ bool Parser::ParseSingleGNUAttribute(ParsedAttributes &Attrs,
 
   LateAttrs->push_back(LA);
 
+  // Record type attributes against the record currently being parsed; their
+  // arguments become resolvable at its closing brace. This is the C analogue of
+  // how C++ late-parses inline method bodies at the end of the class.
+  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())
@@ -3265,12 +3309,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);
 }
@@ -3510,6 +3548,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);
@@ -4830,8 +4872,24 @@ void Parser::ParseStructDeclaration(
   ParsedAttributes Attrs(AttrFactory);
   MaybeParseCXX11Attributes(Attrs);
 
-  // Parse the common specifier-qualifiers-list piece.
-  ParseSpecifierQualifierList(DS);
+  // 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.
+  unsigned DeclSpecMark =
+      CurRecordLateParsedTypeAttrs ? CurRecordLateParsedTypeAttrs->size() : 0;
+
+  // Parse the common specifier-qualifiers-list piece. Only late-parse type
+  // attributes here; they're moved into the DeclSpec, which every declarator
+  // shares. Declaration attributes such as guarded_by are parsed eagerly, so
+  // the DeclSpec applies them to every declarator too, rather than going to
+  // LateFieldAttrs, which pairs each of them with a single field.
+  LateParsedAttrList DeclSpecLateAttrs = LateParsedAttrList::forTypeAttrs();
+  ParseSpecifierQualifierList(DS, AS_none, DeclSpecContext::DSC_normal,
+                              LateFieldAttrs ? &DeclSpecLateAttrs : nullptr);
+
+  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.
@@ -4867,6 +4925,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);
@@ -4892,9 +4952,40 @@ void Parser::ParseStructDeclaration(
 
     // We're done with this declarator;  invoke the callback.
     Decl *Field = FieldsCallback(DeclaratorInfo);
+    // Pair the late-parsed declaration attributes still in LateFieldAttrs (e.g.
+    // guarded_by, acquired_before) with this field. They are parsed at the
+    // closing brace without a Decl, so this is the only way they find theirs.
+    // Only those written after this declarator get here; declaration-specifier
+    // ones are parsed eagerly, so each belongs to this field alone.
+    // Late-parsed type attributes (the counted_by family) were already moved
+    // out of LateFieldAttrs and are paired with the field below instead.
     if (Field)
       DistributeCLateParsedAttrs(Field, LateFieldAttrs);
 
+    // Record the field each pending late-parsed type attribute belongs to. At
+    // this point, incomplete CountAttributedType is created and pairing it with
+    // the field here means the completion pass at the closing brace needs no
+    // search -- which matters because a certain bounds type (e.g.,
+    // __counted_by_or_null, but not counted_by) may sit nested inside the
+    // field's type (in the future extension), where it cannot be recovered by
+    // just inspecting the field's top-level type.
+    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 FD with attributes from the shared declaration-specifier (every
+      // declarator in this declaration gets appended)
+      Attach(DeclSpecMark, AfterDeclSpecMark);
+      // Attach FD with attributes from this declarator.
+      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))
@@ -4941,9 +5032,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);
 }
@@ -5021,6 +5109,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)) {
@@ -5134,8 +5230,42 @@ void Parser::ParseStructUnionBody(SourceLocation RecordLoc,
                       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());
 }
@@ -6660,6 +6790,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());
@@ -6687,26 +6819,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);
@@ -6757,6 +6912,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),
@@ -7270,9 +7427,18 @@ void Parser::ParseParenDeclarator(Declarator &D) {
   // sort of paren this is.
   //
   ParsedAttributes attrs(AttrFactory);
+  LateParsedAttrList LateAttrs = LateParsedAttrList::forTypeAttrs();
   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.
@@ -7327,7 +7493,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);
 
@@ -7338,6 +7504,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 e9a091777917e..2391a61f9a15d 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);
 
@@ -1512,6 +1519,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.
@@ -4747,6 +4757,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)
@@ -5514,6 +5550,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);
@@ -5694,6 +5737,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);
 
@@ -9177,6 +9222,21 @@ bool Sema::ProcessLateParsedTypeAttr(LateParsedTypeAttrInfo &LA, QualType &QT) {
   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 443ccbbae66db..554bcfbd8d5c7 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 d0c50a733acef..9a1cae59f5282 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 233b729f87ccd..9f91f66b6e1c4 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 955e781b5679b..4d4a6e81e3766 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 1bd625b0329cc..e8ab29cd80181 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 073c039b3810a..f9afe558d0e13 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 cfa9d10dba78b..f8957ca8b4eba 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 cfdf3407332c9..054fa0fec51e1 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 b726a9b005b9b..50d5f57ded023 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 201d34c4d2067..69ce8c5a67c70 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 adfa8d82f7d90..d56598f012fc2 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;
 };
 
diff --git a/clang/test/Sema/warn-thread-safety-late-parsed-decl-spec.c b/clang/test/Sema/warn-thread-safety-late-parsed-decl-spec.c
new file mode 100644
index 0000000000000..92ce5fdfb83bb
--- /dev/null
+++ b/clang/test/Sema/warn-thread-safety-late-parsed-decl-spec.c
@@ -0,0 +1,48 @@
+// RUN: %clang_cc1 -fsyntax-only -Wthread-safety -verify %s
+// RUN: %clang_cc1 -fsyntax-only -Wthread-safety -fexperimental-late-parse-attributes -verify %s
+
+// In declaration-specifier position of a struct member, only counted_by-family
+// type attributes are late-parsed. Declaration attributes such as guarded_by
+// are parsed there eagerly, as without late parsing, so they apply to every
+// declarator through the declaration specifier. They used to be late-parsed
+// and attached to the first declarator only.
+
+#define __counted_by(f) __attribute__((counted_by(f)))
+
+struct __attribute__((capability("mutex"))) mutex {
+  int x;
+};
+
+typedef int *IP;
+
+struct decl_spec_guarded_by {
+  struct mutex mu;
+  __attribute__((guarded_by(mu))) int a, b;
+  __attribute__((pt_guarded_by(mu))) int *p, *q;
+};
+
+void write_guarded(struct decl_spec_guarded_by *s) {
+  s->a = 1; // expected-warning {{writing variable 'a' requires holding mutex 's->mu' exclusively}}
+  s->b = 1; // expected-warning {{writing variable 'b' requires holding mutex 's->mu' exclusively}}
+  *s->p = 1; // expected-warning {{writing the value pointed to by 'p' requires holding mutex 's->mu' exclusively}}
+  *s->q = 1; // expected-warning {{writing the value pointed to by 'q' requires holding mutex 's->mu' exclusively}}
+}
+
+// A late-parsed type attribute and an eager declaration attribute in the same
+// declaration specifier.
+struct decl_spec_counted_by_and_guarded_by {
+  struct mutex mu;
+  int n;
+  IP __counted_by(n) __attribute__((guarded_by(mu))) a, b;
+};
+
+void write_counted_guarded(struct decl_spec_counted_by_and_guarded_by *s) {
+  s->a = 0; // expected-warning {{writing variable 'a' requires holding mutex 's->mu' exclusively}}
+  s->b = 0; // expected-warning {{writing variable 'b' requires holding mutex 's->mu' exclusively}}
+}
+
+// Parsed eagerly, so the mutex must be declared first.
+struct decl_spec_guarded_by_mutex_after {
+  __attribute__((guarded_by(mu))) int a, b; // expected-error {{use of undeclared identifier 'mu'}}
+  struct mutex mu;
+};

>From cff26733a52fddd281632a2eb5b1e1924eefece4 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 3/5] [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 0000000000000..51cd306aa1970
--- /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 0000000000000..776f8422ad0fc
--- /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 f9772db8b6554..9d43523bf3180 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 0000000000000..58df0761f2022
--- /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 0000000000000..c0751408045c7
--- /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 0000000000000..1d77bbe13927c
--- /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 0000000000000..091220e313958
--- /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 0000000000000..99c196f4232b5
--- /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 0000000000000..0728c8623c202
--- /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 0000000000000..2668ab1e18346
--- /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 4a5b1a678f19800ac010293cb8e5ca029e5c8092 Mon Sep 17 00:00:00 2001
From: Yeoul Na <yeoul_na at apple.com>
Date: Tue, 6 Oct 2026 18:38:08 -0700
Subject: [PATCH 4/5] [BoundsSafety] Don't orphan a shared counted_by node when
 one declarator nests it

A late-parsed counted_by-family attribute in declaration-specifier position is
shared by every declarator in the declaration, so they share one incomplete
CountAttributedType. When one declarator nests the node, as `*b` does in
`IP __counted_by(n) a, *b;`, it is rejected and the node is marked rejected.
The completion pass then skipped the node for every declarator, so `a` kept a
CountAttributedType without a count and crashed on use in
FieldDecl::findCountedByField.

Make the rejection apply to the declarator only. The parser checks and clears
the mark after each declarator, and doesn't pair a rejected declarator's field
with the attribute. The completion pass skips an attribute with no fields left,
still without parsing its argument.

Assisted-by: Opus 5.5
---
 clang/include/clang/Sema/Sema.h               | 14 ++++----
 clang/lib/Parse/ParseDecl.cpp                 | 23 +++++++++----
 .../attr-counted-by-late-parsed-regressions.c | 32 +++++++++++++++++++
 3 files changed, 57 insertions(+), 12 deletions(-)

diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index f444c0d34691e..284a47e3dd97c 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -2599,10 +2599,10 @@ class Sema final : public SemaBase {
   bool CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes,
                                  bool OrNull);
 
-  /// Late-parsed bounds types dropped while their declarator was built. The
-  /// attribute has already been diagnosed and its node is no longer part of
-  /// any type, so the completion pass must skip it rather than parse its
-  /// argument and complete it.
+  /// Late-parsed bounds types dropped while the current declarator was built.
+  /// The attribute has already been diagnosed and the node is not part of this
+  /// declarator's type, so the parser must not pair the declarator's field with
+  /// the attribute. Other declarators sharing the node may still use it.
   llvm::SmallPtrSet<const BoundsAttributedType *, 1>
       RejectedLateParsedBoundsTypes;
 
@@ -2610,8 +2610,10 @@ class Sema final : public SemaBase {
     RejectedLateParsedBoundsTypes.insert(BATy);
   }
 
-  bool isLateParsedBoundsTypeRejected(const BoundsAttributedType *BATy) const {
-    return RejectedLateParsedBoundsTypes.contains(BATy);
+  /// Return true if \p BATy was dropped while the current declarator was built,
+  /// and clear the mark for the next declarator.
+  bool takeLateParsedBoundsTypeRejected(const BoundsAttributedType *BATy) {
+    return RejectedLateParsedBoundsTypes.erase(BATy);
   }
 
   /// Validate the late-parsed type attribute \p LA and wrap \p QT in a
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index 1a54e2695ca9c..d7876667acb04 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -4969,15 +4969,25 @@ void Parser::ParseStructDeclaration(
     // __counted_by_or_null, but not counted_by) may sit nested inside the
     // field's type (in the future extension), where it cannot be recovered by
     // just inspecting the field's top-level type.
-    if (auto *FD = dyn_cast_if_present<FieldDecl>(Field);
-        FD && CurRecordLateParsedTypeAttrs) {
+    //
+    // Skip a declarator whose type dropped the attribute's node, such as `*b`
+    // in `IP __counted_by(n) a, *b;`. The node stays valid for `a`. Clear the
+    // mark even without a field, so it can't leak into the next declarator.
+    if (CurRecordLateParsedTypeAttrs) {
       unsigned Size = CurRecordLateParsedTypeAttrs->size();
       assert(Size >= DeclMark && DeclMark >= AfterDeclSpecMark &&
              AfterDeclSpecMark >= DeclSpecMark &&
              "late-parsed type attribute list must only grow");
+      auto *FD = dyn_cast_if_present<FieldDecl>(Field);
       auto Attach = [&](unsigned First, unsigned Last) {
-        for (unsigned I = First; I != Last; ++I)
-          (*CurRecordLateParsedTypeAttrs)[I]->addDecl(FD);
+        for (unsigned I = First; I != Last; ++I) {
+          LateParsedTypeAttribute *LTA = (*CurRecordLateParsedTypeAttrs)[I];
+          bool Rejected =
+              LTA->TypeToComplete &&
+              Actions.takeLateParsedBoundsTypeRejected(LTA->TypeToComplete);
+          if (FD && !Rejected)
+            LTA->addDecl(FD);
+        }
       };
       // Attach FD with attributes from the shared declaration-specifier (every
       // declarator in this declaration gets appended)
@@ -5041,8 +5051,10 @@ void Parser::CompleteLateParsedTypeAttributes(
   for (LateParsedTypeAttribute *RawLTA : LateTypeAttrs) {
     std::unique_ptr<LateParsedTypeAttribute> LTA(RawLTA);
 
+    // Skip an attribute rejected for every declarator that shares it, without
+    // parsing its argument, which would cascade diagnostics.
     BoundsAttributedType *BATy = LTA->Info.TypeToComplete;
-    if (!BATy || Actions.isLateParsedBoundsTypeRejected(BATy))
+    if (!BATy || LTA->Decls.empty())
       continue;
 
     ArrayRef<Decl *> Fields = LTA->Decls;
@@ -5060,7 +5072,6 @@ void Parser::CompleteLateParsedTypeAttributes(
     assert(Arg);
 
     bool Valid = true;
-    assert(!Fields.empty());
     for (Decl *FD : Fields)
       Valid &= Actions.ActOnLateParsedTypeAttrArgument(
           BATy, cast<FieldDecl>(FD), Arg);
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 99c196f4232b5..3a50bca54e63a 100644
--- a/clang/test/Sema/attr-counted-by-late-parsed-regressions.c
+++ b/clang/test/Sema/attr-counted-by-late-parsed-regressions.c
@@ -102,3 +102,35 @@ struct nested_with_unresolvable_count {
   // expected-error at +1 {{'counted_by' attribute on nested pointer type is not allowed}}
   int *__counted_by(does_not_exist) *pp;
 };
+
+// Rejecting one declarator that nests a shared declaration-specifier attribute
+// used to reject the shared node, leaving the other declarators with no count
+// and crashing on use. Only the nested declarator is rejected, in either order.
+struct shared_declspec_attr_valid_then_nested {
+  // expected-error at +1 {{'counted_by' attribute on nested pointer type is not allowed}}
+  shared_ptr_ty __counted_by(n) a, *b;
+  int n;
+};
+
+void use_shared_declspec_attr_valid_then_nested(
+    struct shared_declspec_attr_valid_then_nested *s) {
+  (void)__builtin_counted_by_ref(s->a);
+}
+
+struct shared_declspec_attr_nested_then_valid {
+  // expected-error at +1 {{'counted_by' attribute on nested pointer type is not allowed}}
+  shared_ptr_ty __counted_by(n) *a, b;
+  int n;
+};
+
+void use_shared_declspec_attr_nested_then_valid(
+    struct shared_declspec_attr_nested_then_valid *s) {
+  (void)__builtin_counted_by_ref(s->b);
+}
+
+// With every declarator rejected, the argument is never parsed.
+struct shared_declspec_attr_all_nested {
+  // expected-error at +2 {{'counted_by' attribute on nested pointer type is not allowed}}
+  // expected-error at +1 {{'counted_by' attribute on nested pointer type is not allowed}}
+  shared_ptr_ty __counted_by(does_not_exist) *a, *b;
+};

>From aad20d94d76ae343e86983c7e67b761e9f02b09c Mon Sep 17 00:00:00 2001
From: Yeoul Na <yeoul_na at apple.com>
Date: Tue, 20 Oct 2026 10:57:59 -0700
Subject: [PATCH 5/5] [BoundsSafety] Address review feedback on late-parsed
 counted_by types

Fixes for late-parsed counted_by-family type attributes:

- A block-pointer member hands its late attributes to the block-pointer chunk
  instead of asserting, so Sema rejects them like a non-pointer type.
- Late-parse type attributes only in member declarators. Function-pointer
  parameters, type names in `sizeof`, and other abstract declarators nested in a
  member are parsed eagerly. They used to be resolved in struct scope, and a
  paren that turned out to be a parameter list asserted.
- An unnamed struct hands its attributes to the enclosing record only when it's
  declared directly in a record, not when it's in a statement expression.
- In declaration-specifier position, only type attributes are late-parsed.
  Declaration attributes such as guarded_by are parsed eagerly again, so they
  apply to every declarator.
- Reject an attribute on a function return type in the late path too.
- Complete a node whose argument fails to parse with a RecoveryExpr and mark the
  fields invalid, rather than leave a CountAttributedType without a count.
- Report a rejected declaration-specifier attribute once rather than once per
  declarator, and check every attribute on a declarator even after one is
  rejected.
- Check an attribute's position when it's applied rather than when an enclosing
  chunk wraps it. That dropped a ParenType along with the rejected node, which
  asserted when building the TypeLoc.

Assisted-by: Opus 5.5
---
 clang/include/clang/Parse/Parser.h            |  11 ++
 clang/include/clang/Sema/DeclSpec.h           |   6 +
 clang/include/clang/Sema/Sema.h               |  23 ++--
 clang/lib/Parse/ParseDecl.cpp                 |  81 +++++++-----
 clang/lib/Sema/SemaType.cpp                   | 115 +++++++++---------
 .../attr-counted-by-late-parsed-parse-error.c |  41 +++++++
 ...tr-counted-by-late-parsed-anon-stmt-expr.c |  29 +++++
 ...ttr-counted-by-late-parsed-block-pointer.c |  36 ++++++
 .../attr-counted-by-late-parsed-non-member.c  |  42 +++++++
 ...ttr-counted-by-late-parsed-rejected-once.c |  32 +++++
 .../attr-counted-by-multiple-on-pointer.c     |   4 +-
 .../Sema/attr-counted-by-paren-declarator.c   |  31 +++++
 clang/test/Sema/attr-counted-by-return-type.c |  18 ++-
 13 files changed, 367 insertions(+), 102 deletions(-)
 create mode 100644 clang/test/PCH/attr-counted-by-late-parsed-parse-error.c
 create mode 100644 clang/test/Sema/attr-counted-by-late-parsed-anon-stmt-expr.c
 create mode 100644 clang/test/Sema/attr-counted-by-late-parsed-block-pointer.c
 create mode 100644 clang/test/Sema/attr-counted-by-late-parsed-non-member.c
 create mode 100644 clang/test/Sema/attr-counted-by-late-parsed-rejected-once.c
 create mode 100644 clang/test/Sema/attr-counted-by-paren-declarator.c

diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index 7bd6ebe88e6c5..045fb1486d7ee 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -8180,6 +8180,17 @@ class Parser : public CodeCompletionHandler {
   SmallVectorImpl<LateParsedTypeAttribute *> *CurRecordLateParsedTypeAttrs =
       nullptr;
 
+  /// Whether type attributes in \p D are late-parsed. Only member declarators
+  /// are: their arguments are resolved in the record's scope at its closing
+  /// brace. Other declarators nested in a member, such as a function-pointer
+  /// member's parameters or a type name in `sizeof`, parse them eagerly.
+  /// Parameters are tracked by
+  /// https://github.com/llvm/llvm-project/issues/166454.
+  bool isLateParsedTypeAttrContext(const Declarator &D) const {
+    return CurRecordLateParsedTypeAttrs &&
+           D.getContext() == DeclaratorContext::Member;
+  }
+
   /// 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/DeclSpec.h b/clang/include/clang/Sema/DeclSpec.h
index 77f2bd67a375c..5e5ac7f2e00ed 100644
--- a/clang/include/clang/Sema/DeclSpec.h
+++ b/clang/include/clang/Sema/DeclSpec.h
@@ -257,6 +257,12 @@ namespace clang {
     ParsedAttr::Kind Kind;
     SourceLocation NameLoc;
 
+    /// Set by `Sema::ProcessLateParsedTypeAttr` once type construction has
+    /// rejected and diagnosed this attribute, so declarators sharing it (a
+    /// declaration-specifier attribute) skip it rather than diagnose it again.
+    /// The eager path does the same with `ParsedAttr::setInvalid`.
+    bool Invalid = false;
+
     /// The type built for this attribute during type construction, still missing
     /// the argument that hasn't been parsed yet. Filled in by
     /// `Sema::ProcessLateParsedTypeAttr` and completed once the enclosing scope
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 284a47e3dd97c..93cfbe2e5420d 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -2599,10 +2599,11 @@ class Sema final : public SemaBase {
   bool CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes,
                                  bool OrNull);
 
-  /// Late-parsed bounds types dropped while the current declarator was built.
-  /// The attribute has already been diagnosed and the node is not part of this
-  /// declarator's type, so the parser must not pair the declarator's field with
-  /// the attribute. Other declarators sharing the node may still use it.
+  /// Late-parsed bounds types rejected for the current declarator, such as one
+  /// nested under another pointer. The attribute has already been diagnosed and
+  /// the node is not part of this declarator's type, so the parser must not
+  /// pair the declarator's field with the attribute. Other declarators sharing
+  /// the node may still use it.
   llvm::SmallPtrSet<const BoundsAttributedType *, 1>
       RejectedLateParsedBoundsTypes;
 
@@ -2610,8 +2611,8 @@ class Sema final : public SemaBase {
     RejectedLateParsedBoundsTypes.insert(BATy);
   }
 
-  /// Return true if \p BATy was dropped while the current declarator was built,
-  /// and clear the mark for the next declarator.
+  /// Return true if \p BATy was rejected for the current declarator, and clear
+  /// the mark for the next declarator.
   bool takeLateParsedBoundsTypeRejected(const BoundsAttributedType *BATy) {
     return RejectedLateParsedBoundsTypes.erase(BATy);
   }
@@ -2619,7 +2620,15 @@ class Sema final : public SemaBase {
   /// Validate the late-parsed type attribute \p LA and wrap \p QT in a
   /// CountAttributedType with no count yet, recorded on \p LA for later
   /// completion. Returns false if \p LA is invalid.
-  bool ProcessLateParsedTypeAttr(LateParsedTypeAttrInfo &LA, QualType &QT);
+  
+  /// 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. \p PointerNestLevel and \p OnReturnType describe where the
+  /// attribute sits in the current declarator (see
+  /// ValidateBoundsAttrTypeForTypePosition). Ignores other attributes. Returns
+  /// false if \p LA is invalid, or is rejected for the current declarator.
+  bool ProcessLateParsedTypeAttr(LateParsedTypeAttrInfo &LA, QualType &QT,
+                                 unsigned PointerNestLevel, bool OnReturnType);
 
   /// Supply the parsed argument of a late-parsed bounds attribute to the type
   /// built for it by ProcessLateParsedTypeAttr, and run the checks that need
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index d7876667acb04..4c9e3b77b76c7 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -5062,13 +5062,26 @@ void Parser::CompleteLateParsedTypeAttributes(
     AttributeFactory AF;
     ParsedAttributes Attrs(AF);
     ParseLexedTypeAttribute(*LTA, Attrs);
-
-    // An unparseable argument leaves no attribute behind; already diagnosed.
-    if (Attrs.empty())
-      continue;
-    assert(Attrs.size() == 1);
-
-    Expr *Arg = Attrs[0].getArgAsExpr(0);
+    assert(Attrs.size() <= 1);
+
+    // An unparseable argument leaves no attribute behind; it's already been
+    // diagnosed. The node is already part of the fields' types, so complete it
+    // with a recovery expression rather than leave it without a count; Sema
+    // then rejects it and marks the fields invalid, as for an argument that
+    // parses but has errors. Build the RecoveryExpr directly, since the node
+    // needs a count even with -fno-recovery-ast.
+    Expr *Arg;
+    if (Attrs.empty()) {
+      // The cached tokens are `( ... ) <eof>`; end at the closing paren.
+      SourceLocation End = LTA->Toks.size() >= 2
+                               ? LTA->Toks[LTA->Toks.size() - 2].getLocation()
+                               : LTA->AttrNameLoc;
+      ASTContext &Ctx = Actions.getASTContext();
+      Arg = RecoveryExpr::Create(Ctx, Ctx.IntTy, LTA->AttrNameLoc, End,
+                                 /*SubExprs=*/{});
+    } else {
+      Arg = Attrs[0].getArgAsExpr(0);
+    }
     assert(Arg);
 
     bool Valid = true;
@@ -5076,6 +5089,8 @@ void Parser::CompleteLateParsedTypeAttributes(
       Valid &= Actions.ActOnLateParsedTypeAttrArgument(
           BATy, cast<FieldDecl>(FD), Arg);
 
+    if (Attrs.empty())
+      continue;
     if (Valid)
       Attrs[0].setUsedAsTypeAttr();
     else
@@ -5265,9 +5280,15 @@ void Parser::ParseStructUnionBody(SourceLocation RecordLoc,
   // attribute-specifiers between `}` and the `;`/declarator are skipped with a
   // reverting tentative parse, so a trailing `[[...]]` / `__attribute__` etc.
   // doesn't defeat the check.
+  //
+  // It must also be declared directly in a record. An unnamed struct in a
+  // statement expression inside a member, such as
+  // `char a[({ struct { int n; int *__counted_by(n) p; }; 1; })];`, is a local
+  // declaration, not a member, so its fields never join the enclosing record.
   if (getLangOpts().ExperimentalLateParseAttributes && !LateTypeAttrs.empty()) {
     bool IsAnonymous = false;
-    if (!TagDecl->getIdentifier()) {
+    if (!TagDecl->getIdentifier() &&
+        isa<RecordDecl>(TagDecl->getDeclContext())) {
       TentativeParsingAction TPA(*this);
       IsAnonymous = TrySkipAttributes() && Tok.is(tok::semi);
       TPA.Revert();
@@ -6831,24 +6852,17 @@ void Parser::ParseDeclaratorInternal(Declarator &D,
                          ? 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;
-
+    // Late-parsed type attributes apply to members only (see
+    // isLateParsedTypeAttrContext). A free-function prototype (e.g.
+    // `void f(int *__counted_by(n), int n)`) has no record to complete into,
+    // and a function-pointer member's parameters would be resolved in struct
+    // scope rather than the prototype's, so both fall back to eager handling.
+    //
     // No guard on ExperimentalLateParseAttributes is needed here;
     // DS.getLateAttributes() already initializes with
     // LateAttrParseExperimentalExtOnly.
     LateParsedAttrList *LateAttrs =
-        LateParsingContext ? &DS.getLateAttributes() : nullptr;
+        isLateParsedTypeAttrContext(D) ? &DS.getLateAttributes() : nullptr;
 
     ParseTypeQualifierListOpt(DS, Reqs, /*AtomicOrPtrauthAllowed=*/true,
                               !D.mayOmitIdentifier(), {}, LateAttrs);
@@ -6867,11 +6881,14 @@ void Parser::ParseDeclaratorInternal(Declarator &D,
                     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.
+      // Hand over late-parsed type attributes too, as for a pointer: Sema
+      // rejects a counted_by-family attribute on a block pointer when it builds
+      // the chunk, just as it does when the attribute is parsed eagerly.
       D.AddTypeInfo(
           DeclaratorChunk::getBlockPointer(DS.getTypeQualifiers(), Loc),
-          std::move(DS.getAttributes()), SourceLocation());
+          std::move(DS.getAttributes()), SourceLocation(),
+          std::move(DS.getLateAttributes()));
     }
   } else {
     // Is a reference
@@ -7441,14 +7458,14 @@ void Parser::ParseParenDeclarator(Declarator &D) {
   LateParsedAttrList LateAttrs = LateParsedAttrList::forTypeAttrs();
   bool RequiresArg = false;
   if (Tok.is(tok::kw___attribute)) {
-    // 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.
+    // Only late-parse type attributes in a member declarator (see
+    // isLateParsedTypeAttrContext). A grouping paren at file scope, e.g.
+    // `IP (__counted_by(n) x)` where `IP` is a pointer typedef, has no record
+    // to complete the CountAttributedType, and an abstract declarator nested in
+    // a member, such as a parameter or a type name in `sizeof`, isn't resolved
+    // in its own scope, so both parse eagerly instead.
     LateParsedAttrList *LA =
-        CurRecordLateParsedTypeAttrs ? &LateAttrs : nullptr;
+        isLateParsedTypeAttrContext(D) ? &LateAttrs : nullptr;
     ParseGNUAttributes(attrs, LA);
 
     // We require that the argument list (if this is a non-grouping paren) be
@@ -7515,6 +7532,8 @@ void Parser::ParseParenDeclarator(Declarator &D) {
     return;
   }
 
+  // Type attributes are only late-parsed in member declarators, which can't
+  // omit their identifier, so their parens always group.
   assert(LateAttrs.empty() &&
          "Late parsed type attribute on FirstParamAttr is dropped");
 
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 2391a61f9a15d..153be2119a150 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -409,8 +409,9 @@ 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 processLateTypeAttrs(TypeProcessingState &state, QualType &type,
+                                 const LateParsedAttrList &LateAttrs,
+                                 unsigned chunkIndex);
 
 static void
 BuildTypeCoupledDecls(Expr *E,
@@ -1521,7 +1522,8 @@ static QualType ConvertDeclSpecToType(TypeProcessingState &state) {
     processTypeAttrs(state, Result, TAL_DeclSpec, DS.getAttributes());
 
     // Process the late attributes that appeared after the type name
-    processLateTypeAttrs(state, Result, DS.getLateAttributes());
+    processLateTypeAttrs(state, Result, DS.getLateAttributes(),
+                         state.getDeclarator().getNumTypeObjects());
   }
 
   // Apply const/volatile/restrict qualifiers to T.
@@ -4758,31 +4760,6 @@ static TypeSourceInfo *GetFullTypeForDeclarator(TypeProcessingState &state,
     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)
@@ -5550,12 +5527,7 @@ 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);
+    processLateTypeAttrs(state, T, DeclType.LateAttrList, chunkIndex);
 
     if (DeclType.Kind != DeclaratorChunk::Paren) {
       if (ExpectNoDerefChunk && !IsNoDerefableChunk(DeclType))
@@ -5737,7 +5709,8 @@ static TypeSourceInfo *GetFullTypeForDeclarator(TypeProcessingState &state,
   processTypeAttrs(state, T, TAL_DeclName, NonSlidingAttrs);
   processTypeAttrs(state, T, TAL_DeclName, D.getAttributes());
 
-  processLateTypeAttrs(state, T, D.getLateAttributes());
+  // Attributes after the declarator apply to the declared type itself.
+  processLateTypeAttrs(state, T, D.getLateAttributes(), /*chunkIndex=*/0);
 
   // Diagnose any ignored type attributes.
   state.diagnoseIgnoredTypeAttrs(T);
@@ -9197,44 +9170,68 @@ static void HandleCountedByAttrOnType(TypeProcessingState &State,
   CurType = NewType;
 }
 
-bool Sema::ProcessLateParsedTypeAttr(LateParsedTypeAttrInfo &LA, QualType &QT) {
+bool Sema::ProcessLateParsedTypeAttr(LateParsedTypeAttrInfo &LA, QualType &QT,
+                                     unsigned PointerNestLevel,
+                                     bool OnReturnType) {
+  // An attribute already rejected for an earlier declarator was diagnosed then,
+  // so skip it silently.
+  if (LA.Invalid)
+    return false;
+
   // 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 (LA.TypeToComplete) {
-    QT = QualType(LA.TypeToComplete, 0);
-    return true;
+  // `IP __counted_by(n) a, b;`. Check the type it applies to once, and build
+  // one node for every declarator to share, as the eager path does, so no node
+  // is left without a count.
+  if (!LA.TypeToComplete) {
+    BoundsAttrFlags Flags;
+    // The position is checked per declarator below, so skip it here.
+    if (!ValidateBoundsAttrTypeForTypePosition(QT, LA.Kind, LA.NameLoc,
+                                               SourceRange(LA.NameLoc),
+                                               /*PointerNestLevel=*/0,
+                                               /*OnReturnType=*/false, Flags)) {
+      LA.Invalid = true;
+      return false;
+    }
+
+    // The argument isn't parsed yet, so build the type without a count.
+    LA.TypeToComplete = getASTContext().getIncompleteCountAttributedType(
+        QT, Flags.CountInBytes, Flags.OrNull);
   }
 
-  // Nesting is checked once the enclosing declarator chunks wrap the node.
-  BoundsAttrFlags Flags;
-  if (!ValidateBoundsAttrTypeForTypePosition(QT, LA.Kind, LA.NameLoc,
-                                             SourceRange(LA.NameLoc),
-                                             /*PointerNestLevel=*/0,
-                                             /*OnReturnType=*/false, Flags))
+  // The position depends on the declarator, as in `IP __counted_by(n) a, *b;`.
+  // Reject the attribute for this declarator only, leaving its type unwrapped,
+  // and record that so the parser doesn't pair its field with the attribute.
+  // Other declarators sharing the node may still use it.
+  CountAttributedType *CATy = cast<CountAttributedType>(LA.TypeToComplete);
+  if (!ValidateBoundsAttrPosition(CATy->getKind(), LA.NameLoc,
+                                  SourceRange(LA.NameLoc), PointerNestLevel,
+                                  OnReturnType)) {
+    markLateParsedBoundsTypeRejected(CATy);
     return false;
+  }
 
-  // The argument isn't parsed yet, so build the type without a count.
-  CountAttributedType *CATy = getASTContext().getIncompleteCountAttributedType(
-      QT, Flags.CountInBytes, Flags.OrNull);
   QT = QualType(CATy, 0);
-  LA.TypeToComplete = CATy;
   return true;
 }
 
-static bool processLateTypeAttrs(TypeProcessingState &state, QualType &type,
-                                 const LateParsedAttrList &LateAttrs) {
-
+/// Process the late-parsed type attributes \p LateAttrs on chunk \p chunkIndex
+/// of the declarator, or on its declaration specifier if \p chunkIndex is the
+/// number of chunks.
+static void processLateTypeAttrs(TypeProcessingState &state, QualType &type,
+                                 const LateParsedAttrList &LateAttrs,
+                                 unsigned chunkIndex) {
   if (LateAttrs.empty())
-    return true;
+    return;
 
   Sema &S = state.getSema();
+  const Declarator &D = state.getDeclarator();
+  unsigned PointerNestLevel = getPointerNestLevel(D, chunkIndex);
+  bool OnReturnType = isOnFunctionReturnType(D, chunkIndex);
 
+  // Process every attribute even if one is rejected, as processTypeAttrs does,
+  // so each one is diagnosed.
   for (auto *LA : LateAttrs)
-    if (!S.ProcessLateParsedTypeAttr(LA, type))
-      return false;
-
-  return true;
+    S.ProcessLateParsedTypeAttr(LA, type, PointerNestLevel, OnReturnType);
 }
 
 static void processTypeAttrs(TypeProcessingState &state, QualType &type,
diff --git a/clang/test/PCH/attr-counted-by-late-parsed-parse-error.c b/clang/test/PCH/attr-counted-by-late-parsed-parse-error.c
new file mode 100644
index 0000000000000..d5cb103e832eb
--- /dev/null
+++ b/clang/test/PCH/attr-counted-by-late-parsed-parse-error.c
@@ -0,0 +1,41 @@
+// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fallow-pcm-with-compiler-errors -emit-pch -o %t %s -verify=header
+// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fallow-pcm-with-compiler-errors -include-pch %t -fsyntax-only -verify %s
+
+// A late-parsed counted_by whose argument fails to parse leaves no attribute
+// to complete the CountAttributedType built for it. The node used to keep a
+// null count, which reached the AST and asserted when the PCH was read back
+// (`use getIncompleteCountAttributedType for a null count`). The node is now
+// completed with a RecoveryExpr and the field is marked invalid, like an
+// argument that parses but is rejected.
+
+#ifndef HEADER
+#define HEADER
+
+#define __counted_by(f) __attribute__((counted_by(f)))
+
+struct empty_arg {
+  int n;
+  int *__counted_by() p; // header-error {{argument required after attribute}}
+};
+
+struct bad_expr {
+  int n;
+  int *__counted_by(n +) p; // header-error {{expected expression}}
+};
+
+// A declaration-specifier attribute shares one node between declarators.
+typedef int *IP;
+struct shared_node {
+  int n;
+  IP __counted_by(n +) a, b; // header-error {{expected expression}}
+};
+
+#else
+
+// expected-no-diagnostics
+
+int use(struct empty_arg *e, struct bad_expr *b, struct shared_node *s) {
+  return e->p[0] + b->p[0] + s->a[0] + s->b[0];
+}
+
+#endif
diff --git a/clang/test/Sema/attr-counted-by-late-parsed-anon-stmt-expr.c b/clang/test/Sema/attr-counted-by-late-parsed-anon-stmt-expr.c
new file mode 100644
index 0000000000000..721d8d2aea20b
--- /dev/null
+++ b/clang/test/Sema/attr-counted-by-late-parsed-anon-stmt-expr.c
@@ -0,0 +1,29 @@
+// RUN: %clang_cc1 -fsyntax-only -verify=expected,eager %s
+// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -verify=expected,late %s
+
+// An unnamed record followed by `;` hands its late-parsed type attributes to
+// the enclosing record, since as an anonymous member its count may be a field
+// of the enclosing record. Only do that for a real member: an unnamed struct
+// declared in a statement expression inside a member is not one, and used to
+// have its attributes resolved in the enclosing record's scope, where its own
+// fields aren't visible.
+
+#define __counted_by(f) __attribute__((counted_by(f)))
+
+// A real anonymous member still sees the enclosing record's fields.
+struct anon_member {
+  struct {
+    // eager-error at +1{{use of undeclared identifier 'n'}}
+    int *__counted_by(n) p;
+  };
+  int n;
+};
+
+void stmt_expr(void) {
+  struct S {
+    int m;
+    // expected-warning at +2{{declaration does not declare anything}}
+    // expected-warning at +1{{variable length array folded to constant array as an extension}}
+    char a[({ struct { int n; int *__counted_by(n) p; }; 1; })];
+  };
+}
diff --git a/clang/test/Sema/attr-counted-by-late-parsed-block-pointer.c b/clang/test/Sema/attr-counted-by-late-parsed-block-pointer.c
new file mode 100644
index 0000000000000..14f58b12c810c
--- /dev/null
+++ b/clang/test/Sema/attr-counted-by-late-parsed-block-pointer.c
@@ -0,0 +1,36 @@
+// RUN: %clang_cc1 -fblocks -fsyntax-only -verify=expected,eager %s
+// RUN: %clang_cc1 -fblocks -fexperimental-late-parse-attributes -fsyntax-only -verify=expected,late %s
+
+// A counted_by-family attribute on a block pointer is rejected the same way
+// with and without late parsing. With late parsing, the attribute used to be
+// collected by the `^` declarator chunk and then hit
+// `assert(DS.getLateAttributes().empty())` in ParseDeclaratorInternal (a
+// release build dropped it silently). It's now handed to the block-pointer
+// chunk, so Sema rejects it like any other non-pointer type, and the
+// completion pass at the closing brace never parses its argument.
+
+#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)))
+
+struct count_before {
+  int n;
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  void (^__counted_by(n) a)(void);
+  // expected-error at +1{{'counted_by_or_null' only applies to pointers}}
+  void (^__counted_by_or_null(n) b)(void);
+  // expected-error at +1{{'sized_by' only applies to pointers}}
+  void (^__sized_by(n) c)(void);
+  // expected-error at +1{{'sized_by_or_null' only applies to pointers}}
+  void (^__sized_by_or_null(n) d)(void);
+};
+
+// With late parsing, the rejected attribute's argument is never parsed, so a
+// count declared after the field doesn't add an "undeclared identifier" error.
+struct count_after {
+  // late-error at +2{{'counted_by' only applies to pointers or C99 flexible array members}}
+  // eager-error at +1{{use of undeclared identifier 'n'}}
+  void (^__counted_by(n) a)(void);
+  int n;
+};
diff --git a/clang/test/Sema/attr-counted-by-late-parsed-non-member.c b/clang/test/Sema/attr-counted-by-late-parsed-non-member.c
new file mode 100644
index 0000000000000..711c53e1e288a
--- /dev/null
+++ b/clang/test/Sema/attr-counted-by-late-parsed-non-member.c
@@ -0,0 +1,42 @@
+// RUN: %clang_cc1 -fsyntax-only -verify %s
+// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -verify %s
+
+// Late-parsed type attributes are only supported on struct members, whose
+// arguments are resolved at the record's closing brace. Declarators nested in
+// a member that aren't members themselves, such as the parameters of a
+// function-pointer member or a type name in `sizeof`, parse the attribute
+// eagerly. Diagnostics match those without late parsing.
+//
+// Late parsing used to apply to these declarators too, which broke:
+// - a parameter's count was looked up in struct scope at the closing brace, so
+//   it missed the function's other parameters or silently bound to a field;
+// - a paren that turned out to be a parameter list hit
+//   `assert(LateAttrs.empty())` in ParseParenDeclarator.
+// Parameters are tracked by https://github.com/llvm/llvm-project/issues/166454.
+
+#define __counted_by(f) __attribute__((counted_by(f)))
+
+struct fp_param_count_in_proto {
+  // expected-error at +1{{'counted_by' attribute only applies to non-static data members}}
+  void (*cb)(int len, int *__counted_by(len) buf);
+};
+
+struct fp_param_names_field {
+  int len;
+  // A late-parsed count used to bind to the field `len` with no diagnostic.
+  // expected-error at +1{{use of undeclared identifier 'len'}}
+  void (*cb)(int *__counted_by(len) buf);
+};
+
+struct paren_param_list {
+  int n;
+  // The attribute applies to the first parameter of the inner function type.
+  // expected-error at +1{{use of undeclared identifier 'n'}}
+  void (*fp)(int (__counted_by(n) int));
+};
+
+struct sizeof_abstract {
+  int n;
+  // expected-error at +1{{argument required after attribute}}
+  char a[sizeof(int *(__counted_by(n)))];
+};
diff --git a/clang/test/Sema/attr-counted-by-late-parsed-rejected-once.c b/clang/test/Sema/attr-counted-by-late-parsed-rejected-once.c
new file mode 100644
index 0000000000000..2a1bbcfeb6f9c
--- /dev/null
+++ b/clang/test/Sema/attr-counted-by-late-parsed-rejected-once.c
@@ -0,0 +1,32 @@
+// RUN: %clang_cc1 -fsyntax-only -verify %s
+// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -verify %s
+
+// Late-parsed counted_by-family attributes report the same errors as eager
+// ones. A rejected declaration-specifier attribute is reported once, not once
+// per declarator that shares it, and each attribute on a declarator is checked
+// even after an earlier one is rejected.
+
+#define __counted_by(f) __attribute__((counted_by(f)))
+#define __sized_by(f) __attribute__((sized_by(f)))
+
+struct shared_decl_spec {
+  int n;
+  // expected-error at +1{{'counted_by' only applies to pointers or C99 flexible array members}}
+  int __counted_by(n) a, b, c;
+};
+
+struct two_rejected_attrs {
+  int n;
+  int m;
+  // expected-error at +2{{'counted_by' only applies to pointers or C99 flexible array members}}
+  // expected-error at +1{{'sized_by' only applies to pointers}}
+  int __counted_by(n) __sized_by(m) x;
+};
+
+struct two_rejected_shared {
+  int n;
+  int m;
+  // expected-error at +2{{'counted_by' only applies to pointers or C99 flexible array members}}
+  // expected-error at +1{{'sized_by' only applies to pointers}}
+  int __counted_by(n) __sized_by(m) a, b;
+};
diff --git a/clang/test/Sema/attr-counted-by-multiple-on-pointer.c b/clang/test/Sema/attr-counted-by-multiple-on-pointer.c
index be588c15bd164..a06cc8c4e560e 100644
--- a/clang/test/Sema/attr-counted-by-multiple-on-pointer.c
+++ b/clang/test/Sema/attr-counted-by-multiple-on-pointer.c
@@ -1,7 +1,9 @@
 // RUN: %clang_cc1 -fsyntax-only -verify %s
+// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -verify %s
 
 // A pointer can have only one counted_by-family attribute. A second one used
-// to stack another CountAttributedType on the first.
+// to stack another CountAttributedType on the first, 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)))
diff --git a/clang/test/Sema/attr-counted-by-paren-declarator.c b/clang/test/Sema/attr-counted-by-paren-declarator.c
new file mode 100644
index 0000000000000..bc649e618c049
--- /dev/null
+++ b/clang/test/Sema/attr-counted-by-paren-declarator.c
@@ -0,0 +1,31 @@
+// RUN: %clang_cc1 -fsyntax-only -verify %s
+// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -verify %s
+
+// Redundant parens between a rejected counted_by-family attribute and the
+// declarator chunk that rejects it. With late parsing, the rejected type used
+// to be dropped along with the ParenType around it, so the type no longer
+// matched the declarator's chunks and building its TypeLoc asserted
+// (`Chunk.Kind == DeclaratorChunk::Pointer` in VisitPointerTypeLoc).
+
+#define __counted_by(f) __attribute__((counted_by(f)))
+
+extern int g;
+
+struct nested_in_parens {
+  int n;
+  // expected-error at +1{{'counted_by' attribute on nested pointer type is not allowed}}
+  int *__counted_by(n) (*p);
+  // expected-error at +1{{'counted_by' attribute on nested pointer type is not allowed}}
+  int *__counted_by(n) ((*pp));
+};
+
+struct return_type_in_parens {
+  int n;
+  // expected-error at +1{{'counted_by' attribute cannot be applied to a function return type}}
+  int *__counted_by(n) ((*fp)(void));
+};
+
+// The parens group the function declarator, so they sit between the counted
+// pointer and the function chunk.
+// expected-error at +1{{'counted_by' attribute cannot be applied to a function return type}}
+int *__counted_by(g) (fn(void));
diff --git a/clang/test/Sema/attr-counted-by-return-type.c b/clang/test/Sema/attr-counted-by-return-type.c
index 6b9fe33a76853..7d83115bce27a 100644
--- a/clang/test/Sema/attr-counted-by-return-type.c
+++ b/clang/test/Sema/attr-counted-by-return-type.c
@@ -1,10 +1,11 @@
-// RUN: %clang_cc1 -fblocks -fsyntax-only -verify %s
+// RUN: %clang_cc1 -fblocks -fsyntax-only -verify=expected,eager %s
+// RUN: %clang_cc1 -fblocks -fexperimental-late-parse-attributes -fsyntax-only -verify=expected,late %s
 
 // counted_by-family attributes aren't supported on function return types,
 // including the return type of the function a function-pointer or
-// block-pointer member points to. They're rejected with a dedicated diagnostic.
-// They used to be reported as a nested pointer, because the function chunk
-// counted as a level of pointer nesting.
+// block-pointer member points to. Both paths reject them with a dedicated
+// diagnostic. They used to be reported as a nested pointer without late
+// parsing, and silently accepted with it.
 
 #define __counted_by(f) __attribute__((counted_by(f)))
 #define __sized_by(f) __attribute__((sized_by(f)))
@@ -51,3 +52,12 @@ struct fn_ptr_array_nested_return {
   // expected-error at +1{{'counted_by' attribute cannot be applied to a function return type}}
   int *__counted_by(n) *(*fpa[2])(void);
 };
+
+// Without late parsing, the count is looked up when the attribute is parsed.
+// With it, the rejected attribute's argument is never parsed.
+struct fn_ptr_return_count_after {
+  // late-error at +2{{'counted_by' attribute cannot be applied to a function return type}}
+  // eager-error at +1{{use of undeclared identifier 'n'}}
+  int *__counted_by(n) (*fp)(void);
+  int n;
+};



More information about the llvm-branch-commits mailing list