blob: 433f82a4ba52dbb2f0db1f903019adc30bcc9cf4 [file] [log] [blame]
#![no_main]
use std::ffi::{CStr, CString};
use std::fmt::Write;
use libfuzzer_sys::fuzz_target;
#[derive(Debug)]
enum State<'a> {
Ok(&'a str),
Overflow,
}
pub struct FormatBuf<'a> {
buf: &'a mut String,
max_len: usize,
}
impl<'a> Write for FormatBuf<'a> {
fn write_str(&mut self, s: &str) -> Result<(), std::fmt::Error> {
if self.buf.len() + s.len() > self.max_len {
return Err(std::fmt::Error);
}
self.buf.push_str(s);
Ok(())
}
}
fn asciify(x: &str) -> String {
let mut result = String::with_capacity(x.len() * 4);
for ch in x.chars() {
if ch.is_ascii() {
result.push(ch);
} else {
write!(&mut result, "\\u{{{:x}}}", ch as u32).ok();
}
}
result
}
fn fuzz(data: &[u8], alternate: bool) {
let mut str_buf = String::with_capacity(16384);
let mut buf = [0u8; 4096];
let mut demangle = rustc_demangle_native_c::CDemangle::zero();
// We want to allow for easy overflow checking. The C output
// can be longer than the Rust output by a factor of up to *7/2,
// since e.g. an 'α' (U+03b1) in a constant string will be encoded
// as [b'\xce' b'\xb1'] in the C output (2 bytes) but as
// [b'\\' b'u' b'{' b'3' b'b' b'1' '}'] (7 bytes). The
// other factors are smaller than that (4 hex digits = 3 utf-8 bytes,
// leading to a lower expansion factor of *8/3, and so on).
//
// Also, to make the fuzzer more easily encounter overflow conditions
// in the C code, and since for most outputs the output lengths is the
// same, starting with a similar output length makes it easier.
let starting_buf_len = buf.len() / 4;
let state;
if let Ok(s) = std::str::from_utf8(data) {
if let Ok(cs) = CString::new(data) {
unsafe {
rustc_demangle_native_c::rust_demangle_demangle(cs.as_ptr(), &mut demangle);
match rustc_demangle_native_c::rust_demangle_display_demangle(
&demangle,
buf.as_mut_ptr().cast(),
starting_buf_len,
alternate,
) {
0 => {
state = State::Ok(
CStr::from_bytes_until_nul(&buf[..])
.expect("nul")
.to_str()
.expect("utf-8"),
);
}
_ => {
state = State::Overflow;
}
};
}
let rdemangle = rustc_demangle::demangle(s);
match state {
State::Overflow => {
str_buf.clear();
let fmt_buf = &mut FormatBuf {
buf: &mut str_buf,
max_len: starting_buf_len - 4,
};
let rust_overflowed = if alternate {
write!(fmt_buf, "{:#}", rdemangle)
} else {
write!(fmt_buf, "{}", rdemangle)
};
if rust_overflowed.is_err() {
return; // rust overflowed as well, OK
}
// call C again with larger buffer. If it fits in an 1020-byte Rust buffer, it will fit in a 4096-byte C buffer
let c_demangled = unsafe {
match rustc_demangle_native_c::rust_demangle_display_demangle(
&demangle,
buf.as_mut_ptr().cast(),
buf.len(),
alternate,
) {
0 => CStr::from_bytes_until_nul(&buf[..])
.expect("nul")
.to_str()
.expect("utf-8"),
_ => {
panic!("overflow again");
}
}
};
assert_eq!(asciify(&str_buf), asciify(c_demangled));
if c_demangled.len() < starting_buf_len - 3 {
panic!(
"spurious overflow {} {:?} {:?} {:?} {}",
c_demangled.len(),
alternate,
asciify(&str_buf),
asciify(c_demangled),
starting_buf_len
)
}
}
State::Ok(demangled) => {
let fmt_buf = &mut FormatBuf {
buf: &mut str_buf,
max_len: starting_buf_len - 4,
};
let rust_overflowed = if alternate {
write!(fmt_buf, "{:#}", rdemangle)
} else {
write!(fmt_buf, "{}", rdemangle)
};
if rust_overflowed.is_err() {
panic!("rust overflowed 1020 but C output is <1024");
}
assert_eq!(
(alternate, asciify(&str_buf)),
(alternate, asciify(demangled))
);
}
}
}
}
}
fuzz_target!(|data: &[u8]| {
// fuzz both normal and alternate modes.
fuzz(data, false);
fuzz(data, true);
});