aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/ChangeLog12
-rw-r--r--include/demangle.h29
-rw-r--r--libiberty/ChangeLog13
-rw-r--r--libiberty/Makefile.in13
-rw-r--r--libiberty/cplus-dem.c47
-rw-r--r--libiberty/rust-demangle.c348
-rw-r--r--libiberty/testsuite/Makefile.in7
-rw-r--r--libiberty/testsuite/rust-demangle-expected161
8 files changed, 624 insertions, 6 deletions
diff --git a/include/ChangeLog b/include/ChangeLog
index 96ddcc7..18fe3ed 100644
--- a/include/ChangeLog
+++ b/include/ChangeLog
@@ -1,3 +1,15 @@
+2016-11-03 David Tolnay <dtolnay@gmail.com>
+ Mark Wielaard <mark@klomp.org>
+
+ * demangle.h (DMGL_RUST): New macro.
+ (DMGL_STYLE_MASK): Add DMGL_RUST.
+ (demangling_styles): Add dlang_rust.
+ (RUST_DEMANGLING_STYLE_STRING): New macro.
+ (RUST_DEMANGLING): New macro.
+ (rust_demangle): New prototype.
+ (rust_is_mangled): Likewise.
+ (rust_demangle_sym): Likewise.
+
2016-11-07 Jason Merrill <jason@redhat.com>
* demangle.h (enum demangle_component_type): Add
diff --git a/include/demangle.h b/include/demangle.h
index 12f51ca..e9551cb 100644
--- a/include/demangle.h
+++ b/include/demangle.h
@@ -63,9 +63,10 @@ extern "C" {
#define DMGL_GNU_V3 (1 << 14)
#define DMGL_GNAT (1 << 15)
#define DMGL_DLANG (1 << 16)
+#define DMGL_RUST (1 << 17) /* Rust wraps GNU_V3 style mangling. */
/* If none of these are set, use 'current_demangling_style' as the default. */
-#define DMGL_STYLE_MASK (DMGL_AUTO|DMGL_GNU|DMGL_LUCID|DMGL_ARM|DMGL_HP|DMGL_EDG|DMGL_GNU_V3|DMGL_JAVA|DMGL_GNAT|DMGL_DLANG)
+#define DMGL_STYLE_MASK (DMGL_AUTO|DMGL_GNU|DMGL_LUCID|DMGL_ARM|DMGL_HP|DMGL_EDG|DMGL_GNU_V3|DMGL_JAVA|DMGL_GNAT|DMGL_DLANG|DMGL_RUST)
/* Enumeration of possible demangling styles.
@@ -88,7 +89,8 @@ extern enum demangling_styles
gnu_v3_demangling = DMGL_GNU_V3,
java_demangling = DMGL_JAVA,
gnat_demangling = DMGL_GNAT,
- dlang_demangling = DMGL_DLANG
+ dlang_demangling = DMGL_DLANG,
+ rust_demangling = DMGL_RUST
} current_demangling_style;
/* Define string names for the various demangling styles. */
@@ -104,6 +106,7 @@ extern enum demangling_styles
#define JAVA_DEMANGLING_STYLE_STRING "java"
#define GNAT_DEMANGLING_STYLE_STRING "gnat"
#define DLANG_DEMANGLING_STYLE_STRING "dlang"
+#define RUST_DEMANGLING_STYLE_STRING "rust"
/* Some macros to test what demangling style is active. */
@@ -118,6 +121,7 @@ extern enum demangling_styles
#define JAVA_DEMANGLING (((int) CURRENT_DEMANGLING_STYLE) & DMGL_JAVA)
#define GNAT_DEMANGLING (((int) CURRENT_DEMANGLING_STYLE) & DMGL_GNAT)
#define DLANG_DEMANGLING (((int) CURRENT_DEMANGLING_STYLE) & DMGL_DLANG)
+#define RUST_DEMANGLING (((int) CURRENT_DEMANGLING_STYLE) & DMGL_RUST)
/* Provide information about the available demangle styles. This code is
pulled from gdb into libiberty because it is useful to binutils also. */
@@ -175,6 +179,27 @@ ada_demangle (const char *mangled, int options);
extern char *
dlang_demangle (const char *mangled, int options);
+/* 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);
+
+/* Demangles MANGLED if it was GNU_V3 and then RUST mangled, otherwise
+ returns NULL. Uses CPLUS_DEMANGLE_V3, RUST_IS_MANGLED and
+ RUST_DEMANGLE_SYM. Returns a new string that is owned by the caller. */
+extern char *
+rust_demangle (const char *mangled, int options);
+
enum gnu_v3_ctor_kinds {
gnu_v3_complete_object_ctor = 1,
gnu_v3_base_object_ctor,
diff --git a/libiberty/ChangeLog b/libiberty/ChangeLog
index 1082431..7ae91ad 100644
--- a/libiberty/ChangeLog
+++ b/libiberty/ChangeLog
@@ -1,3 +1,16 @@
+2016-11-03 David Tolnay <dtolnay@gmail.com>
+ Mark Wielaard <mark@klomp.org>
+
+ * Makefile.in (CFILES): Add rust-demangle.c.
+ (REQUIRED_OFILES): Add rust-demangle.o.
+ * cplus-dem.c (libiberty_demanglers): Add rust_demangling case.
+ (cplus_demangle): Handle RUST_DEMANGLING.
+ (rust_demangle): New function.
+ * rust-demangle.c: New file.
+ * testsuite/Makefile.in (really-check): Add check-rust-demangle.
+ (check-rust-demangle): New rule.
+ * testsuite/rust-demangle-expected: New file.
+
2016-11-15 Mark Wielaard <mark@klomp.org>
* cp-demangle.c (d_expression_1): Make sure third expression
diff --git a/libiberty/Makefile.in b/libiberty/Makefile.in
index c7a4568..0ff9e45 100644
--- a/libiberty/Makefile.in
+++ b/libiberty/Makefile.in
@@ -146,6 +146,7 @@ CFILES = alloca.c argv.c asprintf.c atexit.c \
pex-unix.c pex-win32.c \
physmem.c putenv.c \
random.c regex.c rename.c rindex.c \
+ rust-demangle.c \
safe-ctype.c setenv.c setproctitle.c sha1.c sigsetmask.c \
simple-object.c simple-object-coff.c simple-object-elf.c \
simple-object-mach-o.c simple-object-xcoff.c \
@@ -183,6 +184,7 @@ REQUIRED_OFILES = \
./partition.$(objext) ./pexecute.$(objext) ./physmem.$(objext) \
./pex-common.$(objext) ./pex-one.$(objext) \
./@pexecute@.$(objext) ./vprintf-support.$(objext) \
+ ./rust-demangle.$(objext) \
./safe-ctype.$(objext) \
./simple-object.$(objext) ./simple-object-coff.$(objext) \
./simple-object-elf.$(objext) ./simple-object-mach-o.$(objext) \
@@ -1188,6 +1190,17 @@ $(CONFIGURED_OFILES): stamp-picdir stamp-noasandir
else true; fi
$(COMPILE.c) $(srcdir)/rindex.c $(OUTPUT_OPTION)
+./rust-demangle.$(objext): $(srcdir)/rust-demangle.c config.h \
+ $(INCDIR)/ansidecl.h $(INCDIR)/demangle.h $(INCDIR)/libiberty.h \
+ $(INCDIR)/safe-ctype.h
+ if [ x"$(PICFLAG)" != x ]; then \
+ $(COMPILE.c) $(PICFLAG) $(srcdir)/rust-demangle.c -o pic/$@; \
+ else true; fi
+ if [ x"$(NOASANFLAG)" != x ]; then \
+ $(COMPILE.c) $(PICFLAG) $(NOASANFLAG) $(srcdir)/rust-demangle.c -o noasan/$@; \
+ else true; fi
+ $(COMPILE.c) $(srcdir)/rust-demangle.c $(OUTPUT_OPTION)
+
./safe-ctype.$(objext): $(srcdir)/safe-ctype.c $(INCDIR)/ansidecl.h \
$(INCDIR)/safe-ctype.h
if [ x"$(PICFLAG)" != x ]; then \
diff --git a/libiberty/cplus-dem.c b/libiberty/cplus-dem.c
index 3125a45..0386da5 100644
--- a/libiberty/cplus-dem.c
+++ b/libiberty/cplus-dem.c
@@ -323,6 +323,12 @@ const struct demangler_engine libiberty_demanglers[] =
}
,
{
+ RUST_DEMANGLING_STYLE_STRING,
+ rust_demangling,
+ "Rust style demangling"
+ }
+ ,
+ {
NULL, unknown_demangling, NULL
}
};
@@ -874,10 +880,26 @@ cplus_demangle (const char *mangled, int options)
work->options |= (int) current_demangling_style & DMGL_STYLE_MASK;
/* The V3 ABI demangling is implemented elsewhere. */
- if (GNU_V3_DEMANGLING || AUTO_DEMANGLING)
+ if (GNU_V3_DEMANGLING || RUST_DEMANGLING || AUTO_DEMANGLING)
{
ret = cplus_demangle_v3 (mangled, work->options);
- if (ret || GNU_V3_DEMANGLING)
+ 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)
return ret;
}
@@ -903,6 +925,27 @@ 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
new file mode 100644
index 0000000..326881e
--- /dev/null
+++ b/libiberty/rust-demangle.c
@@ -0,0 +1,348 @@
+/* Demangler for the Rust programming language
+ Copyright 2016 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/>. */
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "safe-ctype.h"
+
+#include <sys/types.h>
+#include <string.h>
+#include <stdio.h>
+
+#ifdef HAVE_STRING_H
+#include <string.h>
+#else
+extern size_t strlen(const char *s);
+extern int strncmp(const char *s1, const char *s2, size_t n);
+extern void *memset(void *s, int c, size_t n);
+#endif
+
+#include <demangle.h>
+#include "libiberty.h"
+
+
+/* Mangled 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$
+ " " => $u20$
+ "\"" => $u22$
+ "'" => $u27$
+ "+" => $u2b$
+ ";" => $u3b$
+ "[" => $u5b$
+ "]" => $u5d$
+ "{" => $u7b$
+ "}" => $u7d$
+ "~" => $u7e$
+
+ 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 looks_like_rust (const char *sym, size_t len);
+static int unescape (const char **in, char **out, const char *seq, char value);
+
+/* 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 _.:$
+
+ 4. There must be no unrecognized $-sign sequences.
+
+ 5. There must be no sequence of three or more dots in a row ("..."). */
+
+int
+rust_is_mangled (const char *sym)
+{
+ size_t len, len_without_hash;
+
+ 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;
+
+ return looks_like_rust (sym, len_without_hash);
+}
+
+/* A hash is the prefix "::h" followed by 16 lowercase hex digits. The
+ hex digits must comprise between 5 and 15 (inclusive) distinct
+ digits. */
+
+static int
+is_prefixed_hash (const char *str)
+{
+ const char *end;
+ char seen[16];
+ size_t i;
+ int count;
+
+ if (strncmp (str, hash_prefix, hash_prefix_len))
+ return 0;
+ str += hash_prefix_len;
+
+ memset (seen, 0, sizeof(seen));
+ for (end = str + hash_len; str < end; str++)
+ if (*str >= '0' && *str <= '9')
+ seen[*str - '0'] = 1;
+ else if (*str >= 'a' && *str <= 'f')
+ seen[*str - 'a' + 10] = 1;
+ else
+ return 0;
+
+ /* Count how many distinct digits seen */
+ count = 0;
+ for (i = 0; i < 16; i++)
+ if (seen[i])
+ count++;
+
+ return count >= 5 && count <= 15;
+}
+
+static int
+looks_like_rust (const char *str, size_t len)
+{
+ const char *end = str + len;
+
+ while (str < end)
+ switch (*str)
+ {
+ case '$':
+ if (!strncmp (str, "$C$", 3))
+ str += 3;
+ else if (!strncmp (str, "$SP$", 4)
+ || !strncmp (str, "$BP$", 4)
+ || !strncmp (str, "$RF$", 4)
+ || !strncmp (str, "$LT$", 4)
+ || !strncmp (str, "$GT$", 4)
+ || !strncmp (str, "$LP$", 4)
+ || !strncmp (str, "$RP$", 4))
+ str += 4;
+ else if (!strncmp (str, "$u20$", 5)
+ || !strncmp (str, "$u22$", 5)
+ || !strncmp (str, "$u27$", 5)
+ || !strncmp (str, "$u2b$", 5)
+ || !strncmp (str, "$u3b$", 5)
+ || !strncmp (str, "$u5b$", 5)
+ || !strncmp (str, "$u5d$", 5)
+ || !strncmp (str, "$u7b$", 5)
+ || !strncmp (str, "$u7d$", 5)
+ || !strncmp (str, "$u7e$", 5))
+ str += 5;
+ else
+ return 0;
+ break;
+ case '.':
+ /* Do not allow three or more consecutive dots */
+ if (!strncmp (str, "...", 3))
+ return 0;
+ /* Fall through */
+ case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
+ case 'g': case 'h': case 'i': case 'j': case 'k': case 'l':
+ case 'm': case 'n': case 'o': case 'p': case 'q': case 'r':
+ case 's': case 't': case 'u': case 'v': case 'w': case 'x':
+ case 'y': case 'z':
+ case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
+ case 'G': case 'H': case 'I': case 'J': case 'K': case 'L':
+ case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R':
+ case 'S': case 'T': case 'U': case 'V': case 'W': case 'X':
+ case 'Y': case 'Z':
+ case '0': case '1': case '2': case '3': case '4': case '5':
+ case '6': case '7': case '8': case '9':
+ case '_':
+ case ':':
+ str++;
+ break;
+ default:
+ return 0;
+ }
+
+ return 1;
+}
+
+/*
+ INPUT: sym: symbol for which rust_is_mangled(sym) returned 1.
+
+ The input is demangled in-place because the mangled name is always
+ longer than the demangled one. */
+
+void
+rust_demangle_sym (char *sym)
+{
+ const char *in;
+ char *out;
+ const char *end;
+
+ if (!sym)
+ return;
+
+ in = sym;
+ out = sym;
+ end = sym + strlen (sym) - (hash_prefix_len + hash_len);
+
+ while (in < end)
+ switch (*in)
+ {
+ case '$':
+ if (!(unescape (&in, &out, "$C$", ',')
+ || unescape (&in, &out, "$SP$", '@')
+ || unescape (&in, &out, "$BP$", '*')
+ || unescape (&in, &out, "$RF$", '&')
+ || unescape (&in, &out, "$LT$", '<')
+ || unescape (&in, &out, "$GT$", '>')
+ || unescape (&in, &out, "$LP$", '(')
+ || unescape (&in, &out, "$RP$", ')')
+ || unescape (&in, &out, "$u20$", ' ')
+ || unescape (&in, &out, "$u22$", '\"')
+ || unescape (&in, &out, "$u27$", '\'')
+ || unescape (&in, &out, "$u2b$", '+')
+ || unescape (&in, &out, "$u3b$", ';')
+ || unescape (&in, &out, "$u5b$", '[')
+ || unescape (&in, &out, "$u5d$", ']')
+ || unescape (&in, &out, "$u7b$", '{')
+ || unescape (&in, &out, "$u7d$", '}')
+ || unescape (&in, &out, "$u7e$", '~'))) {
+ /* unexpected escape sequence, not looks_like_rust. */
+ goto fail;
+ }
+ break;
+ case '_':
+ /* 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++;
+ break;
+ case '.':
+ if (in[1] == '.')
+ {
+ /* ".." becomes "::" */
+ *out++ = ':';
+ *out++ = ':';
+ in += 2;
+ }
+ else
+ {
+ /* "." becomes "-" */
+ *out++ = '-';
+ in++;
+ }
+ break;
+ case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
+ case 'g': case 'h': case 'i': case 'j': case 'k': case 'l':
+ case 'm': case 'n': case 'o': case 'p': case 'q': case 'r':
+ case 's': case 't': case 'u': case 'v': case 'w': case 'x':
+ case 'y': case 'z':
+ case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
+ case 'G': case 'H': case 'I': case 'J': case 'K': case 'L':
+ case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R':
+ case 'S': case 'T': case 'U': case 'V': case 'W': case 'X':
+ case 'Y': case 'Z':
+ case '0': case '1': case '2': case '3': case '4': case '5':
+ case '6': case '7': case '8': case '9':
+ case ':':
+ *out++ = *in++;
+ break;
+ default:
+ /* unexpected character in symbol, not looks_like_rust. */
+ goto fail;
+ }
+ goto done;
+
+fail:
+ *out++ = '?'; /* This is pretty lame, but it's hard to do better. */
+done:
+ *out = '\0';
+}
+
+static int
+unescape (const char **in, char **out, const char *seq, char value)
+{
+ size_t len = strlen (seq);
+
+ if (strncmp (*in, seq, len))
+ return 0;
+
+ **out = value;
+
+ *in += len;
+ *out += 1;
+
+ return 1;
+}
diff --git a/libiberty/testsuite/Makefile.in b/libiberty/testsuite/Makefile.in
index 8f5f7b5..da0b2f4 100644
--- a/libiberty/testsuite/Makefile.in
+++ b/libiberty/testsuite/Makefile.in
@@ -45,8 +45,8 @@ all:
# CHECK is set to "really_check" or the empty string by configure.
check: @CHECK@
-really-check: check-cplus-dem check-d-demangle check-pexecute check-expandargv \
- check-strtol
+really-check: check-cplus-dem check-d-demangle check-rust-demangle \
+ check-pexecute check-expandargv check-strtol
# Run some tests of the demangler.
check-cplus-dem: test-demangle $(srcdir)/demangle-expected
@@ -55,6 +55,9 @@ check-cplus-dem: test-demangle $(srcdir)/demangle-expected
check-d-demangle: test-demangle $(srcdir)/d-demangle-expected
./test-demangle < $(srcdir)/d-demangle-expected
+check-rust-demangle: test-demangle $(srcdir)/rust-demangle-expected
+ ./test-demangle < $(srcdir)/rust-demangle-expected
+
# Check the pexecute code.
check-pexecute: test-pexecute
./test-pexecute
diff --git a/libiberty/testsuite/rust-demangle-expected b/libiberty/testsuite/rust-demangle-expected
new file mode 100644
index 0000000..0b4288f
--- /dev/null
+++ b/libiberty/testsuite/rust-demangle-expected
@@ -0,0 +1,161 @@
+# This file holds test cases for the Rust demangler.
+# Each test case looks like this:
+# options
+# input to be demangled
+# expected output
+#
+# See demangle-expected for documentation of supported options.
+#
+# A line starting with `#' is ignored.
+# However, blank lines in this file are NOT ignored.
+#
+############
+#
+# Coverage Tests
+#
+#
+# Demangles as rust symbol.
+--format=rust
+_ZN4main4main17he714a2e23ed7db23E
+main::main
+# Also demangles as c++ gnu v3 mangled symbol. But with extra Rust hash.
+--format=gnu-v3
+_ZN4main4main17he714a2e23ed7db23E
+main::main::he714a2e23ed7db23
+# But auto should demangle fully gnu-v3 -> rust -> demangled, not partially.
+--format=auto
+_ZN4main4main17he714a2e23ed7db23E
+main::main
+# Hash is exactly 16 hex chars. Not more.
+--format=auto
+_ZN4main4main18h1e714a2e23ed7db23E
+main::main::h1e714a2e23ed7db23
+# Not less.
+--format=auto
+_ZN4main4main16h714a2e23ed7db23E
+main::main::h714a2e23ed7db23
+# And not non-hex.
+--format=auto
+_ZN4main4main17he714a2e23ed7db2gE
+main::main::he714a2e23ed7db2g
+# $XX$ substitutions should not contain just numbers.
+--format=auto
+_ZN4main4$99$17he714a2e23ed7db23E
+main::$99$::he714a2e23ed7db23
+# _ at start of path should be removed.
+# ".." translates to "::" "$GT$" to ">" and "$LT$" to "<".
+--format=rust
+_ZN71_$LT$Test$u20$$u2b$$u20$$u27$static$u20$as$u20$foo..Bar$LT$Test$GT$$GT$3bar17h930b740aa94f1d3aE
+<Test + 'static as foo::Bar<Test>>::bar
+#
+--format=rust
+_ZN54_$LT$I$u20$as$u20$core..iter..traits..IntoIterator$GT$9into_iter17h8581507801fb8615E
+<I as core::iter::traits::IntoIterator>::into_iter
+#
+--format=rust
+_ZN10parse_tsan4main17hdbbfdf1c6a7e27d9E
+parse_tsan::main
+#
+--format=rust
+_ZN65_$LT$std..env..Args$u20$as$u20$core..iter..iterator..Iterator$GT$4next17h420a7c8d0c7eef40E
+<std::env::Args as core::iter::iterator::Iterator>::next
+#
+--format=rust
+_ZN4core3str9from_utf817hdcea28871313776dE
+core::str::from_utf8
+#
+--format=rust
+_ZN4core3mem7size_of17h18bde9bb8c22e2cfE
+core::mem::size_of
+#
+--format=rust
+_ZN5alloc4heap8allocate17hd55c03e6cb81d924E
+alloc::heap::allocate
+#
+--format=rust
+_ZN4core3ptr8null_mut17h736cce09ca0ac11aE
+core::ptr::null_mut
+#
+--format=rust
+_ZN4core3ptr31_$LT$impl$u20$$BP$mut$u20$T$GT$7is_null17h7f9de798bc3f0879E
+core::ptr::<impl *mut T>::is_null
+#
+--format=rust
+_ZN40_$LT$alloc..raw_vec..RawVec$LT$T$GT$$GT$6double17h4166e2b47539e1ffE
+<alloc::raw_vec::RawVec<T>>::double
+#
+--format=rust
+_ZN39_$LT$collections..vec..Vec$LT$T$GT$$GT$4push17hd4b6b23c1b88141aE
+<collections::vec::Vec<T>>::push
+#
+--format=rust
+_ZN70_$LT$collections..vec..Vec$LT$T$GT$$u20$as$u20$core..ops..DerefMut$GT$9deref_mut17hf299b860dc5a831cE
+<collections::vec::Vec<T> as core::ops::DerefMut>::deref_mut
+#
+--format=rust
+_ZN63_$LT$core..ptr..Unique$LT$T$GT$$u20$as$u20$core..ops..Deref$GT$5deref17hc784b4a166cb5e5cE
+<core::ptr::Unique<T> as core::ops::Deref>::deref
+#
+--format=rust
+_ZN40_$LT$alloc..raw_vec..RawVec$LT$T$GT$$GT$3ptr17h7570b6e9070b693bE
+<alloc::raw_vec::RawVec<T>>::ptr
+#
+--format=rust
+_ZN4core3ptr31_$LT$impl$u20$$BP$mut$u20$T$GT$7is_null17h0f3228f343444ac8E
+core::ptr::<impl *mut T>::is_null
+#
+--format=rust
+_ZN53_$LT$$u5b$T$u5d$$u20$as$u20$core..slice..SliceExt$GT$10as_mut_ptr17h153241df1c7d1666E
+<[T] as core::slice::SliceExt>::as_mut_ptr
+#
+--format=rust
+_ZN11collections5slice29_$LT$impl$u20$$u5b$T$u5d$$GT$10as_mut_ptr17hf12a6d0409938c96E
+collections::slice::<impl [T]>::as_mut_ptr
+#
+--format=rust
+_ZN4core3ptr5write17h651fe53ec860e780E
+core::ptr::write
+#
+--format=rust
+_ZN65_$LT$std..env..Args$u20$as$u20$core..iter..iterator..Iterator$GT$4next17h420a7c8d0c7eef40E
+<std::env::Args as core::iter::iterator::Iterator>::next
+#
+--format=rust
+_ZN54_$LT$I$u20$as$u20$core..iter..traits..IntoIterator$GT$9into_iter17he06cb713aae5b465E
+<I as core::iter::traits::IntoIterator>::into_iter
+#
+--format=rust
+_ZN71_$LT$collections..vec..IntoIter$LT$T$GT$$u20$as$u20$core..ops..Drop$GT$4drop17hf7f23304ebe62eedE
+<collections::vec::IntoIter<T> as core::ops::Drop>::drop
+#
+--format=rust
+_ZN86_$LT$collections..vec..IntoIter$LT$T$GT$$u20$as$u20$core..iter..iterator..Iterator$GT$4next17h04b3fbf148c39713E
+<collections::vec::IntoIter<T> as core::iter::iterator::Iterator>::next
+#
+--format=rust
+_ZN75_$LT$$RF$$u27$a$u20$mut$u20$I$u20$as$u20$core..iter..iterator..Iterator$GT$4next17ha050492063e0fd20E
+<&'a mut I as core::iter::iterator::Iterator>::next
+# Different hashes are OK, they are just stripped.
+--format=rust
+_ZN13drop_contents17hfe3c0a68c8ad1c74E
+drop_contents
+#
+--format=rust
+_ZN13drop_contents17h48cb59bef15bb555E
+drop_contents
+#
+--format=rust
+_ZN4core3mem7size_of17h900b33157bf58f26E
+core::mem::size_of
+#
+--format=rust
+_ZN67_$LT$alloc..raw_vec..RawVec$LT$T$GT$$u20$as$u20$core..ops..Drop$GT$4drop17h96a5cf6e94807905E
+<alloc::raw_vec::RawVec<T> as core::ops::Drop>::drop
+#
+--format=rust
+_ZN68_$LT$core..nonzero..NonZero$LT$T$GT$$u20$as$u20$core..ops..Deref$GT$5deref17hc49056f882aa46dbE
+<core::nonzero::NonZero<T> as core::ops::Deref>::deref
+#
+--format=rust
+_ZN63_$LT$core..ptr..Unique$LT$T$GT$$u20$as$u20$core..ops..Deref$GT$5deref17h19f2ad4920655e85E
+<core::ptr::Unique<T> as core::ops::Deref>::deref