]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_mir/src/transform/check_unsafety.rs
Rollup merge of #82739 - jyn514:separate-stage0-stage1, r=Mark-Simulacrum
[rust.git] / compiler / rustc_mir / src / transform / 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_hir::Node;
8 use rustc_middle::mir::visit::{MutatingUseContext, PlaceContext, Visitor};
9 use rustc_middle::mir::*;
10 use rustc_middle::ty::cast::CastTy;
11 use rustc_middle::ty::query::Providers;
12 use rustc_middle::ty::{self, TyCtxt};
13 use rustc_session::lint::builtin::{UNSAFE_OP_IN_UNSAFE_FN, UNUSED_UNSAFE};
14 use rustc_session::lint::Level;
15
16 use std::ops::Bound;
17
18 use crate::const_eval::is_min_const_fn;
19
20 pub struct UnsafetyChecker<'a, 'tcx> {
21     body: &'a Body<'tcx>,
22     body_did: LocalDefId,
23     const_context: bool,
24     min_const_fn: bool,
25     violations: Vec<UnsafetyViolation>,
26     source_info: SourceInfo,
27     tcx: TyCtxt<'tcx>,
28     param_env: ty::ParamEnv<'tcx>,
29     /// Mark an `unsafe` block as used, so we don't lint it.
30     used_unsafe: FxHashSet<hir::HirId>,
31     inherited_blocks: Vec<(hir::HirId, bool)>,
32 }
33
34 impl<'a, 'tcx> UnsafetyChecker<'a, 'tcx> {
35     fn new(
36         const_context: bool,
37         min_const_fn: bool,
38         body: &'a Body<'tcx>,
39         body_did: LocalDefId,
40         tcx: TyCtxt<'tcx>,
41         param_env: ty::ParamEnv<'tcx>,
42     ) -> Self {
43         // sanity check
44         if min_const_fn {
45             assert!(const_context);
46         }
47         Self {
48             body,
49             body_did,
50             const_context,
51             min_const_fn,
52             violations: vec![],
53             source_info: SourceInfo::outermost(body.span),
54             tcx,
55             param_env,
56             used_unsafe: Default::default(),
57             inherited_blocks: vec![],
58         }
59     }
60 }
61
62 impl<'a, 'tcx> Visitor<'tcx> for UnsafetyChecker<'a, 'tcx> {
63     fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
64         self.source_info = terminator.source_info;
65         match terminator.kind {
66             TerminatorKind::Goto { .. }
67             | TerminatorKind::SwitchInt { .. }
68             | TerminatorKind::Drop { .. }
69             | TerminatorKind::Yield { .. }
70             | TerminatorKind::Assert { .. }
71             | TerminatorKind::DropAndReplace { .. }
72             | TerminatorKind::GeneratorDrop
73             | TerminatorKind::Resume
74             | TerminatorKind::Abort
75             | TerminatorKind::Return
76             | TerminatorKind::Unreachable
77             | TerminatorKind::FalseEdge { .. }
78             | TerminatorKind::FalseUnwind { .. } => {
79                 // safe (at least as emitted during MIR construction)
80             }
81
82             TerminatorKind::Call { ref func, .. } => {
83                 let func_ty = func.ty(self.body, self.tcx);
84                 let sig = func_ty.fn_sig(self.tcx);
85                 if let hir::Unsafety::Unsafe = sig.unsafety() {
86                     self.require_unsafe(
87                         UnsafetyViolationKind::GeneralAndConstFn,
88                         UnsafetyViolationDetails::CallToUnsafeFunction,
89                     )
90                 }
91
92                 if let ty::FnDef(func_id, _) = func_ty.kind() {
93                     self.check_target_features(*func_id);
94                 }
95             }
96
97             TerminatorKind::InlineAsm { .. } => self.require_unsafe(
98                 UnsafetyViolationKind::General,
99                 UnsafetyViolationDetails::UseOfInlineAssembly,
100             ),
101         }
102         self.super_terminator(terminator, location);
103     }
104
105     fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
106         self.source_info = statement.source_info;
107         match statement.kind {
108             StatementKind::Assign(..)
109             | StatementKind::FakeRead(..)
110             | StatementKind::SetDiscriminant { .. }
111             | StatementKind::StorageLive(..)
112             | StatementKind::StorageDead(..)
113             | StatementKind::Retag { .. }
114             | StatementKind::AscribeUserType(..)
115             | StatementKind::Coverage(..)
116             | StatementKind::Nop => {
117                 // safe (at least as emitted during MIR construction)
118             }
119
120             StatementKind::LlvmInlineAsm { .. } => self.require_unsafe(
121                 UnsafetyViolationKind::General,
122                 UnsafetyViolationDetails::UseOfInlineAssembly,
123             ),
124             StatementKind::CopyNonOverlapping(..) => unreachable!(),
125         }
126         self.super_statement(statement, location);
127     }
128
129     fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) {
130         match rvalue {
131             Rvalue::Aggregate(box ref aggregate, _) => match aggregate {
132                 &AggregateKind::Array(..) | &AggregateKind::Tuple => {}
133                 &AggregateKind::Adt(ref def, ..) => {
134                     match self.tcx.layout_scalar_valid_range(def.did) {
135                         (Bound::Unbounded, Bound::Unbounded) => {}
136                         _ => self.require_unsafe(
137                             UnsafetyViolationKind::GeneralAndConstFn,
138                             UnsafetyViolationDetails::InitializingTypeWith,
139                         ),
140                     }
141                 }
142                 &AggregateKind::Closure(def_id, _) | &AggregateKind::Generator(def_id, _, _) => {
143                     let UnsafetyCheckResult { violations, unsafe_blocks } =
144                         self.tcx.unsafety_check_result(def_id.expect_local());
145                     self.register_violations(&violations, &unsafe_blocks);
146                 }
147             },
148             // casting pointers to ints is unsafe in const fn because the const evaluator cannot
149             // possibly know what the result of various operations like `address / 2` would be
150             // pointers during const evaluation have no integral address, only an abstract one
151             Rvalue::Cast(CastKind::Misc, ref operand, cast_ty)
152                 if self.const_context && self.tcx.features().const_raw_ptr_to_usize_cast =>
153             {
154                 let operand_ty = operand.ty(self.body, self.tcx);
155                 let cast_in = CastTy::from_ty(operand_ty).expect("bad input type for cast");
156                 let cast_out = CastTy::from_ty(cast_ty).expect("bad output type for cast");
157                 match (cast_in, cast_out) {
158                     (CastTy::Ptr(_) | CastTy::FnPtr, CastTy::Int(_)) => {
159                         self.require_unsafe(
160                             UnsafetyViolationKind::General,
161                             UnsafetyViolationDetails::CastOfPointerToInt,
162                         );
163                     }
164                     _ => {}
165                 }
166             }
167             _ => {}
168         }
169         self.super_rvalue(rvalue, location);
170     }
171
172     fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, _location: Location) {
173         // On types with `scalar_valid_range`, prevent
174         // * `&mut x.field`
175         // * `x.field = y;`
176         // * `&x.field` if `field`'s type has interior mutability
177         // because either of these would allow modifying the layout constrained field and
178         // insert values that violate the layout constraints.
179         if context.is_mutating_use() || context.is_borrow() {
180             self.check_mut_borrowing_layout_constrained_field(*place, context.is_mutating_use());
181         }
182
183         // Some checks below need the extra metainfo of the local declaration.
184         let decl = &self.body.local_decls[place.local];
185
186         // Check the base local: it might be an unsafe-to-access static. We only check derefs of the
187         // temporary holding the static pointer to avoid duplicate errors
188         // <https://github.com/rust-lang/rust/pull/78068#issuecomment-731753506>.
189         if decl.internal && place.projection.first() == Some(&ProjectionElem::Deref) {
190             // If the projection root is an artifical local that we introduced when
191             // desugaring `static`, give a more specific error message
192             // (avoid the general "raw pointer" clause below, that would only be confusing).
193             if let Some(box LocalInfo::StaticRef { def_id, .. }) = decl.local_info {
194                 if self.tcx.is_mutable_static(def_id) {
195                     self.require_unsafe(
196                         UnsafetyViolationKind::General,
197                         UnsafetyViolationDetails::UseOfMutableStatic,
198                     );
199                     return;
200                 } else if self.tcx.is_foreign_item(def_id) {
201                     self.require_unsafe(
202                         UnsafetyViolationKind::General,
203                         UnsafetyViolationDetails::UseOfExternStatic,
204                     );
205                     return;
206                 }
207             }
208         }
209
210         // Check for raw pointer `Deref`.
211         for (base, proj) in place.iter_projections() {
212             if proj == ProjectionElem::Deref {
213                 let base_ty = base.ty(self.body, self.tcx).ty;
214                 if base_ty.is_unsafe_ptr() {
215                     self.require_unsafe(
216                         UnsafetyViolationKind::GeneralAndConstFn,
217                         UnsafetyViolationDetails::DerefOfRawPointer,
218                     )
219                 }
220             }
221         }
222
223         // Check for union fields. For this we traverse right-to-left, as the last `Deref` changes
224         // whether we *read* the union field or potentially *write* to it (if this place is being assigned to).
225         let mut saw_deref = false;
226         for (base, proj) in place.iter_projections().rev() {
227             if proj == ProjectionElem::Deref {
228                 saw_deref = true;
229                 continue;
230             }
231
232             let base_ty = base.ty(self.body, self.tcx).ty;
233             if base_ty.ty_adt_def().map_or(false, |adt| adt.is_union()) {
234                 // If we did not hit a `Deref` yet and the overall place use is an assignment, the
235                 // rules are different.
236                 let assign_to_field = !saw_deref
237                     && matches!(
238                         context,
239                         PlaceContext::MutatingUse(
240                             MutatingUseContext::Store
241                                 | MutatingUseContext::Drop
242                                 | MutatingUseContext::AsmOutput
243                         )
244                     );
245                 // If this is just an assignment, determine if the assigned type needs dropping.
246                 if assign_to_field {
247                     // We have to check the actual type of the assignment, as that determines if the
248                     // old value is being dropped.
249                     let assigned_ty = place.ty(&self.body.local_decls, self.tcx).ty;
250                     // To avoid semver hazard, we only consider `Copy` and `ManuallyDrop` non-dropping.
251                     let manually_drop = assigned_ty
252                         .ty_adt_def()
253                         .map_or(false, |adt_def| adt_def.is_manually_drop());
254                     let nodrop = manually_drop
255                         || assigned_ty.is_copy_modulo_regions(
256                             self.tcx.at(self.source_info.span),
257                             self.param_env,
258                         );
259                     if !nodrop {
260                         self.require_unsafe(
261                             UnsafetyViolationKind::GeneralAndConstFn,
262                             UnsafetyViolationDetails::AssignToDroppingUnionField,
263                         );
264                     } else {
265                         // write to non-drop union field, safe
266                     }
267                 } else {
268                     self.require_unsafe(
269                         UnsafetyViolationKind::GeneralAndConstFn,
270                         UnsafetyViolationDetails::AccessToUnionField,
271                     )
272                 }
273             }
274         }
275     }
276 }
277
278 impl<'a, 'tcx> UnsafetyChecker<'a, 'tcx> {
279     fn require_unsafe(&mut self, kind: UnsafetyViolationKind, details: UnsafetyViolationDetails) {
280         let source_info = self.source_info;
281         let lint_root = self.body.source_scopes[self.source_info.scope]
282             .local_data
283             .as_ref()
284             .assert_crate_local()
285             .lint_root;
286         self.register_violations(
287             &[UnsafetyViolation { source_info, lint_root, kind, details }],
288             &[],
289         );
290     }
291
292     fn register_violations(
293         &mut self,
294         violations: &[UnsafetyViolation],
295         unsafe_blocks: &[(hir::HirId, bool)],
296     ) {
297         let safety = self.body.source_scopes[self.source_info.scope]
298             .local_data
299             .as_ref()
300             .assert_crate_local()
301             .safety;
302         let within_unsafe = match safety {
303             // `unsafe` blocks are required in safe code
304             Safety::Safe => {
305                 for violation in violations {
306                     match violation.kind {
307                         UnsafetyViolationKind::GeneralAndConstFn
308                         | UnsafetyViolationKind::General => {}
309                         UnsafetyViolationKind::UnsafeFn => {
310                             bug!("`UnsafetyViolationKind::UnsafeFn` in an `Safe` context")
311                         }
312                     }
313                     if !self.violations.contains(violation) {
314                         self.violations.push(*violation)
315                     }
316                 }
317                 false
318             }
319             // With the RFC 2585, no longer allow `unsafe` operations in `unsafe fn`s
320             Safety::FnUnsafe => {
321                 for violation in violations {
322                     let mut violation = *violation;
323
324                     violation.kind = UnsafetyViolationKind::UnsafeFn;
325                     if !self.violations.contains(&violation) {
326                         self.violations.push(violation)
327                     }
328                 }
329                 false
330             }
331             Safety::BuiltinUnsafe => true,
332             Safety::ExplicitUnsafe(hir_id) => {
333                 // mark unsafe block as used if there are any unsafe operations inside
334                 if !violations.is_empty() {
335                     self.used_unsafe.insert(hir_id);
336                 }
337                 // only some unsafety is allowed in const fn
338                 if self.min_const_fn {
339                     for violation in violations {
340                         match violation.kind {
341                             // these unsafe things are stable in const fn
342                             UnsafetyViolationKind::GeneralAndConstFn => {}
343                             // these things are forbidden in const fns
344                             UnsafetyViolationKind::General => {
345                                 let mut violation = *violation;
346                                 // const fns don't need to be backwards compatible and can
347                                 // emit these violations as a hard error instead of a backwards
348                                 // compat lint
349                                 violation.kind = UnsafetyViolationKind::General;
350                                 if !self.violations.contains(&violation) {
351                                     self.violations.push(violation)
352                                 }
353                             }
354                             UnsafetyViolationKind::UnsafeFn => bug!(
355                                 "`UnsafetyViolationKind::UnsafeFn` in an `ExplicitUnsafe` context"
356                             ),
357                         }
358                     }
359                 }
360                 true
361             }
362         };
363         self.inherited_blocks.extend(
364             unsafe_blocks.iter().map(|&(hir_id, is_used)| (hir_id, is_used && !within_unsafe)),
365         );
366     }
367     fn check_mut_borrowing_layout_constrained_field(
368         &mut self,
369         place: Place<'tcx>,
370         is_mut_use: bool,
371     ) {
372         for (place_base, elem) in place.iter_projections().rev() {
373             match elem {
374                 // Modifications behind a dereference don't affect the value of
375                 // the pointer.
376                 ProjectionElem::Deref => return,
377                 ProjectionElem::Field(..) => {
378                     let ty = place_base.ty(&self.body.local_decls, self.tcx).ty;
379                     if let ty::Adt(def, _) = ty.kind() {
380                         if self.tcx.layout_scalar_valid_range(def.did)
381                             != (Bound::Unbounded, Bound::Unbounded)
382                         {
383                             let details = if is_mut_use {
384                                 UnsafetyViolationDetails::MutationOfLayoutConstrainedField
385
386                             // Check `is_freeze` as late as possible to avoid cycle errors
387                             // with opaque types.
388                             } else if !place
389                                 .ty(self.body, self.tcx)
390                                 .ty
391                                 .is_freeze(self.tcx.at(self.source_info.span), self.param_env)
392                             {
393                                 UnsafetyViolationDetails::BorrowOfLayoutConstrainedField
394                             } else {
395                                 continue;
396                             };
397                             self.require_unsafe(UnsafetyViolationKind::GeneralAndConstFn, details);
398                         }
399                     }
400                 }
401                 _ => {}
402             }
403         }
404     }
405
406     /// Checks whether calling `func_did` needs an `unsafe` context or not, i.e. whether
407     /// the called function has target features the calling function hasn't.
408     fn check_target_features(&mut self, func_did: DefId) {
409         let callee_features = &self.tcx.codegen_fn_attrs(func_did).target_features;
410         let self_features = &self.tcx.codegen_fn_attrs(self.body_did).target_features;
411
412         // Is `callee_features` a subset of `calling_features`?
413         if !callee_features.iter().all(|feature| self_features.contains(feature)) {
414             self.require_unsafe(
415                 UnsafetyViolationKind::GeneralAndConstFn,
416                 UnsafetyViolationDetails::CallToFunctionWith,
417             )
418         }
419     }
420 }
421
422 pub(crate) fn provide(providers: &mut Providers) {
423     *providers = Providers {
424         unsafety_check_result: |tcx, def_id| {
425             if let Some(def) = ty::WithOptConstParam::try_lookup(def_id, tcx) {
426                 tcx.unsafety_check_result_for_const_arg(def)
427             } else {
428                 unsafety_check_result(tcx, ty::WithOptConstParam::unknown(def_id))
429             }
430         },
431         unsafety_check_result_for_const_arg: |tcx, (did, param_did)| {
432             unsafety_check_result(
433                 tcx,
434                 ty::WithOptConstParam { did, const_param_did: Some(param_did) },
435             )
436         },
437         ..*providers
438     };
439 }
440
441 struct UnusedUnsafeVisitor<'a> {
442     used_unsafe: &'a FxHashSet<hir::HirId>,
443     unsafe_blocks: &'a mut Vec<(hir::HirId, bool)>,
444 }
445
446 impl<'a, 'tcx> intravisit::Visitor<'tcx> for UnusedUnsafeVisitor<'a> {
447     type Map = intravisit::ErasedMap<'tcx>;
448
449     fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
450         intravisit::NestedVisitorMap::None
451     }
452
453     fn visit_block(&mut self, block: &'tcx hir::Block<'tcx>) {
454         intravisit::walk_block(self, block);
455
456         if let hir::BlockCheckMode::UnsafeBlock(hir::UnsafeSource::UserProvided) = block.rules {
457             self.unsafe_blocks.push((block.hir_id, self.used_unsafe.contains(&block.hir_id)));
458         }
459     }
460 }
461
462 fn check_unused_unsafe(
463     tcx: TyCtxt<'_>,
464     def_id: LocalDefId,
465     used_unsafe: &FxHashSet<hir::HirId>,
466     unsafe_blocks: &mut Vec<(hir::HirId, bool)>,
467 ) {
468     let body_id = tcx.hir().maybe_body_owned_by(tcx.hir().local_def_id_to_hir_id(def_id));
469
470     let body_id = match body_id {
471         Some(body) => body,
472         None => {
473             debug!("check_unused_unsafe({:?}) - no body found", def_id);
474             return;
475         }
476     };
477     let body = tcx.hir().body(body_id);
478     debug!("check_unused_unsafe({:?}, body={:?}, used_unsafe={:?})", def_id, body, used_unsafe);
479
480     let mut visitor = UnusedUnsafeVisitor { used_unsafe, unsafe_blocks };
481     intravisit::Visitor::visit_body(&mut visitor, body);
482 }
483
484 fn unsafety_check_result<'tcx>(
485     tcx: TyCtxt<'tcx>,
486     def: ty::WithOptConstParam<LocalDefId>,
487 ) -> &'tcx UnsafetyCheckResult {
488     debug!("unsafety_violations({:?})", def);
489
490     // N.B., this borrow is valid because all the consumers of
491     // `mir_built` force this.
492     let body = &tcx.mir_built(def).borrow();
493
494     let param_env = tcx.param_env(def.did);
495
496     let id = tcx.hir().local_def_id_to_hir_id(def.did);
497     let (const_context, min_const_fn) = match tcx.hir().body_owner_kind(id) {
498         hir::BodyOwnerKind::Closure => (false, false),
499         hir::BodyOwnerKind::Fn => {
500             (tcx.is_const_fn_raw(def.did.to_def_id()), is_min_const_fn(tcx, def.did.to_def_id()))
501         }
502         hir::BodyOwnerKind::Const | hir::BodyOwnerKind::Static(_) => (true, false),
503     };
504     let mut checker =
505         UnsafetyChecker::new(const_context, min_const_fn, body, def.did, tcx, param_env);
506     checker.visit_body(&body);
507
508     check_unused_unsafe(tcx, def.did, &checker.used_unsafe, &mut checker.inherited_blocks);
509
510     tcx.arena.alloc(UnsafetyCheckResult {
511         violations: checker.violations.into(),
512         unsafe_blocks: checker.inherited_blocks.into(),
513     })
514 }
515
516 /// Returns the `HirId` for an enclosing scope that is also `unsafe`.
517 fn is_enclosed(
518     tcx: TyCtxt<'_>,
519     used_unsafe: &FxHashSet<hir::HirId>,
520     id: hir::HirId,
521     unsafe_op_in_unsafe_fn_allowed: bool,
522 ) -> Option<(&'static str, hir::HirId)> {
523     let parent_id = tcx.hir().get_parent_node(id);
524     if parent_id != id {
525         if used_unsafe.contains(&parent_id) {
526             Some(("block", parent_id))
527         } else if let Some(Node::Item(&hir::Item {
528             kind: hir::ItemKind::Fn(ref sig, _, _), ..
529         })) = tcx.hir().find(parent_id)
530         {
531             if sig.header.unsafety == hir::Unsafety::Unsafe && unsafe_op_in_unsafe_fn_allowed {
532                 Some(("fn", parent_id))
533             } else {
534                 None
535             }
536         } else {
537             is_enclosed(tcx, used_unsafe, parent_id, unsafe_op_in_unsafe_fn_allowed)
538         }
539     } else {
540         None
541     }
542 }
543
544 fn report_unused_unsafe(tcx: TyCtxt<'_>, used_unsafe: &FxHashSet<hir::HirId>, id: hir::HirId) {
545     let span = tcx.sess.source_map().guess_head_span(tcx.hir().span(id));
546     tcx.struct_span_lint_hir(UNUSED_UNSAFE, id, span, |lint| {
547         let msg = "unnecessary `unsafe` block";
548         let mut db = lint.build(msg);
549         db.span_label(span, msg);
550         if let Some((kind, id)) =
551             is_enclosed(tcx, used_unsafe, id, unsafe_op_in_unsafe_fn_allowed(tcx, id))
552         {
553             db.span_label(
554                 tcx.sess.source_map().guess_head_span(tcx.hir().span(id)),
555                 format!("because it's nested under this `unsafe` {}", kind),
556             );
557         }
558         db.emit();
559     });
560 }
561
562 pub fn check_unsafety(tcx: TyCtxt<'_>, def_id: LocalDefId) {
563     debug!("check_unsafety({:?})", def_id);
564
565     // closures are handled by their parent fn.
566     if tcx.is_closure(def_id.to_def_id()) {
567         return;
568     }
569
570     let UnsafetyCheckResult { violations, unsafe_blocks } = tcx.unsafety_check_result(def_id);
571
572     for &UnsafetyViolation { source_info, lint_root, kind, details } in violations.iter() {
573         let (description, note) = details.description_and_note();
574
575         // Report an error.
576         let unsafe_fn_msg =
577             if unsafe_op_in_unsafe_fn_allowed(tcx, lint_root) { " function or" } else { "" };
578
579         match kind {
580             UnsafetyViolationKind::GeneralAndConstFn | UnsafetyViolationKind::General => {
581                 // once
582                 struct_span_err!(
583                     tcx.sess,
584                     source_info.span,
585                     E0133,
586                     "{} is unsafe and requires unsafe{} block",
587                     description,
588                     unsafe_fn_msg,
589                 )
590                 .span_label(source_info.span, description)
591                 .note(note)
592                 .emit();
593             }
594             UnsafetyViolationKind::UnsafeFn => tcx.struct_span_lint_hir(
595                 UNSAFE_OP_IN_UNSAFE_FN,
596                 lint_root,
597                 source_info.span,
598                 |lint| {
599                     lint.build(&format!(
600                         "{} is unsafe and requires unsafe block (error E0133)",
601                         description,
602                     ))
603                     .span_label(source_info.span, description)
604                     .note(note)
605                     .emit();
606                 },
607             ),
608         }
609     }
610
611     let (mut unsafe_used, mut unsafe_unused): (FxHashSet<_>, Vec<_>) = Default::default();
612     for &(block_id, is_used) in unsafe_blocks.iter() {
613         if is_used {
614             unsafe_used.insert(block_id);
615         } else {
616             unsafe_unused.push(block_id);
617         }
618     }
619     // The unused unsafe blocks might not be in source order; sort them so that the unused unsafe
620     // error messages are properly aligned and the issue-45107 and lint-unused-unsafe tests pass.
621     unsafe_unused.sort_by_cached_key(|hir_id| tcx.hir().span(*hir_id));
622
623     for &block_id in &unsafe_unused {
624         report_unused_unsafe(tcx, &unsafe_used, block_id);
625     }
626 }
627
628 fn unsafe_op_in_unsafe_fn_allowed(tcx: TyCtxt<'_>, id: HirId) -> bool {
629     tcx.lint_level_at_node(UNSAFE_OP_IN_UNSAFE_FN, id).0 == Level::Allow
630 }