Skip to content

Commit 5645e8e

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 9e8ef92 commit 5645e8e

13 files changed

+331
-67
lines changed

compiler/rustc_borrowck/src/type_check/mod.rs

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2319,7 +2319,41 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
23192319
let cast_ty_from = CastTy::from_ty(ty_from);
23202320
let cast_ty_to = CastTy::from_ty(*ty);
23212321
match (cast_ty_from, cast_ty_to) {
2322-
(Some(CastTy::Ptr(_)), Some(CastTy::Ptr(_))) => (),
2322+
(Some(CastTy::Ptr(src)), Some(CastTy::Ptr(dst))) => {
2323+
let src_tail = tcx.struct_tail_without_normalization(src.ty);
2324+
let dst_tail = tcx.struct_tail_without_normalization(dst.ty);
2325+
2326+
if let ty::Dynamic(..) = src_tail.kind()
2327+
&& let ty::Dynamic(dst_tty, ..) = dst_tail.kind()
2328+
&& dst_tty.principal().is_some()
2329+
{
2330+
// Erase trait object lifetimes, to allow casts like `*mut dyn FnOnce()` -> `*mut dyn FnOnce() + 'static`.
2331+
let src_tail =
2332+
erase_single_trait_object_lifetime(tcx, src_tail);
2333+
let dst_tail =
2334+
erase_single_trait_object_lifetime(tcx, dst_tail);
2335+
2336+
let trait_ref = ty::TraitRef::new(
2337+
tcx,
2338+
tcx.require_lang_item(LangItem::Unsize, Some(span)),
2339+
[src_tail, dst_tail],
2340+
);
2341+
2342+
self.prove_trait_ref(
2343+
trait_ref,
2344+
location.to_locations(),
2345+
ConstraintCategory::Cast {
2346+
unsize_to: Some(tcx.fold_regions(dst_tail, |r, _| {
2347+
if let ty::ReVar(_) = r.kind() {
2348+
tcx.lifetimes.re_erased
2349+
} else {
2350+
r
2351+
}
2352+
})),
2353+
},
2354+
);
2355+
}
2356+
}
23232357
_ => {
23242358
span_mirbug!(
23252359
self,
@@ -2842,3 +2876,15 @@ impl<'tcx> TypeOp<'tcx> for InstantiateOpaqueType<'tcx> {
28422876
Ok(output)
28432877
}
28442878
}
2879+
2880+
fn erase_single_trait_object_lifetime<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> {
2881+
let &ty::Dynamic(tty, region, dyn_kind @ ty::Dyn) = ty.kind() else {
2882+
bug!("expected trait object")
2883+
};
2884+
2885+
if region.is_erased() {
2886+
return ty;
2887+
}
2888+
2889+
tcx.mk_ty_from_kind(ty::Dynamic(tty, tcx.lifetimes.re_erased, dyn_kind))
2890+
}

compiler/rustc_hir_typeck/src/cast.rs

Lines changed: 72 additions & 30 deletions
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, Diag, 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::bug;
4040
use rustc_middle::mir::Mutability;
@@ -73,7 +73,7 @@ enum PointerKind<'tcx> {
7373
/// No metadata attached, ie pointer to sized type or foreign type
7474
Thin,
7575
/// A trait object
76-
VTable(Option<ty::Binder<'tcx, ty::ExistentialTraitRef<'tcx>>>),
76+
VTable(&'tcx ty::List<ty::Binder<'tcx, ty::ExistentialPredicate<'tcx>>>),
7777
/// Slice
7878
Length,
7979
/// The unsize info of this projection or opaque type
@@ -101,7 +101,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
101101

102102
Ok(match *t.kind() {
103103
ty::Slice(_) | ty::Str => Some(PointerKind::Length),
104-
ty::Dynamic(tty, _, ty::Dyn) => Some(PointerKind::VTable(tty.principal())),
104+
ty::Dynamic(tty, _, ty::Dyn) => Some(PointerKind::VTable(tty)),
105105
ty::Adt(def, args) if def.is_struct() => match def.non_enum_variant().tail_opt() {
106106
None => Some(PointerKind::Thin),
107107
Some(f) => {
@@ -759,7 +759,7 @@ impl<'a, 'tcx> CastCheck<'tcx> {
759759
Err(CastError::IllegalCast)
760760
}
761761

762-
// ptr -> *
762+
// ptr -> ptr
763763
(Ptr(m_e), Ptr(m_c)) => self.check_ptr_ptr_cast(fcx, m_e, m_c), // ptr-ptr-cast
764764

765765
// ptr-addr-cast
@@ -803,40 +803,82 @@ impl<'a, 'tcx> CastCheck<'tcx> {
803803
fn check_ptr_ptr_cast(
804804
&self,
805805
fcx: &FnCtxt<'a, 'tcx>,
806-
m_expr: ty::TypeAndMut<'tcx>,
807-
m_cast: ty::TypeAndMut<'tcx>,
806+
m_src: ty::TypeAndMut<'tcx>,
807+
m_dst: ty::TypeAndMut<'tcx>,
808808
) -> Result<CastKind, CastError> {
809-
debug!("check_ptr_ptr_cast m_expr={:?} m_cast={:?}", m_expr, m_cast);
809+
debug!("check_ptr_ptr_cast m_expr={:?} m_cast={:?}", m_src, m_dst);
810810
// ptr-ptr cast. vtables must match.
811811

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

815-
let Some(cast_kind) = cast_kind else {
815+
match (src_kind, dst_kind) {
816816
// We can't cast if target pointer kind is unknown
817-
return Err(CastError::UnknownCastPtrKind);
818-
};
819-
820-
// Cast to thin pointer is OK
821-
if cast_kind == PointerKind::Thin {
822-
return Ok(CastKind::PtrPtrCast);
823-
}
817+
(_, None) => Err(CastError::UnknownCastPtrKind),
818+
// Cast to thin pointer is OK
819+
(_, Some(PointerKind::Thin)) => Ok(CastKind::PtrPtrCast),
824820

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

830-
// thin -> fat? report invalid cast (don't complain about vtable kinds)
831-
if expr_kind == PointerKind::Thin {
832-
return Err(CastError::SizedUnsizedCast);
833-
}
859+
fcx.register_predicate(obligation);
834860

835-
// vtable kinds must match
836-
if fcx.tcx.erase_regions(cast_kind) == fcx.tcx.erase_regions(expr_kind) {
837-
Ok(CastKind::PtrPtrCast)
838-
} else {
839-
Err(CastError::DifferingKinds)
861+
// FIXME: ideally we'd maybe add a flag here, so that borrowck knows that
862+
// it needs to borrowck this ptr cast. this is made annoying by the
863+
// fact that `thir` does not have `CastKind` and mir restores it
864+
// from types.
865+
Ok(CastKind::PtrPtrCast)
866+
}
867+
868+
// dyn Auto -> dyn Auto'? ok.
869+
(None, None)
870+
// dyn Trait -> dyn Auto? ok.
871+
| (Some(_), None)=> Ok(CastKind::PtrPtrCast),
872+
873+
// dyn Auto -> dyn Trait? not ok.
874+
(None, Some(_)) => Err(CastError::DifferingKinds),
875+
}
876+
}
877+
878+
// fat -> fat? metadata kinds must match
879+
(Some(src_kind), Some(dst_kind)) if src_kind == dst_kind => Ok(CastKind::PtrPtrCast),
880+
881+
(_, _) => Err(CastError::DifferingKinds),
840882
}
841883
}
842884

library/alloc/src/boxed.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2374,7 +2374,7 @@ impl dyn Error + Send {
23742374
let err: Box<dyn Error> = self;
23752375
<dyn Error>::downcast(err).map_err(|s| unsafe {
23762376
// Reapply the `Send` marker.
2377-
Box::from_raw(Box::into_raw(s) as *mut (dyn Error + Send))
2377+
mem::transmute::<Box<dyn Error>, Box<dyn Error + Send>>(s)
23782378
})
23792379
}
23802380
}
@@ -2387,8 +2387,8 @@ impl dyn Error + Send + Sync {
23872387
pub fn downcast<T: Error + 'static>(self: Box<Self>) -> Result<Box<T>, Box<Self>> {
23882388
let err: Box<dyn Error> = self;
23892389
<dyn Error>::downcast(err).map_err(|s| unsafe {
2390-
// Reapply the `Send + Sync` marker.
2391-
Box::from_raw(Box::into_raw(s) as *mut (dyn Error + Send + Sync))
2390+
// Reapply the `Send + Sync` markers.
2391+
mem::transmute::<Box<dyn Error>, Box<dyn Error + Send + Sync>>(s)
23922392
})
23932393
}
23942394
}
Lines changed: 2 additions & 2 deletions
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() {}
Lines changed: 11 additions & 0 deletions
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

Lines changed: 5 additions & 5 deletions
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
}
Lines changed: 30 additions & 14 deletions
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

Lines changed: 2 additions & 2 deletions
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 () {
Lines changed: 10 additions & 0 deletions
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)