use std::collections::HashMap;
#[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,
}
impl AddrMismatch {
pub fn describe(&self) -> String {
format!(
"{}: __synth_wasm_seg_{}+0x{:x} -> linmem 0x{:x} 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,
});
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,
});
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,
});
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,
});
}
}
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}"),
})
}
#[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
);
}
#[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(_)
));
}
}