4
4
from regression .executable_runner import ExecutableRunner
5
5
6
6
7
- class DynamicObject :
8
- """Turn the json for a dynamic object into a python object """
7
+ class Expr :
8
+ """Common code for different exprs """
9
9
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' ]
15
12
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' ]
17
14
else :
18
15
self .type_identifier = None
19
16
20
17
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
+
21
45
class VariableExpectation :
22
46
"""Encapsulate the states that a particular variable can take"""
23
47
24
48
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' ]
28
53
29
54
def check_number_of_values (self , n ):
30
- assert n == len (self .var_state [ 'values' ] )
55
+ assert n == len (self .var_state )
31
56
32
57
def check_contains_null_object (self , expected_number = 1 ):
33
58
matches = self .null_objects
34
59
assert expected_number == len (matches )
35
60
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 ):
37
62
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 ]
38
66
assert expected_number == len (matches )
39
67
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 ]
46
76
assert expected_number == len (matches )
47
77
48
78
@@ -51,9 +81,16 @@ class LvsaExpectation:
51
81
52
82
def __init__ (self , lvsa_stdout , fq_class , fq_function ):
53
83
"""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
55
86
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' ]
57
94
assert len (temp_all_functions ) == 1
58
95
all_functions = temp_all_functions [0 ]['functions' ]
59
96
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):
65
102
66
103
def check_public_static (self , variable_name , fq_class = None ):
67
104
if fq_class is None :
68
- fq_class = self .fq_class
105
+ fq_class = self .fq_class_with_java_prefix
69
106
fq_variable_name = fq_class + '.' + variable_name
70
107
matches = [var_state for var_state in self .state if var_state ['id' ] == fq_variable_name ]
71
108
assert len (matches ) == 1
@@ -80,14 +117,22 @@ def check_local_variable(self, variable_name):
80
117
assert len (matches ) == 1
81
118
return VariableExpectation (matches [0 ])
82
119
120
+ def check_parameter (self , parameter_name ):
121
+ return self .check_local_variable (parameter_name )
122
+
83
123
def check_return_value (self ):
84
124
matches = [var_state for var_state in self .state if
85
125
var_state ['id' ].startswith (self .target_function ) and var_state ['id' ].endswith ('#return_value' )]
86
126
assert len (matches ) == 1
87
127
return VariableExpectation (matches [0 ])
88
128
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 ]
91
136
assert len (matches ) == 1
92
137
return VariableExpectation (matches [0 ])
93
138
@@ -102,6 +147,7 @@ def __init__(self, temp_dir, folder_name):
102
147
self .class_name = 'Test'
103
148
self .class_filename = 'Test.class'
104
149
self .function_name = 'f'
150
+ self .max_input_tree_depth = None
105
151
106
152
def with_test_class_name (self , test_class_name ):
107
153
if test_class_name .endswith ('.class' ):
@@ -116,14 +162,21 @@ def with_test_function(self, test_function_name):
116
162
self .function_name = test_function_name
117
163
return self
118
164
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
+
119
169
def run (self ):
120
170
"""Run LVSA and parse the output before returning it"""
121
171
fq_class_name = '.' .join (['LVSA' , self .folder_name , self .class_name ])
122
172
fq_function_name = fq_class_name + '.' + self .function_name
123
173
executable_path = os .path .join (os .environ ['SECURITY_SCANNER_HOME' ], 'bin' , 'security-analyzer' )
124
174
class_path = os .path .join ('LVSA' , self .folder_name , self .class_filename )
125
175
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 )
127
180
128
181
executable_runner = ExecutableRunner (cmd )
129
182
(stdout , _ , _ ) = executable_runner .run ()
0 commit comments