6 use File::Path qw(make_path remove_tree);
9 print "Usage: $0 <source.c> [extra_args...]\n";
13 sub parse_debug_summary {
16 proved => 0, total => 0, timeouts => 0, failures => 0,
17 warnings => [], failed_goals => [], timing => {}
20 open my $fh, '<', $file or return \%results;
22 my $in_goal_section = 0;
23 my $current_goal = '';
25 while (my $line = <$fh>) {
29 if ($line =~ /Proved goals:\s+(\d+)\s+\/\s+(\d+)/) {
30 $results{proved} = $1;
34 if ($line =~ /Timeout:\s+(\d+)/) { $results{timeouts} = $1; }
35 if ($line =~ /Failure:\s+(\d+)/) { $results{failures} = $1; }
36 if ($line =~ /Cache.*found:(\d+)/) { $results{cache_hits} = $1; }
39 if ($line =~ /(Alt-Ergo|Coq|Z3|CVC4)\s+([\d\.]+):\s+(\d+)\s+\(([^)]+)\)/) {
40 $results{timing}{$1} = {
46 if ($line =~ /Qed:\s+(\d+)\s+\(([^)]+)\)/) {
47 $results{timing}{Qed} = {
54 if ($line =~ /Warning: (.+)/) {
55 push @{$results{warnings}}, $1;
58 # Goal failure details
59 if ($line =~ /^Goal (.+):$/) {
64 if ($in_goal_section && $line =~ /Prover (.+) returns (Timeout|Failure)/) {
65 push @{$results{failed_goals}}, {
66 goal => $current_goal,
78 sub format_time_range {
80 return $range unless $range =~ /(\d+)ms-(\d+)ms/;
82 my ($min, $max) = ($1, $2);
83 if ($max < 1000) { return "${min}-${max}ms"; }
84 if ($max < 10000) { return sprintf("%.1f-%.1fs", $min/1000, $max/1000); }
85 return sprintf("%.0f-%.0fs", $min/1000, $max/1000);
89 my ($progname, $results) = @_;
91 print "Verification: $progname\n";
92 print "Goals: $results->{proved}/$results->{total} proved\n";
94 my $success = ($results->{proved} == $results->{total} &&
95 $results->{timeouts} == 0 && $results->{failures} == 0);
98 print "Status: SUCCESS\n";
100 print "Status: FAILURE";
101 print " ($results->{timeouts} timeouts)" if $results->{timeouts};
102 print " ($results->{failures} failures)" if $results->{failures};
106 # Performance summary
107 if (%{$results->{timing}}) {
108 print "\nProver performance:\n";
109 for my $prover (sort keys %{$results->{timing}}) {
110 my $t = $results->{timing}{$prover};
111 my $time_str = format_time_range($t->{time_range});
112 print " $prover: $t->{goals} goals ($time_str)\n";
116 if ($results->{cache_hits}) {
117 my $cache_rate = sprintf("%.0f%%",
118 ($results->{cache_hits} / $results->{total}) * 100);
119 print "Cache: $results->{cache_hits}/$results->{total} hits ($cache_rate)\n";
123 if (@{$results->{warnings}}) {
124 print "\nWarnings:\n";
125 for my $warning (@{$results->{warnings}}) {
130 if (@{$results->{failed_goals}}) {
131 print "\nFailed goals:\n";
132 for my $goal (@{$results->{failed_goals}}) {
133 print " $goal->{goal}: $goal->{result} ($goal->{prover})\n";
136 # Show common failure patterns
138 for my $goal (@{$results->{failed_goals}}) {
139 my $name = $goal->{goal};
140 if ($name =~ /(lemma_\w+)/) { $goal_types{lemmas}++; }
141 elsif ($name =~ /loop/) { $goal_types{loops}++; }
142 elsif ($name =~ /postcondition/) { $goal_types{postconditions}++; }
143 else { $goal_types{other}++; }
147 print "\nFailure breakdown:";
148 for my $type (sort keys %goal_types) {
149 print " $goal_types{$type} $type";
156 print " Details: build/$progname/results/frama.log\n";
157 print " JSON: build/$progname/results/results.json\n";
158 print " Proofs: proof/$progname/interactive/*.v\n";
164 @ARGV >= 1 or usage();
166 my $source_file = shift @ARGV;
167 my $provers = 'alt-ergo,z3,coq'; # Let Frama-C handle prover fallback internally
168 my @extra_args = @ARGV; # Collect remaining arguments
170 my $progname = basename($source_file, '.c');
171 die "Invalid program name '$progname'\n" if !$progname || $progname =~ /^\.\.?$/;
174 my $build_dir = "build/$progname";
175 my $proof_dir = "proof/$progname";
176 remove_tree($build_dir) if -d $build_dir;
177 make_path("$build_dir/ast", "$build_dir/normalized",
178 "$build_dir/obligations/typed", "$build_dir/results",
181 # Run Frama-C with prover fallback (alt-ergo -> z3 -> coq)
186 '-cpp-extra-args="-I inc"',
187 "-save $build_dir/ast/" . basename($source_file) . ".sav",
189 "-ocode $build_dir/normalized/program.c",
192 "-wp-out $build_dir/obligations",
193 "-wp-session=$proof_dir",
194 "-wp-interactive=update",
195 "-wp-prover $provers",
196 "-wp-report-json $build_dir/results/results.json",
198 @extra_args, # Include extra arguments
200 "> $build_dir/results/frama.log 2>&1"
203 system($cmd) == 0 or die "Frama-C failed. Check $build_dir/results/frama.log\n";
205 # Generate SMT2 files
206 opendir my $dh, "$build_dir/obligations/typed";
207 while (my $file = readdir($dh)) {
208 next unless $file =~ /\.why$/;
209 my $base = basename($file, '.why');
211 open my $in, '<', "$build_dir/obligations/typed/$file" or next;
212 open my $out, '>', "$build_dir/obligations/typed/$base.smt2" or next;
213 <$in>; # Skip first line
214 print $out $_ while <$in>;
215 close $in; close $out;
220 my $results = parse_debug_summary("$build_dir/results/frama.log");
221 my $success = report_results($progname, $results);
223 exit($success ? 0 : 1);