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

Item bounds can reference self projections and still be object safe #122804

Conversation

compiler-errors
Copy link
Member

@compiler-errors compiler-errors commented Mar 21, 2024

Background

Currently, we have some interesting rules about where Self is allowed to be mentioned in objects. Specifically, we allow mentioning Self behind associated types (e.g. fn foo(&self) -> Self::Assoc) only if that Self type comes from the trait we're defining or its supertraits:

trait Foo {
  fn good() -> Self::Assoc; // GOOD :)

  fn bad() -> <Self as OtherTrait>::Assoc; // BAD!
}

And more specifically, these Self::Assoc projections are only allowed to show up in:

  • (A1) Method signatures
  • (A2) Where clauses on traits, GATs and methods

But Self::Assoc projections are not allowed to show up in:

  • (B1) Supertrait bounds (specifically: all super-predicates, which includes the projections that come from elaboration, and not just the traits themselves).
  • (B2) Item bounds of associated types

The reason for (B1) is interesting: specifically, it arises from the fact that we currently eagerly elaborate all projection predicates into the object, so if we had the following code:

trait Sub<Assoc = Self::SuperAssoc> {}
trait Super {
    type SuperAssoc;
}

Then given dyn Sub<SuperAssoc = i32> we would need to have a type that is substituted into itself an infinite number of times1, like dyn Sub<SuperAssoc = i32, Assoc = <dyn Sub<SuperAssoc = i32, Assoc = <dyn Sub<SuperAssoc = i32, Assoc = <... as Super>::SuperAssoc> as Super>::SuperAssoc> as Super>::SuperAssoc>, i.e. the fixed-point of: type T = dyn Sub<SuperAssoc = i32, Assoc = <T as Super>::SuperAssoc>.

Similarly for (B2), we restrict mentioning Self::Assoc in associated type item bounds, which is the cause for #122798. However, there is no reason for us to do so, since item bounds never show up structurally in the dyn Trait object type.

What?

This PR relaxes the check for item bounds so that Self may be mentioned behind associated types in the same cases that they currently work for method signatures (A1) and where clauses (A2).

Why?

Fixes #122798. Removes a subtle and confusing inconsistency for the code mentioned in that issue.

This is sound because we only assemble alias bounds for rigid projections, and all projections coming from an object self type are not rigid, since all associated types should be specified by the type.

This is also desirable because we can do this via supertraits already. In #122789, it is noted that an item bound of Eq already works, just not PartialEq because of the default item bound. This is weird and should be fixed.

Future work

We could make the check for Self in super-predicates more sophisticated as well, only erroring if Self shows up in a projection super-predicate.

Footnotes

  1. This could be fixed by some sort of structural replacement or eager normalization, but I don't think it's necessary currently.

@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. labels Mar 21, 2024
@workingjubilee workingjubilee added T-types Relevant to the types team, which will review and decide on the PR/issue. and removed T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Mar 21, 2024
Copy link
Member

@BoxyUwU BoxyUwU left a comment

Choose a reason for hiding this comment

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

would like to have an fcp for this tho. probably should mention about the fact that you can already kind of do this via supertraits, and also that we only assemble alias bound candidates for rigid aliases and <dyn Trait as Trait>::Assoc is never going to be a rigid alias so the item bounds shouldn't affect the trait object

@BoxyUwU BoxyUwU added needs-fcp This change is insta-stable, so needs a completed FCP to proceed. 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 Apr 1, 2024
@compiler-errors
Copy link
Member Author

Ya, I realized during discussion I should probably explain exactly why this is sound, specifically discussing the way we do Object candidate confirmation, etc. I'll write that up.

@bors
Copy link
Contributor

bors commented May 12, 2024

☔ The latest upstream changes (presumably #125001) made this pull request unmergeable. Please resolve the merge conflicts.

@compiler-errors
Copy link
Member Author

This is ready I believe :3

@rfcbot fcp merge

@rfcbot
Copy link

rfcbot commented May 21, 2024

Team member @compiler-errors has proposed to merge this. The next step is review by the rest of the tagged team members:

No concerns currently listed.

Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!

See this document for info about what commands tagged team members can give me.

@rfcbot rfcbot added proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. labels May 21, 2024
@compiler-errors compiler-errors removed the needs-fcp This change is insta-stable, so needs a completed FCP to proceed. label May 21, 2024
@nikomatsakis
Copy link
Contributor

@rfcbot reviewed

@rfcbot rfcbot added final-comment-period In the final comment period and will be merged soon unless new substantive objections are raised. and removed proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. labels May 24, 2024
@rfcbot
Copy link

rfcbot commented May 24, 2024

🔔 This is now entering its final comment period, as per the review above. 🔔

@rfcbot rfcbot added finished-final-comment-period The final comment period is finished for this PR / Issue. and removed final-comment-period In the final comment period and will be merged soon unless new substantive objections are raised. labels Jun 3, 2024
@rfcbot
Copy link

rfcbot commented Jun 3, 2024

The final comment period, with a disposition to merge, as per the review above, is now complete.

As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed.

This will be merged soon.

@rfcbot rfcbot added the to-announce Announce this issue on triage meeting label Jun 3, 2024
@BoxyUwU
Copy link
Member

BoxyUwU commented Jun 3, 2024

@bors r+

@bors
Copy link
Contributor

bors commented Jun 3, 2024

📌 Commit 1dcf764 has been approved by BoxyUwU

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-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Jun 3, 2024
Noratrieb added a commit to Noratrieb/rust that referenced this pull request Jun 4, 2024
…ference-self, r=BoxyUwU

Item bounds can reference self projections and still be object safe

### Background

Currently, we have some interesting rules about where `Self` is allowed to be mentioned in objects. Specifically, we allow mentioning `Self` behind associated types (e.g. `fn foo(&self) -> Self::Assoc`) only if that `Self` type comes from the trait we're defining or its supertraits:

```
trait Foo {
  fn good() -> Self::Assoc; // GOOD :)

  fn bad() -> <Self as OtherTrait>::Assoc; // BAD!
}
```

And more specifically, these `Self::Assoc` projections are *only* allowed to show up in:
  * (A1) Method signatures
  * (A2) Where clauses on traits, GATs and methods

But `Self::Assoc` projections are **not** allowed to show up in:
  * (B1) Supertrait bounds (specifically: all *super-predicates*, which includes the projections that come from elaboration, and not just the traits themselves).
  * (B2) Item bounds of associated types

The reason for (B1) is interesting: specifically, it arises from the fact that we currently eagerly elaborate all projection predicates into the object, so if we had the following code:

```
trait Sub<Assoc = Self::SuperAssoc> {}
trait Super {
    type SuperAssoc;
}
```

Then given `dyn Sub<SuperAssoc = i32>` we would need to have a type that is substituted into itself an infinite number of times[^1], like `dyn Sub<SuperAssoc = i32, Assoc = <dyn Sub<SuperAssoc = i32, Assoc = <dyn Sub<SuperAssoc = i32, Assoc = <... as Super>::SuperAssoc> as Super>::SuperAssoc> as Super>::SuperAssoc>`, i.e. the fixed-point of: `type T = dyn Sub<SuperAssoc = i32, Assoc = <T as Super>::SuperAssoc>`.

Similarly for (B2), we restrict mentioning `Self::Assoc` in associated type item bounds, which is the cause for rust-lang#122798. However, there is **no reason** for us to do so, since item bounds never show up structurally in the `dyn Trait` object type.

#### What?

This PR relaxes the check for item bounds so that `Self` may be mentioned behind associated types in the same cases that they currently work for method signatures (A1) and where clauses (A2).

#### Why?

Fixes rust-lang#122798. Removes a subtle and confusing inconsistency for the code mentioned in that issue.

This is sound because we only assemble alias bounds for rigid projections, and all projections coming from an object self type are not rigid, since all associated types should be specified by the type.

This is also desirable because we can do this via supertraits already. In rust-lang#122789, it is noted that an item bound of `Eq` already works, just not `PartialEq` because of the default item bound. This is weird and should be fixed.

#### Future work

We could make the check for `Self` in super-predicates more sophisticated as well, only erroring if `Self` shows up in a projection super-predicate.

[^1]: This could be fixed by some sort of structural replacement or eager normalization, but I don't think it's necessary currently.
Noratrieb added a commit to Noratrieb/rust that referenced this pull request Jun 4, 2024
…ference-self, r=BoxyUwU

Item bounds can reference self projections and still be object safe

### Background

Currently, we have some interesting rules about where `Self` is allowed to be mentioned in objects. Specifically, we allow mentioning `Self` behind associated types (e.g. `fn foo(&self) -> Self::Assoc`) only if that `Self` type comes from the trait we're defining or its supertraits:

```
trait Foo {
  fn good() -> Self::Assoc; // GOOD :)

  fn bad() -> <Self as OtherTrait>::Assoc; // BAD!
}
```

And more specifically, these `Self::Assoc` projections are *only* allowed to show up in:
  * (A1) Method signatures
  * (A2) Where clauses on traits, GATs and methods

But `Self::Assoc` projections are **not** allowed to show up in:
  * (B1) Supertrait bounds (specifically: all *super-predicates*, which includes the projections that come from elaboration, and not just the traits themselves).
  * (B2) Item bounds of associated types

The reason for (B1) is interesting: specifically, it arises from the fact that we currently eagerly elaborate all projection predicates into the object, so if we had the following code:

```
trait Sub<Assoc = Self::SuperAssoc> {}
trait Super {
    type SuperAssoc;
}
```

Then given `dyn Sub<SuperAssoc = i32>` we would need to have a type that is substituted into itself an infinite number of times[^1], like `dyn Sub<SuperAssoc = i32, Assoc = <dyn Sub<SuperAssoc = i32, Assoc = <dyn Sub<SuperAssoc = i32, Assoc = <... as Super>::SuperAssoc> as Super>::SuperAssoc> as Super>::SuperAssoc>`, i.e. the fixed-point of: `type T = dyn Sub<SuperAssoc = i32, Assoc = <T as Super>::SuperAssoc>`.

Similarly for (B2), we restrict mentioning `Self::Assoc` in associated type item bounds, which is the cause for rust-lang#122798. However, there is **no reason** for us to do so, since item bounds never show up structurally in the `dyn Trait` object type.

#### What?

This PR relaxes the check for item bounds so that `Self` may be mentioned behind associated types in the same cases that they currently work for method signatures (A1) and where clauses (A2).

#### Why?

Fixes rust-lang#122798. Removes a subtle and confusing inconsistency for the code mentioned in that issue.

This is sound because we only assemble alias bounds for rigid projections, and all projections coming from an object self type are not rigid, since all associated types should be specified by the type.

This is also desirable because we can do this via supertraits already. In rust-lang#122789, it is noted that an item bound of `Eq` already works, just not `PartialEq` because of the default item bound. This is weird and should be fixed.

#### Future work

We could make the check for `Self` in super-predicates more sophisticated as well, only erroring if `Self` shows up in a projection super-predicate.

[^1]: This could be fixed by some sort of structural replacement or eager normalization, but I don't think it's necessary currently.
bors added a commit to rust-lang-ci/rust that referenced this pull request Jun 4, 2024
Rollup of 9 pull requests

Successful merges:

 - rust-lang#122804 (Item bounds can reference self projections and still be object safe)
 - rust-lang#124486 (Add tracking issue and unstable book page for `"vectorcall"` ABI)
 - rust-lang#125504 (Change pedantically incorrect OnceCell/OnceLock wording)
 - rust-lang#125608 (Avoid follow-up errors if the number of generic parameters already doesn't match)
 - rust-lang#125690 (ARM Target Docs Update)
 - rust-lang#125750 (Align `Term` methods with `GenericArg` methods, add `Term::expect_*`)
 - rust-lang#125818 (Handle no values cfgs with `--print=check-cfg`)
 - rust-lang#125909 (rustdoc: add a regression test for a former blanket impl synthesis ICE)
 - rust-lang#125919 (Remove stray "this")

r? `@ghost`
`@rustbot` modify labels: rollup
@jieyouxu
Copy link
Member

jieyouxu commented Jun 4, 2024

Failed in #125956 (comment)
@bors r-

@bors bors 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-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. labels Jun 4, 2024
@lqd
Copy link
Member

lqd commented Jun 4, 2024

hehe, race condition, I was writing the same rollup failure message that this PR needed a rebase

bors added a commit to rust-lang-ci/rust that referenced this pull request Jun 6, 2024
…unsoundness, r=<try>

Fix supertrait associated type unsoundness

This is built on top of rust-lang#122804 though that's really not related, it's just easier to make this modification with the changes to the object safety code that I did in that PR. The only thing is that PR may make this unsoundness slightly easier to abuse, since there are more positions that allow self-associated-types.

This PR only looks for supertrait associated types *without* normalizing. I'm gonna crater this initially -- preferably we don't need to normalize unless there's a lot of breakage. I assume that most people are writing `Self::Assoc` so they don't really care about the trait ref being normalized, so somewhat tempted to believe that this will just work out and we can extend it later if needed.

r? lcnr
@apiraino apiraino removed the to-announce Announce this issue on triage meeting label Jun 7, 2024
@bors
Copy link
Contributor

bors commented Jun 13, 2024

☔ The latest upstream changes (presumably #126374) made this pull request unmergeable. Please resolve the merge conflicts.

@BoxyUwU
Copy link
Member

BoxyUwU commented Jun 27, 2024

@compiler-errors needs a rebase :-)

@compiler-errors
Copy link
Member Author

@BoxyUwU @lcnr: y'all have a strong opinion about if #126090 should land before this?

I discovered an unsoundness in (EXISTING!) object safety that this PR technically makes easier to exploit with this change. I kinda don't think it matters to wait for that PR, but I did want to mention it.

@BoxyUwU
Copy link
Member

BoxyUwU commented Jul 13, 2024

I would be perhaps a little worried about the other PR landing then causing more regressions than expected and it getting reverted. That'd mean this PR would wind up making the problem A Bit worse on a long term basis potentially although I imagine we'd wind up with a fcw in that scenario 🤷‍♀️. I think trying to hit that unsoundness is also pretty hard in practice, I remember trying to find an example that'd hit it a few times and didn't manage to. I don't think it matters to land that PR before this one, we should just go ahead and merge this imo

@lcnr
Copy link
Contributor

lcnr commented Jul 15, 2024

Given that #126090 should be in FCP soon and feels quite straight forward, I think we should wait until it's through FCP and then merge both PRs at once (as that the fix builds on this one).

matthiaskrgr added a commit to matthiaskrgr/rust that referenced this pull request Jul 25, 2024
…y-unsoundness, r=lcnr

Fix supertrait associated type unsoundness

### What?

Object safety allows us to name `Self::Assoc` associated types in certain positions if they come from our trait or one of our supertraits. When this check was implemented, I think it failed to consider that supertraits can have different args, and it was only checking def-id equality.

This is problematic, since we can sneak different implementations in by implementing `Supertrait<NotActuallyTheSupertraitSubsts>` for a `dyn` type. This can be used to implement an unsound transmute function. See the committed test.

### How do we fix it?

We consider the whole trait ref when checking for supertraits. Right now, this is implemented using equality *without* normalization. We erase regions since those don't affect trait selection.

This is a limitation that could theoretically affect code that should be accepted, but doesn't matter in practice -- there are 0 crater regression. We could make this check stronger, but I would be worried about cycle issues. I assume that most people are writing `Self::Assoc` so they don't really care about the trait ref being normalized.

---

### What is up w the stacked commit

This is built on top of rust-lang#122804 though that's really not related, it's just easier to make this modification with the changes to the object safety code that I did in that PR. The only thing is that PR may make this unsoundness slightly easier to abuse, since there are more positions that allow self-associated-types -- I am happy to stall that change until this PR merges.

---

Fixes rust-lang#126079

r? lcnr
rust-timer added a commit to rust-lang-ci/rust that referenced this pull request Jul 26, 2024
Rollup merge of rust-lang#126090 - compiler-errors:supertrait-assoc-ty-unsoundness, r=lcnr

Fix supertrait associated type unsoundness

### What?

Object safety allows us to name `Self::Assoc` associated types in certain positions if they come from our trait or one of our supertraits. When this check was implemented, I think it failed to consider that supertraits can have different args, and it was only checking def-id equality.

This is problematic, since we can sneak different implementations in by implementing `Supertrait<NotActuallyTheSupertraitSubsts>` for a `dyn` type. This can be used to implement an unsound transmute function. See the committed test.

### How do we fix it?

We consider the whole trait ref when checking for supertraits. Right now, this is implemented using equality *without* normalization. We erase regions since those don't affect trait selection.

This is a limitation that could theoretically affect code that should be accepted, but doesn't matter in practice -- there are 0 crater regression. We could make this check stronger, but I would be worried about cycle issues. I assume that most people are writing `Self::Assoc` so they don't really care about the trait ref being normalized.

---

### What is up w the stacked commit

This is built on top of rust-lang#122804 though that's really not related, it's just easier to make this modification with the changes to the object safety code that I did in that PR. The only thing is that PR may make this unsoundness slightly easier to abuse, since there are more positions that allow self-associated-types -- I am happy to stall that change until this PR merges.

---

Fixes rust-lang#126079

r? lcnr
@compiler-errors
Copy link
Member Author

This was merged with #126090

github-actions bot pushed a commit to rust-lang/miri that referenced this pull request Jul 26, 2024
…ness, r=lcnr

Fix supertrait associated type unsoundness

### What?

Object safety allows us to name `Self::Assoc` associated types in certain positions if they come from our trait or one of our supertraits. When this check was implemented, I think it failed to consider that supertraits can have different args, and it was only checking def-id equality.

This is problematic, since we can sneak different implementations in by implementing `Supertrait<NotActuallyTheSupertraitSubsts>` for a `dyn` type. This can be used to implement an unsound transmute function. See the committed test.

### How do we fix it?

We consider the whole trait ref when checking for supertraits. Right now, this is implemented using equality *without* normalization. We erase regions since those don't affect trait selection.

This is a limitation that could theoretically affect code that should be accepted, but doesn't matter in practice -- there are 0 crater regression. We could make this check stronger, but I would be worried about cycle issues. I assume that most people are writing `Self::Assoc` so they don't really care about the trait ref being normalized.

---

### What is up w the stacked commit

This is built on top of rust-lang/rust#122804 though that's really not related, it's just easier to make this modification with the changes to the object safety code that I did in that PR. The only thing is that PR may make this unsoundness slightly easier to abuse, since there are more positions that allow self-associated-types -- I am happy to stall that change until this PR merges.

---

Fixes #126079

r? lcnr
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. finished-final-comment-period The final comment period is finished for this PR / Issue. S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-types Relevant to the types team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

object-safety removed with type AssocType: PartialEq