Unsafety Checking
Certain expressions in Rust can violate memory safety and as such need to be
inside an unsafe
block or function. The compiler will also warn if an unsafe
block is used without any corresponding unsafe operations.
Overview
The unsafety check is located in the check_unsafety
module. It performs a
walk over the THIR of a function and all of its closures and inline constants.
It keeps track of the unsafe context: whether it has entered an unsafe
block.
If an unsafe operation is used outside of an unsafe
block, then an error is
reported. If an unsafe operation is used in an unsafe block then that block is
marked as used for the unused_unsafe lint.
The unsafety check needs type information so could potentially be done on the HIR, making use of typeck results, THIR or MIR. THIR is chosen because there are fewer cases to consider than in HIR, for example unsafe function calls and unsafe method calls have the same representation in THIR. The check is not done on MIR because safety checks do not depend on control flow so MIR is not necessary to use and MIR doesn't have as precise spans for some expressions.
Most unsafe operations can be identified by checking the ExprKind
in THIR and
checking the type of the argument. For example, dereferences of a raw pointer
correspond to ExprKind::Deref
s with an argument that has a raw pointer type.
Looking for unsafe Union field accesses is a bit more complex because writing to a field of a union is safe. The checker tracks when it's visiting the left-hand side of an assignment expression and allows union fields to directly appear there, while erroring in all other cases. Union field accesses can also occur in patterns, so those have to be walked as well.
The other complicated safety check is for writes to fields of layout constrained
structs (such as NonNull
). These are found by looking for the borrow or
assignment expression and then visiting the subexpression being borrowed or
assigned with a separate visitor.
The unused_unsafe lint
The unused_unsafe lint reports unsafe
blocks that can be removed. The unsafety
checker records whenever it finds an operation that requires unsafe. The lint is
then reported if either:
- An
unsafe
block contains no unsafe operations - An
unsafe
block is within another unsafe block, and the outer block isn't considered unused
#![allow(unused)] #![deny(unused_unsafe)] fn main() { let y = 0; let x: *const u8 = core::ptr::addr_of!(y); unsafe { // lint reported for this block unsafe { let z = *x; } let safe_expr = 123; } unsafe { unsafe { // lint reported for this block let z = *x; } let unsafe_expr = *x; } }
Other checks involving unsafe
Unsafe traits require an unsafe impl
to be implemented, the check for this
is done as part of coherence. The unsafe_code
lint is run as a lint pass on
the ast that searches for unsafe blocks, functions and implementations, as well
as certain unsafe attributes.