Skip to content

Commit 198328a

Browse files
committed
Auto merge of #136776 - BoxyUwU:forbid_object_lifetime_casts, r=lcnr
Forbid freely casting lifetime bounds of dyn-types Fixes #136702 Reference PR: - rust-lang/reference#1951 Background reading about VTable calls/dyn compatibility: https://hackmd.io/zUp-sgZ0RFuFgsNfD4JqYw This PR causes us to start enforcing that lifetimes of dyn types are constrained through pointer casts. Currently on stable casting `*mut dyn Trait + 'a` to `*mut dyn Trait + 'b` passes with no requirements on `'a` or `'b`. Under this PR we now require `'a` to outlive `'b`. Even though the pointee of `*mut` pointers is considered to be invariant, we still use subtyping rather than equality. This mirrors how we support coercing `&mut dyn Trait + 'a` to `&mut dyn Trait + 'b` while requiring only `'a: 'b`. I believe this coercion is sound as there is no way for safe code to `mem::swap` two `dyn Trait`'s, and the same is definitely true of raw pointers. See the changes to this test: https://github.com/rust-lang/rust/pull/136776/files#diff-5523f20a800287a89c9f3e92646c887f3f7599be006b29dd9315f734a2137764 We also do not enforce any constraints on the lifetime of the dyn types if there are multiple pointer indirections. For example `*mut *mut dyn Trait + 'a` is allowed to be casted to `*mut *mut dyn Trait + 'b` with no requirements on `'a` or 'b`. This case is just a normal thin pointer cast where we do not care about the pointee type as there is no VTable in play. Test: https://github.com/rust-lang/rust/pull/136776/files#diff-3b6c8da342bb6530524158d686455a545bb8fd6f59cf5ff50d1d991ce74c9649 Finally, this is about *any* cast where the pointee is *unsized* with dyn-type metadata, not just *literally* the pointee type being a dyn-type. E.g. casting `*mut Wrapper<dyn Trait + 'a>` to `*mut Wrapper<dyn Trait + 'b>` requires `'a: 'b` under this PR. Test: https://github.com/rust-lang/rust/pull/136776/files#diff-ca0c44df62ae1ad1be70f892f01a59714336c7baf78602a5887ac1cf81145c96 ### Breakage This is a breaking change. Crater Report Comment: #136776 (comment) Generated Report: https://crater-reports.s3.amazonaws.com/pr-136776-2/index.html The majority of the breakage is caused by the `metrics` crate with 142 of the regressions, and the `may` crate with 14 of the regressions. The `metrics` crate has been fixed and has backported the fix to previous versions of the crate that were also affected. The`may` crate has also been fixed. PRs against affected crates have been opened and can be seen here: - belalang-project/belalang#6 - tyilo/multi-vec#1 - luksan/lox#1 - pfzetto/bring-your-own-memory-demo#1 - vitorhnn/bfr#1 - gipsyh/PPSMC#1 - orengine/orengine#33 - maroider/async_scoped_task#1 - WorldSEnder/scoped_worker_thread#1 - Wind-Corporation/trapiron#5 - Thombrom/snek#1 - Xudong-Huang/may#113 - metrics-rs/metrics#564 - DouglasDwyer/micropool#1 - Magicolo/phylactery#8 - HellButcher/pulz#29 - UxuginPython/rrtk#1 - wvwwvwwv/scalable-delayed-dealloc#4 - ultimaweapon/tsuki#32 There were six regressions I've not filed PRs against: - https://github.com/weiznich/diesel_benches depends on a ~6year old version of diesel (where the regression is) - https://crates.io/crates/cogo/0.1.36 is an old version of cogo, since that release cogo has already been updated to not depend on pattern this PR breaks - https://github.com/cruise-automation/webviz-rust-framework is an archived read only repo so 🤷‍♀️ - makepad_render, doesn't seem to have source available and is 6 years old 🤷‍♀️ - outsource-heap - not on github - zaplib - I couldn't get it to compile locally as it failed to compile a dependency r? `@ghost`
2 parents 377656d + 85aeb4f commit 198328a

18 files changed

+697
-57
lines changed

compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,7 @@ impl<'tcx> BorrowExplanation<'tcx> {
410410
cx.add_sized_or_copy_bound_info(err, category, &path);
411411

412412
if let ConstraintCategory::Cast {
413+
is_raw_ptr_dyn_type_cast: _,
413414
is_implicit_coercion: true,
414415
unsize_to: Some(unsize_ty),
415416
} = category

compiler/rustc_borrowck/src/diagnostics/region_errors.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,23 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
541541
self.add_placeholder_from_predicate_note(&mut diag, &path);
542542
self.add_sized_or_copy_bound_info(&mut diag, category, &path);
543543

544+
for constraint in &path {
545+
if let ConstraintCategory::Cast { is_raw_ptr_dyn_type_cast: true, .. } =
546+
constraint.category
547+
{
548+
diag.span_note(
549+
constraint.span,
550+
format!("raw pointer casts of trait objects cannot extend lifetimes"),
551+
);
552+
diag.note(format!(
553+
"this was previously accepted by the compiler but was changed recently"
554+
));
555+
diag.help(format!(
556+
"see <https://github.com/rust-lang/rust/issues/141402> for more information"
557+
));
558+
}
559+
}
560+
544561
self.buffer_error(diag);
545562
}
546563

compiler/rustc_borrowck/src/region_infer/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1697,6 +1697,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
16971697
// should be as limited as possible; the note is prone to false positives and this
16981698
// constraint usually isn't best to blame.
16991699
ConstraintCategory::Cast {
1700+
is_raw_ptr_dyn_type_cast: _,
17001701
unsize_to: Some(unsize_ty),
17011702
is_implicit_coercion: true,
17021703
} if to_region == self.universal_regions().fr_static

compiler/rustc_borrowck/src/type_check/mod.rs

Lines changed: 116 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1113,15 +1113,23 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
11131113
self.prove_predicate(
11141114
ty::ClauseKind::WellFormed(src_ty.into()),
11151115
location.to_locations(),
1116-
ConstraintCategory::Cast { is_implicit_coercion, unsize_to: None },
1116+
ConstraintCategory::Cast {
1117+
is_raw_ptr_dyn_type_cast: false,
1118+
is_implicit_coercion,
1119+
unsize_to: None,
1120+
},
11171121
);
11181122

