// Copyright 2017 The Rust Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::indexed_vec::IndexVec; use rustc_data_structures::sync::Lrc; use rustc::ty::maps::Providers; use rustc::ty::{self, TyCtxt}; use rustc::hir; use rustc::hir::def_id::DefId; use rustc::lint::builtin::{SAFE_EXTERN_STATICS, SAFE_PACKED_BORROWS, UNUSED_UNSAFE}; use rustc::mir::*; use rustc::mir::visit::{PlaceContext, Visitor}; use syntax::ast; use syntax::symbol::Symbol; use util; pub struct UnsafetyChecker<'a, 'tcx: 'a> { mir: &'a Mir<'tcx>, source_scope_info: &'a IndexVec, violations: Vec, source_info: SourceInfo, tcx: TyCtxt<'a, 'tcx, 'tcx>, param_env: ty::ParamEnv<'tcx>, used_unsafe: FxHashSet, inherited_blocks: Vec<(ast::NodeId, bool)>, } impl<'a, 'gcx, 'tcx> UnsafetyChecker<'a, 'tcx> { fn new(mir: &'a Mir<'tcx>, source_scope_info: &'a IndexVec, tcx: TyCtxt<'a, 'tcx, 'tcx>, param_env: ty::ParamEnv<'tcx>) -> Self { Self { mir, source_scope_info, violations: vec![], source_info: SourceInfo { span: mir.span, scope: OUTERMOST_SOURCE_SCOPE }, tcx, param_env, used_unsafe: FxHashSet(), inherited_blocks: vec![], } } } impl<'a, 'tcx> Visitor<'tcx> for UnsafetyChecker<'a, 'tcx> { fn visit_terminator(&mut self, block: BasicBlock, terminator: &Terminator<'tcx>, location: Location) { self.source_info = terminator.source_info; match terminator.kind { TerminatorKind::Goto { .. } | TerminatorKind::SwitchInt { .. } | TerminatorKind::Drop { .. } | TerminatorKind::Yield { .. } | TerminatorKind::Assert { .. } | TerminatorKind::DropAndReplace { .. } | TerminatorKind::GeneratorDrop | TerminatorKind::Resume | TerminatorKind::Abort | TerminatorKind::Return | TerminatorKind::Unreachable | TerminatorKind::FalseEdges { .. } | TerminatorKind::FalseUnwind { .. } => { // safe (at least as emitted during MIR construction) } TerminatorKind::Call { ref func, .. } => { let func_ty = func.ty(self.mir, self.tcx); let sig = func_ty.fn_sig(self.tcx); if let hir::Unsafety::Unsafe = sig.unsafety() { self.require_unsafe("call to unsafe function") } } } self.super_terminator(block, terminator, location); } fn visit_statement(&mut self, block: BasicBlock, statement: &Statement<'tcx>, location: Location) { self.source_info = statement.source_info; match statement.kind { StatementKind::Assign(..) | StatementKind::ReadForMatch(..) | StatementKind::SetDiscriminant { .. } | StatementKind::StorageLive(..) | StatementKind::StorageDead(..) | StatementKind::EndRegion(..) | StatementKind::Validate(..) | StatementKind::UserAssertTy(..) | StatementKind::Nop => { // safe (at least as emitted during MIR construction) } StatementKind::InlineAsm { .. } => { self.require_unsafe("use of inline assembly") }, } self.super_statement(block, statement, location); } fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) { if let &Rvalue::Aggregate(box ref aggregate, _) = rvalue { match aggregate { &AggregateKind::Array(..) | &AggregateKind::Tuple | &AggregateKind::Adt(..) => {} &AggregateKind::Closure(def_id, _) | &AggregateKind::Generator(def_id, _, _) => { let UnsafetyCheckResult { violations, unsafe_blocks } = self.tcx.unsafety_check_result(def_id); self.register_violations(&violations, &unsafe_blocks); } } } self.super_rvalue(rvalue, location); } fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext<'tcx>, location: Location) { if let PlaceContext::Borrow { .. } = context { if util::is_disaligned(self.tcx, self.mir, self.param_env, place) { let source_info = self.source_info; let lint_root = self.source_scope_info[source_info.scope].lint_root; self.register_violations(&[UnsafetyViolation { source_info, description: Symbol::intern("borrow of packed field").as_interned_str(), kind: UnsafetyViolationKind::BorrowPacked(lint_root) }], &[]); } } match place { &Place::Projection(box Projection { ref base, ref elem }) => { let old_source_info = self.source_info; if let &Place::Local(local) = base { if self.mir.local_decls[local].internal { // Internal locals are used in the `move_val_init` desugaring. // We want to check unsafety against the source info of the // desugaring, rather than the source info of the RHS. self.source_info = self.mir.local_decls[local].source_info; } } let base_ty = base.ty(self.mir, self.tcx).to_ty(self.tcx); match base_ty.sty { ty::TyRawPtr(..) => { self.require_unsafe("dereference of raw pointer") } ty::TyAdt(adt, _) => { if adt.is_union() { if context == PlaceContext::Store || context == PlaceContext::AsmOutput || context == PlaceContext::Drop { let elem_ty = match elem { &ProjectionElem::Field(_, ty) => ty, _ => span_bug!( self.source_info.span, "non-field projection {:?} from union?", place) }; if elem_ty.moves_by_default(self.tcx, self.param_env, self.source_info.span) { self.require_unsafe( "assignment to non-`Copy` union field") } else { // write to non-move union, safe } } else { self.require_unsafe("access to union field") } } } _ => {} } self.source_info = old_source_info; } &Place::Local(..) => { // locals are safe } &Place::Static(box Static { def_id, ty: _ }) => { if self.tcx.is_static(def_id) == Some(hir::Mutability::MutMutable) { self.require_unsafe("use of mutable static"); } else if self.tcx.is_foreign_item(def_id) { let source_info = self.source_info; let lint_root = self.source_scope_info[source_info.scope].lint_root; self.register_violations(&[UnsafetyViolation { source_info, description: Symbol::intern("use of extern static").as_interned_str(), kind: UnsafetyViolationKind::ExternStatic(lint_root) }], &[]); } } }; self.super_place(place, context, location); } } impl<'a, 'tcx> UnsafetyChecker<'a, 'tcx> { fn require_unsafe(&mut self, description: &'static str) { let source_info = self.source_info; self.register_violations(&[UnsafetyViolation { source_info, description: Symbol::intern(description).as_interned_str(), kind: UnsafetyViolationKind::General, }], &[]); } fn register_violations(&mut self, violations: &[UnsafetyViolation], unsafe_blocks: &[(ast::NodeId, bool)]) { let within_unsafe = match self.source_scope_info[self.source_info.scope].safety { Safety::Safe => { for violation in violations { if !self.violations.contains(violation) { self.violations.push(violation.clone()) } } false } Safety::BuiltinUnsafe | Safety::FnUnsafe => true, Safety::ExplicitUnsafe(node_id) => { if !violations.is_empty() { self.used_unsafe.insert(node_id); } true } }; self.inherited_blocks.extend(unsafe_blocks.iter().map(|&(node_id, is_used)| { (node_id, is_used && !within_unsafe) })); } } pub(crate) fn provide(providers: &mut Providers) { *providers = Providers { unsafety_check_result, unsafe_derive_on_repr_packed, ..*providers }; } struct UnusedUnsafeVisitor<'a> { used_unsafe: &'a FxHashSet, unsafe_blocks: &'a mut Vec<(ast::NodeId, bool)>, } impl<'a, 'tcx> hir::intravisit::Visitor<'tcx> for UnusedUnsafeVisitor<'a> { fn nested_visit_map<'this>(&'this mut self) -> hir::intravisit::NestedVisitorMap<'this, 'tcx> { hir::intravisit::NestedVisitorMap::None } fn visit_block(&mut self, block: &'tcx hir::Block) { hir::intravisit::walk_block(self, block); if let hir::UnsafeBlock(hir::UserProvided) = block.rules { self.unsafe_blocks.push((block.id, self.used_unsafe.contains(&block.id))); } } } fn check_unused_unsafe<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, def_id: DefId, used_unsafe: &FxHashSet, unsafe_blocks: &'a mut Vec<(ast::NodeId, bool)>) { let body_id = tcx.hir.as_local_node_id(def_id).and_then(|node_id| { tcx.hir.maybe_body_owned_by(node_id) }); let body_id = match body_id { Some(body) => body, None => { debug!("check_unused_unsafe({:?}) - no body found", def_id); return } }; let body = tcx.hir.body(body_id); debug!("check_unused_unsafe({:?}, body={:?}, used_unsafe={:?})", def_id, body, used_unsafe); let mut visitor = UnusedUnsafeVisitor { used_unsafe, unsafe_blocks }; hir::intravisit::Visitor::visit_body(&mut visitor, body); } fn unsafety_check_result<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, def_id: DefId) -> UnsafetyCheckResult { debug!("unsafety_violations({:?})", def_id); // NB: this borrow is valid because all the consumers of // `mir_built` force this. let mir = &tcx.mir_built(def_id).borrow(); let source_scope_info = match mir.source_scope_info { ClearCrossCrate::Set(ref data) => data, ClearCrossCrate::Clear => { debug!("unsafety_violations: {:?} - remote, skipping", def_id); return UnsafetyCheckResult { violations: Lrc::new([]), unsafe_blocks: Lrc::new([]) } } }; let param_env = tcx.param_env(def_id); let mut checker = UnsafetyChecker::new( mir, source_scope_info, tcx, param_env); checker.visit_mir(mir); check_unused_unsafe(tcx, def_id, &checker.used_unsafe, &mut checker.inherited_blocks); UnsafetyCheckResult { violations: checker.violations.into(), unsafe_blocks: checker.inherited_blocks.into() } } fn unsafe_derive_on_repr_packed<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, def_id: DefId) { let lint_node_id = match tcx.hir.as_local_node_id(def_id) { Some(node_id) => node_id, None => bug!("checking unsafety for non-local def id {:?}", def_id) }; // FIXME: when we make this a hard error, this should have its // own error code. let message = if tcx.generics_of(def_id).own_counts().types != 0 { format!("#[derive] can't be used on a #[repr(packed)] struct with \ type parameters (error E0133)") } else { format!("#[derive] can't be used on a non-Copy #[repr(packed)] struct \ (error E0133)") }; tcx.lint_node(SAFE_PACKED_BORROWS, lint_node_id, tcx.def_span(def_id), &message); } /// Return the NodeId for an enclosing scope that is also `unsafe` fn is_enclosed(tcx: TyCtxt, used_unsafe: &FxHashSet, id: ast::NodeId) -> Option<(String, ast::NodeId)> { let parent_id = tcx.hir.get_parent_node(id); if parent_id != id { if used_unsafe.contains(&parent_id) { Some(("block".to_string(), parent_id)) } else if let Some(hir::map::NodeItem(&hir::Item { node: hir::ItemFn(_, fn_unsafety, _, _, _, _), .. })) = tcx.hir.find(parent_id) { match fn_unsafety { hir::Unsafety::Unsafe => Some(("fn".to_string(), parent_id)), hir::Unsafety::Normal => None, } } else { is_enclosed(tcx, used_unsafe, parent_id) } } else { None } } fn report_unused_unsafe(tcx: TyCtxt, used_unsafe: &FxHashSet, id: ast::NodeId) { let span = tcx.sess.codemap().def_span(tcx.hir.span(id)); let msg = "unnecessary `unsafe` block"; let mut db = tcx.struct_span_lint_node(UNUSED_UNSAFE, id, span, msg); db.span_label(span, msg); if let Some((kind, id)) = is_enclosed(tcx, used_unsafe, id) { db.span_label(tcx.sess.codemap().def_span(tcx.hir.span(id)), format!("because it's nested under this `unsafe` {}", kind)); } db.emit(); } fn builtin_derive_def_id<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, def_id: DefId) -> Option { debug!("builtin_derive_def_id({:?})", def_id); if let Some(impl_def_id) = tcx.impl_of_method(def_id) { if tcx.has_attr(impl_def_id, "automatically_derived") { debug!("builtin_derive_def_id({:?}) - is {:?}", def_id, impl_def_id); Some(impl_def_id) } else { debug!("builtin_derive_def_id({:?}) - not automatically derived", def_id); None } } else { debug!("builtin_derive_def_id({:?}) - not a method", def_id); None } } pub fn check_unsafety<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, def_id: DefId) { debug!("check_unsafety({:?})", def_id); // closures are handled by their parent fn. if tcx.is_closure(def_id) { return; } let UnsafetyCheckResult { violations, unsafe_blocks } = tcx.unsafety_check_result(def_id); for &UnsafetyViolation { source_info, description, kind } in violations.iter() { // Report an error. match kind { UnsafetyViolationKind::General => { struct_span_err!( tcx.sess, source_info.span, E0133, "{} requires unsafe function or block", description) .span_label(source_info.span, &description.as_str()[..]) .emit(); } UnsafetyViolationKind::ExternStatic(lint_node_id) => { tcx.lint_node(SAFE_EXTERN_STATICS, lint_node_id, source_info.span, &format!("{} requires unsafe function or \ block (error E0133)", &description.as_str()[..])); } UnsafetyViolationKind::BorrowPacked(lint_node_id) => { if let Some(impl_def_id) = builtin_derive_def_id(tcx, def_id) { tcx.unsafe_derive_on_repr_packed(impl_def_id); } else { tcx.lint_node(SAFE_PACKED_BORROWS, lint_node_id, source_info.span, &format!("{} requires unsafe function or \ block (error E0133)", &description.as_str()[..])); } } } } let mut unsafe_blocks: Vec<_> = unsafe_blocks.into_iter().collect(); unsafe_blocks.sort(); let used_unsafe: FxHashSet<_> = unsafe_blocks.iter() .flat_map(|&&(id, used)| if used { Some(id) } else { None }) .collect(); for &(block_id, is_used) in unsafe_blocks { if !is_used { report_unused_unsafe(tcx, &used_unsafe, block_id); } } }