diff --git a/kani-driver/src/call_cbmc.rs b/kani-driver/src/call_cbmc.rs index dc4ae72b2db..d1cc218c3f2 100644 --- a/kani-driver/src/call_cbmc.rs +++ b/kani-driver/src/call_cbmc.rs @@ -37,10 +37,14 @@ pub struct CbmcInfo { #[derive(Debug, Clone, Default)] pub struct CbmcStats { pub runtime_symex_s: Option, - pub size_program_expression: Option, - pub slicing_removed_assignments: Option, - pub vccs_generated: Option, - pub vccs_remaining: Option, + // `u64`, not `u32`: these are unbounded counts scraped from CBMC's own text output (program + // expression size, VCCs, sliced assignments), and a sufficiently large real run overflowing + // `u32` used to collapse silently to `null` via `parse::().ok()` -- indistinguishable + // from "not measured". + pub size_program_expression: Option, + pub slicing_removed_assignments: Option, + pub vccs_generated: Option, + pub vccs_remaining: Option, pub runtime_postprocess_equation_s: Option, pub runtime_convert_ssa_s: Option, pub runtime_post_process_s: Option, @@ -107,6 +111,12 @@ fn merge_cbmc_stats(items: &[ParserItem]) -> Option { /// Record the statistic a single CBMC status message carries, if it carries one. Later messages win, /// matching CBMC's own behaviour of reporting a running figure more than once. /// Returns whether this message was recognized. +/// +/// Every field assignment below goes through [`record_stat`], which only overwrites a field when +/// parsing succeeds. Without that, a later message that merely *resembles* a recognized label but +/// fails to parse (a wording tweak, an unexpected unit, a truncated line) would silently erase an +/// already-recorded valid measurement by assigning it `None` -- indistinguishable from "never +/// measured" to a consumer of the export. fn record_cbmc_stat(message: &str, stats: &mut CbmcStats) -> bool { // "Generated 1 VCC(s), 1 remaining after simplification" if let Some(counts) = message @@ -114,9 +124,9 @@ fn record_cbmc_stat(message: &str, stats: &mut CbmcStats) -> bool { .and_then(|rest| rest.strip_suffix(" remaining after simplification")) && let Some((generated, remaining)) = counts.split_once(" VCC(s), ") { - stats.vccs_generated = generated.parse().ok(); - stats.vccs_remaining = remaining.parse().ok(); - return stats.vccs_generated.is_some() || stats.vccs_remaining.is_some(); + let generated_ok = record_stat(&mut stats.vccs_generated, parse_leading_number(generated)); + let remaining_ok = record_stat(&mut stats.vccs_remaining, parse_leading_number(remaining)); + return generated_ok || remaining_ok; } // "slicing removed 81 assignments", or "simple slicing removed 5 assignments" when only the @@ -126,8 +136,7 @@ fn record_cbmc_stat(message: &str, stats: &mut CbmcStats) -> bool { .strip_prefix("slicing removed ") .or_else(|| rest.strip_prefix("simple slicing removed ")) { - stats.slicing_removed_assignments = count.parse().ok(); - return stats.slicing_removed_assignments.is_some(); + return record_stat(&mut stats.slicing_removed_assignments, parse_leading_number(count)); } // Everything else is reported as "