Skip to content

Commit 13cae60

Browse files
committed
Auto merge of rust-lang#120248 - WaffleLapkin:bonk-ptr-object-casts, r=<try>
Make casts of pointers to trait objects stricter This is an attempt to `fix` rust-lang#120222 and rust-lang#120217. This is done by adding restrictions on casting pointers to trait objects. Currently the restriction is > When casting `*const X<dyn A>` -> `*const Y<dyn B>`, if `B` has a principal trait, `dyn A + 'erased: Unsize<dyn B + 'erased>` must hold This ensures that 1. Principal trait's generic arguments match (no `*const dyn Tr<A>` -> `*const dyn Tr<B>` casts, which are a problem for [rust-lang#120222](rust-lang#120222)) 2. Principal trait's lifetime arguments match (no `*const dyn Tr<'a>` -> `*const dyn Tr<'b>` casts, which are a problem for [rust-lang#120217](rust-lang#120217)) 3. No auto traits can be _added_ (this is a problem for arbitrary self types, see [this comment](rust-lang#120248 (comment))) Some notes: - We only care about the metadata/last field, so you can still cast `*const dyn T` to `*const WithHeader<dyn T>`, etc - The lifetime of the trait object itself (`dyn A + 'lt`) is not checked, so you can still cast `*mut FnOnce() + '_` to `*mut FnOnce() + 'static`, etc - This feels fishy, but I couldn't come up with a reason it must be checked - The new checks are only done if `B` has a principal, so you can still do any kinds of cast, if the target only has auto traits - This is because auto traits are not enough to get unsoundness issues that this PR fixes - ...and so it makes sense to minimize breakage The plan is to, ~~once the checks are properly implemented~~, run crate to determine how much damage does this do. The diagnostics are currently not great, to say the least, but as far as I can tell this correctly fixes the issues. cc `@oli-obk` `@compiler-errors` `@lcnr`
2 parents 74c3f5a + 5f27951 commit 13cae60

21 files changed

+495
-70
lines changed

compiler/rustc_borrowck/src/type_check/mod.rs

