Skip to content

Commit b94deb5

Browse files
committed
Taking a raw pointer on a union field is safe since Rust 1.92
1 parent d599529 commit b94deb5

File tree

3 files changed

+188
-38
lines changed

3 files changed

+188
-38
lines changed

clippy_lints/src/multiple_unsafe_ops_per_block.rs

Lines changed: 69 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
use clippy_utils::desugar_await;
22
use clippy_utils::diagnostics::span_lint_and_then;
3-
use clippy_utils::visitors::{Descend, Visitable, for_each_expr};
4-
use core::ops::ControlFlow::Continue;
53
use hir::def::{DefKind, Res};
64
use hir::{BlockCheckMode, ExprKind, QPath, UnOp};
7-
use rustc_ast::Mutability;
5+
use rustc_ast::{BorrowKind, Mutability};
86
use rustc_hir as hir;
7+
use rustc_hir::intravisit::{Visitor, walk_body, walk_expr};
98
use rustc_lint::{LateContext, LateLintPass};
10-
use rustc_middle::ty;
9+
use rustc_middle::hir::nested_filter;
10+
use rustc_middle::ty::{self, TypeckResults};
1111
use rustc_session::declare_lint_pass;
1212
use rustc_span::{DesugaringKind, Span};
1313

@@ -70,8 +70,7 @@ impl<'tcx> LateLintPass<'tcx> for MultipleUnsafeOpsPerBlock {
7070
{
7171
return;
7272
}
73-
let mut unsafe_ops = vec![];
74-
collect_unsafe_exprs(cx, block, &mut unsafe_ops);
73+
let unsafe_ops = UnsafeExprCollector::collect_unsafe_exprs(cx, block);
7574
if unsafe_ops.len() > 1 {
7675
span_lint_and_then(
7776
cx,
@@ -91,25 +90,47 @@ impl<'tcx> LateLintPass<'tcx> for MultipleUnsafeOpsPerBlock {
9190
}
9291
}
9392

94-
fn collect_unsafe_exprs<'tcx>(
95-
cx: &LateContext<'tcx>,
96-
node: impl Visitable<'tcx>,
97-
unsafe_ops: &mut Vec<(&'static str, Span)>,
98-
) {
99-
for_each_expr(cx, node, |expr| {
93+
struct UnsafeExprCollector<'cx, 'tcx> {
94+
cx: &'cx LateContext<'tcx>,
95+
typeck_results: &'tcx TypeckResults<'tcx>,
96+
unsafe_ops: Vec<(&'static str, Span)>,
97+
}
98+
99+
impl<'cx, 'tcx> UnsafeExprCollector<'cx, 'tcx> {
100+
fn collect_unsafe_exprs(cx: &'cx LateContext<'tcx>, block: &'tcx hir::Block<'tcx>) -> Vec<(&'static str, Span)> {
101+
let mut collector = Self {
102+
cx,
103+
typeck_results: cx.typeck_results(),
104+
unsafe_ops: vec![],
105+
};
106+
collector.visit_block(block);
107+
collector.unsafe_ops
108+
}
109+
}
110+
111+
impl<'tcx> Visitor<'tcx> for UnsafeExprCollector<'_, 'tcx> {
112+
type NestedFilter = nested_filter::OnlyBodies;
113+
114+
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
100115
match expr.kind {
101116
// The `await` itself will desugar to two unsafe calls, but we should ignore those.
102117
// Instead, check the expression that is `await`ed
103118
_ if let Some(e) = desugar_await(expr) => {
104-
collect_unsafe_exprs(cx, e, unsafe_ops);
105-
return Continue(Descend::No);
119+
return self.visit_expr(e);
106120
},
107121

108-
ExprKind::InlineAsm(_) => unsafe_ops.push(("inline assembly used here", expr.span)),
122+
ExprKind::InlineAsm(_) => self.unsafe_ops.push(("inline assembly used here", expr.span)),
123+
124+
ExprKind::AddrOf(BorrowKind::Raw, _, mut inner) => {
125+
while let ExprKind::Field(prefix, _) = inner.kind {
126+
inner = prefix;
127+
}
128+
return self.visit_expr(inner);
129+
},
109130

110131
ExprKind::Field(e, _) => {
111-
if cx.typeck_results().expr_ty(e).is_union() {
112-
unsafe_ops.push(("union field access occurs here", expr.span));
132+
if self.typeck_results.expr_ty(e).is_union() {
133+
self.unsafe_ops.push(("union field access occurs here", expr.span));
113134
}
114135
},
115136

@@ -127,32 +148,32 @@ fn collect_unsafe_exprs<'tcx>(
127148
..
128149
},
129150
)) => {
130-
unsafe_ops.push(("access of a mutable static occurs here", expr.span));
151+
self.unsafe_ops
152+
.push(("access of a mutable static occurs here", expr.span));
131153
},
132154

133-
ExprKind::Unary(UnOp::Deref, e) if cx.typeck_results().expr_ty_adjusted(e).is_raw_ptr() => {
134-
unsafe_ops.push(("raw pointer dereference occurs here", expr.span));
155+
ExprKind::Unary(UnOp::Deref, e) if self.typeck_results.expr_ty_adjusted(e).is_raw_ptr() => {
156+
self.unsafe_ops.push(("raw pointer dereference occurs here", expr.span));
135157
},
136158

137159
ExprKind::Call(path_expr, _) => {
138-
let sig = match *cx.typeck_results().expr_ty(path_expr).kind() {
139-
ty::FnDef(id, _) => cx.tcx.fn_sig(id).skip_binder(),
140-
ty::FnPtr(sig_tys, hdr) => sig_tys.with(hdr),
141-
_ => return Continue(Descend::Yes),
160+
let opt_sig = match *self.typeck_results.expr_ty(path_expr).kind() {
161+
ty::FnDef(id, _) => Some(self.cx.tcx.fn_sig(id).skip_binder()),
162+
ty::FnPtr(sig_tys, hdr) => Some(sig_tys.with(hdr)),
163+
_ => None,
142164
};
143-
if sig.safety().is_unsafe() {
144-
unsafe_ops.push(("unsafe function call occurs here", expr.span));
165+
if opt_sig.is_some_and(|sig| sig.safety().is_unsafe()) {
166+
self.unsafe_ops.push(("unsafe function call occurs here", expr.span));
145167
}
146168
},
147169

148170
ExprKind::MethodCall(..) => {
149-
if let Some(sig) = cx
150-
.typeck_results()
171+
let opt_sig = self
172+
.typeck_results
151173
.type_dependent_def_id(expr.hir_id)
152-
.map(|def_id| cx.tcx.fn_sig(def_id))
153-
&& sig.skip_binder().safety().is_unsafe()
154-
{
155-
unsafe_ops.push(("unsafe method call occurs here", expr.span));
174+
.map(|def_id| self.cx.tcx.fn_sig(def_id));
175+
if opt_sig.is_some_and(|sig| sig.skip_binder().safety().is_unsafe()) {
176+
self.unsafe_ops.push(("unsafe method call occurs here", expr.span));
156177
}
157178
},
158179

@@ -173,15 +194,26 @@ fn collect_unsafe_exprs<'tcx>(
173194
}
174195
))
175196
) {
176-
unsafe_ops.push(("modification of a mutable static occurs here", expr.span));
177-
collect_unsafe_exprs(cx, rhs, unsafe_ops);
178-
return Continue(Descend::No);
197+
self.unsafe_ops
198+
.push(("modification of a mutable static occurs here", expr.span));
199+
return self.visit_expr(rhs);
179200
}
180201
},
181202

182203
_ => {},
183204
}
184205

185-
Continue::<(), _>(Descend::Yes)
186-
});
206+
walk_expr(self, expr);
207+
}
208+
209+
fn visit_body(&mut self, body: &hir::Body<'tcx>) {
210+
let saved_typeck_results = self.typeck_results;
211+
self.typeck_results = self.cx.tcx.typeck_body(body.id());
212+
walk_body(self, body);
213+
self.typeck_results = saved_typeck_results;
214+
}
215+
216+
fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
217+
self.cx.tcx
218+
}
187219
}

