-
Notifications
You must be signed in to change notification settings - Fork 267
/
Copy pathProgramModification.cs
225 lines (206 loc) · 9.55 KB
/
ProgramModification.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
// Copyright by the contributors to the Dafny Project
// SPDX-License-Identifier: MIT
#nullable disable
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.Boogie;
using Microsoft.Boogie.SMTLib;
using Microsoft.Dafny;
using Program = Microsoft.Boogie.Program;
namespace DafnyTestGeneration {
public class Modifications {
private readonly DafnyOptions options;
internal HashSet<int> preprocessedPrograms = new();
// List of all types for which a {:synthesize} - annotated method is needed
// These methods are used to get fresh instances of the corresponding types
internal readonly List<UserDefinedType> TypesToSynthesize = new();
public Modifications(DafnyOptions options) {
this.options = options;
}
private readonly Dictionary<string, ProgramModification> idToModification = new();
public ProgramModification GetProgramModification(Program program,
Implementation impl, HashSet<string> capturedStates, HashSet<string> testEntryNames,
string uniqueId) {
if (!idToModification.ContainsKey(uniqueId)) {
idToModification[uniqueId] =
new ProgramModification(options, program, impl, capturedStates, testEntryNames, uniqueId);
}
return idToModification[uniqueId];
}
public IEnumerable<ProgramModification> Values => idToModification.Values;
public int NumberOfBlocksCovered(HashSet<string> blockIds, bool onlyIfTestsExists = false) {
var relevantModifications = Values.Where(modification =>
modification.CounterexampleStatus == ProgramModification.Status.Success && (!onlyIfTestsExists || (modification.TestMethod != null && modification.TestMethod.IsValid)));
return blockIds.Count(blockId =>
relevantModifications.Any(mod => mod.CapturedStates.Contains(blockId)));
}
}
/// <summary>
/// Records a modification of the boogie program under test. The modified
/// program has an assertion that should fail provided a certain block is
/// visited / path is taken.
/// </summary>
public class ProgramModification {
private DafnyOptions Options { get; }
internal enum Status { Success, Failure, Untested }
internal Status CounterexampleStatus;
public readonly Implementation Implementation; // implementation under test
internal readonly string uniqueId;
public readonly HashSet<string> CapturedStates;
private readonly HashSet<string> testEntryNames;
private Program/*?*/ program;
private string/*?*/ counterexampleLog;
internal TestMethod TestMethod;
public ProgramModification(DafnyOptions options, Program program, Implementation impl,
HashSet<string> capturedStates,
HashSet<string> testEntryNames, string uniqueId) {
Options = options;
Implementation = impl;
CounterexampleStatus = Status.Untested;
this.program = program;
this.testEntryNames = testEntryNames;
CapturedStates = capturedStates;
this.uniqueId = uniqueId;
counterexampleLog = null;
TestMethod = null;
}
/// <summary>
/// Setup DafnyOptions to prepare for counterexample extraction
/// </summary>
private static void SetupForCounterexamples(DafnyOptions options) {
options.NormalizeNames = false;
options.EmitDebugInformation = true;
options.ErrorTrace = 1;
options.EnhancedErrorMessages = 1;
options.ModelViewFile = "-";
options.Prune = options.TestGenOptions.ForcePrune;
options.PruneInfeasibleEdges = false; // because same implementation object is reused to generate multiple tests
}
/// <summary>
/// Return the counterexample log produced by trying to verify this modified
/// version of the original boogie program. Return null if this
/// counterexample does not cover any new SourceModifications.
/// </summary>
public async Task<string>/*?*/ GetCounterExampleLog(Modifications cache) {
if (CounterexampleStatus != Status.Untested ||
(CapturedStates.Count != 0 && IsCovered(cache))) {
return counterexampleLog;
}
var options = CopyForProcedure(Options, testEntryNames);
SetupForCounterexamples(options);
var writer = new StringWriter();
if (cache.preprocessedPrograms.Contains(program.UniqueId)) {
options.UseAbstractInterpretation = false; // running abs. inter. twice on the same program leads to errors
} else {
cache.preprocessedPrograms.Add(program.UniqueId);
}
using (var engine = ExecutionEngine.CreateWithoutSharedCache(options)) {
var guid = Guid.NewGuid().ToString();
var result = await Task.WhenAny(engine.InferAndVerify(writer, program,
new PipelineStatistics(), null,
_ => { }, guid),
Task.Delay(TimeSpan.FromSeconds(Options.TimeLimit <= 0
? TestGenerationOptions.DefaultTimeLimit
: Options.TimeLimit)));
program = null; // allows to garbage collect what is no longer needed
CounterexampleStatus = Status.Failure;
counterexampleLog = null;
if (result is not Task<PipelineOutcome>) {
if (Options.Verbose) {
await options.OutputWriter.WriteLineAsync(
$"// No test can be generated for {uniqueId} " +
"because the verifier timed out.");
}
return counterexampleLog;
}
}
var log = writer.ToString();
// If Dafny finds several assertion violations (e.g. because of inlining one trap assertion multiple times),
// pick the first execution trace and extract the counterexample from it
const string executionTraceString = "Execution trace";
var firstTraceIndex = log.IndexOf(executionTraceString, 0, StringComparison.Ordinal);
if (firstTraceIndex > 0 && log.Length > firstTraceIndex + executionTraceString.Length) {
var secondTraceIndex = log.IndexOf(executionTraceString, firstTraceIndex + executionTraceString.Length, StringComparison.Ordinal);
if (secondTraceIndex > 0) {
log = log[..secondTraceIndex];
}
}
// make sure that there is a counterexample (i.e. no parse errors, etc):
var stringReader = new StringReader(log);
while (await stringReader.ReadLineAsync() is { } line) {
if (line.StartsWith("Block |")) {
counterexampleLog = log;
CounterexampleStatus = Status.Success;
var blockId = Regex.Replace(line, @"\s+", "").Split('|')[2];
if (Options.Verbose &&
Options.TestGenOptions.Mode != TestGenerationOptions.Modes.Path && !CapturedStates.Contains(blockId)) {
await options.OutputWriter.WriteLineAsync($"// Test {uniqueId} covers {blockId}");
}
CapturedStates.Add(blockId);
}
}
if (Options.Verbose && counterexampleLog == null) {
if (log == "") {
await options.OutputWriter.WriteLineAsync(
$"// No test is generated for {uniqueId} " +
"because the verifier proved that no inputs could cause this location to be visited.");
} else if (log.Contains("MODEL") || log.Contains("anon0")) {
await options.OutputWriter.WriteLineAsync(
$"// No test is generated for {uniqueId} " +
"because there is no enhanced error trace. This can be caused " +
"by a bug in Boogie error reporting.");
} else {
await options.OutputWriter.WriteLineAsync(
$"// No test is generated for {uniqueId} " +
"because the verifier timed out.");
}
}
return counterexampleLog;
}
/// <summary>
/// Return a copy of the given DafnyOption instance that (for the purposes
/// of test generation) is identical to the <param name="options"></param>
/// parameter in everything except the value of the ProcsToCheck field that
/// determines the procedures to be verified and should be set to the value of
/// the <param name="proceduresToVerify"></param> parameter.
/// </summary>
internal static DafnyOptions CopyForProcedure(DafnyOptions options, HashSet<string> proceduresToVerify) {
var copy = new DafnyOptions(options);
copy.ProcsToCheck = proceduresToVerify.ToList();
return copy;
}
public async Task<TestMethod> GetTestMethod(Modifications cache, DafnyInfo dafnyInfo, bool returnNullIfNotUnique = true) {
if (Options.Verbose) {
await dafnyInfo.Options.OutputWriter.WriteLineAsync(
$"// Constructing the test for {uniqueId}...");
}
var log = await GetCounterExampleLog(cache);
if (log == null) {
return null;
}
TestMethod = new TestMethod(dafnyInfo, log, cache);
if (!TestMethod.IsValid || !returnNullIfNotUnique) {
return TestMethod;
}
var duplicate = cache.Values
.Where(mod => mod != this && Equals(mod.TestMethod, TestMethod))
.FirstOrDefault((ProgramModification)null);
if (duplicate == null) {
return TestMethod;
}
if (Options.Verbose) {
await dafnyInfo.Options.OutputWriter.WriteLineAsync(
$"// Test for {uniqueId} matches a test previously generated " +
$"for {duplicate.uniqueId} - this may occur if the code under test is non-deterministic, " +
$"if a method/function is not inlined, or the input parameters are of a type not supported by " +
$"test generation (trait types and function types).");
}
return null;
}
public bool IsCovered(Modifications cache) => cache.NumberOfBlocksCovered(CapturedStates) == CapturedStates.Count;
}
}