use std::collections::{HashMap, HashSet};
use rucc_base::{Interner, Symbol};
use rucc_ir::{Block, Def, Extra, Flags, Func, FuncId, Inst, Linkage, Module, Opcode, Pic, Value};
use crate::cfg::Cfg;
use crate::copy;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Params {
bits: u64,
rest: bool,
}
impl Params {
pub const NONE: Self = Self { bits: 0, rest: false };
pub const ALL: Self = Self { bits: u64::MAX, rest: true };
#[must_use]
pub fn contains(self, at: usize) -> bool {
match u32::try_from(at) {
Ok(at) if at < u64::BITS => self.bits & (1 << at) != 0,
_ => self.rest,
}
}
#[must_use]
pub fn is_empty(self) -> bool {
self.bits == 0 && !self.rest
}
#[must_use]
fn with(self, at: usize) -> Self {
match u32::try_from(at) {
Ok(at) if at < u64::BITS => Self { bits: self.bits | (1 << at), rest: self.rest },
_ => Self { bits: self.bits, rest: true },
}
}
#[must_use]
fn union(self, other: Self) -> Self {
Self { bits: self.bits | other.bits, rest: self.rest || other.rest }
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Reach {
pub freed: Params,
pub kept: Params,
pub frees_other: bool,
}
impl Reach {
pub const NOTHING: Self = Self { freed: Params::NONE, kept: Params::NONE, frees_other: false };
pub const UNKNOWN: Self = Self { freed: Params::ALL, kept: Params::ALL, frees_other: true };
#[must_use]
pub fn frees_nothing(self) -> bool {
self.freed.is_empty() && !self.frees_other
}
#[must_use]
fn union(self, other: Self) -> Self {
Self {
freed: self.freed.union(other.freed),
kept: self.kept.union(other.kept),
frees_other: self.frees_other || other.frees_other,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct Summaries {
nofree: HashSet<Symbol>,
reach: HashMap<Symbol, Reach>,
}
impl Summaries {
#[must_use]
pub fn nothing() -> Self {
Self::default()
}
#[must_use]
pub fn of_module(module: &Module, names: &Interner, pic: Pic) -> Self {
let ids: Vec<FuncId> = module.funcs().collect();
let mut reach: HashMap<Symbol, Reach> = HashMap::new();
for &id in &ids {
let func = &module[id];
if func.is_declaration() {
if let Some(known) = table(names.resolve(func.name)) {
reach.insert(func.name, known);
}
} else if trusted(func, pic) {
reach.insert(func.name, Reach::NOTHING);
} else {
reach.insert(func.name, Reach::UNKNOWN);
}
}
let present: HashSet<Symbol> = ids.iter().map(|&id| module[id].name).collect();
for &id in &ids {
let func = &module[id];
for block in func.blocks() {
for inst in func.insts(block) {
let Some(callee) = called(func, inst) else { continue };
if present.contains(&callee) || reach.contains_key(&callee) {
continue;
}
if let Some(known) = table(names.resolve(callee)) {
reach.insert(callee, known);
}
}
}
}
let bodies: Vec<(FuncId, HashMap<Value, Params>)> = ids
.iter()
.copied()
.filter(|&id| !module[id].is_declaration() && trusted(&module[id], pic))
.map(|id| (id, derived(&module[id], &Cfg::new(&module[id]))))
.collect();
loop {
let mut settled = true;
for (id, from) in &bodies {
let func = &module[*id];
let was = reach.get(&func.name).copied().unwrap_or(Reach::UNKNOWN);
let now = was.union(reaches(func, from, &reach));
if now != was {
reach.insert(func.name, now);
settled = false;
}
}
if settled {
break;
}
}
let nofree =
reach.iter().filter(|(_, what)| what.frees_nothing()).map(|(&name, _)| name).collect();
Self { nofree, reach }
}
#[must_use]
pub fn cannot_free(&self, name: Symbol) -> bool {
self.nofree.contains(&name)
}
#[must_use]
pub fn reach(&self, name: Symbol) -> Reach {
self.reach.get(&name).copied().unwrap_or(Reach::UNKNOWN)
}
#[must_use]
pub fn len(&self) -> usize {
self.nofree.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.nofree.is_empty()
}
}
pub fn annotate(module: &mut Module, names: &Interner, pic: Pic) -> usize {
let summaries = Summaries::of_module(module, names, pic);
let mut marked = 0;
let ids: Vec<FuncId> = module.funcs().collect();
for id in ids {
if module[id].is_declaration() {
continue;
}
let func = &mut module[id];
let insts: Vec<Inst> =
func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
for inst in insts {
if !matches!(func[inst].opcode, Opcode::Call | Opcode::TailCall) {
continue;
}
let Extra::Call(at) = func[inst].extra else { continue };
let Some(callee) = func[at].callee else { continue };
if func[inst].flags.contains(Flags::NOFREE)
|| !ends_nothing(func, inst, summaries.reach(callee))
{
continue;
}
func[inst].flags |= Flags::NOFREE;
marked += 1;
}
}
marked
}
fn ends_nothing(func: &Func, inst: Inst, reach: Reach) -> bool {
if reach.frees_other {
return false;
}
let args = &func[func[inst].args];
!args.iter().enumerate().any(|(at, &arg)| reach.freed.contains(at) && func[arg].ty.is_ptr())
}
fn called(func: &Func, inst: Inst) -> Option<Symbol> {
if !matches!(func[inst].opcode, Opcode::Call | Opcode::TailCall) {
return None;
}
let Extra::Call(at) = func[inst].extra else { return None };
func[at].callee
}
fn trusted(func: &Func, pic: Pic) -> bool {
!matches!(func.linkage, Linkage::Weak | Linkage::Common)
&& !pic.replaceable(func.linkage, func.visibility)
}
fn derived(func: &Func, cfg: &Cfg) -> HashMap<Value, Params> {
let mut from: HashMap<Value, Params> = HashMap::new();
let Some(entry) = func.entry() else { return from };
loop {
let mut settled = true;
for value in func.values() {
let was = from.get(&value).copied().unwrap_or(Params::NONE);
let now = was.union(source(func, cfg, &from, entry, value));
if now != was {
from.insert(value, now);
settled = false;
}
}
if settled {
return from;
}
}
}
fn source(
func: &Func,
cfg: &Cfg,
from: &HashMap<Value, Params>,
entry: Block,
value: Value,
) -> Params {
let known = |of: Value| from.get(&of).copied().unwrap_or(Params::NONE);
match func[value].def {
Def::Param { block, index } if block == entry => Params::NONE.with(index as usize),
Def::Param { block, index } => {
let mut out = Params::NONE;
for &pred in cfg.predecessors(block) {
let Some(term) = func.terminator(pred) else { continue };
if let Some(&came) = copy::edge_args(func, term, block).get(index as usize) {
out = out.union(known(came));
}
}
out
}
Def::Result { inst, .. } => {
let args = &func[func[inst].args];
match func[inst].opcode {
Opcode::PtrAdd | Opcode::PtrToInt | Opcode::IntToPtr | Opcode::Bitcast => {
args.first().map_or(Params::NONE, |&of| known(of))
}
Opcode::Select => match (args.get(1), args.get(2)) {
(Some(&one), Some(&two)) => known(one).union(known(two)),
_ => Params::NONE,
},
Opcode::Add
| Opcode::Sub
| Opcode::Mul
| Opcode::Shl
| Opcode::LShr
| Opcode::AShr
| Opcode::And
| Opcode::Or
| Opcode::Xor
| Opcode::Trunc
| Opcode::SExt
| Opcode::ZExt => args.iter().fold(Params::NONE, |out, &of| out.union(known(of))),
_ => Params::NONE,
}
}
}
}
fn reaches(func: &Func, from: &HashMap<Value, Params>, at: &HashMap<Symbol, Reach>) -> Reach {
let known = |of: Value| from.get(&of).copied().unwrap_or(Params::NONE);
let ended = |what: Params, out: &mut Reach| {
if what.is_empty() {
out.frees_other = true;
} else {
out.freed = out.freed.union(what);
}
};
let mut out = Reach::NOTHING;
for block in func.blocks() {
for inst in func.insts(block) {
let args = &func[func[inst].args];
match func[inst].opcode {
Opcode::MetaEnd | Opcode::MetaTransfer => {
ended(args.first().map_or(Params::NONE, |&of| known(of)), &mut out);
}
Opcode::Call | Opcode::TailCall => {
let reach = called(func, inst).map_or(Reach::UNKNOWN, |name| {
at.get(&name).copied().unwrap_or(Reach::UNKNOWN)
});
out.frees_other |= reach.frees_other;
for (place, &arg) in args.iter().enumerate() {
if reach.freed.contains(place) && func[arg].ty.is_ptr() {
ended(known(arg), &mut out);
}
if reach.kept.contains(place) {
out.kept = out.kept.union(known(arg));
}
}
}
Opcode::CallIndirect | Opcode::InlineAsm | Opcode::TargetIntrinsic => {
out.frees_other = true;
for &arg in args {
out.kept = out.kept.union(known(arg));
}
}
Opcode::Store => {
out.kept = out.kept.union(args.first().map_or(Params::NONE, |&of| known(of)));
}
Opcode::Return => {
for &arg in args {
out.kept = out.kept.union(known(arg));
}
}
opcode if holds(opcode) => {}
_ => {
for &arg in args {
out.kept = out.kept.union(known(arg));
}
}
}
}
}
out
}
fn holds(opcode: Opcode) -> bool {
matches!(
opcode,
Opcode::Load
| Opcode::PtrAdd
| Opcode::PtrToInt
| Opcode::IntToPtr
| Opcode::Bitcast
| Opcode::Select
| Opcode::ICmp
| Opcode::Add
| Opcode::Sub
| Opcode::Mul
| Opcode::Shl
| Opcode::LShr
| Opcode::AShr
| Opcode::And
| Opcode::Or
| Opcode::Xor
| Opcode::Trunc
| Opcode::SExt
| Opcode::ZExt
| Opcode::Memcpy
| Opcode::Memmove
| Opcode::Memset
| Opcode::Jump
| Opcode::BrIf
| Opcode::Switch
| Opcode::CapOf
| Opcode::CheckBounds
| Opcode::CheckLive
| Opcode::CheckType
| Opcode::CheckInit
| Opcode::CheckDeriv
| Opcode::CheckRace
| Opcode::CheckRestrictRead
| Opcode::CheckRestrictWrite
)
}
fn table(name: &str) -> Option<Reach> {
if never_frees(name) {
return Some(Reach::NOTHING);
}
let bare = name.strip_prefix(WRAPPER_PREFIX).unwrap_or(name);
let bare = bare.strip_prefix("__builtin_").unwrap_or(bare);
let at = FREES_ITS_ARGUMENT.binary_search_by_key(&bare, |&(name, _)| name).ok()?;
let (_, place) = FREES_ITS_ARGUMENT[at];
Some(Reach { freed: Params::NONE.with(place), kept: Params::NONE, frees_other: false })
}
const NEVER_FREES: &[&str] = &[
"abs",
"aligned_alloc",
"bcopy",
"bzero",
"calloc",
"imaxabs",
"labs",
"llabs",
"malloc",
"memchr",
"memcmp",
"memcpy",
"memmove",
"memset",
"posix_memalign",
"pread",
"pwrite",
"read",
"readv",
"recv",
"send",
"stpcpy",
"strcat",
"strchr",
"strcmp",
"strcpy",
"strcspn",
"strlen",
"strncat",
"strncmp",
"strncpy",
"strnlen",
"strpbrk",
"strrchr",
"strspn",
"strstr",
"write",
"writev",
];
const FREES_ITS_ARGUMENT: &[(&str, usize)] = &[("free", 0), ("realloc", 0)];
const WRAPPER_PREFIX: &str = "__rucc_wrap_";
const RUNTIME_NEVER_FREES: &[&str] = &[
"__rucc_cap_witness",
"__rucc_check_bounds",
"__rucc_check_deriv",
"__rucc_check_init",
"__rucc_check_live",
"__rucc_check_race",
"__rucc_check_type",
"__rucc_extent",
"__rucc_extent_back",
"__rucc_meta_acquire",
"__rucc_meta_epoch",
"__rucc_meta_fence_acquire",
"__rucc_meta_fence_release",
"__rucc_meta_init",
"__rucc_meta_init_copy",
"__rucc_meta_init_handed",
"__rucc_meta_release",
"__rucc_meta_type",
"__rucc_meta_type_copy",
];
fn never_frees(name: &str) -> bool {
if RUNTIME_NEVER_FREES.binary_search(&name).is_ok() {
return true;
}
let name = name.strip_prefix(WRAPPER_PREFIX).unwrap_or(name);
let name = name.strip_prefix("__builtin_").unwrap_or(name);
NEVER_FREES.binary_search(&name).is_ok()
}
#[cfg(test)]
mod tests {
use rucc_base::{Interner, Symbol};
use rucc_ir::{
Builder, CallInfo, Extra, Flags, Func, InstData, Linkage, MemInfo, MemOrder, Module,
Opcode, Pic, Restrict, Sig, Signature, Type, Value, Visibility,
};
use rucc_target::{TargetInfo, Triple};
use super::{
FREES_ITS_ARGUMENT, NEVER_FREES, Params, RUNTIME_NEVER_FREES, Reach, Summaries, annotate,
};
struct Def<'a> {
name: &'a str,
defined: bool,
calls: &'a [&'a str],
}
fn defines<'a>(name: &'a str, calls: &'a [&'a str]) -> Def<'a> {
Def { name, defined: true, calls }
}
fn declares(name: &str) -> Def<'_> {
Def { name, defined: false, calls: &[] }
}
fn module(defs: &[Def<'_>]) -> (Interner, Module) {
let mut names = Interner::new();
let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
let mut module = Module::new(names.intern("t.c"), &target);
for def in defs {
let mut func =
Func::new(names.intern(def.name), Signature::new().with_params(&[Type::PTR]));
if def.defined {
let block = func.create_block();
let held = func.append_param(block, Type::PTR);
let mut build = Builder::new(&mut func, block);
let signature =
build.func().add_signature(Signature::new().with_params(&[Type::PTR]));
for call in def.calls {
build.call(names.intern(call), signature, &[held]);
}
build.ret(&[]);
}
module.add_func(func);
}
(names, module)
}
fn cannot_free(names: &mut Interner, module: &Module, name: &str) -> bool {
let summaries = Summaries::of_module(module, names, Pic::Executable);
summaries.cannot_free(names.intern(name))
}
fn marked(names: &Interner, module: &Module) -> Vec<String> {
let mut found = Vec::new();
for id in module.funcs() {
let func = &module[id];
for block in func.blocks() {
for inst in func.insts(block) {
if !func[inst].flags.contains(Flags::NOFREE) {
continue;
}
let Extra::Call(at) = func[inst].extra else { continue };
let Some(callee) = func[at].callee else { continue };
found.push(names.resolve(callee).to_string());
}
}
}
found
}
#[test]
fn a_function_that_calls_nothing_frees_nothing() {
let (mut names, module) = module(&[defines("leaf", &[])]);
assert!(cannot_free(&mut names, &module, "leaf"));
}
#[test]
fn a_function_that_calls_free_can_free_and_so_can_its_callers() {
let (mut names, module) = module(&[
declares("free"),
defines("releases", &["free"]),
defines("above", &["releases"]),
]);
assert!(!cannot_free(&mut names, &module, "free"));
assert!(!cannot_free(&mut names, &module, "releases"));
assert!(!cannot_free(&mut names, &module, "above"));
}
#[test]
fn a_function_that_only_calls_nofree_ones_frees_nothing() {
let (mut names, module) = module(&[
declares("memcpy"),
defines("leaf", &[]),
defines("above", &["leaf", "memcpy"]),
]);
assert!(cannot_free(&mut names, &module, "above"));
}
#[test]
fn the_table_is_asked_about_a_name_the_module_has_no_function_for() {
let (mut names, module) = module(&[defines("above", &["__rucc_wrap_memcpy"])]);
assert!(cannot_free(&mut names, &module, "__rucc_wrap_memcpy"));
assert!(cannot_free(&mut names, &module, "above"));
}
#[test]
fn a_name_the_module_has_no_function_for_and_the_table_does_not_know_can_still_free() {
let (mut names, module) = module(&[defines("above", &["somebodys_free"])]);
assert!(!cannot_free(&mut names, &module, "somebodys_free"));
assert!(!cannot_free(&mut names, &module, "above"));
}
#[test]
fn two_functions_that_call_each_other_and_free_nothing_are_both_nofree() {
let (mut names, module) = module(&[defines("ping", &["pong"]), defines("pong", &["ping"])]);
assert!(cannot_free(&mut names, &module, "ping"));
assert!(cannot_free(&mut names, &module, "pong"));
}
#[test]
fn a_cycle_with_a_free_anywhere_in_it_is_nofree_nowhere() {
let (mut names, module) = module(&[
declares("free"),
defines("ping", &["pong"]),
defines("pong", &["ping", "free"]),
]);
assert!(!cannot_free(&mut names, &module, "ping"));
assert!(!cannot_free(&mut names, &module, "pong"));
}
#[test]
fn a_name_this_module_never_heard_of_can_free() {
let (mut names, module) = module(&[defines("leaf", &[])]);
assert!(!cannot_free(&mut names, &module, "elsewhere"));
}
#[test]
fn the_library_table_is_read_under_every_spelling_a_name_arrives_in() {
let (mut names, module) = module(&[
declares("memcpy"),
declares("__builtin_memcpy"),
declares("__rucc_wrap_memcpy"),
declares("realloc"),
]);
assert!(cannot_free(&mut names, &module, "memcpy"));
assert!(cannot_free(&mut names, &module, "__builtin_memcpy"));
assert!(cannot_free(&mut names, &module, "__rucc_wrap_memcpy"));
assert!(!cannot_free(&mut names, &module, "realloc"));
}
#[test]
fn a_definition_the_linker_may_replace_is_not_believed() {
let (mut names, mut module) = module(&[defines("weakly", &[])]);
let id = module.funcs().next().unwrap();
module[id].linkage = Linkage::Weak;
assert!(!cannot_free(&mut names, &module, "weakly"));
}
#[test]
fn a_library_cannot_believe_a_body_something_else_may_replace() {
let (mut names, module) = module(&[defines("exported", &[])]);
let name = names.intern("exported");
assert!(Summaries::of_module(&module, &names, Pic::Executable).cannot_free(name));
assert!(!Summaries::of_module(&module, &names, Pic::Library).cannot_free(name));
}
#[test]
fn a_library_believes_the_bodies_nothing_outside_it_can_name() {
let (mut names, mut module) = module(&[defines("shy", &[]), defines("quiet", &[])]);
let mut ids = module.funcs();
let shy = ids.next().unwrap();
let quiet = ids.next().unwrap();
module[shy].visibility = Visibility::Hidden;
module[quiet].linkage = Linkage::Internal;
let summaries = Summaries::of_module(&module, &names, Pic::Library);
assert!(summaries.cannot_free(names.intern("shy")));
assert!(summaries.cannot_free(names.intern("quiet")));
}
#[test]
fn a_call_through_an_address_could_reach_anything() {
let mut names = Interner::new();
let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
let mut module = Module::new(names.intern("t.c"), &target);
let mut func = Func::new(names.intern("dispatch"), Signature::new());
let block = func.create_block();
let mut build = Builder::new(&mut func, block);
let signature = build.func().add_signature(Signature::new());
let varargs = build.func().push_abis(&[]);
let info = build.func().add_call(CallInfo { callee: None, signature, varargs });
build.inst(
InstData { extra: Extra::Call(info), ..InstData::new(Opcode::CallIndirect) },
&[],
);
build.ret(&[]);
module.add_func(func);
assert!(!cannot_free(&mut names, &module, "dispatch"));
}
#[test]
fn the_flag_goes_on_the_calls_the_summaries_vouch_for_and_no_others() {
let (names, mut module) = module(&[
declares("free"),
declares("memcpy"),
defines("leaf", &[]),
defines("above", &["leaf", "memcpy", "free"]),
]);
assert_eq!(annotate(&mut module, &names, Pic::Executable), 2);
assert_eq!(marked(&names, &module), ["leaf", "memcpy"]);
assert_eq!(annotate(&mut module, &names, Pic::Executable), 0);
assert_eq!(marked(&names, &module).len(), 2);
}
fn word() -> MemInfo {
MemInfo {
size: 8,
align: 8,
order: MemOrder::NotAtomic,
tbaa: None,
owns: 0,
restrict: Restrict::NONE,
}
}
fn reach_of(body: impl FnOnce(&mut Builder<'_>, Value, Sig, &mut Interner)) -> Reach {
let mut names = Interner::new();
let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
let mut module = Module::new(names.intern("t.c"), &target);
module
.add_func(Func::new(names.intern("free"), Signature::new().with_params(&[Type::PTR])));
let one = Signature::new().with_params(&[Type::PTR]);
let mut func = Func::new(names.intern("holder"), one);
let block = func.create_block();
let held = func.append_param(block, Type::PTR);
let mut build = Builder::new(&mut func, block);
let signature = build.func().add_signature(Signature::new().with_params(&[Type::PTR]));
body(&mut build, held, signature, &mut names);
module.add_func(func);
let holder = names.intern("holder");
Summaries::of_module(&module, &names, Pic::Executable).reach(holder)
}
#[test]
fn a_set_of_parameters_holds_the_ones_put_in_it() {
assert!(Params::NONE.is_empty());
assert!(!Params::NONE.contains(0));
assert!(Params::NONE.with(3).contains(3));
assert!(!Params::NONE.with(3).contains(4));
assert!(Params::ALL.contains(0));
assert!(!Params::NONE.with(3).contains(200));
assert!(Params::NONE.with(200).contains(201));
assert!(Params::ALL.contains(200));
}
#[test]
fn the_parameter_handed_to_free_is_the_one_reported_as_freed() {
let reach = reach_of(|build, held, signature, names| {
build.call(names.intern("free"), signature, &[held]);
build.ret(&[]);
});
assert!(reach.freed.contains(0), "the parameter it was handed");
assert!(!reach.frees_other, "and nothing else");
assert!(reach.kept.is_empty(), "free keeps nothing it is handed");
assert!(!reach.frees_nothing());
}
#[test]
fn a_displacement_off_the_parameter_is_still_the_parameter() {
let reach = reach_of(|build, held, signature, names| {
let by = build.iconst(Type::int(64), 8);
let at = build.binary(Opcode::PtrAdd, held, by, Flags::NONE);
build.call(names.intern("free"), signature, &[at]);
build.ret(&[]);
});
assert!(reach.freed.contains(0));
assert!(!reach.frees_other);
}
#[test]
fn freeing_something_read_out_of_the_parameter_is_freeing_something_else() {
let reach = reach_of(|build, held, signature, names| {
let next = build.load(Type::PTR, held, word(), Flags::NONE);
build.call(names.intern("free"), signature, &[next]);
build.ret(&[]);
});
assert!(reach.freed.is_empty(), "not the parameter");
assert!(reach.frees_other, "something this cannot name");
assert!(!reach.frees_nothing());
}
#[test]
fn a_parameter_written_into_memory_is_one_the_call_kept() {
let reach = reach_of(|build, held, _signature, _names| {
let slot = build.load(Type::PTR, held, word(), Flags::NONE);
build.store(held, slot, word(), Flags::NONE);
build.ret(&[]);
});
assert!(reach.kept.contains(0), "it is somewhere a later call can find it");
assert!(reach.frees_nothing(), "and nothing was freed doing it");
}
#[test]
fn writing_through_a_parameter_is_not_keeping_it() {
let reach = reach_of(|build, held, _signature, _names| {
let zero = build.iconst(Type::int(64), 0);
let value = build.unary(Opcode::IntToPtr, zero, Type::PTR);
build.store(value, held, word(), Flags::NONE);
build.ret(&[]);
});
assert!(reach.kept.is_empty());
assert!(reach.frees_nothing());
}
#[test]
fn a_parameter_handed_back_is_one_the_call_kept() {
let mut names = Interner::new();
let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
let mut module = Module::new(names.intern("t.c"), &target);
let mut func = Func::new(
names.intern("holder"),
Signature::new().with_params(&[Type::PTR]).with_returns(&[Type::PTR]),
);
let block = func.create_block();
let held = func.append_param(block, Type::PTR);
Builder::new(&mut func, block).ret(&[held]);
module.add_func(func);
let holder = names.intern("holder");
let reach = Summaries::of_module(&module, &names, Pic::Executable).reach(holder);
assert!(reach.kept.contains(0), "the caller is not the only one holding it now");
assert!(reach.frees_nothing());
}
#[test]
fn a_call_through_an_address_keeps_every_pointer_it_was_handed() {
let reach = reach_of(|build, held, signature, _names| {
let varargs = build.func().push_abis(&[]);
let info = build.func().add_call(CallInfo { callee: None, signature, varargs });
let args = build.func().push_values(&[held]);
build.inst(
InstData { extra: Extra::Call(info), args, ..InstData::new(Opcode::CallIndirect) },
&[],
);
build.ret(&[]);
});
assert!(reach.kept.contains(0));
assert!(reach.frees_other);
}
#[test]
fn a_name_the_tables_vouch_for_keeps_nothing_it_is_handed() {
let (mut names, module) = module(&[declares("memcpy")]);
let memcpy = names.intern("memcpy");
let reach = Summaries::of_module(&module, &names, Pic::Executable).reach(memcpy);
assert!(reach.kept.is_empty());
assert!(reach.freed.is_empty());
assert!(!reach.frees_other);
}
#[test]
fn a_name_nothing_is_known_about_could_have_done_anything() {
let (mut names, module) = module(&[declares("elsewhere")]);
let elsewhere = names.intern("elsewhere");
let reach = Summaries::of_module(&module, &names, Pic::Executable).reach(elsewhere);
assert_eq!(reach, Reach::UNKNOWN);
}
#[test]
fn a_call_that_hands_no_pointer_to_a_freeing_position_ends_nothing() {
let mut names = Interner::new();
let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
let mut module = Module::new(names.intern("t.c"), &target);
module
.add_func(Func::new(names.intern("free"), Signature::new().with_params(&[Type::PTR])));
let mut holder =
Func::new(names.intern("holder"), Signature::new().with_params(&[Type::PTR]));
let block = holder.create_block();
let held = holder.append_param(block, Type::PTR);
let mut build = Builder::new(&mut holder, block);
let taking_a_pointer =
build.func().add_signature(Signature::new().with_params(&[Type::PTR]));
build.call(names.intern("free"), taking_a_pointer, &[held]);
build.ret(&[]);
module.add_func(holder);
let mut above = Func::new(names.intern("above"), Signature::new());
let block = above.create_block();
let mut build = Builder::new(&mut above, block);
let taking_a_number =
build.func().add_signature(Signature::new().with_params(&[Type::int(32)]));
let number = build.iconst(Type::int(32), 7);
build.call(names.intern("holder"), taking_a_number, &[number]);
build.ret(&[]);
module.add_func(above);
assert_eq!(annotate(&mut module, &names, Pic::Executable), 1);
assert_eq!(marked(&names, &module), ["holder"]);
}
#[test]
fn nothing_is_known_when_nothing_was_asked() {
let empty = Summaries::nothing();
assert!(empty.is_empty());
assert_eq!(empty.len(), 0);
assert!(!empty.cannot_free(Symbol::from_raw(0)));
}
#[test]
fn the_library_table_is_sorted_and_says_each_name_once() {
for pair in NEVER_FREES.windows(2) {
assert!(pair[0] < pair[1], "{} and {} are out of order", pair[0], pair[1]);
}
for &name in NEVER_FREES {
assert!(!name.starts_with("__builtin_"), "{name} is reached under every spelling");
assert!(!name.starts_with(super::WRAPPER_PREFIX), "{name} likewise");
}
for pair in RUNTIME_NEVER_FREES.windows(2) {
assert!(pair[0] < pair[1], "{} and {} are out of order", pair[0], pair[1]);
}
for pair in FREES_ITS_ARGUMENT.windows(2) {
assert!(pair[0].0 < pair[1].0, "{} and {} are out of order", pair[0].0, pair[1].0);
}
for &(name, _) in FREES_ITS_ARGUMENT {
assert!(!super::never_frees(name), "{name} is in both tables");
}
assert!(!super::never_frees("realloc"));
assert!(!super::never_frees("free"));
assert!(!super::never_frees("__rucc_alloc_purge"));
assert!(!super::never_frees("__rucc_frame_clear"));
}
#[test]
fn the_runtime_entry_points_generated_code_calls_end_no_lifetime() {
let (mut names, module) = module(&[defines("above", &["__rucc_cap_witness"])]);
assert!(cannot_free(&mut names, &module, "above"));
for &name in RUNTIME_NEVER_FREES {
assert!(super::never_frees(name), "{name} is in the table and not read from it");
}
}
}