Skip to content

Commit 9ca8d64

Browse files
authored
Merge pull request #335 from Dr-Emann/fuzz_iterators_and_fix_bugs
Fuzz iterators and fix bugs
2 parents b02a94a + 03b3552 commit 9ca8d64

File tree

7 files changed

+307
-17
lines changed

7 files changed

+307
-17
lines changed

fuzz/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

fuzz/fuzz_targets/against_croaring.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ mod arbitrary_ops;
55
use libfuzzer_sys::arbitrary::{self, Arbitrary};
66
use libfuzzer_sys::fuzz_target;
77

8-
use crate::arbitrary_ops::{check_equal, Operation};
8+
use crate::arbitrary_ops::{check_equal, BitmapIteratorOperation, CRoaringIterRange, Operation};
99

1010
#[derive(Arbitrary, Debug)]
1111
struct FuzzInput<'a> {
1212
ops: Vec<Operation>,
13+
iter_ops: Vec<BitmapIteratorOperation>,
1314
initial_input: &'a [u8],
1415
}
1516

@@ -35,6 +36,14 @@ fuzz_target!(|input: FuzzInput| {
3536
}
3637
lhs_r.internal_validate().unwrap();
3738
rhs_r.internal_validate().unwrap();
39+
40+
let mut lhs_c_iter = CRoaringIterRange::new(&lhs_c);
41+
let mut lhs_r_iter = lhs_r.iter();
42+
43+
for op in input.iter_ops {
44+
op.apply(&mut lhs_c_iter, &mut lhs_r_iter);
45+
}
46+
3847
check_equal(&lhs_c, &lhs_r);
3948
check_equal(&rhs_c, &rhs_r);
4049
});

fuzz/fuzz_targets/arbitrary_ops/mod.rs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,16 @@ pub enum BitmapBinaryOperation {
9595
AndNot,
9696
}
9797

