Skip to content

Commit 54eee35

Browse files
committed
Add test for ait framework
1 parent dbf4384 commit 54eee35

File tree

2 files changed

+311
-0
lines changed

2 files changed

+311
-0
lines changed

unit/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ SRC = unit_tests.cpp \
77

88
# Test source files
99
SRC += unit_tests.cpp \
10+
analyses/ai/ai.cpp \
1011
analyses/ai/ai_simplify_lhs.cpp \
1112
analyses/call_graph.cpp \
1213
analyses/constant_propagator.cpp \

unit/analyses/ai/ai.cpp

Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
1+
/*******************************************************************\
2+
3+
Module: Unit tests for selectively retaining particular domains in ait
4+
5+
Author: Diffblue Ltd.
6+
7+
\*******************************************************************/
8+
9+
/// \file
10+
/// Unit tests for selectively retaining particular domains in ait
11+
12+
#include <testing-utils/catch.hpp>
13+
14+
#include <analyses/ai.h>
15+
16+
#include <ansi-c/ansi_c_language.h>
17+
18+
#include <goto-programs/goto_convert_functions.h>
19+
20+
#include <langapi/mode.h>
21+
22+
#include <util/arith_tools.h>
23+
#include <util/config.h>
24+
#include <util/c_types.h>
25+
26+
/// A very simple analysis that counts executed instructions along a particular
27+
/// path, taking the max at merge points and saturating at 100 instructions.
28+
/// It should indicate that instructions not within a loop have a certain path
29+
/// length, and those reachable from a loop have a path length of 100
30+
/// (i.e. potentially infinity)
31+
class instruction_counter_domaint : public ai_domain_baset
32+
{
33+
public:
34+
35+
std::unique_ptr<int> path_length;
36+
37+
instruction_counter_domaint()
38+
{
39+
}
40+
41+
instruction_counter_domaint(const instruction_counter_domaint &other)
42+
{
43+
if(other.path_length)
44+
path_length.reset(new int(*other.path_length));
45+
}
46+
47+
void transform(locationt, locationt, ai_baset &, const namespacet &) override
48+
{
49+
if(*path_length < 100)
50+
++*path_length;
51+
}
52+
53+
void make_bottom() override
54+
{
55+
path_length.reset();
56+
}
57+
void make_top() override
58+
{
59+
UNREACHABLE;
60+
}
61+
void make_entry() override
62+
{
63+
path_length.reset(new int);
64+
*path_length = 0;
65+
}
66+
bool is_bottom() const override
67+
{
68+
return path_length == nullptr;
69+
}
70+
bool is_top() const override
71+
{
72+
UNREACHABLE;
73+
return true;
74+
}
75+
76+
bool merge(const instruction_counter_domaint &b, locationt from, locationt to)
77+
{
78+
if(b.is_bottom())
79+
return false;
80+
81+
if(!path_length)
82+
{
83+
path_length.reset(new int);
84+
*path_length = 0;
85+
}
86+
87+
int old_count = *path_length;
88+
// Max path length to get here:
89+
*path_length =
90+
std::max(*path_length, *b.path_length);
91+
return *path_length != old_count;
92+
}
93+
};
94+
95+
class instruction_counter_analysist : public ait<instruction_counter_domaint>
96+
{
97+
public:
98+
99+
static bool is_y_assignment(const goto_programt::instructiont &i)
100+
{
101+
if(!i.is_assign())
102+
return false;
103+
const code_assignt &assign = to_code_assign(i.code);
104+
return
105+
assign.lhs().id() == ID_symbol &&
106+
id2string(to_symbol_expr(assign.lhs()).get_identifier()).find('y') !=
107+
std::string::npos;
108+
}
109+
110+
static bool is_y_assignment_location(locationt l)
111+
{
112+
// At assignments to variables with a 'y' in their name, keep the domain.
113+
// Otherwise let ait decide whether to keep it.
114+
return is_y_assignment(*l);
115+
}
116+
};
117+
118+
static code_function_callt make_void_call(const symbol_exprt &function)
119+
{
120+
code_function_callt ret;
121+
ret.function() = function;
122+
return ret;
123+
}
124+
125+
SCENARIO(
126+
"ait general testing", "[core][analyses][ai][general]")
127+
{
128+
// Make a program like:
129+
130+
// __CPROVER_start() { f(); }
131+
//
132+
// f() {
133+
// int x = 10;
134+
// int y = 20;
135+
// h();
136+
// while(x != 0) {
137+
// x -= 1;
138+
// y -= 1;
139+
// g();
140+
// }
141+
// g(); // To ensure that this later call doesn't overwrite the earlier
142+
// // calls from within the loop (i.e. the top of 'g' is respected
143+
// // as a merge point)
144+
// }
145+
//
146+
// Called from within loop, so should have a long possible path
147+
// g() { int gy = 0; }
148+
//
149+
// Only called before loop, so should have a short path
150+
// h() { int hy = 0; }
151+
152+
register_language(new_ansi_c_language);
153+
config.ansi_c.set_LP64();
154+
155+
goto_modelt goto_model;
156+
157+
// g:
158+
159+
symbolt gy;
160+
gy.name = "gy";
161+
gy.mode = ID_C;
162+
gy.type = signed_int_type();
163+
164+
symbolt g;
165+
g.name = "g";
166+
g.mode = ID_C;
167+
g.type = code_typet({}, void_typet());
168+
g.value = code_assignt(gy.symbol_expr(), from_integer(0, signed_int_type()));
169+
170+
// h:
171+
172+
symbolt hy;
173+
hy.name = "hy";
174+
hy.mode = ID_C;
175+
hy.type = signed_int_type();
176+
177+
symbolt h;
178+
h.name = "h";
179+
h.mode = ID_C;
180+
h.type = code_typet({}, void_typet());
181+
h.value = code_assignt(hy.symbol_expr(), from_integer(0, signed_int_type()));
182+
183+
goto_model.symbol_table.add(g);
184+
goto_model.symbol_table.add(gy);
185+
goto_model.symbol_table.add(h);
186+
goto_model.symbol_table.add(hy);
187+
188+
// f:
189+
190+
symbolt x;
191+
x.name = "x";
192+
x.mode = ID_C;
193+
x.type = signed_int_type();
194+
195+
symbolt y;
196+
y.name = "y";
197+
y.mode = ID_C;
198+
y.type = signed_int_type();
199+
200+
goto_model.symbol_table.add(x);
201+
goto_model.symbol_table.add(y);
202+
203+
code_blockt f_body;
204+
205+
f_body.copy_to_operands(code_declt(x.symbol_expr()));
206+
f_body.copy_to_operands(code_declt(y.symbol_expr()));
207+
208+
f_body.copy_to_operands(
209+
code_assignt(x.symbol_expr(), from_integer(10, signed_int_type())));
210+
f_body.copy_to_operands(
211+
code_assignt(y.symbol_expr(), from_integer(20, signed_int_type())));
212+
213+
f_body.copy_to_operands(make_void_call(h.symbol_expr()));
214+
215+
code_whilet loop;
216+
217+
loop.cond() =
218+
notequal_exprt(x.symbol_expr(), from_integer(0, signed_int_type()));
219+
220+
code_blockt loop_body;
221+
loop_body.copy_to_operands(
222+
code_assignt(
223+
x.symbol_expr(),
224+
minus_exprt(x.symbol_expr(), from_integer(1, signed_int_type()))));
225+
loop_body.copy_to_operands(
226+
code_assignt(
227+
y.symbol_expr(),
228+
minus_exprt(y.symbol_expr(), from_integer(1, signed_int_type()))));
229+
loop_body.copy_to_operands(make_void_call(g.symbol_expr()));
230+
231+
loop.body() = loop_body;
232+
233+
f_body.move_to_operands(loop);
234+
235+
f_body.copy_to_operands(make_void_call(g.symbol_expr()));
236+
237+
symbolt f;
238+
f.name = "f";
239+
f.mode = ID_C;
240+
f.type = code_typet({}, void_typet());
241+
f.value = f_body;
242+
243+
goto_model.symbol_table.add(f);
244+
245+
// __CPROVER_start:
246+
247+
symbolt start;
248+
start.name = goto_functionst::entry_point();
249+
start.mode = ID_C;
250+
start.type = code_typet({}, void_typet());
251+
start.value = make_void_call(f.symbol_expr());
252+
253+
goto_model.symbol_table.add(start);
254+
255+
null_message_handlert nullout;
256+
goto_convert(goto_model, nullout);
257+
258+
WHEN("The target program is analysed")
259+
{
260+
instruction_counter_analysist example_analysis;
261+
example_analysis(goto_model);
262+
263+
THEN("No state should be bottom")
264+
{
265+
forall_goto_functions(f_it, goto_model.goto_functions)
266+
{
267+
forall_goto_program_instructions(i_it, f_it->second.body)
268+
{
269+
REQUIRE(!example_analysis[i_it].is_bottom());
270+
}
271+
}
272+
}
273+
274+
THEN("The first y-assignment should have a short path; "
275+
"the second should have a long one")
276+
{
277+
const auto &f_instructions =
278+
goto_model.goto_functions.function_map.at("f").body.instructions;
279+
280+
std::vector<goto_programt::const_targett> y_assignments;
281+
for(auto l = f_instructions.begin(); l != f_instructions.end(); ++l)
282+
if(instruction_counter_analysist::is_y_assignment_location(l))
283+
y_assignments.push_back(l);
284+
285+
REQUIRE(y_assignments.size() == 2);
286+
REQUIRE(!example_analysis[y_assignments[0]].is_bottom());
287+
REQUIRE(*(example_analysis[y_assignments[0]].path_length) < 100);
288+
REQUIRE(!example_analysis[y_assignments[1]].is_bottom());
289+
REQUIRE(*(example_analysis[y_assignments[1]].path_length) == 100);
290+
}
291+
292+
THEN("The assignment in function 'g' should have a long path")
293+
{
294+
const auto &g_instructions =
295+
goto_model.goto_functions.function_map.at("g").body.instructions;
296+
REQUIRE(g_instructions.begin()->is_assign());
297+
REQUIRE(!example_analysis[g_instructions.begin()].is_bottom());
298+
REQUIRE(*example_analysis[g_instructions.begin()].path_length == 100);
299+
}
300+
301+
THEN("The assignment in function 'h' should have a short path")
302+
{
303+
const auto &h_instructions =
304+
goto_model.goto_functions.function_map.at("h").body.instructions;
305+
REQUIRE(h_instructions.begin()->is_assign());
306+
REQUIRE(!example_analysis[h_instructions.begin()].is_bottom());
307+
REQUIRE(*example_analysis[h_instructions.begin()].path_length < 100);
308+
}
309+
}
310+
}

0 commit comments

Comments
 (0)