lib.rs (7079B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 #![cfg_attr(oom_with = "hook", feature(alloc_error_hook))] 6 #![cfg_attr(oom_with = "alloc_error_panic", feature(panic_oom_payload))] 7 8 use arrayvec::ArrayString; 9 use std::alloc::{GlobalAlloc, Layout}; 10 use std::cmp; 11 use std::ops::Deref; 12 use std::os::raw::c_char; 13 use std::os::raw::c_int; 14 use std::os::raw::c_void; 15 use std::panic; 16 use std::panic::PanicHookInfo; 17 18 #[link(name = "wrappers")] 19 extern "C" { 20 // We can't use MOZ_Crash directly because it may be weakly linked 21 // and rust can't handle that. 22 fn RustMozCrash(filename: *const c_char, line: c_int, reason: *const c_char) -> !; 23 } 24 25 /// Truncate a string at the closest unicode character boundary 26 /// ``` 27 /// assert_eq!(str_truncate_valid("éà", 3), "é"); 28 /// assert_eq!(str_truncate_valid("éà", 4), "éè"); 29 /// ``` 30 fn str_truncate_valid(s: &str, mut mid: usize) -> &str { 31 loop { 32 if let Some(res) = s.get(..mid) { 33 return res; 34 } 35 mid -= 1; 36 } 37 } 38 39 /// Similar to ArrayString, but with terminating nul character. 40 #[derive(Debug, PartialEq)] 41 struct ArrayCString<const CAP: usize> { 42 inner: ArrayString<CAP>, 43 } 44 45 impl<S: AsRef<str>, const CAP: usize> From<S> for ArrayCString<CAP> { 46 /// Contrary to ArrayString::from, truncates at the closest unicode 47 /// character boundary. 48 /// ``` 49 /// assert_eq!(ArrayCString::<4>::from("éà"), 50 /// ArrayCString::<4>::from("é")); 51 /// assert_eq!(&*ArrayCString::<4>::from("éà"), "é\0"); 52 /// ``` 53 fn from(s: S) -> Self { 54 let s = s.as_ref(); 55 let len = cmp::min(s.len(), CAP - 1); 56 let mut result = Self { 57 inner: ArrayString::from(str_truncate_valid(s, len)).unwrap(), 58 }; 59 result.inner.push('\0'); 60 result 61 } 62 } 63 64 impl<const CAP: usize> Deref for ArrayCString<CAP> { 65 type Target = str; 66 67 fn deref(&self) -> &str { 68 self.inner.as_str() 69 } 70 } 71 72 fn panic_hook(info: &PanicHookInfo) { 73 // Try to handle &str/String payloads, which should handle 99% of cases. 74 let payload = info.payload(); 75 let message = if let Some(layout) = oom_hook::oom_layout(payload) { 76 unsafe { 77 oom_hook::RustHandleOOM(layout.size()); 78 } 79 } else if let Some(s) = payload.downcast_ref::<&str>() { 80 s 81 } else if let Some(s) = payload.downcast_ref::<String>() { 82 s.as_str() 83 } else { 84 // Not the most helpful thing, but seems unlikely to happen 85 // in practice. 86 "Unhandled rust panic payload!" 87 }; 88 let (filename, line) = if let Some(loc) = info.location() { 89 (loc.file(), loc.line()) 90 } else { 91 ("unknown.rs", 0) 92 }; 93 // Copy the message and filename to the stack in order to safely add 94 // a terminating nul character (since rust strings don't come with one 95 // and RustMozCrash wants one). 96 let message = ArrayCString::<512>::from(message); 97 let filename = ArrayCString::<512>::from(filename); 98 unsafe { 99 RustMozCrash( 100 filename.as_ptr() as *const c_char, 101 line as c_int, 102 message.as_ptr() as *const c_char, 103 ); 104 } 105 } 106 107 /// Configure a panic hook to redirect rust panics to MFBT's MOZ_Crash. 108 #[no_mangle] 109 pub extern "C" fn install_rust_hooks() { 110 panic::set_hook(Box::new(panic_hook)); 111 #[cfg(oom_with = "hook")] 112 use std::alloc::set_alloc_error_hook; 113 #[cfg(oom_with = "hook")] 114 set_alloc_error_hook(oom_hook::hook); 115 } 116 117 mod oom_hook { 118 #[cfg(oom_with = "alloc_error_panic")] 119 use std::alloc::AllocErrorPanicPayload; 120 use std::alloc::Layout; 121 use std::any::Any; 122 123 #[inline(always)] 124 pub fn oom_layout(_payload: &dyn Any) -> Option<Layout> { 125 #[cfg(oom_with = "alloc_error_panic")] 126 return _payload 127 .downcast_ref::<AllocErrorPanicPayload>() 128 .map(|p| p.layout()); 129 #[cfg(not(oom_with = "alloc_error_panic"))] 130 return None; 131 } 132 133 extern "C" { 134 pub fn RustHandleOOM(size: usize) -> !; 135 } 136 137 #[cfg(oom_with = "hook")] 138 pub fn hook(layout: Layout) { 139 unsafe { 140 RustHandleOOM(layout.size()); 141 } 142 } 143 } 144 145 extern "C" { 146 fn malloc(size: usize) -> *mut c_void; 147 148 fn free(ptr: *mut c_void); 149 150 fn calloc(nmemb: usize, size: usize) -> *mut c_void; 151 152 fn realloc(ptr: *mut c_void, size: usize) -> *mut c_void; 153 154 #[cfg(windows)] 155 fn _aligned_malloc(size: usize, align: usize) -> *mut c_void; 156 157 #[cfg(windows)] 158 fn _aligned_free(ptr: *mut c_void); 159 160 #[cfg(not(windows))] 161 fn memalign(align: usize, size: usize) -> *mut c_void; 162 } 163 164 #[cfg(windows)] 165 unsafe fn memalign(align: usize, size: usize) -> *mut c_void { 166 _aligned_malloc(size, align) 167 } 168 169 pub struct GeckoAlloc; 170 171 #[inline(always)] 172 fn need_memalign(layout: Layout) -> bool { 173 // mozjemalloc guarantees a minimum alignment of 16 for all sizes, except 174 // for size classes below 16 (4 and 8). 175 layout.align() > layout.size() || layout.align() > 16 176 } 177 178 unsafe impl GlobalAlloc for GeckoAlloc { 179 unsafe fn alloc(&self, layout: Layout) -> *mut u8 { 180 if need_memalign(layout) { 181 memalign(layout.align(), layout.size()) as *mut u8 182 } else { 183 malloc(layout.size()) as *mut u8 184 } 185 } 186 187 // On Windows, _aligned_free must be used to free memory allocated with 188 // _aligned_malloc. Except when mozjemalloc is enabled, in which case 189 // _aligned_malloc-allocated memory can be freed with free. 190 #[cfg(all(windows, not(feature = "moz_memory")))] 191 unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { 192 if need_memalign(layout) { 193 _aligned_free(ptr as *mut c_void) 194 } else { 195 free(ptr as *mut c_void) 196 } 197 } 198 199 #[cfg(any(not(windows), feature = "moz_memory"))] 200 unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) { 201 free(ptr as *mut c_void) 202 } 203 204 unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { 205 if need_memalign(layout) { 206 let ptr = self.alloc(layout); 207 if !ptr.is_null() { 208 std::ptr::write_bytes(ptr, 0, layout.size()); 209 } 210 ptr 211 } else { 212 calloc(1, layout.size()) as *mut u8 213 } 214 } 215 216 unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { 217 let new_layout = Layout::from_size_align_unchecked(new_size, layout.align()); 218 if need_memalign(new_layout) { 219 let new_ptr = self.alloc(new_layout); 220 if !new_ptr.is_null() { 221 let size = std::cmp::min(layout.size(), new_size); 222 std::ptr::copy_nonoverlapping(ptr, new_ptr, size); 223 self.dealloc(ptr, layout); 224 } 225 new_ptr 226 } else { 227 realloc(ptr as *mut c_void, new_size) as *mut u8 228 } 229 } 230 } 231 232 #[global_allocator] 233 static A: GeckoAlloc = GeckoAlloc;