Description
I tried this code:
https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=a2e4bd9f59882c88c4f232379cbb0001
I expected to see this happen:
shrink_to_fit
causes an alloc error, the thread unwinds, Foo(0)
and Foo(1)
are dropped in some order.
Instead, this happened:
thread 'main' panicked at src/main.rs:37:30:
alloc error
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Dropping Foo(1)
Dropping Foo(3472329422384804195)
(the exact value printed differs between runs, which makes sense if it's reading uninit memory)
This occurs because of the way VecDeque::shrink_to
is implemented: It first moves the memory around to fit the new capacity, then calls RawVec::shrink_to_fit(new_capacity)
with no regards to potential unwinding. Specifically, this would look something like this:
- After initialization, the deque buffer looks like this:
[Foo(0), <uninit> * 13, Foo(1)]
^ ^
last head
shrink_to
copies the head to just behind the tail:
[Foo(0), Foo(1), <uninit> * 13]
^ ^
last head
shrink_to
callsRawVec::shrink_to_fit
callsAllocator::shrink
which returns an alloc error, the error handler unwinds, leaving the buffer looking like this:
[Foo(0), Foo(1), <uninit>, <uninit> * 12]
^ ^
head last
- The
Drop
impl ofVecDeque
drops its elements, reading the uninit memory.
Meta
While this specific setup requires nightly to manually set the alloc error handle hook and to trigger the alloc error in shrink
, this same bug could occur on stable if shrink
OOMs. The docs for handle_alloc_error
also explicitly state that it may either abort or unwind.
I'm not sure what would be the best way to fix this. Maybe just catch_unwind
the call to RawVec::shrink_to_fit
and manually abort the process would work well enough?