Skip to content

Commit 057ed6b

Browse files
committed
Track loop stats for functions
1 parent 7e724d4 commit 057ed6b

File tree

1 file changed

+98
-10
lines changed

1 file changed

+98
-10
lines changed

scripts/kani-std-analysis/kani_std_analysis.py

Lines changed: 98 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,17 @@
2727
# But `kani list` runs on this fork, so it can still see it and add it to the total functions under contract.
2828
# - See #TODOs for known limitations.
2929

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+
3041
# Process the results from Kani's std-analysis.sh script for each crate.
3142
class GenericSTDMetrics():
3243
def __init__(self, results_dir, crate):
@@ -36,8 +47,11 @@ def __init__(self, results_dir, crate):
3647
self.safe_abstractions_count = 0
3748
self.safe_fns_count = 0
3849
self.unsafe_fns = []
50+
self.unsafe_fns_with_loop = []
3951
self.safe_abstractions = []
52+
self.safe_abstractions_with_loop = []
4053
self.safe_fns = []
54+
self.safe_fns_with_loop = []
4155

4256
self.read_std_analysis()
4357

@@ -55,7 +69,7 @@ def read_overall_counts(self):
5569
# Read {crate}_scan_functions.csv
5670
# and return an array of the unsafe functions and the safe abstractions
5771
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"
5973
file_path = f"{self.results_directory}/{self.crate}_scan_functions.csv"
6074

6175
with open(file_path, 'r') as f:
@@ -64,25 +78,31 @@ def read_scan_functions(self):
6478
# The row parsing logic below assumes the column structure in expected_header_start,
6579
# so assert that is how the header begins before continuing
6680
header = next(csv_reader)
67-
header_str = ';'.join(header[:3])
81+
header_str = ';'.join(header[:5])
6882
if not header_str.startswith(expected_header_start):
6983
print(f"Error: Unexpected CSV header in {file_path}")
7084
print(f"Expected header to start with: {expected_header_start}")
7185
print(f"Actual header: {header_str}")
7286
sys.exit(1)
7387

7488
for row in csv_reader:
75-
if len(row) >= 3:
89+
if len(row) >= 5:
7690
name, is_unsafe, has_unsafe_ops = row[0], row[1], row[2]
91+
has_unsupported_input, has_loop = row[3], row[4]
7792
# An unsafe function is a function for which is_unsafe=true
78-
if is_unsafe.strip() == "true":
93+
if str_to_bool(is_unsafe):
7994
self.unsafe_fns.append(name)
95+
if str_to_bool(has_loop):
96+
self.unsafe_fns_with_loop.append(name)
8097
else:
81-
assert is_unsafe.strip() == "false" # sanity check against malformed data
8298
self.safe_fns.append(name)
99+
if str_to_bool(has_loop):
100+
self.safe_fns_with_loop.append(name)
83101
# 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):
85103
self.safe_abstractions.append(name)
104+
if str_to_bool(has_loop):
105+
self.safe_abstractions_with_loop.append(name)
86106

87107
def read_std_analysis(self):
88108
self.read_overall_counts()
@@ -143,9 +163,30 @@ class KaniSTDMetricsOverTime():
143163
def __init__(self, metrics_file, crate):
144164
self.crate = crate
145165
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+
]
149190
# The keys in these dictionaries are unsafe_metrics, safe_abstr_metrics, and safe_metrics, respectively; see update_plot_metrics()
150191
self.unsafe_plot_data = defaultdict(list)
151192
self.safe_abstr_plot_data = defaultdict(list)
@@ -193,35 +234,62 @@ def compute_metrics(self, kani_list_filepath, analysis_results_dir):
193234
print("Comparing kani-list output to std-analysis.sh output and computing metrics...")
194235

195236
(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
196239
(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
197242
(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
198245

199246
for (func_under_contract, has_harnesses) in kani_data.fns_under_contract:
200247
if func_under_contract in generic_metrics.unsafe_fns:
248+
has_loop = int(func_under_contract in
249+
generic_metrics.unsafe_fns_with_loop)
201250
unsafe_fns_under_contract += 1
251+
unsafe_fns_with_loop_under_contract += has_loop
202252
if has_harnesses:
203253
verified_unsafe_fns_under_contract += 1
254+
verified_unsafe_fns_with_loop_under_contract += has_loop
204255
if func_under_contract in generic_metrics.safe_abstractions:
256+
has_loop = int(func_under_contract in
257+
generic_metrics.safe_abstractions_with_loop)
205258
safe_abstractions_under_contract += 1
259+
safe_abstractions_with_loop_under_contract += has_loop
206260
if has_harnesses:
207261
verified_safe_abstractions_under_contract += 1
262+
verified_safe_abstractions_with_loop_under_contract += has_loop
208263
if func_under_contract in generic_metrics.safe_fns:
264+
has_loop = int(func_under_contract in
265+
generic_metrics.safe_fns_with_loop)
209266
safe_fns_under_contract += 1
267+
safe_fns_with_loop_under_contract += has_loop
210268
if has_harnesses:
211269
verified_safe_fns_under_contract += 1
270+
verified_safe_fns_with_loop_under_contract += has_loop
212271

213272
# Keep the keys here in sync with unsafe_metrics, safe_metrics, and safe_abstr_metrics
214273
data = {
215274
"date": self.date,
216275
"total_unsafe_fns": generic_metrics.unsafe_fns_count,
276+
"total_unsafe_fns_with_loop": len(generic_metrics.unsafe_fns_with_loop),
217277
"total_safe_abstractions": generic_metrics.safe_abstractions_count,
278+
"total_safe_abstractions_with_loop": len(generic_metrics.total_safe_abstractions_with_loop)
218279
"total_safe_fns": generic_metrics.safe_fns_count,
280+
"total_safe_fns_with_loop": len(generic_metrics.total_safe_fns_with_loop)
219281
"unsafe_fns_under_contract": unsafe_fns_under_contract,
282+
"unsafe_fns_with_loop_under_contract": unsafe_fns_with_loop_under_contract,
220283
"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,
221285
"safe_abstractions_under_contract": safe_abstractions_under_contract,
286+
"safe_abstractions_with_loop_under_contract": safe_abstractions_with_loop_under_contract,
222287
"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,
223289
"safe_fns_under_contract": safe_fns_under_contract,
290+
"safe_fns_with_loop_under_contract": safe_fns_with_loop_under_contract,
224291
"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,
225293
"total_functions_under_contract": kani_data.total_fns_under_contract,
226294
}
227295

@@ -241,7 +309,27 @@ def compute_metrics(self, kani_list_filepath, analysis_results_dir):
241309
def plot_single(self, data, title, filename, plot_dir):
242310
plt.figure(figsize=(14, 8))
243311

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+
]
245333

246334
for i, (metric, values) in enumerate(data.items()):
247335
color = colors[i % len(colors)]

0 commit comments

Comments
 (0)