aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_hir_ty/src/diagnostics/unsafe_check.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_hir_ty/src/diagnostics/unsafe_check.rs')
-rw-r--r--crates/ra_hir_ty/src/diagnostics/unsafe_check.rs123
1 files changed, 123 insertions, 0 deletions
diff --git a/crates/ra_hir_ty/src/diagnostics/unsafe_check.rs b/crates/ra_hir_ty/src/diagnostics/unsafe_check.rs
new file mode 100644
index 000000000..b8ff95ee1
--- /dev/null
+++ b/crates/ra_hir_ty/src/diagnostics/unsafe_check.rs
@@ -0,0 +1,123 @@
1//! Provides validations for unsafe code. Currently checks if unsafe functions are missing
2//! unsafe blocks.
3
4use std::sync::Arc;
5
6use hir_def::{
7 body::Body,
8 expr::{Expr, ExprId, UnaryOp},
9 DefWithBodyId,
10};
11use hir_expand::diagnostics::DiagnosticSink;
12
13use crate::{
14 db::HirDatabase, diagnostics::MissingUnsafe, lower::CallableDef, ApplicationTy,
15 InferenceResult, Ty, TypeCtor,
16};
17
18pub(super) struct UnsafeValidator<'a, 'b: 'a> {
19 owner: DefWithBodyId,
20 infer: Arc<InferenceResult>,
21 sink: &'a mut DiagnosticSink<'b>,
22}
23
24impl<'a, 'b> UnsafeValidator<'a, 'b> {
25 pub(super) fn new(
26 owner: DefWithBodyId,
27 infer: Arc<InferenceResult>,
28 sink: &'a mut DiagnosticSink<'b>,
29 ) -> UnsafeValidator<'a, 'b> {
30 UnsafeValidator { owner, infer, sink }
31 }
32
33 pub(super) fn validate_body(&mut self, db: &dyn HirDatabase) {
34 let def = self.owner.into();
35 let unsafe_expressions = unsafe_expressions(db, self.infer.as_ref(), def);
36 let is_unsafe = match self.owner {
37 DefWithBodyId::FunctionId(it) => db.function_data(it).is_unsafe,
38 DefWithBodyId::StaticId(_) | DefWithBodyId::ConstId(_) => false,
39 };
40 if is_unsafe
41 || unsafe_expressions
42 .iter()
43 .filter(|unsafe_expr| !unsafe_expr.inside_unsafe_block)
44 .count()
45 == 0
46 {
47 return;
48 }
49
50 let (_, body_source) = db.body_with_source_map(def);
51 for unsafe_expr in unsafe_expressions {
52 if !unsafe_expr.inside_unsafe_block {
53 if let Ok(in_file) = body_source.as_ref().expr_syntax(unsafe_expr.expr) {
54 self.sink.push(MissingUnsafe { file: in_file.file_id, expr: in_file.value })
55 }
56 }
57 }
58 }
59}
60
61pub struct UnsafeExpr {
62 pub expr: ExprId,
63 pub inside_unsafe_block: bool,
64}
65
66pub fn unsafe_expressions(
67 db: &dyn HirDatabase,
68 infer: &InferenceResult,
69 def: DefWithBodyId,
70) -> Vec<UnsafeExpr> {
71 let mut unsafe_exprs = vec![];
72 let body = db.body(def);
73 walk_unsafe(&mut unsafe_exprs, db, infer, &body, body.body_expr, false);
74
75 unsafe_exprs
76}
77
78fn walk_unsafe(
79 unsafe_exprs: &mut Vec<UnsafeExpr>,
80 db: &dyn HirDatabase,
81 infer: &InferenceResult,
82 body: &Body,
83 current: ExprId,
84 inside_unsafe_block: bool,
85) {
86 let expr = &body.exprs[current];
87 match expr {
88 Expr::Call { callee, .. } => {
89 let ty = &infer[*callee];
90 if let &Ty::Apply(ApplicationTy {
91 ctor: TypeCtor::FnDef(CallableDef::FunctionId(func)),
92 ..
93 }) = ty
94 {
95 if db.function_data(func).is_unsafe {
96 unsafe_exprs.push(UnsafeExpr { expr: current, inside_unsafe_block });
97 }
98 }
99 }
100 Expr::MethodCall { .. } => {
101 if infer
102 .method_resolution(current)
103 .map(|func| db.function_data(func).is_unsafe)
104 .unwrap_or(false)
105 {
106 unsafe_exprs.push(UnsafeExpr { expr: current, inside_unsafe_block });
107 }
108 }
109 Expr::UnaryOp { expr, op: UnaryOp::Deref } => {
110 if let Ty::Apply(ApplicationTy { ctor: TypeCtor::RawPtr(..), .. }) = &infer[*expr] {
111 unsafe_exprs.push(UnsafeExpr { expr: current, inside_unsafe_block });
112 }
113 }
114 Expr::Unsafe { body: child } => {
115 return walk_unsafe(unsafe_exprs, db, infer, body, *child, true);
116 }
117 _ => {}
118 }
119
120 expr.walk_child_exprs(|child| {
121 walk_unsafe(unsafe_exprs, db, infer, body, child, inside_unsafe_block);
122 });
123}