Skip to content

[Clang][ItaniumMangle] Fix recursive mangling for lambda init-captures - #182667

Merged
oToToT merged 1 commit into
llvm:mainfrom
oToToT:better-mangle
Feb 27, 2026
Merged

oToToT merged 1 commit into
llvm:mainfrom
oToToT:better-mangle

Conversation

@oToToT

@oToToT oToToT commented Feb 21, 2026

Copy link
Copy Markdown
Member

[Clang][ItaniumMangle] Fix recursive mangling for lambda init-captures

Mangle computation for lambda signatures can recurse when a call operator type
references an init-capture (for example via decltype(init-capture)). In these
cases, mangling can re-enter the init-capture declaration and cycle back through
operator() mangling.

Make lambda context publication explicit and independent from numbering state,
then use that context uniformly during mangling:

  • Publish lambda ContextDecl in Sema::handleLambdaNumbering() before
    numbering, so dependent type mangling can resolve the lambda context without
    recursing through the call operator.
  • Introduce CXXRecordDecl::setLambdaContextDecl() and remove ContextDecl
    from CXXRecordDecl::LambdaNumbering.
  • Update ASTImporter to import/set lambda context separately from numbering.
  • In Itanium mangling, derive init-capture handling from context computation:
    • map local-lambda init-captures to the enclosing local context in
      getEffectiveDeclContext()
    • support init-capture variables in getClosurePrefix()
    • keep mangleLocalName() generic and rely on the computed context

Add mangling regression coverage in mangle-lambdas.cpp, including:

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Feb 21, 2026
@llvmbot

llvmbot commented Feb 21, 2026

Copy link
Copy Markdown
Member

@llvm/pr-subscribers-clang

Author: Tommy Chiang (oToToT)

Changes

During lambda numbering, ItaniumNumberingContext::getManglingNumber() computes
the lambda’s <lambda-sig> by mangling the call operator type. If that type
contains decltype(init-capture), mangling re-enters the init-capture VarDecl.
For problematic cases, this recurses back into operator() mangling.

Make init-capture handling in mangleLocalName() explicit:

  • If the init-capture belongs to a lambda that has a closure-prefix context,
    mangle it directly with mangleNestedNameWithClosurePrefix().
  • Otherwise keep local-name mangling, but for local lambdas use the parent
    local container as the function-encoding base to avoid self-recursion.

Also publish lambda ContextDecl early in Sema::handleLambdaNumbering() before
calling getManglingNumber(Method), via a dedicated
CXXRecordDecl::setLambdaContextDecl() helper. This provides the needed context
for context-sensitive mangling without publishing provisional numbering state.

Add non-local regression tests in mangle-lambdas.cpp for:

  • variable-template lambda init-capture used in decltype(x)
  • static inline member lambda init-capture used in decltype(x)

This PR


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

5 Files Affected:

  • (modified) clang/include/clang/AST/DeclCXX.h (+3)
  • (modified) clang/lib/AST/DeclCXX.cpp (+5)
  • (modified) clang/lib/AST/ItaniumMangle.cpp (+443-305)
  • (modified) clang/lib/Sema/SemaLambda.cpp (+7)
  • (modified) clang/test/CodeGenCXX/mangle-lambdas.cpp (+66)
diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h
index 5c4ad3c45da19..0c9bba17d09ef 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -1785,6 +1785,9 @@ class CXXRecordDecl : public RecordDecl {
   /// the declaration context suffices.
   Decl *getLambdaContextDecl() const;
 
+  /// Set the context declaration for a lambda class.
+  void setLambdaContextDecl(Decl *ContextDecl);
+
   /// Retrieve the index of this lambda within the context declaration returned
   /// by getLambdaContextDecl().
   unsigned getLambdaIndexInContext() const {
diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp
index c16b1bb7a3453..55c34605f7513 100644
--- a/clang/lib/AST/DeclCXX.cpp
+++ b/clang/lib/AST/DeclCXX.cpp
@@ -1831,6 +1831,11 @@ Decl *CXXRecordDecl::getLambdaContextDecl() const {
   return getLambdaData().ContextDecl.get(Source);
 }
 
+void CXXRecordDecl::setLambdaContextDecl(Decl *ContextDecl) {
+  assert(isLambda() && "Not a lambda closure type!");
+  getLambdaData().ContextDecl = ContextDecl;
+}
+
 void CXXRecordDecl::setLambdaNumbering(LambdaNumbering Numbering) {
   assert(isLambda() && "Not a lambda closure type!");
   getLambdaData().ManglingNumber = Numbering.ManglingNumber;
diff --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp
index 70acc8a78ed52..6017f3bb3cd5f 100644
--- a/clang/lib/AST/ItaniumMangle.cpp
+++ b/clang/lib/AST/ItaniumMangle.cpp
@@ -68,9 +68,9 @@ static bool isLambda(const NamedDecl *ND) {
 static const unsigned UnknownArity = ~0U;
 
 class ItaniumMangleContextImpl : public ItaniumMangleContext {
-  typedef std::pair<const DeclContext*, IdentifierInfo*> DiscriminatorKeyTy;
+  typedef std::pair<const DeclContext *, IdentifierInfo *> DiscriminatorKeyTy;
   llvm::DenseMap<DiscriminatorKeyTy, unsigned> Discriminator;
-  llvm::DenseMap<const NamedDecl*, unsigned> Uniquifier;
+  llvm::DenseMap<const NamedDecl *, unsigned> Uniquifier;
   const DiscriminatorOverrideTy DiscriminatorOverride = nullptr;
   NamespaceDecl *StdNamespace = nullptr;
 
@@ -162,7 +162,7 @@ class ItaniumMangleContextImpl : public ItaniumMangleContext {
     }
     if (discriminator == 1)
       return false;
-    disc = discriminator-2;
+    disc = discriminator - 2;
     return true;
   }
 
@@ -249,14 +249,10 @@ class CXXNameMangler {
     FunctionTypeDepthState() = default;
 
     /// The number of function types we're inside.
-    unsigned getDepth() const {
-      return Bits >> 1;
-    }
+    unsigned getDepth() const { return Bits >> 1; }
 
     /// True if we're in the return type of the innermost function type.
-    bool isInResultType() const {
-      return Bits & InResultTypeMask;
-    }
+    bool isInResultType() const { return Bits & InResultTypeMask; }
 
     FunctionTypeDepthState push() {
       FunctionTypeDepthState tmp = *this;
@@ -264,13 +260,9 @@ class CXXNameMangler {
       return tmp;
     }
 
-    void enterResultType() {
-      Bits |= InResultTypeMask;
-    }
+    void enterResultType() { Bits |= InResultTypeMask; }
 
-    void leaveResultType() {
-      Bits &= ~InResultTypeMask;
-    }
+    void leaveResultType() { Bits &= ~InResultTypeMask; }
 
     void pop(FunctionTypeDepthState saved) {
       assert(getDepth() == saved.getDepth() + 1);
@@ -334,13 +326,9 @@ class CXXNameMangler {
     }
 
     const AbiTagList &getUsedAbiTags() const { return UsedAbiTags; }
-    void setUsedAbiTags(const AbiTagList &AbiTags) {
-      UsedAbiTags = AbiTags;
-    }
+    void setUsedAbiTags(const AbiTagList &AbiTags) { UsedAbiTags = AbiTags; }
 
-    const AbiTagList &getEmittedAbiTags() const {
-      return EmittedAbiTags;
-    }
+    const AbiTagList &getEmittedAbiTags() const { return EmittedAbiTags; }
 
     const AbiTagList &getSortedUniqueUsedAbiTags() {
       llvm::sort(UsedAbiTags);
@@ -406,8 +394,7 @@ class CXXNameMangler {
       : Context(C), Out(Out_), NullOut(NullOut_), Structor(getStructor(D)),
         AbiTagsRoot(AbiTags) {
     // These can't be mangled without a ctor type or dtor type.
-    assert(!D || (!isa<CXXDestructorDecl>(D) &&
-                  !isa<CXXConstructorDecl>(D)));
+    assert(!D || (!isa<CXXDestructorDecl>(D) && !isa<CXXConstructorDecl>(D)));
   }
   CXXNameMangler(ItaniumMangleContextImpl &C, raw_ostream &Out_,
                  const CXXConstructorDecl *D, CXXCtorType Type)
@@ -434,7 +421,9 @@ class CXXNameMangler {
     NullOut = true;
   }
 
-  struct WithTemplateDepthOffset { unsigned Offset; };
+  struct WithTemplateDepthOffset {
+    unsigned Offset;
+  };
   CXXNameMangler(ItaniumMangleContextImpl &C, raw_ostream &Out,
                  WithTemplateDepthOffset Offset)
       : CXXNameMangler(C, Out) {
@@ -481,7 +470,7 @@ class CXXNameMangler {
   void addSubstitution(TemplateName Template);
   void addSubstitution(uintptr_t Ptr);
   // Destructive copy substitutions from other mangler.
-  void extendSubstitutions(CXXNameMangler* Other);
+  void extendSubstitutions(CXXNameMangler *Other);
 
   void mangleUnresolvedPrefix(NestedNameSpecifier Qualifier,
                               bool recursive = false);
@@ -513,10 +502,10 @@ class CXXNameMangler {
   void mangleRegCallName(const IdentifierInfo *II);
   void mangleDeviceStubName(const IdentifierInfo *II);
   void mangleOCLDeviceStubName(const IdentifierInfo *II);
-  void mangleSourceNameWithAbiTags(
-      const NamedDecl *ND, const AbiTagList *AdditionalAbiTags = nullptr);
-  void mangleLocalName(GlobalDecl GD,
-                       const AbiTagList *AdditionalAbiTags);
+  void
+  mangleSourceNameWithAbiTags(const NamedDecl *ND,
+                              const AbiTagList *AdditionalAbiTags = nullptr);
+  void mangleLocalName(GlobalDecl GD, const AbiTagList *AdditionalAbiTags);
   void mangleBlockForPrefix(const BlockDecl *Block);
   void mangleUnqualifiedBlock(const BlockDecl *Block);
   void mangleTemplateParamDecl(const NamedDecl *Decl);
@@ -528,16 +517,16 @@ class CXXNameMangler {
   void mangleLambda(const CXXRecordDecl *Lambda);
   void mangleNestedName(GlobalDecl GD, const DeclContext *DC,
                         const AbiTagList *AdditionalAbiTags,
-                        bool NoFunction=false);
+                        bool NoFunction = false);
   void mangleNestedName(const TemplateDecl *TD,
                         ArrayRef<TemplateArgument> Args);
   void mangleNestedNameWithClosurePrefix(GlobalDecl GD,
                                          const NamedDecl *PrefixND,
                                          const AbiTagList *AdditionalAbiTags);
   void manglePrefix(NestedNameSpecifier Qualifier);
-  void manglePrefix(const DeclContext *DC, bool NoFunction=false);
+  void manglePrefix(const DeclContext *DC, bool NoFunction = false);
   void manglePrefix(QualType type);
-  void mangleTemplatePrefix(GlobalDecl GD, bool NoFunction=false);
+  void mangleTemplatePrefix(GlobalDecl GD, bool NoFunction = false);
   void mangleTemplatePrefix(TemplateName Template);
   const NamedDecl *getClosurePrefix(const Decl *ND);
   void mangleClosurePrefix(const NamedDecl *ND, bool NoFunction = false);
@@ -545,7 +534,8 @@ class CXXNameMangler {
                                       StringRef Prefix = "");
   void mangleOperatorName(DeclarationName Name, unsigned Arity);
   void mangleOperatorName(OverloadedOperatorKind OO, unsigned Arity);
-  void mangleQualifiers(Qualifiers Quals, const DependentAddressSpaceType *DAST = nullptr);
+  void mangleQualifiers(Qualifiers Quals,
+                        const DependentAddressSpaceType *DAST = nullptr);
   void mangleRefQualifier(RefQualifierKind RefQualifier);
 
   void mangleObjCMethodName(const ObjCMethodDecl *MD);
@@ -556,7 +546,7 @@ class CXXNameMangler {
 #define TYPE(CLASS, PARENT) void mangleType(const CLASS##Type *T);
 #include "clang/AST/TypeNodes.inc"
 
-  void mangleType(const TagType*);
+  void mangleType(const TagType *);
   void mangleType(TemplateName);
   static StringRef getCallingConvQualifierName(CallingConv CC);
   void mangleExtParameterInfo(FunctionProtoType::ExtParameterInfo info);
@@ -610,8 +600,7 @@ class CXXNameMangler {
 
   void mangleFunctionParam(const ParmVarDecl *parm);
 
-  void writeAbiTags(const NamedDecl *ND,
-                    const AbiTagList *AdditionalAbiTags);
+  void writeAbiTags(const NamedDecl *ND, const AbiTagList *AdditionalAbiTags);
 
   // Returns sorted unique list of ABI tags.
   AbiTagList makeFunctionReturnTypeTags(const FunctionDecl *FD);
@@ -619,7 +608,7 @@ class CXXNameMangler {
   AbiTagList makeVariableTypeTags(const VarDecl *VD);
 };
 
-}
+} // namespace
 
 NamespaceDecl *ItaniumMangleContextImpl::getStdNamespace() {
   if (!StdNamespace) {
@@ -962,8 +951,8 @@ bool CXXNameMangler::isStdNamespace(const DeclContext *DC) {
   return isStd(cast<NamespaceDecl>(DC));
 }
 
-static const GlobalDecl
-isTemplate(GlobalDecl GD, const TemplateArgumentList *&TemplateArgs) {
+static const GlobalDecl isTemplate(GlobalDecl GD,
+                                   const TemplateArgumentList *&TemplateArgs) {
   const NamedDecl *ND = cast<NamedDecl>(GD.getDecl());
   // Check if we have a function template.
   if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(ND)) {
@@ -975,7 +964,7 @@ isTemplate(GlobalDecl GD, const TemplateArgumentList *&TemplateArgs) {
 
   // Check if we have a class template.
   if (const ClassTemplateSpecializationDecl *Spec =
-        dyn_cast<ClassTemplateSpecializationDecl>(ND)) {
+          dyn_cast<ClassTemplateSpecializationDecl>(ND)) {
     TemplateArgs = &Spec->getTemplateArgs();
     return GD.getWithDecl(Spec->getSpecializedTemplate());
   }
@@ -992,7 +981,7 @@ isTemplate(GlobalDecl GD, const TemplateArgumentList *&TemplateArgs) {
 
 static TemplateName asTemplateName(GlobalDecl GD) {
   const TemplateDecl *TD = dyn_cast_or_null<TemplateDecl>(GD.getDecl());
-  return TemplateName(const_cast<TemplateDecl*>(TD));
+  return TemplateName(const_cast<TemplateDecl *>(TD));
 }
 
 void CXXNameMangler::mangleName(GlobalDecl GD) {
@@ -1040,8 +1029,8 @@ const RecordDecl *CXXNameMangler::GetLocalClassDecl(const Decl *D) {
   return nullptr;
 }
 
-void CXXNameMangler::mangleNameWithAbiTags(GlobalDecl GD,
-                                           const AbiTagList *AdditionalAbiTags) {
+void CXXNameMangler::mangleNameWithAbiTags(
+    GlobalDecl GD, const AbiTagList *AdditionalAbiTags) {
   const NamedDecl *ND = cast<NamedDecl>(GD.getDecl());
   //  <name> ::= [<module-name>] <nested-name>
   //         ::= [<module-name>] <unscoped-name>
@@ -1230,10 +1219,8 @@ void CXXNameMangler::mangleFloat(const llvm::APFloat &f) {
     hexDigit &= 0xF;
 
     // Map that over to a lowercase hex digit.
-    static const char charForHex[16] = {
-      '0', '1', '2', '3', '4', '5', '6', '7',
-      '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
-    };
+    static const char charForHex[16] = {'0', '1', '2', '3', '4', '5', '6', '7',
+                                        '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
     buffer[stringIndex] = charForHex[hexDigit];
   }
 
@@ -1420,32 +1407,32 @@ void CXXNameMangler::mangleUnresolvedName(
   if (Qualifier)
     mangleUnresolvedPrefix(Qualifier);
   switch (name.getNameKind()) {
-    // <base-unresolved-name> ::= <simple-id>
-    case DeclarationName::Identifier:
-      mangleSourceName(name.getAsIdentifierInfo());
-      break;
-    // <base-unresolved-name> ::= dn <destructor-name>
-    case DeclarationName::CXXDestructorName:
-      Out << "dn";
-      mangleUnresolvedTypeOrSimpleId(name.getCXXNameType());
-      break;
-    // <base-unresolved-name> ::= on <operator-name>
-    case DeclarationName::CXXConversionFunctionName:
-    case DeclarationName::CXXLiteralOperatorName:
-    case DeclarationName::CXXOperatorName:
-      Out << "on";
-      mangleOperatorName(name, knownArity);
-      break;
-    case DeclarationName::CXXConstructorName:
-      llvm_unreachable("Can't mangle a constructor name!");
-    case DeclarationName::CXXUsingDirective:
-      llvm_unreachable("Can't mangle a using directive name!");
-    case DeclarationName::CXXDeductionGuideName:
-      llvm_unreachable("Can't mangle a deduction guide name!");
-    case DeclarationName::ObjCMultiArgSelector:
-    case DeclarationName::ObjCOneArgSelector:
-    case DeclarationName::ObjCZeroArgSelector:
-      llvm_unreachable("Can't mangle Objective-C selector names here!");
+  // <base-unresolved-name> ::= <simple-id>
+  case DeclarationName::Identifier:
+    mangleSourceName(name.getAsIdentifierInfo());
+    break;
+  // <base-unresolved-name> ::= dn <destructor-name>
+  case DeclarationName::CXXDestructorName:
+    Out << "dn";
+    mangleUnresolvedTypeOrSimpleId(name.getCXXNameType());
+    break;
+  // <base-unresolved-name> ::= on <operator-name>
+  case DeclarationName::CXXConversionFunctionName:
+  case DeclarationName::CXXLiteralOperatorName:
+  case DeclarationName::CXXOperatorName:
+    Out << "on";
+    mangleOperatorName(name, knownArity);
+    break;
+  case DeclarationName::CXXConstructorName:
+    llvm_unreachable("Can't mangle a constructor name!");
+  case DeclarationName::CXXUsingDirective:
+    llvm_unreachable("Can't mangle a using directive name!");
+  case DeclarationName::CXXDeductionGuideName:
+    llvm_unreachable("Can't mangle a deduction guide name!");
+  case DeclarationName::ObjCMultiArgSelector:
+  case DeclarationName::ObjCOneArgSelector:
+  case DeclarationName::ObjCZeroArgSelector:
+    llvm_unreachable("Can't mangle Objective-C selector names here!");
   }
 
   // The <simple-id> and on <operator-name> productions end in an optional
@@ -1531,9 +1518,9 @@ void CXXNameMangler::mangleUnqualifiedName(
       if (Context.isInternalLinkageDecl(ND))
         Out << 'L';
 
-      bool IsRegCall = FD &&
-                       FD->getType()->castAs<FunctionType>()->getCallConv() ==
-                           clang::CC_X86RegCall;
+      bool IsRegCall =
+          FD && FD->getType()->castAs<FunctionType>()->getCallConv() ==
+                    clang::CC_X86RegCall;
       bool IsDeviceStub =
           FD && FD->hasAttr<CUDAGlobalAttr>() &&
           GD.getKernelReferenceKind() == KernelReferenceKind::Stub;
@@ -1578,14 +1565,15 @@ void CXXNameMangler::mangleUnqualifiedName(
       //   the data members in the union are unnamed), then there is no way for
       //   a program to refer to the anonymous union, and there is therefore no
       //   need to mangle its name.
-      assert(RD->isAnonymousStructOrUnion()
-             && "Expected anonymous struct or union!");
+      assert(RD->isAnonymousStructOrUnion() &&
+             "Expected anonymous struct or union!");
       const FieldDecl *FD = RD->findFirstNamedDataMember();
 
       // It's actually possible for various reasons for us to get here
       // with an empty anonymous struct / union.  Fortunately, it
       // doesn't really matter what name we generate.
-      if (!FD) break;
+      if (!FD)
+        break;
       assert(FD->getIdentifier() && "Data member name isn't an identifier!");
 
       mangleSourceName(FD->getIdentifier());
@@ -1778,8 +1766,7 @@ void CXXNameMangler::mangleSourceName(const IdentifierInfo *II) {
   Out << II->getLength() << II->getName();
 }
 
-void CXXNameMangler::mangleNestedName(GlobalDecl GD,
-                                      const DeclContext *DC,
+void CXXNameMangler::mangleNestedName(GlobalDecl GD, const DeclContext *DC,
                                       const AbiTagList *AdditionalAbiTags,
                                       bool NoFunction) {
   const NamedDecl *ND = cast<NamedDecl>(GD.getDecl());
@@ -1868,17 +1855,40 @@ void CXXNameMangler::mangleLocalName(GlobalDecl GD,
   const RecordDecl *RD = GetLocalClassDecl(D);
   const DeclContext *DC = Context.getEffectiveDeclContext(RD ? RD : D);
 
+  if (const auto *VD = dyn_cast<VarDecl>(D); VD && VD->isInitCapture()) {
+    if (const auto *MethodDC = dyn_cast<CXXMethodDecl>(DC)) {
+      // Init-captures in non-local lambdas should be mangled in the lambda's
+      // closure-prefix context, not as local entities of operator().
+      if (const NamedDecl *PrefixND = getClosurePrefix(MethodDC->getParent())) {
+        mangleNestedNameWithClosurePrefix(GD, PrefixND, AdditionalAbiTags);
+        return;
+      }
+    }
+  }
+
   Out << 'Z';
 
   {
     AbiTagState LocalAbiTags(AbiTags);
 
-    if (const ObjCMethodDecl *MD = dyn_cast<ObjCMethodDecl>(DC))
+    if (const ObjCMethodDecl *MD = dyn_cast<ObjCMethodDecl>(DC)) {
       mangleObjCMethodName(MD);
-    else if (const BlockDecl *BD = dyn_cast<BlockDecl>(DC))
+    } else if (const BlockDecl *BD = dyn_cast<BlockDecl>(DC)) {
       mangleBlockForPrefix(BD);
-    else
-      mangleFunctionEncoding(getParentOfLocalEntity(DC));
+    } else {
+      const DeclContext *MangleDC = DC;
+      if (const auto *VD = dyn_cast<VarDecl>(D); VD && VD->isInitCapture()) {
+        if (const auto *MethodDC = dyn_cast<CXXMethodDecl>(DC)) {
+          const DeclContext *ParentDC =
+              Context.getEffectiveParentContext(MethodDC->getDeclContext());
+          // For local lambdas, use the parent context as the local-name base
+          // to avoid recursively mangling the lambda call operator.
+          if (isLocalContainerContext(ParentDC))
+            MangleDC = ParentDC;
+        }
+      }
+      mangleFunctionEncoding(getParentOfLocalEntity(MangleDC));
+    }
 
     // Implicit ABI tags (from namespace) are not available in the following
     // entity; reset to actually emitted tags, which are available.
@@ -1898,10 +1908,10 @@ void CXXNameMangler::mangleLocalName(GlobalDecl GD,
     // -- other default arguments do not affect its encoding.
     const CXXRecordDecl *CXXRD = dyn_cast<CXXRecordDecl>(RD);
     if (CXXRD && CXXRD->isLambda()) {
-      if (const ParmVarDecl *Parm
-              = dyn_cast_or_null<ParmVarDecl>(CXXRD->getLambdaContextDecl())) {
-        if (const FunctionDecl *Func
-              = dyn_cast<FunctionDecl>(Parm->getDeclContext())) {
+      if (const ParmVarDecl *Parm =
+              dyn_cast_or_null<ParmVarDecl>(CXXRD->getLambdaContextDecl())) {
+        if (const FunctionDecl *Func =
+                dyn_cast<FunctionDecl>(Parm->getDeclContext())) {
           Out << 'd';
           unsigned Num = Func->getNumParams() - Parm->getFunctionScopeIndex();
           if (Num > 1)
@@ -1913,7 +1923,7 @@ void CXXNameMangler::mangleLocalName(GlobalDecl GD,
 
     // Mangle the name relative to the closest enclosing function.
     // equality ok because RD derived from ND above
-    if (D == RD)  {
+    if (D == RD) {
       mangleUnqualifiedName(RD, DC, AdditionalAbiTags);
     } else if (const BlockDecl *BD = dyn_cast<BlockDecl>(D)) {
       if (const NamedDecl *PrefixND = getClosurePrefix(BD))
@@ -1930,10 +1940,10 @@ void CXXNameMangler::mangleLocalName(GlobalDecl GD,
   } else if (const BlockDecl *BD = dyn_cast<BlockDecl>(D)) {
     // Mangle a block in a default parameter; see above explanation for
     // lambdas.
-    if (const ParmVarDecl *Parm
-            = dyn_cast_or_null<ParmVarDecl>(BD->getBlockManglingContextDecl())) {
-      if (const FunctionDecl *Func
-            = dyn_cast<FunctionDecl>(Parm->getDeclContext())) {
+    if (const ParmVarDecl *Parm =
+            dyn_cast_or_null<ParmVarDecl>(BD->getBlockManglingContextDecl())) {
+      if (const FunctionDecl *Func =
+              dyn_cast<FunctionDecl>(Parm->getDeclContext())) {
         Out << 'd';
         unsigned Num = Func->getNumParams() - Parm->getFunctionScopeIndex();
         if (Num > 1)
@@ -2101,8 +2111,8 @@ void CXXNameMangler::mangleLambda(const CXXRecordDecl *Lambda) {
     if (isCompatibleWith(LangOptions::ClangABI::Ver12) &&
         (isa<VarDecl>(Context) || isa<FieldDecl>(Context)) &&
         !isa<ParmVarDecl>(Context)) {
-      if (const IdentifierInfo *Name
-            = cast<NamedDecl>(Context)->getIdentifier()) {
+      if (const IdentifierInfo *Name =
+              cast<NamedDecl>(Context)->getIdentifier()) {
         mangleSourceName(Name);
         const TemplateArgumentList *TemplateArgs = nullptr;
         if (GlobalDecl TD = isTemplate(cast<NamedDecl>(Context), TemplateArgs))
@@ -2242,8 +2252,7 @@ void CXXNameMangler::mangleTemplatePrefix(TemplateName Template) {
   addSubstitution(Template);
 }
 
-void CXXNameMangler::mangleTemplatePrefix(GlobalDecl GD,
-                                          bool NoFunction) {
+void CXXNameMangler::mang...
[truncated]

@oToToT
oToToT requested a review from cor3ntin February 21, 2026 11:26
@zyn0217
zyn0217 requested a review from mizvekov February 21, 2026 11:59

@mizvekov mizvekov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a lot of pure formatting changes here, can you please offload these to an NFC patch and just pre-commit them?

@oToToT

oToToT commented Feb 23, 2026

Copy link
Copy Markdown
Member Author

Created a new PR #182803 per https://llvm.org/docs/Contributing.html#:~:text=separate%20Pull%20Request

I'll rebase this PR once the NFC PR merged.

@oToToT

oToToT commented Feb 24, 2026

Copy link
Copy Markdown
Member Author

Removed the unnecessary format change

@oToToT oToToT changed the title Fix recursive mangling for lambda init-captures [Clang][ItaniumMangle] Fix recursive mangling for lambda init-captures Feb 24, 2026
@shafik
shafik requested a review from erichkeane February 25, 2026 03:23

@shafik shafik left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks sensible but not my expertise, I checked the mangling w/ gcc and it is consistent.

@erichkeane erichkeane left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems reasonable to me, but since it is mangling related, @efriedma-quic should be taking a look.

@oToToT
oToToT requested a review from efriedma-quic February 25, 2026 17:38
Comment thread clang/lib/Sema/SemaLambda.cpp Outdated
// through the lambda call operator. This avoids publishing provisional
// numbering state before final numbering is assigned below.
if (Numbering.ContextDecl)
Class->setLambdaContextDecl(Numbering.ContextDecl);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're going to set the context here, should we drop the ContextDecl from CXXRecordDecl::LambdaNumbering?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was also thinking about this, but I'm not sure if this is preferred. Anyway, the updated commit drops ContextDecl from CXXRecordDecl::LambdaNumbering.

Comment thread clang/lib/AST/ItaniumMangle.cpp Outdated
@@ -1868,17 +1868,40 @@ void CXXNameMangler::mangleLocalName(GlobalDecl GD,
const RecordDecl *RD = GetLocalClassDecl(D);
const DeclContext *DC = Context.getEffectiveDeclContext(RD ? RD : D);

if (const auto *VD = dyn_cast<VarDecl>(D); VD && VD->isInitCapture()) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic in this function for handling init-captures goes through a lot of cases: you separately handle init captures with a closure prefix, init captures in a local lambda, and init captures which are neither. Is it possible to unify the cases by fixing the "context" computation? Maybe something in getEffectiveDeclContext()?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. Updated around. It's a bit large now as I don't find a (very) simple way to do so.

@@ -301,6 +301,70 @@ void test_StaticInlineMember() {
StaticInlineMember::x();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the Itanium ABI specify how to mangle these cases? Does gcc produce the same mangling? (I think at least some of these cases are externally visible.)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC, this still aligns to the Itanium ABI spec. The 5.1.8 rule "If the context is the body of a function (inline and/or template), the closure type is encoded like any other local entity", and some relevant statements in both 5.1.7 and 5.1.8.

Basically, I think this is a recursion bug in Clang’s mangling path. This fix is to break recursive re-entry by resolving/publishing lambda context early. I do not invent a new recursive-type encoding.

For GCC, it produces the same output of all the test files. (except the existing differences of how we mangle lambda, e.g. main::$_0 vs main::{lambda()#1})

Comment thread clang/lib/AST/ItaniumMangle.cpp Outdated
@@ -1868,17 +1868,40 @@ void CXXNameMangler::mangleLocalName(GlobalDecl GD,
const RecordDecl *RD = GetLocalClassDecl(D);
const DeclContext *DC = Context.getEffectiveDeclContext(RD ? RD : D);

if (const auto *VD = dyn_cast<VarDecl>(D); VD && VD->isInitCapture()) {
if (const auto *MethodDC = dyn_cast<CXXMethodDecl>(DC)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think an init-capture's DeclContext is always a CXXMethodDecl, by construction; you can just cast<CXXMethodDecl>(DC).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@github-actions

github-actions Bot commented Feb 27, 2026

Copy link
Copy Markdown

✅ With the latest revision this PR passed the C/C++ code formatter.

Mangle computation for lambda signatures can recurse when a call operator type
references an init-capture (for example via decltype(init-capture)). In these
cases, mangling can re-enter the init-capture declaration and cycle back through
operator() mangling.

Make lambda context publication explicit and independent from numbering state,
then use that context uniformly during mangling:
* Publish lambda `ContextDecl` in `Sema::handleLambdaNumbering()` before
  numbering, so dependent type mangling can resolve the lambda context without
  recursing through the call operator.
* Introduce `CXXRecordDecl::setLambdaContextDecl()` and remove `ContextDecl`
  from `CXXRecordDecl::LambdaNumbering`.
* Update `ASTImporter` to import/set lambda context separately from numbering.
* In Itanium mangling, derive init-capture handling from context computation:
  - map local-lambda init-captures to the enclosing local context in
    `getEffectiveDeclContext()`
  - support init-capture variables in `getClosurePrefix()`
  - keep `mangleLocalName()` generic and rely on the computed context

Add mangling regression coverage in mangle-lambdas.cpp, including:
* local init-captures used through decltype
* non-local variable-template init-captures in decltype
* non-local static inline member init-captures in decltype

* Fixes llvm#63271
* Fixes llvm#86240
* Fixes llvm#139089

@efriedma-quic efriedma-quic left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@oToToT
oToToT merged commit 8ce2b9c into llvm:main Feb 27, 2026
10 checks passed
@oToToT
oToToT deleted the better-mangle branch February 27, 2026 19:18
sujianIBM pushed a commit to sujianIBM/llvm-project that referenced this pull request Mar 5, 2026
llvm#182667)

[Clang][ItaniumMangle] Fix recursive mangling for lambda init-captures

Mangle computation for lambda signatures can recurse when a call
operator type
references an init-capture (for example via decltype(init-capture)). In
these
cases, mangling can re-enter the init-capture declaration and cycle back
through
operator() mangling.

Make lambda context publication explicit and independent from numbering
state,
then use that context uniformly during mangling:
* Publish lambda `ContextDecl` in `Sema::handleLambdaNumbering()` before
numbering, so dependent type mangling can resolve the lambda context
without
  recursing through the call operator.
* Introduce `CXXRecordDecl::setLambdaContextDecl()` and remove
`ContextDecl`
  from `CXXRecordDecl::LambdaNumbering`.
* Update `ASTImporter` to import/set lambda context separately from
numbering.
* In Itanium mangling, derive init-capture handling from context
computation:
  - map local-lambda init-captures to the enclosing local context in
    `getEffectiveDeclContext()`
  - support init-capture variables in `getClosurePrefix()`
  - keep `mangleLocalName()` generic and rely on the computed context

Add mangling regression coverage in mangle-lambdas.cpp, including:
* local init-captures used through decltype
* non-local variable-template init-captures in decltype
* non-local static inline member init-captures in decltype

* Fixes llvm#63271
* Fixes llvm#86240
* Fixes llvm#139089
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category

Projects

None yet

6 participants