Skip to main content

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, CallableDefId, DefWithBodyId, ExpressionStoreOwnerId, FieldId, FunctionId, GenericDefId,
9    VariantId,
10    expr_store::{Body, ExpressionStore, path::Path},
11    hir::{
12        AsmOperand, Expr, ExprId, ExprOrPatId, InlineAsmKind, Pat, PatId, Statement, UnaryOp,
13        Unsafe,
14    },
15    resolver::{HasResolver, ResolveValueResult, Resolver, ValueNs},
16    signatures::{FunctionSignature, StaticFlags, StaticSignature},
17    type_ref::Rawness,
18};
19use rustc_type_ir::inherent::IntoKind;
20use span::Edition;
21
22use crate::{
23    InferenceResult, TargetFeatures,
24    db::HirDatabase,
25    next_solver::{CallableIdWrapper, TyKind, abi::Safety},
26    utils::{TargetFeatureIsSafeInTarget, is_fn_unsafe_to_call, target_feature_is_safe_in_target},
27};
28
29#[derive(Debug, Default)]
30pub struct MissingUnsafeResult {
31    pub unsafe_exprs: Vec<(ExprOrPatId, UnsafetyReason)>,
32    /// If `fn_is_unsafe` is false, `unsafe_exprs` are hard errors. If true, they're `unsafe_op_in_unsafe_fn`.
33    pub fn_is_unsafe: bool,
34    pub deprecated_safe_calls: Vec<ExprId>,
35}
36
37pub fn missing_unsafe(db: &dyn HirDatabase, def: DefWithBodyId) -> MissingUnsafeResult {
38    let _p = tracing::info_span!("missing_unsafe").entered();
39
40    let is_unsafe = match def {
41        DefWithBodyId::FunctionId(it) => FunctionSignature::of(db, it).is_unsafe(),
42        DefWithBodyId::StaticId(_) | DefWithBodyId::ConstId(_) | DefWithBodyId::VariantId(_) => {
43            false
44        }
45    };
46
47    let mut res = MissingUnsafeResult { fn_is_unsafe: is_unsafe, ..MissingUnsafeResult::default() };
48    let body = Body::of(db, def);
49    let infer = InferenceResult::of(db, def);
50    let mut callback = |diag| match diag {
51        UnsafeDiagnostic::UnsafeOperation { node, inside_unsafe_block, reason } => {
52            if inside_unsafe_block == InsideUnsafeBlock::No {
53                res.unsafe_exprs.push((node, reason));
54            }
55        }
56        UnsafeDiagnostic::DeprecatedSafe2024 { node, inside_unsafe_block } => {
57            if inside_unsafe_block == InsideUnsafeBlock::No {
58                res.deprecated_safe_calls.push(node)
59            }
60        }
61    };
62    let mut visitor = UnsafeVisitor::new(db, infer, body, def.into(), &mut callback);
63    visitor.walk_expr(body.root_expr());
64
65    if !is_unsafe {
66        // Unsafety in function parameter patterns (that can only be union destructuring)
67        // cannot be inserted into an unsafe block, so even with `unsafe_op_in_unsafe_fn`
68        // it is turned off for unsafe functions.
69        for param in &body.params {
70            visitor.walk_pat(param.formal);
71        }
72    }
73
74    res
75}
76
77#[derive(Debug, Clone, Copy)]
78pub enum UnsafetyReason {
79    UnionField,
80    UnsafeFnCall,
81    InlineAsm,
82    RawPtrDeref,
83    MutableStatic,
84    ExternStatic,
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq)]
88pub enum InsideUnsafeBlock {
89    No,
90    Yes,
91}
92
93#[derive(Debug)]
94enum UnsafeDiagnostic {
95    UnsafeOperation {
96        node: ExprOrPatId,
97        inside_unsafe_block: InsideUnsafeBlock,
98        reason: UnsafetyReason,
99    },
100    /// A lint.
101    DeprecatedSafe2024 { node: ExprId, inside_unsafe_block: InsideUnsafeBlock },
102}
103
104pub fn unsafe_operations(
105    db: &dyn HirDatabase,
106    infer: &InferenceResult<'_>,
107    def: ExpressionStoreOwnerId,
108    body: &ExpressionStore,
109    current: ExprId,
110    callback: &mut dyn FnMut(ExprOrPatId, InsideUnsafeBlock),
111) {
112    let mut visitor_callback = |diag| {
113        if let UnsafeDiagnostic::UnsafeOperation { inside_unsafe_block, node, .. } = diag {
114            callback(node, inside_unsafe_block);
115        }
116    };
117    let mut visitor = UnsafeVisitor::new(db, infer, body, def, &mut visitor_callback);
118    _ = visitor.resolver.update_to_inner_scope(db, def, current);
119    visitor.walk_expr(current);
120}
121
122struct UnsafeVisitor<'db> {
123    db: &'db dyn HirDatabase,
124    infer: &'db InferenceResult<'db>,
125    body: &'db ExpressionStore,
126    resolver: Resolver<'db>,
127    def: ExpressionStoreOwnerId,
128    inside_unsafe_block: InsideUnsafeBlock,
129    inside_assignment: bool,
130    inside_union_destructure: bool,
131    callback: &'db mut dyn FnMut(UnsafeDiagnostic),
132    def_target_features: TargetFeatures<'db>,
133    // FIXME: This needs to be the edition of the span of each call.
134    edition: Edition,
135    /// On some targets (WASM), calling safe functions with `#[target_feature]` is always safe, even when
136    /// the target feature is not enabled. This flag encodes that.
137    target_feature_is_safe: TargetFeatureIsSafeInTarget,
138}
139
140impl<'db> UnsafeVisitor<'db> {
141    fn new(
142        db: &'db dyn HirDatabase,
143        infer: &'db InferenceResult<'db>,
144        body: &'db ExpressionStore,
145        def: ExpressionStoreOwnerId,
146        unsafe_expr_cb: &'db mut dyn FnMut(UnsafeDiagnostic),
147    ) -> Self {
148        let resolver = def.resolver(db);
149        let def_target_features = match def {
150            ExpressionStoreOwnerId::Body(DefWithBodyId::FunctionId(func))
151            | ExpressionStoreOwnerId::Signature(GenericDefId::FunctionId(func)) => {
152                TargetFeatures::from_fn(db, func)
153            }
154            _ => TargetFeatures::default(),
155        };
156        let krate = resolver.krate();
157        let edition = krate.data(db).edition;
158        let target_feature_is_safe = match &krate.workspace_data(db).target {
159            Ok(target) => target_feature_is_safe_in_target(target),
160            Err(_) => TargetFeatureIsSafeInTarget::No,
161        };
162        Self {
163            db,
164            infer,
165            body,
166            resolver,
167            def,
168            inside_unsafe_block: InsideUnsafeBlock::No,
169            inside_assignment: false,
170            inside_union_destructure: false,
171            callback: unsafe_expr_cb,
172            def_target_features,
173            edition,
174            target_feature_is_safe,
175        }
176    }
177
178    fn on_unsafe_op(&mut self, node: ExprOrPatId, reason: UnsafetyReason) {
179        (self.callback)(UnsafeDiagnostic::UnsafeOperation {
180            node,
181            inside_unsafe_block: self.inside_unsafe_block,
182            reason,
183        });
184    }
185
186    fn check_call(&mut self, node: ExprId, func: FunctionId) {
187        let unsafety = is_fn_unsafe_to_call(
188            self.db,
189            func,
190            &self.def_target_features,
191            self.edition,
192            self.target_feature_is_safe,
193        );
194        match unsafety {
195            crate::utils::Unsafety::Safe => {}
196            crate::utils::Unsafety::Unsafe => {
197                self.on_unsafe_op(node.into(), UnsafetyReason::UnsafeFnCall)
198            }
199            crate::utils::Unsafety::DeprecatedSafe2024 => {
200                (self.callback)(UnsafeDiagnostic::DeprecatedSafe2024 {
201                    node,
202                    inside_unsafe_block: self.inside_unsafe_block,
203                })
204            }
205        }
206    }
207
208    fn with_inside_unsafe_block<R>(
209        &mut self,
210        inside_unsafe_block: InsideUnsafeBlock,
211        f: impl FnOnce(&mut Self) -> R,
212    ) -> R {
213        let old = mem::replace(&mut self.inside_unsafe_block, inside_unsafe_block);
214        let result = f(self);
215        self.inside_unsafe_block = old;
216        result
217    }
218
219    fn walk_pats_top(&mut self, pats: impl Iterator<Item = PatId>, parent_expr: ExprId) {
220        let guard = self.resolver.update_to_inner_scope(self.db, self.def, parent_expr);
221        pats.for_each(|pat| self.walk_pat(pat));
222        self.resolver.reset_to_guard(guard);
223    }
224
225    fn walk_pat(&mut self, current: PatId) {
226        let pat = &self.body[current];
227
228        if self.inside_union_destructure {
229            match pat {
230                Pat::Tuple { .. }
231                | Pat::Record { .. }
232                | Pat::Range { .. }
233                | Pat::Slice { .. }
234                | Pat::Path(..)
235                | Pat::Lit(..)
236                | Pat::Bind { .. }
237                | Pat::TupleStruct { .. }
238                | Pat::Ref { .. }
239                | Pat::Box { .. }
240                | Pat::Deref { .. }
241                | Pat::Expr(..)
242                | Pat::NotNull => self.on_unsafe_op(current.into(), UnsafetyReason::UnionField),
243                // `Or` only wraps other patterns, and `Missing`/`Wild` do not constitute a read.
244                Pat::Missing | Pat::Rest | Pat::Wild | Pat::Or(_) => {}
245            }
246        }
247
248        match pat {
249            Pat::Record { .. } => {
250                if let Some((AdtId::UnionId(_), _)) = self.infer.pat_ty(current).as_adt() {
251                    let old_inside_union_destructure =
252                        mem::replace(&mut self.inside_union_destructure, true);
253                    self.body.walk_pats_shallow(current, |pat| self.walk_pat(pat));
254                    self.inside_union_destructure = old_inside_union_destructure;
255                    return;
256                }
257            }
258            Pat::Path(path) => self.mark_unsafe_path(current.into(), path),
259            &Pat::Expr(expr) => self.walk_expr(expr),
260            _ => {}
261        }
262
263        self.body.walk_pats_shallow(current, |pat| self.walk_pat(pat));
264    }
265
266    fn walk_expr(&mut self, current: ExprId) {
267        let expr = &self.body[current];
268        let inside_assignment = mem::replace(&mut self.inside_assignment, false);
269        match expr {
270            &Expr::Call { callee, .. } => {
271                let callee = self.infer.expr_ty(callee);
272                if let TyKind::FnDef(CallableIdWrapper(CallableDefId::FunctionId(func)), _) =
273                    callee.kind()
274                {
275                    self.check_call(current, func);
276                }
277                if let TyKind::FnPtr(_, hdr) = callee.kind()
278                    && hdr.safety() == Safety::Unsafe
279                {
280                    self.on_unsafe_op(current.into(), UnsafetyReason::UnsafeFnCall);
281                }
282            }
283            Expr::Path(path) => {
284                let guard = self.resolver.update_to_inner_scope(self.db, self.def, current);
285                self.mark_unsafe_path(current.into(), path);
286                self.resolver.reset_to_guard(guard);
287            }
288            Expr::Ref { expr, rawness: Rawness::RawPtr, mutability: _ } => {
289                match self.body[*expr] {
290                    // Do not report unsafe for `addr_of[_mut]!(EXTERN_OR_MUT_STATIC)`,
291                    // see https://github.com/rust-lang/rust/pull/125834.
292                    Expr::Path(_) => return,
293                    // https://github.com/rust-lang/rust/pull/129248
294                    // Taking a raw ref to a deref place expr is always safe.
295                    Expr::UnaryOp { expr, op: UnaryOp::Deref } => {
296                        self.walk_expr(expr);
297                        return;
298                    }
299                    _ => (),
300                }
301
302                let mut peeled = *expr;
303                while let Expr::Field { expr: lhs, .. } = &self.body[peeled] {
304                    if let Some(Either::Left(FieldId { parent: VariantId::UnionId(_), .. })) =
305                        self.infer.field_resolution(peeled)
306                    {
307                        peeled = *lhs;
308                    } else {
309                        break;
310                    }
311                }
312
313                // Walk the peeled expression (the LHS of the union field chain)
314                self.walk_expr(peeled);
315                // Return so we don't recurse directly onto the union field access(es)
316                return;
317            }
318            Expr::MethodCall { .. } => {
319                if let Some((func, _)) = self.infer.method_resolution(current) {
320                    self.check_call(current, func);
321                }
322            }
323            Expr::UnaryOp { expr, op: UnaryOp::Deref } => {
324                if let TyKind::RawPtr(..) = self.infer.expr_ty(*expr).kind() {
325                    self.on_unsafe_op(current.into(), UnsafetyReason::RawPtrDeref);
326                }
327            }
328            &Expr::Assignment { target, value: _ } => {
329                let old_inside_assignment = mem::replace(&mut self.inside_assignment, true);
330                self.walk_pats_top(std::iter::once(target), current);
331                self.inside_assignment = old_inside_assignment;
332            }
333            Expr::InlineAsm(asm) => {
334                if asm.kind == InlineAsmKind::Asm {
335                    // `naked_asm!()` requires `unsafe` on the attribute (`#[unsafe(naked)]`),
336                    // and `global_asm!()` doesn't require it at all.
337                    self.on_unsafe_op(current.into(), UnsafetyReason::InlineAsm);
338                }
339
340                asm.operands.iter().for_each(|(_, op)| match op {
341                    AsmOperand::In { expr, .. }
342                    | AsmOperand::Out { expr: Some(expr), .. }
343                    | AsmOperand::InOut { expr, .. }
344                    | AsmOperand::Const(expr) => self.walk_expr(*expr),
345                    AsmOperand::SplitInOut { in_expr, out_expr, .. } => {
346                        self.walk_expr(*in_expr);
347                        if let Some(out_expr) = out_expr {
348                            self.walk_expr(*out_expr);
349                        }
350                    }
351                    AsmOperand::Out { expr: None, .. } | AsmOperand::Sym(_) => (),
352                    AsmOperand::Label(expr) => {
353                        // Inline asm labels are considered safe even when inside unsafe blocks.
354                        self.with_inside_unsafe_block(InsideUnsafeBlock::No, |this| {
355                            this.walk_expr(*expr)
356                        });
357                    }
358                });
359                return;
360            }
361            // rustc allows union assignment to propagate through field accesses and casts.
362            Expr::Cast { .. } => self.inside_assignment = inside_assignment,
363            Expr::Field { .. } => {
364                self.inside_assignment = inside_assignment;
365                if !inside_assignment
366                    && let Some(Either::Left(FieldId { parent: VariantId::UnionId(_), .. })) =
367                        self.infer.field_resolution(current)
368                {
369                    self.on_unsafe_op(current.into(), UnsafetyReason::UnionField);
370                }
371            }
372            Expr::Block { unsafe_: Unsafe::Yes, statements, .. } => {
373                self.with_inside_unsafe_block(InsideUnsafeBlock::Yes, |this| {
374                    this.walk_pats_top(
375                        statements.iter().filter_map(|statement| match statement {
376                            &Statement::Let { pat, .. } => Some(pat),
377                            _ => None,
378                        }),
379                        current,
380                    );
381                    this.body.walk_child_exprs_without_pats(current, |child| this.walk_expr(child));
382                });
383                return;
384            }
385            Expr::Block { unsafe_: Unsafe::No, statements, .. } => {
386                self.walk_pats_top(
387                    statements.iter().filter_map(|statement| match statement {
388                        &Statement::Let { pat, .. } => Some(pat),
389                        _ => None,
390                    }),
391                    current,
392                );
393            }
394            Expr::Match { arms, .. } => {
395                self.walk_pats_top(arms.iter().map(|arm| arm.pat), current);
396            }
397            &Expr::Let { pat, .. } => {
398                self.walk_pats_top(std::iter::once(pat), current);
399            }
400            Expr::Closure { args, .. } => {
401                self.walk_pats_top(args.iter().copied(), current);
402            }
403            _ => {}
404        }
405
406        self.body.walk_child_exprs_without_pats(current, |child| self.walk_expr(child));
407    }
408
409    fn mark_unsafe_path(&mut self, node: ExprOrPatId, path: &Path) {
410        let hygiene = self.body.expr_or_pat_path_hygiene(node);
411        let value_or_partial = self.resolver.resolve_path_in_value_ns(self.db, path, hygiene);
412        if let Some(ResolveValueResult::ValueNs(ValueNs::StaticId(id))) = value_or_partial {
413            let static_data = StaticSignature::of(self.db, id);
414            if static_data.flags.contains(StaticFlags::MUTABLE) {
415                self.on_unsafe_op(node, UnsafetyReason::MutableStatic);
416            } else if static_data.flags.contains(StaticFlags::EXTERN)
417                && !static_data.flags.contains(StaticFlags::EXPLICIT_SAFE)
418            {
419                self.on_unsafe_op(node, UnsafetyReason::ExternStatic);
420            }
421        }
422    }
423}