-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
Coherence can be bypassed by an indirect impl for a trait object #57893
Comments
@arielb1 Interesting. I guess we would need a deeper check, something along the lines of: impl<T> Object for SomeType<T> where WC {
/* ... */
}
|
Sure. What makes this non-trivial is auto-traits ( impl<T> Object<'a> for SomeType<T> where WC Then the "ideal" lowering would be something of this format:
If you ignore binders in unification (i.e., you consider
While I don't think this is insurmountable, it is fairly annoying and places some restrictions on how you encode auto-traits. |
I'm interested in working on this. |
It turns out that only one trait is necessary to reproduce this: (playground) trait Object {
type Output;
}
impl<T: ?Sized> Object for T {
type Output = &'static u64;
}
fn foo<'a, T: ?Sized>(x: <T as Object>::Output) -> &'a u64 {
x
}
fn transmute_lifetime<'a, 'b>(x: &'a u64) -> &'b u64 {
foo::<dyn Object<Output=&'a u64>>(x)
}
// And yes this is a genuine `transmute_lifetime`!
fn get_dangling<'a>() -> &'a u64 {
let x = 0;
transmute_lifetime(&x)
}
fn main() {
let r = get_dangling();
println!("{}", r);
} This seems quite bad, as simply writing a blanket impl is enough to expose the issue. |
One way to fix this issue would be the following: If a trait has any associated items, and a blanket impl exists for it, that trait cannot be object-safe. This is pretty extreme - however, the only other solution I can think of would be to deny all blanket impls of a trait with associated items - which seems even worse, given that the trait might not have even been object-safe to begin with. |
Unfortunately, it gets worse. Here's an implementation of trait Object<U> {
type Output;
}
impl<T: ?Sized, U> Object<U> for T {
type Output = U;
}
fn foo<T: ?Sized, U>(x: <T as Object<U>>::Output) -> U {
x
}
fn transmute<T, U>(x: T) -> U {
foo::<dyn Object<U, Output = T>, U>(x)
} I think blame should be assigned to not normalizing I checked the code above with godbolt and the problem has existed since Rust 1.0.0. |
The first comments in the thread seem to indicate that the problem is instead with the blanket impl itself being accepted by the compiler. If I understood correctly, @Centril's impl above can be applied to |
Seems like a good idea to discuss in language and compiler team, nominating. |
Notably, in my reproducer above, if you change fn foo<T: ?Sized + Object<U>, U>(x: <T as Object<U>>::Output) -> U {
x
} then you will get:
|
Is that not just due to where-clauses having precedence over impls? AFAIK trait selection will prefer the |
I made a first attempt at a fix here: https://github.com/Aaron1011/rust/tree/fix/trait-obj-coherence My idea was to extend the obligations generated by
This is combeind with an extra flag in Unfortunately, I wasn't able to get this working, due to how the well-formed predicate generation is integrated with the rest of the compiler. I would need to significantly refactor I'm hoping that someone can come up with a better approach. However, any solution will need to deal with the fact that |
This is interesting! The fact that I wouldn't be sad if we would fix this by being less aggressive about exploiting coherence during type-checking -- that makes analysis much more complicated, and this example shows why. But there's likely already tons of code out there that exploits this. Another fix is to refuse to create trait objects that "contradict" the kind of coherence knowledge that the type checker might exploit, but that seems extremely fragile. @Aaron1011 your plan seems to be to encode in the type of Once again, exploiting assumptions implicitly instead of inferring them to an explicit form proves to be an issue. This reminds me that we still implicitly exploit WF everywhere instead of turning that into explicit assumptions... |
triage: P-high due to unsoundness. Leaving nominated in hope of discussing today. Not assigning to anyone yet. |
assigning to @nikomatsakis with expectation that they will delegate. (and removing nomination label) |
Still nominated for t-lang. |
I am not sure that giving up on coherence is the right choice here. I think it is too easy to split the code into functions, such that no one function sees the coherence violation: struct Data<T: ?Sized, U> where T: Object<U> {
data: <T as Object<U>>::Output
}
trait Object<U> {
type Output;
}
trait Mark {
type Output;
}
impl<T: ?Sized, U: Mark<Output=V>, V> Object<U> for T {
type Output = V;
}
fn iso_1<T, U>(data: T) -> Data<dyn Object<U, Output=T>, U> {
// in any coherence-less solution, this shouldn't "see" the
// blanket impl, as it might not apply (e.g., `Local` might
// be in a third-party crate).
Data { data }
}
fn iso_2<X: ?Sized, U, V>(data: Data<X, U>) -> V
where U: Mark<Output=V>
{
// similarly, this shouldn't "see" the trait-object impl.
data.data
}
fn transmute_m<T, U, V>(data: T) -> V
where U: Mark<Output=V>
{
// This function *also* shouldn't see the blanket impl - `Local`
// might be in a third-party crate.
iso_2::<dyn Object<U, Output=T>, U, V>(
iso_1::<T, U>(data)
)
}
// These 3 functions could be in a child crate
struct Local<T>(T);
impl<T> Mark for Local<T> {
type Output = T;
}
fn transmute<T, U>(x: T) -> U {
// and this function shouldn't see *anything* that looks like a
// problematic associated type.
transmute_m::<_, Local<_>, _>(x)
} |
I think that conditioning object safety on an object type being coherent is a reasonable-enough way out of this. If we want to be tricky, we might put the condition "directly" on the impl - i.e., have |
@arielb1 your |
I thought I'd just note that versions of the "erased trait" pattern may rely on the specialization discussed in this ticket. +// relevant diff from the blog post
+ impl<'data, Item> AsyncIter for dyn ErasedAsyncIter<Item = Item> + 'data {
+ type Item = Item;
+ type Next<'me> = Pin<Box<dyn Future<Output = Option<Item>> + 'me>>
+ where
+ Item: 'me,
+ 'data: 'me,
+ ;
+
+ fn next(&mut self) -> Self::Next<'_> {
+ self.next()
+ }
+ }
impl<T> ErasedAsyncIter for T
where
- T: AsyncIter,
+ T: AsyncIter + ?Sized, |
In today's @rust-lang/types meeting, we discussed this issue and decided we need a deep-dive covering the various details, examples, and attempts that have been made to fix it. Filed rust-lang/types-team#100 to cover that. |
…yn, r=lcnr Don't resolve generic impls that may be shadowed by dyn built-in impls **NOTE:** This is a hack. This is not trying to be a general fix for the issue that we've allowed overlapping built-in trait object impls and user-written impls for quite a long time, and traits like `Any` rely on this (rust-lang#57893) -- this PR specifically aims to mitigate a new unsoundness that is uncovered by the MIR inliner (rust-lang#114928) that interacts with this pre-existing issue. Builtin `dyn Trait` impls may overlap with user-provided blanket impls (`impl<T: ?Sized> Trait for T`) in generic contexts. This leads to bugs when instances are resolved in polymorphic contexts, since we typically prefer object candidates over impl candidates. This PR implements a (hacky) heuristic to `resolve_associated_item` to account for that unfortunate hole in the type system -- we now bail with ambiguity if we try to resolve a non-rigid instance whose self type is not known to be sized. This makes sure we can still inline instances like `impl<T: Sized> Trait for T`, which can never overlap with `dyn Trait`'s built-in impl, but we avoid inlining an impl that may be shadowed by a `dyn Trait`. Fixes rust-lang#114928
…-dyn, r=lcnr Don't resolve generic impls that may be shadowed by dyn built-in impls **NOTE:** This is a hack. This is not trying to be a general fix for the issue that we've allowed overlapping built-in trait object impls and user-written impls for quite a long time, and traits like `Any` rely on this (rust-lang#57893) -- this PR specifically aims to mitigate a new unsoundness that is uncovered by the MIR inliner (rust-lang#114928) that interacts with this pre-existing issue. Builtin `dyn Trait` impls may overlap with user-provided blanket impls (`impl<T: ?Sized> Trait for T`) in generic contexts. This leads to bugs when instances are resolved in polymorphic contexts, since we typically prefer object candidates over impl candidates. This PR implements a (hacky) heuristic to `resolve_associated_item` to account for that unfortunate hole in the type system -- we now bail with ambiguity if we try to resolve a non-rigid instance whose self type is not known to be sized. This makes sure we can still inline instances like `impl<T: Sized> Trait for T`, which can never overlap with `dyn Trait`'s built-in impl, but we avoid inlining an impl that may be shadowed by a `dyn Trait`. Fixes rust-lang#114928
Rollup merge of rust-lang#114941 - compiler-errors:inline-shadowed-by-dyn, r=lcnr Don't resolve generic impls that may be shadowed by dyn built-in impls **NOTE:** This is a hack. This is not trying to be a general fix for the issue that we've allowed overlapping built-in trait object impls and user-written impls for quite a long time, and traits like `Any` rely on this (rust-lang#57893) -- this PR specifically aims to mitigate a new unsoundness that is uncovered by the MIR inliner (rust-lang#114928) that interacts with this pre-existing issue. Builtin `dyn Trait` impls may overlap with user-provided blanket impls (`impl<T: ?Sized> Trait for T`) in generic contexts. This leads to bugs when instances are resolved in polymorphic contexts, since we typically prefer object candidates over impl candidates. This PR implements a (hacky) heuristic to `resolve_associated_item` to account for that unfortunate hole in the type system -- we now bail with ambiguity if we try to resolve a non-rigid instance whose self type is not known to be sized. This makes sure we can still inline instances like `impl<T: Sized> Trait for T`, which can never overlap with `dyn Trait`'s built-in impl, but we avoid inlining an impl that may be shadowed by a `dyn Trait`. Fixes rust-lang#114928
Given these facts, might it be justifiable, as a soundness fix, to add a One wrinkle is that downstream impls of |
This solution is unnecessarily restrictive. The problem is not the dyn-safety of the trait, the problem is the conflict between the blanket and compiler-generated trait impls. Suppressing the latter is sufficient to resolve the incoherence. For example, the trait below could be fully object safe: trait Foo {
type Assoc;
}
impl<T: ?Sized> Foo for T {
type Assoc = i32;
} |
(HRTBs complicate the story somewhat. Given |
I tried that, and it's not really great. |
Hmm, looking at those regressions, perhaps edition-dependent name resolution offers us a way out. Potential plan:
I think that would fix the vast majority of the crater regressions. |
Document behavior of `<dyn Any as Any>::type_id()` See also rust-lang#57893 `@rustbot` label A-docs T-libs
Rollup merge of rust-lang#118028 - Jules-Bertholet:dyn-any-doc, r=thomcc Document behavior of `<dyn Any as Any>::type_id()` See also rust-lang#57893 `@rustbot` label A-docs T-libs
Another possible solution:
|
…pl, r=<try> Reject blanket object impls that are possibly incoherent wrt associated types I would like to make this test more sophisticated. Namely, we should plug the unconstrained associated types of the object type with placeholders, and then detect cases where the placeholders *don't* end up being what the blanket impl would have predicted. In that case, we know that we can use a `dyn Trait` to abuse the unsoundness in rust-lang#57893. However, first I'd like to see what the most naïve fallout of this is. r? `@ghost`
(already linked above via back-link) FYI the reproducer for this miscompile ICEs Miri because said reproducer currently compiles to invalid MIR: #127667 |
this pattern also affects the impl of trait ToString {
type Assoc;
}
trait Display {}
impl<T: ?Sized + Display> ToString for T {
type Assoc = usize;
}
trait Super: ToString + Display {}
fn via_display<T: ?Sized + Display>() -> <T as ToString>::Assoc {
0
}
fn dyn_trait() -> u32 {
*via_display::<dyn Super<Assoc = Box<u32>>>()
}
fn main() {
println!("{}", dyn_trait());
} however, we only get unsoundness if the affected trait has an associated type. |
Comments
The check for manual
impl Object for Object
only makes sure there is no directimpl Object for dyn Object
- it does not consider such indirect impls. Therefore, you can write a blanketimpl<T: ?Sized> Object for T
that conflicts with the builtinimpl Object for dyn Object
.Reproducer
edit: minimal reproducer from #57893 (comment)
I had some difficulties with getting the standard "incoherence ICE" reproducer, because the object candidate supersedes the impl candidate in selection. So here's a "transmute_lifetime" reproducer.
Duplicates, see also
The text was updated successfully, but these errors were encountered: