[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