Skip to content
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

Regression between nightlies with -Zpolonius #126520

Open
Nadrieril opened this issue Jun 15, 2024 · 7 comments
Open

Regression between nightlies with -Zpolonius #126520

Nadrieril opened this issue Jun 15, 2024 · 7 comments
Assignees
Labels
A-borrow-checker Area: The borrow checker C-bug Category: This is a bug. NLL-polonius Issues related for using Polonius in the borrow checker requires-nightly This issue requires a nightly compiler in some way. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-types Relevant to the types team, which will review and decide on the PR/issue.

Comments

@Nadrieril
Copy link
Member

Nadrieril commented Jun 15, 2024

Code

I tried this code:

// GAT hack taken from https://docs.rs/lending-iterator/latest/lending_iterator.
pub trait LendingIterator: Sized
where
    Self: for<'item> LendingIteratorItem<'item>,
{
    fn next(&mut self) -> Option<<Self as LendingIteratorItem>::Item>;
}

/// Hack to express a GAT without GATs.
pub trait LendingIteratorItem<'item> {
    type Item;
}

pub struct Wrapper<I> {
    wrapped: Option<I>,
}

impl<'item, I> LendingIteratorItem<'item> for Wrapper<I>
where
    I: LendingIteratorItem<'item>,
{
    type Item = I::Item;
}

impl<I> LendingIterator for Wrapper<I>
where
    I: LendingIterator,
{
    fn next(&mut self) -> Option<<I as LendingIteratorItem>::Item> {
        if let Some(first) = &mut self.wrapped {
            if let Some(next) = first.next() {
                return Some(next);
            } else {
                self.wrapped = None;
            }
        }
        None
    }
}

When running this with -Zpolonius, this worked with nightly-2024-05-31. With nightly-2024-06-01, I get this error message:

error[E0506]: cannot assign to `self.wrapped` because it is borrowed
  --> src/lib.rs:34:17
   |
