[clang] [BoundsSafety][NFC] Introduce logic to replace the LateParsedAttrType placeholder with CountAttributedType (PR #212906)

via cfe-commits cfe-commits at lists.llvm.org
Fri Jul 31 18:12:11 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang

Author: Yeoul Na (rapidsna)

<details>
<summary>Changes</summary>

Split from #<!-- -->179612 alongside #<!-- -->204125 (LateParsedAttrType AST node).

Adds the resolver logic that walks a `FieldDecl`'s type, finds any `LateParsedAttrType` placeholder inside it, parses the cached attribute tokens, and replaces the placeholder with a concrete `CountAttributedType`. Purely NFC: nothing on this commit creates a `LateParsedAttrType` placeholder in the AST, so the resolver never fires — the producer (parser dispatch + `ActOnLateParsedTypeAttr` wire-up) lands in follow-up commits.

Sold as an NFC to keep the review of the mechanism halved.

Follow-up PR will add the end-to-end wire-up that populates `LateParsedAttrType` placeholders so this resolver can start firing.

---
Full diff: https://github.com/llvm/llvm-project/pull/212906.diff


5 Files Affected:

- (modified) clang/include/clang/AST/DependenceFlags.h (+7-2) 
- (modified) clang/include/clang/AST/TypeBase.h (+9-1) 
- (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+3) 
- (modified) clang/include/clang/Sema/Sema.h (+40) 
- (modified) clang/lib/Sema/SemaDecl.cpp (+201-1) 


``````````diff
diff --git a/clang/include/clang/AST/DependenceFlags.h b/clang/include/clang/AST/DependenceFlags.h
index c4395259f0758..0a5dd53f7696a 100644
--- a/clang/include/clang/AST/DependenceFlags.h
+++ b/clang/include/clang/AST/DependenceFlags.h
@@ -69,12 +69,17 @@ struct TypeDependenceScope {
     /// yields an error type.
     Error = 16,
 
+    /// Whether this type contains a placeholder for a type attribute
+    /// awaiting late parsing, e.g. `int *__counted_by(count)` where
+    /// `count` is a later field of the same struct.
+    LateParsedAttr = 32,
+
     None = 0,
-    All = 31,
+    All = 63,
 
     DependentInstantiation = Dependent | Instantiation,
 
-    LLVM_MARK_AS_BITMASK_ENUM(/*LargestValue=*/Error)
+    LLVM_MARK_AS_BITMASK_ENUM(/*LargestValue=*/LateParsedAttr)
   };
 };
 using TypeDependence = TypeDependenceScope::TypeDependence;
diff --git a/clang/include/clang/AST/TypeBase.h b/clang/include/clang/AST/TypeBase.h
index 530bfe72dac2b..1dbcadbfde574 100644
--- a/clang/include/clang/AST/TypeBase.h
+++ b/clang/include/clang/AST/TypeBase.h
@@ -2866,6 +2866,13 @@ class alignas(TypeAlignment) Type : public ExtQualsTypeCommonBase {
     return getDependence() & TypeDependence::VariablyModified;
   }
 
+  /// Whether this type contains a placeholder for a type attribute awaiting
+  /// late parsing, e.g. `int *__counted_by(count)` where `count` is a later
+  /// field of the same struct.
+  bool hasLateParsedAttr() const {
+    return getDependence() & TypeDependence::LateParsedAttr;
+  }
+
   /// Whether this type involves a variable-length array type
   /// with a definite size.
   bool hasSizedVLAType() const;
