Skip to main content

aster/map/
call_graph_builder.rs

1//! 调用图构建器
2//!
3//! 分析函数/方法之间的调用关系
4
5use std::collections::HashMap;
6
7use super::types::*;
8
9/// 需要忽略的内置函数/关键字
10const IGNORED_NAMES: &[&str] = &[
11    "if",
12    "for",
13    "while",
14    "switch",
15    "catch",
16    "with",
17    "function",
18    "class",
19    "return",
20    "throw",
21    "typeof",
22    "instanceof",
23    "void",
24    "delete",
25    "await",
26    "async",
27    "yield",
28    "new",
29    "super",
30    "this",
31    "console",
32    "Math",
33    "JSON",
34    "Object",
35    "Array",
36    "String",
37    "Number",
38    "Boolean",
39    "Date",
40    "RegExp",
41    "Error",
42    "Promise",
43    "Map",
44    "Set",
45    "parseInt",
46    "parseFloat",
47    "isNaN",
48    "isFinite",
49    "print",
50    "len",
51    "range",
52    "str",
53    "int",
54    "float",
55    "list",
56    "dict",
57];
58
59/// 调用图构建器
60pub struct CallGraphBuilder {
61    function_index: HashMap<String, CallGraphNode>,
62    name_to_ids: HashMap<String, Vec<String>>,
63}
64
65impl CallGraphBuilder {
66    pub fn new() -> Self {
67        Self {
68            function_index: HashMap::new(),
69            name_to_ids: HashMap::new(),
70        }
71    }
72
73    /// 构建调用图
74    pub fn build_call_graph(&mut self, modules: &[ModuleNode]) -> CallGraph {
75        let mut nodes = Vec::new();
76        let mut edges = Vec::new();
77
78        self.build_function_index(modules, &mut nodes);
79
80        for module in modules {
81            self.analyze_module_calls(module, &mut edges);
82        }
83
84        let merged_edges = self.merge_edges(edges);
85        CallGraph {
86            nodes,
87            edges: merged_edges,
88        }
89    }
90
91    /// 建立函数索引
92    fn build_function_index(&mut self, modules: &[ModuleNode], nodes: &mut Vec<CallGraphNode>) {
93        self.function_index.clear();
94        self.name_to_ids.clear();
95
96        for module in modules {
97            for func in &module.functions {
98                let node = CallGraphNode {
99                    id: func.id.clone(),
100                    name: func.name.clone(),
101                    node_type: CallGraphNodeType::Function,
102                    module_id: module.id.clone(),
103                    class_name: None,
104                    signature: Some(func.signature.clone()),
105                };
106                nodes.push(node.clone());
107                self.function_index.insert(func.id.clone(), node);
108                self.add_to_name_index(&func.name, &func.id);
109            }
110
111            for cls in &module.classes {
112                for method in &cls.methods {
113                    let node = CallGraphNode {
114                        id: method.id.clone(),
115                        name: method.name.clone(),
116                        node_type: if method.name == "constructor" {
117                            CallGraphNodeType::Constructor
118                        } else {
119                            CallGraphNodeType::Method
120                        },
121                        module_id: module.id.clone(),
122                        class_name: Some(cls.name.clone()),
123                        signature: Some(method.signature.clone()),
124                    };
125                    nodes.push(node.clone());
126                    self.function_index.insert(method.id.clone(), node);
127                    self.add_to_name_index(&method.name, &method.id);
128                    self.add_to_name_index(&format!("{}.{}", cls.name, method.name), &method.id);
129                }
130            }
131        }
132    }
133
134    fn add_to_name_index(&mut self, name: &str, id: &str) {
135        self.name_to_ids
136            .entry(name.to_string())
137            .or_default()
138            .push(id.to_string());
139    }
140
141    /// 分析模块中的调用
142    fn analyze_module_calls(&self, module: &ModuleNode, edges: &mut Vec<CallGraphEdge>) {
143        let content = match std::fs::read_to_string(&module.path) {
144            Ok(c) => c,
145            Err(_) => return,
146        };
147        let lines: Vec<&str> = content.lines().collect();
148        let call_re = regex::Regex::new(r"\b([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(").unwrap();
149
150        for func in &module.functions {
151            self.analyze_function_calls(func, &lines, &call_re, &module.id, edges);
152        }
153
154        for cls in &module.classes {
155            for method in &cls.methods {
156                self.analyze_method_calls(method, &lines, &call_re, &module.id, edges);
157            }
158        }
159    }
160
161    fn analyze_function_calls(
162        &self,
163        func: &FunctionNode,
164        lines: &[&str],
165        call_re: &regex::Regex,
166        module_id: &str,
167        edges: &mut Vec<CallGraphEdge>,
168    ) {
169        let start = func.location.start_line.saturating_sub(1) as usize;
170        let end = (func.location.end_line as usize).min(lines.len());
171
172        for (i, line) in lines[start..end].iter().enumerate() {
173            let line_num = start + i + 1;
174            for cap in call_re.captures_iter(line) {
175                let called_name = &cap[1];
176                if IGNORED_NAMES.contains(&called_name) || called_name == func.name {
177                    continue;
178                }
179                if let Some(target_ids) = self.name_to_ids.get(called_name) {
180                    if let Some(target_id) = target_ids.iter().next() {
181                        edges.push(CallGraphEdge {
182                            source: func.id.clone(),
183                            target: target_id.clone(),
184                            edge_type: self.detect_call_type(line, called_name),
185                            count: 1,
186                            locations: vec![LocationInfo {
187                                file: module_id.to_string(),
188                                start_line: line_num as u32,
189                                start_column: 0,
190                                end_line: line_num as u32,
191                                end_column: line.len() as u32,
192                            }],
193                        });
194                    }
195                }
196            }
197        }
198    }
199
200    fn analyze_method_calls(
201        &self,
202        method: &MethodNode,
203        lines: &[&str],
204        call_re: &regex::Regex,
205        module_id: &str,
206        edges: &mut Vec<CallGraphEdge>,
207    ) {
208        let start = method.location.start_line.saturating_sub(1) as usize;
209        let end = (method.location.end_line as usize).min(lines.len());
210
211        for (i, line) in lines[start..end].iter().enumerate() {
212            let line_num = start + i + 1;
213            for cap in call_re.captures_iter(line) {
214                let called_name = &cap[1];
215                if IGNORED_NAMES.contains(&called_name) || called_name == method.name {
216                    continue;
217                }
218                if let Some(target_ids) = self.name_to_ids.get(called_name) {
219                    if let Some(target_id) = target_ids.iter().next() {
220                        edges.push(CallGraphEdge {
221                            source: method.id.clone(),
222                            target: target_id.clone(),
223                            edge_type: self.detect_call_type(line, called_name),
224                            count: 1,
225                            locations: vec![LocationInfo {
226                                file: module_id.to_string(),
227                                start_line: line_num as u32,
228                                start_column: 0,
229                                end_line: line_num as u32,
230                                end_column: line.len() as u32,
231                            }],
232                        });
233                    }
234                }
235            }
236        }
237    }
238
239    fn detect_call_type(&self, line: &str, name: &str) -> CallType {
240        if line.contains(&format!(".{}(", name)) || line.contains(&format!("?.{}(", name)) {
241            CallType::Method
242        } else if line.contains(&format!("({})", name)) || line.contains(&format!(", {})", name)) {
243            CallType::Callback
244        } else if line.contains(&format!("[{}](", name)) {
245            CallType::Dynamic
246        } else {
247            CallType::Direct
248        }
249    }
250
251    /// 合并重复边
252    fn merge_edges(&self, edges: Vec<CallGraphEdge>) -> Vec<CallGraphEdge> {
253        let mut edge_map: HashMap<String, CallGraphEdge> = HashMap::new();
254
255        for edge in edges {
256            let key = format!("{}|{}", edge.source, edge.target);
257            if let Some(existing) = edge_map.get_mut(&key) {
258                existing.count += edge.count;
259                existing.locations.extend(edge.locations);
260            } else {
261                edge_map.insert(key, edge);
262            }
263        }
264
265        edge_map.into_values().collect()
266    }
267}
268
269impl Default for CallGraphBuilder {
270    fn default() -> Self {
271        Self::new()
272    }
273}
274
275/// 便捷函数:构建调用图
276pub fn build_call_graph(modules: &[ModuleNode]) -> CallGraph {
277    let mut builder = CallGraphBuilder::new();
278    builder.build_call_graph(modules)
279}