rapx/analysis/api_dependency/graph/
dump.rs

1use 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
91// schema:
92// nodes: [{type: "api"/"type", path}]
93// edges: [{from,to,type}]
94impl<'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        // SAFETY: we assert that indices are contiguous and in-bounds, and we initialize
124        // each slot exactly once, so every element is fully initialized here.
125        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}