use std::collections::HashMap;
pub const MAX_ACCESS_BYTES: u32 = 8;
#[derive(Clone, Debug)]
pub struct DataSegment {
pub linmem_off: u32,
pub bytes: Vec<u8>,
}
#[derive(Clone, Debug)]
pub struct RelocResolution {
pub seg_index: usize,
pub addend: u32,
pub label: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Verdict {
Consistent,
Mismatch(Vec<AddrMismatch>),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AddrMismatch {
pub label: String,
pub seg_index: usize,
pub addend: u32,
pub access_addr: u32,
pub served: u8,
pub runtime: u8,
pub span_byte: u32,
}
impl AddrMismatch {
pub fn describe(&self) -> String {
let span = if self.span_byte == 0 {
String::new()
} else {
format!(
" (span byte +{} of a possibly {}-byte access)",
self.span_byte, MAX_ACCESS_BYTES
)
};
format!(
"{}: __synth_wasm_seg_{}+0x{:x} -> linmem 0x{:x}{span} serves 0x{:02x} but \
the runtime image (segments applied later-wins) owns 0x{:02x}",
self.label, self.seg_index, self.addend, self.access_addr, self.served, self.runtime
)
}
}
fn runtime_image(segments: &[DataSegment]) -> HashMap<u32, u8> {
let mut mem = HashMap::new();
for seg in segments {
for (j, &b) in seg.bytes.iter().enumerate() {
mem.insert(seg.linmem_off + j as u32, b);
}
}
mem
}
pub fn validate_reloc_resolutions(
segments: &[DataSegment],
resolutions: &[RelocResolution],
) -> Verdict {
let runtime = runtime_image(segments);
let mut bad = Vec::new();
for r in resolutions {
let Some(seg) = segments.get(r.seg_index) else {
bad.push(AddrMismatch {
label: r.label.clone(),
seg_index: r.seg_index,
addend: r.addend,
access_addr: 0,
served: 0,
runtime: 0,
span_byte: 0,
});
continue;
};
let access_addr = seg.linmem_off + r.addend;
let Some(&served) = seg.bytes.get(r.addend as usize) else {
bad.push(AddrMismatch {
label: r.label.clone(),
seg_index: r.seg_index,
addend: r.addend,
access_addr,
served: 0,
runtime: 0,
span_byte: 0,
});
continue;
};
let Some(&runtime_byte) = runtime.get(&access_addr) else {
bad.push(AddrMismatch {
label: r.label.clone(),
seg_index: r.seg_index,
addend: r.addend,
access_addr,
served,
runtime: 0,
span_byte: 0,
});
continue;
};
if served != runtime_byte {
bad.push(AddrMismatch {
label: r.label.clone(),
seg_index: r.seg_index,
addend: r.addend,
access_addr,
served,
runtime: runtime_byte,
span_byte: 0,
});
}
}
if bad.is_empty() {
Verdict::Consistent
} else {
Verdict::Mismatch(bad)
}
}
pub fn resolve_owner(segments: &[DataSegment], c: u32, last_wins: bool) -> Option<RelocResolution> {
let hit = |(off, len): (u32, usize)| c >= off && c < off + len as u32;
let idx = if last_wins {
segments
.iter()
.rposition(|s| hit((s.linmem_off, s.bytes.len())))
} else {
segments
.iter()
.position(|s| hit((s.linmem_off, s.bytes.len())))
}?;
Some(RelocResolution {
seg_index: idx,
addend: c - segments[idx].linmem_off,
label: format!("addr 0x{c:x}"),
})
}
#[derive(Clone, Debug)]
pub struct PackedInit<'a> {
pub seg_packed_off: &'a [u32],
pub bytes: &'a [u8],
}
pub fn validate_reloc_resolutions_spanned(
segments: &[DataSegment],
resolutions: &[RelocResolution],
packed: &PackedInit<'_>,
) -> Verdict {
let runtime = runtime_image(segments);
let mut bad = Vec::new();
if let Verdict::Mismatch(m) = validate_reloc_resolutions(segments, resolutions) {
bad.extend(m);
}
for r in resolutions {
let Some(seg) = segments.get(r.seg_index) else {
continue; };
let Some(&poff) = packed.seg_packed_off.get(r.seg_index) else {
continue; };
for j in 0..MAX_ACCESS_BYTES {
let access_addr = seg.linmem_off.wrapping_add(r.addend).wrapping_add(j);
let Some(&runtime_byte) = runtime.get(&access_addr) else {
continue;
};
if j == 0 && seg.bytes.get(r.addend as usize) != Some(&runtime_byte) {
continue;
}
let p = poff as usize + r.addend as usize + j as usize;
let served = packed.bytes.get(p).copied();
if served != Some(runtime_byte) {
bad.push(AddrMismatch {
label: r.label.clone(),
seg_index: r.seg_index,
addend: r.addend,
access_addr,
served: served.unwrap_or(0),
runtime: runtime_byte,
span_byte: j,
});
}
}
}
if bad.is_empty() {
Verdict::Consistent
} else {
Verdict::Mismatch(bad)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ImageVerdict {
Consistent,
Mismatch(Vec<ImageMismatch>),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ImageMismatch {
pub addr: u32,
pub served: u8,
pub runtime: u8,
}
impl ImageMismatch {
pub fn describe(&self) -> String {
format!(
"linmem 0x{:x} serves 0x{:02x} but the runtime image (segments \
applied later-wins) owns 0x{:02x}",
self.addr, self.served, self.runtime
)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum LayoutVerdict {
Disjoint,
Overlap {
func_visible_linmem_base: u32,
linmem_bytes: u32,
globals_base: u32,
overlap_bytes: u32,
},
}
pub fn validate_linmem_globals_disjoint(
func_visible_linmem_base: u32,
linmem_bytes: u32,
globals_base: u32,
globals_bytes: u32,
) -> LayoutVerdict {
if globals_bytes == 0 {
return LayoutVerdict::Disjoint;
}
let ceiling = func_visible_linmem_base as u64 + linmem_bytes as u64;
if (globals_base as u64) < ceiling {
return LayoutVerdict::Overlap {
func_visible_linmem_base,
linmem_bytes,
globals_base,
overlap_bytes: (ceiling - globals_base as u64) as u32,
};
}
LayoutVerdict::Disjoint
}
pub fn image_extent(segments: &[DataSegment]) -> u64 {
segments
.iter()
.map(|s| s.linmem_off as u64 + s.bytes.len() as u64)
.max()
.unwrap_or(0)
}
pub fn pack_rom_image(segments: &[DataSegment], last_wins: bool) -> Vec<u8> {
let mut blob = vec![0u8; image_extent(segments) as usize];
let place = |blob: &mut Vec<u8>, s: &DataSegment| {
let at = s.linmem_off as usize;
blob[at..at + s.bytes.len()].copy_from_slice(&s.bytes);
};
if last_wins {
for s in segments {
place(&mut blob, s);
}
} else {
for s in segments.iter().rev() {
place(&mut blob, s);
}
}
blob
}
pub fn validate_served_image(segments: &[DataSegment], image: &[u8]) -> ImageVerdict {
let runtime = runtime_image(segments);
let mut bad = Vec::new();
for (addr, &served) in image.iter().enumerate() {
let owed = runtime.get(&(addr as u32)).copied().unwrap_or(0);
if served != owed {
bad.push(ImageMismatch {
addr: addr as u32,
served,
runtime: owed,
});
}
}
let mut beyond: Vec<(u32, u8)> = runtime
.into_iter()
.filter(|&(addr, owed)| addr as u64 >= image.len() as u64 && owed != 0)
.collect();
beyond.sort_unstable();
for (addr, owed) in beyond {
bad.push(ImageMismatch {
addr,
served: 0,
runtime: owed,
});
}
if bad.is_empty() {
ImageVerdict::Consistent
} else {
ImageVerdict::Mismatch(bad)
}
}
pub fn pack_segment_records(segments: &[DataSegment]) -> Vec<u8> {
let mut out = Vec::new();
for s in segments {
out.extend_from_slice(&s.linmem_off.to_le_bytes());
out.extend_from_slice(&(s.bytes.len() as u32).to_le_bytes());
out.extend_from_slice(&s.bytes);
while out.len() % 4 != 0 {
out.push(0);
}
}
out
}
pub fn parse_segment_records(blob: &[u8]) -> Option<Vec<DataSegment>> {
let mut recs = Vec::new();
let mut i = 0usize;
while i < blob.len() {
let hdr = blob.get(i..i + 8)?;
let off = u32::from_le_bytes(hdr[0..4].try_into().unwrap());
let len = u32::from_le_bytes(hdr[4..8].try_into().unwrap()) as usize;
i += 8;
let bytes = blob.get(i..i + len)?.to_vec();
i += len;
let aligned = i.next_multiple_of(4);
if aligned > blob.len() {
return None;
}
i = aligned;
recs.push(DataSegment {
linmem_off: off,
bytes,
});
}
Some(recs)
}
pub fn served_image_from_records(blob: &[u8]) -> Option<Vec<u8>> {
let recs = parse_segment_records(blob)?;
let extent = recs
.iter()
.map(|r| r.linmem_off as u64 + r.bytes.len() as u64)
.max()
.unwrap_or(0);
if extent > u32::MAX as u64 {
return None;
}
let mut image = vec![0u8; extent as usize];
for r in &recs {
let at = r.linmem_off as usize;
image[at..at + r.bytes.len()].copy_from_slice(&r.bytes);
}
Some(image)
}
#[cfg(test)]
mod tests {
use super::*;
fn overlapping_segments() -> Vec<DataSegment> {
vec![
DataSegment {
linmem_off: 0x100000,
bytes: vec![0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x20, 0xAA, 0xBB],
},
DataSegment {
linmem_off: 0x100000,
bytes: vec![0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x10],
},
DataSegment {
linmem_off: 0x100000,
bytes: b"gust:os up\n".to_vec(),
},
]
}
#[test]
fn red_on_first_match_green_on_last_match() {
let segs = overlapping_segments();
let c = 0x100008;
assert_eq!(segs[0].bytes[8], 0xAA); assert_eq!(segs[2].bytes[8], b'u');
let wrong = resolve_owner(&segs, c, false).unwrap();
assert_eq!(wrong.seg_index, 0, "first-match must pick seg_0");
let red = validate_reloc_resolutions(&segs, std::slice::from_ref(&wrong));
match red {
Verdict::Mismatch(m) => {
assert_eq!(m.len(), 1);
assert_eq!(m[0].seg_index, 0);
assert_eq!(m[0].access_addr, c);
assert_eq!(m[0].served, 0xAA, "seg_0 serves the stale byte");
assert_eq!(m[0].runtime, b'u', "runtime image (seg_2) owns 'u'");
}
Verdict::Consistent => {
panic!("VACUOUS: validator accepted the #757 wrong-segment resolution")
}
}
let right = resolve_owner(&segs, c, true).unwrap();
assert_eq!(right.seg_index, 2, "last-match must pick seg_2");
assert_eq!(
validate_reloc_resolutions(&segs, std::slice::from_ref(&right)),
Verdict::Consistent,
"the runtime-correct resolution must pass"
);
}
#[test]
fn non_overlapping_both_policies_consistent() {
let segs = vec![
DataSegment {
linmem_off: 0x1000,
bytes: vec![1, 2, 3, 4],
},
DataSegment {
linmem_off: 0x2000,
bytes: vec![5, 6, 7, 8],
},
];
for &c in &[0x1002u32, 0x2003] {
let a = resolve_owner(&segs, c, false).unwrap();
let b = resolve_owner(&segs, c, true).unwrap();
assert_eq!(a.seg_index, b.seg_index);
assert_eq!(validate_reloc_resolutions(&segs, &[a]), Verdict::Consistent);
assert_eq!(validate_reloc_resolutions(&segs, &[b]), Verdict::Consistent);
}
}
#[test]
fn partial_overlap_tail_wins() {
let segs = vec![
DataSegment {
linmem_off: 0x100,
bytes: vec![0x10, 0x11, 0x12, 0x13, 0x14, 0x15],
},
DataSegment {
linmem_off: 0x104,
bytes: vec![0xF4, 0xF5, 0xF6, 0xF7],
},
];
let c = 0x104; let wrong = resolve_owner(&segs, c, false).unwrap();
assert_eq!(wrong.seg_index, 0);
assert!(matches!(
validate_reloc_resolutions(&segs, &[wrong]),
Verdict::Mismatch(_)
));
let right = resolve_owner(&segs, c, true).unwrap();
assert_eq!(right.seg_index, 1);
assert_eq!(
validate_reloc_resolutions(&segs, &[right]),
Verdict::Consistent
);
let head = resolve_owner(&segs, 0x100, false).unwrap();
assert_eq!(head.seg_index, 0);
assert_eq!(
validate_reloc_resolutions(&segs, &[head]),
Verdict::Consistent
);
}
fn mixed_pack(segments: &[DataSegment]) -> (Vec<u32>, Vec<u8>) {
let mut offs = Vec::with_capacity(segments.len());
let mut cur = 0u32;
for s in segments {
cur = cur.next_multiple_of(4);
offs.push(cur);
cur += s.bytes.len() as u32;
}
let mut blob = vec![0u8; cur as usize];
for (s, &o) in segments.iter().zip(offs.iter()) {
blob[o as usize..o as usize + s.bytes.len()].copy_from_slice(&s.bytes);
}
(offs, blob)
}
#[test]
fn phase1_green_but_span_red_on_staggered_overlap() {
let segs = vec![
DataSegment {
linmem_off: 0x10004,
bytes: vec![0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7],
},
DataSegment {
linmem_off: 0x10008,
bytes: vec![0xB0, 0xB1, 0xB2, 0xB3],
},
];
let r = resolve_owner(&segs, 0x10006, true).unwrap();
assert_eq!(r.seg_index, 0, "correct owner of the addend byte is seg_0");
assert_eq!(
validate_reloc_resolutions(&segs, std::slice::from_ref(&r)),
Verdict::Consistent,
"phase 1 must accept the addend byte (it IS runtime-correct)"
);
let (offs, blob) = mixed_pack(&segs);
let packed = PackedInit {
seg_packed_off: &offs,
bytes: &blob,
};
match validate_reloc_resolutions_spanned(&segs, std::slice::from_ref(&r), &packed) {
Verdict::Mismatch(m) => {
assert_eq!(m[0].span_byte, 2, "first divergent byte is +2");
assert_eq!(m[0].access_addr, 0x10008);
assert_eq!(m[0].served, 0xA4, "packed seg_0 serves its stale byte");
assert_eq!(m[0].runtime, 0xB0, "runtime image owns seg_1's byte");
}
Verdict::Consistent => {
panic!("VACUOUS: spanned validator accepted a straddling stale-tail access")
}
}
}
#[test]
fn span_green_on_adjacency_preserving_crossing() {
let segs = vec![
DataSegment {
linmem_off: 0x100,
bytes: vec![1, 2, 3, 4],
},
DataSegment {
linmem_off: 0x104,
bytes: vec![5, 6, 7, 8],
},
];
let (offs, blob) = mixed_pack(&segs);
let packed = PackedInit {
seg_packed_off: &offs,
bytes: &blob,
};
let r = resolve_owner(&segs, 0x102, true).unwrap();
assert_eq!(r.seg_index, 0);
assert_eq!(
validate_reloc_resolutions_spanned(&segs, &[r], &packed),
Verdict::Consistent,
"packed adjacency == linmem adjacency: the crossing serves the right bytes"
);
}
#[test]
fn span_red_on_padding_shifted_crossing() {
let segs = vec![
DataSegment {
linmem_off: 0x100,
bytes: vec![1, 2, 3], },
DataSegment {
linmem_off: 0x103,
bytes: vec![5, 6, 7, 8],
},
];
let (offs, blob) = mixed_pack(&segs);
let packed = PackedInit {
seg_packed_off: &offs,
bytes: &blob,
};
let r = resolve_owner(&segs, 0x101, true).unwrap();
assert_eq!(r.seg_index, 0);
match validate_reloc_resolutions_spanned(&segs, &[r], &packed) {
Verdict::Mismatch(m) => {
assert_eq!(m[0].span_byte, 2);
assert_eq!(m[0].access_addr, 0x103);
assert_eq!(m[0].served, 0, "the pack serves 4-align padding");
assert_eq!(m[0].runtime, 5);
}
Verdict::Consistent => panic!("VACUOUS: padding-shifted crossing accepted"),
}
}
#[test]
fn span_green_on_sparse_tail() {
let segs = vec![
DataSegment {
linmem_off: 0x100,
bytes: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
},
DataSegment {
linmem_off: 0x400,
bytes: vec![0xFF; 4],
},
];
let (offs, blob) = mixed_pack(&segs);
let packed = PackedInit {
seg_packed_off: &offs,
bytes: &blob,
};
let r = resolve_owner(&segs, 0x108, true).unwrap();
assert_eq!(r.seg_index, 0);
assert_eq!(
validate_reloc_resolutions_spanned(&segs, &[r], &packed),
Verdict::Consistent,
"uncovered span bytes are implicit-zero linmem — must not false-red"
);
}
#[test]
fn span_red_on_init_region_escape() {
let segs = vec![
DataSegment {
linmem_off: 0x100,
bytes: vec![0x11; 8], },
DataSegment {
linmem_off: 0x100,
bytes: vec![0x22; 4], },
];
let (offs, blob) = mixed_pack(&segs);
assert_eq!(blob.len(), 12, "seg_1 is the final packed segment");
let packed = PackedInit {
seg_packed_off: &offs,
bytes: &blob,
};
let r = resolve_owner(&segs, 0x102, true).unwrap();
assert_eq!(r.seg_index, 1);
match validate_reloc_resolutions_spanned(&segs, &[r], &packed) {
Verdict::Mismatch(m) => {
assert_eq!(m[0].span_byte, 2);
assert_eq!(m[0].access_addr, 0x104);
assert_eq!(m[0].runtime, 0x11);
}
Verdict::Consistent => panic!("VACUOUS: init-region escape accepted"),
}
}
#[test]
fn span_red_on_blob_fill_corruption_at_addend_byte() {
let segs = vec![DataSegment {
linmem_off: 0x100,
bytes: vec![1, 2, 3, 4],
}];
let (offs, mut blob) = mixed_pack(&segs);
let r = resolve_owner(&segs, 0x102, true).unwrap();
assert_eq!(
validate_reloc_resolutions(&segs, std::slice::from_ref(&r)),
Verdict::Consistent,
"phase 1 (segment bytes) cannot see a blob-fill bug"
);
blob[2] = 0xEE; let packed = PackedInit {
seg_packed_off: &offs,
bytes: &blob,
};
match validate_reloc_resolutions_spanned(&segs, std::slice::from_ref(&r), &packed) {
Verdict::Mismatch(m) => {
assert_eq!(m[0].span_byte, 0);
assert_eq!(m[0].served, 0xEE);
assert_eq!(m[0].runtime, 3);
}
Verdict::Consistent => panic!("VACUOUS: corrupted shipped blob accepted"),
}
}
#[test]
fn rom_image_red_on_first_wins_green_on_last_wins() {
let segs = overlapping_segments();
let wrong = pack_rom_image(&segs, false);
match validate_served_image(&segs, &wrong) {
ImageVerdict::Mismatch(m) => {
let at8 = m.iter().find(|x| x.addr == 0x100008).expect("addr 8");
assert_eq!(at8.served, 0xAA);
assert_eq!(at8.runtime, b'u');
}
ImageVerdict::Consistent => {
panic!("VACUOUS: dense-image validator accepted a first-wins pack")
}
}
let right = pack_rom_image(&segs, true);
assert_eq!(
validate_served_image(&segs, &right),
ImageVerdict::Consistent,
"declaration-order (later-wins) pack must validate"
);
}
#[test]
fn rom_image_gap_garbage_red_zero_tail_green() {
let segs = vec![
DataSegment {
linmem_off: 0,
bytes: vec![1, 2],
},
DataSegment {
linmem_off: 8,
bytes: vec![0, 0, 0, 0],
},
];
let mut img = pack_rom_image(&segs, true);
img[4] = 0xCC;
assert!(matches!(
validate_served_image(&segs, &img),
ImageVerdict::Mismatch(_)
));
assert_eq!(
validate_served_image(&segs, &[1, 2]),
ImageVerdict::Consistent
);
}
#[test]
fn zero_served_image_red_on_nonzero_green_on_zeroed() {
let nonzero = vec![DataSegment {
linmem_off: 16,
bytes: vec![1, 2, 3, 4],
}];
match validate_served_image(&nonzero, &[]) {
ImageVerdict::Mismatch(m) => {
assert_eq!(m[0].addr, 16);
assert_eq!(m[0].served, 0);
assert_eq!(m[0].runtime, 1);
}
ImageVerdict::Consistent => panic!("VACUOUS: dropped nonzero initializer accepted"),
}
let zeroed = vec![
DataSegment {
linmem_off: 16,
bytes: vec![1, 2, 3, 4],
},
DataSegment {
linmem_off: 16,
bytes: vec![0, 0, 0, 0],
},
];
assert_eq!(
validate_served_image(&zeroed, &[]),
ImageVerdict::Consistent
);
}
#[test]
fn out_of_range_is_mismatch() {
let segs = vec![DataSegment {
linmem_off: 0,
bytes: vec![1, 2, 3],
}];
let bad = RelocResolution {
seg_index: 0,
addend: 99,
label: "oob".into(),
};
assert!(matches!(
validate_reloc_resolutions(&segs, &[bad]),
Verdict::Mismatch(_)
));
}
#[test]
fn segment_records_round_trip() {
let segs = vec![
DataSegment {
linmem_off: 16,
bytes: vec![1, 2, 3], },
DataSegment {
linmem_off: 0x10000,
bytes: vec![0xAA; 8],
},
DataSegment {
linmem_off: 4,
bytes: vec![9], },
];
let blob = pack_segment_records(&segs);
assert_eq!(blob.len() % 4, 0, "records blob is 4-aligned throughout");
let back = parse_segment_records(&blob).expect("well-formed blob parses");
assert_eq!(back.len(), 3);
for (a, b) in segs.iter().zip(back.iter()) {
assert_eq!(a.linmem_off, b.linmem_off);
assert_eq!(a.bytes, b.bytes);
}
}
#[test]
fn records_red_on_reversed_pack_green_on_declaration_order() {
let segs = overlapping_segments();
let mut reversed = segs.clone();
reversed.reverse();
let wrong_blob = pack_segment_records(&reversed);
let wrong_served = served_image_from_records(&wrong_blob).unwrap();
match validate_served_image(&segs, &wrong_served) {
ImageVerdict::Mismatch(m) => {
let at8 = m.iter().find(|x| x.addr == 0x100008).expect("addr 8");
assert_eq!(at8.served, 0xAA, "reversed pack serves seg_0's stale byte");
assert_eq!(at8.runtime, b'u', "runtime image owns seg_2's byte");
}
ImageVerdict::Consistent => {
panic!("VACUOUS: read-back gate accepted a reversed (first-wins) pack")
}
}
let right_blob = pack_segment_records(&segs);
let right_served = served_image_from_records(&right_blob).unwrap();
assert_eq!(
validate_served_image(&segs, &right_served),
ImageVerdict::Consistent,
"declaration-order records must serve the later-wins image"
);
}
#[test]
fn records_far_offset_segment_served_correctly() {
let segs = vec![DataSegment {
linmem_off: 0x10000,
bytes: vec![7, 8, 9, 10],
}];
let blob = pack_segment_records(&segs);
assert_eq!(blob.len(), 12, "8-byte header + 4 bytes, no dense image");
let served = served_image_from_records(&blob).unwrap();
assert_eq!(served.len(), 0x10004);
assert_eq!(
validate_served_image(&segs, &served),
ImageVerdict::Consistent
);
}
#[test]
fn records_malformed_blobs_rejected() {
let segs = vec![DataSegment {
linmem_off: 4,
bytes: vec![1, 2, 3, 4, 5],
}];
let blob = pack_segment_records(&segs);
assert!(parse_segment_records(&blob[..4]).is_none(), "cut header");
assert!(parse_segment_records(&blob[..10]).is_none(), "cut payload");
assert!(
parse_segment_records(&blob[..blob.len() - 1]).is_none(),
"cut align padding"
);
assert!(served_image_from_records(&blob[..10]).is_none());
assert_eq!(parse_segment_records(&[]).unwrap().len(), 0);
assert_eq!(served_image_from_records(&[]).unwrap().len(), 0);
}
#[test]
fn layout_gate_761_red_r11_based_globals_overlap() {
let func_visible = 0x2000_0100u32;
let linmem = 64 * 1024;
let bad_globals_base = 0x2000_0000u32 + linmem; let v = validate_linmem_globals_disjoint(func_visible, linmem, bad_globals_base, 4);
assert_eq!(
v,
LayoutVerdict::Overlap {
func_visible_linmem_base: func_visible,
linmem_bytes: linmem,
globals_base: bad_globals_base,
overlap_bytes: 0x100,
}
);
}
#[test]
fn layout_gate_761_green_func_visible_based_globals_disjoint() {
let func_visible = 0x2000_0100u32;
let linmem = 64 * 1024;
let good_globals_base = func_visible + linmem; assert_eq!(
validate_linmem_globals_disjoint(func_visible, linmem, good_globals_base, 4),
LayoutVerdict::Disjoint
);
}
#[test]
fn layout_gate_761_ceiling_boundary_is_exclusive() {
let base = 0x2000_0100u32;
let linmem = 0x1000;
let ceiling = base + linmem;
assert_eq!(
validate_linmem_globals_disjoint(base, linmem, ceiling, 8),
LayoutVerdict::Disjoint
);
match validate_linmem_globals_disjoint(base, linmem, ceiling - 1, 8) {
LayoutVerdict::Overlap { overlap_bytes, .. } => assert_eq!(overlap_bytes, 1),
v => panic!("expected 1-byte overlap, got {v:?}"),
}
}
#[test]
fn layout_gate_761_no_globals_is_disjoint() {
assert_eq!(
validate_linmem_globals_disjoint(0x2000_0100, 64 * 1024, 0x2000_0000, 0),
LayoutVerdict::Disjoint
);
}
#[test]
fn layout_gate_761_low_layout_func_visible_based_disjoint() {
let stack = 0x1000u32;
let func_visible = 0x2000_0100 + stack; let linmem = 64 * 1024;
assert_eq!(
validate_linmem_globals_disjoint(func_visible, linmem, func_visible + linmem, 4),
LayoutVerdict::Disjoint
);
let bad = (0x2000_0000 + stack) + linmem;
match validate_linmem_globals_disjoint(func_visible, linmem, bad, 4) {
LayoutVerdict::Overlap { overlap_bytes, .. } => assert_eq!(overlap_bytes, 0x100),
v => panic!("expected 0x100 overlap under low, got {v:?}"),
}
}
}