Skip to content

Conversation

hns1971
Copy link

@hns1971 hns1971 commented Aug 15, 2025

Fixes #145376

Summary

This PR fixes a false positive E0793 ("reference to packed field is unaligned")
when borrowing from a #[repr(C, packed)] struct field whose type is a const-generic array
with an element type that has ABI alignment <= the struct's packed alignment.

Example before this change:

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

fn bar<const CAP: usize>(s: &PascalString<CAP>) -> &str {
    std::str::from_utf8(&s.buf[0..s.len as usize]).unwrap()
}

Reference PR:

- Add fallback mechanism in is_disaligned() when layout computation fails
- Handle TooGeneric errors by computing element alignment from type structure
- Fix packed-array-const-generic.rs test to pass compilation
- Resolves issue where const generic parameters couldn't be resolved during alignment checks
@rustbot
Copy link
Collaborator

rustbot commented Aug 15, 2025

r? @SparrowLii

rustbot has assigned @SparrowLii.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Aug 15, 2025
@rustbot
Copy link
Collaborator

rustbot commented Aug 15, 2025

Some changes occurred to the CTFE machinery

cc @RalfJung, @oli-obk, @lcnr

@rust-log-analyzer

This comment has been minimized.

Copy link
Contributor

@gralpli gralpli left a comment

Choose a reason for hiding this comment

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

Hey, I’m the bug reporter. Just what I noticed on a first glance.

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

Comment on lines 59 to 60
// 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.

… clarity; Unified to ty::Array(element_ty, _) | ty::Slice(element_ty) => …;

Modify Chinese comments to English comments
@rustbot
Copy link
Collaborator

rustbot commented Aug 18, 2025

Some changes occurred to MIR optimizations

cc @rust-lang/wg-mir-opt

@hns1971
Copy link
Author

hns1971 commented Aug 18, 2025

Fixed:

  1. Rename is_disaligned to is_potentially_disaligned for better semantic clarity
  2. Unified to ty::Array(element_ty, _) | ty::Slice(element_ty) => …
  3. Modify Chinese comments to English comments.

Thanks

@hns1971 hns1971 requested review from RalfJung, lcnr and gralpli August 18, 2025 07:13
@hns1971
Copy link
Author

hns1971 commented Aug 20, 2025

I've updated the PR as requested (renamed function, updated comment, removed special-casing). Just let me know if further adjustments are needed.

