|
30 | 30 | #[macro_use] |
31 | 31 | extern crate std; |
32 | 32 |
|
| 33 | +// HACK(eddyb) helper macros for tests. |
| 34 | +#[cfg(test)] |
| 35 | +macro_rules! assert_contains { |
| 36 | + ($s:expr, $needle:expr) => {{ |
| 37 | + let (s, needle) = ($s, $needle); |
| 38 | + assert!( |
| 39 | + s.contains(needle), |
| 40 | + "{:?} should've contained {:?}", |
| 41 | + s, |
| 42 | + needle |
| 43 | + ); |
| 44 | + }}; |
| 45 | +} |
| 46 | +#[cfg(test)] |
| 47 | +macro_rules! assert_ends_with { |
| 48 | + ($s:expr, $suffix:expr) => {{ |
| 49 | + let (s, suffix) = ($s, $suffix); |
| 50 | + assert!( |
| 51 | + s.ends_with(suffix), |
| 52 | + "{:?} should've ended in {:?}", |
| 53 | + s, |
| 54 | + suffix |
| 55 | + ); |
| 56 | + }}; |
| 57 | +} |
| 58 | + |
33 | 59 | mod legacy; |
34 | 60 | mod v0; |
35 | 61 |
|
@@ -90,7 +116,12 @@ pub fn demangle(mut s: &str) -> Demangle { |
90 | 116 | suffix = s; |
91 | 117 | Some(DemangleStyle::V0(d)) |
92 | 118 | } |
93 | | - Err(v0::Invalid) => None, |
| 119 | + // FIXME(eddyb) would it make sense to treat an unknown-validity |
| 120 | + // symbol (e.g. one that errored with `RecursedTooDeep`) as |
| 121 | + // v0-mangled, and have the error show up in the demangling? |
| 122 | + // (that error already gets past this initial check, and therefore |
| 123 | + // will show up in the demangling, if hidden behind a backref) |
| 124 | + Err(v0::ParseError::Invalid) | Err(v0::ParseError::RecursedTooDeep) => None, |
94 | 125 | }, |
95 | 126 | }; |
96 | 127 |
|
@@ -188,37 +219,55 @@ impl<'a> fmt::Display for DemangleStyle<'a> { |
188 | 219 | // Maximum size of the symbol that we'll print. |
189 | 220 | const MAX_SIZE: usize = 1_000_000; |
190 | 221 |
|
191 | | -struct LimitedFmtWriter<F> { |
192 | | - remaining: usize, |
| 222 | +#[derive(Copy, Clone, Debug)] |
| 223 | +struct SizeLimitExhausted; |
| 224 | + |
| 225 | +struct SizeLimitedFmtAdapter<F> { |
| 226 | + remaining: Result<usize, SizeLimitExhausted>, |
193 | 227 | inner: F, |
194 | 228 | } |
195 | 229 |
|
196 | | -impl<F: fmt::Write> fmt::Write for LimitedFmtWriter<F> { |
| 230 | +impl<F: fmt::Write> fmt::Write for SizeLimitedFmtAdapter<F> { |
197 | 231 | fn write_str(&mut self, s: &str) -> fmt::Result { |
198 | | - let remaining = self.remaining.checked_sub(s.len()); |
199 | | - self.remaining = remaining.unwrap_or(0); |
| 232 | + self.remaining = self |
| 233 | + .remaining |
| 234 | + .and_then(|r| r.checked_sub(s.len()).ok_or(SizeLimitExhausted)); |
200 | 235 |
|
201 | | - match remaining { |
202 | | - Some(_) => self.inner.write_str(s), |
203 | | - None => Err(fmt::Error), |
| 236 | + match self.remaining { |
| 237 | + Ok(_) => self.inner.write_str(s), |
| 238 | + Err(SizeLimitExhausted) => Err(fmt::Error), |
204 | 239 | } |
205 | 240 | } |
206 | 241 | } |
207 | 242 |
|
208 | 243 | impl<'a> fmt::Display for Demangle<'a> { |
209 | 244 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
210 | | - let alternate = f.alternate(); |
211 | | - let mut f = LimitedFmtWriter { |
212 | | - remaining: MAX_SIZE, |
213 | | - inner: f, |
214 | | - }; |
215 | 245 | match self.style { |
216 | 246 | None => f.write_str(self.original)?, |
217 | 247 | Some(ref d) => { |
218 | | - if alternate { |
219 | | - write!(f, "{:#}", d)?; |
| 248 | + let alternate = f.alternate(); |
| 249 | + let mut size_limited_fmt = SizeLimitedFmtAdapter { |
| 250 | + remaining: Ok(MAX_SIZE), |
| 251 | + inner: &mut *f, |
| 252 | + }; |
| 253 | + let fmt_result = if alternate { |
| 254 | + write!(size_limited_fmt, "{:#}", d) |
220 | 255 | } else { |
221 | | - write!(f, "{}", d)?; |
| 256 | + write!(size_limited_fmt, "{}", d) |
| 257 | + }; |
| 258 | + let size_limit_result = size_limited_fmt.remaining.map(|_| ()); |
| 259 | + |
| 260 | + // Translate a `fmt::Error` generated by `SizeLimitedFmtAdapter` |
| 261 | + // into an error message, instead of propagating it upwards |
| 262 | + // (which could cause panicking from inside e.g. `std::io::print`). |
| 263 | + match (fmt_result, size_limit_result) { |
| 264 | + (Err(_), Err(SizeLimitExhausted)) => f.write_str("{size limit reached}")?, |
| 265 | + |
| 266 | + _ => { |
| 267 | + fmt_result?; |
| 268 | + size_limit_result |
| 269 | + .expect("`fmt::Error` from `SizeLimitedFmtAdapter` was discarded"); |
| 270 | + } |
222 | 271 | } |
223 | 272 | } |
224 | 273 | } |
@@ -418,31 +467,27 @@ mod tests { |
418 | 467 |
|
419 | 468 | #[test] |
420 | 469 | fn limit_recursion() { |
421 | | - // NOTE(eddyb) the `?` indicate that a parse error was encountered. |
422 | | - // FIXME(eddyb) replace `v0::Invalid` with a proper `v0::ParseError`, |
423 | | - // that could show e.g. `<recursion limit reached>` instead of `?`. |
424 | | - assert_eq!( |
425 | | - super::demangle("_RNvB_1a").to_string().replace("::a", ""), |
426 | | - "?" |
| 470 | + assert_contains!( |
| 471 | + super::demangle("_RNvB_1a").to_string(), |
| 472 | + "{recursion limit reached}" |
427 | 473 | ); |
428 | | - assert_eq!( |
429 | | - super::demangle("_RMC0RB2_").to_string().replace("&", ""), |
430 | | - "<?>" |
| 474 | + assert_contains!( |
| 475 | + super::demangle("_RMC0RB2_").to_string(), |
| 476 | + "{recursion limit reached}" |
431 | 477 | ); |
432 | 478 | } |
433 | 479 |
|
434 | 480 | #[test] |
435 | 481 | fn limit_output() { |
436 | | - use std::fmt::Write; |
437 | | - let mut s = String::new(); |
438 | | - assert!(write!( |
439 | | - s, |
440 | | - "{}", |
441 | | - super::demangle("RYFG_FGyyEvRYFF_EvRYFFEvERLB_B_B_ERLRjB_B_B_") |
442 | | - ) |
443 | | - .is_err()); |
| 482 | + assert_ends_with!( |
| 483 | + super::demangle("RYFG_FGyyEvRYFF_EvRYFFEvERLB_B_B_ERLRjB_B_B_").to_string(), |
| 484 | + "{size limit reached}" |
| 485 | + ); |
444 | 486 | // NOTE(eddyb) somewhat reduced version of the above, effectively |
445 | 487 | // `<for<...> fn()>` with a larger number of lifetimes in `...`. |
446 | | - assert!(write!(s, "{}", super::demangle("_RMC0FGZZZ_Eu")).is_err()); |
| 488 | + assert_ends_with!( |
| 489 | + super::demangle("_RMC0FGZZZ_Eu").to_string(), |
| 490 | + "{size limit reached}" |
| 491 | + ); |
447 | 492 | } |
448 | 493 | } |
0 commit comments