Skip to content

Commit d170833

Browse files
committed
Auto merge of rust-lang#112431 - Urgau:cast_ref_to_mut_improvments, r=Nilstrieb
Improve `invalid_reference_casting` lint This PR is a follow-up to rust-lang#111567 and rust-lang#113422. This PR does multiple things: - First it adds support for deferred de-reference, the goal is to support code like this, where the casting and de-reference are not done on the same expression ```rust let myself = self as *const Self as *mut Self; *myself = Self::Ready(value); ``` - Second it does not lint anymore on SB/TB UB code by only checking assignments (`=`, `+=`, ...) and creation of mutable references `&mut *` - Thirdly it greatly improves the diagnostics in particular for cast from `&mut` to `&mut` or assignments - ~~And lastly it renames the lint from `cast_ref_to_mut` to `invalid_reference_casting` which is more consistent with the ["rules"](rust-lang/rust-clippy#2845) and also more consistent with what the lint checks~~ *rust-lang#113422 This PR is best reviewed commit by commit. r? compiler
2 parents 64ad036 + 507d497 commit d170833

File tree

11 files changed

+240
-111
lines changed

11 files changed

+240
-111
lines changed

compiler/rustc_lint/messages.ftl

+5-1
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,11 @@ lint_invalid_nan_comparisons_eq_ne = incorrect NaN comparison, NaN cannot be dir
318318
319319
lint_invalid_nan_comparisons_lt_le_gt_ge = incorrect NaN comparison, NaN is not orderable
320320
321-
lint_invalid_reference_casting = casting `&T` to `&mut T` is undefined behavior, even if the reference is unused, consider instead using an `UnsafeCell`
321+
lint_invalid_reference_casting_assign_to_ref = assigning to `&T` is undefined behavior, consider using an `UnsafeCell`
322+
.label = casting happend here
323+
324+
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`
325+
.label = casting happend here
322326
323327
lint_lintpass_by_hand = implementing `LintPass` by hand
324328
.help = try using `declare_lint_pass!` or `impl_lint_pass!` instead

compiler/rustc_lint/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ late_lint_methods!(
218218
BoxPointers: BoxPointers,
219219
PathStatements: PathStatements,
220220
LetUnderscore: LetUnderscore,
221-
InvalidReferenceCasting: InvalidReferenceCasting,
221+
InvalidReferenceCasting: InvalidReferenceCasting::default(),
222222
// Depends on referenced function signatures in expressions
223223
UnusedResults: UnusedResults,
224224
NonUpperCaseGlobals: NonUpperCaseGlobals,

compiler/rustc_lint/src/lints.rs

+12-2
Original file line numberDiff line numberDiff line change
@@ -745,8 +745,18 @@ pub enum InvalidFromUtf8Diag {
745745

746746
// reference_casting.rs
747747
#[derive(LintDiagnostic)]
748-
#[diag(lint_invalid_reference_casting)]
749-
pub struct InvalidReferenceCastingDiag;
748+
pub enum InvalidReferenceCastingDiag {
749+
#[diag(lint_invalid_reference_casting_borrow_as_mut)]
750+
BorrowAsMut {
751+
#[label]
752+
orig_cast: Option<Span>,
753+
},
754+
#[diag(lint_invalid_reference_casting_assign_to_ref)]
755+
AssignToRef {
756+
#[label]
757+
orig_cast: Option<Span>,
758+
},
759+
}
750760

751761
// hidden_unicode_codepoints.rs
752762
#[derive(LintDiagnostic)]
+85-28
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use rustc_ast::Mutability;
2-
use rustc_hir::{Expr, ExprKind, MutTy, TyKind, UnOp};
3-
use rustc_middle::ty;
4-
use rustc_span::sym;
2+
use rustc_data_structures::fx::FxHashMap;
3+
use rustc_hir::{def::Res, Expr, ExprKind, HirId, Local, QPath, StmtKind, UnOp};
4+
use rustc_middle::ty::{self, TypeAndMut};
5+
use rustc_span::{sym, Span};
56

67
use crate::{lints::InvalidReferenceCastingDiag, LateContext, LateLintPass, LintContext};
78

@@ -12,7 +13,6 @@ declare_lint! {
1213
/// ### Example
1314
///
1415
/// ```rust,compile_fail
15-
/// # #![deny(invalid_reference_casting)]
1616
/// fn x(r: &i32) {
1717
/// unsafe {
1818
/// *(r as *const i32 as *mut i32) += 1;
@@ -30,46 +30,103 @@ declare_lint! {
3030
/// `UnsafeCell` is the only way to obtain aliasable data that is considered
3131
/// mutable.
3232
INVALID_REFERENCE_CASTING,
33-
Allow,
33+
Deny,
3434
"casts of `&T` to `&mut T` without interior mutability"
3535
}
3636

37-
declare_lint_pass!(InvalidReferenceCasting => [INVALID_REFERENCE_CASTING]);
37+
#[derive(Default)]
38+
pub struct InvalidReferenceCasting {
39+
casted: FxHashMap<HirId, Span>,
40+
}
41+
42+
impl_lint_pass!(InvalidReferenceCasting => [INVALID_REFERENCE_CASTING]);
3843

3944
impl<'tcx> LateLintPass<'tcx> for InvalidReferenceCasting {
40-
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
41-
let ExprKind::Unary(UnOp::Deref, e) = &expr.kind else {
45+
fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx rustc_hir::Stmt<'tcx>) {
46+
let StmtKind::Local(local) = stmt.kind else {
47+
return;
48+
};
49+
let Local { init: Some(init), els: None, .. } = local else {
4250
return;
4351
};
4452

45-
let e = e.peel_blocks();
46-
let e = if let ExprKind::Cast(e, t) = e.kind
47-
&& let TyKind::Ptr(MutTy { mutbl: Mutability::Mut, .. }) = t.kind {
48-
e
49-
} else if let ExprKind::MethodCall(_, expr, [], _) = e.kind
50-
&& let Some(def_id) = cx.typeck_results().type_dependent_def_id(e.hir_id)
51-
&& cx.tcx.is_diagnostic_item(sym::ptr_cast_mut, def_id) {
53+
if is_cast_from_const_to_mut(cx, init) {
54+
self.casted.insert(local.pat.hir_id, init.span);
55+
}
56+
}
57+
58+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
59+
// &mut <expr>
60+
let inner = if let ExprKind::AddrOf(_, Mutability::Mut, expr) = expr.kind {
61+
expr
62+
// <expr> = ...
63+
} else if let ExprKind::Assign(expr, _, _) = expr.kind {
64+
expr
65+
// <expr> += ...
66+
} else if let ExprKind::AssignOp(_, expr, _) = expr.kind {
5267
expr
5368
} else {
5469
return;
5570
};
5671

57-
let e = e.peel_blocks();
58-
let e = if let ExprKind::Cast(e, t) = e.kind
59-
&& let TyKind::Ptr(MutTy { mutbl: Mutability::Not, .. }) = t.kind {
60-
e
61-
} else if let ExprKind::Call(path, [arg]) = e.kind
62-
&& let ExprKind::Path(ref qpath) = path.kind
63-
&& let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
64-
&& cx.tcx.is_diagnostic_item(sym::ptr_from_ref, def_id) {
65-
arg
72+
let ExprKind::Unary(UnOp::Deref, e) = &inner.kind else {
73+
return;
74+
};
75+
76+
let orig_cast = if is_cast_from_const_to_mut(cx, e) {
77+
None
78+
} else if let ExprKind::Path(QPath::Resolved(_, path)) = e.kind
79+
&& let Res::Local(hir_id) = &path.res
80+
&& let Some(orig_cast) = self.casted.get(hir_id) {
81+
Some(*orig_cast)
6682
} else {
6783
return;
6884
};
6985

70-
let e = e.peel_blocks();
71-
if let ty::Ref(..) = cx.typeck_results().node_type(e.hir_id).kind() {
72-
cx.emit_spanned_lint(INVALID_REFERENCE_CASTING, expr.span, InvalidReferenceCastingDiag);
73-
}
86+
cx.emit_spanned_lint(
87+
INVALID_REFERENCE_CASTING,
88+
expr.span,
89+
if matches!(expr.kind, ExprKind::AddrOf(..)) {
90+
InvalidReferenceCastingDiag::BorrowAsMut { orig_cast }
91+
} else {
92+
InvalidReferenceCastingDiag::AssignToRef { orig_cast }
93+
},
94+
);
7495
}
7596
}
97+
98+
fn is_cast_from_const_to_mut<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> bool {
99+
let e = e.peel_blocks();
100+
101+
// <expr> as *mut ...
102+
let e = if let ExprKind::Cast(e, t) = e.kind
103+
&& let ty::RawPtr(TypeAndMut { mutbl: Mutability::Mut, .. }) = cx.typeck_results().node_type(t.hir_id).kind() {
104+
e
105+
// <expr>.cast_mut()
106+
} else if let ExprKind::MethodCall(_, expr, [], _) = e.kind
107+
&& let Some(def_id) = cx.typeck_results().type_dependent_def_id(e.hir_id)
108+
&& cx.tcx.is_diagnostic_item(sym::ptr_cast_mut, def_id) {
109+
expr
110+
} else {
111+
return false;
112+
};
113+
114+
let e = e.peel_blocks();
115+
116+
// <expr> as *const ...
117+
let e = if let ExprKind::Cast(e, t) = e.kind
118+
&& let ty::RawPtr(TypeAndMut { mutbl: Mutability::Not, .. }) = cx.typeck_results().node_type(t.hir_id).kind() {
119+
e
120+
// ptr::from_ref(<expr>)
121+
} else if let ExprKind::Call(path, [arg]) = e.kind
122+
&& let ExprKind::Path(ref qpath) = path.kind
123+
&& let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
124+
&& cx.tcx.is_diagnostic_item(sym::ptr_from_ref, def_id) {
125+
arg
126+
} else {
127+
return false;
128+
};
129+
130+
let e = e.peel_blocks();
131+
matches!(cx.typeck_results().node_type(e.hir_id).kind(), ty::Ref(_, _, Mutability::Not))
132+
}

library/core/src/cell.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1893,7 +1893,8 @@ impl<T: ?Sized + fmt::Display> fmt::Display for RefMut<'_, T> {
18931893
/// on an _exclusive_ `UnsafeCell<T>`. Even though `T` and `UnsafeCell<T>` have the
18941894
/// same memory layout, the following is not allowed and undefined behavior:
18951895
///
1896-
/// ```rust,no_run
1896+
#[cfg_attr(bootstrap, doc = "```rust,no_run")]
1897+
#[cfg_attr(not(bootstrap), doc = "```rust,compile_fail")]
18971898
/// # use std::cell::UnsafeCell;
18981899
/// unsafe fn not_allowed<T>(ptr: &UnsafeCell<T>) -> &mut T {
18991900
/// let t = ptr as *const UnsafeCell<T> as *mut T;

src/tools/miri/tests/fail/both_borrows/illegal_write1.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
//@revisions: stack tree
22
//@[tree]compile-flags: -Zmiri-tree-borrows
33

4+
#![allow(invalid_reference_casting)]
5+
46
fn main() {
57
let target = Box::new(42); // has an implicit raw
68
let xref = &*target;

src/tools/miri/tests/fail/stacked_borrows/illegal_write3.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#![allow(invalid_reference_casting)]
2+
13
fn main() {
24
let target = 42;
35
// Make sure raw ptr with raw tag cannot mutate frozen location without breaking the shared ref.

tests/ui/const-generics/issues/issue-100313.rs

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ impl <const B: &'static bool> T<B> {
99
unsafe {
1010
*(B as *const bool as *mut bool) = false;
1111
//~^ ERROR evaluation of constant value failed [E0080]
12+
//~| ERROR assigning to `&T` is undefined behavior
1213
}
1314
}
1415
}

tests/ui/const-generics/issues/issue-100313.stderr

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
error: assigning to `&T` is undefined behavior, consider using an `UnsafeCell`
2+
--> $DIR/issue-100313.rs:10:13
3+
|
4+
LL | *(B as *const bool as *mut bool) = false;
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: `#[deny(invalid_reference_casting)]` on by default
8+
19
error[E0080]: evaluation of constant value failed
210
--> $DIR/issue-100313.rs:10:13
311
|
@@ -10,11 +18,11 @@ note: inside `T::<&true>::set_false`
1018
LL | *(B as *const bool as *mut bool) = false;
1119
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1220
note: inside `_`
13-
--> $DIR/issue-100313.rs:18:5
21+
--> $DIR/issue-100313.rs:19:5
1422
|
1523
LL | x.set_false();
1624
| ^^^^^^^^^^^^^
1725

18-
error: aborting due to previous error
26+
error: aborting due to 2 previous errors
1927

2028
For more information about this error, try `rustc --explain E0080`.

tests/ui/lint/reference_casting.rs

+57-37
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// check-fail
22

33
#![feature(ptr_from_ref)]
4-
#![deny(invalid_reference_casting)]
54

65
extern "C" {
76
// N.B., mutability can be easily incorrect in FFI calls -- as
@@ -10,42 +9,63 @@ extern "C" {
109
fn int_ffi(c: *mut i32);
1110
}
1211

13-
fn main() {
12+
unsafe fn ref_to_mut() {
13+
let num = &3i32;
14+
15+
let _num = &mut *(num as *const i32 as *mut i32);
16+
//~^ ERROR casting `&T` to `&mut T` is undefined behavior
17+
let _num = &mut *(num as *const i32).cast_mut();
18+
//~^ ERROR casting `&T` to `&mut T` is undefined behavior
19+
let _num = &mut *std::ptr::from_ref(num).cast_mut();
20+
//~^ ERROR casting `&T` to `&mut T` is undefined behavior
21+
let _num = &mut *std::ptr::from_ref({ num }).cast_mut();
22+
//~^ ERROR casting `&T` to `&mut T` is undefined behavior
23+
let _num = &mut *{ std::ptr::from_ref(num) }.cast_mut();
24+
//~^ ERROR casting `&T` to `&mut T` is undefined behavior
25+
let _num = &mut *(std::ptr::from_ref({ num }) as *mut i32);
26+
//~^ ERROR casting `&T` to `&mut T` is undefined behavior
27+
28+
let deferred = num as *const i32 as *mut i32;
29+
let _num = &mut *deferred;
30+
//~^ ERROR casting `&T` to `&mut T` is undefined behavior
31+
}
32+
33+
unsafe fn assign_to_ref() {
1434
let s = String::from("Hello");
1535
let a = &s;
16-
unsafe {
17-
let num = &3i32;
18-
let mut_num = &mut 3i32;
19-
20-
(*(a as *const _ as *mut String)).push_str(" world");
21-
//~^ ERROR casting `&T` to `&mut T` is undefined behavior
22-
*(a as *const _ as *mut _) = String::from("Replaced");
23-
//~^ ERROR casting `&T` to `&mut T` is undefined behavior
24-
*(a as *const _ as *mut String) += " world";
25-
//~^ ERROR casting `&T` to `&mut T` is undefined behavior
26-
let _num = &mut *(num as *const i32 as *mut i32);
27-
//~^ ERROR casting `&T` to `&mut T` is undefined behavior
28-
let _num = &mut *(num as *const i32).cast_mut();
29-
//~^ ERROR casting `&T` to `&mut T` is undefined behavior
30-
let _num = *{ num as *const i32 }.cast_mut();
31-
//~^ ERROR casting `&T` to `&mut T` is undefined behavior
32-
*std::ptr::from_ref(num).cast_mut() += 1;
33-
//~^ ERROR casting `&T` to `&mut T` is undefined behavior
34-
*std::ptr::from_ref({ num }).cast_mut() += 1;
35-
//~^ ERROR casting `&T` to `&mut T` is undefined behavior
36-
*{ std::ptr::from_ref(num) }.cast_mut() += 1;
37-
//~^ ERROR casting `&T` to `&mut T` is undefined behavior
38-
*(std::ptr::from_ref({ num }) as *mut i32) += 1;
39-
//~^ ERROR casting `&T` to `&mut T` is undefined behavior
40-
41-
// Shouldn't be warned against
42-
println!("{}", *(num as *const _ as *const i16));
43-
println!("{}", *(mut_num as *mut _ as *mut i16));
44-
ffi(a.as_ptr() as *mut _);
45-
int_ffi(num as *const _ as *mut _);
46-
int_ffi(&3 as *const _ as *mut _);
47-
let mut value = 3;
48-
let value: *const i32 = &mut value;
49-
*(value as *const i16 as *mut i16) = 42;
50-
}
36+
let num = &3i32;
37+
38+
*(a as *const _ as *mut _) = String::from("Replaced");
39+
//~^ ERROR assigning to `&T` is undefined behavior
40+
*(a as *const _ as *mut String) += " world";
41+
//~^ ERROR assigning to `&T` is undefined behavior
42+
*std::ptr::from_ref(num).cast_mut() += 1;
43+
//~^ ERROR assigning to `&T` is undefined behavior
44+
*std::ptr::from_ref({ num }).cast_mut() += 1;
45+
//~^ ERROR assigning to `&T` is undefined behavior
46+
*{ std::ptr::from_ref(num) }.cast_mut() += 1;
47+
//~^ ERROR assigning to `&T` is undefined behavior
48+
*(std::ptr::from_ref({ num }) as *mut i32) += 1;
49+
//~^ ERROR assigning to `&T` is undefined behavior
50+
let value = num as *const i32 as *mut i32;
51+
*value = 1;
52+
//~^ ERROR assigning to `&T` is undefined behavior
5153
}
54+
55+
unsafe fn no_warn() {
56+
let num = &3i32;
57+
let mut_num = &mut 3i32;
58+
let a = &String::from("ffi");
59+
60+
*(num as *const i32 as *mut i32);
61+
println!("{}", *(num as *const _ as *const i16));
62+
println!("{}", *(mut_num as *mut _ as *mut i16));
63+
ffi(a.as_ptr() as *mut _);
64+
int_ffi(num as *const _ as *mut _);
65+
int_ffi(&3 as *const _ as *mut _);
66+
let mut value = 3;
67+
let value: *const i32 = &mut value;
68+
*(value as *const i16 as *mut i16) = 42;
69+
}
70+
71+
fn main() {}

0 commit comments

Comments
 (0)