negative impl may overlap with the builtin trait object impl
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 119k
- Forks
- 16.1k
- PR merge metrics
- PR metrics pending
Description
this is bug 6 found by @carlini on zulip https://rust-lang.zulipchat.com/#narrow/channel/364551-t-types.2Ftrait-system-refactor/topic/Reporting.20a.20collection.20of.20soundness.20bugs.20in.20next-solver/near/617737400
Here's the LLM generated unsoundness reproducer:
// Negative coherence lets two overlapping impls of `Choose` for `dyn Obj<T>`
// coexist: it negates one impl's where-clause to `dyn Obj<!p>: !Has<Option<u8>>`
// and proves it via the blanket negative impl, even though the positive goal holds
// at `p = u8` via the object candidate. The old solver still rejects at the call
// site (`dyn Obj<u8>: Choose` is ambiguous); -Znext-solver accepts and the two
// impls disagree on `Out`, which lets `bad` extend a borrow to `'a` (use-after-free).
#![forbid(unsafe_code)]
#![feature(negative_impls, with_negative_coherence)] // needed: without them the impls are rejected as overlapping (E0119)
trait Has<T> {}
impl<X: ?Sized, T> !Has<T> for X {} // needed: this is what coherence uses to "prove" disjointness; `?Sized` so it covers `dyn Obj<_>`
// `dyn Obj<u8>: Has<u8>` and `dyn Obj<u8>: Has<Option<u8>>` both hold via the object candidate.
trait Obj<T>: Has<T> + Has<Option<T>> {}
trait Choose<'a, 'b> {
type Out;
}
impl<'a, 'b, T> Choose<'a, 'b> for dyn Obj<T>
where
dyn Obj<T>: Has<u8>,
{
type Out = &'a str;
}
impl<'a, 'b, T> Choose<'a, 'b> for dyn Obj<T>
where
dyn Obj<T>: Has<Option<u8>>,
{
type Out = &'b str;
}
// Only the first impl applies here: Out == &'a str.
fn out_is_a<'a, 'b, T>(x: <dyn Obj<T> as Choose<'a, 'b>>::Out) -> &'a str
where
dyn Obj<T>: Has<u8>,
{
x
}
// Only the second impl applies here: Out == &'b str.
fn out_is_b<'a, 'b, T>(x: &'b str) -> <dyn Obj<T> as Choose<'a, 'b>>::Out
where
dyn Obj<T>: Has<Option<u8>>,
{
x
}
// needed: the trait-only where-clause keeps `Out` rigid, so the two calls compose unnormalized.
fn bad<'a, 'b>(x: &'b str) -> &'a str
where
dyn Obj<u8>: Choose<'a, 'b>,
{
out_is_a::<u8>(out_is_b::<u8>(x))
}
fn main() {
let dangling;
{
let s = String::from("Hello World");
dangling = bad(&s);
}
println!("{dangling}"); // use-after-free: prints garbage
}
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by compiling the provided reproducer with -Znext-solver and compare its behavior with the old solver, focusing on negative coherence, the builtin trait-object candidate, and the two overlapping Choose impls. Minimize the example if useful; done means the compiler rejects the overlap or no longer accepts the program that produces the dangling reference.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100