use crate::fuzz::types::{
CoverageAdmission, CoverageAdmissionRefusal, CoverageCorpus, CoverageObservation,
CoveragePoint, CoverageStanding, FuzzExecution, InterestingBytes, ReadyPreflight,
RustcProfileRefusal, RustcProfileResult,
};
use std::collections::BTreeSet;
impl InterestingBytes {
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
&self.bytes
}
}
impl CoverageObservation {
pub(crate) fn established(points: BTreeSet<CoveragePoint>) -> Self {
Self {
points: points.into_iter().collect(),
}
}
pub(crate) const fn empty() -> Self {
Self { points: Vec::new() }
}
#[must_use]
pub fn points(&self) -> &[CoveragePoint] {
&self.points
}
}
impl CoverageCorpus {
#[must_use]
pub fn opening(ready: &ReadyPreflight) -> Self {
Self {
standing: ready.standing().clone(),
attempted_cases: 0,
attempted_input_bytes: 0,
observed: BTreeSet::new(),
interesting: Vec::new(),
retained_bytes: 0,
}
}
pub(crate) fn reserve_execution(
&mut self,
ready: &ReadyPreflight,
candidate_bytes: usize,
) -> Result<u32, RustcProfileRefusal> {
if self.standing != *ready.standing() {
return Err(RustcProfileRefusal::CampaignMismatch);
}
let budgets = self.standing.campaign().budgets();
let case_bound = budgets.executions().cases();
if self.attempted_cases >= case_bound {
return Err(RustcProfileRefusal::CaseBudgetExhausted { bound: case_bound });
}
let candidate_bytes = u64::try_from(candidate_bytes).unwrap_or(u64::MAX);
let attempted = self.attempted_input_bytes.saturating_add(candidate_bytes);
let input_bound = budgets.input_bytes().bytes();
if attempted > input_bound {
return Err(RustcProfileRefusal::InputBudgetExhausted {
bound: input_bound,
attempted,
});
}
let case = self.attempted_cases;
self.attempted_cases = self.attempted_cases.saturating_add(1);
self.attempted_input_bytes = attempted;
Ok(case)
}
pub fn admit(
&mut self,
reading: RustcProfileResult,
) -> Result<CoverageAdmission, CoverageAdmissionRefusal> {
if self.standing != reading.standing {
return Err(CoverageAdmissionRefusal::CampaignMismatch);
}
if reading.execution != FuzzExecution::Success {
return Err(CoverageAdmissionRefusal::Execution(reading.execution));
}
if reading.observation.points().is_empty() {
return Err(CoverageAdmissionRefusal::EmptyObservation);
}
let novel_points = reading
.observation
.points()
.iter()
.filter(|point| !self.observed.contains(*point))
.count();
if novel_points == 0 {
return Ok(CoverageAdmission::Known);
}
let attempted_points = u64::try_from(self.observed.len())
.unwrap_or(u64::MAX)
.saturating_add(u64::try_from(novel_points).unwrap_or(u64::MAX));
let budgets = self.standing.campaign().budgets();
if attempted_points > budgets.points() {
return Err(CoverageAdmissionRefusal::PointBudgetExhausted {
bound: budgets.points(),
attempted: attempted_points,
});
}
let retained_cases = u32::try_from(self.interesting.len()).unwrap_or(u32::MAX);
let retained_case_bound = budgets.retained_cases().cases();
if retained_cases >= retained_case_bound {
return Err(CoverageAdmissionRefusal::RetainedCaseBudgetExhausted {
bound: retained_case_bound,
});
}
let candidate_bytes = u64::try_from(reading.candidate.len()).unwrap_or(u64::MAX);
let retained_bytes = self.retained_bytes.saturating_add(candidate_bytes);
let retained_byte_bound = budgets.retained_bytes().bytes();
if retained_bytes > retained_byte_bound {
return Err(CoverageAdmissionRefusal::RetainedByteBudgetExhausted {
bound: retained_byte_bound,
attempted: retained_bytes,
});
}
self.observed
.extend(reading.observation.points().iter().cloned());
let interesting = InterestingBytes {
bytes: reading.candidate,
};
self.interesting.push(interesting.clone());
self.retained_bytes = retained_bytes;
Ok(CoverageAdmission::Interesting(interesting))
}
#[must_use]
pub const fn standing(&self) -> &CoverageStanding {
&self.standing
}
#[must_use]
pub const fn attempted_cases(&self) -> u32 {
self.attempted_cases
}
#[must_use]
pub const fn attempted_input_bytes(&self) -> u64 {
self.attempted_input_bytes
}
#[must_use]
pub const fn observed(&self) -> &BTreeSet<CoveragePoint> {
&self.observed
}
#[must_use]
pub fn interesting(&self) -> &[InterestingBytes] {
&self.interesting
}
#[must_use]
pub const fn retained_bytes(&self) -> u64 {
self.retained_bytes
}
}
impl RustcProfileResult {
pub(crate) const fn established(
case: u32,
candidate: Vec<u8>,
execution: FuzzExecution,
observation: CoverageObservation,
standing: CoverageStanding,
) -> Self {
Self {
case,
candidate,
execution,
observation,
standing,
}
}
#[must_use]
pub const fn case(&self) -> u32 {
self.case
}
#[must_use]
pub fn candidate(&self) -> &[u8] {
&self.candidate
}
#[must_use]
pub const fn execution(&self) -> FuzzExecution {
self.execution
}
#[must_use]
pub const fn observation(&self) -> &CoverageObservation {
&self.observation
}
#[must_use]
pub const fn standing(&self) -> &CoverageStanding {
&self.standing
}
}