1 use rustc_data_structures::fx::FxHashSet;
3 use rustc::ty::query::Providers;
4 use rustc::ty::{self, TyCtxt};
5 use rustc::ty::cast::CastTy;
8 use rustc::hir::def_id::DefId;
9 use rustc::lint::builtin::{SAFE_PACKED_BORROWS, UNUSED_UNSAFE};
11 use rustc::mir::visit::{PlaceContext, Visitor, MutatingUseContext};
13 use syntax::symbol::{Symbol, sym};
19 use rustc_error_codes::*;
21 pub struct UnsafetyChecker<'a, 'tcx> {
25 violations: Vec<UnsafetyViolation>,
26 source_info: SourceInfo,
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)>,
34 impl<'a, 'tcx> UnsafetyChecker<'a, 'tcx> {
40 param_env: ty::ParamEnv<'tcx>,
44 assert!(const_context);
51 source_info: SourceInfo {
53 scope: OUTERMOST_SOURCE_SCOPE
57 used_unsafe: Default::default(),
58 inherited_blocks: vec![],
63 impl<'a, 'tcx> Visitor<'tcx> for UnsafetyChecker<'a, 'tcx> {
64 fn visit_terminator(&mut self,
65 terminator: &Terminator<'tcx>,
68 self.source_info = terminator.source_info;
69 match terminator.kind {
70 TerminatorKind::Goto { .. } |
71 TerminatorKind::SwitchInt { .. } |
72 TerminatorKind::Drop { .. } |
73 TerminatorKind::Yield { .. } |
74 TerminatorKind::Assert { .. } |
75 TerminatorKind::DropAndReplace { .. } |
76 TerminatorKind::GeneratorDrop |
77 TerminatorKind::Resume |
78 TerminatorKind::Abort |
79 TerminatorKind::Return |
80 TerminatorKind::Unreachable |
81 TerminatorKind::FalseEdges { .. } |
82 TerminatorKind::FalseUnwind { .. } => {
83 // safe (at least as emitted during MIR construction)
86 TerminatorKind::Call { ref func, .. } => {
87 let func_ty = func.ty(self.body, self.tcx);
88 let sig = func_ty.fn_sig(self.tcx);
89 if let hir::Unsafety::Unsafe = sig.unsafety() {
90 self.require_unsafe("call to unsafe function",
91 "consult the function's documentation for information on how to avoid \
92 undefined behavior", UnsafetyViolationKind::GeneralAndConstFn)
96 self.super_terminator(terminator, location);
99 fn visit_statement(&mut self,
100 statement: &Statement<'tcx>,
103 self.source_info = statement.source_info;
104 match statement.kind {
105 StatementKind::Assign(..) |
106 StatementKind::FakeRead(..) |
107 StatementKind::SetDiscriminant { .. } |
108 StatementKind::StorageLive(..) |
109 StatementKind::StorageDead(..) |
110 StatementKind::Retag { .. } |
111 StatementKind::AscribeUserType(..) |
112 StatementKind::Nop => {
113 // safe (at least as emitted during MIR construction)
116 StatementKind::InlineAsm { .. } => {
117 self.require_unsafe("use of inline assembly",
118 "inline assembly is entirely unchecked and can cause undefined behavior",
119 UnsafetyViolationKind::General)
122 self.super_statement(statement, location);
125 fn visit_rvalue(&mut self,
126 rvalue: &Rvalue<'tcx>,
130 Rvalue::Aggregate(box ref aggregate, _) => {
132 &AggregateKind::Array(..) |
133 &AggregateKind::Tuple => {}
134 &AggregateKind::Adt(ref def, ..) => {
135 match self.tcx.layout_scalar_valid_range(def.did) {
136 (Bound::Unbounded, Bound::Unbounded) => {},
137 _ => self.require_unsafe(
138 "initializing type with `rustc_layout_scalar_valid_range` attr",
139 "initializing a layout restricted type's field with a value \
140 outside the valid range is undefined behavior",
141 UnsafetyViolationKind::GeneralAndConstFn,
145 &AggregateKind::Closure(def_id, _) |
146 &AggregateKind::Generator(def_id, _, _) => {
147 let UnsafetyCheckResult {
148 violations, unsafe_blocks
149 } = self.tcx.unsafety_check_result(def_id);
150 self.register_violations(&violations, &unsafe_blocks);
154 // casting pointers to ints is unsafe in const fn because the const evaluator cannot
155 // possibly know what the result of various operations like `address / 2` would be
156 // pointers during const evaluation have no integral address, only an abstract one
157 Rvalue::Cast(CastKind::Misc, ref operand, cast_ty)
158 if self.const_context && self.tcx.features().const_raw_ptr_to_usize_cast => {
159 let operand_ty = operand.ty(self.body, self.tcx);
160 let cast_in = CastTy::from_ty(operand_ty).expect("bad input type for cast");
161 let cast_out = CastTy::from_ty(cast_ty).expect("bad output type for cast");
162 match (cast_in, cast_out) {
163 (CastTy::Ptr(_), CastTy::Int(_)) |
164 (CastTy::FnPtr, CastTy::Int(_)) => {
165 self.register_violations(&[UnsafetyViolation {
166 source_info: self.source_info,
167 description: Symbol::intern("cast of pointer to int"),
168 details: Symbol::intern("casting pointers to integers in constants"),
169 kind: UnsafetyViolationKind::General,
175 // raw pointer and fn pointer operations are unsafe as it is not clear whether one
176 // pointer would be "less" or "equal" to another, because we cannot know where llvm
177 // or the linker will place various statics in memory. Without this information the
178 // result of a comparison of addresses would differ between runtime and compile-time.
179 Rvalue::BinaryOp(_, ref lhs, _)
180 if self.const_context && self.tcx.features().const_compare_raw_pointers => {
181 if let ty::RawPtr(_) | ty::FnPtr(..) = lhs.ty(self.body, self.tcx).kind {
182 self.register_violations(&[UnsafetyViolation {
183 source_info: self.source_info,
184 description: Symbol::intern("pointer operation"),
185 details: Symbol::intern("operations on pointers in constants"),
186 kind: UnsafetyViolationKind::General,
192 self.super_rvalue(rvalue, location);
195 fn visit_place(&mut self,
197 context: PlaceContext,
198 _location: Location) {
200 PlaceBase::Local(..) => {
203 PlaceBase::Static(box Static { kind: StaticKind::Promoted(_, _), .. }) => {
204 bug!("unsafety checking should happen before promotion");
206 PlaceBase::Static(box Static { kind: StaticKind::Static, .. }) => {
207 bug!("StaticKind::Static should not exist");
211 for (i, elem) in place.projection.iter().enumerate() {
212 let proj_base = &place.projection[..i];
214 if context.is_borrow() {
215 if util::is_disaligned(self.tcx, self.body, self.param_env, place) {
216 let source_info = self.source_info;
217 let lint_root = self.body.source_scopes[source_info.scope]
220 .assert_crate_local()
222 self.register_violations(&[UnsafetyViolation {
224 description: Symbol::intern("borrow of packed field"),
225 details: Symbol::intern(
226 "fields of packed structs might be misaligned: dereferencing a \
227 misaligned pointer or even just creating a misaligned reference \
228 is undefined behavior"),
229 kind: UnsafetyViolationKind::BorrowPacked(lint_root)
233 let is_borrow_of_interior_mut = context.is_borrow() &&
234 !Place::ty_from(&place.base, proj_base, self.body, self.tcx)
236 .is_freeze(self.tcx, self.param_env, self.source_info.span);
240 // * `&x.field` if `field`'s type has interior mutability
241 // because either of these would allow modifying the layout constrained field and
242 // insert values that violate the layout constraints.
243 if context.is_mutating_use() || is_borrow_of_interior_mut {
244 self.check_mut_borrowing_layout_constrained_field(
245 place, context.is_mutating_use(),
248 let old_source_info = self.source_info;
249 if let (PlaceBase::Local(local), []) = (&place.base, proj_base) {
250 let decl = &self.body.local_decls[*local];
252 // Internal locals are used in the `move_val_init` desugaring.
253 // We want to check unsafety against the source info of the
254 // desugaring, rather than the source info of the RHS.
255 self.source_info = self.body.local_decls[*local].source_info;
256 } else if let LocalInfo::StaticRef { def_id, .. } = decl.local_info {
257 if self.tcx.is_mutable_static(def_id) {
259 "use of mutable static",
260 "mutable statics can be mutated by multiple threads: aliasing \
261 violations or data races will cause undefined behavior",
262 UnsafetyViolationKind::General,
265 } else if self.tcx.is_foreign_item(def_id) {
267 "use of extern static",
268 "extern statics are not controlled by the Rust type system: \
269 invalid data, aliasing violations or data races will cause \
271 UnsafetyViolationKind::General,
277 let base_ty = Place::ty_from(&place.base, proj_base, self.body, self.tcx).ty;
280 self.require_unsafe("dereference of raw pointer",
281 "raw pointers may be NULL, dangling or unaligned; they can violate \
282 aliasing rules and cause data races: all of these are undefined \
283 behavior", UnsafetyViolationKind::General)
287 if context == PlaceContext::MutatingUse(MutatingUseContext::Store) ||
288 context == PlaceContext::MutatingUse(MutatingUseContext::Drop) ||
289 context == PlaceContext::MutatingUse(
290 MutatingUseContext::AsmOutput
293 let elem_ty = match elem {
294 ProjectionElem::Field(_, ty) => ty,
296 self.source_info.span,
297 "non-field projection {:?} from union?",
300 if !elem_ty.is_copy_modulo_regions(
303 self.source_info.span,
306 "assignment to non-`Copy` union field",
307 "the previous content of the field will be dropped, which \
308 causes undefined behavior if the field was not properly \
309 initialized", UnsafetyViolationKind::GeneralAndConstFn)
311 // write to non-move union, safe
314 self.require_unsafe("access to union field",
315 "the field may not be properly initialized: using \
316 uninitialized data will cause undefined behavior",
317 UnsafetyViolationKind::GeneralAndConstFn)
323 self.source_info = old_source_info;
328 impl<'a, 'tcx> UnsafetyChecker<'a, 'tcx> {
331 description: &'static str,
332 details: &'static str,
333 kind: UnsafetyViolationKind,
335 let source_info = self.source_info;
336 self.register_violations(&[UnsafetyViolation {
338 description: Symbol::intern(description),
339 details: Symbol::intern(details),
344 fn register_violations(&mut self,
345 violations: &[UnsafetyViolation],
346 unsafe_blocks: &[(hir::HirId, bool)]) {
347 let safety = self.body.source_scopes[self.source_info.scope]
350 .assert_crate_local()
352 let within_unsafe = match safety {
353 // `unsafe` blocks are required in safe code
355 for violation in violations {
356 let mut violation = violation.clone();
357 match violation.kind {
358 UnsafetyViolationKind::GeneralAndConstFn |
359 UnsafetyViolationKind::General => {},
360 UnsafetyViolationKind::BorrowPacked(_) => if self.min_const_fn {
361 // const fns don't need to be backwards compatible and can
362 // emit these violations as a hard error instead of a backwards
364 violation.kind = UnsafetyViolationKind::General;
367 if !self.violations.contains(&violation) {
368 self.violations.push(violation)
373 // `unsafe` function bodies allow unsafe without additional unsafe blocks
374 Safety::BuiltinUnsafe | Safety::FnUnsafe => true,
375 Safety::ExplicitUnsafe(hir_id) => {
376 // mark unsafe block as used if there are any unsafe operations inside
377 if !violations.is_empty() {
378 self.used_unsafe.insert(hir_id);
380 // only some unsafety is allowed in const fn
381 if self.min_const_fn {
382 for violation in violations {
383 match violation.kind {
384 // these unsafe things are stable in const fn
385 UnsafetyViolationKind::GeneralAndConstFn => {},
386 // these things are forbidden in const fns
387 UnsafetyViolationKind::General |
388 UnsafetyViolationKind::BorrowPacked(_) => {
389 let mut violation = violation.clone();
390 // const fns don't need to be backwards compatible and can
391 // emit these violations as a hard error instead of a backwards
393 violation.kind = UnsafetyViolationKind::General;
394 if !self.violations.contains(&violation) {
395 self.violations.push(violation)
404 self.inherited_blocks.extend(unsafe_blocks.iter().map(|&(hir_id, is_used)| {
405 (hir_id, is_used && !within_unsafe)
408 fn check_mut_borrowing_layout_constrained_field(
413 let mut cursor = place.projection.as_ref();
414 while let &[ref proj_base @ .., elem] = cursor {
418 ProjectionElem::Field(..) => {
420 Place::ty_from(&place.base, proj_base, &self.body.local_decls, self.tcx)
423 ty::Adt(def, _) => match self.tcx.layout_scalar_valid_range(def.did) {
424 (Bound::Unbounded, Bound::Unbounded) => {},
426 let (description, details) = if is_mut_use {
428 "mutation of layout constrained field",
429 "mutating layout constrained fields cannot statically be \
430 checked for valid values",
434 "borrow of layout constrained field with interior \
436 "references to fields of layout constrained fields \
437 lose the constraints. Coupled with interior mutability, \
438 the field can be changed to invalid values",
441 let source_info = self.source_info;
442 self.register_violations(&[UnsafetyViolation {
444 description: Symbol::intern(description),
445 details: Symbol::intern(details),
446 kind: UnsafetyViolationKind::GeneralAndConstFn,
459 pub(crate) fn provide(providers: &mut Providers<'_>) {
460 *providers = Providers {
461 unsafety_check_result,
462 unsafe_derive_on_repr_packed,
467 struct UnusedUnsafeVisitor<'a> {
468 used_unsafe: &'a FxHashSet<hir::HirId>,
469 unsafe_blocks: &'a mut Vec<(hir::HirId, bool)>,
472 impl<'a, 'tcx> hir::intravisit::Visitor<'tcx> for UnusedUnsafeVisitor<'a> {
473 fn nested_visit_map<'this>(&'this mut self) ->
474 hir::intravisit::NestedVisitorMap<'this, 'tcx>
476 hir::intravisit::NestedVisitorMap::None
479 fn visit_block(&mut self, block: &'tcx hir::Block) {
480 hir::intravisit::walk_block(self, block);
482 if let hir::UnsafeBlock(hir::UserProvided) = block.rules {
483 self.unsafe_blocks.push((block.hir_id, self.used_unsafe.contains(&block.hir_id)));
488 fn check_unused_unsafe(
491 used_unsafe: &FxHashSet<hir::HirId>,
492 unsafe_blocks: &mut Vec<(hir::HirId, bool)>,
495 tcx.hir().as_local_hir_id(def_id).and_then(|hir_id| {
496 tcx.hir().maybe_body_owned_by(hir_id)
499 let body_id = match body_id {
502 debug!("check_unused_unsafe({:?}) - no body found", def_id);
506 let body = tcx.hir().body(body_id);
507 debug!("check_unused_unsafe({:?}, body={:?}, used_unsafe={:?})",
508 def_id, body, used_unsafe);
510 let mut visitor = UnusedUnsafeVisitor { used_unsafe, unsafe_blocks };
511 hir::intravisit::Visitor::visit_body(&mut visitor, body);
514 fn unsafety_check_result(tcx: TyCtxt<'_>, def_id: DefId) -> UnsafetyCheckResult {
515 debug!("unsafety_violations({:?})", def_id);
517 // N.B., this borrow is valid because all the consumers of
518 // `mir_built` force this.
519 let body = &tcx.mir_built(def_id).borrow();
521 let param_env = tcx.param_env(def_id);
523 let id = tcx.hir().as_local_hir_id(def_id).unwrap();
524 let (const_context, min_const_fn) = match tcx.hir().body_owner_kind(id) {
525 hir::BodyOwnerKind::Closure => (false, false),
526 hir::BodyOwnerKind::Fn => (tcx.is_const_fn(def_id), tcx.is_min_const_fn(def_id)),
527 hir::BodyOwnerKind::Const |
528 hir::BodyOwnerKind::Static(_) => (true, false),
530 let mut checker = UnsafetyChecker::new(const_context, min_const_fn, body, tcx, param_env);
531 checker.visit_body(body.read_only());
533 check_unused_unsafe(tcx, def_id, &checker.used_unsafe, &mut checker.inherited_blocks);
534 UnsafetyCheckResult {
535 violations: checker.violations.into(),
536 unsafe_blocks: checker.inherited_blocks.into()
540 fn unsafe_derive_on_repr_packed(tcx: TyCtxt<'_>, def_id: DefId) {
541 let lint_hir_id = tcx.hir().as_local_hir_id(def_id).unwrap_or_else(||
542 bug!("checking unsafety for non-local def id {:?}", def_id));
544 // FIXME: when we make this a hard error, this should have its
546 let message = if tcx.generics_of(def_id).own_requires_monomorphization() {
547 "`#[derive]` can't be used on a `#[repr(packed)]` struct with \
548 type or const parameters (error E0133)".to_string()
550 "`#[derive]` can't be used on a `#[repr(packed)]` struct that \
551 does not derive Copy (error E0133)".to_string()
553 tcx.lint_hir(SAFE_PACKED_BORROWS,
555 tcx.def_span(def_id),
559 /// Returns the `HirId` for an enclosing scope that is also `unsafe`.
562 used_unsafe: &FxHashSet<hir::HirId>,
564 ) -> Option<(String, hir::HirId)> {
565 let parent_id = tcx.hir().get_parent_node(id);
567 if used_unsafe.contains(&parent_id) {
568 Some(("block".to_string(), parent_id))
569 } else if let Some(Node::Item(&hir::Item {
570 kind: hir::ItemKind::Fn(ref sig, _, _),
572 })) = tcx.hir().find(parent_id) {
573 match sig.header.unsafety {
574 hir::Unsafety::Unsafe => Some(("fn".to_string(), parent_id)),
575 hir::Unsafety::Normal => None,
578 is_enclosed(tcx, used_unsafe, parent_id)
585 fn report_unused_unsafe(tcx: TyCtxt<'_>, used_unsafe: &FxHashSet<hir::HirId>, id: hir::HirId) {
586 let span = tcx.sess.source_map().def_span(tcx.hir().span(id));
587 let msg = "unnecessary `unsafe` block";
588 let mut db = tcx.struct_span_lint_hir(UNUSED_UNSAFE, id, span, msg);
589 db.span_label(span, msg);
590 if let Some((kind, id)) = is_enclosed(tcx, used_unsafe, id) {
591 db.span_label(tcx.sess.source_map().def_span(tcx.hir().span(id)),
592 format!("because it's nested under this `unsafe` {}", kind));
597 fn builtin_derive_def_id(tcx: TyCtxt<'_>, def_id: DefId) -> Option<DefId> {
598 debug!("builtin_derive_def_id({:?})", def_id);
599 if let Some(impl_def_id) = tcx.impl_of_method(def_id) {
600 if tcx.has_attr(impl_def_id, sym::automatically_derived) {
601 debug!("builtin_derive_def_id({:?}) - is {:?}", def_id, impl_def_id);
604 debug!("builtin_derive_def_id({:?}) - not automatically derived", def_id);
608 debug!("builtin_derive_def_id({:?}) - not a method", def_id);
613 pub fn check_unsafety(tcx: TyCtxt<'_>, def_id: DefId) {
614 debug!("check_unsafety({:?})", def_id);
616 // closures are handled by their parent fn.
617 if tcx.is_closure(def_id) {
621 let UnsafetyCheckResult {
624 } = tcx.unsafety_check_result(def_id);
626 for &UnsafetyViolation {
627 source_info, description, details, kind
628 } in violations.iter() {
631 UnsafetyViolationKind::GeneralAndConstFn |
632 UnsafetyViolationKind::General => {
634 tcx.sess, source_info.span, E0133,
635 "{} is unsafe and requires unsafe function or block", description)
636 .span_label(source_info.span, &*description.as_str())
637 .note(&details.as_str())
640 UnsafetyViolationKind::BorrowPacked(lint_hir_id) => {
641 if let Some(impl_def_id) = builtin_derive_def_id(tcx, def_id) {
642 tcx.unsafe_derive_on_repr_packed(impl_def_id);
644 tcx.lint_node_note(SAFE_PACKED_BORROWS,
647 &format!("{} is unsafe and requires unsafe function or block \
648 (error E0133)", description),
655 let mut unsafe_blocks: Vec<_> = unsafe_blocks.into_iter().collect();
656 unsafe_blocks.sort_by_cached_key(|(hir_id, _)| tcx.hir().hir_to_node_id(*hir_id));
657 let used_unsafe: FxHashSet<_> = unsafe_blocks.iter()
658 .flat_map(|&&(id, used)| if used { Some(id) } else { None })
660 for &(block_id, is_used) in unsafe_blocks {
662 report_unused_unsafe(tcx, &used_unsafe, block_id);