Skip to content

prevent deref coercions in pin!#153457

Open
dianne wants to merge 2 commits intorust-lang:mainfrom
dianne:no-coercing-in-pin-macro
Open

prevent deref coercions in pin!#153457
dianne wants to merge 2 commits intorust-lang:mainfrom
dianne:no-coercing-in-pin-macro

Conversation

@dianne
Copy link
Contributor

@dianne dianne commented Mar 5, 2026

View all comments

Fixes #153438 using a (hopefully temporary!) typed macro idiom to ensure that when pin! produces a Pin<&mut T>, its argument is of type T. See #153438 (comment) for my ideas on how this could be changed in the future.

@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. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Mar 5, 2026
@rustbot
Copy link
Collaborator

rustbot commented Mar 5, 2026

r? @jieyouxu

rustbot has assigned @jieyouxu.
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 69 candidates
  • Random selection from 15 candidates

@dianne
Copy link
Contributor Author

dianne commented Mar 5, 2026

I expect this needs crater, so: @bors try

@rust-bors

This comment has been minimized.

rust-bors bot pushed a commit that referenced this pull request Mar 5, 2026
@dianne dianne force-pushed the no-coercing-in-pin-macro branch from 092ecb8 to 8f93fc9 Compare March 5, 2026 19:25
@dianne
Copy link
Contributor Author

dianne commented Mar 5, 2026

Just realized I could format things more cleanly (diff). This shouldn't affect the correctness of the try build.

@eggyal
Copy link
Contributor

eggyal commented Mar 5, 2026

A more explicit approach could be to do something like:

super let mut pinned = $value;

#[allow(unreachable_code)]
'p: {
    fn unreachable_type_constraint<'a, T>(_: T) -> Pin<&'a mut T> {
        unreachable!()
    }

    break 'p unsafe { Pin::new_unchecked(&mut pinned) };
    unreachable_type_constraint(pinned)
}

@dianne
Copy link
Contributor Author

dianne commented Mar 5, 2026

So that's how to add types to macros! Funky idiom, but I do prefer the explicitness of actually having types, yeah. I think that'd be less likely to break inference too if anything's started relying on it; iirc type expectations are propagated from the block to the break expression (via the block's coercion target type). But it's fine because we'll encounter an error instead of silently adding a coercion if things don't match up.. I think.

Would you prefer to make your own PR @eggyal, or should I just work that into this one? I'm not a library reviewer so I can't say which would be preferable from that perspective, but from the perspective I do have, I think adding a type constraint is a better fix.

@eggyal
Copy link
Contributor

eggyal commented Mar 5, 2026

By all means work it into this one. This idiom was originally suggested to me by lcnr some years ago, so I claim no credit for it!

@Kivooeo
Copy link
Member

Kivooeo commented Mar 5, 2026

this change requires someone from libs team to be approved, so i'm reassigning

r? libs

@rustbot rustbot assigned Mark-Simulacrum and unassigned jieyouxu Mar 5, 2026
@rust-bors
Copy link
Contributor

rust-bors bot commented Mar 5, 2026

☀️ Try build successful (CI)
Build commit: 048f484 (048f484e31141edc1fa71e20406300e92a9c16ba, parent: 64b72a1fa5449d928d5f553b01a596b78ee255d2)

@dianne dianne force-pushed the no-coercing-in-pin-macro branch from 8f93fc9 to 524b11d Compare March 5, 2026 22:22
@dianne
Copy link
Contributor Author

dianne commented Mar 5, 2026

Went ahead with the unreachable type constraint version of this (diff). There's unfortunately a bit of a diagnostics regression due to the extra type checking. I still think it's worth the lower likelihood of breakage, but it's a tradeoff. I'll be firing off a fresh try build, but if the diagnostics are a sticking point, maybe it'd be worth comparing crater results for both approaches?

@dianne
Copy link
Contributor Author

dianne commented Mar 5, 2026

@bors try

@rust-bors

This comment has been minimized.

rust-bors bot pushed a commit that referenced this pull request Mar 5, 2026
@rust-bors
Copy link
Contributor

rust-bors bot commented Mar 6, 2026

☀️ Try build successful (CI)
Build commit: 5d96fa0 (5d96fa0e954d77528204a1ba3b8847ec083c779b, parent: 64b72a1fa5449d928d5f553b01a596b78ee255d2)

@dianne
Copy link
Contributor Author

dianne commented Mar 6, 2026

I'll crater the type constraint approach first since that's my preferred quick fix at the moment.
@craterbot check

Also going to try re-running pr-check-2. It never restarted after being canceled it looks like?

@craterbot
Copy link
Collaborator

👌 Experiment pr-153457 created and queued.
🤖 Automatically detected try build 5d96fa0
🔍 You can check out the queue and this experiment's details.

