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
//! Provides validations for unsafe code. Currently checks if unsafe functions are missing
//! unsafe blocks.

use hir_def::{
    body::Body,
    hir::{Expr, ExprId, ExprOrPatId, Pat, UnaryOp},
    resolver::{resolver_for_expr, ResolveValueResult, Resolver, ValueNs},
    type_ref::Rawness,
    DefWithBodyId,
};

use crate::{
    db::HirDatabase, utils::is_fn_unsafe_to_call, InferenceResult, Interner, TyExt, TyKind,
};

/// Returns `(unsafe_exprs, fn_is_unsafe)`.
///
/// If `fn_is_unsafe` is false, `unsafe_exprs` are hard errors. If true, they're `unsafe_op_in_unsafe_fn`.
pub fn missing_unsafe(db: &dyn HirDatabase, def: DefWithBodyId) -> (Vec<ExprOrPatId>, bool) {
    let _p = tracing::info_span!("missing_unsafe").entered();

    let mut res = Vec::new();
    let is_unsafe = match def {
        DefWithBodyId::FunctionId(it) => db.function_data(it).is_unsafe(),
        DefWithBodyId::StaticId(_)
        | DefWithBodyId::ConstId(_)
        | DefWithBodyId::VariantId(_)
        | DefWithBodyId::InTypeConstId(_) => false,
    };

    let body = db.body(def);
    let infer = db.infer(def);
    unsafe_expressions(db, &infer, def, &body, body.body_expr, &mut |expr| {
        if !expr.inside_unsafe_block {
            res.push(expr.node);
        }
    });

    (res, is_unsafe)
}

pub struct UnsafeExpr {
    pub node: ExprOrPatId,
    pub inside_unsafe_block: bool,
}

// FIXME: Move this out, its not a diagnostic only thing anymore, and handle unsafe pattern accesses as well
pub fn unsafe_expressions(
    db: &dyn HirDatabase,
    infer: &InferenceResult,
    def: DefWithBodyId,
    body: &Body,
    current: ExprId,
    unsafe_expr_cb: &mut dyn FnMut(UnsafeExpr),
) {
    walk_unsafe(
        db,
        infer,
        body,
        &mut resolver_for_expr(db.upcast(), def, current),
        def,
        current,
        false,
        unsafe_expr_cb,
    )
}

fn walk_unsafe(
    db: &dyn HirDatabase,
    infer: &InferenceResult,
    body: &Body,
    resolver: &mut Resolver,
    def: DefWithBodyId,
    current: ExprId,
    inside_unsafe_block: bool,
    unsafe_expr_cb: &mut dyn FnMut(UnsafeExpr),
) {
    let mut mark_unsafe_path = |path, node| {
        let g = resolver.update_to_inner_scope(db.upcast(), def, current);
        let hygiene = body.expr_or_pat_path_hygiene(node);
        let value_or_partial = resolver.resolve_path_in_value_ns(db.upcast(), path, hygiene);
        if let Some(ResolveValueResult::ValueNs(ValueNs::StaticId(id), _)) = value_or_partial {
            let static_data = db.static_data(id);
            if static_data.mutable || (static_data.is_extern && !static_data.has_safe_kw) {
                unsafe_expr_cb(UnsafeExpr { node, inside_unsafe_block });
            }
        }
        resolver.reset_to_guard(g);
    };

    let expr = &body.exprs[current];
    match expr {
        &Expr::Call { callee, .. } => {
            if let Some(func) = infer[callee].as_fn_def(db) {
                if is_fn_unsafe_to_call(db, func) {
                    unsafe_expr_cb(UnsafeExpr { node: current.into(), inside_unsafe_block });
                }
            }
        }
        Expr::Path(path) => mark_unsafe_path(path, current.into()),
        Expr::Ref { expr, rawness: Rawness::RawPtr, mutability: _ } => {
            if let Expr::Path(_) = body.exprs[*expr] {
                // Do not report unsafe for `addr_of[_mut]!(EXTERN_OR_MUT_STATIC)`,
                // see https://github.com/rust-lang/rust/pull/125834.
                return;
            }
        }
        Expr::MethodCall { .. } => {
            if infer
                .method_resolution(current)
                .map(|(func, _)| is_fn_unsafe_to_call(db, func))
                .unwrap_or(false)
            {
                unsafe_expr_cb(UnsafeExpr { node: current.into(), inside_unsafe_block });
            }
        }
        Expr::UnaryOp { expr, op: UnaryOp::Deref } => {
            if let TyKind::Raw(..) = &infer[*expr].kind(Interner) {
                unsafe_expr_cb(UnsafeExpr { node: current.into(), inside_unsafe_block });
            }
        }
        Expr::Unsafe { .. } => {
            return body.walk_child_exprs(current, |child| {
                walk_unsafe(db, infer, body, resolver, def, child, true, unsafe_expr_cb);
            });
        }
        &Expr::Assignment { target, value: _ } => {
            body.walk_pats(target, &mut |pat| {
                if let Pat::Path(path) = &body[pat] {
                    mark_unsafe_path(path, pat.into());
                }
            });
        }
        _ => {}
    }

    body.walk_child_exprs(current, |child| {
        walk_unsafe(db, infer, body, resolver, def, child, inside_unsafe_block, unsafe_expr_cb);
    });
}