use std::cell::Cell;
use std::ffi::{CStr, CString, c_void};
use std::os::raw::c_char;
use std::sync::atomic::{AtomicU64, Ordering};
use crate::ffi::{
ErrorContext as FfiErrorContext, noesis_get_allocated_memory,
noesis_get_allocated_memory_accum, noesis_get_allocations_count, noesis_invoke_assert_handler,
noesis_invoke_error_handler, noesis_set_assert_handler, noesis_set_error_handler,
noesis_set_thread_error_handler,
};
static NEXT_REG_ID: AtomicU64 = AtomicU64::new(1);
fn next_reg_id() -> u64 {
NEXT_REG_ID.fetch_add(1, Ordering::Relaxed)
}
static ERROR_ACTIVE: AtomicU64 = AtomicU64::new(0);
static ASSERT_ACTIVE: AtomicU64 = AtomicU64::new(0);
thread_local! {
static THREAD_ERROR_ACTIVE: Cell<u64> = const { Cell::new(0) };
}
unsafe fn cstr(p: *const c_char) -> String {
if p.is_null() {
String::new()
} else {
unsafe { CStr::from_ptr(p) }.to_string_lossy().into_owned()
}
}
unsafe fn cstr_opt(p: *const c_char) -> Option<String> {
if p.is_null() {
None
} else {
Some(unsafe { CStr::from_ptr(p) }.to_string_lossy().into_owned())
}
}
#[derive(Clone, Debug, PartialEq, Eq, Default)]
pub struct ErrorContext {
pub uri: Option<String>,
pub line: u32,
pub column: u32,
}
type ErrorClosure = Box<dyn Fn(&str, u32, &str, bool) + Send + 'static>;
unsafe extern "C" fn error_trampoline(
userdata: *mut c_void,
file: *const c_char,
line: u32,
message: *const c_char,
fatal: bool,
) {
crate::panic_guard::guard(|| {
let closure = unsafe { &*userdata.cast::<ErrorClosure>() };
let file = unsafe { cstr(file) };
let message = unsafe { cstr(message) };
closure(&file, line, &message, fatal);
})
}
#[must_use = "dropping the guard immediately uninstalls the handler"]
pub struct ErrorHandlerGuard {
boxed: *mut ErrorClosure,
id: u64,
}
impl Drop for ErrorHandlerGuard {
fn drop(&mut self) {
if ERROR_ACTIVE
.compare_exchange(self.id, 0, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
unsafe {
noesis_set_error_handler(
None,
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
);
}
}
unsafe { drop(Box::from_raw(self.boxed)) };
}
}
pub fn set_error_handler<F>(handler: F) -> ErrorHandlerGuard
where
F: Fn(&str, u32, &str, bool) + Send + 'static,
{
let boxed: *mut ErrorClosure = Box::into_raw(Box::new(Box::new(handler)));
let id = next_reg_id();
ERROR_ACTIVE.store(id, Ordering::Release);
unsafe {
noesis_set_error_handler(
Some(error_trampoline),
boxed.cast(),
std::ptr::null_mut(),
std::ptr::null_mut(),
);
}
ErrorHandlerGuard { boxed, id }
}
type AssertClosure = Box<dyn Fn(&str, u32, &str) -> bool + Send + 'static>;
unsafe extern "C" fn assert_trampoline(
userdata: *mut c_void,
file: *const c_char,
line: u32,
expr: *const c_char,
) -> bool {
crate::panic_guard::guard(|| {
let closure = unsafe { &*userdata.cast::<AssertClosure>() };
let file = unsafe { cstr(file) };
let expr = unsafe { cstr(expr) };
closure(&file, line, &expr)
})
}
#[must_use = "dropping the guard immediately uninstalls the handler"]
pub struct AssertHandlerGuard {
boxed: *mut AssertClosure,
id: u64,
}
impl Drop for AssertHandlerGuard {
fn drop(&mut self) {
if ASSERT_ACTIVE
.compare_exchange(self.id, 0, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
unsafe {
noesis_set_assert_handler(
None,
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
);
}
}
unsafe { drop(Box::from_raw(self.boxed)) };
}
}
pub fn set_assert_handler<F>(handler: F) -> AssertHandlerGuard
where
F: Fn(&str, u32, &str) -> bool + Send + 'static,
{
let boxed: *mut AssertClosure = Box::into_raw(Box::new(Box::new(handler)));
let id = next_reg_id();
ASSERT_ACTIVE.store(id, Ordering::Release);
unsafe {
noesis_set_assert_handler(
Some(assert_trampoline),
boxed.cast(),
std::ptr::null_mut(),
std::ptr::null_mut(),
);
}
AssertHandlerGuard { boxed, id }
}
type Error2Closure = Box<dyn Fn(&str, u32, &str, bool, Option<&ErrorContext>) + Send + 'static>;
unsafe extern "C" fn error2_trampoline(
file: *const c_char,
line: u32,
message: *const c_char,
fatal: bool,
context: *mut FfiErrorContext,
userdata: *mut c_void,
) {
crate::panic_guard::guard(|| {
let closure = unsafe { &*userdata.cast::<Error2Closure>() };
let file = unsafe { cstr(file) };
let message = unsafe { cstr(message) };
let ctx = if context.is_null() {
None
} else {
let c = unsafe { &*context };
Some(ErrorContext {
uri: unsafe { cstr_opt(c.uri) },
line: c.line,
column: c.column,
})
};
closure(&file, line, &message, fatal, ctx.as_ref());
})
}
#[must_use = "dropping the guard immediately uninstalls the handler"]
pub struct ThreadErrorHandlerGuard {
boxed: *mut Error2Closure,
id: u64,
}
impl Drop for ThreadErrorHandlerGuard {
fn drop(&mut self) {
let still_active = THREAD_ERROR_ACTIVE.with(|c| {
if c.get() == self.id {
c.set(0);
true
} else {
false
}
});
if still_active {
unsafe {
noesis_set_thread_error_handler(
None,
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
);
}
}
unsafe { drop(Box::from_raw(self.boxed)) };
}
}
pub fn set_thread_error_handler<F>(handler: F) -> ThreadErrorHandlerGuard
where
F: Fn(&str, u32, &str, bool, Option<&ErrorContext>) + Send + 'static,
{
let boxed: *mut Error2Closure = Box::into_raw(Box::new(Box::new(handler)));
let id = next_reg_id();
THREAD_ERROR_ACTIVE.with(|c| c.set(id));
unsafe {
noesis_set_thread_error_handler(
Some(error2_trampoline),
boxed.cast(),
std::ptr::null_mut(),
std::ptr::null_mut(),
);
}
ThreadErrorHandlerGuard { boxed, id }
}
pub fn invoke_error(file: &str, line: u32, fatal: bool, message: &str) {
let cf = CString::new(file).expect("file contained interior NUL");
let cm = CString::new(message).expect("message contained interior NUL");
unsafe {
noesis_invoke_error_handler(
cf.as_ptr(),
line,
fatal,
false,
std::ptr::null(),
0,
0,
cm.as_ptr(),
);
}
}
pub fn invoke_error_with_context(
file: &str,
line: u32,
fatal: bool,
uri: &str,
ctx_line: u32,
ctx_col: u32,
message: &str,
) {
let cf = CString::new(file).expect("file contained interior NUL");
let cu = CString::new(uri).expect("uri contained interior NUL");
let cm = CString::new(message).expect("message contained interior NUL");
unsafe {
noesis_invoke_error_handler(
cf.as_ptr(),
line,
fatal,
true,
cu.as_ptr(),
ctx_line,
ctx_col,
cm.as_ptr(),
);
}
}
#[must_use]
pub fn invoke_assert(file: &str, line: u32, expr: &str) -> bool {
let cf = CString::new(file).expect("file contained interior NUL");
let ce = CString::new(expr).expect("expr contained interior NUL");
unsafe { noesis_invoke_assert_handler(cf.as_ptr(), line, ce.as_ptr()) }
}
#[must_use]
pub fn allocated_memory() -> u32 {
unsafe { noesis_get_allocated_memory() }
}
#[must_use]
pub fn allocated_memory_accum() -> u32 {
unsafe { noesis_get_allocated_memory_accum() }
}
#[must_use]
pub fn allocations_count() -> u32 {
unsafe { noesis_get_allocations_count() }
}