macro_rules! assert_unsafe_precondition { ( $kind:ident, $message:expr, ($($name:ident : $ty:ty = $arg:expr),* $(,)?) => $e:expr $(,)? ) => { ... }; }
ub_checks
)Expand description
Checks 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,language}_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:
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.