Skip to content

Commit 915200f

Browse files
committed
Lint on reference casting to bigger underlying allocation
1 parent bdc1592 commit 915200f

File tree

6 files changed

+356
-18
lines changed

6 files changed

+356
-18
lines changed

compiler/rustc_lint/messages.ftl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,11 @@ lint_invalid_nan_comparisons_lt_le_gt_ge = incorrect NaN comparison, NaN is not
319319
lint_invalid_reference_casting_assign_to_ref = assigning to `&T` is undefined behavior, consider using an `UnsafeCell`
320320
.label = casting happend here
321321
322+
lint_invalid_reference_casting_bigger_layout = casting references to a bigger memory layout than the backing allocation is undefined behavior, even if the reference is unused
323+
.label = casting happend here
324+
.alloc = backing allocation comes from here
325+
.layout = casting from `{$from_ty}` ({$from_size} bytes) to `{$to_ty}` ({$to_size} bytes)
326+
322327
lint_invalid_reference_casting_borrow_as_mut = casting `&T` to `&mut T` is undefined behavior, even if the reference is unused, consider instead using an `UnsafeCell`
323328
.label = casting happend here
324329

compiler/rustc_lint/src/lints.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -716,7 +716,7 @@ pub enum InvalidFromUtf8Diag {
716716

717717
// reference_casting.rs
718718
#[derive(LintDiagnostic)]
719-
pub enum InvalidReferenceCastingDiag {
719+
pub enum InvalidReferenceCastingDiag<'tcx> {
720720
#[diag(lint_invalid_reference_casting_borrow_as_mut)]
721721
#[note(lint_invalid_reference_casting_note_book)]
722722
BorrowAsMut {
@@ -733,6 +733,18 @@ pub enum InvalidReferenceCastingDiag {
733733
#[note(lint_invalid_reference_casting_note_ty_has_interior_mutability)]
734734
ty_has_interior_mutability: Option<()>,
735735
},
736+
#[diag(lint_invalid_reference_casting_bigger_layout)]
737+
#[note(lint_layout)]
738+
BiggerLayout {
739+
#[label]
740+
orig_cast: Option<Span>,
741+
#[label(lint_alloc)]
742+
alloc: Span,
743+
from_ty: Ty<'tcx>,
744+
from_size: u64,
745+
to_ty: Ty<'tcx>,
746+
to_size: u64,
747+
},
736748
}
737749

738750
// hidden_unicode_codepoints.rs

compiler/rustc_lint/src/reference_casting.rs

Lines changed: 66 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use rustc_ast::Mutability;
22
use rustc_hir::{Expr, ExprKind, UnOp};
3-
use rustc_middle::ty::{self, TypeAndMut};
3+
use rustc_middle::ty::layout::LayoutOf as _;
4+
use rustc_middle::ty::{self, layout::TyAndLayout, TypeAndMut};
45
use rustc_span::sym;
56

67
use crate::{lints::InvalidReferenceCastingDiag, LateContext, LateLintPass, LintContext};
@@ -38,13 +39,12 @@ declare_lint_pass!(InvalidReferenceCasting => [INVALID_REFERENCE_CASTING]);
3839
impl<'tcx> LateLintPass<'tcx> for InvalidReferenceCasting {
3940
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
4041
if let Some((e, pat)) = borrow_or_assign(cx, expr) {
41-
if matches!(pat, PatternKind::Borrow { mutbl: Mutability::Mut } | PatternKind::Assign) {
42-
let init = cx.expr_or_init(e);
42+
let init = cx.expr_or_init(e);
43+
let orig_cast = if init.span != e.span { Some(init.span) } else { None };
4344

44-
let Some(ty_has_interior_mutability) = is_cast_from_ref_to_mut_ptr(cx, init) else {
45-
return;
46-
};
47-
let orig_cast = if init.span != e.span { Some(init.span) } else { None };
45+
if matches!(pat, PatternKind::Borrow { mutbl: Mutability::Mut } | PatternKind::Assign)
46+
&& let Some(ty_has_interior_mutability) = is_cast_from_ref_to_mut_ptr(cx, init)
47+
{
4848
let ty_has_interior_mutability = ty_has_interior_mutability.then_some(());
4949

5050
cx.emit_span_lint(
@@ -63,6 +63,23 @@ impl<'tcx> LateLintPass<'tcx> for InvalidReferenceCasting {
6363
},
6464
);
6565
}
66+
67+
if let Some((from_ty_layout, to_ty_layout, e_alloc)) =
68+
is_cast_to_bigger_memory_layout(cx, init)
69+
{
70+
cx.emit_span_lint(
71+
INVALID_REFERENCE_CASTING,
72+
expr.span,
73+
InvalidReferenceCastingDiag::BiggerLayout {
74+
orig_cast,
75+
alloc: e_alloc.span,
76+
from_ty: from_ty_layout.ty,
77+
from_size: from_ty_layout.layout.size().bytes(),
78+
to_ty: to_ty_layout.ty,
79+
to_size: to_ty_layout.layout.size().bytes(),
80+
},
81+
);
82+
}
6683
}
6784
}
6885
}
@@ -151,6 +168,48 @@ fn is_cast_from_ref_to_mut_ptr<'tcx>(
151168
}
152169
}
153170

