diff mbox series

Refactor rust-demangle to be independent of C++ demangling.

Message ID fd31aba1-d21e-4a3e-99b6-84f4aaa033c4@www.fastmail.com
State New
Headers show
Series Refactor rust-demangle to be independent of C++ demangling. | expand

Commit Message

Eduard-Mihai Burtescu Oct. 22, 2019, 6:08 p.m. UTC
This way, rust-demangle is closer to the official "rustc-demangle"
Rust demangler, which also doesn't require full C++ demangling for
the legacy format (implemented here), and only recognizes simple
"_ZN"..."E" paths with identifier components that the format uses.

The new code also has fewer dependencies, so it would be easier
to integrate into a codebase which doesn't use the entirety of
libiberty, and doesn't have a C++ demangler, or it does but
with a different API which would require adjustments to use.

Lastly but not least, the new design allows for a single unified
demangler that internally handles both the legacy format (the only
one implemented right now) and the v0 format (which will come in
a later patch), without exposing any "postprocess C++ symbol" API.
You can see the unified demangler that I plan to upstream soon, at:
https://gist.github.com/eddyb/c41a69378750a433767cf53fe2316768

I've been trying to keep each patch a self-contained step to limit
review work and potential fallout, but if you think I should just
submit the entire final unified rust-demangle instead, I'll do it.

Bootstrapped and tested on x86_64-unknown-linux-gnu.

Also, I have no commit access, so I'd be thankful if
someone would commit this for me if/once approved.

2019-10-22  Eduard-Mihai Burtescu  <eddyb@lyken.rs>
include/ChangeLog:
	* demangle.h (rust_demangle_callback): Add.
libiberty/ChangeLog:
	* cplus-dem.c (cplus_demangle): Use rust_demangle directly.
	(rust_demangle): Remove.
	* rust-demangle.c (is_prefixed_hash): Rename to is_legacy_prefixed_hash.
	(parse_lower_hex_nibble): Rename to decode_lower_hex_nibble.
	(parse_legacy_escape): Rename to decode_legacy_escape.
	(rust_is_mangled): Remove.
	(struct rust_demangler): Add.
	(ERROR_AND): Add.
	(CHECK_OR): Add.
	(peek): Add.
	(next): Add.
	(struct rust_mangled_ident): Add.
	(parse_ident): Add.
	(rust_demangle_sym): Remove.
	(print_str): Add.
	(PRINT): Add.
	(print_ident): Add.
	(rust_demangle_callback): Add.
	(struct str_buf): Add.
	(str_buf_reserve): Add.
	(str_buf_append): Add.
	(str_buf_demangle_callback): Add.
	(rust_demangle): Add.
	* rust-demangle.h: Remove.

Comments

Li, Pan2 via Gcc-patches Oct. 22, 2019, 6:39 p.m. UTC | #1
On Tue, Oct 22, 2019 at 11:08 AM Eduard-Mihai Burtescu <eddyb@lyken.rs> wrote:
>

> @@ -160,27 +159,20 @@ cplus_demangle (const char *mangled, int options)
>    if ((options & DMGL_STYLE_MASK) == 0)
>      options |= (int) current_demangling_style & DMGL_STYLE_MASK;
>
> +  /* The Rust demangling is implemented elsewhere.
> +     Legacy Rust symbols overlap with GNU_V3, so try Rust first.  */
> +  if (RUST_DEMANGLING || AUTO_DEMANGLING)
> +    {
> +      ret = rust_demangle (mangled, options);
> +      if (ret || RUST_DEMANGLING)
> +        return ret;
> +    }
> +
>    /* The V3 ABI demangling is implemented elsewhere.  */
> -  if (GNU_V3_DEMANGLING || RUST_DEMANGLING || AUTO_DEMANGLING)
> +  if (GNU_V3_DEMANGLING || AUTO_DEMANGLING)
>      {
>        ret = cplus_demangle_v3 (mangled, options);
> -      if (GNU_V3_DEMANGLING)
> -       return ret;
> -
> -      if (ret)
> -       {
> -         /* Rust symbols are GNU_V3 mangled plus some extra subtitutions.
> -            The subtitutions are always smaller, so do in place changes.  */
> -         if (rust_is_mangled (ret))
> -           rust_demangle_sym (ret);
> -         else if (RUST_DEMANGLING)
> -           {
> -             free (ret);
> -             ret = NULL;
> -           }
> -       }
> -
> -      if (ret || RUST_DEMANGLING)
> +      if (ret || GNU_V3_DEMANGLING)
>         return ret;
>      }


I have to assume that C++ demangling is still quite a bit more common
than Rust demangling, so it's troubling that it looks like we're going
to do extra work for each symbol that starts with _ZN, which is not a
particularly uncommon prefix for a C++ mangled name.  Is there some
way we can quickly separate out Rust symbols?  Or should we try C++
demangling first?

Ian
Eduard-Mihai Burtescu Oct. 23, 2019, 1:35 p.m. UTC | #2
On Tue, Oct 22, 2019, at 9:39 PM, Ian Lance Taylor wrote:
> I have to assume that C++ demangling is still quite a bit more common
> than Rust demangling, so it's troubling that it looks like we're going
> to do extra work for each symbol that starts with _ZN, which is not a
> particularly uncommon prefix for a C++ mangled name.  Is there some
> way we can quickly separate out Rust symbols?  Or should we try C++
> demangling first?
> 
> Ian
>

I definitely agree, I don't want to make demangling plain C++ symbols
significantly slower. The old code was also doing extra work, at least
in the AUTO_DEMANGLING mode, but less than the parse_ident
loop in this patch.

I've come up with an extra quick check that regular C++ symbols
won't pass most of the time and placed it before the parse_ident
loop, that should make it comparable with the old implementation,
and tests pass just fine with the extra check.

The diff is below, but if you want me to send a combined patch,
or anything else for that matter, please let me know.

