Skip to content

Commit 454bca5

Browse files
committed
Check CastKind::Transmute sizes in a better way
Fixes rust-lang#110005
1 parent 7f6edd3 commit 454bca5

File tree

3 files changed

+134
-32
lines changed

3 files changed

+134
-32
lines changed

compiler/rustc_codegen_ssa/src/mir/operand.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,31 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
259259
}
260260

261261
impl<'a, 'tcx, V: CodegenObject> OperandValue<V> {
262+
/// Returns an `OperandValue` that's generally UB to use in any way.
263+
///
264+
/// Depending on the `layout`, returns an `Immediate` or `Pair` containing
265+
/// poison value(s), or a `Ref` containing a poison pointer.
266+
///
267+
/// Supports sized types only.
268+
pub fn poison<Bx: BuilderMethods<'a, 'tcx, Value = V>>(
269+
bx: &mut Bx,
270+
layout: TyAndLayout<'tcx>,
271+
) -> OperandValue<V> {
272+
assert!(layout.is_sized());
273+
if bx.cx().is_backend_immediate(layout) {
274+
let ibty = bx.cx().immediate_backend_type(layout);
275+
OperandValue::Immediate(bx.const_poison(ibty))
276+
} else if bx.cx().is_backend_scalar_pair(layout) {
277+
let ibty0 = bx.cx().scalar_pair_element_backend_type(layout, 0, true);
278+
let ibty1 = bx.cx().scalar_pair_element_backend_type(layout, 1, true);
279+
OperandValue::Pair(bx.const_poison(ibty0), bx.const_poison(ibty1))
280+
} else {
281+
let bty = bx.cx().backend_type(layout);
282+
let ptr_bty = bx.cx().type_ptr_to(bty);
283+
OperandValue::Ref(bx.const_poison(ptr_bty), None, layout.align.abi)
284+
}
285+
}
286+
262287
pub fn store<Bx: BuilderMethods<'a, 'tcx, Value = V>>(
263288
self,
264289
bx: &mut Bx,

compiler/rustc_codegen_ssa/src/mir/rvalue.rs

Lines changed: 36 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -158,17 +158,6 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
158158
debug_assert!(src.layout.is_sized());
159159
debug_assert!(dst.layout.is_sized());
160160

161-
if src.layout.size != dst.layout.size
162-
|| src.layout.abi.is_uninhabited()
163-
|| dst.layout.abi.is_uninhabited()
164-
{
165-
// In all of these cases it's UB to run this transmute, but that's
166-
// known statically so might as well trap for it, rather than just
167-
// making it unreachable.
168-
bx.abort();
169-
return;
170-
}
171-
172161
if let Some(val) = self.codegen_transmute_operand(bx, src, dst.layout) {
173162
val.store(bx, dst);
174163
return;
@@ -202,8 +191,21 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
202191
operand: OperandRef<'tcx, Bx::Value>,
203192
cast: TyAndLayout<'tcx>,
204193
) -> Option<OperandValue<Bx::Value>> {
205-
// Callers already checked that the layout sizes match
206-
debug_assert_eq!(operand.layout.size, cast.size);
194+
// Check for transmutes that are always UB.
195+
if operand.layout.size != cast.size
196+
|| operand.layout.abi.is_uninhabited()
197+
|| cast.abi.is_uninhabited()
198+
{
199+
if !operand.layout.abi.is_uninhabited() {
200+
// Since this is known statically and the input could have existed
201+
// without already having hit UB, might as well trap for it.
202+
bx.abort();
203+
}
204+
205+
// Because this transmute is UB, return something easy to generate,
206+
// since it's fine that later uses of the value are probably UB.
207+
return Some(OperandValue::poison(bx, cast));
208+
}
207209

208210
let operand_kind = self.value_kind(operand.layout);
209211
let cast_kind = self.value_kind(cast);
@@ -221,11 +223,13 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
221223
let OperandValueKind::Immediate(in_scalar) = operand_kind else {
222224
bug!("Found {operand_kind:?} for operand {operand:?}");
223225
};
224-
if let OperandValueKind::Immediate(out_scalar) = cast_kind {
226+
if let OperandValueKind::Immediate(out_scalar) = cast_kind
227+
&& in_scalar.size(self.cx) == out_scalar.size(self.cx)
228+
{
225229
let cast_bty = bx.backend_type(cast);
226-
Some(OperandValue::Immediate(Self::transmute_immediate(
227-
bx, imm, in_scalar, out_scalar, cast_bty,
228-
)))
230+
Some(OperandValue::Immediate(
231+
self.transmute_immediate(bx, imm, in_scalar, out_scalar, cast_bty),
232+
))
229233
} else {
230234
None
231235
}
@@ -234,12 +238,15 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
234238
let OperandValueKind::Pair(in_a, in_b) = operand_kind else {
235239
bug!("Found {operand_kind:?} for operand {operand:?}");
236240
};
237-
if let OperandValueKind::Pair(out_a, out_b) = cast_kind {
241+
if let OperandValueKind::Pair(out_a, out_b) = cast_kind
242+
&& in_a.size(self.cx) == out_a.size(self.cx)
243+
&& in_b.size(self.cx) == out_b.size(self.cx)
244+
{
238245
let out_a_ibty = bx.scalar_pair_element_backend_type(cast, 0, false);
239246
let out_b_ibty = bx.scalar_pair_element_backend_type(cast, 1, false);
240247
Some(OperandValue::Pair(
241-
Self::transmute_immediate(bx, imm_a, in_a, out_a, out_a_ibty),
242-
Self::transmute_immediate(bx, imm_b, in_b, out_b, out_b_ibty),
248+
self.transmute_immediate(bx, imm_a, in_a, out_a, out_a_ibty),
249+
self.transmute_immediate(bx, imm_b, in_b, out_b, out_b_ibty),
243250
))
244251
} else {
245252
None
@@ -254,12 +261,15 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
254261
/// `to_backend_ty` must be the *non*-immediate backend type (so it will be
255262
/// `i8`, not `i1`, for `bool`-like types.)
256263
fn transmute_immediate(
264+
&self,
257265
bx: &mut Bx,
258266
mut imm: Bx::Value,
259267
from_scalar: abi::Scalar,
260268
to_scalar: abi::Scalar,
261269
to_backend_ty: Bx::Type,
262270
) -> Bx::Value {
271+
debug_assert_eq!(from_scalar.size(self.cx), to_scalar.size(self.cx));
272+
263273
use abi::Primitive::*;
264274
imm = bx.from_immediate(imm);
265275
imm = match (from_scalar.primitive(), to_scalar.primitive()) {
@@ -831,14 +841,6 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
831841
let operand_ty = operand.ty(self.mir, self.cx.tcx());
832842
let cast_layout = self.cx.layout_of(self.monomorphize(cast_ty));
833843
let operand_layout = self.cx.layout_of(self.monomorphize(operand_ty));
834-
if operand_layout.size != cast_layout.size
835-
|| operand_layout.abi.is_uninhabited()
836-
|| cast_layout.abi.is_uninhabited()
837-
{
838-
// Send UB cases to the full form so the operand version can
839-
// `bitcast` without worrying about malformed IR.
840-
return false;
841-
}
842844

843845
match (self.value_kind(operand_layout), self.value_kind(cast_layout)) {
844846
// Can always load from a pointer as needed
@@ -847,9 +849,12 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
847849
// Need to generate an `alloc` to get a pointer from an immediate
848850
(OperandValueKind::Immediate(..) | OperandValueKind::Pair(..), OperandValueKind::Ref) => false,
849851

850-
// When we have scalar immediates, we can convert them as needed
851-
(OperandValueKind::Immediate(..), OperandValueKind::Immediate(..)) |
852-
(OperandValueKind::Pair(..), OperandValueKind::Pair(..)) => true,
852+
// When we have scalar immediates, we can only convert things
853+
// where the sizes match, to avoid endianness questions.
854+
(OperandValueKind::Immediate(a), OperandValueKind::Immediate(b)) =>
855+
a.size(self.cx) == b.size(self.cx),
856+
(OperandValueKind::Pair(a0, a1), OperandValueKind::Pair(b0, b1)) =>
857+
a0.size(self.cx) == b0.size(self.cx) && a1.size(self.cx) == b1.size(self.cx),
853858

854859
// Send mixings between scalars and pairs through the memory route
855860
// FIXME: Maybe this could use insertvalue/extractvalue instead?

tests/codegen/intrinsics/transmute.rs

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,32 @@ pub unsafe fn check_smaller_size(x: u32) -> u16 {
5454
}
5555
}
5656

57+
// CHECK-LABEL: @check_smaller_array(
58+
#[no_mangle]
59+
#[custom_mir(dialect = "runtime", phase = "initial")]
60+
pub unsafe fn check_smaller_array(x: [u32; 7]) -> [u32; 3] {
61+
// CHECK: call void @llvm.trap
62+
mir!{
63+
{
64+
RET = CastTransmute(x);
65+
Return()
66+
}
67+
}
68+
}
69+
70+
// CHECK-LABEL: @check_bigger_array(
71+
#[no_mangle]
72+
#[custom_mir(dialect = "runtime", phase = "initial")]
73+
pub unsafe fn check_bigger_array(x: [u32; 3]) -> [u32; 7] {
74+
// CHECK: call void @llvm.trap
75+
mir!{
76+
{
77+
RET = CastTransmute(x);
78+
Return()
79+
}
80+
}
81+
}
82+
5783
// CHECK-LABEL: @check_to_uninhabited(
5884
#[no_mangle]
5985
#[custom_mir(dialect = "runtime", phase = "initial")]
@@ -71,7 +97,7 @@ pub unsafe fn check_to_uninhabited(x: u16) -> BigNever {
7197
#[no_mangle]
7298
#[custom_mir(dialect = "runtime", phase = "initial")]
7399
pub unsafe fn check_from_uninhabited(x: BigNever) -> u16 {
74-
// CHECK: call void @llvm.trap
100+
// CHECK: ret i16 poison
75101
mir!{
76102
{
77103
RET = CastTransmute(x);
@@ -301,3 +327,49 @@ pub unsafe fn check_pair_to_array(x: (i64, u64)) -> [u8; 16] {
301327
// CHECK: store i64 %x.1, ptr %{{.+}}, align 1
302328
transmute(x)
303329
}
330+
331+
// CHECK-LABEL: @check_heterogeneous_integer_pair(
332+
#[no_mangle]
333+
pub unsafe fn check_heterogeneous_integer_pair(x: (i32, bool)) -> (bool, u32) {
334+
// CHECK: store i32 %x.0
335+
// CHECK: %[[WIDER:.+]] = zext i1 %x.1 to i8
336+
// CHECK: store i8 %[[WIDER]]
337+
338+
// CHECK: %[[BYTE:.+]] = load i8
339+
// CHECK: trunc i8 %[[BYTE:.+]] to i1
340+
// CHECK: load i32
341+
transmute(x)
342+
}
343+
344+
// CHECK-LABEL: @check_heterogeneous_float_pair(
345+
#[no_mangle]
346+
pub unsafe fn check_heterogeneous_float_pair(x: (f64, f32)) -> (f32, f64) {
347+
// CHECK: store double %x.0
348+
// CHECK: store float %x.1
349+
// CHECK: %[[A:.+]] = load float
350+
// CHECK: %[[B:.+]] = load double
351+
// CHECK: %[[P:.+]] = insertvalue { float, double } poison, float %[[A]], 0
352+
// CHECK: insertvalue { float, double } %[[P]], double %[[B]], 1
353+
transmute(x)
354+
}
355+
356+
// CHECK-LABEL: @check_issue_110005(
357+
#[no_mangle]
358+
pub unsafe fn check_issue_110005(x: (usize, bool)) -> Option<Box<[u8]>> {
359+
// CHECK: store i64 %x.0
360+
// CHECK: %[[WIDER:.+]] = zext i1 %x.1 to i8
361+
// CHECK: store i8 %[[WIDER]]
362+
// CHECK: load ptr
363+
// CHECK: load i64
364+
transmute(x)
365+
}
366+
367+
// CHECK-LABEL: @check_pair_to_dst_ref(
368+
#[no_mangle]
369+
pub unsafe fn check_pair_to_dst_ref<'a>(x: (usize, usize)) -> &'a [u8] {
370+
// CHECK: %0 = inttoptr i64 %x.0 to ptr
371+
// CHECK: %1 = insertvalue { ptr, i64 } poison, ptr %0, 0
372+
// CHECK: %2 = insertvalue { ptr, i64 } %1, i64 %x.1, 1
373+
// CHECK: ret { ptr, i64 } %2
374+
transmute(x)
375+
}

0 commit comments

Comments
 (0)