[libcxx-commits] [libcxxabi] [llvm] [ItaniumDemangle] Demangle templated members of local-scope types (PR #201876)

via libcxx-commits libcxx-commits at lists.llvm.org
Fri Jun 5 09:09:38 PDT 2026


https://github.com/mi11ione created https://github.com/llvm/llvm-project/pull/201876

The Itanium demangler rejects a valid, clang-emitted name when a function-template instance is a member of a type nested in a `<local-name>`, a templated member of a local class, or a generic lambda's `operator()`:

```
_ZZ1fvENK3barclIiEEvT_  ->  void f()::bar::operator()<int>(int) const
```

clang emits this shape (e.g. `_ZZ1fvENK3$_0clIiEEDaT_` for a generic lambda whose `operator()` is ODR-used), but `__cxa_demangle` returns -2 and `llvm-cxxfilt` echoes the input unchanged. The entity is parsed with a `NameState`, while types and template arguments use `State == nullptr`, so their scopes are untouched.

`parseLocalName` parses the entity under a `SaveTemplateParams` scope whose destructor restores the template-parameter table when it returns. The enclosing `<encoding>`'s trailing `<bare-function-type>` is parsed after that, so the `T_` in the signature has nothing left to resolve against and the name is rejected. `EndsWithTemplateArgs` does propagate, the return type is parsed correctly, and `_ZZ1fvENK3barclIiEEvi` already demangles (only the `T_` back-reference fails)

When the local entity ends with a template-argument list, keep that scope, so the enclosing encoding can resolve the parameters. The non-local form `_ZN3fooclIiEEvT_` already works because no such scope sits between the name and its signature

Adds cases to libcxxabi/test/DemangleTestCases.inc, full demangle suite passes, both the libcxxabi and llvm copies of the header are in sync

>From 28ff5a1f2d31176b62ab1fc253e86db3132ff4e5 Mon Sep 17 00:00:00 2001
From: mi11ione <i at mi11ion.io>
Date: Fri, 5 Jun 2026 17:48:51 +0200
Subject: [PATCH] [ItaniumDemangle] Fix demangling of templated members of
 local-scope types

---
 libcxxabi/src/demangle/ItaniumDemangle.h     | 10 +++++++++-
 libcxxabi/test/DemangleTestCases.inc         |  7 +++++++
 llvm/include/llvm/Demangle/ItaniumDemangle.h | 10 +++++++++-
 3 files changed, 25 insertions(+), 2 deletions(-)

diff --git a/libcxxabi/src/demangle/ItaniumDemangle.h b/libcxxabi/src/demangle/ItaniumDemangle.h
index 94d780e229631..05fe7acd143ff 100644
--- a/libcxxabi/src/demangle/ItaniumDemangle.h
+++ b/libcxxabi/src/demangle/ItaniumDemangle.h
@@ -2817,6 +2817,7 @@ template <typename Derived, typename Alloc> struct AbstractManglingParser {
     AbstractManglingParser *Parser;
     decltype(TemplateParams) OldParams;
     decltype(OuterTemplateParams) OldOuterParams;
+    bool Restore = true;
 
   public:
     SaveTemplateParams(AbstractManglingParser *TheParser) : Parser(TheParser) {
@@ -2826,9 +2827,12 @@ template <typename Derived, typename Alloc> struct AbstractManglingParser {
       Parser->OuterTemplateParams.clear();
     }
     ~SaveTemplateParams() {
+      if (!Restore)
+        return;
       Parser->TemplateParams = std::move(OldParams);
       Parser->OuterTemplateParams = std::move(OldOuterParams);
     }
+    void keepCurrentScope() { Restore = false; }
   };
 
   // Set of unresolved forward <template-param> references. These can occur in a
@@ -3135,7 +3139,7 @@ Node *AbstractManglingParser<Derived, Alloc>::parseLocalName(NameState *State) {
   }
 
   // The template parameters of the inner name are unrelated to those of the
-  // enclosing context.
+  // enclosing context, unless the name ends with a template argument list.
   SaveTemplateParams SaveTemplateParamsScope(this);
 
   if (consumeIf('d')) {
@@ -3145,6 +3149,8 @@ Node *AbstractManglingParser<Derived, Alloc>::parseLocalName(NameState *State) {
     Node *N = getDerived().parseName(State);
     if (N == nullptr)
       return nullptr;
+    if (State && State->EndsWithTemplateArgs)
+      SaveTemplateParamsScope.keepCurrentScope();
     return make<LocalName>(Encoding, N);
   }
 
@@ -3152,6 +3158,8 @@ Node *AbstractManglingParser<Derived, Alloc>::parseLocalName(NameState *State) {
   if (Entity == nullptr)
     return nullptr;
   First = parse_discriminator(First, Last);
+  if (State && State->EndsWithTemplateArgs)
+    SaveTemplateParamsScope.keepCurrentScope();
   return make<LocalName>(Encoding, Entity);
 }
 
diff --git a/libcxxabi/test/DemangleTestCases.inc b/libcxxabi/test/DemangleTestCases.inc
index 6b7a99d849a94..7508fec084a1f 100644
--- a/libcxxabi/test/DemangleTestCases.inc
+++ b/libcxxabi/test/DemangleTestCases.inc
@@ -30234,4 +30234,11 @@
 {"__alloc_token_123__ZnwmRKSt9nothrow_t", "operator new(unsigned long, std::nothrow_t const&) (.alloc_token)"},
 {"__alloc_token__Znwm.llvm.1234", "operator new(unsigned long) (.llvm.1234) (.alloc_token)"},
 {"__alloc_token_123__Z3foov", "foo() (.alloc_token)"},
+
+{"_ZZ1fvENK3barclIiEEvT_", "void f()::bar::operator()<int>(int) const"},
+{"_ZZ1fvEN3$_0clIiEEvT_", "void f()::$_0::operator()<int>(int)"},
+{"_ZZ1fvENK3$_01gIiEEvT_", "void f()::$_0::g<int>(int) const"},
+{"_ZZ1fvENK3$_0clIiEEDaT_", "auto f()::$_0::operator()<int>(int) const"},
+{"_ZZL17isSafeToIgnoreCWDRKN5clang21CowCompilerInvocationEENK3$_0clINSt3__112basic_stringIcNS5_11char_traitsIcEENS5_9allocatorIcEEEEEEDaRKT_", "auto isSafeToIgnoreCWD(clang::CowCompilerInvocation const&)::$_0::operator()<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>>(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const&) const"},
+{"_Z3fooIZN3BarC1EvE3$_0EvT_", "void foo<Bar::Bar()::$_0>(Bar::Bar()::$_0)"},
     // clang-format on
diff --git a/llvm/include/llvm/Demangle/ItaniumDemangle.h b/llvm/include/llvm/Demangle/ItaniumDemangle.h
index 04338e1d7cb51..8710980a4a99e 100644
--- a/llvm/include/llvm/Demangle/ItaniumDemangle.h
+++ b/llvm/include/llvm/Demangle/ItaniumDemangle.h
@@ -2817,6 +2817,7 @@ template <typename Derived, typename Alloc> struct AbstractManglingParser {
     AbstractManglingParser *Parser;
     decltype(TemplateParams) OldParams;
     decltype(OuterTemplateParams) OldOuterParams;
+    bool Restore = true;
 
   public:
     SaveTemplateParams(AbstractManglingParser *TheParser) : Parser(TheParser) {
@@ -2826,9 +2827,12 @@ template <typename Derived, typename Alloc> struct AbstractManglingParser {
       Parser->OuterTemplateParams.clear();
     }
     ~SaveTemplateParams() {
+      if (!Restore)
+        return;
       Parser->TemplateParams = std::move(OldParams);
       Parser->OuterTemplateParams = std::move(OldOuterParams);
     }
+    void keepCurrentScope() { Restore = false; }
   };
 
   // Set of unresolved forward <template-param> references. These can occur in a
@@ -3135,7 +3139,7 @@ Node *AbstractManglingParser<Derived, Alloc>::parseLocalName(NameState *State) {
   }
 
   // The template parameters of the inner name are unrelated to those of the
-  // enclosing context.
+  // enclosing context, unless the name ends with a template argument list.
   SaveTemplateParams SaveTemplateParamsScope(this);
 
   if (consumeIf('d')) {
@@ -3145,6 +3149,8 @@ Node *AbstractManglingParser<Derived, Alloc>::parseLocalName(NameState *State) {
     Node *N = getDerived().parseName(State);
     if (N == nullptr)
       return nullptr;
+    if (State && State->EndsWithTemplateArgs)
+      SaveTemplateParamsScope.keepCurrentScope();
     return make<LocalName>(Encoding, N);
   }
 
@@ -3152,6 +3158,8 @@ Node *AbstractManglingParser<Derived, Alloc>::parseLocalName(NameState *State) {
   if (Entity == nullptr)
     return nullptr;
   First = parse_discriminator(First, Last);
+  if (State && State->EndsWithTemplateArgs)
+    SaveTemplateParamsScope.keepCurrentScope();
   return make<LocalName>(Encoding, Entity);
 }
 



More information about the libcxx-commits mailing list