Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Challenge 27: Verify atomically reference-counted Cell implementation

  • Status: Open
  • Solution: Option field to point to the PR that solved this challenge.
  • Tracking Issue: #383
  • Start date: 2025/06/01
  • End date: 2025/12/31
  • Reward: 10,000 USD

Goal

The goal of this challenge is to verify Arc (meaning "Atomically reference counted") and its related Weak implementation. Arc is the library-provided building block that enables safe multiple ownership of data through reference counting for multi-threaded code, as opposed to the usual ownership types used by Rust. The Rc implementation is the subject of a related challenge.

Motivation

The Rc (for single-threaded code) and Arc (atomic multi-threaded) cell types are widely used in Rust programs to enable shared ownership of data through reference counting. Since shared ownership is generally not permitted by Rust's type system, these implementations use unsafe code to bypass Rust's usual compile-time checks. Verifying the Rust standard library thus fundamentally requires verification of these types.

Description

This challenge verifies a number of Arc methods that encapsulate unsafety, as well as providing contracts for unsafe methods that impose safety conditions on their callers for correct use.

A key part of the Arc implementation is the Weak struct, which allows keeping a temporary reference to an Arc without creating circular references and without protecting the inner value from being dropped.

Assumptions

Some properties needed for safety are beyond the ability of the Rust type system to express. This is true for all challenges, but we point out some of the properties that are relevant for this challenge.

  • You are not required to check that the reference count is greater than 0 when it is being decremented.

  • Showing that something is initialized, as required by assume_init, appears to be beyond the current state of the art for type systems, so it may be impossible to express the full safety property required there.

Success Criteria

All the following pub unsafe functions must be annotated with safety contracts and the contracts have been verified:

FunctionLocation
Arc<mem::MaybeUninit<T>,A>::assume_initalloc::sync
Arc<[mem::MaybeUninit<T>],A>::assume_initalloc::sync
Arc<T:?Sized>::from_rawalloc::sync
Arc<T:?Sized>::increment_strong_countalloc::sync
Arc<T:?Sized>::decrement_strong_countalloc::sync
Arc<T:?Sized,A:Allocator>::from_raw_inalloc::sync
Arc<T:?Sized,A:Allocator>::increment_strong_cobunt_inalloc::sync
Arc<T:?Sized,A:Allocator>::decrement_strong_count_inalloc::sync
Arc<T:?Sized,A:Allocator>::get_mut_uncheckedalloc::sync
Arc<dyn Any+Send+Sync,A:Allocator>::downcast_uncheckedalloc::sync
Weak<T:?Sized>::from_rawalloc::sync
Weak<T:?Sized,A:Allocator>::from_raw_inalloc::sync

These (not necessarily public) functions contain unsafe code in their bodies but are not themselves marked unsafe. At least 75% of these should be proven unconditionally safe, or safety contracts should be added.

