Skip to content

Commit 78b89eb

Browse files
committed
Auto merge of #145469 - cjgillot:split-transmute, r=nnethercote
Split transmute check from HIR typeck This resolves a FIXME in the implementation of `check_transmute`. `check_transmute` needs to compute type layout, hence needing to see reveal opaques and all type aliases. Having this inside typeck causes a cycle. For instance: `tests/ui/impl-trait/transmute/in-defining-scope.rs`. This PR moves the transmute check outside of typeck, by putting the list of deferred transmute checks in typeck results.
2 parents 6545b05 + 0327e2b commit 78b89eb

26 files changed

+262
-263
lines changed

compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -83,14 +83,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
8383
*self.deferred_cast_checks.borrow_mut() = deferred_cast_checks;
8484
}
8585

86-
pub(in super::super) fn check_transmutes(&self) {
87-
let mut deferred_transmute_checks = self.deferred_transmute_checks.borrow_mut();
88-
debug!("FnCtxt::check_transmutes: {} deferred checks", deferred_transmute_checks.len());
89-
for (from, to, hir_id) in deferred_transmute_checks.drain(..) {
90-
self.check_transmute(from, to, hir_id);
91-
}
92-
}
93-
9486
pub(in super::super) fn check_asms(&self) {
9587
let mut deferred_asm_checks = self.deferred_asm_checks.borrow_mut();
9688
debug!("FnCtxt::check_asm: {} deferred checks", deferred_asm_checks.len());

compiler/rustc_hir_typeck/src/intrinsicck.rs

Lines changed: 104 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,10 @@ use rustc_hir as hir;
77
use rustc_index::Idx;
88
use rustc_middle::bug;
99
use rustc_middle::ty::layout::{LayoutError, SizeSkeleton};
10-
use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt};
10+
use rustc_middle::ty::{self, Ty, TyCtxt};
11+
use rustc_span::def_id::LocalDefId;
1112
use tracing::trace;
1213

