1  extern crate rustc_demangle;
2  
3  use std::alloc::{GlobalAlloc, Layout, System};
4  use std::io::Write;
5  use std::os::raw::{c_char, c_int};
6  use std::ptr;
7  use std::result;
8  
9  type Result<T> = result::Result<T, Status>;
10  
11  /// Convenience function to set return status if a location was provided.
set_status(status: *mut c_int, val: c_int)12  unsafe fn set_status(status: *mut c_int, val: c_int) {
13      if !status.is_null() {
14          *status = val;
15      }
16  }
17  
18  /// Region from the system allocator for demangler output. We use the
19  /// system allocator because the intended client is C/C++ code which
20  /// may not be using the Rust allocator.
21  struct SystemBuffer {
22      buf: *mut u8,
23      size: usize,
24      size_out: *mut usize,
25  }
26  
27  impl SystemBuffer {
28      const DEFAULT_BUFFER_SIZE: usize = 1024;
new(size: usize) -> Result<Self>29      fn new(size: usize) -> Result<Self> {
30          let buf = unsafe { System.alloc_zeroed(Layout::from_size_align_unchecked(size, 1)) };
31          if buf.is_null() {
32              Err(Status::AllocFailure)
33          } else {
34              Ok(Self {
35                  buf,
36                  size,
37                  size_out: ptr::null_mut(),
38              })
39          }
40      }
41      /// Safety: If buf is non-null, size must be non-null and point to the
42      /// non-zero size of the buffer provided in buf.
43      /// Takes ownership of the buffer passed in (and may reallocate it).
44      /// size must outlive the resulting buffer if non-null.
from_raw(buf: *mut c_char, size: *mut usize) -> Result<Self>45      unsafe fn from_raw(buf: *mut c_char, size: *mut usize) -> Result<Self> {
46          if buf.is_null() {
47              if !size.is_null() {
48                  *size = Self::DEFAULT_BUFFER_SIZE;
49              }
50              let fresh = Self::new(Self::DEFAULT_BUFFER_SIZE)?;
51              Ok(Self {
52                  size_out: size,
53                  ..fresh
54              })
55          } else {
56              Ok(Self {
57                  buf: buf as *mut u8,
58                  size: *size,
59                  size_out: size,
60              })
61          }
62      }
as_mut_slice(&mut self) -> &mut [u8]63      fn as_mut_slice(&mut self) -> &mut [u8] {
64          unsafe { std::slice::from_raw_parts_mut(self.buf, self.size) }
65      }
resize(&mut self) -> Result<()>66      fn resize(&mut self) -> Result<()> {
67          let new_size = self.size * 2;
68          let new_buf = unsafe {
69              System.realloc(
70                  self.buf,
71                  Layout::from_size_align_unchecked(self.size, 1),
72                  new_size,
73              )
74          };
75          if new_buf.is_null() {
76              Err(Status::AllocFailure)
77          } else {
78              self.buf = new_buf;
79              self.size = new_size;
80              if !self.size_out.is_null() {
81                  unsafe {
82                      *self.size_out = new_size;
83                  }
84              }
85              Ok(())
86          }
87      }
88  }
89  
90  /// C-style interface for demangling.
91  /// Demangles symbol given in `mangled` argument into `out` buffer.
92  ///
93  /// This interface is a drop-in replacement for `__cxa_demangle`, but for
94  /// Rust demangling.
95  ///
96  /// If `out` is null, a buffer will be allocated using the system allocator
97  /// to contain the results.
98  /// If `out` is non-null, `out_size` must be a pointer to the current size
99  /// of the buffer, and `out` must come from the system allocator.
100  /// If `out_size` is non-null, the size of the output buffer will be written
101  /// to it.
102  ///
103  /// If `status` is non-null, it will be set to one of the following values:
104  /// * 0: Demangling succeeded
105  /// * -1: Allocation failure
106  /// * -2: Name did not demangle
107  /// * -3: Invalid arguments
108  ///
109  /// Returns null if `mangled` is not Rust symbol or demangling failed.
110  /// Returns the buffer containing the demangled symbol name otherwise.
111  ///
112  /// Unsafe as it handles buffers by raw pointers.
113  ///
114  /// For non-null `out`, `out_size` represents a slight deviation from the
115  /// `__cxa_demangle` behavior. For `__cxa_demangle`, the buffer must be at
116  /// *least* the provided size. For `rustc_demangle`, it must be the exact
117  /// buffer size because it is used in the reconstruction of the `Layout`
118  /// for use with `::realloc`.
119  #[no_mangle]
rustc_demangle( mangled: *const c_char, out: *mut c_char, out_size: *mut usize, status: *mut c_int, ) -> *mut c_char120  pub unsafe extern "C" fn rustc_demangle(
121      mangled: *const c_char,
122      out: *mut c_char,
123      out_size: *mut usize,
124      status: *mut c_int,
125  ) -> *mut c_char {
126      match rustc_demangle_native(mangled, out, out_size) {
127          Ok(demangled) => {
128              set_status(status, 0);
129              demangled
130          }
131          Err(e) => {
132              set_status(status, e as c_int);
133              ptr::null_mut()
134          }
135      }
136  }
137  
138  enum Status {
139      AllocFailure = -1,
140      DemangleFailure = -2,
141      InvalidArgs = -3,
142  }
143  
rustc_demangle_native( mangled: *const c_char, out: *mut c_char, out_size: *mut usize, ) -> Result<*mut c_char>144  unsafe fn rustc_demangle_native(
145      mangled: *const c_char,
146      out: *mut c_char,
147      out_size: *mut usize,
148  ) -> Result<*mut c_char> {
149      if mangled.is_null() {
150          return Err(Status::InvalidArgs);
151      }
152      let mangled_str = match std::ffi::CStr::from_ptr(mangled).to_str() {
153          Ok(s) => s,
154          Err(_) => return Err(Status::InvalidArgs),
155      };
156  
157      if !out.is_null() {
158          if out_size.is_null() {
159              return Err(Status::InvalidArgs);
160          }
161          if *out_size == 0 {
162              return Err(Status::InvalidArgs);
163          }
164      }
165  
166      match rustc_demangle::try_demangle(mangled_str) {
167          Ok(demangle) => {
168              let mut out_buf = SystemBuffer::from_raw(out, out_size)?;
169              while write!(out_buf.as_mut_slice(), "{:#}\0", demangle).is_err() {
170                  out_buf.resize()?;
171              }
172              Ok(out_buf.as_mut_slice().as_mut_ptr() as *mut c_char)
173          }
174          Err(_) => Err(Status::DemangleFailure),
175      }
176  }
177  
178  #[cfg(test)]
179  mod tests {
180      use std::alloc::{GlobalAlloc, Layout, System};
181      use std::os::raw::{c_char, c_int};
182      use std::ptr;
183  
184      struct DemangleResult {
185          out_buf: *mut u8,
186          out_size: usize,
187          status: c_int,
188      }
189  
190      impl Drop for DemangleResult {
drop(&mut self)191          fn drop(&mut self) {
192              if !self.out_buf.is_null() {
193                  unsafe {
194                      System.dealloc(
195                          self.out_buf,
196                          Layout::from_size_align_unchecked(self.out_size, 1),
197                      );
198                  }
199              }
200          }
201      }
202  
203      impl DemangleResult {
as_slice(&self) -> &[u8]204          fn as_slice(&self) -> &[u8] {
205              unsafe { std::slice::from_raw_parts(self.out_buf, self.out_size) }
206          }
207      }
208  
demangle(mangled: &str, alloc_size: usize) -> DemangleResult209      fn demangle(mangled: &str, alloc_size: usize) -> DemangleResult {
210          unsafe { raw_demangle(mangled.as_ptr() as *const c_char, alloc_size) }
211      }
212  
raw_demangle(mangled: *const c_char, alloc_size: usize) -> DemangleResult213      unsafe fn raw_demangle(mangled: *const c_char, alloc_size: usize) -> DemangleResult {
214          let mut out_size: usize = alloc_size;
215          let mut status: c_int = 0;
216          let out_buf: *mut c_char = if out_size != 0 {
217              System.alloc(Layout::from_size_align_unchecked(out_size, 1)) as *mut c_char
218          } else {
219              ptr::null_mut()
220          };
221          ptr::write_bytes(out_buf, '*' as u8, out_size);
222  
223          let res = super::rustc_demangle(mangled, out_buf, &mut out_size, &mut status);
224          DemangleResult {
225              out_buf: res as *mut u8,
226              out_size,
227              status,
228          }
229      }
230  
231      #[test]
demangle_c_str_large()232      fn demangle_c_str_large() {
233          let res = demangle("_ZN4testE\0", 8);
234          assert_eq!(res.status, 0);
235          let out_str = core::str::from_utf8(&res.as_slice()[..5]).unwrap();
236          assert_eq!(out_str, "test\0");
237      }
238  
239      #[test]
demangle_c_str_exact()240      fn demangle_c_str_exact() {
241          let res = demangle("_ZN4testE\0", 8);
242          assert_eq!(res.status, 0);
243          // No reallocation necessary, so our * fill should be present
244          let out_str = core::str::from_utf8(res.as_slice()).unwrap();
245          assert_eq!(out_str, "test\0***");
246      }
247  
248      #[test]
demangle_c_str_small()249      fn demangle_c_str_small() {
250          let res = demangle("_ZN4testE\0", 4);
251          assert_eq!(res.status, 0);
252          // demangle should have realloced
253          assert_ne!(res.out_size, 4);
254          // Only check the start, since the reallocation means our * fill may
255          // be absent.
256          let out_str = core::str::from_utf8(&res.as_slice()[..5]).unwrap();
257          assert_eq!(out_str, "test\0");
258      }
259  
260      #[test]
demangle_c_str_alloc()261      fn demangle_c_str_alloc() {
262          let res = demangle("_ZN4testE\0", 0);
263          assert_eq!(res.status, 0);
264          // demangle should have allocated
265          assert_ne!(res.out_size, 0);
266          let out_str = core::str::from_utf8(&res.as_slice()[..5]).unwrap();
267          assert_eq!(out_str, "test\0");
268      }
269  
270      #[test]
demangle_c_str_not_rust_symbol()271      fn demangle_c_str_not_rust_symbol() {
272          let res = demangle("la la la\0", 8);
273          assert_eq!(res.status, -2);
274      }
275  
276      #[test]
demangle_c_str_null()277      fn demangle_c_str_null() {
278          let res = demangle("\0", 8);
279          assert_eq!(res.status, -2);
280      }
281  
282      #[test]
demangle_c_str_invalid_utf8()283      fn demangle_c_str_invalid_utf8() {
284          let mangled = [116, 101, 115, 116, 165, 0];
285          let res = unsafe { raw_demangle(mangled.as_ptr() as *const c_char, 8) };
286          assert_eq!(res.status, -2);
287      }
288  }
289