summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_const_eval/src/transform/validate.rs
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_const_eval/src/transform/validate.rs')
-rw-r--r--compiler/rustc_const_eval/src/transform/validate.rs134
1 files changed, 108 insertions, 26 deletions
diff --git a/compiler/rustc_const_eval/src/transform/validate.rs b/compiler/rustc_const_eval/src/transform/validate.rs
index 5c9263dc5..dd168a9ac 100644
--- a/compiler/rustc_const_eval/src/transform/validate.rs
+++ b/compiler/rustc_const_eval/src/transform/validate.rs
@@ -1,7 +1,8 @@
//! Validates the MIR to ensure that invariants are upheld.
-use rustc_data_structures::fx::FxHashSet;
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_index::bit_set::BitSet;
+use rustc_index::vec::IndexVec;
use rustc_infer::traits::Reveal;
use rustc_middle::mir::interpret::Scalar;
use rustc_middle::mir::visit::NonUseContext::VarDebugInfo;
@@ -9,8 +10,8 @@ use rustc_middle::mir::visit::{PlaceContext, Visitor};
use rustc_middle::mir::{
traversal, AggregateKind, BasicBlock, BinOp, Body, BorrowKind, CastKind, CopyNonOverlapping,
Local, Location, MirPass, MirPhase, NonDivergingIntrinsic, Operand, Place, PlaceElem, PlaceRef,
- ProjectionElem, RuntimePhase, Rvalue, SourceScope, Statement, StatementKind, Terminator,
- TerminatorKind, UnOp, START_BLOCK,
+ ProjectionElem, RetagKind, RuntimePhase, Rvalue, SourceScope, Statement, StatementKind,
+ Terminator, TerminatorKind, UnOp, START_BLOCK,
};
use rustc_middle::ty::{self, InstanceDef, ParamEnv, Ty, TyCtxt, TypeVisitable};
use rustc_mir_dataflow::impls::MaybeStorageLive;
@@ -18,7 +19,7 @@ use rustc_mir_dataflow::storage::always_storage_live_locals;
use rustc_mir_dataflow::{Analysis, ResultsCursor};
use rustc_target::abi::{Size, VariantIdx};
-#[derive(Copy, Clone, Debug)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum EdgeKind {
Unwind,
Normal,
@@ -52,23 +53,25 @@ impl<'tcx> MirPass<'tcx> for Validator {
};
let always_live_locals = always_storage_live_locals(body);
- let storage_liveness = MaybeStorageLive::new(always_live_locals)
+ let storage_liveness = MaybeStorageLive::new(std::borrow::Cow::Owned(always_live_locals))
.into_engine(tcx, body)
.iterate_to_fixpoint()
.into_results_cursor(body);
- TypeChecker {
+ let mut checker = TypeChecker {
when: &self.when,
body,
tcx,
param_env,
mir_phase,
+ unwind_edge_count: 0,
reachable_blocks: traversal::reachable_as_bitset(body),
storage_liveness,
place_cache: Vec::new(),
value_cache: Vec::new(),
- }
- .visit_body(body);
+ };
+ checker.visit_body(body);
+ checker.check_cleanup_control_flow();
}
}
@@ -78,8 +81,9 @@ struct TypeChecker<'a, 'tcx> {
tcx: TyCtxt<'tcx>,
param_env: ParamEnv<'tcx>,
mir_phase: MirPhase,
+ unwind_edge_count: usize,
reachable_blocks: BitSet<BasicBlock>,
- storage_liveness: ResultsCursor<'a, 'tcx, MaybeStorageLive>,
+ storage_liveness: ResultsCursor<'a, 'tcx, MaybeStorageLive<'static>>,
place_cache: Vec<PlaceRef<'tcx>>,
value_cache: Vec<u128>,
}
@@ -102,7 +106,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
);
}
- fn check_edge(&self, location: Location, bb: BasicBlock, edge_kind: EdgeKind) {
+ fn check_edge(&mut self, location: Location, bb: BasicBlock, edge_kind: EdgeKind) {
if bb == START_BLOCK {
self.fail(location, "start block must not have predecessors")
}
@@ -111,10 +115,12 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
match (src.is_cleanup, bb.is_cleanup, edge_kind) {
// Non-cleanup blocks can jump to non-cleanup blocks along non-unwind edges
(false, false, EdgeKind::Normal)
- // Non-cleanup blocks can jump to cleanup blocks along unwind edges
- | (false, true, EdgeKind::Unwind)
// Cleanup blocks can jump to cleanup blocks along non-unwind edges
| (true, true, EdgeKind::Normal) => {}
+ // Non-cleanup blocks can jump to cleanup blocks along unwind edges
+ (false, true, EdgeKind::Unwind) => {
+ self.unwind_edge_count += 1;
+ }
// All other jumps are invalid
_ => {
self.fail(
@@ -134,6 +140,88 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
}
}
+ fn check_cleanup_control_flow(&self) {
+ if self.unwind_edge_count <= 1 {
+ return;
+ }
+ let doms = self.body.basic_blocks.dominators();
+ let mut post_contract_node = FxHashMap::default();
+ // Reusing the allocation across invocations of the closure
+ let mut dom_path = vec![];
+ let mut get_post_contract_node = |mut bb| {
+ let root = loop {
+ if let Some(root) = post_contract_node.get(&bb) {
+ break *root;
+ }
+ let parent = doms.immediate_dominator(bb);
+ dom_path.push(bb);
+ if !self.body.basic_blocks[parent].is_cleanup {
+ break bb;
+ }
+ bb = parent;
+ };
+ for bb in dom_path.drain(..) {
+ post_contract_node.insert(bb, root);
+ }
+ root
+ };
+
+ let mut parent = IndexVec::from_elem(None, &self.body.basic_blocks);
+ for (bb, bb_data) in self.body.basic_blocks.iter_enumerated() {
+ if !bb_data.is_cleanup || !self.reachable_blocks.contains(bb) {
+ continue;
+ }
+ let bb = get_post_contract_node(bb);
+ for s in bb_data.terminator().successors() {
+ let s = get_post_contract_node(s);
+ if s == bb {
+ continue;
+ }
+ let parent = &mut parent[bb];
+ match parent {
+ None => {
+ *parent = Some(s);
+ }
+ Some(e) if *e == s => (),
+ Some(e) => self.fail(
+ Location { block: bb, statement_index: 0 },
+ format!(
+ "Cleanup control flow violation: The blocks dominated by {:?} have edges to both {:?} and {:?}",
+ bb,
+ s,
+ *e
+ )
+ ),
+ }
+ }
+ }
+
+ // Check for cycles
+ let mut stack = FxHashSet::default();
+ for i in 0..parent.len() {
+ let mut bb = BasicBlock::from_usize(i);
+ stack.clear();
+ stack.insert(bb);
+ loop {
+ let Some(parent)= parent[bb].take() else {
+ break
+ };
+ let no_cycle = stack.insert(parent);
+ if !no_cycle {
+ self.fail(
+ Location { block: bb, statement_index: 0 },
+ format!(
+ "Cleanup control flow violation: Cycle involving edge {:?} -> {:?}",
+ bb, parent,
+ ),
+ );
+ break;
+ }
+ bb = parent;
+ }
+ }
+ }
+
/// Check if src can be assigned into dest.
/// This is not precise, it will accept some incorrect assignments.
fn mir_assign_valid_types(&self, src: Ty<'tcx>, dest: Ty<'tcx>) -> bool {
@@ -241,7 +329,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
};
let kind = match parent_ty.ty.kind() {
- &ty::Opaque(def_id, substs) => {
+ &ty::Alias(ty::Opaque, ty::AliasTy { def_id, substs, .. }) => {
self.tcx.bound_type_of(def_id).subst(self.tcx, substs).kind()
}
kind => kind,
@@ -652,7 +740,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
self.fail(location, "`SetDiscriminant`is not allowed until deaggregation");
}
let pty = place.ty(&self.body.local_decls, self.tcx).ty.kind();
- if !matches!(pty, ty::Adt(..) | ty::Generator(..) | ty::Opaque(..)) {
+ if !matches!(pty, ty::Adt(..) | ty::Generator(..) | ty::Alias(ty::Opaque, ..)) {
self.fail(
location,
format!(
@@ -667,10 +755,13 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
self.fail(location, "`Deinit`is not allowed until deaggregation");
}
}
- StatementKind::Retag(_, _) => {
+ StatementKind::Retag(kind, _) => {
// FIXME(JakobDegen) The validator should check that `self.mir_phase <
// DropsLowered`. However, this causes ICEs with generation of drop shims, which
// seem to fail to set their `MirPhase` correctly.
+ if *kind == RetagKind::Raw || *kind == RetagKind::TwoPhase {
+ self.fail(location, format!("explicit `{:?}` is forbidden", kind));
+ }
}
StatementKind::StorageLive(..)
| StatementKind::StorageDead(..)
@@ -686,17 +777,8 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
TerminatorKind::Goto { target } => {
self.check_edge(location, *target, EdgeKind::Normal);
}
- TerminatorKind::SwitchInt { targets, switch_ty, discr } => {
- let ty = discr.ty(&self.body.local_decls, self.tcx);
- if ty != *switch_ty {
- self.fail(
- location,
- format!(
- "encountered `SwitchInt` terminator with type mismatch: {:?} != {:?}",
- ty, switch_ty,
- ),
- );
- }
+ TerminatorKind::SwitchInt { targets, discr } => {
+ let switch_ty = discr.ty(&self.body.local_decls, self.tcx);
let target_width = self.tcx.sess.target.pointer_width;