https://github.com/rapidsna updated https://github.com/llvm/llvm-project/pull/224556
>From 9be681ae5916c3562b80ac99990b6c99e844a30e Mon Sep 17 00:00:00 2001 From: Yeoul Na <[email protected]> Date: Thu, 10 Sep 2026 07:07:26 -0700 Subject: [PATCH 1/3] [BoundsSafety][NFC] Add the count-refill logic for late-parsed bounds attributes Introduce the machinery that fills in a late-parsed bounds attribute's argument once the enclosing record is complete, without wiring it up yet. Nothing creates an incomplete CountAttributedType or records one for completion at this point, so this is inert -- the functions are unused and behavior is unchanged. Activation follows in the next commit. --- clang/include/clang/Parse/Parser.h | 15 +++++++++ clang/include/clang/Sema/Sema.h | 7 ++++ clang/lib/AST/Decl.cpp | 8 ++++- clang/lib/Parse/ParseDecl.cpp | 48 ++++++++++++++++++++++++++ clang/lib/Sema/SemaType.cpp | 54 ++++++++++++++++++++++++++++++ 5 files changed, 131 insertions(+), 1 deletion(-) diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h index e9bab81b095fd..ce97ad25bcd16 100644 --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -232,6 +232,16 @@ struct LateParsedAttribute : public LateParsedDeclaration { /// is replaced with a concrete type (e.g., CountAttributedType). struct LateParsedTypeAttribute : public LateParsedAttribute { + /// The type built for this attribute during type construction, still missing + /// the argument that hasn't been parsed yet. Filled in by + /// `Parser::ProcessLateParsedTypeAttrCallback` and completed once the + /// enclosing scope makes the argument parseable. Null if type construction + /// rejected the attribute. + /// + /// Held as the base class so the parser stays agnostic about which bounds + /// attribute this is; Sema dispatches on the concrete kind when completing. + BoundsAttributedType *TypeToComplete = nullptr; + explicit LateParsedTypeAttribute(Parser *P, IdentifierInfo &Name, SourceLocation Loc) : LateParsedAttribute(P, Name, Loc, Kind::Type) {} @@ -1524,6 +1534,11 @@ class Parser : public CodeCompletionHandler { void ParseLexedTypeAttribute(LateParsedTypeAttribute &LA, ParsedAttributes &OutAttrs); + /// Complete every late-parsed type attribute queued for the record whose body + /// just closed. Consumes and clears \p LateTypeAttrs. + void CompleteLateParsedTypeAttributes( + SmallVectorImpl<LateParsedTypeAttribute *> &LateTypeAttrs); + /// Parse cached tokens for a late-parsed attribute and return the parsed /// attributes. Shared implementation used by both ParseLexedAttribute and /// ParseLexedTypeAttribute. diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 5becfc9fae152..46df36396bc6f 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -2514,6 +2514,13 @@ class Sema final : public SemaBase { bool CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes, bool OrNull); + /// Supply the parsed argument of a late-parsed bounds attribute to the type + /// built for it by ActOnLateParsedTypeAttr, and run the checks that need the + /// owning declaration. \p FD is the field the type belongs to. Returns false + /// if the attribute was rejected. + bool ActOnLateParsedTypeAttrArgument(BoundsAttributedType *BATy, + FieldDecl *FD, Expr *Arg); + /// Perform Bounds Safety Semantic checks for assigning to a `__counted_by` or /// `__counted_by_or_null` pointer type \param LHSTy. /// diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp index 8c1418625bf33..4b931d1836dec 100644 --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -4924,7 +4924,13 @@ const FieldDecl *FieldDecl::findCountedByField() const { if (!CAT) return nullptr; - const auto *CountDRE = cast<DeclRefExpr>(CAT->getCountExpr()); + // A late-parsed attribute whose argument was rejected keeps the node with the + // raw argument as its count (see Sema::ActOnLateParsedTypeAttrArgument). That + // argument may not be a simple declaration reference (e.g. it may be an error + // expression or a `sizeof`), in which case it refers to no field. + const auto *CountDRE = dyn_cast<DeclRefExpr>(CAT->getCountExpr()); + if (!CountDRE) + return nullptr; const auto *CountDecl = CountDRE->getDecl(); if (const auto *IFD = dyn_cast<IndirectFieldDecl>(CountDecl)) CountDecl = IFD->getAnonField(); diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp index 5976f5a7ccdea..9a49ce16447cc 100644 --- a/clang/lib/Parse/ParseDecl.cpp +++ b/clang/lib/Parse/ParseDecl.cpp @@ -4896,6 +4896,54 @@ void Parser::ParseLexedTypeAttribute(LateParsedTypeAttribute &LA, OutAttrs.takeAllAppendingFrom(Attrs); } +void Parser::CompleteLateParsedTypeAttributes( + SmallVectorImpl<LateParsedTypeAttribute *> &LateTypeAttrs) { + for (LateParsedTypeAttribute *LTA : LateTypeAttrs) { + // Read these out before parsing, which destroys the attribute. The type is + // null if construction rejected the attribute, in which case the diagnostic + // has already been emitted and there is nothing to complete. + BoundsAttributedType *BATy = LTA->TypeToComplete; + // Rejected during construction (already diagnosed); the cached tokens are + // self-contained, so there is nothing to drain — just discard it. + if (!BATy) { + delete LTA; + continue; + } + // The fields were + // attached in ParseStructDeclaration as each declarator was completed; more + // than one appears when several declarators share a + // declaration-specifier-position attribute. + SmallVector<Decl *, 2> Fields(LTA->Decls); + + AttributeFactory AF; + ParsedAttributes Attrs(AF); + ParseLexedTypeAttribute(*LTA, Attrs); + delete LTA; + + // An unparseable argument leaves no attribute behind; already diagnosed. + if (Attrs.empty()) + continue; + assert(Attrs.size() == 1); + + Expr *Arg = Attrs[0].getArgAsExpr(0); + assert(Arg); + + // No field means the attribute never reached a field declarator (for + // instance the type was rejected during construction, which unwraps the + // node and leaves it unreferenced), so nothing is left to complete. + bool Valid = !Fields.empty(); + for (Decl *FD : Fields) + Valid &= Actions.ActOnLateParsedTypeAttrArgument( + BATy, cast<FieldDecl>(FD), Arg); + + if (Valid) + Attrs[0].setUsedAsTypeAttr(); + else + Attrs[0].setInvalid(); + } + LateTypeAttrs.clear(); +} + void LateParsedTypeAttribute::ParseInto(ParsedAttributes &OutAttrs) { // Delegate to the Parser that created this attribute Self->ParseLexedTypeAttribute(*this, OutAttrs); diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp index 483f9ab088799..1026e5e938137 100644 --- a/clang/lib/Sema/SemaType.cpp +++ b/clang/lib/Sema/SemaType.cpp @@ -10001,6 +10001,60 @@ BuildTypeCoupledDecls(Expr *E, Decls.push_back(TypeCoupledDeclRefInfo(CountDecl, /*IsDref*/ false)); } +bool Sema::ActOnLateParsedTypeAttrArgument(BoundsAttributedType *BATy, + FieldDecl *FD, Expr *Arg) { + assert(Arg); + + // Only the counted_by family exists so far. + auto *CATy = cast<CountAttributedType>(BATy); + + // A nested counted_by (buried under a pointer or array) was diagnosed and + // dropped to its wrapped type while the declarator was built, orphaning this + // node -- it is no longer part of the field's type. getAs finds only a + // top-level (through-sugar) CountAttributedType, so when it can't find this + // node the node was dropped: skip it, leaving the field as-is. This matches + // the eager path, which drops the attribute for a nested counted_by. + if (FD->getType()->getAs<CountAttributedType>() != CATy) + return false; + + // Rejected: complete the node in place with the raw argument and no coupled + // decls. The argument isn't a valid count reference, so there are none -- + // and BuildTypeCoupledDecls would assert on a non-DeclRefExpr. Mark the field + // invalid; consumers bail on a non-DeclRefExpr count. Guarded so shared + // declarators (`IP __counted_by(n) a, b;`) only complete the node once. + auto Reject = [&]() -> bool { + if (!CATy->getCountExpr()) + Context.completeCountAttributedType(CATy, Arg, {}); + FD->setInvalidDecl(); + return false; + }; + + // A failed parse was already diagnosed; skip the checks (they would only add + // noise) and recover the node directly. + if (Arg->containsErrors()) + return Reject(); + + // Rejected (diagnostic emitted by the check): a bad count expression, or a + // valid reference in an invalid position (union member, non-flexible array, + // cross-struct count). + if (CheckCountedByAttrOnField(FD, Arg, CATy->isCountInBytes(), + CATy->isOrNull())) + return Reject(); + + // Valid: the argument is a simple declaration reference, so it's safe to + // derive the coupled decls. Several declarators can share one node when the + // attribute was written in declaration-specifier position + // (`IP __counted_by(n) a, b;`), so this runs once per field; completion is + // idempotent -- the first field supplies the count, the rest only need the + // decl-context check above. + llvm::SmallVector<TypeCoupledDeclRefInfo, 1> Decls; + BuildTypeCoupledDecls(Arg, Decls); + if (!CATy->getCountExpr()) + Context.completeCountAttributedType(CATy, Arg, Decls); + + return true; +} + QualType Sema::BuildCountAttributedArrayOrPointerType(QualType WrappedTy, Expr *CountExpr, bool CountInBytes, >From 00329c9772e3114a29acbb2dce60f288def118c3 Mon Sep 17 00:00:00 2001 From: Yeoul Na <[email protected]> Date: Fri, 18 Sep 2026 09:54:36 -0700 Subject: [PATCH 2/3] Use unique_ptr and remove raw deletes; assertions instead of bail out --- clang/lib/Parse/ParseDecl.cpp | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp index 9a49ce16447cc..848390205b983 100644 --- a/clang/lib/Parse/ParseDecl.cpp +++ b/clang/lib/Parse/ParseDecl.cpp @@ -4898,27 +4898,18 @@ void Parser::ParseLexedTypeAttribute(LateParsedTypeAttribute &LA, void Parser::CompleteLateParsedTypeAttributes( SmallVectorImpl<LateParsedTypeAttribute *> &LateTypeAttrs) { - for (LateParsedTypeAttribute *LTA : LateTypeAttrs) { - // Read these out before parsing, which destroys the attribute. The type is - // null if construction rejected the attribute, in which case the diagnostic - // has already been emitted and there is nothing to complete. + for (LateParsedTypeAttribute *RawLTA : LateTypeAttrs) { + std::unique_ptr<LateParsedTypeAttribute> LTA(RawLTA); + BoundsAttributedType *BATy = LTA->TypeToComplete; - // Rejected during construction (already diagnosed); the cached tokens are - // self-contained, so there is nothing to drain — just discard it. - if (!BATy) { - delete LTA; + if (!BATy) continue; - } - // The fields were - // attached in ParseStructDeclaration as each declarator was completed; more - // than one appears when several declarators share a - // declaration-specifier-position attribute. - SmallVector<Decl *, 2> Fields(LTA->Decls); + + ArrayRef<Decl *> Fields = LTA->Decls; AttributeFactory AF; ParsedAttributes Attrs(AF); ParseLexedTypeAttribute(*LTA, Attrs); - delete LTA; // An unparseable argument leaves no attribute behind; already diagnosed. if (Attrs.empty()) @@ -4928,13 +4919,11 @@ void Parser::CompleteLateParsedTypeAttributes( Expr *Arg = Attrs[0].getArgAsExpr(0); assert(Arg); - // No field means the attribute never reached a field declarator (for - // instance the type was rejected during construction, which unwraps the - // node and leaves it unreferenced), so nothing is left to complete. - bool Valid = !Fields.empty(); + bool Valid = true; + assert(!Fields.empty()); for (Decl *FD : Fields) - Valid &= Actions.ActOnLateParsedTypeAttrArgument( - BATy, cast<FieldDecl>(FD), Arg); + Valid &= Actions.ActOnLateParsedTypeAttrArgument(BATy, cast<FieldDecl>(FD), + Arg); if (Valid) Attrs[0].setUsedAsTypeAttr(); >From 30cd926e97afa5b60bc49929a55b7dae3b6a1e58 Mon Sep 17 00:00:00 2001 From: Yeoul Na <[email protected]> Date: Sat, 19 Sep 2026 07:49:07 -0700 Subject: [PATCH 3/3] Record rejected nodes explicitly and skip; trim wordy comments --- clang/include/clang/Sema/Sema.h | 15 +++++++++++++++ clang/lib/Parse/ParseDecl.cpp | 2 +- clang/lib/Sema/SemaType.cpp | 30 +++++------------------------- 3 files changed, 21 insertions(+), 26 deletions(-) diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 46df36396bc6f..03fc536e4ec6e 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -2514,6 +2514,21 @@ class Sema final : public SemaBase { bool CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes, bool OrNull); + /// Late-parsed bounds types dropped while their declarator was built. The + /// attribute has already been diagnosed and its node is no longer part of + /// any type, so the completion pass must skip it rather than parse its + /// argument and complete it. + llvm::SmallPtrSet<const BoundsAttributedType *, 4> + RejectedLateParsedBoundsTypes; + + void markLateParsedBoundsTypeRejected(const BoundsAttributedType *BATy) { + RejectedLateParsedBoundsTypes.insert(BATy); + } + + bool isLateParsedBoundsTypeRejected(const BoundsAttributedType *BATy) const { + return RejectedLateParsedBoundsTypes.contains(BATy); + } + /// Supply the parsed argument of a late-parsed bounds attribute to the type /// built for it by ActOnLateParsedTypeAttr, and run the checks that need the /// owning declaration. \p FD is the field the type belongs to. Returns false diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp index 848390205b983..e3b0eda1af670 100644 --- a/clang/lib/Parse/ParseDecl.cpp +++ b/clang/lib/Parse/ParseDecl.cpp @@ -4902,7 +4902,7 @@ void Parser::CompleteLateParsedTypeAttributes( std::unique_ptr<LateParsedTypeAttribute> LTA(RawLTA); BoundsAttributedType *BATy = LTA->TypeToComplete; - if (!BATy) + if (!BATy || Actions.isLateParsedBoundsTypeRejected(BATy)) continue; ArrayRef<Decl *> Fields = LTA->Decls; diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp index 1026e5e938137..0e2a6414fcd5f 100644 --- a/clang/lib/Sema/SemaType.cpp +++ b/clang/lib/Sema/SemaType.cpp @@ -10008,47 +10008,27 @@ bool Sema::ActOnLateParsedTypeAttrArgument(BoundsAttributedType *BATy, // Only the counted_by family exists so far. auto *CATy = cast<CountAttributedType>(BATy); - // A nested counted_by (buried under a pointer or array) was diagnosed and - // dropped to its wrapped type while the declarator was built, orphaning this - // node -- it is no longer part of the field's type. getAs finds only a - // top-level (through-sugar) CountAttributedType, so when it can't find this - // node the node was dropped: skip it, leaving the field as-is. This matches - // the eager path, which drops the attribute for a nested counted_by. - if (FD->getType()->getAs<CountAttributedType>() != CATy) - return false; - - // Rejected: complete the node in place with the raw argument and no coupled - // decls. The argument isn't a valid count reference, so there are none -- - // and BuildTypeCoupledDecls would assert on a non-DeclRefExpr. Mark the field - // invalid; consumers bail on a non-DeclRefExpr count. Guarded so shared - // declarators (`IP __counted_by(n) a, b;`) only complete the node once. auto Reject = [&]() -> bool { + // Guarded so shared declarators (`IP __counted_by(n) a, b;`) only complete + // the node once. if (!CATy->getCountExpr()) Context.completeCountAttributedType(CATy, Arg, {}); FD->setInvalidDecl(); return false; }; - // A failed parse was already diagnosed; skip the checks (they would only add - // noise) and recover the node directly. if (Arg->containsErrors()) return Reject(); - // Rejected (diagnostic emitted by the check): a bad count expression, or a - // valid reference in an invalid position (union member, non-flexible array, - // cross-struct count). if (CheckCountedByAttrOnField(FD, Arg, CATy->isCountInBytes(), CATy->isOrNull())) return Reject(); - // Valid: the argument is a simple declaration reference, so it's safe to - // derive the coupled decls. Several declarators can share one node when the - // attribute was written in declaration-specifier position - // (`IP __counted_by(n) a, b;`), so this runs once per field; completion is - // idempotent -- the first field supplies the count, the rest only need the - // decl-context check above. llvm::SmallVector<TypeCoupledDeclRefInfo, 1> Decls; BuildTypeCoupledDecls(Arg, Decls); + // Several declarators can share one node when the attribute was written in + // declaration-specifier position (`IP __counted_by(n) a, b;`), so this runs + // once per field if (!CATy->getCountExpr()) Context.completeCountAttributedType(CATy, Arg, Decls); _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
