use alloc::{
string::{String, ToString},
vec::Vec,
};
use core::{fmt::Debug, marker::PhantomData};
use num_traits::PrimInt;
use serde::{Deserialize, Serialize};
use crate::{
bolts::{
tuples::{MatchName, Named},
AsSlice, HasRefCnt,
},
corpus::Testcase,
events::{Event, EventFirer},
executors::ExitKind,
feedbacks::{Feedback, FeedbackState},
inputs::Input,
monitors::UserStats,
observers::{MapObserver, ObserversTuple},
state::{HasClientPerfMonitor, HasFeedbackStates, HasMetadata},
Error,
};
pub type AflMapFeedback<I, O, S, T> = MapFeedback<I, DifferentIsNovel, O, OrReducer, S, T>;
pub type MaxMapFeedback<I, O, S, T> = MapFeedback<I, DifferentIsNovel, O, MaxReducer, S, T>;
pub type MinMapFeedback<I, O, S, T> = MapFeedback<I, DifferentIsNovel, O, MinReducer, S, T>;
pub type MaxMapPow2Feedback<I, O, S, T> = MapFeedback<I, NextPow2IsNovel, O, MaxReducer, S, T>;
pub type MaxMapOneOrFilledFeedback<I, O, S, T> =
MapFeedback<I, OneOrFilledIsNovel, O, MaxReducer, S, T>;
pub trait Reducer<T>: Serialize + serde::de::DeserializeOwned + 'static + Debug
where
T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned,
{
fn reduce(first: T, second: T) -> T;
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct OrReducer {}
impl<T> Reducer<T> for OrReducer
where
T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned + PartialOrd,
{
#[inline]
fn reduce(history: T, new: T) -> T {
history | new
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct AndReducer {}
impl<T> Reducer<T> for AndReducer
where
T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned + PartialOrd,
{
#[inline]
fn reduce(history: T, new: T) -> T {
history & new
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct MaxReducer {}
impl<T> Reducer<T> for MaxReducer
where
T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned + PartialOrd,
{
#[inline]
fn reduce(first: T, second: T) -> T {
if first > second {
first
} else {
second
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct MinReducer {}
impl<T> Reducer<T> for MinReducer
where
T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned + PartialOrd,
{
#[inline]
fn reduce(first: T, second: T) -> T {
if first < second {
first
} else {
second
}
}
}
pub trait IsNovel<T>: Serialize + serde::de::DeserializeOwned + 'static + Debug
where
T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned,
{
fn is_novel(old: T, new: T) -> bool;
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct AllIsNovel {}
impl<T> IsNovel<T> for AllIsNovel
where
T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned,
{
#[inline]
fn is_novel(_old: T, _new: T) -> bool {
true
}
}
#[inline]
fn saturating_next_power_of_two<T: PrimInt>(n: T) -> T {
if n <= T::one() {
T::one()
} else {
(T::max_value() >> (n - T::one()).leading_zeros().try_into().unwrap())
.saturating_add(T::one())
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct DifferentIsNovel {}
impl<T> IsNovel<T> for DifferentIsNovel
where
T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned,
{
#[inline]
fn is_novel(old: T, new: T) -> bool {
old != new
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct NextPow2IsNovel {}
impl<T> IsNovel<T> for NextPow2IsNovel
where
T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned,
{
#[inline]
fn is_novel(old: T, new: T) -> bool {
if new <= old {
false
} else {
let pow2 = saturating_next_power_of_two(old.saturating_add(T::one()));
new >= pow2
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct OneOrFilledIsNovel {}
impl<T> IsNovel<T> for OneOrFilledIsNovel
where
T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned,
{
#[inline]
fn is_novel(old: T, new: T) -> bool {
(new == T::one() || new == T::max_value()) && new > old
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct MapIndexesMetadata {
pub list: Vec<usize>,
pub tcref: isize,
}
crate::impl_serdeany!(MapIndexesMetadata);
impl AsSlice<usize> for MapIndexesMetadata {
fn as_slice(&self) -> &[usize] {
self.list.as_slice()
}
}
impl HasRefCnt for MapIndexesMetadata {
fn refcnt(&self) -> isize {
self.tcref
}
fn refcnt_mut(&mut self) -> &mut isize {
&mut self.tcref
}
}
impl MapIndexesMetadata {
#[must_use]
pub fn new(list: Vec<usize>) -> Self {
Self { list, tcref: 0 }
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct MapNoveltiesMetadata {
pub list: Vec<usize>,
}
crate::impl_serdeany!(MapNoveltiesMetadata);
impl AsSlice<usize> for MapNoveltiesMetadata {
#[must_use]
fn as_slice(&self) -> &[usize] {
self.list.as_slice()
}
}
impl MapNoveltiesMetadata {
#[must_use]
pub fn new(list: Vec<usize>) -> Self {
Self { list }
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(bound = "T: serde::de::DeserializeOwned")]
pub struct MapFeedbackState<T>
where
T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned,
{
pub history_map: Vec<T>,
pub name: String,
}
impl<T> FeedbackState for MapFeedbackState<T>
where
T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned + Debug,
{
fn reset(&mut self) -> Result<(), Error> {
self.history_map
.iter_mut()
.for_each(|x| *x = T::min_value());
Ok(())
}
}
impl<T> Named for MapFeedbackState<T>
where
T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned,
{
#[inline]
fn name(&self) -> &str {
self.name.as_str()
}
}
impl<T> MapFeedbackState<T>
where
T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned,
{
#[must_use]
pub fn new(name: &'static str, map_size: usize) -> Self {
Self {
history_map: vec![T::min_value(); map_size],
name: name.to_string(),
}
}
pub fn with_observer<O>(map_observer: &O) -> Self
where
O: MapObserver<T>,
T: Debug,
{
Self {
history_map: vec![T::min_value(); map_observer.len()],
name: map_observer.name().to_string(),
}
}
#[must_use]
pub fn with_history_map(name: &'static str, history_map: Vec<T>) -> Self {
Self {
history_map,
name: name.to_string(),
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(bound = "T: serde::de::DeserializeOwned")]
pub struct MapFeedback<I, N, O, R, S, T>
where
T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned + Debug,
R: Reducer<T>,
O: MapObserver<T>,
N: IsNovel<T>,
S: HasFeedbackStates,
{
indexes: Option<Vec<usize>>,
novelties: Option<Vec<usize>>,
name: String,
observer_name: String,
phantom: PhantomData<(I, N, S, R, O, T)>,
}
impl<I, N, O, R, S, T> Feedback<I, S> for MapFeedback<I, N, O, R, S, T>
where
T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned + Debug,
R: Reducer<T>,
O: MapObserver<T>,
N: IsNovel<T>,
I: Input,
S: HasFeedbackStates + HasClientPerfMonitor + Debug,
{
fn is_interesting<EM, OT>(
&mut self,
state: &mut S,
manager: &mut EM,
_input: &I,
observers: &OT,
_exit_kind: &ExitKind,
) -> Result<bool, Error>
where
EM: EventFirer<I>,
OT: ObserversTuple<I, S>,
{
let mut interesting = false;
let observer = observers.match_name::<O>(&self.observer_name).unwrap();
let size = observer.usable_count();
let initial = observer.initial();
let map_state = state
.feedback_states_mut()
.match_name_mut::<MapFeedbackState<T>>(&self.name)
.unwrap();
assert!(size <= map_state.history_map.len(), "The size of the associated map observer cannot exceed the size of the history map of the feedback. If you are running multiple instances of slightly different fuzzers (e.g. one with ASan and another without) synchronized using LLMP please check the `configuration` field of the LLMP manager.");
assert!(size <= observer.len());
if self.novelties.is_some() {
for i in 0..size {
let history = map_state.history_map[i];
let item = *observer.get(i);
let reduced = R::reduce(history, item);
if N::is_novel(history, reduced) {
map_state.history_map[i] = reduced;
interesting = true;
self.novelties.as_mut().unwrap().push(i);
}
}
} else {
for i in 0..size {
let history = map_state.history_map[i];
let item = *observer.get(i);
let reduced = R::reduce(history, item);
if N::is_novel(history, reduced) {
map_state.history_map[i] = reduced;
interesting = true;
}
}
}
if interesting {
let mut filled = 0;
for i in 0..size {
if map_state.history_map[i] != initial {
filled += 1;
if self.indexes.is_some() {
self.indexes.as_mut().unwrap().push(i);
}
}
}
manager.fire(
state,
Event::UpdateUserStats {
name: self.name.to_string(),
value: UserStats::Ratio(filled, size as u64),
phantom: PhantomData,
},
)?;
}
Ok(interesting)
}
fn append_metadata(&mut self, _state: &mut S, testcase: &mut Testcase<I>) -> Result<(), Error> {
if let Some(v) = self.indexes.as_mut() {
let meta = MapIndexesMetadata::new(core::mem::take(v));
testcase.add_metadata(meta);
};
if let Some(v) = self.novelties.as_mut() {
let meta = MapNoveltiesMetadata::new(core::mem::take(v));
testcase.add_metadata(meta);
};
Ok(())
}
fn discard_metadata(&mut self, _state: &mut S, _input: &I) -> Result<(), Error> {
if let Some(v) = self.indexes.as_mut() {
v.clear();
}
if let Some(v) = self.novelties.as_mut() {
v.clear();
}
Ok(())
}
}
impl<I, N, O, R, S, T> Named for MapFeedback<I, N, O, R, S, T>
where
T: PrimInt + Default + Copy + 'static + Serialize + serde::de::DeserializeOwned + Debug,
R: Reducer<T>,
N: IsNovel<T>,
O: MapObserver<T>,
S: HasFeedbackStates,
{
#[inline]
fn name(&self) -> &str {
self.name.as_str()
}
}
impl<I, N, O, R, S, T> MapFeedback<I, N, O, R, S, T>
where
T: PrimInt
+ Default
+ Copy
+ 'static
+ Serialize
+ serde::de::DeserializeOwned
+ PartialOrd
+ Debug,
R: Reducer<T>,
N: IsNovel<T>,
O: MapObserver<T>,
S: HasFeedbackStates,
{
#[must_use]
pub fn new(feedback_state: &MapFeedbackState<T>, map_observer: &O) -> Self {
Self {
indexes: None,
novelties: None,
name: feedback_state.name().to_string(),
observer_name: map_observer.name().to_string(),
phantom: PhantomData,
}
}
#[must_use]
pub fn new_tracking(
feedback_state: &MapFeedbackState<T>,
map_observer: &O,
track_indexes: bool,
track_novelties: bool,
) -> Self {
Self {
indexes: if track_indexes { Some(vec![]) } else { None },
novelties: if track_novelties { Some(vec![]) } else { None },
name: feedback_state.name().to_string(),
observer_name: map_observer.name().to_string(),
phantom: PhantomData,
}
}
#[must_use]
pub fn with_names(name: &'static str, observer_name: &'static str) -> Self {
Self {
indexes: None,
novelties: None,
name: name.to_string(),
observer_name: observer_name.to_string(),
phantom: PhantomData,
}
}
#[must_use]
pub fn with_names_tracking(
name: &'static str,
observer_name: &'static str,
track_indexes: bool,
track_novelties: bool,
) -> Self {
Self {
indexes: if track_indexes { Some(vec![]) } else { None },
novelties: if track_novelties { Some(vec![]) } else { None },
observer_name: observer_name.to_string(),
name: name.to_string(),
phantom: PhantomData,
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ReachabilityFeedback<O> {
name: String,
target_idx: Vec<usize>,
phantom: PhantomData<O>,
}
impl<O> ReachabilityFeedback<O>
where
O: MapObserver<usize>,
{
#[must_use]
pub fn new(map_observer: &O) -> Self {
Self {
name: map_observer.name().to_string(),
target_idx: vec![],
phantom: PhantomData,
}
}
#[must_use]
pub fn with_name(name: &'static str) -> Self {
Self {
name: name.to_string(),
target_idx: vec![],
phantom: PhantomData,
}
}
}
impl<I, O, S> Feedback<I, S> for ReachabilityFeedback<O>
where
I: Input,
O: MapObserver<usize>,
S: HasClientPerfMonitor,
{
fn is_interesting<EM, OT>(
&mut self,
_state: &mut S,
_manager: &mut EM,
_input: &I,
observers: &OT,
_exit_kind: &ExitKind,
) -> Result<bool, Error>
where
EM: EventFirer<I>,
OT: ObserversTuple<I, S>,
{
let observer = observers.match_name::<O>(&self.name).unwrap();
let size = observer.usable_count();
let mut hit_target: bool = false;
for i in 0..size {
if *observer.get(i) > 0 {
self.target_idx.push(i);
hit_target = true;
}
}
if hit_target {
Ok(true)
} else {
Ok(false)
}
}
fn append_metadata(&mut self, _state: &mut S, testcase: &mut Testcase<I>) -> Result<(), Error> {
if !self.target_idx.is_empty() {
let meta = MapIndexesMetadata::new(core::mem::take(self.target_idx.as_mut()));
testcase.add_metadata(meta);
};
Ok(())
}
fn discard_metadata(&mut self, _state: &mut S, _input: &I) -> Result<(), Error> {
self.target_idx.clear();
Ok(())
}
}
impl<O> Named for ReachabilityFeedback<O>
where
O: MapObserver<usize>,
{
#[inline]
fn name(&self) -> &str {
self.name.as_str()
}
}
#[cfg(test)]
mod tests {
use crate::feedbacks::{AllIsNovel, IsNovel, NextPow2IsNovel};
#[test]
fn test_map_is_novel() {
assert!(AllIsNovel::is_novel(0_u8, 0));
assert!(!NextPow2IsNovel::is_novel(0_u8, 0));
assert!(NextPow2IsNovel::is_novel(0_u8, 1));
assert!(!NextPow2IsNovel::is_novel(1_u8, 1));
assert!(NextPow2IsNovel::is_novel(1_u8, 2));
assert!(!NextPow2IsNovel::is_novel(2_u8, 2));
assert!(!NextPow2IsNovel::is_novel(2_u8, 3));
assert!(NextPow2IsNovel::is_novel(2_u8, 4));
assert!(!NextPow2IsNovel::is_novel(128_u8, 128));
assert!(!NextPow2IsNovel::is_novel(129_u8, 128));
assert!(NextPow2IsNovel::is_novel(128_u8, 255));
assert!(!NextPow2IsNovel::is_novel(255_u8, 128));
assert!(NextPow2IsNovel::is_novel(254_u8, 255));
assert!(!NextPow2IsNovel::is_novel(255_u8, 255));
}
}