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

Warn on references casting to bigger memory layout #118983

Merged
merged 4 commits into from
Feb 13, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
Lint on reference casting to bigger underlying allocation
  • Loading branch information
Urgau committed Feb 12, 2024
commit 915200fbe077786370bf40aac30aae36dd0ff71b
5 changes: 5 additions & 0 deletions compiler/rustc_lint/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 319,11 @@ lint_invalid_nan_comparisons_lt_le_gt_ge = incorrect NaN comparison, NaN is not
lint_invalid_reference_casting_assign_to_ref = assigning to `&T` is undefined behavior, consider using an `UnsafeCell`
.label = casting happend here

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
.label = casting happend here
.alloc = backing allocation comes from here
.layout = casting from `{$from_ty}` ({$from_size} bytes) to `{$to_ty}` ({$to_size} bytes)

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`
.label = casting happend here

Expand Down
14 changes: 13 additions & 1 deletion compiler/rustc_lint/src/lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -716,7 716,7 @@ pub enum InvalidFromUtf8Diag {

// reference_casting.rs
#[derive(LintDiagnostic)]
pub enum InvalidReferenceCastingDiag {
pub enum InvalidReferenceCastingDiag<'tcx> {
#[diag(lint_invalid_reference_casting_borrow_as_mut)]
#[note(lint_invalid_reference_casting_note_book)]
BorrowAsMut {
Expand All @@ -733,6 733,18 @@ pub enum InvalidReferenceCastingDiag {
#[note(lint_invalid_reference_casting_note_ty_has_interior_mutability)]
ty_has_interior_mutability: Option<()>,
},
#[diag(lint_invalid_reference_casting_bigger_layout)]
#[note(lint_layout)]
BiggerLayout {
#[label]
orig_cast: Option<Span>,
#[label(lint_alloc)]
alloc: Span,
from_ty: Ty<'tcx>,
from_size: u64,
to_ty: Ty<'tcx>,
to_size: u64,
},
}

// hidden_unicode_codepoints.rs
Expand Down
73 changes: 66 additions & 7 deletions compiler/rustc_lint/src/reference_casting.rs
Original file line number Diff line number Diff line change
@@ -1,6 1,7 @@
use rustc_ast::Mutability;
use rustc_hir::{Expr, ExprKind, UnOp};
use rustc_middle::ty::{self, TypeAndMut};
use rustc_middle::ty::layout::LayoutOf as _;
use rustc_middle::ty::{self, layout::TyAndLayout, TypeAndMut};
use rustc_span::sym;

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

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

cx.emit_span_lint(
Expand All @@ -63,6 63,23 @@ impl<'tcx> LateLintPass<'tcx> for InvalidReferenceCasting {
},
);
}

if let Some((from_ty_layout, to_ty_layout, e_alloc)) =
is_cast_to_bigger_memory_layout(cx, init)
{
cx.emit_span_lint(
INVALID_REFERENCE_CASTING,
expr.span,
InvalidReferenceCastingDiag::BiggerLayout {
orig_cast,
alloc: e_alloc.span,
from_ty: from_ty_layout.ty,
from_size: from_ty_layout.layout.size().bytes(),
to_ty: to_ty_layout.ty,
to_size: to_ty_layout.layout.size().bytes(),
},
);
}
}
}
}
Expand Down Expand Up @@ -151,6 168,48 @@ fn is_cast_from_ref_to_mut_ptr<'tcx>(
}
}

fn is_cast_to_bigger_memory_layout<'tcx>(
cx: &LateContext<'tcx>,
orig_expr: &'tcx Expr<'tcx>,
) -> Option<(TyAndLayout<'tcx>, TyAndLayout<'tcx>, Expr<'tcx>)> {
let end_ty = cx.typeck_results().node_type(orig_expr.hir_id);

let ty::RawPtr(TypeAndMut { ty: inner_end_ty, mutbl: _ }) = end_ty.kind() else {
return None;
};

let (e, _) = peel_casts(cx, orig_expr);
let start_ty = cx.typeck_results().node_type(e.hir_id);

let ty::Ref(_, inner_start_ty, _) = start_ty.kind() else {
return None;
};

// try to find the underlying allocation
let e_alloc = cx.expr_or_init(e);
let e_alloc =
if let ExprKind::AddrOf(_, _, inner_expr) = e_alloc.kind { inner_expr } else { e_alloc };
let alloc_ty = cx.typeck_results().node_type(e_alloc.hir_id);

// if we do not find it we bail out, as this may not be UB
// see https://github.com/rust-lang/unsafe-code-guidelines/issues/256
if alloc_ty.is_any_ptr() {
return None;
}

let from_layout = cx.layout_of(*inner_start_ty).ok()?;
let alloc_layout = cx.layout_of(alloc_ty).ok()?;
let to_layout = cx.layout_of(*inner_end_ty).ok()?;

if to_layout.layout.size() > from_layout.layout.size()
&& to_layout.layout.size() > alloc_layout.layout.size()
{
Some((from_layout, to_layout, *e_alloc))
} else {
None
}
}

fn peel_casts<'tcx>(cx: &LateContext<'tcx>, mut e: &'tcx Expr<'tcx>) -> (&'tcx Expr<'tcx>, bool) {
let mut gone_trough_unsafe_cell_raw_get = false;

Expand Down
12 changes: 6 additions & 6 deletions tests/ui/cast/cast-rfc0401.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 81,14 @@ fn main()
assert_eq!(u as *const u16, p as *const u16);

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

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

// ptr-ptr-cast (Length vtables, check length is preserved)
Expand Down
105 changes: 104 additions & 1 deletion tests/ui/lint/reference_casting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 30,7 @@ unsafe fn ref_to_mut() {
//~^ ERROR casting `&T` to `&mut T` is undefined behavior
let _num = &mut *(num as *const i32).cast::<i32>().cast_mut().cast_const().cast_mut();
//~^ ERROR casting `&T` to `&mut T` is undefined behavior
let _num = &mut *(std::ptr::from_ref(static_u8()) as *mut i32);
let _num = &mut *(std::ptr::from_ref(static_u8()) as *mut i8);
//~^ ERROR casting `&T` to `&mut T` is undefined behavior
let _num = &mut *std::mem::transmute::<_, *mut i32>(num);
//~^ ERROR casting `&T` to `&mut T` is undefined behavior
Expand Down Expand Up @@ -141,6 141,109 @@ unsafe fn assign_to_ref() {
}
}

#[repr(align(16))]
struct I64(i64);

#[repr(C)]
struct Mat3<T> {
a: Vec3<T>,
b: Vec3<T>,
c: Vec3<T>,
}

#[repr(C)]
struct Vec3<T>(T, T, T);

unsafe fn bigger_layout() {
{
let num = &mut 3i32;

let _num = &*(num as *const i32 as *const i64);
//~^ ERROR casting references to a bigger memory layout
let _num = &mut *(num as *mut i32 as *mut i64);
//~^ ERROR casting references to a bigger memory layout
let _num = &mut *(num as *mut i32 as *mut I64);
//~^ ERROR casting references to a bigger memory layout
std::ptr::write(num as *mut i32 as *mut i64, 2);
//~^ ERROR casting references to a bigger memory layout

let _num = &mut *(num as *mut i32);
}

{
let num = &mut [0i32; 3];

let _num = &mut *(num as *mut _ as *mut [i64; 2]);
//~^ ERROR casting references to a bigger memory layout
std::ptr::write_unaligned(num as *mut _ as *mut [i32; 4], [0, 0, 1, 1]);
//~^ ERROR casting references to a bigger memory layout

let _num = &mut *(num as *mut _ as *mut [u32; 3]);
let _num = &mut *(num as *mut _ as *mut [u32; 2]);
}

{
let num = &mut [0i32; 3] as &mut [i32];

let _num = &mut *(num as *mut _ as *mut i128);
//~^ ERROR casting references to a bigger memory layout
let _num = &mut *(num as *mut _ as *mut [i64; 4]);
//~^ ERROR casting references to a bigger memory layout

let _num = &mut *(num as *mut _ as *mut [u32]);
let _num = &mut *(num as *mut _ as *mut [i16]);
}

{
let mat3 = Mat3 { a: Vec3(0i32, 0, 0), b: Vec3(0, 0, 0), c: Vec3(0, 0, 0) };

let _num = &mut *(&mat3 as *const _ as *mut [[i64; 3]; 3]);
//~^ ERROR casting `&T` to `&mut T`
//~^^ ERROR casting references to a bigger memory layout
let _num = &*(&mat3 as *const _ as *mut [[i64; 3]; 3]);
//~^ ERROR casting references to a bigger memory layout

let _num = &*(&mat3 as *const _ as *mut [[i32; 3]; 3]);
}

{
let mut l: [u8; 2] = [0,1];
let w: *mut [u16; 2] = &mut l as *mut [u8; 2] as *mut _;
let w: *mut [u16] = unsafe {&mut *w};
//~^ ERROR casting references to a bigger memory layout
}

{
fn foo() -> [i32; 1] { todo!() }

let num = foo();
let _num = &*(&num as *const i32 as *const i64);
//~^ ERROR casting references to a bigger memory layout
let _num = &*(&foo() as *const i32 as *const i64);
//~^ ERROR casting references to a bigger memory layout
}

{
fn bar(_a: &[i32; 2]) -> &[i32; 1] { todo!() }

let num = bar(&[0, 0]);
let _num = &*(num as *const i32 as *const i64);
let _num = &*(bar(&[0, 0]) as *const i32 as *const i64);
}

{
fn foi<T>() -> T { todo!() }

let num = foi::<i32>();
let _num = &*(&num as *const i32 as *const i64);
//~^ ERROR casting references to a bigger memory layout
}

unsafe fn from_ref(this: &i32) -> &i64 {
&*(this as *const i32 as *const i64)
}
}

const RAW_PTR: *mut u8 = 1 as *mut u8;
unsafe fn no_warn() {
let num = &3i32;
Expand Down
Loading