Fix a soundness problem with get
This commit is contained in:
parent
e3fb7062aa
commit
11c63eaad2
2 changed files with 79 additions and 31 deletions
|
@ -273,3 +273,11 @@ fn test_static_pointer() {
|
||||||
set(key, @&VALUE);
|
set(key, @&VALUE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_owned() {
|
||||||
|
unsafe {
|
||||||
|
fn key(_x: ~int) { }
|
||||||
|
set(key, ~1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -48,23 +48,36 @@ impl Handle {
|
||||||
trait LocalData {}
|
trait LocalData {}
|
||||||
impl<T: 'static> LocalData for T {}
|
impl<T: 'static> LocalData for T {}
|
||||||
|
|
||||||
// The task-local-map actually stores all TLS information. Right now it's a list
|
// The task-local-map stores all TLS information for the currently running task.
|
||||||
// of triples of (key, value, loans). The key is a code pointer (right now at
|
// It is stored as an owned pointer into the runtime, and it's only allocated
|
||||||
// least), the value is a trait so destruction can work, and the loans value
|
// when TLS is used for the first time. This map must be very carefully
|
||||||
// is a count of the number of times the value is currently on loan via
|
// constructed because it has many mutable loans unsoundly handed out on it to
|
||||||
// `local_data_get`.
|
// the various invocations of TLS requests.
|
||||||
//
|
//
|
||||||
// TLS is designed to be able to store owned data, so `local_data_get` must
|
// One of the most important operations is loaning a value via `get` to a
|
||||||
// return a borrowed pointer to this data. In order to have a proper lifetime, a
|
// caller. In doing so, the slot that the TLS entry is occupying cannot be
|
||||||
// borrowed pointer is instead yielded to a closure specified to the `get`
|
// invalidated because upon returning it's loan state must be updated. Currently
|
||||||
// function. As a result, it would be unsound to perform `local_data_set` on the
|
// the TLS map is a vector, but this is possibly dangerous because the vector
|
||||||
// same key inside of a `local_data_get`, so we ensure at runtime that this does
|
// can be reallocated/moved when new values are pushed onto it.
|
||||||
// not happen.
|
|
||||||
//
|
//
|
||||||
// n.b. Has to be a pointer at outermost layer; the foreign call returns void *.
|
// This problem currently isn't solved in a very elegant way. Inside the `get`
|
||||||
|
// function, it internally "invalidates" all references after the loan is
|
||||||
|
// finished and looks up into the vector again. In theory this will prevent
|
||||||
|
// pointers from being moved under our feet so long as LLVM doesn't go too crazy
|
||||||
|
// with the optimizations.
|
||||||
|
//
|
||||||
|
// n.b. Other structures are not sufficient right now:
|
||||||
|
// * HashMap uses ~[T] internally (push reallocates/moves)
|
||||||
|
// * TreeMap is plausible, but it's in extra
|
||||||
|
// * dlist plausible, but not in std
|
||||||
|
// * a custom owned linked list was attempted, but difficult to write
|
||||||
|
// and involved a lot of extra code bloat
|
||||||
|
//
|
||||||
|
// n.b. Has to be stored with a pointer at outermost layer; the foreign call
|
||||||
|
// returns void *.
|
||||||
//
|
//
|
||||||
// n.b. If TLS is used heavily in future, this could be made more efficient with
|
// n.b. If TLS is used heavily in future, this could be made more efficient with
|
||||||
// a proper map.
|
// a proper map.
|
||||||
type TaskLocalMap = ~[Option<(*libc::c_void, TLSValue, uint)>];
|
type TaskLocalMap = ~[Option<(*libc::c_void, TLSValue, uint)>];
|
||||||
type TLSValue = ~LocalData:;
|
type TLSValue = ~LocalData:;
|
||||||
|
|
||||||
|
@ -181,32 +194,59 @@ pub unsafe fn local_pop<T: 'static>(handle: Handle,
|
||||||
pub unsafe fn local_get<T: 'static, U>(handle: Handle,
|
pub unsafe fn local_get<T: 'static, U>(handle: Handle,
|
||||||
key: local_data::Key<T>,
|
key: local_data::Key<T>,
|
||||||
f: &fn(Option<&T>) -> U) -> U {
|
f: &fn(Option<&T>) -> U) -> U {
|
||||||
// This does in theory take multiple mutable loans on the tls map, but the
|
// This function must be extremely careful. Because TLS can store owned
|
||||||
// references returned are never removed because the map is only increasing
|
// values, and we must have some form of `get` function other than `pop`,
|
||||||
// in size (it never shrinks).
|
// this function has to give a `&` reference back to the caller.
|
||||||
|
//
|
||||||
|
// One option is to return the reference, but this cannot be sound because
|
||||||
|
// the actual lifetime of the object is not known. The slot in TLS could not
|
||||||
|
// be modified until the object goes out of scope, but the TLS code cannot
|
||||||
|
// know when this happens.
|
||||||
|
//
|
||||||
|
// For this reason, the reference is yielded to a specified closure. This
|
||||||
|
// way the TLS code knows exactly what the lifetime of the yielded pointer
|
||||||
|
// is, allowing callers to acquire references to owned data. This is also
|
||||||
|
// sound so long as measures are taken to ensure that while a TLS slot is
|
||||||
|
// loaned out to a caller, it's not modified recursively.
|
||||||
let map = get_local_map(handle);
|
let map = get_local_map(handle);
|
||||||
let key_value = key_to_key_value(key);
|
let key_value = key_to_key_value(key);
|
||||||
for map.mut_iter().advance |entry| {
|
|
||||||
|
let pos = map.iter().position(|entry| {
|
||||||
match *entry {
|
match *entry {
|
||||||
Some((k, ref data, ref mut loans)) if k == key_value => {
|
Some((k, _, _)) if k == key_value => true, _ => false
|
||||||
let ret;
|
}
|
||||||
*loans = *loans + 1;
|
});
|
||||||
// data was created with `~~T as ~LocalData`, so we extract
|
match pos {
|
||||||
// pointer part of the trait, (as ~~T), and then use compiler
|
None => { return f(None); }
|
||||||
// coercions to achieve a '&' pointer
|
Some(i) => {
|
||||||
match *cast::transmute::<&TLSValue, &(uint, ~~T)>(data) {
|
let ret;
|
||||||
(_vtable, ref box) => {
|
match map[i] {
|
||||||
let value: &T = **box;
|
Some((_, ref data, ref mut loans)) => {
|
||||||
ret = f(Some(value));
|
*loans = *loans + 1;
|
||||||
|
// data was created with `~~T as ~LocalData`, so we extract
|
||||||
|
// pointer part of the trait, (as ~~T), and then use
|
||||||
|
// compiler coercions to achieve a '&' pointer
|
||||||
|
match *cast::transmute::<&TLSValue, &(uint, ~~T)>(data) {
|
||||||
|
(_vtable, ref box) => {
|
||||||
|
let value: &T = **box;
|
||||||
|
ret = f(Some(value));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*loans = *loans - 1;
|
_ => libc::abort()
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
|
// n.b. 'data' and 'loans' are both invalid pointers at the point
|
||||||
|
// 'f' returned because `f` could have appended more TLS items which
|
||||||
|
// in turn relocated the vector. Hence we do another lookup here to
|
||||||
|
// fixup the loans.
|
||||||
|
match map[i] {
|
||||||
|
Some((_, _, ref mut loans)) => { *loans = *loans - 1; }
|
||||||
|
None => { libc::abort(); }
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return f(None);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub unsafe fn local_set<T: 'static>(handle: Handle,
|
pub unsafe fn local_set<T: 'static>(handle: Handle,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue