flowchart LR
subgraph Edition2024
RPITLifetimeCaptureRules[" \n\nowner: TC/errs"]
WeakTypeAliases["Changing type aliases to be weak\n\nowner: (lang)"]
end
subgraph EOY2024
NewSolverEverywhere["Use the new trait solver in all the places\n\nowner: lcnr"]
NegativeImpls["Support negative impls in coherence\n\nowner: niko"]
PoloniusStable["Location-sensitive polonius\n\nowner: lqd"]
end
subgraph HeatDeath
UnlimiTAITed
Coinduction
Fix25860
ImpliedBounds
BeautifulGat
PerfectDerive
FixOrSettle41756["Address how reordering where clauses can change behavior"]
ExistentialLifetimes["more usable RPIT via existential lifetimes"]
end
subgraph Early2024
ModelCoherenceInFormality["Model coherence in formality\n\nowner: nikomatsakis"]
NormalizeInOrphanCheck["Normalize in orphan check\n\nowner: lcnr"]
NewSolverInCoherence["New solver in coherence\n\nowner: lcnr"]
ImprovedOutlivesWithOpaqueBounds["`-> impl Trait + 'static` is ok even if it captures\n\nowner: errs"]
MinTAITs["Minimal version of TAITs\n\nowner: oli, TC for prose, fallback to errs"]
PoloniusPrototype["Prototype of Polonius\n\nowner: lqd"]
DynUpcasting["Dyn upcasting\n\nowner: lcnr"]
end
Early2024 -.- Edition2024
Edition2024 -.- EOY2024
EOY2024 -.- HeatDeath
NewSolverInCoherence --> NewSolverEverywhere
NormalizeInOrphanCheck --> WeakTypeAliases
MinTAITs --> RPITLifetimeCaptureRules
MinTAITs --> UnlimiTAITed
PoloniusPrototype --> PoloniusStable
ImprovedOutlivesWithOpaqueBounds --> ExistentialLifetimes
NewSolverEverywhere --> Coinduction
NewSolverEverywhere --> UnlimiTAITed
ModelCoherenceInFormality --> NewSolverInCoherence
Coinduction --> ImpliedBounds
ImpliedBounds --> BeautifulGat
Coinduction --> Fix25860
linkStyle 0 stroke-width:0px
linkStyle 1 stroke-width:0px
linkStyle 2 stroke-width:0px
fn codegen_select_candidate
Not on roadmap:
https://github.com/rust-lang/rust/issues?q=is%3Aissue+is%3Aopen+label%3AI-unsound+label%3AT-types
Last year: https://hackmd.io/nNC_Z6nVSpW-ANkpJdSgpg?both (https://i.imgur.com/1zqc2pc.jpg)
E-mentor
tagFollowup: Niko and Jack to put together an initial draft of unsoundess on a roadmap
This used to be true:
fn(&'a u32): 'static
But we wanted T::Item: 'x
if T: 'x
and…
impl<'a> Trait for fn(&'a u32) {
type Item = &'a u32;
}
But we could say that if a lifetime is used in contravariant position then it's not constrained.
I wish we could change the current behavior. If we could do this, doing it over an edition might be the way. It would be nice if T: 'static
would always mean that T
doesn't contain any data by reference.
This is related to:
– Rust allows impl Fn(T<'a>) -> T<'b> to be : 'static, which is unsound #112905
Issue with TypeId
:
fn(&'a u32): 'static
means you can convert Box<fn(&'a u32)>
to Box<dyn Any>
, downcast to Box<fn(&'static u32)>
.
https://github.com/rust-lang/rust/issues/112905
The heart of 112905 is leverage the dyn trait loophole to convert a…
type F<'a, 'b> = impl 'static + Fn(T<'a>) -> T<'b>;
F<'a, 'b>
to F<'a, 'static>
. Which lets you call the function and convert anything into 'static
.
type F<'a, 'b> = impl 'static + Fn(T<'a>, &mut T<'b>);
Suppose we had an explicit notation:
type F<'a, 'b> = impl<'a, 'b> 'static + Fn(T<'a>, &mut T<'b>);
what if the type were
type G<'a, 'b> = impl<'a, 'b> 'static;
is that a soundness hole? It IS possible to have
mod boundary {
type G<'a, 'b> = impl<'a, 'b> 'static;
fn get<'a>(x: &'a u32) -> G<'a, 'a> {
() // <-- is there a hidden type here that is unsound
}
fn take<'a, 'b>(g: G<'a, 'b>) {
// ...given that caller may change 'a and 'b arbitrarily here
}
}
fn caller<'x>(x: &'x u32) {
let g0: boundary::G<'x, 'x> = get(x);
let g1: boundary::G<'x, 'static> = dyn_trick(g0);
boudary::take(g1); // but...so what??
}
fn dyn_trick<T, U>(t: T) -> U
where T: 'static, U: 'static,
{
let x: Box<dyn Any> = Box::new(t);
*x.downcast::<U>().unwrap()
}
Another view of #112905 is that, in this code…
#![feature(type_alias_impl_trait)]
struct T<'x>(&'x ());
type F<'a, 'b> = impl 'static + Fn(T<'a>) -> T<'b>;
fn helper<'a, 'b>(_: [&'b &'a (); 0]) -> F<'a, 'b> {
// Why does this compile? Because of an implied bound
// `where `'a: 'b` (which could well have been explicit);
// that relationship is required to make the hidden type
// well-formed, but we lose it when
|x: T<'a>| -> T<'b> { x } // this should *not* be `: 'static`
}
#![feature(type_alias_impl_trait)]
struct Foo<T: Ord> { t: T }
type FooAlias<T> = impl std::fmt::Debug;
fn foo<T: Ord>(t: T) -> FooAlias<T> {
Foo::<T> { t }
}
Writing the implied where 'a: 'b
bound correctly gives you this error:
message:
error[E0478]: lifetime bound not satisfied
--> src/main.rs:9:18
|
9 | type F<'a, 'b> = impl Any + Fn(T<'a>) -> T<'b>;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
note: lifetime parameter instantiated with the lifetime `'a` as defined here
--> src/main.rs:9:8
|
9 | type F<'a, 'b> = impl Any + Fn(T<'a>) -> T<'b>;
| ^^
note: but lifetime parameter must outlive the lifetime `'b` as defined here
--> src/main.rs:9:12
|
9 | type F<'a, 'b> = impl Any + Fn(T<'a>) -> T<'b>;
| ^^
#![forbid(unsafe_code)] // No `unsafe!`
#![feature(type_alias_impl_trait)]
use core::any::Any;
/// Anything covariant will do, for this demo.
type T<'lt> = &'lt str;
type F<'a, 'b> where 'a: 'b = impl Any + Fn(T<'a>) -> T<'b>;
fn helper<'a, 'b>(_: [&'b &'a (); 0]) -> F<'a, 'b>
where 'a: 'b { // <-- the change
|x: T<'a>| -> T<'b> { x }
}
fn exploit<'a, 'b>(a: T<'a>) -> T<'b> {
let f: F<'a, 'a> = helper([]);
let any = Box::new(f) as Box<dyn Any>;
let f: F<'a, 'static> = *any.downcast().unwrap();
f(a)
}
fn main() {
let r: T<'static> = {
let local = String::from("...");
exploit(&local)
};
// Since `r` now dangles, we can easily make the use-after-free
// point to newly allocated memory!
let _unrelated = String::from("UAF");
dbg!(r); // may print `UAF`! Run with `miri` to see the UB.
}
In summary…
NM: Not convinced this bug blocks semantic 'static
.
NM: Am convinced that our implied bounds rules need work.
Resources: