Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Forbid object lifetime changing pointer casts #136776

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
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
33 changes: 6 additions & 27 deletions compiler/rustc_borrowck/src/type_check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ use rustc_mir_dataflow::move_paths::MoveData;
use rustc_mir_dataflow::points::DenseLocationMap;
use rustc_span::def_id::CRATE_DEF_ID;
use rustc_span::source_map::Spanned;
use rustc_span::{DUMMY_SP, Span, sym};
use rustc_span::{Span, sym};
use rustc_trait_selection::traits::query::type_op::custom::scrape_region_constraints;
use rustc_trait_selection::traits::query::type_op::{TypeOp, TypeOpOutput};
use tracing::{debug, instrument, trace};
Expand All @@ -52,7 +52,6 @@ use crate::polonius::legacy::{PoloniusFacts, PoloniusLocationTable};
use crate::polonius::{PoloniusContext, PoloniusLivenessContext};
use crate::region_infer::TypeTest;
use crate::region_infer::values::{LivenessValues, PlaceholderIndex, PlaceholderIndices};
use crate::renumber::RegionCtxt;
use crate::session_diagnostics::{MoveUnsized, SimdIntrinsicArgConst};
use crate::type_check::free_region_relations::{CreateResult, UniversalRegionRelations};
use crate::universal_regions::{DefiningTy, UniversalRegions};
Expand Down Expand Up @@ -2120,8 +2119,9 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
//
// Note that other checks (such as denying `dyn Send` -> `dyn
// Debug`) are in `rustc_hir_typeck`.
if let ty::Dynamic(src_tty, ..) = src_tail.kind()
&& let ty::Dynamic(dst_tty, ..) = dst_tail.kind()
if let ty::Dynamic(src_tty, src_region_bound, ..) = src_tail.kind()
&& let ty::Dynamic(dst_tty, dst_region_bound, ..) =
dst_tail.kind()
&& src_tty.principal().is_some()
&& dst_tty.principal().is_some()
Copy link
Member

Choose a reason for hiding this comment

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

Is the "when there aren't principal traits" still true? It seems like I could get into trouble with a fn (self: dyn Send + 'static), right?

{
Expand All @@ -2131,25 +2131,17 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
tcx.mk_poly_existential_predicates(
&src_tty.without_auto_traits().collect::<Vec<_>>(),
),
tcx.lifetimes.re_static,
*src_region_bound,
ty::Dyn,
));
let dst_obj = tcx.mk_ty_from_kind(ty::Dynamic(
tcx.mk_poly_existential_predicates(
&dst_tty.without_auto_traits().collect::<Vec<_>>(),
),
tcx.lifetimes.re_static,
*dst_region_bound,
ty::Dyn,
));

// Replace trait object lifetimes with fresh vars, to allow
// casts like
// `*mut dyn FnOnce() + 'a` -> `*mut dyn FnOnce() + 'static`
let src_obj =
freshen_single_trait_object_lifetime(self.infcx, src_obj);
let dst_obj =
freshen_single_trait_object_lifetime(self.infcx, dst_obj);

debug!(?src_tty, ?dst_tty, ?src_obj, ?dst_obj);

self.sub_types(
Expand Down Expand Up @@ -2706,16 +2698,3 @@ impl<'tcx> TypeOp<'tcx> for InstantiateOpaqueType<'tcx> {
Ok(output)
}
}

fn freshen_single_trait_object_lifetime<'tcx>(
infcx: &BorrowckInferCtxt<'tcx>,
ty: Ty<'tcx>,
) -> Ty<'tcx> {
let &ty::Dynamic(tty, _, dyn_kind @ ty::Dyn) = ty.kind() else { bug!("expected trait object") };

