Skip to content

Commit f19f101

Browse files
authored
Merge pull request #48 from RalfJung/patterns
add document on consts in patterns
2 parents d4fd617 + 18f3020 commit f19f101

File tree

2 files changed

+68
-0
lines changed

2 files changed

+68
-0
lines changed

const.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ To make this work, we need to ensure [const safety](const_safety.md).
1212
Based on this requirement, we allow other constants and [promoteds](promotion.md) to read from constants.
1313
This is why the value of a `const` is subject to validity checks.
1414

15+
Constants can also be [used in patterns](patterns.md), which brings its own soundness concerns.
16+
1517
## References
1618

1719
One issue is constants of reference type:

patterns.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Constants in patterns
2+
3+
Constants that pass the [structural equality check][struct-eq] can be used in patterns:
4+
```rust
5+
const FOO: i32 = 13;
6+
7+
fn is_foo(x: i32) -> bool {
8+
match x {
9+
FOO => true,
10+
_ => false,
11+
}
12+
}
13+
```
14+
However, that check has some loopholes, so e.g. `&T` can be used in a pattern no matter the `T`.
15+
16+
Any reference type const-pattern is compiled by [calling `PartialEq::eq`][compile-partial-eq] to compare the subject of the `match` with the constant. Const-patterns with other types (enum, struct, tuple, array) are treated as if the constant was inlined as a pattern (and the usual `match` tree is constructed).
17+
[RFC 1445](https://github.com/rust-lang/rfcs/blob/master/text/1445-restrict-constants-in-patterns.md) lays the groundwork for changing this in the future to always using `PartialEq::eq`, but no decision either way has been made yet.
18+
19+
[struct-eq]: https://github.com/rust-lang/rust/blob/2c28244cf0fc9868f55070e55b8f332d196eaf3f/src/librustc_mir_build/hair/pattern/const_to_pat.rs#L121
20+
[compile-partial-eq]: https://github.com/rust-lang/rust/blob/2c28244cf0fc9868f55070e55b8f332d196eaf3f/src/librustc_mir_build/build/matches/test.rs#L355
21+
22+
## Soundness concerns
23+
24+
Most of the time there are no extra soundness concerns due to const-patterns; except for surprising behavior nothing can go wrong when the structural equality check gives a wrong answer.
25+
However, there is one exception: some constants participate in exhaustiveness checking.
26+
If a pattern is incorrectly considered exhaustive, that leads to a critical soundness bug.
27+
28+
Exhaustiveness checking is done for all constants that do not have reference type, as well as `&[u8]` and `&str`.
29+
This means we can write:
30+
```rust
31+
#![feature(exclusive_range_pattern)]
32+
#![feature(half_open_range_patterns)]
33+
const PAT: &[u8] = &[0];
34+
35+
pub fn test(x: &[u8]) -> bool {
36+
match x {
37+
PAT => true,
38+
&[] => false,
39+
&[1..] => false,
40+
&[_, _, ..] => false
41+
}
42+
}
43+
```
44+
45+
To ensure soundness of exhaustiveness checking, it is crucial that all data considered this check is fully immutable.
46+
In particular, for constants of reference type, it is important that they only point to immutable data.
47+
For this reason, the static const checks reject references to `static` items.
48+
This is a new soundness concern that otherwise does not come up during CTFE.
49+
Note that this is orthogonal to the concern of [*reading from* a mutable `static`](const.md#reading-statics) during const initialization, which is a problem for all `const` (even when they are not used in patterns) because it breaks the "inlining" property.
50+
A more precise check could be possible, but is non-trivial: even an immutable `static` could point to a mutable `static`; that would have to be excluded.
51+
(We could, in principle, also rely on it being UB to have a shared reference to mutable memory, but for now we prefer not to rely on the aliasing model like that---aliasing of global pointers is a tricky subject.)
52+
53+
*Dynamic check.*
54+
To dynamically ensure constants only point to read-only data, we maintain a global invariant:
55+
pointers to a `static` (including memory that was created as part of interning a `static`) may never "leak" to a non-`static`.
56+
All memory outside of `static`s is marked as immutable.
57+
As a consequence, non-`static`s recursively only point to immutable memory.
58+
59+
This check is uncomfortably close to the static checks, and fragile due to its reliance on a global invariant.
60+
Longer-term, it would be better to move to a more local check, by making validation check if consts are recursively read-only.
61+
This check (unlike our current validation) would have to descend through pointers to `static`.
62+
63+
An alternative could be to wait for the [value tree proposal](https://github.com/rust-lang/compiler-team/issues/323) to be implemented.
64+
Then we could check during value tree construction that all memory is read-only (if we use the CTFE machine in `const`-mode, that will happen implicitly), and thus we know for sure we can rely on the value tree to be sound to use for exhaustiveness checking.
65+
How exactly this looks like depends on how we resolve [pattern matching allowing many things that do not fit a value tree](https://github.com/rust-lang/rust/issues/74446#issuecomment-663439899).
66+
However, if only the well-behaved part of the value tree is used for exhaustiveness checking, handling these extra patterns should not interact closely with the soundness concerns discussed here.

0 commit comments

Comments
 (0)