use alloc::{borrow::Cow, vec::Vec};
use core::{
fmt::Debug,
marker::PhantomData,
ops::{Deref, DerefMut},
};
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
use libafl_bolts::simd::vector::u8x16;
#[cfg(not(feature = "simd"))]
use libafl_bolts::simd::{MinReducer, OrReducer};
#[cfg(feature = "simd")]
use libafl_bolts::simd::{SimdMaxReducer, SimdMinReducer, SimdOrReducer, vector::u8x32};
use libafl_bolts::{
AsIter, HasRefCnt, Named,
simd::{MaxReducer, NopReducer, Reducer},
tuples::{Handle, Handled, MatchName, MatchNameRef},
};
use num_traits::PrimInt;
use serde::{Deserialize, Serialize, de::DeserializeOwned};
#[cfg(feature = "simd")]
use super::simd::SimdMapFeedback;
#[cfg(feature = "track_hit_feedbacks")]
use crate::feedbacks::premature_last_result_err;
use crate::{
Error, HasMetadata, HasNamedMetadata,
corpus::Testcase,
events::{Event, EventFirer, EventWithStats},
executors::ExitKind,
feedbacks::{Feedback, HasObserverHandle, StateInitializer},
monitors::stats::{AggregatorOps, UserStats, UserStatsValue},
observers::{CanTrack, MapObserver},
state::HasExecutions,
};
#[cfg(feature = "simd")]
pub type AflMapFeedback<C, O> = SimdMapFeedback<C, O, SimdOrReducer, u8x32>;
#[cfg(not(feature = "simd"))]
pub type AflMapFeedback<C, O> = MapFeedback<C, DifferentIsNovel, O, OrReducer>;
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
pub type MaxMapFeedback<C, O> = SimdMapFeedback<C, O, SimdMaxReducer, u8x16>;
#[cfg(all(feature = "simd", not(target_arch = "x86_64")))]
pub type MaxMapFeedback<C, O> = SimdMapFeedback<C, O, SimdMaxReducer, u8x32>;
#[cfg(not(feature = "simd"))]
pub type MaxMapFeedback<C, O> = MapFeedback<C, DifferentIsNovel, O, MaxReducer>;
#[cfg(feature = "simd")]
pub type MinMapFeedback<C, O> = SimdMapFeedback<C, O, SimdMinReducer, u8x32>;
#[cfg(not(feature = "simd"))]
pub type MinMapFeedback<C, O> = MapFeedback<C, DifferentIsNovel, O, MinReducer>;
pub type AlwaysInterestingMapFeedback<C, O> = MapFeedback<C, AllIsNovel, O, NopReducer>;
pub type MaxMapPow2Feedback<C, O> = MapFeedback<C, NextPow2IsNovel, O, MaxReducer>;
pub type MaxMapOneOrFilledFeedback<C, O> = MapFeedback<C, OneOrFilledIsNovel, O, MaxReducer>;
pub trait IsNovel<T> {
fn is_novel(old: T, new: T) -> bool;
}
#[derive(Debug, Clone)]
pub struct AllIsNovel {}
impl<T> IsNovel<T> for AllIsNovel
where
T: Default + Copy + 'static,
{
#[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(Debug, Clone)]
pub struct DifferentIsNovel {}
impl<T> IsNovel<T> for DifferentIsNovel
where
T: PartialEq + Default + Copy + 'static,
{
#[inline]
fn is_novel(old: T, new: T) -> bool {
old != new
}
}
#[derive(Debug, Clone)]
pub struct NextPow2IsNovel {}
impl<T> IsNovel<T> for NextPow2IsNovel
where
T: PrimInt + Default + Copy + 'static,
{
#[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(Debug, Clone)]
pub struct OneOrFilledIsNovel {}
impl<T> IsNovel<T> for OneOrFilledIsNovel
where
T: PrimInt + Default + Copy + 'static,
{
#[inline]
fn is_novel(old: T, new: T) -> bool {
(new == T::one() || new == T::max_value()) && new > old
}
}
#[derive(Debug, Serialize, Deserialize)]
#[expect(clippy::unsafe_derive_deserialize)] pub struct MapIndexesMetadata {
pub list: Vec<usize>,
pub tcref: isize,
}
libafl_bolts::impl_serdeany!(MapIndexesMetadata);
impl Deref for MapIndexesMetadata {
type Target = [usize];
fn deref(&self) -> &[usize] {
&self.list
}
}
impl DerefMut for MapIndexesMetadata {
fn deref_mut(&mut self) -> &mut [usize] {
&mut self.list
}
}
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)]
#[expect(clippy::unsafe_derive_deserialize)] pub struct MapNoveltiesMetadata {
pub list: Vec<usize>,
}
libafl_bolts::impl_serdeany!(MapNoveltiesMetadata);
impl Deref for MapNoveltiesMetadata {
type Target = [usize];
fn deref(&self) -> &[usize] {
&self.list
}
}
impl DerefMut for MapNoveltiesMetadata {
fn deref_mut(&mut self) -> &mut [usize] {
&mut self.list
}
}
impl MapNoveltiesMetadata {
#[must_use]
pub fn new(list: Vec<usize>) -> Self {
Self { list }
}
}
#[derive(Default, Serialize, Deserialize, Debug, Clone)]
#[expect(clippy::unsafe_derive_deserialize)] pub struct MapFeedbackMetadata<T> {
pub history_map: Vec<T>,
pub num_covered_map_indexes: usize,
}
libafl_bolts::impl_serdeany!(
MapFeedbackMetadata<T: 'static + Debug + Serialize + DeserializeOwned>,
<u8>,<u16>,<u32>,<u64>,<i8>,<i16>,<i32>,<i64>,<f32>,<f64>,<bool>,<char>,<usize>
);
impl<T> MapFeedbackMetadata<T>
where
T: Default + Copy + 'static + Serialize + DeserializeOwned + PartialEq,
{
#[must_use]
pub fn new(map_size: usize) -> Self {
Self {
history_map: vec![T::default(); map_size],
num_covered_map_indexes: 0,
}
}
#[must_use]
pub fn with_history_map(history_map: Vec<T>, initial_elem_value: T) -> Self {
let num_covered_map_indexes = history_map
.iter()
.fold(0, |acc, x| acc + usize::from(*x != initial_elem_value));
Self {
history_map,
num_covered_map_indexes,
}
}
pub fn reset(&mut self) -> Result<(), Error> {
let cnt = self.history_map.len();
for i in 0..cnt {
self.history_map[i] = T::default();
}
self.num_covered_map_indexes = 0;
Ok(())
}
pub fn reset_with_value(&mut self, value: T) -> Result<(), Error> {
let cnt = self.history_map.len();
for i in 0..cnt {
self.history_map[i] = value;
}
self.num_covered_map_indexes = 0;
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct MapFeedback<C, N, O, R> {
pub(crate) novelties: Option<Vec<usize>>,
name: Cow<'static, str>,
map_ref: Handle<C>,
stats_name: Cow<'static, str>,
#[cfg(feature = "track_hit_feedbacks")]
pub(crate) last_result: Option<bool>,
#[expect(clippy::type_complexity)]
phantom: PhantomData<fn() -> (N, O, R)>,
}
impl<C, N, O, R, S> StateInitializer<S> for MapFeedback<C, N, O, R>
where
O: MapObserver,
O::Entry: 'static + Default + Debug + DeserializeOwned + Serialize,
S: HasNamedMetadata,
{
fn init_state(&mut self, state: &mut S) -> Result<(), Error> {
state.add_named_metadata_checked(&self.name, MapFeedbackMetadata::<O::Entry>::default())?;
Ok(())
}
}
impl<C, EM, I, N, O, OT, R, S> Feedback<EM, I, OT, S> for MapFeedback<C, N, O, R>
where
C: CanTrack + AsRef<O>,
EM: EventFirer<I, S>,
N: IsNovel<O::Entry>,
O: MapObserver + for<'it> AsIter<'it, Item = O::Entry>,
O::Entry: 'static + Default + Debug + DeserializeOwned + Serialize,
OT: MatchName,
R: Reducer<O::Entry>,
S: HasNamedMetadata + HasExecutions,
{
fn is_interesting(
&mut self,
state: &mut S,
_manager: &mut EM,
_input: &I,
observers: &OT,
_exit_kind: &ExitKind,
) -> Result<bool, Error> {
let res = self.is_interesting_default(state, observers);
#[cfg(feature = "track_hit_feedbacks")]
{
self.last_result = Some(res);
}
Ok(res)
}
#[cfg(feature = "track_hit_feedbacks")]
fn last_result(&self) -> Result<bool, Error> {
self.last_result.ok_or(premature_last_result_err())
}
fn append_metadata(
&mut self,
state: &mut S,
manager: &mut EM,
observers: &OT,
testcase: &mut Testcase<I>,
) -> Result<(), Error> {
if let Some(novelties) = self.novelties.as_mut().map(core::mem::take) {
let meta = MapNoveltiesMetadata::new(novelties);
testcase.add_metadata(meta);
}
let observer = observers.get(&self.map_ref).expect("MapObserver not found. This is likely because you entered the crash handler with the wrong executor/observer").as_ref();
let initial = observer.initial();
let map_state = state
.named_metadata_map_mut()
.get_mut::<MapFeedbackMetadata<O::Entry>>(&self.name)
.unwrap();
let len = observer.len();
if map_state.history_map.len() < len {
map_state.history_map.resize(len, observer.initial());
}
let history_map = &mut map_state.history_map;
if C::INDICES {
let mut indices = Vec::new();
for (i, value) in observer
.as_iter()
.map(|x| *x)
.enumerate()
.filter(|(_, value)| *value != initial)
{
let val = R::reduce(history_map[i], value);
if history_map[i] == initial && val != initial {
map_state.num_covered_map_indexes += 1;
}
history_map[i] = val;
indices.push(i);
}
let meta = MapIndexesMetadata::new(indices);
if testcase.try_add_metadata(meta).is_err() {
return Err(Error::key_exists(
"MapIndexesMetadata is already attached to this testcase. You should not have more than one observer with tracking.",
));
}
} else {
for (i, value) in observer
.as_iter()
.map(|x| *x)
.enumerate()
.filter(|(_, value)| *value != initial)
{
let val = R::reduce(history_map[i], value);
if history_map[i] == initial && val != initial {
map_state.num_covered_map_indexes += 1;
}
history_map[i] = val;
}
}
debug_assert!(
history_map
.iter()
.fold(0, |acc, x| acc + usize::from(*x != initial))
== map_state.num_covered_map_indexes,
"history_map had {} filled, but map_state.num_covered_map_indexes was {}",
history_map
.iter()
.fold(0, |acc, x| acc + usize::from(*x != initial)),
map_state.num_covered_map_indexes,
);
let covered = map_state.num_covered_map_indexes;
let len = history_map.len();
manager.fire(
state,
EventWithStats::with_current_time(
Event::UpdateUserStats {
name: self.stats_name.clone(),
value: UserStats::new(
UserStatsValue::Ratio(covered as u64, len as u64),
AggregatorOps::Avg,
),
phantom: PhantomData,
},
*state.executions(),
),
)?;
Ok(())
}
}
impl<C, N, O, R> Named for MapFeedback<C, N, O, R> {
#[inline]
fn name(&self) -> &Cow<'static, str> {
&self.name
}
}
#[expect(clippy::ptr_arg)]
fn create_stats_name(name: &Cow<'static, str>) -> Cow<'static, str> {
if name.chars().all(char::is_lowercase) {
name.clone()
} else {
name.to_lowercase().into()
}
}
impl<C, N, O, R> MapFeedback<C, N, O, R>
where
C: CanTrack + AsRef<O> + Named,
{
#[must_use]
pub fn new(map_observer: &C) -> Self {
Self {
novelties: if C::NOVELTIES { Some(vec![]) } else { None },
name: map_observer.name().clone(),
map_ref: map_observer.handle(),
stats_name: create_stats_name(map_observer.name()),
#[cfg(feature = "track_hit_feedbacks")]
last_result: None,
phantom: PhantomData,
}
}
#[must_use]
pub fn with_name(name: &'static str, map_observer: &C) -> Self {
let name = Cow::from(name);
Self {
novelties: if C::NOVELTIES { Some(vec![]) } else { None },
map_ref: map_observer.handle(),
stats_name: create_stats_name(&name),
name,
#[cfg(feature = "track_hit_feedbacks")]
last_result: None,
phantom: PhantomData,
}
}
}
impl<C, N, O, R> HasObserverHandle for MapFeedback<C, N, O, R> {
type Observer = C;
#[inline]
fn observer_handle(&self) -> &Handle<C> {
&self.map_ref
}
}
impl<C, N, O, R> MapFeedback<C, N, O, R>
where
R: Reducer<O::Entry>,
O: MapObserver + for<'it> AsIter<'it, Item = O::Entry>,
O::Entry: 'static + Debug + Serialize + DeserializeOwned,
N: IsNovel<O::Entry>,
C: AsRef<O>,
{
fn is_interesting_default<OT, S>(&mut self, state: &mut S, observers: &OT) -> bool
where
S: HasNamedMetadata,
OT: MatchName,
{
let mut interesting = false;
let observer = observers.get(&self.map_ref).unwrap().as_ref();
let map_state = state
.named_metadata_map_mut()
.get_mut::<MapFeedbackMetadata<O::Entry>>(&self.name)
.unwrap();
let len = observer.len();
if map_state.history_map.len() < len {
map_state.history_map.resize(len, observer.initial());
}
let history_map = map_state.history_map.as_slice();
let initial = observer.initial();
if let Some(novelties) = self.novelties.as_mut() {
novelties.clear();
for (i, item) in observer
.as_iter()
.map(|x| *x)
.enumerate()
.filter(|(_, item)| *item != initial)
{
let existing = unsafe { *history_map.get_unchecked(i) };
let reduced = R::reduce(existing, item);
if N::is_novel(existing, reduced) {
interesting = true;
novelties.push(i);
}
}
} else {
for (i, item) in observer
.as_iter()
.map(|x| *x)
.enumerate()
.filter(|(_, item)| *item != initial)
{
let existing = unsafe { *history_map.get_unchecked(i) };
let reduced = R::reduce(existing, item);
if N::is_novel(existing, reduced) {
interesting = true;
break;
}
}
}
interesting
}
}
#[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));
}
}