13-
use super::FnCtxt;
14-
1514
/// If the type is `Option<T>`, it will return `T`, otherwise
1615
/// the type itself. Works on most `Option`-like types.
1716
fn unpack_option_like<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> {
@@ -39,119 +38,117 @@ fn unpack_option_like<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> {
3938
ty
4039
}
4140

42-
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
43-
/// FIXME: Move this check out of typeck, since it'll easily cycle when revealing opaques,
44-
/// and we shouldn't need to check anything here if the typeck results are tainted.
45-
pub(crate) fn check_transmute(&self, from: Ty<'tcx>, to: Ty<'tcx>, hir_id: HirId) {
46-
let tcx = self.tcx;
47-
let dl = &tcx.data_layout;
48-
let span = tcx.hir_span(hir_id);
49-
let normalize = |ty| {
50-
let ty = self.resolve_vars_if_possible(ty);
51-
if let Ok(ty) =
52-
self.tcx.try_normalize_erasing_regions(self.typing_env(self.param_env), ty)
53-
{
54-
ty
41+
/// Try to display a sensible error with as much information as possible.
42+
fn skeleton_string<'tcx>(
43+
ty: Ty<'tcx>,
44+
sk: Result<SizeSkeleton<'tcx>, &'tcx LayoutError<'tcx>>,
45+
) -> String {
46+
match sk {
47+
Ok(SizeSkeleton::Pointer { tail, .. }) => format!("pointer to `{tail}`"),
48+
Ok(SizeSkeleton::Known(size, _)) => {
49+
if let Some(v) = u128::from(size.bytes()).checked_mul(8) {
50+
format!("{v} bits")
5551
} else {
56-
Ty::new_error_with_message(
57-
tcx,
58-
span,
59-
"tried to normalize non-wf type in check_transmute",
60-
)
52+
// `u128` should definitely be able to hold the size of different architectures
53+
// larger sizes should be reported as error `are too big for the target architecture`
54+
// otherwise we have a bug somewhere
55+
bug!("{:?} overflow for u128", size)
6156
}
62-
};
63-
let from = normalize(from);
64-
let to = normalize(to);
65-
trace!(?from, ?to);
66-
if from.has_non_region_infer() || to.has_non_region_infer() {
67-
// Note: this path is currently not reached in any test, so any
68-
// example that triggers this would be worth minimizing and
69-
// converting into a test.
70-
self.dcx().span_bug(span, "argument to transmute has inference variables");
7157
}
72-
// Transmutes that are only changing lifetimes are always ok.
73-
if from == to {
74-
return;
58+
Ok(SizeSkeleton::Generic(size)) => {
59+
format!("generic size {size}")
60+
}
61+
Err(LayoutError::TooGeneric(bad)) => {
62+
if *bad == ty {
63+
"this type does not have a fixed size".to_owned()
64+
} else {
65+
format!("size can vary because of {bad}")
66+
}
67+
}
68+
Err(err) => err.to_string(),
69+
}
70+
}
71+
72+
fn check_transmute<'tcx>(
73+
tcx: TyCtxt<'tcx>,
74+
typing_env: ty::TypingEnv<'tcx>,
75+
from: Ty<'tcx>,
76+
to: Ty<'tcx>,
77+
hir_id: HirId,
78+
) {
79+
let span = || tcx.hir_span(hir_id);
80+
let normalize = |ty| {
81+
if let Ok(ty) = tcx.try_normalize_erasing_regions(typing_env, ty) {
82+
ty
83+
} else {
84+
Ty::new_error_with_message(
85+
tcx,
86+
span(),
87+
format!("tried to normalize non-wf type {ty:#?} in check_transmute"),
88+
)
7589
}
90+
};
7691

77-
let skel = |ty| SizeSkeleton::compute(ty, tcx, self.typing_env(self.param_env));
78-
let sk_from = skel(from);
79-
let sk_to = skel(to);
80-
trace!(?sk_from, ?sk_to);
92+
let from = normalize(from);
93+
let to = normalize(to);
94+
trace!(?from, ?to);
8195

82-
// Check for same size using the skeletons.
83-
if let (Ok(sk_from), Ok(sk_to)) = (sk_from, sk_to) {
84-
if sk_from.same_size(sk_to) {
85-
return;
86-
}
96+
// Transmutes that are only changing lifetimes are always ok.
97+
if from == to {
98+
return;
99+
}
87100

88-
// Special-case transmuting from `typeof(function)` and
89-
// `Option<typeof(function)>` to present a clearer error.
90-
let from = unpack_option_like(tcx, from);
91-
if let (&ty::FnDef(..), SizeSkeleton::Known(size_to, _)) = (from.kind(), sk_to)
92-
&& size_to == Pointer(dl.instruction_address_space).size(&tcx)
93-
{
94-
struct_span_code_err!(self.dcx(), span, E0591, "can't transmute zero-sized type")
95-
.with_note(format!("source type: {from}"))
96-
.with_note(format!("target type: {to}"))
97-
.with_help("cast with `as` to a pointer instead")
98-
.emit();
99-
return;
100-
}
101+
let sk_from = SizeSkeleton::compute(from, tcx, typing_env);
102+
let sk_to = SizeSkeleton::compute(to, tcx, typing_env);
103+
trace!(?sk_from, ?sk_to);
104+
105+
// Check for same size using the skeletons.
106+
if let Ok(sk_from) = sk_from
107+
&& let Ok(sk_to) = sk_to
108+
{
109+
if sk_from.same_size(sk_to) {
110+
return;
101111
}
102112

103-
// Try to display a sensible error with as much information as possible.
104-
let skeleton_string = |ty: Ty<'tcx>, sk: Result<_, &_>| match sk {
105-
Ok(SizeSkeleton::Pointer { tail, .. }) => format!("pointer to `{tail}`"),
106-
Ok(SizeSkeleton::Known(size, _)) => {
107-
if let Some(v) = u128::from(size.bytes()).checked_mul(8) {
108-
format!("{v} bits")
109-
} else {
110-
// `u128` should definitely be able to hold the size of different architectures
111-
// larger sizes should be reported as error `are too big for the target architecture`
112-
// otherwise we have a bug somewhere
113-
bug!("{:?} overflow for u128", size)
114-
}
115-
}
116-
Ok(SizeSkeleton::Generic(size)) => {
117-
if let Some(size) =
118-
self.try_structurally_resolve_const(span, size).try_to_target_usize(tcx)
119-
{
120-
format!("{size} bytes")
121-
} else {
122-
format!("generic size {size}")
123-
}
124-
}
125-
Err(LayoutError::TooGeneric(bad)) => {
126-
if *bad == ty {
127-
"this type does not have a fixed size".to_owned()
128-
} else {
129-
format!("size can vary because of {bad}")
130-
}
131-
}
132-
Err(err) => err.to_string(),
133-
};
134-
135-
let mut err = struct_span_code_err!(
136-
self.dcx(),
137-
span,
138-
E0512,
139-
"cannot transmute between types of different sizes, \
140-
or dependently-sized types"
141-
);
142-
if from == to {
143-
err.note(format!("`{from}` does not have a fixed size"));
144-
err.emit();
145-
} else {
146-
err.note(format!("source type: `{}` ({})", from, skeleton_string(from, sk_from)))
147-
.note(format!("target type: `{}` ({})", to, skeleton_string(to, sk_to)));
148-
if let Err(LayoutError::ReferencesError(_)) = sk_from {
149-
err.delay_as_bug();
150-
} else if let Err(LayoutError::ReferencesError(_)) = sk_to {
151-
err.delay_as_bug();
152-
} else {
153-
err.emit();
154-
}
113+
// Special-case transmuting from `typeof(function)` and
114+
// `Option<typeof(function)>` to present a clearer error.
115+
let from = unpack_option_like(tcx, from);
116+
if let ty::FnDef(..) = from.kind()
117+
&& let SizeSkeleton::Known(size_to, _) = sk_to
118+
&& size_to == Pointer(tcx.data_layout.instruction_address_space).size(&tcx)
119+
{
120+
struct_span_code_err!(tcx.sess.dcx(), span(), E0591, "can't transmute zero-sized type")
121+
.with_note(format!("source type: {from}"))
122+
.with_note(format!("target type: {to}"))
123+
.with_help("cast with `as` to a pointer instead")
124+
.emit();
125+
return;
155126
}
156127
}
128+
129+
let mut err = struct_span_code_err!(
130+
tcx.sess.dcx(),
131+
span(),
132+
E0512,
133+
"cannot transmute between types of different sizes, or dependently-sized types"
134+
);
135+
if from == to {
136+
err.note(format!("`{from}` does not have a fixed size"));
137+
err.emit();
138+
} else {
139+
err.note(format!("source type: `{}` ({})", from, skeleton_string(from, sk_from)));
140+
err.note(format!("target type: `{}` ({})", to, skeleton_string(to, sk_to)));
141+
err.emit();
142+
}
143+
}
144+
145+
pub(crate) fn check_transmutes(tcx: TyCtxt<'_>, owner: LocalDefId) {
146+
assert!(!tcx.is_typeck_child(owner.to_def_id()));
147+
let typeck_results = tcx.typeck(owner);
148+
let None = typeck_results.tainted_by_errors else { return };
149+
150+
let typing_env = ty::TypingEnv::post_analysis(tcx, owner);
151+
for &(from, to, hir_id) in &typeck_results.transmutes_to_check {
152+
check_transmute(tcx, typing_env, from, to, hir_id);
153+
}
157154
}

compiler/rustc_hir_typeck/src/lib.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -251,10 +251,6 @@ fn typeck_with_inspect<'tcx>(
251251
fcx.report_ambiguity_errors();
252252
}
253253

254-
if let None = fcx.infcx.tainted_by_errors() {
255-
fcx.check_transmutes();
256-
}
257-
258254
fcx.check_asms();
259255

260256
let typeck_results = fcx.resolve_type_vars_in_body(body);
@@ -555,6 +551,7 @@ pub fn provide(providers: &mut Providers) {
555551
method_autoderef_steps: method::probe::method_autoderef_steps,
556552
typeck,
557553
used_trait_imports,
554+
check_transmutes: intrinsicck::check_transmutes,
558555
..*providers
559556
};
560557
}

compiler/rustc_hir_typeck/src/writeback.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
7474
wbcx.visit_user_provided_tys();
7575
wbcx.visit_user_provided_sigs();
7676
wbcx.visit_coroutine_interior();
77+
wbcx.visit_transmutes();
7778
wbcx.visit_offset_of_container_types();
7879

7980
wbcx.typeck_results.rvalue_scopes =
@@ -532,6 +533,18 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> {
532533
}
533534
}
534535

536+
fn visit_transmutes(&mut self) {
537+
let tcx = self.tcx();
538+
let fcx_typeck_results = self.fcx.typeck_results.borrow();
539+
assert_eq!(fcx_typeck_results.hir_owner, self.typeck_results.hir_owner);
540+
for &(from, to, hir_id) in self.fcx.deferred_transmute_checks.borrow().iter() {
541+
let span = tcx.hir_span(hir_id);
542+
let from = self.resolve(from, &span);
543+
let to = self.resolve(to, &span);
544+
self.typeck_results.transmutes_to_check.push((from, to, hir_id));
545+
}
546+
}
547+
535548
#[instrument(skip(self), level = "debug")]
536549
fn visit_opaque_types(&mut self) {
537550
let tcx = self.tcx();

compiler/rustc_interface/src/passes.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1080,7 +1080,8 @@ fn run_required_analyses(tcx: TyCtxt<'_>) {
10801080
if !tcx.is_typeck_child(def_id.to_def_id()) {
10811081
// Child unsafety and borrowck happens together with the parent
10821082
tcx.ensure_ok().check_unsafety(def_id);
1083-
tcx.ensure_ok().mir_borrowck(def_id)
1083+
tcx.ensure_ok().mir_borrowck(def_id);
1084+
tcx.ensure_ok().check_transmutes(def_id);
10841085
}
10851086
tcx.ensure_ok().has_ffi_unwind_calls(def_id);
10861087

compiler/rustc_middle/src/query/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1115,6 +1115,11 @@ rustc_queries! {
11151115
desc { |tcx| "collecting all inherent impls for `{:?}`", key }
11161116
}
11171117

1118+
/// Unsafety-check this `LocalDefId`.
1119+
query check_transmutes(key: LocalDefId) {
1120+
desc { |tcx| "check transmute calls inside `{}`", tcx.def_path_str(key) }
1121+
}
1122+
11181123
/// Unsafety-check this `LocalDefId`.
11191124
query check_unsafety(key: LocalDefId) {
11201125
desc { |tcx| "unsafety-checking `{}`", tcx.def_path_str(key) }

compiler/rustc_middle/src/ty/typeck_results.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,11 @@ pub struct TypeckResults<'tcx> {
210210
/// on closure size.
211211
pub closure_size_eval: LocalDefIdMap<ClosureSizeProfileData<'tcx>>,
212212

213+
/// Stores the types involved in calls to `transmute` intrinsic. These are meant to be checked
214+
/// outside of typeck and borrowck to avoid cycles with opaque types and coroutine layout
215+
/// computation.
216+
pub transmutes_to_check: Vec<(Ty<'tcx>, Ty<'tcx>, HirId)>,
217+
213218
/// Container types and field indices of `offset_of!` expressions
214219
offset_of_data: ItemLocalMap<(Ty<'tcx>, Vec<(VariantIdx, FieldIdx)>)>,
215220
}
@@ -241,6 +246,7 @@ impl<'tcx> TypeckResults<'tcx> {
241246
rvalue_scopes: Default::default(),
242247
coroutine_stalled_predicates: Default::default(),
243248
closure_size_eval: Default::default(),
249+
transmutes_to_check: Default::default(),
244250
offset_of_data: Default::default(),
245251
}
246252
}
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
//@ only-x86_64-unknown-linux-gnu
1+
//@ only-64bit
22

33
#![feature(const_transmute)]
44

5-
pub const ZST: &[u8] = unsafe { std::mem::transmute(1usize) }; //~ ERROR cannot transmute between types of different sizes, or dependently-sized types
5+
pub const ZST: &[u8] = unsafe { std::mem::transmute(1usize) };
6+
//~^ ERROR transmuting from 8-byte type to 16-byte type
Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
1-
error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
1+
error[E0080]: transmuting from 8-byte type to 16-byte type: `usize` -> `&[u8]`
22
--> $DIR/issue-79494.rs:5:33
33
|
44
LL | pub const ZST: &[u8] = unsafe { std::mem::transmute(1usize) };
5-
| ^^^^^^^^^^^^^^^^^^^
6-
|
7-
= note: source type: `usize` (64 bits)
8-
= note: target type: `&[u8]` (128 bits)
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ evaluation of `ZST` failed here
96

107
error: aborting due to 1 previous error
118

12-
For more information about this error, try `rustc --explain E0512`.
9+
For more information about this error, try `rustc --explain E0080`.

tests/ui/const-generics/transmute-fail.stderr

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@ LL | fn bar<const W: bool, const H: usize>(v: [[u32; H]; W]) -> [[u32; W]; H] {
66
|
77
= note: the length of array `[[u32; H]; W]` must be type `usize`
88

9+
error: the constant `W` is not of type `usize`
10+
--> $DIR/transmute-fail.rs:19:9
11+
|
12+
LL | std::mem::transmute(v)
13+
| ^^^^^^^^^^^^^^^^^^^ expected `usize`, found `bool`
14+
|
15+
= note: the length of array `[[u32; H]; W]` must be type `usize`
16+
917
error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
1018
--> $DIR/transmute-fail.rs:11:9
1119
|
@@ -15,14 +23,6 @@ LL | std::mem::transmute(v)
1523
= note: source type: `[[u32; H + 1]; W]` (size can vary because of [u32; H + 1])
1624
= note: target type: `[[u32; W + 1]; H]` (size can vary because of [u32; W + 1])
1725

18-
error: the constant `W` is not of type `usize`
19-
--> $DIR/transmute-fail.rs:19:9
20-
|
21-
LL | std::mem::transmute(v)
22-
| ^^^^^^^^^^^^^^^^^^^ expected `usize`, found `bool`
23-
|
24-
= note: the length of array `[[u32; H]; W]` must be type `usize`
25-
2626
error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
2727
--> $DIR/transmute-fail.rs:26:9
2828
|

0 commit comments

Comments
 (0)