Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/nornir

crates/mimir-binary/src/v0.rs

//! A structured Rust **v0** symbol decoder (RFC 2603, the grammar rustc's
//! `rustc_symbol_mangling::v0` writes). `rustc-demangle` only prints text; the
//! binary layer needs the TREE: the crate root's StableCrateId (the `Cs…_`
//! disambiguator), each path segment's namespace (`t` type / `v` value /
//! closures / shims), impl paths with their self type and trait, and the
//! generic args of an instance.
//!
//! Supported: every production rustc emits for ordinary code — crate roots,
//! nested paths, inherent / trait impls, `<T as Trait>` qualification, generic
//! args (types, lifetimes, consts), all basic types, refs, raw pointers,
//! arrays, slices, tuples, fn pointers (abi, unsafe, binders), `dyn` bounds
//! with assoc bindings, backrefs. Const values are decoded to their printed
//! form (ints, bool, char, str, refs, arrays, tuples, ADT consts).
//! NOT supported (decode error, counted by the caller, never guessed):
//! punycode identifiers (`u` prefix), pattern types (`W`), symbols past a
//! nesting depth of 256.

use std::fmt::Write;

/// A decoded path.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Path {
    /// `C` — a crate root: name + StableCrateId (the v0 disambiguator).
    Crate { name: String, id: u64 },
    /// `M` — `<SelfTy>` (an inherent impl), `parent` = the impl's parent.
    InherentImpl { parent: Box<Path>, self_ty: Box<Type> },
    /// `X` — `<SelfTy as Trait>` (a trait impl), `parent` = the impl's parent.
    TraitImpl { parent: Box<Path>, self_ty: Box<Type>, trait_: Box<Path> },
    /// `Y` — `<SelfTy as Trait>` (an item of the trait definition, used for a
    /// provided method instantiated for a Self type).
    TraitDef { self_ty: Box<Type>, trait_: Box<Path> },
    /// `N` — `parent::name` in namespace `ns` (`t`, `v`, or an uppercase /
    /// special one: `C` closure, `S` shim, `K` anon const, `c` ctor …).
    Nested { ns: char, parent: Box<Path>, name: String, dis: u64 },
    /// `I` — `path::<args>`.
    Generic { path: Box<Path>, args: Vec<GenericArg> },
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum GenericArg {
    Lifetime,
    Type(Type),
    Const(String),
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Type {
    Basic(&'static str),
    Path(Path),
    Array(Box<Type>, String),
    Slice(Box<Type>),
    Tuple(Vec<Type>),
    Ref { mutable: bool, ty: Box<Type> },
    Ptr { mutable: bool, ty: Box<Type> },
    Fn { unsafe_: bool, abi: Option<String>, inputs: Vec<Type>, output: Box<Type> },
    Dyn(Vec<(Path, Vec<(String, Type)>)>),
}

/// One decoded symbol.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Symbol {
    pub path: Path,
    /// the crate that instantiated a generic (`<instantiating-crate>`)
    pub instantiating_crate: Option<Path>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Error {
    NotV0,
    Invalid(&'static str, usize),
    Unsupported(&'static str),
}

impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Error::NotV0 => f.write_str("not a v0 symbol"),
            Error::Invalid(w, at) => write!(f, "invalid v0 symbol: {w} at {at}"),
            Error::Unsupported(w) => write!(f, "unsupported v0 production: {w}"),
        }
    }
}

const MAX_DEPTH: u32 = 256;

/// Decode a v0 symbol. Suffixes after the mangled name (`.llvm.123`,
/// `.cold`, `$got` …) are ignored.
pub fn decode(sym: &str) -> Result<Symbol, Error> {
    let inner = sym.strip_prefix("_R").ok_or(Error::NotV0)?;
    // the mangled name is [A-Za-z0-9_]; anything after is a vendor suffix
    let end = inner.find(|c: char| !(c.is_ascii_alphanumeric() || c == '_')).unwrap_or(inner.len());
    let inner = &inner.as_bytes()[..end];
    let mut p = Parser { s: inner, pos: 0, depth: 0 };
    // optional encoding version (decimal); only version 0 (absent) exists
    if p.peek().is_some_and(|c| c.is_ascii_digit()) {
        return Err(Error::Unsupported("encoding version"));
    }
    let path = p.path()?;
    let instantiating_crate = if p.pos < p.s.len() { Some(p.path()?) } else { None };
    if p.pos != p.s.len() {
        return Err(Error::Invalid("trailing bytes", p.pos));
    }
    Ok(Symbol { path, instantiating_crate })
}

struct Parser<'a> {
    s: &'a [u8],
    pos: usize,
    depth: u32,
}

