]> git.lizzy.rs Git - rust.git/blob - crates/hir-ty/src/diagnostics/unsafe_check.rs
feat: Handle operators like their trait functions in the IDE
[rust.git] / crates / hir-ty / src / diagnostics / unsafe_check.rs
1 //! Provides validations for unsafe code. Currently checks if unsafe functions are missing
2 //! unsafe blocks.
3
4 use hir_def::{
5     body::Body,
6     expr::{Expr, ExprId, UnaryOp},
7     resolver::{resolver_for_expr, ResolveValueResult, ValueNs},
8     DefWithBodyId,
9 };
10
11 use crate::{
12     db::HirDatabase, utils::is_fn_unsafe_to_call, InferenceResult, Interner, TyExt, TyKind,
13 };
14
15 pub fn missing_unsafe(db: &dyn HirDatabase, def: DefWithBodyId) -> Vec<ExprId> {
16     let infer = db.infer(def);
17     let mut res = Vec::new();
18
19     let is_unsafe = match def {
20         DefWithBodyId::FunctionId(it) => db.function_data(it).has_unsafe_kw(),
21         DefWithBodyId::StaticId(_) | DefWithBodyId::ConstId(_) => false,
22     };
23     if is_unsafe {
24         return res;
25     }
26
27     let body = db.body(def);
28     unsafe_expressions(db, &infer, def, &body, body.body_expr, &mut |expr| {
29         if !expr.inside_unsafe_block {
30             res.push(expr.expr);
31         }
32     });
33
34     res
35 }
36
37 pub struct UnsafeExpr {
38     pub expr: ExprId,
39     pub inside_unsafe_block: bool,
40 }
41
42 // FIXME: Move this out, its not a diagnostic only thing anymore, and handle unsafe pattern accesses as well
43 pub fn unsafe_expressions(
44     db: &dyn HirDatabase,
45     infer: &InferenceResult,
46     def: DefWithBodyId,
47     body: &Body,
48     current: ExprId,
49     unsafe_expr_cb: &mut dyn FnMut(UnsafeExpr),
50 ) {
51     walk_unsafe(db, infer, def, body, current, false, unsafe_expr_cb)
52 }
53
54 fn walk_unsafe(
55     db: &dyn HirDatabase,
56     infer: &InferenceResult,
57     def: DefWithBodyId,
58     body: &Body,
59     current: ExprId,
60     inside_unsafe_block: bool,
61     unsafe_expr_cb: &mut dyn FnMut(UnsafeExpr),
62 ) {
63     let expr = &body.exprs[current];
64     match expr {
65         &Expr::Call { callee, .. } => {
66             if let Some(func) = infer[callee].as_fn_def(db) {
67                 if is_fn_unsafe_to_call(db, func) {
68                     unsafe_expr_cb(UnsafeExpr { expr: current, inside_unsafe_block });
69                 }
70             }
71         }
72         Expr::Path(path) => {
73             let resolver = resolver_for_expr(db.upcast(), def, current);
74             let value_or_partial = resolver.resolve_path_in_value_ns(db.upcast(), path.mod_path());
75             if let Some(ResolveValueResult::ValueNs(ValueNs::StaticId(id))) = value_or_partial {
76                 if db.static_data(id).mutable {
77                     unsafe_expr_cb(UnsafeExpr { expr: current, inside_unsafe_block });
78                 }
79             }
80         }
81         Expr::MethodCall { .. } => {
82             if infer
83                 .method_resolution(current)
84                 .map(|(func, _)| is_fn_unsafe_to_call(db, func))
85                 .unwrap_or(false)
86             {
87                 unsafe_expr_cb(UnsafeExpr { expr: current, inside_unsafe_block });
88             }
89         }
90         Expr::UnaryOp { expr, op: UnaryOp::Deref } => {
91             if let TyKind::Raw(..) = &infer[*expr].kind(Interner) {
92                 unsafe_expr_cb(UnsafeExpr { expr: current, inside_unsafe_block });
93             }
94         }
95         Expr::Unsafe { body: child } => {
96             return walk_unsafe(db, infer, def, body, *child, true, unsafe_expr_cb);
97         }
98         _ => {}
99     }
100
101     expr.walk_child_exprs(|child| {
102         walk_unsafe(db, infer, def, body, child, inside_unsafe_block, unsafe_expr_cb);
103     });
104 }