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