use alloc::{borrow::Cow, vec::Vec};
use core::{fmt::Debug, num::NonZero};
use libafl_bolts::{
Named, impl_serdeany, math::calculate_cumulative_distribution_in_place, rands::Rand,
tuples::NamedTuple,
};
use serde::{Deserialize, Serialize};
pub use crate::mutators::{mutations::*, token_mutations::*};
use crate::{
Error, HasMetadata,
mutators::{
ComposedByMutations, MutationId, MutationResult, Mutator, MutatorsTuple, ScheduledMutator,
},
state::HasRand,
};
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
#[cfg_attr(
any(not(feature = "serdeany_autoreg"), miri),
expect(clippy::unsafe_derive_deserialize)
)] pub struct TuneableScheduledMutatorMetadata {
pub mutation_ids: Vec<MutationId>,
pub next_id: MutationId,
pub mutation_probabilities_cumulative: Vec<f32>,
pub iters: Option<u64>,
pub iter_probabilities_pow_cumulative: Vec<f32>,
}
impl_serdeany!(TuneableScheduledMutatorMetadata);
impl Default for TuneableScheduledMutatorMetadata {
fn default() -> Self {
Self {
mutation_ids: Vec::default(),
next_id: 0.into(),
mutation_probabilities_cumulative: Vec::default(),
iters: None,
iter_probabilities_pow_cumulative: Vec::default(),
}
}
}
impl TuneableScheduledMutatorMetadata {
pub fn get<S: HasMetadata>(state: &S) -> Result<&Self, Error> {
state
.metadata_map()
.get::<Self>()
.ok_or_else(|| Error::illegal_state("TuneableScheduledMutator not in use"))
}
pub fn get_mut<S: HasMetadata>(state: &mut S) -> Result<&mut Self, Error> {
state
.metadata_map_mut()
.get_mut::<Self>()
.ok_or_else(|| Error::illegal_state("TuneableScheduledMutator not in use"))
}
}
#[derive(Debug)]
pub struct TuneableScheduledMutator<MT> {
name: Cow<'static, str>,
mutations: MT,
max_stack_pow: usize,
}
impl<I, MT, S> Mutator<I, S> for TuneableScheduledMutator<MT>
where
MT: MutatorsTuple<I, S>,
S: HasRand + HasMetadata,
{
#[inline]
fn mutate(&mut self, state: &mut S, input: &mut I) -> Result<MutationResult, Error> {
self.scheduled_mutate(state, input)
}
#[inline]
fn post_exec(
&mut self,
_state: &mut S,
_new_corpus_id: Option<crate::corpus::CorpusId>,
) -> Result<(), Error> {
Ok(())
}
}
impl<MT> ComposedByMutations for TuneableScheduledMutator<MT> {
type Mutations = MT;
#[inline]
fn mutations(&self) -> &MT {
&self.mutations
}
#[inline]
fn mutations_mut(&mut self) -> &mut MT {
&mut self.mutations
}
}
impl<MT> Named for TuneableScheduledMutator<MT> {
fn name(&self) -> &Cow<'static, str> {
&self.name
}
}
impl<I, MT, S> ScheduledMutator<I, S> for TuneableScheduledMutator<MT>
where
MT: MutatorsTuple<I, S>,
S: HasRand + HasMetadata,
{
fn iterations(&self, state: &mut S, _: &I) -> u64 {
let metadata = TuneableScheduledMutatorMetadata::get_mut(state).unwrap();
if metadata.iter_probabilities_pow_cumulative.is_empty() {
if let Some(iters) = metadata.iters {
iters
} else {
1 << (1 + state.rand_mut().below_or_zero(self.max_stack_pow))
}
} else {
let coin = state.rand_mut().next_float() as f32;
let metadata = TuneableScheduledMutatorMetadata::get_mut(state).unwrap();
let power = metadata
.iter_probabilities_pow_cumulative
.iter()
.position(|i| *i >= coin)
.unwrap();
1 << (1 + power)
}
}
fn schedule(&self, state: &mut S, _: &I) -> MutationId {
let metadata = TuneableScheduledMutatorMetadata::get_mut(state).unwrap();
if !metadata.mutation_ids.is_empty() {
let ret = metadata.mutation_ids[metadata.next_id.0];
metadata.next_id.0 += 1_usize;
if metadata.next_id.0 >= metadata.mutation_ids.len() {
metadata.next_id = 0.into();
}
debug_assert!(
self.mutations.len() > ret.0,
"TuneableScheduler: next vec may not contain id larger than number of mutations!"
);
return ret;
}
if !metadata.mutation_probabilities_cumulative.is_empty() {
let coin = state.rand_mut().next_float() as f32;
let metadata = TuneableScheduledMutatorMetadata::get_mut(state).unwrap();
debug_assert_eq!(
self.mutations.len(),
metadata.mutation_probabilities_cumulative.len(),
"TuneableScheduler: mutation probabilities do not match with number of mutations"
);
let mutation_id = metadata
.mutation_probabilities_cumulative
.iter()
.position(|i| *i >= coin)
.unwrap()
.into();
return mutation_id;
}
state
.rand_mut()
.below(NonZero::new(self.mutations.len()).expect("No mutations provided!"))
.into()
}
}
impl<MT> TuneableScheduledMutator<MT> {
pub fn new<S>(state: &mut S, mutations: MT) -> Self
where
MT: NamedTuple,
S: HasRand + HasMetadata,
{
if !state.has_metadata::<TuneableScheduledMutatorMetadata>() {
state.add_metadata(TuneableScheduledMutatorMetadata::default());
}
TuneableScheduledMutator {
name: Cow::from(format!("TuneableMutator[{}]", mutations.names().join(", "))),
mutations,
max_stack_pow: 7,
}
}
}
impl<MT> TuneableScheduledMutator<MT> {
pub fn set_iters<S>(&self, state: &mut S, iters: u64)
where
S: HasMetadata,
{
let metadata = TuneableScheduledMutatorMetadata::get_mut(state).unwrap();
metadata.iters = Some(iters);
metadata.iter_probabilities_pow_cumulative.clear();
}
pub fn set_iter_probabilities_pow<S>(
&self,
state: &mut S,
mut iter_probabilities_pow: Vec<f32>,
) -> Result<(), Error>
where
S: HasMetadata,
{
if iter_probabilities_pow.len() >= 32 {
return Err(Error::illegal_argument(
"Cannot stack more than 2^32 mutations",
));
}
let metadata = TuneableScheduledMutatorMetadata::get_mut(state).unwrap();
metadata.iters = None;
calculate_cumulative_distribution_in_place(&mut iter_probabilities_pow)?;
metadata.iter_probabilities_pow_cumulative = iter_probabilities_pow;
Ok(())
}
pub fn get_iters<S>(&self, state: &S) -> Option<u64>
where
S: HasMetadata,
{
let metadata = TuneableScheduledMutatorMetadata::get(state).unwrap();
metadata.iters
}
pub fn set_mutation_ids<S>(&self, state: &mut S, mutations: Vec<MutationId>)
where
S: HasMetadata,
{
let metadata = TuneableScheduledMutatorMetadata::get_mut(state).unwrap();
metadata.mutation_ids = mutations;
metadata.next_id = 0.into();
}
pub fn set_mutation_probabilities<S>(
&self,
state: &mut S,
mut mutation_probabilities: Vec<f32>,
) -> Result<(), Error>
where
S: HasMetadata,
{
let metadata = TuneableScheduledMutatorMetadata::get_mut(state).unwrap();
metadata.mutation_ids.clear();
metadata.next_id = 0.into();
calculate_cumulative_distribution_in_place(&mut mutation_probabilities)?;
metadata.mutation_probabilities_cumulative = mutation_probabilities;
Ok(())
}
pub fn set_mutation_ids_and_iters<S>(
&self,
state: &mut S,
mutations: Vec<MutationId>,
iters: u64,
) where
S: HasMetadata,
{
let metadata = TuneableScheduledMutatorMetadata::get_mut(state).unwrap();
metadata.mutation_ids = mutations;
metadata.next_id = 0.into();
metadata.iters = Some(iters);
}
pub fn push_mutation_id<S>(state: &mut S, mutation_id: MutationId)
where
S: HasMetadata,
{
let metadata = TuneableScheduledMutatorMetadata::get_mut(state).unwrap();
metadata.mutation_ids.push(mutation_id);
}
pub fn reset<S>(self, state: &mut S)
where
S: HasMetadata,
{
let metadata = state
.metadata_map_mut()
.get_mut::<TuneableScheduledMutatorMetadata>()
.unwrap();
metadata.mutation_ids.clear();
metadata.next_id = 0.into();
metadata.iters = None;
metadata.mutation_probabilities_cumulative.clear();
metadata.iter_probabilities_pow_cumulative.clear();
}
}
#[cfg(test)]
mod test {
use libafl_bolts::tuples::tuple_list;
use super::{
BitFlipMutator, ByteDecMutator, TuneableScheduledMutator, TuneableScheduledMutatorMetadata,
};
use crate::{
inputs::BytesInput,
mutators::{ByteRandMutator, ScheduledMutator},
state::NopState,
};
#[test]
fn test_tuning() {
#[cfg(any(not(feature = "serdeany_autoreg"), miri))]
unsafe {
TuneableScheduledMutatorMetadata::register();
}
let mut state: NopState<BytesInput> = NopState::new();
let mutators = tuple_list!(
BitFlipMutator::new(),
ByteDecMutator::new(),
ByteRandMutator::new()
);
let tuneable = TuneableScheduledMutator::new(&mut state, mutators);
let input = BytesInput::new(vec![42]);
let metadata = TuneableScheduledMutatorMetadata::get_mut(&mut state).unwrap();
metadata.mutation_ids.push(1.into());
metadata.mutation_ids.push(2.into());
assert_eq!(tuneable.schedule(&mut state, &input), 1.into());
assert_eq!(tuneable.schedule(&mut state, &input), 2.into());
assert_eq!(tuneable.schedule(&mut state, &input), 1.into());
}
#[test]
fn test_mutation_distribution() {
#[cfg(any(not(feature = "serdeany_autoreg"), miri))]
unsafe {
TuneableScheduledMutatorMetadata::register();
}
let mut state: NopState<BytesInput> = NopState::new();
let mutators = tuple_list!(
BitFlipMutator::new(),
ByteDecMutator::new(),
ByteRandMutator::new()
);
let tuneable = TuneableScheduledMutator::new(&mut state, mutators);
let input = BytesInput::new(vec![42]);
assert!(
tuneable
.set_mutation_probabilities(&mut state, vec![0.0])
.is_err()
);
assert!(
tuneable
.set_mutation_probabilities(&mut state, vec![1.0; 3])
.is_err()
);
assert!(
tuneable
.set_mutation_probabilities(&mut state, vec![-1.0, 1.0, 1.0])
.is_err()
);
assert!(
tuneable
.set_mutation_probabilities(&mut state, vec![])
.is_err()
);
assert!(
tuneable
.set_mutation_probabilities(&mut state, vec![0.0, 0.0, 1.0])
.is_ok()
);
assert_eq!(tuneable.schedule(&mut state, &input), 2.into());
assert!(
tuneable
.set_mutation_probabilities(&mut state, vec![0.0, 1.0, 0.0])
.is_ok()
);
assert_eq!(tuneable.schedule(&mut state, &input), 1.into());
assert!(
tuneable
.set_mutation_probabilities(&mut state, vec![1.0, 0.0, 0.0])
.is_ok()
);
assert_eq!(tuneable.schedule(&mut state, &input), 0.into());
assert!(
tuneable
.set_mutation_probabilities(&mut state, vec![0.5, 0.0, 0.5])
.is_ok()
);
assert!(tuneable.schedule(&mut state, &input) != 1.into());
}
}