[clang] [clang][ThreadSafety] Instantiate a parameter's attribute naming a later parameter (PR #232784)

Jameson Nash via cfe-commits cfe-commits at lists.llvm.org
Fri Oct 16 07:52:50 PDT 2026


https://github.com/vtjnash created https://github.com/llvm/llvm-project/pull/232784

Under -fexperimental-late-parse-attributes, an attribute on a parameter may name a parameter declared after it:

  template <class T> void f(void (*release)(T) RELEASE(mu), Mutex *mu);

A parameter's attributes were instantiated as the parameter was, before any later one, so instantiating this asserted in
LocalInstantiationScope::findInstantiationOf, and the late parsing change rejected it in a template. Defer a parameter's late-parsed attributes, with the mechanism a class uses for its members, until every parameter of the prototype is instantiated in
TransformFunctionTypeParams. A function whose type needs no substitution instantiated its parameters one by one instead; route it through SubstParmTypes, so there is one place that defers. SubstParmTypes now takes whether to evaluate constraints, which SubstFunctionType passes in both of its calls, so a typedef'd function type also follows the instantiator's setting instead of always evaluating them. With that, remove the error for a later parameter; an attribute in a template still cannot name a pointee's parameter.

Assisted-by: Claude (Opus 5.5, Fable 5.1)

>From 9f4640284de97bede2711ac48a3efcc537cc6a79 Mon Sep 17 00:00:00 2001
From: Jameson Nash <vtjnash at gmail.com>
Date: Thu, 24 Sep 2026 16:45:42 +0000
Subject: [PATCH] [clang][ThreadSafety] Instantiate a parameter's attribute
 naming a later parameter

Under -fexperimental-late-parse-attributes, an attribute on a parameter
may name a parameter declared after it:

  template <class T> void f(void (*release)(T) RELEASE(mu), Mutex *mu);

A parameter's attributes were instantiated as the parameter was, before
any later one, so instantiating this asserted in
LocalInstantiationScope::findInstantiationOf, and the late parsing
change rejected it in a template. Defer a parameter's late-parsed
attributes, with the mechanism a class uses for its members, until every
parameter of the prototype is instantiated in
TransformFunctionTypeParams. A function whose type needs no substitution
instantiated its parameters one by one instead; route it through
SubstParmTypes, so there is one place that defers. SubstParmTypes now
takes whether to evaluate constraints, which SubstFunctionType passes in
both of its calls, so a typedef'd function type also follows the
instantiator's setting instead of always evaluating them. With that,
remove the error for a later parameter; an attribute in a template still
cannot name a pointee's parameter.

Assisted-by: Claude (Opus 5.5, Fable 5.1)
---
 .../clang/Basic/DiagnosticParseKinds.td       |   3 +-
 clang/include/clang/Parse/Parser.h            |   1 -
 clang/include/clang/Sema/Sema.h               |  47 +++++---
 clang/include/clang/Sema/Template.h           |   3 +-
 clang/lib/Parse/ParseCXXInlineMethods.cpp     |  19 +---
 clang/lib/Sema/SemaTemplateInstantiate.cpp    | 103 +++++++++++++-----
 .../lib/Sema/SemaTemplateInstantiateDecl.cpp  |  99 +++++++++++------
 ...read-safety-late-parsing-instantiation.cpp |  49 +++++++++
 .../warn-thread-safety-late-parsing.cpp       |  32 +++---
 9 files changed, 243 insertions(+), 113 deletions(-)
 create mode 100644 clang/test/SemaCXX/warn-thread-safety-late-parsing-instantiation.cpp

diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td
index 7665688686b5d2..f8a3c74f2c2cd2 100644
--- a/clang/include/clang/Basic/DiagnosticParseKinds.td
+++ b/clang/include/clang/Basic/DiagnosticParseKinds.td
@@ -325,8 +325,7 @@ def warn_attribute_no_decl : Warning<
   "attribute %0 ignored, because it is not attached to a declaration">,
   InGroup<IgnoredAttributes>;
 def err_late_attribute_param_in_template : Error<
