-
Notifications
You must be signed in to change notification settings - Fork 118
Description
Reproduce
From Dafny using this version:
4.11.0+fcb2042d6d043a2634f0854338c08feeaaaf4ae2
With this code:
method Abs(x: int) returns (y: int)
ensures x>=0 ==> x==y
ensures x<0 ==> x==-y
{
if x < 0 {
y := -x;
} else {
y := x;
}
}Running the proof dependencies tool:
dafny verify [file_name] --verification-coverage-report cov --log-format text --solver-option LOG_FILE=output.smt2 --bprint output.bpl
We obtain that line y:=x was unused by the proof.
Problem
The thing is:
In the generated Boogie implementation I see both assignment statements present:
implementation {:smt_option "smt.arith.solver", "2"} {:verboseName "Abs (correctness)"} Impl$$_module.__default.Abs(x#0: int) returns (defass#y#0: bool, y#0: int, $_reverifyPost: bool)
{
var $_ModifiesFrame: [ref,Field]bool;
// AddMethodImpl: Abs, Impl$$_module.__default.Abs
$_ModifiesFrame := (lambda $o: ref, $f: Field ::
$o != null && $Unbox(read($Heap, $o, alloc)): bool ==> false);
assume {:captureState "abs.dfy(4,0): initial state"} true;
$_reverifyPost := false;
// ----- if statement ----- abs.dfy(5,3)
assume true;
if (x#0 < 0)
{
// ----- assignment statement ----- abs.dfy(6,7)
assume true;
assume true;
y#0 := 0 - x#0;
defass#y#0 := true;
assume {:captureState "abs.dfy(6,11)"} true;
}
else
{
// ----- assignment statement ----- abs.dfy(8,7)
assume true;
assume true;
y#0 := x#0;
defass#y#0 := true;
assume {:captureState "abs.dfy(8,11)"} true;
}
assert {:id "id12"} defass#y#0;
}In the SMT translation (output.smt2) the arithmetic assignment (y := -x) leads to a small, named assume that can be extracted in unsat cores / coverage. Example pattern (from SMT):
assert (! $generated@@93 :named aux$$assume$$id10))
(=> $generated@@93 (= $generated@@94 (- 0 $generated@@90))That aux$$assume$$id10 name lets the coverage/unsat-core tooling attribute the verification obligation to the Dafny/Boogie source line.
However, for the trivial copy y := x there is no analogous named assume generated in the SMT. As a result the coverage report (and unsat-core extraction) never attributes that source line as a relevant proof node — it appears “unused by the proof”
Why I think this is a problem
From a coverage / proof-explanation point of view a line present in the Boogie translation should be reflected as a traceable proof obligation (or at least have instrumentation) so tools that extract unsat cores or compute verification coverage can report which source lines were relevant to the proof. Omitting a named assume for y := x makes the coverage tool miss that line.
This is surprising because semantically y := x is a state mutation like y := x + 2. And y := x + 2 generated the named assume clause.
Workarounds
Replacing y := x with a call to an identity function forces the SMT to create a named assume, so coverage shows the line as relevant. Example:
function {:inline} id(x: int): int := x
...
y := id(x);Expected behaviour
1 - The translation should produce a named assume (or other traceable instrumentation) for every assignment statement in the Boogie program, even trivial copies like y := x, so coverage / unsat-core extraction can reliably point to source lines; or
2 - There should be a clear documented option to disable the collapsing/optimization that removes such named assumptions, or an option to enable “full instrumentation” for coverage/unsat-core extraction.
Question
1 - Is this an intentional optimization in Boogie -> smt pipeline (i.e., trivial copies are collapsed and not instrumented), or is it a bug/omission in the SMT-name/assume generation?
2 - If this is intentional, is there a recommended way to force per-statement named assumptions (or otherwise ensure that every source statement appears in unsat cores / coverage reports) without modifying source code (i.e., without adding id(x) calls or asserts)?