diff --git a/Cargo.toml b/Cargo.toml index cf54ce142..e26d4e3c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,6 +57,7 @@ cfg-if = "1.0" serde = { version = "1.0", optional = true, default-features = false } sval = { version = "=1.0.0-alpha.5", optional = true, default-features = false } value-bag = { version = "=1.0.0-alpha.9", optional = true, default-features = false } +arbitrary = {version = "1.1.6", optional = true, features = ["derive"]} [dev-dependencies] rustversion = "1.0" diff --git a/src/arbitrary_impl.rs b/src/arbitrary_impl.rs new file mode 100644 index 000000000..45beae62b --- /dev/null +++ b/src/arbitrary_impl.rs @@ -0,0 +1,49 @@ +use crate::{Level, MetadataBuilder, RecordBuilder}; +use arbitrary::{Arbitrary, Result, Unstructured}; + +impl<'a> Arbitrary<'a> for RecordBuilder<'a> { + fn arbitrary(u: &mut Unstructured<'a>) -> Result { + let target = &<&'a str>::arbitrary(u)?; + let path = <&'a str>::arbitrary(u)?; + let file = <&'a str>::arbitrary(u)?; + + let mut builder = RecordBuilder::new(); + + builder + // We can't yet provide an arbitrary fmt::Argument object because + // the output of format_args! must be consumed where it is called. + // It cannot be bound to a variable. See https://github.com/rust-lang/rust/issues/92698#ref-pullrequest-1225460272 + // .args(format_args!("{}", logoutput)) + .metadata( + MetadataBuilder::new() + .level(Level::arbitrary(u)?) + .target(target) + .build(), + ) + .file(Some(file.clone())) + .line(Option::::arbitrary(u)?) + .module_path(Some(path.clone())); + + return Ok(builder); + } +} +#[cfg(test)] +mod tests { + use crate::{logger, RecordBuilder}; + use arbitrary::{Arbitrary, Unstructured}; + + #[derive(Arbitrary, Debug)] + struct LogFuzzerInput<'a> { + builder: RecordBuilder<'a>, + message: String, + } + + #[test] + fn arbitrary_record() { + let input: [u8; 8] = [1, 2, 3, 4, 5, 6, 7, 8]; + let mut buf = Unstructured::new(&input); + let mut arb = LogFuzzerInput::arbitrary(&mut buf).unwrap(); + + logger().log(&arb.builder.args(format_args!("{}", arb.message)).build()); + } +} diff --git a/src/lib.rs b/src/lib.rs index 42a3d77d2..4c8375050 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -328,6 +328,12 @@ #[cfg(all(not(feature = "std"), not(test)))] extern crate core as std; + +#[cfg(feature = "arbitrary")] +mod arbitrary_impl; +#[cfg(feature = "arbitrary")] +extern crate arbitrary; + #[macro_use] extern crate cfg_if; @@ -423,6 +429,7 @@ static LEVEL_PARSE_ERROR: &str = /// [`LevelFilter`](enum.LevelFilter.html). #[repr(usize)] #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub enum Level { /// The "error" level. /// @@ -551,6 +558,7 @@ impl Level { /// [`max_level()`]: fn.max_level.html /// [`set_max_level`]: fn.set_max_level.html #[repr(usize)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] pub enum LevelFilter { /// A level lower than all log levels. @@ -725,6 +733,8 @@ pub struct Record<'a> { key_values: KeyValues<'a>, } + + // This wrapper type is only needed so we can // `#[derive(Debug)]` on `Record`. It also // provides a useful `Debug` implementation for