Repository navigation
unsoundness relating to WF requirements on trait object types #44454
Description
Activity
- addedI-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/SoundnessT-langRelevant to the language teamRelevant to the language team
on Sep 9, 2017 Actually, thinking about this more, the problem is maybe not quite how I was thinking of it. That is, it seems like the problem lies in the definition of object safety. That is, if you imagine that you had to write the impl for
Animal<X>by hand, it would look like:impl<X> Animal<X> for Animal<X> { ... }
and this would fail the well-formedness checks because
Animal<X>: 'staticdoes not hold, at least not without relying on the impl we are writing =).The goal of the object safety rules are to ensure such an impl could be written.
Maybe the rules of object safety have to be more conditional -- i.e.,
Animal<X>is object safe ifX: 'static.Here is an actual unsoundness, using
std::any::Any:
https://play.rust-lang.org/?gist=1385ed4e051354b305ea2d2413f8724f&version=stable- changed the title
[-]*possible* unsoundness relating to WF requirements on trait types[/-][+]*possible* unsoundness relating to WF requirements on trait object types[/+]on Sep 10, 2017 - changed the title
[-]*possible* unsoundness relating to WF requirements on trait object types[/-][+]unsoundness relating to WF requirements on trait object types[/+]on Sep 10, 2017 This looks similar to #27675 - in both cases we have a trait object that is not WF.
I think a reasonable way to solve this would be to have a trait object predicate depend on a
WF(Self)predicate, so we basically have the rule'λ₀...'λₙ lifetimes τ₀...τₙ types Trait object-safe ~~WF(for<...> Trait<'λ₀...'λₙ, τ₀...τₙ>) - this part is new~~ WF(for<...> Trait<'λ₀...'λₙ, τ₀...τₙ>: Trait<'λ₀...'λₙ, τ₀...τₙ>) - this part is new ---------------- for<...> Trait<'λ₀...'λₙ, τ₀...τₙ>: for<...> Trait<'λ₀...'λₙ, τ₀...τₙ>
The "this part is new" part is actually more subtle than I realized.
Object types (aka
dyn Trait<'λ₀...'λₙ, τ₀...τₙ>, to avoid confusion) are actually a funny way of writingexists<σ> (σ, for<...> WF(dyn Trait<'λ₀...'λₙ, τ₀...τₙ>: Trait<'λ₀...'λₙ, τ₀...τₙ>) → σ: Trait<'λ₀...'λₙ, τ₀...τₙ>)And the idea is that if the trait is object-safe, we can write a "compiler impl"
impl<'λ₀...'λₙ, τ₀...τₙ> Traitl<'λ₀...'λₙ, τ₀...τₙ> for dyn Trait<'λ₀...'λₙ, τ₀...τₙ> { // ... }
If we want to write such an impl, we need the where-clauses on the trait (both associated types and others) to hold. For supertraits, this follows by obvious induction. For other constraints, this is something that won't hold by itself.
Therefore, we probably need to add these other predicates as conditions on the impl, and have
impl<'λ₀...'λₙ, τ₀...τₙ> Trait<'λ₀...'λₙ, τ₀...τₙ> for dyn Trait<'λ₀...'λₙ, τ₀...τₙ> where WF(Trait<'λ₀...'λₙ, τ₀...τₙ>: dyn Trait<'λ₀...'λₙ, τ₀...τₙ>) { // ... }
I agree that it is the same as #27675. As we said on IRC, I think that the current fix is roughly as I described here -- in particular, when we check that
dyn Trait<P...>: Trait<P...>, we need to also check that the where-clauses declared onTraithold fordyn Trait<P...>. This subsumes some of the object safety checks (e.g. the search forwhere Self: Sized).I agree that it is the same as #27675.
Shall we close this as a dupe, then?
ping @arielb1 -- whatever happened with your branch here? I remember we had some discussions about cycles and don't recall how (if at all) they were resolved.
21 remaining items
searched nightlies: from nightly-2020-01-01 to nightly-2022-11-30
regressed nightly: nightly-2022-08-10
searched commit range: f03ce30...34a6cae
regressed commit: 63e4312bisected with cargo-bisect-rustc v0.6.4
Host triple: x86_64-unknown-linux-gnu
Reproduce with:cargo bisect-rustc --access github --regress error
so it was fixed by #99217
it looks like the original unsoundness was fixed before #99217, it seems like that stopped compiling in 1.61. @Nilstrieb can I get you to bisect this one as well?
use std::any::Any; trait Animal<X>: 'static { } trait Projector { type Foo; } impl<X> Projector for dyn Animal<X> { type Foo = X; } fn make_static<'a, T>(t: &'a T) -> &'static T { let x: <dyn Animal<&'a T> as Projector>::Foo = t; let any = generic::<dyn Animal<&'a T>, &'a T>(x); any.downcast_ref::<&'static T>().unwrap() } fn generic<T: Projector + Animal<U> + ?Sized, U>(x: <T as Projector>::Foo) -> Box<dyn Any> { make_static_any(x) } fn make_static_any<U: 'static>(u: U) -> Box<dyn Any> { Box::new(u) } fn main() { let a = make_static(&"salut".to_string()); println!("{}", *a); }
I got
Regression in nightly-2022-03-16, which maps to2022-03-14UTC: Auto merge of #94929 - flip1995:clippyup, r=Manishearth 2022-03-14UTC: Auto merge of #94935 - matthiaskrgr:rollup-2o2kyz6, r=matthiaskrgr 2022-03-15UTC: Auto merge of #92285 - compiler-errors:dyn-proj-bounds, r=nikomatsakis 2022-03-15UTC: Auto merge of #94584 - pnkfelix:inject-use-suggestion-sites, r=ekuber 2022-03-15UTC: Auto merge of #94938 - lnicola:rust-analyzer-2022-03-14, r=lnicola 2022-03-15UTC: Auto merge of #94261 - michaelwoerister:debuginfo-types-refactor, r=wesleywiser 2022-03-15UTC: Auto merge of #94928 - lcnr:inline-as_substs, r=michaelwoerister 2022-03-15UTC: Auto merge of #94966 - matthiaskrgr:rollup-iqzswh3, r=matthiaskrgr 2022-03-15UTC: Auto merge of #94973 - GuillaumeGomez:more-gui-tests, r=notriddleUnfortunately, there is no CI builds anymore for this range, and I do not have the power to bisect those commit myself now.
Reproduce with
cargo bisect-rustc --access=github --end 1.61.0 -- checkI'm guessing it was @compiler-errors' #92285
- removedE-needs-bisectionCall for participation: This issue needs bisection: https://github.com/rust-lang/cargo-bisect-rustcCall for participation: This issue needs bisection: https://github.com/rust-lang/cargo-bisect-rustcE-needs-mcveCall for participation: This issue has a repro, but needs a Minimal Complete and Verifiable ExampleCall for participation: This issue has a repro, but needs a Minimal Complete and Verifiable Example
on Dec 1, 2022 that's cool, looks like we only need the add ui tests and can then close this issue 🎉
Reacted by Yuki Okushi- addedA-trait-systemArea: Trait systemArea: Trait systemand removedA-trait-systemArea: Trait systemArea: Trait system
on Dec 21, 2024
So @scalexm pointed me at this curious example:
There's a lot going on here, but the key point is that, in the call to
foo::<Animal<&'a i32>, &'a i32>, we assume thatAnimal<&'a i32>: Animal<&'a i32>. This is reasonable, since it's a trait object type -- however, it is a malformed trait object type, and hence one where no impl could ever exist (i.e., we could never make an instance of this type). Interestingly, we are not smart enough to extend this to the case where the'staticbound is applied directly (hence the commented out call tobaz::<Animal<&'a i32>>.At the very least, this is inconsistent behavior, but I feel like there is an unsoundness lurking here. What makes me nervous is that we say that a projection type
<P0 as Trait<P1...Pn>>::Foooutlives'aifPi: 'afor alli. I am worried that you could use an impl like this:to prove then that
<Animal<&'a i32> as Projector>::Foooutlives'static(say). I have not yet quite managed to weaponize this, but I got pretty close -- that's a gist in which we invoke a functionis_static<U: 'static>(u: U)whereUwill be of type&'a i32when monomophized. If rustc were as smart as chalk, I suspect we could easily craft a transmute of some kind to&'static i32. There may be a way to do it within current rustc too.I'm not sure what's the best fix here. I suspect we should require that trait object types meet the WF requirements declared on the trait; and yet, I think the reason we did not is because we don't know the
Selftype, which means we can't fully validate that. =) This also feels a mite inconsistent with implied bounds. (Perhaps most likely is that we could tweak the WF rules for trait objects to specifically target lifetime bounds, but I have to think about that, feels .. hacky?)We could tweak the notion of what type parameters are constrained -- or at least which may appear in a projection. e.g., we could disallow that impl of
ProjectorforAnimal<X>, becauseXwould not be considered "sufficiently" constrained to appear intype Foo = .... Backwards incompatible, obviously, and feels unfortunate.This causes actual unsoundness, as discovered by @scalexm in #44454 (comment);
cc @arielb1 @aturon