edb_engine/analysis/
analyzer.rs

1// EDB - Ethereum Debugger
2// Copyright (C) 2024 Zhuo Zhang and Wuqi Zhang
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU Affero General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <https://www.gnu.org/licenses/>.
16
17use std::{collections::BTreeMap, path::PathBuf};
18
19use foundry_compilers::artifacts::{
20    ast::SourceLocation, Assignment, Block, ContractDefinition, EnumDefinition, ErrorDefinition,
21    EventDefinition, Expression, ForStatement, FunctionCall, FunctionCallKind, FunctionDefinition,
22    ModifierDefinition, Mutability, PragmaDirective, Source, SourceUnit, StateMutability,
23    Statement, StructDefinition, TypeName, UncheckedBlock, UserDefinedValueTypeDefinition,
24    VariableDeclaration, Visibility,
25};
26use std::collections::HashMap;
27
28use semver::VersionReq;
29use serde::{Deserialize, Serialize};
30use thiserror::Error;
31use tracing::error;
32
33use crate::{
34    // new_usid, AnnotationsToChange,
35    analysis::{
36        visitor::VisitorAction, Contract, ContractRef, Function, FunctionRef, FunctionTypeNameRef,
37        ScopeNode, Step, StepRef, StepVariant, UserDefinedType, UserDefinedTypeRef,
38        UserDefinedTypeVariant, Variable, VariableScope, VariableScopeRef, Visitor, Walk, UCID,
39        UFID, UTID,
40    },
41    block_or_stmt_src,
42    contains_user_defined_type,
43    sloc_ldiff,
44    sloc_rdiff,
45    VariableRef,
46    USID,
47    UVID,
48};
49
50/// Analysis results for a single source file.
51///
52/// Contains all the analysis data for one Solidity source file, including the original
53/// source content, parsed AST, and step-by-step analysis results.
54///
55/// # Fields
56///
57/// - `id`: Unique identifier for this source file
58/// - `path`: File system path to the source file
59/// - `source`: Original source content and metadata
60/// - `ast`: Parsed Abstract Syntax Tree
61/// - `unit`: Processed source unit ready for analysis
62/// - `steps`: List of analyzed execution steps in this file
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct SourceAnalysis {
65    /// Unique identifier for this source file
66    pub id: u32,
67    /// File system path to the source file
68    pub path: PathBuf,
69    /// Processed source unit ready for analysis
70    pub unit: SourceUnit,
71    /// Version requirement for the source file. The source file may not declare the solidity version.
72    pub version_req: Option<VersionReq>,
73    /// Global variable scope of the source file
74    pub global_scope: VariableScopeRef,
75    /// List of analyzed execution steps in this file
76    pub steps: Vec<StepRef>,
77    /// State variables that should be made public
78    pub private_state_variables: Vec<VariableRef>,
79    /// List of all contracts in this file.
80    pub contracts: Vec<ContractRef>,
81    /// List of all functions in this file.
82    pub functions: Vec<FunctionRef>,
83    /// List of all state variables in this file.
84    pub state_variables: Vec<VariableRef>,
85    /// Functions that should be made public
86    pub private_functions: Vec<FunctionRef>,
87    /// Functions that should be made mutable (i.e., neither pure nor view)
88    pub immutable_functions: Vec<FunctionRef>,
89    /// Variables that are defined as function types
90    pub function_types: Vec<FunctionTypeNameRef>,
91    /// User defined types defined in this file.
92    pub user_defined_types: Vec<UserDefinedTypeRef>,
93}
94
95impl SourceAnalysis {
96    /// Returns a mapping of all variables in this source file by their UVID.
97    ///
98    /// This method traverses the entire variable scope tree and collects all
99    /// variables into a flat HashMap for efficient lookup.
100    ///
101    /// # Returns
102    ///
103    /// A HashMap mapping UVIDs to their corresponding VariableRef instances.
104    pub fn variable_table(&self) -> HashMap<UVID, VariableRef> {
105        let mut table = HashMap::default();
106        fn walk_scope(scope: &VariableScopeRef, table: &mut HashMap<UVID, VariableRef>) {
107            for variable in scope.variables() {
108                table.insert(variable.read().id(), variable.clone());
109            }
110            for child in scope.children() {
111                walk_scope(child, table);
112            }
113        }
114        walk_scope(&self.global_scope, &mut table);
115        table
116    }
117
118    /// Returns a mapping of all steps in this source file by their USID.
119    ///
120    /// This method creates a HashMap for efficient step lookup by their
121    /// unique step identifiers.
122    ///
123    /// # Returns
124    ///
125    /// A HashMap mapping USIDs to their corresponding StepRef instances.
126    pub fn step_table(&self) -> HashMap<USID, StepRef> {
127        let mut table = HashMap::default();
128        for step in &self.steps {
129            table.insert(step.read().usid, step.clone());
130        }
131        table
132    }
133
134    /// Returns a mapping of all functions in this source file by their UFID.
135    pub fn function_table(&self) -> HashMap<UFID, FunctionRef> {
136        let mut table = HashMap::default();
137        for function in &self.functions {
138            table.insert(function.read().ufid, function.clone());
139        }
140        table
141    }
142
143    /// Returns a mapping of all contracts in this source file by their UCID.
144    pub fn contract_table(&self) -> HashMap<UCID, ContractRef> {
145        let mut table = HashMap::default();
146        for contract in &self.contracts {
147            table.insert(contract.read().ucid, contract.clone());
148        }
149        table
150    }
151
152    /// Returns a mapping of all user defined types in this source file by their UTID.
153    pub fn user_defined_type_table(&self) -> HashMap<UTID, UserDefinedTypeRef> {
154        let mut table = HashMap::default();
155        for user_defined_type in &self.user_defined_types {
156            table.insert(user_defined_type.read().utid, user_defined_type.clone());
157        }
158        table
159    }
160
161    /// Returns a mapping of all user defined types in this source file by their AST ID.
162    ///
163    /// # Returns
164    ///
165    /// A HashMap mapping AST IDs to their corresponding UserDefinedTypeRef instances.
166    pub fn user_defined_types(&self) -> HashMap<usize, UserDefinedTypeRef> {
167        let mut table = HashMap::default();
168        for user_defined_type in &self.user_defined_types {
169            table.insert(user_defined_type.ast_id(), user_defined_type.clone());
170        }
171        table
172    }
173
174    /// Prints the analysis results in a human-readable format.
175    ///
176    /// This method displays a comprehensive overview of the source analysis,
177    /// including file information, variable scopes, execution steps, and
178    /// recommendations for code improvements.
179    ///
180    /// # Example
181    ///
182    /// ```rust
183    /// use edb_engine::analysis::{Analyzer, SourceAnalysis};
184    /// use foundry_compilers::artifacts::Artifact;
185    ///
186    /// // Assuming you have a compiled artifact
187    /// let artifact: Artifact = /* your compiled artifact */;
188    ///
189    /// // Analyze the artifact
190    /// let analyses = Analyzer::analyze(&artifact).unwrap();
191    ///
192    /// // Print the analysis results for each source file
193    /// for analysis in analyses {
194    ///     analysis.pretty_display();
195    /// }
196    /// ```
197    ///
198    /// This will output something like:
199    /// ```
200    /// === Source Analysis Report ===
201    /// File ID: 1
202    /// Path: contract.sol
203    /// Source Content Length: 1234 characters
204    ///
205    /// === Variable Scopes ===
206    /// Scope(SourceUnit): {}
207    ///   Scope(ContractDefinition): {balance, owner}
208    ///     Scope(Block): {amount}
209    ///
210    /// === Execution Steps (5 total) ===
211    /// Step 1 (USID: 0):
212    ///   Type: Variable Declaration
213    ///   Location: 45:12
214    ///   Source: uint256 balance = 0;
215    ///   Declared variables: balance (state): uint256 (Private)
216    ///
217    /// Step 2 (USID: 1):
218    ///   Type: Expression
219    ///   Location: 50:15
220    ///   Source: transfer(amount);
221    ///   Function calls: transfer(1 args)
222    ///
223    /// === Recommendations ===
224    /// Private state variables that should be made public:
225    ///   - balance (visibility: Private)
226    /// === End Report ===
227    /// ```
228    pub fn pretty_display(&self, sources: &BTreeMap<u32, Source>) {
229        println!("=== Source Analysis Report ===");
230        println!("File ID: {}", self.id);
231        println!("Path: {}", self.path.display());
232        println!();
233
234        // Display variable scope information
235        println!("=== Variable Scopes ===");
236        println!("{}", self.global_scope.pretty_display());
237        println!();
238
239        // Display execution steps
240        println!("=== Execution Steps ({} total) ===", self.steps.len());
241        for (i, step) in self.steps.iter().enumerate() {
242            println!("Step {} (USID: {}):", i + 1, step.read().usid);
243            println!("  Type: {}", self.step_variant_name(&step.read().variant));
244            println!(
245                "  Location: {}:{}",
246                step.read().src.start.map(|s| s.to_string()).unwrap_or_else(|| "?".to_string()),
247                step.read().src.length.map(|l| l.to_string()).unwrap_or_else(|| "?".to_string())
248            );
249
250            // Display source code
251            if let Some(source_code) = self.extract_source_code(sources, &step.read().src) {
252                println!("  Source: {}", source_code.trim());
253            }
254
255            // Display function calls
256            if !step.read().function_calls.is_empty() {
257                println!(
258                    "  Function calls: {}",
259                    self.format_function_calls(&step.read().function_calls)
260                );
261            }
262
263            // Display declared variables
264            if !step.read().declared_variables.is_empty() {
265                println!(
266                    "  Declared variables: {}",
267                    self.format_declared_variables(
268                        step.read()
269                            .declared_variables
270                            .iter()
271                            .map(|v| v.declaration().clone())
272                            .collect::<Vec<_>>()
273                            .as_slice()
274                    )
275                );
276            }
277
278            // Display accessible variables
279            if !step.read().accessible_variables.is_empty() {
280                println!(
281                    "  Accessible variables: {}",
282                    self.format_updated_variables(&step.read().accessible_variables)
283                );
284            }
285
286            // Display updated variables
287            if !step.read().updated_variables.is_empty() {
288                println!(
289                    "  Updated variables: {}",
290                    self.format_updated_variables(&step.read().updated_variables)
291                );
292            }
293            println!();
294        }
295
296        // Display recommendations
297        self.display_recommendations();
298        println!("=== End Report ===");
299    }
300
301    /// Returns a human-readable name for the step variant.
302    fn step_variant_name(&self, variant: &StepVariant) -> String {
303        match variant {
304            StepVariant::FunctionEntry(_) => "Function Entry".to_string(),
305            StepVariant::ModifierEntry(_) => "Modifier Entry".to_string(),
306            StepVariant::Statement(stmt) => self.statement_name(stmt),
307            StepVariant::Statements(_) => "Multiple Statements".to_string(),
308            StepVariant::IfCondition(_) => "If Condition".to_string(),
309            StepVariant::ForLoop(_) => "For Loop".to_string(),
310            StepVariant::WhileLoop(_) => "While Loop".to_string(),
311            StepVariant::DoWhileLoop(_) => "Do-While Loop".to_string(),
312            StepVariant::Try(_) => "Try Statement".to_string(),
313        }
314    }
315
316    /// Returns a human-readable name for a statement.
317    fn statement_name(&self, stmt: &Statement) -> String {
318        match stmt {
319            Statement::Block(_) => "Block".to_string(),
320            Statement::Break(_) => "Break".to_string(),
321            Statement::Continue(_) => "Continue".to_string(),
322            Statement::DoWhileStatement(_) => "Do-While".to_string(),
323            Statement::EmitStatement(_) => "Emit".to_string(),
324            Statement::ExpressionStatement(_) => "Expression".to_string(),
325            Statement::ForStatement(_) => "For".to_string(),
326            Statement::IfStatement(_) => "If".to_string(),
327            Statement::InlineAssembly(_) => "Inline Assembly".to_string(),
328            Statement::PlaceholderStatement(_) => "Placeholder".to_string(),
329            Statement::Return(_) => "Return".to_string(),
330            Statement::RevertStatement(_) => "Revert".to_string(),
331            Statement::TryStatement(_) => "Try".to_string(),
332            Statement::UncheckedBlock(_) => "Unchecked Block".to_string(),
333            Statement::VariableDeclarationStatement(_) => "Variable Declaration".to_string(),
334            Statement::WhileStatement(_) => "While".to_string(),
335        }
336    }
337
338    /// Formats a list of function calls with detailed information.
339    fn format_function_calls(&self, calls: &[FunctionCall]) -> String {
340        calls
341            .iter()
342            .map(|call| {
343                let args = call.arguments.len();
344                // The expression field contains the function being called
345                // We'll use a simple representation since extracting the name is complex
346                format!("function_call({args} args)")
347            })
348            .collect::<Vec<_>>()
349            .join(", ")
350    }
351
352    /// Formats a list of declared variables with detailed information.
353    fn format_declared_variables(&self, variables: &[VariableDeclaration]) -> String {
354        variables
355            .iter()
356            .map(|var| {
357                let name = &var.name;
358                name.to_string()
359            })
360            .collect::<Vec<_>>()
361            .join(", ")
362    }
363
364    /// Formats a list of updated variables with detailed information.
365    fn format_updated_variables(&self, variables: &[VariableRef]) -> String {
366        variables.iter().map(|var| var.read().pretty_display()).collect::<Vec<_>>().join(", ")
367    }
368
369    /// Extracts the source code for a given source location.
370    fn extract_source_code(
371        &self,
372        sources: &BTreeMap<u32, Source>,
373        src: &SourceLocation,
374    ) -> Option<String> {
375        let start = src.start?;
376        let length = src.length?;
377        let index = src.index?;
378
379        if let Some(source) = sources.get(&(index as u32)) {
380            if start + length <= source.content.len() {
381                Some(source.content[start..start + length].to_string())
382            } else {
383                None
384            }
385        } else {
386            None
387        }
388    }
389
390    /// Displays recommendations for code improvements.
391    fn display_recommendations(&self) {
392        let mut has_recommendations = false;
393
394        if !self.private_state_variables.is_empty() {
395            if !has_recommendations {
396                println!("=== Recommendations ===");
397                has_recommendations = true;
398            }
399            println!("Private state variables that should be made public:");
400            for var in &self.private_state_variables {
401                println!(
402                    "  - {} (visibility: {:?})",
403                    var.declaration().name,
404                    var.declaration().visibility
405                );
406            }
407        }
408
409        if !self.private_functions.is_empty() {
410            if !has_recommendations {
411                println!("=== Recommendations ===");
412                has_recommendations = true;
413            }
414            println!("Private functions that should be made public:");
415            for func in &self.private_functions {
416                println!("  - {} (visibility: {:?})", func.name(), func.visibility());
417            }
418        }
419
420        if !self.immutable_functions.is_empty() {
421            if !has_recommendations {
422                println!("=== Recommendations ===");
423                has_recommendations = true;
424            }
425            println!("Functions that should be made mutable:");
426            for func in &self.immutable_functions {
427                let mutability = func
428                    .state_mutability()
429                    .as_ref()
430                    .map(|m| format!("{m:?}"))
431                    .unwrap_or_else(|| "None".to_string());
432                println!("  - {} (mutability: {})", func.name(), mutability);
433            }
434        }
435
436        if has_recommendations {
437            println!();
438        }
439    }
440}
441
442/// Main analyzer for processing Solidity source code and extracting execution steps.
443///
444/// The Analyzer walks through the Abstract Syntax Tree (AST) of Solidity source code
445/// and identifies executable steps, manages variable scopes, and tracks function
446/// visibility and mutability requirements.
447///
448/// # Fields
449///
450/// - `scope_stack`: Stack of variable scopes for managing variable visibility
451/// - `finished_steps`: Completed execution steps that have been fully analyzed
452/// - `current_step`: Currently being analyzed step (if any)
453/// - `private_state_variables`: State variables that should be made public
454/// - `private_functions`: Functions that should be made public
455/// - `immutable_functions`: Functions that should be made mutable
456#[derive(Debug, Clone)]
457pub struct Analyzer {
458    source_id: u32,
459    version_requirements: Vec<String>,
460
461    scope_stack: Vec<VariableScopeRef>,
462
463    finished_steps: Vec<StepRef>,
464    current_step: Option<StepRef>,
465    current_function: Option<FunctionRef>,
466    current_contract: Option<ContractRef>,
467    /// List of all contracts in this file.
468    contracts: Vec<ContractRef>,
469    /// List of all functions in this file.
470    functions: Vec<FunctionRef>,
471    /// A mapping from the `VariableDeclaration` AST node ID to the variable reference.
472    variables: HashMap<usize, VariableRef>,
473    /// List of all state variables in this file.
474    state_variables: Vec<VariableRef>,
475    /// State variables that should be made public
476    private_state_variables: Vec<VariableRef>,
477    /// Functions that should be made public
478    private_functions: Vec<FunctionRef>,
479    /// Functions that should be made mutable (i.e., neither pure nor view)
480    immutable_functions: Vec<FunctionRef>,
481    /// Function types defined in this file.
482    function_types: Vec<FunctionTypeNameRef>,
483    /// User defined types defined in this file.
484    user_defined_types: Vec<UserDefinedTypeRef>,
485}
486
487impl Analyzer {
488    /// Creates a new instance of the Analyzer.
489    ///
490    /// This method initializes a fresh analyzer with default state, ready to analyze
491    /// Solidity source code.
492    ///
493    /// # Returns
494    ///
495    /// A new `Analyzer` instance with empty scope stack and step collections.
496    pub fn new(source_id: u32) -> Self {
497        Self {
498            source_id,
499            version_requirements: Vec::new(),
500            scope_stack: Vec::new(),
501            finished_steps: Vec::new(),
502            current_step: None,
503            current_function: None,
504            current_contract: None,
505            contracts: Vec::new(),
506            functions: Vec::new(),
507            variables: HashMap::default(),
508            state_variables: Vec::new(),
509            private_state_variables: Vec::new(),
510            private_functions: Vec::new(),
511            immutable_functions: Vec::new(),
512            function_types: Vec::new(),
513            user_defined_types: Vec::new(),
514        }
515    }
516
517    /// Analyzes a source unit and returns the analysis results.
518    ///
519    /// This method walks through the AST of the source unit, identifies execution steps,
520    /// manages variable scopes, and collects recommendations for code improvements.
521    ///
522    /// # Arguments
523    ///
524    /// * `source_id` - Unique identifier for the source file
525    /// * `source_path` - File system path to the source file
526    /// * `source_unit` - The source unit to analyze
527    ///
528    /// # Returns
529    ///
530    /// A `Result` containing the `SourceAnalysis` on success, or an `AnalysisError` on failure.
531    ///
532    /// # Errors
533    ///
534    /// Returns an error if the AST walk fails or if there are issues with step partitioning.
535    pub fn analyze(
536        mut self,
537        source_id: u32,
538        source_path: &PathBuf,
539        source_unit: &SourceUnit,
540    ) -> Result<SourceAnalysis, AnalysisError> {
541        source_unit.walk(&mut self).map_err(AnalysisError::Other)?;
542        assert!(self.scope_stack.len() == 1, "scope stack should have exactly one scope");
543        assert!(self.current_step.is_none(), "current step should be none");
544        let version_req = if !self.version_requirements.is_empty() {
545            let compact_version_req = self.version_requirements.join(",");
546            VersionReq::parse(&compact_version_req)
547                .inspect_err(|err| {
548                    error!(source_id, ?source_path, %err, "failed to parse version requirements");
549                })
550                .ok()
551        } else {
552            None
553        };
554        let global_scope = self.scope_stack.pop().expect("global scope should not be empty");
555        let steps = self.finished_steps;
556        let functions = self.functions;
557        Ok(SourceAnalysis {
558            id: source_id,
559            path: source_path.clone(),
560            unit: source_unit.clone(),
561            version_req,
562            global_scope,
563            steps,
564            contracts: self.contracts,
565            private_state_variables: self.private_state_variables,
566            state_variables: self.state_variables,
567            functions,
568            private_functions: self.private_functions,
569            immutable_functions: self.immutable_functions,
570            function_types: self.function_types,
571            user_defined_types: self.user_defined_types,
572        })
573    }
574}
575
576/* Scope analysis utils */
577impl Analyzer {
578    fn current_scope(&self) -> VariableScopeRef {
579        self.scope_stack.last().expect("scope stack is empty").clone()
580    }
581
582    fn enter_new_scope(&mut self, node: ScopeNode) -> eyre::Result<()> {
583        let new_scope = VariableScope {
584            node,
585            variables: Vec::default(),
586            children: vec![],
587            parent: self.scope_stack.last().cloned(),
588        }
589        .into();
590        self.scope_stack.push(new_scope);
591        Ok(())
592    }
593
594    fn declare_variable(&mut self, declaration: &VariableDeclaration) -> eyre::Result<()> {
595        if declaration.name.is_empty() {
596            // if a variable has no name, we skip the variable declaration
597            return Ok(());
598        }
599        if declaration.mutability == Some(Mutability::Immutable)
600            || declaration.mutability == Some(Mutability::Constant)
601            || declaration.constant
602        {
603            // constant and immutable variables are excluded.
604            return Ok(());
605        }
606
607        // collect function types from this variable declaration
608        self.collect_function_types_from_variable(declaration)?;
609
610        // add a new variable to the current scope
611        let scope = self.current_scope();
612        let function = self.current_function.clone();
613        let contract = self.current_contract.clone();
614        let uvid = UVID::next();
615        let state_variable = declaration.state_variable;
616        let variable: VariableRef = Variable::Plain {
617            uvid,
618            declaration: declaration.clone(),
619            state_variable,
620            function,
621            contract,
622        }
623        .into();
624        self.check_state_variable_visibility(&variable)?;
625        if state_variable {
626            self.state_variables.push(variable.clone());
627        }
628        scope.write().variables.push(variable.clone());
629
630        // add the variable to the variable_declarations map
631        self.variables.insert(declaration.id, variable.clone());
632
633        if let Some(step) = self.current_step.as_mut() {
634            // add the variable to the current step
635            step.write().declared_variables.push(variable.clone());
636        }
637        Ok(())
638    }
639
640    fn exit_current_scope(&mut self, src: SourceLocation) -> eyre::Result<()> {
641        assert_eq!(
642            self.current_scope().src(),
643            src,
644            "scope mismatch: the post-visit block's source location does not match the current scope's location"
645        );
646        // close the scope
647        let closed_scope = self.scope_stack.pop().expect("scope stack is empty");
648        if let Some(parent) = self.scope_stack.last_mut() {
649            parent.write().children.push(closed_scope);
650        }
651        Ok(())
652    }
653
654    /// Collects function types from a variable declaration.
655    ///
656    /// This function recursively walks through the type structure of a variable declaration
657    /// and collects any FunctionTypeName instances found within the type hierarchy.
658    ///
659    /// # Arguments
660    /// * `declaration` - The variable declaration to analyze
661    ///
662    /// # Returns
663    /// * `Result<(), eyre::Report>` - Ok if successful, Err if an error occurs during analysis
664    fn collect_function_types_from_variable(
665        &mut self,
666        declaration: &VariableDeclaration,
667    ) -> eyre::Result<()> {
668        if let Some(type_name) = &declaration.type_name {
669            self.collect_function_types_recursive(type_name);
670        }
671        Ok(())
672    }
673
674    /// Recursively collects function types from a TypeName.
675    ///
676    /// This function traverses the type hierarchy and adds any FunctionTypeName
677    /// instances to the function_types collection.
678    ///
679    /// # Arguments
680    /// * `type_name` - The type to analyze for function types
681    fn collect_function_types_recursive(&mut self, type_name: &TypeName) {
682        match type_name {
683            TypeName::FunctionTypeName(function_type) => {
684                // Found a function type - add it to our collection
685                self.function_types.push((*function_type.clone()).into());
686            }
687            TypeName::ArrayTypeName(array_type) => {
688                // Recursively check the array's base type
689                self.collect_function_types_recursive(&array_type.base_type);
690            }
691            TypeName::Mapping(mapping) => {
692                // Recursively check both key and value types
693                self.collect_function_types_recursive(&mapping.key_type);
694                self.collect_function_types_recursive(&mapping.value_type);
695            }
696            TypeName::ElementaryTypeName(_) | TypeName::UserDefinedTypeName(_) => {
697                // These types don't contain function types, so nothing to do
698            }
699        }
700    }
701
702    fn check_state_variable_visibility(&mut self, variable: &VariableRef) -> eyre::Result<()> {
703        let declaration = variable.declaration();
704        if declaration.state_variable {
705            // FIXME: this is a temporary workaround on user defined struct types.
706            // Struct may not be able to be declared as a public state variable.
707            // So here when we encounter a state variable with a user defined type, we skip the visibility check.
708            // In the future, we may further consider to support user defined struct types as public state variables
709            // under the condition that it does not contain inner recursive types (array or mapping fields).
710            if declaration.type_name.as_ref().map(contains_user_defined_type).unwrap_or(false) {
711                return Ok(());
712            }
713
714            // we need to change the visibility of the state variable to public
715            if declaration.visibility != Visibility::Public {
716                self.private_state_variables.push(variable.clone());
717            }
718        }
719        Ok(())
720    }
721}
722
723/* Contract analysis utils */
724impl Analyzer {
725    fn enter_new_contract(&mut self, contract: &ContractDefinition) -> eyre::Result<VisitorAction> {
726        assert!(self.current_contract.is_none(), "Contract cannot be nested");
727        let new_contract: ContractRef = Contract::new(contract.clone()).into();
728        self.current_contract = Some(new_contract);
729        Ok(VisitorAction::Continue)
730    }
731
732    fn exit_current_contract(&mut self) -> eyre::Result<()> {
733        assert!(self.current_contract.is_some(), "current contract should be set");
734        let contract = self.current_contract.take().unwrap();
735        self.contracts.push(contract);
736        Ok(())
737    }
738}
739
740/* Function analysis utils */
741impl Analyzer {
742    fn current_function(&self) -> FunctionRef {
743        self.current_function.as_ref().expect("current function should be set").clone()
744    }
745
746    fn enter_new_function(&mut self, function: &FunctionDefinition) -> eyre::Result<VisitorAction> {
747        assert!(self.current_function.is_none(), "Function cannot be nested");
748        let new_func: FunctionRef =
749            Function::new_function(self.current_contract.clone(), function.clone()).into();
750        self.check_function_visibility_and_mutability(&new_func)?;
751        self.current_function = Some(new_func.clone());
752        Ok(VisitorAction::Continue)
753    }
754
755    fn exit_current_function(&mut self) -> eyre::Result<()> {
756        assert!(self.current_function.is_some(), "current function should be set");
757        let function = self.current_function.take().unwrap();
758        self.functions.push(function);
759        Ok(())
760    }
761
762    fn enter_new_modifier(&mut self, modifier: &ModifierDefinition) -> eyre::Result<VisitorAction> {
763        assert!(self.current_function.is_none(), "Function cannot be nested");
764        let current_contract =
765            self.current_contract.as_ref().expect("current contract should be set");
766        let new_func: FunctionRef =
767            Function::new_modifier(current_contract.clone(), modifier.clone()).into();
768        self.current_function = Some(new_func);
769        Ok(VisitorAction::Continue)
770    }
771
772    fn exit_current_modifier(&mut self) -> eyre::Result<()> {
773        assert!(self.current_function.is_some(), "current function should be set");
774        let function = self.current_function.take().unwrap();
775        self.functions.push(function);
776        Ok(())
777    }
778
779    fn check_function_visibility_and_mutability(&mut self, func: &FunctionRef) -> eyre::Result<()> {
780        if func.visibility() != Visibility::Public && func.visibility() != Visibility::External {
781            self.private_functions.push(func.clone());
782        }
783
784        if func
785            .state_mutability()
786            .as_ref()
787            .is_some_and(|mu| *mu == StateMutability::View || *mu == StateMutability::Pure)
788        {
789            self.immutable_functions.push(func.clone());
790        }
791        Ok(())
792    }
793}
794
795/* Step partition utils */
796impl Analyzer {
797    fn enter_new_statement_step(&mut self, statement: &Statement) -> eyre::Result<VisitorAction> {
798        assert!(self.current_step.is_none(), "Step cannot be nested");
799        let current_function = self.current_function();
800        let current_scope = self.current_scope();
801
802        macro_rules! step {
803            ($variant:ident, $stmt:expr, $loc:expr) => {{
804                let variables_in_scope = current_scope.read().variables_recursive();
805                let new_step: StepRef = Step::new(
806                    current_function.ufid(),
807                    StepVariant::$variant($stmt),
808                    $loc,
809                    current_scope.clone(),
810                    variables_in_scope.clone(),
811                )
812                .into();
813                self.current_step = Some(new_step.clone());
814                // add the step to the current function
815                current_function.write().steps.push(new_step);
816            }};
817        }
818        macro_rules! simple_stmt_to_step {
819            ($stmt:expr) => {
820                step!(Statement, statement.clone(), $stmt.src)
821            };
822        }
823        match statement {
824            Statement::Block(_) => {}
825            Statement::Break(break_stmt) => simple_stmt_to_step!(break_stmt),
826            Statement::Continue(continue_stmt) => simple_stmt_to_step!(continue_stmt),
827            Statement::DoWhileStatement(do_while_statement) => {
828                // the step is the `while(...)`
829                let loc = sloc_rdiff(do_while_statement.src, do_while_statement.body.src);
830                step!(DoWhileLoop, *do_while_statement.clone(), loc);
831
832                // we take over the walk of the sub ast tree in the do-while statement step.
833                let mut single_step_walker = AnalyzerSingleStepWalker { analyzer: self };
834                do_while_statement.condition.walk(&mut single_step_walker)?;
835
836                // end the do-while statement step early and then walk the body of the do-while statement.
837                self.exit_current_statement_step(statement)?;
838                do_while_statement.body.walk(self)?;
839
840                // skip the subtree of the do-while statement since we have already walked it
841                return Ok(VisitorAction::SkipSubtree);
842            }
843            Statement::EmitStatement(emit_statement) => simple_stmt_to_step!(emit_statement),
844            Statement::ExpressionStatement(expr_stmt) => simple_stmt_to_step!(expr_stmt),
845            Statement::ForStatement(for_statement) => {
846                // the step is the `for(...)`
847                let loc = sloc_ldiff(for_statement.src, block_or_stmt_src(&for_statement.body));
848                step!(ForLoop, *for_statement.clone(), loc);
849
850                // we take over the walk of the sub ast tree in the for statement step.
851                let mut single_step_walker = AnalyzerSingleStepWalker { analyzer: self };
852                if let Some(initialization_expression) = &for_statement.initialization_expression {
853                    initialization_expression.walk(&mut single_step_walker)?;
854                }
855                if let Some(condition) = &for_statement.condition {
856                    condition.walk(&mut single_step_walker)?;
857                }
858                if let Some(loop_expression) = &for_statement.loop_expression {
859                    loop_expression.walk(&mut single_step_walker)?;
860                }
861
862                // end the for statement step early and then walk the body of the for statement.
863                self.exit_current_statement_step(statement)?;
864                for_statement.body.walk(self)?;
865
866                // skip the subtree of the for statement since we have already walked it
867                return Ok(VisitorAction::SkipSubtree);
868            }
869            Statement::IfStatement(if_statement) => {
870                // the step is the `if(...)`
871                let loc = sloc_ldiff(if_statement.src, block_or_stmt_src(&if_statement.true_body));
872                step!(IfCondition, *if_statement.clone(), loc);
873
874                // we take over the walk of the sub ast tree in the if statement step.
875                let mut single_step_walker = AnalyzerSingleStepWalker { analyzer: self };
876                if_statement.condition.walk(&mut single_step_walker)?;
877
878                // end the if statement step early and then walk the true and false body of the if statement.
879                self.exit_current_statement_step(statement)?;
880                if_statement.true_body.walk(self)?;
881                if let Some(false_body) = &if_statement.false_body {
882                    false_body.walk(self)?;
883                }
884
885                // skip the subtree of the if statement since we have already walked it
886                return Ok(VisitorAction::SkipSubtree);
887            }
888            Statement::InlineAssembly(inline_assembly) => simple_stmt_to_step!(inline_assembly),
889            Statement::PlaceholderStatement(_) => {}
890            Statement::Return(return_stmt) => simple_stmt_to_step!(return_stmt),
891            Statement::RevertStatement(revert_statement) => simple_stmt_to_step!(revert_statement),
892            Statement::TryStatement(try_statement) => {
893                // the step is the `try`
894                let first_clause = &try_statement.clauses[0];
895                let loc = sloc_ldiff(try_statement.src, first_clause.block.src);
896                step!(Try, *try_statement.clone(), loc);
897
898                // we take over the walk of the sub ast tree in the try statement step.
899                let mut single_step_walker = AnalyzerSingleStepWalker { analyzer: self };
900                try_statement.external_call.walk(&mut single_step_walker)?;
901
902                // end the try statement step early and then walk the clauses of the try statement.
903                self.exit_current_statement_step(statement)?;
904                for clause in &try_statement.clauses {
905                    clause.block.walk(self)?;
906                }
907
908                // skip the subtree of the try statement since we have already walked it
909                return Ok(VisitorAction::SkipSubtree);
910            }
911            Statement::UncheckedBlock(_) => { /* walk in the block */ }
912            Statement::VariableDeclarationStatement(variable_declaration_statement) => {
913                simple_stmt_to_step!(variable_declaration_statement)
914            }
915            Statement::WhileStatement(while_statement) => {
916                // the step is the `while(...)`
917                let loc = sloc_ldiff(while_statement.src, block_or_stmt_src(&while_statement.body));
918                step!(WhileLoop, *while_statement.clone(), loc);
919
920                // we take over the walk of the sub ast tree in the while statement step.
921                let mut single_step_walker = AnalyzerSingleStepWalker { analyzer: self };
922                while_statement.condition.walk(&mut single_step_walker)?;
923
924                // end the while statement step early and then walk the body of the while statement.
925                self.exit_current_statement_step(statement)?;
926                while_statement.body.walk(self)?;
927
928                // skip the subtree of the while statement since we have already walked it
929                return Ok(VisitorAction::SkipSubtree);
930            }
931        };
932        Ok(VisitorAction::Continue)
933    }
934
935    fn enter_new_function_step(
936        &mut self,
937        function: &FunctionDefinition,
938    ) -> eyre::Result<VisitorAction> {
939        assert!(self.current_step.is_none(), "Step cannot be nested");
940        let current_function = self.current_function();
941
942        if function.body.is_none() {
943            // if a function has no body, we skip the function step
944            return Ok(VisitorAction::SkipSubtree);
945        }
946
947        // step is the function header
948        let current_scope = self.current_scope();
949        let accessible_variables = current_scope.read().variables_recursive();
950        let loc = sloc_ldiff(function.src, function.body.as_ref().unwrap().src);
951        let new_step: StepRef = Step::new(
952            current_function.ufid(),
953            StepVariant::FunctionEntry(function.clone()),
954            loc,
955            current_scope,
956            accessible_variables,
957        )
958        .into();
959        self.current_step = Some(new_step.clone());
960        current_function.write().steps.push(new_step);
961
962        // we take over the walk of the sub ast tree in the function step.
963        let mut single_step_walker = AnalyzerSingleStepWalker { analyzer: self };
964        function.parameters.walk(&mut single_step_walker)?;
965        function.return_parameters.walk(&mut single_step_walker)?;
966
967        // end the function step early and then walk the body of the function.
968        let step = self.current_step.take().unwrap();
969        self.finished_steps.push(step);
970        if let Some(body) = &function.body {
971            body.walk(self)?;
972        }
973
974        // skip the subtree of the function since we have already walked it
975        Ok(VisitorAction::SkipSubtree)
976    }
977
978    fn enter_new_modifier_step(
979        &mut self,
980        modifier: &ModifierDefinition,
981    ) -> eyre::Result<VisitorAction> {
982        assert!(self.current_step.is_none(), "Step cannot be nested");
983        let current_function = self.current_function();
984
985        if modifier.body.is_none() {
986            // if a modifier has no body, we skip the modifier step
987            return Ok(VisitorAction::SkipSubtree);
988        }
989
990        // step is the modifier header
991        let current_scope = self.current_scope();
992        let accessible_variables = current_scope.read().variables_recursive();
993        let loc = sloc_ldiff(modifier.src, modifier.body.as_ref().unwrap().src);
994        let new_step: StepRef = Step::new(
995            current_function.ufid(),
996            StepVariant::ModifierEntry(modifier.clone()),
997            loc,
998            current_scope,
999            accessible_variables,
1000        )
1001        .into();
1002        self.current_step = Some(new_step.clone());
1003        current_function.write().steps.push(new_step);
1004
1005        // we take over the walk of the sub ast tree in the modifier step.
1006        let mut single_step_walker = AnalyzerSingleStepWalker { analyzer: self };
1007        modifier.parameters.walk(&mut single_step_walker)?;
1008
1009        // end the modifier step early and then walk the body of the modifier.
1010        let step = self.current_step.take().unwrap();
1011        self.finished_steps.push(step);
1012        if let Some(body) = &modifier.body {
1013            body.walk(self)?;
1014        }
1015
1016        // skip the subtree of the modifier since we have already walked it
1017        Ok(VisitorAction::SkipSubtree)
1018    }
1019
1020    /// Add a function call to the current step, if we are in a step.
1021    fn add_function_call(&mut self, call: &FunctionCall) -> eyre::Result<()> {
1022        if let Some(step) = self.current_step.as_mut() {
1023            if call.kind == FunctionCallKind::FunctionCall {
1024                step.write().function_calls.push(call.clone());
1025            }
1026        }
1027        Ok(())
1028    }
1029
1030    fn exit_current_statement_step(&mut self, statement: &Statement) -> eyre::Result<()> {
1031        if self.current_step.is_none() {
1032            return Ok(());
1033        }
1034
1035        match statement {
1036            Statement::Block(_)
1037            | Statement::PlaceholderStatement(_)
1038            | Statement::UncheckedBlock(_) => {}
1039            _ => {
1040                let step = self.current_step.take().unwrap();
1041                self.finished_steps.push(step);
1042            }
1043        }
1044        Ok(())
1045    }
1046}
1047
1048/* Variable update analysis */
1049impl Analyzer {
1050    fn record_assignment(&mut self, variable: &Assignment) -> eyre::Result<VisitorAction> {
1051        fn get_varaiable(this: &Analyzer, expr: &Expression) -> Option<VariableRef> {
1052            match expr {
1053                Expression::Identifier(identifier) => {
1054                    if let Some(declaration_id) = &identifier.referenced_declaration {
1055                        if declaration_id >= &0 {
1056                            if let Some(variable) = this.variables.get(&(*declaration_id as usize))
1057                            {
1058                                return Some(variable.clone());
1059                            }
1060                        }
1061                    }
1062                    None
1063                }
1064                Expression::IndexAccess(index_access) => {
1065                    if let Some(base_variable) = get_varaiable(this, &index_access.base_expression)
1066                    {
1067                        if let Some(index) = &index_access.index_expression {
1068                            let var = Variable::Index { base: base_variable, index: index.clone() };
1069                            return Some(var.into());
1070                        }
1071                    }
1072                    None
1073                }
1074                Expression::IndexRangeAccess(index_range_access) => {
1075                    if let Some(base_variable) =
1076                        get_varaiable(this, &index_range_access.base_expression)
1077                    {
1078                        let var = Variable::IndexRange {
1079                            base: base_variable,
1080                            start: index_range_access.start_expression.clone(),
1081                            end: index_range_access.end_expression.clone(),
1082                        };
1083                        return Some(var.into());
1084                    }
1085                    None
1086                }
1087                Expression::MemberAccess(member_access) => {
1088                    if let Some(base_variable) = get_varaiable(this, &member_access.expression) {
1089                        let var = Variable::Member {
1090                            base: base_variable,
1091                            member: member_access.member_name.clone(),
1092                        };
1093                        return Some(var.into());
1094                    }
1095                    None
1096                }
1097                Expression::TupleExpression(_) => unreachable!(),
1098                _ => None,
1099            }
1100        }
1101
1102        let updated_variables: Vec<VariableRef> = match &variable.lhs {
1103            Expression::Identifier(_)
1104            | Expression::IndexAccess(_)
1105            | Expression::IndexRangeAccess(_)
1106            | Expression::MemberAccess(_) => {
1107                if let Some(var) = get_varaiable(self, &variable.lhs) {
1108                    vec![var]
1109                } else {
1110                    vec![]
1111                }
1112            }
1113            Expression::TupleExpression(tuple_expression) => {
1114                let mut vars = vec![];
1115                for comp in tuple_expression.components.iter().flatten() {
1116                    if let Some(var) = get_varaiable(self, comp) {
1117                        vars.push(var);
1118                    }
1119                }
1120                vars
1121            }
1122            _ => vec![],
1123        };
1124
1125        if let Some(step) = self.current_step.as_mut() {
1126            step.write().updated_variables.extend(updated_variables);
1127        }
1128        Ok(VisitorAction::Continue)
1129    }
1130
1131    /// Record a declared variable's initial value to the current step's updated variables.
1132    fn record_declared_varaible(&mut self, declaration: &VariableDeclaration) -> eyre::Result<()> {
1133        let Some(step) = self.current_step.as_mut() else {
1134            return Ok(());
1135        };
1136        if declaration.name.is_empty() {
1137            // if the variable has no name, we skip the variable declaration
1138            return Ok(());
1139        }
1140        let variable: VariableRef = self.variables.get(&declaration.id).unwrap().clone();
1141        step.write().updated_variables.push(variable);
1142        Ok(())
1143    }
1144}
1145
1146/* User defined type analysis */
1147impl Analyzer {
1148    fn record_user_defined_value_type(
1149        &mut self,
1150        type_definition: &UserDefinedValueTypeDefinition,
1151    ) -> eyre::Result<()> {
1152        let user_defined_type = UserDefinedType::new(
1153            self.source_id,
1154            UserDefinedTypeVariant::UserDefinedValueType(type_definition.clone()),
1155        );
1156        self.user_defined_types.push(user_defined_type.into());
1157        Ok(())
1158    }
1159
1160    fn record_struct_type(&mut self, struct_definition: &StructDefinition) -> eyre::Result<()> {
1161        let user_defined_type = UserDefinedType::new(
1162            self.source_id,
1163            UserDefinedTypeVariant::Struct(struct_definition.clone()),
1164        );
1165        self.user_defined_types.push(user_defined_type.into());
1166        Ok(())
1167    }
1168
1169    fn record_enum_type(&mut self, enum_definition: &EnumDefinition) -> eyre::Result<()> {
1170        let user_defined_type = UserDefinedType::new(
1171            self.source_id,
1172            UserDefinedTypeVariant::Enum(enum_definition.clone()),
1173        );
1174        self.user_defined_types.push(user_defined_type.into());
1175        Ok(())
1176    }
1177
1178    fn record_contract_type(
1179        &mut self,
1180        contract_definition: &ContractDefinition,
1181    ) -> eyre::Result<()> {
1182        let user_defined_type = UserDefinedType::new(
1183            self.source_id,
1184            UserDefinedTypeVariant::Contract(contract_definition.clone()),
1185        );
1186        self.user_defined_types.push(user_defined_type.into());
1187        Ok(())
1188    }
1189}
1190
1191impl Visitor for Analyzer {
1192    fn visit_source_unit(&mut self, source_unit: &SourceUnit) -> eyre::Result<VisitorAction> {
1193        // enter a global scope
1194        self.enter_new_scope(ScopeNode::SourceUnit(source_unit.clone()))?;
1195        Ok(VisitorAction::Continue)
1196    }
1197
1198    fn post_visit_source_unit(&mut self, _source_unit: &SourceUnit) -> eyre::Result<()> {
1199        assert_eq!(
1200            self.scope_stack.len(),
1201            1,
1202            "Scope stack should only have one scope (the global scope)"
1203        );
1204        assert!(self.current_step.is_none(), "Step should be finished");
1205        Ok(())
1206    }
1207
1208    fn visit_pragma_directive(
1209        &mut self,
1210        directive: &PragmaDirective,
1211    ) -> eyre::Result<VisitorAction> {
1212        let literals = &directive.literals;
1213        if literals.len() > 1 && literals[0].trim() == "solidity" {
1214            let mut version_str = vec![];
1215            let mut current_req = String::new();
1216            let mut i = 1;
1217            while i < literals.len() {
1218                let literal = &literals[i];
1219                if literal.starts_with('.') {
1220                    current_req.push_str(literal);
1221                } else if ["=", "<", ">", "~", "^"].iter().any(|p| literal.starts_with(p)) {
1222                    version_str.push(current_req);
1223                    current_req = literal.clone();
1224                    i += 1;
1225                    current_req.push_str(&literals[i]);
1226                } else {
1227                    version_str.push(current_req);
1228                    current_req = literal.clone();
1229                }
1230                i += 1;
1231            }
1232            version_str.push(current_req);
1233
1234            let version_str =
1235                version_str.into_iter().filter(|s| !s.is_empty()).collect::<Vec<_>>().join(",");
1236            // one source file may have multiple `pragma solidity` directives, we collect all of them
1237            self.version_requirements.push(version_str);
1238        }
1239        Ok(VisitorAction::Continue)
1240    }
1241
1242    fn visit_contract_definition(
1243        &mut self,
1244        _definition: &ContractDefinition,
1245    ) -> eyre::Result<VisitorAction> {
1246        // record the contract type
1247        self.record_contract_type(_definition)?;
1248
1249        // enter a new contract
1250        self.enter_new_contract(_definition)?;
1251
1252        // enter a contract scope
1253        self.enter_new_scope(ScopeNode::ContractDefinition(_definition.clone()))?;
1254        Ok(VisitorAction::Continue)
1255    }
1256
1257    fn post_visit_contract_definition(
1258        &mut self,
1259        _definition: &ContractDefinition,
1260    ) -> eyre::Result<()> {
1261        // exit the contract scope
1262        self.exit_current_scope(_definition.src)?;
1263
1264        // exit the contract
1265        self.exit_current_contract()?;
1266        Ok(())
1267    }
1268
1269    fn visit_user_defined_value_type(
1270        &mut self,
1271        _value_type: &UserDefinedValueTypeDefinition,
1272    ) -> eyre::Result<VisitorAction> {
1273        self.record_user_defined_value_type(_value_type)?;
1274        Ok(VisitorAction::Continue)
1275    }
1276
1277    fn visit_struct_definition(
1278        &mut self,
1279        _definition: &StructDefinition,
1280    ) -> eyre::Result<VisitorAction> {
1281        self.record_struct_type(_definition)?;
1282        Ok(VisitorAction::Continue)
1283    }
1284
1285    fn visit_enum_definition(
1286        &mut self,
1287        _definition: &EnumDefinition,
1288    ) -> eyre::Result<VisitorAction> {
1289        self.record_enum_type(_definition)?;
1290        Ok(VisitorAction::Continue)
1291    }
1292
1293    fn visit_event_definition(
1294        &mut self,
1295        _definition: &EventDefinition,
1296    ) -> eyre::Result<VisitorAction> {
1297        Ok(VisitorAction::SkipSubtree)
1298    }
1299
1300    fn visit_error_definition(
1301        &mut self,
1302        _definition: &ErrorDefinition,
1303    ) -> eyre::Result<VisitorAction> {
1304        Ok(VisitorAction::SkipSubtree)
1305    }
1306
1307    fn visit_function_definition(
1308        &mut self,
1309        definition: &FunctionDefinition,
1310    ) -> eyre::Result<VisitorAction> {
1311        // enter a new function
1312        self.enter_new_function(definition)?;
1313
1314        // enter a variable scope for the function
1315        self.enter_new_scope(ScopeNode::FunctionDefinition(definition.clone()))?;
1316
1317        // enter a function step
1318        self.enter_new_function_step(definition)
1319    }
1320
1321    fn post_visit_function_definition(
1322        &mut self,
1323        definition: &FunctionDefinition,
1324    ) -> eyre::Result<()> {
1325        // exit the function scope
1326        self.exit_current_scope(definition.src)?;
1327
1328        // exit the function
1329        self.exit_current_function()?;
1330        Ok(())
1331    }
1332
1333    fn visit_modifier_definition(
1334        &mut self,
1335        definition: &ModifierDefinition,
1336    ) -> eyre::Result<VisitorAction> {
1337        // enter a new modifier
1338        self.enter_new_modifier(definition)?;
1339
1340        // enter a variable scope for the modifier
1341        self.enter_new_scope(ScopeNode::ModifierDefinition(definition.clone()))?;
1342
1343        // enter a modifier step
1344        self.enter_new_modifier_step(definition)
1345    }
1346
1347    fn post_visit_modifier_definition(
1348        &mut self,
1349        definition: &ModifierDefinition,
1350    ) -> eyre::Result<()> {
1351        // exit the modifier scope
1352        self.exit_current_scope(definition.src)?;
1353
1354        // exit the modifier
1355        self.exit_current_modifier()?;
1356        Ok(())
1357    }
1358
1359    fn visit_block(&mut self, block: &Block) -> eyre::Result<VisitorAction> {
1360        // enter a block scope
1361        self.enter_new_scope(ScopeNode::Block(block.clone()))?;
1362        Ok(VisitorAction::Continue)
1363    }
1364
1365    fn post_visit_block(&mut self, block: &Block) -> eyre::Result<()> {
1366        // exit the block scope
1367        self.exit_current_scope(block.src)?;
1368        Ok(())
1369    }
1370
1371    fn visit_unchecked_block(
1372        &mut self,
1373        unchecked_block: &UncheckedBlock,
1374    ) -> eyre::Result<VisitorAction> {
1375        // enter an unchecked block scope
1376        self.enter_new_scope(ScopeNode::UncheckedBlock(unchecked_block.clone()))?;
1377        Ok(VisitorAction::Continue)
1378    }
1379
1380    fn post_visit_unchecked_block(&mut self, unchecked_block: &UncheckedBlock) -> eyre::Result<()> {
1381        // exit the unchecked block scope
1382        self.exit_current_scope(unchecked_block.src)?;
1383        Ok(())
1384    }
1385
1386    fn visit_for_statement(&mut self, for_statement: &ForStatement) -> eyre::Result<VisitorAction> {
1387        // enter a for statement scope
1388        self.enter_new_scope(ScopeNode::ForStatement(for_statement.clone()))?;
1389        Ok(VisitorAction::Continue)
1390    }
1391
1392    fn post_visit_for_statement(&mut self, for_statement: &ForStatement) -> eyre::Result<()> {
1393        // exit the for statement scope
1394        self.exit_current_scope(for_statement.src)?;
1395        Ok(())
1396    }
1397
1398    fn visit_statement(&mut self, _statement: &Statement) -> eyre::Result<VisitorAction> {
1399        // try to enter a new step
1400        self.enter_new_statement_step(_statement)
1401    }
1402
1403    fn post_visit_statement(&mut self, _statement: &Statement) -> eyre::Result<()> {
1404        // exit the current step
1405        self.exit_current_statement_step(_statement)?;
1406        Ok(())
1407    }
1408
1409    fn visit_function_call(&mut self, function_call: &FunctionCall) -> eyre::Result<VisitorAction> {
1410        self.add_function_call(function_call)?;
1411        Ok(VisitorAction::Continue)
1412    }
1413
1414    fn visit_variable_declaration(
1415        &mut self,
1416        declaration: &VariableDeclaration,
1417    ) -> eyre::Result<VisitorAction> {
1418        // declare a variable
1419        self.declare_variable(declaration)?;
1420        // record the declared variable
1421        self.record_declared_varaible(declaration)?;
1422        Ok(VisitorAction::Continue)
1423    }
1424
1425    fn visit_assignment(&mut self, assignment: &Assignment) -> eyre::Result<VisitorAction> {
1426        // record updated variables
1427        self.record_assignment(assignment)
1428    }
1429}
1430
1431/// A walker wrapping [`Analyzer`] that only walks a single step.
1432#[derive(derive_more::Deref, derive_more::DerefMut)]
1433struct AnalyzerSingleStepWalker<'a> {
1434    #[deref]
1435    #[deref_mut]
1436    analyzer: &'a mut Analyzer,
1437}
1438
1439impl<'a> Visitor for AnalyzerSingleStepWalker<'a> {
1440    fn visit_function_call(&mut self, function_call: &FunctionCall) -> eyre::Result<VisitorAction> {
1441        self.analyzer.add_function_call(function_call)?;
1442        Ok(VisitorAction::Continue)
1443    }
1444
1445    fn visit_variable_declaration(
1446        &mut self,
1447        declaration: &VariableDeclaration,
1448    ) -> eyre::Result<VisitorAction> {
1449        self.analyzer.declare_variable(declaration)?;
1450        self.analyzer.record_declared_varaible(declaration)?;
1451        Ok(VisitorAction::Continue)
1452    }
1453}
1454
1455/// Errors that can occur during source code analysis.
1456///
1457/// This enum represents all possible error conditions that can arise during
1458/// the analysis process, from compilation failures to step partitioning errors.
1459#[derive(Debug, Error)]
1460pub enum AnalysisError {
1461    /// AST data is not available in the compiled artifact
1462    #[error("AST is not selected as compiler output")]
1463    MissingAst,
1464
1465    /// Error during AST conversion
1466    #[error("failed to convert AST: {0}")]
1467    ASTConversionError(eyre::Report),
1468
1469    /// Error during step partitioning of source code
1470    #[error("failed to partition source steps: {0}")]
1471    StepPartitionError(eyre::Report),
1472
1473    /// Other analysis-related errors
1474    #[error("other error: {0}")]
1475    Other(eyre::Report),
1476}
1477
1478#[cfg(test)]
1479pub(crate) mod tests {
1480    use foundry_compilers::{
1481        artifacts::{Severity, Sources},
1482        solc::{SolcCompiler, SolcLanguage, SolcSettings, SolcVersionedInput},
1483        CompilationError, Compiler, CompilerInput,
1484    };
1485    use semver::Version;
1486
1487    use crate::{
1488        compile_contract_source_to_source_unit, source_string_at_location_unchecked, ASTPruner,
1489    };
1490
1491    use super::*;
1492
1493    pub(crate) const TEST_CONTRACT_SOURCE_PATH: &str = "test.sol";
1494    pub(crate) const TEST_CONTRACT_SOURCE_ID: u32 = 0;
1495
1496    /// Utility function to compile Solidity source code and analyze it
1497    ///
1498    /// This function encapsulates the common pattern used across all tests:
1499    /// 1. Compile the source code to get the AST
1500    /// 2. Create an analyzer and analyze the contract
1501    /// 3. Return the analysis result
1502    ///
1503    /// # Arguments
1504    /// * `source` - The Solidity source code as a string
1505    ///
1506    /// # Returns
1507    /// * `SourceAnalysis` - The analysis result containing steps, scopes, and recommendations
1508    pub(crate) fn compile_and_analyze(source: &str) -> (BTreeMap<u32, Source>, SourceAnalysis) {
1509        // Compile the source code to get the AST
1510        let version = Version::parse("0.8.20").unwrap();
1511        let result = compile_contract_source_to_source_unit(version, source, false);
1512        assert!(result.is_ok(), "Source compilation should succeed: {}", result.unwrap_err());
1513
1514        let source_unit = result.unwrap();
1515        let sources = BTreeMap::from([(TEST_CONTRACT_SOURCE_ID, Source::new(source))]);
1516
1517        // Create an analyzer and analyze the contract
1518        let analyzer = Analyzer::new(TEST_CONTRACT_SOURCE_ID);
1519        let analysis = analyzer
1520            .analyze(
1521                TEST_CONTRACT_SOURCE_ID,
1522                &PathBuf::from(TEST_CONTRACT_SOURCE_PATH),
1523                &source_unit,
1524            )
1525            .unwrap();
1526
1527        (sources, analysis)
1528    }
1529
1530    macro_rules! count_step_by_variant {
1531        ($analysis:expr, $variant:ident()) => {
1532            $analysis
1533                .steps
1534                .iter()
1535                .filter(|s| matches!(s.read().variant, StepVariant::$variant(_)))
1536                .count()
1537        };
1538
1539        ($analysis:expr, $variant:ident{}) => {
1540            $analysis
1541                .steps
1542                .iter()
1543                .filter(|s| matches!(s.read().variant, StepVariant::$variant { .. }))
1544                .count()
1545        };
1546    }
1547
1548    macro_rules! count_updated_variables {
1549        ($analysis:expr) => {
1550            $analysis.steps.iter().map(|s| s.read().updated_variables.len()).sum::<usize>()
1551        };
1552    }
1553
1554    #[test]
1555    fn test_function_step() {
1556        // Create a simple contract with a function to test function step extraction
1557        let source = r#"
1558abstract contract TestContract {
1559    function setValue(uint256 newValue) public {}
1560
1561    function getValue() public view returns (uint256) {
1562        return 0;
1563    }
1564
1565    function getBalance() public view returns (uint256 balance) {}
1566
1567    function template() public virtual returns (uint256);
1568}
1569"#;
1570
1571        // Use utility function to compile and analyze
1572        let (_sources, analysis) = compile_and_analyze(source);
1573
1574        // Assert all non-empty functions are present as steps
1575        assert!(count_step_by_variant!(analysis, FunctionEntry()) == 3);
1576    }
1577
1578    #[test]
1579    fn test_statement_step() {
1580        // Create a simple contract with a function to test statement step extraction
1581        let source = r#"
1582contract TestContract {
1583    function getValue() public view returns (uint256) {
1584        uint256 value = 0;
1585        return 0;
1586    }
1587}
1588"#;
1589
1590        // Use utility function to compile and analyze
1591        let (_sources, analysis) = compile_and_analyze(source);
1592        analysis.pretty_display(&_sources);
1593
1594        // Assert that we have two statement steps
1595        assert!(count_step_by_variant!(analysis, Statement()) == 2);
1596    }
1597
1598    #[test]
1599    fn test_if_step() {
1600        // Create a simple contract with a function to test if statement extraction
1601        let source = r#"
1602contract TestContract {
1603    function getValue() public view returns (uint256) {
1604        if (true) {
1605            return 0;
1606        } else {
1607            return 1;
1608        }
1609    }
1610}
1611"#;
1612
1613        // Use utility function to compile and analyze
1614        let (_sources, analysis) = compile_and_analyze(source);
1615
1616        // Assert that we have one if step, and two statement steps
1617        assert!(count_step_by_variant!(analysis, IfCondition()) == 1);
1618        assert!(count_step_by_variant!(analysis, Statement()) == 2);
1619    }
1620
1621    #[test]
1622    fn test_for_step() {
1623        // Create a simple contract with a function to test for statement extraction
1624        let source = r#"
1625contract TestContract {
1626    function getValue() public view returns (uint256) {
1627        for (uint256 i = 0; i < 10; i++) {
1628            return 0;
1629        }
1630    }
1631}
1632"#;
1633
1634        // Use utility function to compile and analyze
1635        let (_sources, analysis) = compile_and_analyze(source);
1636
1637        // Assert that we have one for step, and one statement step
1638        assert!(count_step_by_variant!(analysis, ForLoop {}) == 1);
1639        assert!(count_step_by_variant!(analysis, Statement()) == 1);
1640    }
1641
1642    #[test]
1643    fn test_while_step() {
1644        // Create a simple contract with a function to test while statement extraction
1645        let source = r#"
1646contract TestContract {
1647    function getValue() public view returns (uint256) {
1648        while (true) {
1649            return 0;
1650
1651        }
1652    }
1653}
1654"#;
1655
1656        // Use utility function to compile and analyze
1657        let (_sources, analysis) = compile_and_analyze(source);
1658
1659        // Assert that we have one while step, and one statement step
1660        assert!(count_step_by_variant!(analysis, WhileLoop()) == 1);
1661        assert!(count_step_by_variant!(analysis, Statement()) == 1);
1662    }
1663
1664    #[test]
1665    fn test_try_step() {
1666        // Create a simple contract with a function to test try statement extraction
1667        let source = r#"
1668contract TestContract {
1669    function getValue() public view returns (uint256) {
1670        try this.getValue() {
1671            revert();
1672        } catch {
1673            return 1;
1674        }
1675    }
1676}
1677"#;
1678
1679        // Use utility function to compile and analyze
1680        let (_sources, analysis) = compile_and_analyze(source);
1681
1682        // Assert that we have one try step, and two statement steps
1683        assert!(count_step_by_variant!(analysis, Try()) == 1);
1684        assert!(count_step_by_variant!(analysis, Statement()) == 2);
1685    }
1686
1687    #[test]
1688    fn test_if_statement_body() {
1689        // Create a simple contract with a function to test if statement extraction
1690        let source = r#"
1691contract TestContract {
1692    function getValue() public view returns (uint256) {
1693        if (true) revert();
1694        return 0;
1695    }
1696}
1697"#;
1698
1699        // Use utility function to compile and analyze
1700        let (_sources, analysis) = compile_and_analyze(source);
1701        analysis.pretty_display(&_sources);
1702
1703        // Assert that we have one if step, and one statement step
1704        assert!(count_step_by_variant!(analysis, IfCondition()) == 1);
1705        assert!(count_step_by_variant!(analysis, Statement()) == 2);
1706    }
1707
1708    #[test]
1709    fn test_type_conversion_is_not_function_call() {
1710        let source = r#"
1711interface ITestContract {
1712    function getValue() external view returns (uint256);
1713}
1714
1715contract TestContract {
1716    struct S {
1717        uint256 value;
1718    }
1719    function getValue() public view returns (uint256) {
1720        ITestContract I = ITestContract(msg.sender);
1721        S memory s = S({ value: 1 });
1722        getValue();
1723        this.getValue();
1724        return uint256(1);
1725    }
1726}
1727"#;
1728
1729        // Use utility function to compile and analyze
1730        let (_sources, analysis) = compile_and_analyze(source);
1731
1732        // Assert that we have one function call step
1733        let mut function_calls = 0;
1734        analysis.steps.iter().for_each(|step| {
1735            function_calls += step.read().function_calls.len();
1736        });
1737        assert_eq!(function_calls, 2);
1738    }
1739
1740    #[test]
1741    fn test_steps_in_modifier() {
1742        let source = r#"
1743contract TestContract {
1744    modifier test() {
1745        uint x = 1;
1746        _;
1747        uint y = 2;
1748    }
1749}
1750"#;
1751        let (_sources, analysis) = compile_and_analyze(source);
1752
1753        // Assert that we have one modifier step, and two statement steps
1754        assert!(count_step_by_variant!(analysis, ModifierEntry()) == 1);
1755        assert!(count_step_by_variant!(analysis, Statement()) == 2);
1756    }
1757
1758    #[test]
1759    fn test_type_casting_multi_files() {
1760        let interface_file0 = "interface.sol";
1761        let source0 = r#"
1762interface ITestContract {
1763    function getValue() external view returns (uint256);
1764}
1765"#;
1766        let source1 = r#"
1767import { ITestContract } from "interface.sol";
1768
1769contract TestContract {
1770    function foo() public {
1771        ITestContract I = ITestContract(msg.sender);
1772    }
1773}
1774"#;
1775        let version = Version::parse("0.8.19").unwrap();
1776        let sources = Sources::from_iter([
1777            (PathBuf::from(interface_file0), Source::new(source0)),
1778            (PathBuf::from(TEST_CONTRACT_SOURCE_PATH), Source::new(source1)),
1779        ]);
1780        let settings = SolcSettings::default();
1781        let solc_input =
1782            SolcVersionedInput::build(sources, settings, SolcLanguage::Solidity, version);
1783        let compiler = SolcCompiler::AutoDetect;
1784        let output = compiler.compile(&solc_input).unwrap();
1785
1786        // return error if compiler error
1787        let errors = output
1788            .errors
1789            .iter()
1790            .filter(|e| e.severity() == Severity::Error)
1791            .map(|e| format!("{e}"))
1792            .collect::<Vec<_>>();
1793        if !errors.is_empty() {
1794            panic!("Compiler error: {}", errors.join("\n"));
1795        }
1796
1797        let mut ast = output
1798            .sources
1799            .get(&PathBuf::from(TEST_CONTRACT_SOURCE_PATH))
1800            .unwrap()
1801            .ast
1802            .clone()
1803            .unwrap();
1804        let source_unit = ASTPruner::convert(&mut ast, false).unwrap();
1805
1806        let analyzer = Analyzer::new(TEST_CONTRACT_SOURCE_ID);
1807        let analysis = analyzer
1808            .analyze(
1809                TEST_CONTRACT_SOURCE_ID,
1810                &PathBuf::from(TEST_CONTRACT_SOURCE_PATH),
1811                &source_unit,
1812            )
1813            .unwrap();
1814
1815        let mut function_calls = 0;
1816        for step in analysis.steps {
1817            function_calls += step.read().function_calls.len();
1818        }
1819        assert_eq!(function_calls, 0);
1820    }
1821
1822    #[test]
1823    fn test_statement_semicolon() {
1824        let source = r#"
1825contract TestContract {
1826    function foo() public returns (uint) {
1827        require(false, "error");
1828        revert();
1829        uint x = 1;
1830        x = 2;
1831        x + 1;
1832        return 1;
1833    }
1834}
1835
1836"#;
1837        let (_sources, analysis) = compile_and_analyze(source);
1838        let source = _sources.get(&TEST_CONTRACT_SOURCE_ID).unwrap().content.as_str();
1839        for step in &analysis.steps {
1840            let s = source_string_at_location_unchecked(source, &step.read().src);
1841            println!("step: {s}");
1842        }
1843    }
1844
1845    #[test]
1846    fn test_function_type_collection() {
1847        let source = r#"
1848        contract TestContract {
1849            // Function type as state variable
1850            function(uint256) returns (bool) private callback;
1851
1852            // Function type in array
1853            function(uint256) external pure returns (bool)[] private validators;
1854
1855            // Function type in mapping
1856            mapping(address => function(uint256) returns (bool)) private userCallbacks;
1857
1858            // Function with function type parameters (internal function)
1859            function setCallback(function(uint256) returns (bool) _callback) internal {
1860                callback = _callback;
1861            }
1862
1863            // Modifier with function type parameter
1864            modifier onlyValidated(function(uint256) returns (bool) _validator) {
1865                require(_validator(123), "Not validated");
1866                _;
1867            }
1868        }
1869        "#;
1870
1871        let (_sources, analysis) = compile_and_analyze(source);
1872
1873        // Should collect function types from:
1874        // 1. State variable 'callback'
1875        // 2. Array element type in 'validators'
1876        // 3. Mapping value type in 'userCallbacks'
1877        // 4. Function parameter in 'setCallback'
1878        // 5. Modifier parameter in 'onlyValidated'
1879        // Total: at least 5 function types
1880        assert!(
1881            analysis.function_types.len() >= 5,
1882            "Expected at least 5 function types, found: {}",
1883            analysis.function_types.len()
1884        );
1885
1886        println!("Collected {} function types", analysis.function_types.len());
1887
1888        // Print each function type for inspection
1889        for (i, func_type) in analysis.function_types.iter().enumerate() {
1890            println!(
1891                "Function type {}: visibility={:?}, stateMutability={:?}",
1892                i + 1,
1893                func_type.visibility(),
1894                func_type.state_mutability()
1895            );
1896        }
1897    }
1898
1899    #[test]
1900    fn test_variable_assignment() {
1901        let source = r#"
1902        contract TestContract {
1903            struct S {
1904                uint256 a;
1905                uint[] b;
1906                mapping(address => uint256) c;
1907            }
1908            S[] internal s;
1909            function foo(bool b) public {
1910                uint256 x = 1;
1911                x = 2;
1912
1913                s[x].c[msg.sender] = 3;
1914                s[x].b[0] = 4;
1915                s[x].a = x;
1916            }
1917        }
1918        "#;
1919        let (_sources, analysis) = compile_and_analyze(source);
1920        assert_eq!(count_updated_variables!(analysis), 4);
1921    }
1922
1923    #[test]
1924    fn test_variable_declaration_is_updated() {
1925        let source = r#"
1926        contract TestContract {
1927            function foo() public {
1928                uint256 x = 1;
1929            }
1930        }
1931        "#;
1932        let (_sources, analysis) = compile_and_analyze(source);
1933        assert_eq!(count_updated_variables!(analysis), 1);
1934    }
1935
1936    #[test]
1937    fn test_variable_accessible() {
1938        let source = r#"
1939        contract TestContract {
1940            uint256[] internal s;
1941            function foo(bool b) public {
1942                uint256 x = 1;
1943                x = 2;
1944
1945                if (b) {
1946                    uint y = x;
1947                    x = 3;
1948                }
1949
1950                uint z = s[x];
1951            }
1952        }
1953        "#;
1954        let (_sources, analysis) = compile_and_analyze(source);
1955
1956        // no step should have `z` in its accessible variables
1957        for step in &analysis.steps {
1958            assert!(!step
1959                .read()
1960                .accessible_variables
1961                .iter()
1962                .any(|v| v.read().declaration().name == "z"));
1963        }
1964    }
1965
1966    #[test]
1967    fn test_contract_collection() {
1968        let source = r#"
1969        function foo() {
1970        }
1971        contract TestContract {
1972            function bar() public {
1973            }
1974        }
1975        "#;
1976        let (_sources, analysis) = compile_and_analyze(source);
1977
1978        let foo_func = analysis
1979            .functions
1980            .iter()
1981            .find(|c| c.read().definition.name() == "foo")
1982            .expect("foo function should be found");
1983        let bar_func = analysis
1984            .functions
1985            .iter()
1986            .find(|c| c.read().definition.name() == "bar")
1987            .expect("bar function should be found");
1988        assert!(foo_func.read().contract.is_none());
1989        assert!(bar_func.read().contract.as_ref().is_some_and(|c| c.name() == "TestContract"));
1990    }
1991
1992    #[test]
1993    fn test_solidity_version() {
1994        let source = r#"
1995        pragma solidity ^0.8.0;
1996        pragma solidity ^0.8.1;
1997        pragma solidity >=0.7 .1 0;
1998        contract C {}
1999        "#;
2000        let (_sources, analysis) = compile_and_analyze(source);
2001        assert_eq!(
2002            analysis.version_req,
2003            Some(VersionReq::parse("^0.8.0,^0.8.1,>=0.7.1,0").unwrap())
2004        );
2005
2006        let source = r#"
2007        contract C {}
2008        "#;
2009        let (_sources, analysis) = compile_and_analyze(source);
2010        assert_eq!(analysis.version_req, None);
2011    }
2012
2013    #[test]
2014    fn test_dyn_abi_using_variable_declaration() {
2015        let source = r#"
2016        contract C {
2017            function f() public {
2018                address[] memory b = new address[](0);
2019            }
2020        }
2021        "#;
2022        let (_sources, analysis) = compile_and_analyze(source);
2023        let variables = analysis
2024            .variable_table()
2025            .values()
2026            .map(|v| v.base().declaration().clone())
2027            .collect::<Vec<_>>();
2028        let variable = variables.first().unwrap();
2029        assert_eq!(variable.type_descriptions.type_string.as_ref().unwrap(), "address[]");
2030    }
2031}