#![allow(clippy::collapsible_if)]
#![allow(clippy::clone_on_copy)]
#![allow(clippy::only_used_in_recursion)]
use crate::types::{GraphData, GraphEdge, GraphEdgeType, GraphEntityType, GraphNode};
use crate::{types::*, util::node_to_range};
use serde_json::json;
use tower_lsp::lsp_types::{Position, Range};
use tree_sitter::{Node, Point, Tree};
pub fn has_synchronization_in_block(tree: &Tree, range: Range, code: &str) -> bool {
let target = Point {
row: range.start.line as usize,
column: range.start.character as usize,
};
let mut enclosing: Option<Node> = None;
let mut stack = vec![tree.root_node()];
while let Some(node) = stack.pop() {
if node.kind() == "block"
&& node.start_position() <= target
&& target <= node.end_position()
{
enclosing = Some(node);
break;
}
for i in (0..node.child_count()).rev() {
if let Some(c) = node.child(i) {
stack.push(c);
}
}
}
let block = match enclosing {
Some(b) => b,
None => return false,
};
let mut cursor = block.walk();
if cursor.goto_first_child() {
loop {
let node = cursor.node();
let kind = node.kind();
eprintln!(
"[block_child] kind: {} bytes: {:?}",
kind,
node.byte_range()
);
if kind != "{" && kind != "}" && find_sync_in_node(node, code) {
return true;
}
if !cursor.goto_next_sibling() {
break;
}
}
}
false
}
fn find_sync_in_node(node: Node, code: &str) -> bool {
if node.kind() == "call_expression" {
eprintln!("[has_sync] call_expression: {:?}", text(code, node));
if is_mutex_call(node, code) || is_atomic_call(node, code) {
return true;
}
}
let mut cursor = node.walk();
if cursor.goto_first_child() {
loop {
if find_sync_in_node(cursor.node(), code) {
return true;
}
if !cursor.goto_next_sibling() {
break;
}
}
}
false
}
#[inline]
fn is_mutex_call(call: Node, code: &str) -> bool {
if let Some(sel) = call.child_by_field_name("function") {
if sel.kind() == "selector_expression" {
if let Some(field) = sel.child_by_field_name("field") {
let name = text(code, field);
return matches!(name, "Lock" | "Unlock" | "Wait");
}
}
}
false
}
#[inline]
fn is_atomic_call(call: Node, code: &str) -> bool {
let func = match call.child_by_field_name("function") {
Some(f) => f,
None => return false,
};
if func.kind() == "selector_expression" {
let pkg = func.child_by_field_name("operand").map(|n| text(code, n));
let field = func.child_by_field_name("field").map(|n| text(code, n));
if matches!(pkg, Some("atomic")) {
if let Some(f) = field {
return crate::types::ATOMIC_FUNCS.contains(&f);
}
}
}
false
}
pub fn determine_race_severity(tree: &Tree, range: Range, code: &str) -> RaceSeverity {
let target_point = Point {
row: range.start.line as usize,
column: range.start.character as usize,
};
if let Some(goroutine_node) = find_goroutine_context(tree.root_node(), target_point) {
if has_synchronization_in_goroutine(goroutine_node, code) {
RaceSeverity::Low
} else {
RaceSeverity::High
}
} else {
if has_synchronization_in_block(tree, range, code) {
RaceSeverity::Low
} else {
RaceSeverity::High
}
}
}
fn has_synchronization_in_goroutine(goroutine_node: tree_sitter::Node, code: &str) -> bool {
find_sync_in_node(goroutine_node, code)
}
pub fn find_variable_at_position(tree: &Tree, code: &str, pos: Position) -> Option<VariableInfo> {
let target_point = Point {
row: pos.line as usize,
column: pos.character as usize,
};
let target_node = find_node_at_position(tree.root_node(), target_point)?;
let var_name = extract_variable_name(target_node, code)?;
let function_scope = find_function_scope(tree.root_node(), target_point);
collect_variable_info(tree, code, &var_name, function_scope)
}
fn find_node_at_position(node: tree_sitter::Node, target: Point) -> Option<tree_sitter::Node> {
if !is_position_in_node_range(node, target) {
return None;
}
let mut best_match = node;
let mut best_size = node_size(node);
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
if let Some(child_match) = find_node_at_position(child, target) {
let child_size = node_size(child_match);
if child_size < best_size && is_meaningful_node(child_match) {
best_match = child_match;
best_size = child_size;
}
}
}
}
Some(best_match)
}
fn is_position_in_node_range(node: tree_sitter::Node, position: Point) -> bool {
let start = node.start_position();
let end = node.end_position();
if start.row == end.row {
return start.row == position.row
&& start.column <= position.column
&& position.column <= end.column;
}
if position.row < start.row || position.row > end.row {
return false;
}
if position.row == start.row {
return position.column >= start.column;
}
if position.row == end.row {
return position.column <= end.column;
}
true
}
fn node_size(node: tree_sitter::Node) -> usize {
let start = node.start_position();
let end = node.end_position();
if start.row == end.row {
end.column - start.column
} else {
(end.row - start.row) * 1000 + end.column + start.column
}
}
fn is_meaningful_node(node: tree_sitter::Node) -> bool {
!matches!(
node.kind(),
"{" | "}"
| "("
| ")"
| "["
| "]"
| ","
| ";"
| ":"
| "."
| "="
| "+"
| "-"
| "*"
| "/"
| "%"
| "<"
| ">"
| "!"
| "&"
| "|"
| "^"
| "~"
| "?"
| "comment"
| "\n"
| " "
)
}
pub fn find_node_at_cursor_with_context(tree: &Tree, position: Position) -> Option<CursorContext> {
let target_point = Point {
row: position.line as usize,
column: position.character as usize,
};
let node = find_node_at_position(tree.root_node(), target_point)?;
Some(CursorContext {
target_node_kind: node.kind().to_string(),
position: node_to_range(node),
context_type: determine_cursor_context(node),
parent_context: node.parent().map(|p| determine_cursor_context(p)),
details: Some(format!(
"Node: {} at {}:{}",
node.kind(),
position.line,
position.character
)),
})
}
fn determine_cursor_context(node: tree_sitter::Node) -> CursorContextType {
match node.kind() {
"identifier" => {
if let Some(parent) = node.parent() {
match parent.kind() {
"var_spec" | "short_var_declaration" => CursorContextType::VariableDeclaration,
"parameter_declaration" => CursorContextType::ParameterDeclaration,
"field_identifier" => CursorContextType::StructField,
"function_declaration" => CursorContextType::FunctionName,
"call_expression" => CursorContextType::FunctionCall,
"selector_expression" => {
if let Some(field_node) = parent.child_by_field_name("field") {
if field_node == node {
CursorContextType::FieldAccess
} else {
CursorContextType::ObjectAccess
}
} else {
CursorContextType::VariableUse
}
}
"go_statement" => CursorContextType::GoroutineContext,
"assignment_statement" => CursorContextType::Assignment,
_ => CursorContextType::VariableUse,
}
} else {
CursorContextType::Unknown
}
}
"field_identifier" => CursorContextType::FieldAccess,
"type_identifier" => CursorContextType::TypeReference,
"package_identifier" => CursorContextType::PackageReference,
"function_declaration" => CursorContextType::FunctionDeclaration,
"go_statement" => CursorContextType::GoroutineStatement,
"channel_type" => CursorContextType::ChannelType,
"interface_type" => CursorContextType::InterfaceType,
"struct_type" => CursorContextType::StructType,
_ => CursorContextType::Unknown,
}
}
pub fn find_variable_at_position_enhanced(
tree: &Tree,
code: &str,
pos: Position,
) -> Option<VariableInfo> {
let cursor_context = find_node_at_cursor_with_context(tree, pos)?;
match cursor_context.context_type {
CursorContextType::VariableDeclaration
| CursorContextType::ParameterDeclaration
| CursorContextType::VariableUse
| CursorContextType::FieldAccess
| CursorContextType::ObjectAccess => {
find_variable_at_position(tree, code, pos)
}
CursorContextType::FunctionCall => {
find_variable_at_position(tree, code, pos)
}
_ => {
find_variable_at_position(tree, code, pos)
}
}
}
fn extract_variable_name(node: tree_sitter::Node, code: &str) -> Option<String> {
match node.kind() {
"identifier" => {
let byte_range = node.byte_range();
code.get(byte_range).map(|s| s.to_string())
}
"field_identifier" => {
let byte_range = node.byte_range();
code.get(byte_range).map(|s| s.to_string())
}
"method_identifier" => {
let byte_range = node.byte_range();
code.get(byte_range).map(|s| s.to_string())
}
_ => {
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
if let Some(name) = extract_variable_name(child, code) {
return Some(name);
}
}
}
None
}
}
}
fn find_function_scope(node: tree_sitter::Node, target: Point) -> Option<tree_sitter::Node> {
if (node.kind() == "function_declaration" || node.kind() == "method_declaration")
&& node.start_position() <= target
&& target <= node.end_position()
{
return Some(node);
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
if let Some(scope) = find_function_scope(child, target) {
return Some(scope);
}
}
}
None
}
fn collect_variable_info(
tree: &Tree,
code: &str,
var_name: &str,
scope: Option<tree_sitter::Node>,
) -> Option<VariableInfo> {
let search_root = scope.unwrap_or(tree.root_node());
let mut var_info = VariableInfo {
name: var_name.to_string(),
declaration: Range::new(Position::new(0, 0), Position::new(0, 0)),
uses: vec![],
is_pointer: false,
potential_race: false,
race_severity: RaceSeverity::Medium,
var_id: VarId {
start_byte: 0,
end_byte: 0,
},
};
let mut found_declaration = false;
fn traverse_for_variable(
node: tree_sitter::Node,
code: &str,
var_name: &str,
var_info: &mut VariableInfo,
found_declaration: &mut bool,
) {
match node.kind() {
"var_spec" | "short_var_declaration" => {
handle_variable_declaration(node, code, var_name, var_info, found_declaration);
}
"parameter_declaration" => {
handle_parameter_declaration(node, code, var_name, var_info, found_declaration);
}
"range_clause" => {
handle_range_clause(node, code, var_name, var_info, found_declaration);
}
"type_switch_statement" => {
handle_type_switch(node, code, var_name, var_info, found_declaration);
}
"identifier" | "field_identifier" => {
handle_identifier_use(node, code, var_name, var_info);
}
"selector_expression" => {
handle_selector_expression(node, code, var_name, var_info);
}
_ => {}
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
traverse_for_variable(child, code, var_name, var_info, found_declaration);
}
}
}
traverse_for_variable(
search_root,
code,
var_name,
&mut var_info,
&mut found_declaration,
);
if found_declaration || !var_info.uses.is_empty() {
Some(var_info)
} else {
None
}
}
fn handle_variable_declaration(
node: tree_sitter::Node,
code: &str,
var_name: &str,
var_info: &mut VariableInfo,
found_declaration: &mut bool,
) {
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
if child.kind() == "identifier" {
let byte_range = child.byte_range();
if let Some(name) = code.get(byte_range.clone()) {
if name == var_name {
var_info.declaration = node_to_range(child);
var_info.var_id = VarId {
start_byte: byte_range.start,
end_byte: byte_range.end,
};
*found_declaration = true;
if let Some(parent) = node.parent() {
check_pointer_context(parent, code, var_info);
}
}
}
}
}
}
}
fn handle_parameter_declaration(
node: tree_sitter::Node,
code: &str,
var_name: &str,
var_info: &mut VariableInfo,
found_declaration: &mut bool,
) {
if let Some(name_node) = node.child_by_field_name("name") {
let byte_range = name_node.byte_range();
if let Some(name) = code.get(byte_range.clone()) {
if name == var_name {
var_info.declaration = node_to_range(name_node);
var_info.var_id = VarId {
start_byte: byte_range.start,
end_byte: byte_range.end,
};
*found_declaration = true;
if let Some(type_node) = node.child_by_field_name("type") {
if type_node.kind() == "pointer_type" {
var_info.is_pointer = true;
}
}
}
}
}
}
fn handle_range_clause(
node: tree_sitter::Node,
code: &str,
var_name: &str,
var_info: &mut VariableInfo,
found_declaration: &mut bool,
) {
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
if child.kind() == "identifier" {
let byte_range = child.byte_range();
if let Some(name) = code.get(byte_range.clone()) {
if name == var_name {
var_info.declaration = node_to_range(child);
var_info.var_id = VarId {
start_byte: byte_range.start,
end_byte: byte_range.end,
};
*found_declaration = true;
}
}
}
}
}
}
fn handle_type_switch(
node: tree_sitter::Node,
code: &str,
var_name: &str,
var_info: &mut VariableInfo,
found_declaration: &mut bool,
) {
if let Some(assign_node) = node.child_by_field_name("initializer") {
handle_variable_declaration(assign_node, code, var_name, var_info, found_declaration);
}
}
fn handle_identifier_use(
node: tree_sitter::Node,
code: &str,
var_name: &str,
var_info: &mut VariableInfo,
) {
let byte_range = node.byte_range();
if let Some(name) = code.get(byte_range) {
if name == var_name {
let use_range = node_to_range(node);
if use_range == var_info.declaration {
return;
}
if var_info.uses.contains(&use_range) {
return;
}
if let Some(parent) = node.parent() {
check_pointer_context(parent, code, var_info);
if matches!(
parent.kind(),
"var_spec" | "short_var_declaration" | "parameter_declaration"
) {
return;
}
}
var_info.uses.push(use_range);
}
}
}
fn handle_selector_expression(
node: tree_sitter::Node,
code: &str,
var_name: &str,
var_info: &mut VariableInfo,
) {
if let Some(operand) = node.child_by_field_name("operand") {
if operand.kind() == "identifier" {
let byte_range = operand.byte_range();
if let Some(name) = code.get(byte_range) {
if name == var_name {
let use_range = node_to_range(operand);
if !var_info.uses.contains(&use_range) && use_range != var_info.declaration {
var_info.uses.push(use_range);
}
}
}
}
}
if let Some(field) = node.child_by_field_name("field") {
let byte_range = field.byte_range();
if let Some(name) = code.get(byte_range) {
if name == var_name {
let use_range = node_to_range(field);
if !var_info.uses.contains(&use_range) && use_range != var_info.declaration {
var_info.uses.push(use_range);
}
}
}
}
}
fn check_pointer_context(node: tree_sitter::Node, code: &str, var_info: &mut VariableInfo) {
match node.kind() {
"unary_expression" => {
if let Some(operator) = node.child_by_field_name("operator") {
let op_text = text(code, operator);
if op_text == "&" || op_text == "*" {
var_info.is_pointer = true;
}
}
}
"pointer_type" => {
var_info.is_pointer = true;
}
_ => {
if let Some(parent) = node.parent() {
check_pointer_context(parent, code, var_info);
}
}
}
}
pub fn is_variable_reassignment(tree: &Tree, var_name: &str, use_range: Range, code: &str) -> bool {
let target_point = Point {
row: use_range.start.line as usize,
column: use_range.start.character as usize,
};
if let Some(node) = find_node_at_position(tree.root_node(), target_point) {
if let Some(parent) = node.parent() {
match parent.kind() {
"assignment_statement" => {
if let Some(left) = parent.child_by_field_name("left") {
if contains_variable_name(left, var_name, code) {
return true;
}
}
}
"short_var_declaration" => {
if let Some(left) = parent.child_by_field_name("left") {
if contains_variable_name(left, var_name, code) {
return false;
}
}
}
_ => {}
}
}
}
false
}
fn contains_variable_name(node: tree_sitter::Node, var_name: &str, code: &str) -> bool {
match node.kind() {
"identifier" => {
let node_text = tree_sitter_text(node, code);
node_text == var_name
}
"expression_list" | "identifier_list" => {
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
if contains_variable_name(child, var_name, code) {
return true;
}
}
}
false
}
_ => {
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
if contains_variable_name(child, var_name, code) {
return true;
}
}
}
false
}
}
}
fn tree_sitter_text(node: tree_sitter::Node, code: &str) -> String {
text(code, node).to_string()
}
#[allow(dead_code)]
fn is_initial_declaration(_tree: &Tree, _var_name: &str, _current_range: Range) -> bool {
true
}
pub fn is_variable_captured(
tree: &Tree,
var_name: &str,
use_range: Range,
declaration_range: Range,
) -> bool {
let target_point = Point {
row: use_range.start.line as usize,
column: use_range.start.character as usize,
};
let decl_point = Point {
row: declaration_range.start.line as usize,
column: declaration_range.start.character as usize,
};
if let Some(use_node) = find_node_at_position(tree.root_node(), target_point) {
if let Some(decl_node) = find_node_at_position(tree.root_node(), decl_point) {
return is_captured_in_different_scope(use_node, decl_node, var_name);
}
}
false
}
fn is_captured_in_different_scope(
use_node: tree_sitter::Node,
decl_node: tree_sitter::Node,
_var_name: &str,
) -> bool {
let decl_function = find_enclosing_function(decl_node);
let use_closure = find_enclosing_closure_or_goroutine(use_node);
let use_function = find_enclosing_function(use_node);
match (use_closure, decl_function, use_function) {
(Some(_), Some(decl_func), Some(use_func)) => {
if decl_func == use_func {
true
} else {
false
}
}
(Some(_), Some(_), None) => {
false
}
(Some(_), None, _) => {
true
}
(None, _, _) => {
false
}
}
}
fn find_enclosing_function(node: tree_sitter::Node) -> Option<tree_sitter::Node> {
let mut current = Some(node);
while let Some(node) = current {
match node.kind() {
"function_declaration" | "method_declaration" => {
return Some(node);
}
_ => {
current = node.parent();
}
}
}
None
}
#[allow(dead_code)]
fn is_in_different_closure_scope(
use_node: tree_sitter::Node,
decl_node: tree_sitter::Node,
) -> bool {
let use_closure = find_enclosing_closure_or_goroutine(use_node);
let decl_closure = find_enclosing_closure_or_goroutine(decl_node);
match (use_closure, decl_closure) {
(Some(use_closure_node), Some(decl_closure_node)) => {
use_closure_node != decl_closure_node
}
(Some(_), None) => {
true
}
(None, Some(_)) => {
false
}
(None, None) => {
false
}
}
}
fn find_enclosing_closure_or_goroutine(node: tree_sitter::Node) -> Option<tree_sitter::Node> {
let mut current = Some(node);
while let Some(node) = current {
match node.kind() {
"function_literal" => {
return Some(node);
}
"go_statement" => {
return Some(node);
}
"function_declaration" => {
return None;
}
_ => {
current = node.parent();
}
}
}
None
}
pub fn is_in_goroutine(tree: &Tree, range: Range) -> bool {
let target_point = Point {
row: range.start.line as usize,
column: range.start.character as usize,
};
find_goroutine_context(tree.root_node(), target_point).is_some()
}
fn find_goroutine_context(
node: tree_sitter::Node,
target_point: Point,
) -> Option<tree_sitter::Node> {
if node.start_position() > target_point || target_point > node.end_position() {
return None;
}
match node.kind() {
"go_statement" => {
if node.start_position() <= target_point && target_point <= node.end_position() {
return Some(node);
}
}
"function_literal" => {
if let Some(parent) = node.parent() {
if parent.kind() == "go_statement" {
if node.start_position() <= target_point && target_point <= node.end_position()
{
return Some(parent);
}
}
}
}
"call_expression" => {
if let Some(parent) = node.parent() {
if parent.kind() == "go_statement" {
if node.start_position() <= target_point && target_point <= node.end_position()
{
return Some(parent);
}
}
}
}
_ => {}
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
if let Some(goroutine_node) = find_goroutine_context(child, target_point) {
return Some(goroutine_node);
}
}
}
None
}
#[allow(dead_code)]
pub fn analyze_goroutine_usage(tree: &Tree, var_name: &str, code: &str) -> Vec<GoroutineUsage> {
let mut usages = Vec::new();
fn traverse_goroutines(
node: tree_sitter::Node,
var_name: &str,
code: &str,
usages: &mut Vec<GoroutineUsage>,
) {
if node.kind() == "go_statement" {
let goroutine_usage = analyze_variable_in_goroutine(node, var_name, code);
if let Some(usage) = goroutine_usage {
usages.push(usage);
}
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
traverse_goroutines(child, var_name, code, usages);
}
}
}
traverse_goroutines(tree.root_node(), var_name, code, &mut usages);
usages
}
#[allow(dead_code)]
fn analyze_variable_in_goroutine(
goroutine_node: tree_sitter::Node,
var_name: &str,
code: &str,
) -> Option<GoroutineUsage> {
let mut usage = GoroutineUsage {
goroutine_range: node_to_range(goroutine_node),
variable_accesses: Vec::new(),
goroutine_type: classify_goroutine_type(goroutine_node, code),
potential_race_level: RaceSeverity::Medium,
};
fn find_variable_accesses(
node: tree_sitter::Node,
var_name: &str,
code: &str,
accesses: &mut Vec<VariableAccess>,
) {
if node.kind() == "identifier" {
let byte_range = node.byte_range();
if let Some(name) = code.get(byte_range) {
if name == var_name {
let access_type = determine_access_type(node, code);
accesses.push(VariableAccess {
range: node_to_range(node),
access_type,
context: get_access_context(node, code),
});
}
}
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
find_variable_accesses(child, var_name, code, accesses);
}
}
}
find_variable_accesses(goroutine_node, var_name, code, &mut usage.variable_accesses);
if !usage.variable_accesses.is_empty() {
usage.potential_race_level = calculate_race_severity(&usage, code);
Some(usage)
} else {
None
}
}
#[allow(dead_code)]
fn classify_goroutine_type(goroutine_node: tree_sitter::Node, _code: &str) -> GoroutineType {
for i in 0..goroutine_node.child_count() {
if let Some(child) = goroutine_node.child(i) {
match child.kind() {
"function_literal" => return GoroutineType::AnonymousFunction,
"call_expression" => {
if let Some(func_node) = child.child_by_field_name("function") {
if func_node.kind() == "selector_expression" {
return GoroutineType::MethodCall;
} else {
return GoroutineType::FunctionCall;
}
}
}
"identifier" => return GoroutineType::FunctionCall,
_ => {}
}
}
}
GoroutineType::Unknown
}
#[allow(dead_code)]
fn determine_access_type(node: tree_sitter::Node, code: &str) -> VariableAccessType {
if let Some(parent) = node.parent() {
match parent.kind() {
"assignment_statement" => {
if let Some(left) = parent.child_by_field_name("left") {
if node_contains_position(left, node.start_position()) {
return VariableAccessType::Write;
}
}
VariableAccessType::Read
}
"unary_expression" => {
if let Some(operator) = parent.child_by_field_name("operator") {
let op_text = text(code, operator);
match op_text {
"&" => VariableAccessType::AddressOf,
"*" => VariableAccessType::Dereference,
_ => VariableAccessType::Read,
}
} else {
VariableAccessType::Read
}
}
"inc_statement" | "dec_statement" => VariableAccessType::Modify,
"composite_literal" | "slice_expression" | "index_expression" => {
VariableAccessType::Read
}
_ => VariableAccessType::Read,
}
} else {
VariableAccessType::Read
}
}
#[allow(dead_code)]
fn get_access_context(node: tree_sitter::Node, _code: &str) -> String {
if let Some(parent) = node.parent() {
match parent.kind() {
"call_expression" => "function call".to_string(),
"assignment_statement" => "assignment".to_string(),
"if_statement" => "conditional".to_string(),
"for_statement" => "loop".to_string(),
"return_statement" => "return".to_string(),
"send_statement" => "channel send".to_string(),
_ => parent.kind().to_string(),
}
} else {
"unknown".to_string()
}
}
#[allow(dead_code)]
fn calculate_race_severity(usage: &GoroutineUsage, code: &str) -> RaceSeverity {
let has_writes = usage.variable_accesses.iter().any(|access| {
matches!(
access.access_type,
VariableAccessType::Write | VariableAccessType::Modify
)
});
let has_address_taken = usage
.variable_accesses
.iter()
.any(|access| matches!(access.access_type, VariableAccessType::AddressOf));
let has_sync = has_synchronization_in_range(usage.goroutine_range, code);
if has_writes || has_address_taken {
if has_sync {
RaceSeverity::Low
} else {
RaceSeverity::High
}
} else {
if has_sync {
RaceSeverity::Low
} else {
RaceSeverity::Medium
}
}
}
#[allow(dead_code)]
fn has_synchronization_in_range(_range: Range, code: &str) -> bool {
code.contains("Lock") || code.contains("Unlock") || code.contains("atomic.")
}
#[allow(dead_code)]
fn node_contains_position(node: tree_sitter::Node, position: Point) -> bool {
node.start_position() <= position && position <= node.end_position()
}
pub fn count_entities(tree: &Tree, code: &str) -> EntityCount {
fn traverse(node: Node, _code: &str, counts: &mut EntityCount) {
match node.kind() {
"var_spec" | "short_var_declaration" => {
let mut cursor = node.walk();
if cursor.goto_first_child() {
loop {
let child = cursor.node();
if child.kind() == "identifier" {
counts.variables += 1;
} else {
let mut sub_cursor = child.walk();
if sub_cursor.goto_first_child() {
loop {
let sub_child = sub_cursor.node();
if sub_child.kind() == "identifier" {
counts.variables += 1;
}
if !sub_cursor.goto_next_sibling() {
break;
}
}
}
}
if !cursor.goto_next_sibling() {
break;
}
}
}
}
"function_declaration" => counts.functions += 1,
"go_statement" => counts.goroutines += 1,
"channel_type" => counts.channels += 1,
_ => {}
}
let mut cursor = node.walk();
if cursor.goto_first_child() {
loop {
traverse(cursor.node(), _code, counts);
if !cursor.goto_next_sibling() {
break;
}
}
}
}
let mut counts = EntityCount {
variables: 0,
functions: 0,
channels: 0,
goroutines: 0,
};
traverse(tree.root_node(), code, &mut counts);
counts
}
#[inline]
fn text<'a>(code: &'a str, node: Node) -> &'a str {
let bytes = code.as_bytes();
if let Some(slice) = bytes.get(node.start_byte()..node.end_byte()) {
unsafe { std::str::from_utf8_unchecked(slice) }
} else {
""
}
}
pub fn build_graph_data(tree: &Tree, code: &str) -> GraphData {
let mut nodes = Vec::new();
let mut edges = Vec::new();
use std::collections::HashMap;
let mut var_decl_ids = HashMap::new();
fn make_id(kind: &str, name: &str, range: &Range) -> String {
format!(
"{}:{}:{}:{}:{}",
kind, name, range.start.line, range.start.character, range.end.character
)
}
fn traverse(
node: Node,
code: &str,
nodes: &mut Vec<GraphNode>,
edges: &mut Vec<GraphEdge>,
var_decl_ids: &mut HashMap<String, String>,
) {
match node.kind() {
"var_spec" | "short_var_declaration" => {
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
if child.kind() == "identifier" {
let name = crate::analysis::text(code, child);
let range = crate::util::node_to_range(child);
let id = make_id("var", name, &range);
var_decl_ids.insert(name.to_string(), id.clone());
let node_info = GraphNode {
id: id.clone(),
label: name.to_string(),
entity_type: GraphEntityType::Variable,
range: range.clone(),
extra: None,
};
nodes.push(node_info);
}
}
}
}
"function_declaration" => {
if let Some(ident) = node.child_by_field_name("name") {
let name = crate::analysis::text(code, ident);
let range = crate::util::node_to_range(ident);
let id = make_id("fn", name, &range);
let node_info = GraphNode {
id: id.clone(),
label: name.to_string(),
entity_type: GraphEntityType::Function,
range: range.clone(),
extra: None,
};
nodes.push(node_info);
}
}
"go_statement" => {
let range = crate::util::node_to_range(node);
let id = make_id("go", "goroutine", &range);
let node_info = GraphNode {
id: id.clone(),
label: "goroutine".to_string(),
entity_type: GraphEntityType::Goroutine,
range: range.clone(),
extra: None,
};
nodes.push(node_info);
}
"channel_type" => {
let range = crate::util::node_to_range(node);
let id = make_id("chan", "channel", &range);
let node_info = GraphNode {
id: id.clone(),
label: "channel".to_string(),
entity_type: GraphEntityType::Channel,
range: range.clone(),
extra: None,
};
nodes.push(node_info);
}
_ => {}
}
if node.kind() == "identifier" {
let name = crate::analysis::text(code, node);
let range = crate::util::node_to_range(node);
if let Some(parent) = node.parent() {
if parent.kind() != "var_spec" && parent.kind() != "short_var_declaration" {
if let Some(decl_id) = var_decl_ids.get(name) {
let use_id = make_id("use", name, &range);
nodes.push(GraphNode {
id: use_id.clone(),
label: name.to_string(),
entity_type: GraphEntityType::Variable,
range: range.clone(),
extra: Some(json!({"use": true})),
});
edges.push(GraphEdge {
from: decl_id.clone(),
to: use_id,
edge_type: GraphEdgeType::Use,
});
}
}
}
}
if node.kind() == "call_expression" {
if let Some(func_node) = node.child_by_field_name("function") {
let func_name = crate::analysis::text(code, func_node);
let range = crate::util::node_to_range(func_node);
let to_id = make_id("fn", func_name, &range);
let from_id = make_id("callsite", func_name, &crate::util::node_to_range(node));
edges.push(GraphEdge {
from: from_id,
to: to_id,
edge_type: GraphEdgeType::Call,
});
}
if is_mutex_call(node, code) || is_atomic_call(node, code) {
let sync_id = make_id("sync", "sync", &crate::util::node_to_range(node));
let from_id = make_id("callsite", "sync", &crate::util::node_to_range(node));
edges.push(GraphEdge {
from: from_id,
to: sync_id,
edge_type: GraphEdgeType::Sync,
});
}
}
if node.kind() == "send_statement" {
if let Some(chan_node) = node.child_by_field_name("channel") {
let chan_name = crate::analysis::text(code, chan_node);
let range = crate::util::node_to_range(chan_node);
let to_id = make_id("chan", chan_name, &range);
let from_id = make_id("send", chan_name, &crate::util::node_to_range(node));
edges.push(GraphEdge {
from: from_id,
to: to_id,
edge_type: GraphEdgeType::Send,
});
}
}
if node.kind() == "unary_expression" && crate::analysis::text(code, node).starts_with("<-")
{
if let Some(chan_node) = node.child(0) {
let chan_name = crate::analysis::text(code, chan_node);
let range = crate::util::node_to_range(chan_node);
let to_id = make_id("chan", chan_name, &range);
let from_id = make_id("recv", chan_name, &crate::util::node_to_range(node));
edges.push(GraphEdge {
from: from_id,
to: to_id,
edge_type: GraphEdgeType::Receive,
});
}
}
if node.kind() == "go_statement" {
let range = crate::util::node_to_range(node);
let from_id = make_id("spawnsite", "go", &range);
let to_id = make_id("go", "goroutine", &range);
edges.push(GraphEdge {
from: from_id,
to: to_id,
edge_type: GraphEdgeType::Spawn,
});
}
let mut cursor = node.walk();
if cursor.goto_first_child() {
loop {
traverse(cursor.node(), code, nodes, edges, var_decl_ids);
if !cursor.goto_next_sibling() {
break;
}
}
}
}
traverse(
tree.root_node(),
code,
&mut nodes,
&mut edges,
&mut var_decl_ids,
);
GraphData { nodes, edges }
}