Skip to content

Commit bac4ccd

Browse files
committed
Add /warnVacuousProofs option
This option enables `/trackVerificationCoverage` and automatically warns if it detects vacuous proofs. This is intended for the less-common case where a front end doesn't add IDs to program elements and then post- process the results.
1 parent adaa245 commit bac4ccd

File tree

5 files changed

+286
-2
lines changed

5 files changed

+286
-2
lines changed

Source/Core/CoverageAnnotator.cs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
using System.Collections.Generic;
2+
using System.Diagnostics;
3+
4+
namespace Microsoft.Boogie;
5+
6+
/// <summary>
7+
/// Add `{:id ...}` attributes to all assertions, assumptions, requires
8+
/// clauses, ensures clauses, and call statements so that verification
9+
/// coverage tracking is possible. This exists primarily to support the
10+
/// automatic detection of vacuous proofs in the case where no front
11+
/// end has added these already.
12+
/// </summary>
13+
public class CoverageAnnotator : StandardVisitor
14+
{
15+
private int idCount = 0;
16+
private string currentImplementation;
17+
private Dictionary<string, ISet<string>> implementationGoalIds = new();
18+
private Dictionary<string, Absy> idMap = new();
19+
20+
private void addImplementationGoalId(string id)
21+
{
22+
implementationGoalIds.TryAdd(currentImplementation, new HashSet<string>());
23+
implementationGoalIds[currentImplementation].Add(id);
24+
}
25+
26+
private void AddId(ICarriesAttributes node, bool isGoal)
27+
{
28+
var idStr = "id" + idCount;
29+
idCount++;
30+
Absy absy = node as Absy;
31+
idMap.Add(idStr, absy);
32+
if (isGoal) {
33+
addImplementationGoalId(idStr);
34+
}
35+
node.AddStringAttribute(absy.tok, "id", idStr);
36+
}
37+
38+
/// <summary>
39+
/// Get the set of IDs that correspond to goals within the named
40+
/// implementation.
41+
/// </summary>
42+
/// <param name="implName">The name of the implementation.</param>
43+
/// <returns>The IDs for all goal elements within the implementation.</returns>
44+
public ISet<string> GetImplementationGoalIds(string implName) => implementationGoalIds[implName];
45+
46+
/// <summary>
47+
/// Get the AST node corresponding to the given ID.
48+
/// </summary>
49+
/// <param name="idStr">The `id` attribute placed on an AST node.</param>
50+
/// <returns>The node where that `id` occurs.</returns>
51+
public Absy GetIdNode(string idStr) => idMap[idStr];
52+
53+
public override Implementation VisitImplementation(Implementation node)
54+
{
55+
currentImplementation = node.Name;
56+
return base.VisitImplementation(node);
57+
}
58+
59+
public override Cmd VisitAssertCmd(AssertCmd node)
60+
{
61+
AddId(node, true);
62+
return base.VisitAssertCmd(node);
63+
}
64+
65+
public override Cmd VisitAssumeCmd(AssumeCmd node)
66+
{
67+
AddId(node, false);
68+
return base.VisitAssumeCmd(node);
69+
}
70+
71+
public override Cmd VisitCallCmd(CallCmd node)
72+
{
73+
AddId(node, false);
74+
return base.VisitCallCmd(node);
75+
}
76+
77+
public override Requires VisitRequires(Requires requires)
78+
{
79+
AddId(requires, false);
80+
return base.VisitRequires(requires);
81+
}
82+
83+
public override Ensures VisitEnsures(Ensures ensures)
84+
{
85+
AddId(ensures, true);
86+
return base.VisitEnsures(ensures);
87+
}
88+
}

Source/ExecutionEngine/CommandLineOptions.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,15 @@ public bool TrackVerificationCoverage {
322322
set => trackVerificationCoverage = value;
323323
}
324324

325+
public bool WarnVacuousProofs
326+
{
327+
get => warnVacuousProofs;
328+
set {
329+
warnVacuousProofs = value;
330+
if (warnVacuousProofs) { trackVerificationCoverage = true; }
331+
}
332+
}
333+
325334
public int InlineDepth { get; set; } = -1;
326335

