Refactor Output Observers (#856)

* Refactor Output Observers

* Delete .gitmodules

* modules

* Drop need for OutputObserving list
This commit is contained in:
Dominik Maier 2022-10-24 02:50:00 +02:00 committed by GitHub
parent 5b75b6b8ac
commit 9695ce0029
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 205 additions and 151 deletions

View File

@ -24,7 +24,7 @@ use libafl::{
inputs::{HasTargetBytes, Input}, inputs::{HasTargetBytes, Input},
monitors::SimpleMonitor, monitors::SimpleMonitor,
mutators::scheduled::{havoc_mutations, StdScheduledMutator}, mutators::scheduled::{havoc_mutations, StdScheduledMutator},
observers::{get_asan_runtime_flags, ASANBacktraceObserver, StdMapObserver}, observers::{get_asan_runtime_flags, AsanBacktraceObserver, StdMapObserver},
schedulers::QueueScheduler, schedulers::QueueScheduler,
stages::mutational::StdMutationalStage, stages::mutational::StdMutationalStage,
state::StdState, state::StdState,
@ -40,7 +40,7 @@ pub fn main() {
// Create an observation channel using the signals map // Create an observation channel using the signals map
let observer = StdMapObserver::new("signals", signals.as_mut_slice()); let observer = StdMapObserver::new("signals", signals.as_mut_slice());
// Create a stacktrace observer // Create a stacktrace observer
let bt_observer = ASANBacktraceObserver::new("ASANBacktraceObserver"); let bt_observer = AsanBacktraceObserver::new("AsanBacktraceObserver");
// Feedback to rate the interestingness of an input, obtained by ANDing the interestingness of both feedbacks // Feedback to rate the interestingness of an input, obtained by ANDing the interestingness of both feedbacks
let mut feedback = MaxMapFeedback::new(&observer); let mut feedback = MaxMapFeedback::new(&observer);
@ -48,7 +48,7 @@ pub fn main() {
// A feedback to choose if an input is a solution or not // A feedback to choose if an input is a solution or not
let mut objective = feedback_and!( let mut objective = feedback_and!(
CrashFeedback::new(), CrashFeedback::new(),
NewHashFeedback::<ASANBacktraceObserver>::new(&bt_observer) NewHashFeedback::<AsanBacktraceObserver>::new(&bt_observer)
); );
// let mut objective = CrashFeedback::new(); // let mut objective = CrashFeedback::new();
@ -76,7 +76,7 @@ pub fn main() {
// such as the notification of the addition of a new item to the corpus // such as the notification of the addition of a new item to the corpus
let mut mgr = SimpleEventManager::new(mon); let mut mgr = SimpleEventManager::new(mon);
// A queue policy to get testcasess from the corpus // A queue policy to get testcases from the corpus
let scheduler = QueueScheduler::new(); let scheduler = QueueScheduler::new();
// A fuzzer with feedbacks and a corpus scheduler // A fuzzer with feedbacks and a corpus scheduler
@ -108,12 +108,7 @@ pub fn main() {
} }
} }
let mut executor = MyExecutor { shmem_id }.into_executor( let mut executor = MyExecutor { shmem_id }.into_executor(tuple_list!(observer, bt_observer));
tuple_list!(observer, bt_observer),
None,
None,
Some("ASANBacktraceObserver".to_string()),
);
// Generator of printable bytearrays of max size 32 // Generator of printable bytearrays of max size 32
let mut generator = RandPrintablesGenerator::new(32); let mut generator = RandPrintablesGenerator::new(32);

View File

@ -22,7 +22,7 @@ use libafl::{
inputs::BytesInput, inputs::BytesInput,
monitors::SimpleMonitor, monitors::SimpleMonitor,
mutators::scheduled::{havoc_mutations, StdScheduledMutator}, mutators::scheduled::{havoc_mutations, StdScheduledMutator},
observers::{ASANBacktraceObserver, ConstMapObserver, HitcountsMapObserver}, observers::{AsanBacktraceObserver, ConstMapObserver, HitcountsMapObserver},
schedulers::QueueScheduler, schedulers::QueueScheduler,
stages::mutational::StdMutationalStage, stages::mutational::StdMutationalStage,
state::StdState, state::StdState,
@ -50,7 +50,7 @@ pub fn main() {
shmem_map, shmem_map,
)); ));
let bt_observer = ASANBacktraceObserver::new("ASANBacktraceObserver"); let bt_observer = AsanBacktraceObserver::new("AsanBacktraceObserver");
// Feedback to rate the interestingness of an input // Feedback to rate the interestingness of an input
// This one is composed by two Feedbacks in OR // This one is composed by two Feedbacks in OR

View File

@ -224,7 +224,7 @@ fn fuzz(
// Create a concolic trace // Create a concolic trace
ConcolicTracingStage::new( ConcolicTracingStage::new(
TracingStage::new( TracingStage::new(
MyCommandConfigurator::default().into_executor(tuple_list!(concolic_observer), None, None, None) MyCommandConfigurator::default().into_executor(tuple_list!(concolic_observer))
), ),
concolic_observer_name, concolic_observer_name,
), ),

View File

@ -36,7 +36,7 @@ pub fn read_time_counter() -> u64 {
#[cfg(target_arch = "aarch64")] #[cfg(target_arch = "aarch64")]
#[must_use] #[must_use]
pub fn read_time_counter() -> u64 { pub fn read_time_counter() -> u64 {
let mut v: u64 = 0; let mut v: u64;
unsafe { unsafe {
// TODO pushing a change in core::arch::aarch64 ? // TODO pushing a change in core::arch::aarch64 ?
asm!("mrs {v}, cntvct_el0", v = out(reg) v); asm!("mrs {v}, cntvct_el0", v = out(reg) v);

View File

@ -27,7 +27,7 @@ use crate::{
AsSlice, AsSlice,
}, },
inputs::HasTargetBytes, inputs::HasTargetBytes,
observers::{ASANBacktraceObserver, ObserversTuple, StdErrObserver, StdOutObserver}, observers::ObserversTuple,
std::borrow::ToOwned, std::borrow::ToOwned,
}; };
#[cfg(feature = "std")] #[cfg(feature = "std")]
@ -45,10 +45,10 @@ enum InputLocation {
}, },
/// Deliver input via `StdIn` /// Deliver input via `StdIn`
StdIn, StdIn,
/// Deliver the iniput via the specified [`InputFile`] /// Deliver the input via the specified [`InputFile`]
/// You can use specify [`InputFile::create(INPUTFILE_STD)`] to use a default filename. /// You can use specify [`InputFile::create(INPUTFILE_STD)`] to use a default filename.
File { File {
/// The fiel to write input to. The target should read input from this location. /// The file to write input to. The target should read input from this location.
out_file: InputFile, out_file: InputFile,
}, },
} }
@ -79,7 +79,6 @@ pub struct StdCommandConfigurator {
debug_child: bool, debug_child: bool,
has_stdout_observer: bool, has_stdout_observer: bool,
has_stderr_observer: bool, has_stderr_observer: bool,
has_asan_observer: bool,
/// true: input gets delivered via stdink /// true: input gets delivered via stdink
input_location: InputLocation, input_location: InputLocation,
/// The Command to execute /// The Command to execute
@ -104,7 +103,7 @@ impl CommandConfigurator for StdCommandConfigurator {
if self.has_stdout_observer { if self.has_stdout_observer {
cmd.stdout(Stdio::piped()); cmd.stdout(Stdio::piped());
} }
if self.has_stderr_observer || self.has_asan_observer { if self.has_stderr_observer {
cmd.stderr(Stdio::piped()); cmd.stderr(Stdio::piped());
} }
@ -150,23 +149,16 @@ impl CommandConfigurator for StdCommandConfigurator {
/// A `CommandExecutor` is a wrapper around [`std::process::Command`] to execute a target as a child process. /// A `CommandExecutor` is a wrapper around [`std::process::Command`] to execute a target as a child process.
/// Construct a `CommandExecutor` by implementing [`CommandConfigurator`] for a type of your choice and calling [`CommandConfigurator::into_executor`] on it. /// Construct a `CommandExecutor` by implementing [`CommandConfigurator`] for a type of your choice and calling [`CommandConfigurator::into_executor`] on it.
/// Instead, you can use [`CommandExecutor::builder()`] to construct a [`CommandExecutor`] backed by a [`StdCommandConfigurator`]. /// Instead, you can also use [`CommandExecutor::builder()`] to construct a [`CommandExecutor`] backed by a [`StdCommandConfigurator`].
pub struct CommandExecutor<EM, I, OT, S, T, Z> pub struct CommandExecutor<EM, I, OT, S, T, Z>
where where
T: Debug, T: Debug,
OT: Debug, OT: Debug,
{ {
/// The wrapped comand configurer /// The wrapped command configurer
configurer: T, configurer: T,
/// The obsevers used by this executor
observers: OT, observers: OT,
/// cache if the AsanBacktraceObserver is present
has_asan_observer: Option<String>,
/// If set, we found a [`StdErrObserver`] in the observer list.
/// Pipe the child's `stderr` instead of closing it.
has_stdout_observer: Option<String>,
/// If set, we found a [`StdOutObserver`] in the observer list
/// Pipe the child's `stdout` instead of closing it.
has_stderr_observer: Option<String>,
phantom: PhantomData<(EM, I, S, Z)>, phantom: PhantomData<(EM, I, S, Z)>,
} }
@ -215,18 +207,15 @@ where
impl<EM, I, OT, S, Z> CommandExecutor<EM, I, OT, S, StdCommandConfigurator, Z> impl<EM, I, OT, S, Z> CommandExecutor<EM, I, OT, S, StdCommandConfigurator, Z>
where where
OT: MatchName + Debug, OT: MatchName + Debug + ObserversTuple<I, S>,
{ {
/// Creates a new `CommandExecutor`. /// Creates a new `CommandExecutor`.
/// Instead of parsing the Command for `@@`, it will /// Instead of parsing the Command for `@@`, it will
pub fn from_cmd_with_file<P>( pub fn from_cmd_with_file<P>(
cmd: &Command, cmd: &Command,
debug_child: bool, debug_child: bool,
observers: OT, mut observers: OT,
path: P, path: P,
has_stdout_observer: Option<String>,
has_stderr_observer: Option<String>,
has_asan_observer: Option<String>,
) -> Result<Self, Error> ) -> Result<Self, Error>
where where
P: AsRef<Path>, P: AsRef<Path>,
@ -238,43 +227,37 @@ where
} }
command.stdin(Stdio::null()); command.stdin(Stdio::null());
if has_stdout_observer.is_some() { let has_stdout_observer = observers.observes_stdout();
if has_stdout_observer {
command.stdout(Stdio::piped()); command.stdout(Stdio::piped());
} }
if has_stderr_observer.is_some() || has_asan_observer.is_some() { let has_stderr_observer = observers.observes_stderr();
if has_stderr_observer {
command.stderr(Stdio::piped()); command.stderr(Stdio::piped());
} }
Ok(Self { Ok(Self {
observers, observers,
configurer: StdCommandConfigurator { configurer: StdCommandConfigurator {
input_location: InputLocation::File { input_location: InputLocation::File {
out_file: InputFile::create(path)?, out_file: InputFile::create(path)?,
}, },
command, command,
debug_child, debug_child,
has_stdout_observer: has_stdout_observer.is_some(),
has_stderr_observer: has_stderr_observer.is_some(),
has_asan_observer: has_asan_observer.is_some(),
},
has_stdout_observer, has_stdout_observer,
has_stderr_observer, has_stderr_observer,
has_asan_observer, },
phantom: PhantomData, phantom: PhantomData,
}) })
} }
/// Parses an AFL-like comandline, replacing `@@` with the input file. /// Parses an AFL-like commandline, replacing `@@` with the input file.
/// If no `@@` was found, will use stdin for input. /// If no `@@` was found, will use stdin for input.
/// The arg 0 is the program. /// The arg 0 is the program.
pub fn parse_afl_cmdline<IT, O>( pub fn parse_afl_cmdline<IT, O>(
args: IT, args: IT,
observers: OT, observers: OT,
debug_child: bool, debug_child: bool,
has_stdout_observer: Option<String>,
has_stderr_observer: Option<String>,
has_asan_observer: Option<String>,
) -> Result<Self, Error> ) -> Result<Self, Error>
where where
IT: IntoIterator<Item = O>, IT: IntoIterator<Item = O>,
@ -283,16 +266,6 @@ where
let mut atat_at = None; let mut atat_at = None;
let mut builder = CommandExecutorBuilder::new(); let mut builder = CommandExecutorBuilder::new();
builder.debug_child(debug_child); builder.debug_child(debug_child);
if let Some(name) = has_stdout_observer {
builder.stdout_observer(name);
}
if let Some(name) = has_stderr_observer {
builder.stderr_observer(name);
}
if let Some(name) = has_asan_observer {
builder.asan_observer(name);
}
let afl_delim = OsStr::new("@@"); let afl_delim = OsStr::new("@@");
for (pos, arg) in args.into_iter().enumerate() { for (pos, arg) in args.into_iter().enumerate() {
@ -326,7 +299,7 @@ impl<EM, I, OT, S, T, Z> Executor<EM, I, S, Z> for CommandExecutor<EM, I, OT, S,
where where
I: Input + HasTargetBytes, I: Input + HasTargetBytes,
T: CommandConfigurator, T: CommandConfigurator,
OT: Debug + MatchName, OT: Debug + MatchName + ObserversTuple<I, S>,
T: Debug, T: Debug,
{ {
fn run_target( fn run_target(
@ -361,37 +334,23 @@ where
} }
}; };
if self.has_asan_observer.is_some() || self.has_stderr_observer.is_some() { if self.observers.observes_stderr() {
let mut stderr = String::new(); let mut stderr = String::new();
child.stderr.as_mut().ok_or_else(|| { child.stderr.as_mut().ok_or_else(|| {
Error::illegal_state( Error::illegal_state(
"Observer tries to read stderr, but stderr was not `Stdio::pipe` in CommandExecutor", "Observer tries to read stderr, but stderr was not `Stdio::pipe` in CommandExecutor",
) )
})?.read_to_string(&mut stderr)?; })?.read_to_string(&mut stderr)?;
if let Some(name) = self.has_asan_observer.as_ref() { self.observers.observe_stderr(&stderr);
self.observers
.match_name_mut::<ASANBacktraceObserver>(name)
.unwrap()
.parse_asan_output(&stderr);
} }
if let Some(name) = self.has_stderr_observer.as_ref() { if self.observers.observes_stdout() {
self.observers
.match_name_mut::<StdErrObserver>(name)
.unwrap()
.stderr = Some(stderr);
}
}
if let Some(name) = self.has_stdout_observer.as_ref() {
let mut stdout = String::new(); let mut stdout = String::new();
child.stdout.as_mut().ok_or_else(|| { child.stdout.as_mut().ok_or_else(|| {
Error::illegal_state( Error::illegal_state(
"Observer tries to read stdout, but stdout was not `Stdio::pipe` in CommandExecutor", "Observer tries to read stdout, but stdout was not `Stdio::pipe` in CommandExecutor",
) )
})?.read_to_string(&mut stdout)?; })?.read_to_string(&mut stdout)?;
self.observers self.observers.observe_stdout(&stdout);
.match_name_mut::<StdOutObserver>(name)
.unwrap()
.stdout = Some(stdout);
} }
res res
@ -419,9 +378,6 @@ pub struct CommandExecutorBuilder {
input_location: InputLocation, input_location: InputLocation,
cwd: Option<PathBuf>, cwd: Option<PathBuf>,
envs: Vec<(OsString, OsString)>, envs: Vec<(OsString, OsString)>,
has_stdout_observer: Option<String>,
has_stderr_observer: Option<String>,
has_asan_observer: Option<String>,
} }
impl Default for CommandExecutorBuilder { impl Default for CommandExecutorBuilder {
@ -441,30 +397,9 @@ impl CommandExecutorBuilder {
cwd: None, cwd: None,
envs: vec![], envs: vec![],
debug_child: false, debug_child: false,
has_stdout_observer: None,
has_stderr_observer: None,
has_asan_observer: None,
} }
} }
/// Set the stdout observer name
pub fn stdout_observer(&mut self, name: String) -> &mut Self {
self.has_stdout_observer = Some(name);
self
}
/// Set the stderr observer name
pub fn stderr_observer(&mut self, name: String) -> &mut Self {
self.has_stderr_observer = Some(name);
self
}
/// Set the asan observer name
pub fn asan_observer(&mut self, name: String) -> &mut Self {
self.has_asan_observer = Some(name);
self
}
/// Set the binary to execute /// Set the binary to execute
/// This option is required. /// This option is required.
pub fn program<O>(&mut self, program: O) -> &mut Self pub fn program<O>(&mut self, program: O) -> &mut Self
@ -573,13 +508,13 @@ impl CommandExecutorBuilder {
self self
} }
/// Builds the `ComandExecutor` /// Builds the `CommandExecutor`.
pub fn build<EM, I, OT, S, Z>( pub fn build<EM, I, OT, S, Z>(
&self, &self,
observers: OT, mut observers: OT,
) -> Result<CommandExecutor<EM, I, OT, S, StdCommandConfigurator, Z>, Error> ) -> Result<CommandExecutor<EM, I, OT, S, StdCommandConfigurator, Z>, Error>
where where
OT: Debug + MatchName, OT: Debug + MatchName + ObserversTuple<I, S>,
{ {
let program = if let Some(program) = &self.program { let program = if let Some(program) = &self.program {
program program
@ -610,28 +545,22 @@ impl CommandExecutorBuilder {
command.stdout(Stdio::null()); command.stdout(Stdio::null());
command.stderr(Stdio::null()); command.stderr(Stdio::null());
} }
if self.has_stderr_observer.is_some() || self.has_asan_observer.is_some() { if observers.observes_stdout() {
// we need stderr for ASANBackt
command.stderr(Stdio::piped());
}
if self.has_stdout_observer.is_some() {
command.stdout(Stdio::piped()); command.stdout(Stdio::piped());
} }
if observers.observes_stderr() {
// we need stderr for `AsanBacktaceObserver`, and others
command.stderr(Stdio::piped());
}
let configurator = StdCommandConfigurator { let configurator = StdCommandConfigurator {
debug_child: self.debug_child, debug_child: self.debug_child,
has_stdout_observer: self.has_stdout_observer.is_some(), has_stdout_observer: observers.observes_stdout(),
has_stderr_observer: self.has_stderr_observer.is_some(), has_stderr_observer: observers.observes_stderr(),
has_asan_observer: self.has_asan_observer.is_some(),
input_location: self.input_location.clone(), input_location: self.input_location.clone(),
command, command,
}; };
Ok(configurator.into_executor::<EM, I, OT, S, Z>( Ok(configurator.into_executor::<EM, I, OT, S, Z>(observers))
observers,
self.has_stdout_observer.clone(),
self.has_stderr_observer.clone(),
self.has_asan_observer.clone(),
))
} }
} }
@ -663,7 +592,7 @@ impl CommandExecutorBuilder {
/// } /// }
/// ///
/// fn make_executor<EM, I: Input + HasTargetBytes, S, Z>() -> impl Executor<EM, I, S, Z> { /// fn make_executor<EM, I: Input + HasTargetBytes, S, Z>() -> impl Executor<EM, I, S, Z> {
/// MyExecutor.into_executor((), None, None, None) /// MyExecutor.into_executor(())
/// } /// }
/// ``` /// ```
@ -675,21 +604,13 @@ pub trait CommandConfigurator: Sized + Debug {
I: Input + HasTargetBytes; I: Input + HasTargetBytes;
/// Create an `Executor` from this `CommandConfigurator`. /// Create an `Executor` from this `CommandConfigurator`.
fn into_executor<EM, I, OT, S, Z>( /// It will observe the outputs with the respective given observer name.
self, fn into_executor<EM, I, OT, S, Z>(self, observers: OT) -> CommandExecutor<EM, I, OT, S, Self, Z>
observers: OT,
has_stdout_observer: Option<String>,
has_stderr_observer: Option<String>,
has_asan_observer: Option<String>,
) -> CommandExecutor<EM, I, OT, S, Self, Z>
where where
OT: Debug + MatchName, OT: Debug + MatchName,
{ {
CommandExecutor { CommandExecutor {
observers, observers,
has_asan_observer,
has_stdout_observer,
has_stderr_observer,
configurer: self, configurer: self,
phantom: PhantomData, phantom: PhantomData,
} }
@ -740,14 +661,8 @@ mod tests {
println!("{status}"); println!("{status}");
})); }));
let mut executor = CommandExecutor::parse_afl_cmdline( let mut executor =
&["file".to_string(), "@@".to_string()], CommandExecutor::parse_afl_cmdline(&["file".to_string(), "@@".to_string()], (), true)
(),
true,
None,
None,
None,
)
.unwrap(); .unwrap();
executor executor
.run_target( .run_target(

View File

@ -146,6 +146,29 @@ where
.as_mut() .as_mut()
.post_exec_child_all(state, input, exit_kind) .post_exec_child_all(state, input, exit_kind)
} }
/// Returns true if a `stdout` observer was added to the list
#[inline]
fn observes_stdout(&mut self) -> bool {
self.primary.as_mut().observes_stdout() || self.secondary.as_mut().observes_stdout()
}
/// Returns true if a `stderr` observer was added to the list
#[inline]
fn observes_stderr(&mut self) -> bool {
self.primary.as_mut().observes_stderr() || self.secondary.as_mut().observes_stderr()
}
/// Runs `observe_stdout` for all stdout observers in the list
fn observe_stdout(&mut self, stdout: &str) {
self.primary.as_mut().observe_stderr(stdout);
self.secondary.as_mut().observe_stderr(stdout);
}
/// Runs `observe_stderr` for all stderr observers in the list
fn observe_stderr(&mut self, stderr: &str) {
self.primary.as_mut().observe_stderr(stderr);
self.secondary.as_mut().observe_stderr(stderr);
}
} }
impl<A, B> MatchName for ProxyObserversTuple<A, B> impl<A, B> MatchName for ProxyObserversTuple<A, B>

