diff --git a/resources/pills/oom.pill b/resources/pills/oom.pill new file mode 100644 index 00000000..e43ce0d6 Binary files /dev/null and b/resources/pills/oom.pill differ diff --git a/resources/pills/src/oom/oom.hoon b/resources/pills/src/oom/oom.hoon new file mode 100644 index 00000000..de9b4f40 --- /dev/null +++ b/resources/pills/src/oom/oom.hoon @@ -0,0 +1,81 @@ +:: A working pill designed to force bail:meme OOM errors. Requires +:: playpen.hoon, originally used by the toddler pill. +:: +/+ playpen +!. +=/ core + => playpen + != + => + |% + +$ card (cask) + ++ cask |$ [a] (pair mark a) + +$ goof [mote=term =tang] + +$ mark @tas + +$ ovum [=wire =card] + +$ vere [[non=@ta rev=path] kel=wynn] + +$ wire path + +$ wasp + :: %crud: reroute $ovum with $goof + :: %wack: iterate entropy + :: %wyrd: check/record runtime kelvin stack + :: + $% [%crud =goof =ovum] + [%wack p=@uvJ] + [%wyrd p=vere] + == + +$ weft [lal=@tas num=@ud] + +$ wynn (list weft) + -- => + :: + =| counter=@u + |% + ++ load !! + ++ peek _~ + ++ wish !! + ++ poke + |= [now=@da ovo=ovum] + ^- ^ + :: + ?. ?=(?(%crud %wack %wyrd) p.card.ovo) + ?: (gte counter 20) + ~> %slog.[0 %leaf .=(~ =|(i=@ |-(?:(=(i ^~((bex 32))) ~ [i $(i +(i))])))) 0] + [~ ..poke] + ~> %slog.[0 leaf+(scow %ud counter)] + =. counter +(counter) + [~ ..poke] + :: + =/ buz + ~> %mean.'pith: bad wasp' + ;;(wasp card.ovo) + ?+ -.buz + ~> %slog.[0 leaf+"%wack / %wyrd"] + [~ ..poke] + :: + %crud + =/ tang tang.goof.buz + |- + ?~ tang + [~ ..poke] + ~> %slog.[0 -.tang] + $(tang +.tang) + == + -- + :: + |= [now=@da ovo=ovum] + ^- * + .(+> +:(poke now ovo)) +:: +|% +++ aeon + ^- * + => *[arvo=* epic=*] + != + |- ^- * + ?@ epic arvo + %= $ + epic +.epic + arvo .*([arvo -.epic] [%9 2 %10 [6 %0 3] %0 2]) + == +-- +[%pill %toddler [aeon .*(playpen core) ~] ~ ~] diff --git a/rust/ares/Cargo.lock b/rust/ares/Cargo.lock index d585a78d..274c0b57 100644 --- a/rust/ares/Cargo.lock +++ b/rust/ares/Cargo.lock @@ -59,6 +59,7 @@ name = "ares" version = "0.1.0" dependencies = [ "ares_crypto", + "ares_guard", "ares_macros", "ares_pma", "assert_no_alloc", @@ -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" @@ -114,8 +123,6 @@ dependencies = [ [[package]] name = "assert_no_alloc" version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55ca83137a482d61d916ceb1eba52a684f98004f18e0cafea230fe5579c178a3" [[package]] name = "atty" @@ -145,11 +152,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 +181,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 +671,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 +808,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 +867,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 +879,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 +911,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 +998,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" diff --git a/rust/ares/Cargo.toml b/rust/ares/Cargo.toml index 3d7ec25d..3156e0d2 100644 --- a/rust/ares/Cargo.toml +++ b/rust/ares/Cargo.toml @@ -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 = "../rust-assert-no-alloc", features=["warn_debug"] } +assert_no_alloc = { path = "../rust-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" diff --git a/rust/ares/src/guard.rs b/rust/ares/src/guard.rs new file mode 100644 index 00000000..f83b8c54 --- /dev/null +++ b/rust/ares/src/guard.rs @@ -0,0 +1,97 @@ +use crate::interpreter::{Error, Mote, Result}; +use crate::noun::D; +use ares_guard::*; +use assert_no_alloc::permit_alloc; +use std::ffi::c_void; +use std::marker::PhantomData; + +#[derive(Debug)] +pub enum GuardError { + MemoryProtection, + NullPointer, + OutOfMemory, + Setup, + Unknown, +} + +impl From for GuardError { + fn from(value: u32) -> Self { + match value { + GUARD_NULL => Self::NullPointer, + 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(closure: &'closure mut F) -> Self + where + F: FnMut() -> Result, + { + let function: unsafe extern "C" fn(*mut c_void) -> *mut c_void = Self::call_closure::; + + Self { + function, + input: closure as *mut F as *mut c_void, + _lifetime: PhantomData, + } + } + + unsafe extern "C" fn call_closure(input: *mut c_void) -> *mut c_void + where + F: FnMut() -> Result, + { + let cb: &mut F = input.cast::().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 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, + ); + + if res == 0 { + permit_alloc(|| { + let result_box = Box::from_raw(ret_p as *mut Result); + *result_box + }) + } else { + let err = GuardError::from(res); + match err { + GuardError::OutOfMemory => Err(Error::NonDeterministic(Mote::Meme, D(0))), + _ => { + panic!("serf: guard: unexpected error {:?} {}", err, res); + } + } + } + } +} diff --git a/rust/ares/src/interpreter.rs b/rust/ares/src/interpreter.rs index 186ba3d8..12bb2298 100644 --- a/rust/ares/src/interpreter.rs +++ b/rust/ares/src/interpreter.rs @@ -1,6 +1,7 @@ use crate::assert_acyclic; use crate::assert_no_forwarding_pointers; use crate::assert_no_junior_pointers; +use crate::guard::call_with_guard; use crate::hamt::Hamt; use crate::jets::cold; use crate::jets::cold::Cold; @@ -16,9 +17,9 @@ use crate::serf::TERMINATOR; use crate::trace::{write_nock_trace, TraceInfo, TraceStack}; use crate::unifying_equality::unifying_equality; use ares_macros::tas; -use assert_no_alloc::assert_no_alloc; +use assert_no_alloc::{assert_no_alloc, ensure_alloc_counters}; use bitvec::prelude::{BitSlice, Lsb0}; -use either::Either::*; +use either::*; use std::result; use std::sync::atomic::Ordering; use std::sync::Arc; @@ -400,533 +401,560 @@ pub fn interpret(context: &mut Context, mut subject: Noun, formula: Noun) -> Res // ``` // // (See https://docs.rs/assert_no_alloc/latest/assert_no_alloc/#advanced-use) - let nock = assert_no_alloc(|| unsafe { - push_formula(&mut context.stack, formula, true)?; - - loop { - let work: NockWork = *context.stack.top(); - match work { - NockWork::Done => { - write_trace(context); - - let stack = &mut context.stack; - debug_assertions(stack, orig_subject); - debug_assertions(stack, subject); - debug_assertions(stack, res); - - stack.preserve(&mut context.cache); - stack.preserve(&mut context.cold); - stack.preserve(&mut context.warm); - stack.preserve(&mut res); - stack.frame_pop(); - - debug_assertions(stack, orig_subject); - debug_assertions(stack, res); - - break Ok(res); - } - NockWork::Ret => { - write_trace(context); - - let stack = &mut context.stack; - debug_assertions(stack, orig_subject); - debug_assertions(stack, subject); - debug_assertions(stack, res); - - stack.preserve(&mut context.cache); - stack.preserve(&mut context.cold); - stack.preserve(&mut context.warm); - stack.preserve(&mut res); - stack.frame_pop(); - - debug_assertions(stack, orig_subject); - debug_assertions(stack, res); - } - NockWork::WorkCons(mut cons) => match cons.todo { - TodoCons::ComputeHead => { - cons.todo = TodoCons::ComputeTail; - *context.stack.top() = NockWork::WorkCons(cons); - push_formula(&mut context.stack, cons.head, false)?; - } - TodoCons::ComputeTail => { - cons.todo = TodoCons::Cons; - cons.head = res; - *context.stack.top() = NockWork::WorkCons(cons); - push_formula(&mut context.stack, cons.tail, false)?; - } - TodoCons::Cons => { - let stack = &mut context.stack; - res = T(stack, &[cons.head, res]); - stack.pop::(); - } - }, - NockWork::Work0(zero) => { - if let Ok(noun) = subject.slot_atom(zero.axis) { - res = noun; - context.stack.pop::(); - } else { - // Axis invalid for input Noun - break BAIL_EXIT; - } - } - NockWork::Work1(once) => { - res = once.noun; - context.stack.pop::(); - } - NockWork::Work2(mut vale) => { - if (*terminator).load(Ordering::Relaxed) { - break BAIL_INTR; - } + let nock = assert_no_alloc(|| { + ensure_alloc_counters(|| { + let stack_pp = context.stack.get_stack_pointer_pointer() as *const *const u64; + let alloc_pp = context.stack.get_alloc_pointer_pointer() as *const *const u64; + let work_f = &mut || unsafe { + push_formula(&mut context.stack, formula, true)?; + + loop { + let work: NockWork = *context.stack.top(); + match work { + NockWork::Done => { + write_trace(context); - match vale.todo { - Todo2::ComputeSubject => { - vale.todo = Todo2::ComputeFormula; - *context.stack.top() = NockWork::Work2(vale); - push_formula(&mut context.stack, vale.subject, false)?; - } - Todo2::ComputeFormula => { - vale.todo = Todo2::ComputeResult; - vale.subject = res; - *context.stack.top() = NockWork::Work2(vale); - push_formula(&mut context.stack, vale.formula, false)?; - } - Todo2::ComputeResult => { let stack = &mut context.stack; - if vale.tail { - stack.pop::(); - subject = vale.subject; - push_formula(stack, res, true)?; - } else { - vale.todo = Todo2::RestoreSubject; - std::mem::swap(&mut vale.subject, &mut subject); - *stack.top() = NockWork::Work2(vale); + debug_assertions(stack, orig_subject); + debug_assertions(stack, subject); + debug_assertions(stack, res); - debug_assertions(stack, orig_subject); - debug_assertions(stack, subject); - debug_assertions(stack, res); + stack.preserve(&mut context.cache); + stack.preserve(&mut context.cold); + stack.preserve(&mut context.warm); + stack.preserve(&mut res); + stack.frame_pop(); - mean_frame_push(stack, 0); - *stack.push() = NockWork::Ret; - push_formula(stack, res, true)?; - } + debug_assertions(stack, orig_subject); + debug_assertions(stack, res); + + break Ok(res); } - Todo2::RestoreSubject => { + NockWork::Ret => { + write_trace(context); + let stack = &mut context.stack; + debug_assertions(stack, orig_subject); + debug_assertions(stack, subject); + debug_assertions(stack, res); - subject = vale.subject; - stack.pop::(); + stack.preserve(&mut context.cache); + stack.preserve(&mut context.cold); + stack.preserve(&mut context.warm); + stack.preserve(&mut res); + stack.frame_pop(); debug_assertions(stack, orig_subject); - debug_assertions(stack, subject); debug_assertions(stack, res); } - } - } - NockWork::Work3(mut thee) => match thee.todo { - Todo3::ComputeChild => { - thee.todo = Todo3::ComputeType; - *context.stack.top() = NockWork::Work3(thee); - push_formula(&mut context.stack, thee.child, false)?; - } - Todo3::ComputeType => { - res = if res.is_cell() { D(0) } else { D(1) }; - context.stack.pop::(); - } - }, - NockWork::Work4(mut four) => match four.todo { - Todo4::ComputeChild => { - four.todo = Todo4::Increment; - *context.stack.top() = NockWork::Work4(four); - push_formula(&mut context.stack, four.child, false)?; - } - Todo4::Increment => { - if let Ok(atom) = res.as_atom() { - res = inc(&mut context.stack, atom).as_noun(); - context.stack.pop::(); - } else { - // Cannot increment (Nock 4) a cell - break BAIL_EXIT; - } - } - }, - NockWork::Work5(mut five) => match five.todo { - Todo5::ComputeLeftChild => { - five.todo = Todo5::ComputeRightChild; - *context.stack.top() = NockWork::Work5(five); - push_formula(&mut context.stack, five.left, false)?; - } - Todo5::ComputeRightChild => { - five.todo = Todo5::TestEquals; - five.left = res; - *context.stack.top() = NockWork::Work5(five); - push_formula(&mut context.stack, five.right, false)?; - } - Todo5::TestEquals => { - let stack = &mut context.stack; - let saved_value_ptr = &mut five.left; - res = if unifying_equality(stack, &mut res, saved_value_ptr) { - D(0) - } else { - D(1) - }; - stack.pop::(); - } - }, - NockWork::Work6(mut cond) => match cond.todo { - Todo6::ComputeTest => { - cond.todo = Todo6::ComputeBranch; - *context.stack.top() = NockWork::Work6(cond); - push_formula(&mut context.stack, cond.test, false)?; - } - Todo6::ComputeBranch => { - let stack = &mut context.stack; - stack.pop::(); - if let Left(direct) = res.as_either_direct_allocated() { - if direct.data() == 0 { - push_formula(stack, cond.zero, cond.tail)?; - } else if direct.data() == 1 { - push_formula(stack, cond.once, cond.tail)?; + NockWork::WorkCons(mut cons) => match cons.todo { + TodoCons::ComputeHead => { + cons.todo = TodoCons::ComputeTail; + *context.stack.top() = NockWork::WorkCons(cons); + push_formula(&mut context.stack, cons.head, false)?; + } + TodoCons::ComputeTail => { + cons.todo = TodoCons::Cons; + cons.head = res; + *context.stack.top() = NockWork::WorkCons(cons); + push_formula(&mut context.stack, cons.tail, false)?; + } + TodoCons::Cons => { + let stack = &mut context.stack; + res = T(stack, &[cons.head, res]); + stack.pop::(); + } + }, + NockWork::Work0(zero) => { + if let Ok(noun) = subject.slot_atom(zero.axis) { + res = noun; + context.stack.pop::(); } else { - // Test branch of Nock 6 must return 0 or 1 + // Axis invalid for input Noun break BAIL_EXIT; } - } else { - // Test branch of Nock 6 must return a direct atom - break BAIL_EXIT; - } - } - }, - NockWork::Work7(mut pose) => match pose.todo { - Todo7::ComputeSubject => { - pose.todo = Todo7::ComputeResult; - *context.stack.top() = NockWork::Work7(pose); - push_formula(&mut context.stack, pose.subject, false)?; - } - Todo7::ComputeResult => { - let stack = &mut context.stack; - if pose.tail { - stack.pop::(); - subject = res; - push_formula(stack, pose.formula, true)?; - } else { - pose.todo = Todo7::RestoreSubject; - pose.subject = subject; - *stack.top() = NockWork::Work7(pose); - subject = res; - push_formula(stack, pose.formula, false)?; } - } - Todo7::RestoreSubject => { - subject = pose.subject; - context.stack.pop::(); - } - }, - NockWork::Work8(mut pins) => match pins.todo { - Todo8::ComputeSubject => { - pins.todo = Todo8::ComputeResult; - *context.stack.top() = NockWork::Work8(pins); - push_formula(&mut context.stack, pins.pin, false)?; - } - Todo8::ComputeResult => { - let stack = &mut context.stack; - if pins.tail { - subject = T(stack, &[res, subject]); - stack.pop::(); - push_formula(stack, pins.formula, true)?; - } else { - pins.todo = Todo8::RestoreSubject; - pins.pin = subject; - *stack.top() = NockWork::Work8(pins); - subject = T(stack, &[res, subject]); - push_formula(stack, pins.formula, false)?; + NockWork::Work1(once) => { + res = once.noun; + context.stack.pop::(); } - } - Todo8::RestoreSubject => { - subject = pins.pin; - context.stack.pop::(); - } - }, - NockWork::Work9(mut kale) => { - if (*terminator).load(Ordering::Relaxed) { - break BAIL_INTR; - } + NockWork::Work2(mut vale) => { + if (*terminator).load(Ordering::Relaxed) { + break BAIL_INTR; + } - match kale.todo { - Todo9::ComputeCore => { - kale.todo = Todo9::ComputeResult; - *context.stack.top() = NockWork::Work9(kale); - push_formula(&mut context.stack, kale.core, false)?; - } - Todo9::ComputeResult => { - if let Ok(mut formula) = res.slot_atom(kale.axis) { - if !cfg!(feature = "sham_hints") { - if let Some((jet, _path)) = context.warm.find_jet( - &mut context.stack, - &mut res, - &mut formula, - ) { - match jet(context, res) { - Ok(jet_res) => { - res = jet_res; - context.stack.pop::(); - continue; - } - Err(JetErr::Punt) => {} - Err(err) => { - break Err(err.into()); - } - } - } - }; + match vale.todo { + Todo2::ComputeSubject => { + vale.todo = Todo2::ComputeFormula; + *context.stack.top() = NockWork::Work2(vale); + push_formula(&mut context.stack, vale.subject, false)?; + } + Todo2::ComputeFormula => { + vale.todo = Todo2::ComputeResult; + vale.subject = res; + *context.stack.top() = NockWork::Work2(vale); + push_formula(&mut context.stack, vale.formula, false)?; + } + Todo2::ComputeResult => { + let stack = &mut context.stack; + if vale.tail { + stack.pop::(); + subject = vale.subject; + push_formula(stack, res, true)?; + } else { + vale.todo = Todo2::RestoreSubject; + std::mem::swap(&mut vale.subject, &mut subject); + *stack.top() = NockWork::Work2(vale); - let stack = &mut context.stack; - if kale.tail { - stack.pop::(); + debug_assertions(stack, orig_subject); + debug_assertions(stack, subject); + debug_assertions(stack, res); - // We could trace on 2 as well, but 2 only comes from Hoon via - // '.*', so we can assume it's never directly used to invoke - // jetted code. - if context.trace_info.is_some() { - if let Some(path) = context.cold.matches(stack, &mut res) { - append_trace(stack, path); - }; - }; + mean_frame_push(stack, 0); + *stack.push() = NockWork::Ret; + push_formula(stack, res, true)?; + } + } + Todo2::RestoreSubject => { + let stack = &mut context.stack; - subject = res; - push_formula(stack, formula, true)?; - } else { - kale.todo = Todo9::RestoreSubject; - kale.core = subject; - *stack.top() = NockWork::Work9(kale); + subject = vale.subject; + stack.pop::(); debug_assertions(stack, orig_subject); debug_assertions(stack, subject); debug_assertions(stack, res); - - subject = res; - mean_frame_push(stack, 0); - *stack.push() = NockWork::Ret; - push_formula(stack, formula, true)?; - - // We could trace on 2 as well, but 2 only comes from Hoon via - // '.*', so we can assume it's never directly used to invoke - // jetted code. - if context.trace_info.is_some() { - if let Some(path) = context.cold.matches(stack, &mut res) { - append_trace(stack, path); - }; - }; } - } else { - // Axis into core must be atom - break BAIL_EXIT; } } - Todo9::RestoreSubject => { - let stack = &mut context.stack; - - subject = kale.core; - stack.pop::(); - - debug_assertions(stack, orig_subject); - debug_assertions(stack, subject); - debug_assertions(stack, res); - } - } - } - NockWork::Work10(mut diet) => { - match diet.todo { - Todo10::ComputeTree => { - diet.todo = Todo10::ComputePatch; // should we compute patch then tree? - *context.stack.top() = NockWork::Work10(diet); - push_formula(&mut context.stack, diet.tree, false)?; - } - Todo10::ComputePatch => { - diet.todo = Todo10::Edit; - diet.tree = res; - *context.stack.top() = NockWork::Work10(diet); - push_formula(&mut context.stack, diet.patch, false)?; - } - Todo10::Edit => { - res = edit(&mut context.stack, diet.axis.as_bitslice(), res, diet.tree); - context.stack.pop::(); - } - } - } - NockWork::Work11D(mut dint) => match dint.todo { - Todo11D::ComputeHint => { - if let Some(ret) = - hint::match_pre_hint(context, subject, dint.tag, dint.hint, dint.body) - { - match ret { - Ok(found) => { - res = found; + NockWork::Work3(mut thee) => match thee.todo { + Todo3::ComputeChild => { + thee.todo = Todo3::ComputeType; + *context.stack.top() = NockWork::Work3(thee); + push_formula(&mut context.stack, thee.child, false)?; + } + Todo3::ComputeType => { + res = if res.is_cell() { D(0) } else { D(1) }; + context.stack.pop::(); + } + }, + NockWork::Work4(mut four) => match four.todo { + Todo4::ComputeChild => { + four.todo = Todo4::Increment; + *context.stack.top() = NockWork::Work4(four); + push_formula(&mut context.stack, four.child, false)?; + } + Todo4::Increment => { + if let Ok(atom) = res.as_atom() { + res = inc(&mut context.stack, atom).as_noun(); context.stack.pop::(); - } - Err(err) => { - break Err(err); + } else { + // Cannot increment (Nock 4) a cell + break BAIL_EXIT; } } - } else { - dint.todo = Todo11D::ComputeResult; - *context.stack.top() = NockWork::Work11D(dint); - push_formula(&mut context.stack, dint.hint, false)?; - } - } - Todo11D::ComputeResult => { - if let Some(ret) = hint::match_pre_nock( - context, - subject, - dint.tag, - Some((dint.hint, res)), - dint.body, - ) { - match ret { - Ok(found) => { - res = found; - context.stack.pop::(); + }, + NockWork::Work5(mut five) => match five.todo { + Todo5::ComputeLeftChild => { + five.todo = Todo5::ComputeRightChild; + *context.stack.top() = NockWork::Work5(five); + push_formula(&mut context.stack, five.left, false)?; + } + Todo5::ComputeRightChild => { + five.todo = Todo5::TestEquals; + five.left = res; + *context.stack.top() = NockWork::Work5(five); + push_formula(&mut context.stack, five.right, false)?; + } + Todo5::TestEquals => { + let stack = &mut context.stack; + let saved_value_ptr = &mut five.left; + res = if unifying_equality(stack, &mut res, saved_value_ptr) { + D(0) + } else { + D(1) + }; + stack.pop::(); + } + }, + NockWork::Work6(mut cond) => match cond.todo { + Todo6::ComputeTest => { + cond.todo = Todo6::ComputeBranch; + *context.stack.top() = NockWork::Work6(cond); + push_formula(&mut context.stack, cond.test, false)?; + } + Todo6::ComputeBranch => { + let stack = &mut context.stack; + stack.pop::(); + if let Left(direct) = res.as_either_direct_allocated() { + if direct.data() == 0 { + push_formula(stack, cond.zero, cond.tail)?; + } else if direct.data() == 1 { + push_formula(stack, cond.once, cond.tail)?; + } else { + // Test branch of Nock 6 must return 0 or 1 + break BAIL_EXIT; + } + } else { + // Test branch of Nock 6 must return a direct atom + break BAIL_EXIT; } - Err(err) => { - break Err(err); + } + }, + NockWork::Work7(mut pose) => match pose.todo { + Todo7::ComputeSubject => { + pose.todo = Todo7::ComputeResult; + *context.stack.top() = NockWork::Work7(pose); + push_formula(&mut context.stack, pose.subject, false)?; + } + Todo7::ComputeResult => { + let stack = &mut context.stack; + if pose.tail { + stack.pop::(); + subject = res; + push_formula(stack, pose.formula, true)?; + } else { + pose.todo = Todo7::RestoreSubject; + pose.subject = subject; + *stack.top() = NockWork::Work7(pose); + subject = res; + push_formula(stack, pose.formula, false)?; } } - } else { - if dint.tail { + Todo7::RestoreSubject => { + subject = pose.subject; context.stack.pop::(); - } else { - dint.todo = Todo11D::Done; - dint.hint = res; - *context.stack.top() = NockWork::Work11D(dint); } - push_formula(&mut context.stack, dint.body, dint.tail)?; - } - } - Todo11D::Done => { - if let Some(found) = hint::match_post_nock( - context, - subject, - dint.tag, - Some(dint.hint), - dint.body, - res, - ) { - res = found; - } - context.stack.pop::(); - } - }, - NockWork::Work11S(mut sint) => match sint.todo { - Todo11S::ComputeResult => { - if let Some(ret) = - hint::match_pre_nock(context, subject, sint.tag, None, sint.body) - { - match ret { - Ok(found) => { - res = found; - context.stack.pop::(); - } - Err(err) => { - break Err(err); + }, + NockWork::Work8(mut pins) => match pins.todo { + Todo8::ComputeSubject => { + pins.todo = Todo8::ComputeResult; + *context.stack.top() = NockWork::Work8(pins); + push_formula(&mut context.stack, pins.pin, false)?; + } + Todo8::ComputeResult => { + let stack = &mut context.stack; + if pins.tail { + subject = T(stack, &[res, subject]); + stack.pop::(); + push_formula(stack, pins.formula, true)?; + } else { + pins.todo = Todo8::RestoreSubject; + pins.pin = subject; + *stack.top() = NockWork::Work8(pins); + subject = T(stack, &[res, subject]); + push_formula(stack, pins.formula, false)?; } } - } else { - if sint.tail { + Todo8::RestoreSubject => { + subject = pins.pin; context.stack.pop::(); - } else { - sint.todo = Todo11S::Done; - *context.stack.top() = NockWork::Work11S(sint); } - push_formula(&mut context.stack, sint.body, sint.tail)?; + }, + NockWork::Work9(mut kale) => { + if (*terminator).load(Ordering::Relaxed) { + break BAIL_INTR; + } + + match kale.todo { + Todo9::ComputeCore => { + kale.todo = Todo9::ComputeResult; + *context.stack.top() = NockWork::Work9(kale); + push_formula(&mut context.stack, kale.core, false)?; + } + Todo9::ComputeResult => { + if let Ok(mut formula) = res.slot_atom(kale.axis) { + if !cfg!(feature = "sham_hints") { + if let Some((jet, _path)) = context.warm.find_jet( + &mut context.stack, + &mut res, + &mut formula, + ) { + match jet(context, res) { + Ok(jet_res) => { + res = jet_res; + context.stack.pop::(); + continue; + } + Err(JetErr::Punt) => {} + Err(err) => { + break Err(err.into()); + } + } + } + }; + + let stack = &mut context.stack; + if kale.tail { + stack.pop::(); + + // We could trace on 2 as well, but 2 only comes from Hoon via + // '.*', so we can assume it's never directly used to invoke + // jetted code. + if context.trace_info.is_some() { + if let Some(path) = + context.cold.matches(stack, &mut res) + { + append_trace(stack, path); + }; + }; + + subject = res; + push_formula(stack, formula, true)?; + } else { + kale.todo = Todo9::RestoreSubject; + kale.core = subject; + *stack.top() = NockWork::Work9(kale); + + debug_assertions(stack, orig_subject); + debug_assertions(stack, subject); + debug_assertions(stack, res); + + subject = res; + mean_frame_push(stack, 0); + *stack.push() = NockWork::Ret; + push_formula(stack, formula, true)?; + + // We could trace on 2 as well, but 2 only comes from Hoon via + // '.*', so we can assume it's never directly used to invoke + // jetted code. + if context.trace_info.is_some() { + if let Some(path) = + context.cold.matches(stack, &mut res) + { + append_trace(stack, path); + }; + }; + } + } else { + // Axis into core must be atom + break BAIL_EXIT; + } + } + Todo9::RestoreSubject => { + let stack = &mut context.stack; + + subject = kale.core; + stack.pop::(); + + debug_assertions(stack, orig_subject); + debug_assertions(stack, subject); + debug_assertions(stack, res); + } + } } - } - Todo11S::Done => { - if let Some(found) = - hint::match_post_nock(context, subject, sint.tag, None, sint.body, res) - { - res = found; + NockWork::Work10(mut diet) => { + match diet.todo { + Todo10::ComputeTree => { + diet.todo = Todo10::ComputePatch; // should we compute patch then tree? + *context.stack.top() = NockWork::Work10(diet); + push_formula(&mut context.stack, diet.tree, false)?; + } + Todo10::ComputePatch => { + diet.todo = Todo10::Edit; + diet.tree = res; + *context.stack.top() = NockWork::Work10(diet); + push_formula(&mut context.stack, diet.patch, false)?; + } + Todo10::Edit => { + res = edit( + &mut context.stack, + diet.axis.as_bitslice(), + res, + diet.tree, + ); + context.stack.pop::(); + } + } } - context.stack.pop::(); - } - }, - NockWork::Work12(mut scry) => match scry.todo { - Todo12::ComputeReff => { - let stack = &mut context.stack; - scry.todo = Todo12::ComputePath; - *stack.top() = NockWork::Work12(scry); - push_formula(stack, scry.reff, false)?; - } - Todo12::ComputePath => { - let stack = &mut context.stack; - scry.todo = Todo12::Scry; - scry.reff = res; - *stack.top() = NockWork::Work12(scry); - push_formula(stack, scry.path, false)?; - } - Todo12::Scry => { - if let Some(cell) = context.scry_stack.cell() { - scry.path = res; - let scry_stack = context.scry_stack; - let scry_handler = cell.head(); - let scry_gate = scry_handler.as_cell()?; - let payload = T(&mut context.stack, &[scry.reff, res]); - let scry_core = T( - &mut context.stack, - &[ - scry_gate.head(), - payload, - scry_gate.tail().as_cell()?.tail(), - ], - ); - let scry_form = T(&mut context.stack, &[D(9), D(2), D(1), scry_core]); - - context.scry_stack = cell.tail(); - // Alternately, we could use scry_core as the subject and [9 2 0 1] as - // the formula. It's unclear if performance will be better with a purely - // static formula. - match interpret(context, D(0), scry_form) { - Ok(noun) => match noun.as_either_atom_cell() { - Left(atom) => { - if atom.as_noun().raw_equals(D(0)) { - break Err(Error::ScryBlocked(scry.path)); - } else { - break Err(Error::ScryCrashed(D(0))); + NockWork::Work11D(mut dint) => match dint.todo { + Todo11D::ComputeHint => { + if let Some(ret) = hint::match_pre_hint( + context, subject, dint.tag, dint.hint, dint.body, + ) { + match ret { + Ok(found) => { + res = found; + context.stack.pop::(); + } + Err(err) => { + break Err(err); } } - Right(cell) => match cell.tail().as_either_atom_cell() { - Left(_) => { - let stack = &mut context.stack; - let hunk = - T(stack, &[D(tas!(b"hunk")), scry.reff, scry.path]); - mean_push(stack, hunk); - break Err(Error::ScryCrashed(D(0))); + } else { + dint.todo = Todo11D::ComputeResult; + *context.stack.top() = NockWork::Work11D(dint); + push_formula(&mut context.stack, dint.hint, false)?; + } + } + Todo11D::ComputeResult => { + if let Some(ret) = hint::match_pre_nock( + context, + subject, + dint.tag, + Some((dint.hint, res)), + dint.body, + ) { + match ret { + Ok(found) => { + res = found; + context.stack.pop::(); } - Right(cell) => { - res = cell.tail(); - context.scry_stack = scry_stack; + Err(err) => { + break Err(err); + } + } + } else { + if dint.tail { + context.stack.pop::(); + } else { + dint.todo = Todo11D::Done; + dint.hint = res; + *context.stack.top() = NockWork::Work11D(dint); + } + push_formula(&mut context.stack, dint.body, dint.tail)?; + } + } + Todo11D::Done => { + if let Some(found) = hint::match_post_nock( + context, + subject, + dint.tag, + Some(dint.hint), + dint.body, + res, + ) { + res = found; + } + context.stack.pop::(); + } + }, + NockWork::Work11S(mut sint) => match sint.todo { + Todo11S::ComputeResult => { + if let Some(ret) = hint::match_pre_nock( + context, subject, sint.tag, None, sint.body, + ) { + match ret { + Ok(found) => { + res = found; context.stack.pop::(); } - }, - }, - Err(error) => match error { - Error::Deterministic(_, trace) | Error::ScryCrashed(trace) => { - break Err(Error::ScryCrashed(trace)); + Err(err) => { + break Err(err); + } } - Error::NonDeterministic(_, _) => { - break Err(error); + } else { + if sint.tail { + context.stack.pop::(); + } else { + sint.todo = Todo11S::Done; + *context.stack.top() = NockWork::Work11S(sint); } - Error::ScryBlocked(_) => { - break BAIL_FAIL; + push_formula(&mut context.stack, sint.body, sint.tail)?; + } + } + Todo11S::Done => { + if let Some(found) = hint::match_post_nock( + context, subject, sint.tag, None, sint.body, res, + ) { + res = found; + } + context.stack.pop::(); + } + }, + NockWork::Work12(mut scry) => match scry.todo { + Todo12::ComputeReff => { + let stack = &mut context.stack; + scry.todo = Todo12::ComputePath; + *stack.top() = NockWork::Work12(scry); + push_formula(stack, scry.reff, false)?; + } + Todo12::ComputePath => { + let stack = &mut context.stack; + scry.todo = Todo12::Scry; + scry.reff = res; + *stack.top() = NockWork::Work12(scry); + push_formula(stack, scry.path, false)?; + } + Todo12::Scry => { + if let Some(cell) = context.scry_stack.cell() { + scry.path = res; + let scry_stack = context.scry_stack; + let scry_handler = cell.head(); + let scry_gate = scry_handler.as_cell()?; + let payload = T(&mut context.stack, &[scry.reff, res]); + let scry_core = T( + &mut context.stack, + &[ + scry_gate.head(), + payload, + scry_gate.tail().as_cell()?.tail(), + ], + ); + let scry_form = + T(&mut context.stack, &[D(9), D(2), D(1), scry_core]); + + context.scry_stack = cell.tail(); + // Alternately, we could use scry_core as the subject and [9 2 0 1] as + // the formula. It's unclear if performance will be better with a purely + // static formula. + match interpret(context, D(0), scry_form) { + Ok(noun) => match noun.as_either_atom_cell() { + Left(atom) => { + if atom.as_noun().raw_equals(D(0)) { + break Err(Error::ScryBlocked(scry.path)); + } else { + break Err(Error::ScryCrashed(D(0))); + } + } + Right(cell) => { + match cell.tail().as_either_atom_cell() { + Left(_) => { + let stack = &mut context.stack; + let hunk = T( + stack, + &[ + D(tas!(b"hunk")), + scry.reff, + scry.path, + ], + ); + mean_push(stack, hunk); + break Err(Error::ScryCrashed(D(0))); + } + Right(cell) => { + res = cell.tail(); + context.scry_stack = scry_stack; + context.stack.pop::(); + } + } + } + }, + Err(error) => match error { + Error::Deterministic(_, trace) + | Error::ScryCrashed(trace) => { + break Err(Error::ScryCrashed(trace)); + } + Error::NonDeterministic(_, _) => { + break Err(error); + } + Error::ScryBlocked(_) => { + break BAIL_FAIL; + } + }, } - }, + } else { + // No scry handler + break BAIL_EXIT; + } } - } else { - // No scry handler - break BAIL_EXIT; - } - } - }, + }, + }; + } }; - } + + call_with_guard(stack_pp, alloc_pp, work_f) + }) }); match nock { @@ -1163,6 +1191,7 @@ fn exit( Error::Deterministic(_, t) | Error::NonDeterministic(_, t) | Error::ScryCrashed(t) => { // Return $tang of traces let h = *(stack.local_noun_pointer(0)); + // XX: Small chance of clobbering something important after OOM? T(stack, &[h, t]) } }; diff --git a/rust/ares/src/jets.rs b/rust/ares/src/jets.rs index 7bb034da..9d1a97da 100644 --- a/rust/ares/src/jets.rs +++ b/rust/ares/src/jets.rs @@ -234,8 +234,7 @@ pub mod util { } pub fn slot(noun: Noun, axis: u64) -> Result { - noun.slot(axis) - .map_err(|_e| JetErr::Fail(Error::Deterministic(Mote::Exit, D(0)))) + noun.slot(axis).map_err(|_e| BAIL_EXIT) } /// Extract a bloq and check that it's computable by the current system diff --git a/rust/ares/src/lib.rs b/rust/ares/src/lib.rs index 17b72230..d6f6391e 100644 --- a/rust/ares/src/lib.rs +++ b/rust/ares/src/lib.rs @@ -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; diff --git a/rust/ares/src/mem.rs b/rust/ares/src/mem.rs index 15a69de6..210e8976 100644 --- a/rust/ares/src/mem.rs +++ b/rust/ares/src/mem.rs @@ -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 @@ -255,7 +265,7 @@ impl NockStack { } } - /** Pointer to where the previous stack pointer is saved in a frame */ + /** Pointer to where the previous alloc pointer is saved in a frame */ unsafe fn prev_alloc_pointer_pointer(&self) -> *mut *mut u64 { if !self.pc { self.slot_pointer(ALLOC) as *mut *mut u64 @@ -278,14 +288,8 @@ impl NockStack { if self.pc { panic!("Allocation during cleanup phase is prohibited."); } - - let alloc = self.alloc_pointer.sub(words); - if alloc < self.stack_pointer { - ptr::null_mut() - } else { - self.alloc_pointer = alloc; - alloc - } + self.alloc_pointer = self.alloc_pointer.sub(words); + self.alloc_pointer } /** Bump the alloc pointer for an east frame to make space for an allocation */ @@ -293,15 +297,9 @@ impl NockStack { if self.pc { panic!("Allocation during cleanup phase is prohibited."); } - let alloc = self.alloc_pointer; - let new_ap = self.alloc_pointer.add(words); - if new_ap > self.stack_pointer { - ptr::null_mut() - } else { - self.alloc_pointer = new_ap; - alloc - } + self.alloc_pointer = self.alloc_pointer.add(words); + alloc } /** Allocate space for an indirect pointer in a west frame */ @@ -345,24 +343,14 @@ impl NockStack { unsafe fn raw_alloc_in_previous_frame_west(&mut self, words: usize) -> *mut u64 { // note that the allocation is on the east frame, and thus resembles raw_alloc_east let alloc = *self.prev_alloc_pointer_pointer(); - let new_prev_ap = (*(self.prev_alloc_pointer_pointer())).add(words); - if new_prev_ap > self.stack_pointer { - ptr::null_mut() - } else { - *(self.prev_alloc_pointer_pointer()) = new_prev_ap; - alloc - } + *(self.prev_alloc_pointer_pointer()) = (*(self.prev_alloc_pointer_pointer())).add(words); + alloc } unsafe fn raw_alloc_in_previous_frame_east(&mut self, words: usize) -> *mut u64 { // note that the allocation is on the west frame, and thus resembles raw_alloc_west - let alloc = (*(self.prev_alloc_pointer_pointer())).sub(words); - if alloc < self.stack_pointer { - ptr::null_mut() - } else { - *(self.prev_alloc_pointer_pointer()) = alloc; - alloc - } + *(self.prev_alloc_pointer_pointer()) = (*(self.prev_alloc_pointer_pointer())).sub(words); + *(self.prev_alloc_pointer_pointer()) } /** Allocate space in the previous stack frame. This calls pre_copy() first to ensure that the @@ -427,27 +415,16 @@ impl NockStack { * or not pre_copy() has been called.*/ unsafe fn pre_copy(&mut self) { if !self.pc { - let old_stack_pointer = self.stack_pointer; - + *(self.free_slot(FRAME)) = *(self.slot_pointer(FRAME)); + *(self.free_slot(STACK)) = *(self.slot_pointer(STACK)); + *(self.free_slot(ALLOC)) = *(self.slot_pointer(ALLOC)); + self.pc = true; // Change polarity of lightweight stack. if self.is_west() { self.stack_pointer = self.alloc_pointer.sub(RESERVED + 1); - if self.stack_pointer < old_stack_pointer { - // OOM - std::ptr::null::().read_volatile(); - } } else { self.stack_pointer = self.alloc_pointer.add(RESERVED); - if self.stack_pointer > old_stack_pointer { - // OOM - std::ptr::null::().read_volatile(); - } } - self.pc = true; - - *(self.free_slot(FRAME)) = *(self.slot_pointer(FRAME)); - *(self.free_slot(STACK)) = *(self.slot_pointer(STACK)); - *(self.free_slot(ALLOC)) = *(self.slot_pointer(ALLOC)); } } @@ -652,22 +629,13 @@ impl NockStack { let current_stack_pointer = self.stack_pointer; let current_alloc_pointer = self.alloc_pointer; unsafe { - if self.is_west() { - self.frame_pointer = current_alloc_pointer.sub(num_locals + RESERVED); - if self.frame_pointer <= current_stack_pointer { - // OOM - std::ptr::null::().read_volatile(); - } + self.frame_pointer = if self.is_west() { + current_alloc_pointer.sub(num_locals + RESERVED) } else { - self.frame_pointer = current_alloc_pointer.add(num_locals + RESERVED); - if self.frame_pointer >= current_stack_pointer { - // OOM - std::ptr::null::().read_volatile(); - } - } + current_alloc_pointer.add(num_locals + RESERVED) + }; self.alloc_pointer = current_stack_pointer; self.stack_pointer = self.frame_pointer; - *(self.slot_pointer(FRAME)) = current_frame_pointer as u64; *(self.slot_pointer(STACK)) = current_stack_pointer as u64; *(self.slot_pointer(ALLOC)) = current_alloc_pointer as u64; diff --git a/rust/ares/src/serf.rs b/rust/ares/src/serf.rs index 3aa80317..1f4aecce 100644 --- a/rust/ares/src/serf.rs +++ b/rust/ares/src/serf.rs @@ -143,7 +143,7 @@ impl Context { snapshot: Option, 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::::new(&mut stack); @@ -401,6 +401,7 @@ fn slam(context: &mut Context, axis: u64, ovo: Noun) -> Result { 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) } @@ -604,18 +605,14 @@ fn work_swap(context: &mut Context, job: Noun, goof: Noun) { context.work_swap(ovo, fec); } Err(goof_crud) => { - work_bail(context, &[goof_crud, goof]); + eprintln!("\rserf: bail"); + let stack = &mut context.nock_context.stack; + let lud = T(stack, &[goof_crud, goof, D(0)]); + context.work_bail(lud); } } } -fn work_bail(context: &mut Context, goofs: &[Noun]) { - let stack = &mut context.nock_context.stack; - let lest = T(stack, goofs); - let lud = T(stack, &[lest, D(0)]); - context.work_bail(lud); -} - fn work_trace_name(stack: &mut NockStack, wire: Noun, vent: Atom) -> String { let wpc = path_to_cord(stack, wire); let wpc_len = met3_usize(wpc); diff --git a/rust/ares_crypto/Cargo.lock b/rust/ares_crypto/Cargo.lock index 4610dce3..cf47b036 100644 --- a/rust/ares_crypto/Cargo.lock +++ b/rust/ares_crypto/Cargo.lock @@ -61,7 +61,6 @@ dependencies = [ "rand", "sha1", "sha2", - "urcrypt-sys", "x25519-dalek", ] @@ -611,16 +610,6 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" -[[package]] -name = "urcrypt-sys" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced751f95a527a3458eb67c75e4ae7093d41585edaa7565f5769101502473019" -dependencies = [ - "bindgen", - "pkg-config", -] - [[package]] name = "version_check" version = "0.9.4" diff --git a/rust/ares_crypto/Cargo.toml b/rust/ares_crypto/Cargo.toml index e2b895bf..3ee2ab1e 100644 --- a/rust/ares_crypto/Cargo.toml +++ b/rust/ares_crypto/Cargo.toml @@ -6,9 +6,9 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -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 = "../rust-assert-no-alloc", features=["warn_debug"] } +assert_no_alloc = { path = "../rust-assert-no-alloc" } ibig = "0.3.6" # ed25519 @@ -25,13 +25,14 @@ sha1 = { version = "0.10.6", default-features = false, optional = true } sha2 = { version = "0.10.8", default-features = false, optional = true } # test_vs_urcrypt +# XX: can be removed once stable rand = { version = "0.8.4", default-features = false, features = ["getrandom"], optional = true } urcrypt-sys = { version = "0.1.1", optional = true } [features] -# XX turn off test_vs_urcrypt after development default = ["aes_siv", "ed25519", "sha"] aes_siv = ["aes", "aes-siv"] ed25519 = ["curve25519-dalek", "ed25519-dalek", "x25519-dalek"] sha = ["sha1", "sha2"] +# XX: can be removed once stable test_vs_urcrypt = ["urcrypt-sys", "rand"] diff --git a/rust/ares_guard/Cargo.lock b/rust/ares_guard/Cargo.lock new file mode 100644 index 00000000..26d68b9a --- /dev/null +++ b/rust/ares_guard/Cargo.lock @@ -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" diff --git a/rust/ares_guard/Cargo.toml b/rust/ares_guard/Cargo.toml new file mode 100644 index 00000000..5a3f7183 --- /dev/null +++ b/rust/ares_guard/Cargo.toml @@ -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" diff --git a/rust/ares_guard/build.rs b/rust/ares_guard/build.rs new file mode 100644 index 00000000..0c11bf3e --- /dev/null +++ b/rust/ares_guard/build.rs @@ -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!"); +} diff --git a/rust/ares_guard/c-src/guard.c b/rust/ares_guard/c-src/guard.c new file mode 100644 index 00000000..e296201a --- /dev/null +++ b/rust/ares_guard/c-src/guard.c @@ -0,0 +1,243 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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)) + +#ifdef __APPLE__ + #define GD_SIGNAL SIGBUS +#else + #define GD_SIGNAL SIGSEGV +#endif + +/** + * Linked list stack of jump buffers. + */ +typedef struct GD_buflistnode GD_buflistnode; +struct GD_buflistnode { + jmp_buf buffer; + GD_buflistnode *next; +}; + +/** + * Global guard page state. + */ +typedef struct GD_state GD_state; +struct GD_state { + uintptr_t guard_p; + const uintptr_t *stack_pp; + const uintptr_t *alloc_pp; + GD_buflistnode *buffer_list; + struct sigaction prev_sa; +}; + +static GD_state _gd_state = { + .guard_p = 0, + .stack_pp = NULL, + .alloc_pp = NULL, + .buffer_list = NULL, + .prev_sa = { .sa_sigaction = NULL, .sa_flags = 0 }, +}; + +static uint32_t +_protect_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 ; + } + + return 0; +} + +// Center the guard page. +static uint32_t +_focus_guard(GD_state *gd) +{ + uintptr_t stack_p = *(gd->stack_pp); + uintptr_t alloc_p = *(gd->alloc_pp); + uintptr_t old_guard_p = (gd->guard_p); + uintptr_t new_guard_p; + uint32_t err = 0; + + 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) { + return guard_oom; + } + + // Compute new guard page. + new_guard_p = GD_PAGE_ROUND_DOWN((stack_p + alloc_p) / 2); + if (new_guard_p == old_guard_p) { + return guard_oom; + } + + // Mark new guard page. + if ((err = _protect_page((void *)new_guard_p, PROT_NONE))) { + fprintf(stderr, "guard: focus: mark error\r\n"); + return err; + } + + // Update guard page tracker. + gd->guard_p = new_guard_p; + + // Unmark the old guard page if there is one. + if (old_guard_p) { + if ((err = _protect_page((void *)old_guard_p, PROT_READ | PROT_WRITE))) { + fprintf(stderr, "guard: focus: unmark error\r\n"); + return err; + } + } + + return 0; +} + +static void +_signal_handler(int sig, siginfo_t *si, void *unused) +{ + uintptr_t sig_addr; + uint32_t err = 0; + + assert(_gd_state.guard_p); + if (sig != GD_SIGNAL) { + fprintf(stderr, "guard: handler: invalid signal: %d\r\n", sig); + assert(0); + } + + sig_addr = (uintptr_t)si->si_addr; + + if (sig_addr >= _gd_state.guard_p && + sig_addr < _gd_state.guard_p + GD_PAGE_SIZE) + { + err = _focus_guard(&_gd_state); + if (err) { + siglongjmp(_gd_state.buffer_list->buffer, err); + } + } + else { + struct sigaction prev_sa = _gd_state.prev_sa; + + 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 handler + assert(0); + } + } +} + +// Registers the handler function. +static uint32_t +_register_handler(GD_state *gd) +{ + struct sigaction sa; + + sa.sa_flags = SA_SIGINFO; + sa.sa_sigaction = _signal_handler; + + if (sigaction(GD_SIGNAL, &sa, &(gd->prev_sa))) { + fprintf(stderr, "guard: register: sigaction error\r\n"); + fprintf(stderr, "%s\r\n", strerror(errno)); + return guard_sigaction; + } + + return 0; +} + +uint32_t +guard( + void *(*f)(void *), + void *closure, + const uintptr_t *const s_pp, + const uintptr_t *const a_pp, + void **ret +) { + GD_buflistnode *new_buffer; + uint32_t err = 0; + uint32_t td_err = 0; + + if (_gd_state.guard_p == 0) { + assert(_gd_state.buffer_list == NULL); + + _gd_state.stack_pp = s_pp; + _gd_state.alloc_pp = a_pp; + + // Initialize the guard page. + if ((err = _focus_guard(&_gd_state))) { + fprintf(stderr, "guard: initial focus error\r\n"); + goto exit; + } + + // Register guard page signal handler. + if ((err = _register_handler(&_gd_state))) { + fprintf(stderr, "guard: registration error\r\n"); + goto tidy; + } + } else { + assert(_gd_state.buffer_list != NULL); + } + + // Setup new longjmp buffer. + new_buffer = (GD_buflistnode *)malloc(sizeof(GD_buflistnode)); + if (new_buffer == NULL) { + fprintf(stderr, "guard: malloc error\r\n"); + fprintf(stderr, "%s\r\n", strerror(errno)); + err = guard_malloc; + goto skip; + } + new_buffer->next = _gd_state.buffer_list; + _gd_state.buffer_list = new_buffer; + + // Run given closure. + if (!(err = sigsetjmp(_gd_state.buffer_list->buffer, 1))) { + *ret = f(closure); + } + + // Restore previous longjmp buffer. + _gd_state.buffer_list = _gd_state.buffer_list->next; + free((void *)new_buffer); + +skip: + if (_gd_state.buffer_list == NULL) { + if (sigaction(GD_SIGNAL, &_gd_state.prev_sa, NULL)) { + fprintf(stderr, "guard: error replacing sigsegv handler\r\n"); + fprintf(stderr, "%s\r\n", strerror(errno)); + td_err = guard_sigaction; + + if (!err) { + err = td_err; + } + } + +tidy: + // Unmark guard page. + assert(_gd_state.guard_p != 0); + td_err = _protect_page((void *)_gd_state.guard_p, PROT_READ | PROT_WRITE); + if (td_err) { + fprintf(stderr, "guard: unmark error\r\n"); + fprintf(stderr, "%s\r\n", strerror(errno)); + if (!err) { + err = td_err; + } + } + _gd_state.guard_p = 0; + } + +exit: + return err; +} diff --git a/rust/ares_guard/c-src/guard.h b/rust/ares_guard/c-src/guard.h new file mode 100644 index 00000000..bcbffb26 --- /dev/null +++ b/rust/ares_guard/c-src/guard.h @@ -0,0 +1,73 @@ +#ifndef __GUARD_H__ +#define __GUARD_H__ + +#include +#include + +/** + * Error codes. + */ +typedef enum { + guard_null, // null stack or alloc pointer + guard_signal, // invalid signal + guard_oom, // out of memory + guard_malloc, // malloc error + guard_mprotect, // mprotect error + guard_sigaction, // sigaction error +} guard_err; + +/** + * @brief Executes the given callback function `f` within the memory arena + * between the stack and allocation pointers pointed to by `s_pp` and `a_pp`, + * with guard page protection. If `f`'s execution succeeds, its result is + * written to the return pointer `*ret`. If `f`'s execution triggers an + * out of memory error or any other `guard_err`, the `guard_err` is + * returned and `*ret` is left empty. In either case, cleanup is performed + * before returning. + * + * Definitions: + * - A guard page is marked `PROT_NONE`. + * + * Assumptions: + * - `NockStack` pages are marked `PROT_READ|PROT_WRITE` by default. + * - All memory access patterns are outside-in. + * - Callback functions are compatible with the C ABI. + * - `NockStack` stack and allocation pointer locations are fixed. + * - The caller is responsible for return value memory allocation. + * - The caller is responsible for managing any external state the callback + * function may mutate. + * - The callback function may be interrupted in the case of memory exhaustion + * or other `guard_err` error (failure to `mprotect`, `malloc`, etc.). + * - `SIGSEGV` (`SIGBUS` on macOS) signals are expected to be raised only on + * guard page accesses. + * + * Invariants: + * - A single guard page is installed and maintained in the approximate center + * until `crate::guard::call_with_guard` returns. + * - A return value is only written to `*ret` on successful callback execution. + * - A `guard_err` is returned. + * + * Enhancements: + * - Use only a single, static jump buffer variable instead of a linked list. + * We currently use a linked list of jump buffers because we don't have a + * function for preserving stack traces across `crate::interpreter::interpret` + * calls. + * + * @param f The callback function to execute. + * @param closure A pointer to the closure data for the callback function. + * @param s_pp A pointer to the stack pointer location. + * @param a_pp A pointer to the allocation pointer location. + * @param ret A pointer to a location where the callback's result can be stored. + * + * @return 0 on callback success; otherwise `guard_err` error code. + */ +uint32_t +guard( + void *(*f)(void *), + void *closure, + const uintptr_t *const s_pp, + const uintptr_t *const a_pp, + void **ret +); + +#endif // __GUARD_H__ diff --git a/rust/ares_guard/c-src/wrapper.h b/rust/ares_guard/c-src/wrapper.h new file mode 100644 index 00000000..29d14dd2 --- /dev/null +++ b/rust/ares_guard/c-src/wrapper.h @@ -0,0 +1 @@ +#include "guard.h" diff --git a/rust/ares_guard/src/lib.rs b/rust/ares_guard/src/lib.rs new file mode 100644 index 00000000..f7b1089a --- /dev/null +++ b/rust/ares_guard/src/lib.rs @@ -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; diff --git a/rust/rust-assert-no-alloc/Cargo.toml b/rust/rust-assert-no-alloc/Cargo.toml new file mode 100644 index 00000000..004f51c5 --- /dev/null +++ b/rust/rust-assert-no-alloc/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "assert_no_alloc" +version = "1.1.2" +authors = ["Florian Jung "] +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 = [] +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"] diff --git a/rust/rust-assert-no-alloc/LICENSE b/rust/rust-assert-no-alloc/LICENSE new file mode 100644 index 00000000..c108e4ad --- /dev/null +++ b/rust/rust-assert-no-alloc/LICENSE @@ -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 + +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. diff --git a/rust/rust-assert-no-alloc/README.md b/rust/rust-assert-no-alloc/README.md new file mode 100644 index 00000000..8411b1c9 --- /dev/null +++ b/rust/rust-assert-no-alloc/README.md @@ -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 +``` diff --git a/rust/rust-assert-no-alloc/examples/main.rs b/rust/rust-assert-no-alloc/examples/main.rs new file mode 100644 index 00000000..7f43d579 --- /dev/null +++ b/rust/rust-assert-no-alloc/examples/main.rs @@ -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."); +} diff --git a/rust/rust-assert-no-alloc/src/lib.rs b/rust/rust-assert-no-alloc/src/lib.rs new file mode 100644 index 00000000..8192d560 --- /dev/null +++ b/rust/rust-assert-no-alloc/src/lib.rs @@ -0,0 +1,258 @@ +/* assert_no_alloc -- A custom Rust allocator allowing to temporarily disable + * memory (de)allocations for a thread. + * + * Copyright (c) 2020 Florian Jung + * + * 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 = Cell::new(0); + static ALLOC_PERMIT_COUNT: Cell = Cell::new(0); + + #[cfg(any( all(feature="warn_debug", debug_assertions), all(feature="warn_release", not(debug_assertions)) ))] + static ALLOC_VIOLATION_COUNT: Cell = Cell::new(0); +} + +#[cfg(all(feature = "disable_release", not(debug_assertions)))] // if disabled +pub fn assert_no_alloc T> (func: F) -> T { // no-op + func() +} + +#[cfg(all(feature = "disable_release", not(debug_assertions)))] // if disabled +pub fn permit_alloc 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> (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; +} + +/// Calls the `func` closure, but ensures that the forbid and permit counters +/// are maintained accurately even if a longjmp originates and terminates +/// within the closure. If you longjmp over this function, we can't fix +/// anything about it. +pub fn ensure_alloc_counters T> (func: F) -> T { + let forbid_counter = ALLOC_FORBID_COUNT.with(|c| c.get()); + let permit_counter = ALLOC_PERMIT_COUNT.with(|c| c.get()); + + let ret = func(); + + ALLOC_FORBID_COUNT.with(|c| c.set(forbid_counter)); + ALLOC_PERMIT_COUNT.with(|c| c.set(permit_counter)); + + 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> (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)); +} + + + + +#[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(Option); + +impl PermitDrop { + pub fn new(t: T) -> PermitDrop { + permit_alloc(|| { + PermitDrop(Some(t)) + }) + } +} + +impl std::ops::Deref for PermitDrop { + type Target = T; + fn deref(&self) -> &T { self.0.as_ref().unwrap() } +} + +impl std::ops::DerefMut for PermitDrop { + fn deref_mut(&mut self) -> &mut T { self.0.as_mut().unwrap() } +} + +impl Iterator for PermitDrop { + type Item = I::Item; + fn next(&mut self) -> Option { + (**self).next() + } +} + + +impl Drop for PermitDrop { + fn drop(&mut self) { + let mut tmp = None; + std::mem::swap(&mut tmp, &mut self.0); + permit_alloc(|| { + std::mem::drop(tmp); + }); + } +} diff --git a/rust/rust-assert-no-alloc/tests/test.rs b/rust/rust-assert-no-alloc/tests/test.rs new file mode 100644 index 00000000..d5f6c8da --- /dev/null +++ b/rust/rust-assert-no-alloc/tests/test.rs @@ -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 = 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); +}