[llvm-branch-commits] [clang] [BoundsSafety] Handle counted_by as a type attribute and complete late-parsed counts (PR #224554)

via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Fri Sep 18 01:10:44 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang

Author: Yeoul Na (rapidsna)

<details>
<summary>Changes</summary>

Two commits:

**1. Handle the counted_by family as a type attribute**

counted_by / sized_by (and their _or_null variants) were only handled as a
declaration attribute: `handleCountedByAttrField` validated the attribute and
then patched the field afterwards with `FieldDecl::setType`. Add
`HandleCountedByAttrOnType`, dispatch the family from `processTypeAttrs`, and
remove the decl-position handler, so the type is built during type
construction from a single handler serving both positions. The FieldDecl-based
type-shape checks are superseded by `Sema::ValidateBoundsAttrTypeShape` and
deleted; no diagnostic is dropped. Test expectations are updated in the second
commit, so this commit alone leaves them stale.

**2. Create incomplete counted_by types and wire up the refill**

Activate late parsing under `-fexperimental-late-parse-attributes`. When the
attribute is seen during type construction and its argument cannot be resolved
yet, build the `CountAttributedType` immediately with
`getIncompleteCountAttributedType` and record it against the enclosing record;
the count expression is filled in at the closing brace. Because enclosing types
refer to the node by pointer, completing it in place leaves the type chain
untouched -- no rebuild and no TypeLoc re-emission.

Depends on #<!-- -->224552

---

Patch is 98.91 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/224554.diff


23 Files Affected:

- (modified) clang/include/clang/AST/TypeBase.h (+1-1) 
- (modified) clang/include/clang/Parse/Parser.h (+11) 
- (modified) clang/include/clang/Sema/Sema.h (+23-16) 
- (modified) clang/lib/Parse/ParseDecl.cpp (+178-25) 
- (modified) clang/lib/Sema/SemaBoundsSafety.cpp (+3-109) 
- (modified) clang/lib/Sema/SemaDecl.cpp (+24-1) 
- (modified) clang/lib/Sema/SemaDeclAttr.cpp (-44) 
- (modified) clang/lib/Sema/SemaType.cpp (+131-1) 
- (modified) clang/test/AST/attr-counted-by-or-null-struct-ptrs.c (-23) 
- (modified) clang/test/AST/attr-counted-by-struct-ptrs.c (-26) 
- (modified) clang/test/AST/attr-sized-by-or-null-struct-ptrs.c (-24) 
- (modified) clang/test/AST/attr-sized-by-struct-ptrs.c (-24) 
- (modified) clang/test/Sema/attr-counted-by-late-parsed-struct-ptrs.c (+29-50) 
- (modified) clang/test/Sema/attr-counted-by-or-null-last-field.c (+1-3) 
- (modified) clang/test/Sema/attr-counted-by-or-null-late-parsed-struct-ptrs.c (+14-50) 
- (modified) clang/test/Sema/attr-counted-by-or-null-struct-ptrs-completable-incomplete-pointee.c (+17-20) 
- (modified) clang/test/Sema/attr-counted-by-or-null-struct-ptrs.c (+8-6) 
- (modified) clang/test/Sema/attr-counted-by-struct-ptrs-completable-incomplete-pointee.c (+15-17) 
- (modified) clang/test/Sema/attr-counted-by-struct-ptrs.c (+10-8) 
- (modified) clang/test/Sema/attr-sized-by-late-parsed-struct-ptrs.c (+10-42) 
- (modified) clang/test/Sema/attr-sized-by-or-null-late-parsed-struct-ptrs.c (+11-42) 
- (modified) clang/test/Sema/attr-sized-by-or-null-struct-ptrs.c (+8-6) 
- (modified) clang/test/Sema/attr-sized-by-struct-ptrs.c (+8-4) 


``````````diff
diff --git a/clang/include/clang/AST/TypeBase.h b/clang/include/clang/AST/TypeBase.h
index 28f102fdaf534..454829ff58b43 100644
--- a/clang/include/clang/AST/TypeBase.h
+++ b/clang/include/clang/AST/TypeBase.h
@@ -3451,7 +3451,7 @@ class BoundsAttributedType : public Type, public llvm::FoldingSetNode {
   QualType WrappedTy;
 
 protected:
-  ArrayRef<TypeCoupledDeclRefInfo> Decls; // stored in trailing objects
+  ArrayRef<TypeCoupledDeclRefInfo> Decls; // allocated in the ASTContext
 
   BoundsAttributedType(TypeClass TC, QualType Wrapped, QualType Canon);
 
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index 6d0affa3c8822..af0f9040185f2 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -292,6 +292,8 @@ class Parser : public CodeCompletionHandler {
   friend class PoisonSEHIdentifiersRAIIObject;
   friend class ParenBraceBracketBalancer;
   friend class BalancedDelimiterTracker;
+  friend struct LateParsedAttribute;
+  friend struct LateParsedTypeAttribute;
 
   Parser(Preprocessor &PP, Sema &Actions, bool SkipFunctionBodies);
   ~Parser() override;
@@ -2246,6 +2248,8 @@ class Parser : public CodeCompletionHandler {
       ParsedAttributes Attrs(AttrFactory);
       ParseGNUAttributes(Attrs, LateAttrs, &D);
       D.takeAttributesAppending(Attrs);
+      if (LateAttrs)
+        Parser::TakeTypeAttrsAppendingFrom(D.getLateAttributes(), *LateAttrs);
     }
   }
 
@@ -8172,6 +8176,13 @@ class Parser : public CodeCompletionHandler {
                                                 QualType &type,
                                                 unsigned pointerNestLevel);
 
+  /// The late-parsed type attributes of the record currently being parsed, so a
+  /// nested anonymous record can hand its unresolved attributes to the enclosing
+  /// record whose scope makes their arguments visible. Null outside a record
+  /// body.
+  SmallVectorImpl<LateParsedTypeAttribute *> *CurRecordLateParsedTypeAttrs =
+      nullptr;
+
   /// 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/Sema.h b/clang/include/clang/Sema/Sema.h
index f643f1cb328d8..952a15d029569 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;
@@ -2538,22 +2540,27 @@ class Sema final : public SemaBase {
                                    bool AllowRedecl = false,
                                    Expr *AttrArg = nullptr);
 
-  /// Check if applying the specified attribute variant from the "counted by"
-  /// family of attributes to FieldDecl \p FD is semantically valid. If
-  /// semantically invalid diagnostics will be emitted explaining the problems.
-  ///
-  /// \param FD The FieldDecl to apply the attribute to
-  /// \param E The count expression on the attribute
-  /// \param CountInBytes If true the attribute is from the "sized_by" family of
-  ///                     attributes. If the false the attribute is from
-  ///                     "counted_by" family of attributes.
-  /// \param OrNull If true the attribute is from the "_or_null" suffixed family
-  ///               of attributes. If false the attribute does not have the
-  ///               suffix.
-  ///
-  /// Together \p CountInBytes and \p OrNull decide the attribute variant. E.g.
-  /// \p CountInBytes and \p OrNull both being true indicates the
-  /// `counted_by_or_null` attribute.
+  /// Perform semantic validation on a FieldDecl with a "counted_by" family
+  /// attribute. This is called after the attribute has been attached to the
+  /// field's type (as a CountAttributedType) to validate the attribute is
+  /// correctly applied.
+  ///
+  /// This performs declaration-level checks that require the FieldDecl to
+  /// exist, complementing the type-level checks performed in
+  /// HandleCountedByAttrOnType during type processing. Specifically, this
+  /// validates:
+  /// - Field is not in a union
+  /// - For array fields, the field is a flexible array member
+  /// - Count expression is an integer type (not bool)
+  /// - Count expression references a field in the same struct
+  /// - Count field is not in a union
+  ///
+  /// \param FD The FieldDecl with the attribute
+  /// \param E The count expression from the attribute
+  /// \param CountInBytes If true the attribute is from the "sized_by" family.
+  ///                     If false the attribute is from the "counted_by"
+  ///                     family.
+  /// \param OrNull If true the attribute has the "_or_null" suffix.
   ///
   /// \returns false iff semantically valid.
   bool CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes,
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index 6b121119550b1..fe2cfe8a5e641 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -34,6 +34,7 @@
 #include "llvm/ADT/ScopeExit.h"
 #include "llvm/ADT/SmallSet.h"
 #include "llvm/ADT/StringSwitch.h"
+#include "llvm/Support/SaveAndRestore.h"
 #include <optional>
 
 using namespace clang;
@@ -118,6 +119,23 @@ static bool IsAttributeArgsParsedInFunctionScope(const IdentifierInfo &II) {
 #undef CLANG_ATTR_PARSE_ARGS_IN_FUNCTION_SCOPE_LIST
 }
 
+/// returns true iff the attribute appertains to a type (a TYPE_ATTR or
+/// DECL_OR_TYPE_ATTR in `Attr.td`).
+static bool IsAttributeTypeAttr(ParsedAttr::Kind Kind) {
+  switch (Kind) {
+#define ATTR(NAME)
+#define DECL_OR_TYPE_ATTR(NAME) case ParsedAttr::AT_##NAME:
+#define TYPE_ATTR(NAME) case ParsedAttr::AT_##NAME:
+#include "clang/Basic/AttrList.inc"
+    return true;
+  default:
+    return false;
+#undef DECL_OR_TYPE_ATTR
+#undef TYPE_ATTR
+#undef ATTR
+  }
+}
+
 /// Check if the a start and end source location expand to the same macro.
 static bool FindLocsWithCommonFileID(Preprocessor &PP, SourceLocation StartLoc,
                                      SourceLocation EndLoc) {
@@ -167,6 +185,9 @@ bool Parser::ParseSingleGNUAttribute(ParsedAttributes &Attrs,
     return false;
   }
 
+  ParsedAttr::Kind AttrKind = ParsedAttr::getParsedKind(
+      AttrName, nullptr, ParsedAttr::Form::GNU().getSyntax());
+
   bool LateParse = false;
   if (!LateAttrs)
     LateParse = false;
@@ -175,7 +196,9 @@ bool Parser::ParseSingleGNUAttribute(ParsedAttributes &Attrs,
     // parsed for `LateAttrParseExperimentalExt` attributes. This will
     // only be late parsed if the experimental language option is enabled.
     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,10 +216,24 @@ bool Parser::ParseSingleGNUAttribute(ParsedAttributes &Attrs,
   }
 
   // Handle attributes with arguments that require late parsing.
-  LateParsedAttribute *LA =
-      new LateParsedAttribute(this, *AttrName, AttrNameLoc);
+  // Late parsing for type attributes isn't properly supported in C++ yet.
+  LateParsedAttribute *LA = nullptr;
+  if (IsAttributeTypeAttr(AttrKind) && !getLangOpts().CPlusPlus)
+    LA = new LateParsedTypeAttribute(this, *AttrName, AttrNameLoc);
+  else
+    LA = new LateParsedAttribute(this, *AttrName, AttrNameLoc);
+
   LateAttrs->push_back(LA);
 
+  // Record type attributes against the record currently being parsed, whose
+  // closing brace is when their arguments become resolvable. `LateAttrs` can't
+  // serve here: for a declarator-position attribute it is a transient local
+  // that is drained into a DeclaratorChunk, and for a decl-spec-position one
+  // TakeTypeAttrsAppendingFrom moves the entry into the DeclSpec.
+  if (auto *LTA = dyn_cast<LateParsedTypeAttribute>(LA);
+      LTA && CurRecordLateParsedTypeAttrs)
+    CurRecordLateParsedTypeAttrs->push_back(LTA);
+
   // Attributes in a class are parsed at the end of the class, along
   // with other late-parsed declarations.
   if (!ClassStack.empty() && !LateAttrs->parseSoon())
@@ -3177,10 +3214,11 @@ void Parser::DistributeCLateParsedAttrs(Decl *Dcl,
   if (!LateAttrs)
     return;
 
+  // Attach `Decl *` to each `LateParsedAttribute *`.
   if (Dcl) {
-    for (auto *LateAttr : *LateAttrs) {
-      if (LateAttr->Decls.empty())
-        LateAttr->addDecl(Dcl);
+    for (auto *LA : *LateAttrs) {
+      if (LA->Decls.empty())
+        LA->addDecl(Dcl);
     }
   }
 }
@@ -3253,12 +3291,6 @@ void Parser::ParseBoundsAttribute(IdentifierInfo &AttrName,
   ArgExprs.push_back(ArgExpr.get());
   Parens.consumeClose();
 
-  ASTContext &Ctx = Actions.getASTContext();
-
-  ArgExprs.push_back(IntegerLiteral::Create(
-      Ctx, llvm::APInt(Ctx.getTypeSize(Ctx.getSizeType()), 0),
-      Ctx.getSizeType(), SourceLocation()));
-
   Attrs.addNew(&AttrName, SourceRange(AttrNameLoc, Parens.getCloseLocation()),
                AttributeScopeInfo(), ArgExprs.data(), ArgExprs.size(), Form);
 }
@@ -3498,6 +3530,10 @@ void Parser::ParseDeclarationSpecifiers(
         DS.takeAttributesAppendingingFrom(attrs);
       }
 
+      if (LateAttrs) {
+        Parser::TakeTypeAttrsAppendingFrom(DS.getLateAttributes(), *LateAttrs);
+      }
+
       // If this is not a declaration specifier token, we're done reading decl
       // specifiers.  First verify that DeclSpec's are consistent.
       DS.Finish(Actions, Policy);
@@ -4030,7 +4066,6 @@ void Parser::ParseDeclarationSpecifiers(
     case tok::kw___declspec:
       ParseAttributes(PAKM_GNU | PAKM_Declspec, DS.getAttributes(), LateAttrs);
       continue;
-
     // Microsoft single token adornments.
     case tok::kw___forceinline: {
       isInvalid = DS.setFunctionSpecForceInline(Loc, PrevSpec, DiagID);
@@ -4782,8 +4817,20 @@ void Parser::ParseStructDeclaration(
   ParsedAttributes Attrs(AttrFactory);
   MaybeParseCXX11Attributes(Attrs);
 
+  // Late-parsed type attributes written in declaration-specifier position (e.g.
+  // `IP __counted_by(n) a, b;` where `IP` is a pointer typedef) belong to every
+  // declarator in this declaration, so remember where this declaration's entries
+  // start before the specifier list is parsed. Indices, not iterators: the side
+  // list is a SmallVector and only ever grows within a record body.
+  unsigned DeclSpecMark =
+      CurRecordLateParsedTypeAttrs ? CurRecordLateParsedTypeAttrs->size() : 0;
+
   // Parse the common specifier-qualifiers-list piece.
-  ParseSpecifierQualifierList(DS);
+  ParseSpecifierQualifierList(DS, AS_none, DeclSpecContext::DSC_normal,
+                              LateFieldAttrs);
+
+  unsigned AfterDeclSpecMark =
+      CurRecordLateParsedTypeAttrs ? CurRecordLateParsedTypeAttrs->size() : 0;
 
   // If there are no declarators, this is a free-standing declaration
   // specifier. Let the actions module cope with it.
@@ -4819,6 +4866,8 @@ void Parser::ParseStructDeclaration(
 
     /// struct-declarator: declarator
     /// struct-declarator: declarator[opt] ':' constant-expression
+    unsigned DeclMark =
+        CurRecordLateParsedTypeAttrs ? CurRecordLateParsedTypeAttrs->size() : 0;
     if (Tok.isNot(tok::colon)) {
       // Don't parse FOO:BAR as if it were a typo for FOO::BAR.
       ColonProtectionRAIIObject X(*this);
@@ -4847,6 +4896,31 @@ void Parser::ParseStructDeclaration(
     if (Field)
       DistributeCLateParsedAttrs(Field, LateFieldAttrs);
 
+    // Record the field each pending late-parsed type attribute belongs to, in
+    // the base class's coupled-decl list. The callback above ran
+    // GetTypeForDeclarator, so the attribute's type node exists by now; pairing
+    // it with the field here means the completion pass at the closing brace
+    // needs no search -- which matters because a bounds type may sit nested
+    // inside the field's type, where it cannot be recovered by inspecting the
+    // field's top-level type.
+    //
+    // Two ranges apply: attributes from the shared declaration-specifier (every
+    // declarator in this declaration gets appended), and those from this
+    // declarator alone.
+    if (auto *FD = dyn_cast_if_present<FieldDecl>(Field);
+        FD && CurRecordLateParsedTypeAttrs) {
+      unsigned Size = CurRecordLateParsedTypeAttrs->size();
+      assert(Size >= DeclMark && DeclMark >= AfterDeclSpecMark &&
+             AfterDeclSpecMark >= DeclSpecMark &&
+             "late-parsed type attribute list must only grow");
+      auto Attach = [&](unsigned First, unsigned Last) {
+        for (unsigned I = First; I != Last; ++I)
+          (*CurRecordLateParsedTypeAttrs)[I]->addDecl(FD);
+      };
+      Attach(DeclSpecMark, AfterDeclSpecMark);
+      Attach(DeclMark, Size);
+    }
+
     // If we don't have a comma, it is either the end of the list (a ';')
     // or an error, bail out.
     if (!TryConsumeToken(tok::comma, CommaLoc))
@@ -4897,9 +4971,6 @@ ParsedAttributes Parser::ParseLexedAttributeTokens(LateParsedAttribute &LPA) {
 
 void Parser::ParseLexedTypeAttribute(LateParsedTypeAttribute &LA,
                                      ParsedAttributes &OutAttrs) {
-  assert(LA.Decls.size() <= 1 &&
-         "late field attribute expects to have at most one declaration.");
-
   ParsedAttributes Attrs = ParseLexedAttributeTokens(LA);
   OutAttrs.takeAllAppendingFrom(Attrs);
 }
@@ -5020,6 +5091,14 @@ void Parser::ParseStructUnionBody(SourceLocation RecordLoc,
   LateParsedAttrList LateFieldAttrs(/*PSoon=*/true,
                                     /*LateAttrParseExperimentalExtOnly=*/true);
 
+  // Pending late-parsed type attributes for this record, populated as its
+  // fields are parsed and drained at the closing brace. Exposed to nested
+  // bodies so an anonymous nested record can hand its own up to us;
+  // `Enclosing.get()` is our caller's list, or null for the outermost record.
+  SmallVector<LateParsedTypeAttribute *, 2> LateTypeAttrs;
+  llvm::SaveAndRestore<SmallVectorImpl<LateParsedTypeAttribute *> *> Enclosing(
+      CurRecordLateParsedTypeAttrs, &LateTypeAttrs);
+
   // While we still have something to read, read the declarations in the struct.
   while (!tryParseMisplacedModuleImport() && Tok.isNot(tok::r_brace) &&
          Tok.isNot(tok::eof)) {
@@ -5126,15 +5205,49 @@ void Parser::ParseStructUnionBody(SourceLocation RecordLoc,
   ParsedAttributes attrs(AttrFactory);
   // If attributes exist after struct contents, parse them.
   MaybeParseGNUAttributes(attrs, &LateFieldAttrs);
-
   SmallVector<Decl *, 32> FieldDecls(TagDecl->fields());
 
   Actions.ActOnFields(getCurScope(), RecordLoc, TagDecl, FieldDecls,
                       T.getOpenLocation(), T.getCloseLocation(), attrs);
 
   // Late parse field attributes if necessary.
+  //
+  // Late-parsed type attributes are owned by CompleteLateParsedTypeAttributes
+  // via the record's side list, which parses their tokens and deletes them.
+  // They are only in this generic list to be routed to type construction; if
+  // any remain (e.g. a type attribute that wasn't moved into a DeclSpec /
+  // DeclaratorChunk), drop them here so ParseLexedAttributeList doesn't parse
+  // and free them a second time.
+  llvm::erase_if(LateFieldAttrs, [](LateParsedAttribute *LA) {
+    return isa<LateParsedTypeAttribute>(LA);
+  });
   ParseLexedAttributeList(LateFieldAttrs, /*D=*/nullptr, /*EnterScope=*/false,
                           /*OnDefinition=*/false);
+
+  // Resolve late-parsed type attributes while this record's fields are still in
+  // scope. A truly anonymous record can't do that yet — its count may live in
+  // the enclosing record and only becomes visible once its members are
+  // flattened in — so it hands its pending attributes up instead.
+  //
+  // `isAnonymousStructOrUnion()` isn't set until the enclosing context sees
+  // whether a declarator follows, which happens after we return. Determine it
+  // the way the parser can: no tag name and no declarator after the body. Any
+  // attribute-specifiers between `}` and the `;`/declarator are skipped with a
+  // reverting tentative parse, so a trailing `[[...]]` / `__attribute__` etc.
+  // doesn't defeat the check.
+  if (getLangOpts().ExperimentalLateParseAttributes &&
+      !LateTypeAttrs.empty()) {
+    bool IsAnonymous = false;
+    if (!TagDecl->getIdentifier()) {
+      TentativeParsingAction TPA(*this);
+      IsAnonymous = TrySkipAttributes() && Tok.is(tok::semi);
+      TPA.Revert();
+    }
+    if (IsAnonymous && Enclosing.get())
+      llvm::append_range(*Enclosing.get(), LateTypeAttrs);
+    else
+      CompleteLateParsedTypeAttributes(LateTypeAttrs);
+  }
   StructScope.Exit();
   Actions.ActOnTagFinishDefinition(getCurScope(), TagDecl, T.getRange());
 }
@@ -6467,7 +6580,9 @@ void Parser::ParseTypeQualifierListOpt(
       // recovery is graceful.
       if (AttrReqs & AR_GNUAttributesParsed ||
           AttrReqs & AR_GNUAttributesParsedAndRejected) {
-        ParseGNUAttributes(DS.getAttributes());
+
+        // FIXME: Late parse only when some flag is set.
+        ParseGNUAttributes(DS.getAttributes(), LateAttrs);
         continue; // do *not* consume the next token!
       }
       // otherwise, FALL THROUGH!
@@ -6627,6 +6742,8 @@ void Parser::ParseDeclaratorInternal(Declarator &D,
     DeclSpec DS(AttrFactory);
     ParseTypeQualifierListOpt(DS);
 
+    assert(DS.getLateAttributes().empty());
+
     D.AddTypeInfo(
         DeclaratorChunk::getPipe(DS.getTypeQualifiers(), DS.getPipeLoc()),
         std::move(DS.getAttributes()), SourceLocation());
@@ -6654,26 +6771,48 @@ void Parser::ParseDeclaratorInternal(Declarator &D,
                     ((D.getContext() != DeclaratorContext::CXXNew)
                          ? AR_GNUAttributesParsed
                          : AR_GNUAttributesParsedAndRejected);
+
+    // Late-parsed type attributes apply to members and function parameters,
+    // not variables. Completion is driven by the enclosing record
+    // (CompleteLateParsedTypeAttributes), so only late-parse when there is one:
+    // a free-function prototype (e.g. `void f(int *__counted_by(n), int n)`)
+    // has no record to complete into, and late-parsing there would leave a
+    // CountAttributedType with a null count in the AST. Such parameters fall
+    // back to eager handling instead. A function-pointer parameter inside a
+    // struct field is still late-parsed, since that record completes it.
+    bool LateParsingContext = (D.getContext() == DeclaratorContext::Member ||
+                               D.getContext() == DeclaratorContext::Prototype) &&
+                              CurRecordLateParsedTypeAttrs != nullptr;
+
+    // No guard on ExperimentalLateParseAttributes is needed here;
+    // DS.getLateAttributes() already initializes with
+    // LateAttrParseExperimentalExtOnly.
+    LateParsedAttrList *LateAttrs =
+        LateParsingContext ? &DS.getLateAttributes() : nullptr;
+
     ParseTypeQualifierListOpt(DS, Reqs, /*AtomicOrPtrauthAllowed=*/true,
-                              !D.mayOmitIdentifier());
+                              !D.mayOmitIdentifier(), {}, LateAttrs);
     D.ExtendWithDeclSpec(DS);
 
     // Recursively parse the declarator.
     Actions.runWithSufficientStackSpace(
         D.getBeginLoc(), [&] { ParseDeclaratorInternal(D, DirectDeclParser); });
-    if (Kind == tok::star)
+    if (Kind == tok::star) {
       // Remember that we parsed a pointer type, and remember the type-quals.
       D.AddTypeInfo(DeclaratorChunk::getPointer(
                         DS.getTypeQualifiers(), Loc, DS.getConstSpecLoc(),
                         DS.getVolatileSpecLoc(), DS.getRestrictSpecLoc(),
                         DS.getAtomicSpecLoc(), DS.getUnalignedSpecLoc(),
                         DS.getOverflowBehaviorLoc(), DS.isWrapSpecified()),
-                    std::move(DS.getAttributes()), SourceLocation());
-    else
+ ...
[truncated]

``````````

</details>


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


More information about the llvm-branch-commits mailing list