Stack overflow when using `zeroed_box()`
Closed this issue · 4 comments
With a debug build, I'm consistently getting a stack overflow when using zeroed_box()
. Minimal example:
const PAGE_SIZE: usize = 4096;
type BlockChunk = [u8; PAGE_SIZE];
const MIB_16: usize = 16 * 1024 * 1024;
const SUPER_SIZE: usize = MIB_16 / std::mem::size_of::<BlockChunk>();
type SuperPage = [BlockChunk; SUPER_SIZE];
#[test]
fn test_alloc() {
// ABORT: thread '[...]::test_alloc' has overflowed its stack
let _: Box<SuperPage> = bytemuck::zeroed_box();
}
EDIT: This happens on windows, have not checked anything else yet.
Reproducible on playpen: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=547372389428bf450c9bf885cf7ea6b8
As far as I can tell, zeroed_box
, which is try_zeroed_box
with an unwrap
, can't possibly be using more than 5 or 6 usize
worth of stack space itself. So the allocator must be doing something funky.
I'm putting you on the case!
So, I'm currently guessing there is undefined behavior in play. This repoduces the issue standalone:
use std::alloc;
use std::mem::{size_of, align_of, zeroed};
use std::alloc::{alloc_zeroed, Layout};
#[inline]
pub fn try_zeroed_box<T>() -> Result<Box<T>, ()> {
if size_of::<T>() == 0 {
return Ok(Box::new(unsafe{zeroed()}));
}
let layout =
Layout::from_size_align(size_of::<T>(), align_of::<T>()).unwrap();
let ptr = unsafe { alloc_zeroed(layout) };
if ptr.is_null() {
// we don't know what the error is because `alloc_zeroed` is a dumb API
Err(())
} else {
Ok(unsafe { Box::<T>::from_raw(ptr as *mut T) })
}
}
const PAGE_SIZE: usize = 4096;
type BlockChunk = [u8; PAGE_SIZE];
const MIB_16: usize = 16 * 1024 * 1024;
const SUPER_SIZE: usize = MIB_16 / std::mem::size_of::<BlockChunk>();
type SuperPage = [BlockChunk; SUPER_SIZE];
fn main() {
// ABORT: thread '[...]::test_alloc' has overflowed its stack
let _: Box<SuperPage> = try_zeroed_box().unwrap();
}
Note in particual these lines:
if size_of::<T>() == 0 {
return Ok(Box::new(unsafe{zeroed()}));
}
I'm guessing that this somehow is undefined behavior if expanded with a very large T
Actually its perfectly well define beheavior - its a normal branch, which contains a temporary stack variable that needs to be allocated in the stack frame. So the issue is just how to avoid it.