Storing our formulae in a configuration file
As we saw before, we are always “playing” with some formulae and it can be boring to lug around with it everytime. Especially when we have more than one formula. Therefore, I decided to create a configuration file to keep a track on what we need to use, for now it only contains a list of formula, but later we can consider to add more informations (Model checking backend to use, Number of thread to use…).
Which file format are we going to use ?
There are a few file format we can use, YAML1, JSON2, XML3,
CSV4, Protobuf5 and many more I guess.
My choice went to JSON, why ? Because golang provide directly a package,
encoding/json
.
What’s new in go2pins-mc
?
All formulae are now automatically saved in a config.json
located at the root
of the generated directory.
Using the -formulae
flag with go2pins
or just giving a single formula will
be detected and then placed accordingly in the configuration file.
Example:
{
"formulae": [
"EMPTY !<> ((\"main___VERIFIER_error_n == 0\") && \"main___VERIFIER_error__callerLabel != 0\")",
...
]
}
Thus, we have our configuration file and we want to interract with it, let me introduce two new flags:
-formulae
displays all the saved formulae with their index.-formula index
computes the desired formula from the configuration file based on its index.
A concrete example would be to use it like that, let’s suppose a formulae.txt
file containing 42 formulae:
❯ go2pins -formulae formulae.txt file.go
❯ ./output/go2pins-mc -formula 27
This will compute the 28th formula of :formulae.txt
.
Want to have a look on this ? Here it is:
- SHA1: 72aecc7b
Going deeper with our Blackbox transitions
As we saw during the previous weeks, we are now able to ignore “any” desired functions to focus on one or more global variables with our blackbox. We want now to focus on a more complex problem, what if we want to ignore part of a function ?
Ignoring Statements ?
We need to put an image on what we are going to do so let’s focus on an example and I will try to explain what we need to do with it.
package main
var a int
func foo() {
// Modifying global variable a
a = 2
}
func main() {
println(a)
foo()
println(a)
}
Okay nothing too complicated here, if we want to keep everything modifying our
variable a
, we will keep every functions, here foo
.
Let’s see a simple case where we can ignore some statements.
package main
var a int
func foo() {
// Creating a new variable, b
b := 20
b += 20
// Modifying global variable a
a = 2
}
func main() {
println(a)
foo()
println(a)
}
You’re beginning to see where I want to go, here the variable b
is not useful
for us, so we can simply put it away by “ignoring” and put it in a blackbox
block.
I continue with something a little bit more tricky.
package main
var a int
func foo() {
// Creating a new variable, b
b := 20
b += 20
// Modifying global variable a
a = 2
// Assigning to b with global variable a
b += a
}
func main() {
println(a)
foo()
println(a)
}
We can see our output is always the same.
Since the global variable a
is only accessed and not modified, do we now need
to keep our b
variable with us or can we just ignore this new new statement
too ? It can’t be really obvious at the beginning, but since we need to access
to our global variable a
, we can’t put these new statement away unless we create
an error: Not defined variable: a
.
So here we can see our guideline, whenever the wanted variable is accessed or modified, we will need to keep the current statement.
Variable dependencies
Let’s continue to escalate thing with trickier and trickier cases.
package main
var a int
func foo() {
// Creating a new variable, d
d := 10
// Creating a new variable, c
c := 10 + d
// Creating a new variable, b
b := 20
b += c
// Modifying global variable a
a = 2
// Assigning to b with global variable a
b += a
}
func main() {
println(a)
foo()
println(a)
}
There are not much codes and it’s already really messy to figure out what we
need to do…
Okay so we want to keep our global variable a
, but since b
is accessing a
,
we also want to keep b
and same for c
and d
.
What’s the point to show allof that ? It’s really important to figure now how we are going to process to ignore undesired statements. And this case allow me to think that we will need a map of all variable inter-dependencies. Let me picture it:
a: []
b: [c, a]
c: [d]
d: []
First, if a
had some dependencies we would be sure that we can’t ignore them.
Secondly, we can easily draw a graph of our dependencies
(representing in green our wanted variables):
We now know we will have to explore our Abstract syntax tree to get this
informations, before making any decisions. We also know c
and d
can be
computed in a blackbox function since a
is not directly interacting with it.
Function calls
Okay, now let’s see about function call, obviously same as our variable dependencies, any function accessing or modifying our wanted variable won’t be ignored. But what if a function takes as parameter one of our wanted variable ? Same as before, if our variable is not used in the function, using it as a parameter won’t do anything, thus the function call will remain the same and the function ignored.
package main
var a int
func foo(x int) {
// Creating a new variable, b
b := x
b += 2
}
func main() {
println(a)
foo(a)
println(a)
}
We clearly see with this example that a
is used as parameter in foo
function,
since in foo
, it’s not a
anymore but x
, this function will be ignored anyway.
Let’s suppose already have a blackboxed function but there are statements to ignore.
package main
var a int
func bar() {
// Creating a new variable, b
b := 40
b += 2
}
func foo() {
// Calling a function, bar
bar()
// Modifying global variable a
a = 2
// Calling a function, bar
bar()
// Calling a function, bar
bar()
}
func main() {
println(a)
foo()
println(a)
}
The first call to bar
will already be a call to a blackbox function, but
after there are two calls to bar
, do we regroup this into one statement ?
Variable shadowing
Let’s focus on a case that may occurs and seems tricky, variable shadowing.
package main
var a int
func foo() {
// Creating a new variable, b
b := 40
// Modifying global variable a
a = 2
// Creating a new variable, a (variable shadowing)
a := 60
b += a
}
func main() {
println(a)
foo()
println(a)
}
We clearly see we don’t need anything excepting the statement that modifies
our global variable a
. The problem is that we have a new variable with the
same name. How to resolve this conflict ? Let’s take a look at
ast.Ident. We can identify a variable
with its Name, but with its Obj too.
So we can now deduce two new things:
- We will need a map for each function.
- We will not store variable Name, but variable Obj.
Scoping
Next question to ask is how are we gonna handle the scoping ? If we take our previous example:
package main
var a int
func foo() {
// TO IGNORE
// Creating a new variable, b
b := 40
// Modifying global variable a
a = 2
// TO IGNORE
// Creating a new variable, a (variable shadowing)
a := 60
// TO IGNORE
b += a
}
func main() {
println(a)
foo()
println(a)
}
We need to ignore three statements, but between them we have a statement we
can’t ignore. Do we make two new scopes to ignore or one big scope in which we put
the three statements ? Since a
does not need them, they can be regrouped.
Recap
We have now an idea what we are going to deal with and what need to be ignored. The guideline is to explore our abstract syntax tree to determine which statements need to be kept. And especially when it’s a matter of variable.
Links and more
References
- The
encoding/json
package, golang https://golang.org/pkg/encoding/json/ - The
ast
package, golang https://golang.org/pkg/go/ast/