# Introduction
This document details how to write good safety documentation. The goals of such documentation are:
- reduce the amount of bugs in `unsafe` code,
- help readers know immediately what conditions hold and why they hold,
- help programmers write `unsafe` code with confidence,
- simplify the work of reviewers,
## General Rules
The general theme behind all rules in this document is that everything that cannot be statically
checked by the Rust compiler and guaranteed -- modulo any compiler bugs -- need to be either checked
at runtime, or have to be documented by the means detailed here.
This is a tall task, however there are some ways to prevent having to manually document everything.
The most common way is to write a safe abstraction. This means translating every condition into
Rust-checkable types and functions. If this cannot be done then there is the option of using runtime
checks and `panic!()` when the conditions are violated. If runtime checks lead to unacceptable
performance or failure is not an option, then you will probably have to reach for `unsafe` and
documentation.
Note that there still is one last option to avoid `unsafe` code. Custom static checks implemented by
`klint`. Currently `klint` is used to ensure that that mutexes are not locked while in atomic
contexts. The rationale for this decision is that using something as fundamental as a mutex must not
be `unsafe`. This last resort is something that is not taken lightly and most APIs are better off
using `unsafe` code and relying on documentation.
To summarize, you should take the following steps:
1. Explore the currently present abstraction and see if they fulfill your needs.
2. Try to write your own abstraction layer -- feel free to reach out to us at [zulip]!
3. Evaluate whether runtime checks could be used in your use-case.
4. Implement an `unsafe` proof of concept API (does not need to be complete) for your purpose as
described by this document.
5. If you still think that it would be better to offload the API to `klint`, then contact us.
# Documentation Elements
In order to make it easy for everyone to understand what is going on this document details certain
standardized documentation elements. Some are already established convention in the Rust world,
others have been invented for the kernel.
First we will describe the two main sections that we use and then we will explain how we write
common safety conditions.
## `# Safety` section
The safety section must be present on `unsafe` traits and `unsafe` functions. This section consists
of a list of safety conditions needed to implement the trait or call the function. There are no
restrictions as to what these conditions can be, they can affect anything.
If a user upholds every listed safety property then there must not exist an execution path of the
program that runs into UB. In other words, users that use the API exactly as documented benefit from
having UB-free programs.
If the property from the previous paragraph is not guaranteed by the API, we call it *unsound*.
It is the purpose of safety documentation to avoid this at all costs and e.g. catch it during
review.
## `# Invariants` section
The `Invariants` section can be put on traits, structs, enums and unions. For functions
use the `# Guarantees` section. Structs, enums and unions requrie for any construction of the type or
modification of a field that is used in an invariant requires an `INVARIANTS` comment. This comment is similar to a `SAFETY` comment in that it needs to prove every condition.
## Specifying Safety Conditions
In both `Safety` and `Invariants` sections we use bullet point lists of safety conditions. This
section lists a comprehensive table of standardized safety conditions. Whenever possible, use these
terms instead of natural language.
We denote the set of primitive types by
`PRIMITIVE = {u8, u16, u32, u64, usize, i8, i16, i32, i64, isize, f32, f64}`.
Parameters/Fields refer to function parameters or fields of the item that is being documented.
| Syntax | Parameters/Fields | Meta-variables | Semantics |
|------------------------------------|-------------------------------|------------------------------------|---------------------------------------------------------------|
| `val in i..j` | `val: T` with `T ∈ PRIMITIVE` | `i, j: T` | `i <= val && val < j` |
| `val in i..=j` | `val: T` with `T ∈ PRIMITIVE` | `i, j: T` | `i <= val && val <= j` |
| `val in i..` | `val: T` with `T ∈ PRIMITIVE` | `i, j: T` | `i <= val && val <= T::MAX` |
| `val in ..j` | `val: T` with `T ∈ PRIMITIVE` | `i, j: T` | `T::MIN <= val && val < j` |
| `val in ..=j` | `val: T` with `T ∈ PRIMITIVE` | `i, j: T` | `T::MIN <= val && val <= j` |
| `ptr` is R-valid | `ptr: *const T` | | Abbreviation for "`ptr` is R-valid[..size_of::<T>()]" |
| `ptr` is R-valid[..j] | `ptr: *const T` | `j: usize` | Abbreviation for "`ptr` is R-valid[0..j]" |
| `ptr` is R-valid[i..j] | `ptr: *const T` | `i,j: usize` | Abbreviation for "`ptr` is R-valid[i..j] for [`'self`]" |
| `ptr` is R-valid[i..j] for `'a` | `ptr: *const T` | `i,j: usize` | `ptr` is dereferenceable from `i` to `j`: all bytes from `ptr.offset(i)` until (excluding) `ptr.offset(j)` have to be part of the same allocated object. No concurrent (another thread) writes to the memory behind `ptr` must occur (reads are still permitted). |
| `ptr` is RW-valid | `ptr: *mut T` | | Abbreviation for "`ptr` is RW-valid[..size_of::<T>()]" |
| `ptr` is RW-valid[..j] | `ptr: *mut T` | `j: usize` | Abbreviation for "`ptr` is RW-valid[0..j]" |
| `ptr` is RW-valid[i..j] | `ptr: *mut T` | `i,j: usize` | Abbreviation for "`ptr` is RW-valid[i..j] for [`'self`]" |
| `ptr` is RW-valid[i..j] for `'a` | `ptr: *mut T` | `i,j: usize` | `ptr` is R-valid[i..j] for `'a` and writeable from `i` to `j` (all bytes from `ptr.offset(i)` until (excluding) `ptr.offset(j)`). No concurrent (another thread) reads or writes to the memory behind `ptr` must occur. |
| `EX $var: T` where COND | | `var: T`, `COND` a meta condition | There exists a value of type `T` that satisfies COND (any condition; preferable defined using this document). For the rest of the documented item there exists the meta variable `$var` with some value that satisfies the condition. The `$` is part of the name of that variable to distinguish it from normal variables. Also take care that the value is either unique or the item can use any value that satisfies COND |
| while `RET` is alive uphold COND | | `COND` a meta condition | For as long as the returned value is alive -- i.e. until it is no longer used, this can be before any associated lifetimes expire -- COND has to be true. Note that dropping a value with a `Drop` implementation aka drop-glue also counts as a use. |
| during `'a` uphold COND | `'a` lifetime | `COND` a meta condition | While `'a` has not expired COND has to be true. |
| `bytes` is valid UTF-8 | `bytes: &[u8]` | | The sequence of bytes contained in `bytes` forms valid UTF-8. |
### Meta Conditions
Some safety conditions require us to write some explicit conditions. We use the following notation for that:
| Syntax | Meta-variables | Semantics |
|------------------------------------|---------------------------------------------------------|-----------------------------------------------------------------------------------------------------|
| `mem` is not mutated | `mem` an expression evaluating to a memory location | The memory location must not be mutated (this should be part of a continuously checked condition) |
If the property you require is not present above, then you can try to contact us to see if it would
be logical to add it. If not, then you can still specify the property in two ways:
1. If the property can be expressed by Rust code, then just do so, e.g. for a string `s` you could
require that `s.len() > 2`.
2. If that is not an option, then write your property in natural language, but prefix it with
`TOFORMALIZE:` when the time for review rolls around the reviewers will discuss and try to
find a good formalization. You should also add that prefix to `SAFETY` and `INVARIANT`
discharge comments.
## Proving Properties
The discharging of `Safety` and `Invariants` sections happens in non-doc comments prefixed with
`SAFETY` and `INVARIANT` respectively. They should mirror the section on the used item and provide a
proof for every safety condition. To make life easier there are standardized terms to prove certain
safety conditions. The syntax is: `$cond by $justification` where `$cond` is the safety condition
that is required. It is important that the proven property is repeated here, since it lets reviewers
discover mismatching discharging comments. It also helps readers to understand what properties are
actually required for e.g. a function call. The `$justification` should be one from the list below.
| Syntax | Meta-variables | Semantics |
|----------------------|-----------------------|---------------------------------------------------------------------------------------------------------------|
| FR | | "FR = function requirements" the required safety condition is also required by the sorrounding function |
| TI | | Abbreviation for "TI of `Self`" |
| TI of `T` | `T`: arbitrary type | "TI = type invariant" the required safety condition is provided by `T`'s invariants |
| check above | | A check in the code above (up to 5 lines above the comment counting from `SAFETY`). |
| check `name` | `name` check identifier | A check in the code labeled by `name`. Labels should be `//CHECK: name`. |
| FG of `func` | `func`: function name | "FG = function guarantee" the function `func` guarantees the safety condition in its `# Guarantees` section |
| DEF of `var` | `var`: variable name | "DEF = definition" the safety condition is satisfied by the defintion/construction of `var` |
| use of `var` | `var`: variable name | The usage of `var` guarantees the safety condition |
When multiple justifications have to be considered together write `by $just1 & $just2`.
When using "FR" you are allowed to use the following shorthand: "SAFETY: .. by FR"
if the safety conditions of the current function exactly match the safety conditions of the called
function. The same can be done for `INVARIANT` comments. We allow this, since the safety conditions
are already listed on the function and the reader knows the context.
## General Formatting
When there are no safety condition on a function but it is still `unsafe` e.g. because it is an ffi
function, then we write `SAFETY: <nothing>,`. If there is exactly one safety condition then you
should write it in the same line as `SAFETY`. If there are more than one condition then you should
write a bullet point list with the same order as on the definition. A comma should be present at the
end of every item in these lists. This also applies to the last item, this makes appending the list
easier, since you do not have to change `,` to `.`.
## Examples
TODO
# `Send` and `Sync`
# Type invariants
Types may list custom invariants in their documentation. These must only affect fields that are not
`pub`. Invariants should be specified similarly to safety sections in this document:
- an itemized list of invariants using the concise conventions from this document.
```rust
/// # Invariants
/// - `count in 10..100`,
pub struct Foo {
count: usize
}
```
Then the module owning `Foo` must uphold that invariant, every time the field is set, an appropriate
`INVARIANT` comment must be present that explains why the invariant is upheld. For example:
```rust
impl Foo {
pub fn set_count(&mut self, count: usize) {
if count >= 10 && count < 100 {
// INVARIANT: by check above: `count in 10..100`.
self.count = count;
}
}
}
```
# Unresolved Questions
## Checking C API
When calling FFI functions, how do we check the safety reqs?
1. have a list of function -> reqs somewhere
[zulip]: https://rust-for-linux.zulipchat.com/