Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 22 additions & 9 deletions compiler/rustc_borrowck/src/type_check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1563,15 +1563,6 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
//
// We must not allow freely casting lifetime bounds of dyn-types as it
// may allow for inaccessible VTable methods being callable: #136702
//
// We don't enforce this for casts of principal-less dyn types as their
// VTables do not contain any functions with `Self: 'a` bounds that
// could start holding after a pointer cast.
//
// We also don't enforce this for casts of pointers to pointers to dyn
// types. E.g. `*mut *mut dyn Trait + 'a -> *mut *mut dyn Trait +
// 'static` is allowed. This is fine because there is no actual VTable
// in play.
self.sub_types(
src_obj,
dst_obj,
Expand All @@ -1583,6 +1574,28 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
},
)
.unwrap();
} else if let ty::Dynamic(src_tty, src_lt) =
*self.struct_tail(src.ty, location).kind()
&& let ty::Dynamic(dst_tty, dst_lt) =
*self.struct_tail(dst.ty, location).kind()
&& src_tty.principal().is_none()
&& dst_tty.principal().is_none()
Comment on lines +1581 to +1582
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Darksonn I think this currently means that casting something like: *mut dyn Trait + Send + 'a to *mut Send + 'b won't check 'a: 'b. We ought to drop the src_tty.principal().is_none() check here, unless I'm misreading this code :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you help come up with a test that catches this? I tried adding a few, but I got the results I expected.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gotta say I would have expected those tests to not be emitting errors right now and can't figure out just from skimming the code why they wouldn't be 🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, it just happens to work due to MIR lowering jank. doing this kind of cast seems to result in both an Unsize cast to drop the principal, and then also a PtrToPtr cast where neither side has a principal (which then gets lifetime checked from ur changes)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fn unprincipled3_static<'a>(x: *mut (dyn Trait + Send + 'a)) -> *mut (dyn Send + 'static) {
    x as _
}
fn unprincipled3_static(_1: *mut dyn Trait + Send) -> *mut dyn Send {
    debug x => _1;                       // in scope 0 at ./qux.rs:2:29: 2:30
    let mut _0: *mut dyn std::marker::Send; // return place in scope 0 at ./qux.rs:2:65: 2:90
    let mut _2: *mut dyn std::marker::Send; // in scope 0 at ./qux.rs:3:5: 3:11
    let mut _3: *mut dyn std::marker::Send; // in scope 0 at ./qux.rs:3:5: 3:11
    let mut _4: *mut dyn std::marker::Send; // in scope 0 at ./qux.rs:3:5: 3:11
    let mut _5: *mut dyn Trait + std::marker::Send; // in scope 0 at ./qux.rs:3:5: 3:6

        _5 = copy _1;                    // scope 0 at ./qux.rs:3:5: 3:6
        _4 = move _5 as *mut dyn std::marker::Send (PointerCoercion(Unsize, AsCast)); // scope 0 at ./qux.rs:3:5: 3:11
        StorageDead(_5);                 // scope 0 at ./qux.rs:3:5: 3:6
        _3 = move _4 as *mut dyn std::marker::Send (PtrToPtr); // scope 0 at ./qux.rs:3:5: 3:11
        AscribeUserType(_3, o, UserTypeProjection { base: UserType(0), projs: [] }); // scope 0 at ./qux.rs:3:10: 3:11
        _2 = copy _3;                    // scope 0 at ./qux.rs:3:5: 3:11
        StorageDead(_4);                 // scope 0 at ./qux.rs:3:10: 3:11
        _0 = move _2 as *mut dyn std::marker::Send (PointerCoercion(Unsize, Implicit)); // scope 0 at ./qux.rs:3:5: 3:11

this is the mir from that test case. note the 3 separate pointer casts, one of which is a PtrToPtr between two pointers to dyn Send trait objects xd

Copy link
Member Author

@BoxyUwU BoxyUwU Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could ICE on encountering (Some, None), run crater and if anything blows up then you've got a test case, otherwise just leave it like that and then in the future if someone files an ICE report we have a test case :>

Alternatively we just write the code to handle (Some, None) properly and then hope it's correct without a test case

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you expect uncovered (Some, None) cases to ICE rather than be accepted without error?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It makes sense that it happens in the coercion. After all, ptr-to-ptr casts are only possible in cases where the vtable is the same, but I would not expect dyn T1 + Send to have the same vtable as dyn Send. Especially considering that implies dyn T1 + Send has same vtable as dyn Send has same vtable as dyn T2 + Send. So such ptr-to-ptr casts probably simply do not exist.

{
// The principalless (no non-auto traits) case:
// You can only cast `dyn Send + 'long` to `dyn Send + 'short`.
self.constraints.outlives_constraints.push(OutlivesConstraint {
sup: src_lt.as_var(),
sub: dst_lt.as_var(),
locations: location.to_locations(),
span: location.to_locations().span(self.body),
category: ConstraintCategory::Cast {
is_raw_ptr_dyn_type_cast: true,
is_implicit_coercion: false,
unsize_to: None,
},
variance_info: ty::VarianceDiagInfo::default(),
from_closure: false,
});
}
}
CastKind::Transmute => {
Expand Down
47 changes: 44 additions & 3 deletions tests/ui/cast/ptr-to-ptr-principalless.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,49 @@
//@ check-pass
// Test cases involving principal-less traits (dyn Send without a primary trait).

fn lifetime_cast_send<'a, 'b>(a: *mut (dyn Send + 'a)) -> *mut (dyn Send + 'b) {
a as _
struct Wrapper<T: ?Sized>(T);

// Cast to same auto trait

fn unprincipled<'a, 'b>(x: *mut (dyn Send + 'a)) -> *mut (dyn Send + 'b) {
x as _
//~^ ERROR: lifetime may not live long enough
}

fn unprincipled_static<'a>(x: *mut (dyn Send + 'a)) -> *mut (dyn Send + 'static) {
x as _
//~^ ERROR: lifetime may not live long enough
}

fn unprincipled_wrap<'a, 'b>(x: *mut (dyn Send + 'a)) -> *mut Wrapper<dyn Send + 'b> {
x as _
//~^ ERROR: lifetime may not live long enough
}

fn unprincipled_wrap_static<'a>(x: *mut (dyn Send + 'a)) -> *mut Wrapper<dyn Send + 'static> {
x as _
//~^ ERROR: lifetime may not live long enough
}

// Cast to different auto trait

fn unprincipled2<'a, 'b>(x: *mut (dyn Send + 'a)) -> *mut (dyn Sync + 'b) {
x as _
//~^ ERROR: lifetime may not live long enough
}

fn unprincipled2_static<'a>(x: *mut (dyn Send + 'a)) -> *mut (dyn Sync + 'static) {
x as _
//~^ ERROR: lifetime may not live long enough
}

