mbt-gen 0.0.2 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/mbt-gen.rb +451 -150
- data/lib/progress.rb +47 -5
- data/lib/solver-lib.rb +245 -98
- metadata +2 -2
data/lib/progress.rb
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/ruby
|
|
2
2
|
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
|
|
3
5
|
class Progress
|
|
4
6
|
|
|
5
|
-
def initialize()
|
|
7
|
+
def initialize(logfilename)
|
|
6
8
|
@last_line = ""
|
|
9
|
+
dir = File.dirname(logfilename)
|
|
10
|
+
FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
|
|
11
|
+
@logfile = File.open(logfilename, "w")
|
|
7
12
|
end
|
|
8
13
|
|
|
9
14
|
def update_last_line(last_line)
|
|
@@ -13,27 +18,64 @@ class Progress
|
|
|
13
18
|
end
|
|
14
19
|
|
|
15
20
|
def print_line(line)
|
|
21
|
+
@logfile.puts(line)
|
|
22
|
+
@logfile.flush()
|
|
16
23
|
print "\033[M#{line}\n#{@last_line}"
|
|
17
24
|
$stdout.flush()
|
|
18
25
|
end
|
|
19
26
|
end
|
|
20
27
|
|
|
21
28
|
class ProgressBar < Progress
|
|
22
|
-
def initialize(max)
|
|
29
|
+
def initialize(max, logfilename)
|
|
30
|
+
super(logfilename)
|
|
23
31
|
@max = max.to_f
|
|
24
32
|
end
|
|
25
33
|
|
|
34
|
+
def set_max(value)
|
|
35
|
+
@max = value.to_f
|
|
36
|
+
end
|
|
37
|
+
|
|
26
38
|
def render_bar(current, length)
|
|
27
39
|
bars = (current.to_f * length.to_f).floor
|
|
28
40
|
"=" * bars + ">" + " " * (length - bars)
|
|
29
41
|
end
|
|
30
42
|
|
|
31
|
-
def
|
|
43
|
+
def processing_time(start_time, prior_processessing_time)
|
|
44
|
+
total_sec = prior_processessing_time + (Time.now().to_i - start_time)
|
|
45
|
+
sec = total_sec
|
|
46
|
+
days = sec / (3600*24)
|
|
47
|
+
sec -= days * (3600*24)
|
|
48
|
+
hours = sec / 3600
|
|
49
|
+
sec -= hours * 3600
|
|
50
|
+
min = sec / 60
|
|
51
|
+
sec -= min * 60
|
|
52
|
+
if days > 0 then
|
|
53
|
+
return format("%d:%02d:%02d:%02d", days, hours, min, sec)
|
|
54
|
+
else
|
|
55
|
+
if hours > 0 then
|
|
56
|
+
return format("%02d:%02d:%02d", hours, min, sec)
|
|
57
|
+
else
|
|
58
|
+
return format("%02d:%02d", min, sec)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def report_progress(progress, start_time, prior_processessing_time, num_docs)
|
|
64
|
+
time_str = processing_time(start_time, prior_processessing_time)
|
|
32
65
|
if progress then
|
|
33
66
|
current = (progress.to_f / @max)
|
|
34
|
-
update_last_line("[#{render_bar(current, 40)}] combinations covered: #{progress}/#{@max} (#{format("%.8f", current * 100.0)}%)")
|
|
67
|
+
update_last_line("#{time_str} [#{render_bar(current, 40)}] combinations covered: #{progress}/#{@max} (#{format("%.8f", current * 100.0)}%), docs generated: #{num_docs}")
|
|
35
68
|
else
|
|
36
|
-
update_last_line("[... ? ... ? ... ? ...] combinations covered: ?/∞ (?%)")
|
|
69
|
+
update_last_line("#{time_str} [... ? ... ? ... ? ...] combinations covered: ?/∞ (?%), docs generated: #{num_docs}")
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def self.logging(max, logfile, &block)
|
|
74
|
+
progress = ProgressBar.new(max, logfile)
|
|
75
|
+
begin
|
|
76
|
+
block.call(progress)
|
|
77
|
+
ensure
|
|
78
|
+
progress.print_line("Output written to #{logfile}")
|
|
37
79
|
end
|
|
38
80
|
end
|
|
39
81
|
end
|
data/lib/solver-lib.rb
CHANGED
|
@@ -5,9 +5,9 @@ require 'open3'
|
|
|
5
5
|
|
|
6
6
|
class SolverSession
|
|
7
7
|
|
|
8
|
-
def initialize(
|
|
9
|
-
@
|
|
10
|
-
@
|
|
8
|
+
def initialize(solverFactory, logFileName)
|
|
9
|
+
@solverFactory = solverFactory
|
|
10
|
+
@solver = @solverFactory.new_solver()
|
|
11
11
|
@protocol_filename = logFileName
|
|
12
12
|
@protocol = File.open(@protocol_filename, "w")
|
|
13
13
|
@assertCounter = -1
|
|
@@ -15,13 +15,26 @@ class SolverSession
|
|
|
15
15
|
@consts = {}
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
+
def switch_solver(progress)
|
|
19
|
+
@solver.close()
|
|
20
|
+
@solver = @solverFactory.new_solver()
|
|
21
|
+
# re-feed the session protocol into the new solver to get it up-to-date
|
|
22
|
+
@protocol.close()
|
|
23
|
+
File.open(@protocol_filename, "r") do |protocol|
|
|
24
|
+
protocol.each_line do |line|
|
|
25
|
+
to_solver(progress, line)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
@protocol = File.open(@protocol_filename, "a")
|
|
29
|
+
end
|
|
30
|
+
|
|
18
31
|
def emptyReadBuffer(progress)
|
|
19
32
|
empty = false
|
|
20
33
|
while !empty do
|
|
21
34
|
begin
|
|
22
35
|
c = ""
|
|
23
|
-
@z3outAndErr.read_nonblock(1, c)
|
|
24
|
-
line = c + @z3outAndErr.gets
|
|
36
|
+
@solver.z3outAndErr.read_nonblock(1, c)
|
|
37
|
+
line = c + @solver.z3outAndErr.gets
|
|
25
38
|
checkLineForSolverError(line, progress)
|
|
26
39
|
@protocol.puts("; Solver Response: #{line}")
|
|
27
40
|
rescue Errno::EWOULDBLOCK
|
|
@@ -45,16 +58,27 @@ class SolverSession
|
|
|
45
58
|
|
|
46
59
|
def to_solver(progress, line)
|
|
47
60
|
emptyReadBuffer(progress);
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
61
|
+
success = false
|
|
62
|
+
while (!success) do
|
|
63
|
+
begin
|
|
64
|
+
@solver.z3in.puts(line)
|
|
65
|
+
success = true
|
|
66
|
+
rescue Errno::EINVAL => e
|
|
67
|
+
progress.print_line("DEBUG: Invalid argument to puts(): #{line.inspect}")
|
|
68
|
+
progress.print_line("An Error occured: #{e.message}")
|
|
69
|
+
e.backtrace.each { |stline| progress.print_line(stline) }
|
|
70
|
+
progress.print_line("=> Switching Solver")
|
|
71
|
+
switch_solver(progress)
|
|
72
|
+
progress.print_line("=> Switch successful. Trying again.")
|
|
73
|
+
rescue Errno::EPIPE => e
|
|
74
|
+
progress.print_line("An Error occured: #{e.message}")
|
|
75
|
+
e.backtrace.each { |stline| progress.print_line(stline) }
|
|
76
|
+
progress.print_line("=> Switching Solver")
|
|
77
|
+
switch_solver(progress)
|
|
78
|
+
progress.print_line("=> Switch successful. Trying again.")
|
|
79
|
+
end
|
|
57
80
|
end
|
|
81
|
+
@protocol.puts(line)
|
|
58
82
|
end
|
|
59
83
|
|
|
60
84
|
def checkLineForSolverError(line, progress)
|
|
@@ -65,14 +89,14 @@ class SolverSession
|
|
|
65
89
|
|
|
66
90
|
|
|
67
91
|
def from_solver(progress)
|
|
68
|
-
result = @z3outAndErr.gets
|
|
92
|
+
result = @solver.z3outAndErr.gets(chomp: true)
|
|
69
93
|
checkLineForSolverError(result, progress)
|
|
70
94
|
@protocol.puts("; Solver Response: #{result}")
|
|
71
95
|
return result
|
|
72
96
|
end
|
|
73
97
|
|
|
74
98
|
def replace_strings(line)
|
|
75
|
-
line.gsub(/"
|
|
99
|
+
line.gsub(/"[^"]*"/, "")
|
|
76
100
|
end
|
|
77
101
|
|
|
78
102
|
def read_multiline_from_solver()
|
|
@@ -80,21 +104,23 @@ class SolverSession
|
|
|
80
104
|
for_counting = ""
|
|
81
105
|
@protocol.puts
|
|
82
106
|
@protocol.puts("; BEGIN Multiline Solver Response:")
|
|
83
|
-
line = @z3outAndErr.gets
|
|
107
|
+
line = @solver.z3outAndErr.gets(chomp: true)
|
|
84
108
|
result += line
|
|
85
|
-
for_counting
|
|
109
|
+
for_counting = replace_strings(line)
|
|
86
110
|
@protocol.puts("; #{line}")
|
|
87
111
|
|
|
88
112
|
num_opening = for_counting.count("(")
|
|
89
113
|
num_closing = for_counting.count(")")
|
|
90
|
-
while num_opening > num_closing do
|
|
91
|
-
line = @z3outAndErr.gets
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
114
|
+
while num_opening > num_closing && line != nil do
|
|
115
|
+
line = @solver.z3outAndErr.gets(chomp: true)
|
|
116
|
+
if line
|
|
117
|
+
result += line
|
|
118
|
+
for_counting = replace_strings(line)
|
|
119
|
+
@protocol.puts("; #{line}")
|
|
120
|
+
|
|
121
|
+
num_opening += for_counting.count("(")
|
|
122
|
+
num_closing += for_counting.count(")")
|
|
123
|
+
end
|
|
98
124
|
end
|
|
99
125
|
@protocol.puts("; END Multiline Solver Response")
|
|
100
126
|
return result
|
|
@@ -113,6 +139,13 @@ class SolverSession
|
|
|
113
139
|
return nextAssertionID
|
|
114
140
|
end
|
|
115
141
|
|
|
142
|
+
def associateAssertionIDWith(assertionID, hash)
|
|
143
|
+
if @associations.has_key?(assertionID) then
|
|
144
|
+
raise RuntimeError.new("AssertionID #{assertionID.inspect} already has info associated with it!")
|
|
145
|
+
end
|
|
146
|
+
@associations[assertionID] = hash
|
|
147
|
+
end
|
|
148
|
+
|
|
116
149
|
def getAssociationForAssertionID(assertionID)
|
|
117
150
|
return @associations[assertionID]
|
|
118
151
|
end
|
|
@@ -126,93 +159,103 @@ class SolverSession
|
|
|
126
159
|
def get_const_info(name)
|
|
127
160
|
@consts[name]
|
|
128
161
|
end
|
|
162
|
+
|
|
163
|
+
def parse_unsat_core(unsat_core)
|
|
164
|
+
@solver.parse_unsat_core(unsat_core, self)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def handleSolverError(progress, options, result)
|
|
168
|
+
@solver.handleSolverError(progress, self, options, result)
|
|
169
|
+
end
|
|
129
170
|
end
|
|
130
171
|
|
|
131
|
-
class
|
|
172
|
+
class SolverFactory
|
|
173
|
+
|
|
174
|
+
attr_reader :z3path, :z3version
|
|
132
175
|
|
|
133
176
|
def initialize(progress, z3path)
|
|
134
|
-
|
|
135
|
-
|
|
177
|
+
@progress = progress
|
|
178
|
+
z3version = nil
|
|
179
|
+
exitstatus = nil
|
|
180
|
+
begin
|
|
181
|
+
z3version = `#{z3path} --version`.chomp()
|
|
182
|
+
exitstatus = $?.exitstatus
|
|
183
|
+
rescue Errno::ENOENT
|
|
184
|
+
end
|
|
185
|
+
if exitstatus == 0 && z3version != nil then
|
|
136
186
|
progress.print_line "INFO: Using #{z3version} found in z3path=#{z3path}"
|
|
137
|
-
@
|
|
138
|
-
|
|
187
|
+
@z3path = z3path
|
|
188
|
+
@z3version = z3version
|
|
139
189
|
else
|
|
140
190
|
progress.print_line "WARN: could not find z3 in z3path=#{z3path.inspect}"
|
|
141
191
|
`which z3`
|
|
142
192
|
if $?.exitstatus == 0 then
|
|
143
193
|
z3version = `z3 --version`.chomp()
|
|
144
194
|
progress.print_line "However, #{z3version} is availlable on the PATH. Using this one."
|
|
145
|
-
@
|
|
146
|
-
|
|
195
|
+
@z3path = 'z3'
|
|
196
|
+
@z3version = z3version
|
|
147
197
|
else
|
|
148
|
-
raise RuntimeError.new("ERROR: unable to find Z3. Exiting! (Please provide its location in the config file using the key :z3path)")
|
|
198
|
+
raise RuntimeError.new("ERROR: unable to find Z3 solver. Exiting! (Please provide its location in the config file using the key :z3path)")
|
|
149
199
|
end
|
|
150
200
|
end
|
|
151
|
-
@z3in.sync = true
|
|
152
|
-
|
|
153
|
-
@sessionIdCounter = 0
|
|
154
201
|
end
|
|
155
202
|
|
|
156
|
-
def
|
|
157
|
-
|
|
158
|
-
@z3outAndErr.close()
|
|
159
|
-
Thread.kill(@z3thr)
|
|
203
|
+
def new_solver()
|
|
204
|
+
Z3Solver.new(@progress, @z3path)
|
|
160
205
|
end
|
|
161
206
|
|
|
162
|
-
def query(&block)
|
|
163
|
-
|
|
207
|
+
def query(options, &block)
|
|
208
|
+
unless options[:solverLog]
|
|
209
|
+
raise RuntimeError.new("No solverLog specified, but the option :solverLog is required!")
|
|
210
|
+
end
|
|
211
|
+
@progress.print_line("calling solver (solver-log is written to #{options[:solverLog]})")
|
|
164
212
|
|
|
165
|
-
|
|
213
|
+
session = SolverSession.new(self, options[:solverLog])
|
|
214
|
+
block.call(session)
|
|
166
215
|
|
|
167
|
-
|
|
168
|
-
result =
|
|
169
|
-
|
|
216
|
+
session.to_solver(@progress, "(check-sat)")
|
|
217
|
+
result = session.from_solver(@progress)
|
|
218
|
+
explanation = true
|
|
219
|
+
if result == "sat"
|
|
170
220
|
# model is not contradictory -> continue
|
|
171
|
-
elsif result
|
|
221
|
+
elsif result == "unsat"
|
|
172
222
|
# model is contradictory -> add a message
|
|
173
223
|
|
|
174
|
-
|
|
175
|
-
unsat_core =
|
|
176
|
-
explanation =
|
|
177
|
-
messages << {:type => :issue, :text => "Model is contradictory", :explanation => explanation}
|
|
178
|
-
solver.writeProtocolToFile("contradictions.smt2")
|
|
224
|
+
session.to_solver(@progress, "(get-unsat-core)")
|
|
225
|
+
unsat_core = session.from_solver(@progress)
|
|
226
|
+
explanation = session.parse_unsat_core(unsat_core)
|
|
179
227
|
else
|
|
180
|
-
handleSolverError(
|
|
228
|
+
session.handleSolverError(@progress, options, result)
|
|
181
229
|
end
|
|
182
230
|
|
|
183
|
-
|
|
184
|
-
solver.toSolver "(reset)"
|
|
185
|
-
solver.toSolver "(set-logic ALL)"
|
|
186
|
-
solver.toSolver "(set-option :produce-unsat-cores true)"
|
|
187
|
-
return messages
|
|
231
|
+
return explanation
|
|
188
232
|
end
|
|
189
233
|
|
|
190
234
|
# main entry point!
|
|
191
|
-
def query_model(
|
|
235
|
+
def query_model(options, &block)
|
|
192
236
|
unless options[:solverLog]
|
|
193
237
|
raise RuntimeError.new("No solverLog specified, but the option :solverLog is required!")
|
|
194
238
|
end
|
|
195
|
-
progress.print_line("calling solver (solver-log is written to #{options[:solverLog]})")
|
|
196
|
-
|
|
239
|
+
@progress.print_line("calling solver (solver-log is written to #{options[:solverLog]})")
|
|
240
|
+
session = SolverSession.new(self, options[:solverLog])
|
|
197
241
|
|
|
198
|
-
block.call(
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
result = solver.from_solver(progress)
|
|
242
|
+
block.call(session)
|
|
243
|
+
session.to_solver(@progress, "(check-sat)")
|
|
244
|
+
result = session.from_solver(@progress)
|
|
202
245
|
if result.chomp == "sat"
|
|
203
246
|
# model is not contradictory -> extract model
|
|
204
247
|
const_list = []
|
|
205
248
|
const_info = {}
|
|
206
|
-
|
|
249
|
+
session.for_each_const do |name, info|
|
|
207
250
|
const_list << name
|
|
208
251
|
const_info[name] = info
|
|
209
252
|
end
|
|
210
|
-
|
|
211
|
-
model_str =
|
|
253
|
+
session.to_solver(@progress, "(get-value (#{const_list.join(" ")}))")
|
|
254
|
+
model_str = session.read_multiline_from_solver()
|
|
212
255
|
model = {}
|
|
213
|
-
foreach_field_in_model_str(model_str,
|
|
256
|
+
Z3Solver.foreach_field_in_model_str(model_str, const_info) do |varname, value, info|
|
|
214
257
|
unless info then
|
|
215
|
-
progress.print_line "WARN: no info availlable for varname=#{varname.inspect}, value=#{value.inspect}"
|
|
258
|
+
@progress.print_line "WARN: no info availlable for varname=#{varname.inspect}, value=#{value.inspect}"
|
|
216
259
|
end
|
|
217
260
|
xpath = info[:xpath]
|
|
218
261
|
if model.has_key?(xpath) then
|
|
@@ -221,7 +264,7 @@ class Z3Solver
|
|
|
221
264
|
raise RuntimeError.new("inconsistent model: xpath #{xpath} should be a field, not a #{model[xpath][:type]}.")
|
|
222
265
|
end
|
|
223
266
|
unless value == "true" or value == "false" then
|
|
224
|
-
raise RuntimeError.new("inconsistent model value filled-value of #{xpath} should be a Bool, but was #{value.inspect}.")
|
|
267
|
+
raise RuntimeError.new("inconsistent model value: filled-value of #{xpath} should be a Bool, but was #{value.inspect}.")
|
|
225
268
|
end
|
|
226
269
|
if value == "true" then
|
|
227
270
|
model[xpath][:filled] = true
|
|
@@ -249,10 +292,13 @@ class Z3Solver
|
|
|
249
292
|
end
|
|
250
293
|
end
|
|
251
294
|
elsif varname.end_with?("-size") then
|
|
252
|
-
unless model[xpath][:type] == :list then
|
|
253
|
-
raise RuntimeError.new("inconsistent model: xpath #{xpath} should be a list, not a #{model[xpath][:type]}.")
|
|
295
|
+
unless model[xpath][:type] == :list || model[xpath][:type] == :field then
|
|
296
|
+
raise RuntimeError.new("inconsistent model: xpath #{xpath} should be a list or a field, not a #{model[xpath][:type].inspect}.")
|
|
297
|
+
end
|
|
298
|
+
if model[xpath][:type] == :field then
|
|
299
|
+
model[xpath][:type] = :list
|
|
254
300
|
end
|
|
255
|
-
model[xpath][:
|
|
301
|
+
model[xpath][:size] = Integer(value)
|
|
256
302
|
elsif varname.start_with?("struct-") && varname.end_with?("-exists") then
|
|
257
303
|
unless model[xpath][:type] == :struct then
|
|
258
304
|
raise RuntimeError.new("inconsistent model: xpath #{xpath} should be a struct, not a #{model[xpath][:type]}.")
|
|
@@ -262,7 +308,7 @@ class Z3Solver
|
|
|
262
308
|
else
|
|
263
309
|
if varname.end_with?("-filled") then
|
|
264
310
|
unless value == "true" or value == "false" then
|
|
265
|
-
raise RuntimeError.new("inconsistent model value filled-value of #{xpath} should be a Bool, but was #{value.inspect}.")
|
|
311
|
+
raise RuntimeError.new("inconsistent model value: filled-value of #{xpath} should be a Bool, but was #{value.inspect}.")
|
|
266
312
|
end
|
|
267
313
|
if value == "true" then
|
|
268
314
|
model[xpath] = { :type => :field, :filled => true }
|
|
@@ -274,7 +320,7 @@ class Z3Solver
|
|
|
274
320
|
model[xpath] = { :type => :field, :value => value }
|
|
275
321
|
elsif varname.start_with?("struct-") && varname.end_with?("-exists") then
|
|
276
322
|
unless value == "true" or value == "false" then
|
|
277
|
-
raise RuntimeError.new("inconsistent model value exists-value of struct #{xpath} should be a Bool, but was #{value.inspect}.")
|
|
323
|
+
raise RuntimeError.new("inconsistent model value: exists-value of struct #{xpath} should be a Bool, but was #{value.inspect}.")
|
|
278
324
|
end
|
|
279
325
|
model[xpath] = { :type => :struct, :exists => value }
|
|
280
326
|
elsif varname.end_with?("-size") then
|
|
@@ -286,21 +332,27 @@ class Z3Solver
|
|
|
286
332
|
elsif result.chomp == "unsat"
|
|
287
333
|
# model is contradictory -> add a message
|
|
288
334
|
|
|
289
|
-
|
|
290
|
-
unsat_core =
|
|
291
|
-
|
|
292
|
-
progress.print_line "Solver reported a contradiction! please see #{options[:solverLog]} for further information."
|
|
335
|
+
session.to_solver(@progress, "(get-unsat-core)")
|
|
336
|
+
unsat_core = session.from_solver(@progress)
|
|
337
|
+
session.writeProtocolToFile(@progress, options[:solverLog])
|
|
338
|
+
@progress.print_line "Solver reported a contradiction! please see #{options[:solverLog]} for further information."
|
|
293
339
|
return nil
|
|
294
340
|
else
|
|
295
|
-
handleSolverError(progress,
|
|
341
|
+
session.handleSolverError(@progress, options, result)
|
|
296
342
|
end
|
|
297
343
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
solver.to_solver "(set-logic ALL)"
|
|
301
|
-
solver.to_solver "(set-option :produce-unsat-cores true)"
|
|
344
|
+
return nil
|
|
345
|
+
end
|
|
302
346
|
|
|
303
|
-
|
|
347
|
+
def convert_solver_value(type, datatype, value, xpath)
|
|
348
|
+
check_type(type, value, xpath)
|
|
349
|
+
if datatype == :date then
|
|
350
|
+
return (Time.at(0).to_date + Integer(value)).to_s
|
|
351
|
+
elsif datatype == :timestamp then
|
|
352
|
+
return Time.at(Integer(value)).to_s
|
|
353
|
+
else
|
|
354
|
+
return value
|
|
355
|
+
end
|
|
304
356
|
end
|
|
305
357
|
|
|
306
358
|
def check_type(type, value, xpath)
|
|
@@ -321,24 +373,119 @@ class Z3Solver
|
|
|
321
373
|
end
|
|
322
374
|
end
|
|
323
375
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
class Z3Solver
|
|
379
|
+
|
|
380
|
+
attr_reader :z3in, :z3outAndErr
|
|
381
|
+
|
|
382
|
+
def initialize(progress, z3path)
|
|
383
|
+
@progress = progress
|
|
384
|
+
@z3in, @z3outAndErr, @z3thr = Open3.popen2e(z3path, '-in', '-smt2')
|
|
385
|
+
Process.detach(@z3thr.pid)
|
|
386
|
+
@z3in.sync = true
|
|
387
|
+
@sessionIdCounter = 0
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
def close()
|
|
391
|
+
begin
|
|
392
|
+
@z3in.close() unless @z3in.closed?
|
|
393
|
+
rescue Errno::EINVAL
|
|
394
|
+
end
|
|
395
|
+
begin
|
|
396
|
+
@z3outAndErr.close() unless @z3outAndErr.closed?
|
|
397
|
+
rescue Errno::EINVAL
|
|
398
|
+
end
|
|
399
|
+
Thread.kill(@z3thr)
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
def display_assertion(assertion, session)
|
|
403
|
+
if assertion =~ /validation_rule_(.*)_instance_(.*)/ then
|
|
404
|
+
return "Validation rule #{$1} of structure #{$2.gsub("_", "/")}"
|
|
330
405
|
else
|
|
331
|
-
|
|
406
|
+
info = session.getAssociationForAssertionID(assertion)
|
|
407
|
+
if info then
|
|
408
|
+
if info[:assertion_type] == :doc_field_filled then
|
|
409
|
+
return "Field #{info[:xpath]} contains value #{info[:value].inspect} in document"
|
|
410
|
+
elsif info[:assertion_type] == :doc_field_empty then
|
|
411
|
+
return "Field #{info[:xpath]} is empty in document"
|
|
412
|
+
elsif info[:assertion_type] == :doc_structure_exists then
|
|
413
|
+
return "Structure #{info[:xpath]} exists in document"
|
|
414
|
+
elsif info[:assertion_type] == :doc_structure_not_exists then
|
|
415
|
+
return "Structure #{info[:xpath]} does not exist in document"
|
|
416
|
+
elsif info[:assertion_type] == :constraint then
|
|
417
|
+
if info[:constraint_type] == :min then
|
|
418
|
+
return "Field #{info[:xpath]} has a minimum value of #{info[:value]}."
|
|
419
|
+
elsif info[:constraint_type] == :max then
|
|
420
|
+
return "Field #{info[:xpath]} has a maximum value of #{info[:value]}."
|
|
421
|
+
elsif info[:constraint_type] == :required then
|
|
422
|
+
return "Field #{info[:xpath]} is required."
|
|
423
|
+
else
|
|
424
|
+
p info
|
|
425
|
+
raise RuntimeError.new("encountered unknown :constraint_type #{info[:constraint_type].inspect} for assertionID #{assertion.inspect}")
|
|
426
|
+
end
|
|
427
|
+
else
|
|
428
|
+
raise RuntimeError.new("encountered unknown :assertion_type #{info[:assertion_type].inspect} for assertionID #{assertion.inspect}")
|
|
429
|
+
end
|
|
430
|
+
else
|
|
431
|
+
return assertion
|
|
432
|
+
end
|
|
433
|
+
end
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
def parse_unsat_core(unsat_core, session)
|
|
437
|
+
unless unsat_core =~ /\((.*)\)/ then
|
|
438
|
+
raise RuntimeError.new("could not parse unsat core: #{unsat_core.inspect}")
|
|
439
|
+
end
|
|
440
|
+
list = $1.split(" ")
|
|
441
|
+
|
|
442
|
+
result = "EXPLANATION:\n"
|
|
443
|
+
result += "The following values\n"
|
|
444
|
+
|
|
445
|
+
list.filter{|a| a.start_with?("doc-")}.each do |assertion|
|
|
446
|
+
result += "- #{display_assertion(assertion, session)} (#{assertion})\n"
|
|
447
|
+
end
|
|
448
|
+
result += "\nviolate the following constraints\n"
|
|
449
|
+
list.filter{|a| !a.start_with?("doc-")}.each do |assertion|
|
|
450
|
+
result += "- #{display_assertion(assertion, session)} (#{assertion})\n"
|
|
451
|
+
end
|
|
452
|
+
return result
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
def self.find_matching_bracket(str)
|
|
456
|
+
i = 0
|
|
457
|
+
open = 0
|
|
458
|
+
in_str = false
|
|
459
|
+
while open <= 0 do
|
|
460
|
+
if str[i] == '(' and !in_str then
|
|
461
|
+
open += 1
|
|
462
|
+
elsif str[i] == '"' then
|
|
463
|
+
in_str = !in_str
|
|
464
|
+
end
|
|
465
|
+
i += 1
|
|
466
|
+
end
|
|
467
|
+
while open > 0 do
|
|
468
|
+
if str[i] == '(' && !in_str then
|
|
469
|
+
open += 1
|
|
470
|
+
elsif str[i] == ')' && !in_str then
|
|
471
|
+
open -= 1
|
|
472
|
+
elsif str[i] == '"' then
|
|
473
|
+
in_str = !in_str
|
|
474
|
+
end
|
|
475
|
+
i += 1
|
|
332
476
|
end
|
|
477
|
+
str[0,i]
|
|
333
478
|
end
|
|
334
479
|
|
|
335
|
-
def foreach_field_in_model_str(str,
|
|
480
|
+
def self.foreach_field_in_model_str(str, const_info, &block)
|
|
336
481
|
unless str =~ /^\((.*)\)$/m
|
|
337
482
|
raise RuntimeError.new("could not parse model: #{str.inspect}")
|
|
338
483
|
end
|
|
339
484
|
str = $1
|
|
340
485
|
|
|
341
|
-
str.
|
|
486
|
+
while str.size() > 0 do
|
|
487
|
+
line = Z3Solver.find_matching_bracket(str)
|
|
488
|
+
str = str[line.size()..]
|
|
342
489
|
unless line =~ /\(([^\ ]*)\ (.*)\)/
|
|
343
490
|
raise RuntimeError.new("could not parse model line: #{line.inspect}")
|
|
344
491
|
end
|
|
@@ -356,8 +503,8 @@ class Z3Solver
|
|
|
356
503
|
return nil
|
|
357
504
|
end
|
|
358
505
|
|
|
359
|
-
def handleSolverError(progress,
|
|
360
|
-
|
|
506
|
+
def handleSolverError(progress, session, options, result)
|
|
507
|
+
session.writeProtocolToFile(progress, options[:solverLog])
|
|
361
508
|
raise RuntimeError.new("Solver Error: Solver returned '#{result}', for more information see #{options[:solverLog]}")
|
|
362
509
|
end
|
|
363
510
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mbt-gen
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- FM-enthusiast
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-06-18 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: nokogiri
|