98+
#[derive(Arbitrary, Debug)]
99+
pub enum BitmapIteratorOperation {
100+
Next,
101+
NextBack,
102+
AdvanceTo(Num),
103+
AdvanceBackTo(Num),
104+
Nth(Num),
105+
NthBack(Num),
106+
}
107+
98108
impl ReadBitmapOperation {
99109
pub fn apply(&self, x: &mut croaring::Bitmap, y: &mut roaring::RoaringBitmap) {
100110
match *self {
@@ -387,6 +397,104 @@ impl BitmapBinaryOperation {
387397
}
388398
}
389399

400+
pub struct CRoaringIterRange<'a> {
401+
cursor: croaring::bitmap::BitmapCursor<'a>,
402+
empty: bool,
403+
start: u32,
404+
end_inclusive: u32,
405+
}
406+
407+
impl<'a> CRoaringIterRange<'a> {
408+
pub fn new(bitmap: &'a croaring::Bitmap) -> Self {
409+
CRoaringIterRange {
410+
cursor: bitmap.cursor(),
411+
start: 0,
412+
end_inclusive: u32::MAX,
413+
empty: false,
414+
}
415+
}
416+
417+
fn next(&mut self) -> Option<u32> {
418+
if self.empty {
419+
return None;
420+
}
421+
self.cursor.reset_at_or_after(self.start);
422+
let res = self.cursor.current().filter(|&n| n <= self.end_inclusive);
423+
match res {
424+
None => self.empty = true,
425+
Some(n) if n == self.end_inclusive => self.empty = true,
426+
Some(n) => self.start = n + 1,
427+
}
428+
res
429+
}
430+
431+
fn next_back(&mut self) -> Option<u32> {
432+
if self.empty {
433+
return None;
434+
}
435+
self.cursor.reset_at_or_after(self.end_inclusive);
436+
if self.cursor.current().is_none_or(|n| n > self.end_inclusive) {
437+
self.cursor.move_prev();
438+
}
439+
let res = self.cursor.current().filter(|&n| n >= self.start);
440+
match res {
441+
None => self.empty = true,
442+
Some(n) if n == self.start => self.empty = true,
443+
Some(n) => self.end_inclusive = n - 1,
444+
}
445+
res
446+
}
447+
448+
fn advance_to(&mut self, num: u32) {
449+
self.start = self.start.max(num);
450+
}
451+
452+
fn advance_back_to(&mut self, num: u32) {
453+
self.end_inclusive = self.end_inclusive.min(num);
454+
}
455+
456+
fn nth(&mut self, num: u32) -> Option<u32> {
457+
for _ in 0..num {
458+
_ = self.next();
459+
}
460+
self.next()
461+
}
462+
463+
fn nth_back(&mut self, num: u32) -> Option<u32> {
464+
for _ in 0..num {
465+
_ = self.next_back();
466+
}
467+
self.next_back()
468+
}
469+
}
470+
471+
impl BitmapIteratorOperation {
472+
pub fn apply(&self, x: &mut CRoaringIterRange, y: &mut roaring::bitmap::Iter) {
473+
match *self {
474+
BitmapIteratorOperation::Next => {
475+
assert_eq!(x.next(), y.next());
476+
}
477+
BitmapIteratorOperation::NextBack => {
478+
assert_eq!(x.next_back(), y.next_back());
479+
}
480+
BitmapIteratorOperation::AdvanceTo(n) => {
481+
x.advance_to(n.0);
482+
y.advance_to(n.0);
483+
}
484+
BitmapIteratorOperation::AdvanceBackTo(n) => {
485+
x.advance_back_to(n.0);
486+
y.advance_back_to(n.0);
487+
}
488+
BitmapIteratorOperation::Nth(n) => {
489+
assert_eq!(x.nth(n.0), y.nth(n.0 as usize));
490+
}
491+
BitmapIteratorOperation::NthBack(n) => {
492+
assert_eq!(x.nth_back(n.0), y.nth_back(n.0 as usize));
493+
}
494+
}
495+
}
496+
}
497+
390498
pub(crate) fn check_equal(c: &croaring::Bitmap, r: &roaring::RoaringBitmap) {
391499
let mut lhs = c.iter();
392500
let mut rhs = r.iter();

roaring/src/bitmap/store/bitmap_store.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,9 @@ impl<B: Borrow<[u64; BITMAP_LENGTH]>> BitmapIter<B> {
515515
} else if cmp == Ordering::Equal {
516516
self.value_back
517517
} else {
518+
// New key is greater than original key and key_back, this iterator is now empty
519+
self.key = self.key_back;
520+
self.value = 0;
518521
self.value_back = 0;
519522
return;
520523
}

roaring/src/bitmap/store/interval_store.rs

Lines changed: 68 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -657,13 +657,18 @@ impl<I: SliceIterator<Interval>> RunIter<I> {
657657
self.forward_offset = value;
658658
} else {
659659
self.intervals.next();
660+
self.forward_offset = 0;
660661
return;
661662
}
662-
if Some(self.forward_offset as u64)
663-
>= self.intervals.as_slice().first().map(|f| f.run_len())
664-
{
663+
let only_interval = self.intervals.as_slice().len() == 1;
664+
let total_offset = u64::from(self.forward_offset)
665+
+ if only_interval { u64::from(self.backward_offset) } else { 0 };
666+
if Some(total_offset) >= self.intervals.as_slice().first().map(|f| f.run_len()) {
665667
self.intervals.next();
666668
self.forward_offset = 0;
669+
if only_interval {
670+
self.backward_offset = 0;
671+
}
667672
}
668673
}
669674

@@ -672,20 +677,26 @@ impl<I: SliceIterator<Interval>> RunIter<I> {
672677
self.backward_offset = value;
673678
} else {
674679
self.intervals.next_back();
680+
self.backward_offset = 0;
675681
return;
676682
}
677-
if Some(self.backward_offset as u64)
678-
>= self.intervals.as_slice().last().map(|f| f.run_len())
679-
{
683+
let only_interval = self.intervals.as_slice().len() == 1;
684+
let total_offset = u64::from(self.backward_offset)
685+
+ if only_interval { u64::from(self.forward_offset) } else { 0 };
686+
if Some(total_offset) >= self.intervals.as_slice().last().map(|f| f.run_len()) {
680687
self.intervals.next_back();
681688
self.backward_offset = 0;
689+
if only_interval {
690+
self.forward_offset = 0;
691+
}
682692
}
683693
}
684694

685695
fn remaining_size(&self) -> usize {
686-
(self.intervals.as_slice().iter().map(|f| f.run_len()).sum::<u64>()
687-
- self.forward_offset as u64
688-
- self.backward_offset as u64) as usize
696+
let total_size = self.intervals.as_slice().iter().map(|f| f.run_len()).sum::<u64>();
697+
let total_offset = u64::from(self.forward_offset) + u64::from(self.backward_offset);
698+
debug_assert!(total_size >= total_offset);
699+
total_size.saturating_sub(total_offset) as usize
689700
}
690701

691702
/// Advance the iterator to the first value greater than or equal to `n`.
@@ -708,10 +719,25 @@ impl<I: SliceIterator<Interval>> RunIter<I> {
708719
if let Some(value) = index.checked_sub(1) {
709720
self.intervals.nth(value);
710721
}
711-
self.forward_offset = n - self.intervals.as_slice().first().unwrap().start;
722+
let first_interval = self.intervals.as_slice().first().unwrap();
723+
self.forward_offset = n - first_interval.start;
724+
if self.intervals.as_slice().len() == 1
725+
&& u64::from(self.forward_offset) + u64::from(self.backward_offset)
726+
>= first_interval.run_len()
727+
{
728+
// If we are now the only interval, and we've now met the forward offset,
729+
// consume the final interval
730+
_ = self.intervals.next();
731+
self.forward_offset = 0;
732+
self.backward_offset = 0;
733+
}
712734
}
713735
Err(index) => {
714736
if index == self.intervals.as_slice().len() {
737+
// Consume the whole iterator
738+
self.intervals.nth(index);
739+
self.forward_offset = 0;
740+
self.backward_offset = 0;
715741
return;
716742
}
717743
if let Some(value) = index.checked_sub(1) {
@@ -743,10 +769,25 @@ impl<I: SliceIterator<Interval>> RunIter<I> {
743769
if let Some(value) = backward_index.checked_sub(1) {
744770
self.intervals.nth_back(value);
745771
}
746-
self.backward_offset = self.intervals.as_slice().last().unwrap().end - n;
772+
let last_interval = self.intervals.as_slice().last().unwrap();
773+
self.backward_offset = last_interval.end - n;
774+
if self.intervals.as_slice().len() == 1
775+
&& u64::from(self.forward_offset) + u64::from(self.backward_offset)
776+
>= last_interval.run_len()
777+
{
778+
// If we are now the only interval, and we've now met the forward offset,
779+
// consume the final interval
780+
_ = self.intervals.next_back();
781+
self.forward_offset = 0;
782+
self.backward_offset = 0;
783+
}
747784
}
748785
Err(index) => {
749786
if index == 0 {
787+
// Consume the whole iterator
788+
self.intervals.nth_back(self.intervals.as_slice().len());
789+
self.forward_offset = 0;
790+
self.backward_offset = 0;
750791
return;
751792
}
752793
let backward_index = self.intervals.as_slice().len() - index;
@@ -778,12 +819,24 @@ impl<I: SliceIterator<Interval>> Iterator for RunIter<I> {
778819
}
779820

780821
fn nth(&mut self, n: usize) -> Option<Self::Item> {
822+
if n > usize::from(u16::MAX) {
823+
// Consume the whole iterator
824+
self.intervals.nth(self.intervals.as_slice().len());
825+
self.forward_offset = 0;
826+
self.backward_offset = 0;
827+
return None;
828+
}
781829
if let Some(skip) = n.checked_sub(1) {
782830
let mut to_skip = skip as u64;
783831
loop {
784-
let to_remove = (self.intervals.as_slice().first()?.run_len()
785-
- self.forward_offset as u64)
786-
.min(to_skip);
832+
let full_first_interval_len = self.intervals.as_slice().first()?.run_len();
833+
let consumed_len = u64::from(self.forward_offset)
834+
+ if self.intervals.as_slice().len() == 1 {
835+
u64::from(self.backward_offset)
836+
} else {
837+
0
838+
};
839+
let to_remove = (full_first_interval_len - consumed_len).min(to_skip);
787840
to_skip -= to_remove;
788841
self.forward_offset += to_remove as u16;
789842
self.move_next();
@@ -2401,7 +2454,7 @@ mod tests {
24012454
iter.advance_to(800);
24022455
assert_eq!(iter.next(), Some(800));
24032456
iter.advance_to(u16::MAX);
2404-
assert_eq!(iter.next(), Some(801));
2457+
assert_eq!(iter.next(), None);
24052458

24062459
let mut iter = interval_store.iter();
24072460
iter.advance_to(100);

roaring/tests/iter.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,31 @@ fn interleaved_bitmap() {
210210
assert!(outside_in(values).eq(outside_in(bitmap)));
211211
}
212212

213+
#[test]
214+
fn run_nth_max() {
215+
let mut bitmap = RoaringBitmap::new();
216+
bitmap.insert_range(0..0x1_0000);
217+
let mut iter = bitmap.iter();
218+
assert_eq!(iter.nth(0x0_FFFF), Some(0x0_FFFF));
219+
assert_eq!(iter.len(), 0);
220+
#[allow(clippy::iter_nth_zero)]
221+
{
222+
assert_eq!(iter.nth(0), None);
223+
}
224+
assert_eq!(iter.next(), None);
225+
}
226+
227+
#[test]
228+
fn run_nth_back_max() {
229+
let mut bitmap = RoaringBitmap::new();
230+
bitmap.insert_range(0..0x1_0000);
231+
let mut iter = bitmap.iter();
232+
assert_eq!(iter.nth_back(0x0_FFFF), Some(0));
233+
assert_eq!(iter.len(), 0);
234+
assert_eq!(iter.nth_back(0), None);
235+
assert_eq!(iter.next_back(), None);
236+
}
237+
213238
proptest! {
214239
#[test]
215240
fn interleaved_iter(values in btree_set(any::<u32>(), 50_000..=100_000)) {

0 commit comments

Comments
 (0)