use crate::interface_resolver::BindingRegistry; use crate::manifest::Manifest; use crate::parser::get_node_text;
use anyhow::{anyhow, Context, Result}; use std::collections::{HashMap, HashSet}; use std::iter;
use streaming_iterator::StreamingIterator;
use tree_sitter::{Node as TsNode, Query, QueryCursor, Tree};
use tracing::debug;
pub(crate) const EVM_NODE_NAME: &str = "EVM";
pub(crate) const EVENT_LISTENER_NODE_NAME: &str = "EventListener";
pub(crate) const _REQUIRE_NODE_NAME: &str = "Require"; pub(crate) const IF_CONDITION_NODE_NAME: &str = "IfCondition"; pub(crate) const THEN_BLOCK_NODE_NAME: &str = "ThenBlock"; pub(crate) const ELSE_BLOCK_NODE_NAME: &str = "ElseBlock"; pub(crate) const WHILE_CONDITION_NODE_NAME: &str = "WhileCondition"; pub(crate) const WHILE_BLOCK_NODE_NAME: &str = "WhileBlock"; pub(crate) const FOR_CONDITION_NODE_NAME: &str = "ForCondition"; pub(crate) const FOR_BLOCK_NODE_NAME: &str = "ForBlock";
#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd, serde::Serialize, serde::Deserialize)]
pub enum EdgeType {
Call,
Return,
StorageRead, StorageWrite, Require, IfConditionBranch, ThenBranch, ElseBranch, WhileConditionBranch, WhileBodyBranch, ForConditionBranch, ForBodyBranch, }
#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd, serde::Serialize, serde::Deserialize)]
pub enum NodeType {
Function,
Interface,
Constructor,
Modifier,
Library, StorageVariable, Evm, EventListener, RequireCondition, IfStatement, ThenBlock, ElseBlock, WhileStatement, WhileBlock, ForCondition, ForBlock, }
#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd, serde::Serialize, serde::Deserialize)]
pub enum Visibility {
Public,
Private,
Internal,
External,
Default,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct Node {
pub id: usize,
pub name: String,
pub node_type: NodeType,
pub contract_name: Option<String>,
pub visibility: Visibility,
pub span: (usize, usize),
pub has_explicit_return: bool, pub declared_return_type: Option<String>, pub parameters: Vec<ParameterInfo>, pub revert_message: Option<String>, pub condition_expression: Option<String>, }
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, PartialOrd, Ord)] pub struct ParameterInfo {
pub name: String,
pub param_type: String,
pub description: Option<String>, }
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MappingInfo {
pub name: String,
pub visibility: Visibility,
pub key_types: Vec<String>, pub value_type: String, pub span: (usize, usize), pub full_type_str: String, }
impl crate::cg_dot::ToDotLabel for Node {
fn to_dot_label(&self) -> String {
format!(
"{}{}\n({})", self.contract_name
.as_deref()
.map(|c| format!("{}.", c))
.unwrap_or_default(),
self.name,
format!("{:?}", self.node_type).to_lowercase()
)
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct Edge {
pub source_node_id: usize,
pub target_node_id: usize,
pub edge_type: EdgeType,
pub call_site_span: (usize, usize),
pub return_site_span: Option<(usize, usize)>,
pub sequence_number: usize,
pub returned_value: Option<String>,
pub argument_names: Option<Vec<String>>, pub event_name: Option<String>, pub declared_return_type: Option<String>, }
impl crate::cg_dot::ToDotLabel for Edge {
fn to_dot_label(&self) -> String {
match self.edge_type {
EdgeType::Call => self.sequence_number.to_string(),
EdgeType::Return => "ret".to_string(),
EdgeType::StorageRead => "read".to_string(),
EdgeType::StorageWrite => "write".to_string(),
EdgeType::Require => "require".to_string(), EdgeType::IfConditionBranch => "if_cond".to_string(),
EdgeType::ThenBranch => "then".to_string(),
EdgeType::ElseBranch => "else".to_string(),
EdgeType::WhileConditionBranch => "while_cond".to_string(),
EdgeType::WhileBodyBranch => "while_body".to_string(),
EdgeType::ForConditionBranch => "for_cond".to_string(),
EdgeType::ForBodyBranch => "for_body".to_string(),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct CallGraph {
pub nodes: Vec<Node>,
pub edges: Vec<Edge>,
pub(crate) node_lookup: HashMap<(Option<String>, String), usize>,
}
impl CallGraph {
pub fn new() -> Self {
Default::default()
}
pub fn add_node(
&mut self,
name: String,
node_type: NodeType,
contract_name: Option<String>,
visibility: Visibility,
span: (usize, usize),
) -> usize {
let id = self.nodes.len();
let node = Node {
id,
name: name.clone(),
node_type,
contract_name: contract_name.clone(),
visibility,
span,
has_explicit_return: false,
declared_return_type: None, parameters: Vec::new(), revert_message: None, condition_expression: None, };
self.nodes.push(node);
self.node_lookup.insert((contract_name, name), id);
id
}
pub fn add_edge(
&mut self,
source_node_id: usize,
target_node_id: usize,
edge_type: EdgeType, call_site_span: (usize, usize), return_site_span: Option<(usize, usize)>, sequence_number: usize, returned_value: Option<String>, argument_names: Option<Vec<String>>, event_name: Option<String>, declared_return_type: Option<String>, ) {
debug!(
"Attempting to add edge: {} -> {} (Type: {:?}, Seq: {}, RetVal: {:?}, Args: {:?}, DeclRetType: {:?})",
source_node_id, target_node_id, edge_type, sequence_number, returned_value, argument_names, declared_return_type
);
let edge = Edge {
source_node_id,
target_node_id,
edge_type, call_site_span,
return_site_span, sequence_number,
returned_value, argument_names, event_name,
declared_return_type, };
self.edges.push(edge);
}
pub fn iter_nodes(&self) -> impl Iterator<Item = &Node> {
self.nodes.iter()
}
pub fn iter_edges(&self) -> impl Iterator<Item = &Edge> {
self.edges.iter()
}
pub fn add_explicit_return_edges(
&mut self,
input: &CallGraphGeneratorInput, ctx: &CallGraphGeneratorContext, ) -> Result<()> {
debug!(
"[Address Debug add_explicit_return_edges START] Graph: {:p}, Total Edges: {}",
self,
self.edges.len()
);
let mut found_node4_edges = false;
for (idx, edge) in self.edges.iter().enumerate() {
if edge.source_node_id == 4 {
let target_node_name =
self.nodes
.get(edge.target_node_id)
.map_or("?".to_string(), |n| {
format!(
"{}.{}",
n.contract_name.as_deref().unwrap_or("Global"),
n.name
)
});
debug!(
"Edge Index {}: {} -> {} (Target: '{}'), Type: {:?}, Seq: {}",
idx, edge.source_node_id, edge.target_node_id, target_node_name, edge.edge_type, edge.sequence_number
);
found_node4_edges = true;
}
}
if !found_node4_edges {
debug!("No edges found originating from Node 4.");
}
let mut callee_to_callers_and_seq: HashMap<usize, Vec<(usize, usize)>> = HashMap::new();
for edge in self.edges.iter() {
if edge.edge_type == EdgeType::Call {
callee_to_callers_and_seq
.entry(edge.target_node_id)
.or_default()
.push((edge.source_node_id, edge.sequence_number)); }
}
let return_query_str = r#"
(return_statement
(expression)? @return_value ; Optional: capture the returned expression node
) @return ; Capture the whole return statement node
"#;
let return_query = Query::new(&input.solidity_lang, return_query_str)
.context("Failed to create return statement query")?;
let mut return_cursor = QueryCursor::new();
let source_bytes = input.source.as_bytes();
let mut new_return_edges: Vec<Edge> = Vec::new(); let mut total_returns_found_by_query = 0; let mut total_returns_processed = 0; let mut _nodes_with_explicit_return_set = 0;
for (callee_node_id, callee_node_info, _caller_contract_name_opt) in
&ctx.definition_nodes_info
{
let definition_ts_node = input.tree.root_node()
.descendant_for_byte_range(callee_node_info.span.0, callee_node_info.span.1)
.ok_or_else(|| anyhow!("Failed to find definition TsNode for span {:?} in add_explicit_return_edges", callee_node_info.span))?;
let callee_node_exists = self.nodes.get(*callee_node_id).is_some();
if !callee_node_exists {
debug!(
"Warning: Node ID {} found in definition_nodes_info but not in graph.nodes",
callee_node_id
);
continue; }
if let Some(callee_node_for_log) = self.nodes.get(*callee_node_id) {
debug!(
"Processing callee: Node ID {}, Name: {}.{}, Type: {:?}",
callee_node_for_log.id,
callee_node_for_log
.contract_name
.as_deref()
.unwrap_or("Global"),
callee_node_for_log.name,
callee_node_for_log.node_type
);
}
if *callee_node_id == 21 {
debug!(
"S-expression for Node ID {}:\n{}",
callee_node_id,
definition_ts_node.to_sexp()
);
}
let node_type_for_check = self.nodes[*callee_node_id].node_type.clone();
match node_type_for_check {
NodeType::Function
| NodeType::Modifier
| NodeType::Constructor
| NodeType::Library => {
let actual_declared_ret_type = get_function_return_type(*callee_node_id, ctx, input);
if let Some(node_mut) = self.nodes.get_mut(*callee_node_id) {
if !node_mut.has_explicit_return {
let mut return_check_matches = return_cursor.matches(
&return_query,
definition_ts_node,
|node: TsNode| iter::once(&source_bytes[node.byte_range()]),
);
return_check_matches.advance(); if return_check_matches.get().is_some() {
node_mut.has_explicit_return = true;
_nodes_with_explicit_return_set += 1;
debug!(
"Set has_explicit_return=true for Node ID {}",
*callee_node_id
);
}
}
node_mut.declared_return_type = actual_declared_ret_type.clone();
if actual_declared_ret_type.is_some() {
debug!(
"Set declared_return_type='{:?}' for Node ID {}",
actual_declared_ret_type,
*callee_node_id
);
}
}
if let Some(callers_info) = callee_to_callers_and_seq.get(callee_node_id) {
let mut matches = return_cursor.matches(
&return_query,
definition_ts_node,
|node: TsNode| iter::once(&source_bytes[node.byte_range()]),
);
matches.advance();
while let Some(match_) = matches.get() {
total_returns_found_by_query += 1;
let mut return_node_opt: Option<TsNode> = None;
let mut return_value_node_opt: Option<TsNode> = None;
for capture in match_.captures {
let capture_name =
&return_query.capture_names()[capture.index as usize];
match capture_name.as_ref() {
"return" => return_node_opt = Some(capture.node),
"return_value" => return_value_node_opt = Some(capture.node),
_ => {}
}
}
if let Some(return_node) = return_node_opt {
total_returns_processed += 1;
let return_span =
(return_node.start_byte(), return_node.end_byte());
let return_kind = return_node.kind();
debug!(
" Found return statement within definition: Kind='{}', Span={:?}. => ACCEPTED (within definition node)",
return_kind, return_span
);
let returned_value_text = return_value_node_opt
.map(|n| get_node_text(&n, &input.source).to_string());
for (caller_id, call_sequence) in callers_info {
let callee_node_span = self.nodes[*callee_node_id].span;
new_return_edges.push(Edge {
source_node_id: *callee_node_id,
target_node_id: *caller_id,
edge_type: EdgeType::Return,
call_site_span: callee_node_span, return_site_span: Some(return_span), sequence_number: *call_sequence, returned_value: returned_value_text.clone(), argument_names: None, event_name: None,
declared_return_type: actual_declared_ret_type.clone(), });
}
} else {
debug!(" Warning: Query matched but failed to extract @return capture.");
}
matches.advance(); }
}
}
_ => {} }
}
debug!(
"Total returns found by query: {}", total_returns_found_by_query
);
debug!(
"Total returns processed (found within definition): {}", total_returns_processed
);
debug!(
"Total return edges generated: {}", new_return_edges.len()
);
debug!(
"Edge count BEFORE extend: {}",
self.edges.len()
);
debug!(
"[Address Debug add_explicit_return_edges] Graph: {:p}",
self
); self.edges.extend(new_return_edges);
debug!(
"Edge count AFTER extend: {}",
self.edges.len()
);
Ok(())
}
pub(crate) fn get_ancestor_contracts(
&self,
contract_name: &str,
ctx: &CallGraphGeneratorContext,
) -> Vec<String> {
let mut ancestors = Vec::new();
let mut queue = std::collections::VecDeque::new();
let mut visited = HashSet::new();
queue.push_back(contract_name.to_string());
visited.insert(contract_name.to_string());
while let Some(current_contract) = queue.pop_front() {
ancestors.push(current_contract.clone());
if let Some(parents) = ctx.contract_inherits.get(¤t_contract) {
for parent_name in parents {
if visited.insert(parent_name.clone()) {
queue.push_back(parent_name.clone());
}
}
}
}
ancestors.reverse(); ancestors
}
}
#[derive(Debug)]
pub struct CallGraphGeneratorInput {
pub source: String,
pub tree: Tree,
pub solidity_lang: tree_sitter::Language,
}
impl Clone for CallGraphGeneratorInput {
fn clone(&self) -> Self {
Self {
source: self.source.clone(),
tree: self.tree.clone(),
solidity_lang: self.solidity_lang.clone(),
}
}
}
#[derive(Debug, Clone)]
pub struct NodeInfo {
pub span: (usize, usize),
pub kind: String,
}
#[derive(Debug, Clone, Default)] pub struct CallGraphGeneratorContext {
pub state_var_types: HashMap<(String, String), String>,
pub using_for_directives: HashMap<(Option<String>, String), Vec<String>>,
pub definition_nodes_info: Vec<(usize, NodeInfo, Option<String>)>,
pub all_contracts: HashMap<String, NodeInfo>,
pub contracts_with_explicit_constructors: HashSet<String>,
pub all_libraries: HashMap<String, NodeInfo>,
pub all_interfaces: HashMap<String, NodeInfo>,
pub interface_functions: HashMap<String, Vec<String>>,
pub contract_implements: HashMap<String, Vec<String>>, pub interface_inherits: HashMap<String, Vec<String>>, pub contract_inherits: HashMap<String, Vec<String>>, pub storage_var_nodes: HashMap<(Option<String>, String), usize>, pub contract_mappings: HashMap<(String, String), MappingInfo>,
pub manifest: Option<Manifest>,
pub binding_registry: Option<BindingRegistry>,
}
pub trait CallGraphGeneratorStep {
fn name(&self) -> &'static str;
fn config(&mut self, config: &HashMap<String, String>);
fn generate(
&self,
input: CallGraphGeneratorInput,
ctx: &mut CallGraphGeneratorContext, graph: &mut CallGraph,
) -> Result<()>;
}
pub(crate) fn extract_function_parameters(
fn_like_ts_node: TsNode, source: &str,
) -> Vec<ParameterInfo> {
let mut parameters = Vec::new();
debug!("[cg::extract_function_parameters DEBUG] Analyzing TsNode kind: '{}', text snippet: '{}'", fn_like_ts_node.kind(), get_node_text(&fn_like_ts_node, source).chars().take(70).collect::<String>());
let mut child_cursor = fn_like_ts_node.walk();
for child_node in fn_like_ts_node.children(&mut child_cursor) {
debug!("[cg::extract_function_parameters DEBUG] Child of fn_like_ts_node: Kind='{}', Text='{}'", child_node.kind(), get_node_text(&child_node, source).chars().take(30).collect::<String>());
if child_node.kind() == "parameter" { let type_node = child_node.child_by_field_name("type");
let name_node = child_node.child_by_field_name("name");
if let (Some(tn), Some(nn)) = (type_node, name_node) {
let param_name = crate::parser::get_node_text(&nn, source).to_string();
let param_type = crate::parser::get_node_text(&tn, source).to_string();
debug!("[cg::extract_function_parameters DEBUG] Extracted param: name='{}', type='{}'", param_name, param_type);
parameters.push(ParameterInfo {
name: param_name,
param_type: param_type,
description: None,
});
} else {
debug!("[cg::extract_function_parameters DEBUG] Found 'parameter' node (Kind: '{}', Text: '{}') but missing type or name field.", child_node.kind(), get_node_text(&child_node, source).chars().take(30).collect::<String>());
}
}
}
if parameters.is_empty() {
let signature_text = get_node_text(&fn_like_ts_node, source);
if signature_text.contains('(') && signature_text.contains(')') && !signature_text.contains("()") {
let mut has_parameter_nodes_in_signature = false;
let mut temp_cursor = fn_like_ts_node.walk();
for child in fn_like_ts_node.children(&mut temp_cursor) {
if child.kind() == "parameter" {
has_parameter_nodes_in_signature = true;
break;
}
}
if has_parameter_nodes_in_signature {
debug!("[cg::extract_function_parameters DEBUG] No parameters extracted, but 'parameter' nodes were found as direct children. This indicates the loop logic might be incorrect or tree-sitter grammar differs from expectation.");
} else {
debug!("[cg::extract_function_parameters DEBUG] No parameters extracted, and no 'parameter' nodes found as direct children. This might be a function/constructor with no parameters, or an issue with identifying 'parameter' nodes.");
}
} else {
debug!("[cg::extract_function_parameters DEBUG] No parameters extracted. This appears to be a function/constructor with no parameters based on signature text: '{}'", signature_text.chars().take(50).collect::<String>());
}
}
debug!("[cg::extract_function_parameters DEBUG] For node kind '{}', extracted {} parameters: {:?}", fn_like_ts_node.kind(), parameters.len(), parameters);
parameters
}
pub(crate) fn extract_arguments<'a>(
call_expr_node: TsNode<'a>,
input: &CallGraphGeneratorInput,
) -> Vec<String> {
let mut argument_texts: Vec<String> = Vec::new();
debug!(
"[cg::extract_arguments DEBUG] Extracting argument texts for Call Expr Node: Kind='{}', Span=({:?})",
call_expr_node.kind(),
(call_expr_node.start_byte(), call_expr_node.end_byte())
);
if let Some(arguments_field_node) = call_expr_node.child_by_field_name("arguments") {
debug!("[cg::extract_arguments DEBUG] Found 'arguments' field. Iterating its children.");
let mut arg_cursor = arguments_field_node.walk();
for arg_node in arguments_field_node.children(&mut arg_cursor) { if arg_node.kind() == "call_argument" { let arg_text = get_node_text(&arg_node, &input.source).to_string();
debug!("[cg::extract_arguments DEBUG] Extracted argument text (from 'arguments' field, child is call_argument): '{}'", arg_text);
argument_texts.push(arg_text);
}
}
} else {
debug!("[cg::extract_arguments DEBUG] No 'arguments' field found. Iterating direct children of call_expression for 'call_argument' nodes.");
let mut direct_child_cursor = call_expr_node.walk();
for child_node in call_expr_node.children(&mut direct_child_cursor) {
if child_node.kind() == "call_argument" {
let arg_text = get_node_text(&child_node, &input.source).to_string();
debug!("[cg::extract_arguments DEBUG] Extracted argument text (direct child call_argument): '{}'", arg_text);
argument_texts.push(arg_text);
}
}
if argument_texts.is_empty() {
debug!("[cg::extract_arguments DEBUG] No 'call_argument' nodes found as direct children either (after checking 'arguments' field).");
}
}
argument_texts
}
pub(crate) fn extract_argument_nodes<'a>(call_expr_node: TsNode<'a>) -> Vec<TsNode<'a>> {
let mut argument_nodes_ts: Vec<TsNode<'a>> = Vec::new();
debug!(
"[cg::extract_argument_nodes DEBUG] Extracting argument nodes for Call Expr Node: Kind='{}', Span=({:?})",
call_expr_node.kind(),
(call_expr_node.start_byte(), call_expr_node.end_byte())
);
if let Some(arguments_field_node) = call_expr_node.child_by_field_name("arguments") {
debug!("[cg::extract_argument_nodes DEBUG] Found 'arguments' field. Iterating its children.");
let mut arg_cursor = arguments_field_node.walk();
for arg_node in arguments_field_node.children(&mut arg_cursor) { if arg_node.kind() == "call_argument" { debug!("[cg::extract_argument_nodes DEBUG] Extracted argument node (from 'arguments' field, child is call_argument): Kind='{}', Span=({:?})", arg_node.kind(), (arg_node.start_byte(), arg_node.end_byte()));
argument_nodes_ts.push(arg_node);
}
}
} else {
debug!("[cg::extract_argument_nodes DEBUG] No 'arguments' field found. Iterating direct children of call_expression for 'call_argument' nodes.");
let mut direct_child_cursor = call_expr_node.walk();
for child_node in call_expr_node.children(&mut direct_child_cursor) {
if child_node.kind() == "call_argument" {
debug!("[cg::extract_argument_nodes DEBUG] Extracted argument node (direct child call_argument): Kind='{}', Span=({:?})", child_node.kind(), (child_node.start_byte(), child_node.end_byte()));
argument_nodes_ts.push(child_node);
}
}
if argument_nodes_ts.is_empty() {
debug!("[cg::extract_argument_nodes DEBUG] No 'call_argument' nodes found as direct children either (after checking 'arguments' field).");
}
}
argument_nodes_ts
}
pub(crate) fn get_function_return_type(
target_node_id: usize,
ctx: &CallGraphGeneratorContext,
input: &CallGraphGeneratorInput,
) -> Option<String> {
let definition_node_info_opt = ctx
.definition_nodes_info
.iter()
.find(|(id, _, _)| *id == target_node_id)
.map(|(_, node_info, _)| node_info);
if let Some(definition_node_info) = definition_node_info_opt {
let definition_ts_node = match input.tree.root_node()
.descendant_for_byte_range(definition_node_info.span.0, definition_node_info.span.1) {
Some(node) => node,
None => {
debug!("[Return Type Parse DEBUG] Failed to find TsNode for span {:?} for node ID {}", definition_node_info.span, target_node_id);
return None;
}
};
debug!("[Return Type Parse DEBUG] Analyzing definition node ID {} for return type. S-Expr:\n{}", target_node_id, definition_ts_node.to_sexp());
let return_type_query_str = r#"
[
(function_definition
return_type: (return_type_definition
(parameter
type: (type_name) @return_type_name_node
)
)
)
(interface_declaration (_ ;; Same for interfaces
(function_definition
return_type: (return_type_definition
(parameter
type: (type_name) @return_type_name_node
)
)
)
))
]
"#;
let return_type_query = match Query::new(&input.solidity_lang, return_type_query_str) {
Ok(q) => q,
Err(e) => {
debug!(
"[Return Type Parse DEBUG] Failed to create query for node ID {}: {}",
target_node_id, e
);
return None;
}
};
debug!(
"[Return Type Parse DEBUG] Query created successfully for node ID {}.",
target_node_id
);
let mut cursor = QueryCursor::new();
let source_bytes = input.source.as_bytes();
debug!("[Return Type Parse DEBUG] Running matches on definition node...");
let mut matches = cursor.matches(
&return_type_query,
definition_ts_node, |node: TsNode| iter::once(&source_bytes[node.byte_range()]),
);
matches.advance();
debug!("[Return Type Parse DEBUG] Checking for match result...");
if let Some(match_) = matches.get() {
debug!(
"[Return Type Parse DEBUG] Match found! Processing {} captures...",
match_.captures.len()
); let mut found_expected_capture = false; for (cap_idx, capture) in match_.captures.iter().enumerate() {
let capture_name = &return_type_query.capture_names()[capture.index as usize];
debug!(
"[Return Type Parse DEBUG] Capture {}: Name='{}', Node Kind='{}', Text='{}'",
cap_idx,
capture_name,
capture.node.kind(),
get_node_text(&capture.node, &input.source)
);
if *capture_name == "return_type_name_node" {
debug!("[Return Type Parse DEBUG] Match on '@return_type_name_node'!"); found_expected_capture = true; let type_name_node = capture.node;
let type_name_text = get_node_text(&type_name_node, &input.source).to_string();
if !type_name_text.is_empty() {
debug!(
"[Return Type Parse DEBUG] Found single return type name: '{}'",
type_name_text
);
return Some(type_name_text); } else {
debug!("[Return Type Parse DEBUG] Found empty return type name node.");
}
} else {
debug!("[Return Type Parse DEBUG] Capture name '{}' did not match expected '@return_type_name_node'.", capture_name);
}
}
if !found_expected_capture {
debug!("[Return Type Parse DEBUG] Loop finished. Query matched but the '@return_type_name_node' capture was not found among the captures for node ID {}.", target_node_id);
} else {
debug!("[Return Type Parse DEBUG] Loop finished. Found '@return_type_name_node' capture but it resulted in empty text or didn't return for node ID {}.", target_node_id);
}
} else {
debug!(
"[Return Type Parse DEBUG] Query found no return type match for node ID {}.",
target_node_id
); }
} else {
debug!(
"[Return Type Parse DEBUG] Definition TsNode not found for node ID {}",
target_node_id
);
}
debug!(
"[Return Type Parse DEBUG] Function returning None for node ID {}.",
target_node_id
); None }
pub struct CallGraphGeneratorPipeline {
steps: Vec<Box<dyn CallGraphGeneratorStep>>, enabled_steps: HashSet<String>, }
impl Default for CallGraphGeneratorPipeline {
fn default() -> Self {
Self::new()
}
}
impl CallGraphGeneratorPipeline {
pub fn new() -> Self {
Self {
steps: Vec::new(),
enabled_steps: HashSet::new(), }
}
pub fn add_step(&mut self, step: Box<dyn CallGraphGeneratorStep>) { self.enabled_steps.insert(step.name().to_string()); self.steps.push(step);
}
pub fn enable_step(&mut self, name: &str) {
self.enabled_steps.insert(name.to_string());
}
pub fn disable_step(&mut self, name: &str) {
self.enabled_steps.remove(name);
}
pub fn run(
&mut self,
input: CallGraphGeneratorInput,
ctx: &mut CallGraphGeneratorContext, graph: &mut CallGraph,
config: &HashMap<String, String>, ) -> Result<()> {
for step in self.steps.iter_mut() {
if self.enabled_steps.contains(step.name()) {
step.config(config);
}
}
for step in &self.steps {
if self.enabled_steps.contains(step.name()) {
debug!(
"[Address Debug Pipeline] Running step '{}', Graph: {:p}",
step.name(),
graph
); let step_input = input.clone(); step.generate(step_input, ctx, graph)?;
}
}
Ok(())
}
}