* [PATCH 00/13] Make the C++ name parser recognize more char literal forms
@ 2026-10-20 16:17 Simon Marchi
2026-10-20 16:17 ` [PATCH 01/13] gdb: make demangle_parse_info own the memory its tree points into Simon Marchi
` (12 more replies)
0 siblings, 13 replies; 21+ messages in thread
From: Simon Marchi @ 2026-10-20 16:17 UTC (permalink / raw)
To: gdb-patches; +Cc: Simon Marchi
I am trying to fix some bugs in the area of non-type template
parameters. For example, here is one related bug:
RTTI symbols not found for non-type templates
https://sourceware.org/bugzilla/show_bug.cgi?id=19320
It's quite complicated, it seems like I can always come up with some
more corner cases which break any of my attempts fix this generally. So
I'll start with this series of patches that I am fairly confident are
good in any case. They teach the C++ name parser to recognize literal
forms that clang emits but GDB does not recognize, such as:
C<(char)'\xff'>
C<u'\u0152a'>
C<L'\U0001f600'>
Recognizing these forms allows GDB to canonicalize these names, makes
some more type lookups succeed, and gets rid the infamous `RTTI symbol
not found for class '...'` error in some cases.
Simon Marchi (13):
gdb: make demangle_parse_info own the memory its tree points into
gdb: remove p_allocated_size parameter of gdb_cplus_demangle_print
gdb: modernize mangled_name_to_comp and cp_class_name_from_physname
gdb: fix crash in "maint canonicalize" without argument
gdb: move C++ name parser parsing selftest to its own test
gdb: check exact results in the canonicalize selftest
gdb: extract lex_char_literal from cpname_yylex
gdb: remove \^ escape sequence support from C++ name parser
gdb: remove backslash-newline handling from cp_parse_escape
gdb: fix out-of-bounds reads in lex_char_literal
gdb: handle hexadecimal escapes in C++ name parser
gdb: handle character literal encoding prefixes in C++ name parser
gdb: handle universal character names in C++ name parser
gdb/cp-name-parser.c | 325 +++++++++++++-----
gdb/cp-support.c | 131 ++++---
gdb/cp-support.h | 10 +-
gdb/maint.c | 3 +
gdb/testsuite/gdb.base/maint.exp | 1 +
gdb/testsuite/gdb.cp/nontype-template-rtti.cc | 44 +++
.../gdb.cp/nontype-template-rtti.exp | 89 +++++
.../gdb.dwarf2/nontype-template-lookup.exp | 113 ++++++
gdb/testsuite/lib/gdb.exp | 7 +
9 files changed, 561 insertions(+), 162 deletions(-)
create mode 100644 gdb/testsuite/gdb.cp/nontype-template-rtti.cc
create mode 100644 gdb/testsuite/gdb.cp/nontype-template-rtti.exp
create mode 100644 gdb/testsuite/gdb.dwarf2/nontype-template-lookup.exp
base-commit: d74cbaa58f3a8771cb7dd0191b45e3d5b5c5dbbd
--
2.56.0
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH 01/13] gdb: make demangle_parse_info own the memory its tree points into
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 ` Simon Marchi
2026-10-20 16:17 ` [PATCH 02/13] gdb: remove p_allocated_size parameter of gdb_cplus_demangle_print Simon Marchi
` (11 subsequent siblings)
12 siblings, 0 replies; 21+ messages in thread
From: Simon Marchi @ 2026-10-20 16:17 UTC (permalink / raw)
To: gdb-patches; +Cc: Simon Marchi
mangled_name_to_comp returns, through output parameters, some memory
that the caller needs to hold onto while it uses the returned
demangle_component. The `memory` parameter is used if we take the
gdb_cplus_demangle_v3_components branch, while the `demangled_p`
parameter is used if we take the fallback.
To make it easier for the callers, move these two fields inside
demangle_parse_info. demangle_parse_info already contains other objects
whose lifetime is tied to the demangle_component.
Change-Id: Ib22c73d07706096771ba74284da19f8597497286
---
gdb/cp-support.c | 59 ++++++++++++++++++++++--------------------------
gdb/cp-support.h | 7 ++++++
2 files changed, 34 insertions(+), 32 deletions(-)
diff --git a/gdb/cp-support.c b/gdb/cp-support.c
index 7a1b07a3b161..0f1ead6a4e98 100644
--- a/gdb/cp-support.c
+++ b/gdb/cp-support.c
@@ -75,9 +75,8 @@ static void
canonicalization_ftype *finder,
void *data);
-static struct demangle_component *
- gdb_cplus_demangle_v3_components (const char *mangled,
- int options, void **mem);
+static demangle_parse_info_up gdb_cplus_demangle_v3_components
+ (const char *mangled, int options);
/* A convenience function to copy STRING into OBSTACK, returning a pointer
to the newly allocated string and saving the number of bytes saved in LEN.
@@ -665,32 +664,23 @@ cp_canonicalize_string (const char *string)
return us;
}
-/* Convert a mangled name to a demangle_component tree. *MEMORY is
- set to the block of used memory that should be freed when finished
- with the tree. DEMANGLED_P is set to the char * that should be
- freed when finished with the tree, or NULL if none was needed.
- OPTIONS will be passed to the demangler. */
+/* Convert a mangled name to a demangle_component tree. OPTIONS is passed to
+ the demangler.
+
+ The returned tree may point into MANGLED_NAME, so MANGLED_NAME must
+ outlive it. */
static demangle_parse_info_up
-mangled_name_to_comp (const char *mangled_name, int options,
- void **memory,
- gdb::unique_xmalloc_ptr<char> *demangled_p)
+mangled_name_to_comp (const char *mangled_name, int options)
{
/* If it looks like a v3 mangled name, then try to go directly
to trees. */
if (mangled_name[0] == '_' && mangled_name[1] == 'Z')
{
- struct demangle_component *ret;
-
- ret = gdb_cplus_demangle_v3_components (mangled_name,
- options, memory);
- if (ret)
- {
- auto info = std::make_unique<demangle_parse_info> ();
- info->tree = ret;
- *demangled_p = NULL;
- return info;
- }
+ if (demangle_parse_info_up info
+ = gdb_cplus_demangle_v3_components (mangled_name, options);
+ info != nullptr)
+ return info;
}
/* If it doesn't, or if that failed, then try to demangle the
@@ -708,7 +698,7 @@ mangled_name_to_comp (const char *mangled_name, int options,
if (info == NULL)
return NULL;
- *demangled_p = std::move (demangled_name);
+ info->demangled_name = std::move (demangled_name);
return info;
}
@@ -717,12 +707,9 @@ mangled_name_to_comp (const char *mangled_name, int options,
gdb::unique_xmalloc_ptr<char>
cp_class_name_from_physname (const char *physname)
{
- void *storage = NULL;
- gdb::unique_xmalloc_ptr<char> demangled_name;
gdb::unique_xmalloc_ptr<char> ret;
struct demangle_component *ret_comp, *prev_comp, *cur_comp;
- demangle_parse_info_up info
- = mangled_name_to_comp (physname, DMGL_ANSI, &storage, &demangled_name);
+ demangle_parse_info_up info = mangled_name_to_comp (physname, DMGL_ANSI);
int done;
if (info == nullptr)
@@ -796,7 +783,6 @@ cp_class_name_from_physname (const char *physname)
ret = cp_comp_to_string (ret_comp, 10);
}
- xfree (storage);
return ret;
}
@@ -1666,11 +1652,20 @@ gdb_cplus_demangle_print (int options,
/* A wrapper for cplus_demangle_v3_components that forces
DMGL_VERBOSE. */
-static struct demangle_component *
-gdb_cplus_demangle_v3_components (const char *mangled,
- int options, void **mem)
+static demangle_parse_info_up
+gdb_cplus_demangle_v3_components (const char *mangled, int options)
{
- return cplus_demangle_v3_components (mangled, options | DMGL_VERBOSE, mem);
+ void *storage;
+ demangle_component *tree
+ = cplus_demangle_v3_components (mangled, options | DMGL_VERBOSE,
+ &storage);
+ if (tree == nullptr)
+ return nullptr;
+
+ auto info = std::make_unique<demangle_parse_info> ();
+ info->tree = tree;
+ info->demangler_storage.reset (storage);
+ return info;
}
/* See cp-support.h. */
diff --git a/gdb/cp-support.h b/gdb/cp-support.h
index 00703c52c880..103cbf0cd652 100644
--- a/gdb/cp-support.h
+++ b/gdb/cp-support.h
@@ -67,6 +67,13 @@ struct demangle_parse_info
/* Any memory used during processing. */
auto_obstack obstack;
+ /* Memory allocated by cplus_demangle_v3_components that TREE points
+ into, if any. */
+ gdb::unique_xmalloc_ptr<void> demangler_storage;
+
+ /* The demangled name TREE was parsed from and points into, if any. */
+ gdb::unique_xmalloc_ptr<char> demangled_name;
+
/* True if the parser had to add a dummy '()' at the end of the
input text to make it parse. */
bool added_parens = false;
--
2.56.0
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH 02/13] gdb: remove p_allocated_size parameter of gdb_cplus_demangle_print
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 ` Simon Marchi
2026-10-20 16:17 ` [PATCH 03/13] gdb: modernize mangled_name_to_comp and cp_class_name_from_physname Simon Marchi
` (10 subsequent siblings)
12 siblings, 0 replies; 21+ messages in thread
From: Simon Marchi @ 2026-10-20 16:17 UTC (permalink / raw)
To: gdb-patches; +Cc: Simon Marchi
The only caller of gdb_cplus_demangle_print, cp_comp_to_string, ignores
the allocated size it returns. Remove the parameter.
Change-Id: I0efad01e56f1f3ee6df2c543574279a23cd66027
---
gdb/cp-name-parser.c | 4 +---
gdb/cp-support.c | 7 ++++---
gdb/cp-support.h | 3 +--
3 files changed, 6 insertions(+), 8 deletions(-)
diff --git a/gdb/cp-name-parser.c b/gdb/cp-name-parser.c
index fe2ffd61a6c8..deb4ae4dd83b 100644
--- a/gdb/cp-name-parser.c
+++ b/gdb/cp-name-parser.c
@@ -380,10 +380,8 @@ cpname_state::parse_number (const char *p, int len, int parsed_float,
gdb::unique_xmalloc_ptr<char>
cp_comp_to_string (struct demangle_component *result, int estimated_len)
{
- size_t err;
-
return gdb_cplus_demangle_print (DMGL_PARAMS | DMGL_ANSI, result,
- estimated_len, &err);
+ estimated_len);
}
/* Merge the two parse trees given by DEST and SRC. The parse tree
diff --git a/gdb/cp-support.c b/gdb/cp-support.c
index 0f1ead6a4e98..80f3f14c4086 100644
--- a/gdb/cp-support.c
+++ b/gdb/cp-support.c
@@ -1641,12 +1641,13 @@ gdb_demangle (const char *name, int options)
gdb::unique_xmalloc_ptr<char>
gdb_cplus_demangle_print (int options,
struct demangle_component *tree,
- int estimated_length,
- size_t *p_allocated_size)
+ int estimated_length)
{
+ size_t allocated_size;
+
return gdb::unique_xmalloc_ptr<char>
(cplus_demangle_print (options | DMGL_VERBOSE, tree,
- estimated_length, p_allocated_size));
+ estimated_length, &allocated_size));
}
/* A wrapper for cplus_demangle_v3_components that forces
diff --git a/gdb/cp-support.h b/gdb/cp-support.h
index 103cbf0cd652..4f5a12c8f69d 100644
--- a/gdb/cp-support.h
+++ b/gdb/cp-support.h
@@ -208,8 +208,7 @@ gdb::unique_xmalloc_ptr<char> gdb_demangle (const char *name, int options);
/* A wrapper for cplus_demangle_print. */
extern gdb::unique_xmalloc_ptr<char> gdb_cplus_demangle_print
- (int options, struct demangle_component *tree, int estimated_length,
- size_t *p_allocated_size);
+ (int options, struct demangle_component *tree, int estimated_length);
/* Find an instance of the character C in the string S that is outside
of all parenthesis pairs, single-quoted strings, and double-quoted
--
2.56.0
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH 03/13] gdb: modernize mangled_name_to_comp and cp_class_name_from_physname
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 ` Simon Marchi
2026-10-20 16:17 ` [PATCH 04/13] gdb: fix crash in "maint canonicalize" without argument Simon Marchi
` (9 subsequent siblings)
12 siblings, 0 replies; 21+ messages in thread
From: Simon Marchi @ 2026-10-20 16:17 UTC (permalink / raw)
To: gdb-patches; +Cc: Simon Marchi
- Move gdb_cplus_demangle_v3_components above mangled_name_to_comp,
removing the need for a forward declaration.
- Use nullptr instead of NULL, and fix the indentation of a return
statement in mangled_name_to_comp.
- In cp_class_name_from_physname, declare variables where they are
initialized, use bool instead of int for the loop flag, use for loops
instead of while loops to reduce the scope of the loop variables, and
return early when no class name is found.
Change-Id: I35be6a6907b15e353a5042e77f4fc6d168039c92
---
gdb/cp-support.c | 95 +++++++++++++++++++++---------------------------
1 file changed, 42 insertions(+), 53 deletions(-)
diff --git a/gdb/cp-support.c b/gdb/cp-support.c
index 80f3f14c4086..3ddbc4083745 100644
--- a/gdb/cp-support.c
+++ b/gdb/cp-support.c
@@ -75,9 +75,6 @@ static void
canonicalization_ftype *finder,
void *data);
-static demangle_parse_info_up gdb_cplus_demangle_v3_components
- (const char *mangled, int options);
-
/* A convenience function to copy STRING into OBSTACK, returning a pointer
to the newly allocated string and saving the number of bytes saved in LEN.
@@ -664,6 +661,25 @@ cp_canonicalize_string (const char *string)
return us;
}
+/* A wrapper for cplus_demangle_v3_components that forces
+ DMGL_VERBOSE. */
+
+static demangle_parse_info_up
+gdb_cplus_demangle_v3_components (const char *mangled, int options)
+{
+ void *storage;
+ demangle_component *tree
+ = cplus_demangle_v3_components (mangled, options | DMGL_VERBOSE,
+ &storage);
+ if (tree == nullptr)
+ return nullptr;
+
+ auto info = std::make_unique<demangle_parse_info> ();
+ info->tree = tree;
+ info->demangler_storage.reset (storage);
+ return info;
+}
+
/* Convert a mangled name to a demangle_component tree. OPTIONS is passed to
the demangler.
@@ -685,18 +701,17 @@ mangled_name_to_comp (const char *mangled_name, int options)
/* If it doesn't, or if that failed, then try to demangle the
name. */
- gdb::unique_xmalloc_ptr<char> demangled_name = gdb_demangle (mangled_name,
- options);
- if (demangled_name == NULL)
- return NULL;
+ gdb::unique_xmalloc_ptr<char> demangled_name
+ = gdb_demangle (mangled_name, options);
+ if (demangled_name == nullptr)
+ return nullptr;
/* If we could demangle the name, parse it to build the component
tree. */
demangle_parse_info_up info
= cp_demangled_name_to_comp (demangled_name.get ());
-
- if (info == NULL)
- return NULL;
+ if (info == nullptr)
+ return nullptr;
info->demangled_name = std::move (demangled_name);
return info;
@@ -707,20 +722,15 @@ mangled_name_to_comp (const char *mangled_name, int options)
gdb::unique_xmalloc_ptr<char>
cp_class_name_from_physname (const char *physname)
{
- gdb::unique_xmalloc_ptr<char> ret;
- struct demangle_component *ret_comp, *prev_comp, *cur_comp;
demangle_parse_info_up info = mangled_name_to_comp (physname, DMGL_ANSI);
- int done;
-
if (info == nullptr)
return nullptr;
- done = 0;
- ret_comp = info->tree;
+ demangle_component *ret_comp = info->tree;
/* First strip off any qualifiers, if we have a function or
method. */
- while (!done)
+ for (bool done = false; !done;)
switch (ret_comp->type)
{
case DEMANGLE_COMPONENT_CONST:
@@ -733,7 +743,7 @@ cp_class_name_from_physname (const char *physname)
ret_comp = d_left (ret_comp);
break;
default:
- done = 1;
+ done = true;
break;
}
@@ -749,10 +759,9 @@ cp_class_name_from_physname (const char *physname)
/* What we have now should be a name, possibly qualified.
Additional qualifiers could live in the left subtree or the right
subtree. Find the last piece. */
- done = 0;
- prev_comp = NULL;
- cur_comp = ret_comp;
- while (!done)
+ demangle_component *prev_comp = nullptr;
+ demangle_component *cur_comp = ret_comp;
+ for (bool done = false; !done;)
switch (cur_comp->type)
{
case DEMANGLE_COMPONENT_QUAL_NAME:
@@ -766,24 +775,23 @@ cp_class_name_from_physname (const char *physname)
case DEMANGLE_COMPONENT_DTOR:
case DEMANGLE_COMPONENT_OPERATOR:
case DEMANGLE_COMPONENT_EXTENDED_OPERATOR:
- done = 1;
+ done = true;
break;
default:
- done = 1;
- cur_comp = NULL;
+ done = true;
+ cur_comp = nullptr;
break;
}
- if (cur_comp != NULL && prev_comp != NULL)
- {
- /* We want to discard the rightmost child of PREV_COMP. */
- *prev_comp = *d_left (prev_comp);
- /* The ten is completely arbitrary; we don't have a good
- estimate. */
- ret = cp_comp_to_string (ret_comp, 10);
- }
+ if (cur_comp == nullptr || prev_comp == nullptr)
+ return nullptr;
- return ret;
+ /* We want to discard the rightmost child of PREV_COMP. */
+ *prev_comp = *d_left (prev_comp);
+
+ /* The ten is completely arbitrary; we don't have a good
+ estimate. */
+ return cp_comp_to_string (ret_comp, 10);
}
/* Return the child of COMP which is the basename of a method,
@@ -1650,25 +1658,6 @@ gdb_cplus_demangle_print (int options,
estimated_length, &allocated_size));
}
-/* A wrapper for cplus_demangle_v3_components that forces
- DMGL_VERBOSE. */
-
-static demangle_parse_info_up
-gdb_cplus_demangle_v3_components (const char *mangled, int options)
-{
- void *storage;
- demangle_component *tree
- = cplus_demangle_v3_components (mangled, options | DMGL_VERBOSE,
- &storage);
- if (tree == nullptr)
- return nullptr;
-
- auto info = std::make_unique<demangle_parse_info> ();
- info->tree = tree;
- info->demangler_storage.reset (storage);
- return info;
-}
-
/* See cp-support.h. */
unsigned int
--
2.56.0
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH 04/13] gdb: fix crash in "maint canonicalize" without argument
2026-10-20 16:17 [PATCH 00/13] Make the C++ name parser recognize more char literal forms Simon Marchi
` (2 preceding siblings ...)
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 ` Simon Marchi
2026-10-20 16:17 ` [PATCH 05/13] gdb: move C++ name parser parsing selftest to its own test Simon Marchi
` (8 subsequent siblings)
12 siblings, 0 replies; 21+ messages in thread
From: Simon Marchi @ 2026-10-20 16:17 UTC (permalink / raw)
To: gdb-patches; +Cc: Simon Marchi
Running "maint canonicalize" without an argument crashes GDB:
$ ./gdb -nx -q --data-directory=data-directory -ex "maint canon" -batch
/home/smarchi/src/binutils-gdb/gdb/cp-support.c:105:27: runtime error: load of null pointer of type 'const char'
This message is from UBSan, otherwise we get a segfault.
maintenance_canonicalize passes the NULL ARGS straight to
cp_canonicalize_string. Error out with error_no_arg if no argument is
given.
Add a test in gdb.base/maint.exp.
Change-Id: I2924a6e8d2a0a5ea77bdee5595a75c8c0fb59673
---
gdb/maint.c | 3 +++
gdb/testsuite/gdb.base/maint.exp | 1 +
2 files changed, 4 insertions(+)
diff --git a/gdb/maint.c b/gdb/maint.c
index 22954f624efe..7864458ba136 100644
--- a/gdb/maint.c
+++ b/gdb/maint.c
@@ -118,6 +118,9 @@ maintenance_demangle (const char *args, int from_tty)
static void
maintenance_canonicalize (const char *args, int from_tty)
{
+ if (args == nullptr)
+ error_no_arg (_("name"));
+
gdb::unique_xmalloc_ptr<char> canon = cp_canonicalize_string (args);
if (canon == nullptr)
gdb_printf ("No change.\n");
diff --git a/gdb/testsuite/gdb.base/maint.exp b/gdb/testsuite/gdb.base/maint.exp
index f0c183c8c417..d13dab21fbe9 100644
--- a/gdb/testsuite/gdb.base/maint.exp
+++ b/gdb/testsuite/gdb.base/maint.exp
@@ -456,5 +456,6 @@ gdb_test "maint canonicalize int short" "canonical = short"
gdb_test "maint canonicalize fn<ty<int>>" \
"canonical = fn<ty<int> >"
gdb_test "maint canonical unsigned int" "No change\\."
+gdb_test "maint canonicalize" "Argument required \\(name\\)\\."
gdb_exit
--
2.56.0
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH 05/13] gdb: move C++ name parser parsing selftest to its own test
2026-10-20 16:17 [PATCH 00/13] Make the C++ name parser recognize more char literal forms Simon Marchi
` (3 preceding siblings ...)
2026-10-20 16:17 ` [PATCH 04/13] gdb: fix crash in "maint canonicalize" without argument Simon Marchi
@ 2026-10-20 16:17 ` Simon Marchi
2026-10-20 16:17 ` [PATCH 06/13] gdb: check exact results in the canonicalize selftest Simon Marchi
` (7 subsequent siblings)
12 siblings, 0 replies; 21+ messages in thread
From: Simon Marchi @ 2026-10-20 16:17 UTC (permalink / raw)
To: gdb-patches; +Cc: Simon Marchi
canonicalize_tests contains a should_parse call, which only checks that
the C++ name parser accepts a name, with no canonicalization involved.
Move it to its own "cp-name-parser" selftest. Following patches will
add more such tests (some "should_not_parse" tests) to that function.
Change-Id: I361e65e5eff32856fcb08ab9f4a1d32f700d4f9c
---
gdb/cp-name-parser.c | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/gdb/cp-name-parser.c b/gdb/cp-name-parser.c
index deb4ae4dd83b..710ffafcfc13 100644
--- a/gdb/cp-name-parser.c
+++ b/gdb/cp-name-parser.c
@@ -1063,8 +1063,6 @@ canonicalize_tests ()
should_be_the_same ("something<void ()>", "something< void() >");
should_be_the_same ("something<void ()>", "something<void (void)>");
- should_parse ("void whatever::operator<=><int, int>");
-
should_be_the_same ("Foozle<int>::fogey<Empty<int> > (Empty<int>)",
"Foozle<int>::fogey<Empty<int>> (Empty<int>)");
@@ -1075,11 +1073,20 @@ canonicalize_tests ()
should_be_the_same ("operator()", "operator ()");
}
+/* Test the C++ name parser's handling of some inputs. */
+
+static void
+parser_tests ()
+{
+ should_parse ("void whatever::operator<=><int, int>");
+}
+
#endif
INIT_GDB_FILE (cp_name_parser)
{
#if GDB_SELF_TEST
selftests::register_test ("canonicalize", canonicalize_tests);
+ selftests::register_test ("cp-name-parser", parser_tests);
#endif
}
--
2.56.0
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH 06/13] gdb: check exact results in the canonicalize selftest
2026-10-20 16:17 [PATCH 00/13] Make the C++ name parser recognize more char literal forms Simon Marchi
` (4 preceding siblings ...)
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 ` Simon Marchi
2026-10-21 15:21 ` Tom Tromey
2026-10-20 16:17 ` [PATCH 07/13] gdb: extract lex_char_literal from cpname_yylex Simon Marchi
` (6 subsequent siblings)
12 siblings, 1 reply; 21+ messages in thread
From: Simon Marchi @ 2026-10-20 16:17 UTC (permalink / raw)
To: gdb-patches; +Cc: Simon Marchi
The canonicalize selftest uses should_be_the_same, which canonicalizes
two names and checks that the results are equal. This doesn't check
what the canonical form actually is: both names going through the same
wrong conversion would still pass.
Replace should_be_the_same with should_canonicalize_to, which
canonicalizes a name and compares the result to an expected string.
Convert the existing tests: each name that was passed to
should_be_the_same is now checked against the canonical form.
Change-Id: Iaaa4712255a4d140b8a9756ccca062f4dc251f13
---
gdb/cp-name-parser.c | 59 ++++++++++++++++++++++++--------------------
1 file changed, 32 insertions(+), 27 deletions(-)
diff --git a/gdb/cp-name-parser.c b/gdb/cp-name-parser.c
index 710ffafcfc13..8012d6cc0a20 100644
--- a/gdb/cp-name-parser.c
+++ b/gdb/cp-name-parser.c
@@ -1023,18 +1023,19 @@ cpname_yylex (cp_name_parser_YYSTYPE *lvalp,
#if GDB_SELF_TEST
+/* Check that canonicalizing NAME gives EXPECTED. */
+
static void
-should_be_the_same (const char *one, const char *two)
+should_canonicalize_to (const char *name, const char *expected)
{
- gdb::unique_xmalloc_ptr<char> cpone = cp_canonicalize_string (one);
- gdb::unique_xmalloc_ptr<char> cptwo = cp_canonicalize_string (two);
+ gdb::unique_xmalloc_ptr<char> canon = cp_canonicalize_string (name);
+ const char *result = canon != nullptr ? canon.get () : name;
- if (cpone != nullptr)
- one = cpone.get ();
- if (cptwo != nullptr)
- two = cptwo.get ();
+ if (!streq (result, expected))
+ debug_printf ("canonicalize: \"%s\" gave \"%s\", expected \"%s\"\n",
+ name, result, expected);
- SELF_CHECK (streq (one, two));
+ SELF_CHECK (streq (result, expected));
}
static void
@@ -1047,30 +1048,34 @@ should_parse (const char *name)
static void
canonicalize_tests ()
{
- should_be_the_same ("short int", "short");
- should_be_the_same ("int short", "short");
+ should_canonicalize_to ("short int", "short");
+ should_canonicalize_to ("int short", "short");
- should_be_the_same ("C<(char) 1>::m()", "C<(char) '\\001'>::m()");
- should_be_the_same ("x::y::z<1>", "x::y::z<0x01>");
- should_be_the_same ("x::y::z<1>", "x::y::z<01>");
- should_be_the_same ("x::y::z<(unsigned long long) 1>", "x::y::z<01ull>");
- should_be_the_same ("x::y::z<0b111>", "x::y::z<7>");
- should_be_the_same ("x::y::z<0b111>", "x::y::z<0t7>");
- should_be_the_same ("x::y::z<0b111>", "x::y::z<0D7>");
+ should_canonicalize_to ("C<(char) 1>::m()", "C<(char)1>::m()");
+ should_canonicalize_to ("C<(char) '\\001'>::m()", "C<(char)1>::m()");
+ should_canonicalize_to ("x::y::z<0x01>", "x::y::z<1>");
+ should_canonicalize_to ("x::y::z<01>", "x::y::z<1>");
+ should_canonicalize_to ("x::y::z<(unsigned long long) 1>", "x::y::z<1ull>");
+ should_canonicalize_to ("x::y::z<01ull>", "x::y::z<1ull>");
+ should_canonicalize_to ("x::y::z<0b111>", "x::y::z<7>");
+ should_canonicalize_to ("x::y::z<0t7>", "x::y::z<7>");
+ should_canonicalize_to ("x::y::z<0D7>", "x::y::z<7>");
- should_be_the_same ("x::y::z<0xff'ff>", "x::y::z<65535>");
+ should_canonicalize_to ("x::y::z<0xff'ff>", "x::y::z<65535>");
- should_be_the_same ("something<void ()>", "something< void() >");
- should_be_the_same ("something<void ()>", "something<void (void)>");
+ should_canonicalize_to ("something< void() >", "something<void()>");
+ should_canonicalize_to ("something<void (void)>", "something<void()>");
- should_be_the_same ("Foozle<int>::fogey<Empty<int> > (Empty<int>)",
- "Foozle<int>::fogey<Empty<int>> (Empty<int>)");
+ should_canonicalize_to ("Foozle<int>::fogey<Empty<int> > (Empty<int>)",
+ "Foozle<int>::fogey<Empty<int> >(Empty<int>)");
+ should_canonicalize_to ("Foozle<int>::fogey<Empty<int>> (Empty<int>)",
+ "Foozle<int>::fogey<Empty<int> >(Empty<int>)");
- should_be_the_same ("something :: operator new [ ]",
- "something::operator new[]");
- should_be_the_same ("something :: operator new",
- "something::operator new");
- should_be_the_same ("operator()", "operator ()");
+ should_canonicalize_to ("something :: operator new [ ]",
+ "something::operator new[]");
+ should_canonicalize_to ("something :: operator new",
+ "something::operator new");
+ should_canonicalize_to ("operator ()", "operator()");
}
/* Test the C++ name parser's handling of some inputs. */
--
2.56.0
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH 07/13] gdb: extract lex_char_literal from cpname_yylex
2026-10-20 16:17 [PATCH 00/13] Make the C++ name parser recognize more char literal forms Simon Marchi
` (5 preceding siblings ...)
2026-10-20 16:17 ` [PATCH 06/13] gdb: check exact results in the canonicalize selftest Simon Marchi
@ 2026-10-20 16:17 ` 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
` (5 subsequent siblings)
12 siblings, 1 reply; 21+ messages in thread
From: Simon Marchi @ 2026-10-20 16:17 UTC (permalink / raw)
To: gdb-patches; +Cc: Simon Marchi
From: Simon Marchi <simon.marchi@polymtl.ca>
Move the code lexing character constants in the C++ name parser to its own
function, lex_char_literal. This makes the code easier to read, but also a
later patch will call it from another place.
The comment mentioned that a quote could also start a quoted symbol
reference, like 'foo(int,int)', but this code doesn't seem handle that, it
really just handles character constants, so drop that part of the comment.
While at it, add selftests for the error paths of this function ("empty
character constant" and "invalid character constant"). Add a new
should_not_parse helper for that.
Change-Id: I6413606d03d8291f29117decf1c34000323e4533
---
gdb/cp-name-parser.c | 87 ++++++++++++++++++++++++++++----------------
1 file changed, 56 insertions(+), 31 deletions(-)
diff --git a/gdb/cp-name-parser.c b/gdb/cp-name-parser.c
index 8012d6cc0a20..d7ecb534b718 100644
--- a/gdb/cp-name-parser.c
+++ b/gdb/cp-name-parser.c
@@ -554,6 +554,45 @@ cpname_yyerror (cp_name_parser::cpname_state *state, const char *msg)
state->global_errmsg = msg ? msg : "parse error";
}
+/* 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. */
+
+static int
+lex_char_literal (cp_name_parser_YYSTYPE *lvalp,
+ cp_name_parser::cpname_state *state)
+{
+ /* Skip opening quote. */
+ state->lexptr++;
+
+ int c = *state->lexptr++;
+ if (c == '\\')
+ c = cp_parse_escape (&state->lexptr);
+ else if (c == '\'')
+ {
+ cpname_yyerror (state, _("empty character constant"));
+ return ERROR;
+ }
+
+ /* 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);
+
+ c = *state->lexptr++;
+ if (c != '\'')
+ {
+ cpname_yyerror (state, _("invalid character constant"));
+ return ERROR;
+ }
+
+ lvalp->comp
+ = state->fill_comp (DEMANGLE_COMPONENT_LITERAL,
+ state->make_builtin_type ("char"),
+ state->make_name (copy, strlen (copy)));
+
+ return INT;
+}
+
/* Read one token, getting characters through LEXPTR. */
int
@@ -563,7 +602,6 @@ cpname_yylex (cp_name_parser_YYSTYPE *lvalp,
int c;
int namelen;
const char *tokstart;
- char *copy;
/* If the input starts with OP_STR, consume it, use OP_STR as the
token's operator name and return true. Otherwise, return false. */
@@ -605,36 +643,7 @@ cpname_yylex (cp_name_parser_YYSTYPE *lvalp,
goto retry;
case '\'':
- /* We either have a character constant ('0' or '\177' for example)
- or we have a quoted symbol reference ('foo(int,int)' in C++
- for example). */
- state->lexptr++;
- c = *state->lexptr++;
- if (c == '\\')
- c = cp_parse_escape (&state->lexptr);
- else if (c == '\'')
- {
- cpname_yyerror (state, _("empty character constant"));
- return ERROR;
- }
-
- /* We over-allocate here, but it doesn't really matter . */
- copy = (char *) obstack_alloc (&state->demangle_info->obstack, 30);
- xsnprintf (copy, 30, "%d", c);
-
- c = *state->lexptr++;
- if (c != '\'')
- {
- cpname_yyerror (state, _("invalid character constant"));
- return ERROR;
- }
-
- lvalp->comp
- = state->fill_comp (DEMANGLE_COMPONENT_LITERAL,
- state->make_builtin_type ("char"),
- state->make_name (copy, strlen (copy)));
-
- return INT;
+ return lex_char_literal (lvalp, state);
case '(':
if (startswith (tokstart, "(anonymous namespace)"))
@@ -1038,6 +1047,8 @@ should_canonicalize_to (const char *name, const char *expected)
SELF_CHECK (streq (result, expected));
}
+/* Check that parsing NAME succeeds. */
+
static void
should_parse (const char *name)
{
@@ -1045,6 +1056,17 @@ should_parse (const char *name)
SELF_CHECK (parsed != nullptr);
}
+/* Check that parsing NAME fails with error message EXPECTED_ERRMSG. */
+
+static void
+should_not_parse (const char *name, const char *expected_errmsg)
+{
+ std::string errmsg;
+ auto parsed = cp_demangled_name_to_comp (name, &errmsg);
+ SELF_CHECK (parsed == nullptr);
+ SELF_CHECK (errmsg == expected_errmsg);
+}
+
static void
canonicalize_tests ()
{
@@ -1084,6 +1106,9 @@ static void
parser_tests ()
{
should_parse ("void whatever::operator<=><int, int>");
+
+ should_not_parse ("C<''>", "empty character constant");
+ should_not_parse ("C<'ab'>", "invalid character constant");
}
#endif
--
2.56.0
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH 08/13] gdb: remove \^ escape sequence support from C++ name parser
2026-10-20 16:17 [PATCH 00/13] Make the C++ name parser recognize more char literal forms Simon Marchi
` (6 preceding siblings ...)
2026-10-20 16:17 ` [PATCH 07/13] gdb: extract lex_char_literal from cpname_yylex Simon Marchi
@ 2026-10-20 16:17 ` Simon Marchi
2026-10-20 16:17 ` [PATCH 09/13] gdb: remove backslash-newline handling from cp_parse_escape Simon Marchi
` (4 subsequent siblings)
12 siblings, 0 replies; 21+ messages in thread
From: Simon Marchi @ 2026-10-20 16:17 UTC (permalink / raw)
To: gdb-patches; +Cc: Simon Marchi
cp_parse_escape, used by the C++ name parser to parse escape sequences
in character literals, accepts "\^" followed by a character. This
denotes a control character, as in caret notation: "\^A" is 1, "\^?" is
0177 (DEL).
This is not valid C or C++, no known compiler (gcc or clang) nor the
demangler produce it in symbol names, so I don't think that the C++ name
parser needs to accept it.
This support is a leftover from GDB's old generic parse_escape function,
in utils.c, which already supported "\^" since basically forever. When
the C++ name parser was introduced, in commit fb4c6eba439c (2005-03-11),
it got a copy of parse_escape, including the "\^" support. That copy
was renamed to cp_parse_escape in commit 5256a53bb3fb (2008-10-27).
parse_escape itself lost the "\^" support in commit 6c7a06a3facf
(2009-03-20), but the copy was not updated.
Remove it. A "\^" sequence is now handled like other unknown escape
sequences.
Change-Id: I866d04281d16dc2f82ea391ffa84370450682951
---
gdb/cp-name-parser.c | 18 ------------------
1 file changed, 18 deletions(-)
diff --git a/gdb/cp-name-parser.c b/gdb/cp-name-parser.c
index d7ecb534b718..4c5f18c9851f 100644
--- a/gdb/cp-name-parser.c
+++ b/gdb/cp-name-parser.c
@@ -494,24 +494,6 @@ cp_parse_escape (const char **string_ptr)
case 0:
(*string_ptr)--;
return 0;
- case '^':
- {
- c = *(*string_ptr)++;
-
- if (c == '?')
- return 0177;
- else if (c == '\\')
- target_char = cp_parse_escape (string_ptr);
- else
- target_char = c;
-
- /* Now target_char is something like `c', and we want to find
- its control-character equivalent. */
- target_char = target_char & 037;
-
- return target_char;
- }
-
case '0':
case '1':
case '2':
--
2.56.0
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH 09/13] gdb: remove backslash-newline handling from cp_parse_escape
2026-10-20 16:17 [PATCH 00/13] Make the C++ name parser recognize more char literal forms Simon Marchi
` (7 preceding siblings ...)
2026-10-20 16:17 ` [PATCH 08/13] gdb: remove \^ escape sequence support from C++ name parser Simon Marchi
@ 2026-10-20 16:17 ` Simon Marchi
2026-10-20 16:17 ` [PATCH 10/13] gdb: fix out-of-bounds reads in lex_char_literal Simon Marchi
` (3 subsequent siblings)
12 siblings, 0 replies; 21+ messages in thread
From: Simon Marchi @ 2026-10-20 16:17 UTC (permalink / raw)
To: gdb-patches; +Cc: Simon Marchi
From: Simon Marchi <simon.marchi@polymtl.ca>
cp_parse_escape returns -2 when the backslash is followed by a newline.
In source code, a backslash-newline is a line splice, which can appear
anywhere, including in string literals. But cp_parse_escape does not
need to handle that.
This comes from GDB's old generic parse_escape function, where a negative
value told the caller to treat the sequence as nothing at all, emulating a
line splice. lex_char_literal doesn't even handle that: it uses the
returned -2 as the value of the character, so C<'\<newline>'> canonicalizes
to "C<(char)-2>".
Remove the special case. A backslash followed by a newline is now
handled like any other unknown escape sequence, giving the character
following the backslash. So now, C<'\<newline>'> canonicalizes to
"C<(char)10>". cp_parse_escape doesn't reject unknown escape sequences
in general, which is acceptable, and we don't expect to see this one in
practice anyway.
Update the comment of cp_parse_escape, which mentioned the negative
value for backslash-newline.
Change-Id: I25a4587309f201ac8da2ae87361ad4f3e89d7327
---
gdb/cp-name-parser.c | 5 -----
1 file changed, 5 deletions(-)
diff --git a/gdb/cp-name-parser.c b/gdb/cp-name-parser.c
index 4c5f18c9851f..931f74e8ed51 100644
--- a/gdb/cp-name-parser.c
+++ b/gdb/cp-name-parser.c
@@ -470,9 +470,6 @@ c_parse_backslash (int host_char, int *target_char)
is updated past the characters we use. The value of the
escape sequence is returned.
- A negative value means the sequence \ newline was seen,
- which is supposed to be equivalent to nothing at all.
-
If \ is followed by a null character, we return a negative
value and leave the string pointer pointing at the null character.
@@ -489,8 +486,6 @@ cp_parse_escape (const char **string_ptr)
else
switch (c)
{
- case '\n':
- return -2;
case 0:
(*string_ptr)--;
return 0;
--
2.56.0
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH 10/13] gdb: fix out-of-bounds reads in lex_char_literal
2026-10-20 16:17 [PATCH 00/13] Make the C++ name parser recognize more char literal forms Simon Marchi
` (8 preceding siblings ...)
2026-10-20 16:17 ` [PATCH 09/13] gdb: remove backslash-newline handling from cp_parse_escape Simon Marchi
@ 2026-10-20 16:17 ` Simon Marchi
2026-10-20 16:17 ` [PATCH 11/13] gdb: handle hexadecimal escapes in C++ name parser Simon Marchi
` (2 subsequent siblings)
12 siblings, 0 replies; 21+ messages in thread
From: Simon Marchi @ 2026-10-20 16:17 UTC (permalink / raw)
To: gdb-patches; +Cc: Simon Marchi
From: Simon Marchi <simon.marchi@polymtl.ca>
The C++ name parser reads out of bounds when the input is
C<'
lex_char_literal reads the terminating null character as the character of
the literal and advances past it. It then reads one more character to
check for the closing quote, which is past the end of the string. Add a
'\0' check to handle that.
Similar problem when the input ends right after a backslash, as in
C<'\
cp_parse_escape means to handle this case in the switch: it rewinds
*string_ptr to leave it pointing to the nul character and returns 0.
However, we don't even reach that point, because c_parse_backslash returns
true and cp_parse_escape returns early.
c_parse_backslash returns true because it looks up the nul character in
backslashable, which returns a match. Once we return to lex_char_literal,
the string pointer points past the end of the string, and the quote check
does an out of bounds read.
Fix it by:
- making cp_parse_escape check for the null character before calling
c_parse_backslash, so that it handles it as intended
- reporting an "unterminated character constant" error when the
character following the opening quote is the null character
- reporting an "unterminated character constant" error when the input
ends where the closing quote is expected. This also handles inputs
like:
C<'a
Add an assert in c_parse_backslash and modernize it a bit.
The comment of cp_parse_escape says that, when the backslash is followed by
a null character, it returns a negative value and leaves the string pointer
on the null character. The negative value part is not true, change it to
say that it returns 0. The "leaves the string pointer on the null
character" part becomes true with this patch.
Add selftests for these cases.
Change-Id: I94e81e6bc01841ed4f247b19c5ed91a8d8e45521
---
gdb/cp-name-parser.c | 47 ++++++++++++++++++++++++++++++++------------
1 file changed, 34 insertions(+), 13 deletions(-)
diff --git a/gdb/cp-name-parser.c b/gdb/cp-name-parser.c
index 931f74e8ed51..38ab258a3cfc 100644
--- a/gdb/cp-name-parser.c
+++ b/gdb/cp-name-parser.c
@@ -452,16 +452,20 @@ static const char backslashable[] = "abefnrtv";
static const char represented[] = "\a\b\e\f\n\r\t\v";
/* Translate the backslash the way we would in the host character set. */
+
static int
c_parse_backslash (int host_char, int *target_char)
{
- const char *ix;
- ix = strchr (backslashable, host_char);
- if (! ix)
- return 0;
- else
- *target_char = represented[ix - backslashable];
- return 1;
+ gdb_assert (host_char != '\0');
+
+ if (const char *ix = strchr (backslashable, host_char);
+ ix != nullptr)
+ {
+ *target_char = represented[ix - backslashable];
+ return 1;
+ }
+
+ return 0;
}
/* Parse a C escape sequence. STRING_PTR points to a variable
@@ -470,8 +474,8 @@ c_parse_backslash (int host_char, int *target_char)
is updated past the characters we use. The value of the
escape sequence is returned.
- If \ is followed by a null character, we return a negative
- value and leave the string pointer pointing at the null character.
+ If \ is followed by a null character, we return 0 and leave the
+ string pointer pointing at the null character.
If \ is followed by 000, we return 0 and leave the string pointer
after the zeros. A value of 0 does not mean end of string. */
@@ -481,14 +485,18 @@ cp_parse_escape (const char **string_ptr)
{
int target_char;
int c = *(*string_ptr)++;
+
+ if (c == '\0')
+ {
+ (*string_ptr)--;
+ return 0;
+ }
+
if (c_parse_backslash (c, &target_char))
return target_char;
else
switch (c)
{
- case 0:
- (*string_ptr)--;
- return 0;
case '0':
case '1':
case '2':
@@ -550,13 +558,23 @@ lex_char_literal (cp_name_parser_YYSTYPE *lvalp,
cpname_yyerror (state, _("empty character constant"));
return ERROR;
}
+ else if (c == '\0')
+ {
+ cpname_yyerror (state, _("unterminated character constant"));
+ return ERROR;
+ }
/* 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);
c = *state->lexptr++;
- if (c != '\'')
+ if (c == '\0')
+ {
+ cpname_yyerror (state, _("unterminated character constant"));
+ return ERROR;
+ }
+ else if (c != '\'')
{
cpname_yyerror (state, _("invalid character constant"));
return ERROR;
@@ -1086,6 +1104,9 @@ parser_tests ()
should_not_parse ("C<''>", "empty character constant");
should_not_parse ("C<'ab'>", "invalid character constant");
+ should_not_parse ("C<'", "unterminated character constant");
+ should_not_parse ("C<'a", "unterminated character constant");
+ should_not_parse ("C<'\\", "unterminated character constant");
}
#endif
--
2.56.0
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH 11/13] gdb: handle hexadecimal escapes in C++ name parser
2026-10-20 16:17 [PATCH 00/13] Make the C++ name parser recognize more char literal forms Simon Marchi
` (9 preceding siblings ...)
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 ` Simon Marchi
2026-10-20 16:17 ` [PATCH 12/13] gdb: handle character literal encoding prefixes " Simon Marchi
2026-10-20 16:17 ` [PATCH 13/13] gdb: handle universal character names " Simon Marchi
12 siblings, 0 replies; 21+ messages in thread
From: Simon Marchi @ 2026-10-20 16:17 UTC (permalink / raw)
To: gdb-patches; +Cc: Simon Marchi
When non-printable character values are involved, clang produces
template names with character literals using hexadecimal escapes. For
instance, this:
template <unsigned char c> struct UChar {};
UChar<5> u;
produces a type with name:
UChar<(unsigned char)'\x05'>
The C++ name parser doesn't currently handle the hex escapes in the
character literals. These names therefore fail to canonicalize and are
kept as-is in the index. This spelling does not match the demangler's
spelling of the same type:
UChar<(unsigned char)5>
One visible consequence of this is that GDB fails to find the runtime
type of polymorphic objects when such types are involved. Given a
pointer to an object, GDB finds its runtime type like this:
1. Read the vtable pointer stored in the object, and adjust it to get
the address of the vtable.
2. Find the minimal symbol at that address.
3. Demangle it, giving
vtable for UChar<(unsigned char)5>
and extract the class name.
4. Look up that class name in the debug info.
Step 4 fails, because the cooked index contains the non-canonical name
UChar<(unsigned char)'\x05'>
GDB then gives up with:
warning: RTTI symbol not found for class 'UChar<(unsigned char)5>'
Fix it by handling hexadecimal escapes (\x followed by one or more hex
digits) in cp_parse_escape. Make cp_parse_escape return an optional,
where an empty optional means "error".
For testing, add:
- some canonicalization selftests
- some parser selftests, checking that \x without hex digits doesn't
parse
- gdb.dwarf2/nontype-template-lookup.exp, which uses the DWARF
assembler to create types named like clang names them, and looks
them up using the demangler's spelling. Following patches add more
cases to it.
- gdb.cp/nontype-template-rtti.exp, which exercises the RTTI case with
a real compiler. It uses a single class template,
"template <typename T, T N> struct S", that following patches
instantiate with more template arguments. The test checks the value
of a member initialized from N, to make sure that GDB resolves each
object to the right instantiation. It fails with gcc, which names
the types "S<unsigned char, 200>" and "S<signed char, 5>" (missing
the casts), so it is marked as a known failure with gcc.
Change-Id: I3183ec87db182b1c64fa69d909febef6221272b6
---
gdb/cp-name-parser.c | 50 ++++++++++++-
gdb/testsuite/gdb.cp/nontype-template-rtti.cc | 36 ++++++++++
.../gdb.cp/nontype-template-rtti.exp | 72 +++++++++++++++++++
.../gdb.dwarf2/nontype-template-lookup.exp | 61 ++++++++++++++++
4 files changed, 216 insertions(+), 3 deletions(-)
create mode 100644 gdb/testsuite/gdb.cp/nontype-template-rtti.cc
create mode 100644 gdb/testsuite/gdb.cp/nontype-template-rtti.exp
create mode 100644 gdb/testsuite/gdb.dwarf2/nontype-template-lookup.exp
diff --git a/gdb/cp-name-parser.c b/gdb/cp-name-parser.c
index 38ab258a3cfc..b8007187b35b 100644
--- a/gdb/cp-name-parser.c
+++ b/gdb/cp-name-parser.c
@@ -478,9 +478,11 @@ c_parse_backslash (int host_char, int *target_char)
string pointer pointing at the null character.
If \ is followed by 000, we return 0 and leave the string pointer
- after the zeros. A value of 0 does not mean end of string. */
+ after the zeros. A value of 0 does not mean end of string.
-static int
+ If the escape sequence is invalid, return an empty optional. */
+
+static std::optional<int>
cp_parse_escape (const char **string_ptr)
{
int target_char;
@@ -497,6 +499,27 @@ cp_parse_escape (const char **string_ptr)
else
switch (c)
{
+ case 'x':
+ {
+ /* Hexadecimal escape (\x followed by one or more hex digits).
+
+ An escape with more than 8 digits will silently wrap around,
+ that's ok. */
+ unsigned int value = 0;
+ int count = 0;
+
+ while (c_isxdigit (**string_ptr))
+ {
+ value = value * 16 + fromhex (*(*string_ptr)++);
+ count++;
+ }
+
+ if (count == 0)
+ return {};
+
+ return value;
+ }
+
case '0':
case '1':
case '2':
@@ -552,7 +575,16 @@ lex_char_literal (cp_name_parser_YYSTYPE *lvalp,
int c = *state->lexptr++;
if (c == '\\')
- c = cp_parse_escape (&state->lexptr);
+ {
+ std::optional<int> escaped = cp_parse_escape (&state->lexptr);
+ if (!escaped.has_value ())
+ {
+ cpname_yyerror (state, _("invalid escape sequence"));
+ return ERROR;
+ }
+
+ c = *escaped;
+ }
else if (c == '\'')
{
cpname_yyerror (state, _("empty character constant"));
@@ -1080,6 +1112,13 @@ canonicalize_tests ()
should_canonicalize_to ("x::y::z<0xff'ff>", "x::y::z<65535>");
+ /* Hexadecimal escapes. */
+ should_canonicalize_to ("C<'\\xa'>", "C<(char)10>");
+ should_canonicalize_to ("C<(char) 10>", "C<(char)10>");
+ should_canonicalize_to ("C<(unsigned char)'\\xC8'>", "C<(unsigned char)200>");
+ should_canonicalize_to ("C<(unsigned char) 200>", "C<(unsigned char)200>");
+ should_canonicalize_to ("C<(signed char)'\\x05'>", "C<(signed char)5>");
+
should_canonicalize_to ("something< void() >", "something<void()>");
should_canonicalize_to ("something<void (void)>", "something<void()>");
@@ -1107,6 +1146,11 @@ 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<'\\x'>", "invalid escape sequence");
+ should_not_parse ("C<'\\x", "invalid escape sequence");
+ should_not_parse ("C<'\\xg'>", "invalid escape sequence");
+ should_not_parse ("C<'\\xabg'>", "invalid character constant");
}
#endif
diff --git a/gdb/testsuite/gdb.cp/nontype-template-rtti.cc b/gdb/testsuite/gdb.cp/nontype-template-rtti.cc
new file mode 100644
index 000000000000..c12203fbee39
--- /dev/null
+++ b/gdb/testsuite/gdb.cp/nontype-template-rtti.cc
@@ -0,0 +1,36 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2026 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+struct base
+{
+ virtual ~base () {}
+};
+
+template <typename T, T N>
+struct S : base
+{
+ T value = N;
+};
+
+base *uchar_200 = new S<unsigned char, 200>;
+base *schar_5 = new S<signed char, 5>;
+
+int
+main ()
+{
+ return 0; /* break-here */
+}
diff --git a/gdb/testsuite/gdb.cp/nontype-template-rtti.exp b/gdb/testsuite/gdb.cp/nontype-template-rtti.exp
new file mode 100644
index 000000000000..b67736d37697
--- /dev/null
+++ b/gdb/testsuite/gdb.cp/nontype-template-rtti.exp
@@ -0,0 +1,72 @@
+# Copyright 2026 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# Check that GDB can resolve the dynamic (RTTI) type of a polymorphic
+# object whose class is a template instantiation with a non-type
+# template argument. GDB finds the dynamic type by demangling the
+# object's vtable symbol and looking the name up. Producers spell
+# non-type template arguments in DW_AT_name differently from the
+# demangler, which can make the lookup fail. We need to make sure
+# that GDB can match those up.
+
+require allow_cplus_tests
+
+standard_testfile .cc
+
+if {[prepare_for_testing "failed to prepare" $testfile $srcfile {debug c++}]} {
+ return
+}
+
+if {![runto [gdb_get_line_number "break-here"]]} {
+ return
+}
+
+# Enable dynamic type resolution.
+gdb_test_no_output "set print object on"
+
+# clang 21 and later emit an artificial static member describing the
+# vtable. Disable printing static members so that the output is easier
+# to match.
+gdb_test_no_output "set print static-members off"
+
+# The objects to check, with the type of their template argument and the
+# expected value of their "value" member.
+set objects {
+ {uchar_200 "unsigned char" 0xc8}
+ {schar_5 "signed char" 0x5}
+}
+
+foreach obj $objects {
+ lassign $obj var type value
+
+ # 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}} {
+ setup_kfail "gdb/19320" *-*-*
+ }
+
+ # If GDB successfully determines the runtime type of the object, it
+ # prints it, as well as the "<base>" subobject. The spelling of the
+ # value template argument can vary based on the compiler, so we
+ # don't match it, but the type template argument does not, so we
+ # match it.
+ #
+ # The value of the "value" member tells us that GDB found the right
+ # instantiation, in case there are multiple for the same type.
+ # Print in hexadecimal, so that the output is in a predictable form.
+ set type_re "S<[string_to_regexp $type], \[^>]+>"
+ gdb_test "print/x *$var" \
+ "= \\($type_re\\) \\{<base> = \\{.*\\}, value = $value\\}"
+}
diff --git a/gdb/testsuite/gdb.dwarf2/nontype-template-lookup.exp b/gdb/testsuite/gdb.dwarf2/nontype-template-lookup.exp
new file mode 100644
index 000000000000..464766c7c3fb
--- /dev/null
+++ b/gdb/testsuite/gdb.dwarf2/nontype-template-lookup.exp
@@ -0,0 +1,61 @@
+# Copyright 2026 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# Check that GDB can look up C++ types whose names have non-type template
+# arguments. Producers emit those types with different DW_AT_names, this
+# test verifies that GDB is able to parse and canonicalize them.
+
+load_lib dwarf.exp
+
+require dwarf2_support allow_cplus_tests
+
+standard_testfile main.c -dw.S
+
+set asm_file [standard_output_file $srcfile2]
+Dwarf::assemble $asm_file {
+ cu {} {
+ compile_unit {
+ DW_AT_language @DW_LANG_C_plus_plus
+ DW_AT_name "nontype-template-lookup.c"
+ } {
+ # This is how clang encodes an unsigned char value.
+ structure_type {
+ DW_AT_name {UCharHexEscape<(unsigned char)'\\xc8'>}
+ DW_AT_byte_size 1 DW_FORM_sdata
+ }
+
+ # This is how clang encodes a signed char value.
+ structure_type {
+ DW_AT_name {SCharHexEscape<(signed char)'\\x05'>}
+ DW_AT_byte_size 1 DW_FORM_sdata
+ }
+ }
+ }
+}
+
+if { [prepare_for_testing "failed to prepare" $testfile \
+ [list $srcfile $asm_file] {nodebug}] } {
+ return
+}
+
+# Look up each type using the spelling of the demangler, which is the
+# canonical spelling. GDB also prints the type name using that spelling.
+foreach name [list \
+ {UCharHexEscape<(unsigned char)200>} \
+ {SCharHexEscape<(signed char)5>} \
+] {
+ gdb_test "ptype $name" \
+ "type = struct [string_to_regexp $name] \{.*\}"
+}
--
2.56.0
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH 12/13] gdb: handle character literal encoding prefixes in C++ name parser
2026-10-20 16:17 [PATCH 00/13] Make the C++ name parser recognize more char literal forms Simon Marchi
` (10 preceding siblings ...)
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
2026-10-20 16:17 ` [PATCH 13/13] gdb: handle universal character names " Simon Marchi
12 siblings, 0 replies; 21+ messages in thread
From: Simon Marchi @ 2026-10-20 16:17 UTC (permalink / raw)
To: gdb-patches; +Cc: Simon Marchi
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
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH 13/13] gdb: handle universal character names in C++ name parser
2026-10-20 16:17 [PATCH 00/13] Make the C++ name parser recognize more char literal forms Simon Marchi
` (11 preceding siblings ...)
2026-10-20 16:17 ` [PATCH 12/13] gdb: handle character literal encoding prefixes " Simon Marchi
@ 2026-10-20 16:17 ` Simon Marchi
12 siblings, 0 replies; 21+ messages in thread
From: Simon Marchi @ 2026-10-20 16:17 UTC (permalink / raw)
To: gdb-patches; +Cc: Simon Marchi
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
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH 06/13] gdb: check exact results in the canonicalize selftest
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
0 siblings, 1 reply; 21+ messages in thread
From: Tom Tromey @ 2026-10-21 15:21 UTC (permalink / raw)
To: Simon Marchi; +Cc: gdb-patches
>>>>> "Simon" == Simon Marchi <simon.marchi@efficios.com> writes:
Simon> The canonicalize selftest uses should_be_the_same, which canonicalizes
Simon> two names and checks that the results are equal. This doesn't check
Simon> what the canonical form actually is: both names going through the same
Simon> wrong conversion would still pass.
I think the idea here is that gdb shouldn't actually care which form is
the canonical one. So I'm wondering if there's some reason you think it
should?
Maybe some kind of comment explaining that the rest of gdb ought not
care would be sufficient. As in, if some future developer needs to
change the canonicalizer to return something else for some reason,
they'd be free to.
Tom
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH 07/13] gdb: extract lex_char_literal from cpname_yylex
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
0 siblings, 0 replies; 21+ messages in thread
From: Tom Tromey @ 2026-10-21 15:22 UTC (permalink / raw)
To: Simon Marchi; +Cc: gdb-patches, Simon Marchi
>>>>> "Simon" == Simon Marchi <simon.marchi@efficios.com> writes:
Simon> The comment mentioned that a quote could also start a quoted symbol
Simon> reference, like 'foo(int,int)', but this code doesn't seem handle that, it
Simon> really just handles character constants, so drop that part of the comment.
This is probably some ancient copy-paste from the C parser.
Tom
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH 06/13] gdb: check exact results in the canonicalize selftest
2026-10-21 15:21 ` Tom Tromey
@ 2026-10-21 15:48 ` Simon Marchi
2026-10-21 16:56 ` Tom Tromey
0 siblings, 1 reply; 21+ messages in thread
From: Simon Marchi @ 2026-10-21 15:48 UTC (permalink / raw)
To: Tom Tromey, Simon Marchi; +Cc: gdb-patches
On 10/21/26 11:21 AM, Tom Tromey wrote:
>>>>>> "Simon" == Simon Marchi <simon.marchi@efficios.com> writes:
>
> Simon> The canonicalize selftest uses should_be_the_same, which canonicalizes
> Simon> two names and checks that the results are equal. This doesn't check
> Simon> what the canonical form actually is: both names going through the same
> Simon> wrong conversion would still pass.
>
> I think the idea here is that gdb shouldn't actually care which form is
> the canonical one. So I'm wondering if there's some reason you think it
> should?
I think it's nice to see how the function actually transforms its
inputs, it helps understand what it does. And in a sense documents what
the canonical form actually is, which could be useful.
It also makes the test a bit more robust. If we have:
should_be_the_same ("something<void ()>", "something< void() >");
And I introduce a bug that makes the function "forget" to print "void",
then both sides will canonicalize to:
something<()>
The test will still pass, when it could have caught the problem.
> Maybe some kind of comment explaining that the rest of gdb ought not
> care would be sufficient. As in, if some future developer needs to
> change the canonicalizer to return something else for some reason,
> they'd be free to.
I think that the rest of GDB cares to some extent what the canonical
for is. The functions that work with canonicalized names as inputs need
to know what they could possibly encounter.
With my patch, if a developer needs to change what the canonical form
is, they will need to update the tests accordingly. But I think that
it's a good thing, because it will make it clear how the behavior
actually changes.
Simon
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH 06/13] gdb: check exact results in the canonicalize selftest
2026-10-21 15:48 ` Simon Marchi
@ 2026-10-21 16:56 ` Tom Tromey
2026-10-21 17:01 ` Simon Marchi
0 siblings, 1 reply; 21+ messages in thread
From: Tom Tromey @ 2026-10-21 16:56 UTC (permalink / raw)
To: Simon Marchi; +Cc: Tom Tromey, Simon Marchi, gdb-patches
Simon> I think that the rest of GDB cares to some extent what the canonical
Simon> for is. The functions that work with canonicalized names as inputs need
Simon> to know what they could possibly encounter.
Yeah, I think it would be better if they didn't.
Though maybe we're already in trouble here due to the builtin type stuff
doing:
builtin_type->builtin_unsigned_short
= init_integer_type (alloc, gdbarch_short_bit (gdbarch),
1, "unsigned short");
... where "short unsigned" is a valid spelling as well.
Tom
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH 06/13] gdb: check exact results in the canonicalize selftest
2026-10-21 16:56 ` Tom Tromey
@ 2026-10-21 17:01 ` Simon Marchi
2026-10-21 17:48 ` Tom Tromey
0 siblings, 1 reply; 21+ messages in thread
From: Simon Marchi @ 2026-10-21 17:01 UTC (permalink / raw)
To: Tom Tromey; +Cc: Simon Marchi, gdb-patches
On 10/21/26 12:56 PM, Tom Tromey wrote:
> Simon> I think that the rest of GDB cares to some extent what the canonical
> Simon> for is. The functions that work with canonicalized names as inputs need
> Simon> to know what they could possibly encounter.
>
> Yeah, I think it would be better if they didn't.
>
> Though maybe we're already in trouble here due to the builtin type stuff
> doing:
>
> builtin_type->builtin_unsigned_short
> = init_integer_type (alloc, gdbarch_short_bit (gdbarch),
> 1, "unsigned short");
>
> ... where "short unsigned" is a valid spelling as well.
What do you mean by that?
Simon
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH 06/13] gdb: check exact results in the canonicalize selftest
2026-10-21 17:01 ` Simon Marchi
@ 2026-10-21 17:48 ` Tom Tromey
2026-10-22 16:28 ` Simon Marchi
0 siblings, 1 reply; 21+ messages in thread
From: Tom Tromey @ 2026-10-21 17:48 UTC (permalink / raw)
To: Simon Marchi; +Cc: Tom Tromey, Simon Marchi, gdb-patches
>>>>> "Simon" == Simon Marchi <simark@simark.ca> writes:
>> Yeah, I think it would be better if they didn't.
>>
>> Though maybe we're already in trouble here due to the builtin type stuff
>> doing:
>> ... where "short unsigned" is a valid spelling as well.
Simon> What do you mean by that?
What I mean is that my wish is that gdb internals shouldn't care about
the precise spelling that the canonicalizer spits out.
However, builtin types are given hard-coded names in the source.
Now, I was under the impression that some spot looked through the
builtin types by name. But I can't actually find that, so maybe it
doesn't happen.
But if it does happen, then the builtin type code is working against the
approach I had been wanting.
Tom
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH 06/13] gdb: check exact results in the canonicalize selftest
2026-10-21 17:48 ` Tom Tromey
@ 2026-10-22 16:28 ` Simon Marchi
0 siblings, 0 replies; 21+ messages in thread
From: Simon Marchi @ 2026-10-22 16:28 UTC (permalink / raw)
To: Tom Tromey; +Cc: Simon Marchi, gdb-patches
On 10/21/26 1:48 PM, Tom Tromey wrote:
>>>>>> "Simon" == Simon Marchi <simark@simark.ca> writes:
>
>>> Yeah, I think it would be better if they didn't.
>>>
>>> Though maybe we're already in trouble here due to the builtin type stuff
>>> doing:
>
>>> ... where "short unsigned" is a valid spelling as well.
>
> Simon> What do you mean by that?
>
> What I mean is that my wish is that gdb internals shouldn't care about
> the precise spelling that the canonicalizer spits out.
>
> However, builtin types are given hard-coded names in the source.
>
> Now, I was under the impression that some spot looked through the
> builtin types by name. But I can't actually find that, so maybe it
> doesn't happen.
>
> But if it does happen, then the builtin type code is working against the
> approach I had been wanting.
Let me share the broader context of what I am trying to fix, perhaps
that will make more sense, or perhaps you can help me think where to go
with all this. We have a few bugs open about symbol lookup problems
when non-type template parameters (I don't know why it's not called
"value template parameters") are involved, I'm trying to fix some of
them.
For example, if you have this:
template <unsigned char N>
struct C {};
C<0> c;
And you compile it with clang, you get a type with DW_AT_name:
C<(unsigned char)'\x00'>
Following this series (which adds support for recognizing hex escapes),
it canonicalizes to:
C<(unsigned char)0>
If the user does:
(gdb) ptype C<0>
GDB does does not find the type. They would have to do:
(gdb) ptype C<(unsigned char)0>
Since `C<0>` is how they wrote the type in the source (and it's a very
reasonable way to spell it), my goal is to make it work. Making it
work, as far as I understand, involves teaching strncmp_iw_with_mode to
be a bit smarter when comparing template arguments, to understand that
"0" and "(unsigned char)0" match. It's not going to be a literal string
comparison anymore.
Similarly, for gcc, it might be necessary to teach it that "2" and "2u"
match (if you recall gcc bug 81932).
To implement this, strncmp_iw_with_mode will need to have some form of
integer literal parsing, but it will be limited to just what can
possibly be found in a canonical name.
For example, it can assume that all the literals it will see are in
decimal form. It won't see a '\x00' or a 0x0. It can also assume that
integer suffix have been normalized, for instance 2U is changed to 2u.
I also have a patch (not sent) that normalizes the integer
representation, so that if the user typed:
(gdb) ptype C<(unsigned int) -1>
It gets normalized to
C<4294967295u>
which is what clang actually emits for:
template <unsigned int N>
struct C {};
C<(unsigned int)-1> c;
With this, strncmp_iw_with_mode will be able to assume that integers
have been converted to their "normal" form, which subsequently makes
comparison much easier.
In any case, changing how the tests work is not mandatory to making this
work, I just happen to like if the test demonstrates precisely what the
function does.
Simon
^ permalink raw reply [flat|nested] 21+ messages in thread
end of thread, other threads:[~2026-10-22 16:29 UTC | newest]
Thread overview: 21+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
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 ` [PATCH 13/13] gdb: handle universal character names " Simon Marchi
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox