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