use core::cmp::Ordering;
use core::fmt;
use core::hash::{BuildHasher, Hasher};
use foldhash::fast::FixedState;
const NAME_HASH_SEED: u64 = 0x716c_645f_7379_6d73;
#[derive(Clone, Copy)]
pub struct SymbolName<'a> {
bytes: &'a [u8],
version: Option<&'a [u8]>,
hash: u64,
}
impl<'a> SymbolName<'a> {
#[inline]
#[must_use]
pub fn new(bytes: &'a [u8]) -> Self {
Self::with_version(bytes, None)
}
#[inline]
#[must_use]
pub fn with_version(bytes: &'a [u8], version: Option<&'a [u8]>) -> Self {
let mut hasher = FixedState::with_seed(NAME_HASH_SEED).build_hasher();
hasher.write(bytes);
if let Some(version) = version {
hasher.write_u8(b'@');
hasher.write(version);
}
Self {
bytes,
version,
hash: hasher.finish(),
}
}
#[inline]
#[must_use]
pub fn bytes(&self) -> &'a [u8] {
self.bytes
}
#[inline]
#[must_use]
pub fn version(&self) -> Option<&'a [u8]> {
self.version
}
#[inline]
#[must_use]
pub fn hash(&self) -> u64 {
self.hash
}
#[must_use]
pub fn display(&self) -> impl fmt::Display + '_ {
DisplayName(self)
}
#[inline]
pub(crate) fn cmp_contents(&self, other: &Self) -> Ordering {
self.bytes
.cmp(other.bytes)
.then_with(|| self.version.cmp(&other.version))
}
}
impl PartialEq for SymbolName<'_> {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.hash == other.hash && self.bytes == other.bytes && self.version == other.version
}
}
impl Eq for SymbolName<'_> {}
impl core::hash::Hash for SymbolName<'_> {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_u64(self.hash);
}
}
impl fmt::Debug for SymbolName<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "SymbolName({:?})", self.display().to_string())
}
}
struct DisplayName<'n, 'a>(&'n SymbolName<'a>);
impl fmt::Display for DisplayName<'_, '_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&String::from_utf8_lossy(self.0.bytes))?;
if let Some(version) = self.0.version {
write!(f, "@{}", String::from_utf8_lossy(version))?;
}
Ok(())
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
pub struct InputPosition(u64);
impl InputPosition {
#[inline]
#[must_use]
pub const fn new(input: u32, member: u32) -> Self {
Self(((input as u64) << 32) | member as u64)
}
#[inline]
#[must_use]
pub const fn from_raw(raw: u64) -> Self {
Self(raw)
}
#[inline]
#[must_use]
pub const fn raw(self) -> u64 {
self.0
}
#[inline]
#[must_use]
pub const fn input(self) -> u32 {
(self.0 >> 32) as u32
}
#[inline]
#[must_use]
pub const fn member(self) -> u32 {
self.0 as u32
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hash_is_deterministic_and_covers_version() {
let a = SymbolName::new(b"printf");
let b = SymbolName::new(b"printf");
assert_eq!(a.hash(), b.hash());
assert_eq!(a, b);
let versioned = SymbolName::with_version(b"printf", Some(b"GLIBC_2.2.5"));
assert_ne!(a, versioned);
assert_ne!(a.hash(), versioned.hash());
let split = SymbolName::with_version(b"printfGLIBC", Some(b"_2.2.5"));
assert_ne!(versioned, split);
}
#[test]
#[cfg(all(target_endian = "little", target_pointer_width = "64"))]
fn hash_value_is_pinned() {
assert_eq!(SymbolName::new(b"main").hash(), 0x52ef_74d3_7756_ea78);
assert_eq!(
SymbolName::with_version(b"main", Some(b"V")).hash(),
0x94f4_63c4_e7fe_fe93
);
}
#[test]
fn display_is_lossy_and_shows_version() {
let name = SymbolName::with_version(b"f\xffo", Some(b"V1"));
assert_eq!(name.display().to_string(), "f\u{fffd}o@V1");
}
#[test]
fn positions_order_by_input_then_member() {
let a = InputPosition::new(1, 900);
let b = InputPosition::new(2, 0);
assert!(a < b);
assert_eq!(a.input(), 1);
assert_eq!(a.member(), 900);
assert_eq!(InputPosition::from_raw(a.raw()), a);
}
}