mbt-gen 0.0.3 → 0.0.5

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.
Files changed (5) hide show
  1. checksums.yaml +4 -4
  2. data/lib/mbt-gen.rb +2058 -1958
  3. data/lib/progress.rb +53 -5
  4. data/lib/solver-lib.rb +572 -433
  5. metadata +6 -3
data/lib/mbt-gen.rb CHANGED
@@ -1,1958 +1,2058 @@
1
- #!/usr/bin/ruby
2
-
3
- require 'date'
4
- require 'fileutils'
5
- require 'tmpdir'
6
- require 'yaml'
7
- require 'time'
8
-
9
- require 'nokogiri'
10
-
11
- require_relative 'solver-lib'
12
- require_relative 'progress'
13
- require_relative 'regexp-to-smtlib'
14
-
15
-
16
- CHUNK_SIZE = 10000
17
- DEFAULT_Z3_PATH = "z3"
18
-
19
- CONFIG_FILE = "config.yml"
20
- OUTPUT_FILE = "result.xml"
21
- CONTINUATION_FILE = "continuation.log"
22
- COMBINATION_LOG = "combinations.log"
23
- VARS_LOG = "vars.log"
24
-
25
- OUTPUT_DIR = "result"
26
- LISTS_DIR = "lists"
27
- QUERY_DIR = "queries"
28
- PARAMETERIZED_QUERY_DIR = "parameterized_queries"
29
- XML_TEMPLATE = ""
30
-
31
- DEFAULT_CONFIG = {
32
- :z3path => "z3",
33
- :generation_system_mode => "Production",
34
- :generation_system => "Production"
35
- }
36
-
37
- USAGE = <<EOF
38
- usage:
39
-
40
- GENERATING A SINGLE MESSAGE:
41
-
42
- ruby #{$PROGRAM_NAME} -list-validation-rules
43
- - outputs a list of all validation rule names that have been specified in #{$PROGRAM_NAME}.
44
-
45
- ruby #{$PROGRAM_NAME} <options>
46
- - generates a message of the given type, which
47
- - is valid XML
48
- - adheres to the schema, and
49
- - satisfies all validation rules specified for messages of this type.
50
- where <options> is a combination of
51
- -negate <validation_rule> - negates the named validation rule. This causes the resulting message to still satisfy all other validation rules, but to violate this one.
52
- NOTE: the result can be found in #{OUTPUT_FILE}
53
-
54
- GENERATING MESSAGES FOR ALL COMBINATIONS:
55
-
56
- ruby #{$PROGRAM_NAME} -list-keys
57
- - outputs a list of all keys that have been used in #{$PROGRAM_NAME} to mark fields or structures.
58
-
59
- ruby #{$PROGRAM_NAME} -list <key>
60
- - outputs a list of all fields and structures that have been marked with <key> in #{$PROGRAM_NAME}.
61
-
62
- ruby #{$PROGRAM_NAME} -count-docs-for-key <key>
63
- - calculates the number of combinations of all fields and structures marked with <key>
64
-
65
- ruby #{$PROGRAM_NAME} -generate-docs-for-key <key> <options>
66
- - generates all valid messages of the given type that can be derived from combinations of all the fields and structures marked with <key>.
67
- where <options> is a combination of
68
- -continue - continues a previously interrupted generation process. Must use the same <key> the the interrupted generation.ARGF
69
- -max-num-docs <limit> - stops generation process after generating <limit> documents.
70
- NOTE: enumerating large combination-spaces may take a long time.
71
- - Use -count-docs-for-key to measure the size of the combination-space beforehand.
72
- - Also: you can interrupt the generation process at any time using Ctrl+C. The intermediate result is stored in the directory #{OUTPUT_DIR} and the generation process can
73
- be restarted later by adding -continue to the command line.
74
- NOTE: do not expect the number of generated documents to equal the number of combinations as combinations may not have any valid message instances (for instance when they contradict the validation rules).
75
- NOTE: the result can be found in the directory #{OUTPUT_DIR}
76
-
77
- VALIDATING THE MODEL:
78
-
79
- ruby #{$PROGRAM_NAME} -validate <file.xml>
80
- - reads in the given XML file and validates it against the model - that is, it checks whether it conforms to the specified XML schema and to the validation rules.
81
- EOF
82
-
83
- class FatalError < RuntimeError
84
- end
85
-
86
- class NormalProgramTermination < RuntimeError
87
- end
88
-
89
- def raw_field(xpath)
90
- path_str = "base"
91
- path_str = xpath.gsub("/", "-") if xpath
92
- "field-#{path_str}"
93
- end
94
-
95
- def filled_var_for_field(xpath)
96
- "#{raw_field(xpath)}-filled"
97
- end
98
-
99
- def value_var_for_field(xpath)
100
- "#{raw_field(xpath)}-value"
101
- end
102
-
103
- def exists_var_for_structure(xpath)
104
- struct_str = "base"
105
- struct_str = xpath.gsub("/", "-") if xpath
106
- "struct-#{struct_str}-exists"
107
- end
108
-
109
- def size_var_for_list(xpath)
110
- "#{raw_field(xpath)}-size"
111
- end
112
-
113
- def value_var_for_list(xpath, index)
114
- "#{raw_field(xpath)}-index-#{index}-value"
115
- end
116
-
117
- def translate_datatype_to_SMTLIB(datatype)
118
- case datatype
119
- when :string
120
- return "String"
121
- when :int
122
- return "Int"
123
- when :bool
124
- return "Bool"
125
- when :date
126
- return "Int"
127
- when :timestamp
128
- return "Int"
129
- when :enum
130
- return "String"
131
- end
132
- end
133
-
134
- def translate_value_to_SMTLIB(value)
135
- if value.is_a?(Integer) then
136
- return value.to_s
137
- elsif value.is_a?(String) then
138
- return value.inspect
139
- elsif value.is_a?(Date) then
140
- return (value - (Time.at(0).to_date)).to_i # days since 1970.01.01
141
- elsif value.is_a?(Time) then
142
- return value.to_i
143
- else
144
- raise RuntimeError.new("cannot translate value to SMTLIB: #{value.inspect}")
145
- end
146
- end
147
-
148
- def min_constraint_for_expr(expr, lower_bound)
149
- "(>= #{expr} #{lower_bound})"
150
- end
151
-
152
- def max_constraint_for_expr(expr, upper_bound)
153
- "(<= #{expr} #{upper_bound})"
154
- end
155
-
156
- def regex_constraint_for_expr(expr, regex)
157
- "(str.in.re #{expr} #{Regexp_to_SMTLIB.translate_regexp_to_SMTLIB(regex)})"
158
- end
159
-
160
- def maxLength_contraint_for_expr(expr, maxLength)
161
- "(<= (str.len #{expr}) #{maxLength})"
162
- end
163
-
164
- def oneOf_contraint_for_expr(expr, values)
165
- list = values.map do |val|
166
- "(= #{expr} \"#{val}\")"
167
- end
168
- "(or #{list.join(" ")})"
169
- end
170
-
171
- def enum_constraint_for_expr(expr, values)
172
- "(or #{values.map{|x| "(= #{expr} #{translate_value_to_SMTLIB(x)})"}.join(" ")})"
173
- end
174
-
175
- def translate_field_def_to_SMTLIB_constraints(progress, solver, fielddef, xpath)
176
- varname = raw_field(xpath)
177
- if fielddef[:optional] == nil || fielddef[:optional] == false then
178
- # field is required
179
- assertionID = "required-#{varname}"
180
- info = {
181
- :assertion_type => :constraint,
182
- :xpath => xpath,
183
- :constraint_type => :required
184
- }
185
- solver.associateAssertionIDWith(assertionID, info)
186
- solver.to_solver(progress, "(assert (! #{varname}-filled :named #{assertionID}))")
187
- end
188
- datatype = fielddef[:datatype]
189
- case datatype
190
- when :int
191
- if fielddef[:min] then
192
- assertionID = "min-#{varname}"
193
- info = {
194
- :assertion_type => :constraint,
195
- :xpath => xpath,
196
- :datatype => :int,
197
- :value => fielddef[:min],
198
- :constraint_type => :min
199
- }
200
- solver.associateAssertionIDWith(assertionID, info)
201
- solver.to_solver(progress, "(assert (! (=> #{varname}-filled #{min_constraint_for_expr("#{varname}-value", translate_value_to_SMTLIB(fielddef[:min]))}) :named #{assertionID}))")
202
- end
203
- if fielddef[:max] then
204
- assertionID = "max-#{varname}"
205
- info = {
206
- :assertion_type => :constraint,
207
- :xpath => xpath,
208
- :datatype => :int,
209
- :value => fielddef[:max],
210
- :constraint_type => :max
211
- }
212
- solver.associateAssertionIDWith(assertionID, info)
213
- solver.to_solver(progress, "(assert (! (=> #{varname}-filled #{max_constraint_for_expr("#{varname}-value", translate_value_to_SMTLIB(fielddef[:max]))}) :named #{assertionID}))")
214
- end
215
- when :string
216
- if fielddef[:regex] then
217
- assertionID = "regex-#{varname}"
218
- info = {
219
- :assertion_type => :constraint,
220
- :xpath => xpath,
221
- :datatype => :string,
222
- :pattern => fielddef[:regex],
223
- :constraint_type => :regex
224
- }
225
- solver.associateAssertionIDWith(assertionID, info)
226
- solver.to_solver(progress, "(assert (! (=> #{varname}-filled #{regex_constraint_for_expr("#{varname}-value", fielddef[:regex])}) :named #{assertionID}))")
227
- end
228
- # TODO: minLength?
229
- if fielddef[:maxLength] then
230
- assertionID = "maxLength-#{varname}"
231
- info = {
232
- :assertion_type => :constraint,
233
- :xpath => xpath,
234
- :datatype => :string,
235
- :value => fielddef[:maxLength],
236
- :constraint_type => :maxLength
237
- }
238
- solver.associateAssertionIDWith(assertionID, info)
239
- solver.to_solver(progress, "(assert (! (=> #{varname}-filled #{maxLength_contraint_for_expr("#{varname}-value", fielddef[:maxLength])}) :named #{assertionID}))")
240
- end
241
- if fielddef[:from_list] then
242
- assertionID = "oneOf-#{varname}"
243
- list_filename = "#{LISTS_DIR}/#{fielddef[:from_list]}"
244
- info = {
245
- :assertion_type => :constraint,
246
- :xpath => xpath,
247
- :datatype => :string,
248
- :filename => list_filename,
249
- :constraint_type => :from_list
250
- }
251
- unless File.exist?(list_filename)
252
- raise RuntimeError.new("could not find list file #{list_filename}.")
253
- end
254
- values = File.read(list_filename).split("\n").map{|v| v.chomp()}
255
- solver.associateAssertionIDWith(assertionID, info)
256
- solver.to_solver(progress, "(assert (! (=> #{varname}-filled #{oneOf_contraint_for_expr("#{varname}-value", values)}) :named #{assertionID}))")
257
- end
258
- when :enum
259
- assertionID = "enum-#{varname}"
260
- info = {
261
- :assertion_type => :constraint,
262
- :xpath => xpath,
263
- :datatype => :enum,
264
- :values => fielddef[:values],
265
- :constraint_type => :values
266
- }
267
- solver.associateAssertionIDWith(assertionID, info)
268
- solver.to_solver(progress, "(assert (! (=> #{varname}-filled #{enum_constraint_for_expr("#{varname}-value", fielddef[:values])}) :named #{assertionID}))")
269
- end
270
- end
271
-
272
- def translate_list_def_to_SMTLIB_constraints(progress, solver, fielddef, varname)
273
- datatype = fielddef[:datatype]
274
- case datatype
275
- when :int
276
- if fielddef[:min] then
277
- fielddef[:model_maxLength].times do |i|
278
- solver.to_solver(progress, "(assert (! (=> (< #{i} #{varname}-size) #{min_constraint_for_expr("#{varname}-index-#{i}-value", translate_value_to_SMTLIB(fielddef[:min]))}) :named list-min-#{varname}-list-index-#{i}))")
279
- end
280
- end
281
- if fielddef[:max] then
282
- fielddef[:model_maxLength].times do |i|
283
- solver.to_solver(progress, "(assert (! (=> (< #{i} #{varname}-size) #{max_constraint_for_expr("#{varname}-index-#{i}-value", translate_value_to_SMTLIB(fielddef[:max]))}) :named list-max-#{varname}-list-index-#{i}))")
284
- end
285
- end
286
- when :string
287
- if fielddef[:regex] then
288
- fielddef[:model_maxLength].times do |i|
289
- solver.to_solver(progress, "(assert (! (=> (< #{i} #{varname}-size) #{regex_constraint_for_expr("#{varname}-index-#{i}-value", fielddef[:regex])}) :named list-regex-#{varname}-index-#{i}))")
290
- end
291
- end
292
- # TODO: minLength?
293
- if fielddef[:maxLength] then
294
- fielddef[:model_maxLength].times do |i|
295
- solver.to_solver(progress, "(assert (! (=> (< #{i} #{varname}-size) #{maxLength_contraint_for_expr("#{varname}-index-#{i}-value", fielddef[:maxLength])}) :named list-maxLength-#{varname}-index-#{i}))")
296
- end
297
- end
298
- when :enum
299
- fielddef[:model_maxLength].times do |i|
300
- solver.to_solver(progress, "(assert (! (=> (< #{i} #{varname}-size) #{enum_constraint_for_expr("#{varname}-index-#{i}-value", fielddef[:values])}) :named list-enum-#{varname}-index-#{i}))")
301
- end
302
- end
303
- end
304
-
305
- def default_value_for_fielddef(fielddef)
306
- datatype = fielddef[:datatype]
307
- case datatype
308
- when :int
309
- return "0"
310
- when :string
311
- return "".inspect
312
- when :date
313
- return "0"
314
- when :timestamp
315
- return "0"
316
- when :enum
317
- return "".inspect
318
- else
319
- raise RuntimeError.new("unknown datatype: #{datatype.inspect}")
320
- end
321
- end
322
-
323
- def translate_structure_to_SMTLIB(progress, solver, struct, hash = {})
324
- prefix = hash[:prefix]
325
- config = hash[:config]
326
- options = hash[:options]
327
- translate_substructure_to_SMTLIB(progress, solver, struct, hash)
328
- translate_validation_rules_to_SMTLIB(progress, solver, struct, prefix, config, options)
329
- if options[:add_SMTLIB] then
330
- solver.to_solver(progress, "; --- additional SMTLIB Code: ---")
331
- solver.to_solver(progress, options[:add_SMTLIB])
332
- end
333
- end
334
-
335
- def translate_substructure_to_SMTLIB(progress, solver, struct, hash = {})
336
- prefix = hash[:prefix]
337
- struct_varname = hash[:struct_varname]
338
- config = hash[:config]
339
- options = hash[:options]
340
- solver.to_solver(progress, "; --- Structure: ---")
341
- struct.each do |field, definition|
342
- if field == :additional_smtlib then
343
- solver.to_solver(progress, "; additional SMTLIB of structure #{prefix}")
344
- solver.to_solver(progress, translate_validation_rule_to_SMTLIB(definition, struct, prefix, config, options, "<additional SMTLIB of structure #{prefix}>"))
345
- elsif field == :validation_rules || field == :predicates || field == :parent_link || field == :struct_xpath_prefix
346
- next
347
- else
348
- solver.to_solver(progress, "; #{field.to_s}")
349
- if definition[:type] == :field then
350
- display_prefix = ""
351
- display_prefix = "#{prefix}/" if prefix
352
- xpath = field.to_s
353
- xpath = "#{prefix}/#{xpath}" if prefix
354
- solver.declare_const(progress, filled_var_for_field(xpath), "Bool", {:xpath => xpath})
355
- solver.declare_const(progress, value_var_for_field(xpath), translate_datatype_to_SMTLIB(definition[:datatype]), {:xpath => xpath, :datatype => definition[:datatype]})
356
- solver.to_solver(progress, "(assert (! (=> (not #{filled_var_for_field(xpath)}) (= #{value_var_for_field(xpath)} #{default_value_for_fielddef(definition)})) :named empty-field-has-no-value-for-#{raw_field(xpath)}))")
357
- if (struct_varname)
358
- solver.to_solver(progress, "(assert (! (=> (not #{struct_varname}) (not #{filled_var_for_field(xpath)})) :named empty-struct-has-no-fields-for-#{raw_field(xpath)}))")
359
- end
360
- translate_field_def_to_SMTLIB_constraints(progress, solver, definition, xpath)
361
- elsif definition[:type] == :list then
362
- display_prefix = ""
363
- display_prefix = "#{prefix}/" if prefix
364
- xpath = field.to_s
365
- xpath = "#{prefix}/#{xpath}" if prefix
366
- solver.declare_const(progress, filled_var_for_field(xpath), "Bool", {:xpath => xpath})
367
- solver.declare_const(progress, size_var_for_list(xpath), "Int", {:xpath => xpath})
368
- solver.to_solver(progress, "(assert (! (=> (not #{filled_var_for_field(xpath)}) (= #{size_var_for_list(xpath)} 0)) :named empty-list-field-has-zero-size-#{raw_field(xpath)}))")
369
- definition[:model_maxLength].times do |i|
370
- solver.declare_const(progress, value_var_for_list(xpath, i), translate_datatype_to_SMTLIB(definition[:datatype]), {:xpath => xpath, :xpath_element => definition[:xpath_element], :index => i, :datatype => definition[:datatype]})
371
- solver.to_solver(progress, "(assert (! (=> (<= #{size_var_for_list(xpath)} #{i}) (= #{value_var_for_list(xpath, i)} #{default_value_for_fielddef(definition)})) :named empty-list-field-has-no-value-for-#{raw_field(xpath)}-index-#{i}))")
372
- end
373
- if (struct_varname)
374
- solver.to_solver(progress, "(assert (! (=> (not #{struct_varname}) (= #{size_var_for_list(xpath)} 0)) :named empty-struct-has-no-fields-for-#{raw_field(xpath)}))")
375
- end
376
- translate_list_def_to_SMTLIB_constraints(progress, solver, definition, raw_field(xpath))
377
- elsif definition[:type] == :structure then
378
- xpath = field.to_s
379
- xpath = "#{prefix}/#{xpath}" if prefix
380
- solver.declare_const(progress, exists_var_for_structure(xpath), "Bool", {:xpath => xpath})
381
- if (struct_varname)
382
- solver.to_solver(progress, "(assert (! (=> (not #{struct_varname}) (not #{exists_var_for_structure(xpath)})) :named empty-struct-has-no-substructure-#{raw_field(xpath)}))")
383
- end
384
- translate_substructure_to_SMTLIB(progress, solver, definition[:ref], :prefix => xpath, :struct_varname => exists_var_for_structure(xpath), :options => options, :config => config)
385
- else
386
- next
387
- end
388
- end
389
- end
390
- end
391
-
392
- def resolve_field_path(path, relative_to_struct, prefix)
393
- current = relative_to_struct
394
- result = nil
395
- path.split("/").each do |segment|
396
- unless current then
397
- raise RuntimeError.new("could not resolve segment #{segment.inspect} of field path: #{path.inspect}: resolution worked up until #{result.inspect}, but then the last segment was a field or list.")
398
- end
399
- field_def = current[segment.to_sym]
400
- unless field_def then
401
- raise RuntimeError.new("could not resolve segment #{segment.inspect} of field path: #{path.inspect}: structure #{prefix.inspect} does not have a member #{segment.to_sym.inspect}.")
402
- end
403
- if field_def[:type] == :structure then
404
- unless field_def[:ref] then
405
- raise RuntimeError.new("could not resolve field path: #{path.inspect}: substructure #{segment.inspect} of structure #{prefix.inspect} does not have a :ref.")
406
- end
407
- current = field_def[:ref]
408
- if prefix then
409
- prefix = "#{prefix}/#{segment}"
410
- else
411
- prefix = segment
412
- end
413
- elsif field_def[:type] == :field then
414
- current = nil
415
- result = segment
416
- if prefix
417
- result = "#{prefix}/#{result}"
418
- end
419
- elsif field_def[:type] == :list then
420
- current = nil
421
- result = segment
422
- if prefix
423
- result = "#{prefix}/#{result}"
424
- end
425
- end
426
- end
427
- return result
428
- end
429
-
430
- def resolve_field_def(path, relative_to_struct, prefix)
431
- current = relative_to_struct
432
- result = nil
433
- path.split("/").each do |segment|
434
- unless current then
435
- raise RuntimeError.new("could not resolve segment #{segment.inspect} of field path: #{path.inspect}: resolution worked up until #{result.inspect}, but then the last segment was a field or list.")
436
- end
437
- field_def = current[segment.to_sym]
438
- unless field_def then
439
- raise RuntimeError.new("could not resolve segment #{segment.inspect} of field path: #{path.inspect}: structure #{prefix.inspect} does not have a member #{segment.to_sym.inspect}.")
440
- end
441
- if field_def[:type] == :structure then
442
- unless field_def[:ref] then
443
- raise RuntimeError.new("could not resolve field path: #{path.inspect}: substructure #{segment.inspect} of structure #{prefix.inspect} does not have a :ref.")
444
- end
445
- current = field_def[:ref]
446
- if prefix then
447
- prefix = "#{prefix}/#{segment}"
448
- else
449
- prefix = segment
450
- end
451
- elsif field_def[:type] == :field then
452
- current = nil
453
- result = field_def
454
- elsif field_def[:type] == :list then
455
- current = nil
456
- result = field_def
457
- end
458
- end
459
- return result
460
- end
461
-
462
- def resolve_struct_path(path, relative_to_struct, prefix)
463
- current = relative_to_struct
464
- result = nil
465
- path.split("/").each do |segment|
466
- unless current then
467
- raise RuntimeError.new("could not resolve field path: #{path.inspect} relative to struct #{relative_to_struct.inspect}")
468
- end
469
- if segment == "." then
470
- result = current[:struct_xpath_prefix]
471
- elsif segment == ".." then
472
- current = current[:parent_link]
473
- if prefix then
474
- prefix_segs = prefix.split("/")
475
- prefix = prefix_segs[0,prefix_segs.size()-1].join("/")
476
- else
477
- prefix = current[:struct_xpath_prefix]
478
- end
479
- else
480
- field_def = current[segment.to_sym]
481
- unless field_def then
482
- raise RuntimeError.new("could not resolve segment #{segment.inspect} of field path: #{path.inspect} relative to struct #{relative_to_struct.inspect}")
483
- end
484
- if field_def[:type] == :structure then
485
- current = field_def[:ref]
486
- if prefix then
487
- prefix = "#{prefix}/#{segment}"
488
- else
489
- prefix = segment
490
- end
491
- result = prefix
492
- end
493
- end
494
- end
495
- return result
496
- end
497
-
498
- def resolve_struct_def(path, relative_to_struct, prefix, context_desc = "")
499
- current = relative_to_struct
500
- result = nil
501
- path.split("/").each do |segment|
502
- unless current then
503
- raise RuntimeError.new("could not resolve field path: #{path.inspect} relative to struct #{relative_to_struct.inspect} #{context_desc}")
504
- end
505
- if segment == "." then
506
- result = current
507
- elsif segment == ".." then
508
- current = current[:parent_link]
509
- if prefix then
510
- prefix_segs = prefix.split("/")
511
- prefix = prefix_segs[0,prefix_segs.size()-1].join("/")
512
- else
513
- prefix = current[:struct_xpath_prefix]
514
- end
515
- else
516
- field_def = current[segment.to_sym]
517
- unless field_def then
518
- raise RuntimeError.new("could not resolve segment #{segment.inspect} of field path: #{path.inspect} relative to struct #{relative_to_struct.inspect} #{context_desc}")
519
- end
520
- if field_def[:type] == :structure then
521
- current = field_def[:ref]
522
- if prefix then
523
- prefix = "#{prefix}/#{segment}"
524
- else
525
- prefix = segment
526
- end
527
- result = current
528
- end
529
- end
530
- end
531
- return result
532
- end
533
-
534
- def translate_validation_rules_to_SMTLIB(progress, solver, struct, prefix, config, options)
535
- solver.to_solver(progress, "; --- Validation Rules ---")
536
- translate_validation_rules_for_structure_to_SMTLIB(progress, solver, struct, prefix, config, options)
537
- struct.each_pair do |key, value|
538
- next if key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
539
- if value[:type] == :structure then
540
- if prefix then
541
- new_prefix = "#{prefix.to_s}/#{key}"
542
- else
543
- new_prefix = key.to_s
544
- end
545
- translate_validation_rules_to_SMTLIB(progress, solver, value[:ref], new_prefix, config, options)
546
- end
547
- end
548
- end
549
-
550
- def translate_validation_rule_to_SMTLIB(rule, context_struct, prefix, config, options, rule_name)
551
- date_now = options[:date_now]
552
- timestamp_now = options[:timestamp_now]
553
- return rule.gsub(/FILLED\[[^\]]*\]/) do |m|
554
- unless m =~ /FILLED\[([^\]]*)\]/ then
555
- raise RuntimeError.new("could not parse FILLED[...]: #{m.inspect} in validation rule #{rule_name}")
556
- end
557
- field = resolve_field_path($1, context_struct, prefix)
558
- filled_var_for_field(field)
559
- end
560
- .gsub(/VALUE\[[^\]]*\]/) do |m|
561
- unless m =~ /VALUE\[([^\]]*)\]/ then
562
- raise RuntimeError.new("could not parse VALUE[...]: #{m.inspect} in validation rule #{rule_name}")
563
- end
564
- field = resolve_field_path($1, context_struct, prefix)
565
- value_var_for_field(field)
566
- end
567
- .gsub(/EXIST\[[^\]]*\]/) do |m|
568
- unless m =~ /EXIST\[([^\]]*)\]/ then
569
- raise RuntimeError.new("could not parse EXIST[...]: #{m.inspect} in validation rule #{rule_name}")
570
- end
571
- s = resolve_struct_path($1, context_struct, prefix)
572
- exists_var_for_structure(s)
573
- end
574
- .gsub(/SIZE\[[^\]]*\]/) do |m|
575
- unless m =~ /SIZE\[([^\]]*)\]/ then
576
- raise RuntimeError.new("could not parse SIZE[...]: #{m.inspect} in validation rule #{rule_name}")
577
- end
578
- l = resolve_field_path($1, context_struct, prefix)
579
- size_var_for_list(l)
580
- end
581
- .gsub(/LIST_CONTAINS\[[^\]]*,[^\]]*\]/) do |m|
582
- unless m =~ /LIST_CONTAINS\[([^\]]*),([^\]]*)\]/ then
583
- raise RuntimeError.new("could not parse LIST_CONTAINS[...]: #{m.inspect} in validation rule #{rule_name}")
584
- end
585
- l = resolve_field_path($1, context_struct, prefix)
586
- l_def = resolve_field_def($1, context_struct, prefix)
587
- value = $2
588
- terms = []
589
- l_def[:model_maxLength].times do |i|
590
- terms << "(and (< #{i} #{size_var_for_list(l)}) (= #{value_var_for_list(l, i)} #{value}))"
591
- end
592
- "(or #{terms.join(" ")})"
593
- end
594
- .gsub(/LIST_VALUE_FIRST_ELEMENT\[[^\]]*\]/) do |m|
595
- unless m =~ /LIST_VALUE_FIRST_ELEMENT\[([^\]]*)\]/ then
596
- raise RuntimeError.new("could not parse LIST_VALUE_FIRST_ELEMENT[...]: #{m.inspect} in validation rule #{rule_name}")
597
- end
598
- xpath = $1
599
- l = resolve_field_path(xpath, context_struct, prefix)
600
- value_var_for_list(l, 0)
601
- end
602
- .gsub("[Date.now]", date_now.to_s)
603
- .gsub("[Timestamp.now]", timestamp_now.to_s)
604
- .gsub(/PREDICATE\[[^\]]*,[^\]]*\]/) do |m|
605
- unless m =~ /PREDICATE\[([^\]]*),([^\]]*)\]/ then
606
- raise RuntimeError.new("could not parse PREDICATE[...]: #{m.inspect} in validation rule #{rule_name}")
607
- end
608
- path = $1
609
- key = $2.strip().to_sym
610
- sdef = resolve_struct_def(path, context_struct, prefix, "in validation rule #{rule_name}")
611
- unless sdef
612
- raise RuntimeError.new("could not resolve struct path #{path.inspect} in validation rule #{rule_name}")
613
- end
614
- preds = sdef[:predicates]
615
- mdk = preds[key]
616
- if mdk then
617
- if mdk[:smtlib] then
618
- translate_validation_rule_to_SMTLIB(mdk[:smtlib], sdef, sdef[:struct_xpath_prefix], config, options, "#{rule_name}.predicate.#{key}[#{sdef[:struct_xpath_prefix]}]" )
619
- else
620
- raise RuntimeError.new("Predicate #{key} for structure #{sdef[:struct_xpath_prefix]} does not have an SMTLIB definition, but is referenced in validation rule #{rule_name}")
621
- end
622
- else
623
- raise RuntimeError.new("No predicate #{key.inspect} defined for structure #{sdef[:struct_xpath_prefix]}, but one referenced in validation rule #{rule_name}")
624
- end
625
- end
626
- .gsub(/CONFIG\[[^\]]*\]/) do |m|
627
- unless m =~ /CONFIG\[([^\]]*)\]/ then
628
- raise RuntimeError.new("could not parse LIST_CONTAINS[...]: #{m.inspect} in validation rule #{rule_name}")
629
- end
630
- key = $1.to_sym
631
- if config.has_key?(key) then
632
- "\"#{config[key]}\""
633
- else
634
- raise RuntimeError.new("unknown CONFIG-key #{key.inspect} encountered in validation rule #{rule_name}" )
635
- end
636
- end
637
- .gsub(/FIELD_IN_QUERY\[[^\]]*\]/) do |m|
638
- unless m =~ /FIELD_IN_QUERY\[([^\]]*),([^\]]*)\]/ then
639
- raise RuntimeError.new("could not parse FIELD_IN_QUERY[..,..]: #{m.inspect} in validation rule #{rule_name}")
640
- end
641
- xpath = $1
642
- query_filename = $2
643
- fpath = resolve_field_path(xpath, context_struct, prefix)
644
- fdef = resolve_field_def(xpath, context_struct, prefix)
645
- values = File.read("#{QUERY_DIR}/#{query_filename.strip()}").split("\n")
646
- if fdef[:datatype] == :string then
647
- terms = values.filter{|l| !l.start_with?("#")}.map do |val|
648
- "(= #{value_var_for_field(fpath)} \"#{val}\")"
649
- end
650
- elsif fdef[:datatype] == :int then
651
- terms = values.filter{|l| !l.start_with?("#")}.map do |val|
652
- "(= #{value_var_for_field(fpath)} #{Integer(val)})"
653
- end
654
- else
655
- raise RuntimeError.new("FIELD_IN_QUERY[] was applied to a field of datatype #{fdef[:datatype].inspect}. However, it can only be applied to fields of datatype string or int.")
656
- end
657
- "(or #{terms.join(" ")})"
658
- end
659
- .gsub(/FIELD_IN_SMTLIB_PARAMETERIZED_QUERY\[[^\]]*\]/) do |m|
660
- unless m =~ /FIELD_IN_SMTLIB_PARAMETERIZED_QUERY\[([^\]]*),([^\]]*),([^\]]*)\]/ then
661
- raise RuntimeError.new("could not parse FIELD_IN_SMTLIB_PARAMETERIZED_QUERY[..,..,..]: #{m.inspect} in validation rule #{rule_name}")
662
- end
663
- field_xpath = $1
664
- param_smtlib = $2
665
- query_filename = $3
666
- fpath = resolve_field_path(field_xpath.strip(), context_struct, prefix)
667
- fdef = resolve_field_def(field_xpath.strip(), context_struct, prefix)
668
- query_result = YAML.load_file("#{PARAMETERIZED_QUERY_DIR}/#{query_filename.strip()}")
669
- terms = []
670
- if fdef[:datatype] == :string then
671
- terms << "(or #{query_result.keys.map { |key| "(= #{param_smtlib} \"#{key}\")" }.join(" ")})"
672
- query_result.each_pair do |key, values|
673
- terms << "(=> (= #{param_smtlib} \"#{key}\") (or #{values.map{ |v| "(= #{value_var_for_field(fpath)} \"#{v}\")" }.join(" ")}))"
674
- end
675
- else
676
- raise RuntimeError.new("FIELD_IN_SMTLIB_PARAMETERIZED_QUERY[] was applied to a field of datatype #{fdef[:datatype].inspect}. However, it can only be applied to fields of datatype string.")
677
- end
678
- "(and #{terms.join(" ")})"
679
- end
680
- .gsub(/CONFIG_IN_QUERY\[[^\]]*\]/) do |m|
681
- unless m =~ /CONFIG_IN_QUERY\[([^\]]*),([^\]]*)\]/ then
682
- raise RuntimeError.new("could not parse CONFIG_IN_QUERY[..,..]: #{m.inspect} in validation rule #{rule_name}")
683
- end
684
- key = $1.to_sym
685
- unless config.has_key?(key) then
686
- raise RuntimeError.new("unknown CONFIG key #{key.inspect} encountered in validation rule #{rule_name}")
687
- end
688
- query_filename = $2
689
- values = File.read("#{QUERY_DIR}/#{query_filename.strip()}").split("\n")
690
- terms = values.filter{|l| !l.start_with?("#")}.map do |val|
691
- "(= \"#{config[key]}\" \"#{val}\")"
692
- end
693
- "(or #{terms.join(" ")})"
694
- end
695
- end
696
-
697
- def translate_validation_rules_for_structure_to_SMTLIB(progress, solver, struct, prefix, config, options)
698
- return unless struct[:validation_rules]
699
-
700
- solver.to_solver(progress, "; --- Validation Rules: ---")
701
- negated_rules = options[:negated_validation_rules] || []
702
- solver.to_solver(progress, "; negated rules: #{negated_rules.empty? ? "<none>" : negated_rules.join(", ")}")
703
-
704
- rules = struct[:validation_rules]
705
- rules.each do |rule_name, rule|
706
- rule = rule[:smtlib] # ignore the text representation
707
- next unless rule # warning about this omission is the job of validate_model()
708
- rule_SMTLIB = translate_validation_rule_to_SMTLIB(rule, struct, prefix, config, options, rule_name)
709
- if negated_rules.include?(rule_name) then
710
- rule_SMTLIB = "(not #{rule_SMTLIB})"
711
- end
712
- solver.to_solver(progress, "(assert (! #{rule_SMTLIB} :named validation_rule_#{rule_name}_instance_#{prefix.gsub("/", "_")}))")
713
- end
714
- end
715
-
716
- def parse_field_value(fielddef, value)
717
- value = value.gsub("\n", "")
718
- datatype = fielddef[:datatype]
719
- case datatype
720
- when :int
721
- return Integer(value)
722
- when :string
723
- return value
724
- when :enum
725
- return value
726
- when :date
727
- return Date.parse(value)
728
- when :timestamp
729
- return Time.new(value)
730
- end
731
- end
732
-
733
- def solver_value_to_string(solver_value)
734
- if solver_value =~ /\"(.*)\"/ then
735
- return $1
736
- else
737
- return solver_value
738
- end
739
- end
740
-
741
- def build_document_from_model(progress, struct, model)
742
- doc = Nokogiri::XML(XML_TEMPLATE)
743
- add_model_struct_to_doc(progress, struct, doc, model)
744
- return doc
745
- end
746
-
747
- def add_model_struct_to_doc(progress, struct, doc, model, prefix = nil)
748
- struct.each do |key, value|
749
- next if key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
750
- if prefix then
751
- xpath = "#{prefix}/#{key}"
752
- else
753
- xpath = key.to_s
754
- end
755
- model_value = model[xpath]
756
- if value[:type] == :field then
757
- unless model_value then
758
- raise RuntimeError.new("xpath '#{xpath}' not contained in model.")
759
- end
760
- if model_value[:filled] == true || model_value[:filled] == "true" then
761
- doc.add_child("<#{key}>#{solver_value_to_string(model_value[:value])}</#{key}>")
762
- end
763
- elsif value[:type] == :list then
764
- node = doc.add_child("<#{key}></#{key}>")
765
- if node.is_a?(Nokogiri::XML::NodeSet) then
766
- node = node.first
767
- end
768
- xpath_element = value[:xpath_element]
769
- if Integer(model_value[:size]) > 0 then
770
- model_value[:value].slice(0, Integer(model_value[:size])).each do |mvalue|
771
- node.add_child("<#{xpath_element}>#{solver_value_to_string(mvalue)}</#{xpath_element}>")
772
- end
773
- end
774
- elsif value[:type] == :structure then
775
- unless value[:ref] then
776
- raise RuntimeError.new("struct is broken: :structure #{key.inspect} does not have a :ref")
777
- end
778
- if model_value[:exists] == true || model_value[:exists] == "true" then
779
- node = doc.add_child("<#{key}></#{key}>")
780
- if node.is_a?(Nokogiri::XML::NodeSet) then
781
- node = node.first
782
- end
783
- add_model_struct_to_doc(progress, value[:ref], node, model, xpath)
784
- end
785
- else
786
- raise RuntimeError.new("struct is broken: encountered unknown :type #{value[:type].inspect}")
787
- end
788
- end
789
- end
790
-
791
- def collect_validation_rule_names(struct)
792
- result = []
793
- struct.each do |key, value|
794
- next if key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
795
- if key == :validation_rules
796
- value.each do |key, value|
797
- result << key
798
- end
799
- else
800
- if value[:type] == :structure
801
- result += collect_validation_rule_names(value[:ref])
802
- end
803
- end
804
- end
805
- return result
806
- end
807
-
808
- def set_standard_options(progress, solver)
809
- solver.to_solver(progress, "(set-option :produce-unsat-cores true) ; enable generation of unsat cores")
810
- solver.to_solver(progress, "(set-option :smt.core.minimize true) ; ensure that unsat cores are minimal")
811
- solver.to_solver(progress, "")
812
- end
813
-
814
- def generate_doc(progress, message, output_filename, config, options)
815
- log = options[:log] || nil
816
- log.puts "generating doc #{output_filename}" if log
817
- options[:solverLog] = options[:solverLog] || "#{output_filename}.smt2"
818
-
819
- z3path = config[:z3path] || DEFAULT_Z3_PATH
820
- z3 = Z3Solver.new(progress, z3path)
821
- begin
822
- model = z3.query_model(progress, options) do |solver|
823
- set_standard_options(progress, solver)
824
- log.puts "translating structure" if log
825
- translate_structure_to_SMTLIB(progress, solver, message, :options => options, :config => config)
826
- solver.to_solver(progress, "")
827
- end
828
- ensure
829
- z3.close()
830
- end
831
-
832
- if model then
833
- result_doc = build_document_from_model(progress, message, model)
834
- dir = File.dirname(output_filename)
835
- FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
836
- File.open(output_filename, "w") do |f|
837
- f.puts result_doc.to_xml()
838
- end
839
- progress.print_line "DONE. Generated XML was written to #{output_filename}, SMTLIB log was written to #{options[:solverLog]}"
840
- return true
841
- else
842
- progress.print_line "FAILED. No XML document was generated. For more details, please see the SMTLIB log in #{options[:solverLog]}"
843
- return false
844
- end
845
- end
846
-
847
- def z3ValueToRubyValue(value)
848
- if value =~ /\"(.*)\"/ then # string
849
- return $1
850
- elsif value =~ /\d+/ then # int
851
- return Integer(value)
852
- elsif value == "true"
853
- return true
854
- elsif value == "false" then
855
- return false
856
- else
857
- raise RuntimeError.new("enountered unknown type of value in z3 model: #{value.inspect}")
858
- end
859
- end
860
-
861
- def extract_key_field_values_from_model(key_fields, model)
862
- result = {}
863
- key_fields.each do |xpath, fieldDesc|
864
- fieldInfo = model[xpath]
865
- if fieldDesc[:type] == :field then
866
- if fieldInfo[:filled] then
867
- result[xpath] = z3ValueToRubyValue(fieldInfo[:value])
868
- else
869
- result[xpath] = nil
870
- end
871
- elsif fieldDesc[:type] == :struct || fieldDesc[:type] == :structure then
872
- if fieldInfo.has_key?(:exists) then
873
- result[xpath] = z3ValueToRubyValue(fieldInfo[:exists])
874
- else
875
- result[xpath] = false
876
- end
877
- else
878
- raise RuntimeError.new("encountered unknown :type in solver model: #{fieldDesc[:type].inspect}")
879
- end
880
- end
881
- return result
882
- end
883
-
884
- def all_values_for_field(field)
885
- unless field[:type] == :field then
886
- raise RuntimeError.new("something that is not a field definition was passed to all_values_for_field(): #{field.inspect}")
887
- end
888
- unless field[:datatype] == :enum then
889
- raise RuntimeError.new("all_values_for_field() is only applicable to fields of datatype enum! not #{field[:datatype].inspect}.")
890
- end
891
- values = field[:values].dup
892
- if field[:optional] == true then
893
- values << nil
894
- end
895
- return values
896
- end
897
-
898
- def calc_combinations_for_key_fields(key_fields)
899
- count = 1
900
- key_fields.each do |field|
901
- if field[1][:type] == :field then
902
- if field[1][:datatype] == :enum then
903
- num_values = field[1][:values].size
904
- num_values += 1 if field[1][:optional]
905
- elsif field[1][:datatype] == :int then
906
- if field[1].has_key?(:min) and field[1].has_key?(:max) and field[1][:min] <= field[1][:max] then
907
- if field[1][:optional] then
908
- num_values = 2 + field[1][:max] - field[1][:min]
909
- else
910
- num_values = 1 + field[1][:max] - field[1][:min]
911
- end
912
- else
913
- return "potentially infinite"
914
- end
915
- elsif field[1][:datatype] == :string then
916
- return "potentially infinite"
917
- end
918
- elsif field[1][:type] == :structure then
919
- num_values = 2
920
- end
921
- count *= num_values
922
- end
923
- return count
924
- end
925
-
926
- def calc_combinations(message, key)
927
- key_fields = collect_key_fields(key, message)
928
- return calc_combinations_for_key_fields(key_fields)
929
- end
930
-
931
- def read_docs_from_log_file(log_filename)
932
- result = []
933
- if File.exist?(log_filename) then
934
- File.open(log_filename, "r") do |f|
935
- f.each_line do |line|
936
- doc = {}
937
- if line =~ /(.*)\ <-\ \{(.*)\}/ then
938
- filename = $1
939
- combination = $2
940
- combination.split(", ").each do |assignment|
941
- if assignment =~ /(.*):\ (.*)/ then
942
- key = $1
943
- value = $2
944
- if value =~ /\"(.*)\"/ then
945
- value = $1
946
- elsif value == "nil" then
947
- value = nil
948
- end
949
- doc[key] = value
950
- else
951
- raise RuntimeError.new("could not parse logfile #{log_filename}: invalid assignment #{assignment.inspect}")
952
- end
953
- end
954
- else
955
- raise RuntimeError.new("could not parse logfile #{log_filename}: invalid line #{line.inspect}")
956
- end
957
- result << doc
958
- end
959
- end
960
- end
961
- return result
962
- end
963
-
964
- def prepare_output_doc_filename(number, output_dir)
965
- chunk_number = number / 100000
966
- dirname = "#{output_dir}/#{chunk_number}"
967
- unless Dir.exist?(dirname) then
968
- FileUtils.mkdir_p(dirname)
969
- end
970
- return "#{dirname}/doc#{number}.xml"
971
- end
972
-
973
- def values_for_key_field(field)
974
- if field[:type] == :field then
975
- if field[:datatype] == :enum then
976
- if field[:optional] then
977
- return field[:values] + [nil]
978
- else
979
- return field[:values]
980
- end
981
- elsif field[:datatype] == :int then
982
- if field.has_key?(:min) and field.has_key?(:max) and field[:min] <= field[:max] then
983
- return (field[:min]..field[:max]).to_a
984
- else
985
- raise RuntimeError.new("cannot determine values for unbounded :int field")
986
- end
987
- else
988
- raise RuntimeError.new("cannot determine values for datatype #{field[:datatype].inspect}")
989
- end
990
- elsif field[:type] == :structure then
991
- if field[:optional] then
992
- return [true, false]
993
- else
994
- return [true]
995
- end
996
- else
997
- raise RuntimeError.new("Encountered unknown :type in model: #{field[:type].inspect} - keys can only mark fields or structures.")
998
- end
999
- end
1000
-
1001
- def collect_key_fields(fkey, struct, prefix = nil)
1002
- result = {}
1003
- struct.each do |key, value|
1004
- next if key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
1005
- if value[:type] == :structure
1006
- if value.has_key?(:keys) and value[:keys].include?(fkey) then
1007
- xpath = key.to_s
1008
- xpath = "#{prefix}/#{xpath}" if prefix
1009
- result[xpath] = value.clone().filter{|k,v| [:type, :optional, :datatype, :values].include?(k)}
1010
- end
1011
- if prefix then
1012
- new_prefix = "#{prefix}/#{key}"
1013
- else
1014
- new_prefix = key
1015
- end
1016
- result = result.merge(collect_key_fields(fkey, value[:ref], new_prefix))
1017
- elsif value[:type] == :field and value.has_key?(:keys) and value[:keys].include?(fkey) then
1018
- xpath = key.to_s
1019
- xpath = "#{prefix}/#{xpath}" if prefix
1020
- result[xpath] = value.clone().filter{|k,v| [:type, :optional, :datatype, :values, :min, :max].include?(k)}
1021
- end
1022
- end
1023
- return result
1024
- end
1025
-
1026
- def collect_key_fields_and_strctures(fkey, struct, prefix = nil)
1027
- fields = []
1028
- structures = []
1029
- struct.each do |key, value|
1030
- next if key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
1031
- if value[:type] == :structure
1032
- if value.has_key?(:keys) and value[:keys].include?(fkey) then
1033
- xpath = key.to_s
1034
- xpath = "#{prefix}/#{xpath}" if prefix
1035
- structures << xpath
1036
- end
1037
- if prefix then
1038
- new_prefix = "#{prefix}/#{key}"
1039
- else
1040
- new_prefix = key
1041
- end
1042
- result_fields, result_structures = collect_key_fields_and_strctures(fkey, value[:ref], new_prefix)
1043
- fields += result_fields
1044
- structures += result_structures
1045
- elsif value[:type] == :field and value.has_key?(:keys) and value[:keys].include?(fkey) then
1046
- xpath = key.to_s
1047
- xpath = "#{prefix}/#{xpath}" if prefix
1048
- fields << xpath
1049
- end
1050
- end
1051
- return fields, structures
1052
- end
1053
-
1054
- def collect_keys(struct)
1055
- result = []
1056
- struct.each do |key, value|
1057
- next if key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
1058
- if value[:type] == :structure
1059
- if value.has_key?(:keys) then
1060
- result += value[:keys]
1061
- end
1062
- if value[:ref] then
1063
- result += collect_keys(value[:ref])
1064
- else
1065
- raise RuntimeError.new("Model error: encountered structure without :ref: key=#{key.inspect} value=#{value.inspect}")
1066
- end
1067
- elsif value[:type] == :field and value.has_key?(:keys) then
1068
- result += value[:keys]
1069
- end
1070
- end
1071
- result.uniq
1072
- end
1073
-
1074
- def collect_validation_rules(struct)
1075
- result = []
1076
- struct.each do |key, value|
1077
- next if key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
1078
- if key == :validation_rules then
1079
- result += value.keys
1080
- else
1081
- if value[:type] == :structure
1082
- if value[:ref] then
1083
- result += collect_validation_rules(value[:ref])
1084
- else
1085
- raise RuntimeError.new("Model error: encountered structure without :ref: key=#{key.inspect} value=#{value.inspect}")
1086
- end
1087
- end
1088
- end
1089
- end
1090
- result.uniq
1091
- end
1092
-
1093
- def list_keys(message)
1094
- keys = collect_keys(message)
1095
- puts "keys: #{keys.join(", ")}"
1096
- end
1097
-
1098
- def list_fields_and_structures(message, fkey)
1099
- fields, structures = collect_key_fields_and_strctures(fkey, message)
1100
- puts "fields for key #{fkey}:"
1101
- fields.each { |f| puts " #{f}" }
1102
- puts "structures for key #{fkey}:"
1103
- structures.each { |s| puts " #{s}" }
1104
- end
1105
-
1106
- def list_validation_rules(message)
1107
- rules = collect_validation_rules(message)
1108
- puts "Validation Rules:"
1109
- rules.each { |r| puts " #{r}" }
1110
- end
1111
-
1112
- def value_of_type(value, type)
1113
- if type == :string or type == :enum then
1114
- "\"#{value}\""
1115
- elsif type == :int then
1116
- value.to_s
1117
- elsif type == :date then
1118
- (Date.parse(value) - Time.at(0).to_date).to_i.to_s
1119
- elsif type == :timestamp then
1120
- (Time.new(value) - Time.at(0)).to_i.to_s
1121
- else
1122
- raise RuntimeError.new("encountered unknown :datatype #{type.inspect}")
1123
- end
1124
- end
1125
-
1126
- class CoreEnumerationAlgo
1127
-
1128
- def initialize()
1129
- @num_tries = 0
1130
- @num_docs = 0
1131
- @on_new_doc = nil
1132
- @on_unsat = nil
1133
- end
1134
-
1135
-
1136
- # fix values of all variables left from switched
1137
- def enumerate_fixing_clause(assignment, vars, switched)
1138
- clauses = []
1139
- switched.times do |index|
1140
- var_xpath = vars[index][0]
1141
- var_def = vars[index][1]
1142
- if var_def[:type] == :field then
1143
- if assignment[var_xpath] == nil then
1144
- clauses << "(assert (! (= #{filled_var_for_field(var_xpath)} false) :named fixing_clause_#{index}))"
1145
- else
1146
- clauses << "(assert (! (and (= #{filled_var_for_field(var_xpath)} true) (= #{value_var_for_field(var_xpath)} #{value_of_type(assignment[var_xpath], var_def[:datatype])})) :named fixing_clause_#{index}))"
1147
- end
1148
- elsif var_def[:type] == :structure then
1149
- clauses << "(assert (! (= #{exists_var_for_structure(var_xpath)} #{assignment[var_xpath].inspect}) :named fixing_clause_#{index}))"
1150
- end
1151
- end
1152
- "; fixing clauses:\n#{clauses.join("\n")}"
1153
- end
1154
-
1155
- def summarize_fixing_clause(assignment, vars, switched)
1156
- fixed = switched.times.to_a.map do |index|
1157
- var_xpath = vars[index][0]
1158
- "#{var_xpath}=#{assignment[var_xpath].inspect}"
1159
- end
1160
- "fixed(#{fixed.join(", ")})"
1161
- end
1162
-
1163
- # prevent the var from taking values that already have been enumerated exhaustively
1164
- def enumerate_blocking_clause(var_xpath, var_def, blocked)
1165
- clauses = []
1166
- num = 0
1167
- if var_def[:type] == :field then
1168
- blocked.each do |value|
1169
- if value == nil then
1170
- clauses << "(assert (! (not (= #{filled_var_for_field(var_xpath)} false)) :named blocking_clause_#{num}))"
1171
- else
1172
- clauses << "(assert (! (not (and (= #{filled_var_for_field(var_xpath)} true) (= #{value_var_for_field(var_xpath)} #{value_of_type(value, var_def[:datatype])}))) :named blocking_clause_#{num}))"
1173
- end
1174
- num += 1
1175
- end
1176
- elsif var_def[:type] == :structure then
1177
- blocked.each do |value|
1178
- clauses << "(assert (! (not (= #{exists_var_for_structure(var_xpath)} #{value.inspect})) :named blocking_clause_#{num}))"
1179
- num += 1
1180
- end
1181
- end
1182
- "; blocking clauses:\n#{clauses.join("\n")}"
1183
- end
1184
-
1185
- def summarize_blocking_clause(var_xpath, blocked)
1186
- "blocked(#{var_xpath})=[#{blocked.map{|v| v.inspect}.join(", ")}]"
1187
- end
1188
-
1189
- def generate_sparse_doc(progress, message, key, config, options, code, output_doc_filename)
1190
- additional_options = {}
1191
- if code then
1192
- additional_options = { :add_SMTLIB => code }
1193
- end
1194
- return generate_doc_return_key_values(progress, message, key, output_doc_filename, config, options.merge(additional_options))
1195
- end
1196
-
1197
- def log_filename_for_output_filename(output_filename)
1198
- "#{output_filename}.smt2"
1199
- end
1200
-
1201
- def generate_doc_return_key_values(progress, message, key, output_filename, config, options)
1202
- log = options[:log] || nil
1203
- log.puts "generating doc #{output_filename}" if log
1204
- options[:solverLog] = log_filename_for_output_filename(output_filename)
1205
-
1206
- key_fields = collect_key_fields(key, message)
1207
-
1208
- z3path = config[:z3path] || DEFAULT_Z3_PATH
1209
- z3 = Z3Solver.new(progress, z3path)
1210
- begin
1211
- model = z3.query_model(progress, options) do |solver|
1212
- set_standard_options(progress, solver)
1213
- log.puts "translating structure" if log
1214
- translate_structure_to_SMTLIB(progress, solver, message, :options => options, :config => config)
1215
- solver.to_solver(progress, "")
1216
- end
1217
- ensure
1218
- z3.close()
1219
- end
1220
-
1221
- if model then
1222
- result_doc = build_document_from_model(progress, message, model)
1223
- dir = File.dirname(output_filename)
1224
- FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
1225
- File.open(output_filename, "w") do |f|
1226
- f.puts result_doc.to_xml(:save_with => Nokogiri::XML::Node::SaveOptions::AS_XML)
1227
- end
1228
- progress.print_line "DONE. Generated XML was written to #{output_filename}, SMTLIB log was written to #{options[:solverLog]}"
1229
- return extract_key_field_values_from_model(key_fields, model)
1230
- else
1231
- progress.print_line "FAILED. No XML document was generated. For more details, please see the SMTLIB log in #{options[:solverLog]}"
1232
- return nil
1233
- end
1234
- end
1235
-
1236
- def store_continuation_point(doc_number, state, assignment, new_assignment, filename)
1237
- File.open(filename, "w") do |f|
1238
- f.puts("#{doc_number};#{@num_docs};state(#{state.map{|s| if s then "[#{s.map{|v| v.inspect}.join(",")}]" else "nil" end}.join("#")});assignment(#{assignment.to_a.map{|k,v| "#{k}:#{v.inspect}"}.join(",")});new_assignment(#{new_assignment.to_a.map{|k,v| "#{k}:#{v.inspect}"}.join(",")})")
1239
- end
1240
- end
1241
-
1242
- def self.parse_state(state_str)
1243
- unless state_str =~ /state\((.*)\)/
1244
- return nil
1245
- end
1246
- split = $1.split("#")
1247
- split.map do |e|
1248
- if e == "nil" then
1249
- nil
1250
- elsif e =~ /\[(.*)\]/ then
1251
- $1.split(",").map do |i|
1252
- if i.strip == "nil" then
1253
- nil
1254
- elsif i.strip == "true" then
1255
- true
1256
- elsif i.strip == "false" then
1257
- false
1258
- elsif i.strip =~ /\"(.*)\"/ then
1259
- $1
1260
- end
1261
- end
1262
- end
1263
- end
1264
- end
1265
-
1266
- def self.parse_assignment(str)
1267
- unless str =~ /assignment\((.*)\)/
1268
- return nil
1269
- end
1270
- split = $1.split(",")
1271
- result = {}
1272
- split.each do |e|
1273
- s2 = e.split(":")
1274
- key = s2[0]
1275
- value = s2[1]
1276
- if value == "nil" then
1277
- result[key] = nil
1278
- elsif value == "true" then
1279
- result[key] = true
1280
- elsif value == "false" then
1281
- result[key] = false
1282
- elsif value =~ /\"(.*)\"/
1283
- result[key] = $1
1284
- end
1285
- end
1286
- result
1287
- end
1288
-
1289
- def self.parse_new_assignment(str)
1290
- unless str =~ /new_assignment\((.*)\)/
1291
- return nil
1292
- end
1293
- split = $1.split(",")
1294
- result = {}
1295
- split.each do |e|
1296
- s2 = e.split(":")
1297
- key = s2[0]
1298
- value = s2[1]
1299
- if value == "nil" then
1300
- result[key] = nil
1301
- elsif value == "true" then
1302
- result[key] = true
1303
- elsif value == "false" then
1304
- result[key] = false
1305
- elsif value =~ /\"(.*)\"/
1306
- result[key] = $1
1307
- end
1308
- end
1309
- result
1310
- end
1311
-
1312
- def self.parse_continuation_point(line)
1313
- split = line.split(";")
1314
- doc_number = Integer(split[0])
1315
- num_docs = Integer(split[1])
1316
- state = CoreEnumerationAlgo.parse_state(split[2])
1317
- assignment = CoreEnumerationAlgo.parse_assignment(split[3])
1318
- new_assignment = CoreEnumerationAlgo.parse_assignment(split[4])
1319
- return [doc_number, num_docs, state, assignment, new_assignment]
1320
- end
1321
-
1322
- # TODO: logging
1323
-
1324
- def display_combination_space(assignment, vars, switched, blocked)
1325
- # enumerate_fixing_clause(assignment, vars, switched) + enumerate_blocking_clause(vars[switched][0], vars[switched][1], blocked)
1326
-
1327
- # => all vars left of switched are fixed to their assigned values (when the value is nil, then they are empty)
1328
- # => the switched var is prevented to have any of the blocked values
1329
- res = switched.times.to_a.map do |index|
1330
- var_xpath = vars[index][0]
1331
- "#{var_xpath}=#{assignment[var_xpath].inspect}"
1332
- end + ["#{vars[switched][0]}\{#{blocked.map{|v| v.inspect}.join(",")}}"]
1333
- "{#{res.join(", ")}}"
1334
- end
1335
-
1336
- # core algo
1337
- def enumerate(progress, switched, vars, assignment, message, key, output_dir, config, options, state = [nil] * vars.size())
1338
- enumerate(progress, switched+1, vars, assignment, message, key, output_dir, config, options, state) if switched < vars.size()-1
1339
- is_finite_state = keys_are_finite_state(vars)
1340
- if state[switched] == nil then
1341
- blocked = [assignment[vars[switched][0]]]
1342
- state[switched] = blocked
1343
- else
1344
- blocked = state[switched] + [assignment[vars[switched][0]]]
1345
- state[switched] = blocked
1346
- end
1347
- found = true
1348
- while found do
1349
- found = false
1350
- # - fix all vars left of switched to their values in assignment
1351
- # - block switched from obtaining the values in blocked
1352
- # progress.print_line "DEBUG: #{summarize_fixing_clause(assignment, vars, switched)}, #{summarize_blocking_clause(vars[switched][0], blocked)}"
1353
- clauses = enumerate_fixing_clause(assignment, vars, switched) + enumerate_blocking_clause(vars[switched][0], vars[switched][1], blocked)
1354
- output_doc_filename = prepare_output_doc_filename(@num_tries, output_dir)
1355
- new_assignment = generate_sparse_doc(progress, message, key, config, options, clauses, output_doc_filename)
1356
- generated_doc_number = @num_tries
1357
- store_continuation_point(generated_doc_number, state, assignment, new_assignment, "#{output_dir}/#{CONTINUATION_FILE}")
1358
- if is_finite_state then
1359
- progress.report_progress(determine_progress(state, vars))
1360
- else
1361
- progress.report_progress(nil)
1362
- end
1363
- @num_tries += 1
1364
- if new_assignment then # sat
1365
- @num_docs += 1
1366
- @on_new_doc.call(generated_doc_number, output_doc_filename, new_assignment) if @on_new_doc
1367
- if switched < vars.size()-1 then
1368
- enumerate(progress, switched+1, vars, new_assignment, message, key, output_dir, config, options, state)
1369
- end
1370
- blocked << new_assignment[vars[switched][0]]
1371
- state[switched] = blocked
1372
- found = true
1373
- else # unsat
1374
- @on_unsat.call(generated_doc_number, log_filename_for_output_filename(output_doc_filename), display_combination_space(assignment, vars, switched, blocked)) if @on_unsat
1375
- found = false
1376
- end
1377
- end
1378
- state[switched] = nil
1379
- end
1380
-
1381
- # NOTE: can only be called when all key-fields are finite-state
1382
- def is_final_state(state, new_assignment, key_fields_list)
1383
- return state[1,state.size()] == [nil] * (state.size()-1) &&
1384
- state[0].to_set == values_for_key_field(key_fields_list[0][1]).to_set &&
1385
- new_assignment.empty?
1386
- end
1387
-
1388
- # NOTE: can only be called when all key-fields are finite-state
1389
- def determine_progress(state, vars)
1390
- combinations_per_value = [nil] * vars.size()
1391
- comb = 1
1392
- vars.size().times.to_a.reverse_each do |i|
1393
- combinations_per_value[i] = comb
1394
- comb = values_for_key_field(vars[i][1]).size() * comb
1395
- end
1396
-
1397
- blocked_combinations = 0
1398
- state.size().times do |i|
1399
- if state[i] then
1400
- blocked_combinations += state[i].size() * combinations_per_value[i]
1401
- end
1402
- end
1403
- blocked_combinations
1404
- end
1405
-
1406
- def num_combinations(key_def)
1407
- if key_def[:type] == :field then
1408
- if key_def[:datatype] == :enum then
1409
- key_def[:values].size() + (key_def[:optional] ? 1 : 0)
1410
- elsif key_def[:datatype] == :int then
1411
- if key_def.has_key?(:min) and key_def.has_key?(:max) and key_def[:min] <= key_def[:max] then
1412
- 1 + key_def[:max] - key_def[:min] + (key_def[:optional] ? 1 : 0)
1413
- else
1414
- nil
1415
- end
1416
- else
1417
- nil
1418
- end
1419
- elsif key_def[:type] == :structure then
1420
- 2
1421
- end
1422
- end
1423
-
1424
- def keys_are_finite_state(list)
1425
- list.each do |field|
1426
- name = field[0]
1427
- if field[1][:type] == :field then
1428
- if field[1][:datatype] == :enum then
1429
- # enums are finite-state
1430
- elsif field[1][:datatype] == :int and field[1].has_key?(:min) and field[1].has_key?(:max) and field[1][:min] <= field[1][:max] then
1431
- # ints are finite-state when both bounds are given and :min <= :max
1432
- else
1433
- return false
1434
- end
1435
- end
1436
- end
1437
- return true
1438
- end
1439
-
1440
- def generate_docs_for_key(message, key, output_dir, config, options)
1441
-
1442
- number_of_combinations = calc_combinations(message, key)
1443
- if number_of_combinations.is_a?(String) then
1444
- progress = ProgressBar.new(100000000000000)
1445
- else
1446
- progress = ProgressBar.new(number_of_combinations)
1447
- end
1448
- begin
1449
- ensure
1450
- end
1451
- progress.report_progress(0.0)
1452
- progress.print_line "generating sparse documents efficiently..."
1453
- progress.print_line "there is a theoretical maximum of #{number_of_combinations} combinations."
1454
- progress.print_line "Note, however, that we are generating only those documents that are valid, which are usually much less."
1455
-
1456
- progress.print_line "preparing output dir #{output_dir}..."
1457
- if options[:continue] then
1458
- unless Dir.exist?(output_dir) then
1459
- raise FatalError.new("FATAL: directory #{output_dir} not found. There seems to be nothing to continue from...")
1460
- end
1461
- log_mode = "a"
1462
- else
1463
- unless Dir.exist?(output_dir) then
1464
- FileUtils.mkdir_p(output_dir)
1465
- end
1466
- log_mode = "w"
1467
- end
1468
- log_filename = "#{output_dir}/#{COMBINATION_LOG}"
1469
-
1470
- key_fields = collect_key_fields(key, message)
1471
- key_fields_list = key_fields.each_pair.to_a
1472
- key_fields_list.sort! {|a,b| num_combinations(b[1]) <=> num_combinations(a[1]) }
1473
-
1474
- if options[:continue] then
1475
- stored_key_fields_str = File.read("#{output_dir}/#{VARS_LOG}")
1476
- unless key_fields_list.to_yaml == stored_key_fields_str then
1477
- raise FatalError.new("vars are differing! please specify the same key in order to use -continue!")
1478
- end
1479
- progress.print_line "continuing enumerating documents for key #{key}!"
1480
- else
1481
- # store vars
1482
- File.open("#{output_dir}/#{VARS_LOG}", "w") do |f|
1483
- f.puts(key_fields_list.to_yaml)
1484
- end
1485
- end
1486
-
1487
- cont_filename = "#{output_dir}/#{CONTINUATION_FILE}"
1488
- if options[:continue] then
1489
- unless File.exist?(cont_filename) then
1490
- raise FatalError.new("FATAL: file #{cont_filename} not found. There seems to be no continuation point to continue from...")
1491
- end
1492
- doc_number, num_docs, state, assignment, new_assignment = CoreEnumerationAlgo.parse_continuation_point(File.read(cont_filename).chomp)
1493
- if is_final_state(state, new_assignment, key_fields_list) then
1494
- progress.report_progress(number_of_combinations)
1495
- raise FatalError.new("FATAL: The enumeration is finished. There is nothing left to do. Resuming the enumeration hence does not make sense.")
1496
- end
1497
- progress.print_line "RESUMING enumeration from the following continuation point:"
1498
- progress.print_line "|doc_number=#{doc_number}"
1499
- progress.print_line "|num_docs=#{num_docs}"
1500
- progress.print_line "|state=#{state.inspect}"
1501
- progress.print_line "|assignment=#{assignment.inspect}"
1502
- progress.print_line "|new_assignment=#{new_assignment.inspect}"
1503
- if keys_are_finite_state(key_fields_list) then
1504
- progress.report_progress(determine_progress(state, key_fields_list))
1505
- else
1506
- progress.report_progress(nil)
1507
- end
1508
-
1509
- if new_assignment && !new_assignment.empty? then
1510
- assignment = new_assignment
1511
- end
1512
-
1513
- # restore state at continuation point
1514
- @num_tries = doc_number+1 # we do not want to overwrite the last document written before the STOP
1515
- number_of_combinations += 1 # account for this in the safety mechanism
1516
- @num_docs = num_docs
1517
- doc_counter = 0
1518
- File.open(log_filename, log_mode) do |log|
1519
-
1520
- @on_new_doc = Proc.new { |num_doc, doc_filename, combination|
1521
- # combination logging
1522
- log.puts "#{doc_filename} <- {#{combination.map{|k,v| "#{k}=#{v.inspect}"}.join(", ")}}"
1523
-
1524
- doc_counter += 1
1525
- if options[:max_num_docs] && doc_counter >= options[:max_num_docs] then
1526
- raise NormalProgramTermination.new("-max-num-docs exceeded. Stopping!")
1527
- end
1528
-
1529
- # safety mechanism
1530
- if @num_docs > number_of_combinations then
1531
- raise RuntimeError.new("This should never happen! Number of combinations exceeded. #{@num_docs} documents generated, but there are only #{number_of_combinations} possible combinations of the key vars.")
1532
- end
1533
- }
1534
- @on_unsat = Proc.new { |num_doc, log_filename, combination_str|
1535
- log.puts "CONTRADICTION (see: #{log_filename}) <- #{combination_str}"
1536
- progress.print_line "doc#{num_doc} NOT generated"
1537
- progress.print_line "No more valid combinations of the free vars could be found. -> Switching"
1538
- }
1539
-
1540
- # core algo - resume from state
1541
- enumerate(progress, 0, key_fields_list, assignment, message, key, output_dir, config, options, state)
1542
- end
1543
- else
1544
- # init safety mechanism
1545
- @num_tries = 0
1546
- @num_docs = 0
1547
- doc_counter = 0
1548
- # first try - a freely generated document
1549
- File.open(log_filename, log_mode) do |log|
1550
- output_doc_filename = prepare_output_doc_filename(@num_tries, output_dir)
1551
- key_values = generate_doc_return_key_values(progress, message, key, output_doc_filename, config, options)
1552
- log.puts "#{output_doc_filename} <- {#{key_values.map{|k,v| "#{k}=#{v.inspect}"}.join(", ")}}"
1553
- doc_counter += 1
1554
- @num_tries += 1
1555
- @on_new_doc = Proc.new { |num_doc, doc_filename, combination|
1556
- # combination logging
1557
- log.puts "#{doc_filename} <- {#{combination.map{|k,v| "#{k}=#{v.inspect}"}.join(", ")}}"
1558
-
1559
- doc_counter += 1
1560
- if options[:max_num_docs] && doc_counter >= options[:max_num_docs] then
1561
- raise NormalProgramTermination.new("-max-num-docs exceeded. Stopping!")
1562
- end
1563
-
1564
- # safety mechanism
1565
- unless number_of_combinations.is_a?(String) then
1566
- if @num_docs > number_of_combinations then
1567
- raise RuntimeError.new("This should never happen! Number of combinations exceeded. #{@num_docs} documents generated, but there are only #{number_of_combinations} possible combinations of the key vars.")
1568
- end
1569
- end
1570
- }
1571
- @on_unsat = Proc.new { |num_doc, log_filename, combination_str|
1572
- log.puts "CONTRADICTION (see: #{log_filename}) <- #{combination_str}"
1573
- progress.print_line "doc#{num_doc} NOT generated"
1574
- progress.print_line "No more valid combinations of the free vars could be found. -> Switching"
1575
- }
1576
-
1577
- # core algo - start enumeration
1578
- enumerate(progress, 0, key_fields_list, key_values, message, key, output_dir, config, options)
1579
- end
1580
-
1581
- progress.print_line "No more valid documents could be found. -> Exiting"
1582
- progress.report_progress(number_of_combinations)
1583
- end
1584
- end
1585
- end
1586
-
1587
- def introduce_parent_links(message, parent = nil, prefix = nil)
1588
- if parent != nil then
1589
- message[:parent_link] = parent
1590
- end
1591
- if prefix != nil then
1592
- message[:struct_xpath_prefix] = prefix
1593
- end
1594
- message.each_pair do |key, value|
1595
- next if key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
1596
- if value[:type] == :structure then
1597
- if prefix then
1598
- new_prefix = "#{prefix}/#{key}"
1599
- else
1600
- new_prefix = key
1601
- end
1602
- introduce_parent_links(value[:ref], message, new_prefix)
1603
- end
1604
- end
1605
- end
1606
-
1607
- def validate_model(message, prefix=nil)
1608
-
1609
- message.each_pair do |key, value|
1610
- if key == :validation_rules then
1611
- value.each_pair do |vr_key, vr_value|
1612
- unless vr_value[:text] && vr_value[:text] != "" then
1613
- puts "INFO: Model Problem: validation rule #{vr_key} does not have a textual representation."
1614
- end
1615
- unless vr_value[:smtlib] && vr_value[:smtlib] != "" then
1616
- puts "WARN: Model Problem: validation rule #{vr_key} does not have an SMTLIB representation. It will hence be omitted!"
1617
- end
1618
- end
1619
- elsif key == :predicates then
1620
- value.each_pair do |mkey, mvalue|
1621
- next unless mvalue
1622
- unless mvalue[:text] && mvalue[:text] != "" then
1623
- puts "INFO: Model Problem: structure #{prefix}'s predicate #{mkey} does not have a textual representation."
1624
- end
1625
- unless mvalue[:smtlib] && mvalue[:smtlib] != "" then
1626
- puts "WARN: Model Problem: structure #{prefix}'s predicate #{mkey} does not have an SMTLIB representation. It will be regarded as always true."
1627
- end
1628
- end
1629
- elsif key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
1630
- next
1631
- else
1632
- if value[:type] == :structure then
1633
- unless value[:ref] then
1634
- raise RuntimeError.new("Model Problem: encountered structure without a :ref (key=#{key.inspect}, value=#{value.inspect})")
1635
- end
1636
- if prefix then
1637
- new_prefix = "#{prefix}/#{key}"
1638
- else
1639
- new_prefix = key
1640
- end
1641
- validate_model(value[:ref], new_prefix)
1642
- elsif value[:type] == :field then
1643
- unless value[:datatype] then
1644
- raise RuntimeError.new("Model Problem: encountered field without a :datatype (key=#{key.inspect}, value=#{value.inspect})")
1645
- end
1646
- if value[:datatype] == :enum then
1647
- unless value[:values] then
1648
- raise RuntimeError.new("Model Problem: encountered enum field without a :values (key=#{key.inspect}, value=#{value.inspect})")
1649
- end
1650
- end
1651
- end
1652
- end
1653
- end
1654
- end
1655
-
1656
- def validate_doc_against_schema(struct, doc)
1657
- root = doc.root
1658
- sdef = struct[root.name.to_sym]
1659
- unless sdef[:type] == :structure then
1660
- puts "ERROR: schema validation failed: root element should be a structure"
1661
- end
1662
- # first, check that each doc element is defined for the struct
1663
- validate_doc_struct_against_schema(sdef[:ref], root, root.name)
1664
- # second, check that all required elements from the struct are actually present in the doc
1665
- validate_schema_against_doc_struct(sdef[:ref], root, root.name)
1666
- end
1667
-
1668
- def validate_doc_struct_against_schema(struct, doc, prefix = nil)
1669
- doc.children.to_a.filter{|x| x.is_a?(Nokogiri::XML::Element) }.each do |c|
1670
- fdef = struct[c.name.to_sym]
1671
- if prefix then
1672
- xpath = "#{prefix}/#{c.name}"
1673
- else
1674
- xpath = c.name
1675
- end
1676
- if fdef
1677
- if fdef[:type] == :structure then
1678
- return unless validate_doc_struct_against_schema(fdef[:ref], c, xpath)
1679
- end
1680
- else
1681
- if prefix then
1682
- xpath = "#{prefix}/#{c.name}"
1683
- else
1684
- xpath = c.name
1685
- end
1686
- raise FatalError.new("ERROR: schema validation failed: #{c.name} of #{xpath} is present in document, but not defined in schema.")
1687
- end
1688
- end
1689
- end
1690
-
1691
- def validate_schema_against_doc_struct(struct, doc, prefix = nil)
1692
- struct.each do |key, value|
1693
- next if key == :additional_smtlib || key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix
1694
- if prefix then
1695
- xpath = "#{prefix}/#{key}"
1696
- else
1697
- xpath = key
1698
- end
1699
- if value[:type] == :structure then
1700
- node = doc.at_xpath(key.to_s)
1701
- if node then
1702
- validate_schema_against_doc_struct(value[:ref], node, xpath)
1703
- else
1704
- if value[:optional] == false then
1705
- raise FatalError.new("ERROR: schema validation failed: non-optional structure #{xpath} not found in doc.")
1706
- end
1707
- end
1708
- elsif value[:type] == :field then
1709
- if value[:optional] == false then
1710
- unless doc.at_xpath(key.to_s) then
1711
- raise FatalError.new("ERROR: schema validation failed: non-optional field #{xpath} not found in doc.")
1712
- end
1713
- end
1714
- elsif value[:type] == :list then
1715
- if value[:optional] == false then
1716
- unless doc.at_xpath(key.to_s) then
1717
- raise FatalError.new("ERROR: schema validation failed: non-optional list #{xpath} not found in doc.")
1718
- end
1719
- end
1720
- list = doc.at_xpath(key.to_s)
1721
- if list then
1722
- list.children.to_a.each do |c|
1723
- next unless c.is_a?(Nokogiri::XML::Element)
1724
- unless c.name == value[:xpath_element] then
1725
- raise FatalError.new("ERROR: schema validation failed: encountered element #{c.name} in list #{xpath}, which should only contain #{value[:xpath_element]} elements.")
1726
- end
1727
- end
1728
- end
1729
- else
1730
- raise RuntimeError.new("Model error: encountered element #{xpath} of unknown type: #{value[:type]}.")
1731
- end
1732
- end
1733
- end
1734
-
1735
- def translate_doc_to_SMTLIB(progress, solver, struct, doc, hash = {})
1736
- options = hash[:options]
1737
- config = hash[:config]
1738
-
1739
- solver.to_solver(progress, "; --- Document: ---")
1740
- translate_struct_in_doc_to_SMTLIB(progress, solver, struct, doc, nil, hash)
1741
- end
1742
-
1743
- def translate_struct_in_doc_to_SMTLIB(progress, solver, struct, doc, prefix, hash = {})
1744
- options = hash[:options]
1745
- config = hash[:config]
1746
-
1747
- struct.each do |key, value|
1748
- next if key == :additional_smtlib || key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix
1749
- if prefix then
1750
- xpath = "#{prefix}/#{key.to_s}"
1751
- else
1752
- xpath = key.to_s
1753
- end
1754
- if value[:type] == :structure then
1755
- node = doc.at_xpath(xpath)
1756
- if node then
1757
- info = {
1758
- :assertion_type => :doc_structure_exists,
1759
- :xpath => xpath,
1760
- }
1761
- assertionID = "doc-#{xpath.gsub('/', '-')}"
1762
- solver.associateAssertionIDWith(assertionID, info)
1763
- solver.to_solver(progress, "(assert (! (= #{exists_var_for_structure(xpath)} true) :named #{assertionID}))")
1764
-
1765
- translate_struct_in_doc_to_SMTLIB(progress, solver, value[:ref], doc, xpath, hash)
1766
- else
1767
- info = {
1768
- :assertion_type => :doc_structure_not_exists,
1769
- :xpath => xpath,
1770
- }
1771
- assertionID = "doc-#{xpath.gsub('/', '-')}"
1772
- solver.associateAssertionIDWith(assertionID, info)
1773
- solver.to_solver(progress, "(assert (! (= #{exists_var_for_structure(xpath)} false) :named #{assertionID}))")
1774
- end
1775
- elsif value[:type] == :field then
1776
- node = doc.at_xpath(xpath)
1777
- if node then
1778
- fvalue = node.text
1779
- info = {
1780
- :assertion_type => :doc_field_filled,
1781
- :xpath => xpath,
1782
- :value => fvalue
1783
- }
1784
- fvalue_str = value_of_type(fvalue, value[:datatype])
1785
- assertionID = "doc-#{xpath.gsub('/', '-')}"
1786
- solver.associateAssertionIDWith(assertionID, info)
1787
- solver.to_solver(progress, "(assert (! (and (= #{filled_var_for_field(xpath)} true) (= #{value_var_for_field(xpath)} #{fvalue_str})) :named #{assertionID}))")
1788
- else
1789
- info = {
1790
- :assertion_type => :doc_field_empty,
1791
- :xpath => xpath,
1792
- }
1793
- assertionID = "doc-#{xpath.gsub('/', '-')}"
1794
- solver.associateAssertionIDWith(assertionID, info)
1795
- solver.to_solver(progress, "(assert (! (= #{filled_var_for_field(xpath)} false) :named #{assertionID}))")
1796
- end
1797
- elsif value[:type] == :list then
1798
- node = doc.at_xpath(xpath)
1799
- if node then
1800
- fvalues = []
1801
- node.children.to_a.each do |c|
1802
- next unless c.is_a?(Nokogiri::XML::Element)
1803
- fvalues << c.text
1804
- end
1805
- info = {
1806
- :assertion_type => :doc_list_filled,
1807
- :xpath => xpath,
1808
- :values => fvalues
1809
- }
1810
- fvalue_str = value_of_type(fvalues, value[:datatype])
1811
- assertionID = "doc-#{xpath.gsub('/', '-')}"
1812
- solver.associateAssertionIDWith(assertionID, info)
1813
- clauses = []
1814
- fvalues.each_with_index do |fvalue, index|
1815
- clauses << "(= #{value_var_for_list(xpath,index)} #{value_of_type(fvalue, value[:datatype])})"
1816
- end
1817
- solver.to_solver(progress, "(assert (! (and (= #{filled_var_for_field(xpath)} true) (= #{size_var_for_list(xpath)} #{fvalues.size}) #{clauses.join(' ')}) :named #{assertionID}))")
1818
- else
1819
- info = {
1820
- :assertion_type => :doc_field_empty,
1821
- :xpath => xpath,
1822
- }
1823
- assertionID = "doc-#{xpath.gsub('/', '-')}"
1824
- solver.associateAssertionIDWith(assertionID, info)
1825
- solver.to_solver(progress, "(assert (! (= #{filled_var_for_field(xpath)} false) :named #{assertionID}))")
1826
- end
1827
- else
1828
- raise RuntimeError.new("Model Error: encountered unknown type #{value[:type].inspect}")
1829
- end
1830
- end
1831
- end
1832
-
1833
- def validate_doc(progress, struct, doc, options, config)
1834
- options[:solverLog] = "validation.smt2"
1835
-
1836
- z3path = config[:z3path] || DEFAULT_Z3_PATH
1837
- z3 = Z3Solver.new(progress, z3path)
1838
- begin
1839
- result = z3.query(progress, options) do |solver|
1840
- set_standard_options(progress, solver)
1841
- translate_structure_to_SMTLIB(progress, solver, struct, :options => options, :config => config)
1842
- translate_doc_to_SMTLIB(progress, solver, struct, doc, :options => options, :config => config)
1843
- solver.to_solver(progress, "")
1844
- end
1845
- unless result == true then
1846
- raise FatalError.new("VALIDATION FAILED! SMTLIB log was written to #{options[:solverLog]}.\n\n#{result}")
1847
- end
1848
- ensure
1849
- z3.close()
1850
- end
1851
-
1852
- return true
1853
- end
1854
-
1855
- def generate(message, argv, message_config_filename = nil)
1856
- if ARGV[0] == "--help" || ARGV[0] == "-help" || ARGV[0] == "help" then
1857
- puts USAGE
1858
- exit(0)
1859
- end
1860
-
1861
- options = { :negated_validation_rules => [], :date_now => (Date.today - Time.at(0).to_date).to_i, :timestamp_now => Time.now().to_i }
1862
-
1863
- opt = argv.shift
1864
- while opt do
1865
- if opt == "-negate" then
1866
- rule = argv.shift
1867
- options[:negated_validation_rules] << rule
1868
- elsif opt == "-list-keys" then
1869
- options[:list_keys] = true
1870
- elsif opt == "-list" then
1871
- options[:list] = argv.shift
1872
- elsif opt == "-list-validation-rules" then
1873
- options[:list_validation_rules] = true
1874
- elsif opt == "-generate-docs-from" then
1875
- options[:combination_source] = argv.shift
1876
- elsif opt == "-skip" then
1877
- options[:skip] = Integer(argv.shift)
1878
- elsif opt == "-generate-docs-for-key" then
1879
- options[:docs_for_key] = argv.shift
1880
- elsif opt == "-count-docs-for-key" then
1881
- options[:count_docs_for_key] = argv.shift
1882
- elsif opt == "-continue" then
1883
- options[:continue] = true
1884
- elsif opt == "-max-num-docs" then
1885
- options[:max_num_docs] = Integer(argv.shift)
1886
- puts "flag -max-num-docs used. Will stop after generating #{options[:max_num_docs]} documents."
1887
- elsif opt == "-validate" then
1888
- if options.has_key?(:validate_files) then
1889
- options[:validate_files] << argv.shift
1890
- else
1891
- options[:validate_files] = [argv.shift]
1892
- end
1893
- else
1894
- puts "FATAL: unknown option #{opt.inspect}."
1895
- puts USAGE
1896
- raise FatalError.new("")
1897
- end
1898
- opt = argv.shift
1899
- end
1900
-
1901
- unless options[:negated_validation_rules].empty? then
1902
- puts "negating rules: #{options[:negated_validation_rules].join(", ")}"
1903
-
1904
- rules = collect_validation_rule_names(message)
1905
- unknown_negated_rules = options[:negated_validation_rules].filter { |rule_name| !rules.include?(rule_name) }
1906
- unless unknown_negated_rules.empty? then
1907
- throw RuntimeError.new("unknown negated rules #{unknown_negated_rules.join(", ")}")
1908
- end
1909
-
1910
- end
1911
-
1912
- if File.exist?(CONFIG_FILE) then
1913
- config = YAML.load_file(CONFIG_FILE)
1914
- else
1915
- config = DEFAULT_CONFIG
1916
- File.open(CONFIG_FILE, "w") do |f|
1917
- f.puts(config.to_yaml)
1918
- end
1919
- puts "No config file found. Default config written to #{CONFIG_FILE}"
1920
- end
1921
- if message_config_filename then
1922
- message_config = YAML.load_file(message_config_filename)
1923
- config = config.merge(message_config)
1924
- end
1925
-
1926
- introduce_parent_links(message)
1927
-
1928
- if options[:validate_files] then
1929
- puts "using #{options[:date_now]} as [Date.now]"
1930
- puts "using #{options[:timestamp_now]} as [Timestamp.now]"
1931
- options[:validate_files].each do |filename|
1932
- puts "validating file #{filename}"
1933
- doc = Nokogiri::XML(File.read(filename))
1934
- validate_doc_against_schema(message, doc)
1935
- progress = ProgressBar.new(1)
1936
- validate_doc(progress, message, doc, options, config)
1937
- end
1938
- elsif options[:count_docs_for_key] then
1939
- puts "there are #{calc_combinations(message, options[:count_docs_for_key])} combinations."
1940
- elsif options[:docs_for_key] then
1941
- puts "using #{options[:date_now]} as [Date.now]"
1942
- puts "using #{options[:timestamp_now]} as [Timestamp.now]"
1943
- validate_model(message)
1944
- CoreEnumerationAlgo.new().generate_docs_for_key(message, options[:docs_for_key], OUTPUT_DIR, config, options)
1945
- elsif options[:list_keys] then
1946
- list_keys(message)
1947
- elsif options[:list] then
1948
- list_fields_and_structures(message, options[:list])
1949
- elsif options[:list_validation_rules] then
1950
- list_validation_rules(message)
1951
- else
1952
- puts "using #{options[:date_now]} as [Date.now]"
1953
- puts "using #{options[:timestamp_now]} as [Timestamp.now]"
1954
- validate_model(message)
1955
- progress = ProgressBar.new(1)
1956
- generate_doc(progress, message, OUTPUT_FILE, config, options)
1957
- end
1958
- end
1
+ #!/usr/bin/ruby
2
+
3
+ require 'date'
4
+ require 'fileutils'
5
+ require 'tmpdir'
6
+ require 'yaml'
7
+ require 'time'
8
+
9
+ require 'nokogiri'
10
+
11
+ require_relative 'solver-lib'
12
+ require_relative 'progress'
13
+ require_relative 'regexp-to-smtlib'
14
+
15
+
16
+ CHUNK_SIZE = 10000
17
+ DEFAULT_Z3_PATH = "z3"
18
+
19
+ CONFIG_FILE = "config.yml"
20
+ OUTPUT_FILE = "result.xml"
21
+ CONTINUATION_FILE = "continuation.log"
22
+ COMBINATION_LOG = "combinations.log"
23
+ VARS_LOG = "vars.log"
24
+
25
+ OUTPUT_DIR = "result"
26
+ LISTS_DIR = "lists"
27
+ QUERY_DIR = "queries"
28
+ PARAMETERIZED_QUERY_DIR = "parameterized_queries"
29
+ XML_TEMPLATE = ""
30
+
31
+ DEFAULT_CONFIG = {
32
+ :z3path => "z3",
33
+ :generation_system_mode => "Production",
34
+ :generation_system => "Production"
35
+ }
36
+
37
+ USAGE = <<EOF
38
+ usage:
39
+
40
+ GENERATING A SINGLE MESSAGE:
41
+
42
+ ruby #{$PROGRAM_NAME} -list-validation-rules
43
+ - outputs a list of all validation rule names that have been specified in #{$PROGRAM_NAME}.
44
+
45
+ ruby #{$PROGRAM_NAME} <options>
46
+ - generates a message of the given type, which
47
+ - is valid XML
48
+ - adheres to the schema, and
49
+ - satisfies all validation rules specified for messages of this type.
50
+ where <options> is a combination of
51
+ -negate <validation_rule> - negates the named validation rule. This causes the resulting message to still satisfy all other validation rules, but to violate this one.
52
+ NOTE: the result can be found in #{OUTPUT_FILE}
53
+
54
+ GENERATING MESSAGES FOR ALL COMBINATIONS:
55
+
56
+ ruby #{$PROGRAM_NAME} -list-keys
57
+ - outputs a list of all keys that have been used in #{$PROGRAM_NAME} to mark fields or structures.
58
+
59
+ ruby #{$PROGRAM_NAME} -list <key>
60
+ - outputs a list of all fields and structures that have been marked with <key> in #{$PROGRAM_NAME}.
61
+
62
+ ruby #{$PROGRAM_NAME} -count-docs-for-key <key>
63
+ - calculates the number of combinations of all fields and structures marked with <key>
64
+
65
+ ruby #{$PROGRAM_NAME} -generate-docs-for-key <key> <options>
66
+ - generates all valid messages of the given type that can be derived from combinations of all the fields and structures marked with <key>.
67
+ where <options> is a combination of
68
+ -continue - continues a previously interrupted generation process. Must use the same <key> the the interrupted generation.ARGF
69
+ -max-num-docs <limit> - stops generation process after generating <limit> documents.
70
+ NOTE: enumerating large combination-spaces may take a long time.
71
+ - Use -count-docs-for-key to measure the size of the combination-space beforehand.
72
+ - Also: you can interrupt the generation process at any time using Ctrl+C. The intermediate result is stored in the directory #{OUTPUT_DIR} and the generation process can
73
+ be restarted later by adding -continue to the command line.
74
+ NOTE: do not expect the number of generated documents to equal the number of combinations as combinations may not have any valid message instances (for instance when they contradict the validation rules).
75
+ NOTE: the result can be found in the directory #{OUTPUT_DIR}
76
+
77
+ VALIDATING THE MODEL:
78
+
79
+ ruby #{$PROGRAM_NAME} -validate <file.xml>
80
+ - reads in the given XML file and validates it against the model - that is, it checks whether it conforms to the specified XML schema and to the validation rules.
81
+ EOF
82
+
83
+ class FatalError < RuntimeError
84
+ end
85
+
86
+ class NormalProgramTermination < RuntimeError
87
+ end
88
+
89
+ def raw_field(xpath)
90
+ path_str = "base"
91
+ path_str = xpath.gsub("/", "-") if xpath
92
+ "field-#{path_str}"
93
+ end
94
+
95
+ def filled_var_for_field(xpath)
96
+ "#{raw_field(xpath)}-filled"
97
+ end
98
+
99
+ def value_var_for_field(xpath)
100
+ "#{raw_field(xpath)}-value"
101
+ end
102
+
103
+ def exists_var_for_structure(xpath, index = nil)
104
+ if index then
105
+ struct_str = "base"
106
+ struct_str = xpath.gsub("/", "-") if xpath
107
+ struct_str = "#{struct_str}-#{index}" if index
108
+ "struct-#{struct_str}-exists"
109
+ else
110
+ struct_str = "base"
111
+ struct_str = xpath.gsub("/", "-") if xpath
112
+ "struct-#{struct_str}-exists"
113
+ end
114
+ end
115
+
116
+ def size_var_for_list(xpath)
117
+ "#{raw_field(xpath)}-size"
118
+ end
119
+
120
+ def value_var_for_list(xpath, index)
121
+ "#{raw_field(xpath)}-index-#{index}-value"
122
+ end
123
+
124
+ def translate_datatype_to_SMTLIB(datatype)
125
+ case datatype
126
+ when :string
127
+ return "String"
128
+ when :int
129
+ return "Int"
130
+ when :bool
131
+ return "Bool"
132
+ when :date
133
+ return "Int"
134
+ when :timestamp
135
+ return "Int"
136
+ when :enum
137
+ return "String"
138
+ end
139
+ end
140
+
141
+ def translate_value_to_SMTLIB(value)
142
+ if value.is_a?(Integer) then
143
+ return value.to_s
144
+ elsif value.is_a?(String) then
145
+ return value.inspect
146
+ elsif value.is_a?(Date) then
147
+ return (value - (Time.at(0).to_date)).to_i # days since 1970.01.01
148
+ elsif value.is_a?(Time) then
149
+ return value.to_i
150
+ else
151
+ raise RuntimeError.new("cannot translate value to SMTLIB: #{value.inspect}")
152
+ end
153
+ end
154
+
155
+ def min_constraint_for_expr(expr, lower_bound)
156
+ "(>= #{expr} #{lower_bound})"
157
+ end
158
+
159
+ def max_constraint_for_expr(expr, upper_bound)
160
+ "(<= #{expr} #{upper_bound})"
161
+ end
162
+
163
+ def regex_constraint_for_expr(expr, regex)
164
+ "(str.in.re #{expr} #{Regexp_to_SMTLIB.translate_regexp_to_SMTLIB(regex)})"
165
+ end
166
+
167
+ def maxLength_contraint_for_expr(expr, maxLength)
168
+ "(<= (str.len #{expr}) #{maxLength})"
169
+ end
170
+
171
+ def oneOf_contraint_for_expr(expr, values)
172
+ list = values.map do |val|
173
+ "(= #{expr} \"#{val}\")"
174
+ end
175
+ "(or #{list.join(" ")})"
176
+ end
177
+
178
+ def enum_constraint_for_expr(expr, values)
179
+ "(or #{values.map{|x| "(= #{expr} #{translate_value_to_SMTLIB(x)})"}.join(" ")})"
180
+ end
181
+
182
+ def translate_field_def_to_SMTLIB_constraints(progress, solver, fielddef, xpath, struct_var)
183
+ varname = raw_field(xpath)
184
+ if fielddef[:optional] == nil || fielddef[:optional] == false then
185
+ # field is required
186
+ assertionID = "required-#{varname}"
187
+ info = {
188
+ :assertion_type => :constraint,
189
+ :xpath => xpath,
190
+ :constraint_type => :required
191
+ }
192
+ solver.associateAssertionIDWith(assertionID, info)
193
+ solver.to_solver(progress, "(assert (! (=> #{struct_var} #{varname}-filled) :named #{assertionID}))")
194
+ end
195
+ datatype = fielddef[:datatype]
196
+ case datatype
197
+ when :int
198
+ if fielddef[:min] then
199
+ assertionID = "min-#{varname}"
200
+ info = {
201
+ :assertion_type => :constraint,
202
+ :xpath => xpath,
203
+ :datatype => :int,
204
+ :value => fielddef[:min],
205
+ :constraint_type => :min
206
+ }
207
+ solver.associateAssertionIDWith(assertionID, info)
208
+ solver.to_solver(progress, "(assert (! (=> #{varname}-filled #{min_constraint_for_expr("#{varname}-value", translate_value_to_SMTLIB(fielddef[:min]))}) :named #{assertionID}))")
209
+ end
210
+ if fielddef[:max] then
211
+ assertionID = "max-#{varname}"
212
+ info = {
213
+ :assertion_type => :constraint,
214
+ :xpath => xpath,
215
+ :datatype => :int,
216
+ :value => fielddef[:max],
217
+ :constraint_type => :max
218
+ }
219
+ solver.associateAssertionIDWith(assertionID, info)
220
+ solver.to_solver(progress, "(assert (! (=> #{varname}-filled #{max_constraint_for_expr("#{varname}-value", translate_value_to_SMTLIB(fielddef[:max]))}) :named #{assertionID}))")
221
+ end
222
+ when :string
223
+ if fielddef[:regex] then
224
+ assertionID = "regex-#{varname}"
225
+ info = {
226
+ :assertion_type => :constraint,
227
+ :xpath => xpath,
228
+ :datatype => :string,
229
+ :pattern => fielddef[:regex],
230
+ :constraint_type => :regex
231
+ }
232
+ solver.associateAssertionIDWith(assertionID, info)
233
+ solver.to_solver(progress, "(assert (! (=> #{varname}-filled #{regex_constraint_for_expr("#{varname}-value", fielddef[:regex])}) :named #{assertionID}))")
234
+ end
235
+ # TODO: minLength?
236
+ if fielddef[:maxLength] then
237
+ assertionID = "maxLength-#{varname}"
238
+ info = {
239
+ :assertion_type => :constraint,
240
+ :xpath => xpath,
241
+ :datatype => :string,
242
+ :value => fielddef[:maxLength],
243
+ :constraint_type => :maxLength
244
+ }
245
+ solver.associateAssertionIDWith(assertionID, info)
246
+ solver.to_solver(progress, "(assert (! (=> #{varname}-filled #{maxLength_contraint_for_expr("#{varname}-value", fielddef[:maxLength])}) :named #{assertionID}))")
247
+ end
248
+ if fielddef[:from_list] then
249
+ assertionID = "oneOf-#{varname}"
250
+ list_filename = "#{LISTS_DIR}/#{fielddef[:from_list]}"
251
+ info = {
252
+ :assertion_type => :constraint,
253
+ :xpath => xpath,
254
+ :datatype => :string,
255
+ :filename => list_filename,
256
+ :constraint_type => :from_list
257
+ }
258
+ unless File.exist?(list_filename)
259
+ raise RuntimeError.new("could not find list file #{list_filename}.")
260
+ end
261
+ values = File.read(list_filename).split("\n").map{|v| v.chomp()}
262
+ solver.associateAssertionIDWith(assertionID, info)
263
+ solver.to_solver(progress, "(assert (! (=> #{varname}-filled #{oneOf_contraint_for_expr("#{varname}-value", values)}) :named #{assertionID}))")
264
+ end
265
+ when :enum
266
+ assertionID = "enum-#{varname}"
267
+ info = {
268
+ :assertion_type => :constraint,
269
+ :xpath => xpath,
270
+ :datatype => :enum,
271
+ :values => fielddef[:values],
272
+ :constraint_type => :values
273
+ }
274
+ solver.associateAssertionIDWith(assertionID, info)
275
+ solver.to_solver(progress, "(assert (! (=> #{varname}-filled #{enum_constraint_for_expr("#{varname}-value", fielddef[:values])}) :named #{assertionID}))")
276
+ end
277
+ end
278
+
279
+ def translate_list_def_to_SMTLIB_constraints(progress, solver, fielddef, varname)
280
+ datatype = fielddef[:datatype]
281
+ case datatype
282
+ when :int
283
+ if fielddef[:min] then
284
+ fielddef[:model_maxLength].times do |i|
285
+ solver.to_solver(progress, "(assert (! (=> (< #{i} #{varname}-size) #{min_constraint_for_expr("#{varname}-index-#{i}-value", translate_value_to_SMTLIB(fielddef[:min]))}) :named list-min-#{varname}-list-index-#{i}))")
286
+ end
287
+ end
288
+ if fielddef[:max] then
289
+ fielddef[:model_maxLength].times do |i|
290
+ solver.to_solver(progress, "(assert (! (=> (< #{i} #{varname}-size) #{max_constraint_for_expr("#{varname}-index-#{i}-value", translate_value_to_SMTLIB(fielddef[:max]))}) :named list-max-#{varname}-list-index-#{i}))")
291
+ end
292
+ end
293
+ when :string
294
+ if fielddef[:regex] then
295
+ fielddef[:model_maxLength].times do |i|
296
+ solver.to_solver(progress, "(assert (! (=> (< #{i} #{varname}-size) #{regex_constraint_for_expr("#{varname}-index-#{i}-value", fielddef[:regex])}) :named list-regex-#{varname}-index-#{i}))")
297
+ end
298
+ end
299
+ # TODO: minLength?
300
+ if fielddef[:maxLength] then
301
+ fielddef[:model_maxLength].times do |i|
302
+ solver.to_solver(progress, "(assert (! (=> (< #{i} #{varname}-size) #{maxLength_contraint_for_expr("#{varname}-index-#{i}-value", fielddef[:maxLength])}) :named list-maxLength-#{varname}-index-#{i}))")
303
+ end
304
+ end
305
+ when :enum
306
+ fielddef[:model_maxLength].times do |i|
307
+ solver.to_solver(progress, "(assert (! (=> (< #{i} #{varname}-size) #{enum_constraint_for_expr("#{varname}-index-#{i}-value", fielddef[:values])}) :named list-enum-#{varname}-index-#{i}))")
308
+ end
309
+ end
310
+ end
311
+
312
+ def default_value_for_fielddef(fielddef)
313
+ datatype = fielddef[:datatype]
314
+ case datatype
315
+ when :int
316
+ return "0"
317
+ when :string
318
+ return "".inspect
319
+ when :date
320
+ return "0"
321
+ when :timestamp
322
+ return "0"
323
+ when :enum
324
+ return "".inspect
325
+ else
326
+ raise RuntimeError.new("unknown datatype: #{datatype.inspect}")
327
+ end
328
+ end
329
+
330
+ def translate_structure_to_SMTLIB(progress, solver, struct, hash = {})
331
+ prefix = hash[:prefix]
332
+ config = hash[:config]
333
+ options = hash[:options]
334
+ translate_substructure_to_SMTLIB(progress, solver, struct, hash)
335
+ translate_validation_rules_to_SMTLIB(progress, solver, struct, prefix, config, options)
336
+ if options[:add_SMTLIB] then
337
+ solver.to_solver(progress, "; --- additional SMTLIB Code: ---")
338
+ solver.to_solver(progress, options[:add_SMTLIB])
339
+ end
340
+ end
341
+
342
+ def translate_substructure_to_SMTLIB(progress, solver, struct, hash = {})
343
+ prefix = hash[:prefix]
344
+ struct_varname = hash[:struct_varname]
345
+ config = hash[:config]
346
+ options = hash[:options]
347
+ solver.to_solver(progress, "; --- Structure: ---")
348
+ struct.each do |field, definition|
349
+ if field == :additional_smtlib then
350
+ solver.to_solver(progress, "; additional SMTLIB of structure #{prefix}")
351
+ solver.to_solver(progress, translate_validation_rule_to_SMTLIB(definition, struct, prefix, config, options, "<additional SMTLIB of structure #{prefix}>"))
352
+ elsif field == :validation_rules || field == :predicates || field == :parent_link || field == :struct_xpath_prefix
353
+ next
354
+ else
355
+ solver.to_solver(progress, "; #{field.to_s}")
356
+ if definition[:type] == :field then
357
+ display_prefix = ""
358
+ display_prefix = "#{prefix}/" if prefix
359
+ xpath = field.to_s
360
+ xpath = "#{prefix}/#{xpath}" if prefix
361
+ solver.declare_const(progress, filled_var_for_field(xpath), "Bool", {:xpath => xpath})
362
+ solver.declare_const(progress, value_var_for_field(xpath), translate_datatype_to_SMTLIB(definition[:datatype]), {:xpath => xpath, :datatype => definition[:datatype]})
363
+ solver.to_solver(progress, "(assert (! (=> (not #{filled_var_for_field(xpath)}) (= #{value_var_for_field(xpath)} #{default_value_for_fielddef(definition)})) :named empty-field-has-no-value-for-#{raw_field(xpath)}))")
364
+ if (struct_varname)
365
+ solver.to_solver(progress, "(assert (! (=> (not #{struct_varname}) (not #{filled_var_for_field(xpath)})) :named empty-struct-has-no-fields-for-#{raw_field(xpath)}))")
366
+ end
367
+ translate_field_def_to_SMTLIB_constraints(progress, solver, definition, xpath, struct_varname)
368
+ elsif definition[:type] == :list then
369
+ display_prefix = ""
370
+ display_prefix = "#{prefix}/" if prefix
371
+ xpath = field.to_s
372
+ xpath = "#{prefix}/#{xpath}" if prefix
373
+ solver.declare_const(progress, filled_var_for_field(xpath), "Bool", {:xpath => xpath})
374
+ solver.declare_const(progress, size_var_for_list(xpath), "Int", {:xpath => xpath})
375
+ solver.to_solver(progress, "(assert (! (=> (not #{filled_var_for_field(xpath)}) (= #{size_var_for_list(xpath)} 0)) :named empty-list-field-has-zero-size-#{raw_field(xpath)}))")
376
+ solver.to_solver(progress, "(assert (! (=> (= #{size_var_for_list(xpath)} 0) (not #{filled_var_for_field(xpath)})) :named list-has-zero-size-implies-empty-list-#{raw_field(xpath)}))")
377
+ solver.to_solver(progress, "(assert (! (=> #{filled_var_for_field(xpath)} (> #{size_var_for_list(xpath)} 0)) :named filled-list-field-has-positive-size-#{raw_field(xpath)}))")
378
+ solver.to_solver(progress, "(assert (! (=> (> #{size_var_for_list(xpath)} 0) #{filled_var_for_field(xpath)}) :named list-has-positive-size-implies-filled-list-#{raw_field(xpath)}))")
379
+ definition[:model_maxLength].times do |i|
380
+ solver.declare_const(progress, value_var_for_list(xpath, i), translate_datatype_to_SMTLIB(definition[:datatype]), {:xpath => xpath, :xpath_element => definition[:xpath_element], :index => i, :datatype => definition[:datatype]})
381
+ solver.to_solver(progress, "(assert (! (=> (<= #{size_var_for_list(xpath)} #{i}) (= #{value_var_for_list(xpath, i)} #{default_value_for_fielddef(definition)})) :named empty-list-field-has-no-value-for-#{raw_field(xpath)}-index-#{i}))")
382
+ end
383
+ if (struct_varname)
384
+ solver.to_solver(progress, "(assert (! (=> (not #{struct_varname}) (= #{size_var_for_list(xpath)} 0)) :named empty-struct-has-no-fields-for-#{raw_field(xpath)}))")
385
+ end
386
+ translate_list_def_to_SMTLIB_constraints(progress, solver, definition, raw_field(xpath))
387
+ elsif definition[:type] == :objlist then
388
+ xpath = field.to_s
389
+ xpath = "#{prefix}/#{xpath}" if prefix
390
+ solver.declare_const(progress, filled_var_for_field(xpath), "Bool", {:xpath => xpath})
391
+ solver.declare_const(progress, size_var_for_list(xpath), "Int", {:xpath => xpath})
392
+ if (struct_varname)
393
+ solver.to_solver(progress, "(assert (! (=> (not #{struct_varname}) (not #{filled_var_for_field(xpath)})) :named empty-struct-has-no-objlist-#{raw_field(xpath)}))")
394
+ end
395
+ if definition[:optional] == nil || definition[:optional] == false then
396
+ solver.to_solver(progress, "(assert (! (=> #{struct_varname} #{filled_var_for_field(xpath)}) :named required-objlist-#{raw_field(xpath)}))")
397
+ end
398
+ solver.to_solver(progress, "(assert (! (=> (not #{filled_var_for_field(xpath)}) (= #{size_var_for_list(xpath)} 0)) :named empty-objlist-has-zero-size-#{raw_field(xpath)}))")
399
+ solver.to_solver(progress, "(assert (! (=> (= #{size_var_for_list(xpath)} 0) (not #{filled_var_for_field(xpath)})) :named objlist-has-zero-size-implies-empty-objlist-#{raw_field(xpath)}))")
400
+ minSize = 0
401
+ solver.to_solver(progress, "(assert (! (=> #{filled_var_for_field(xpath)} (and (> #{size_var_for_list(xpath)} #{minSize}) (< #{size_var_for_list(xpath)} #{definition[:model_maxLength]}))) :named filled-objlist-has-positive-size-#{raw_field(xpath)}))")
402
+ solver.to_solver(progress, "(assert (! (=> (> #{size_var_for_list(xpath)} 0) #{filled_var_for_field(xpath)}) :named objlist-has-positive-size-implies-filled-objlist-#{raw_field(xpath)}))")
403
+ definition[:model_maxLength].times do |i|
404
+ ext_xpath = "#{xpath}/#{i}"
405
+ solver.declare_const(progress, exists_var_for_structure(xpath, i), "Bool", {:xpath => ext_xpath})
406
+ solver.to_solver(progress, "(assert (! (=> (<= #{size_var_for_list(xpath)} #{i}) (not #{exists_var_for_structure(xpath, i)})) :named objlist-struct-exist1-for-#{raw_field(xpath)}-index-#{i}))")
407
+ solver.to_solver(progress, "(assert (! (=> (> #{size_var_for_list(xpath)} #{i}) #{exists_var_for_structure(xpath, i)}) :named objlist-struct-exist2-for-#{raw_field(xpath)}-index-#{i}))")
408
+ translate_substructure_to_SMTLIB(progress, solver, definition[:ref], :prefix => ext_xpath, :struct_varname => exists_var_for_structure(xpath, i), :options => options, :config => config)
409
+ end
410
+ elsif definition[:type] == :structure then
411
+ xpath = field.to_s
412
+ xpath = "#{prefix}/#{xpath}" if prefix
413
+ solver.declare_const(progress, exists_var_for_structure(xpath), "Bool", {:xpath => xpath})
414
+ if (struct_varname)
415
+ solver.to_solver(progress, "(assert (! (=> (not #{struct_varname}) (not #{exists_var_for_structure(xpath)})) :named empty-struct-has-no-substructure-#{raw_field(xpath)}))")
416
+ end
417
+ translate_substructure_to_SMTLIB(progress, solver, definition[:ref], :prefix => xpath, :struct_varname => exists_var_for_structure(xpath), :options => options, :config => config)
418
+ else
419
+ raise RuntimeError.new("Model Error: encountered unknown :type #{definition[:type].inspect} for element #{field.inspect}")
420
+ end
421
+ end
422
+ end
423
+ end
424
+
425
+ def resolve_field_path(path, relative_to_struct, prefix)
426
+ current = relative_to_struct
427
+ result = nil
428
+ path.split("/").each do |segment|
429
+ unless current then
430
+ raise RuntimeError.new("could not resolve segment #{segment.inspect} of field path: #{path.inspect}: resolution worked up until #{result.inspect}, but then the last segment was a field or list.")
431
+ end
432
+ field_def = current[segment.to_sym]
433
+ unless field_def then
434
+ raise RuntimeError.new("could not resolve segment #{segment.inspect} of field path: #{path.inspect}: structure #{prefix.inspect} does not have a member #{segment.to_sym.inspect}.")
435
+ end
436
+ if field_def[:type] == :structure then
437
+ unless field_def[:ref] then
438
+ raise RuntimeError.new("could not resolve field path: #{path.inspect}: substructure #{segment.inspect} of structure #{prefix.inspect} does not have a :ref.")
439
+ end
440
+ current = field_def[:ref]
441
+ if prefix then
442
+ prefix = "#{prefix}/#{segment}"
443
+ else
444
+ prefix = segment
445
+ end
446
+ elsif field_def[:type] == :field then
447
+ current = nil
448
+ result = segment
449
+ if prefix
450
+ result = "#{prefix}/#{result}"
451
+ end
452
+ elsif field_def[:type] == :list then
453
+ current = nil
454
+ result = segment
455
+ if prefix
456
+ result = "#{prefix}/#{result}"
457
+ end
458
+ end
459
+ end
460
+ return result
461
+ end
462
+
463
+ def resolve_field_def(path, relative_to_struct, prefix)
464
+ current = relative_to_struct
465
+ result = nil
466
+ path.split("/").each do |segment|
467
+ unless current then
468
+ raise RuntimeError.new("could not resolve segment #{segment.inspect} of field path: #{path.inspect}: resolution worked up until #{result.inspect}, but then the last segment was a field or list.")
469
+ end
470
+ field_def = current[segment.to_sym]
471
+ unless field_def then
472
+ raise RuntimeError.new("could not resolve segment #{segment.inspect} of field path: #{path.inspect}: structure #{prefix.inspect} does not have a member #{segment.to_sym.inspect}.")
473
+ end
474
+ if field_def[:type] == :structure then
475
+ unless field_def[:ref] then
476
+ raise RuntimeError.new("could not resolve field path: #{path.inspect}: substructure #{segment.inspect} of structure #{prefix.inspect} does not have a :ref.")
477
+ end
478
+ current = field_def[:ref]
479
+ if prefix then
480
+ prefix = "#{prefix}/#{segment}"
481
+ else
482
+ prefix = segment
483
+ end
484
+ elsif field_def[:type] == :field then
485
+ current = nil
486
+ result = field_def
487
+ elsif field_def[:type] == :list then
488
+ current = nil
489
+ result = field_def
490
+ end
491
+ end
492
+ return result
493
+ end
494
+
495
+ def resolve_struct_path(path, relative_to_struct, prefix)
496
+ current = relative_to_struct
497
+ result = nil
498
+ path.split("/").each do |segment|
499
+ unless current then
500
+ raise RuntimeError.new("could not resolve field path: #{path.inspect} relative to struct #{relative_to_struct.inspect}")
501
+ end
502
+ if segment == "." then
503
+ result = current[:struct_xpath_prefix]
504
+ elsif segment == ".." then
505
+ current = current[:parent_link]
506
+ if prefix then
507
+ prefix_segs = prefix.split("/")
508
+ prefix = prefix_segs[0,prefix_segs.size()-1].join("/")
509
+ else
510
+ prefix = current[:struct_xpath_prefix]
511
+ end
512
+ else
513
+ field_def = current[segment.to_sym]
514
+ unless field_def then
515
+ raise RuntimeError.new("could not resolve segment #{segment.inspect} of field path: #{path.inspect} relative to struct #{relative_to_struct.inspect}")
516
+ end
517
+ if field_def[:type] == :structure then
518
+ current = field_def[:ref]
519
+ if prefix then
520
+ prefix = "#{prefix}/#{segment}"
521
+ else
522
+ prefix = segment
523
+ end
524
+ result = prefix
525
+ end
526
+ end
527
+ end
528
+ return result
529
+ end
530
+
531
+ def resolve_struct_def(path, relative_to_struct, prefix, context_desc = "")
532
+ current = relative_to_struct
533
+ result = nil
534
+ path.split("/").each do |segment|
535
+ unless current then
536
+ raise RuntimeError.new("could not resolve field path: #{path.inspect} relative to struct #{relative_to_struct.inspect} #{context_desc}")
537
+ end
538
+ if segment == "." then
539
+ result = current
540
+ elsif segment == ".." then
541
+ current = current[:parent_link]
542
+ if prefix then
543
+ prefix_segs = prefix.split("/")
544
+ prefix = prefix_segs[0,prefix_segs.size()-1].join("/")
545
+ else
546
+ prefix = current[:struct_xpath_prefix]
547
+ end
548
+ else
549
+ field_def = current[segment.to_sym]
550
+ unless field_def then
551
+ raise RuntimeError.new("could not resolve segment #{segment.inspect} of field path: #{path.inspect} relative to struct #{relative_to_struct.inspect} #{context_desc}")
552
+ end
553
+ if field_def[:type] == :structure then
554
+ current = field_def[:ref]
555
+ if prefix then
556
+ prefix = "#{prefix}/#{segment}"
557
+ else
558
+ prefix = segment
559
+ end
560
+ result = current
561
+ end
562
+ end
563
+ end
564
+ return result
565
+ end
566
+
567
+ def translate_validation_rules_to_SMTLIB(progress, solver, struct, prefix, config, options)
568
+ solver.to_solver(progress, "; --- Validation Rules ---")
569
+ translate_validation_rules_for_structure_to_SMTLIB(progress, solver, struct, prefix, config, options)
570
+ struct.each_pair do |key, value|
571
+ next if key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
572
+ if value[:type] == :structure then
573
+ if prefix then
574
+ new_prefix = "#{prefix.to_s}/#{key}"
575
+ else
576
+ new_prefix = key.to_s
577
+ end
578
+ translate_validation_rules_to_SMTLIB(progress, solver, value[:ref], new_prefix, config, options)
579
+ end
580
+ end
581
+ end
582
+
583
+ def translate_validation_rule_to_SMTLIB(rule, context_struct, prefix, config, options, rule_name)
584
+ date_now = options[:date_now]
585
+ timestamp_now = options[:timestamp_now]
586
+ return rule.gsub(/FILLED\[[^\]]*\]/) do |m|
587
+ unless m =~ /FILLED\[([^\]]*)\]/ then
588
+ raise RuntimeError.new("could not parse FILLED[...]: #{m.inspect} in validation rule #{rule_name}")
589
+ end
590
+ field = resolve_field_path($1, context_struct, prefix)
591
+ filled_var_for_field(field)
592
+ end
593
+ .gsub(/VALUE\[[^\]]*\]/) do |m|
594
+ unless m =~ /VALUE\[([^\]]*)\]/ then
595
+ raise RuntimeError.new("could not parse VALUE[...]: #{m.inspect} in validation rule #{rule_name}")
596
+ end
597
+ field = resolve_field_path($1, context_struct, prefix)
598
+ value_var_for_field(field)
599
+ end
600
+ .gsub(/EXIST\[[^\]]*\]/) do |m|
601
+ unless m =~ /EXIST\[([^\]]*)\]/ then
602
+ raise RuntimeError.new("could not parse EXIST[...]: #{m.inspect} in validation rule #{rule_name}")
603
+ end
604
+ s = resolve_struct_path($1, context_struct, prefix)
605
+ exists_var_for_structure(s)
606
+ end
607
+ .gsub(/SIZE\[[^\]]*\]/) do |m|
608
+ unless m =~ /SIZE\[([^\]]*)\]/ then
609
+ raise RuntimeError.new("could not parse SIZE[...]: #{m.inspect} in validation rule #{rule_name}")
610
+ end
611
+ l = resolve_field_path($1, context_struct, prefix)
612
+ size_var_for_list(l)
613
+ end
614
+ .gsub(/LIST_CONTAINS\[[^\]]*,[^\]]*\]/) do |m|
615
+ unless m =~ /LIST_CONTAINS\[([^\]]*),([^\]]*)\]/ then
616
+ raise RuntimeError.new("could not parse LIST_CONTAINS[...]: #{m.inspect} in validation rule #{rule_name}")
617
+ end
618
+ l = resolve_field_path($1, context_struct, prefix)
619
+ l_def = resolve_field_def($1, context_struct, prefix)
620
+ value = $2
621
+ terms = []
622
+ l_def[:model_maxLength].times do |i|
623
+ terms << "(and (< #{i} #{size_var_for_list(l)}) (= #{value_var_for_list(l, i)} #{value}))"
624
+ end
625
+ "(or #{terms.join(" ")})"
626
+ end
627
+ .gsub(/LIST_VALUE_FIRST_ELEMENT\[[^\]]*\]/) do |m|
628
+ unless m =~ /LIST_VALUE_FIRST_ELEMENT\[([^\]]*)\]/ then
629
+ raise RuntimeError.new("could not parse LIST_VALUE_FIRST_ELEMENT[...]: #{m.inspect} in validation rule #{rule_name}")
630
+ end
631
+ xpath = $1
632
+ l = resolve_field_path(xpath, context_struct, prefix)
633
+ value_var_for_list(l, 0)
634
+ end
635
+ .gsub("[Date.now]", date_now.to_s)
636
+ .gsub("[Timestamp.now]", timestamp_now.to_s)
637
+ .gsub(/PREDICATE\[[^\]]*,[^\]]*\]/) do |m|
638
+ unless m =~ /PREDICATE\[([^\]]*),([^\]]*)\]/ then
639
+ raise RuntimeError.new("could not parse PREDICATE[...]: #{m.inspect} in validation rule #{rule_name}")
640
+ end
641
+ path = $1
642
+ key = $2.strip().to_sym
643
+ sdef = resolve_struct_def(path, context_struct, prefix, "in validation rule #{rule_name}")
644
+ unless sdef
645
+ raise RuntimeError.new("could not resolve struct path #{path.inspect} in validation rule #{rule_name}")
646
+ end
647
+ preds = sdef[:predicates]
648
+ mdk = preds[key]
649
+ if mdk then
650
+ if mdk[:smtlib] then
651
+ translate_validation_rule_to_SMTLIB(mdk[:smtlib], sdef, sdef[:struct_xpath_prefix], config, options, "#{rule_name}.predicate.#{key}[#{sdef[:struct_xpath_prefix]}]" )
652
+ else
653
+ raise RuntimeError.new("Predicate #{key} for structure #{sdef[:struct_xpath_prefix]} does not have an SMTLIB definition, but is referenced in validation rule #{rule_name}")
654
+ end
655
+ else
656
+ raise RuntimeError.new("No predicate #{key.inspect} defined for structure #{sdef[:struct_xpath_prefix]}, but one referenced in validation rule #{rule_name}")
657
+ end
658
+ end
659
+ .gsub(/CONFIG\[[^\]]*\]/) do |m|
660
+ unless m =~ /CONFIG\[([^\]]*)\]/ then
661
+ raise RuntimeError.new("could not parse LIST_CONTAINS[...]: #{m.inspect} in validation rule #{rule_name}")
662
+ end
663
+ key = $1.to_sym
664
+ if config.has_key?(key) then
665
+ "\"#{config[key]}\""
666
+ else
667
+ raise RuntimeError.new("unknown CONFIG-key #{key.inspect} encountered in validation rule #{rule_name}" )
668
+ end
669
+ end
670
+ .gsub(/FIELD_IN_QUERY\[[^\]]*\]/) do |m|
671
+ unless m =~ /FIELD_IN_QUERY\[([^\]]*),([^\]]*)\]/ then
672
+ raise RuntimeError.new("could not parse FIELD_IN_QUERY[..,..]: #{m.inspect} in validation rule #{rule_name}")
673
+ end
674
+ xpath = $1
675
+ query_filename = $2
676
+ fpath = resolve_field_path(xpath, context_struct, prefix)
677
+ fdef = resolve_field_def(xpath, context_struct, prefix)
678
+ values = File.read("#{QUERY_DIR}/#{query_filename.strip()}").split("\n")
679
+ if fdef[:datatype] == :string then
680
+ terms = values.filter{|l| !l.start_with?("#")}.map do |val|
681
+ "(= #{value_var_for_field(fpath)} \"#{val}\")"
682
+ end
683
+ elsif fdef[:datatype] == :int then
684
+ terms = values.filter{|l| !l.start_with?("#")}.map do |val|
685
+ "(= #{value_var_for_field(fpath)} #{Integer(val)})"
686
+ end
687
+ else
688
+ raise RuntimeError.new("FIELD_IN_QUERY[] was applied to a field of datatype #{fdef[:datatype].inspect}. However, it can only be applied to fields of datatype string or int.")
689
+ end
690
+ "(or #{terms.join(" ")})"
691
+ end
692
+ .gsub(/FIELD_IN_SMTLIB_PARAMETERIZED_QUERY\[[^\]]*\]/) do |m|
693
+ unless m =~ /FIELD_IN_SMTLIB_PARAMETERIZED_QUERY\[([^\]]*),([^\]]*),([^\]]*)\]/ then
694
+ raise RuntimeError.new("could not parse FIELD_IN_SMTLIB_PARAMETERIZED_QUERY[..,..,..]: #{m.inspect} in validation rule #{rule_name}")
695
+ end
696
+ field_xpath = $1
697
+ param_smtlib = $2
698
+ query_filename = $3
699
+ fpath = resolve_field_path(field_xpath.strip(), context_struct, prefix)
700
+ fdef = resolve_field_def(field_xpath.strip(), context_struct, prefix)
701
+ query_result = YAML.load_file("#{PARAMETERIZED_QUERY_DIR}/#{query_filename.strip()}")
702
+ terms = []
703
+ if fdef[:datatype] == :string then
704
+ terms << "(or #{query_result.keys.map { |key| "(= #{param_smtlib} \"#{key}\")" }.join(" ")})"
705
+ query_result.each_pair do |key, values|
706
+ terms << "(=> (= #{param_smtlib} \"#{key}\") (or #{values.map{ |v| "(= #{value_var_for_field(fpath)} \"#{v}\")" }.join(" ")}))"
707
+ end
708
+ else
709
+ raise RuntimeError.new("FIELD_IN_SMTLIB_PARAMETERIZED_QUERY[] was applied to a field of datatype #{fdef[:datatype].inspect}. However, it can only be applied to fields of datatype string.")
710
+ end
711
+ "(and #{terms.join(" ")})"
712
+ end
713
+ .gsub(/CONFIG_IN_QUERY\[[^\]]*\]/) do |m|
714
+ unless m =~ /CONFIG_IN_QUERY\[([^\]]*),([^\]]*)\]/ then
715
+ raise RuntimeError.new("could not parse CONFIG_IN_QUERY[..,..]: #{m.inspect} in validation rule #{rule_name}")
716
+ end
717
+ key = $1.to_sym
718
+ unless config.has_key?(key) then
719
+ raise RuntimeError.new("unknown CONFIG key #{key.inspect} encountered in validation rule #{rule_name}")
720
+ end
721
+ query_filename = $2
722
+ values = File.read("#{QUERY_DIR}/#{query_filename.strip()}").split("\n")
723
+ terms = values.filter{|l| !l.start_with?("#")}.map do |val|
724
+ "(= \"#{config[key]}\" \"#{val}\")"
725
+ end
726
+ "(or #{terms.join(" ")})"
727
+ end
728
+ end
729
+
730
+ def translate_validation_rules_for_structure_to_SMTLIB(progress, solver, struct, prefix, config, options)
731
+ return unless struct[:validation_rules]
732
+
733
+ solver.to_solver(progress, "; --- Validation Rules: ---")
734
+ negated_rules = options[:negated_validation_rules] || []
735
+ solver.to_solver(progress, "; negated rules: #{negated_rules.empty? ? "<none>" : negated_rules.join(", ")}")
736
+
737
+ rules = struct[:validation_rules]
738
+ rules.each do |rule_name, rule|
739
+ rule = rule[:smtlib] # ignore the text representation
740
+ next unless rule # warning about this omission is the job of validate_model()
741
+ rule_SMTLIB = translate_validation_rule_to_SMTLIB(rule, struct, prefix, config, options, rule_name)
742
+ if negated_rules.include?(rule_name) then
743
+ rule_SMTLIB = "(not #{rule_SMTLIB})"
744
+ end
745
+ solver.to_solver(progress, "(assert (! #{rule_SMTLIB} :named validation_rule_#{rule_name}_instance_#{prefix.gsub("/", "_")}))")
746
+ end
747
+ end
748
+
749
+ def parse_field_value(fielddef, value)
750
+ value = value.gsub("\n", "")
751
+ datatype = fielddef[:datatype]
752
+ case datatype
753
+ when :int
754
+ return Integer(value)
755
+ when :string
756
+ return value
757
+ when :enum
758
+ return value
759
+ when :date
760
+ return Date.parse(value)
761
+ when :timestamp
762
+ return Time.new(value)
763
+ end
764
+ end
765
+
766
+ def solver_value_to_string(solver_value)
767
+ if solver_value =~ /\"(.*)\"/ then
768
+ return $1
769
+ else
770
+ return solver_value
771
+ end
772
+ end
773
+
774
+ def build_document_from_model(progress, struct, model)
775
+ doc = Nokogiri::XML(XML_TEMPLATE)
776
+ add_model_struct_to_doc(progress, struct, doc, model)
777
+ return doc
778
+ end
779
+
780
+ def add_model_struct_to_doc(progress, struct, doc, model, prefix = nil)
781
+ struct.each do |key, value|
782
+ next if key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
783
+ if prefix then
784
+ xpath = "#{prefix}/#{key}"
785
+ else
786
+ xpath = key.to_s
787
+ end
788
+ model_value = model[xpath]
789
+ if value[:type] == :field then
790
+ unless model_value then
791
+ raise RuntimeError.new("xpath '#{xpath}' not contained in model.")
792
+ end
793
+ if model_value[:filled] == true || model_value[:filled] == "true" then
794
+ doc.add_child("<#{key}>#{solver_value_to_string(model_value[:value])}</#{key}>")
795
+ end
796
+ elsif value[:type] == :list then
797
+ node = doc.add_child("<#{key}></#{key}>")
798
+ if node.is_a?(Nokogiri::XML::NodeSet) then
799
+ node = node.first
800
+ end
801
+ xpath_element = value[:xpath_element]
802
+ if Integer(model_value[:size]) > 0 then
803
+ model_value[:value].slice(0, Integer(model_value[:size])).each do |mvalue|
804
+ node.add_child("<#{xpath_element}>#{solver_value_to_string(mvalue)}</#{xpath_element}>")
805
+ end
806
+ end
807
+ elsif value[:type] == :objlist then
808
+ node = doc.add_child("<#{key}></#{key}>")
809
+ if node.is_a?(Nokogiri::XML::NodeSet) then
810
+ node = node.first
811
+ end
812
+ xpath_element = value[:xpath_element]
813
+ if Integer(model_value[:size]) > 0 then
814
+ model_value[:size].times do |index|
815
+ cnode = node.add_child("<#{xpath_element}></#{xpath_element}>")
816
+ if cnode.is_a?(Nokogiri::XML::NodeSet) then
817
+ cnode = cnode.first
818
+ end
819
+ add_model_struct_to_doc(progress, value[:ref], cnode, model, "#{xpath}/#{index}")
820
+ end
821
+ end
822
+ elsif value[:type] == :structure then
823
+ unless value[:ref] then
824
+ raise RuntimeError.new("struct is broken: :structure #{key.inspect} does not have a :ref")
825
+ end
826
+ if model_value[:exists] == true || model_value[:exists] == "true" then
827
+ node = doc.add_child("<#{key}></#{key}>")
828
+ if node.is_a?(Nokogiri::XML::NodeSet) then
829
+ node = node.first
830
+ end
831
+ add_model_struct_to_doc(progress, value[:ref], node, model, xpath)
832
+ end
833
+ else
834
+ raise RuntimeError.new("struct is broken: encountered unknown :type #{value[:type].inspect}")
835
+ end
836
+ end
837
+ end
838
+
839
+ def collect_validation_rule_names(struct)
840
+ result = []
841
+ struct.each do |key, value|
842
+ next if key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
843
+ if key == :validation_rules
844
+ value.each do |key, value|
845
+ result << key
846
+ end
847
+ else
848
+ if value[:type] == :structure
849
+ result += collect_validation_rule_names(value[:ref])
850
+ end
851
+ end
852
+ end
853
+ return result
854
+ end
855
+
856
+ def set_standard_options(progress, solver)
857
+ solver.to_solver(progress, "(set-option :produce-unsat-cores true) ; enable generation of unsat cores")
858
+ solver.to_solver(progress, "(set-option :smt.core.minimize true) ; ensure that unsat cores are minimal")
859
+ solver.to_solver(progress, "")
860
+ end
861
+
862
+ def generate_doc(progress, message, output_filename, config, options)
863
+ raise RuntimeError.new("option :solverFactory is required") unless options[:solverFactory]
864
+ log = options[:log] || nil
865
+ log.puts "generating doc #{output_filename}" if log
866
+ options[:solverLog] = options[:solverLog] || "#{output_filename}.smt2"
867
+
868
+ solverFactory = options[:solverFactory]
869
+ model = solverFactory.query_model(options) do |solver|
870
+ set_standard_options(progress, solver)
871
+ log.puts "translating structure" if log
872
+ translate_structure_to_SMTLIB(progress, solver, message, :options => options, :config => config)
873
+ solver.to_solver(progress, "")
874
+ end
875
+
876
+ if model then
877
+ result_doc = build_document_from_model(progress, message, model)
878
+ dir = File.dirname(output_filename)
879
+ FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
880
+ File.open(output_filename, "w") do |f|
881
+ f.puts result_doc.to_xml()
882
+ end
883
+ progress.print_line "DONE. Generated XML was written to #{output_filename}, SMTLIB log was written to #{options[:solverLog]}"
884
+ return true
885
+ else
886
+ progress.print_line "FAILED. No XML document was generated. For more details, please see the SMTLIB log in #{options[:solverLog]}"
887
+ return false
888
+ end
889
+ end
890
+
891
+ def z3ValueToRubyValue(value)
892
+ if value =~ /\"(.*)\"/ then # string
893
+ return $1
894
+ elsif value =~ /\d+/ then # int
895
+ return Integer(value)
896
+ elsif value == "true"
897
+ return true
898
+ elsif value == "false" then
899
+ return false
900
+ else
901
+ raise RuntimeError.new("enountered unknown type of value in z3 model: #{value.inspect}")
902
+ end
903
+ end
904
+
905
+ def extract_key_field_values_from_model(key_fields, model)
906
+ result = {}
907
+ key_fields.each do |xpath, fieldDesc|
908
+ fieldInfo = model[xpath]
909
+ if fieldDesc[:type] == :field then
910
+ if fieldInfo[:filled] then
911
+ result[xpath] = z3ValueToRubyValue(fieldInfo[:value])
912
+ else
913
+ result[xpath] = nil
914
+ end
915
+ elsif fieldDesc[:type] == :struct || fieldDesc[:type] == :structure then
916
+ if fieldInfo.has_key?(:exists) then
917
+ result[xpath] = z3ValueToRubyValue(fieldInfo[:exists])
918
+ else
919
+ result[xpath] = false
920
+ end
921
+ else
922
+ raise RuntimeError.new("encountered unknown :type in solver model: #{fieldDesc[:type].inspect}")
923
+ end
924
+ end
925
+ return result
926
+ end
927
+
928
+ def all_values_for_field(field)
929
+ unless field[:type] == :field then
930
+ raise RuntimeError.new("something that is not a field definition was passed to all_values_for_field(): #{field.inspect}")
931
+ end
932
+ unless field[:datatype] == :enum then
933
+ raise RuntimeError.new("all_values_for_field() is only applicable to fields of datatype enum! not #{field[:datatype].inspect}.")
934
+ end
935
+ values = field[:values].dup
936
+ if field[:optional] == true then
937
+ values << nil
938
+ end
939
+ return values
940
+ end
941
+
942
+ def calc_combinations_for_key_fields(key_fields)
943
+ count = 1
944
+ key_fields.each do |field|
945
+ if field[1][:type] == :field then
946
+ if field[1][:datatype] == :enum then
947
+ num_values = field[1][:values].size
948
+ num_values += 1 if field[1][:optional]
949
+ elsif field[1][:datatype] == :int then
950
+ if field[1].has_key?(:min) and field[1].has_key?(:max) and field[1][:min] <= field[1][:max] then
951
+ if field[1][:optional] then
952
+ num_values = 2 + field[1][:max] - field[1][:min]
953
+ else
954
+ num_values = 1 + field[1][:max] - field[1][:min]
955
+ end
956
+ else
957
+ return "potentially infinite"
958
+ end
959
+ elsif field[1][:datatype] == :string then
960
+ return "potentially infinite"
961
+ end
962
+ elsif field[1][:type] == :structure then
963
+ num_values = 2
964
+ end
965
+ count *= num_values
966
+ end
967
+ return count
968
+ end
969
+
970
+ def calc_combinations(message, key)
971
+ key_fields = collect_key_fields(key, message)
972
+ if key_fields.empty?
973
+ raise FatalError.new("unknown key: #{key}")
974
+ end
975
+ return calc_combinations_for_key_fields(key_fields)
976
+ end
977
+
978
+ def read_docs_from_log_file(log_filename)
979
+ result = []
980
+ if File.exist?(log_filename) then
981
+ File.open(log_filename, "r") do |f|
982
+ f.each_line do |line|
983
+ doc = {}
984
+ if line =~ /(.*)\ <-\ \{(.*)\}/ then
985
+ filename = $1
986
+ combination = $2
987
+ combination.split(", ").each do |assignment|
988
+ if assignment =~ /(.*):\ (.*)/ then
989
+ key = $1
990
+ value = $2
991
+ if value =~ /\"(.*)\"/ then
992
+ value = $1
993
+ elsif value == "nil" then
994
+ value = nil
995
+ end
996
+ doc[key] = value
997
+ else
998
+ raise RuntimeError.new("could not parse logfile #{log_filename}: invalid assignment #{assignment.inspect}")
999
+ end
1000
+ end
1001
+ else
1002
+ raise RuntimeError.new("could not parse logfile #{log_filename}: invalid line #{line.inspect}")
1003
+ end
1004
+ result << doc
1005
+ end
1006
+ end
1007
+ end
1008
+ return result
1009
+ end
1010
+
1011
+ def prepare_output_doc_filename(number, output_dir)
1012
+ chunk_number = number / 100000
1013
+ dirname = "#{output_dir}/#{chunk_number}"
1014
+ unless Dir.exist?(dirname) then
1015
+ FileUtils.mkdir_p(dirname)
1016
+ end
1017
+ return "#{dirname}/doc#{number}.xml"
1018
+ end
1019
+
1020
+ def values_for_key_field(field)
1021
+ if field[:type] == :field then
1022
+ if field[:datatype] == :enum then
1023
+ if field[:optional] then
1024
+ return field[:values] + [nil]
1025
+ else
1026
+ return field[:values]
1027
+ end
1028
+ elsif field[:datatype] == :int then
1029
+ if field.has_key?(:min) and field.has_key?(:max) and field[:min] <= field[:max] then
1030
+ return (field[:min]..field[:max]).to_a
1031
+ else
1032
+ raise RuntimeError.new("cannot determine values for unbounded :int field")
1033
+ end
1034
+ else
1035
+ raise RuntimeError.new("cannot determine values for datatype #{field[:datatype].inspect}")
1036
+ end
1037
+ elsif field[:type] == :structure then
1038
+ if field[:optional] then
1039
+ return [true, false]
1040
+ else
1041
+ return [true]
1042
+ end
1043
+ else
1044
+ raise RuntimeError.new("Encountered unknown :type in model: #{field[:type].inspect} - keys can only mark fields or structures.")
1045
+ end
1046
+ end
1047
+
1048
+ def collect_key_fields(fkey, struct, prefix = nil)
1049
+ result = {}
1050
+ struct.each do |key, value|
1051
+ next if key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
1052
+ if value[:type] == :structure
1053
+ if value.has_key?(:keys) and value[:keys].include?(fkey) then
1054
+ xpath = key.to_s
1055
+ xpath = "#{prefix}/#{xpath}" if prefix
1056
+ result[xpath] = value.clone().filter{|k,v| [:type, :optional, :datatype, :values].include?(k)}
1057
+ end
1058
+ if prefix then
1059
+ new_prefix = "#{prefix}/#{key}"
1060
+ else
1061
+ new_prefix = key
1062
+ end
1063
+ result = result.merge(collect_key_fields(fkey, value[:ref], new_prefix))
1064
+ elsif value[:type] == :field and value.has_key?(:keys) and value[:keys].include?(fkey) then
1065
+ xpath = key.to_s
1066
+ xpath = "#{prefix}/#{xpath}" if prefix
1067
+ result[xpath] = value.clone().filter{|k,v| [:type, :optional, :datatype, :values, :min, :max].include?(k)}
1068
+ end
1069
+ end
1070
+ return result
1071
+ end
1072
+
1073
+ def collect_key_fields_and_strctures(fkey, struct, prefix = nil)
1074
+ fields = []
1075
+ structures = []
1076
+ struct.each do |key, value|
1077
+ next if key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
1078
+ if value[:type] == :structure
1079
+ if value.has_key?(:keys) and value[:keys].include?(fkey) then
1080
+ xpath = key.to_s
1081
+ xpath = "#{prefix}/#{xpath}" if prefix
1082
+ structures << xpath
1083
+ end
1084
+ if prefix then
1085
+ new_prefix = "#{prefix}/#{key}"
1086
+ else
1087
+ new_prefix = key
1088
+ end
1089
+ result_fields, result_structures = collect_key_fields_and_strctures(fkey, value[:ref], new_prefix)
1090
+ fields += result_fields
1091
+ structures += result_structures
1092
+ elsif value[:type] == :field and value.has_key?(:keys) and value[:keys].include?(fkey) then
1093
+ xpath = key.to_s
1094
+ xpath = "#{prefix}/#{xpath}" if prefix
1095
+ fields << xpath
1096
+ end
1097
+ end
1098
+ return fields, structures
1099
+ end
1100
+
1101
+ def collect_keys(struct)
1102
+ result = []
1103
+ struct.each do |key, value|
1104
+ next if key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
1105
+ if value[:type] == :structure
1106
+ if value.has_key?(:keys) then
1107
+ result += value[:keys]
1108
+ end
1109
+ if value[:ref] then
1110
+ result += collect_keys(value[:ref])
1111
+ else
1112
+ raise RuntimeError.new("Model error: encountered structure without :ref: key=#{key.inspect} value=#{value.inspect}")
1113
+ end
1114
+ elsif value[:type] == :field and value.has_key?(:keys) then
1115
+ result += value[:keys]
1116
+ end
1117
+ end
1118
+ result.uniq
1119
+ end
1120
+
1121
+ def collect_validation_rules(struct)
1122
+ result = []
1123
+ struct.each do |key, value|
1124
+ next if key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
1125
+ if key == :validation_rules then
1126
+ result += value.keys
1127
+ else
1128
+ if value[:type] == :structure
1129
+ if value[:ref] then
1130
+ result += collect_validation_rules(value[:ref])
1131
+ else
1132
+ raise RuntimeError.new("Model error: encountered structure without :ref: key=#{key.inspect} value=#{value.inspect}")
1133
+ end
1134
+ end
1135
+ end
1136
+ end
1137
+ result.uniq
1138
+ end
1139
+
1140
+ def list_keys(message)
1141
+ keys = collect_keys(message)
1142
+ puts "keys: #{keys.join(", ")}"
1143
+ end
1144
+
1145
+ def list_fields_and_structures(message, fkey)
1146
+ fields, structures = collect_key_fields_and_strctures(fkey, message)
1147
+ puts "fields for key #{fkey}:"
1148
+ fields.each { |f| puts " #{f}" }
1149
+ puts "structures for key #{fkey}:"
1150
+ structures.each { |s| puts " #{s}" }
1151
+ end
1152
+
1153
+ def list_validation_rules(message)
1154
+ rules = collect_validation_rules(message)
1155
+ puts "Validation Rules:"
1156
+ rules.each { |r| puts " #{r}" }
1157
+ end
1158
+
1159
+ def value_of_type(value, type)
1160
+ if type == :string or type == :enum then
1161
+ "\"#{value}\""
1162
+ elsif type == :int then
1163
+ value.to_s
1164
+ elsif type == :date then
1165
+ (Date.parse(value) - Time.at(0).to_date).to_i.to_s
1166
+ elsif type == :timestamp then
1167
+ (Time.new(value) - Time.at(0)).to_i.to_s
1168
+ else
1169
+ raise RuntimeError.new("encountered unknown :datatype #{type.inspect}")
1170
+ end
1171
+ end
1172
+
1173
+ class CoreEnumerationAlgo
1174
+
1175
+ def initialize()
1176
+ @num_tries = 0
1177
+ @num_docs = 0
1178
+ @on_new_doc = nil
1179
+ @on_unsat = nil
1180
+ end
1181
+
1182
+
1183
+ # fix values of all variables left from switched
1184
+ def enumerate_fixing_clause(assignment, vars, switched)
1185
+ clauses = []
1186
+ switched.times do |index|
1187
+ var_xpath = vars[index][0]
1188
+ var_def = vars[index][1]
1189
+ if var_def[:type] == :field then
1190
+ if assignment[var_xpath] == nil then
1191
+ clauses << "(assert (! (= #{filled_var_for_field(var_xpath)} false) :named fixing_clause_#{index}))"
1192
+ else
1193
+ clauses << "(assert (! (and (= #{filled_var_for_field(var_xpath)} true) (= #{value_var_for_field(var_xpath)} #{value_of_type(assignment[var_xpath], var_def[:datatype])})) :named fixing_clause_#{index}))"
1194
+ end
1195
+ elsif var_def[:type] == :structure then
1196
+ clauses << "(assert (! (= #{exists_var_for_structure(var_xpath)} #{assignment[var_xpath].inspect}) :named fixing_clause_#{index}))"
1197
+ end
1198
+ end
1199
+ "; fixing clauses:\n#{clauses.join("\n")}"
1200
+ end
1201
+
1202
+ def summarize_fixing_clause(assignment, vars, switched)
1203
+ fixed = switched.times.to_a.map do |index|
1204
+ var_xpath = vars[index][0]
1205
+ "#{var_xpath}=#{assignment[var_xpath].inspect}"
1206
+ end
1207
+ "fixed(#{fixed.join(", ")})"
1208
+ end
1209
+
1210
+ # prevent the var from taking values that already have been enumerated exhaustively
1211
+ def enumerate_blocking_clause(var_xpath, var_def, blocked)
1212
+ clauses = []
1213
+ num = 0
1214
+ if var_def[:type] == :field then
1215
+ blocked.each do |value|
1216
+ if value == nil then
1217
+ clauses << "(assert (! (not (= #{filled_var_for_field(var_xpath)} false)) :named blocking_clause_#{num}))"
1218
+ else
1219
+ clauses << "(assert (! (not (and (= #{filled_var_for_field(var_xpath)} true) (= #{value_var_for_field(var_xpath)} #{value_of_type(value, var_def[:datatype])}))) :named blocking_clause_#{num}))"
1220
+ end
1221
+ num += 1
1222
+ end
1223
+ elsif var_def[:type] == :structure then
1224
+ blocked.each do |value|
1225
+ clauses << "(assert (! (not (= #{exists_var_for_structure(var_xpath)} #{value.inspect})) :named blocking_clause_#{num}))"
1226
+ num += 1
1227
+ end
1228
+ end
1229
+ "; blocking clauses:\n#{clauses.join("\n")}"
1230
+ end
1231
+
1232
+ def summarize_blocking_clause(var_xpath, blocked)
1233
+ "blocked(#{var_xpath})=[#{blocked.map{|v| v.inspect}.join(", ")}]"
1234
+ end
1235
+
1236
+ def generate_sparse_doc(progress, message, key, config, options, code, output_doc_filename)
1237
+ additional_options = {}
1238
+ if code then
1239
+ additional_options = { :add_SMTLIB => code }
1240
+ end
1241
+ return generate_doc_return_key_values(progress, message, key, output_doc_filename, config, options.merge(additional_options))
1242
+ end
1243
+
1244
+ def log_filename_for_output_filename(output_filename)
1245
+ "#{output_filename}.smt2"
1246
+ end
1247
+
1248
+ def generate_doc_return_key_values(progress, message, key, output_filename, config, options)
1249
+ raise RuntimeError.new("option :solverFactory is required.") unless options[:solverFactory]
1250
+ log = options[:log] || nil
1251
+ log.puts "generating doc #{output_filename}" if log
1252
+ options[:solverLog] = log_filename_for_output_filename(output_filename)
1253
+
1254
+ key_fields = collect_key_fields(key, message)
1255
+
1256
+ solverFactory = options[:solverFactory]
1257
+ model = solverFactory.query_model(options) do |solver|
1258
+ set_standard_options(progress, solver)
1259
+ log.puts "translating structure" if log
1260
+ translate_structure_to_SMTLIB(progress, solver, message, :options => options, :config => config)
1261
+ solver.to_solver(progress, "")
1262
+ end
1263
+
1264
+ if model then
1265
+ result_doc = build_document_from_model(progress, message, model)
1266
+ dir = File.dirname(output_filename)
1267
+ FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
1268
+ File.open(output_filename, "w") do |f|
1269
+ f.puts result_doc.to_xml(:save_with => Nokogiri::XML::Node::SaveOptions::AS_XML)
1270
+ end
1271
+ progress.print_line "DONE. Generated XML was written to #{output_filename}, SMTLIB log was written to #{options[:solverLog]}"
1272
+ return extract_key_field_values_from_model(key_fields, model)
1273
+ else
1274
+ progress.print_line "FAILED. No XML document was generated. For more details, please see the SMTLIB log in #{options[:solverLog]}"
1275
+ return nil
1276
+ end
1277
+ end
1278
+
1279
+ def store_continuation_point(doc_number, state, assignment, new_assignment, filename, options, time)
1280
+ File.open(filename, "w") do |f|
1281
+ state_str = "state(#{state.map{|s| if s then "[#{s.map{|v| v.inspect}.join(",")}]" else "nil" end}.join("#")})"
1282
+ assignment_str = "assignment(#{assignment.to_a.map{|k,v| "#{k}:#{v.inspect}"}.join(",")})"
1283
+ new_assignment_str = "new_assignment(#{new_assignment.to_a.map{|k,v| "#{k}:#{v.inspect}"}.join(",")})"
1284
+ f.puts("#{doc_number};#{@num_docs};#{state_str};#{assignment_str};#{new_assignment_str};#{time}")
1285
+ end
1286
+ end
1287
+
1288
+ def self.parse_state(state_str)
1289
+ unless state_str =~ /state\((.*)\)/
1290
+ return nil
1291
+ end
1292
+ split = $1.split("#")
1293
+ split.map do |e|
1294
+ if e == "nil" then
1295
+ nil
1296
+ elsif e =~ /\[(.*)\]/ then
1297
+ $1.split(",").map do |i|
1298
+ if i.strip == "nil" then
1299
+ nil
1300
+ elsif i.strip == "true" then
1301
+ true
1302
+ elsif i.strip == "false" then
1303
+ false
1304
+ elsif i.strip =~ /\"(.*)\"/ then
1305
+ $1
1306
+ end
1307
+ end
1308
+ end
1309
+ end
1310
+ end
1311
+
1312
+ def self.parse_assignment(str)
1313
+ unless str =~ /assignment\((.*)\)/
1314
+ return nil
1315
+ end
1316
+ split = $1.split(",")
1317
+ result = {}
1318
+ split.each do |e|
1319
+ s2 = e.split(":")
1320
+ key = s2[0]
1321
+ value = s2[1]
1322
+ if value == "nil" then
1323
+ result[key] = nil
1324
+ elsif value == "true" then
1325
+ result[key] = true
1326
+ elsif value == "false" then
1327
+ result[key] = false
1328
+ elsif value =~ /\"(.*)\"/
1329
+ result[key] = $1
1330
+ end
1331
+ end
1332
+ result
1333
+ end
1334
+
1335
+ def self.parse_new_assignment(str)
1336
+ unless str =~ /new_assignment\((.*)\)/
1337
+ return nil
1338
+ end
1339
+ split = $1.split(",")
1340
+ result = {}
1341
+ split.each do |e|
1342
+ s2 = e.split(":")
1343
+ key = s2[0]
1344
+ value = s2[1]
1345
+ if value == "nil" then
1346
+ result[key] = nil
1347
+ elsif value == "true" then
1348
+ result[key] = true
1349
+ elsif value == "false" then
1350
+ result[key] = false
1351
+ elsif value =~ /\"(.*)\"/
1352
+ result[key] = $1
1353
+ end
1354
+ end
1355
+ result
1356
+ end
1357
+
1358
+ def self.parse_continuation_point(line)
1359
+ split = line.split(";")
1360
+ doc_number = Integer(split[0])
1361
+ num_docs = Integer(split[1])
1362
+ state = CoreEnumerationAlgo.parse_state(split[2])
1363
+ assignment = CoreEnumerationAlgo.parse_assignment(split[3])
1364
+ new_assignment = CoreEnumerationAlgo.parse_assignment(split[4])
1365
+ processessing_time = Integer(split[5])
1366
+ return [doc_number, num_docs, state, assignment, new_assignment, processessing_time]
1367
+ end
1368
+
1369
+ # TODO: logging
1370
+
1371
+ def display_combination_space(assignment, vars, switched, blocked)
1372
+ # enumerate_fixing_clause(assignment, vars, switched) + enumerate_blocking_clause(vars[switched][0], vars[switched][1], blocked)
1373
+
1374
+ # => all vars left of switched are fixed to their assigned values (when the value is nil, then they are empty)
1375
+ # => the switched var is prevented to have any of the blocked values
1376
+ res = switched.times.to_a.map do |index|
1377
+ var_xpath = vars[index][0]
1378
+ "#{var_xpath}=#{assignment[var_xpath].inspect}"
1379
+ end + ["#{vars[switched][0]}\\{#{blocked.map{|v| v.inspect}.join(",")}}"]
1380
+ "{#{res.join(", ")}}"
1381
+ end
1382
+
1383
+
1384
+ # core algo
1385
+ def enumerate(progress, switched, vars, assignment, message, key, output_dir, config, options, state = [nil] * vars.size(), start_time, prior_processing_time)
1386
+ enumerate(progress, switched+1, vars, assignment, message, key, output_dir, config, options, state, start_time, prior_processing_time) if switched < vars.size()-1
1387
+ is_finite_state = keys_are_finite_state(vars)
1388
+ if state[switched] == nil then
1389
+ blocked = [assignment[vars[switched][0]]]
1390
+ state[switched] = blocked
1391
+ else
1392
+ blocked = state[switched] + [assignment[vars[switched][0]]]
1393
+ state[switched] = blocked
1394
+ end
1395
+ found = true
1396
+ while found do
1397
+ found = false
1398
+ # - fix all vars left of switched to their values in assignment
1399
+ # - block switched from obtaining the values in blocked
1400
+ # progress.print_line "DEBUG: #{summarize_fixing_clause(assignment, vars, switched)}, #{summarize_blocking_clause(vars[switched][0], blocked)}"
1401
+ clauses = enumerate_fixing_clause(assignment, vars, switched) + enumerate_blocking_clause(vars[switched][0], vars[switched][1], blocked)
1402
+ output_doc_filename = prepare_output_doc_filename(@num_tries, output_dir)
1403
+ new_assignment = generate_sparse_doc(progress, message, key, config, options, clauses, output_doc_filename)
1404
+ generated_doc_number = @num_tries
1405
+ time = prior_processing_time + (Time.now().to_i - options[:timestamp_now])
1406
+ store_continuation_point(generated_doc_number, state, assignment, new_assignment, "#{output_dir}/#{CONTINUATION_FILE}", options, time)
1407
+ progress.print_line "STORING continuation point"
1408
+ progress.print_line "|time=#{time}"
1409
+ progress.print_line "|doc_number=#{generated_doc_number}"
1410
+ progress.print_line "|num_docs=#{@num_docs}"
1411
+ progress.print_line "|state=#{state.inspect}"
1412
+ progress.print_line "|assignment=#{assignment.inspect}"
1413
+ progress.print_line "|new_assignment=#{new_assignment.inspect}"
1414
+
1415
+ if is_finite_state then
1416
+ progress.report_progress(determine_progress(state, vars), start_time, prior_processing_time, @num_docs)
1417
+ else
1418
+ progress.report_progress(nil, start_time, prior_processing_time, @num_docs)
1419
+ end
1420
+ @num_tries += 1
1421
+ if new_assignment then # sat
1422
+ @num_docs += 1
1423
+ @on_new_doc.call(generated_doc_number, output_doc_filename, new_assignment) if @on_new_doc
1424
+ if switched < vars.size()-1 then
1425
+ enumerate(progress, switched+1, vars, new_assignment, message, key, output_dir, config, options, state, start_time, prior_processing_time)
1426
+ end
1427
+ blocked << new_assignment[vars[switched][0]]
1428
+ state[switched] = blocked
1429
+ found = true
1430
+ else # unsat
1431
+ @on_unsat.call(generated_doc_number, log_filename_for_output_filename(output_doc_filename), display_combination_space(assignment, vars, switched, blocked), switched) if @on_unsat
1432
+ found = false
1433
+ end
1434
+ end
1435
+ state[switched] = nil
1436
+ end
1437
+
1438
+ # NOTE: can only be called when all key-fields are finite-state
1439
+ def is_final_state(state, new_assignment, key_fields_list)
1440
+ return state[1,state.size()] == [nil] * (state.size()-1) &&
1441
+ state[0].to_set == values_for_key_field(key_fields_list[0][1]).to_set &&
1442
+ new_assignment.empty?
1443
+ end
1444
+
1445
+ # NOTE: can only be called when all key-fields are finite-state
1446
+ def determine_progress(state, vars)
1447
+ combinations_per_value = [nil] * vars.size()
1448
+ comb = 1
1449
+ vars.size().times.to_a.reverse_each do |i|
1450
+ combinations_per_value[i] = comb
1451
+ comb = values_for_key_field(vars[i][1]).size() * comb
1452
+ end
1453
+
1454
+ blocked_combinations = 0
1455
+ state.size().times do |i|
1456
+ if state[i] then
1457
+ blocked_combinations += state[i].size() * combinations_per_value[i]
1458
+ end
1459
+ end
1460
+ blocked_combinations
1461
+ end
1462
+
1463
+ def num_combinations(key_def)
1464
+ if key_def[:type] == :field then
1465
+ if key_def[:datatype] == :enum then
1466
+ key_def[:values].size() + (key_def[:optional] ? 1 : 0)
1467
+ elsif key_def[:datatype] == :int then
1468
+ if key_def.has_key?(:min) and key_def.has_key?(:max) and key_def[:min] <= key_def[:max] then
1469
+ 1 + key_def[:max] - key_def[:min] + (key_def[:optional] ? 1 : 0)
1470
+ else
1471
+ nil
1472
+ end
1473
+ else
1474
+ nil
1475
+ end
1476
+ elsif key_def[:type] == :structure then
1477
+ 2
1478
+ end
1479
+ end
1480
+
1481
+ def keys_are_finite_state(list)
1482
+ list.each do |field|
1483
+ name = field[0]
1484
+ if field[1][:type] == :field then
1485
+ if field[1][:datatype] == :enum then
1486
+ # enums are finite-state
1487
+ elsif field[1][:datatype] == :int and field[1].has_key?(:min) and field[1].has_key?(:max) and field[1][:min] <= field[1][:max] then
1488
+ # ints are finite-state when both bounds are given and :min <= :max
1489
+ else
1490
+ return false
1491
+ end
1492
+ end
1493
+ end
1494
+ return true
1495
+ end
1496
+
1497
+ def generate_docs_for_key(progress, message, key, output_dir, config, options)
1498
+
1499
+ prior_processing_time = 0
1500
+ start_time = options[:timestamp_now]
1501
+ number_of_combinations = calc_combinations(message, key)
1502
+ if number_of_combinations.is_a?(String) then
1503
+ progress.set_max(100000000000000)
1504
+ else
1505
+ progress.set_max(number_of_combinations)
1506
+ end
1507
+ progress.report_progress(0.0, start_time, prior_processing_time, 0)
1508
+ progress.print_line "generating sparse documents efficiently..."
1509
+ progress.print_line "there is a theoretical maximum of #{number_of_combinations} combinations."
1510
+ progress.print_line "Note, however, that we are generating only those documents that are valid, which are usually much less."
1511
+
1512
+ progress.print_line "preparing output dir #{output_dir}..."
1513
+ if options[:continue] then
1514
+ unless Dir.exist?(output_dir) then
1515
+ raise FatalError.new("FATAL: directory #{output_dir} not found. There seems to be nothing to continue from...")
1516
+ end
1517
+ log_mode = "a"
1518
+ else
1519
+ unless Dir.exist?(output_dir) then
1520
+ FileUtils.mkdir_p(output_dir)
1521
+ end
1522
+ log_mode = "w"
1523
+ end
1524
+ log_filename = "#{output_dir}/#{COMBINATION_LOG}"
1525
+
1526
+ key_fields = collect_key_fields(key, message)
1527
+ key_fields_list = key_fields.each_pair.to_a
1528
+ key_fields_list.sort! {|a,b| num_combinations(b[1]) <=> num_combinations(a[1]) }
1529
+
1530
+ if options[:continue] then
1531
+ stored_key_fields_str = File.read("#{output_dir}/#{VARS_LOG}")
1532
+ unless key_fields_list.to_yaml == stored_key_fields_str then
1533
+ raise FatalError.new("vars are differing! please specify the same key in order to use -continue!")
1534
+ end
1535
+ progress.print_line "continuing enumerating documents for key #{key}!"
1536
+ else
1537
+ # store vars
1538
+ File.open("#{output_dir}/#{VARS_LOG}", "w") do |f|
1539
+ f.puts(key_fields_list.to_yaml)
1540
+ end
1541
+ end
1542
+
1543
+ cont_filename = "#{output_dir}/#{CONTINUATION_FILE}"
1544
+ if options[:continue] then
1545
+ unless File.exist?(cont_filename) then
1546
+ raise FatalError.new("FATAL: file #{cont_filename} not found. There seems to be no continuation point to continue from...")
1547
+ end
1548
+ doc_number, num_docs, state, assignment, new_assignment, prior_processing_time = CoreEnumerationAlgo.parse_continuation_point(File.read(cont_filename).chomp)
1549
+ if is_final_state(state, new_assignment, key_fields_list) then
1550
+ progress.report_progress(number_of_combinations, start_time, prior_processing_time, num_docs)
1551
+ raise FatalError.new("FATAL: The enumeration is finished. There is nothing left to do. Resuming the enumeration hence does not make sense.")
1552
+ end
1553
+ progress.print_line "RESUMING enumeration from the following continuation point:"
1554
+ progress.print_line "|time=#{prior_processing_time}"
1555
+ progress.print_line "|doc_number=#{doc_number}"
1556
+ progress.print_line "|num_docs=#{num_docs}"
1557
+ progress.print_line "|state=#{state.inspect}"
1558
+ progress.print_line "|assignment=#{assignment.inspect}"
1559
+ progress.print_line "|new_assignment=#{new_assignment.inspect}"
1560
+ if keys_are_finite_state(key_fields_list) then
1561
+ progress.report_progress(determine_progress(state, key_fields_list), start_time, prior_processing_time, num_docs)
1562
+ else
1563
+ progress.report_progress(nil, start_time, prior_processing_time, num_docs)
1564
+ end
1565
+
1566
+ if new_assignment && !new_assignment.empty? then
1567
+ assignment = new_assignment
1568
+ end
1569
+
1570
+ # restore state at continuation point
1571
+ @num_tries = doc_number+1 # we do not want to overwrite the last document written before the STOP
1572
+ number_of_combinations += 1 # account for this in the safety mechanism
1573
+ @num_docs = num_docs
1574
+ File.open(log_filename, log_mode) do |log|
1575
+
1576
+ @on_new_doc = Proc.new { |num_doc, doc_filename, combination|
1577
+ # combination logging
1578
+ log.puts "#{doc_filename} <- {#{combination.map{|k,v| "#{k}=#{v.inspect}"}.join(", ")}}"
1579
+
1580
+ if options[:max_num_docs] && @num_docs >= options[:max_num_docs] then
1581
+ raise NormalProgramTermination.new("-max-num-docs exceeded. Stopping!")
1582
+ end
1583
+
1584
+ # safety mechanism
1585
+ if @num_docs > number_of_combinations then
1586
+ raise RuntimeError.new("This should never happen! Number of combinations exceeded. #{@num_docs} documents generated, but there are only #{number_of_combinations} possible combinations of the key vars.")
1587
+ end
1588
+ }
1589
+ @on_unsat = Proc.new { |num_doc, log_filename, combination_str, switched|
1590
+ log.puts "CONTRADICTION (see: #{log_filename}) <- #{combination_str}"
1591
+ progress.print_line "doc#{num_doc} NOT generated"
1592
+ progress.print_line "No more valid combinations of the free vars could be found. -> Switching (var #{switched})"
1593
+ }
1594
+
1595
+ # core algo - resume from state
1596
+ enumerate(progress, 0, key_fields_list, assignment, message, key, output_dir, config, options, state, start_time, prior_processing_time)
1597
+ end
1598
+ else
1599
+ # init safety mechanism
1600
+ @num_tries = 0
1601
+ @num_docs = 0
1602
+ # first try - a freely generated document
1603
+ File.open(log_filename, log_mode) do |log|
1604
+ output_doc_filename = prepare_output_doc_filename(@num_tries, output_dir)
1605
+ @on_new_doc = Proc.new { |num_doc, doc_filename, combination|
1606
+ # combination logging
1607
+ log.puts "#{doc_filename} <- {#{combination.map{|k,v| "#{k}=#{v.inspect}"}.join(", ")}}"
1608
+
1609
+ if options[:max_num_docs] && @num_docs >= options[:max_num_docs] then
1610
+ raise NormalProgramTermination.new("-max-num-docs exceeded. Stopping!")
1611
+ end
1612
+
1613
+ # safety mechanism
1614
+ unless number_of_combinations.is_a?(String) then
1615
+ if @num_docs > number_of_combinations then
1616
+ raise RuntimeError.new("This should never happen! Number of combinations exceeded. #{@num_docs} documents generated, but there are only #{number_of_combinations} possible combinations of the key vars.")
1617
+ end
1618
+ end
1619
+ }
1620
+ @on_unsat = Proc.new { |num_doc, log_filename, combination_str, switched|
1621
+ log.puts "CONTRADICTION (see: #{log_filename}) <- #{combination_str}"
1622
+ progress.print_line "doc#{num_doc} NOT generated"
1623
+ progress.print_line "No more valid combinations of the free vars could be found. -> Switching (var #{switched})"
1624
+ }
1625
+ key_values = generate_doc_return_key_values(progress, message, key, output_doc_filename, config, options)
1626
+ @num_tries += 1
1627
+ if key_values then
1628
+ @num_docs += 1
1629
+ @on_new_doc.call(@num_tries, output_doc_filename, key_values)
1630
+ else
1631
+ # NOTE: this only occurs when the model is inconsistent and has no valid documents
1632
+ progress.print_line("ERROR: Model inconsistent - no valid documents could be found.")
1633
+ @on_unsat.call(@num_tries, log_filename_for_output_filename(output_doc_filename), "{}", 0)
1634
+ end
1635
+
1636
+ # core algo - start enumeration
1637
+ enumerate(progress, 0, key_fields_list, key_values, message, key, output_dir, config, options, start_time, prior_processing_time)
1638
+ end
1639
+ end
1640
+ progress.print_line "No more valid documents could be found. -> Exiting"
1641
+ progress.report_progress(number_of_combinations, start_time, prior_processing_time, @num_docs)
1642
+ end
1643
+ end
1644
+
1645
+ def introduce_parent_links(message, parent = nil, prefix = nil)
1646
+ if parent != nil then
1647
+ message[:parent_link] = parent
1648
+ end
1649
+ if prefix != nil then
1650
+ message[:struct_xpath_prefix] = prefix
1651
+ end
1652
+ message.each_pair do |key, value|
1653
+ next if key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
1654
+ if value[:type] == :structure then
1655
+ if prefix then
1656
+ new_prefix = "#{prefix}/#{key}"
1657
+ else
1658
+ new_prefix = key
1659
+ end
1660
+ introduce_parent_links(value[:ref], message, new_prefix)
1661
+ end
1662
+ end
1663
+ end
1664
+
1665
+ def validate_model(message, prefix=nil)
1666
+
1667
+ message.each_pair do |key, value|
1668
+ if key == :validation_rules then
1669
+ value.each_pair do |vr_key, vr_value|
1670
+ unless vr_value[:text] && vr_value[:text] != "" then
1671
+ puts "INFO: Model Problem: validation rule #{vr_key} does not have a textual representation."
1672
+ end
1673
+ unless vr_value[:smtlib] && vr_value[:smtlib] != "" then
1674
+ puts "WARN: Model Problem: validation rule #{vr_key} does not have an SMTLIB representation. It will hence be omitted!"
1675
+ end
1676
+ end
1677
+ elsif key == :predicates then
1678
+ value.each_pair do |mkey, mvalue|
1679
+ next unless mvalue
1680
+ unless mvalue[:text] && mvalue[:text] != "" then
1681
+ puts "INFO: Model Problem: structure #{prefix}'s predicate #{mkey} does not have a textual representation."
1682
+ end
1683
+ unless mvalue[:smtlib] && mvalue[:smtlib] != "" then
1684
+ puts "WARN: Model Problem: structure #{prefix}'s predicate #{mkey} does not have an SMTLIB representation. It will be regarded as always true."
1685
+ end
1686
+ end
1687
+ elsif key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
1688
+ next
1689
+ else
1690
+ if value[:type] == :structure then
1691
+ unless value[:ref] then
1692
+ raise RuntimeError.new("Model Problem: encountered structure without a :ref (key=#{key.inspect}, value=#{value.inspect})")
1693
+ end
1694
+ if prefix then
1695
+ new_prefix = "#{prefix}/#{key}"
1696
+ else
1697
+ new_prefix = key
1698
+ end
1699
+ validate_model(value[:ref], new_prefix)
1700
+ elsif value[:type] == :field then
1701
+ unless value[:datatype] then
1702
+ raise RuntimeError.new("Model Problem: encountered field without a :datatype (key=#{key.inspect}, value=#{value.inspect})")
1703
+ end
1704
+ if value[:datatype] == :enum then
1705
+ unless value[:values] then
1706
+ raise RuntimeError.new("Model Problem: encountered enum field without a :values (key=#{key.inspect}, value=#{value.inspect})")
1707
+ end
1708
+ end
1709
+ elsif value[:type] == :list then
1710
+ unless value[:datatype] then
1711
+ raise RuntimeError.new("Model Problem: encountered list without a :datatype (key=#{key.inspect}, value=#{value.inspect})")
1712
+ end
1713
+ if value[:datatype] == :enum then
1714
+ unless value[:values] then
1715
+ raise RuntimeError.new("Model Problem: encountered enum list without a :values (key=#{key.inspect}, value=#{value.inspect})")
1716
+ end
1717
+ end
1718
+ elsif value[:type] == :objlist then
1719
+ unless value[:ref] then
1720
+ raise RuntimeError.new("Model Problem: encountered objlist without a :ref (key=#{key.inspect}, value=#{value.inspect})")
1721
+ end
1722
+ unless value[:model_maxLength] then
1723
+ raise RuntimeError.new("Model Problem: encountered objlist without a :model_maxLength (key=#{key.inspect}, value=#{value.inspect})")
1724
+ end
1725
+ unless value[:model_maxLength].is_a?(Integer) then
1726
+ raise RuntimeError.new("Model Problem: encountered objlist whose :model_maxLength property is not an Integer (key=#{key.inspect}, value=#{value.inspect})")
1727
+ end
1728
+ unless value[:xpath_element] then
1729
+ raise RuntimeError.new("Model Problem: encountered objlist without a :xpath_element property (key=#{key.inspect}, value=#{value.inspect})")
1730
+ end
1731
+ unless value[:xpath_element].is_a?(String) then
1732
+ raise RuntimeError.new("Model Problem: encountered objlist whose :xpath_element property is not a String (key=#{key.inspect}, value=#{value.inspect})")
1733
+ end
1734
+ if prefix then
1735
+ new_prefix = "#{prefix}/#{key}"
1736
+ else
1737
+ new_prefix = key
1738
+ end
1739
+ validate_model(value[:ref], new_prefix)
1740
+ else
1741
+ raise RuntimeError.new("Model Problem: encountered unknown :type #{value[:type].inspect}")
1742
+ end
1743
+ end
1744
+ end
1745
+ end
1746
+
1747
+ def validate_doc_against_schema(struct, doc)
1748
+ root = doc.root
1749
+ sdef = struct[root.name.to_sym]
1750
+ unless sdef[:type] == :structure then
1751
+ puts "ERROR: schema validation failed: root element should be a structure"
1752
+ end
1753
+ # first, check that each doc element is defined for the struct
1754
+ validate_doc_struct_against_schema(sdef[:ref], root, root.name)
1755
+ # second, check that all required elements from the struct are actually present in the doc
1756
+ validate_schema_against_doc_struct(sdef[:ref], root, root.name)
1757
+ end
1758
+
1759
+ def validate_doc_struct_against_schema(struct, doc, prefix = nil)
1760
+ doc.children.to_a.filter{|x| x.is_a?(Nokogiri::XML::Element) }.each do |c|
1761
+ fdef = struct[c.name.to_sym]
1762
+ if prefix then
1763
+ xpath = "#{prefix}/#{c.name}"
1764
+ else
1765
+ xpath = c.name
1766
+ end
1767
+ if fdef
1768
+ if fdef[:type] == :structure then
1769
+ return unless validate_doc_struct_against_schema(fdef[:ref], c, xpath)
1770
+ end
1771
+ else
1772
+ if prefix then
1773
+ xpath = "#{prefix}/#{c.name}"
1774
+ else
1775
+ xpath = c.name
1776
+ end
1777
+ raise FatalError.new("ERROR: schema validation failed: #{c.name} of #{xpath} is present in document, but not defined in schema.")
1778
+ end
1779
+ end
1780
+ end
1781
+
1782
+ def validate_schema_against_doc_struct(struct, doc, prefix = nil)
1783
+ struct.each do |key, value|
1784
+ next if key == :additional_smtlib || key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix
1785
+ if prefix then
1786
+ xpath = "#{prefix}/#{key}"
1787
+ else
1788
+ xpath = key
1789
+ end
1790
+ if value[:type] == :structure then
1791
+ node = doc.at_xpath(key.to_s)
1792
+ if node then
1793
+ validate_schema_against_doc_struct(value[:ref], node, xpath)
1794
+ else
1795
+ if value[:optional] == false then
1796
+ raise FatalError.new("ERROR: schema validation failed: non-optional structure #{xpath} not found in doc.")
1797
+ end
1798
+ end
1799
+ elsif value[:type] == :field then
1800
+ if value[:optional] == false then
1801
+ unless doc.at_xpath(key.to_s) then
1802
+ raise FatalError.new("ERROR: schema validation failed: non-optional field #{xpath} not found in doc.")
1803
+ end
1804
+ end
1805
+ elsif value[:type] == :list then
1806
+ if value[:optional] == false then
1807
+ unless doc.at_xpath(key.to_s) then
1808
+ raise FatalError.new("ERROR: schema validation failed: non-optional list #{xpath} not found in doc.")
1809
+ end
1810
+ end
1811
+ list = doc.at_xpath(key.to_s)
1812
+ if list then
1813
+ list.children.to_a.each do |c|
1814
+ next unless c.is_a?(Nokogiri::XML::Element)
1815
+ unless c.name == value[:xpath_element] then
1816
+ raise FatalError.new("ERROR: schema validation failed: encountered element #{c.name} in list #{xpath}, which should only contain #{value[:xpath_element]} elements.")
1817
+ end
1818
+ end
1819
+ end
1820
+ else
1821
+ raise RuntimeError.new("Model error: encountered element #{xpath} of unknown type: #{value[:type]}.")
1822
+ end
1823
+ end
1824
+ end
1825
+
1826
+ def translate_doc_to_SMTLIB(progress, solver, struct, doc, hash = {})
1827
+ options = hash[:options]
1828
+ config = hash[:config]
1829
+
1830
+ solver.to_solver(progress, "; --- Document: ---")
1831
+ translate_struct_in_doc_to_SMTLIB(progress, solver, struct, doc, nil, hash)
1832
+ end
1833
+
1834
+ def translate_struct_in_doc_to_SMTLIB(progress, solver, struct, doc, prefix, hash = {})
1835
+ options = hash[:options]
1836
+ config = hash[:config]
1837
+
1838
+ struct.each do |key, value|
1839
+ next if key == :additional_smtlib || key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix
1840
+ if prefix then
1841
+ xpath = "#{prefix}/#{key.to_s}"
1842
+ else
1843
+ xpath = key.to_s
1844
+ end
1845
+ if value[:type] == :structure then
1846
+ node = doc.at_xpath(xpath)
1847
+ if node then
1848
+ info = {
1849
+ :assertion_type => :doc_structure_exists,
1850
+ :xpath => xpath,
1851
+ }
1852
+ assertionID = "doc-#{xpath.gsub('/', '-')}"
1853
+ solver.associateAssertionIDWith(assertionID, info)
1854
+ solver.to_solver(progress, "(assert (! (= #{exists_var_for_structure(xpath)} true) :named #{assertionID}))")
1855
+
1856
+ translate_struct_in_doc_to_SMTLIB(progress, solver, value[:ref], doc, xpath, hash)
1857
+ else
1858
+ info = {
1859
+ :assertion_type => :doc_structure_not_exists,
1860
+ :xpath => xpath,
1861
+ }
1862
+ assertionID = "doc-#{xpath.gsub('/', '-')}"
1863
+ solver.associateAssertionIDWith(assertionID, info)
1864
+ solver.to_solver(progress, "(assert (! (= #{exists_var_for_structure(xpath)} false) :named #{assertionID}))")
1865
+ end
1866
+ elsif value[:type] == :field then
1867
+ node = doc.at_xpath(xpath)
1868
+ if node then
1869
+ fvalue = node.text
1870
+ info = {
1871
+ :assertion_type => :doc_field_filled,
1872
+ :xpath => xpath,
1873
+ :value => fvalue
1874
+ }
1875
+ fvalue_str = value_of_type(fvalue, value[:datatype])
1876
+ assertionID = "doc-#{xpath.gsub('/', '-')}"
1877
+ solver.associateAssertionIDWith(assertionID, info)
1878
+ solver.to_solver(progress, "(assert (! (and (= #{filled_var_for_field(xpath)} true) (= #{value_var_for_field(xpath)} #{fvalue_str})) :named #{assertionID}))")
1879
+ else
1880
+ info = {
1881
+ :assertion_type => :doc_field_empty,
1882
+ :xpath => xpath,
1883
+ }
1884
+ assertionID = "doc-#{xpath.gsub('/', '-')}"
1885
+ solver.associateAssertionIDWith(assertionID, info)
1886
+ solver.to_solver(progress, "(assert (! (= #{filled_var_for_field(xpath)} false) :named #{assertionID}))")
1887
+ end
1888
+ elsif value[:type] == :list then
1889
+ node = doc.at_xpath(xpath)
1890
+ if node then
1891
+ fvalues = []
1892
+ node.children.to_a.each do |c|
1893
+ next unless c.is_a?(Nokogiri::XML::Element)
1894
+ fvalues << c.text
1895
+ end
1896
+ info = {
1897
+ :assertion_type => :doc_list_filled,
1898
+ :xpath => xpath,
1899
+ :values => fvalues
1900
+ }
1901
+ fvalue_str = value_of_type(fvalues, value[:datatype])
1902
+ assertionID = "doc-#{xpath.gsub('/', '-')}"
1903
+ solver.associateAssertionIDWith(assertionID, info)
1904
+ clauses = []
1905
+ fvalues.each_with_index do |fvalue, index|
1906
+ clauses << "(= #{value_var_for_list(xpath,index)} #{value_of_type(fvalue, value[:datatype])})"
1907
+ end
1908
+ solver.to_solver(progress, "(assert (! (and (= #{filled_var_for_field(xpath)} true) (= #{size_var_for_list(xpath)} #{fvalues.size}) #{clauses.join(' ')}) :named #{assertionID}))")
1909
+ else
1910
+ info = {
1911
+ :assertion_type => :doc_field_empty,
1912
+ :xpath => xpath,
1913
+ }
1914
+ assertionID = "doc-#{xpath.gsub('/', '-')}"
1915
+ solver.associateAssertionIDWith(assertionID, info)
1916
+ solver.to_solver(progress, "(assert (! (= #{filled_var_for_field(xpath)} false) :named #{assertionID}))")
1917
+ end
1918
+ else
1919
+ raise RuntimeError.new("Model Error: encountered unknown type #{value[:type].inspect}")
1920
+ end
1921
+ end
1922
+ end
1923
+
1924
+ def validate_doc(progress, struct, doc, options, config)
1925
+ raise RuntimeError.new("option :solverFactory is required") unless options[:solverFactory]
1926
+ options[:solverLog] = "validation.smt2"
1927
+
1928
+ solverFactory = options[:solverFactory]
1929
+ result = solverFactory.query(options) do |solver|
1930
+ set_standard_options(progress, solver)
1931
+ translate_structure_to_SMTLIB(progress, solver, struct, :options => options, :config => config)
1932
+ translate_doc_to_SMTLIB(progress, solver, struct, doc, :options => options, :config => config)
1933
+ solver.to_solver(progress, "")
1934
+ end
1935
+ unless result == true then
1936
+ raise FatalError.new("VALIDATION FAILED! SMTLIB log was written to #{options[:solverLog]}.\n\n#{result}")
1937
+ end
1938
+
1939
+ return true
1940
+ end
1941
+
1942
+ def generate_unused_logfile_name()
1943
+ number = 0
1944
+ result = "logs/#{number}.log"
1945
+ while File.exist?(result) do
1946
+ number += 1
1947
+ result = "logs/#{number}.log"
1948
+ end
1949
+ result
1950
+ end
1951
+
1952
+ def generate(message, argv, message_config_filename = nil)
1953
+ if ARGV[0] == "--help" || ARGV[0] == "-help" || ARGV[0] == "help" then
1954
+ puts USAGE
1955
+ exit(0)
1956
+ end
1957
+
1958
+ options = { :negated_validation_rules => [], :date_now => (Date.today - Time.at(0).to_date).to_i, :timestamp_now => Time.now().to_i }
1959
+
1960
+ opt = argv.shift
1961
+ while opt do
1962
+ if opt == "-negate" then
1963
+ rule = argv.shift
1964
+ options[:negated_validation_rules] << rule
1965
+ elsif opt == "-list-keys" then
1966
+ options[:list_keys] = true
1967
+ elsif opt == "-list" then
1968
+ options[:list] = argv.shift
1969
+ elsif opt == "-list-validation-rules" then
1970
+ options[:list_validation_rules] = true
1971
+ elsif opt == "-generate-docs-from" then
1972
+ options[:combination_source] = argv.shift
1973
+ elsif opt == "-skip" then
1974
+ options[:skip] = Integer(argv.shift)
1975
+ elsif opt == "-generate-docs-for-key" then
1976
+ options[:docs_for_key] = argv.shift
1977
+ elsif opt == "-count-docs-for-key" then
1978
+ options[:count_docs_for_key] = argv.shift
1979
+ elsif opt == "-continue" then
1980
+ options[:continue] = true
1981
+ elsif opt == "-max-num-docs" then
1982
+ options[:max_num_docs] = Integer(argv.shift)
1983
+ puts "flag -max-num-docs used. Will stop after generating #{options[:max_num_docs]} documents."
1984
+ elsif opt == "-validate" then
1985
+ if options.has_key?(:validate_files) then
1986
+ options[:validate_files] << argv.shift
1987
+ else
1988
+ options[:validate_files] = [argv.shift]
1989
+ end
1990
+ else
1991
+ puts "FATAL: unknown option #{opt.inspect}."
1992
+ puts USAGE
1993
+ raise FatalError.new("")
1994
+ end
1995
+ opt = argv.shift
1996
+ end
1997
+
1998
+ ProgressBar.logging(1, generate_unused_logfile_name()) do |progress|
1999
+ unless options[:negated_validation_rules].empty? then
2000
+ progress.print_line "negating rules: #{options[:negated_validation_rules].join(", ")}"
2001
+
2002
+ rules = collect_validation_rule_names(message)
2003
+ unknown_negated_rules = options[:negated_validation_rules].filter { |rule_name| !rules.include?(rule_name) }
2004
+ unless unknown_negated_rules.empty? then
2005
+ throw RuntimeError.new("unknown negated rules #{unknown_negated_rules.join(", ")}")
2006
+ end
2007
+
2008
+ end
2009
+
2010
+ if File.exist?(CONFIG_FILE) then
2011
+ config = YAML.load_file(CONFIG_FILE)
2012
+ else
2013
+ config = DEFAULT_CONFIG
2014
+ File.open(CONFIG_FILE, "w") do |f|
2015
+ f.puts(config.to_yaml)
2016
+ end
2017
+ progress.print_line "No config file found. Default config written to #{CONFIG_FILE}"
2018
+ end
2019
+ if message_config_filename then
2020
+ message_config = YAML.load_file(message_config_filename)
2021
+ config = config.merge(message_config)
2022
+ end
2023
+
2024
+ introduce_parent_links(message)
2025
+ z3path = config[:z3path] || DEFAULT_Z3_PATH
2026
+ options[:solverFactory] = SolverFactory.new(progress, z3path)
2027
+
2028
+ if options[:validate_files] then
2029
+ progress.print_line "using #{options[:date_now]} as [Date.now]"
2030
+ progress.print_line "using #{options[:timestamp_now]} as [Timestamp.now]"
2031
+ options[:validate_files].each do |filename|
2032
+ progress.print_line "validating file #{filename}"
2033
+ doc = Nokogiri::XML(File.read(filename))
2034
+ validate_doc_against_schema(message, doc)
2035
+ validate_doc(progress, message, doc, options, config)
2036
+ end
2037
+ elsif options[:count_docs_for_key] then
2038
+ progress.print_line "there are #{calc_combinations(message, options[:count_docs_for_key])} combinations."
2039
+ elsif options[:docs_for_key] then
2040
+ progress.print_line "using #{options[:date_now]} as [Date.now]"
2041
+ progress.print_line "using #{options[:timestamp_now]} as [Timestamp.now]"
2042
+ validate_model(message)
2043
+ CoreEnumerationAlgo.new().generate_docs_for_key(progress, message, options[:docs_for_key], OUTPUT_DIR, config, options)
2044
+ elsif options[:list_keys] then
2045
+ list_keys(message)
2046
+ elsif options[:list] then
2047
+ list_fields_and_structures(message, options[:list])
2048
+ elsif options[:list_validation_rules] then
2049
+ list_validation_rules(message)
2050
+ else
2051
+ progress.print_line "using #{options[:date_now]} as [Date.now]"
2052
+ progress.print_line "using #{options[:timestamp_now]} as [Timestamp.now]"
2053
+ validate_model(message)
2054
+ generate_doc(progress, message, OUTPUT_FILE, config, options)
2055
+ end
2056
+ end
2057
+ end
2058
+