use alloc::vec::Vec;
use core::fmt::Debug;
use hashbrown::HashMap;
use serde::{Deserialize, Serialize};
use crate::{
bolts::{rands::Rand, AsMutSlice, AsSlice, HasLen, HasRefCnt},
corpus::{Corpus, CorpusId, Testcase},
feedbacks::MapIndexesMetadata,
inputs::UsesInput,
schedulers::{
minimizer::{IsFavoredMetadata, MinimizerScheduler, DEFAULT_SKIP_NON_FAVORED_PROB},
LenTimeMulTestcaseScore, Scheduler,
},
state::{HasCorpus, HasMetadata, HasRand, UsesState},
Error,
};
#[derive(Debug, Serialize, Deserialize)]
pub struct AccountingIndexesMetadata {
pub list: Vec<usize>,
pub tcref: isize,
}
crate::impl_serdeany!(AccountingIndexesMetadata);
impl AsSlice for AccountingIndexesMetadata {
type Entry = usize;
fn as_slice(&self) -> &[usize] {
self.list.as_slice()
}
}
impl AsMutSlice for AccountingIndexesMetadata {
type Entry = usize;
fn as_mut_slice(&mut self) -> &mut [usize] {
self.list.as_mut_slice()
}
}
impl HasRefCnt for AccountingIndexesMetadata {
fn refcnt(&self) -> isize {
self.tcref
}
fn refcnt_mut(&mut self) -> &mut isize {
&mut self.tcref
}
}
impl AccountingIndexesMetadata {
#[must_use]
pub fn new(list: Vec<usize>) -> Self {
Self { list, tcref: 0 }
}
#[must_use]
pub fn with_tcref(list: Vec<usize>, tcref: isize) -> Self {
Self { list, tcref }
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct TopAccountingMetadata {
pub map: HashMap<usize, CorpusId>,
pub changed: bool,
pub max_accounting: Vec<u32>,
}
crate::impl_serdeany!(TopAccountingMetadata);
impl TopAccountingMetadata {
#[must_use]
pub fn new(acc_len: usize) -> Self {
Self {
map: HashMap::default(),
changed: false,
max_accounting: vec![0; acc_len],
}
}
}
#[derive(Debug)]
pub struct CoverageAccountingScheduler<'a, CS>
where
CS: UsesState,
CS::State: Debug,
{
accounting_map: &'a [u32],
skip_non_favored_prob: u64,
inner: MinimizerScheduler<
CS,
LenTimeMulTestcaseScore<<CS as UsesState>::State>,
MapIndexesMetadata,
>,
}
impl<'a, CS> UsesState for CoverageAccountingScheduler<'a, CS>
where
CS: UsesState,
CS::State: Debug,
{
type State = CS::State;
}
impl<'a, CS> Scheduler for CoverageAccountingScheduler<'a, CS>
where
CS: Scheduler,
CS::State: HasCorpus + HasMetadata + HasRand + Debug,
<CS::State as UsesInput>::Input: HasLen,
{
fn on_add(&self, state: &mut Self::State, idx: CorpusId) -> Result<(), Error> {
self.update_accounting_score(state, idx)?;
self.inner.on_add(state, idx)
}
fn on_replace(
&self,
state: &mut Self::State,
idx: CorpusId,
testcase: &Testcase<<Self::State as UsesInput>::Input>,
) -> Result<(), Error> {
self.inner.on_replace(state, idx, testcase)
}
fn on_remove(
&self,
state: &mut Self::State,
idx: CorpusId,
testcase: &Option<Testcase<<Self::State as UsesInput>::Input>>,
) -> Result<(), Error> {
self.inner.on_remove(state, idx, testcase)
}
fn next(&self, state: &mut Self::State) -> Result<CorpusId, Error> {
if state
.metadata()
.get::<TopAccountingMetadata>()
.map_or(false, |x| x.changed)
{
self.accounting_cull(state)?;
} else {
self.inner.cull(state)?;
}
let mut idx = self.inner.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.inner.base().next(state)?;
}
Ok(idx)
}
}
impl<'a, CS> CoverageAccountingScheduler<'a, CS>
where
CS: Scheduler,
CS::State: HasCorpus + HasMetadata + HasRand + Debug,
<CS::State as UsesInput>::Input: HasLen,
{
#[allow(clippy::unused_self)]
#[allow(clippy::cast_possible_wrap)]
pub fn update_accounting_score(
&self,
state: &mut CS::State,
idx: CorpusId,
) -> Result<(), Error> {
let mut indexes = vec![];
let mut new_favoreds = vec![];
{
for idx in 0..self.accounting_map.len() {
if self.accounting_map[idx] == 0 {
continue;
}
indexes.push(idx);
let mut equal_score = false;
{
let top_acc = state.metadata().get::<TopAccountingMetadata>().unwrap();
if let Some(old_idx) = top_acc.map.get(&idx) {
if top_acc.max_accounting[idx] > self.accounting_map[idx] {
continue;
}
if top_acc.max_accounting[idx] >= self.accounting_map[idx] {
equal_score = true;
}
let mut old = state.corpus().get(*old_idx)?.borrow_mut();
let must_remove = {
let old_meta = old.metadata_mut().get_mut::<AccountingIndexesMetadata>().ok_or_else(|| {
Error::key_not_found(format!(
"AccountingIndexesMetadata, needed by CoverageAccountingScheduler, not found in testcase #{old_idx}"
))
})?;
*old_meta.refcnt_mut() -= 1;
old_meta.refcnt() <= 0
};
if must_remove {
drop(old.metadata_mut().remove::<AccountingIndexesMetadata>());
}
}
}
let top_acc = state
.metadata_mut()
.get_mut::<TopAccountingMetadata>()
.unwrap();
if equal_score {
top_acc.map.remove(&idx);
} else if top_acc.max_accounting[idx] < self.accounting_map[idx] {
new_favoreds.push(idx);
top_acc.max_accounting[idx] = self.accounting_map[idx];
}
}
}
if new_favoreds.is_empty() {
return Ok(());
}
state.corpus().get(idx)?.borrow_mut().metadata_mut().insert(
AccountingIndexesMetadata::with_tcref(indexes, new_favoreds.len() as isize),
);
let top_acc = state
.metadata_mut()
.get_mut::<TopAccountingMetadata>()
.unwrap();
top_acc.changed = true;
for elem in new_favoreds {
top_acc.map.insert(elem, idx);
}
Ok(())
}
#[allow(clippy::unused_self)]
pub fn accounting_cull(&self, state: &mut CS::State) -> Result<(), Error> {
let Some(top_rated) = state.metadata().get::<TopAccountingMetadata>() else { return Ok(()) };
for (_key, idx) in &top_rated.map {
let mut entry = state.corpus().get(*idx)?.borrow_mut();
if entry.fuzzed() {
continue;
}
entry.add_metadata(IsFavoredMetadata {});
}
Ok(())
}
pub fn new(state: &mut CS::State, base: CS, accounting_map: &'a [u32]) -> Self {
match state.metadata().get::<TopAccountingMetadata>() {
Some(meta) => {
if meta.max_accounting.len() != accounting_map.len() {
state.add_metadata(TopAccountingMetadata::new(accounting_map.len()));
}
}
None => {
state.add_metadata(TopAccountingMetadata::new(accounting_map.len()));
}
}
Self {
accounting_map,
inner: MinimizerScheduler::new(base),
skip_non_favored_prob: DEFAULT_SKIP_NON_FAVORED_PROB,
}
}
pub fn with_skip_prob(
state: &mut CS::State,
base: CS,
skip_non_favored_prob: u64,
accounting_map: &'a [u32],
) -> Self {
match state.metadata().get::<TopAccountingMetadata>() {
Some(meta) => {
if meta.max_accounting.len() != accounting_map.len() {
state.add_metadata(TopAccountingMetadata::new(accounting_map.len()));
}
}
None => {
state.add_metadata(TopAccountingMetadata::new(accounting_map.len()));
}
}
Self {
accounting_map,
inner: MinimizerScheduler::with_skip_prob(base, skip_non_favored_prob),
skip_non_favored_prob,
}
}
}