Mirror of the gdb-patches mailing list
 help / color / mirror / Atom feed
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


      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