diff options
Diffstat (limited to 'mozglue/static/rust')
-rw-r--r-- | mozglue/static/rust/Cargo.toml | 19 | ||||
-rw-r--r-- | mozglue/static/rust/build.rs | 32 | ||||
-rw-r--r-- | mozglue/static/rust/lib.rs | 221 | ||||
-rw-r--r-- | mozglue/static/rust/moz_asserts/Cargo.toml | 11 | ||||
-rw-r--r-- | mozglue/static/rust/moz_asserts/lib.rs | 103 | ||||
-rw-r--r-- | mozglue/static/rust/wrappers.cpp | 23 |
6 files changed, 409 insertions, 0 deletions
diff --git a/mozglue/static/rust/Cargo.toml b/mozglue/static/rust/Cargo.toml new file mode 100644 index 0000000000..d0307a5bda --- /dev/null +++ b/mozglue/static/rust/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "mozglue-static" +version = "0.1.0" +edition = "2018" +license = "MPL-2.0" + +[lib] +path = "lib.rs" + +[dependencies] +arrayvec = "0.7" + +[build-dependencies] +mozbuild = "0.1" +cc = "1" +rustc_version = "0.4" + +[features] +moz_memory = [] diff --git a/mozglue/static/rust/build.rs b/mozglue/static/rust/build.rs new file mode 100644 index 0000000000..e44419fb16 --- /dev/null +++ b/mozglue/static/rust/build.rs @@ -0,0 +1,32 @@ +/* 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/. */ + +use rustc_version::{version, Version}; + +fn main() { + let mut build = cc::Build::new(); + build.cpp(true); + // For js-confdefs.h, see wrappers.cpp. + build.include(mozbuild::TOPOBJDIR.join("js").join("src")); + build.include(mozbuild::TOPOBJDIR.join("dist").join("include")); + build.define("MOZ_HAS_MOZGLUE", None); + build.file("wrappers.cpp"); + build.compile("wrappers"); + println!("cargo:rerun-if-changed=wrappers.cpp"); + + let ver = version().unwrap(); + let max_oom_hook_version = Version::parse("1.77.0-alpha").unwrap(); + // The new alloc error panic feature was temporarily reverted. We kept the + // code in tree, but the version here is such that it's effectively never used. + let max_alloc_error_panic_version = Version::parse("1.77.0-alpha").unwrap(); + + if ver < max_oom_hook_version { + println!("cargo:rustc-cfg=feature=\"oom_with_hook\""); + } else if ver < max_alloc_error_panic_version { + println!("cargo:rustc-cfg=feature=\"oom_with_alloc_error_panic\""); + } else if std::env::var("MOZ_AUTOMATION").is_ok() { + panic!("Builds on automation must use a version of rust for which we know how to hook OOM: want < {}, have {}", + max_alloc_error_panic_version, ver); + } +} diff --git a/mozglue/static/rust/lib.rs b/mozglue/static/rust/lib.rs new file mode 100644 index 0000000000..001b920766 --- /dev/null +++ b/mozglue/static/rust/lib.rs @@ -0,0 +1,221 @@ +/* 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(feature = "oom_with_hook", feature(alloc_error_hook))] +#![cfg_attr(feature = "oom_with_alloc_error_panic", feature(panic_oom_payload))] + +use arrayvec::ArrayString; +use std::cmp; +use std::ops::Deref; +use std::os::raw::c_char; +use std::os::raw::c_int; +use std::panic; + +#[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: &panic::PanicInfo) { + // 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(feature = "oom_with_hook")] + use std::alloc::set_alloc_error_hook; + #[cfg(feature = "oom_with_hook")] + set_alloc_error_hook(oom_hook::hook); +} + +mod oom_hook { + #[cfg(feature = "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(feature = "oom_with_alloc_error_panic")] + return _payload + .downcast_ref::<AllocErrorPanicPayload>() + .map(|p| p.layout()); + #[cfg(not(feature = "oom_with_alloc_error_panic"))] + return None; + } + + extern "C" { + pub fn RustHandleOOM(size: usize) -> !; + } + + #[cfg(feature = "oom_with_hook")] + pub fn hook(layout: Layout) { + unsafe { + RustHandleOOM(layout.size()); + } + } +} + +#[cfg(feature = "moz_memory")] +mod moz_memory { + use std::alloc::{GlobalAlloc, Layout}; + use std::os::raw::c_void; + + 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(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 + } + } + + 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 + } + } + } +} + +#[cfg(feature = "moz_memory")] +#[global_allocator] +static A: moz_memory::GeckoAlloc = moz_memory::GeckoAlloc; diff --git a/mozglue/static/rust/moz_asserts/Cargo.toml b/mozglue/static/rust/moz_asserts/Cargo.toml new file mode 100644 index 0000000000..27e336f7c7 --- /dev/null +++ b/mozglue/static/rust/moz_asserts/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "moz_asserts" +version = "0.1.0" +edition = "2018" +license = "MPL-2.0" + +[lib] +path = "lib.rs" + +[dependencies] +mozbuild = "0.1" diff --git a/mozglue/static/rust/moz_asserts/lib.rs b/mozglue/static/rust/moz_asserts/lib.rs new file mode 100644 index 0000000000..f4b00a5ad2 --- /dev/null +++ b/mozglue/static/rust/moz_asserts/lib.rs @@ -0,0 +1,103 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +// Implementation note: It seems tempting to use if cfg!(feature = ...) directly +// in the macro, but that doesn't work, as the feature would be evaluated +// against the dependent crate. +// +// It'd also seem tempting to export different macros depending on the features +// enabled, but that's also not great because it causes warnings when the +// features are enabled, e.g.: +// +// nightly_panic!("foo"); +// return SAFE_VALUE; +// +// would cause an unreachable code warning for Nightly builds. So instead we +// choose an exported constant to guard the condition. (For reference, this +// is also how rust's `debug_assert!` is implemented) + +/// Whether Nightly-only assertions are enabled. +pub use mozbuild::config::NIGHTLY_BUILD; + +/// Whether diagnostic assertions are enabled. +pub use mozbuild::config::MOZ_DIAGNOSTIC_ASSERT_ENABLED; + +/// assert! on Nightly, gets compiled out otherwise. +#[macro_export] +macro_rules! nightly_assert { + ($($arg:tt)*) => (if $crate::NIGHTLY_BUILD { assert!($($arg)*); }) +} + +/// assert_eq! on Nightly, gets compiled out otherwise. +#[macro_export] +macro_rules! nightly_assert_eq { + ($($arg:tt)*) => (if $crate::NIGHTLY_BUILD { assert_eq!($($arg)*); }) +} + +/// assert_ne! on Nightly, gets compiled out otherwise. +#[macro_export] +macro_rules! nightly_assert_ne { + ($($arg:tt)*) => (if $crate::NIGHTLY_BUILD { assert_ne!($($arg)*); }) +} + +/// panic! on Nightly, gets compiled out otherwise. +#[macro_export] +macro_rules! nightly_panic { + ($($arg:tt)*) => (if $crate::NIGHTLY_BUILD { panic!($($arg)*); }) +} + +/// unreachable! on Nightly, `std::hint::unreachable_unchecked()` otherwise. +/// +/// Use carefully! Consider using nightly_panic! and handling the failure +/// gracefully if you can't prove it's really unreachable. +/// +/// See https://doc.rust-lang.org/std/hint/fn.unreachable_unchecked.html#safety +/// for safety details. +#[macro_export] +macro_rules! nightly_unreachable { + ($($arg:tt)*) => { + if $crate::NIGHTLY_BUILD { + unreachable!($($arg)*); + } else { + std::hint::unreachable_unchecked() + } + } +} + +/// assert! when diagnostic asserts are enabled, gets compiled out otherwise. +#[macro_export] +macro_rules! diagnostic_assert { + ($($arg:tt)*) => (if $crate::MOZ_DIAGNOSTIC_ASSERT_ENABLED { assert!($($arg)*); }) +} + +/// assert_eq! when diagnostic asserts are enabled, gets compiled out otherwise. +#[macro_export] +macro_rules! diagnostic_assert_eq { + ($($arg:tt)*) => (if $crate::MOZ_DIAGNOSTIC_ASSERT_ENABLED { assert_eq!($($arg)*); }) +} + +/// assert_ne! when diagnostic asserts are enabled, gets compiled out otherwise. +#[macro_export] +macro_rules! diagnostic_assert_ne { + ($($arg:tt)*) => (if $crate::MOZ_DIAGNOSTIC_ASSERT_ENABLED { assert_ne!($($arg)*); }) +} + +/// unreachable! when diagnostic asserts are enabled, +/// `std::hint::unreachable_unchecked()` otherwise. +/// +/// Use carefully! Consider using diagnostic_panic! and handling the failure +/// gracefully if you can't prove it's really unreachable. +/// +/// See https://doc.rust-lang.org/std/hint/fn.unreachable_unchecked.html#safety +/// for safety details. +#[macro_export] +macro_rules! diagnostic_unreachable { + ($($arg:tt)*) => { + if $crate::MOZ_DIAGNOSTIC_ASSERT_ENABLED { + unreachable!($($arg)*); + } else { + std::hint::unreachable_unchecked() + } + } +} diff --git a/mozglue/static/rust/wrappers.cpp b/mozglue/static/rust/wrappers.cpp new file mode 100644 index 0000000000..926593f871 --- /dev/null +++ b/mozglue/static/rust/wrappers.cpp @@ -0,0 +1,23 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +// This ensures the right configuration for e.g. MOZ_GLUE_IN_PROGRAM, +// used in the MFBT headers included further below. We use js-confdefs.h +// instead of mozilla-config.h because the latter is not present in +// spidermonkey standalone builds while the former is always present. +#include "js-confdefs.h" +#include "mozilla/Assertions.h" +#include "mozilla/Types.h" +#include "mozilla/mozalloc_oom.h" + +// MOZ_Crash wrapper for use by rust, since MOZ_Crash is an inline function. +extern "C" void RustMozCrash(const char* aFilename, int aLine, + const char* aReason) { + MOZ_Crash(aFilename, aLine, aReason); +} + +// mozalloc_handle_oom wrapper for use by rust, because mozalloc_handle_oom is +// MFBT_API, that rust can't respect. +extern "C" void RustHandleOOM(size_t size) { mozalloc_handle_oom(size); } |