fn unprincipled_wrap2<'a, 'b>(x: *mut (dyn Send + 'a)) -> *mut Wrapper<dyn Sync + 'b> {
x as _
//~^ ERROR: lifetime may not live long enough
}

fn unprincipled_wrap2_static<'a>(x: *mut (dyn Send + 'a)) -> *mut Wrapper<dyn Sync + 'static> {
x as _
//~^ ERROR: lifetime may not live long enough
}

fn main() {}
206 changes: 206 additions & 0 deletions tests/ui/cast/ptr-to-ptr-principalless.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
error: lifetime may not live long enough
--> $DIR/ptr-to-ptr-principalless.rs:8:5
|
LL | fn unprincipled<'a, 'b>(x: *mut (dyn Send + 'a)) -> *mut (dyn Send + 'b) {
| -- -- lifetime `'b` defined here
| |
| lifetime `'a` defined here
LL | x as _
| ^^^^^^ function was supposed to return data with lifetime `'b` but it is returning data with lifetime `'a`
|
= help: consider adding the following bound: `'a: 'b`
= note: requirement occurs because of a mutable pointer to `dyn Send`
= note: mutable pointers are invariant over their type parameter
= help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
note: raw pointer casts of trait objects do not cast away lifetimes
--> $DIR/ptr-to-ptr-principalless.rs:8:5
|
LL | x as _
| ^^^^^^
= note: this was previously accepted by the compiler but was changed recently
= help: see <https://github.com/rust-lang/rust/issues/141402> for more information

error: lifetime may not live long enough
--> $DIR/ptr-to-ptr-principalless.rs:13:5
|
LL | fn unprincipled_static<'a>(x: *mut (dyn Send + 'a)) -> *mut (dyn Send + 'static) {
| -- lifetime `'a` defined here
LL | x as _
| ^^^^^^ returning this value requires that `'a` must outlive `'static`
|
= note: requirement occurs because of a mutable pointer to `dyn Send`
= note: mutable pointers are invariant over their type parameter
= help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
note: raw pointer casts of trait objects do not cast away lifetimes
--> $DIR/ptr-to-ptr-principalless.rs:13:5
|
LL | x as _
| ^^^^^^
= note: this was previously accepted by the compiler but was changed recently
= help: see <https://github.com/rust-lang/rust/issues/141402> for more information
help: consider changing the trait object's explicit `'static` bound to the lifetime of argument `x`
|
LL - fn unprincipled_static<'a>(x: *mut (dyn Send + 'a)) -> *mut (dyn Send + 'static) {
LL + fn unprincipled_static<'a>(x: *mut (dyn Send + 'a)) -> *mut (dyn Send + 'a) {
|
help: alternatively, add an explicit `'static` bound to this reference
|
LL - fn unprincipled_static<'a>(x: *mut (dyn Send + 'a)) -> *mut (dyn Send + 'static) {
LL + fn unprincipled_static<'a>(x: *mut (dyn Send + 'static)) -> *mut (dyn Send + 'static) {
|

error: lifetime may not live long enough
--> $DIR/ptr-to-ptr-principalless.rs:18:5
|
LL | fn unprincipled_wrap<'a, 'b>(x: *mut (dyn Send + 'a)) -> *mut Wrapper<dyn Send + 'b> {
| -- -- lifetime `'b` defined here
| |
| lifetime `'a` defined here
LL | x as _
| ^^^^^^ function was supposed to return data with lifetime `'b` but it is returning data with lifetime `'a`
|
= help: consider adding the following bound: `'a: 'b`
= note: requirement occurs because of a mutable pointer to `Wrapper<dyn Send>`
= note: mutable pointers are invariant over their type parameter
= help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
note: raw pointer casts of trait objects do not cast away lifetimes
--> $DIR/ptr-to-ptr-principalless.rs:18:5
|
LL | x as _
| ^^^^^^
= note: this was previously accepted by the compiler but was changed recently
= help: see <https://github.com/rust-lang/rust/issues/141402> for more information

error: lifetime may not live long enough
--> $DIR/ptr-to-ptr-principalless.rs:23:5
|
LL | fn unprincipled_wrap_static<'a>(x: *mut (dyn Send + 'a)) -> *mut Wrapper<dyn Send + 'static> {
| -- lifetime `'a` defined here
LL | x as _
| ^^^^^^ returning this value requires that `'a` must outlive `'static`
|
= note: requirement occurs because of a mutable pointer to `Wrapper<dyn Send>`
= note: mutable pointers are invariant over their type parameter
= help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
note: raw pointer casts of trait objects do not cast away lifetimes
--> $DIR/ptr-to-ptr-principalless.rs:23:5
|
LL | x as _
| ^^^^^^
= note: this was previously accepted by the compiler but was changed recently
= help: see <https://github.com/rust-lang/rust/issues/141402> for more information
help: consider changing the trait object's explicit `'static` bound to the lifetime of argument `x`
|
LL - fn unprincipled_wrap_static<'a>(x: *mut (dyn Send + 'a)) -> *mut Wrapper<dyn Send + 'static> {
LL + fn unprincipled_wrap_static<'a>(x: *mut (dyn Send + 'a)) -> *mut Wrapper<dyn Send + 'a> {
|
help: alternatively, add an explicit `'static` bound to this reference
|
LL - fn unprincipled_wrap_static<'a>(x: *mut (dyn Send + 'a)) -> *mut Wrapper<dyn Send + 'static> {
LL + fn unprincipled_wrap_static<'a>(x: *mut (dyn Send + 'static)) -> *mut Wrapper<dyn Send + 'static> {
|

error: lifetime may not live long enough
--> $DIR/ptr-to-ptr-principalless.rs:30:5
|
LL | fn unprincipled2<'a, 'b>(x: *mut (dyn Send + 'a)) -> *mut (dyn Sync + 'b) {
| -- -- lifetime `'b` defined here
| |
| lifetime `'a` defined here
LL | x as _
| ^^^^^^ function was supposed to return data with lifetime `'b` but it is returning data with lifetime `'a`
|
= help: consider adding the following bound: `'a: 'b`
= note: requirement occurs because of a mutable pointer to `dyn Sync`
= note: mutable pointers are invariant over their type parameter
= help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
note: raw pointer casts of trait objects do not cast away lifetimes
--> $DIR/ptr-to-ptr-principalless.rs:30:5
|
LL | x as _
| ^^^^^^
= note: this was previously accepted by the compiler but was changed recently
= help: see <https://github.com/rust-lang/rust/issues/141402> for more information

error: lifetime may not live long enough
--> $DIR/ptr-to-ptr-principalless.rs:35:5
|
LL | fn unprincipled2_static<'a>(x: *mut (dyn Send + 'a)) -> *mut (dyn Sync + 'static) {
| -- lifetime `'a` defined here
LL | x as _
| ^^^^^^ returning this value requires that `'a` must outlive `'static`
|
= note: requirement occurs because of a mutable pointer to `dyn Sync`
= note: mutable pointers are invariant over their type parameter
= help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
note: raw pointer casts of trait objects do not cast away lifetimes
--> $DIR/ptr-to-ptr-principalless.rs:35:5
|
LL | x as _
| ^^^^^^
= note: this was previously accepted by the compiler but was changed recently
= help: see <https://github.com/rust-lang/rust/issues/141402> for more information
help: consider changing the trait object's explicit `'static` bound to the lifetime of argument `x`
|
LL - fn unprincipled2_static<'a>(x: *mut (dyn Send + 'a)) -> *mut (dyn Sync + 'static) {
LL + fn unprincipled2_static<'a>(x: *mut (dyn Send + 'a)) -> *mut (dyn Sync + 'a) {
|
help: alternatively, add an explicit `'static` bound to this reference
|
LL - fn unprincipled2_static<'a>(x: *mut (dyn Send + 'a)) -> *mut (dyn Sync + 'static) {
LL + fn unprincipled2_static<'a>(x: *mut (dyn Send + 'static)) -> *mut (dyn Sync + 'static) {
|

error: lifetime may not live long enough
--> $DIR/ptr-to-ptr-principalless.rs:40:5
|
LL | fn unprincipled_wrap2<'a, 'b>(x: *mut (dyn Send + 'a)) -> *mut Wrapper<dyn Sync + 'b> {
| -- -- lifetime `'b` defined here
| |
| lifetime `'a` defined here
LL | x as _
| ^^^^^^ function was supposed to return data with lifetime `'b` but it is returning data with lifetime `'a`
|
= help: consider adding the following bound: `'a: 'b`
= note: requirement occurs because of a mutable pointer to `Wrapper<dyn Sync>`
= note: mutable pointers are invariant over their type parameter
= help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
note: raw pointer casts of trait objects do not cast away lifetimes
--> $DIR/ptr-to-ptr-principalless.rs:40:5
|
LL | x as _
| ^^^^^^
= note: this was previously accepted by the compiler but was changed recently
= help: see <https://github.com/rust-lang/rust/issues/141402> for more information

error: lifetime may not live long enough
--> $DIR/ptr-to-ptr-principalless.rs:45:5
|
LL | fn unprincipled_wrap2_static<'a>(x: *mut (dyn Send + 'a)) -> *mut Wrapper<dyn Sync + 'static> {
| -- lifetime `'a` defined here
LL | x as _
| ^^^^^^ returning this value requires that `'a` must outlive `'static`
|
= note: requirement occurs because of a mutable pointer to `Wrapper<dyn Sync>`
= note: mutable pointers are invariant over their type parameter
= help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
note: raw pointer casts of trait objects do not cast away lifetimes
--> $DIR/ptr-to-ptr-principalless.rs:45:5
|
LL | x as _
| ^^^^^^
= note: this was previously accepted by the compiler but was changed recently
= help: see <https://github.com/rust-lang/rust/issues/141402> for more information
help: consider changing the trait object's explicit `'static` bound to the lifetime of argument `x`
|
LL - fn unprincipled_wrap2_static<'a>(x: *mut (dyn Send + 'a)) -> *mut Wrapper<dyn Sync + 'static> {
LL + fn unprincipled_wrap2_static<'a>(x: *mut (dyn Send + 'a)) -> *mut Wrapper<dyn Sync + 'a> {
|
help: alternatively, add an explicit `'static` bound to this reference
|
LL - fn unprincipled_wrap2_static<'a>(x: *mut (dyn Send + 'a)) -> *mut Wrapper<dyn Sync + 'static> {
LL + fn unprincipled_wrap2_static<'a>(x: *mut (dyn Send + 'static)) -> *mut Wrapper<dyn Sync + 'static> {
|

error: aborting due to 8 previous errors

4 changes: 2 additions & 2 deletions tests/ui/cast/ptr-to-trait-obj-ok.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ fn cast_away_higher_ranked<'a>(x: *mut dyn for<'b> Trait<'b>) -> *mut dyn Trait<
x as _
}

fn unprincipled<'a, 'b>(x: *mut (dyn Send + 'a)) -> *mut (dyn Sync + 'b) {
fn unprincipled<'a: 'b, 'b>(x: *mut (dyn Send + 'a)) -> *mut (dyn Sync + 'b) {
x as _
}

Expand All @@ -41,7 +41,7 @@ fn cast_away_higher_ranked_wrap<'a>(x: *mut dyn for<'b> Trait<'b>) -> *mut Wrapp
x as _
}

fn unprincipled_wrap<'a, 'b>(x: *mut (dyn Send + 'a)) -> *mut Wrapper<dyn Sync + 'b> {
fn unprincipled_wrap<'a: 'b, 'b>(x: *mut (dyn Send + 'a)) -> *mut Wrapper<dyn Sync + 'b> {
x as _
}

Expand Down
Loading