diff options
Diffstat (limited to 'crates/hir_ty/src/diagnostics/unsafe_check.rs')
-rw-r--r-- | crates/hir_ty/src/diagnostics/unsafe_check.rs | 205 |
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 | |||
4 | use std::sync::Arc; | ||
5 | |||
6 | use hir_def::{ | ||
7 | body::Body, | ||
8 | expr::{Expr, ExprId, UnaryOp}, | ||
9 | resolver::{resolver_for_expr, ResolveValueResult, ValueNs}, | ||
10 | DefWithBodyId, | ||
11 | }; | ||
12 | use hir_expand::diagnostics::DiagnosticSink; | ||
13 | |||
14 | use crate::{ | ||
15 | db::HirDatabase, diagnostics::MissingUnsafe, lower::CallableDefId, ApplicationTy, | ||
16 | InferenceResult, Ty, TypeCtor, | ||
17 | }; | ||
18 | |||
19 | pub(super) struct UnsafeValidator<'a, 'b: 'a> { | ||
20 | owner: DefWithBodyId, | ||
21 | infer: Arc<InferenceResult>, | ||
22 | sink: &'a mut DiagnosticSink<'b>, | ||
23 | } | ||
24 | |||
25 | impl<'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 | |||
62 | pub struct UnsafeExpr { | ||
63 | pub expr: ExprId, | ||
64 | pub inside_unsafe_block: bool, | ||
65 | } | ||
66 | |||
67 | pub 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 | |||
79 | fn 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)] | ||
137 | mod tests { | ||
138 | use crate::diagnostics::tests::check_diagnostics; | ||
139 | |||
140 | #[test] | ||
141 | fn missing_unsafe_diagnostic_with_raw_ptr() { | ||
142 | check_diagnostics( | ||
143 | r#" | ||
144 | fn 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#" | ||
157 | struct HasUnsafe; | ||
158 | |||
159 | impl HasUnsafe { | ||
160 | unsafe fn unsafe_fn(&self) { | ||
161 | let x = &5 as *const usize; | ||
162 | let y = *x; | ||
163 | } | ||
164 | } | ||
165 | |||
166 | unsafe fn unsafe_fn() { | ||
167 | let x = &5 as *const usize; | ||
168 | let y = *x; | ||
169 | } | ||
170 | |||
171 | fn 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#" | ||
189 | struct Ty { | ||
190 | a: u8, | ||
191 | } | ||
192 | |||
193 | static mut static_mut: Ty = Ty { a: 0 }; | ||
194 | |||
195 | fn 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 | } | ||