#![cfg(kani)]
mod records_proofs {
use crate::storage::mmap::records::*;
#[kani::proof]
fn verify_struct_sizes_match_constants() {
assert_eq!(
std::mem::size_of::<FileHeader>(),
HEADER_SIZE,
"FileHeader size mismatch"
);
assert_eq!(
std::mem::size_of::<NodeRecord>(),
NODE_RECORD_SIZE,
"NodeRecord size mismatch"
);
assert_eq!(
std::mem::size_of::<EdgeRecord>(),
EDGE_RECORD_SIZE,
"EdgeRecord size mismatch"
);
assert_eq!(
std::mem::size_of::<PropertyEntry>(),
PROPERTY_ENTRY_HEADER_SIZE,
"PropertyEntry size mismatch"
);
assert_eq!(
std::mem::size_of::<StringEntry>(),
STRING_ENTRY_HEADER_SIZE,
"StringEntry size mismatch"
);
}
#[kani::proof]
fn verify_file_header_roundtrip() {
let node_count: u64 = kani::any();
let node_capacity: u64 = kani::any();
let edge_count: u64 = kani::any();
let edge_capacity: u64 = kani::any();
let string_table_offset: u64 = kani::any();
let string_table_end: u64 = kani::any();
let property_arena_offset: u64 = kani::any();
let arena_next_offset: u64 = kani::any();
let free_node_head: u64 = kani::any();
let free_edge_head: u64 = kani::any();
let next_node_id: u64 = kani::any();
let next_edge_id: u64 = kani::any();
let schema_offset: u64 = kani::any();
let schema_size: u64 = kani::any();
let schema_version: u32 = kani::any();
let mut header = FileHeader::new();
header.node_count = node_count;
header.node_capacity = node_capacity;
header.edge_count = edge_count;
header.edge_capacity = edge_capacity;
header.string_table_offset = string_table_offset;
header.string_table_end = string_table_end;
header.property_arena_offset = property_arena_offset;
header.arena_next_offset = arena_next_offset;
header.free_node_head = free_node_head;
header.free_edge_head = free_edge_head;
header.next_node_id = next_node_id;
header.next_edge_id = next_edge_id;
header.schema_offset = schema_offset;
header.schema_size = schema_size;
header.schema_version = schema_version;
let bytes = header.to_bytes();
let recovered = FileHeader::from_bytes(&bytes);
let rec_node_count = recovered.node_count;
let rec_node_capacity = recovered.node_capacity;
let rec_edge_count = recovered.edge_count;
let rec_edge_capacity = recovered.edge_capacity;
let rec_magic = recovered.magic;
let rec_version = recovered.version;
assert_eq!(rec_magic, MAGIC);
assert_eq!(rec_version, VERSION);
assert_eq!(rec_node_count, node_count);
assert_eq!(rec_node_capacity, node_capacity);
assert_eq!(rec_edge_count, edge_count);
assert_eq!(rec_edge_capacity, edge_capacity);
}
#[kani::proof]
fn verify_node_record_roundtrip() {
let id: u64 = kani::any();
let label_id: u32 = kani::any();
let flags: u32 = kani::any();
let first_out_edge: u64 = kani::any();
let first_in_edge: u64 = kani::any();
let prop_head: u64 = kani::any();
let mut record = NodeRecord::new(id, label_id);
record.flags = flags;
record.first_out_edge = first_out_edge;
record.first_in_edge = first_in_edge;
record.prop_head = prop_head;
let bytes = record.to_bytes();
let recovered = NodeRecord::from_bytes(&bytes);
let rec_id = recovered.id;
let rec_label_id = recovered.label_id;
let rec_flags = recovered.flags;
let rec_first_out_edge = recovered.first_out_edge;
let rec_first_in_edge = recovered.first_in_edge;
let rec_prop_head = recovered.prop_head;
assert_eq!(rec_id, id);
assert_eq!(rec_label_id, label_id);
assert_eq!(rec_flags, flags);
assert_eq!(rec_first_out_edge, first_out_edge);
assert_eq!(rec_first_in_edge, first_in_edge);
assert_eq!(rec_prop_head, prop_head);
}
#[kani::proof]
fn verify_edge_record_roundtrip() {
let id: u64 = kani::any();
let label_id: u32 = kani::any();
let flags: u32 = kani::any();
let src: u64 = kani::any();
let dst: u64 = kani::any();
let next_out: u64 = kani::any();
let next_in: u64 = kani::any();
let prop_head: u64 = kani::any();
let mut record = EdgeRecord::new(id, label_id, src, dst);
record.flags = flags;
record.next_out = next_out;
record.next_in = next_in;
record.prop_head = prop_head;
let bytes = record.to_bytes();
let recovered = EdgeRecord::from_bytes(&bytes);
let rec_id = recovered.id;
let rec_label_id = recovered.label_id;
let rec_flags = recovered.flags;
let rec_src = recovered.src;
let rec_dst = recovered.dst;
let rec_next_out = recovered.next_out;
let rec_next_in = recovered.next_in;
let rec_prop_head = recovered.prop_head;
assert_eq!(rec_id, id);
assert_eq!(rec_label_id, label_id);
assert_eq!(rec_flags, flags);
assert_eq!(rec_src, src);
assert_eq!(rec_dst, dst);
assert_eq!(rec_next_out, next_out);
assert_eq!(rec_next_in, next_in);
assert_eq!(rec_prop_head, prop_head);
}
#[kani::proof]
fn verify_property_entry_roundtrip() {
let key_id: u32 = kani::any();
let value_type: u8 = kani::any();
let value_len: u32 = kani::any();
let next: u64 = kani::any();
let entry = PropertyEntry::new(key_id, value_type, value_len, next);
let bytes = entry.to_bytes();
let recovered = PropertyEntry::from_bytes(&bytes);
let rec_key_id = recovered.key_id;
let rec_value_type = recovered.value_type;
let rec_value_len = recovered.value_len;
let rec_next = recovered.next;
assert_eq!(rec_key_id, key_id);
assert_eq!(rec_value_type, value_type);
assert_eq!(rec_value_len, value_len);
assert_eq!(rec_next, next);
}
#[kani::proof]
fn verify_string_entry_roundtrip() {
let id: u32 = kani::any();
let len: u32 = kani::any();
let entry = StringEntry::new(id, len);
let bytes = entry.to_bytes();
let recovered = StringEntry::from_bytes(&bytes);
let rec_id = recovered.id;
let rec_len = recovered.len;
assert_eq!(rec_id, id);
assert_eq!(rec_len, len);
}
}
mod value_proofs {
use crate::value::Value;
#[kani::proof]
fn verify_u64_to_value_documents_overflow() {
let value: u64 = kani::any();
let result = Value::from(value);
if let Value::Int(n) = result {
if value <= i64::MAX as u64 {
assert_eq!(n as u64, value, "small u64 should roundtrip");
} else {
assert!(n < 0, "large u64 wraps to negative");
}
} else {
panic!("From<u64> should produce Value::Int");
}
}
#[kani::proof]
fn verify_u64_to_value_safe_range() {
let value: u64 = kani::any();
kani::assume(value <= i64::MAX as u64);
let result = Value::from(value);
if let Value::Int(n) = result {
assert!(n >= 0, "value in safe range should be non-negative");
assert_eq!(n as u64, value, "value should roundtrip");
} else {
panic!("From<u64> should produce Value::Int");
}
}
#[kani::proof]
fn verify_i64_to_value() {
let value: i64 = kani::any();
let result = Value::from(value);
if let Value::Int(n) = result {
assert_eq!(n, value);
} else {
panic!("From<i64> should produce Value::Int");
}
}
#[kani::proof]
fn verify_u32_to_value() {
let value: u32 = kani::any();
let result = Value::from(value);
if let Value::Int(n) = result {
assert!(n >= 0);
assert_eq!(n as u32, value);
} else {
panic!("From<u32> should produce Value::Int");
}
}
#[kani::proof]
fn verify_i32_to_value() {
let value: i32 = kani::any();
let result = Value::from(value);
if let Value::Int(n) = result {
assert_eq!(n, value as i64);
} else {
panic!("From<i32> should produce Value::Int");
}
}
#[kani::proof]
fn verify_f64_to_value() {
let value: f64 = kani::any();
kani::assume(value.is_finite());
let result = Value::from(value);
if let Value::Float(f) = result {
assert_eq!(f, value);
} else {
panic!("From<f64> should produce Value::Float");
}
}
#[kani::proof]
fn verify_f32_to_value() {
let value: f32 = kani::any();
kani::assume(value.is_finite());
let result = Value::from(value);
if let Value::Float(f) = result {
assert_eq!(f, value as f64);
} else {
panic!("From<f32> should produce Value::Float");
}
}
#[kani::proof]
fn verify_bool_to_value() {
let value: bool = kani::any();
let result = Value::from(value);
match result {
Value::Bool(b) => assert_eq!(b, value),
_ => panic!("From<bool> should produce Value::Bool"),
}
}
}
mod serialization_proofs {
#[kani::proof]
fn verify_serialization_length_bounds_documented() {
let len: usize = kani::any();
kani::assume(len <= u32::MAX as usize);
let truncated = len as u32;
assert_eq!(truncated as usize, len, "length should not truncate");
}
}
mod offset_proofs {
use crate::storage::mmap::records::{EDGE_RECORD_SIZE, HEADER_SIZE, NODE_RECORD_SIZE};
const MAX_NODE_CAPACITY: u64 = 1_000_000_000;
const MAX_EDGE_CAPACITY: u64 = 10_000_000_000;
#[kani::proof]
fn verify_node_offset_no_overflow() {
let node_id: u64 = kani::any();
kani::assume(node_id < MAX_NODE_CAPACITY);
let offset = (HEADER_SIZE as u64)
.checked_add(node_id.checked_mul(NODE_RECORD_SIZE as u64).unwrap())
.unwrap();
assert!(offset >= HEADER_SIZE as u64);
assert!(offset < u64::MAX - NODE_RECORD_SIZE as u64);
}
#[kani::proof]
fn verify_edge_offset_no_overflow() {
let node_capacity: u64 = kani::any();
let edge_id: u64 = kani::any();
kani::assume(node_capacity <= MAX_NODE_CAPACITY);
kani::assume(edge_id < MAX_EDGE_CAPACITY);
let node_table_size = node_capacity.checked_mul(NODE_RECORD_SIZE as u64).unwrap();
let edge_table_start = (HEADER_SIZE as u64).checked_add(node_table_size).unwrap();
let edge_offset_in_table = edge_id.checked_mul(EDGE_RECORD_SIZE as u64).unwrap();
let offset = edge_table_start.checked_add(edge_offset_in_table).unwrap();
assert!(offset >= edge_table_start);
}
#[kani::proof]
fn verify_total_file_size_no_overflow() {
let node_capacity: u64 = kani::any();
let edge_capacity: u64 = kani::any();
let arena_size: u64 = kani::any();
let string_table_size: u64 = kani::any();
kani::assume(node_capacity <= MAX_NODE_CAPACITY);
kani::assume(edge_capacity <= MAX_EDGE_CAPACITY);
kani::assume(arena_size <= 100_000_000_000); kani::assume(string_table_size <= 10_000_000_000);
let total = (HEADER_SIZE as u64)
.checked_add(node_capacity.checked_mul(NODE_RECORD_SIZE as u64).unwrap())
.unwrap()
.checked_add(edge_capacity.checked_mul(EDGE_RECORD_SIZE as u64).unwrap())
.unwrap()
.checked_add(arena_size)
.unwrap()
.checked_add(string_table_size)
.unwrap();
assert!(total < 1_000_000_000_000_000_000u64);
}
}
mod freelist_proofs {
use crate::storage::mmap::freelist::FreeList;
#[kani::proof]
fn verify_freelist_empty_allocate() {
let mut list = FreeList::new();
let current_count: u64 = kani::any();
let allocated = list.allocate(current_count);
assert_eq!(allocated, current_count);
}
#[kani::proof]
fn verify_freelist_with_head() {
let head: u64 = kani::any();
let list = FreeList::with_head(head);
if head == u64::MAX {
assert!(list.is_empty());
} else {
assert!(!list.is_empty());
assert_eq!(list.head(), head);
}
}
}
mod arena_proofs {
use crate::storage::mmap::arena::ArenaAllocator;
#[kani::proof]
fn verify_arena_allocation_bounded() {
let start: u64 = kani::any();
let end: u64 = kani::any();
let current: u64 = kani::any();
kani::assume(start <= 10000);
kani::assume(end <= 20000);
kani::assume(start <= current);
kani::assume(current <= end);
let allocator = ArenaAllocator::new(start, end, current);
let size: usize = kani::any();
kani::assume(size <= 100); kani::assume(size > 0);
if let Ok(offset) = allocator.allocate(size) {
assert!(offset >= start);
assert!(offset + size as u64 <= end);
}
}
#[kani::proof]
fn verify_arena_full_returns_error() {
let start: u64 = 0;
let end: u64 = 100;
let current: u64 = 100;
let allocator = ArenaAllocator::new(start, end, current);
let size: usize = kani::any();
kani::assume(size > 0);
kani::assume(size <= 1000);
assert!(allocator.allocate(size).is_err());
}
#[kani::proof]
fn verify_arena_has_space_consistency() {
let start: u64 = kani::any();
let end: u64 = kani::any();
let current: u64 = kani::any();
kani::assume(start <= 1000);
kani::assume(end <= 2000);
kani::assume(start <= current);
kani::assume(current <= end);
let allocator = ArenaAllocator::new(start, end, current);
let size: usize = kani::any();
kani::assume(size <= 100);
kani::assume(size > 0);
let has_space = allocator.has_space(size);
if has_space {
assert!(allocator.allocate(size).is_ok());
}
}
#[kani::proof]
fn verify_arena_entry_size() {
use crate::storage::mmap::records::PROPERTY_ENTRY_HEADER_SIZE;
let value_len: usize = kani::any();
kani::assume(value_len <= 1_000_000);
let entry_size = ArenaAllocator::entry_size(value_len);
assert_eq!(entry_size, PROPERTY_ENTRY_HEADER_SIZE + value_len);
}
}