pub mod varint;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Site {
pub line: u32,
pub column: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Error {
Truncated,
Overflow,
OutOfOrder {
at: u32,
},
TrailingBytes {
count: usize,
},
}
impl From<varint::Error> for Error {
fn from(err: varint::Error) -> Self {
match err {
varint::Error::Truncated => Self::Truncated,
varint::Error::Overflow => Self::Overflow,
}
}
}
#[must_use]
pub fn resolve(table: &[u8], address: u32) -> Option<Site> {
let mut at = 0usize;
let count = varint::u32(table, &mut at).ok()?;
let mut current = 0u32;
for _ in 0..count {
current = current.checked_add(varint::u32(table, &mut at).ok()?)?;
let line = varint::u32(table, &mut at).ok()?;
let column = varint::u32(table, &mut at).ok()?;
if current == address {
return (line != 0).then_some(Site { line, column });
}
if current > address {
return None;
}
}
None
}
pub fn count(table: &[u8]) -> Result<u32, Error> {
let mut at = 0usize;
Ok(varint::u32(table, &mut at)?)
}
pub fn check(table: &[u8]) -> Result<(), Error> {
let mut at = 0usize;
let count = varint::u32(table, &mut at)?;
let mut previous: Option<u32> = None;
for _ in 0..count {
let delta = varint::u32(table, &mut at)?;
let address = match previous {
None => delta,
Some(previous) if delta == 0 => return Err(Error::OutOfOrder { at: previous }),
Some(previous) => previous.checked_add(delta).ok_or(Error::Overflow)?,
};
let _line = varint::u32(table, &mut at)?;
let _column = varint::u32(table, &mut at)?;
previous = Some(address);
}
if at != table.len() {
return Err(Error::TrailingBytes {
count: table.len() - at,
});
}
Ok(())
}
#[must_use]
pub fn encode(sites: impl IntoIterator<Item = (u32, Site)>) -> alloc::vec::Vec<u8> {
let kept: alloc::vec::Vec<_> = sites
.into_iter()
.filter(|(_, site)| site.line != 0)
.collect();
let mut out = alloc::vec::Vec::new();
varint::put_u64(&mut out, kept.len() as u64);
let mut previous = 0u32;
for (index, (address, site)) in kept.iter().enumerate() {
if index > 0 {
assert!(
*address > previous,
"table addresses must strictly ascend: {address} follows {previous}",
);
}
varint::put_u64(&mut out, u64::from(address - previous));
varint::put_u64(&mut out, u64::from(site.line));
varint::put_u64(&mut out, u64::from(site.column));
previous = *address;
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
use alloc::vec::Vec;
fn site(line: u32, column: u32) -> Site {
Site { line, column }
}
fn table() -> Vec<u8> {
encode(vec![
(0, site(1, 5)),
(3, site(1, 11)),
(40, site(7, 2)),
(41, site(9, 0)),
])
}
#[test]
fn a_recorded_address_resolves_to_its_site() {
let table = table();
assert_eq!(resolve(&table, 0), Some(site(1, 5)));
assert_eq!(resolve(&table, 3), Some(site(1, 11)));
assert_eq!(resolve(&table, 40), Some(site(7, 2)));
}
#[test]
fn the_start_of_a_line_is_a_position() {
assert_eq!(resolve(&table(), 41), Some(site(9, 0)));
}
#[test]
fn an_address_with_no_site_resolves_to_nothing() {
let table = table();
assert_eq!(resolve(&table, 1), None, "between two entries");
assert_eq!(resolve(&table, 99), None, "past the last entry");
}
#[test]
fn an_empty_table_resolves_nothing_and_is_sound() {
let table = encode(Vec::new());
assert_eq!(check(&table), Ok(()));
assert_eq!(count(&table), Ok(0));
assert_eq!(resolve(&table, 0), None);
}
#[test]
fn sites_with_no_line_are_not_stored() {
let table = encode(vec![(0, site(1, 1)), (1, site(0, 0)), (2, site(3, 3))]);
assert_eq!(count(&table), Ok(2));
assert_eq!(resolve(&table, 1), None);
assert_eq!(resolve(&table, 2), Some(site(3, 3)));
}
#[test]
fn a_dense_run_costs_three_bytes_an_entry() {
let dense: Vec<_> = (0..100).map(|pc| (pc, site(1, 1))).collect();
let table = encode(dense);
assert_eq!(table.len(), 1 + 100 * 3);
}
#[test]
fn a_sound_table_passes_its_own_check() {
assert_eq!(check(&table()), Ok(()));
}
#[test]
fn a_truncated_table_is_named_as_such() {
let table = table();
for cut in 1..table.len() {
assert!(
check(&table[..cut]).is_err(),
"a {cut}-byte prefix passed the check",
);
}
}
#[test]
fn a_repeated_address_is_refused() {
let mut bytes = Vec::new();
varint::put_u64(&mut bytes, 2);
for _ in 0..2 {
varint::put_u64(&mut bytes, 0); varint::put_u64(&mut bytes, 1); varint::put_u64(&mut bytes, 1); }
assert_eq!(check(&bytes), Err(Error::OutOfOrder { at: 0 }));
}
#[test]
fn bytes_past_the_last_entry_are_refused() {
let mut bytes = table();
bytes.push(0);
assert_eq!(check(&bytes), Err(Error::TrailingBytes { count: 1 }));
}
#[test]
fn no_byte_string_can_make_resolution_panic() {
let table = table();
for index in 0..table.len() {
for bit in 0..8 {
let mut corrupt = table.clone();
corrupt[index] ^= 1 << bit;
for address in 0..64 {
let _ = resolve(&corrupt, address);
}
let _ = check(&corrupt);
}
}
for junk in [&b""[..], &[0xff][..], &[0xff; 32][..], &[0x80; 12][..]] {
for address in 0..8 {
let _ = resolve(junk, address);
}
let _ = check(junk);
}
}
#[test]
#[should_panic(expected = "must strictly ascend")]
fn encoding_out_of_order_sites_is_a_caller_bug() {
let _ = encode(vec![(5, site(1, 1)), (2, site(1, 1))]);
}
}