@@ -3565,7 +3572,8 @@ class LateParsedAttrType : public Type {
 
   LateParsedAttrType(QualType Wrapped, QualType Canon,
                      LateParsedTypeAttribute *Attr)
-      : Type(LateParsedAttr, Canon, Wrapped->getDependence()),
+      : Type(LateParsedAttr, Canon,
+             TypeDependence::LateParsedAttr | Wrapped->getDependence()),
         WrappedTy(Wrapped), LateParsedTypeAttr(Attr) {}
 
 public:
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 01527e87c903f..db118c699ce02 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -7245,6 +7245,9 @@ def err_builtin_counted_by_ref_invalid_use : Error<
   "value returned by '__builtin_counted_by_ref' cannot be used in "
   "%select{an array subscript|a binary}0 expression">;
 
+def err_counted_by_on_nested_pointer : Error<
+  "'%select{counted_by|sized_by|counted_by_or_null|sized_by_or_null}0' attribute on nested pointer type is not allowed">;
+
 let CategoryName = "ARC Semantic Issue" in {
 
 // ARC-mode diagnostics.
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 778c1a2f5c427..d8081e0095d09 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -142,6 +142,8 @@ class InitializationKind;
 class InitializationSequence;
 class InitializedEntity;
 enum class LangAS : unsigned int;
+struct LateParsedAttribute;
+struct LateParsedTypeAttribute;
 class LocalInstantiationScope;
 class LookupResult;
 class MangleNumberingContext;
@@ -1360,6 +1362,11 @@ class Sema final : public SemaBase {
     OpaqueParser = P;
   }
 
+  /// Callback type to parse and consume a LateParsedTypeAttribute. Used as an
+  /// argument to ProcessLateParsedTypeAttributes.
+  typedef void ParseLateParsedTypeAttributeCB(LateParsedTypeAttribute *LTA,
+                                              ParsedAttributes *OutAttrs);
+
   /// Callback to the parser to parse a type expressed as a string.
   std::function<TypeResult(StringRef, StringRef, SourceLocation)>
       ParseTypeFromStringCallback;
@@ -4428,6 +4435,39 @@ class Sema final : public SemaBase {
                    ArrayRef<Decl *> Fields, SourceLocation LBrac,
                    SourceLocation RBrac, const ParsedAttributesView &AttrList);
 
+  /// Resolve placeholders left in field types by late-parsed type
+  /// attributes, now that \p EnclosingDecl's fields are all visible and
+  /// name lookup for the attribute's argument can succeed.
+  ///
+  /// Called for each record whose scope makes a previously-deferred
+  /// argument resolvable. That is usually the same record that carries
+  /// the annotated field, e.g.
+  /// \code
+  ///   struct Flat {
+  ///     char *__counted_by(len) p;
+  ///     unsigned len;   // resolved when Flat finishes parsing
+  ///   };
+  /// \endcode
+  /// but the resolving record can also be an inner or outer record when
+  /// the count and the annotated field are separated by nested records:
+  /// \code
+  ///   struct NestedNamed {
+  ///     struct {
+  ///       char *__counted_by(len) p;
+  ///       unsigned len;   // resolved when the nested struct finishes,
+  ///     } inner;          //   since name lookup is closed at that scope
+  ///   };
+  ///
+  ///   struct NestedAnon {
+  ///     struct {
+  ///       char *__counted_by(len) p;   // len isn't in scope until
+  ///     };                              //   the anonymous struct is
+  ///     unsigned len;                   //   flattened into NestedAnon,
+  ///   };                                //   so resolved when it finishes
+  /// \endcode
+  void ProcessLateParsedTypeAttributes(RecordDecl *EnclosingDecl,
+                                       ParseLateParsedTypeAttributeCB *ParseCB);
+
   /// ActOnTagStartDefinition - Invoked when we have entered the
   /// scope of a tag's definition (e.g., for an enumeration, class,
   /// struct, or union).
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index a553c6994f0ed..67ce1f1631cdd 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -10,6 +10,7 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include "TreeTransform.h"
 #include "TypeLocBuilder.h"
 #include "clang/AST/ASTConsumer.h"
 #include "clang/AST/ASTContext.h"
@@ -17240,7 +17241,7 @@ void Sema::ActOnFinishDelayedAttribute(Scope *S, Decl *D,
   // Always attach attributes to the underlying decl.
   if (TemplateDecl *TD = dyn_cast<TemplateDecl>(D))
     D = TD->getTemplatedDecl();
-  ProcessDeclAttributeList(S, D, Attrs);
+  ProcessDeclAttributeList(S, D, Attrs, ProcessDeclAttributeOptions());
   ProcessAPINotes(D);
 
   if (CXXMethodDecl *Method = dyn_cast_or_null<CXXMethodDecl>(D))
@@ -19983,6 +19984,205 @@ bool Sema::EntirelyFunctionPointers(const RecordDecl *Record) {
   return llvm::all_of(Record->decls(), IsFunctionPointerOrForwardDecl);
 }
 
+static QualType handleCountedByAttrField(Sema &S, QualType T, Decl *D,
+                                         const ParsedAttr &AL) {
+  if (!AL.diagnoseLangOpts(S))
+    return QualType();
+
+  assert(isa<FieldDecl>(D));
+
+  auto *CountExpr = AL.getArgAsExpr(0);
+  if (!CountExpr)
+    return QualType();
+
+  bool CountInBytes;
+  bool OrNull;
+  switch (AL.getKind()) {
+  case ParsedAttr::AT_CountedBy:
+    CountInBytes = false;
+    OrNull = false;
+    break;
+  case ParsedAttr::AT_CountedByOrNull:
+    CountInBytes = false;
+    OrNull = true;
+    break;
+  case ParsedAttr::AT_SizedBy:
+    CountInBytes = true;
+    OrNull = false;
+    break;
+  case ParsedAttr::AT_SizedByOrNull:
+    CountInBytes = true;
+    OrNull = true;
+    break;
+  default:
+    llvm_unreachable("unexpected counted_by family attribute");
+  }
+
+  return S.BuildCountAttributedArrayOrPointerType(T, CountExpr, CountInBytes,
+                                                  OrNull);
+}
+struct RebuildTypeWithLateParsedAttr
+    : TreeTransform<RebuildTypeWithLateParsedAttr> {
+  FieldDecl *FD;
+  Sema::ParseLateParsedTypeAttributeCB *ParseCallback;
+
+  /// Non-zero while transforming under a pointer or array. Used to reject
+  /// nested counted_by (e.g. `int *__counted_by(n) *p` or
+  /// `int *__counted_by(n) arr[10]`), where the resolved CountAttributedType
+  /// would sit inside another wrapper.
+  unsigned PointerOrArrayDepth = 0;
+
+  RebuildTypeWithLateParsedAttr(Sema &SemaRef, FieldDecl *FD,
+                                Sema::ParseLateParsedTypeAttributeCB *ParseCB)
+      : TreeTransform(SemaRef), FD(FD), ParseCallback(ParseCB) {}
+
+  QualType TransformLateParsedAttrType(TypeLocBuilder &TLB,
+                                       LateParsedAttrTypeLoc TL) {
+    const LateParsedAttrType *LPA = TL.getTypePtr();
+    auto *LTA = LPA->getLateParsedAttribute();
+
+    assert(LTA && "LateParsedAttrType must have a LateParsedTypeAttribute");
+
+    AttributeFactory AF{};
+    ParsedAttributes Attrs(AF);
+
+    // Invoke the parser callback to parse and consume the cached tokens.
+    // ParseCallback is also responsible for deleting LTA.
+    assert(ParseCallback);
+    ParseCallback(LTA, &Attrs);
+
+    // Invalid argument
+    if (Attrs.empty())
+      return QualType();
+
+    assert(Attrs.size() == 1);
+    auto &AL = Attrs[0];
+
+    QualType InnerType = TransformType(TLB, TL.getInnerLoc());
+    if (InnerType.isNull()) {
+      FD->setInvalidDecl();
+      return QualType();
+    }
+
+    QualType T = handleCountedByAttrField(SemaRef, InnerType, FD, AL);
+    if (T.isNull()) {
+      AL.setInvalid();
+      FD->setInvalidDecl();
+      return QualType();
+    }
+
+    // Reject nested counted_by, e.g. `int *__counted_by(n) *p` or
+    // `int *__counted_by(n) arr[10]` — the resolved CountAttributedType
+    // must sit at the outermost level of the field's type.
+    //
+    // FIXME: In the -fbounds-safety model:
+    //   - `counted_by` / `sized_by` on nested pointers is permitted only
+    //     for function parameters, and only one level of nesting.
+    //   - `counted_by_or_null` / `sized_by_or_null` on nested pointers is
+    //     generally permitted, because it makes sense to allow such a
+    //     pointer to be valid when the top-level pointer is null (the
+    //     inner counted_by / sized_by simply doesn't need to hold in
+    //     that case).
+    // We reject unconditionally here because this slice only covers
+    // struct fields; extending coverage will need to relax this check
+    // along those lines.
+    if (PointerOrArrayDepth > 0) {
+      SemaRef.Diag(TL.getAttrNameLoc(), diag::err_counted_by_on_nested_pointer)
+          << T->getAs<CountAttributedType>()->getKind();
+      FD->setInvalidDecl();
+      return QualType();
+    }
+
+    AL.setUsedAsTypeAttr();
+
+    TLB.push<CountAttributedTypeLoc>(T);
+    return T;
+  }
+
+  // The five overrides below only track pointer/array depth so
+  // TransformLateParsedAttrType can reject nested counted_by. Recursion,
+  // rebuild, and TypeLoc pushing are delegated to the base TreeTransform.
+
+  QualType TransformPointerType(TypeLocBuilder &TLB, PointerTypeLoc TL) {
+    ++PointerOrArrayDepth;
+    QualType Result = TreeTransform::TransformPointerType(TLB, TL);
+    --PointerOrArrayDepth;
+    if (Result.isNull())
+      FD->setInvalidDecl();
+    return Result;
+  }
+
+  QualType TransformConstantArrayType(TypeLocBuilder &TLB,
+                                      ConstantArrayTypeLoc TL) {
+    ++PointerOrArrayDepth;
+    QualType Result = TreeTransform::TransformConstantArrayType(TLB, TL);
+    --PointerOrArrayDepth;
+    if (Result.isNull())
+      FD->setInvalidDecl();
+    return Result;
+  }
+
+  QualType TransformIncompleteArrayType(TypeLocBuilder &TLB,
+                                        IncompleteArrayTypeLoc TL) {
+    ++PointerOrArrayDepth;
+    QualType Result = TreeTransform::TransformIncompleteArrayType(TLB, TL);
+    --PointerOrArrayDepth;
+    if (Result.isNull())
+      FD->setInvalidDecl();
+    return Result;
+  }
+
+  QualType TransformVariableArrayType(TypeLocBuilder &TLB,
+                                      VariableArrayTypeLoc TL) {
+    ++PointerOrArrayDepth;
+    QualType Result = TreeTransform::TransformVariableArrayType(TLB, TL);
+    --PointerOrArrayDepth;
+    if (Result.isNull())
+      FD->setInvalidDecl();
+    return Result;
+  }
+
+  QualType TransformDependentSizedArrayType(TypeLocBuilder &TLB,
+                                            DependentSizedArrayTypeLoc TL) {
+    ++PointerOrArrayDepth;
+    QualType Result = TreeTransform::TransformDependentSizedArrayType(TLB, TL);
+    --PointerOrArrayDepth;
+    if (Result.isNull())
+      FD->setInvalidDecl();
+    return Result;
+  }
+};
+
+void Sema::ProcessLateParsedTypeAttributes(
+    RecordDecl *EnclosingDecl, ParseLateParsedTypeAttributeCB *ParseCB) {
+  for (auto *I : EnclosingDecl->decls()) {
+    FieldDecl *FD = dyn_cast<FieldDecl>(I);
+    IndirectFieldDecl *IFD = dyn_cast<IndirectFieldDecl>(I);
+    if (!FD && IFD) {
+      FD = IFD->getAnonField();
+    }
+    if (!FD || !FD->getType()->hasLateParsedAttr() ||
+        FD->getType()->isRecordType())
+      continue;
+
+    RebuildTypeWithLateParsedAttr RebuildFieldType(*this, FD, ParseCB);
+    auto *OldTSI = FD->getTypeSourceInfo();
+    auto *TSI = RebuildFieldType.TransformType(FD->getTypeSourceInfo());
+    if (TSI && TSI != OldTSI) {
+      FD->setTypeSourceInfo(TSI);
+      FD->setType(TSI->getType());
+      if (IFD) {
+        IFD->setType(TSI->getType());
+      }
+    }
+
+    if (auto *CAT = FD->getType()->getAs<CountAttributedType>()) {
+      CheckCountedByAttrOnField(FD, CAT->getCountExpr(), CAT->isCountInBytes(),
+                                CAT->isOrNull());
+    }
+  }
+}
+
 void Sema::ActOnFields(Scope *S, SourceLocation RecLoc, Decl *EnclosingDecl,
                        ArrayRef<Decl *> Fields, SourceLocation LBrac,
                        SourceLocation RBrac,

``````````

</details>


https://github.com/llvm/llvm-project/pull/212906


More information about the cfe-commits mailing list