Skip to content

Commit 5da6067

Browse files
committed
Uplift clippy::cast_ref_to_mut to rustc
1 parent a51ad13 commit 5da6067

File tree

6 files changed

+196
-0
lines changed

6 files changed

+196
-0
lines changed

compiler/rustc_lint/messages.ftl

+2
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,8 @@ lint_builtin_unused_doc_comment = unused doc comment
155155
lint_builtin_while_true = denote infinite loops with `loop {"{"} ... {"}"}`
156156
.suggestion = use `loop`
157157
158+
lint_cast_ref_to_mut = casting `&T` to `&mut T` is undefined behavior, even if the reference is unused, consider instead using an `UnsafeCell`
159+
158160
lint_check_name_deprecated = lint name `{$lint_name}` is deprecated and does not have an effect anymore. Use: {$new_name}
159161
160162
lint_check_name_unknown = unknown lint: `{$lint_name}`
+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
use rustc_ast::Mutability;
2+
use rustc_hir::{Expr, ExprKind, MutTy, TyKind, UnOp};
3+
use rustc_middle::ty;
4+
use rustc_span::sym;
5+
6+
use crate::{lints::CastRefToMutDiag, LateContext, LateLintPass, LintContext};
7+
8+
declare_lint! {
9+
/// The `cast_ref_to_mut` lint checks for casts of `&T` to `&mut T`
10+
/// without using interior mutability.
11+
///
12+
/// ### Example
13+
///
14+
/// ```rust,compile_fail
15+
/// fn x(r: &i32) {
16+
/// unsafe {
17+
/// *(r as *const i32 as *mut i32) += 1;
18+
/// }
19+
/// }
20+
/// ```
21+
///
22+
/// {{produces}}
23+
///
24+
/// ### Explanation
25+
///
26+
/// Casting `&T` to `&mut T` without using interior mutability is undefined behavior,
27+
/// as it's a violation of Rust reference aliasing requirements.
28+
///
29+
/// `UnsafeCell` is the only way to obtain aliasable data that is considered
30+
/// mutable.
31+
CAST_REF_TO_MUT,
32+
Deny,
33+
"casts of `&T` to `&mut T` without interior mutability"
34+
}
35+
36+
declare_lint_pass!(CastRefToMut => [CAST_REF_TO_MUT]);
37+
38+
impl<'tcx> LateLintPass<'tcx> for CastRefToMut {
39+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
40+
let ExprKind::Unary(UnOp::Deref, e) = &expr.kind else { return; };
41+
42+
let e = e.peel_blocks();
43+
let e = if let ExprKind::Cast(e, t) = e.kind
44+
&& let TyKind::Ptr(MutTy { mutbl: Mutability::Mut, .. }) = t.kind {
45+
e
46+
} else if let ExprKind::MethodCall(_, expr, [], _) = e.kind
47+
&& let Some(def_id) = cx.typeck_results().type_dependent_def_id(e.hir_id)
48+
&& cx.tcx.is_diagnostic_item(sym::ptr_cast_mut, def_id) {
49+
expr
50+
} else {
51+
return;
52+
};
53+
54+
let e = e.peel_blocks();
55+
let e = if let ExprKind::Cast(e, t) = e.kind
56+
&& let TyKind::Ptr(MutTy { mutbl: Mutability::Not, .. }) = t.kind {
57+
e
58+
} else if let ExprKind::Call(path, [arg]) = e.kind
59+
&& let ExprKind::Path(ref qpath) = path.kind
60+
&& let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
61+
&& cx.tcx.is_diagnostic_item(sym::ptr_from_ref, def_id) {
62+
arg
63+
} else {
64+
return;
65+
};
66+
67+
let e = e.peel_blocks();
68+
if let ty::Ref(..) = cx.typeck_results().node_type(e.hir_id).kind() {
69+
cx.emit_spanned_lint(CAST_REF_TO_MUT, expr.span, CastRefToMutDiag);
70+
}
71+
}
72+
}

compiler/rustc_lint/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ extern crate tracing;
5050

5151
mod array_into_iter;
5252
pub mod builtin;
53+
mod cast_ref_to_mut;
5354
mod context;
5455
mod deref_into_dyn_supertrait;
5556
mod drop_forget_useless;
@@ -97,6 +98,7 @@ use rustc_span::Span;
9798

9899
use array_into_iter::ArrayIntoIter;
99100
use builtin::*;
101+
use cast_ref_to_mut::*;
100102
use deref_into_dyn_supertrait::*;
101103
use drop_forget_useless::*;
102104
use enum_intrinsics_non_enums::EnumIntrinsicsNonEnums;
@@ -214,6 +216,7 @@ late_lint_methods!(
214216
BoxPointers: BoxPointers,
215217
PathStatements: PathStatements,
216218
LetUnderscore: LetUnderscore,
219+
CastRefToMut: CastRefToMut,
217220
// Depends on referenced function signatures in expressions
218221
UnusedResults: UnusedResults,
219222
NonUpperCaseGlobals: NonUpperCaseGlobals,

compiler/rustc_lint/src/lints.rs

+5
Original file line numberDiff line numberDiff line change
@@ -718,6 +718,11 @@ pub enum InvalidFromUtf8Diag {
718718
},
719719
}
720720

721+
// cast_ref_to_mut.rs
722+
#[derive(LintDiagnostic)]
723+
#[diag(lint_cast_ref_to_mut)]
724+
pub struct CastRefToMutDiag;
725+
721726
// hidden_unicode_codepoints.rs
722727
#[derive(LintDiagnostic)]
723728
#[diag(lint_hidden_unicode_codepoints)]

tests/ui/lint/cast_ref_to_mut.rs

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// check-fail
2+
3+
#![feature(ptr_from_ref)]
4+
5+
extern "C" {
6+
// N.B., mutability can be easily incorrect in FFI calls -- as
7+
// in C, the default is mutable pointers.
8+
fn ffi(c: *mut u8);
9+
fn int_ffi(c: *mut i32);
10+
}
11+
12+
fn main() {
13+
let s = String::from("Hello");
14+
let a = &s;
15+
unsafe {
16+
let num = &3i32;
17+
let mut_num = &mut 3i32;
18+
19+
(*(a as *const _ as *mut String)).push_str(" world");
20+
//~^ ERROR casting `&T` to `&mut T` is undefined behavior
21+
*(a as *const _ as *mut _) = String::from("Replaced");
22+
//~^ ERROR casting `&T` to `&mut T` is undefined behavior
23+
*(a as *const _ as *mut String) += " world";
24+
//~^ ERROR casting `&T` to `&mut T` is undefined behavior
25+
let _num = &mut *(num as *const i32 as *mut i32);
26+
//~^ ERROR casting `&T` to `&mut T` is undefined behavior
27+
let _num = &mut *(num as *const i32).cast_mut();
28+
//~^ ERROR casting `&T` to `&mut T` is undefined behavior
29+
let _num = *{ num as *const i32 }.cast_mut();
30+
//~^ ERROR casting `&T` to `&mut T` is undefined behavior
31+
*std::ptr::from_ref(num).cast_mut() += 1;
32+
//~^ ERROR casting `&T` to `&mut T` is undefined behavior
33+
*std::ptr::from_ref({ num }).cast_mut() += 1;
34+
//~^ ERROR casting `&T` to `&mut T` is undefined behavior
35+
*{ std::ptr::from_ref(num) }.cast_mut() += 1;
36+
//~^ ERROR casting `&T` to `&mut T` is undefined behavior
37+
*(std::ptr::from_ref({ num }) as *mut i32) += 1;
38+
//~^ ERROR casting `&T` to `&mut T` is undefined behavior
39+
40+
// Shouldn't be warned against
41+
println!("{}", *(num as *const _ as *const i16));
42+
println!("{}", *(mut_num as *mut _ as *mut i16));
43+
ffi(a.as_ptr() as *mut _);
44+
int_ffi(num as *const _ as *mut _);
45+
int_ffi(&3 as *const _ as *mut _);
46+
let mut value = 3;
47+
let value: *const i32 = &mut value;
48+
*(value as *const i16 as *mut i16) = 42;
49+
}
50+
}

tests/ui/lint/cast_ref_to_mut.stderr

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
error: casting `&T` to `&mut T` is undefined behavior, even if the reference is unused, consider instead using an `UnsafeCell`
2+
--> $DIR/cast_ref_to_mut.rs:19:9
3+
|
4+
LL | (*(a as *const _ as *mut String)).push_str(" world");
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: `#[deny(cast_ref_to_mut)]` on by default
8+
9+
error: casting `&T` to `&mut T` is undefined behavior, even if the reference is unused, consider instead using an `UnsafeCell`
10+
--> $DIR/cast_ref_to_mut.rs:21:9
11+
|
12+
LL | *(a as *const _ as *mut _) = String::from("Replaced");
13+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
14+
15+
error: casting `&T` to `&mut T` is undefined behavior, even if the reference is unused, consider instead using an `UnsafeCell`
16+
--> $DIR/cast_ref_to_mut.rs:23:9
17+
|
18+
LL | *(a as *const _ as *mut String) += " world";
19+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
20+
21+
error: casting `&T` to `&mut T` is undefined behavior, even if the reference is unused, consider instead using an `UnsafeCell`
22+
--> $DIR/cast_ref_to_mut.rs:25:25
23+
|
24+
LL | let _num = &mut *(num as *const i32 as *mut i32);
25+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
26+
27+
error: casting `&T` to `&mut T` is undefined behavior, even if the reference is unused, consider instead using an `UnsafeCell`
28+
--> $DIR/cast_ref_to_mut.rs:27:25
29+
|
30+
LL | let _num = &mut *(num as *const i32).cast_mut();
31+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
32+
33+
error: casting `&T` to `&mut T` is undefined behavior, even if the reference is unused, consider instead using an `UnsafeCell`
34+
--> $DIR/cast_ref_to_mut.rs:29:20
35+
|
36+
LL | let _num = *{ num as *const i32 }.cast_mut();
37+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
38+
39+
error: casting `&T` to `&mut T` is undefined behavior, even if the reference is unused, consider instead using an `UnsafeCell`
40+
--> $DIR/cast_ref_to_mut.rs:31:9
41+
|
42+
LL | *std::ptr::from_ref(num).cast_mut() += 1;
43+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
44+
45+
error: casting `&T` to `&mut T` is undefined behavior, even if the reference is unused, consider instead using an `UnsafeCell`
46+
--> $DIR/cast_ref_to_mut.rs:33:9
47+
|
48+
LL | *std::ptr::from_ref({ num }).cast_mut() += 1;
49+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
50+
51+
error: casting `&T` to `&mut T` is undefined behavior, even if the reference is unused, consider instead using an `UnsafeCell`
52+
--> $DIR/cast_ref_to_mut.rs:35:9
53+
|
54+
LL | *{ std::ptr::from_ref(num) }.cast_mut() += 1;
55+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
56+
57+
error: casting `&T` to `&mut T` is undefined behavior, even if the reference is unused, consider instead using an `UnsafeCell`
58+
--> $DIR/cast_ref_to_mut.rs:37:9
59+
|
60+
LL | *(std::ptr::from_ref({ num }) as *mut i32) += 1;
61+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
62+
63+
error: aborting due to 10 previous errors
64+

0 commit comments

Comments
 (0)