Skip to content

Commit

Permalink
add 共享可变的错误/UB 做法
Browse files Browse the repository at this point in the history
  • Loading branch information
zjp-CN committed Jul 6, 2024
1 parent 15a8343 commit d34611f
Show file tree
Hide file tree
Showing 2 changed files with 176 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
- [静态生命周期提升](./forum/static-promotion.md)
- [`+=` 运算符与 MIR 应用](./forum/addassgin.md)
- [lib.rs 网站新功能一览](./forum/lib.rs.md)
- [共享可变的错误/UB 做法](./forum/shared-mutablity-UB.md)
- [赏析 PR/issue](./learn-from-pr-issue.md)
- [`LazyLock` 未稳定的难点](./learn-from-pr-issue/LazyLock.md)
- [阅读清单](./reading-list/index.md)
Expand Down
175 changes: 175 additions & 0 deletions src/forum/shared-mutablity-UB.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
# 共享可变的错误/UB 做法

## 多线程中缺乏同步机制

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


```rust
#![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}");
}
```

```rust
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>

```rust
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);
}
```

```rust
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);
| ^^^^^^^^^^^^^^^^^^^^
```

0 comments on commit d34611f

Please sign in to comment.