]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
Handle an argument-less operator in the C++ name parser
authorTom Tromey <tom@tromey.com>
Thu, 8 May 2025 20:04:05 +0000 (14:04 -0600)
committerTom Tromey <tom@tromey.com>
Fri, 23 May 2025 14:48:18 +0000 (08:48 -0600)
While debugging a new failure in my long-suffering "search-in-psyms"
series, I found that the C++ name canonicalizer did not handle a case
like "some_name::operator new []".  This should remove the space,
resulting in "some_name::operator new[]" -- but does not.

This happens because the parser requires an operator to be followed by
argument types.  That is, it's expected.

However, it seems to me that we do want to be able to canonicalize a
name like this.  It will appear in the DWARF as a DW_AT_name, and
furthermore it could be entered by the user.

This patch fixes this problem by changing the grammar to supply the
"()" itself, then removing the trailing "()" when changing to string
form (in the functions that matter).

This isn't ideal -- it might miss a very obscure case involving the
gdb extension of providing fully-qualified names for function-local
statics -- but it improves the situation at least.

It's possible a better solution might be to rewrite the name
canonicalizer.  I was wondering if this could perhaps be done without
reference to the grammar -- just by examining the tokens.  However,
that's much more involved.

Let me know what you think.

Regression tested on x86-64 Fedora 40.

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=32939
Reviewed-By: Keith Seitz <keiths@redhat.com>
gdb/cp-name-parser.y
gdb/cp-support.c
gdb/cp-support.h

index d4ab98c7a108b996f6874df326f188136ff14c86..cb0babbf15ec9ab66091a49b5f63440be00e7101 100644 (file)
@@ -374,6 +374,22 @@ function
                |       colon_ext_only function_arglist start_opt
                        { $$ = state->fill_comp (DEMANGLE_COMPONENT_TYPED_NAME, $1, $2.comp);
                          if ($3) $$ = state->fill_comp (DEMANGLE_COMPONENT_LOCAL_NAME, $$, $3); }
+               |       colon_ext_only
+                       {
+                         /* This production is a hack to handle
+                            something like "name::operator new[]" --
+                            without arguments, this ordinarily would
+                            not parse, but canonicalizing it is
+                            important.  So we infer the "()" and then
+                            remove it when converting back to string.
+                            Note that this works because this
+                            production is terminal.  */
+                         demangle_component *comp
+                           = state->fill_comp (DEMANGLE_COMPONENT_FUNCTION_TYPE,
+                                               nullptr, nullptr);
+                         $$ = state->fill_comp (DEMANGLE_COMPONENT_TYPED_NAME, $1, comp);
+                         state->demangle_info->added_parens = true;
+                       }
 
                |       conversion_op_name start_opt
                        { $$ = $1.comp;
@@ -2111,6 +2127,12 @@ canonicalize_tests ()
 
   should_be_the_same ("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 ()");
 }
 
 #endif
index c71b8d901b53ba6a6b63534c6d5509bafd0e033a..900132901e0409c06ba949fdf99f7bde97eeb089 100644 (file)
@@ -573,6 +573,17 @@ replace_typedefs (struct demangle_parse_info *info,
     }
 }
 
+/* A helper to strip a trailing "()" from PTR.  The string is modified
+   in place.  */
+
+static void
+maybe_strip_parens (char *ptr)
+{
+  size_t len = strlen (ptr);
+  if (len > 2 && ptr[len - 2] == '(' && ptr[len - 1] == ')')
+    ptr[len - 2] = '\0';
+}
+
 /* Parse STRING and convert it to canonical form, resolving any
    typedefs.  If parsing fails, or if STRING is already canonical,
    return nullptr.  Otherwise return the canonical form.  If
@@ -599,6 +610,9 @@ cp_canonicalize_string_full (const char *string,
                                                            estimated_len);
       gdb_assert (us);
 
+      if (info->added_parens)
+       maybe_strip_parens (us.get ());
+
       /* Finally, compare the original string with the computed
         name, returning NULL if they are the same.  */
       if (strcmp (us.get (), string) == 0)
@@ -647,6 +661,9 @@ cp_canonicalize_string (const char *string)
       return nullptr;
     }
 
+  if (info->added_parens)
+    maybe_strip_parens (us.get ());
+
   if (strcmp (us.get (), string) == 0)
     return nullptr;
 
index 3ed354c1f1614a3f6f270bacd51f4ada68c45dc0..ffe0ba134684fc67c58b0059d33f4975e1cf052f 100644 (file)
@@ -63,6 +63,10 @@ struct demangle_parse_info
   /* Any memory used during processing.  */
   auto_obstack obstack;
 
+  /* True if the parser had to add a dummy '()' at the end of the
+     input text to make it parse.  */
+  bool added_parens = false;
+
   /* Any other objects referred to by this object, and whose storage
      lifetime must be linked.  */
   std::vector<std::unique_ptr<demangle_parse_info>> infos;