1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
//! Custom symbol demangling support.
//!
//! By default, Tracy demangles symbols using the C++ ABI, which is not fully compatible with Rust
//! symbol mangling.
//!
//! With the `demangle` feature enabled, clients must register a custom demangling function.
//! This can be done by calling the [`register_demangler!`][macro] macro with either no arguments
//! to use the [default demangler](default), or with a path to a custom demangler function. See
//! [its documentation][macro] for how to use it.
//!
//! Note that only one demangler can be registered at a time. Attempting to register multiple
//! demanglers will result in a linking failure due to multiple definitions of the underlying
//! `extern "C"` function.
//!
//! [macro]: crate::register_demangler
use std::fmt;
/// Opaque buffer used to write demangled symbols.
///
/// The only exposed API is currently [`fmt::Write`].
///
/// See [the module-level documentation](self) for more information.
pub struct Buffer(String);
impl fmt::Write for Buffer {
#[inline]
fn write_str(&mut self, s: &str) -> fmt::Result {
self.0.write_str(s)
}
#[inline]
fn write_char(&mut self, c: char) -> fmt::Result {
self.0.write_char(c)
}
}
impl Buffer {
const fn new() -> Self {
Self(String::new())
}
fn clear_on_err<T, E>(&mut self, f: impl FnOnce(&mut Self) -> Result<T, E>) -> Result<T, E> {
let r = f(self);
if r.is_err() {
self.0.clear();
}
r
}
}
/// Demangles a Rust symbol using [`rustc_demangle`].
///
/// See [the module-level documentation](self) for more information.
pub fn default(s: &str, buffer: &mut impl fmt::Write) -> fmt::Result {
let Ok(demangled) = rustc_demangle::try_demangle(s) else {
return Err(fmt::Error);
};
// Use `:#` formatting to elide the hash.
write!(buffer, "{demangled:#}")
}
/// Symbol demangler that does nothing.
///
/// See [the module-level documentation](self) for more information.
pub fn noop(_: &str, _: &mut impl fmt::Write) -> fmt::Result {
Err(fmt::Error)
}
pub(super) mod internal {
use super::Buffer;
use std::ffi::c_char;
use std::fmt::{self, Write};
use std::ptr::null;
/// Demangling glue.
pub unsafe fn implementation<F>(mangled: *const c_char, run: F) -> *const c_char
where
F: FnOnce(&str, &mut Buffer) -> fmt::Result,
{
// https://github.com/wolfpld/tracy/blob/d4a4b623968d99a7403cd93bae5247ed0735680a/public/client/TracyCallstack.cpp#L57-L67
// > The demangling function is responsible for managing memory for this string.
// > It is expected that it will be internally reused.
// > When a call to ___tracy_demangle is made, previous contents of the string memory
// > do not need to be preserved.
static mut BUFFER: Buffer = Buffer::new();
if mangled.is_null() {
return null();
}
let cstr = unsafe { std::ffi::CStr::from_ptr(mangled) };
let Ok(str) = cstr.to_str() else {
return null();
};
let buffer = unsafe { &mut *std::ptr::addr_of_mut!(BUFFER) };
buffer.0.clear();
let result = buffer.clear_on_err(|buffer| {
run(str, buffer)?;
match buffer.0.as_bytes().split_last() {
None | Some((&0, [])) => return Err(fmt::Error),
Some((_, v)) if v.contains(&0) => return Err(fmt::Error),
Some((&0, _)) => return Ok(()),
_ => (),
}
buffer.write_char('\0')?;
Ok(())
});
match result {
Ok(()) => {
debug_assert_eq!(buffer.0.as_bytes().last().copied(), Some(0));
buffer.0.as_ptr().cast()
}
Err(fmt::Error) => null(),
}
}
}
/// Registers a custom demangler function.
///
/// A [default implementation](default) for demangling Rust symbols can be registered by passing no
/// arguments.
///
/// Custom implementations can be registered by passing a function with the following signature:
/// `fn(mangled: &str, buffer: &mut tracy_client::demangle::Buffer) -> std::fmt::Result`
///
/// Custom demanglers:
/// - Must not write null bytes to the buffer.
/// - Returning `Err` or leaving the buffer unchanged will result in the symbol being displayed as-is.
///
/// See [the module-level documentation](self) for more information.
///
/// # Examples
///
/// ```
/// use tracy_client::{demangle, register_demangler};
///
/// // Register the default demangler.
/// # #[cfg(any())]
/// register_demangler!();
///
/// // Register a noop demangler.
/// # #[cfg(any())]
/// register_demangler!(demangle::noop);
///
/// // Register a custom demangler.
/// # #[cfg(any())]
/// register_demangler!(my_demangler);
///
/// fn my_demangler(s: &str, buffer: &mut demangle::Buffer) -> std::fmt::Result {
/// // Custom demangling logic...
/// use std::fmt::Write;
/// write!(buffer, "{s}")?;
/// Ok(())
/// }
/// ```
#[macro_export]
macro_rules! register_demangler {
() => {
$crate::register_demangler!($crate::internal::demangle::default);
};
($path:path) => {
const _: () = {
#[no_mangle]
unsafe extern "C" fn ___tracy_demangle(
mangled: *const std::ffi::c_char,
) -> *const std::ffi::c_char {
unsafe { $crate::internal::demangle::implementation(mangled, $path) }
}
};
};
}