From f48351c41eee394d09ae4682e9aefbd87e54ca82 Mon Sep 17 00:00:00 2001 From: Jake Hughes Date: Thu, 14 Sep 2023 12:40:17 +0100 Subject: [PATCH] Add wrapper type for opting out of FSA This can be very useful when using types from third party crates which do not implement `FinalizerSafe`. Instead of implementing `FinalizerSafe` on any `T` with a `!FinalizerSafe` field, we can now ensure each problematic field uses the `FinalizeUnchecked` wrapper. This is safer than a blanket implementation. --- library/core/src/gc.rs | 42 ++++++++++++++++++++++ tests/ui/runtime/gc/check_finalizers.rs | 4 ++- tests/ui/runtime/gc/unchecked_finalizer.rs | 38 ++++++++++++++++++++ 3 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 tests/ui/runtime/gc/unchecked_finalizer.rs diff --git a/library/core/src/gc.rs b/library/core/src/gc.rs index 04b90fac3536e..8f3ca703ac372 100644 --- a/library/core/src/gc.rs +++ b/library/core/src/gc.rs @@ -47,6 +47,48 @@ impl DerefMut for NonFinalizable { } } +/// A wrapper to prevent alloy from performing Finaliser Safety Analysis (FSA) +/// on `T`. +/// +/// FSA is a compile-time analysis performed by alloy which checks whether it is +/// sound to call a type's drop method by a garbage collection finaliser. It +/// works by looking at each line in T's drop method for potential soundness +/// violations. +/// +/// However, where this is too strict -- and the user knows T::drop to be sound +/// -- `FinalizeUnchecked` can be used to opt-out of FSA. This is preferable to +/// implementing the `FinalizerSafe` trait for `T` as `FinalizeUnchecked` +/// applies only to individual uses of `T`. +#[unstable(feature = "gc", issue = "none")] +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct FinalizeUnchecked(T); + +impl FinalizeUnchecked { + pub unsafe fn new(value: T) -> Self { + FinalizeUnchecked(value) + } +} + +#[unstable(feature = "gc", issue = "none")] +impl Deref for FinalizeUnchecked { + type Target = T; + #[inline(always)] + fn deref(&self) -> &T { + &self.0 + } +} + +#[unstable(feature = "gc", issue = "none")] +impl DerefMut for FinalizeUnchecked { + #[inline(always)] + fn deref_mut(&mut self) -> &mut T { + &mut self.0 + } +} + +#[cfg(not(bootstrap))] +unsafe impl FinalizerSafe for FinalizeUnchecked {} + #[unstable(feature = "gc", issue = "none")] #[cfg_attr(not(test), rustc_diagnostic_item = "ReferenceFree")] pub auto trait ReferenceFree {} diff --git a/tests/ui/runtime/gc/check_finalizers.rs b/tests/ui/runtime/gc/check_finalizers.rs index 6471cfefdae67..705fc7d806196 100644 --- a/tests/ui/runtime/gc/check_finalizers.rs +++ b/tests/ui/runtime/gc/check_finalizers.rs @@ -2,7 +2,7 @@ #![feature(negative_impls)] use std::cell::Cell; -use std::gc::Gc; +use std::gc::{Gc, FinalizeUnchecked}; use std::marker::FinalizerSafe; use std::rc::Rc; @@ -61,4 +61,6 @@ fn main() { let self_call = ShouldFail2(123 as *mut u8); Gc::new(self_call); //~ ERROR: `self_call` cannot be safely finalized. + + unsafe { Gc::new(FinalizeUnchecked::new(ShouldFail(Cell::new(123)))) }; } diff --git a/tests/ui/runtime/gc/unchecked_finalizer.rs b/tests/ui/runtime/gc/unchecked_finalizer.rs new file mode 100644 index 0000000000000..e0807b23931f0 --- /dev/null +++ b/tests/ui/runtime/gc/unchecked_finalizer.rs @@ -0,0 +1,38 @@ +// run-pass +// ignore-tidy-linelength +#![feature(gc)] +#![feature(rustc_private)] +#![feature(negative_impls)] + +use std::gc::{Gc, GcAllocator, FinalizeUnchecked}; +use std::sync::atomic::{self, AtomicUsize}; + +struct UnsafeContainer(usize); + +impl Drop for UnsafeContainer { + fn drop(&mut self) { + FINALIZER_COUNT.fetch_add(1, atomic::Ordering::Relaxed); + } +} + +impl !FinalizerSafe for UnsafeContainer {} + +static FINALIZER_COUNT: AtomicUsize = AtomicUsize::new(0); +static ALLOCATED_COUNT: usize = 100; + +fn foo() { + for i in 0..ALLOCATED_COUNT { + { + let mut _gc = unsafe { Some(Gc::new(FinalizeUnchecked::new(UnsafeContainer(i)))) }; + + // Zero the root to the GC object. + _gc = None; + } + } +} + +fn main() { + foo(); + GcAllocator::force_gc(); + assert_eq!(FINALIZER_COUNT.load(atomic::Ordering::Relaxed), ALLOCATED_COUNT); +}