This repository contains the task's implementation for the "Formal verification framework for Kotlin". The code is written using Kotlin 1.7.10, and it has only dependence (Dotlin) already embedded inside.
Write a function that takes such AST representation and transform it to a symbolic forward execution tree, with nodes containing the following values:
expr- next expression to executeS- symbolic store containingExpr.Letexpressions over variables, constants or symbolic values (see below)Pi- path constraints store, containing comparison expressions (Expr.Eq,Expr.NEq) that are encountered on the branch points for the current path
For more details, see the full description here.
The code is structured in three packages:
|- ast: contains the Abstract Syntax Tree definition for expressions, and an expression reducer
|- symbolic: contains the definition needed for the Excution Symbolic Tree
|- visualizer: contains classes for pretty-printing the Symbolic Tree, using Graphviz
The symbolic forward execution tree is mainly built by the ExecutionTreeBuilder.buildNode function, which works via
pattern matching on the given expression. To the first function call, four parameters are passed:
currentExpr: the expression to which to build the nodeexprDeque: a double ended queue for the remaining expressions to build nodesstore: aSymbolicStorerepresenting the current variable environment, updated for the next node each time aExpr.Letis matchedconstraints: aPathConstraintsrepresenting the current expression guards seen so far, updated for the next node each time aExpr.Ifis matched
Therefore, when currentExpr is matched we can have four cases:
- We're matching an
Expr.Block, if theexprDequeis not empty, we insert all the expressions contained inside the block to a new deque, and we continue the node construction with the new deque. - We're matching an
Expr.Let, we create a newSymbolicStorecontaining the new info given from the current expression, and we pass it to the new child node. It may happen, as shown inExecutionTreeBuilderTest::testBerkeleyandExecutionTreeBuilderTest::testWashington, that an existing symbolic value can be updated. Therefore, we update the current constraints to reflect this event, removing the old constrain related to the variable. - We're matching an
Expr.If, this is the case where the execution path split, therefore it has to be handled carefully. To summarize it, we create two new deques (one for thethenbranch, the other one forelse). These new deques, have a copy of the current expression's deque, used for building the children. Notice that, theExpr.If, as written before add a new constraint to the currentconstraintsdata structure. - We're matching the remaining expressions, and we just return a new
ExecutionTreeNode
Equal and NotEqual expressions have been grouped inside a new sealed class Expr.Comparison. Moreover, greater than (or equal) and less than (or equal) have been added to this class, with respective tests.
The code uses Dotlin to generate Graphviz output to be rendered, similar to the one inside Section 1.1 of "A Survey of Symbolic Execution Techniques". The code can be found inside the class GraphvizVisualizer.
Here are some SVG examples rendered by Graphviz Online.