-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Aliasing rules are broken for closures #17403
Comments
Initially discovered in rust-voxel. CC @aepsil0n |
Nominating. |
Aren't the old closures going to be removed as part of implementing unboxed closures? Unless the problem exists for regular types / trait objects, I think it will solve itself. |
Related to rust-lang/rust#17403. Should not have compiled in the first place.
cc me |
I suspect this is related to how we handle lifetimes in closures, which is one of the areas where the code doesn't properly treat closures in the desugared way we'd like to. This is good because I've been looking for breakage that results from this, so I'm glad to have some concrete examples. In particular I suspect the problem is that we currently consider the closure region bound to be its enclosing expression, which is patently wrong (#3696). There is a better algorithm for handling this described in a comment but I haven't gotten around to implementing it. I was waiting to look into this until the dust form @pcwalton's SEME region work settled, which seems to have happened. So now is the time, I guess. :) |
Assigning P-backcompat-lang, 1.0. |
Here is an example of the same issue happening with unboxed closures: #![feature(unboxed_closures, overloaded_calls)]
fn main() {
let mut x = 0u;
let mut f = |&mut:| &mut x;
let _y = f();
let _z = f();
} |
The equivalent desugared program #![feature(overloaded_calls)]
struct F<'a> {
x: &'a mut uint
}
impl<'a> Fn<(), &'a mut uint> for F<'a> {
#[rust_call_abi_hack]
fn call(&self, _: ()) -> &'a mut uint {
self.x
}
}
fn main() {
let mut x = 0u;
let f = F { x: &mut x };
let _y = f();
let _z = f();
} gives an error
This suggests a fix for the issue by appropriately constraining the lifetime parameters of borrows in unboxed closures so that the constraints have no solution in this case. |
My grasp of the region code is tenuous, but I think the interesting bit that causes the desugared version to be rejected is in It should be possible to make it handle the upvar in a similar way. I've been experimenting with a quick hack, and it seems to fix the simple unsound case above and pass unit tests, but it doesn't deal with the fact that the mutability of an upvar can be changed by later borrows or nested closures that have yet to be processed. It also produces really poor diagnostics. Can anyone with deeper knowledge of regionck confirm if I'm on the right track here? |
@bkoropoff I will try to take a deeper look tomorrow (I'm traveling and haven't had a chance to dig into this). However, your analysis seems possibly reasonable. |
This fixes a soundness problem where `Fn` unboxed closures can mutate free variables in the environment. The following presently builds: ```rust #![feature(unboxed_closures, overloaded_calls)] fn main() { let mut x = 0u; let _f = |&:| x = 42; } ``` However, this is equivalent to writing the following, which borrowck rightly rejects: ```rust struct F<'a> { x: &'a mut uint } impl<'a> Fn<(),()> for F<'a> { #[rust_call_abi_hack] fn call(&self, _: ()) { *self.x = 42; // error: cannot assign to data in a `&` reference } } fn main() { let mut x = 0u; let _f = F { x: &mut x }; } ``` This problem is unique to unboxed closures; boxed closures cannot be invoked through an immutable reference and are not subject to it. This change marks upvars of `Fn` unboxed closures as freely aliasable in mem_categorization, which causes borrowck to reject attempts to mutate or mutably borrow them. @zwarich pointed out that even with this change, there are remaining soundness issues related to regionck (issue #17403). This region issue affects boxed closures as well. Closes issue #17780
- Unify the representations of `cat_upvar` and `cat_copied_upvar` - In `link_reborrowed_region`, account for the ability of upvars to change their mutability due to later processing. A map of recursive region links we may want to establish in the future is maintained, with the links being established when the kind of the borrow is adjusted. - When categorizing upvars, add an explicit deref that represents the closure environment pointer for closures that do not take the environment by value. The region for the implicit pointer is an anonymous free region type introduced for this purpose. This creates the necessary constraint to prevent unsound reborrows from the environment. - Add a note to categorizations to make it easier to tell when extra dereferences have been inserted by an upvar without having to perform deep pattern matching. - Adjust borrowck to deal with the changes. Where `cat_upvar` and `cat_copied_upvar` were previously treated differently, they are now both treated roughly like local variables within the closure body, as the explicit derefs now ensure proper behavior. However, error diagnostics had to be changed to explicitly look through the extra dereferences to avoid producing confusing messages about references not present in the source code. Closes issue rust-lang#17403. Remaining work: - The error diagnostics that result from failed region inference are pretty inscrutible and should be improved. Code like the following is now rejected: let mut x = 0u; let f = || &mut x; let y = f(); let z = f(); // multiple mutable references to the same location This also breaks code that uses a similar construction even if it does not go on to violate aliasability semantics. Such code will need to be reworked in some way, such as by using a capture-by-value closure type. [breaking-change]
…sakis This PR is based on #17784, which fixes closure soundness problems in borrowck. Only the last two commits are unique to this PR. My understanding of regionck is still evolving, so I'm not sure if this is the right approach. Feedback is appreciated. - In `link_reborrowed_region`, we account for the ability of upvars to change their mutability due to later processing. A map of recursive region links we may want to establish in the future is maintained, with the links being established when the mutability of the borrow is adjusted. - When asked to establish a region link for an upvar, we link it to the region of the closure body. This creates the necessary constraint to stop unsound reborrows from the closure environment. This partially (maybe completely) solves issue #17403. Remaining work: - This is only known to help with by-ref upvars. I have not looked at by-value upvars yet to see if they can cause problems. - The error diagnostics that result from failed region inference are pretty inscrutible.
- Unify the representations of `cat_upvar` and `cat_copied_upvar` - In `link_reborrowed_region`, account for the ability of upvars to change their mutability due to later processing. A map of recursive region links we may want to establish in the future is maintained, with the links being established when the kind of the borrow is adjusted. - When categorizing upvars, add an explicit deref that represents the closure environment pointer for closures that do not take the environment by value. The region for the implicit pointer is an anonymous free region type introduced for this purpose. This creates the necessary constraint to prevent unsound reborrows from the environment. - Add a note to categorizations to make it easier to tell when extra dereferences have been inserted by an upvar without having to perform deep pattern matching. - Adjust borrowck to deal with the changes. Where `cat_upvar` and `cat_copied_upvar` were previously treated differently, they are now both treated roughly like local variables within the closure body, as the explicit derefs now ensure proper behavior. However, error diagnostics had to be changed to explicitly look through the extra dereferences to avoid producing confusing messages about references not present in the source code. Closes issue rust-lang#17403. Remaining work: - The error diagnostics that result from failed region inference are pretty inscrutible and should be improved. Code like the following is now rejected: let mut x = 0u; let f = || &mut x; let y = f(); let z = f(); // multiple mutable references to the same location This also breaks code that uses a similar construction even if it does not go on to violate aliasability semantics. Such code will need to be reworked in some way, such as by using a capture-by-value closure type. [breaking-change]
…sakis This PR is based on #17784, which fixes closure soundness problems in borrowck. Only the last two commits are unique to this PR. My understanding of regionck is still evolving, so I'm not sure if this is the right approach. Feedback is appreciated. - In `link_reborrowed_region`, we account for the ability of upvars to change their mutability due to later processing. A map of recursive region links we may want to establish in the future is maintained, with the links being established when the mutability of the borrow is adjusted. - When asked to establish a region link for an upvar, we link it to the region of the closure body. This creates the necessary constraint to stop unsound reborrows from the closure environment. This partially (maybe completely) solves issue #17403. Remaining work: - This is only known to help with by-ref upvars. I have not looked at by-value upvars yet to see if they can cause problems. - The error diagnostics that result from failed region inference are pretty inscrutible.
This bug can now be closed |
Restructure Canvas::draw_data(), since change in rust-lang/rust#17403 no longer allows returning a mutable ref directly.
Use proper `ImplTraits` in `insert_inference_vars_for_impl_trait` Fixes rust-lang#17199 and fixes rust-lang#17403 In the previous implementation, I passed `rpits` as a function parameter and used `idx` of `ImplTraitId` for indexing `ImplTrait`. https://github.com/rust-lang/rust-analyzer/blob/4e836c622a7bdab41be8e82733dd9fe40af128b2/crates/hir-ty/src/infer.rs#L881-L887 But that `idx` is rather a "local" one, so in the cases like mentioned issues, the async function that can be expanded roughly as ```rust type TypeAlias = impl Something; fn expanded_async() -> impl Future<Output = TypeAlias> { ... } ``` there are two bundles of `ImplTraits`; one for the `impl Future` and the other one for `TypeAlias`. So using `idx` with `rpits` returns `ImplTrait` for `impl Future` even if we are asking for `TypeAlias` and this caused a stack overflow. This PR is a fix for that implementation miss 😅
This compiles and runs:
x
andy
are not supposed to live at the same time. Results in an UB (in a more complex case)Rust:
The text was updated successfully, but these errors were encountered: