Description
The following domain definition is from the 2vyper project here. $Struct defines a domain giving a unique identifier to every element of a struct, and $StructOps defines a domain for struct operations, parameterized by the type of struct element you are getting or setting. (Struct is defined in this way to avoid axioms that need to quantify over types.)
domain $Struct { function $struct_loc($s: $Struct, $m: Int): Int }
domain $StructOps[$T] {
function $struct_get($l: Int): $T
function $struct_set($s: $Struct, $m: Int, $t: $T): $Struct
axiom $get_set_0_ax {
forall $s: $Struct, $m: Int, $t: $T ::
{ $struct_loc($struct_set($s, $m, $t), $m) }
$struct_get($struct_loc($struct_set($s, $m, $t), $m)) == $t
}
axiom $get_set_1_ax {
forall $s: $Struct, $m: Int, $n: Int, $t: $T ::
{ $struct_loc($struct_set($s, $n, $t), $m) }
$m != $n ==> $struct_loc($s, $m) == $struct_loc($struct_set($s, $n, $t), $m)
}
}
The following does not raise a type error, allowing us to set a Bool variable to an Int value. Furthermore, it exposes a soundness bug, since the assertion (y == true) || (y == false) passes, despite the actual value of y being 10.
method test_struct() {
var s: $Struct
s := $struct_set(s,0,10)
var y: Bool
y := $struct_get($struct_loc(s,0))
assert (y == true) || (y == false)
}
It seems that the SMT solver assumes that the type checker is catching all type errors, and so relies on the original type of y as a Bool to vacuously verify assert (y == true) || (y == false)
. However, the type checker does not catch the type safety violation of setting y to 10, leading the solver to verify the false statement (y == true) || (y == false)
.
(Credit to my advisor Thomas Wies on realizing that the type safety violation can lead to a soundness bug!)