impl<'a> Parser<'a> {
    fn peek(&self) -> Option<u8> {
        self.s.get(self.pos).copied()
    }
    fn next(&mut self) -> Result<u8, Error> {
        let c = self.peek().ok_or(Error::Invalid("unexpected end", self.pos))?;
        self.pos += 1;
        Ok(c)
    }
    fn eat(&mut self, c: u8) -> bool {
        if self.peek() == Some(c) {
            self.pos += 1;
            true
        } else {
            false
        }
    }
    fn enter(&mut self) -> Result<(), Error> {
        self.depth += 1;
        if self.depth > MAX_DEPTH { Err(Error::Unsupported("nesting depth")) } else { Ok(()) }
    }

    /// `<base-62-number>`: `_` = 0, else digits `_` = value + 1.
    fn integer_62(&mut self) -> Result<u64, Error> {
        if self.eat(b'_') {
            return Ok(0);
        }
        let mut x: u64 = 0;
        loop {
            let c = self.next()?;
            if c == b'_' {
                break;
            }
            let d = match c {
                b'0'..=b'9' => c - b'0',
                b'a'..=b'z' => 10 + (c - b'a'),
                b'A'..=b'Z' => 36 + (c - b'A'),
                _ => return Err(Error::Invalid("base62 digit", self.pos - 1)),
            };
            x = x
                .checked_mul(62)
                .and_then(|x| x.checked_add(d as u64))
                .ok_or(Error::Invalid("base62 overflow", self.pos))?;
        }
        x.checked_add(1).ok_or(Error::Invalid("base62 overflow", self.pos))
    }

    /// `[s <base-62-number>]` → 0 when absent, else value + 1.
    fn disambiguator(&mut self) -> Result<u64, Error> {
        if !self.eat(b's') {
            return Ok(0);
        }
        self.integer_62()?.checked_add(1).ok_or(Error::Invalid("disambiguator overflow", self.pos))
    }

    fn decimal(&mut self) -> Result<usize, Error> {
        let start = self.pos;
        if self.eat(b'0') {
            return Ok(0);
        }
        while self.peek().is_some_and(|c| c.is_ascii_digit()) {
            self.pos += 1;
        }
        if self.pos == start {
            return Err(Error::Invalid("decimal", start));
        }
        std::str::from_utf8(&self.s[start..self.pos])
            .unwrap()
            .parse()
            .map_err(|_| Error::Invalid("decimal overflow", start))
    }

    /// `<undisambiguated-identifier>`
    fn ident(&mut self) -> Result<String, Error> {
        if self.eat(b'u') {
            return Err(Error::Unsupported("punycode identifier"));
        }
        let len = self.decimal()?;
        self.eat(b'_');
        let end = self.pos.checked_add(len).filter(|&e| e <= self.s.len()).ok_or(Error::Invalid("identifier length", self.pos))?;
        let id = std::str::from_utf8(&self.s[self.pos..end]).map_err(|_| Error::Invalid("identifier utf8", self.pos))?;
        self.pos = end;
        Ok(id.to_string())
    }

