Skip to content

Commit ffd011e

Browse files
author
owen-jones-diffblue
authored
Merge pull request diffblue#244 from diffblue/owen/tests-for-lvsa
LVSA EVS tests
2 parents 97c830e + c64c196 commit ffd011e

File tree

10 files changed

+179
-35
lines changed

10 files changed

+179
-35
lines changed

regression/LVSA/TestArray/test_array.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def test_lvsa_manually_filled_array_1(tmpdir):
2424

2525
output_value_expectation.check_number_of_values(2)
2626
output_value_expectation.check_contains_null_object()
27-
output_value_expectation.check_contains_most_recent_allocation_dynamic_object()
27+
output_value_expectation.check_contains_dynamic_object(is_most_recent_allocation=True)
2828

2929

3030
def test_lvsa_manually_filled_array_2(tmpdir):
@@ -36,8 +36,8 @@ def test_lvsa_manually_filled_array_2(tmpdir):
3636

3737
output_value_expectation.check_number_of_values(3)
3838
output_value_expectation.check_contains_null_object()
39-
output_value_expectation.check_contains_non_recent_allocation_dynamic_object()
40-
output_value_expectation.check_contains_most_recent_allocation_dynamic_object()
39+
output_value_expectation.check_contains_dynamic_object(is_most_recent_allocation=True)
40+
output_value_expectation.check_contains_dynamic_object(is_most_recent_allocation=False)
4141

4242

4343
def test_lvsa_initialize_array(tmpdir):
@@ -49,4 +49,4 @@ def test_lvsa_initialize_array(tmpdir):
4949

5050
output_value_expectation.check_number_of_values(4)
5151
output_value_expectation.check_contains_null_object()
52-
output_value_expectation.check_contains_most_recent_allocation_dynamic_object(3)
52+
output_value_expectation.check_contains_dynamic_object(3, is_most_recent_allocation=True)
475 Bytes
Binary file not shown.

regression/LVSA/TestEVS/Test.class

1.09 KB
Binary file not shown.

regression/LVSA/TestEVS/Test.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package LVSA.TestEVS;
2+
3+
public class Test {
4+
public static Node static_node;
5+
public static Object static_object;
6+
7+
public class Node {
8+
Object left;
9+
Object right;
10+
}
11+
12+
public void write_static_field_to_parameter_field(Node parameter_node) {
13+
parameter_node.left = static_node.left;
14+
}
15+
16+
public void write_parameter_field_to_static_field(Node parameter_node) {
17+
static_node.left = parameter_node.left;
18+
}
19+
20+
public void write_new_class_to_parameter_field(Node parameter_node) {
21+
parameter_node.left = new Object();
22+
}
23+
24+
public void write_new_class_to_static_field() {
25+
static_node.left = new Object();
26+
}
27+
28+
public void modified(Node parameter_node) {
29+
parameter_node.left = new Object();
30+
parameter_node.right = new Object();
31+
static_object = parameter_node.left;
32+
}
33+
}

regression/LVSA/TestEVS/__init__.py

Whitespace-only changes.

regression/LVSA/TestEVS/test_evs.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
from regression.LVSA.lvsa_driver import LvsaDriver
2+
3+
folder_name = 'TestEVS'
4+
5+
6+
def test_write_static_field_to_parameter_field(tmpdir):
7+
lvsa_driver = LvsaDriver(tmpdir, folder_name).with_test_function('write_static_field_to_parameter_field')
8+
lvsa_expectation = lvsa_driver.run()
9+
10+
output_value_expectation = lvsa_expectation.check_external_value_set('Node', '.left')
11+
12+
output_value_expectation.check_number_of_values(2)
13+
output_value_expectation.check_contains_external_value_set(expected_number=2)
14+
15+
16+
def test_write_parameter_field_to_static_field(tmpdir):
17+
lvsa_driver = LvsaDriver(tmpdir, folder_name).with_test_function('write_parameter_field_to_static_field')
18+
lvsa_expectation = lvsa_driver.run()
19+
20+
output_value_expectation = lvsa_expectation.check_external_value_set('Node', '.left')
21+
22+
output_value_expectation.check_number_of_values(2)
23+
output_value_expectation.check_contains_external_value_set(expected_number=2)
24+
25+
26+
def test_write_new_class_to_parameter_field(tmpdir):
27+
lvsa_driver = LvsaDriver(tmpdir, folder_name).with_test_function('write_new_class_to_parameter_field')
28+
lvsa_expectation = lvsa_driver.run()
29+
30+
output_value_expectation = lvsa_expectation.check_external_value_set('Node', '.left')
31+
32+
output_value_expectation.check_contains_dynamic_object()
33+
34+
35+
def test_write_new_class_to_static_field(tmpdir):
36+
lvsa_driver = LvsaDriver(tmpdir, folder_name).with_test_function('write_new_class_to_static_field')
37+
lvsa_expectation = lvsa_driver.run()
38+
39+
output_value_expectation = lvsa_expectation.check_external_value_set('Node', '.left')
40+
41+
output_value_expectation.check_contains_dynamic_object()
42+
43+
44+
def test_modified(tmpdir):
45+
lvsa_driver = LvsaDriver(tmpdir, folder_name).with_test_function('modified')
46+
lvsa_expectation = lvsa_driver.run()
47+
48+
output_value_expectation_1 = lvsa_expectation.check_external_value_set('Node', '.left')
49+
50+
output_value_expectation_1.check_number_of_values(2)
51+
output_value_expectation_1.check_contains_dynamic_object(is_most_recent_allocation=True)
52+
output_value_expectation_1.check_contains_external_value_set(has_been_read=False)
53+
54+
output_value_expectation_2 = lvsa_expectation.check_public_static('static_object')
55+
56+
output_value_expectation_2.check_number_of_values(1)
57+
output_value_expectation_2.check_contains_external_value_set(has_been_read=True)

regression/LVSA/TestRecency/test_recency.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ def test_outputs_most_recent(tmpdir):
1111
output_value_expectation = lvsa_expectation.check_output()
1212

1313
output_value_expectation.check_number_of_values(1)
14-
output_value_expectation.check_contains_most_recent_allocation_dynamic_object()
14+
output_value_expectation.check_contains_dynamic_object(is_most_recent_allocation=True)
1515

1616

1717
def test_outputs_most_recent_or_null(tmpdir):
@@ -22,7 +22,7 @@ def test_outputs_most_recent_or_null(tmpdir):
2222

2323
output_value_expectation.check_number_of_values(2)
2424
output_value_expectation.check_contains_null_object()
25-
output_value_expectation.check_contains_most_recent_allocation_dynamic_object()
25+
output_value_expectation.check_contains_dynamic_object(is_most_recent_allocation=True)
2626

2727

2828
def test_outputs_most_recent_non_recent_or_null(tmpdir):
@@ -33,8 +33,8 @@ def test_outputs_most_recent_non_recent_or_null(tmpdir):
3333

3434
output_value_expectation.check_number_of_values(3)
3535
output_value_expectation.check_contains_null_object()
36-
output_value_expectation.check_contains_most_recent_allocation_dynamic_object()
37-
output_value_expectation.check_contains_non_recent_allocation_dynamic_object()
36+
output_value_expectation.check_contains_dynamic_object(is_most_recent_allocation=True)
37+
output_value_expectation.check_contains_dynamic_object(is_most_recent_allocation=False)
3838

3939

4040
def test_outputs_non_recent_or_null(tmpdir):
@@ -45,7 +45,7 @@ def test_outputs_non_recent_or_null(tmpdir):
4545

4646
output_value_expectation.check_number_of_values(2)
4747
output_value_expectation.check_contains_null_object()
48-
output_value_expectation.check_contains_non_recent_allocation_dynamic_object()
48+
output_value_expectation.check_contains_dynamic_object(is_most_recent_allocation=False)
4949

5050

5151
def test_most_recent_gets_strong_update(tmpdir):

regression/LVSA/lvsa_driver.py

Lines changed: 78 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,45 +4,75 @@
44
from regression.executable_runner import ExecutableRunner
55

66

7-
class DynamicObject:
8-
"""Turn the json for a dynamic object into a python object"""
7+
class Expr:
8+
"""Common code for different exprs"""
99

10-
def __init__(self, dynamic_object_expr):
11-
assert dynamic_object_expr['id'] == 'dynamic_object'
12-
self.instance = dynamic_object_expr['sub'][0]['namedSub']['value']
13-
self.is_most_recent_allocation = dynamic_object_expr['namedSub']['is_most_recent_allocation']['id']
14-
self.type = dynamic_object_expr['namedSub']['type']['id']
10+
def __init__(self, expr):
11+
self.type = expr['namedSub']['type']['id']
1512
if self.type == 'symbol':
16-
self.type_identifier = dynamic_object_expr['namedSub']['type']['namedSub']['identifier']['id']
13+
self.type_identifier = expr['namedSub']['type']['namedSub']['identifier']['id']
1714
else:
1815
self.type_identifier = None
1916

2017

18+
class DynamicObject(Expr):
19+
"""Turn the json for a dynamic object expr into a python object"""
20+
21+
def __init__(self, dynamic_object_expr):
22+
assert dynamic_object_expr['id'] == 'dynamic_object'
23+
Expr.__init__(self, dynamic_object_expr)
24+
self.instance = dynamic_object_expr['sub'][0]['namedSub']['value']
25+
# Expect 'most_recent_allocation' or 'any_allocation'
26+
self.is_most_recent_allocation = \
27+
dynamic_object_expr['namedSub']['is_most_recent_allocation']['id'] == 'most_recent_allocation'
28+
29+
30+
class ExternalValueSet(Expr):
31+
"""Turn the json for an external value set expr into a python object"""
32+
33+
def __init__(self, evs_expr):
34+
assert evs_expr['id'] == 'external_value_set'
35+
Expr.__init__(self, evs_expr)
36+
# Expect '0' or '1', corresponding to whether the field has ever been read (and hence values from the field
37+
# could have been written to the field)
38+
self.has_been_read = evs_expr['namedSub']['modified']['id'] == '1'
39+
# Expect the parameter name for parameters and 'external_objects' for other things, like static fields
40+
self.label = evs_expr['sub'][0]['namedSub']['value']['id']
41+
self.is_external_object = self.label == 'external_objects'
42+
self.access_paths = evs_expr['sub'][1:]
43+
44+
2145
class VariableExpectation:
2246
"""Encapsulate the states that a particular variable can take"""
2347

2448
def __init__(self, var_state):
25-
self.var_state = var_state
26-
self.null_objects = [x for x in self.var_state['values'] if x['id'] == 'NULL-object']
27-
self.dynamic_objects = [DynamicObject(x) for x in self.var_state['values'] if x['id'] == 'dynamic_object']
49+
self.var_state = var_state['values']
50+
self.null_objects = [x for x in self.var_state if x['id'] == 'NULL-object']
51+
self.dynamic_objects = [DynamicObject(x) for x in self.var_state if x['id'] == 'dynamic_object']
52+
self.external_value_sets = [ExternalValueSet(x) for x in self.var_state if x['id'] == 'external_value_set']
2853

2954
def check_number_of_values(self, n):
30-
assert n == len(self.var_state['values'])
55+
assert n == len(self.var_state)
3156

3257
def check_contains_null_object(self, expected_number=1):
3358
matches = self.null_objects
3459
assert expected_number == len(matches)
3560

36-
def check_contains_dynamic_object(self, expected_number=1):
61+
def check_contains_dynamic_object(self, expected_number=1, is_most_recent_allocation=None):
3762
matches = self.dynamic_objects
63+
if is_most_recent_allocation is not None:
64+
# Expect True or False
65+
matches = [x for x in matches if x.is_most_recent_allocation == is_most_recent_allocation]
3866
assert expected_number == len(matches)
3967

40-
def check_contains_most_recent_allocation_dynamic_object(self, expected_number=1):
41-
matches = [x for x in self.dynamic_objects if x.is_most_recent_allocation == 'most_recent_allocation']
42-
assert expected_number == len(matches)
43-
44-
def check_contains_non_recent_allocation_dynamic_object(self, expected_number=1):
45-
matches = [x for x in self.dynamic_objects if x.is_most_recent_allocation == 'any_allocation']
68+
def check_contains_external_value_set(self, expected_number=1, is_external_object=None, has_been_read=None):
69+
matches = self.external_value_sets
70+
if is_external_object is not None:
71+
# Expect True or False
72+
matches = [x for x in matches if x.is_external_object == is_external_object]
73+
if has_been_read is not None:
74+
# Expect True or False
75+
matches = [x for x in matches if x.has_been_read == has_been_read]
4676
assert expected_number == len(matches)
4777

4878

@@ -51,9 +81,16 @@ class LvsaExpectation:
5181

