CBMC
string_dependencies.cpp
Go to the documentation of this file.
1 /*******************************************************************\
2 
3 Module: String solver
4 
5 Author: Diffblue Ltd.
6 
7 \*******************************************************************/
8 
9 #include "string_dependencies.h"
12 #include <unordered_set>
13 #include <util/expr_iterator.h>
14 #include <util/graph.h>
15 #include <util/ssa_expr.h>
16 
19 static void for_each_atomic_string(
20  const array_string_exprt &e,
21  const std::function<void(const array_string_exprt &)> f);
22 
25 static std::unique_ptr<string_builtin_functiont> to_string_builtin_function(
26  const function_application_exprt &fun_app,
27  const exprt &return_code,
28  array_poolt &array_pool)
29 {
30  const auto name = expr_checked_cast<symbol_exprt>(fun_app.function());
31  PRECONDITION(!is_ssa_expr(name));
32 
33  const irep_idt &id = name.get_identifier();
34 
35  if(id == ID_cprover_string_insert_func)
36  return std::make_unique<string_insertion_builtin_functiont>(
37  return_code, fun_app.arguments(), array_pool);
38 
39  if(id == ID_cprover_string_concat_func)
40  return std::make_unique<string_concatenation_builtin_functiont>(
41  return_code, fun_app.arguments(), array_pool);
42 
43  if(id == ID_cprover_string_concat_char_func)
44  return std::make_unique<string_concat_char_builtin_functiont>(
45  return_code, fun_app.arguments(), array_pool);
46 
47  if(id == ID_cprover_string_format_func)
48  return std::make_unique<string_format_builtin_functiont>(
49  return_code, fun_app.arguments(), array_pool);
50 
51  if(id == ID_cprover_string_of_int_func)
52  return std::make_unique<string_of_int_builtin_functiont>(
53  return_code, fun_app.arguments(), array_pool);
54 
55  if(id == ID_cprover_string_char_set_func)
56  return std::make_unique<string_set_char_builtin_functiont>(
57  return_code, fun_app.arguments(), array_pool);
58 
59  if(id == ID_cprover_string_to_lower_case_func)
60  return std::make_unique<string_to_lower_case_builtin_functiont>(
61  return_code, fun_app.arguments(), array_pool);
62 
63  if(id == ID_cprover_string_to_upper_case_func)
64  return std::make_unique<string_to_upper_case_builtin_functiont>(
65  return_code, fun_app.arguments(), array_pool);
66 
67  return std::make_unique<string_builtin_function_with_no_evalt>(
68  return_code, fun_app, array_pool);
69 }
70 
73 {
74  auto entry_inserted = node_index_pool.emplace(e, string_nodes.size());
75  if(!entry_inserted.second)
76  return string_nodes[entry_inserted.first->second];
77 
78  string_nodes.emplace_back(e, entry_inserted.first->second);
79  return string_nodes.back();
80 }
81 
82 std::unique_ptr<const string_dependenciest::string_nodet>
84 {
85  const auto &it = node_index_pool.find(e);
86  if(it != node_index_pool.end())
87  return std::make_unique<const string_nodet>(string_nodes.at(it->second));
88  return {};
89 }
90 
92  std::unique_ptr<string_builtin_functiont> builtin_function)
93 {
94  builtin_function_nodes.emplace_back(
95  std::move(builtin_function), builtin_function_nodes.size());
96  return builtin_function_nodes.back();
97 }
98 
100  const builtin_function_nodet &node) const
101 {
102  return *node.data;
103 }
104 
106  const array_string_exprt &e,
107  const std::function<void(const array_string_exprt &)> f)
108 {
109  if(e.id() != ID_if)
110  return f(e);
111 
112  const auto if_expr = to_if_expr(e);
113  for_each_atomic_string(to_array_string_expr(if_expr.true_case()), f);
114  for_each_atomic_string(to_array_string_expr(if_expr.false_case()), f);
115 }
116 
118  const array_string_exprt &e,
119  const builtin_function_nodet &builtin_function_node)
120 {
121  for_each_atomic_string(e, [&](const array_string_exprt &s) { //NOLINT
122  string_nodet &string_node = get_node(s);
123  string_node.dependencies.push_back(builtin_function_node.index);
124  });
125 }
126 
128 {
129  builtin_function_nodes.clear();
130  string_nodes.clear();
131  node_index_pool.clear();
132  clean_cache();
133 }
134 
136  string_dependenciest &dependencies,
137  const function_application_exprt &fun_app,
138  const string_dependenciest::builtin_function_nodet &builtin_function_node,
139  array_poolt &array_pool)
140 {
141  PRECONDITION(fun_app.arguments()[0].type().id() != ID_pointer);
142  if(
143  fun_app.arguments().size() > 1 &&
144  fun_app.arguments()[1].type().id() == ID_pointer)
145  {
146  // Case where the result is given as a pointer argument
147  const array_string_exprt string =
148  array_pool.find(fun_app.arguments()[1], fun_app.arguments()[0]);
149  dependencies.add_dependency(string, builtin_function_node);
150  }
151 
152  for(const auto &expr : fun_app.arguments())
153  {
154  std::for_each(
155  expr.depth_begin(),
156  expr.depth_end(),
157  [&](const exprt &e) { // NOLINT
158  if(is_refined_string_type(e.type()))
159  {
160  const auto string_struct = expr_checked_cast<struct_exprt>(e);
161  const auto string = of_argument(array_pool, string_struct);
162  dependencies.add_dependency(string, builtin_function_node);
163  }
164  });
165  }
166 }
167 
168 std::optional<exprt> string_dependenciest::eval(
169  const array_string_exprt &s,
170  const std::function<exprt(const exprt &)> &get_value) const
171 {
172  const auto &it = node_index_pool.find(s);
173  if(it == node_index_pool.end())
174  return {};
175 
176  if(eval_string_cache[it->second])
177  return eval_string_cache[it->second];
178 
179  const auto node = string_nodes[it->second];
180  const auto &f = node.result_from;
181  if(f && node.dependencies.size() == 1)
182  {
183  const auto value_opt = builtin_function_nodes[*f].data->eval(get_value);
184  eval_string_cache[it->second] = value_opt;
185  return value_opt;
186  }
187  return {};
188 }
189 
191 {
192  eval_string_cache.resize(string_nodes.size());
193  for(auto &e : eval_string_cache)
194  e.reset();
195 }
196 
197 std::optional<exprt> add_node(
198  string_dependenciest &dependencies,
199  const exprt &expr,
200  array_poolt &array_pool,
201  symbol_generatort &fresh_symbol)
202 {
203  const auto fun_app = expr_try_dynamic_cast<function_application_exprt>(expr);
204  if(!fun_app)
205  {
206  exprt copy = expr;
207  bool copy_differs_from_expr = false;
208  for(std::size_t i = 0; i < expr.operands().size(); ++i)
209  {
210  auto new_op =
211  add_node(dependencies, expr.operands()[i], array_pool, fresh_symbol);
212  if(new_op)
213  {
214  copy.operands()[i] = *new_op;
215  copy_differs_from_expr = true;
216  }
217  }
218  if(copy_differs_from_expr)
219  return copy;
220  return {};
221  }
222 
223  const exprt return_code = fresh_symbol("string_builtin_return", expr.type());
224  auto builtin_function =
225  to_string_builtin_function(*fun_app, return_code, array_pool);
226 
227  CHECK_RETURN(builtin_function != nullptr);
228  const auto &builtin_function_node =
229  dependencies.make_node(std::move(builtin_function));
230 
231  if(
232  const auto &string_result =
233  dependencies.get_builtin_function(builtin_function_node).string_result())
234  {
235  dependencies.add_dependency(*string_result, builtin_function_node);
236  auto &node = dependencies.get_node(*string_result);
237  node.result_from = builtin_function_node.index;
238 
239  // Ensure all atomic strings in the argument have an associated node
240  for(const auto &arg : builtin_function_node.data->string_arguments())
241  {
243  arg, [&](const array_string_exprt &atomic) { // NOLINT
244  (void)dependencies.get_node(atomic);
245  });
246  }
247  }
248  else
250  dependencies, *fun_app, builtin_function_node, array_pool);
251 
252  return return_code;
253 }
254 
256  const builtin_function_nodet &node,
257  const std::function<void(const string_nodet &)> &f) const
258 {
259  for(const auto &s : node.data->string_arguments())
260  {
261  std::vector<std::reference_wrapper<const exprt>> stack({s});
262  while(!stack.empty())
263  {
264  const auto current = stack.back();
265  stack.pop_back();
266  if(const auto if_expr = expr_try_dynamic_cast<if_exprt>(current.get()))
267  {
268  stack.emplace_back(if_expr->true_case());
269  stack.emplace_back(if_expr->false_case());
270  }
271  else
272  {
273  const auto string_node = node_at(to_array_string_expr(current));
274  INVARIANT(
275  string_node,
276  "dependencies of the node should have been added to the graph at "
277  "node creation " +
278  current.get().pretty());
279  f(*string_node);
280  }
281  }
282  }
283 }
284 
286  const string_nodet &node,
287  const std::function<void(const builtin_function_nodet &)> &f) const
288 {
289  for(const std::size_t &index : node.dependencies)
290  f(builtin_function_nodes[index]);
291 }
292 
294  const nodet &node,
295  const std::function<void(const nodet &)> &f) const
296 {
297  switch(node.kind)
298  {
299  case nodet::BUILTIN:
302  [&](const string_nodet &n) { return f(nodet(n)); });
303  break;
304 
305  case nodet::STRING:
307  string_nodes[node.index],
308  [&](const builtin_function_nodet &n) { return f(nodet(n)); });
309  break;
310  }
311 }
312 
314  const std::function<void(const nodet &)> &f) const
315 {
316  for(const auto &string_node : string_nodes)
317  f(nodet(string_node));
318  for(std::size_t i = 0; i < builtin_function_nodes.size(); ++i)
320 }
321 
322 void string_dependenciest::output_dot(std::ostream &stream) const
323 {
324  const auto for_each =
325  [&](const std::function<void(const nodet &)> &f) { // NOLINT
326  for_each_node(f);
327  };
328  const auto for_each_succ =
329  [&](const nodet &n, const std::function<void(const nodet &)> &f) { // NOLINT
330  for_each_successor(n, f);
331  };
332  const auto node_to_string = [&](const nodet &n) { // NOLINT
333  std::stringstream ostream;
334  if(n.kind == nodet::BUILTIN)
335  ostream << '"' << builtin_function_nodes[n.index].data->name() << '_'
336  << n.index << '"';
337  else
338  ostream << '"' << format(string_nodes[n.index].expr) << '"';
339  return ostream.str();
340  };
341  stream << "digraph dependencies {\n";
342  output_dot_generic<nodet>(
343  stream, for_each, for_each_succ, node_to_string, node_to_string);
344  stream << '}' << std::endl;
345 }
346 
348  string_constraint_generatort &generator,
349  message_handlert &message_handler)
350 {
351  std::unordered_set<nodet, node_hash> test_dependencies;
352  for(const auto &builtin : builtin_function_nodes)
353  {
354  if(builtin.data->maybe_testing_function())
355  test_dependencies.insert(nodet(builtin));
356  }
357 
359  test_dependencies,
360  [&](
361  const nodet &n,
362  const std::function<void(const nodet &)> &f) { // NOLINT
363  for_each_successor(n, f);
364  });
365 
366  string_constraintst constraints;
367  for(const auto &node : builtin_function_nodes)
368  {
369  if(test_dependencies.count(nodet(node)))
370  {
371  const auto &builtin = builtin_function_nodes[node.index];
372  merge(constraints, builtin.data->constraints(generator, message_handler));
373  }
374  else
375  constraints.existential.push_back(node.data->length_constraint());
376  }
377  return constraints;
378 }
Correspondance between arrays and pointers string representations.
Definition: array_pool.h:42
array_string_exprt find(const exprt &pointer, const exprt &length)
Creates a new array if the pointer is not pointing to an array.
Definition: array_pool.cpp:184
dstringt has one field, an unsigned integer no which is an index into a static table of strings.
Definition: dstring.h:38
Base class for all expressions.
Definition: expr.h:56
typet & type()
Return the type of the expression.
Definition: expr.h:84
operandst & operands()
Definition: expr.h:94
Application of (mathematical) function.
const irep_idt & id() const
Definition: irep.h:384
Base class for string functions that are built in the solver.
virtual std::optional< array_string_exprt > string_result() const
A builtin function node contains a builtin function call.
std::unique_ptr< string_builtin_functiont > data
enum string_dependenciest::nodet::@6 kind
A string node points to builtin_function on which it depends.
std::optional< std::size_t > result_from
std::vector< std::size_t > dependencies
Keep track of dependencies between strings.
std::unique_ptr< const string_nodet > node_at(const array_string_exprt &e) const
std::vector< std::optional< exprt > > eval_string_cache
void for_each_node(const std::function< void(const nodet &)> &f) const
Applies f on all nodes.
builtin_function_nodet & make_node(std::unique_ptr< string_builtin_functiont > builtin_function)
void output_dot(std::ostream &stream) const
void clean_cache()
Clean the cache used by eval
std::vector< builtin_function_nodet > builtin_function_nodes
Set of nodes representing builtin_functions.
std::optional< exprt > eval(const array_string_exprt &s, const std::function< exprt(const exprt &)> &get_value) const
Attempt to evaluate the given string from the dependencies and valuation of strings on which it depen...
void clear()
Clear the content of the dependency graph.
const string_builtin_functiont & get_builtin_function(const builtin_function_nodet &node) const
std::unordered_map< array_string_exprt, std::size_t, irep_hash > node_index_pool
Nodes describing dependencies of a string: values of the map correspond to indexes in the vector stri...
std::vector< string_nodet > string_nodes
Set of nodes representing strings.
void for_each_dependency(const string_nodet &node, const std::function< void(const builtin_function_nodet &)> &f) const
Applies f to each node on which node depends.
string_constraintst add_constraints(string_constraint_generatort &generatort, message_handlert &message_handler)
For all builtin call on which a test (or an unsupported buitin) result depends, add the corresponding...
void for_each_successor(const nodet &i, const std::function< void(const nodet &)> &f) const
Applies f on all successors of the node n.
void add_dependency(const array_string_exprt &e, const builtin_function_nodet &builtin_function)
Add edge from node for e&#160;to node for builtin_function if e is a simple array expression.
string_nodet & get_node(const array_string_exprt &e)
Generation of fresh symbols of a given type.
Definition: array_pool.h:22
Forward depth-first search iterators These iterators' copy operations are expensive,...
static format_containert< T > format(const T &o)
Definition: format.h:37
A Template Class for Graphs.
void get_reachable(Container &set, const std::function< void(const typename Container::value_type &, const std::function< void(const typename Container::value_type &)> &)> &for_each_successor)
Add to set, nodes that are reachable from set.
Definition: graph.h:573
#define CHECK_RETURN(CONDITION)
Definition: invariant.h:495
#define PRECONDITION(CONDITION)
Definition: invariant.h:463
bool is_ssa_expr(const exprt &expr)
Definition: ssa_expr.h:125
const if_exprt & to_if_expr(const exprt &expr)
Cast an exprt to an if_exprt.
Definition: std_expr.h:2450
Builtin functions for string concatenations.
void merge(string_constraintst &result, string_constraintst other)
Merge two sets of constraints by appending to the first one.
static std::unique_ptr< string_builtin_functiont > to_string_builtin_function(const function_application_exprt &fun_app, const exprt &return_code, array_poolt &array_pool)
Construct a string_builtin_functiont object from a function application.
std::optional< exprt > add_node(string_dependenciest &dependencies, const exprt &expr, array_poolt &array_pool, symbol_generatort &fresh_symbol)
When a sub-expression of expr is a builtin_function, add a "string_builtin_function" node to the grap...
static void for_each_atomic_string(const array_string_exprt &e, const std::function< void(const array_string_exprt &)> f)
Applies f on all strings contained in e that are not if-expressions.
static void add_dependency_to_string_subexprs(string_dependenciest &dependencies, const function_application_exprt &fun_app, const string_dependenciest::builtin_function_nodet &builtin_function_node, array_poolt &array_pool)
Keeps track of dependencies between strings.
array_string_exprt & to_array_string_expr(exprt &expr)
Definition: string_expr.h:96
Built-in function for String.format.
Collection of constraints of different types: existential formulas, universal formulas,...