Skip to content
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

Fixed mutable vars being marked used when they weren't #43582

Merged
merged 11 commits into from
Aug 10, 2017

Conversation

ivanbakel
Copy link
Contributor

@ivanbakel ivanbakel commented Aug 1, 2017

NB : bootstrapping is slow on my machine, even with keep-stage - fixes for occurances in the current codebase are in the pipeline done. This PR is being put up for review of the fix of the issue.

Fixes #43526, Fixes #30280, Fixes #25049

Issue

Whenever the compiler detected a mutable deref being used mutably, it marked an associated value as being used mutably as well. In the case of derefencing local variables which were mutable references, this incorrectly marked the reference itself being used mutably, instead of its contents - with the consequence of making the following code emit no warnings

fn do_thing<T>(mut arg : &mut T) {
    ... // don't touch arg - just deref it to access the T
}

Fix

Make dereferences not be counted as a mutable use, but only when they're on borrows on local variables.

Why not on things other than local variables?

  • Whenever you capture a variable in a closure, it gets turned into a hidden reference - when you use it in the closure, it gets dereferenced. If the closure uses the variable mutably, that is actually a mutable use of the thing being dereffed to, so it has to be counted.
  • If you deref a mutable Box to access the contents mutably, you are using the Box mutably - so it has to be counted.

@rust-highfive
Copy link
Collaborator

Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @pnkfelix (or someone else) soon.

If any changes to this PR are deemed necessary, please add them as extra commits. This ensures that the reviewer can see what has changed since they last reviewed the code. Due to the way GitHub handles out-of-date commits, this should also make it reasonably obvious what issues have or haven't been addressed. Large or tricky changes may require several passes of review and changes.

Please see the contribution instructions for more information.

@kennytm
Copy link
Member

kennytm commented Aug 1, 2017

You caught bugs in libcore! 😄

Also, you need to add three compile-fail test cases.

