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

* demangle.h (rust_demangle_callback): Add.

	* 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.

From-SVN: r278358
This commit is contained in:
Eduard-Mihai Burtescu 2019-11-16 16:32:50 +01:00 committed by Jeff Law
parent f73cb38f65
commit 32fc3719e0
6 changed files with 442 additions and 272 deletions

View File

@ -1,3 +1,7 @@
2019-10-22 Eduard-Mihai Burtescu <eddyb@lyken.rs>
* demangle.h (rust_demangle_callback): Add.
2019-11-13 Andrew Stubbs <ams@codesourcery.com> 2019-11-13 Andrew Stubbs <ams@codesourcery.com>
Kwok Cheung Yeung <kcy@codesourcery.com> Kwok Cheung Yeung <kcy@codesourcery.com>
Julian Brown <julian@codesourcery.com> Julian Brown <julian@codesourcery.com>

View File

@ -159,6 +159,11 @@ ada_demangle (const char *mangled, int options);
extern char * extern char *
dlang_demangle (const char *mangled, int options); dlang_demangle (const char *mangled, int options);
extern int
rust_demangle_callback (const char *mangled, int options,
demangle_callbackref callback, void *opaque);
extern char * extern char *
rust_demangle (const char *mangled, int options); rust_demangle (const char *mangled, int options);

View File

@ -1,3 +1,28 @@
2019-10-22 Eduard-Mihai Burtescu <eddyb@lyken.rs>
* 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.
2019-11-15 Miguel Saldivar <saldivarcher@gmail.com> 2019-11-15 Miguel Saldivar <saldivarcher@gmail.com>
* testsuite/demangle-expected: Fix test. * testsuite/demangle-expected: Fix test.

View File

@ -52,7 +52,6 @@ void * realloc ();
#define CURRENT_DEMANGLING_STYLE options #define CURRENT_DEMANGLING_STYLE options
#include "libiberty.h" #include "libiberty.h"
#include "rust-demangle.h"
enum demangling_styles current_demangling_style = auto_demangling; 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) if ((options & DMGL_STYLE_MASK) == 0)
options |= (int) current_demangling_style & DMGL_STYLE_MASK; 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. */ /* 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); ret = cplus_demangle_v3 (mangled, options);
if (GNU_V3_DEMANGLING) if (ret || 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; return ret;
} }
@ -204,27 +196,6 @@ cplus_demangle (const char *mangled, int options)
return (ret); 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. */ /* Demangle ada names. The encoding is documented in gcc/ada/exp_dbug.ads. */
char * char *

View File

