forked from rust-lang/rust
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Auto merge of rust-lang#64470 - ecstatic-morse:split-promotion-and-va…
…lidation, r=eddyb,oli-obk Implement dataflow-based const validation This PR adds a separate, dataflow-enabled pass that checks the bodies of `const`s, `static`s and `const fn`s for [const safety](https://github.com/rust-rfcs/const-eval/blob/master/const.md). This is based on my work in rust-lang#63860, which tried to integrate into the existing pass in [`qualify_consts.rs`](https://github.com/rust-lang/rust/blob/master/src/librustc_mir/transform/qualify_consts.rs). However, the resulting pass was even more unwieldy than the original. Unlike its predecessor, this PR is designed to be combined with rust-lang#63812 to replace the existing pass completely. The new checker lives in [`librustc_mir/transform/check_consts`](https://github.com/ecstatic-morse/rust/tree/split-promotion-and-validation/src/librustc_mir/transform/check_consts). [`qualifs.rs`](https://github.com/ecstatic-morse/rust/blob/split-promotion-and-validation/src/librustc_mir/transform/check_consts/qualifs.rs) contains small modifications to the existing `Qualif` trait and its implementors, but is mostly unchanged except for the removal of `IsNotPromotable` and `IsNotImplicitlyPromotable`, which are only necessary for promotion. [`resolver.rs`](https://github.com/ecstatic-morse/rust/blob/split-promotion-and-validation/src/librustc_mir/transform/check_consts/resolver.rs) contains the dataflow analysis used to propagate qualifs between locals. Finally, [`validation.rs`](https://github.com/ecstatic-morse/rust/blob/split-promotion-and-validation/src/librustc_mir/transform/check_consts/validation.rs) contains a refactored version of the existing [`Visitor`](https://github.com/rust-lang/rust/blob/ca3766e2e58f462a20922e42c821a37eaf0e13db/src/librustc_mir/transform/qualify_consts.rs#L1024) in `qualfy_consts.rs`. All errors have been associated with a `struct` to make [comparison with the existing pass](https://github.com/ecstatic-morse/rust/blob/1c19f2d540ca0a964900449d79a5d5181b43146d/src/librustc_mir/transform/qualify_consts.rs#L1006) simple. The existing validation logic in [`qualify_consts`](https://github.com/rust-lang/rust/blob/master/src/librustc_mir/transform/qualify_consts.rs) has been modified to allow it to run in parallel with the new validator. If [`use_new_validator`](https://github.com/rust-lang/rust/pull/64470/files#diff-c2552a106550d05b69d5e07612f0f812R950) is not set, the old validation will be responsible for actually generating the errors, but those errors can be compared with the ones from the new validator.
- Loading branch information
Showing
28 changed files
with
2,009 additions
and
290 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
use rustc::mir::visit::Visitor; | ||
use rustc::mir::{self, Local, Location}; | ||
use rustc::ty::{self, TyCtxt}; | ||
use rustc_data_structures::bit_set::BitSet; | ||
use syntax_pos::DUMMY_SP; | ||
|
||
use crate::dataflow::{self, GenKillSet}; | ||
|
||
/// Whether a borrow to a `Local` has been created that could allow that `Local` to be mutated | ||
/// indirectly. This could either be a mutable reference (`&mut`) or a shared borrow if the type of | ||
/// that `Local` allows interior mutability. Operations that can mutate local's indirectly include: | ||
/// assignments through a pointer (`*p = 42`), function calls, drop terminators and inline assembly. | ||
/// | ||
/// If this returns false for a `Local` at a given statement (or terminator), that `Local` could | ||
/// not possibly have been mutated indirectly prior to that statement. | ||
#[derive(Copy, Clone)] | ||
pub struct IndirectlyMutableLocals<'mir, 'tcx> { | ||
body: &'mir mir::Body<'tcx>, | ||
tcx: TyCtxt<'tcx>, | ||
param_env: ty::ParamEnv<'tcx>, | ||
} | ||
|
||
impl<'mir, 'tcx> IndirectlyMutableLocals<'mir, 'tcx> { | ||
pub fn new( | ||
tcx: TyCtxt<'tcx>, | ||
body: &'mir mir::Body<'tcx>, | ||
param_env: ty::ParamEnv<'tcx>, | ||
) -> Self { | ||
IndirectlyMutableLocals { body, tcx, param_env } | ||
} | ||
|
||
fn transfer_function<'a>( | ||
&self, | ||
trans: &'a mut GenKillSet<Local>, | ||
) -> TransferFunction<'a, 'mir, 'tcx> { | ||
TransferFunction { | ||
body: self.body, | ||
tcx: self.tcx, | ||
param_env: self.param_env, | ||
trans | ||
} | ||
} | ||
} | ||
|
||
impl<'mir, 'tcx> dataflow::BitDenotation<'tcx> for IndirectlyMutableLocals<'mir, 'tcx> { | ||
type Idx = Local; | ||
|
||
fn name() -> &'static str { "mut_borrowed_locals" } | ||
|
||
fn bits_per_block(&self) -> usize { | ||
self.body.local_decls.len() | ||
} | ||
|
||
fn start_block_effect(&self, _entry_set: &mut BitSet<Local>) { | ||
// Nothing is borrowed on function entry | ||
} | ||
|
||
fn statement_effect( | ||
&self, | ||
trans: &mut GenKillSet<Local>, | ||
loc: Location, | ||
) { | ||
let stmt = &self.body[loc.block].statements[loc.statement_index]; | ||
self.transfer_function(trans).visit_statement(stmt, loc); | ||
} | ||
|
||
fn terminator_effect( | ||
&self, | ||
trans: &mut GenKillSet<Local>, | ||
loc: Location, | ||
) { | ||
let terminator = self.body[loc.block].terminator(); | ||
self.transfer_function(trans).visit_terminator(terminator, loc); | ||
} | ||
|
||
fn propagate_call_return( | ||
&self, | ||
_in_out: &mut BitSet<Local>, | ||
_call_bb: mir::BasicBlock, | ||
_dest_bb: mir::BasicBlock, | ||
_dest_place: &mir::Place<'tcx>, | ||
) { | ||
// Nothing to do when a call returns successfully | ||
} | ||
} | ||
|
||
impl<'mir, 'tcx> dataflow::BottomValue for IndirectlyMutableLocals<'mir, 'tcx> { | ||
// bottom = unborrowed | ||
const BOTTOM_VALUE: bool = false; | ||
} | ||
|
||
/// A `Visitor` that defines the transfer function for `IndirectlyMutableLocals`. | ||
struct TransferFunction<'a, 'mir, 'tcx> { | ||
trans: &'a mut GenKillSet<Local>, | ||
body: &'mir mir::Body<'tcx>, | ||
tcx: TyCtxt<'tcx>, | ||
param_env: ty::ParamEnv<'tcx>, | ||
} | ||
|
||
impl<'tcx> Visitor<'tcx> for TransferFunction<'_, '_, 'tcx> { | ||
fn visit_rvalue( | ||
&mut self, | ||
rvalue: &mir::Rvalue<'tcx>, | ||
location: Location, | ||
) { | ||
if let mir::Rvalue::Ref(_, kind, ref borrowed_place) = *rvalue { | ||
let is_mut = match kind { | ||
mir::BorrowKind::Mut { .. } => true, | ||
|
||
| mir::BorrowKind::Shared | ||
| mir::BorrowKind::Shallow | ||
| mir::BorrowKind::Unique | ||
=> { | ||
!borrowed_place | ||
.ty(self.body, self.tcx) | ||
.ty | ||
.is_freeze(self.tcx, self.param_env, DUMMY_SP) | ||
} | ||
}; | ||
|
||
if is_mut { | ||
match borrowed_place.base { | ||
mir::PlaceBase::Local(borrowed_local) if !borrowed_place.is_indirect() | ||
=> self.trans.gen(borrowed_local), | ||
|
||
_ => (), | ||
} | ||
} | ||
} | ||
|
||
self.super_rvalue(rvalue, location); | ||
} | ||
|
||
|
||
fn visit_terminator(&mut self, terminator: &mir::Terminator<'tcx>, location: Location) { | ||
// This method purposely does nothing except call `super_terminator`. It exists solely to | ||
// document the subtleties around drop terminators. | ||
|
||
self.super_terminator(terminator, location); | ||
|
||
if let mir::TerminatorKind::Drop { location: _, .. } | ||
| mir::TerminatorKind::DropAndReplace { location: _, .. } = &terminator.kind | ||
{ | ||
// Although drop terminators mutably borrow the location being dropped, that borrow | ||
// cannot live beyond the drop terminator because the dropped location is invalidated. | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
//! Check the bodies of `const`s, `static`s and `const fn`s for illegal operations. | ||
//! | ||
//! This module will eventually replace the parts of `qualify_consts.rs` that check whether a local | ||
//! has interior mutability or needs to be dropped, as well as the visitor that emits errors when | ||
//! it finds operations that are invalid in a certain context. | ||
use rustc::hir::def_id::DefId; | ||
use rustc::mir; | ||
use rustc::ty::{self, TyCtxt}; | ||
|
||
pub use self::qualifs::Qualif; | ||
|
||
pub mod ops; | ||
mod qualifs; | ||
mod resolver; | ||
pub mod validation; | ||
|
||
/// Information about the item currently being validated, as well as a reference to the global | ||
/// context. | ||
pub struct Item<'mir, 'tcx> { | ||
body: &'mir mir::Body<'tcx>, | ||
tcx: TyCtxt<'tcx>, | ||
def_id: DefId, | ||
param_env: ty::ParamEnv<'tcx>, | ||
mode: validation::Mode, | ||
} | ||
|
||
impl Item<'mir, 'tcx> { | ||
pub fn new( | ||
tcx: TyCtxt<'tcx>, | ||
def_id: DefId, | ||
body: &'mir mir::Body<'tcx>, | ||
) -> Self { | ||
let param_env = tcx.param_env(def_id); | ||
let mode = validation::Mode::for_item(tcx, def_id) | ||
.expect("const validation must only be run inside a const context"); | ||
|
||
Item { | ||
body, | ||
tcx, | ||
def_id, | ||
param_env, | ||
mode, | ||
} | ||
} | ||
} | ||
|
||
|
||
fn is_lang_panic_fn(tcx: TyCtxt<'tcx>, def_id: DefId) -> bool { | ||
Some(def_id) == tcx.lang_items().panic_fn() || | ||
Some(def_id) == tcx.lang_items().begin_panic_fn() | ||
} |
Oops, something went wrong.