diff --git a/compiler/rustc_const_eval/src/util/alignment.rs b/compiler/rustc_const_eval/src/util/alignment.rs index 9507b24f603eb..104044522b0f7 100644 --- a/compiler/rustc_const_eval/src/util/alignment.rs +++ b/compiler/rustc_const_eval/src/util/alignment.rs @@ -1,12 +1,12 @@ use rustc_abi::Align; use rustc_middle::mir::*; -use rustc_middle::ty::{self, TyCtxt}; +use rustc_middle::ty::{self, Ty, TyCtxt}; use tracing::debug; /// Returns `true` if this place is allowed to be less aligned /// than its containing struct (because it is within a packed /// struct). -pub fn is_disaligned<'tcx, L>( +pub fn is_potentially_misaligned<'tcx, L>( tcx: TyCtxt<'tcx>, local_decls: &L, typing_env: ty::TypingEnv<'tcx>, @@ -15,38 +15,71 @@ pub fn is_disaligned<'tcx, L>( where L: HasLocalDecls<'tcx>, { - debug!("is_disaligned({:?})", place); + debug!("is_potentially_misaligned({:?})", place); let Some(pack) = is_within_packed(tcx, local_decls, place) else { - debug!("is_disaligned({:?}) - not within packed", place); + debug!("is_potentially_misaligned({:?}) - not within packed", place); return false; }; let ty = place.ty(local_decls, tcx).ty; let unsized_tail = || tcx.struct_tail_for_codegen(ty, typing_env); + match tcx.layout_of(typing_env.as_query_input(ty)) { - Ok(layout) + Ok(layout) => { if layout.align.abi <= pack - && (layout.is_sized() - || matches!(unsized_tail().kind(), ty::Slice(..) | ty::Str)) => - { - // If the packed alignment is greater or equal to the field alignment, the type won't be - // further disaligned. - // However we need to ensure the field is sized; for unsized fields, `layout.align` is - // just an approximation -- except when the unsized tail is a slice, where the alignment - // is fully determined by the type. - debug!( - "is_disaligned({:?}) - align = {}, packed = {}; not disaligned", - place, - layout.align.abi.bytes(), - pack.bytes() - ); - false + && (layout.is_sized() || matches!(unsized_tail().kind(), ty::Slice(..) | ty::Str)) + { + // If the packed alignment is greater or equal to the field alignment, the type won't be + // further disaligned. + // However we need to ensure the field is sized; for unsized fields, `layout.align` is + // just an approximation -- except when the unsized tail is a slice, where the alignment + // is fully determined by the type. + debug!( + "is_potentially_misaligned({:?}) - align = {}, packed = {}; not disaligned", + place, + layout.align.abi.bytes(), + pack.bytes() + ); + false + } else { + true + } + } + Err(_) => { + // Soundness: For any `T`, the ABI alignment requirement of `[T]` equals that of `T`. + // Proof sketch: + // (1) From `&[T]` we can obtain `&T`, hence align([T]) >= align(T). + // (2) Using `std::array::from_ref(&T)` we can obtain `&[T; 1]` (and thus `&[T]`), + // hence align(T) >= align([T]). + // Therefore align([T]) == align(T). Length does not affect alignment. + + // Try to determine alignment from the type structure + if let Some(element_align) = get_element_alignment(tcx, typing_env, ty) { + element_align > pack + } else { + // If we still can't determine alignment, conservatively assume disaligned + true + } } - _ => { - // We cannot figure out the layout. Conservatively assume that this is disaligned. - debug!("is_disaligned({:?}) - true", place); - true + } +} + +// For arrays/slices, `align([T]) == align(T)` (independent of length). +// So if layout_of([T; N]) is unavailable, we can fall back to layout_of(T). +fn get_element_alignment<'tcx>( + tcx: TyCtxt<'tcx>, + typing_env: ty::TypingEnv<'tcx>, + ty: Ty<'tcx>, +) -> Option { + match ty.kind() { + ty::Array(elem_ty, _) | ty::Slice(elem_ty) => { + // Try to obtain the element's layout; if we can, use its ABI align. + match tcx.layout_of(typing_env.as_query_input(*elem_ty)) { + Ok(layout) => Some(layout.align.abi), + Err(_) => None, // stay conservative when even the element's layout is unknown + } } + _ => None, } } diff --git a/compiler/rustc_const_eval/src/util/mod.rs b/compiler/rustc_const_eval/src/util/mod.rs index 5be5ee8d1ae97..fa582ef0d430b 100644 --- a/compiler/rustc_const_eval/src/util/mod.rs +++ b/compiler/rustc_const_eval/src/util/mod.rs @@ -6,7 +6,7 @@ mod check_validity_requirement; mod compare_types; mod type_name; -pub use self::alignment::{is_disaligned, is_within_packed}; +pub use self::alignment::{is_potentially_misaligned, is_within_packed}; pub use self::check_validity_requirement::check_validity_requirement; pub(crate) use self::check_validity_requirement::validate_scalar_in_layout; pub use self::compare_types::{relate_types, sub_types}; diff --git a/compiler/rustc_mir_transform/src/add_moves_for_packed_drops.rs b/compiler/rustc_mir_transform/src/add_moves_for_packed_drops.rs index 7ae2ebaf4ff09..dbc35087ba207 100644 --- a/compiler/rustc_mir_transform/src/add_moves_for_packed_drops.rs +++ b/compiler/rustc_mir_transform/src/add_moves_for_packed_drops.rs @@ -51,7 +51,7 @@ impl<'tcx> crate::MirPass<'tcx> for AddMovesForPackedDrops { match terminator.kind { TerminatorKind::Drop { place, .. } - if util::is_disaligned(tcx, body, typing_env, place) => + if util::is_potentially_misaligned(tcx, body, typing_env, place) => { add_move_for_packed_drop( tcx, diff --git a/compiler/rustc_mir_transform/src/check_packed_ref.rs b/compiler/rustc_mir_transform/src/check_packed_ref.rs index dcb812c78993e..db67a43da5653 100644 --- a/compiler/rustc_mir_transform/src/check_packed_ref.rs +++ b/compiler/rustc_mir_transform/src/check_packed_ref.rs @@ -37,7 +37,8 @@ impl<'tcx> Visitor<'tcx> for PackedRefChecker<'_, 'tcx> { } fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, _location: Location) { - if context.is_borrow() && util::is_disaligned(self.tcx, self.body, self.typing_env, *place) + if context.is_borrow() + && util::is_potentially_misaligned(self.tcx, self.body, self.typing_env, *place) { let def_id = self.body.source.instance.def_id(); if let Some(impl_def_id) = self.tcx.impl_of_assoc(def_id) diff --git a/tests/ui/packed/packed-array-generic-length.rs b/tests/ui/packed/packed-array-generic-length.rs new file mode 100644 index 0000000000000..dc205a9b44452 --- /dev/null +++ b/tests/ui/packed/packed-array-generic-length.rs @@ -0,0 +1,137 @@ +//! Borrowing from packed arrays with generic length: +//! - allowed if ABI align([T]) == align(T) <= packed alignment +//! - hard error (E0793) otherwise +#![allow(dead_code, unused_variables, unused_mut)] +use std::mem::MaybeUninit; +use std::num::{NonZeroU8, NonZeroU16}; + +// +// -------- PASS CASES -------- +// + +mod pass_u8 { + #[repr(C, packed)] + pub struct PascalString { + len: u8, + buf: [u8; N], + } + + pub fn bar(s: &PascalString) -> &str { + // should NOT trigger E0793 + std::str::from_utf8(&s.buf[0..s.len as usize]).unwrap() + } + + pub fn run() { + let p = PascalString::<10> { len: 3, buf: *b"abc\0\0\0\0\0\0\0" }; + let s = bar(&p); + assert_eq!(s, "abc"); + } +} + +mod pass_i8 { + #[repr(C, packed)] + pub struct S { + buf: [i8; N], + } + pub fn run() { + let s = S::<4> { buf: [1, 2, 3, 4] }; + let _ok = &s.buf[..]; // no E0793 + } +} + +mod pass_nonzero_u8 { + use super::*; + #[repr(C, packed)] + pub struct S { + buf: [NonZeroU8; N], + } + pub fn run() { + let s = S::<3> { + buf: [ + NonZeroU8::new(1).unwrap(), + NonZeroU8::new(2).unwrap(), + NonZeroU8::new(3).unwrap(), + ], + }; + let _ok = &s.buf[..]; // no E0793 + } +} + +mod pass_maybeuninit_u8 { + use super::*; + #[repr(C, packed)] + pub struct S { + buf: [MaybeUninit; N], + } + pub fn run() { + let s = S::<2> { buf: [MaybeUninit::new(1), MaybeUninit::new(2)] }; + let _ok = &s.buf[..]; // no E0793 + } +} + +mod pass_transparent_u8 { + #[repr(transparent)] + pub struct WrapU8(u8); + + #[repr(C, packed)] + pub struct S { + buf: [WrapU8; N], + } + pub fn run() { + let s = S::<2> { buf: [WrapU8(1), WrapU8(2)] }; + let _ok = &s.buf[..]; // no E0793 + } +} + +// +// -------- FAIL CASES (expect E0793) -------- +// + +mod fail_u16 { + #[repr(C, packed)] + pub struct S { + buf: [u16; N], + } + pub fn run() { + let s = S::<2> { buf: [1, 2] }; + let _err = &s.buf[..]; + //~^ ERROR: reference to packed field is unaligned + } +} + +mod fail_nonzero_u16 { + use super::*; + #[repr(C, packed)] + pub struct S { + buf: [NonZeroU16; N], + } + pub fn run() { + let s = S::<1> { buf: [NonZeroU16::new(1).unwrap()] }; + let _err = &s.buf[..]; + //~^ ERROR: reference to packed field is unaligned + } +} + +mod fail_transparent_u16 { + #[repr(transparent)] + pub struct WrapU16(u16); + + #[repr(C, packed)] + pub struct S { + buf: [WrapU16; N], + } + pub fn run() { + let s = S::<1> { buf: [WrapU16(42)] }; + let _err = &s.buf[..]; + //~^ ERROR: reference to packed field is unaligned + } +} + +fn main() { + // Run pass cases (fail cases only check diagnostics) + pass_u8::run(); + pass_i8::run(); + pass_nonzero_u8::run(); + pass_maybeuninit_u8::run(); + pass_transparent_u8::run(); +} diff --git a/tests/ui/packed/packed-array-generic-length.stderr b/tests/ui/packed/packed-array-generic-length.stderr new file mode 100644 index 0000000000000..937e21e7d948f --- /dev/null +++ b/tests/ui/packed/packed-array-generic-length.stderr @@ -0,0 +1,33 @@ +error[E0793]: reference to packed field is unaligned + --> $DIR/packed-array-generic-length.rs:97:21 + | +LL | let _err = &s.buf[..]; + | ^^^^^ + | + = note: packed structs are only aligned by one byte, and many modern architectures penalize unaligned field accesses + = note: creating a misaligned reference is undefined behavior (even if that reference is never dereferenced) + = help: copy the field contents to a local variable, or replace the reference with a raw pointer and use `read_unaligned`/`write_unaligned` (loads and stores via `*p` must be properly aligned even when using raw pointers) + +error[E0793]: reference to packed field is unaligned + --> $DIR/packed-array-generic-length.rs:110:21 + | +LL | let _err = &s.buf[..]; + | ^^^^^ + | + = note: packed structs are only aligned by one byte, and many modern architectures penalize unaligned field accesses + = note: creating a misaligned reference is undefined behavior (even if that reference is never dereferenced) + = help: copy the field contents to a local variable, or replace the reference with a raw pointer and use `read_unaligned`/`write_unaligned` (loads and stores via `*p` must be properly aligned even when using raw pointers) + +error[E0793]: reference to packed field is unaligned + --> $DIR/packed-array-generic-length.rs:125:21 + | +LL | let _err = &s.buf[..]; + | ^^^^^ + | + = note: packed structs are only aligned by one byte, and many modern architectures penalize unaligned field accesses + = note: creating a misaligned reference is undefined behavior (even if that reference is never dereferenced) + = help: copy the field contents to a local variable, or replace the reference with a raw pointer and use `read_unaligned`/`write_unaligned` (loads and stores via `*p` must be properly aligned even when using raw pointers) + +error: aborting due to 3 previous errors + +For more information about this error, try `rustc --explain E0793`.