Allow two different observers for DiffExecutor (#843)

* DifferentialExecutor for CommandExecutor along with StdIO observer

* format

* fix CI issues

* fix format and unit test

* fix documentation

* allow three structs and doc only for linux

* resolve documentation test failure

* minor

* running fmt_all.sh

* into_executor() takes 4 params, not just 1

Co-authored-by: Dominik Maier <domenukk@gmail.com>
This commit is contained in:
Alessandro Mantovani 2022-10-23 01:59:40 +02:00 committed by GitHub
parent 64bc5d5bdb
commit 0307dadcd1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 247 additions and 84 deletions

View File

@ -108,7 +108,12 @@ pub fn main() {
} }
} }
let mut executor = MyExecutor { shmem_id }.into_executor(tuple_list!(observer, bt_observer)); let mut executor = MyExecutor { shmem_id }.into_executor(
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

@ -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)) MyCommandConfigurator::default().into_executor(tuple_list!(concolic_observer), None, None, None)
), ),
concolic_observer_name, concolic_observer_name,
), ),

View File

@ -1,6 +1,5 @@
//! The command executor executes a sub program for each run //! The command executor executes a sub program for each run
#[cfg(feature = "std")] use alloc::{string::String, vec::Vec};
use alloc::{borrow::ToOwned, string::String, vec::Vec};
use core::{ use core::{
fmt::{self, Debug, Formatter}, fmt::{self, Debug, Formatter},
marker::PhantomData, marker::PhantomData,
@ -29,6 +28,7 @@ use crate::{
}, },
inputs::HasTargetBytes, inputs::HasTargetBytes,
observers::{ASANBacktraceObserver, ObserversTuple, StdErrObserver, StdOutObserver}, observers::{ASANBacktraceObserver, ObserversTuple, StdErrObserver, StdOutObserver},
std::borrow::ToOwned,
}; };
#[cfg(feature = "std")] #[cfg(feature = "std")]
use crate::{inputs::Input, Error}; use crate::{inputs::Input, Error};
@ -37,19 +37,19 @@ use crate::{inputs::Input, Error};
/// `StdIn`: The traget reads from stdin /// `StdIn`: The traget reads from stdin
/// `File`: The target reads from the specified [`InputFile`] /// `File`: The target reads from the specified [`InputFile`]
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum InputLocation { enum InputLocation {
/// Mutate a command line argument to deliver an input /// Mutate a commandline argument to deliver an input
Arg { Arg {
/// The offset of the argument to mutate /// The offset of the argument to mutate
argnum: usize, argnum: usize,
}, },
/// Deliver input via `StdIn` /// Deliver input via `StdIn`
StdIn, StdIn,
/// Deliver the input via the specified [`InputFile`] /// Deliver the iniput 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 file to write input to. The target should read input from this location. /// The fiel to write input to. The target should read input from this location.
input_file: InputFile, out_file: InputFile,
}, },
} }
@ -71,11 +71,15 @@ fn clone_command(cmd: &Command) -> Command {
/// A simple Configurator that takes the most common parameters /// A simple Configurator that takes the most common parameters
/// Writes the input either to stdio or to a file /// Writes the input either to stdio or to a file
/// Use [`CommandExecutor::builder()`] to use this configurator. /// Use [`CommandExecutor::builder()`] to use this configurator.
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug)] #[derive(Debug)]
pub struct StdCommandConfigurator { pub struct StdCommandConfigurator {
/// If set to true, the child output will remain visible /// If set to true, the child output will remain visible
/// By default, the child output is hidden to increase execution speed /// By default, the child output is hidden to increase execution speed
debug_child: bool, debug_child: bool,
has_stdout_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
@ -97,6 +101,13 @@ impl CommandConfigurator for StdCommandConfigurator {
cmd.stderr(Stdio::null()); cmd.stderr(Stdio::null());
} }
if self.has_stdout_observer {
cmd.stdout(Stdio::piped());
}
if self.has_stderr_observer || self.has_asan_observer {
cmd.stderr(Stdio::piped());
}
for (i, arg) in args.enumerate() { for (i, arg) in args.enumerate() {
if i == *argnum { if i == *argnum {
debug_assert_eq!(arg, "DUMMY"); debug_assert_eq!(arg, "DUMMY");
@ -129,8 +140,8 @@ impl CommandConfigurator for StdCommandConfigurator {
drop(stdin); drop(stdin);
Ok(handle) Ok(handle)
} }
InputLocation::File { input_file } => { InputLocation::File { out_file } => {
input_file.write_buf(input.target_bytes().as_slice())?; out_file.write_buf(input.target_bytes().as_slice())?;
Ok(self.command.spawn()?) Ok(self.command.spawn()?)
} }
} }
@ -149,13 +160,13 @@ where
configurer: T, configurer: T,
observers: OT, observers: OT,
/// cache if the AsanBacktraceObserver is present /// cache if the AsanBacktraceObserver is present
has_asan_observer: bool, has_asan_observer: Option<String>,
/// If set, we found a [`StdErrObserver`] in the observer list. /// If set, we found a [`StdErrObserver`] in the observer list.
/// Pipe the child's `stderr` instead of closing it. /// Pipe the child's `stderr` instead of closing it.
has_stdout_observer: bool, has_stdout_observer: Option<String>,
/// If set, we found a [`StdOutObserver`] in the observer list /// If set, we found a [`StdOutObserver`] in the observer list
/// Pipe the child's `stdout` instead of closing it. /// Pipe the child's `stdout` instead of closing it.
has_stderr_observer: bool, has_stderr_observer: Option<String>,
phantom: PhantomData<(EM, I, S, Z)>, phantom: PhantomData<(EM, I, S, Z)>,
} }
@ -213,6 +224,9 @@ where
debug_child: bool, debug_child: bool,
observers: OT, 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>,
@ -224,35 +238,29 @@ where
} }
command.stdin(Stdio::null()); command.stdin(Stdio::null());
let has_stdout_observer = observers if has_stdout_observer.is_some() {
.match_name::<StdOutObserver>("StdOutObserver") command.stdout(Stdio::piped());
.is_some();
if has_stdout_observer {
command.stderr(Stdio::piped());
} }
if has_stderr_observer.is_some() || has_asan_observer.is_some() {
let has_stderr_observer = observers
.match_name::<StdErrObserver>("StdErrObserver")
.is_some();
let has_asan_observer = observers
.match_name::<ASANBacktraceObserver>("ASANBacktraceObserver")
.is_some();
if has_stderr_observer || has_asan_observer {
command.stderr(Stdio::piped()); command.stderr(Stdio::piped());
} }
Ok(Self { Ok(Self {
observers, observers,
has_asan_observer,
configurer: StdCommandConfigurator { configurer: StdCommandConfigurator {
input_location: InputLocation::File { input_location: InputLocation::File {
input_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,
}) })
} }
@ -264,6 +272,9 @@ where
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>,
@ -272,6 +283,16 @@ 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() {
@ -340,27 +361,27 @@ where
} }
}; };
if self.has_asan_observer || self.has_stderr_observer { if self.has_asan_observer.is_some() || self.has_stderr_observer.is_some() {
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 self.has_asan_observer { if let Some(name) = self.has_asan_observer.as_ref() {
self.observers self.observers
.match_name_mut::<ASANBacktraceObserver>("ASANBacktraceObserver") .match_name_mut::<ASANBacktraceObserver>(name)
.unwrap() .unwrap()
.parse_asan_output(&stderr); .parse_asan_output(&stderr);
} }
if self.has_stderr_observer { if let Some(name) = self.has_stderr_observer.as_ref() {
self.observers self.observers
.match_name_mut::<StdErrObserver>("StdErrObserver") .match_name_mut::<StdErrObserver>(name)
.unwrap() .unwrap()
.stderr = Some(stderr); .stderr = Some(stderr);
} }
} }
if self.has_stdout_observer { 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(
@ -368,7 +389,7 @@ where
) )
})?.read_to_string(&mut stdout)?; })?.read_to_string(&mut stdout)?;
self.observers self.observers
.match_name_mut::<StdOutObserver>("StdOutObserver") .match_name_mut::<StdOutObserver>(name)
.unwrap() .unwrap()
.stdout = Some(stdout); .stdout = Some(stdout);
} }
@ -398,6 +419,9 @@ 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 {
@ -417,9 +441,30 @@ 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
@ -466,9 +511,9 @@ impl CommandExecutorBuilder {
/// and adds the filename as arg to at the current position. /// and adds the filename as arg to at the current position.
pub fn arg_input_file<P: AsRef<Path>>(&mut self, path: P) -> &mut Self { pub fn arg_input_file<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
self.arg(path.as_ref()); self.arg(path.as_ref());
let input_file_std = InputFile::create(path.as_ref()).unwrap(); let out_file_std = InputFile::create(path.as_ref()).unwrap();
self.input(InputLocation::File { self.input(InputLocation::File {
input_file: input_file_std, out_file: out_file_std,
}); });
self self
} }
@ -565,29 +610,28 @@ impl CommandExecutorBuilder {
command.stdout(Stdio::null()); command.stdout(Stdio::null());
command.stderr(Stdio::null()); command.stderr(Stdio::null());
} }
if observers if self.has_stderr_observer.is_some() || self.has_asan_observer.is_some() {
.match_name::<ASANBacktraceObserver>("ASANBacktraceObserver")
.is_some()
|| observers
.match_name::<StdErrObserver>("StdErrObserver")
.is_some()
{
// we need stderr for ASANBackt // we need stderr for ASANBackt
command.stderr(Stdio::piped()); command.stderr(Stdio::piped());
} }
if observers if self.has_stdout_observer.is_some() {
.match_name::<StdOutObserver>("StdOutObserver")
.is_some()
{
command.stdout(Stdio::piped()); command.stdout(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_stderr_observer: self.has_stderr_observer.is_some(),
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(observers)) Ok(configurator.into_executor::<EM, I, OT, S, Z>(
observers,
self.has_stdout_observer.clone(),
self.has_stderr_observer.clone(),
self.has_asan_observer.clone(),
))
} }
} }
@ -619,9 +663,10 @@ 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(()) /// MyExecutor.into_executor((), None, None, None)
/// } /// }
/// ``` /// ```
#[cfg(all(feature = "std", any(unix, doc)))] #[cfg(all(feature = "std", any(unix, doc)))]
pub trait CommandConfigurator: Sized + Debug { pub trait CommandConfigurator: Sized + Debug {
/// Spawns a new process with the given configuration. /// Spawns a new process with the given configuration.
@ -630,22 +675,16 @@ 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>(self, observers: OT) -> CommandExecutor<EM, I, OT, S, Self, Z> fn into_executor<EM, I, OT, S, Z>(
self,
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,
{ {
let has_asan_observer = observers
.match_name::<ASANBacktraceObserver>("ASANBacktraceObserver")
.is_some();
let has_stdout_observer = observers
.match_name::<StdOutObserver>("StdOutObserver")
.is_some();
let has_stderr_observer = observers
.match_name::<StdErrObserver>("StdErrObserver")
.is_some();
CommandExecutor { CommandExecutor {
observers, observers,
has_asan_observer, has_asan_observer,
@ -697,13 +736,18 @@ mod tests {
#[cfg(unix)] #[cfg(unix)]
fn test_parse_afl_cmdline() { fn test_parse_afl_cmdline() {
use alloc::string::ToString; use alloc::string::ToString;
let mut mgr = SimpleEventManager::<BytesInput, _, ()>::new(SimpleMonitor::new(|status| { let mut mgr = SimpleEventManager::<BytesInput, _, ()>::new(SimpleMonitor::new(|status| {
println!("{status}"); println!("{status}");
})); }));
let mut executor = let mut executor = CommandExecutor::parse_afl_cmdline(
CommandExecutor::parse_afl_cmdline(&["file".to_string(), "@@".to_string()], (), true) &["file".to_string(), "@@".to_string()],
(),
true,
None,
None,
None,
)
.unwrap(); .unwrap();
executor executor
.run_target( .run_target(

View File

@ -2,9 +2,12 @@
//! It wraps two exeutors that will be run after each other with the same input. //! It wraps two exeutors that will be run after each other with the same input.
//! In comparison to the [`crate::executors::CombinedExecutor`] it also runs the secondary executor in `run_target`. //! In comparison to the [`crate::executors::CombinedExecutor`] it also runs the secondary executor in `run_target`.
//! //!
use core::fmt::Debug; use core::{cell::UnsafeCell, fmt::Debug};
use serde::{Deserialize, Serialize};
use crate::{ use crate::{
bolts::{ownedref::OwnedPtrMut, tuples::MatchName},
executors::{Executor, ExitKind, HasObservers}, executors::{Executor, ExitKind, HasObservers},
inputs::Input, inputs::Input,
observers::ObserversTuple, observers::ObserversTuple,
@ -13,19 +16,24 @@ use crate::{
/// A [`DiffExecutor`] wraps a primary executor, forwarding its methods, and a secondary one /// A [`DiffExecutor`] wraps a primary executor, forwarding its methods, and a secondary one
#[derive(Debug)] #[derive(Debug)]
pub struct DiffExecutor<A, B> pub struct DiffExecutor<A, B, OTA, OTB>
where where
A: Debug, A: Debug,
B: Debug, B: Debug,
OTA: Debug,
OTB: Debug,
{ {
primary: A, primary: A,
secondary: B, secondary: B,
observers: UnsafeCell<ProxyObserversTuple<OTA, OTB>>,
} }
impl<A, B> DiffExecutor<A, B> impl<A, B, OTA, OTB> DiffExecutor<A, B, OTA, OTB>
where where
A: Debug, A: Debug,
B: Debug, B: Debug,
OTA: Debug,
OTB: Debug,
{ {
/// Create a new `DiffExecutor`, wrapping the given `executor`s. /// Create a new `DiffExecutor`, wrapping the given `executor`s.
pub fn new<EM, I, S, Z>(primary: A, secondary: B) -> Self pub fn new<EM, I, S, Z>(primary: A, secondary: B) -> Self
@ -34,7 +42,14 @@ where
B: Executor<EM, I, S, Z>, B: Executor<EM, I, S, Z>,
I: Input, I: Input,
{ {
Self { primary, secondary } Self {
primary,
secondary,
observers: UnsafeCell::new(ProxyObserversTuple {
primary: OwnedPtrMut::Ptr(core::ptr::null_mut()),
secondary: OwnedPtrMut::Ptr(core::ptr::null_mut()),
}),
}
} }
/// Retrieve the primary `Executor` that is wrapped by this `DiffExecutor`. /// Retrieve the primary `Executor` that is wrapped by this `DiffExecutor`.
@ -48,11 +63,13 @@ where
} }
} }
impl<A, B, EM, I, S, Z> Executor<EM, I, S, Z> for DiffExecutor<A, B> impl<A, B, EM, I, OTA, OTB, S, Z> Executor<EM, I, S, Z> for DiffExecutor<A, B, OTA, OTB>
where where
A: Executor<EM, I, S, Z>, A: Executor<EM, I, S, Z>,
B: Executor<EM, I, S, Z>, B: Executor<EM, I, S, Z>,
I: Input, I: Input,
OTA: Debug,
OTB: Debug,
{ {
fn run_target( fn run_target(
&mut self, &mut self,
@ -77,19 +94,115 @@ where
} }
} }
impl<A, B, I, OT, S> HasObservers<I, OT, S> for DiffExecutor<A, B> /// Proxy the observers of the inner executors
#[derive(Serialize, Deserialize, Debug)]
#[serde(
bound = "A: serde::Serialize + serde::de::DeserializeOwned, B: serde::Serialize + serde::de::DeserializeOwned"
)]
pub struct ProxyObserversTuple<A, B> {
primary: OwnedPtrMut<A>,
secondary: OwnedPtrMut<B>,
}
impl<A, B, I, S> ObserversTuple<I, S> for ProxyObserversTuple<A, B>
where where
A: HasObservers<I, OT, S>, A: ObserversTuple<I, S>,
B: Debug, B: ObserversTuple<I, S>,
OT: ObserversTuple<I, S>, {
fn pre_exec_all(&mut self, state: &mut S, input: &I) -> Result<(), Error> {
self.primary.as_mut().pre_exec_all(state, input)?;
self.secondary.as_mut().pre_exec_all(state, input)
}
fn post_exec_all(
&mut self,
state: &mut S,
input: &I,
exit_kind: &ExitKind,
) -> Result<(), Error> {
self.primary
.as_mut()
.post_exec_all(state, input, exit_kind)?;
self.secondary
.as_mut()
.post_exec_all(state, input, exit_kind)
}
fn pre_exec_child_all(&mut self, state: &mut S, input: &I) -> Result<(), Error> {
self.primary.as_mut().pre_exec_child_all(state, input)?;
self.secondary.as_mut().pre_exec_child_all(state, input)
}
fn post_exec_child_all(
&mut self,
state: &mut S,
input: &I,
exit_kind: &ExitKind,
) -> Result<(), Error> {
self.primary
.as_mut()
.post_exec_child_all(state, input, exit_kind)?;
self.secondary
.as_mut()
.post_exec_child_all(state, input, exit_kind)
}
}
impl<A, B> MatchName for ProxyObserversTuple<A, B>
where
A: MatchName,
B: MatchName,
{
fn match_name<T>(&self, name: &str) -> Option<&T> {
if let Some(t) = self.primary.as_ref().match_name::<T>(name) {
return Some(t);
}
self.secondary.as_ref().match_name::<T>(name)
}
fn match_name_mut<T>(&mut self, name: &str) -> Option<&mut T> {
if let Some(t) = self.primary.as_mut().match_name_mut::<T>(name) {
return Some(t);
}
self.secondary.as_mut().match_name_mut::<T>(name)
}
}
impl<A, B> ProxyObserversTuple<A, B> {
fn set(&mut self, primary: &A, secondary: &B) {
self.primary = OwnedPtrMut::Ptr(primary as *const A as *mut A);
self.secondary = OwnedPtrMut::Ptr(secondary as *const B as *mut B);
}
}
impl<A, B, I, OTA, OTB, S> HasObservers<I, ProxyObserversTuple<OTA, OTB>, S>
for DiffExecutor<A, B, OTA, OTB>
where
A: HasObservers<I, OTA, S>,
B: HasObservers<I, OTB, S>,
OTA: ObserversTuple<I, S>,
OTB: ObserversTuple<I, S>,
{ {
#[inline] #[inline]
fn observers(&self) -> &OT { fn observers(&self) -> &ProxyObserversTuple<OTA, OTB> {
self.primary.observers() unsafe {
self.observers
.get()
.as_mut()
.unwrap()
.set(self.primary.observers(), self.secondary.observers());
self.observers.get().as_ref().unwrap()
}
} }
#[inline] #[inline]
fn observers_mut(&mut self) -> &mut OT { fn observers_mut(&mut self) -> &mut ProxyObserversTuple<OTA, OTB> {
self.primary.observers_mut() unsafe {
self.observers
.get()
.as_mut()
.unwrap()
.set(self.primary.observers(), self.secondary.observers());
self.observers.get().as_mut().unwrap()
}
} }
} }

View File

@ -140,7 +140,6 @@ where
fn err(name: &str) -> Error { fn err(name: &str) -> Error {
Error::illegal_argument(format!("DiffFeedback: observer {name} not found")) Error::illegal_argument(format!("DiffFeedback: observer {name} not found"))
} }
let o1: &O1 = observers let o1: &O1 = observers
.match_name(&self.o1_name) .match_name(&self.o1_name)
.ok_or_else(|| err(&self.o1_name))?; .ok_or_else(|| err(&self.o1_name))?;
@ -148,7 +147,7 @@ where
.match_name(&self.o2_name) .match_name(&self.o2_name)
.ok_or_else(|| err(&self.o2_name))?; .ok_or_else(|| err(&self.o2_name))?;
Ok(o1 != o2) Ok((self.compare_fn)(o1, o2) == DiffResult::Diff)
} }
} }

View File

@ -4,11 +4,13 @@
use alloc::string::String; use alloc::string::String;
use serde::{Deserialize, Serialize};
use crate::{bolts::tuples::Named, observers::Observer}; use crate::{bolts::tuples::Named, observers::Observer};
/// An observer that captures stdout of a target. /// An observer that captures stdout of a target.
/// Only works for supported executors. /// Only works for supported executors.
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct StdOutObserver { pub struct StdOutObserver {
/// The name of the observer. /// The name of the observer.
pub name: String, pub name: String,