https://github.com/Holo-xy created 
https://github.com/llvm/llvm-project/pull/213305

None

>From 0d9194315989b7088ca5de18dd9d85972494a103 Mon Sep 17 00:00:00 2001
From: Holo-xy <[email protected]>
Date: Sat, 13 Jun 2026 06:46:32 +0300
Subject: [PATCH 1/6] Introduce LateParsedAttrType AST placeholder type

---
 clang/include/clang/AST/ASTContext.h          |  6 +++
 clang/include/clang/AST/RecursiveASTVisitor.h |  6 +++
 clang/include/clang/AST/TypeBase.h            | 41 +++++++++++++++++++
 clang/include/clang/AST/TypeLoc.h             | 31 ++++++++++++++
 clang/include/clang/AST/TypeProperties.td     |  6 +++
 clang/include/clang/Basic/TypeNodes.td        |  1 +
 clang/lib/AST/ASTContext.cpp                  | 18 ++++++++
 clang/lib/AST/ASTImporter.cpp                 |  7 ++++
 clang/lib/AST/ASTStructuralEquivalence.cpp    |  7 ++++
 clang/lib/AST/ItaniumMangle.cpp               |  1 +
 clang/lib/AST/TypePrinter.cpp                 | 15 +++++++
 clang/lib/CIR/CodeGen/CIRGenFunction.cpp      |  1 +
 clang/lib/CodeGen/CGDebugInfo.cpp             |  1 +
 clang/lib/CodeGen/CodeGenFunction.cpp         |  1 +
 clang/lib/Sema/SemaExpr.cpp                   |  1 +
 clang/lib/Sema/TreeTransform.h                | 20 +++++++++
 clang/lib/Serialization/ASTReader.cpp         |  4 ++
 clang/lib/Serialization/ASTWriter.cpp         |  7 ++++
 clang/tools/libclang/CIndex.cpp               |  4 ++
 .../TypeSystem/Clang/TypeSystemClang.cpp      |  9 ++++
 20 files changed, 187 insertions(+)

diff --git a/clang/include/clang/AST/ASTContext.h 
b/clang/include/clang/AST/ASTContext.h
index a4ed852d36442..2095bc1a1df67 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -1630,6 +1630,12 @@ class ASTContext : public RefCountedBase<ASTContext> {
                          bool OrNull,
                          ArrayRef<TypeCoupledDeclRefInfo> DependentDecls) 
const;
 
+  /// Return a placeholder type for a late-parsed type attribute.
+  /// This type wraps another type and holds the LateParsedAttribute
+  /// that will be parsed later.
+  QualType getLateParsedAttrType(QualType Wrapped,
+                                 LateParsedTypeAttribute *LateParsedAttr) 
const;
+
   /// Return the uniqued reference to a type adjusted from the original
   /// type to a new type.
   QualType getAdjustedType(QualType Orig, QualType New) const;
diff --git a/clang/include/clang/AST/RecursiveASTVisitor.h 
b/clang/include/clang/AST/RecursiveASTVisitor.h
index 529d657fc01f5..5a72ef0f17785 100644
--- a/clang/include/clang/AST/RecursiveASTVisitor.h
+++ b/clang/include/clang/AST/RecursiveASTVisitor.h
@@ -1164,6 +1164,9 @@ DEF_TRAVERSE_TYPE(CountAttributedType, {
   TRY_TO(TraverseType(T->desugar()));
 })
 
+DEF_TRAVERSE_TYPE(LateParsedAttrType,
+                  { TRY_TO(TraverseType(T->getWrappedType())); })
+
 DEF_TRAVERSE_TYPE(BTFTagAttributedType,
                   { TRY_TO(TraverseType(T->getWrappedType())); })
 
@@ -1522,6 +1525,9 @@ DEF_TRAVERSE_TYPELOC(AttributedType,
 DEF_TRAVERSE_TYPELOC(CountAttributedType,
                      { TRY_TO(TraverseTypeLoc(TL.getInnerLoc())); })
 
+DEF_TRAVERSE_TYPELOC(LateParsedAttrType,
+                     { TRY_TO(TraverseTypeLoc(TL.getInnerLoc())); })
+
 DEF_TRAVERSE_TYPELOC(BTFTagAttributedType,
                      { TRY_TO(TraverseTypeLoc(TL.getWrappedLoc())); })
 
diff --git a/clang/include/clang/AST/TypeBase.h 
b/clang/include/clang/AST/TypeBase.h
index e3844d0cefa78..dd46a58a6fa50 100644
--- a/clang/include/clang/AST/TypeBase.h
+++ b/clang/include/clang/AST/TypeBase.h
@@ -70,6 +70,7 @@ class TagDecl;
 class TemplateParameterList;
 class Type;
 class Attr;
+struct LateParsedTypeAttribute;
 
 enum {
   TypeAlignmentInBits = 4,
@@ -3545,6 +3546,46 @@ class CountAttributedType final
   StringRef getAttributeName(bool WithMacroPrefix) const;
 };
 
+/// Represents a placeholder type for late-parsed type attributes.
+/// This type wraps another type and holds an opaque pointer to a
+/// LateParsedAttribute that will be parsed later (e.g., in ActOnFields).
+/// Once parsed, this type is replaced with the appropriate attributed type
+/// (e.g., CountAttributedType for counted_by).
+class LateParsedAttrType : public Type, public llvm::FoldingSetNode {
+  friend class ASTContext; // ASTContext creates these.
+
+  QualType WrappedTy;
+  LateParsedTypeAttribute *LateParsedTypeAttr;
+
+  LateParsedAttrType(QualType Wrapped, QualType Canon,
+                     LateParsedTypeAttribute *Attr)
+      : Type(LateParsedAttr, Canon, Wrapped->getDependence()),
+        WrappedTy(Wrapped), LateParsedTypeAttr(Attr) {}
+
+public:
+  QualType getWrappedType() const { return WrappedTy; }
+  LateParsedTypeAttribute *getLateParsedAttribute() const {
+    return LateParsedTypeAttr;
+  }
+
+  bool isSugared() const { return true; }
+  QualType desugar() const { return WrappedTy; }
+
+  void Profile(llvm::FoldingSetNodeID &ID) {
+    Profile(ID, WrappedTy, LateParsedTypeAttr);
+  }
+
+  static void Profile(llvm::FoldingSetNodeID &ID, QualType Wrapped,
+                      LateParsedTypeAttribute *Attr) {
+    ID.AddPointer(Wrapped.getAsOpaquePtr());
+    ID.AddPointer(Attr);
+  }
+
+  static bool classof(const Type *T) {
+    return T->getTypeClass() == LateParsedAttr;
+  }
+};
+
 /// Represents a type which was implicitly adjusted by the semantic
 /// engine for arbitrary reasons.  For example, array and function types can
 /// decay, and function types can have their calling conventions adjusted.
diff --git a/clang/include/clang/AST/TypeLoc.h 
b/clang/include/clang/AST/TypeLoc.h
index 24df18dbaace4..2648806ac687a 100644
--- a/clang/include/clang/AST/TypeLoc.h
+++ b/clang/include/clang/AST/TypeLoc.h
@@ -1358,6 +1358,37 @@ class CountAttributedTypeLoc final
   SourceRange getLocalSourceRange() const;
 };
 
+struct LateParsedAttrLocInfo {
+  SourceLocation AttrNameLoc;
+};
+
+class LateParsedAttrTypeLoc
+    : public ConcreteTypeLoc<UnqualTypeLoc, LateParsedAttrTypeLoc,
+                             LateParsedAttrType, LateParsedAttrLocInfo> {
+public:
+  TypeLoc getInnerLoc() const { return getInnerTypeLoc(); }
+
+  SourceLocation getAttrNameLoc() const { return getLocalData()->AttrNameLoc; }
+
+  void setAttrNameLoc(SourceLocation Loc) { getLocalData()->AttrNameLoc = Loc; 
}
+
+  SourceRange getLocalSourceRange() const {
+    return SourceRange(getAttrNameLoc(), getAttrNameLoc());
+  }
+
+  void initializeLocal(ASTContext &Context, SourceLocation Loc) {
+    setAttrNameLoc(Loc);
+  }
+
+  unsigned getLocalDataSize() const { return sizeof(LateParsedAttrLocInfo); }
+
+  QualType getInnerType() const { return getTypePtr()->getWrappedType(); }
+
+  LateParsedTypeAttribute *getLateParsedAttribute() const {
+    return getTypePtr()->getLateParsedAttribute();
+  }
+};
+
 struct MacroQualifiedLocInfo {
   SourceLocation ExpansionLoc;
 };
diff --git a/clang/include/clang/AST/TypeProperties.td 
b/clang/include/clang/AST/TypeProperties.td
index f16c10da430f9..c5402a1925d99 100644
--- a/clang/include/clang/AST/TypeProperties.td
+++ b/clang/include/clang/AST/TypeProperties.td
@@ -44,6 +44,12 @@ let Class = CountAttributedType in {
   def : Creator<[{ return ctx.getCountAttributedType(WrappedTy, CountExpr, 
CountInBytes, OrNull, CoupledDecls); }]>;
 }
 
