pub const WSC_FACTS_SECTION_NAME: &str = "wsc.facts";
pub const WSC_FACTS_SCHEMA_VERSION: u8 = 1;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WscFact {
pub func_index: u32,
pub value_id: u32,
pub kind: FactKind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FactKind {
ValueRange { lo: i64, hi: i64 },
ShiftBound { bound: u32 },
DivisorNonZero,
InBoundsAccess { access_width: u32 },
SelectTotality,
MemoryDisjointness { other_value_id: u32 },
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct WscFactsParse {
pub facts: Vec<WscFact>,
pub section_ignored: Option<String>,
pub records_skipped: u32,
}
pub fn parse_wsc_facts(payload: &[u8]) -> WscFactsParse {
let mut out = WscFactsParse::default();
if parse_wsc_facts_inner(payload, &mut out).is_none() {
return WscFactsParse {
facts: Vec::new(),
section_ignored: Some(
"unknown schema version or truncated/overrun record framing".to_string(),
),
records_skipped: 0,
};
}
out
}
fn parse_wsc_facts_inner(payload: &[u8], out: &mut WscFactsParse) -> Option<()> {
let mut r = Reader::new(payload);
if r.byte()? != WSC_FACTS_SCHEMA_VERSION {
return None;
}
let count = r.leb_u32()?;
for _ in 0..count {
let kind = r.byte()?;
let func_index = r.leb_u32()?;
let value_id = r.leb_u32()?;
let body_len = r.leb_u32()?;
let body = r.bytes(body_len as usize)?;
match decode_body(kind, body) {
Some(kind) => out.facts.push(WscFact {
func_index,
value_id,
kind,
}),
None => out.records_skipped += 1,
}
}
if !r.is_empty() {
return None;
}
Some(())
}
fn decode_body(kind: u8, body: &[u8]) -> Option<FactKind> {
let mut r = Reader::new(body);
let fact = match kind {
0x01 => FactKind::ValueRange {
lo: r.leb_s64()?,
hi: r.leb_s64()?,
},
0x02 => FactKind::ShiftBound {
bound: r.leb_u32()?,
},
0x03 => FactKind::DivisorNonZero,
0x04 => FactKind::InBoundsAccess {
access_width: r.leb_u32()?,
},
0x05 => FactKind::SelectTotality,
0x06 => FactKind::MemoryDisjointness {
other_value_id: r.leb_u32()?,
},
_ => return None,
};
if !r.is_empty() {
return None;
}
Some(fact)
}
struct Reader<'a> {
data: &'a [u8],
pos: usize,
}
impl<'a> Reader<'a> {
fn new(data: &'a [u8]) -> Self {
Self { data, pos: 0 }
}
fn is_empty(&self) -> bool {
self.pos >= self.data.len()
}
fn byte(&mut self) -> Option<u8> {
let b = *self.data.get(self.pos)?;
self.pos += 1;
Some(b)
}
fn bytes(&mut self, n: usize) -> Option<&'a [u8]> {
let end = self.pos.checked_add(n)?;
let s = self.data.get(self.pos..end)?;
self.pos = end;
Some(s)
}
fn leb_u32(&mut self) -> Option<u32> {
let mut result: u32 = 0;
for shift in [0u32, 7, 14, 21, 28] {
let b = self.byte()?;
let low = u32::from(b & 0x7f);
if low.checked_shl(shift)? >> shift != low {
return None;
}
result |= low << shift;
if b & 0x80 == 0 {
return Some(result);
}
}
None }
fn leb_s64(&mut self) -> Option<i64> {
let mut result: i64 = 0;
let mut shift: u32 = 0;
loop {
let b = self.byte()?;
if shift >= 63 {
if b & 0x80 != 0 || (b != 0x00 && b != 0x7f) {
return None;
}
if b == 0x7f {
result |= -1i64 << shift;
}
return Some(result);
}
result |= i64::from(b & 0x7f) << shift;
shift += 7;
if b & 0x80 == 0 {
if shift < 64 && b & 0x40 != 0 {
result |= -1i64 << shift;
}
return Some(result);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn facts(payload: &[u8]) -> Vec<WscFact> {
parse_wsc_facts(payload).facts
}
fn leb_u32(mut v: u32, out: &mut Vec<u8>) {
loop {
let mut b = (v & 0x7f) as u8;
v >>= 7;
if v != 0 {
b |= 0x80;
}
out.push(b);
if v == 0 {
return;
}
}
}
fn leb_s64(mut v: i64, out: &mut Vec<u8>) {
loop {
let mut b = (v & 0x7f) as u8;
v >>= 7; let done = (v == 0 && b & 0x40 == 0) || (v == -1 && b & 0x40 != 0);
if !done {
b |= 0x80;
}
out.push(b);
if done {
return;
}
}
}
fn record(kind: u8, func_index: u32, value_id: u32, body: &[u8]) -> Vec<u8> {
let mut out = vec![kind];
leb_u32(func_index, &mut out);
leb_u32(value_id, &mut out);
leb_u32(body.len() as u32, &mut out);
out.extend_from_slice(body);
out
}
fn section(version: u8, records: &[Vec<u8>]) -> Vec<u8> {
let mut out = vec![version];
leb_u32(records.len() as u32, &mut out);
for r in records {
out.extend_from_slice(r);
}
out
}
fn value_range_body(lo: i64, hi: i64) -> Vec<u8> {
let mut b = Vec::new();
leb_s64(lo, &mut b);
leb_s64(hi, &mut b);
b
}
#[test]
fn parses_value_range_494() {
let s = section(1, &[record(0x01, 3, 7, &value_range_body(524, 1524))]);
assert_eq!(
facts(&s),
vec![WscFact {
func_index: 3,
value_id: 7,
kind: FactKind::ValueRange { lo: 524, hi: 1524 },
}]
);
}
#[test]
fn parses_negative_and_extreme_range_bounds_494() {
let s = section(
1,
&[record(0x01, 0, 0, &value_range_body(i64::MIN, i64::MAX))],
);
assert_eq!(
facts(&s),
vec![WscFact {
func_index: 0,
value_id: 0,
kind: FactKind::ValueRange {
lo: i64::MIN,
hi: i64::MAX,
},
}]
);
}
#[test]
fn parses_shift_bound_494() {
let mut body = Vec::new();
leb_u32(32, &mut body);
let s = section(1, &[record(0x02, 1, 4, &body)]);
assert_eq!(
facts(&s),
vec![WscFact {
func_index: 1,
value_id: 4,
kind: FactKind::ShiftBound { bound: 32 },
}]
);
}
#[test]
fn parses_divisor_nonzero_494() {
let s = section(1, &[record(0x03, 2, 9, &[])]);
assert_eq!(
facts(&s),
vec![WscFact {
func_index: 2,
value_id: 9,
kind: FactKind::DivisorNonZero,
}]
);
}
#[test]
fn parses_in_bounds_access_494() {
let mut body = Vec::new();
leb_u32(4, &mut body);
let s = section(1, &[record(0x04, 5, 11, &body)]);
assert_eq!(
facts(&s),
vec![WscFact {
func_index: 5,
value_id: 11,
kind: FactKind::InBoundsAccess { access_width: 4 },
}]
);
}
#[test]
fn parses_select_totality_494() {
let s = section(1, &[record(0x05, 6, 13, &[])]);
assert_eq!(
facts(&s),
vec![WscFact {
func_index: 6,
value_id: 13,
kind: FactKind::SelectTotality,
}]
);
}
#[test]
fn parses_memory_disjointness_494() {
let mut body = Vec::new();
leb_u32(21, &mut body);
let s = section(1, &[record(0x06, 7, 17, &body)]);
assert_eq!(
facts(&s),
vec![WscFact {
func_index: 7,
value_id: 17,
kind: FactKind::MemoryDisjointness { other_value_id: 21 },
}]
);
}
#[test]
fn parses_multiple_facts_in_order_494() {
let s = section(
1,
&[
record(0x01, 0, 1, &value_range_body(-8, 8)),
record(0x03, 0, 2, &[]),
],
);
let facts = facts(&s);
assert_eq!(facts.len(), 2);
assert_eq!(facts[0].kind, FactKind::ValueRange { lo: -8, hi: 8 });
assert_eq!(facts[1].kind, FactKind::DivisorNonZero);
}
#[test]
fn parses_empty_fact_list_494() {
let parsed = parse_wsc_facts(§ion(1, &[]));
assert_eq!(parsed, WscFactsParse::default());
}
#[test]
fn unknown_kind_is_skipped_others_kept_494() {
let s = section(
1,
&[
record(0x03, 0, 1, &[]),
record(0x2a, 0, 2, &[0xde, 0xad, 0xbe]),
record(0x05, 0, 3, &[]),
],
);
let parsed = parse_wsc_facts(&s);
assert_eq!(parsed.facts.len(), 2);
assert_eq!(parsed.facts[0].kind, FactKind::DivisorNonZero);
assert_eq!(parsed.facts[1].kind, FactKind::SelectTotality);
assert_eq!(parsed.records_skipped, 1);
assert_eq!(parsed.section_ignored, None);
}
#[test]
fn kind_zero_is_not_a_fact_494() {
let s = section(1, &[record(0x00, 0, 0, &[])]);
assert_eq!(facts(&s), vec![]);
}
#[test]
fn extended_body_on_known_kind_drops_that_record_only_494() {
let s = section(
1,
&[record(0x03, 0, 1, &[0x01, 0x02]), record(0x05, 0, 2, &[])],
);
let facts = facts(&s);
assert_eq!(facts.len(), 1);
assert_eq!(facts[0].kind, FactKind::SelectTotality);
}
#[test]
fn short_body_on_known_kind_drops_that_record_only_494() {
let mut body = Vec::new();
leb_s64(524, &mut body);
let s = section(1, &[record(0x01, 0, 1, &body), record(0x03, 0, 2, &[])]);
let facts = facts(&s);
assert_eq!(facts.len(), 1);
assert_eq!(facts[0].kind, FactKind::DivisorNonZero);
}
#[test]
fn version_mismatch_ignores_whole_section_494() {
for version in [0, 2, 0xff] {
let parsed = parse_wsc_facts(§ion(version, &[record(0x03, 0, 1, &[])]));
assert_eq!(parsed.facts, vec![]);
assert!(parsed.section_ignored.is_some());
}
}
#[test]
fn empty_and_truncated_sections_ignored_494() {
assert_eq!(facts(&[]), vec![]); assert_eq!(facts(&[0x01]), vec![]); assert_eq!(facts(&[0x01, 0x01]), vec![]);
assert_eq!(facts(&[0x01, 0x01, 0x01]), vec![]);
}
#[test]
fn body_len_overrun_ignores_whole_section_494() {
let good = record(0x03, 0, 1, &[]);
let mut bad = vec![0x01];
leb_u32(0, &mut bad);
leb_u32(0, &mut bad);
leb_u32(200, &mut bad);
bad.extend_from_slice(&[0xaa, 0xbb]);
let s = section(1, &[good, bad]);
assert_eq!(facts(&s), vec![]);
}
#[test]
fn trailing_bytes_after_records_ignore_whole_section_494() {
let mut s = section(1, &[record(0x03, 0, 1, &[])]);
s.push(0xff);
assert_eq!(facts(&s), vec![]);
}
#[test]
fn overlong_leb_ignores_whole_section_494() {
let mut s = vec![0x01, 0x01, 0x03];
s.extend_from_slice(&[0x80, 0x80, 0x80, 0x80, 0x80, 0x00]); s.push(0x00); s.push(0x00); assert_eq!(facts(&s), vec![]);
}
#[test]
fn garbage_is_ignored_never_panics_494() {
for garbage in [
&[0xff_u8, 0xff, 0xff, 0xff][..],
&[0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff][..],
&[0x00, 0x00, 0x00][..],
] {
assert_eq!(facts(garbage), vec![]);
}
}
}