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