+let Class = LateParsedAttrType in {
+  // Note: LateParsedAttrType is a transient placeholder type that should
+  // normally be replaced before serialization. So this won't be serialized.
+  def : Creator<[{ (void)ctx; llvm_unreachable("unreachable for 
serialization"); }]>;
+}
+
 let Class = AdjustedType in {
   def : Property<"originalType", QualType> {
     let Read = [{ node->getOriginalType() }];
diff --git a/clang/include/clang/Basic/TypeNodes.td 
b/clang/include/clang/Basic/TypeNodes.td
index a9965a4a89aa1..700a73f669690 100644
--- a/clang/include/clang/Basic/TypeNodes.td
+++ b/clang/include/clang/Basic/TypeNodes.td
@@ -105,6 +105,7 @@ def ObjCInterfaceType : TypeNode<ObjCObjectType>, 
AlwaysCanonical;
 def ObjCObjectPointerType : TypeNode<Type>;
 def BoundsAttributedType : TypeNode<Type, 1>;
 def CountAttributedType : TypeNode<BoundsAttributedType>, NeverCanonical;
+def LateParsedAttrType : TypeNode<Type>, NeverCanonical;
 def PipeType : TypeNode<Type>;
 def AtomicType : TypeNode<Type>;
 def BitIntType : TypeNode<Type>;
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index abf0cd5e18c2b..562694ffc2b8c 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -2581,6 +2581,9 @@ TypeInfo ASTContext::getTypeInfoImpl(const Type *T) const 
{
   case Type::CountAttributed:
     return getTypeInfo(cast<CountAttributedType>(T)->desugar().getTypePtr());
 
+  case Type::LateParsedAttr:
+    return getTypeInfo(cast<LateParsedAttrType>(T)->desugar().getTypePtr());
+
   case Type::BTFTagAttributed:
     return getTypeInfo(
         cast<BTFTagAttributedType>(T)->getWrappedType().getTypePtr());
@@ -3751,6 +3754,17 @@ QualType ASTContext::getCountAttributedType(
   return QualType(CATy, 0);
 }
 
+QualType ASTContext::getLateParsedAttrType(
+    QualType WrappedTy, LateParsedTypeAttribute *LateParsedAttr) const {
+  QualType CanonTy = getCanonicalType(WrappedTy);
+
+  auto *LPATy = new (*this, alignof(LateParsedAttrType))
+      LateParsedAttrType(WrappedTy, CanonTy, LateParsedAttr);
+
+  Types.push_back(LPATy);
+  return QualType(LPATy, 0);
+}
+
 QualType
 ASTContext::adjustType(QualType Orig,
                        llvm::function_ref<QualType(QualType)> Adjust) const {
@@ -14920,6 +14934,10 @@ static QualType getCommonSugarTypeNode(const 
ASTContext &Ctx, const Type *X,
                                       DX->isCountInBytes(), DX->isOrNull(),
                                       CDX);
   }
+
+  case Type::LateParsedAttr:
+    return QualType();
+
   case Type::PredefinedSugar:
     assert(cast<PredefinedSugarType>(X)->getKind() !=
            cast<PredefinedSugarType>(Y)->getKind());
diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp
index f5848d154f49e..d91da096b2466 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -1842,6 +1842,13 @@ ASTNodeImporter::VisitCountAttributedType(const 
CountAttributedType *T) {
       ArrayRef(CoupledDecls));
 }
 
+ExpectedType
+ASTNodeImporter::VisitLateParsedAttrType(const LateParsedAttrType *T) {
+  // LateParsedAttrType is a transient placeholder that should not normally
+  // appear during AST import. Import as the wrapped type.
+  return import(T->getWrappedType());
+}
+
 ExpectedType ASTNodeImporter::VisitTemplateTypeParmType(
     const TemplateTypeParmType *T) {
   Expected<TemplateTypeParmDecl *> ToDeclOrErr = import(T->getDecl());
diff --git a/clang/lib/AST/ASTStructuralEquivalence.cpp 
b/clang/lib/AST/ASTStructuralEquivalence.cpp
index e0b62591a6a73..d8bbfbe5dac72 100644
--- a/clang/lib/AST/ASTStructuralEquivalence.cpp
+++ b/clang/lib/AST/ASTStructuralEquivalence.cpp
@@ -1208,6 +1208,13 @@ bool ASTStructuralEquivalence::isEquivalent(
       return false;
     break;
 
+  case Type::LateParsedAttr:
+    if (!IsStructurallyEquivalent(
+            Context, cast<LateParsedAttrType>(T1)->getWrappedType(),
+            cast<LateParsedAttrType>(T2)->getWrappedType()))
+      return false;
+    break;
+
   case Type::BTFTagAttributed:
     if (!IsStructurallyEquivalent(
             Context, cast<BTFTagAttributedType>(T1)->getWrappedType(),
diff --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp
index e5cdd6f31c507..60e77b66d92cf 100644
--- a/clang/lib/AST/ItaniumMangle.cpp
+++ b/clang/lib/AST/ItaniumMangle.cpp
@@ -2472,6 +2472,7 @@ bool 
CXXNameMangler::mangleUnresolvedTypeOrSimpleId(QualType Ty,
   case Type::BitInt:
   case Type::DependentBitInt:
   case Type::CountAttributed:
+  case Type::LateParsedAttr:
     llvm_unreachable("type is illegal as a nested name specifier");
 
   case Type::SubstBuiltinTemplatePack:
diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index 53b869e019074..ae9838413ab44 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -288,6 +288,7 @@ bool TypePrinter::canPrefixQualifiers(const Type *T,
     case Type::MacroQualified:
     case Type::OverflowBehavior:
     case Type::CountAttributed:
+    case Type::LateParsedAttr:
       CanPrefixQualifiers = false;
       break;
 
@@ -1832,6 +1833,20 @@ void TypePrinter::printCountAttributedAfter(const 
CountAttributedType *T,
     printCountAttributedImpl(T, OS, Policy);
 }
 
+void TypePrinter::printLateParsedAttrBefore(const LateParsedAttrType *T,
+                                            raw_ostream &OS) {
+  // LateParsedAttrType is a transient placeholder that should not appear
+  // in user-facing output. Just print the wrapped type.
+  printBefore(T->getWrappedType(), OS);
+}
+
+void TypePrinter::printLateParsedAttrAfter(const LateParsedAttrType *T,
+                                           raw_ostream &OS) {
+  // LateParsedAttrType is a transient placeholder that should not appear
+  // in user-facing output. Just print the wrapped type.
+  printAfter(T->getWrappedType(), OS);
+}
+
 void TypePrinter::printAttributedBefore(const AttributedType *T,
                                         raw_ostream &OS) {
   // FIXME: Generate this with TableGen.
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp 
b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
index 4b020c96964a7..7489a315af98c 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
@@ -1642,6 +1642,7 @@ void CIRGenFunction::emitVariablyModifiedType(QualType 
type) {
     case Type::HLSLAttributedResource:
     case Type::HLSLInlineSpirv:
     case Type::PredefinedSugar:
+    case Type::LateParsedAttr:
       cgm.errorNYI("CIRGenFunction::emitVariablyModifiedType");
       break;
 
diff --git a/clang/lib/CodeGen/CGDebugInfo.cpp 
b/clang/lib/CodeGen/CGDebugInfo.cpp
index 3d429d0d78e82..6305c3a115dd4 100644
--- a/clang/lib/CodeGen/CGDebugInfo.cpp
+++ b/clang/lib/CodeGen/CGDebugInfo.cpp
@@ -4332,6 +4332,7 @@ llvm::DIType *CGDebugInfo::CreateTypeNode(QualType Ty, 
llvm::DIFile *Unit) {
   case Type::PredefinedSugar:
     return getOrCreateType(cast<PredefinedSugarType>(Ty)->desugar(), Unit);
   case Type::CountAttributed:
+  case Type::LateParsedAttr:
   case Type::Auto:
   case Type::Attributed:
   case Type::BTFTagAttributed:
diff --git a/clang/lib/CodeGen/CodeGenFunction.cpp 
b/clang/lib/CodeGen/CodeGenFunction.cpp
index b920266b59808..a5f71a88535b4 100644
--- a/clang/lib/CodeGen/CodeGenFunction.cpp
+++ b/clang/lib/CodeGen/CodeGenFunction.cpp
@@ -2569,6 +2569,7 @@ void CodeGenFunction::EmitVariablyModifiedType(QualType 
type) {
     case Type::BitInt:
     case Type::HLSLInlineSpirv:
     case Type::PredefinedSugar:
+    case Type::LateParsedAttr:
       llvm_unreachable("type class is never variably-modified!");
 
     case Type::Adjusted:
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index ad6e7183cb3a4..0c051dc7d755a 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -4667,6 +4667,7 @@ static void captureVariablyModifiedType(ASTContext 
&Context, QualType T,
     case Type::SubstTemplateTypeParm:
     case Type::MacroQualified:
     case Type::CountAttributed:
+    case Type::LateParsedAttr:
       // Keep walking after single level desugaring.
       T = T.getSingleStepDesugaredType(Context);
       break;
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index 53107c827006d..50a9d4feb9b6d 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -7768,6 +7768,26 @@ QualType 
TreeTransform<Derived>::TransformCountAttributedType(
   return Result;
 }
 
+template <typename Derived>
+QualType
+TreeTransform<Derived>::TransformLateParsedAttrType(TypeLocBuilder &TLB,
+                                                    LateParsedAttrTypeLoc TL) {
+  const LateParsedAttrType *OldTy = TL.getTypePtr();
+  QualType InnerTy = getDerived().TransformType(TLB, TL.getInnerLoc());
+  if (InnerTy.isNull())
+    return QualType();
+
+  QualType Result = TL.getType();
+  if (getDerived().AlwaysRebuild() || InnerTy != OldTy->getWrappedType()) {
+    Result = SemaRef.Context.getLateParsedAttrType(
+        InnerTy, OldTy->getLateParsedAttribute());
+  }
+
+  LateParsedAttrTypeLoc newTL = TLB.push<LateParsedAttrTypeLoc>(Result);
+  newTL.setAttrNameLoc(TL.getAttrNameLoc());
+  return Result;
+}
+
 template <typename Derived>
 QualType TreeTransform<Derived>::TransformBTFTagAttributedType(
     TypeLocBuilder &TLB, BTFTagAttributedTypeLoc TL) {
diff --git a/clang/lib/Serialization/ASTReader.cpp 
b/clang/lib/Serialization/ASTReader.cpp
index f8a6a38bb9b5c..5c8827ea32514 100644
--- a/clang/lib/Serialization/ASTReader.cpp
+++ b/clang/lib/Serialization/ASTReader.cpp
@@ -7701,6 +7701,10 @@ void 
TypeLocReader::VisitCountAttributedTypeLoc(CountAttributedTypeLoc TL) {
   // Nothing to do
 }
 
+void TypeLocReader::VisitLateParsedAttrTypeLoc(LateParsedAttrTypeLoc TL) {
+  // Nothing to do
+}
+
 void TypeLocReader::VisitBTFTagAttributedTypeLoc(BTFTagAttributedTypeLoc TL) {
   // Nothing to do.
 }
diff --git a/clang/lib/Serialization/ASTWriter.cpp 
b/clang/lib/Serialization/ASTWriter.cpp
index 21dda6f3733e4..2c56a052a5fb4 100644
--- a/clang/lib/Serialization/ASTWriter.cpp
+++ b/clang/lib/Serialization/ASTWriter.cpp
@@ -156,6 +156,9 @@ static TypeCode getTypeCodeForTypeClass(Type::TypeClass id) 
{
 #define TYPE_BIT_CODE(CLASS_ID, CODE_ID, CODE_VALUE) \
   case Type::CLASS_ID: return TYPE_##CODE_ID;
 #include "clang/Serialization/TypeBitCodes.def"
+  case Type::LateParsedAttr:
+    llvm_unreachable(
+        "should be replaced with a concrete type before serialization");
   case Type::Builtin:
     llvm_unreachable("shouldn't be serializing a builtin type this way");
   }
@@ -587,6 +590,10 @@ void 
TypeLocWriter::VisitCountAttributedTypeLoc(CountAttributedTypeLoc TL) {
   // Nothing to do
 }
 
+void TypeLocWriter::VisitLateParsedAttrTypeLoc(LateParsedAttrTypeLoc TL) {
+  // Nothing to do
+}
+
 void TypeLocWriter::VisitBTFTagAttributedTypeLoc(BTFTagAttributedTypeLoc TL) {
   // Nothing to do.
 }
diff --git a/clang/tools/libclang/CIndex.cpp b/clang/tools/libclang/CIndex.cpp
index ac2fad38a1348..42d4b5bef7fb8 100644
--- a/clang/tools/libclang/CIndex.cpp
+++ b/clang/tools/libclang/CIndex.cpp
@@ -1738,6 +1738,10 @@ bool 
CursorVisitor::VisitCountAttributedTypeLoc(CountAttributedTypeLoc TL) {
   return Visit(TL.getInnerLoc());
 }
 
+bool CursorVisitor::VisitLateParsedAttrTypeLoc(LateParsedAttrTypeLoc TL) {
+  return Visit(TL.getInnerLoc());
+}
+
 bool CursorVisitor::VisitBTFTagAttributedTypeLoc(BTFTagAttributedTypeLoc TL) {
   return Visit(TL.getWrappedLoc());
 }
diff --git a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp 
b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp
index 82fd9844cf96a..8adfe096a7624 100644
--- a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp
+++ b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp
@@ -4099,6 +4099,9 @@ 
TypeSystemClang::GetTypeClass(lldb::opaque_compiler_type_t type) {
   case clang::Type::Using:
   case clang::Type::PredefinedSugar:
     llvm_unreachable("Handled in RemoveWrappingTypes!");
+  case clang::Type::LateParsedAttr:
+    llvm_unreachable("LateParsedAttrType is a transient parsing placeholder "
+                     "that is resolved before the AST is finalized.");
   case clang::Type::UnaryTransform:
     break;
   case clang::Type::FunctionNoProto:
@@ -4803,6 +4806,9 @@ lldb::Encoding 
TypeSystemClang::GetEncoding(lldb::opaque_compiler_type_t type) {
   case clang::Type::Using:
   case clang::Type::PredefinedSugar:
     llvm_unreachable("Handled in RemoveWrappingTypes!");
+  case clang::Type::LateParsedAttr:
+    llvm_unreachable("LateParsedAttrType is a transient parsing placeholder "
+                     "that is resolved before the AST is finalized.");
 
   case clang::Type::UnaryTransform:
     break;
@@ -5105,6 +5111,9 @@ lldb::Format 
TypeSystemClang::GetFormat(lldb::opaque_compiler_type_t type) {
   case clang::Type::Using:
   case clang::Type::PredefinedSugar:
     llvm_unreachable("Handled in RemoveWrappingTypes!");
+  case clang::Type::LateParsedAttr:
+    llvm_unreachable("LateParsedAttrType is a transient parsing placeholder "
+                     "that is resolved before the AST is finalized.");
   case clang::Type::UnaryTransform:
     break;
 

>From 90367bbe3755e98d035101a9fb595bc669241801 Mon Sep 17 00:00:00 2001
From: Holo-xy <[email protected]>
Date: Fri, 19 Jun 2026 22:03:24 +0300
Subject: [PATCH 2/6] fix code placement

---
 clang/lib/CodeGen/CodeGenFunction.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/lib/CodeGen/CodeGenFunction.cpp 
b/clang/lib/CodeGen/CodeGenFunction.cpp
index a5f71a88535b4..51426183211ec 100644
--- a/clang/lib/CodeGen/CodeGenFunction.cpp
+++ b/clang/lib/CodeGen/CodeGenFunction.cpp
@@ -2569,7 +2569,6 @@ void CodeGenFunction::EmitVariablyModifiedType(QualType 
type) {
     case Type::BitInt:
     case Type::HLSLInlineSpirv:
     case Type::PredefinedSugar:
-    case Type::LateParsedAttr:
       llvm_unreachable("type class is never variably-modified!");
 
     case Type::Adjusted:
@@ -2663,6 +2662,7 @@ void CodeGenFunction::EmitVariablyModifiedType(QualType 
type) {
     case Type::SubstTemplateTypeParm:
     case Type::MacroQualified:
     case Type::CountAttributed:
+    case Type::LateParsedAttr:
       // Keep walking after single level desugaring.
       type = type.getSingleStepDesugaredType(getContext());
       break;

>From 45490e6b37694883c039c1a8680613ec02b9b8c3 Mon Sep 17 00:00:00 2001
From: Holo-xy <[email protected]>
Date: Sun, 19 Jul 2026 20:12:04 +0300
Subject: [PATCH 3/6] make LateParsedAttrType unreachable in AST import and
 serialization

---
 clang/lib/AST/ASTImporter.cpp         | 4 +---
 clang/lib/Serialization/ASTReader.cpp | 3 ++-
 clang/lib/Serialization/ASTWriter.cpp | 3 ++-
 3 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp
index d91da096b2466..9c45fd3ffd185 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -1844,9 +1844,7 @@ ASTNodeImporter::VisitCountAttributedType(const 
CountAttributedType *T) {
 
 ExpectedType
 ASTNodeImporter::VisitLateParsedAttrType(const LateParsedAttrType *T) {
-  // LateParsedAttrType is a transient placeholder that should not normally
-  // appear during AST import. Import as the wrapped type.
-  return import(T->getWrappedType());
+  llvm_unreachable("should be replaced with a concrete type before AST 
import");
 }
 
 ExpectedType ASTNodeImporter::VisitTemplateTypeParmType(
diff --git a/clang/lib/Serialization/ASTReader.cpp 
b/clang/lib/Serialization/ASTReader.cpp
index 5c8827ea32514..49a78431506c5 100644
--- a/clang/lib/Serialization/ASTReader.cpp
+++ b/clang/lib/Serialization/ASTReader.cpp
@@ -7702,7 +7702,8 @@ void 
TypeLocReader::VisitCountAttributedTypeLoc(CountAttributedTypeLoc TL) {
 }
 
 void TypeLocReader::VisitLateParsedAttrTypeLoc(LateParsedAttrTypeLoc TL) {
-  // Nothing to do
+  llvm_unreachable(
+      "should be replaced with a concrete type before serialization");
 }
 
 void TypeLocReader::VisitBTFTagAttributedTypeLoc(BTFTagAttributedTypeLoc TL) {
diff --git a/clang/lib/Serialization/ASTWriter.cpp 
b/clang/lib/Serialization/ASTWriter.cpp
index 2c56a052a5fb4..0da13cd518ecd 100644
--- a/clang/lib/Serialization/ASTWriter.cpp
+++ b/clang/lib/Serialization/ASTWriter.cpp
@@ -591,7 +591,8 @@ void 
TypeLocWriter::VisitCountAttributedTypeLoc(CountAttributedTypeLoc TL) {
 }
 
 void TypeLocWriter::VisitLateParsedAttrTypeLoc(LateParsedAttrTypeLoc TL) {
-  // Nothing to do
+  llvm_unreachable(
+      "should be replaced with a concrete type before serialization");
 }
 
 void TypeLocWriter::VisitBTFTagAttributedTypeLoc(BTFTagAttributedTypeLoc TL) {

>From 7c3277ba7ebbbb24419e54dcee0cf1345b01edb4 Mon Sep 17 00:00:00 2001
From: Holo-xy <[email protected]>
Date: Sun, 19 Jul 2026 20:30:01 +0300
Subject: [PATCH 4/6] document LateParsedAttrType canonical type and drop
 FoldingSetNode

---
 clang/include/clang/AST/TypeBase.h | 20 +++++++-------------
 1 file changed, 7 insertions(+), 13 deletions(-)

diff --git a/clang/include/clang/AST/TypeBase.h 
b/clang/include/clang/AST/TypeBase.h
index dd46a58a6fa50..663ccb5a8a446 100644
--- a/clang/include/clang/AST/TypeBase.h
+++ b/clang/include/clang/AST/TypeBase.h
@@ -3548,10 +3548,14 @@ class CountAttributedType final
 
 /// Represents a placeholder type for late-parsed type attributes.
 /// This type wraps another type and holds an opaque pointer to a
-/// LateParsedAttribute that will be parsed later (e.g., in ActOnFields).
+/// LateParsedTypeAttribute that will be parsed later (e.g., in ActOnFields).
 /// Once parsed, this type is replaced with the appropriate attributed type
-/// (e.g., CountAttributedType for counted_by).
-class LateParsedAttrType : public Type, public llvm::FoldingSetNode {
+/// (e.g., CountAttributedType for `__counted_by`).
+///
+/// Its canonical type is that of the wrapped type, so a consumer walking the
+/// AST during late parsing must treat this as "attribute unresolved", not "no
+/// attribute here".
+class LateParsedAttrType : public Type {
   friend class ASTContext; // ASTContext creates these.
 
   QualType WrappedTy;
@@ -3571,16 +3575,6 @@ class LateParsedAttrType : public Type, public 
llvm::FoldingSetNode {
   bool isSugared() const { return true; }
   QualType desugar() const { return WrappedTy; }
 
-  void Profile(llvm::FoldingSetNodeID &ID) {
-    Profile(ID, WrappedTy, LateParsedTypeAttr);
-  }
-
-  static void Profile(llvm::FoldingSetNodeID &ID, QualType Wrapped,
-                      LateParsedTypeAttribute *Attr) {
-    ID.AddPointer(Wrapped.getAsOpaquePtr());
-    ID.AddPointer(Attr);
-  }
-
   static bool classof(const Type *T) {
     return T->getTypeClass() == LateParsedAttr;
   }

>From 9006e13956171092b1b5003bedb423cc88cb7d3c Mon Sep 17 00:00:00 2001
From: Holo-xy <[email protected]>
Date: Fri, 31 Jul 2026 19:06:00 +0300
Subject: [PATCH 5/6] support counted_by on function params

make the counted_by family (counted_by, counted_by_or_null, sized_by,
sized_by_or_null) work in type position on a function parameter under
-fexperimental-late-parse-attributes
---
 .../clang/Basic/DiagnosticSemaKinds.td        |   6 +
 clang/include/clang/Parse/Parser.h            |  18 +-
 clang/include/clang/Sema/DeclSpec.h           |   6 +-
 clang/include/clang/Sema/Sema.h               |  65 +++
 clang/lib/Parse/ParseDecl.cpp                 |  82 ++-
 clang/lib/Parse/Parser.cpp                    |   5 +
 clang/lib/Sema/SemaBoundsSafety.cpp           | 529 +++++++++++++++---
 clang/lib/Sema/SemaType.cpp                   |  39 ++
 8 files changed, 674 insertions(+), 76 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td 
b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 8097800e6744a..8039ac7b8cc18 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -7131,6 +7131,12 @@ def err_counted_by_attr_refer_to_itself : Error<
   "'counted_by' cannot refer to the flexible array member %0">;
 def err_count_attr_must_be_in_structure : Error<
   "field %0 in 
'%select{counted_by|sized_by|counted_by_or_null|sized_by_or_null}1' not inside 
structure">;
+def err_count_attr_not_param_of_same_function : Error<
+  "'%select{counted_by|sized_by|counted_by_or_null|sized_by_or_null}1' 
argument %0 is not a parameter of the same function as the annotated pointer">;
+def err_count_attr_on_nested_type : Error<
+  "'%select{counted_by|sized_by|counted_by_or_null|sized_by_or_null}0' is not 
supported on "
+  "%select{a nested pointer|an array element}1; it must apply to the "
+  "annotated declaration's own type">;
 def err_count_attr_argument_not_integer : Error<
   "'%select{counted_by|sized_by|counted_by_or_null|sized_by_or_null}0' 
requires a non-boolean integer type argument">;
 def err_count_attr_only_support_simple_decl_reference : Error<
diff --git a/clang/include/clang/Parse/Parser.h 
b/clang/include/clang/Parse/Parser.h
index 5e7af97feeb6c..7c72b1c307ea0 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -2670,7 +2670,8 @@ class Parser : public CodeCompletionHandler {
   void ParseTypeQualifierListOpt(
       DeclSpec &DS, unsigned AttrReqs = AR_AllAttributesParsed,
       bool AtomicOrPtrauthAllowed = true, bool IdentifierRequired = false,
-      llvm::function_ref<void()> CodeCompletionHandler = {});
+      llvm::function_ref<void()> CodeCompletionHandler = {},
+      LateParsedAttrList *LateAttrs = nullptr);
 
   /// ParseDirectDeclarator
   /// \verbatim
@@ -8090,6 +8091,21 @@ class Parser : public CodeCompletionHandler {
 
   static void LateTemplateParserCallback(void *P, LateParsedTemplate &LPT);
 
+  /// Parse the cached tokens of \p LTA into \p OutAttrs. Takes ownership of
+  /// \p LTA.
+  static void ParseLateParsedTypeAttributeCallback(LateParsedTypeAttribute 
*LTA,
+                                                   ParsedAttributes *OutAttrs);
+
+  /// Validate \p LA against \p type and, if it applies, wrap \p type in a
+  /// \c LateParsedAttrType placeholder. Returns false if the attribute is
+  /// invalid for \p type.
+  static bool ProcessLateParsedTypeAttrCallback(LateParsedAttribute *LA,
+                                                QualType &type);
+
+  /// Return the source location of the attribute name stored in \p LTA.
+  static SourceLocation
+  GetLateParsedAttributeLocationCallback(const LateParsedTypeAttribute *LTA);
+
   /// 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 6e7f9cd6e3d38..dac4c9f498f63 100644
--- a/clang/include/clang/Sema/DeclSpec.h
+++ b/clang/include/clang/Sema/DeclSpec.h
@@ -1303,6 +1303,7 @@ struct DeclaratorChunk {
   }
 
   ParsedAttributesView AttrList;
+  LateParsedAttrList LateAttrList;
 
   struct PointerTypeInfo {
     /// The type qualifiers: const/volatile/restrict/unaligned/atomic.
@@ -2403,13 +2404,16 @@ class Declarator {
   /// This function takes attrs by R-Value reference because it takes ownership
   /// of those attributes from the parameter.
   void AddTypeInfo(const DeclaratorChunk &TI, ParsedAttributes &&attrs,
-                   SourceLocation EndLoc) {
+                   SourceLocation EndLoc,
+                   const LateParsedAttrList &LateAttrs = {}) {
     DeclTypeInfo.push_back(TI);
     DeclTypeInfo.back().getAttrs().prepend(attrs.begin(), attrs.end());
     getAttributePool().takeAllFrom(attrs.getPool());
 
     if (!EndLoc.isInvalid())
       SetRangeEnd(EndLoc);
+
+    DeclTypeInfo.back().LateAttrList.append(LateAttrs);
   }
 
   /// AddTypeInfo - Add a chunk to this declarator. Also extend the range to
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index b8d760e7e0975..ca0ccb9cac29d 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -143,6 +143,8 @@ class InitializationKind;
 class InitializationSequence;
 class InitializedEntity;
 enum class LangAS : unsigned int;
+struct LateParsedAttribute;
+struct LateParsedTypeAttribute;
 class LocalInstantiationScope;
 class LookupResult;
 class MangleNumberingContext;
@@ -1356,6 +1358,69 @@ class Sema final : public SemaBase {
     OpaqueParser = P;
   }
 
+  /// Parse the cached tokens of \p LTA into \p OutAttrs. Takes ownership of
+  /// \p LTA.
+  typedef void ParseLateParsedTypeAttributeCB(LateParsedTypeAttribute *LTA,
+                                              ParsedAttributes *OutAttrs);
+
+  /// Validate \p LA against \p type and, if it applies, wrap \p type in a
+  /// \c LateParsedAttrType placeholder. Returns false if the attribute is
+  /// invalid for \p type.
+  typedef bool ProcessLateParsedTypeAttrCB(LateParsedAttribute *LA,
+                                           QualType &type);
+  ProcessLateParsedTypeAttrCB *ProcessLateParsedTypeAttrCallback = nullptr;
+
+  /// Return the source location of the attribute name stored in \p LTA.
+  typedef SourceLocation
+  GetLateParsedAttributeLocationCB(const LateParsedTypeAttribute *LTA);
+  GetLateParsedAttributeLocationCB *GetLateParsedAttributeLocationCallback =
+      nullptr;
+
+  /// Wrap \p type in a LateParsedAttrType placeholder for \p LTA, after
+  /// checking that the attribute is applicable to \p type at all. Returns
+  /// false if the attribute should be dropped.
+  bool ActOnLateParsedTypeAttr(ParsedAttr::Kind AttrKind,
+                               SourceLocation AttrNameLoc, QualType &type,
+                               LateParsedTypeAttribute *LTA);
+
+  /// RAII object that pushes a function prototype scope holding \p Params and
+  /// restores the previous scope on destruction.
+  ///
+  /// A count expression written inside a *nested* function prototype, as in
+  ///
+  /// \code
+  ///   void f(int *__counted_by(len) (*cb)(int len));
+  /// \endcode
+  ///
+  /// names a parameter of that inner prototype, whose scope has already been
+  /// popped by the time the attribute's tokens are parsed. Re-creating the
+  /// scope lets ordinary name lookup resolve the count at any nesting depth.
+  class FunctionPrototypeScopeRAII {
+    Sema &S;
+    Scope ProtoScope;
+
+  public:
+    FunctionPrototypeScopeRAII(Sema &S, ArrayRef<Decl *> Params);
+    ~FunctionPrototypeScopeRAII();
+
+    FunctionPrototypeScopeRAII(const FunctionPrototypeScopeRAII &) = delete;
+    FunctionPrototypeScopeRAII &
+    operator=(const FunctionPrototypeScopeRAII &) = delete;
+  };
+
+  /// Resolve late-parsed type attributes in the types of \p Params.
+  ///
+  /// Called once the whole parameter clause has been parsed, so that a count
+  /// expression may refer to any parameter of the function, including one
+  /// declared after the annotated parameter.
+  ///
+  /// This pushes its own prototype scope for \p Params, so it does not require
+  /// the caller's prototype scope to still be open. It does need *some* scope
+  /// to be current, since lookup has to be able to reach file scope to
+  /// diagnose a count that names a global.
+  void ProcessLateParsedTypeAttributesForParams(
+      ArrayRef<Decl *> Params, ParseLateParsedTypeAttributeCB *ParseCB);
+
   /// Callback to the parser to parse a type expressed as a string.
   std::function<TypeResult(StringRef, StringRef, SourceLocation)>
       ParseTypeFromStringCallback;
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index 405dddf7991b4..92f04a91d1e5a 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -117,6 +117,15 @@ static bool IsAttributeArgsParsedInFunctionScope(const 
IdentifierInfo &II) {
 #undef CLANG_ATTR_PARSE_ARGS_IN_FUNCTION_SCOPE_LIST
 }
 
+/// Returns true iff the attribute kind corresponds to a TypeAttr or
+/// DeclOrTypeAttr in Attr.td.
+static bool IsAttributeTypeAttr(ParsedAttr::Kind Kind) {
+  const auto &Infos = ParsedAttrInfo::getAllBuiltin();
+  if ((size_t)Kind < Infos.size())
+    return Infos[Kind]->IsType;
+  return false;
+}
+
 /// Check if the a start and end source location expand to the same macro.
 static bool FindLocsWithCommonFileID(Preprocessor &PP, SourceLocation StartLoc,
                                      SourceLocation EndLoc) {
@@ -173,8 +182,14 @@ bool Parser::ParseSingleGNUAttribute(ParsedAttributes 
&Attrs,
     // 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 type-attr-only list takes type attributes only; a decl attribute like
+    // `guarded_by` captured there would be silently dropped.
+    ParsedAttr::Kind AttrKind = ParsedAttr::getParsedKind(
+        AttrName, nullptr, ParsedAttr::Form::GNU().getSyntax());
     LateParse = getLangOpts().ExperimentalLateParseAttributes &&
-                IsAttributeLateParsedExperimentalExt(*AttrName);
+                IsAttributeLateParsedExperimentalExt(*AttrName) &&
+                (IsAttributeTypeAttr(AttrKind) ||
+                 !LateAttrs->lateAttrParseTypeAttrOnly());
   } else {
     // The caller did not restrict late parsing to only
     // `LateAttrParseExperimentalExt` attributes so late parse
@@ -193,7 +208,9 @@ bool Parser::ParseSingleGNUAttribute(ParsedAttributes 
&Attrs,
 
   // Handle attributes with arguments that require late parsing.
   LateParsedAttribute *LA =
-      new LateParsedAttribute(this, *AttrName, AttrNameLoc);
+      LateAttrs->lateAttrParseTypeAttrOnly()
+          ? new LateParsedTypeAttribute(this, *AttrName, AttrNameLoc)
+          : new LateParsedAttribute(this, *AttrName, AttrNameLoc);
   LateAttrs->push_back(LA);
 
   // Attributes in a class are parsed at the end of the class, along
@@ -4922,6 +4939,35 @@ void LateParsedTypeAttribute::ParseInto(ParsedAttributes 
&OutAttrs) {
   Self->ParseLexedTypeAttribute(*this, OutAttrs);
 }
 
+void Parser::ParseLateParsedTypeAttributeCallback(LateParsedTypeAttribute *LTA,
+                                                  ParsedAttributes *OutAttrs) {
+  LTA->ParseInto(*OutAttrs);
+  delete LTA;
+}
+
+SourceLocation Parser::GetLateParsedAttributeLocationCallback(
+    const LateParsedTypeAttribute *LTA) {
+  assert(LTA);
+  return LTA->AttrNameLoc;
+}
+
+bool Parser::ProcessLateParsedTypeAttrCallback(LateParsedAttribute *LA,
+                                               QualType &type) {
+  auto *LTA = dyn_cast_if_present<LateParsedTypeAttribute>(LA);
+  if (!LTA)
+    return true;
+
+  ParsedAttr::Kind AttrKind = ParsedAttr::getParsedKind(
+      &LTA->AttrName, nullptr, ParsedAttr::Form::GNU().getSyntax());
+  if (LTA->Self->Actions.ActOnLateParsedTypeAttr(AttrKind, LTA->AttrNameLoc,
+                                                 type, LTA))
+    return true;
+
+  // No placeholder was built, so nothing else will ever own these tokens.
+  delete LTA;
+  return false;
+}
+
 void Parser::TakeTypeAttrsAppendingFrom(LateParsedAttrList &To,
                                         LateParsedAttrList &From) {
   LateParsedAttrList::iterator It =
@@ -6243,7 +6289,8 @@ bool Parser::isConstructorDeclarator(bool IsUnqualified, 
bool DeductionGuide,
 
 void Parser::ParseTypeQualifierListOpt(
     DeclSpec &DS, unsigned AttrReqs, bool AtomicOrPtrauthAllowed,
-    bool IdentifierRequired, llvm::function_ref<void()> CodeCompletionHandler) 
{
+    bool IdentifierRequired, llvm::function_ref<void()> CodeCompletionHandler,
+    LateParsedAttrList *LateAttrs) {
   if ((AttrReqs & AR_CXX11AttributesParsed) &&
       isAllowedCXX11AttributeSpecifier()) {
     ParsedAttributes Attrs(AttrFactory);
@@ -6405,7 +6452,7 @@ void Parser::ParseTypeQualifierListOpt(
       // recovery is graceful.
       if (AttrReqs & AR_GNUAttributesParsed ||
           AttrReqs & AR_GNUAttributesParsedAndRejected) {
-        ParseGNUAttributes(DS.getAttributes());
+        ParseGNUAttributes(DS.getAttributes(), LateAttrs);
         continue; // do *not* consume the next token!
       }
       // otherwise, FALL THROUGH!
@@ -6588,8 +6635,19 @@ void Parser::ParseDeclaratorInternal(Declarator &D,
                     ((D.getContext() != DeclaratorContext::CXXNew)
                          ? AR_GNUAttributesParsed
                          : AR_GNUAttributesParsedAndRejected);
+
+    LateParsedAttrList LateAttrs(/*PSoon=*/true,
+                                 /*LateAttrParseExperimentalExtOnly=*/true,
+                                 /*LateAttrParseTypeAttrOnly=*/true);
+
+    LateParsedAttrList *LateAttrsPtr =
+        D.getContext() == DeclaratorContext::Prototype &&
+                !getLangOpts().CPlusPlus
+            ? &LateAttrs
+            : nullptr;
+
     ParseTypeQualifierListOpt(DS, Reqs, /*AtomicOrPtrauthAllowed=*/true,
-                              !D.mayOmitIdentifier());
+                              !D.mayOmitIdentifier(), {}, LateAttrsPtr);
     D.ExtendWithDeclSpec(DS);
 
     // Recursively parse the declarator.
@@ -6602,12 +6660,12 @@ void Parser::ParseDeclaratorInternal(Declarator &D,
                         DS.getVolatileSpecLoc(), DS.getRestrictSpecLoc(),
                         DS.getAtomicSpecLoc(), DS.getUnalignedSpecLoc(),
                         DS.getOverflowBehaviorLoc(), DS.isWrapSpecified()),
-                    std::move(DS.getAttributes()), SourceLocation());
+                    std::move(DS.getAttributes()), SourceLocation(), 
LateAttrs);
     else
       // Remember that we parsed a Block type, and remember the type-quals.
       D.AddTypeInfo(
           DeclaratorChunk::getBlockPointer(DS.getTypeQualifiers(), Loc),
-          std::move(DS.getAttributes()), SourceLocation());
+          std::move(DS.getAttributes()), SourceLocation(), LateAttrs);
   } else {
     // Is a reference
     DeclSpec DS(AttrFactory);
@@ -7358,6 +7416,16 @@ void Parser::ParseFunctionDeclarator(Declarator &D,
     else if (RequiresArg)
       Diag(Tok, diag::err_argument_required_after_attribute);
 
+    // Every parameter is now declared and the prototype scope is still active,
+    // so resolve late-parsed attributes here.
+    if (getLangOpts().ExperimentalLateParseAttributes && !ParamInfo.empty()) {
+      SmallVector<Decl *, 16> Params;
+      for (const DeclaratorChunk::ParamInfo &PI : ParamInfo)
+        Params.push_back(PI.Param);
+      Actions.ProcessLateParsedTypeAttributesForParams(
+          Params, &Parser::ParseLateParsedTypeAttributeCallback);
+    }
+
     // OpenCL disallows functions without a prototype, but it doesn't enforce
     // strict prototypes as in C23 because it allows a function definition to
     // have an identifier list. See OpenCL 3.0 6.11/g for more details.
diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp
index 5e1fd4df1a3f0..66d6e4a2c8342 100644
--- a/clang/lib/Parse/Parser.cpp
+++ b/clang/lib/Parse/Parser.cpp
@@ -83,6 +83,11 @@ Parser::Parser(Preprocessor &pp, Sema &actions, bool 
skipFunctionBodies)
       [this](StringRef TypeStr, StringRef Context, SourceLocation IncludeLoc) {
         return this->ParseTypeFromString(TypeStr, Context, IncludeLoc);
       };
+
+  Actions.ProcessLateParsedTypeAttrCallback =
+      &Parser::ProcessLateParsedTypeAttrCallback;
+  Actions.GetLateParsedAttributeLocationCallback =
+      &Parser::GetLateParsedAttributeLocationCallback;
 }
 
 DiagnosticBuilder Parser::Diag(SourceLocation Loc, unsigned DiagID) {
diff --git a/clang/lib/Sema/SemaBoundsSafety.cpp 
b/clang/lib/Sema/SemaBoundsSafety.cpp
index b01250ccd0ffc..21ff9a30def3b 100644
--- a/clang/lib/Sema/SemaBoundsSafety.cpp
+++ b/clang/lib/Sema/SemaBoundsSafety.cpp
@@ -11,9 +11,12 @@
 /// (e.g. `counted_by`)
 ///
 
//===----------------------------------------------------------------------===//
+#include "TreeTransform.h"
 #include "clang/Lex/Lexer.h"
 #include "clang/Sema/Initialization.h"
 #include "clang/Sema/Sema.h"
+#include "llvm/Support/SaveAndRestore.h"
+#include <optional>
 
 namespace clang {
 
@@ -26,6 +29,56 @@ getCountAttrKind(bool CountInBytes, bool OrNull) {
                 : CountAttributedType::CountedBy;
 }
 
+namespace {
+struct BoundsAttrFlags {
+  bool CountInBytes = false;
+  bool OrNull = false;
+};
+} // namespace
+
+static std::optional<BoundsAttrFlags> getBoundsAttrFlags(ParsedAttr::Kind K) {
+  BoundsAttrFlags Flags;
+  switch (K) {
+  case ParsedAttr::AT_CountedBy:
+    break;
+  case ParsedAttr::AT_CountedByOrNull:
+    Flags.OrNull = true;
+    break;
+  case ParsedAttr::AT_SizedBy:
+    Flags.CountInBytes = true;
+    break;
+  case ParsedAttr::AT_SizedByOrNull:
+    Flags.CountInBytes = true;
+    Flags.OrNull = true;
+    break;
+  default:
+    return std::nullopt;
+  }
+  return Flags;
+}
+
+bool Sema::ActOnLateParsedTypeAttr(ParsedAttr::Kind AttrKind,
+                                   SourceLocation AttrNameLoc, QualType &type,
+                                   LateParsedTypeAttribute *LTA) {
+  // Not an attribute this mechanism handles; leave it alone.
+  std::optional<BoundsAttrFlags> Flags = getBoundsAttrFlags(AttrKind);
+  if (!Flags)
+    return false;
+
+  // The argument-independent validation, run before the attribute's argument
+  // has been parsed. An array parameter has already decayed to a pointer, so
+  // unlike a flexible array member there is no array spelling to accept here.
+  if (!type->isPointerType()) {
+    unsigned Kind = getCountAttrKind(Flags->CountInBytes, Flags->OrNull);
+    Diag(AttrNameLoc, diag::err_count_attr_not_on_ptr_or_flexible_array_member)
+        << Kind << AttrNameLoc << /*do not suggest counted_by*/ 0;
+    return false;
+  }
+
+  type = Context.getLateParsedAttrType(type, LTA);
+  return true;
+}
+
 static const RecordDecl *GetEnclosingNamedOrTopAnonRecord(const FieldDecl *FD) 
{
   const auto *RD = FD->getParent();
   // An unnamed struct is treated as anonymous struct at this point.
@@ -49,59 +102,19 @@ enum class CountedByInvalidPointeeTypeKind {
   VALID,
 };
 
-bool Sema::CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes,
-                                     bool OrNull) {
-  // Check the context the attribute is used in
-
-  unsigned Kind = getCountAttrKind(CountInBytes, OrNull);
-
-  if (FD->getParent()->isUnion()) {
-    Diag(FD->getBeginLoc(), diag::err_count_attr_in_union)
-        << Kind << FD->getSourceRange();
-    return true;
-  }
-
-  const auto FieldTy = FD->getType();
-  if (FieldTy->isArrayType() && (CountInBytes || OrNull)) {
-    Diag(FD->getBeginLoc(),
-         diag::err_count_attr_not_on_ptr_or_flexible_array_member)
-        << Kind << FD->getLocation() << /* suggest counted_by */ 1;
-    return true;
-  }
-  if (!FieldTy->isArrayType() && !FieldTy->isPointerType()) {
-    Diag(FD->getBeginLoc(),
-         diag::err_count_attr_not_on_ptr_or_flexible_array_member)
-        << Kind << FD->getLocation() << /* do not suggest counted_by */ 0;
-    return true;
-  }
-
-  LangOptions::StrictFlexArraysLevelKind StrictFlexArraysLevel =
-      LangOptions::StrictFlexArraysLevelKind::IncompleteOnly;
-  if (FieldTy->isArrayType() &&
-      !Decl::isFlexibleArrayMemberLike(getASTContext(), FD, FieldTy,
-                                       StrictFlexArraysLevel, true)) {
-    Diag(FD->getBeginLoc(),
-         diag::err_counted_by_attr_on_array_not_flexible_array_member)
-        << Kind << FD->getLocation();
-    return true;
-  }
-
+/// Diagnose a counted_by-family attribute whose pointee (or array element)
+/// type \p PointeeTy cannot support bounds computation. Shared between the
+/// struct-field and function-parameter paths so both accept and reject the
+/// same shapes. \p DowngradeFAMPointeeErrToWarn is the Linux-kernel
+/// workaround; see the caller in CheckCountedByAttrOnField. Returns true if
+/// the attribute cannot be applied.
+static bool CheckCountAttrPointeeType(Sema &S, QualType PointeeTy,
+                                      bool CountInBytes, unsigned Kind,
+                                      int SelectPtrOrArr,
+                                      bool DowngradeFAMPointeeErrToWarn,
+                                      SourceLocation Loc, SourceRange Range) {
   CountedByInvalidPointeeTypeKind InvalidTypeKind =
       CountedByInvalidPointeeTypeKind::VALID;
-  QualType PointeeTy;
-  int SelectPtrOrArr = 0;
-  if (FieldTy->isPointerType()) {
-    PointeeTy = FieldTy->getPointeeType();
-    SelectPtrOrArr = 0;
-  } else {
-    assert(FieldTy->isArrayType());
-    const ArrayType *AT = getASTContext().getAsArrayType(FieldTy);
-    PointeeTy = AT->getElementType();
-    SelectPtrOrArr = 1;
-  }
-  // Note: The `Decl::isFlexibleArrayMemberLike` check earlier on means
-  // only `PointeeTy->isStructureTypeWithFlexibleArrayMember()` is reachable
-  // when `FieldTy->isArrayType()`.
   bool ShouldWarn = false;
   if (PointeeTy->isAlwaysIncompleteType() && !CountInBytes) {
     // In general using `counted_by` or `counted_by_or_null` on
@@ -125,8 +138,8 @@ bool Sema::CheckCountedByAttrOnField(FieldDecl *FD, Expr 
*E, bool CountInBytes,
     // * When the pointee type might not always be an incomplete type (i.e.
     // a type that is currently incomplete but might be completed later
     // on in the translation unit) the attribute is allowed by this method
-    // but later uses of the FieldDecl are checked that the pointee type
-    // is complete see `BoundsSafetyCheckAssignmentToCountAttrPtr`,
+    // but later uses of the annotated declaration are checked that the pointee
+    // type is complete see `BoundsSafetyCheckAssignmentToCountAttrPtr`,
     // `BoundsSafetyCheckInitialization`, and
     // `BoundsSafetyCheckUseOfCountAttrPtr`
     //
@@ -141,9 +154,8 @@ bool Sema::CheckCountedByAttrOnField(FieldDecl *FD, Expr 
*E, bool CountInBytes,
     bool IsVoidPtr = PointeeTy->isVoidType();
     if (IsVoidPtr) {
       // Emit a warning that this is a GNU extension.
-      Diag(FD->getBeginLoc(), diag::ext_gnu_counted_by_void_ptr) << Kind;
-      Diag(FD->getBeginLoc(), diag::note_gnu_counted_by_void_ptr_use_sized_by)
-          << Kind;
+      S.Diag(Loc, diag::ext_gnu_counted_by_void_ptr) << Kind;
+      S.Diag(Loc, diag::note_gnu_counted_by_void_ptr_use_sized_by) << Kind;
       assert(InvalidTypeKind == CountedByInvalidPointeeTypeKind::VALID);
     } else {
       InvalidTypeKind = CountedByInvalidPointeeTypeKind::INCOMPLETE;
@@ -153,15 +165,8 @@ bool Sema::CheckCountedByAttrOnField(FieldDecl *FD, Expr 
*E, bool CountInBytes,
   } else if (PointeeTy->isFunctionType()) {
     InvalidTypeKind = CountedByInvalidPointeeTypeKind::FUNCTION;
   } else if (PointeeTy->isStructureTypeWithFlexibleArrayMember()) {
-    if (FieldTy->isArrayType() && !getLangOpts().BoundsSafety) {
-      // This is a workaround for the Linux kernel that has already adopted
-      // `counted_by` on a FAM where the pointee is a struct with a FAM. This
-      // should be an error because computing the bounds of the array cannot be
-      // done correctly without manually traversing every struct object in the
-      // array at runtime. To allow the code to be built this error is
-      // downgraded to a warning.
+    if (DowngradeFAMPointeeErrToWarn)
       ShouldWarn = true;
-    }
     InvalidTypeKind = CountedByInvalidPointeeTypeKind::FLEXIBLE_ARRAY_MEMBER;
   }
 
@@ -169,12 +174,78 @@ bool Sema::CheckCountedByAttrOnField(FieldDecl *FD, Expr 
*E, bool CountInBytes,
     unsigned DiagID = ShouldWarn
                           ? diag::warn_counted_by_attr_elt_type_unknown_size
                           : diag::err_counted_by_attr_pointee_unknown_size;
-    Diag(FD->getBeginLoc(), DiagID)
-        << SelectPtrOrArr << PointeeTy << (int)InvalidTypeKind
-        << (ShouldWarn ? 1 : 0) << Kind << FD->getSourceRange();
+    S.Diag(Loc, DiagID) << SelectPtrOrArr << PointeeTy << (int)InvalidTypeKind
+                        << (ShouldWarn ? 1 : 0) << Kind << Range;
+    return true;
+  }
+  return false;
+}
+
+bool Sema::CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes,
+                                     bool OrNull) {
+  // Check the context the attribute is used in
+
+  unsigned Kind = getCountAttrKind(CountInBytes, OrNull);
+
+  if (FD->getParent()->isUnion()) {
+    Diag(FD->getBeginLoc(), diag::err_count_attr_in_union)
+        << Kind << FD->getSourceRange();
+    return true;
+  }
+
+  const auto FieldTy = FD->getType();
+  if (FieldTy->isArrayType() && (CountInBytes || OrNull)) {
+    Diag(FD->getBeginLoc(),
+         diag::err_count_attr_not_on_ptr_or_flexible_array_member)
+        << Kind << FD->getLocation() << /* suggest counted_by */ 1;
+    return true;
+  }
+  if (!FieldTy->isArrayType() && !FieldTy->isPointerType()) {
+    Diag(FD->getBeginLoc(),
+         diag::err_count_attr_not_on_ptr_or_flexible_array_member)
+        << Kind << FD->getLocation() << /* do not suggest counted_by */ 0;
+    return true;
+  }
+
+  LangOptions::StrictFlexArraysLevelKind StrictFlexArraysLevel =
+      LangOptions::StrictFlexArraysLevelKind::IncompleteOnly;
+  if (FieldTy->isArrayType() &&
+      !Decl::isFlexibleArrayMemberLike(getASTContext(), FD, FieldTy,
+                                       StrictFlexArraysLevel, true)) {
+    Diag(FD->getBeginLoc(),
+         diag::err_counted_by_attr_on_array_not_flexible_array_member)
+        << Kind << FD->getLocation();
     return true;
   }
 
+  QualType PointeeTy;
+  int SelectPtrOrArr = 0;
+  if (FieldTy->isPointerType()) {
+    PointeeTy = FieldTy->getPointeeType();
+    SelectPtrOrArr = 0;
+  } else {
+    assert(FieldTy->isArrayType());
+    const ArrayType *AT = getASTContext().getAsArrayType(FieldTy);
+    PointeeTy = AT->getElementType();
+    SelectPtrOrArr = 1;
+  }
+  // Note: The `Decl::isFlexibleArrayMemberLike` check earlier on means
+  // only `PointeeTy->isStructureTypeWithFlexibleArrayMember()` is reachable
+  // when `FieldTy->isArrayType()`.
+  //
+  // Downgrading the FAM-pointee error to a warning on such arrays is a
+  // workaround for the Linux kernel that has already adopted `counted_by` on
+  // a FAM where the pointee is a struct with a FAM. This should be an error
+  // because computing the bounds of the array cannot be done correctly
+  // without manually traversing every struct object in the array at runtime.
+  // To allow the code to be built the error is downgraded to a warning.
+  bool DowngradeFAMPointeeErrToWarn =
+      FieldTy->isArrayType() && !getLangOpts().BoundsSafety;
+  if (CheckCountAttrPointeeType(*this, PointeeTy, CountInBytes, Kind,
+                                SelectPtrOrArr, DowngradeFAMPointeeErrToWarn,
+                                FD->getBeginLoc(), FD->getSourceRange()))
+    return true;
+
   // Check the expression
 
   if (!E->getType()->isIntegerType() || E->getType()->isBooleanType()) {
@@ -411,4 +482,328 @@ bool Sema::BoundsSafetyCheckUseOfCountAttrPtr(const Expr 
*E) {
   return false;
 }
 
+/// Build the CountAttributedType for a counted_by-family attribute on a
+/// parameter's type, whose argument has just been parsed. \p Params is the
+/// prototype's parameter list; the count must name one of them. Null on error.
+static QualType buildCountAttributedTypeForParam(Sema &S, QualType InnerTy,
+                                                 ArrayRef<Decl *> Params,
+                                                 ParsedAttr &AL) {
+  std::optional<BoundsAttrFlags> Flags = getBoundsAttrFlags(AL.getKind());
+  assert(Flags && "placeholder for a non-counted_by-family attribute");
+  auto [CountInBytes, OrNull] = *Flags;
+  unsigned Kind = getCountAttrKind(CountInBytes, OrNull);
+
+  // The same pointee rules as the struct-field path, checked in the same
+  // order: the pointee's size before the count expression. A placeholder is
+  // only created for a pointer, so the pointee is always there to take.
+  if (CheckCountAttrPointeeType(S, InnerTy->getPointeeType(), CountInBytes,
+                                Kind, /*SelectPtrOrArr=*/0,
+                                /*DowngradeFAMPointeeErrToWarn=*/false,
+                                AL.getLoc(), AL.getRange()))
+    return QualType();
+
+  Expr *CountExpr = AL.getArgAsExpr(0);
+  if (!CountExpr)
+    return QualType();
+
+  // The argument must name a single declaration, so that assignments to the
+  // count can be related back to the pointer. No paren-stripping: the
+  // expression handed to BuildCountAttributedArrayOrPointerType must itself be
+  // the DeclRefExpr (BuildTypeCoupledDecls casts it), and the field path in
+  // CheckCountedByAttrOnField rejects parens the same way.
+  auto *DRE = dyn_cast<DeclRefExpr>(CountExpr);
+  if (!DRE) {
+    S.Diag(CountExpr->getBeginLoc(),
+           diag::err_count_attr_only_support_simple_decl_reference)
+        << Kind << CountExpr->getSourceRange();
+    return QualType();
+  }
+
+  // The count must name a parameter of *this* prototype. Check list membership
+  // rather than DeclContext: mid-parse every ParmVarDecl shares the enclosing
+  // context, so a DeclContext check would wrongly accept an enclosing
+  // prototype's parameter, as in
+  //   void f(int n, void (*cb)(int *__counted_by(n) p));
+  auto *CountDecl = dyn_cast<ParmVarDecl>(DRE->getDecl());
+  if (!CountDecl || !llvm::is_contained(Params, CountDecl)) {
+    S.Diag(DRE->getBeginLoc(), diag::err_count_attr_not_param_of_same_function)
+        << DRE->getDecl() << Kind << DRE->getSourceRange();
+    return QualType();
+  }
+
+  if (!CountDecl->getType()->isIntegerType() ||
+      CountDecl->getType()->isBooleanType()) {
+    S.Diag(DRE->getBeginLoc(), diag::err_count_attr_argument_not_integer)
+        << Kind << DRE->getSourceRange();
+    return QualType();
+  }
+
+  return S.BuildCountAttributedArrayOrPointerType(InnerTy, CountExpr,
+                                                  CountInBytes, OrNull);
+}
+
+namespace {
+
+/// Where a LateParsedAttrType placeholder sits inside a parameter's type.
+enum class LateAttrPosition {
+  Parameter,
+  NestedPointer,
+  ArrayElement,
+};
+
+using LateAttrTypeAndPosition =
+    std::pair<const LateParsedAttrType *, LateAttrPosition>;
+
+} // namespace
+
+/// Collect every LateParsedAttrType written in \p T, together with where it
+/// sits. A placeholder must never survive into the finalized AST, so this has
+/// to reach all of them, not just the ones in a position we can resolve.
+static void
+findLateParsedAttrTypes(ASTContext &Ctx, QualType T, LateAttrPosition Pos,
+                        SmallVectorImpl<LateAttrTypeAndPosition> &Out) {
+  // A position is only ever demoted: once a placeholder is out of the running
+  // for describing the parameter it cannot come back into it.
+  auto Demote = [](LateAttrPosition Pos, LateAttrPosition To) {
+    return Pos == LateAttrPosition::Parameter ? To : Pos;
+  };
+
+  // Each step below matches the node itself with isa/dyn_cast and desugars by
+  // a single step otherwise. Desugaring helpers that use getAs would step
+  // straight over a placeholder, which is the one thing this must not do; the
+  // getPointeeType() call is safe only because the isa<> in front of it
+  // guarantees the match is the node itself, not something behind sugar.
+  while (!T.isNull()) {
+    const Type *Ty = T.getTypePtr();
+
+    if (const auto *LPT = dyn_cast<LateParsedAttrType>(Ty)) {
+      Out.emplace_back(LPT, Pos);
+      T = LPT->desugar();
+      continue;
+    }
+
+    // The two pointer kinds a declarator chunk can produce in C. Late parsing
+    // is not enabled for C++, so references and member pointers cannot occur.
+    if (isa<PointerType, BlockPointerType>(Ty)) {
+      T = Ty->getPointeeType();
+      Pos = Demote(Pos, LateAttrPosition::NestedPointer);
+      continue;
+    }
+    if (const auto *AT = dyn_cast<AtomicType>(Ty)) {
+      // Not sugar, so the desugar fallback would stop here. No demotion: a
+      // placeholder cannot sit directly below _Atomic, since an atomic pointer
+      // is rejected when the placeholder is created.
+      T = AT->getValueType();
+      continue;
+    }
+    if (const auto *AT = dyn_cast<ArrayType>(Ty)) {
+      T = AT->getElementType();
+      Pos = Demote(Pos, LateAttrPosition::ArrayElement);
+      continue;
+    }
+    if (const auto *FT = dyn_cast<FunctionType>(Ty)) {
+      // A count written inside a function prototype names that prototype's own
+      // parameters, so the position resets: the return type and each parameter
+      // type are "own types" again, just of a different declaration.
+      // RebuildTypeWithLateParsedAttr::TransformFunctionProtoType switches the
+      // parameter list to match.
+      //
+      // Only the return type reaches this in practice: the parser resolves at
+      // every prototype's closing paren, so a nested prototype's parameters
+      // were handled by their own trigger. The loop below keeps the walk
+      // exhaustive, since a placeholder it misses escapes into the AST.
+      findLateParsedAttrTypes(Ctx, FT->getReturnType(),
+                              LateAttrPosition::Parameter, Out);
+      if (const auto *FPT = dyn_cast<FunctionProtoType>(FT))
+        for (QualType ParamTy : FPT->getParamTypes())
+          findLateParsedAttrTypes(Ctx, ParamTy, LateAttrPosition::Parameter,
+                                  Out);
+      return;
+    }
+
+    // Anything else can only hide a placeholder behind sugar.
+    QualType Desugared = T.getSingleStepDesugaredType(Ctx);
+    if (Desugared == T)
+      return;
+    T = Desugared;
+  }
+}
+
+namespace {
+
+/// Rebuilds a type, replacing each LateParsedAttrType placeholder with the
+/// concrete type its attribute denotes, parsing the cached tokens on the way.
+///
+/// Every placeholder is replaced, including the ones whose attribute turns out
+/// to be unusable: a placeholder may not reach the finalized AST, and once its
+/// tokens are parsed it no longer refers to a live attribute either.
+struct RebuildTypeWithLateParsedAttr
+    : TreeTransform<RebuildTypeWithLateParsedAttr> {
+  ParmVarDecl *PVD;
+  ArrayRef<Decl *> Params;
+  Sema::ParseLateParsedTypeAttributeCB *ParseCallback;
+  ArrayRef<LateAttrTypeAndPosition> Positions;
+
+  RebuildTypeWithLateParsedAttr(Sema &SemaRef, ParmVarDecl *PVD,
+                                ArrayRef<Decl *> Params,
+                                Sema::ParseLateParsedTypeAttributeCB *ParseCB,
+                                ArrayRef<LateAttrTypeAndPosition> Positions)
+      : TreeTransform(SemaRef), PVD(PVD), Params(Params),
+        ParseCallback(ParseCB), Positions(Positions) {}
+
+  /// findLateParsedAttrTypes walked the same type this transform is walking, 
so
+  /// every placeholder reached here was classified by it.
+  LateAttrPosition getPosition(const LateParsedAttrType *LPT) const {
+    for (LateAttrTypeAndPosition Entry : Positions)
+      if (Entry.first == LPT)
+        return Entry.second;
+    llvm_unreachable("placeholder reached by the transform was not 
classified");
+  }
+
+  // TransformFunctionProtoType is overloaded; overriding one would hide the
+  // rest. The base two-argument version dispatches to the five-argument one
+  // through getDerived().
+  using TreeTransform::TransformFunctionProtoType;
+
+  /// A count inside a nested prototype names *that* prototype's parameters, as
+  /// in `void f(int *__counted_by(len) (*cb)(int len), int len2);`. Two things
+  /// are needed and they are not alternatives: the re-entered scope makes 
`len`
+  /// findable at all (the inner prototype's scope was popped when its
+  /// declarator finished), and the parameter list decides whether what lookup
+  /// found is allowed (lookup falls through to the enclosing scopes).
+  QualType TransformFunctionProtoType(TypeLocBuilder &TLB,
+                                      FunctionProtoTypeLoc TL) {
+    SmallVector<Decl *, 4> InnerParams;
+    for (unsigned I = 0, E = TL.getNumParams(); I != E; ++I)
+      if (ParmVarDecl *PD = TL.getParam(I))
+        InnerParams.push_back(PD);
+
+    Sema::FunctionPrototypeScopeRAII ProtoScope(SemaRef, InnerParams);
+    SaveAndRestore<ArrayRef<Decl *>> SavedParams(Params, InnerParams);
+    return TreeTransform::TransformFunctionProtoType(TLB, TL);
+  }
+
+  /// A no-prototype function declares no parameters, so nothing in its return
+  /// type can name one: `void f(int *__counted_by(len) (*cb)(), int len);`.
+  /// Clearing the list is what rejects that; otherwise the list is still the
+  /// enclosing prototype's and `len` is accepted. There is no scope worth
+  /// re-entering: an empty one would not change what lookup finds.
+  QualType TransformFunctionNoProtoType(TypeLocBuilder &TLB,
+                                        FunctionNoProtoTypeLoc TL) {
+    SaveAndRestore<ArrayRef<Decl *>> SavedParams(Params, ArrayRef<Decl *>());
+    return TreeTransform::TransformFunctionNoProtoType(TLB, TL);
+  }
+
+  QualType TransformLateParsedAttrType(TypeLocBuilder &TLB,
+                                       LateParsedAttrTypeLoc TL) {
+    const LateParsedAttrType *LPT = TL.getTypePtr();
+    LateParsedTypeAttribute *LTA = LPT->getLateParsedAttribute();
+    assert(LTA && "LateParsedAttrType without a LateParsedTypeAttribute");
+
+    AttributeFactory AF;
+    ParsedAttributes Attrs(AF);
+
+    // Parse the cached tokens. The callback also destroys LTA, so from here on
+    // the placeholder refers to an attribute that no longer exists.
+    assert(ParseCallback);
+    ParseCallback(LTA, &Attrs);
+
+    QualType InnerTy = TransformType(TLB, TL.getInnerLoc());
+    if (InnerTy.isNull()) {
+      PVD->setInvalidDecl();
+      return QualType();
+    }
+
+    // An empty list means the argument failed to parse, which is already
+    // diagnosed.
+    QualType T;
+    if (!Attrs.empty()) {
+      assert(Attrs.size() == 1);
+      LateAttrPosition Pos = getPosition(LPT);
+      if (Pos == LateAttrPosition::Parameter) {
+        T = buildCountAttributedTypeForParam(SemaRef, InnerTy, Params,
+                                             Attrs[0]);
+      } else {
+        std::optional<BoundsAttrFlags> Flags =
+            getBoundsAttrFlags(Attrs[0].getKind());
+        assert(Flags && "placeholder for a non-counted_by-family attribute");
+        SemaRef.Diag(TL.getAttrNameLoc(), diag::err_count_attr_on_nested_type)
+            << getCountAttrKind(Flags->CountInBytes, Flags->OrNull)
+            << (Pos == LateAttrPosition::NestedPointer ? /*nested pointer*/ 0
+                                                       : /*array element*/ 1);
+      }
+    }
+
+    if (T.isNull()) {
+      // Drop the attribute and keep the type it wrapped. Returning nothing
+      // would leave the caller holding the original type, placeholder and all.
+      PVD->setInvalidDecl();
+      return InnerTy;
+    }
+
+    TLB.push<CountAttributedTypeLoc>(T);
+    return T;
+  }
+};
+
+} // namespace
+
+Sema::FunctionPrototypeScopeRAII::FunctionPrototypeScopeRAII(
+    Sema &S, ArrayRef<Decl *> Params)
+    : S(S), ProtoScope(S.getCurScope(),
+                       Scope::FunctionPrototypeScope | Scope::DeclScope,
+                       S.getDiagnostics()) {
+  S.CurScope = &ProtoScope;
+  for (Decl *D : Params)
+    S.ActOnReenterCXXMethodParameter(&ProtoScope,
+                                     dyn_cast_if_present<ParmVarDecl>(D));
+}
+
+Sema::FunctionPrototypeScopeRAII::~FunctionPrototypeScopeRAII() {
+  // ActOnPopScope is what takes the parameters back out of the IdResolver. It
+  // is a no-op otherwise here: a scope holding only ParmVarDecls produces no
+  // end-of-scope diagnostics.
+  S.ActOnPopScope(SourceLocation(), &ProtoScope);
+  S.CurScope = ProtoScope.getParent();
+}
+
+void Sema::ProcessLateParsedTypeAttributesForParams(
+    ArrayRef<Decl *> Params, ParseLateParsedTypeAttributeCB *ParseCB) {
+  // The parameters of a nested prototype are put back in scope by
+  // RebuildTypeWithLateParsedAttr::TransformFunctionProtoType. Do the same for
+  // the outermost prototype's own parameters rather than relying on the
+  // caller's scope, so that resolution works wherever this is called from.
+  //
+  // Created on the first parameter that needs it: the parser calls this for
+  // every prototype it parses under -fexperimental-late-parse-attributes, and
+  // almost none of them carry a late-parsed attribute.
+  std::optional<FunctionPrototypeScopeRAII> ProtoScope;
+
+  for (Decl *D : Params) {
+    auto *PVD = dyn_cast_if_present<ParmVarDecl>(D);
+    if (!PVD || !PVD->getTypeSourceInfo())
+      continue;
+
+    TypeSourceInfo *OldTSI = PVD->getTypeSourceInfo();
+    SmallVector<LateAttrTypeAndPosition, 2> Found;
+    findLateParsedAttrTypes(Context, OldTSI->getType(),
+                            LateAttrPosition::Parameter, Found);
+    if (Found.empty())
+      continue;
+
+    if (!ProtoScope)
+      ProtoScope.emplace(*this, Params);
+
+    RebuildTypeWithLateParsedAttr Rebuild(*this, PVD, Params, ParseCB, Found);
+    TypeSourceInfo *TSI = Rebuild.TransformType(OldTSI);
+    if (!TSI) {
+      PVD->setInvalidDecl();
+      continue;
+    }
+    PVD->setTypeSourceInfo(TSI);
+    // A parameter's declared type is the adjusted form of its written type.
+    PVD->setType(Context.getAdjustedParameterType(TSI->getType()));
+  }
+}
+
 } // namespace clang
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index d2bb312feadc1..420735b95076f 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -401,11 +401,21 @@ enum TypeAttrLocation {
   TAL_DeclName
 };
 
+static void fillAttrNameLocForLateParsedAttrTypeLoc(Sema &S,
+                                                    LateParsedAttrTypeLoc TL) {
+  if (auto *LateAttr = TL.getLateParsedAttribute())
+    if (S.GetLateParsedAttributeLocationCallback)
+      TL.setAttrNameLoc(S.GetLateParsedAttributeLocationCallback(LateAttr));
+}
+
 static void
 processTypeAttrs(TypeProcessingState &state, QualType &type,
                  TypeAttrLocation TAL, const ParsedAttributesView &attrs,
                  CUDAFunctionTarget CFT = CUDAFunctionTarget::HostDevice);
 
+static void processLateTypeAttrs(TypeProcessingState &state, QualType &type,
+                                 const LateParsedAttrList &LateAttrs);
+
 static bool handleFunctionTypeAttr(TypeProcessingState &state, ParsedAttr 
&attr,
                                    QualType &type, CUDAFunctionTarget CFT);
 
@@ -5488,6 +5498,10 @@ static TypeSourceInfo 
*GetFullTypeForDeclarator(TypeProcessingState &state,
     processTypeAttrs(state, T, TAL_DeclChunk, DeclType.getAttrs(),
                      S.CUDA().IdentifyTarget(D.getAttributes()));
 
+    // Wrap the type in a LateParsedAttrType placeholder for any not-yet-parsed
+    // attribute on this chunk, to be resolved once its arguments are in scope.
+    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);
@@ -6258,6 +6272,9 @@ namespace {
     void VisitCountAttributedTypeLoc(CountAttributedTypeLoc TL) {
       // nothing
     }
+    void VisitLateParsedAttrTypeLoc(LateParsedAttrTypeLoc TL) {
+      fillAttrNameLocForLateParsedAttrTypeLoc(State.getSema(), TL);
+    }
     void VisitBTFTagAttributedTypeLoc(BTFTagAttributedTypeLoc TL) {
       // nothing
     }
@@ -6425,6 +6442,13 @@ GetTypeSourceInfoForDeclarator(TypeProcessingState 
&State,
         break;
       }
 
+      case TypeLoc::LateParsedAttr: {
+        auto TL = CurrTL.castAs<LateParsedAttrTypeLoc>();
+        fillAttrNameLocForLateParsedAttrTypeLoc(S, TL);
+        CurrTL = TL.getNextTypeLoc().getUnqualifiedLoc();
+        break;
+      }
+
       case TypeLoc::Adjusted:
       case TypeLoc::BTFTagAttributed: {
         CurrTL = CurrTL.getNextTypeLoc().getUnqualifiedLoc();
@@ -9384,6 +9408,21 @@ static void processTypeAttrs(TypeProcessingState &state, 
QualType &type,
   }
 }
 
+static void processLateTypeAttrs(TypeProcessingState &state, QualType &type,
+                                 const LateParsedAttrList &LateAttrs) {
+  if (LateAttrs.empty())
+    return;
+
+  Sema &S = state.getSema();
+  assert(S.ProcessLateParsedTypeAttrCallback &&
+         "late-parsed type attribute without a parser callback");
+
+  // Every attribute has to be offered, even after one is rejected: the
+  // callback is what hands each one to a placeholder or destroys it.
+  for (auto *LA : LateAttrs)
+    S.ProcessLateParsedTypeAttrCallback(LA, type);
+}
+
 void Sema::completeExprArrayBound(Expr *E) {
   if (DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(E->IgnoreParens())) {
     if (VarDecl *Var = dyn_cast<VarDecl>(DRE->getDecl())) {

>From 20b2670d11276bee74d2cdb3de80616d9f2976f7 Mon Sep 17 00:00:00 2001
From: Holo-xy <[email protected]>
Date: Fri, 31 Jul 2026 19:21:32 +0300
Subject: [PATCH 6/6] [Clang] Add tests for counted_by on function parameters

Cover both orderings of the annotated pointer and its count, counts written
in nested function-pointer prototypes, the pointee-kind and referent rules
shared with the struct-field path, and the deferred incomplete-pointee
checks. The AST test pins that both orderings converge on the same
CountAttributedType.

Co-Authored-By: Claude Opus 5 <[email protected]>
---
 .../AST/attr-counted-by-function-params.c     |  40 +++
 .../Sema/attr-counted-by-function-params.c    | 329 ++++++++++++++++++
 clang/test/Sema/warn-thread-safety-analysis.c |   5 +
 3 files changed, 374 insertions(+)
 create mode 100644 clang/test/AST/attr-counted-by-function-params.c
 create mode 100644 clang/test/Sema/attr-counted-by-function-params.c

diff --git a/clang/test/AST/attr-counted-by-function-params.c 
b/clang/test/AST/attr-counted-by-function-params.c
new file mode 100644
index 0000000000000..20fe3473f526c
--- /dev/null
+++ b/clang/test/AST/attr-counted-by-function-params.c
@@ -0,0 +1,40 @@
+// RUN: %clang_cc1 -fexperimental-late-parse-attributes %s -ast-dump | 
FileCheck %s
+
+#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)))
+
+// The count parameter is declared first. The attribute is still late-parsed;
+// that is decided by the attribute and the language option, not by whether the
+// name is already in scope. This pins that both orderings end up with the same
+// CountAttributedType.
+void back_ref(int count, int *__counted_by(count) buf);
+// CHECK-LABEL: FunctionDecl {{.*}} back_ref 'void (int, int * 
__counted_by(count))'
+// CHECK-NEXT: | |-ParmVarDecl {{.*}} used count 'int'
+// CHECK-NEXT: | `-ParmVarDecl {{.*}} buf 'int * __counted_by(count)':'int *'
+
+// The count parameter is declared after the annotated pointer, so the
+// attribute argument can only be parsed once the whole prototype is known.
+void fwd_ref(int *__counted_by(count) buf, int count);
+// CHECK-LABEL: FunctionDecl {{.*}} fwd_ref 'void (int * __counted_by(count), 
int)'
+// CHECK-NEXT: | |-ParmVarDecl {{.*}} buf 'int * __counted_by(count)':'int *'
+// CHECK-NEXT: | `-ParmVarDecl {{.*}} used count 'int'
+
+void fwd_ref_or_null(int *__counted_by_or_null(count) buf, int count);
+// CHECK-LABEL: FunctionDecl {{.*}} fwd_ref_or_null 'void (int * 
__counted_by_or_null(count), int)'
+
+void fwd_ref_sized(void *__sized_by(count) buf, int count);
+// CHECK-LABEL: FunctionDecl {{.*}} fwd_ref_sized 'void (void * 
__sized_by(count), int)'
+
+// Two parameters referring to the same count.
+void two_buffers(int *__counted_by(count) a, int *__counted_by(count) b,
+                 int count);
+// CHECK-LABEL: FunctionDecl {{.*}} two_buffers 'void (int * 
__counted_by(count), int * __counted_by(count), int)'
+
+// The count expression resolves to the ParmVarDecl of this function, not to
+// the global of the same name.
+int count;
+void shadowed(int *__counted_by(count) buf, int count);
+// CHECK-LABEL: FunctionDecl {{.*}} shadowed 'void (int * __counted_by(count), 
int)'
+// CHECK-NEXT: |-ParmVarDecl {{.*}} buf 'int * __counted_by(count)':'int *'
+// CHECK-NEXT: `-ParmVarDecl {{.*}} used count 'int'
diff --git a/clang/test/Sema/attr-counted-by-function-params.c 
b/clang/test/Sema/attr-counted-by-function-params.c
new file mode 100644
index 0000000000000..8e7d95fa4f9ae
--- /dev/null
+++ b/clang/test/Sema/attr-counted-by-function-params.c
@@ -0,0 +1,329 @@
+// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fblocks 
-Wpointer-arith -fsyntax-only -verify %s
+
+#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 size_unknown;
+struct size_known {
+  int field;
+};
+
+//==============================================================================
+// Valid: the count parameter is declared before the annotated pointer
+//==============================================================================
+
+void back_ref(int count, int *__counted_by(count) buf);
+void back_ref_or_null(int count, int *__counted_by_or_null(count) buf);
+void back_ref_sized(int count, void *__sized_by(count) buf);
+void back_ref_sized_or_null(int count, void *__sized_by_or_null(count) buf);
+
+//==============================================================================
+// Valid: the count parameter is declared *after* the annotated pointer
+//==============================================================================
+
+// This is the case that requires late parsing: at the point the attribute is
+// written, `count` is not yet in scope.
+
+void fwd_ref(int *__counted_by(count) buf, int count);
+void fwd_ref_or_null(int *__counted_by_or_null(count) buf, int count);
+void fwd_ref_sized(void *__sized_by(count) buf, int count);
+
+// The annotated pointer and the count may be separated by other parameters.
+void fwd_ref_interleaved(int *__counted_by(count) buf, int other, int count);
+
+// Several parameters may refer to the same count.
+void two_buffers(int *__counted_by(count) a, int *__counted_by(count) b,
+                 int count);
+
+// A pointer may be annotated with a count declared before it and another
+// pointer in the same prototype with a count declared after it.
+void mixed(int first, int *__counted_by(first) a, int *__counted_by(second) b,
+           int second);
+
+// Incomplete pointee types are allowed, as for struct fields.
+void incomplete_pointee(struct size_unknown *__counted_by(count) buf,
+                        int count);
+void const_pointee(const struct size_known *__counted_by(count) buf, int 
count);
+
+// The attribute binds to the pointer formed by the `*` immediately preceding
+// it, so here it applies to the parameter itself, which is a pointer.
+void outer_of_two(int **__counted_by(count) buf, int count);
+
+// Qualifiers on the annotated pointer itself do not disturb the attribute.
+void qualified_ptr(int *__counted_by(count) const buf, int count);
+
+// A definition, not just a prototype.
+int sum(int *__counted_by(count) buf, int count) {
+  int total = 0;
+  for (int i = 0; i < count; ++i)
+    total += buf[i];
+  return total;
+}
+
+//==============================================================================
+// Invalid: the argument does not name a parameter of this function
+//==============================================================================
+
+int global_count;
+
+// expected-error@+1{{'counted_by' argument 'global_count' is not a parameter 
of the same function as the annotated pointer}}
+void not_a_param(int *__counted_by(global_count) buf);
+
+// expected-error@+1{{use of undeclared identifier 'nope'}}
+void undeclared(int *__counted_by(nope) buf);
+
+// A nested function-pointer parameter has its own parameter list. Its count
+// must come from that list, not from the enclosing prototype, even though the
+// enclosing prototype's parameters are in scope here.
+// expected-error@+1{{'counted_by' argument 'n' is not a parameter of the same 
function as the annotated pointer}}
+void outer_param_leak(int n, void (*cb)(int *__counted_by(n) p));
+
+// The same shape, but referring to the inner prototype's own parameter, is OK.
+void inner_param_ok(void (*cb)(int *__counted_by(m) p, int m));
+
+// A global declared *after* the prototype is not visible either: the tokens 
are
+// parsed at the end of the parameter clause, not at the end of the translation
+// unit, so this fails in lookup rather than at the parameter check above.
+// expected-error@+1{{use of undeclared identifier 'later_global'}}
+void global_declared_later(int *__counted_by(later_global) buf);
+int later_global;
+
+//==============================================================================
+// Invalid: the argument must be a simple declaration reference
+//==============================================================================
+
+// expected-error@+1{{'counted_by' argument must be a simple declaration 
reference}}
+void not_simple_ref(int *__counted_by(count + 1) buf, int count);
+
+// expected-error@+1{{'counted_by' argument must be a simple declaration 
reference}}
+void not_simple_ref_deref(int *__counted_by(*count) buf, int *count);
+
+// A parenthesized reference is rejected too, matching the struct-field path:
+// the count expression itself must be the declaration reference.
+// expected-error@+1{{'counted_by' argument must be a simple declaration 
reference}}
+void not_simple_ref_paren(int *__counted_by((count)) buf, int count);
+
+// A constant count is meaningful under -fbounds-safety but not supported
+// upstream, where the argument must name a parameter.
+// expected-error@+1{{'counted_by' argument must be a simple declaration 
reference}}
+void count_is_literal(int *__counted_by(4) buf);
+
+//==============================================================================
+// Invalid: the count parameter must have a non-boolean integer type
+//==============================================================================
+
+// expected-error@+1{{'counted_by' requires a non-boolean integer type 
argument}}
+void count_is_ptr(int *__counted_by(count) buf, int *count);
+
+// expected-error@+1{{'counted_by' requires a non-boolean integer type 
argument}}
+void count_is_float(int *__counted_by(count) buf, float count);
+
+// expected-error@+1{{'counted_by' requires a non-boolean integer type 
argument}}
+void count_is_bool(int *__counted_by(count) buf, _Bool count);
+
+//==============================================================================
+// Attributes in the declaration-specifiers are not part of this mechanism
+//==============================================================================
+
+// They are still parsed eagerly and handled as declaration attributes. Both
+// diagnostics below are pre-existing behavior, recorded here so a future 
change
+// to the decl-attribute path is noticed.
+
+// Parsed eagerly, so `count` is not yet in scope.
+// expected-error@+1{{use of undeclared identifier 'count'}}
+void on_int(int __counted_by(count) buf, int count);
+
+// With `count` already in scope, it reaches the declaration-attribute path,
+// whose subject list only permits struct fields.
+// expected-error@+1{{'counted_by' attribute only applies to non-static data 
members}}
+void on_int_count_first(int count, int __counted_by(count) buf);
+
+//==============================================================================
+// Invalid: nested pointers are not supported for parameters
+//==============================================================================
+
+// Bounds on a pointer that is not the parameter itself are neither checked nor
+// updated by anything, so reject rather than silently accept.
+
+// expected-error@+1{{'counted_by' is not supported on a nested pointer; it 
must apply to the annotated declaration's own type}}
+void nested(int *__counted_by(count) *buf, int count);
+
+// expected-error@+1{{'sized_by' is not supported on a nested pointer; it must 
apply to the annotated declaration's own type}}
+void nested_sized(void *__sized_by(count) *buf, int count);
+
+// expected-error@+1{{'counted_by_or_null' is not supported on a nested 
pointer; it must apply to the annotated declaration's own type}}
+void nested_or_null(int *__counted_by_or_null(count) *buf, int count);
+
+// An _Atomic qualifier on the outer pointer becomes a real AtomicType node
+// above the placeholder, not sugar; the placeholder walk must step through it
+// or the attribute would escape resolution entirely.
+// expected-error@+1{{'counted_by' is not supported on a nested pointer; it 
must apply to the annotated declaration's own type}}
+void nested_atomic(int *__counted_by(count) * _Atomic buf, int count);
+
+//==============================================================================
+// Valid: the count names a nested function-pointer prototype's own parameters
+//==============================================================================
+
+// A count written inside a function-pointer parameter's own prototype names
+// that prototype's parameters rather than this function's. Both the parameters
+// and the return type of the inner prototype may be annotated this way.
+
+void inner_return(int *__counted_by(len) (*cb)(int len), int other);
+void inner_return_sized(void *__sized_by(len) (*cb)(int len));
+void inner_param_and_return(int *__counted_by(len) (*cb)(int 
*__counted_by(len) p,
+                                                         int len));
+
+// A parameter spelled as a function rather than a function pointer behaves the
+// same, since it adjusts to a function pointer.
+void inner_return_fn(int *__counted_by(len) cb(int len));
+
+// The inner prototype's parameter shadows an outer name of the same name, and
+// the inner one wins.
+void inner_shadows_outer(int len, int *__counted_by(len) (*cb)(int len));
+
+// A parameter shadowing a *global* of the same name binds to the parameter. 
The
+// parameter list alone could not produce this: it can reject what lookup 
found,
+// but only a scope can make lookup prefer the parameter over the file-scope
+// declaration. The second one needs that scope to be *re-entered*, since the
+// inner prototype's own scope is long gone by the time its count is parsed.
+// Accepting these is what proves the count bound to the parameter; had it
+// bound to `global_count`, the parameter check would have rejected it, exactly
+// as it does for `not_a_param` above.
+void shadows_global(int *__counted_by(global_count) buf, int global_count);
+void inner_shadows_global(void (*cb)(int *__counted_by(global_count) p,
+                                     int global_count));
+
+// ... and without a parameter to shadow it, the same spelling in a nested
+// prototype still reaches the global and is still rejected.
+// expected-error@+1{{'counted_by' argument 'global_count' is not a parameter 
of the same function as the annotated pointer}}
+void inner_reaches_global(void (*cb)(int *__counted_by(global_count) p));
+
+//==============================================================================
+// Invalid: a count in the return type of a *no-prototype* inner function
+//==============================================================================
+
+// `()` is a FunctionNoProtoType in C, not a zero-parameter FunctionProtoType,
+// so it declares no parameters at all and no count written in its return type
+// can name one.
+//
+// The enclosing prototype's parameters are in scope here, so lookup does find
+// them; what makes these ill-formed is that they do not belong to the inner
+// function, whose callers cannot see them.
+
+// expected-error@+1{{'counted_by' argument 'len' is not a parameter of the 
same function as the annotated pointer}}
+void noproto_reaches_outer_param(int *__counted_by(len) (*cb)(), int len);
+
+// expected-error@+1{{'sized_by' argument 'len' is not a parameter of the same 
function as the annotated pointer}}
+void noproto_reaches_outer_param_sized(void *__sized_by(len) (*cb)(), int len);
+
+// The same shape spelled as a function rather than a function pointer.
+// expected-error@+1{{'counted_by' argument 'len' is not a parameter of the 
same function as the annotated pointer}}
+void noproto_fn_reaches_outer_param(int *__counted_by(len) cb(), int len);
+
+// A no-prototype inner function reaching a global is rejected too, though that
+// falls out of the count not naming a parameter at all rather than of the
+// no-prototype handling.
+// expected-error@+1{{'counted_by' argument 'global_count' is not a parameter 
of the same function as the annotated pointer}}
+void noproto_reaches_global(int *__counted_by(global_count) (*cb)());
+
+// `(void)` *is* a zero-parameter FunctionProtoType, so it takes the prototype
+// path. Kept alongside the cases above so a regression that conflates the two
+// spellings shows up as a diagnostic appearing on only one of them.
+// expected-error@+1{{'counted_by' argument 'len' is not a parameter of the 
same function as the annotated pointer}}
+void protovoid_reaches_outer_param(int *__counted_by(len) (*cb)(void), int 
len);
+
+//==============================================================================
+// Invalid: the attribute is not on any declaration's own type
+//==============================================================================
+
+// These positions have to be rejected rather than ignored: the placeholder the
+// parser leaves behind must never reach the finalized AST, which asserts on it
+// when the AST is serialized.
+
+// The attribute applies to the array's element type, not to the parameter.
+// expected-error@+1{{'counted_by' is not supported on an array element; it 
must apply to the annotated declaration's own type}}
+void on_array_element(int *__counted_by(count) buf[10], int count);
+
+// A function-pointer parameter's return type may only name that prototype's 
own
+// parameters, and this prototype has none.
+// expected-error@+1{{'counted_by' argument 'count' is not a parameter of the 
same function as the annotated pointer}}
+void on_fn_ptr_return(int *__counted_by(count) (*cb)(void), int count);
+
+// The same, spelled as a function parameter rather than a function pointer.
+// expected-error@+1{{'counted_by' argument 'count' is not a parameter of the 
same function as the annotated pointer}}
+void on_fn_return(int *__counted_by(count) cb(void), int count);
+
+// A prototype that is already invalid must not leave a placeholder behind for
+// the enclosing prototype to trip over.
+// expected-error@+1{{'counted_by' argument 'n' is not a parameter of the same 
function as the annotated pointer}}
+void outer_then_nested(int n, void (*cb)(int *__counted_by(n) p));
+// expected-error@+1{{'counted_by' is not supported on a nested pointer; it 
must apply to the annotated declaration's own type}}
+void after_invalid_nested(int *__counted_by(count) *buf, int count);
+
+//==============================================================================
+// Invalid: the annotated type is not a pointer at all.
+//==============================================================================
+
+// A block pointer is written with the same declarator syntax as a pointer, and
+// so collects late-parsed attributes the same way, but it is not a pointer
+// type. Rejecting it has to happen on the late-parsed path too: if the
+// attribute were simply dropped there, turning the flag on would make clang
+// accept what it otherwise diagnoses.
+// expected-error@+1{{'counted_by' only applies to pointers or C99 flexible 
array members}}
+void on_block_pointer(int (^__counted_by(count) blk)(void), int count);
+
+// An _Atomic pointer is an AtomicType, not a pointer type, so no placeholder
+// is created for it in the first place.
+// expected-error@+1{{'counted_by' only applies to pointers or C99 flexible 
array members}}
+void on_atomic_pointer(int *__counted_by(count) _Atomic p, int count);
+
+//==============================================================================
+// Invalid: the pointee type cannot support bounds computation
+//==============================================================================
+
+// Parameters share the struct-field path's pointee rules, so both accept and
+// reject the same shapes.
+
+struct fam { int count; int elems[]; };
+
+// expected-error@+1{{'counted_by' cannot be applied to a pointer with pointee 
of unknown size because 'void (int)' is a function type}}
+void fn_pointee(void (*__counted_by(count) fp)(int), int count);
+
+// expected-error@+1{{'sized_by' cannot be applied to a pointer with pointee 
of unknown size because 'void (int)' is a function type}}
+void fn_pointee_sized(void (*__sized_by(count) fp)(int), int count);
+
+// expected-error@+1{{'counted_by' cannot be applied to a pointer with pointee 
of unknown size because 'struct fam' is a struct type with a flexible array 
member}}
+void fam_pointee(struct fam *__counted_by(count) p, int count);
+
+// counted_by on a pointer to void counts bytes, like sized_by; a GNU
+// extension, warned on exactly as for a struct field.
+// expected-warning@+2{{'counted_by' on a pointer to void is a GNU extension, 
treated as 'sized_by'}}
+// expected-note@+1{{use '__sized_by' to suppress this warning}}
+void void_pointee(void *__counted_by(count) p, int count);
+
+//==============================================================================
+// The deferred incomplete-pointee model applies to parameters as to fields
+//==============================================================================
+
+// The attribute is accepted while the pointee may still be completed later, 
and
+// each use or assignment then requires the complete type.
+
+// expected-note@+1 2{{consider providing a complete definition for 'struct 
deferred_pointee'}}
+struct deferred_pointee;
+
+void take_deferred(struct deferred_pointee *q);
+
+// expected-note@+1{{consider using '__sized_by' instead of '__counted_by'}}
+void deferred_use(struct deferred_pointee *__counted_by(count) p, int count) {
+  // expected-error@+1{{cannot use 'p' with '__counted_by' attributed type 
'struct deferred_pointee * __counted_by(count)' (aka 'struct deferred_pointee 
*') because the pointee type 'struct deferred_pointee' is incomplete}}
+  take_deferred(p);
+}
+
+// expected-note@+1{{consider using '__sized_by' instead of '__counted_by'}}
+void deferred_assign(struct deferred_pointee *__counted_by(count) p, int count,
+                     struct deferred_pointee *q) {
+  // expected-error@+1{{cannot assign to 'p' with '__counted_by' attributed 
type 'struct deferred_pointee * __counted_by(count)' (aka 'struct 
deferred_pointee *') because the pointee type 'struct deferred_pointee' is 
incomplete}}
+  p = q;
+}
diff --git a/clang/test/Sema/warn-thread-safety-analysis.c 
b/clang/test/Sema/warn-thread-safety-analysis.c
index a0e9e7ce724cc..e980c3049abbd 100644
--- a/clang/test/Sema/warn-thread-safety-analysis.c
+++ b/clang/test/Sema/warn-thread-safety-analysis.c
@@ -343,6 +343,11 @@ void test_bdev_ops_fail(struct BDevOps *ops, struct BDev 
*bdev) {
 // attribute in a single __attribute__.
 void run(void) __attribute__((guarded_by(mu1), guarded_by(mu1))); // 
expected-warning 2{{only applies to non-static data members and global 
variables}}
 
+// Under late parsing, a guarded_by written in the type-qualifier position of
+// a function parameter used to be captured by the type-attr-only late list
+// and silently dropped, losing the warning below.
+void misplaced_in_param(int *__attribute__((guarded_by(mu1))) p); // 
expected-warning {{only applies to non-static data members and global 
variables}}
+
 int value_with_multiple_guarded_args GUARDED_BY(mu1, mu2);
 
 int *ptr_with_multiple_guarded_args PT_GUARDED_BY(mu1, mu2);

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to