diff options
| author | LeSeulArtichaut <leseulartichaut@gmail.com> | 2021-03-14 20:10:22 +0100 |
|---|---|---|
| committer | LeSeulArtichaut <leseulartichaut@gmail.com> | 2021-05-11 15:33:00 +0200 |
| commit | 29780f43e2067ee1c88e73fa76ef1cced28758d3 (patch) | |
| tree | 498a22e3694741ae7d10b828b658f1801bff62c6 | |
| parent | d956122f7e278748310cfe25a982879ed2d90fba (diff) | |
| download | rust-29780f43e2067ee1c88e73fa76ef1cced28758d3.tar.gz rust-29780f43e2067ee1c88e73fa76ef1cced28758d3.zip | |
Introduce the (WIP) THIR unsafety checker
| -rw-r--r-- | compiler/rustc_interface/src/passes.rs | 6 | ||||
| -rw-r--r-- | compiler/rustc_interface/src/tests.rs | 1 | ||||
| -rw-r--r-- | compiler/rustc_middle/src/query/mod.rs | 13 | ||||
| -rw-r--r-- | compiler/rustc_mir_build/src/check_unsafety.rs | 319 | ||||
| -rw-r--r-- | compiler/rustc_mir_build/src/lib.rs | 3 | ||||
| -rw-r--r-- | compiler/rustc_session/src/options.rs | 2 |
6 files changed, 343 insertions, 1 deletions
diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index 02e62a2cee9..601b6d2b608 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -884,7 +884,11 @@ fn analysis(tcx: TyCtxt<'_>, cnum: CrateNum) -> Result<()> { sess.time("MIR_effect_checking", || { for def_id in tcx.body_owners() { - mir::transform::check_unsafety::check_unsafety(tcx, def_id); + if tcx.sess.opts.debugging_opts.thir_unsafeck { + tcx.ensure().thir_check_unsafety(def_id); + } else { + mir::transform::check_unsafety::check_unsafety(tcx, def_id); + } if tcx.hir().body_const_context(def_id).is_some() { tcx.ensure() diff --git a/compiler/rustc_interface/src/tests.rs b/compiler/rustc_interface/src/tests.rs index fd13cb3d59a..50a0c1067d0 100644 --- a/compiler/rustc_interface/src/tests.rs +++ b/compiler/rustc_interface/src/tests.rs @@ -735,6 +735,7 @@ fn test_debugging_options_tracking_hash() { tracked!(symbol_mangling_version, Some(SymbolManglingVersion::V0)); tracked!(teach, true); tracked!(thinlto, Some(true)); + tracked!(thir_unsafeck, true); tracked!(tune_cpu, Some(String::from("abc"))); tracked!(tls_model, Some(TlsModel::GeneralDynamic)); tracked!(trap_unreachable, Some(false)); diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs index 3ffc3641b62..699716d8203 100644 --- a/compiler/rustc_middle/src/query/mod.rs +++ b/compiler/rustc_middle/src/query/mod.rs @@ -611,6 +611,19 @@ rustc_queries! { } } + /// Unsafety-check this `LocalDefId` with THIR unsafeck. This should be + /// used with `-Zthir-unsafeck`. + query thir_check_unsafety(key: LocalDefId) { + desc { |tcx| "unsafety-checking `{}`", tcx.def_path_str(key.to_def_id()) } + cache_on_disk_if { true } + } + query thir_check_unsafety_for_const_arg(key: (LocalDefId, DefId)) { + desc { + |tcx| "unsafety-checking the const argument `{}`", + tcx.def_path_str(key.0.to_def_id()) + } + } + /// HACK: when evaluated, this reports a "unsafe derive on repr(packed)" error. /// /// Unsafety checking is executed for each method separately, but we only want diff --git a/compiler/rustc_mir_build/src/check_unsafety.rs b/compiler/rustc_mir_build/src/check_unsafety.rs new file mode 100644 index 00000000000..fb25d6893be --- /dev/null +++ b/compiler/rustc_mir_build/src/check_unsafety.rs @@ -0,0 +1,319 @@ +use crate::thir::visit::{self, Visitor}; +use crate::thir::*; + +use rustc_errors::struct_span_err; +use rustc_hir as hir; +use rustc_middle::ty::{self, TyCtxt}; +use rustc_session::lint::builtin::{UNSAFE_OP_IN_UNSAFE_FN, UNUSED_UNSAFE}; +use rustc_session::lint::Level; +use rustc_span::def_id::{DefId, LocalDefId}; +use rustc_span::Span; + +struct UnsafetyVisitor<'tcx> { + tcx: TyCtxt<'tcx>, + /// The `HirId` of the current scope, which would be the `HirId` + /// of the current HIR node, modulo adjustments. Used for lint levels. + hir_context: hir::HirId, + /// The current "safety context". This notably tracks whether we are in an + /// `unsafe` block, and whether it has been used. + safety_context: SafetyContext, + body_unsafety: BodyUnsafety, +} + +impl<'tcx> UnsafetyVisitor<'tcx> { + fn requires_unsafe(&mut self, span: Span, kind: UnsafeOpKind) { + let (description, note) = kind.description_and_note(); + let unsafe_op_in_unsafe_fn_allowed = self.unsafe_op_in_unsafe_fn_allowed(); + match self.safety_context { + SafetyContext::UnsafeBlock { ref mut used, .. } => { + if !self.body_unsafety.is_unsafe() || !unsafe_op_in_unsafe_fn_allowed { + // Mark this block as useful + *used = true; + } + } + SafetyContext::UnsafeFn if unsafe_op_in_unsafe_fn_allowed => {} + SafetyContext::UnsafeFn => { + // unsafe_op_in_unsafe_fn is disallowed + if kind == BorrowOfPackedField { + // FIXME handle borrows of packed fields + } else { + struct_span_err!( + self.tcx.sess, + span, + E0133, + "{} is unsafe and requires unsafe block", + description, + ) + .span_label(span, description) + .note(note) + .emit(); + } + } + SafetyContext::Safe => { + if kind == BorrowOfPackedField { + // FIXME handle borrows of packed fields + } else { + let fn_sugg = if unsafe_op_in_unsafe_fn_allowed { " function or" } else { "" }; + struct_span_err!( + self.tcx.sess, + span, + E0133, + "{} is unsafe and requires unsafe{} block", + description, + fn_sugg, + ) + .span_label(span, description) + .note(note) + .emit(); + } + } + } + } + + fn warn_unused_unsafe( + &self, + hir_id: hir::HirId, + block_span: Span, + enclosing_span: Option<Span>, + ) { + let block_span = self.tcx.sess.source_map().guess_head_span(block_span); + self.tcx.struct_span_lint_hir(UNUSED_UNSAFE, hir_id, block_span, |lint| { + let msg = "unnecessary `unsafe` block"; + let mut db = lint.build(msg); + db.span_label(block_span, msg); + if let Some(enclosing_span) = enclosing_span { + db.span_label( + enclosing_span, + format!("because it's nested under this `unsafe` block"), + ); + } + db.emit(); + }); + } + + /// Whether the `unsafe_op_in_unsafe_fn` lint is `allow`ed at the current HIR node. + fn unsafe_op_in_unsafe_fn_allowed(&self) -> bool { + self.tcx.lint_level_at_node(UNSAFE_OP_IN_UNSAFE_FN, self.hir_context).0 == Level::Allow + } +} + +impl<'thir, 'tcx> Visitor<'thir, 'tcx> for UnsafetyVisitor<'tcx> { + fn visit_block(&mut self, block: &Block<'thir, 'tcx>) { + if let BlockSafety::ExplicitUnsafe(hir_id) = block.safety_mode { + if let SafetyContext::UnsafeBlock { span: enclosing_span, .. } = self.safety_context { + self.warn_unused_unsafe( + hir_id, + block.span, + Some(self.tcx.sess.source_map().guess_head_span(enclosing_span)), + ); + } else { + let prev_context = self.safety_context; + self.safety_context = + SafetyContext::UnsafeBlock { span: block.span, hir_id, used: false }; + visit::walk_block(self, block); + if let SafetyContext::UnsafeBlock { used: false, span, hir_id } = + self.safety_context + { + self.warn_unused_unsafe(hir_id, span, self.body_unsafety.unsafe_fn_sig_span()); + } + self.safety_context = prev_context; + return; + } + } + + visit::walk_block(self, block); + } + + fn visit_expr(&mut self, expr: &'thir Expr<'thir, 'tcx>) { + match expr.kind { + ExprKind::Scope { value, lint_level: LintLevel::Explicit(hir_id), .. } => { + let prev_id = self.hir_context; + self.hir_context = hir_id; + self.visit_expr(value); + self.hir_context = prev_id; + return; + } + ExprKind::Call { fun, .. } => { + if fun.ty.fn_sig(self.tcx).unsafety() == hir::Unsafety::Unsafe { + self.requires_unsafe(expr.span, CallToUnsafeFunction); + } + } + _ => {} + } + + visit::walk_expr(self, expr); + } +} + +#[derive(Clone, Copy)] +enum SafetyContext { + Safe, + UnsafeFn, + UnsafeBlock { span: Span, hir_id: hir::HirId, used: bool }, +} + +#[derive(Clone, Copy)] +enum BodyUnsafety { + /// The body is not unsafe. + Safe, + /// The body is an unsafe function. The span points to + /// the signature of the function. + Unsafe(Span), +} + +impl BodyUnsafety { + /// Returns whether the body is unsafe. + fn is_unsafe(&self) -> bool { + matches!(self, BodyUnsafety::Unsafe(_)) + } + + /// If the body is unsafe, returns the `Span` of its signature. + fn unsafe_fn_sig_span(self) -> Option<Span> { + match self { + BodyUnsafety::Unsafe(span) => Some(span), + BodyUnsafety::Safe => None, + } + } +} + +#[derive(Clone, Copy, PartialEq)] +enum UnsafeOpKind { + CallToUnsafeFunction, + #[allow(dead_code)] // FIXME + UseOfInlineAssembly, + #[allow(dead_code)] // FIXME + InitializingTypeWith, + #[allow(dead_code)] // FIXME + CastOfPointerToInt, + #[allow(dead_code)] // FIXME + BorrowOfPackedField, + #[allow(dead_code)] // FIXME + UseOfMutableStatic, + #[allow(dead_code)] // FIXME + UseOfExternStatic, + #[allow(dead_code)] // FIXME + DerefOfRawPointer, + #[allow(dead_code)] // FIXME + AssignToDroppingUnionField, + #[allow(dead_code)] // FIXME + AccessToUnionField, + #[allow(dead_code)] // FIXME + MutationOfLayoutConstrainedField, + #[allow(dead_code)] // FIXME + BorrowOfLayoutConstrainedField, + #[allow(dead_code)] // FIXME + CallToFunctionWith, +} + +use UnsafeOpKind::*; + +impl UnsafeOpKind { + pub fn description_and_note(&self) -> (&'static str, &'static str) { + match self { + CallToUnsafeFunction => ( + "call to unsafe function", + "consult the function's documentation for information on how to avoid undefined \ + behavior", + ), + UseOfInlineAssembly => ( + "use of inline assembly", + "inline assembly is entirely unchecked and can cause undefined behavior", + ), + InitializingTypeWith => ( + "initializing type with `rustc_layout_scalar_valid_range` attr", + "initializing a layout restricted type's field with a value outside the valid \ + range is undefined behavior", + ), + CastOfPointerToInt => { + ("cast of pointer to int", "casting pointers to integers in constants") + } + BorrowOfPackedField => ( + "borrow of packed field", + "fields of packed structs might be misaligned: dereferencing a misaligned pointer \ + or even just creating a misaligned reference is undefined behavior", + ), + UseOfMutableStatic => ( + "use of mutable static", + "mutable statics can be mutated by multiple threads: aliasing violations or data \ + races will cause undefined behavior", + ), + UseOfExternStatic => ( + "use of extern static", + "extern statics are not controlled by the Rust type system: invalid data, \ + aliasing violations or data races will cause undefined behavior", + ), + DerefOfRawPointer => ( + "dereference of raw pointer", + "raw pointers may be NULL, dangling or unaligned; they can violate aliasing rules \ + and cause data races: all of these are undefined behavior", + ), + AssignToDroppingUnionField => ( + "assignment to union field that might need dropping", + "the previous content of the field will be dropped, which causes undefined \ + behavior if the field was not properly initialized", + ), + AccessToUnionField => ( + "access to union field", + "the field may not be properly initialized: using uninitialized data will cause \ + undefined behavior", + ), + MutationOfLayoutConstrainedField => ( + "mutation of layout constrained field", + "mutating layout constrained fields cannot statically be checked for valid values", + ), + BorrowOfLayoutConstrainedField => ( + "borrow of layout constrained field with interior mutability", + "references to fields of layout constrained fields lose the constraints. Coupled \ + with interior mutability, the field can be changed to invalid values", + ), + CallToFunctionWith => ( + "call to function with `#[target_feature]`", + "can only be called if the required target features are available", + ), + } + } +} + +// FIXME: checking unsafety for closures should be handled by their parent body, +// as they inherit their "safety context" from their declaration site. +pub fn check_unsafety<'tcx>(tcx: TyCtxt<'tcx>, thir: &Expr<'_, 'tcx>, hir_id: hir::HirId) { + let body_unsafety = tcx.hir().fn_sig_by_hir_id(hir_id).map_or(BodyUnsafety::Safe, |fn_sig| { + if fn_sig.header.unsafety == hir::Unsafety::Unsafe { + BodyUnsafety::Unsafe(fn_sig.span) + } else { + BodyUnsafety::Safe + } + }); + let safety_context = + if body_unsafety.is_unsafe() { SafetyContext::UnsafeFn } else { SafetyContext::Safe }; + let mut visitor = UnsafetyVisitor { tcx, safety_context, hir_context: hir_id, body_unsafety }; + visitor.visit_expr(thir); +} + +crate fn thir_check_unsafety_inner<'tcx>( + tcx: TyCtxt<'tcx>, + def: ty::WithOptConstParam<LocalDefId>, +) { + let hir_id = tcx.hir().local_def_id_to_hir_id(def.did); + let body_id = tcx.hir().body_owned_by(hir_id); + let body = tcx.hir().body(body_id); + + let arena = Arena::default(); + let thir = cx::build_thir(tcx, def, &arena, &body.value); + check_unsafety(tcx, thir, hir_id); +} + +crate fn thir_check_unsafety<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) { + if let Some(def) = ty::WithOptConstParam::try_lookup(def_id, tcx) { + tcx.thir_check_unsafety_for_const_arg(def) + } else { + thir_check_unsafety_inner(tcx, ty::WithOptConstParam::unknown(def_id)) + } +} + +crate fn thir_check_unsafety_for_const_arg<'tcx>( + tcx: TyCtxt<'tcx>, + (did, param_did): (LocalDefId, DefId), +) { + thir_check_unsafety_inner(tcx, ty::WithOptConstParam { did, const_param_did: Some(param_did) }) +} diff --git a/compiler/rustc_mir_build/src/lib.rs b/compiler/rustc_mir_build/src/lib.rs index da9a0b08e86..d4e9a0a3169 100644 --- a/compiler/rustc_mir_build/src/lib.rs +++ b/compiler/rustc_mir_build/src/lib.rs @@ -19,6 +19,7 @@ extern crate tracing; extern crate rustc_middle; mod build; +mod check_unsafety; mod lints; pub mod thir; @@ -28,4 +29,6 @@ pub fn provide(providers: &mut Providers) { providers.check_match = thir::pattern::check_match; providers.lit_to_const = thir::constant::lit_to_const; providers.mir_built = build::mir_built; + providers.thir_check_unsafety = check_unsafety::thir_check_unsafety; + providers.thir_check_unsafety_for_const_arg = check_unsafety::thir_check_unsafety_for_const_arg; } diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index 10e195f4f25..91b619e8e12 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -1251,6 +1251,8 @@ options! { "select processor to schedule for (`rustc --print target-cpus` for details)"), thinlto: Option<bool> = (None, parse_opt_bool, [TRACKED], "enable ThinLTO when possible"), + thir_unsafeck: bool = (false, parse_bool, [TRACKED], + "use the work-in-progress THIR unsafety checker. NOTE: this is unsound (default: no)"), /// We default to 1 here since we want to behave like /// a sequential compiler for now. This'll likely be adjusted /// in the future. Note that -Zthreads=0 is the way to get |
