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

Coherence can be bypassed by an indirect impl for a trait object #57893

Open
arielb1 opened this issue Jan 25, 2019 · 47 comments
Open

Coherence can be bypassed by an indirect impl for a trait object #57893

arielb1 opened this issue Jan 25, 2019 · 47 comments

Comments

@arielb1
Copy link
Contributor

@arielb1 arielb1 commented Jan 25, 2019

Comments

The check for manual impl Object for Object only makes sure there is no direct impl Object for dyn Object - it does not consider such indirect impls. Therefore, you can write a blanket impl<T: ?Sized> Object for T that conflicts with the builtin impl Object for dyn Object.

Reproducer

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.

trait Object {
    type Output;
}

trait Marker<'b> {}
impl<'b> Marker<'b> for dyn Object<Output=&'b u64> {}

impl<'b, T: ?Sized + Marker<'b>> Object for T {
    type Output = &'static u64;
}

fn foo<'a, 'b, T: Marker<'b> + ?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);
}
@scalexm
Copy link
Member

@scalexm scalexm commented Jan 31, 2019

@arielb1 Interesting. I guess we would need a deeper check, something along the lines of:

impl<T> Object for SomeType<T> where WC {
/* ... */
}
// If the following goal has a non-empty set of solutions, reject the impl.
exists<T> {
    exists<U> {
        Unify(dyn Object<Output = U>, SomeType<T>),
        dyn Object<Output = U>: WC
    }
}

cc @nikomatsakis

@arielb1
Copy link
Contributor Author

@arielb1 arielb1 commented Feb 2, 2019

Sure.

What makes this non-trivial is auto-traits (dyn Object + Send is a distinct wrt. unification from dyn Object), and to some extent binders (unless you ignore them in unification). i.e., supposing the impl you have is

impl<T> Object<'a> for SomeType<T> where WC

Then the "ideal" lowering would be something of this format:

// If the following goal has a non-empty set of solutions, reject the impl.
exists<T: type, 'a('x): lifetime -> lifetime, U('x): lifetime -> type, AT: list of auto-traits> [
    Unify(dyn for<'s> Object<'a('s), Output = U('s)> + AT, SomeType<T>),
    dyn for<'s> Object<'a('s), Output = U('s)> + AT: WC
}