11191123
let src_ty = self.normalize(src_ty, location);
11201124
if let Err(terr) = self.sub_types(
11211125
src_ty,
11221126
*ty,
11231127
location.to_locations(),
1124-
ConstraintCategory::Cast { is_implicit_coercion, unsize_to: None },
1128+
ConstraintCategory::Cast {
1129+
is_raw_ptr_dyn_type_cast: false,
1130+
is_implicit_coercion,
1131+
unsize_to: None,
1132+
},
11251133
) {
11261134
span_mirbug!(
11271135
self,
@@ -1142,7 +1150,11 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
11421150
self.prove_predicate(
11431151
ty::ClauseKind::WellFormed(src_ty.into()),
11441152
location.to_locations(),
1145-
ConstraintCategory::Cast { is_implicit_coercion, unsize_to: None },
1153+
ConstraintCategory::Cast {
1154+
is_raw_ptr_dyn_type_cast: false,
1155+
is_implicit_coercion,
1156+
unsize_to: None,
1157+
},
11461158
);
11471159

11481160
// The type that we see in the fcx is like
@@ -1155,7 +1167,11 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
11551167
src_ty,
11561168
*ty,
11571169
location.to_locations(),
1158-
ConstraintCategory::Cast { is_implicit_coercion, unsize_to: None },
1170+
ConstraintCategory::Cast {
1171+
is_raw_ptr_dyn_type_cast: false,
1172+
is_implicit_coercion,
1173+
unsize_to: None,
1174+
},
11591175
) {
11601176
span_mirbug!(
11611177
self,
@@ -1184,7 +1200,11 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
11841200
ty_fn_ptr_from,
11851201
*ty,
11861202
location.to_locations(),
1187-
ConstraintCategory::Cast { is_implicit_coercion, unsize_to: None },
1203+
ConstraintCategory::Cast {
1204+
is_raw_ptr_dyn_type_cast: false,
1205+
is_implicit_coercion,
1206+
unsize_to: None,
1207+
},
11881208
) {
11891209
span_mirbug!(
11901210
self,
@@ -1217,7 +1237,11 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
12171237
ty_fn_ptr_from,
12181238
*ty,
12191239
location.to_locations(),
1220-
ConstraintCategory::Cast { is_implicit_coercion, unsize_to: None },
1240+
ConstraintCategory::Cast {
1241+
is_raw_ptr_dyn_type_cast: false,
1242+
is_implicit_coercion,
1243+
unsize_to: None,
1244+
},
12211245
) {
12221246
span_mirbug!(
12231247
self,
@@ -1246,6 +1270,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
12461270
trait_ref,
12471271
location.to_locations(),
12481272
ConstraintCategory::Cast {
1273+
is_raw_ptr_dyn_type_cast: false,
12491274
is_implicit_coercion,
12501275
unsize_to: Some(unsize_to),
12511276
},
@@ -1271,7 +1296,11 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
12711296
*ty_from,
12721297
*ty_to,
12731298
location.to_locations(),
1274-
ConstraintCategory::Cast { is_implicit_coercion, unsize_to: None },
1299+
ConstraintCategory::Cast {
1300+
is_raw_ptr_dyn_type_cast: false,
1301+
is_implicit_coercion,
1302+
unsize_to: None,
1303+
},
12751304
) {
12761305
span_mirbug!(
12771306
self,
@@ -1334,7 +1363,11 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
13341363
*ty_elem,
13351364
*ty_to,
13361365
location.to_locations(),
1337-
ConstraintCategory::Cast { is_implicit_coercion, unsize_to: None },
1366+
ConstraintCategory::Cast {
1367+
is_raw_ptr_dyn_type_cast: false,
1368+
is_implicit_coercion,
1369+
unsize_to: None,
1370+
},
13381371
) {
13391372
span_mirbug!(
13401373
self,
@@ -1491,55 +1524,90 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
14911524
trait_ref,
14921525
location.to_locations(),
14931526
ConstraintCategory::Cast {
1527+
is_raw_ptr_dyn_type_cast: false,
14941528
is_implicit_coercion: true,
14951529
unsize_to: None,
14961530
},
14971531
);
1498-
} else if let ty::Dynamic(src_tty, _src_lt) =
1532+
} else if let ty::Dynamic(src_tty, src_lt) =
14991533
*self.struct_tail(src.ty, location).kind()
15001534
&& let ty::Dynamic(dst_tty, dst_lt) =
15011535
*self.struct_tail(dst.ty, location).kind()
1502-
&& src_tty.principal().is_some()
1503-
&& dst_tty.principal().is_some()
15041536
{
1505-
// This checks (lifetime part of) vtable validity for pointer casts,
1506-
// which is irrelevant when there are aren't principal traits on
1507-
// both sides (aka only auto traits).
1508-
//
1509-
// Note that other checks (such as denying `dyn Send` -> `dyn
1510-
// Debug`) are in `rustc_hir_typeck`.
1511-
1512-
// Remove auto traits.
1513-
// Auto trait checks are handled in `rustc_hir_typeck` as FCW.
1514-
let src_obj = Ty::new_dynamic(
1515-
tcx,
1516-
tcx.mk_poly_existential_predicates(
1517-
&src_tty.without_auto_traits().collect::<Vec<_>>(),
1518-
),
1519-
// FIXME: Once we disallow casting `*const dyn Trait + 'short`
1520-
// to `*const dyn Trait + 'long`, then this can just be `src_lt`.
1521-
dst_lt,
1522-
);
1523-
let dst_obj = Ty::new_dynamic(
1524-
tcx,
1525-
tcx.mk_poly_existential_predicates(
1526-
&dst_tty.without_auto_traits().collect::<Vec<_>>(),
1537+
match (src_tty.principal(), dst_tty.principal()) {
1538+
(Some(_), Some(_)) => {
1539+
// This checks (lifetime part of) vtable validity for pointer casts,
1540+
// which is irrelevant when there are aren't principal traits on
1541+
// both sides (aka only auto traits).
1542+
//
1543+
// Note that other checks (such as denying `dyn Send` -> `dyn
1544+
// Debug`) are in `rustc_hir_typeck`.
1545+
1546+
// Remove auto traits.
1547+
// Auto trait checks are handled in `rustc_hir_typeck`.
1548+
let src_obj = Ty::new_dynamic(
1549+
tcx,
1550+
tcx.mk_poly_existential_predicates(
1551+
&src_tty.without_auto_traits().collect::<Vec<_>>(),
1552+
),
1553+
src_lt,
1554+
);
1555+
let dst_obj = Ty::new_dynamic(
1556+
tcx,
1557+
tcx.mk_poly_existential_predicates(
1558+
&dst_tty.without_auto_traits().collect::<Vec<_>>(),
1559+
),
1560+
dst_lt,
1561+
);
1562+
1563+
debug!(?src_tty, ?dst_tty, ?src_obj, ?dst_obj);
1564+
1565+
// Trait parameters are invariant, the only part that actually has
1566+
// subtyping here is the lifetime bound of the dyn-type.
1567+
//
1568+
// For example in `dyn Trait<'a> + 'b <: dyn Trait<'c> + 'd` we would
1569+
// require that `'a == 'c` but only that `'b: 'd`.
1570+
//
1571+
// We must not allow freely casting lifetime bounds of dyn-types as it
1572+
// may allow for inaccessible VTable methods being callable: #136702
1573+
self.sub_types(
1574+
src_obj,
1575+
dst_obj,
1576+
location.to_locations(),
1577+
ConstraintCategory::Cast {
1578+
is_raw_ptr_dyn_type_cast: true,
1579+
is_implicit_coercion: false,
1580+
unsize_to: None,
1581+
},
1582+
)
1583+
.unwrap();
1584+
}
1585+
(None, None) => {
1586+
// The principalless (no non-auto traits) case:
1587+
// You can only cast `dyn Send + 'long` to `dyn Send + 'short`.
1588+
self.constraints.outlives_constraints.push(
1589+
OutlivesConstraint {
1590+
sup: src_lt.as_var(),
1591+
sub: dst_lt.as_var(),
1592+
locations: location.to_locations(),
1593+
span: location.to_locations().span(self.body),
1594+
category: ConstraintCategory::Cast {
1595+
is_raw_ptr_dyn_type_cast: true,
1596+
is_implicit_coercion: false,
1597+
unsize_to: None,
1598+
},
1599+
variance_info: ty::VarianceDiagInfo::default(),
1600+
from_closure: false,
1601+
},
1602+
);
1603+
}
1604+
(None, Some(_)) => bug!(
1605+
"introducing a principal should have errored in HIR typeck"
15271606
),
1528-
dst_lt,
1529-
);
1530-
1531-
debug!(?src_tty, ?dst_tty, ?src_obj, ?dst_obj);
1532-
1533-
self.sub_types(
1534-
src_obj,
1535-
dst_obj,
1536-
location.to_locations(),
1537-
ConstraintCategory::Cast {
1538-
is_implicit_coercion: false,
1539-
unsize_to: None,
1540-
},
1541-
)
1542-
.unwrap();
1607+
(Some(_), None) => {
1608+
bug!("dropping the principal should have been an unsizing cast")
1609+
}
1610+
}
15431611
}
15441612
}
15451613
CastKind::Transmute => {

compiler/rustc_middle/src/mir/query.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ pub enum ConstraintCategory<'tcx> {
108108
UseAsStatic,
109109
TypeAnnotation(AnnotationSource),
110110
Cast {
111+
is_raw_ptr_dyn_type_cast: bool,
111112
/// Whether this cast is a coercion that was automatically inserted by the compiler.
112113
is_implicit_coercion: bool,
113114
/// Whether this is an unsizing coercion and if yes, this contains the target type.

library/std/src/thread/lifecycle.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,12 @@ where
111111
// SAFETY: dynamic size and alignment of the Box remain the same. See below for why the
112112
// lifetime change is justified.
113113
let rust_start = unsafe {
114-
Box::from_raw(Box::into_raw(Box::new(rust_start)) as *mut (dyn FnOnce() + Send + 'static))
114+
let ptr = Box::into_raw(Box::new(rust_start));
115+
let ptr = crate::mem::transmute::<
116+
*mut (dyn FnOnce() + Send + '_),
117+
*mut (dyn FnOnce() + Send + 'static),
118+
>(ptr);
119+
Box::from_raw(ptr)
115120
};
116121

117122
let init = Box::new(ThreadInit { handle: thread.clone(), rust_start });
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//@ check-pass
2+
3+
// We allow extending lifetimes of object types if they are behind two layers
4+
// of pointer indirection (as opposed to one). This is because this is the more
5+
// general case of casting between two sized types (`*mut T as *mut U`).
6+
7+
trait Trait {
8+
fn foo(&self) {}
9+
}
10+
11+
fn bar<'a>(a: *mut *mut (dyn Trait + 'a)) -> *mut *mut (dyn Trait + 'static) {
12+
a as _
13+
}
14+
15+
fn main() {}

tests/ui/cast/ptr-to-ptr-different-regions.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
1-
//@ check-pass
2-
31
// https://github.com/rust-lang/rust/issues/113257
42

53
#![deny(trivial_casts)] // The casts here are not trivial.
64

7-
struct Foo<'a> { a: &'a () }
5+
struct Foo<'a> {
6+
a: &'a (),
7+
}
88

99
fn extend_lifetime_very_very_safely<'a>(v: *const Foo<'a>) -> *const Foo<'static> {
10-
// This should pass because raw pointer casts can do anything they want.
10+
// This should pass because raw pointer casts can do anything they want when
11+
// VTables are not involved
1112
v as *const Foo<'static>
1213
}
1314

1415
trait Trait {}
1516

17+
// We want to forbid this as extending lifetimes on object types may allow for
18+
// uncallable VTable methods to become accessible.
1619
fn assert_static<'a>(ptr: *mut (dyn Trait + 'a)) -> *mut (dyn Trait + 'static) {
1720
ptr as _
21+
//~^ ERROR: lifetime may not live long enough
1822
}
1923

2024
fn main() {
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
error: lifetime may not live long enough
2+
--> $DIR/ptr-to-ptr-different-regions.rs:20:5
3+
|
4+
LL | fn assert_static<'a>(ptr: *mut (dyn Trait + 'a)) -> *mut (dyn Trait + 'static) {
5+
| -- lifetime `'a` defined here
6+
LL | ptr as _
7+
| ^^^^^^^^ returning this value requires that `'a` must outlive `'static`
8+
|
9+
= note: requirement occurs because of a mutable pointer to `dyn Trait`
10+
= note: mutable pointers are invariant over their type parameter
11+
= help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
12+
note: raw pointer casts of trait objects cannot extend lifetimes
13+
--> $DIR/ptr-to-ptr-different-regions.rs:20:5
14+
|
15+
LL | ptr as _
16+
| ^^^^^^^^
17+
= note: this was previously accepted by the compiler but was changed recently
18+
= help: see <https://github.com/rust-lang/rust/issues/141402> for more information
19+
help: consider changing the trait object's explicit `'static` bound to the lifetime of argument `ptr`
20+
|
21+
LL - fn assert_static<'a>(ptr: *mut (dyn Trait + 'a)) -> *mut (dyn Trait + 'static) {
22+
LL + fn assert_static<'a>(ptr: *mut (dyn Trait + 'a)) -> *mut (dyn Trait + 'a) {
23+
|
24+
help: alternatively, add an explicit `'static` bound to this reference
25+
|
26+
LL - fn assert_static<'a>(ptr: *mut (dyn Trait + 'a)) -> *mut (dyn Trait + 'static) {
27+
LL + fn assert_static<'a>(ptr: *mut (dyn Trait + 'static)) -> *mut (dyn Trait + 'static) {
28+
|
29+
30+
error: aborting due to 1 previous error
31+
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// We want to forbid extending lifetimes on object types behind ptrs
2+
// as it may allow for uncallable VTable methods to become accessible.
3+
4+
trait Trait {
5+
fn foo(&self) {}
6+
}
7+
8+
struct MyWrap<T: ?Sized>(T);
9+
10+
fn bar<'a>(a: *mut MyWrap<(dyn Trait + 'a)>) -> *mut MyWrap<(dyn Trait + 'static)> {
11+
a as _
12+
//~^ ERROR: lifetime may not live long enough
13+
}
14+
15+
fn main() {}

0 commit comments

Comments
 (0)