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

Compiler accepts return mut ref to local var on no longer valid stackframe #46557

Closed
CasualX opened this issue Dec 7, 2017 · 24 comments
Closed
Labels
A-borrow-checker Area: The borrow checker A-NLL Area: Non-lexical lifetimes (NLL) C-bug Category: This is a bug. I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness P-medium Medium priority regression-from-stable-to-stable Performance or correctness regression from one stable version to another. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@CasualX
Copy link

CasualX commented Dec 7, 2017

Spotted on reddit: https://www.reddit.com/r/rust/comments/7i64sy/safe_function_returns_static_mut/
credit to jDomantas

I tried this code: playground

fn gimme_static_mut() -> &'static mut u32 {
    let ref mut x = 1234543;
    x
}

fn main() {
    let x = gimme_static_mut();
    let y = gimme_static_mut();
    *y = 42;
    let a = *x;
    println!("a: {}", a);
}

This is accepted by the compiler and prints 42.

I expected to see this happen: The program should be rejected by the compiler with a lifetime error

Instead this happened: The program was accepted and miscompiled badly

The assembly code clearly demonstrates the problem, a local variable is created for the literal, its address is taken and returned:

playground::gimme_static_mut:
.Lfunc_begin2:
	push	rbp
.Lcfi6:
.Lcfi7:
	mov	rbp, rsp
.Lcfi8:
	sub	rsp, 16
	lea	rax, [rbp - 4]
.Ltmp6:
	mov	dword ptr [rbp - 4], 1234543
	mov	qword ptr [rbp - 16], rax
	mov	rax, qword ptr [rbp - 16]
.Ltmp7:
	add	rsp, 16
	pop	rbp
	ret
.Ltmp8:
.Lfunc_end2:

Meta

Latest version on playground

@collares
Copy link

collares commented Dec 7, 2017

This program is rejected by rustc 1.20.0, but is accepted by rustc 1.21.0. Bisecting now.

@skade
Copy link
Contributor

skade commented Dec 7, 2017

@mauricioc This is due to #38865. It wasn't a feature in 1.20. 1.20 also doesn't compile the immutable (safe) variant:

fn gimme_static() -> &'static u32 {
    let ref x = 1234543;
    x
}

@CasualX
Copy link
Author

CasualX commented Dec 7, 2017

An alternative way to define gimme_static_mut:

fn gimme_static_mut() -> &'static mut u32 {
    let x = &mut 1234543;
    x
}

Gives the expected compiler error:

   Compiling playground v0.0.1 (file:///playground)
error[E0597]: borrowed value does not live long enough
 --> src/main.rs:2:18
  |
2 |     let x = &mut 1234543;
  |                  ^^^^^^^ does not live long enough
3 |     x
4 | }
  | - temporary value only lives until here
  |
  = note: borrowed value must be valid for the static lifetime...

error: aborting due to previous error

error: Could not compile `playground`.

To learn more, run the command again with --verbose.

Looks like an oversight with the ref mut pattern match syntax to capture the literal by reference.

@nikomatsakis nikomatsakis added A-borrow-checker Area: The borrow checker I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Dec 7, 2017
@nikomatsakis
Copy link
Contributor

Fixed by MIR borrowck.

@shepmaster
Copy link
Member

Fixed by MIR borrowck.

Which isn't enabled in Rust 1.23 beta, so we still need to fix this in some other fashion for the next release, correct?

@bstrie
Copy link
Contributor

bstrie commented Dec 7, 2017

I see that Niko has commented though I don't see a priority assigned yet, so nominating for completeness.

@pnkfelix pnkfelix added A-NLL Area: Non-lexical lifetimes (NLL) WG-compiler-nll labels Dec 21, 2017
@shepmaster shepmaster added the regression-from-stable-to-stable Performance or correctness regression from one stable version to another. label Jan 5, 2018
@shepmaster
Copy link
Member

shepmaster commented Jan 5, 2018

This issue continues to ship in stable Rust 1.23

@nikomatsakis
Copy link
Contributor

My expectation is that this will be fixed (along with a number of other bugs) when we transition to the MIR-based borrow checker (and NLL). I guess it's worth discussing whether we feel we ought to try to fix faster than that.

@bstrie
Copy link
Contributor

bstrie commented Jan 7, 2018

Well before we can discuss whether we ought to get a fix in "faster", we'd need to have an official estimate of when MIR borrowck/NLL is expected to land in stable. :) I've gotten the impression that people have been deliberately noncommittal towards a timeframe up til now, but is it safe to assume that the answer is "hopefully stable sometime in 2018"?

@nikomatsakis
Copy link
Contributor

@bstrie definitely -- first half of 2018 is my general plan, hopefully towards the beginning of that.

