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