FunctionLocation
`Arc<T:?Sized, A:Allocator>::into_inner_with_allocatoralloc::sync
Arc<T>::newalloc::sync
Arc<T>::new_uninitalloc::sync
Arc<T>::new_zeroedalloc::sync
Arc<T>::pinalloc::sync
Arc<T>::try_pinalloc::sync
Arc<T>::try_newalloc::sync
Arc<T>::try_new_uninitalloc::sync
Arc<T>::try_new_zeroedalloc::sync
Arc<T,A:Allocator>::new_inalloc::sync
Arc<T,A:Allocator>::new_uninit_inalloc::sync
Arc<T,A:Allocator>::new_zeroed_inalloc::sync
Arc<T,A:Allocator>::new_cyclic_inalloc::sync
Arc<T,A:Allocator>::pin_inalloc::sync
Arc<T,A:Allocator>::try_pin_inalloc::sync
Arc<T,A:Allocator>::try_new_inalloc::sync
Arc<T,A:Allocator>::try_new_uninit_inalloc::sync
Arc<T,A:Allocator>::try_new_zeroed_inalloc::sync
Arc<T,A:Allocator>::try_unwrap]alloc::sync
Arc<T,A:Allocator>::into_inneralloc::sync
Arc<[T]>::new_uninit_slicealloc::sync
Arc<[T]>::new_zeroed_slicealloc::sync
Arc<[T]>::into_arrayalloc::sync
Arc<[T],A:Allocator>::new_uninit_slice_inalloc::sync
Arc<[T],A:Allocator>::new_zeroed_slice_inalloc::sync
Arc<T:?Sized,A:Allocator>::into_raw_with_allocatoralloc::sync
Arc<T:?Sized,A:Allocator>::as_ptralloc::sync
Arc<T:?Sized,A:Allocator>::inneralloc::sync
Arc<T:?Sized,A:Allocator>::from_box_inalloc::sync
ArcFromSlice<T: Clone>::from_slicealloc::sync
ArcFromSlice<T: Copy>::from_slicealloc::sync
Clone<T: ?Sized, A:Allocator>::clone for Arcalloc::sync
Arc<T:?Sized+CloneToUninit, A:Allocator+Clone>::make_mutalloc::sync
Arc<T:?Sized, A:Allocator>::get_mutalloc::sync
Drop<T: ?Sized, A:Allocator>::drop for Arcalloc::sync
Arc<dyn Any+Send+Sync,A:Allocator>::downcastalloc::sync
Weak<T:?Sized,A:Allocator>::as_ptralloc::sync
Weak<T:?Sized,A:Allocator>::into_raw_with_allocatoralloc::sync
Weak<T:?Sized,A:Allocator>::upgradealloc::sync
Weak<T:?Sized,A:Allocator>::inneralloc::sync
Drop<T:?Sized, A:Allocator>::drop for Weakalloc::sync
Default<T:Default>::defaultalloc::sync
Default<str>::defaultalloc::sync
Default<core::ffi::CStr>::defaultalloc::sync
Default<[T]>::defaultalloc::sync
From<&Str>::fromalloc::sync
From<Vec<T,A:Allocator+Clone>>::fromalloc::sync
From<Arc<str>>::fromalloc::sync
TryFrom<Arc[T],A:Allocator>::try_fromalloc::sync
ToArcSlice<T, I>::to_arc_slicealloc::sync
UniqueArcUninit<T:?Sized, A:Allocator>::new]alloc::sync
UniqueArcUninit<T:?Sized, A:Allocator>::data_ptralloc::sync
Drop<T:?Sized, A:Allocator>::drop for UniqueArcUninitalloc::sync
UniqueArc<T:?Sized,A:Allocator>::into_arcalloc::sync
UniqueArc<T:?Sized,A:Allocator+Clone>::downgradealloc::sync
Deref<T:?Sized,A:Allocator>::derefalloc::sync
DerefMut<T:?Sized,A:Allocator>::deref_mutalloc::sync
Drop<T:?Sized, A:Allocator>::drop for UniqueArcalloc::sync

This list excludes non-public unsafe functions; relevant ones should be called from public unsafe functions.

For functions taking inputs of generic type 'T', the proofs can be limited to primitive types o nly. Moreover, for functions taking allocators as input, the proofs can be limited to only the allocators implemented in the standard library (Global/System).

Note that solving this challenge will in part require proving an absence of data races from methods using atomic types (in this case Arc). This is also one of the main difficulties of Challenge 7. It's likely that a technique to do this for one challenge could be used for the other, with some adaptation.

List of UBs

In addition to any properties called out as SAFETY comments in the source code, all proofs must automatically ensure the absence of the following undefined behaviors:

  • Data races.
  • Accessing (loading from or storing to) a place that is dangling or based on a misaligned pointer.
  • Invoking undefined behavior via compiler intrinsics.
  • Mutating immutable bytes.
  • Producing an invalid value.

Note: All solutions to verification challenges need to satisfy the criteria established in the challenge book in addition to the ones listed above.