tests/ui/multiple_unsafe_ops_per_block.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,4 +209,60 @@ async fn issue13879() {
209209
}
210210
}
211211

212+
fn issue16076() {
213+
#[derive(Clone, Copy)]
214+
union U {
215+
i: u32,
216+
f: f32,
217+
}
218+
219+
let u = U { i: 0 };
220+
221+
// Taking a raw pointer to a place is safe since Rust 1.92
222+
unsafe {
223+
_ = &raw const u.i;
224+
_ = &raw const u.i;
225+
}
226+
227+
// Taking a reference to a union field is not safe
228+
unsafe {
229+
//~^ multiple_unsafe_ops_per_block
230+
_ = &u.i;
231+
_ = &u.i;
232+
}
233+
234+
// Check that we still check and lint the prefix of the raw pointer to a field access
235+
#[expect(clippy::deref_addrof)]
236+
unsafe {
237+
//~^ multiple_unsafe_ops_per_block
238+
_ = &raw const (*&raw const u).i;
239+
_ = &raw const (*&raw const u).i;
240+
}
241+
242+
union V {
243+
u: U,
244+
}
245+
246+
// Taking a raw pointer to a union field of an union field (etc.) is safe
247+
let v = V { u };
248+
unsafe {
249+
_ = &raw const v.u.i;
250+
_ = &raw const v.u.i;
251+
}
252+
}
253+
254+
fn check_closures() {
255+
unsafe fn apply(f: impl Fn()) {
256+
todo!()
257+
}
258+
unsafe fn f(_x: i32) {
259+
todo!()
260+
}
261+
262+
unsafe {
263+
//~^ multiple_unsafe_ops_per_block
264+
apply(|| f(0));
265+
}
266+
}
267+
212268
fn main() {}

tests/ui/multiple_unsafe_ops_per_block.stderr

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,5 +255,67 @@ note: unsafe function call occurs here
255255
LL | Some(foo_unchecked()).unwrap_unchecked().await;
256256
| ^^^^^^^^^^^^^^^
257257

258-
error: aborting due to 11 previous errors
258+
error: this `unsafe` block contains 2 unsafe operations, expected only one
259+
--> tests/ui/multiple_unsafe_ops_per_block.rs:228:5
260+
|
261+
LL | / unsafe {
262+
LL | |
263+
LL | | _ = &u.i;
264+
LL | | _ = &u.i;
265+
LL | | }
266+
| |_____^
267+
|
268+
note: union field access occurs here
269+
--> tests/ui/multiple_unsafe_ops_per_block.rs:230:14
270+
|
271+
LL | _ = &u.i;
272+
| ^^^
273+
note: union field access occurs here
274+
--> tests/ui/multiple_unsafe_ops_per_block.rs:231:14
275+
|
276+
LL | _ = &u.i;
277+
| ^^^
278+
279+
error: this `unsafe` block contains 2 unsafe operations, expected only one
280+
--> tests/ui/multiple_unsafe_ops_per_block.rs:236:5
281+
|
282+
LL | / unsafe {
283+
LL | |
284+
LL | | _ = &raw const (*&raw const u).i;
285+
LL | | _ = &raw const (*&raw const u).i;
286+
LL | | }
287+
| |_____^
288+
|
289+
note: raw pointer dereference occurs here
290+
--> tests/ui/multiple_unsafe_ops_per_block.rs:238:24
291+
|
292+
LL | _ = &raw const (*&raw const u).i;
293+
| ^^^^^^^^^^^^^^^
294+
note: raw pointer dereference occurs here
295+
--> tests/ui/multiple_unsafe_ops_per_block.rs:239:24
296+
|
297+
LL | _ = &raw const (*&raw const u).i;
298+
| ^^^^^^^^^^^^^^^
299+
300+
error: this `unsafe` block contains 2 unsafe operations, expected only one
301+
--> tests/ui/multiple_unsafe_ops_per_block.rs:262:5
302+
|
303+
LL | / unsafe {
304+
LL | |
305+
LL | | apply(|| f(0));
306+
LL | | }
307+
| |_____^
308+
|
309+
note: unsafe function call occurs here
310+
--> tests/ui/multiple_unsafe_ops_per_block.rs:264:9
311+
|
312+
LL | apply(|| f(0));
313+
| ^^^^^^^^^^^^^^
314+
note: unsafe function call occurs here
315+
--> tests/ui/multiple_unsafe_ops_per_block.rs:264:18
316+
|
317+
LL | apply(|| f(0));
318+
| ^^^^
319+
320+
error: aborting due to 14 previous errors
259321

0 commit comments

Comments
 (0)