CBMC
smt_response_validation.cpp
Go to the documentation of this file.
1 // Author: Diffblue Ltd.
2 
17 
19 
20 #include <util/arith_tools.h>
21 #include <util/mp_arith.h>
22 #include <util/range.h>
23 
25 
26 #include "smt_to_smt2_string.h"
27 
28 #include <regex>
29 
31  const irept &parse_tree,
32  const std::unordered_map<irep_idt, smt_identifier_termt> &identifier_table);
33 
34 // Implementation detail of `collect_error_messages` below.
35 template <typename argumentt, typename... argumentst>
37  std::vector<std::string> &collected_error_messages,
38  argumentt &&argument)
39 {
40  if(const auto messages = argument.get_if_error())
41  {
42  collected_error_messages.insert(
43  collected_error_messages.end(), messages->cbegin(), messages->end());
44  }
45 }
46 
47 // Implementation detail of `collect_error_messages` below.
48 template <typename argumentt, typename... argumentst>
50  std::vector<std::string> &collected_error_messages,
51  argumentt &&argument,
52  argumentst &&... arguments)
53 {
54  collect_error_messages_impl(collected_error_messages, argument);
55  collect_error_messages_impl(collected_error_messages, arguments...);
56 }
57 
62 template <typename... argumentst>
63 std::vector<std::string> collect_error_messages(argumentst &&... arguments)
64 {
65  std::vector<std::string> collected_error_messages;
66  collect_error_messages_impl(collected_error_messages, arguments...);
67  return collected_error_messages;
68 }
69 
84 template <
85  typename smt_to_constructt,
86  typename smt_baset = smt_to_constructt,
87  typename... argumentst>
89 {
90  const auto collected_error_messages = collect_error_messages(arguments...);
91  if(!collected_error_messages.empty())
92  return response_or_errort<smt_baset>(collected_error_messages);
93  else
94  {
96  smt_to_constructt{(*arguments.get_if_valid())...}};
97  }
98 }
99 
106 static std::string print_parse_tree(const irept &parse_tree)
107 {
108  return parse_tree.pretty(0, 0);
109 }
110 
112 validate_string_literal(const irept &parse_tree)
113 {
114  if(!parse_tree.get_sub().empty())
115  {
117  "Expected string literal, found \"" + print_parse_tree(parse_tree) +
118  "\".");
119  }
120  return response_or_errort<irep_idt>{parse_tree.id()};
121 }
122 
128 static std::optional<response_or_errort<smt_responset>>
129 valid_smt_error_response(const irept &parse_tree)
130 {
131  // Check if the parse tree looks to be an error response.
132  if(!parse_tree.id().empty())
133  return {};
134  if(parse_tree.get_sub().empty())
135  return {};
136  if(parse_tree.get_sub().at(0).id() != "error")
137  return {};
138  // Parse tree is now considered to be an error response and anything
139  // unexpected in the parse tree is now considered to be an invalid response.
140  if(parse_tree.get_sub().size() == 1)
141  {
142  return {response_or_errort<smt_responset>{std::vector<std::string>{
143  {"Error response is missing the error message."}}}};
144  }
145  if(parse_tree.get_sub().size() > 2)
146  {
147  return {response_or_errort<smt_responset>{std::vector<std::string>{
148  {"Error response has multiple error messages - \"" +
149  print_parse_tree(parse_tree) + "\"."}}}};
150  }
151  return validation_propagating<smt_error_responset, smt_responset>(
152  validate_string_literal(parse_tree.get_sub()[1]));
153 }
154 
155 static bool all_subs_are_pairs(const irept &parse_tree)
156 {
157  return std::all_of(
158  parse_tree.get_sub().cbegin(),
159  parse_tree.get_sub().cend(),
160  [](const irept &sub) { return sub.get_sub().size() == 2; });
161 }
162 
165 static std::optional<smt_termt>
167 {
168  if(parse_tree.get_sub().size() != 3)
169  return {};
170  if(parse_tree.get_sub().at(0).id() != "_")
171  return {};
172  const auto value_string = id2string(parse_tree.get_sub().at(1).id());
173  std::smatch match_results;
174  static const std::regex bv_value_regex{R"(^bv(\d+)$)", std::regex::optimize};
175  if(!std::regex_search(value_string, match_results, bv_value_regex))
176  return {};
177  INVARIANT(
178  match_results.size() == 2,
179  "Match results should include digits sub-expression if regex is matched.");
180  const std::string value_digits = match_results[1];
181  const auto value = string2integer(value_digits);
182  const auto bit_width_string = id2string(parse_tree.get_sub().at(2).id());
183  const auto bit_width =
184  numeric_cast_v<std::size_t>(string2integer(bit_width_string));
185  if(bit_width == 0)
186  return {};
187  if(value >= power(mp_integer{2}, bit_width))
188  return {};
189  return smt_bit_vector_constant_termt{value, bit_width};
190 }
191 
192 static std::optional<smt_termt> valid_smt_bool(const irept &parse_tree)
193 {
194  if(!parse_tree.get_sub().empty())
195  return {};
196  if(parse_tree.id() == ID_true)
197  return {smt_bool_literal_termt{true}};
198  if(parse_tree.id() == ID_false)
199  return {smt_bool_literal_termt{false}};
200  return {};
201 }
202 
203 static std::optional<smt_termt> valid_smt_binary(const std::string &text)
204 {
205  static const std::regex binary_format{"#b[01]+"};
206  if(!std::regex_match(text, binary_format))
207  return {};
208  const mp_integer value = string2integer({text.begin() + 2, text.end()}, 2);
209  // Width is number of bit values minus the "#b" prefix.
210  const std::size_t width = text.size() - 2;
211  return {smt_bit_vector_constant_termt{value, width}};
212 }
213 
214 static std::optional<smt_termt> valid_smt_hex(const std::string &text)
215 {
216  static const std::regex hex_format{"#x[0-9A-Za-z]+"};
217  if(!std::regex_match(text, hex_format))
218  return {};
219  const std::string hex{text.begin() + 2, text.end()};
220  // SMT-LIB 2 allows hex characters to be upper or lower case, but they should
221  // be upper case for mp_integer.
222  const mp_integer value =
223  string2integer(make_range(hex).map<std::function<int(int)>>(toupper), 16);
224  const std::size_t width = (text.size() - 2) * 4;
225  return {smt_bit_vector_constant_termt{value, width}};
226 }
227 
228 static std::optional<smt_termt>
230 {
231  if(const auto indexed = valid_smt_indexed_bit_vector(parse_tree))
232  return *indexed;
233  if(!parse_tree.get_sub().empty() || parse_tree.id().empty())
234  return {};
235  const auto value_string = id2string(parse_tree.id());
236  if(const auto smt_binary = valid_smt_binary(value_string))
237  return *smt_binary;
238  if(const auto smt_hex = valid_smt_hex(value_string))
239  return *smt_hex;
240  return {};
241 }
242 
243 static std::optional<response_or_errort<smt_termt>> try_select_validation(
244  const irept &parse_tree,
245  const std::unordered_map<irep_idt, smt_identifier_termt> &identifier_table)
246 {
247  if(parse_tree.get_sub().empty())
248  return {};
249  if(parse_tree.get_sub()[0].id() != "select")
250  return {};
251  if(parse_tree.get_sub().size() != 3)
252  {
253  return response_or_errort<smt_termt>{std::vector<std::string>{
254  {"\"select\" is expected to have 2 arguments, but " +
255  std::to_string(parse_tree.get_sub().size()) +
256  " arguments were found - \"" + print_parse_tree(parse_tree) + "\"."}}};
257  }
258  const auto array = validate_term(parse_tree.get_sub()[1], identifier_table);
259  const auto index = validate_term(parse_tree.get_sub()[2], identifier_table);
260  const auto error_messages = collect_error_messages(array, index);
261  if(!error_messages.empty())
262  return response_or_errort<smt_termt>{error_messages};
263  return {smt_array_theoryt::select.validation(
264  *array.get_if_valid(), *index.get_if_valid())};
265 }
266 
268  const irept &parse_tree,
269  const std::unordered_map<irep_idt, smt_identifier_termt> &identifier_table)
270 {
271  if(const auto smt_bool = valid_smt_bool(parse_tree))
272  return response_or_errort<smt_termt>{*smt_bool};
273  if(const auto bit_vector_constant = valid_smt_bit_vector_constant(parse_tree))
274  return response_or_errort<smt_termt>{*bit_vector_constant};
275  const auto find_result = identifier_table.find(parse_tree.id());
276  if(find_result != identifier_table.end())
277  return response_or_errort<smt_termt>{find_result->second};
278  if(
279  const auto select_validation =
280  try_select_validation(parse_tree, identifier_table))
281  {
282  return *select_validation;
283  }
284  return response_or_errort<smt_termt>{std::vector<std::string>{
285  {"Unrecognised SMT term - \"" + print_parse_tree(parse_tree) + "\"."}}};
286 }
287 
290  const irept &pair_parse_tree,
291  const std::unordered_map<irep_idt, smt_identifier_termt> &identifier_table)
292 {
294  PRECONDITION(pair_parse_tree.get_sub().size() == 2);
295  const auto descriptor_validation =
296  validate_term(pair_parse_tree.get_sub()[0], identifier_table);
297  const auto value_validation =
298  validate_term(pair_parse_tree.get_sub()[1], identifier_table);
299  const auto error_messages =
300  collect_error_messages(descriptor_validation, value_validation);
301  if(!error_messages.empty())
302  return resultt{error_messages};
303  const auto &valid_descriptor = *descriptor_validation.get_if_valid();
304  const auto &valid_value = *value_validation.get_if_valid();
305  if(valid_descriptor.get_sort() != valid_value.get_sort())
306  {
307  return resultt{
308  {"Mismatched descriptor and value sorts in - " +
309  print_parse_tree(pair_parse_tree) + "\nDescriptor has sort " +
310  smt_to_smt2_string(valid_descriptor.get_sort()) + "\nValue has sort " +
311  smt_to_smt2_string(valid_value.get_sort())}};
312  }
313  // see https://github.com/diffblue/cbmc/issues/7464 for why we explicitly name
314  // the valuation_pairt type here:
315  return resultt{
316  smt_get_value_responset::valuation_pairt{valid_descriptor, valid_value}};
317 }
318 
324 static std::optional<response_or_errort<smt_responset>>
326  const irept &parse_tree,
327  const std::unordered_map<irep_idt, smt_identifier_termt> &identifier_table)
328 {
329  // Shape matching for does this look like a get value response?
330  if(!parse_tree.id().empty())
331  return {};
332  if(parse_tree.get_sub().empty())
333  return {};
334  if(!all_subs_are_pairs(parse_tree))
335  return {};
336  std::vector<std::string> error_messages;
337  std::vector<smt_get_value_responset::valuation_pairt> valuation_pairs;
338  for(const auto &pair : parse_tree.get_sub())
339  {
340  const auto pair_validation_result =
341  validate_valuation_pair(pair, identifier_table);
342  if(const auto error = pair_validation_result.get_if_error())
343  error_messages.insert(error_messages.end(), error->begin(), error->end());
344  if(const auto valid_pair = pair_validation_result.get_if_valid())
345  valuation_pairs.push_back(*valid_pair);
346  }
347  if(!error_messages.empty())
348  return {response_or_errort<smt_responset>{std::move(error_messages)}};
349  else
350  {
352  smt_get_value_responset{valuation_pairs}}};
353  }
354 }
355 
357  const irept &parse_tree,
358  const std::unordered_map<irep_idt, smt_identifier_termt> &identifier_table)
359 {
360  if(parse_tree.id() == "sat")
363  if(parse_tree.id() == "unsat")
366  if(parse_tree.id() == "unknown")
369  if(const auto error_response = valid_smt_error_response(parse_tree))
370  return *error_response;
371  if(parse_tree.id() == "success")
373  if(parse_tree.id() == "unsupported")
375  if(
376  const auto get_value_response =
377  valid_smt_get_value_response(parse_tree, identifier_table))
378  {
379  return *get_value_response;
380  }
381  return response_or_errort<smt_responset>{std::vector<std::string>{
382  {"Invalid SMT response \"" + id2string(parse_tree.id()) + "\""}}};
383 }
mp_integer power(const mp_integer &base, const mp_integer &exponent)
A multi-precision implementation of the power operator.
bool empty() const
Definition: dstring.h:89
There are a large number of kinds of tree structured or tree-like data in CPROVER.
Definition: irep.h:360
subt & get_sub()
Definition: irep.h:444
std::string pretty(unsigned indent=0, unsigned max_indent=0) const
Definition: irep.cpp:482
const irep_idt & id() const
Definition: irep.h:384
Holds either a valid parsed response or response sub-tree of type.
static const smt_function_application_termt::factoryt< selectt > select
int toupper(int c)
Definition: ctype.c:105
const std::string & id2string(const irep_idt &d)
Definition: irep.h:40
const mp_integer string2integer(const std::string &n, unsigned base)
Definition: mp_arith.cpp:54
resultt
The result of goto verifying.
Definition: properties.h:45
Ranges: pair of begin and end iterators, which can be initialized from containers,...
ranget< iteratort > make_range(iteratort begin, iteratort end)
Definition: range.h:522
static std::optional< response_or_errort< smt_responset > > valid_smt_error_response(const irept &parse_tree)
static std::string print_parse_tree(const irept &parse_tree)
Produces a human-readable representation of the given parse_tree, for use in error messaging.
response_or_errort< smt_baset > validation_propagating(argumentst &&... arguments)
Given a class to construct and a set of arguments to its constructor which may include errors,...
void collect_error_messages_impl(std::vector< std::string > &collected_error_messages, argumentt &&argument)
static std::optional< response_or_errort< smt_responset > > valid_smt_get_value_response(const irept &parse_tree, const std::unordered_map< irep_idt, smt_identifier_termt > &identifier_table)
static response_or_errort< smt_termt > validate_term(const irept &parse_tree, const std::unordered_map< irep_idt, smt_identifier_termt > &identifier_table)
static response_or_errort< smt_get_value_responset::valuation_pairt > validate_valuation_pair(const irept &pair_parse_tree, const std::unordered_map< irep_idt, smt_identifier_termt > &identifier_table)
static std::optional< smt_termt > valid_smt_bit_vector_constant(const irept &parse_tree)
static std::optional< smt_termt > valid_smt_binary(const std::string &text)
static bool all_subs_are_pairs(const irept &parse_tree)
static std::optional< smt_termt > valid_smt_bool(const irept &parse_tree)
static std::optional< smt_termt > valid_smt_hex(const std::string &text)
static std::optional< smt_termt > valid_smt_indexed_bit_vector(const irept &parse_tree)
Checks for valid bit vector constants of the form (_ bv(value) (width)) for example - (_ bv4 64).
response_or_errort< smt_responset > validate_smt_response(const irept &parse_tree, const std::unordered_map< irep_idt, smt_identifier_termt > &identifier_table)
static response_or_errort< irep_idt > validate_string_literal(const irept &parse_tree)
std::vector< std::string > collect_error_messages(argumentst &&... arguments)
Builds a collection of error messages composed all error messages in the response_or_errort typed arg...
static std::optional< response_or_errort< smt_termt > > try_select_validation(const irept &parse_tree, const std::unordered_map< irep_idt, smt_identifier_termt > &identifier_table)
BigInt mp_integer
Definition: smt_terms.h:17
std::string smt_to_smt2_string(const smt_indext &index)
Streaming SMT data structures to a string based output stream.
#define PRECONDITION(CONDITION)
Definition: invariant.h:463
#define INVARIANT(CONDITION, REASON)
This macro uses the wrapper function 'invariant_violated_string'.
Definition: invariant.h:423
std::string to_string(const string_not_contains_constraintt &expr)
Used for debug printing.