    /// `B <base-62-number>`: re-parse at an earlier offset.
    fn backref<T>(&mut self, f: impl FnOnce(&mut Parser<'a>) -> Result<T, Error>) -> Result<T, Error> {
        let at = self.pos - 1;
        let target = self.integer_62()? as usize;
        if target >= at {
            return Err(Error::Invalid("forward backref", at));
        }
        let mut sub = Parser { s: self.s, pos: target, depth: self.depth };
        sub.enter()?;
        f(&mut sub)
    }

    fn path(&mut self) -> Result<Path, Error> {
        self.enter()?;
        let r = self.path_inner();
        self.depth -= 1;
        r
    }

    fn path_inner(&mut self) -> Result<Path, Error> {
        let at = self.pos;
        Ok(match self.next()? {
            b'C' => {
                let id = self.disambiguator()?;
                let name = self.ident()?;
                Path::Crate { name, id }
            }
            b'M' => {
                self.disambiguator()?;
                let parent = Box::new(self.path()?);
                let self_ty = Box::new(self.ty()?);
                Path::InherentImpl { parent, self_ty }
            }
            b'X' => {
                self.disambiguator()?;
                let parent = Box::new(self.path()?);
                let self_ty = Box::new(self.ty()?);
                let trait_ = Box::new(self.path()?);
                Path::TraitImpl { parent, self_ty, trait_ }
            }
            b'Y' => {
                let self_ty = Box::new(self.ty()?);
                let trait_ = Box::new(self.path()?);
                Path::TraitDef { self_ty, trait_ }
            }
            b'N' => {
                let ns = self.next()?;
                if !ns.is_ascii_alphabetic() {
                    return Err(Error::Invalid("namespace", at + 1));
                }
                let parent = Box::new(self.path()?);
                let dis = self.disambiguator()?;
                let name = self.ident()?;
                Path::Nested { ns: ns as char, parent, name, dis }
            }
            b'I' => {
                let path = Box::new(self.path()?);
                let mut args = Vec::new();
                while !self.eat(b'E') {
                    args.push(self.generic_arg()?);
                }
                Path::Generic { path, args }
            }
            b'B' => self.backref(|p| p.path())?,
            _ => return Err(Error::Invalid("path tag", at)),
        })
    }

    fn generic_arg(&mut self) -> Result<GenericArg, Error> {
        if self.eat(b'L') {
            self.integer_62()?;
            return Ok(GenericArg::Lifetime);
        }
        if self.eat(b'K') {
            return Ok(GenericArg::Const(self.konst()?));
        }
        Ok(GenericArg::Type(self.ty()?))
    }

    fn binder(&mut self) -> Result<(), Error> {
        if self.eat(b'G') {
            self.integer_62()?;
        }
        Ok(())
    }

    fn ty(&mut self) -> Result<Type, Error> {
        self.enter()?;
        let r = self.ty_inner();
        self.depth -= 1;
        r
    }

    fn ty_inner(&mut self) -> Result<Type, Error> {
        let at = self.pos;
        let c = self.next()?;
        if let Some(b) = basic(c) {
            return Ok(Type::Basic(b));
        }
        Ok(match c {
            b'A' => {
                let t = self.ty()?;
                let n = self.konst()?;
                Type::Array(Box::new(t), n)
            }
            b'S' => Type::Slice(Box::new(self.ty()?)),
            b'T' => {
                let mut v = Vec::new();
                while !self.eat(b'E') {
                    v.push(self.ty()?);
                }
                Type::Tuple(v)
            }
            b'R' | b'Q' => {
                if self.eat(b'L') {
                    self.integer_62()?;
                }
                Type::Ref { mutable: c == b'Q', ty: Box::new(self.ty()?) }
            }
            b'P' | b'O' => Type::Ptr { mutable: c == b'O', ty: Box::new(self.ty()?) },
            b'F' => {
                self.binder()?;
                let unsafe_ = self.eat(b'U');
                let abi = if self.eat(b'K') {
                    if self.eat(b'C') { Some("C".to_string()) } else { Some(self.ident()?.replace('_', "-")) }
                } else {
                    None
                };
                let mut inputs = Vec::new();
                while !self.eat(b'E') {
                    inputs.push(self.ty()?);
                }
                let output = Box::new(self.ty()?);
                Type::Fn { unsafe_, abi, inputs, output }
            }
            b'D' => {
                self.binder()?;
                let mut bounds = Vec::new();
                while !self.eat(b'E') {
                    let path = self.path()?;
                    let mut assoc = Vec::new();
                    while self.eat(b'p') {
                        let name = self.ident()?;
                        let t = self.ty()?;
                        assoc.push((name, t));
                    }
                    bounds.push((path, assoc));
                }
                // the object lifetime
                if !self.eat(b'L') {
                    return Err(Error::Invalid("dyn lifetime", self.pos));
                }
                self.integer_62()?;
                Type::Dyn(bounds)
            }
            b'W' => return Err(Error::Unsupported("pattern type")),
            b'B' => self.backref(|p| p.ty())?,
            b'C' | b'M' | b'X' | b'Y' | b'N' | b'I' => {
                self.pos = at;
                Type::Path(self.path()?)
            }
            _ => return Err(Error::Invalid("type tag", at)),
        })
    }

    /// A const generic arg / array length, decoded to its printed form.
    fn konst(&mut self) -> Result<String, Error> {
        self.enter()?;
        let r = self.konst_inner();
        self.depth -= 1;
        r
    }

    fn konst_inner(&mut self) -> Result<String, Error> {
        let at = self.pos;
        let c = self.next()?;
        Ok(match c {
            b'p' => "_".to_string(),
            b'B' => self.backref(|p| p.konst())?,
            b'e' => {
                // str: hex bytes then `_`
                let hex = self.hex()?;
                let bytes: Vec<u8> = (0..hex.len() / 2)
                    .filter_map(|i| u8::from_str_radix(&hex[2 * i..2 * i + 2], 16).ok())
                    .collect();
                format!("{:?}", String::from_utf8_lossy(&bytes))
            }
            b'R' | b'Q' => {
                let inner = self.konst()?;
                if c == b'Q' { format!("&mut {inner}") } else { format!("&{inner}") }
            }
            b'A' => {
                let mut v = Vec::new();
                while !self.eat(b'E') {
                    v.push(self.konst()?);
                }
                format!("[{}]", v.join(", "))
            }
            b'T' => {
                let mut v = Vec::new();
                while !self.eat(b'E') {
                    v.push(self.konst()?);
                }
                if v.len() == 1 { format!("({},)", v[0]) } else { format!("({})", v.join(", ")) }
            }
            b'V' => {
                let path = self.path()?;
                let name = print_path(&path);
                match self.next()? {
                    b'U' => name,
                    b'T' => {
                        let mut v = Vec::new();
                        while !self.eat(b'E') {
                            v.push(self.konst()?);
                        }
                        format!("{name}({})", v.join(", "))
                    }
                    b'S' => {
                        let mut v = Vec::new();
                        while !self.eat(b'E') {
                            self.disambiguator()?;
                            let f = self.ident()?;
                            v.push(format!("{f}: {}", self.konst()?));
                        }
                        format!("{name} {{ {} }}", v.join(", "))
                    }
                    _ => return Err(Error::Invalid("const variant fields", self.pos - 1)),
                }
            }
            _ => {
                let Some(t) = basic(c) else { return Err(Error::Invalid("const type", at)) };
                match t {
                    "bool" => {
                        let h = self.hex()?;
                        if h == "0" { "false".into() } else { "true".into() }
                    }
                    "char" => {
                        let h = self.hex()?;
                        let v = u32::from_str_radix(&h, 16).map_err(|_| Error::Invalid("char const", at))?;
                        char::from_u32(v).map(|ch| format!("{ch:?}")).unwrap_or_else(|| format!("'\\u{{{h}}}'"))
                    }
                    "_" => "_".into(),
                    _ => {
                        let neg = self.eat(b'n');
                        let h = self.hex()?;
                        let v = u128::from_str_radix(if h.is_empty() { "0" } else { &h }, 16)
                            .map_err(|_| Error::Invalid("int const", at))?;
                        if neg { format!("-{v}") } else { v.to_string() }
                    }
                }
            }
        })
    }

    fn hex(&mut self) -> Result<String, Error> {
        let start = self.pos;
        while self.peek().is_some_and(|c| c.is_ascii_hexdigit()) {
            self.pos += 1;
        }
        let h = std::str::from_utf8(&self.s[start..self.pos]).unwrap().to_string();
        if !self.eat(b'_') {
            return Err(Error::Invalid("hex const terminator", self.pos));
        }
        Ok(h)
    }
}

fn basic(c: u8) -> Option<&'static str> {
    Some(match c {
        b'a' => "i8",
        b'b' => "bool",
        b'c' => "char",
        b'd' => "f64",
        b'e' => "str",
        b'f' => "f32",
        b'h' => "u8",
        b'i' => "isize",
        b'j' => "usize",
        b'l' => "i32",
        b'm' => "u32",
        b'n' => "i128",
        b'o' => "u128",
        b's' => "i16",
        b't' => "u16",
        b'u' => "()",
        b'v' => "...",
        b'x' => "i64",
        b'y' => "u64",
        b'z' => "!",
        b'p' => "_",
        _ => return None,
    })
}

