Skip to content

Commit

Permalink
detect recursive struct fields
Browse files Browse the repository at this point in the history
  • Loading branch information
SkymanOne committed Feb 24, 2024
1 parent a8578bd commit de0d0c4
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 17 deletions.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@ syn = "2.0"
synstructure = "0.13"
proc-macro2 = "1.0"
quote = "1.0"
indexmap = "2.2"
indexmap = "2.2"
petgraph = "0.6.4"
3 changes: 2 additions & 1 deletion crates/semantics/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ version.workspace = true
folidity-parser = { workspace = true }
folidity-diagnostics = { workspace = true }
derive-node = { workspace = true }
indexmap = { workspace = true }
indexmap = { workspace = true }
petgraph = { workspace = true }
57 changes: 56 additions & 1 deletion crates/semantics/src/ast.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,71 @@
use std::collections::HashSet;

use derive_node::Node;
use folidity_diagnostics::Report;
use folidity_parser::{
ast::{BinaryExpression, Identifier, MappingRelation, UnaryExpression},
Span,
};
use indexmap::IndexMap;

use crate::global_symbol::{GlobalSymbol, SymbolInfo};
use crate::{
contract::ContractDefinition,
global_symbol::{GlobalSymbol, SymbolInfo},
};

#[derive(Clone, Debug, PartialEq, Node)]
pub struct Type {
pub loc: Span,
pub ty: TypeVariant,
}

impl Type {
/// Is data type primitive.
pub fn is_primitive(&self) -> bool {
matches!(
&self.ty,
TypeVariant::Int
| TypeVariant::Uint
| TypeVariant::Float
| TypeVariant::Char
| TypeVariant::String
| TypeVariant::Hex
| TypeVariant::Address
| TypeVariant::Unit
| TypeVariant::Bool
)
}

/// Find the set of dependent user defined types that are encapsulated by this type.
pub fn custom_type_dependencies(&self, contract: &mut ContractDefinition) -> HashSet<usize> {
match &self.ty {
TypeVariant::Set(s) => s.ty.custom_type_dependencies(contract),
TypeVariant::List(s) => s.ty.custom_type_dependencies(contract),
TypeVariant::Mapping(m) => {
let mut set = m.from_ty.custom_type_dependencies(contract);
set.extend(m.to_ty.custom_type_dependencies(contract));
set
}
TypeVariant::Struct(s) => HashSet::from([s.i]),
TypeVariant::Model(s) => {
contract.diagnostics.push(Report::semantic_error(
s.loc.clone(),
String::from("Cannot use model as a type here,"),
));
HashSet::new()
}
TypeVariant::State(s) => {
contract.diagnostics.push(Report::semantic_error(
s.loc.clone(),
String::from("Cannot use state as a type here,"),
));
HashSet::new()
}
_ => HashSet::new(),
}
}
}

