#[derive(Clone, Copy, Debug, Default)]
pub struct StringTable<'a> {
data: &'a [u8],
file_offset: u64,
}
impl<'a> StringTable<'a> {
#[must_use]
pub fn new(data: &'a [u8], file_offset: u64) -> Self {
Self { data, file_offset }
}
#[must_use]
pub fn data(&self) -> &'a [u8] {
self.data
}
#[must_use]
pub fn file_offset(&self) -> u64 {
self.file_offset
}
#[inline]
#[must_use]
pub fn get(&self, offset: u32) -> Option<&'a [u8]> {
let start = usize::try_from(offset).ok()?;
let tail = self.data.get(start..)?;
let len = find_nul(tail)?;
tail.get(..len)
}
}
#[inline]
pub(crate) fn find_nul(bytes: &[u8]) -> Option<usize> {
find_byte(bytes, 0)
}
#[inline]
pub(crate) fn find_byte(bytes: &[u8], needle: u8) -> Option<usize> {
const LO: u64 = 0x0101_0101_0101_0101;
const HI: u64 = 0x8080_8080_8080_8080;
let spread = LO.wrapping_mul(u64::from(needle));
let (words, _) = bytes.as_chunks::<8>();
let mut base = 0usize;
for word in words {
let w = u64::from_le_bytes(*word) ^ spread;
let zero = w.wrapping_sub(LO) & !w & HI;
if zero != 0 {
let byte = (zero.trailing_zeros() / 8) as usize;
return Some(base.wrapping_add(byte));
}
base = base.wrapping_add(8);
}
let rest = bytes.get(base..)?;
rest.iter()
.position(|&b| b == needle)
.map(|i| base.wrapping_add(i))
}
#[cfg(test)]
#[allow(clippy::arithmetic_side_effects)]
mod tests {
use super::*;
#[test]
fn finds_nul_everywhere() {
for len in 0..40 {
for pos in 0..len {
let mut v = vec![b'a'; len];
v[pos] = 0;
if pos + 1 < len {
v[len - 1] = 0;
}
assert_eq!(find_nul(&v), Some(pos), "len {len} pos {pos}");
}
assert_eq!(find_nul(&vec![0x80u8; len]), None);
}
assert_eq!(find_nul(&[0x81, 0x80, 0x01, 0x00, 0xff, 0, 0, 0]), Some(3));
}
#[test]
fn finds_any_byte_everywhere() {
for needle in [b'@', 0x80, 0xff, 0x01] {
for len in 0..40 {
for pos in 0..len {
let mut v = vec![needle ^ 0x7e; len];
v[pos] = needle;
if pos + 1 < len {
v[len - 1] = needle;
}
assert_eq!(
find_byte(&v, needle),
Some(pos),
"{needle} len {len} pos {pos}"
);
}
assert_eq!(find_byte(&vec![needle ^ 1; len], needle), None);
}
}
assert_eq!(find_byte(b"_ZN4llvm3foo@@VERS_1", b'@'), Some(12));
assert_eq!(find_byte(&[0x3f, 0xc0, 0x41, 0x40, 0x40], b'@'), Some(3));
}
#[test]
fn lookups() {
let table = StringTable::new(b"\0foo\0bar\0unterminated", 0);
assert_eq!(table.get(0), Some(&b""[..]));
assert_eq!(table.get(1), Some(&b"foo"[..]));
assert_eq!(table.get(3), Some(&b"o"[..]));
assert_eq!(table.get(5), Some(&b"bar"[..]));
assert_eq!(table.get(9), None);
assert_eq!(table.get(100), None);
assert_eq!(table.get(u32::MAX), None);
}
}