| // SPDX-License-Identifier: Apache-2.0 OR MIT |
| |
| // The contents of this file come from the Rust rustc-demangle library, hosted |
| // in the <https://github.com/rust-lang/rustc-demangle> repository, licensed |
| // under "Apache-2.0 OR MIT". For copyright details, see |
| // <https://github.com/rust-lang/rustc-demangle/blob/main/README.md>. |
| // Please note that the file should be kept as close as possible to upstream. |
| |
| // Code for demangling Rust symbols. This code is mostly |
| // a line-by-line translation of the Rust code in `rustc-demangle`. |
| |
| // you can find the latest version of this code in https://github.com/rust-lang/rustc-demangle |
| |
| #include <stdint.h> |
| #include <stddef.h> |
| #include <string.h> |
| #include <stdbool.h> |
| #include <sys/param.h> |
| #include <stdio.h> |
| |
| #include "demangle-rust-v0.h" |
| |
| #if defined(__GNUC__) || defined(__clang__) |
| #define NODISCARD __attribute__((warn_unused_result)) |
| #else |
| #define NODISCARD |
| #endif |
| |
| #define MAX_DEPTH 500 |
| |
| typedef enum { |
| DemangleOk, |
| DemangleInvalid, |
| DemangleRecursed, |
| DemangleBug, |
| } demangle_status; |
| |
| struct demangle_v0 { |
| const char *mangled; |
| size_t mangled_len; |
| }; |
| |
| struct demangle_legacy { |
| const char *mangled; |
| size_t mangled_len; |
| size_t elements; |
| }; |
| |
| // private version of memrchr to avoid _GNU_SOURCE |
| static void *demangle_memrchr(const void *s, int c, size_t n) { |
| const uint8_t *s_ = s; |
| for (; n != 0; n--) { |
| if (s_[n-1] == c) { |
| return (void*)&s_[n-1]; |
| } |
| } |
| return NULL; |
| } |
| |
| |
| static bool unicode_iscontrol(uint32_t ch) { |
| // this is *technically* a unicode table, but |
| // some unicode properties are simpler than you might think |
| return ch < 0x20 || (ch >= 0x7f && ch < 0xa0); |
| } |
| |
| // "good enough" tables, the only consequence is that when printing |
| // *constant strings*, some characters are printed as `\u{abcd}` rather than themselves. |
| // |
| // I'm leaving these here to allow easily replacing them with actual |
| // tables if desired. |
| static bool unicode_isprint(uint32_t ch) { |
| if (ch < 0x20) { |
| return false; |
| } |
| if (ch < 0x7f) { |
| return true; |
| } |
| return false; |
| } |
| |
| static bool unicode_isgraphemextend(uint32_t ch) { |
| (void)ch; |
| return false; |
| } |
| |
| static bool str_isascii(const char *s, size_t s_len) { |
| for (size_t i = 0; i < s_len; i++) { |
| if (s[i] & 0x80) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| typedef enum { |
| PunycodeOk, |
| PunycodeError |
| } punycode_status; |
| |
| struct parser { |
| // the parser assumes that `sym` has a safe "terminating byte". It might be NUL, |
| // but it might also be something else if a symbol is "truncated". |
| const char *sym; |
| size_t sym_len; |
| size_t next; |
| uint32_t depth; |
| }; |
| |
| struct printer { |
| demangle_status status; // if status == 0 parser is valid |
| struct parser parser; |
| char *out; // NULL for no output [in which case out_len is not decremented] |
| size_t out_len; |
| uint32_t bound_lifetime_depth; |
| bool alternate; |
| }; |
| |
| static NODISCARD overflow_status printer_print_path(struct printer *printer, bool in_value); |
| static NODISCARD overflow_status printer_print_type(struct printer *printer); |
| static NODISCARD overflow_status printer_print_const(struct printer *printer, bool in_value); |
| |
| static NODISCARD demangle_status try_parse_path(struct parser *parser) { |
| struct printer printer = { |
| DemangleOk, |
| *parser, |
| NULL, |
| SIZE_MAX, |
| 0, |
| false |
| }; |
| overflow_status ignore = printer_print_path(&printer, false); // can't fail since no output |
| (void)ignore; |
| *parser = printer.parser; |
| return printer.status; |
| } |
| |
| NODISCARD static demangle_status rust_demangle_v0_demangle(const char *s, size_t s_len, struct demangle_v0 *res, const char **rest) { |
| if (s_len > strlen(s)) { |
| // s_len only exists to shorten the string, this is not a buffer API |
| return DemangleInvalid; |
| } |
| |
| const char *inner; |
| size_t inner_len; |
| if (s_len >= 2 && !strncmp(s, "_R", strlen("_R"))) { |
| inner = s+2; |
| inner_len = s_len - 2; |
| } else if (s_len >= 1 && !strncmp(s, "R", strlen("R"))) { |
| // On Windows, dbghelp strips leading underscores, so we accept "R..." |
| // form too. |
| inner = s+1; |
| inner_len = s_len - 1; |
| } else if (s_len >= 3 && !strncmp(s, "__R", strlen("__R"))) { |
| // On OSX, symbols are prefixed with an extra _ |
| inner = s+3; |
| inner_len = s_len - 3; |
| } else { |
| return DemangleInvalid; |
| } |
| |
| // Paths always start with uppercase characters. |
| if (*inner < 'A' || *inner > 'Z') { |
| return DemangleInvalid; |
| } |
| |
| if (!str_isascii(inner, inner_len)) { |
| return DemangleInvalid; |
| } |
| |
| struct parser parser = { inner, inner_len, 0, 0 }; |
| |
| demangle_status status = try_parse_path(&parser); |
| if (status != DemangleOk) return status; |
| char next = parser.sym[parser.next]; |
| |
| // Instantiating crate (paths always start with uppercase characters). |
| if (parser.next < parser.sym_len && next >= 'A' && next <= 'Z') { |
| status = try_parse_path(&parser); |
| if (status != DemangleOk) return status; |
| } |
| |
| res->mangled = inner; |
| res->mangled_len = inner_len; |
| if (rest) { |
| *rest = parser.sym + parser.next; |
| } |
| |
| return DemangleOk; |
| } |
| |
| // This might require `len` to be up to 3 characters bigger than the real output len in case of utf-8 |
| NODISCARD static overflow_status rust_demangle_v0_display_demangle(struct demangle_v0 res, char *out, size_t len, bool alternate) { |
| struct printer printer = { |
| DemangleOk, |
| { |
| res.mangled, |
| res.mangled_len, |
| 0, |
| 0 |
| }, |
| out, |
| len, |
| 0, |
| alternate |
| }; |
| if (printer_print_path(&printer, true) == OverflowOverflow) { |
| return OverflowOverflow; |
| } |
| if (printer.out_len < OVERFLOW_MARGIN) { |
| return OverflowOverflow; |
| } |
| *printer.out = '\0'; |
| return OverflowOk; |
| } |
| |
| static size_t code_to_utf8(unsigned char *buffer, uint32_t code) |
| { |
| if (code <= 0x7F) { |
| buffer[0] = code; |
| return 1; |
| } |
| if (code <= 0x7FF) { |
| buffer[0] = 0xC0 | (code >> 6); /* 110xxxxx */ |
| buffer[1] = 0x80 | (code & 0x3F); /* 10xxxxxx */ |
| return 2; |
| } |
| if (code <= 0xFFFF) { |
| buffer[0] = 0xE0 | (code >> 12); /* 1110xxxx */ |
| buffer[1] = 0x80 | ((code >> 6) & 0x3F); /* 10xxxxxx */ |
| buffer[2] = 0x80 | (code & 0x3F); /* 10xxxxxx */ |
| return 3; |
| } |
| if (code <= 0x10FFFF) { |
| buffer[0] = 0xF0 | (code >> 18); /* 11110xxx */ |
| buffer[1] = 0x80 | ((code >> 12) & 0x3F); /* 10xxxxxx */ |
| buffer[2] = 0x80 | ((code >> 6) & 0x3F); /* 10xxxxxx */ |
| buffer[3] = 0x80 | (code & 0x3F); /* 10xxxxxx */ |
| return 4; |
| } |
| return 0; |
| } |
| |
| |
| // return length of char at byte, or SIZE_MAX if invalid. buf should have 4 valid characters |
| static NODISCARD size_t utf8_next_char(uint8_t *s, uint32_t *ch) { |
| uint8_t byte = *s; |
| // UTF8-1 = %x00-7F |
| // UTF8-2 = %xC2-DF UTF8-tail |
| // UTF8-3 = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) / |
| // %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail ) |
| // UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) / |
| // %xF4 %x80-8F 2( UTF8-tail ) |
| if (byte < 0x80) { |
| *ch = byte; |
| return 1; |
| } else if (byte < 0xc2) { |
| return SIZE_MAX; |
| } else if (byte < 0xe0) { |
| if (s[1] >= 0x80 && s[1] < 0xc0) { |
| *ch = ((byte&0x1f)<<6) + (s[1] & 0x3f); |
| return 2; |
| } |
| return SIZE_MAX; |
| } if (byte < 0xf0) { |
| if (!(s[1] >= 0x80 && s[1] < 0xc0) || !(s[2] >= 0x80 && s[2] < 0xc0)) { |
| return SIZE_MAX; // basic validation |
| } |
| if (byte == 0xe0 && s[1] < 0xa0) { |
| return SIZE_MAX; // overshort |
| } |
| if (byte == 0xed && s[1] >= 0xa0) { |
| return SIZE_MAX; // surrogate |
| } |
| *ch = ((byte&0x0f)<<12) + ((s[1] & 0x3f)<<6) + (s[2] & 0x3f); |
| return 3; |
| } else if (byte < 0xf5) { |
| if (!(s[1] >= 0x80 && s[1] < 0xc0) || !(s[2] >= 0x80 && s[2] < 0xc0) || !(s[3] >= 0x80 && s[3] < 0xc0)) { |
| return SIZE_MAX; // basic validation |
| } |
| if (byte == 0xf0 && s[1] < 0x90) { |
| return SIZE_MAX; // overshort |
| } |
| if (byte == 0xf4 && s[1] >= 0x90) { |
| return SIZE_MAX; // over max |
| } |
| *ch = ((byte&0x07)<<18) + ((s[1] & 0x3f)<<12) + ((s[2] & 0x3f)<<6) + (s[3]&0x3f); |
| return 4; |
| } else { |
| return SIZE_MAX; |
| } |
| } |
| |
| static NODISCARD bool validate_char(uint32_t n) { |
| return ((n ^ 0xd800) - 0x800) < 0x110000 - 0x800; |
| } |
| |
| #define SMALL_PUNYCODE_LEN 128 |
| |
| static NODISCARD punycode_status punycode_decode(const char *start, size_t ascii_len, const char *punycode_start, size_t punycode_len, uint32_t (*out_)[SMALL_PUNYCODE_LEN], size_t *out_len) { |
| uint32_t *out = *out_; |
| |
| if (punycode_len == 0) { |
| return PunycodeError; |
| } |
| |
| if (ascii_len > SMALL_PUNYCODE_LEN) { |
| return PunycodeError; |
| } |
| for (size_t i = 0; i < ascii_len; i++) { |
| out[i] = start[i]; |
| } |
| size_t len = ascii_len; |
| |
| size_t base = 36, t_min = 1, t_max = 26, skew = 38, damp = 700, bias = 72, i = 0, n = 0x80; |
| for (;;) { |
| size_t delta = 0, w = 1, k = 0; |
| for (;;) { |
| k += base; |
| size_t biased = k < bias ? 0 : k - bias; |
| size_t t = MIN(MAX(biased, t_min), t_max); |
| size_t d; |
| if (punycode_len == 0) { |
| return PunycodeError; |
| } |
| char nx = *punycode_start++; |
| punycode_len--; |
| if ('a' <= nx && nx <= 'z') { |
| d = nx - 'a'; |
| } else if ('0' <= nx && nx <= '9') { |
| d = 26 + (nx - '0'); |
| } else { |
| return PunycodeError; |
| } |
| if (w == 0 || d > SIZE_MAX / w || d*w > SIZE_MAX - delta) { |
| return PunycodeError; |
| } |
| delta += d * w; |
| if (d < t) { |
| break; |
| } |
| if (base < t || w == 0 || (base - t) > SIZE_MAX / w) { |
| return PunycodeError; |
| } |
| w *= (base - t); |
| } |
| |
| len += 1; |
| if (i > SIZE_MAX - delta) { |
| return PunycodeError; |
| } |
| i += delta; |
| if (n > SIZE_MAX - i / len) { |
| return PunycodeError; |
| } |
| n += i / len; |
| i %= len; |
| |
| // char validation |
| if (n > UINT32_MAX || !validate_char((uint32_t)n)) { |
| return PunycodeError; |
| } |
| |
| // insert new character |
| if (len > SMALL_PUNYCODE_LEN) { |
| return PunycodeError; |
| } |
| memmove(out + i + 1, out + i, (len - i - 1) * sizeof(uint32_t)); |
| out[i] = (uint32_t)n; |
| |
| // start i index at incremented position |
| i++; |
| |
| // If there are no more deltas, decoding is complete. |
| if (punycode_len == 0) { |
| *out_len = len; |
| return PunycodeOk; |
| } |
| |
| // Perform bias adaptation. |
| delta /= damp; |
| damp = 2; |
| |
| delta += delta / len; |
| k = 0; |
| while (delta > ((base - t_min) * t_max) / 2) { |
| delta /= base - t_min; |
| k += base; |
| } |
| bias = k + ((base - t_min + 1) * delta) / (delta + skew); |
| } |
| } |
| |
| struct ident { |
| const char *ascii_start; |
| size_t ascii_len; |
| const char *punycode_start; |
| size_t punycode_len; |
| }; |
| |
| static NODISCARD overflow_status display_ident(const char *ascii_start, size_t ascii_len, const char *punycode_start, size_t punycode_len, uint8_t *out, size_t *out_len) { |
| uint32_t outbuf[SMALL_PUNYCODE_LEN]; |
| |
| size_t wide_len; |
| size_t out_buflen = *out_len; |
| |
| if (punycode_len == 0) { |
| if (ascii_len > out_buflen) { |
| return OverflowOverflow; |
| } |
| memcpy(out, ascii_start, ascii_len); |
| *out_len = ascii_len; |
| } else if (punycode_decode(ascii_start, ascii_len, punycode_start, punycode_len, &outbuf, &wide_len) == PunycodeOk) { |
| size_t narrow_len = 0; |
| for (size_t i = 0; i < wide_len; i++) { |
| if (out_buflen - narrow_len < 4) { |
| return OverflowOverflow; |
| } |
| unsigned char *pos = &out[narrow_len]; |
| narrow_len += code_to_utf8(pos, outbuf[i]); |
| } |
| *out_len = narrow_len; |
| } else { |
| size_t narrow_len = 0; |
| if (out_buflen < strlen("punycode{")) { |
| return OverflowOverflow; |
| } |
| memcpy(out, "punycode{", strlen("punycode{")); |
| narrow_len = strlen("punycode{"); |
| if (ascii_len > 0) { |
| if (out_buflen - narrow_len < ascii_len || out_buflen - narrow_len - ascii_len < 1) { |
| return OverflowOverflow; |
| } |
| memcpy(out + narrow_len, ascii_start, ascii_len); |
| narrow_len += ascii_len; |
| out[narrow_len] = '-'; |
| narrow_len++; |
| } |
| if (out_buflen - narrow_len < punycode_len || out_buflen - narrow_len - punycode_len < 1) { |
| return OverflowOverflow; |
| } |
| memcpy(out + narrow_len, punycode_start, punycode_len); |
| narrow_len += punycode_len; |
| out[narrow_len] = '}'; |
| narrow_len++; |
| *out_len = narrow_len; |
| } |
| |
| return OverflowOk; |
| } |
| |
| static NODISCARD bool try_parse_uint(const char *buf, size_t len, uint64_t *result) { |
| size_t cur = 0; |
| for(;cur < len && buf[cur] == '0';cur++); |
| uint64_t result_val = 0; |
| if (len - cur > 16) return false; |
| for(;cur < len;cur++) { |
| char c = buf[cur]; |
| result_val <<= 4; |
| if ('0' <= c && c <= '9') { |
| result_val += c - '0'; |
| } else if ('a' <= c && c <= 'f') { |
| result_val += 10 + (c - 'a'); |
| } else { |
| return false; |
| } |
| } |
| *result = result_val; |
| return true; |
| } |
| |
| static NODISCARD bool dinibble2int(const char *buf, uint8_t *result) { |
| uint8_t result_val = 0; |
| for (int i = 0; i < 2; i++) { |
| char c = buf[i]; |
| result_val <<= 4; |
| if ('0' <= c && c <= '9') { |
| result_val += c - '0'; |
| } else if ('a' <= c && c <= 'f') { |
| result_val += 10 + (c - 'a'); |
| } else { |
| return false; |
| } |
| } |
| *result = result_val; |
| return true; |
| } |
| |
| |
| typedef enum { |
| NtsOk = 0, |
| NtsOverflow = 1, |
| NtsInvalid = 2 |
| } nibbles_to_string_status; |
| |
| // '\u{10ffff}', +margin |
| #define ESCAPED_SIZE 12 |
| |
| static NODISCARD size_t char_to_string(uint32_t ch, uint8_t quote, bool first, char (*buf)[ESCAPED_SIZE]) { |
| // encode the character |
| char *escaped_buf = *buf; |
| escaped_buf[0] = '\\'; |
| size_t escaped_len = 2; |
| switch (ch) { |
| case '\0': |
| escaped_buf[1] = '0'; |
| break; |
| case '\t': |
| escaped_buf[1] = 't'; |
| break; |
| case '\r': |
| escaped_buf[1] = 'r'; |
| break; |
| case '\n': |
| escaped_buf[1] = 'n'; |
| break; |
| case '\\': |
| escaped_buf[1] = '\\'; |
| break; |
| default: |
| if (ch == quote) { |
| escaped_buf[1] = ch; |
| } else if (!unicode_isprint(ch) || (first && unicode_isgraphemextend(ch))) { |
| int hexlen = snprintf(escaped_buf, ESCAPED_SIZE, "\\u{%x}", (unsigned int)ch); |
| if (hexlen < 0) { |
| return 0; // (snprintf shouldn't fail!) |
| } |
| escaped_len = hexlen; |
| } else { |
| // printable character |
| escaped_buf[0] = ch; |
| escaped_len = 1; |
| } |
| break; |
| } |
| |
| return escaped_len; |
| } |
| |
| // convert nibbles to a single/double-quoted string |
| static NODISCARD nibbles_to_string_status nibbles_to_string(const char *buf, size_t len, uint8_t *out, size_t *out_len) { |
| uint8_t quote = '"'; |
| bool first = true; |
| |
| if ((len % 2) != 0) { |
| return NtsInvalid; // odd number of nibbles |
| } |
| |
| size_t cur_out_len = 0; |
| |
| // write starting quote |
| if (out != NULL) { |
| cur_out_len = *out_len; |
| if (cur_out_len == 0) { |
| return NtsOverflow; |
| } |
| *out++ = quote; |
| cur_out_len--; |
| } |
| |
| uint8_t conv_buf[4] = {0}; |
| size_t conv_buf_len = 0; |
| while (len > 1 || conv_buf_len > 0) { |
| while (len > 1 && conv_buf_len < sizeof(conv_buf)) { |
| if (!dinibble2int(buf, &conv_buf[conv_buf_len])) { |
| return NtsInvalid; |
| } |
| conv_buf_len++; |
| buf += 2; |
| len -= 2; |
| } |
| |
| // conv_buf is full here if possible, process 1 UTF-8 character |
| uint32_t ch = 0; |
| size_t consumed = utf8_next_char(conv_buf, &ch); |
| if (consumed > conv_buf_len) { |
| // either SIZE_MAX (invalid UTF-8) or finished input buffer and |
| // there are still bytes remaining, in both cases invalid |
| return NtsInvalid; |
| } |
| |
| // "consume" the character |
| memmove(conv_buf, conv_buf+consumed, conv_buf_len-consumed); |
| conv_buf_len -= consumed; |
| |
| char escaped_buf[ESCAPED_SIZE]; |
| size_t escaped_len = char_to_string(ch, '"', first, &escaped_buf); |
| if (out != NULL) { |
| if (cur_out_len < escaped_len) { |
| return NtsOverflow; |
| } |
| memcpy(out, escaped_buf, escaped_len); |
| out += escaped_len; |
| cur_out_len -= escaped_len; |
| } |
| first = false; |
| } |
| |
| // write ending quote |
| if (out != NULL) { |
| if (cur_out_len == 0) { |
| return NtsOverflow; |
| } |
| *out++ = quote; |
| cur_out_len--; |
| *out_len -= cur_out_len; // subtract remaining space to get used space |
| } |
| |
| return NtsOk; |
| } |
| |
| static const char* basic_type(uint8_t tag) { |
| switch(tag) { |
| case 'b': |
| return "bool"; |
| case 'c': |
| return "char"; |
| case 'e': |
| return "str"; |
| case 'u': |
| return "()"; |
| case 'a': |
| return "i8"; |
| case 's': |
| return "i16"; |
| case 'l': |
| return "i32"; |
| case 'x': |
| return "i64"; |
| case 'n': |
| return "i128"; |
| case 'i': |
| return "isize"; |
| case 'h': |
| return "u8"; |
| case 't': |
| return "u16"; |
| case 'm': |
| return "u32"; |
| case 'y': |
| return "u64"; |
| case 'o': |
| return "u128"; |
| case 'j': |
| return "usize"; |
| case 'f': |
| return "f32"; |
| case 'd': |
| return "f64"; |
| case 'z': |
| return "!"; |
| case 'p': |
| return "_"; |
| case 'v': |
| return "..."; |
| default: |
| return NULL; |
| } |
| } |
| |
| static NODISCARD demangle_status parser_push_depth(struct parser *parser) { |
| parser->depth++; |
| if (parser->depth > MAX_DEPTH) { |
| return DemangleRecursed; |
| } else { |
| return DemangleOk; |
| } |
| } |
| |
| static demangle_status parser_pop_depth(struct parser *parser) { |
| parser->depth--; |
| return DemangleOk; |
| } |
| |
| static uint8_t parser_peek(struct parser const *parser) { |
| if (parser->next == parser->sym_len) { |
| return 0; // add a "pseudo nul terminator" to avoid peeking past the end of a symbol |
| } else { |
| return parser->sym[parser->next]; |
| } |
| } |
| |
| static bool parser_eat(struct parser *parser, uint8_t ch) { |
| if (parser_peek(parser) == ch) { |
| if (ch != 0) { // safety: make sure we don't skip past the NUL terminator |
| parser->next++; |
| } |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| static uint8_t parser_next(struct parser *parser) { |
| // don't advance after end of input, and return an imaginary NUL terminator |
| if (parser->next == parser->sym_len) { |
| return 0; |
| } else { |
| return parser->sym[parser->next++]; |
| } |
| } |
| |
| static NODISCARD demangle_status parser_ch(struct parser *parser, uint8_t *next) { |
| // don't advance after end of input |
| if (parser->next == parser->sym_len) { |
| return DemangleInvalid; |
| } else { |
| *next = parser->sym[parser->next++]; |
| return DemangleOk; |
| } |
| } |
| |
| struct buf { |
| const char *start; |
| size_t len; |
| }; |
| |
| static NODISCARD demangle_status parser_hex_nibbles(struct parser *parser, struct buf *buf) { |
| size_t start = parser->next; |
| for (;;) { |
| uint8_t ch = parser_next(parser); |
| if (ch == '_') { |
| break; |
| } |
| if (!(('0' <= ch && ch <= '9') || ('a' <= ch && ch <= 'f'))) { |
| return DemangleInvalid; |
| } |
| } |
| buf->start = parser->sym + start; |
| buf->len = parser->next - start - 1; // skip final _ |
| return DemangleOk; |
| } |
| |
| static NODISCARD demangle_status parser_digit_10(struct parser *parser, uint8_t *out) { |
| uint8_t ch = parser_peek(parser); |
| if ('0' <= ch && ch <= '9') { |
| *out = ch - '0'; |
| parser->next++; |
| return DemangleOk; |
| } else { |
| return DemangleInvalid; |
| } |
| } |
| |
| static NODISCARD demangle_status parser_digit_62(struct parser *parser, uint64_t *out) { |
| uint8_t ch = parser_peek(parser); |
| if ('0' <= ch && ch <= '9') { |
| *out = ch - '0'; |
| parser->next++; |
| return DemangleOk; |
| } else if ('a' <= ch && ch <= 'z') { |
| *out = 10 + (ch - 'a'); |
| parser->next++; |
| return DemangleOk; |
| } else if ('A' <= ch && ch <= 'Z') { |
| *out = 10 + 26 + (ch - 'A'); |
| parser->next++; |
| return DemangleOk; |
| } else { |
| return DemangleInvalid; |
| } |
| } |
| |
| static NODISCARD demangle_status parser_integer_62(struct parser *parser, uint64_t *out) { |
| if (parser_eat(parser, '_')) { |
| *out = 0; |
| return DemangleOk; |
| } |
| |
| uint64_t x = 0; |
| demangle_status status; |
| while (!parser_eat(parser, '_')) { |
| uint64_t d; |
| if ((status = parser_digit_62(parser, &d)) != DemangleOk) { |
| return status; |
| } |
| if (x > UINT64_MAX / 62) { |
| return DemangleInvalid; |
| } |
| x *= 62; |
| if (x > UINT64_MAX - d) { |
| return DemangleInvalid; |
| } |
| x += d; |
| } |
| if (x == UINT64_MAX) { |
| return DemangleInvalid; |
| } |
| *out = x + 1; |
| return DemangleOk; |
| } |
| |
| static NODISCARD demangle_status parser_opt_integer_62(struct parser *parser, uint8_t tag, uint64_t *out) { |
| if (!parser_eat(parser, tag)) { |
| *out = 0; |
| return DemangleOk; |
| } |
| |
| demangle_status status; |
| if ((status = parser_integer_62(parser, out)) != DemangleOk) { |
| return status; |
| } |
| if (*out == UINT64_MAX) { |
| return DemangleInvalid; |
| } |
| *out = *out + 1; |
| return DemangleOk; |
| } |
| |
| static NODISCARD demangle_status parser_disambiguator(struct parser *parser, uint64_t *out) { |
| return parser_opt_integer_62(parser, 's', out); |
| } |
| |
| typedef uint8_t parser_namespace_type; |
| |
| static NODISCARD demangle_status parser_namespace(struct parser *parser, parser_namespace_type *out) { |
| uint8_t next = parser_next(parser); |
| if ('A' <= next && next <= 'Z') { |
| *out = next; |
| return DemangleOk; |
| } else if ('a' <= next && next <= 'z') { |
| *out = 0; |
| return DemangleOk; |
| } else { |
| return DemangleInvalid; |
| } |
| } |
| |
| static NODISCARD demangle_status parser_backref(struct parser *parser, struct parser *out) { |
| size_t start = parser->next; |
| if (start == 0) { |
| return DemangleBug; |
| } |
| size_t s_start = start - 1; |
| uint64_t i; |
| demangle_status status = parser_integer_62(parser, &i); |
| if (status != DemangleOk) { |
| return status; |
| } |
| if (i >= s_start) { |
| return DemangleInvalid; |
| } |
| struct parser res = { |
| .sym = parser->sym, |
| .sym_len = parser->sym_len, |
| .next = (size_t)i, |
| .depth = parser->depth |
| }; |
| status = parser_push_depth(&res); |
| if (status != DemangleOk) { |
| return status; |
| } |
| *out = res; |
| return DemangleOk; |
| } |
| |
| static NODISCARD demangle_status parser_ident(struct parser *parser, struct ident *out) { |
| bool is_punycode = parser_eat(parser, 'u'); |
| size_t len; |
| uint8_t d; |
| demangle_status status = parser_digit_10(parser, &d); |
| len = d; |
| if (status != DemangleOk) { |
| return status; |
| } |
| if (len) { |
| for (;;) { |
| status = parser_digit_10(parser, &d); |
| if (status != DemangleOk) { |
| break; |
| } |
| if (len > SIZE_MAX / 10) { |
| return DemangleInvalid; |
| } |
| len *= 10; |
| if (len > SIZE_MAX - d) { |
| return DemangleInvalid; |
| } |
| len += d; |
| } |
| } |
| |
| // Skip past the optional `_` separator. |
| parser_eat(parser, '_'); |
| |
| size_t start = parser->next; |
| if (parser->sym_len - parser->next < len) { |
| return DemangleInvalid; |
| } |
| parser->next += len; |
| |
| const char *ident = &parser->sym[start]; |
| |
| if (is_punycode) { |
| const char *underscore = demangle_memrchr(ident, '_', (size_t)len); |
| if (underscore == NULL) { |
| *out = (struct ident){ |
| .ascii_start="", |
| .ascii_len=0, |
| .punycode_start=ident, |
| .punycode_len=len |
| }; |
| } else { |
| size_t ascii_len = underscore - ident; |
| // ascii_len <= len - 1 since `_` is in the first len bytes |
| size_t punycode_len = len - 1 - ascii_len; |
| *out = (struct ident){ |
| .ascii_start=ident, |
| .ascii_len=ascii_len, |
| .punycode_start=underscore + 1, |
| .punycode_len=punycode_len |
| }; |
| } |
| if (out->punycode_len == 0) { |
| return DemangleInvalid; |
| } |
| return DemangleOk; |
| } else { |
| *out = (struct ident) { |
| .ascii_start=ident, |
| .ascii_len=(size_t)len, |
| .punycode_start="", |
| .punycode_len=0, |
| }; |
| return DemangleOk; |
| } |
| } |
| |
| #define INVALID_SYNTAX "{invalid syntax}" |
| |
| static const char *demangle_error_message(demangle_status status) { |
| switch (status) { |
| case DemangleInvalid: |
| return INVALID_SYNTAX; |
| case DemangleBug: |
| return "{bug}"; |
| case DemangleRecursed: |
| return "{recursion limit reached}"; |
| default: |
| return "{unknown error}"; |
| } |
| } |
| |
| #define PRINT(print_fn) \ |
| do { \ |
| if ((print_fn) == OverflowOverflow) { \ |
| return OverflowOverflow; \ |
| } \ |
| } while(0) |
| |
| #define PRINT_CH(printer, s) PRINT(printer_print_ch((printer), (s))) |
| #define PRINT_STR(printer, s) PRINT(printer_print_str((printer), (s))) |
| #define PRINT_U64(printer, s) PRINT(printer_print_u64((printer), (s))) |
| #define PRINT_IDENT(printer, s) PRINT(printer_print_ident((printer), (s))) |
| |
| #define INVALID(printer) \ |
| do { \ |
| PRINT_STR((printer), INVALID_SYNTAX); \ |
| (printer)->status = DemangleInvalid; \ |
| return OverflowOk; \ |
| } while(0) |
| |
| #define PARSE(printer, method, ...) \ |
| do { \ |
| if ((printer)->status != DemangleOk) { \ |
| PRINT_STR((printer), "?"); \ |
| return OverflowOk; \ |
| } else { \ |
| demangle_status _parse_status = method(&(printer)->parser, ## __VA_ARGS__); \ |
| if (_parse_status != DemangleOk) { \ |
| PRINT_STR((printer), demangle_error_message(_parse_status)); \ |
| (printer)->status = _parse_status; \ |
| return OverflowOk; \ |
| } \ |
| } \ |
| } while(0) |
| |
| #define PRINT_SEP_LIST(printer, body, sep) \ |
| do { \ |
| size_t _sep_list_i; \ |
| PRINT_SEP_LIST_COUNT(printer, _sep_list_i, body, sep); \ |
| } while(0) |
| |
| #define PRINT_SEP_LIST_COUNT(printer, count, body, sep) \ |
| do { \ |
| count = 0; \ |
| while ((printer)->status == DemangleOk && !printer_eat((printer), 'E')) { \ |
| if (count > 0) { PRINT_STR(printer, sep); } \ |
| body; \ |
| count++; \ |
| } \ |
| } while(0) |
| |
| static bool printer_eat(struct printer *printer, uint8_t b) { |
| if (printer->status != DemangleOk) { |
| return false; |
| } |
| |
| return parser_eat(&printer->parser, b); |
| } |
| |
| static void printer_pop_depth(struct printer *printer) { |
| if (printer->status == DemangleOk) { |
| parser_pop_depth(&printer->parser); |
| } |
| } |
| |
| static NODISCARD overflow_status printer_print_buf(struct printer *printer, const char *start, size_t len) { |
| if (printer->out == NULL) { |
| return OverflowOk; |
| } |
| if (printer->out_len < len) { |
| return OverflowOverflow; |
| } |
| |
| memcpy(printer->out, start, len); |
| printer->out += len; |
| printer->out_len -= len; |
| return OverflowOk; |
| } |
| |
| static NODISCARD overflow_status printer_print_str(struct printer *printer, const char *buf) { |
| return printer_print_buf(printer, buf, strlen(buf)); |
| } |
| |
| static NODISCARD overflow_status printer_print_ch(struct printer *printer, char ch) { |
| return printer_print_buf(printer, &ch, 1); |
| } |
| |
| static NODISCARD overflow_status printer_print_u64(struct printer *printer, uint64_t n) { |
| char buf[32] = {0}; |
| sprintf(buf, "%llu", (unsigned long long)n); // printing uint64 uses 21 < 32 chars |
| return printer_print_str(printer, buf); |
| } |
| |
| static NODISCARD overflow_status printer_print_ident(struct printer *printer, struct ident *ident) { |
| if (printer->out == NULL) { |
| return OverflowOk; |
| } |
| |
| size_t out_len = printer->out_len; |
| overflow_status status; |
| if ((status = display_ident(ident->ascii_start, ident->ascii_len, ident->punycode_start, ident->punycode_len, (uint8_t*)printer->out, &out_len)) != OverflowOk) { |
| return status; |
| } |
| printer->out += out_len; |
| printer->out_len -= out_len; |
| return OverflowOk; |
| } |
| |
| typedef overflow_status (*printer_fn)(struct printer *printer); |
| typedef overflow_status (*backref_fn)(struct printer *printer, bool *arg); |
| |
| static NODISCARD overflow_status printer_print_backref(struct printer *printer, backref_fn func, bool *arg) { |
| struct parser backref; |
| PARSE(printer, parser_backref, &backref); |
| |
| if (printer->out == NULL) { |
| return OverflowOk; |
| } |
| |
| struct parser orig_parser = printer->parser; |
| demangle_status orig_status = printer->status; // fixme not sure this is needed match for Ok on the Rust side |
| printer->parser = backref; |
| printer->status = DemangleOk; |
| overflow_status status = func(printer, arg); |
| printer->parser = orig_parser; |
| printer->status = orig_status; |
| |
| return status; |
| } |
| |
| static NODISCARD overflow_status printer_print_lifetime_from_index(struct printer *printer, uint64_t lt) { |
| // Bound lifetimes aren't tracked when skipping printing. |
| if (printer->out == NULL) { |
| return OverflowOk; |
| } |
| |
| PRINT_STR(printer, "'"); |
| if (lt == 0) { |
| PRINT_STR(printer, "_"); |
| return OverflowOk; |
| } |
| |
| if (printer->bound_lifetime_depth < lt) { |
| INVALID(printer); |
| } else { |
| uint64_t depth = printer->bound_lifetime_depth - lt; |
| if (depth < 26) { |
| PRINT_CH(printer, 'a' + depth); |
| } else { |
| PRINT_STR(printer, "_"); |
| PRINT_U64(printer, depth); |
| } |
| |
| return OverflowOk; |
| } |
| } |
| |
| static NODISCARD overflow_status printer_in_binder(struct printer *printer, printer_fn func) { |
| uint64_t bound_lifetimes; |
| PARSE(printer, parser_opt_integer_62, 'G', &bound_lifetimes); |
| |
| // Don't track bound lifetimes when skipping printing. |
| if (printer->out == NULL) { |
| return func(printer); |
| } |
| |
| if (bound_lifetimes > 0) { |
| PRINT_STR(printer, "for<"); |
| for (uint64_t i = 0; i < bound_lifetimes; i++) { |
| if (i > 0) { |
| PRINT_STR(printer, ", "); |
| } |
| printer->bound_lifetime_depth++; |
| PRINT(printer_print_lifetime_from_index(printer, 1)); |
| } |
| PRINT_STR(printer, "> "); |
| } |
| |
| overflow_status r = func(printer); |
| printer->bound_lifetime_depth -= bound_lifetimes; |
| |
| return r; |
| } |
| |
| static NODISCARD overflow_status printer_print_generic_arg(struct printer *printer) { |
| if (printer_eat(printer, 'L')) { |
| uint64_t lt; |
| PARSE(printer, parser_integer_62, <); |
| return printer_print_lifetime_from_index(printer, lt); |
| } else if (printer_eat(printer, 'K')) { |
| return printer_print_const(printer, false); |
| } else { |
| return printer_print_type(printer); |
| } |
| } |
| |
| static NODISCARD overflow_status printer_print_generic_args(struct printer *printer) { |
| PRINT_STR(printer, "<"); |
| PRINT_SEP_LIST(printer, PRINT(printer_print_generic_arg(printer)), ", "); |
| PRINT_STR(printer, ">"); |
| return OverflowOk; |
| } |
| |
| static NODISCARD overflow_status printer_print_path_out_of_value(struct printer *printer, bool *_arg) { |
| (void)_arg; |
| return printer_print_path(printer, false); |
| } |
| |
| static NODISCARD overflow_status printer_print_path_in_value(struct printer *printer, bool *_arg) { |
| (void)_arg; |
| return printer_print_path(printer, true); |
| } |
| |
| static NODISCARD overflow_status printer_print_path(struct printer *printer, bool in_value) { |
| PARSE(printer, parser_push_depth); |
| uint8_t tag; |
| PARSE(printer, parser_ch, &tag); |
| |
| overflow_status st; |
| uint64_t dis; |
| struct ident name; |
| parser_namespace_type ns; |
| char *orig_out; |
| |
| switch(tag) { |
| case 'C': |
| PARSE(printer, parser_disambiguator, &dis); |
| PARSE(printer, parser_ident, &name); |
| |
| PRINT_IDENT(printer, &name); |
| |
| if (printer->out != NULL && !printer->alternate && dis != 0) { |
| PRINT_STR(printer, "["); |
| char buf[24] = {0}; |
| sprintf(buf, "%llx", (unsigned long long)dis); |
| PRINT_STR(printer, buf); |
| PRINT_STR(printer, "]"); |
| } |
| break; |
| case 'N': |
| PARSE(printer, parser_namespace, &ns); |
| if ((st = printer_print_path(printer, in_value)) != OverflowOk) { |
| return st; |
| } |
| |
| // HACK(eddyb) if the parser is already marked as having errored, |
| // `parse!` below will print a `?` without its preceding `::` |
| // (because printing the `::` is skipped in certain conditions, |
| // i.e. a lowercase namespace with an empty identifier), |
| // so in order to get `::?`, the `::` has to be printed here. |
| if (printer->status != DemangleOk) { |
| PRINT_STR(printer, "::"); |
| } |
| |
| PARSE(printer, parser_disambiguator, &dis); |
| PARSE(printer, parser_ident, &name); |
| // Special namespace, like closures and shims |
| if (ns) { |
| PRINT_STR(printer, "::{"); |
| if (ns == 'C') { |
| PRINT_STR(printer, "closure"); |
| } else if (ns == 'S') { |
| PRINT_STR(printer, "shim"); |
| } else { |
| PRINT_CH(printer, ns); |
| } |
| if (name.ascii_len != 0 || name.punycode_len != 0) { |
| PRINT_STR(printer, ":"); |
| PRINT_IDENT(printer, &name); |
| } |
| PRINT_STR(printer, "#"); |
| PRINT_U64(printer, dis); |
| PRINT_STR(printer, "}"); |
| } else { |
| // Implementation-specific/unspecified namespaces |
| if (name.ascii_len != 0 || name.punycode_len != 0) { |
| PRINT_STR(printer, "::"); |
| PRINT_IDENT(printer, &name); |
| } |
| } |
| break; |
| case 'M': |
| case 'X': |
| // for impls, ignore the impls own path |
| PARSE(printer, parser_disambiguator, &dis); |
| orig_out = printer->out; |
| printer->out = NULL; |
| PRINT(printer_print_path(printer, false)); |
| printer->out = orig_out; |
| |
| // fallthru |
| case 'Y': |
| PRINT_STR(printer, "<"); |
| PRINT(printer_print_type(printer)); |
| if (tag != 'M') { |
| PRINT_STR(printer, " as "); |
| PRINT(printer_print_path(printer, false)); |
| } |
| PRINT_STR(printer, ">"); |
| break; |
| case 'I': |
| PRINT(printer_print_path(printer, in_value)); |
| if (in_value) { |
| PRINT_STR(printer, "::"); |
| } |
| PRINT(printer_print_generic_args(printer)); |
| break; |
| case 'B': |
| PRINT(printer_print_backref(printer, in_value ? printer_print_path_in_value : printer_print_path_out_of_value, NULL)); |
| break; |
| default: |
| INVALID(printer); |
| break; |
| } |
| |
| printer_pop_depth(printer); |
| return OverflowOk; |
| } |
| |
| static NODISCARD overflow_status printer_print_const_uint(struct printer *printer, uint8_t tag) { |
| struct buf hex; |
| PARSE(printer, parser_hex_nibbles, &hex); |
| |
| uint64_t val; |
| if (try_parse_uint(hex.start, hex.len, &val)) { |
| PRINT_U64(printer, val); |
| } else { |
| PRINT_STR(printer, "0x"); |
| PRINT(printer_print_buf(printer, hex.start, hex.len)); |
| } |
| |
| if (printer->out != NULL && !printer->alternate) { |
| const char *ty = basic_type(tag); |
| if (/* safety */ ty != NULL) { |
| PRINT_STR(printer, ty); |
| } |
| } |
| |
| return OverflowOk; |
| } |
| |
| static NODISCARD overflow_status printer_print_const_str_literal(struct printer *printer) { |
| struct buf hex; |
| PARSE(printer, parser_hex_nibbles, &hex); |
| |
| size_t out_len = SIZE_MAX; |
| nibbles_to_string_status nts_status = nibbles_to_string(hex.start, hex.len, NULL, &out_len); |
| switch (nts_status) { |
| case NtsOk: |
| if (printer->out != NULL) { |
| out_len = printer->out_len; |
| nts_status = nibbles_to_string(hex.start, hex.len, (uint8_t*)printer->out, &out_len); |
| if (nts_status != NtsOk) { |
| return OverflowOverflow; |
| } |
| printer->out += out_len; |
| printer->out_len -= out_len; |
| } |
| return OverflowOk; |
| case NtsOverflow: |
| // technically if there is a string of size `SIZE_MAX/6` whose escaped version overflows |
| // SIZE_MAX but has an invalid char, this will be a "fake" overflow. In practice, |
| // that is not going to happen and a fuzzer will not generate strings of this length. |
| return OverflowOverflow; |
| case NtsInvalid: |
| default: |
| INVALID(printer); |
| } |
| } |
| |
| static NODISCARD overflow_status printer_print_const_struct(struct printer *printer) { |
| uint64_t dis; |
| struct ident name; |
| PARSE(printer, parser_disambiguator, &dis); |
| PARSE(printer, parser_ident, &name); |
| PRINT_IDENT(printer, &name); |
| PRINT_STR(printer, ": "); |
| return printer_print_const(printer, true); |
| } |
| |
| static NODISCARD overflow_status printer_print_const_out_of_value(struct printer *printer, bool *_arg) { |
| (void)_arg; |
| return printer_print_const(printer, false); |
| } |
| |
| static NODISCARD overflow_status printer_print_const_in_value(struct printer *printer, bool *_arg) { |
| (void)_arg; |
| return printer_print_const(printer, true); |
| } |
| |
| static NODISCARD overflow_status printer_print_const(struct printer *printer, bool in_value) { |
| uint8_t tag; |
| |
| PARSE(printer, parser_ch, &tag); |
| PARSE(printer, parser_push_depth); |
| |
| struct buf hex; |
| uint64_t val; |
| size_t count; |
| |
| bool opened_brace = false; |
| #define OPEN_BRACE_IF_OUTSIDE_EXPR \ |
| do { if (!in_value) { \ |
| opened_brace = true; \ |
| PRINT_STR(printer, "{"); \ |
| } } while(0) |
| |
| switch(tag) { |
| case 'p': |
| PRINT_STR(printer, "_"); |
| break; |
| // Primitive leaves with hex-encoded values (see `basic_type`). |
| case 'a': |
| case 's': |
| case 'l': |
| case 'x': |
| case 'n': |
| case 'i': |
| if (printer_eat(printer, 'n')) { |
| PRINT_STR(printer, "-"); |
| } |
| /* fallthrough */ |
| case 'h': |
| case 't': |
| case 'm': |
| case 'y': |
| case 'o': |
| case 'j': |
| PRINT(printer_print_const_uint(printer, tag)); |
| break; |
| case 'b': |
| PARSE(printer, parser_hex_nibbles, &hex); |
| if (try_parse_uint(hex.start, hex.len, &val)) { |
| if (val == 0) { |
| PRINT_STR(printer, "false"); |
| } else if (val == 1) { |
| PRINT_STR(printer, "true"); |
| } else { |
| INVALID(printer); |
| } |
| } else { |
| INVALID(printer); |
| } |
| break; |
| case 'c': |
| PARSE(printer, parser_hex_nibbles, &hex); |
| if (try_parse_uint(hex.start, hex.len, &val) |
| && val < UINT32_MAX |
| && validate_char((uint32_t)val)) |
| { |
| char escaped_buf[ESCAPED_SIZE]; |
| size_t escaped_size = char_to_string((uint32_t)val, '\'', true, &escaped_buf); |
| |
| PRINT_STR(printer, "'"); |
| PRINT(printer_print_buf(printer, escaped_buf, escaped_size)); |
| PRINT_STR(printer, "'"); |
| } else { |
| INVALID(printer); |
| } |
| break; |
| case 'e': |
| OPEN_BRACE_IF_OUTSIDE_EXPR; |
| PRINT_STR(printer, "*"); |
| PRINT(printer_print_const_str_literal(printer)); |
| break; |
| case 'R': |
| case 'Q': |
| if (tag == 'R' && printer_eat(printer, 'e')) { |
| PRINT(printer_print_const_str_literal(printer)); |
| } else { |
| OPEN_BRACE_IF_OUTSIDE_EXPR; |
| PRINT_STR(printer, "&"); |
| if (tag != 'R') { |
| PRINT_STR(printer, "mut "); |
| } |
| PRINT(printer_print_const(printer, true)); |
| } |
| break; |
| case 'A': |
| OPEN_BRACE_IF_OUTSIDE_EXPR; |
| PRINT_STR(printer, "["); |
| PRINT_SEP_LIST(printer, PRINT(printer_print_const(printer, true)), ", "); |
| PRINT_STR(printer, "]"); |
| break; |
| case 'T': |
| OPEN_BRACE_IF_OUTSIDE_EXPR; |
| PRINT_STR(printer, "("); |
| PRINT_SEP_LIST_COUNT(printer, count, PRINT(printer_print_const(printer, true)), ", "); |
| if (count == 1) { |
| PRINT_STR(printer, ","); |
| } |
| PRINT_STR(printer, ")"); |
| break; |
| case 'V': |
| OPEN_BRACE_IF_OUTSIDE_EXPR; |
| PRINT(printer_print_path(printer, true)); |
| PARSE(printer, parser_ch, &tag); |
| switch(tag) { |
| case 'U': |
| break; |
| case 'T': |
| PRINT_STR(printer, "("); |
| PRINT_SEP_LIST(printer, PRINT(printer_print_const(printer, true)), ", "); |
| PRINT_STR(printer, ")"); |
| break; |
| case 'S': |
| PRINT_STR(printer, " { "); |
| PRINT_SEP_LIST(printer, PRINT(printer_print_const_struct(printer)), ", "); |
| PRINT_STR(printer, " }"); |
| break; |
| default: |
| INVALID(printer); |
| } |
| break; |
| case 'B': |
| PRINT(printer_print_backref(printer, in_value ? printer_print_const_in_value : printer_print_const_out_of_value, NULL)); |
| break; |
| default: |
| INVALID(printer); |
| } |
| #undef OPEN_BRACE_IF_OUTSIDE_EXPR |
| |
| if (opened_brace) { |
| PRINT_STR(printer, "}"); |
| } |
| printer_pop_depth(printer); |
| |
| return OverflowOk; |
| } |
| |
| /// A trait in a trait object may have some "existential projections" |
| /// (i.e. associated type bindings) after it, which should be printed |
| /// in the `<...>` of the trait, e.g. `dyn Trait<T, U, Assoc=X>`. |
| /// To this end, this method will keep the `<...>` of an 'I' path |
| /// open, by omitting the `>`, and return `Ok(true)` in that case. |
| static NODISCARD overflow_status printer_print_maybe_open_generics(struct printer *printer, bool *open) { |
| if (printer_eat(printer, 'B')) { |
| // NOTE(eddyb) the closure may not run if printing is being skipped, |
| // but in that case the returned boolean doesn't matter. |
| *open = false; |
| return printer_print_backref(printer, printer_print_maybe_open_generics, open); |
| } else if(printer_eat(printer, 'I')) { |
| PRINT(printer_print_path(printer, false)); |
| PRINT_STR(printer, "<"); |
| PRINT_SEP_LIST(printer, PRINT(printer_print_generic_arg(printer)), ", "); |
| *open = true; |
| return OverflowOk; |
| } else { |
| PRINT(printer_print_path(printer, false)); |
| *open = false; |
| return OverflowOk; |
| } |
| } |
| |
| static NODISCARD overflow_status printer_print_dyn_trait(struct printer *printer) { |
| bool open; |
| PRINT(printer_print_maybe_open_generics(printer, &open)); |
| |
| while (printer_eat(printer, 'p')) { |
| if (!open) { |
| PRINT_STR(printer, "<"); |
| open = true; |
| } else { |
| PRINT_STR(printer, ", "); |
| } |
| |
| struct ident name; |
| PARSE(printer, parser_ident, &name); |
| |
| PRINT_IDENT(printer, &name); |
| PRINT_STR(printer, " = "); |
| PRINT(printer_print_type(printer)); |
| } |
| |
| if (open) { |
| PRINT_STR(printer, ">"); |
| } |
| |
| return OverflowOk; |
| } |
| |
| static NODISCARD overflow_status printer_print_object_bounds(struct printer *printer) { |
| PRINT_SEP_LIST(printer, PRINT(printer_print_dyn_trait(printer)), " + "); |
| return OverflowOk; |
| } |
| |
| static NODISCARD overflow_status printer_print_function_type(struct printer *printer) { |
| bool is_unsafe = printer_eat(printer, 'U'); |
| const char *abi; |
| size_t abi_len; |
| if (printer_eat(printer, 'K')) { |
| if (printer_eat(printer, 'C')) { |
| abi = "C"; |
| abi_len = 1; |
| } else { |
| struct ident abi_ident; |
| PARSE(printer, parser_ident, &abi_ident); |
| if (abi_ident.ascii_len == 0 || abi_ident.punycode_len != 0) { |
| INVALID(printer); |
| } |
| abi = abi_ident.ascii_start; |
| abi_len = abi_ident.ascii_len; |
| } |
| } else { |
| abi = NULL; |
| abi_len = 0; |
| } |
| |
| if (is_unsafe) { |
| PRINT_STR(printer, "unsafe "); |
| } |
| |
| if (abi != NULL) { |
| PRINT_STR(printer, "extern \""); |
| |
| // replace _ with - |
| while (abi_len > 0) { |
| const char *minus = memchr(abi, '_', abi_len); |
| if (minus == NULL) { |
| PRINT(printer_print_buf(printer, (const char*)abi, abi_len)); |
| break; |
| } else { |
| size_t space_to_minus = minus - abi; |
| PRINT(printer_print_buf(printer, (const char*)abi, space_to_minus)); |
| PRINT_STR(printer, "-"); |
| abi = minus + 1; |
| abi_len -= (space_to_minus + 1); |
| } |
| } |
| |
| PRINT_STR(printer, "\" "); |
| } |
| |
| PRINT_STR(printer, "fn("); |
| PRINT_SEP_LIST(printer, PRINT(printer_print_type(printer)), ", "); |
| PRINT_STR(printer, ")"); |
| |
| if (printer_eat(printer, 'u')) { |
| // Skip printing the return type if it's 'u', i.e. `()`. |
| } else { |
| PRINT_STR(printer, " -> "); |
| PRINT(printer_print_type(printer)); |
| } |
| |
| return OverflowOk; |
| } |
| |
| static NODISCARD overflow_status printer_print_type_backref(struct printer *printer, bool *_arg) { |
| (void)_arg; |
| return printer_print_type(printer); |
| } |
| |
| static NODISCARD overflow_status printer_print_type(struct printer *printer) { |
| uint8_t tag; |
| PARSE(printer, parser_ch, &tag); |
| |
| const char *basic_ty = basic_type(tag); |
| if (basic_ty) { |
| return printer_print_str(printer, basic_ty); |
| } |
| |
| uint64_t count; |
| uint64_t lt; |
| |
| PARSE(printer, parser_push_depth); |
| switch (tag) { |
| case 'R': |
| case 'Q': |
| PRINT_STR(printer, "&"); |
| if (printer_eat(printer, 'L')) { |
| PARSE(printer, parser_integer_62, <); |
| if (lt != 0) { |
| PRINT(printer_print_lifetime_from_index(printer, lt)); |
| PRINT_STR(printer, " "); |
| } |
| } |
| if (tag != 'R') { |
| PRINT_STR(printer, "mut "); |
| } |
| PRINT(printer_print_type(printer)); |
| break; |
| case 'P': |
| case 'O': |
| PRINT_STR(printer, "*"); |
| if (tag != 'P') { |
| PRINT_STR(printer, "mut "); |
| } else { |
| PRINT_STR(printer, "const "); |
| } |
| PRINT(printer_print_type(printer)); |
| break; |
| case 'A': |
| case 'S': |
| PRINT_STR(printer, "["); |
| PRINT(printer_print_type(printer)); |
| if (tag == 'A') { |
| PRINT_STR(printer, "; "); |
| PRINT(printer_print_const(printer, true)); |
| } |
| PRINT_STR(printer, "]"); |
| break; |
| case 'T': |
| PRINT_STR(printer, "("); |
| PRINT_SEP_LIST_COUNT(printer, count, PRINT(printer_print_type(printer)), ", "); |
| if (count == 1) { |
| PRINT_STR(printer, ","); |
| } |
| PRINT_STR(printer, ")"); |
| break; |
| case 'F': |
| PRINT(printer_in_binder(printer, printer_print_function_type)); |
| break; |
| case 'D': |
| PRINT_STR(printer, "dyn "); |
| PRINT(printer_in_binder(printer, printer_print_object_bounds)); |
| |
| if (!printer_eat(printer, 'L')) { |
| INVALID(printer); |
| } |
| PARSE(printer, parser_integer_62, <); |
| |
| if (lt != 0) { |
| PRINT_STR(printer, " + "); |
| PRINT(printer_print_lifetime_from_index(printer, lt)); |
| } |
| break; |
| case 'B': |
| PRINT(printer_print_backref(printer, printer_print_type_backref, NULL)); |
| break; |
| default: |
| // Go back to the tag, so `print_path` also sees it. |
| if (printer->status == DemangleOk && /* safety */ printer->parser.next > 0) { |
| printer->parser.next--; |
| } |
| PRINT(printer_print_path(printer, false)); |
| } |
| |
| printer_pop_depth(printer); |
| return OverflowOk; |
| } |
| |
| NODISCARD static demangle_status rust_demangle_legacy_demangle(const char *s, size_t s_len, struct demangle_legacy *res, const char **rest) |
| { |
| if (s_len > strlen(s)) { |
| // s_len only exists to shorten the string, this is not a buffer API |
| return DemangleInvalid; |
| } |
| |
| const char *inner; |
| size_t inner_len; |
| if (s_len >= 3 && !strncmp(s, "_ZN", 3)) { |
| inner = s + 3; |
| inner_len = s_len - 3; |
| } else if (s_len >= 2 && !strncmp(s, "ZN", 2)) { |
| // On Windows, dbghelp strips leading underscores, so we accept "ZN...E" |
| // form too. |
| inner = s + 2; |
| inner_len = s_len - 2; |
| } else if (s_len >= 4 && !strncmp(s, "__ZN", 4)) { |
| // On OSX, symbols are prefixed with an extra _ |
| inner = s + 4; |
| inner_len = s_len - 4; |
| } else { |
| return DemangleInvalid; |
| } |
| |
| if (!str_isascii(inner, inner_len)) { |
| return DemangleInvalid; |
| } |
| |
| size_t elements = 0; |
| const char *chars = inner; |
| size_t chars_len = inner_len; |
| if (chars_len == 0) { |
| return DemangleInvalid; |
| } |
| char c; |
| while ((c = *chars) != 'E') { |
| // Decode an identifier element's length |
| if (c < '0' || c > '9') { |
| return DemangleInvalid; |
| } |
| size_t len = 0; |
| while (c >= '0' && c <= '9') { |
| size_t d = c - '0'; |
| if (len > SIZE_MAX / 10) { |
| return DemangleInvalid; |
| } |
| len *= 10; |
| if (len > SIZE_MAX - d) { |
| return DemangleInvalid; |
| } |
| len += d; |
| |
| chars++; |
| chars_len--; |
| if (chars_len == 0) { |
| return DemangleInvalid; |
| } |
| c = *chars; |
| } |
| |
| // Advance by the length |
| if (chars_len <= len) { |
| return DemangleInvalid; |
| } |
| chars += len; |
| chars_len -= len; |
| elements++; |
| } |
| *res = (struct demangle_legacy) { inner, inner_len, elements }; |
| *rest = chars + 1; |
| return DemangleOk; |
| } |
| |
| static bool is_rust_hash(const char *s, size_t len) { |
| if (len == 0 || s[0] != 'h') { |
| return false; |
| } |
| |
| for (size_t i = 1; i < len; i++) { |
| if (!((s[i] >= '0' && s[i] <= '9') || (s[i] >= 'a' && s[i] <= 'f') || (s[i] >= 'A' && s[i] <= 'F'))) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| NODISCARD static overflow_status rust_demangle_legacy_display_demangle(struct demangle_legacy res, char *out, size_t len, bool alternate) |
| { |
| struct printer printer = { |
| // not actually using the parser part of the printer, just keeping it to share the format functions |
| DemangleOk, |
| { NULL }, |
| out, |
| len, |
| 0, |
| alternate |
| }; |
| const char *inner = res.mangled; |
| for (size_t element = 0; element < res.elements; element++) { |
| size_t i = 0; |
| const char *rest; |
| for (rest = inner; rest < res.mangled + res.mangled_len && *rest >= '0' && *rest <= '9'; rest++) { |
| i *= 10; |
| i += *rest - '0'; |
| } |
| if ((size_t)(res.mangled + res.mangled_len - rest) < i) { |
| // safety: shouldn't reach this place if the input string is validated. bail out. |
| // safety: we knwo rest <= res.mangled + res.mangled_len from the for-loop above |
| break; |
| } |
| |
| size_t len = i; |
| inner = rest + len; |
| |
| // From here on, inner contains a pointer to the next element, rest[:len] to the current one |
| if (alternate && element + 1 == res.elements && is_rust_hash(rest, i)) { |
| break; |
| } |
| if (element != 0) { |
| PRINT_STR(&printer, "::"); |
| } |
| |
| if (len >= 2 && !strncmp(rest, "_$", 2)) { |
| rest++; |
| len--; |
| } |
| |
| while (len > 0) { |
| if (rest[0] == '.') { |
| if (len >= 2 && rest[1] == '.') { |
| PRINT_STR(&printer, "::"); |
| rest += 2; |
| len -= 2; |
| } else { |
| PRINT_STR(&printer, "."); |
| rest += 1; |
| len -= 1; |
| } |
| } else if (rest[0] == '$') { |
| const char *escape = memchr(rest + 1, '$', len - 1); |
| if (escape == NULL) { |
| break; |
| } |
| const char *escape_start = rest + 1; |
| size_t escape_len = escape - (rest + 1); |
| |
| size_t next_len = len - (escape + 1 - rest); |
| const char *next_rest = escape + 1; |
| |
| char ch; |
| if ((escape_len == 2 && escape_start[0] == 'S' && escape_start[1] == 'P')) { |
| ch = '@'; |
| } else if ((escape_len == 2 && escape_start[0] == 'B' && escape_start[1] == 'P')) { |
| ch = '*'; |
| } else if ((escape_len == 2 && escape_start[0] == 'R' && escape_start[1] == 'F')) { |
| ch = '&'; |
| } else if ((escape_len == 2 && escape_start[0] == 'L' && escape_start[1] == 'T')) { |
| ch = '<'; |
| } else if ((escape_len == 2 && escape_start[0] == 'G' && escape_start[1] == 'T')) { |
| ch = '>'; |
| } else if ((escape_len == 2 && escape_start[0] == 'L' && escape_start[1] == 'P')) { |
| ch = '('; |
| } else if ((escape_len == 2 && escape_start[0] == 'R' && escape_start[1] == 'P')) { |
| ch = ')'; |
| } else if ((escape_len == 1 && escape_start[0] == 'C')) { |
| ch = ','; |
| } else { |
| if (escape_len > 1 && escape_start[0] == 'u') { |
| escape_start++; |
| escape_len--; |
| uint64_t val; |
| if (try_parse_uint(escape_start, escape_len, &val) |
| && val < UINT32_MAX |
| && validate_char((uint32_t)val)) |
| { |
| if (!unicode_iscontrol(val)) { |
| uint8_t wchr[4]; |
| size_t wchr_len = code_to_utf8(wchr, (uint32_t)val); |
| PRINT(printer_print_buf(&printer, (const char*)wchr, wchr_len)); |
| len = next_len; |
| rest = next_rest; |
| continue; |
| } |
| } |
| } |
| break; // print the rest of this element raw |
| } |
| PRINT_CH(&printer, ch); |
| len = next_len; |
| rest = next_rest; |
| } else { |
| size_t j = 0; |
| for (;j < len && rest[j] != '$' && rest[j] != '.';j++); |
| if (j == len) { |
| break; |
| } |
| PRINT(printer_print_buf(&printer, rest, j)); |
| rest += j; |
| len -= j; |
| } |
| } |
| PRINT(printer_print_buf(&printer, rest, len)); |
| } |
| |
| if (printer.out_len < OVERFLOW_MARGIN) { |
| return OverflowOverflow; |
| } |
| *printer.out = '\0'; |
| return OverflowOk; |
| } |
| |
| static bool is_symbol_like(const char *s, size_t len) { |
| // rust-demangle definition of symbol like: control characters and space are not symbol-like, all else is |
| for (size_t i = 0; i < len; i++) { |
| char ch = s[i]; |
| if (!(ch >= 0x21 && ch <= 0x7e)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| void rust_demangle_demangle(const char *s, struct demangle *res) |
| { |
| // During ThinLTO LLVM may import and rename internal symbols, so strip out |
| // those endings first as they're one of the last manglings applied to symbol |
| // names. |
| const char *llvm = ".llvm."; |
| const char *found_llvm = strstr(s, llvm); |
| size_t s_len = strlen(s); |
| if (found_llvm) { |
| const char *all_hex_ptr = found_llvm + strlen(".llvm."); |
| bool all_hex = true; |
| for (;*all_hex_ptr;all_hex_ptr++) { |
| if (!(('0' <= *all_hex_ptr && *all_hex_ptr <= '9') || |
| ('A' <= *all_hex_ptr && *all_hex_ptr <= 'F') || |
| *all_hex_ptr == '@')) { |
| all_hex = false; |
| break; |
| } |
| } |
| |
| if (all_hex) { |
| s_len = found_llvm - s; |
| } |
| } |
| |
| const char *suffix; |
| struct demangle_legacy legacy; |
| demangle_status st = rust_demangle_legacy_demangle(s, s_len, &legacy, &suffix); |
| if (st == DemangleOk) { |
| *res = (struct demangle) { |
| .style=DemangleStyleLegacy, |
| .mangled=legacy.mangled, |
| .mangled_len=legacy.mangled_len, |
| .elements=legacy.elements, |
| .original=s, |
| .original_len=s_len, |
| .suffix=suffix, |
| .suffix_len=s_len - (suffix - s), |
| }; |
| } else { |
| struct demangle_v0 v0; |
| st = rust_demangle_v0_demangle(s, s_len, &v0, &suffix); |
| if (st == DemangleOk) { |
| *res = (struct demangle) { |
| .style=DemangleStyleV0, |
| .mangled=v0.mangled, |
| .mangled_len=v0.mangled_len, |
| .elements=0, |
| .original=s, |
| .original_len=s_len, |
| .suffix=suffix, |
| .suffix_len=s_len - (suffix - s), |
| }; |
| } else { |
| *res = (struct demangle) { |
| .style=DemangleStyleUnknown, |
| .mangled=NULL, |
| .mangled_len=0, |
| .elements=0, |
| .original=s, |
| .original_len=s_len, |
| .suffix=s, |
| .suffix_len=0, |
| }; |
| } |
| } |
| |
| // Output like LLVM IR adds extra period-delimited words. See if |
| // we are in that case and save the trailing words if so. |
| if (res->suffix_len) { |
| if (res->suffix[0] == '.' && is_symbol_like(res->suffix, res->suffix_len)) { |
| // Keep the suffix |
| } else { |
| // Reset the suffix and invalidate the demangling |
| res->style = DemangleStyleUnknown; |
| res->suffix_len = 0; |
| } |
| } |
| } |
| |
| bool rust_demangle_is_known(struct demangle *res) { |
| return res->style != DemangleStyleUnknown; |
| } |
| |
| overflow_status rust_demangle_display_demangle(struct demangle const *res, char *out, size_t len, bool alternate) { |
| size_t original_len = res->original_len; |
| size_t out_len; |
| switch (res->style) { |
| case DemangleStyleUnknown: |
| if (len < original_len) { |
| return OverflowOverflow; |
| } else { |
| memcpy(out, res->original, original_len); |
| out += original_len; |
| len -= original_len; |
| break; |
| } |
| break; |
| case DemangleStyleLegacy: { |
| struct demangle_legacy legacy = { |
| res->mangled, |
| res->mangled_len, |
| res->elements |
| }; |
| if (rust_demangle_legacy_display_demangle(legacy, out, len, alternate) == OverflowOverflow) { |
| return OverflowOverflow; |
| } |
| out_len = strlen(out); |
| out += out_len; |
| len -= out_len; |
| break; |
| } |
| case DemangleStyleV0: { |
| struct demangle_v0 v0 = { |
| res->mangled, |
| res->mangled_len |
| }; |
| if (rust_demangle_v0_display_demangle(v0, out, len, alternate) == OverflowOverflow) { |
| return OverflowOverflow; |
| } |
| out_len = strlen(out); |
| out += out_len; |
| len -= out_len; |
| break; |
| } |
| } |
| size_t suffix_len = res->suffix_len; |
| if (len < suffix_len || len - suffix_len < OVERFLOW_MARGIN) { |
| return OverflowOverflow; |
| } |
| memcpy(out, res->suffix, suffix_len); |
| out[suffix_len] = 0; |
| return OverflowOk; |
| } |