1use std::mem;
5
6use either::Either;
7use hir_def::{
8 AdtId, DefWithBodyId, FieldId, FunctionId, VariantId,
9 expr_store::Body,
10 hir::{Expr, ExprId, ExprOrPatId, Pat, PatId, Statement, UnaryOp},
11 path::Path,
12 resolver::{HasResolver, ResolveValueResult, Resolver, ValueNs},
13 type_ref::Rawness,
14};
15use span::Edition;
16
17use crate::{
18 InferenceResult, Interner, TargetFeatures, TyExt, TyKind, db::HirDatabase,
19 utils::is_fn_unsafe_to_call,
20};
21
22#[derive(Debug, Default)]
23pub struct MissingUnsafeResult {
24 pub unsafe_exprs: Vec<(ExprOrPatId, UnsafetyReason)>,
25 pub fn_is_unsafe: bool,
27 pub deprecated_safe_calls: Vec<ExprId>,
28}
29
30pub fn missing_unsafe(db: &dyn HirDatabase, def: DefWithBodyId) -> MissingUnsafeResult {
31 let _p = tracing::info_span!("missing_unsafe").entered();
32
33 let is_unsafe = match def {
34 DefWithBodyId::FunctionId(it) => db.function_data(it).is_unsafe(),
35 DefWithBodyId::StaticId(_)
36 | DefWithBodyId::ConstId(_)
37 | DefWithBodyId::VariantId(_)
38 | DefWithBodyId::InTypeConstId(_) => false,
39 };
40
41 let mut res = MissingUnsafeResult { fn_is_unsafe: is_unsafe, ..MissingUnsafeResult::default() };
42 let body = db.body(def);
43 let infer = db.infer(def);
44 let mut callback = |diag| match diag {
45 UnsafeDiagnostic::UnsafeOperation { node, inside_unsafe_block, reason } => {
46 if inside_unsafe_block == InsideUnsafeBlock::No {
47 res.unsafe_exprs.push((node, reason));
48 }
49 }
50 UnsafeDiagnostic::DeprecatedSafe2024 { node, inside_unsafe_block } => {
51 if inside_unsafe_block == InsideUnsafeBlock::No {
52 res.deprecated_safe_calls.push(node)
53 }
54 }
55 };
56 let mut visitor = UnsafeVisitor::new(db, &infer, &body, def, &mut callback);
57 visitor.walk_expr(body.body_expr);
58
59 if !is_unsafe {
60 for ¶m in &body.params {
64 visitor.walk_pat(param);
65 }
66 }
67
68 res
69}
70
71#[derive(Debug, Clone, Copy)]
72pub enum UnsafetyReason {
73 UnionField,
74 UnsafeFnCall,
75 InlineAsm,
76 RawPtrDeref,
77 MutableStatic,
78 ExternStatic,
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82pub enum InsideUnsafeBlock {
83 No,
84 Yes,
85}
86
87#[derive(Debug)]
88enum UnsafeDiagnostic {
89 UnsafeOperation {
90 node: ExprOrPatId,
91 inside_unsafe_block: InsideUnsafeBlock,
92 reason: UnsafetyReason,
93 },
94 DeprecatedSafe2024 { node: ExprId, inside_unsafe_block: InsideUnsafeBlock },
96}
97
98pub fn unsafe_operations_for_body(
99 db: &dyn HirDatabase,
100 infer: &InferenceResult,
101 def: DefWithBodyId,
102 body: &Body,
103 callback: &mut dyn FnMut(ExprOrPatId),
104) {
105 let mut visitor_callback = |diag| {
106 if let UnsafeDiagnostic::UnsafeOperation { node, .. } = diag {
107 callback(node);
108 }
109 };
110 let mut visitor = UnsafeVisitor::new(db, infer, body, def, &mut visitor_callback);
111 visitor.walk_expr(body.body_expr);
112 for ¶m in &body.params {
113 visitor.walk_pat(param);
114 }
115}
116
117pub fn unsafe_operations(
118 db: &dyn HirDatabase,
119 infer: &InferenceResult,
120 def: DefWithBodyId,
121 body: &Body,
122 current: ExprId,
123 callback: &mut dyn FnMut(InsideUnsafeBlock),
124) {
125 let mut visitor_callback = |diag| {
126 if let UnsafeDiagnostic::UnsafeOperation { inside_unsafe_block, .. } = diag {
127 callback(inside_unsafe_block);
128 }
129 };
130 let mut visitor = UnsafeVisitor::new(db, infer, body, def, &mut visitor_callback);
131 _ = visitor.resolver.update_to_inner_scope(db.upcast(), def, current);
132 visitor.walk_expr(current);
133}
134
135struct UnsafeVisitor<'a> {
136 db: &'a dyn HirDatabase,
137 infer: &'a InferenceResult,
138 body: &'a Body,
139 resolver: Resolver,
140 def: DefWithBodyId,
141 inside_unsafe_block: InsideUnsafeBlock,
142 inside_assignment: bool,
143 inside_union_destructure: bool,
144 callback: &'a mut dyn FnMut(UnsafeDiagnostic),
145 def_target_features: TargetFeatures,
146 edition: Edition,
148}
149
150impl<'a> UnsafeVisitor<'a> {
151 fn new(
152 db: &'a dyn HirDatabase,
153 infer: &'a InferenceResult,
154 body: &'a Body,
155 def: DefWithBodyId,
156 unsafe_expr_cb: &'a mut dyn FnMut(UnsafeDiagnostic),
157 ) -> Self {
158 let resolver = def.resolver(db.upcast());
159 let def_target_features = match def {
160 DefWithBodyId::FunctionId(func) => TargetFeatures::from_attrs(&db.attrs(func.into())),
161 _ => TargetFeatures::default(),
162 };
163 let edition = resolver.module().krate().data(db).edition;
164 Self {
165 db,
166 infer,
167 body,
168 resolver,
169 def,
170 inside_unsafe_block: InsideUnsafeBlock::No,
171 inside_assignment: false,
172 inside_union_destructure: false,
173 callback: unsafe_expr_cb,
174 def_target_features,
175 edition,
176 }
177 }
178
179 fn on_unsafe_op(&mut self, node: ExprOrPatId, reason: UnsafetyReason) {
180 (self.callback)(UnsafeDiagnostic::UnsafeOperation {
181 node,
182 inside_unsafe_block: self.inside_unsafe_block,
183 reason,
184 });
185 }
186
187 fn check_call(&mut self, node: ExprId, func: FunctionId) {
188 let unsafety = is_fn_unsafe_to_call(self.db, func, &self.def_target_features, self.edition);
189 match unsafety {
190 crate::utils::Unsafety::Safe => {}
191 crate::utils::Unsafety::Unsafe => {
192 self.on_unsafe_op(node.into(), UnsafetyReason::UnsafeFnCall)
193 }
194 crate::utils::Unsafety::DeprecatedSafe2024 => {
195 (self.callback)(UnsafeDiagnostic::DeprecatedSafe2024 {
196 node,
197 inside_unsafe_block: self.inside_unsafe_block,
198 })
199 }
200 }
201 }
202
203 fn walk_pats_top(&mut self, pats: impl Iterator<Item = PatId>, parent_expr: ExprId) {
204 let guard = self.resolver.update_to_inner_scope(self.db.upcast(), self.def, parent_expr);
205 pats.for_each(|pat| self.walk_pat(pat));
206 self.resolver.reset_to_guard(guard);
207 }
208
209 fn walk_pat(&mut self, current: PatId) {
210 let pat = &self.body.pats[current];
211
212 if self.inside_union_destructure {
213 match pat {
214 Pat::Tuple { .. }
215 | Pat::Record { .. }
216 | Pat::Range { .. }
217 | Pat::Slice { .. }
218 | Pat::Path(..)
219 | Pat::Lit(..)
220 | Pat::Bind { .. }
221 | Pat::TupleStruct { .. }
222 | Pat::Ref { .. }
223 | Pat::Box { .. }
224 | Pat::Expr(..)
225 | Pat::ConstBlock(..) => {
226 self.on_unsafe_op(current.into(), UnsafetyReason::UnionField)
227 }
228 Pat::Missing | Pat::Wild | Pat::Or(_) => {}
230 }
231 }
232
233 match pat {
234 Pat::Record { .. } => {
235 if let Some((AdtId::UnionId(_), _)) = self.infer[current].as_adt() {
236 let old_inside_union_destructure =
237 mem::replace(&mut self.inside_union_destructure, true);
238 self.body.walk_pats_shallow(current, |pat| self.walk_pat(pat));
239 self.inside_union_destructure = old_inside_union_destructure;
240 return;
241 }
242 }
243 Pat::Path(path) => self.mark_unsafe_path(current.into(), path),
244 &Pat::ConstBlock(expr) => {
245 let old_inside_assignment = mem::replace(&mut self.inside_assignment, false);
246 self.walk_expr(expr);
247 self.inside_assignment = old_inside_assignment;
248 }
249 &Pat::Expr(expr) => self.walk_expr(expr),
250 _ => {}
251 }
252
253 self.body.walk_pats_shallow(current, |pat| self.walk_pat(pat));
254 }
255
256 fn walk_expr(&mut self, current: ExprId) {
257 let expr = &self.body.exprs[current];
258 let inside_assignment = mem::replace(&mut self.inside_assignment, false);
259 match expr {
260 &Expr::Call { callee, .. } => {
261 let callee = &self.infer[callee];
262 if let Some(func) = callee.as_fn_def(self.db) {
263 self.check_call(current, func);
264 }
265 if let TyKind::Function(fn_ptr) = callee.kind(Interner) {
266 if fn_ptr.sig.safety == chalk_ir::Safety::Unsafe {
267 self.on_unsafe_op(current.into(), UnsafetyReason::UnsafeFnCall);
268 }
269 }
270 }
271 Expr::Path(path) => {
272 let guard =
273 self.resolver.update_to_inner_scope(self.db.upcast(), self.def, current);
274 self.mark_unsafe_path(current.into(), path);
275 self.resolver.reset_to_guard(guard);
276 }
277 Expr::Ref { expr, rawness: Rawness::RawPtr, mutability: _ } => {
278 match self.body.exprs[*expr] {
279 Expr::Path(_) => return,
282 Expr::UnaryOp { expr, op: UnaryOp::Deref } => {
285 self.body
286 .walk_child_exprs_without_pats(expr, |child| self.walk_expr(child));
287
288 return;
289 }
290 _ => (),
291 }
292 }
293 Expr::MethodCall { .. } => {
294 if let Some((func, _)) = self.infer.method_resolution(current) {
295 self.check_call(current, func);
296 }
297 }
298 Expr::UnaryOp { expr, op: UnaryOp::Deref } => {
299 if let TyKind::Raw(..) = &self.infer[*expr].kind(Interner) {
300 self.on_unsafe_op(current.into(), UnsafetyReason::RawPtrDeref);
301 }
302 }
303 &Expr::Assignment { target, value: _ } => {
304 let old_inside_assignment = mem::replace(&mut self.inside_assignment, true);
305 self.walk_pats_top(std::iter::once(target), current);
306 self.inside_assignment = old_inside_assignment;
307 }
308 Expr::InlineAsm(_) => self.on_unsafe_op(current.into(), UnsafetyReason::InlineAsm),
309 Expr::Cast { .. } => self.inside_assignment = inside_assignment,
311 Expr::Field { .. } => {
312 self.inside_assignment = inside_assignment;
313 if !inside_assignment {
314 if let Some(Either::Left(FieldId { parent: VariantId::UnionId(_), .. })) =
315 self.infer.field_resolution(current)
316 {
317 self.on_unsafe_op(current.into(), UnsafetyReason::UnionField);
318 }
319 }
320 }
321 Expr::Unsafe { statements, .. } => {
322 let old_inside_unsafe_block =
323 mem::replace(&mut self.inside_unsafe_block, InsideUnsafeBlock::Yes);
324 self.walk_pats_top(
325 statements.iter().filter_map(|statement| match statement {
326 &Statement::Let { pat, .. } => Some(pat),
327 _ => None,
328 }),
329 current,
330 );
331 self.body.walk_child_exprs_without_pats(current, |child| self.walk_expr(child));
332 self.inside_unsafe_block = old_inside_unsafe_block;
333 return;
334 }
335 Expr::Block { statements, .. } | Expr::Async { statements, .. } => {
336 self.walk_pats_top(
337 statements.iter().filter_map(|statement| match statement {
338 &Statement::Let { pat, .. } => Some(pat),
339 _ => None,
340 }),
341 current,
342 );
343 }
344 Expr::Match { arms, .. } => {
345 self.walk_pats_top(arms.iter().map(|arm| arm.pat), current);
346 }
347 &Expr::Let { pat, .. } => {
348 self.walk_pats_top(std::iter::once(pat), current);
349 }
350 Expr::Closure { args, .. } => {
351 self.walk_pats_top(args.iter().copied(), current);
352 }
353 _ => {}
354 }
355
356 self.body.walk_child_exprs_without_pats(current, |child| self.walk_expr(child));
357 }
358
359 fn mark_unsafe_path(&mut self, node: ExprOrPatId, path: &Path) {
360 let hygiene = self.body.expr_or_pat_path_hygiene(node);
361 let value_or_partial =
362 self.resolver.resolve_path_in_value_ns(self.db.upcast(), path, hygiene);
363 if let Some(ResolveValueResult::ValueNs(ValueNs::StaticId(id), _)) = value_or_partial {
364 let static_data = self.db.static_data(id);
365 if static_data.mutable() {
366 self.on_unsafe_op(node, UnsafetyReason::MutableStatic);
367 } else if static_data.is_extern() && !static_data.has_safe_kw() {
368 self.on_unsafe_op(node, UnsafetyReason::ExternStatic);
369 }
370 }
371 }
372}