rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
98.57k stars 12.74k forks source link

object-safe traits can have associated types with unchecked bounds #27675

Closed arielb1 closed 4 years ago

arielb1 commented 9 years ago

STR

trait Foo { type Assoc: PartialEq<Box<Self>>; }
impl Foo for u32 { type Assoc = Box<u32>; }

fn foo<T: Foo+?Sized>(u: Box<T>, v: T::Assoc) -> bool {
    &v == &u
}

fn main() {
    let bar: Box<Foo<Assoc=Box<u32>>> = Box::new(4);
    foo(bar, Box::new(5));
}

Result

<anon>:5:5: 5:13 error: internal compiler error: Encountered errors `[FulfillmentError(Obligation(predicate=Binder(TraitPredicate(<Box<u32> as core::cmp::PartialEq<Box<Foo<Assoc=Box<u32>> + 'static>>>)),depth=1),Unimplemented)]` fulfilling during trans

I guess this should be banned.

cc @nikomatsakis

arielb1 commented 9 years ago

Related:

use std::fmt;

trait Foo {
    type Assoc: 'static;
}

fn foo<T: Foo+?Sized>(t: T::Assoc) -> Box<fmt::Display+'static>
        where T::Assoc: fmt::Display {
    Box::new(t)
}

fn wat() -> Box<fmt::Display+'static> {
    let x = 42;
    foo::<Foo<Assoc=&u32>>(&x)
}

fn main() {
    println!("{}", wat());
}
nikomatsakis commented 9 years ago

Interesting. I agree that it should be illegal and we should amend WF relations for object types to check that the bindings cover the requirements of the trait -- the RFC was incomplete on this topic.

pnkfelix commented 8 years ago

The example from the first comment (https://github.com/rust-lang/rust/issues/27675#issuecomment-130067761) is actually scarier than the original bug description; at least, I usually can think "oh, an ICE, well that might just be something that won't actually ever compile"; but the first comment is showing code that is indeed a case of "wat"!

pnkfelix commented 8 years ago

triage: P-medium

HeroicKatora commented 4 years ago

Since this has not been demonstrated: We can indeed build a safe, arbitrary transmutation primitive from this, though this will ICE in practice except if used only for the purpose of the second comment, casting away lifetimes.

trait Id<T>: Sized {
    fn id(self) -> T;
}
impl<T> Id<T> for T {
    fn id(self) -> T { self }
}

trait Setup<T> {
    type From: Id<T>;
}

fn transmute<T, U: Setup<T> + ?Sized>(from: U::From) -> T {
    Id::id(from)
}

// compiles fine
pub fn safe_transmute<T, U>(t: T) -> U {
    transmute::<U, dyn Setup<U, From=T>>(t)
}
fn main() {
    let static_word = {
        let st = String::from("Hello!");
        safe_transmute::<_, &'static mut str>(&*st)
    };

    println!("{:?}", static_word);
}
// ICE
fn main() {
    safe_transmute::<usize, &'static str>(0usize);
}
HeroicKatora commented 4 years ago

Uuups, we can make any type Copy. That's quite severe, wouldn't you say?

trait Setup {
    type From: Copy;
}

fn copy<U: Setup + ?Sized>(from: &U::From) -> U::From {
    *from
}

pub fn copy_any<T>(t: &T) -> T {
    copy::<dyn Setup<From=T>>(t)
}

fn main() {
    let st = String::from("Hello");
    copy_any(&st);
}
HeroicKatora commented 4 years ago

Feature unsize gives the ability to do some other interesting coercions that enable arbitrary sized reference transmute.

#![feature(unsize)]

trait Setup<T> {
    type From: std::marker::Unsize<[T]>;
}

fn unsize<T, U: Setup<T> + ?Sized>(from: &U::From) -> &[T] {
    from
}

fn unsize_to_any<T, U>(t: &T) -> &U {
    &unsize::<U, dyn Setup<U, From=[T]>>(core::slice::from_ref(t))[0]
}

fn main() {
    let trust_me_im_64: u8 = 0;
    println!("{}", unsize_to_any::<u8, u64>(&trust_me_im_64))
}
camelid commented 4 years ago

Decided on P-high as discussed in the prioritization working group procedure.

HeroicKatora commented 4 years ago

For completeness sake. This may be used to send any reference across threads:

trait Id<T> {
    fn id(&self) -> &T;
}

impl<T> Id<T> for T {
    fn id(&self) -> &T { self }
}

trait SyncSetup<T> {
    type From: Id<T> + Sync + 'static;
}

fn cast<T, U>(from: &T) -> &(dyn Id<U> + Sync) {
    fn do_it<T, U: ?Sized + SyncSetup<T>>(val: &U::From) -> &(dyn Id<T> + Sync) {
        val
    }

    do_it::<U, dyn SyncSetup<U, From=T>>(from)
}

fn syncify<T>(from: &T) -> &'static (dyn Id<T> + Sync) {
    // Very carefully avoid ICE. This only adds marker trait to an unsized trait.
    let x: &(dyn Id<T> + Sync) = cast(from);
    // Indirect over another reference and make `x` appear `&'static`.
    // Lifetime change is invisible to trait materialization checks.
    let y: &(dyn Id<&'static (dyn Id<T> + Sync)>) = cast(&x);
    // .. and grab that one.
    let x: &'static (dyn Id<T> + Sync) = *(y.id());
    x
}

use std::cell::Cell;
fn main() {
    let st = Cell::new(0u32);
    let x = syncify(&st);

    std::thread::spawn(move || {
        x.id().set(1);
    });

    while st.get() == 0 {}
    println!("Spooky");
}
Aaron1011 commented 4 years ago

I suspect that this will be fixed by https://github.com/rust-lang/rust/pull/73905

lqd commented 4 years ago

The 4 weaponized examples above will indeed be rejected with #73905

Aaron1011 commented 4 years ago

A regression test was added in https://github.com/rust-lang/rust/pull/77663.