Improve overall `unsafe` hygiene
This adds `#![deny(unsafe_op_in_unsafe_fn)]` which removes the
implicit `unsafe` block that `unsafe fn` does.
It also adds many more `SAFETY` docs, corrects some incomplete
ones, and catches a null pointer returned by `upb_Arena_New`.
PiperOrigin-RevId: 549067106
diff --git a/rust/cpp.rs b/rust/cpp.rs
index b60ef58..deac9d9 100644
--- a/rust/cpp.rs
+++ b/rust/cpp.rs
@@ -30,15 +30,13 @@
// Rust Protobuf runtime using the C++ kernel.
-use std::alloc;
use std::alloc::Layout;
use std::cell::UnsafeCell;
use std::fmt;
use std::marker::PhantomData;
use std::mem::MaybeUninit;
use std::ops::Deref;
-use std::ptr::NonNull;
-use std::slice;
+use std::ptr::{self, NonNull};
/// A wrapper over a `proto2::Arena`.
///
@@ -72,7 +70,7 @@
///
/// # Safety
///
- /// `layout`'s alignment must be less than `UPB_MALLOC_ALIGN`.
+ /// TODO alignment requirement for layout
#[inline]
pub unsafe fn alloc(&self, _layout: Layout) -> &mut [MaybeUninit<u8>] {
unimplemented!()
@@ -83,8 +81,8 @@
/// # Safety
///
/// After calling this function, `ptr` is essentially zapped. `old` must
- /// be the layout `ptr` was allocated with via [`Arena::alloc()`]. `new`'s
- /// alignment must be less than `UPB_MALLOC_ALIGN`.
+ /// be the layout `ptr` was allocated with via [`Arena::alloc()`].
+ /// TODO alignment for layout
#[inline]
pub unsafe fn resize(&self, _ptr: *mut u8, _old: Layout, _new: Layout) -> &[MaybeUninit<u8>] {
unimplemented!()
@@ -111,23 +109,42 @@
}
impl SerializedData {
+ /// Constructs owned serialized data from raw components.
+ ///
+ /// # Safety
+ /// - `data` must be readable for `len` bytes.
+ /// - `data` must be an owned pointer and valid until deallocated.
+ /// - `data` must have been allocated by the Rust global allocator with a
+ /// size of `len` and align of 1.
pub unsafe fn from_raw_parts(data: NonNull<u8>, len: usize) -> Self {
Self { data, len }
}
+
+ /// Gets a raw slice pointer.
+ pub fn as_ptr(&self) -> *const [u8] {
+ ptr::slice_from_raw_parts(self.data.as_ptr(), self.len)
+ }
+
+ /// Gets a mutable raw slice pointer.
+ fn as_mut_ptr(&mut self) -> *mut [u8] {
+ ptr::slice_from_raw_parts_mut(self.data.as_ptr(), self.len)
+ }
}
impl Deref for SerializedData {
type Target = [u8];
fn deref(&self) -> &Self::Target {
- unsafe { slice::from_raw_parts(self.data.as_ptr(), self.len) }
+ // SAFETY: `data` is valid for `len` bytes until deallocated as promised by
+ // `from_raw_parts`.
+ unsafe { &*self.as_ptr() }
}
}
impl Drop for SerializedData {
fn drop(&mut self) {
- unsafe {
- alloc::dealloc(self.data.as_ptr(), Layout::array::<u8>(self.len).unwrap());
- };
+ // SAFETY: `data` was allocated by the Rust global allocator with a
+ // size of `len` and align of 1 as promised by `from_raw_parts`.
+ unsafe { drop(Box::from_raw(self.as_mut_ptr())) }
}
}
diff --git a/rust/cpp_kernel/cpp_api.h b/rust/cpp_kernel/cpp_api.h
index 78357a1..a9e18fc 100644
--- a/rust/cpp_kernel/cpp_api.h
+++ b/rust/cpp_kernel/cpp_api.h
@@ -50,7 +50,7 @@
// * The data were allocated using the Rust allocator.
//
extern "C" struct SerializedData {
- /// Owns the memory.
+ // Owns the memory.
const char* data;
size_t len;
diff --git a/rust/internal.rs b/rust/internal.rs
index ec4fd00..ada1025 100644
--- a/rust/internal.rs
+++ b/rust/internal.rs
@@ -61,14 +61,17 @@
/// Unsafely dereference this slice.
///
/// # Safety
- /// - `ptr` must be valid for `len` bytes. It can be null or dangling if
- /// `self.len == 0`.
+ /// - `self.ptr` must be dereferencable and immutable for `self.len` bytes
+ /// for the lifetime `'a`. It can be null or dangling if `self.len == 0`.
pub unsafe fn as_ref<'a>(self) -> &'a [u8] {
if self.ptr.is_null() {
assert_eq!(self.len, 0, "Non-empty slice with null data pointer");
&[]
} else {
- slice::from_raw_parts(self.ptr, self.len)
+ // SAFETY:
+ // - `ptr` is non-null
+ // - `ptr` is valid for `len` bytes as promised by the caller.
+ unsafe { slice::from_raw_parts(self.ptr, self.len) }
}
}
}
diff --git a/rust/shared.rs b/rust/shared.rs
index f27b5bc..0e8b7d2 100644
--- a/rust/shared.rs
+++ b/rust/shared.rs
@@ -32,6 +32,19 @@
//!
//! For kernel-specific logic this crate delegates to the respective `__runtime`
//! crate.
+#![deny(unsafe_op_in_unsafe_fn)]
+
+use std::fmt;
+
+pub use optional::{AbsentField, FieldEntry, Optional, PresentField};
+pub use proxied::{Mut, MutProxy, Proxied, ProxiedWithPresence, SettableValue, View, ViewProxy};
+pub use string::{BytesMut, ProtoStr};
+
+/// Everything in `__internal` is allowed to change without it being considered
+/// a breaking change for the protobuf library. Nothing in here should be
+/// exported in `protobuf.rs`.
+#[path = "internal.rs"]
+pub mod __internal;
/// Everything in `__runtime` is allowed to change without it being considered
/// a breaking change for the protobuf library. Nothing in here should be
@@ -47,18 +60,6 @@
mod proxied;
mod string;
-pub use optional::{AbsentField, FieldEntry, Optional, PresentField};
-pub use proxied::{Mut, MutProxy, Proxied, ProxiedWithPresence, SettableValue, View, ViewProxy};
-pub use string::{BytesMut, ProtoStr};
-
-/// Everything in `__internal` is allowed to change without it being considered
-/// a breaking change for the protobuf library. Nothing in here should be
-/// exported in `protobuf.rs`.
-#[path = "internal.rs"]
-pub mod __internal;
-
-use std::fmt;
-
/// An error that happened during deserialization.
#[derive(Debug, Clone)]
pub struct ParseError;
diff --git a/rust/string.rs b/rust/string.rs
index 88464bf..fe9d827 100644
--- a/rust/string.rs
+++ b/rust/string.rs
@@ -31,7 +31,6 @@
//! Items specific to `bytes` and `string` fields.
#![allow(dead_code)]
#![allow(unused)]
-#![deny(unsafe_op_in_unsafe_fn)]
use crate::__internal::Private;
use crate::{Mut, MutProxy, Proxied, ProxiedWithPresence, SettableValue, View, ViewProxy};
diff --git a/rust/upb.rs b/rust/upb.rs
index 11c2246..e1e795a 100644
--- a/rust/upb.rs
+++ b/rust/upb.rs
@@ -37,7 +37,7 @@
use std::marker::PhantomData;
use std::mem::MaybeUninit;
use std::ops::Deref;
-use std::ptr::NonNull;
+use std::ptr::{self, NonNull};
use std::slice;
/// See `upb/port/def.inc`.
@@ -62,12 +62,14 @@
///
/// Note that this type is neither `Sync` nor `Send`.
pub struct Arena {
+ // Safety invariant: this must always be a valid arena
raw: RawArena,
_not_sync: PhantomData<UnsafeCell<()>>,
}
extern "C" {
- fn upb_Arena_New() -> RawArena;
+ // `Option<NonNull<T: Sized>>` is ABI-compatible with `*mut T`
+ fn upb_Arena_New() -> Option<RawArena>;
fn upb_Arena_Free(arena: RawArena);
fn upb_Arena_Malloc(arena: RawArena, size: usize) -> *mut u8;
fn upb_Arena_Realloc(arena: RawArena, ptr: *mut u8, old: usize, new: usize) -> *mut u8;
@@ -77,7 +79,19 @@
/// Allocates a fresh arena.
#[inline]
pub fn new() -> Self {
- Self { raw: unsafe { upb_Arena_New() }, _not_sync: PhantomData }
+ #[inline(never)]
+ #[cold]
+ fn arena_new_failed() -> ! {
+ panic!("Could not create a new UPB arena");
+ }
+
+ // SAFETY:
+ // - `upb_Arena_New` is assumed to be implemented correctly and always sound to
+ // call; if it returned a non-null pointer, it is a valid arena.
+ unsafe {
+ let Some(raw) = upb_Arena_New() else { arena_new_failed() };
+ Self { raw, _not_sync: PhantomData }
+ }
}
/// Returns the raw, UPB-managed pointer to the arena.
@@ -90,34 +104,54 @@
///
/// # Safety
///
- /// `layout`'s alignment must be less than `UPB_MALLOC_ALIGN`.
+ /// - `layout`'s alignment must be less than `UPB_MALLOC_ALIGN`.
#[inline]
pub unsafe fn alloc(&self, layout: Layout) -> &mut [MaybeUninit<u8>] {
debug_assert!(layout.align() <= UPB_MALLOC_ALIGN);
- let ptr = upb_Arena_Malloc(self.raw, layout.size());
+ // SAFETY: `self.raw` is a valid UPB arena
+ let ptr = unsafe { upb_Arena_Malloc(self.raw, layout.size()) };
if ptr.is_null() {
alloc::handle_alloc_error(layout);
}
- slice::from_raw_parts_mut(ptr.cast(), layout.size())
+ // SAFETY:
+ // - `upb_Arena_Malloc` promises that if the return pointer is non-null, it is
+ // dereferencable for `size` bytes and has an alignment of `UPB_MALLOC_ALIGN`
+ // until the arena is destroyed.
+ // - `[MaybeUninit<u8>]` has no alignment requirement, and `ptr` is aligned to a
+ // `UPB_MALLOC_ALIGN` boundary.
+ unsafe { slice::from_raw_parts_mut(ptr.cast(), layout.size()) }
}
/// Resizes some memory on the arena.
///
/// # Safety
///
- /// After calling this function, `ptr` is essentially zapped. `old` must
- /// be the layout `ptr` was allocated with via [`Arena::alloc()`]. `new`'s
- /// alignment must be less than `UPB_MALLOC_ALIGN`.
+ /// - `ptr` must be the data pointer returned by a previous call to `alloc`
+ /// or `resize` on `self`.
+ /// - After calling this function, `ptr` is no longer dereferencable - it is
+ /// zapped.
+ /// - `old` must be the layout `ptr` was allocated with via `alloc` or
+ /// `realloc`.
+ /// - `new`'s alignment must be less than `UPB_MALLOC_ALIGN`.
#[inline]
- pub unsafe fn resize(&self, ptr: *mut u8, old: Layout, new: Layout) -> &[MaybeUninit<u8>] {
+ pub unsafe fn resize(&self, ptr: *mut u8, old: Layout, new: Layout) -> &mut [MaybeUninit<u8>] {
debug_assert!(new.align() <= UPB_MALLOC_ALIGN);
- let ptr = upb_Arena_Realloc(self.raw, ptr, old.size(), new.size());
+ // SAFETY:
+ // - `self.raw` is a valid UPB arena
+ // - `ptr` was allocated by a previous call to `alloc` or `realloc` as promised
+ // by the caller.
+ let ptr = unsafe { upb_Arena_Realloc(self.raw, ptr, old.size(), new.size()) };
if ptr.is_null() {
alloc::handle_alloc_error(new);
}
- slice::from_raw_parts_mut(ptr.cast(), new.size())
+ // SAFETY:
+ // - `upb_Arena_Realloc` promises that if the return pointer is non-null, it is
+ // dereferencable for the new `size` in bytes until the arena is destroyed.
+ // - `[MaybeUninit<u8>]` has no alignment requirement, and `ptr` is aligned to a
+ // `UPB_MALLOC_ALIGN` boundary.
+ unsafe { slice::from_raw_parts_mut(ptr.cast(), new.size()) }
}
}
@@ -142,15 +176,28 @@
}
impl SerializedData {
+ /// Construct `SerializedData` from raw pointers and its owning arena.
+ ///
+ /// # Safety
+ /// - `arena` must be have allocated `data`
+ /// - `data` must be readable for `len` bytes and not mutate while this
+ /// struct exists
pub unsafe fn from_raw_parts(arena: Arena, data: NonNull<u8>, len: usize) -> Self {
SerializedData { _arena: arena, data, len }
}
+
+ /// Gets a raw slice pointer.
+ pub fn as_ptr(&self) -> *const [u8] {
+ ptr::slice_from_raw_parts(self.data.as_ptr(), self.len)
+ }
}
impl Deref for SerializedData {
type Target = [u8];
fn deref(&self) -> &Self::Target {
- unsafe { slice::from_raw_parts(self.data.as_ptr() as *const _, self.len) }
+ // SAFETY: `data` is valid for `len` bytes as promised by
+ // the caller of `SerializedData::from_raw_parts`.
+ unsafe { slice::from_raw_parts(self.data.as_ptr(), self.len) }
}
}