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