-
Notifications
You must be signed in to change notification settings - Fork 17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Attempt to fix cyclic re-borrow issue #29
Conversation
090eac6
to
dff568f
Compare
@steffahn I'm a little worried about the change in leak_dependent.rs test tests/invalid/leak_dependent.rs ... mismatch
EXPECTED:
┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈
error: lifetime may not live long enough
--> $DIR/leak_dependent.rs:18:58
|
18 | let _leaked_ref = cell.with_dependent(|_, dependent| dependent);
| - - ^^^^^^^^^ returning this value requires that `'1` must outlive `'2`
| | |
| | return type of closure is &Cell<&'2 String>
| has type `&'1 String`
┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈
ACTUAL OUTPUT:
┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈
error[E0597]: `cell` does not live long enough
--> $DIR/leak_dependent.rs:18:23
|
18 | let _leaked_ref = cell.with_dependent(|_, dependent| dependent);
| ^^^^ --------- returning this value requires that `cell` is borrowed for `'static`
| |
| borrowed value does not live long enough
19 | }
| - `cell` dropped here while still borrowed
┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ It asks for a static lifetime. Without having tried it, something where the owner is covariant over it's lifetime and the dependent is not_covariant, we might be able to coerce the cell via owner into a static lifetime which the leaked ref then abused as seen in #5. What do you think? |
The new error mirrors what would happen with #[self_referencing]
struct NoCov2 {
owner: String,
#[not_covariant]
#[borrows(owner)]
dependent: NotCovariant<'this>,
}
fn main() {
let cell = NoCov2::new("hi this is no good".into(), |owner| Cell::new(owner));
let _leaked_ref = cell.with_dependent(|dependent| dependent);
}
This is interesting behavior, but I think I understand what’s going on. If you were to get a I’m surprised that rustc seems to be able to come to that conclusion (i.e. that // in the ouroboros example
fn main() {
let cell = NoCov2::new("hi this is no good".into(), |owner| Cell::new(owner));
let cell = Box::leak(Box::new(cell));
let _leaked_ref = cell.with_dependent(|dependent| dependent);
} or // in the self_cell example
fn main() {
let cell = NoCov:new("hi this is no good".into(), |owner| Cell::new(owner));
let cell = Box::leak(Box::new(cell));
let _leaked_ref = cell.with_dependent(|_, dependent| dependent);
} I have yet to look into #5 in order to give an answer to your question at the end. |
I’m having a hard time connecting what you explain here with the issue described in #5. Could you perhaps try to explain your (potential) concern a bit more detailed? In particular you talk about an owner with a lifetime. If the owner has a lifetime, the situation is
then Here, initially we have I.e. use std::{cell::Cell, marker::PhantomData};
use self_cell::self_cell;
type NotCovariant<'a> = Cell<&'a String>;
struct Owner<'a>(String, PhantomData<&'a ()>);
self_cell!(
struct NoCov<'a> {
owner: Owner<'a>,
#[not_covariant]
dependent: NotCovariant,
}
);
fn main() {
let cell = NoCov::new(Owner("hi this is no good".into(), PhantomData), |owner| {
Cell::new(&owner.0)
});
let _leaked_ref = cell.with_dependent(|_, dependent| dependent);
} let’s see if that’s problematic ……… yes it is 🙈 use std::{cell::Cell, marker::PhantomData};
use self_cell::self_cell;
type NotCovariant<'a> = Cell<&'a str>;
struct Owner<'a>(String, PhantomData<&'a ()>);
self_cell!(
struct NoCov<'a> {
owner: Owner<'a>,
#[not_covariant]
dependent: NotCovariant,
}
);
fn main() {
let cell = NoCov::new(Owner("hi this is no good".into(), PhantomData), |owner| {
Cell::new(&owner.0)
});
let leaked_ref = cell.with_dependent(|_, dependent| dependent);
leaked_ref.set(&String::from("temporary"));
cell.with_dependent(|_, dep| {
println!("{}", dep.replace(""));
});
} goddammit. |
It’s a bit too late today already, I think I’ll do further comparisons to |
I've not yet found the time to look at your examples in detail. A rough idea: pub fn with_dependent<'outer_fn, Ret>(
&'outer_borrow self,
func: impl for<'_q, 'outer_fn: '_q> FnOnce(&'_q (), &'_q Bar<'_q>) -> Ret
) -> Ret Somehow apply both constraints the '_q as before that stops leaking inner refs and the fn lifetime that stops #28 |
Right, I should probably continue to write something here :-D Anything that's providing any W.r.t. what #[self_referencing]
struct NoCov<'a> {
owner: Owner<'a>,
#[not_covariant]
#[borrows(owner)]
dependent: NotCovariant<'this>,
} would make They do this by using #[self_referencing]
struct Foo {
owner: Owner,
#[not_covariant]
#[borrows(owner)]
dependent: NotCovariant<'this>,
} translates to // actually inside of a module, so the fields are really inaccessible
struct Foo {
owner: AliasableBox<Owner>,
dependent: NotCovariant<'static>,
} , something like #[self_referencing]
struct NoCov<'a> {
owner: Owner<'a>,
#[not_covariant]
#[borrows(owner)]
dependent: NotCovariant<'this>,
} translates to // actually inside of a module, so the fields are really inaccessible
struct NoCov<'a> {
owner: AliasableBox<Owner<'a>>,
dependent: NotCovariant<'a>,
} |
Look, I just noticed the same kind of problem as described above can happen on the current version of use std::{cell::Cell, marker::PhantomData};
use self_cell::self_cell;
self_cell! {
struct Foo<'a> {
owner: PhantomData<&'a ()>,
#[not_covariant]
dependent: Dependent,
}
}
type Dependent<'q> = Cell<&'q str>;
fn main() {
let foo = Foo::new(PhantomData, |_| Cell::new(""));
let s: String = "Hello World".into();
foo.with_dependent(|_, d| {
d.set(&s);
});
drop(s);
foo.with_dependent(|_, d| println!("{}", d.get()));
} the problem here is the owner part of Really, the most logical/straightforward might be just to make The alternative would be the ouroboros approach. I don't even know if they did that intentionally, but it seems to prevent the unsoundness successfully: Make a Actually, thinking about the first approach again… it might be enough to make |
@steffahn sorry for taking so long, I've been busy. Marking the result struct as invariant over the owner lifetime if the dependent is not covariant seems like the more prudent approach to me. I would appreciate it if you could take a look at fcc2dc6 I still unsure about &'a mut owners, there might be some nasty surprise left there but I don't see how. And I wonder if |
src/lib.rs
Outdated
// If the dependent is non_covariant, mark the owner as invariant over it's | ||
// lifetime. Otherwise unsound use is possible. | ||
core::marker::PhantomData<core::cell::UnsafeCell<&$OwnerLifetime ()>> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
“it’s” → “its” 😉
Regarding the UnsafeCell
, the best way of getting only the (in-)variance constraints and nothing else like !Sync
is by using fn(T) -> T
. A similar thing also generally applies for generic arguments, e.g. it can make sense to use PhantomData<fn() -> T>
instead of PhantomData<T>
; in this case (i.e. in the covariant
case above) it doesn’t matter because &'lt ()
doesn’t come with any restrictions on auto-traits (Send
/Sync
/UnwindSafe
/Unpin
/etc…) anyways so PhantomData<&'lt ()>
is okay. So
// If the dependent is non_covariant, mark the owner as invariant over it's | |
// lifetime. Otherwise unsound use is possible. | |
core::marker::PhantomData<core::cell::UnsafeCell<&$OwnerLifetime ()>> | |
// If the dependent is non_covariant, mark the owner as invariant over its | |
// lifetime. Otherwise unsound use is possible. | |
core::marker::PhantomData<fn(&$OwnerLifetime ()) -> &$OwnerLifetime ()> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe I'm missing something, I don't understand how that would imply invariance, here https://doc.rust-lang.org/nomicon/subtyping.html it only talks about contravariant and covariant for function pointers. Also wouldn't this have the same problem as #18 ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If fn() -> T
is covariant in T
, but not contravariant; and fn(T) -> ()
is contravariant, but not covariant; then fn(T) -> T
is invariant (neither covariant nor contravariant).
If you preferer it, fn() -> UnsafeCell<T>
would be an option, too, that solves the !Sync
problem of UnsaveCell<T>
as well. I guess, Mutex<T>
would also work in this case; since T == &'lt ()
implements Send
, that doesn't introduce any extra bounds on auto traits either.
AFAICT, you’re using (even before this PR) a lot of |
Well, one can hardly be certain 100% that there’s no unknown unsoundness hiding, but it seems good to me now; I’ve taken a look through the commit and added two comments for review above, the rest looks fine. |
I'm starting to wonder if this whole owner with lifetime feature is worth it, if your dependent depends on the owners lifetime you should just reuse that lifetime eg. https://godbolt.org/z/6P9oshd91. It only seems useful for cases where the owner contains some unrelated lifetime and you want to depend on parts unrelated to that lifetime https://godbolt.org/z/vdKzbzbz9. |
In which case one could restructure the code to separate out the lifetime independent part https://godbolt.org/z/8Y895bj5s So really is there good reason to keep it? @steffahn what do you think? |
I suppose for an #![allow(unused)]
use std::{cell::{self, RefCell}, ops::{self, Not}};
type Dep<'a> = &'a str;
self_cell::self_cell!(
struct SliceRef<'a> {
owner: cell::Ref<'a, String>,
#[covariant]
dependent: Dep,
}
);
impl<'a> ops::Deref for SliceRef<'a> {
type Target = str;
fn deref(&self) -> &Self::Target {
self.borrow_dependent()
}
}
// nontrivial example
fn all_but_first_char(x: &RefCell<String>) -> Option<SliceRef<'_>> {
let x = x.borrow();
x.is_empty()
.not()
.then(|| SliceRef::new(x, |s| &s[s[0..].chars().next().unwrap().len_utf8()..]))
} |
The fix for the issue described in #28 (reborrow_dependent_cyclic.rs) allows
compiling leak_dependent.rs which should not be possible. This is a first
attempt and incomplete as seen by the stubbed out impls for with_dependent and
with_dependent_mut.
DO NOT MERGE
fixes #28