-
Notifications
You must be signed in to change notification settings - Fork 13.1k
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
borrow-checker allows partial reinit of struct that has been moved away, but no use of it. #21232
Comments
(however, unlike rust-lang/rfcs#533, it would not be all that terrible for non-zeroing dynamic-drop if we did not get around to making this illegal; we're already going to have to support representing structure fragments to implement non-zeroing dynamic drop, and initializations like the one in this ticket are just another case of that. Plus we might in the future add the ability to track such partial initializations and allow reads from initialized fields in partially-initialized structs.) |
If this doesn't become an error, it should at least be a warning. Code like this is almost certainly a logic error of some kind, but currently compiles without error or warning: struct Foo { x: i32 }
let mut f = Foo { x: 0 };
drop(f);
f.x = 1; |
triage: P-backcompat-lang, 1.0 beta (but do see comments above noting that its not the end of the world if we did not fix this.) |
triage: P-backcompat-lang (1.0 beta) just trying to accommodate highfive... |
Interesting, I see the bug being that you get an error at all -- that is, I think that once you have fully "reinitialized" the structure, you should be able to read it again. (Rather than the ability to partially reinit the struct as being an error.) |
I'm going to tag this as I-Needs-Decision. |
Note that this is currently an error if the moved value has a destructor. For example, if you add struct Foo { x: i32 }
impl Drop for Foo { fn drop(&mut self) {} }
let mut f = Foo { x: 0 };
drop(f);
f.x = 1; then it is rejected with "error: partial reinitialization of uninitialized structure |
at this point I think I would be in favor saying that we can live with the current semantics as is today, and fix things in the future backwards-compatibly to either 1. track the initialized parts and allow reading from them, as I mentioned at the end of my comment, or 2. the slightly more limited (but perhaps more in line with our current compiler infrastructure) approach of tracking when the entire (non-Drop) structure is reinitialized and then allow reads from it, as outlined in niko's comment. So, bascially, I'm willing to reclassify this as a P-low, not 1.0 bug. |
triage: P-low () |
(apparently empty brackets does not clears the milestone, though they may have at one point?) |
@mbrubeck also, if you wanted to try to develop a lint to warn about this case, I would not object (assuming it did not have too many false positives). |
@mbrubeck yes, we are more restrictive about structs with destructors, because in that case it doesn't make sense to drop one part and not another. |
Real quick question after seeing this on Reddit: When we are "writing into the uninitialized memory that resulted from moving p into the closure", where does that written value go? Does the compiler actually emit write instructions? If so, could that lead to a vulnerability by overwriting an important field? (I would check the LLVM output myself, but I wouldn't have a clue how to read it.) |
It will generate writes to |
@logannc If one omits the assignment to fn main() {
let mut a = A{val: 1};
let a_ = a;
/// a.val = 2; // In the original example this line is uncommented
println!("a_: {}", a_.val);
/// println!("a: {}", a.val); // error: use of moved value: `a.val` ?!?!
} the optimizer should omit the stack slot of fn main() {
let mut a : A; // uninitialized
let a_ = A{val: 1};
a.val = 2; // Initialize a here
println!("a_: {}", a_.val);
/// println!("a: {}", a.val); // error: use of possibly uninitialized variable
} However, I do not understand why using a reinitialized variable results in an error, e.g. by uncommenting the lines in the above examples. It seems that writing to uninitialized memory:
This makes no sense: either one cannot write to uninitialized memory, or doing so reinitializes the variable so that it can be used. Being able to write to uninitialized memory but not being able to use that value is weird. |
Triage: updated code
Same error happens. |
the effort here, i.e. the new check described in my previous comment, needs to happen soon if its going to happen at all. So I'm retagging this as |
Assigning to @spastorino; they are going to drive the initial effort on making it an error, for the short-term, to write to a field of a struct if that whole struct has not be already initialized. Once that is done, we should either leave this issue open and assign it to someone working on the longer term project for supporting partial-assignments of uninitialized records, or close this issue and open a fresh issue to track the longer term project. |
Putting on the RC2 milestone. If we're doing this change at all, its gotta land by then IMO. |
…#54986. Treat attempt to partially intialize local `l` as uses of a `mut` in `let mut l;`, to fix rust-lang#54499.
(We did talk about this at the last meeting; removing the I-nominated tag.) |
This commit makes two changes: First, it updates the dataflow builder to add an init for the place containing a union if there is an assignment into the field of that union. Second, it stops a "use of uninitialized" error occuring when there is an assignment into the field of an uninitialized union that was previously initialized. Making this assignment would re-initialize the union, as tested in `src/test/ui/borrowck/borrowck-union-move-assign.nll.stderr`. The check for previous initialization ensures that we do not start supporting partial initialization yet (cc rust-lang#21232, rust-lang#54499, rust-lang#54986).
NLL Diagnostic Review 3: Unions not reinitialized after assignment into field Fixes #55651, #55652. This PR makes two changes: First, it updates the dataflow builder to add an init for the place containing a union if there is an assignment into the field of that union. Second, it stops a "use of uninitialized" error occuring when there is an assignment into the field of an uninitialized union that was previously initialized. Making this assignment would re-initialize the union, as tested in `src/test/ui/borrowck/borrowck-union-move-assign.nll.stderr`. The check for previous initialization ensures that we do not start supporting partial initialization yet (cc #21232, #54499, #54986). This PR also fixes #55652 which was marked as requiring investigation as the changes in this PR add an error that was previously missing (and mentioned in the review comments) and confirms that the error that was present is correct and a result of earlier partial initialization changes in NLL. r? @pnkfelix (due to earlier work with partial initialization) cc @nikomatsakis
Here is some sample code:
This compiles without an error; running it prints
f: 2u8
.If you uncomment the last line, you get an error saying "error: use of moved value:
p
". Likewise if you just attempt to print individual fields.While I do see the logic being used here ("we are not overwriting the value within the closure itself; we are writing into the uninitialized memory that resulted from moving
p
into the closure"), it seems potentially confusing.Is there any reason that we allowing these writes, which cannot be subsequently read? It seems like a situation analogous to moving into a moved-away array (see rust-lang/rfcs#533 )
The text was updated successfully, but these errors were encountered: