-
Notifications
You must be signed in to change notification settings - Fork 58
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
Stacked Borrows: Retag (private) fields of ADTs? #125
Comments
With rust-lang/miri#872, retags no longer descend into fields of structs/enums/... In particular, no barriers/protectors are added for references passed inside other types. This fixes the problem here. But I will leave the issue open because, it is very unclear if that is the right solution -- I implemented it because it helps not reject code that we are not sure is wrong. |
Make Retag shallow A shallow retag does not traverse into fields of compound typed to search for references to retag. It only retags "top-level"/"bare" references (and boxes). This helps with rust-lang/unsafe-code-guidelines#125 because it also means that we do not add protectors for references passed in fields of a struct (or other compound types). Until we know what the rules should be for protectors, I prefer to be less aggressive about what we are rejecting. This also matches our work-in-progress Coq formalization.
I'm not super fluent with RefCell stuff, but near as I can tell, Since this is all safe code, then there should be a panic instead of UB when |
This is all safe code, so if there is UB it's |
Yeah, I know that in some cases RefCell type can panic to avoid UB, I'm not sure if it's possible here. If I understand you correctly, the problem that RefCell has is that the LLVM IR for it is being too strict, but RefCell has no way to examine what the IR is of course. Since something like RefCell can be made by any user of Rust, I think it's better to fix the LLVM IR generation for this sort of situation (if possible) instead of trying to explain this edge case to every user of Rust ever. |
No, this shouldn't panic. This is an intended usecase for
Not sure where you are getting that from, LLVM IR has not been mentioned in this thread. |
Ah, I thought that the "Barriers for private fields" was an LLVM IR element. |
Turns out what we actually do emit @eddyb @rkruppe do you know for which types we do this? Is it only newtypes? If we also did this e.g. for |
At least |
I'm not seeing that here: https://rust.godbolt.org/z/69WSrn . We do emit noalias for AFAICT, the rule is that if an Adt field is noalias and the ABI of the Adt is Pointer, then the Adt is noalias. For some reason we don't emit noalias for |
Only when enabling |
@gnzlbg you need to set |
That flag exists solely because of LLVM bugs, see rust-lang/rust#54878. Rust must still generate correct (UB-free) LLVM IR even when that flag is set. |
Sure, I just tried your examples out and wasn't seeing the For fn foo(rc: &RefCell<i32>, r: Ref<'_, i32>) { ... } produces define void @foo(
{ i64, i32 }* nocapture readnone align 8 dereferenceable(16) %rc,
i32* noalias readonly align 4 dereferenceable(4), i64* align 8 dereferenceable(8) %r)
{ ... } which is incorrect since |
@gnzlbg here's a concrete example turning this into UB: rust-lang/rust#63787. |
Is Could we then rely on the presence of a EDIT: oops, the discussion is over at rust-lang/rust#63787 (comment). |
With references, we generally don't distinguish between a move and a copy/reborrow as they are If the reference is being moved into If instead the reference is being copied/reborrowed into the function, then So: when is the reference moved vs reborrowed/copied? I think that would depend on whether the struct containing it is reborrowed/copied, and that depends on whether the struct is itself Admittedly, this line of thinking leads to some rather bizarre behaviour: generic code could become UB when concrete types are plugged in, or if additional trait bounds are added: fn move<T>(x: T) {
break_it(x);
} -> fn reborrow<T: Copy>(x: T) {
break_it(x); // UB because `x` is not a move anymore!
} So instead, it may be better to be more conservative, and treat more things as moves, even when they could be reborrowes/copies (the cost being that optimisations depending on the stack protectors would not be possible in those cases). |
In lccc, there is a mechanism to indicate that the validity of a pointer does not continue past a particular point (a so-called "destroy" operation). That would make the above code sound (and thus RefCell itself), since it invalidates the shared reference in r before reborrowing the RefCell as mutable. Would it be reasonable for Stacked Borrows to have something like this for these situations. For example, an "end protector" operation could be used to the same effect, and provide a valid way for rust code to pop a protected item. |
I am not sure how that's supposed to work -- are you saying there should be a new primitive that programmers can use to invoke this operation, or is the operation somehow inserted automatically at certain places? |
It would be a primitive operation that could be inserted in particular places, such as the destructors of types like Box in particular, but also extending to |
Some updates:
|
Miri has enabled field retagging for a while now, and it did not trigger an apocalypse. I think we can close this now, as this makes Miri match long-standing precedent of rustc itself. If someone wants to propose to change this, that should be a new issue with accompanying motivation. |
The following code currently gets rejected by Miri:
A similar issue exists with
RefMut
, andvec_deque::Drain
also has this problem.In each of these cases, a protector gets added for a reference that is stored in a private field, and that reference gets invalidated while the protector is still active.
Another way to phrase is: Are types allowed to "lie" about the lifetime of references stored in private fields? Also see rust-lang/rust-memory-model#5.
The text was updated successfully, but these errors were encountered: