Skip to content

Commit 8bcca68

Browse files
committed
Add unit tests for value-set-analysis customisation
1 parent 34dc4a9 commit 8bcca68

File tree

4 files changed

+372
-0
lines changed

4 files changed

+372
-0
lines changed

unit/Makefile

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

0 commit comments

Comments
 (0)