scheduler, mutator changes

This commit is contained in:
Alwin Berger 2024-05-22 21:54:07 +02:00
parent c533b7e184
commit bde16f8297
6 changed files with 253 additions and 130 deletions

View File

@ -5,7 +5,7 @@ authors = ["Alwin Berger <alwin.berger@tu-dortmund.de>"]
edition = "2021" edition = "2021"
[features] [features]
default = ["std", "snapshot_restore", "singlecore", "restarting", "do_hash_notify_state", "config_stg" ] default = ["std", "snapshot_restore", "singlecore", "restarting", "do_hash_notify_state", "config_stg", "fuzz_int" ]
std = [] std = []
# Exec environemnt basics # Exec environemnt basics
snapshot_restore = [] snapshot_restore = []
@ -26,6 +26,7 @@ feed_stg = [ "trace_stg", "observe_systemstate" ]
feed_stg_pathhash = [ "feed_stg"] feed_stg_pathhash = [ "feed_stg"]
feed_stg_abbhash = [ "feed_stg"] feed_stg_abbhash = [ "feed_stg"]
feed_stg_aggregatehash = [ "feed_stg"] feed_stg_aggregatehash = [ "feed_stg"]
mutate_stg = [ "observe_systemstate" ]
feed_longest = [ ] feed_longest = [ ]
feed_afl = [ "observe_edges" ] feed_afl = [ "observe_edges" ]
feed_genetic = [] feed_genetic = []
@ -44,7 +45,7 @@ sched_stg_aggregatehash = ['sched_stg'] # every aggregated path (order independe
config_genetic = ["gensize_100","feed_genetic","sched_genetic"] config_genetic = ["gensize_100","feed_genetic","sched_genetic"]
config_afl = ["feed_afl","sched_afl","observe_hitcounts"] config_afl = ["feed_afl","sched_afl","observe_hitcounts"]
config_frafl = ["feed_afl","sched_afl","feed_longest"] config_frafl = ["feed_afl","sched_afl","feed_longest"]
config_stg = ["feed_stg","sched_stg"] config_stg = ["feed_stg_aggregatehash","sched_stg_aggregatehash","mutate_stg"]
[profile.release] [profile.release]
lto = true lto = true

View File

