CBMC
function_call_harness_generator.cpp
Go to the documentation of this file.
1 /******************************************************************\
2 
3 Module: harness generator for functions
4 
5 Author: Diffblue Ltd.
6 
7 \******************************************************************/
8 
10 
11 #include <util/arith_tools.h>
12 #include <util/c_types.h>
13 #include <util/exception_utils.h>
14 #include <util/prefix.h>
15 #include <util/std_code.h>
16 #include <util/string_utils.h>
17 #include <util/ui_message.h>
18 
22 
26 
27 #include <algorithm>
28 #include <iterator>
29 #include <set>
30 #include <utility>
31 
35 /* NOLINTNEXTLINE(readability/identifier_spacing) */
37 {
39  irep_idt function;
43  bool nondet_globals = false;
44 
46  std::unique_ptr<recursive_initializationt> recursive_initialization;
47 
50 
51  std::vector<std::set<irep_idt>> function_parameters_to_treat_equal;
52  std::vector<std::set<irep_idt>> function_arguments_to_treat_equal;
53 
56 
57  std::map<irep_idt, irep_idt> function_argument_to_associated_array_size;
58  std::map<irep_idt, irep_idt> function_parameter_to_associated_array_size;
59 
60  std::set<symbol_exprt> global_pointers;
61 
63  void generate(goto_modelt &goto_model, const irep_idt &harness_function_name);
66  void generate_nondet_globals(code_blockt &function_body);
70  void generate_initialisation_code_for(code_blockt &block, const exprt &lhs);
80  void call_function(
81  const code_function_callt::argumentst &arguments,
82  code_blockt &function_body);
91  std::unordered_set<irep_idt>
93 };
94 
96  ui_message_handlert &message_handler)
97  : goto_harness_generatort{}, p_impl(std::make_unique<implt>())
98 {
99  p_impl->message_handler = &message_handler;
100 }
101 
103 
105  const std::string &option,
106  const std::list<std::string> &values)
107 {
110 
111  if(p_impl->recursive_initialization_config.handle_option(option, values))
112  {
113  // the option belongs to recursive initialization
114  }
115  else if(option == FUNCTION_HARNESS_GENERATOR_FUNCTION_OPT)
116  {
117  p_impl->function = require_exactly_one_value(option, values);
118  }
120  {
121  p_impl->nondet_globals = true;
122  }
124  {
125  p_impl->function_parameters_to_treat_as_arrays.insert(
126  values.begin(), values.end());
127  }
129  {
130  for(auto const &value : values)
131  {
132  for(auto const &param_cluster : split_string(value, ';'))
133  {
134  std::set<irep_idt> equal_param_set;
135  for(auto const &param_id : split_string(param_cluster, ','))
136  {
137  equal_param_set.insert(param_id);
138  }
139  p_impl->function_parameters_to_treat_equal.push_back(
140  std::move(equal_param_set));
141  }
142  }
143  }
145  {
146  for(auto const &array_size_pair : values)
147  {
148  try
149  {
150  std::string array;
151  std::string size;
152  split_string(array_size_pair, ':', array, size);
153  // --associated-array-size implies --treat-pointer-as-array
154  // but it is not an error to specify both, so we don't check
155  // for duplicates here
156  p_impl->function_parameters_to_treat_as_arrays.insert(array);
157  auto const inserted =
158  p_impl->function_parameter_to_associated_array_size.emplace(
159  array, size);
160  if(!inserted.second)
161  {
163  "can not have two associated array sizes for one array",
165  }
166  }
167  catch(const deserialization_exceptiont &)
168  {
170  "'" + array_size_pair +
171  "' is in an invalid format for array size pair",
173  "array_name:size_name, where both are the names of function "
174  "parameters"};
175  }
176  }
177  }
179  {
180  p_impl->function_parameters_to_treat_as_cstrings.insert(
181  values.begin(), values.end());
182  }
184  {
185  p_impl->recursive_initialization_config.arguments_may_be_equal = true;
186  }
188  {
190  values.begin(),
191  values.end(),
192  std::inserter(
193  p_impl->recursive_initialization_config
194  .potential_null_function_pointers,
195  p_impl->recursive_initialization_config.potential_null_function_pointers
196  .end()),
197  [](const std::string &opt) -> irep_idt { return irep_idt{opt}; });
198  }
200  {
202  values.begin(),
203  values.end(),
204  std::inserter(
205  p_impl->recursive_initialization_config
206  .potential_null_function_pointers,
207  p_impl->recursive_initialization_config.potential_null_function_pointers
208  .end()),
209  [](const std::string &opt) -> irep_idt { return irep_idt{opt}; });
210  }
211  else
212  {
214  "function harness generator cannot handle this option", "--" + option};
215  }
216 }
217 
219  goto_modelt &goto_model,
220  const irep_idt &harness_function_name)
221 {
222  p_impl->generate(goto_model, harness_function_name);
223 }
224 
226  goto_modelt &goto_model,
227  const irep_idt &harness_function_name)
228 {
229  symbol_table = &goto_model.symbol_table;
230  goto_functions = &goto_model.goto_functions;
231  this->harness_function_name = harness_function_name;
234 
235  // create body for the function
236  code_blockt function_body{};
237  auto const arguments = declare_arguments(function_body);
238 
239  // configure and create recursive initialisation object
247  for(const auto &pair : function_argument_to_associated_array_size)
248  {
250  pair.second);
251  }
256  recursive_initialization = std::make_unique<recursive_initializationt>(
257  recursive_initialization_config, goto_model);
258 
259  generate_nondet_globals(function_body);
260  call_function(arguments, function_body);
261  for(const auto &global_pointer : global_pointers)
262  {
263  recursive_initialization->free_if_possible(global_pointer, function_body);
264  }
265  recursive_initialization->free_cluster_origins(function_body);
266  add_harness_function_to_goto_model(std::move(function_body));
267 }
268 
270  code_blockt &function_body)
271 {
272  if(nondet_globals)
273  {
274  // generating initialisation code may introduce new globals
275  // i.e. modify the symbol table.
276  // Modifying the symbol table while iterating over it is not
277  // a good idea, therefore we just collect the names of globals
278  // we need to initialise first and then generate the
279  // initialisation code for all of them.
280  auto globals = std::vector<symbol_exprt>{};
281  for(const auto &symbol_table_entry : *symbol_table)
282  {
283  const auto &symbol = symbol_table_entry.second;
285  {
286  globals.push_back(symbol.symbol_expr());
287  }
288  }
289  for(auto const &global : globals)
290  {
291  generate_initialisation_code_for(function_body, global);
292  }
293  }
294 }
295 
297  code_blockt &block,
298  const exprt &lhs)
299 {
300  recursive_initialization->initialize(
301  lhs, from_integer(0, signed_int_type()), block);
302  if(recursive_initialization->needs_freeing(lhs))
303  global_pointers.insert(to_symbol_expr(lhs));
304 }
305 
307  const goto_modelt &goto_model)
308 {
309  if(p_impl->function == ID_empty_string)
311  "required parameter entry function not set",
313  if(
314  p_impl->recursive_initialization_config.min_dynamic_array_size >
315  p_impl->recursive_initialization_config.max_dynamic_array_size)
316  {
318  "min dynamic array size cannot be greater than max dynamic array size",
321  }
322 
323  const auto function_to_call_pointer =
324  goto_model.symbol_table.lookup(p_impl->function);
325  if(function_to_call_pointer == nullptr)
326  {
328  "entry function `" + id2string(p_impl->function) +
329  "' does not exist in the symbol table",
331  }
332  const auto &function_to_call = *function_to_call_pointer;
333  const auto &ftype = to_code_type(function_to_call.type);
334  for(auto const &equal_cluster : p_impl->function_parameters_to_treat_equal)
335  {
336  for(auto const &pointer_id : equal_cluster)
337  {
338  std::string decorated_pointer_id =
339  id2string(p_impl->function) + "::" + id2string(pointer_id);
340  bool is_a_param = false;
341  typet common_type = empty_typet{};
342  for(auto const &formal_param : ftype.parameters())
343  {
344  if(formal_param.get_identifier() == decorated_pointer_id)
345  {
346  is_a_param = true;
347  if(formal_param.type().id() != ID_pointer)
348  {
350  id2string(pointer_id) + " is not a pointer parameter",
352  }
353  if(common_type.id() != ID_empty)
354  {
355  if(common_type != formal_param.type())
356  {
358  "pointer arguments of conflicting type",
360  }
361  }
362  else
363  common_type = formal_param.type();
364  }
365  }
366  if(!is_a_param)
367  {
369  id2string(pointer_id) + " is not a parameter",
371  }
372  }
373  }
374 
375  const namespacet ns{goto_model.symbol_table};
376 
377  // Make sure all function pointers that the user asks are nullable are
378  // present in the symbol table.
379  for(const auto &nullable :
380  p_impl->recursive_initialization_config.potential_null_function_pointers)
381  {
382  const auto &function_pointer_symbol_pointer =
383  goto_model.symbol_table.lookup(nullable);
384 
385  if(function_pointer_symbol_pointer == nullptr)
386  {
388  "nullable function pointer `" + id2string(nullable) +
389  "' not found in symbol table",
391  }
392 
393  const auto &function_pointer_type =
394  ns.follow(function_pointer_symbol_pointer->type);
395 
396  if(!can_cast_type<pointer_typet>(function_pointer_type))
397  {
399  "`" + id2string(nullable) + "' is not a pointer",
401  }
402 
404  to_pointer_type(function_pointer_type).base_type()))
405  {
407  "`" + id2string(nullable) + "' is not pointing to a function",
409  }
410  }
411 }
412 
413 const symbolt &
415 {
416  auto function_found = symbol_table->lookup(function);
417 
418  if(function_found == nullptr)
419  {
421  "function that should be harnessed is not found " + id2string(function),
423  }
424 
425  return *function_found;
426 }
427 
430 {
431  if(symbol_table->lookup(harness_function_name))
432  {
434  "harness function already exists in the symbol table",
436  }
437 }
438 
441 {
442  const auto &function_to_call = lookup_function_to_call();
443 
444  // create the function symbol
445  symbolt harness_function_symbol{};
446  harness_function_symbol.name = harness_function_symbol.base_name =
447  harness_function_symbol.pretty_name = harness_function_name;
448 
449  harness_function_symbol.is_lvalue = true;
450  harness_function_symbol.mode = function_to_call.mode;
451  harness_function_symbol.type = code_typet{{}, empty_typet{}};
452  harness_function_symbol.value = std::move(function_body);
453 
454  symbol_table->insert(harness_function_symbol);
455 
456  goto_convert(*symbol_table, *goto_functions, *message_handler);
457 }
458 
461  code_blockt &function_body)
462 {
463  const auto &function_to_call = lookup_function_to_call();
464  const auto &function_type = to_code_type(function_to_call.type);
465  const auto &parameters = function_type.parameters();
466 
467  code_function_callt::operandst arguments{};
468 
469  auto allocate_objects = allocate_objectst{function_to_call.mode,
470  function_to_call.location,
471  "__goto_harness",
472  *symbol_table};
473  std::map<irep_idt, irep_idt> parameter_name_to_argument_name;
474  for(const auto &parameter : parameters)
475  {
476  auto argument = allocate_objects.allocate_automatic_local_object(
477  remove_const(parameter.type()), parameter.get_base_name());
478  parameter_name_to_argument_name.insert(
479  {parameter.get_base_name(), argument.get_identifier()});
480  arguments.push_back(argument);
481  }
482 
483  for(const auto &pair : parameter_name_to_argument_name)
484  {
485  auto const &parameter_name = pair.first;
486  auto const &argument_name = pair.second;
487  if(function_parameters_to_treat_as_arrays.count(parameter_name) != 0)
488  {
489  function_arguments_to_treat_as_arrays.insert(argument_name);
490  }
491 
492  if(function_parameters_to_treat_as_cstrings.count(parameter_name) != 0)
493  {
494  function_arguments_to_treat_as_cstrings.insert(argument_name);
495  }
496 
497  auto it = function_parameter_to_associated_array_size.find(parameter_name);
498  if(it != function_parameter_to_associated_array_size.end())
499  {
500  auto const &associated_array_size_parameter = it->second;
501  auto associated_array_size_argument =
502  parameter_name_to_argument_name.find(associated_array_size_parameter);
503  if(
504  associated_array_size_argument == parameter_name_to_argument_name.end())
505  {
507  "could not find parameter to associate the array size of array \"" +
508  id2string(parameter_name) + "\" (Expected parameter: \"" +
509  id2string(associated_array_size_parameter) + "\" on function \"" +
510  id2string(function_to_call.display_name()) + "\" in " +
511  function_to_call.location.as_string() + ")",
513  }
514  function_argument_to_associated_array_size.insert(
515  {argument_name, associated_array_size_argument->second});
516  }
517  }
518 
519  // translate the parameter to argument also in the equality clusters
520  for(auto const &equal_cluster : function_parameters_to_treat_equal)
521  {
522  std::set<irep_idt> cluster_argument_names;
523  for(auto const &parameter_name : equal_cluster)
524  {
525  INVARIANT(
526  parameter_name_to_argument_name.count(parameter_name) != 0,
527  id2string(parameter_name) + " is not a parameter");
528  cluster_argument_names.insert(
529  parameter_name_to_argument_name[parameter_name]);
530  }
531  function_arguments_to_treat_equal.push_back(
532  std::move(cluster_argument_names));
533  }
534 
535  allocate_objects.declare_created_symbols(function_body);
536  return arguments;
537 }
538 
540  const code_function_callt::argumentst &arguments,
541  code_blockt &function_body)
542 {
543  auto const &function_to_call = lookup_function_to_call();
544  for(auto const &argument : arguments)
545  {
546  generate_initialisation_code_for(function_body, argument);
547  if(recursive_initialization->needs_freeing(argument))
548  global_pointers.insert(to_symbol_expr(argument));
549  }
550  code_function_callt function_call{function_to_call.symbol_expr(),
551  std::move(arguments)};
552  function_call.add_source_location() = function_to_call.location;
553 
554  function_body.add(std::move(function_call));
555 }
556 
557 std::unordered_set<irep_idt> function_call_harness_generatort::implt::
559 {
560  std::unordered_set<irep_idt> nullables;
561  for(const auto &nullable :
562  recursive_initialization_config.potential_null_function_pointers)
563  {
564  const auto &nullable_name = id2string(nullable);
565  const auto &function_prefix = id2string(function) + "::";
566  if(has_prefix(nullable_name, function_prefix))
567  {
568  nullables.insert(
569  "__goto_harness::" + nullable_name.substr(function_prefix.size()));
570  }
571  else
572  {
573  nullables.insert(nullable_name);
574  }
575  }
576  return nullables;
577 }
static abstract_object_pointert transform(const exprt &expr, const std::vector< abstract_object_pointert > &operands, const abstract_environmentt &environment, const namespacet &ns)
constant_exprt from_integer(const mp_integer &int_value, const typet &type)
signedbv_typet signed_int_type()
Definition: c_types.cpp:22
unsigned char opt
Definition: cegis.c:20
A codet representing sequential composition of program statements.
Definition: std_code.h:130
void add(const codet &code)
Definition: std_code.h:168
goto_instruction_codet representation of a function call statement.
exprt::operandst argumentst
Base type of functions.
Definition: std_types.h:583
Thrown when failing to deserialize a value from some low level format, like JSON or raw bytes.
dstringt has one field, an unsigned integer no which is an index into a static table of strings.
Definition: dstring.h:38
The empty type.
Definition: std_types.h:51
Base class for all expressions.
Definition: expr.h:56
std::vector< exprt > operandst
Definition: expr.h:58
source_locationt & add_source_location()
Definition: expr.h:236
typet & type()
Return the type of the expression.
Definition: expr.h:84
void validate_options(const goto_modelt &goto_model) override
Check if options are in a sane state, throw otherwise.
function_call_harness_generatort(ui_message_handlert &message_handler)
void handle_option(const std::string &option, const std::list< std::string > &values) override
Handle a command line argument.
void generate(goto_modelt &goto_model, const irep_idt &harness_function_name) override
Generate a harness according to the set options.
A collection of goto functions.
symbol_tablet symbol_table
Symbol table.
Definition: goto_model.h:31
goto_functionst goto_functions
GOTO functions.
Definition: goto_model.h:34
Thrown when users pass incorrect command line arguments, for example passing no files to analysis or ...
const irept & find(const irep_idt &name) const
Definition: irep.cpp:93
const irep_idt & id() const
Definition: irep.h:384
A namespacet is essentially one or two symbol tables bound together, to allow for symbol lookups in t...
Definition: namespace.h:94
static bool is_initialization_allowed(const symbolt &symbol)
const symbolt * lookup(const irep_idt &name) const
Find a symbol in the symbol table for read-only access.
The symbol table.
Definition: symbol_table.h:14
Symbol table entry.
Definition: symbol.h:28
irep_idt name
The unique identifier.
Definition: symbol.h:40
The type of an expression, extends irept.
Definition: type.h:29
#define COMMON_HARNESS_GENERATOR_FUNCTION_POINTER_CAN_BE_NULL_OPT
#define COMMON_HARNESS_GENERATOR_MIN_ARRAY_SIZE_OPT
#define COMMON_HARNESS_GENERATOR_MAX_ARRAY_SIZE_OPT
bool has_prefix(const std::string &s, const std::string &prefix)
Definition: converter.cpp:13
code_function_callt function_to_call(symbol_table_baset &symbol_table, const irep_idt &id, const irep_idt &argument)
Definition: function.cpp:22
#define FUNCTION_HARNESS_GENERATOR_FUNCTION_OPT
#define FUNCTION_HARNESS_GENERATOR_ASSOCIATED_ARRAY_SIZE_OPT
#define FUNCTION_HARNESS_GENERATOR_TREAT_POINTERS_EQUAL_MAYBE_OPT
#define FUNCTION_HARNESS_GENERATOR_NONDET_GLOBALS_OPT
#define FUNCTION_HARNESS_GENERATOR_TREAT_POINTERS_EQUAL_OPT
#define FUNCTION_HARNESS_GENERATOR_TREAT_POINTER_AS_ARRAY_OPT
#define FUNCTION_HARNESS_GENERATOR_TREAT_POINTER_AS_CSTRING
void goto_convert(const codet &code, symbol_table_baset &symbol_table, goto_programt &dest, message_handlert &message_handler, const irep_idt &mode)
Goto Programs with Functions.
#define GOTO_HARNESS_GENERATOR_HARNESS_FUNCTION_NAME_OPT
Symbol Table + CFG.
const std::string & id2string(const irep_idt &d)
Definition: irep.h:40
std::string require_exactly_one_value(const std::string &option, const std::list< std::string > &values)
Returns the only value of a single element list, throws an exception if not passed a single element l...
bool can_cast_type< pointer_typet >(const typet &type)
Check whether a reference to a typet is a pointer_typet.
Definition: pointer_expr.h:80
const pointer_typet & to_pointer_type(const typet &type)
Cast a typet to a pointer_typet.
Definition: pointer_expr.h:93
const symbol_exprt & to_symbol_expr(const exprt &expr)
Cast an exprt to a symbol_exprt.
Definition: std_expr.h:272
const code_typet & to_code_type(const typet &type)
Cast a typet to a code_typet.
Definition: std_types.h:788
bool can_cast_type< code_typet >(const typet &type)
Check whether a reference to a typet is a code_typet.
Definition: std_types.h:775
void split_string(const std::string &s, char delim, std::vector< std::string > &result, bool strip, bool remove_empty)
This contains implementation details of function call harness generator to avoid leaking them out int...
std::map< irep_idt, irep_idt > function_parameter_to_associated_array_size
void call_function(const code_function_callt::argumentst &arguments, code_blockt &function_body)
Write initialisation code for each of the arguments into function_body, then insert a call to the ent...
std::map< irep_idt, irep_idt > function_argument_to_associated_array_size
void generate(goto_modelt &goto_model, const irep_idt &harness_function_name)
std::vector< std::set< irep_idt > > function_arguments_to_treat_equal
const symbolt & lookup_function_to_call()
Return a reference to the entry function or throw if it doesn't exist.
code_function_callt::argumentst declare_arguments(code_blockt &function_body)
Declare local variables for each of the parameters of the entry function and return them.
void add_harness_function_to_goto_model(code_blockt function_body)
Update the goto-model with the new harness function.
std::unique_ptr< recursive_initializationt > recursive_initialization
void generate_nondet_globals(code_blockt &function_body)
Iterate over the symbol table and generate initialisation code for globals into the function body.
recursive_initialization_configt recursive_initialization_config
void generate_initialisation_code_for(code_blockt &block, const exprt &lhs)
Generate initialisation code for one lvalue inside block.
std::vector< std::set< irep_idt > > function_parameters_to_treat_equal
std::unordered_set< irep_idt > map_function_parameters_to_function_argument_names()
For function parameters that are pointers to functions we want to be able to specify whether or not t...
void ensure_harness_does_not_already_exist()
Throw if the harness function already exists in the symbol table.
std::set< irep_idt > pointers_to_treat_as_cstrings
std::vector< std::set< irep_idt > > pointers_to_treat_equal
std::map< irep_idt, irep_idt > array_name_to_associated_array_size_variable
std::set< irep_idt > variables_that_hold_array_sizes
std::set< irep_idt > pointers_to_treat_as_arrays
std::unordered_set< irep_idt > potential_null_function_pointers
typet remove_const(typet type)
Remove const qualifier from type (if any).
Definition: type.cpp:32