leo_passes/static_analysis/
program.rs

1// Copyright (C) 2019-2025 Provable Inc.
2// This file is part of the Leo library.
3
4// The Leo library is free software: you can redistribute it and/or modify
5// it under the terms of the GNU 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// The Leo library 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 General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
16
17use super::StaticAnalyzingVisitor;
18
19use leo_ast::{Type, *};
20use leo_errors::{StaticAnalyzerError, StaticAnalyzerWarning};
21
22impl ProgramVisitor for StaticAnalyzingVisitor<'_> {
23    fn visit_program_scope(&mut self, input: &ProgramScope) {
24        // Set the current program name.
25        self.current_program = input.program_id.name.name;
26        // Do the default implementation for visiting the program scope.
27        input.consts.iter().for_each(|(_, c)| (self.visit_const(c)));
28        input.structs.iter().for_each(|(_, c)| (self.visit_struct(c)));
29        input.mappings.iter().for_each(|(_, c)| (self.visit_mapping(c)));
30        input.functions.iter().for_each(|(_, c)| (self.visit_function(c)));
31        if let Some(c) = input.constructor.as_ref() {
32            self.visit_constructor(c);
33        }
34    }
35
36    fn visit_function(&mut self, function: &Function) {
37        function.const_parameters.iter().for_each(|input| self.visit_type(&input.type_));
38        function.input.iter().for_each(|input| self.visit_type(&input.type_));
39        function.output.iter().for_each(|output| self.visit_type(&output.type_));
40        self.visit_type(&function.output_type);
41
42        // Set the function name and variant.
43        self.variant = Some(function.variant);
44
45        // Set `non_async_external_call_seen` to false.
46        self.non_async_external_call_seen = false;
47
48        if matches!(self.variant, Some(Variant::AsyncFunction) | Some(Variant::AsyncTransition)) {
49            super::future_checker::future_check_function(function, &self.state.type_table, &self.state.handler);
50        }
51
52        // If the function is an async function, initialize the await checker.
53        if self.variant == Some(Variant::AsyncFunction) {
54            // Initialize the list of input futures. Each one must be awaited before the end of the function.
55            self.await_checker.set_futures(
56                function
57                    .input
58                    .iter()
59                    .filter_map(|input| {
60                        if let Type::Future(_) = input.type_.clone() { Some(input.identifier.name) } else { None }
61                    })
62                    .collect(),
63            );
64        }
65
66        self.visit_block(&function.block);
67
68        // Check that all futures were awaited exactly once.
69        if self.variant == Some(Variant::AsyncFunction) {
70            // Throw error if not all futures awaits even appear once.
71            if !self.await_checker.static_to_await.is_empty() {
72                self.emit_err(StaticAnalyzerError::future_awaits_missing(
73                    self.await_checker
74                        .static_to_await
75                        .clone()
76                        .iter()
77                        .map(|f| f.to_string())
78                        .collect::<Vec<String>>()
79                        .join(", "),
80                    function.span(),
81                ));
82            } else if !self.await_checker.to_await.is_empty() {
83                // Tally up number of paths that are unawaited and number of paths that are awaited more than once.
84                let (num_paths_unawaited, num_paths_duplicate_awaited, num_perfect) =
85                    self.await_checker.to_await.iter().fold((0, 0, 0), |(unawaited, duplicate, perfect), path| {
86                        (
87                            unawaited + if !path.elements.is_empty() { 1 } else { 0 },
88                            duplicate + if path.counter > 0 { 1 } else { 0 },
89                            perfect + if path.counter > 0 || !path.elements.is_empty() { 0 } else { 1 },
90                        )
91                    });
92
93                // Throw error if there does not exist a path in which all futures are awaited exactly once.
94                if num_perfect == 0 {
95                    self.emit_err(StaticAnalyzerError::no_path_awaits_all_futures_exactly_once(
96                        self.await_checker.to_await.len(),
97                        function.span(),
98                    ));
99                }
100
101                // Throw warning if not all futures are awaited in some paths.
102                if num_paths_unawaited > 0 {
103                    self.emit_warning(StaticAnalyzerWarning::some_paths_do_not_await_all_futures(
104                        self.await_checker.to_await.len(),
105                        num_paths_unawaited,
106                        function.span(),
107                    ));
108                }
109
110                // Throw warning if some futures are awaited more than once in some paths.
111                if num_paths_duplicate_awaited > 0 {
112                    self.emit_warning(StaticAnalyzerWarning::some_paths_contain_duplicate_future_awaits(
113                        self.await_checker.to_await.len(),
114                        num_paths_duplicate_awaited,
115                        function.span(),
116                    ));
117                }
118            }
119        }
120    }
121
122    fn visit_constructor(&mut self, _: &Constructor) {
123        // Do nothing, since constructors do not have awaits or futures.
124    }
125}