Skip to content

Commit 5365315

Browse files
authored
Merge pull request #328 from Dr-Emann/internal_validate
Add a function for internal validation, and use it in the fuzzer
2 parents 72d4b40 + 164433f commit 5365315

File tree

9 files changed

+91
-3
lines changed

9 files changed

+91
-3
lines changed

.github/workflows/test.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,5 +162,8 @@ jobs:
162162
fuzz/artifacts
163163
fuzz/corpus
164164
165-
- name: Run Fuzzer vs croaring for 30 minutes
166-
run: cargo fuzz run against_croaring -s none -- -timeout=5 -max_total_time=1800
165+
- name: Run Fuzzer vs croaring for 15 minutes
166+
run: cargo fuzz run against_croaring -s none -- -timeout=5 -max_total_time=900
167+
168+
- name: Run Fuzzer (with simd) vs croaring for 15 minutes
169+
run: cargo fuzz run --features=simd against_croaring -- -timeout=5 -max_total_time=900

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/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ libfuzzer-sys = { version = "0.4.9", features = ["arbitrary-derive"] }
1212
roaring = { path = "../roaring" }
1313
croaring = "2.0"
1414

15+
[features]
16+
simd = ["roaring/simd"]
17+
1518
[[bin]]
1619
name = "against_croaring"
1720
path = "fuzz_targets/against_croaring.rs"

fuzz/fuzz_targets/against_croaring.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ fuzz_target!(|input: FuzzInput| {
3333
for op in input.ops {
3434
op.apply(&mut lhs_c, &mut rhs_c, &mut lhs_r, &mut rhs_r);
3535
}
36+
lhs_r.internal_validate().unwrap();
37+
rhs_r.internal_validate().unwrap();
3638
check_equal(&lhs_c, &lhs_r);
3739
check_equal(&rhs_c, &rhs_r);
3840
});

roaring/src/bitmap/inherent.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -899,6 +899,28 @@ impl RoaringBitmap {
899899
}
900900
changed
901901
}
902+
903+
/// Ensure the bitmap is internally valid
904+
///
905+
/// This is useful for development, but is not needed for normal use:
906+
/// bitmaps should _always_ be internally valid.
907+
///
908+
/// # Errors
909+
///
910+
/// Returns an error if the bitmap is not valid, with a description of the problem.
911+
#[doc(hidden)]
912+
pub fn internal_validate(&self) -> Result<(), &'static str> {
913+
for window in self.containers.windows(2) {
914+
let [first, second] = window else { unreachable!() };
915+
if second.key <= first.key {
916+
return Err("keys are not strictly increasing");
917+
}
918+
}
919+
for container in &self.containers {
920+
container.store.internal_validate()?;
921+
}
922+
Ok(())
923+
}
902924
}
903925

904926
impl Default for RoaringBitmap {

roaring/src/bitmap/store/array_store/mod.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,23 @@ impl ArrayStore {
307307
}
308308
self.vec.truncate(pos);
309309
}
310+
311+
pub(crate) fn internal_validate(&self) -> Result<(), &'static str> {
312+
if self.vec.is_empty() {
313+
return Err("zero cardinality in array container");
314+
}
315+
if self.vec.len() > super::ARRAY_LIMIT as usize {
316+
return Err("array cardinality exceeds ARRAY_LIMIT");
317+
}
318+
for window in self.vec.windows(2) {
319+
let &[first, second] = window else { unreachable!() };
320+
if first >= second {
321+
return Err("array elements not strictly increasing");
322+
}
323+
}
324+
325+
Ok(())
326+
}
310327
}
311328

312329
impl Default for ArrayStore {

roaring/src/bitmap/store/bitmap_store.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,17 @@ impl BitmapStore {
426426
}
427427
}
428428
}
429+
430+
pub(crate) fn internal_validate(&self) -> Result<(), &'static str> {
431+
let expected_len: u64 = self.bits.iter().map(|bits| u64::from(bits.count_ones())).sum();
432+
if self.len != expected_len {
433+
return Err("bitmap cardinality is incorrect");
434+
}
435+
if self.len <= super::ARRAY_LIMIT {
436+
return Err("cardinality is too small for a bitmap container");
437+
}
438+
Ok(())
439+
}
429440
}
430441

431442
// this can be done in 3 instructions on x86-64 with bmi2 with: tzcnt(pdep(1 << rank, value))

roaring/src/bitmap/store/interval_store.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,28 @@ impl IntervalStore {
482482
pub(crate) fn iter_intervals(&'_ self) -> core::slice::Iter<'_, Interval> {
483483
self.0.iter()
484484
}
485+
486+
pub(crate) fn internal_validate(&self) -> Result<(), &'static str> {
487+
if self.0.is_empty() {
488+
return Err("run container with zero runs");
489+
}
490+
let mut last_end: Option<u16> = None;
491+
for run in &self.0 {
492+
if run.start > run.end {
493+
return Err("empty run container");
494+
}
495+
if let Some(last_end) = last_end.replace(run.end) {
496+
if last_end >= run.start {
497+
return Err("overlapping or unordered runs");
498+
}
499+
if last_end.saturating_add(1) >= run.start {
500+
return Err("contiguous runs");
501+
}
502+
}
503+
}
504+
505+
Ok(())
506+
}
485507
}
486508

487509
impl From<IntervalStore> for BitmapStore {

roaring/src/bitmap/store/mod.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,14 @@ impl Store {
400400
Run(intervals) => Run(intervals.clone()),
401401
}
402402
}
403+
404+
pub(crate) fn internal_validate(&self) -> Result<(), &'static str> {
405+
match self {
406+
Array(vec) => vec.internal_validate(),
407+
Bitmap(bits) => bits.internal_validate(),
408+
Run(runs) => runs.internal_validate(),
409+
}
410+
}
403411
}
404412

405413
impl Default for Store {

0 commit comments

Comments
 (0)