@ -33,9 +33,11 @@ If not, see <http://www.gnu.org/licenses/>. */
#include "safe-ctype.h" #include "safe-ctype.h"
#include <inttypes.h>
#include <sys/types.h> #include <sys/types.h>
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#ifdef HAVE_STRING_H #ifdef HAVE_STRING_H
#include <string.h> #include <string.h>
@ -47,207 +49,110 @@ extern void *memset(void *s, int c, size_t n);
#include <demangle.h> #include <demangle.h>
#include "libiberty.h" #include "libiberty.h"
#include "rust-demangle.h"
struct rust_demangler
/* 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)
{ {
size_t len, len_without_hash; const char *sym;
const char *end; size_t sym_len;
if (!sym) void *callback_opaque;
return 0; demangle_callbackref callback;
len = strlen (sym); /* Position of the next character to read from the symbol. */
if (len <= hash_prefix_len + hash_len) size_t next;
/* Not long enough to contain "::h" + hash + something else */
return 0;
len_without_hash = len - (hash_prefix_len + hash_len); /* Non-zero if any error occurred. */
if (!is_prefixed_hash (sym + len_without_hash)) int errored;
return 0;
end = sym + len_without_hash; /* Non-zero if printing should be verbose (e.g. include hashes). */
int verbose;
while (sym < end) /* Rust mangling version, with legacy mangling being -1. */
{ int version;
if (*sym == '$' || *sym == '.' || *sym == '_' || *sym == ':' };
|| ISALNUM (*sym))
sym++;
else
return 0;
}
return 1; /* Parsing functions. */
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 static char
hex digits must contain at least 5 distinct digits. */ next (struct rust_demangler *rdm)
static int
is_prefixed_hash (const char *str)
{ {
const char *end; char c = peek (rdm);
char seen[16]; if (!c)
size_t i; rdm->errored = 1;
int count, nibble; else
rdm->next++;
if (strncmp (str, hash_prefix, hash_prefix_len)) return c;
return 0;
str += hash_prefix_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;
}
/* Count how many distinct digits seen */
count = 0;
for (i = 0; i < 16; i++)
if (seen[i])
count++;
return count >= 5;
} }
/* struct rust_mangled_ident
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; /* ASCII part of the identifier. */
char *out; const char *ascii;
const char *end; size_t ascii_len;
char unescaped; };
if (!sym) static struct rust_mangled_ident
return; parse_ident (struct rust_demangler *rdm)
{
char c;
size_t start, len;
struct rust_mangled_ident ident;
in = sym; ident.ascii = NULL;
out = sym; ident.ascii_len = 0;
end = sym + strlen (sym) - (hash_prefix_len + hash_len);
while (in < end) c = next (rdm);
if (!ISDIGIT (c))
{ {
if (*in == '$') rdm->errored = 1;
{ return ident;
unescaped = parse_legacy_escape (&in); }
if (unescaped) len = c - '0';
*out++ = unescaped;
else if (c != '0')
/* unexpected escape sequence, skip the rest of this segment. */ while (ISDIGIT (peek (rdm)))
while (in < end && *in != ':') len = len * 10 + (next (rdm) - '0');
*out++ = *in++;
} start = rdm->next;
else if (*in == '_') rdm->next += len;
{ /* Check for overflows. */
/* If this is the start of a path component and the next if ((start > rdm->next) || (rdm->next > rdm->sym_len))
character is an escape sequence, ignore the underscore. The {
mangler inserts an underscore to make sure the path rdm->errored = 1;
component begins with a XID_Start character. */ return ident;
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'; ident.ascii = rdm->sym + start;
ident.ascii_len = len;
if (ident.ascii_len == 0)
ident.ascii = NULL;
return ident;
} }
/* Printing functions. */
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. */ /* Return a 0x0-0xf value if the char is 0-9a-f, and -1 otherwise. */
static int static int
parse_lower_hex_nibble (char nibble) decode_lower_hex_nibble (char nibble)
{ {
if ('0' <= nibble && nibble <= '9') if ('0' <= nibble && nibble <= '9')
return nibble - '0'; return nibble - '0';
@ -258,17 +163,17 @@ parse_lower_hex_nibble (char nibble)
/* Return the unescaped character for a "$...$" escape, or 0 if invalid. */ /* Return the unescaped character for a "$...$" escape, or 0 if invalid. */
static char static char
parse_legacy_escape (const char **in) decode_legacy_escape (const char *e, size_t len, size_t *out_len)
{ {
char c = 0; char c = 0;
const char *e;
size_t escape_len = 0; size_t escape_len = 0;
int lo_nibble = -1, hi_nibble = -1; int lo_nibble = -1, hi_nibble = -1;
if ((*in)[0] != '$') if (len < 3 || e[0] != '$')
return 0; return 0;
e = *in + 1; e++;
len--;
if (e[0] == 'C') if (e[0] == 'C')
{ {
@ -276,7 +181,7 @@ parse_legacy_escape (const char **in)
c = ','; c = ',';
} }
else else if (len > 2)
{ {
escape_len = 2; escape_len = 2;
@ -294,14 +199,14 @@ parse_legacy_escape (const char **in)
c = '('; c = '(';
else if (e[0] == 'R' && e[1] == 'P') else if (e[0] == 'R' && e[1] == 'P')
c = ')'; c = ')';
else if (e[0] == 'u') else if (e[0] == 'u' && len > 3)
{ {
escape_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) if (hi_nibble < 0)
return 0; return 0;
lo_nibble = parse_lower_hex_nibble (e[2]); lo_nibble = decode_lower_hex_nibble (e[2]);
if (lo_nibble < 0) if (lo_nibble < 0)
return 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; return 0;
*in += 2 + escape_len; *out_len = 2 + escape_len;
return c; 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;
}

View File

@ -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);