use backtrace::BacktraceFrame;
use std::{cell::Cell, ffi::c_void, fmt, ptr};
mod symbol;
mod tree;
use symbol::Symbol;
use tree::Tree;
type Backtrace = Vec<BacktraceFrame>;
type SymbolTrace = Vec<Symbol>;
#[derive(Clone)]
pub struct Trace {
backtraces: Vec<Backtrace>,
}
struct Context {
root_addr: *const c_void,
trace: Trace,
}
impl Trace {
pub fn root<F, R>(f: F) -> (R, Trace)
where
F: FnOnce() -> R,
{
Context::with_current(|cell| Self::root_inner(f, cell))
}
#[inline(never)]
fn root_inner<F, R>(f: F, cell: &Cell<Option<Context>>) -> (R, Trace)
where
F: FnOnce() -> R,
{
cell.set(Some(Context::new::<F, R>()));
let _deferred = defer(|| {
cell.set(None);
});
let result = f();
let context = cell.take().unwrap();
(result, context.trace)
}
#[inline(never)]
pub fn leaf() {
Context::with_current(|context_cell| {
if let Some(mut context) = context_cell.take() {
let mut frames = vec![];
let mut above_leaf = false;
backtrace::trace(|frame| {
let below_root = !ptr::eq(frame.symbol_address(), context.root_addr);
if above_leaf && below_root {
frames.push(frame.to_owned().into());
}
if ptr::eq(frame.symbol_address(), Self::leaf as *const _) {
above_leaf = true;
}
below_root
});
context.trace.backtraces.push(frames);
context_cell.set(Some(context));
}
});
}
}
impl fmt::Display for Trace {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Tree::from_trace(self.clone()).fmt(f)
}
}
impl Context {
fn new<F, R>() -> Self
where
F: FnOnce() -> R,
{
let root_addr = Trace::root_inner::<F, R> as *const c_void;
Self {
root_addr,
trace: Trace { backtraces: vec![] },
}
}
fn with_current<F, R>(f: F) -> R
where
F: FnOnce(&Cell<Option<Context>>) -> R,
{
thread_local! {
#[allow(clippy::declare_interior_mutable_const)]
static CURRENT_CONTEXT: Cell<Option<Context>> = const { Cell::new(None) };
}
CURRENT_CONTEXT.with(f)
}
}
fn defer<F: FnOnce() -> R, R>(f: F) -> impl Drop {
use std::mem::ManuallyDrop;
struct Defer<F: FnOnce() -> R, R>(ManuallyDrop<F>);
impl<F: FnOnce() -> R, R> Drop for Defer<F, R> {
#[inline(always)]
fn drop(&mut self) {
unsafe {
ManuallyDrop::take(&mut self.0)();
}
}
}
Defer(ManuallyDrop::new(f))
}