327336
public bool UseProverEvaluate {
@@ -605,6 +614,7 @@ void ObjectInvariant5()
605614
private bool normalizeNames;
606615
private bool normalizeDeclarationOrder = true;
607616
private bool keepQuantifier = false;
617+
private bool warnVacuousProofs;
608618

609619
public List<CoreOptions.ConcurrentHoudiniOptions> Cho { get; set; } = new();
610620

@@ -1373,6 +1383,7 @@ protected override bool ParseOption(string name, CommandLineParseState ps)
13731383
ps.CheckBooleanFlag("useUnsatCoreForContractInfer", x => useUnsatCoreForContractInfer = x) ||
13741384
ps.CheckBooleanFlag("printAssignment", x => PrintAssignment = x) ||
13751385
ps.CheckBooleanFlag("trackVerificationCoverage", x => trackVerificationCoverage = x) ||
1386+
ps.CheckBooleanFlag("warnVacuousProofs", x => WarnVacuousProofs = x) ||
13761387
ps.CheckBooleanFlag("useProverEvaluate", x => useProverEvaluate = x) ||
13771388
ps.CheckBooleanFlag("deterministicExtractLoops", x => DeterministicExtractLoops = x) ||
13781389
ps.CheckBooleanFlag("verifySeparately", x => VerifySeparately = x) ||
@@ -2005,7 +2016,11 @@ Track and report which program elements labeled with an
20052016
assignments, and calls can be labeled for inclusion in this
20062017
report. This generalizes and replaces the previous
20072018
(undocumented) `/printNecessaryAssertions` option.
2008-
2019+
/warnVacuousProofs
2020+
Automatically add `{:id ...}` attributes to assumptions, assertions,
2021+
requires clauses, ensures clauses, and calls; enable the
2022+
`/trackVerificationCoverage` option; and warn when proof goals are
2023+
not covered by a proof.
20092024
/keepQuantifier
20102025
If pool-based quantifier instantiation creates instances of a quantifier
20112026
then keep the quantifier along with the instances. By default, the quantifier

Source/ExecutionEngine/ExecutionEngine.cs

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,9 @@ private Task<ProcessedProgram> PreProcessProgramVerification(Program program, Ca
611611
}
612612

613613
var processedProgram = Options.ExtractLoops ? ExtractLoops(program) : new ProcessedProgram(program);
614+
if (Options.WarnVacuousProofs) {
615+
processedProgram = AddVacuityChecking(processedProgram);
616+
}
614617

615618
if (Options.PrintInstrumented)
616619
{
@@ -635,6 +638,55 @@ private ProcessedProgram ExtractLoops(Program program)
635638
});
636639
}
637640

