mirror of https://github.com/rust-lang/nomicon
parent
680e284b0a
commit
6cbff7eaa6
@ -0,0 +1,434 @@
|
|||||||
|
# Drop Check: The Escape Patch
|
||||||
|
|
||||||
|
In spite of everything stated in the previous section, this code compiles:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn main() {
|
||||||
|
let (day, inspector);
|
||||||
|
day = Box::new(0);
|
||||||
|
inspector = vec![&*day];
|
||||||
|
println!("{:?}", inspector);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Here instead of storing a reference in our own custom type, we use a Vec, which
|
||||||
|
is of course generic and implements Drop. Surprisingly, the compiler thinks it's
|
||||||
|
fine that the Vec stores a reference that lives exactly as long as it.
|
||||||
|
|
||||||
|
What happened?
|
||||||
|
|
||||||
|
Well, there's an unsafe escape hatch, and the standard library uses it for its
|
||||||
|
collections and owning pointer types such as Box, Rc, Vec, and BTreeMap. With
|
||||||
|
this escape hatch, we can tell the compiler that we *promise* that it's safe
|
||||||
|
for a given generic argument to dangle.
|
||||||
|
|
||||||
|
Before we proceed, I must emphasize that this is an incredibly obscure feature.
|
||||||
|
As in, many people who work on the Rust standard library and rustc itself don't
|
||||||
|
even know that this exists. So with all likelihood, no one will notice if you
|
||||||
|
don't use this feature.
|
||||||
|
|
||||||
|
**In other words: please don't use the unstable feature we're about to describe!**
|
||||||
|
|
||||||
|
This feature primarily exists because some tricky parts of rustc itself use it.
|
||||||
|
We document it here primarily for the purposes of maintaining the rust-lang
|
||||||
|
codebase itself.
|
||||||
|
|
||||||
|
So let's say we want to write this modified inspector code:
|
||||||
|
|
||||||
|
```rust,ignore
|
||||||
|
struct Inspector<'a, T: 'a> { data: &'a T }
|
||||||
|
|
||||||
|
impl<'a, T> Drop for Inspector<'a, T> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
println!("I was only one day from retirement!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let (data, inspector);
|
||||||
|
data = Box::new(0u8);
|
||||||
|
inspector = Inspector { data: &*data };
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This Inspector is perfectly safe, because it doesn't actually access its
|
||||||
|
generic data in its destructor. Sadly, the code still doesn't compile:
|
||||||
|
|
||||||
|
```text
|
||||||
|
error: `*data` does not live long enough
|
||||||
|
--> src/main.rs:13:1
|
||||||
|
|
|
||||||
|
12 | inspector = Inspector { data: &*data };
|
||||||
|
| ----- borrow occurs here
|
||||||
|
13 | }
|
||||||
|
| ^ `*data` dropped here while still borrowed
|
||||||
|
|
|
||||||
|
= note: values in a scope are dropped in the opposite order they are created
|
||||||
|
```
|
||||||
|
|
||||||
|
This is because as far as Rust is concerned you *could have* accessed it, and
|
||||||
|
Rust refuses to inspect your drop implementation to be sure.
|
||||||
|
|
||||||
|
With [the eyepatch RFC][eyepatch], we can *partially blind* dropck, by hiding one of our
|
||||||
|
generic parameters from it. (...by covering it with a patch. Get it? ...Eyepatch?)
|
||||||
|
|
||||||
|
The patch we apply is as follows:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#![feature(generic_param_attrs, dropck_eyepatch)]
|
||||||
|
|
||||||
|
struct Inspector<'a, T: 'a> { data: &'a T }
|
||||||
|
|
||||||
|
// Changes here:
|
||||||
|
unsafe impl<#[may_dangle] 'a, T> Drop for Inspector<'a, T> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
println!("I was only one day from retirement!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let (data, inspector);
|
||||||
|
data = Box::new(0u8);
|
||||||
|
inspector = Inspector { data: &*data };
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
...and it compiles and runs!
|
||||||
|
|
||||||
|
There are two changes:
|
||||||
|
|
||||||
|
* We add `#[may_dangle]` to one of our type parameters
|
||||||
|
* We add `unsafe` to the impl block (which is required by may_dangle to emphasize the risks)
|
||||||
|
|
||||||
|
Note also that `#[may_dangle]` requires both the `generic_param_attrs`, and the
|
||||||
|
`dropck_eyepatch` features.
|
||||||
|
|
||||||
|
The may_dangle attribute tells the dropck to ignore `'a` in its analysis. Since this was
|
||||||
|
the only reason the Inspector was considered unsound (T is just a u8), our code compiles.
|
||||||
|
|
||||||
|
`#[may_dangle]` may be applied to any type parameter. For instance, if we change
|
||||||
|
`data` to just `T` (so `T = &'a u8`), then we need to blind dropck from `T`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#![feature(generic_param_attrs, dropck_eyepatch)]
|
||||||
|
|
||||||
|
struct Inspector<T> { data: T }
|
||||||
|
|
||||||
|
// Changes here:
|
||||||
|
unsafe impl<#[may_dangle] T> Drop for Inspector<T> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
println!("I was only one day from retirement!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let (data, inspector);
|
||||||
|
data = Box::new(0u8);
|
||||||
|
inspector = Inspector { data: &*data };
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# When The Eyepatch is (Un)Sound
|
||||||
|
|
||||||
|
The general rule for when it's safe to apply the dropck eyepatch to a type parameter
|
||||||
|
`T` is that the destructor must only do things to values of type `T` that could be
|
||||||
|
done with *all* types. Basically: we can move (or copy) the values around, take
|
||||||
|
references to them, get their size/align, and drop them. Just to be clear
|
||||||
|
on why these are fine:
|
||||||
|
|
||||||
|
* Moving and Copying is just bitwise, and it's perfectly safe to copy the bits
|
||||||
|
representing a dangling pointer.
|
||||||
|
|
||||||
|
* Static size/align computation (as with `size_of`) doesn't involve actually
|
||||||
|
looking at instances of the type, so dangling doesn't matter.
|
||||||
|
|
||||||
|
* Dynamic size/align computation (as with `size_of_val`) is also fine, because
|
||||||
|
it only looks at the trait object's vtable. This vtable is statically
|
||||||
|
allocated, and can be found without looking at the actual instance's data.
|
||||||
|
|
||||||
|
* Dropping a pointer is a noop, so it doesn't matter if they're actually
|
||||||
|
dangling.
|
||||||
|
|
||||||
|
In theory, a function that's generic over all `T` (like `mem::replace`) must also
|
||||||
|
follow these rules, but in a world with specialization that isn't necessarily true.
|
||||||
|
For instance any totally-generic function may specialize on `T: Display` to print
|
||||||
|
the values when possible (please file 100 bugs if `mem::replace` ever does this).
|
||||||
|
|
||||||
|
Also note that the following closure isn't actually generic over all values of
|
||||||
|
type `T`; its body knows the exact type of `T` and therefore can dereference
|
||||||
|
any dangling pointers `T` might contain:
|
||||||
|
|
||||||
|
```rust,ignore
|
||||||
|
impl<T, F> where F: Fn(T) { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
All `Vec<T>` and friends do in their destructors is traverse themselves using their
|
||||||
|
own structure, drop all of the `T`'s they contain, and free themselves. This is
|
||||||
|
why it's sound for them to apply the eyepatch to their parameters.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# When The Eyepatch Needs Help
|
||||||
|
|
||||||
|
Applying the eyepatch correctly isn't sufficient to get a sound drop checking.
|
||||||
|
To see why, consider this example:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#![feature(generic_param_attrs, dropck_eyepatch)]
|
||||||
|
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
struct Inspector<T: Debug> { data: T }
|
||||||
|
|
||||||
|
// Doesn't use eyepatch, but clearly looks at its payload. This is fine.
|
||||||
|
// Dropck will correctly require that this strictly outlives its payload.
|
||||||
|
impl<T: Debug> Drop for Inspector<T> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
println!("I was only {:?} days from retirement!", self.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Our own custom implementation of Box.
|
||||||
|
struct MyBox<T> {
|
||||||
|
data: *mut T,
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is uninteresting
|
||||||
|
impl<T> MyBox<T> {
|
||||||
|
fn new(t: T) -> MyBox<T> {
|
||||||
|
MyBox { data: Box::into_raw(Box::new(t)) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The stdlib's Box impl uses may_dangle, so it should be fine for us!
|
||||||
|
// (This is true... almost)
|
||||||
|
unsafe impl<#[may_dangle] T> Drop for MyBox<T> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe { Box::from_raw(self.data); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inspect() {
|
||||||
|
let (data, inspector);
|
||||||
|
|
||||||
|
// We store this in a std box to avoid distractions
|
||||||
|
data = Box::new(7u8);
|
||||||
|
|
||||||
|
// This time we store an Inspector in our custom Box type
|
||||||
|
inspector = MyBox::new(Inspector { data: &*data });
|
||||||
|
|
||||||
|
// !!! If this compiles, the Inspector will read the dangling data here !!!
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
**This compiles, and will perform a use-after-free.**
|
||||||
|
|
||||||
|
Something has gone wrong. Just to check, let's replace our use of MyBox with
|
||||||
|
std's Box.
|
||||||
|
|
||||||
|
```rust,ignore
|
||||||
|
fn inspect() {
|
||||||
|
let (data, inspector);
|
||||||
|
|
||||||
|
data = Box::new(7u8);
|
||||||
|
|
||||||
|
// This time we use std's Box type
|
||||||
|
inspector = Box::new(Inspector { data: &*data });
|
||||||
|
|
||||||
|
// !!! If this compiles, the Inspector will read the dangling data here !!!
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```text
|
||||||
|
error[E0597]: `*data` does not live long enough
|
||||||
|
--> src/main.rs:45:1
|
||||||
|
|
|
||||||
|
42 | inspector = Box::new(Inspector { data: &*data });
|
||||||
|
| ----- borrow occurs here
|
||||||
|
...
|
||||||
|
45 | }
|
||||||
|
| ^ `*data` dropped here while still borrowed
|
||||||
|
|
|
||||||
|
= note: values in a scope are dropped in the opposite order they are created
|
||||||
|
```
|
||||||
|
|
||||||
|
Somehow, dropck now notices that we're doing something bad, and catches us.
|
||||||
|
|
||||||
|
Here's the problem: dropck doesn't know that MyBox will drop an Inspector, and
|
||||||
|
it really needs to know that to perform a proper analysis. To understand the
|
||||||
|
analysis it's trying to perform, let's step back to the "generic but no
|
||||||
|
destructor" case:
|
||||||
|
|
||||||
|
|
||||||
|
```rust,ignore
|
||||||
|
// Our own custom implementation of a Box, which doesn't actually box.
|
||||||
|
struct MyFakeBox<T> {
|
||||||
|
data: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is uninteresting
|
||||||
|
impl<T> MyFakeBox<T> {
|
||||||
|
fn new(t: T) -> MyFakeBox<T> {
|
||||||
|
MyFakeBox { data: t }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inspect() {
|
||||||
|
let (data, inspector);
|
||||||
|
|
||||||
|
data = Box::new(7u8);
|
||||||
|
|
||||||
|
// This time we store an Inspector in our custom FakeBox type
|
||||||
|
inspector = MyFakeBox::new(Inspector { data: &*data });
|
||||||
|
|
||||||
|
// !!! If this compiles, the Inspector will read the dangling data here !!!
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
error[E0597]: `*data` does not live long enough
|
||||||
|
--> src/main.rs:37:1
|
||||||
|
|
|
||||||
|
34 | inspector = MyFakeBox::new(Inspector { data: &*data });
|
||||||
|
| ----- borrow occurs here
|
||||||
|
...
|
||||||
|
37 | }
|
||||||
|
| ^ `*data` dropped here while still borrowed
|
||||||
|
|
|
||||||
|
= note: values in a scope are dropped in the opposite order they are created
|
||||||
|
```
|
||||||
|
|
||||||
|
Ok, without a destructor the compiler also performs the right analysis, even
|
||||||
|
though it should let MyFakeBox contain strictly equal lifetimes when possible.
|
||||||
|
How does it know that an Inspector will be dropped when a MyFakeBox will be?
|
||||||
|
|
||||||
|
Quite simply: it looks at MyFakeBox's fields.
|
||||||
|
|
||||||
|
Without an explicit destructor, the compiler is the one providing the destructor
|
||||||
|
implementation, and so it knows exactly what will be dropped: the fields.
|
||||||
|
|
||||||
|
It turns out that this is also the exact analysis that is applied to our MyBox
|
||||||
|
type. The *problem* is that MyBox stores a `*mut T`, and the compiler knows
|
||||||
|
dropping a `*mut T` is a noop. So it decides no Inspectors are involved in the
|
||||||
|
destruction of a MyBox, and lets this code compile (which would be correct if
|
||||||
|
that conclusion were true).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Fixing Your Eyepatches
|
||||||
|
|
||||||
|
The solution to this problem is fairly simple: if the compiler is going to check
|
||||||
|
our fields for what we drop, let's add some more fields!
|
||||||
|
|
||||||
|
In particular, we will use the PhantomData type to simulate a stored T.
|
||||||
|
(We'll discuss PhantomData more in the next section. For now, take it for
|
||||||
|
granted.)
|
||||||
|
|
||||||
|
```rust,ignore
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
// Our own custom implementation of Box.
|
||||||
|
struct MyBox<T> {
|
||||||
|
data: *mut T,
|
||||||
|
_boo: PhantomData<T>, // Tell the compiler we drop a T!
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is still uninteresting
|
||||||
|
impl<T> MyBox<T> {
|
||||||
|
fn new(t: T) -> MyBox<T> {
|
||||||
|
MyBox {
|
||||||
|
data: Box::into_raw(Box::new(t)),
|
||||||
|
_boo: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Completely unchanged! We got this part right!
|
||||||
|
unsafe impl<#[may_dangle] T> Drop for MyBox<T> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe { Box::from_raw(self.data); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inspect() {
|
||||||
|
let (data, inspector);
|
||||||
|
|
||||||
|
data = Box::new(7u8);
|
||||||
|
|
||||||
|
// Back to our MyBox type
|
||||||
|
inspector = MyBox::new(Inspector { data: &*data });
|
||||||
|
|
||||||
|
// !!! If this compiles, the Inspector will read the dangling data here !!!
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```text
|
||||||
|
Compiling playground v0.0.1 (file:///playground)
|
||||||
|
error[E0597]: `*data` does not live long enough
|
||||||
|
--> src/main.rs:50:1
|
||||||
|
|
|
||||||
|
47 | inspector = MyBox::new(Inspector { data: &*data });
|
||||||
|
| ----- borrow occurs here
|
||||||
|
...
|
||||||
|
50 | }
|
||||||
|
| ^ `*data` dropped here while still borrowed
|
||||||
|
|
|
||||||
|
= note: values in a scope are dropped in the opposite order they are created
|
||||||
|
```
|
||||||
|
|
||||||
|
Hurray! It worked!
|
||||||
|
|
||||||
|
And just to check that we can still store dangling things when it's sound:
|
||||||
|
|
||||||
|
```rust,ignore
|
||||||
|
fn inspect() {
|
||||||
|
let (data, non_inspector);
|
||||||
|
|
||||||
|
data = Box::new(7u8);
|
||||||
|
|
||||||
|
// Our custom box type, but no inspector.
|
||||||
|
non_inspector = MyBox::new(&*data);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Compiles fine! Great! 🎉
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Dropck Eyepatch Summary (TL;DR)
|
||||||
|
|
||||||
|
When a generic type provides a destructor, the compiler will conservatively
|
||||||
|
disallow any of the type parameters living exactly as long as that type.
|
||||||
|
|
||||||
|
With the dropck eyepatch, we can tell it to ignore certain type parameters
|
||||||
|
which the destructor only does "trivial" things with. Which is to say, `MyType<T>`
|
||||||
|
doesn't do anything that `Vec<T>` wouldn't do with `T`.
|
||||||
|
|
||||||
|
However we then also become responsible for telling dropck about all the types
|
||||||
|
*related* to T that we drop. It knows we will drop anything in our fields, but
|
||||||
|
things like raw pointers "trick" it, as dropping a raw pointer does nothing.
|
||||||
|
|
||||||
|
To solve this, you should include a `PhantomData` field that stores each of the
|
||||||
|
types related to T that you may Drop.
|
||||||
|
|
||||||
|
**Note that this includes any associated items that you may drop in the destructor.**
|
||||||
|
|
||||||
|
For instance, if you have a destructor for `MyType<I: IntoIter>` that calls
|
||||||
|
`into_iter()`, you should probably include `PhantomData<I::IntoIter>`.
|
||||||
|
|
||||||
|
Yes, this is a big hassle and easy to get wrong. Please don't use the eyepatch.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[eyepatch]: https://github.com/rust-lang/rfcs/blob/master/text/1327-dropck-param-eyepatch.md
|
Loading…
Reference in new issue