View File

@ -33,7 +33,7 @@ use crate::{
executors::{Executor, ExitKind, HasObservers}, executors::{Executor, ExitKind, HasObservers},
inputs::{HasTargetBytes, Input}, inputs::{HasTargetBytes, Input},
mutators::Tokens, mutators::Tokens,
observers::{get_asan_runtime_flags_with_log_path, ASANBacktraceObserver, ObserversTuple}, observers::{get_asan_runtime_flags_with_log_path, AsanBacktraceObserver, ObserversTuple},
Error, Error,
}; };
@ -966,13 +966,13 @@ where
if self.has_asan_observer.is_none() { if self.has_asan_observer.is_none() {
self.has_asan_observer = Some( self.has_asan_observer = Some(
self.observers() self.observers()
.match_name::<ASANBacktraceObserver>("ASANBacktraceObserver") .match_name::<AsanBacktraceObserver>("AsanBacktraceObserver")
.is_some(), .is_some(),
); );
} }
if self.has_asan_observer.unwrap() { if self.has_asan_observer.unwrap() {
self.observers_mut() self.observers_mut()
.match_name_mut::<ASANBacktraceObserver>("ASANBacktraceObserver") .match_name_mut::<AsanBacktraceObserver>("AsanBacktraceObserver")
.unwrap() .unwrap()
.parse_asan_output_from_asan_log_file(pid)?; .parse_asan_output_from_asan_log_file(pid)?;
} }

