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