From: Simon Marchi <simon.marchi@efficios.com>
To: gdb-patches@sourceware.org
Cc: Simon Marchi <simon.marchi@polymtl.ca>
Subject: [PATCH 13/13] gdb: handle universal character names in C++ name parser
Date: Tue, 20 Oct 2026 12:17:35 -0400 [thread overview]
Message-ID: <20261020162054.2140671-14-simon.marchi@efficios.com> (raw)
In-Reply-To: <20261020162054.2140671-1-simon.marchi@efficios.com>
From: Simon Marchi <simon.marchi@polymtl.ca>
clang uses UCN, or "universal character names" (\u followed by exactly 4
hex digits or \U followed by exactly 8), in the names of templates with
character arguments, for instance:
C16<u'\u0152'>
WChar<L'\U0001f600'>
The demangler spells them:
C16<(char16_t)338>
WChar<(wchar_t)128512>
Handle them in cp_parse_escape, the same way as hexadecimal escapes, but
with the exact number of digits. If there are fewer or more digits than
expected, the escape sequence is invalid. In C++, extra digits would
instead be separate characters, making it a multicharacter literal, but
it doesn't matter here, since we reject those anyway.
The value of a universal character name can be 2^31 or more. For
instance, clang names a type with a char32_t template argument of value
0xffffffff:
C<U'\Uffffffff'>
The values of character literals are currently returned as an int and
formatted with "%d", which results in "-1". This creates a
demangle_component of type DEMANGLE_COMPONENT_LITERAL with a negative
value, which is unexpected. The demangler normally represents a
negative value with DEMANGLE_COMPONENT_LITERAL_NEG and a string holding
the absolute value. To avoid this, change cp_parse_escape to return an
unsigned int and format the value with "%u". This makes subsequent
patches simpler, as we don't have to handle the case of
DEMANGLE_COMPONENT_LITERAL with a value string holding a negative value.
Add some tests:
- canonicalization selftests
- parser selftests checking that malformed UCNs don't parse
- cases to gdb.dwarf2/nontype-template-lookup.exp
- cases to gdb.cp/nontype-template-rtti.exp
Change-Id: I374333f2b629f921542ae03eec25c4624e8bb11f
---
gdb/cp-name-parser.c | 39 ++++++++++++++-----
gdb/testsuite/gdb.cp/nontype-template-rtti.cc | 2 +
.../gdb.cp/nontype-template-rtti.exp | 5 ++-
.../gdb.dwarf2/nontype-template-lookup.exp | 24 ++++++++++++
4 files changed, 60 insertions(+), 10 deletions(-)
diff --git a/gdb/cp-name-parser.c b/gdb/cp-name-parser.c
index abc2e2f19eed..c8d96c9ace33 100644
--- a/gdb/cp-name-parser.c
+++ b/gdb/cp-name-parser.c
@@ -482,11 +482,11 @@ c_parse_backslash (int host_char, int *target_char)
If the escape sequence is invalid, return an empty optional. */
-static std::optional<int>
+static std::optional<unsigned int>
cp_parse_escape (const char **string_ptr)
{
int target_char;
- int c = *(*string_ptr)++;
+ unsigned int c = static_cast<unsigned char> (*(*string_ptr)++);
if (c == '\0')
{
@@ -500,11 +500,15 @@ cp_parse_escape (const char **string_ptr)
switch (c)
{
case 'x':
+ case 'u':
+ case 'U':
{
- /* Hexadecimal escape (\x followed by one or more hex digits).
+ /* Hexadecimal escape (\x followed by one or more hex digits) or
+ universal character name (\u followed by exactly 4 hex digits,
+ \U followed by exactly 8).
- An escape with more than 8 digits will silently wrap around,
- that's ok. */
+ An hex escape with more than 8 digits will silently wrap around,
+ that's ok.*/
unsigned int value = 0;
int count = 0;
@@ -514,7 +518,9 @@ cp_parse_escape (const char **string_ptr)
count++;
}
- if (count == 0)
+ if (count == 0
+ || (c == 'u' && count != 4)
+ || (c == 'U' && count != 8))
return {};
return value;
@@ -577,10 +583,10 @@ lex_char_literal (cp_name_parser_YYSTYPE *lvalp,
/* Skip opening quote. */
state->lexptr++;
- int c = *state->lexptr++;
+ unsigned int c = static_cast<unsigned char> (*state->lexptr++);
if (c == '\\')
{
- std::optional<int> escaped = cp_parse_escape (&state->lexptr);
+ std::optional<unsigned int> escaped = cp_parse_escape (&state->lexptr);
if (!escaped.has_value ())
{
cpname_yyerror (state, _("invalid escape sequence"));
@@ -602,7 +608,7 @@ lex_char_literal (cp_name_parser_YYSTYPE *lvalp,
/* We over-allocate here, but it doesn't really matter. */
char *copy = (char *) obstack_alloc (&state->demangle_info->obstack, 30);
- xsnprintf (copy, 30, "%d", c);
+ xsnprintf (copy, 30, "%u", c);
c = *state->lexptr++;
if (c == '\0')
@@ -1168,6 +1174,14 @@ canonicalize_tests ()
should_canonicalize_to ("C<U'\\x1f600'>", "C<(char32_t)128512>");
should_canonicalize_to ("C<(char32_t) 128512>", "C<(char32_t)128512>");
+ /* Universal character names. */
+ should_canonicalize_to ("C<u'\\u0152'>", "C<(char16_t)338>");
+ should_canonicalize_to ("C<(char16_t) 338>", "C<(char16_t)338>");
+ should_canonicalize_to ("C<U'\\U0001f600'>", "C<(char32_t)128512>");
+ should_canonicalize_to ("C<U'\\Uffffffff'>", "C<(char32_t)4294967295>");
+ should_canonicalize_to ("C<(char32_t) 4294967295>",
+ "C<(char32_t)4294967295>");
+
/* Identifiers that start like an encoding prefix. */
should_canonicalize_to ("C<Lx,ux,Ux,u8x>", "C<Lx, ux, Ux, u8x>");
@@ -1204,6 +1218,13 @@ parser_tests ()
should_not_parse ("C<'\\x", "invalid escape sequence");
should_not_parse ("C<'\\xg'>", "invalid escape sequence");
should_not_parse ("C<'\\xabg'>", "invalid character constant");
+
+ should_not_parse ("C<u'\\u'>", "invalid escape sequence");
+ should_not_parse ("C<u'\\u123'>", "invalid escape sequence");
+ should_not_parse ("C<u'\\u12345'>", "invalid escape sequence");
+ should_not_parse ("C<U'\\U'>", "invalid escape sequence");
+ should_not_parse ("C<U'\\U1234567'>", "invalid escape sequence");
+ should_not_parse ("C<U'\\U123456789'>", "invalid escape sequence");
}
#endif
diff --git a/gdb/testsuite/gdb.cp/nontype-template-rtti.cc b/gdb/testsuite/gdb.cp/nontype-template-rtti.cc
index e19c7e4371ba..ebd6f3b6aede 100644
--- a/gdb/testsuite/gdb.cp/nontype-template-rtti.cc
+++ b/gdb/testsuite/gdb.cp/nontype-template-rtti.cc
@@ -34,6 +34,8 @@ base *char32_a = new S<char32_t, U'a'>;
#ifdef __cpp_char8_t
base *char8_a = new S<char8_t, u8'a'>;
#endif
+base *char16_0x152 = new S<char16_t, u'\u0152'>;
+base *char32_0xffffffff = new S<char32_t, 0xffffffff>;
int
main ()
diff --git a/gdb/testsuite/gdb.cp/nontype-template-rtti.exp b/gdb/testsuite/gdb.cp/nontype-template-rtti.exp
index f3de351b648a..5ea53a1bdc65 100644
--- a/gdb/testsuite/gdb.cp/nontype-template-rtti.exp
+++ b/gdb/testsuite/gdb.cp/nontype-template-rtti.exp
@@ -55,6 +55,8 @@ set objects {
{wchar_a wchar_t 0x61}
{char16_a char16_t 0x61}
{char32_a char32_t 0x61}
+ {char16_0x152 char16_t 0x152}
+ {char32_0xffffffff char32_t 0xffffffff}
}
if {[cxx_compiler_supports_cxx20]} {
@@ -67,7 +69,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 wchar_a char16_a char32_a char8_a}} {
+ && $var in {uchar_200 schar_5 wchar_a char16_a char32_a char8_a
+ char16_0x152 char32_0xffffffff}} {
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 537d5cb1e9e1..42f0a8f1609c 100644
--- a/gdb/testsuite/gdb.dwarf2/nontype-template-lookup.exp
+++ b/gdb/testsuite/gdb.dwarf2/nontype-template-lookup.exp
@@ -65,6 +65,27 @@ Dwarf::assemble $asm_file {
DW_AT_name {Char8Prefix<u8'a'>}
DW_AT_byte_size 1 DW_FORM_sdata
}
+
+ # This is how clang encodes a char16_t value, using a universal
+ # character name.
+ structure_type {
+ DW_AT_name {Char16UCN<u'\\u0152'>}
+ DW_AT_byte_size 1 DW_FORM_sdata
+ }
+
+ # This is how clang encodes a wchar_t value, using a universal
+ # character name.
+ structure_type {
+ DW_AT_name {WCharUCN<L'\\U0001f600'>}
+ DW_AT_byte_size 1 DW_FORM_sdata
+ }
+
+ # This is how clang encodes a char32_t value, using a universal
+ # character name.
+ structure_type {
+ DW_AT_name {Char32Max<U'\\Uffffffff'>}
+ DW_AT_byte_size 1 DW_FORM_sdata
+ }
}
}
}
@@ -83,6 +104,9 @@ foreach name [list \
{Char16Prefix<(char16_t)97>} \
{Char32Prefix<(char32_t)97>} \
{Char8Prefix<(char8_t)97>} \
+ {Char16UCN<(char16_t)338>} \
+ {WCharUCN<(wchar_t)128512>} \
+ {Char32Max<(char32_t)4294967295>} \
] {
gdb_test "ptype $name" \
"type = struct [string_to_regexp $name] \{.*\}"
--
2.56.0
prev parent reply other threads:[~2026-10-20 16:25 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 ` [PATCH 12/13] gdb: handle character literal encoding prefixes " Simon Marchi
2026-10-20 16:17 ` Simon Marchi [this message]
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-14-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