danielhenrymantilla / lending-iterator.rs

Lending iterators on stable Rust
https://docs.rs/lending-iterator
Apache License 2.0
80 stars 4 forks source link

Seems to rely on behavior related to known soundness bug #5

Open steffahn opened 2 years ago

steffahn commented 2 years ago

This crate (and also similarly polonius-the-crab) work with a

pub trait HKT 
where
    Self: for<'any> WithLifetime<'any>, 
{ }
pub trait WithLifetime<'lt> {
    type T;
}

trait, and dyn for<'a> WithLifetime<'a, T = Foo<'a>> trait objects that implement it.

However, such trait objects currently are able to implement the trait in ways that a manual implementation never could. E.g. try replicating dyn for<'a> WithLifetime<'a, T = &'a T> for some type T that does not fulfill T: 'static.

Code example and error message (click to expand)… --- ```rs pub trait HKT where Self: for<'any> WithLifetime<'any>, { } pub trait WithLifetime<'lt> { type T; } use std::convert::Infallible; use std::marker::PhantomData; struct HktRef(PhantomData T>, Infallible); impl<'a, T> WithLifetime<'a> for HktRef { type T = &'a T; } ``` ``` error[E0309]: the parameter type `T` may not live long enough --> src/lib.rs:15:14 | 15 | type T = &'a T; | ^^^^^ ...so that the reference type `&'a T` does not outlive the data it points at | help: consider adding an explicit lifetime bound... | 14 | impl<'a, T: 'a> WithLifetime<'a> for HktRef { | ++++ For more information about this error, try `rustc --explain E0309`. ``` ---

And such impossible-to-implement-manually examples are used throughout the crate, e.g. in the implementation of .map_to_ref to name just the first example I could find.

The relevant soundness issue is demonstrated here.

I’m not saying that typical usage of polonius-the-crap or lending-iterator as intended is necessarily prone to being unsound (though I haven’t really thought too deeply yet about whether or not that’s the case). However, the heavy usage of such trait objects seems “risky” at least in the sense that an eventual bugfix of rust-lang/rust#84533 might break these crates.

I think these crates have enough potential for being useful, so that this risk of breakage might eventually apply to a large number of crates that depend on them, which seems like something we should try to avoid.


IMO, with the current status of soundness issues, the “safest” approach would be to try and avoid using trait objects alltogether, even though I admit the trait-object-types-as-HKTs hack is highly convenient (every manual-trait-impls approach would presumably involve the need for listing free variables as well as their relevant trait bounds in a HKT! macro).

danielhenrymantilla commented 2 years ago

polonius-the-crap

😭


Thanks for the report, I have to admit I was initially surprised the first time I noticed that dyn for<'a> WithLifetime<'a, T = &'a T> would work without the need for "upper bounds" 😅. But, in practice, this is indeed what managed to make these crates —especially lending-iterator!— actually usable, since without 'static bounds in many places, as you said, we'd have to be carrying upper bounds around.

FWIW, I've transposed your PoC of the unsoundness using WithLifetime & HKT:

```rs use ::lending_iterator::higher_kinded_types::*; trait ConversionProof<'a, 'b> : Sized { fn convert ( proof: [Self; 0], x: &'a T, ) -> &'b T ; } impl<'a, 'b> ConversionProof<'a, 'b> for &'b &'a () { fn convert ( _proof: [Self; 0], x: &'a T, ) -> &'b T { x } } fn convert<'a, 'b, T, O : HKT> ( x: &'a T, ) -> &'b T where Apply!(O<'a>) : ConversionProof<'a, 'b>, { O::T::convert([], x) } pub fn extend_lifetime<'a, 'b, T> ( x: &'a T, ) -> &'b T { convert::<_, HKT!(&'b &'_ ())>(x) } ``` - (I've skipped the `T : ?Sized` loosening to keep the snippet more lightweight, but it would also work with that unbound)

Regarding potential breakage in the future

steffahn commented 2 years ago

polonius-the-crap

:sob:

OMG, sorry, what a typo 🤣