]> git.proxmox.com Git - rustc.git/blame - src/tools/clippy/clippy_lints/src/multiple_unsafe_ops_per_block.rs
New upstream version 1.73.0+dfsg1
[rustc.git] / src / tools / clippy / clippy_lints / src / multiple_unsafe_ops_per_block.rs
CommitLineData
add651ee
FG
1use clippy_utils::diagnostics::span_lint_and_then;
2use clippy_utils::visitors::{for_each_expr_with_closures, Descend, Visitable};
9ffffee4 3use core::ops::ControlFlow::Continue;
add651ee
FG
4use hir::def::{DefKind, Res};
5use hir::{BlockCheckMode, ExprKind, QPath, UnOp, Unsafety};
9ffffee4
FG
6use rustc_ast::Mutability;
7use rustc_hir as hir;
8use rustc_lint::{LateContext, LateLintPass};
9use rustc_middle::lint::in_external_macro;
353b0b11 10use rustc_middle::ty;
9ffffee4
FG
11use rustc_session::{declare_lint_pass, declare_tool_lint};
12use rustc_span::Span;
13
14declare_clippy_lint! {
15 /// ### What it does
16 /// Checks for `unsafe` blocks that contain more than one unsafe operation.
17 ///
18 /// ### Why is this bad?
19 /// Combined with `undocumented_unsafe_blocks`,
20 /// this lint ensures that each unsafe operation must be independently justified.
21 /// Combined with `unused_unsafe`, this lint also ensures
22 /// elimination of unnecessary unsafe blocks through refactoring.
23 ///
24 /// ### Example
25 /// ```rust
26 /// /// Reads a `char` from the given pointer.
27 /// ///
28 /// /// # Safety
29 /// ///
30 /// /// `ptr` must point to four consecutive, initialized bytes which
31 /// /// form a valid `char` when interpreted in the native byte order.
32 /// fn read_char(ptr: *const u8) -> char {
33 /// // SAFETY: The caller has guaranteed that the value pointed
34 /// // to by `bytes` is a valid `char`.
35 /// unsafe { char::from_u32_unchecked(*ptr.cast::<u32>()) }
36 /// }
37 /// ```
38 /// Use instead:
39 /// ```rust
40 /// /// Reads a `char` from the given pointer.
41 /// ///
42 /// /// # Safety
43 /// ///
44 /// /// - `ptr` must be 4-byte aligned, point to four consecutive
45 /// /// initialized bytes, and be valid for reads of 4 bytes.
46 /// /// - The bytes pointed to by `ptr` must represent a valid
47 /// /// `char` when interpreted in the native byte order.
48 /// fn read_char(ptr: *const u8) -> char {
49 /// // SAFETY: `ptr` is 4-byte aligned, points to four consecutive
50 /// // initialized bytes, and is valid for reads of 4 bytes.
51 /// let int_value = unsafe { *ptr.cast::<u32>() };
52 ///
53 /// // SAFETY: The caller has guaranteed that the four bytes
54 /// // pointed to by `bytes` represent a valid `char`.
55 /// unsafe { char::from_u32_unchecked(int_value) }
56 /// }
57 /// ```
49aad941 58 #[clippy::version = "1.69.0"]
9ffffee4
FG
59 pub MULTIPLE_UNSAFE_OPS_PER_BLOCK,
60 restriction,
61 "more than one unsafe operation per `unsafe` block"
62}
63declare_lint_pass!(MultipleUnsafeOpsPerBlock => [MULTIPLE_UNSAFE_OPS_PER_BLOCK]);
64
65impl<'tcx> LateLintPass<'tcx> for MultipleUnsafeOpsPerBlock {
66 fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) {
67 if !matches!(block.rules, BlockCheckMode::UnsafeBlock(_)) || in_external_macro(cx.tcx.sess, block.span) {
68 return;
69 }
70 let mut unsafe_ops = vec![];
71 collect_unsafe_exprs(cx, block, &mut unsafe_ops);
72 if unsafe_ops.len() > 1 {
73 span_lint_and_then(
74 cx,
75 MULTIPLE_UNSAFE_OPS_PER_BLOCK,
76 block.span,
77 &format!(
78 "this `unsafe` block contains {} unsafe operations, expected only one",
79 unsafe_ops.len()
80 ),
81 |diag| {
82 for (msg, span) in unsafe_ops {
83 diag.span_note(span, msg);
84 }
85 },
86 );
87 }
88 }
89}
90
91fn collect_unsafe_exprs<'tcx>(
92 cx: &LateContext<'tcx>,
93 node: impl Visitable<'tcx>,
94 unsafe_ops: &mut Vec<(&'static str, Span)>,
95) {
96 for_each_expr_with_closures(cx, node, |expr| {
97 match expr.kind {
98 ExprKind::InlineAsm(_) => unsafe_ops.push(("inline assembly used here", expr.span)),
99
100 ExprKind::Field(e, _) => {
101 if cx.typeck_results().expr_ty(e).is_union() {
102 unsafe_ops.push(("union field access occurs here", expr.span));
103 }
104 },
105
106 ExprKind::Path(QPath::Resolved(
107 _,
108 hir::Path {
109 res: Res::Def(DefKind::Static(Mutability::Mut), _),
110 ..
111 },
112 )) => {
113 unsafe_ops.push(("access of a mutable static occurs here", expr.span));
114 },
115
116 ExprKind::Unary(UnOp::Deref, e) if cx.typeck_results().expr_ty_adjusted(e).is_unsafe_ptr() => {
117 unsafe_ops.push(("raw pointer dereference occurs here", expr.span));
118 },
119
353b0b11
FG
120 ExprKind::Call(path_expr, _) => {
121 let sig = match *cx.typeck_results().expr_ty(path_expr).kind() {
122 ty::FnDef(id, _) => cx.tcx.fn_sig(id).skip_binder(),
123 ty::FnPtr(sig) => sig,
124 _ => return Continue(Descend::Yes),
125 };
126 if sig.unsafety() == Unsafety::Unsafe {
127 unsafe_ops.push(("unsafe function call occurs here", expr.span));
128 }
9ffffee4
FG
129 },
130
131 ExprKind::MethodCall(..) => {
132 if let Some(sig) = cx
133 .typeck_results()
134 .type_dependent_def_id(expr.hir_id)
135 .map(|def_id| cx.tcx.fn_sig(def_id))
136 {
fe692bf9 137 if sig.skip_binder().unsafety() == Unsafety::Unsafe {
9ffffee4
FG
138 unsafe_ops.push(("unsafe method call occurs here", expr.span));
139 }
140 }
141 },
142
143 ExprKind::AssignOp(_, lhs, rhs) | ExprKind::Assign(lhs, rhs, _) => {
144 if matches!(
145 lhs.kind,
146 ExprKind::Path(QPath::Resolved(
147 _,
148 hir::Path {
149 res: Res::Def(DefKind::Static(Mutability::Mut), _),
150 ..
151 }
152 ))
153 ) {
154 unsafe_ops.push(("modification of a mutable static occurs here", expr.span));
155 collect_unsafe_exprs(cx, rhs, unsafe_ops);
156 return Continue(Descend::No);
157 }
158 },
159
160 _ => {},
161 };
162
163 Continue::<(), _>(Descend::Yes)
164 });
165}