-  "%0 attribute in a template cannot name "
-  "%select{pointee function parameter|later parameter}1 %2">;
+  "%0 attribute in a template cannot name pointee function parameter %1">;
 def err_ms_attributes_not_enabled : Error<
   "'__declspec' attributes are not enabled; use '-fdeclspec' or "
   "'-fms-extensions' to enable support for __declspec attributes">;
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index 9a1abd4f3872de..a742f826187281 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -1552,7 +1552,6 @@ class Parser : public CodeCompletionHandler {
   /// Diagnose a late-parsed attribute naming a parameter that it cannot, and
   /// return true if it must be dropped.
   bool checkLateAttributeParamRefs(const LateParsedAttribute &LPA,
-                                   const Decl *D,
                                    ArrayRef<const DeclRefExpr *> ParamRefs,
                                    bool ReenteredProtoParams);
 
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 865defa98a1b33..045f00e9cf6fec 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -13951,21 +13951,43 @@ class Sema final : public SemaBase {
                           FunctionProtoType::ExceptionSpecInfo &ESI,
                           SmallVectorImpl<QualType> &ExceptionStorage,
                           const MultiLevelTemplateArgumentList &Args);
+  /// An attribute whose instantiation is deferred, together with the
+  /// instantiation scope to instantiate it in.
+  struct LateInstantiatedAttribute {
+    const Attr *TmplAttr;
+    LocalInstantiationScope *Scope;
+    Decl *NewDecl;
+
+    LateInstantiatedAttribute(const Attr *A, LocalInstantiationScope *S,
+                              Decl *D)
+        : TmplAttr(A), Scope(S), NewDecl(D) {}
+  };
+  typedef SmallVector<LateInstantiatedAttribute, 1> LateInstantiatedAttrVec;
+
+  /// Substitute into a function parameter. If \p LateAttrs is given, its
+  /// late-parsed attributes are deferred there, since they may name a
+  /// parameter that is not substituted yet; see \ref InstantiateLateAttrs.
   ParmVarDecl *
   SubstParmVarDecl(ParmVarDecl *D,
                    const MultiLevelTemplateArgumentList &TemplateArgs,
                    int indexAdjustment, UnsignedOrNone NumExpansions,
-                   bool ExpectParameterPack, bool EvaluateConstraints = true);
+                   bool ExpectParameterPack, bool EvaluateConstraints = true,
+                   LateInstantiatedAttrVec *LateAttrs = nullptr);
 
   /// Substitute the given template arguments into the given set of
   /// parameters, producing the set of parameter types that would be generated