171+
fn is_cast_to_bigger_memory_layout<'tcx>(
172+
cx: &LateContext<'tcx>,
173+
orig_expr: &'tcx Expr<'tcx>,
174+
) -> Option<(TyAndLayout<'tcx>, TyAndLayout<'tcx>, Expr<'tcx>)> {
175+
let end_ty = cx.typeck_results().node_type(orig_expr.hir_id);
176+
177+
let ty::RawPtr(TypeAndMut { ty: inner_end_ty, mutbl: _ }) = end_ty.kind() else {
178+
return None;
179+
};
180+
181+
let (e, _) = peel_casts(cx, orig_expr);
182+
let start_ty = cx.typeck_results().node_type(e.hir_id);
183+
184+
let ty::Ref(_, inner_start_ty, _) = start_ty.kind() else {
185+
return None;
186+
};
187+
188+
// try to find the underlying allocation
189+
let e_alloc = cx.expr_or_init(e);
190+
let e_alloc =
191+
if let ExprKind::AddrOf(_, _, inner_expr) = e_alloc.kind { inner_expr } else { e_alloc };
192+
let alloc_ty = cx.typeck_results().node_type(e_alloc.hir_id);
193+
194+
// if we do not find it we bail out, as this may not be UB
195+
// see https://github.com/rust-lang/unsafe-code-guidelines/issues/256
196+
if alloc_ty.is_any_ptr() {
197+
return None;
198+
}
199+
200+
let from_layout = cx.layout_of(*inner_start_ty).ok()?;
201+
let alloc_layout = cx.layout_of(alloc_ty).ok()?;
202+
let to_layout = cx.layout_of(*inner_end_ty).ok()?;
203+
204+
if to_layout.layout.size() > from_layout.layout.size()
205+
&& to_layout.layout.size() > alloc_layout.layout.size()
206+
{
207+
Some((from_layout, to_layout, *e_alloc))
208+
} else {
209+
None
210+
}
211+
}
212+
154213
fn peel_casts<'tcx>(cx: &LateContext<'tcx>, mut e: &'tcx Expr<'tcx>) -> (&'tcx Expr<'tcx>, bool) {
155214
let mut gone_trough_unsafe_cell_raw_get = false;
156215

tests/ui/cast/cast-rfc0401.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,14 +81,14 @@ fn main()
8181
assert_eq!(u as *const u16, p as *const u16);
8282

8383
// ptr-ptr-cast (Length vtables)
84-
let mut l : [u8; 2] = [0,1];
85-
let w: *mut [u16; 2] = &mut l as *mut [u8; 2] as *mut _;
86-
let w: *mut [u16] = unsafe {&mut *w};
87-
let w_u8 : *const [u8] = w as *const [u8];
88-
assert_eq!(unsafe{&*w_u8}, &l);
84+
let mut l : [u16; 2] = [0,1];
85+
let w: *mut [u8; 2] = &mut l as *mut [u16; 2] as *mut _;
86+
let w: *mut [u8] = unsafe {&mut *w};
87+
let w_u16 : *const [u16] = w as *const [u16];
88+
assert_eq!(unsafe{&*w_u16}, &l);
8989

9090
let s: *mut str = w as *mut str;
91-
let l_via_str = unsafe{&*(s as *const [u8])};
91+
let l_via_str = unsafe{&*(s as *const [u16])};
9292
assert_eq!(&l, l_via_str);
9393

9494
// ptr-ptr-cast (Length vtables, check length is preserved)

tests/ui/lint/reference_casting.rs

Lines changed: 104 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ unsafe fn ref_to_mut() {
3030
//~^ ERROR casting `&T` to `&mut T` is undefined behavior
3131
let _num = &mut *(num as *const i32).cast::<i32>().cast_mut().cast_const().cast_mut();
3232
//~^ ERROR casting `&T` to `&mut T` is undefined behavior
33-
let _num = &mut *(std::ptr::from_ref(static_u8()) as *mut i32);
33+
let _num = &mut *(std::ptr::from_ref(static_u8()) as *mut i8);
3434
//~^ ERROR casting `&T` to `&mut T` is undefined behavior
3535
let _num = &mut *std::mem::transmute::<_, *mut i32>(num);
3636
//~^ ERROR casting `&T` to `&mut T` is undefined behavior
@@ -141,6 +141,109 @@ unsafe fn assign_to_ref() {
141141
}
142142
}
143143

144+
#[repr(align(16))]
145+
struct I64(i64);
146+
147+
#[repr(C)]
148+
struct Mat3<T> {
149+
a: Vec3<T>,
150+
b: Vec3<T>,
151+
c: Vec3<T>,
152+
}
153+
154+
#[repr(C)]
155+
struct Vec3<T>(T, T, T);
156+
157+
unsafe fn bigger_layout() {
158+
{
159+
let num = &mut 3i32;
160+
161+
let _num = &*(num as *const i32 as *const i64);
162+
//~^ ERROR casting references to a bigger memory layout
163+
let _num = &mut *(num as *mut i32 as *mut i64);
164+
//~^ ERROR casting references to a bigger memory layout
165+
let _num = &mut *(num as *mut i32 as *mut I64);
166+
//~^ ERROR casting references to a bigger memory layout
167+
std::ptr::write(num as *mut i32 as *mut i64, 2);
168+
//~^ ERROR casting references to a bigger memory layout
169+
170+
let _num = &mut *(num as *mut i32);
171+
}
172+
173+
{
174+
let num = &mut [0i32; 3];
175+
176+
let _num = &mut *(num as *mut _ as *mut [i64; 2]);
177+
//~^ ERROR casting references to a bigger memory layout
178+
std::ptr::write_unaligned(num as *mut _ as *mut [i32; 4], [0, 0, 1, 1]);
179+
//~^ ERROR casting references to a bigger memory layout
180+
181+
let _num = &mut *(num as *mut _ as *mut [u32; 3]);
182+
let _num = &mut *(num as *mut _ as *mut [u32; 2]);
183+
}
184+
185+
{
186+
let num = &mut [0i32; 3] as &mut [i32];
187+
188+
let _num = &mut *(num as *mut _ as *mut i128);
189+
//~^ ERROR casting references to a bigger memory layout
190+
let _num = &mut *(num as *mut _ as *mut [i64; 4]);
191+
//~^ ERROR casting references to a bigger memory layout
192+
193+
let _num = &mut *(num as *mut _ as *mut [u32]);
194+
let _num = &mut *(num as *mut _ as *mut [i16]);
195+
}
196+
197+
{
198+
let mat3 = Mat3 { a: Vec3(0i32, 0, 0), b: Vec3(0, 0, 0), c: Vec3(0, 0, 0) };
199+
200+
let _num = &mut *(&mat3 as *const _ as *mut [[i64; 3]; 3]);
201+
//~^ ERROR casting `&T` to `&mut T`
202+
//~^^ ERROR casting references to a bigger memory layout
203+
let _num = &*(&mat3 as *const _ as *mut [[i64; 3]; 3]);
204+
//~^ ERROR casting references to a bigger memory layout
205+
206+
let _num = &*(&mat3 as *const _ as *mut [[i32; 3]; 3]);
207+
}
208+
209+
{
210+
let mut l: [u8; 2] = [0,1];
211+
let w: *mut [u16; 2] = &mut l as *mut [u8; 2] as *mut _;
212+
let w: *mut [u16] = unsafe {&mut *w};
213+
//~^ ERROR casting references to a bigger memory layout
214+
}
215+
216+
{
217+
fn foo() -> [i32; 1] { todo!() }
218+
219+
let num = foo();
220+
let _num = &*(&num as *const i32 as *const i64);
221+
//~^ ERROR casting references to a bigger memory layout
222+
let _num = &*(&foo() as *const i32 as *const i64);
223+
//~^ ERROR casting references to a bigger memory layout
224+
}
225+
226+
{
227+
fn bar(_a: &[i32; 2]) -> &[i32; 1] { todo!() }
228+
229+
let num = bar(&[0, 0]);
230+
let _num = &*(num as *const i32 as *const i64);
231+
let _num = &*(bar(&[0, 0]) as *const i32 as *const i64);
232+
}
233+
234+
{
235+
fn foi<T>() -> T { todo!() }
236+
237+
let num = foi::<i32>();
238+
let _num = &*(&num as *const i32 as *const i64);
239+
//~^ ERROR casting references to a bigger memory layout
240+
}
241+
242+
unsafe fn from_ref(this: &i32) -> &i64 {
243+
&*(this as *const i32 as *const i64)
244+
}
245+
}
246+
144247
const RAW_PTR: *mut u8 = 1 as *mut u8;
145248
unsafe fn no_warn() {
146249
let num = &3i32;

0 commit comments

Comments
 (0)