Skip to content

refactor check_{lang,library}_ub: use a single intrinsic #122629

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

Merged
merged 2 commits into from
Mar 23, 2024
Merged
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
2 changes: 1 addition & 1 deletion compiler/rustc_borrowck/src/type_check/mod.rs
Original file line number Diff line number Diff line change
@@ -2000,7 +2000,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
ConstraintCategory::SizedBound,
);
}
&Rvalue::NullaryOp(NullOp::UbCheck(_), _) => {}
&Rvalue::NullaryOp(NullOp::UbChecks, _) => {}

Rvalue::ShallowInitBox(operand, ty) => {
self.check_operand(operand, location);
2 changes: 1 addition & 1 deletion compiler/rustc_codegen_cranelift/src/base.rs
Original file line number Diff line number Diff line change
@@ -780,7 +780,7 @@ fn codegen_stmt<'tcx>(
NullOp::OffsetOf(fields) => {
layout.offset_of_subfield(fx, fields.iter()).bytes()
}
NullOp::UbCheck(_) => {
NullOp::UbChecks => {
let val = fx.tcx.sess.opts.debug_assertions;
let val = CValue::by_val(
fx.bcx.ins().iconst(types::I8, i64::try_from(val).unwrap()),
3 changes: 1 addition & 2 deletions compiler/rustc_codegen_ssa/src/mir/rvalue.rs
Original file line number Diff line number Diff line change
@@ -680,8 +680,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
let val = layout.offset_of_subfield(bx.cx(), fields.iter()).bytes();
bx.cx().const_usize(val)
}
mir::NullOp::UbCheck(_) => {
// In codegen, we want to check for language UB and library UB
mir::NullOp::UbChecks => {
let val = bx.tcx().sess.opts.debug_assertions;
bx.cx().const_bool(val)
}
12 changes: 1 addition & 11 deletions compiler/rustc_const_eval/src/interpret/step.rs
Original file line number Diff line number Diff line change
@@ -258,17 +258,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
let val = layout.offset_of_subfield(self, fields.iter()).bytes();
Scalar::from_target_usize(val, self)
}
mir::NullOp::UbCheck(kind) => {
// We want to enable checks for library UB, because the interpreter doesn't
// know about those on its own.
// But we want to disable checks for language UB, because the interpreter
// has its own better checks for that.
let should_check = match kind {
mir::UbKind::LibraryUb => self.tcx.sess.opts.debug_assertions,
mir::UbKind::LanguageUb => false,
};
Scalar::from_bool(should_check)
}
mir::NullOp::UbChecks => Scalar::from_bool(self.tcx.sess.opts.debug_assertions),
};
self.write_scalar(val, &dest)?;
}
Original file line number Diff line number Diff line change
@@ -558,7 +558,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
Rvalue::Cast(_, _, _) => {}

Rvalue::NullaryOp(
NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(_) | NullOp::UbCheck(_),
NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(_) | NullOp::UbChecks,
_,
) => {}
Rvalue::ShallowInitBox(_, _) => {}
2 changes: 1 addition & 1 deletion compiler/rustc_const_eval/src/transform/validate.rs
Original file line number Diff line number Diff line change
@@ -1168,7 +1168,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
Rvalue::Repeat(_, _)
| Rvalue::ThreadLocalRef(_)
| Rvalue::AddressOf(_, _)
| Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::UbCheck(_), _)
| Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::UbChecks, _)
| Rvalue::Discriminant(_) => {}
}
self.super_rvalue(rvalue, location);
5 changes: 2 additions & 3 deletions compiler/rustc_hir_analysis/src/check/intrinsic.rs
Original file line number Diff line number Diff line change
@@ -127,8 +127,7 @@ pub fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: LocalDefId) -
| sym::variant_count
| sym::is_val_statically_known
| sym::ptr_mask
| sym::check_language_ub
| sym::check_library_ub
| sym::ub_checks
| sym::fadd_algebraic
| sym::fsub_algebraic
| sym::fmul_algebraic
@@ -571,7 +570,7 @@ pub fn check_intrinsic_type(
(0, 0, vec![Ty::new_imm_ptr(tcx, Ty::new_unit(tcx))], tcx.types.usize)
}

sym::check_language_ub | sym::check_library_ub => (0, 1, Vec::new(), tcx.types.bool),
sym::ub_checks => (0, 1, Vec::new(), tcx.types.bool),

sym::simd_eq
| sym::simd_ne
2 changes: 1 addition & 1 deletion compiler/rustc_middle/src/mir/mod.rs
Original file line number Diff line number Diff line change
@@ -796,7 +796,7 @@ impl<'tcx> Body<'tcx> {
}

match rvalue {
Rvalue::NullaryOp(NullOp::UbCheck(_), _) => {
Rvalue::NullaryOp(NullOp::UbChecks, _) => {
Some((tcx.sess.opts.debug_assertions as u128, targets))
}
Rvalue::Use(Operand::Constant(constant)) => {
2 changes: 1 addition & 1 deletion compiler/rustc_middle/src/mir/pretty.rs
Original file line number Diff line number Diff line change
@@ -944,7 +944,7 @@ impl<'tcx> Debug for Rvalue<'tcx> {
NullOp::SizeOf => write!(fmt, "SizeOf({t})"),
NullOp::AlignOf => write!(fmt, "AlignOf({t})"),
NullOp::OffsetOf(fields) => write!(fmt, "OffsetOf({t}, {fields:?})"),
NullOp::UbCheck(kind) => write!(fmt, "UbCheck({kind:?})"),
NullOp::UbChecks => write!(fmt, "UbChecks()"),
Copy link
Member

@saethlin saethlin Mar 21, 2024

Choose a reason for hiding this comment

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

I don't see any corresponding changes to the mir-opt tests, which has me worried. CI is green but the mir-opt suite does not pass locally. Do you know what's happening here? I'm confident that this either will fail in bors or should fail in bors, so at the least the mir-opt tests need to be blessed, and maybe we also have a CI/bootstrap bug.

Copy link
Member Author

Choose a reason for hiding this comment

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

Good point. I think this happens because of

//@ ignore-debug assertions change the output MIR

The PR runner has debug assertions turned on.

Copy link
Member

Choose a reason for hiding this comment

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

Ah yes this old problem. I'll see if I can do a PR to fix it.

Copy link
Member Author

Choose a reason for hiding this comment

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

Is that even still needed? The stdlib is less dependent on the sysroot cfg(debug_assertions) now with your new intrinsic.

Copy link
Member

Choose a reason for hiding this comment

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

There is less need for it, but it's not gone entirely. I'll start by just enabling the tests that can be enabled for free: #122921

}
}
ThreadLocalRef(did) => ty::tls::with(|tcx| {
13 changes: 3 additions & 10 deletions compiler/rustc_middle/src/mir/syntax.rs
Original file line number Diff line number Diff line change
@@ -1367,16 +1367,9 @@ pub enum NullOp<'tcx> {
AlignOf,
/// Returns the offset of a field
OffsetOf(&'tcx List<(VariantIdx, FieldIdx)>),
/// Returns whether we want to check for library UB or language UB at monomorphization time.
/// Both kinds of UB evaluate to `true` in codegen, and only library UB evalutes to `true` in
/// const-eval/Miri, because the interpreter has its own better checks for language UB.
UbCheck(UbKind),
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, TyEncodable, TyDecodable, Hash, HashStable)]
pub enum UbKind {
LanguageUb,
LibraryUb,
/// Returns whether we want to check for UB.
/// This returns the value of `cfg!(debug_assertions)` at monomorphization time.
UbChecks,
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
2 changes: 1 addition & 1 deletion compiler/rustc_middle/src/mir/tcx.rs
Original file line number Diff line number Diff line change
@@ -194,7 +194,7 @@ impl<'tcx> Rvalue<'tcx> {
Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(..), _) => {
tcx.types.usize
}
Rvalue::NullaryOp(NullOp::UbCheck(_), _) => tcx.types.bool,
Rvalue::NullaryOp(NullOp::UbChecks, _) => tcx.types.bool,
Rvalue::Aggregate(ref ak, ref ops) => match **ak {
AggregateKind::Array(ty) => Ty::new_array(tcx, ty, ops.len() as u64),
AggregateKind::Tuple => {
2 changes: 1 addition & 1 deletion compiler/rustc_mir_dataflow/src/move_paths/builder.rs
Original file line number Diff line number Diff line change
@@ -433,7 +433,7 @@ impl<'b, 'a, 'tcx, F: Fn(Ty<'tcx>) -> bool> Gatherer<'b, 'a, 'tcx, F> {
| Rvalue::Discriminant(..)
| Rvalue::Len(..)
| Rvalue::NullaryOp(
NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(..) | NullOp::UbCheck(_),
NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(..) | NullOp::UbChecks,
_,
) => {}
}
2 changes: 1 addition & 1 deletion compiler/rustc_mir_transform/src/gvn.rs
Original file line number Diff line number Diff line change
@@ -487,7 +487,7 @@ impl<'body, 'tcx> VnState<'body, 'tcx> {
NullOp::OffsetOf(fields) => {
layout.offset_of_subfield(&self.ecx, fields.iter()).bytes()
}
NullOp::UbCheck(_) => return None,
NullOp::UbChecks => return None,
};
let usize_layout = self.ecx.layout_of(self.tcx.types.usize).unwrap();
let imm = ImmTy::try_from_uint(val, usize_layout)?;
2 changes: 1 addition & 1 deletion compiler/rustc_mir_transform/src/known_panics_lint.rs
Original file line number Diff line number Diff line change
@@ -639,7 +639,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
NullOp::OffsetOf(fields) => {
op_layout.offset_of_subfield(self, fields.iter()).bytes()
}
NullOp::UbCheck(_) => return None,
NullOp::UbChecks => return None,
};
ImmTy::from_scalar(Scalar::from_target_usize(val, self), layout).into()
}
21 changes: 2 additions & 19 deletions compiler/rustc_mir_transform/src/lower_intrinsics.rs
Original file line number Diff line number Diff line change
@@ -20,30 +20,13 @@ impl<'tcx> MirPass<'tcx> for LowerIntrinsics {
sym::unreachable => {
terminator.kind = TerminatorKind::Unreachable;
}
sym::check_language_ub => {
sym::ub_checks => {
let target = target.unwrap();
block.statements.push(Statement {
source_info: terminator.source_info,
kind: StatementKind::Assign(Box::new((
*destination,
Rvalue::NullaryOp(
NullOp::UbCheck(UbKind::LanguageUb),
tcx.types.bool,
),
))),
});
terminator.kind = TerminatorKind::Goto { target };
}
sym::check_library_ub => {
let target = target.unwrap();
block.statements.push(Statement {
source_info: terminator.source_info,
kind: StatementKind::Assign(Box::new((
*destination,
Rvalue::NullaryOp(
NullOp::UbCheck(UbKind::LibraryUb),
tcx.types.bool,
),
Rvalue::NullaryOp(NullOp::UbChecks, tcx.types.bool),
))),
});
terminator.kind = TerminatorKind::Goto { target };
2 changes: 1 addition & 1 deletion compiler/rustc_mir_transform/src/promote_consts.rs
Original file line number Diff line number Diff line change
@@ -446,7 +446,7 @@ impl<'tcx> Validator<'_, 'tcx> {
NullOp::SizeOf => {}
NullOp::AlignOf => {}
NullOp::OffsetOf(_) => {}
NullOp::UbCheck(_) => {}
NullOp::UbChecks => {}
},

Rvalue::ShallowInitBox(_, _) => return Err(Unpromotable),
8 changes: 1 addition & 7 deletions compiler/rustc_smir/src/rustc_smir/convert/mir.rs
Original file line number Diff line number Diff line change
@@ -251,19 +251,13 @@ impl<'tcx> Stable<'tcx> for mir::NullOp<'tcx> {
type T = stable_mir::mir::NullOp;
fn stable(&self, tables: &mut Tables<'_>) -> Self::T {
use rustc_middle::mir::NullOp::*;
use rustc_middle::mir::UbKind;
match self {
SizeOf => stable_mir::mir::NullOp::SizeOf,
AlignOf => stable_mir::mir::NullOp::AlignOf,
OffsetOf(indices) => stable_mir::mir::NullOp::OffsetOf(
indices.iter().map(|idx| idx.stable(tables)).collect(),
),
UbCheck(UbKind::LanguageUb) => {
stable_mir::mir::NullOp::UbCheck(stable_mir::mir::UbKind::LanguageUb)
}
UbCheck(UbKind::LibraryUb) => {
stable_mir::mir::NullOp::UbCheck(stable_mir::mir::UbKind::LibraryUb)
}
UbChecks => stable_mir::mir::NullOp::UbChecks,
}
}
}
3 changes: 1 addition & 2 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
@@ -518,8 +518,6 @@ symbols! {
cfi,
cfi_encoding,
char,
check_language_ub,
check_library_ub,
client,
clippy,
clobber_abi,
@@ -1867,6 +1865,7 @@ symbols! {
u8_legacy_fn_max_value,
u8_legacy_fn_min_value,
u8_legacy_mod,
ub_checks,
unaligned_volatile_load,
unaligned_volatile_store,
unboxed_closures,
10 changes: 2 additions & 8 deletions compiler/stable_mir/src/mir/body.rs
Original file line number Diff line number Diff line change
@@ -621,7 +621,7 @@ impl Rvalue {
Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(..), _) => {
Ok(Ty::usize_ty())
}
Rvalue::NullaryOp(NullOp::UbCheck(_), _) => Ok(Ty::bool_ty()),
Rvalue::NullaryOp(NullOp::UbChecks, _) => Ok(Ty::bool_ty()),
Rvalue::Aggregate(ak, ops) => match *ak {
AggregateKind::Array(ty) => Ty::try_new_array(ty, ops.len() as u64),
AggregateKind::Tuple => Ok(Ty::new_tuple(
@@ -989,13 +989,7 @@ pub enum NullOp {
/// Returns the offset of a field.
OffsetOf(Vec<(VariantIdx, FieldIdx)>),
/// cfg!(debug_assertions), but at codegen time
UbCheck(UbKind),
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub enum UbKind {
LanguageUb,
LibraryUb,
UbChecks,
Copy link
Member Author

@RalfJung RalfJung Mar 17, 2024

Choose a reason for hiding this comment

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

I see no choice but to change smir here; the things it exposed were never meant to be very stable to begin with and I don't think we want to keep them.

Copy link
Member

Choose a reason for hiding this comment

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

I've just been breaking smir every time I change MIR. Is there any guidance or enforcement that we don't do that?

Copy link
Member Author

Choose a reason for hiding this comment

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

No idea. SMIR people get pinged when this happens so I guess they'll complain when it gets too much for them. ;)

Copy link
Contributor

Choose a reason for hiding this comment

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

It would be nice to avoid these breakages. If something is unstable, we recommend to not expose them, or expose as an Opaque type.

See Coverage statement as an example.

Copy link
Contributor

Choose a reason for hiding this comment

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

In this specific case, we could just always use the same UbKind value, and mark UbValue as deprecated.

Copy link
Member

Choose a reason for hiding this comment

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

If we leave compatibility shims in stable MIR (not that I think we should do that in this PR), is there a plan for how to remove them eventually?

Copy link
Contributor

Choose a reason for hiding this comment

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

StableMIR is not yet versioned, but once it is, we would likely go over things that has been marked as deprecated and remove them when a new major is about to be released.

Copy link
Contributor

Choose a reason for hiding this comment

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

@RalfJung I think it is fine to remove it now.

I do have a question about how to evaluate this operator though. Is the idea here that we are assessing the value of cfg!(debug_assertions) for the crate being compiled? So if the MIR comes from a dependency that was compiled with debug_assertions off, but the current crate has the debug_assertions on, this rvalue will be evaluated to true. Is that correct?

Is it possible to document the answer here?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, exactly. This is what the existing documentation means when it says "the value of debug_assertions at monomorphization time".

Copy link
Member Author

Choose a reason for hiding this comment

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

I have also extended the intrinsic's docs to make this more clear.

}

impl Operand {
2 changes: 1 addition & 1 deletion library/core/src/char/convert.rs
Original file line number Diff line number Diff line change
@@ -4,9 +4,9 @@ use crate::char::TryFromCharError;
use crate::convert::TryFrom;
use crate::error::Error;
use crate::fmt;
use crate::intrinsics::assert_unsafe_precondition;
use crate::mem::transmute;
use crate::str::FromStr;
use crate::ub_checks::assert_unsafe_precondition;

/// Converts a `u32` to a `char`. See [`char::from_u32`].
#[must_use]
5 changes: 3 additions & 2 deletions library/core/src/hint.rs
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@
//! Hints may be compile time or runtime.

use crate::intrinsics;
use crate::ub_checks;

/// Informs the compiler that the site which is calling this function is not
/// reachable, possibly enabling further optimizations.
@@ -98,7 +99,7 @@ use crate::intrinsics;
#[rustc_const_stable(feature = "const_unreachable_unchecked", since = "1.57.0")]
#[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
pub const unsafe fn unreachable_unchecked() -> ! {
intrinsics::assert_unsafe_precondition!(
ub_checks::assert_unsafe_precondition!(
check_language_ub,
"hint::unreachable_unchecked must never be reached",
() => false
@@ -148,7 +149,7 @@ pub const unsafe fn unreachable_unchecked() -> ! {
pub const unsafe fn assert_unchecked(cond: bool) {
// SAFETY: The caller promised `cond` is true.
unsafe {
intrinsics::assert_unsafe_precondition!(
ub_checks::assert_unsafe_precondition!(
check_language_ub,
"hint::assert_unchecked must never be called when the condition is false",
(cond: bool = cond) => cond,
189 changes: 24 additions & 165 deletions library/core/src/intrinsics.rs
Original file line number Diff line number Diff line change
@@ -67,6 +67,7 @@ use crate::marker::DiscriminantKind;
use crate::marker::Tuple;
use crate::mem::align_of;
use crate::ptr;
use crate::ub_checks;

pub mod mir;
pub mod simd;
@@ -2660,38 +2661,22 @@ pub const unsafe fn typed_swap<T>(x: *mut T, y: *mut T) {
unsafe { ptr::swap_nonoverlapping(x, y, 1) };
}

/// Returns whether we should check for library UB. This evaluate to the value of `cfg!(debug_assertions)`
/// during monomorphization.
///
/// This intrinsic is evaluated after monomorphization, and therefore branching on this value can
/// be used to implement debug assertions that are included in the precompiled standard library,
/// but can be optimized out by builds that monomorphize the standard library code with debug
/// assertions disabled. This intrinsic is primarily used by [`assert_unsafe_precondition`].
///
/// We have separate intrinsics for library UB and language UB because checkers like the const-eval
/// interpreter and Miri already implement checks for language UB. Since such checkers do not know
/// about library preconditions, checks guarded by this intrinsic let them find more UB.
#[rustc_const_unstable(feature = "ub_checks", issue = "none")]
#[unstable(feature = "core_intrinsics", issue = "none")]
#[inline(always)]
#[rustc_intrinsic]
pub(crate) const fn check_library_ub() -> bool {
cfg!(debug_assertions)
}

/// Returns whether we should check for language UB. This evaluate to the value of `cfg!(debug_assertions)`
/// during monomorphization.
///
/// Since checks implemented at the source level must come strictly before the operation that
/// executes UB, if we enabled language UB checks in const-eval/Miri we would miss out on the
/// interpreter's improved diagnostics for the cases that our source-level checks catch.
///
/// See `check_library_ub` for more information.
#[rustc_const_unstable(feature = "ub_checks", issue = "none")]
/// Returns whether we should perform some UB-checking at runtime. This evaluate to the value of
/// `cfg!(debug_assertions)` during monomorphization.
///
/// This intrinsic is evaluated after monomorphization, which is relevant when mixing crates
/// compiled with and without debug_assertions. The common case here is a user program built with
/// debug_assertions linked against the distributed sysroot which is built without debug_assertions.
/// For code that gets monomorphized in the user crate (i.e., generic functions and functions with
/// `#[inline]`), gating assertions on `ub_checks()` rather than `cfg!(debug_assertions)` means that
/// assertions are enabled whenever the *user crate* has debug assertions enabled. However if the
/// user has debug assertions disabled, the checks will still get optimized out. This intrinsic is
/// primarily used by [`ub_checks::assert_unsafe_precondition`].
#[rustc_const_unstable(feature = "const_ub_checks", issue = "none")]
#[unstable(feature = "core_intrinsics", issue = "none")]
#[inline(always)]
#[rustc_intrinsic]
pub(crate) const fn check_language_ub() -> bool {
#[cfg_attr(not(bootstrap), rustc_intrinsic)] // just make it a regular fn in bootstrap
pub(crate) const fn ub_checks() -> bool {
cfg!(debug_assertions)
}

@@ -2755,132 +2740,6 @@ pub unsafe fn vtable_align(_ptr: *const ()) -> usize {
// (`transmute` also falls into this category, but it cannot be wrapped due to the
// check that `T` and `U` have the same size.)

/// Check that the preconditions of an unsafe function are followed. The check is enabled at
/// runtime if debug assertions are enabled when the caller is monomorphized. In const-eval/Miri
/// checks implemented with this macro for language UB are always ignored.
///
/// This macro should be called as
/// `assert_unsafe_precondition!(check_{library,lang}_ub, "message", (ident: type = expr, ident: type = expr) => check_expr)`
/// where each `expr` will be evaluated and passed in as function argument `ident: type`. Then all
/// those arguments are passed to a function with the body `check_expr`.
/// Pick `check_language_ub` when this is guarding a violation of language UB, i.e., immediate UB
/// according to the Rust Abstract Machine. Pick `check_library_ub` when this is guarding a violation
/// of a documented library precondition that does not *immediately* lead to language UB.
///
/// If `check_library_ub` is used but the check is actually guarding language UB, the check will
/// slow down const-eval/Miri and we'll get the panic message instead of the interpreter's nice
/// diagnostic, but our ability to detect UB is unchanged.
/// But if `check_language_ub` is used when the check is actually for library UB, the check is
/// omitted in const-eval/Miri and thus if we eventually execute language UB which relies on the
/// library UB, the backtrace Miri reports may be far removed from original cause.
///
/// These checks are behind a condition which is evaluated at codegen time, not expansion time like
/// [`debug_assert`]. This means that a standard library built with optimizations and debug
/// assertions disabled will have these checks optimized out of its monomorphizations, but if a
/// caller of the standard library has debug assertions enabled and monomorphizes an expansion of
/// this macro, that monomorphization will contain the check.
///
/// Since these checks cannot be optimized out in MIR, some care must be taken in both call and
/// implementation to mitigate their compile-time overhead. Calls to this macro always expand to
/// this structure:
/// ```ignore (pseudocode)
/// if ::core::intrinsics::check_language_ub() {
/// precondition_check(args)
/// }
/// ```
/// where `precondition_check` is monomorphic with the attributes `#[rustc_nounwind]`, `#[inline]` and
/// `#[rustc_no_mir_inline]`. This combination of attributes ensures that the actual check logic is
/// compiled only once and generates a minimal amount of IR because the check cannot be inlined in
/// MIR, but *can* be inlined and fully optimized by a codegen backend.
///
/// Callers should avoid introducing any other `let` bindings or any code outside this macro in
/// order to call it. Since the precompiled standard library is built with full debuginfo and these
/// variables cannot be optimized out in MIR, an innocent-looking `let` can produce enough
/// debuginfo to have a measurable compile-time impact on debug builds.
#[allow_internal_unstable(ub_checks)] // permit this to be called in stably-const fn
macro_rules! assert_unsafe_precondition {
($kind:ident, $message:expr, ($($name:ident:$ty:ty = $arg:expr),*$(,)?) => $e:expr $(,)?) => {
{
// This check is inlineable, but not by the MIR inliner.
// The reason for this is that the MIR inliner is in an exceptionally bad position
// to think about whether or not to inline this. In MIR, this call is gated behind `debug_assertions`,
// which will codegen to `false` in release builds. Inlining the check would be wasted work in that case and
// would be bad for compile times.
//
// LLVM on the other hand sees the constant branch, so if it's `false`, it can immediately delete it without
// inlining the check. If it's `true`, it can inline it and get significantly better performance.
#[rustc_no_mir_inline]
#[inline]
#[rustc_nounwind]
#[rustc_const_unstable(feature = "ub_checks", issue = "none")]
const fn precondition_check($($name:$ty),*) {
if !$e {
::core::panicking::panic_nounwind(
concat!("unsafe precondition(s) violated: ", $message)
);
}
}

if ::core::intrinsics::$kind() {
precondition_check($($arg,)*);
}
}
};
}
pub(crate) use assert_unsafe_precondition;

/// Checks whether `ptr` is properly aligned with respect to
/// `align_of::<T>()`.
///
/// In `const` this is approximate and can fail spuriously. It is primarily intended
/// for `assert_unsafe_precondition!` with `check_language_ub`, in which case the
/// check is anyway not executed in `const`.
#[inline]
pub(crate) const fn is_aligned_and_not_null(ptr: *const (), align: usize) -> bool {
!ptr.is_null() && ptr.is_aligned_to(align)
}

#[inline]
pub(crate) const fn is_valid_allocation_size(size: usize, len: usize) -> bool {
let max_len = if size == 0 { usize::MAX } else { isize::MAX as usize / size };
len <= max_len
}

/// Checks whether the regions of memory starting at `src` and `dst` of size
/// `count * size` do *not* overlap.
///
/// Note that in const-eval this function just returns `true` and therefore must
/// only be used with `assert_unsafe_precondition!`, similar to `is_aligned_and_not_null`.
#[inline]
pub(crate) const fn is_nonoverlapping(
src: *const (),
dst: *const (),
size: usize,
count: usize,
) -> bool {
#[inline]
fn runtime(src: *const (), dst: *const (), size: usize, count: usize) -> bool {
let src_usize = src.addr();
let dst_usize = dst.addr();
let Some(size) = size.checked_mul(count) else {
crate::panicking::panic_nounwind(
"is_nonoverlapping: `size_of::<T>() * count` overflows a usize",
)
};
let diff = src_usize.abs_diff(dst_usize);
// If the absolute distance between the ptrs is at least as big as the size of the buffer,
// they do not overlap.
diff >= size
}

#[inline]
const fn comptime(_: *const (), _: *const (), _: usize, _: usize) -> bool {
true
}

const_eval_select((src, dst, size, count), comptime, runtime)
}

/// Copies `count * size_of::<T>()` bytes from `src` to `dst`. The source
/// and destination must *not* overlap.
///
@@ -2979,7 +2838,7 @@ pub const unsafe fn copy_nonoverlapping<T>(src: *const T, dst: *mut T, count: us
pub fn copy_nonoverlapping<T>(src: *const T, dst: *mut T, count: usize);
}

assert_unsafe_precondition!(
ub_checks::assert_unsafe_precondition!(
check_language_ub,
"ptr::copy_nonoverlapping requires that both pointer arguments are aligned and non-null \
and the specified memory ranges do not overlap",
@@ -2990,9 +2849,9 @@ pub const unsafe fn copy_nonoverlapping<T>(src: *const T, dst: *mut T, count: us
align: usize = align_of::<T>(),
count: usize = count,
) =>
is_aligned_and_not_null(src, align)
&& is_aligned_and_not_null(dst, align)
&& is_nonoverlapping(src, dst, size, count)
ub_checks::is_aligned_and_not_null(src, align)
&& ub_checks::is_aligned_and_not_null(dst, align)
&& ub_checks::is_nonoverlapping(src, dst, size, count)
);

// SAFETY: the safety contract for `copy_nonoverlapping` must be
@@ -3083,7 +2942,7 @@ pub const unsafe fn copy<T>(src: *const T, dst: *mut T, count: usize) {

// SAFETY: the safety contract for `copy` must be upheld by the caller.
unsafe {
assert_unsafe_precondition!(
ub_checks::assert_unsafe_precondition!(
check_language_ub,
"ptr::copy_nonoverlapping requires that both pointer arguments are aligned and non-null \
and the specified memory ranges do not overlap",
@@ -3092,8 +2951,8 @@ pub const unsafe fn copy<T>(src: *const T, dst: *mut T, count: usize) {
dst: *mut () = dst as *mut (),
align: usize = align_of::<T>(),
) =>
is_aligned_and_not_null(src, align)
&& is_aligned_and_not_null(dst, align)
ub_checks::is_aligned_and_not_null(src, align)
&& ub_checks::is_aligned_and_not_null(dst, align)
);
copy(src, dst, count)
}
@@ -3164,13 +3023,13 @@ pub const unsafe fn write_bytes<T>(dst: *mut T, val: u8, count: usize) {

// SAFETY: the safety contract for `write_bytes` must be upheld by the caller.
unsafe {
assert_unsafe_precondition!(
ub_checks::assert_unsafe_precondition!(
check_language_ub,
"ptr::write_bytes requires that the destination pointer is aligned and non-null",
(
addr: *const () = dst as *const (),
align: usize = align_of::<T>(),
) => is_aligned_and_not_null(addr, align)
) => ub_checks::is_aligned_and_not_null(addr, align)
);
write_bytes(dst, val, count)
}
2 changes: 2 additions & 0 deletions library/core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -171,6 +171,7 @@
#![feature(const_type_id)]
#![feature(const_type_name)]
#![feature(const_typed_swap)]
#![feature(const_ub_checks)]
#![feature(const_unicode_case_lookup)]
#![feature(const_unsafecell_get_mut)]
#![feature(const_waker)]
@@ -367,6 +368,7 @@ pub mod hint;
pub mod intrinsics;
pub mod mem;
pub mod ptr;
mod ub_checks;

/* Core language traits */

5 changes: 3 additions & 2 deletions library/core/src/num/nonzero.rs
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@ use crate::ops::{BitOr, BitOrAssign, Div, DivAssign, Neg, Rem, RemAssign};
use crate::panic::{RefUnwindSafe, UnwindSafe};
use crate::ptr;
use crate::str::FromStr;
use crate::ub_checks;

use super::from_str_radix;
use super::{IntErrorKind, ParseIntError};
@@ -369,7 +370,7 @@ where
None => {
// SAFETY: The caller guarantees that `n` is non-zero, so this is unreachable.
unsafe {
intrinsics::assert_unsafe_precondition!(
ub_checks::assert_unsafe_precondition!(
check_language_ub,
"NonZero::new_unchecked requires the argument to be non-zero",
() => false,
@@ -409,7 +410,7 @@ where
None => {
// SAFETY: The caller guarantees that `n` references a value that is non-zero, so this is unreachable.
unsafe {
intrinsics::assert_unsafe_precondition!(
ub_checks::assert_unsafe_precondition!(
check_library_ub,
"NonZero::from_mut_unchecked requires the argument to dereference as non-zero",
() => false,
5 changes: 3 additions & 2 deletions library/core/src/ops/index_range.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::intrinsics::{assert_unsafe_precondition, unchecked_add, unchecked_sub};
use crate::intrinsics::{unchecked_add, unchecked_sub};
use crate::iter::{FusedIterator, TrustedLen};
use crate::num::NonZero;
use crate::ub_checks;

/// Like a `Range<usize>`, but with a safety invariant that `start <= end`.
///
@@ -19,7 +20,7 @@ impl IndexRange {
/// - `start <= end`
#[inline]
pub const unsafe fn new_unchecked(start: usize, end: usize) -> Self {
assert_unsafe_precondition!(
ub_checks::assert_unsafe_precondition!(
check_library_ub,
"IndexRange::new_unchecked requires `start <= end`",
(start: usize = start, end: usize = end) => start <= end,
4 changes: 2 additions & 2 deletions library/core/src/ptr/alignment.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::convert::{TryFrom, TryInto};
#[cfg(debug_assertions)]
use crate::intrinsics::assert_unsafe_precondition;
use crate::num::NonZero;
#[cfg(debug_assertions)]
use crate::ub_checks::assert_unsafe_precondition;
use crate::{cmp, fmt, hash, mem, num};

/// A type storing a `usize` which is a power of two, and thus
2 changes: 1 addition & 1 deletion library/core/src/ptr/const_ptr.rs
Original file line number Diff line number Diff line change
@@ -818,7 +818,7 @@ impl<T: ?Sized> *const T {
intrinsics::const_eval_select((this, origin), comptime, runtime)
}

assert_unsafe_precondition!(
ub_checks::assert_unsafe_precondition!(
check_language_ub,
"ptr::sub_ptr requires `self >= origin`",
(
33 changes: 16 additions & 17 deletions library/core/src/ptr/mod.rs
Original file line number Diff line number Diff line change
@@ -388,10 +388,9 @@
use crate::cmp::Ordering;
use crate::fmt;
use crate::hash;
use crate::intrinsics::{
self, assert_unsafe_precondition, is_aligned_and_not_null, is_nonoverlapping,
};
use crate::intrinsics;
use crate::marker::FnPtr;
use crate::ub_checks;

use crate::mem::{self, align_of, size_of, MaybeUninit};

@@ -1019,7 +1018,7 @@ pub const unsafe fn swap_nonoverlapping<T>(x: *mut T, y: *mut T, count: usize) {
};
}

assert_unsafe_precondition!(
ub_checks::assert_unsafe_precondition!(
check_language_ub,
"ptr::swap_nonoverlapping requires that both pointer arguments are aligned and non-null \
and the specified memory ranges do not overlap",
@@ -1030,9 +1029,9 @@ pub const unsafe fn swap_nonoverlapping<T>(x: *mut T, y: *mut T, count: usize) {
align: usize = align_of::<T>(),
count: usize = count,
) =>
is_aligned_and_not_null(x, align)
&& is_aligned_and_not_null(y, align)
&& is_nonoverlapping(x, y, size, count)
ub_checks::is_aligned_and_not_null(x, align)
&& ub_checks::is_aligned_and_not_null(y, align)
&& ub_checks::is_nonoverlapping(x, y, size, count)
);

// Split up the slice into small power-of-two-sized chunks that LLVM is able
@@ -1135,13 +1134,13 @@ pub const unsafe fn replace<T>(dst: *mut T, src: T) -> T {
// and cannot overlap `src` since `dst` must point to a distinct
// allocated object.
unsafe {
assert_unsafe_precondition!(
ub_checks::assert_unsafe_precondition!(
check_language_ub,
"ptr::replace requires that the pointer argument is aligned and non-null",
(
addr: *const () = dst as *const (),
align: usize = align_of::<T>(),
) => is_aligned_and_not_null(addr, align)
) => ub_checks::is_aligned_and_not_null(addr, align)
);
mem::replace(&mut *dst, src)
}
@@ -1287,13 +1286,13 @@ pub const unsafe fn read<T>(src: *const T) -> T {
// SAFETY: the caller must guarantee that `src` is valid for reads.
unsafe {
#[cfg(debug_assertions)] // Too expensive to always enable (for now?)
assert_unsafe_precondition!(
ub_checks::assert_unsafe_precondition!(
check_language_ub,
"ptr::read requires that the pointer argument is aligned and non-null",
(
addr: *const () = src as *const (),
align: usize = align_of::<T>(),
) => is_aligned_and_not_null(addr, align)
) => ub_checks::is_aligned_and_not_null(addr, align)
);
crate::intrinsics::read_via_copy(src)
}
@@ -1496,13 +1495,13 @@ pub const unsafe fn write<T>(dst: *mut T, src: T) {
// to `dst` while `src` is owned by this function.
unsafe {
#[cfg(debug_assertions)] // Too expensive to always enable (for now?)
assert_unsafe_precondition!(
ub_checks::assert_unsafe_precondition!(
check_language_ub,
"ptr::write requires that the pointer argument is aligned and non-null",
(
addr: *mut () = dst as *mut (),
align: usize = align_of::<T>(),
) => is_aligned_and_not_null(addr, align)
) => ub_checks::is_aligned_and_not_null(addr, align)
);
intrinsics::write_via_move(dst, src)
}
@@ -1668,13 +1667,13 @@ pub const unsafe fn write_unaligned<T>(dst: *mut T, src: T) {
pub unsafe fn read_volatile<T>(src: *const T) -> T {
// SAFETY: the caller must uphold the safety contract for `volatile_load`.
unsafe {
assert_unsafe_precondition!(
ub_checks::assert_unsafe_precondition!(
check_language_ub,
"ptr::read_volatile requires that the pointer argument is aligned and non-null",
(
addr: *const () = src as *const (),
align: usize = align_of::<T>(),
) => is_aligned_and_not_null(addr, align)
) => ub_checks::is_aligned_and_not_null(addr, align)
);
intrinsics::volatile_load(src)
}
@@ -1747,13 +1746,13 @@ pub unsafe fn read_volatile<T>(src: *const T) -> T {
pub unsafe fn write_volatile<T>(dst: *mut T, src: T) {
// SAFETY: the caller must uphold the safety contract for `volatile_store`.
unsafe {
assert_unsafe_precondition!(
ub_checks::assert_unsafe_precondition!(
check_language_ub,
"ptr::write_volatile requires that the pointer argument is aligned and non-null",
(
addr: *mut () = dst as *mut (),
align: usize = align_of::<T>(),
) => is_aligned_and_not_null(addr, align)
) => ub_checks::is_aligned_and_not_null(addr, align)
);
intrinsics::volatile_store(dst, src);
}
2 changes: 1 addition & 1 deletion library/core/src/ptr/non_null.rs
Original file line number Diff line number Diff line change
@@ -2,14 +2,14 @@ use crate::cmp::Ordering;
use crate::fmt;
use crate::hash;
use crate::intrinsics;
use crate::intrinsics::assert_unsafe_precondition;
use crate::marker::Unsize;
use crate::mem::{MaybeUninit, SizedTypeProperties};
use crate::num::NonZero;
use crate::ops::{CoerceUnsized, DispatchFromDyn};
use crate::ptr;
use crate::ptr::Unique;
use crate::slice::{self, SliceIndex};
use crate::ub_checks::assert_unsafe_precondition;

/// `*mut T` but non-zero and [covariant].
///
2 changes: 1 addition & 1 deletion library/core/src/slice/index.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
//! Indexing implementations for `[T]`.
use crate::intrinsics::assert_unsafe_precondition;
use crate::intrinsics::const_eval_select;
use crate::intrinsics::unchecked_sub;
use crate::ops;
use crate::ptr;
use crate::ub_checks::assert_unsafe_precondition;

#[stable(feature = "rust1", since = "1.0.0")]
impl<T, I> ops::Index<I> for [T]
2 changes: 1 addition & 1 deletion library/core/src/slice/mod.rs
Original file line number Diff line number Diff line change
@@ -9,14 +9,14 @@
use crate::cmp::Ordering::{self, Equal, Greater, Less};
use crate::fmt;
use crate::hint;
use crate::intrinsics::assert_unsafe_precondition;
use crate::intrinsics::exact_div;
use crate::mem::{self, SizedTypeProperties};
use crate::num::NonZero;
use crate::ops::{Bound, OneSidedRange, Range, RangeBounds};
use crate::ptr;
use crate::simd::{self, Simd};
use crate::slice;
use crate::ub_checks::assert_unsafe_precondition;

#[unstable(
feature = "slice_internals",
16 changes: 7 additions & 9 deletions library/core/src/slice/raw.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
//! Free functions to create `&[T]` and `&mut [T]`.
use crate::array;
use crate::intrinsics::{
assert_unsafe_precondition, is_aligned_and_not_null, is_valid_allocation_size,
};
use crate::mem::{align_of, size_of};
use crate::ops::Range;
use crate::ptr;
use crate::ub_checks;

/// Forms a slice from a pointer and a length.
///
@@ -95,7 +93,7 @@ use crate::ptr;
pub const unsafe fn from_raw_parts<'a, T>(data: *const T, len: usize) -> &'a [T] {
// SAFETY: the caller must uphold the safety contract for `from_raw_parts`.
unsafe {
assert_unsafe_precondition!(
ub_checks::assert_unsafe_precondition!(
check_language_ub,
"slice::from_raw_parts requires the pointer to be aligned and non-null, and the total size of the slice not to exceed `isize::MAX`",
(
@@ -104,8 +102,8 @@ pub const unsafe fn from_raw_parts<'a, T>(data: *const T, len: usize) -> &'a [T]
align: usize = align_of::<T>(),
len: usize = len,
) =>
is_aligned_and_not_null(data, align)
&& is_valid_allocation_size(size, len)
ub_checks::is_aligned_and_not_null(data, align)
&& ub_checks::is_valid_allocation_size(size, len)
);
&*ptr::slice_from_raw_parts(data, len)
}
@@ -149,7 +147,7 @@ pub const unsafe fn from_raw_parts<'a, T>(data: *const T, len: usize) -> &'a [T]
pub const unsafe fn from_raw_parts_mut<'a, T>(data: *mut T, len: usize) -> &'a mut [T] {
// SAFETY: the caller must uphold the safety contract for `from_raw_parts_mut`.
unsafe {
assert_unsafe_precondition!(
ub_checks::assert_unsafe_precondition!(
check_language_ub,
"slice::from_raw_parts_mut requires the pointer to be aligned and non-null, and the total size of the slice not to exceed `isize::MAX`",
(
@@ -158,8 +156,8 @@ pub const unsafe fn from_raw_parts_mut<'a, T>(data: *mut T, len: usize) -> &'a m
align: usize = align_of::<T>(),
len: usize = len,
) =>
is_aligned_and_not_null(data, align)
&& is_valid_allocation_size(size, len)
ub_checks::is_aligned_and_not_null(data, align)
&& ub_checks::is_valid_allocation_size(size, len)
);
&mut *ptr::slice_from_raw_parts_mut(data, len)
}
2 changes: 1 addition & 1 deletion library/core/src/str/traits.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
//! Trait implementations for `str`.
use crate::cmp::Ordering;
use crate::intrinsics::assert_unsafe_precondition;
use crate::ops;
use crate::ptr;
use crate::slice::SliceIndex;
use crate::ub_checks::assert_unsafe_precondition;

use super::ParseBoolError;

158 changes: 158 additions & 0 deletions library/core/src/ub_checks.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
//! Provides the [`assert_unsafe_precondition`] macro as well as some utility functions that cover
//! common preconditions.
use crate::intrinsics::{self, const_eval_select};

/// Check that the preconditions of an unsafe function are followed. The check is enabled at
/// runtime if debug assertions are enabled when the caller is monomorphized. In const-eval/Miri
/// checks implemented with this macro for language UB are always ignored.
///
/// This macro should be called as
/// `assert_unsafe_precondition!(check_{library,lang}_ub, "message", (ident: type = expr, ident: type = expr) => check_expr)`
/// where each `expr` will be evaluated and passed in as function argument `ident: type`. Then all
/// those arguments are passed to a function with the body `check_expr`.
/// Pick `check_language_ub` when this is guarding a violation of language UB, i.e., immediate UB
/// according to the Rust Abstract Machine. Pick `check_library_ub` when this is guarding a violation
/// of a documented library precondition that does not *immediately* lead to language UB.
///
/// If `check_library_ub` is used but the check is actually guarding language UB, the check will
/// slow down const-eval/Miri and we'll get the panic message instead of the interpreter's nice
/// diagnostic, but our ability to detect UB is unchanged.
/// But if `check_language_ub` is used when the check is actually for library UB, the check is
/// omitted in const-eval/Miri and thus if we eventually execute language UB which relies on the
/// library UB, the backtrace Miri reports may be far removed from original cause.
///
/// These checks are behind a condition which is evaluated at codegen time, not expansion time like
/// [`debug_assert`]. This means that a standard library built with optimizations and debug
/// assertions disabled will have these checks optimized out of its monomorphizations, but if a
/// caller of the standard library has debug assertions enabled and monomorphizes an expansion of
/// this macro, that monomorphization will contain the check.
///
/// Since these checks cannot be optimized out in MIR, some care must be taken in both call and
/// implementation to mitigate their compile-time overhead. Calls to this macro always expand to
/// this structure:
/// ```ignore (pseudocode)
/// if ::core::intrinsics::check_language_ub() {
/// precondition_check(args)
/// }
/// ```
/// where `precondition_check` is monomorphic with the attributes `#[rustc_nounwind]`, `#[inline]` and
/// `#[rustc_no_mir_inline]`. This combination of attributes ensures that the actual check logic is
/// compiled only once and generates a minimal amount of IR because the check cannot be inlined in
/// MIR, but *can* be inlined and fully optimized by a codegen backend.
///
/// Callers should avoid introducing any other `let` bindings or any code outside this macro in
/// order to call it. Since the precompiled standard library is built with full debuginfo and these
/// variables cannot be optimized out in MIR, an innocent-looking `let` can produce enough
/// debuginfo to have a measurable compile-time impact on debug builds.
#[allow_internal_unstable(const_ub_checks)] // permit this to be called in stably-const fn
macro_rules! assert_unsafe_precondition {
($kind:ident, $message:expr, ($($name:ident:$ty:ty = $arg:expr),*$(,)?) => $e:expr $(,)?) => {
{
// This check is inlineable, but not by the MIR inliner.
// The reason for this is that the MIR inliner is in an exceptionally bad position
// to think about whether or not to inline this. In MIR, this call is gated behind `debug_assertions`,
// which will codegen to `false` in release builds. Inlining the check would be wasted work in that case and
// would be bad for compile times.
//
// LLVM on the other hand sees the constant branch, so if it's `false`, it can immediately delete it without
// inlining the check. If it's `true`, it can inline it and get significantly better performance.
#[rustc_no_mir_inline]
#[inline]
#[rustc_nounwind]
#[rustc_const_unstable(feature = "const_ub_checks", issue = "none")]
const fn precondition_check($($name:$ty),*) {
if !$e {
::core::panicking::panic_nounwind(
concat!("unsafe precondition(s) violated: ", $message)
);
}
}

if ::core::ub_checks::$kind() {
precondition_check($($arg,)*);
}
}
};
}
pub(crate) use assert_unsafe_precondition;

/// Checking library UB is always enabled when UB-checking is done
/// (and we use a reexport so that there is no unnecessary wrapper function).
pub(crate) use intrinsics::ub_checks as check_library_ub;

/// Determines whether we should check for language UB.
///
/// The intention is to not do that when running in the interpreter, as that one has its own
/// language UB checks which generally produce better errors.
#[rustc_const_unstable(feature = "const_ub_checks", issue = "none")]
#[inline]
Copy link
Member Author

Choose a reason for hiding this comment

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

FWIW this is the inline that was missing.

pub(crate) const fn check_language_ub() -> bool {
#[inline]
fn runtime() -> bool {
// Disable UB checks in Miri.
!cfg!(miri)
}

#[inline]
const fn comptime() -> bool {
// Always disable UB checks.
false
}

// Only used for UB checks so we may const_eval_select.
intrinsics::ub_checks() && const_eval_select((), comptime, runtime)
}

/// Checks whether `ptr` is properly aligned with respect to
/// `align_of::<T>()`.
///
/// In `const` this is approximate and can fail spuriously. It is primarily intended
/// for `assert_unsafe_precondition!` with `check_language_ub`, in which case the
/// check is anyway not executed in `const`.
#[inline]
pub(crate) const fn is_aligned_and_not_null(ptr: *const (), align: usize) -> bool {
!ptr.is_null() && ptr.is_aligned_to(align)
}

#[inline]
pub(crate) const fn is_valid_allocation_size(size: usize, len: usize) -> bool {
let max_len = if size == 0 { usize::MAX } else { isize::MAX as usize / size };
len <= max_len
}

/// Checks whether the regions of memory starting at `src` and `dst` of size
/// `count * size` do *not* overlap.
///
/// Note that in const-eval this function just returns `true` and therefore must
/// only be used with `assert_unsafe_precondition!`, similar to `is_aligned_and_not_null`.
#[inline]
pub(crate) const fn is_nonoverlapping(
src: *const (),
dst: *const (),
size: usize,
count: usize,
) -> bool {
#[inline]
fn runtime(src: *const (), dst: *const (), size: usize, count: usize) -> bool {
let src_usize = src.addr();
let dst_usize = dst.addr();
let Some(size) = size.checked_mul(count) else {
crate::panicking::panic_nounwind(
"is_nonoverlapping: `size_of::<T>() * count` overflows a usize",
)
};
let diff = src_usize.abs_diff(dst_usize);
// If the absolute distance between the ptrs is at least as big as the size of the buffer,
// they do not overlap.
diff >= size
}

#[inline]
const fn comptime(_: *const (), _: *const (), _: usize, _: usize) -> bool {
true
}

// This is just for safety checks so we can const_eval_select.
const_eval_select((src, dst, size, count), comptime, runtime)
}
2 changes: 1 addition & 1 deletion src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs
Original file line number Diff line number Diff line change
@@ -174,7 +174,7 @@ fn check_rvalue<'tcx>(
))
}
},
Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(_) | NullOp::UbCheck(_), _)
Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(_) | NullOp::UbChecks, _)
| Rvalue::ShallowInitBox(_, _) => Ok(()),
Rvalue::UnaryOp(_, operand) => {
let ty = operand.ty(body, tcx);
Original file line number Diff line number Diff line change
@@ -28,6 +28,10 @@
let mut _10: *mut ();
let mut _11: *const [bool; 0];
scope 13 {
scope 14 (inlined core::ub_checks::check_language_ub) {
scope 15 (inlined core::ub_checks::check_language_ub::runtime) {
}
}
}
}
}
@@ -60,7 +64,7 @@
StorageDead(_7);
StorageLive(_11);
StorageLive(_8);
_8 = UbCheck(LanguageUb);
_8 = UbChecks();
switchInt(move _8) -> [0: bb4, otherwise: bb2];
}

Original file line number Diff line number Diff line change
@@ -28,6 +28,10 @@
let mut _10: *mut ();
let mut _11: *const [bool; 0];
scope 13 {
scope 14 (inlined core::ub_checks::check_language_ub) {
scope 15 (inlined core::ub_checks::check_language_ub::runtime) {
}
}
}
}
}
@@ -60,7 +64,7 @@
StorageDead(_7);
StorageLive(_11);
StorageLive(_8);
_8 = UbCheck(LanguageUb);
_8 = UbChecks();
switchInt(move _8) -> [0: bb5, otherwise: bb3];
}

Original file line number Diff line number Diff line change
@@ -28,6 +28,10 @@
let mut _10: *mut ();
let mut _11: *const [bool; 0];
scope 13 {
scope 14 (inlined core::ub_checks::check_language_ub) {
scope 15 (inlined core::ub_checks::check_language_ub::runtime) {
}
}
}
}
}
@@ -60,7 +64,7 @@
StorageDead(_7);
StorageLive(_11);
StorageLive(_8);
_8 = UbCheck(LanguageUb);
_8 = UbChecks();
switchInt(move _8) -> [0: bb4, otherwise: bb2];
}

Original file line number Diff line number Diff line change
@@ -28,6 +28,10 @@
let mut _10: *mut ();
let mut _11: *const [bool; 0];
scope 13 {
scope 14 (inlined core::ub_checks::check_language_ub) {
scope 15 (inlined core::ub_checks::check_language_ub::runtime) {
}
}
}
}
}
@@ -60,7 +64,7 @@
StorageDead(_7);
StorageLive(_11);
StorageLive(_8);
_8 = UbCheck(LanguageUb);
_8 = UbChecks();
switchInt(move _8) -> [0: bb5, otherwise: bb3];
}

Original file line number Diff line number Diff line change
@@ -28,6 +28,10 @@
let mut _10: *mut ();
let mut _11: *const [bool; 0];
scope 13 {
scope 14 (inlined core::ub_checks::check_language_ub) {
scope 15 (inlined core::ub_checks::check_language_ub::runtime) {
}
}
}
}
}
@@ -62,7 +66,7 @@
StorageDead(_7);
StorageLive(_11);
StorageLive(_8);
_8 = UbCheck(LanguageUb);
_8 = UbChecks();
switchInt(move _8) -> [0: bb4, otherwise: bb2];
}

Original file line number Diff line number Diff line change
@@ -28,6 +28,10 @@
let mut _10: *mut ();
let mut _11: *const [bool; 0];
scope 13 {
scope 14 (inlined core::ub_checks::check_language_ub) {
scope 15 (inlined core::ub_checks::check_language_ub::runtime) {
}
}
}
}
}
@@ -62,7 +66,7 @@
StorageDead(_7);
StorageLive(_11);
StorageLive(_8);
_8 = UbCheck(LanguageUb);
_8 = UbChecks();
switchInt(move _8) -> [0: bb5, otherwise: bb3];
}

Original file line number Diff line number Diff line change
@@ -28,6 +28,10 @@
let mut _10: *mut ();
let mut _11: *const [bool; 0];
scope 13 {
scope 14 (inlined core::ub_checks::check_language_ub) {
scope 15 (inlined core::ub_checks::check_language_ub::runtime) {
}
}
}
}
}
@@ -62,7 +66,7 @@
StorageDead(_7);
StorageLive(_11);
StorageLive(_8);
_8 = UbCheck(LanguageUb);
_8 = UbChecks();
switchInt(move _8) -> [0: bb4, otherwise: bb2];
}

Original file line number Diff line number Diff line change
@@ -28,6 +28,10 @@
let mut _10: *mut ();
let mut _11: *const [bool; 0];
scope 13 {
scope 14 (inlined core::ub_checks::check_language_ub) {
scope 15 (inlined core::ub_checks::check_language_ub::runtime) {
}
}
}
}
}
@@ -62,7 +66,7 @@
StorageDead(_7);
StorageLive(_11);
StorageLive(_8);
_8 = UbCheck(LanguageUb);
_8 = UbChecks();
switchInt(move _8) -> [0: bb5, otherwise: bb3];
}

Original file line number Diff line number Diff line change
@@ -17,6 +17,10 @@
+ let _5: ();
+ scope 5 {
+ }
+ scope 6 (inlined core::ub_checks::check_language_ub) {
+ scope 7 (inlined core::ub_checks::check_language_ub::runtime) {
+ }
+ }
+ }
+ }
+ }
@@ -37,7 +41,7 @@
+
+ bb2: {
+ StorageLive(_4);
+ _4 = UbCheck(LanguageUb);
+ _4 = UbChecks();
+ assume(_4);
+ _5 = unreachable_unchecked::precondition_check() -> [return: bb1, unwind unreachable];
+ }
Original file line number Diff line number Diff line change
@@ -17,6 +17,10 @@
+ let _5: ();
+ scope 5 {
+ }
+ scope 6 (inlined core::ub_checks::check_language_ub) {
+ scope 7 (inlined core::ub_checks::check_language_ub::runtime) {
+ }
+ }
+ }
+ }
+ }
@@ -41,7 +45,7 @@
- resume;
+ bb2: {
+ StorageLive(_4);
+ _4 = UbCheck(LanguageUb);
+ _4 = UbChecks();
+ assume(_4);
+ _5 = unreachable_unchecked::precondition_check() -> [return: bb1, unwind unreachable];
+ }
Original file line number Diff line number Diff line change
@@ -15,6 +15,10 @@ fn unwrap_unchecked(_1: Option<T>) -> T {
let _4: ();
scope 5 {
}
scope 6 (inlined core::ub_checks::check_language_ub) {
scope 7 (inlined core::ub_checks::check_language_ub::runtime) {
}
}
}
}
}
@@ -27,7 +31,7 @@ fn unwrap_unchecked(_1: Option<T>) -> T {

bb1: {
StorageLive(_3);
_3 = UbCheck(LanguageUb);
_3 = UbChecks();
assume(_3);
_4 = unreachable_unchecked::precondition_check() -> [return: bb3, unwind unreachable];
}
Original file line number Diff line number Diff line change
@@ -15,6 +15,10 @@ fn unwrap_unchecked(_1: Option<T>) -> T {
let _4: ();
scope 5 {
}
scope 6 (inlined core::ub_checks::check_language_ub) {
scope 7 (inlined core::ub_checks::check_language_ub::runtime) {
}
}
}
}
}
@@ -27,7 +31,7 @@ fn unwrap_unchecked(_1: Option<T>) -> T {

bb1: {
StorageLive(_3);
_3 = UbCheck(LanguageUb);
_3 = UbChecks();
assume(_3);
_4 = unreachable_unchecked::precondition_check() -> [return: bb3, unwind unreachable];
}
Original file line number Diff line number Diff line change
@@ -9,6 +9,10 @@ fn ub_if_b(_1: Thing) -> Thing {
let _4: ();
scope 2 {
}
scope 3 (inlined core::ub_checks::check_language_ub) {
scope 4 (inlined core::ub_checks::check_language_ub::runtime) {
}
}
}

bb0: {
@@ -23,7 +27,7 @@ fn ub_if_b(_1: Thing) -> Thing {

bb2: {
StorageLive(_3);
_3 = UbCheck(LanguageUb);
_3 = UbChecks();
assume(_3);
_4 = unreachable_unchecked::precondition_check() -> [return: bb3, unwind unreachable];
}