|
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