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