641+
private ProcessedProgram AddVacuityChecking(ProcessedProgram processedProgram)
642+
{
643+
Program program = processedProgram.Program;
644+
CoverageAnnotator annotator = new CoverageAnnotator();
645+
foreach (var impl in program.Implementations) {
646+
annotator.VisitImplementation(impl);
647+
}
648+
return new ProcessedProgram(program, (vcgen, impl, result) =>
649+
{
650+
CheckVacuity(annotator, impl, result);
651+
processedProgram.PostProcessResult(vcgen, impl, result);
652+
}
653+
);
654+
}
655+
656+
private void CheckVacuity(CoverageAnnotator annotator, Implementation impl, ImplementationRunResult verificationResult)
657+
{
658+
var covered = verificationResult
659+
.RunResults
660+
.SelectMany(r => r.CoveredElements)
661+
.ToHashSet();
662+
foreach (var goalId in annotator.GetImplementationGoalIds(impl.Name)) {
663+
if (!CoveredId(goalId, covered)) {
664+
var node = annotator.GetIdNode(goalId);
665+
Options.Printer.AdvisoryWriteLine(Options.OutputWriter,
666+
$"{node.tok.filename}({node.tok.line},{node.tok.col - 1}): Warning: Proved vacuously");
667+
}
668+
}
669+
}
670+
671+
private static bool CoveredId(string goalId, HashSet<TrackedNodeComponent> covered)
672+
{
673+
foreach (var component in covered) {
674+
if (component is LabeledNodeComponent { id: var id } && id == goalId) {
675+
return true;
676+
}
677+
678+
if (component is TrackedInvariantEstablished { invariantId: var eid } && eid == goalId) {
679+
return true;
680+
}
681+
682+
if (component is TrackedInvariantMaintained { invariantId: var mid } && mid == goalId) {
683+
return true;
684+
}
685+
}
686+
687+
return false;
688+
}
689+
638690
private Implementation[] GetPrioritizedImplementations(Program program)
639691
{
640692
var impls = program.Implementations.Where(
@@ -964,7 +1016,6 @@ private Task<ImplementationRunResult> VerifyImplementationWithLargeThread(Proces
9641016
return resultTask;
9651017
}
9661018

967-
9681019
#region Houdini
9691020

9701021
private async Task<PipelineOutcome> RunHoudini(Program program, PipelineStatistics stats, ErrorReporterDelegate er,

Source/Provers/SMTLib/SMTLibOptions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public interface SMTLibOptions : CoreOptions
1414
bool ImmediatelyAcceptCommands { get; }
1515
bool RunningBoogieFromCommandLine { get; }
1616
bool TrackVerificationCoverage { get; }
17+
bool WarnVacuousProofs { get; }
1718
string ProverPreamble { get; }
1819
bool TraceDiagnosticsOnTimeout { get; }
1920
uint TimeLimitPerAssertionInPercent { get; }
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
Parsing verificationCoverage.bpl
2+
Coalescing blocks...
3+
Inlining...
4+
5+
Verifying testRequiresAssign ...
6+
[TRACE] Using prover: z3
7+
Proof dependencies:
8+
a0
9+
id0
10+
id1
11+
id2
12+
13+
Verifying sum ...
14+
Proof dependencies:
15+
id10
16+
id7
17+
id8
18+
id9
19+
invariant id4 assumed in body
20+
invariant id4 established
21+
invariant id4 maintained
22+
invariant id5 assumed in body
23+
invariant id5 established
24+
invariant id5 maintained
25+
invariant id6 established
26+
invariant id6 maintained
27+
28+
Verifying contradictoryAssume ...
29+
Proof dependencies:
30+
id11
31+
id12
32+
verificationCoverage.bpl(143,4): Warning: Proved vacuously
33+
34+
Verifying falseRequires ...
35+
Proof dependencies:
36+
id16
37+
verificationCoverage.bpl(150,4): Warning: Proved vacuously
38+
39+
Verifying contradictoryRequires ...
40+
Proof dependencies:
41+
id19
42+
id20
43+
verificationCoverage.bpl(158,4): Warning: Proved vacuously
44+
45+
Verifying assumeFalse ...
46+
Proof dependencies:
47+
id21
48+
verificationCoverage.bpl(163,2): Warning: Proved vacuously
49+
50+
Verifying testEnsuresCallee ...
51+
Proof dependencies:
52+
id23
53+
id24
54+
id25
55+
56+
Verifying testEnsuresCaller ...
57+
Proof dependencies:
58+
ensures clause id23 from call id26
59+
ensures clause id23 from call id27
60+
ensures clause id24 from call id27
61+
id28
62+
id29
63+
id30
64+
requires clause id25 proved for call id26
65+
requires clause id25 proved for call id27
66+
xy_sum
67+
68+
Verifying obviouslyUnconstrainedCode ...
69+
Proof dependencies:
70+
constrained
71+
id31
72+
id32
73+
74+
Verifying callContradictoryFunction ...
75+
Proof dependencies:
76+
ensures clause cont_ens_abs from call id34
77+
id36
78+
requires clause xpos_abs proved for call id34
79+
verificationCoverage.bpl(203,2): Warning: Proved vacuously
80+
81+
Verifying usesSomeInteger ...
82+
Proof dependencies:
83+
id37
84+
someInteger_value_axiom
85+
Proof dependencies of whole program:
86+
a0
87+
constrained
88+
ensures clause cont_ens_abs from call id34
89+
ensures clause id23 from call id26
90+
ensures clause id23 from call id27
91+
ensures clause id24 from call id27
92+
id0
93+
id1
94+
id10
95+
id11
96+
id12
97+
id16
98+
id19
99+
id2
100+
id20
101+
id21
102+
id23
103+
id24
104+
id25
105+
id28
106+
id29
107+
id30
108+
id31
109+
id32
110+
id36
111+
id37
112+
id7
113+
id8
114+
id9
115+
invariant id4 assumed in body
116+
invariant id4 established
117+
invariant id4 maintained
118+
invariant id5 assumed in body
119+
invariant id5 established
120+
invariant id5 maintained
121+
invariant id6 established
122+
invariant id6 maintained
123+
requires clause id25 proved for call id26
124+
requires clause id25 proved for call id27
125+
requires clause xpos_abs proved for call id34
126+
someInteger_value_axiom
127+
xy_sum
128+
129+
Boogie program verifier finished with 11 verified, 0 errors

0 commit comments

Comments
 (0)