// ------------------------------------------------------------------ printing

/// The hash-free demangled form (`rustc-demangle`'s `{:#}` shape):
/// `a::ident::<u32>`, `<a::Point>::new`, `<b::Dog as a::Greet>::greet`,
/// `a::foo::{closure#0}`. Used as the mono-instance key.
pub fn print_path(p: &Path) -> String {
    let mut s = String::new();
    write_path(&mut s, p, true);
    s
}

fn write_path(s: &mut String, p: &Path, turbofish: bool) {
    match p {
        Path::Crate { name, .. } => s.push_str(name),
        Path::InherentImpl { self_ty, .. } => {
            s.push('<');
            write_type(s, self_ty);
            s.push('>');
        }
        Path::TraitImpl { self_ty, trait_, .. } | Path::TraitDef { self_ty, trait_ } => {
            s.push('<');
            write_type(s, self_ty);
            s.push_str(" as ");
            write_path(s, trait_, false);
            s.push('>');
        }
        Path::Nested { ns, parent, name, dis } => {
            write_path(s, parent, turbofish);
            // rustc-demangle's rule: special (uppercase) namespaces print as
            // `{closure#n}` / `{shim:vtable#n}` / `{K#n}`; internal (lowercase)
            // ones print just the name, nothing when it is empty.
            if ns.is_ascii_uppercase() {
                s.push_str("::{");
                match ns {
                    'C' => s.push_str("closure"),
                    'S' => s.push_str("shim"),
                    c => s.push(*c),
                }
                if !name.is_empty() {
                    s.push(':');
                    s.push_str(name);
                }
                let _ = write!(s, "#{dis}}}");
            } else if !name.is_empty() {
                s.push_str("::");
                s.push_str(name);
            }
        }
        Path::Generic { path, args } => {
            write_path(s, path, turbofish);
            let args: Vec<&GenericArg> = args.iter().filter(|a| !matches!(a, GenericArg::Lifetime)).collect();
            if !args.is_empty() {
                if turbofish {
                    s.push_str("::");
                }
                s.push('<');
                for (i, a) in args.iter().enumerate() {
                    if i > 0 {
                        s.push_str(", ");
                    }
                    match a {
                        GenericArg::Type(t) => write_type(s, t),
                        GenericArg::Const(c) => s.push_str(c),
                        GenericArg::Lifetime => {}
                    }
                }
                s.push('>');
            }
        }
    }
}

