Dropped values may escape from destructors
!120 (merged) introduces (amongst many other things) destructors. These are defined like so:
import std::drop::Drop
class Thing {
...
}
impl Drop for Thing {
fn mut drop {
...
}
}
The destructor needs to be mutable, as dropping certain values may require
mutable access to fields. This however introduces a problem: self
may escape
through such values. A simple example of this is the following:
import std::drop::(drop, Drop)
class Boom {
let @values: mut Array[Boom]
}
impl Drop for Boom {
fn mut drop {
@values.push(self)
}
}
class async Main {
fn async main {
let values = []
let boom = Boom { @values = values }
drop(boom)
# `values` now contains a `Boom` that has been released.
}
}
This can then result in use-after-free errors. Note that this can only happen through mutable references stored in the value to drop. Owned values are, well, owned, and so we can't escape through them.
We could insert a CheckRefs
instruction after calling the destructor (if one
is defined), but this results in two such checks: one when we initially decide
to drop the value (before calling the destructor), and one after calling the
destructor. Assuming 99.99% of destructors won't escape dropped values, this is
a waste of CPU time.
Most likely we'll need some form of (general) escape analysis, then build upon
that for drop
such that we can disallow self
or any fields from
escaping/outliving a drop
.