@nikomatsakis
Copy link
Contributor

triage: P-medium

Plan is to fix this as part of the transition to NLL. I'll be adding a test case (marked with #![feature(nll)] soon.)

@rust-highfive rust-highfive added P-medium Medium priority and removed I-nominated labels Jan 11, 2018
chrisvittal added a commit to chrisvittal/rust that referenced this issue Jan 11, 2018
Closes rust-lang#47366

Adapt the sample code from the issues into mir-borrowck/nll test cases.
kennytm added a commit to kennytm/rust that referenced this issue Jan 15, 2018
Add NLL tests for rust-lang#46557 and rust-lang#38899

This adapts the sample code from the two issues into test code.

Closes rust-lang#46557
Closes rust-lang#38899

r? @nikomatsakis
@SimonSapin
Copy link
Contributor

Should this be close while #![feature(nll)] is not yet the default?

@aidanhs
Copy link
Member

aidanhs commented Jan 15, 2018

I think it's reasonable to say this is a P-medium issue until the fix is in master and enabled by default (I'm open to disagreement though!).

@nikomatsakis
Copy link
Contributor

Well, it was intentional to close the issues now that there is a test, so as to keep the issue tracked (and in particular the WG-compielr-nll) more oriented at "actionable" issues. In other words, this is basically -- at this point -- a dup of the NLL tracking issue. I'm still inclined to close, I don't know what value there is from keeping it open. But I too am willing to give away if others feel really strongly.

We could perhaps compromise by removing WG-compiler-nll or something, so that I don't have to keep looking at the same issues over and over. But it's kind of annoying.

@nikomatsakis
Copy link
Contributor

More specifically, the idea was to mark this as a "duplicate" of #47366, which admittedly wasn't quite clear.

@nikomatsakis
Copy link
Contributor

@aidanhs and I discussed. Since there is a test and this is fixed in MIR borrow check, closing as duplicate of #47366.

@nikomatsakis
Copy link
Contributor

Er, perhaps that was premature.

@nikomatsakis nikomatsakis reopened this Jan 16, 2018
@nikomatsakis
Copy link
Contributor

Decided to just remove WG-compiler-nll for such issues, so we can keep them open but they can stop annoying me.

@nikomatsakis
Copy link
Contributor

I took a peek at this. The problem seems to be that we decide that the expression is promotable — which is true, but we ought not to be promoting a ref mut (let ref x = ... -- which also type checks -- is just fine).

@nikomatsakis
Copy link
Contributor

Ah, I suspect I know the problem. Note that this variant is properly rejected:

fn gimme_static_mut() -> &'static mut u32 {
    match 123443 {
       ref mut x => x,
    }
}

fn main() {
    let x = gimme_static_mut();
    let y = gimme_static_mut();
    *y = 42;
    let a = *x;
    println!("a: {}", a);
}

I think the error is that we specifically mark things that are ref mut matched as non-promotable for matches:

if let hir::ExprMatch(ref discr, ref arms, _) = ex.node {
// Compute the most demanding borrow from all the arms'
// patterns and set that on the discriminator.
let mut mut_borrow = false;
for pat in arms.iter().flat_map(|arm| &arm.pats) {
if self.mut_rvalue_borrows.remove(&pat.id) {
mut_borrow = true;
}
}
if mut_borrow {
self.mut_rvalue_borrows.insert(discr.id);
}
}

but we don't do anything similar for let initializers.

cc @eddyb — sound plausible?

@nikomatsakis
Copy link
Contributor

I imagine then that we have to add some similar code here:

hir::DeclLocal(_) => {
self.promotable = false;
}

basically, checking if the variable being assigned has a ref mut binding somewhere, and then — if so — adding the "initializer.id" into mut_rvalue_borrows.

@nikomatsakis
Copy link
Contributor

Here is a bonus bug. This compiles:

fn gimme_static_mut() -> &'static mut u32 {
    match (123443,) {
       (ref mut x,) => x,
    }
}

@nikomatsakis
Copy link
Contributor

Fix in #51274

bors added a commit that referenced this issue Jun 2, 2018
also check `let` arms and nested patterns for mutable borrows

Fixes #46557

r? @eddyb
@pnkfelix
Copy link
Member

since this was fixed for AST borrow-check, it no longer should have the NLL-fixed-by-NLL label.

@pnkfelix pnkfelix removed the NLL-fixed-by-NLL Bugs fixed, but only when NLL is enabled. label Jun 27, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-borrow-checker Area: The borrow checker A-NLL Area: Non-lexical lifetimes (NLL) C-bug Category: This is a bug. I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness P-medium Medium priority regression-from-stable-to-stable Performance or correctness regression from one stable version to another. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests