hir_ty/diagnostics/
unsafe_check.rs

1//! Provides validations for unsafe code. Currently checks if unsafe functions are missing
2//! unsafe blocks.
3
4use std::mem;
5
6use either::Either;
7use hir_def::{
8    AdtId, DefWithBodyId, FieldId, FunctionId, VariantId,
9    expr_store::Body,
10    hir::{Expr, ExprId, ExprOrPatId, Pat, PatId, Statement, UnaryOp},
11    path::Path,
12    resolver::{HasResolver, ResolveValueResult, Resolver, ValueNs},
13    type_ref::Rawness,
14};
15use span::Edition;
16
17use crate::{
18    InferenceResult, Interner, TargetFeatures, TyExt, TyKind, db::HirDatabase,
19    utils::is_fn_unsafe_to_call,
20};
21
22#[derive(Debug, Default)]
23pub struct MissingUnsafeResult {
24    pub unsafe_exprs: Vec<(ExprOrPatId, UnsafetyReason)>,
25    /// If `fn_is_unsafe` is false, `unsafe_exprs` are hard errors. If true, they're `unsafe_op_in_unsafe_fn`.
26    pub fn_is_unsafe: bool,
27    pub deprecated_safe_calls: Vec<ExprId>,
28}
29
30pub fn missing_unsafe(db: &dyn HirDatabase, def: DefWithBodyId) -> MissingUnsafeResult {
31    let _p = tracing::info_span!("missing_unsafe").entered();
32
33    let is_unsafe = match def {
34        DefWithBodyId::FunctionId(it) => db.function_data(it).is_unsafe(),
35        DefWithBodyId::StaticId(_)
36        | DefWithBodyId::ConstId(_)
37        | DefWithBodyId::VariantId(_)
38        | DefWithBodyId::InTypeConstId(_) => false,
39    };
40
41    let mut res = MissingUnsafeResult { fn_is_unsafe: is_unsafe, ..MissingUnsafeResult::default() };
42    let body = db.body(def);
43    let infer = db.infer(def);
44    let mut callback = |diag| match diag {
45        UnsafeDiagnostic::UnsafeOperation { node, inside_unsafe_block, reason } => {
46            if inside_unsafe_block == InsideUnsafeBlock::No {
47                res.unsafe_exprs.push((node, reason));
48            }
49        }
50        UnsafeDiagnostic::DeprecatedSafe2024 { node, inside_unsafe_block } => {
51            if inside_unsafe_block == InsideUnsafeBlock::No {
52                res.deprecated_safe_calls.push(node)
53            }
54        }
55    };
56    let mut visitor = UnsafeVisitor::new(db, &infer, &body, def, &mut callback);
57    visitor.walk_expr(body.body_expr);
58
59    if !is_unsafe {
60        // Unsafety in function parameter patterns (that can only be union destructuring)
61        // cannot be inserted into an unsafe block, so even with `unsafe_op_in_unsafe_fn`
62        // it is turned off for unsafe functions.
63        for &param in &body.params {
64            visitor.walk_pat(param);
65        }
66    }
67
68    res
69}
70
71#[derive(Debug, Clone, Copy)]
72pub enum UnsafetyReason {
73    UnionField,
74    UnsafeFnCall,
75    InlineAsm,
76    RawPtrDeref,
77    MutableStatic,
78    ExternStatic,
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82pub enum InsideUnsafeBlock {
83    No,
84    Yes,
85}
86
87#[derive(Debug)]
88enum UnsafeDiagnostic {
89    UnsafeOperation {
90        node: ExprOrPatId,
91        inside_unsafe_block: InsideUnsafeBlock,
92        reason: UnsafetyReason,
93    },
94    /// A lint.
95    DeprecatedSafe2024 { node: ExprId, inside_unsafe_block: InsideUnsafeBlock },
96}
97
98pub fn unsafe_operations_for_body(
99    db: &dyn HirDatabase,
100    infer: &InferenceResult,
101    def: DefWithBodyId,
102    body: &Body,
103    callback: &mut dyn FnMut(ExprOrPatId),
104) {
105    let mut visitor_callback = |diag| {
106        if let UnsafeDiagnostic::UnsafeOperation { node, .. } = diag {
107            callback(node);
108        }
109    };
110    let mut visitor = UnsafeVisitor::new(db, infer, body, def, &mut visitor_callback);
111    visitor.walk_expr(body.body_expr);
112    for &param in &body.params {
113        visitor.walk_pat(param);
114    }
115}
116
117pub fn unsafe_operations(
118    db: &dyn HirDatabase,
119    infer: &InferenceResult,
120    def: DefWithBodyId,
121    body: &Body,
122    current: ExprId,
123    callback: &mut dyn FnMut(InsideUnsafeBlock),
124) {
125    let mut visitor_callback = |diag| {
126        if let UnsafeDiagnostic::UnsafeOperation { inside_unsafe_block, .. } = diag {
127            callback(inside_unsafe_block);
128        }
129    };
130    let mut visitor = UnsafeVisitor::new(db, infer, body, def, &mut visitor_callback);
131    _ = visitor.resolver.update_to_inner_scope(db.upcast(), def, current);
132    visitor.walk_expr(current);
133}
134
135struct UnsafeVisitor<'a> {
136    db: &'a dyn HirDatabase,
137    infer: &'a InferenceResult,
138    body: &'a Body,
139    resolver: Resolver,
140    def: DefWithBodyId,
141    inside_unsafe_block: InsideUnsafeBlock,
142    inside_assignment: bool,
143    inside_union_destructure: bool,
144    callback: &'a mut dyn FnMut(UnsafeDiagnostic),
145    def_target_features: TargetFeatures,
146    // FIXME: This needs to be the edition of the span of each call.
147    edition: Edition,
148}
149
150impl<'a> UnsafeVisitor<'a> {
151    fn new(
152        db: &'a dyn HirDatabase,
153        infer: &'a InferenceResult,
154        body: &'a Body,
155        def: DefWithBodyId,
156        unsafe_expr_cb: &'a mut dyn FnMut(UnsafeDiagnostic),
157    ) -> Self {
158        let resolver = def.resolver(db.upcast());
159        let def_target_features = match def {
160            DefWithBodyId::FunctionId(func) => TargetFeatures::from_attrs(&db.attrs(func.into())),
161            _ => TargetFeatures::default(),
162        };
163        let edition = resolver.module().krate().data(db).edition;
164        Self {
165            db,
166            infer,
167            body,
168            resolver,
169            def,
170            inside_unsafe_block: InsideUnsafeBlock::No,
171            inside_assignment: false,
172            inside_union_destructure: false,
173            callback: unsafe_expr_cb,
174            def_target_features,
175            edition,
176        }
177    }
178
179    fn on_unsafe_op(&mut self, node: ExprOrPatId, reason: UnsafetyReason) {
180        (self.callback)(UnsafeDiagnostic::UnsafeOperation {
181            node,
182            inside_unsafe_block: self.inside_unsafe_block,
183            reason,
184        });
185    }
186
187    fn check_call(&mut self, node: ExprId, func: FunctionId) {
188        let unsafety = is_fn_unsafe_to_call(self.db, func, &self.def_target_features, self.edition);
189        match unsafety {
190            crate::utils::Unsafety::Safe => {}
191            crate::utils::Unsafety::Unsafe => {
192                self.on_unsafe_op(node.into(), UnsafetyReason::UnsafeFnCall)
193            }
194            crate::utils::Unsafety::DeprecatedSafe2024 => {
195                (self.callback)(UnsafeDiagnostic::DeprecatedSafe2024 {
196                    node,
197                    inside_unsafe_block: self.inside_unsafe_block,
198                })
199            }
200        }
201    }
202
203    fn walk_pats_top(&mut self, pats: impl Iterator<Item = PatId>, parent_expr: ExprId) {
204        let guard = self.resolver.update_to_inner_scope(self.db.upcast(), self.def, parent_expr);
205        pats.for_each(|pat| self.walk_pat(pat));
206        self.resolver.reset_to_guard(guard);
207    }
208
209    fn walk_pat(&mut self, current: PatId) {
210        let pat = &self.body.pats[current];
211
212        if self.inside_union_destructure {
213            match pat {
214                Pat::Tuple { .. }
215                | Pat::Record { .. }
216                | Pat::Range { .. }
217                | Pat::Slice { .. }
218                | Pat::Path(..)
219                | Pat::Lit(..)
220                | Pat::Bind { .. }
221                | Pat::TupleStruct { .. }
222                | Pat::Ref { .. }
223                | Pat::Box { .. }
224                | Pat::Expr(..)
225                | Pat::ConstBlock(..) => {
226                    self.on_unsafe_op(current.into(), UnsafetyReason::UnionField)
227                }
228                // `Or` only wraps other patterns, and `Missing`/`Wild` do not constitute a read.
229                Pat::Missing | Pat::Wild | Pat::Or(_) => {}
230            }
231        }
232
233        match pat {
234            Pat::Record { .. } => {
235                if let Some((AdtId::UnionId(_), _)) = self.infer[current].as_adt() {
236                    let old_inside_union_destructure =
237                        mem::replace(&mut self.inside_union_destructure, true);
238                    self.body.walk_pats_shallow(current, |pat| self.walk_pat(pat));
239                    self.inside_union_destructure = old_inside_union_destructure;
240                    return;
241                }
242            }
243            Pat::Path(path) => self.mark_unsafe_path(current.into(), path),
244            &Pat::ConstBlock(expr) => {
245                let old_inside_assignment = mem::replace(&mut self.inside_assignment, false);
246                self.walk_expr(expr);
247                self.inside_assignment = old_inside_assignment;
248            }
249            &Pat::Expr(expr) => self.walk_expr(expr),
250            _ => {}
251        }
252
253        self.body.walk_pats_shallow(current, |pat| self.walk_pat(pat));
254    }
255
256    fn walk_expr(&mut self, current: ExprId) {
257        let expr = &self.body.exprs[current];
258        let inside_assignment = mem::replace(&mut self.inside_assignment, false);
259        match expr {
260            &Expr::Call { callee, .. } => {
261                let callee = &self.infer[callee];
262                if let Some(func) = callee.as_fn_def(self.db) {
263                    self.check_call(current, func);
264                }
265                if let TyKind::Function(fn_ptr) = callee.kind(Interner) {
266                    if fn_ptr.sig.safety == chalk_ir::Safety::Unsafe {
267                        self.on_unsafe_op(current.into(), UnsafetyReason::UnsafeFnCall);
268                    }
269                }
270            }
271            Expr::Path(path) => {
272                let guard =
273                    self.resolver.update_to_inner_scope(self.db.upcast(), self.def, current);
274                self.mark_unsafe_path(current.into(), path);
275                self.resolver.reset_to_guard(guard);
276            }
277            Expr::Ref { expr, rawness: Rawness::RawPtr, mutability: _ } => {
278                match self.body.exprs[*expr] {
279                    // Do not report unsafe for `addr_of[_mut]!(EXTERN_OR_MUT_STATIC)`,
280                    // see https://github.com/rust-lang/rust/pull/125834.
281                    Expr::Path(_) => return,
282                    // https://github.com/rust-lang/rust/pull/129248
283                    // Taking a raw ref to a deref place expr is always safe.
284                    Expr::UnaryOp { expr, op: UnaryOp::Deref } => {
285                        self.body
286                            .walk_child_exprs_without_pats(expr, |child| self.walk_expr(child));
287
288                        return;
289                    }
290                    _ => (),
291                }
292            }
293            Expr::MethodCall { .. } => {
294                if let Some((func, _)) = self.infer.method_resolution(current) {
295                    self.check_call(current, func);
296                }
297            }
298            Expr::UnaryOp { expr, op: UnaryOp::Deref } => {
299                if let TyKind::Raw(..) = &self.infer[*expr].kind(Interner) {
300                    self.on_unsafe_op(current.into(), UnsafetyReason::RawPtrDeref);
301                }
302            }
303            &Expr::Assignment { target, value: _ } => {
304                let old_inside_assignment = mem::replace(&mut self.inside_assignment, true);
305                self.walk_pats_top(std::iter::once(target), current);
306                self.inside_assignment = old_inside_assignment;
307            }
308            Expr::InlineAsm(_) => self.on_unsafe_op(current.into(), UnsafetyReason::InlineAsm),
309            // rustc allows union assignment to propagate through field accesses and casts.
310            Expr::Cast { .. } => self.inside_assignment = inside_assignment,
311            Expr::Field { .. } => {
312                self.inside_assignment = inside_assignment;
313                if !inside_assignment {
314                    if let Some(Either::Left(FieldId { parent: VariantId::UnionId(_), .. })) =
315                        self.infer.field_resolution(current)
316                    {
317                        self.on_unsafe_op(current.into(), UnsafetyReason::UnionField);
318                    }
319                }
320            }
321            Expr::Unsafe { statements, .. } => {
322                let old_inside_unsafe_block =
323                    mem::replace(&mut self.inside_unsafe_block, InsideUnsafeBlock::Yes);
324                self.walk_pats_top(
325                    statements.iter().filter_map(|statement| match statement {
326                        &Statement::Let { pat, .. } => Some(pat),
327                        _ => None,
328                    }),
329                    current,
330                );
331                self.body.walk_child_exprs_without_pats(current, |child| self.walk_expr(child));
332                self.inside_unsafe_block = old_inside_unsafe_block;
333                return;
334            }
335            Expr::Block { statements, .. } | Expr::Async { statements, .. } => {
336                self.walk_pats_top(
337                    statements.iter().filter_map(|statement| match statement {
338                        &Statement::Let { pat, .. } => Some(pat),
339                        _ => None,
340                    }),
341                    current,
342                );
343            }
344            Expr::Match { arms, .. } => {
345                self.walk_pats_top(arms.iter().map(|arm| arm.pat), current);
346            }
347            &Expr::Let { pat, .. } => {
348                self.walk_pats_top(std::iter::once(pat), current);
349            }
350            Expr::Closure { args, .. } => {
351                self.walk_pats_top(args.iter().copied(), current);
352            }
353            _ => {}
354        }
355
356        self.body.walk_child_exprs_without_pats(current, |child| self.walk_expr(child));
357    }
358
359    fn mark_unsafe_path(&mut self, node: ExprOrPatId, path: &Path) {
360        let hygiene = self.body.expr_or_pat_path_hygiene(node);
361        let value_or_partial =
362            self.resolver.resolve_path_in_value_ns(self.db.upcast(), path, hygiene);
363        if let Some(ResolveValueResult::ValueNs(ValueNs::StaticId(id), _)) = value_or_partial {
364            let static_data = self.db.static_data(id);
365            if static_data.mutable() {
366                self.on_unsafe_op(node, UnsafetyReason::MutableStatic);
367            } else if static_data.is_extern() && !static_data.has_safe_kw() {
368                self.on_unsafe_op(node, UnsafetyReason::ExternStatic);
369            }
370        }
371    }
372}