diff options
Diffstat (limited to 'compiler/rustc_mir')
| -rw-r--r-- | compiler/rustc_mir/src/transform/check_consts/ops.rs | 53 | ||||
| -rw-r--r-- | compiler/rustc_mir/src/transform/check_consts/validation.rs | 50 |
2 files changed, 98 insertions, 5 deletions
diff --git a/compiler/rustc_mir/src/transform/check_consts/ops.rs b/compiler/rustc_mir/src/transform/check_consts/ops.rs index d2e65abfbc7..9e90a7519cf 100644 --- a/compiler/rustc_mir/src/transform/check_consts/ops.rs +++ b/compiler/rustc_mir/src/transform/check_consts/ops.rs @@ -209,16 +209,61 @@ impl NonConstOp for LiveDrop { } #[derive(Debug)] +/// A borrow of a type that contains an `UnsafeCell` somewhere. The borrow never escapes to +/// the final value of the constant. +pub struct TransientCellBorrow; +impl NonConstOp for TransientCellBorrow { + fn status_in_item(&self, _: &ConstCx<'_, '_>) -> Status { + Status::Unstable(sym::const_refs_to_cell) + } + fn importance(&self) -> DiagnosticImportance { + // The cases that cannot possibly work will already emit a `CellBorrow`, so we should + // not additionally emit a feature gate error if activating the feature gate won't work. + DiagnosticImportance::Secondary + } + fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> { + feature_err( + &ccx.tcx.sess.parse_sess, + sym::const_refs_to_cell, + span, + "cannot borrow here, since the borrowed element may contain interior mutability", + ) + } +} + +#[derive(Debug)] +/// A borrow of a type that contains an `UnsafeCell` somewhere. The borrow might escape to +/// the final value of the constant, and thus we cannot allow this (for now). We may allow +/// it in the future for static items. pub struct CellBorrow; impl NonConstOp for CellBorrow { fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> { - struct_span_err!( + let mut err = struct_span_err!( ccx.tcx.sess, span, E0492, - "cannot borrow a constant which may contain \ - interior mutability, create a static instead" - ) + "{}s cannot refer to interior mutable data", + ccx.const_kind(), + ); + err.span_label( + span, + format!("this borrow of an interior mutable value may end up in the final value"), + ); + if let hir::ConstContext::Static(_) = ccx.const_kind() { + err.help( + "to fix this, the value can be extracted to a separate \ + `static` item and then referenced", + ); + } + if ccx.tcx.sess.teach(&err.get_code().unwrap()) { + err.note( + "A constant containing interior mutable data behind a reference can allow you + to modify that data. This would make multiple uses of a constant to be able to + see different values and allow circumventing the `Send` and `Sync` requirements + for shared mutable data, which is unsound.", + ); + } + err } } diff --git a/compiler/rustc_mir/src/transform/check_consts/validation.rs b/compiler/rustc_mir/src/transform/check_consts/validation.rs index 90688ebbd0a..d1c07d1051d 100644 --- a/compiler/rustc_mir/src/transform/check_consts/validation.rs +++ b/compiler/rustc_mir/src/transform/check_consts/validation.rs @@ -3,6 +3,7 @@ use rustc_errors::{struct_span_err, Applicability, Diagnostic, ErrorReported}; use rustc_hir::def_id::DefId; use rustc_hir::{self as hir, HirId, LangItem}; +use rustc_index::bit_set::BitSet; use rustc_infer::infer::TyCtxtInferExt; use rustc_infer::traits::{ImplSource, Obligation, ObligationCause}; use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor}; @@ -188,6 +189,9 @@ pub struct Validator<'mir, 'tcx> { /// The span of the current statement. span: Span, + /// A set that stores for each local whether it has a `StorageDead` for it somewhere. + local_has_storage_dead: Option<BitSet<Local>>, + error_emitted: Option<ErrorReported>, secondary_errors: Vec<Diagnostic>, } @@ -206,6 +210,7 @@ impl Validator<'mir, 'tcx> { span: ccx.body.span, ccx, qualifs: Default::default(), + local_has_storage_dead: None, error_emitted: None, secondary_errors: Vec::new(), } @@ -282,6 +287,27 @@ impl Validator<'mir, 'tcx> { } } + fn local_has_storage_dead(&mut self, local: Local) -> bool { + let ccx = self.ccx; + self.local_has_storage_dead + .get_or_insert_with(|| { + struct StorageDeads { + locals: BitSet<Local>, + } + impl Visitor<'tcx> for StorageDeads { + fn visit_statement(&mut self, stmt: &Statement<'tcx>, _: Location) { + if let StatementKind::StorageDead(l) = stmt.kind { + self.locals.insert(l); + } + } + } + let mut v = StorageDeads { locals: BitSet::new_empty(ccx.body.local_decls.len()) }; + v.visit_body(ccx.body); + v.locals + }) + .contains(local) + } + pub fn qualifs_in_return_place(&mut self) -> ConstQualifs { self.qualifs.in_return_place(self.ccx, self.error_emitted) } @@ -556,7 +582,29 @@ impl Visitor<'tcx> for Validator<'mir, 'tcx> { ); if borrowed_place_has_mut_interior { - self.check_op(ops::CellBorrow); + match self.const_kind() { + // In a const fn all borrows are transient or point to the places given via + // references in the arguments (so we already checked them with + // TransientCellBorrow/CellBorrow as appropriate). + // The borrow checker guarantees that no new non-transient borrows are created. + // NOTE: Once we have heap allocations during CTFE we need to figure out + // how to prevent `const fn` to create long-lived allocations that point + // to (interior) mutable memory. + hir::ConstContext::ConstFn => self.check_op(ops::TransientCellBorrow), + _ => { + // Locals with StorageDead are definitely not part of the final constant value, and + // it is thus inherently safe to permit such locals to have their + // address taken as we can't end up with a reference to them in the + // final value. + // Note: This is only sound if every local that has a `StorageDead` has a + // `StorageDead` in every control flow path leading to a `return` terminator. + if self.local_has_storage_dead(place.local) { + self.check_op(ops::TransientCellBorrow); + } else { + self.check_op(ops::CellBorrow); + } + } + } } } |
