diff --git a/enforcing-invariants/Cargo.toml b/enforcing-invariants/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..fb3dd3c6f71a70ef3b7eabcb4be75e585267b2a3 --- /dev/null +++ b/enforcing-invariants/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "enforcing-invariants" +version = "0.1.0" +edition = "2024" + +[dependencies] +rand = "0.9.0" \ No newline at end of file diff --git a/enforcing-invariants/src/main.rs b/enforcing-invariants/src/main.rs new file mode 100644 index 0000000000000000000000000000000000000000..6e1d908fd5794f8357df2997aa9219bb0693930b --- /dev/null +++ b/enforcing-invariants/src/main.rs @@ -0,0 +1,239 @@ +use std::{ptr, mem, collections::HashSet}; + +use rand::{rngs::ThreadRng, Rng}; + +/// A list of possible holders for an item. +/// +/// # Invariants +/// +/// The list of possible holders does not contain duplicates. +pub struct PossibleHolders { + vec: Vec<usize> +} +impl PossibleHolders { + /// Returns a [`PossibleHolders`] containing the same items as `vec`. + /// + /// If `vec` does not contain duplicates, a [`Some`] is returned + /// containing the corresponding [`PossibleHolders`]; + /// otherwise, [`None`] is returned. + pub fn from_vec(vec: Vec<usize>) -> Option<PossibleHolders> { + let mut set = HashSet::new(); + for item in vec.iter() { + if !set.insert(item) { + return None + } + } + Some(PossibleHolders { vec: vec }) + } + + /// Returns a reference to the list of possible holders. + pub fn holders(&self) -> &Vec<usize> { + &self.vec + } + + /// Swaps the first item in the list with the item with index `idx`. + /// + /// # Safety + /// + /// Calling this function when `idx` is `0usize` or when `idx` is not + /// an index of the list is undefined behavior. + pub unsafe fn swap_first_unchecked(&mut self, idx: usize) -> usize { + // SAFETY: 0 < idx < self.vec.len() so fst and snd are non-overlapping. + // Furthermore, self is valid for writes until return, + // and self is not accessed again. + let (fst, snd) = unsafe { + let fst = ptr::from_mut(self.vec.get_unchecked_mut(0)); + let snd = ptr::from_mut(self.vec.get_unchecked_mut(idx)); + (&mut *fst, &mut *snd) + }; + mem::swap(fst, snd); + *fst + } +} + +/// A [`String`] value together with a list of possible holders. +/// +/// # Invariants +/// +/// The list of possible holders satisfies the invariants of [`PossibleHolders`], +/// and is guaranteed to have length at least 2. +pub struct ItemWithHolders { + value: String, + holders: PossibleHolders +} +impl ItemWithHolders { + /// Returns a [`ItemWithHolders`] with possible holders `holders` + /// and value `value`. + /// + /// If `holders` is of length at least 2, a [`Some`] is returned + /// containing the corresponding [`ItemWithHolders`]; + /// otherwise, [`None`] is returned. + pub fn new(value: String, holders: PossibleHolders) -> Option<ItemWithHolders> { + if holders.holders().len() >= 2 { + Some(ItemWithHolders { + value: value, + holders: holders + }) + } else { + None + } + } + + /// Returns the first possible holder in the list of `self`. + pub fn get_first(&self) -> usize { + // SAFETY: the list of possible holders for `self` is of length + // at least 2. + unsafe { *self.holders.holders().get_unchecked(0) } + } + + /// Swaps the first possible holder in `self` by a randomly selected + /// possible holder among the rest in its list. + pub fn change_first(&mut self, rng: &mut ThreadRng) -> usize { + let idx = rng.random_range(1 .. self.holders.holders().len()); + // SAFETY: 0 < 1 <= idx < self.holders.holders().len() + unsafe { self.holders.swap_first_unchecked(idx) } + } + + /// Returns a reference to the value in `self`. + pub fn value(&self) -> &String { + &self.value + } + + /// Returns a reference to the holders list in `self`. + pub fn holders(&self) -> &PossibleHolders { + &self.holders + } + + /// Returns the maximum holder in the list of `self`. + pub fn max_holder(&self) -> usize { + let maxopt = self.holders.holders().iter().max(); + // SAFETY: the list of possible holders for `self` is nonempty. + unsafe { *maxopt.unwrap_unchecked() } + } +} + +/// A [`ItemWithHolders`] where the first item is understood as its current +/// holder. +pub struct HeldItem { + item: ItemWithHolders +} +impl HeldItem { + /// Returns a pair `(held, holder)`, where `held` is `item` as a [`HeldItem`] + /// and `holder` is the first holder in its holders list. + pub fn new(item: ItemWithHolders) -> (HeldItem, usize) { + (HeldItem { item: item }, 0usize) + } + + /// Returns a reference to the value in `self`. + pub fn get_value(&self) -> &String { + self.item.value() + } + + /// Returns a reference to the holders list in `self`. + pub fn get_possible_holders(&self) -> &Vec<usize> { + self.item.holders().holders() + } + + /// Returns the holder that currently holds `self`. + pub fn get_holder(&self) -> usize { + self.item.get_first() + } + + /// Chooses a different holder for `self` among those in its holders list. + pub fn choose_another_holder(&mut self, rng: &mut ThreadRng) -> usize { + self.item.change_first(rng) + } +} + +/// A list of holders, each holding a list of items. +pub struct HolderFamily { + vec: Vec<Vec<HeldItem>> +} +impl HolderFamily { + /// Creates a new empty list of holders. + pub fn new() -> HolderFamily { + HolderFamily { + vec: Vec::new() + } + } + + /// Returns a reference to the list of holders. + pub fn lists(&self) -> &Vec<Vec<HeldItem>> { + &self.vec + } + + /// Adds an [`ItemWithHolders`] `item` to `self` in the list corresponding + /// to the current holder of `item`. + /// + /// If the underlying list of holders does not contain the current holder, + /// it is extended with new holders to fit it. All other new holders hold nothing. + pub fn add_item(&mut self, item: ItemWithHolders) { + let needed = item.max_holder().checked_add(1usize) + .expect("capacity exceeded"); + if needed >= self.vec.len() { + self.vec.resize_with(needed, || Vec::new()) + } + let (held, holder) = HeldItem::new(item); + // SAFETY: above we make sure this is within bounds. + let list = unsafe { self.vec.get_unchecked_mut(holder) }; + list.push(held) + } + + /// Redistributes every item in `holder`'s list in `self` to a randomly selected + /// new holder. The holder is selected among the rest of each item's possible holders. + pub fn redistribute_items(&mut self, holder: usize, rng: &mut ThreadRng) { + if holder < self.vec.len() { + let lists = self.vec.as_mut_ptr(); + // SAFETY: lists + holder is within the same allocation, + // and ptr is valid for writes. + let holder_list = unsafe { + let ptr = lists.add(holder); + &mut *ptr + }; + for mut item in holder_list.drain(..) { + let dest = item.choose_another_holder(rng); + // SAFETY: lists + dest is within the same allocation. + // Moreover, `dest` is guaranteed to be distinct from `holder`, + // so dest_list is valid for writes. + let dest_list = unsafe { &mut *lists.add(dest) }; + dest_list.push(item) + } + } + } +} + + +fn main() { + let mut rng = rand::rng(); + let mut family = HolderFamily::new(); + + let mut count = 0; + while count < 50 { + let opt_holders = { + let mut vec = Vec::new(); + for _ in 0 .. 4 { + vec.push(rng.random_range(0 .. 16)); + } + PossibleHolders::from_vec(vec) + }; + if let Some(holders) = opt_holders { + let value = format!("Item #{}", count); + let item = ItemWithHolders::new(value, holders).unwrap(); + family.add_item(item) + } + count += 1; + } + for _ in 0 .. 1000 { + let holder = rng.random_range(0 .. 16); + family.redistribute_items(holder, &mut rng); + } + for (n, list) in family.lists().iter().enumerate() { + println!("Holder #{}", n); + for item in list { + assert!(item.get_holder() == n); + println!("\t{}, holders: {:?}", + item.get_value(), + item.get_possible_holders()) + } + } +} diff --git a/unsafe-rust/presentation.tex b/unsafe-rust/presentation.tex index 0e7f8dabb881ddd3e4cdc5aaed34712233ecab72..bfc39c02f6e7d8157a5bca2b430df4b7c143d3e7 100644 --- a/unsafe-rust/presentation.tex +++ b/unsafe-rust/presentation.tex @@ -21,6 +21,7 @@ Safe Rust guarantees{${}^{\text{[\emph{citation needed}]}}$} memory safety: \item Calls respect the corresponding ABI \end{itemize} +\pause Safe Rust does not guarantee (in principle): \begin{itemize} \item General race conditions (deadlocks, out-of-spec synchronization) @@ -29,6 +30,7 @@ Safe Rust does not guarantee (in principle): \item Panics and aborts \end{itemize} +\pause The safe/unsafe boundary can be weaponized though! \end{frame} @@ -84,7 +86,10 @@ Raw pointers need not respect the borrow rules... \item \emph{The Jedi version:} Using unsafe even once means you are foregoing all safety guarantees \item \emph{The Sith version:} If you use unsafe, you may as well use it everywhere \end{itemize} - + \pause \emph{Reality (the Andor version)}: Safe does not mean ``memory-safe'', it means ``unsafety correctly encapsulated by the API''. + + \pause + \emph{The Rael version}: Same as Andor, but also ``memory safety can be strengthened to whatever you feel like''. \end{frame} \end{document} \ No newline at end of file