diff --git a/libiberty/rust-demangle.c b/libiberty/rust-demangle.c
index da707dbab9b..4cb189c4019 100644
--- a/libiberty/rust-demangle.c
+++ b/libiberty/rust-demangle.c
@@ -384,6 +384,14 @@ rust_demangle_callback (const char *mangled, int options,
         return 0;
       rdm.sym_len--;
 
+      /* Legacy Rust symbols also always end with a path segment
+         that encodes a 16 hex digit hash, i.e. '17h[a-f0-9]{16}'.
+         This early check, before any parse_ident calls, should
+         quickly filter out most C++ symbols unrelated to Rust. */
+      if (!(rdm.sym_len > 19
+            && !strncmp (&rdm.sym[rdm.sym_len - 19], "17h", 3)))
+        return 0;
+
       do
         {
           ident = parse_ident (&rdm);
Alexander Monakov Oct. 23, 2019, 4:22 p.m. UTC | #3
On Wed, 23 Oct 2019, Eduard-Mihai Burtescu wrote:

> @@ -384,6 +384,14 @@ rust_demangle_callback (const char *mangled, int options,
>          return 0;
>        rdm.sym_len--;
>  
> +      /* Legacy Rust symbols also always end with a path segment
> +         that encodes a 16 hex digit hash, i.e. '17h[a-f0-9]{16}'.
> +         This early check, before any parse_ident calls, should
> +         quickly filter out most C++ symbols unrelated to Rust. */
> +      if (!(rdm.sym_len > 19
> +            && !strncmp (&rdm.sym[rdm.sym_len - 19], "17h", 3)))

This can be further optimized by using memcmp in place of strncmp, since from
the length check you know that you won't see the null terminator among the three
chars you're checking.

The compiler can expand memcmp(buf, "abc", 3) inline as two comparisons against
a 16-bit immediate and an 8-bit immediate.  It can't do the same for strncmp.

Alexander
Segher Boessenkool Oct. 23, 2019, 4:37 p.m. UTC | #4
On Wed, Oct 23, 2019 at 07:22:47PM +0300, Alexander Monakov wrote:
> On Wed, 23 Oct 2019, Eduard-Mihai Burtescu wrote:
> > @@ -384,6 +384,14 @@ rust_demangle_callback (const char *mangled, int options,
> >          return 0;
> >        rdm.sym_len--;
> >  
> > +      /* Legacy Rust symbols also always end with a path segment
> > +         that encodes a 16 hex digit hash, i.e. '17h[a-f0-9]{16}'.
> > +         This early check, before any parse_ident calls, should
> > +         quickly filter out most C++ symbols unrelated to Rust. */
> > +      if (!(rdm.sym_len > 19
> > +            && !strncmp (&rdm.sym[rdm.sym_len - 19], "17h", 3)))
> 
> This can be further optimized by using memcmp in place of strncmp, since from
> the length check you know that you won't see the null terminator among the three
> chars you're checking.
> 
> The compiler can expand memcmp(buf, "abc", 3) inline as two comparisons against
> a 16-bit immediate and an 8-bit immediate.  It can't do the same for strncmp.

The compiler does not currently do that, but it *could*.  Or why not?  The
compiler is always allowed to load 3 characters here, whether some string
has a NUL character earlier or not.


Segher
Jakub Jelinek Oct. 23, 2019, 4:46 p.m. UTC | #5
On Wed, Oct 23, 2019 at 11:37:26AM -0500, Segher Boessenkool wrote:
> On Wed, Oct 23, 2019 at 07:22:47PM +0300, Alexander Monakov wrote:
> > On Wed, 23 Oct 2019, Eduard-Mihai Burtescu wrote:
> > > @@ -384,6 +384,14 @@ rust_demangle_callback (const char *mangled, int options,
> > >          return 0;
> > >        rdm.sym_len--;
> > >  
> > > +      /* Legacy Rust symbols also always end with a path segment
> > > +         that encodes a 16 hex digit hash, i.e. '17h[a-f0-9]{16}'.
> > > +         This early check, before any parse_ident calls, should
> > > +         quickly filter out most C++ symbols unrelated to Rust. */
> > > +      if (!(rdm.sym_len > 19
> > > +            && !strncmp (&rdm.sym[rdm.sym_len - 19], "17h", 3)))
> > 
> > This can be further optimized by using memcmp in place of strncmp, since from
> > the length check you know that you won't see the null terminator among the three
> > chars you're checking.
> > 
> > The compiler can expand memcmp(buf, "abc", 3) inline as two comparisons against
> > a 16-bit immediate and an 8-bit immediate.  It can't do the same for strncmp.
> 
> The compiler does not currently do that, but it *could*.  Or why not?  The
> compiler is always allowed to load 3 characters here, whether some string
> has a NUL character earlier or not.

It is valid to call strncmp (mmap(...)+page_size-1, "abc", 3), the reading
of the string should stop when 0 is seen.
Of course, it might be that there is a strlen call visible and the strlen
pass could figure out that rdm.sym_len contains the strlen, but maybe it
isn't visible or there is some call in between that might in theory
invalidate it.

	Jakub
Segher Boessenkool Oct. 23, 2019, 5:19 p.m. UTC | #6
On Wed, Oct 23, 2019 at 06:46:14PM +0200, Jakub Jelinek wrote:
> On Wed, Oct 23, 2019 at 11:37:26AM -0500, Segher Boessenkool wrote:
> > On Wed, Oct 23, 2019 at 07:22:47PM +0300, Alexander Monakov wrote:
> > > On Wed, 23 Oct 2019, Eduard-Mihai Burtescu wrote:
> > > > @@ -384,6 +384,14 @@ rust_demangle_callback (const char *mangled, int options,
> > > >          return 0;
> > > >        rdm.sym_len--;
> > > >  
> > > > +      /* Legacy Rust symbols also always end with a path segment
> > > > +         that encodes a 16 hex digit hash, i.e. '17h[a-f0-9]{16}'.
> > > > +         This early check, before any parse_ident calls, should
> > > > +         quickly filter out most C++ symbols unrelated to Rust. */
> > > > +      if (!(rdm.sym_len > 19
> > > > +            && !strncmp (&rdm.sym[rdm.sym_len - 19], "17h", 3)))
> > > 
> > > This can be further optimized by using memcmp in place of strncmp, since from
> > > the length check you know that you won't see the null terminator among the three
> > > chars you're checking.
> > > 
> > > The compiler can expand memcmp(buf, "abc", 3) inline as two comparisons against
> > > a 16-bit immediate and an 8-bit immediate.  It can't do the same for strncmp.
> > 
> > The compiler does not currently do that, but it *could*.  Or why not?  The
> > compiler is always allowed to load 3 characters here, whether some string
> > has a NUL character earlier or not.
> 
> It is valid to call strncmp (mmap(...)+page_size-1, "abc", 3), the reading
> of the string should stop when 0 is seen.

Where does it say that, though?  I don't see where it prohibits reading
more characters (up to 3 here), and you can get much better code using
that.

I of course know that for e.g. strcmp or strlen we need to be careful of
page crossings; but this is strncmp, which has a size argument saying the
size of the array objects of its arguments!


Segher
Jakub Jelinek Oct. 23, 2019, 5:28 p.m. UTC | #7
On Wed, Oct 23, 2019 at 12:19:10PM -0500, Segher Boessenkool wrote:
> I of course know that for e.g. strcmp or strlen we need to be careful of
> page crossings; but this is strncmp, which has a size argument saying the
> size of the array objects of its arguments!

https://pubs.opengroup.org/onlinepubs/009695399/functions/strncmp.html
The strncmp() function shall compare not more than n bytes
(bytes that follow a null byte are not compared)
from the array pointed to by s1 to the array pointed to by s2.

In particular the second line.

Similarly C11 7.24.4.4:
The strncmp function compares not more than n characters (characters that follow a
null character are not compared) from the array pointed to by s1 to the array pointed to
by s2.

Similarly C99.

	Jakub
Segher Boessenkool Oct. 23, 2019, 5:41 p.m. UTC | #8
On Wed, Oct 23, 2019 at 07:28:48PM +0200, Jakub Jelinek wrote:
> On Wed, Oct 23, 2019 at 12:19:10PM -0500, Segher Boessenkool wrote:
> > I of course know that for e.g. strcmp or strlen we need to be careful of
> > page crossings; but this is strncmp, which has a size argument saying the
> > size of the array objects of its arguments!
> 
> https://pubs.opengroup.org/onlinepubs/009695399/functions/strncmp.html
> The strncmp() function shall compare not more than n bytes
> (bytes that follow a null byte are not compared)
> from the array pointed to by s1 to the array pointed to by s2.
> 
> In particular the second line.
> 
> Similarly C11 7.24.4.4:
> The strncmp function compares not more than n characters (characters that follow a
> null character are not compared) from the array pointed to by s1 to the array pointed to
> by s2.
> 
> Similarly C99.

Yes, and that does not say you cannot read more characters.  It also does
not say it compares the strings pointed to, it explicitly says characters
from the array pointed to.


Segher
Eduard-Mihai Burtescu Oct. 25, 2019, 12:44 p.m. UTC | #9
> This can be further optimized by using memcmp in place of strncmp, since from
> the length check you know that you won't see the null terminator among the three
> chars you're checking.

Fair enough, here's the combined changelog/diff, with memcmp:

2019-10-22  Eduard-Mihai Burtescu  <eddyb@lyken.rs>
include/ChangeLog:
	* demangle.h (rust_demangle_callback): Add.
libiberty/ChangeLog:
	* cplus-dem.c (cplus_demangle): Use rust_demangle directly.
	(rust_demangle): Remove.
	* rust-demangle.c (is_prefixed_hash): Rename to is_legacy_prefixed_hash.
	(parse_lower_hex_nibble): Rename to decode_lower_hex_nibble.
	(parse_legacy_escape): Rename to decode_legacy_escape.
	(rust_is_mangled): Remove.
	(struct rust_demangler): Add.
	(ERROR_AND): Add.
	(CHECK_OR): Add.
	(peek): Add.
	(next): Add.
	(struct rust_mangled_ident): Add.
	(parse_ident): Add.
	(rust_demangle_sym): Remove.
	(print_str): Add.
	(PRINT): Add.
	(print_ident): Add.
	(rust_demangle_callback): Add.
	(struct str_buf): Add.
	(str_buf_reserve): Add.
	(str_buf_append): Add.
	(str_buf_demangle_callback): Add.
	(rust_demangle): Add.
	* rust-demangle.h: Remove.

diff --git a/include/demangle.h b/include/demangle.h
index 06c32571d5c..ce7235d13f3 100644
--- a/include/demangle.h
+++ b/include/demangle.h
@@ -159,6 +159,11 @@ ada_demangle (const char *mangled, int options);
 extern char *
 dlang_demangle (const char *mangled, int options);
 
+extern int
+rust_demangle_callback (const char *mangled, int options,
+                        demangle_callbackref callback, void *opaque);
+
+
 extern char *
 rust_demangle (const char *mangled, int options);
 
diff --git a/libiberty/cplus-dem.c b/libiberty/cplus-dem.c
index a39e2bf2ed4..735a61d7a82 100644
--- a/libiberty/cplus-dem.c
+++ b/libiberty/cplus-dem.c
@@ -52,7 +52,6 @@ void * realloc ();
 #define CURRENT_DEMANGLING_STYLE options
 
 #include "libiberty.h"
-#include "rust-demangle.h"
 
 enum demangling_styles current_demangling_style = auto_demangling;
 
@@ -160,27 +159,20 @@ cplus_demangle (const char *mangled, int options)
   if ((options & DMGL_STYLE_MASK) == 0)
     options |= (int) current_demangling_style & DMGL_STYLE_MASK;
 
+  /* The Rust demangling is implemented elsewhere.
+     Legacy Rust symbols overlap with GNU_V3, so try Rust first.  */
+  if (RUST_DEMANGLING || AUTO_DEMANGLING)
+    {
+      ret = rust_demangle (mangled, options);
+      if (ret || RUST_DEMANGLING)
+        return ret;
+    }
+
   /* The V3 ABI demangling is implemented elsewhere.  */
-  if (GNU_V3_DEMANGLING || RUST_DEMANGLING || AUTO_DEMANGLING)
+  if (GNU_V3_DEMANGLING || AUTO_DEMANGLING)
     {
       ret = cplus_demangle_v3 (mangled, options);
-      if (GNU_V3_DEMANGLING)
-	return ret;
-
-      if (ret)
-	{
-	  /* Rust symbols are GNU_V3 mangled plus some extra subtitutions.
-	     The subtitutions are always smaller, so do in place changes.  */
-	  if (rust_is_mangled (ret))
-	    rust_demangle_sym (ret);
-	  else if (RUST_DEMANGLING)
-	    {
-	      free (ret);
-	      ret = NULL;
-	    }
-	}
-
-      if (ret || RUST_DEMANGLING)
+      if (ret || GNU_V3_DEMANGLING)
 	return ret;
     }
 
@@ -204,27 +196,6 @@ cplus_demangle (const char *mangled, int options)
   return (ret);
 }
 
-char *
-rust_demangle (const char *mangled, int options)
-{
-  /* Rust symbols are GNU_V3 mangled plus some extra subtitutions.  */
-  char *ret = cplus_demangle_v3 (mangled, options);
-
-  /* The Rust subtitutions are always smaller, so do in place changes.  */
-  if (ret != NULL)
-    {
-      if (rust_is_mangled (ret))
-	rust_demangle_sym (ret);
-      else
-	{
-	  free (ret);
-	  ret = NULL;
-	}
-    }
-
-  return ret;
-}
-
 /* Demangle ada names.  The encoding is documented in gcc/ada/exp_dbug.ads.  */
 
 char *
diff --git a/libiberty/rust-demangle.c b/libiberty/rust-demangle.c
index 6b62e6dbd80..95255d0b601 100644
--- a/libiberty/rust-demangle.c
+++ b/libiberty/rust-demangle.c
@@ -33,9 +33,11 @@ If not, see <http://www.gnu.org/licenses/>.  */
 
 #include "safe-ctype.h"
 
+#include <inttypes.h>
 #include <sys/types.h>
 #include <string.h>
 #include <stdio.h>
+#include <stdlib.h>
 
 #ifdef HAVE_STRING_H
 #include <string.h>
@@ -47,207 +49,115 @@ extern void *memset(void *s, int c, size_t n);
 
 #include <demangle.h>
 #include "libiberty.h"
-#include "rust-demangle.h"
 
-
-/* Mangled (legacy) Rust symbols look like this:
-     _$LT$std..sys..fd..FileDesc$u20$as$u20$core..ops..Drop$GT$::drop::hc68340e1baa4987a
-
-   The original symbol is:
-     <std::sys::fd::FileDesc as core::ops::Drop>::drop
-
-   The last component of the path is a 64-bit hash in lowercase hex,
-   prefixed with "h". Rust does not have a global namespace between
-   crates, an illusion which Rust maintains by using the hash to
-   distinguish things that would otherwise have the same symbol.
-
-   Any path component not starting with a XID_Start character is
-   prefixed with "_".
-
-   The following escape sequences are used:
-
-   ","  =>  $C$
-   "@"  =>  $SP$
-   "*"  =>  $BP$
-   "&"  =>  $RF$
-   "<"  =>  $LT$
-   ">"  =>  $GT$
-   "("  =>  $LP$
-   ")"  =>  $RP$
-   "\u{XY}"  =>  $uXY$
-
-   A double ".." means "::" and a single "." means "-".
-
-   The only characters allowed in the mangled symbol are a-zA-Z0-9 and _.:$  */
-
-static const char *hash_prefix = "::h";
-static const size_t hash_prefix_len = 3;
-static const size_t hash_len = 16;
-
-static int is_prefixed_hash (const char *start);
-static int parse_lower_hex_nibble (char nibble);
-static char parse_legacy_escape (const char **in);
-
-/* INPUT: sym: symbol that has been through C++ (gnu v3) demangling
-
-   This function looks for the following indicators:
-
-   1. The hash must consist of "h" followed by 16 lowercase hex digits.
-
-   2. As a sanity check, the hash must use between 5 and 15 of the 16
-      possible hex digits. This is true of 99.9998% of hashes so once
-      in your life you may see a false negative. The point is to
-      notice path components that could be Rust hashes but are
-      probably not, like "haaaaaaaaaaaaaaaa". In this case a false
-      positive (non-Rust symbol has an important path component
-      removed because it looks like a Rust hash) is worse than a false
-      negative (the rare Rust symbol is not demangled) so this sets
-      the balance in favor of false negatives.
-
-   3. There must be no characters other than a-zA-Z0-9 and _.:$  */
-
-int
-rust_is_mangled (const char *sym)
+struct rust_demangler
 {
-  size_t len, len_without_hash;
-  const char *end;
-
-  if (!sym)
-    return 0;
-
-  len = strlen (sym);
-  if (len <= hash_prefix_len + hash_len)
-    /* Not long enough to contain "::h" + hash + something else */
-    return 0;
-
-  len_without_hash = len - (hash_prefix_len + hash_len);
-  if (!is_prefixed_hash (sym + len_without_hash))
-    return 0;
-
-  end = sym + len_without_hash;
-
-  while (sym < end)
-    {
-      if (*sym == '$' || *sym == '.' || *sym == '_' || *sym == ':'
-          || ISALNUM (*sym))
-        sym++;
-      else
-        return 0;
-    }
+  const char *sym;
+  size_t sym_len;
+
+  void *callback_opaque;
+  demangle_callbackref callback;
+
+  /* Position of the next character to read from the symbol. */
+  size_t next;
+
+  /* Non-zero if any error occurred. */
+  int errored;
+
+  /* Non-zero if printing should be verbose (e.g. include hashes). */
+  int verbose;
+
+  /* Rust mangling version, with legacy mangling being -1. */
+  int version;
+};
+
+#define ERROR_AND(x)                                                          \
+  do                                                                          \
+    {                                                                         \
+      rdm->errored = 1;                                                       \
+      x;                                                                      \
+    }                                                                         \
+  while (0)
+#define CHECK_OR(cond, x)                                                     \
+  do                                                                          \
+    {                                                                         \
+      if (!(cond))                                                            \
+        ERROR_AND (x);                                                        \
+    }                                                                         \
+  while (0)
+
+/* Parsing functions. */
 
-  return 1;
+static char
+peek (const struct rust_demangler *rdm)
+{
+  if (rdm->next < rdm->sym_len)
+    return rdm->sym[rdm->next];
+  return 0;
 }
 
-/* A hash is the prefix "::h" followed by 16 lowercase hex digits. The
-   hex digits must contain at least 5 distinct digits.  */
+static char
+next (struct rust_demangler *rdm)
+{
+  char c = peek (rdm);
+  CHECK_OR (c, return 0);
+  rdm->next++;
+  return c;
+}
 
-static int
-is_prefixed_hash (const char *str)
+struct rust_mangled_ident
 {
-  const char *end;
-  char seen[16];
-  size_t i;
-  int count, nibble;
+  /* ASCII part of the identifier. */
+  const char *ascii;
+  size_t ascii_len;
+};
 
-  if (strncmp (str, hash_prefix, hash_prefix_len))
-    return 0;
-  str += hash_prefix_len;
+static struct rust_mangled_ident
+parse_ident (struct rust_demangler *rdm)
+{
+  char c;
+  size_t start, len;
+  struct rust_mangled_ident ident;
 
-  memset (seen, 0, sizeof(seen));
-  for (end = str + hash_len; str < end; str++)
-    {
-      nibble = parse_lower_hex_nibble (*str);
-      if (nibble < 0)
-        return 0;
-      seen[nibble] = 1;
-    }
+  ident.ascii = NULL;
+  ident.ascii_len = 0;
 
-  /* Count how many distinct digits seen */
-  count = 0;
-  for (i = 0; i < 16; i++)
-    if (seen[i])
-      count++;
+  c = next (rdm);
+  CHECK_OR (ISDIGIT (c), return ident);
+  len = c - '0';
 
-  return count >= 5;
-}
+  if (c != '0')
+    while (ISDIGIT (peek (rdm)))
+      len = len * 10 + (next (rdm) - '0');
 
-/*
-  INPUT: sym: symbol for which rust_is_mangled(sym) returned 1.
+  start = rdm->next;
+  rdm->next += len;
+  /* Check for overflows. */
+  CHECK_OR ((start <= rdm->next) && (rdm->next <= rdm->sym_len), return ident);
 
-  The input is demangled in-place because the mangled name is always
-  longer than the demangled one.  */
+  ident.ascii = rdm->sym + start;
+  ident.ascii_len = len;
 
-void
-rust_demangle_sym (char *sym)
-{
-  const char *in;
-  char *out;
-  const char *end;
-  char unescaped;
+  if (ident.ascii_len == 0)
+    ident.ascii = NULL;
 
-  if (!sym)
-    return;
+  return ident;
+}
 
-  in = sym;
-  out = sym;
-  end = sym + strlen (sym) - (hash_prefix_len + hash_len);
+/* Printing functions. */
 
-  while (in < end)
-    {
-      if (*in == '$')
-        {
-          unescaped = parse_legacy_escape (&in);
-          if (unescaped)
-            *out++ = unescaped;
-          else
-            /* unexpected escape sequence, skip the rest of this segment. */
-            while (in < end && *in != ':')
-              *out++ = *in++;
-        }
-      else if (*in == '_')
-        {
-          /* If this is the start of a path component and the next
-             character is an escape sequence, ignore the underscore. The
-             mangler inserts an underscore to make sure the path
-             component begins with a XID_Start character. */
-          if ((in == sym || in[-1] == ':') && in[1] == '$')
-            in++;
-          else
-            *out++ = *in++;
-        }
-      else if (*in == '.')
-        {
-          if (in[1] == '.')
-            {
-              /* ".." becomes "::" */
-              *out++ = ':';
-              *out++ = ':';
-              in += 2;
-            }
-          else
-            {
-              /* "." becomes "-" */
-              *out++ = '-';
-              in++;
-            }
-        }
-      else if (*in == ':' || ISALNUM (*in))
-        *out++ = *in++;
-      else
-        {
-          /* unexpected character in symbol, not rust_is_mangled.  */
-          *out++ = '?'; /* This is pretty lame, but it's hard to do better. */
-          *out = '\0';
-          return;
-        }
-    }
-
-  *out = '\0';
+static void
+print_str (struct rust_demangler *rdm, const char *data, size_t len)
+{
+  if (!rdm->errored)
+    rdm->callback (data, len, rdm->callback_opaque);
 }
 
+#define PRINT(s) print_str (rdm, s, strlen (s))
+
 /* Return a 0x0-0xf value if the char is 0-9a-f, and -1 otherwise. */
 static int
-parse_lower_hex_nibble (char nibble)
+decode_lower_hex_nibble (char nibble)
 {
   if ('0' <= nibble && nibble <= '9')
     return nibble - '0';
@@ -258,17 +168,17 @@ parse_lower_hex_nibble (char nibble)
 
 /* Return the unescaped character for a "$...$" escape, or 0 if invalid. */
 static char
-parse_legacy_escape (const char **in)
+decode_legacy_escape (const char *e, size_t len, size_t *out_len)
 {
   char c = 0;
-  const char *e;
   size_t escape_len = 0;
   int lo_nibble = -1, hi_nibble = -1;
 
-  if ((*in)[0] != '$')
+  if (len < 3 || e[0] != '$')
     return 0;
 
-  e = *in + 1;
+  e++;
+  len--;
 
   if (e[0] == 'C')
     {
@@ -276,7 +186,7 @@ parse_legacy_escape (const char **in)
 
       c = ',';
     }
-  else
+  else if (len > 2)
     {
       escape_len = 2;
 
@@ -294,14 +204,14 @@ parse_legacy_escape (const char **in)
         c = '(';
       else if (e[0] == 'R' && e[1] == 'P')
         c = ')';
-      else if (e[0] == 'u')
+      else if (e[0] == 'u' && len > 3)
         {
           escape_len = 3;
 
-          hi_nibble = parse_lower_hex_nibble (e[1]);
+          hi_nibble = decode_lower_hex_nibble (e[1]);
           if (hi_nibble < 0)
             return 0;
-          lo_nibble = parse_lower_hex_nibble (e[2]);
+          lo_nibble = decode_lower_hex_nibble (e[2]);
           if (lo_nibble < 0)
             return 0;
 
@@ -314,9 +224,314 @@ parse_legacy_escape (const char **in)
         }
     }
 
-  if (!c || e[escape_len] != '$')
+  if (!c || len <= escape_len || e[escape_len] != '$')
     return 0;
 
-  *in += 2 + escape_len;
+  *out_len = 2 + escape_len;
   return c;
 }
+
+static void
+print_ident (struct rust_demangler *rdm, struct rust_mangled_ident ident)
+{
+  char unescaped;
+  size_t len;
+
+  if (rdm->errored)
+    return;
+
+  if (rdm->version == -1)
+    {
+      /* Ignore leading underscores preceding escape sequences.
+         The mangler inserts an underscore to make sure the
+         identifier begins with a XID_Start character. */
+      if (ident.ascii_len >= 2 && ident.ascii[0] == '_'
+          && ident.ascii[1] == '$')
+        {
+          ident.ascii++;
+          ident.ascii_len--;
+        }
+
+      while (ident.ascii_len > 0)
+        {
+          /* Handle legacy escape sequences ("$...$", ".." or "."). */
+          if (ident.ascii[0] == '$')
+            {
+              unescaped
+                  = decode_legacy_escape (ident.ascii, ident.ascii_len, &len);
+              if (unescaped)
+                print_str (rdm, &unescaped, 1);
+              else
+                {
+                  /* Unexpected escape sequence, print the rest verbatim. */
+                  print_str (rdm, ident.ascii, ident.ascii_len);
+                  return;
+                }
+            }
+          else if (ident.ascii[0] == '.')
+            {
+              if (ident.ascii_len >= 2 && ident.ascii[1] == '.')
+                {
+                  /* ".." becomes "::" */
+                  PRINT ("::");
+                  len = 2;
+                }
+              else
+                {
+                  /* "." becomes "-" */
+                  PRINT ("-");
+                  len = 1;
+                }
+            }
+          else
+            {
+              /* Print everything before the next escape sequence, at once. */
+              for (len = 0; len < ident.ascii_len; len++)
+                if (ident.ascii[len] == '$' || ident.ascii[len] == '.')
+                  break;
+
+              print_str (rdm, ident.ascii, len);
+            }
+
+          ident.ascii += len;
+          ident.ascii_len -= len;
+        }
+
+      return;
+    }
+}
+
+/* A legacy hash is the prefix "h" followed by 16 lowercase hex digits.
+   The hex digits must contain at least 5 distinct digits. */
+static int
+is_legacy_prefixed_hash (struct rust_mangled_ident ident)
+{
+  uint16_t seen;
+  int nibble;
+  size_t i, count;
+
+  if (ident.ascii_len != 17 || ident.ascii[0] != 'h')
+    return 0;
+
+  seen = 0;
+  for (i = 0; i < 16; i++)
+    {
+      nibble = decode_lower_hex_nibble (ident.ascii[1 + i]);
+      if (nibble < 0)
+        return 0;
+      seen |= (uint16_t)1 << nibble;
+    }
+
+  /* Count how many distinct digits were seen. */
+  count = 0;
+  while (seen)
+    {
+      if (seen & 1)
+        count++;
+      seen >>= 1;
+    }
+
+  return count >= 5;
+}
+
+int
+rust_demangle_callback (const char *mangled, int options,
+                        demangle_callbackref callback, void *opaque)
+{
+  const char *p;
+  struct rust_demangler rdm;
+  struct rust_mangled_ident ident;
+
+  rdm.sym = mangled;
+  rdm.sym_len = 0;
+
+  rdm.callback_opaque = opaque;
+  rdm.callback = callback;
+
+  rdm.next = 0;
+  rdm.errored = 0;
+  rdm.verbose = (options & DMGL_VERBOSE) != 0;
+  rdm.version = 0;
+
+  /* Rust symbols always start with _ZN (legacy). */
+  if (rdm.sym[0] == '_' && rdm.sym[1] == 'Z' && rdm.sym[2] == 'N')
+    {
+      rdm.sym += 3;
+      rdm.version = -1;
+    }
+  else
+    return 0;
+
+  /* Legacy Rust symbols use only [_0-9a-zA-Z.:$] characters. */
+  for (p = rdm.sym; *p; p++)
+    {
+      rdm.sym_len++;
+
+      if (*p == '_' || ISALNUM (*p))
+        continue;
+
+      if (rdm.version == -1 && (*p == '$' || *p == '.' || *p == ':'))
+        continue;
+
+      return 0;
+    }
+
+  /* Legacy Rust symbols need to be handled separately. */
+  if (rdm.version == -1)
+    {
+      /* Legacy Rust symbols always end with E. */
+      if (!(rdm.sym_len > 0 && rdm.sym[rdm.sym_len - 1] == 'E'))
+        return 0;
+      rdm.sym_len--;
+
+      /* Legacy Rust symbols also always end with a path segment
+         that encodes a 16 hex digit hash, i.e. '17h[a-f0-9]{16}'.
+         This early check, before any parse_ident calls, should
+         quickly filter out most C++ symbols unrelated to Rust. */
+      if (!(rdm.sym_len > 19
+            && !memcmp (&rdm.sym[rdm.sym_len - 19], "17h", 3)))
+        return 0;
+
+      do
+        {
+          ident = parse_ident (&rdm);
+          if (rdm.errored || !ident.ascii)
+            return 0;
+        }
+      while (rdm.next < rdm.sym_len);
+
+      /* The last path segment should be the hash. */
+      if (!is_legacy_prefixed_hash (ident))
+        return 0;
+
+      /* Reset the state for a second pass, to print the symbol. */
+      rdm.next = 0;
+      if (!rdm.verbose && rdm.sym_len > 19)
+        {
+          /* Hide the last segment, containing the hash, if not verbose. */
+          rdm.sym_len -= 19;
+        }
+
+      do
+        {
+          if (rdm.next > 0)
+            print_str (&rdm, "::", 2);
+
+          ident = parse_ident (&rdm);
+          print_ident (&rdm, ident);
+        }
+      while (rdm.next < rdm.sym_len);
+    }
+  else
+    return 0;
+
+  return !rdm.errored;
+}
+
+/* Growable string buffers. */
+struct str_buf
+{
+  char *ptr;
+  size_t len;
+  size_t cap;
+  int errored;
+};
+
+static void
+str_buf_reserve (struct str_buf *buf, size_t extra)
+{
+  size_t available, min_new_cap, new_cap;
+  char *new_ptr;
+
+  /* Allocation failed before. */
+  if (buf->errored)
+    return;
+
+  available = buf->cap - buf->len;
+
+  if (extra <= available)
+    return;
+
+  min_new_cap = buf->cap + (extra - available);
+
+  /* Check for overflows. */
+  if (min_new_cap < buf->cap)
+    {
+      buf->errored = 1;
+      return;
+    }
+
+  new_cap = buf->cap;
+
+  if (new_cap == 0)
+    new_cap = 4;
+
+  /* Double capacity until sufficiently large. */
+  while (new_cap < min_new_cap)
+    {
+      new_cap *= 2;
+
+      /* Check for overflows. */
+      if (new_cap < buf->cap)
+        {
+          buf->errored = 1;
+          return;
+        }
+    }
+
+  new_ptr = (char *)realloc (buf->ptr, new_cap);
+  if (new_ptr == NULL)
+    {
+      free (buf->ptr);
+      buf->ptr = NULL;
+      buf->len = 0;
+      buf->cap = 0;
+      buf->errored = 1;
+    }
+  else
+    {
+      buf->ptr = new_ptr;
+      buf->cap = new_cap;
+    }
+}
+
+static void
+str_buf_append (struct str_buf *buf, const char *data, size_t len)
+{
+  str_buf_reserve (buf, len);
+  if (buf->errored)
+    return;
+
+  memcpy (buf->ptr + buf->len, data, len);
+  buf->len += len;
+}
+
+static void
+str_buf_demangle_callback (const char *data, size_t len, void *opaque)
+{
+  str_buf_append ((struct str_buf *)opaque, data, len);
+}
+
+char *
+rust_demangle (const char *mangled, int options)
+{
+  struct str_buf out;
+  int success;
+
+  out.ptr = NULL;
+  out.len = 0;
+  out.cap = 0;
+  out.errored = 0;
+
+  success = rust_demangle_callback (mangled, options,
+                                    str_buf_demangle_callback, &out);
+
+  if (!success)
+    {
+      free (out.ptr);
+      return NULL;
+    }
+
+  str_buf_append (&out, "\0", 1);
+  return out.ptr;
+}
diff --git a/libiberty/rust-demangle.h b/libiberty/rust-demangle.h
deleted file mode 100644
index abf4c6cde55..00000000000
--- a/libiberty/rust-demangle.h
+++ /dev/null
@@ -1,45 +0,0 @@
-/* Internal demangler interface for the Rust programming language.
-   Copyright (C) 2016-2019 Free Software Foundation, Inc.
-   Written by David Tolnay (dtolnay@gmail.com).
-
-This file is part of the libiberty library.
-Libiberty is free software; you can redistribute it and/or
-modify it under the terms of the GNU Library General Public
-License as published by the Free Software Foundation; either
-version 2 of the License, or (at your option) any later version.
-
-In addition to the permissions in the GNU Library General Public
-License, the Free Software Foundation gives you unlimited permission
-to link the compiled version of this file into combinations with other
-programs, and to distribute those combinations without any restriction
-coming from the use of this file.  (The Library Public License
-restrictions do apply in other respects; for example, they cover
-modification of the file, and distribution when not linked into a
-combined executable.)
-
-Libiberty 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
-Library General Public License for more details.
-
-You should have received a copy of the GNU Library General Public
-License along with libiberty; see the file COPYING.LIB.
-If not, see <http://www.gnu.org/licenses/>.  */
-
-/* This file provides some definitions shared by cplus-dem.c and
-   rust-demangle.c.  It should not be included by any other files.  */
-
-/* Returns non-zero iff MANGLED is a rust mangled symbol.  MANGLED must
-   already have been demangled through cplus_demangle_v3.  If this function
-   returns non-zero then MANGLED can be demangled (in-place) using
-   RUST_DEMANGLE_SYM.  */
-extern int
-rust_is_mangled (const char *mangled);
-
-/* Demangles SYM (in-place) if RUST_IS_MANGLED returned non-zero for SYM.
-   If RUST_IS_MANGLED returned zero for SYM then RUST_DEMANGLE_SYM might
-   replace characters that cannot be demangled with '?' and might truncate
-   SYM.  After calling RUST_DEMANGLE_SYM SYM might be shorter, but never
-   larger.  */
-extern void
-rust_demangle_sym (char *sym);
Eduard-Mihai Burtescu Oct. 30, 2019, 4:46 p.m. UTC | #10
Ping: https://gcc.gnu.org/ml/gcc-patches/2019-10/msg01830.html
Original patch (without the early exit optimization): https://gcc.gnu.org/ml/gcc-patches/2019-10/msg01591.html

Thanks,
- Eddy B.

On Fri, Oct 25, 2019, at 3:44 PM, Eduard-Mihai Burtescu wrote:
> > This can be further optimized by using memcmp in place of strncmp, since from
> > the length check you know that you won't see the null terminator among the three
> > chars you're checking.
> 
> Fair enough, here's the combined changelog/diff, with memcmp:
Eduard-Mihai Burtescu Nov. 8, 2019, 5:02 p.m. UTC | #11
Ping #2 for https://gcc.gnu.org/ml/gcc-patches/2019-10/msg01830.html
Original patch (without the early exit optimization): https://gcc.gnu.org/ml/gcc-patches/2019-10/msg01591.html

Thanks,
- Eddy B.

On Wed, Oct 30, 2019, at 6:46 PM, Eduard-Mihai Burtescu wrote:
> Ping: https://gcc.gnu.org/ml/gcc-patches/2019-10/msg01830.html
> Original patch (without the early exit optimization): 
> https://gcc.gnu.org/ml/gcc-patches/2019-10/msg01591.html
> 
> Thanks,
> - Eddy B.
> 
> On Fri, Oct 25, 2019, at 3:44 PM, Eduard-Mihai Burtescu wrote:
> > > This can be further optimized by using memcmp in place of strncmp, since from
> > > the length check you know that you won't see the null terminator among the three
> > > chars you're checking.
> > 
> > Fair enough, here's the combined changelog/diff, with memcmp:
Li, Pan2 via Gcc-patches Nov. 8, 2019, 5:43 p.m. UTC | #12
On Fri, Nov 8, 2019 at 9:02 AM Eduard-Mihai Burtescu <eddyb@lyken.rs> wrote:
>
> Ping #2 for https://gcc.gnu.org/ml/gcc-patches/2019-10/msg01830.html
> Original patch (without the early exit optimization): https://gcc.gnu.org/ml/gcc-patches/2019-10/msg01591.html

Sorry for letting this slide.

Do we need the CHECK_OR and ERROR_AND macros?  Is there anything like
those elsewhere in the libiberty or GCC sources?  I would rather than
have ordinary code than obscure macros.

Ian


> On Wed, Oct 30, 2019, at 6:46 PM, Eduard-Mihai Burtescu wrote:
> > Ping: https://gcc.gnu.org/ml/gcc-patches/2019-10/msg01830.html
> > Original patch (without the early exit optimization):
> > https://gcc.gnu.org/ml/gcc-patches/2019-10/msg01591.html
> >
> > Thanks,
> > - Eddy B.
> >
> > On Fri, Oct 25, 2019, at 3:44 PM, Eduard-Mihai Burtescu wrote:
> > > > This can be further optimized by using memcmp in place of strncmp, since from
> > > > the length check you know that you won't see the null terminator among the three
> > > > chars you're checking.
> > >
> > > Fair enough, here's the combined changelog/diff, with memcmp:
Eduard-Mihai Burtescu Nov. 8, 2019, 7:16 p.m. UTC | #13
On Fri, Nov 8, 2019, at 7:43 PM, Ian Lance Taylor wrote:
> On Fri, Nov 8, 2019 at 9:02 AM Eduard-Mihai Burtescu <eddyb@lyken.rs> wrote:
> >
> > Ping #2 for https://gcc.gnu.org/ml/gcc-patches/2019-10/msg01830.html
> > Original patch (without the early exit optimization): https://gcc.gnu.org/ml/gcc-patches/2019-10/msg01591.html
> 
> Sorry for letting this slide.
> 
> Do we need the CHECK_OR and ERROR_AND macros?  Is there anything like
> those elsewhere in the libiberty or GCC sources?  I would rather than
> have ordinary code than obscure macros.

Good point, I was wondering about the macros but forgot to ask explicitly, they're the least usual (for C) part of this code, they arose from porting the demangler for the new format (most of which isn't even in the current patch) from Rust, where we have facilities for conveniently propagating errors, and I wanted to avoid using goto too much for this purpose.

Looking at https://gist.github.com/eddyb/c41a69378750a433767cf53fe2316768#file-rust-demangle-c I can see:
* 5 uses of ERROR_AND
* 20 uses of CHECK_OR
  * 7 of those are CHECK_OR (!rdm->errored, return ); which can just be if (rdm->errored) return;

So in the final code there'd be ~18 places that would need to set rdm->errored = 1; (and then return or goto cleanup).
That's not that bad, I guess, and I'd welcome any suggestions for how to clean up that.

For this current patch, however, there's only 3 uses total, so the macros are definitely overkill.
Assuming you'd want them removed, I took the liberty of doing that and here's the fixed patch:

2019-10-22  Eduard-Mihai Burtescu  <eddyb@lyken.rs>
include/ChangeLog:
	* demangle.h (rust_demangle_callback): Add.
libiberty/ChangeLog:
	* cplus-dem.c (cplus_demangle): Use rust_demangle directly.
	(rust_demangle): Remove.
	* rust-demangle.c (is_prefixed_hash): Rename to is_legacy_prefixed_hash.
	(parse_lower_hex_nibble): Rename to decode_lower_hex_nibble.
	(parse_legacy_escape): Rename to decode_legacy_escape.
	(rust_is_mangled): Remove.
	(struct rust_demangler): Add.
	(peek): Add.
	(next): Add.
	(struct rust_mangled_ident): Add.
	(parse_ident): Add.
	(rust_demangle_sym): Remove.
	(print_str): Add.
	(PRINT): Add.
	(print_ident): Add.
	(rust_demangle_callback): Add.
	(struct str_buf): Add.
	(str_buf_reserve): Add.
	(str_buf_append): Add.
	(str_buf_demangle_callback): Add.
	(rust_demangle): Add.
	* rust-demangle.h: Remove.

diff --git a/include/demangle.h b/include/demangle.h
index 06c32571d5c..ce7235d13f3 100644
--- a/include/demangle.h
+++ b/include/demangle.h
@@ -159,6 +159,11 @@ ada_demangle (const char *mangled, int options);
 extern char *
 dlang_demangle (const char *mangled, int options);
 
+extern int
+rust_demangle_callback (const char *mangled, int options,
+                        demangle_callbackref callback, void *opaque);
+
+
 extern char *
 rust_demangle (const char *mangled, int options);
 
diff --git a/libiberty/cplus-dem.c b/libiberty/cplus-dem.c
index a39e2bf2ed4..735a61d7a82 100644
--- a/libiberty/cplus-dem.c
+++ b/libiberty/cplus-dem.c
@@ -52,7 +52,6 @@ void * realloc ();
 #define CURRENT_DEMANGLING_STYLE options
 
 #include "libiberty.h"
-#include "rust-demangle.h"
 
 enum demangling_styles current_demangling_style = auto_demangling;
 
@@ -160,27 +159,20 @@ cplus_demangle (const char *mangled, int options)
   if ((options & DMGL_STYLE_MASK) == 0)
     options |= (int) current_demangling_style & DMGL_STYLE_MASK;
 
+  /* The Rust demangling is implemented elsewhere.
+     Legacy Rust symbols overlap with GNU_V3, so try Rust first.  */
+  if (RUST_DEMANGLING || AUTO_DEMANGLING)
+    {
+      ret = rust_demangle (mangled, options);
+      if (ret || RUST_DEMANGLING)
+        return ret;
+    }
+
   /* The V3 ABI demangling is implemented elsewhere.  */
-  if (GNU_V3_DEMANGLING || RUST_DEMANGLING || AUTO_DEMANGLING)
+  if (GNU_V3_DEMANGLING || AUTO_DEMANGLING)
     {
       ret = cplus_demangle_v3 (mangled, options);
-      if (GNU_V3_DEMANGLING)
-	return ret;
-
-      if (ret)
-	{
-	  /* Rust symbols are GNU_V3 mangled plus some extra subtitutions.
-	     The subtitutions are always smaller, so do in place changes.  */
-	  if (rust_is_mangled (ret))
-	    rust_demangle_sym (ret);
-	  else if (RUST_DEMANGLING)
-	    {
-	      free (ret);
-	      ret = NULL;
-	    }
-	}
-
-      if (ret || RUST_DEMANGLING)
+      if (ret || GNU_V3_DEMANGLING)
 	return ret;
     }
 
@@ -204,27 +196,6 @@ cplus_demangle (const char *mangled, int options)
   return (ret);
 }
 
-char *
-rust_demangle (const char *mangled, int options)
-{
-  /* Rust symbols are GNU_V3 mangled plus some extra subtitutions.  */
-  char *ret = cplus_demangle_v3 (mangled, options);
-
-  /* The Rust subtitutions are always smaller, so do in place changes.  */
-  if (ret != NULL)
-    {
-      if (rust_is_mangled (ret))
-	rust_demangle_sym (ret);
-      else
-	{
-	  free (ret);
-	  ret = NULL;
-	}
-    }
-
-  return ret;
-}
-
 /* Demangle ada names.  The encoding is documented in gcc/ada/exp_dbug.ads.  */
 
 char *
diff --git a/libiberty/rust-demangle.c b/libiberty/rust-demangle.c
index 6b62e6dbd80..fa9d4724010 100644
--- a/libiberty/rust-demangle.c
+++ b/libiberty/rust-demangle.c
@@ -33,9 +33,11 @@ If not, see <http://www.gnu.org/licenses/>.  */
 
 #include "safe-ctype.h"
 
+#include <inttypes.h>
 #include <sys/types.h>
 #include <string.h>
 #include <stdio.h>
+#include <stdlib.h>
 
 #ifdef HAVE_STRING_H
 #include <string.h>
@@ -47,207 +49,110 @@ extern void *memset(void *s, int c, size_t n);
 
 #include <demangle.h>
 #include "libiberty.h"
-#include "rust-demangle.h"
 
+struct rust_demangler
+{
+  const char *sym;
+  size_t sym_len;
 
-/* Mangled (legacy) Rust symbols look like this:
-     _$LT$std..sys..fd..FileDesc$u20$as$u20$core..ops..Drop$GT$::drop::hc68340e1baa4987a
-
-   The original symbol is:
-     <std::sys::fd::FileDesc as core::ops::Drop>::drop
-
-   The last component of the path is a 64-bit hash in lowercase hex,
-   prefixed with "h". Rust does not have a global namespace between
-   crates, an illusion which Rust maintains by using the hash to
-   distinguish things that would otherwise have the same symbol.
-
-   Any path component not starting with a XID_Start character is
-   prefixed with "_".
-
-   The following escape sequences are used:
-
-   ","  =>  $C$
-   "@"  =>  $SP$
-   "*"  =>  $BP$
-   "&"  =>  $RF$
-   "<"  =>  $LT$
-   ">"  =>  $GT$
-   "("  =>  $LP$
-   ")"  =>  $RP$
-   "\u{XY}"  =>  $uXY$
-
-   A double ".." means "::" and a single "." means "-".
-
-   The only characters allowed in the mangled symbol are a-zA-Z0-9 and _.:$  */
-
-static const char *hash_prefix = "::h";
-static const size_t hash_prefix_len = 3;
-static const size_t hash_len = 16;
-
-static int is_prefixed_hash (const char *start);
-static int parse_lower_hex_nibble (char nibble);
-static char parse_legacy_escape (const char **in);
+  void *callback_opaque;
+  demangle_callbackref callback;
 
-/* INPUT: sym: symbol that has been through C++ (gnu v3) demangling
+  /* Position of the next character to read from the symbol. */
+  size_t next;
 
-   This function looks for the following indicators:
+  /* Non-zero if any error occurred. */
+  int errored;
 
-   1. The hash must consist of "h" followed by 16 lowercase hex digits.
+  /* Non-zero if printing should be verbose (e.g. include hashes). */
+  int verbose;
 
-   2. As a sanity check, the hash must use between 5 and 15 of the 16
-      possible hex digits. This is true of 99.9998% of hashes so once
-      in your life you may see a false negative. The point is to
-      notice path components that could be Rust hashes but are
-      probably not, like "haaaaaaaaaaaaaaaa". In this case a false
-      positive (non-Rust symbol has an important path component
-      removed because it looks like a Rust hash) is worse than a false
-      negative (the rare Rust symbol is not demangled) so this sets
-      the balance in favor of false negatives.
+  /* Rust mangling version, with legacy mangling being -1. */
+  int version;
+};
 
-   3. There must be no characters other than a-zA-Z0-9 and _.:$  */
+/* Parsing functions. */
 
-int
-rust_is_mangled (const char *sym)
+static char
+peek (const struct rust_demangler *rdm)
 {
-  size_t len, len_without_hash;
-  const char *end;
+  if (rdm->next < rdm->sym_len)
+    return rdm->sym[rdm->next];
+  return 0;
+}
 
-  if (!sym)
-    return 0;
+static char
+next (struct rust_demangler *rdm)
+{
+  char c = peek (rdm);
+  if (!c)
+    rdm->errored = 1;
+  else
+    rdm->next++;
+  return c;
+}
 
-  len = strlen (sym);
-  if (len <= hash_prefix_len + hash_len)
-    /* Not long enough to contain "::h" + hash + something else */
-    return 0;
+struct rust_mangled_ident
+{
+  /* ASCII part of the identifier. */
+  const char *ascii;
+  size_t ascii_len;
+};
 
-  len_without_hash = len - (hash_prefix_len + hash_len);
-  if (!is_prefixed_hash (sym + len_without_hash))
-    return 0;
+static struct rust_mangled_ident
+parse_ident (struct rust_demangler *rdm)
+{
+  char c;
+  size_t start, len;
+  struct rust_mangled_ident ident;
 
-  end = sym + len_without_hash;
+  ident.ascii = NULL;
+  ident.ascii_len = 0;
 
-  while (sym < end)
+  c = next (rdm);
+  if (!ISDIGIT (c))
     {
-      if (*sym == '$' || *sym == '.' || *sym == '_' || *sym == ':'
-          || ISALNUM (*sym))
-        sym++;
-      else
-        return 0;
+      rdm->errored = 1;
+      return ident;
     }
+  len = c - '0';
 
-  return 1;
-}
-
-/* A hash is the prefix "::h" followed by 16 lowercase hex digits. The
-   hex digits must contain at least 5 distinct digits.  */
-
-static int
-is_prefixed_hash (const char *str)
-{
-  const char *end;
-  char seen[16];
-  size_t i;
-  int count, nibble;
-
-  if (strncmp (str, hash_prefix, hash_prefix_len))
-    return 0;
-  str += hash_prefix_len;
+  if (c != '0')
+    while (ISDIGIT (peek (rdm)))
+      len = len * 10 + (next (rdm) - '0');
 
-  memset (seen, 0, sizeof(seen));
-  for (end = str + hash_len; str < end; str++)
+  start = rdm->next;
+  rdm->next += len;
+  /* Check for overflows. */
+  if ((start > rdm->next) || (rdm->next > rdm->sym_len))
     {
-      nibble = parse_lower_hex_nibble (*str);
-      if (nibble < 0)
-        return 0;
-      seen[nibble] = 1;
+      rdm->errored = 1;
+      return ident;
     }
 
-  /* Count how many distinct digits seen */
-  count = 0;
-  for (i = 0; i < 16; i++)
-    if (seen[i])
-      count++;
+  ident.ascii = rdm->sym + start;
+  ident.ascii_len = len;
 
-  return count >= 5;
-}
+  if (ident.ascii_len == 0)
+    ident.ascii = NULL;
 
-/*
-  INPUT: sym: symbol for which rust_is_mangled(sym) returned 1.
+  return ident;
+}
 
-  The input is demangled in-place because the mangled name is always
-  longer than the demangled one.  */
+/* Printing functions. */
 
-void
-rust_demangle_sym (char *sym)
+static void
+print_str (struct rust_demangler *rdm, const char *data, size_t len)
 {
-  const char *in;
-  char *out;
-  const char *end;
-  char unescaped;
-
-  if (!sym)
-    return;
-
-  in = sym;
-  out = sym;
-  end = sym + strlen (sym) - (hash_prefix_len + hash_len);
-
-  while (in < end)
-    {
-      if (*in == '$')
-        {
-          unescaped = parse_legacy_escape (&in);
-          if (unescaped)
-            *out++ = unescaped;
-          else
-            /* unexpected escape sequence, skip the rest of this segment. */
-            while (in < end && *in != ':')
-              *out++ = *in++;
-        }
-      else if (*in == '_')
-        {
-          /* If this is the start of a path component and the next
-             character is an escape sequence, ignore the underscore. The
-             mangler inserts an underscore to make sure the path
-             component begins with a XID_Start character. */
-          if ((in == sym || in[-1] == ':') && in[1] == '$')
-            in++;
-          else
-            *out++ = *in++;
-        }
-      else if (*in == '.')
-        {
-          if (in[1] == '.')
-            {
-              /* ".." becomes "::" */
-              *out++ = ':';
-              *out++ = ':';
-              in += 2;
-            }
-          else
-            {
-              /* "." becomes "-" */
-              *out++ = '-';
-              in++;
-            }
-        }
-      else if (*in == ':' || ISALNUM (*in))
-        *out++ = *in++;
-      else
-        {
-          /* unexpected character in symbol, not rust_is_mangled.  */
-          *out++ = '?'; /* This is pretty lame, but it's hard to do better. */
-          *out = '\0';
-          return;
-        }
-    }
-
-  *out = '\0';
+  if (!rdm->errored)
+    rdm->callback (data, len, rdm->callback_opaque);
 }
 
+#define PRINT(s) print_str (rdm, s, strlen (s))
+
 /* Return a 0x0-0xf value if the char is 0-9a-f, and -1 otherwise. */
 static int
-parse_lower_hex_nibble (char nibble)
+decode_lower_hex_nibble (char nibble)
 {
   if ('0' <= nibble && nibble <= '9')
     return nibble - '0';
@@ -258,17 +163,17 @@ parse_lower_hex_nibble (char nibble)
 
 /* Return the unescaped character for a "$...$" escape, or 0 if invalid. */
 static char
-parse_legacy_escape (const char **in)
+decode_legacy_escape (const char *e, size_t len, size_t *out_len)
 {
   char c = 0;
-  const char *e;
   size_t escape_len = 0;
   int lo_nibble = -1, hi_nibble = -1;
 
-  if ((*in)[0] != '$')
+  if (len < 3 || e[0] != '$')
     return 0;
 
-  e = *in + 1;
+  e++;
+  len--;
 
   if (e[0] == 'C')
     {
@@ -276,7 +181,7 @@ parse_legacy_escape (const char **in)
 
       c = ',';
     }
-  else
+  else if (len > 2)
     {
       escape_len = 2;
 
@@ -294,14 +199,14 @@ parse_legacy_escape (const char **in)
         c = '(';
       else if (e[0] == 'R' && e[1] == 'P')
         c = ')';
-      else if (e[0] == 'u')
+      else if (e[0] == 'u' && len > 3)
         {
           escape_len = 3;
 
-          hi_nibble = parse_lower_hex_nibble (e[1]);
+          hi_nibble = decode_lower_hex_nibble (e[1]);
           if (hi_nibble < 0)
             return 0;
-          lo_nibble = parse_lower_hex_nibble (e[2]);
+          lo_nibble = decode_lower_hex_nibble (e[2]);
           if (lo_nibble < 0)
             return 0;
 
@@ -314,9 +219,314 @@ parse_legacy_escape (const char **in)
         }
     }
 
-  if (!c || e[escape_len] != '$')
+  if (!c || len <= escape_len || e[escape_len] != '$')
     return 0;
 
-  *in += 2 + escape_len;
+  *out_len = 2 + escape_len;
   return c;
 }
+
+static void
+print_ident (struct rust_demangler *rdm, struct rust_mangled_ident ident)
+{
+  char unescaped;
+  size_t len;
+
+  if (rdm->errored)
+    return;
+
+  if (rdm->version == -1)
+    {
+      /* Ignore leading underscores preceding escape sequences.
+         The mangler inserts an underscore to make sure the
+         identifier begins with a XID_Start character. */
+      if (ident.ascii_len >= 2 && ident.ascii[0] == '_'
+          && ident.ascii[1] == '$')
+        {
+          ident.ascii++;
+          ident.ascii_len--;
+        }
+
+      while (ident.ascii_len > 0)
+        {
+          /* Handle legacy escape sequences ("$...$", ".." or "."). */
+          if (ident.ascii[0] == '$')
+            {
+              unescaped
+                  = decode_legacy_escape (ident.ascii, ident.ascii_len, &len);
+              if (unescaped)
+                print_str (rdm, &unescaped, 1);
+              else
+                {
+                  /* Unexpected escape sequence, print the rest verbatim. */
+                  print_str (rdm, ident.ascii, ident.ascii_len);
+                  return;
+                }
+            }
+          else if (ident.ascii[0] == '.')
+            {
+              if (ident.ascii_len >= 2 && ident.ascii[1] == '.')
+                {
+                  /* ".." becomes "::" */
+                  PRINT ("::");
+                  len = 2;
+                }
+              else
+                {
+                  /* "." becomes "-" */
+                  PRINT ("-");
+                  len = 1;
+                }
+            }
+          else
+            {
+              /* Print everything before the next escape sequence, at once. */
+              for (len = 0; len < ident.ascii_len; len++)
+                if (ident.ascii[len] == '$' || ident.ascii[len] == '.')
+                  break;
+
+              print_str (rdm, ident.ascii, len);
+            }
+
+          ident.ascii += len;
+          ident.ascii_len -= len;
+        }
+
+      return;
+    }
+}
+
+/* A legacy hash is the prefix "h" followed by 16 lowercase hex digits.
+   The hex digits must contain at least 5 distinct digits. */
+static int
+is_legacy_prefixed_hash (struct rust_mangled_ident ident)
+{
+  uint16_t seen;
+  int nibble;
+  size_t i, count;
+
+  if (ident.ascii_len != 17 || ident.ascii[0] != 'h')
+    return 0;
+
+  seen = 0;
+  for (i = 0; i < 16; i++)
+    {
+      nibble = decode_lower_hex_nibble (ident.ascii[1 + i]);
+      if (nibble < 0)
+        return 0;
+      seen |= (uint16_t)1 << nibble;
+    }
+
+  /* Count how many distinct digits were seen. */
+  count = 0;
+  while (seen)
+    {
+      if (seen & 1)
+        count++;
+      seen >>= 1;
+    }
+
+  return count >= 5;
+}
+
+int
+rust_demangle_callback (const char *mangled, int options,
+                        demangle_callbackref callback, void *opaque)
+{
+  const char *p;
+  struct rust_demangler rdm;
+  struct rust_mangled_ident ident;
+
+  rdm.sym = mangled;
+  rdm.sym_len = 0;
+
+  rdm.callback_opaque = opaque;
+  rdm.callback = callback;
+
+  rdm.next = 0;
+  rdm.errored = 0;
+  rdm.verbose = (options & DMGL_VERBOSE) != 0;
+  rdm.version = 0;
+
+  /* Rust symbols always start with _ZN (legacy). */
+  if (rdm.sym[0] == '_' && rdm.sym[1] == 'Z' && rdm.sym[2] == 'N')
+    {
+      rdm.sym += 3;
+      rdm.version = -1;
+    }
+  else
+    return 0;
+
+  /* Legacy Rust symbols use only [_0-9a-zA-Z.:$] characters. */
+  for (p = rdm.sym; *p; p++)
+    {
+      rdm.sym_len++;
+
+      if (*p == '_' || ISALNUM (*p))
+        continue;
+
+      if (rdm.version == -1 && (*p == '$' || *p == '.' || *p == ':'))
+        continue;
+
+      return 0;
+    }
+
+  /* Legacy Rust symbols need to be handled separately. */
+  if (rdm.version == -1)
+    {
+      /* Legacy Rust symbols always end with E. */
+      if (!(rdm.sym_len > 0 && rdm.sym[rdm.sym_len - 1] == 'E'))
+        return 0;
+      rdm.sym_len--;
+
+      /* Legacy Rust symbols also always end with a path segment
+         that encodes a 16 hex digit hash, i.e. '17h[a-f0-9]{16}'.
+         This early check, before any parse_ident calls, should
+         quickly filter out most C++ symbols unrelated to Rust. */
+      if (!(rdm.sym_len > 19
+            && !memcmp (&rdm.sym[rdm.sym_len - 19], "17h", 3)))
+        return 0;
+
+      do
+        {
+          ident = parse_ident (&rdm);
+          if (rdm.errored || !ident.ascii)
+            return 0;
+        }
+      while (rdm.next < rdm.sym_len);
+
+      /* The last path segment should be the hash. */
+      if (!is_legacy_prefixed_hash (ident))
+        return 0;
+
+      /* Reset the state for a second pass, to print the symbol. */
+      rdm.next = 0;
+      if (!rdm.verbose && rdm.sym_len > 19)
+        {
+          /* Hide the last segment, containing the hash, if not verbose. */
+          rdm.sym_len -= 19;
+        }
+
+      do
+        {
+          if (rdm.next > 0)
+            print_str (&rdm, "::", 2);
+
+          ident = parse_ident (&rdm);
+          print_ident (&rdm, ident);
+        }
+      while (rdm.next < rdm.sym_len);
+    }
+  else
+    return 0;
+
+  return !rdm.errored;
+}
+
+/* Growable string buffers. */
+struct str_buf
+{
+  char *ptr;
+  size_t len;
+  size_t cap;
+  int errored;
+};
+
+static void
+str_buf_reserve (struct str_buf *buf, size_t extra)
+{
+  size_t available, min_new_cap, new_cap;
+  char *new_ptr;
+
+  /* Allocation failed before. */
+  if (buf->errored)
+    return;
+
+  available = buf->cap - buf->len;
+
+  if (extra <= available)
+    return;
+
+  min_new_cap = buf->cap + (extra - available);
+
+  /* Check for overflows. */
+  if (min_new_cap < buf->cap)
+    {
+      buf->errored = 1;
+      return;
+    }
+
+  new_cap = buf->cap;
+
+  if (new_cap == 0)
+    new_cap = 4;
+
+  /* Double capacity until sufficiently large. */
+  while (new_cap < min_new_cap)
+    {
+      new_cap *= 2;
+
+      /* Check for overflows. */
+      if (new_cap < buf->cap)
+        {
+          buf->errored = 1;
+          return;
+        }
+    }
+
+  new_ptr = (char *)realloc (buf->ptr, new_cap);
+  if (new_ptr == NULL)
+    {
+      free (buf->ptr);
+      buf->ptr = NULL;
+      buf->len = 0;
+      buf->cap = 0;
+      buf->errored = 1;
+    }
+  else
+    {
+      buf->ptr = new_ptr;
+      buf->cap = new_cap;
+    }
+}
+
+static void
+str_buf_append (struct str_buf *buf, const char *data, size_t len)
+{
+  str_buf_reserve (buf, len);
+  if (buf->errored)
+    return;
+
+  memcpy (buf->ptr + buf->len, data, len);
+  buf->len += len;
+}
+
+static void
+str_buf_demangle_callback (const char *data, size_t len, void *opaque)
+{
+  str_buf_append ((struct str_buf *)opaque, data, len);
+}
+
+char *
+rust_demangle (const char *mangled, int options)
+{
+  struct str_buf out;
+  int success;
+
+  out.ptr = NULL;
+  out.len = 0;
+  out.cap = 0;
+  out.errored = 0;
+
+  success = rust_demangle_callback (mangled, options,
+                                    str_buf_demangle_callback, &out);
+
+  if (!success)
+    {
+      free (out.ptr);
+      return NULL;
+    }
+
+  str_buf_append (&out, "\0", 1);
+  return out.ptr;
+}
diff --git a/libiberty/rust-demangle.h b/libiberty/rust-demangle.h
deleted file mode 100644
index abf4c6cde55..00000000000
--- a/libiberty/rust-demangle.h
+++ /dev/null
@@ -1,45 +0,0 @@
-/* Internal demangler interface for the Rust programming language.
-   Copyright (C) 2016-2019 Free Software Foundation, Inc.
-   Written by David Tolnay (dtolnay@gmail.com).
-
-This file is part of the libiberty library.
-Libiberty is free software; you can redistribute it and/or
-modify it under the terms of the GNU Library General Public
-License as published by the Free Software Foundation; either
-version 2 of the License, or (at your option) any later version.
-
-In addition to the permissions in the GNU Library General Public
-License, the Free Software Foundation gives you unlimited permission
-to link the compiled version of this file into combinations with other
-programs, and to distribute those combinations without any restriction
-coming from the use of this file.  (The Library Public License
-restrictions do apply in other respects; for example, they cover
-modification of the file, and distribution when not linked into a
-combined executable.)
-
-Libiberty 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
-Library General Public License for more details.
-
-You should have received a copy of the GNU Library General Public
-License along with libiberty; see the file COPYING.LIB.
-If not, see <http://www.gnu.org/licenses/>.  */
-
-/* This file provides some definitions shared by cplus-dem.c and
-   rust-demangle.c.  It should not be included by any other files.  */
-
-/* Returns non-zero iff MANGLED is a rust mangled symbol.  MANGLED must
-   already have been demangled through cplus_demangle_v3.  If this function
-   returns non-zero then MANGLED can be demangled (in-place) using
-   RUST_DEMANGLE_SYM.  */
-extern int
-rust_is_mangled (const char *mangled);
-
-/* Demangles SYM (in-place) if RUST_IS_MANGLED returned non-zero for SYM.
-   If RUST_IS_MANGLED returned zero for SYM then RUST_DEMANGLE_SYM might
-   replace characters that cannot be demangled with '?' and might truncate
-   SYM.  After calling RUST_DEMANGLE_SYM SYM might be shorter, but never
-   larger.  */
-extern void
-rust_demangle_sym (char *sym);
Li, Pan2 via Gcc-patches Nov. 8, 2019, 7:23 p.m. UTC | #14
On Fri, Nov 8, 2019 at 11:17 AM Eduard-Mihai Burtescu <eddyb@lyken.rs> wrote:
>
> On Fri, Nov 8, 2019, at 7:43 PM, Ian Lance Taylor wrote:
> > On Fri, Nov 8, 2019 at 9:02 AM Eduard-Mihai Burtescu <eddyb@lyken.rs> wrote:
> > >
> > > Ping #2 for https://gcc.gnu.org/ml/gcc-patches/2019-10/msg01830.html
> > > Original patch (without the early exit optimization): https://gcc.gnu.org/ml/gcc-patches/2019-10/msg01591.html
> >
> > Sorry for letting this slide.
> >
> > Do we need the CHECK_OR and ERROR_AND macros?  Is there anything like
> > those elsewhere in the libiberty or GCC sources?  I would rather than
> > have ordinary code than obscure macros.
>
> Good point, I was wondering about the macros but forgot to ask explicitly, they're the least usual (for C) part of this code, they arose from porting the demangler for the new format (most of which isn't even in the current patch) from Rust, where we have facilities for conveniently propagating errors, and I wanted to avoid using goto too much for this purpose.
>
> Looking at https://gist.github.com/eddyb/c41a69378750a433767cf53fe2316768#file-rust-demangle-c I can see:
> * 5 uses of ERROR_AND
> * 20 uses of CHECK_OR
>   * 7 of those are CHECK_OR (!rdm->errored, return ); which can just be if (rdm->errored) return;
>
> So in the final code there'd be ~18 places that would need to set rdm->errored = 1; (and then return or goto cleanup).
> That's not that bad, I guess, and I'd welcome any suggestions for how to clean up that.
>
> For this current patch, however, there's only 3 uses total, so the macros are definitely overkill.
> Assuming you'd want them removed, I took the liberty of doing that and here's the fixed patch:
>
> 2019-10-22  Eduard-Mihai Burtescu  <eddyb@lyken.rs>
> include/ChangeLog:
>         * demangle.h (rust_demangle_callback): Add.
> libiberty/ChangeLog:
>         * cplus-dem.c (cplus_demangle): Use rust_demangle directly.
>         (rust_demangle): Remove.
>         * rust-demangle.c (is_prefixed_hash): Rename to is_legacy_prefixed_hash.
>         (parse_lower_hex_nibble): Rename to decode_lower_hex_nibble.
>         (parse_legacy_escape): Rename to decode_legacy_escape.
>         (rust_is_mangled): Remove.
>         (struct rust_demangler): Add.
>         (peek): Add.
>         (next): Add.
>         (struct rust_mangled_ident): Add.
>         (parse_ident): Add.
>         (rust_demangle_sym): Remove.
>         (print_str): Add.
>         (PRINT): Add.
>         (print_ident): Add.
>         (rust_demangle_callback): Add.
>         (struct str_buf): Add.
>         (str_buf_reserve): Add.
>         (str_buf_append): Add.
>         (str_buf_demangle_callback): Add.
>         (rust_demangle): Add.
>         * rust-demangle.h: Remove.

This is OK.

Thanks.

Ian
Eduard-Mihai Burtescu Nov. 15, 2019, 8:52 p.m. UTC | #15
> This is OK.
> 
> Thanks.
> 
> Ian
>

Ping for someone to commit this (as mentioned before, I have no commit access).
I've tried pinging some people on IRC, but saw no response.

Approved version of the patch: https://gcc.gnu.org/ml/gcc-patches/2019-11/msg00647.html

Original email, containing the description: https://gcc.gnu.org/ml/gcc-patches/2019-10/msg01591.html
(ignore the original patch, two changes had to be made before it was approved)

Thanks,
- Eddy B.
Jeff Law Nov. 16, 2019, 3:32 p.m. UTC | #16
On 11/8/19 12:16 PM, Eduard-Mihai Burtescu wrote:
> 2019-10-22  Eduard-Mihai Burtescu  <eddyb@lyken.rs>
> include/ChangeLog:
> 	* demangle.h (rust_demangle_callback): Add.
> libiberty/ChangeLog:
> 	* cplus-dem.c (cplus_demangle): Use rust_demangle directly.
> 	(rust_demangle): Remove.
> 	* rust-demangle.c (is_prefixed_hash): Rename to is_legacy_prefixed_hash.
> 	(parse_lower_hex_nibble): Rename to decode_lower_hex_nibble.
> 	(parse_legacy_escape): Rename to decode_legacy_escape.
> 	(rust_is_mangled): Remove.
> 	(struct rust_demangler): Add.
> 	(peek): Add.
> 	(next): Add.
> 	(struct rust_mangled_ident): Add.
> 	(parse_ident): Add.
> 	(rust_demangle_sym): Remove.
> 	(print_str): Add.
> 	(PRINT): Add.
> 	(print_ident): Add.
> 	(rust_demangle_callback): Add.
> 	(struct str_buf): Add.
> 	(str_buf_reserve): Add.
> 	(str_buf_append): Add.
> 	(str_buf_demangle_callback): Add.
> 	(rust_demangle): Add.
> 	* rust-demangle.h: Remove.
THanks.  Installed.
jeff
diff mbox series

Patch

diff --git a/include/demangle.h b/include/demangle.h
index 06c32571d5c..ce7235d13f3 100644
--- a/include/demangle.h
+++ b/include/demangle.h
@@ -159,6 +159,11 @@  ada_demangle (const char *mangled, int options);
 extern char *
 dlang_demangle (const char *mangled, int options);
 
+extern int
+rust_demangle_callback (const char *mangled, int options,
+                        demangle_callbackref callback, void *opaque);
+
+
 extern char *
 rust_demangle (const char *mangled, int options);
 
diff --git a/libiberty/cplus-dem.c b/libiberty/cplus-dem.c
index a39e2bf2ed4..735a61d7a82 100644
--- a/libiberty/cplus-dem.c
+++ b/libiberty/cplus-dem.c
@@ -52,7 +52,6 @@  void * realloc ();
 #define CURRENT_DEMANGLING_STYLE options
 
 #include "libiberty.h"
-#include "rust-demangle.h"
 
 enum demangling_styles current_demangling_style = auto_demangling;
 
@@ -160,27 +159,20 @@  cplus_demangle (const char *mangled, int options)
   if ((options & DMGL_STYLE_MASK) == 0)
     options |= (int) current_demangling_style & DMGL_STYLE_MASK;
 
+  /* The Rust demangling is implemented elsewhere.
+     Legacy Rust symbols overlap with GNU_V3, so try Rust first.  */
+  if (RUST_DEMANGLING || AUTO_DEMANGLING)
+    {
+      ret = rust_demangle (mangled, options);
+      if (ret || RUST_DEMANGLING)
+        return ret;
+    }
+
   /* The V3 ABI demangling is implemented elsewhere.  */
-  if (GNU_V3_DEMANGLING || RUST_DEMANGLING || AUTO_DEMANGLING)
+  if (GNU_V3_DEMANGLING || AUTO_DEMANGLING)
     {
       ret = cplus_demangle_v3 (mangled, options);
-      if (GNU_V3_DEMANGLING)
-	return ret;
-
-      if (ret)
-	{
-	  /* Rust symbols are GNU_V3 mangled plus some extra subtitutions.
-	     The subtitutions are always smaller, so do in place changes.  */
-	  if (rust_is_mangled (ret))
-	    rust_demangle_sym (ret);
-	  else if (RUST_DEMANGLING)
-	    {
-	      free (ret);
-	      ret = NULL;
-	    }
-	}
-
-      if (ret || RUST_DEMANGLING)
+      if (ret || GNU_V3_DEMANGLING)
 	return ret;
     }
 
@@ -204,27 +196,6 @@  cplus_demangle (const char *mangled, int options)
   return (ret);
 }
 
-char *
-rust_demangle (const char *mangled, int options)
-{
-  /* Rust symbols are GNU_V3 mangled plus some extra subtitutions.  */
-  char *ret = cplus_demangle_v3 (mangled, options);
-
-  /* The Rust subtitutions are always smaller, so do in place changes.  */
-  if (ret != NULL)
-    {
-      if (rust_is_mangled (ret))
-	rust_demangle_sym (ret);
-      else
-	{
-	  free (ret);
-	  ret = NULL;
-	}
-    }
-
-  return ret;
-}
-
 /* Demangle ada names.  The encoding is documented in gcc/ada/exp_dbug.ads.  */
 
 char *
diff --git a/libiberty/rust-demangle.c b/libiberty/rust-demangle.c
index 6b62e6dbd80..da707dbab9b 100644
--- a/libiberty/rust-demangle.c
+++ b/libiberty/rust-demangle.c
@@ -33,9 +33,11 @@  If not, see <http://www.gnu.org/licenses/>.  */
 
 #include "safe-ctype.h"
 
+#include <inttypes.h>
 #include <sys/types.h>
 #include <string.h>
 #include <stdio.h>
+#include <stdlib.h>
 
 #ifdef HAVE_STRING_H
 #include <string.h>
@@ -47,207 +49,115 @@  extern void *memset(void *s, int c, size_t n);
 
 #include <demangle.h>
 #include "libiberty.h"
-#include "rust-demangle.h"
 
-
-/* Mangled (legacy) Rust symbols look like this:
-     _$LT$std..sys..fd..FileDesc$u20$as$u20$core..ops..Drop$GT$::drop::hc68340e1baa4987a
-
-   The original symbol is:
-     <std::sys::fd::FileDesc as core::ops::Drop>::drop
-
-   The last component of the path is a 64-bit hash in lowercase hex,
-   prefixed with "h". Rust does not have a global namespace between
-   crates, an illusion which Rust maintains by using the hash to
-   distinguish things that would otherwise have the same symbol.
-
-   Any path component not starting with a XID_Start character is
-   prefixed with "_".
-
-   The following escape sequences are used:
-
-   ","  =>  $C$
-   "@"  =>  $SP$
-   "*"  =>  $BP$
-   "&"  =>  $RF$
-   "<"  =>  $LT$
-   ">"  =>  $GT$
-   "("  =>  $LP$
-   ")"  =>  $RP$
-   "\u{XY}"  =>  $uXY$
-
-   A double ".." means "::" and a single "." means "-".
-
-   The only characters allowed in the mangled symbol are a-zA-Z0-9 and _.:$  */
-
-static const char *hash_prefix = "::h";
-static const size_t hash_prefix_len = 3;
-static const size_t hash_len = 16;
-
-static int is_prefixed_hash (const char *start);
-static int parse_lower_hex_nibble (char nibble);
-static char parse_legacy_escape (const char **in);
-
-/* INPUT: sym: symbol that has been through C++ (gnu v3) demangling
-
-   This function looks for the following indicators:
-
-   1. The hash must consist of "h" followed by 16 lowercase hex digits.
-
-   2. As a sanity check, the hash must use between 5 and 15 of the 16
-      possible hex digits. This is true of 99.9998% of hashes so once
-      in your life you may see a false negative. The point is to
-      notice path components that could be Rust hashes but are
-      probably not, like "haaaaaaaaaaaaaaaa". In this case a false
-      positive (non-Rust symbol has an important path component
-      removed because it looks like a Rust hash) is worse than a false
-      negative (the rare Rust symbol is not demangled) so this sets
-      the balance in favor of false negatives.
-
-   3. There must be no characters other than a-zA-Z0-9 and _.:$  */
-
-int
-rust_is_mangled (const char *sym)
+struct rust_demangler
 {
-  size_t len, len_without_hash;
-  const char *end;
-
-  if (!sym)
-    return 0;
+  const char *sym;
+  size_t sym_len;
+
+  void *callback_opaque;
+  demangle_callbackref callback;
+
+  /* Position of the next character to read from the symbol. */
+  size_t next;
+
+  /* Non-zero if any error occurred. */
+  int errored;
+
+  /* Non-zero if printing should be verbose (e.g. include hashes). */
+  int verbose;
+
+  /* Rust mangling version, with legacy mangling being -1. */
+  int version;
+};
+
+#define ERROR_AND(x)                                                          \
+  do                                                                          \
+    {                                                                         \
+      rdm->errored = 1;                                                       \
+      x;                                                                      \
+    }                                                                         \
+  while (0)
+#define CHECK_OR(cond, x)                                                     \
+  do                                                                          \
+    {                                                                         \
+      if (!(cond))                                                            \
+        ERROR_AND (x);                                                        \
+    }                                                                         \
+  while (0)
+
+/* Parsing functions. */
 
-  len = strlen (sym);
-  if (len <= hash_prefix_len + hash_len)
-    /* Not long enough to contain "::h" + hash + something else */
-    return 0;
-
-  len_without_hash = len - (hash_prefix_len + hash_len);
-  if (!is_prefixed_hash (sym + len_without_hash))
-    return 0;
-
-  end = sym + len_without_hash;
-
-  while (sym < end)
-    {
-      if (*sym == '$' || *sym == '.' || *sym == '_' || *sym == ':'
-          || ISALNUM (*sym))
-        sym++;
-      else
-        return 0;
-    }
-
-  return 1;
+static char
+peek (const struct rust_demangler *rdm)
+{
+  if (rdm->next < rdm->sym_len)
+    return rdm->sym[rdm->next];
+  return 0;
 }
 
-/* A hash is the prefix "::h" followed by 16 lowercase hex digits. The
-   hex digits must contain at least 5 distinct digits.  */
-
-static int
-is_prefixed_hash (const char *str)
+static char
+next (struct rust_demangler *rdm)
 {
-  const char *end;
-  char seen[16];
-  size_t i;
-  int count, nibble;
+  char c = peek (rdm);
+  CHECK_OR (c, return 0);
+  rdm->next++;
+  return c;
+}
 
-  if (strncmp (str, hash_prefix, hash_prefix_len))
-    return 0;
-  str += hash_prefix_len;
+struct rust_mangled_ident
+{
+  /* ASCII part of the identifier. */
+  const char *ascii;
+  size_t ascii_len;
+};
 
-  memset (seen, 0, sizeof(seen));
-  for (end = str + hash_len; str < end; str++)
-    {
-      nibble = parse_lower_hex_nibble (*str);
-      if (nibble < 0)
-        return 0;
-      seen[nibble] = 1;
-    }
+static struct rust_mangled_ident
+parse_ident (struct rust_demangler *rdm)
+{
+  char c;
+  size_t start, len;
+  struct rust_mangled_ident ident;
 
-  /* Count how many distinct digits seen */
-  count = 0;
-  for (i = 0; i < 16; i++)
-    if (seen[i])
-      count++;
+  ident.ascii = NULL;
+  ident.ascii_len = 0;
 
-  return count >= 5;
-}
+  c = next (rdm);
+  CHECK_OR (ISDIGIT (c), return ident);
+  len = c - '0';
 
-/*
-  INPUT: sym: symbol for which rust_is_mangled(sym) returned 1.
+  if (c != '0')
+    while (ISDIGIT (peek (rdm)))
+      len = len * 10 + (next (rdm) - '0');
 
-  The input is demangled in-place because the mangled name is always
-  longer than the demangled one.  */
+  start = rdm->next;
+  rdm->next += len;
+  /* Check for overflows. */
+  CHECK_OR ((start <= rdm->next) && (rdm->next <= rdm->sym_len), return ident);
 
-void
-rust_demangle_sym (char *sym)
-{
-  const char *in;
-  char *out;
-  const char *end;
-  char unescaped;
+  ident.ascii = rdm->sym + start;
+  ident.ascii_len = len;
 
-  if (!sym)
-    return;
+  if (ident.ascii_len == 0)
+    ident.ascii = NULL;
 
-  in = sym;
-  out = sym;
-  end = sym + strlen (sym) - (hash_prefix_len + hash_len);
+  return ident;
+}
 
-  while (in < end)
-    {
-      if (*in == '$')
-        {
-          unescaped = parse_legacy_escape (&in);
-          if (unescaped)
-            *out++ = unescaped;
-          else
-            /* unexpected escape sequence, skip the rest of this segment. */
-            while (in < end && *in != ':')
-              *out++ = *in++;
-        }
-      else if (*in == '_')
-        {
-          /* If this is the start of a path component and the next
-             character is an escape sequence, ignore the underscore. The
-             mangler inserts an underscore to make sure the path
-             component begins with a XID_Start character. */
-          if ((in == sym || in[-1] == ':') && in[1] == '$')
-            in++;
-          else
-            *out++ = *in++;
-        }
-      else if (*in == '.')
-        {
-          if (in[1] == '.')
-            {
-              /* ".." becomes "::" */
-              *out++ = ':';
-              *out++ = ':';
-              in += 2;
-            }
-          else
-            {
-              /* "." becomes "-" */
-              *out++ = '-';
-              in++;
-            }
-        }
-      else if (*in == ':' || ISALNUM (*in))
-        *out++ = *in++;
-      else
-        {
-          /* unexpected character in symbol, not rust_is_mangled.  */
-          *out++ = '?'; /* This is pretty lame, but it's hard to do better. */
-          *out = '\0';
-          return;
-        }
-    }
+/* Printing functions. */
 
-  *out = '\0';
+static void
+print_str (struct rust_demangler *rdm, const char *data, size_t len)
+{
+  if (!rdm->errored)
+    rdm->callback (data, len, rdm->callback_opaque);
 }
 
+#define PRINT(s) print_str (rdm, s, strlen (s))
+
 /* Return a 0x0-0xf value if the char is 0-9a-f, and -1 otherwise. */
 static int
-parse_lower_hex_nibble (char nibble)
+decode_lower_hex_nibble (char nibble)
 {
   if ('0' <= nibble && nibble <= '9')
     return nibble - '0';
@@ -258,17 +168,17 @@  parse_lower_hex_nibble (char nibble)
 
 /* Return the unescaped character for a "$...$" escape, or 0 if invalid. */
 static char
-parse_legacy_escape (const char **in)
+decode_legacy_escape (const char *e, size_t len, size_t *out_len)
 {
   char c = 0;
-  const char *e;
   size_t escape_len = 0;
   int lo_nibble = -1, hi_nibble = -1;
 
-  if ((*in)[0] != '$')
+  if (len < 3 || e[0] != '$')
     return 0;
 
-  e = *in + 1;
+  e++;
+  len--;
 
   if (e[0] == 'C')
     {
@@ -276,7 +186,7 @@  parse_legacy_escape (const char **in)
 
       c = ',';
     }
-  else
+  else if (len > 2)
     {
       escape_len = 2;
 
@@ -294,14 +204,14 @@  parse_legacy_escape (const char **in)
         c = '(';
       else if (e[0] == 'R' && e[1] == 'P')
         c = ')';
-      else if (e[0] == 'u')
+      else if (e[0] == 'u' && len > 3)
         {
           escape_len = 3;
 
-          hi_nibble = parse_lower_hex_nibble (e[1]);
+          hi_nibble = decode_lower_hex_nibble (e[1]);
           if (hi_nibble < 0)
             return 0;
-          lo_nibble = parse_lower_hex_nibble (e[2]);
+          lo_nibble = decode_lower_hex_nibble (e[2]);
           if (lo_nibble < 0)
             return 0;
 
@@ -314,9 +224,306 @@  parse_legacy_escape (const char **in)
         }
     }
 
-  if (!c || e[escape_len] != '$')
+  if (!c || len <= escape_len || e[escape_len] != '$')
     return 0;
 
-  *in += 2 + escape_len;
+  *out_len = 2 + escape_len;
   return c;
 }
+
+static void
+print_ident (struct rust_demangler *rdm, struct rust_mangled_ident ident)
+{
+  char unescaped;
+  size_t len;
+
+  if (rdm->errored)
+    return;
+
+  if (rdm->version == -1)
+    {
+      /* Ignore leading underscores preceding escape sequences.
+         The mangler inserts an underscore to make sure the
+         identifier begins with a XID_Start character. */
+      if (ident.ascii_len >= 2 && ident.ascii[0] == '_'
+          && ident.ascii[1] == '$')
+        {
+          ident.ascii++;
+          ident.ascii_len--;
+        }
+
+      while (ident.ascii_len > 0)
+        {
+          /* Handle legacy escape sequences ("$...$", ".." or "."). */
+          if (ident.ascii[0] == '$')
+            {
+              unescaped
+                  = decode_legacy_escape (ident.ascii, ident.ascii_len, &len);
+              if (unescaped)
+                print_str (rdm, &unescaped, 1);
+              else
+                {
+                  /* Unexpected escape sequence, print the rest verbatim. */
+                  print_str (rdm, ident.ascii, ident.ascii_len);
+                  return;
+                }
+            }
+          else if (ident.ascii[0] == '.')
+            {
+              if (ident.ascii_len >= 2 && ident.ascii[1] == '.')
+                {
+                  /* ".." becomes "::" */
+                  PRINT ("::");
+                  len = 2;
+                }
+              else
+                {
+                  /* "." becomes "-" */
+                  PRINT ("-");
+                  len = 1;
+                }
+            }
+          else
+            {
+              /* Print everything before the next escape sequence, at once. */
+              for (len = 0; len < ident.ascii_len; len++)
+                if (ident.ascii[len] == '$' || ident.ascii[len] == '.')
+                  break;
+
+              print_str (rdm, ident.ascii, len);
+            }
+
+          ident.ascii += len;
+          ident.ascii_len -= len;
+        }
+
+      return;
+    }
+}
+
+/* A legacy hash is the prefix "h" followed by 16 lowercase hex digits.
+   The hex digits must contain at least 5 distinct digits. */
+static int
+is_legacy_prefixed_hash (struct rust_mangled_ident ident)
+{
+  uint16_t seen;
+  int nibble;
+  size_t i, count;
+
+  if (ident.ascii_len != 17 || ident.ascii[0] != 'h')
+    return 0;
+
+  seen = 0;
+  for (i = 0; i < 16; i++)
+    {
+      nibble = decode_lower_hex_nibble (ident.ascii[1 + i]);
+      if (nibble < 0)
+        return 0;
+      seen |= (uint16_t)1 << nibble;
+    }
+
+  /* Count how many distinct digits were seen. */
+  count = 0;
+  while (seen)
+    {
+      if (seen & 1)
+        count++;
+      seen >>= 1;
+    }
+
+  return count >= 5;
+}
+
+int
+rust_demangle_callback (const char *mangled, int options,
+                        demangle_callbackref callback, void *opaque)
+{
+  const char *p;
+  struct rust_demangler rdm;
+  struct rust_mangled_ident ident;
+
+  rdm.sym = mangled;
+  rdm.sym_len = 0;
+
+  rdm.callback_opaque = opaque;
+  rdm.callback = callback;
+
+  rdm.next = 0;
+  rdm.errored = 0;
+  rdm.verbose = (options & DMGL_VERBOSE) != 0;
+  rdm.version = 0;
+
+  /* Rust symbols always start with _ZN (legacy). */
+  if (rdm.sym[0] == '_' && rdm.sym[1] == 'Z' && rdm.sym[2] == 'N')
+    {
+      rdm.sym += 3;
+      rdm.version = -1;
+    }
+  else
+    return 0;
+
+  /* Legacy Rust symbols use only [_0-9a-zA-Z.:$] characters. */
+  for (p = rdm.sym; *p; p++)
+    {
+      rdm.sym_len++;
+
+      if (*p == '_' || ISALNUM (*p))
+        continue;
+
+      if (rdm.version == -1 && (*p == '$' || *p == '.' || *p == ':'))
+        continue;
+
+      return 0;
+    }
+
+  /* Legacy Rust symbols need to be handled separately. */
+  if (rdm.version == -1)
+    {
+      /* Legacy Rust symbols always end with E. */
+      if (!(rdm.sym_len > 0 && rdm.sym[rdm.sym_len - 1] == 'E'))
+        return 0;
+      rdm.sym_len--;
+
+      do
+        {
+          ident = parse_ident (&rdm);
+          if (rdm.errored || !ident.ascii)
+            return 0;
+        }
+      while (rdm.next < rdm.sym_len);
+
+      /* The last path segment should be the hash. */
+      if (!is_legacy_prefixed_hash (ident))
+        return 0;
+
+      /* Reset the state for a second pass, to print the symbol. */
+      rdm.next = 0;
+      if (!rdm.verbose && rdm.sym_len > 19)
+        {
+          /* Hide the last segment, containing the hash, if not verbose. */
+          rdm.sym_len -= 19;
+        }
+
+      do
+        {
+          if (rdm.next > 0)
+            print_str (&rdm, "::", 2);
+
+          ident = parse_ident (&rdm);
+          print_ident (&rdm, ident);
+        }
+      while (rdm.next < rdm.sym_len);
+    }
+  else
+    return 0;
+
+  return !rdm.errored;
+}
+
+/* Growable string buffers. */
+struct str_buf
+{
+  char *ptr;
+  size_t len;
+  size_t cap;
+  int errored;
+};
+
+static void
+str_buf_reserve (struct str_buf *buf, size_t extra)
+{
+  size_t available, min_new_cap, new_cap;
+  char *new_ptr;
+
+  /* Allocation failed before. */
+  if (buf->errored)
+    return;
+
+  available = buf->cap - buf->len;
+
+  if (extra <= available)
+    return;
+
+  min_new_cap = buf->cap + (extra - available);
+
+  /* Check for overflows. */
+  if (min_new_cap < buf->cap)
+    {
+      buf->errored = 1;
+      return;
+    }
+
+  new_cap = buf->cap;
+
+  if (new_cap == 0)
+    new_cap = 4;
+
+  /* Double capacity until sufficiently large. */
+  while (new_cap < min_new_cap)
+    {
+      new_cap *= 2;
+
+      /* Check for overflows. */
+      if (new_cap < buf->cap)
+        {
+          buf->errored = 1;
+          return;
+        }
+    }
+
+  new_ptr = (char *)realloc (buf->ptr, new_cap);
+  if (new_ptr == NULL)
+    {
+      free (buf->ptr);
+      buf->ptr = NULL;
+      buf->len = 0;
+      buf->cap = 0;
+      buf->errored = 1;
+    }
+  else
+    {
+      buf->ptr = new_ptr;
+      buf->cap = new_cap;
+    }
+}
+
+static void
+str_buf_append (struct str_buf *buf, const char *data, size_t len)
+{
+  str_buf_reserve (buf, len);
+  if (buf->errored)
+    return;
+
+  memcpy (buf->ptr + buf->len, data, len);
+  buf->len += len;
+}
+
+static void
+str_buf_demangle_callback (const char *data, size_t len, void *opaque)
+{
+  str_buf_append ((struct str_buf *)opaque, data, len);
+}
+
+char *
+rust_demangle (const char *mangled, int options)
+{
+  struct str_buf out;
+  int success;
+
+  out.ptr = NULL;
+  out.len = 0;
+  out.cap = 0;
+  out.errored = 0;
+
+  success = rust_demangle_callback (mangled, options,
+                                    str_buf_demangle_callback, &out);
+
+  if (!success)
+    {
+      free (out.ptr);
+      return NULL;
+    }
+
+  str_buf_append (&out, "\0", 1);
+  return out.ptr;
+}
diff --git a/libiberty/rust-demangle.h b/libiberty/rust-demangle.h
deleted file mode 100644
index abf4c6cde55..00000000000
--- a/libiberty/rust-demangle.h
+++ /dev/null
@@ -1,45 +0,0 @@ 
-/* Internal demangler interface for the Rust programming language.
-   Copyright (C) 2016-2019 Free Software Foundation, Inc.
-   Written by David Tolnay (dtolnay@gmail.com).
-
-This file is part of the libiberty library.
-Libiberty is free software; you can redistribute it and/or
-modify it under the terms of the GNU Library General Public
-License as published by the Free Software Foundation; either
-version 2 of the License, or (at your option) any later version.
-
-In addition to the permissions in the GNU Library General Public
-License, the Free Software Foundation gives you unlimited permission
-to link the compiled version of this file into combinations with other
-programs, and to distribute those combinations without any restriction
-coming from the use of this file.  (The Library Public License
-restrictions do apply in other respects; for example, they cover
-modification of the file, and distribution when not linked into a
-combined executable.)
-
-Libiberty 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
-Library General Public License for more details.
-
-You should have received a copy of the GNU Library General Public
-License along with libiberty; see the file COPYING.LIB.
-If not, see <http://www.gnu.org/licenses/>.  */
-
-/* This file provides some definitions shared by cplus-dem.c and
-   rust-demangle.c.  It should not be included by any other files.  */
-
-/* Returns non-zero iff MANGLED is a rust mangled symbol.  MANGLED must
-   already have been demangled through cplus_demangle_v3.  If this function
-   returns non-zero then MANGLED can be demangled (in-place) using
-   RUST_DEMANGLE_SYM.  */
-extern int
-rust_is_mangled (const char *mangled);
-
-/* Demangles SYM (in-place) if RUST_IS_MANGLED returned non-zero for SYM.
-   If RUST_IS_MANGLED returned zero for SYM then RUST_DEMANGLE_SYM might
-   replace characters that cannot be demangled with '?' and might truncate
-   SYM.  After calling RUST_DEMANGLE_SYM SYM might be shorter, but never
-   larger.  */
-extern void
-rust_demangle_sym (char *sym);