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 calls RawVec::shrink_to_fit calls Allocator::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 of VecDeque 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?
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_fitcauses an alloc error, the thread unwinds,Foo(0)andFoo(1)are dropped in some order.Instead, this happened:
(the exact value printed differs between runs, which makes sense if it's reading uninit memory)
This occurs because of the way
VecDeque::shrink_tois implemented: It first moves the memory around to fit the new capacity, then callsRawVec::shrink_to_fit(new_capacity)with no regards to potential unwinding. Specifically, this would look something like this:shrink_tocopies the head to just behind the tail:shrink_tocallsRawVec::shrink_to_fitcallsAllocator::shrinkwhich returns an alloc error, the error handler unwinds, leaving the buffer looking like this:Dropimpl ofVecDequedrops 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 ifshrinkOOMs. The docs forhandle_alloc_erroralso 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_unwindthe call toRawVec::shrink_to_fitand manually abort the process would work well enough?