Skip to content

Unsoundness and ICE due to DispatchFromDyn allowing bogus impls on references. #148727

Description

@theemathas

Code

#![feature(dispatch_from_dyn, arbitrary_self_types)]

use std::ops::{DispatchFromDyn, Receiver};

pub struct Ptr<T: ?Sized>(Box<T>, Box<T>);

impl<'a, T: ?Sized, U: ?Sized> DispatchFromDyn<&'a Ptr<U>> for &'a Ptr<T> {}
impl<T: ?Sized> Receiver for Ptr<T> {
    type Target = T;
}

pub trait Trait {
    fn method(self: &Ptr<Self>) {}
}

struct Thing;
impl Trait for Thing {}

fn main() {
    let x: Ptr<dyn Trait> = Ptr(
        Box::new(Thing) as Box<dyn Trait>,
        Box::new(Thing) as Box<dyn Trait>,
    );
    x.method();
}

DispatchFromDyn does some validation on impls to ensure that they're sensible. However, for some reason, it is currently documented to always allow impls on references. This leads to bogus impls such as above.

Meta

Reproducible on the playground with version 1.93.0-nightly (2025-11-08 fb23dd3c6b120f0d2e55)

Error output

error: internal compiler error: /rustc-dev/fb23dd3c6b120f0d2e55e5f2c69a464df7b35fdf/compiler/rustc_codegen_ssa/src/mir/block.rs:1148:25: can't codegen a virtual call on OperandRef(Immediate((ptr:  %3 = alloca [32 x i8], align 8)) @ TyAndLayout { ty: &Ptr<dyn Trait>, layout: Layout { size: Size(8 bytes), align: AbiAlign { abi: Align(8 bytes) }, backend_repr: Scalar(Initialized { value: Pointer(AddressSpace(0)), valid_range: 1..=18446744073709551615 }), fields: Primitive, largest_niche: Some(Niche { offset: Size(0 bytes), value: Pointer(AddressSpace(0)), valid_range: 1..=18446744073709551615 }), uninhabited: false, variants: Single { index: 0 }, max_repr_align: None, unadjusted_abi_align: Align(8 bytes), randomization_seed: 281492156579847 } })
  --> src/main.rs:24:7
   |
24 |     x.method();
   |       ^^^^^^^^


thread 'rustc' (28) panicked at /rustc-dev/fb23dd3c6b120f0d2e55e5f2c69a464df7b35fdf/compiler/rustc_codegen_ssa/src/mir/block.rs:1148:25:
Box<dyn Any>
Backtrace

