Repository: AFLplusplus/LibAFL (language: rust)

Issue:
Merge StdOut and StdErr observers/feedbacks
These are largely duplicated and can be merged.


Discussion / hints:

Fix patch:
diff --git a/libafl/src/executors/command.rs b/libafl/src/executors/command.rs
index f0eabc60b8..4e60cafebe 100644
--- a/libafl/src/executors/command.rs
+++ b/libafl/src/executors/command.rs
@@ -368,7 +368,7 @@ where
              })?.read_to_end(&mut stdout)?;
             let mut observers = self.observers_mut();
             let obs = observers.index_mut(h);
-            obs.observe_stdout(&stdout);
+            obs.observe(&stdout);
         }
         if let Some(h) = &mut self.configurer.stderr_observer() {
             let mut stderr = Vec::new();
@@ -379,7 +379,7 @@ where
              })?.read_to_end(&mut stderr)?;
             let mut observers = self.observers_mut();
             let obs = observers.index_mut(h);
-            obs.observe_stderr(&stderr);
+            obs.observe(&stderr);
         }
         Ok(exit_kind)
     }
diff --git a/libafl/src/feedbacks/stdio.rs b/libafl/src/feedbacks/stdio.rs
index 2d3106b761..332bf135bd 100644
--- a/libafl/src/feedbacks/stdio.rs
+++ b/libafl/src/feedbacks/stdio.rs
@@ -46,7 +46,7 @@ impl StdOutToMetadataFeedback {
             .get(&self.o_ref)
             .ok_or(Error::illegal_state("StdOutObserver is missing"))?;
         let buffer = observer
-            .stdout
+            .data
             .as_ref()
             .ok_or(Error::illegal_state("StdOutObserver has no stdout"))?;
         let stdout = String::from_utf8_lossy(buffer).into_owned();
@@ -139,7 +139,7 @@ where
             .get(&self.o_ref)
             .ok_or(Error::illegal_state("StdErrObserver is missing"))?;
         let buffer = observer
-            .stderr
+            .data
             .as_ref()
             .ok_or(Error::illegal_state("StdErrObserver has no stderr"))?;
         let stderr = String::from_utf8_lossy(buffer).into_owned();
diff --git a/libafl/src/observers/stdio.rs b/libafl/src/observers/stdio.rs
index 5c4547d520..d98335e37d 100644
--- a/libafl/src/observers/stdio.rs
+++ b/libafl/src/observers/stdio.rs
@@ -8,6 +8,7 @@
 )]
 
 use alloc::borrow::Cow;
+use core::marker::PhantomData;
 use std::vec::Vec;
 
 use libafl_bolts::Named;
@@ -70,8 +71,8 @@ use crate::{observers::Observer, Error};
 ///     ) -> Result<bool, Error>
 ///     {
 ///         unsafe {
-///             STDOUT = observers.get(&self.stdout_observer).unwrap().stdout.clone();
-///             STDERR = observers.get(&self.stderr_observer).unwrap().stderr.clone();
+///             STDOUT = observers.get(&self.stdout_observer).unwrap().data.clone();
+///             STDERR = observers.get(&self.stderr_observer).unwrap().data.clone();
 ///         }
 ///         Ok(true)
 ///     }
@@ -168,91 +169,58 @@ use crate::{observers::Observer, Error};
 /// ```
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
-pub struct StdOutObserver {
+pub struct StreamObserver<T> {
     /// The name of the observer.
     pub name: Cow<'static, str>,
-    /// The stdout of the target during its last execution.
-    pub stdout: Option<Vec<u8>>,
+    /// The captured stdout/stderr data during last execution.
+    pub data: Option<Vec<u8>>,
+    /// Phantom data to hold the stream type
+    phantom: PhantomData<T>,
 }
 
-/// An observer that captures stdout of a target.
-impl StdOutObserver {
-    /// Create a new [`StdOutObserver`] with the given name.
+/// Marker traits to distinguish between stdout and stderr
+#[derive(Debug, Clone)]
+pub struct StdOutMarker;
+/// Marker traits to distinguish between stdout and stderr
+#[derive(Debug, Clone)]
+pub struct StdErrMarker;
+
+impl<T> StreamObserver<T> {
+    /// Create a new `StreamObserver` with the given name.
     #[must_use]
     pub fn new(name: &'static str) -> Self {
         Self {
             name: Cow::from(name),
-            stdout: None,
+            data: None,
+            phantom: PhantomData,
         }
     }
 
-    /// React to new `stdout`
-    pub fn observe_stdout(&mut self, stdout: &[u8]) {
-        self.stdout = Some(stdout.into());
+    /// React to new stream data
+    pub fn observe(&mut self, data: &[u8]) {
+        self.data = Some(data.into());
     }
 }
 
-impl Named for StdOutObserver {
+impl<T> Named for StreamObserver<T> {
     fn name(&self) -> &Cow<'static, str> {
         &self.name
     }
 }
 
-impl<I, S> Observer<I, S> for StdOutObserver {
+impl<I, S, T> Observer<I, S> for StreamObserver<T> {
     fn pre_exec_child(&mut self, _state: &mut S, _input: &I) -> Result<(), Error> {
-        self.stdout = None;
+        self.data = None;
         Ok(())
     }
 
     fn pre_exec(&mut self, _state: &mut S, _input: &I) -> Result<(), Error> {
-        self.stdout = None;
+        self.data = None;
         Ok(())
     }
 }
 
+/// An observer that captures stdout of a target.
+pub type StdOutObserver = StreamObserver<StdOutMarker>;
 /// An observer that captures stderr of a target.
-/// Only works for supported executors.
-///
-/// Check docs for [`StdOutObserver`] for example.
-#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
-pub struct StdErrObserver {
-    /// The name of the observer.
-    pub name: Cow<'static, str>,
-    /// The stderr of the target during its last execution.
-    pub stderr: Option<Vec<u8>>,
-}
-
-/// An observer that captures stderr of a target.
-impl StdErrObserver {
-    /// Create a new [`StdErrObserver`] with the given name.
-    #[must_use]
-    pub fn new(name: &'static str) -> Self {
-        Self {
-            name: Cow::from(name),
-            stderr: None,
-        }
-    }
-
-    /// React to new `stderr`
-    pub fn observe_stderr(&mut self, stderr: &[u8]) {
-        self.stderr = Some(stderr.into());
-    }
-}
-
-impl Named for StdErrObserver {
-    fn name(&self) -> &Cow<'static, str> {
-        &self.name
-    }
-}
-
-impl<I, S> Observer<I, S> for StdErrObserver {
-    fn pre_exec_child(&mut self, _state: &mut S, _input: &I) -> Result<(), Error> {
-        self.stderr = None;
-        Ok(())
-    }
-
-    fn pre_exec(&mut self, _state: &mut S, _input: &I) -> Result<(), Error> {
-        self.stderr = None;
-        Ok(())
-    }
-}
+pub type StdErrObserver = StreamObserver<StdErrMarker>;
diff --git a/libafl_nyx/src/executor.rs b/libafl_nyx/src/executor.rs
index 7b51f9b86b..76b5edb310 100644
--- a/libafl_nyx/src/executor.rs
+++ b/libafl_nyx/src/executor.rs
@@ -120,7 +120,7 @@ where
                 .read_to_end(&mut stdout)
                 .map_err(|e| Error::illegal_state(format!("Failed to read Nyx stdout: {e}")))?;
 
-            ob.observe_stdout(&stdout);
+            ob.observe(&stdout);
         }
 
         unsafe {


Test patch: