rapx/analysis/core/api_dependency/graph/
dump.rs

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