Skip to content

Commit 8a39eeb

Browse files
authored
Marginally improve v0 error granularity and testing. (#54)
* v0: use `parse!` in `print_backref`. * v0: stop using the `parser_mut` method outside of `parse!`. * v0: replace `Invalid` error type with a two-case `ParseError`. * v0: remove unnecessary escaping from fuzzer-generated testcases. * v0: replace control characters with `.` in fuzzer-generated testcases. * v0: move the (long) fuzzer-generated testcases into a separate file. * Rework size limiting to not "leak" `fmt::Error`s to the user.
1 parent d860281 commit 8a39eeb

File tree

3 files changed

+194
-654
lines changed

3 files changed

+194
-654
lines changed

src/lib.rs

+80-35
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,32 @@
3030
#[macro_use]
3131
extern crate std;
3232

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+
3359
mod legacy;
3460
mod v0;
3561

@@ -90,7 +116,12 @@ pub fn demangle(mut s: &str) -> Demangle {
90116
suffix = s;
91117
Some(DemangleStyle::V0(d))
92118
}
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,
94125
},
95126
};
96127

@@ -188,37 +219,55 @@ impl<'a> fmt::Display for DemangleStyle<'a> {
188219
// Maximum size of the symbol that we'll print.
189220
const MAX_SIZE: usize = 1_000_000;
190221

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>,
193227
inner: F,
194228
}
195229

196-
impl<F: fmt::Write> fmt::Write for LimitedFmtWriter<F> {
230+
impl<F: fmt::Write> fmt::Write for SizeLimitedFmtAdapter<F> {
197231
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));
200235

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),
204239
}
205240
}
206241
}
207242

208243
impl<'a> fmt::Display for Demangle<'a> {
209244
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-
};
215245
match self.style {
216246
None => f.write_str(self.original)?,
217247
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)
220255
} 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+
}
222271
}
223272
}
224273
}
@@ -418,31 +467,27 @@ mod tests {
418467

419468
#[test]
420469
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}"
427473
);
428-
assert_eq!(
429-
super::demangle("_RMC0RB2_").to_string().replace("&", ""),
430-
"<?>"
474+
assert_contains!(
475+
super::demangle("_RMC0RB2_").to_string(),
476+
"{recursion limit reached}"
431477
);
432478
}
433479

434480
#[test]
435481
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+
);
444486
// NOTE(eddyb) somewhat reduced version of the above, effectively
445487
// `<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+
);
447492
}
448493
}

0 commit comments

Comments
 (0)