From a2d5386566f2b3cf687384e90a809792e5ba8a7e Mon Sep 17 00:00:00 2001 From: Garret Rieger Date: Wed, 27 Nov 2024 22:35:27 +0000 Subject: [PATCH 1/6] [collections] Add a RangeSet collection. This stores a disjoint list of ranges with automatica merging of overlapping and adjacent ranges. --- font-types/src/fixed.rs | 6 + fuzz/Cargo.toml | 6 + fuzz/fuzz_targets/fuzz_range_set.rs | 48 ++++ read-fonts/src/collections.rs | 3 + read-fonts/src/collections/range_set/mod.rs | 246 ++++++++++++++++++++ 5 files changed, 309 insertions(+) create mode 100644 fuzz/fuzz_targets/fuzz_range_set.rs create mode 100644 read-fonts/src/collections/range_set/mod.rs 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..bfcacab3f --- /dev/null +++ b/fuzz/fuzz_targets/fuzz_range_set.rs @@ -0,0 +1,48 @@ +#![no_main] +//! Fuzzes the incremental_font_transfer patch_group.rs API + +use libfuzzer_sys::{arbitrary, fuzz_target}; +use read_fonts::collections::{IntSet, RangeSet}; + +#[derive(Debug, arbitrary::Arbitrary)] +enum Operation { + Insert(u16, u16), + Iter(), +} + +#[derive(Default)] +struct State { + range_set: RangeSet, + int_set: IntSet, +} + +const OP_COUNT_LIMIT: u64 = 1000; + +fuzz_target!(|operations: Vec| { + let mut state: State = Default::default(); + let mut op_count = 0u64; + for op in operations { + match op { + Operation::Insert(start, end) => { + if end > start { + let count = (end as u64 - start as u64); + op_count = op_count.saturating_add(count.saturating_mul(count.ilog2() as u64)); + if op_count > OP_COUNT_LIMIT { + return; + } + } + + state.range_set.insert(start, end); + state.int_set.insert_range(start..=end); + } + 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())); + } + } + } +}); diff --git a/read-fonts/src/collections.rs b/read-fonts/src/collections.rs index 5f75365d5..299c85625 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; + +pub mod range_set; +pub use range_set::RangeSet; diff --git a/read-fonts/src/collections/range_set/mod.rs b/read-fonts/src/collections/range_set/mod.rs new file mode 100644 index 000000000..de6633ea6 --- /dev/null +++ b/read-fonts/src/collections/range_set/mod.rs @@ -0,0 +1,246 @@ +//! Stores a disjoint collection of ranges over numeric types. +//! +//! Overlapping ranges are automatically merged together. + +use core::{ + cmp::{max, min}, + ops::RangeInclusive, +}; +use std::collections::BTreeMap; + +use types::Fixed; + +#[derive(Default)] +pub struct RangeSet { + // an entry in the map ranges[a] = b implies there is an range [a, b] (inclusive) in this set. + ranges: BTreeMap, +} + +pub trait Sequence { + fn next(&self) -> Option; +} + +impl RangeSet +where + T: Ord + Copy + Sequence, +{ + pub fn insert(&mut self, start: T, end: T) { + if end < start { + // ignore empty or malformed ranges. + return; + } + + let mut start = start; + let mut end = 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; + } + } + } + + pub fn iter(&'_ self) -> impl Iterator> + '_ { + self.ranges.iter().map(|(a, b)| *a..=*b) + } + + /// 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).rev().next()?; + Some((*next_start, *next_end)) + } +} + +impl Sequence for u32 { + fn next(&self) -> Option { + self.checked_add(1) + } +} + +impl Sequence for u16 { + fn next(&self) -> Option { + self.checked_add(1) + } +} + +impl Sequence for Fixed { + fn next(&self) -> Option { + self.checked_add(Fixed::EPSILON) + } +} + +/// 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 + Sequence, +{ + (a_start <= b_end && b_start <= a_end) + || (a_end.next() == Some(b_start)) + || (b_end.next() == Some(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] + 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],); + } +} From d742dd4157e628599d76770bd76c48c66ce4e329 Mon Sep 17 00:00:00 2001 From: Garret Rieger Date: Wed, 27 Nov 2024 22:38:58 +0000 Subject: [PATCH 2/6] [collections] fix clippy. --- fuzz/fuzz_targets/fuzz_range_set.rs | 2 +- read-fonts/src/collections/range_set/mod.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/fuzz/fuzz_targets/fuzz_range_set.rs b/fuzz/fuzz_targets/fuzz_range_set.rs index bfcacab3f..263e18366 100644 --- a/fuzz/fuzz_targets/fuzz_range_set.rs +++ b/fuzz/fuzz_targets/fuzz_range_set.rs @@ -25,7 +25,7 @@ fuzz_target!(|operations: Vec| { match op { Operation::Insert(start, end) => { if end > start { - let count = (end as u64 - start as u64); + let count = end as u64 - start as u64; op_count = op_count.saturating_add(count.saturating_mul(count.ilog2() as u64)); if op_count > OP_COUNT_LIMIT { return; diff --git a/read-fonts/src/collections/range_set/mod.rs b/read-fonts/src/collections/range_set/mod.rs index de6633ea6..181237d4d 100644 --- a/read-fonts/src/collections/range_set/mod.rs +++ b/read-fonts/src/collections/range_set/mod.rs @@ -26,7 +26,7 @@ where { pub fn insert(&mut self, start: T, end: T) { if end < start { - // ignore empty or malformed ranges. + // ignore or malformed ranges. return; } @@ -79,7 +79,7 @@ where /// 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).rev().next()?; + let (next_start, next_end) = self.ranges.range(..start).next_back()?; Some((*next_start, *next_end)) } } From cdb5e5f0bff89ce854191fc873baa511c24377d3 Mon Sep 17 00:00:00 2001 From: Garret Rieger Date: Thu, 28 Nov 2024 18:13:51 +0000 Subject: [PATCH 3/6] [collections] range_set add extend, from iterator, and an intersection iterator. --- read-fonts/src/collections/range_set/mod.rs | 220 ++++++++++++++++---- 1 file changed, 179 insertions(+), 41 deletions(-) diff --git a/read-fonts/src/collections/range_set/mod.rs b/read-fonts/src/collections/range_set/mod.rs index 181237d4d..bbda961b6 100644 --- a/read-fonts/src/collections/range_set/mod.rs +++ b/read-fonts/src/collections/range_set/mod.rs @@ -1,16 +1,21 @@ //! Stores a disjoint collection of ranges over numeric types. //! -//! Overlapping ranges are automatically merged together. +//! 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)] +#[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, @@ -24,14 +29,15 @@ impl RangeSet where T: Ord + Copy + Sequence, { - pub fn insert(&mut self, start: T, end: T) { - if end < start { + /// 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 = start; - let mut end = end; + 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) { @@ -67,10 +73,22 @@ where } } + /// 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()?; @@ -84,6 +102,93 @@ where } } +impl Extend> for RangeSet +where + T: Copy + Ord + Sequence, +{ + fn extend>>(&mut self, iter: I) { + iter.into_iter().for_each(|r| self.insert(r)); + } +} + +impl FromIterator> for RangeSet +where + T: Default + Copy + Ord + Sequence, +{ + 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 Sequence for u32 { fn next(&self) -> Option { self.checked_add(1) @@ -102,6 +207,18 @@ impl Sequence for Fixed { } } +/// 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. @@ -132,7 +249,7 @@ mod test { #[test] fn insert_invalid() { let mut map: RangeSet = Default::default(); - map.insert(12, 11); + map.insert(12..=11); assert_eq!(map.iter().collect::>(), vec![],); } @@ -140,9 +257,9 @@ mod test { fn insert_non_overlapping() { let mut map: RangeSet = Default::default(); - map.insert(11, 11); - map.insert(2, 3); - map.insert(6, 9); + map.insert(11..=11); + map.insert(2..=3); + map.insert(6..=9); assert_eq!(map.iter().collect::>(), vec![2..=3, 6..=9, 11..=11],); } @@ -151,8 +268,8 @@ mod test { fn insert_subset_before() { let mut map: RangeSet = Default::default(); - map.insert(2, 8); - map.insert(3, 7); + map.insert(2..=8); + map.insert(3..=7); assert_eq!(map.iter().collect::>(), vec![2..=8],); } @@ -161,9 +278,9 @@ mod test { fn insert_subset_after() { let mut map: RangeSet = Default::default(); - map.insert(2, 8); - map.insert(2, 7); - map.insert(2, 8); + map.insert(2..=8); + map.insert(2..=7); + map.insert(2..=8); assert_eq!(map.iter().collect::>(), vec![2..=8],); } @@ -172,8 +289,8 @@ mod test { fn insert_overlapping_before() { let mut map: RangeSet = Default::default(); - map.insert(2, 8); - map.insert(7, 11); + map.insert(2..=8); + map.insert(7..=11); assert_eq!(map.iter().collect::>(), vec![2..=11],); } @@ -181,34 +298,34 @@ mod test { #[test] fn insert_overlapping_after() { let mut map: RangeSet = Default::default(); - map.insert(10, 14); - map.insert(7, 11); + 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); + 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); + 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); + 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); + map.insert(10..=14); + map.insert(16..=17); + map.insert(10..=17); assert_eq!(map.iter().collect::>(), vec![10..=17],); } @@ -216,11 +333,11 @@ mod 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(6..=8); + map.insert(10..=14); + map.insert(16..=20); - map.insert(7, 19); + map.insert(7..=19); assert_eq!(map.iter().collect::>(), vec![6..=20],); } @@ -228,19 +345,40 @@ mod test { #[test] fn insert_joins_adjacent() { let mut map: RangeSet = Default::default(); - map.insert(6, 8); - map.insert(9, 10); + 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); + 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); + 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); + } } From 3c46e0a2c6f14c6f7060099568b15e71ceb83a2a Mon Sep 17 00:00:00 2001 From: Garret Rieger Date: Thu, 28 Nov 2024 18:20:59 +0000 Subject: [PATCH 4/6] [collections] reorg. --- fuzz/fuzz_targets/fuzz_range_set.rs | 2 +- read-fonts/src/collections.rs | 2 +- read-fonts/src/collections/{range_set/mod.rs => range_set.rs} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename read-fonts/src/collections/{range_set/mod.rs => range_set.rs} (100%) diff --git a/fuzz/fuzz_targets/fuzz_range_set.rs b/fuzz/fuzz_targets/fuzz_range_set.rs index 263e18366..43c95fb8f 100644 --- a/fuzz/fuzz_targets/fuzz_range_set.rs +++ b/fuzz/fuzz_targets/fuzz_range_set.rs @@ -32,7 +32,7 @@ fuzz_target!(|operations: Vec| { } } - state.range_set.insert(start, end); + state.range_set.insert(start..=end); state.int_set.insert_range(start..=end); } Operation::Iter() => { diff --git a/read-fonts/src/collections.rs b/read-fonts/src/collections.rs index 299c85625..cf9df63f3 100644 --- a/read-fonts/src/collections.rs +++ b/read-fonts/src/collections.rs @@ -3,5 +3,5 @@ pub mod int_set; pub use int_set::IntSet; -pub mod range_set; +mod range_set; pub use range_set::RangeSet; diff --git a/read-fonts/src/collections/range_set/mod.rs b/read-fonts/src/collections/range_set.rs similarity index 100% rename from read-fonts/src/collections/range_set/mod.rs rename to read-fonts/src/collections/range_set.rs From b1dad011326082817a41bfac23cdb2605c52c19e Mon Sep 17 00:00:00 2001 From: Garret Rieger Date: Thu, 28 Nov 2024 18:32:25 +0000 Subject: [PATCH 5/6] [collections] add extend() and intersection() to RangeSet fuzzer. --- fuzz/fuzz_targets/fuzz_range_set.rs | 72 ++++++++++++++++++++----- read-fonts/src/collections/range_set.rs | 1 + 2 files changed, 60 insertions(+), 13 deletions(-) diff --git a/fuzz/fuzz_targets/fuzz_range_set.rs b/fuzz/fuzz_targets/fuzz_range_set.rs index 43c95fb8f..14988a6d0 100644 --- a/fuzz/fuzz_targets/fuzz_range_set.rs +++ b/fuzz/fuzz_targets/fuzz_range_set.rs @@ -1,13 +1,23 @@ #![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(u16, u16), - Iter(), + Insert(RangeInclusive), + Extend(Vec>), + Intersection, + Iter, +} + +#[derive(Debug, arbitrary::Arbitrary)] +enum SetIndex { + One, + Two, } #[derive(Default)] @@ -18,24 +28,46 @@ struct State { const OP_COUNT_LIMIT: u64 = 1000; -fuzz_target!(|operations: Vec| { - let mut state: State = Default::default(); +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 in operations { + 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(start, end) => { - if end > start { - let count = end as u64 - start as u64; - op_count = op_count.saturating_add(count.saturating_mul(count.ilog2() as u64)); + 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.insert(start..=end); - state.int_set.insert_range(start..=end); + state.range_set.extend(ranges.into_iter()); } - Operation::Iter() => { + Operation::Iter => { op_count = op_count.saturating_add(state.int_set.iter_ranges().count() as u64); if op_count > OP_COUNT_LIMIT { return; @@ -43,6 +75,20 @@ fuzz_target!(|operations: Vec| { 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/range_set.rs b/read-fonts/src/collections/range_set.rs index bbda961b6..04147def8 100644 --- a/read-fonts/src/collections/range_set.rs +++ b/read-fonts/src/collections/range_set.rs @@ -247,6 +247,7 @@ mod test { use super::*; #[test] + #[allow(clippy::reversed_empty_ranges)] fn insert_invalid() { let mut map: RangeSet = Default::default(); map.insert(12..=11); From db88138cd57d6f30fbe357af250f0e9549b86eae Mon Sep 17 00:00:00 2001 From: Garret Rieger Date: Thu, 28 Nov 2024 18:49:20 +0000 Subject: [PATCH 6/6] [collections] Change Sequence trait to OrdAdjacency. --- read-fonts/src/collections/range_set.rs | 44 +++++++++++++++---------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/read-fonts/src/collections/range_set.rs b/read-fonts/src/collections/range_set.rs index 04147def8..e9b2252d2 100644 --- a/read-fonts/src/collections/range_set.rs +++ b/read-fonts/src/collections/range_set.rs @@ -21,13 +21,15 @@ pub struct RangeSet { ranges: BTreeMap, } -pub trait Sequence { - fn next(&self) -> Option; +/// 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 + Sequence, + T: Ord + Copy + OrdAdjacency, { /// Insert a range into this set, automatically merging with existing ranges as needed. pub fn insert(&mut self, range: RangeInclusive) { @@ -104,7 +106,7 @@ where impl Extend> for RangeSet where - T: Copy + Ord + Sequence, + T: Copy + Ord + OrdAdjacency, { fn extend>>(&mut self, iter: I) { iter.into_iter().for_each(|r| self.insert(r)); @@ -113,7 +115,7 @@ where impl FromIterator> for RangeSet where - T: Default + Copy + Ord + Sequence, + T: Default + Copy + Ord + OrdAdjacency, { fn from_iter>>(iter: I) -> Self { let mut result: Self = Default::default(); @@ -189,21 +191,29 @@ where } } -impl Sequence for u32 { - fn next(&self) -> Option { - self.checked_add(1) +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 Sequence for u16 { - fn next(&self) -> Option { - self.checked_add(1) +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 Sequence for Fixed { - fn next(&self) -> Option { - self.checked_add(Fixed::EPSILON) +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) + ) } } @@ -224,11 +234,11 @@ fn range_intersection( /// 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 + Sequence, + T: Ord + OrdAdjacency, { (a_start <= b_end && b_start <= a_end) - || (a_end.next() == Some(b_start)) - || (b_end.next() == Some(a_start)) + || (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].