共享可变的错误/UB 做法

多线程中缺乏同步机制

https://users.rust-lang.org/t/why-cant-self-lambda-be-fnmut-when-self-is-borrowed-immutably/113989/4

#![feature(sync_unsafe_cell)]

pub struct Analyzer<F> {
    postprocess: ::core::cell::SyncUnsafeCell<F>,
}

impl<F: FnMut(i32) -> i32> Analyzer<F> {
    fn process(&self, n: i32) -> i32 { n + 1 }
    pub fn pipeline(&self, n: i32) -> i32 {
        let n = self.process(n);
        // 1. let's assume we were allowed to get `&mut postprocess`
        // e.g., here, using (unsound) `unsafe` to make Rust look
        // other way.
        let postprocess: &mut F = unsafe {
            &mut *self.postprocess.get()
        };
        postprocess(n)
    }
}

fn main() {
    let mut v = Vec::new();
    let mut total = 0;
    let postprocess = |n| {
        v.push(1);
        total += n;
        n + 1
    };
    let analyzer = Analyzer {
        postprocess: ::core::cell::SyncUnsafeCell::new(postprocess),
    };
    // 2. then, the following code compiles fine:
    ::std::thread::scope(|s| {
        _ = s.spawn(|| analyzer.pipeline(1));
        _ = s.spawn(|| analyzer.pipeline(2));
    });
    let res = analyzer.pipeline(1);
    println!("{res}");
}

#![allow(unused)]
fn main() {
error: Undefined Behavior: Data race detected between (1) retag write on thread `unnamed-1` and (2) retag write of type `{closure@src/main.rs:24:23: 24:26}` on thread `unnamed-2` at alloc1096. (2) just happened here
  --> src/main.rs:15:13
   |
15 |             &mut *self.postprocess.get()
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Data race detected between (1) retag write on thread `unnamed-1` and (2) retag write of type `{closure@src/main.rs:24:23: 24:26}` on thread `unnamed-2` at alloc1096. (2) just happened here
   |
help: and (1) occurred earlier here
  --> src/main.rs:24:23
   |
24 |       let postprocess = |n| {
   |  _______________________^
25 | |         v.push(1);
26 | |         total += n;
27 | |         n + 1
28 | |     };
   | |_____^
   = help: retags occur on all (re)borrows and as well as when references are copied or moved
   = help: retags permit optimizations that insert speculative reads or writes
   = help: therefore from the perspective of data races, a retag has the same implications as a read or write
   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
   = note: BACKTRACE (of the first span) on thread `unnamed-2`:
   = note: inside `Analyzer::<{closure@src/main.rs:24:23: 24:26}>::pipeline` at src/main.rs:15:13: 15:41
note: inside closure
  --> src/main.rs:35:24
   |
35 |         _ = s.spawn(|| analyzer.pipeline(2));
   |                        ^^^^^^^^^^^^^^^^^^^^
}

单线程中打破别名规则

https://users.rust-lang.org/t/why-cant-self-lambda-be-fnmut-when-self-is-borrowed-immutably/113989/5

use std::cell::UnsafeCell;
use std::rc::Rc;

pub struct Analyzer<F> {
    postprocess: UnsafeCell<F>,
}

impl<F: FnMut(i32) -> i32> Analyzer<F> {
    fn process(&self, n: i32) -> i32 { n + 1 }
    pub fn pipeline(&self, n: i32) -> i32 {
        let n = self.process(n);
        // 1. let's assume we were allowed to get `&mut postprocess`
        // e.g., here, using (unsound) `unsafe` to make Rust look
        // other way.
        let postprocess: &mut F = unsafe {
            &mut *self.postprocess.get()
        };
        postprocess(n)
    }
}

fn main() {
    let analyzer = Rc::<Analyzer<Box<dyn FnMut(i32) -> i32>>>::new_cyclic(|analyzer| {
        let analyzer = analyzer.clone();
        let mut s = String::new();
        let mut first = true;
        let postprocess = Box::new(move |n| {
            s = String::from("foo");
            let s_ref = &*s;
            
            if first {
                first = false;
                // 2. here a re-entrant call is made, leading to a second call to
                // postprocess while the first one is still running and
                // ultimately creating two aliasing mutable references.
                // This is then exploited to access deallocated data
                // when printing s_ref in the first call, as at that point
                // the second call will have replaced s.
                analyzer.upgrade().unwrap().pipeline(0);
            }
            
            println!("{s_ref}");
            n
        });
        
        Analyzer { postprocess: UnsafeCell::new(postprocess) }
    });

    analyzer.pipeline(1);
}

#![allow(unused)]
fn main() {
error: Undefined Behavior: not granting access to tag <3301> because that would remove [Unique for <2766>] which is strongly protected because it is an argument of call 646
  --> src/main.rs:16:13
   |
16 |             &mut *self.postprocess.get()
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not granting access to tag <3301> because that would remove [Unique for <2766>] which is strongly protected because it is an argument of call 646
   |
   = 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: <3301> was created by a SharedReadWrite retag at offsets [0x10..0x20]
  --> src/main.rs:16:19
   |
16 |             &mut *self.postprocess.get()
   |                   ^^^^^^^^^^^^^^^^^^^^^^
help: <2766> is this argument
  --> src/main.rs:18:9
   |
18 |         postprocess(n)
   |         ^^^^^^^^^^^^^^
   = note: BACKTRACE (of the first span):
   = note: inside `Analyzer::<std::boxed::Box<dyn std::ops::FnMut(i32) -> i32>>::pipeline` at src/main.rs:16:13: 16:41
note: inside closure
  --> src/main.rs:39:17
   |
39 |                 analyzer.upgrade().unwrap().pipeline(0);
   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   = note: inside `<std::boxed::Box<dyn std::ops::FnMut(i32) -> i32> as std::ops::FnMut<(i32,)>>::call_mut` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/alloc/src/boxed.rs:2071:9: 2071:49
note: inside `Analyzer::<std::boxed::Box<dyn std::ops::FnMut(i32) -> i32>>::pipeline`
  --> src/main.rs:18:9
   |
18 |         postprocess(n)
   |         ^^^^^^^^^^^^^^
note: inside `main`
  --> src/main.rs:49:5
   |
49 |     analyzer.pipeline(1);
   |     ^^^^^^^^^^^^^^^^^^^^
}