Enforce unsafe blocks to have comments explaining why the required invariants hold · Issue #9330 · rust-lang/rust-clippy (original) (raw)

What it does

This is the users side of the missing_safety_doc lint. Enforce each block of unsafe to have a corresponding comment explaining why all the calls to unsafe meet the invariants required by the Safety section of the unsafe fn.

Motivation

unsafe rust relaxes some of the invariant that safe rust enforces. This is to allow the programmer to express some operations that the compiler cannot prove will be memory safe. The point being highlighted is that unsafe rust is not a mechanism the programmer can use to do unsafe things, but rather a promise to the compiler that the code is safe even though the compiler cannot prove it. The burden of proof then lies on the programmer [and reviewers] to ensure that all the invariants that the function requires to hold, does hold. This does not mean the programmer cannot provide an incorrect proof, but it does make the following less likely:

Stricter variant

A possible future improvement would update the missing_safety_doc lint to require the documentation to provide a list of the invariants required by the unsafe fn, each with an identifier - e.g. dangling_pointer and an explanation of what it entails. The missing_safety_proof lint would then provide the same list of identifiers, but instead of explaining the required invariant would contain a proof of why that invariant holds at that point in the code. The documentation can then easily provide links from the proof to the explanation via the identifier.

Some Safety documentation is already in list form, e.g. pointer as_ref method:

/// # Safety
///
/// When calling this method, you have to ensure that *either* the pointer is null *or*
/// all of the following is true:
///
/// * The pointer must be properly aligned.
///
/// * It must be "dereferenceable" in the sense defined in [the module documentation].
///
/// * The pointer must point to an initialized instance of `T`.
///
/// * You must enforce Rust's aliasing rules, since the returned lifetime `'a` is
///   arbitrarily chosen and does not necessarily reflect the actual lifetime of the data.
///   In particular, while this reference exists, the memory the pointer points to must
///   not get mutated (except inside `UnsafeCell`).
///
/// This applies even if the result of this method is unused!
/// (The part about being initialized is not yet fully decided, but until
/// it is, the only safe approach is to ensure that they are indeed initialized.)
///

Lint Name

missing_safety_proof

Category

style - Same as missing_safety_check although I can see both moving to correctness.

Advantage

Drawbacks

Example

use std::ptr::null;

struct MyType {}

fn main() { let my_type = MyType {}; let reference: &MyType = &my_type; #[allow(unused_assignments)] let mut pointer = reference as *const MyType; #[allow(clippy::drop_non_drop)] drop(my_type); pointer = null(); let _valid_reference = unsafe { pointer.as_ref() }; }

Could be written as:

use std::ptr::null;

struct MyType {}

fn main() { let my_type = MyType {}; let reference: &MyType = &my_type; #[allow(unused_assignments)] let mut pointer = reference as *const MyType; #[allow(clippy::drop_non_drop)] drop(my_type); pointer = null();

// # Safety proof
// Three lines above I set the pointer to null.
let _valid_reference = unsafe { pointer.as_ref() };

}