Rust: Inconsistent evaluation order for assignment operations

Created on 17 Aug 2015  路  10Comments  路  Source: rust-lang/rust

UPDATE: This is fixed by the MIR-based borrow checker and just needs a test. See the mentoring instructions below.


Currently, the borrow checker thinks that the RHS of += is evaluated before the LHS. trans thinks that the LHS is evaluated before the RHS. The disagreement leads to bad results.

fn main() {
    let x = Box::new(0);
    let mut y = 0;
    *{ drop(x); &mut y } += *x;
    assert_eq!(y, 0);
}
A-NLL A-borrow-checker C-bug E-mentor I-unsound 馃挜 NLL-fixed-by-NLL P-medium T-lang

Most helpful comment

Assignment operators are still wrong, which can lead to use-after-free:

use std::ops::AddAssign;

struct MyVec<T>(Vec<T>);

impl <T> Drop for MyVec<T> {
    fn drop(&mut self) {
        println!("Being dropped.");
    }
}

impl<T> AddAssign<T> for MyVec<T> {
    fn add_assign(&mut self, _elem: T) {
        println!("In add_assign.");
    }
}

fn main() {
    let mut vec = MyVec(vec![0]);
    let mut vecvec = vec![vec];

    vecvec[0] += {
        vecvec = vec![];
        0
    };
}

Prints

Being dropped.
In add_assign.

Credit to @xfix for bringing this up in https://github.com/rust-lang/rfcs/pull/2025#issuecomment-308808143, I just weaponized it.

All 10 comments

triage: P-medium

Also fixed by MIR (almost a dup of #28160, but not quite)/

So the internals thread says this: "before MIR, we were actually quite inconsistent here, in that borrowck and trans disagreed on some of the particulars. MIR eliminated that inconsistency, but there are still some matters that we should try to resolve -- and quick!" If that's the case, then this should be closed. @nikomatsakis Is this the case?

Well, we're not yet running the borrowck on MIR, so I'm not sure.

But certainly the example seems to work ok now.

Assignment operators are still wrong, which can lead to use-after-free:

use std::ops::AddAssign;

struct MyVec<T>(Vec<T>);

impl <T> Drop for MyVec<T> {
    fn drop(&mut self) {
        println!("Being dropped.");
    }
}

impl<T> AddAssign<T> for MyVec<T> {
    fn add_assign(&mut self, _elem: T) {
        println!("In add_assign.");
    }
}

fn main() {
    let mut vec = MyVec(vec![0]);
    let mut vecvec = vec![vec];

    vecvec[0] += {
        vecvec = vec![];
        0
    };
}

Prints

Being dropped.
In add_assign.

Credit to @xfix for bringing this up in https://github.com/rust-lang/rfcs/pull/2025#issuecomment-308808143, I just weaponized it.

@RalfJung's example gets an error with MIR borrowck enabled (as expected):

https://play.rust-lang.org/?gist=761478ea89b3cd1df50ee274cb7a909d&version=nightly

I am filing this under "bugs fixed by MIR borrowck" https://github.com/rust-lang/rust/issues/47366

Marking as E-needstest and E-mentor:

Since this code works now, what we need to do is add it to our test-suite. To do that, you would create a file in src/test/ui/nll named issue-27868.rs containing the example I linked to here. There are general instructions for adding new tests here.

Apparently PR #53326 added the desired test here. So this is NLL-fixed-by-NLL now, I think.

NLL (migrate mode) is enabled in all editions as of PR #59114.

The only policy question is that, under migrate mode, we only emit a warning on this (unsound) test case. Therefore, I am not 100% sure whether we should close this until that warning has been turned into a hard-error under our (still in development) future-compatibility lint policy.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

cramertj picture cramertj  路  512Comments

thestinger picture thestinger  路  234Comments

Leo1003 picture Leo1003  路  898Comments

withoutboats picture withoutboats  路  211Comments

Mark-Simulacrum picture Mark-Simulacrum  路  681Comments