Skip to content

Commit 30beeda

Browse files
martinmartin
authored andcommitted
Replace locations with history abstractions in the abstract interpreter
This is a fundamental change in how the abstract intepreter works. Previously it's working unit was a location. Transforms were between locations and the most precise unit for domain storage was per location. History objects are abstractions that represent one or more control flow path histories. A single location is the most coarse grains of these but they can include calling contexts, paths taken, loop iterations thread changes, etc. This change should be backwards compatible but also allow more sophisticated histories, giving loop unwinding, context sensitivity, path sensitivity, thread awareness and many others.
1 parent a5b390d commit 30beeda

File tree

7 files changed

+603
-97
lines changed

7 files changed

+603
-97
lines changed

src/analyses/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
SRC = ai.cpp \
22
ai_domain.cpp \
3+
ai_history.cpp \
34
call_graph.cpp \
45
call_graph_helpers.cpp \
56
constant_propagator.cpp \

src/analyses/ai.cpp

Lines changed: 83 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Author: Daniel Kroening, [email protected]
1414
#include <cassert>
1515
#include <memory>
1616
#include <sstream>
17+
#include <type_traits>
1718

1819
#include <util/invariant.h>
1920
#include <util/std_code.h>
@@ -170,7 +171,8 @@ void ai_baset::entry_state(const goto_functionst &goto_functions)
170171
void ai_baset::entry_state(const goto_programt &goto_program)
171172
{
172173
// The first instruction of 'goto_program' is the entry point
173-
get_state(goto_program.instructions.begin()).make_entry();
174+
trace_ptrt p = history_factory->epoch(goto_program.instructions.begin());
175+
get_state(p).make_entry();
174176
}
175177

176178
void ai_baset::initialize(
@@ -197,16 +199,22 @@ void ai_baset::finalize()
197199
// Nothing to do per default
198200
}
199201

200-
ai_baset::locationt ai_baset::get_next(
201-
working_sett &working_set)
202+
ai_baset::trace_ptrt ai_baset::get_next(working_sett &working_set)
202203
{
203204
PRECONDITION(!working_set.empty());
204205

205-
working_sett::iterator i=working_set.begin();
206-
locationt l=i->second;
207-
working_set.erase(i);
206+
static_assert(
207+
std::is_same<
208+
working_sett,
209+
std::set<trace_ptrt, ai_history_baset::compare_historyt>>::value,
210+
"begin must return the minimal entry");
211+
auto first = working_set.begin();
212+
213+
trace_ptrt t = *first;
214+
215+
working_set.erase(first);
208216

209-
return l;
217+
return t;
210218
}
211219

212220
bool ai_baset::fixedpoint(
@@ -219,18 +227,19 @@ bool ai_baset::fixedpoint(
219227

220228
// Put the first location in the working set
221229
if(!goto_program.empty())
230+
{
222231
put_in_working_set(
223-
working_set,
224-
goto_program.instructions.begin());
232+
working_set, history_factory->bang(goto_program.instructions.begin()));
233+
}
225234

226235
bool new_data=false;
227236

228237
while(!working_set.empty())
229238
{
230-
locationt l=get_next(working_set);
239+
trace_ptrt p = get_next(working_set);
231240

232241
// goto_program is really only needed for iterator manipulation
233-
if(visit(function_id, l, working_set, goto_program, goto_functions, ns))
242+
if(visit(function_id, p, working_set, goto_program, goto_functions, ns))
234243
new_data=true;
235244
}
236245

@@ -239,13 +248,14 @@ bool ai_baset::fixedpoint(
239248

240249
bool ai_baset::visit(
241250
const irep_idt &function_id,
242-
locationt l,
251+
trace_ptrt p,
243252
working_sett &working_set,
244253
const goto_programt &goto_program,
245254
const goto_functionst &goto_functions,
246255
const namespacet &ns)
247256
{
248257
bool new_data=false;
258+
locationt l = p->current_location();
249259

250260
// Function call and end are special cases
251261
if(l->is_function_call())
@@ -259,7 +269,7 @@ bool ai_baset::visit(
259269
"function call successor / return location must be the next instruction");
260270

261271
new_data = visit_function_call(
262-
function_id, l, working_set, goto_program, goto_functions, ns);
272+
function_id, p, working_set, goto_program, goto_functions, ns);
263273
}
264274
else if(l->is_end_function())
265275
{
@@ -268,7 +278,7 @@ bool ai_baset::visit(
268278
"The end function instruction should have no successors.");
269279

270280
new_data = visit_end_function(
271-
function_id, l, working_set, goto_program, goto_functions, ns);
281+
function_id, p, working_set, goto_program, goto_functions, ns);
272282
}
273283
else
274284
{
@@ -280,7 +290,7 @@ bool ai_baset::visit(
280290
continue;
281291

282292
new_data |=
283-
visit_edge(function_id, l, function_id, to_l, ns, working_set);
293+
visit_edge(function_id, p, function_id, to_l, ns, working_set);
284294
}
285295
}
286296

