Skip to content

Commit 51228ea

Browse files
committed
Add more checks for pointers with vtable meta
The rules for casting `*mut X<dyn A>` -> `*mut Y<dyn B>` are as follows: - If `B` has a principal - `A` must have exactly the same principal (including generics) - Auto traits of `B` must be a subset of autotraits in `A` Note that `X<_>` and `Y<_>` can be identity, or arbitrary structs with last field being the dyn type. The lifetime of the trait object itself (`dyn ... + 'a`) is not checked. This prevents a few soundness issues with `#![feature(arbitrary_self_types)]` and trait upcasting. Namely, these checks make sure that vtable is always valid for the pointee.
1 parent 124ada5 commit 51228ea

13 files changed

+333
-67
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

+73-30
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ 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;
@@ -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<ty::Binder<'tcx, ty::ExistentialTraitRef<'tcx>>>),
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())),
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
}
+2-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
// check-pass
1+
// check-fail
22

33
trait Trait<'a> {}
44

55
fn add_auto<'a>(x: *mut dyn Trait<'a>) -> *mut (dyn Trait<'a> + Send) {
6-
x as _
6+
x as _ //~ error: the trait bound `dyn Trait<'_>: Unsize<dyn Trait<'_> + Send>` is not satisfied
77
}
88

99
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`.

tests/ui/cast/ptr-to-trait-obj-different-args.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,23 @@ impl<T> Trait<Y> for T {}
1616

1717
fn main() {
1818
let a: *const dyn A = &();
19-
let b: *const dyn B = a as _; //~ error: casting `*const dyn A` as `*const dyn B` is invalid
19+
let b: *const dyn B = a as _; //~ error: the trait bound `dyn A: Unsize<dyn B>` is not satisfied
2020

2121
let x: *const dyn Trait<X> = &();
22-
let y: *const dyn Trait<Y> = x as _; //~ error: casting `*const dyn Trait<X>` as `*const dyn Trait<Y>` is invalid
22+
let y: *const dyn Trait<Y> = x as _; //~ error: the trait bound `dyn Trait<X>: Unsize<dyn Trait<Y>>` is not satisfied
2323

2424
_ = (b, y);
2525
}
2626

2727
fn generic<T>(x: *const dyn Trait<X>, t: *const dyn Trait<T>) {
28-
let _: *const dyn Trait<T> = x as _; //~ error: casting `*const (dyn Trait<X> + 'static)` as `*const dyn Trait<T>` is invalid
29-
let _: *const dyn Trait<X> = t as _; //~ error: casting `*const (dyn Trait<T> + 'static)` as `*const dyn Trait<X>` is invalid
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
3030
}
3131

3232
trait Assocked {
3333
type Assoc: ?Sized;
3434
}
3535

3636
fn change_assoc(x: *mut dyn Assocked<Assoc = u8>) -> *mut dyn Assocked<Assoc = u32> {
37-
x as _
37+
x as _ //~ error: the trait bound `dyn Assocked<Assoc = u8>: Unsize<dyn Assocked<Assoc = u32>>` is not satisfied
3838
}
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,51 @@
1-
error[E0606]: casting `*const dyn A` as `*const dyn B` is invalid
1+
error[E0277]: the trait bound `dyn A: Unsize<dyn B>` is not satisfied
22
--> $DIR/ptr-to-trait-obj-different-args.rs:19:27
33
|
44
LL | let b: *const dyn B = a as _;
5-
| ^^^^^^
5+
| ^^^^^^ the trait `Unsize<dyn B>` is not implemented for `dyn A`
66
|
7-
= note: vtable kinds may not match
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
88

9-
error[E0606]: casting `*const dyn Trait<X>` as `*const dyn Trait<Y>` is invalid
9+
error[E0277]: the trait bound `dyn Trait<X>: Unsize<dyn Trait<Y>>` is not satisfied
1010
--> $DIR/ptr-to-trait-obj-different-args.rs:22:34
1111
|
1212
LL | let y: *const dyn Trait<Y> = x as _;
13-
| ^^^^^^
13+
| ^^^^^^ the trait `Unsize<dyn Trait<Y>>` is not implemented for `dyn Trait<X>`
1414
|
15-
= note: vtable kinds may not match
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
1616

17-
error[E0606]: casting `*const (dyn Trait<X> + 'static)` as `*const dyn Trait<T>` is invalid
17+
error[E0277]: the trait bound `dyn Trait<X>: Unsize<dyn Trait<T>>` is not satisfied
1818
--> $DIR/ptr-to-trait-obj-different-args.rs:28:34
1919
|
2020
LL | let _: *const dyn Trait<T> = x as _;
21-
| ^^^^^^
21+
| ^^^^^^ the trait `Unsize<dyn Trait<T>>` is not implemented for `dyn Trait<X>`
2222
|
23-
= note: vtable kinds may not match
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+
| ++++++++++++++++++++++++++++++++++++++++
2428

25-
error[E0606]: casting `*const (dyn Trait<T> + 'static)` as `*const dyn Trait<X>` is invalid
29+
error[E0277]: the trait bound `dyn Trait<T>: Unsize<dyn Trait<X>>` is not satisfied
2630
--> $DIR/ptr-to-trait-obj-different-args.rs:29:34
2731
|
2832
LL | let _: *const dyn Trait<X> = t as _;
29-
| ^^^^^^
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>`
3046
|
31-
= note: vtable kinds may not match
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
3248

33-
error: aborting due to 4 previous errors
49+
error: aborting due to 5 previous errors
3450

35-
For more information about this error, try `rustc --explain E0606`.
51+
For more information about this error, try `rustc --explain E0277`.

tests/ui/cast/ptr-to-trait-obj-different-regions-lt-ext.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// check-pass
1+
// check-fail
22
//
33
// issue: <https://github.com/rust-lang/rust/issues/120217>
44

@@ -9,7 +9,7 @@ trait Static<'a> {
99
}
1010

1111
fn bad_cast<'a>(x: *const dyn Static<'static>) -> *const dyn Static<'a> {
12-
x as _
12+
x as _ //~ error: lifetime may not live long enough
1313
}
1414

1515
impl Static<'static> for () {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
error: lifetime may not live long enough
2+
--> $DIR/ptr-to-trait-obj-different-regions-lt-ext.rs:12:5
3+
|
4+
LL | fn bad_cast<'a>(x: *const dyn Static<'static>) -> *const dyn Static<'a> {
5+
| -- lifetime `'a` defined here
6+
LL | x as _
7+
| ^^^^^^ returning this value requires that `'a` must outlive `'static`
8+
9+
error: aborting due to 1 previous error
10+

0 commit comments

Comments
 (0)