/// A type, full paths (`alloc::vec::Vec<u8>`).
pub fn print_type(t: &Type) -> String {
    let mut s = String::new();
    write_type(&mut s, t);
    s
}

fn write_type(s: &mut String, t: &Type) {
    write_type_with(s, t, &|s, p| write_path(s, p, false));
}

/// A type the way rustc's force-trimmed `Ty` Display prints it — the form the
/// driver writes into impl monikers (`Vec<Point>`, `&str`, `[u8; 4]`).
pub fn print_type_short(t: &Type) -> String {
    let mut s = String::new();
    write_type_with(&mut s, t, &|s, p| write_path_short(s, p));
    s
}

fn write_path_short(s: &mut String, p: &Path) {
    // last named segment + its generic args, trimmed recursively
    match p {
        Path::Generic { path, args } => {
            write_path_short(s, path);
            let args: Vec<&GenericArg> = args.iter().filter(|a| !matches!(a, GenericArg::Lifetime)).collect();
            if !args.is_empty() {
                s.push('<');
                for (i, a) in args.iter().enumerate() {
                    if i > 0 {
                        s.push_str(", ");
                    }
                    match a {
                        GenericArg::Type(t) => write_type_with(s, t, &|s, p| write_path_short(s, p)),
                        GenericArg::Const(c) => s.push_str(c),
                        GenericArg::Lifetime => {}
                    }
                }
                s.push('>');
            }
        }
        Path::Nested { name, .. } => s.push_str(name),
        Path::Crate { name, .. } => s.push_str(name),
        other => write_path(s, other, false),
    }
}