29 |     fn next(&mut self) -> Option<<I as LendingIteratorItem>::Item> {
   |             - let's call the lifetime of this reference `'1`
30 |         if let Some(first) = &mut self.wrapped {
   |                              ----------------- `self.wrapped` is borrowed here
31 |             if let Some(next) = first.next() {
32 |                 return Some(next);
   |                        ---------- returning this value requires that `self.wrapped` is borrowed for `'1`
33 |             } else {
34 |                 self.wrapped = None;
   |                 ^^^^^^^^^^^^ `self.wrapped` is assigned to here but it was already borrowed

Bisection

searched nightlies: from nightly-2024-05-31 to nightly-2024-06-01
regressed nightly: nightly-2024-06-01
searched commit range: 6f3df08...ada5e2c
regressed commit: ada5e2c

bisected with cargo-bisect-rustc v0.6.8

Host triple: x86_64-unknown-linux-gnu
Reproduce with:

cargo bisect-rustc --start=2024-05-31 --end=2024-06-01

This is likely caused by #125652, cc @amandasystems.

@Nadrieril Nadrieril added C-bug Category: This is a bug. NLL-polonius Issues related for using Polonius in the borrow checker requires-nightly This issue requires a nightly compiler in some way. labels Jun 15, 2024
@rustbot rustbot added the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label Jun 15, 2024
@amandasystems
Copy link
Contributor

Ok that was almost certainly caused by my fix, thanks for finding this. I’ll look into it this week!

@jieyouxu jieyouxu added T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. and removed needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. labels Jun 15, 2024
@fmease fmease added A-borrow-checker Area: The borrow checker T-types Relevant to the types team, which will review and decide on the PR/issue. labels Jun 15, 2024
@lqd
Copy link
Member

lqd commented Jun 16, 2024

Do we know if this an actual regression or a false negative that was "fixed" by #125652? I myself didn't have time to look at the differences in fact generation produced by the PR, nor test cases that could be impacted -- and I don't know of Amanda's own results there.

This looks like the former but -Zpolonius is incomplete and misses errors in some/most/all 1 cases of higher-ranked subtyping, because it'd require interactions with the trait solver. #123720 changes the situation here by lowering incompatible universe constraints to the root universe, so it will also be interesting to check: what happens in the complete 4-case matrix of with/without #125652 + with/without #123720?

And finally, since this pattern is obviously one of our most important use-cases, I'd also be interested in knowing why use the hack to define your lending iterators? Are there other constraints that prevent using regular GATs, or was that maybe a constraint of your own tooling?

Footnotes

  1. rayer les mentions inutiles

@amandasystems
Copy link
Contributor

My understanding (though not very firm) was that #125652 should be a no-op for Polonius facts and I’ll have to look into why it isn’t, so this bug is useful in the sense that it proves this belief wrong.

In general this code path is too complicated and I’d like to clear it up; this seems like a good motivation to do that now.

@Nadrieril
Copy link
Member Author

Nadrieril commented Jun 16, 2024

Do we know if this an actual regression or a false negative that was "fixed" by #125652?

I don't know if this is a valid argument, but I was able to make this code work using polonius_the_crab, which suggests to me that it should be accepted.

And finally, since this pattern is obviously one of our most important use-cases, I'd also be interested in knowing why use the hack to define your lending iterators?

Yep, I can't use GATs because a well-known limitation: I want to define a trait alias

pub trait TypeWalker:
    Sized + LendingIterator + for<'item> LendingIterator<Item<'item> = (&'item mut dyn Any, Event)>
{}
impl<T> TypeWalker for T
where
    T: LendingIterator,
    T: for<'item> LendingIterator<Item<'item> = (&'item mut dyn Any, Event)>,
{}

but if I use real GATs this bound forces Self: 'static, which is undesireable for my use case.

@amandasystems
Copy link
Contributor

First things first, I guess:

@rustbot claim

I have looked into this a bit, and the code I reactivated in #125652 now seems deeply suspicious to me. I'll continue investigations this week.

This looks like the former but -Zpolonius is incomplete and misses errors in some/most/all cases of higher-ranked subtyping, because it'd require interactions with the trait solver. #123720 changes the situation here by lowering incompatible universe constraints to the root universe, so it will also be interesting to check: what happens in the complete 4-case matrix of with/without #125652 + with/without #123720?

The results are in!

Polonius --> NLL No Polonius --> NLL
Higher-kinded constraints lowered Fails Compiles
Higher-kinded constraints not lowered Fails Compiles

Specifically, removing the call to polonius::add_drop_of_var_derefs_origin(self.cx.typeck, dropped_local, &kind); when adding drop facts from Polonius using add_drop_live_facts_for() is enough to address the issue.

@danielhenrymantilla
Copy link
Contributor

danielhenrymantilla commented Oct 31, 2024

Heh, polonius-the-crab does not run into this because of the way it phrases the properties using that higher-order partitioning function. And "enforces" it through unsafe code.

I do, however, have a "soundness test", wherein I delegate a proof of soundness of my API to -Zpolonius, by disabling that unsafe code and making sure cargo rustc -- -Zpolonius passes. And now that I look at it, the test has been regressing since around the time of this issue, it looks like (good catch @Nadrieril!).

I have produced a smaller repro, at the very least: https://rust.godbolt.org/z/bb6n7b8Yx:

trait ForLt { type Of<'__>; }

fn polonius<T, R: ForLt>(
    input: &mut T,
    f: impl FnOnce(&mut T) -> Option<R::Of<'_>>,
) -> Result<R::Of<'_>, &mut T> {
    if let Some(r) = f(input) {
        Ok(r)
    } else {
        Err(input)
    }
}

Do we know if this an actual regression or a false negative that was "fixed"

This is indeed an important question!

I cannot answer that, but someone here mentioned the word "drop", so I decided to try and rewrite this very snippet in a way that would show the Option<R::Of<'_>> being "provably dropped" before the "polonius borrow-shortening logic" were needed:

trait ForLt { type Of<'__>; }

fn polonius<T, R: ForLt>(
    input: &mut T,
    f: impl FnOnce(&mut T) -> Option<R::Of<'_>>,
) -> Result<R::Of<'_>, &mut T> {
    if let Some(r) = f(input) {
        return Ok(r);
    }
    // else { drop(f_input) }
    Err(input)
}

and that one works! So it just looks like the logic is a bit "too" scared of the drop of None::<Option<R::Of<'_>>>, which could be legitimate for other cases which might be too difficult for the compiler to distinguish? 🤷

@amandasystems
Copy link
Contributor

it just looks like the logic is a bit "too" scared of the drop of None::<Option<R::Of<'_>>>, which could be legitimate for other cases which might be too difficult for the compiler to distinguish?

This checks out; I think me connecting a few loose pipes I found in #125652 must have caused too much drop-liveness logic to be applied. I'm very tempted to remove all of it but I'll have to look into it a bit more. So far we don't have a counterexample that shows unsoundness without the extra drop handling, but that doesn't mean one isn't hiding out there somewhere.

Anyway, thanks for the smaller repro, that one will be a lot easier to work on.

danielhenrymantilla added a commit to danielhenrymantilla/polonius-the-crab.rs that referenced this issue Nov 8, 2024
Fixes the "future-proofing CI" failures caused by a "false positive regression" in nightly rustc's semantics of `-Zpolonius`.

This crate tries to justify its usage of `unsafe` by offering a way to disable it when `-Zpolonius` is used, _via_ its Cargo `--feature polonius`.

Indeed, when `-Zpolonius` is being used, there is no need to used any of the API of this crate, which, in fact, includes the very implementation of this crate. So we do precisely that, thereby avoiding the need for any `unsafe`, and thus "proving" that our branch API is sound, _even beyond a `-Zpolonius` context_, since it is proven to be sound there.

However, around June, the semantics of `-Zpolonius` changed a bit in `rustc` (nightly), and this non-`unsafe`-`-Zpolonius`-vetted implementation was actually being rejected by it.

There is, in fact, an issue about this very thing:

  - rust-lang/rust#126520

It is unclear whether the rejection is fully legitimate; in this case, and also in the minimization I wrote on that issue, it is a false positive: in the non-dependent branch, there is nothing _dependent_ being dropped "after" our `return` "starts". Still, it may be difficult for the compiler maintainers to properly identify and distinguish this legitimate case from one where the drop could have been problematic (in such cases, the change of `-Zpolonius` semantics was actually a soundness fix!).

We thus decide to just work around this tiny "false positive regression" issue by making sure our "drop" of the remainders of the non-dependent value (which happen to be a silly empty ZST (`Placeholder`)) clearly happen, syntactically, before our re-access to the initially (re)borrowed value occurs, when we "start" our `return`.
danielhenrymantilla added a commit to danielhenrymantilla/polonius-the-crab.rs that referenced this issue Nov 8, 2024
Fixes the "future-proofing CI" failures caused by a "false positive
regression" in nightly rustc's semantics of `-Zpolonius`.

This crate tries to justify its usage of `unsafe` by offering a way to
disable it when `-Zpolonius` is used, _via_ its Cargo `--feature
polonius`.

Indeed, when `-Zpolonius` is being used, there is no need to used any of
the API of this crate, which, in fact, includes the very implementation
of this crate. So we do precisely that, thereby avoiding the need for
any `unsafe`, and thus "proving" that our branch API is sound, _even
beyond a `-Zpolonius` context_, since it is proven to be sound there.

However, around June, the semantics of `-Zpolonius` changed a bit in
`rustc` (nightly), and this non-`unsafe`-`-Zpolonius`-vetted
implementation was actually being rejected by it.

There is, in fact, an issue about this very thing:

  - rust-lang/rust#126520

It is unclear whether the rejection is fully legitimate; in this case,
and also in the minimization I wrote on that issue, it is a false
positive: in the non-dependent branch, there is nothing _dependent_
being dropped "after" our `return` "starts". Still, it may be difficult
for the compiler maintainer(s) working on this areä to properly identify
and distinguish this legitimate case from one where the drop could have
been problematic (in such cases, the change of `-Zpolonius` semantics
was actually a soundness fix!).

We thus decide to just work around this tiny "false positive regression"
issue by making sure our "drop" of the remainders of the non-dependent
value (which happen to be a silly empty ZST (`Placeholder`)) clearly
happen, syntactically, before our re-access to the initially
(re)borrowed value occurs, when we "start" our `return`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-borrow-checker Area: The borrow checker C-bug Category: This is a bug. NLL-polonius Issues related for using Polonius in the borrow checker requires-nightly This issue requires a nightly compiler in some way. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-types Relevant to the types team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

7 participants