diff --git a/src/librustc/ty/layout.rs b/src/librustc/ty/layout.rs index 4af26e19b370c..eb42831daf87d 100644 --- a/src/librustc/ty/layout.rs +++ b/src/librustc/ty/layout.rs @@ -764,12 +764,28 @@ impl<'tcx> LayoutCx<'tcx, TyCtxt<'tcx>> { size = cmp::max(size, field.size); } + size = size.align_to(align.abi); + + if let Abi::ScalarPair(a, b) = &abi { + // Only use `ScalarPair` if there is no padding between + // `a` and `b`, or after `b`, to ensure that every byte + // of a `#[repr(transparent)]` `union` is covered by + // either a byte of `a` or one of `b`. + let a_size = a.value.size(dl); + let b_offset = a_size.align_to(b.value.align(dl).abi); + let has_inner_padding = a_size < b_offset; + let has_trailing_padding = b_offset + b.value.size(dl) < size; + if has_inner_padding || has_trailing_padding { + abi = Abi::Aggregate { sized: true }; + } + } + return Ok(tcx.intern_layout(LayoutDetails { variants: Variants::Single { index }, fields: FieldPlacement::Union(variants[index].len()), abi, align, - size: size.align_to(align.abi) + size, })); } diff --git a/src/test/ui/union/union-padding-preserved.rs b/src/test/ui/union/union-padding-preserved.rs new file mode 100644 index 0000000000000..eb45626a94e0d --- /dev/null +++ b/src/test/ui/union/union-padding-preserved.rs @@ -0,0 +1,87 @@ +// run-pass + +// Test that unions don't lose padding bytes (i.e. not covered by any leaf field). + +#![feature(core_intrinsics, test, transparent_unions)] + +extern crate test; + +#[repr(transparent)] +#[derive(Copy, Clone)] +union U { _x: T, y: () } + +impl U { + fn uninit() -> Self { + U { y: () } + } + + unsafe fn write(&mut self, i: usize, v: u8) { + (self as *mut _ as *mut u8).add(i).write(v); + } + + #[inline(never)] + unsafe fn read_rust(self, i: usize) -> u8 { + test::black_box((&self as *const _ as *const u8).add(i)).read() + } + + #[inline(never)] + unsafe extern "C" fn read_c(self, i: usize) -> u8 { + test::black_box((&self as *const _ as *const u8).add(i)).read() + } +} + +#[derive(Copy, Clone, Default)] +struct Options { + demote_c_to_warning: bool, +} + +unsafe fn check_at(i: usize, v: u8, opts: Options) { + let mut u = U::::uninit(); + u.write(i, v); + let msg = |abi: &str| format!( + "check_at::<{}>: {} ABI failed at byte {}", + std::intrinsics::type_name::(), + abi, + i, + ); + if u.read_rust(i) != v { + panic!(msg("Rust")); + } + if u.read_c(i) != v { + if opts.demote_c_to_warning { + eprintln!("warning: {}", msg("C")); + } else { + panic!(msg("C")); + } + } +} + +fn check_all(opts: Options) { + for i in 0..std::mem::size_of::() { + unsafe { + check_at::(i, 100 + i as u8, opts); + } + } +} + +#[repr(C, align(16))] +#[derive(Copy, Clone)] +struct Align16(T); + +#[repr(C)] +#[derive(Copy, Clone)] +struct Pair(A, B); + +fn main() { + // NOTE(eddyb) we can't error for the `extern "C"` ABI in these cases, as + // the x86_64 SysV calling convention will drop padding bytes inside unions. + check_all::>(Options { demote_c_to_warning: true }); + check_all::>(Options { demote_c_to_warning: true }); + check_all::>(Options { demote_c_to_warning: true }); + + check_all::<(u8, u32)>(Options::default()); + check_all::<(u8, u64)>(Options::default()); + + check_all::>(Options::default()); + check_all::>(Options::default()); +}