1use std::collections::BTreeMap;
11use std::collections::BTreeSet;
12use std::sync::Arc;
13
14use ra_ap_hir::{DisplayTarget, Semantics};
15use ra_ap_ide::RootDatabase;
16use ra_ap_syntax::{AstNode, SyntaxKind, ast};
17
18use super::super::facts::DataFlowFact;
19use super::common::{FnContext, ParsedFile, display_target_for_file, format_type};
20use super::context::FnEntry;
21use super::function_summary::{FlowRange, FunctionAnalysisSummary, FunctionSummaryData};
22use super::{concurrency, perf, quality, reachability};
23
24pub struct SemanticFileAnalysis {
30 call_graph: Box<[(Box<str>, Box<str>)]>,
31 fn_entries: Box<[FnEntry]>,
32 reachable_names: BTreeSet<Box<str>>,
33 data_flows: Arc<[DataFlowFact]>,
34 fn_summaries: BTreeMap<Box<str>, FunctionSummaryData>,
35 resolved_types: BTreeMap<(usize, usize), Box<str>>,
37}
38
39impl SemanticFileAnalysis {
40 pub(super) fn build(pf: &ParsedFile<'_>) -> Self {
47 let mut call_graph_edges: Vec<(Box<str>, Box<str>)> = Vec::new();
48 let mut fn_entries: Vec<FnEntry> = Vec::new();
49 let mut fn_summaries: BTreeMap<Box<str>, FunctionSummaryData> = BTreeMap::new();
50 let mut all_flows: Vec<DataFlowFact> = Vec::new();
51 let mut resolved_types: BTreeMap<(usize, usize), Box<str>> = BTreeMap::new();
52
53 let display_target = display_target_for_file(&pf.sema, pf.file_id, pf.db);
55
56 let mut fn_ranges: Vec<ra_ap_syntax::TextRange> = Vec::new();
58
59 for fn_node in pf.tree.syntax().descendants().filter_map(ast::Fn::cast) {
60 let fn_range = fn_node.syntax().text_range();
61 fn_ranges.push(fn_range);
62
63 let Some(ctx) = FnContext::build(pf, &fn_node) else {
64 continue;
65 };
66
67 ctx.extend_call_graph(&mut call_graph_edges);
69
70 let taint = super::taint::detect(&ctx);
72 let quality = quality::detect(&ctx);
73 let performance = perf::detect(&ctx);
74 let concurrency = concurrency::detect(&ctx);
75
76 let taint_range = append_flows(&mut all_flows, taint);
78 let quality_range = append_flows(&mut all_flows, quality);
79 let perf_range = append_flows(&mut all_flows, performance);
80 let conc_range = append_flows(&mut all_flows, concurrency);
81
82 if let Some(dt) = display_target {
84 resolve_types_in_subtree(pf, fn_node.syntax(), dt, &mut resolved_types);
85 }
86
87 let (name, start, end, entry, lock_acquisitions) = ctx.into_entry_data();
88 fn_entries.push((Box::from(&*name), start, end, entry));
89 fn_summaries.insert(
90 name,
91 FunctionSummaryData {
92 lock_acquisitions,
93 taint: taint_range,
94 quality: quality_range,
95 performance: perf_range,
96 concurrency: conc_range,
97 },
98 );
99 }
100
101 call_graph_edges.sort();
102 call_graph_edges.dedup();
103 let call_graph = call_graph_edges.into_boxed_slice();
104 let fn_entries = fn_entries.into_boxed_slice();
105 let reachable_names = reachability::compute_reachable_names(&fn_entries, &call_graph);
106
107 let lock_ordering = concurrency::detect_lock_ordering(&fn_summaries);
109 all_flows.extend(lock_ordering.into_vec());
110
111 let data_flows: Arc<[DataFlowFact]> = all_flows.into();
112
113 if let Some(dt) = display_target {
115 resolve_module_level_types(pf, dt, &fn_ranges, &mut resolved_types);
116 }
117
118 Self {
119 call_graph,
120 fn_entries,
121 reachable_names,
122 data_flows,
123 fn_summaries,
124 resolved_types,
125 }
126 }
127
128 pub fn call_graph(&self) -> &[(Box<str>, Box<str>)] {
130 &self.call_graph
131 }
132
133 pub fn data_flows(&self) -> &Arc<[DataFlowFact]> {
138 &self.data_flows
139 }
140
141 pub fn function(&self, name: &str) -> Option<FunctionAnalysisSummary<'_>> {
146 self.fn_summaries
147 .get(name)
148 .map(|data| FunctionAnalysisSummary::new(data, &self.data_flows))
149 }
150
151 pub fn resolve_type(&self, line: usize, column: usize) -> Option<&str> {
156 self.resolved_types.get(&(line, column)).map(|s| &**s)
157 }
158
159 pub fn is_line_reachable(&self, line: usize) -> bool {
161 reachability::is_line_in_reachable_fn(&self.fn_entries, &self.reachable_names, line)
162 }
163
164 pub fn check_reachability_batch(&self, lines: &[usize]) -> Box<[bool]> {
166 lines
167 .iter()
168 .map(|&line| self.is_line_reachable(line))
169 .collect::<Vec<_>>()
170 .into_boxed_slice()
171 }
172}
173
174fn append_flows(all: &mut Vec<DataFlowFact>, facts: Box<[DataFlowFact]>) -> FlowRange {
176 let start = all.len();
177 all.extend(facts.into_vec());
178 FlowRange::new(start, all.len())
179}
180
181fn resolve_types_in_subtree(
183 pf: &ParsedFile<'_>,
184 root: &ra_ap_syntax::SyntaxNode,
185 display_target: DisplayTarget,
186 resolved: &mut BTreeMap<(usize, usize), Box<str>>,
187) {
188 for node in root.descendants() {
189 resolve_type_bearing_node(pf, &node, display_target, resolved);
190 }
191}
192
193fn resolve_module_level_types(
197 pf: &ParsedFile<'_>,
198 display_target: DisplayTarget,
199 fn_ranges: &[ra_ap_syntax::TextRange],
200 resolved: &mut BTreeMap<(usize, usize), Box<str>>,
201) {
202 for node in pf.tree.syntax().descendants() {
203 let range = node.text_range();
204 if fn_ranges.iter().any(|fr| fr.contains_range(range)) {
205 continue;
206 }
207 resolve_type_bearing_node(pf, &node, display_target, resolved);
208 }
209}
210
211fn resolve_type_bearing_node(
213 pf: &ParsedFile<'_>,
214 node: &ra_ap_syntax::SyntaxNode,
215 display_target: DisplayTarget,
216 resolved: &mut BTreeMap<(usize, usize), Box<str>>,
217) {
218 match node.kind() {
219 SyntaxKind::LET_STMT => {
220 resolve_let_stmt_type(pf, node, display_target, resolved);
221 }
222 SyntaxKind::PATH_TYPE
223 | SyntaxKind::TUPLE_TYPE
224 | SyntaxKind::ARRAY_TYPE
225 | SyntaxKind::SLICE_TYPE
226 | SyntaxKind::REF_TYPE
227 | SyntaxKind::PTR_TYPE => {
228 resolve_type_node(pf, node, display_target, resolved);
229 }
230 _ => {}
231 }
232}
233
234fn resolve_let_stmt_type(
236 pf: &ParsedFile<'_>,
237 node: &ra_ap_syntax::SyntaxNode,
238 display_target: DisplayTarget,
239 resolved: &mut BTreeMap<(usize, usize), Box<str>>,
240) {
241 let Some(let_stmt) = ast::LetStmt::cast(node.clone()) else {
242 return;
243 };
244
245 let init_resolved = let_stmt.initializer().and_then(|init| {
247 let type_str = resolve_expr_type(&pf.sema, &init, pf.db, display_target)?;
248 let lc = pf.line_index.line_col(init.syntax().text_range().start());
249 Some(((lc.line + 1) as usize, lc.col as usize, type_str))
250 });
251 if let Some((line, col, type_str)) = init_resolved {
252 resolved.insert((line, col), type_str);
253 }
254
255 let ann_resolved = let_stmt.ty().and_then(|ty| {
257 let type_str = resolve_ast_type(&pf.sema, &ty, pf.db, display_target)?;
258 let lc = pf.line_index.line_col(ty.syntax().text_range().start());
259 Some(((lc.line + 1) as usize, lc.col as usize, type_str))
260 });
261 if let Some((line, col, type_str)) = ann_resolved {
262 resolved.insert((line, col), type_str);
263 }
264}
265
266fn resolve_type_node(
268 pf: &ParsedFile<'_>,
269 node: &ra_ap_syntax::SyntaxNode,
270 display_target: DisplayTarget,
271 resolved: &mut BTreeMap<(usize, usize), Box<str>>,
272) {
273 let Some(ty) = ast::Type::cast(node.clone()) else {
274 return;
275 };
276 let Some(type_str) = resolve_ast_type(&pf.sema, &ty, pf.db, display_target) else {
277 return;
278 };
279 let range = ty.syntax().text_range();
280 let lc = pf.line_index.line_col(range.start());
281 resolved.insert(((lc.line + 1) as usize, lc.col as usize), type_str);
282}
283
284fn resolve_expr_type(
286 sema: &Semantics<'_, RootDatabase>,
287 expr: &ast::Expr,
288 db: &RootDatabase,
289 display_target: DisplayTarget,
290) -> Option<Box<str>> {
291 let ty_info = sema.type_of_expr(expr)?;
292 Some(format_type(&ty_info.original, db, display_target))
293}
294
295fn resolve_ast_type(
297 sema: &Semantics<'_, RootDatabase>,
298 ty: &ast::Type,
299 db: &RootDatabase,
300 display_target: DisplayTarget,
301) -> Option<Box<str>> {
302 sema.resolve_type(ty)
303 .map(|resolved| format_type(&resolved, db, display_target))
304 .or_else(|| resolve_path_type(sema, ty, db, display_target))
305}
306
307fn resolve_path_type(
309 sema: &Semantics<'_, RootDatabase>,
310 ty: &ast::Type,
311 db: &RootDatabase,
312 display_target: DisplayTarget,
313) -> Option<Box<str>> {
314 let ast::Type::PathType(p) = ty else {
315 return None;
316 };
317 let path = p.path()?;
318 let resolution = sema.resolve_path(&path)?;
319 match resolution {
320 ra_ap_hir::PathResolution::Def(module_def) => {
321 resolve_module_def_type(module_def, db, display_target)
322 }
323 _ => None,
324 }
325}
326
327fn resolve_module_def_type(
329 def: ra_ap_hir::ModuleDef,
330 db: &RootDatabase,
331 display_target: DisplayTarget,
332) -> Option<Box<str>> {
333 match def {
334 ra_ap_hir::ModuleDef::TypeAlias(alias) => {
335 Some(format_type(&alias.ty(db), db, display_target))
336 }
337 ra_ap_hir::ModuleDef::Adt(adt) => Some(format_type(&adt.ty(db), db, display_target)),
338 ra_ap_hir::ModuleDef::BuiltinType(builtin) => Some(builtin.name().as_str().into()),
339 _ => None,
340 }
341}