Skip to content

Commit ec3d993

Browse files
committed
Add cycle checking to cleanup control flow validation
1 parent f49126e commit ec3d993

File tree

2 files changed

+54
-22
lines changed

2 files changed

+54
-22
lines changed

compiler/rustc_const_eval/src/transform/validate.rs

+47-18
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
//! Validates the MIR to ensure that invariants are upheld.
22
3-
use std::collections::hash_map::Entry;
4-
53
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
64
use rustc_index::bit_set::BitSet;
5+
use rustc_index::vec::IndexVec;
76
use rustc_infer::traits::Reveal;
87
use rustc_middle::mir::interpret::Scalar;
98
use rustc_middle::mir::visit::NonUseContext::VarDebugInfo;
@@ -140,23 +139,27 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
140139
fn check_cleanup_control_flow(&self) {
141140
let doms = self.body.basic_blocks.dominators();
142141
let mut post_contract_node = FxHashMap::default();
142+
// Reusing the allocation across invocations of the closure
143+
let mut dom_path = vec![];
143144
let mut get_post_contract_node = |mut bb| {
144-
if let Some(res) = post_contract_node.get(&bb) {
145-
return *res;
146-
}
147-
let mut dom_path = vec![];
148-
while self.body.basic_blocks[bb].is_cleanup {
145+
let root = loop {
146+
if let Some(root) = post_contract_node.get(&bb) {
147+
break *root;
148+
}
149+
let parent = doms.immediate_dominator(bb);
149150
dom_path.push(bb);
150-
bb = doms.immediate_dominator(bb);
151-
}
152-
let root = *dom_path.last().unwrap();
153-
for bb in dom_path {
151+
if !self.body.basic_blocks[parent].is_cleanup {
152+
break bb;
153+
}
154+
bb = parent;
155+
};
156+
for bb in dom_path.drain(..) {
154157
post_contract_node.insert(bb, root);
155158
}
156159
root
157160
};
158161

159-
let mut parent = FxHashMap::default();
162+
let mut parent = IndexVec::from_elem(None, &self.body.basic_blocks);
160163
for (bb, bb_data) in self.body.basic_blocks.iter_enumerated() {
161164
if !bb_data.is_cleanup || !self.reachable_blocks.contains(bb) {
162165
continue;
@@ -167,23 +170,49 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
167170
if s == bb {
168171
continue;
169172
}
170-
match parent.entry(bb) {
171-
Entry::Vacant(e) => {
172-
e.insert(s);
173+
let parent = &mut parent[bb];
174+
match parent {
175+
None => {
176+
*parent = Some(s);
173177
}
174-
Entry::Occupied(e) if s != *e.get() => self.fail(
178+
Some(e) if *e == s => (),
179+
Some(e) => self.fail(
175180
Location { block: bb, statement_index: 0 },
176181
format!(
177182
"Cleanup control flow violation: The blocks dominated by {:?} have edges to both {:?} and {:?}",
178183
bb,
179184
s,
180-
*e.get()
185+
*e
181186
)
182187
),
183-
Entry::Occupied(_) => (),
184188
}
185189
}
186190
}
191+
192+
// Check for cycles
193+
let mut stack = FxHashSet::default();
194+
for i in 0..parent.len() {
195+
let mut bb = BasicBlock::from_usize(i);
196+
stack.clear();
197+
stack.insert(bb);
198+
loop {
199+
let Some(parent )= parent[bb].take() else {
200+
break
201+
};
202+
let no_cycle = stack.insert(parent);
203+
if !no_cycle {
204+
self.fail(
205+
Location { block: bb, statement_index: 0 },
206+
format!(
207+
"Cleanup control flow violation: Cycle involving edge {:?} -> {:?}",
208+
bb, parent,
209+
),
210+
);
211+
break;
212+
}
213+
bb = parent;
214+
}
215+
}
187216
}
188217

189218
/// Check if src can be assigned into dest.

compiler/rustc_middle/src/mir/syntax.rs

+7-4
Original file line numberDiff line numberDiff line change
@@ -512,13 +512,16 @@ pub struct CopyNonOverlapping<'tcx> {
512512
/// must also be `cleanup`. This is a part of the type system and checked statically, so it is
513513
/// still an error to have such an edge in the CFG even if it's known that it won't be taken at
514514
/// runtime.
515-
/// 4. The induced subgraph on cleanup blocks must look roughly like an upside down tree. This is
516-
/// necessary to ensure that landing pad information can be correctly codegened. More precisely:
515+
/// 4. The control flow between cleanup blocks must look like an upside down tree. Roughly
516+
/// speaking, this means that control flow that looks like a V is allowed, while control flow
517+
/// that looks like a W is not. This is necessary to ensure that landing pad information can be
518+
/// correctly codegened on MSVC. More precisely:
517519
///
518520
/// Begin with the standard control flow graph `G`. Modify `G` as follows: for any two cleanup
519521
/// vertices `u` and `v` such that `u` dominates `v`, contract `u` and `v` into a single vertex,
520-
/// deleting self edges and duplicate edges in the process. The cleanup blocks of the resulting
521-
/// graph must form an inverted forest.
522+
/// deleting self edges and duplicate edges in the process. Now remove all vertices from `G`
523+
/// that are not cleanup vertices or are not reachable. The resulting graph must be an inverted
524+
/// tree, that is each vertex may have at most one successor and there may be no cycles.
522525
#[derive(Clone, TyEncodable, TyDecodable, Hash, HashStable, PartialEq, TypeFoldable, TypeVisitable)]
523526
pub enum TerminatorKind<'tcx> {
524527
/// Block has one successor; we continue execution there.

0 commit comments

Comments
 (0)