5282
def __init__(self, lvsa_stdout, fq_class, fq_function):
5383
"""Get the state at the end of test_function"""
54-
self.fq_class = 'java::' + fq_class
84+
self.fq_class = fq_class
85+
self.fq_class_with_java_prefix = 'java::' + self.fq_class
5586
self.target_function = 'java::' + fq_function + ':'
56-
temp_all_functions = [x for x in json.loads(lvsa_stdout) if x.get('messageType') == 'LVSA-ALL-FUNCTIONS-DUMP']
87+
try:
88+
json_output = json.loads(lvsa_stdout)
89+
except json.decoder.JSONDecodeError:
90+
print('Error: LVSA output is not json\n\n')
91+
print (lvsa_stdout)
92+
return
93+
temp_all_functions = [x for x in json_output if x.get('messageType') == 'LVSA-ALL-FUNCTIONS-DUMP']
5794
assert len(temp_all_functions) == 1
5895
all_functions = temp_all_functions[0]['functions']
5996
temp_function_data = [all_functions[key] for key in all_functions.keys() if key.startswith(
@@ -65,7 +102,7 @@ def __init__(self, lvsa_stdout, fq_class, fq_function):
65102

66103
def check_public_static(self, variable_name, fq_class=None):
67104
if fq_class is None:
68-
fq_class = self.fq_class
105+
fq_class = self.fq_class_with_java_prefix
69106
fq_variable_name = fq_class + '.' + variable_name
70107
matches = [var_state for var_state in self.state if var_state['id'] == fq_variable_name]
71108
assert len(matches) == 1
@@ -80,14 +117,22 @@ def check_local_variable(self, variable_name):
80117
assert len(matches) == 1
81118
return VariableExpectation(matches[0])
82119

120+
def check_parameter(self, parameter_name):
121+
return self.check_local_variable(parameter_name)
122+
83123
def check_return_value(self):
84124
matches = [var_state for var_state in self.state if
85125
var_state['id'].startswith(self.target_function) and var_state['id'].endswith('#return_value')]
86126
assert len(matches) == 1
87127
return VariableExpectation(matches[0])
88128

89-
def check_value_set(self, variable_name):
90-
matches = [var_state for var_state in self.state if var_state['id'].startswith('value_set::')]
129+
def check_external_value_set(self, inner_class=None, suffix=None):
130+
target_id = 'external_objects_' + self.fq_class.replace('.', '_')
131+
if inner_class is not None:
132+
target_id += '$' + inner_class
133+
matches = [var_state for var_state in self.state if var_state['id'].startswith(target_id)]
134+
if suffix is not None:
135+
matches = [var_state for var_state in matches if var_state['suffix'] == suffix]
91136
assert len(matches) == 1
92137
return VariableExpectation(matches[0])
93138

@@ -102,6 +147,7 @@ def __init__(self, temp_dir, folder_name):
102147
self.class_name = 'Test'
103148
self.class_filename = 'Test.class'
104149
self.function_name = 'f'
150+
self.max_input_tree_depth = None
105151

106152
def with_test_class_name(self, test_class_name):
107153
if test_class_name.endswith('.class'):
@@ -116,14 +162,21 @@ def with_test_function(self, test_function_name):
116162
self.function_name = test_function_name
117163
return self
118164

165+
def with_max_input_tree_depth(self, max_input_tree_depth):
166+
self.max_input_tree_depth = max_input_tree_depth
167+
return self
168+
119169
def run(self):
120170
"""Run LVSA and parse the output before returning it"""
121171
fq_class_name = '.'.join(['LVSA', self.folder_name, self.class_name])
122172
fq_function_name = fq_class_name + '.' + self.function_name
123173
executable_path = os.path.join(os.environ['SECURITY_SCANNER_HOME'], 'bin', 'security-analyzer')
124174
class_path = os.path.join('LVSA', self.folder_name, self.class_filename)
125175
cmd = [executable_path, '--local-value-set-analysis', '--lvsa-summary-directory', self.temp_dir_path,
126-
'--function', fq_function_name, '--show-value-sets', '--json-ui', class_path]
176+
'--function', fq_function_name, '--show-value-sets', '--json-ui']
177+
if self.max_input_tree_depth is not None:
178+
cmd += ['--java-max-input-tree-depth', str(self.max_input_tree_depth)]
179+
cmd.append(class_path)
127180

128181
executable_runner = ExecutableRunner(cmd)
129182
(stdout, _, _) = executable_runner.run()

src/driver/sec_driver_parse_options.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,7 @@ void sec_driver_parse_optionst::help()
450450
"Java Bytecode frontend options:\n"
451451
" --classpath dir/jar set the classpath\n"
452452
" --main-class class-name set the name of the main class\n"
453+
JAVA_BYTECODE_LANGUAGE_OPTIONS_HELP
453454
"\n"
454455
"Program representations:\n"
455456
" --show-parse-tree show parse tree\n"

src/driver/sec_driver_parse_options.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ class optionst;
4040
"(local-value-set-analysis)(show-value-sets)(lvsa-function):" \
4141
"(security-scanner):" \
4242
"(rebuild-taint-cache)" \
43-
"(lazy-methods)" \
4443
UI_MESSAGE_OPTIONS \
44+
JAVA_BYTECODE_LANGUAGE_OPTIONS \
4545
// End of options
4646

4747

0 commit comments

Comments
 (0)