Skip to main content

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