let fresh = infcx
.next_region_var(rustc_infer::infer::RegionVariableOrigin::MiscVariable(DUMMY_SP), || {
RegionCtxt::Unknown
});
infcx.tcx.mk_ty_from_kind(ty::Dynamic(tty, fresh, dyn_kind))
}
7 changes: 5 additions & 2 deletions library/std/src/thread/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -578,8 +578,11 @@ impl Builder {
let main = Box::new(main);
// SAFETY: dynamic size and alignment of the Box remain the same. See below for why the
// lifetime change is justified.
let main =
unsafe { Box::from_raw(Box::into_raw(main) as *mut (dyn FnOnce() + Send + 'static)) };
let main = unsafe {
let ptr = Box::into_raw(main) as *mut (dyn FnOnce() + Send + '_);
let ptr: *mut (dyn FnOnce() + Send + 'static) = crate::mem::transmute(ptr);
Box::from_raw(ptr)
};

Ok(JoinInner {
// SAFETY:
Expand Down
7 changes: 4 additions & 3 deletions tests/ui/cast/ptr-to-ptr-different-regions.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
//@ check-pass

// https://github.com/rust-lang/rust/issues/113257

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

struct Foo<'a> { a: &'a () }
struct Foo<'a> {
a: &'a (),
}

fn extend_lifetime_very_very_safely<'a>(v: *const Foo<'a>) -> *const Foo<'static> {
// This should pass because raw pointer casts can do anything they want.
Expand All @@ -15,6 +15,7 @@ trait Trait {}

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

fn main() {
Expand Down
22 changes: 22 additions & 0 deletions tests/ui/cast/ptr-to-ptr-different-regions.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
error: lifetime may not live long enough
--> $DIR/ptr-to-ptr-different-regions.rs:17:5
|
LL | fn assert_static<'a>(ptr: *mut (dyn Trait + 'a)) -> *mut (dyn Trait + 'static) {
| -- lifetime `'a` defined here
LL | ptr as _
| ^^^^^^^^ returning this value requires that `'a` must outlive `'static`
|
= note: requirement occurs because of a mutable pointer to `dyn Trait`
= note: mutable pointers are invariant over their type parameter
= help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
help: consider changing the trait object's explicit `'static` bound to the lifetime of argument `ptr`
|
LL | fn assert_static<'a>(ptr: *mut (dyn Trait + 'a)) -> *mut (dyn Trait + 'a) {
| ~~
help: alternatively, add an explicit `'static` bound to this reference
|
LL | fn assert_static<'a>(ptr: *mut (dyn Trait + 'static)) -> *mut (dyn Trait + 'static) {
| ~~~~~~~~~~~~~~~~~~~~~~~~~~

error: aborting due to 1 previous error

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
@@ -1,5 +1,3 @@
//@ check-pass

trait Trait<'a> {}

fn remove_auto<'a>(x: *mut (dyn Trait<'a> + Send)) -> *mut dyn Trait<'a> {
Expand All @@ -8,6 +6,7 @@ fn remove_auto<'a>(x: *mut (dyn Trait<'a> + Send)) -> *mut dyn Trait<'a> {

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

fn cast_away_higher_ranked<'a>(x: *mut dyn for<'b> Trait<'b>) -> *mut dyn Trait<'a> {
Expand All @@ -33,6 +32,7 @@ fn cast_inherent_lt_wrap<'a, 'b>(
x: *mut (dyn Trait<'static> + 'a),
) -> *mut Wrapper<dyn Trait<'static> + 'b> {
x as _
//~^ ERROR: lifetime may not live long enough
}

fn cast_away_higher_ranked_wrap<'a>(x: *mut dyn for<'b> Trait<'b>) -> *mut Wrapper<dyn Trait<'a>> {
Expand Down
33 changes: 33 additions & 0 deletions tests/ui/cast/ptr-to-trait-obj-ok.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
error: lifetime may not live long enough
--> $DIR/ptr-to-trait-obj-ok.rs:8:5
|
LL | fn cast_inherent_lt<'a, 'b>(x: *mut (dyn Trait<'static> + 'a)) -> *mut (dyn Trait<'static> + '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 Trait<'_>`
= note: mutable pointers are invariant over their type parameter
= help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
Copy link
Member

Choose a reason for hiding this comment

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

Is it possible for us to note something in these errors pointing at some docs for why this is a bad idea? I could easily see someone just changing this to a transmute without realizing this is an intentional limitation of as casts.

I guess this falls under "dyn Trait metadata is invalid if it is not a pointer to a vtable for Trait that matches the actual dynamic trait the pointer or reference points to" in some sense (from https://doc.rust-lang.org/nightly/nomicon/what-unsafe-does.html) but maybe that should be clarified to say that it's not just "trait" but rather "trait and lifetime bounds on it" (or explicitly note this is a safety, not validity, invariant)...

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah I'm gonna try improve the diagnostics here before this can land as it's not a very helpful error message to get both from the POV of someone whose code just broke, or from the POV of someone who just tried to ptr cast the lifetimes in new code


error: lifetime may not live long enough
--> $DIR/ptr-to-trait-obj-ok.rs:34:5
|
LL | fn cast_inherent_lt_wrap<'a, '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 Trait<'_>>`
= note: mutable pointers are invariant over their type parameter
= help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance

error: aborting due to 2 previous errors

Loading