Skip to content

Commit 8744db7

Browse files
committed
Add test for ait framework
1 parent dbf4384 commit 8744db7

File tree

2 files changed

+298
-0
lines changed

2 files changed

+298
-0
lines changed

unit/Makefile

+1
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

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

0 commit comments

Comments
 (0)