Skip to content

ReferencePropagation introduces UB into code that is accepted by Stacked Borrows #132898

Open
@RalfJung

Description

This program is accepted by Stacked Borrows:

fn main() {
    struct Foo(u64);
    impl Foo {
        fn add(&mut self, n: u64) -> u64 {
            self.0 + n
        }
    }

    let mut f = Foo(0);
    let alias = &mut f.0 as *mut u64;
    let res = f.add(unsafe {
        *alias = 42;
        0
    });
    assert_eq!(res, 42);
}

That is a Stacked Borrows limitation; it is caused by the fact that 2-phase borrows cannot be modeled properly with just a Stack.
However, it also shows that defining an aliasing model that rejects this code is non-trivial, and we should be very careful with optimizations on such code until we have a clear plan for how to model this.

And yet, it turns out that running this code with mir-opt-level=2 introduces UB:

error: Undefined Behavior: trying to retag from <1484> for Unique permission at alloc702[0x0], but that tag does not exist in the borrow stack for this location
  --> 2phase.rs:4:16
   |
4  |         fn add(&mut self, n: u64) -> u64 {
   |                ^^^^^^^^^
   |                |
   |                trying to retag from <1484> for Unique permission at alloc702[0x0], but that tag does not exist in the borrow stack for this location
   |                this error occurs as part of function-entry retag at alloc702[0x0..0x8]
   |
   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
help: <1484> was created by a SharedReadWrite retag at offsets [0x0..0x8]
  --> 2phase.rs:11:15
   |
11 |     let res = f.add(unsafe {
   |               ^
help: <1484> was later invalidated at offsets [0x0..0x8] by a write access
  --> 2phase.rs:14:9
   |
14 |         *alias = 42;
   |         ^^^^^^^^^^^
   = note: BACKTRACE (of the first span):
   = note: inside `main::Foo::add` at 2phase.rs:4:16: 4:25
note: inside `main`
  --> 2phase.rs:11:15
   |
11 |       let res = f.add(unsafe {
   |  _______________^
12 | |         // This is the access at fault, but it's not immediately apparent because
13 | |         // the reference that got invalidated is not under a Protector.
14 | |         *alias = 42;
15 | |         0
16 | |     });
   | |______^

This is quite surprising, I thought we were very conservative in terms of doing optimizations that rely on the aliasing model. I have not yet figured out where exactly this comes from.
Cc @rust-lang/opsem @rust-lang/wg-mir-opt @cjgillot

Metadata

Assignees

No one assigned

    Labels

    A-mir-optArea: MIR optimizationsC-bugCategory: This is a bug.E-needs-investigationCall for partcipation: This issues needs some investigation to determine current statusI-miscompileIssue: Correct Rust code lowers to incorrect machine codeT-compilerRelevant to the compiler team, which will review and decide on the PR/issue.WG-mir-optWorking group: MIR optimizations

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions