Skip to content

fix Zip unsoundness (again) #141076

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 19, 2025
Merged

Conversation

the8472
Copy link
Member

@the8472 the8472 commented May 16, 2025

Some history: The Zip TrustedRandomAccess specialization has tried to emulate the side-effects of the naive implementation for a long time, including backwards iteration. #82292 tried to fix unsoundness (#82291) in that side-effect-preservation code, but this introduced some panic-safety unsoundness (#86443), but the fix #86452 didn't fix it for nested Zip iterators (#137255).

Rather than piling yet another fix ontop of this heap of fixes this PR reduces the number of cases in which side-effects will be preserved; the necessary API guarantee change was approved in #83791 but we haven't made use of that so far.

fixes #137255

@rustbot
Copy link
Collaborator

rustbot commented May 16, 2025

r? @workingjubilee

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

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels May 16, 2025
@the8472
Copy link
Member Author

the8472 commented May 16, 2025

CC @steffahn

@rust-log-analyzer

This comment has been minimized.

@the8472 the8472 force-pushed the fix-zip-panic-safety2 branch from 1f7a9f5 to 41aca57 Compare May 16, 2025 12:20
@workingjubilee
Copy link
Member

exciting!

Copy link
Member

@workingjubilee workingjubilee left a comment

Choose a reason for hiding this comment

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

Could you either commit these comments or, if they're wrong (and that seems reasonably likely), correct them? The description of the issue so far is... a bit in-the-weeds. Tests are a valuable opportunity to spend time describing the actual issues we are trying to avoid.

});
let b = [1, 2, 3, 4].as_slice().iter().copied();
let c = [5, 6, 7].as_slice().iter().copied();
let ab = zip(a, b);
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
let ab = zip(a, b);
// The actual case we are testing: this Zip gets nested in the next, and
// `a` forces a premature unwind from Zip's iterator-driving code.
let ab = zip(a, b);

@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 3, 2025
@the8472 the8472 force-pushed the fix-zip-panic-safety2 branch from 41aca57 to ec51514 Compare June 4, 2025 23:25
@rustbot

This comment has been minimized.

the8472 added 2 commits June 5, 2025 01:27
Some history: The Zip TrustedRandomAccess specialization has tried
to emulate the side-effects of the naive implementation for a long time,
including backwards iteration. 82292¹ tried to fix unsoundness (82291¹) in that
side-effect-preservation code, but this introduced some panic-safety
unsoundness (86443¹), but the fix 86452¹ didn't fix it for nested Zip
iterators (137255¹).

Rather than piling yet another fix ontop of this heap of fixes this PR reduces
the number of cases in which side-effects will be preserved; the necessary
API guarantee change was approved in 83791¹ but we haven't made use of that
so far.

¹ see merge commit for linkfied issues.
@the8472 the8472 force-pushed the fix-zip-panic-safety2 branch from ec51514 to 61c2c12 Compare June 4, 2025 23:29
@the8472
Copy link
Member Author

the8472 commented Jun 4, 2025

@rustbot ready

The issue is complicated, so I don't think those few suggested comments would a good job, so I tried to add some more instead.

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Jun 4, 2025
@the8472 the8472 requested a review from workingjubilee June 4, 2025 23:30
@the8472
Copy link
Member Author

the8472 commented Jul 15, 2025

@workingjubilee will you take another look at it or should I reroll?

