| use core::convert::TryFrom; |
| use core::{char, fmt, iter, mem, str}; |
| |
| #[allow(unused_macros)] |
| macro_rules! write { |
| ($($ignored:tt)*) => { |
| compile_error!( |
| "use `self.print(value)` or `fmt::Trait::fmt(&value, self.out)`, \ |
| instead of `write!(self.out, \"{...}\", value)`" |
| ) |
| }; |
| } |
| |
| // Maximum recursion depth when parsing symbols before we just bail out saying |
| // "this symbol is invalid" |
| const MAX_DEPTH: u32 = 500; |
| |
| /// Representation of a demangled symbol name. |
| pub struct Demangle<'a> { |
| inner: &'a str, |
| } |
| |
| #[derive(PartialEq, Eq, Debug)] |
| pub enum ParseError { |
| /// Symbol doesn't match the expected `v0` grammar. |
| Invalid, |
| |
| /// Parsing the symbol crossed the recursion limit (see `MAX_DEPTH`). |
| RecursedTooDeep, |
| } |
| |
| /// De-mangles a Rust symbol into a more readable version |
| /// |
| /// This function will take a **mangled** symbol and return a value. When printed, |
| /// the de-mangled version will be written. If the symbol does not look like |
| /// a mangled symbol, the original value will be written instead. |
| pub fn demangle(s: &str) -> Result<(Demangle, &str), ParseError> { |
| // First validate the symbol. If it doesn't look like anything we're |
| // expecting, we just print it literally. Note that we must handle non-Rust |
| // symbols because we could have any function in the backtrace. |
| let inner; |
| if s.len() > 2 && s.starts_with("_R") { |
| inner = &s[2..]; |
| } else if s.len() > 1 && s.starts_with('R') { |
| // On Windows, dbghelp strips leading underscores, so we accept "R..." |
| // form too. |
| inner = &s[1..]; |
| } else if s.len() > 3 && s.starts_with("__R") { |
| // On OSX, symbols are prefixed with an extra _ |
| inner = &s[3..]; |
| } else { |
| return Err(ParseError::Invalid); |
| } |
| |
| // Paths always start with uppercase characters. |
| match inner.as_bytes()[0] { |
| b'A'..=b'Z' => {} |
| _ => return Err(ParseError::Invalid), |
| } |
| |
| // only work with ascii text |
| if inner.bytes().any(|c| c & 0x80 != 0) { |
| return Err(ParseError::Invalid); |
| } |
| |
| // Verify that the symbol is indeed a valid path. |
| let try_parse_path = |parser| { |
| let mut dummy_printer = Printer { |
| parser: Ok(parser), |
| out: None, |
| bound_lifetime_depth: 0, |
| }; |
| dummy_printer |
| .print_path(false) |
| .expect("`fmt::Error`s should be impossible without a `fmt::Formatter`"); |
| dummy_printer.parser |
| }; |
| let mut parser = Parser { |
| sym: inner, |
| next: 0, |
| depth: 0, |
| }; |
| parser = try_parse_path(parser)?; |
| |
| // Instantiating crate (paths always start with uppercase characters). |
| if let Some(&(b'A'..=b'Z')) = parser.sym.as_bytes().get(parser.next) { |
| parser = try_parse_path(parser)?; |
| } |
| |
| Ok((Demangle { inner }, &parser.sym[parser.next..])) |
| } |
| |
| impl<'s> fmt::Display for Demangle<'s> { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| let mut printer = Printer { |
| parser: Ok(Parser { |
| sym: self.inner, |
| next: 0, |
| depth: 0, |
| }), |
| out: Some(f), |
| bound_lifetime_depth: 0, |
| }; |
| printer.print_path(true) |
| } |
| } |
| |
| struct Ident<'s> { |
| /// ASCII part of the identifier. |
| ascii: &'s str, |
| /// Punycode insertion codes for Unicode codepoints, if any. |
| punycode: &'s str, |
| } |
| |
| const SMALL_PUNYCODE_LEN: usize = 128; |
| |
| impl<'s> Ident<'s> { |
| /// Attempt to decode punycode on the stack (allocation-free), |
| /// and pass the char slice to the closure, if successful. |
| /// This supports up to `SMALL_PUNYCODE_LEN` characters. |
| fn try_small_punycode_decode<F: FnOnce(&[char]) -> R, R>(&self, f: F) -> Option<R> { |
| let mut out = ['\0'; SMALL_PUNYCODE_LEN]; |
| let mut out_len = 0; |
| let r = self.punycode_decode(|i, c| { |
| // Check there's space left for another character. |
| out.get(out_len).ok_or(())?; |
| |
| // Move the characters after the insert position. |
| let mut j = out_len; |
| out_len += 1; |
| |
| while j > i { |
| out[j] = out[j - 1]; |
| j -= 1; |
| } |
| |
| // Insert the new character. |
| out[i] = c; |
| |
| Ok(()) |
| }); |
| if r.is_ok() { |
| Some(f(&out[..out_len])) |
| } else { |
| None |
| } |
| } |
| |
| /// Decode punycode as insertion positions and characters |
| /// and pass them to the closure, which can return `Err(())` |
| /// to stop the decoding process. |
| fn punycode_decode<F: FnMut(usize, char) -> Result<(), ()>>( |
| &self, |
| mut insert: F, |
| ) -> Result<(), ()> { |
| let mut punycode_bytes = self.punycode.bytes().peekable(); |
| if punycode_bytes.peek().is_none() { |
| return Err(()); |
| } |
| |
| let mut len = 0; |
| |
| // Populate initial output from ASCII fragment. |
| for c in self.ascii.chars() { |
| insert(len, c)?; |
| len += 1; |
| } |
| |
| // Punycode parameters and initial state. |
| let base = 36; |
| let t_min = 1; |
| let t_max = 26; |
| let skew = 38; |
| let mut damp = 700; |
| let mut bias = 72; |
| let mut i: usize = 0; |
| let mut n: usize = 0x80; |
| |
| loop { |
| // Read one delta value. |
| let mut delta: usize = 0; |
| let mut w = 1; |
| let mut k: usize = 0; |
| loop { |
| use core::cmp::{max, min}; |
| |
| k += base; |
| let t = min(max(k.saturating_sub(bias), t_min), t_max); |
| |
| let d = match punycode_bytes.next() { |
| Some(d @ b'a'..=b'z') => d - b'a', |
| Some(d @ b'0'..=b'9') => 26 + (d - b'0'), |
| _ => return Err(()), |
| }; |
| let d = d as usize; |
| delta = delta.checked_add(d.checked_mul(w).ok_or(())?).ok_or(())?; |
| if d < t { |
| break; |
| } |
| w = w.checked_mul(base - t).ok_or(())?; |
| } |
| |
| // Compute the new insert position and character. |
| len += 1; |
| i = i.checked_add(delta).ok_or(())?; |
| n = n.checked_add(i / len).ok_or(())?; |
| i %= len; |
| |
| let n_u32 = n as u32; |
| let c = if n_u32 as usize == n { |
| char::from_u32(n_u32).ok_or(())? |
| } else { |
| return Err(()); |
| }; |
| |
| // Insert the new character and increment the insert position. |
| insert(i, c)?; |
| i += 1; |
| |
| // If there are no more deltas, decoding is complete. |
| if punycode_bytes.peek().is_none() { |
| return Ok(()); |
| } |
| |
| // Perform bias adaptation. |
| delta /= damp; |
| damp = 2; |
| |
| delta += delta / len; |
| let mut 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); |
| } |
| } |
| } |
| |
| impl<'s> fmt::Display for Ident<'s> { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| self.try_small_punycode_decode(|chars| { |
| for &c in chars { |
| c.fmt(f)?; |
| } |
| Ok(()) |
| }) |
| .unwrap_or_else(|| { |
| if !self.punycode.is_empty() { |
| f.write_str("punycode{")?; |
| |
| // Reconstruct a standard Punycode encoding, |
| // by using `-` as the separator. |
| if !self.ascii.is_empty() { |
| f.write_str(self.ascii)?; |
| f.write_str("-")?; |
| } |
| f.write_str(self.punycode)?; |
| |
| f.write_str("}") |
| } else { |
| f.write_str(self.ascii) |
| } |
| }) |
| } |
| } |
| |
| /// Sequence of lowercase hexadecimal nibbles (`0-9a-f`), used by leaf consts. |
| struct HexNibbles<'s> { |
| nibbles: &'s str, |
| } |
| |
| impl<'s> HexNibbles<'s> { |
| /// Decode an integer value (with the "most significant nibble" first), |
| /// returning `None` if it can't fit in an `u64`. |
| // FIXME(eddyb) should this "just" use `u128` instead? |
| fn try_parse_uint(&self) -> Option<u64> { |
| let nibbles = self.nibbles.trim_start_matches("0"); |
| |
| if nibbles.len() > 16 { |
| return None; |
| } |
| |
| let mut v = 0; |
| for nibble in nibbles.chars() { |
| v = (v << 4) | (nibble.to_digit(16).unwrap() as u64); |
| } |
| Some(v) |
| } |
| |
| /// Decode a UTF-8 byte sequence (with each byte using a pair of nibbles) |
| /// into individual `char`s, returning `None` for invalid UTF-8. |
| fn try_parse_str_chars(&self) -> Option<impl Iterator<Item = char> + 's> { |
| if self.nibbles.len() % 2 != 0 { |
| return None; |
| } |
| |
| // FIXME(eddyb) use `array_chunks` instead, when that becomes stable. |
| let mut bytes = self |
| .nibbles |
| .as_bytes() |
| .chunks_exact(2) |
| .map(|slice| match slice { |
| [a, b] => [a, b], |
| _ => unreachable!(), |
| }) |
| .map(|[&hi, &lo]| { |
| let half = |nibble: u8| (nibble as char).to_digit(16).unwrap() as u8; |
| (half(hi) << 4) | half(lo) |
| }); |
| |
| let chars = iter::from_fn(move || { |
| // As long as there are any bytes left, there's at least one more |
| // UTF-8-encoded `char` to decode (or the possibility of error). |
| bytes.next().map(|first_byte| -> Result<char, ()> { |
| // FIXME(eddyb) this `enum` and `fn` should be somewhere in `core`. |
| enum Utf8FirstByteError { |
| ContinuationByte, |
| TooLong, |
| } |
| fn utf8_len_from_first_byte(byte: u8) -> Result<usize, Utf8FirstByteError> { |
| match byte { |
| 0x00..=0x7f => Ok(1), |
| 0x80..=0xbf => Err(Utf8FirstByteError::ContinuationByte), |
| 0xc0..=0xdf => Ok(2), |
| 0xe0..=0xef => Ok(3), |
| 0xf0..=0xf7 => Ok(4), |
| 0xf8..=0xff => Err(Utf8FirstByteError::TooLong), |
| } |
| } |
| |
| // Collect the appropriate amount of bytes (up to 4), according |
| // to the UTF-8 length implied by the first byte. |
| let utf8_len = utf8_len_from_first_byte(first_byte).map_err(|_| ())?; |
| let utf8 = &mut [first_byte, 0, 0, 0][..utf8_len]; |
| for i in 1..utf8_len { |
| utf8[i] = bytes.next().ok_or(())?; |
| } |
| |
| // Fully validate the UTF-8 sequence. |
| let s = str::from_utf8(utf8).map_err(|_| ())?; |
| |
| // Since we included exactly one UTF-8 sequence, and validation |
| // succeeded, `str::chars` should return exactly one `char`. |
| let mut chars = s.chars(); |
| match (chars.next(), chars.next()) { |
| (Some(c), None) => Ok(c), |
| _ => unreachable!( |
| "str::from_utf8({:?}) = {:?} was expected to have 1 char, \ |
| but {} chars were found", |
| utf8, |
| s, |
| s.chars().count() |
| ), |
| } |
| }) |
| }); |
| |
| // HACK(eddyb) doing a separate validation iteration like this might be |
| // wasteful, but it's easier to avoid starting to print a string literal |
| // in the first place, than to abort it mid-string. |
| if chars.clone().any(|r| r.is_err()) { |
| None |
| } else { |
| Some(chars.map(Result::unwrap)) |
| } |
| } |
| } |
| |
| fn basic_type(tag: u8) -> Option<&'static str> { |
| Some(match tag { |
| b'b' => "bool", |
| b'c' => "char", |
| b'e' => "str", |
| b'u' => "()", |
| b'a' => "i8", |
| b's' => "i16", |
| b'l' => "i32", |
| b'x' => "i64", |
| b'n' => "i128", |
| b'i' => "isize", |
| b'h' => "u8", |
| b't' => "u16", |
| b'm' => "u32", |
| b'y' => "u64", |
| b'o' => "u128", |
| b'j' => "usize", |
| b'f' => "f32", |
| b'd' => "f64", |
| b'z' => "!", |
| b'p' => "_", |
| b'v' => "...", |
| |
| _ => return None, |
| }) |
| } |
| |
| struct Parser<'s> { |
| sym: &'s str, |
| next: usize, |
| depth: u32, |
| } |
| |
| impl<'s> Parser<'s> { |
| fn push_depth(&mut self) -> Result<(), ParseError> { |
| self.depth += 1; |
| if self.depth > MAX_DEPTH { |
| Err(ParseError::RecursedTooDeep) |
| } else { |
| Ok(()) |
| } |
| } |
| |
| fn pop_depth(&mut self) { |
| self.depth -= 1; |
| } |
| |
| fn peek(&self) -> Option<u8> { |
| self.sym.as_bytes().get(self.next).cloned() |
| } |
| |
| fn eat(&mut self, b: u8) -> bool { |
| if self.peek() == Some(b) { |
| self.next += 1; |
| true |
| } else { |
| false |
| } |
| } |
| |
| fn next(&mut self) -> Result<u8, ParseError> { |
| let b = self.peek().ok_or(ParseError::Invalid)?; |
| self.next += 1; |
| Ok(b) |
| } |
| |
| fn hex_nibbles(&mut self) -> Result<HexNibbles<'s>, ParseError> { |
| let start = self.next; |
| loop { |
| match self.next()? { |
| b'0'..=b'9' | b'a'..=b'f' => {} |
| b'_' => break, |
| _ => return Err(ParseError::Invalid), |
| } |
| } |
| Ok(HexNibbles { |
| nibbles: &self.sym[start..self.next - 1], |
| }) |
| } |
| |
| fn digit_10(&mut self) -> Result<u8, ParseError> { |
| let d = match self.peek() { |
| Some(d @ b'0'..=b'9') => d - b'0', |
| _ => return Err(ParseError::Invalid), |
| }; |
| self.next += 1; |
| Ok(d) |
| } |
| |
| fn digit_62(&mut self) -> Result<u8, ParseError> { |
| let d = match self.peek() { |
| Some(d @ b'0'..=b'9') => d - b'0', |
| Some(d @ b'a'..=b'z') => 10 + (d - b'a'), |
| Some(d @ b'A'..=b'Z') => 10 + 26 + (d - b'A'), |
| _ => return Err(ParseError::Invalid), |
| }; |
| self.next += 1; |
| Ok(d) |
| } |
| |
| fn integer_62(&mut self) -> Result<u64, ParseError> { |
| if self.eat(b'_') { |
| return Ok(0); |
| } |
| |
| let mut x: u64 = 0; |
| while !self.eat(b'_') { |
| let d = self.digit_62()? as u64; |
| x = x.checked_mul(62).ok_or(ParseError::Invalid)?; |
| x = x.checked_add(d).ok_or(ParseError::Invalid)?; |
| } |
| x.checked_add(1).ok_or(ParseError::Invalid) |
| } |
| |
| fn opt_integer_62(&mut self, tag: u8) -> Result<u64, ParseError> { |
| if !self.eat(tag) { |
| return Ok(0); |
| } |
| self.integer_62()?.checked_add(1).ok_or(ParseError::Invalid) |
| } |
| |
| fn disambiguator(&mut self) -> Result<u64, ParseError> { |
| self.opt_integer_62(b's') |
| } |
| |
| fn namespace(&mut self) -> Result<Option<char>, ParseError> { |
| match self.next()? { |
| // Special namespaces, like closures and shims. |
| ns @ b'A'..=b'Z' => Ok(Some(ns as char)), |
| |
| // Implementation-specific/unspecified namespaces. |
| b'a'..=b'z' => Ok(None), |
| |
| _ => Err(ParseError::Invalid), |
| } |
| } |
| |
| fn backref(&mut self) -> Result<Parser<'s>, ParseError> { |
| let s_start = self.next - 1; |
| let i = self.integer_62()?; |
| if i >= s_start as u64 { |
| return Err(ParseError::Invalid); |
| } |
| let mut new_parser = Parser { |
| sym: self.sym, |
| next: i as usize, |
| depth: self.depth, |
| }; |
| new_parser.push_depth()?; |
| Ok(new_parser) |
| } |
| |
| fn ident(&mut self) -> Result<Ident<'s>, ParseError> { |
| let is_punycode = self.eat(b'u'); |
| let mut len = self.digit_10()? as usize; |
| if len != 0 { |
| while let Ok(d) = self.digit_10() { |
| len = len.checked_mul(10).ok_or(ParseError::Invalid)?; |
| len = len.checked_add(d as usize).ok_or(ParseError::Invalid)?; |
| } |
| } |
| |
| // Skip past the optional `_` separator. |
| self.eat(b'_'); |
| |
| let start = self.next; |
| self.next = self.next.checked_add(len).ok_or(ParseError::Invalid)?; |
| if self.next > self.sym.len() { |
| return Err(ParseError::Invalid); |
| } |
| |
| let ident = &self.sym[start..self.next]; |
| |
| if is_punycode { |
| let ident = match ident.bytes().rposition(|b| b == b'_') { |
| Some(i) => Ident { |
| ascii: &ident[..i], |
| punycode: &ident[i + 1..], |
| }, |
| None => Ident { |
| ascii: "", |
| punycode: ident, |
| }, |
| }; |
| if ident.punycode.is_empty() { |
| return Err(ParseError::Invalid); |
| } |
| Ok(ident) |
| } else { |
| Ok(Ident { |
| ascii: ident, |
| punycode: "", |
| }) |
| } |
| } |
| } |
| |
| struct Printer<'a, 'b: 'a, 's> { |
| /// The input parser to demangle from, or `Err` if any (parse) error was |
| /// encountered (in order to disallow further likely-incorrect demangling). |
| /// |
| /// See also the documentation on the `invalid!` and `parse!` macros below. |
| parser: Result<Parser<'s>, ParseError>, |
| |
| /// The output formatter to demangle to, or `None` while skipping printing. |
| out: Option<&'a mut fmt::Formatter<'b>>, |
| |
| /// Cumulative number of lifetimes bound by `for<...>` binders ('G'), |
| /// anywhere "around" the current entity (e.g. type) being demangled. |
| /// This value is not tracked while skipping printing, as it'd be unused. |
| /// |
| /// See also the documentation on the `Printer::in_binder` method. |
| bound_lifetime_depth: u32, |
| } |
| |
| impl ParseError { |
| /// Snippet to print when the error is initially encountered. |
| fn message(&self) -> &str { |
| match self { |
| ParseError::Invalid => "{invalid syntax}", |
| ParseError::RecursedTooDeep => "{recursion limit reached}", |
| } |
| } |
| } |
| |
| /// Mark the parser as errored (with `ParseError::Invalid`), print the |
| /// appropriate message (see `ParseError::message`) and return early. |
| macro_rules! invalid { |
| ($printer:ident) => {{ |
| let err = ParseError::Invalid; |
| $printer.print(err.message())?; |
| $printer.parser = Err(err); |
| return Ok(()); |
| }}; |
| } |
| |
| /// Call a parser method (if the parser hasn't errored yet), |
| /// and mark the parser as errored if it returns `Err`. |
| /// |
| /// If the parser errored, before or now, this returns early, |
| /// from the current function, after printing either: |
| /// * for a new error, the appropriate message (see `ParseError::message`) |
| /// * for an earlier error, only `?` - this allows callers to keep printing |
| /// the approximate syntax of the path/type/const, despite having errors, |
| /// e.g. `Vec<[(A, ?); ?]>` instead of `Vec<[(A, ?` |
| macro_rules! parse { |
| ($printer:ident, $method:ident $(($($arg:expr),*))*) => { |
| match $printer.parser { |
| Ok(ref mut parser) => match parser.$method($($($arg),*)*) { |
| Ok(x) => x, |
| Err(err) => { |
| $printer.print(err.message())?; |
| $printer.parser = Err(err); |
| return Ok(()); |
| } |
| } |
| Err(_) => return $printer.print("?"), |
| } |
| }; |
| } |
| |
| impl<'a, 'b, 's> Printer<'a, 'b, 's> { |
| /// Eat the given character from the parser, |
| /// returning `false` if the parser errored. |
| fn eat(&mut self, b: u8) -> bool { |
| self.parser.as_mut().map(|p| p.eat(b)) == Ok(true) |
| } |
| |
| /// Skip printing (i.e. `self.out` will be `None`) for the duration of the |
| /// given closure. This should not change parsing behavior, only disable the |
| /// output, but there may be optimizations (such as not traversing backrefs). |
| fn skipping_printing<F>(&mut self, f: F) |
| where |
| F: FnOnce(&mut Self) -> fmt::Result, |
| { |
| let orig_out = self.out.take(); |
| f(self).expect("`fmt::Error`s should be impossible without a `fmt::Formatter`"); |
| self.out = orig_out; |
| } |
| |
| /// Print the target of a backref, using the given closure. |
| /// When printing is being skipped, the backref will only be parsed, |
| /// ignoring the backref's target completely. |
| fn print_backref<F>(&mut self, f: F) -> fmt::Result |
| where |
| F: FnOnce(&mut Self) -> fmt::Result, |
| { |
| let backref_parser = parse!(self, backref); |
| |
| if self.out.is_none() { |
| return Ok(()); |
| } |
| |
| let orig_parser = mem::replace(&mut self.parser, Ok(backref_parser)); |
| let r = f(self); |
| self.parser = orig_parser; |
| r |
| } |
| |
| fn pop_depth(&mut self) { |
| if let Ok(ref mut parser) = self.parser { |
| parser.pop_depth(); |
| } |
| } |
| |
| /// Output the given value to `self.out` (using `fmt::Display` formatting), |
| /// if printing isn't being skipped. |
| fn print(&mut self, x: impl fmt::Display) -> fmt::Result { |
| if let Some(out) = &mut self.out { |
| fmt::Display::fmt(&x, out)?; |
| } |
| Ok(()) |
| } |
| |
| /// Output the given `char`s (escaped using `char::escape_debug`), with the |
| /// whole sequence wrapped in quotes, for either a `char` or `&str` literal, |
| /// if printing isn't being skipped. |
| fn print_quoted_escaped_chars( |
| &mut self, |
| quote: char, |
| chars: impl Iterator<Item = char>, |
| ) -> fmt::Result { |
| if let Some(out) = &mut self.out { |
| use core::fmt::Write; |
| |
| out.write_char(quote)?; |
| for c in chars { |
| // Special-case not escaping a single/double quote, when |
| // inside the opposite kind of quote. |
| if matches!((quote, c), ('\'', '"') | ('"', '\'')) { |
| out.write_char(c)?; |
| continue; |
| } |
| |
| for escaped in c.escape_debug() { |
| out.write_char(escaped)?; |
| } |
| } |
| out.write_char(quote)?; |
| } |
| Ok(()) |
| } |
| |
| /// Print the lifetime according to the previously decoded index. |
| /// An index of `0` always refers to `'_`, but starting with `1`, |
| /// indices refer to late-bound lifetimes introduced by a binder. |
| fn print_lifetime_from_index(&mut self, lt: u64) -> fmt::Result { |
| // Bound lifetimes aren't tracked when skipping printing. |
| if self.out.is_none() { |
| return Ok(()); |
| } |
| |
| self.print("'")?; |
| if lt == 0 { |
| return self.print("_"); |
| } |
| match (self.bound_lifetime_depth as u64).checked_sub(lt) { |
| Some(depth) => { |
| // Try to print lifetimes alphabetically first. |
| if depth < 26 { |
| let c = (b'a' + depth as u8) as char; |
| self.print(c) |
| } else { |
| // Use `'_123` after running out of letters. |
| self.print("_")?; |
| self.print(depth) |
| } |
| } |
| None => invalid!(self), |
| } |
| } |
| |
| /// Optionally enter a binder ('G') for late-bound lifetimes, |
| /// printing e.g. `for<'a, 'b> ` before calling the closure, |
| /// and make those lifetimes visible to it (via depth level). |
| fn in_binder<F>(&mut self, f: F) -> fmt::Result |
| where |
| F: FnOnce(&mut Self) -> fmt::Result, |
| { |
| let bound_lifetimes = parse!(self, opt_integer_62(b'G')); |
| |
| // Don't track bound lifetimes when skipping printing. |
| if self.out.is_none() { |
| return f(self); |
| } |
| |
| if bound_lifetimes > 0 { |
| self.print("for<")?; |
| for i in 0..bound_lifetimes { |
| if i > 0 { |
| self.print(", ")?; |
| } |
| self.bound_lifetime_depth += 1; |
| self.print_lifetime_from_index(1)?; |
| } |
| self.print("> ")?; |
| } |
| |
| let r = f(self); |
| |
| // Restore `bound_lifetime_depth` to the previous value. |
| self.bound_lifetime_depth -= bound_lifetimes as u32; |
| |
| r |
| } |
| |
| /// Print list elements using the given closure and separator, |
| /// until the end of the list ('E') is found, or the parser errors. |
| /// Returns the number of elements printed. |
| fn print_sep_list<F>(&mut self, f: F, sep: &str) -> Result<usize, fmt::Error> |
| where |
| F: Fn(&mut Self) -> fmt::Result, |
| { |
| let mut i = 0; |
| while self.parser.is_ok() && !self.eat(b'E') { |
| if i > 0 { |
| self.print(sep)?; |
| } |
| f(self)?; |
| i += 1; |
| } |
| Ok(i) |
| } |
| |
| fn print_path(&mut self, in_value: bool) -> fmt::Result { |
| parse!(self, push_depth); |
| |
| let tag = parse!(self, next); |
| match tag { |
| b'C' => { |
| let dis = parse!(self, disambiguator); |
| let name = parse!(self, ident); |
| |
| self.print(name)?; |
| if let Some(out) = &mut self.out { |
| if !out.alternate() { |
| out.write_str("[")?; |
| fmt::LowerHex::fmt(&dis, out)?; |
| out.write_str("]")?; |
| } |
| } |
| } |
| b'N' => { |
| let ns = parse!(self, namespace); |
| |
| self.print_path(in_value)?; |
| |
| // 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 self.parser.is_err() { |
| self.print("::")?; |
| } |
| |
| let dis = parse!(self, disambiguator); |
| let name = parse!(self, ident); |
| |
| match ns { |
| // Special namespaces, like closures and shims. |
| Some(ns) => { |
| self.print("::{")?; |
| match ns { |
| 'C' => self.print("closure")?, |
| 'S' => self.print("shim")?, |
| _ => self.print(ns)?, |
| } |
| if !name.ascii.is_empty() || !name.punycode.is_empty() { |
| self.print(":")?; |
| self.print(name)?; |
| } |
| self.print("#")?; |
| self.print(dis)?; |
| self.print("}")?; |
| } |
| |
| // Implementation-specific/unspecified namespaces. |
| None => { |
| if !name.ascii.is_empty() || !name.punycode.is_empty() { |
| self.print("::")?; |
| self.print(name)?; |
| } |
| } |
| } |
| } |
| b'M' | b'X' | b'Y' => { |
| if tag != b'Y' { |
| // Ignore the `impl`'s own path. |
| parse!(self, disambiguator); |
| self.skipping_printing(|this| this.print_path(false)); |
| } |
| |
| self.print("<")?; |
| self.print_type()?; |
| if tag != b'M' { |
| self.print(" as ")?; |
| self.print_path(false)?; |
| } |
| self.print(">")?; |
| } |
| b'I' => { |
| self.print_path(in_value)?; |
| if in_value { |
| self.print("::")?; |
| } |
| self.print("<")?; |
| self.print_sep_list(Self::print_generic_arg, ", ")?; |
| self.print(">")?; |
| } |
| b'B' => { |
| self.print_backref(|this| this.print_path(in_value))?; |
| } |
| _ => invalid!(self), |
| } |
| |
| self.pop_depth(); |
| Ok(()) |
| } |
| |
| fn print_generic_arg(&mut self) -> fmt::Result { |
| if self.eat(b'L') { |
| let lt = parse!(self, integer_62); |
| self.print_lifetime_from_index(lt) |
| } else if self.eat(b'K') { |
| self.print_const(false) |
| } else { |
| self.print_type() |
| } |
| } |
| |
| fn print_type(&mut self) -> fmt::Result { |
| let tag = parse!(self, next); |
| |
| if let Some(ty) = basic_type(tag) { |
| return self.print(ty); |
| } |
| |
| parse!(self, push_depth); |
| |
| match tag { |
| b'R' | b'Q' => { |
| self.print("&")?; |
| if self.eat(b'L') { |
| let lt = parse!(self, integer_62); |
| if lt != 0 { |
| self.print_lifetime_from_index(lt)?; |
| self.print(" ")?; |
| } |
| } |
| if tag != b'R' { |
| self.print("mut ")?; |
| } |
| self.print_type()?; |
| } |
| |
| b'P' | b'O' => { |
| self.print("*")?; |
| if tag != b'P' { |
| self.print("mut ")?; |
| } else { |
| self.print("const ")?; |
| } |
| self.print_type()?; |
| } |
| |
| b'A' | b'S' => { |
| self.print("[")?; |
| self.print_type()?; |
| if tag == b'A' { |
| self.print("; ")?; |
| self.print_const(true)?; |
| } |
| self.print("]")?; |
| } |
| b'T' => { |
| self.print("(")?; |
| let count = self.print_sep_list(Self::print_type, ", ")?; |
| if count == 1 { |
| self.print(",")?; |
| } |
| self.print(")")?; |
| } |
| b'F' => self.in_binder(|this| { |
| let is_unsafe = this.eat(b'U'); |
| let abi = if this.eat(b'K') { |
| if this.eat(b'C') { |
| Some("C") |
| } else { |
| let abi = parse!(this, ident); |
| if abi.ascii.is_empty() || !abi.punycode.is_empty() { |
| invalid!(this); |
| } |
| Some(abi.ascii) |
| } |
| } else { |
| None |
| }; |
| |
| if is_unsafe { |
| this.print("unsafe ")?; |
| } |
| |
| if let Some(abi) = abi { |
| this.print("extern \"")?; |
| |
| // If the ABI had any `-`, they were replaced with `_`, |
| // so the parts between `_` have to be re-joined with `-`. |
| let mut parts = abi.split('_'); |
| this.print(parts.next().unwrap())?; |
| for part in parts { |
| this.print("-")?; |
| this.print(part)?; |
| } |
| |
| this.print("\" ")?; |
| } |
| |
| this.print("fn(")?; |
| this.print_sep_list(Self::print_type, ", ")?; |
| this.print(")")?; |
| |
| if this.eat(b'u') { |
| // Skip printing the return type if it's 'u', i.e. `()`. |
| } else { |
| this.print(" -> ")?; |
| this.print_type()?; |
| } |
| |
| Ok(()) |
| })?, |
| b'D' => { |
| self.print("dyn ")?; |
| self.in_binder(|this| { |
| this.print_sep_list(Self::print_dyn_trait, " + ")?; |
| Ok(()) |
| })?; |
| |
| if !self.eat(b'L') { |
| invalid!(self); |
| } |
| let lt = parse!(self, integer_62); |
| if lt != 0 { |
| self.print(" + ")?; |
| self.print_lifetime_from_index(lt)?; |
| } |
| } |
| b'B' => { |
| self.print_backref(Self::print_type)?; |
| } |
| _ => { |
| // Go back to the tag, so `print_path` also sees it. |
| let _ = self.parser.as_mut().map(|p| p.next -= 1); |
| self.print_path(false)?; |
| } |
| } |
| |
| self.pop_depth(); |
| Ok(()) |
| } |
| |
| /// 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. |
| fn print_path_maybe_open_generics(&mut self) -> Result<bool, fmt::Error> { |
| if self.eat(b'B') { |
| // NOTE(eddyb) the closure may not run if printing is being skipped, |
| // but in that case the returned boolean doesn't matter. |
| let mut open = false; |
| self.print_backref(|this| { |
| open = this.print_path_maybe_open_generics()?; |
| Ok(()) |
| })?; |
| Ok(open) |
| } else if self.eat(b'I') { |
| self.print_path(false)?; |
| self.print("<")?; |
| self.print_sep_list(Self::print_generic_arg, ", ")?; |
| Ok(true) |
| } else { |
| self.print_path(false)?; |
| Ok(false) |
| } |
| } |
| |
| fn print_dyn_trait(&mut self) -> fmt::Result { |
| let mut open = self.print_path_maybe_open_generics()?; |
| |
| while self.eat(b'p') { |
| if !open { |
| self.print("<")?; |
| open = true; |
| } else { |
| self.print(", ")?; |
| } |
| |
| let name = parse!(self, ident); |
| self.print(name)?; |
| self.print(" = ")?; |
| self.print_type()?; |
| } |
| |
| if open { |
| self.print(">")?; |
| } |
| |
| Ok(()) |
| } |
| |
| fn print_const(&mut self, in_value: bool) -> fmt::Result { |
| let tag = parse!(self, next); |
| |
| parse!(self, push_depth); |
| |
| // Only literals (and the names of `const` generic parameters, but they |
| // don't get mangled at all), can appear in generic argument position |
| // without any disambiguation, all other expressions require braces. |
| // To avoid duplicating the mapping between `tag` and what syntax gets |
| // used (especially any special-casing), every case that needs braces |
| // has to call `open_brace(self)?` (and the closing brace is automatic). |
| let mut opened_brace = false; |
| let mut open_brace_if_outside_expr = |this: &mut Self| { |
| // If this expression is nested in another, braces aren't required. |
| if in_value { |
| return Ok(()); |
| } |
| |
| opened_brace = true; |
| this.print("{") |
| }; |
| |
| match tag { |
| b'p' => self.print("_")?, |
| |
| // Primitive leaves with hex-encoded values (see `basic_type`). |
| b'h' | b't' | b'm' | b'y' | b'o' | b'j' => self.print_const_uint(tag)?, |
| b'a' | b's' | b'l' | b'x' | b'n' | b'i' => { |
| if self.eat(b'n') { |
| self.print("-")?; |
| } |
| |
| self.print_const_uint(tag)?; |
| } |
| b'b' => match parse!(self, hex_nibbles).try_parse_uint() { |
| Some(0) => self.print("false")?, |
| Some(1) => self.print("true")?, |
| _ => invalid!(self), |
| }, |
| b'c' => { |
| let valid_char = parse!(self, hex_nibbles) |
| .try_parse_uint() |
| .and_then(|v| u32::try_from(v).ok()) |
| .and_then(char::from_u32); |
| match valid_char { |
| Some(c) => self.print_quoted_escaped_chars('\'', iter::once(c))?, |
| None => invalid!(self), |
| } |
| } |
| b'e' => { |
| // NOTE(eddyb) a string literal `"..."` has type `&str`, so |
| // to get back the type `str`, `*"..."` syntax is needed |
| // (even if that may not be valid in Rust itself). |
| open_brace_if_outside_expr(self)?; |
| self.print("*")?; |
| |
| self.print_const_str_literal()?; |
| } |
| |
| b'R' | b'Q' => { |
| // NOTE(eddyb) this prints `"..."` instead of `&*"..."`, which |
| // is what `Re..._` would imply (see comment for `str` above). |
| if tag == b'R' && self.eat(b'e') { |
| self.print_const_str_literal()?; |
| } else { |
| open_brace_if_outside_expr(self)?; |
| self.print("&")?; |
| if tag != b'R' { |
| self.print("mut ")?; |
| } |
| self.print_const(true)?; |
| } |
| } |
| b'A' => { |
| open_brace_if_outside_expr(self)?; |
| self.print("[")?; |
| self.print_sep_list(|this| this.print_const(true), ", ")?; |
| self.print("]")?; |
| } |
| b'T' => { |
| open_brace_if_outside_expr(self)?; |
| self.print("(")?; |
| let count = self.print_sep_list(|this| this.print_const(true), ", ")?; |
| if count == 1 { |
| self.print(",")?; |
| } |
| self.print(")")?; |
| } |
| b'V' => { |
| open_brace_if_outside_expr(self)?; |
| self.print_path(true)?; |
| match parse!(self, next) { |
| b'U' => {} |
| b'T' => { |
| self.print("(")?; |
| self.print_sep_list(|this| this.print_const(true), ", ")?; |
| self.print(")")?; |
| } |
| b'S' => { |
| self.print(" { ")?; |
| self.print_sep_list( |
| |this| { |
| parse!(this, disambiguator); |
| let name = parse!(this, ident); |
| this.print(name)?; |
| this.print(": ")?; |
| this.print_const(true) |
| }, |
| ", ", |
| )?; |
| self.print(" }")?; |
| } |
| _ => invalid!(self), |
| } |
| } |
| b'B' => { |
| self.print_backref(|this| this.print_const(in_value))?; |
| } |
| _ => invalid!(self), |
| } |
| |
| if opened_brace { |
| self.print("}")?; |
| } |
| |
| self.pop_depth(); |
| Ok(()) |
| } |
| |
| fn print_const_uint(&mut self, ty_tag: u8) -> fmt::Result { |
| let hex = parse!(self, hex_nibbles); |
| |
| match hex.try_parse_uint() { |
| Some(v) => self.print(v)?, |
| |
| // Print anything that doesn't fit in `u64` verbatim. |
| None => { |
| self.print("0x")?; |
| self.print(hex.nibbles)?; |
| } |
| } |
| |
| if let Some(out) = &mut self.out { |
| if !out.alternate() { |
| let ty = basic_type(ty_tag).unwrap(); |
| self.print(ty)?; |
| } |
| } |
| |
| Ok(()) |
| } |
| |
| fn print_const_str_literal(&mut self) -> fmt::Result { |
| match parse!(self, hex_nibbles).try_parse_str_chars() { |
| Some(chars) => self.print_quoted_escaped_chars('"', chars), |
| None => invalid!(self), |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use std::prelude::v1::*; |
| |
| macro_rules! t { |
| ($a:expr, $b:expr) => {{ |
| assert_eq!(format!("{}", ::demangle($a)), $b); |
| }}; |
| } |
| macro_rules! t_nohash { |
| ($a:expr, $b:expr) => {{ |
| assert_eq!(format!("{:#}", ::demangle($a)), $b); |
| }}; |
| } |
| macro_rules! t_nohash_type { |
| ($a:expr, $b:expr) => { |
| t_nohash!(concat!("_RMC0", $a), concat!("<", $b, ">")) |
| }; |
| } |
| macro_rules! t_const { |
| ($mangled:expr, $value:expr) => { |
| t_nohash!( |
| concat!("_RIC0K", $mangled, "E"), |
| concat!("::<", $value, ">") |
| ) |
| }; |
| } |
| macro_rules! t_const_suffixed { |
| ($mangled:expr, $value:expr, $value_ty_suffix:expr) => {{ |
| t_const!($mangled, $value); |
| t!( |
| concat!("_RIC0K", $mangled, "E"), |
| concat!("[0]::<", $value, $value_ty_suffix, ">") |
| ); |
| }}; |
| } |
| |
| #[test] |
| fn demangle_crate_with_leading_digit() { |
| t_nohash!("_RNvC6_123foo3bar", "123foo::bar"); |
| } |
| |
| #[test] |
| fn demangle_utf8_idents() { |
| t_nohash!( |
| "_RNqCs4fqI2P2rA04_11utf8_identsu30____7hkackfecea1cbdathfdh9hlq6y", |
| "utf8_idents::แกแแญแแแแแ_แแแแ แแแแ_แกแแแแแ" |
| ); |
| } |
| |
| #[test] |
| fn demangle_closure() { |
| t_nohash!( |
| "_RNCNCNgCs6DXkGYLi8lr_2cc5spawn00B5_", |
| "cc::spawn::{closure#0}::{closure#0}" |
| ); |
| t_nohash!( |
| "_RNCINkXs25_NgCsbmNqQUJIY6D_4core5sliceINyB9_4IterhENuNgNoBb_4iter8iterator8Iterator9rpositionNCNgNpB9_6memchr7memrchrs_0E0Bb_", |
| "<core::slice::Iter<u8> as core::iter::iterator::Iterator>::rposition::<core::slice::memchr::memrchr::{closure#1}>::{closure#0}" |
| ); |
| } |
| |
| #[test] |
| fn demangle_dyn_trait() { |
| t_nohash!( |
| "_RINbNbCskIICzLVDPPb_5alloc5alloc8box_freeDINbNiB4_5boxed5FnBoxuEp6OutputuEL_ECs1iopQbuBiw2_3std", |
| "alloc::alloc::box_free::<dyn alloc::boxed::FnBox<(), Output = ()>>" |
| ); |
| } |
| |
| #[test] |
| fn demangle_const_generics_preview() { |
| // NOTE(eddyb) this was hand-written, before rustc had working |
| // const generics support (but the mangling format did include them). |
| t_nohash_type!( |
| "INtC8arrayvec8ArrayVechKj7b_E", |
| "arrayvec::ArrayVec<u8, 123>" |
| ); |
| t_const_suffixed!("j7b_", "123", "usize"); |
| } |
| |
| #[test] |
| fn demangle_min_const_generics() { |
| t_const!("p", "_"); |
| t_const_suffixed!("hb_", "11", "u8"); |
| t_const_suffixed!("off00ff00ff00ff00ff_", "0xff00ff00ff00ff00ff", "u128"); |
| t_const_suffixed!("s98_", "152", "i16"); |
| t_const_suffixed!("anb_", "-11", "i8"); |
| t_const!("b0_", "false"); |
| t_const!("b1_", "true"); |
| t_const!("c76_", "'v'"); |
| t_const!("c22_", r#"'"'"#); |
| t_const!("ca_", "'\\n'"); |
| t_const!("c2202_", "'โ'"); |
| } |
| |
| #[test] |
| fn demangle_const_str() { |
| t_const!("e616263_", "{*\"abc\"}"); |
| t_const!("e27_", r#"{*"'"}"#); |
| t_const!("e090a_", "{*\"\\t\\n\"}"); |
| t_const!("ee28882c3bc_", "{*\"โรผ\"}"); |
| t_const!( |
| "ee183a1e18390e183ade1839be18394e1839ae18390e183935fe18392e18394e1839b\ |
| e183a0e18398e18394e1839ae183985fe183a1e18390e18393e18398e1839ae18398_", |
| "{*\"แกแแญแแแแแ_แแแแ แแแแ_แกแแแแแ\"}" |
| ); |
| t_const!( |
| "ef09f908af09fa688f09fa686f09f90ae20c2a720f09f90b6f09f9192e298\ |
| 95f09f94a520c2a720f09fa7a1f09f929bf09f929af09f9299f09f929c_", |
| "{*\"๐๐ฆ๐ฆ๐ฎ ยง ๐ถ๐โ๐ฅ ยง ๐งก๐๐๐๐\"}" |
| ); |
| } |
| |
| // NOTE(eddyb) this uses the same strings as `demangle_const_str` and should |
| // be kept in sync with it - while a macro could be used to generate both |
| // `str` and `&str` tests, from a single list of strings, this seems clearer. |
| #[test] |
| fn demangle_const_ref_str() { |
| t_const!("Re616263_", "\"abc\""); |
| t_const!("Re27_", r#""'""#); |
| t_const!("Re090a_", "\"\\t\\n\""); |
| t_const!("Ree28882c3bc_", "\"โรผ\""); |
| t_const!( |
| "Ree183a1e18390e183ade1839be18394e1839ae18390e183935fe18392e18394e1839b\ |
| e183a0e18398e18394e1839ae183985fe183a1e18390e18393e18398e1839ae18398_", |
| "\"แกแแญแแแแแ_แแแแ แแแแ_แกแแแแแ\"" |
| ); |
| t_const!( |
| "Ref09f908af09fa688f09fa686f09f90ae20c2a720f09f90b6f09f9192e298\ |
| 95f09f94a520c2a720f09fa7a1f09f929bf09f929af09f9299f09f929c_", |
| "\"๐๐ฆ๐ฆ๐ฎ ยง ๐ถ๐โ๐ฅ ยง ๐งก๐๐๐๐\"" |
| ); |
| } |
| |
| #[test] |
| fn demangle_const_ref() { |
| t_const!("Rp", "{&_}"); |
| t_const!("Rh7b_", "{&123}"); |
| t_const!("Rb0_", "{&false}"); |
| t_const!("Rc58_", "{&'X'}"); |
| t_const!("RRRh0_", "{&&&0}"); |
| t_const!("RRRe_", "{&&\"\"}"); |
| t_const!("QAE", "{&mut []}"); |
| } |
| |
| #[test] |
| fn demangle_const_array() { |
| t_const!("AE", "{[]}"); |
| t_const!("Aj0_E", "{[0]}"); |
| t_const!("Ah1_h2_h3_E", "{[1, 2, 3]}"); |
| t_const!("ARe61_Re62_Re63_E", "{[\"a\", \"b\", \"c\"]}"); |
| t_const!("AAh1_h2_EAh3_h4_EE", "{[[1, 2], [3, 4]]}"); |
| } |
| |
| #[test] |
| fn demangle_const_tuple() { |
| t_const!("TE", "{()}"); |
| t_const!("Tj0_E", "{(0,)}"); |
| t_const!("Th1_b0_E", "{(1, false)}"); |
| t_const!( |
| "TRe616263_c78_RAh1_h2_h3_EE", |
| "{(\"abc\", 'x', &[1, 2, 3])}" |
| ); |
| } |
| |
| #[test] |
| fn demangle_const_adt() { |
| t_const!( |
| "VNvINtNtC4core6option6OptionjE4NoneU", |
| "{core::option::Option::<usize>::None}" |
| ); |
| t_const!( |
| "VNvINtNtC4core6option6OptionjE4SomeTj0_E", |
| "{core::option::Option::<usize>::Some(0)}" |
| ); |
| t_const!( |
| "VNtC3foo3BarS1sRe616263_2chc78_5sliceRAh1_h2_h3_EE", |
| "{foo::Bar { s: \"abc\", ch: 'x', slice: &[1, 2, 3] }}" |
| ); |
| } |
| |
| #[test] |
| fn demangle_exponential_explosion() { |
| // NOTE(eddyb) because of the prefix added by `t_nohash_type!` is |
| // 3 bytes long, `B2_` refers to the start of the type, not `B_`. |
| // 6 backrefs (`B8_E` through `B3_E`) result in 2^6 = 64 copies of `_`. |
| // Also, because the `p` (`_`) type is after all of the starts of the |
| // backrefs, it can be replaced with any other type, independently. |
| t_nohash_type!( |
| concat!("TTTTTT", "p", "B8_E", "B7_E", "B6_E", "B5_E", "B4_E", "B3_E"), |
| "((((((_, _), (_, _)), ((_, _), (_, _))), (((_, _), (_, _)), ((_, _), (_, _)))), \ |
| ((((_, _), (_, _)), ((_, _), (_, _))), (((_, _), (_, _)), ((_, _), (_, _))))), \ |
| (((((_, _), (_, _)), ((_, _), (_, _))), (((_, _), (_, _)), ((_, _), (_, _)))), \ |
| ((((_, _), (_, _)), ((_, _), (_, _))), (((_, _), (_, _)), ((_, _), (_, _))))))" |
| ); |
| } |
| |
| #[test] |
| fn demangle_thinlto() { |
| t_nohash!("_RC3foo.llvm.9D1C9369", "foo"); |
| t_nohash!("_RC3foo.llvm.9D1C9369@@16", "foo"); |
| t_nohash!("_RNvC9backtrace3foo.llvm.A5310EB9", "backtrace::foo"); |
| } |
| |
| #[test] |
| fn demangle_extra_suffix() { |
| // From alexcrichton/rustc-demangle#27: |
| t_nohash!( |
| "_RNvNtNtNtNtCs92dm3009vxr_4rand4rngs7adapter9reseeding4fork23FORK_HANDLER_REGISTERED.0.0", |
| "rand::rngs::adapter::reseeding::fork::FORK_HANDLER_REGISTERED.0.0" |
| ); |
| } |
| |
| #[test] |
| fn demangling_limits() { |
| // Stress tests found via fuzzing. |
| |
| for sym in include_str!("v0-large-test-symbols/early-recursion-limit") |
| .lines() |
| .filter(|line| !line.is_empty() && !line.starts_with('#')) |
| { |
| assert_eq!( |
| super::demangle(sym).map(|_| ()), |
| Err(super::ParseError::RecursedTooDeep) |
| ); |
| } |
| |
| assert_contains!( |
| ::demangle( |
| "RIC20tRYIMYNRYFG05_EB5_B_B6_RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR\ |
| RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRB_E", |
| ) |
| .to_string(), |
| "{recursion limit reached}" |
| ); |
| } |
| |
| #[test] |
| fn recursion_limit_leaks() { |
| // NOTE(eddyb) this test checks that both paths and types support the |
| // recursion limit correctly, i.e. matching `push_depth` and `pop_depth`, |
| // and don't leak "recursion levels" and trip the limit. |
| // The test inputs are generated on the fly, using a repeated pattern, |
| // as hardcoding the actual strings would be too verbose. |
| // Also, `MAX_DEPTH` can be directly used, instead of assuming its value. |
| for &(sym_leaf, expected_leaf) in &[("p", "_"), ("Rp", "&_"), ("C1x", "x")] { |
| let mut sym = format!("_RIC0p"); |
| let mut expected = format!("::<_"); |
| for _ in 0..(super::MAX_DEPTH * 2) { |
| sym.push_str(sym_leaf); |
| expected.push_str(", "); |
| expected.push_str(expected_leaf); |
| } |
| sym.push('E'); |
| expected.push('>'); |
| |
| t_nohash!(&sym, expected); |
| } |
| } |
| |
| #[test] |
| fn recursion_limit_backref_free_bypass() { |
| // NOTE(eddyb) this test checks that long symbols cannot bypass the |
| // recursion limit by not using backrefs, and cause a stack overflow. |
| |
| // This value was chosen to be high enough that stack overflows were |
| // observed even with `cargo test --release`. |
| let depth = 100_000; |
| |
| // In order to hide the long mangling from the initial "shallow" parse, |
| // it's nested in an identifier (crate name), preceding its use. |
| let mut sym = format!("_RIC{}", depth); |
| let backref_start = sym.len() - 2; |
| for _ in 0..depth { |
| sym.push('R'); |
| } |
| |
| // Write a backref to just after the length of the identifier. |
| sym.push('B'); |
| sym.push(char::from_digit((backref_start - 1) as u32, 36).unwrap()); |
| sym.push('_'); |
| |
| // Close the `I` at the start. |
| sym.push('E'); |
| |
| assert_contains!(::demangle(&sym).to_string(), "{recursion limit reached}"); |
| } |
| } |