extern crate rustc_demangle; use std::alloc::{GlobalAlloc, Layout, System}; use std::io::Write; use std::os::raw::{c_char, c_int}; use std::ptr; use std::result; type Result = result::Result; /// Convenience function to set return status if a location was provided. unsafe fn set_status(status: *mut c_int, val: c_int) { if !status.is_null() { *status = val; } } /// Region from the system allocator for demangler output. We use the /// system allocator because the intended client is C/C++ code which /// may not be using the Rust allocator. struct SystemBuffer { buf: *mut u8, size: usize, size_out: *mut usize, } impl SystemBuffer { const DEFAULT_BUFFER_SIZE: usize = 1024; fn new(size: usize) -> Result { let buf = unsafe { System.alloc_zeroed(Layout::from_size_align_unchecked(size, 1)) }; if buf.is_null() { Err(Status::AllocFailure) } else { Ok(Self { buf, size, size_out: ptr::null_mut(), }) } } /// Safety: If buf is non-null, size must be non-null and point to the /// non-zero size of the buffer provided in buf. /// Takes ownership of the buffer passed in (and may reallocate it). /// size must outlive the resulting buffer if non-null. unsafe fn from_raw(buf: *mut c_char, size: *mut usize) -> Result { if buf.is_null() { if !size.is_null() { *size = Self::DEFAULT_BUFFER_SIZE; } let fresh = Self::new(Self::DEFAULT_BUFFER_SIZE)?; Ok(Self { size_out: size, ..fresh }) } else { Ok(Self { buf: buf as *mut u8, size: *size, size_out: size, }) } } fn as_mut_slice(&mut self) -> &mut [u8] { unsafe { std::slice::from_raw_parts_mut(self.buf, self.size) } } fn resize(&mut self) -> Result<()> { let new_size = self.size * 2; let new_buf = unsafe { System.realloc( self.buf, Layout::from_size_align_unchecked(self.size, 1), new_size, ) }; if new_buf.is_null() { Err(Status::AllocFailure) } else { self.buf = new_buf; self.size = new_size; if !self.size_out.is_null() { unsafe { *self.size_out = new_size; } } Ok(()) } } } /// C-style interface for demangling. /// Demangles symbol given in `mangled` argument into `out` buffer. /// /// This interface is a drop-in replacement for `__cxa_demangle`, but for /// Rust demangling. /// /// If `out` is null, a buffer will be allocated using the system allocator /// to contain the results. /// If `out` is non-null, `out_size` must be a pointer to the current size /// of the buffer, and `out` must come from the system allocator. /// If `out_size` is non-null, the size of the output buffer will be written /// to it. /// /// If `status` is non-null, it will be set to one of the following values: /// * 0: Demangling succeeded /// * -1: Allocation failure /// * -2: Name did not demangle /// * -3: Invalid arguments /// /// Returns null if `mangled` is not Rust symbol or demangling failed. /// Returns the buffer containing the demangled symbol name otherwise. /// /// Unsafe as it handles buffers by raw pointers. /// /// For non-null `out`, `out_size` represents a slight deviation from the /// `__cxa_demangle` behavior. For `__cxa_demangle`, the buffer must be at /// *least* the provided size. For `rustc_demangle`, it must be the exact /// buffer size because it is used in the reconstruction of the `Layout` /// for use with `::realloc`. #[no_mangle] pub unsafe extern "C" fn rustc_demangle( mangled: *const c_char, out: *mut c_char, out_size: *mut usize, status: *mut c_int, ) -> *mut c_char { match rustc_demangle_native(mangled, out, out_size) { Ok(demangled) => { set_status(status, 0); demangled } Err(e) => { set_status(status, e as c_int); ptr::null_mut() } } } enum Status { AllocFailure = -1, DemangleFailure = -2, InvalidArgs = -3, } unsafe fn rustc_demangle_native( mangled: *const c_char, out: *mut c_char, out_size: *mut usize, ) -> Result<*mut c_char> { if mangled.is_null() { return Err(Status::InvalidArgs); } let mangled_str = match std::ffi::CStr::from_ptr(mangled).to_str() { Ok(s) => s, Err(_) => return Err(Status::InvalidArgs), }; if !out.is_null() { if out_size.is_null() { return Err(Status::InvalidArgs); } if *out_size == 0 { return Err(Status::InvalidArgs); } } match rustc_demangle::try_demangle(mangled_str) { Ok(demangle) => { let mut out_buf = SystemBuffer::from_raw(out, out_size)?; while write!(out_buf.as_mut_slice(), "{:#}\0", demangle).is_err() { out_buf.resize()?; } Ok(out_buf.as_mut_slice().as_mut_ptr() as *mut c_char) } Err(_) => Err(Status::DemangleFailure), } } #[cfg(test)] mod tests { use std::alloc::{GlobalAlloc, Layout, System}; use std::os::raw::{c_char, c_int}; use std::ptr; struct DemangleResult { out_buf: *mut u8, out_size: usize, status: c_int, } impl Drop for DemangleResult { fn drop(&mut self) { if !self.out_buf.is_null() { unsafe { System.dealloc( self.out_buf, Layout::from_size_align_unchecked(self.out_size, 1), ); } } } } impl DemangleResult { fn as_slice(&self) -> &[u8] { unsafe { std::slice::from_raw_parts(self.out_buf, self.out_size) } } } fn demangle(mangled: &str, alloc_size: usize) -> DemangleResult { unsafe { raw_demangle(mangled.as_ptr() as *const c_char, alloc_size) } } unsafe fn raw_demangle(mangled: *const c_char, alloc_size: usize) -> DemangleResult { let mut out_size: usize = alloc_size; let mut status: c_int = 0; let out_buf: *mut c_char = if out_size != 0 { System.alloc(Layout::from_size_align_unchecked(out_size, 1)) as *mut c_char } else { ptr::null_mut() }; ptr::write_bytes(out_buf, '*' as u8, out_size); let res = super::rustc_demangle(mangled, out_buf, &mut out_size, &mut status); DemangleResult { out_buf: res as *mut u8, out_size, status, } } #[test] fn demangle_c_str_large() { let res = demangle("_ZN4testE\0", 8); assert_eq!(res.status, 0); let out_str = core::str::from_utf8(&res.as_slice()[..5]).unwrap(); assert_eq!(out_str, "test\0"); } #[test] fn demangle_c_str_exact() { let res = demangle("_ZN4testE\0", 8); assert_eq!(res.status, 0); // No reallocation necessary, so our * fill should be present let out_str = core::str::from_utf8(res.as_slice()).unwrap(); assert_eq!(out_str, "test\0***"); } #[test] fn demangle_c_str_small() { let res = demangle("_ZN4testE\0", 4); assert_eq!(res.status, 0); // demangle should have realloced assert_ne!(res.out_size, 4); // Only check the start, since the reallocation means our * fill may // be absent. let out_str = core::str::from_utf8(&res.as_slice()[..5]).unwrap(); assert_eq!(out_str, "test\0"); } #[test] fn demangle_c_str_alloc() { let res = demangle("_ZN4testE\0", 0); assert_eq!(res.status, 0); // demangle should have allocated assert_ne!(res.out_size, 0); let out_str = core::str::from_utf8(&res.as_slice()[..5]).unwrap(); assert_eq!(out_str, "test\0"); } #[test] fn demangle_c_str_not_rust_symbol() { let res = demangle("la la la\0", 8); assert_eq!(res.status, -2); } #[test] fn demangle_c_str_null() { let res = demangle("\0", 8); assert_eq!(res.status, -2); } #[test] fn demangle_c_str_invalid_utf8() { let mangled = [116, 101, 115, 116, 165, 0]; let res = unsafe { raw_demangle(mangled.as_ptr() as *const c_char, 8) }; assert_eq!(res.status, -2); } }