Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 68 additions & 22 deletions compiler/rustc_const_eval/src/util/alignment.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
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
Expand All @@ -23,33 +23,79 @@ where

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)

// Try to normalize the type to resolve any generic parameters
let normalized_ty = match tcx.try_normalize_erasing_regions(typing_env, ty) {
Ok(normalized) => normalized,
Err(_) => {
// If normalization fails, fall back to the original type
ty
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why? it feels a lot safer to conservatively return true here, same as when layout_of fails

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

still relevant

}
};

match tcx.layout_of(typing_env.as_query_input(normalized_ty)) {
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_disaligned({:?}) - align = {}, packed = {}; not disaligned",
place,
layout.align.abi.bytes(),
pack.bytes()
);
false
} else {
true
}
}
_ => {
// We cannot figure out the layout. Conservatively assume that this is disaligned.
debug!("is_disaligned({:?}) - true", place);
true
Err(_) => {
// We cannot figure out the layout. This often happens with generic types.
// For const generic arrays like [u8; CAP], we can make a reasonable assumption
// about their alignment based on the element type.
Copy link
Contributor

@lcnr lcnr Aug 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we're not making a reasonable assumption. Their layout literally only depend son the layout of their element type, doesn't it?

Getting this right is soundness critical, so we need to be confident about that we don#t introduce false negatives.

Also, please rename this function to is_potentially_disaligned, to clarify that it's allowed to return false positives, but not false nnegatives

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use "misaligned" elsewhere in the compiler, maybe let's go with that instead of "disaligned" (which I never saw/heard before).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also still relevant

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the detailed feedback!

I agree with the soundness concern and with using the term “misaligned”.
I will:

  • rename the function to is_potentially_misaligned and update call sites/docs to clarify that it may return false positives but must not return false negatives (soundness-critical),
  • avoid relaxing the check when layout_of(ty) fails in general.

To still address the motivating case without introducing false negatives, I’ll only add a very small, layout-free special-case:
when ty.kind() is Array(elem, _) and elem is u8 or i8 (which have ABI alignment 1 by definition, independent of the array length), we treat the borrow as not potentially misaligned. All other cases remain conservative and return true when layout_of(ty) fails.

This fixes the [u8; CAP] packed-struct case while keeping the function strictly conservative for any type whose ABI alignment could exceed the packed alignment.

If you’d prefer the even stricter variant (no special-casing at all), I can drop the exception and keep returning true on layout_of failure across the board.

Copy link
Contributor

@lcnr lcnr Aug 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think ignoring the length and only checking the argument is fine. My issue was with the comment, not the behavior. We already guarantee that the alignment constraints are equal:

you can get a &T out of an &[T], so [T] must have stronger requirements than &T

you can get a &[T] from a &T via std::array::from_ref , so T must have stronger requirements than &[T]

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I’ve updated the comment to formally justify that align([T]) == align(T),
using the two directions you mentioned (&[T] -> &T and std::array::from_ref(&T)).
Behavior remains unchanged: we only consider the element type’s ABI alignment and
ignore the array length.


// Try to determine alignment from the type structure
if let Some(element_align) = get_element_alignment(tcx, normalized_ty) {
element_align > pack
} else {
// If we still can't determine alignment, conservatively assume disaligned
true
}
}
}
}

/// Try to determine the alignment of an array element type
fn get_element_alignment<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option<Align> {
match ty.kind() {
ty::Array(element_ty, _) => {
// For arrays, the alignment is the same as the element type
let param_env = ty::ParamEnv::empty();
let typing_env =
ty::TypingEnv { typing_mode: ty::TypingMode::non_body_analysis(), param_env };
match tcx.layout_of(typing_env.as_query_input(*element_ty)) {
Ok(layout) => Some(layout.align.abi),
Err(_) => None,
}
}
ty::Slice(element_ty) => {
// For slices, the alignment is the same as the element type
let param_env = ty::ParamEnv::empty();
let typing_env =
ty::TypingEnv { typing_mode: ty::TypingMode::non_body_analysis(), param_env };
match tcx.layout_of(typing_env.as_query_input(*element_ty)) {
Ok(layout) => Some(layout.align.abi),
Err(_) => None,
}
}
_ => None,
}
}
pub fn is_within_packed<'tcx, L>(
tcx: TyCtxt<'tcx>,
local_decls: &L,
Expand Down
19 changes: 19 additions & 0 deletions tests/ui/packed/packed-array-const-generic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//@ check-pass
#![allow(dead_code)]

#[repr(C, packed)]
struct PascalString<const CAP: usize> {
len: u8,
buf: [u8; CAP],
}

fn bar<const CAP: usize>(s: &PascalString<CAP>) -> &str {
// 目标:这行不应触发 E0793
std::str::from_utf8(&s.buf[0..s.len as usize]).unwrap()
}

fn main() {
let p = PascalString::<10> { len: 3, buf: *b"abc\0\0\0\0\0\0\0" };
let s = bar(&p);
assert_eq!(s, "abc");
}
Loading