[clang] f873bf3 - [BoundsSafety][NFC] Allow CountAttributedType's count to be filled in later (#223267)
via cfe-commits
cfe-commits at lists.llvm.org
Thu Sep 17 09:47:38 PDT 2026
Author: Yeoul Na
Date: 2026-09-17T09:47:32-07:00
New Revision: f873bf351c19c711451a80df6bea7c76f17727be
URL: https://github.com/llvm/llvm-project/commit/f873bf351c19c711451a80df6bea7c76f17727be
DIFF: https://github.com/llvm/llvm-project/commit/f873bf351c19c711451a80df6bea7c76f17727be.diff
LOG: [BoundsSafety][NFC] Allow CountAttributedType's count to be filled in later (#223267)
Prepare CountAttributedType so its count expression can be supplied
after the node is created, which the new late-parsed counted_by
mechanism needs: the type is built when the attribute is seen, but its
argument isn't parsed until the enclosing record is complete.
- Drop the TrailingObjects coupled-decl storage in favour of an
ASTContext-allocated array held by the ArrayRef the base class already
has, so the decls can be attached after construction.
- Add CountAttributedType::setCountExpr for in-place completion.
- Add ASTContext::getIncompleteCountAttributedType (count-less, not
uniqued) and completeCountAttributedType. Incomplete nodes are not
registered in ASTContext.Types until completed, so a node abandoned with
a null count is never reachable.
No functional change: getCountAttributedType still builds a
fully-formed, count-carrying type as before.
This is an adjusted direction from the previous attempt to create a
place holder type (LateParsedAttrType) and rebuild it once late parsing
is done (https://github.com/llvm/llvm-project/pull/212906) based on the
feedback from @cor3ntin
https://github.com/llvm/llvm-project/pull/212906#issuecomment-5532012528.
Creating a CountAttributedType in the first pass and filling in the
count expression in the second pass has a benefit that the types
(including the enclosing types) won't need to be reconstructed, allowing
to drop TreeTransform.
Added:
Modified:
clang/include/clang/AST/ASTContext.h
clang/include/clang/AST/TypeBase.h
clang/lib/AST/ASTContext.cpp
clang/lib/AST/Type.cpp
clang/lib/Sema/SemaBoundsSafety.cpp
Removed:
################################################################################
diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index 2f7d39599c477..b2d407e412b3d 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -1681,6 +1681,22 @@ class ASTContext : public RefCountedBase<ASTContext> {
bool OrNull,
ArrayRef<TypeCoupledDeclRefInfo> DependentDecls) const;
+ /// Return a `CountAttributedType` whose count expression has not been parsed
+ /// yet, for use by a late-parsed bounds attribute. The result is *not*
+ /// uniqued, and must be completed with `completeCountAttributedType` once the
+ /// argument becomes parseable. Returns the node rather than a `QualType` so
+ /// the caller can retain it for completion.
+ CountAttributedType *getIncompleteCountAttributedType(QualType WrappedTy,
+ bool CountInBytes,
+ bool OrNull) const;
+
+ /// Supply the count expression and coupled declarations for a type created by
+ /// `getIncompleteCountAttributedType`. Enclosing types keep pointing at the
+ /// same node, so nothing above it needs rebuilding.
+ void completeCountAttributedType(
+ CountAttributedType *CATy, Expr *CountExpr,
+ 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.
diff --git a/clang/include/clang/AST/TypeBase.h b/clang/include/clang/AST/TypeBase.h
index 424a2afee84da..28f102fdaf534 100644
--- a/clang/include/clang/AST/TypeBase.h
+++ b/clang/include/clang/AST/TypeBase.h
@@ -3456,6 +3456,13 @@ class BoundsAttributedType : public Type, public llvm::FoldingSetNode {
BoundsAttributedType(TypeClass TC, QualType Wrapped, QualType Canon);
public:
+ enum BoundsAttrKind {
+ CountedBy = 0,
+ SizedBy,
+ CountedByOrNull,
+ SizedByOrNull,
+ };
+
bool isSugared() const { return true; }
QualType desugar() const { return WrappedTy; }
@@ -3492,10 +3499,7 @@ class BoundsAttributedType : public Type, public llvm::FoldingSetNode {
/// Represents a sugar type with `__counted_by` or `__sized_by` annotations,
/// including their `_or_null` variants.
-class CountAttributedType final
- : public BoundsAttributedType,
- public llvm::TrailingObjects<CountAttributedType,
- TypeCoupledDeclRefInfo> {
+class CountAttributedType final : public BoundsAttributedType {
friend class ASTContext;
Expr *CountExpr;
@@ -3505,27 +3509,36 @@ class CountAttributedType final
/// __counted_by_or_null or __sized_by_or_null) \p CoupledDecls contains the
/// list of declarations referenced by \p CountExpr, which the type depends on
/// for the bounds information.
+ ///
+ /// \p CountExpr may be null, and \p CoupledDecls empty, for a type created by
+ /// a late-parsed attribute whose argument has not been parsed yet; such a
+ /// type is completed by \c complete once the enclosing scope is known. See
+ /// \c Parser::CompleteLateParsedTypeAttributes.
CountAttributedType(QualType Wrapped, QualType Canon, Expr *CountExpr,
bool CountInBytes, bool OrNull,
ArrayRef<TypeCoupledDeclRefInfo> CoupledDecls);
- unsigned numTrailingObjects(OverloadToken<TypeCoupledDeclRefInfo>) const {
- return CountAttributedTypeBits.NumCoupledDecls;
- }
+ /// Allocate and construct a \c CountAttributedType in \p Ctx, including its
+ /// coupled-declaration array. \p CountExpr may be null (with \p CoupledDecls
+ /// empty) for a late-parsed attribute whose argument is not yet parsed;
+ /// complete such a node later with \c complete.
+ static CountAttributedType *
+ Create(const ASTContext &Ctx, QualType Wrapped, QualType Canon,
+ Expr *CountExpr, bool CountInBytes, bool OrNull,
+ ArrayRef<TypeCoupledDeclRefInfo> CoupledDecls);
+
+ /// Supply the count expression and coupled declarations for a node created by
+ /// \c Create with a null count -- a late-parsed attribute whose argument has
+ /// now been parsed. Allocates the decl array in \p Ctx, so the node owns it.
+ void complete(const ASTContext &Ctx, Expr *E,
+ ArrayRef<TypeCoupledDeclRefInfo> CoupledDecls);
public:
- enum DynamicCountPointerKind {
- CountedBy = 0,
- SizedBy,
- CountedByOrNull,
- SizedByOrNull,
- };
-
Expr *getCountExpr() const { return CountExpr; }
bool isCountInBytes() const { return CountAttributedTypeBits.CountInBytes; }
bool isOrNull() const { return CountAttributedTypeBits.OrNull; }
- DynamicCountPointerKind getKind() const {
+ BoundsAttrKind getKind() const {
if (isOrNull())
return isCountInBytes() ? SizedByOrNull : CountedByOrNull;
return isCountInBytes() ? SizedBy : CountedBy;
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 650a3206e3d48..ee8663e6906af 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -3758,7 +3758,13 @@ QualType ASTContext::getCountAttributedType(
QualType WrappedTy, Expr *CountExpr, bool CountInBytes, bool OrNull,
ArrayRef<TypeCoupledDeclRefInfo> DependentDecls) const {
assert(WrappedTy->isPointerType() || WrappedTy->isArrayType());
+ assert(CountExpr && "use getIncompleteCountAttributedType for a null count");
+ // Complete (non-late-parsed) path: the count expression is known up front.
+ // This deliberately preserves the pre-existing uniquing behavior -- the
+ // FoldingSet lookup/insert below is unchanged by late-parse support. Only
+ // getIncompleteCountAttributedType (count filled in later) opts out of
+ // uniquing.
llvm::FoldingSetNodeID ID;
CountAttributedType::Profile(ID, WrappedTy, CountExpr, CountInBytes, OrNull);
@@ -3768,17 +3774,53 @@ QualType ASTContext::getCountAttributedType(
return QualType(CATy, 0);
QualType CanonTy = getCanonicalType(WrappedTy);
- size_t Size = CountAttributedType::totalSizeToAlloc<TypeCoupledDeclRefInfo>(
- DependentDecls.size());
- CATy = (CountAttributedType *)Allocate(Size, TypeAlignment);
- new (CATy) CountAttributedType(WrappedTy, CanonTy, CountExpr, CountInBytes,
- OrNull, DependentDecls);
+ CATy = CountAttributedType::Create(*this, WrappedTy, CanonTy, CountExpr,
+ CountInBytes, OrNull, DependentDecls);
Types.push_back(CATy);
CountAttributedTypes.insert(CATy, Token);
return QualType(CATy, 0);
}
+CountAttributedType *ASTContext::getIncompleteCountAttributedType(
+ QualType WrappedTy, bool CountInBytes, bool OrNull) const {
+ assert(WrappedTy->isPointerType() || WrappedTy->isArrayType());
+
+ // Deliberately opts out of the uniquing that `getCountAttributedType` does:
+ // `CountAttributedType::Profile` keys on the `CountExpr` pointer, which is
+ // null here, so every incomplete node would profile identically as
+ // `(WrappedTy, flags, nullptr)` and two fields with
diff erent counts would
+ // collide. The node stays un-uniqued even after completion; see
+ // `completeCountAttributedType`.
+ //
+ // Also deliberately not in `Types` yet. An incomplete node can be abandoned
+ // without ever being completed (a nested counted_by, or an argument that
+ // fails to parse), and a null-count node must not be reachable by anything
+ // that scans `Types`. `completeCountAttributedType` registers it once the
+ // count is in place.
+ return CountAttributedType::Create(
+ *this, WrappedTy, getCanonicalType(WrappedTy),
+ /*CountExpr=*/nullptr, CountInBytes, OrNull,
+ /*CoupledDecls=*/{});
+}
+
+void ASTContext::completeCountAttributedType(
+ CountAttributedType *CATy, Expr *CountExpr,
+ ArrayRef<TypeCoupledDeclRefInfo> DependentDecls) const {
+ CATy->complete(*this, CountExpr, DependentDecls);
+ // Safe for `Types` scanners now that the count is in place; see
+ // `getIncompleteCountAttributedType` for why it was held back.
+ //
+ // It stays out of the `CountAttributedTypes` FoldingSet permanently, unlike
+ // an eagerly built node: this pointer is already embedded in the enclosing
+ // types and handed out, so an equal node that happens to exist cannot be
+ // merged into. The only cost is that a completed node is never
+ // pointer-shared with an equal eager one, which does not affect semantic
+ // type equality -- `hasSameType` compares canonical types, and this sugar's
+ // canonical type is the wrapped type's.
+ Types.push_back(CATy);
+}
+
QualType ASTContext::getLateParsedAttrType(
QualType WrappedTy, LateParsedTypeAttribute *LateParsedAttr) const {
QualType CanonTy = getCanonicalType(WrappedTy);
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index cbacca1c44d91..98901502cf37a 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -4178,9 +4178,45 @@ CountAttributedType::CountAttributedType(
CountAttributedTypeBits.NumCoupledDecls = CoupledDecls.size();
CountAttributedTypeBits.CountInBytes = CountInBytes;
CountAttributedTypeBits.OrNull = OrNull;
- auto *DeclSlot = getTrailingObjects();
- llvm::copy(CoupledDecls, DeclSlot);
- Decls = llvm::ArrayRef(DeclSlot, CoupledDecls.size());
+ // `CoupledDecls` is already allocated by the caller (Create), so it
+ // can be retained by reference. This lets a type created by a late-parsed
+ // attribute start out with no decls and gain them later via `complete`,
+ // which a trailing-object array could not accommodate.
+ Decls = CoupledDecls;
+}
+
+/// Copy \p Decls into \p Ctx so a \c CountAttributedType can retain it by
+/// reference. The node owns this allocation rather than its callers, so both
+/// \c Create and \c complete route through here.
+static ArrayRef<TypeCoupledDeclRefInfo>
+allocateCoupledDecls(const ASTContext &Ctx,
+ ArrayRef<TypeCoupledDeclRefInfo> Decls) {
+ if (Decls.empty())
+ return {};
+ auto *Slots = Ctx.Allocate<TypeCoupledDeclRefInfo>(Decls.size());
+ llvm::copy(Decls, Slots);
+ return ArrayRef(Slots, Decls.size());
+}
+
+CountAttributedType *
+CountAttributedType::Create(const ASTContext &Ctx, QualType Wrapped,
+ QualType Canon, Expr *CountExpr, bool CountInBytes,
+ bool OrNull,
+ ArrayRef<TypeCoupledDeclRefInfo> CoupledDecls) {
+ ArrayRef<TypeCoupledDeclRefInfo> Decls =
+ allocateCoupledDecls(Ctx, CoupledDecls);
+ return new (Ctx, alignof(CountAttributedType)) CountAttributedType(
+ Wrapped, Canon, CountExpr, CountInBytes, OrNull, Decls);
+}
+
+void CountAttributedType::complete(
+ const ASTContext &Ctx, Expr *E,
+ ArrayRef<TypeCoupledDeclRefInfo> CoupledDecls) {
+ assert(!CountExpr && "count expression is already set");
+ assert(E && "completing with a null count expression");
+ CountExpr = E;
+ Decls = allocateCoupledDecls(Ctx, CoupledDecls);
+ CountAttributedTypeBits.NumCoupledDecls = Decls.size();
}
StringRef CountAttributedType::getAttributeName(bool WithMacroPrefix) const {
diff --git a/clang/lib/Sema/SemaBoundsSafety.cpp b/clang/lib/Sema/SemaBoundsSafety.cpp
index 066dab2f0bef2..75041c801b6ff 100644
--- a/clang/lib/Sema/SemaBoundsSafety.cpp
+++ b/clang/lib/Sema/SemaBoundsSafety.cpp
@@ -17,8 +17,8 @@
namespace clang {
-static CountAttributedType::DynamicCountPointerKind
-getCountAttrKind(bool CountInBytes, bool OrNull) {
+static CountAttributedType::BoundsAttrKind getCountAttrKind(bool CountInBytes,
+ bool OrNull) {
if (CountInBytes)
return OrNull ? CountAttributedType::SizedByOrNull
: CountAttributedType::SizedBy;
More information about the cfe-commits
mailing list