[00:17:02] error: variable does not need to be mutable
[00:17:02]    --> /checkout/src/libcore/ops/function.rs:190:41
[00:17:02]     |
[00:17:02] 190 |         extern "rust-call" fn call_once(mut self, args: A) -> F::Output {
[00:17:02]     |                                         ^^^^^^^^
[00:17:02]     |
[00:17:02] note: lint level defined here
[00:17:02]    --> /checkout/src/libcore/lib.rs:68:9
[00:17:02]     |
[00:17:02] 68  | #![deny(warnings)]
[00:17:02]     |         ^^^^^^^^
[00:17:02]     = note: #[deny(unused_mut)] implied by #[deny(warnings)]
[00:17:02] 
[00:17:03] error: variable does not need to be mutable
[00:17:03]    --> /checkout/src/libcore/option.rs:875:18
[00:17:03]     |
[00:17:03] 875 |     fn into_iter(mut self) -> IterMut<'a, T> {
[00:17:03]     |                  ^^^^^^^^
[00:17:03] 
[00:17:03] error: variable does not need to be mutable
[00:17:03]    --> /checkout/src/libcore/result.rs:912:18
[00:17:03]     |
[00:17:03] 912 |     fn into_iter(mut self) -> IterMut<'a, T> {
[00:17:03]     |                  ^^^^^^^^
[00:17:03] 
[00:17:03] error: aborting due to 3 previous errors
[00:17:03] 
[00:17:03] error: Could not compile `core`.

@arielb1
Copy link
Contributor

arielb1 commented Aug 1, 2017

This would still be broken when you have a deref of a field:

fn main() {
    let mut x = 0;
    let mut y = (&mut x,);
    *y.0 = 1;
}

You can detect upvar derefs (aka derefs of captured variables in closures) by looking for a NoteUpvarRef (and NoteClosureEnv) on the Deref - the note created in https://github.com/rust-lang/rust/blob/master/src/librustc/middle/mem_categorization.rs#L759.

That means you'll have to use the cmt instead of the loan path, but you have a cmt available.

@ivanbakel
Copy link
Contributor Author

What three test cases are needed? The three issues are all instances of the same problem, or should I test for use of mutable references inside structs/tuples as well?

@arielb1 I didn't notice that. The mut-checker can track upvars with LoanPathKind, the trouble being that the nesting is slightly difficult to manage - there's a need to recurse into the LoanPath to see if it terminates in an upvar. I'll work on the fix for that now.

@kennytm
Copy link
Member

kennytm commented Aug 1, 2017

@ivanbakel The test case for each issue reported. Perhaps modify src/test/compile-fail/lint-unused-mut-variables.rs to include the new tests.

#![allow(unused_assignments)]
#![allow(unused_variables)]
#![allow(dead_code)]
#![deny(unused_mut)]

fn main() {
   ...
   // issue 43526
   let mut x43526 = &mut 5; //~ ERROR: variable does not need to be mutable
   *x43526 = 4;

   // include 30280 here as well
}

// issue 25049, reduced
fn foo(mut buf: &mut [u8]) { //~ ERROR: variable does not need to be mutable
    &mut buf[..];
}

...

The example of arielb1's comment should be added as well if you fixed it.

@ivanbakel
Copy link
Contributor Author

The code has been updated to catch derefs of borrows inside other types as well, which resolves the issue arielb1 raised - this has raised some new violations in core, so I'm waiting for those to be found and fixed as well.

Is there something special about the order of operations for 25049 that makes it different from the other bugs? I'm not that familiar with slice logic.

@kennytm
Copy link
Member

kennytm commented Aug 1, 2017

@ivanbakel Nothing special. The test cases are there to prove that you do have fixed all 3 issues, and to catch future regression.

@ivanbakel
Copy link
Contributor Author

Alright, I've added tests for the 3 issue cases and the tuple case as well.

Boxes are the exception - since a Box is as mutable as its contents.
The mutability system now checks where derefs go through borrows in the
loan chain, and can correctly detect mutable borrows inside structs and
tuples.
@kennytm
Copy link
Member

kennytm commented Aug 2, 2017

Now libcore and libstd test cases need to be updated 😂

[00:50:15] error: variable does not need to be mutable
[00:50:15]    --> /checkout/src/libcore/../libcore/tests/slice.rs:108:9
[00:50:15]     |
[00:50:15] 108 |     let mut v: &mut [i32] = &mut [0, 1, 2, 3, 4, 5];
[00:50:15]     |         ^^^^^
[00:50:15]     |
[00:50:15] 
[00:50:15] error: variable does not need to be mutable
[00:50:15]    --> /checkout/src/libcore/../libcore/tests/slice.rs:112:9
[00:50:15]     |
[00:50:15] 112 |     let mut v2: &mut [i32] = &mut [0, 1, 2, 3, 4];
[00:50:15]     |         ^^^^^^
[00:50:15] 
[00:50:15] error: variable does not need to be mutable
[00:50:15]    --> /checkout/src/libcore/../libcore/tests/slice.rs:116:9
[00:50:15]     |
[00:50:15] 116 |     let mut v3: &mut [i32] = &mut [];
[00:50:15]     |         ^^^^^^
[00:50:15] 
[00:50:15] error: variable does not need to be mutable
[00:50:15]    --> /checkout/src/libcore/../libcore/tests/slice.rs:123:9
[00:50:15]     |
[00:50:15] 123 |     let mut v: &mut [i32] = &mut [0, 1, 2, 3, 4, 5];
[00:50:15]     |         ^^^^^
[00:50:15] 
[00:50:15] error: variable does not need to be mutable
[00:50:15]    --> /checkout/src/libcore/../libcore/tests/slice.rs:128:9
[00:50:15]     |
[00:50:15] 128 |     let mut v2: &mut [i32] = &mut [0, 1, 2, 3, 4];
[00:50:15]     |         ^^^^^^
[00:50:15] 
[00:50:15] error: variable does not need to be mutable
[00:50:15]    --> /checkout/src/libcore/../libcore/tests/slice.rs:197:9
[00:50:15]     |
[00:50:15] 197 |     let mut v: &mut [i32] = &mut [0, 1, 2, 3, 4, 5];
[00:50:15]     |         ^^^^^
[00:50:15] 
[00:50:20] error: variable does not need to be mutable
[00:50:20]    --> /checkout/src/libstd/error.rs:517:13
[00:50:20]     |
[00:50:20] 517 |         let mut a = &mut a as &mut (Error + 'static);
[00:50:20]     |             ^^^^^
[00:50:20]     |
[00:50:20] 
[00:50:20] error: variable does not need to be mutable
[00:50:20]    --> /checkout/src/libstd/io/cursor.rs:459:13
[00:50:20]     |
[00:50:20] 459 |         let mut reader = &mut &in_buf[..];
[00:50:20]     |             ^^^^^^^^^^
[00:50:20] 

@ivanbakel
Copy link
Contributor Author

I finally understand the meaning of the words "technical debt".

@kennytm
Copy link
Member

kennytm commented Aug 3, 2017

@arielb1 @pnkfelix Travis is green now.

@aidanhs aidanhs added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Aug 3, 2017
None
}
LpUpvar(ty::UpvarId{ var_id: local_id, closure_expr_id: _ }) => {
self.tcx().used_mut_nodes.borrow_mut().insert(local_id);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No through_borrow here?

fn main() {
    let mut z = 0;
    let mut x = &mut z;
    let mut klos = || {
        *x = 1;
    };
    klos();
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's not needed because closures don't borrow mutably (they borrow uniquely) unless they have to. But please add a test.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I'm don't understand your example: This already gives a warning for x being mut, even with the new code. through_borrow is always true reaching the upvar case, since it is always wrapped in a LpExtend(.., .., LpDeref(..)).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. I understand why it works. Just add it as a test to make sure it does work, because it could be an edge case (e.g. when we move to MIR borrowck, we'll like to keep everything working).

@arielb1
Copy link
Contributor

arielb1 commented Aug 6, 2017

r=me with closure test (or is such a test already present?)

@arielb1
Copy link
Contributor

arielb1 commented Aug 6, 2017

Nice PR!

@ivanbakel
Copy link
Contributor Author

Alright, I've added a test for the closure case.

@arielb1
Copy link
Contributor

arielb1 commented Aug 7, 2017

@bors r+

@bors
Copy link
Contributor

bors commented Aug 7, 2017

📌 Commit d1fffd8 has been approved by arielb1

@bors
Copy link
Contributor

bors commented Aug 7, 2017

⌛ Testing commit d1fffd8 with merge e399d7ee6953f3960158ea009f2b93e9a86292b0...

@ivanbakel
Copy link
Contributor Author

😢
Is there a way to force this to build on GNU/Linux? Are there any platform-specific tests to build and run as well?

@arielb1 arielb1 added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Aug 8, 2017
@arielb1
Copy link
Contributor

arielb1 commented Aug 8, 2017

@ivanbakel

I'll reboot to my Windows tonight and push a fix to your repo.

@arielb1 arielb1 assigned arielb1 and unassigned pnkfelix Aug 9, 2017
@arielb1
Copy link
Contributor

arielb1 commented Aug 9, 2017

@bors r+

@bors
Copy link
Contributor

bors commented Aug 9, 2017

📌 Commit 3b0ff0d has been approved by arielb1

@bors
Copy link
Contributor

bors commented Aug 9, 2017

⌛ Testing commit 3b0ff0d with merge 4a8360c3a24d96daf26234ac15cc15b53584d975...

@bors
Copy link
Contributor

bors commented Aug 9, 2017

💔 Test failed - status-travis

@kennytm
Copy link
Member

kennytm commented Aug 9, 2017

Legit. It's cargo this time.

[00:53:34] error: variable does not need to be mutable
[00:53:34]     --> src/tools/cargo/src/cargo/core/resolver/mod.rs:1112:17
[00:53:34]      |
[00:53:34] 1112 |             let mut set = self.resolve_features.entry(pkgid.clone())
[00:53:34]      |                 ^^^^^^^
[00:53:34]      |
[00:53:34] 
[00:53:34] error: variable does not need to be mutable
[00:53:34]    --> src/tools/cargo/src/cargo/util/config.rs:484:13
[00:53:34]     |
[00:53:34] 484 |         let mut cfg = match *cfg {
[00:53:34]     |             ^^^^^^^
[00:53:34] 
[00:53:34] error: variable does not need to be mutable
[00:53:34]   --> src/tools/cargo/src/cargo/util/read2.rs:15:18
[00:53:34]    |
[00:53:34] 15 |                  mut data: &mut FnMut(bool, &mut Vec<u8>, bool)) -> io::Result<()> {
[00:53:34]    |                  ^^^^^^^^
[00:53:34] 
[00:53:34] error: aborting due to 3 previous errors
[00:53:34] 
[00:53:34] error: Could not compile `cargo`.

@ivanbakel You need to

  1. submit a PR to rust-lang/cargo
  2. after the cargo PR is merged, update the cargo submodule in this PR.

Meanwhile you may want to check if RLS and other dependencies (if they are compiled with #[deny(warnings)]) need to be updated as well.

@ivanbakel
Copy link
Contributor Author

I'll get on making the Cargo PR. Is there an easy way to build all submodules to check for these problems?

@kennytm
Copy link
Member

kennytm commented Aug 9, 2017

@ivanbakel I'd just do it manually 😂 Out of all submodules,

 6f1a03dae6bcea44976918186f2d554186b3499c src/doc/book (remotes/origin/ch11-edits-95-g6f1a03da)
 f570bcb681771d691aa4fdb8dfcfad1939844bf5 src/doc/nomicon (remotes/origin/HEAD)
 1abfbaa70313fdf527cf799ffd9b9a096a62105c src/doc/reference (remotes/origin/HEAD)
 11bfb0dcf85f7aa92abd30524bb1e42e18d108c6 src/jemalloc (3.6.0-539-g11bfb0d)
 5e49856003f33aa5781a0edca148be21025e18e7 src/libcompiler_builtins (remotes/origin/HEAD)
 2a5b50b7f7f539a0fd201331d6c1e0534aa332f5 src/liblibc (0.2.29-28-g2a5b50b7f)
 d9e7d2696e41983b6b5a0b4fac604b4e548a84d3 src/llvm (remotes/origin/rust-llvm-release-4-0-1)
 da282f1bb7277b4d30fa1599ee29ad8eb4dd2a92 src/rt/hoedown (2.0.0-226-gda282f1)
 305bc25d5e105e84ffe261655b46cf74570f6e5b src/tools/cargo (0.14.0-1065-g305bc25d)
 5d4bbd9052fe2af849a7d017b85df98ad002c20f src/tools/rls (alpha-388-g5d4bbd9)
 b4ff403041f17957f735ad750c3241a3a428b9b7 src/tools/rust-installer (remotes/origin/HEAD)
  • book, nomicon and reference are doc-tests and they don't test by denying all warnings AFAIK
  • jemalloc, hoedown and llvm are C libraries, so irrelevant
  • libcompiler_builtins and liblibc are used during compilation, and compilation succeeded, so they should be fine

That leaves cargo, rls and rust-installer, where we already know cargo needs to be updated. Since there are only 3 submodules, doing RUSTC=/path/to/your/rustc cargo test three times seems easy enough.

bors added a commit to rust-lang/cargo that referenced this pull request Aug 9, 2017
Fixed some variables being unnecessarily mutable

### Changes

Some variables are marked `mut` when they don't need to be. This PR changes those variables to no longer be `mut`.

### Context

PR on rust-lang/rust#43582

tl:dr; There's a bug with the mutability checker that sometimes marks mutable ref variables as being used when they're not.
@ivanbakel
Copy link
Contributor Author

After some work:

  • cargo changes are merged!
  • rls builds, but some tests fail due to output differences. Not sure this is me.
  • rust_installer fails to build, since error_chain was built with 1.21.0. Not sure how to resolve this - do I need to update the repo and rebuild my local compiler for it to work?

@kennytm
Copy link
Member

kennytm commented Aug 9, 2017

Maybe someone could just r+ and let bors figure it out (after the cargo submodule is updated, that is).

@arielb1
Copy link
Contributor

arielb1 commented Aug 10, 2017

@bors r+

@bors
Copy link
Contributor

bors commented Aug 10, 2017

📌 Commit 8f78d45 has been approved by arielb1

@bors
Copy link
Contributor

bors commented Aug 10, 2017

⌛ Testing commit 8f78d45 with merge d21ec9b...

bors added a commit that referenced this pull request Aug 10, 2017
Fixed mutable vars being marked used when they weren't

#### NB : bootstrapping is slow on my machine, even with `keep-stage` - fixes for occurances in the current codebase are <s>in the pipeline</s> done. This PR is being put up for review of the fix of the issue.

Fixes #43526, Fixes #30280, Fixes #25049

### Issue
Whenever the compiler detected a mutable deref being used mutably, it marked an associated value as being used mutably as well. In the case of derefencing local variables which were mutable references, this incorrectly marked the reference itself being used mutably, instead of its contents - with the consequence of making the following code emit no warnings
```
fn do_thing<T>(mut arg : &mut T) {
    ... // don't touch arg - just deref it to access the T
}
```

### Fix
Make dereferences not be counted as a mutable use, but only when they're on borrows on local variables.
#### Why not on things other than local variables?
  * Whenever you capture a variable in a closure, it gets turned into a hidden reference - when you use it in the closure, it gets dereferenced. If the closure uses the variable mutably, that is actually a mutable use of the thing being dereffed to, so it has to be counted.
  * If you deref a mutable `Box` to access the contents mutably, you are using the `Box` mutably - so it has to be counted.
@bors
Copy link
Contributor

bors commented Aug 10, 2017

☀️ Test successful - status-appveyor, status-travis
Approved by: arielb1
Pushing d21ec9b to master...

@bors bors merged commit 8f78d45 into rust-lang:master Aug 10, 2017
ijanos pushed a commit to exercism/rust that referenced this pull request Sep 1, 2017
`iter_mut()` and `or_insert()` yields mutable references, the variables 
they bind to does not need to be mutable. 

Previously, because of rust-lang/rust#30280
this was not considered a warning.

The fix is in rust-lang/rust#43582
This means on recently nightlies this warning is emitted.
@spamwax
Copy link

spamwax commented Mar 4, 2018

Compiling a bin project under 1.22.1 issues unused_mut warning while the same project compiled with 1.24 stable does not!

In other words, code gets compiled under 1.24 with or without the unused mut

Trying to get a minimal snippet to reproduce this but the offending line is like this:

{
  let (x, mut y) = setup().unwrap_or_else(process::exit(1));
  foo(x, &y);
  bar(x, &y);
}

Was just wondering if this PR is related to above issue?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author.
Projects
None yet
8 participants