|
| 1 | +//! The "Shadow Graph" is maintained on the main thread and which |
| 2 | +//! tracks each message relating to the dep-graph and applies some |
| 3 | +//! sanity checks as they go by. If an error results, it means you get |
| 4 | +//! a nice stack-trace telling you precisely what caused the error. |
| 5 | +//! |
| 6 | +//! NOTE: This is a debugging facility which can potentially have non-trivial |
| 7 | +//! runtime impact. Therefore, it is largely compiled out if |
| 8 | +//! debug-assertions are not enabled. |
| 9 | +//! |
| 10 | +//! The basic sanity check, always enabled, is that there is always a |
| 11 | +//! task (or ignore) on the stack when you do read/write. |
| 12 | +//! |
| 13 | +//! Optionally, if you specify RUST_FORBID_DEP_GRAPH_EDGE, you can |
| 14 | +//! specify an edge filter to be applied to each edge as it is |
| 15 | +//! created. See `./README.md` for details. |
| 16 | +
|
| 17 | +use hir::def_id::DefId; |
| 18 | +use std::cell::{BorrowState, RefCell}; |
| 19 | +use std::env; |
| 20 | + |
| 21 | +use super::DepNode; |
| 22 | +use super::thread::DepMessage; |
| 23 | +use super::debug::EdgeFilter; |
| 24 | + |
| 25 | +pub struct ShadowGraph { |
| 26 | + // if you push None onto the stack, that corresponds to an Ignore |
| 27 | + stack: RefCell<Vec<Option<DepNode<DefId>>>>, |
| 28 | + forbidden_edge: Option<EdgeFilter>, |
| 29 | +} |
| 30 | + |
| 31 | +const ENABLED: bool = cfg!(debug_assertions); |
| 32 | + |
| 33 | +impl ShadowGraph { |
| 34 | + pub fn new() -> Self { |
| 35 | + let forbidden_edge = if !ENABLED { |
| 36 | + None |
| 37 | + } else { |
| 38 | + match env::var("RUST_FORBID_DEP_GRAPH_EDGE") { |
| 39 | + Ok(s) => { |
| 40 | + match EdgeFilter::new(&s) { |
| 41 | + Ok(f) => Some(f), |
| 42 | + Err(err) => bug!("RUST_FORBID_DEP_GRAPH_EDGE invalid: {}", err), |
| 43 | + } |
| 44 | + } |
| 45 | + Err(_) => None, |
| 46 | + } |
| 47 | + }; |
| 48 | + |
| 49 | + ShadowGraph { |
| 50 | + stack: RefCell::new(vec![]), |
| 51 | + forbidden_edge: forbidden_edge, |
| 52 | + } |
| 53 | + } |
| 54 | + |
| 55 | + pub fn enqueue(&self, message: &DepMessage) { |
| 56 | + if ENABLED { |
| 57 | + match self.stack.borrow_state() { |
| 58 | + BorrowState::Unused => {} |
| 59 | + _ => { |
| 60 | + // When we apply edge filters, that invokes the |
| 61 | + // Debug trait on DefIds, which in turn reads from |
| 62 | + // various bits of state and creates reads! Ignore |
| 63 | + // those recursive reads. |
| 64 | + return; |
| 65 | + } |
| 66 | + } |
| 67 | + |
| 68 | + let mut stack = self.stack.borrow_mut(); |
| 69 | + match *message { |
| 70 | + DepMessage::Read(ref n) => self.check_edge(Some(Some(n)), top(&stack)), |
| 71 | + DepMessage::Write(ref n) => self.check_edge(top(&stack), Some(Some(n))), |
| 72 | + DepMessage::PushTask(ref n) => stack.push(Some(n.clone())), |
| 73 | + DepMessage::PushIgnore => stack.push(None), |
| 74 | + DepMessage::PopTask(_) | |
| 75 | + DepMessage::PopIgnore => { |
| 76 | + // we could easily check that the stack is |
| 77 | + // well-formed here, but since we use closures and |
| 78 | + // RAII accessors, this bug basically never |
| 79 | + // happens, so it seems not worth the overhead |
| 80 | + stack.pop(); |
| 81 | + } |
| 82 | + DepMessage::Query => (), |
| 83 | + } |
| 84 | + } |
| 85 | + } |
| 86 | + |
| 87 | + fn check_edge(&self, |
| 88 | + source: Option<Option<&DepNode<DefId>>>, |
| 89 | + target: Option<Option<&DepNode<DefId>>>) { |
| 90 | + assert!(ENABLED); |
| 91 | + match (source, target) { |
| 92 | + // cannot happen, one side is always Some(Some(_)) |
| 93 | + (None, None) => unreachable!(), |
| 94 | + |
| 95 | + // nothing on top of the stack |
| 96 | + (None, Some(n)) | (Some(n), None) => bug!("read/write of {:?} but no current task", n), |
| 97 | + |
| 98 | + // this corresponds to an Ignore being top of the stack |
| 99 | + (Some(None), _) | (_, Some(None)) => (), |
| 100 | + |
| 101 | + // a task is on top of the stack |
| 102 | + (Some(Some(source)), Some(Some(target))) => { |
| 103 | + if let Some(ref forbidden_edge) = self.forbidden_edge { |
| 104 | + if forbidden_edge.test(source, target) { |
| 105 | + bug!("forbidden edge {:?} -> {:?} created", source, target) |
| 106 | + } |
| 107 | + } |
| 108 | + } |
| 109 | + } |
| 110 | + } |
| 111 | +} |
| 112 | + |
| 113 | +// Do a little juggling: we get back a reference to an option at the |
| 114 | +// top of the stack, convert it to an optional reference. |
| 115 | +fn top<'s>(stack: &'s Vec<Option<DepNode<DefId>>>) -> Option<Option<&'s DepNode<DefId>>> { |
| 116 | + stack.last() |
| 117 | + .map(|n: &'s Option<DepNode<DefId>>| -> Option<&'s DepNode<DefId>> { |
| 118 | + // (*) |
| 119 | + // (*) type annotation just there to clarify what would |
| 120 | + // otherwise be some *really* obscure code |
| 121 | + n.as_ref() |
| 122 | + }) |
| 123 | +} |
0 commit comments