27
27
# But `kani list` runs on this fork, so it can still see it and add it to the total functions under contract.
28
28
# - See #TODOs for known limitations.
29
29
30
+ def str_to_bool (string : str ):
31
+ match string .strip ().lower ():
32
+ case "true" :
33
+ return True
34
+ case "false" :
35
+ return False
36
+ case _:
37
+ print (f"Unexpected to-be-Boolean string { string } " )
38
+ sys .exit (1 )
39
+
40
+
30
41
# Process the results from Kani's std-analysis.sh script for each crate.
31
42
class GenericSTDMetrics ():
32
43
def __init__ (self , results_dir , crate ):
@@ -36,8 +47,11 @@ def __init__(self, results_dir, crate):
36
47
self .safe_abstractions_count = 0
37
48
self .safe_fns_count = 0
38
49
self .unsafe_fns = []
50
+ self .unsafe_fns_with_loop = []
39
51
self .safe_abstractions = []
52
+ self .safe_abstractions_with_loop = []
40
53
self .safe_fns = []
54
+ self .safe_fns_with_loop = []
41
55
42
56
self .read_std_analysis ()
43
57
@@ -55,7 +69,7 @@ def read_overall_counts(self):
55
69
# Read {crate}_scan_functions.csv
56
70
# and return an array of the unsafe functions and the safe abstractions
57
71
def read_scan_functions (self ):
58
- expected_header_start = "name;is_unsafe;has_unsafe_ops"
72
+ expected_header_start = "name;is_unsafe;has_unsafe_ops;has_unsupported_input;has_loop "
59
73
file_path = f"{ self .results_directory } /{ self .crate } _scan_functions.csv"
60
74
61
75
with open (file_path , 'r' ) as f :
@@ -64,25 +78,31 @@ def read_scan_functions(self):
64
78
# The row parsing logic below assumes the column structure in expected_header_start,
65
79
# so assert that is how the header begins before continuing
66
80
header = next (csv_reader )
67
- header_str = ';' .join (header [:3 ])
81
+ header_str = ';' .join (header [:5 ])
68
82
if not header_str .startswith (expected_header_start ):
69
83
print (f"Error: Unexpected CSV header in { file_path } " )
70
84
print (f"Expected header to start with: { expected_header_start } " )
71
85
print (f"Actual header: { header_str } " )
72
86
sys .exit (1 )
73
87
74
88
for row in csv_reader :
75
- if len (row ) >= 3 :
89
+ if len (row ) >= 5 :
76
90
name , is_unsafe , has_unsafe_ops = row [0 ], row [1 ], row [2 ]
91
+ has_unsupported_input , has_loop = row [3 ], row [4 ]
77
92
# An unsafe function is a function for which is_unsafe=true
78
- if is_unsafe . strip () == "true" :
93
+ if str_to_bool ( is_unsafe ) :
79
94
self .unsafe_fns .append (name )
95
+ if str_to_bool (has_loop ):
96
+ self .unsafe_fns_with_loop .append (name )
80
97
else :
81
- assert is_unsafe .strip () == "false" # sanity check against malformed data
82
98
self .safe_fns .append (name )
99
+ if str_to_bool (has_loop ):
100
+ self .safe_fns_with_loop .append (name )
83
101
# A safe abstraction is a safe function with unsafe ops
84
- if has_unsafe_ops . strip () == "true" :
102
+ if str_to_bool ( has_unsafe_ops ) :
85
103
self .safe_abstractions .append (name )
104
+ if str_to_bool (has_loop ):
105
+ self .safe_abstractions_with_loop .append (name )
86
106
87
107
def read_std_analysis (self ):
88
108
self .read_overall_counts ()
@@ -143,9 +163,30 @@ class KaniSTDMetricsOverTime():
143
163
def __init__ (self , metrics_file , crate ):
144
164
self .crate = crate
145
165
self .dates = []
146
- self .unsafe_metrics = ['total_unsafe_fns' , 'unsafe_fns_under_contract' , 'verified_unsafe_fns_under_contract' ]
147
- self .safe_abstr_metrics = ['total_safe_abstractions' , 'safe_abstractions_under_contract' , 'verified_safe_abstractions_under_contract' ]
148
- self .safe_metrics = ['total_safe_fns' , 'safe_fns_under_contract' , 'verified_safe_fns_under_contract' ]
166
+ self .unsafe_metrics = [
167
+ 'total_unsafe_fns' ,
168
+ 'total_unsafe_fns_with_loop' ,
169
+ 'unsafe_fns_under_contract' ,
170
+ 'unsafe_fns_with_loop_under_contract' ,
171
+ 'verified_unsafe_fns_under_contract' ,
172
+ 'verified_unsafe_fns_with_loop_under_contract'
173
+ ]
174
+ self .safe_abstr_metrics = [
175
+ 'total_safe_abstractions' ,
176
+ 'total_safe_abstractions_with_loop'
177
+ 'safe_abstractions_under_contract' ,
178
+ 'safe_abstractions_with_loop_under_contract' ,
179
+ 'verified_safe_abstractions_under_contract' ,
180
+ 'verified_safe_abstractions_with_loop_under_contract'
181
+ ]
182
+ self .safe_metrics = [
183
+ 'total_safe_fns' ,
184
+ 'total_safe_fns_with_loop' ,
185
+ 'safe_fns_under_contract' ,
186
+ 'safe_fns_with_loop_under_contract' ,
187
+ 'verified_safe_fns_under_contract' ,
188
+ 'verified_safe_fns_with_loop_under_contract'
189
+ ]
149
190
# The keys in these dictionaries are unsafe_metrics, safe_abstr_metrics, and safe_metrics, respectively; see update_plot_metrics()
150
191
self .unsafe_plot_data = defaultdict (list )
151
192
self .safe_abstr_plot_data = defaultdict (list )
@@ -193,35 +234,62 @@ def compute_metrics(self, kani_list_filepath, analysis_results_dir):
193
234
print ("Comparing kani-list output to std-analysis.sh output and computing metrics..." )
194
235
195
236
(unsafe_fns_under_contract , verified_unsafe_fns_under_contract ) = (0 , 0 )
237
+ unsafe_fns_with_loop_under_contract = 0
238
+ verified_unsafe_fns_with_loop_under_contract = 0
196
239
(safe_abstractions_under_contract , verified_safe_abstractions_under_contract ) = (0 , 0 )
240
+ safe_abstractions_with_loop_under_contract = 0
241
+ verified_safe_abstractions_with_loop_under_contract = 0
197
242
(safe_fns_under_contract , verified_safe_fns_under_contract ) = (0 , 0 )
243
+ safe_fns_with_loop_under_contract = 0
244
+ verified_safe_fns_with_loop_under_contract = 0
198
245
199
246
for (func_under_contract , has_harnesses ) in kani_data .fns_under_contract :
200
247
if func_under_contract in generic_metrics .unsafe_fns :
248
+ has_loop = int (func_under_contract in
249
+ generic_metrics .unsafe_fns_with_loop )
201
250
unsafe_fns_under_contract += 1
251
+ unsafe_fns_with_loop_under_contract += has_loop
202
252
if has_harnesses :
203
253
verified_unsafe_fns_under_contract += 1
254
+ verified_unsafe_fns_with_loop_under_contract += has_loop
204
255
if func_under_contract in generic_metrics .safe_abstractions :
256
+ has_loop = int (func_under_contract in
257
+ generic_metrics .safe_abstractions_with_loop )
205
258
safe_abstractions_under_contract += 1
259
+ safe_abstractions_with_loop_under_contract += has_loop
206
260
if has_harnesses :
207
261
verified_safe_abstractions_under_contract += 1
262
+ verified_safe_abstractions_with_loop_under_contract += has_loop
208
263
if func_under_contract in generic_metrics .safe_fns :
264
+ has_loop = int (func_under_contract in
265
+ generic_metrics .safe_fns_with_loop )
209
266
safe_fns_under_contract += 1
267
+ safe_fns_with_loop_under_contract += has_loop
210
268
if has_harnesses :
211
269
verified_safe_fns_under_contract += 1
270
+ verified_safe_fns_with_loop_under_contract += has_loop
212
271
213
272
# Keep the keys here in sync with unsafe_metrics, safe_metrics, and safe_abstr_metrics
214
273
data = {
215
274
"date" : self .date ,
216
275
"total_unsafe_fns" : generic_metrics .unsafe_fns_count ,
276
+ "total_unsafe_fns_with_loop" : len (generic_metrics .unsafe_fns_with_loop ),
217
277
"total_safe_abstractions" : generic_metrics .safe_abstractions_count ,
278
+ "total_safe_abstractions_with_loop" : len (generic_metrics .total_safe_abstractions_with_loop )
218
279
"total_safe_fns" : generic_metrics .safe_fns_count ,
280
+ "total_safe_fns_with_loop" : len (generic_metrics .total_safe_fns_with_loop )
219
281
"unsafe_fns_under_contract" : unsafe_fns_under_contract ,
282
+ "unsafe_fns_with_loop_under_contract" : unsafe_fns_with_loop_under_contract ,
220
283
"verified_unsafe_fns_under_contract" : verified_unsafe_fns_under_contract ,
284
+ "verified_unsafe_fns_with_loop_under_contract" : verified_unsafe_fns_with_loop_under_contract ,
221
285
"safe_abstractions_under_contract" : safe_abstractions_under_contract ,
286
+ "safe_abstractions_with_loop_under_contract" : safe_abstractions_with_loop_under_contract ,
222
287
"verified_safe_abstractions_under_contract" : verified_safe_abstractions_under_contract ,
288
+ "verified_safe_abstractions_with_loop_under_contract" : verified_safe_abstractions_with_loop_under_contract ,
223
289
"safe_fns_under_contract" : safe_fns_under_contract ,
290
+ "safe_fns_with_loop_under_contract" : safe_fns_with_loop_under_contract ,
224
291
"verified_safe_fns_under_contract" : verified_safe_fns_under_contract ,
292
+ "verified_safe_fns_with_loop_under_contract" : verified_safe_fns_with_loop_under_contract ,
225
293
"total_functions_under_contract" : kani_data .total_fns_under_contract ,
226
294
}
227
295
@@ -241,7 +309,27 @@ def compute_metrics(self, kani_list_filepath, analysis_results_dir):
241
309
def plot_single (self , data , title , filename , plot_dir ):
242
310
plt .figure (figsize = (14 , 8 ))
243
311
244
- colors = ['#1f77b4' , '#ff7f0e' , '#2ca02c' , '#d62728' , '#946F7bd' , '#8c564b' , '#e377c2' , '#7f7f7f' , '#bcbd22' , '#17becf' ]
312
+ colors = [
313
+ '#1f77b4' , #total_unsafe_fns
314
+ '#941fb4' , #total_unsafe_fns_with_loop
315
+ '#ff7f0e' , #total_safe_abstractions
316
+ '#abff0e' , #total_safe_abstractions_with_loop
317
+ '#2ca02c' , #total_safe_fns
318
+ '#a02c8d' , #total_safe_fns_with_loop
319
+ '#d62728' , #unsafe_fns_under_contract
320
+ '#27d6aa' , #unsafe_fns_with_loop_under_contract
321
+ '#9467bd' , #verified_unsafe_fns_under_contract
322
+ '#67acbd' , #verified_unsafe_fns_with_loop_under_contract
323
+ '#8c564b' , #safe_abstractions_under_contract
324
+ '#8c814b' , #safe_abstractions_with_loop_under_contract
325
+ '#e377c2' , #verified_safe_abstractions_under_contract
326
+ '#a277e3' , #verified_safe_abstractions_with_loop_under_contract
327
+ '#7f7f7f' , #safe_fns_under_contract
328
+ '#9e6767' , #safe_fns_with_loop_under_contract
329
+ '#bcbd22' , #verified_safe_fns_under_contract
330
+ '#49bd22' , #verified_safe_fns_with_loop_under_contract
331
+ '#17becf' #total_functions_under_contract
332
+ ]
245
333
246
334
for i , (metric , values ) in enumerate (data .items ()):
247
335
color = colors [i % len (colors )]
0 commit comments