gaurd: initial guard page commit

This commit is contained in:
Alex Shelkovnykov 2024-02-10 14:43:20 +09:00
parent 3a64e7f656
commit 42ffde2208
19 changed files with 1698 additions and 34 deletions

53
rust/ares/Cargo.lock generated
View File

@ -59,9 +59,10 @@ name = "ares"
version = "0.1.0"
dependencies = [
"ares_crypto",
"ares_guard",
"ares_macros",
"ares_pma",
"assert_no_alloc",
"assert_no_alloc 1.1.2",
"autotools",
"bitvec",
"cc",
@ -86,7 +87,7 @@ version = "0.1.0"
dependencies = [
"aes",
"aes-siv",
"assert_no_alloc",
"assert_no_alloc 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"curve25519-dalek",
"ed25519-dalek",
"ibig 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
@ -95,6 +96,14 @@ dependencies = [
"x25519-dalek",
]
[[package]]
name = "ares_guard"
version = "0.1.0"
dependencies = [
"bindgen",
"cc",
]
[[package]]
name = "ares_macros"
version = "0.1.0"
@ -111,6 +120,10 @@ dependencies = [
"cc",
]
[[package]]
name = "assert_no_alloc"
version = "1.1.2"
[[package]]
name = "assert_no_alloc"
version = "1.1.2"
@ -145,11 +158,11 @@ dependencies = [
[[package]]
name = "bindgen"
version = "0.69.1"
version = "0.69.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ffcebc3849946a7170a05992aac39da343a90676ab392c51a4280981d6379c2"
checksum = "a4c69fae65a523209d34240b60abe0c42d33d1045d445c0839d8a4894a736e2d"
dependencies = [
"bitflags 2.4.1",
"bitflags 2.4.2",
"cexpr",
"clang-sys",
"lazy_static",
@ -174,9 +187,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.4.1"
version = "2.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
[[package]]
name = "bitvec"
@ -664,9 +677,9 @@ dependencies = [
[[package]]
name = "linux-raw-sys"
version = "0.4.12"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456"
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
[[package]]
name = "log"
@ -801,9 +814,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.76"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
dependencies = [
"unicode-ident",
]
@ -860,9 +873,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.10.2"
version = "1.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
dependencies = [
"aho-corasick",
"memchr",
@ -872,9 +885,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.4.3"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
checksum = "3b7fa1134405e2ec9353fd416b17f8dacd46c473d7d3fd1cf202706a14eb792a"
dependencies = [
"aho-corasick",
"memchr",
@ -904,11 +917,11 @@ dependencies = [
[[package]]
name = "rustix"
version = "0.38.28"
version = "0.38.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316"
checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca"
dependencies = [
"bitflags 2.4.1",
"bitflags 2.4.2",
"errno",
"libc",
"linux-raw-sys",
@ -991,9 +1004,9 @@ dependencies = [
[[package]]
name = "shlex"
version = "1.2.0"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signal-hook"

View File

@ -11,14 +11,15 @@ edition = "2018"
# Please keep these alphabetized
[dependencies]
ares_guard = { path = "../ares_guard" }
ares_crypto = { path = "../ares_crypto" }
ares_macros = { path = "../ares_macros" }
# Use this when debugging requires the debug printfs in the PMA
# ares_pma = { path = "../ares_pma", features=["debug_prints"] }
ares_pma = { path = "../ares_pma" }
assert_no_alloc = "1.1.2"
# use this when debugging requires allocation (e.g. eprintln)
# assert_no_alloc = {version="1.1.2", features=["warn_debug"]}
# assert_no_alloc = { path = "../assert-no-alloc", features=["warn_debug"] }
assert_no_alloc = { path = "../assert-no-alloc" }
bitvec = "1.0.0"
criterion = "0.4"
either = "1.9.0"
@ -35,8 +36,8 @@ signal-hook = "0.3"
static_assertions = "1.1.0"
[build-dependencies]
autotools = "0.2.6"
cc = "1.0.79"
autotools = "0.2"
cc = "1.0"
[[bin]]
name = "ares"

114
rust/ares/src/guard.rs Normal file
View File

@ -0,0 +1,114 @@
use crate::interpreter::{Error, Mote, Result};
use crate::noun::D;
use ares_guard::*;
use assert_no_alloc::permit_alloc;
use std::convert::TryFrom;
use std::ffi::c_void;
use std::marker::PhantomData;
#[derive(Debug)]
pub enum GuardError {
InvalidSignal,
MemoryProtection,
NullPointer,
OutOfMemory,
Setup,
Unknown,
}
impl From<u32> for GuardError {
fn from(value: u32) -> Self {
match value {
GUARD_NULL => Self::NullPointer,
GUARD_SIGNAL => Self::InvalidSignal,
GUARD_OOM => Self::OutOfMemory,
x if (x & GUARD_MPROTECT) != 0 => Self::MemoryProtection,
x if (x & (GUARD_MALLOC | GUARD_SIGACTION)) != 0 => Self::Setup,
_ => Self::Unknown,
}
}
}
pub struct CCallback<'closure> {
pub function: unsafe extern "C" fn(*mut c_void) -> *mut c_void,
pub input: *mut c_void,
// without this it's too easy to accidentally drop the closure too soon
_lifetime: PhantomData<&'closure mut c_void>,
}
impl<'closure> CCallback<'closure> {
pub fn new<F>(closure: &'closure mut F) -> Self
where
F: FnMut() -> Result,
{
let function: unsafe extern "C" fn(*mut c_void) -> *mut c_void = Self::call_closure::<F>;
// debug_assert_eq!(
// std::mem::size_of::<&'closure mut F>(),
// std::mem::size_of::<*const c_void>()
// );
// debug_assert_eq!(
// std::mem::size_of_val(&function),
// std::mem::size_of::<*const c_void>()
// );
Self {
function,
input: closure as *mut F as *mut c_void,
_lifetime: PhantomData,
}
}
unsafe extern "C" fn call_closure<F>(input: *mut c_void) -> *mut c_void
where
F: FnMut() -> Result,
{
let cb: &mut F = input.cast::<F>().as_mut().unwrap();
let v = (*cb)();
permit_alloc(|| {
let v_box = Box::new(v);
let v_ptr = Box::into_raw(v_box);
v_ptr as *mut c_void
})
}
}
pub fn call_with_guard<F: FnMut() -> Result>(
stack_pp: *const *const u64,
alloc_pp: *const *const u64,
closure: &mut F,
) -> Result {
let cb = CCallback::new(closure);
let mut ret_p: *mut c_void = std::ptr::null_mut();
let ret_pp = &mut ret_p as *mut *mut c_void;
unsafe {
let res = guard(
Some(cb.function as unsafe extern "C" fn(*mut c_void) -> *mut c_void),
cb.input,
stack_pp as *const usize,
alloc_pp as *const usize,
ret_pp,
);
// eprintln!("\r BEFORE:");
// eprintln!("\r ret = {:?}", ret);
// eprintln!("\r ret_p = {:p}, {:?}", ret_p as *mut Result, *(ret_p as *mut Result));
// eprintln!("\r ret_pp = {:p}, {:p}, {:?}", ret_pp, *ret_pp, **(ret_pp as *mut *mut Result));
if res == 0 {
// TODO: come back to this
permit_alloc(|| {
let result_box = Box::from_raw(ret_p as *mut Result);
*result_box
})
} else {
let err = GuardError::from(u32::try_from(res).unwrap());
match err {
GuardError::OutOfMemory => Err(Error::NonDeterministic(Mote::Meme, D(0))),
_ => {
panic!("serf: guard: unexpected error {:?}", err);
}
}
}
}
}

View File

@ -3,6 +3,7 @@ extern crate num_derive;
extern crate lazy_static;
#[macro_use]
extern crate static_assertions;
pub mod guard;
pub mod hamt;
pub mod interpreter;
pub mod jets;

View File

@ -152,6 +152,16 @@ impl NockStack {
self.alloc_pointer
}
/** Current stack pointer of this NockStack */
pub fn get_stack_pointer_pointer(&self) -> *const *mut u64 {
&self.stack_pointer
}
/** Current alloc pointer of this NockStack */
pub fn get_alloc_pointer_pointer(&self) -> *const *mut u64 {
&self.alloc_pointer
}
/** Start of the memory range for this NockStack */
pub fn get_start(&self) -> *const u64 {
self.start

View File

@ -1,3 +1,4 @@
use crate::guard::call_with_guard;
use crate::hamt::Hamt;
use crate::interpreter;
use crate::interpreter::{inc, interpret, Error, Mote};
@ -143,7 +144,7 @@ impl Context {
snapshot: Option<Snapshot>,
constant_hot_state: &[HotEntry],
) -> Self {
let mut stack = NockStack::new(4096 << 10 << 10, 0);
let mut stack = NockStack::new(2048 << 10 << 10, 0);
let newt = Newt::new();
let cache = Hamt::<Noun>::new(&mut stack);
@ -401,7 +402,28 @@ fn slam(context: &mut Context, axis: u64, ovo: Noun) -> Result<Noun, Error> {
let sam = T(stack, &[D(6), D(0), D(7)]);
let fol = T(stack, &[D(8), pul, D(9), D(2), D(10), sam, D(0), D(2)]);
let sub = T(stack, &[arvo, ovo]);
interpret(&mut context.nock_context, sub, fol)
let frame_p = stack.get_frame_pointer();
let stack_pp = stack.get_stack_pointer_pointer();
let alloc_pp = stack.get_alloc_pointer_pointer();
let res = call_with_guard(
stack_pp as *const *const u64,
alloc_pp as *const *const u64,
&mut || interpret(&mut context.nock_context, sub, fol),
);
if let Err(Error::NonDeterministic(Mote::Meme, _)) = res {
unsafe {
let stack = &mut context.nock_context.stack;
assert_no_alloc::reset_counters();
while stack.get_frame_pointer() != frame_p {
stack.frame_pop();
}
}
}
res
}
fn peek(context: &mut Context, ovo: Noun) -> Noun {
@ -604,16 +626,12 @@ fn work_swap(context: &mut Context, job: Noun, goof: Noun) {
context.work_swap(ovo, fec);
}
Err(goof_crud) => {
work_bail(context, &[goof_crud, goof]);
}
}
}
fn work_bail(context: &mut Context, goofs: &[Noun]) {
eprintln!("\r serf: bail");
let stack = &mut context.nock_context.stack;
let lest = T(stack, goofs);
let lud = T(stack, &[lest, D(0)]);
let lud = T(stack, &[goof_crud, goof, D(0)]);
context.work_bail(lud);
}
}
}
fn work_trace_name(stack: &mut NockStack, wire: Noun, vent: Atom) -> String {

432
rust/ares_guard/Cargo.lock generated Normal file
View File

@ -0,0 +1,432 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
dependencies = [
"memchr",
]
[[package]]
name = "ares_guard"
version = "0.1.0"
dependencies = [
"bindgen",
"cc",
]
[[package]]
name = "bindgen"
version = "0.69.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4c69fae65a523209d34240b60abe0c42d33d1045d445c0839d8a4894a736e2d"
dependencies = [
"bitflags",
"cexpr",
"clang-sys",
"lazy_static",
"lazycell",
"log",
"peeking_take_while",
"prettyplease",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
"syn",
"which",
]
[[package]]
name = "bitflags"
version = "2.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
[[package]]
name = "cc"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
dependencies = [
"libc",
]
[[package]]
name = "cexpr"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
dependencies = [
"nom",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clang-sys"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1"
dependencies = [
"glob",
"libc",
"libloading",
]
[[package]]
name = "either"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
[[package]]
name = "errno"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
dependencies = [
"libc",
"windows-sys 0.52.0",
]
[[package]]
name = "glob"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "home"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lazycell"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "libc"
version = "0.2.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
[[package]]
name = "libloading"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161"
dependencies = [
"cfg-if",
"windows-sys 0.48.0",
]
[[package]]
name = "linux-raw-sys"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
[[package]]
name = "log"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]]
name = "memchr"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "once_cell"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "peeking_take_while"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
[[package]]
name = "prettyplease"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5"
dependencies = [
"proc-macro2",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustix"
version = "0.38.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.52.0",
]
[[package]]
name = "shlex"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380"
[[package]]
name = "syn"
version = "2.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "which"
version = "4.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
dependencies = [
"either",
"home",
"once_cell",
"rustix",
]
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets 0.48.5",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.0",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm 0.48.5",
"windows_aarch64_msvc 0.48.5",
"windows_i686_gnu 0.48.5",
"windows_i686_msvc 0.48.5",
"windows_x86_64_gnu 0.48.5",
"windows_x86_64_gnullvm 0.48.5",
"windows_x86_64_msvc 0.48.5",
]
[[package]]
name = "windows-targets"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
dependencies = [
"windows_aarch64_gnullvm 0.52.0",
"windows_aarch64_msvc 0.52.0",
"windows_i686_gnu 0.52.0",
"windows_i686_msvc 0.52.0",
"windows_x86_64_gnu 0.52.0",
"windows_x86_64_gnullvm 0.52.0",
"windows_x86_64_msvc 0.52.0",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"

View File

@ -0,0 +1,12 @@
[package]
name = "ares_guard"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
[build-dependencies]
bindgen = "0.69"
cc = "1.0"

77
rust/ares_guard/build.rs Normal file
View File

@ -0,0 +1,77 @@
extern crate bindgen;
use std::env;
use std::path::PathBuf;
fn main() {
let opt_level = env::var("OPT_LEVEL").unwrap();
let define_debug = if env::var("CARGO_FEATURE_DEBUG_PRINTS").is_ok() {
"-DDEBUG"
} else {
"-UDEBUG"
};
// This is the directory where the `c` library is located.
let libdir_path = PathBuf::from("c-src")
// Canonicalize the path as `rustc-link-search` requires an absolute
// path.
.canonicalize()
.expect("cannot canonicalize path");
let libdir_path_str = libdir_path.to_str().expect("Path is not a valid string");
// This is the path to the `c` headers file.
let headers_path = libdir_path.join("wrapper.h");
let headers_path_str = headers_path.to_str().expect("Path is not a valid string");
println!("cargo:rerun-if-changed={}", libdir_path_str);
let res = cc::Build::new()
.file(
libdir_path
.join("guard.c")
.to_str()
.expect("Path is not a valid string"),
)
.flag(format!("-O{}", opt_level).as_ref())
.flag(define_debug)
.flag("-g3")
.flag("-Wall")
.flag("-Wextra")
.flag("-Wformat=2")
.flag("-Wmissing-include-dirs")
.flag("-Wnested-externs")
.flag("-Wpedantic")
.flag("-Wredundant-decls")
.flag("-Wshadow")
.flag("-Wwrite-strings")
.flag("-Wno-unused-parameter")
.flag("-Wno-pointer-arith")
.flag("-Wno-strict-prototypes")
.flag("-Wno-unused-function")
.try_compile("guard");
if let Err(err) = res {
panic!("{}", err);
}
// The bindgen::Builder is the main entry point
// to bindgen, and lets you build up options for
// the resulting bindings.
let bindings = bindgen::Builder::default()
// The input header we would like to generate
// bindings for.
.header(headers_path_str)
// Tell cargo to invalidate the built crate whenever any of the
// included header files changed.
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
// Finish the builder and generate the bindings.
.generate()
// Unwrap the Result and panic on failure.
.expect("Unable to generate bindings");
// Write the bindings to the $OUT_DIR/bindings.rs file.
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("bindings.rs");
bindings
.write_to_file(out_path)
.expect("Couldn't write bindings!");
}

View File

@ -0,0 +1,287 @@
#include <assert.h>
#include <errno.h>
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#include "guard.h"
#define GD_PAGE_BITS 14ULL
#define GD_PAGE_SIZE (1ULL << GD_PAGE_BITS) // 16 KB
#define GD_PAGE_MASK (GD_PAGE_SIZE - 1)
#define GD_PAGE_ROUND_DOWN(foo) (foo & (~GD_PAGE_MASK))
/**
* XX: documentation
*/
typedef struct _gs {
uintptr_t guard_p;
const uintptr_t *stack_pp;
const uintptr_t *alloc_pp;
jmp_buf env_buffer;
struct sigaction prev_sa;
} GuardState;
static GuardState *_guard_state = NULL;
static int32_t
_prot_page(void *address, int prot)
{
if (mprotect(address, GD_PAGE_SIZE, prot)) {
fprintf(stderr, "guard: prot: mprotect error %d\r\n", errno);
fprintf(stderr, "%s\r\n", strerror(errno));
return guard_mprotect | errno;
}
return 0;
}
static int32_t
_mark_page(void *address)
{
return _prot_page(address, PROT_NONE);
}
static int32_t
_unmark_page(void *address)
{
return _prot_page(address, PROT_READ | PROT_WRITE);
}
/**
* Center the guard page.
*/
static int32_t
_focus_guard(
uintptr_t *guard_pp,
const uintptr_t stack_p,
const uintptr_t alloc_p
) {
// Check for strange situations.
fprintf(stderr, "guard: focus: stack pointer at %p\r\n", (void *)stack_p);
fprintf(stderr, "guard: focus: alloc pointer at %p\r\n", (void *)alloc_p);
if (stack_p == 0 || alloc_p == 0) {
fprintf(stderr, "guard: focus: stack or alloc pointer is null\r\n");
return guard_null;
} else if (stack_p == alloc_p) {
fprintf(stderr, "guard: focus: stack and alloc pointers equal\r\n");
return guard_oom;
}
uintptr_t old_guard_p = *guard_pp;
uintptr_t new_guard_p;
int32_t err = 0;
fprintf(stderr, "guard: focus: old guard = %p\r\n", (void *)old_guard_p);
// Unmark the old guard page (if there is one)
if (old_guard_p) {
if ((err = _unmark_page((void *)old_guard_p))) {
fprintf(stderr, "guard: focus: unmark error\r\n");
return err;
}
}
// Compute new guard page
// XX: Should we also check for new_guard_p < min(stack_p, alloc_p)?
new_guard_p = GD_PAGE_ROUND_DOWN((stack_p + alloc_p) / 2);
fprintf(stderr, "guard: focus: new guard = %p\r\n", (void *)new_guard_p);
if (new_guard_p == old_guard_p) {
fprintf(stderr, "guard: focus: OOM\r\n");
return guard_oom;
}
// Mark new guard page
if ((err = _mark_page((void *)new_guard_p))) {
fprintf(stderr, "guard: focus: mark error\r\n");
return err;
}
// Update guard page tracker
fprintf(stderr, "guard: focus: installed guard page at %p\r\n", (void *)new_guard_p);
*guard_pp = new_guard_p;
return 0;
}
void
_signal_handler(int sig, siginfo_t *si, void *unused)
{
uintptr_t sig_addr;
int32_t err = 0;
fprintf(stderr, "guard: sig_handle: %d received\r\n", sig);
if (sig != SIGSEGV) {
fprintf(stderr, "guard: sig_handle: invalid signal\r\n");
siglongjmp(_guard_state->env_buffer, guard_signal);
}
sig_addr = (uintptr_t)si->si_addr;
fprintf(stderr, "guard: SIGSEGV address = %p\r\n", (void *)sig_addr);
fprintf(stderr, "guard: sig_handle: %p \r\n", _guard_state);
if (
sig_addr >= _guard_state->guard_p &&
sig_addr < (_guard_state->guard_p + GD_PAGE_SIZE))
{
fprintf(stderr, "guard: page at %p hit\r\n", (void *)_guard_state->guard_p);
err = _focus_guard(
&(_guard_state->guard_p),
*(_guard_state->stack_pp),
*(_guard_state->alloc_pp));
if (err) {
fprintf(stderr, "guard: sig_handle: focus error\r\n");
siglongjmp(_guard_state->env_buffer, err);
}
} else {
struct sigaction prev_sa = _guard_state->prev_sa;
fprintf(stderr, "guard: page at %p miss\r\n", (void *)_guard_state->guard_p);
if (prev_sa.sa_sigaction != NULL) {
prev_sa.sa_sigaction(sig, si, unused);
} else if (prev_sa.sa_handler != NULL) {
prev_sa.sa_handler(sig);
} else {
// There should always be a default SIGSEGV handler
assert(0);
}
}
}
int32_t
_register_handler(struct sigaction *prev_sa)
{
struct sigaction sa;
// Flag to use sa_sigaction
sa.sa_flags = SA_SIGINFO;
// Must use sa_sigaction; sa-handler takes signal handler as its only argument
sa.sa_sigaction = _signal_handler;
// Set mask of signals to ignore while running signal handler
// TODO: By default the signal that triggered the signal handler is automatically added to the
// mask while it's being handled, so unless we plan to add more signals to this then I
// don't think it's necessary.
// sigemptyset(&sa.sa_mask);
// sigaddset(&(sa.sa_mask), SIGSEGV);
// Set the new SIGSEGV handler, and save the old SIGSEGV handler (if any)
if (sigaction(SIGSEGV, &sa, prev_sa)) {
fprintf(stderr, "guard: register: sigaction error\r\n");
fprintf(stderr, "%s\r\n", strerror(errno));
return guard_sigaction | errno;
}
return 0;
}
int32_t
_setup(
GuardState **gs_p,
const uintptr_t *const stack_pp,
const uintptr_t *const alloc_pp
) {
GuardState *gs;
int32_t err = 0;
assert(*gs_p == NULL);
fprintf(stderr, "guard: setup: stack pointer at %p\r\n", (void *)(*stack_pp));
fprintf(stderr, "guard: setup: alloc pointer at %p\r\n", (void *)(*alloc_pp));
// Setup guard page state
*gs_p = (GuardState *)malloc(sizeof(GuardState));
gs = *gs_p;
if (gs == NULL) {
fprintf(stderr, "guard: malloc error\r\n");
fprintf(stderr, "%s\r\n", strerror(errno));
return guard_malloc | errno;
}
fprintf(stderr, "guard: state allocated to %p \r\n", gs);
gs->guard_p = 0;
gs->stack_pp = stack_pp;
gs->alloc_pp = alloc_pp;
// Initialize the guard page
if ((err = _focus_guard(&(gs->guard_p), *stack_pp, *alloc_pp))) {
fprintf(stderr, "guard: setup: _focus_guard error\r\n");
return err;
}
// Register guard page signal handler
if ((err = _register_handler(&(gs->prev_sa)))) {
fprintf(stderr, "guard: setup: _register_handler error\r\n");
return err;
}
return 0;
}
int32_t
_teardown(GuardState** gs_p)
{
int32_t err = 0;
if (*gs_p != NULL) {
GuardState *gs = *gs_p;
if (gs->guard_p != 0) {
err = _unmark_page((void *)gs->guard_p);
}
if (sigaction(SIGSEGV, &(gs->prev_sa), NULL)) {
fprintf(stderr, "guard: teardown: sigaction error\r\n");
fprintf(stderr, "%s\r\n", strerror(errno));
err = guard_sigaction | errno;
}
free(gs);
*gs_p = NULL;
}
return err;
}
int32_t
guard(
callback f,
void *closure,
const uintptr_t *const stack_pp,
const uintptr_t *const alloc_pp,
void ** ret
) {
int32_t err = 0;
// Setup the guard page
fprintf(stderr, "guard: setup\r\n");
if ((err = _setup(&_guard_state, stack_pp, alloc_pp))) {
goto done;
}
// Run given closure
fprintf(stderr, "guard: run\r\n");
if (!(err = sigsetjmp(_guard_state->env_buffer, 1))) {
*ret = f(closure);
// Clean up
fprintf(stderr, "guard: teardown\r\n");
err = _teardown(&_guard_state);
fprintf(stderr, "guard: return\r\n");
return err;
} else {
done:
// Clean up
fprintf(stderr, "guard: teardown\r\n");
_teardown(&_guard_state);
fprintf(stderr, "guard: return\r\n");
return err;
}
}

View File

@ -0,0 +1,59 @@
#ifndef __GUARD_H__
#define __GUARD_H__
#include <stdint.h>
/**
* Error codes and flags.
*
* The flags are bitwise added to the errno of their respective errors.
*/
typedef enum {
guard_null = 1, // null stack or alloc pointer
guard_signal, // invalid signal
guard_oom, // OOM
guard_malloc = 0x10000000, // malloc error flag
guard_mprotect = 0x20000000, // mprotect error flag
guard_sigaction = 0x40000000, // sigaction error flag
} guard_err;
typedef void *(*callback)(void *);
/**
* Execute the given closure `f` within the memory arena between the
* `stack` and `alloc` pointers, with guard page protection. Write either
* `f`'s succesful result or a `guard_err` to the given `ret` pointer.
*
* Memory
* ------
* The free memory arena between the `stack` and `alloc` pointers is part of a
* NockStack frame, which may either face east or west. If the frame faces
* east, the `stack` pointer will be greater than the `alloc` pointer. If it
* faces west, the `stack` pointer will be less than the `alloc` pointer.
*
* All the pages in the memory arena are marked clean (`PROT_READ | PROT_WRITE`)
* by default, with the exception of a single guard page in the middle of the
* arena, which is marked with `PROT_NONE`.
*
* Guard
* -----
* This function protects the free memory arena between the `stack` and `alloc`
* pointers with a guard page. A guard page is simply a single page of memory
* which is marked with `PROT_NONE`. Since all other pages are marked clean by
* default, a SIGSEGV will only be raised if the `f` function attempts to write
* to the guard page. When it does, the signal handler will attempt to re-center
* the guard page in the remaining free space left in the arena. If there is no
* more free space, then memory exhaustion has occurred and the `guard_spent`
* error will be written to the `ret` pointer. The caller is then responsible
* for handling this error and aborting with a `bail:meme`.
*/
int32_t
guard(
callback f,
void *closure,
const uintptr_t *const stack_pp,
const uintptr_t *const alloc_pp,
void ** ret
);
#endif // __GUARD_H__

View File

@ -0,0 +1 @@
#include "guard.h"

View File

@ -0,0 +1,12 @@
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
pub const GUARD_NULL: u32 = guard_err_guard_null;
pub const GUARD_SIGNAL: u32 = guard_err_guard_signal;
pub const GUARD_OOM: u32 = guard_err_guard_oom;
pub const GUARD_MALLOC: u32 = guard_err_guard_malloc;
pub const GUARD_MPROTECT: u32 = guard_err_guard_mprotect;
pub const GUARD_SIGACTION: u32 = guard_err_guard_sigaction;

View File

@ -0,0 +1,32 @@
[package]
name = "assert_no_alloc"
version = "1.1.2"
authors = ["Florian Jung <flo@windfis.ch>"]
edition = "2018"
license = "BSD-1-Clause"
description = "Custom Rust allocator allowing to temporarily disable memory (de)allocations for a thread. Aborts or prints a warning if allocating although forbidden."
homepage = "https://github.com/Windfisch/rust-assert-no-alloc"
repository = "https://github.com/Windfisch/rust-assert-no-alloc"
readme = "README.md"
keywords = ["allocator", "real-time", "debug", "audio"]
categories = ["development-tools::debugging"]
[features]
default = ["disable_release"]
warn_debug = []
warn_release = []
disable_release = []
# Print a backtrace before aborting the program when an allocation failure happens
backtrace = ["dep:backtrace"]
# Use the `log` crate instead of printing to STDERR
# WARNING: If the allocation failure happens during a logger call, then
# depending on the logger's implementation this may block indefinitely
log = ["dep:log"]
[dependencies]
backtrace = { version = "0.3", optional = true }
log = { version = "0.4", optional = true }
[package.metadata.docs.rs]
features = ["warn_debug"]

View File

@ -0,0 +1,21 @@
assert_no_alloc -- A custom Rust allocator allowing to temporarily disable
memory (de)allocations for a thread.
Copyright (c) 2020 Florian Jung <flo@windfis.ch>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,143 @@
assert_no_alloc
===============
This crate provides a custom allocator that allows to temporarily disable
memory (de)allocations for a thread. If a (de)allocation is attempted
anyway, the program will abort or print a warning.
It uses thread local storage for the "disabled-flag/counter", and thus
should be thread safe, if the underlying allocator (currently hard-coded
to `std::alloc::System`) is.
[documentation @ docs.rs](https://docs.rs/assert_no_alloc/1.1.0/assert_no_alloc/),
[crates.io](https://crates.io/crates/assert_no_alloc)
Rationale
---------
No-allocation-zones are relevant e.g. in real-time scenarios like audio
callbacks. Allocation and deallocation can take unpredictable amounts of
time, and thus can *sometimes* lead to audible glitches because the audio
data is not served in time.
Debugging such problems can be hard, because it is difficult to reproduce
such problems consistently. Avoiding such problems is also hard, since
allocation/deallocation is a common thing to do and most libraries are not
explicit whether certain functions can allocate or not. Also, this might
even depend on the run-time situation (e.g. a `Vec::push` might allocate,
but it is guaranteed to not allocate *if* enough space has been `reserve()`d
before).
To aid the developer in tackling these problems, this crate offers an easy
way of detecting all forbidden allocations.
How to use
----------
First, configure the features: `warn_debug` and `warn_release` change the
behaviour from aborting your program into just printing an error message
on `stderr`. Aborting is useful for debugging purposes, as it allows you
to retrieve a stacktrace, while warning is less intrusive.
Note that you need to disable the (default-enabled) `disable_release` feature
by specify `default-features = false` if you want to use `warn_release`. If
`disable_release` is set (which is the default), then this crate will do
nothing if built in `--release` mode.
Second, use the allocator provided by this crate. Add this to `main.rs`:
```rust
use assert_no_alloc::*;
#[cfg(debug_assertions)] // required when disable_release is set (default)
#[global_allocator]
static A: AllocDisabler = AllocDisabler;
```
Third, wrap code sections that may not allocate like this:
```rust
assert_no_alloc(|| {
println!("This code can not allocate.");
});
```
Advanced use
------------
Values can be returned using:
```rust
let answer = assert_no_alloc(|| { 42 });
```
The effect of `assert_no_alloc` can be overridden using `permit_alloc`:
```rust
assert_no_alloc(|| {
permit_alloc(|| {
// Allocate some memory here. This will work.
});
});
```
This is useful for test stubs whose code is executed in an `assert_no_alloc`
context.
Objects that deallocate upon `Drop` can be wrapped in `PermitDrop`:
```rust
let foo = PermitDrop::new(
permit_alloc(||
Box::new(...)
)
);
```
Dropping `foo` will not trigger an assertion (but dropping a `Box` would).
`assert_no_alloc()` calls can be nested, with proper panic unwinding handling.
Note that to fully bypass this crate, e.g. when in release mode, you need to
*both* have the `disable_release` feature flag enabled (which it is by default)
and to not register `AllocDisabler` as `global_allocator`.
Optional features
-----------------
These compile time features are not enabled by default:
- `backtrace` causes a backtrace to be printed before the allocation failure.
This backtrace is gathered at runtime, and its accuracy depends on the
platform and the compilation options used.
- `log` uses the `log` crate to write the allocation failure message to the
configured logger. If the `backtrace` feature is also enabled, then the
backtrace will also be written to the logger This can be useful when using a
logger that writes directly to a file or any other place that isn't STDERR.
The main caveat here is that if the allocation was caused by the logger and if
the logger wraps its entire log function in a regular non-entrant mutex, then
this may result in a deadlock. Make sure your logger doesn't do this before
enabling this feature.
Examples
--------
See [examples/main.rs](https://github.com/Windfisch/rust-assert-no-alloc/blob/master/examples/main.rs) for an example.
You can try out the different feature flags:
- `cargo run --example main` -> memory allocation of 4 bytes failed. Aborted (core dumped)
- `cargo run --example main --release --no-default-features` -> same as above.
- `cargo run --example main --features=warn_debug` -> Tried to (de)allocate memory in a thread that forbids allocator calls! This will not be executed if the above allocation has aborted.
- `cargo run --example main --features=warn_release --release --no-default-features` -> same as above.
- `cargo run --example main --release` will not even check for forbidden allocations
Test suite
----------
The tests will fail to compile with the default features. Run them using:
```
cargo test --features=warn_debug --tests
```

View File

@ -0,0 +1,34 @@
use assert_no_alloc::*;
#[cfg(debug_assertions)]
#[global_allocator]
static A: AllocDisabler = AllocDisabler;
fn main() {
println!("Alloc is allowed. Let's allocate some memory...");
let mut vec_can_push = Vec::new();
vec_can_push.push(42);
println!();
let fib5 = assert_no_alloc(|| {
println!("Alloc is forbidden. Let's calculate something without memory allocations...");
fn fib(n: u32) -> u32 {
if n<=1 { 1 }
else { fib(n-1) + fib(n-2) }
}
fib(5)
});
println!("\tSuccess, the 5th fibonacci number is {}", fib5);
println!();
assert_no_alloc(|| {
println!("Alloc is forbidden. Let's allocate some memory...");
let mut vec_cannot_push = Vec::new();
vec_cannot_push.push(42); // panics
});
println!("This will not be executed if the above allocation has aborted.");
}

View File

@ -0,0 +1,249 @@
/* assert_no_alloc -- A custom Rust allocator allowing to temporarily disable
* memory (de)allocations for a thread.
*
* Copyright (c) 2020 Florian Jung <flo@windfis.ch>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#![doc = include_str!("../README.md")]
use std::alloc::{System,GlobalAlloc,Layout};
use std::cell::Cell;
// check for mutually exclusive features.
#[cfg(all(feature = "disable_release", feature = "warn_release"))]
compile_error!("disable_release cannot be active at the same time with warn_release");
#[cfg(not(all(feature = "disable_release", not(debug_assertions))))] // if not disabled
thread_local! {
static ALLOC_FORBID_COUNT: Cell<u32> = Cell::new(0);
static ALLOC_PERMIT_COUNT: Cell<u32> = Cell::new(0);
#[cfg(any( all(feature="warn_debug", debug_assertions), all(feature="warn_release", not(debug_assertions)) ))]
static ALLOC_VIOLATION_COUNT: Cell<u32> = Cell::new(0);
}
#[cfg(all(feature = "disable_release", not(debug_assertions)))] // if disabled
pub fn assert_no_alloc<T, F: FnOnce() -> T> (func: F) -> T { // no-op
func()
}
#[cfg(all(feature = "disable_release", not(debug_assertions)))] // if disabled
pub fn permit_alloc<T, F: FnOnce() -> T> (func: F) -> T { // no-op
func()
}
#[cfg(not(all(feature = "disable_release", not(debug_assertions))))] // if not disabled
/// Calls the `func` closure, but forbids any (de)allocations.
///
/// If a call to the allocator is made, the program will abort with an error,
/// print a warning (depending on the `warn_debug` feature flag. Or ignore
/// the situation, when compiled in `--release` mode with the `disable_release`
///feature flag set (which is the default)).
pub fn assert_no_alloc<T, F: FnOnce() -> T> (func: F) -> T {
// RAII guard for managing the forbid counter. This is to ensure correct behaviour
// when catch_unwind is used
struct Guard;
impl Guard {
fn new() -> Guard {
ALLOC_FORBID_COUNT.with(|c| c.set(c.get()+1));
Guard
}
}
impl Drop for Guard {
fn drop(&mut self) {
ALLOC_FORBID_COUNT.with(|c| c.set(c.get()-1));
}
}
#[cfg(any( all(feature="warn_debug", debug_assertions), all(feature="warn_release", not(debug_assertions)) ))] // if warn mode is selected
let old_violation_count = violation_count();
let guard = Guard::new(); // increment the forbid counter
let ret = func();
std::mem::drop(guard); // decrement the forbid counter
#[cfg(any( all(feature="warn_debug", debug_assertions), all(feature="warn_release", not(debug_assertions)) ))] // if warn mode is selected
if violation_count() > old_violation_count {
eprintln!("Tried to (de)allocate memory in a thread that forbids allocator calls!");
}
return ret;
}
#[cfg(not(all(feature = "disable_release", not(debug_assertions))))] // if not disabled
/// Calls the `func` closure. Allocations are temporarily allowed, even if this
/// code runs inside of assert_no_alloc.
pub fn permit_alloc<T, F: FnOnce() -> T> (func: F) -> T {
// RAII guard for managing the permit counter
struct Guard;
impl Guard {
fn new() -> Guard {
ALLOC_PERMIT_COUNT.with(|c| c.set(c.get()+1));
Guard
}
}
impl Drop for Guard {
fn drop(&mut self) {
ALLOC_PERMIT_COUNT.with(|c| c.set(c.get()-1));
}
}
let guard = Guard::new(); // increment the forbid counter
let ret = func();
std::mem::drop(guard); // decrement the forbid counter
return ret;
}
#[cfg(any( all(feature="warn_debug", debug_assertions), all(feature="warn_release", not(debug_assertions)) ))] // if warn mode is selected
/// Returns the count of allocation warnings emitted so far.
///
/// Only available when the `warn_debug` or `warn release` features are enabled.
pub fn violation_count() -> u32 {
ALLOC_VIOLATION_COUNT.with(|c| c.get())
}
#[cfg(any( all(feature="warn_debug", debug_assertions), all(feature="warn_release", not(debug_assertions)) ))] // if warn mode is selected
/// Resets the count of allocation warnings to zero.
///
/// Only available when the `warn_debug` or `warn release` features are enabled.
pub fn reset_violation_count() {
ALLOC_VIOLATION_COUNT.with(|c| c.set(0));
}
pub fn reset_counters() {
ALLOC_FORBID_COUNT.with(|c| c.set(0));
ALLOC_PERMIT_COUNT.with(|c| c.set(0));
#[cfg(any( all(feature="warn_debug", debug_assertions), all(feature="warn_release", not(debug_assertions)) ))]
ALLOC_VIOLATION_COUNT.with(|c| c.set(0));
}
#[cfg(not(all(feature = "disable_release", not(debug_assertions))))] // if not disabled
/// The custom allocator that handles the checking.
///
/// To use this crate, you must add the following in your `main.rs`:
/// ```rust
/// use assert_no_alloc::*;
/// // ...
/// #[cfg(debug_assertions)]
/// #[global_allocator]
/// static A: AllocDisabler = AllocDisabler;
/// ```
pub struct AllocDisabler;
#[cfg(not(all(feature = "disable_release", not(debug_assertions))))] // if not disabled
impl AllocDisabler {
fn check(&self, layout: Layout) {
let forbid_count = ALLOC_FORBID_COUNT.with(|f| f.get());
let permit_count = ALLOC_PERMIT_COUNT.with(|p| p.get());
if forbid_count > 0 && permit_count == 0 {
#[cfg(any( all(feature="warn_debug", debug_assertions), all(feature="warn_release", not(debug_assertions)) ))] // if warn mode is selected
ALLOC_VIOLATION_COUNT.with(|c| c.set(c.get()+1));
#[cfg(any( all(not(feature="warn_debug"), debug_assertions), all(not(feature="warn_release"), not(debug_assertions)) ))] // if abort mode is selected
{
#[cfg(all(feature = "log", feature = "backtrace"))]
permit_alloc(|| log::error!("Memory allocation of {} bytes failed from:\n{:?}", layout.size(), backtrace::Backtrace::new()));
#[cfg(all(feature = "log", not(feature = "backtrace")))]
permit_alloc(|| log::error!("Memory allocation of {} bytes failed", layout.size()));
#[cfg(all(not(feature = "log"), feature = "backtrace"))]
permit_alloc(|| eprintln!("Allocation failure from:\n{:?}", backtrace::Backtrace::new()));
// This handler can be overridden (although as of writing, the API to do so is still
// unstable) so we must always call this even when the log feature is enabled
std::alloc::handle_alloc_error(layout);
}
}
}
}
#[cfg(not(all(feature = "disable_release", not(debug_assertions))))] // if not disabled
unsafe impl GlobalAlloc for AllocDisabler {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
self.check(layout);
System.alloc(layout)
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
self.check(layout);
System.dealloc(ptr, layout)
}
}
/// Wrapper for objects whose Drop implementation shall be permitted
/// to (de)allocate.
///
/// Typical usage:
///
/// ```rust
/// let foo = PermitDrop::new(
/// permit_alloc(||
/// Box::new(...)
/// )
/// );
/// ```
///
/// Here, creation of the Box is guarded by the explicit `permit_alloc` call,
/// and destruction of the Box is guarded by PermitDrop. Neither creation nor
/// destruction will cause an assertion failure from within `assert_no_alloc`.
pub struct PermitDrop<T>(Option<T>);
impl<T> PermitDrop<T> {
pub fn new(t: T) -> PermitDrop<T> {
permit_alloc(|| {
PermitDrop(Some(t))
})
}
}
impl<T> std::ops::Deref for PermitDrop<T> {
type Target = T;
fn deref(&self) -> &T { self.0.as_ref().unwrap() }
}
impl<T> std::ops::DerefMut for PermitDrop<T> {
fn deref_mut(&mut self) -> &mut T { self.0.as_mut().unwrap() }
}
impl<I: Iterator> Iterator for PermitDrop<I> {
type Item = I::Item;
fn next(&mut self) -> Option<Self::Item> {
(**self).next()
}
}
impl<T> Drop for PermitDrop<T> {
fn drop(&mut self) {
let mut tmp = None;
std::mem::swap(&mut tmp, &mut self.0);
permit_alloc(|| {
std::mem::drop(tmp);
});
}
}

View File

@ -0,0 +1,148 @@
use assert_no_alloc::*;
use std::panic::catch_unwind;
#[global_allocator]
static A: AllocDisabler = AllocDisabler;
#[cfg(not(feature = "warn_debug"))]
compile_error!("The test suite requires the warn_debug feature to be enabled. Use `cargo test --features warn_debug`");
// This is only a kludge; what we actually want to check is "will do_alloc() be optimized out?", e.g. due to
// compiler optimizations turned on in --release mode. We can't do that, the closest we can get is to check
// whether debug_assertions are disabled, which coincidentially also happens in release mode.
#[cfg(not(debug_assertions))]
compile_error!("The test suite only works in debug mode. Use `cargo test --features warn_debug`");
#[cfg(feature = "warn_debug")]
fn check_and_reset() -> bool {
let result = violation_count() > 0;
reset_violation_count();
result
}
// Provide a stub check_and_reset() function if warn_debug is disabled. This will never be compiled due to the
// compile_error!() above, but this stub ensures that the output will not be cluttered with spurious error
// messages.
#[cfg(not(feature = "warn_debug"))]
fn check_and_reset() -> bool { unreachable!() }
fn do_alloc() {
let _tmp: Box<u32> = Box::new(42);
}
#[test]
fn ok_noop() {
assert_eq!(check_and_reset(), false);
do_alloc();
assert_eq!(check_and_reset(), false);
}
#[test]
fn ok_simple() {
assert_eq!(check_and_reset(), false);
assert_no_alloc(|| {
});
do_alloc();
assert_eq!(check_and_reset(), false);
}
#[test]
fn ok_nested() {
assert_eq!(check_and_reset(), false);
assert_no_alloc(|| {
assert_no_alloc(|| {
});
});
do_alloc();
assert_eq!(check_and_reset(), false);
}
#[test]
fn forbidden_simple() {
assert_eq!(check_and_reset(), false);
assert_no_alloc(|| {
do_alloc();
});
assert_eq!(check_and_reset(), true);
}
#[test]
fn forbidden_in_nested() {
assert_eq!(check_and_reset(), false);
assert_no_alloc(|| {
assert_no_alloc(|| {
do_alloc();
});
});
assert_eq!(check_and_reset(), true);
}
#[test]
fn forbidden_after_nested() {
assert_eq!(check_and_reset(), false);
assert_no_alloc(|| {
assert_no_alloc(|| {
});
do_alloc();
});
assert_eq!(check_and_reset(), true);
}
#[test]
fn unwind_ok() {
assert_eq!(check_and_reset(), false);
assert_no_alloc(|| {
let r = catch_unwind(|| {
assert_no_alloc(|| {
panic!();
});
});
assert!(r.is_err());
});
check_and_reset(); // unwinding might have allocated memory; we don't care about that.
do_alloc();
assert_eq!(check_and_reset(), false);
}
#[test]
fn unwind_nested() {
assert_eq!(check_and_reset(), false);
assert_no_alloc(|| {
let r = catch_unwind(|| {
assert_no_alloc(|| {
panic!();
});
});
assert!(r.is_err());
check_and_reset(); // unwinding might have allocated memory; we don't care about that.
do_alloc();
assert_eq!(check_and_reset(), true);
});
}
#[test]
fn unwind_nested2() {
assert_eq!(check_and_reset(), false);
assert_no_alloc(|| {
assert_no_alloc(|| {
let r = catch_unwind(|| {
assert_no_alloc(|| {
assert_no_alloc(|| {
panic!();
});
});
});
assert!(r.is_err());
check_and_reset(); // unwinding might have allocated memory; we don't care about that.
do_alloc();
assert_eq!(check_and_reset(), true);
});
});
check_and_reset(); // unwinding might have allocated memory; we don't care about that.
do_alloc();
assert_eq!(check_and_reset(), false);
}