Reject #[repr(packed)] on #[pin_v2] types#157542
Conversation
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.
|
r? @folkertdev rustbot has assigned @folkertdev. Use Why was this reviewer chosen?The reviewer was selected based on:
|
| 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()), | ||
| }); | ||
| } |
There was a problem hiding this comment.
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
rust/compiler/rustc_hir_analysis/src/check/always_applicable.rs
Lines 408 to 415 in 61d7280
|
Reminder, once the PR becomes ready for a review, use |
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`.
75249a0 to
9663384
Compare
Could you provide example code for this? |
yeah here's a repro w/ no #![feature(pin_ergonomics)]
#![allow(incomplete_features)]
use std::{marker::PhantomPinned, pin::{Pin, pin}};
// no #[pin_v2] on either type
#[repr(C, packed(4))]
struct One<T>(T);
#[repr(C, align(4096))]
struct Two(Thing);
struct Thing(#[expect(dead_code)] i32, PhantomPinned);
fn main() {
let pinned_one: Pin<&mut One<Two>> = pin!(One(Two(Thing(0, PhantomPinned))));
let &pin mut One(Two(ref pin mut pinned_thing)) = pinned_one;
access(pinned_thing);
}
fn access(x: Pin<&mut Thing>) {
println!("Pinned access at {x:p}")
}
impl Drop for Thing {
fn drop(&mut self) {
println!("Dropped at {self:p}");
}
}the pinned-access addr and the drop addr come out different, so the afaict two things worth noting. i used |
Is there an issue for this and can either of you open one if it doesn't exist? |
On my search I didn't found any related with this specifically....so, yes, I will open one. |
Can you guys validate, please? cc// @theemathas @mejrs |
There was a problem hiding this comment.
I'll admit to not being an expert on Pin stuff but that looks good to me. I've also added it as a sub-issue.
@bors r+ rollup
Rollup of 5 pull requests Successful merges: - #154543 (Fix deref field pattern suggestions and improve error messages) - #157476 (Adopt to LLVM 23 CfiFunctionIndex change) - #157542 (Reject `#[repr(packed)]` on `#[pin_v2]` types) - #157603 (Fixed Doc Comment Typo in Linewritershim) - #157624 (Move AttributeTemplate from rustc_feature to rustc_attr_parsing)
Rollup merge of #157542 - Dnreikronos:pin_v2_ban_repr_packed, r=mejrs Reject `#[repr(packed)]` on `#[pin_v2]` types 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.*
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 aPin<&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 incheck_packedinrustc_hir_analysis, so it catches the concrete case and the generic one too (likeOne<T>, where we don't knowT'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 mutprojection 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
@workingjubileeover 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/600522893Tests live in
tests/ui/pin-ergonomics/pin_v2-packed.rs: the three rejected shapes (packed struct, genericpacked(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.