use alloc::vec::Vec;
use core::{cmp::Ordering, marker::PhantomData};
use hashbrown::{HashMap, HashSet};
use serde::{Deserialize, Serialize};
use crate::{
bolts::{rands::Rand, serdeany::SerdeAny, AsSlice, HasRefCnt},
corpus::{Corpus, Testcase},
feedbacks::MapIndexesMetadata,
inputs::Input,
schedulers::{LenTimeMulTestcaseScore, Scheduler, TestcaseScore},
state::{HasCorpus, HasMetadata, HasRand},
Error,
};
pub const DEFAULT_SKIP_NON_FAVORED_PROB: u64 = 95;
#[derive(Debug, Serialize, Deserialize)]
pub struct IsFavoredMetadata {}
crate::impl_serdeany!(IsFavoredMetadata);
#[derive(Debug, Serialize, Deserialize)]
pub struct TopRatedsMetadata {
pub map: HashMap<usize, usize>,
}
crate::impl_serdeany!(TopRatedsMetadata);
impl TopRatedsMetadata {
#[must_use]
pub fn new() -> Self {
Self {
map: HashMap::default(),
}
}
#[must_use]
pub fn map(&self) -> &HashMap<usize, usize> {
&self.map
}
}
impl Default for TopRatedsMetadata {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct MinimizerScheduler<CS, F, I, M, S>
where
CS: Scheduler<I, S>,
F: TestcaseScore<I, S>,
I: Input,
M: AsSlice<usize> + SerdeAny + HasRefCnt,
S: HasCorpus<I> + HasMetadata,
{
base: CS,
skip_non_favored_prob: u64,
phantom: PhantomData<(F, I, M, S)>,
}
impl<CS, F, I, M, S> Scheduler<I, S> for MinimizerScheduler<CS, F, I, M, S>
where
CS: Scheduler<I, S>,
F: TestcaseScore<I, S>,
I: Input,
M: AsSlice<usize> + SerdeAny + HasRefCnt,
S: HasCorpus<I> + HasMetadata + HasRand,
{
fn on_add(&self, state: &mut S, idx: usize) -> Result<(), Error> {
self.update_score(state, idx)?;
self.base.on_add(state, idx)
}
fn on_replace(&self, state: &mut S, idx: usize, testcase: &Testcase<I>) -> Result<(), Error> {
self.base.on_replace(state, idx, testcase)
}
fn on_remove(
&self,
state: &mut S,
idx: usize,
testcase: &Option<Testcase<I>>,
) -> Result<(), Error> {
self.base.on_remove(state, idx, testcase)?;
let mut entries = if let Some(meta) = state.metadata_mut().get_mut::<TopRatedsMetadata>() {
let entries = meta
.map
.drain_filter(|_, other_idx| *other_idx == idx)
.map(|(entry, _)| entry)
.collect::<Vec<_>>();
meta.map
.values_mut()
.filter(|other_idx| **other_idx > idx)
.for_each(|other_idx| {
*other_idx -= 1;
});
entries
} else {
return Ok(());
};
entries.sort_unstable(); let mut map = HashMap::new();
for i in 0..state.corpus().count() {
let mut old = state.corpus().get(i)?.borrow_mut();
let factor = F::compute(&mut *old, state)?;
if let Some(old_map) = old.metadata_mut().get_mut::<M>() {
let mut e_iter = entries.iter();
let mut map_iter = old_map.as_slice().iter();
let mut entry = e_iter.next();
let mut map_entry = map_iter.next();
while let Some(e) = entry {
if let Some(me) = map_entry {
match e.cmp(me) {
Ordering::Less => {
entry = e_iter.next();
}
Ordering::Equal => {
map.entry(*e)
.and_modify(|(f, idx)| {
if *f > factor {
*f = factor;
*idx = i;
}
})
.or_insert((factor, i));
}
Ordering::Greater => {
map_entry = map_iter.next();
}
}
} else {
break;
}
}
}
}
if let Some(meta) = state.metadata_mut().get_mut::<TopRatedsMetadata>() {
meta.map
.extend(map.into_iter().map(|(entry, (_, idx))| (entry, idx)));
}
Ok(())
}
fn next(&self, state: &mut S) -> Result<usize, Error> {
self.cull(state)?;
let mut idx = self.base.next(state)?;
while {
let has = !state
.corpus()
.get(idx)?
.borrow()
.has_metadata::<IsFavoredMetadata>();
has
} && state.rand_mut().below(100) < self.skip_non_favored_prob
{
idx = self.base.next(state)?;
}
Ok(idx)
}
}
impl<CS, F, I, M, S> MinimizerScheduler<CS, F, I, M, S>
where
CS: Scheduler<I, S>,
F: TestcaseScore<I, S>,
I: Input,
M: AsSlice<usize> + SerdeAny + HasRefCnt,
S: HasCorpus<I> + HasMetadata + HasRand,
{
#[allow(clippy::unused_self)]
#[allow(clippy::cast_possible_wrap)]
pub fn update_score(&self, state: &mut S, idx: usize) -> Result<(), Error> {
if state.metadata().get::<TopRatedsMetadata>().is_none() {
state.add_metadata(TopRatedsMetadata::new());
}
let mut new_favoreds = vec![];
{
let mut entry = state.corpus().get(idx)?.borrow_mut();
let factor = F::compute(&mut *entry, state)?;
let meta = entry.metadata_mut().get_mut::<M>().ok_or_else(|| {
Error::key_not_found(format!(
"Metadata needed for MinimizerScheduler not found in testcase #{}",
idx
))
})?;
for elem in meta.as_slice() {
if let Some(old_idx) = state
.metadata()
.get::<TopRatedsMetadata>()
.unwrap()
.map
.get(elem)
{
let mut old = state.corpus().get(*old_idx)?.borrow_mut();
if factor > F::compute(&mut *old, state)? {
continue;
}
let must_remove = {
let old_meta = old.metadata_mut().get_mut::<M>().ok_or_else(|| {
Error::key_not_found(format!(
"Metadata needed for MinimizerScheduler not found in testcase #{}",
old_idx
))
})?;
*old_meta.refcnt_mut() -= 1;
old_meta.refcnt() <= 0
};
if must_remove {
drop(old.metadata_mut().remove::<M>());
}
}
new_favoreds.push(*elem);
}
*meta.refcnt_mut() = new_favoreds.len() as isize;
}
if new_favoreds.is_empty() {
drop(
state
.corpus()
.get(idx)?
.borrow_mut()
.metadata_mut()
.remove::<M>(),
);
return Ok(());
}
for elem in new_favoreds {
state
.metadata_mut()
.get_mut::<TopRatedsMetadata>()
.unwrap()
.map
.insert(elem, idx);
}
Ok(())
}
#[allow(clippy::unused_self)]
pub fn cull(&self, state: &mut S) -> Result<(), Error> {
let top_rated = match state.metadata().get::<TopRatedsMetadata>() {
None => return Ok(()),
Some(val) => val,
};
let mut acc = HashSet::new();
for (key, idx) in &top_rated.map {
if !acc.contains(key) {
let mut entry = state.corpus().get(*idx)?.borrow_mut();
let meta = entry.metadata().get::<M>().ok_or_else(|| {
Error::key_not_found(format!(
"Metadata needed for MinimizerScheduler not found in testcase #{}",
idx
))
})?;
for elem in meta.as_slice() {
acc.insert(*elem);
}
entry.add_metadata(IsFavoredMetadata {});
}
}
Ok(())
}
pub fn base(&self) -> &CS {
&self.base
}
pub fn new(base: CS) -> Self {
Self {
base,
skip_non_favored_prob: DEFAULT_SKIP_NON_FAVORED_PROB,
phantom: PhantomData,
}
}
pub fn with_skip_prob(base: CS, skip_non_favored_prob: u64) -> Self {
Self {
base,
skip_non_favored_prob,
phantom: PhantomData,
}
}
}
pub type LenTimeMinimizerScheduler<CS, I, M, S> =
MinimizerScheduler<CS, LenTimeMulTestcaseScore<I, S>, I, M, S>;
pub type IndexesLenTimeMinimizerScheduler<CS, I, S> =
MinimizerScheduler<CS, LenTimeMulTestcaseScore<I, S>, I, MapIndexesMetadata, S>;