rapx/analysis/api_dependency/graph/
dump.rs1use super::dep_edge::DepEdge;
2use super::dep_node::DepNode;
3use crate::analysis::api_dependency::ApiDependencyGraph;
4use crate::helpers::path::{PathResolver, get_path_resolver};
5use anyhow::Result;
6use itertools::Itertools;
7use petgraph::Graph;
8use petgraph::dot;
9use petgraph::graph::NodeIndex;
10use rustc_middle::ty::{self, Ty, TyCtxt, TyKind};
11use rustc_middle::ty::{GenericArgsRef, List};
12use serde::{Serialize, ser::SerializeMap};
13use serde_yaml;
14use std::io::Write;
15use std::mem::MaybeUninit;
16use std::path::Path;
17
18#[derive(Debug, Clone, Serialize)]
19#[serde(tag = "type")]
20enum NodeInfo {
21 Api {
22 path: String,
23 generic_args: Vec<String>,
24 },
25 Ty {
26 path: String,
27 },
28}
29
30#[derive(Debug, Clone, Serialize)]
31struct EdgeInfo {
32 from: usize,
33 to: usize,
34 kind: DepEdge,
35}
36
37impl<'tcx> ApiDependencyGraph<'tcx> {
38 pub fn dump_to_file(&self, path: impl AsRef<Path>) -> Result<()> {
39 let dump_path = path.as_ref();
40 let file = std::fs::File::create(path.as_ref())?;
41 match dump_path.extension() {
42 Some(ext) if ext == "json" => {
43 serde_json::to_writer_pretty(file, self)?;
44 }
45 Some(ext) if ext == "dot" => {
46 let dot_str = self.dump_to_dot();
47 std::fs::write(dump_path, dot_str)?;
48 }
49 Some(ext) if ext == "yml" || ext == "yaml" => {
50 serde_yaml::to_writer(file, self)?;
51 }
52 _ => {
53 rap_info!(
54 "Unsupported dump format: {:?}, skip dumping API graph",
55 dump_path.extension()
56 );
57 }
58 }
59 rap_info!("Dump API dependency graph to {}", dump_path.display());
60 Ok(())
61 }
62}
63
64impl<'tcx> DepNode<'tcx> {
65 fn to_node_info(&self, resolver: &PathResolver<'tcx>) -> NodeInfo {
66 match self {
67 DepNode::Api(def_id, args) => NodeInfo::Api {
68 path: resolver.path_str_with_args(*def_id, ty::GenericArgs::empty()),
69 generic_args: args
70 .iter()
71 .map(|arg| resolver.generic_arg_str(arg))
72 .collect_vec(),
73 },
74 DepNode::Ty(ty_wrapper) => NodeInfo::Ty {
75 path: resolver.ty_str(ty_wrapper.ty()),
76 },
77 }
78 }
79}
80
81impl DepEdge {
82 fn to_edge_info(&self, from: usize, to: usize) -> EdgeInfo {
83 EdgeInfo {
84 from,
85 to,
86 kind: *self,
87 }
88 }
89}
90
91impl<'tcx> Serialize for ApiDependencyGraph<'tcx> {
95 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
96 where
97 S: serde::Serializer,
98 {
99 let mut map = serializer.serialize_map(Some(2))?;
100 let resolver = get_path_resolver(self.tcx);
101
102 let node_len = self.graph.node_count();
103 let mut nodes = Box::<[NodeInfo]>::new_uninit_slice(node_len);
104 let mut initialized_count = 0usize;
105
106 for (expected_offset, node_index) in self.graph.node_indices().enumerate() {
107 let offset = node_index.index();
108 assert!(offset < node_len, "node index out of bounds");
109
110 let node = self
111 .graph
112 .node_weight(node_index)
113 .expect("node index from node_indices must exist");
114 nodes[offset].write(node.to_node_info(&resolver));
115 initialized_count += 1;
116 }
117
118 assert_eq!(
119 initialized_count, node_len,
120 "all node slots must be initialized"
121 );
122
123 let nodes = unsafe { nodes.assume_init() }.into_vec();
126
127 let mut edges = Vec::with_capacity(self.graph.edge_count());
128 for edge_index in self.graph.edge_indices() {
129 let (from, to) = self
130 .graph
131 .edge_endpoints(edge_index)
132 .expect("edge index from edge_indices must have endpoints");
133 let edge = self
134 .graph
135 .edge_weight(edge_index)
136 .expect("edge index from edge_indices must exist");
137 edges.push(edge.to_edge_info(from.index(), to.index()));
138 }
139
140 map.serialize_entry("nodes", &nodes)?;
141 map.serialize_entry("edges", &edges)?;
142 map.end()
143 }
144}
145
146impl<'tcx> ApiDependencyGraph<'tcx> {
147 pub fn dump_to_dot(&self) -> String {
148 let tcx = self.tcx;
149 let get_edge_attr =
150 |graph: &Graph<DepNode<'tcx>, DepEdge>,
151 edge_ref: petgraph::graph::EdgeReference<DepEdge>| {
152 let color = match edge_ref.weight() {
153 DepEdge::Arg { .. } | DepEdge::Ret => "black",
154 DepEdge::Transform(_) => "darkorange",
155 };
156 format!("label=\"{}\", color = {}", edge_ref.weight(), color)
157 };
158 let get_node_attr = |graph: &Graph<DepNode<'tcx>, DepEdge>,
159 node_ref: (NodeIndex, &DepNode<'tcx>)| {
160 format!("label={:?}, ", node_ref.1.desc_str(tcx))
161 + match node_ref.1 {
162 DepNode::Api(..) => "color = blue",
163 DepNode::Ty(_) => "color = red",
164 }
165 + ", shape=box"
166 };
167
168 let dot = dot::Dot::with_attr_getters(
169 &self.graph,
170 &[dot::Config::NodeNoLabel, dot::Config::EdgeNoLabel],
171 &get_edge_attr,
172 &get_node_attr,
173 );
174 format!("{:?}", dot)
175 }
176}