Merge pull request #202 from urbit/msl/guard

guard page and `bail:meme`
This commit is contained in:
Matthew LeVan 2024-02-21 11:23:55 -05:00 committed by GitHub
commit 7316368e29
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 730 additions and 688 deletions

10
rust/ares/Cargo.lock generated
View File

@ -62,7 +62,7 @@ dependencies = [
"ares_guard",
"ares_macros",
"ares_pma",
"assert_no_alloc 1.1.2",
"assert_no_alloc",
"autotools",
"bitvec",
"cc",
@ -87,7 +87,7 @@ version = "0.1.0"
dependencies = [
"aes",
"aes-siv",
"assert_no_alloc 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"assert_no_alloc",
"curve25519-dalek",
"ed25519-dalek",
"ibig 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
@ -124,12 +124,6 @@ dependencies = [
name = "assert_no_alloc"
version = "1.1.2"
[[package]]
name = "assert_no_alloc"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55ca83137a482d61d916ceb1eba52a684f98004f18e0cafea230fe5579c178a3"
[[package]]
name = "atty"
version = "0.2.14"

View File

@ -18,8 +18,8 @@ ares_macros = { path = "../ares_macros" }
# ares_pma = { path = "../ares_pma", features=["debug_prints"] }
ares_pma = { path = "../ares_pma" }
# use this when debugging requires allocation (e.g. eprintln)
# assert_no_alloc = { path = "../assert-no-alloc", features=["warn_debug"] }
assert_no_alloc = { path = "../assert-no-alloc" }
# 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"

View File

@ -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,7 +401,11 @@ 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 {
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 {
@ -687,7 +692,9 @@ pub fn interpret(context: &mut Context, mut subject: Noun, formula: Noun) -> Res
// '.*', 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) {
if let Some(path) =
context.cold.matches(stack, &mut res)
{
append_trace(stack, path);
};
};
@ -712,7 +719,9 @@ pub fn interpret(context: &mut Context, mut subject: Noun, formula: Noun) -> Res
// '.*', 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) {
if let Some(path) =
context.cold.matches(stack, &mut res)
{
append_trace(stack, path);
};
};
@ -748,16 +757,21 @@ pub fn interpret(context: &mut Context, mut subject: Noun, formula: Noun) -> Res
push_formula(&mut context.stack, diet.patch, false)?;
}
Todo10::Edit => {
res = edit(&mut context.stack, diet.axis.as_bitslice(), res, diet.tree);
res = edit(
&mut context.stack,
diet.axis.as_bitslice(),
res,
diet.tree,
);
context.stack.pop::<NockWork>();
}
}
}
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)
{
if let Some(ret) = hint::match_pre_hint(
context, subject, dint.tag, dint.hint, dint.body,
) {
match ret {
Ok(found) => {
res = found;
@ -817,9 +831,9 @@ pub fn interpret(context: &mut Context, mut subject: Noun, formula: Noun) -> Res
},
NockWork::Work11S(mut sint) => match sint.todo {
Todo11S::ComputeResult => {
if let Some(ret) =
hint::match_pre_nock(context, subject, sint.tag, None, sint.body)
{
if let Some(ret) = hint::match_pre_nock(
context, subject, sint.tag, None, sint.body,
) {
match ret {
Ok(found) => {
res = found;
@ -840,9 +854,9 @@ pub fn interpret(context: &mut Context, mut subject: Noun, formula: Noun) -> Res
}
}
Todo11S::Done => {
if let Some(found) =
hint::match_post_nock(context, subject, sint.tag, None, sint.body, res)
{
if let Some(found) = hint::match_post_nock(
context, subject, sint.tag, None, sint.body, res,
) {
res = found;
}
context.stack.pop::<NockWork>();
@ -877,7 +891,8 @@ pub fn interpret(context: &mut Context, mut subject: Noun, formula: Noun) -> Res
scry_gate.tail().as_cell()?.tail(),
],
);
let scry_form = T(&mut context.stack, &[D(9), D(2), D(1), scry_core]);
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
@ -892,11 +907,18 @@ pub fn interpret(context: &mut Context, mut subject: Noun, formula: Noun) -> Res
break Err(Error::ScryCrashed(D(0)));
}
}
Right(cell) => match cell.tail().as_either_atom_cell() {
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]);
let hunk = T(
stack,
&[
D(tas!(b"hunk")),
scry.reff,
scry.path,
],
);
mean_push(stack, hunk);
break Err(Error::ScryCrashed(D(0)));
}
@ -905,10 +927,12 @@ pub fn interpret(context: &mut Context, mut subject: Noun, formula: Noun) -> Res
context.scry_stack = scry_stack;
context.stack.pop::<NockWork>();
}
},
}
}
},
Err(error) => match error {
Error::Deterministic(_, trace) | Error::ScryCrashed(trace) => {
Error::Deterministic(_, trace)
| Error::ScryCrashed(trace) => {
break Err(Error::ScryCrashed(trace));
}
Error::NonDeterministic(_, _) => {
@ -927,6 +951,10 @@ pub fn interpret(context: &mut Context, mut subject: Noun, formula: Noun) -> Res
},
};
}
};
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])
}
};

View File

@ -1,4 +1,3 @@
use crate::guard::call_with_guard;
use crate::hamt::Hamt;
use crate::interpreter;
use crate::interpreter::{inc, interpret, Error, Mote};
@ -403,27 +402,7 @@ fn slam(context: &mut Context, axis: u64, ovo: Noun) -> Result<Noun, Error> {
let fol = T(stack, &[D(8), pul, D(9), D(2), D(10), sam, D(0), D(2)]);
let sub = T(stack, &[arvo, ovo]);
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
interpret(&mut context.nock_context, sub, fol)
}
fn peek(context: &mut Context, ovo: Noun) -> Noun {

View File

@ -7,8 +7,8 @@ edition = "2021"
[dependencies]
# use this when debugging requires allocation (e.g. eprintln)
# assert_no_alloc = {version="1.1.2", features=["warn_debug"]}
assert_no_alloc = "1.1.2"
# assert_no_alloc = { path = "../rust-assert-no-alloc", features=["warn_debug"] }
assert_no_alloc = { path = "../rust-assert-no-alloc" }
ibig = "0.3.6"
# ed25519

View File

@ -22,54 +22,56 @@
#endif
/**
* XX: documentation
* Linked list stack of jump buffers.
*/
typedef struct _GD_state GD_state;
struct _GD_state {
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;
jmp_buf env_buffer;
GD_buflistnode *buffer_list;
struct sigaction prev_sa;
};
static GD_state *_guard_state = NULL;
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
_prot_page(void *address, int prot)
_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 | errno;
return guard_mprotect ;
}
return 0;
}
// Center the guard page.
static uint32_t
_mark_page(void *address)
_focus_guard(GD_state *gd)
{
return _prot_page(address, PROT_NONE);
}
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;
static uint32_t
_unmark_page(void *address)
{
return _prot_page(address, PROT_READ | PROT_WRITE);
}
/**
* Center the guard page.
*/
static uint32_t
_focus_guard(GD_state *gs) {
uintptr_t *guard_pp = &(gs->guard_p);
uintptr_t stack_p = *(gs->stack_pp);
uintptr_t alloc_p = *(gs->alloc_pp);
// Check anomalous arguments
if (stack_p == 0 || alloc_p == 0) {
fprintf(stderr, "guard: focus: stack or alloc pointer is null\r\n");
return guard_null;
@ -77,30 +79,25 @@ _focus_guard(GD_state *gs) {
return guard_oom;
}
uintptr_t old_guard_p = *guard_pp;
uintptr_t new_guard_p;
int32_t err = 0;
// Compute new guard page
// XX: Should we also check for new_guard_p < min(stack_p, alloc_p)?
// 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 = _mark_page((void *)new_guard_p))) {
fprintf(stderr, "guard: focus: mark error %p\r\n", (void *)new_guard_p);
// 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
*guard_pp = new_guard_p;
// Update guard page tracker.
gd->guard_p = new_guard_p;
// Unmark the old guard page (if there is one)
// 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, %p\r\n", (void *)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;
}
}
@ -108,58 +105,55 @@ _focus_guard(GD_state *gs) {
return 0;
}
void
static void
_signal_handler(int sig, siginfo_t *si, void *unused)
{
uintptr_t sig_addr;
int32_t err = 0;
uint32_t err = 0;
assert(sig == GD_SIGNAL);
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 >= _guard_state->guard_p &&
sig_addr < (_guard_state->guard_p + GD_PAGE_SIZE))
if (sig_addr >= _gd_state.guard_p &&
sig_addr < _gd_state.guard_p + GD_PAGE_SIZE)
{
err = _focus_guard(_guard_state);
err = _focus_guard(&_gd_state);
if (err) {
siglongjmp(_guard_state->env_buffer, err);
siglongjmp(_gd_state.buffer_list->buffer, err);
}
} else {
struct sigaction prev_sa = _guard_state->prev_sa;
}
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 SIGSEGV handler
// There should always be a default handler
assert(0);
}
}
}
uint32_t
_register_handler(GD_state *gs)
// Registers the handler function.
static uint32_t
_register_handler(GD_state *gd)
{
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
// XX: 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(GD_SIGNAL, &sa, &(gs->prev_sa))) {
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 | errno;
return guard_sigaction;
}
return 0;
@ -167,66 +161,83 @@ _register_handler(GD_state *gs)
uint32_t
guard(
callback f,
void *(*f)(void *),
void *closure,
const uintptr_t *const stack_pp,
const uintptr_t *const alloc_pp,
const uintptr_t *const s_pp,
const uintptr_t *const a_pp,
void **ret
) {
int32_t err = 0;
int32_t err_c = 0;
GD_buflistnode *new_buffer;
uint32_t err = 0;
uint32_t td_err = 0;
//
// Setup guard page state
//
if (_gd_state.guard_p == 0) {
assert(_gd_state.buffer_list == NULL);
// guard() presumes that it is only ever called once at a time
assert(_guard_state == NULL);
_gd_state.stack_pp = s_pp;
_gd_state.alloc_pp = a_pp;
_guard_state = (GD_state *)malloc(sizeof(GD_state));
if (_guard_state == NULL) {
// 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));
return guard_malloc | errno;
err = guard_malloc;
goto skip;
}
_guard_state->guard_p = 0;
_guard_state->stack_pp = stack_pp;
_guard_state->alloc_pp = alloc_pp;
new_buffer->next = _gd_state.buffer_list;
_gd_state.buffer_list = new_buffer;
// Initialize the guard page
if ((err = _focus_guard(_guard_state))) {
goto clear;
}
// Register guard page signal handler
if ((err = _register_handler(_guard_state))) {
goto unmark;
}
//
// Run closure
//
if (!(err = sigsetjmp(_guard_state->env_buffer, 1))) {
// Run given closure.
if (!(err = sigsetjmp(_gd_state.buffer_list->buffer, 1))) {
*ret = f(closure);
}
//
// Clean up guard page state
//
// Restore previous longjmp buffer.
_gd_state.buffer_list = _gd_state.buffer_list->next;
free((void *)new_buffer);
if (sigaction(GD_SIGNAL, &(_guard_state->prev_sa), NULL)) {
fprintf(stderr, "guard: sigaction error\r\n");
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));
err_c = guard_sigaction | errno;
td_err = guard_sigaction;
if (!err) {
err = td_err;
}
}
unmark:
err_c = _unmark_page((void *)_guard_state->guard_p);
clear:
free(_guard_state);
_guard_state = NULL;
return err ? err : err_c;
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;
}

