1#![allow(clippy::collapsible_if)]
2#![allow(clippy::clone_on_copy)]
3#![allow(clippy::only_used_in_recursion)]
4
5use crate::types::{GraphData, GraphEdge, GraphEdgeType, GraphEntityType, GraphNode};
6use crate::{types::*, util::node_to_range};
7use serde_json::json;
8use tower_lsp::lsp_types::{Position, Range};
9use tree_sitter::{Node, Point, Tree};
10
11pub fn has_synchronization_in_block(tree: &Tree, range: Range, code: &str) -> bool {
12 let target = Point {
13 row: range.start.line as usize,
14 column: range.start.character as usize,
15 };
16
17 let mut enclosing: Option<Node> = None;
18 let mut stack = vec![tree.root_node()];
19 while let Some(node) = stack.pop() {
20 if node.kind() == "block"
21 && node.start_position() <= target
22 && target <= node.end_position()
23 {
24 enclosing = Some(node);
25 break;
26 }
27 for i in (0..node.child_count()).rev() {
28 if let Some(c) = node.child(i) {
29 stack.push(c);
30 }
31 }
32 }
33 let block = match enclosing {
34 Some(b) => b,
35 None => return false,
36 };
37
38 let mut cursor = block.walk();
39 if cursor.goto_first_child() {
40 loop {
41 let node = cursor.node();
42 let kind = node.kind();
43 eprintln!(
44 "[block_child] kind: {} bytes: {:?}",
45 kind,
46 node.byte_range()
47 );
48 if kind != "{" && kind != "}" && find_sync_in_node(node, code) {
49 return true;
50 }
51 if !cursor.goto_next_sibling() {
52 break;
53 }
54 }
55 }
56 false
57}
58
59fn find_sync_in_node(node: Node, code: &str) -> bool {
60 if node.kind() == "call_expression" {
61 eprintln!("[has_sync] call_expression: {:?}", text(code, node));
62 if is_mutex_call(node, code) || is_atomic_call(node, code) {
63 return true;
64 }
65 }
66 let mut cursor = node.walk();
67 if cursor.goto_first_child() {
68 loop {
69 if find_sync_in_node(cursor.node(), code) {
70 return true;
71 }
72 if !cursor.goto_next_sibling() {
73 break;
74 }
75 }
76 }
77 false
78}
79
80#[inline]
81fn is_mutex_call(call: Node, code: &str) -> bool {
82 if let Some(sel) = call.child_by_field_name("function") {
83 if sel.kind() == "selector_expression" {
84 if let Some(field) = sel.child_by_field_name("field") {
85 let name = text(code, field);
86 return matches!(name, "Lock" | "Unlock" | "Wait");
87 }
88 }
89 }
90 false
91}
92
93#[inline]
94fn is_atomic_call(call: Node, code: &str) -> bool {
95 let func = match call.child_by_field_name("function") {
96 Some(f) => f,
97 None => return false,
98 };
99 if func.kind() == "selector_expression" {
100 let pkg = func.child_by_field_name("operand").map(|n| text(code, n));
101 let field = func.child_by_field_name("field").map(|n| text(code, n));
102 if matches!(pkg, Some("atomic")) {
103 if let Some(f) = field {
104 return crate::types::ATOMIC_FUNCS.contains(&f);
105 }
106 }
107 }
108 false
109}
110
111pub fn determine_race_severity(tree: &Tree, range: Range, code: &str) -> RaceSeverity {
112 let target_point = Point {
114 row: range.start.line as usize,
115 column: range.start.character as usize,
116 };
117
118 if let Some(goroutine_node) = find_goroutine_context(tree.root_node(), target_point) {
120 if has_synchronization_in_goroutine(goroutine_node, code) {
122 RaceSeverity::Low
123 } else {
124 RaceSeverity::High
125 }
126 } else {
127 if has_synchronization_in_block(tree, range, code) {
129 RaceSeverity::Low
130 } else {
131 RaceSeverity::High
132 }
133 }
134}
135
136fn has_synchronization_in_goroutine(goroutine_node: tree_sitter::Node, code: &str) -> bool {
138 find_sync_in_node(goroutine_node, code)
140}
141
142pub fn find_variable_at_position(tree: &Tree, code: &str, pos: Position) -> Option<VariableInfo> {
143 let target_point = Point {
144 row: pos.line as usize,
145 column: pos.character as usize,
146 };
147
148 let target_node = find_node_at_position(tree.root_node(), target_point)?;
150 let var_name = extract_variable_name(target_node, code)?;
151
152 let function_scope = find_function_scope(tree.root_node(), target_point);
154
155 collect_variable_info(tree, code, &var_name, function_scope)
157}
158
159fn find_node_at_position(node: tree_sitter::Node, target: Point) -> Option<tree_sitter::Node> {
161 if !is_position_in_node_range(node, target) {
163 return None;
164 }
165
166 let mut best_match = node;
168 let mut best_size = node_size(node);
169
170 for i in 0..node.child_count() {
172 if let Some(child) = node.child(i) {
173 if let Some(child_match) = find_node_at_position(child, target) {
174 let child_size = node_size(child_match);
175 if child_size < best_size && is_meaningful_node(child_match) {
177 best_match = child_match;
178 best_size = child_size;
179 }
180 }
181 }
182 }
183
184 Some(best_match)
185}
186
187fn is_position_in_node_range(node: tree_sitter::Node, position: Point) -> bool {
189 let start = node.start_position();
190 let end = node.end_position();
191
192 if start.row == end.row {
194 return start.row == position.row
195 && start.column <= position.column
196 && position.column <= end.column;
197 }
198
199 if position.row < start.row || position.row > end.row {
201 return false;
202 }
203
204 if position.row == start.row {
205 return position.column >= start.column;
206 }
207
208 if position.row == end.row {
209 return position.column <= end.column;
210 }
211
212 true
214}
215
216fn node_size(node: tree_sitter::Node) -> usize {
218 let start = node.start_position();
219 let end = node.end_position();
220
221 if start.row == end.row {
222 end.column - start.column
223 } else {
224 (end.row - start.row) * 1000 + end.column + start.column
226 }
227}
228
229fn is_meaningful_node(node: tree_sitter::Node) -> bool {
231 !matches!(
232 node.kind(),
233 "{" | "}"
234 | "("
235 | ")"
236 | "["
237 | "]"
238 | ","
239 | ";"
240 | ":"
241 | "."
242 | "="
243 | "+"
244 | "-"
245 | "*"
246 | "/"
247 | "%"
248 | "<"
249 | ">"
250 | "!"
251 | "&"
252 | "|"
253 | "^"
254 | "~"
255 | "?"
256 | "comment"
257 | "\n"
258 | " "
259 )
260}
261
262pub fn find_node_at_cursor_with_context(tree: &Tree, position: Position) -> Option<CursorContext> {
264 let target_point = Point {
265 row: position.line as usize,
266 column: position.character as usize,
267 };
268
269 let node = find_node_at_position(tree.root_node(), target_point)?;
270
271 Some(CursorContext {
272 target_node_kind: node.kind().to_string(),
273 position: node_to_range(node),
274 context_type: determine_cursor_context(node),
275 parent_context: node.parent().map(|p| determine_cursor_context(p)),
276 details: Some(format!(
277 "Node: {} at {}:{}",
278 node.kind(),
279 position.line,
280 position.character
281 )),
282 })
283}
284
285fn determine_cursor_context(node: tree_sitter::Node) -> CursorContextType {
287 match node.kind() {
288 "identifier" => {
289 if let Some(parent) = node.parent() {
290 match parent.kind() {
291 "var_spec" | "short_var_declaration" => CursorContextType::VariableDeclaration,
292 "parameter_declaration" => CursorContextType::ParameterDeclaration,
293 "field_identifier" => CursorContextType::StructField,
294 "function_declaration" => CursorContextType::FunctionName,
295 "call_expression" => CursorContextType::FunctionCall,
296 "selector_expression" => {
297 if let Some(field_node) = parent.child_by_field_name("field") {
299 if field_node == node {
300 CursorContextType::FieldAccess
301 } else {
302 CursorContextType::ObjectAccess
303 }
304 } else {
305 CursorContextType::VariableUse
306 }
307 }
308 "go_statement" => CursorContextType::GoroutineContext,
309 "assignment_statement" => CursorContextType::Assignment,
310 _ => CursorContextType::VariableUse,
311 }
312 } else {
313 CursorContextType::Unknown
314 }
315 }
316 "field_identifier" => CursorContextType::FieldAccess,
317 "type_identifier" => CursorContextType::TypeReference,
318 "package_identifier" => CursorContextType::PackageReference,
319 "function_declaration" => CursorContextType::FunctionDeclaration,
320 "go_statement" => CursorContextType::GoroutineStatement,
321 "channel_type" => CursorContextType::ChannelType,
322 "interface_type" => CursorContextType::InterfaceType,
323 "struct_type" => CursorContextType::StructType,
324 _ => CursorContextType::Unknown,
325 }
326}
327
328pub fn find_variable_at_position_enhanced(
330 tree: &Tree,
331 code: &str,
332 pos: Position,
333) -> Option<VariableInfo> {
334 let cursor_context = find_node_at_cursor_with_context(tree, pos)?;
336
337 match cursor_context.context_type {
339 CursorContextType::VariableDeclaration
340 | CursorContextType::ParameterDeclaration
341 | CursorContextType::VariableUse
342 | CursorContextType::FieldAccess
343 | CursorContextType::ObjectAccess => {
344 find_variable_at_position(tree, code, pos)
346 }
347 CursorContextType::FunctionCall => {
348 find_variable_at_position(tree, code, pos)
351 }
352 _ => {
353 find_variable_at_position(tree, code, pos)
355 }
356 }
357}
358
359fn extract_variable_name(node: tree_sitter::Node, code: &str) -> Option<String> {
361 match node.kind() {
362 "identifier" => {
363 let byte_range = node.byte_range();
364 code.get(byte_range).map(|s| s.to_string())
365 }
366 "field_identifier" => {
367 let byte_range = node.byte_range();
369 code.get(byte_range).map(|s| s.to_string())
370 }
371 "method_identifier" => {
372 let byte_range = node.byte_range();
374 code.get(byte_range).map(|s| s.to_string())
375 }
376 _ => {
377 for i in 0..node.child_count() {
379 if let Some(child) = node.child(i) {
380 if let Some(name) = extract_variable_name(child, code) {
381 return Some(name);
382 }
383 }
384 }
385 None
386 }
387 }
388}
389
390fn find_function_scope(node: tree_sitter::Node, target: Point) -> Option<tree_sitter::Node> {
392 if (node.kind() == "function_declaration" || node.kind() == "method_declaration")
393 && node.start_position() <= target
394 && target <= node.end_position()
395 {
396 return Some(node);
397 }
398
399 for i in 0..node.child_count() {
400 if let Some(child) = node.child(i) {
401 if let Some(scope) = find_function_scope(child, target) {
402 return Some(scope);
403 }
404 }
405 }
406
407 None
408}
409
410fn collect_variable_info(
412 tree: &Tree,
413 code: &str,
414 var_name: &str,
415 scope: Option<tree_sitter::Node>,
416) -> Option<VariableInfo> {
417 let search_root = scope.unwrap_or(tree.root_node());
418
419 let mut var_info = VariableInfo {
420 name: var_name.to_string(),
421 declaration: Range::new(Position::new(0, 0), Position::new(0, 0)),
422 uses: vec![],
423 is_pointer: false,
424 potential_race: false,
425 race_severity: RaceSeverity::Medium,
426 var_id: VarId {
427 start_byte: 0,
428 end_byte: 0,
429 },
430 };
431
432 let mut found_declaration = false;
433
434 fn traverse_for_variable(
435 node: tree_sitter::Node,
436 code: &str,
437 var_name: &str,
438 var_info: &mut VariableInfo,
439 found_declaration: &mut bool,
440 ) {
441 match node.kind() {
442 "var_spec" | "short_var_declaration" => {
444 handle_variable_declaration(node, code, var_name, var_info, found_declaration);
445 }
446 "parameter_declaration" => {
448 handle_parameter_declaration(node, code, var_name, var_info, found_declaration);
449 }
450 "range_clause" => {
452 handle_range_clause(node, code, var_name, var_info, found_declaration);
453 }
454 "type_switch_statement" => {
456 handle_type_switch(node, code, var_name, var_info, found_declaration);
457 }
458 "identifier" | "field_identifier" => {
460 handle_identifier_use(node, code, var_name, var_info);
461 }
462 "selector_expression" => {
464 handle_selector_expression(node, code, var_name, var_info);
465 }
466 _ => {}
467 }
468
469 for i in 0..node.child_count() {
471 if let Some(child) = node.child(i) {
472 traverse_for_variable(child, code, var_name, var_info, found_declaration);
473 }
474 }
475 }
476
477 traverse_for_variable(
478 search_root,
479 code,
480 var_name,
481 &mut var_info,
482 &mut found_declaration,
483 );
484
485 if found_declaration || !var_info.uses.is_empty() {
486 Some(var_info)
487 } else {
488 None
489 }
490}
491
492fn handle_variable_declaration(
494 node: tree_sitter::Node,
495 code: &str,
496 var_name: &str,
497 var_info: &mut VariableInfo,
498 found_declaration: &mut bool,
499) {
500 for i in 0..node.child_count() {
501 if let Some(child) = node.child(i) {
502 if child.kind() == "identifier" {
503 let byte_range = child.byte_range();
504 if let Some(name) = code.get(byte_range.clone()) {
505 if name == var_name {
506 var_info.declaration = node_to_range(child);
507 var_info.var_id = VarId {
508 start_byte: byte_range.start,
509 end_byte: byte_range.end,
510 };
511 *found_declaration = true;
512
513 if let Some(parent) = node.parent() {
515 check_pointer_context(parent, code, var_info);
516 }
517 }
518 }
519 }
520 }
521 }
522}
523
524fn handle_parameter_declaration(
526 node: tree_sitter::Node,
527 code: &str,
528 var_name: &str,
529 var_info: &mut VariableInfo,
530 found_declaration: &mut bool,
531) {
532 if let Some(name_node) = node.child_by_field_name("name") {
533 let byte_range = name_node.byte_range();
534 if let Some(name) = code.get(byte_range.clone()) {
535 if name == var_name {
536 var_info.declaration = node_to_range(name_node);
537 var_info.var_id = VarId {
538 start_byte: byte_range.start,
539 end_byte: byte_range.end,
540 };
541 *found_declaration = true;
542
543 if let Some(type_node) = node.child_by_field_name("type") {
545 if type_node.kind() == "pointer_type" {
546 var_info.is_pointer = true;
547 }
548 }
549 }
550 }
551 }
552}
553
554fn handle_range_clause(
556 node: tree_sitter::Node,
557 code: &str,
558 var_name: &str,
559 var_info: &mut VariableInfo,
560 found_declaration: &mut bool,
561) {
562 for i in 0..node.child_count() {
564 if let Some(child) = node.child(i) {
565 if child.kind() == "identifier" {
566 let byte_range = child.byte_range();
567 if let Some(name) = code.get(byte_range.clone()) {
568 if name == var_name {
569 var_info.declaration = node_to_range(child);
570 var_info.var_id = VarId {
571 start_byte: byte_range.start,
572 end_byte: byte_range.end,
573 };
574 *found_declaration = true;
575 }
576 }
577 }
578 }
579 }
580}
581
582fn handle_type_switch(
584 node: tree_sitter::Node,
585 code: &str,
586 var_name: &str,
587 var_info: &mut VariableInfo,
588 found_declaration: &mut bool,
589) {
590 if let Some(assign_node) = node.child_by_field_name("initializer") {
592 handle_variable_declaration(assign_node, code, var_name, var_info, found_declaration);
593 }
594}
595
596fn handle_identifier_use(
598 node: tree_sitter::Node,
599 code: &str,
600 var_name: &str,
601 var_info: &mut VariableInfo,
602) {
603 let byte_range = node.byte_range();
604 if let Some(name) = code.get(byte_range) {
605 if name == var_name {
606 let use_range = node_to_range(node);
607
608 if use_range == var_info.declaration {
610 return;
611 }
612
613 if var_info.uses.contains(&use_range) {
615 return;
616 }
617
618 if let Some(parent) = node.parent() {
620 check_pointer_context(parent, code, var_info);
621
622 if matches!(
624 parent.kind(),
625 "var_spec" | "short_var_declaration" | "parameter_declaration"
626 ) {
627 return;
628 }
629 }
630
631 var_info.uses.push(use_range);
632 }
633 }
634}
635
636fn handle_selector_expression(
638 node: tree_sitter::Node,
639 code: &str,
640 var_name: &str,
641 var_info: &mut VariableInfo,
642) {
643 if let Some(operand) = node.child_by_field_name("operand") {
645 if operand.kind() == "identifier" {
646 let byte_range = operand.byte_range();
647 if let Some(name) = code.get(byte_range) {
648 if name == var_name {
649 let use_range = node_to_range(operand);
650 if !var_info.uses.contains(&use_range) && use_range != var_info.declaration {
651 var_info.uses.push(use_range);
652 }
653 }
654 }
655 }
656 }
657
658 if let Some(field) = node.child_by_field_name("field") {
660 let byte_range = field.byte_range();
661 if let Some(name) = code.get(byte_range) {
662 if name == var_name {
663 let use_range = node_to_range(field);
664 if !var_info.uses.contains(&use_range) && use_range != var_info.declaration {
665 var_info.uses.push(use_range);
666 }
667 }
668 }
669 }
670}
671
672fn check_pointer_context(node: tree_sitter::Node, code: &str, var_info: &mut VariableInfo) {
674 match node.kind() {
675 "unary_expression" => {
676 if let Some(operator) = node.child_by_field_name("operator") {
678 let op_text = text(code, operator);
679 if op_text == "&" || op_text == "*" {
680 var_info.is_pointer = true;
681 }
682 }
683 }
684 "pointer_type" => {
685 var_info.is_pointer = true;
686 }
687 _ => {
688 if let Some(parent) = node.parent() {
690 check_pointer_context(parent, code, var_info);
691 }
692 }
693 }
694}
695
696pub fn is_variable_reassignment(tree: &Tree, var_name: &str, use_range: Range, code: &str) -> bool {
698 let target_point = Point {
699 row: use_range.start.line as usize,
700 column: use_range.start.character as usize,
701 };
702
703 if let Some(node) = find_node_at_position(tree.root_node(), target_point) {
704 if let Some(parent) = node.parent() {
705 match parent.kind() {
706 "assignment_statement" => {
707 if let Some(left) = parent.child_by_field_name("left") {
710 if contains_variable_name(left, var_name, code) {
712 return true;
713 }
714 }
715 }
716 "short_var_declaration" => {
717 if let Some(left) = parent.child_by_field_name("left") {
720 if contains_variable_name(left, var_name, code) {
721 return false;
724 }
725 }
726 }
727 _ => {}
728 }
729 }
730 }
731 false
732}
733
734fn contains_variable_name(node: tree_sitter::Node, var_name: &str, code: &str) -> bool {
736 match node.kind() {
737 "identifier" => {
738 let node_text = tree_sitter_text(node, code);
739 node_text == var_name
740 }
741 "expression_list" | "identifier_list" => {
742 for i in 0..node.child_count() {
744 if let Some(child) = node.child(i) {
745 if contains_variable_name(child, var_name, code) {
746 return true;
747 }
748 }
749 }
750 false
751 }
752 _ => {
753 for i in 0..node.child_count() {
755 if let Some(child) = node.child(i) {
756 if contains_variable_name(child, var_name, code) {
757 return true;
758 }
759 }
760 }
761 false
762 }
763 }
764}
765
766fn tree_sitter_text(node: tree_sitter::Node, code: &str) -> String {
768 text(code, node).to_string()
769}
770
771#[allow(dead_code)]
773fn is_initial_declaration(_tree: &Tree, _var_name: &str, _current_range: Range) -> bool {
774 true
779}
780
781pub fn is_variable_captured(
783 tree: &Tree,
784 var_name: &str,
785 use_range: Range,
786 declaration_range: Range,
787) -> bool {
788 let target_point = Point {
789 row: use_range.start.line as usize,
790 column: use_range.start.character as usize,
791 };
792
793 let decl_point = Point {
794 row: declaration_range.start.line as usize,
795 column: declaration_range.start.character as usize,
796 };
797
798 if let Some(use_node) = find_node_at_position(tree.root_node(), target_point) {
800 if let Some(decl_node) = find_node_at_position(tree.root_node(), decl_point) {
802 return is_captured_in_different_scope(use_node, decl_node, var_name);
804 }
805 }
806 false
807}
808
809fn is_captured_in_different_scope(
811 use_node: tree_sitter::Node,
812 decl_node: tree_sitter::Node,
813 _var_name: &str,
814) -> bool {
815 let decl_function = find_enclosing_function(decl_node);
817
818 let use_closure = find_enclosing_closure_or_goroutine(use_node);
820 let use_function = find_enclosing_function(use_node);
821
822 match (use_closure, decl_function, use_function) {
823 (Some(_), Some(decl_func), Some(use_func)) => {
824 if decl_func == use_func {
827 true
829 } else {
830 false
832 }
833 }
834 (Some(_), Some(_), None) => {
835 false
838 }
839 (Some(_), None, _) => {
840 true
843 }
844 (None, _, _) => {
845 false
847 }
848 }
849}
850
851fn find_enclosing_function(node: tree_sitter::Node) -> Option<tree_sitter::Node> {
853 let mut current = Some(node);
854
855 while let Some(node) = current {
856 match node.kind() {
857 "function_declaration" | "method_declaration" => {
858 return Some(node);
859 }
860 _ => {
861 current = node.parent();
862 }
863 }
864 }
865 None
866}
867
868#[allow(dead_code)]
870fn is_in_different_closure_scope(
871 use_node: tree_sitter::Node,
872 decl_node: tree_sitter::Node,
873) -> bool {
874 let use_closure = find_enclosing_closure_or_goroutine(use_node);
875 let decl_closure = find_enclosing_closure_or_goroutine(decl_node);
876
877 match (use_closure, decl_closure) {
878 (Some(use_closure_node), Some(decl_closure_node)) => {
879 use_closure_node != decl_closure_node
881 }
882 (Some(_), None) => {
883 true
885 }
886 (None, Some(_)) => {
887 false
889 }
890 (None, None) => {
891 false
893 }
894 }
895}
896
897fn find_enclosing_closure_or_goroutine(node: tree_sitter::Node) -> Option<tree_sitter::Node> {
899 let mut current = Some(node);
900
901 while let Some(node) = current {
902 match node.kind() {
903 "function_literal" => {
904 return Some(node);
905 }
906 "go_statement" => {
907 return Some(node);
908 }
909 "function_declaration" => {
910 return None;
912 }
913 _ => {
914 current = node.parent();
915 }
916 }
917 }
918 None
919}
920
921pub fn is_in_goroutine(tree: &Tree, range: Range) -> bool {
922 let target_point = Point {
923 row: range.start.line as usize,
924 column: range.start.character as usize,
925 };
926
927 find_goroutine_context(tree.root_node(), target_point).is_some()
928}
929
930fn find_goroutine_context(
932 node: tree_sitter::Node,
933 target_point: Point,
934) -> Option<tree_sitter::Node> {
935 if node.start_position() > target_point || target_point > node.end_position() {
937 return None;
938 }
939
940 match node.kind() {
941 "go_statement" => {
942 if node.start_position() <= target_point && target_point <= node.end_position() {
944 return Some(node);
945 }
946 }
947 "function_literal" => {
948 if let Some(parent) = node.parent() {
950 if parent.kind() == "go_statement" {
951 if node.start_position() <= target_point && target_point <= node.end_position()
952 {
953 return Some(parent);
954 }
955 }
956 }
957 }
958 "call_expression" => {
959 if let Some(parent) = node.parent() {
961 if parent.kind() == "go_statement" {
962 if node.start_position() <= target_point && target_point <= node.end_position()
963 {
964 return Some(parent);
965 }
966 }
967 }
968 }
969 _ => {}
970 }
971
972 for i in 0..node.child_count() {
974 if let Some(child) = node.child(i) {
975 if let Some(goroutine_node) = find_goroutine_context(child, target_point) {
976 return Some(goroutine_node);
977 }
978 }
979 }
980
981 None
982}
983
984#[allow(dead_code)]
986pub fn analyze_goroutine_usage(tree: &Tree, var_name: &str, code: &str) -> Vec<GoroutineUsage> {
987 let mut usages = Vec::new();
988
989 fn traverse_goroutines(
990 node: tree_sitter::Node,
991 var_name: &str,
992 code: &str,
993 usages: &mut Vec<GoroutineUsage>,
994 ) {
995 if node.kind() == "go_statement" {
996 let goroutine_usage = analyze_variable_in_goroutine(node, var_name, code);
998 if let Some(usage) = goroutine_usage {
999 usages.push(usage);
1000 }
1001 }
1002
1003 for i in 0..node.child_count() {
1005 if let Some(child) = node.child(i) {
1006 traverse_goroutines(child, var_name, code, usages);
1007 }
1008 }
1009 }
1010
1011 traverse_goroutines(tree.root_node(), var_name, code, &mut usages);
1012 usages
1013}
1014
1015#[allow(dead_code)]
1017fn analyze_variable_in_goroutine(
1018 goroutine_node: tree_sitter::Node,
1019 var_name: &str,
1020 code: &str,
1021) -> Option<GoroutineUsage> {
1022 let mut usage = GoroutineUsage {
1023 goroutine_range: node_to_range(goroutine_node),
1024 variable_accesses: Vec::new(),
1025 goroutine_type: classify_goroutine_type(goroutine_node, code),
1026 potential_race_level: RaceSeverity::Medium,
1027 };
1028
1029 fn find_variable_accesses(
1030 node: tree_sitter::Node,
1031 var_name: &str,
1032 code: &str,
1033 accesses: &mut Vec<VariableAccess>,
1034 ) {
1035 if node.kind() == "identifier" {
1036 let byte_range = node.byte_range();
1037 if let Some(name) = code.get(byte_range) {
1038 if name == var_name {
1039 let access_type = determine_access_type(node, code);
1040 accesses.push(VariableAccess {
1041 range: node_to_range(node),
1042 access_type,
1043 context: get_access_context(node, code),
1044 });
1045 }
1046 }
1047 }
1048
1049 for i in 0..node.child_count() {
1050 if let Some(child) = node.child(i) {
1051 find_variable_accesses(child, var_name, code, accesses);
1052 }
1053 }
1054 }
1055
1056 find_variable_accesses(goroutine_node, var_name, code, &mut usage.variable_accesses);
1057
1058 if !usage.variable_accesses.is_empty() {
1059 usage.potential_race_level = calculate_race_severity(&usage, code);
1061 Some(usage)
1062 } else {
1063 None
1064 }
1065}
1066
1067#[allow(dead_code)]
1069fn classify_goroutine_type(goroutine_node: tree_sitter::Node, _code: &str) -> GoroutineType {
1070 for i in 0..goroutine_node.child_count() {
1072 if let Some(child) = goroutine_node.child(i) {
1073 match child.kind() {
1074 "function_literal" => return GoroutineType::AnonymousFunction,
1075 "call_expression" => {
1076 if let Some(func_node) = child.child_by_field_name("function") {
1078 if func_node.kind() == "selector_expression" {
1079 return GoroutineType::MethodCall;
1080 } else {
1081 return GoroutineType::FunctionCall;
1082 }
1083 }
1084 }
1085 "identifier" => return GoroutineType::FunctionCall,
1086 _ => {}
1087 }
1088 }
1089 }
1090 GoroutineType::Unknown
1091}
1092
1093#[allow(dead_code)]
1095fn determine_access_type(node: tree_sitter::Node, code: &str) -> VariableAccessType {
1096 if let Some(parent) = node.parent() {
1097 match parent.kind() {
1098 "assignment_statement" => {
1099 if let Some(left) = parent.child_by_field_name("left") {
1101 if node_contains_position(left, node.start_position()) {
1102 return VariableAccessType::Write;
1103 }
1104 }
1105 VariableAccessType::Read
1106 }
1107 "unary_expression" => {
1108 if let Some(operator) = parent.child_by_field_name("operator") {
1110 let op_text = text(code, operator);
1111 match op_text {
1112 "&" => VariableAccessType::AddressOf,
1113 "*" => VariableAccessType::Dereference,
1114 _ => VariableAccessType::Read,
1115 }
1116 } else {
1117 VariableAccessType::Read
1118 }
1119 }
1120 "inc_statement" | "dec_statement" => VariableAccessType::Modify,
1121 "composite_literal" | "slice_expression" | "index_expression" => {
1122 VariableAccessType::Read
1123 }
1124 _ => VariableAccessType::Read,
1125 }
1126 } else {
1127 VariableAccessType::Read
1128 }
1129}
1130
1131#[allow(dead_code)]
1133fn get_access_context(node: tree_sitter::Node, _code: &str) -> String {
1134 if let Some(parent) = node.parent() {
1135 match parent.kind() {
1136 "call_expression" => "function call".to_string(),
1137 "assignment_statement" => "assignment".to_string(),
1138 "if_statement" => "conditional".to_string(),
1139 "for_statement" => "loop".to_string(),
1140 "return_statement" => "return".to_string(),
1141 "send_statement" => "channel send".to_string(),
1142 _ => parent.kind().to_string(),
1143 }
1144 } else {
1145 "unknown".to_string()
1146 }
1147}
1148
1149#[allow(dead_code)]
1151fn calculate_race_severity(usage: &GoroutineUsage, code: &str) -> RaceSeverity {
1152 let has_writes = usage.variable_accesses.iter().any(|access| {
1153 matches!(
1154 access.access_type,
1155 VariableAccessType::Write | VariableAccessType::Modify
1156 )
1157 });
1158
1159 let has_address_taken = usage
1160 .variable_accesses
1161 .iter()
1162 .any(|access| matches!(access.access_type, VariableAccessType::AddressOf));
1163
1164 let has_sync = has_synchronization_in_range(usage.goroutine_range, code);
1166
1167 if has_writes || has_address_taken {
1168 if has_sync {
1169 RaceSeverity::Low
1170 } else {
1171 RaceSeverity::High
1172 }
1173 } else {
1174 if has_sync {
1176 RaceSeverity::Low
1177 } else {
1178 RaceSeverity::Medium
1179 }
1180 }
1181}
1182
1183#[allow(dead_code)]
1185fn has_synchronization_in_range(_range: Range, code: &str) -> bool {
1186 code.contains("Lock") || code.contains("Unlock") || code.contains("atomic.")
1189}
1190
1191#[allow(dead_code)]
1193fn node_contains_position(node: tree_sitter::Node, position: Point) -> bool {
1194 node.start_position() <= position && position <= node.end_position()
1195}
1196
1197pub fn count_entities(tree: &Tree, code: &str) -> EntityCount {
1198 fn traverse(node: Node, _code: &str, counts: &mut EntityCount) {
1199 match node.kind() {
1200 "var_spec" | "short_var_declaration" => {
1201 let mut cursor = node.walk();
1202 if cursor.goto_first_child() {
1203 loop {
1204 let child = cursor.node();
1205 if child.kind() == "identifier" {
1206 counts.variables += 1;
1207 } else {
1208 let mut sub_cursor = child.walk();
1209 if sub_cursor.goto_first_child() {
1210 loop {
1211 let sub_child = sub_cursor.node();
1212 if sub_child.kind() == "identifier" {
1213 counts.variables += 1;
1214 }
1215 if !sub_cursor.goto_next_sibling() {
1216 break;
1217 }
1218 }
1219 }
1220 }
1221 if !cursor.goto_next_sibling() {
1222 break;
1223 }
1224 }
1225 }
1226 }
1227 "function_declaration" => counts.functions += 1,
1228 "go_statement" => counts.goroutines += 1,
1229 "channel_type" => counts.channels += 1,
1230 _ => {}
1231 }
1232 let mut cursor = node.walk();
1233 if cursor.goto_first_child() {
1234 loop {
1235 traverse(cursor.node(), _code, counts);
1236 if !cursor.goto_next_sibling() {
1237 break;
1238 }
1239 }
1240 }
1241 }
1242 let mut counts = EntityCount {
1243 variables: 0,
1244 functions: 0,
1245 channels: 0,
1246 goroutines: 0,
1247 };
1248 traverse(tree.root_node(), code, &mut counts);
1249 counts
1250}
1251
1252#[inline]
1253fn text<'a>(code: &'a str, node: Node) -> &'a str {
1254 let bytes = code.as_bytes();
1255 if let Some(slice) = bytes.get(node.start_byte()..node.end_byte()) {
1256 unsafe { std::str::from_utf8_unchecked(slice) }
1257 } else {
1258 ""
1260 }
1261}
1262
1263pub fn build_graph_data(tree: &Tree, code: &str) -> GraphData {
1265 let mut nodes = Vec::new();
1266 let mut edges = Vec::new();
1267
1268 use std::collections::HashMap;
1270 let mut var_decl_ids = HashMap::new();
1271
1272 fn make_id(kind: &str, name: &str, range: &Range) -> String {
1274 format!(
1275 "{}:{}:{}:{}:{}",
1276 kind, name, range.start.line, range.start.character, range.end.character
1277 )
1278 }
1279
1280 fn traverse(
1282 node: Node,
1283 code: &str,
1284 nodes: &mut Vec<GraphNode>,
1285 edges: &mut Vec<GraphEdge>,
1286 var_decl_ids: &mut HashMap<String, String>,
1287 ) {
1288 match node.kind() {
1289 "var_spec" | "short_var_declaration" => {
1290 for i in 0..node.child_count() {
1291 if let Some(child) = node.child(i) {
1292 if child.kind() == "identifier" {
1293 let name = crate::analysis::text(code, child);
1294 let range = crate::util::node_to_range(child);
1295 let id = make_id("var", name, &range);
1296 var_decl_ids.insert(name.to_string(), id.clone());
1297 let node_info = GraphNode {
1298 id: id.clone(),
1299 label: name.to_string(),
1300 entity_type: GraphEntityType::Variable,
1301 range: range.clone(),
1302 extra: None,
1303 };
1304 nodes.push(node_info);
1305 }
1306 }
1307 }
1308 }
1309 "function_declaration" => {
1310 if let Some(ident) = node.child_by_field_name("name") {
1311 let name = crate::analysis::text(code, ident);
1312 let range = crate::util::node_to_range(ident);
1313 let id = make_id("fn", name, &range);
1314 let node_info = GraphNode {
1315 id: id.clone(),
1316 label: name.to_string(),
1317 entity_type: GraphEntityType::Function,
1318 range: range.clone(),
1319 extra: None,
1320 };
1321 nodes.push(node_info);
1322 }
1323 }
1324 "go_statement" => {
1325 let range = crate::util::node_to_range(node);
1326 let id = make_id("go", "goroutine", &range);
1327 let node_info = GraphNode {
1328 id: id.clone(),
1329 label: "goroutine".to_string(),
1330 entity_type: GraphEntityType::Goroutine,
1331 range: range.clone(),
1332 extra: None,
1333 };
1334 nodes.push(node_info);
1335 }
1336 "channel_type" => {
1337 let range = crate::util::node_to_range(node);
1338 let id = make_id("chan", "channel", &range);
1339 let node_info = GraphNode {
1340 id: id.clone(),
1341 label: "channel".to_string(),
1342 entity_type: GraphEntityType::Channel,
1343 range: range.clone(),
1344 extra: None,
1345 };
1346 nodes.push(node_info);
1347 }
1348 _ => {}
1349 }
1350 if node.kind() == "identifier" {
1352 let name = crate::analysis::text(code, node);
1353 let range = crate::util::node_to_range(node);
1354 if let Some(parent) = node.parent() {
1355 if parent.kind() != "var_spec" && parent.kind() != "short_var_declaration" {
1356 if let Some(decl_id) = var_decl_ids.get(name) {
1358 let use_id = make_id("use", name, &range);
1359 nodes.push(GraphNode {
1360 id: use_id.clone(),
1361 label: name.to_string(),
1362 entity_type: GraphEntityType::Variable,
1363 range: range.clone(),
1364 extra: Some(json!({"use": true})),
1365 });
1366 edges.push(GraphEdge {
1367 from: decl_id.clone(),
1368 to: use_id,
1369 edge_type: GraphEdgeType::Use,
1370 });
1371 }
1372 }
1373 }
1374 }
1375 if node.kind() == "call_expression" {
1377 if let Some(func_node) = node.child_by_field_name("function") {
1379 let func_name = crate::analysis::text(code, func_node);
1380 let range = crate::util::node_to_range(func_node);
1381 let to_id = make_id("fn", func_name, &range);
1382 let from_id = make_id("callsite", func_name, &crate::util::node_to_range(node));
1383 edges.push(GraphEdge {
1384 from: from_id,
1385 to: to_id,
1386 edge_type: GraphEdgeType::Call,
1387 });
1388 }
1389 if is_mutex_call(node, code) || is_atomic_call(node, code) {
1391 let sync_id = make_id("sync", "sync", &crate::util::node_to_range(node));
1392 let from_id = make_id("callsite", "sync", &crate::util::node_to_range(node));
1393 edges.push(GraphEdge {
1394 from: from_id,
1395 to: sync_id,
1396 edge_type: GraphEdgeType::Sync,
1397 });
1398 }
1399 }
1400 if node.kind() == "send_statement" {
1401 if let Some(chan_node) = node.child_by_field_name("channel") {
1403 let chan_name = crate::analysis::text(code, chan_node);
1404 let range = crate::util::node_to_range(chan_node);
1405 let to_id = make_id("chan", chan_name, &range);
1406 let from_id = make_id("send", chan_name, &crate::util::node_to_range(node));
1407 edges.push(GraphEdge {
1408 from: from_id,
1409 to: to_id,
1410 edge_type: GraphEdgeType::Send,
1411 });
1412 }
1413 }
1414 if node.kind() == "unary_expression" && crate::analysis::text(code, node).starts_with("<-")
1415 {
1416 if let Some(chan_node) = node.child(0) {
1418 let chan_name = crate::analysis::text(code, chan_node);
1419 let range = crate::util::node_to_range(chan_node);
1420 let to_id = make_id("chan", chan_name, &range);
1421 let from_id = make_id("recv", chan_name, &crate::util::node_to_range(node));
1422 edges.push(GraphEdge {
1423 from: from_id,
1424 to: to_id,
1425 edge_type: GraphEdgeType::Receive,
1426 });
1427 }
1428 }
1429 if node.kind() == "go_statement" {
1430 let range = crate::util::node_to_range(node);
1432 let from_id = make_id("spawnsite", "go", &range);
1433 let to_id = make_id("go", "goroutine", &range);
1434 edges.push(GraphEdge {
1435 from: from_id,
1436 to: to_id,
1437 edge_type: GraphEdgeType::Spawn,
1438 });
1439 }
1440 let mut cursor = node.walk();
1442 if cursor.goto_first_child() {
1443 loop {
1444 traverse(cursor.node(), code, nodes, edges, var_decl_ids);
1445 if !cursor.goto_next_sibling() {
1446 break;
1447 }
1448 }
1449 }
1450 }
1451
1452 traverse(
1453 tree.root_node(),
1454 code,
1455 &mut nodes,
1456 &mut edges,
1457 &mut var_decl_ids,
1458 );
1459 GraphData { nodes, edges }
1460}