#![doc(html_root_url = "https://docs.rs/rucc-safety/0.10.19")]
pub mod boundary;
pub mod lower;
pub mod summary;
pub mod wrap;
pub use boundary::{Sites, WITNESS, witness};
pub use lower::{Descriptor, SECTION, lower};
pub use summary::{Frames, Summary, summarize};
pub use wrap::{INTERPOSED, PREFIX, redirect};
use rucc_ir::{Def, Extra, Func, Imm, Inst, InstData, Module, Opcode, Type, Value};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Counts {
pub checked: usize,
pub live: usize,
pub derived: usize,
pub skipped: usize,
}
impl Counts {
fn add(&mut self, other: Counts) {
self.checked += other.checked;
self.live += other.live;
self.derived += other.derived;
self.skipped += other.skipped;
}
}
pub fn run(module: &mut Module) -> Counts {
let mut counts = Counts::default();
for id in module.funcs() {
if !module[id].is_declaration() {
counts.add(insert(&mut module[id]));
}
}
counts
}
pub fn insert(func: &mut Func) -> Counts {
let mut counts = Counts::default();
let insts: Vec<Inst> =
func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
for inst in insts {
match func[inst].opcode {
Opcode::Load | Opcode::Store => match pointer_of(func, inst) {
Some(pointer) => {
check(func, inst, pointer);
counts.checked += 1;
counts.live += 1;
}
None => counts.skipped += 1,
},
Opcode::PtrAdd => {
if derivation(func, inst) {
counts.derived += 1;
} else {
counts.skipped += 1;
}
}
_ => {}
}
}
counts
}
fn pointer_of(func: &Func, access: Inst) -> Option<Value> {
let args = &func[func[access].args];
let at = match func[access].opcode {
Opcode::Load => 0,
Opcode::Store => 1,
_ => return None,
};
let &value = args.get(at)?;
func[value].ty.is_ptr().then_some(value)
}
fn check(func: &mut Func, access: Inst, pointer: Value) {
let span = func.span(access);
let Extra::Mem(info) = func[access].extra else { return };
let mut info = func[info];
info.size = covered(func, access, info.size);
let capability = cap_of(func, pointer, access);
let args = func.push_values(&[capability, pointer]);
let extra = Extra::Mem(func.add_mem(info));
let bounds =
func.create_inst(InstData { args, extra, ..InstData::new(Opcode::CheckBounds) }, &[], span);
func.insert_before(bounds, access);
let args = func.push_values(&[capability, pointer]);
let live = func.create_inst(InstData { args, ..InstData::new(Opcode::CheckLive) }, &[], span);
func.insert_before(live, access);
}
fn covered(func: &Func, access: Inst, stated: u64) -> u64 {
if stated != 0 {
return stated;
}
let ty = match func[access].opcode {
Opcode::Load => func[access].results().next().map(|value| func[value].ty),
Opcode::Store => func[func[access].args].first().map(|&value| func[value].ty),
_ => None,
};
ty.map_or(0, |ty| u64::from(ty.bits().div_ceil(8)) * u64::from(ty.lanes()))
}
fn derivation(func: &mut Func, add: Inst) -> bool {
let Some(&base) = func[func[add].args].first() else { return false };
if !func[base].ty.is_ptr() {
return false;
}
let Some(derived) = func[add].results().next() else { return false };
let span = func.span(add);
let width = stride(func, add);
let capability = cap_of(func, base, add);
let args = func.push_values(&[capability, base, derived, width]);
let check = func.create_inst(InstData { args, ..InstData::new(Opcode::CheckDeriv) }, &[], span);
func.insert_after(check, add);
true
}
fn stride(func: &mut Func, add: Inst) -> Value {
let Some(&offset) = func[func[add].args].get(1) else { return one(func, add, Type::int(64)) };
let word = func[offset].ty;
let forwards = match operand_of(func, offset, Opcode::Sub, 0) {
Some(zero) if is_zero(func, zero) => operand_of(func, offset, Opcode::Sub, 1),
_ => None,
};
let scaled = forwards.unwrap_or(offset);
match operand_of(func, scaled, Opcode::Mul, 1) {
Some(width) if func[width].ty == word => width,
_ => one(func, add, word),
}
}
fn operand_of(func: &Func, value: Value, opcode: Opcode, index: usize) -> Option<Value> {
let Def::Result { inst, .. } = func[value].def else { return None };
if func[inst].opcode != opcode {
return None;
}
func[func[inst].args].get(index).copied()
}
fn is_zero(func: &Func, value: Value) -> bool {
let Def::Result { inst, .. } = func[value].def else { return false };
match func[inst].extra {
Extra::Imm(imm) if func[inst].opcode == Opcode::IConst => func[imm].bits() == 0,
_ => false,
}
}
fn one(func: &mut Func, at: Inst, ty: Type) -> Value {
let span = func.span(at);
let extra = Extra::Imm(func.add_imm(Imm::int(1, ty)));
let made = func.create_inst(InstData { extra, ..InstData::new(Opcode::IConst) }, &[ty], span);
func.insert_before(made, at);
func[made].results().next().expect("a constant created with one result has one")
}
fn cap_of(func: &mut Func, pointer: Value, at: Inst) -> Value {
let span = func.span(at);
let args = func.push_values(&[pointer]);
let cap =
func.create_inst(InstData { args, ..InstData::new(Opcode::CapOf) }, &[Type::CAP], span);
func.insert_before(cap, at);
func[cap].results().next().expect("cap_of produces one value")
}
#[cfg(test)]
mod tests {
use rucc_base::Interner;
use rucc_ir::{
Builder, Flags, MemInfo, MemOrder, Restrict, Signature, print_func, verify_func,
};
use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
use super::*;
fn target() -> TargetInfo {
TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
}
fn one_of_each(names: &mut Interner) -> Func {
let i32_ = Type::int(32);
let mut func = Func::new(
names.intern("both"),
Signature::new().with_params(&[Type::PTR]).with_returns(&[i32_]),
);
let entry = func.create_block();
let p = func.append_param(entry, Type::PTR);
let info = MemInfo {
size: 4,
align: 4,
order: MemOrder::NotAtomic,
tbaa: None,
restrict: Restrict::NONE,
};
let mut b = Builder::new(&mut func, entry);
let args = b.func().push_values(&[p]);
let extra = Extra::Mem(b.func().add_mem(info));
let loaded = b.value(InstData { args, extra, ..InstData::new(Opcode::Load) }, i32_);
let args = b.func().push_values(&[loaded, p]);
let extra = Extra::Mem(b.func().add_mem(info));
b.inst(InstData { args, extra, ..InstData::new(Opcode::Store) }, &[]);
b.ret(&[loaded]);
func
}
#[test]
fn every_access_gets_a_bounds_check_and_a_lifetime_check() {
let mut names = Interner::new();
let mut func = one_of_each(&mut names);
assert_eq!(insert(&mut func), Counts { checked: 2, live: 2, derived: 0, skipped: 0 });
let module = Module::new(names.intern("both.c"), &target());
assert_eq!(
print_func(&module, &func, &names),
"func @both(ptr) -> i32, linkage(external) {\n\
block0(%0: ptr):\n \
%1 = cap_of %0\n \
check_bounds %1, %0, size 4, align 4\n \
check_live %1, %0\n \
%2 = load.i32 %0, size 4, align 4\n \
%3 = cap_of %0\n \
check_bounds %3, %0, size 4, align 4\n \
check_live %3, %0\n \
store %2 -> %0, size 4, align 4\n \
return %2\n\
}\n"
);
}
#[test]
fn a_walk_over_elements_hands_the_check_the_width_of_one() {
let mut names = Interner::new();
let mut func = Func::new(
names.intern("walk"),
Signature::new().with_params(&[Type::PTR, Type::int(64)]).with_returns(&[Type::PTR]),
);
let entry = func.create_block();
let p = func.append_param(entry, Type::PTR);
let n = func.append_param(entry, Type::int(64));
let mut b = Builder::new(&mut func, entry);
let width = b.iconst(Type::int(64), 24);
let bytes = b.binary(Opcode::Mul, n, width, Flags::NSW);
let args = b.func().push_values(&[p, bytes]);
let moved = b.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
b.ret(&[moved]);
insert(&mut func);
let module = Module::new(names.intern("walk.c"), &target());
assert_eq!(
print_func(&module, &func, &names),
"func @walk(ptr, i64) -> ptr, linkage(external) {\n\
block0(%0: ptr, %1: i64):\n \
%2 = iconst.i64 24\n \
%3 = mul.nsw %1, %2\n \
%4 = cap_of %0\n \
%5 = ptr_add %0, %3\n \
check_deriv %4, %0, %5, %2\n \
return %5\n\
}\n"
);
}
#[test]
fn a_walk_that_goes_backwards_is_still_a_walk_over_elements() {
let mut names = Interner::new();
let mut func = Func::new(
names.intern("back"),
Signature::new().with_params(&[Type::PTR, Type::int(64)]).with_returns(&[Type::PTR]),
);
let entry = func.create_block();
let p = func.append_param(entry, Type::PTR);
let n = func.append_param(entry, Type::int(64));
let mut b = Builder::new(&mut func, entry);
let width = b.iconst(Type::int(64), 24);
let bytes = b.binary(Opcode::Mul, n, width, Flags::NSW);
let zero = b.iconst(Type::int(64), 0);
let back = b.binary(Opcode::Sub, zero, bytes, Flags::NONE);
let args = b.func().push_values(&[p, back]);
let moved = b.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
b.ret(&[moved]);
insert(&mut func);
let printed = print_func(&Module::new(names.intern("back.c"), &target()), &func, &names);
assert!(printed.contains("check_deriv %6, %0, %7, %2\n"), "{printed}");
}
#[test]
fn a_pointer_computed_from_another_pointer_is_checked_where_it_is_computed() {
let mut names = Interner::new();
let mut func = Func::new(
names.intern("walk"),
Signature::new().with_params(&[Type::PTR, Type::int(64)]).with_returns(&[Type::PTR]),
);
let entry = func.create_block();
let p = func.append_param(entry, Type::PTR);
let n = func.append_param(entry, Type::int(64));
let mut b = Builder::new(&mut func, entry);
let args = b.func().push_values(&[p, n]);
let moved = b.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
b.ret(&[moved]);
assert_eq!(insert(&mut func), Counts { checked: 0, live: 0, derived: 1, skipped: 0 });
let module = Module::new(names.intern("walk.c"), &target());
assert_eq!(
print_func(&module, &func, &names),
"func @walk(ptr, i64) -> ptr, linkage(external) {\n\
block0(%0: ptr, %1: i64):\n \
%2 = iconst.i64 1\n \
%3 = cap_of %0\n \
%4 = ptr_add %0, %1\n \
check_deriv %3, %0, %4, %2\n \
return %4\n\
}\n"
);
if let Err(errors) = verify_func(&module, &func, &names) {
panic!("that was expected to be believed: {errors:#?}");
}
}
#[test]
fn what_it_produces_is_a_function_the_verifier_believes() {
let mut names = Interner::new();
let mut func = one_of_each(&mut names);
insert(&mut func);
let module = Module::new(names.intern("both.c"), &target());
if let Err(errors) = verify_func(&module, &func, &names) {
panic!("that was expected to be believed: {errors:#?}");
}
}
#[test]
fn every_definition_in_a_module_is_walked_and_the_declarations_are_not() {
let mut names = Interner::new();
let one = one_of_each(&mut names);
let mut two = one_of_each(&mut names);
two.name = names.intern("other");
let declared = Func::new(
names.intern("elsewhere"),
Signature::new().with_params(&[Type::PTR]).with_returns(&[Type::int(32)]),
);
let mut module = Module::new(names.intern("two.c"), &target());
module.add_func(one);
module.add_func(two);
module.add_func(declared);
assert_eq!(run(&mut module), Counts { checked: 4, live: 4, derived: 0, skipped: 0 });
if let Err(errors) = rucc_ir::verify(&module, &names) {
panic!("that was expected to be believed: {errors:#?}");
}
}
#[test]
fn a_function_with_no_accesses_is_left_alone() {
let mut names = Interner::new();
let i32_ = Type::int(32);
let mut func = Func::new(names.intern("nothing"), Signature::new().with_returns(&[i32_]));
let entry = func.create_block();
let mut b = Builder::new(&mut func, entry);
let zero = b.iconst(i32_, 0);
b.ret(&[zero]);
let before = func.counts();
assert_eq!(insert(&mut func), Counts::default());
assert_eq!(func.counts(), before);
}
}