@@ -289,27 +299,36 @@ bool ai_baset::visit(
289299

290300
bool ai_baset::visit_edge(
291301
const irep_idt &function_id,
292-
locationt l,
302+
trace_ptrt p,
293303
const irep_idt &to_function_id,
294-
const locationt &to_l,
304+
locationt to_l,
295305
const namespacet &ns,
296306
working_sett &working_set)
297307
{
308+
// Has history taught us not to step here...
309+
auto next = p->step(to_l, *(storage->abstract_traces_before(to_l)));
310+
if(next.first == ai_history_baset::step_statust::BLOCKED)
311+
return false;
312+
trace_ptrt to_p = next.second;
313+
298314
// Abstract domains are mutable so we must copy before we transform
299-
statet &current = get_state(l);
315+
statet &current = get_state(p);
300316

301317
std::unique_ptr<statet> tmp_state(make_temporary_state(current));
302318
statet &new_values = *tmp_state;
303319

304320
// Apply transformer
305-
new_values.transform(function_id, l, to_function_id, to_l, *this, ns);
306-
307-
// Initialize state(s), if necessary
308-
get_state(to_l);
309-
310-
if(merge(new_values, l, to_l))
321+
new_values.transform(function_id, p, to_function_id, to_p, *this, ns);
322+
323+
// Expanding a domain means that it has to be analysed again
324+
// Likewise if the history insists that it is a new trace
325+
// (assuming it is actually reachable).
326+
if(
327+
merge(new_values, p, to_p) ||
328+
(next.first == ai_history_baset::step_statust::NEW &&
329+
!new_values.is_bottom()))
311330
{
312-
put_in_working_set(working_set, to_l);
331+
put_in_working_set(working_set, to_p);
313332
return true;
314333
}
315334

@@ -318,7 +337,7 @@ bool ai_baset::visit_edge(
318337

319338
bool ai_baset::visit_edge_function_call(
320339
const irep_idt &calling_function_id,
321-
locationt l_call,
340+
trace_ptrt p_call,
322341
locationt l_return,
323342
const irep_idt &,
324343
working_sett &working_set,
@@ -330,7 +349,7 @@ bool ai_baset::visit_edge_function_call(
330349
// so the effects of the call are approximated but nothing else
331350
return visit_edge(
332351
calling_function_id,
333-
l_call,
352+
p_call,
334353
calling_function_id,
335354
l_return,
336355
ns,
@@ -339,12 +358,14 @@ bool ai_baset::visit_edge_function_call(
339358

340359
bool ai_baset::visit_function_call(
341360
const irep_idt &calling_function_id,
342-
locationt l_call,
361+
trace_ptrt p_call,
343362
working_sett &working_set,
344363
const goto_programt &caller,
345364
const goto_functionst &goto_functions,
346365
const namespacet &ns)
347366
{
367+
locationt l_call = p_call->current_location();
368+
348369
PRECONDITION(l_call->is_function_call());
349370

350371
locationt l_return = std::next(l_call);
@@ -374,7 +395,7 @@ bool ai_baset::visit_function_call(
374395
{
375396
return visit_edge_function_call(
376397
calling_function_id,
377-
l_call,
398+
p_call,
378399
l_return,
379400
callee_function_id,
380401
working_set,
@@ -401,7 +422,7 @@ bool ai_baset::visit_function_call(
401422
// in the caller. Domains should over-approximate what the function might do.
402423
return visit_edge(
403424
calling_function_id,
404-
l_call,
425+
p_call,
405426
calling_function_id,
406427
l_return,
407428
ns,
@@ -410,12 +431,13 @@ bool ai_baset::visit_function_call(
410431

411432
bool ai_baset::visit_end_function(
412433
const irep_idt &function_id,
413-
locationt l,
434+
trace_ptrt p,
414435
working_sett &working_set,
415436
const goto_programt &goto_program,
416437
const goto_functionst &goto_functions,
417438
const namespacet &ns)
418439
{
440+
locationt l = p->current_location();
419441
PRECONDITION(l->is_end_function());
420442

421443
// Do nothing
@@ -435,7 +457,7 @@ void ai_baset::fixedpoint(
435457

436458
bool ai_recursive_interproceduralt::visit_edge_function_call(
437459
const irep_idt &calling_function_id,
438-
locationt l_call,
460+
trace_ptrt p_call,
439461
locationt l_return,
440462
const irep_idt &callee_function_id,
441463
working_sett &working_set,
@@ -452,7 +474,7 @@ bool ai_recursive_interproceduralt::visit_edge_function_call(
452474
// Do the edge from the call site to the beginning of the function
453475
bool new_data = visit_edge(
454476
calling_function_id,
455-
l_call,
477+
p_call,
456478
callee_function_id,
457479
l_begin,
458480
ns,
@@ -464,26 +486,42 @@ bool ai_recursive_interproceduralt::visit_edge_function_call(
464486
}
465487

466488
// This is the edge from function end to return site.
467-
468489
{
469490
// get location at end of the procedure we have called
470491
locationt l_end = --callee.instructions.end();
471492
DATA_INVARIANT(
472493
l_end->is_end_function(),
473494
"The last instruction of a goto_program must be END_FUNCTION");
474495

475-
// do edge from end of function to instruction after call
476-
const statet &end_state = get_state(l_end);
496+
// Construct a history from a location
497+
auto traces = storage->abstract_traces_before(l_end);
477498

478-
if(end_state.is_bottom())
479-
return false; // function exit point not reachable
499+
bool new_data = false;
480500

481-
return visit_edge(
482-
callee_function_id,
483-
l_end,
484-
calling_function_id,
485-
l_return,
486-
ns,
487-
working_set);
501+
// The history used may mean there are multiple histories at the end of the
502+
// function. Some of these can be progressed (for example, if the history
503+
// tracks paths within functions), some can't be (for example, not all
504+
// calling contexts will be appropriate). We rely on the history's step,
505+
// called from visit_edge to prune as applicable.
506+
for(auto p_end : *traces)
507+
{
508+
// do edge from end of function to instruction after call
509+
const statet &end_state = get_state(p_end);
510+
511+
if(!end_state.is_bottom())
512+
{
513+
// function exit point reachable in that history
514+
515+
new_data |= visit_edge(
516+
callee_function_id,
517+
p_end,
518+
calling_function_id,
519+
l_return,
520+
ns,
521+
working_set);
522+
}
523+
}
524+
525+
return new_data;
488526
}
489527
}

0 commit comments

Comments
 (0)