fn write_type_with(s: &mut String, t: &Type, wp: &dyn Fn(&mut String, &Path)) {
    match t {
        Type::Basic(b) => s.push_str(b),
        Type::Path(p) => wp(s, p),
        Type::Array(t, n) => {
            s.push('[');
            write_type_with(s, t, wp);
            let _ = write!(s, "; {n}]");
        }
        Type::Slice(t) => {
            s.push('[');
            write_type_with(s, t, wp);
            s.push(']');
        }
        Type::Tuple(v) => {
            s.push('(');
            for (i, t) in v.iter().enumerate() {
                if i > 0 {
                    s.push_str(", ");
                }
                write_type_with(s, t, wp);
            }
            if v.len() == 1 {
                s.push(',');
            }
            s.push(')');
        }
        Type::Ref { mutable, ty } => {
            s.push_str(if *mutable { "&mut " } else { "&" });
            write_type_with(s, ty, wp);
        }
        Type::Ptr { mutable, ty } => {
            s.push_str(if *mutable { "*mut " } else { "*const " });
            write_type_with(s, ty, wp);
        }
        Type::Fn { unsafe_, abi, inputs, output } => {
            if *unsafe_ {
                s.push_str("unsafe ");
            }
            if let Some(a) = abi {
                let _ = write!(s, "extern \"{a}\" ");
            }
            s.push_str("fn(");
            for (i, t) in inputs.iter().enumerate() {
                if i > 0 {
                    s.push_str(", ");
                }
                write_type_with(s, t, wp);
            }
            s.push(')');
            if !matches!(**output, Type::Basic("()")) {
                s.push_str(" -> ");
                write_type_with(s, output, wp);
            }
        }
        Type::Dyn(bounds) => {
            s.push_str("dyn ");
            for (i, (p, assoc)) in bounds.iter().enumerate() {
                if i > 0 {
                    s.push_str(" + ");
                }
                if assoc.is_empty() {
                    wp(s, p);
                } else {
                    // `Trait<Assoc = T>`: splice the bindings into the arg list
                    let mut inner = String::new();
                    wp(&mut inner, p);
                    let binds: Vec<String> = assoc
                        .iter()
                        .map(|(n, t)| {
                            let mut x = format!("{n} = ");
                            write_type_with(&mut x, t, wp);
                            x
                        })
                        .collect();
                    if let Some(stripped) = inner.strip_suffix('>') {
                        let _ = write!(s, "{stripped}, {}>", binds.join(", "));
                    } else {
                        let _ = write!(s, "{inner}<{}>", binds.join(", "));
                    }
                }
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn crate_root_disambiguator_is_the_stable_crate_id() {
        // from a real 1.98 binary: CU `std.6c98fd8553dbae28-cgu.0`
        let s = decode("_RNvNtCs9k3SxhrAWiO_3std2rt10lang_start").unwrap();
        let mut p = &s.path;
        while let Path::Nested { parent, .. } = p {
            p = parent;
        }
        assert_eq!(p, &Path::Crate { name: "std".into(), id: 0x6c98fd8553dbae28 });
    }

    #[test]
    fn generic_instance_with_instantiating_crate() {
        let s = decode("_RINvCsbR8JNGoZHix_1a5identReECsfPIvSHfysBz_1b").unwrap();
        assert_eq!(print_path(&s.path), "a::ident::<&str>");
        assert!(matches!(s.instantiating_crate, Some(Path::Crate { ref name, .. }) if name == "b"));
        let s = decode("_RINvCsbR8JNGoZHix_1a5identmECsfPIvSHfysBz_1b").unwrap();
        assert_eq!(print_path(&s.path), "a::ident::<u32>");
    }

    #[test]
    fn trait_impl_and_backrefs() {
        let s = decode("_RNvXs1g_NtCsgxBkk5gSRhY_4core3fmtReNtB6_5Debug3fmtCsfPIvSHfysBz_1b").unwrap();
        assert_eq!(print_path(&s.path), "<&str as core::fmt::Debug>::fmt");
        let s = decode("_RNvMs4_NtCs6i54tJFfzR_5alloc7raw_vecINtB5_6RawVecINtNtB7_5boxed3BoxDINtNtNtCsgxBkk5gSRhY_4core3ops8function5FnMutuEp6OutputINtNtB1b_6result6ResultuNtNtNtB1b_2io5error5ErrorENtNtB1b_6marker4SendNtB2N_4SyncEL_EE8grow_oneCs9k3SxhrAWiO_3std").unwrap();
        assert_eq!(
            print_path(&s.path),
            "<alloc::raw_vec::RawVec<alloc::boxed::Box<dyn core::ops::function::FnMut<(), Output = core::result::Result<(), core::io::error::Error>> + core::marker::Send + core::marker::Sync>>>::grow_one"
        );
    }

    #[test]
    fn closures_and_shims_print_like_rustc_demangle() {
        for sym in [
            "_RNCINvMs0_NtNtCs9k3SxhrAWiO_3std4sync4onceNtB8_4Once15call_once_forceNCINvMNtBa_9once_lockINtB1b_8OnceLockINtNtBa_14reentrant_lock13ReentrantLockINtNtCsgxBkk5gSRhY_4core4cell7RefCellINtNtNtNtBc_2io8buffered10linewriter10LineWriterNtNtB31_5stdio9StdoutRawEEEE10initializeNCINvB1a_11get_or_initNCNvB3I_6stdout0E0zE0E0Bc_",
            "_RNvYINtNvNtCs9k3SxhrAWiO_3std9panicking11begin_panic7PayloadReENtNtCsgxBkk5gSRhY_4core5panic12PanicPayload6as_strB9_",
        ] {
            let ours = print_path(&decode(sym).unwrap().path);
            let theirs = format!("{:#}", rustc_demangle::demangle(sym));
            assert_eq!(ours, theirs, "{sym}");
        }
    }
}