Conversation
|
r? @jieyouxu rustbot has assigned @jieyouxu. Use Why was this reviewer chosen?The reviewer was selected based on:
|
|
I expect this needs crater, so: @bors try |
This comment has been minimized.
This comment has been minimized.
prevent deref coercions in `pin!`
092ecb8 to
8f93fc9
Compare
|
Just realized I could format things more cleanly (diff). This shouldn't affect the correctness of the try build. |
|
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)
} |
|
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. |
|
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! |
|
this change requires someone from libs team to be approved, so i'm reassigning r? libs |
8f93fc9 to
524b11d
Compare
|
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? |
|
@bors try |
This comment has been minimized.
This comment has been minimized.
prevent deref coercions in `pin!`
|
I'll crater the type constraint approach first since that's my preferred quick fix at the moment. Also going to try re-running pr-check-2. It never restarted after being canceled it looks like? |
|
👌 Experiment ℹ️ Crater is a tool to run experiments across parts of the Rust ecosystem. Learn more |
|
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
I am not sure whether there exist types Edit: Added two more bullet points Edit 2: Changed a requirement into the Unpin requirement. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
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_refSince the type of 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 |
| fn unreachable_type_constraint<'a, T>(_: T) -> $crate::pin::Pin<&'a mut T> { | ||
| unreachable!() | ||
| } |
There was a problem hiding this comment.
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_codewarning and the'p-labelled block with anif 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 thatexpect, after all)
There was a problem hiding this comment.
This ought to be factored out in some macro-internals module of
::core, to reduce compiler overhead (we shouldn't be defining an ad-hocfnfor everypin!invocation).
Good point! I've been holding off on addressing that to allow more time to discuss which approach would be best, but I'll make sure to do that if this ends up involving a helper function.
- Tangentially, we may be able to avoid the
unreachable_codewarning and the'p-labelled block with anif 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 thatexpect, after all)
As ugly as the labeled block and break are, I'd like for static analyses to see that the only non-panicking control-flow path is the one that goes through Pin::new_unchecked(&mut pinned), and unreachable_type_constraint is never called, nor is its argument moved. Not having a branch at all is conceptually the most straightforward way to do that, but an unreachable!() inside the if's unreachable branch may do it too; I think its control flow graph representation's only outgoing edge is for panicking, so the unreachable_type_constraint call would properly be unreachable.
|
🚧 Experiment ℹ️ Crater is a tool to run experiments across parts of the Rust ecosystem. Learn more |
|
🎉 Experiment
Footnotes
|
|
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 |
seems good enough to me |
This might already have been adequately addressed, but just to note - the operand of #[allow(unreachable_code)]
'p: {
fn unreachable_type_constraint<'a, T>(_: *mut *mut T) -> Pin<&'a mut T> {
unreachable!()
}
break 'p unsafe { Pin::new_unchecked(&mut pinned) };
// The RHS of `let _ = _` is only coerced if the LHS is given a `: _` type ascription.
// The operand of `&raw mut _` is not coerced.
let mut raw_mut = &raw mut pinned;
// Function arguments are coerced, but we make any non-identity coercions impossible.
unreachable_type_constraint(&raw mut raw_mut)
}Footnotes
|
There was a problem hiding this comment.
In general this feels like something ~T-types should perhaps review. I don't feel like I have the knowledge to say whether this aligns with our inference logic sufficiently well to guarantee the behavior we want is respected. From a libs perspective this seems fine, I'd be happy to r+, but I can't really say whether it accomplishes the goal or not.
(And I guess there's potentially Crater / FCP for the breaking change; it seems likely we want to accept it since it's a soundness fix, but knowing how much would be useful).
In that case, r? types
Crater looked clean last week for the unreachable type constraint idiom currently in the PR; results are at #153457 (comment). If one of the inference-limiting fixes I proposed in #153457 (comment) would be preferable, that'd need a fresh crater run; I imagine they have higher potential for breakage (though it'd be returning to previous behavior, so hopefully not too many people started relying on inference there!). @robofinch's version of the unreachable type constraint in #153457 (comment) probably doesn't break anything more than the current version, but it might warrant a fresh crater run anyway. |
|
@rustbot review |
View all comments
Fixes #153438 using a (hopefully temporary!) typed macro idiom to ensure that when
pin!produces aPin<&mut T>, its argument is of typeT. See #153438 (comment) for my ideas on how this could be changed in the future.