From: Simon Marchi <simon.marchi@efficios.com>
To: gdb-patches@sourceware.org
Cc: Simon Marchi <simon.marchi@polymtl.ca>
Subject: [PATCH 12/13] gdb: handle character literal encoding prefixes in C++ name parser
Date: Tue, 20 Oct 2026 12:17:34 -0400 [thread overview]
Message-ID: <20261020162054.2140671-13-simon.marchi@efficios.com> (raw)
In-Reply-To: <20261020162054.2140671-1-simon.marchi@efficios.com>
From: Simon Marchi <simon.marchi@polymtl.ca>
clang produces template names with character literals with an encoding
prefix, for instance:
WChar<L'a'>
Char16<u'a'>
Char32<U'a'>
Char8<u8'a'>
The C++ name parser doesn't handle those, so these names fail to
canonicalize, and don't match the demangler's spelling of the same
types:
WChar<(wchar_t)97>
Char16<(char16_t)97>
Char32<(char32_t)97>
Char8<(char8_t)97>
This causes the same problem with RTTI lookups as described in the
previous patch.
Fix it by handling the L, u, U and u8 prefixes in the lexer, which give
the literal the type wchar_t, char16_t, char32_t and char8_t
respectively. Add a TYPE_NAME parameter to lex_char_literal for that,
instead of always using "char".
Add some tests:
- canonicalization selftests
- a parser selftest, checking that a prefixed character literal at the
end of the input is reported as unterminated
- cases to gdb.dwarf2/nontype-template-lookup.exp
- cases to gdb.cp/nontype-template-rtti.exp
The latter is now compiled with -std=c++20 when the compiler supports it,
to test char8_t. Add a cxx_compiler_supports_cxx20 proc in lib/gdb.exp for
that. With gcc, the new cases fail for the same reason as the existing one
(gcc spells the arguments as plain numbers, like "97"), so they are marked
as known failures.
Change-Id: I95f023ecc6c00894e68a5aad65f35b1a2280a406
---
gdb/cp-name-parser.c | 63 +++++++++++++++++--
gdb/testsuite/gdb.cp/nontype-template-rtti.cc | 6 ++
.../gdb.cp/nontype-template-rtti.exp | 18 +++++-
.../gdb.dwarf2/nontype-template-lookup.exp | 28 +++++++++
gdb/testsuite/lib/gdb.exp | 7 +++
5 files changed, 115 insertions(+), 7 deletions(-)
diff --git a/gdb/cp-name-parser.c b/gdb/cp-name-parser.c
index b8007187b35b..abc2e2f19eed 100644
--- a/gdb/cp-name-parser.c
+++ b/gdb/cp-name-parser.c
@@ -563,12 +563,16 @@ cpname_yyerror (cp_name_parser::cpname_state *state, const char *msg)
}
/* Lex a character constant ('0' or '\177' for example), starting at the
- opening quote at STATE->LEXPTR. Store the result in LVALP and return
- INT, or ERROR on error. */
+ opening quote at STATE->LEXPTR.
+
+ TYPE_NAME is the name of the character's type, which depends on the
+ encoding prefix that precedes the quote, if any.
+
+ Store the result in LVALP and return INT, or ERROR on error. */
static int
lex_char_literal (cp_name_parser_YYSTYPE *lvalp,
- cp_name_parser::cpname_state *state)
+ cp_name_parser::cpname_state *state, const char *type_name)
{
/* Skip opening quote. */
state->lexptr++;
@@ -614,7 +618,7 @@ lex_char_literal (cp_name_parser_YYSTYPE *lvalp,
lvalp->comp
= state->fill_comp (DEMANGLE_COMPONENT_LITERAL,
- state->make_builtin_type ("char"),
+ state->make_builtin_type (type_name),
state->make_name (copy, strlen (copy)));
return INT;
@@ -670,7 +674,40 @@ cpname_yylex (cp_name_parser_YYSTYPE *lvalp,
goto retry;
case '\'':
- return lex_char_literal (lvalp, state);
+ return lex_char_literal (lvalp, state, "char");
+
+ case 'L':
+ if (tokstart[1] == '\'')
+ {
+ /* L'...' */
+ state->lexptr++;
+ return lex_char_literal (lvalp, state, "wchar_t");
+ }
+ break;
+
+ case 'u':
+ if (tokstart[1] == '\'')
+ {
+ /* u'...' */
+ state->lexptr++;
+ return lex_char_literal (lvalp, state, "char16_t");
+ }
+ else if (tokstart[1] == '8' && tokstart[2] == '\'')
+ {
+ /* u8'...' */
+ state->lexptr += 2;
+ return lex_char_literal (lvalp, state, "char8_t");
+ }
+ break;
+
+ case 'U':
+ if (tokstart[1] == '\'')
+ {
+ /* U'...' */
+ state->lexptr++;
+ return lex_char_literal (lvalp, state, "char32_t");
+ }
+ break;
case '(':
if (startswith (tokstart, "(anonymous namespace)"))
@@ -1119,6 +1156,21 @@ canonicalize_tests ()
should_canonicalize_to ("C<(unsigned char) 200>", "C<(unsigned char)200>");
should_canonicalize_to ("C<(signed char)'\\x05'>", "C<(signed char)5>");
+ /* Character constants with an encoding prefix. */
+ should_canonicalize_to ("C<L'a'>", "C<(wchar_t)97>");
+ should_canonicalize_to ("C<(wchar_t) 97>", "C<(wchar_t)97>");
+ should_canonicalize_to ("C<u'a'>", "C<(char16_t)97>");
+ should_canonicalize_to ("C<(char16_t) 97>", "C<(char16_t)97>");
+ should_canonicalize_to ("C<U'a'>", "C<(char32_t)97>");
+ should_canonicalize_to ("C<(char32_t) 97>", "C<(char32_t)97>");
+ should_canonicalize_to ("C<u8'a'>", "C<(char8_t)97>");
+ should_canonicalize_to ("C<(char8_t) 97>", "C<(char8_t)97>");
+ should_canonicalize_to ("C<U'\\x1f600'>", "C<(char32_t)128512>");
+ should_canonicalize_to ("C<(char32_t) 128512>", "C<(char32_t)128512>");
+
+ /* Identifiers that start like an encoding prefix. */
+ should_canonicalize_to ("C<Lx,ux,Ux,u8x>", "C<Lx, ux, Ux, u8x>");
+
should_canonicalize_to ("something< void() >", "something<void()>");
should_canonicalize_to ("something<void (void)>", "something<void()>");
@@ -1146,6 +1198,7 @@ parser_tests ()
should_not_parse ("C<'", "unterminated character constant");
should_not_parse ("C<'a", "unterminated character constant");
should_not_parse ("C<'\\", "unterminated character constant");
+ should_not_parse ("C<L'", "unterminated character constant");
should_not_parse ("C<'\\x'>", "invalid escape sequence");
should_not_parse ("C<'\\x", "invalid escape sequence");
diff --git a/gdb/testsuite/gdb.cp/nontype-template-rtti.cc b/gdb/testsuite/gdb.cp/nontype-template-rtti.cc
index c12203fbee39..e19c7e4371ba 100644
--- a/gdb/testsuite/gdb.cp/nontype-template-rtti.cc
+++ b/gdb/testsuite/gdb.cp/nontype-template-rtti.cc
@@ -28,6 +28,12 @@ struct S : base
base *uchar_200 = new S<unsigned char, 200>;
base *schar_5 = new S<signed char, 5>;
+base *wchar_a = new S<wchar_t, L'a'>;
+base *char16_a = new S<char16_t, u'a'>;
+base *char32_a = new S<char32_t, U'a'>;
+#ifdef __cpp_char8_t
+base *char8_a = new S<char8_t, u8'a'>;
+#endif
int
main ()
diff --git a/gdb/testsuite/gdb.cp/nontype-template-rtti.exp b/gdb/testsuite/gdb.cp/nontype-template-rtti.exp
index b67736d37697..f3de351b648a 100644
--- a/gdb/testsuite/gdb.cp/nontype-template-rtti.exp
+++ b/gdb/testsuite/gdb.cp/nontype-template-rtti.exp
@@ -25,7 +25,13 @@ require allow_cplus_tests
standard_testfile .cc
-if {[prepare_for_testing "failed to prepare" $testfile $srcfile {debug c++}]} {
+# char8_t requires C++20.
+set flags {debug c++}
+if {[cxx_compiler_supports_cxx20]} {
+ lappend flags additional_flags=-std=c++20
+}
+
+if {[prepare_for_testing "failed to prepare" $testfile $srcfile $flags]} {
return
}
@@ -46,6 +52,13 @@ gdb_test_no_output "set print static-members off"
set objects {
{uchar_200 "unsigned char" 0xc8}
{schar_5 "signed char" 0x5}
+ {wchar_a wchar_t 0x61}
+ {char16_a char16_t 0x61}
+ {char32_a char32_t 0x61}
+}
+
+if {[cxx_compiler_supports_cxx20]} {
+ lappend objects {char8_a char8_t 0x61}
}
foreach obj $objects {
@@ -53,7 +66,8 @@ foreach obj $objects {
# gcc spells the template arguments of these objects in a way GDB
# doesn't match yet.
- if {[test_compiler_info gcc-* c++] && $var in {uchar_200 schar_5}} {
+ if {[test_compiler_info gcc-* c++]
+ && $var in {uchar_200 schar_5 wchar_a char16_a char32_a char8_a}} {
setup_kfail "gdb/19320" *-*-*
}
diff --git a/gdb/testsuite/gdb.dwarf2/nontype-template-lookup.exp b/gdb/testsuite/gdb.dwarf2/nontype-template-lookup.exp
index 464766c7c3fb..537d5cb1e9e1 100644
--- a/gdb/testsuite/gdb.dwarf2/nontype-template-lookup.exp
+++ b/gdb/testsuite/gdb.dwarf2/nontype-template-lookup.exp
@@ -41,6 +41,30 @@ Dwarf::assemble $asm_file {
DW_AT_name {SCharHexEscape<(signed char)'\\x05'>}
DW_AT_byte_size 1 DW_FORM_sdata
}
+
+ # This is how clang encodes a wchar_t value.
+ structure_type {
+ DW_AT_name {WCharPrefix<L'a'>}
+ DW_AT_byte_size 1 DW_FORM_sdata
+ }
+
+ # This is how clang encodes a char16_t value.
+ structure_type {
+ DW_AT_name {Char16Prefix<u'a'>}
+ DW_AT_byte_size 1 DW_FORM_sdata
+ }
+
+ # This is how clang encodes a char32_t value.
+ structure_type {
+ DW_AT_name {Char32Prefix<U'a'>}
+ DW_AT_byte_size 1 DW_FORM_sdata
+ }
+
+ # This is how clang encodes a char8_t value.
+ structure_type {
+ DW_AT_name {Char8Prefix<u8'a'>}
+ DW_AT_byte_size 1 DW_FORM_sdata
+ }
}
}
}
@@ -55,6 +79,10 @@ if { [prepare_for_testing "failed to prepare" $testfile \
foreach name [list \
{UCharHexEscape<(unsigned char)200>} \
{SCharHexEscape<(signed char)5>} \
+ {WCharPrefix<(wchar_t)97>} \
+ {Char16Prefix<(char16_t)97>} \
+ {Char32Prefix<(char32_t)97>} \
+ {Char8Prefix<(char8_t)97>} \
] {
gdb_test "ptype $name" \
"type = struct [string_to_regexp $name] \{.*\}"
diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp
index 1aae8cad1ae1..71e1e23bc041 100644
--- a/gdb/testsuite/lib/gdb.exp
+++ b/gdb/testsuite/lib/gdb.exp
@@ -5761,6 +5761,13 @@ gdb_caching_proc has_int128_cxx {} {
return [gdb_int128_helper c++]
}
+# Return true if the C++ compiler supports C++20, through the
+# -std=c++20 flag.
+gdb_caching_proc cxx_compiler_supports_cxx20 {} {
+ return [gdb_can_simple_compile cxx_compiler_supports_cxx20 {int x;} \
+ object {c++ additional_flags=-std=c++20}]
+}
+
# Return true if the IFUNC feature is supported.
gdb_caching_proc allow_ifunc_tests {} {
if {[gdb_can_simple_compile ifunc {
--
2.56.0
next prev parent reply other threads:[~2026-10-20 16:24 UTC|newest]
Thread overview: 21+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-20 16:17 [PATCH 00/13] Make the C++ name parser recognize more char literal forms Simon Marchi
2026-10-20 16:17 ` [PATCH 01/13] gdb: make demangle_parse_info own the memory its tree points into Simon Marchi
2026-10-20 16:17 ` [PATCH 02/13] gdb: remove p_allocated_size parameter of gdb_cplus_demangle_print Simon Marchi
2026-10-20 16:17 ` [PATCH 03/13] gdb: modernize mangled_name_to_comp and cp_class_name_from_physname Simon Marchi
2026-10-20 16:17 ` [PATCH 04/13] gdb: fix crash in "maint canonicalize" without argument Simon Marchi
2026-10-20 16:17 ` [PATCH 05/13] gdb: move C++ name parser parsing selftest to its own test Simon Marchi
2026-10-20 16:17 ` [PATCH 06/13] gdb: check exact results in the canonicalize selftest Simon Marchi
2026-10-21 15:21 ` Tom Tromey
2026-10-21 15:48 ` Simon Marchi
2026-10-21 16:56 ` Tom Tromey
2026-10-21 17:01 ` Simon Marchi
2026-10-21 17:48 ` Tom Tromey
2026-10-22 16:28 ` Simon Marchi
2026-10-20 16:17 ` [PATCH 07/13] gdb: extract lex_char_literal from cpname_yylex Simon Marchi
2026-10-21 15:22 ` Tom Tromey
2026-10-20 16:17 ` [PATCH 08/13] gdb: remove \^ escape sequence support from C++ name parser Simon Marchi
2026-10-20 16:17 ` [PATCH 09/13] gdb: remove backslash-newline handling from cp_parse_escape Simon Marchi
2026-10-20 16:17 ` [PATCH 10/13] gdb: fix out-of-bounds reads in lex_char_literal Simon Marchi
2026-10-20 16:17 ` [PATCH 11/13] gdb: handle hexadecimal escapes in C++ name parser Simon Marchi
2026-10-20 16:17 ` Simon Marchi [this message]
2026-10-20 16:17 ` [PATCH 13/13] gdb: handle universal character names " Simon Marchi
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261020162054.2140671-13-simon.marchi@efficios.com \
--to=simon.marchi@efficios.com \
--cc=gdb-patches@sourceware.org \
--cc=simon.marchi@polymtl.ca \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox