Skip to content

Commit 848ff4b

Browse files
authored
Merge pull request diffblue#427 from diffblue/smowton/feature/lvsa-avoid-storing-all-domains
LVSA: only store selected domains
2 parents 17407ca + be31c46 commit 848ff4b

12 files changed

+377
-100
lines changed

cbmc/src/analyses/static_analysis.cpp

+54-28
Original file line numberDiff line numberDiff line change
@@ -221,28 +221,6 @@ bool static_analysis_baset::fixedpoint(
221221
return new_data;
222222
}
223223

224-
bool static_analysis_baset::fixedpoint(
225-
const goto_programt &goto_program,
226-
const goto_functionst &goto_functions,
227-
working_sett &working_set)
228-
{
229-
if(goto_program.instructions.empty())
230-
return false;
231-
232-
bool new_data = false;
233-
234-
while(!working_set.empty())
235-
{
236-
++nsteps;
237-
locationt l = get_next(working_set);
238-
239-
if(visit(l, working_set, goto_program, goto_functions))
240-
new_data = true;
241-
}
242-
243-
return new_data;
244-
}
245-
246224
bool static_analysis_baset::visit(
247225
locationt l,
248226
working_sett &working_set,
@@ -254,23 +232,46 @@ bool static_analysis_baset::visit(
254232
statet &current=get_state(l);
255233

256234
current.seen=true;
235+
bool must_retain_current_state = must_retain_state(l);
257236

258-
for(const auto &to_l : goto_program.get_successors(l))
237+
const auto &successors = goto_program.get_successors(l);
238+
for(const auto &to_l : successors)
259239
{
260240
if(to_l==goto_program.instructions.end())
261241
continue;
262242

263-
std::unique_ptr<statet> tmp_state(
264-
make_temporary_state(current));
243+
std::unique_ptr<statet> tmp_state;
244+
245+
// Note this condition is stricter than `!must_retain_current_state` because
246+
// when we have multiple successors `transform_or_apply_function_call` may
247+
// do something different for different successors, in which case we must
248+
// use a clean copy of the pre-existing state each time.
249+
bool may_work_in_place =
250+
successors.size() == 1 && !must_retain_current_state;
265251

266-
statet &new_values=*tmp_state;
252+
if(!may_work_in_place)
253+
tmp_state = make_temporary_state(current);
254+
255+
statet &new_values =
256+
may_work_in_place ?
257+
current :
258+
*tmp_state;
267259

268260
transform_or_apply_function_call(l, to_l, goto_functions, new_values);
269261

270262
statet &other=get_state(to_l);
271263

272-
bool have_new_values=
273-
merge(other, new_values, to_l);
264+
bool have_new_values;
265+
266+
if(other.is_empty())
267+
{
268+
move(other, std::move(new_values));
269+
have_new_values = !other.is_empty();
270+
}
271+
else
272+
{
273+
have_new_values = merge(other, new_values, to_l);
274+
}
274275

275276
if(have_new_values)
276277
new_data=true;
@@ -279,6 +280,13 @@ bool static_analysis_baset::visit(
279280
put_in_working_set(working_set, to_l);
280281
}
281282

283+
if(!must_retain_current_state)
284+
{
285+
// If this state isn't needed any longer, destroy it now:
286+
current.initialize(ns, l);
287+
current.seen = false;
288+
}
289+
282290
return new_data;
283291
}
284292

@@ -573,3 +581,21 @@ void static_analysis_baset::transform_or_apply_function_call(
573581
else
574582
new_values.transform(ns, l, to_l);
575583
}
584+
585+
bool static_analysis_baset::must_retain_state(locationt l)
586+
{
587+
// If the derived class doesn't specify otherwise, assume the old default
588+
// behaviour: always retain state.
589+
if(!must_retain_state_callback)
590+
return true;
591+
592+
// Regardless, always keep states with multiple predecessors, which is
593+
// required for termination when loops are present, and saves redundant
594+
// re-investigation of successors for other kinds of convergence.
595+
if(l->incoming_edges.size() > 1)
596+
return true;
597+
598+
// Finally, retain states where the particular subclass specifies they
599+
// should be kept:
600+
return must_retain_state_callback(l);
601+
}

cbmc/src/analyses/static_analysis.h

+22-8
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ class domain_baset
4949
{
5050
}
5151

52+
virtual bool is_empty() const = 0;
53+
5254
// how function calls are treated:
5355
// a) there is an edge from each call site to the function head
5456
// b) there is an edge from each return to the last instruction (END_FUNCTION)
@@ -109,10 +111,13 @@ class static_analysis_baset
109111
typedef domain_baset statet;
110112
typedef goto_programt::const_targett locationt;
111113

112-
explicit static_analysis_baset(const namespacet &_ns):
114+
explicit static_analysis_baset(
115+
const namespacet &_ns,
116+
std::function<bool(locationt)> must_retain_state_callback) :
113117
nsteps(0),
114118
ns(_ns),
115-
initialized(false)
119+
initialized(false),
120+
must_retain_state_callback(must_retain_state_callback)
116121
{
117122
ignore_recursion=get_ignore_recursion();
118123
}
@@ -217,10 +222,6 @@ class static_analysis_baset
217222
bool fixedpoint(
218223
const goto_programt &goto_program,
219224
const goto_functionst &goto_functions);
220-
bool fixedpoint(
221-
const goto_programt &goto_program,
222-
const goto_functionst &goto_functions,
223-
working_sett &working_set);
224225

225226
virtual void fixedpoint(
226227
const goto_functionst &goto_functions)=0;
@@ -253,6 +254,8 @@ class static_analysis_baset
253254
// for concurrent fixedpoint
254255
virtual bool merge_shared(statet &a, const statet &b, locationt to)=0;
255256

257+
virtual void move(statet &a, statet &&b)=0;
258+
256259
typedef std::set<irep_idt> functions_donet;
257260
functions_donet functions_done;
258261

@@ -267,6 +270,10 @@ class static_analysis_baset
267270

268271
bool initialized;
269272

273+
std::function<bool(locationt)> must_retain_state_callback;
274+
275+
bool must_retain_state(locationt);
276+
270277
// function calls
271278
void do_function_call_rec(
272279
locationt l_call, locationt l_return,
@@ -314,8 +321,10 @@ class static_analysist:public static_analysis_baset
314321
{
315322
public:
316323
// constructor
317-
explicit static_analysist(const namespacet &_ns):
318-
static_analysis_baset(_ns)
324+
explicit static_analysist(
325+
const namespacet &_ns,
326+
std::function<bool(locationt)> must_retain_state = nullptr) :
327+
static_analysis_baset(_ns, must_retain_state)
319328
{
320329
}
321330

@@ -382,6 +391,11 @@ class static_analysist:public static_analysis_baset
382391
return util_make_unique<T>(static_cast<const T &>(s));
383392
}
384393

394+
virtual void move(statet &a, statet &&b)
395+
{
396+
static_cast<T &>(a).move_state_from(static_cast<T &&>(b));
397+
}
398+
385399
virtual void generate_state(locationt l)
386400
{
387401
state_map[l].initialize(ns, l);

cbmc/src/pointer-analysis/value_set.h

+5
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,11 @@ class value_sett
489489
exprt &expr,
490490
const namespacet &ns) const;
491491

492+
void move_state_from(value_sett &&other)
493+
{
494+
this->values = std::move(other.values);
495+
}
496+
492497
protected:
493498
/// Reads the set of objects pointed to by `expr`, including making
494499
/// recursive lookups for dereference operations etc.

cbmc/src/pointer-analysis/value_set_analysis.h

+4-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,10 @@ class value_set_analysis_templatet:
3636
typedef static_analysist<domaint> baset;
3737
typedef typename baset::locationt locationt;
3838

39-
explicit value_set_analysis_templatet(const namespacet &ns):baset(ns)
39+
explicit value_set_analysis_templatet(
40+
const namespacet &ns,
41+
std::function<bool(locationt)> must_retain_state = nullptr) :
42+
baset(ns, must_retain_state)
4043
{
4144
}
4245

cbmc/src/pointer-analysis/value_set_domain.h

+10
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ class value_set_domain_templatet:public domain_baset
5151
value_set.function=l->function;
5252
}
5353

54+
bool is_empty() const override
55+
{
56+
return value_set.values.empty();
57+
}
58+
5459
void transform(
5560
const namespacet &ns,
5661
locationt from_l,
@@ -63,6 +68,11 @@ class value_set_domain_templatet:public domain_baset
6368
{
6469
value_set.get_reference_set(expr, dest, ns);
6570
}
71+
72+
void move_state_from(value_set_domain_templatet<VST> &&other)
73+
{
74+
value_set.move_state_from(std::move(other.value_set));
75+
}
6676
};
6777

6878
typedef value_set_domain_templatet<value_sett> value_set_domaint;

regression/LVSA/lvsa_driver.py

+95-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import json
22
import os
3+
import copy
34
from enum import Enum
45

56
from regression.executable_runner import ExecutableRunner
@@ -191,6 +192,48 @@ def check_contains_precise_evs(self, expected_number=1, is_initializer=False, ac
191192
def get_dynamic_object_ids(self):
192193
return [dynamic_object.instance['id'] for dynamic_object in self.dynamic_objects]
193194

195+
def report_difference_with_context(state1, state2, context):
196+
if type(state1) != type(state2):
197+
return (context, str(type(state1)), str(type(state2)))
198+
if isinstance(state1, list) or \
199+
isinstance(state1, dict) or \
200+
isinstance(state1, set):
201+
if len(state1) != len(state2):
202+
return(context + ["length"], len(state1), len(state2))
203+
context.append(None)
204+
if isinstance(state1, list):
205+
for (idx, (element1, element2)) in enumerate(zip(state1, state2)):
206+
context[-1] = idx
207+
subrep = \
208+
report_difference_with_context(element1, element2, context)
209+
if subrep is not None:
210+
return subrep
211+
elif isinstance(state1, set):
212+
for element1 in state1:
213+
if element1 not in state2:
214+
return (context, element1, "Element not present in set")
215+
else:
216+
for key in state1:
217+
if key not in state2:
218+
return (context, "Key {} present".format(key), "Key absent")
219+
context[-1] = key
220+
subrep = report_difference_with_context( \
221+
state1[key], state2[key], context)
222+
if subrep is not None:
223+
return subrep
224+
context.pop()
225+
else:
226+
if state1 != state2:
227+
return (context, state1, state2)
228+
return None
229+
230+
def report_first_state_difference(state1, state2, desc1, desc2):
231+
state_difference = report_difference_with_context(state1, state2, [])
232+
if state_difference is not None:
233+
return "In context: {}\n{}: {}\n{}: {}".format(
234+
state_difference[0],
235+
desc1, state_difference[1],
236+
desc2, state_difference[2])
194237

195238
class LvsaExpectation:
196239
"""Encapsulate the output of LvsaDriver"""
@@ -281,6 +324,9 @@ def get_value_set_for_dynamic_object_value_set(self, dynamic_object_id, suffix,
281324
def get_all_value_sets(self):
282325
return [ValueSetExpectation(var_state) for var_state in self.state]
283326

327+
def report_first_difference(self, other, self_desc, other_desc):
328+
return report_first_state_difference(
329+
self.state, other.state, self_desc, other_desc)
284330

285331
class LvsaDriver:
286332
"""Run LVSA"""
@@ -324,4 +370,52 @@ def run(self):
324370
executable_runner = ExecutableRunner(cmd)
325371
(stdout, _, _) = executable_runner.run()
326372

327-
return LvsaExpectation(stdout, fq_class_name, fq_function_name)
373+
result = LvsaExpectation(stdout, fq_class_name, fq_function_name)
374+
375+
# Check that LVSA's selective domain storage is working correctly: also
376+
# run this command in debug mode, which retains domains for all program
377+
# points, and check it has the same results.
378+
379+
debug_cmd = copy.copy(cmd)
380+
debug_cmd.append("--lvsa-show-all-program-points")
381+
382+
debug_runner = ExecutableRunner(debug_cmd)
383+
(debug_stdout, _, _) = debug_runner.run()
384+
385+
debug_result = \
386+
LvsaExpectation(debug_stdout, fq_class_name, fq_function_name)
387+
388+
difference = result.report_first_difference(
389+
debug_result, "Selective domain storage", "Storing all domains")
390+
391+
if difference is not None:
392+
raise Exception(difference)
393+
394+
# Further check that the results with domain reconstruction (when
395+
# program points are rebuilt from those selectively stored) exactly
396+
# match those produced by retaining all domains in the first place:
397+
398+
test_reconstruction_cmd = copy.copy(debug_cmd)
399+
test_reconstruction_cmd.append(
400+
"--lvsa-reconstruct-intermediate-program-points")
401+
402+
test_reconstruction_runner = ExecutableRunner(test_reconstruction_cmd)
403+
(test_reconstruction_stdout, _, _) = test_reconstruction_runner.run()
404+
405+
debug_full_result = json.loads(debug_stdout)
406+
debug_full_result = \
407+
[x for x in debug_full_result if x.get('messageType') == 'LVSA-ALL-FUNCTIONS-DUMP']
408+
test_reconstruction_full_result = json.loads(test_reconstruction_stdout)
409+
test_reconstruction_full_result = \
410+
[x for x in test_reconstruction_full_result if x.get('messageType') == 'LVSA-ALL-FUNCTIONS-DUMP']
411+
412+
reconstruction_difference = report_first_state_difference(
413+
debug_full_result,
414+
test_reconstruction_full_result,
415+
"Conventionally computed domains",
416+
"Reconstructed intermediate domains")
417+
418+
if reconstruction_difference is not None:
419+
raise Exception(reconstruction_difference)
420+
421+
return result

0 commit comments

Comments
 (0)