stack backtrace:
   0: std::panicking::begin_panic::<rustc_errors::ExplicitBug>
   1: <rustc_errors::diagnostic::BugAbort as rustc_errors::diagnostic::EmissionGuarantee>::emit_producing_guarantee
   2: <rustc_errors::DiagCtxtHandle>::span_bug::<rustc_span::span_encoding::Span, alloc::string::String>
   3: rustc_middle::util::bug::opt_span_bug_fmt::<rustc_span::span_encoding::Span>::{closure#0}
   4: rustc_middle::ty::context::tls::with_opt::<rustc_middle::util::bug::opt_span_bug_fmt<rustc_span::span_encoding::Span>::{closure#0}, !>::{closure#0}
   5: rustc_middle::ty::context::tls::with_context_opt::<rustc_middle::ty::context::tls::with_opt<rustc_middle::util::bug::opt_span_bug_fmt<rustc_span::span_encoding::Span>::{closure#0}, !>::{closure#0}, !>
   6: rustc_middle::util::bug::span_bug_fmt::<rustc_span::span_encoding::Span>
   7: rustc_codegen_ssa::mir::codegen_mir::<rustc_codegen_llvm::builder::GenericBuilder<rustc_codegen_llvm::context::FullCx>>
   8: rustc_codegen_llvm::base::compile_codegen_unit::module_codegen
   9: <rustc_codegen_llvm::LlvmCodegenBackend as rustc_codegen_ssa::traits::backend::ExtraBackendMethods>::compile_codegen_unit
  10: rustc_codegen_ssa::base::codegen_crate::<rustc_codegen_llvm::LlvmCodegenBackend>
  11: <rustc_codegen_llvm::LlvmCodegenBackend as rustc_codegen_ssa::traits::backend::CodegenBackend>::codegen_crate
  12: <rustc_interface::queries::Linker>::codegen_and_build_linker
  13: <rustc_interface::passes::create_and_enter_global_ctxt<core::option::Option<rustc_interface::queries::Linker>, rustc_driver_impl::run_compiler::{closure#0}::{closure#2}>::{closure#2} as core::ops::function::FnOnce<(&rustc_session::session::Session, rustc_middle::ty::context::CurrentGcx, alloc::sync::Arc<rustc_data_structures::jobserver::Proxy>, &std::sync::once_lock::OnceLock<rustc_middle::ty::context::GlobalCtxt>, &rustc_data_structures::sync::worker_local::WorkerLocal<rustc_middle::arena::Arena>, &rustc_data_structures::sync::worker_local::WorkerLocal<rustc_hir::Arena>, rustc_driver_impl::run_compiler::{closure#0}::{closure#2})>>::call_once::{shim:vtable#0}
  14: rustc_interface::interface::run_compiler::<(), rustc_driver_impl::run_compiler::{closure#0}>::{closure#1}
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

note: we would appreciate a bug report: https://github.com/rust-lang/rust/issues/new?labels=C-bug%2C+I-ICE%2C+T-compiler&template=ice.md

note: please make sure that you have updated to the latest nightly

note: please attach the file at `/playground/rustc-ice-2025-11-09T04_18_49-26.txt` to your bug report

note: compiler flags: --crate-type bin -C embed-bitcode=no -C codegen-units=1 -C debuginfo=2

note: some of the compiler flags provided by cargo are hidden

query stack during panic:
end of query stack

Activity

  1. added
    I-ICEIssue: The compiler panicked, giving an Internal Compilation Error (ICE) ❄️
    T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.
    C-bugCategory: This is a bug.
    T-typesRelevant to the types team, which will review and decide on the PR/issue.
    A-dyn-compatibilityArea: Dyn compatibility (formerly: object safety)
    on Nov 9, 2025
  2. added
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    on Nov 9, 2025
  3. added
    requires-nightlyThis issue requires a nightly compiler in some way. When possible, use a F-* label instead.
    on Nov 9, 2025
  4. theemathas commented on Nov 9, 2025

    @theemathas
    ContributorAuthor

    Here's code that causes UB with only one use of unsafe. And I think that this use of unsafe should be sound. Therefore, DispatchFromDyn is unsound.

    #![feature(dispatch_from_dyn, arbitrary_self_types)]
    
    use std::marker::PhantomData;
    use std::ops::{DispatchFromDyn, Receiver};
    
    pub struct Inner<T: ?Sized, U: ?Sized>(Box<T>, U);
    #[repr(transparent)]
    pub struct Outer<T: ?Sized>(PhantomData<T>, Inner<T, T>);
    
    fn wrap<T: ?Sized>(x: &Inner<T, T>) -> &Outer<T> {
        // SAFETY: We're using repr(transparent)
        unsafe { &*(x as *const Inner<T, T> as *const Outer<T>) }
    }
    
    impl<'a, T: ?Sized, U: ?Sized> DispatchFromDyn<&'a Outer<U>> for &'a Outer<T> {}
    impl<T: ?Sized> Receiver for Outer<T> {
        type Target = T;
    }
    
    pub trait Trait {
        fn method(self: &Outer<Self>);
    }
    
    struct Thing1;
    impl Trait for Thing1 {
        fn method(self: &Outer<Self>) {
            unreachable!()
        }
    }
    
    struct Thing2(i32);
    impl Trait for Thing2 {
        fn method(self: &Outer<Self>) {
            println!("{}", self.1.0.0);
        }
    }
    
    fn main() {
        let inner: &Inner<dyn Trait, dyn Trait> = &Inner(Box::new(Thing1) as Box<dyn Trait>, Thing2(2));
        let x: &Outer<dyn Trait> = wrap(inner);
        x.method();
    }

    Output in debug mode:

    thread 'main' (12) panicked at src/main.rs:34:9:
    misaligned pointer dereference: address must be a multiple of 0x4 but is 0x1
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
    thread caused non-unwinding panic. aborting.
    

    This code effectively transmutes an &Inner<dyn Trait, Thing2> into an &Inner<Thing2, Thing2> via the incorrect dyn dispatch.

  5. added
    I-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness
    on Nov 9, 2025
  6. changed the title [-]ICE due to `DispatchFromDyn` allowing bogus impls on references.[/-] [+]Unsoundness and ICE due to `DispatchFromDyn` allowing bogus impls on references.[/+] on Nov 9, 2025
  7. added
    I-lang-radarItems that are on lang's radar and will need eventual work or consideration.
    on Nov 15, 2025
  8. added 2 commits that reference this issue on Nov 18, 2025
    75de383
    88de0f9
  9. dingxiangfei2009 commented on Nov 19, 2025

    @dingxiangfei2009
    Contributor

    @rustbot claim

    I am preparing a patch. Let's see how it goes.

  10. Darksonn commented on Dec 11, 2025

    @Darksonn
    Member

    This impl should be illegal:

    impl<'a, T: Unsize<U> + ?Sized, U: ?Sized> DispatchFromDyn<&'a Outer<U>> for &'a Outer<T> {}

    It compiles because it falls into the

    EITHER Self and T are either both references or both raw pointers; in either case, with the same mutability.

    case in the built-in check. But the reference / raw pointer types are supposed to only have the impls found in the stdlib. If they can have more, there's going to be all sorts of problems. We need to reject all downstream impls of DispatchFromDyn for references and raw pointers.

    Probably DispatchFromDyn should be "anti-fundamental" so that impl DispatchFromDyn<_> for Fundamental<LocalType> is rejected in general, which would reject the above impl. It's similar to what has been discussed elsewhere as being needed for DerefMut in #145608 and CoerceUnsize in #148899. cc @lcnr

  11. removed
    requires-nightlyThis issue requires a nightly compiler in some way. When possible, use a F-* label instead.
    on Dec 12, 2025
  12. removed
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    on Jan 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

A-dyn-compatibilityArea: Dyn compatibility (formerly: object safety)C-bugCategory: This is a bug.F-arbitrary_self_types`#![feature(arbitrary_self_types)]`F-dispatch_from_dyn`#![feature(dispatch_from_dyn)]`I-ICEIssue: The compiler panicked, giving an Internal Compilation Error (ICE) ❄️I-lang-radarItems that are on lang's radar and will need eventual work or consideration.I-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/SoundnessT-compilerRelevant to the compiler team, which will review and decide on the PR/issue.T-typesRelevant to the types team, which will review and decide on the PR/issue.

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions