1 use crate::build::ExprCategory;
2 use rustc_middle::thir::visit::{self, Visitor};
4 use rustc_errors::struct_span_err;
6 use rustc_middle::mir::BorrowKind;
7 use rustc_middle::thir::*;
8 use rustc_middle::ty::{self, ParamEnv, Ty, TyCtxt};
9 use rustc_session::lint::builtin::{UNSAFE_OP_IN_UNSAFE_FN, UNUSED_UNSAFE};
10 use rustc_session::lint::Level;
11 use rustc_span::def_id::{DefId, LocalDefId};
12 use rustc_span::symbol::Symbol;
18 struct UnsafetyVisitor<'a, 'tcx> {
21 /// The `HirId` of the current scope, which would be the `HirId`
22 /// of the current HIR node, modulo adjustments. Used for lint levels.
23 hir_context: hir::HirId,
24 /// The current "safety context". This notably tracks whether we are in an
25 /// `unsafe` block, and whether it has been used.
26 safety_context: SafetyContext,
27 body_unsafety: BodyUnsafety,
28 /// The `#[target_feature]` attributes of the body. Used for checking
29 /// calls to functions with `#[target_feature]` (RFC 2396).
30 body_target_features: &'tcx [Symbol],
31 /// When inside the LHS of an assignment to a field, this is the type
32 /// of the LHS and the span of the assignment expression.
33 assignment_info: Option<(Ty<'tcx>, Span)>,
34 in_union_destructure: bool,
35 param_env: ParamEnv<'tcx>,
39 impl<'tcx> UnsafetyVisitor<'_, 'tcx> {
40 fn in_safety_context(&mut self, safety_context: SafetyContext, f: impl FnOnce(&mut Self)) {
42 SafetyContext::UnsafeBlock { span: enclosing_span, .. },
43 SafetyContext::UnsafeBlock { span: block_span, hir_id, .. },
44 ) = (self.safety_context, safety_context)
46 self.warn_unused_unsafe(
49 Some((self.tcx.sess.source_map().guess_head_span(enclosing_span), "block")),
53 let prev_context = self.safety_context;
54 self.safety_context = safety_context;
58 if let SafetyContext::UnsafeBlock { used: false, span, hir_id } = self.safety_context {
59 self.warn_unused_unsafe(
62 if self.unsafe_op_in_unsafe_fn_allowed() {
63 self.body_unsafety.unsafe_fn_sig_span().map(|span| (span, "fn"))
69 self.safety_context = prev_context;
73 fn requires_unsafe(&mut self, span: Span, kind: UnsafeOpKind) {
74 let unsafe_op_in_unsafe_fn_allowed = self.unsafe_op_in_unsafe_fn_allowed();
75 match self.safety_context {
76 SafetyContext::BuiltinUnsafeBlock => {}
77 SafetyContext::UnsafeBlock { ref mut used, .. } => {
78 if !self.body_unsafety.is_unsafe() || !unsafe_op_in_unsafe_fn_allowed {
79 // Mark this block as useful
83 SafetyContext::UnsafeFn if unsafe_op_in_unsafe_fn_allowed => {}
84 SafetyContext::UnsafeFn => {
85 let (description, note) = kind.description_and_note(self.tcx);
86 // unsafe_op_in_unsafe_fn is disallowed
87 self.tcx.struct_span_lint_hir(
88 UNSAFE_OP_IN_UNSAFE_FN,
93 "{} is unsafe and requires unsafe block (error E0133)",
96 .span_label(span, kind.simple_description())
102 SafetyContext::Safe => {
103 let (description, note) = kind.description_and_note(self.tcx);
104 let fn_sugg = if unsafe_op_in_unsafe_fn_allowed { " function or" } else { "" };
109 "{} is unsafe and requires unsafe{} block",
113 .span_label(span, kind.simple_description())
120 fn warn_unused_unsafe(
124 enclosing_unsafe: Option<(Span, &'static str)>,
126 let block_span = self.tcx.sess.source_map().guess_head_span(block_span);
127 self.tcx.struct_span_lint_hir(UNUSED_UNSAFE, hir_id, block_span, |lint| {
128 let msg = "unnecessary `unsafe` block";
129 let mut db = lint.build(msg);
130 db.span_label(block_span, msg);
131 if let Some((span, kind)) = enclosing_unsafe {
132 db.span_label(span, format!("because it's nested under this `unsafe` {}", kind));
138 /// Whether the `unsafe_op_in_unsafe_fn` lint is `allow`ed at the current HIR node.
139 fn unsafe_op_in_unsafe_fn_allowed(&self) -> bool {
140 self.tcx.lint_level_at_node(UNSAFE_OP_IN_UNSAFE_FN, self.hir_context).0 == Level::Allow
144 // Searches for accesses to layout constrained fields.
145 struct LayoutConstrainedPlaceVisitor<'a, 'tcx> {
147 thir: &'a Thir<'tcx>,
151 impl<'a, 'tcx> LayoutConstrainedPlaceVisitor<'a, 'tcx> {
152 fn new(thir: &'a Thir<'tcx>, tcx: TyCtxt<'tcx>) -> Self {
153 Self { found: false, thir, tcx }
157 impl<'a, 'tcx> Visitor<'a, 'tcx> for LayoutConstrainedPlaceVisitor<'a, 'tcx> {
158 fn thir(&self) -> &'a Thir<'tcx> {
162 fn visit_expr(&mut self, expr: &Expr<'tcx>) {
164 ExprKind::Field { lhs, .. } => {
165 if let ty::Adt(adt_def, _) = self.thir[lhs].ty.kind() {
166 if (Bound::Unbounded, Bound::Unbounded)
167 != self.tcx.layout_scalar_valid_range(adt_def.did())
172 visit::walk_expr(self, expr);
175 // Keep walking through the expression as long as we stay in the same
176 // place, i.e. the expression is a place expression and not a dereference
177 // (since dereferencing something leads us to a different place).
178 ExprKind::Deref { .. } => {}
179 ref kind if ExprCategory::of(kind).map_or(true, |cat| cat == ExprCategory::Place) => {
180 visit::walk_expr(self, expr);
188 impl<'a, 'tcx> Visitor<'a, 'tcx> for UnsafetyVisitor<'a, 'tcx> {
189 fn thir(&self) -> &'a Thir<'tcx> {
193 fn visit_block(&mut self, block: &Block) {
194 match block.safety_mode {
195 // compiler-generated unsafe code should not count towards the usefulness of
196 // an outer unsafe block
197 BlockSafety::BuiltinUnsafe => {
198 self.in_safety_context(SafetyContext::BuiltinUnsafeBlock, |this| {
199 visit::walk_block(this, block)
202 BlockSafety::ExplicitUnsafe(hir_id) => {
203 self.in_safety_context(
204 SafetyContext::UnsafeBlock { span: block.span, hir_id, used: false },
205 |this| visit::walk_block(this, block),
208 BlockSafety::Safe => {
209 visit::walk_block(self, block);
214 fn visit_pat(&mut self, pat: &Pat<'tcx>) {
215 if self.in_union_destructure {
217 // binding to a variable allows getting stuff out of variable
218 PatKind::Binding { .. }
219 // match is conditional on having this value
220 | PatKind::Constant { .. }
221 | PatKind::Variant { .. }
222 | PatKind::Leaf { .. }
223 | PatKind::Deref { .. }
224 | PatKind::Range { .. }
225 | PatKind::Slice { .. }
226 | PatKind::Array { .. } => {
227 self.requires_unsafe(pat.span, AccessToUnionField);
228 return; // we can return here since this already requires unsafe
230 // wildcard doesn't take anything
232 // these just wrap other patterns
234 PatKind::AscribeUserType { .. } => {}
239 PatKind::Leaf { .. } => {
240 if let ty::Adt(adt_def, ..) = pat.ty.kind() {
241 if adt_def.is_union() {
242 let old_in_union_destructure =
243 std::mem::replace(&mut self.in_union_destructure, true);
244 visit::walk_pat(self, pat);
245 self.in_union_destructure = old_in_union_destructure;
246 } else if (Bound::Unbounded, Bound::Unbounded)
247 != self.tcx.layout_scalar_valid_range(adt_def.did())
249 let old_inside_adt = std::mem::replace(&mut self.inside_adt, true);
250 visit::walk_pat(self, pat);
251 self.inside_adt = old_inside_adt;
253 visit::walk_pat(self, pat);
256 visit::walk_pat(self, pat);
259 PatKind::Binding { mode: BindingMode::ByRef(borrow_kind), ty, .. } => {
261 let ty::Ref(_, ty, _) = ty.kind() else {
264 "BindingMode::ByRef in pattern, but found non-reference type {}",
269 BorrowKind::Shallow | BorrowKind::Shared | BorrowKind::Unique => {
270 if !ty.is_freeze(self.tcx.at(pat.span), self.param_env) {
271 self.requires_unsafe(pat.span, BorrowOfLayoutConstrainedField);
274 BorrowKind::Mut { .. } => {
275 self.requires_unsafe(pat.span, MutationOfLayoutConstrainedField);
279 visit::walk_pat(self, pat);
281 PatKind::Deref { .. } => {
282 let old_inside_adt = std::mem::replace(&mut self.inside_adt, false);
283 visit::walk_pat(self, pat);
284 self.inside_adt = old_inside_adt;
287 visit::walk_pat(self, pat);
292 fn visit_expr(&mut self, expr: &Expr<'tcx>) {
293 // could we be in the LHS of an assignment to a field?
295 ExprKind::Field { .. }
296 | ExprKind::VarRef { .. }
297 | ExprKind::UpvarRef { .. }
298 | ExprKind::Scope { .. }
299 | ExprKind::Cast { .. } => {}
301 ExprKind::AddressOf { .. }
302 | ExprKind::Adt { .. }
303 | ExprKind::Array { .. }
304 | ExprKind::Binary { .. }
305 | ExprKind::Block { .. }
306 | ExprKind::Borrow { .. }
307 | ExprKind::Literal { .. }
308 | ExprKind::NamedConst { .. }
309 | ExprKind::NonHirLiteral { .. }
310 | ExprKind::ZstLiteral { .. }
311 | ExprKind::ConstParam { .. }
312 | ExprKind::ConstBlock { .. }
313 | ExprKind::Deref { .. }
314 | ExprKind::Index { .. }
315 | ExprKind::NeverToAny { .. }
316 | ExprKind::PlaceTypeAscription { .. }
317 | ExprKind::ValueTypeAscription { .. }
318 | ExprKind::Pointer { .. }
319 | ExprKind::Repeat { .. }
320 | ExprKind::StaticRef { .. }
321 | ExprKind::ThreadLocalRef { .. }
322 | ExprKind::Tuple { .. }
323 | ExprKind::Unary { .. }
324 | ExprKind::Call { .. }
325 | ExprKind::Assign { .. }
326 | ExprKind::AssignOp { .. }
327 | ExprKind::Break { .. }
328 | ExprKind::Closure { .. }
329 | ExprKind::Continue { .. }
330 | ExprKind::Return { .. }
331 | ExprKind::Yield { .. }
332 | ExprKind::Loop { .. }
333 | ExprKind::Let { .. }
334 | ExprKind::Match { .. }
335 | ExprKind::Box { .. }
336 | ExprKind::If { .. }
337 | ExprKind::InlineAsm { .. }
338 | ExprKind::LogicalOp { .. }
339 | ExprKind::Use { .. } => {
340 // We don't need to save the old value and restore it
341 // because all the place expressions can't have more
343 self.assignment_info = None;
347 ExprKind::Scope { value, lint_level: LintLevel::Explicit(hir_id), region_scope: _ } => {
348 let prev_id = self.hir_context;
349 self.hir_context = hir_id;
350 self.visit_expr(&self.thir[value]);
351 self.hir_context = prev_id;
352 return; // don't visit the whole expression
354 ExprKind::Call { fun, ty: _, args: _, from_hir_call: _, fn_span: _ } => {
355 if self.thir[fun].ty.fn_sig(self.tcx).unsafety() == hir::Unsafety::Unsafe {
356 let func_id = if let ty::FnDef(func_id, _) = self.thir[fun].ty.kind() {
361 self.requires_unsafe(expr.span, CallToUnsafeFunction(func_id));
362 } else if let &ty::FnDef(func_did, _) = self.thir[fun].ty.kind() {
363 // If the called function has target features the calling function hasn't,
364 // the call requires `unsafe`. Don't check this on wasm
365 // targets, though. For more information on wasm see the
366 // is_like_wasm check in typeck/src/collect.rs
367 if !self.tcx.sess.target.options.is_like_wasm
370 .codegen_fn_attrs(func_did)
373 .all(|feature| self.body_target_features.contains(feature))
375 self.requires_unsafe(expr.span, CallToFunctionWith(func_did));
379 ExprKind::Deref { arg } => {
380 if let ExprKind::StaticRef { def_id, .. } = self.thir[arg].kind {
381 if self.tcx.is_mutable_static(def_id) {
382 self.requires_unsafe(expr.span, UseOfMutableStatic);
383 } else if self.tcx.is_foreign_item(def_id) {
384 self.requires_unsafe(expr.span, UseOfExternStatic);
386 } else if self.thir[arg].ty.is_unsafe_ptr() {
387 self.requires_unsafe(expr.span, DerefOfRawPointer);
390 ExprKind::InlineAsm { .. } => {
391 self.requires_unsafe(expr.span, UseOfInlineAssembly);
393 ExprKind::Adt(box Adt {
400 }) => match self.tcx.layout_scalar_valid_range(adt_def.did()) {
401 (Bound::Unbounded, Bound::Unbounded) => {}
402 _ => self.requires_unsafe(expr.span, InitializingTypeWith),
411 let closure_id = closure_id.expect_local();
412 let closure_def = if let Some((did, const_param_id)) =
413 ty::WithOptConstParam::try_lookup(closure_id, self.tcx)
415 ty::WithOptConstParam { did, const_param_did: Some(const_param_id) }
417 ty::WithOptConstParam::unknown(closure_id)
419 let (closure_thir, expr) = self.tcx.thir_body(closure_def).unwrap_or_else(|_| {
420 (self.tcx.alloc_steal_thir(Thir::new()), ExprId::from_u32(0))
422 let closure_thir = &closure_thir.borrow();
423 let hir_context = self.tcx.hir().local_def_id_to_hir_id(closure_id);
424 let mut closure_visitor =
425 UnsafetyVisitor { thir: closure_thir, hir_context, ..*self };
426 closure_visitor.visit_expr(&closure_thir[expr]);
427 // Unsafe blocks can be used in closures, make sure to take it into account
428 self.safety_context = closure_visitor.safety_context;
430 ExprKind::Field { lhs, .. } => {
431 let lhs = &self.thir[lhs];
432 if let ty::Adt(adt_def, _) = lhs.ty.kind() && adt_def.is_union() {
433 if let Some((assigned_ty, assignment_span)) = self.assignment_info {
434 // To avoid semver hazard, we only consider `Copy` and `ManuallyDrop` non-dropping.
437 .map_or(false, |adt| adt.is_manually_drop())
439 .is_copy_modulo_regions(self.tcx.at(expr.span), self.param_env))
441 self.requires_unsafe(assignment_span, AssignToDroppingUnionField);
443 // write to non-drop union field, safe
446 self.requires_unsafe(expr.span, AccessToUnionField);
450 ExprKind::Assign { lhs, rhs } | ExprKind::AssignOp { lhs, rhs, .. } => {
451 let lhs = &self.thir[lhs];
452 // First, check whether we are mutating a layout constrained field
453 let mut visitor = LayoutConstrainedPlaceVisitor::new(self.thir, self.tcx);
454 visit::walk_expr(&mut visitor, lhs);
456 self.requires_unsafe(expr.span, MutationOfLayoutConstrainedField);
459 // Second, check for accesses to union fields
460 // don't have any special handling for AssignOp since it causes a read *and* write to lhs
461 if matches!(expr.kind, ExprKind::Assign { .. }) {
462 self.assignment_info = Some((lhs.ty, expr.span));
463 visit::walk_expr(self, lhs);
464 self.assignment_info = None;
465 visit::walk_expr(self, &self.thir()[rhs]);
466 return; // we have already visited everything by now
469 ExprKind::Borrow { borrow_kind, arg } => {
470 let mut visitor = LayoutConstrainedPlaceVisitor::new(self.thir, self.tcx);
471 visit::walk_expr(&mut visitor, expr);
474 BorrowKind::Shallow | BorrowKind::Shared | BorrowKind::Unique
477 .is_freeze(self.tcx.at(self.thir[arg].span), self.param_env) =>
479 self.requires_unsafe(expr.span, BorrowOfLayoutConstrainedField)
481 BorrowKind::Mut { .. } => {
482 self.requires_unsafe(expr.span, MutationOfLayoutConstrainedField)
484 BorrowKind::Shallow | BorrowKind::Shared | BorrowKind::Unique => {}
488 ExprKind::Let { expr: expr_id, .. } => {
489 let let_expr = &self.thir[expr_id];
490 if let ty::Adt(adt_def, _) = let_expr.ty.kind() && adt_def.is_union() {
491 self.requires_unsafe(expr.span, AccessToUnionField);
496 visit::walk_expr(self, expr);
500 #[derive(Clone, Copy)]
505 UnsafeBlock { span: Span, hir_id: hir::HirId, used: bool },
508 #[derive(Clone, Copy)]
510 /// The body is not unsafe.
512 /// The body is an unsafe function. The span points to
513 /// the signature of the function.
518 /// Returns whether the body is unsafe.
519 fn is_unsafe(&self) -> bool {
520 matches!(self, BodyUnsafety::Unsafe(_))
523 /// If the body is unsafe, returns the `Span` of its signature.
524 fn unsafe_fn_sig_span(self) -> Option<Span> {
526 BodyUnsafety::Unsafe(span) => Some(span),
527 BodyUnsafety::Safe => None,
532 #[derive(Clone, Copy, PartialEq)]
534 CallToUnsafeFunction(Option<DefId>),
536 InitializingTypeWith,
540 AssignToDroppingUnionField,
542 MutationOfLayoutConstrainedField,
543 BorrowOfLayoutConstrainedField,
544 CallToFunctionWith(DefId),
550 pub fn simple_description(&self) -> &'static str {
552 CallToUnsafeFunction(..) => "call to unsafe function",
553 UseOfInlineAssembly => "use of inline assembly",
554 InitializingTypeWith => "initializing type with `rustc_layout_scalar_valid_range` attr",
555 UseOfMutableStatic => "use of mutable static",
556 UseOfExternStatic => "use of extern static",
557 DerefOfRawPointer => "dereference of raw pointer",
558 AssignToDroppingUnionField => "assignment to union field that might need dropping",
559 AccessToUnionField => "access to union field",
560 MutationOfLayoutConstrainedField => "mutation of layout constrained field",
561 BorrowOfLayoutConstrainedField => {
562 "borrow of layout constrained field with interior mutability"
564 CallToFunctionWith(..) => "call to function with `#[target_feature]`",
568 pub fn description_and_note(&self, tcx: TyCtxt<'_>) -> (Cow<'static, str>, &'static str) {
570 CallToUnsafeFunction(did) => (
571 if let Some(did) = did {
572 Cow::from(format!("call to unsafe function `{}`", tcx.def_path_str(*did)))
574 Cow::Borrowed(self.simple_description())
576 "consult the function's documentation for information on how to avoid undefined \
579 UseOfInlineAssembly => (
580 Cow::Borrowed(self.simple_description()),
581 "inline assembly is entirely unchecked and can cause undefined behavior",
583 InitializingTypeWith => (
584 Cow::Borrowed(self.simple_description()),
585 "initializing a layout restricted type's field with a value outside the valid \
586 range is undefined behavior",
588 UseOfMutableStatic => (
589 Cow::Borrowed(self.simple_description()),
590 "mutable statics can be mutated by multiple threads: aliasing violations or data \
591 races will cause undefined behavior",
593 UseOfExternStatic => (
594 Cow::Borrowed(self.simple_description()),
595 "extern statics are not controlled by the Rust type system: invalid data, \
596 aliasing violations or data races will cause undefined behavior",
598 DerefOfRawPointer => (
599 Cow::Borrowed(self.simple_description()),
600 "raw pointers may be null, dangling or unaligned; they can violate aliasing rules \
601 and cause data races: all of these are undefined behavior",
603 AssignToDroppingUnionField => (
604 Cow::Borrowed(self.simple_description()),
605 "the previous content of the field will be dropped, which causes undefined \
606 behavior if the field was not properly initialized",
608 AccessToUnionField => (
609 Cow::Borrowed(self.simple_description()),
610 "the field may not be properly initialized: using uninitialized data will cause \
613 MutationOfLayoutConstrainedField => (
614 Cow::Borrowed(self.simple_description()),
615 "mutating layout constrained fields cannot statically be checked for valid values",
617 BorrowOfLayoutConstrainedField => (
618 Cow::Borrowed(self.simple_description()),
619 "references to fields of layout constrained fields lose the constraints. Coupled \
620 with interior mutability, the field can be changed to invalid values",
622 CallToFunctionWith(did) => (
624 "call to function `{}` with `#[target_feature]`",
625 tcx.def_path_str(*did)
627 "can only be called if the required target features are available",
633 pub fn check_unsafety<'tcx>(tcx: TyCtxt<'tcx>, def: ty::WithOptConstParam<LocalDefId>) {
634 // THIR unsafeck is gated under `-Z thir-unsafeck`
635 if !tcx.sess.opts.debugging_opts.thir_unsafeck {
639 // Closures are handled by their owner, if it has a body
640 if tcx.is_closure(def.did.to_def_id()) {
642 let owner = hir.enclosing_body_owner(hir.local_def_id_to_hir_id(def.did));
643 tcx.ensure().thir_check_unsafety(hir.local_def_id(owner));
647 let Ok((thir, expr)) = tcx.thir_body(def) else {
650 let thir = &thir.borrow();
651 // If `thir` is empty, a type error occurred, skip this body.
652 if thir.exprs.is_empty() {
656 let hir_id = tcx.hir().local_def_id_to_hir_id(def.did);
657 let body_unsafety = tcx.hir().fn_sig_by_hir_id(hir_id).map_or(BodyUnsafety::Safe, |fn_sig| {
658 if fn_sig.header.unsafety == hir::Unsafety::Unsafe {
659 BodyUnsafety::Unsafe(fn_sig.span)
664 let body_target_features = &tcx.body_codegen_attrs(def.did.to_def_id()).target_features;
666 if body_unsafety.is_unsafe() { SafetyContext::UnsafeFn } else { SafetyContext::Safe };
667 let mut visitor = UnsafetyVisitor {
673 body_target_features,
674 assignment_info: None,
675 in_union_destructure: false,
676 param_env: tcx.param_env(def.did),
679 visitor.visit_expr(&thir[expr]);
682 pub(crate) fn thir_check_unsafety<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) {
683 if let Some(def) = ty::WithOptConstParam::try_lookup(def_id, tcx) {
684 tcx.thir_check_unsafety_for_const_arg(def)
686 check_unsafety(tcx, ty::WithOptConstParam::unknown(def_id))
690 pub(crate) fn thir_check_unsafety_for_const_arg<'tcx>(
692 (did, param_did): (LocalDefId, DefId),
694 check_unsafety(tcx, ty::WithOptConstParam { did, const_param_did: Some(param_did) })