summaryrefslogtreecommitdiffstats
path: root/mozglue/static/rust
diff options
context:
space:
mode:
Diffstat (limited to 'mozglue/static/rust')
-rw-r--r--mozglue/static/rust/Cargo.toml19
-rw-r--r--mozglue/static/rust/build.rs32
-rw-r--r--mozglue/static/rust/lib.rs221
-rw-r--r--mozglue/static/rust/moz_asserts/Cargo.toml11
-rw-r--r--mozglue/static/rust/moz_asserts/lib.rs103
-rw-r--r--mozglue/static/rust/wrappers.cpp23
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); }