Following compiles, and crashes when trying to print moved from value:
fn main() {
let a = Some("...".to_owned());
let b = match a {
Some(_) if { drop(a); false } => None,
x => x,
};
println!("{:?}", b);
}
CC https://github.com/rust-lang/rfcs/pull/107#issuecomment-45311170, #27282.
triage: T-lang I-nominated
I thought we fixed this :(
@alexcrichton unfortunately no. I fixed a very similar issue involving multiple guards, but not this case.
I did try to fix it, but it essentially requires SEME to work. This is because preventing this case introduces edges between successive patterns in the CFG and consequentially causes code like this:
match foo {
(ref a, 0) => (),
(ref a, 1) => ()
_ => ()
}
to be rejected because the borrow in the first pattern overlaps the borrow in the second pattern. The reason why this has to be this way is because both move checking and borrow checking are done using the CFG, right now the CFG for a match "visits" all the arms in parallel, with pattern guards chaining into each other where appropriate. Improving it further requires chaining from the guard to the next pattern instead, but also requires indicating that the borrow in the pattern ends at both the "else" branch of the guard and the end of the block for the arm. In other words, we need regions with multiple exits aka SEME.
Can't we just borrow the discriminant in all guards separately (cc @pnkfelix)? Actually, that might cause conflicts with ref mut
borrows, but that conflict can be ignored (actually, we may want to mark patterns as aliasable in guards).
This issue won't be really fixed with MIR if we are talking about bypassing-exhaustiveness rather than
@arielb1 did you accidentally comment on the wrong issue? I'm not sure how discriminants and exhaustiveness come in here.
triage: P-high
I think this is one of those "fixed by moving region/borrowck to MIR"
@pnkfelix Is this still on your radar? It's been assigned a while.
@rust-lang/lang nominated for retriage. Still looks bad. Can we make progress now?
As an update, still segfaults both on normal and MIR trans (example above)
Moving to compiler team; we believe this is blocked on MIR borrowck.
triage: P-medium
After some discussion in @rust-lang/compiler, the consensus was that we should increase the priority of this issue. The preferred fix is still MIR borrowck. Assigning to @pnkfelix as he has been doing work in that direction.
triage: P-high
Reproducer without matching on the discriminant:
fn main() {
let x = Box::new(0);
match () {
_ if { drop(x); false } => {}
() => println!("{:?}", x)
}
}
Related example from #37891; here the if
is part of the if let
desugaring:
fn main() {
let vec = get_vec();
if let Err(err) = Ok::<_, ()>(1) {
println!("Got an error: {:?}", err);
}
else if vec.unwrap().len() == 0 {
println!("Vec was 0 length.");
}
else {
for i in vec.unwrap() {
println!("{}", i);
}
}
}
fn get_vec() -> Result<Vec<i32>, i32> {
Ok::<Vec<i32>, i32>(vec![1,2,3])
}
triage: P-medium
We decided that having this P-high wasn't really proper, since although we're actively working on a fix, it's via MIR borrowck, and not really a "drop everything" sort of situation.
There's a weird facet to this issue that so far I haven't seen mentioned (unless I've just totally overlooked it). Playing with the code from @insanitybit's Golang and Rustlang Memory Safety, I found that inserting a println!
_prior_ to the offending match expression ends up causing the println!
following the match expression to print the intended data, effectively masking the primary issue.
I (and the several other more experienced Rustaceans I discussed this with at Rust DC) was surprised that a legal read-only use of the variable affected behavior _after_ where it should have been dropped.
This could make quirks arising from the actual issue far more difficult to debug and could possibly hide them from showing up until production if there are debug-only println!
s in the code.
I wasn't sure whether to post this here or to https://github.com/rust-lang/rust/issues/29723
fn main() {
let foo = String::from("FOO");
// The addition of this line prevents the subsequent `println!` from
// printing out garbage data.
println!("1: {:?}", foo);
let foo2 = match 0 {
0 if {
some_func(foo) // foo is freed here
} => unreachable!(),
_ => {
// Use After Free - we return freed memory
foo
}
};
println!("2: {:?}", foo2); // And here we access the invalid memory
}
fn some_func(foo: String) -> bool {
drop(foo);
false
}
prints out
1: "FOO"
2: "FOO"
I find the proof of the corruption even more stunning if you add
let bar = String::from("123456");
in front of the last println!
so that memory get reused, as you now see:
1: "FOO"
2: "123"
cc #27282 -- seems like a duplicate of that
Discussed in the weekly NLL triage.
All of the examples here fail to compile with NLL. This also has a test added from #47549. So we think this can be closed.
A friend encountered this case which is not considered a hard error on the latest stable or nightly, but is a clear UAF (check the Miri interpreter):
fn main() {
let x = String::from("sadsadsadsadsa");
let x = match 0 {
0 if { y(x) } => unreachable!(),
_ => x,
};
println!("Hello, world!");
}
fn y(p: String) -> bool {
false
}
Relevant warning:
warning[E0382]: use of moved value: `x`
--> src/main.rs:6:14
|
2 | let x = String::from("sadsadsadsadsa");
| - move occurs because `x` has type `std::string::String`, which does not implement the `Copy` trait
...
5 | 0 if { y(x) } => unreachable!(),
| - value moved here
6 | _ => x,
| ^ value used here after move
|
= warning: this error has been downgraded to a warning for backwards compatibility with previous releases
= warning: this represents potential undefined behavior in your code and this warning will become a hard error in the future
This only becomes a hard error caught by NLL if you explicitly add #![feature(nll)]
: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=b87d208ad05b96a6f3f2552c2b948ea0
I'm not sure if this was intended to be considered a hard error now with NLL, but if not, I think it's worth revisiting.
I think this was closed before we decided to not close fixed by NLL issues until they're hard errors.
Reopening.
I'm a bit surprised about #![feature(nll)]
having any effect on Rust 2018, is that intentional?
If so, where can I read more about that decision?
I think the downgrade is performed here: https://github.com/rust-lang/rust/blob/2f1bc91803b04caf3e20b3849633bb7ffe6b4074/src/librustc_mir/borrow_check/mod.rs#L394-L411
And references these issues related to the downgrade https://github.com/rust-lang/rust/issues/55492 https://github.com/rust-lang/rust/issues/58776
For the explicit #![feature(nll)]
, it looks like this has to do with if tcx.migrate_borrowck()
, which going down the call graph leads us to: https://github.com/rust-lang/rust/blob/fe5f42cdb88d8ce31f746130099321e7c95e1ef0/src/librustc/ty/context.rs#L1555-L1583
Can't seem to find the relevant discussion for the decision here though.
@landaire Yeah but shouldn't Rust 2018 turn on the NLL borrowck in its full mode?
Unless BorrowckMode::Mir
was never on stable and the Compare
mode was enabled for 2015 recently?
(after having been the mode on the 2018 edition all this time)
Unless
BorrowckMode::Mir
was never on stable
it wasn't.
@eddyb wrote:
I'm a bit surprised about
#![feature(nll)]
having _any_ effect on Rust 2018, is that intentional?
If so, where can I read more about that decision?
and also wrote:
@landaire Yeah but shouldn't Rust 2018 turn on the NLL borrowck in its full mode?
UnlessBorrowckMode::Mir
was never on stable and theCompare
mode was enabled for 2015 recently?
(after having been the mode on the 2018 edition all this time)
See also #58781.
In particular is mention there of making full NLL mode the default for the 2018 edition on its own (vs doing that for all editions en masse).
p.s. the default is BorrowckMode::Migrate
, not Compare
. We should kill off Compare
mode in the short term, as its not serving a real purpose anymore.
Most helpful comment
Discussed in the weekly NLL triage.
All of the examples here fail to compile with NLL. This also has a test added from #47549. So we think this can be closed.