aboutsummaryrefslogtreecommitdiff
path: root/crates/hir_ty/src/diagnostics/unsafe_check.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/hir_ty/src/diagnostics/unsafe_check.rs')
-rw-r--r--crates/hir_ty/src/diagnostics/unsafe_check.rs205
1 files changed, 205 insertions, 0 deletions
diff --git a/crates/hir_ty/src/diagnostics/unsafe_check.rs b/crates/hir_ty/src/diagnostics/unsafe_check.rs
new file mode 100644
index 000000000..61ffbf5d1
--- /dev/null
+++ b/crates/hir_ty/src/diagnostics/unsafe_check.rs
@@ -0,0 +1,205 @@
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 resolver::{resolver_for_expr, ResolveValueResult, ValueNs},
10 DefWithBodyId,
11};
12use hir_expand::diagnostics::DiagnosticSink;
13
14use crate::{
15 db::HirDatabase, diagnostics::MissingUnsafe, lower::CallableDefId, ApplicationTy,
16 InferenceResult, Ty, TypeCtor,
17};
18
19pub(super) struct UnsafeValidator<'a, 'b: 'a> {
20 owner: DefWithBodyId,
21 infer: Arc<InferenceResult>,
22 sink: &'a mut DiagnosticSink<'b>,
23}
24
25impl<'a, 'b> UnsafeValidator<'a, 'b> {
26 pub(super) fn new(
27 owner: DefWithBodyId,
28 infer: Arc<InferenceResult>,
29 sink: &'a mut DiagnosticSink<'b>,
30 ) -> UnsafeValidator<'a, 'b> {
31 UnsafeValidator { owner, infer, sink }
32 }
33
34 pub(super) fn validate_body(&mut self, db: &dyn HirDatabase) {
35 let def = self.owner.into();
36 let unsafe_expressions = unsafe_expressions(db, self.infer.as_ref(), def);
37 let is_unsafe = match self.owner {
38 DefWithBodyId::FunctionId(it) => db.function_data(it).is_unsafe,
39 DefWithBodyId::StaticId(_) | DefWithBodyId::ConstId(_) => false,
40 };
41 if is_unsafe
42 || unsafe_expressions
43 .iter()
44 .filter(|unsafe_expr| !unsafe_expr.inside_unsafe_block)
45 .count()
46 == 0
47 {
48 return;
49 }
50
51 let (_, body_source) = db.body_with_source_map(def);
52 for unsafe_expr in unsafe_expressions {
53 if !unsafe_expr.inside_unsafe_block {
54 if let Ok(in_file) = body_source.as_ref().expr_syntax(unsafe_expr.expr) {
55 self.sink.push(MissingUnsafe { file: in_file.file_id, expr: in_file.value })
56 }
57 }
58 }
59 }
60}
61
62pub struct UnsafeExpr {
63 pub expr: ExprId,
64 pub inside_unsafe_block: bool,
65}
66
67pub fn unsafe_expressions(
68 db: &dyn HirDatabase,
69 infer: &InferenceResult,
70 def: DefWithBodyId,
71) -> Vec<UnsafeExpr> {
72 let mut unsafe_exprs = vec![];
73 let body = db.body(def);
74 walk_unsafe(&mut unsafe_exprs, db, infer, def, &body, body.body_expr, false);
75
76 unsafe_exprs
77}
78
79fn walk_unsafe(
80 unsafe_exprs: &mut Vec<UnsafeExpr>,
81 db: &dyn HirDatabase,
82 infer: &InferenceResult,
83 def: DefWithBodyId,
84 body: &Body,
85 current: ExprId,
86 inside_unsafe_block: bool,
87) {
88 let expr = &body.exprs[current];
89 match expr {
90 Expr::Call { callee, .. } => {
91 let ty = &infer[*callee];
92 if let &Ty::Apply(ApplicationTy {
93 ctor: TypeCtor::FnDef(CallableDefId::FunctionId(func)),
94 ..
95 }) = ty
96 {
97 if db.function_data(func).is_unsafe {
98 unsafe_exprs.push(UnsafeExpr { expr: current, inside_unsafe_block });
99 }
100 }
101 }
102 Expr::Path(path) => {
103 let resolver = resolver_for_expr(db.upcast(), def, current);
104 let value_or_partial = resolver.resolve_path_in_value_ns(db.upcast(), path.mod_path());
105 if let Some(ResolveValueResult::ValueNs(ValueNs::StaticId(id))) = value_or_partial {
106 if db.static_data(id).mutable {
107 unsafe_exprs.push(UnsafeExpr { expr: current, inside_unsafe_block });
108 }
109 }
110 }
111 Expr::MethodCall { .. } => {
112 if infer
113 .method_resolution(current)
114 .map(|func| db.function_data(func).is_unsafe)
115 .unwrap_or(false)
116 {
117 unsafe_exprs.push(UnsafeExpr { expr: current, inside_unsafe_block });
118 }
119 }
120 Expr::UnaryOp { expr, op: UnaryOp::Deref } => {
121 if let Ty::Apply(ApplicationTy { ctor: TypeCtor::RawPtr(..), .. }) = &infer[*expr] {
122 unsafe_exprs.push(UnsafeExpr { expr: current, inside_unsafe_block });
123 }
124 }
125 Expr::Unsafe { body: child } => {
126 return walk_unsafe(unsafe_exprs, db, infer, def, body, *child, true);
127 }
128 _ => {}
129 }
130
131 expr.walk_child_exprs(|child| {
132 walk_unsafe(unsafe_exprs, db, infer, def, body, child, inside_unsafe_block);
133 });
134}
135
136#[cfg(test)]
137mod tests {
138 use crate::diagnostics::tests::check_diagnostics;
139
140 #[test]
141 fn missing_unsafe_diagnostic_with_raw_ptr() {
142 check_diagnostics(
143 r#"
144fn main() {
145 let x = &5 as *const usize;
146 unsafe { let y = *x; }
147 let z = *x;
148} //^^ This operation is unsafe and requires an unsafe function or block
149"#,
150 )
151 }
152
153 #[test]
154 fn missing_unsafe_diagnostic_with_unsafe_call() {
155 check_diagnostics(
156 r#"
157struct HasUnsafe;
158
159impl HasUnsafe {
160 unsafe fn unsafe_fn(&self) {
161 let x = &5 as *const usize;
162 let y = *x;
163 }
164}
165
166unsafe fn unsafe_fn() {
167 let x = &5 as *const usize;
168 let y = *x;
169}
170
171fn main() {
172 unsafe_fn();
173 //^^^^^^^^^^^ This operation is unsafe and requires an unsafe function or block
174 HasUnsafe.unsafe_fn();
175 //^^^^^^^^^^^^^^^^^^^^^ This operation is unsafe and requires an unsafe function or block
176 unsafe {
177 unsafe_fn();
178 HasUnsafe.unsafe_fn();
179 }
180}
181"#,
182 );
183 }
184
185 #[test]
186 fn missing_unsafe_diagnostic_with_static_mut() {
187 check_diagnostics(
188 r#"
189struct Ty {
190 a: u8,
191}
192
193static mut static_mut: Ty = Ty { a: 0 };
194
195fn main() {
196 let x = static_mut.a;
197 //^^^^^^^^^^ This operation is unsafe and requires an unsafe function or block
198 unsafe {
199 let x = static_mut.a;
200 }
201}
202"#,
203 );
204 }
205}