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