diff --git a/src/librustc_error_codes/error_codes/E0493.md b/src/librustc_error_codes/error_codes/E0493.md index 90a0cbce623d0..0dcc3b62b4b2f 100644 --- a/src/librustc_error_codes/error_codes/E0493.md +++ b/src/librustc_error_codes/error_codes/E0493.md @@ -1,5 +1,4 @@ -A type with a `Drop` implementation was destructured when trying to initialize -a static item. +A value with a custom `Drop` implementation may be dropped during const-eval. Erroneous code example: @@ -16,13 +15,14 @@ struct Foo { field1: DropType, } -static FOO: Foo = Foo { ..Foo { field1: DropType::A } }; // error! +static FOO: Foo = Foo { field1: (DropType::A, DropType::A).1 }; // error! ``` The problem here is that if the given type or one of its fields implements the -`Drop` trait, this `Drop` implementation cannot be called during the static -type initialization which might cause a memory leak. To prevent this issue, -you need to instantiate all the static type's fields by hand. +`Drop` trait, this `Drop` implementation cannot be called within a const +context since it may run arbitrary, non-const-checked code. To prevent this +issue, ensure all values with custom a custom `Drop` implementation escape the +initializer. ``` enum DropType { diff --git a/src/librustc_feature/active.rs b/src/librustc_feature/active.rs index b4935236b6a97..d186f35a12b5a 100644 --- a/src/librustc_feature/active.rs +++ b/src/librustc_feature/active.rs @@ -577,6 +577,9 @@ declare_features! ( /// Allows `extern "avr-interrupt" fn()` and `extern "avr-non-blocking-interrupt" fn()`. (active, abi_avr_interrupt, "1.45.0", Some(69664), None), + /// Be more precise when looking for live drops in a const context. + (active, const_precise_live_drops, "1.46.0", Some(73255), None), + // ------------------------------------------------------------------------- // feature-group-end: actual feature gates // ------------------------------------------------------------------------- diff --git a/src/librustc_interface/passes.rs b/src/librustc_interface/passes.rs index 1a9bf4e1e8f3d..1ed9bc3f1f509 100644 --- a/src/librustc_interface/passes.rs +++ b/src/librustc_interface/passes.rs @@ -847,7 +847,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) + mir::transform::check_unsafety::check_unsafety(tcx, def_id); + + if tcx.hir().body_const_context(def_id).is_some() { + tcx.ensure().mir_drops_elaborated_and_const_checked(def_id); + } } }); diff --git a/src/librustc_middle/mir/mod.rs b/src/librustc_middle/mir/mod.rs index 129f9691ea521..21f5d9e7dd4c6 100644 --- a/src/librustc_middle/mir/mod.rs +++ b/src/librustc_middle/mir/mod.rs @@ -76,7 +76,8 @@ pub enum MirPhase { Build = 0, Const = 1, Validated = 2, - Optimized = 3, + DropElab = 3, + Optimized = 4, } impl MirPhase { diff --git a/src/librustc_middle/query/mod.rs b/src/librustc_middle/query/mod.rs index be15e6c576f69..d758b7737c3b9 100644 --- a/src/librustc_middle/query/mod.rs +++ b/src/librustc_middle/query/mod.rs @@ -190,6 +190,12 @@ rustc_queries! { no_hash } + query mir_drops_elaborated_and_const_checked(key: LocalDefId) -> Steal> { + storage(ArenaCacheSelector<'tcx>) + no_hash + desc { |tcx| "elaborating drops for `{}`", tcx.def_path_str(key.to_def_id()) } + } + query mir_validated(key: LocalDefId) -> ( Steal>, diff --git a/src/librustc_mir/transform/check_consts/mod.rs b/src/librustc_mir/transform/check_consts/mod.rs index 7c439f80ef6ad..e4aa88e3c20a7 100644 --- a/src/librustc_mir/transform/check_consts/mod.rs +++ b/src/librustc_mir/transform/check_consts/mod.rs @@ -12,6 +12,7 @@ use rustc_middle::ty::{self, TyCtxt}; pub use self::qualifs::Qualif; mod ops; +pub mod post_drop_elaboration; pub mod qualifs; mod resolver; pub mod validation; diff --git a/src/librustc_mir/transform/check_consts/ops.rs b/src/librustc_mir/transform/check_consts/ops.rs index 92bd740e27aa6..d5059c98c9511 100644 --- a/src/librustc_mir/transform/check_consts/ops.rs +++ b/src/librustc_mir/transform/check_consts/ops.rs @@ -10,6 +10,22 @@ use rustc_span::{Span, Symbol}; use super::ConstCx; +/// Emits an error if `op` is not allowed in the given const context. +pub fn non_const(ccx: &ConstCx<'_, '_>, op: O, span: Span) { + debug!("illegal_op: op={:?}", op); + + if op.is_allowed_in_item(ccx) { + return; + } + + if ccx.tcx.sess.opts.debugging_opts.unleash_the_miri_inside_of_you { + ccx.tcx.sess.miri_unleashed_feature(span, O::feature_gate()); + return; + } + + op.emit_error(ccx, span); +} + /// An operation that is not *always* allowed in a const context. pub trait NonConstOp: std::fmt::Debug { /// Returns the `Symbol` corresponding to the feature gate that would enable this operation, diff --git a/src/librustc_mir/transform/check_consts/post_drop_elaboration.rs b/src/librustc_mir/transform/check_consts/post_drop_elaboration.rs new file mode 100644 index 0000000000000..226e0e2049ebd --- /dev/null +++ b/src/librustc_mir/transform/check_consts/post_drop_elaboration.rs @@ -0,0 +1,119 @@ +use rustc_hir::def_id::LocalDefId; +use rustc_middle::mir::visit::Visitor; +use rustc_middle::mir::{self, BasicBlock, Location}; +use rustc_middle::ty::TyCtxt; +use rustc_span::Span; + +use super::ops; +use super::qualifs::{NeedsDrop, Qualif}; +use super::validation::Qualifs; +use super::ConstCx; + +/// Returns `true` if we should use the more precise live drop checker that runs after drop +/// elaboration. +pub fn checking_enabled(tcx: TyCtxt<'tcx>) -> bool { + tcx.features().const_precise_live_drops +} + +/// Look for live drops in a const context. +/// +/// This is separate from the rest of the const checking logic because it must run after drop +/// elaboration. +pub fn check_live_drops(tcx: TyCtxt<'tcx>, def_id: LocalDefId, body: &mir::Body<'tcx>) { + let const_kind = tcx.hir().body_const_context(def_id); + if const_kind.is_none() { + return; + } + + if !checking_enabled(tcx) { + return; + } + + let ccx = ConstCx { + body, + tcx, + def_id: def_id.to_def_id(), + const_kind, + param_env: tcx.param_env(def_id), + }; + + let mut visitor = CheckLiveDrops { ccx: &ccx, qualifs: Qualifs::default() }; + + visitor.visit_body(body); +} + +struct CheckLiveDrops<'mir, 'tcx> { + ccx: &'mir ConstCx<'mir, 'tcx>, + qualifs: Qualifs<'mir, 'tcx>, +} + +// So we can access `body` and `tcx`. +impl std::ops::Deref for CheckLiveDrops<'mir, 'tcx> { + type Target = ConstCx<'mir, 'tcx>; + + fn deref(&self) -> &Self::Target { + &self.ccx + } +} + +impl CheckLiveDrops<'mir, 'tcx> { + fn check_live_drop(&self, span: Span) { + ops::non_const(self.ccx, ops::LiveDrop, span); + } +} + +impl Visitor<'tcx> for CheckLiveDrops<'mir, 'tcx> { + fn visit_basic_block_data(&mut self, bb: BasicBlock, block: &mir::BasicBlockData<'tcx>) { + trace!("visit_basic_block_data: bb={:?} is_cleanup={:?}", bb, block.is_cleanup); + + // Ignore drop terminators in cleanup blocks. + if block.is_cleanup { + return; + } + + self.super_basic_block_data(bb, block); + } + + fn visit_terminator(&mut self, terminator: &mir::Terminator<'tcx>, location: Location) { + trace!("visit_terminator: terminator={:?} location={:?}", terminator, location); + + match &terminator.kind { + mir::TerminatorKind::Drop { location: dropped_place, .. } => { + let dropped_ty = dropped_place.ty(self.body, self.tcx).ty; + if !NeedsDrop::in_any_value_of_ty(self.ccx, dropped_ty) { + return; + } + + if dropped_place.is_indirect() { + self.check_live_drop(terminator.source_info.span); + return; + } + + if self.qualifs.needs_drop(self.ccx, dropped_place.local, location) { + // Use the span where the dropped local was declared for the error. + let span = self.body.local_decls[dropped_place.local].source_info.span; + self.check_live_drop(span); + } + } + + mir::TerminatorKind::DropAndReplace { .. } => span_bug!( + terminator.source_info.span, + "`DropAndReplace` should be removed by drop elaboration", + ), + + mir::TerminatorKind::Abort + | mir::TerminatorKind::Call { .. } + | mir::TerminatorKind::Assert { .. } + | mir::TerminatorKind::FalseEdge { .. } + | mir::TerminatorKind::FalseUnwind { .. } + | mir::TerminatorKind::GeneratorDrop + | mir::TerminatorKind::Goto { .. } + | mir::TerminatorKind::InlineAsm { .. } + | mir::TerminatorKind::Resume + | mir::TerminatorKind::Return + | mir::TerminatorKind::SwitchInt { .. } + | mir::TerminatorKind::Unreachable + | mir::TerminatorKind::Yield { .. } => {} + } + } +} diff --git a/src/librustc_mir/transform/check_consts/validation.rs b/src/librustc_mir/transform/check_consts/validation.rs index ab87d70da7da3..428a74bcdcbfb 100644 --- a/src/librustc_mir/transform/check_consts/validation.rs +++ b/src/librustc_mir/transform/check_consts/validation.rs @@ -40,7 +40,7 @@ pub struct Qualifs<'mir, 'tcx> { } impl Qualifs<'mir, 'tcx> { - fn indirectly_mutable( + pub fn indirectly_mutable( &mut self, ccx: &'mir ConstCx<'mir, 'tcx>, local: Local, @@ -68,7 +68,7 @@ impl Qualifs<'mir, 'tcx> { /// Returns `true` if `local` is `NeedsDrop` at the given `Location`. /// /// Only updates the cursor if absolutely necessary - fn needs_drop( + pub fn needs_drop( &mut self, ccx: &'mir ConstCx<'mir, 'tcx>, local: Local, @@ -95,7 +95,7 @@ impl Qualifs<'mir, 'tcx> { /// Returns `true` if `local` is `HasMutInterior` at the given `Location`. /// /// Only updates the cursor if absolutely necessary. - fn has_mut_interior( + pub fn has_mut_interior( &mut self, ccx: &'mir ConstCx<'mir, 'tcx>, local: Local, @@ -232,30 +232,15 @@ impl Validator<'mir, 'tcx> { self.qualifs.in_return_place(self.ccx) } - /// Emits an error at the given `span` if an expression cannot be evaluated in the current - /// context. - pub fn check_op_spanned(&mut self, op: O, span: Span) - where - O: NonConstOp, - { - debug!("check_op: op={:?}", op); - - if op.is_allowed_in_item(self) { - return; - } - - if self.tcx.sess.opts.debugging_opts.unleash_the_miri_inside_of_you { - self.tcx.sess.miri_unleashed_feature(span, O::feature_gate()); - return; - } - - op.emit_error(self, span); - } - /// Emits an error if an expression cannot be evaluated in the current context. pub fn check_op(&mut self, op: impl NonConstOp) { - let span = self.span; - self.check_op_spanned(op, span) + ops::non_const(self.ccx, op, self.span); + } + + /// Emits an error at the given `span` if an expression cannot be evaluated in the current + /// context. + pub fn check_op_spanned(&mut self, op: impl NonConstOp, span: Span) { + ops::non_const(self.ccx, op, span); } fn check_static(&mut self, def_id: DefId, span: Span) { @@ -577,6 +562,12 @@ impl Visitor<'tcx> for Validator<'mir, 'tcx> { // projections that cannot be `NeedsDrop`. TerminatorKind::Drop { location: dropped_place, .. } | TerminatorKind::DropAndReplace { location: dropped_place, .. } => { + // If we are checking live drops after drop-elaboration, don't emit duplicate + // errors here. + if super::post_drop_elaboration::checking_enabled(self.tcx) { + return; + } + let mut err_span = self.span; // Check to see if the type of this place can ever have a drop impl. If not, this diff --git a/src/librustc_mir/transform/mod.rs b/src/librustc_mir/transform/mod.rs index 26725a2ac02d5..4240b528a6124 100644 --- a/src/librustc_mir/transform/mod.rs +++ b/src/librustc_mir/transform/mod.rs @@ -49,6 +49,7 @@ pub(crate) fn provide(providers: &mut Providers<'_>) { mir_const, mir_const_qualif, mir_validated, + mir_drops_elaborated_and_const_checked, optimized_mir, is_mir_available, promoted_mir, @@ -294,12 +295,31 @@ fn mir_validated( (tcx.alloc_steal_mir(body), tcx.alloc_steal_promoted(promoted)) } -fn run_optimization_passes<'tcx>( +fn mir_drops_elaborated_and_const_checked<'tcx>( + tcx: TyCtxt<'tcx>, + def_id: LocalDefId, +) -> Steal> { + // (Mir-)Borrowck uses `mir_validated`, so we have to force it to + // execute before we can steal. + tcx.ensure().mir_borrowck(def_id); + + let (body, _) = tcx.mir_validated(def_id); + let mut body = body.steal(); + + run_post_borrowck_cleanup_passes(tcx, &mut body, def_id, None); + check_consts::post_drop_elaboration::check_live_drops(tcx, def_id, &body); + tcx.alloc_steal_mir(body) +} + +/// After this series of passes, no lifetime analysis based on borrowing can be done. +fn run_post_borrowck_cleanup_passes<'tcx>( tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>, def_id: LocalDefId, promoted: Option, ) { + debug!("post_borrowck_cleanup({:?})", def_id); + let post_borrowck_cleanup: &[&dyn MirPass<'tcx>] = &[ // Remove all things only needed by analysis &no_landing_pads::NoLandingPads::new(tcx), @@ -318,9 +338,24 @@ fn run_optimization_passes<'tcx>( // but before optimizations begin. &add_retag::AddRetag, &simplify::SimplifyCfg::new("elaborate-drops"), - // No lifetime analysis based on borrowing can be done from here on out. ]; + run_passes( + tcx, + body, + InstanceDef::Item(def_id.to_def_id()), + promoted, + MirPhase::DropElab, + &[post_borrowck_cleanup], + ); +} + +fn run_optimization_passes<'tcx>( + tcx: TyCtxt<'tcx>, + body: &mut Body<'tcx>, + def_id: LocalDefId, + promoted: Option, +) { let optimizations: &[&dyn MirPass<'tcx>] = &[ &unreachable_prop::UnreachablePropagation, &uninhabited_enum_branching::UninhabitedEnumBranching, @@ -368,6 +403,7 @@ fn run_optimization_passes<'tcx>( let mir_opt_level = tcx.sess.opts.debugging_opts.mir_opt_level; + #[rustfmt::skip] run_passes( tcx, body, @@ -375,7 +411,6 @@ fn run_optimization_passes<'tcx>( promoted, MirPhase::Optimized, &[ - post_borrowck_cleanup, if mir_opt_level > 0 { optimizations } else { no_optimizations }, pre_codegen_cleanup, ], @@ -393,12 +428,7 @@ fn optimized_mir(tcx: TyCtxt<'_>, def_id: DefId) -> Body<'_> { let def_id = def_id.expect_local(); - // (Mir-)Borrowck uses `mir_validated`, so we have to force it to - // execute before we can steal. - tcx.ensure().mir_borrowck(def_id); - - let (body, _) = tcx.mir_validated(def_id); - let mut body = body.steal(); + let mut body = tcx.mir_drops_elaborated_and_const_checked(def_id).steal(); run_optimization_passes(tcx, &mut body, def_id, None); debug_assert!(!body.has_free_regions(), "Free regions in optimized MIR"); @@ -418,6 +448,7 @@ fn promoted_mir(tcx: TyCtxt<'_>, def_id: DefId) -> IndexVec> let mut promoted = promoted.steal(); for (p, mut body) in promoted.iter_enumerated_mut() { + run_post_borrowck_cleanup_passes(tcx, &mut body, def_id, Some(p)); run_optimization_passes(tcx, &mut body, def_id, Some(p)); } diff --git a/src/librustc_span/symbol.rs b/src/librustc_span/symbol.rs index fea5880f01ebd..fdeb58b7b7a31 100644 --- a/src/librustc_span/symbol.rs +++ b/src/librustc_span/symbol.rs @@ -227,6 +227,7 @@ symbols! { const_loop, const_mut_refs, const_panic, + const_precise_live_drops, const_raw_ptr_deref, const_raw_ptr_to_usize_cast, const_transmute, diff --git a/src/test/ui/consts/control-flow/drop-fail.precise.stderr b/src/test/ui/consts/control-flow/drop-fail.precise.stderr new file mode 100644 index 0000000000000..b4b6be8a1e5f0 --- /dev/null +++ b/src/test/ui/consts/control-flow/drop-fail.precise.stderr @@ -0,0 +1,15 @@ +error[E0493]: destructors cannot be evaluated at compile-time + --> $DIR/drop-fail.rs:10:9 + | +LL | let x = Some(Vec::new()); + | ^ constants cannot evaluate destructors + +error[E0493]: destructors cannot be evaluated at compile-time + --> $DIR/drop-fail.rs:41:9 + | +LL | let mut tmp = None; + | ^^^^^^^ constants cannot evaluate destructors + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0493`. diff --git a/src/test/ui/consts/control-flow/drop-failure.rs b/src/test/ui/consts/control-flow/drop-fail.rs similarity index 68% rename from src/test/ui/consts/control-flow/drop-failure.rs rename to src/test/ui/consts/control-flow/drop-fail.rs index 9da5546976c75..7bd36726cead5 100644 --- a/src/test/ui/consts/control-flow/drop-failure.rs +++ b/src/test/ui/consts/control-flow/drop-fail.rs @@ -1,11 +1,14 @@ +// revisions: stock precise + #![feature(const_if_match)] #![feature(const_loop)] +#![cfg_attr(precise, feature(const_precise_live_drops))] -// `x` is *not* always moved into the final value may be dropped inside the initializer. +// `x` is *not* always moved into the final value and may be dropped inside the initializer. const _: Option> = { let y: Option> = None; let x = Some(Vec::new()); - //~^ ERROR destructors cannot be evaluated at compile-time + //[stock,precise]~^ ERROR destructors cannot be evaluated at compile-time if true { x @@ -18,7 +21,7 @@ const _: Option> = { // existing analysis. const _: Vec = { let vec_tuple = (Vec::new(),); - //~^ ERROR destructors cannot be evaluated at compile-time + //[stock]~^ ERROR destructors cannot be evaluated at compile-time vec_tuple.0 }; @@ -26,7 +29,7 @@ const _: Vec = { // This applies to single-field enum variants as well. const _: Vec = { let x: Result<_, Vec> = Ok(Vec::new()); - //~^ ERROR destructors cannot be evaluated at compile-time + //[stock]~^ ERROR destructors cannot be evaluated at compile-time match x { Ok(x) | Err(x) => x, @@ -36,7 +39,7 @@ const _: Vec = { const _: Option> = { let mut some = Some(Vec::new()); let mut tmp = None; - //~^ ERROR destructors cannot be evaluated at compile-time + //[stock,precise]~^ ERROR destructors cannot be evaluated at compile-time let mut i = 0; while i < 10 { diff --git a/src/test/ui/consts/control-flow/drop-failure.stderr b/src/test/ui/consts/control-flow/drop-fail.stock.stderr similarity index 85% rename from src/test/ui/consts/control-flow/drop-failure.stderr rename to src/test/ui/consts/control-flow/drop-fail.stock.stderr index 3eec3a929a07f..77cded5c438b5 100644 --- a/src/test/ui/consts/control-flow/drop-failure.stderr +++ b/src/test/ui/consts/control-flow/drop-fail.stock.stderr @@ -1,23 +1,23 @@ error[E0493]: destructors cannot be evaluated at compile-time - --> $DIR/drop-failure.rs:7:9 + --> $DIR/drop-fail.rs:10:9 | LL | let x = Some(Vec::new()); | ^ constants cannot evaluate destructors error[E0493]: destructors cannot be evaluated at compile-time - --> $DIR/drop-failure.rs:20:9 + --> $DIR/drop-fail.rs:23:9 | LL | let vec_tuple = (Vec::new(),); | ^^^^^^^^^ constants cannot evaluate destructors error[E0493]: destructors cannot be evaluated at compile-time - --> $DIR/drop-failure.rs:28:9 + --> $DIR/drop-fail.rs:31:9 | LL | let x: Result<_, Vec> = Ok(Vec::new()); | ^ constants cannot evaluate destructors error[E0493]: destructors cannot be evaluated at compile-time - --> $DIR/drop-failure.rs:38:9 + --> $DIR/drop-fail.rs:41:9 | LL | let mut tmp = None; | ^^^^^^^ constants cannot evaluate destructors diff --git a/src/test/ui/consts/control-flow/drop-success.rs b/src/test/ui/consts/control-flow/drop-pass.rs similarity index 89% rename from src/test/ui/consts/control-flow/drop-success.rs rename to src/test/ui/consts/control-flow/drop-pass.rs index 185d6b639962b..b0afd76c4e6ef 100644 --- a/src/test/ui/consts/control-flow/drop-success.rs +++ b/src/test/ui/consts/control-flow/drop-pass.rs @@ -1,7 +1,9 @@ // run-pass +// revisions: stock precise #![feature(const_if_match)] #![feature(const_loop)] +#![cfg_attr(precise, feature(const_precise_live_drops))] // `x` is always moved into the final value and is not dropped inside the initializer. const _: Option> = { diff --git a/src/test/ui/consts/control-flow/drop-precise.rs b/src/test/ui/consts/control-flow/drop-precise.rs new file mode 100644 index 0000000000000..95df76d990554 --- /dev/null +++ b/src/test/ui/consts/control-flow/drop-precise.rs @@ -0,0 +1,20 @@ +// run-pass +// gate-test-const_precise_live_drops + +#![feature(const_if_match)] +#![feature(const_loop)] +#![feature(const_precise_live_drops)] + +const _: Vec = { + let vec_tuple = (Vec::new(),); + vec_tuple.0 +}; + +const _: Vec = { + let x: Result<_, Vec> = Ok(Vec::new()); + match x { + Ok(x) | Err(x) => x, + } +}; + +fn main() {}