If you ignore binders in unification (i.e., you consider for<'a> Object<'a, Output=&'a T> to be the same as Object<'x, Output=&'y T> in coherence), then you don't have to deal with the HRTB problem, but you still have to deal with auto-traits:

// If the following goal has a non-empty set of solutions, reject the impl.
exists<T: type, 'a: lifetime, U: type, AT: list of auto-traits> [
    Unify(dyn Object<'a, Output = U> + AT, SomeType<T>),
    dyn Object<'a, Output = U> + AT: WC
}

While I don't think this is insurmountable, it is fairly annoying and places some restrictions on how you encode auto-traits.

@Aaron1011
Copy link
Member

@Aaron1011 Aaron1011 commented May 30, 2019

I'm interested in working on this.

@Aaron1011
Copy link
Member

@Aaron1011 Aaron1011 commented Jun 9, 2019

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.

@Aaron1011
Copy link
Member

@Aaron1011 Aaron1011 commented Jun 9, 2019

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.

@Centril
Copy link
Contributor

@Centril Centril commented Jun 9, 2019

Unfortunately, it gets worse. Here's an implementation of mem::transmute in safe Rust:

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 <T as Object<U>>::Output to U in fn foo. If that happened, foo::<dyn Object<U, Output = T>, U>(x) would not type-check.

I checked the code above with godbolt and the problem has existed since Rust 1.0.0.

@jonas-schievink
Copy link
Member

@jonas-schievink jonas-schievink commented Jun 9, 2019

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 dyn Object<U, Output=T> for any U and T, and defines Output=U. However, a trait object like dyn Object<U, Output=T> already provides an implicit impl with the same input types, but a different output type, which is incoherent.

@jonas-schievink
Copy link
Member

@jonas-schievink jonas-schievink commented Jun 9, 2019

Seems like a good idea to discuss in language and compiler team, nominating.

@Centril
Copy link
Contributor

@Centril Centril commented Jun 9, 2019

Notably, in my reproducer above, if you change fn foo to:

fn foo<T: ?Sized + Object<U>, U>(x: <T as Object<U>>::Output) -> U {
    x
}

then you will get:


error[E0308]: mismatched types
  --> src/lib.rs:10:5
   |
9  | fn foo<T: ?Sized + Object<U>, U>(x: <T as Object<U>>::Output) -> U {
   |                                                                  - expected `U` because of return type
10 |     x
   |     ^ expected type parameter, found associated type
   |
   = note: expected type `U`
              found type `<T as Object<U>>::Output`

@jonas-schievink
Copy link
Member

@jonas-schievink jonas-schievink commented Jun 9, 2019

Is that not just due to where-clauses having precedence over impls? AFAIK trait selection will prefer the Object<U> impl in the bounds, which doesn't have an assoc. type specified, so the compiler can't normalize it.

@Aaron1011
Copy link
Member

@Aaron1011 Aaron1011 commented Jun 10, 2019

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 ty::wf::obligations to include projection predicates of the form:

<dyn MyTrait<type Assoc=T> as MyTrait>::Assoc = T.

This is combeind with an extra flag in SelectionContext to force impl candidates to be chosen over object candidates. In the case of an incoherent blanket impl (e.g. any of the reproduces in this thread), <dyn MyTrait<type Assoc=T> as MyTrait>::Assoc should project to the type specified in the blanket impl. This will fail to unify with T, correctly causing a compilation error.

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 FulfillmentContext, SelectionContext, and/or Predicatein order to keep track of the extra flag that needs to be passed toSelectionContext`.

I'm hoping that someone can come up with a better approach. However, any solution will need to deal with the fact that SelectionContext masks attempts to detect the incoherence by discarding the impl candidate in favor of the object candidate.

@RalfJung
Copy link
Member

@RalfJung RalfJung commented Jun 10, 2019

This is interesting! The fact that foo in @Centril's example even gets accepted shows that we aggressively exploit coherence during type-checking: because we saw an impl that uses a certain associated type, we type-check assuming that that is the impl that got used here. Unfortunately, as the example here shows, that is in conflict with the "implicit impl" that we get for trait objects.

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 foo the fact that we exploited coherence? However this "exploitation" happens in the body of the function, so what exactly gets exploited cannot be known yet when computing the well-formedness predicate for foo. Thus I guess what you want to / have to do is to aggressively look for all things one might be able to exploit up-front, add them all to the well-formedness obligations, and then during type-checking the boy look only at the obligations and not exploit coherence any more (as doing so would introduce scary bugs if the two systems for getting such type equalities out of coherence would ever not agree).

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...

@pnkfelix
Copy link
Member

@pnkfelix pnkfelix commented Jun 13, 2019

triage: P-high due to unsoundness. Leaving nominated in hope of discussing today. Not assigning to anyone yet.

@pnkfelix
Copy link
Member

@pnkfelix pnkfelix commented Jun 13, 2019

assigning to @nikomatsakis with expectation that they will delegate. (and removing nomination label)

@Centril
Copy link
Contributor

@Centril Centril commented Jun 13, 2019

Still nominated for t-lang.

@arielb1
Copy link
Contributor Author

@arielb1 arielb1 commented Jun 26, 2019

@RalfJung

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)
}

@arielb1
Copy link
Contributor Author

@arielb1 arielb1 commented Jun 26, 2019

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 ObjectCandidate not apply if an ImplCandidate might "theoretically" apply to any substitution of this type. This would however require doing coherence-style negative reasoning during selection, which would be ugly.

@RalfJung
Copy link
Member

@RalfJung RalfJung commented Jun 26, 2019

@arielb1 your iso_2 exploits coherence, so "giving up on exploiting coherence" would not let that function type-check.

@Aaron1011
Copy link
Member

@Aaron1011 Aaron1011 commented Oct 25, 2019

@nikomatsakis: What would be the migration path for any crates with traits similar to Borrow or BorrowMut? I'm concerned that a specialization-based approach would leave such crates with no path forward, other than waiting on the stabilization of specialization.

@nikomatsakis
Copy link
Contributor

@nikomatsakis nikomatsakis commented Oct 28, 2019

I was thinking about this more over the weekend. @Aaron1011, in answer to your question, it seems obvious we're going to have to manage the timing of our fix here pretty carefully. For one thing, we've talked about stabilizing some subset of specialization, and maybe we should try to execute on that before we make any hard errors here.

One thing that's worth pointing out -- the only thing that users need to be able to do is to declare individual items as default (i.e., eligible to be specialized). They don't need to write the specializing impl themselves.

Leaving the timing question aside, I had some further thoughts.

First, we don't want to unilaterally introduce a impl Foo for dyn Foo impl, clearly -- for example, this doesn't make sense for traits that are not object safe. So you could imagine some logic like:

  • If trait is object safe, introduce the impl Foo for dyn Foo impl.

However, this is kind of a mess. In particular, you may not have meant for your trait to be used as a dyn Trait -- for example, I'm not sure we anticipated dyn Borrow, although there are a few users -- but now you may be getting coherence errors due to that dyn Foo impl that you never asked for.

This comes back to the original mistake (or so I view it now) of having traits implicitly become object safe if they can be. In retrospect, I think I would've preferred a design where users "opted in" to having a trait be dyn-compatible, perhaps by writing

dyn trait Iterator { .. .}

or something like that. It would then be a hard-error if the trait were not dyn compatible. If we had that design, then it would be fairly natural that dyn trait Iterator also introduces an impl of Iterator for dyn Iterator. In fact, since we are moving to a world where dyn Iterator is always a valid type, even if Iterator is not dyn-compatible, you can view dyn trait Iterator as a kind of "convenient short-hand" where the compiler generates the impl for you, but in theory you could write it yourself (as we discussed in a lang-team meeting). This obviously ignores the fact that you don't have the primitives you need to write that impl (i.e., to do virtual dispatch), but that is something we could in principle correct.

Of course, requiring dyn trait opt-in would not be a compatible change at this point. It could in principle be done across an Edition boundary, although the "warning" would be a bit tricky (in particular, we can only suggest adding dyn to the trait if we see the trait used as a dyn value locally, we can't know about all downstream uses).

One thing we could do is something like this:

  • We enable dyn trait declarations
    • in such a case, we generate an impl and check the coherence/specialization rules I suggested
    • we also require the existing rules be satisfied
  • For backwards compatibility, in Rust 2018 and before, a trait can still be implicitly dyn compatible, but only if there is no overlapping impl (i.e., the check I implemented here)
    • if there is an overlapping impl, then we still create the object candidate -- but we error out when you create a trait object
  • in Rust 2021, we require dyn trait declarations

Note this last point: this is not the logic that exists on the branch. My thought here is that the "drop the object candidate" logic has been shown to change the semantics of existing code (i.e., the Any branch), so merely making such a change would be bad.

This change means that if people want to use dyn Foo for some trait Foo that has an overlapping impl, they have to explicitly declare the trait to be dyn trait Foo, in which case the coherence rules apply.

(To be clear, I think this change requires an RFC -- but it may make sense to adapt my branch such that I can create a PR and test the impact.)

bors added a commit that referenced this issue Nov 2, 2019
…3, r=<try>

[wip] degenerate object safety check for crater

The purpose of this PR is just to be able to do a crater run to assess the impact of the proposed fix for #57893.

With this fix:

* traits that are implemented for unsized types are not dyn safe
* unless they are marked `#[rustc_dyn]` (this would eventually be `dyn trait`)

r? @nikomatsakis  -- this doesn't need review, won't land in this form
bors added a commit that referenced this issue Nov 12, 2019
…3, r=<try>

[wip] degenerate object safety check for crater

The purpose of this PR is just to be able to do a crater run to assess the impact of the proposed fix for #57893.

With this fix:

* traits that are implemented for unsized types are not dyn safe
* unless they are marked `#[rustc_dyn]` (this would eventually be `dyn trait`)

r? @nikomatsakis  -- this doesn't need review, won't land in this form
@nikomatsakis
Copy link
Contributor

@nikomatsakis nikomatsakis commented Nov 22, 2019

So I've been distracted here. I think that the crater results here appear to be promising, but there is still a bit more analysis needed. Those results show that relatively few craters are affected, but what I didn't figure out yet is what kind of fix is needed for those crates. In particular, part of the fix I've proposed leans on specialization, and it'd be good to know if those crates would require default declarations in order to be compatible.

The answer is definitely yes for some of them, but maybe not all. It's worth doing the investigation. To do so, we basically want to look at the logs and see if there is indeed an impl.

So far I just took a quick look at the axiom 1.0 crate. It's actually raises an interesting point -- here are its crater logs. What we can see that there is a warning issues with the text "impl_potentially_overlapping_dyn_trait", indicating that we found an impl that potentially overlaps with the dyn ActorMessage (per these rules). However, [that impl requires other traits, like Serialize, and of course dyn ActorMessage: Serialize would not hold. However, if we look to the future, we would have to consider the possibility of trait objects like dyn (ActorMessage + Serialize) -- and such a trait object naturally could match against that impl. So I think that this is indeed a case where the correct impl would require a default declaration, so that it can be overridden by the (compiler-supplied) impl for dyn ActorMessage.

To track the progress of the investigation, I created a hackmd. I'm going to try and leave some notes in crates I've looked at, and if others want to pitch in, please feel free to do so!

bors added a commit that referenced this issue Jan 14, 2020
…3, r=<try>

[wip] degenerate object safety check for crater

The purpose of this PR is just to be able to do a crater run to assess the impact of the proposed fix for #57893.

With this fix:

* traits that are implemented for unsized types are not dyn safe
* unless they are marked `#[rustc_dyn]` (this would eventually be `dyn trait`)

r? @nikomatsakis  -- this doesn't need review, won't land in this form
@nikomatsakis
Copy link
Contributor

@nikomatsakis nikomatsakis commented Jan 14, 2020

We held a lang-team design meeting (minutes and video available here) where we discussed this issue in depth.

After the meeting, I realized that one simpler fix (at least for now) would be to revoke "dyn safety" if:

  • there is a potentially dyn-overlapping impl and
  • the trait has associated items that are not fns (@cramertj pointed out that associated types which require where Self: Sized could be permitted)

I've implemented this in PR #66037 and it is being tested on crater.

bors added a commit that referenced this issue Jan 19, 2020
…3, r=<try>

[wip] degenerate object safety check for crater

The purpose of this PR is just to be able to do a crater run to assess the impact of the proposed fix for #57893.

With this fix:

* traits that are implemented for unsized types are not dyn safe
* unless they are marked `#[rustc_dyn]` (this would eventually be `dyn trait`)

r? @nikomatsakis  -- this doesn't need review, won't land in this form
bors added a commit that referenced this issue Jan 22, 2020
…3, r=<try>

[wip] degenerate object safety check for crater

The purpose of this PR is just to be able to do a crater run to assess the impact of the proposed fix for #57893.

With this fix:

* traits that are implemented for unsized types are not dyn safe
* unless they are marked `#[rustc_dyn]` (this would eventually be `dyn trait`)

r? @nikomatsakis  -- this doesn't need review, won't land in this form
bors added a commit that referenced this issue Jan 23, 2020
…3, r=<try>

[wip] degenerate object safety check for crater

The purpose of this PR is just to be able to do a crater run to assess the impact of the proposed fix for #57893.

With this fix:

* traits that are implemented for unsized types are not dyn safe
* unless they are marked `#[rustc_dyn]` (this would eventually be `dyn trait`)

r? @nikomatsakis  -- this doesn't need review, won't land in this form
bors added a commit that referenced this issue Jan 23, 2020
…3, r=<try>

[wip] degenerate object safety check for crater

The purpose of this PR is just to be able to do a crater run to assess the impact of the proposed fix for #57893.

With this fix:

* traits that are implemented for unsized types are not dyn safe
* unless they are marked `#[rustc_dyn]` (this would eventually be `dyn trait`)

r? @nikomatsakis  -- this doesn't need review, won't land in this form
bors added a commit that referenced this issue Jan 27, 2020
…3, r=<try>

[wip] degenerate object safety check for crater

The purpose of this PR is just to be able to do a crater run to assess the impact of the proposed fix for #57893.

With this fix:

* traits that are implemented for unsized types are not dyn safe
* unless they are marked `#[rustc_dyn]` (this would eventually be `dyn trait`)

r? @nikomatsakis  -- this doesn't need review, won't land in this form
@withoutboats
Copy link
Contributor

@withoutboats withoutboats commented Jan 28, 2020

I just wanted to mention since its not highlighted in the notes that this intersects with #67562. The proposal here is to make the compiler treat the Any impl as specializable by the compiler generated trait object impl. There are several variations on this proposal, but we should remember: we cannot allow end users to specialize Any without making Any an unsafe trait, because the safety of the code in std::any depends on the invariant that the TypeId returned by Any's method is the correct TypeId for the erased type (currently upheld through by virtue of the sole unspecializable impl of Any which covers all possible instantiations).

Any of these solutions are sufficient:

  • Don't let end users specialize these impls the compiler forces to be treated as "default" for sound resolution.
  • Mark Any as something like default(crate) (downstream users cannot specialize)
  • Mark Any as an unsafe crate.

The first two are both mentioned in the document and it sounded like the lang team was in favor of both. My impression of #67562 is that the libs team would prefer not to see Any specializable by end users, and to keep its unsafety encapsulated in the any module.

@nikomatsakis
Copy link
Contributor

@nikomatsakis nikomatsakis commented Jun 11, 2020

I'm dumping here some notes from the lang team dropbox paper:

  • No progress

  • Relevant issue: rust-lang/rust#57893

  • Relevant PR: rust-lang/rust#66037

  • What the PR implemented:

    • Making dyn Trait not dyn safe if:
      • there is a dyn overlapping impl and
      • the trait has associated non-functions
        • that do not have where Self: Sized (not implemented)
  • Crater impact virtually negligible, but the 1 item we did identify is interesting

    • has to do with the [TryStream](https://docs.rs/futures/0.3.1/futures/stream/trait.TryStream.html) trait, which is used roughly like this:
      trait StreamX {
      type Item;
      }

    trait TryStreamX: StreamX {
    type Ok;
    type Error;
    }

    impl<S, T, E> TryStreamX for S
    where S: ?Sized + StreamX<Item = Result<T, E>>
    {
    type Ok = T;
    type Error = E;
    }

  • the crate in question has a dyn TryStream which is an error now

    • Why? The concern is that S in the above impl might (in the future) become
      • dyn TryStreamX<Ok = A, Error = B> + StreamX<Item = Result<C, D>>
    • in which case type Ok would be A from the default impl but C from the impl above
  • I’m not sure if there is some path forward apart from breaking dyn TryStream, which is a bit of a shame

    • note that a trait alias is really what we want here
  • other alternative that I haven’t tried to implement yet, but which I discussed before

    • in a dyn-overlapping impl, all associated items must be default
      • and compiler (for backwards compatibility) will add the default implicitly
        • (but user would not be permitted to specialize with their own impls, as boats noted)
      • this is to reflect that the compiler introduces its own impl
    • but impact on TryStream would be pretty severe here, I think
      • you wouldn’t be able to take T: Stream<Item = Result<...>>
      • you would not be able to do T::Ok
  • impact of existing PR

    • you cannot use dyn TryStream but you can do fn foo<T: TryStream<..>>()>
    • you could write dyn Stream<Item = ..>
  • really this code wants to be a trait alias

    • This would require trait aliases to support associated types, but that seems feasible
    • Trait alias semantics: “A Stream of Result<T, E> can be called a TryStream, where the associated types Ok and Error refer to T and E respectively”
  • next step:

    • revisit the soundness hole and see if we can “weaponize” the TryStream example in a similar way
    • revisit the write-up covering the various options before us:
      • i.e., this change is modifying what dyn safety means, has very slight impact
        • concerns about the fact that “whether trait X is dyn safe depends on its impls”
      • other options might be introducing default, but this has potentially broader impact

@joshtriplett
Copy link
Member

@joshtriplett joshtriplett commented Sep 28, 2020

I'm un-tagging this as P-high. In practice, it is not actually P-high. We'd like to see someone work on this, but leaving it perpetually tagged P-high is not helping with that.

@steffahn
Copy link
Contributor

@steffahn steffahn commented Jan 7, 2021

I got some more example code in #80783, which I closed as it’s a duplicate.

I guess they aren’t any worse (as in “more unsafe”) than the examples that we already have in this thread.

@steffahn
Copy link
Contributor

@steffahn steffahn commented Jan 7, 2021

@nikomatsakis

  • revisit the soundness hole and see if we can “weaponize” the TryStream example in a similar way

It surely can:

use futures::{Stream, TryStream};

fn transmute<B, A>(x: B) -> A {
    foo::<dyn TryStream<Ok = B, Error = (), Item = Result<A, ()>>, A, B>(x)
}

fn foo<S: ?Sized,  A, B>(x: <S as TryStream>::Ok) -> A
where
    S: Stream<Item = Result<A, ()>>,
{
    x
}

static X: u8 = 0;
fn main() {
    let x = transmute::<&u8, &[u8; 1000000]>(&X);
    println!("{:?}", x);
}

(playground)


Edit:

Apparently, not even changing it to

pub trait TryStream:
    Stream<Item = Result<<Self as TryStream>::Ok, <Self as TryStream>::Error>>

changes anything o.O
(playground)

@steffahn
Copy link
Contributor

@steffahn steffahn commented Jan 7, 2021

Apparently, not even changing it to

pub trait TryStream:
    Stream<Item = Result<<Self as TryStream>::Ok, <Self as TryStream>::Error>>

changes anything o.O

Which means that we can get unsafety without any dyn-overlapping implementation:

trait SuperTrait {
    type A;
    type B;
}

trait Trait: SuperTrait<A = <Self as SuperTrait>::B> {}

fn transmute<A, B>(x: A) -> B {
    foo::<A, B, dyn Trait<A = A, B = B>>(x)
}

fn foo<A, B, T: ?Sized>(x: T::A) -> B
where
    T: Trait<B = B>,
{
    x
}

static X: u8 = 0;
fn main() {
    let x = transmute::<&u8, &[u8; 1000000]>(&X);
    println!("{:?}", x);
}

(in case this isn’t known yet, it might be worth opening another issue..)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet