diff --git a/src/plugins/value/engine/compute_functions.ml b/src/plugins/value/engine/compute_functions.ml index 409a3b2a28558415904f514d257a33ea2a6adfbe..13a6e265714a9c2c28b8b8977a32699613169d52 100644 --- a/src/plugins/value/engine/compute_functions.ml +++ b/src/plugins/value/engine/compute_functions.ml @@ -347,6 +347,7 @@ module Make (Abstract: Abstractions.Eva) = struct Value_parameters.feedback "done for function %a" Kernel_function.pretty kf; post_analysis (); Abstract.Dom.post_analysis final_state; + Value_results.print_summary (); with | Db.Value.Aborted -> post_analysis_cleanup ~aborted:true; diff --git a/src/plugins/value/utils/value_results.ml b/src/plugins/value/utils/value_results.ml index c9db33d1cca57a42727963b1c262e568220b01df..08ccbe8ea20d43eb42c6e888db097015d3924ca6 100644 --- a/src/plugins/value/utils/value_results.ml +++ b/src/plugins/value/utils/value_results.ml @@ -355,6 +355,236 @@ let merge r1 r2 = { main; before_states; after_states; kf_initial_states; kf_is_called; initial_state; initial_args; alarms; statuses; kf_callers } +(* ---------------------- Printing an analysis summary ---------------------- *) + +open Cil_types + +let plural count = if count = 1 then "" else "s" + +let consider_function vi = + not (Cil.is_builtin vi + || Cil.is_special_builtin vi.vname + || Cil.hasAttribute "fc_stdlib" vi.vattr + || Cil.hasAttribute "fc_stdlib_generated" vi.vattr) + +let print_coverage fmt = + let ignored, analyzed = ref 0, ref 0 + and dead, reachable = ref 0, ref 0 in + let is_reachable = Db.Value.is_reachable_stmt in + let do_stmt stmt = incr (if is_reachable stmt then reachable else dead) in + let visit fundec = + if consider_function fundec.svar then + if is_called (Globals.Functions.get fundec.svar) + then (incr analyzed; List.iter do_stmt fundec.sallstmts) + else incr ignored + in + Globals.Functions.iter_on_fundecs visit; + let all = !dead + !analyzed in + if all = 0 + then Format.fprintf fmt "No function to be analyzed.@;" + else + Format.fprintf fmt + "%i function%s analyzed (out of %i): %i%% coverage.@;" + !analyzed (plural !analyzed) all (!analyzed * 100 / all); + let total = !dead + !reachable in + if !analyzed > 0 && total > 0 then + Format.fprintf fmt + "In %s, %i statements reached (out of %i): %i%% coverage.@;" + (if !analyzed > 1 then "these functions" else "this function") + !reachable total (!reachable * 100 / total) + +let print_warning fmt = + let eva_warnings, eva_errors = ref 0, ref 0 + and kernel_warnings, kernel_errors = ref 0, ref 0 in + let report_event event = + let open Log in + match event.evt_kind, event.evt_plugin with + | Warning, "eva" when event.evt_category <> Some "alarm" -> incr eva_warnings + | Warning, name when name = Log.kernel_label_name -> incr kernel_warnings + | Error, "eva" when event.evt_category <> Some "alarm" -> incr eva_errors + | Error, name when name = Log.kernel_label_name -> incr kernel_errors + | _ -> () + in + Messages.iter report_event; + let total = !eva_errors + !eva_warnings + !kernel_errors + !kernel_warnings in + if total = 0 + then Format.fprintf fmt "No errors or warnings raised during the analysis.@;" + else + let print str errors warnings = + Format.fprintf fmt " by %-19s %3i error%s %3i warning%s@;" + (str ^ ":") errors (plural errors) warnings (plural warnings) + in + Format.fprintf fmt + "Some errors and warnings have been raised during the analysis:@;"; + print "the Eva analyzer" !eva_errors !eva_warnings; + print "the Frama-C kernel" !kernel_errors !kernel_warnings + +type alarms = + { division_by_zero: int ref; + memory_access: int ref; + index_out_of_bound: int ref; + overflow: int ref; + invalid_shift: int ref; + uninitialized: int ref; + dangling: int ref; + nan_or_infinite: int ref; + float_to_int: int ref; + others: int ref; } + +type statuses = { valid: int ref; unknown: int ref; invalid: int ref; } + +type report = + { alarms: statuses * alarms; + assertions: statuses; + preconds: statuses; } + +let empty_report () = + let empty () = { valid = ref 0; unknown = ref 0; invalid = ref 0 } in + let empty_alarms = + { division_by_zero = ref 0; + memory_access = ref 0; + index_out_of_bound = ref 0; + overflow = ref 0; + invalid_shift = ref 0; + uninitialized = ref 0; + dangling = ref 0; + nan_or_infinite = ref 0; + float_to_int = ref 0; + others = ref 0; } + in + { alarms = empty (), empty_alarms; + assertions = empty (); + preconds = empty (); } + +let report_alarm report alarm = + let open Alarms in + let counter = match alarm with + | Division_by_zero _ -> report.division_by_zero + | Memory_access _ -> report.memory_access + | Index_out_of_bound _ -> report.index_out_of_bound + | Invalid_shift _ -> report.invalid_shift + | Overflow _ -> report.overflow + | Uninitialized _ -> report.uninitialized + | Dangling _ -> report.dangling + | Is_nan_or_infinite _ + | Is_nan _ -> report.nan_or_infinite + | Float_to_int _ -> report.float_to_int + | _ -> report.others + in + incr counter + +let eva_emitter = Value_util.emitter + +let get_status ip = + let aux_status emitter status acc = + let emitter = Emitter.Usable_emitter.get emitter.Property_status.emitter in + if Emitter.equal eva_emitter emitter + then Some status + else acc + in + Property_status.fold_on_statuses aux_status ip None + +let report_status acc = function + | None -> () + | Some status -> match status with + | Property_status.Dont_know -> incr acc.unknown + | Property_status.True -> incr acc.valid + | Property_status.False_if_reachable + | Property_status.False_and_reachable -> incr acc.invalid + +let make_report () = + let report = empty_report () in + let report_property ip = + match ip with + | Property.IPCodeAnnot (_kf, _stmt, code_annot) -> + begin + let status = get_status ip in + match Alarms.find code_annot with + | None -> report_status report.assertions status + | Some alarm -> + let acc_status, acc_alarms = report.alarms in + report_status acc_status status; + report_alarm acc_alarms alarm + end + | Property.IPPropertyInstance _ -> + let status = get_status ip in + report_status report.preconds status + | _ -> () + in + Property_status.iter report_property; + report + +let print_alarms_kind fmt kind = + let print count str plural str' = + if !count > 0 then + Format.fprintf fmt " %4i %s%s%s@;" + !count str (if !count > 1 then plural else "") str' + in + print kind.division_by_zero "division" "s" " by zero"; + print kind.memory_access "invalid memory access" "es" ""; + print kind.index_out_of_bound "access" "es" " out of bounds index"; + print kind.overflow "integer overflow" "s" ""; + print kind.invalid_shift "invalid shift" "s" ""; + print kind.uninitialized "access" "es" " to uninitialized left-values"; + print kind.dangling "escaping address" "es" ""; + print kind.nan_or_infinite "nan or infinite floating-point value" "s" ""; + print kind.float_to_int "illegal conversion" "s" " from floating-point to integer"; + print kind.others "other" "s" "" + +let print_alarms fmt report = + let alarms, kind = report.alarms in + let total = !(alarms.unknown) + !(alarms.invalid) in + Format.fprintf fmt "%i alarm%s generated by the analysis" total (plural total); + if total = !(kind.others) + then Format.fprintf fmt ".@;" + else Format.fprintf fmt ":@;%a" print_alarms_kind kind; + let invalid = !(alarms.invalid) in + if invalid > 0 then + Format.fprintf fmt "%i of them %s sure alarm%s (invalid status).@;" + invalid (if invalid = 1 then "is a" else "are") (plural invalid) + +let print_properties fmt report = + let { assertions; preconds } = report in + let total acc = !(acc.valid) + !(acc.unknown) + !(acc.invalid) in + let total_assertions = total assertions + and total_preconds = total preconds in + let total = total_assertions + total_preconds in + if total = 0 + then + Format.fprintf fmt + "No logical properties have been reached by the analysis.@;" + else + let print_line header status total = + Format.fprintf fmt + " %-14s %4d valid %4d unknown %4d invalid %4d total@;" + header !(status.valid) !(status.unknown) !(status.invalid) total; + in + Format.fprintf fmt + "Evaluation of the logical properties reached by the analysis:@;"; + print_line "Assertions" assertions total_assertions; + print_line "Preconditions" preconds total_preconds; + let proven = !(assertions.valid) + !(preconds.valid) in + let proven = proven * 100 / total in + Format.fprintf fmt + "%i%% of the logical properties reached have been proven.@;" proven + +let print_summary fmt = + let bar = String.make 76 '-' in + let report = make_report () in + Format.fprintf fmt "%s@;" bar; + print_coverage fmt; + Format.fprintf fmt "%s@;" bar; + print_warning fmt; + Format.fprintf fmt "%s@;" bar; + print_alarms fmt report; + Format.fprintf fmt "%s@;" bar; + print_properties fmt report; + Format.fprintf fmt "%s" bar + +let print_summary () = + let dkey = Value_parameters.dkey_summary in + let header fmt = Format.fprintf fmt " ====== ANALYSIS SUMMARY ======" in + Value_parameters.printf ~header ~dkey ~level:1 " @[<v>%t@]" print_summary (* Local Variables: diff --git a/src/plugins/value/utils/value_results.mli b/src/plugins/value/utils/value_results.mli index ae92d8878ad5f6a6dd35716ef6a6fdac8abbae83..5238a634bbfdfe97e8cf10131b53475eec1f81a1 100644 --- a/src/plugins/value/utils/value_results.mli +++ b/src/plugins/value/utils/value_results.mli @@ -46,6 +46,8 @@ val change_callstacks: For technical reasons, the top of the callstack must currently be preserved. *) +(** Prints a summary of the analysis. *) +val print_summary: unit -> unit (* Local Variables: diff --git a/src/plugins/value/value_parameters.ml b/src/plugins/value/value_parameters.ml index 0f8cb4ca773b121746c0af4c6c998912fbb4df51..7b0254eb4854e6efd6d47df74d0e7ef0ed107de7 100644 --- a/src/plugins/value/value_parameters.ml +++ b/src/plugins/value/value_parameters.ml @@ -70,6 +70,7 @@ let () = add_plugin_output_aliases [ "value" ] (* Debug categories. *) let dkey_initial_state = register_category "initial-state" let dkey_final_states = register_category "final-states" +let dkey_summary = register_category "summary" let dkey_pointer_comparison = register_category "pointer-comparison" let dkey_cvalue_domain = register_category "d-cvalue" let dkey_incompatible_states = register_category "incompatible-states" @@ -80,7 +81,7 @@ let dkey_widening = register_category "widening" let () = let activate dkey = add_debug_keys dkey in List.iter activate - [dkey_initial_state; dkey_final_states; dkey_cvalue_domain] + [dkey_initial_state; dkey_final_states; dkey_summary; dkey_cvalue_domain] (* Warning categories. *) let wkey_alarm = register_warn_category "alarm" diff --git a/src/plugins/value/value_parameters.mli b/src/plugins/value/value_parameters.mli index 1edd3dac575243c6c528a686411f75ac92df9a65..c6a81aa6e935db912cd109fdec2d1c5f6ea3c6cf 100644 --- a/src/plugins/value/value_parameters.mli +++ b/src/plugins/value/value_parameters.mli @@ -166,6 +166,7 @@ val parameters_abstractions: Typed_parameter.t list -value-msg-key="-initial_state,-final_state" *) val dkey_initial_state : category val dkey_final_states : category +val dkey_summary : category (** Warning category used when emitting an alarm in "warning" mode. *) val wkey_alarm: warn_category