+48-1
Original file line numberDiff line numberDiff line change
@@ -2358,7 +2358,42 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
23582358
let cast_ty_from = CastTy::from_ty(ty_from);
23592359
let cast_ty_to = CastTy::from_ty(*ty);
23602360
match (cast_ty_from, cast_ty_to) {
2361-
(Some(CastTy::Ptr(_)), Some(CastTy::Ptr(_))) => (),
2361+
(Some(CastTy::Ptr(src)), Some(CastTy::Ptr(dst))) => {
2362+
let src_tail = tcx.struct_tail_without_normalization(src.ty);
2363+
let dst_tail = tcx.struct_tail_without_normalization(dst.ty);
2364+
2365+
if let ty::Dynamic(..) = src_tail.kind()
2366+
&& let ty::Dynamic(dst_tty, ..) = dst_tail.kind()
2367+
&& dst_tty.principal().is_some()
2368+
{
2369+
// Erase trait object lifetimes, to allow casts like `*mut dyn FnOnce()` -> `*mut dyn FnOnce() + 'static`.
2370+
let src_tail =
2371+
erase_single_trait_object_lifetime(tcx, src_tail);
2372+
let dst_tail =
2373+
erase_single_trait_object_lifetime(tcx, dst_tail);
2374+
2375+
let trait_ref = ty::TraitRef::from_lang_item(
2376+
tcx,
2377+
LangItem::Unsize,
2378+
span,
2379+
[src_tail, dst_tail],
2380+
);
2381+
2382+
self.prove_trait_ref(
2383+
trait_ref,
2384+
location.to_locations(),
2385+
ConstraintCategory::Cast {
2386+
unsize_to: Some(tcx.fold_regions(dst_tail, |r, _| {
2387+
if let ty::ReVar(_) = r.kind() {
2388+
tcx.lifetimes.re_erased
2389+
} else {
2390+
r
2391+
}
2392+
})),
2393+
},
2394+
);
2395+
}
2396+
}
23622397
_ => {
23632398
span_mirbug!(
23642399
self,
@@ -2881,3 +2916,15 @@ impl<'tcx> TypeOp<'tcx> for InstantiateOpaqueType<'tcx> {
28812916
Ok(output)
28822917
}
28832918
}
2919+
2920+
fn erase_single_trait_object_lifetime<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> {
2921+
let &ty::Dynamic(tty, region, dyn_kind @ ty::Dyn) = ty.kind() else {
2922+
bug!("expected trait object")
2923+
};
2924+
2925+
if region.is_erased() {
2926+
return ty;
2927+
}
2928+
2929+
tcx.mk_ty_from_kind(ty::Dynamic(tty, tcx.lifetimes.re_erased, dyn_kind))
2930+
}

compiler/rustc_hir_typeck/src/cast.rs

+74-31
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,17 @@ use super::FnCtxt;
3232

3333
use crate::errors;
3434
use crate::type_error_struct;
35-
use hir::ExprKind;
3635
use rustc_errors::{codes::*, Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed};
37-
use rustc_hir as hir;
36+
use rustc_hir::{self as hir, ExprKind, LangItem};
37+
use rustc_infer::traits::Obligation;
3838
use rustc_macros::{TypeFoldable, TypeVisitable};
3939
use rustc_middle::mir::Mutability;
4040
use rustc_middle::ty::adjustment::AllowTwoPhase;
4141
use rustc_middle::ty::cast::{CastKind, CastTy};
4242
use rustc_middle::ty::error::TypeError;
4343
use rustc_middle::ty::{self, Ty, TypeAndMut, TypeVisitableExt, VariantDef};
4444
use rustc_session::lint;
45-
use rustc_span::def_id::{DefId, LOCAL_CRATE};
45+
use rustc_span::def_id::LOCAL_CRATE;
4646
use rustc_span::symbol::sym;
4747
use rustc_span::Span;
4848
use rustc_trait_selection::infer::InferCtxtExt;
@@ -72,7 +72,7 @@ enum PointerKind<'tcx> {
7272
/// No metadata attached, ie pointer to sized type or foreign type
7373
Thin,
7474
/// A trait object
75-
VTable(Option<DefId>),
75+
VTable(&'tcx ty::List<ty::Binder<'tcx, ty::ExistentialPredicate<'tcx>>>),
7676
/// Slice
7777
Length,
7878
/// The unsize info of this projection or opaque type
@@ -100,7 +100,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
100100

101101
Ok(match *t.kind() {
102102
ty::Slice(_) | ty::Str => Some(PointerKind::Length),
103-
ty::Dynamic(tty, _, ty::Dyn) => Some(PointerKind::VTable(tty.principal_def_id())),
103+
ty::Dynamic(tty, _, ty::Dyn) => Some(PointerKind::VTable(tty)),
104104
ty::Adt(def, args) if def.is_struct() => match def.non_enum_variant().tail_opt() {
105105
None => Some(PointerKind::Thin),
106106
Some(f) => {
@@ -747,7 +747,7 @@ impl<'a, 'tcx> CastCheck<'tcx> {
747747
Err(CastError::IllegalCast)
748748
}
749749

750-
// ptr -> *
750+
// ptr -> ptr
751751
(Ptr(m_e), Ptr(m_c)) => self.check_ptr_ptr_cast(fcx, m_e, m_c), // ptr-ptr-cast
752752

753753
// ptr-addr-cast
@@ -791,40 +791,83 @@ impl<'a, 'tcx> CastCheck<'tcx> {
791791
fn check_ptr_ptr_cast(
792792
&self,
793793
fcx: &FnCtxt<'a, 'tcx>,
794-
m_expr: ty::TypeAndMut<'tcx>,
795-
m_cast: ty::TypeAndMut<'tcx>,
794+
m_src: ty::TypeAndMut<'tcx>,
795+
m_dst: ty::TypeAndMut<'tcx>,
796796
) -> Result<CastKind, CastError> {
797-
debug!("check_ptr_ptr_cast m_expr={:?} m_cast={:?}", m_expr, m_cast);
797+
debug!("check_ptr_ptr_cast m_expr={:?} m_cast={:?}", m_src, m_dst);
798798
// ptr-ptr cast. vtables must match.
799799

800-
let expr_kind = fcx.pointer_kind(m_expr.ty, self.span)?;
801-
let cast_kind = fcx.pointer_kind(m_cast.ty, self.span)?;
800+
let src_kind = fcx.tcx.erase_regions(fcx.pointer_kind(m_src.ty, self.span)?);
801+
let dst_kind = fcx.tcx.erase_regions(fcx.pointer_kind(m_dst.ty, self.span)?);
802802

803-
let Some(cast_kind) = cast_kind else {
803+
match (src_kind, dst_kind) {
804804
// We can't cast if target pointer kind is unknown
805-
return Err(CastError::UnknownCastPtrKind);
806-
};
807-
808-
// Cast to thin pointer is OK
809-
if cast_kind == PointerKind::Thin {
810-
return Ok(CastKind::PtrPtrCast);
811-
}
805+
(_, None) => Err(CastError::UnknownCastPtrKind),
806+
// Cast to thin pointer is OK
807+
(_, Some(PointerKind::Thin)) => Ok(CastKind::PtrPtrCast),
812808

813-
let Some(expr_kind) = expr_kind else {
814809
// We can't cast to fat pointer if source pointer kind is unknown
815-
return Err(CastError::UnknownExprPtrKind);
816-
};
810+
(None, _) => Err(CastError::UnknownExprPtrKind),
811+
812+
// thin -> fat? report invalid cast (don't complain about vtable kinds)
813+
(Some(PointerKind::Thin), _) => Err(CastError::SizedUnsizedCast),
814+
815+
// trait object -> trait object? need to do additional checks
816+
(Some(PointerKind::VTable(src_tty)), Some(PointerKind::VTable(dst_tty))) => {
817+
match (src_tty.principal(), dst_tty.principal()) {
818+
// A<dyn Trait + Auto> -> B<dyn Trait' + Auto'>. need to make sure
819+
// - traits are the same & have the same generic arguments
820+
// - Auto' is a subset of Auto
821+
//
822+
// This is checked by checking `dyn Trait + Auto + 'erased: Unsize<dyn Trait' + Auto' + 'erased>`.
823+
(Some(_), Some(_)) => {
824+
let tcx = fcx.tcx;
825+
826+
// We need to reconstruct trait object types.
827+
// `m_src` and `m_dst` won't work for us here because they will potentially
828+
// contain wrappers, which we do not care about.
829+
//
830+
// e.g. we want to allow `dyn T -> (dyn T,)`, etc.
831+
let src_obj = tcx.mk_ty_from_kind(ty::Dynamic(src_tty, tcx.lifetimes.re_erased, ty::Dyn));
832+
let dst_obj = tcx.mk_ty_from_kind(ty::Dynamic(dst_tty, tcx.lifetimes.re_erased, ty::Dyn));
833+
834+
// `dyn Src: Unsize<dyn Dst>`
835+
let cause = fcx.misc(self.span);
836+
let obligation = Obligation::new(
837+
tcx,
838+
cause,
839+
fcx.param_env,
840+
ty::TraitRef::from_lang_item(
841+
tcx,
842+
LangItem::Unsize,
843+
self.span,
844+
[src_obj, dst_obj]
845+
)
846+
);
817847

818-
// thin -> fat? report invalid cast (don't complain about vtable kinds)
819-
if expr_kind == PointerKind::Thin {
820-
return Err(CastError::SizedUnsizedCast);
821-
}
848+
fcx.register_predicate(obligation);
822849

823-
// vtable kinds must match
824-
if fcx.tcx.erase_regions(cast_kind) == fcx.tcx.erase_regions(expr_kind) {
825-
Ok(CastKind::PtrPtrCast)
826-
} else {
827-
Err(CastError::DifferingKinds)
850+
// FIXME: ideally we'd maybe add a flag here, so that borrowck knows that
851+
// it needs to borrowck this ptr cast. this is made annoying by the
852+
// fact that `thir` does not have `CastKind` and mir restores it
853+
// from types.
854+
Ok(CastKind::PtrPtrCast)
855+
}
856+
857+
// dyn Auto -> dyn Auto'? ok.
858+
(None, None)
859+
// dyn Trait -> dyn Auto? ok.
860+
| (Some(_), None)=> Ok(CastKind::PtrPtrCast),
861+
862+
// dyn Auto -> dyn Trait? not ok.
863+
(None, Some(_)) => Err(CastError::DifferingKinds),
864+
}
865+
}
866+
867+
// fat -> fat? metadata kinds must match
868+
(Some(src_kind), Some(dst_kind)) if src_kind == dst_kind => Ok(CastKind::PtrPtrCast),
869+
870+
(_, _) => Err(CastError::DifferingKinds),
828871
}
829872
}
830873

library/alloc/src/boxed.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -2220,7 +2220,7 @@ impl dyn Error + Send {
22202220
let err: Box<dyn Error> = self;
22212221
<dyn Error>::downcast(err).map_err(|s| unsafe {
22222222
// Reapply the `Send` marker.
2223-
Box::from_raw(Box::into_raw(s) as *mut (dyn Error + Send))
2223+
mem::transmute::<Box<dyn Error>, Box<dyn Error + Send>>(s)
22242224
})
22252225
}
22262226
}
@@ -2233,8 +2233,8 @@ impl dyn Error + Send + Sync {
22332233
pub fn downcast<T: Error + 'static>(self: Box<Self>) -> Result<Box<T>, Box<Self>> {
22342234
let err: Box<dyn Error> = self;
22352235
<dyn Error>::downcast(err).map_err(|s| unsafe {
2236-
// Reapply the `Send + Sync` marker.
2237-
Box::from_raw(Box::into_raw(s) as *mut (dyn Error + Send + Sync))
2236+
// Reapply the `Send + Sync` markers.
2237+
mem::transmute::<Box<dyn Error>, Box<dyn Error + Send + Sync>>(s)
22382238
})
22392239
}
22402240
}

tests/ui/cast/cast-rfc0401-vtable-kinds.rs

-18
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,12 @@
44

55
#![feature(unsized_tuple_coercion)]
66

7-
trait Foo<T> {
8-
fn foo(&self, _: T) -> u32 { 42 }
9-
}
10-
117
trait Bar { //~ WARN trait `Bar` is never used
128
fn bar(&self) { println!("Bar!"); }
139
}
1410

15-
impl<T> Foo<T> for () {}
16-
impl Foo<u32> for u32 { fn foo(&self, _: u32) -> u32 { self+43 } }
1711
impl Bar for () {}
1812

19-
unsafe fn round_trip_and_call<'a>(t: *const (dyn Foo<u32>+'a)) -> u32 {
20-
let foo_e : *const dyn Foo<u16> = t as *const _;
21-
let r_1 = foo_e as *mut dyn Foo<u32>;
22-
23-
(&*r_1).foo(0)
24-
}
25-
2613
#[repr(C)]
2714
struct FooS<T:?Sized>(T);
2815
#[repr(C)]
@@ -38,11 +25,6 @@ fn tuple_i32_to_u32<T:?Sized>(u: *const (i32, T)) -> *const (u32, T) {
3825

3926

4027
fn main() {
41-
let x = 4u32;
42-
let y : &dyn Foo<u32> = &x;
43-
let fl = unsafe { round_trip_and_call(y as *const dyn Foo<u32>) };
44-
assert_eq!(fl, (43+4));
45-
4628
let s = FooS([0,1,2]);
4729
let u: &FooS<[u32]> = &s;
4830
let u: *const FooS<[u32]> = u;

tests/ui/cast/cast-rfc0401-vtable-kinds.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
warning: trait `Bar` is never used
2-
--> $DIR/cast-rfc0401-vtable-kinds.rs:11:7
2+
--> $DIR/cast-rfc0401-vtable-kinds.rs:7:7
33
|
44
LL | trait Bar {
55
| ^^^
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// check-fail
2+
3+
trait Trait<'a> {}
4+
5+
fn add_auto<'a>(x: *mut dyn Trait<'a>) -> *mut (dyn Trait<'a> + Send) {
6+
x as _ //~ error: the trait bound `dyn Trait<'_>: Unsize<dyn Trait<'_> + Send>` is not satisfied
7+
}
8+
9+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
error[E0277]: the trait bound `dyn Trait<'_>: Unsize<dyn Trait<'_> + Send>` is not satisfied
2+
--> $DIR/ptr-to-trait-obj-add-auto.rs:6:5
3+
|
4+
LL | x as _
5+
| ^^^^^^ the trait `Unsize<dyn Trait<'_> + Send>` is not implemented for `dyn Trait<'_>`
6+
|
7+
= note: all implementations of `Unsize` are provided automatically by the compiler, see <https://doc.rust-lang.org/stable/std/marker/trait.Unsize.html> for more information
8+
9+
error: aborting due to 1 previous error
10+
11+
For more information about this error, try `rustc --explain E0277`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// check-fail
2+
//
3+
// issue: <https://github.com/rust-lang/rust/issues/120222>
4+
5+
6+
trait A {}
7+
impl<T> A for T {}
8+
trait B {}
9+
impl<T> B for T {}
10+
11+
trait Trait<G> {}
12+
struct X;
13+
impl<T> Trait<X> for T {}
14+
struct Y;
15+
impl<T> Trait<Y> for T {}
16+
17+
fn main() {
18+
let a: *const dyn A = &();
19+
let b: *const dyn B = a as _; //~ error: the trait bound `dyn A: Unsize<dyn B>` is not satisfied
20+
21+
let x: *const dyn Trait<X> = &();
22+
let y: *const dyn Trait<Y> = x as _; //~ error: the trait bound `dyn Trait<X>: Unsize<dyn Trait<Y>>` is not satisfied
23+
24+
_ = (b, y);
25+
}
26+
27+
fn generic<T>(x: *const dyn Trait<X>, t: *const dyn Trait<T>) {
28+
let _: *const dyn Trait<T> = x as _; //~ error: the trait bound `dyn Trait<X>: Unsize<dyn Trait<T>>` is not satisfied
29+
let _: *const dyn Trait<X> = t as _; //~ error: the trait bound `dyn Trait<T>: Unsize<dyn Trait<X>>` is not satisfied
30+
}
31+
32+
trait Assocked {
33+
type Assoc: ?Sized;
34+
}
35+
36+
fn change_assoc(x: *mut dyn Assocked<Assoc = u8>) -> *mut dyn Assocked<Assoc = u32> {
37+
x as _ //~ error: the trait bound `dyn Assocked<Assoc = u8>: Unsize<dyn Assocked<Assoc = u32>>` is not satisfied
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
error[E0277]: the trait bound `dyn A: Unsize<dyn B>` is not satisfied
2+
--> $DIR/ptr-to-trait-obj-different-args.rs:19:27
3+
|
4+
LL | let b: *const dyn B = a as _;
5+
| ^^^^^^ the trait `Unsize<dyn B>` is not implemented for `dyn A`
6+
|
7+
= note: all implementations of `Unsize` are provided automatically by the compiler, see <https://doc.rust-lang.org/stable/std/marker/trait.Unsize.html> for more information
8+
9+
error[E0277]: the trait bound `dyn Trait<X>: Unsize<dyn Trait<Y>>` is not satisfied
10+
--> $DIR/ptr-to-trait-obj-different-args.rs:22:34
11+
|
12+
LL | let y: *const dyn Trait<Y> = x as _;
13+
| ^^^^^^ the trait `Unsize<dyn Trait<Y>>` is not implemented for `dyn Trait<X>`
14+
|
15+
= note: all implementations of `Unsize` are provided automatically by the compiler, see <https://doc.rust-lang.org/stable/std/marker/trait.Unsize.html> for more information
16+
17+
error[E0277]: the trait bound `dyn Trait<X>: Unsize<dyn Trait<T>>` is not satisfied
18+
--> $DIR/ptr-to-trait-obj-different-args.rs:28:34
19+
|
20+
LL | let _: *const dyn Trait<T> = x as _;
21+
| ^^^^^^ the trait `Unsize<dyn Trait<T>>` is not implemented for `dyn Trait<X>`
22+
|
23+
= note: all implementations of `Unsize` are provided automatically by the compiler, see <https://doc.rust-lang.org/stable/std/marker/trait.Unsize.html> for more information
24+
help: consider introducing a `where` clause, but there might be an alternative better way to express this requirement
25+
|
26+
LL | fn generic<T>(x: *const dyn Trait<X>, t: *const dyn Trait<T>) where dyn Trait<X>: Unsize<dyn Trait<T>> {
27+
| ++++++++++++++++++++++++++++++++++++++++
28+
29+
error[E0277]: the trait bound `dyn Trait<T>: Unsize<dyn Trait<X>>` is not satisfied
30+
--> $DIR/ptr-to-trait-obj-different-args.rs:29:34
31+
|
32+
LL | let _: *const dyn Trait<X> = t as _;
33+
| ^^^^^^ the trait `Unsize<dyn Trait<X>>` is not implemented for `dyn Trait<T>`
34+
|
35+
= note: all implementations of `Unsize` are provided automatically by the compiler, see <https://doc.rust-lang.org/stable/std/marker/trait.Unsize.html> for more information
36+
help: consider introducing a `where` clause, but there might be an alternative better way to express this requirement
37+
|
38+
LL | fn generic<T>(x: *const dyn Trait<X>, t: *const dyn Trait<T>) where dyn Trait<T>: Unsize<dyn Trait<X>> {
39+
| ++++++++++++++++++++++++++++++++++++++++
40+
41+
error[E0277]: the trait bound `dyn Assocked<Assoc = u8>: Unsize<dyn Assocked<Assoc = u32>>` is not satisfied
42+
--> $DIR/ptr-to-trait-obj-different-args.rs:37:5
43+
|
44+
LL | x as _
45+
| ^^^^^^ the trait `Unsize<dyn Assocked<Assoc = u32>>` is not implemented for `dyn Assocked<Assoc = u8>`
46+
|
47+
= note: all implementations of `Unsize` are provided automatically by the compiler, see <https://doc.rust-lang.org/stable/std/marker/trait.Unsize.html> for more information
48+
49+
error: aborting due to 5 previous errors
50+
51+
For more information about this error, try `rustc --explain E0277`.

0 commit comments

Comments
 (0)