Skip to content

Add contracts for all functions in Alignment #136578

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions library/core/src/intrinsics/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3410,6 +3410,7 @@ pub const fn contract_checks() -> bool {
// doesn't honor `#[allow_internal_unstable]`, so for the const feature gate we use the user-facing
// `contracts` feature rather than the perma-unstable `contracts_internals`
#[rustc_const_unstable(feature = "contracts", issue = "128044")]
#[miri::intrinsic_fallback_is_spec]
#[lang = "contract_check_requires"]
#[rustc_intrinsic]
pub const fn contract_check_requires<C: Fn() -> bool + Copy>(cond: C) {
Expand Down Expand Up @@ -3438,6 +3439,7 @@ pub const fn contract_check_requires<C: Fn() -> bool + Copy>(cond: C) {
// `contracts` feature rather than the perma-unstable `contracts_internals`.
// Const-checking doesn't honor allow_internal_unstable logic used by contract expansion.
#[rustc_const_unstable(feature = "contracts", issue = "128044")]
#[miri::intrinsic_fallback_is_spec]
#[lang = "contract_check_ensures"]
#[rustc_intrinsic]
pub const fn contract_check_ensures<C: Fn(&Ret) -> bool + Copy, Ret>(cond: C, ret: Ret) -> Ret {
Expand All @@ -3459,6 +3461,7 @@ pub const fn contract_check_ensures<C: Fn(&Ret) -> bool + Copy, Ret>(cond: C, re
/// This is the old version of contract_check_ensures kept here for bootstrap only.
#[cfg(bootstrap)]
#[unstable(feature = "contracts_internals", issue = "128044" /* compiler-team#759 */)]
#[miri::intrinsic_fallback_is_spec]
#[rustc_intrinsic]
pub fn contract_check_ensures<'a, Ret, C: Fn(&'a Ret) -> bool>(ret: &'a Ret, cond: C) {
if contract_checks() && !cond(ret) {
Expand Down
1 change: 1 addition & 0 deletions library/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
//
// Library features:
// tidy-alphabetical-start
#![cfg_attr(not(bootstrap), feature(contracts))]
#![feature(array_ptr_get)]
#![feature(asm_experimental_arch)]
#![feature(bigint_helper_methods)]
Expand Down
2 changes: 2 additions & 0 deletions library/core/src/mem/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,8 @@ pub const fn align_of<T>() -> usize {
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_stable(feature = "const_align_of_val", since = "1.85.0")]
#[allow(deprecated)]
#[rustc_allow_const_fn_unstable(contracts)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should add rustc_const_stable_indirect to contract functions instead. @rust-lang/wg-const-eval?

Copy link
Member

@RalfJung RalfJung Mar 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which functions would that affect?

Ah, #136925 anyway needs to be resolved first.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd still like to find a solution that does not require rustc_allow_const_fn_unstable. As @celinval suggested, we should try to mark the contract functions as #[rustc_const_stable_indirect]; if that works, that's a better solution.

#[cfg_attr(not(bootstrap), core::contracts::ensures(|result: &usize| result.is_power_of_two()))]
pub const fn align_of_val<T: ?Sized>(val: &T) -> usize {
// SAFETY: val is a reference, so it's a valid raw pointer
unsafe { intrinsics::min_align_of_val(val) }
Expand Down
34 changes: 34 additions & 0 deletions library/core/src/ptr/alignment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ impl Alignment {
#[unstable(feature = "ptr_alignment_type", issue = "102070")]
#[inline]
#[must_use]
#[rustc_allow_const_fn_unstable(contracts)]
#[cfg_attr(not(bootstrap), core::contracts::ensures(
|result: &Alignment| result.as_usize().is_power_of_two()))]
pub const fn of<T>() -> Self {
// This can't actually panic since type alignment is always a power of two.
const { Alignment::new(align_of::<T>()).unwrap() }
Expand All @@ -54,6 +57,11 @@ impl Alignment {
/// Note that `0` is not a power of two, nor a valid alignment.
#[unstable(feature = "ptr_alignment_type", issue = "102070")]
#[inline]
#[rustc_allow_const_fn_unstable(contracts)]
#[cfg_attr(not(bootstrap), core::contracts::ensures(
move
|result: &Option<Alignment>| align.is_power_of_two() == result.is_some() &&
(result.is_none() || result.unwrap().as_usize() == align)))]
pub const fn new(align: usize) -> Option<Self> {
if align.is_power_of_two() {
// SAFETY: Just checked it only has one bit set
Expand All @@ -73,6 +81,12 @@ impl Alignment {
/// It must *not* be zero.
#[unstable(feature = "ptr_alignment_type", issue = "102070")]
#[inline]
#[cfg_attr(not(bootstrap), rustc_const_unstable(feature = "contracts", issue = "128044"))]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't feel right. I don't think you should add rustc_const_unstable here and below.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I don't do this I get:

error: const function that might be (indirectly) exposed to stable cannot use `#[feature(contracts)]`
  --> library/core/src/ptr/alignment.rs:82:5
   |
82 | /     #[cfg_attr(not(bootstrap), core::contracts::ensures(
83 | |             |result: &Alignment| result.as_usize() == align &&
84 | |             result.as_usize().is_power_of_two()))]
   | |__________________________________________________^
   |
help: if the function is not (yet) meant to be exposed to stable, add `#[rustc_const_unstable]` (this is what you probably want to do)
   |
82 +     #[rustc_const_unstable(feature = "...", issue = "...")]
83 |     #[cfg_attr(not(bootstrap), core::contracts::ensures(
   |
help: otherwise, as a last resort `#[rustc_allow_const_fn_unstable]` can be used to bypass stability checks (this requires team approval)
   |
82 +     #[rustc_allow_const_fn_unstable(contracts)]
83 |     #[cfg_attr(not(bootstrap), core::contracts::ensures(
   |

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the the same question as #136578 (comment). The error you get from the compiler is different since this function is unstable while align_of_val is stable, but the underlying problem is the same and we should use the same solution.

#[cfg_attr(not(bootstrap), core::contracts::requires(align.is_power_of_two()))]
#[cfg_attr(not(bootstrap), core::contracts::ensures(
move
|result: &Alignment| result.as_usize() == align &&
result.as_usize().is_power_of_two()))]
pub const unsafe fn new_unchecked(align: usize) -> Self {
assert_unsafe_precondition!(
check_language_ub,
Expand All @@ -88,13 +102,21 @@ impl Alignment {
/// Returns the alignment as a [`usize`].
#[unstable(feature = "ptr_alignment_type", issue = "102070")]
#[inline]
#[rustc_allow_const_fn_unstable(contracts)]
#[cfg_attr(not(bootstrap), core::contracts::ensures(
|result: &usize| result.is_power_of_two()))]
pub const fn as_usize(self) -> usize {
self.0 as usize
}

/// Returns the alignment as a <code>[NonZero]<[usize]></code>.
#[unstable(feature = "ptr_alignment_type", issue = "102070")]
#[inline]
#[rustc_allow_const_fn_unstable(contracts)]
#[cfg_attr(not(bootstrap), core::contracts::ensures(
move
|result: &NonZero<usize>| result.get().is_power_of_two() &&
result.get() == self.as_usize()))]
pub const fn as_nonzero(self) -> NonZero<usize> {
// This transmutes directly to avoid the UbCheck in `NonZero::new_unchecked`
// since there's no way for the user to trip that check anyway -- the
Expand All @@ -120,6 +142,12 @@ impl Alignment {
/// ```
#[unstable(feature = "ptr_alignment_type", issue = "102070")]
#[inline]
#[cfg_attr(not(bootstrap), rustc_const_unstable(feature = "contracts", issue = "128044"))]
#[cfg_attr(not(bootstrap), core::contracts::requires(self.as_usize().is_power_of_two()))]
#[cfg_attr(not(bootstrap), core::contracts::ensures(
move
|result: &u32| *result < usize::BITS &&
(1usize << *result) == self.as_usize()))]
pub const fn log2(self) -> u32 {
self.as_nonzero().trailing_zeros()
}
Expand Down Expand Up @@ -149,6 +177,12 @@ impl Alignment {
/// ```
#[unstable(feature = "ptr_alignment_type", issue = "102070")]
#[inline]
#[cfg_attr(not(bootstrap), rustc_const_unstable(feature = "contracts", issue = "128044"))]
#[cfg_attr(not(bootstrap), core::contracts::ensures(
move
|result: &usize| *result > 0 &&
*result == !(self.as_usize() -1) &&
self.as_usize() & *result == self.as_usize()))]
pub const fn mask(self) -> usize {
// SAFETY: The alignment is always nonzero, and therefore decrementing won't overflow.
!(unsafe { self.as_usize().unchecked_sub(1) })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,34 @@
scope 4 (inlined Unique::<[bool; 0]>::dangling) {
let mut _5: std::ptr::NonNull<[bool; 0]>;
scope 5 (inlined NonNull::<[bool; 0]>::dangling) {
let mut _6: std::num::NonZero<usize>;
let _6: std::ptr::Alignment;
let mut _7: std::num::NonZero<usize>;
scope 6 {
scope 8 (inlined std::ptr::Alignment::as_nonzero) {
scope 10 (inlined std::ptr::Alignment::as_nonzero) {
let mut _8: {closure@std::ptr::Alignment::as_nonzero::{closure#0}};
let mut _9: std::num::NonZero<usize>;
scope 11 {
}
scope 12 (inlined core::contracts::build_check_ensures::<NonZero<usize>, {closure@std::ptr::Alignment::as_nonzero::{closure#0}}>) {
}
}
scope 9 (inlined NonNull::<[bool; 0]>::without_provenance) {
let _7: *const [bool; 0];
scope 10 {
scope 13 (inlined NonNull::<[bool; 0]>::without_provenance) {
let _10: *const [bool; 0];
scope 14 {
}
scope 11 (inlined NonZero::<usize>::get) {
scope 15 (inlined NonZero::<usize>::get) {
}
scope 12 (inlined std::ptr::without_provenance::<[bool; 0]>) {
scope 13 (inlined without_provenance_mut::<[bool; 0]>) {
scope 16 (inlined std::ptr::without_provenance::<[bool; 0]>) {
scope 17 (inlined without_provenance_mut::<[bool; 0]>) {
}
}
}
}
scope 7 (inlined std::ptr::Alignment::of::<[bool; 0]>) {
scope 8 {
}
scope 9 (inlined core::contracts::build_check_ensures::<std::ptr::Alignment, {closure@std::ptr::Alignment::of<[bool; 0]>::{closure#0}}>) {
}
}
}
}
Expand All @@ -45,33 +56,42 @@
StorageLive(_4);
StorageLive(_5);
StorageLive(_6);
_6 = const NonZero::<usize>(core::num::niche_types::NonZeroUsizeInner(1_usize));
_6 = contract_check_ensures::<{closure@std::ptr::Alignment::of<[bool; 0]>::{closure#0}}, std::ptr::Alignment>(const ZeroSized: {closure@std::ptr::Alignment::of<[bool; 0]>::{closure#0}}, const std::ptr::Alignment::of::<[bool; 0]>::{constant#0}) -> [return: bb2, unwind unreachable];
}

bb1: {
StorageDead(_1);
return;
}

bb2: {
StorageLive(_7);
_7 = const {0x1 as *const [bool; 0]};
_5 = const NonNull::<[bool; 0]> {{ pointer: {0x1 as *const [bool; 0]} }};
StorageLive(_8);
_8 = {closure@$SRC_DIR/core/src/ptr/alignment.rs:LL:COL} { 0: copy _6 };
StorageLive(_9);
_9 = copy _6 as std::num::NonZero<usize> (Transmute);
_7 = contract_check_ensures::<{closure@std::ptr::Alignment::as_nonzero::{closure#0}}, NonZero<usize>>(move _8, move _9) -> [return: bb3, unwind unreachable];
}

bb3: {
StorageDead(_9);
StorageDead(_8);
StorageLive(_10);
_10 = copy _7 as *const [bool; 0] (Transmute);
_5 = NonNull::<[bool; 0]> { pointer: copy _10 };
StorageDead(_10);
StorageDead(_7);
StorageDead(_6);
_4 = const Unique::<[bool; 0]> {{ pointer: NonNull::<[bool; 0]> {{ pointer: {0x1 as *const [bool; 0]} }}, _marker: PhantomData::<[bool; 0]> }};
_4 = Unique::<[bool; 0]> { pointer: move _5, _marker: const PhantomData::<[bool; 0]> };
StorageDead(_5);
_3 = const Unique::<[bool]> {{ pointer: NonNull::<[bool]> {{ pointer: Indirect { alloc_id: ALLOC0, offset: Size(0 bytes) }: *const [bool] }}, _marker: PhantomData::<[bool]> }};
_3 = move _4 as std::ptr::Unique<[bool]> (PointerCoercion(Unsize, Implicit));
StorageDead(_4);
_2 = const Box::<[bool]>(Unique::<[bool]> {{ pointer: NonNull::<[bool]> {{ pointer: Indirect { alloc_id: ALLOC1, offset: Size(0 bytes) }: *const [bool] }}, _marker: PhantomData::<[bool]> }}, std::alloc::Global);
_2 = Box::<[bool]>(copy _3, const std::alloc::Global);
StorageDead(_3);
_1 = const A {{ foo: Box::<[bool]>(Unique::<[bool]> {{ pointer: NonNull::<[bool]> {{ pointer: Indirect { alloc_id: ALLOC2, offset: Size(0 bytes) }: *const [bool] }}, _marker: PhantomData::<[bool]> }}, std::alloc::Global) }};
_1 = A { foo: move _2 };
StorageDead(_2);
_0 = const ();
drop(_1) -> [return: bb1, unwind unreachable];
}

bb1: {
StorageDead(_1);
return;
}
}

ALLOC2 (size: 8, align: 4) { .. }

ALLOC1 (size: 8, align: 4) { .. }

ALLOC0 (size: 8, align: 4) { .. }

Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,34 @@
scope 4 (inlined Unique::<[bool; 0]>::dangling) {
let mut _5: std::ptr::NonNull<[bool; 0]>;
scope 5 (inlined NonNull::<[bool; 0]>::dangling) {
let mut _6: std::num::NonZero<usize>;
let _6: std::ptr::Alignment;
let mut _7: std::num::NonZero<usize>;
scope 6 {
scope 8 (inlined std::ptr::Alignment::as_nonzero) {
scope 10 (inlined std::ptr::Alignment::as_nonzero) {
let mut _8: {closure@std::ptr::Alignment::as_nonzero::{closure#0}};
let mut _9: std::num::NonZero<usize>;
scope 11 {
}
scope 12 (inlined core::contracts::build_check_ensures::<NonZero<usize>, {closure@std::ptr::Alignment::as_nonzero::{closure#0}}>) {
}
}
scope 9 (inlined NonNull::<[bool; 0]>::without_provenance) {
let _7: *const [bool; 0];
scope 10 {
scope 13 (inlined NonNull::<[bool; 0]>::without_provenance) {
let _10: *const [bool; 0];
scope 14 {
}
scope 11 (inlined NonZero::<usize>::get) {
scope 15 (inlined NonZero::<usize>::get) {
}
scope 12 (inlined std::ptr::without_provenance::<[bool; 0]>) {
scope 13 (inlined without_provenance_mut::<[bool; 0]>) {
scope 16 (inlined std::ptr::without_provenance::<[bool; 0]>) {
scope 17 (inlined without_provenance_mut::<[bool; 0]>) {
}
}
}
}
scope 7 (inlined std::ptr::Alignment::of::<[bool; 0]>) {
scope 8 {
}
scope 9 (inlined core::contracts::build_check_ensures::<std::ptr::Alignment, {closure@std::ptr::Alignment::of<[bool; 0]>::{closure#0}}>) {
}
}
}
}
Expand All @@ -45,22 +56,7 @@
StorageLive(_4);
StorageLive(_5);
StorageLive(_6);
_6 = const NonZero::<usize>(core::num::niche_types::NonZeroUsizeInner(1_usize));
StorageLive(_7);
_7 = const {0x1 as *const [bool; 0]};
_5 = const NonNull::<[bool; 0]> {{ pointer: {0x1 as *const [bool; 0]} }};
StorageDead(_7);
StorageDead(_6);
_4 = const Unique::<[bool; 0]> {{ pointer: NonNull::<[bool; 0]> {{ pointer: {0x1 as *const [bool; 0]} }}, _marker: PhantomData::<[bool; 0]> }};
StorageDead(_5);
_3 = const Unique::<[bool]> {{ pointer: NonNull::<[bool]> {{ pointer: Indirect { alloc_id: ALLOC0, offset: Size(0 bytes) }: *const [bool] }}, _marker: PhantomData::<[bool]> }};
StorageDead(_4);
_2 = const Box::<[bool]>(Unique::<[bool]> {{ pointer: NonNull::<[bool]> {{ pointer: Indirect { alloc_id: ALLOC1, offset: Size(0 bytes) }: *const [bool] }}, _marker: PhantomData::<[bool]> }}, std::alloc::Global);
StorageDead(_3);
_1 = const A {{ foo: Box::<[bool]>(Unique::<[bool]> {{ pointer: NonNull::<[bool]> {{ pointer: Indirect { alloc_id: ALLOC2, offset: Size(0 bytes) }: *const [bool] }}, _marker: PhantomData::<[bool]> }}, std::alloc::Global) }};
StorageDead(_2);
_0 = const ();
drop(_1) -> [return: bb1, unwind: bb2];
_6 = contract_check_ensures::<{closure@std::ptr::Alignment::of<[bool; 0]>::{closure#0}}, std::ptr::Alignment>(const ZeroSized: {closure@std::ptr::Alignment::of<[bool; 0]>::{closure#0}}, const std::ptr::Alignment::of::<[bool; 0]>::{constant#0}) -> [return: bb3, unwind continue];
}

bb1: {
Expand All @@ -71,11 +67,35 @@
bb2 (cleanup): {
resume;
}
}

ALLOC2 (size: 8, align: 4) { .. }

ALLOC1 (size: 8, align: 4) { .. }
bb3: {
StorageLive(_7);
StorageLive(_8);
_8 = {closure@$SRC_DIR/core/src/ptr/alignment.rs:LL:COL} { 0: copy _6 };
StorageLive(_9);
_9 = copy _6 as std::num::NonZero<usize> (Transmute);
_7 = contract_check_ensures::<{closure@std::ptr::Alignment::as_nonzero::{closure#0}}, NonZero<usize>>(move _8, move _9) -> [return: bb4, unwind continue];
}

ALLOC0 (size: 8, align: 4) { .. }
bb4: {
StorageDead(_9);
StorageDead(_8);
StorageLive(_10);
_10 = copy _7 as *const [bool; 0] (Transmute);
_5 = NonNull::<[bool; 0]> { pointer: copy _10 };
StorageDead(_10);
StorageDead(_7);
StorageDead(_6);
_4 = Unique::<[bool; 0]> { pointer: move _5, _marker: const PhantomData::<[bool; 0]> };
StorageDead(_5);
_3 = move _4 as std::ptr::Unique<[bool]> (PointerCoercion(Unsize, Implicit));
StorageDead(_4);
_2 = Box::<[bool]>(copy _3, const std::alloc::Global);
StorageDead(_3);
_1 = A { foo: move _2 };
StorageDead(_2);
_0 = const ();
drop(_1) -> [return: bb1, unwind: bb2];
}
}

Loading
Loading