-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
Permit mutable references in all const contexts #78578
Conversation
Some changes occurred in diagnostic error codes |
This comment has been minimized.
This comment has been minimized.
But what about interior mutability? Also, what is even the problem against which this is defending? Assuming the lifetimes work out, I see no reason that an
So, we are giving up on doing static checks here, and rely on (post-monomorphitation) "dynamic" checks? |
We already permit that on stable and it's only possible through wrapper types with
Since we allow let x: &'static mut i32 = FOO;
let y: &'static mut i32 = FOO; and then they have two mutable references to the same memory. BUT since we actually generate the above as let x: &'static mut i32 = *&FOO;
let y: &'static mut i32 = *&FOO; The user will get an error because you can't access a mutable reference behind an immutable referenc as mutable.
Not in the current way that MIR implements static accesses, and not in the way I think we want to ever use statics, but it still something we need to protect against.
Why into mutable memory? You cannot safely access it mutably, and any access will go through an immutable reference, so unsafely accessing it mutably would be UB, right?
hmm... indeed. I'll read through @eddyb 's suggestion in zulip again. |
What I meant is,
Ah, right. So even if we move away again from "statics as const pointers", we have to make sure they are still treated as an immutable place by the borrow checker.
I said "we need to make sure that we put it into mutable global memory whenever it can actually be mutated" (emphasis is new). Like for example in case of |
Well, not use core::cell::UnsafeCell;
struct NotAMutex<T>(UnsafeCell<T>);
unsafe impl<T> Sync for NotAMutex<T> {}
const FOO: NotAMutex<&i32> = NotAMutex(UnsafeCell::new(&42)); The above only works, because const FOO: NotAMutex<&i32> = NotAMutex(UnsafeCell::new(&{
let x = 42;
x
})); I'll write appropriate tests and post my findings here. |
I added a new test for your example, and as I expected, it is not possible to do that, so we do not need to check whether interning can handle this. |
What about using |
For constants that is not possible anyway (yes, this time it is really a static check ;) ). For static items, it's not a problem, as the allocation behind the reference is not part of the static currently being interned. For all other concerns, accessing a mutable static requires |
Hm, I am running out of ideas.^^ Would unleashing Miri let us disable some of these restrictions? I feel like there probably are or will be other ways to get In particular you invoked the const-may-not-point-to-static restriction, which only exists for the benefit of const-in-pattern and which rejects way more code that it would have to. If, hypothetically, we'd instead dynamically check this only when a const is actually used in a pattern, your scheme here does not work any more. I have a plan for how to make that restriction not load-beaing any more (when constructing a valtree, ensure we only read from immutable allocations; make patterns go through valtrees). I think we should be careful not to add anything else that would make this restriction load-bearing in different ways. EDIT: Or, did you invoke that restriction? I am losing track of what all has to work together to make this sound, which is a bad sign. :/ But I think yes you did, "For constants that is not possible anyway" -- "that" being
As discussed in this PR before, I don't think this check should be relied upon for soundness. So I propose that for
For |
Btw, there is a funny corner case here where the same allocation is referenced multiple times, so it might end up immutable from the first reference we encounter to it and then later another reference wants it to be mutable... something like #![feature(const_mut_refs)]
#![feature(const_raw_ptr_deref)]
static mut FOO: i32 = 0;
struct SyncRawPtr<T>(*mut T);
unsafe impl<T> Sync for SyncRawPtr<T> {}
static EVIL: (&i32, SyncRawPtr<i32>) = {
let x: *mut i32 = unsafe { &mut FOO };
(unsafe { &*x }, SyncRawPtr(x))
}; or #![feature(const_mut_refs)]
#![feature(const_raw_ptr_deref)]
static mut FOO: i32 = 0;
struct SyncRawPtr<T>(*mut T);
unsafe impl<T> Sync for SyncRawPtr<T> {}
const fn make_evil() -> (&'static i32, SyncRawPtr<i32>) {
let x: *mut i32 = unsafe { &mut FOO };
(unsafe { &*x }, SyncRawPtr(x))
}
static EVIL: (&i32, SyncRawPtr<i32>) = {
make_evil()
}; The latter seems to hinge only on "constant functions cannot refer to statics" (and one could imagine One could argue that if |
Sorry for all the rambling, but I feel very uneasy about this change.^^ I don't think the required soundness conditions and the checks that uphold them have been documented clearly enough. We shouldn't start with examples, we should start with "here is a list of (dynamic) conditions that, if they are all met, we agree are sufficient to make this all sound", and then we should go on with "here are some (static and/or dynamic) checks that ensure that all conditions are met", and then we can come up with testcases to try to beak them. |
I think part of this is because, as you correctly noted, we are mixing a lot of different concerns. E.g. I consider it ok for the dynamic checks to permit
I thought we had to consider all references to be used, no matter if they actually are. I don't see how having a shared reference to some memory that is mutated through other means is fine, but a reference to dangling memory is not. The only way that example seems doable to me is to use |
Well, the following is allowed under Stacked Borrows in runtime code: let x: *mut i32 = unsafe { &mut FOO };
let (_, x) = (unsafe { &*x }, SyncRawPtr(x));
unsafe { *x = 1; } Basically, the lifetime of the shared reference ends immediately, that's why this is okay. (It would also be permitted to dangle.) The guarantee of a shared ref is that between any two of its uses, nothing has been mutated, but if the shared ref is unused... well, this guarantee doesn't do anything. (The only special case is references passed as function parameters; that is the "protector" stuff in Stacked Borrows.) I think to obtain the behavior you seem to expect, we'd need to somehow reflect the idea that "the 'static lifetime goes on forever" into Stacked Borrows, making sure the tag of the shared reference never gets popped... but we don't actually have any tags in compile-time computed pointers so this cannot really work.^^ Indeed having provenance somehow connect compile-time and run-time computations seems tricky (and strange), but if we make compile-time-computed pointers have no provenance, that automatically means there is no way to distinguish different compile-time computed pointers to the same data. |
So... "must be unused" is actually not true here? We could alternately use the reference to read and the |
Sorry, all of this is getting a bit off topic, but I really want to understand the details even if irrelevant to this PR. Should we take it to some other place? I fully agree that we should not intern an allocation as immutable if some later place wants to intern it as mutable. Though, in the case you showed, we won't intern anything anyway, as that is a pointer to a mutable static, which by itself is already a mutable allocation. But for hyptothetical heap allocations, this could definitely occur. |
Right, sorry, I was somewhat distracted this morning and did not have enough time to reply properly. I still don't really have enough time... So in terms of soundness, if all "inner" static allocations are interned mutable and hidden pointers are not allowed in So my main soundness concerns are around
|
After thinking about this some more, I wonder if there isn't another approach -- given my inability to come up with an example that has mutable memory in an inner static allocation. We could say that all inner allocations of @oli-obk almost convinced me that this could be sound for current CTFE, though I'd need to think about this some more. It would clearly not be sound with heap allocations during CTFE, but those are easily recognized and we could just always intern them mutably. |
I can unconvince you in a single line that works on stable: static mut FOO: &mut [u32] = &mut [5, 3, 4]; your mention of |
Isn't that just because #75585 did not arrive on stable yet? |
This comment has been minimized.
This comment has been minimized.
abc1811
to
c82456a
Compare
☔ The latest upstream changes (presumably #80942) made this pull request unmergeable. Please resolve the merge conflicts. |
c82456a
to
15af4b5
Compare
… mutability check
…with the existing exceptions
15af4b5
to
cd09871
Compare
const fn helper() -> Option<&'static mut i32> { unsafe { | ||
// Undefined behaviour, who doesn't love tests like this. | ||
// This code never gets executed, because the static checks fail before that. | ||
Some(&mut *(42 as *mut i32)) | ||
} } | ||
// Check that we do not look into function bodies. | ||
// We treat all functions as not returning a mutable reference, because there is no way to | ||
// do that without causing the borrow checker to complain (see the B5/helper2 test below). | ||
const B4: Option<&mut i32> = helper(); | ||
|
||
const fn helper2(x: &mut i32) -> Option<&mut i32> { Some(x) } | ||
const B5: Option<&mut i32> = helper2(&mut 42); //~ ERROR temporary value dropped while borrowed |
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.
I'm not too happy with B4
... but also not sure what else to do here. We could start running checks on the return type of functions, but I'm not sure how to do that properly, especially once generics are involved.
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.
because the static checks fail before that
Why is there no //~ ERROR
here then?
What if you do Some(&mut *(&mut 42 as *mut i32))
instead?
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.
the static checks on other constants fail. This code will work and then error in validation if we were getting this far, but since everything else erros first, and B4 is not used, it isn't evaluated.
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.
What if you do Some(&mut *(&mut 42 as *mut i32)) instead?
that would just give us a dangling pointer, but still not get to validation. I could put this test in a separate file, then we would get to validation.
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.
Oh, the static checks in other consts fail.
Looks like this test needs its own file then.
that would just give us a dangling pointer, but still not get to validation. I could put this test in a separate file, then we would get to validation.
Sure, I meant to test that in addition to B4, not instead of.
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.
done
@bors r+ |
📌 Commit 819b008 has been approved by |
…as-schievink Rollup of 14 pull requests Successful merges: - rust-lang#75180 (Implement Error for &(impl Error)) - rust-lang#78578 (Permit mutable references in all const contexts) - rust-lang#79174 (Make std::future a re-export of core::future) - rust-lang#79884 (Replace magic numbers with existing constants) - rust-lang#80855 (Expand assert!(expr, args..) to include $crate for hygiene on 2021.) - rust-lang#80933 (Fix sysroot option not being honored across rustc) - rust-lang#81259 (Replace version_check dependency with own version parsing code) - rust-lang#81264 (Add unstable option to control doctest run directory) - rust-lang#81279 (Small refactor in typeck) - rust-lang#81297 (Don't provide backend_optimization_level query for extern crates) - rust-lang#81302 (Fix rendering of stabilization version for trait implementors) - rust-lang#81310 (Do not mark unit variants as used when in path pattern) - rust-lang#81320 (Make bad shlex parsing a pretty error) - rust-lang#81338 (Clean up `dominators_given_rpo`) Failed merges: r? `@ghost` `@rustbot` modify labels: rollup
fixes #71212
cc @rust-lang/wg-const-eval @christianpoveda