diff --git a/font-types/src/fixed.rs b/font-types/src/fixed.rs index 4463ecea0..14635ad61 100644 --- a/font-types/src/fixed.rs +++ b/font-types/src/fixed.rs @@ -80,6 +80,12 @@ macro_rules! fixed_impl { Self(self.0.saturating_add(other.0)) } + /// Checked addition. + #[inline(always)] + pub fn checked_add(self, other: Self) -> Option { + self.0.checked_add(other.0).map(|inner| Self(inner)) + } + /// Wrapping substitution. #[inline(always)] pub const fn wrapping_sub(self, other: Self) -> Self { diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index a999c6633..367e01c8d 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -58,6 +58,12 @@ path = "fuzz_targets/fuzz_int_set.rs" test = false doc = false +[[bin]] +name = "fuzz_range_set" +path = "fuzz_targets/fuzz_range_set.rs" +test = false +doc = false + [[bin]] name = "fuzz_ift_patch_group" path = "fuzz_targets/fuzz_ift_patch_group.rs" diff --git a/fuzz/fuzz_targets/fuzz_range_set.rs b/fuzz/fuzz_targets/fuzz_range_set.rs new file mode 100644 index 000000000..14988a6d0 --- /dev/null +++ b/fuzz/fuzz_targets/fuzz_range_set.rs @@ -0,0 +1,94 @@ +#![no_main] +//! Fuzzes the incremental_font_transfer patch_group.rs API + +use std::ops::RangeInclusive; + +use libfuzzer_sys::{arbitrary, fuzz_target}; +use read_fonts::collections::{IntSet, RangeSet}; + +#[derive(Debug, arbitrary::Arbitrary)] +enum Operation { + Insert(RangeInclusive), + Extend(Vec>), + Intersection, + Iter, +} + +#[derive(Debug, arbitrary::Arbitrary)] +enum SetIndex { + One, + Two, +} + +#[derive(Default)] +struct State { + range_set: RangeSet, + int_set: IntSet, +} + +const OP_COUNT_LIMIT: u64 = 1000; + +fn range_len(range: &RangeInclusive) -> u64 { + if range.end() > range.start() { + let count = *range.end() as u64 - *range.start() as u64; + count.saturating_mul(count.ilog2() as u64) + } else { + 0 + } +} + +fuzz_target!(|operations: Vec<(Operation, SetIndex)>| { + let mut state1: State = Default::default(); + let mut state2: State = Default::default(); + let mut op_count = 0u64; + for (op, index) in operations { + let (state, state_other) = match index { + SetIndex::One => (&mut state1, &mut state2), + SetIndex::Two => (&mut state2, &mut state1), + }; + + match op { + Operation::Insert(range) => { + op_count = op_count.saturating_add(range_len(&range)); + if op_count > OP_COUNT_LIMIT { + return; + } + + state.range_set.insert(range.clone()); + state.int_set.insert_range(range.clone()); + } + Operation::Extend(ranges) => { + for range in ranges.iter() { + op_count = op_count.saturating_add(range_len(range)); + if op_count > OP_COUNT_LIMIT { + return; + } + state.int_set.insert_range(range.clone()); + } + state.range_set.extend(ranges.into_iter()); + } + Operation::Iter => { + op_count = op_count.saturating_add(state.int_set.iter_ranges().count() as u64); + if op_count > OP_COUNT_LIMIT { + return; + } + + assert!(state.range_set.iter().eq(state.int_set.iter_ranges())); + } + Operation::Intersection => { + op_count = op_count.saturating_add(state.int_set.len()); + op_count = op_count.saturating_add(state_other.int_set.len()); + if op_count > OP_COUNT_LIMIT { + return; + } + + let mut tmp = state.int_set.clone(); + tmp.intersect(&state_other.int_set); + assert!(state + .range_set + .intersection(&state_other.range_set) + .eq(tmp.iter_ranges())); + } + } + } +}); diff --git a/read-fonts/src/collections.rs b/read-fonts/src/collections.rs index 5f75365d5..cf9df63f3 100644 --- a/read-fonts/src/collections.rs +++ b/read-fonts/src/collections.rs @@ -2,3 +2,6 @@ pub mod int_set; pub use int_set::IntSet; + +mod range_set; +pub use range_set::RangeSet; diff --git a/read-fonts/src/collections/range_set.rs b/read-fonts/src/collections/range_set.rs new file mode 100644 index 000000000..e9b2252d2 --- /dev/null +++ b/read-fonts/src/collections/range_set.rs @@ -0,0 +1,395 @@ +//! Stores a disjoint collection of ranges over numeric types. +//! +//! Overlapping and adjacent ranges are automatically merged together. + +use core::{ + cmp::{max, min}, + fmt::{Debug, Formatter}, + iter::Peekable, + ops::RangeInclusive, +}; +use std::collections::BTreeMap; + +use types::Fixed; + +#[derive(Default, Clone, PartialEq, Eq)] +/// A set of disjoint ranges over numeric types. +/// +/// Overlapping and adjacent ranges are automatically merged together. +pub struct RangeSet { + // an entry in the map ranges[a] = b implies there is an range [a, b] (inclusive) in this set. + ranges: BTreeMap, +} + +/// Allows a two values to be tested for adjacency. +pub trait OrdAdjacency { + /// Returns true if self is adjacent on either side of rhs. + fn are_adjacent(self, rhs: Self) -> bool; +} + +impl RangeSet +where + T: Ord + Copy + OrdAdjacency, +{ + /// Insert a range into this set, automatically merging with existing ranges as needed. + pub fn insert(&mut self, range: RangeInclusive) { + if range.end() < range.start() { + // ignore or malformed ranges. + return; + } + + let mut start = *range.start(); + let mut end = *range.end(); + + // There may be up to one intersecting range prior to this new range, check for it and merge if needed. + if let Some((prev_start, prev_end)) = self.prev_range(start) { + if range_is_subset(start, end, prev_start, prev_end) { + return; + } + if ranges_overlap_or_adjacent(start, end, prev_start, prev_end) { + start = min(start, prev_start); + end = max(end, prev_end); + self.ranges.remove(&prev_start); + } + }; + + // There may be one or more ranges proceeding this new range that intersect, find and merge them as needed. + loop { + let Some((next_start, next_end)) = self.next_range(start) else { + // No existing ranges which might overlap, can now insert the current range + self.ranges.insert(start, end); + return; + }; + + if range_is_subset(start, end, next_start, next_end) { + return; + } + if ranges_overlap_or_adjacent(start, end, next_start, next_end) { + start = min(start, next_start); + end = max(end, next_end); + self.ranges.remove(&next_start); + } else { + self.ranges.insert(start, end); + return; + } + } + } + + /// Returns an iterator over the contained ranges. + pub fn iter(&'_ self) -> impl Iterator> + '_ { + self.ranges.iter().map(|(a, b)| *a..=*b) + } + + /// Returns an iterator over the intersection of this and other. + pub fn intersection<'a>( + &'a self, + other: &'a Self, + ) -> impl Iterator> + 'a { + IntersectionIter { + it_a: self.iter().peekable(), + it_b: other.iter().peekable(), + } + } + + /// Finds a range in this set with a start greater than or equal to the provided start value. + fn next_range(&self, start: T) -> Option<(T, T)> { + let (next_start, next_end) = self.ranges.range(start..).next()?; + Some((*next_start, *next_end)) + } + + /// Finds a range in this set with a start less than the provided start value. + fn prev_range(&self, start: T) -> Option<(T, T)> { + let (next_start, next_end) = self.ranges.range(..start).next_back()?; + Some((*next_start, *next_end)) + } +} + +impl Extend> for RangeSet +where + T: Copy + Ord + OrdAdjacency, +{ + fn extend>>(&mut self, iter: I) { + iter.into_iter().for_each(|r| self.insert(r)); + } +} + +impl FromIterator> for RangeSet +where + T: Default + Copy + Ord + OrdAdjacency, +{ + fn from_iter>>(iter: I) -> Self { + let mut result: Self = Default::default(); + result.extend(iter); + result + } +} + +impl Debug for RangeSet +where + T: Debug, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { + write!(f, "RangeSet {{")?; + for (start, end) in self.ranges.iter() { + write!(f, "[{:?}, {:?}], ", start, end)?; + } + write!(f, "}}") + } +} + +struct IntersectionIter +where + A: Iterator>, + B: Iterator>, +{ + it_a: Peekable, + it_b: Peekable, +} + +impl Iterator for IntersectionIter +where + A: Iterator>, + B: Iterator>, + T: Ord + Copy, +{ + type Item = RangeInclusive; + + fn next(&mut self) -> Option { + loop { + let (Some(a), Some(b)) = (self.it_a.peek(), self.it_b.peek()) else { + return None; + }; + + let a = a.clone(); + let b = b.clone(); + + match range_intersection(&a, &b) { + Some(intersection) => { + self.step_iterators(&a, &b); + return Some(intersection); + } + None => self.step_iterators(&a, &b), + } + } + } +} + +impl IntersectionIter +where + A: Iterator>, + B: Iterator>, + T: Ord, +{ + fn step_iterators(&mut self, a: &RangeInclusive, b: &RangeInclusive) { + if a.end() <= b.end() { + self.it_a.next(); + } + + if a.end() >= b.end() { + self.it_b.next(); + } + } +} + +impl OrdAdjacency for u32 { + fn are_adjacent(self, rhs: u32) -> bool { + matches!(self.checked_add(1).map(|r| r == rhs), Some(true)) + || matches!(rhs.checked_add(1).map(|r| r == self), Some(true)) + } +} + +impl OrdAdjacency for u16 { + fn are_adjacent(self, rhs: u16) -> bool { + matches!(self.checked_add(1).map(|r| r == rhs), Some(true)) + || matches!(rhs.checked_add(1).map(|r| r == self), Some(true)) + } +} + +impl OrdAdjacency for Fixed { + fn are_adjacent(self, rhs: Fixed) -> bool { + matches!( + self.checked_add(Fixed::EPSILON).map(|r| r == rhs), + Some(true) + ) || matches!( + rhs.checked_add(Fixed::EPSILON).map(|r| r == self), + Some(true) + ) + } +} + +/// If a and b intersect return a range representing the intersection. +fn range_intersection( + a: &RangeInclusive, + b: &RangeInclusive, +) -> Option> { + if a.start() <= b.end() && b.start() <= a.end() { + Some(*max(a.start(), b.start())..=*min(a.end(), b.end())) + } else { + None + } +} + +/// Returns true if the ranges [a_start, a_end] and [b_start, b_end] overlap or are adjacent to each other. +/// +/// All bounds are inclusive. +fn ranges_overlap_or_adjacent(a_start: T, a_end: T, b_start: T, b_end: T) -> bool +where + T: Ord + OrdAdjacency, +{ + (a_start <= b_end && b_start <= a_end) + || (a_end.are_adjacent(b_start)) + || (b_end.are_adjacent(a_start)) +} + +/// Returns true if the range [a_start, a_end] is a subset of [b_start, b_end]. +/// +/// All bounds are inclusive. +fn range_is_subset(a_start: T, a_end: T, b_start: T, b_end: T) -> bool +where + T: Ord, +{ + a_start >= b_start && a_end <= b_end +} + +#[cfg(test)] +mod test { + + use super::*; + + #[test] + #[allow(clippy::reversed_empty_ranges)] + fn insert_invalid() { + let mut map: RangeSet = Default::default(); + map.insert(12..=11); + assert_eq!(map.iter().collect::>(), vec![],); + } + + #[test] + fn insert_non_overlapping() { + let mut map: RangeSet = Default::default(); + + map.insert(11..=11); + map.insert(2..=3); + map.insert(6..=9); + + assert_eq!(map.iter().collect::>(), vec![2..=3, 6..=9, 11..=11],); + } + + #[test] + fn insert_subset_before() { + let mut map: RangeSet = Default::default(); + + map.insert(2..=8); + map.insert(3..=7); + + assert_eq!(map.iter().collect::>(), vec![2..=8],); + } + + #[test] + fn insert_subset_after() { + let mut map: RangeSet = Default::default(); + + map.insert(2..=8); + map.insert(2..=7); + map.insert(2..=8); + + assert_eq!(map.iter().collect::>(), vec![2..=8],); + } + + #[test] + fn insert_overlapping_before() { + let mut map: RangeSet = Default::default(); + + map.insert(2..=8); + map.insert(7..=11); + + assert_eq!(map.iter().collect::>(), vec![2..=11],); + } + + #[test] + fn insert_overlapping_after() { + let mut map: RangeSet = Default::default(); + map.insert(10..=14); + map.insert(7..=11); + assert_eq!(map.iter().collect::>(), vec![7..=14],); + + let mut map: RangeSet = Default::default(); + map.insert(10..=14); + map.insert(10..=17); + assert_eq!(map.iter().collect::>(), vec![10..=17],); + } + + #[test] + fn insert_overlapping_multiple_after() { + let mut map: RangeSet = Default::default(); + map.insert(10..=14); + map.insert(16..=17); + map.insert(7..=16); + assert_eq!(map.iter().collect::>(), vec![7..=17],); + + let mut map: RangeSet = Default::default(); + map.insert(10..=14); + map.insert(16..=17); + map.insert(10..=16); + assert_eq!(map.iter().collect::>(), vec![10..=17],); + + let mut map: RangeSet = Default::default(); + map.insert(10..=14); + map.insert(16..=17); + map.insert(10..=17); + assert_eq!(map.iter().collect::>(), vec![10..=17],); + } + + #[test] + fn insert_overlapping_before_and_after() { + let mut map: RangeSet = Default::default(); + + map.insert(6..=8); + map.insert(10..=14); + map.insert(16..=20); + + map.insert(7..=19); + + assert_eq!(map.iter().collect::>(), vec![6..=20],); + } + + #[test] + fn insert_joins_adjacent() { + let mut map: RangeSet = Default::default(); + map.insert(6..=8); + map.insert(9..=10); + assert_eq!(map.iter().collect::>(), vec![6..=10],); + + let mut map: RangeSet = Default::default(); + map.insert(9..=10); + map.insert(6..=8); + assert_eq!(map.iter().collect::>(), vec![6..=10],); + + let mut map: RangeSet = Default::default(); + map.insert(6..=8); + map.insert(10..=10); + map.insert(9..=9); + assert_eq!(map.iter().collect::>(), vec![6..=10],); + } + + #[test] + fn from_iter_and_extend() { + let mut map: RangeSet = [2..=5, 13..=64, 7..=9].into_iter().collect(); + assert_eq!(map.iter().collect::>(), vec![2..=5, 7..=9, 13..=64],); + + map.extend([6..=17, 100..=101]); + + assert_eq!(map.iter().collect::>(), vec![2..=64, 100..=101],); + } + + #[test] + fn intersection() { + let a: RangeSet = [2..=5, 7..=9, 13..=64].into_iter().collect(); + let b: RangeSet = [1..=3, 5..=8, 13..=64, 67..=69].into_iter().collect(); + + let expected = vec![2..=3, 5..=5, 7..=8, 13..=64]; + + assert_eq!(a.intersection(&b).collect::>(), expected); + assert_eq!(b.intersection(&a).collect::>(), expected); + } +}