1 use rustc_data_structures::fx::FxHashSet;
2 use rustc_errors::struct_span_err;
4 use rustc_hir::def_id::DefId;
5 use rustc_hir::intravisit;
7 use rustc_middle::mir::visit::{MutatingUseContext, PlaceContext, Visitor};
8 use rustc_middle::mir::*;
9 use rustc_middle::ty::cast::CastTy;
10 use rustc_middle::ty::query::Providers;
11 use rustc_middle::ty::{self, TyCtxt};
12 use rustc_session::lint::builtin::{SAFE_PACKED_BORROWS, UNUSED_UNSAFE};
13 use rustc_span::symbol::{sym, Symbol};
17 use crate::const_eval::{is_const_fn, is_min_const_fn};
20 pub struct UnsafetyChecker<'a, 'tcx> {
24 violations: Vec<UnsafetyViolation>,
25 source_info: SourceInfo,
27 param_env: ty::ParamEnv<'tcx>,
28 /// Mark an `unsafe` block as used, so we don't lint it.
29 used_unsafe: FxHashSet<hir::HirId>,
30 inherited_blocks: Vec<(hir::HirId, bool)>,
33 impl<'a, 'tcx> UnsafetyChecker<'a, 'tcx> {
39 param_env: ty::ParamEnv<'tcx>,
43 assert!(const_context);
50 source_info: SourceInfo { span: body.span, scope: OUTERMOST_SOURCE_SCOPE },
53 used_unsafe: Default::default(),
54 inherited_blocks: vec![],
59 impl<'a, 'tcx> Visitor<'tcx> for UnsafetyChecker<'a, 'tcx> {
60 fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
61 self.source_info = terminator.source_info;
62 match terminator.kind {
63 TerminatorKind::Goto { .. }
64 | TerminatorKind::SwitchInt { .. }
65 | TerminatorKind::Drop { .. }
66 | TerminatorKind::Yield { .. }
67 | TerminatorKind::Assert { .. }
68 | TerminatorKind::DropAndReplace { .. }
69 | TerminatorKind::GeneratorDrop
70 | TerminatorKind::Resume
71 | TerminatorKind::Abort
72 | TerminatorKind::Return
73 | TerminatorKind::Unreachable
74 | TerminatorKind::FalseEdges { .. }
75 | TerminatorKind::FalseUnwind { .. } => {
76 // safe (at least as emitted during MIR construction)
79 TerminatorKind::Call { ref func, .. } => {
80 let func_ty = func.ty(self.body, self.tcx);
81 let sig = func_ty.fn_sig(self.tcx);
82 if let hir::Unsafety::Unsafe = sig.unsafety() {
84 "call to unsafe function",
85 "consult the function's documentation for information on how to avoid \
87 UnsafetyViolationKind::GeneralAndConstFn,
92 self.super_terminator(terminator, location);
95 fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
96 self.source_info = statement.source_info;
97 match statement.kind {
98 StatementKind::Assign(..)
99 | StatementKind::FakeRead(..)
100 | StatementKind::SetDiscriminant { .. }
101 | StatementKind::StorageLive(..)
102 | StatementKind::StorageDead(..)
103 | StatementKind::Retag { .. }
104 | StatementKind::AscribeUserType(..)
105 | StatementKind::Nop => {
106 // safe (at least as emitted during MIR construction)
109 StatementKind::LlvmInlineAsm { .. } => self.require_unsafe(
110 "use of inline assembly",
111 "inline assembly is entirely unchecked and can cause undefined behavior",
112 UnsafetyViolationKind::General,
115 self.super_statement(statement, location);
118 fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) {
120 Rvalue::Aggregate(box ref aggregate, _) => match aggregate {
121 &AggregateKind::Array(..) | &AggregateKind::Tuple => {}
122 &AggregateKind::Adt(ref def, ..) => {
123 match self.tcx.layout_scalar_valid_range(def.did) {
124 (Bound::Unbounded, Bound::Unbounded) => {}
125 _ => self.require_unsafe(
126 "initializing type with `rustc_layout_scalar_valid_range` attr",
127 "initializing a layout restricted type's field with a value \
128 outside the valid range is undefined behavior",
129 UnsafetyViolationKind::GeneralAndConstFn,
133 &AggregateKind::Closure(def_id, _) | &AggregateKind::Generator(def_id, _, _) => {
134 let UnsafetyCheckResult { violations, unsafe_blocks } =
135 self.tcx.unsafety_check_result(def_id);
136 self.register_violations(&violations, &unsafe_blocks);
139 // casting pointers to ints is unsafe in const fn because the const evaluator cannot
140 // possibly know what the result of various operations like `address / 2` would be
141 // pointers during const evaluation have no integral address, only an abstract one
142 Rvalue::Cast(CastKind::Misc, ref operand, cast_ty)
143 if self.const_context && self.tcx.features().const_raw_ptr_to_usize_cast =>
145 let operand_ty = operand.ty(self.body, self.tcx);
146 let cast_in = CastTy::from_ty(operand_ty).expect("bad input type for cast");
147 let cast_out = CastTy::from_ty(cast_ty).expect("bad output type for cast");
148 match (cast_in, cast_out) {
149 (CastTy::Ptr(_) | CastTy::FnPtr, CastTy::Int(_)) => {
151 "cast of pointer to int",
152 "casting pointers to integers in constants",
153 UnsafetyViolationKind::General,
159 // raw pointer and fn pointer operations are unsafe as it is not clear whether one
160 // pointer would be "less" or "equal" to another, because we cannot know where llvm
161 // or the linker will place various statics in memory. Without this information the
162 // result of a comparison of addresses would differ between runtime and compile-time.
163 Rvalue::BinaryOp(_, ref lhs, _)
164 if self.const_context && self.tcx.features().const_compare_raw_pointers =>
166 if let ty::RawPtr(_) | ty::FnPtr(..) = lhs.ty(self.body, self.tcx).kind {
169 "operations on pointers in constants",
170 UnsafetyViolationKind::General,
176 self.super_rvalue(rvalue, location);
179 fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, _location: Location) {
183 // * `&x.field` if `field`'s type has interior mutability
184 // because either of these would allow modifying the layout constrained field and
185 // insert values that violate the layout constraints.
186 if context.is_mutating_use() || context.is_borrow() {
187 self.check_mut_borrowing_layout_constrained_field(*place, context.is_mutating_use());
190 for (i, elem) in place.projection.iter().enumerate() {
191 let proj_base = &place.projection[..i];
193 if context.is_borrow() {
194 if util::is_disaligned(self.tcx, self.body, self.param_env, *place) {
195 let source_info = self.source_info;
196 let lint_root = self.body.source_scopes[source_info.scope]
199 .assert_crate_local()
202 "borrow of packed field",
203 "fields of packed structs might be misaligned: dereferencing a \
204 misaligned pointer or even just creating a misaligned reference \
205 is undefined behavior",
206 UnsafetyViolationKind::BorrowPacked(lint_root),
210 let old_source_info = self.source_info;
211 if let [] = proj_base {
212 let decl = &self.body.local_decls[place.local];
214 if let LocalInfo::StaticRef { def_id, .. } = decl.local_info {
215 if self.tcx.is_mutable_static(def_id) {
217 "use of mutable static",
218 "mutable statics can be mutated by multiple threads: aliasing \
219 violations or data races will cause undefined behavior",
220 UnsafetyViolationKind::General,
223 } else if self.tcx.is_foreign_item(def_id) {
225 "use of extern static",
226 "extern statics are not controlled by the Rust type system: \
227 invalid data, aliasing violations or data races will cause \
229 UnsafetyViolationKind::General,
234 // Internal locals are used in the `move_val_init` desugaring.
235 // We want to check unsafety against the source info of the
236 // desugaring, rather than the source info of the RHS.
237 self.source_info = self.body.local_decls[place.local].source_info;
241 let base_ty = Place::ty_from(place.local, proj_base, self.body, self.tcx).ty;
243 ty::RawPtr(..) => self.require_unsafe(
244 "dereference of raw pointer",
245 "raw pointers may be NULL, dangling or unaligned; they can violate \
246 aliasing rules and cause data races: all of these are undefined \
248 UnsafetyViolationKind::General,
252 if context == PlaceContext::MutatingUse(MutatingUseContext::Store)
253 || context == PlaceContext::MutatingUse(MutatingUseContext::Drop)
254 || context == PlaceContext::MutatingUse(MutatingUseContext::AsmOutput)
256 let elem_ty = match elem {
257 ProjectionElem::Field(_, ty) => ty,
259 self.source_info.span,
260 "non-field projection {:?} from union?",
264 if !elem_ty.is_copy_modulo_regions(
267 self.source_info.span,
270 "assignment to non-`Copy` union field",
271 "the previous content of the field will be dropped, which \
272 causes undefined behavior if the field was not properly \
274 UnsafetyViolationKind::GeneralAndConstFn,
277 // write to non-move union, safe
281 "access to union field",
282 "the field may not be properly initialized: using \
283 uninitialized data will cause undefined behavior",
284 UnsafetyViolationKind::GeneralAndConstFn,
291 self.source_info = old_source_info;
296 impl<'a, 'tcx> UnsafetyChecker<'a, 'tcx> {
299 description: &'static str,
300 details: &'static str,
301 kind: UnsafetyViolationKind,
303 let source_info = self.source_info;
304 self.register_violations(
305 &[UnsafetyViolation {
307 description: Symbol::intern(description),
308 details: Symbol::intern(details),
315 fn register_violations(
317 violations: &[UnsafetyViolation],
318 unsafe_blocks: &[(hir::HirId, bool)],
320 let safety = self.body.source_scopes[self.source_info.scope]
323 .assert_crate_local()
325 let within_unsafe = match safety {
326 // `unsafe` blocks are required in safe code
328 for violation in violations {
329 let mut violation = *violation;
330 match violation.kind {
331 UnsafetyViolationKind::GeneralAndConstFn
332 | UnsafetyViolationKind::General => {}
333 UnsafetyViolationKind::BorrowPacked(_) => {
334 if self.min_const_fn {
335 // const fns don't need to be backwards compatible and can
336 // emit these violations as a hard error instead of a backwards
338 violation.kind = UnsafetyViolationKind::General;
342 if !self.violations.contains(&violation) {
343 self.violations.push(violation)
348 // `unsafe` function bodies allow unsafe without additional unsafe blocks
349 Safety::BuiltinUnsafe | Safety::FnUnsafe => true,
350 Safety::ExplicitUnsafe(hir_id) => {
351 // mark unsafe block as used if there are any unsafe operations inside
352 if !violations.is_empty() {
353 self.used_unsafe.insert(hir_id);
355 // only some unsafety is allowed in const fn
356 if self.min_const_fn {
357 for violation in violations {
358 match violation.kind {
359 // these unsafe things are stable in const fn
360 UnsafetyViolationKind::GeneralAndConstFn => {}
361 // these things are forbidden in const fns
362 UnsafetyViolationKind::General
363 | UnsafetyViolationKind::BorrowPacked(_) => {
364 let mut violation = *violation;
365 // const fns don't need to be backwards compatible and can
366 // emit these violations as a hard error instead of a backwards
368 violation.kind = UnsafetyViolationKind::General;
369 if !self.violations.contains(&violation) {
370 self.violations.push(violation)
379 self.inherited_blocks.extend(
380 unsafe_blocks.iter().map(|&(hir_id, is_used)| (hir_id, is_used && !within_unsafe)),
383 fn check_mut_borrowing_layout_constrained_field(
388 let mut cursor = place.projection.as_ref();
389 while let &[ref proj_base @ .., elem] = cursor {
393 // Modifications behind a dereference don't affect the value of
395 ProjectionElem::Deref => return,
396 ProjectionElem::Field(..) => {
398 Place::ty_from(place.local, proj_base, &self.body.local_decls, self.tcx).ty;
399 if let ty::Adt(def, _) = ty.kind {
400 if self.tcx.layout_scalar_valid_range(def.did)
401 != (Bound::Unbounded, Bound::Unbounded)
403 let (description, details) = if is_mut_use {
405 "mutation of layout constrained field",
406 "mutating layout constrained fields cannot statically be \
407 checked for valid values",
410 // Check `is_freeze` as late as possible to avoid cycle errors
411 // with opaque types.
412 } else if !place.ty(self.body, self.tcx).ty.is_freeze(
415 self.source_info.span,
418 "borrow of layout constrained field with interior \
420 "references to fields of layout constrained fields \
421 lose the constraints. Coupled with interior mutability, \
422 the field can be changed to invalid values",
430 UnsafetyViolationKind::GeneralAndConstFn,
441 pub(crate) fn provide(providers: &mut Providers<'_>) {
442 *providers = Providers { unsafety_check_result, unsafe_derive_on_repr_packed, ..*providers };
445 struct UnusedUnsafeVisitor<'a> {
446 used_unsafe: &'a FxHashSet<hir::HirId>,
447 unsafe_blocks: &'a mut Vec<(hir::HirId, bool)>,
450 impl<'a, 'tcx> intravisit::Visitor<'tcx> for UnusedUnsafeVisitor<'a> {
451 type Map = intravisit::ErasedMap<'tcx>;
453 fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
454 intravisit::NestedVisitorMap::None
457 fn visit_block(&mut self, block: &'tcx hir::Block<'tcx>) {
458 intravisit::walk_block(self, block);
460 if let hir::BlockCheckMode::UnsafeBlock(hir::UnsafeSource::UserProvided) = block.rules {
461 self.unsafe_blocks.push((block.hir_id, self.used_unsafe.contains(&block.hir_id)));
466 fn check_unused_unsafe(
469 used_unsafe: &FxHashSet<hir::HirId>,
470 unsafe_blocks: &mut Vec<(hir::HirId, bool)>,
473 tcx.hir().as_local_hir_id(def_id).and_then(|hir_id| tcx.hir().maybe_body_owned_by(hir_id));
475 let body_id = match body_id {
478 debug!("check_unused_unsafe({:?}) - no body found", def_id);
482 let body = tcx.hir().body(body_id);
483 debug!("check_unused_unsafe({:?}, body={:?}, used_unsafe={:?})", def_id, body, used_unsafe);
485 let mut visitor = UnusedUnsafeVisitor { used_unsafe, unsafe_blocks };
486 intravisit::Visitor::visit_body(&mut visitor, body);
489 fn unsafety_check_result(tcx: TyCtxt<'_>, def_id: DefId) -> UnsafetyCheckResult {
490 debug!("unsafety_violations({:?})", def_id);
492 // N.B., this borrow is valid because all the consumers of
493 // `mir_built` force this.
494 let body = &tcx.mir_built(def_id).borrow();
496 let param_env = tcx.param_env(def_id);
498 let id = tcx.hir().as_local_hir_id(def_id).unwrap();
499 let (const_context, min_const_fn) = match tcx.hir().body_owner_kind(id) {
500 hir::BodyOwnerKind::Closure => (false, false),
501 hir::BodyOwnerKind::Fn => (is_const_fn(tcx, def_id), is_min_const_fn(tcx, def_id)),
502 hir::BodyOwnerKind::Const | hir::BodyOwnerKind::Static(_) => (true, false),
504 let mut checker = UnsafetyChecker::new(const_context, min_const_fn, body, tcx, param_env);
505 // mir_built ensures that body has a computed cache, so we don't (and can't) attempt to
506 // recompute it here.
507 let body = body.unwrap_read_only();
508 checker.visit_body(&body);
510 check_unused_unsafe(tcx, def_id, &checker.used_unsafe, &mut checker.inherited_blocks);
511 UnsafetyCheckResult {
512 violations: checker.violations.into(),
513 unsafe_blocks: checker.inherited_blocks.into(),
517 fn unsafe_derive_on_repr_packed(tcx: TyCtxt<'_>, def_id: DefId) {
518 let lint_hir_id = tcx
520 .as_local_hir_id(def_id)
521 .unwrap_or_else(|| bug!("checking unsafety for non-local def id {:?}", def_id));
523 tcx.struct_span_lint_hir(SAFE_PACKED_BORROWS, lint_hir_id, tcx.def_span(def_id), |lint| {
524 // FIXME: when we make this a hard error, this should have its
526 let message = if tcx.generics_of(def_id).own_requires_monomorphization() {
527 "`#[derive]` can't be used on a `#[repr(packed)]` struct with \
528 type or const parameters (error E0133)"
531 "`#[derive]` can't be used on a `#[repr(packed)]` struct that \
532 does not derive Copy (error E0133)"
535 lint.build(&message).emit()
539 /// Returns the `HirId` for an enclosing scope that is also `unsafe`.
542 used_unsafe: &FxHashSet<hir::HirId>,
544 ) -> Option<(String, hir::HirId)> {
545 let parent_id = tcx.hir().get_parent_node(id);
547 if used_unsafe.contains(&parent_id) {
548 Some(("block".to_string(), parent_id))
549 } else if let Some(Node::Item(&hir::Item {
550 kind: hir::ItemKind::Fn(ref sig, _, _), ..
551 })) = tcx.hir().find(parent_id)
553 match sig.header.unsafety {
554 hir::Unsafety::Unsafe => Some(("fn".to_string(), parent_id)),
555 hir::Unsafety::Normal => None,
558 is_enclosed(tcx, used_unsafe, parent_id)
565 fn report_unused_unsafe(tcx: TyCtxt<'_>, used_unsafe: &FxHashSet<hir::HirId>, id: hir::HirId) {
566 let span = tcx.sess.source_map().guess_head_span(tcx.hir().span(id));
567 tcx.struct_span_lint_hir(UNUSED_UNSAFE, id, span, |lint| {
568 let msg = "unnecessary `unsafe` block";
569 let mut db = lint.build(msg);
570 db.span_label(span, msg);
571 if let Some((kind, id)) = is_enclosed(tcx, used_unsafe, id) {
573 tcx.sess.source_map().guess_head_span(tcx.hir().span(id)),
574 format!("because it's nested under this `unsafe` {}", kind),
581 fn builtin_derive_def_id(tcx: TyCtxt<'_>, def_id: DefId) -> Option<DefId> {
582 debug!("builtin_derive_def_id({:?})", def_id);
583 if let Some(impl_def_id) = tcx.impl_of_method(def_id) {
584 if tcx.has_attr(impl_def_id, sym::automatically_derived) {
585 debug!("builtin_derive_def_id({:?}) - is {:?}", def_id, impl_def_id);
588 debug!("builtin_derive_def_id({:?}) - not automatically derived", def_id);
592 debug!("builtin_derive_def_id({:?}) - not a method", def_id);
597 pub fn check_unsafety(tcx: TyCtxt<'_>, def_id: DefId) {
598 debug!("check_unsafety({:?})", def_id);
600 // closures are handled by their parent fn.
601 if tcx.is_closure(def_id) {
605 let UnsafetyCheckResult { violations, unsafe_blocks } = tcx.unsafety_check_result(def_id);
607 for &UnsafetyViolation { source_info, description, details, kind } in violations.iter() {
610 UnsafetyViolationKind::GeneralAndConstFn | UnsafetyViolationKind::General => {
615 "{} is unsafe and requires unsafe function or block",
618 .span_label(source_info.span, &*description.as_str())
619 .note(&details.as_str())
622 UnsafetyViolationKind::BorrowPacked(lint_hir_id) => {
623 if let Some(impl_def_id) = builtin_derive_def_id(tcx, def_id) {
624 tcx.unsafe_derive_on_repr_packed(impl_def_id);
626 tcx.struct_span_lint_hir(
632 "{} is unsafe and requires unsafe function or block (error E0133)",
635 .note(&details.as_str())
644 let (mut unsafe_used, mut unsafe_unused): (FxHashSet<_>, Vec<_>) = Default::default();
645 for &(block_id, is_used) in unsafe_blocks.iter() {
647 unsafe_used.insert(block_id);
649 unsafe_unused.push(block_id);
652 // The unused unsafe blocks might not be in source order; sort them so that the unused unsafe
653 // error messages are properly aligned and the issue-45107 and lint-unused-unsafe tests pass.
654 unsafe_unused.sort_by_cached_key(|hir_id| tcx.hir().span(*hir_id));
656 for &block_id in &unsafe_unused {
657 report_unused_unsafe(tcx, &unsafe_used, block_id);