1use std::{
2 collections::{HashMap, HashSet},
3 fmt::{self, Write},
4};
5
6use crate::{analysis::utils::fn_info::*, utils::source::get_adt_name};
7use rustc_hir::{Safety, def_id::DefId};
8use rustc_middle::ty::TyCtxt;
9
10use super::upg_unit::UPGUnit;
11use petgraph::{
12 Graph,
13 dot::{Config, Dot},
14 graph::{DiGraph, EdgeReference, NodeIndex},
15};
16
17#[derive(Debug, Clone, Eq, PartialEq, Hash)]
18pub enum UPGNode {
19 SafeFn(DefId, String),
20 UnsafeFn(DefId, String),
21 MergedCallerCons(String),
22 MutMethods(String),
23}
24
25#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
26pub enum UPGEdge {
27 CallerToCallee,
28 ConsToMethod,
29 MutToCaller,
30}
31
32impl UPGNode {
33 pub fn from(node: FnInfo) -> Self {
34 match node.fn_safety {
35 Safety::Unsafe => match node.fn_kind {
36 FnKind::Constructor => UPGNode::UnsafeFn(node.def_id, "box".to_string()),
37 FnKind::Method => UPGNode::UnsafeFn(node.def_id, "ellipse".to_string()),
38 FnKind::Fn => UPGNode::UnsafeFn(node.def_id, "box".to_string()),
39 FnKind::Intrinsic => UPGNode::UnsafeFn(node.def_id, "box".to_string()),
40 },
41 Safety::Safe => match node.fn_kind {
42 FnKind::Constructor => UPGNode::SafeFn(node.def_id, "box".to_string()),
43 FnKind::Method => UPGNode::SafeFn(node.def_id, "ellipse".to_string()),
44 FnKind::Fn => UPGNode::SafeFn(node.def_id, "box".to_string()),
45 FnKind::Intrinsic => UPGNode::SafeFn(node.def_id, "box".to_string()),
46 },
47 }
48 }
49}
50
51impl fmt::Display for UPGNode {
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 match self {
54 UPGNode::SafeFn(_, _) => write!(f, "Safe"),
55 UPGNode::UnsafeFn(_, _) => write!(f, "Unsafe"),
56 UPGNode::MergedCallerCons(_) => write!(f, "MergedCallerCons"),
57 UPGNode::MutMethods(_) => write!(f, "MutMethods"),
58 }
59 }
60}
61
62impl fmt::Display for UPGEdge {
63 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64 match self {
65 UPGEdge::CallerToCallee => write!(f, "CallerToCallee"),
66 UPGEdge::ConsToMethod => write!(f, "ConsToMethod"),
67 UPGEdge::MutToCaller => write!(f, "MutToCaller"),
68 }
69 }
70}
71
72pub struct UPGraph {
74 structs: HashMap<String, HashSet<FnInfo>>,
76 edges: HashSet<(DefId, DefId, UPGEdge)>,
78 nodes: HashMap<DefId, String>,
80}
81
82impl UPGraph {
83 pub fn new() -> Self {
84 Self {
85 structs: HashMap::new(),
86 edges: HashSet::new(),
87 nodes: HashMap::new(),
88 }
89 }
90
91 pub fn add_node(&mut self, tcx: TyCtxt<'_>, node: FnInfo, custom_label: Option<String>) {
92 let adt_name = get_adt_name(tcx, node.def_id);
93 self.structs.entry(adt_name).or_default().insert(node);
94
95 if !self.nodes.contains_key(&node.def_id) || custom_label.is_some() {
96 let attr = if let Some(label) = custom_label {
97 if node.fn_kind == FnKind::Constructor {
98 format!(
99 "label=\"{}\", shape=\"septagon\", style=\"filled\", fillcolor=\"#f0f0f0\", color=\"#555555\"",
100 label
101 )
102 } else {
103 format!(
104 "label=\"{}\", shape=\"ellipse\", style=\"filled\", fillcolor=\"#f0f0f0\", color=\"#555555\"",
105 label
106 )
107 }
108 } else {
109 let upg_node = UPGNode::from(node);
110 Self::node_to_dot_attr(&upg_node)
111 };
112
113 self.nodes.insert(node.def_id, attr);
114 }
115 }
116
117 pub fn add_edge(&mut self, from: DefId, to: DefId, edge_type: UPGEdge) {
118 if from == to {
119 return;
120 }
121 self.edges.insert((from, to, edge_type));
122 }
123
124 pub fn upg_unit_string(&self, module_name: &str) -> String {
125 let mut dot = String::new();
126 let graph_id = module_name
127 .replace("::", "_")
128 .replace(|c: char| !c.is_alphanumeric(), "_");
129
130 writeln!(dot, "digraph {} {{", graph_id).unwrap();
131 writeln!(dot, " compound=true;").unwrap();
132 writeln!(dot, " rankdir=LR;").unwrap();
133
134 for (struct_name, nodes) in &self.structs {
135 let cluster_id = format!(
136 "cluster_{}",
137 struct_name.replace(|c: char| !c.is_alphanumeric(), "_")
138 );
139
140 writeln!(dot, " subgraph {} {{", cluster_id).unwrap();
141 writeln!(dot, " label=\"{}\";", struct_name).unwrap();
142 writeln!(dot, " style=dashed;").unwrap();
143 writeln!(dot, " color=gray;").unwrap();
144
145 for node in nodes {
146 let def_id = node.def_id;
147 let node_id =
148 format!("n_{:?}", def_id).replace(|c: char| !c.is_alphanumeric(), "_");
149
150 if let Some(attr) = self.nodes.get(&def_id) {
151 writeln!(dot, " {} [{}];", node_id, attr).unwrap();
152 }
153 }
154 writeln!(dot, " }}").unwrap();
155 }
156
157 for (from, to, edge_type) in &self.edges {
158 let from_id = format!("n_{:?}", from).replace(|c: char| !c.is_alphanumeric(), "_");
159 let to_id = format!("n_{:?}", to).replace(|c: char| !c.is_alphanumeric(), "_");
160
161 let attr = match edge_type {
162 UPGEdge::CallerToCallee => "color=black, style=solid",
163 UPGEdge::ConsToMethod => "color=black, style=dotted",
164 UPGEdge::MutToCaller => "color=blue, style=dashed",
165 };
166
167 writeln!(dot, " {} -> {} [{}];", from_id, to_id, attr).unwrap();
168 }
169
170 writeln!(dot, "}}").unwrap();
171 dot
172 }
173
174 fn node_to_dot_attr(node: &UPGNode) -> String {
175 match node {
176 UPGNode::SafeFn(def_id, shape) => {
177 format!("label=\"{:?}\", color=black, shape={:?}", def_id, shape)
178 }
179 UPGNode::UnsafeFn(def_id, shape) => {
180 let label = format!("{:?}", def_id);
181 format!("label=\"{}\", shape={:?}, color=red", label, shape)
182 }
183 _ => "label=\"Unknown\"".to_string(),
184 }
185 }
186
187 pub fn generate_dot_from_upg_unit(upg: &UPGUnit) -> String {
188 let mut graph: Graph<UPGNode, UPGEdge> = DiGraph::new();
189 let get_edge_attr = |_graph: &Graph<UPGNode, UPGEdge>,
190 edge_ref: EdgeReference<'_, UPGEdge>| {
191 match edge_ref.weight() {
192 UPGEdge::CallerToCallee => "color=black, style=solid",
193 UPGEdge::ConsToMethod => "color=black, style=dotted",
194 UPGEdge::MutToCaller => "color=blue, style=dashed",
195 }
196 .to_owned()
197 };
198 let get_node_attr =
199 |_graph: &Graph<UPGNode, UPGEdge>, node_ref: (NodeIndex, &UPGNode)| match node_ref.1 {
200 UPGNode::SafeFn(def_id, shape) => {
201 format!("label=\"{:?}\", color=black, shape={:?}", def_id, shape)
202 }
203 UPGNode::UnsafeFn(def_id, shape) => {
204 let label = format!("{:?}\n ", def_id);
205 let node_attr = format!("label={:?}, shape={:?}, color=red", label, shape);
206 node_attr
207 }
208 UPGNode::MergedCallerCons(label) => {
209 format!(
210 "label=\"{}\", shape=box, style=filled, fillcolor=lightgrey",
211 label
212 )
213 }
214 UPGNode::MutMethods(label) => {
215 format!(
216 "label=\"{}\", shape=octagon, style=filled, fillcolor=lightyellow",
217 label
218 )
219 }
220 };
221
222 let caller_node = graph.add_node(UPGNode::from(upg.caller));
223 if !upg.caller_cons.is_empty() {
224 let cons_labels: Vec<String> = upg
225 .caller_cons
226 .iter()
227 .map(|con| format!("{:?}", con.def_id))
228 .collect();
229 let merged_label = format!("Caller Constructors\n{}", cons_labels.join("\n"));
230 let merged_cons_node = graph.add_node(UPGNode::MergedCallerCons(merged_label));
231 graph.add_edge(merged_cons_node, caller_node, UPGEdge::ConsToMethod);
232 }
233
234 if !upg.mut_methods.is_empty() {
235 let mut_method_labels: Vec<String> = upg
236 .mut_methods
237 .iter()
238 .map(|def_id| format!("{:?}", def_id))
239 .collect();
240 let merged_label = format!("Mutable Methods\n{}", mut_method_labels.join("\n"));
241
242 let mut_methods_node = graph.add_node(UPGNode::MutMethods(merged_label));
243 graph.add_edge(mut_methods_node, caller_node, UPGEdge::MutToCaller);
244 }
245
246 let mut dot_str = String::new();
257 let dot = Dot::with_attr_getters(
258 &graph,
259 &[Config::NodeNoLabel],
261 &get_edge_attr,
262 &get_node_attr,
263 );
264
265 write!(dot_str, "{}", dot).unwrap();
266 dot_str
268 }
269}