use crate::{
CompilerState,
cei_analysis::effect_summary::{AutomatonState, CeiCategory, EffectSummary, classify_intrinsic, peel_assign_root},
errors::cei_analyzer,
};
use leo_ast::*;
use leo_errors::Formatted;
use leo_span::{Span, Symbol};
use indexmap::IndexMap;
pub struct FinalizeCeiVisitor<'a> {
pub state: &'a mut CompilerState,
pub current_program: Symbol,
pub effect_summaries: IndexMap<Location, EffectSummary>,
pub automaton_state: AutomatonState,
pub interaction_span: Option<Span>,
pub current_variant: Option<Variant>,
pub in_finalize: bool,
}
impl FinalizeCeiVisitor<'_> {
pub fn emit_warning(&self, warning: Formatted) {
self.state.handler.emit_warning(warning);
}
fn is_storage_variable(&self, path: &Path) -> bool {
crate::cei_analysis::effect_summary::is_storage_variable(&self.state.symbol_table, self.current_program, path)
}
fn transition_to_after_interaction(&mut self, span: Span) {
self.automaton_state = AutomatonState::AfterInteraction;
self.interaction_span = Some(span);
}
fn visit_assign_lhs_reads(&mut self, expr: &Expression) {
match expr {
Expression::Path(_) => {}
Expression::MemberAccess(access) => self.visit_assign_lhs_reads(&access.inner),
Expression::TupleAccess(access) => self.visit_assign_lhs_reads(&access.tuple),
Expression::ArrayAccess(access) => {
self.visit_assign_lhs_reads(&access.array);
self.visit_expression(&access.index, &Default::default());
}
_ => {}
}
}
fn warn_if_after_interaction(&self, category: CeiCategory, description: &str, span: Span) {
if self.automaton_state == AutomatonState::AfterInteraction {
match category {
CeiCategory::Check => {
self.emit_warning(cei_analyzer::check_after_interaction(description, span));
}
CeiCategory::Effect => {
self.emit_warning(cei_analyzer::effect_after_interaction(description, span));
}
CeiCategory::Interaction => {} }
}
}
}
impl AstVisitor for FinalizeCeiVisitor<'_> {
type AdditionalInput = ();
type Output = ();
fn visit_intrinsic(&mut self, input: &IntrinsicExpression, _additional: &Self::AdditionalInput) -> Self::Output {
for arg in &input.arguments {
self.visit_expression(arg, &Default::default());
}
if self.in_finalize
&& let Some(intrinsic) = Intrinsic::from_symbol(input.name, &input.type_parameters)
&& let Some(category) = classify_intrinsic(&intrinsic)
{
match category {
CeiCategory::Interaction => {
self.transition_to_after_interaction(input.span());
}
CeiCategory::Check | CeiCategory::Effect => {
self.warn_if_after_interaction(category, &format!("a call to `{}`", input.name), input.span());
}
}
}
}
fn visit_call(&mut self, input: &CallExpression, _additional: &Self::AdditionalInput) -> Self::Output {
for arg in &input.arguments {
self.visit_expression(arg, &Default::default());
}
if self.in_finalize
&& let Some(loc) = input.function.try_global_location()
&& let Some(callee_summary) = self.effect_summaries.get(loc)
{
if self.automaton_state == AutomatonState::AfterInteraction
&& (callee_summary.has_checks || callee_summary.has_effects)
{
self.emit_warning(cei_analyzer::callee_has_effects_after_interaction(&input.function, input.span()));
}
if callee_summary.has_interactions {
self.transition_to_after_interaction(input.span());
}
}
}
fn visit_dynamic_op(&mut self, input: &DynamicOpExpression, _additional: &Self::AdditionalInput) -> Self::Output {
self.visit_expression(&input.target_program, &Default::default());
if let Some(ref network) = input.network {
self.visit_expression(network, &Default::default());
}
match &input.kind {
DynamicOpKind::Call { arguments, .. } => {
for arg in arguments {
self.visit_expression(arg, &Default::default());
}
}
DynamicOpKind::Read { storage } => {
if self.in_finalize {
self.warn_if_after_interaction(
CeiCategory::Check,
&format!("a read of dynamic storage `{storage}`"),
input.span(),
);
}
}
DynamicOpKind::Op { op, arguments, .. } => {
for arg in arguments {
self.visit_expression(arg, &Default::default());
}
if self.in_finalize {
self.warn_if_after_interaction(
CeiCategory::Check,
&format!("a `{op}` call on dynamic storage"),
input.span(),
);
}
}
}
}
fn visit_assert(&mut self, input: &AssertStatement) {
match &input.variant {
AssertVariant::Assert(expr) => {
self.visit_expression(expr, &Default::default());
}
AssertVariant::AssertEq(left, right) | AssertVariant::AssertNeq(left, right) => {
self.visit_expression(left, &Default::default());
self.visit_expression(right, &Default::default());
}
}
if self.in_finalize {
self.warn_if_after_interaction(CeiCategory::Check, "an `assert`", input.span);
}
}
fn visit_assign(&mut self, input: &AssignStatement) {
self.visit_assign_lhs_reads(&input.place);
self.visit_expression(&input.value, &Default::default());
if self.in_finalize {
if let Some(root) = peel_assign_root(&input.place)
&& self.is_storage_variable(root)
{
self.warn_if_after_interaction(CeiCategory::Effect, "a storage variable write", input.span);
}
}
}
fn visit_path(&mut self, input: &Path, _additional: &Self::AdditionalInput) -> Self::Output {
if self.in_finalize && self.is_storage_variable(input) {
self.warn_if_after_interaction(CeiCategory::Check, "a storage variable read", input.span());
}
}
fn visit_conditional(&mut self, input: &ConditionalStatement) {
self.visit_expression(&input.condition, &Default::default());
let saved_state = self.automaton_state;
let saved_span = self.interaction_span;
self.visit_block(&input.then);
let then_state = self.automaton_state;
let then_span = self.interaction_span;
self.automaton_state = saved_state;
self.interaction_span = saved_span;
if let Some(otherwise) = &input.otherwise {
match &**otherwise {
Statement::Block(block) => self.visit_block(block),
Statement::Conditional(cond) => self.visit_conditional(cond),
_ => unreachable!("Else-case can only be a block or conditional statement."),
}
}
let else_state = self.automaton_state;
if then_state == AutomatonState::AfterInteraction || else_state == AutomatonState::AfterInteraction {
self.automaton_state = AutomatonState::AfterInteraction;
self.interaction_span = then_span.or(self.interaction_span);
}
}
fn visit_iteration(&mut self, input: &IterationStatement) {
self.visit_expression(&input.start, &Default::default());
self.visit_expression(&input.stop, &Default::default());
let mut loop_summary = EffectSummary::default();
let ctx = crate::cei_analysis::effect_summary::SummaryContext {
symbol_table: &self.state.symbol_table,
current_program: self.current_program,
};
crate::cei_analysis::effect_summary::collect_block_effects_with_summaries(
&input.block,
&self.effect_summaries,
&mut loop_summary,
&ctx,
);
if loop_summary.has_interactions && (loop_summary.has_checks || loop_summary.has_effects) {
self.emit_warning(cei_analyzer::cei_violation_in_loop(input.variable.span()));
}
self.visit_block(&input.block);
if loop_summary.has_interactions {
self.transition_to_after_interaction(input.span);
}
}
fn visit_async(&mut self, input: &AsyncExpression, _additional: &Self::AdditionalInput) -> Self::Output {
let saved_automaton = self.automaton_state;
let saved_span = self.interaction_span;
let saved_in_finalize = self.in_finalize;
self.automaton_state = AutomatonState::BeforeInteraction;
self.interaction_span = None;
self.in_finalize = true;
self.visit_block(&input.block);
self.automaton_state = saved_automaton;
self.interaction_span = saved_span;
self.in_finalize = saved_in_finalize;
}
}
impl UnitVisitor for FinalizeCeiVisitor<'_> {
fn visit_program_scope(&mut self, input: &ProgramScope) {
self.current_program = input.program_id.as_symbol();
input.functions.iter().for_each(|(_, c)| self.visit_function(c));
}
fn visit_function(&mut self, input: &Function) {
self.current_variant = Some(input.variant);
if input.variant.is_finalize_context() {
self.automaton_state = AutomatonState::BeforeInteraction;
self.interaction_span = None;
self.in_finalize = true;
self.visit_block(&input.block);
self.in_finalize = false;
} else {
self.in_finalize = false;
self.visit_block(&input.block);
}
}
}