-
-
Notifications
You must be signed in to change notification settings - Fork 14.8k
UB after Rc::increment_strong_count with &mut T from prior Rc::get_mut or get_mut_unchecked. #154859
Description
I tried this code under Miri:
use std::rc::Rc;
fn main() {
let rc = Rc::new(1);
let ptr = Rc::into_raw(rc);
let mut rc = unsafe { Rc::from_raw(ptr) };
let val = Rc::get_mut(&mut rc).unwrap(); // or get_mut_unchecked
*val = 2;
unsafe { Rc::increment_strong_count(ptr) };
*val = 3; // We still have a `&mut i32`, even though the strong count is 2 at this point
unsafe { Rc::decrement_strong_count(ptr) };
*val = 4;
dbg!(*rc);
}I expected to see this happen: No UB is reported, and it prints 4
Instead, this happened: Miri reports UB under both Stacked Borrows and Tree Borrows at the *val = 3; line
The program is a bit weird, but by my reading, it upholds the safety contract of Rc::increment_strong_count.
The pointer must have been obtained through Rc::into_raw and must satisfy the same layout requirements specified in Rc::from_raw_in. The associated Rc instance must be valid (i.e. the strong count must be at least 1) for the duration of this method, and ptr must point to a block of memory allocated by the global allocator.
There is no mention of there not being allowed to be existing &mut Ts as given out by Rc::get_mut or Rc::get_mut_unchecked, only that the strong count must be at least 1. And, conceptually, I don't think that things which only need to access the refcounts of an Rc<T> should cause aliasing issues for the T.
Tree Borrows error
error: Undefined Behavior: write access through <452> at alloc198[0x10] is forbidden
--> src/main.rs:10:5
|
10 | *val = 3; // We still have a `&mut i32`, even though the strong count is 2 at this point
| ^^^^^^^^ Undefined Behavior occurred here
|
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
= help: the accessed tag <452> has state Frozen which forbids this child write access
help: the accessed tag <452> was created here, in the initial state Reserved
--> src/main.rs:7:15
|
7 | let val = Rc::get_mut(&mut rc).unwrap();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: the accessed tag <452> later transitioned to Unique due to a child write access at offsets [0x10..0x14]
--> src/main.rs:8:5
|
8 | *val = 2;
| ^^^^^^^^
= help: this transition corresponds to the first write to a 2-phase borrowed mutable reference
help: the accessed tag <452> later transitioned to Frozen due to a reborrow (acting as a foreign read access) at offsets [0x10..0x18]
--> src/main.rs:9:14
|
9 | unsafe { Rc::increment_strong_count(ptr) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= help: this transition corresponds to a loss of write permissions
= note: stack backtrace:
0: main
at src/main.rs:10:5: 10:13
1: <fn() as std::ops::FnOnce<()>>::call_once - shim(fn())
at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:250:5: 250:71
2: std::sys::backtrace::__rust_begin_short_backtrace::<fn(), ()>
at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/sys/backtrace.rs:166:18: 166:21
3: std::rt::lang_start::<()>::{closure#0}
at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:206:18: 206:75
4: std::ops::function::impls::<impl std::ops::FnOnce<()> for &dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe>::call_once
at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:287:13: 287:31
5: std::panicking::catch_unwind::do_call::<&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe, i32>
at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:581:40: 581:43
6: std::panicking::catch_unwind::<i32, &dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe>
at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:544:19: 544:88
7: std::panic::catch_unwind::<&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe, i32>
at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panic.rs:359:14: 359:40
8: std::rt::lang_start_internal::{closure#0}
at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:175:24: 175:49
9: std::panicking::catch_unwind::do_call::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>
at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:581:40: 581:43
10: std::panicking::catch_unwind::<isize, {closure@std::rt::lang_start_internal::{closure#0}}>
at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:544:19: 544:88
11: std::panic::catch_unwind::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>
at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panic.rs:359:14: 359:40
12: std::rt::lang_start_internal
at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:171:5: 193:7
13: std::rt::lang_start::<()>
at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:205:5: 210:6
error: aborting due to 1 previous errorStacked Borrows error
error: Undefined Behavior: attempting a write access using <470> at alloc198[0x10], but that tag does not exist in the borrow stack for this location
--> src/main.rs:10:5
|
10 | *val = 3; // We still have a `&mut i32`, even though the strong count is 2 at this point
| ^^^^^^^^ this error occurs as part of an access at alloc198[0x10..0x14]
|
= 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: <470> was created by a Unique retag at offsets [0x10..0x14]
--> src/main.rs:7:15
|
7 | let val = Rc::get_mut(&mut rc).unwrap();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: <470> was later invalidated at offsets [0x0..0x18] by a SharedReadOnly retag
--> src/main.rs:9:14
|
9 | unsafe { Rc::increment_strong_count(ptr) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: stack backtrace:
0: main
at src/main.rs:10:5: 10:13
1: <fn() as std::ops::FnOnce<()>>::call_once - shim(fn())
at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:250:5: 250:71
2: std::sys::backtrace::__rust_begin_short_backtrace::<fn(), ()>
at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/sys/backtrace.rs:166:18: 166:21
3: std::rt::lang_start::<()>::{closure#0}
at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:206:18: 206:75
4: std::ops::function::impls::<impl std::ops::FnOnce<()> for &dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe>::call_once
at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:287:13: 287:31
5: std::panicking::catch_unwind::do_call::<&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe, i32>
at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:581:40: 581:43
6: std::panicking::catch_unwind::<i32, &dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe>
at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:544:19: 544:88
7: std::panic::catch_unwind::<&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe, i32>
at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panic.rs:359:14: 359:40
8: std::rt::lang_start_internal::{closure#0}
at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:175:24: 175:49
9: std::panicking::catch_unwind::do_call::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>
at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:581:40: 581:43
10: std::panicking::catch_unwind::<isize, {closure@std::rt::lang_start_internal::{closure#0}}>
at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:544:19: 544:88
11: std::panic::catch_unwind::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>
at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panic.rs:359:14: 359:40
12: std::rt::lang_start_internal
at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:171:5: 193:7
13: std::rt::lang_start::<()>
at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:205:5: 210:6
error: aborting due to 1 previous errorRelated issue: #136322 . If this is determined that this shouldn't be UB (I think it shouldn't be UB), then I think it would have the same fix mentioned in #136322 (comment) .
The Miri error (under both Stacked and Tree Borrows) would be fixed by #132553 , as it does not make a reference covering T to access the refcounts (tested under Miri after a rebase).
Meta
rustc --version --verbose:
rustc 1.96.0-nightly (2972b5e59 2026-04-03)
binary: rustc
commit-hash: 2972b5e59f1c5529b6ba770437812fd83ab4ebd4
commit-date: 2026-04-03
host: x86_64-unknown-linux-gnu
release: 1.96.0-nightly
LLVM version: 22.1.2
@rustbot label T-libs