Comment on lines 71 to 73
// Only allow u8 and i8 arrays when layout computation fails
// Other types are conservatively assumed to be misaligned
match element_ty.kind() {
Copy link
Contributor

Choose a reason for hiding this comment

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

properly compute the of the element_ty here, limiting it to only u8 and i8 feels unnecessary 🤔

@rust-log-analyzer

This comment has been minimized.

@hns1971
Copy link
Author

hns1971 commented Aug 28, 2025

Thanks for the detailed feedback!

I’ve updated the PR with the following:

  • Generalized the check to any [T; N]: when layout_of([T; N]) is unavailable,
    we compute layout_of(T) and compare abi_align(T) to the packed alignment.
    This relies on align([T]) == align(T) (length is irrelevant).
    I also updated the comment with the proof sketch:
    (1) &[T] -> &T implies align([T]) >= align(T)
    (2) std::array::from_ref(&T) -> &[T; 1] implies align(T) >= align([T])
    Hence align([T]) == align(T).
  • Renamed the helper to is_potentially_misaligned and switched terminology to “misaligned”.
    The doc comment now explicitly states that this may return false positives, but must not
    return false negatives (soundness-critical).
  • Merged tests into a single file and renamed it to packed-array-generic-length.rs.
    The test covers:
    • allowed: [u8; N], [i8; N], [NonZeroU8; N], [MaybeUninit<u8>; N],
      and #[repr(transparent)](u8) wrappers
    • still error: [u16; N], [NonZeroU16; N], and #[repr(transparent)](u16) wrappers

I’ve also drafted the exact rules in the PR description and opened a Reference PR
documenting this behavior (see rust-lang/reference#1984).
Please let me know if you want the rules tweaked further or if there’s a preferred place
in the Reference to put this.

@tmandry
Copy link
Member

tmandry commented Sep 10, 2025

Does this PR create a new semver hazard? For example..

// In a remote crate
struct Remote(u8);

// In local crate
#[repr(packed)]
struct Local {
  remote: Remote,
}

let remote = &Local::default().remote;

Then Remote can be changed to have a u16, which is now a semver-breaking change.

@rfcbot concern semver hazard

Otherwise, +1 for the overall idea.

@rfcbot reviewed

@RalfJung
Copy link
Member

That semver hazard already exists outside of arrays. This PR just makes the logic that figures out the alignment of a type smarter: we can know the alignment of [T; N] without knowing N.

Comment on lines +49 to +54
// 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.
Copy link
Member

@RalfJung RalfJung Sep 10, 2025

Choose a reason for hiding this comment

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

Suggested change
// 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.
// We can't compute the layout of `T`. But maybe we can still compute the alignment:
// For any `T`, the ABI alignment requirement of `[T]` and `[T; N]` equals that of `T`.
// Length does not affect alignment.

I don't think referencing standard library operations here makes much sense. Maybe we can reference the Reference wherever it defines the alignment of arrays, but TBH that doesn't seem necessary. What's relevant is to explicitly invoke the fact o that arrays are aligned like their element type, that's the one key reasoning step needed here. Let's not drown that in noise.

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

Comment on lines +74 to +77
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)) {
Copy link
Member

Choose a reason for hiding this comment

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

I'd recommend inlining this function at its only call site, this seems unnecessarily verbose.

If you want to make a helper function, it'd be get_type_align containing the entire match tcx.layout_of(typing_env.as_query_input(ty)) {.

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, I would personally prefer adding a single get_type_align function which does the nested layout_of call

}

fn main() {
// Run pass cases (fail cases only check diagnostics)
Copy link
Member

Choose a reason for hiding this comment

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

These never get run anyway since the file fails to compile, so this is quite pointless.

Copy link
Contributor

Choose a reason for hiding this comment

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

I would split this test into a pass and a fail version. Having them in the same test may not actually test the pass cases as we often end up hiding errors if others have been emitted.

I don't have an opinion on whether to make that a run-pass test or not

Comment on lines +31 to +84
mod pass_i8 {
#[repr(C, packed)]
pub struct S<const N: usize> {
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<const N: usize> {
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<const N: usize> {
buf: [MaybeUninit<u8>; 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<const N: usize> {
buf: [WrapU8; N],
}
pub fn run() {
let s = S::<2> { buf: [WrapU8(1), WrapU8(2)] };
let _ok = &s.buf[..]; // no E0793
}
}
Copy link
Member

Choose a reason for hiding this comment

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

All of these already pass before your PR, so they don't actually test what you want them to test.

You need to make the N generic, like in pass_u8. (In the fail tests it should also be generic to ensure it hits the same codepath.)

Comment on lines +24 to +28
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");
}
Copy link
Member

Choose a reason for hiding this comment

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

There's no reason I can see for this function to exist.

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

@nikomatsakis
Copy link
Contributor

@rfcbot reviewed

The intent (make the warning more precise for cases where the packed struct fields do not actually have stricter alignment requirements) makes sense to me.

@tmandry
Copy link
Member

tmandry commented Sep 17, 2025

Hmm, I wish we could address the semver hazard with repr(packed), but now that I understand what this PR actually does I can see it's not relevant.

@rfcbot resolve semver hazard

@scottmcm
Copy link
Member

Thanks for the reference update.
@rfcbot resolve exact-rules-list

I do think that, from an implementation perspective, it's a bit icky for this particular check to do this dance of "fail to get layout, then do extra layout calculations". It feels like there ought to be a general query for "layout but I only care about alignment (or an under- or over-estimate thereof)" that everywhere that doesn't care about things like niches and field offsets, just the alignment, could use so that checks like this would always be consistent. (Like const prop of the Align UnOp in a MIR optimization arguably wants this as well.)

@rust-rfcbot rust-rfcbot added final-comment-period In the final comment period and will be merged soon unless new substantive objections are raised. and removed proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. labels Sep 17, 2025
@rust-rfcbot
Copy link
Collaborator

🔔 This is now entering its final comment period, as per the review above. 🔔

@RalfJung
Copy link
Member

I do think that, from an implementation perspective, it's a bit icky for this particular check to do this dance of "fail to get layout, then do extra layout calculations". It feels like there ought to be a general query for "layout but I only care about alignment (or an under- or over-estimate thereof)" that everywhere that doesn't care about things like niches and field offsets, just the alignment, could use so that checks like this would always be consistent. (Like const prop of the Align UnOp in a MIR optimization arguably wants this as well.)

Yeah, agreed.

Seems worth at least filing a C-cleanup issue for?

@traviscross traviscross removed I-lang-nominated Nominated for discussion during a lang team meeting. P-lang-drag-1 Lang team prioritization drag level 1. https://rust-lang.zulipchat.com/#narrow/channel/410516-t-lang labels Sep 17, 2025
@hns1971
Copy link
Author

hns1971 commented Sep 27, 2025

I do think that, from an implementation perspective, it's a bit icky for this particular check to do this dance of "fail to get layout, then do extra layout calculations". It feels like there ought to be a general query for "layout but I only care about alignment (or an under- or over-estimate thereof)" that everywhere that doesn't care about things like niches and field offsets, just the alignment, could use so that checks like this would always be consistent. (Like const prop of the Align UnOp in a MIR optimization arguably wants this as well.)

Yeah, agreed.

Seems worth at least filing a C-cleanup issue for?

Hi @RalfJung,

Thank you for the insightful architectural suggestion. I agree that a general align_of query would be a cleaner and more consistent solution, not only for this check but also for other parts of the compiler like const prop.

To keep the scope of this current PR focused and ensure it can be merged smoothly, I plan to file a follow-up issue to track this idea after this PR is merged. This will allow us to discuss and design the general query interface properly without blocking the immediate improvement here.

Thanks again for the review and the valuable idea.

@RalfJung
Copy link
Member

Sounds good. :) Note that I didn't even make the architectural suggestion though. Also, no need to be so formal in your writing :) . Short and to the point beats long and verbose.

@rust-rfcbot rust-rfcbot added finished-final-comment-period The final comment period is finished for this PR / Issue. to-announce Announce this issue on triage meeting and removed final-comment-period In the final comment period and will be merged soon unless new substantive objections are raised. labels Sep 27, 2025
@rust-rfcbot
Copy link
Collaborator

The final comment period, with a disposition to merge, as per the review above, is now complete.

As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed.

This will be merged soon.

@bors
Copy link
Collaborator

bors commented Sep 29, 2025

☔ The latest upstream changes (presumably #147145) made this pull request unmergeable. Please resolve the merge conflicts.

@lcnr
Copy link
Contributor

lcnr commented Sep 30, 2025

@rustbot author

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Sep 30, 2025
@rustbot
Copy link
Collaborator

rustbot commented Sep 30, 2025

Reminder, once the PR becomes ready for a review, use @rustbot ready.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. finished-final-comment-period The final comment period is finished for this PR / Issue. I-lang-radar Items that are on lang's radar and will need eventual work or consideration. S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. S-waiting-on-documentation Status: Waiting on approved PRs to documentation before merging T-lang Relevant to the language team to-announce Announce this issue on triage meeting
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Compiler doesn’t allow creating u8 slices when using const generics