// 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. pubstruct Demangle<'a> {
inner: &'a str,
}
#[derive(PartialEq, Eq, Debug)] pubenum 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. pubfn 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..];
} elseif s.len() > 1 && s.starts_with('R') { // On Windows, dbghelp strips leading underscores, so we accept "R..." // form too.
inner = &s[1..];
} elseif 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| { letmut 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
}; letmut parser = Parser {
sym: inner,
next: 0,
depth: 0,
};
parser = try_parse_path(parser)?;
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> { letmut out = ['\0'; SMALL_PUNYCODE_LEN]; letmut 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. letmut j = out_len;
out_len += 1;
/// 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<(), ()> { letmut punycode_bytes = self.punycode.bytes().peekable(); if punycode_bytes.peek().is_none() { return Err(());
}
letmut len = 0;
// Populate initial output from ASCII fragment. for c inself.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; letmut damp = 700; letmut bias = 72; letmut i: usize = 0; letmut n: usize = 0x80;
loop { // Read one delta value. letmut delta: usize = 0; letmut w = 1; letmut 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(());
}
delta += delta / len; letmut 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)?;
/// 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;
}
letmut 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> { ifself.nibbles.len() % 2 != 0 { return None;
}
// FIXME(eddyb) use `array_chunks` instead, when that becomes stable. letmut 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 in1..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`. letmut 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))
}
}
}
letmut 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 backref(&mutself) -> 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);
} letmut new_parser = Parser {
sym: self.sym,
next: i as usize,
depth: self.depth,
};
new_parser.push_depth()?;
Ok(new_parser)
}
fn ident(&mutself) -> Result<Ident<'s>, ParseError> { let is_punycode = self.eat(b'u'); letmut len = self.digit_10()? as usize; if len != 0 { whilelet 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'_');
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 { matchself {
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(refmut 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(&mutself, 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>(&mutself, f: F) where
F: FnOnce(&mutSelf) -> 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>(&mutself, f: F) -> fmt::Result where
F: FnOnce(&mutSelf) -> fmt::Result,
{ let backref_parser = parse!(self, backref);
ifself.out.is_none() { return Ok(());
}
let orig_parser = mem::replace(&mutself.parser, Ok(backref_parser)); let r = f(self); self.parser = orig_parser;
r
}
/// Output the given value to `self.out` (using `fmt::Display` formatting), /// if printing isn't being skipped. fn print(&mutself, x: impl fmt::Display) -> fmt::Result { iflet Some(out) = &mutself.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(
&mutself,
quote: char,
chars: impl Iterator<Item = char>,
) -> fmt::Result { iflet Some(out) = &mutself.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(&mutself, lt: u64) -> fmt::Result { // Bound lifetimes aren't tracked when skipping printing. ifself.out.is_none() { return Ok(());
}
self.print("'")?; if lt == 0 { returnself.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>(&mutself, f: F) -> fmt::Result where
F: FnOnce(&mutSelf) -> fmt::Result,
{ let bound_lifetimes = parse!(self, opt_integer_62(b'G'));
if bound_lifetimes > 0 { self.print("for<")?; for i in0..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>(&mutself, f: F, sep: &str) -> Result<usize, fmt::Error> where
F: Fn(&mutSelf) -> fmt::Result,
{ letmut i = 0; whileself.parser.is_ok() && !self.eat(b'E') { if i > 0 { self.print(sep)?;
}
f(self)?;
i += 1;
}
Ok(i)
}
// 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. ifself.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),
}
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 ")?;
}
iflet 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 `-`. letmut parts = abi.split('_');
this.print(parts.next().unwrap())?; for part in parts {
this.print("-")?;
this.print(part)?;
}
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(&mutself) -> Result<bool, fmt::Error> { ifself.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. letmut open = false; self.print_backref(|this| {
open = this.print_path_maybe_open_generics()?;
Ok(())
})?;
Ok(open)
} elseifself.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(&mutself) -> fmt::Result { letmut open = self.print_path_maybe_open_generics()?;
whileself.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(&mutself, 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). letmut opened_brace = false; letmut open_brace_if_outside_expr = |this: &mutSelf| { // If this expression is nested in another, braces aren't required. if in_value { return Ok(());
}
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),
}
#[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");
}
// 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_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 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")] { letmut sym = format!("_RIC0p"); letmut expected = format!("::<_"); for _ in0..(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. letmut sym = format!("_RIC{}", depth); let backref_start = sym.len() - 2; for _ in0..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('_');
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.