Skip to content

Reject #[repr(packed)] on #[pin_v2] types#157542

Open
Dnreikronos wants to merge 2 commits into
rust-lang:mainfrom
Dnreikronos:pin_v2_ban_repr_packed
Open

Reject #[repr(packed)] on #[pin_v2] types#157542
Dnreikronos wants to merge 2 commits into
rust-lang:mainfrom
Dnreikronos:pin_v2_ban_repr_packed

Conversation

@Dnreikronos
Copy link
Copy Markdown
Contributor

@Dnreikronos Dnreikronos commented Jun 6, 2026

Fixes #157011

#[repr(packed)] can store an over-aligned field below its alignment, and the drop glue for a packed type moves that field to a properly aligned spot before dropping it. When the field is structurally pinned, that move pulls it out from under a Pin<&mut _> we already handed out, which breaks the pin invariant. The repro in the issue makes it pretty clear: it prints one address during pinned access and a different one on drop.

So the fix just rejects the combo at the type definition. If a type is both #[pin_v2] and #[repr(packed)], it no longer compiles. The check sits in check_packed in rustc_hir_analysis, so it catches the concrete case and the generic one too (like One<T>, where we don't know T's alignment yet), with no layout or monomorphization needed.

One thing I want to be upfront about: this is necessary but don't solve the problem entirely. Sure, it stops the spelling the issue used, but you can still trigger the same move-on-drop through an explicit &pin mut / ref pin mut projection with no #[pin_v2] anywhere. Leaving that broader case open is intentional, imo the narrow ban is the right call for now, and the leftover stays tracked on the pin ergonomics tracking issue #130494.

This is the direction we landed on with @workingjubilee over on Zulip, lgtm from their side: https://rust-lang.zulipchat.com/#narrow/channel/182449-t-compiler.2Fhelp/topic/.60pin_v2.60.20is.20unsound.20with.20.60packed.60/with/600522893

Tests live in tests/ui/pin-ergonomics/pin_v2-packed.rs: the three rejected shapes (packed struct, generic packed(4) struct, packed union), plus two controls that still compile fine, #[pin_v2] without packed and packed without #[pin_v2].

Disclosure: AI tooling was used on the code changes, and everything was strictly validated by me before sending to remote.

A `#[repr(packed)]` type can store an over-aligned field below its
alignment. Drop glue for such a type moves the field to a properly
aligned location before dropping it, which would move a structurally
pinned field out from under a `Pin<&mut _>` that was handed out,
breaking Pin's invariant.

Reject the combination at the type definition so the unsound program
never compiles.
@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 Jun 6, 2026
@rustbot
Copy link
Copy Markdown
Collaborator

rustbot commented Jun 6, 2026

r? @folkertdev

rustbot has assigned @folkertdev.
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

Why was this reviewer chosen?

The reviewer was selected based on:

  • Owners of files modified in this PR: compiler
  • compiler expanded to 73 candidates
  • Random selection from 20 candidates

Copy link
Copy Markdown
Contributor

@mejrs mejrs left a comment

Choose a reason for hiding this comment

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

Comment on lines +1639 to +1647
if def.is_pin_project()
&& let Some(pin_v2_span) = find_attr!(tcx, def.did(), PinV2(span) => *span)
{
tcx.dcx().emit_err(errors::PinV2OnPacked {
span: sp,
pin_v2_span,
adt_name: tcx.item_name(def.did()),
});
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This error should be emitted whether or not the attribute can be found. Your best option is to use Option<Span> instead. Take for example

if tcx.adt_def(adt_def_id).is_pin_project() {
let pin_v2_span = rustc_hir::find_attr!(tcx, adt_def_id, PinV2(attr) => *attr);
let adt_name = tcx.item_name(adt_def_id);
return Err(tcx.dcx().emit_err(crate::errors::PinV2WithoutPinDrop {
span,
pin_v2_span,
adt_name,
}));

@rustbot rustbot assigned mejrs and unassigned folkertdev Jun 6, 2026
@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 Jun 6, 2026
@rustbot
Copy link
Copy Markdown
Collaborator

rustbot commented Jun 6, 2026

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

Tie emission to `is_pin_project()` alone and pass the `#[pin_v2]`
span as `Option<Span>`, so the error still fires if the span cannot
be found, mirroring `PinV2WithoutPinDrop`.
@Dnreikronos Dnreikronos force-pushed the pin_v2_ban_repr_packed branch from 75249a0 to 9663384 Compare June 7, 2026 00:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unsoundness due to #[pin_v2] incorrectly handles packed structs.

4 participants