Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  demangle-rust-v0.c

  Sprache: C
 

// 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)&nbsp;== 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 *printerbool *_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;
}

Messung V0.5 in Prozent
C=91 H=93 G=91

¤ Dauer der Verarbeitung: 0.35 Sekunden  (vorverarbeitet am  2026-04-25) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge