Skip to content

Commit 1393ef1

Browse files
committed
Auto merge of #116199 - Urgau:simplify-invalid_ref_casting, r=cjgillot
Simplify some of the logic in the `invalid_reference_casting` lint This PR simplifies 2 areas of the logic for the `invalid_reference_casting` lint: - The init detection: we now use the newly added `expr_or_init` function instead of a manual detection - The ref-to-mut-ptr casting detection logic: I simplified this logic by caring less hardly about the order of the casting operations Those two simplifications permits us to detect more cases, as can be seen in the test output changes.
2 parents 42faef5 + 1b2c1a8 commit 1393ef1

File tree

6 files changed

+182
-134
lines changed

6 files changed

+182
-134
lines changed

compiler/rustc_lint/src/context.rs

+38-1
Original file line numberDiff line numberDiff line change
@@ -1316,6 +1316,40 @@ impl<'tcx> LateContext<'tcx> {
13161316
})
13171317
}
13181318

1319+
/// If the given expression is a local binding, find the initializer expression.
1320+
/// If that initializer expression is another local binding, find its initializer again.
1321+
///
1322+
/// This process repeats as long as possible (but usually no more than once).
1323+
/// Type-check adjustments are not taken in account in this function.
1324+
///
1325+
/// Examples:
1326+
/// ```
1327+
/// let abc = 1;
1328+
/// let def = abc + 2;
1329+
/// // ^^^^^^^ output
1330+
/// let def = def;
1331+
/// dbg!(def);
1332+
/// // ^^^ input
1333+
/// ```
1334+
pub fn expr_or_init<'a>(&self, mut expr: &'a hir::Expr<'tcx>) -> &'a hir::Expr<'tcx> {
1335+
expr = expr.peel_blocks();
1336+
1337+
while let hir::ExprKind::Path(ref qpath) = expr.kind
1338+
&& let Some(parent_node) = match self.qpath_res(qpath, expr.hir_id) {
1339+
Res::Local(hir_id) => self.tcx.hir().find_parent(hir_id),
1340+
_ => None,
1341+
}
1342+
&& let Some(init) = match parent_node {
1343+
hir::Node::Expr(expr) => Some(expr),
1344+
hir::Node::Local(hir::Local { init, .. }) => *init,
1345+
_ => None
1346+
}
1347+
{
1348+
expr = init.peel_blocks();
1349+
}
1350+
expr
1351+
}
1352+
13191353
/// If the given expression is a local binding, find the initializer expression.
13201354
/// If that initializer expression is another local or **outside** (`const`/`static`)
13211355
/// binding, find its initializer again.
@@ -1338,7 +1372,10 @@ impl<'tcx> LateContext<'tcx> {
13381372
/// dbg!(def);
13391373
/// // ^^^ input
13401374
/// ```
1341-
pub fn expr_or_init<'a>(&self, mut expr: &'a hir::Expr<'tcx>) -> &'a hir::Expr<'tcx> {
1375+
pub fn expr_or_init_with_outside_body<'a>(
1376+
&self,
1377+
mut expr: &'a hir::Expr<'tcx>,
1378+
) -> &'a hir::Expr<'tcx> {
13421379
expr = expr.peel_blocks();
13431380

13441381
while let hir::ExprKind::Path(ref qpath) = expr.kind

compiler/rustc_lint/src/invalid_from_utf8.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,9 @@ impl<'tcx> LateLintPass<'tcx> for InvalidFromUtf8 {
8484
)
8585
};
8686

87-
let mut init = cx.expr_or_init(arg);
87+
let mut init = cx.expr_or_init_with_outside_body(arg);
8888
while let ExprKind::AddrOf(.., inner) = init.kind {
89-
init = cx.expr_or_init(inner);
89+
init = cx.expr_or_init_with_outside_body(inner);
9090
}
9191
match init.kind {
9292
ExprKind::Lit(Spanned { node: lit, .. }) => {

compiler/rustc_lint/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ late_lint_methods!(
196196
BoxPointers: BoxPointers,
197197
PathStatements: PathStatements,
198198
LetUnderscore: LetUnderscore,
199-
InvalidReferenceCasting: InvalidReferenceCasting::default(),
199+
InvalidReferenceCasting: InvalidReferenceCasting,
200200
// Depends on referenced function signatures in expressions
201201
UnusedResults: UnusedResults,
202202
NonUpperCaseGlobals: NonUpperCaseGlobals,

compiler/rustc_lint/src/reference_casting.rs

+39-108
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
use rustc_ast::Mutability;
2-
use rustc_data_structures::fx::FxHashMap;
3-
use rustc_hir::{def::Res, Expr, ExprKind, HirId, Local, QPath, StmtKind, UnOp};
2+
use rustc_hir::{Expr, ExprKind, UnOp};
43
use rustc_middle::ty::{self, TypeAndMut};
5-
use rustc_span::{sym, Span};
4+
use rustc_span::sym;
65

76
use crate::{lints::InvalidReferenceCastingDiag, LateContext, LateLintPass, LintContext};
87

@@ -34,38 +33,18 @@ declare_lint! {
3433
"casts of `&T` to `&mut T` without interior mutability"
3534
}
3635

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

4438
impl<'tcx> LateLintPass<'tcx> for InvalidReferenceCasting {
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 {
50-
return;
51-
};
52-
53-
if is_cast_from_const_to_mut(cx, init) {
54-
self.casted.insert(local.pat.hir_id, init.span);
55-
}
56-
}
57-
5839
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
5940
let Some((is_assignment, e)) = is_operation_we_care_about(cx, expr) else {
6041
return;
6142
};
6243

63-
let orig_cast = if is_cast_from_const_to_mut(cx, e) {
64-
None
65-
} else if let ExprKind::Path(QPath::Resolved(_, path)) = e.kind
66-
&& let Res::Local(hir_id) = &path.res
67-
&& let Some(orig_cast) = self.casted.get(hir_id) {
68-
Some(*orig_cast)
44+
let init = cx.expr_or_init(e);
45+
46+
let orig_cast = if is_cast_from_const_to_mut(cx, init) {
47+
if init.span != e.span { Some(init.span) } else { None }
6948
} else {
7049
return;
7150
};
@@ -125,99 +104,51 @@ fn is_operation_we_care_about<'tcx>(
125104
deref_assign_or_addr_of(e).or_else(|| ptr_write(cx, e))
126105
}
127106

128-
fn is_cast_from_const_to_mut<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> bool {
129-
let e = e.peel_blocks();
107+
fn is_cast_from_const_to_mut<'tcx>(cx: &LateContext<'tcx>, orig_expr: &'tcx Expr<'tcx>) -> bool {
108+
let mut need_check_freeze = false;
109+
let mut e = orig_expr;
130110

131-
fn from_casts<'tcx>(
132-
cx: &LateContext<'tcx>,
133-
e: &'tcx Expr<'tcx>,
134-
need_check_freeze: &mut bool,
135-
) -> Option<&'tcx Expr<'tcx>> {
136-
// <expr> as *mut ...
137-
let mut e = if let ExprKind::Cast(e, t) = e.kind
138-
&& let ty::RawPtr(TypeAndMut { mutbl: Mutability::Mut, .. }) = cx.typeck_results().node_type(t.hir_id).kind() {
139-
e
140-
// <expr>.cast_mut()
111+
let end_ty = cx.typeck_results().node_type(orig_expr.hir_id);
112+
113+
// Bail out early if the end type is **not** a mutable pointer.
114+
if !matches!(end_ty.kind(), ty::RawPtr(TypeAndMut { ty: _, mutbl: Mutability::Mut })) {
115+
return false;
116+
}
117+
118+
loop {
119+
e = e.peel_blocks();
120+
// <expr> as ...
121+
e = if let ExprKind::Cast(expr, _) = e.kind {
122+
expr
123+
// <expr>.cast(), <expr>.cast_mut() or <expr>.cast_const()
141124
} else if let ExprKind::MethodCall(_, expr, [], _) = e.kind
142125
&& let Some(def_id) = cx.typeck_results().type_dependent_def_id(e.hir_id)
143-
&& cx.tcx.is_diagnostic_item(sym::ptr_cast_mut, def_id) {
126+
&& matches!(
127+
cx.tcx.get_diagnostic_name(def_id),
128+
Some(sym::ptr_cast | sym::const_ptr_cast | sym::ptr_cast_mut | sym::ptr_cast_const)
129+
)
130+
{
144131
expr
145-
// UnsafeCell::raw_get(<expr>)
132+
// ptr::from_ref(<expr>), UnsafeCell::raw_get(<expr>) or mem::transmute<_, _>(<expr>)
146133
} else if let ExprKind::Call(path, [arg]) = e.kind
147134
&& let ExprKind::Path(ref qpath) = path.kind
148135
&& let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
149-
&& cx.tcx.is_diagnostic_item(sym::unsafe_cell_raw_get, def_id)
136+
&& matches!(
137+
cx.tcx.get_diagnostic_name(def_id),
138+
Some(sym::ptr_from_ref | sym::unsafe_cell_raw_get | sym::transmute)
139+
)
150140
{
151-
*need_check_freeze = true;
141+
if cx.tcx.is_diagnostic_item(sym::unsafe_cell_raw_get, def_id) {
142+
need_check_freeze = true;
143+
}
152144
arg
153145
} else {
154-
return None;
146+
break;
155147
};
156-
157-
let mut had_at_least_one_cast = false;
158-
loop {
159-
e = e.peel_blocks();
160-
// <expr> as *mut/const ... or <expr> as <uint>
161-
e = if let ExprKind::Cast(expr, t) = e.kind
162-
&& matches!(cx.typeck_results().node_type(t.hir_id).kind(), ty::RawPtr(_) | ty::Uint(_)) {
163-
had_at_least_one_cast = true;
164-
expr
165-
// <expr>.cast(), <expr>.cast_mut() or <expr>.cast_const()
166-
} else if let ExprKind::MethodCall(_, expr, [], _) = e.kind
167-
&& let Some(def_id) = cx.typeck_results().type_dependent_def_id(e.hir_id)
168-
&& matches!(
169-
cx.tcx.get_diagnostic_name(def_id),
170-
Some(sym::ptr_cast | sym::const_ptr_cast | sym::ptr_cast_mut | sym::ptr_cast_const)
171-
)
172-
{
173-
had_at_least_one_cast = true;
174-
expr
175-
// ptr::from_ref(<expr>) or UnsafeCell::raw_get(<expr>)
176-
} else if let ExprKind::Call(path, [arg]) = e.kind
177-
&& let ExprKind::Path(ref qpath) = path.kind
178-
&& let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
179-
&& matches!(
180-
cx.tcx.get_diagnostic_name(def_id),
181-
Some(sym::ptr_from_ref | sym::unsafe_cell_raw_get)
182-
)
183-
{
184-
if cx.tcx.is_diagnostic_item(sym::unsafe_cell_raw_get, def_id) {
185-
*need_check_freeze = true;
186-
}
187-
return Some(arg);
188-
} else if had_at_least_one_cast {
189-
return Some(e);
190-
} else {
191-
return None;
192-
};
193-
}
194-
}
195-
196-
fn from_transmute<'tcx>(
197-
cx: &LateContext<'tcx>,
198-
e: &'tcx Expr<'tcx>,
199-
) -> Option<&'tcx Expr<'tcx>> {
200-
// mem::transmute::<_, *mut _>(<expr>)
201-
if let ExprKind::Call(path, [arg]) = e.kind
202-
&& let ExprKind::Path(ref qpath) = path.kind
203-
&& let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
204-
&& cx.tcx.is_diagnostic_item(sym::transmute, def_id)
205-
&& let ty::RawPtr(TypeAndMut { mutbl: Mutability::Mut, .. }) = cx.typeck_results().node_type(e.hir_id).kind() {
206-
Some(arg)
207-
} else {
208-
None
209-
}
210148
}
211149

212-
let mut need_check_freeze = false;
213-
let Some(e) = from_casts(cx, e, &mut need_check_freeze).or_else(|| from_transmute(cx, e))
214-
else {
215-
return false;
216-
};
217-
218-
let e = e.peel_blocks();
219-
let node_type = cx.typeck_results().node_type(e.hir_id);
220-
if let ty::Ref(_, inner_ty, Mutability::Not) = node_type.kind() {
150+
let start_ty = cx.typeck_results().node_type(e.hir_id);
151+
if let ty::Ref(_, inner_ty, Mutability::Not) = start_ty.kind() {
221152
// If an UnsafeCell method is involved we need to additionaly check the
222153
// inner type for the presence of the Freeze trait (ie does NOT contain
223154
// an UnsafeCell), since in that case we would incorrectly lint on valid casts.

tests/ui/lint/reference_casting.rs

+23
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ unsafe fn ref_to_mut() {
3636
//~^ ERROR casting `&T` to `&mut T` is undefined behavior
3737
let _num = &mut *std::mem::transmute::<_, *mut i32>(num);
3838
//~^ ERROR casting `&T` to `&mut T` is undefined behavior
39+
let _num = &mut *(std::mem::transmute::<_, *mut i32>(num) as *mut i32);
40+
//~^ ERROR casting `&T` to `&mut T` is undefined behavior
3941
let _num = &mut *std::cell::UnsafeCell::raw_get(
4042
//~^ ERROR casting `&T` to `&mut T` is undefined behavior
4143
num as *const i32 as *const std::cell::UnsafeCell<i32>
@@ -47,8 +49,20 @@ unsafe fn ref_to_mut() {
4749
let deferred = (std::ptr::from_ref(num) as *const i32 as *const i32).cast_mut() as *mut i32;
4850
let _num = &mut *deferred;
4951
//~^ ERROR casting `&T` to `&mut T` is undefined behavior
52+
let deferred_rebind = deferred;
53+
let _num = &mut *deferred_rebind;
54+
//~^ ERROR casting `&T` to `&mut T` is undefined behavior
5055
let _num = &mut *(num as *const _ as usize as *mut i32);
5156
//~^ ERROR casting `&T` to `&mut T` is undefined behavior
57+
let _num = &mut *(std::mem::transmute::<_, *mut _>(num as *const i32) as *mut i32);
58+
//~^ ERROR casting `&T` to `&mut T` is undefined behavior
59+
60+
static NUM: &'static i32 = &2;
61+
let num = NUM as *const i32 as *mut i32;
62+
let num = num;
63+
let num = num;
64+
let _num = &mut *num;
65+
//~^ ERROR casting `&T` to `&mut T` is undefined behavior
5266

5367
unsafe fn generic_ref_cast_mut<T>(this: &T) -> &mut T {
5468
&mut *((this as *const _) as *mut _)
@@ -85,6 +99,8 @@ unsafe fn assign_to_ref() {
8599
//~^ ERROR assigning to `&T` is undefined behavior
86100
*std::mem::transmute::<_, *mut i32>(num) += 1;
87101
//~^ ERROR assigning to `&T` is undefined behavior
102+
*(std::mem::transmute::<_, *mut i32>(num) as *mut i32) += 1;
103+
//~^ ERROR assigning to `&T` is undefined behavior
88104
std::ptr::write(
89105
//~^ ERROR assigning to `&T` is undefined behavior
90106
std::mem::transmute::<*const i32, *mut i32>(num),
@@ -94,6 +110,9 @@ unsafe fn assign_to_ref() {
94110
let value = num as *const i32 as *mut i32;
95111
*value = 1;
96112
//~^ ERROR assigning to `&T` is undefined behavior
113+
let value_rebind = value;
114+
*value_rebind = 1;
115+
//~^ ERROR assigning to `&T` is undefined behavior
97116
*(num as *const i32).cast::<i32>().cast_mut() = 2;
98117
//~^ ERROR assigning to `&T` is undefined behavior
99118
*(num as *const _ as usize as *mut i32) = 2;
@@ -111,6 +130,7 @@ unsafe fn assign_to_ref() {
111130
}
112131
}
113132

133+
const RAW_PTR: *mut u8 = 1 as *mut u8;
114134
unsafe fn no_warn() {
115135
let num = &3i32;
116136
let mut_num = &mut 3i32;
@@ -125,6 +145,9 @@ unsafe fn no_warn() {
125145
let mut value = 3;
126146
let value: *const i32 = &mut value;
127147
*(value as *const i16 as *mut i16) = 42;
148+
*RAW_PTR = 42; // RAW_PTR is defined outside the function body,
149+
// make sure we don't ICE on it when trying to
150+
// determine if we should lint on it or not.
128151

129152
fn safe_as_mut<T>(x: &std::cell::UnsafeCell<T>) -> &mut T {
130153
unsafe { &mut *std::cell::UnsafeCell::raw_get(x as *const _ as *const _) }

0 commit comments

Comments
 (0)