summaryrefslogtreecommitdiffstats
path: root/src/tools/clippy/clippy_lints/src/multiple_unsafe_ops_per_block.rs
blob: 63c575fca30ba17b2fecc1bf616e564345f6c10f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
use clippy_utils::{
    diagnostics::span_lint_and_then,
    visitors::{for_each_expr_with_closures, Descend, Visitable},
};
use core::ops::ControlFlow::Continue;
use hir::{
    def::{DefKind, Res},
    BlockCheckMode, ExprKind, QPath, UnOp, Unsafety,
};
use rustc_ast::Mutability;
use rustc_hir as hir;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::Span;

declare_clippy_lint! {
    /// ### What it does
    /// Checks for `unsafe` blocks that contain more than one unsafe operation.
    ///
    /// ### Why is this bad?
    /// Combined with `undocumented_unsafe_blocks`,
    /// this lint ensures that each unsafe operation must be independently justified.
    /// Combined with `unused_unsafe`, this lint also ensures
    /// elimination of unnecessary unsafe blocks through refactoring.
    ///
    /// ### Example
    /// ```rust
    /// /// Reads a `char` from the given pointer.
    /// ///
    /// /// # Safety
    /// ///
    /// /// `ptr` must point to four consecutive, initialized bytes which
    /// /// form a valid `char` when interpreted in the native byte order.
    /// fn read_char(ptr: *const u8) -> char {
    ///     // SAFETY: The caller has guaranteed that the value pointed
    ///     // to by `bytes` is a valid `char`.
    ///     unsafe { char::from_u32_unchecked(*ptr.cast::<u32>()) }
    /// }
    /// ```
    /// Use instead:
    /// ```rust
    /// /// Reads a `char` from the given pointer.
    /// ///
    /// /// # Safety
    /// ///
    /// /// - `ptr` must be 4-byte aligned, point to four consecutive
    /// ///   initialized bytes, and be valid for reads of 4 bytes.
    /// /// - The bytes pointed to by `ptr` must represent a valid
    /// ///   `char` when interpreted in the native byte order.
    /// fn read_char(ptr: *const u8) -> char {
    ///     // SAFETY: `ptr` is 4-byte aligned, points to four consecutive
    ///     // initialized bytes, and is valid for reads of 4 bytes.
    ///     let int_value = unsafe { *ptr.cast::<u32>() };
    ///
    ///     // SAFETY: The caller has guaranteed that the four bytes
    ///     // pointed to by `bytes` represent a valid `char`.
    ///     unsafe { char::from_u32_unchecked(int_value) }
    /// }
    /// ```
    #[clippy::version = "1.68.0"]
    pub MULTIPLE_UNSAFE_OPS_PER_BLOCK,
    restriction,
    "more than one unsafe operation per `unsafe` block"
}
declare_lint_pass!(MultipleUnsafeOpsPerBlock => [MULTIPLE_UNSAFE_OPS_PER_BLOCK]);

impl<'tcx> LateLintPass<'tcx> for MultipleUnsafeOpsPerBlock {
    fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) {
        if !matches!(block.rules, BlockCheckMode::UnsafeBlock(_)) || in_external_macro(cx.tcx.sess, block.span) {
            return;
        }
        let mut unsafe_ops = vec![];
        collect_unsafe_exprs(cx, block, &mut unsafe_ops);
        if unsafe_ops.len() > 1 {
            span_lint_and_then(
                cx,
                MULTIPLE_UNSAFE_OPS_PER_BLOCK,
                block.span,
                &format!(
                    "this `unsafe` block contains {} unsafe operations, expected only one",
                    unsafe_ops.len()
                ),
                |diag| {
                    for (msg, span) in unsafe_ops {
                        diag.span_note(span, msg);
                    }
                },
            );
        }
    }
}

fn collect_unsafe_exprs<'tcx>(
    cx: &LateContext<'tcx>,
    node: impl Visitable<'tcx>,
    unsafe_ops: &mut Vec<(&'static str, Span)>,
) {
    for_each_expr_with_closures(cx, node, |expr| {
        match expr.kind {
            ExprKind::InlineAsm(_) => unsafe_ops.push(("inline assembly used here", expr.span)),

            ExprKind::Field(e, _) => {
                if cx.typeck_results().expr_ty(e).is_union() {
                    unsafe_ops.push(("union field access occurs here", expr.span));
                }
            },

            ExprKind::Path(QPath::Resolved(
                _,
                hir::Path {
                    res: Res::Def(DefKind::Static(Mutability::Mut), _),
                    ..
                },
            )) => {
                unsafe_ops.push(("access of a mutable static occurs here", expr.span));
            },

            ExprKind::Unary(UnOp::Deref, e) if cx.typeck_results().expr_ty_adjusted(e).is_unsafe_ptr() => {
                unsafe_ops.push(("raw pointer dereference occurs here", expr.span));
            },

            ExprKind::Call(path_expr, _) => match path_expr.kind {
                ExprKind::Path(QPath::Resolved(
                    _,
                    hir::Path {
                        res: Res::Def(kind, def_id),
                        ..
                    },
                )) if kind.is_fn_like() => {
                    let sig = cx.tcx.fn_sig(*def_id);
                    if sig.0.unsafety() == Unsafety::Unsafe {
                        unsafe_ops.push(("unsafe function call occurs here", expr.span));
                    }
                },

                ExprKind::Path(QPath::TypeRelative(..)) => {
                    if let Some(sig) = cx
                        .typeck_results()
                        .type_dependent_def_id(path_expr.hir_id)
                        .map(|def_id| cx.tcx.fn_sig(def_id))
                    {
                        if sig.0.unsafety() == Unsafety::Unsafe {
                            unsafe_ops.push(("unsafe function call occurs here", expr.span));
                        }
                    }
                },

                _ => {},
            },

            ExprKind::MethodCall(..) => {
                if let Some(sig) = cx
                    .typeck_results()
                    .type_dependent_def_id(expr.hir_id)
                    .map(|def_id| cx.tcx.fn_sig(def_id))
                {
                    if sig.0.unsafety() == Unsafety::Unsafe {
                        unsafe_ops.push(("unsafe method call occurs here", expr.span));
                    }
                }
            },

            ExprKind::AssignOp(_, lhs, rhs) | ExprKind::Assign(lhs, rhs, _) => {
                if matches!(
                    lhs.kind,
                    ExprKind::Path(QPath::Resolved(
                        _,
                        hir::Path {
                            res: Res::Def(DefKind::Static(Mutability::Mut), _),
                            ..
                        }
                    ))
                ) {
                    unsafe_ops.push(("modification of a mutable static occurs here", expr.span));
                    collect_unsafe_exprs(cx, rhs, unsafe_ops);
                    return Continue(Descend::No);
                }
            },

            _ => {},
        };

        Continue::<(), _>(Descend::Yes)
    });
}