[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