-
-
Notifications
You must be signed in to change notification settings - Fork 14.7k
iter::Fuse is unsound with how specialization currently behaves around HRTB fn pointers #85863
Copy link
Copy link
Closed
Labels
A-iteratorsArea: IteratorsArea: IteratorsA-lifetimesArea: Lifetimes / regionsArea: Lifetimes / regionsA-specializationArea: Trait impl specializationArea: Trait impl specializationA-trait-systemArea: Trait systemArea: Trait systemA-type-systemArea: Type systemArea: Type systemC-bugCategory: This is a bug.Category: This is a bug.I-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/SoundnessIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/SoundnessP-criticalCritical priorityCritical priorityT-compilerRelevant to the compiler team, which will review and decide on the PR/issue.Relevant to the compiler team, which will review and decide on the PR/issue.T-langRelevant to the language teamRelevant to the language teamT-libsRelevant to the library team, which will review and decide on the PR/issue.Relevant to the library team, which will review and decide on the PR/issue.T-libs-apiRelevant to the library API team, which will review and decide on the PR/issue.Relevant to the library API team, which will review and decide on the PR/issue.
Metadata
Metadata
Assignees
Labels
A-iteratorsArea: IteratorsArea: IteratorsA-lifetimesArea: Lifetimes / regionsArea: Lifetimes / regionsA-specializationArea: Trait impl specializationArea: Trait impl specializationA-trait-systemArea: Trait systemArea: Trait systemA-type-systemArea: Type systemArea: Type systemC-bugCategory: This is a bug.Category: This is a bug.I-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/SoundnessIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/SoundnessP-criticalCritical priorityCritical priorityT-compilerRelevant to the compiler team, which will review and decide on the PR/issue.Relevant to the compiler team, which will review and decide on the PR/issue.T-langRelevant to the language teamRelevant to the language teamT-libsRelevant to the library team, which will review and decide on the PR/issue.Relevant to the library team, which will review and decide on the PR/issue.T-libs-apiRelevant to the library API team, which will review and decide on the PR/issue.Relevant to the library API team, which will review and decide on the PR/issue.
Type
Fields
Give feedbackNo fields configured for issues without a type.
New high-level explanation of this issue, also covering #85873
Fuse<T>andZip<T, U>have optimizations relying on specialization if their type parameters implement a trait (FusedIteratororTrustedRandomAccess, respectively).These optimizations fundamentally change the way the iterator operates.
All type arguments are covariant. Coercing e.g.
Fuse<T>toFuse<U>ifUis a subtype ofTcan “switch between” these fundamentally different ways of operation ifT: !FusedIteratorandU: FusedIteratorwhich can bring the iterator into an invalid state that can cause UB; the same kind of problem exists forZip.For
Zip, this problem can be avoided ifTrustedRandomAccessnever differs between types and subtypes.Copy-dependent impls for e.g.vec::IntoIterhave to be removed (PR #85874).Regarding
Fuse:FusedIteratoris a safe public trait, this is problematic. Some possible fixes: Either changeFuseto not get into a UB-causing invalid state by such a coercion (which kills some (or perhaps most) of the optimization offered byFuse), or makeFuseinvariant (but that’s a breaking change!). Here’s another possible approach, but this one requires new language features and could be done in the future if an immediate fix that just makesFuseless performant is taken now.Specialization not differentiating between e.g.
Foo<'a>andFoo<'b>made this issue harder to spot.But
fn(&())(that is,for<'a> fn(&'a ())) is asupertype[edited:] subtype offn(&'static ())and those are entirely different types.Previous start of issue description
(playground)
@rustbot label T-libs, T-libs-impl, T-compiler, A-specialization, A-iterators, A-typesystem, A-lifetimes, A-traits, C-bug
someone please add the unsound labelthanksExplanation
The types
for<'a> fn(&'a ())(i.e.fn(&())) andfn(&'static ())do seem to be distinguished by rust’s specialization. (I.e. the difference between the two types is not erased before the trait impl for specializing onFusedIteratoris chosen.) But it’s also possible to convertFuse<MyEmptyIterator<fn(&i32)>>intoFuse<MyEmptyIterator<fn(&'static i32)>>. This way, the first call to.next()will use unspecialized code and write aNonevalue into the field ofFuse, whereas the second call will use the specialized version that usesintrinsics::unreachable()to unwrap the containedOption.I haven’t tested it, but I’d guess thatthe same thing might be possible using HRTB trait objects instead of function pointers. (See comment below.)Possible fixes
This soundness issue would probably go away if, somehow, specialization would also consider types such as
fn(&())andfn(&'static ())to be “the same type”. Edit2: I don’t think that this approach is reasonable, and it’s probably even impossible.The easiest / fastest fix for now would probably be to remove all the
intrinsics::unreachable()assertions in the implementation ofFuseand replace them either withpanics (which results in unexpected panicking but at least no unsoundness), or replace them with code that “properly” handles theNonecase as the iterator being empty. In either case, performance benefits fromFusedIteratorimplementations could probably somewhat be retained by somehow marking theSomecase as the hot path and theNonecase as a cold path. (In the approach with theNonecase panicking, this would probably be automatically considered the cold path due to the panic.)Edit: Yet another alternative fix is to make
Fuse<T>invariant overT.