// cases the unsafe code has to handle.
// See #137255 for a case where where too many epicycles lead to unsoundness.
if self.index < self.len {
let old_len = self.len;
Copy link
Member

Choose a reason for hiding this comment

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

We record the original length we are working with...

// which can panic we decrement the counter beforehand
// so that the same index won't be accessed twice, as required by TrustedRandomAccess.
// Additionally this will ensure that the side-effects code won't run a second time.
self.len -= 1;
Copy link
Member

Choose a reason for hiding this comment

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

So now, if we somehow arrive in this code again, old_len will be the lower self.len value, preventing the rerun...

Copy link
Member Author

Choose a reason for hiding this comment

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

yep

Comment on lines +379 to +393
// This condition can and must only be true on the first `next_back` call,
// otherwise we will break the restriction on calls to `self.next_back()`
// after calling `get_unchecked()`.
if sz_a != sz_b && (old_len == sz_a || old_len == sz_b) {
if A::MAY_HAVE_SIDE_EFFECT && sz_a > old_len {
for _ in 0..sz_a - old_len {
self.a.next_back();
}
}
if B::MAY_HAVE_SIDE_EFFECT && sz_b > old_len {
for _ in 0..sz_b - old_len {
self.b.next_back();
}
}
debug_assert_eq!(self.a.size(), self.b.size());
Copy link
Member

Choose a reason for hiding this comment

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

So we're calling next_back a bunch of times just to run side-effects of the closure, which becomes observable. Right?

It feels kinda weird that we potentially next_back a bunch of times here. This allows using next_back to force running side effects that would not be encountered if we only drove using next, right?

Copy link
Member Author

@the8472 the8472 Jul 17, 2025

Choose a reason for hiding this comment

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

The regular, non-TrustedRandomAccess-specialized next_back naturally performs this tail-trimming because it needs to bring the two inner iterators down to the same length (known through ExactSizeIterator) before it can pop off a tail element at the same index.

It's the side-effects of the trimming that we try to emulate here because otherwise it'd look like we're jumping into the middle of the iterator.

Personally I'd prefer if we just did the jumping, but some people like their cute liddle closure side-effects and are sad if they don't get to run.

Copy link
Member

Choose a reason for hiding this comment

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

Hm. It's not obvious to me that this sort of behavior is guaranteed or even expected according to the contract of core::iter::zip. Nonetheless, I suppose this is fine for now, since this is the current behavior.

Copy link
Member Author

Choose a reason for hiding this comment

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

There's just no stable way skip in the middle of an iterator and elide its side-effects. So people might try to reason from its public interface as-if things were implemented by next().

Comment on lines +382 to +389
if sz_a != sz_b && (old_len == sz_a || old_len == sz_b) {
if A::MAY_HAVE_SIDE_EFFECT && sz_a > old_len {
for _ in 0..sz_a - old_len {
self.a.next_back();
}
}
if B::MAY_HAVE_SIDE_EFFECT && sz_b > old_len {
for _ in 0..sz_b - old_len {
Copy link
Member

Choose a reason for hiding this comment

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

The subtlety here that we are catching is that the len of the zip-iter (zipter?) is the lesser of the two sizes.

Copy link
Member Author

Choose a reason for hiding this comment

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

It's hopefully less surprising if one starts with the regular implementation and only then looks at the specialized one.

Copy link
Member

Choose a reason for hiding this comment

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

you mean the default fn implementation?

Copy link
Member Author

Choose a reason for hiding this comment

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

yes

@workingjubilee
Copy link
Member

Alright, sorry for taking so long to review this. It's quite painful to reason about. I'm satisfied with it.

@bors r+

@bors
Copy link
Collaborator

bors commented Jul 18, 2025

📌 Commit 61c2c12 has been approved by workingjubilee

It is now in the queue for this repository.

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jul 18, 2025
bors added a commit that referenced this pull request Jul 19, 2025
Rollup of 10 pull requests

Successful merges:

 - #141076 (fix Zip unsoundness (again))
 - #142444 (adding run-make test to autodiff)
 - #143704 (Be a bit more careful around exotic cycles in in the inliner)
 - #144073 (Don't test panic=unwind in panic_main.rs on Fuchsia)
 - #144083 (miri sleep tests: increase slack)
 - #144092 (bootstrap: Detect musl hosts)
 - #144098 (Do not lint private-in-public for RPITIT)
 - #144103 (Rename `emit_unless` to `emit_unless_delay`)
 - #144108 (Ignore tests/run-make/link-eh-frame-terminator/rmake.rs when cross-compiling)
 - #144115 (fix outdated comment)

r? `@ghost`
`@rustbot` modify labels: rollup
@bors bors merged commit fff53f1 into rust-lang:master Jul 19, 2025
10 checks passed
@rustbot rustbot added this to the 1.90.0 milestone Jul 19, 2025
rust-timer added a commit that referenced this pull request Jul 19, 2025
Rollup merge of #141076 - the8472:fix-zip-panic-safety2, r=workingjubilee

fix Zip unsoundness (again)

Some history: The Zip TrustedRandomAccess specialization has tried to emulate the side-effects of the naive implementation for a long time, including backwards iteration. #82292 tried to fix unsoundness (#82291) in that side-effect-preservation code, but this introduced some panic-safety unsoundness (#86443), but the fix #86452 didn't fix it for nested Zip iterators (#137255).

Rather than piling yet another fix ontop of this heap of fixes this PR reduces the number of cases in which side-effects will be preserved; the necessary API guarantee change was approved in #83791 but we haven't made use of that so far.

fixes #137255
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. 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.

Panic-safety issue with Zip specializations
5 participants