233 lines
6.9 KiB
Rust
233 lines
6.9 KiB
Rust
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#![cfg_attr(oom_with = "hook", feature(alloc_error_hook))]
|
|
#![cfg_attr(oom_with = "alloc_error_panic", feature(panic_oom_payload))]
|
|
|
|
use arrayvec::ArrayString;
|
|
use std::alloc::{GlobalAlloc, Layout};
|
|
use std::cmp;
|
|
use std::ops::Deref;
|
|
use std::os::raw::c_char;
|
|
use std::os::raw::c_int;
|
|
use std::os::raw::c_void;
|
|
use std::panic;
|
|
use std::panic::PanicHookInfo;
|
|
|
|
#[link(name = "wrappers")]
|
|
extern "C" {
|
|
// We can't use MOZ_Crash directly because it may be weakly linked
|
|
// and rust can't handle that.
|
|
fn RustMozCrash(filename: *const c_char, line: c_int, reason: *const c_char) -> !;
|
|
}
|
|
|
|
/// Truncate a string at the closest unicode character boundary
|
|
/// ```
|
|
/// assert_eq!(str_truncate_valid("éà", 3), "é");
|
|
/// assert_eq!(str_truncate_valid("éà", 4), "éè");
|
|
/// ```
|
|
fn str_truncate_valid(s: &str, mut mid: usize) -> &str {
|
|
loop {
|
|
if let Some(res) = s.get(..mid) {
|
|
return res;
|
|
}
|
|
mid -= 1;
|
|
}
|
|
}
|
|
|
|
/// Similar to ArrayString, but with terminating nul character.
|
|
#[derive(Debug, PartialEq)]
|
|
struct ArrayCString<const CAP: usize> {
|
|
inner: ArrayString<CAP>,
|
|
}
|
|
|
|
impl<S: AsRef<str>, const CAP: usize> From<S> for ArrayCString<CAP> {
|
|
/// Contrary to ArrayString::from, truncates at the closest unicode
|
|
/// character boundary.
|
|
/// ```
|
|
/// assert_eq!(ArrayCString::<4>::from("éà"),
|
|
/// ArrayCString::<4>::from("é"));
|
|
/// assert_eq!(&*ArrayCString::<4>::from("éà"), "é\0");
|
|
/// ```
|
|
fn from(s: S) -> Self {
|
|
let s = s.as_ref();
|
|
let len = cmp::min(s.len(), CAP - 1);
|
|
let mut result = Self {
|
|
inner: ArrayString::from(str_truncate_valid(s, len)).unwrap(),
|
|
};
|
|
result.inner.push('\0');
|
|
result
|
|
}
|
|
}
|
|
|
|
impl<const CAP: usize> Deref for ArrayCString<CAP> {
|
|
type Target = str;
|
|
|
|
fn deref(&self) -> &str {
|
|
self.inner.as_str()
|
|
}
|
|
}
|
|
|
|
fn panic_hook(info: &PanicHookInfo) {
|
|
// Try to handle &str/String payloads, which should handle 99% of cases.
|
|
let payload = info.payload();
|
|
let message = if let Some(layout) = oom_hook::oom_layout(payload) {
|
|
unsafe {
|
|
oom_hook::RustHandleOOM(layout.size());
|
|
}
|
|
} else if let Some(s) = payload.downcast_ref::<&str>() {
|
|
s
|
|
} else if let Some(s) = payload.downcast_ref::<String>() {
|
|
s.as_str()
|
|
} else {
|
|
// Not the most helpful thing, but seems unlikely to happen
|
|
// in practice.
|
|
"Unhandled rust panic payload!"
|
|
};
|
|
let (filename, line) = if let Some(loc) = info.location() {
|
|
(loc.file(), loc.line())
|
|
} else {
|
|
("unknown.rs", 0)
|
|
};
|
|
// Copy the message and filename to the stack in order to safely add
|
|
// a terminating nul character (since rust strings don't come with one
|
|
// and RustMozCrash wants one).
|
|
let message = ArrayCString::<512>::from(message);
|
|
let filename = ArrayCString::<512>::from(filename);
|
|
unsafe {
|
|
RustMozCrash(
|
|
filename.as_ptr() as *const c_char,
|
|
line as c_int,
|
|
message.as_ptr() as *const c_char,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Configure a panic hook to redirect rust panics to MFBT's MOZ_Crash.
|
|
#[no_mangle]
|
|
pub extern "C" fn install_rust_hooks() {
|
|
panic::set_hook(Box::new(panic_hook));
|
|
#[cfg(oom_with = "hook")]
|
|
use std::alloc::set_alloc_error_hook;
|
|
#[cfg(oom_with = "hook")]
|
|
set_alloc_error_hook(oom_hook::hook);
|
|
}
|
|
|
|
mod oom_hook {
|
|
#[cfg(oom_with = "alloc_error_panic")]
|
|
use std::alloc::AllocErrorPanicPayload;
|
|
use std::alloc::Layout;
|
|
use std::any::Any;
|
|
|
|
#[inline(always)]
|
|
pub fn oom_layout(_payload: &dyn Any) -> Option<Layout> {
|
|
#[cfg(oom_with = "alloc_error_panic")]
|
|
return _payload
|
|
.downcast_ref::<AllocErrorPanicPayload>()
|
|
.map(|p| p.layout());
|
|
#[cfg(not(oom_with = "alloc_error_panic"))]
|
|
return None;
|
|
}
|
|
|
|
extern "C" {
|
|
pub fn RustHandleOOM(size: usize) -> !;
|
|
}
|
|
|
|
#[cfg(oom_with = "hook")]
|
|
pub fn hook(layout: Layout) {
|
|
unsafe {
|
|
RustHandleOOM(layout.size());
|
|
}
|
|
}
|
|
}
|
|
|
|
extern "C" {
|
|
fn malloc(size: usize) -> *mut c_void;
|
|
|
|
fn free(ptr: *mut c_void);
|
|
|
|
fn calloc(nmemb: usize, size: usize) -> *mut c_void;
|
|
|
|
fn realloc(ptr: *mut c_void, size: usize) -> *mut c_void;
|
|
|
|
#[cfg(windows)]
|
|
fn _aligned_malloc(size: usize, align: usize) -> *mut c_void;
|
|
|
|
#[cfg(windows)]
|
|
fn _aligned_free(ptr: *mut c_void);
|
|
|
|
#[cfg(not(windows))]
|
|
fn memalign(align: usize, size: usize) -> *mut c_void;
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
unsafe fn memalign(align: usize, size: usize) -> *mut c_void {
|
|
_aligned_malloc(size, align)
|
|
}
|
|
|
|
pub struct GeckoAlloc;
|
|
|
|
#[inline(always)]
|
|
fn need_memalign(layout: Layout) -> bool {
|
|
// mozjemalloc guarantees a minimum alignment of 16 for all sizes, except
|
|
// for size classes below 16 (4 and 8).
|
|
layout.align() > layout.size() || layout.align() > 16
|
|
}
|
|
|
|
unsafe impl GlobalAlloc for GeckoAlloc {
|
|
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
|
|
if need_memalign(layout) {
|
|
memalign(layout.align(), layout.size()) as *mut u8
|
|
} else {
|
|
malloc(layout.size()) as *mut u8
|
|
}
|
|
}
|
|
|
|
// On Windows, _aligned_free must be used to free memory allocated with
|
|
// _aligned_malloc. Except when mozjemalloc is enabled, in which case
|
|
// _aligned_malloc-allocated memory can be freed with free.
|
|
#[cfg(all(windows, not(feature = "moz_memory")))]
|
|
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
|
|
if need_memalign(layout) {
|
|
_aligned_free(ptr as *mut c_void)
|
|
} else {
|
|
free(ptr as *mut c_void)
|
|
}
|
|
}
|
|
|
|
#[cfg(any(not(windows), feature = "moz_memory"))]
|
|
unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
|
|
free(ptr as *mut c_void)
|
|
}
|
|
|
|
unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
|
|
if need_memalign(layout) {
|
|
let ptr = self.alloc(layout);
|
|
if !ptr.is_null() {
|
|
std::ptr::write_bytes(ptr, 0, layout.size());
|
|
}
|
|
ptr
|
|
} else {
|
|
calloc(1, layout.size()) as *mut u8
|
|
}
|
|
}
|
|
|
|
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
|
|
let new_layout = Layout::from_size_align_unchecked(new_size, layout.align());
|
|
if need_memalign(new_layout) {
|
|
let new_ptr = self.alloc(new_layout);
|
|
if !new_ptr.is_null() {
|
|
let size = std::cmp::min(layout.size(), new_size);
|
|
std::ptr::copy_nonoverlapping(ptr, new_ptr, size);
|
|
self.dealloc(ptr, layout);
|
|
}
|
|
new_ptr
|
|
} else {
|
|
realloc(ptr as *mut c_void, new_size) as *mut u8
|
|
}
|
|
}
|
|
}
|
|
|
|
#[global_allocator]
|
|
static A: GeckoAlloc = GeckoAlloc;
|