#[derive(Clone, Debug, PartialEq)]
pub enum TypeVariant {
Int,
Expand Down Expand Up @@ -77,6 +130,8 @@ pub struct Param {
pub name: Identifier,
/// Is param mutable.
pub is_mut: bool,
/// Is the field recursive.
pub recursive: bool,
}

/// View state modifier.
Expand Down
8 changes: 5 additions & 3 deletions crates/semantics/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ impl ContractDefinition {
let fields = self.analyze_fields(params, &state.decl.name);
Some(StateBody::Raw(fields))
}
// If the body is a model, then we need to resolve the model symbol in the symbol table
Some(parsed_ast::StateBody::Model(ident)) => {
let Some(symbol) = self.declaration_symbols.get(&ident.name) else {
self.diagnostics.push(Report::semantic_error(
Expand All @@ -99,6 +100,7 @@ impl ContractDefinition {
};
match symbol {
GlobalSymbol::Model(m) => Some(StateBody::Model(m.clone())),
// The symbol must be a model, otherwise the type is invalid.
_ => {
self.diagnostics.push(Report::semantic_error(
ident.loc.clone(),
Expand All @@ -112,10 +114,9 @@ impl ContractDefinition {
};

self.states[state.i].body = body;

// todo: check for cycles.
// todo: check for valid types.
}
// todo: check for cycles.
// todo: check for valid types.
}

/// Resolve fields of declarations.
Expand Down Expand Up @@ -160,6 +161,7 @@ impl ContractDefinition {
ty: param_type,
name: field.name.clone(),
is_mut: field.is_mut,
recursive: false,
};

analyzed_fields.push(param);
Expand Down
4 changes: 3 additions & 1 deletion crates/semantics/src/decls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ pub struct DelayedDeclaration<T> {
pub i: usize,
}

/// Delayed declarations for the second pass semantic analysis.
/// Saved declaration for later analysis.
/// The first pass should resolve the fields.
/// The second pass should resolve model bounds.
#[derive(Debug, Default)]
pub struct DelayedDeclarations {
pub structs: Vec<DelayedDeclaration<folidity_parser::ast::StructDeclaration>>,
Expand Down
84 changes: 74 additions & 10 deletions crates/semantics/src/types.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
use crate::ast::{List, Mapping, Set, Type, TypeVariant};
use std::collections::HashSet;

use crate::ast::{List, Mapping, Param, Set, Type, TypeVariant};
use crate::contract::ContractDefinition;
use crate::global_symbol::{GlobalSymbol, SymbolInfo};
use folidity_diagnostics::Report;
use folidity_parser::ast::Identifier;
use folidity_parser::{ast as parsed_ast, Span};
use petgraph::algo::{all_simple_paths, tarjan_scc};
use petgraph::graph::NodeIndex;
use petgraph::{Directed, Graph};

type FieldGraph = Graph<(), usize, Directed, usize>;

/// Maps type from parsed AST to semantically resolved type.
/// - Primitive types are simply mapped 1-1.
Expand Down Expand Up @@ -68,16 +75,73 @@ pub fn map_type(contract: &mut ContractDefinition, ty: &parsed_ast::Type) -> Res
/// # Outline
/// - Generate a dependency tree of user defined types.
/// - Check for cycles.
pub fn find_user_type_recursion(contract: &mut ContractDefinition, ty: &Type) -> Result<(), Span> {
match &ty.ty {
TypeVariant::Struct(s) => find_field_cycles(contract, s),
TypeVariant::Model(s) => find_field_cycles(contract, s),
TypeVariant::State(s) => find_field_cycles(contract, s),
_ => Ok(()),
/// # Note
/// Inspired by https://github.com/hyperledger/solang/blob/d7a875afe73f95e3c9d5112aa36c8f9eb91a6e00/src/sema/types.rs#L359.
///
/// Licensed as Apache 2.0
//todo: rewrite.
//TODO: support finite size recursive types.
pub fn find_user_type_recursion(contract: &mut ContractDefinition) -> Result<(), Span> {
let mut edges = HashSet::new();
for n in 0..contract.structs.len() {
collect_edges(
&mut edges,
&contract.structs[n].fields,
n,
&mut contract.clone(),
)
}

let graph: FieldGraph = Graph::from_edges(edges);
let tarjan = tarjan_scc(&graph);
let mut nodes = HashSet::new();
for node in tarjan.iter().flatten() {
nodes.insert(node);
}

for node in nodes {
check_for_recursive_types(node.index(), &graph, contract);
}

for n in 0..contract.structs.len() {
for field in contract.structs[n].fields.iter().filter(|f| f.recursive) {
contract.diagnostics.push(Report::semantic_error(
field.loc.clone(),
String::from("Recursive field detected."),
));
}
}
}

fn find_field_cycles(contract: &mut ContractDefinition, s: &SymbolInfo) -> Result<(), Span> {
// let fields = contract.
Ok(())
}

/// Collect field dependencies into the graph edges.
fn collect_edges(
edges: &mut HashSet<(usize, usize, usize)>,
fields: &[Param],
struct_no: usize,
contract: &mut ContractDefinition,
) {
for (no, field) in fields.iter().enumerate() {
for dependency in field.ty.custom_type_dependencies(contract) {
if edges.insert((no, dependency, struct_no)) {
collect_edges(edges, fields, struct_no, contract)
}
}
}
}

/// Check for recursive edges.
fn check_for_recursive_types(node: usize, graph: &FieldGraph, contract: &mut ContractDefinition) {
for n in 0..contract.structs.len() {
for simple_path in
all_simple_paths::<Vec<_>, &FieldGraph>(graph, n.into(), node.into(), 0, None)
{
for (a, b) in simple_path.windows(2).map(|pair| (pair[0], pair[1])) {
for edge in graph.edges_connecting(a, b) {
contract.structs[a.index()].fields[*edge.weight()].recursive = true;
}
}
}
}
}

0 comments on commit de0d0c4

Please sign in to comment.