Implement check for cyclic call graph

This commit is contained in:
d0cd 2022-11-16 00:57:27 -08:00
parent b17702e019
commit f06b83c7f4
5 changed files with 31 additions and 4 deletions

View File

@ -481,6 +481,13 @@ impl<'a> ExpressionVisitor<'a> for TypeChecker<'a> {
self.visit_expression(argument, &Some(expected.type_()));
});
// Add the call to the call graph.
let caller_name = match self.function {
None => unreachable!("`self.function` is set every time a function is visited."),
Some(func) => func,
};
self.call_graph.add_edge(caller_name, ident.name);
Some(ret)
} else {
self.emit_err(TypeCheckerError::unknown_sym("function", ident.name, ident.span()));

View File

@ -75,6 +75,11 @@ impl<'a> ProgramVisitor<'a> for TypeChecker<'a> {
}
}
// Check that the call graph does not have any cycles.
if let Err(GraphError::CycleDetected(path)) = self.call_graph.post_order() {
self.emit_err(TypeCheckerError::cyclic_function_dependency(path));
}
// TODO: Use the snarkVM configurations to parameterize the check, need similar checks for structs (all in separate PR)
// Check that the number of transitions does not exceed the maximum.
if transition_count > 15 {

View File

@ -14,7 +14,7 @@
// 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::{StructGraph, SymbolTable};
use crate::{CallGraph, StructGraph, SymbolTable};
use leo_ast::{Identifier, IntegerType, Node, Type};
use leo_core::*;
@ -29,6 +29,8 @@ pub struct TypeChecker<'a> {
pub(crate) symbol_table: RefCell<SymbolTable>,
/// A dependency graph of the structs in program.
pub(crate) struct_graph: StructGraph,
/// The call graph for the program.
pub(crate) call_graph: CallGraph,
/// The error handler.
pub(crate) handler: &'a Handler,
/// The name of the function that we are currently traversing.
@ -95,9 +97,12 @@ impl<'a> TypeChecker<'a> {
pub fn new(symbol_table: SymbolTable, handler: &'a Handler) -> Self {
let struct_names = symbol_table.structs.keys().cloned().collect();
let function_names = symbol_table.functions.keys().cloned().collect();
Self {
symbol_table: RefCell::new(symbol_table),
struct_graph: StructGraph::new(struct_names),
call_graph: CallGraph::new(function_names),
handler,
function: None,
has_return: false,

View File

@ -27,20 +27,20 @@ pub use check_statements::*;
pub mod checker;
pub use checker::*;
use crate::{Pass, StructGraph, SymbolTable};
use crate::{CallGraph, Pass, StructGraph, SymbolTable};
use leo_ast::{Ast, ProgramVisitor};
use leo_errors::{emitter::Handler, Result};
impl<'a> Pass for TypeChecker<'a> {
type Input = (&'a Ast, &'a Handler, SymbolTable);
type Output = Result<(SymbolTable, StructGraph)>;
type Output = Result<(SymbolTable, StructGraph, CallGraph)>;
fn do_pass((ast, handler, st): Self::Input) -> Self::Output {
let mut visitor = TypeChecker::new(st, handler);
visitor.visit_program(ast.as_repr());
handler.last_err().map_err(|e| *e)?;
Ok((visitor.symbol_table.take(), visitor.struct_graph))
Ok((visitor.symbol_table.take(), visitor.struct_graph, visitor.call_graph))
}
}

View File

@ -549,4 +549,14 @@ create_messages!(
},
help: None,
}
@backtraced
cyclic_function_dependency {
args: (path: Vec<impl Display>),
msg: {
let path_string = path.into_iter().map(|name| format!("`{name}`")).collect::<Vec<String>>().join(" --> ");
format!("Cyclic dependency between functions: {path_string}")
},
help: None,
}
);