ℹ️ Crater is a tool to run experiments across parts of the Rust ecosystem. Learn more

@craterbot craterbot added S-waiting-on-crater Status: Waiting on a crater run to be completed. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Mar 6, 2026
@theemathas
Copy link
Contributor

theemathas commented Mar 6, 2026

I'm not yet convinced that this approach is 100% sound. Although I think it would be a good idea to do this fix even if it's not 100% sound, since any soundness exploit of this would be significantly more convoluted than the current exploit.

Given a value x of type Src, it might be possible with this PR for the expression pin!(x) to create a value of type Pin<&mut Dst>, where Src and Dst are different types. This can happen and cause unsoundness if:

  • Src can be coerced to Dst (this coercion would be done in the parameter of unreachable_type_constraint); and
  • &mut Src can be coerced to &mut Dst.(this coercion would be done in the parameter of Pin::new_unchecked); and
  • Src and Dst are different types and have no subtyping relationship with each other; and
  • Dst does not implement Unpin

I am not sure whether there exist types Src and Dst such that these two coercions can happen.

Edit: Added two more bullet points

Edit 2: Changed a requirement into the Unpin requirement.

@eggyal

This comment has been minimized.

@theemathas

This comment has been minimized.

@eggyal

This comment has been minimized.

@theemathas

This comment has been minimized.

@dianne
Copy link
Contributor Author

dianne commented Mar 6, 2026

Ooh, I hadn't considered that. Even if this is 100% sound, that means the argument for it is subtle enough that I'm not sure I'd want to rely on it over something simpler. Maybe going back to an inference-limiting approach (or combining approaches) would be best? That way we can be sure there's no coercions in the first place. For ease of comparison, the trick in my original version of this PR was

let pinned_ref = unsafe { $crate::pin::Pin::new_unchecked(&mut pinned) };
pinned_ref

Since the type of pinned_ref isn't known when checking the let statement, we won't get coercions in the argument of Pin::new_unchecked.

If that's too subtle, I think there are other ways to limit inference/coercions too. This is probably even hackier, but to have an example, a trick I've used for preventing coercions in tests is defining trait Is<T> {} with impl<T> Is<T> for T {}. Making a function take impl Is<T> instead of T makes calls of it unable to coerce that argument even if T is known, since impl Is<T> is a different parameter and only constrained later. To combine this with the type constraint approach, unreachable_type_constraint could take an Is<T> to prevent coercions there. A #[diagnostic::on_unimplemented] attribute could maybe prevent the diagnostics from getting too much worse if a trait-based approach is desired? I've never used the attribute myself though.

Copy link
Contributor

@danielhenrymantilla danielhenrymantilla left a comment

Choose a reason for hiding this comment

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

Nice, I like the helper function over merely relying on an extra let binding to prevent the deref coërcion

View changes since this review

Comment on lines +2043 to +2045
fn unreachable_type_constraint<'a, T>(_: T) -> $crate::pin::Pin<&'a mut T> {
unreachable!()
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This ought to be factored out in some macro-internals module of ::core, to reduce compiler overhead (we shouldn't be defining an ad-hoc fn for every pin! invocation).

  • Tangentially, we may be able to avoid the unreachable_code warning and the 'p-labelled block with an if false { unreachable_type_constraint(pinned) } else { Pin::new_unchecked(&mut pinner) }, although this touches on a matter of taste and preference (there is some appeal in that expect, after all)

@craterbot
Copy link
Collaborator

🚧 Experiment pr-153457 is now running

ℹ️ Crater is a tool to run experiments across parts of the Rust ecosystem. Learn more

@craterbot
Copy link
Collaborator

🎉 Experiment pr-153457 is completed!
📊 1 regressed and 5 fixed (839795 total)
📊 2279 spurious results on the retry-regressed-list.txt, consider a retry1 if this is a significant amount.
📰 Open the summary report.

⚠️ If you notice any spurious failure please add them to the denylist!
ℹ️ Crater is a tool to run experiments across parts of the Rust ecosystem. Learn more

Footnotes

  1. re-run the experiment with crates=https://crater-reports.s3.amazonaws.com/pr-153457/retry-regressed-list.txt

@craterbot craterbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-crater Status: Waiting on a crater run to be completed. labels Mar 9, 2026
@theemathas
Copy link
Contributor

@Skgland
Copy link
Contributor

Skgland commented Mar 9, 2026

Wait what? Why did crater not catch https://github.com/restatedev/restate/blob/a472bad4e12427012f6215947edd35561d62d0b3/crates/vqueues/src/scheduler/drr.rs#L563 ?

The create does not appear to be on crates.io (it has package.publish = "false") nor does the repo appear to be in https://github.com/rust-lang/rust-repos/blob/master/data/github.csv.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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. T-libs Relevant to the library team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

pin!() is unsound due to coercions

10 participants