use crate::structures::util::{read_u16, read_u32, read_uintptr};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MapLayout {
HmapWithHmapType,
HmapBools,
HmapFlags,
HmapHasher,
Swiss,
SwissSplitGroup,
Probe,
}
impl MapLayout {
pub fn for_go_minor(minor: u32) -> Self {
match minor {
0..=10 => Self::HmapWithHmapType,
11 => Self::HmapBools,
12 | 13 => Self::HmapFlags,
14..=23 => Self::HmapHasher,
24..=26 => Self::Swiss,
_ => Self::SwissSplitGroup,
}
}
pub fn infer(go_minor: Option<u32>, go120_magic: bool, md_is_v5: bool, md_is_v4: bool) -> Self {
if let Some(m) = go_minor {
return Self::for_go_minor(m);
}
if md_is_v5 {
return Self::SwissSplitGroup;
}
if md_is_v4 {
return Self::Swiss;
}
if !go120_magic {
return Self::HmapHasher;
}
Self::Probe
}
pub fn resolve_for(self, extra: &[u8], ps: u8) -> Self {
if self != Self::Probe {
return self;
}
let Some(off) = (ps as usize).checked_mul(4) else {
return Self::Swiss;
};
let Some(word) = read_uintptr(extra, off, ps) else {
return Self::Swiss;
};
if word >> 16 != 0 {
Self::HmapHasher
} else {
Self::Swiss
}
}
pub fn is_hmap(self) -> bool {
matches!(
self,
Self::HmapWithHmapType | Self::HmapBools | Self::HmapFlags | Self::HmapHasher
)
}
fn pointer_field_count(self) -> usize {
match self {
Self::HmapWithHmapType => 4,
Self::HmapBools | Self::HmapFlags => 3,
Self::HmapHasher => 4,
Self::Swiss => 7,
Self::SwissSplitGroup => 10,
Self::Probe => 7,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct MapFlags {
pub indirect_key: Option<bool>,
pub indirect_elem: Option<bool>,
pub reflexive_key: Option<bool>,
pub need_key_update: Option<bool>,
pub hash_might_panic: Option<bool>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MapTypeExtra {
pub layout: MapLayout,
pub key: u64,
pub elem: u64,
pub group: u64,
pub hmap: Option<u64>,
pub hasher: Option<u64>,
pub group_size: Option<u64>,
pub slot_size: Option<u64>,
pub keys_off: Option<u64>,
pub key_stride: Option<u64>,
pub elems_off: Option<u64>,
pub elem_stride: Option<u64>,
pub elem_off: Option<u64>,
pub key_size: Option<u8>,
pub value_size: Option<u8>,
pub bucket_size: Option<u16>,
pub raw_flags: Option<u32>,
pub flags: MapFlags,
}
impl MapTypeExtra {
pub fn size(ps: u8, layout: MapLayout) -> usize {
let p = ps as usize;
let pointers = p.saturating_mul(layout.pointer_field_count());
if layout.is_hmap() {
return pointers.saturating_add(8);
}
let base = pointers.saturating_add(4);
if ps == 8 {
base.saturating_add(4) } else {
base
}
}
pub fn parse(data: &[u8], ps: u8, layout: MapLayout) -> Option<Self> {
let layout = layout.resolve_for(data, ps);
let p = ps as usize;
if data.len() < Self::size(ps, layout) {
return None;
}
let mut off: usize = 0;
let next = |off: &mut usize| -> Option<u64> {
let v = read_uintptr(data, *off, ps)?;
*off = off.checked_add(p)?;
Some(v)
};
let key = next(&mut off)?;
let elem = next(&mut off)?;
let group = next(&mut off)?;
let mut out = Self {
layout,
key,
elem,
group,
hmap: None,
hasher: None,
group_size: None,
slot_size: None,
keys_off: None,
key_stride: None,
elems_off: None,
elem_stride: None,
elem_off: None,
key_size: None,
value_size: None,
bucket_size: None,
raw_flags: None,
flags: MapFlags::default(),
};
match layout {
MapLayout::HmapWithHmapType => {
out.hmap = Some(next(&mut off)?);
out.read_hmap_sizes(data, off)?;
out.read_bool_tail(data, off)?;
}
MapLayout::HmapBools => {
out.read_hmap_sizes(data, off)?;
out.read_bool_tail(data, off)?;
}
MapLayout::HmapFlags => {
out.read_hmap_sizes(data, off)?;
out.read_hmap_flags(data, off.checked_add(4)?)?;
}
MapLayout::HmapHasher => {
out.hasher = Some(next(&mut off)?);
out.read_hmap_sizes(data, off)?;
out.read_hmap_flags(data, off.checked_add(4)?)?;
}
MapLayout::Swiss => {
out.hasher = Some(next(&mut off)?);
out.group_size = Some(next(&mut off)?);
out.slot_size = Some(next(&mut off)?);
out.elem_off = Some(next(&mut off)?);
out.read_swiss_flags(data, off)?;
}
MapLayout::SwissSplitGroup | MapLayout::Probe => {
out.hasher = Some(next(&mut off)?);
out.group_size = Some(next(&mut off)?);
out.keys_off = Some(next(&mut off)?);
out.key_stride = Some(next(&mut off)?);
out.elems_off = Some(next(&mut off)?);
out.elem_stride = Some(next(&mut off)?);
out.elem_off = Some(next(&mut off)?);
out.read_swiss_flags(data, off)?;
}
}
Some(out)
}
fn read_hmap_sizes(&mut self, data: &[u8], off: usize) -> Option<()> {
let bools = matches!(
self.layout,
MapLayout::HmapWithHmapType | MapLayout::HmapBools
);
let (value_at, bucket_at) = if bools {
(2usize, 4usize)
} else {
(1usize, 2usize)
};
self.key_size = data.get(off).copied();
self.value_size = data.get(off.checked_add(value_at)?).copied();
self.bucket_size = read_u16(data, off.checked_add(bucket_at)?);
Some(())
}
fn read_bool_tail(&mut self, data: &[u8], off: usize) -> Option<()> {
let at = |i: usize| -> Option<bool> {
off.checked_add(i)
.and_then(|o| data.get(o))
.map(|&b| b != 0)
};
self.flags = MapFlags {
indirect_key: at(1),
indirect_elem: at(3),
reflexive_key: at(6),
need_key_update: at(7),
hash_might_panic: None,
};
Some(())
}
fn read_hmap_flags(&mut self, data: &[u8], off: usize) -> Option<()> {
let f = read_u32(data, off)?;
self.raw_flags = Some(f);
self.flags = MapFlags {
indirect_key: Some(f & 1 != 0),
indirect_elem: Some(f & 2 != 0),
reflexive_key: Some(f & 4 != 0),
need_key_update: Some(f & 8 != 0),
hash_might_panic: Some(f & 16 != 0),
};
Some(())
}
fn read_swiss_flags(&mut self, data: &[u8], off: usize) -> Option<()> {
let f = read_u32(data, off)?;
self.raw_flags = Some(f);
self.flags = MapFlags {
indirect_key: Some(f & 4 != 0),
indirect_elem: Some(f & 8 != 0),
reflexive_key: None,
need_key_update: Some(f & 1 != 0),
hash_might_panic: Some(f & 2 != 0),
};
Some(())
}
pub fn key_stride(&self) -> Option<u64> {
match self.layout {
l if l.is_hmap() => self.key_size.map(u64::from),
MapLayout::Swiss => self.slot_size,
_ => self.key_stride,
}
}
pub fn elem_stride(&self) -> Option<u64> {
match self.layout {
l if l.is_hmap() => self.value_size.map(u64::from),
MapLayout::Swiss => self.slot_size,
_ => self.elem_stride,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sizes_match_upstream() {
assert_eq!(MapTypeExtra::size(8, MapLayout::HmapWithHmapType), 40);
assert_eq!(MapTypeExtra::size(8, MapLayout::HmapBools), 32);
assert_eq!(MapTypeExtra::size(8, MapLayout::HmapFlags), 32);
assert_eq!(MapTypeExtra::size(8, MapLayout::HmapHasher), 40);
assert_eq!(MapTypeExtra::size(8, MapLayout::Swiss), 64);
assert_eq!(MapTypeExtra::size(8, MapLayout::SwissSplitGroup), 88);
assert_eq!(MapTypeExtra::size(4, MapLayout::HmapWithHmapType), 24);
assert_eq!(MapTypeExtra::size(4, MapLayout::HmapBools), 20);
assert_eq!(MapTypeExtra::size(4, MapLayout::HmapFlags), 20);
assert_eq!(MapTypeExtra::size(4, MapLayout::HmapHasher), 24);
assert_eq!(MapTypeExtra::size(4, MapLayout::Swiss), 32);
assert_eq!(MapTypeExtra::size(4, MapLayout::SwissSplitGroup), 44);
}
#[test]
fn for_go_minor_covers_every_boundary() {
use MapLayout::*;
let cases = [
(7, HmapWithHmapType),
(10, HmapWithHmapType),
(11, HmapBools),
(12, HmapFlags),
(13, HmapFlags),
(14, HmapHasher),
(23, HmapHasher),
(24, Swiss),
(26, Swiss),
(27, SwissSplitGroup),
(28, SwissSplitGroup),
];
for (minor, want) in cases {
assert_eq!(MapLayout::for_go_minor(minor), want, "go1.{minor}");
}
}
#[test]
fn infer_without_a_version_uses_structural_evidence() {
assert_eq!(
MapLayout::infer(None, true, true, false),
MapLayout::SwissSplitGroup
);
assert_eq!(MapLayout::infer(None, true, false, true), MapLayout::Swiss);
assert_eq!(
MapLayout::infer(None, false, false, false),
MapLayout::HmapHasher
);
assert_eq!(MapLayout::infer(None, true, false, false), MapLayout::Probe);
assert_eq!(
MapLayout::infer(Some(11), true, true, true),
MapLayout::HmapBools
);
}
fn go126_map_string_float64() -> Vec<u8> {
let mut d = Vec::new();
for w in [
0x4a9ae0u64, 0x4a9ea0, 0x4b2320, 0x4cf168, 0xc8, 0x18, 0x10, 0x1, ] {
d.extend_from_slice(&w.to_le_bytes());
}
d
}
#[test]
fn swiss_layout_reads_go126_fields() {
let d = go126_map_string_float64();
let m = MapTypeExtra::parse(&d, 8, MapLayout::Swiss).unwrap();
assert_eq!(m.layout, MapLayout::Swiss);
assert_eq!(m.key, 0x4a9ae0);
assert_eq!(m.elem, 0x4a9ea0);
assert_eq!(m.group, 0x4b2320);
assert_eq!(m.hasher, Some(0x4cf168));
assert_eq!(m.group_size, Some(0xc8));
assert_eq!(m.slot_size, Some(0x18));
assert_eq!(m.elem_off, Some(0x10));
assert_eq!(m.raw_flags, Some(1));
assert_eq!(m.flags.need_key_update, Some(true));
assert_eq!(m.flags.indirect_key, Some(false));
assert_eq!(m.flags.reflexive_key, None, "Swiss maps dropped it");
assert!(m.keys_off.is_none());
assert!(m.key_stride.is_none());
assert!(m.elems_off.is_none());
assert!(m.elem_stride.is_none());
}
#[test]
fn go126_descriptor_is_not_read_with_the_go127_layout() {
let d = go126_map_string_float64();
assert_eq!(d.len(), 64);
assert!(
MapTypeExtra::parse(&d, 8, MapLayout::SwissSplitGroup).is_none(),
"the 1.27 layout must not fit a 1.26 descriptor"
);
}
#[test]
fn probe_picks_swiss_for_a_go126_descriptor() {
let d = go126_map_string_float64();
assert_eq!(MapLayout::Probe.resolve_for(&d, 8), MapLayout::Swiss);
let m = MapTypeExtra::parse(&d, 8, MapLayout::Probe).unwrap();
assert_eq!(m.layout, MapLayout::Swiss);
assert_eq!(m.group_size, Some(0xc8));
}
fn hmap_hasher_map_string_float64() -> Vec<u8> {
let mut d = Vec::new();
for w in [0x4a9ae0u64, 0x4a9ea0, 0x4b2320, 0x4cf168] {
d.extend_from_slice(&w.to_le_bytes());
}
d.push(16); d.push(8); d.extend_from_slice(&208u16.to_le_bytes()); d.extend_from_slice(&4u32.to_le_bytes()); d
}
#[test]
fn probe_picks_hmap_for_a_bucket_descriptor() {
let d = hmap_hasher_map_string_float64();
assert_eq!(MapLayout::Probe.resolve_for(&d, 8), MapLayout::HmapHasher);
let m = MapTypeExtra::parse(&d, 8, MapLayout::Probe).unwrap();
assert_eq!(m.layout, MapLayout::HmapHasher);
assert_eq!(m.hasher, Some(0x4cf168));
assert_eq!(m.key_size, Some(16));
assert_eq!(m.value_size, Some(8));
assert_eq!(m.bucket_size, Some(208));
assert_eq!(m.raw_flags, Some(4));
assert_eq!(m.flags.reflexive_key, Some(true));
assert_eq!(m.flags.indirect_key, Some(false));
assert_eq!(m.key_stride(), Some(16));
assert_eq!(m.elem_stride(), Some(8));
assert!(m.group_size.is_none());
assert!(m.slot_size.is_none());
}
#[test]
fn pre_112_bool_tail_decodes_without_a_flags_word() {
let mut d = Vec::new();
for w in [0x4a9ae0u64, 0x4a9ea0, 0x4b2320, 0x4b3000] {
d.extend_from_slice(&w.to_le_bytes());
}
d.extend_from_slice(&[16, 0, 8, 0]);
d.extend_from_slice(&208u16.to_le_bytes());
d.extend_from_slice(&[1, 0]);
let m = MapTypeExtra::parse(&d, 8, MapLayout::HmapWithHmapType).unwrap();
assert_eq!(m.hmap, Some(0x4b3000));
assert_eq!(m.hasher, None, "no hasher field before Go 1.14");
assert_eq!(m.key_size, Some(16));
assert_eq!(m.value_size, Some(8));
assert_eq!(m.bucket_size, Some(208));
assert_eq!(
m.raw_flags, None,
"the bool tail has no flags word to report"
);
assert_eq!(m.flags.indirect_key, Some(false));
assert_eq!(m.flags.indirect_elem, Some(false));
assert_eq!(m.flags.reflexive_key, Some(true));
assert_eq!(m.flags.need_key_update, Some(false));
assert_eq!(m.flags.hash_might_panic, None);
}
#[test]
fn go111_drops_the_hmap_pointer() {
let mut d = Vec::new();
for w in [0x4a9ae0u64, 0x4a9ea0, 0x4b2320] {
d.extend_from_slice(&w.to_le_bytes());
}
d.extend_from_slice(&[16, 1, 8, 0]);
d.extend_from_slice(&208u16.to_le_bytes());
d.extend_from_slice(&[0, 1]);
let m = MapTypeExtra::parse(&d, 8, MapLayout::HmapBools).unwrap();
assert_eq!(m.group, 0x4b2320);
assert_eq!(m.hmap, None);
assert_eq!(m.key_size, Some(16));
assert_eq!(m.value_size, Some(8));
assert_eq!(m.bucket_size, Some(208));
assert_eq!(m.flags.indirect_key, Some(true));
assert_eq!(m.flags.need_key_update, Some(true));
}
#[test]
fn go112_flags_word_replaces_the_bools() {
let mut d = Vec::new();
for w in [0x4a9ae0u64, 0x4a9ea0, 0x4b2320] {
d.extend_from_slice(&w.to_le_bytes());
}
d.push(16);
d.push(8);
d.extend_from_slice(&208u16.to_le_bytes());
d.extend_from_slice(&(1u32 | 16).to_le_bytes());
let m = MapTypeExtra::parse(&d, 8, MapLayout::HmapFlags).unwrap();
assert_eq!(m.hasher, None, "hasher arrives in Go 1.14");
assert_eq!(m.key_size, Some(16));
assert_eq!(m.bucket_size, Some(208));
assert_eq!(m.flags.indirect_key, Some(true));
assert_eq!(m.flags.hash_might_panic, Some(true));
assert_eq!(m.flags.reflexive_key, Some(false));
}
#[test]
fn split_group_layout_reads_every_stride() {
let mut d = Vec::new();
for w in [
0x551000u64, 0x551100, 0x551200, 0x551300, 0xc8, 0x8, 0x18, 0x18, 0x18, 0x10, 0xc, ] {
d.extend_from_slice(&w.to_le_bytes());
}
let m = MapTypeExtra::parse(&d, 8, MapLayout::SwissSplitGroup).unwrap();
assert_eq!(m.keys_off, Some(0x8));
assert_eq!(m.key_stride(), Some(0x18));
assert_eq!(m.elem_stride(), Some(0x18));
assert_eq!(m.elem_off, Some(0x10));
assert_eq!(m.flags.indirect_key, Some(true));
assert_eq!(m.flags.indirect_elem, Some(true));
assert_eq!(m.flags.need_key_update, Some(false));
assert!(m.slot_size.is_none());
}
#[test]
fn too_short_returns_none() {
let d = vec![0u8; 16];
assert!(MapTypeExtra::parse(&d, 8, MapLayout::Swiss).is_none());
assert!(MapTypeExtra::parse(&d, 8, MapLayout::HmapHasher).is_none());
assert!(MapTypeExtra::parse(&d, 8, MapLayout::HmapWithHmapType).is_none());
}
}