Skip to content

Commit 181cb9c

Browse files
committed
Count unsafe operations only towards the innermost unsafe block
Also, macro calls are counted as at most one unsafe operation.
1 parent 24e16f9 commit 181cb9c

File tree

3 files changed

+130
-11
lines changed

3 files changed

+130
-11
lines changed

clippy_lints/src/multiple_unsafe_ops_per_block.rs

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use rustc_lint::{LateContext, LateLintPass};
99
use rustc_middle::hir::nested_filter;
1010
use rustc_middle::ty::{self, TyCtxt, TypeckResults};
1111
use rustc_session::declare_lint_pass;
12-
use rustc_span::{DesugaringKind, Span};
12+
use rustc_span::Span;
1313

1414
declare_clippy_lint! {
1515
/// ### What it does
@@ -56,12 +56,16 @@ declare_clippy_lint! {
5656
/// }
5757
/// ```
5858
///
59-
/// ### Note
59+
/// ### Notes
6060
///
61-
/// Taking a raw pointer to a union field is always safe and will
62-
/// not be considered unsafe by this lint, even when linting code written
63-
/// with a specified Rust version of 1.91 or earlier (which required
64-
/// using an `unsafe` block).
61+
/// - Unsafe operations only count towards the total for the innermost
62+
/// enclosing `unsafe` block.
63+
/// - Each call to a macro expanding to unsafe operations count for one
64+
/// unsafe operation.
65+
/// - Taking a raw pointer to a union field is always safe and will
66+
/// not be considered unsafe by this lint, even when linting code written
67+
/// with a specified Rust version of 1.91 or earlier (which required
68+
/// using an `unsafe` block).
6569
#[clippy::version = "1.69.0"]
6670
pub MULTIPLE_UNSAFE_OPS_PER_BLOCK,
6771
restriction,
@@ -71,10 +75,7 @@ declare_lint_pass!(MultipleUnsafeOpsPerBlock => [MULTIPLE_UNSAFE_OPS_PER_BLOCK])
7175

7276
impl<'tcx> LateLintPass<'tcx> for MultipleUnsafeOpsPerBlock {
7377
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) {
74-
if !matches!(block.rules, BlockCheckMode::UnsafeBlock(_))
75-
|| block.span.in_external_macro(cx.tcx.sess.source_map())
76-
|| block.span.is_desugaring(DesugaringKind::Await)
77-
{
78+
if !matches!(block.rules, BlockCheckMode::UnsafeBlock(_)) || block.span.from_expansion() {
7879
return;
7980
}
8081
let unsafe_ops = UnsafeExprCollector::collect_unsafe_exprs(cx, block);
@@ -126,6 +127,26 @@ impl<'tcx> Visitor<'tcx> for UnsafeExprCollector<'tcx> {
126127
return self.visit_expr(e);
127128
},
128129

130+
// Do not recurse inside an inner `unsafe` block, it will be checked on its own
131+
ExprKind::Block(block, _) if matches!(block.rules, BlockCheckMode::UnsafeBlock(_)) => return,
132+
133+
// Macro calls containing at least one unsafe operations count
134+
// as one unsafe operation.
135+
_ if expr.span.from_expansion() => {
136+
let mut inner_collector = Self {
137+
tcx: self.tcx,
138+
typeck_results: self.typeck_results,
139+
unsafe_ops: vec![],
140+
};
141+
walk_expr(&mut inner_collector, expr);
142+
if !inner_collector.unsafe_ops.is_empty() {
143+
let span = expr.span.source_callsite();
144+
self.unsafe_ops
145+
.push(("macro call expanded to unsafe operation here", span));
146+
}
147+
return;
148+
},
149+
129150
ExprKind::InlineAsm(_) => self.unsafe_ops.push(("inline assembly used here", expr.span)),
130151

131152
ExprKind::AddrOf(BorrowKind::Raw, _, mut inner) => {

tests/ui/multiple_unsafe_ops_per_block.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,4 +312,60 @@ fn check_closures() {
312312
}
313313
}
314314

315+
fn issue16116() {
316+
unsafe fn foo() -> u32 {
317+
0
318+
}
319+
320+
unsafe {
321+
// Do not lint even though `format!` expansion
322+
// contains unsafe calls.
323+
let _ = format!("{}", foo());
324+
}
325+
326+
unsafe {
327+
//~^ multiple_unsafe_ops_per_block
328+
let _ = format!("{}", foo());
329+
let _ = format!("{}", foo());
330+
}
331+
332+
unsafe {
333+
// Do not lint: only one `assert!()` argument is unsafe
334+
assert_eq!(foo(), 0, "{}", 1 + 2);
335+
}
336+
337+
unsafe {
338+
// Do not lint, each macro call counts for at most
339+
// one unsafe operation.
340+
assert_eq!(foo(), 0, "{}", foo()); // One unsafe operation
341+
}
342+
343+
unsafe {
344+
// Do not lint, a macro that doesn't emit unsafe operations
345+
// will not count.
346+
assert_eq!(foo(), 0, "{}", foo()); // One unsafe operation
347+
assert_eq!(1, 2); // Zero unsafe operations
348+
}
349+
350+
unsafe {
351+
//~^ multiple_unsafe_ops_per_block
352+
assert_eq!(foo(), 0, "{}", 1 + 2);
353+
assert_eq!(foo(), 0, "{}", 1 + 2);
354+
}
355+
356+
unsafe {
357+
// A macro whose expansion contains unsafe blocks will not
358+
// check inside the blocks.
359+
macro_rules! unsafe_twice {
360+
($e:expr) => {
361+
unsafe {
362+
$e;
363+
$e;
364+
}
365+
};
366+
};
367+
unsafe_twice!(foo());
368+
}
369+
}
370+
315371
fn main() {}

tests/ui/multiple_unsafe_ops_per_block.stderr

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,5 +359,47 @@ note: unsafe function call occurs here
359359
LL | apply(|| f(0));
360360
| ^^^^
361361

362-
error: aborting due to 16 previous errors
362+
error: this `unsafe` block contains 2 unsafe operations, expected only one
363+
--> tests/ui/multiple_unsafe_ops_per_block.rs:326:5
364+
|
365+
LL | / unsafe {
366+
LL | |
367+
LL | | let _ = format!("{}", foo());
368+
LL | | let _ = format!("{}", foo());
369+
LL | | }
370+
| |_____^
371+
|
372+
note: macro call expanded to unsafe operation here
373+
--> tests/ui/multiple_unsafe_ops_per_block.rs:328:17
374+
|
375+
LL | let _ = format!("{}", foo());
376+
| ^^^^^^^^^^^^^^^^^^^^
377+
note: macro call expanded to unsafe operation here
378+
--> tests/ui/multiple_unsafe_ops_per_block.rs:329:17
379+
|
380+
LL | let _ = format!("{}", foo());
381+
| ^^^^^^^^^^^^^^^^^^^^
382+
383+
error: this `unsafe` block contains 2 unsafe operations, expected only one
384+
--> tests/ui/multiple_unsafe_ops_per_block.rs:350:5
385+
|
386+
LL | / unsafe {
387+
LL | |
388+
LL | | assert_eq!(foo(), 0, "{}", 1 + 2);
389+
LL | | assert_eq!(foo(), 0, "{}", 1 + 2);
390+
LL | | }
391+
| |_____^
392+
|
393+
note: macro call expanded to unsafe operation here
394+
--> tests/ui/multiple_unsafe_ops_per_block.rs:352:9
395+
|
396+
LL | assert_eq!(foo(), 0, "{}", 1 + 2);
397+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
398+
note: macro call expanded to unsafe operation here
399+
--> tests/ui/multiple_unsafe_ops_per_block.rs:353:9
400+
|
401+
LL | assert_eq!(foo(), 0, "{}", 1 + 2);
402+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
403+
404+
error: aborting due to 18 previous errors
363405

0 commit comments

Comments
 (0)