1use std::env;
37use std::fs::{self, File};
38use std::io::Write;
39
40use rustc_data_structures::fx::FxIndexSet;
41use rustc_data_structures::graph::linked_graph::{Direction, INCOMING, NodeIndex, OUTGOING};
42use rustc_graphviz as dot;
43use rustc_hir as hir;
44use rustc_hir::Attribute;
45use rustc_hir::attrs::AttributeKind;
46use rustc_hir::def_id::{CRATE_DEF_ID, DefId, LocalDefId};
47use rustc_hir::intravisit::{self, Visitor};
48use rustc_middle::bug;
49use rustc_middle::dep_graph::{DepKind, DepNode, DepNodeFilter, EdgeFilter, RetainedDepGraph};
50use rustc_middle::hir::nested_filter;
51use rustc_middle::ty::TyCtxt;
52use rustc_span::{Span, Symbol, sym};
53use tracing::debug;
54
55use crate::diagnostics;
56
57#[allow(missing_docs)]
58pub(crate) fn assert_dep_graph(tcx: TyCtxt<'_>) {
59 tcx.dep_graph.with_ignore(|| {
60 let retained_dep_graph = tcx.dep_graph.retained_dep_graph();
63
64 if tcx.sess.opts.unstable_opts.dump_dep_graph {
65 if let Some(graph) = &retained_dep_graph {
66 dump_graph(graph);
67 }
68 }
69
70 if !tcx.sess.opts.unstable_opts.query_dep_graph {
71 return;
72 }
73
74 if !tcx.features().rustc_attrs() {
78 return;
79 }
80
81 let (if_this_changed, then_this_would_need) = {
83 let mut visitor =
84 IfThisChanged { tcx, if_this_changed: ::alloc::vec::Vec::new()vec![], then_this_would_need: ::alloc::vec::Vec::new()vec![] };
85 visitor.process_attrs(CRATE_DEF_ID);
86 tcx.hir_visit_all_item_likes_in_crate(&mut visitor);
87 (visitor.if_this_changed, visitor.then_this_would_need)
88 };
89
90 if !if_this_changed.is_empty() || !then_this_would_need.is_empty() {
91 if !tcx.sess.opts.unstable_opts.query_dep_graph {
{
::core::panicking::panic_fmt(format_args!("cannot use the `#[{0}]` or `#[{1}]` annotations without supplying `-Z query-dep-graph`",
sym::rustc_if_this_changed, sym::rustc_then_this_would_need));
}
};assert!(
92 tcx.sess.opts.unstable_opts.query_dep_graph,
93 "cannot use the `#[{}]` or `#[{}]` annotations \
94 without supplying `-Z query-dep-graph`",
95 sym::rustc_if_this_changed,
96 sym::rustc_then_this_would_need
97 );
98 }
99
100 check_paths(tcx, retained_dep_graph.as_ref(), &if_this_changed, &then_this_would_need);
102 })
103}
104
105type Sources = Vec<(Span, DefId, DepNode)>;
106type Targets = Vec<(Span, Symbol, hir::HirId, DepNode)>;
107
108struct IfThisChanged<'tcx> {
109 tcx: TyCtxt<'tcx>,
110 if_this_changed: Sources,
111 then_this_would_need: Targets,
112}
113
114impl<'tcx> IfThisChanged<'tcx> {
115 fn process_attrs(&mut self, def_id: LocalDefId) {
116 let def_path_hash = self.tcx.def_path_hash(def_id.to_def_id());
117 let hir_id = self.tcx.local_def_id_to_hir_id(def_id);
118 let attrs = self.tcx.hir_attrs(hir_id);
119 for attr in attrs {
120 if let Attribute::Parsed(AttributeKind::RustcIfThisChanged(span, dep_node)) = *attr {
121 let dep_node = match dep_node {
122 None => {
123 DepNode::from_def_path_hash(self.tcx, def_path_hash, DepKind::hir_owner)
124 }
125 Some(n) => {
126 match DepNode::from_label_string(self.tcx, n.as_str(), def_path_hash) {
127 Ok(n) => n,
128 Err(()) => self
129 .tcx
130 .dcx()
131 .emit_fatal(diagnostics::UnrecognizedDepNode { span, name: n }),
132 }
133 }
134 };
135 self.if_this_changed.push((span, def_id.to_def_id(), dep_node));
136 } else if let Attribute::Parsed(AttributeKind::RustcThenThisWouldNeed(dep_nodes)) = attr
137 {
138 for &n in dep_nodes {
139 let Ok(dep_node) =
140 DepNode::from_label_string(self.tcx, n.as_str(), def_path_hash)
141 else {
142 self.tcx.dcx().emit_fatal(diagnostics::UnrecognizedDepNode {
143 span: n.span,
144 name: n.name,
145 });
146 };
147 self.then_this_would_need.push((n.span, n.name, hir_id, dep_node));
148 }
149 }
150 }
151 }
152}
153
154impl<'tcx> Visitor<'tcx> for IfThisChanged<'tcx> {
155 type NestedFilter = nested_filter::OnlyBodies;
156
157 fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
158 self.tcx
159 }
160
161 fn visit_item(&mut self, item: &'tcx hir::Item<'tcx>) {
162 self.process_attrs(item.owner_id.def_id);
163 intravisit::walk_item(self, item);
164 }
165
166 fn visit_trait_item(&mut self, trait_item: &'tcx hir::TraitItem<'tcx>) {
167 self.process_attrs(trait_item.owner_id.def_id);
168 intravisit::walk_trait_item(self, trait_item);
169 }
170
171 fn visit_impl_item(&mut self, impl_item: &'tcx hir::ImplItem<'tcx>) {
172 self.process_attrs(impl_item.owner_id.def_id);
173 intravisit::walk_impl_item(self, impl_item);
174 }
175
176 fn visit_field_def(&mut self, s: &'tcx hir::FieldDef<'tcx>) {
177 self.process_attrs(s.def_id);
178 intravisit::walk_field_def(self, s);
179 }
180}
181
182fn check_paths<'tcx>(
183 tcx: TyCtxt<'tcx>,
184 retained_dep_graph: Option<&RetainedDepGraph>,
185 if_this_changed: &Sources,
186 then_this_would_need: &Targets,
187) {
188 if if_this_changed.is_empty() {
189 for &(target_span, _, _, _) in then_this_would_need {
190 tcx.dcx().emit_err(diagnostics::MissingIfThisChanged { span: target_span });
191 }
192 return;
193 }
194 let Some(query) = retained_dep_graph else { return };
195 for &(_, source_def_id, ref source_dep_node) in if_this_changed {
196 let dependents = query.transitive_predecessors(source_dep_node);
197 for &(target_span, ref target_pass, _, ref target_dep_node) in then_this_would_need {
198 if !dependents.contains(&target_dep_node) {
199 tcx.dcx().emit_err(diagnostics::NoPath {
200 span: target_span,
201 source: tcx.def_path_str(source_def_id),
202 target: *target_pass,
203 });
204 } else {
205 tcx.dcx().emit_err(diagnostics::Ok { span: target_span });
206 }
207 }
208 }
209}
210
211fn dump_graph(graph: &RetainedDepGraph) {
212 let path: String = env::var("RUST_DEP_GRAPH").unwrap_or_else(|_| "dep_graph".to_string());
213
214 let nodes = match env::var("RUST_DEP_GRAPH_FILTER") {
215 Ok(string) => {
216 let edge_filter =
218 EdgeFilter::new(&string).unwrap_or_else(|e| ::rustc_middle::util::bug::bug_fmt(format_args!("invalid filter: {0}", e))bug!("invalid filter: {}", e));
219 let sources = node_set(graph, &edge_filter.source);
220 let targets = node_set(graph, &edge_filter.target);
221 filter_nodes(graph, &sources, &targets)
222 }
223 Err(_) => graph.nodes().into_iter().map(|n| n.kind).collect(),
224 };
225 let edges = filter_edges(graph, &nodes);
226
227 {
228 let txt_path = ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0}.txt", path))
})format!("{path}.txt");
230 let mut file = File::create_buffered(&txt_path).unwrap();
231 for (source, target) in &edges {
232 file.write_fmt(format_args!("{0:?} -> {1:?}\n", source, target))write!(file, "{source:?} -> {target:?}\n").unwrap();
233 }
234 }
235
236 {
237 let dot_path = ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0}.dot", path))
})format!("{path}.dot");
239 let mut v = Vec::new();
240 dot::render(&GraphvizDepGraph(nodes, edges), &mut v).unwrap();
241 fs::write(dot_path, v).unwrap();
242 }
243}
244
245#[allow(missing_docs)]
246struct GraphvizDepGraph(FxIndexSet<DepKind>, Vec<(DepKind, DepKind)>);
247
248impl<'a> dot::GraphWalk<'a> for GraphvizDepGraph {
249 type Node = DepKind;
250 type Edge = (DepKind, DepKind);
251 fn nodes(&self) -> dot::Nodes<'_, DepKind> {
252 let nodes: Vec<_> = self.0.iter().cloned().collect();
253 nodes.into()
254 }
255 fn edges(&self) -> dot::Edges<'_, (DepKind, DepKind)> {
256 self.1[..].into()
257 }
258 fn source(&self, edge: &(DepKind, DepKind)) -> DepKind {
259 edge.0
260 }
261 fn target(&self, edge: &(DepKind, DepKind)) -> DepKind {
262 edge.1
263 }
264}
265
266impl<'a> dot::Labeller<'a> for GraphvizDepGraph {
267 type Node = DepKind;
268 type Edge = (DepKind, DepKind);
269 fn graph_id(&self) -> dot::Id<'_> {
270 dot::Id::new("DependencyGraph").unwrap()
271 }
272 fn node_id(&self, n: &DepKind) -> dot::Id<'_> {
273 let s: String = ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0:?}", n))
})format!("{n:?}")
274 .chars()
275 .map(|c| if c == '_' || c.is_alphanumeric() { c } else { '_' })
276 .collect();
277 {
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event compiler/rustc_incremental/src/assert_dep_graph.rs:277",
"rustc_incremental::assert_dep_graph",
::tracing::Level::DEBUG,
::tracing_core::__macro_support::Option::Some("compiler/rustc_incremental/src/assert_dep_graph.rs"),
::tracing_core::__macro_support::Option::Some(277u32),
::tracing_core::__macro_support::Option::Some("rustc_incremental::assert_dep_graph"),
::tracing_core::field::FieldSet::new(&["message"],
::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::EVENT)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let enabled =
::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
&&
::tracing::Level::DEBUG <=
::tracing::level_filters::LevelFilter::current() &&
{
let interest = __CALLSITE.interest();
!interest.is_never() &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest)
};
if enabled {
(|value_set: ::tracing::field::ValueSet|
{
let meta = __CALLSITE.metadata();
::tracing::Event::dispatch(meta, &value_set);
;
})({
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
let mut iter = __CALLSITE.metadata().fields().iter();
__CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
::tracing::__macro_support::Option::Some(&format_args!("n={0:?} s={1:?}",
n, s) as &dyn Value))])
});
} else { ; }
};debug!("n={:?} s={:?}", n, s);
278 dot::Id::new(s).unwrap()
279 }
280 fn node_label(&self, n: &DepKind) -> dot::LabelText<'_> {
281 dot::LabelText::label(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0:?}", n))
})format!("{n:?}"))
282 }
283}
284
285fn node_set<'g>(
289 graph: &'g RetainedDepGraph,
290 filter: &DepNodeFilter,
291) -> Option<FxIndexSet<&'g DepNode>> {
292 {
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event compiler/rustc_incremental/src/assert_dep_graph.rs:292",
"rustc_incremental::assert_dep_graph",
::tracing::Level::DEBUG,
::tracing_core::__macro_support::Option::Some("compiler/rustc_incremental/src/assert_dep_graph.rs"),
::tracing_core::__macro_support::Option::Some(292u32),
::tracing_core::__macro_support::Option::Some("rustc_incremental::assert_dep_graph"),
::tracing_core::field::FieldSet::new(&["message"],
::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::EVENT)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let enabled =
::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
&&
::tracing::Level::DEBUG <=
::tracing::level_filters::LevelFilter::current() &&
{
let interest = __CALLSITE.interest();
!interest.is_never() &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest)
};
if enabled {
(|value_set: ::tracing::field::ValueSet|
{
let meta = __CALLSITE.metadata();
::tracing::Event::dispatch(meta, &value_set);
;
})({
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
let mut iter = __CALLSITE.metadata().fields().iter();
__CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
::tracing::__macro_support::Option::Some(&format_args!("node_set(filter={0:?})",
filter) as &dyn Value))])
});
} else { ; }
};debug!("node_set(filter={:?})", filter);
293
294 if filter.accepts_all() {
295 return None;
296 }
297
298 Some(graph.nodes().into_iter().filter(|n| filter.test(n)).collect())
299}
300
301fn filter_nodes<'g>(
302 graph: &'g RetainedDepGraph,
303 sources: &Option<FxIndexSet<&'g DepNode>>,
304 targets: &Option<FxIndexSet<&'g DepNode>>,
305) -> FxIndexSet<DepKind> {
306 if let Some(sources) = sources {
307 if let Some(targets) = targets {
308 walk_between(graph, sources, targets)
309 } else {
310 walk_nodes(graph, sources, OUTGOING)
311 }
312 } else if let Some(targets) = targets {
313 walk_nodes(graph, targets, INCOMING)
314 } else {
315 graph.nodes().into_iter().map(|n| n.kind).collect()
316 }
317}
318
319fn walk_nodes<'g>(
320 graph: &'g RetainedDepGraph,
321 starts: &FxIndexSet<&'g DepNode>,
322 direction: Direction,
323) -> FxIndexSet<DepKind> {
324 let mut set = FxIndexSet::default();
325 for &start in starts {
326 {
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event compiler/rustc_incremental/src/assert_dep_graph.rs:326",
"rustc_incremental::assert_dep_graph",
::tracing::Level::DEBUG,
::tracing_core::__macro_support::Option::Some("compiler/rustc_incremental/src/assert_dep_graph.rs"),
::tracing_core::__macro_support::Option::Some(326u32),
::tracing_core::__macro_support::Option::Some("rustc_incremental::assert_dep_graph"),
::tracing_core::field::FieldSet::new(&["message"],
::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::EVENT)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let enabled =
::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
&&
::tracing::Level::DEBUG <=
::tracing::level_filters::LevelFilter::current() &&
{
let interest = __CALLSITE.interest();
!interest.is_never() &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest)
};
if enabled {
(|value_set: ::tracing::field::ValueSet|
{
let meta = __CALLSITE.metadata();
::tracing::Event::dispatch(meta, &value_set);
;
})({
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
let mut iter = __CALLSITE.metadata().fields().iter();
__CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
::tracing::__macro_support::Option::Some(&format_args!("walk_nodes: start={0:?} outgoing?={1:?}",
start, direction == OUTGOING) as &dyn Value))])
});
} else { ; }
};debug!("walk_nodes: start={:?} outgoing?={:?}", start, direction == OUTGOING);
327 if set.insert(start.kind) {
328 let mut stack = ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
[graph.indices[start]]))vec![graph.indices[start]];
329 while let Some(index) = stack.pop() {
330 for (_, edge) in graph.inner.adjacent_edges(index, direction) {
331 let neighbor_index = edge.source_or_target(direction);
332 let neighbor = graph.inner.node_data(neighbor_index);
333 if set.insert(neighbor.kind) {
334 stack.push(neighbor_index);
335 }
336 }
337 }
338 }
339 }
340 set
341}
342
343fn walk_between<'g>(
344 graph: &'g RetainedDepGraph,
345 sources: &FxIndexSet<&'g DepNode>,
346 targets: &FxIndexSet<&'g DepNode>,
347) -> FxIndexSet<DepKind> {
348 #[derive(#[automatically_derived]
impl ::core::marker::Copy for State { }Copy, #[automatically_derived]
impl ::core::clone::Clone for State {
#[inline]
fn clone(&self) -> State { *self }
}Clone, #[automatically_derived]
impl ::core::cmp::PartialEq for State {
#[inline]
fn eq(&self, other: &State) -> bool {
let __self_discr = ::core::intrinsics::discriminant_value(self);
let __arg1_discr = ::core::intrinsics::discriminant_value(other);
__self_discr == __arg1_discr
}
}PartialEq)]
354 enum State {
355 Undecided,
356 Deciding,
357 Included,
358 Excluded,
359 }
360
361 let mut node_states = ::alloc::vec::from_elem(State::Undecided, graph.inner.len_nodes())vec![State::Undecided; graph.inner.len_nodes()];
362
363 for &target in targets {
364 node_states[graph.indices[target].0] = State::Included;
365 }
366
367 for source in sources.iter().map(|&n| graph.indices[n]) {
368 recurse(graph, &mut node_states, source);
369 }
370
371 return graph
372 .nodes()
373 .into_iter()
374 .filter(|&n| {
375 let index = graph.indices[n];
376 node_states[index.0] == State::Included
377 })
378 .map(|n| n.kind)
379 .collect();
380
381 fn recurse(graph: &RetainedDepGraph, node_states: &mut [State], node: NodeIndex) -> bool {
382 match node_states[node.0] {
383 State::Included => return true,
385
386 State::Excluded => return false,
388
389 State::Deciding => return false,
391
392 State::Undecided => {}
393 }
394
395 node_states[node.0] = State::Deciding;
396
397 for neighbor_index in graph.inner.successor_nodes(node) {
398 if recurse(graph, node_states, neighbor_index) {
399 node_states[node.0] = State::Included;
400 }
401 }
402
403 if node_states[node.0] == State::Deciding {
405 node_states[node.0] = State::Excluded;
406 false
407 } else {
408 if !(node_states[node.0] == State::Included) {
::core::panicking::panic("assertion failed: node_states[node.0] == State::Included")
};assert!(node_states[node.0] == State::Included);
409 true
410 }
411 }
412}
413
414fn filter_edges(graph: &RetainedDepGraph, nodes: &FxIndexSet<DepKind>) -> Vec<(DepKind, DepKind)> {
415 let uniq: FxIndexSet<_> = graph
416 .edges()
417 .into_iter()
418 .map(|(s, t)| (s.kind, t.kind))
419 .filter(|(source, target)| nodes.contains(source) && nodes.contains(target))
420 .collect();
421 uniq.into_iter().collect()
422}