Skip to content

Commit a3c878f

Browse files
committed
Separate transmute checking from typeck.
1 parent d20509c commit a3c878f

24 files changed

+253
-254
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: 101 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,9 @@ use rustc_index::Idx;
88
use rustc_middle::bug;
99
use rustc_middle::ty::layout::{LayoutError, SizeSkeleton};
1010
use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt};
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,115 @@ 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
55-
} else {
56-
Ty::new_error_with_message(
57-
tcx,
58-
span,
59-
"tried to normalize non-wf type in check_transmute",
60-
)
61-
}
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");
71-
}
72-
// Transmutes that are only changing lifetimes are always ok.
73-
if from == to {
74-
return;
41+
fn check_transmute<'tcx>(
42+
tcx: TyCtxt<'tcx>,
43+
typing_env: ty::TypingEnv<'tcx>,
44+
from: Ty<'tcx>,
45+
to: Ty<'tcx>,
46+
hir_id: HirId,
47+
) {
48+
let dl = &tcx.data_layout;
49+
let span = tcx.hir_span(hir_id);
50+
let normalize = |ty| {
51+
if let Ok(ty) = tcx.try_normalize_erasing_regions(typing_env, ty) {
52+
ty
53+
} else {
54+
Ty::new_error_with_message(
55+
tcx,
56+
span,
57+
format!("tried to normalize non-wf type {ty:#?} in check_transmute"),
58+
)
7559
}
60+
};
61+
let from = normalize(from);
62+
let to = normalize(to);
63+
trace!(?from, ?to);
64+
if from.has_non_region_infer() || to.has_non_region_infer() {
65+
// Note: this path is currently not reached in any test, so any
66+
// example that triggers this would be worth minimizing and
67+
// converting into a test.
68+
tcx.sess.dcx().span_bug(span, "argument to transmute has inference variables");
69+
}
70+
// Transmutes that are only changing lifetimes are always ok.
71+
if from == to {
72+
return;
73+
}
7674

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);
75+
let skel = |ty| SizeSkeleton::compute(ty, tcx, typing_env);
76+
let sk_from = skel(from);
77+
let sk_to = skel(to);
78+
trace!(?sk_from, ?sk_to);
8179

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-
}
80+
// Check for same size using the skeletons.
81+
if let (Ok(sk_from), Ok(sk_to)) = (sk_from, sk_to) {
82+
if sk_from.same_size(sk_to) {
83+
return;
84+
}
8785

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-
}
86+
// Special-case transmuting from `typeof(function)` and
87+
// `Option<typeof(function)>` to present a clearer error.
88+
let from = unpack_option_like(tcx, from);
89+
if let (&ty::FnDef(..), SizeSkeleton::Known(size_to, _)) = (from.kind(), sk_to)
90+
&& size_to == Pointer(dl.instruction_address_space).size(&tcx)
91+
{
92+
struct_span_code_err!(tcx.sess.dcx(), span, E0591, "can't transmute zero-sized type")
93+
.with_note(format!("source type: {from}"))
94+
.with_note(format!("target type: {to}"))
95+
.with_help("cast with `as` to a pointer instead")
96+
.emit();
97+
return;
10198
}
99+
}
102100

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-
}
101+
// Try to display a sensible error with as much information as possible.
102+
let skeleton_string = |ty: Ty<'tcx>, sk: Result<_, &_>| match sk {
103+
Ok(SizeSkeleton::Pointer { tail, .. }) => format!("pointer to `{tail}`"),
104+
Ok(SizeSkeleton::Known(size, _)) => {
105+
if let Some(v) = u128::from(size.bytes()).checked_mul(8) {
106+
format!("{v} bits")
107+
} else {
108+
// `u128` should definitely be able to hold the size of different architectures
109+
// larger sizes should be reported as error `are too big for the target architecture`
110+
// otherwise we have a bug somewhere
111+
bug!("{:?} overflow for u128", size)
131112
}
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();
113+
}
114+
Ok(SizeSkeleton::Generic(size)) => {
115+
format!("generic size {size}")
116+
}
117+
Err(LayoutError::TooGeneric(bad)) => {
118+
if *bad == ty {
119+
"this type does not have a fixed size".to_owned()
152120
} else {
153-
err.emit();
121+
format!("size can vary because of {bad}")
154122
}
155123
}
124+
Err(err) => err.to_string(),
125+
};
126+
127+
let mut err = struct_span_code_err!(
128+
tcx.sess.dcx(),
129+
span,
130+
E0512,
131+
"cannot transmute between types of different sizes, or dependently-sized types"
132+
);
133+
if from == to {
134+
err.note(format!("`{from}` does not have a fixed size"));
135+
err.emit();
136+
} else {
137+
err.note(format!("source type: `{}` ({})", from, skeleton_string(from, sk_from)));
138+
err.note(format!("target type: `{}` ({})", to, skeleton_string(to, sk_to)));
139+
err.emit();
140+
}
141+
}
142+
143+
pub(crate) fn check_transmutes(tcx: TyCtxt<'_>, owner: LocalDefId) {
144+
assert!(!tcx.is_typeck_child(owner.to_def_id()));
145+
let typeck_results = tcx.typeck(owner);
146+
let None = typeck_results.tainted_by_errors else { return };
147+
148+
let typing_env = ty::TypingEnv::post_analysis(tcx, owner);
149+
for &(from, to, hir_id) in &typeck_results.transmutes_to_check {
150+
check_transmute(tcx, typing_env, from, to, hir_id);
156151
}
157152
}

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
}

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
|

tests/ui/consts/transmute-size-mismatch-before-typeck.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1+
//@ normalize-stderr-64bit: "8 byte" -> "word size"
2+
//@ normalize-stderr-32bit: "4 byte" -> "word size"
13
//@ normalize-stderr-64bit: "64 bits" -> "word size"
24
//@ normalize-stderr-32bit: "32 bits" -> "word size"
5+
//@ normalize-stderr-64bit: "16 byte" -> "2 * word size"
6+
//@ normalize-stderr-32bit: "8 byte" -> "2 * word size"
37
//@ normalize-stderr-64bit: "128 bits" -> "2 * word size"
48
//@ normalize-stderr-32bit: "64 bits" -> "2 * word size"
59

@@ -10,4 +14,5 @@ fn main() {
1014
}
1115

1216
const ZST: &[u8] = unsafe { std::mem::transmute(1usize) };
13-
//~^ ERROR cannot transmute between types of different sizes
17+
//~^ ERROR transmuting from
18+
//~| ERROR cannot transmute between types of different sizes

0 commit comments

Comments
 (0)