]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_mir_transform/src/check_unsafety.rs
Rollup merge of #101423 - mkroening:hermit-warnings, r=sanxiyn
[rust.git] / compiler / rustc_mir_transform / src / check_unsafety.rs
1 use rustc_data_structures::fx::FxHashSet;
2 use rustc_errors::struct_span_err;
3 use rustc_hir as hir;
4 use rustc_hir::def_id::{DefId, LocalDefId};
5 use rustc_hir::hir_id::HirId;
6 use rustc_hir::intravisit;
7 use rustc_middle::mir::visit::{MutatingUseContext, PlaceContext, Visitor};
8 use rustc_middle::mir::*;
9 use rustc_middle::ty::query::Providers;
10 use rustc_middle::ty::{self, TyCtxt};
11 use rustc_session::lint::builtin::{UNSAFE_OP_IN_UNSAFE_FN, UNUSED_UNSAFE};
12 use rustc_session::lint::Level;
13
14 use std::ops::Bound;
15
16 pub struct UnsafetyChecker<'a, 'tcx> {
17     body: &'a Body<'tcx>,
18     body_did: LocalDefId,
19     violations: Vec<UnsafetyViolation>,
20     source_info: SourceInfo,
21     tcx: TyCtxt<'tcx>,
22     param_env: ty::ParamEnv<'tcx>,
23
24     /// Used `unsafe` blocks in this function. This is used for the "unused_unsafe" lint.
25     used_unsafe_blocks: FxHashSet<HirId>,
26 }
27
28 impl<'a, 'tcx> UnsafetyChecker<'a, 'tcx> {
29     fn new(
30         body: &'a Body<'tcx>,
31         body_did: LocalDefId,
32         tcx: TyCtxt<'tcx>,
33         param_env: ty::ParamEnv<'tcx>,
34     ) -> Self {
35         Self {
36             body,
37             body_did,
38             violations: vec![],
39             source_info: SourceInfo::outermost(body.span),
40             tcx,
41             param_env,
42             used_unsafe_blocks: Default::default(),
43         }
44     }
45 }
46
47 impl<'tcx> Visitor<'tcx> for UnsafetyChecker<'_, 'tcx> {
48     fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
49         self.source_info = terminator.source_info;
50         match terminator.kind {
51             TerminatorKind::Goto { .. }
52             | TerminatorKind::SwitchInt { .. }
53             | TerminatorKind::Drop { .. }
54             | TerminatorKind::Yield { .. }
55             | TerminatorKind::Assert { .. }
56             | TerminatorKind::DropAndReplace { .. }
57             | TerminatorKind::GeneratorDrop
58             | TerminatorKind::Resume
59             | TerminatorKind::Abort
60             | TerminatorKind::Return
61             | TerminatorKind::Unreachable
62             | TerminatorKind::FalseEdge { .. }
63             | TerminatorKind::FalseUnwind { .. } => {
64                 // safe (at least as emitted during MIR construction)
65             }
66
67             TerminatorKind::Call { ref func, .. } => {
68                 let func_ty = func.ty(self.body, self.tcx);
69                 let func_id =
70                     if let ty::FnDef(func_id, _) = func_ty.kind() { Some(func_id) } else { None };
71                 let sig = func_ty.fn_sig(self.tcx);
72                 if let hir::Unsafety::Unsafe = sig.unsafety() {
73                     self.require_unsafe(
74                         UnsafetyViolationKind::General,
75                         UnsafetyViolationDetails::CallToUnsafeFunction,
76                     )
77                 }
78
79                 if let Some(func_id) = func_id {
80                     self.check_target_features(*func_id);
81                 }
82             }
83
84             TerminatorKind::InlineAsm { .. } => self.require_unsafe(
85                 UnsafetyViolationKind::General,
86                 UnsafetyViolationDetails::UseOfInlineAssembly,
87             ),
88         }
89         self.super_terminator(terminator, location);
90     }
91
92     fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
93         self.source_info = statement.source_info;
94         match statement.kind {
95             StatementKind::Assign(..)
96             | StatementKind::FakeRead(..)
97             | StatementKind::SetDiscriminant { .. }
98             | StatementKind::Deinit(..)
99             | StatementKind::StorageLive(..)
100             | StatementKind::StorageDead(..)
101             | StatementKind::Retag { .. }
102             | StatementKind::AscribeUserType(..)
103             | StatementKind::Coverage(..)
104             | StatementKind::Nop => {
105                 // safe (at least as emitted during MIR construction)
106             }
107
108             // Move to above list once mir construction uses it.
109             StatementKind::Intrinsic(..) => unreachable!(),
110         }
111         self.super_statement(statement, location);
112     }
113
114     fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) {
115         match rvalue {
116             Rvalue::Aggregate(box ref aggregate, _) => match aggregate {
117                 &AggregateKind::Array(..) | &AggregateKind::Tuple => {}
118                 &AggregateKind::Adt(adt_did, ..) => {
119                     match self.tcx.layout_scalar_valid_range(adt_did) {
120                         (Bound::Unbounded, Bound::Unbounded) => {}
121                         _ => self.require_unsafe(
122                             UnsafetyViolationKind::General,
123                             UnsafetyViolationDetails::InitializingTypeWith,
124                         ),
125                     }
126                 }
127                 &AggregateKind::Closure(def_id, _) | &AggregateKind::Generator(def_id, _, _) => {
128                     let UnsafetyCheckResult { violations, used_unsafe_blocks, .. } =
129                         self.tcx.unsafety_check_result(def_id);
130                     self.register_violations(violations, used_unsafe_blocks.iter().copied());
131                 }
132             },
133             _ => {}
134         }
135         self.super_rvalue(rvalue, location);
136     }
137
138     fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, _location: Location) {
139         // On types with `scalar_valid_range`, prevent
140         // * `&mut x.field`
141         // * `x.field = y;`
142         // * `&x.field` if `field`'s type has interior mutability
143         // because either of these would allow modifying the layout constrained field and
144         // insert values that violate the layout constraints.
145         if context.is_mutating_use() || context.is_borrow() {
146             self.check_mut_borrowing_layout_constrained_field(*place, context.is_mutating_use());
147         }
148
149         // Some checks below need the extra meta info of the local declaration.
150         let decl = &self.body.local_decls[place.local];
151
152         // Check the base local: it might be an unsafe-to-access static. We only check derefs of the
153         // temporary holding the static pointer to avoid duplicate errors
154         // <https://github.com/rust-lang/rust/pull/78068#issuecomment-731753506>.
155         if decl.internal && place.projection.first() == Some(&ProjectionElem::Deref) {
156             // If the projection root is an artificial local that we introduced when
157             // desugaring `static`, give a more specific error message
158             // (avoid the general "raw pointer" clause below, that would only be confusing).
159             if let Some(box LocalInfo::StaticRef { def_id, .. }) = decl.local_info {
160                 if self.tcx.is_mutable_static(def_id) {
161                     self.require_unsafe(
162                         UnsafetyViolationKind::General,
163                         UnsafetyViolationDetails::UseOfMutableStatic,
164                     );
165                     return;
166                 } else if self.tcx.is_foreign_item(def_id) {
167                     self.require_unsafe(
168                         UnsafetyViolationKind::General,
169                         UnsafetyViolationDetails::UseOfExternStatic,
170                     );
171                     return;
172                 }
173             }
174         }
175
176         // Check for raw pointer `Deref`.
177         for (base, proj) in place.iter_projections() {
178             if proj == ProjectionElem::Deref {
179                 let base_ty = base.ty(self.body, self.tcx).ty;
180                 if base_ty.is_unsafe_ptr() {
181                     self.require_unsafe(
182                         UnsafetyViolationKind::General,
183                         UnsafetyViolationDetails::DerefOfRawPointer,
184                     )
185                 }
186             }
187         }
188
189         // Check for union fields. For this we traverse right-to-left, as the last `Deref` changes
190         // whether we *read* the union field or potentially *write* to it (if this place is being assigned to).
191         let mut saw_deref = false;
192         for (base, proj) in place.iter_projections().rev() {
193             if proj == ProjectionElem::Deref {
194                 saw_deref = true;
195                 continue;
196             }
197
198             let base_ty = base.ty(self.body, self.tcx).ty;
199             if base_ty.is_union() {
200                 // If we did not hit a `Deref` yet and the overall place use is an assignment, the
201                 // rules are different.
202                 let assign_to_field = !saw_deref
203                     && matches!(
204                         context,
205                         PlaceContext::MutatingUse(
206                             MutatingUseContext::Store
207                                 | MutatingUseContext::Drop
208                                 | MutatingUseContext::AsmOutput
209                         )
210                     );
211                 // If this is just an assignment, determine if the assigned type needs dropping.
212                 if assign_to_field {
213                     // We have to check the actual type of the assignment, as that determines if the
214                     // old value is being dropped.
215                     let assigned_ty = place.ty(&self.body.local_decls, self.tcx).ty;
216                     if assigned_ty.needs_drop(self.tcx, self.param_env) {
217                         // This would be unsafe, but should be outright impossible since we reject such unions.
218                         self.tcx.sess.delay_span_bug(
219                             self.source_info.span,
220                             format!("union fields that need dropping should be impossible: {assigned_ty}")
221                         );
222                     }
223                 } else {
224                     self.require_unsafe(
225                         UnsafetyViolationKind::General,
226                         UnsafetyViolationDetails::AccessToUnionField,
227                     )
228                 }
229             }
230         }
231     }
232 }
233
234 impl<'tcx> UnsafetyChecker<'_, 'tcx> {
235     fn require_unsafe(&mut self, kind: UnsafetyViolationKind, details: UnsafetyViolationDetails) {
236         // Violations can turn out to be `UnsafeFn` during analysis, but they should not start out as such.
237         assert_ne!(kind, UnsafetyViolationKind::UnsafeFn);
238
239         let source_info = self.source_info;
240         let lint_root = self.body.source_scopes[self.source_info.scope]
241             .local_data
242             .as_ref()
243             .assert_crate_local()
244             .lint_root;
245         self.register_violations(
246             [&UnsafetyViolation { source_info, lint_root, kind, details }],
247             [],
248         );
249     }
250
251     fn register_violations<'a>(
252         &mut self,
253         violations: impl IntoIterator<Item = &'a UnsafetyViolation>,
254         new_used_unsafe_blocks: impl IntoIterator<Item = HirId>,
255     ) {
256         let safety = self.body.source_scopes[self.source_info.scope]
257             .local_data
258             .as_ref()
259             .assert_crate_local()
260             .safety;
261         match safety {
262             // `unsafe` blocks are required in safe code
263             Safety::Safe => violations.into_iter().for_each(|&violation| {
264                 match violation.kind {
265                     UnsafetyViolationKind::General => {}
266                     UnsafetyViolationKind::UnsafeFn => {
267                         bug!("`UnsafetyViolationKind::UnsafeFn` in an `Safe` context")
268                     }
269                 }
270                 if !self.violations.contains(&violation) {
271                     self.violations.push(violation)
272                 }
273             }),
274             // With the RFC 2585, no longer allow `unsafe` operations in `unsafe fn`s
275             Safety::FnUnsafe => violations.into_iter().for_each(|&(mut violation)| {
276                 violation.kind = UnsafetyViolationKind::UnsafeFn;
277                 if !self.violations.contains(&violation) {
278                     self.violations.push(violation)
279                 }
280             }),
281             Safety::BuiltinUnsafe => {}
282             Safety::ExplicitUnsafe(hir_id) => violations.into_iter().for_each(|_violation| {
283                 self.used_unsafe_blocks.insert(hir_id);
284             }),
285         };
286
287         new_used_unsafe_blocks.into_iter().for_each(|hir_id| {
288             self.used_unsafe_blocks.insert(hir_id);
289         });
290     }
291     fn check_mut_borrowing_layout_constrained_field(
292         &mut self,
293         place: Place<'tcx>,
294         is_mut_use: bool,
295     ) {
296         for (place_base, elem) in place.iter_projections().rev() {
297             match elem {
298                 // Modifications behind a dereference don't affect the value of
299                 // the pointer.
300                 ProjectionElem::Deref => return,
301                 ProjectionElem::Field(..) => {
302                     let ty = place_base.ty(&self.body.local_decls, self.tcx).ty;
303                     if let ty::Adt(def, _) = ty.kind() {
304                         if self.tcx.layout_scalar_valid_range(def.did())
305                             != (Bound::Unbounded, Bound::Unbounded)
306                         {
307                             let details = if is_mut_use {
308                                 UnsafetyViolationDetails::MutationOfLayoutConstrainedField
309
310                             // Check `is_freeze` as late as possible to avoid cycle errors
311                             // with opaque types.
312                             } else if !place
313                                 .ty(self.body, self.tcx)
314                                 .ty
315                                 .is_freeze(self.tcx.at(self.source_info.span), self.param_env)
316                             {
317                                 UnsafetyViolationDetails::BorrowOfLayoutConstrainedField
318                             } else {
319                                 continue;
320                             };
321                             self.require_unsafe(UnsafetyViolationKind::General, details);
322                         }
323                     }
324                 }
325                 _ => {}
326             }
327         }
328     }
329
330     /// Checks whether calling `func_did` needs an `unsafe` context or not, i.e. whether
331     /// the called function has target features the calling function hasn't.
332     fn check_target_features(&mut self, func_did: DefId) {
333         // Unsafety isn't required on wasm targets. For more information see
334         // the corresponding check in typeck/src/collect.rs
335         if self.tcx.sess.target.options.is_like_wasm {
336             return;
337         }
338
339         let callee_features = &self.tcx.codegen_fn_attrs(func_did).target_features;
340         // The body might be a constant, so it doesn't have codegen attributes.
341         let self_features = &self.tcx.body_codegen_attrs(self.body_did.to_def_id()).target_features;
342
343         // Is `callee_features` a subset of `calling_features`?
344         if !callee_features.iter().all(|feature| self_features.contains(feature)) {
345             self.require_unsafe(
346                 UnsafetyViolationKind::General,
347                 UnsafetyViolationDetails::CallToFunctionWith,
348             )
349         }
350     }
351 }
352
353 pub(crate) fn provide(providers: &mut Providers) {
354     *providers = Providers {
355         unsafety_check_result: |tcx, def_id| {
356             if let Some(def) = ty::WithOptConstParam::try_lookup(def_id, tcx) {
357                 tcx.unsafety_check_result_for_const_arg(def)
358             } else {
359                 unsafety_check_result(tcx, ty::WithOptConstParam::unknown(def_id))
360             }
361         },
362         unsafety_check_result_for_const_arg: |tcx, (did, param_did)| {
363             unsafety_check_result(
364                 tcx,
365                 ty::WithOptConstParam { did, const_param_did: Some(param_did) },
366             )
367         },
368         ..*providers
369     };
370 }
371
372 /// Context information for [`UnusedUnsafeVisitor`] traversal,
373 /// saves (innermost) relevant context
374 #[derive(Copy, Clone, Debug)]
375 enum Context {
376     Safe,
377     /// in an `unsafe fn`
378     UnsafeFn(HirId),
379     /// in a *used* `unsafe` block
380     /// (i.e. a block without unused-unsafe warning)
381     UnsafeBlock(HirId),
382 }
383
384 struct UnusedUnsafeVisitor<'a, 'tcx> {
385     tcx: TyCtxt<'tcx>,
386     used_unsafe_blocks: &'a FxHashSet<HirId>,
387     context: Context,
388     unused_unsafes: &'a mut Vec<(HirId, UnusedUnsafe)>,
389 }
390
391 impl<'tcx> intravisit::Visitor<'tcx> for UnusedUnsafeVisitor<'_, 'tcx> {
392     fn visit_block(&mut self, block: &'tcx hir::Block<'tcx>) {
393         if let hir::BlockCheckMode::UnsafeBlock(hir::UnsafeSource::UserProvided) = block.rules {
394             let used = match self.tcx.lint_level_at_node(UNUSED_UNSAFE, block.hir_id) {
395                 (Level::Allow, _) => true,
396                 _ => self.used_unsafe_blocks.contains(&block.hir_id),
397             };
398             let unused_unsafe = match (self.context, used) {
399                 (_, false) => UnusedUnsafe::Unused,
400                 (Context::Safe, true) | (Context::UnsafeFn(_), true) => {
401                     let previous_context = self.context;
402                     self.context = Context::UnsafeBlock(block.hir_id);
403                     intravisit::walk_block(self, block);
404                     self.context = previous_context;
405                     return;
406                 }
407                 (Context::UnsafeBlock(hir_id), true) => UnusedUnsafe::InUnsafeBlock(hir_id),
408             };
409             self.unused_unsafes.push((block.hir_id, unused_unsafe));
410         }
411         intravisit::walk_block(self, block);
412     }
413
414     fn visit_fn(
415         &mut self,
416         fk: intravisit::FnKind<'tcx>,
417         _fd: &'tcx hir::FnDecl<'tcx>,
418         b: hir::BodyId,
419         _s: rustc_span::Span,
420         _id: HirId,
421     ) {
422         if matches!(fk, intravisit::FnKind::Closure) {
423             self.visit_body(self.tcx.hir().body(b))
424         }
425     }
426 }
427
428 fn check_unused_unsafe(
429     tcx: TyCtxt<'_>,
430     def_id: LocalDefId,
431     used_unsafe_blocks: &FxHashSet<HirId>,
432 ) -> Vec<(HirId, UnusedUnsafe)> {
433     let body_id = tcx.hir().maybe_body_owned_by(def_id);
434
435     let Some(body_id) = body_id else {
436         debug!("check_unused_unsafe({:?}) - no body found", def_id);
437         return vec![];
438     };
439
440     let body = tcx.hir().body(body_id);
441     let hir_id = tcx.hir().local_def_id_to_hir_id(def_id);
442     let context = match tcx.hir().fn_sig_by_hir_id(hir_id) {
443         Some(sig) if sig.header.unsafety == hir::Unsafety::Unsafe => Context::UnsafeFn(hir_id),
444         _ => Context::Safe,
445     };
446
447     debug!(
448         "check_unused_unsafe({:?}, context={:?}, body={:?}, used_unsafe_blocks={:?})",
449         def_id, body, context, used_unsafe_blocks
450     );
451
452     let mut unused_unsafes = vec![];
453
454     let mut visitor = UnusedUnsafeVisitor {
455         tcx,
456         used_unsafe_blocks,
457         context,
458         unused_unsafes: &mut unused_unsafes,
459     };
460     intravisit::Visitor::visit_body(&mut visitor, body);
461
462     unused_unsafes
463 }
464
465 fn unsafety_check_result<'tcx>(
466     tcx: TyCtxt<'tcx>,
467     def: ty::WithOptConstParam<LocalDefId>,
468 ) -> &'tcx UnsafetyCheckResult {
469     debug!("unsafety_violations({:?})", def);
470
471     // N.B., this borrow is valid because all the consumers of
472     // `mir_built` force this.
473     let body = &tcx.mir_built(def).borrow();
474
475     let param_env = tcx.param_env(def.did);
476
477     let mut checker = UnsafetyChecker::new(body, def.did, tcx, param_env);
478     checker.visit_body(&body);
479
480     let unused_unsafes = (!tcx.is_closure(def.did.to_def_id()))
481         .then(|| check_unused_unsafe(tcx, def.did, &checker.used_unsafe_blocks));
482
483     tcx.arena.alloc(UnsafetyCheckResult {
484         violations: checker.violations,
485         used_unsafe_blocks: checker.used_unsafe_blocks,
486         unused_unsafes,
487     })
488 }
489
490 fn report_unused_unsafe(tcx: TyCtxt<'_>, kind: UnusedUnsafe, id: HirId) {
491     let span = tcx.sess.source_map().guess_head_span(tcx.hir().span(id));
492     tcx.struct_span_lint_hir(UNUSED_UNSAFE, id, span, |lint| {
493         let msg = "unnecessary `unsafe` block";
494         let mut db = lint.build(msg);
495         db.span_label(span, msg);
496         match kind {
497             UnusedUnsafe::Unused => {}
498             UnusedUnsafe::InUnsafeBlock(id) => {
499                 db.span_label(
500                     tcx.sess.source_map().guess_head_span(tcx.hir().span(id)),
501                     "because it's nested under this `unsafe` block",
502                 );
503             }
504         }
505
506         db.emit();
507     });
508 }
509
510 pub fn check_unsafety(tcx: TyCtxt<'_>, def_id: LocalDefId) {
511     debug!("check_unsafety({:?})", def_id);
512
513     // closures are handled by their parent fn.
514     if tcx.is_closure(def_id.to_def_id()) {
515         return;
516     }
517
518     let UnsafetyCheckResult { violations, unused_unsafes, .. } = tcx.unsafety_check_result(def_id);
519
520     for &UnsafetyViolation { source_info, lint_root, kind, details } in violations.iter() {
521         let (description, note) = details.description_and_note();
522
523         // Report an error.
524         let unsafe_fn_msg =
525             if unsafe_op_in_unsafe_fn_allowed(tcx, lint_root) { " function or" } else { "" };
526
527         match kind {
528             UnsafetyViolationKind::General => {
529                 // once
530                 struct_span_err!(
531                     tcx.sess,
532                     source_info.span,
533                     E0133,
534                     "{} is unsafe and requires unsafe{} block",
535                     description,
536                     unsafe_fn_msg,
537                 )
538                 .span_label(source_info.span, description)
539                 .note(note)
540                 .emit();
541             }
542             UnsafetyViolationKind::UnsafeFn => tcx.struct_span_lint_hir(
543                 UNSAFE_OP_IN_UNSAFE_FN,
544                 lint_root,
545                 source_info.span,
546                 |lint| {
547                     lint.build(&format!(
548                         "{} is unsafe and requires unsafe block (error E0133)",
549                         description,
550                     ))
551                     .span_label(source_info.span, description)
552                     .note(note)
553                     .emit();
554                 },
555             ),
556         }
557     }
558
559     for &(block_id, kind) in unused_unsafes.as_ref().unwrap() {
560         report_unused_unsafe(tcx, kind, block_id);
561     }
562 }
563
564 fn unsafe_op_in_unsafe_fn_allowed(tcx: TyCtxt<'_>, id: HirId) -> bool {
565     tcx.lint_level_at_node(UNSAFE_OP_IN_UNSAFE_FN, id).0 == Level::Allow
566 }