rust
rust copied to clipboard
`trait_upcasting` unsoundness due to reordered super traits
The following program will trigger a segmentation fault. (playground link)
#![feature(trait_upcasting)]
trait Pollable {
#[allow(unused)]
fn poll(&self) {}
}
trait FileIo: Pollable + Send + Sync {
fn read(&self) {}
}
trait Terminal: Send + Sync + FileIo {}
struct A;
impl Pollable for A {}
impl FileIo for A {}
impl Terminal for A {}
fn main() {
let a = A;
let b = &a as &dyn Terminal;
let c = b as &dyn FileIo;
c.read();
}
This is because the vtable layout of Send + Sync + FileIo
(i.e., the supertrait of Terminal
) is different from that of FileIo
, even though (i) Send
and Sync
are auto traits and (ii) FileIo
is Send
and Sync
. However, it seems that the upcast coerion thinks they are the same, so it reuses the vtable, causing segmentation faults.
vtables
.L__unnamed_1: # `dyn Terminal`'s vtable
.asciz "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\001\000\000\000\000\000\000"
.quad playground::FileIo::read # `Pollable::poll`
.quad playground::FileIo::read # `FileIo::read`
.L__unnamed_2: # `dyn FileIo`'s vtable
.asciz "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\001\000\000\000\000\000\000"
.quad playground::FileIo::read # `Pollable::poll`
.quad .L__unnamed_3 # `Send`'s vptr
.quad .L__unnamed_3 # `Sync`'s vptr
.quad playground::FileIo::read # `FileIo::read`
I checked how the vtable layout is generated: https://github.com/rust-lang/rust/blob/798fb83f7d24e31b16acca113496f39ff168c143/compiler/rustc_trait_selection/src/traits/vtable.rs#L157-L168
By the current implementation, Send + Sync + FileIo
will not have vptr
s for Send
and Sync
because Send
and Sync
are iterated at the very first during the prepare_vtable_segments_inner
method. So emit_vptr_on_new_entry
is false when Send
and Sync
are iterated.
- However,
FileIo
will havevptr
s forSend
andSync
becauseSend
andSync
are iterated afterPollable
is iterated. This means thatprepare_vtable_segments_inner
is true whenSend
andSync
are iterated.
This sounds a little strange to me.
Patch
I tried the following patch, which makes both Send + Sync + FileIo
and FileIo
not have vptr
s for Send
and Sync
. I confirmed that the reported problem disappears, but I don't know if I'm on the right track since I'm not familiar with the code. (I can open a PR if it sounds right).
patch
diff --git a/compiler/rustc_trait_selection/src/traits/vtable.rs b/compiler/rustc_trait_selection/src/traits/vtable.rs
index 6e6f948a2cd..ed221e2a183 100644
--- a/compiler/rustc_trait_selection/src/traits/vtable.rs
+++ b/compiler/rustc_trait_selection/src/traits/vtable.rs
@@ -154,18 +154,17 @@ fn prepare_vtable_segments_inner<'tcx, T>(
// emit innermost item, move to next sibling and stop there if possible, otherwise jump to outer level.
while let Some((inner_most_trait_ref, emit_vptr, mut siblings)) = stack.pop() {
+ let has_entries =
+ has_own_existential_vtable_entries(tcx, inner_most_trait_ref.def_id());
+
segment_visitor(VtblSegment::TraitOwnEntries {
trait_ref: inner_most_trait_ref,
- emit_vptr: emit_vptr && !tcx.sess.opts.unstable_opts.no_trait_vptr,
+ emit_vptr: emit_vptr && has_entries && !tcx.sess.opts.unstable_opts.no_trait_vptr,
})?;
// If we've emitted (fed to `segment_visitor`) a trait that has methods present in the vtable,
// we'll need to emit vptrs from now on.
- if !emit_vptr_on_new_entry
- && has_own_existential_vtable_entries(tcx, inner_most_trait_ref.def_id())
- {
- emit_vptr_on_new_entry = true;
- }
+ emit_vptr_on_new_entry |= has_entries;
if let Some(next_inner_most_trait_ref) =
siblings.find(|&sibling| visited.insert(sibling.upcast(tcx)))
Cc #65991 (tracking issue for trait_upcasting
)
@rustbot label +F-trait_upcasting +A-coercions +A-trait-objects +I-unsound +T-lang +T-type