View File

@ -1,57 +1,72 @@
#ifndef __GUARD_H__
#define __GUARD_H__
#include <setjmp.h>
#include <stdint.h>
/**
* Error codes and flags.
*
* The flags are bitwise added to the errno of their respective errors.
* Error codes.
*/
typedef enum {
guard_null = 1, // null stack or alloc pointer
guard_oom, // OOM
guard_malloc = 0x10000000, // malloc error flag
guard_mprotect = 0x20000000, // mprotect error flag
guard_sigaction = 0x40000000, // sigaction error flag
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;
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.
* @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.
*
* 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.
* Definitions:
* - A guard page is marked `PROT_NONE`.
*
* 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`.
* 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.
*
* 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`.
* 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(
callback f,
void *(*f)(void *),
void *closure,
const uintptr_t *const stack_pp,
const uintptr_t *const alloc_pp,
const uintptr_t *const s_pp,
const uintptr_t *const a_pp,
void **ret
);

View File

@ -5,6 +5,7 @@
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;

View File

@ -76,6 +76,7 @@ STATIC_ASSERT(0, "debugger break instruction unimplemented");
/* the opposite of P2BYTES */
#define B2PAGES(x) ((size_t)(x) >> BT_PAGEBITS)
#define __packed __attribute__((__packed__))
#define UNUSED(x) ((void)(x))
@ -94,6 +95,7 @@ STATIC_ASSERT(0, "debugger break instruction unimplemented");
/* given a pointer p returns the low page-aligned addr */
#define LO_ALIGN_PAGE(p) ((BT_page *)(((uintptr_t)p) & ~(BT_PAGESIZE - 1)))
#define BT_MAPADDR ((BYTE *) S(0x1000,0000,0000))
static inline vaof_t
@ -339,6 +341,7 @@ struct BT_state {
/*
//// ===========================================================================
//// btree internal routines

View File

@ -12,6 +12,7 @@ keywords = ["allocator", "real-time", "debug", "audio"]
categories = ["development-tools::debugging"]
[features]
default = []
warn_debug = []
warn_release = []
disable_release = []

View File

@ -91,6 +91,22 @@ pub fn assert_no_alloc<T, F: FnOnce() -> T> (func: F) -> T {
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, F: FnOnce() -> 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.
@ -132,13 +148,6 @@ 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));
}