Skip to content

Commit e669c12

Browse files
committed
Add unit tests for value-set-analysis customisation
1 parent 9465771 commit e669c12

File tree

5 files changed

+348
-16
lines changed

5 files changed

+348
-16
lines changed

unit/CustomVSATest.class

-890 Bytes
Binary file not shown.

unit/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ SRC += unit_tests.cpp \
2020
miniBDD_new.cpp \
2121
java_bytecode/java_string_library_preprocess/convert_exprt_to_string_exprt.cpp \
2222
java_bytecode/java_utils_test.cpp \
23+
pointer-analysis/custom_value_set_analysis.cpp \
2324
solvers/refinement/string_constraint_generator_valueof/calculate_max_string_length.cpp \
2425
solvers/refinement/string_constraint_generator_valueof/get_numeric_value_from_character.cpp \
2526
solvers/refinement/string_constraint_generator_valueof/is_digit_with_radix.cpp \
969 Bytes
Binary file not shown.

unit/CustomVSATest.java renamed to unit/pointer-analysis/CustomVSATest.java

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
public class CustomVSATest {
33

44
public Object never_read;
5-
public Object normal_field;
6-
public CustomVSATest unknown_field_ref;
75

8-
public static CustomVSATest unknown_global_ref;
6+
public static Object cause;
7+
public static Object effect;
8+
public static Object first_effect_read;
9+
public static Object second_effect_read;
10+
public static Object maybe_unknown_read;
911

1012
public static void test() {
1113

@@ -30,21 +32,14 @@ public static void test() {
3032
// This should acquire a "*" unknown-object, even though
3133
// it is obvious what it points to:
3234
Object maybe_unknown = new Object();
35+
maybe_unknown_read=maybe_unknown;
3336

34-
// Both of these fields, despite being initialised, should be
35-
// considered to point to some unknown target, and so the reads
36-
// through them return some unknown entity:
37+
effect = new Object();
38+
first_effect_read = effect;
3739

38-
CustomVSATest outer = new CustomVSATest();
39-
outer.unknown_field_ref = new CustomVSATest();
40-
outer.unknown_field_ref.normal_field = new Object();
41-
42-
Object get_unknown_field = outer.unknown_field_ref.normal_field;
43-
44-
unknown_global_ref = new CustomVSATest();
45-
unknown_global_ref.normal_field = new Object();
46-
47-
Object get_unknown_global = unknown_global_ref.normal_field;
40+
// Under our custom VSA, this write should cause effect to become null:
41+
cause = new Object();
42+
second_effect_read = effect;
4843

4944
}
5045

Lines changed: 336 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,336 @@
1+
/*******************************************************************\
2+
3+
Module: Value-set analysis tests
4+
5+
Author: Chris Smowton, [email protected]
6+
7+
\*******************************************************************/
8+
9+
#include <testing-utils/catch.hpp>
10+
11+
#include <util/config.h>
12+
#include <langapi/mode.h>
13+
#include <goto-programs/initialize_goto_model.h>
14+
#include <goto-programs/goto_inline.h>
15+
#include <java_bytecode/java_bytecode_language.h>
16+
#include <java_bytecode/java_types.h>
17+
#include <pointer-analysis/value_set_analysis.h>
18+
19+
/// Counts calls to our custom simplifier, registered below:
20+
int simplifies=0;
21+
22+
/// An example customised value_sett. It makes a series of small changes
23+
/// to the underlying value_sett logic, which can then be verified by the
24+
/// test below:
25+
/// * Writes to variables with 'ignored' in their name are ignored.
26+
/// * Never propagate values via the field "never_read"
27+
/// * Adds an ID_unknown to the value of variable "maybe_unknown" as it is read
28+
/// * When a variable named `cause` is written, one named `effect` is zeroed.
29+
class test_value_sett:public value_sett
30+
{
31+
public:
32+
test_value_sett():
33+
value_sett([this](exprt &e, const namespacet &ns) { simplifies++; })
34+
{
35+
}
36+
37+
static bool assigns_to_ignored_variable(const code_assignt &assign)
38+
{
39+
if(assign.lhs().id()!=ID_symbol)
40+
return false;
41+
const irep_idt &id=to_symbol_expr(assign.lhs()).get_identifier();
42+
return id2string(id).find("ignored")!=std::string::npos;
43+
}
44+
45+
void apply_code_rec(const codet &code, const namespacet &ns) override
46+
{
47+
// Ignore assignments to the local "ignored"
48+
if(code.get_statement()==ID_assign &&
49+
assigns_to_ignored_variable(to_code_assign(code)))
50+
{
51+
return;
52+
}
53+
else
54+
{
55+
value_sett::apply_code_rec(code, ns);
56+
}
57+
}
58+
59+
void assign_rec(
60+
const exprt &lhs,
61+
const object_mapt &values_rhs,
62+
const std::string &suffix,
63+
const namespacet &ns,
64+
bool add_to_sets) override
65+
{
66+
// Disregard writes against variables containing 'no_write':
67+
if(lhs.id()==ID_symbol)
68+
{
69+
const irep_idt &id=to_symbol_expr(lhs).get_identifier();
70+
if(id2string(id).find("no_write")!=std::string::npos)
71+
return;
72+
}
73+
74+
value_sett::assign_rec(lhs, values_rhs, suffix, ns, add_to_sets);
75+
}
76+
77+
void get_value_set_rec(
78+
const exprt &expr,
79+
object_mapt &dest,
80+
const std::string &suffix,
81+
const typet &original_type,
82+
const namespacet &ns) const override
83+
{
84+
// Ignore reads from fields named "never_read"
85+
if(expr.id()==ID_member &&
86+
to_member_expr(expr).get_component_name()=="never_read")
87+
{
88+
return;
89+
}
90+
else
91+
{
92+
value_sett::get_value_set_rec(
93+
expr, dest, suffix, original_type, ns);
94+
}
95+
}
96+
97+
void adjust_assign_rhs_values(
98+
const exprt &expr,
99+
const namespacet &ns,
100+
object_mapt &dest) const override
101+
{
102+
// Always add an ID_unknown to reads from variables containing
103+
// "maybe_unknown":
104+
exprt read_sym=expr;
105+
while(read_sym.id()==ID_typecast)
106+
read_sym=read_sym.op0();
107+
if(read_sym.id()==ID_symbol)
108+
{
109+
const irep_idt &id=to_symbol_expr(read_sym).get_identifier();
110+
if(id2string(id).find("maybe_unknown")!=std::string::npos)
111+
insert(dest, exprt(ID_unknown, read_sym.type()));
112+
}
113+
}
114+
115+
void apply_assign_side_effects(
116+
const exprt &lhs,
117+
const exprt &rhs,
118+
const namespacet &ns) override
119+
{
120+
// Whenever someone touches the variable "cause", null the
121+
// variable "effect":
122+
if(lhs.id()==ID_symbol)
123+
{
124+
const irep_idt &id=to_symbol_expr(lhs).get_identifier();
125+
const auto &id_str=id2string(id);
126+
auto find_idx=id_str.find("cause");
127+
if(find_idx!=std::string::npos)
128+
{
129+
std::string effect_id=id_str;
130+
effect_id.replace(find_idx, 5, "effect");
131+
assign(
132+
symbol_exprt(effect_id, lhs.type()),
133+
null_pointer_exprt(to_pointer_type(lhs.type())),
134+
ns,
135+
true,
136+
false);
137+
}
138+
}
139+
}
140+
};
141+
142+
typedef value_set_analysis_baset<test_value_sett> test_value_set_analysist;
143+
144+
#define TEST_PREFIX "java::CustomVSATest."
145+
#define TEST_FUNCTION_NAME TEST_PREFIX "test:()V"
146+
#define TEST_LOCAL_PREFIX TEST_FUNCTION_NAME "::"
147+
148+
template<class VST>
149+
static value_setst::valuest
150+
get_values(const VST &value_set, const namespacet &ns, const exprt &expr)
151+
{
152+
value_setst::valuest vals;
153+
value_set.read_value_set(expr, vals, ns);
154+
return vals;
155+
}
156+
157+
static std::size_t exprs_with_id(
158+
const value_setst::valuest &exprs, const irep_idt &id)
159+
{
160+
return std::count_if(
161+
exprs.begin(),
162+
exprs.end(),
163+
[&id](const exprt &expr)
164+
{
165+
return expr.id()==id ||
166+
(expr.id()==ID_object_descriptor &&
167+
to_object_descriptor_expr(expr).object().id()==id);
168+
});
169+
}
170+
171+
SCENARIO("test_value_set_analysis",
172+
"[core][pointer-analysis][value_set_analysis]")
173+
{
174+
GIVEN("Normal and custom value-set analysis of CustomVSATest::test")
175+
{
176+
goto_modelt goto_model;
177+
null_message_handlert null_output;
178+
cmdlinet command_line;
179+
180+
// This classpath is the default, but the config object
181+
// is global and previous unit tests may have altered it
182+
command_line.set("java-cp-include-files", ".");
183+
config.java.classpath={"."};
184+
command_line.args.push_back("pointer-analysis/CustomVSATest.jar");
185+
186+
register_language(new_java_bytecode_language);
187+
188+
bool model_init_failed=
189+
initialize_goto_model(goto_model, command_line, null_output);
190+
REQUIRE(!model_init_failed);
191+
192+
namespacet ns(goto_model.symbol_table);
193+
194+
// Fully inline the test program, to avoid VSA conflating
195+
// constructor callsites confusing the results we're trying to check:
196+
goto_function_inline(goto_model, TEST_FUNCTION_NAME, null_output);
197+
198+
const goto_programt &test_function=
199+
goto_model.goto_functions.function_map.at(TEST_PREFIX "test:()V").body;
200+
201+
value_set_analysist::locationt test_function_end=
202+
std::prev(test_function.instructions.end());
203+
204+
value_set_analysist normal_analysis(ns);
205+
normal_analysis(goto_model.goto_functions);
206+
const auto &normal_function_end_vs=
207+
normal_analysis[test_function_end].value_set;
208+
209+
test_value_set_analysist test_analysis(ns);
210+
test_analysis(goto_model.goto_functions);
211+
const auto &test_function_end_vs=
212+
test_analysis[test_function_end].value_set;
213+
214+
reference_typet jlo_ref_type=java_lang_object_type();
215+
216+
WHEN("Writing to a local named 'ignored'")
217+
{
218+
symbol_exprt written_symbol(
219+
TEST_LOCAL_PREFIX "23::ignored", jlo_ref_type);
220+
THEN("The normal analysis should write to it")
221+
{
222+
auto normal_exprs=
223+
get_values(normal_function_end_vs, ns, written_symbol);
224+
REQUIRE(exprs_with_id(normal_exprs, ID_dynamic_object)==1);
225+
REQUIRE(exprs_with_id(normal_exprs, ID_unknown)==0);
226+
}
227+
THEN("The custom analysis should ignore the write to it")
228+
{
229+
auto test_exprs=
230+
get_values(test_function_end_vs, ns, written_symbol);
231+
REQUIRE(exprs_with_id(test_exprs, ID_dynamic_object)==0);
232+
REQUIRE(exprs_with_id(test_exprs, ID_unknown)==1);
233+
}
234+
}
235+
236+
WHEN("Writing to a local named 'no_write'")
237+
{
238+
symbol_exprt written_symbol(
239+
TEST_LOCAL_PREFIX "31::no_write", jlo_ref_type);
240+
THEN("The normal analysis should write to it")
241+
{
242+
auto normal_exprs=
243+
get_values(normal_function_end_vs, ns, written_symbol);
244+
REQUIRE(exprs_with_id(normal_exprs, ID_dynamic_object)==1);
245+
REQUIRE(exprs_with_id(normal_exprs, ID_unknown)==0);
246+
}
247+
THEN("The custom analysis should ignore the write to it")
248+
{
249+
auto test_exprs=
250+
get_values(test_function_end_vs, ns, written_symbol);
251+
REQUIRE(exprs_with_id(test_exprs, ID_dynamic_object)==0);
252+
REQUIRE(exprs_with_id(test_exprs, ID_unknown)==1);
253+
}
254+
}
255+
256+
WHEN("Reading from a field named 'never_read'")
257+
{
258+
symbol_exprt written_symbol(
259+
TEST_LOCAL_PREFIX "55::read", jlo_ref_type);
260+
THEN("The normal analysis should find a dynamic object")
261+
{
262+
auto normal_exprs=
263+
get_values(normal_function_end_vs, ns, written_symbol);
264+
REQUIRE(exprs_with_id(normal_exprs, ID_dynamic_object)==1);
265+
REQUIRE(exprs_with_id(normal_exprs, ID_unknown)==0);
266+
}
267+
THEN("The custom analysis should have no information about it")
268+
{
269+
auto test_exprs=
270+
get_values(test_function_end_vs, ns, written_symbol);
271+
REQUIRE(test_exprs.size()==0);
272+
}
273+
}
274+
275+
WHEN("Reading from a variable named 'maybe_unknown'")
276+
{
277+
symbol_exprt written_symbol(
278+
TEST_PREFIX "maybe_unknown_read", jlo_ref_type);
279+
THEN("The normal analysis should find a dynamic object")
280+
{
281+
auto normal_exprs=
282+
get_values(normal_function_end_vs, ns, written_symbol);
283+
REQUIRE(exprs_with_id(normal_exprs, ID_dynamic_object)==1);
284+
REQUIRE(exprs_with_id(normal_exprs, ID_unknown)==0);
285+
}
286+
THEN("The custom analysis should find a dynamic object "
287+
"*and* an unknown entry")
288+
{
289+
auto test_exprs=
290+
get_values(test_function_end_vs, ns, written_symbol);
291+
REQUIRE(test_exprs.size()==2);
292+
REQUIRE(exprs_with_id(test_exprs, ID_unknown)==1);
293+
REQUIRE(exprs_with_id(test_exprs, ID_dynamic_object)==1);
294+
}
295+
}
296+
297+
WHEN("Writing to a variable named 'cause'")
298+
{
299+
symbol_exprt read_before_cause_write(
300+
TEST_PREFIX "first_effect_read", jlo_ref_type);
301+
auto normal_exprs_before=
302+
get_values(normal_function_end_vs, ns, read_before_cause_write);
303+
auto test_exprs_before=
304+
get_values(test_function_end_vs, ns, read_before_cause_write);
305+
symbol_exprt read_after_cause_write(
306+
TEST_PREFIX "second_effect_read", jlo_ref_type);
307+
auto normal_exprs_after=
308+
get_values(normal_function_end_vs, ns, read_after_cause_write);
309+
auto test_exprs_after=
310+
get_values(test_function_end_vs, ns, read_after_cause_write);
311+
312+
THEN("Before writing to 'cause' both analyses should think 'effect' "
313+
"points to some object")
314+
{
315+
REQUIRE(normal_exprs_before.size()==1);
316+
REQUIRE(exprs_with_id(normal_exprs_before, ID_dynamic_object)==1);
317+
318+
REQUIRE(test_exprs_before.size()==1);
319+
REQUIRE(exprs_with_id(test_exprs_before, ID_dynamic_object)==1);
320+
}
321+
322+
THEN("After writing to 'cause', the normal analysis should see no change "
323+
"to 'effect', while the custom analysis should see it changed")
324+
{
325+
REQUIRE(normal_exprs_after.size()==1);
326+
REQUIRE(exprs_with_id(normal_exprs_after, ID_dynamic_object)==1);
327+
328+
REQUIRE(test_exprs_after.size()==1);
329+
REQUIRE(exprs_with_id(test_exprs_after, "NULL-object")==1);
330+
}
331+
}
332+
333+
// Check that our custom simplifier was invoked during all of the above:
334+
REQUIRE(simplifies>0);
335+
}
336+
}

0 commit comments

Comments
 (0)