Skip to content

Commit ac7b5e6

Browse files
committed
docs: overhaul pre-RFC syntax to align with RAPx
- Replace #[safety { ... }] with #[safety::requires(...)] etc. - Use () instead of {} for tool attributes - Merge repeated attributes into comma-separated groups - Add any(...) combinator to grammar and examples - Drop dedicated #[safety::hazard]; use kind="hazard" in requires - Simplify to 4 attributes: requires, invariant, ensures, verify - Use safety_tool for register_tool to avoid crate name conflict
1 parent 83c755d commit ac7b5e6

2 files changed

Lines changed: 161 additions & 102 deletions

File tree

pre-RFC.md

Lines changed: 149 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -49,46 +49,109 @@ descriptions of safety properties or safety requirements that must be satisfied
4949
when using an unsafe API. This is the current form of safety descriptions used in Rust.
5050

5151
In contrast, **safety tags** represent safety properties using a formal language, i.e., a
52-
[tool attribute] written in the form `#[safety { Prop: "reason" }]` where
53-
- `safety` is proc-macro,
54-
- `type` is one of `{precond, hazard, option}`,
55-
- precond denotes a safety requirement that must be satisfied before invoking an unsafe API.
56-
Most unsafe APIs carry at least one precondition.
57-
- hazard denotes invoking the unsafe API may temporarily leave the program in a vulnerable
58-
state; e.g. [`String::as_bytes_mut`].
59-
- option denotes an optional precondition for an unsafe API—conditions that are sufficient but
60-
not necessary to uphold the safety invariant.
61-
- `Prop` is a safety property (SP) instance. For safety propeties in libcore and libstd,
52+
[tool attribute] namespace `safety` with sub-attributes for each contract category:
53+
54+
- `#[safety::requires(Prop1, Prop2, ...)]`**preconditions**: safety requirements that must be
55+
satisfied before invoking an unsafe API. Most unsafe APIs carry at least one precondition.
56+
By default, every property in `requires` is a precondition. A property tagged with
57+
`kind = "hazard"` denotes that invoking the unsafe API may temporarily leave the program
58+
in a vulnerable state with respect to Rust's safety invariants (e.g. [`String::as_bytes_mut`]).
59+
- `#[safety::invariant(Prop)]`**struct invariants**: properties that must hold for every instance
60+
of a struct at all observable points.
61+
- `#[safety::verify]` — marks a function as a verification entry point.
62+
- `#[safety::ensures(Prop)]`**postconditions**: properties guaranteed after the function returns
63+
(reserved for future use).
64+
- A property group can carry an optional `kind = "..."` tag for fine-grained categorization:
65+
```rust
66+
#[safety::requires(
67+
(ValidPtr(ptr, T, 1), Align(ptr, T), kind = "precond"),
68+
(Alias(ptr, ret), kind = "hazard"),
69+
)]
70+
```
71+
When `kind` is omitted, `"precond"` is the default for `requires`, `"hazard"` for `hazard`.
72+
- `/ *Prop */`**inline discharge** on a callsite, discharging specific safety properties of the
73+
callee (see [Discharge Safety Properties](#discharge-safety-properties)).
74+
- When multiple properties share the same kind, they are grouped with commas inside a single
75+
attribute rather than repeated:
76+
```rust
77+
// Preferred (merged)
78+
#[safety::requires(ValidPtr(ptr, T, 1), Align(ptr, T), Init(ptr, T, 1))]
79+
// Equivalent but verbose
80+
// #[safety::requires(ValidPtr(ptr, T, 1))]
81+
// #[safety::requires(Align(ptr, T))]
82+
// #[safety::requires(Init(ptr, T, 1))]
83+
```
84+
- `Prop` is a safety property (SP) instance. For safety properties in libcore and libstd,
6285
refer to [this document][primitive-sp] and our ongoing [paper].
63-
- multiple SPs can be grouped together by separating them with commas, such as `SP1, SP2`.
64-
- `: "reason"` is an *optional* string to clarify what SP means in the context.
65-
- when a reason string appears, use `;` to separate props like `SP1: ""; Sp2: ""`.
6686

6787
Here are some basic syntax examples:
6888

6989
```rust
70-
#[safety { SP }]
71-
#[safety { SP1, SP2 }]
90+
#[safety::requires(SP)]
91+
#[safety::requires(SP1, SP2)]
92+
#[safety::requires((SP1, kind = "precond"), (SP2, kind = "hazard"))]
93+
#[safety::invariant(SP1, SP2)]
94+
#[safety::invariant(any(Null(p), (ValidPtr(p, T, 1), Align(p, T))))]
95+
#[safety::verify]
96+
#[safety::ensures(SP)]
97+
```
98+
99+
### The `any(...)` Combinator
100+
101+
For properties that admit alternative states, the `any(...)` combinator expresses a logical OR
102+
between disjuncts, where commas inside each parenthesized disjunct mean logical AND:
103+
104+
```text
105+
any(D1, D2)
106+
any(Null(p), (P1(p, ...), P2(p, ...)))
107+
```
72108

73-
#[safety { SP1: "reason" }]
74-
#[safety { SP1: "reason"; SP2: "reason" }]
109+
The primary use case is a **null guard**: when a pointer may legally be null, `any(Null(p), ...)`
110+
declares that the conjunct properties only need to hold when `p` is non-null. This is the
111+
raw-pointer counterpart of `Option` invariants:
75112

76-
#[safety { SP1, SP2: "shared reason for the two SPs" }]
77-
#[safety { SP1, SP2: "shared reason for the two SPs"; SP3 }]
78-
#[safety { SP3; SP1, SP2: "shared reason for the two SPs" }]
113+
```rust
114+
// A linked list node whose `next` field may be null
115+
#[safety::invariant(any(
116+
Null(self.next),
117+
(ValidPtr(self.next, Node, 1), Align(self.next, Node))
118+
))]
119+
pub struct Node {
120+
value: u32,
121+
next: *mut Node,
122+
}
123+
```
124+
125+
In the `ptr::read` example, the `Owning` precondition and `Trait(T, Copy)` advisory can be grouped
126+
under `any` because either `T: Copy` or `Owning(src)` must hold — non-Copy types require ownership
127+
transfer:
128+
129+
```rust
130+
#[safety::requires(
131+
ValidPtr(src, T, 1),
132+
Aligned(src, T),
133+
Init(src, T, 1),
134+
Alias(src, ret),
135+
any(Owning(src), Trait(T, Copy)),
136+
)]
137+
pub const unsafe fn read<T>(src: *const T) -> T { ... }
79138
```
80139

81140
We can define the annotation language with context-free grammar as follows:
82141

83142
```text
84-
SafetyAnnotation => '#' '[' 'safety' '{' SPUnits '}' ']'
85-
SPUnits => SPUnit (';' SPUnit)*
86-
SPUnit => SPItem (',' SPItem)* (':' Reasons)?
87-
SPItem => ID (Args)?
88-
ID => ([a-z][A-Z])+
89-
Args => '(' Arg (, Arg)* ')'
90-
Arg => expression
91-
Reasons => '"' Text '"'
143+
SafetyAnnotation => '#' '[' 'safety' '::' attr '(' SPGroups ')' ']'
144+
attr => 'requires' | 'invariant' | 'ensures' | 'verify'
145+
SPGroups => SPGroup (',' SPGroup)*
146+
SPGroup => '(' SPList (',' 'kind' '=' STRING)? ')'
147+
| SPList
148+
SPList => SPItem (',' SPItem)*
149+
SPItem => ID ('(' Arg (',' Arg)* ')')?
150+
| 'any' '(' Disjunct (',' Disjunct)* ')'
151+
Disjunct => SPItem
152+
| '(' SPList ')'
153+
ID => [A-Z][A-Za-z]*
154+
Arg => expression | type
92155
```
93156

94157
See the following usage of `ptr::read` as a full example.
@@ -128,14 +191,13 @@ pub const unsafe fn read<T>(src: *const T) -> T { ... }
128191

129192
We can extract safety requirements above into propeties below:
130193

131-
| Type | Property | Arguments | Description |
132-
|---------|----------|-----------|-----------------------------------------------------------------------------------------------------------------------------------------------------|
133-
| Precond | ValidPtr | src, T, 1 | `src` must be [valid] for reads (for 1 * sizeof(T) bytes). |
134-
| Precond | Aligned | src, T | `src` must be properly aligned (with T). |
135-
| Precond | Init | src, T, 1 | `src` must point to a properly initialized value of type `T`. |
136-
| Option | Trait | T, Copy | If `T` is not [`Copy`], using both the returned value and the value at `*src` can violate memory safety. |
137-
| Precond | Owning | src | Further clarification: The memory pointed by src must not be owned if T is not copy, or the object hold by *src should not be automatically dropped |
138-
| Hazard | Alias | src, ret | Further clarification: The return value may incur aliases between src and the return value |
194+
| Category | Property | Arguments | Description |
195+
|---------------|----------|-------------|-----------------------------------------------------------------------------------------------------------------------------------------------------|
196+
| `requires` | ValidPtr | src, T, 1 | `src` must be [valid] for reads (for 1 * sizeof(T) bytes). |
197+
| `requires` | Aligned | src, T | `src` must be properly aligned (with T). |
198+
| `requires` | Init | src, T, 1 | `src` must point to a properly initialized value of type `T`. |
199+
| `requires` | Alias | src, ret | The return value may incur aliases between src and the return value (informational). |
200+
| `requires` | any | Owning, Trait(T, Copy) | Either `src` must be uniquely owned (non-Copy types), or `T: Copy` must hold. |
139201

140202
[valid]: https://doc.rust-lang.org/std/ptr/index.html#safety
141203
[alignment]: https://doc.rust-lang.org/std/ptr/index.html#alignment
@@ -144,10 +206,13 @@ We can extract safety requirements above into propeties below:
144206
We can represent these safety requirements using safety tags as shown below.
145207

146208
```rust
147-
#[safety {
148-
ValidPtr, Aligned, Init, Alias,
149-
any { Owning, Trait(T, Copy) },
150-
}]
209+
#[safety::requires(
210+
ValidPtr(src, T, 1),
211+
Aligned(src, T),
212+
Init(src, T, 1),
213+
Alias(src, ret),
214+
any(Owning(src), Trait(T, Copy)),
215+
)]
151216
pub const unsafe fn read<T>(src: *const T) -> T { ... }
152217
```
153218

@@ -178,7 +243,7 @@ url = "https://doc.rust-lang.org/nightly/std/ptr/index.html#alignment"
178243
We defined a property called `Aligned`, which includes two arguments, a dynamic description derived
179244
from user input and some other fields. All fields are optional.
180245

181-
When `#[safety { Aligned(src, T) }]` is used, a corresponding doc comment is generated:
246+
When `#[safety::requires(Aligned(src, T))]` is used, a corresponding doc comment is generated:
182247

183248
```rust
184249
#[doc = "pointer `src` must be properly aligned for type `T`"]
@@ -195,10 +260,13 @@ For detailed usage and examples, refer to [tag-std#35].
195260
We also support SPs with arguments, which are required in verification scenarios.
196261

197262
```rust
198-
#[safety {
199-
ValidPtr(src, T, 1), Aligned(src, T), Init(src, T, 1), Alias(src, ret),
200-
any{ Owning(src), Trait(T, Copy) }
201-
}]
263+
#[safety::requires(
264+
ValidPtr(src, T, 1),
265+
Aligned(src, T),
266+
Init(src, T, 1),
267+
Alias(src, ret),
268+
any(Owning(src), Trait(T, Copy)),
269+
)]
202270
pub const unsafe fn read<T>(src: *const T) -> T { ... }
203271
```
204272

@@ -275,7 +343,8 @@ depends on the behavior of Guard's Drop implementation. If `try_fold::Guard::dro
275343
developers must check whether the associated safety comments still hold. (This RFC does not address
276344
this problem, but see [Entity Reference System](#reference-entity) for our thought.)
277345

278-
To address the first issue, we propose a solution based on annotating `#[safety {}]` on callsites.
346+
To address the first issue, we propose a solution based on annotating safety tags on callsites using
347+
inline `/* property */` discharge comments or `#[safety::verify]` for automated verification.
279348

280349
```rust
281350
fn try_fold<B, F, R>(&mut self, mut init: B, mut f: F) -> R {
@@ -284,11 +353,9 @@ fn try_fold<B, F, R>(&mut self, mut init: B, mut f: F) -> R {
284353
init = head.iter().map(|elem| {
285354
guard.consumed += 1;
286355

287-
#[safety {
288-
ValidPtr, Aligned, Init, Alias,
289-
Owning: "Because we incremented `guard.consumed`, the deque \
290-
effectively forgot the element, so we can take ownership."
291-
}]
356+
// SAFETY: Because we incremented `guard.consumed`, the deque
357+
// effectively forgot the element, so we can take ownership.
358+
/* ValidPtr(elem, T, 1), Aligned(elem, T), Init(elem, T, 1), Owning(elem) */
292359
unsafe { ptr::read(elem) }
293360
})
294361
.try_fold(init, &mut f)?;
@@ -319,62 +386,52 @@ LLL | unsafe { ptr::read(elem) }
319386
Since this RFC does not require significant changes to the Rust compiler or language, the
320387
implementation details discussed in this section are tool-specific and primarily focus on syntax.
321388

322-
Take one of safety tag on `ptr::read` as an example:
389+
Take one safety tag on `ptr::read` as an example:
323390

324391
```rust
325-
use safety::safety;
326-
327-
#[safety { ValidPtr }]
392+
// The `safety` tool attribute is registered via
393+
// #![feature(register_tool)]
394+
// #![register_tool(safety)]
395+
//
396+
// or injected through RUSTFLAGS:
397+
// RUSTFLAGS="--cfg=safety -Zcrate-attr=feature(register_tool) -Zcrate-attr=register_tool(safety)"
398+
399+
#[safety::requires(ValidPtr(src, T, 1), Aligned(src, T), Init(src, T, 1))]
328400
```
329401

330-
#[safety] is a procedural macro imported into scope by a crate named `safety-macro`.
331-
332-
Since we don’t have permissions to the `safety` crate, users can rename our crate in their
333-
Cargo.toml file as follows:
334-
335-
```toml
336-
# This renames the dependency `safety-macro` as `safety` within your crate.
337-
safety = { version = "0.3.0", package = "safety-macro" }
338-
```
339-
340-
Proc-macros can be directly used in `no_std` projects and even in non-Cargo environments, such as
341-
Rust for Linux, by passing the compiled `libsafety_macro.so` as a direct dependency.
342-
343-
The proc macro expands to three attributes:
402+
The attribute can expand to multiple downstream annotations for different tools. For example,
403+
`#[safety::requires(ValidPtr(src, T, 1), Aligned(src, T), Init(src, T, 1))]` on `ptr::read` could
404+
generate:
344405

345406
```rust
346407
#[doc = "`src` must be [valid] for reads.\n\n[valid]: https://doc.rust-lang.org/std/ptr/index.html#safety"]
347-
#[safety_tool::...]
408+
#[rapx::requires(ValidPtr(src, T, 1), Aligned(src, T), Init(src, T, 1))]
348409
#[kani::requires(kani::mem::can_dereference(src))]
349410
```
350411

351-
* `#[doc]` is a safety comment, possibly with extra argument infomation interpolated into the text.
352-
* `#[kani]` is a [contract]. If the safety property has a countepart of external verification macro
353-
such as kani, we hope to support this feature in the future.
354-
* `#[safety_tool]` is a [tool attribute] registered by our linter. `register_tool` feature needs to
355-
be stabilized, so developers must enable the following features in the root module:
412+
* `#[doc]` is a safety comment, possibly with extra argument information interpolated into the text.
413+
* `#[kani]` is a [contract]. If the safety property has a counterpart in an external verification
414+
tool such as kani, we hope to support this feature in the future.
415+
* `#[rapx::requires(...)]` is a [tool attribute] processed by the RAPx verifier. The
416+
`register_tool` feature is needed, which can be provided via source annotation or compiler
417+
flags:
356418

357419
[contract]: https://model-checking.github.io/kani/reference/experimental/contracts.html
358420
[register_tool]: https://github.com/rust-lang/rfcs/pull/3808
359421

360422
```rust
361423
#![feature(register_tool)]
362-
#![register_tool(safety_tool)]
424+
#![register_tool(safety)]
363425
```
364426

365427
or add them to [`--crate-attr`](https://github.com/rust-lang/rfcs/pull/3791) compiler flag:
366428

367429
```bash
368-
rustc --crate-attr="feature(register_tool)" --crate="register_tool(safety_tool)"
430+
rustc --crate-attr="feature(register_tool)" --crate-attr="register_tool(safety)"
369431
```
370432

371-
To support `#[discharges]`, additional unstable features are required to allow attributes on
372-
statements and expressions:
373-
374-
```rust
375-
#![feature(proc_macro_hygiene)]
376-
#![feature(stmt_expr_attributes)]
377-
```
433+
To support inline `/* property */` discharge comments, the RAPx driver parses comments
434+
adjacent to unsafe call sites without requiring additional unstable features.
378435

379436
Details of implementation on reference entity system belongs to the linter tool.
380437

@@ -539,11 +596,9 @@ fn try_fold<B, F, R>(&mut self, mut init: B, mut f: F) -> R
539596
guard.consumed += 1;
540597

541598
#[ref(try_fold)] // 💡
542-
#[safety {
543-
ValidPtr, Aligned, Init, Alias,
544-
Owning: "Because we incremented `guard.consumed`, the deque \
545-
effectively forgot the element, so we can take ownership."
546-
}]
599+
// SAFETY: Because we incremented `guard.consumed`, the deque
600+
// effectively forgot the element, so we can take ownership.
601+
/* ValidPtr(elem, T, 1), Aligned(elem, T), Init(elem, T, 1), Owning(elem) */
547602
unsafe { ptr::read(elem) }
548603
})
549604
.try_fold(init, &mut f)?;
@@ -592,18 +647,16 @@ Arguments in a property can be any expression, and sometimes the type of argumen
592647
analysis and doc comments:
593648

594649
```rust
595-
// Syntax1: we don't need to query type if user is asked to provide it.
596-
// But we're responsible to check the given type is valid!
597-
// So this means we have to reach type systems anyway.
598-
#[safety::precond::Aligned(p, T)]
599-
// Syntax2: we must get type info from rustc.
600-
#[safety::precond::Aligned(p)]
650+
// Syntax1: type provided explicitly — verifier can check the given type is valid
651+
#[safety::requires(Aligned(p, T))]
652+
// Syntax2: type inferred from the function signature — simpler for the annotator
653+
#[safety::requires(Aligned(p))]
601654
unsafe fn read<T>(src: *const T) {}
602655
```
603656

604657
The generic type `T` will be rendered in `#[doc]`, so it'd be tricky if the type needs
605658
[normalization] or trait bounds analysis. It happens to be the case that `ptr::read` has a safety
606-
property `#[option::Trait(T, Copy)]`.
659+
property `Trait(T, Copy)` (informational, not a hard precondition).
607660

608661
[normalization]: https://rustc-dev-guide.rust-lang.org/normalization.html
609662

0 commit comments

Comments
 (0)