Skip to content

Commit afb2e6c

Browse files
committed
add unsafe attributes RFC
1 parent e68eb8f commit afb2e6c

File tree

1 file changed

+173
-0
lines changed

1 file changed

+173
-0
lines changed

text/0000-unsafe-attributes.md

+173
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
- Feature Name: `unsafe_attributes`
2+
- Start Date: 2022-10-11
3+
- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000)
4+
- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000)
5+
6+
# Summary
7+
[summary]: #summary
8+
9+
Consider some attributes 'unsafe', so that they must only be used like this:
10+
11+
```rust
12+
#[unsafe(no_mangle)]
13+
```
14+
15+
# Motivation
16+
[motivation]: #motivation
17+
18+
Some of our attributes, such as `no_mangle`, can be used to
19+
[cause Undefined Behavior without any `unsafe` block](https://github.com/rust-lang/rust/issues/28179).
20+
If this was regular code we would require them to be placed in an `unsafe {}`
21+
block, but since they are attributes that makes less sense. Hence we need a
22+
concept of 'unsafe attributes' and accompanying syntax to declare that one is
23+
aware of the UB risks here (and it might be good to add a SAFETY comment
24+
explaining why this use of the attribute is fine).
25+
26+
# Guide-level explanation
27+
[guide-level-explanation]: #guide-level-explanation
28+
29+
*Example explanation for `no_mangle`; the other attributes need something similar.*
30+
31+
When declaring a function like this
32+
33+
```rust
34+
#[no_mangle]
35+
pub fn write(...) { ... }
36+
```
37+
38+
this will cause Rust to generate a globally visible function with the
39+
linker/export name `write`. As consequence of that, other code that wants to
40+
call the
41+
[C `write` function](https://man7.org/linux/man-pages/man2/write.2.html) might
42+
end up calling this other `write` instead. This can easily lead to Undefined
43+
Behavior:
44+
- The other `write` might have the wrong signature, so arguments are passed
45+
incorrectly.
46+
- The other `write` might not have the expected behavior of
47+
[write](https://man7.org/linux/man-pages/man2/write.2.html), causing code
48+
relying on this behavior to misbehave.
49+
50+
To avoid this, when declaring a function `no_mangle`, it is important that the
51+
name of the function does not clash with other globally named functions. Similar
52+
to how `unsafe { ... }` blocks are used to acknowledge that this code is
53+
dangerous and needs manual checking, `unsafe(no_mangle)` acknowledges that
54+
`no_mangle` is dangerous and needs to be manually checked for correctness:
55+
56+
```rust
57+
// SAFETY: there is no other global function of this name
58+
#[unsafe(no_mangle)]
59+
pub fn my_own_write(...) { ... }
60+
```
61+
62+
# Reference-level explanation
63+
[reference-level-explanation]: #reference-level-explanation
64+
65+
Some attributes (e.g. `no_mangle`, `export_name`, `link_section` -- see
66+
[here](https://github.com/rust-lang/rust/issues/82499) for a more complete list)
67+
are considered "unsafe" attributes. An unsafe attribute must only be used inside
68+
`unsafe(...)` in the attribute declaration, like
69+
70+
```rust
71+
#[unsafe(no_mangle)]
72+
```
73+
74+
For backwards compatibility reasons, using these attributes outside of
75+
`unsafe(...)` is just a warning, not a hard error. Unsafe attributes that are
76+
added in the future can hard-require `unsafe` from the start since the backwards
77+
compatibility concern does not apply to them.
78+
79+
Syntactically, for each unsafe attribute `attr`, we now also accept
80+
`unsafe(attr)` anywhere that `attr` can be used. `unsafe` cannot be nested,
81+
cannot contain `cfg_attr`, and cannot contain any other (non-unsafe) attributes.
82+
83+
The `deny(unsafe_code)` lint denies the use of unsafe attributes both inside and
84+
outside of `unsafe(...)` blocks. (That lint currently has special handling to
85+
deny these attributes. Once there is a general notion of 'unsafe attributes' as
86+
proposed by this RFC, that special handling will no longer be needed.)
87+
88+
# Drawbacks
89+
[drawbacks]: #drawbacks
90+
91+
I think if we had thought of this around Rust 1.0, then this would be rather
92+
uncontroversial. As things stand now, this proposal will cause a lot of churn
93+
since all existing uses of these unsafe attributes need to be adjusted. The
94+
warning for using unsafe attributes outside `unsafe(...)` should probably have
95+
an auto-fix available to help ease the transition here.
96+
97+
# Rationale and alternatives
98+
[rationale-and-alternatives]: #rationale-and-alternatives
99+
100+
- **Nothing.** We could do nothing at all, and live with the status quo. However
101+
then we will not be able to fix issues like
102+
[`no_mangle` being unsound](https://github.com/rust-lang/rust/issues/28179),
103+
which is one of the oldest open soundness issues.
104+
- **Rename.** We could just rename the attributes to `unsafe_no_mangle` etc.
105+
However that is inconsistent with how we approach `unsafe` on expressions, and
106+
feels much less systematic and much more ad-hoc.
107+
- **`deny(unsafe_code)`.** We already
108+
[started the process](https://github.com/rust-lang/rust/issues/82499) of
109+
rejecting these attributes when `deny(unsafe_code)` is used. We could say that
110+
is enough. However the RFC authors thinks that is insufficient, since only few
111+
crates use that lint, and since it is the wrong default for Rust (users have
112+
to opt-in to a soundness-critical diagnostic -- that's totally against the
113+
"safety by default" goal of Rust). This RFC says that yes, `deny(unsafe_code)`
114+
should deny those attributes, but we should go further and require an explicit
115+
`unsafe(...)` attribute block for them to be used at all.
116+
- **Item-level unsafe blocks.** We could find some way to have 'unsafe blocks'
117+
around entire functions or modules. However, those would go against the usual
118+
goal of keeping `unsafe` blocks small. Big `unsafe` blocks risk accidentally
119+
calling an unsafe operation in there without even realizing it.
120+
- **Other syntax.** Obviously we could pick a different syntax for the same
121+
concept, but this seems like the most natural marriage of the idea of unsafe
122+
blocks from regular code, and the existing attributes syntax.
123+
124+
# Prior art
125+
[prior-art]: #prior-art
126+
127+
We have `unsafe` blocks; this is basically the same thing for the "attributes
128+
DSL".
129+
130+
In the attribute DSL, we already have a "nesting" construct: `cfg_attr`. That
131+
allows terms like
132+
`#[cfg_attr(debug_assertions, deny(unsafe_code), allow(unused))]`, so there is
133+
precedent for having a list of attributes inside a single attribute.
134+
135+
I don't know of other languages that would distinguish safe and unsafe
136+
attributes.
137+
138+
# Unresolved questions
139+
[unresolved-questions]: #unresolved-questions
140+
141+
- **Different lint staging.** The lint on using existing unsafe attributes like
142+
`no_mangle` outside `unsafe(...)` could be staged in various ways: it could be
143+
allow-by-default to start, it could be edition-dependent, it might eventually
144+
be deny-by-default or even a hard error on some editions -- there are lots of
145+
details here, which can be determined later during the process.
146+
147+
# Future possibilities
148+
[future-possibilities]: #future-possibilities
149+
150+
- **Unsafe attribute proc macros.** We could imagine something like
151+
```
152+
#[proc_macro_attribute(require_unsafe)]
153+
fn spoopy(args: TokenStream, input: TokenStream) -> TokenStream {…}
154+
```
155+
to declare that an attribute proc macro is unsafe to use, and must only
156+
occur as an unsafe macro. Such an unsafe-to-use attribute proc macro must
157+
declare in a comment what its safety requirements are. (This is the `unsafe`
158+
from `unsafe fn`, whereas the rest of the RFC is using the `unsafe` from
159+
`unsafe { ... }`.)
160+
- **Unsafe derive.** We could use `#[unsafe(derive(Trait))]` to derive an
161+
`unsafe impl` where the deriving macro itself cannot check all required safety
162+
conditions.
163+
- **Unsafe tool attributes.** Same as above, but for tool attributes.
164+
- **Unsafe attributes on statements.** For now, the only unsafe attributes we
165+
have don't make sense on the statement level. Once we do have unsafe statement
166+
attributes, we need to figure out whether inside `unsafe {}` blocks one still
167+
needs to also write `unsafe(...)`.
168+
- **Lists and nesting.** We could specify that `unsafe(...)` may contain a list
169+
of arbitrary attributes (including safe ones), may be nested, and may contain
170+
`cfg_attr` that gets expanded appropriately. However that could make it tricky
171+
to consistently support non-builtin unsafe attributes in the future, so the
172+
RFC proposes to not do that yet. The current approach is forward-compatible
173+
with allowing lists and nesting in the future.

0 commit comments

Comments
 (0)