CBMC
complexity_limiter.cpp
Go to the documentation of this file.
1 /*******************************************************************\
2 
3 Module: Symbolic Execution
4 
5 Author: John Dumbell
6 
7 \*******************************************************************/
8 
9 #include "complexity_limiter.h"
10 
11 #include <util/options.h>
12 
13 #include "goto_symex_state.h"
14 
15 #include <cmath>
16 
18  message_handlert &message_handler,
19  const optionst &options)
20  : log(message_handler)
21 {
22  std::size_t limit = options.get_signed_int_option("symex-complexity-limit");
23  if((complexity_active = limit > 0))
24  {
25  // This gives a curve that allows low limits to be rightly restrictive,
26  // while larger numbers are very large.
27  max_complexity = static_cast<std::size_t>((limit * limit) * 25);
28 
29  const std::size_t failed_child_loops_limit = options.get_signed_int_option(
30  "symex-complexity-failed-child-loops-limit");
31  const std::size_t unwind = options.get_signed_int_option("unwind");
32 
33  // If we have complexity enabled, try to work out a failed_children_limit.
34  // In order of priority:
35  // * explicit limit
36  // * inferred limit from unwind
37  // * best limit we can apply with very little information.
38  if(failed_child_loops_limit > 0)
39  max_loops_complexity = failed_child_loops_limit;
40  else if(unwind > 0)
41  max_loops_complexity = std::max(static_cast<int>(floor(unwind / 3)), 1);
42  else
43  max_loops_complexity = limit;
44  }
45 }
46 
49 {
50  for(auto frame_iter = current_call_stack.rbegin();
51  frame_iter != current_call_stack.rend();
52  ++frame_iter)
53  {
54  // As we're walking bottom-up, the first frame we find with active loops
55  // is our closest active one.
56  if(!frame_iter->active_loops.empty())
57  {
58  return &frame_iter->active_loops.back();
59  }
60  }
61 
62  return {};
63 }
64 
66  const call_stackt &current_call_stack,
67  const goto_programt::const_targett &instr)
68 {
69  for(auto frame_iter = current_call_stack.rbegin();
70  frame_iter != current_call_stack.rend();
71  ++frame_iter)
72  {
73  for(auto &loop_iter : frame_iter->active_loops)
74  {
75  for(auto &blacklisted_loop : loop_iter.blacklisted_loops)
76  {
77  if(blacklisted_loop.get().contains(instr))
78  {
79  return true;
80  }
81  }
82  }
83  }
84 
85  return false;
86 }
87 
89  call_stackt &current_call_stack)
90 {
91  std::size_t sum_complexity = 0;
92 
93  // This will walk all currently active loops, from inner-most to outer-most,
94  // and sum the times their branches have failed.
95  //
96  // If we find that this sum is higher than our max_loops_complexity we take
97  // note of the loop that happens in and then cause every parent still
98  // executing that loop to blacklist it.
99  //
100  // This acts as a context-sensitive loop cancel, so if we've got unwind 20
101  // and find out at the 3rd iteration a particular nested loop is too
102  // complicated, we make sure we don't execute it the other 17 times. But as
103  // soon as we're running the loop again via a different context it gets a
104  // chance to redeem itself.
105  lexical_loopst::loopt *loop_to_blacklist = nullptr;
106  for(auto frame_iter = current_call_stack.rbegin();
107  frame_iter != current_call_stack.rend();
108  ++frame_iter)
109  {
110  for(auto it = frame_iter->active_loops.rbegin();
111  it != frame_iter->active_loops.rend();
112  it++)
113  {
114  auto &loop_info = *it;
115 
116  // Because we're walking in reverse this will only be non-empty for
117  // parents of the loop that's been blacklisted. We then add that to their
118  // internal lists of blacklisted children.
119  if(loop_to_blacklist)
120  {
121  loop_info.blacklisted_loops.emplace_back(*loop_to_blacklist);
122  }
123  else
124  {
125  sum_complexity += loop_info.children_too_complex;
126  if(sum_complexity > max_loops_complexity)
127  {
128  loop_to_blacklist = &loop_info.loop;
129  }
130  }
131  }
132  }
133 
134  return !loop_to_blacklist;
135 }
136 
139 {
140  if(!complexity_limits_active() || !state.reachable)
142 
143  std::size_t complexity =
145  if(complexity == 1)
147 
148  auto &current_call_stack = state.call_stack();
149 
150  // Check if this branch is too complicated to continue.
151  auto active_loop = get_current_active_loop(current_call_stack);
152  if(complexity >= max_complexity)
153  {
154  // If we're too complex, add a counter to the current loop we're in and
155  // check if we've violated child-loop complexity limits.
156  if(active_loop != nullptr)
157  {
158  active_loop->children_too_complex++;
159 
160  // If we're considered too complex, cancel branch.
161  if(are_loop_children_too_complicated(current_call_stack))
162  {
163  log.warning()
164  << "[symex-complexity] Loop operations considered too complex"
165  << (state.source.pc->source_location().is_not_nil()
166  ? " at: " + state.source.pc->source_location().as_string()
167  : ", location number: " +
168  std::to_string(state.source.pc->location_number) + ".")
169  << messaget::eom;
170 
172  }
173  }
174 
175  log.warning() << "[symex-complexity] Branch considered too complex"
176  << (state.source.pc->source_location().is_not_nil()
177  ? " at: " +
178  state.source.pc->source_location().as_string()
179  : ", location number: " +
180  std::to_string(state.source.pc->location_number) +
181  ".")
182  << messaget::eom;
183 
184  // Then kill this branch.
186  }
187 
188  // If we're not in any loop, return with no violation.
189  if(!active_loop)
191 
192  // Check if we've entered a loop that has been previously black-listed, and
193  // if so then cancel before we go any further.
194  if(in_blacklisted_loop(current_call_stack, state.source.pc))
195  {
196  log.warning() << "[symex-complexity] Trying to enter blacklisted loop"
197  << (state.source.pc->source_location().is_not_nil()
198  ? " at: " +
199  state.source.pc->source_location().as_string()
200  : ", location number: " +
201  std::to_string(state.source.pc->location_number) +
202  ".")
203  << messaget::eom;
204 
206  }
207 
209 }
210 
212  complexity_violationt complexity_violation,
213  goto_symex_statet &current_state)
214 {
215  if(violation_transformations.empty())
216  default_transformation.transform(complexity_violation, current_state);
217  else
218  for(auto transform_lambda : violation_transformations)
219  transform_lambda.transform(complexity_violation, current_state);
220 }
221 
226 static std::size_t
227 bounded_expr_size(const exprt &expr, std::size_t count, std::size_t limit)
228 {
229  const auto &ops = expr.operands();
230  count += ops.size();
231  for(const auto &op : ops)
232  {
233  if(count >= limit)
234  {
235  return count;
236  }
237  count = bounded_expr_size(op, count, limit);
238  }
239  return count;
240 }
241 
242 std::size_t
243 complexity_limitert::bounded_expr_size(const exprt &expr, std::size_t limit)
244 {
245  return ::bounded_expr_size(expr, 1, limit);
246 }
complexity_violationt check_complexity(goto_symex_statet &state)
Checks the passed-in state to see if its become too complex for us to deal with, and if so set its gu...
bool complexity_limits_active()
Is the complexity module active?
static std::size_t bounded_expr_size(const exprt &expr, std::size_t limit)
Amount of nodes in expr approximately bounded by limit.
bool complexity_active
Is the complexity module active, usually coincides with a max_complexity value above 0.
static bool in_blacklisted_loop(const call_stackt &current_call_stack, const goto_programt::const_targett &instr)
Checks whether we're in a loop that is currently considered blacklisted, and shouldn't be executed.
std::size_t max_complexity
The max complexity rating that a branch can be before it's abandoned.
std::vector< symex_complexity_limit_exceeded_actiont > violation_transformations
Functions called when the heuristic has been violated.
std::size_t max_loops_complexity
The amount of branches that can fail within the scope of a loops execution before the entire loop is ...
static framet::active_loop_infot * get_current_active_loop(call_stackt &current_call_stack)
Returns inner-most currently active loop.
complexity_limitert()=default
symex_complexity_limit_exceeded_actiont default_transformation
Default heuristic transformation. Sets state as unreachable.
void run_transformations(complexity_violationt complexity_violation, goto_symex_statet &current_state)
Runs a suite of transformations on the state and symex executable, performing whatever transformation...
bool are_loop_children_too_complicated(call_stackt &current_call_stack)
Checks whether the current loop execution stack has violated max_loops_complexity.
Base class for all expressions.
Definition: expr.h:56
operandst & operands()
Definition: expr.h:94
instructionst::const_iterator const_targett
Definition: goto_program.h:615
guardt guard
Definition: goto_state.h:58
bool reachable
Is this code reachable? If not we can take shortcuts such as not entering function calls,...
Definition: goto_state.h:62
Central data structure: state.
call_stackt & call_stack()
symex_targett::sourcet source
exprt as_expr() const
Definition: guard_expr.h:46
A loop, specified as a set of instructions.
Definition: loop_analysis.h:24
mstreamt & warning() const
Definition: message.h:404
static eomt eom
Definition: message.h:297
signed int get_signed_int_option(const std::string &option) const
Definition: options.cpp:50
virtual void transform(const complexity_violationt heuristic_result, goto_symex_statet &current_state)
static std::size_t bounded_expr_size(const exprt &expr, std::size_t count, std::size_t limit)
Amount of nodes expr contains, with a bound on how far to search.
Symbolic Execution.
complexity_violationt
What sort of symex-complexity violation has taken place.
Symbolic Execution.
double floor(double x)
Definition: math.c:1451
double log(double x)
Definition: math.c:2776
Options.
std::string to_string(const string_not_contains_constraintt &expr)
Used for debug printing.
goto_programt::const_targett pc
Definition: symex_target.h:42