View File

@ -85,6 +85,28 @@ pub trait Observer<I, S>: Named + Debug {
) -> Result<(), Error> { ) -> Result<(), Error> {
Ok(()) Ok(())
} }
/// If this observer observes `stdout`
#[inline]
fn observes_stdout(&mut self) -> bool {
false
}
/// If this observer observes `stderr`
#[inline]
fn observes_stderr(&mut self) -> bool {
false
}
/// React to new `stdout`
/// To use this, always return `true` from `observes_stdout`
#[inline]
#[allow(unused_variables)]
fn observe_stdout(&mut self, stdout: &str) {}
/// React to new `stderr`
/// To use this, always return `true` from `observes_stderr`
#[inline]
#[allow(unused_variables)]
fn observe_stderr(&mut self, stderr: &str) {}
} }
/// A haskell-style tuple of observers /// A haskell-style tuple of observers
@ -110,6 +132,16 @@ pub trait ObserversTuple<I, S>: MatchName + Debug {
input: &I, input: &I,
exit_kind: &ExitKind, exit_kind: &ExitKind,
) -> Result<(), Error>; ) -> Result<(), Error>;
/// Returns true if a `stdout` observer was added to the list
fn observes_stdout(&mut self) -> bool;
/// Returns true if a `stderr` observer was added to the list
fn observes_stderr(&mut self) -> bool;
/// Runs `observe_stdout` for all stdout observers in the list
fn observe_stdout(&mut self, stdout: &str);
/// Runs `observe_stderr` for all stderr observers in the list
fn observe_stderr(&mut self, stderr: &str);
} }
impl<I, S> ObserversTuple<I, S> for () { impl<I, S> ObserversTuple<I, S> for () {
@ -138,6 +170,26 @@ impl<I, S> ObserversTuple<I, S> for () {
) -> Result<(), Error> { ) -> Result<(), Error> {
Ok(()) Ok(())
} }
/// Returns true if a `stdout` observer was added to the list
#[inline]
fn observes_stdout(&mut self) -> bool {
false
}
/// Returns true if a `stderr` observer was added to the list
#[inline]
fn observes_stderr(&mut self) -> bool {
false
}
/// Runs `observe_stdout` for all stdout observers in the list
#[inline]
#[allow(unused_variables)]
fn observe_stdout(&mut self, stdout: &str) {}
/// Runs `observe_stderr` for all stderr observers in the list
#[inline]
#[allow(unused_variables)]
fn observe_stderr(&mut self, stderr: &str) {}
} }
impl<Head, Tail, I, S> ObserversTuple<I, S> for (Head, Tail) impl<Head, Tail, I, S> ObserversTuple<I, S> for (Head, Tail)
@ -174,9 +226,31 @@ where
self.0.post_exec_child(state, input, exit_kind)?; self.0.post_exec_child(state, input, exit_kind)?;
self.1.post_exec_child_all(state, input, exit_kind) self.1.post_exec_child_all(state, input, exit_kind)
} }
/// Returns true if a `stdout` observer was added to the list
#[inline]
fn observes_stdout(&mut self) -> bool {
self.0.observes_stdout() || self.1.observes_stdout()
}
/// Returns true if a `stderr` observer was added to the list
#[inline]
fn observes_stderr(&mut self) -> bool {
self.0.observes_stderr() || self.1.observes_stderr()
}
/// Runs `observe_stdout` for all stdout observers in the list
#[inline]
fn observe_stdout(&mut self, stdout: &str) {
self.0.observe_stdout(stdout);
}
/// Runs `observe_stderr` for all stderr observers in the list
#[inline]
fn observe_stderr(&mut self, stderr: &str) {
self.1.observe_stderr(stderr);
}
} }
/// A trait for obervers with a hash field /// A trait for [`Observer`]`s` with a hash field
pub trait ObserverWithHashField { pub trait ObserverWithHashField {
/// get the value of the hash field /// get the value of the hash field
fn hash(&self) -> &Option<u64>; fn hash(&self) -> &Option<u64>;
@ -876,6 +950,23 @@ pub mod pybind {
} }
Ok(()) Ok(())
} }
// TODO: expose stdout/stderr to python
#[inline]
fn observes_stdout(&mut self) -> bool {
false
}
#[inline]
fn observes_stderr(&mut self) -> bool {
false
}
#[inline]
fn observe_stderr(&mut self, _: &str) {}
#[inline]
fn observe_stdout(&mut self, _: &str) {}
} }
impl MatchName for PythonObserversTuple { impl MatchName for PythonObserversTuple {

View File

@ -165,12 +165,12 @@ pub fn get_asan_runtime_flags() -> String {
/// An observer looking at the backtrace of target command using ASAN output /// An observer looking at the backtrace of target command using ASAN output
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ASANBacktraceObserver { pub struct AsanBacktraceObserver {
observer_name: String, observer_name: String,
hash: Option<u64>, hash: Option<u64>,
} }
impl ASANBacktraceObserver { impl AsanBacktraceObserver {
/// Creates a new [`BacktraceObserver`] with the given name. /// Creates a new [`BacktraceObserver`] with the given name.
#[must_use] #[must_use]
pub fn new(observer_name: &str) -> Self { pub fn new(observer_name: &str) -> Self {
@ -216,7 +216,7 @@ impl ASANBacktraceObserver {
} }
} }
impl ObserverWithHashField for ASANBacktraceObserver { impl ObserverWithHashField for AsanBacktraceObserver {
/// Gets the hash value of this observer. /// Gets the hash value of this observer.
#[must_use] #[must_use]
fn hash(&self) -> &Option<u64> { fn hash(&self) -> &Option<u64> {
@ -234,13 +234,13 @@ impl ObserverWithHashField for ASANBacktraceObserver {
} }
} }
impl Default for ASANBacktraceObserver { impl Default for AsanBacktraceObserver {
fn default() -> Self { fn default() -> Self {
Self::new("ASANBacktraceObserver") Self::new("AsanBacktraceObserver")
} }
} }
impl<I, S> Observer<I, S> for ASANBacktraceObserver impl<I, S> Observer<I, S> for AsanBacktraceObserver
where where
I: Debug, I: Debug,
{ {
@ -256,9 +256,20 @@ where
) -> Result<(), Error> { ) -> Result<(), Error> {
Ok(()) Ok(())
} }
/// Do nothing on new `stderr`
#[inline]
fn observes_stderr(&mut self) -> bool {
true
}
/// Do nothing on new `stderr`
fn observe_stderr(&mut self, stderr: &str) {
self.parse_asan_output(stderr);
}
} }
impl Named for ASANBacktraceObserver { impl Named for AsanBacktraceObserver {
fn name(&self) -> &str { fn name(&self) -> &str {
&self.observer_name &self.observer_name
} }

View File

@ -1,5 +1,5 @@
//! The [`StdOutObserver`] and [`StdErrObserver`] observers look at the stdout of a program //! The [`StdOutObserver`] and [`StdErrObserver`] observers look at the stdout of a program
//! The executor must explicitely support these observers. //! The executor must explicitly support these observers.
//! For example, they are supported on the [`crate::executors::CommandExecutor`]. //! For example, they are supported on the [`crate::executors::CommandExecutor`].
use alloc::string::String; use alloc::string::String;
@ -27,7 +27,16 @@ impl StdOutObserver {
} }
} }
impl<I, S> Observer<I, S> for StdOutObserver {} impl<I, S> Observer<I, S> for StdOutObserver {
#[inline]
fn observes_stdout(&mut self) -> bool {
true
}
/// React to new `stdout`
fn observe_stdout(&mut self, stdout: &str) {
self.stdout = Some(stdout.into());
}
}
impl Named for StdOutObserver { impl Named for StdOutObserver {
fn name(&self) -> &str { fn name(&self) -> &str {
@ -54,7 +63,17 @@ impl StdErrObserver {
} }
} }
impl<I, S> Observer<I, S> for StdErrObserver {} impl<I, S> Observer<I, S> for StdErrObserver {
#[inline]
fn observes_stderr(&mut self) -> bool {
true
}
/// Do nothing on new `stderr`
fn observe_stderr(&mut self, stderr: &str) {
self.stderr = Some(stderr.into());
}
}
impl Named for StdErrObserver { impl Named for StdErrObserver {
fn name(&self) -> &str { fn name(&self) -> &str {