Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,21 @@
### v1.0.3 (2022-06-11)

- **Fixes**:
- Fix `Gaps` iterator for `RangeMap` yielding an empty gap for an empty outer range. Simplified gaps logic and expanded fuzz testing to better cover this and similar cases.


### v1.0.2 (2022-05-17)

- **Fixes**:
- Fix empty gaps returned by `Gaps` iterator for `RangeInclusiveMap`. Added fuzz tests for `Gaps` iterators.


### v1.0.1 (2022-01-29)

- **Fixes**:
- Fix empty gaps returned by `Gaps` iterator for `RangeMap`, and incorrect gaps returned by `Gaps` iterator for `RangeInclusiveMap`.


### v1.0.0 (2022-01-28)

It's time. (No functional change.)
Expand Down
10 changes: 5 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
[package]
name = "rangemap"
version = "1.0.1"
name = "vulkano-rangemap"
version = "1.0.3"
authors = ["Jeff Parsons <[email protected]>"]
edition = "2018"
license = "MIT/Apache-2.0"
readme = "README.md"
repository = "https://github.com/jeffparsons/rangemap"
documentation = "https://docs.rs/rangemap"
homepage = "https://github.com/jeffparsons/rangemap"
repository = "https://github.com/vulkano-rs/rangemap"
documentation = "https://docs.rs/vulkano-rangemap"
homepage = "https://github.com/vulkano-rs/rangemap"
description = """
Map and set data structures whose keys are stored as ranges.

Expand Down
2 changes: 2 additions & 0 deletions README.fuzz.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ Run one of these:
```
cargo +nightly fuzz run rangemap_coalesce
cargo +nightly fuzz run rangemap_inclusive_coalesce
cargo +nightly fuzz run rangemap_gaps
cargo +nightly fuzz run rangemap_inclusive_gaps
```
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
# rangemap
## ⚠️ This crate is a fork of [`rangemap`](https://github.com/jeffparsons/rangemap/).

[![Crate](https://img.shields.io/crates/v/rangemap.svg)](https://crates.io/crates/rangemap)
[![Docs](https://docs.rs/rangemap/badge.svg)](https://docs.rs/rangemap)
[![Build status](https://github.com/jeffparsons/rangemap/workflows/CI/badge.svg)](https://github.com/jeffparsons/rangemap/actions)
[![Rust](https://img.shields.io/badge/rust-1.46%2B-blue.svg?maxAge=3600)](https://github.com/jeffparsons/rangemap) <!-- Don't forget to update the GitHub actions config when bumping minimum Rust version. -->
The Vulkano organization has created a fork of rangemap with additional functionality for use in [`vulkano`](https://github.com/vulkano-rs/vulkano). These changes will hopefully be upstreamed in the future.

## rangemap

[`RangeMap`] and [`RangeInclusiveMap`] are map data structures whose keys
are stored as ranges. Contiguous and overlapping ranges that map to the same
Expand Down
12 changes: 12 additions & 0 deletions fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,15 @@ name = "rangemap_inclusive_coalesce"
path = "fuzz_targets/rangemap_inclusive_coalesce.rs"
test = false
doc = false

[[bin]]
name = "rangemap_gaps"
path = "fuzz_targets/rangemap_gaps.rs"
test = false
doc = false

[[bin]]
name = "rangemap_inclusive_gaps"
path = "fuzz_targets/rangemap_inclusive_gaps.rs"
test = false
doc = false
94 changes: 94 additions & 0 deletions fuzz/fuzz_targets/rangemap_gaps.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#![feature(array_windows)]
#![no_main]
use libfuzzer_sys::fuzz_target;

use arbitrary::{Arbitrary, Unstructured};
use rangemap::RangeMap;
use std::ops::Range;

#[derive(Clone, Debug, Arbitrary)]
enum Op {
Insert(Range<u8>, u8),
Remove(Range<u8>),
}

impl Op {
fn apply(self, map: &mut RangeMap<u8, u8>) {
match self {
Op::Insert(r, v) if r.start < r.end => map.insert(r, v),
Op::Remove(r) if r.start < r.end => map.remove(r),
_ => (),
}
}
}

#[derive(Clone, Debug)]
struct Input {
ops: Vec<Op>,
outer_range: Range<u8>,
}

impl Arbitrary for Input {
fn arbitrary(u: &mut Unstructured) -> arbitrary::Result<Self> {
Ok(Self {
ops: u.arbitrary()?,
// Larger margins than these are too
// far away from boundary conditions to be interesting.
// ("Oh, the fools! If only they'd built it with 6,001 hulls." -- Philip J. Fry)
//
// NOTE: Not using `int_in_range` because of <https://github.com/rust-fuzz/arbitrary/issues/106>.
outer_range: *u.choose(&[0, 1, 2, 3, 100, 101, 102, 103])?
..*u.choose(&[100, 101, 102, 103, 252, 253, 254, 255])?,
})
}
}

fuzz_target!(|input: Input| {
let Input { ops, outer_range } = input;

let mut map = RangeMap::new();

for op in ops {
op.apply(&mut map);
}

// Check that the combination of gaps and keys fills the entire outer range.
let gaps: Vec<Range<u8>> = map.gaps(&outer_range).collect();
// TODO: Replace the filtering and mapping with a `range` iterator
// on the map itself.
let mut keys: Vec<Range<u8>> = map
.into_iter()
.map(|(k, _v)| k)
.filter(|Range { start, end }| {
// Reject anything with zero of its width inside the outer range.
*end > outer_range.start && *start < outer_range.end
})
.map(|Range { start, end }| {
// Truncate anything straddling either edge.
u8::max(start, outer_range.start)..u8::min(end, outer_range.end)
})
.filter(|range| {
// Reject anything that is now empty after being truncated.
!range.is_empty()
})
.collect();

keys.extend(gaps.into_iter());
keys.sort_by_key(|key| key.start);

if outer_range.is_empty() {
// There should be no gaps or keys returned if the outer range is empty,
// because empty ranges cover no values.
assert!(keys.is_empty());
return;
}

// Gaps and keys combined should span whole outer range.
assert_eq!(keys.first().unwrap().start, outer_range.start);
assert_eq!(keys.last().unwrap().end, outer_range.end);

// Each gap/key should start where the previous one ended.
for [a, b] in keys.array_windows::<2>() {
assert_eq!(a.end, b.start);
}
});
93 changes: 93 additions & 0 deletions fuzz/fuzz_targets/rangemap_inclusive_gaps.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#![feature(array_windows)]
#![no_main]
use libfuzzer_sys::fuzz_target;

use arbitrary::{Arbitrary, Unstructured};
use rangemap::RangeInclusiveMap;
use std::ops::RangeInclusive;

#[derive(Clone, Debug, Arbitrary)]
enum Op {
Insert(RangeInclusive<u8>, u8),
Remove(RangeInclusive<u8>),
}

impl Op {
fn apply(self, map: &mut RangeInclusiveMap<u8, u8>) {
match self {
Op::Insert(r, v) => map.insert(r, v),
Op::Remove(r) => map.remove(r),
}
}
}

#[derive(Clone, Debug)]
struct Input {
ops: Vec<Op>,
outer_range: RangeInclusive<u8>,
}

impl Arbitrary for Input {
fn arbitrary(u: &mut Unstructured) -> arbitrary::Result<Self> {
Ok(Self {
ops: u.arbitrary()?,
// Larger margins than these are too
// far away from boundary conditions to be interesting.
// ("Oh, the fools! If only they'd built it with 6,001 hulls." -- Philip J. Fry)
//
// NOTE: Not using `int_in_range` because of <https://github.com/rust-fuzz/arbitrary/issues/106>.
outer_range: *u.choose(&[0, 1, 2, 3, 100, 101, 102, 103])?
..=*u.choose(&[100, 101, 102, 103, 252, 253, 254, 255])?,
})
}
}

fuzz_target!(|input: Input| {
let Input { ops, outer_range } = input;

let mut map = RangeInclusiveMap::new();

for op in ops {
op.apply(&mut map);
}

// Check that the combination of gaps and keys fills the entire outer range.
let gaps: Vec<RangeInclusive<u8>> = map.gaps(&outer_range).collect();
// TODO: Replace the filtering and mapping with a `range` iterator
// on the map itself.
let mut keys: Vec<RangeInclusive<u8>> = map
.into_iter()
.map(|(k, _v)| k)
.filter(|range| {
// Reject anything with zero of its width inside the outer range.
*range.end() >= *outer_range.start() && *range.start() <= *outer_range.end()
})
.map(|range| {
// Truncate anything straddling either edge.
u8::max(*range.start(), *outer_range.start())
..=u8::min(*range.end(), *outer_range.end())
})
.filter(|range| {
// Reject anything that is now empty after being truncated.
!range.is_empty()
})
.collect();
keys.extend(gaps.into_iter());
keys.sort_by_key(|key| *key.start());

if outer_range.is_empty() {
// There should be no gaps or keys returned if the outer range is empty,
// because empty ranges cover no values.
assert!(keys.is_empty());
return;
}

// Gaps and keys combined should span whole outer range.
assert_eq!(keys.first().unwrap().start(), outer_range.start());
assert_eq!(keys.last().unwrap().end(), outer_range.end());

// Each gap/key should start where the previous one ended.
for [a, b] in keys.array_windows::<2>() {
assert_eq!(*a.end() + 1, *b.start());
}
});
Loading