#![cfg_attr(coverage_nightly, coverage(off))]
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct CoverageInfo {
pub lines_covered: HashSet<u32>,
pub blocks_covered: HashSet<u64>,
pub branches_taken: HashSet<(u64, u64)>,
pub execution_count: u64,
}
impl CoverageInfo {
pub fn new() -> Self {
Self::default()
}
pub fn coverage_percentage(&self, total_lines: u32) -> f64 {
if total_lines == 0 {
return 0.0;
}
(self.lines_covered.len() as f64 / total_lines as f64) * 100.0
}
pub fn covers(&self, other: &CoverageInfo) -> bool {
other.lines_covered.is_subset(&self.lines_covered)
&& other.blocks_covered.is_subset(&self.blocks_covered)
&& other.branches_taken.is_subset(&self.branches_taken)
}
pub fn coverage_increase(&self, baseline: &CoverageInfo) -> f64 {
let new_lines = self
.lines_covered
.difference(&baseline.lines_covered)
.count();
let new_blocks = self
.blocks_covered
.difference(&baseline.blocks_covered)
.count();
let new_branches = self
.branches_taken
.difference(&baseline.branches_taken)
.count();
let total_new = (new_lines * 2 + new_blocks * 3 + new_branches * 5) as f64;
let baseline_size = (baseline.lines_covered.len() * 2
+ baseline.blocks_covered.len() * 3
+ baseline.branches_taken.len() * 5) as f64;
if baseline_size == 0.0 {
return if total_new > 0.0 { 1.0 } else { 0.0 };
}
total_new / baseline_size
}
pub fn merge(&mut self, other: &CoverageInfo) {
self.lines_covered.extend(&other.lines_covered);
self.blocks_covered.extend(&other.blocks_covered);
self.branches_taken.extend(&other.branches_taken);
self.execution_count += other.execution_count;
}
pub fn is_interesting(&self, corpus: &[CoverageInfo]) -> bool {
for existing in corpus {
if existing.covers(self) {
return false; }
}
true
}
}
#[derive(Debug, Clone, Default)]
pub struct CoverageCorpus {
pub interesting_inputs: Vec<(Vec<u8>, CoverageInfo)>,
pub baseline: CoverageInfo,
pub max_coverage: CoverageInfo,
}
impl CoverageCorpus {
pub fn new(baseline: CoverageInfo) -> Self {
Self {
interesting_inputs: Vec::new(),
baseline: baseline.clone(),
max_coverage: baseline,
}
}
pub fn add_if_interesting(&mut self, input: Vec<u8>, coverage: CoverageInfo) -> bool {
if coverage.is_interesting(
&self
.interesting_inputs
.iter()
.map(|(_, c)| c.clone())
.collect::<Vec<_>>(),
) {
self.max_coverage.merge(&coverage);
self.interesting_inputs.push((input, coverage));
true
} else {
false
}
}
pub fn total_coverage_increase(&self) -> f64 {
self.max_coverage.coverage_increase(&self.baseline)
}
pub fn get_seeds(&self, count: usize) -> Vec<Vec<u8>> {
self.interesting_inputs
.iter()
.take(count)
.map(|(input, _)| input.clone())
.collect()
}
}
#[derive(Debug, Clone, Copy)]
pub struct InstrumentationPoint {
pub line: u32,
pub block_id: u64,
}
#[derive(Debug, Default)]
pub struct CoverageTracker {
current: CoverageInfo,
}
impl CoverageTracker {
pub fn new() -> Self {
Self::default()
}
pub fn record_line(&mut self, line: u32) {
self.current.lines_covered.insert(line);
}
pub fn record_block(&mut self, block_id: u64) {
self.current.blocks_covered.insert(block_id);
}
pub fn record_branch(&mut self, from: u64, to: u64) {
self.current.branches_taken.insert((from, to));
}
pub fn take_coverage(&mut self) -> CoverageInfo {
std::mem::take(&mut self.current)
}
pub fn simulate_coverage(input: &[u8]) -> CoverageInfo {
let mut coverage = CoverageInfo::new();
let len = input.len();
for line in 1..=10 {
coverage.lines_covered.insert(line);
}
if len > 10 {
for line in 11..=20 {
coverage.lines_covered.insert(line);
}
}
if len > 50 {
for line in 21..=30 {
coverage.lines_covered.insert(line);
}
}
if len > 100 {
for line in 31..=40 {
coverage.lines_covered.insert(line);
}
}
for (idx, &byte) in input.iter().take(5).enumerate() {
coverage
.blocks_covered
.insert(byte as u64 + idx as u64 * 256);
}
if input.contains(&0xAA) {
coverage.branches_taken.insert((1, 2));
}
if input.contains(&0xBB) {
coverage.branches_taken.insert((2, 3));
}
if input.contains(&0xCC) {
coverage.branches_taken.insert((3, 4));
}
coverage.execution_count = 1;
coverage
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_coverage_info_creation() {
let coverage = CoverageInfo::new();
assert_eq!(coverage.lines_covered.len(), 0);
assert_eq!(coverage.blocks_covered.len(), 0);
}
#[test]
fn test_coverage_percentage() {
let mut coverage = CoverageInfo::new();
coverage.lines_covered.insert(1);
coverage.lines_covered.insert(2);
coverage.lines_covered.insert(3);
assert_eq!(coverage.coverage_percentage(10), 30.0);
}
#[test]
fn test_coverage_increase() {
let mut baseline = CoverageInfo::new();
baseline.lines_covered.insert(1);
baseline.lines_covered.insert(2);
let mut new_coverage = CoverageInfo::new();
new_coverage.lines_covered.insert(1);
new_coverage.lines_covered.insert(2);
new_coverage.lines_covered.insert(3);
new_coverage.lines_covered.insert(4);
let increase = new_coverage.coverage_increase(&baseline);
assert!(increase > 0.0);
}
#[test]
fn test_coverage_merge() {
let mut coverage1 = CoverageInfo::new();
coverage1.lines_covered.insert(1);
coverage1.lines_covered.insert(2);
let mut coverage2 = CoverageInfo::new();
coverage2.lines_covered.insert(3);
coverage2.lines_covered.insert(4);
coverage1.merge(&coverage2);
assert_eq!(coverage1.lines_covered.len(), 4);
}
#[test]
fn test_coverage_corpus() {
let baseline = CoverageInfo::new();
let mut corpus = CoverageCorpus::new(baseline);
let input1 = vec![1, 2, 3];
let mut cov1 = CoverageInfo::new();
cov1.lines_covered.insert(1);
let added = corpus.add_if_interesting(input1, cov1);
assert!(added);
assert_eq!(corpus.interesting_inputs.len(), 1);
}
#[test]
fn test_simulate_coverage_length() {
let short_input = vec![0; 5];
let long_input = vec![0; 150];
let short_cov = CoverageTracker::simulate_coverage(&short_input);
let long_cov = CoverageTracker::simulate_coverage(&long_input);
assert!(long_cov.lines_covered.len() > short_cov.lines_covered.len());
}
#[test]
fn test_simulate_coverage_patterns() {
let input_with_pattern = vec![0xAA, 0xBB, 0xCC];
let input_without_pattern = vec![0x00, 0x11, 0x22];
let cov_with = CoverageTracker::simulate_coverage(&input_with_pattern);
let cov_without = CoverageTracker::simulate_coverage(&input_without_pattern);
assert!(cov_with.branches_taken.len() > cov_without.branches_taken.len());
}
}