@ -4,6 +4,7 @@
use core::marker::PhantomData; use core::marker::PhantomData;
use std::cmp::{max, min}; use std::cmp::{max, min};
use hashbrown::HashMap;
use libafl_bolts::rands::{ use libafl_bolts::rands::{
StdRand, RandomSeed, StdRand, RandomSeed,
Rand Rand
@ -12,7 +13,7 @@ use libafl::{
corpus::{self, Corpus}, fuzzer::Evaluator, mark_feature_time, prelude::{new_hash_feedback, CorpusId, HasBytesVec, MutationResult, Mutator, UsesInput}, stages::Stage, start_timer, state::{HasCorpus, HasMetadata, HasNamedMetadata, HasRand, MaybeHasClientPerfMonitor, UsesState}, Error corpus::{self, Corpus}, fuzzer::Evaluator, mark_feature_time, prelude::{new_hash_feedback, CorpusId, HasBytesVec, MutationResult, Mutator, UsesInput}, stages::Stage, start_timer, state::{HasCorpus, HasMetadata, HasNamedMetadata, HasRand, MaybeHasClientPerfMonitor, UsesState}, Error
}; };
use libafl::prelude::State; use libafl::prelude::State;
use crate::{clock::IcHist, fuzzer::{DO_NUM_INTERRUPT, FIRST_INT}, systemstate::{stg::{STGFeedbackState, STGNodeMetadata}, ExecInterval, FreeRTOSSystemStateMetadata, ReducedFreeRTOSSystemState}}; use crate::{clock::IcHist, fuzzer::{DO_NUM_INTERRUPT, FIRST_INT}, systemstate::{stg::{STGFeedbackState, STGNodeMetadata}, CaptureEvent, ExecInterval, FreeRTOSSystemStateMetadata, ReducedFreeRTOSSystemState}};
pub static mut MINIMUM_INTER_ARRIVAL_TIME : u32 = 1000 /*ms*/ * 62500; pub static mut MINIMUM_INTER_ARRIVAL_TIME : u32 = 1000 /*ms*/ * 62500;
// one isn per 2**4 ns // one isn per 2**4 ns
@ -89,9 +90,8 @@ where
.get(corpus_idx)? .get(corpus_idx)?
.borrow_mut().clone(); .borrow_mut().clone();
let mut newinput = _input.input_mut().as_mut().unwrap().clone(); let mut newinput = _input.input_mut().as_mut().unwrap().clone();
// let mut tmpinput = _input.input_mut().as_mut().unwrap().clone();
let mut do_rerun = false; let mut do_rerun = false;
if state.rand_mut().between(1, 100) <= 50 // only attempt the mutation half of the time // if state.rand_mut().between(1, 100) <= 50 // only attempt the mutation half of the time
{ {
// need our own random generator, because borrowing rules // need our own random generator, because borrowing rules
let mut myrand = StdRand::new(); let mut myrand = StdRand::new();
@ -120,19 +120,31 @@ where
let mut suffix = target_bytes.split_off(4 * num_interrupts); let mut suffix = target_bytes.split_off(4 * num_interrupts);
let mut prefix : Vec<[u8; 4]> = vec![]; let mut prefix : Vec<[u8; 4]> = vec![];
// let mut suffix : Vec<u8> = vec![]; // let mut suffix : Vec<u8> = vec![];
#[cfg(feature = "trace_stg")] #[cfg(feature = "mutate_stg")]
{ {
if myrand.between(1,100) <= 25 { // 0.5*0.25 = 12.5% of the time fully randomize all interrupts
do_rerun = true;
let metadata = state.metadata_map(); let metadata = state.metadata_map();
let hist = metadata.get::<IcHist>().unwrap(); let hist = metadata.get::<IcHist>().unwrap();
let maxtick : u64 = hist.1.0; let maxtick : u64 = hist.1.0;
drop(hist);
if interrupt_offsets[0] as u64 > maxtick {
do_rerun = true;
for _ in 0..num_interrupts {
prefix.push(u32::to_le_bytes(myrand.between(0, min(maxtick, u32::MAX as u64)).try_into().expect("ticks > u32")));
}
} else {
let choice = myrand.between(1,100);
if choice <= 25 { // 0.5*0.25 = 12.5% of the time fully randomize all interrupts
do_rerun = true;
// let metadata = state.metadata_map();
let hist = metadata.get::<IcHist>().unwrap();
let maxtick : u64 = hist.1.0;
// let maxtick : u64 = (_input.exec_time().expect("No duration found").as_nanos() >> 4).try_into().unwrap(); // let maxtick : u64 = (_input.exec_time().expect("No duration found").as_nanos() >> 4).try_into().unwrap();
let mut numbers : Vec<u32> = vec![]; let mut numbers : Vec<u32> = vec![];
for i in 0..num_interrupts { for i in 0..num_interrupts {
prefix.push(u32::to_le_bytes(myrand.between(0, min(maxtick, u32::MAX as u64)).try_into().expect("ticks > u32"))); prefix.push(u32::to_le_bytes(myrand.between(0, min(maxtick, u32::MAX as u64)).try_into().expect("ticks > u32")));
} }
} else { }
else if choice <= 75 { // 0.5 * 0.25 = 12.5% of cases
let feedbackstate = match state let feedbackstate = match state
.named_metadata_map_mut() .named_metadata_map_mut()
.get_mut::<STGFeedbackState>("stgfeedbackstate") { .get_mut::<STGFeedbackState>("stgfeedbackstate") {
@ -141,7 +153,55 @@ where
panic!("STGfeedbackstate not visible") panic!("STGfeedbackstate not visible")
} }
}; };
let tmp = _input.metadata_map().get::<STGNodeMetadata>();
if tmp.is_some() {
let trace = tmp.expect("STGNodeMetadata not found");
let mut node_indices = vec![];
for i in (0..trace.intervals.len()).into_iter() {
if let Some(abb) = &trace.intervals[i].abb {
if let Some(idx) = feedbackstate.state_abb_hash_index.get(&(abb.get_hash(), trace.intervals[i].start_state)) {
node_indices.push(Some(idx));
continue;
}
}
node_indices.push(None);
}
// let mut marks : HashMap<u32, usize>= HashMap::new(); // interrupt -> block hit
// for i in 0..trace.intervals.len() {
// let curr = &trace.intervals[i];
// let m = interrupt_offsets[0..num_interrupts].iter().filter(|x| (curr.start_tick..curr.end_tick).contains(&((**x) as u64)));
// for k in m {
// marks.insert(*k,i);
// }
// }
// walk backwards trough the trace and try moving the interrupt to a block that does not have an outgoing interrupt edge or ist already hit by a predecessor
for i in (0..num_interrupts).rev() {
let mut lb = FIRST_INT;
let mut ub : u32 = trace.intervals[trace.intervals.len()-1].end_tick.try_into().expect("ticks > u32");
if i > 0 {
lb = u32::saturating_add(interrupt_offsets[i-1],unsafe{MINIMUM_INTER_ARRIVAL_TIME});
}
if i < num_interrupts-1 {
ub = u32::saturating_sub(interrupt_offsets[i+1],unsafe{MINIMUM_INTER_ARRIVAL_TIME});
}
let alternatives : Vec<_> = (0..trace.intervals.len()).filter(|x|
trace.intervals[*x].start_tick < (lb as u64) && (lb as u64) < trace.intervals[*x].end_tick
|| trace.intervals[*x].start_tick > (lb as u64) && trace.intervals[*x].start_tick < (ub as u64)
).collect();
let not_yet_hit : Vec<_> = alternatives.iter().filter(
|x| feedbackstate.graph.edges_directed(*node_indices[**x].unwrap(), petgraph::Direction::Outgoing).any(|y| y.weight().event != CaptureEvent::ISRStart)).collect();
if not_yet_hit.len() > 0 {
let replacement = &trace.intervals[*myrand.choose(not_yet_hit)];
interrupt_offsets[i] = (myrand.between(replacement.start_tick,
replacement.end_tick)).try_into().expect("ticks > u32");
// println!("chose new alternative, i: {} {} -> {}",i,tmp, interrupt_offsets[i]);
do_rerun = true;
break;
}
}
}
}
else { // old version of the alternative search
let tmp = _input.metadata_map().get::<STGNodeMetadata>(); let tmp = _input.metadata_map().get::<STGNodeMetadata>();
if tmp.is_some() { if tmp.is_some() {
let trace = tmp.expect("STGNodeMetadata not found"); let trace = tmp.expect("STGNodeMetadata not found");
@ -165,7 +225,7 @@ where
} }
for i in 0..num_interrupts { for i in 0..num_interrupts {
// bounds based on minimum inter-arrival time // bounds based on minimum inter-arrival time
let mut lb = 0; let mut lb = FIRST_INT;
let mut ub : u32 = marks[marks.len()-1].0.end_tick.try_into().expect("ticks > u32"); let mut ub : u32 = marks[marks.len()-1].0.end_tick.try_into().expect("ticks > u32");
if i > 0 { if i > 0 {
lb = u32::saturating_add(interrupt_offsets[i-1],unsafe{MINIMUM_INTER_ARRIVAL_TIME}); lb = u32::saturating_add(interrupt_offsets[i-1],unsafe{MINIMUM_INTER_ARRIVAL_TIME});
@ -188,7 +248,7 @@ where
x.2 != 2 && x.2 != 2 &&
( (
x.0.start_tick < (lb as u64) && (lb as u64) < x.0.end_tick x.0.start_tick < (lb as u64) && (lb as u64) < x.0.end_tick
|| x.0.start_tick < (ub as u64) && (ub as u64) < x.0.end_tick ) || x.0.start_tick > (lb as u64) && x.0.start_tick < (ub as u64))
).collect(); ).collect();
// in cases there are no alternatives // in cases there are no alternatives
if alternatives.len() == 0 { if alternatives.len() == 0 {
@ -244,6 +304,7 @@ where
} }
} }
} }
}
#[cfg(not(feature = "trace_stg"))] #[cfg(not(feature = "trace_stg"))]
{ {
if myrand.between(1,100) <= 25 { // we have no hint if interrupt times will change anything if myrand.between(1,100) <= 25 { // we have no hint if interrupt times will change anything

View File

@ -336,7 +336,7 @@ impl Ord for AtomicBasicBlock {
} }
impl AtomicBasicBlock { impl AtomicBasicBlock {
fn get_hash(&self) -> u64 { pub fn get_hash(&self) -> u64 {
let mut s = DefaultHasher::new(); let mut s = DefaultHasher::new();
self.hash(&mut s); self.hash(&mut s);
s.finish() s.finish()

View File

@ -344,7 +344,7 @@ fn add_abb_info(trace: &mut Vec<ExecInterval>, table: &HashMap<u64, ReducedFreeR
/// restore the isr/api begin/end invariant /// restore the isr/api begin/end invariant
fn fix_broken_trace(meta: &mut Vec<(u64, CaptureEvent, String, (Option<u32>, Option<u32>))>) { fn fix_broken_trace(meta: &mut Vec<(u64, CaptureEvent, String, (Option<u32>, Option<u32>))>) {
for i in meta.iter_mut() { for i in meta.iter_mut() {
if i.1 == CaptureEvent::APIStart && i.2 == "vTaskGenericNotifyGiveFromISR" { if i.1 == CaptureEvent::APIStart && i.2.ends_with("FromISR") {
i.1 = CaptureEvent::ISREnd; i.1 = CaptureEvent::ISREnd;
i.2 = "isr_starter".to_string(); i.2 = "isr_starter".to_string();
} }

View File

@ -1,4 +1,5 @@
use hashbrown::HashSet;
use libafl::SerdeAny; use libafl::SerdeAny;
/// Feedbacks organizing SystemStates as a graph /// Feedbacks organizing SystemStates as a graph
use libafl::inputs::HasBytesVec; use libafl::inputs::HasBytesVec;
@ -108,8 +109,8 @@ impl PartialEq for STGNode {
pub struct STGEdge pub struct STGEdge
{ {
// is_interrupt: bool, // is_interrupt: bool,
event: CaptureEvent, pub event: CaptureEvent,
name: String pub name: String
} }
impl STGEdge { impl STGEdge {
@ -146,14 +147,15 @@ pub struct STGFeedbackState
// aggregated traces as a graph // aggregated traces as a graph
pub graph: DiGraph<STGNode, STGEdge>, pub graph: DiGraph<STGNode, STGEdge>,
systemstate_index: HashMap<u64, ReducedFreeRTOSSystemState>, systemstate_index: HashMap<u64, ReducedFreeRTOSSystemState>,
state_abb_hash_index: HashMap<(u64, u64), NodeIndex>, pub state_abb_hash_index: HashMap<(u64, u64), NodeIndex>,
stgnode_index: HashMap<u64, NodeIndex>, stgnode_index: HashMap<u64, NodeIndex>,
entrypoint: NodeIndex, entrypoint: NodeIndex,
exitpoint: NodeIndex, exitpoint: NodeIndex,
// Metadata about aggregated traces. aggegated meaning, order has been removed // Metadata about aggregated traces. aggegated meaning, order has been removed
worst_observed_per_aggegated_path: HashMap<Vec<AtomicBasicBlock>,u64>, worst_observed_per_aggegated_path: HashMap<Vec<AtomicBasicBlock>,u64>,
worst_observed_per_abb_path: HashMap<u64,u64>, worst_observed_per_abb_path: HashMap<u64,u64>,
worst_observed_per_stg_path: HashMap<u64,u64> worst_observed_per_stg_path: HashMap<u64,u64>,
worst_abb_exec_count: HashMap<AtomicBasicBlock, usize>
} }
impl Default for STGFeedbackState { impl Default for STGFeedbackState {
@ -184,6 +186,7 @@ impl Default for STGFeedbackState {
worst_observed_per_aggegated_path: HashMap::new(), worst_observed_per_aggegated_path: HashMap::new(),
worst_observed_per_abb_path: HashMap::new(), worst_observed_per_abb_path: HashMap::new(),
worst_observed_per_stg_path: HashMap::new(), worst_observed_per_stg_path: HashMap::new(),
worst_abb_exec_count: HashMap::new(),
systemstate_index, systemstate_index,
state_abb_hash_index state_abb_hash_index
} }
@ -203,13 +206,15 @@ impl Named for STGFeedbackState
pub struct STGNodeMetadata { pub struct STGNodeMetadata {
pub nodes: Vec<NodeIndex>, pub nodes: Vec<NodeIndex>,
pub edges: Vec<EdgeIndex>, pub edges: Vec<EdgeIndex>,
pub abbs: Vec<AtomicBasicBlock>, pub abbs: u64,
pub aggregate: u64,
pub top_abb_counts: Vec<u64>,
pub intervals: Vec<ExecInterval>, pub intervals: Vec<ExecInterval>,
indices: Vec<usize>, indices: Vec<usize>,
tcref: isize, tcref: isize,
} }
impl STGNodeMetadata { impl STGNodeMetadata {
pub fn new(nodes: Vec<NodeIndex>, edges: Vec<EdgeIndex>, abbs: Vec<AtomicBasicBlock>, intervals: Vec<ExecInterval>) -> Self{ pub fn new(nodes: Vec<NodeIndex>, edges: Vec<EdgeIndex>, abbs: u64, aggregate: u64, top_abb_counts: Vec<u64>, intervals: Vec<ExecInterval>) -> Self{
let mut indices : Vec<_> = vec![]; let mut indices : Vec<_> = vec![];
#[cfg(all(feature = "sched_stg",not(any(feature = "sched_stg_pathhash",feature = "sched_stg_abbhash",feature = "sched_stg_aggregatehash"))))] #[cfg(all(feature = "sched_stg",not(any(feature = "sched_stg_pathhash",feature = "sched_stg_abbhash",feature = "sched_stg_aggregatehash"))))]
{ {
@ -223,15 +228,14 @@ impl STGNodeMetadata {
} }
#[cfg(feature = "sched_stg_abbhash")] #[cfg(feature = "sched_stg_abbhash")]
{ {
indices.push(get_generic_hash(&abbs) as usize); indices.push(abbs as usize);
} }
#[cfg(feature = "sched_stg_aggregatehash")] #[cfg(feature = "sched_stg_aggregatehash")]
{ {
let mut _abbs = abbs.clone(); // indices.push(aggregate as usize);
_abbs.sort_unstable(); indices = top_abb_counts.iter().map(|x| (*x) as usize).collect();
indices.push(get_generic_hash(&_abbs) as usize);
} }
Self {indices, intervals, nodes, abbs, edges, tcref: 0} Self {indices, intervals, nodes, abbs, aggregate, top_abb_counts, edges, tcref: 0}
} }
} }
impl AsSlice for STGNodeMetadata { impl AsSlice for STGNodeMetadata {
@ -258,6 +262,36 @@ libafl_bolts::impl_serdeany!(STGNodeMetadata);
pub type GraphMaximizerCorpusScheduler<CS> = pub type GraphMaximizerCorpusScheduler<CS> =
MinimizerScheduler<CS, MaxTimeFavFactor<<CS as UsesState>::State>,STGNodeMetadata>; MinimizerScheduler<CS, MaxTimeFavFactor<<CS as UsesState>::State>,STGNodeMetadata>;
// AI generated, human verified
fn count_occurrences<T>(vec: &Vec<T>) -> HashMap<&T, usize>
where
T: PartialEq + Eq + Hash + Clone,
{
let mut counts = HashMap::new();
if vec.is_empty() {
return counts;
}
let mut current_obj = &vec[0];
let mut current_count = 1;
for obj in vec.iter().skip(1) {
if obj == current_obj {
current_count += 1;
} else {
counts.insert(current_obj, current_count);
current_obj = obj;
current_count = 1;
}
}
// Insert the count of the last object
counts.insert(current_obj, current_count);
counts
}
//============================= Graph Feedback //============================= Graph Feedback
pub static mut STG_MAP: [u8; EDGES_MAP_SIZE] = [0; EDGES_MAP_SIZE]; pub static mut STG_MAP: [u8; EDGES_MAP_SIZE] = [0; EDGES_MAP_SIZE];
@ -274,7 +308,9 @@ pub struct StgFeedback
last_node_trace: Option<Vec<NodeIndex>>, last_node_trace: Option<Vec<NodeIndex>>,
last_edge_trace: Option<Vec<EdgeIndex>>, last_edge_trace: Option<Vec<EdgeIndex>>,
last_intervals: Option<Vec<ExecInterval>>, last_intervals: Option<Vec<ExecInterval>>,
last_abbs: Option<Vec<AtomicBasicBlock>>, last_abbs_hash: Option<u64>, // only set, if it was interesting
last_aggregate_hash: Option<u64>, // only set, if it was interesting
last_top_abb_hashes: Option<Vec<u64>>, // only set, if it was interesting
dump_path: Option<PathBuf> dump_path: Option<PathBuf>
} }
#[cfg(feature = "feed_stg")] #[cfg(feature = "feed_stg")]
@ -458,6 +494,7 @@ where
if INTEREST_AGGREGATE || INTEREST_ABBPATH { if INTEREST_AGGREGATE || INTEREST_ABBPATH {
if INTEREST_ABBPATH { if INTEREST_ABBPATH {
let h = get_generic_hash(&tmp); let h = get_generic_hash(&tmp);
self.last_abbs_hash = Some(h);
// order of execution is relevant // order of execution is relevant
if let Some(x) = feedbackstate.worst_observed_per_abb_path.get_mut(&h) { if let Some(x) = feedbackstate.worst_observed_per_abb_path.get_mut(&h) {
let t = clock_observer.last_runtime(); let t = clock_observer.last_runtime();
@ -474,6 +511,22 @@ where
// aggegation by sorting, order of states is not relevant // aggegation by sorting, order of states is not relevant
let mut _tmp = tmp.clone(); let mut _tmp = tmp.clone();
_tmp.sort(); _tmp.sort();
let counts = count_occurrences(&_tmp);
let mut top_indices = Vec::new();
for (k,c) in counts {
if let Some(reference) = feedbackstate.worst_abb_exec_count.get_mut(k) {
if *reference < c {
*reference = c;
top_indices.push(get_generic_hash(k));
}
} else {
top_indices.push(get_generic_hash(k));
feedbackstate.worst_abb_exec_count.insert(k.clone(), c);
}
}
self.last_top_abb_hashes = Some(top_indices);
self.last_aggregate_hash = Some(get_generic_hash(&_tmp));
if let Some(x) = feedbackstate.worst_observed_per_aggegated_path.get_mut(&_tmp) { if let Some(x) = feedbackstate.worst_observed_per_aggegated_path.get_mut(&_tmp) {
let t = clock_observer.last_runtime(); let t = clock_observer.last_runtime();
if t > *x { if t > *x {
@ -494,7 +547,6 @@ where
self.last_node_trace = Some(nodetrace); self.last_node_trace = Some(nodetrace);
self.last_edge_trace = Some(edgetrace); self.last_edge_trace = Some(edgetrace);
self.last_intervals = Some(observer.last_trace.clone()); self.last_intervals = Some(observer.last_trace.clone());
self.last_abbs = Some(tmp);
if let Some(dp) = &self.dump_path { if let Some(dp) = &self.dump_path {
if updated { if updated {
@ -516,9 +568,8 @@ where
fn append_metadata<OT>(&mut self, _state: &mut S, _observers: &OT, testcase: &mut Testcase<S::Input>) -> Result<(), Error> { fn append_metadata<OT>(&mut self, _state: &mut S, _observers: &OT, testcase: &mut Testcase<S::Input>) -> Result<(), Error> {
let nodes = self.last_node_trace.take(); let nodes = self.last_node_trace.take();
let edges = self.last_edge_trace.take(); let edges = self.last_edge_trace.take();
let abbs = self.last_abbs.take();
match nodes { match nodes {
Some(s) => testcase.metadata_map_mut().insert(STGNodeMetadata::new(s, edges.unwrap(), abbs.unwrap(), self.last_intervals.take().unwrap())), Some(s) => testcase.metadata_map_mut().insert(STGNodeMetadata::new(s, edges.unwrap(), self.last_abbs_hash.take().unwrap_or_default(), self.last_aggregate_hash.take().unwrap_or_default(), self.last_top_abb_hashes.take().unwrap_or_default(), self.last_intervals.take().unwrap())),
None => (), None => (),
} }
Ok(()) Ok(())

View File

@ -58,6 +58,15 @@ impl TopRatedsMetadata {
pub fn map(&self) -> &HashMap<usize, CorpusId> { pub fn map(&self) -> &HashMap<usize, CorpusId> {
&self.map &self.map
} }
/// Retruns the number of inices that are considered interesting
pub fn get_number(&self) -> usize {
let mut tmp = HashSet::new();
for i in self.map.values() {
tmp.insert(*i);
}
tmp.len()
}
} }
impl Default for TopRatedsMetadata { impl Default for TopRatedsMetadata {
@ -325,6 +334,7 @@ where
.map .map
.insert(elem, idx); .insert(elem, idx);
} }
println!("Number of interesting corpus elements: {}", state.metadata_map_mut().get::<TopRatedsMetadata>().unwrap().get_number());
Ok(()) Ok(())
} }