about summary refs log tree commit diff
diff options
context:
space:
mode:
authorLeSeulArtichaut <leseulartichaut@gmail.com>2021-03-14 20:10:22 +0100
committerLeSeulArtichaut <leseulartichaut@gmail.com>2021-05-11 15:33:00 +0200
commit29780f43e2067ee1c88e73fa76ef1cced28758d3 (patch)
tree498a22e3694741ae7d10b828b658f1801bff62c6
parentd956122f7e278748310cfe25a982879ed2d90fba (diff)
downloadrust-29780f43e2067ee1c88e73fa76ef1cced28758d3.tar.gz
rust-29780f43e2067ee1c88e73fa76ef1cced28758d3.zip
Introduce the (WIP) THIR unsafety checker
-rw-r--r--compiler/rustc_interface/src/passes.rs6
-rw-r--r--compiler/rustc_interface/src/tests.rs1
-rw-r--r--compiler/rustc_middle/src/query/mod.rs13
-rw-r--r--compiler/rustc_mir_build/src/check_unsafety.rs319
-rw-r--r--compiler/rustc_mir_build/src/lib.rs3
-rw-r--r--compiler/rustc_session/src/options.rs2
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