1use 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 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#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct SourceAnalysis {
65 pub id: u32,
67 pub path: PathBuf,
69 pub unit: SourceUnit,
71 pub version_req: Option<VersionReq>,
73 pub global_scope: VariableScopeRef,
75 pub steps: Vec<StepRef>,
77 pub private_state_variables: Vec<VariableRef>,
79 pub contracts: Vec<ContractRef>,
81 pub functions: Vec<FunctionRef>,
83 pub state_variables: Vec<VariableRef>,
85 pub private_functions: Vec<FunctionRef>,
87 pub immutable_functions: Vec<FunctionRef>,
89 pub function_types: Vec<FunctionTypeNameRef>,
91 pub user_defined_types: Vec<UserDefinedTypeRef>,
93}
94
95impl SourceAnalysis {
96 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 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 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 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 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 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 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 println!("=== Variable Scopes ===");
236 println!("{}", self.global_scope.pretty_display());
237 println!();
238
239 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 if let Some(source_code) = self.extract_source_code(sources, &step.read().src) {
252 println!(" Source: {}", source_code.trim());
253 }
254
255 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 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 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 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 self.display_recommendations();
298 println!("=== End Report ===");
299 }
300
301 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 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 fn format_function_calls(&self, calls: &[FunctionCall]) -> String {
340 calls
341 .iter()
342 .map(|call| {
343 let args = call.arguments.len();
344 format!("function_call({args} args)")
347 })
348 .collect::<Vec<_>>()
349 .join(", ")
350 }
351
352 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 fn format_updated_variables(&self, variables: &[VariableRef]) -> String {
366 variables.iter().map(|var| var.read().pretty_display()).collect::<Vec<_>>().join(", ")
367 }
368
369 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 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#[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 contracts: Vec<ContractRef>,
469 functions: Vec<FunctionRef>,
471 variables: HashMap<usize, VariableRef>,
473 state_variables: Vec<VariableRef>,
475 private_state_variables: Vec<VariableRef>,
477 private_functions: Vec<FunctionRef>,
479 immutable_functions: Vec<FunctionRef>,
481 function_types: Vec<FunctionTypeNameRef>,
483 user_defined_types: Vec<UserDefinedTypeRef>,
485}
486
487impl Analyzer {
488 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 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
576impl 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 return Ok(());
598 }
599 if declaration.mutability == Some(Mutability::Immutable)
600 || declaration.mutability == Some(Mutability::Constant)
601 || declaration.constant
602 {
603 return Ok(());
605 }
606
607 self.collect_function_types_from_variable(declaration)?;
609
610 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 self.variables.insert(declaration.id, variable.clone());
632
633 if let Some(step) = self.current_step.as_mut() {
634 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 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 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 fn collect_function_types_recursive(&mut self, type_name: &TypeName) {
682 match type_name {
683 TypeName::FunctionTypeName(function_type) => {
684 self.function_types.push((*function_type.clone()).into());
686 }
687 TypeName::ArrayTypeName(array_type) => {
688 self.collect_function_types_recursive(&array_type.base_type);
690 }
691 TypeName::Mapping(mapping) => {
692 self.collect_function_types_recursive(&mapping.key_type);
694 self.collect_function_types_recursive(&mapping.value_type);
695 }
696 TypeName::ElementaryTypeName(_) | TypeName::UserDefinedTypeName(_) => {
697 }
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 if declaration.type_name.as_ref().map(contains_user_defined_type).unwrap_or(false) {
711 return Ok(());
712 }
713
714 if declaration.visibility != Visibility::Public {
716 self.private_state_variables.push(variable.clone());
717 }
718 }
719 Ok(())
720 }
721}
722
723impl 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
740impl 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
795impl 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 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 let loc = sloc_rdiff(do_while_statement.src, do_while_statement.body.src);
830 step!(DoWhileLoop, *do_while_statement.clone(), loc);
831
832 let mut single_step_walker = AnalyzerSingleStepWalker { analyzer: self };
834 do_while_statement.condition.walk(&mut single_step_walker)?;
835
836 self.exit_current_statement_step(statement)?;
838 do_while_statement.body.walk(self)?;
839
840 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 let loc = sloc_ldiff(for_statement.src, block_or_stmt_src(&for_statement.body));
848 step!(ForLoop, *for_statement.clone(), loc);
849
850 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 self.exit_current_statement_step(statement)?;
864 for_statement.body.walk(self)?;
865
866 return Ok(VisitorAction::SkipSubtree);
868 }
869 Statement::IfStatement(if_statement) => {
870 let loc = sloc_ldiff(if_statement.src, block_or_stmt_src(&if_statement.true_body));
872 step!(IfCondition, *if_statement.clone(), loc);
873
874 let mut single_step_walker = AnalyzerSingleStepWalker { analyzer: self };
876 if_statement.condition.walk(&mut single_step_walker)?;
877
878 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 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 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 let mut single_step_walker = AnalyzerSingleStepWalker { analyzer: self };
900 try_statement.external_call.walk(&mut single_step_walker)?;
901
902 self.exit_current_statement_step(statement)?;
904 for clause in &try_statement.clauses {
905 clause.block.walk(self)?;
906 }
907
908 return Ok(VisitorAction::SkipSubtree);
910 }
911 Statement::UncheckedBlock(_) => { }
912 Statement::VariableDeclarationStatement(variable_declaration_statement) => {
913 simple_stmt_to_step!(variable_declaration_statement)
914 }
915 Statement::WhileStatement(while_statement) => {
916 let loc = sloc_ldiff(while_statement.src, block_or_stmt_src(&while_statement.body));
918 step!(WhileLoop, *while_statement.clone(), loc);
919
920 let mut single_step_walker = AnalyzerSingleStepWalker { analyzer: self };
922 while_statement.condition.walk(&mut single_step_walker)?;
923
924 self.exit_current_statement_step(statement)?;
926 while_statement.body.walk(self)?;
927
928 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 return Ok(VisitorAction::SkipSubtree);
945 }
946
947 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 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 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 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 return Ok(VisitorAction::SkipSubtree);
988 }
989
990 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 let mut single_step_walker = AnalyzerSingleStepWalker { analyzer: self };
1007 modifier.parameters.walk(&mut single_step_walker)?;
1008
1009 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 Ok(VisitorAction::SkipSubtree)
1018 }
1019
1020 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
1048impl 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 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 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
1146impl 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 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 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 self.record_contract_type(_definition)?;
1248
1249 self.enter_new_contract(_definition)?;
1251
1252 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 self.exit_current_scope(_definition.src)?;
1263
1264 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 self.enter_new_function(definition)?;
1313
1314 self.enter_new_scope(ScopeNode::FunctionDefinition(definition.clone()))?;
1316
1317 self.enter_new_function_step(definition)
1319 }
1320
1321 fn post_visit_function_definition(
1322 &mut self,
1323 definition: &FunctionDefinition,
1324 ) -> eyre::Result<()> {
1325 self.exit_current_scope(definition.src)?;
1327
1328 self.exit_current_function()?;
1330 Ok(())
1331 }
1332
1333 fn visit_modifier_definition(
1334 &mut self,
1335 definition: &ModifierDefinition,
1336 ) -> eyre::Result<VisitorAction> {
1337 self.enter_new_modifier(definition)?;
1339
1340 self.enter_new_scope(ScopeNode::ModifierDefinition(definition.clone()))?;
1342
1343 self.enter_new_modifier_step(definition)
1345 }
1346
1347 fn post_visit_modifier_definition(
1348 &mut self,
1349 definition: &ModifierDefinition,
1350 ) -> eyre::Result<()> {
1351 self.exit_current_scope(definition.src)?;
1353
1354 self.exit_current_modifier()?;
1356 Ok(())
1357 }
1358
1359 fn visit_block(&mut self, block: &Block) -> eyre::Result<VisitorAction> {
1360 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 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 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 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 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 self.exit_current_scope(for_statement.src)?;
1395 Ok(())
1396 }
1397
1398 fn visit_statement(&mut self, _statement: &Statement) -> eyre::Result<VisitorAction> {
1399 self.enter_new_statement_step(_statement)
1401 }
1402
1403 fn post_visit_statement(&mut self, _statement: &Statement) -> eyre::Result<()> {
1404 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 self.declare_variable(declaration)?;
1420 self.record_declared_varaible(declaration)?;
1422 Ok(VisitorAction::Continue)
1423 }
1424
1425 fn visit_assignment(&mut self, assignment: &Assignment) -> eyre::Result<VisitorAction> {
1426 self.record_assignment(assignment)
1428 }
1429}
1430
1431#[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#[derive(Debug, Error)]
1460pub enum AnalysisError {
1461 #[error("AST is not selected as compiler output")]
1463 MissingAst,
1464
1465 #[error("failed to convert AST: {0}")]
1467 ASTConversionError(eyre::Report),
1468
1469 #[error("failed to partition source steps: {0}")]
1471 StepPartitionError(eyre::Report),
1472
1473 #[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 pub(crate) fn compile_and_analyze(source: &str) -> (BTreeMap<u32, Source>, SourceAnalysis) {
1509 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 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 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 let (_sources, analysis) = compile_and_analyze(source);
1573
1574 assert!(count_step_by_variant!(analysis, FunctionEntry()) == 3);
1576 }
1577
1578 #[test]
1579 fn test_statement_step() {
1580 let source = r#"
1582contract TestContract {
1583 function getValue() public view returns (uint256) {
1584 uint256 value = 0;
1585 return 0;
1586 }
1587}
1588"#;
1589
1590 let (_sources, analysis) = compile_and_analyze(source);
1592 analysis.pretty_display(&_sources);
1593
1594 assert!(count_step_by_variant!(analysis, Statement()) == 2);
1596 }
1597
1598 #[test]
1599 fn test_if_step() {
1600 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 let (_sources, analysis) = compile_and_analyze(source);
1615
1616 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 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 let (_sources, analysis) = compile_and_analyze(source);
1636
1637 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 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 let (_sources, analysis) = compile_and_analyze(source);
1658
1659 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 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 let (_sources, analysis) = compile_and_analyze(source);
1681
1682 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 let source = r#"
1691contract TestContract {
1692 function getValue() public view returns (uint256) {
1693 if (true) revert();
1694 return 0;
1695 }
1696}
1697"#;
1698
1699 let (_sources, analysis) = compile_and_analyze(source);
1701 analysis.pretty_display(&_sources);
1702
1703 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 let (_sources, analysis) = compile_and_analyze(source);
1731
1732 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!(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 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 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 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 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}