-  /// from such a substitution.
+  /// from such a substitution. If there is a parameter with no ParmVarDecl to
+  /// substitute (a null entry of \p Params, as from a typedef'd function
+  /// type), its type needs to come from \p OldParamTypes instead, which must
+  /// then be passed, and \p OutParams gets null for it.
   bool SubstParmTypes(SourceLocation Loc, ArrayRef<ParmVarDecl *> Params,
                       const FunctionProtoType::ExtParameterInfo *ExtParamInfos,
                       const MultiLevelTemplateArgumentList &TemplateArgs,
                       SmallVectorImpl<QualType> &ParamTypes,
                       SmallVectorImpl<ParmVarDecl *> *OutParams,
-                      ExtParameterInfoBuilder &ParamInfos);
+                      ExtParameterInfoBuilder &ParamInfos,
+                      const QualType *OldParamTypes = nullptr,
+                      bool EvaluateConstraints = true);
 
   /// Substitute the given template arguments into the default argument.
   bool SubstDefaultArgument(SourceLocation Loc, ParmVarDecl *Param,
@@ -14317,17 +14339,6 @@ class Sema final : public SemaBase {
   ExplicitSpecifier instantiateExplicitSpecifier(
       const MultiLevelTemplateArgumentList &TemplateArgs, ExplicitSpecifier ES);
 
-  struct LateInstantiatedAttribute {
-    const Attr *TmplAttr;
-    LocalInstantiationScope *Scope;
-    Decl *NewDecl;
-
-    LateInstantiatedAttribute(const Attr *A, LocalInstantiationScope *S,
-                              Decl *D)
-        : TmplAttr(A), Scope(S), NewDecl(D) {}
-  };
-  typedef SmallVector<LateInstantiatedAttribute, 1> LateInstantiatedAttrVec;
-
   /// Recheck instantiated thread-safety attributes that could not be validated
   /// on the dependent pattern declaration.
   bool checkInstantiatedThreadSafetyAttrs(const Decl *D, const Attr *A);
@@ -14337,6 +14348,14 @@ class Sema final : public SemaBase {
                         LateInstantiatedAttrVec *LateAttrs = nullptr,
                         LocalInstantiationScope *OuterMostScope = nullptr);
 
+  /// Instantiate the attributes that \ref InstantiateAttrs deferred to \p
+  /// LateAttrs, and attach them to their declarations. They are instantiated
+  /// in the scopes saved with them, which extend \p OuterMostScope, the
+  /// current scope.
+  void InstantiateLateAttrs(const MultiLevelTemplateArgumentList &TemplateArgs,
+                            LateInstantiatedAttrVec &LateAttrs,
+                            LocalInstantiationScope *OuterMostScope);
+
   /// Update instantiation attributes after template was late parsed.
   ///
   /// Some attributes are evaluated based on the body of template. If it is
diff --git a/clang/include/clang/Sema/Template.h b/clang/include/clang/Sema/Template.h
index 50e950e56c6cac..bc84ca45348ccb 100644
--- a/clang/include/clang/Sema/Template.h
+++ b/clang/include/clang/Sema/Template.h
@@ -516,7 +516,8 @@ enum class TemplateSubstitutionKind : char {
     /// given outermost scope.
     static void deleteScopes(LocalInstantiationScope *Scope,
                              LocalInstantiationScope *Outermost) {
-      while (Scope && Scope != Outermost) {
+      while (Scope != Outermost) {
+        assert(Scope && "scope does not extend the outermost scope");
         LocalInstantiationScope *Out = Scope->Outer;
         delete Scope;
         Scope = Out;
diff --git a/clang/lib/Parse/ParseCXXInlineMethods.cpp b/clang/lib/Parse/ParseCXXInlineMethods.cpp
index 6b478ec2e36f6d..a86f628ade4c57 100644
--- a/clang/lib/Parse/ParseCXXInlineMethods.cpp
+++ b/clang/lib/Parse/ParseCXXInlineMethods.cpp
@@ -723,8 +723,8 @@ static void collectParamRefs(const ParsedAttributes &Attrs,
 }
 
 bool Parser::checkLateAttributeParamRefs(
-    const LateParsedAttribute &LPA, const Decl *D,
-    ArrayRef<const DeclRefExpr *> ParamRefs, bool ReenteredProtoParams) {
+    const LateParsedAttribute &LPA, ArrayRef<const DeclRefExpr *> ParamRefs,
+    bool ReenteredProtoParams) {
   auto IsPointeeParam = [&](const ParmVarDecl *PVD) {
     return ReenteredProtoParams && llvm::is_contained(LPA.ProtoParams, PVD);
   };
@@ -753,24 +753,17 @@ bool Parser::checkLateAttributeParamRefs(
 
   // A name binds the same way in a template, but instantiation cannot yet map a
   // pointee's parameters, which are instantiated with the declaration's type
-  // and not kept, nor a parameter declared after the one the attribute is on,
-  // which is instantiated after that parameter's attributes. Reject the
-  // attribute rather than instantiate it wrongly.
+  // and not kept. Reject the attribute rather than instantiate it wrongly.
   // FIXME: Map these parameters during template instantiation.
   if (Actions.CurContext->isDependentContext() ||
       getCurScope()->getTemplateParamParent() ||
       Actions.getCurGenericLambda()) {
-    const auto *DParam = dyn_cast_or_null<ParmVarDecl>(D);
     for (const DeclRefExpr *DRE : ParamRefs) {
       const auto *PVD = cast<ParmVarDecl>(DRE->getDecl());
-      bool Pointee = IsPointeeParam(PVD);
-      if (!Pointee &&
-          !(DParam &&
-            DParam->getFunctionScopeDepth() == PVD->getFunctionScopeDepth() &&
-            DParam->getFunctionScopeIndex() < PVD->getFunctionScopeIndex()))
+      if (!IsPointeeParam(PVD))
         continue;
       Diag(DRE->getLocation(), diag::err_late_attribute_param_in_template)
-          << &LPA.AttrName << !Pointee << PVD;
+          << &LPA.AttrName << PVD;
       return true;
     }
   }
@@ -831,7 +824,7 @@ void Parser::ParseLexedAttribute(LateParsedAttribute &LPA, bool EnterScope,
     SmallVector<const DeclRefExpr *, 4> ParamRefs;
     collectParamRefs(Parsed, ParamRefs);
     if (!ParamRefs.empty() &&
-        checkLateAttributeParamRefs(LPA, D, ParamRefs, HasProtoParams))
+        checkLateAttributeParamRefs(LPA, ParamRefs, HasProtoParams))
       Parsed.clear();
     Attrs.takeAllAppendingFrom(Parsed);
 
diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp
index 036642f22bd8cb..f173931f60de44 100644
--- a/clang/lib/Sema/SemaTemplateInstantiate.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp
@@ -1327,6 +1327,11 @@ namespace {
     llvm::DenseMap<llvm::FoldingSetNodeID, TemplateArgumentLoc>
         *CurrentCachedTemplateArgs = nullptr;
 
+    // While the parameters of a prototype are being substituted, the vector is
+    // populated with the late-parsed attributes, and the scope that maps them.
+    Sema::LateInstantiatedAttrVec *LateParmAttrs = nullptr;
+    LocalInstantiationScope *LateParmAttrScope = nullptr;
+
     bool instantiateMissingDeclsToScopeForConcepts(Decl *D);
 
   public:
@@ -1731,6 +1736,15 @@ namespace {
                                             UnsignedOrNone NumExpansions,
                                             bool ExpectParameterPack);
 
+    using inherited::TransformFunctionTypeParams;
+    bool TransformFunctionTypeParams(
+        SourceLocation Loc, ArrayRef<ParmVarDecl *> Params,
+        const QualType *ParamTypes,
+        const FunctionProtoType::ExtParameterInfo *ParamInfos,
+        SmallVectorImpl<QualType> &PTypes,
+        SmallVectorImpl<ParmVarDecl *> *PVars,
+        Sema::ExtParameterInfoBuilder &PInfos, unsigned *LastParamTransformed);
+
     using inherited::TransformTemplateTypeParmType;
     /// Transforms a template type parameter type by performing
     /// substitution of the corresponding template type argument.
@@ -2507,14 +2521,54 @@ QualType TemplateInstantiator::TransformFunctionProtoType(TypeLocBuilder &TLB,
 ParmVarDecl *TemplateInstantiator::TransformFunctionTypeParam(
     ParmVarDecl *OldParm, int indexAdjustment, UnsignedOrNone NumExpansions,
     bool ExpectParameterPack) {
+  // Defer the late-parsed attributes of a parameter of the prototype being
+  // transformed, when in its scope. A parameter is otherwise transformed on its
+  // own only on demand, for a constraint that names it (see
+  // instantiateMissingDeclsToScopeForConcepts). Its attributes are then
+  // instantiated at once, and a later parameter they name is instantiated on
+  // demand as well.
+  Sema::LateInstantiatedAttrVec *LateAttrs = nullptr;
+  if (LateParmAttrs && SemaRef.CurrentInstantiationScope == LateParmAttrScope)
+    LateAttrs = LateParmAttrs;
+  else
+    assert((SemaRef.inConstraintSubstitution() ||
+            SemaRef.inParameterMappingSubstitution()) &&
+           "parameter transformed outside its prototype");
   auto NewParm = SemaRef.SubstParmVarDecl(
       OldParm, TemplateArgs, indexAdjustment, NumExpansions,
-      ExpectParameterPack, EvaluateConstraints);
+      ExpectParameterPack, EvaluateConstraints, LateAttrs);
   if (NewParm && SemaRef.getLangOpts().OpenCL)
     SemaRef.deduceOpenCLAddressSpace(NewParm);
   return NewParm;
 }
 
+bool TemplateInstantiator::TransformFunctionTypeParams(
+    SourceLocation Loc, ArrayRef<ParmVarDecl *> Params,
+    const QualType *ParamTypes,
+    const FunctionProtoType::ExtParameterInfo *ParamInfos,
+    SmallVectorImpl<QualType> &PTypes, SmallVectorImpl<ParmVarDecl *> *PVars,
+    Sema::ExtParameterInfoBuilder &PInfos, unsigned *LastParamTransformed) {
+  // A late-parsed attribute on a parameter may name a parameter declared after
+  // it, so transform all parameters then instantiate all late attributes.
+  Sema::LateInstantiatedAttrVec LateAttrs;
+  LocalInstantiationScope *Scope = SemaRef.CurrentInstantiationScope;
+  llvm::SaveAndRestore SaveLateAttrs(LateParmAttrs, &LateAttrs);
+  llvm::SaveAndRestore SaveLateScope(LateParmAttrScope, Scope);
+  bool Failed = inherited::TransformFunctionTypeParams(
+      Loc, Params, ParamTypes, ParamInfos, PTypes, PVars, PInfos,
+      LastParamTransformed);
+  // The attributes were deferred in this scope itself, so there is nothing to
+  // free on failure.
+  assert(
+      llvm::all_of(LateAttrs, [&](const Sema::LateInstantiatedAttribute &LA) {
+        return LA.Scope == Scope;
+      }));
+  if (Failed)
+    return true;
+  SemaRef.InstantiateLateAttrs(TemplateArgs, LateAttrs, Scope);
+  return false;
+}
+
 QualType TemplateInstantiator::BuildSubstTemplateTypeParmType(
     TypeLocBuilder &TLB, bool SuppressObjCLifetime, bool Final,
     Decl *AssociatedDecl, unsigned Index, UnsignedOrNone PackIndex,
@@ -3166,11 +3220,10 @@ bool Sema::SubstTypeConstraint(
           : SourceLocation());
 }
 
-ParmVarDecl *
-Sema::SubstParmVarDecl(ParmVarDecl *OldParm,
-                       const MultiLevelTemplateArgumentList &TemplateArgs,
-                       int indexAdjustment, UnsignedOrNone NumExpansions,
-                       bool ExpectParameterPack, bool EvaluateConstraint) {
+ParmVarDecl *Sema::SubstParmVarDecl(
+    ParmVarDecl *OldParm, const MultiLevelTemplateArgumentList &TemplateArgs,
+    int indexAdjustment, UnsignedOrNone NumExpansions, bool ExpectParameterPack,
+    bool EvaluateConstraint, LateInstantiatedAttrVec *LateAttrs) {
   TypeSourceInfo *OldTSI = OldParm->getTypeSourceInfo();
   TypeSourceInfo *NewTSI = nullptr;
 
@@ -3281,7 +3334,8 @@ Sema::SubstParmVarDecl(ParmVarDecl *OldParm,
   NewParm->setScopeInfo(OldParm->getFunctionScopeDepth(),
                         OldParm->getFunctionScopeIndex() + indexAdjustment);
 
-  InstantiateAttrs(TemplateArgs, OldParm, NewParm);
+  InstantiateAttrs(TemplateArgs, OldParm, NewParm, LateAttrs,
+                   CurrentInstantiationScope);
 
   NewParm->deduceParmAddressSpace(Context);
 
@@ -3294,11 +3348,18 @@ bool Sema::SubstParmTypes(
     const MultiLevelTemplateArgumentList &TemplateArgs,
     SmallVectorImpl<QualType> &ParamTypes,
     SmallVectorImpl<ParmVarDecl *> *OutParams,
-    ExtParameterInfoBuilder &ParamInfos) {
+    ExtParameterInfoBuilder &ParamInfos, const QualType *OldParamTypes,
+    bool EvaluateConstraints) {
+  // If there is a parameter with no ParmVarDecl to substitute, its type needs
+  // to come from OldParamTypes instead.
+  assert((OldParamTypes || !llvm::is_contained(Params, nullptr)) &&
+         "a parameter with no ParmVarDecl needs OldParamTypes to be passed");
   TemplateInstantiator Instantiator(*this, TemplateArgs, Loc,
                                     DeclarationName());
-  return Instantiator.TransformFunctionTypeParams(
-      Loc, Params, nullptr, ExtParamInfos, ParamTypes, OutParams, ParamInfos);
+  Instantiator.setEvaluateConstraints(EvaluateConstraints);
+  return Instantiator.TransformFunctionTypeParams(Loc, Params, OldParamTypes,
+                                                  ExtParamInfos, ParamTypes,
+                                                  OutParams, ParamInfos);
 }
 
 bool Sema::SubstDefaultArgument(
@@ -3742,27 +3803,9 @@ bool Sema::InstantiateClassImpl(
     ActOnFinishCXXNonNestedClass();
 
   // Instantiate late parsed attributes, and attach them to their decls.
-  // See Sema::InstantiateAttrs
-  for (LateInstantiatedAttrVec::iterator I = LateAttrs.begin(),
-       E = LateAttrs.end(); I != E; ++I) {
-    assert(CurrentInstantiationScope == Instantiator.getStartingScope());
-    CurrentInstantiationScope = I->Scope;
-
-    // Allow 'this' within late-parsed attributes.
-    auto *ND = cast<NamedDecl>(I->NewDecl);
-    auto *ThisContext = dyn_cast_or_null<CXXRecordDecl>(ND->getDeclContext());
-    CXXThisScopeRAII ThisScope(*this, ThisContext, Qualifiers(),
-                               ND->isCXXInstanceMember());
-
-    Attr *NewAttr =
-      instantiateTemplateAttribute(I->TmplAttr, Context, *this, TemplateArgs);
-    if (NewAttr && checkInstantiatedThreadSafetyAttrs(I->NewDecl, NewAttr))
-      I->NewDecl->addAttr(NewAttr);
-    LocalInstantiationScope::deleteScopes(I->Scope,
-                                          Instantiator.getStartingScope());
-  }
+  InstantiateLateAttrs(TemplateArgs, LateAttrs,
+                       Instantiator.getStartingScope());
   Instantiator.disableLateAttributeInstantiation();
-  LateAttrs.clear();
 
   ActOnFinishDelayedMemberInitializers(Instantiation);
 
diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index b4881d0c6db5cb..eb612f4f35e193 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -876,6 +876,24 @@ attrToRetainOwnershipKind(const Attr *A) {
   }
 }
 
+/// Instantiate \p TmplAttr for \p New in the current instantiation scope and
+/// attach it, if it is still valid there.
+static void
+instantiateAttrOnDecl(Sema &S,
+                      const MultiLevelTemplateArgumentList &TemplateArgs,
+                      const Attr *TmplAttr, Decl *New) {
+  // Allow 'this' within late-parsed attributes.
+  auto *ND = cast<NamedDecl>(New);
+  auto *ThisContext = dyn_cast_or_null<CXXRecordDecl>(ND->getDeclContext());
+  Sema::CXXThisScopeRAII ThisScope(S, ThisContext, Qualifiers(),
+                                   ND->isCXXInstanceMember());
+
+  Attr *NewAttr =
+      sema::instantiateTemplateAttribute(TmplAttr, S.Context, S, TemplateArgs);
+  if (NewAttr && S.checkInstantiatedThreadSafetyAttrs(New, NewAttr))
+    New->addAttr(NewAttr);
+}
+
 // Implementation is down with the rest of the OpenACC Decl instantiations.
 static void instantiateDependentOpenACCRoutineDeclAttr(
     Sema &S, const MultiLevelTemplateArgumentList &TemplateArgs,
@@ -1068,28 +1086,37 @@ void Sema::InstantiateAttrs(const MultiLevelTemplateArgumentList &TemplateArgs,
 
     assert(!TmplAttr->isPackExpansion());
     if (TmplAttr->isLateParsed() && LateAttrs) {
-      // Late parsed attributes must be instantiated and attached after the
-      // enclosing class has been instantiated.  See Sema::InstantiateClass.
-      LocalInstantiationScope *Saved = nullptr;
-      if (CurrentInstantiationScope)
-        Saved = CurrentInstantiationScope->cloneScopes(OuterMostScope);
+      // A late-parsed attribute may name a declaration that is instantiated
+      // after the one it is on: a class member's names a later member, and a
+      // parameter's a later parameter. Defer it, with the scope to instantiate
+      // it in, until they all are; see InstantiateLateAttrs.
+      assert(CurrentInstantiationScope && "no scope to defer the attribute in");
+      LocalInstantiationScope *Saved = CurrentInstantiationScope->cloneScopes(OuterMostScope);
       LateAttrs->push_back(LateInstantiatedAttribute(TmplAttr, Saved, New));
     } else {
-      // Allow 'this' within late-parsed attributes.
-      auto *ND = cast<NamedDecl>(New);
-      auto *ThisContext = dyn_cast_or_null<CXXRecordDecl>(ND->getDeclContext());
-      CXXThisScopeRAII ThisScope(*this, ThisContext, Qualifiers(),
-                                 ND->isCXXInstanceMember());
-
-      Attr *NewAttr = sema::instantiateTemplateAttribute(TmplAttr, Context,
-                                                         *this, TemplateArgs);
-      if (NewAttr && isRelevantAttr(*this, New, TmplAttr) &&
-          checkInstantiatedThreadSafetyAttrs(New, NewAttr))
-        New->addAttr(NewAttr);
+      instantiateAttrOnDecl(*this, TemplateArgs, TmplAttr, New);
     }
   }
 }
 
+void Sema::InstantiateLateAttrs(
+    const MultiLevelTemplateArgumentList &TemplateArgs,
+    LateInstantiatedAttrVec &LateAttrs,
+    LocalInstantiationScope *OuterMostScope) {
+  assert(
+      CurrentInstantiationScope == OuterMostScope &&
+      "late attributes instantiated outside the scope they were deferred in");
+  for (const LateInstantiatedAttribute &LA : LateAttrs) {
+    CurrentInstantiationScope = LA.Scope;
+    instantiateAttrOnDecl(*this, TemplateArgs, LA.TmplAttr, LA.NewDecl);
+    // Exiting the saved scopes restores the outermost one.
+    LocalInstantiationScope::deleteScopes(LA.Scope, OuterMostScope);
+    assert(CurrentInstantiationScope == OuterMostScope &&
+           "deferred scope did not extend the outermost scope");
+  }
+  LateAttrs.clear();
+}
+
 void Sema::updateAttrsForLateParsedTemplate(const Decl *Pattern, Decl *Inst) {
   for (const auto *Attr : Pattern->attrs()) {
     if (auto *A = dyn_cast<StrictFPAttr>(Attr)) {
@@ -5493,23 +5520,25 @@ TypeSourceInfo *TemplateDeclInstantiator::SubstFunctionType(
       // the function parameters themselves.
       const FunctionProtoType *OldProto =
           cast<FunctionProtoType>(OldProtoLoc.getType());
-      for (unsigned i = 0, i_end = OldProtoLoc.getNumParams(); i != i_end;
-           ++i) {
-        ParmVarDecl *OldParam = OldProtoLoc.getParam(i);
-        if (!OldParam) {
-          Params.push_back(SemaRef.BuildParmVarDeclForTypedef(
-              D, D->getLocation(), OldProto->getParamType(i)));
-          continue;
-        }
-
-        ParmVarDecl *Parm = SemaRef.SubstParmVarDecl(
-            OldParam, TemplateArgs, /*indexAdjustment=*/0,
-            /*NumExpansions=*/std::nullopt,
-            /*ExpectParameterPack=*/false, EvaluateConstraints);
-        if (!Parm)
-          return nullptr;
-        Params.push_back(Parm);
-      }
+      SmallVector<QualType, 4> ParamTypes;
+      Sema::ExtParameterInfoBuilder ExtParamInfos;
+      if (SemaRef.SubstParmTypes(D->getLocation(), OldProtoLoc.getParams(),
+                                 nullptr, TemplateArgs, ParamTypes, &Params,
+                                 ExtParamInfos, OldProto->param_type_begin(),
+                                 EvaluateConstraints))
+        return nullptr;
+      // Declare a parameter that was only a type in the prototype. A function
+      // declared through a typedef with an attribute that rewrites the
+      // function type in place, such as noreturn, gets a prototype whose
+      // parameters have no ParmVarDecl, so no attribute of the typedef's
+      // parameters reaches it; see TypeSpecLocFiller in SemaType.
+      // FIXME: Keep the typedef's parameters there instead. The dependent
+      // branch above returns null for such a prototype, dropping the member.
+      assert(Params.size() == ParamTypes.size());
+      for (unsigned i = 0, i_end = Params.size(); i != i_end; ++i)
+        if (!Params[i])
+          Params[i] = SemaRef.BuildParmVarDeclForTypedef(D, D->getLocation(),
+                                                         ParamTypes[i]);
     }
   } else {
     // If the type of this function, after ignoring parentheses, is not
@@ -5525,8 +5554,8 @@ TypeSourceInfo *TemplateDeclInstantiator::SubstFunctionType(
     SmallVector<QualType, 4> ParamTypes;
     Sema::ExtParameterInfoBuilder ExtParamInfos;
     if (SemaRef.SubstParmTypes(D->getLocation(), D->parameters(), nullptr,
-                               TemplateArgs, ParamTypes, &Params,
-                               ExtParamInfos))
+                               TemplateArgs, ParamTypes, &Params, ExtParamInfos,
+                               /*OldParamTypes=*/nullptr, EvaluateConstraints))
       return nullptr;
   }
 
diff --git a/clang/test/SemaCXX/warn-thread-safety-late-parsing-instantiation.cpp b/clang/test/SemaCXX/warn-thread-safety-late-parsing-instantiation.cpp
new file mode 100644
index 00000000000000..4eb5339199f7be
--- /dev/null
+++ b/clang/test/SemaCXX/warn-thread-safety-late-parsing-instantiation.cpp
@@ -0,0 +1,49 @@
+// Under -fexperimental-late-parse-attributes, an attribute on a parameter may
+// name a parameter declared later in the same prototype. In a template, it is
+// instantiated once every parameter is, so it names the instantiated one.
+//
+// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -verify -Wthread-safety -std=c++20 %s
+
+#define RELEASE(...) __attribute__((release_capability(__VA_ARGS__)))
+
+class __attribute__((capability("mutex"))) Mutex {};
+
+// Each callback releases a lock that is not held, which is reported only if
+// its attribute was instantiated naming the later parameter.
+template <typename T>
+void put_later(void (*release)(T) RELEASE(mu), Mutex *mu, T t) {
+  release(t); // expected-warning{{releasing mutex 'mu' that was not held}}
+}
+template void put_later<int>(void (*)(int), Mutex *, int); // expected-note{{in instantiation of function template specialization 'put_later<int>' requested here}}
+
+// After a parameter pack.
+template <typename... Ts>
+void put_later_pack(void (*release)(Ts...) RELEASE(mu), Mutex *mu, Ts... ts) {
+  release(ts...); // expected-warning{{releasing mutex 'mu' that was not held}}
+}
+template void put_later_pack<int, long>(void (*)(int, long), Mutex *, int, long); // expected-note{{in instantiation of function template specialization 'put_later_pack<int, long>' requested here}}
+
+template <typename T>
+struct Methods {
+  void dependent(void (*release)(T) RELEASE(mu), Mutex *mu, T t) {
+    release(t); // expected-warning{{releasing mutex 'mu' that was not held}}
+  }
+  // A prototype that does not depend on the template.
+  void independent(void (*release)(int) RELEASE(mu), Mutex *mu) {
+    release(0); // expected-warning{{releasing mutex 'mu' that was not held}}
+  }
+  template <typename U>
+  void member_template(void (*release)(U) RELEASE(mu), Mutex *mu, U u) {
+    release(u); // expected-warning{{releasing mutex 'mu' that was not held}}
+  }
+};
+template struct Methods<int>; // expected-note{{in instantiation of member function 'Methods<int>::dependent' requested here}} \
+                              // expected-note{{in instantiation of member function 'Methods<int>::independent' requested here}}
+template void Methods<int>::member_template<long>(void (*)(long), Mutex *, long); // expected-note{{in instantiation of function template specialization 'Methods<int>::member_template<long>' requested here}}
+
+auto generic_lambda = [](auto x, void (*release)(int) RELEASE(mu), Mutex *mu) {
+  release(x); // expected-warning{{releasing mutex 'mu' that was not held}}
+};
+void use_generic_lambda(void (*release)(int), Mutex *mu) {
+  generic_lambda(0, release, mu); // expected-note{{in instantiation of function template specialization}}
+}
diff --git a/clang/test/SemaCXX/warn-thread-safety-late-parsing.cpp b/clang/test/SemaCXX/warn-thread-safety-late-parsing.cpp
index 065309d6eeb0f1..7d43b51a6e9145 100644
--- a/clang/test/SemaCXX/warn-thread-safety-late-parsing.cpp
+++ b/clang/test/SemaCXX/warn-thread-safety-late-parsing.cpp
@@ -2,7 +2,7 @@
 // name a parameter declared later in the same prototype. Without it, that
 // forward reference is an error.
 //
-// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -verify=both,late -Wthread-safety -std=c++20 %s
+// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -verify=both -Wthread-safety -std=c++20 %s
 // RUN: %clang_cc1 -fsyntax-only -verify=both,early -Wthread-safety -std=c++20 %s
 
 #define RELEASE(...)  __attribute__((release_capability(__VA_ARGS__)))
@@ -68,9 +68,9 @@ struct Outer {
   static Mutex m;
 };
 
-// Template instantiation cannot yet map a pointee's parameters, or a parameter
-// declared after the one an attribute is on. A name binds the same way in a
-// template as elsewhere, but an attribute naming one of those is rejected.
+// Template instantiation cannot yet map a pointee's parameters. A name binds
+// the same way in a template as elsewhere, but an attribute naming one is
+// rejected.
 template <typename T>
 struct InTemplate {
   void (*cb)(T *h) REQUIRES(h->lock); // both-error{{'requires_capability' attribute in a template cannot name pointee function parameter 'h'}}
@@ -78,22 +78,21 @@ struct InTemplate {
   void (*shadow)(T *own) REQUIRES(own->lock); // both-error{{reference to 'own' is ambiguous}} \
                                               // both-note{{candidate found by name lookup is 'own'}}
   T *own; // both-note{{candidate found by name lookup is 'InTemplate::own'}}
-  void method(void (*release)(T) RELEASE(mu), // early-error{{use of undeclared identifier 'mu'}} \
-                                              // late-error{{'release_capability' attribute in a template cannot name later parameter 'mu'}}
+  void method(void (*release)(T) RELEASE(mu), // early-error{{use of undeclared identifier 'mu'}}
               Mutex *mu);
 };
 InTemplate<Holder> in_template;
 
 template <typename T>
-void put_later_template(void (*release)(T) RELEASE(mu), // early-error{{use of undeclared identifier 'mu'}} \
-                                                        // late-error{{'release_capability' attribute in a template cannot name later parameter 'mu'}}
+void put_later_template(void (*release)(T) RELEASE(mu), // early-error{{use of undeclared identifier 'mu'}}
                         Mutex *mu);
+void use_put_later_template(Mutex *mu) { put_later_template<int>(nullptr, mu); }
 
-auto generic_lambda = [](auto x, void (*release)(int) RELEASE(mu), // early-error{{use of undeclared identifier 'mu'}} \
-                                                                   // late-error{{'release_capability' attribute in a template cannot name later parameter 'mu'}}
+auto generic_lambda = [](auto x, void (*release)(int) RELEASE(mu), // early-error{{use of undeclared identifier 'mu'}}
                          Mutex *mu) {};
+void use_generic_lambda(Mutex *mu) { generic_lambda(0, nullptr, mu); }
 
-// An earlier parameter is instantiated first, so it can be named.
+// An earlier parameter could always be named.
 template <typename T>
 void put_earlier_template(Mutex *mu, void (*release)(T) RELEASE(mu)) {}
 void use_put_earlier_template(Mutex *mu) { put_earlier_template<int>(mu, nullptr); }
@@ -102,19 +101,18 @@ void use_put_earlier_template(Mutex *mu) { put_earlier_template<int>(mu, nullptr
 // prototype, not on how macros spell it.
 #define SWAP(a, b) b, a
 template <typename T>
-void swapped_later(SWAP(Mutex *mu, // early-error{{use of undeclared identifier 'mu'}} \
-                                   // late-error{{'release_capability' attribute in a template cannot name later parameter 'mu'}}
+void swapped_later(SWAP(Mutex *mu, // early-error{{use of undeclared identifier 'mu'}}
                         void (*release)(T) RELEASE(mu)));
+void use_swapped_later(Mutex *mu) { swapped_later<int>(nullptr, mu); }
 template <typename T>
 void swapped_earlier(SWAP(void (*release)(T) RELEASE(mu), Mutex *mu)) {}
 void use_swapped_earlier(Mutex *mu) { swapped_earlier<int>(mu, nullptr); }
 
-// Nor on how deeply its prototype is nested: a later parameter of a nested
-// prototype is rejected, and an earlier parameter of an enclosing one works.
+// Nor on how deeply its prototype is nested.
 template <typename T>
-void nested_later(void (*cb)(void (*release)(T) RELEASE(mu), // early-error{{use of undeclared identifier 'mu'}} \
-                                                             // late-error{{'release_capability' attribute in a template cannot name later parameter 'mu'}}
+void nested_later(void (*cb)(void (*release)(T) RELEASE(mu), // early-error{{use of undeclared identifier 'mu'}}
                              Mutex *mu));
+void use_nested_later(void (*cb)(void (*)(int), Mutex *)) { nested_later<int>(cb); }
 template <typename T>
 void nested_earlier(Mutex *mu, void (*cb)(void (*release)(T) RELEASE(mu))) {}
 void use_nested_earlier(Mutex *mu) { nested_earlier<int>(mu, nullptr); }



More information about the cfe-commits mailing list