leo_passes/static_analysis/
analyzer.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
// Copyright (C) 2019-2025 Provable Inc.
// This file is part of the Leo library.

// The Leo library is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// The Leo library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.

use crate::{SymbolTable, TypeTable, static_analysis::await_checker::AwaitChecker};

use leo_ast::*;
use leo_errors::{StaticAnalyzerError, StaticAnalyzerWarning, emitter::Handler};
use leo_span::{Span, Symbol};

use snarkvm::console::network::Network;

use std::marker::PhantomData;

pub struct StaticAnalyzer<'a, N: Network> {
    /// The symbol table for the program.
    pub(crate) symbol_table: &'a SymbolTable,
    /// The type table for the program.
    // Note that this pass does not use the type table in a meaningful way.
    // However, this may be useful in future static analysis passes.
    pub(crate) type_table: &'a TypeTable,
    /// The error handler.
    pub(crate) handler: &'a Handler,
    /// Struct to store the state relevant to checking all futures are awaited.
    pub(crate) await_checker: AwaitChecker,
    /// The current program name.
    pub(crate) current_program: Option<Symbol>,
    /// The variant of the function that we are currently traversing.
    pub(crate) variant: Option<Variant>,
    /// Whether or not a non-async external call has been seen in this function.
    pub(crate) non_async_external_call_seen: bool,
    // Allows the type checker to be generic over the network.
    phantom: PhantomData<N>,
}

impl<'a, N: Network> StaticAnalyzer<'a, N> {
    /// Returns a new static analyzer given a symbol table and error handler.
    pub fn new(
        symbol_table: &'a SymbolTable,
        _type_table: &'a TypeTable,
        handler: &'a Handler,
        max_depth: usize,
        disabled: bool,
    ) -> Self {
        Self {
            symbol_table,
            type_table: _type_table,
            handler,
            await_checker: AwaitChecker::new(max_depth, !disabled),
            current_program: None,
            variant: None,
            non_async_external_call_seen: false,
            phantom: Default::default(),
        }
    }

    /// Emits a type checker error.
    pub(crate) fn emit_err(&self, err: StaticAnalyzerError) {
        self.handler.emit_err(err);
    }

    /// Emits a type checker warning
    pub fn emit_warning(&self, warning: StaticAnalyzerWarning) {
        self.handler.emit_warning(warning.into());
    }

    /// Type checks the awaiting of a future.
    pub(crate) fn assert_future_await(&mut self, future: &Option<&Expression>, span: Span) {
        // Make sure that it is an identifier expression.
        let future_variable = match future {
            Some(Expression::Identifier(name)) => name,
            _ => {
                return self.emit_err(StaticAnalyzerError::invalid_await_call(span));
            }
        };

        // Make sure that the future is defined.
        match self.type_table.get(&future_variable.id) {
            Some(type_) => {
                if !matches!(type_, Type::Future(_)) {
                    self.emit_err(StaticAnalyzerError::expected_future(future_variable.name, future_variable.span()));
                }
                // Mark the future as consumed.
                // If the call returns true, it means that a future was not awaited in the order of the input list, emit a warning.
                if self.await_checker.remove(future_variable) {
                    self.emit_warning(StaticAnalyzerWarning::future_not_awaited_in_order(
                        future_variable.name,
                        future_variable.span(),
                    ));
                }
            }
            None => {
                self.emit_err(StaticAnalyzerError::expected_future(future_variable.name, future_variable.span()));
            }
        }
    }

    /// Assert that an async call is a "simple" one.
    /// Simple is defined as an async transition function which does not return a `Future` that itself takes a `Future` as an argument.
    pub(crate) fn assert_simple_async_transition_call(&self, program: Symbol, function_name: Symbol, span: Span) {
        let func_symbol = self
            .symbol_table
            .lookup_function(Location::new(program, function_name))
            .expect("Type checking guarantees functions are present.");

        // If it is not an async transition, return.
        if func_symbol.function.variant != Variant::AsyncTransition {
            return;
        }

        let finalizer = func_symbol
            .finalizer
            .as_ref()
            .expect("Typechecking guarantees that all async transitions have an associated `finalize` field.");

        let async_function = self
            .symbol_table
            .lookup_function(finalizer.location)
            .expect("Type checking guarantees functions are present.");

        // If the async function takes a future as an argument, emit an error.
        if async_function.function.input.iter().any(|input| matches!(input.type_(), Type::Future(..))) {
            self.emit_err(StaticAnalyzerError::async_transition_call_with_future_argument(function_name, span));
        }
    }
}