Skip to content

Commit

Permalink
Auto merge of rust-lang#64470 - ecstatic-morse:split-promotion-and-va…
Browse files Browse the repository at this point in the history
…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
bors committed Sep 29, 2019
2 parents b61e694 + 0bf1a80 commit 0bbab7d
Show file tree
Hide file tree
Showing 28 changed files with 2,009 additions and 290 deletions.
2 changes: 2 additions & 0 deletions src/librustc/session/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1359,6 +1359,8 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options,
"describes how to render the `rendered` field of json diagnostics"),
unleash_the_miri_inside_of_you: bool = (false, parse_bool, [TRACKED],
"take the breaks off const evaluation. NOTE: this is unsound"),
suppress_const_validation_back_compat_ice: bool = (false, parse_bool, [TRACKED],
"silence ICE triggered when the new const validator disagrees with the old"),
osx_rpath_install_name: bool = (false, parse_bool, [TRACKED],
"pass `-install_name @rpath/...` to the macOS linker"),
sanitizer: Option<Sanitizer> = (None, parse_sanitizer, [TRACKED],
Expand Down
148 changes: 148 additions & 0 deletions src/librustc_mir/dataflow/impls/indirect_mutation.rs
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.
}
}
}
8 changes: 4 additions & 4 deletions src/librustc_mir/dataflow/impls/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ use super::drop_flag_effects_for_function_entry;
use super::drop_flag_effects_for_location;
use super::on_lookup_result_bits;

mod storage_liveness;

pub use self::storage_liveness::*;

mod borrowed_locals;
mod indirect_mutation;
mod storage_liveness;

pub use self::borrowed_locals::*;
pub use self::indirect_mutation::IndirectlyMutableLocals;
pub use self::storage_liveness::*;

pub(super) mod borrows;

Expand Down
1 change: 1 addition & 0 deletions src/librustc_mir/dataflow/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pub use self::impls::DefinitelyInitializedPlaces;
pub use self::impls::EverInitializedPlaces;
pub use self::impls::borrows::Borrows;
pub use self::impls::HaveBeenBorrowedLocals;
pub use self::impls::IndirectlyMutableLocals;
pub use self::at_location::{FlowAtLocation, FlowsAtLocation};
pub(crate) use self::drop_flag_effects::*;

Expand Down
52 changes: 52 additions & 0 deletions src/librustc_mir/transform/check_consts/mod.rs
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()
}
Loading

0 comments on commit 0bbab7d

Please sign in to comment.