diff --git a/ivette/src/frama-c/api/generated/plugins/eva/general/index.ts b/ivette/src/frama-c/api/generated/plugins/eva/general/index.ts index 96be458013ea25c00c86201d01d91dd023508c90..053707094794108ec1bbae31e17d63b128672dc4 100644 --- a/ivette/src/frama-c/api/generated/plugins/eva/general/index.ts +++ b/ivette/src/frama-c/api/generated/plugins/eva/general/index.ts @@ -244,4 +244,207 @@ const properties_internal: State.Array<Json.key<'#property'>,propertiesData> = { /** Status of Registered Properties */ export const properties: State.Array<Json.key<'#property'>,propertiesData> = properties_internal; +/** The alarms are counted after being grouped by these categories */ +export enum alarmCategory { + /** Integer division by zero */ + division_by_zero = 'division_by_zero', + /** Invalid pointer dereferencing */ + mem_access = 'mem_access', + /** Array access out of bounds */ + index_bound = 'index_bound', + /** Invalid shift */ + shift = 'shift', + /** Integer overflow or downcast */ + overflow = 'overflow', + /** Uninitialized memory read */ + initialization = 'initialization', + /** Read of a dangling pointer */ + dangling_pointer = 'dangling_pointer', + /** Non-finite (nan or infinite) floating-point value */ + is_nan_or_infinite = 'is_nan_or_infinite', + /** Overflow in float to int conversion */ + float_to_int = 'float_to_int', + /** Any other alarm */ + other = 'other', +} + +/** Loose decoder for `alarmCategory` */ +export const jAlarmCategory: Json.Loose<alarmCategory> = + Json.jEnum(alarmCategory); + +/** Safe decoder for `alarmCategory` */ +export const jAlarmCategorySafe: Json.Safe<alarmCategory> = + Json.jFail(Json.jEnum(alarmCategory), + 'plugins.eva.general.alarmCategory expected'); + +/** Natural order for `alarmCategory` */ +export const byAlarmCategory: Compare.Order<alarmCategory> = + Compare.byEnum(alarmCategory); + +const alarmCategoryTags_internal: Server.GetRequest<null,tag[]> = { + kind: Server.RqKind.GET, + name: 'plugins.eva.general.alarmCategoryTags', + input: Json.jNull, + output: Json.jList(jTag), +}; +/** Registered tags for the above type. */ +export const alarmCategoryTags: Server.GetRequest<null,tag[]>= alarmCategoryTags_internal; + +/** Statuses count.œ */ +export type statusesEntry = + { valid: number, unknown: number, invalid: number }; + +/** Loose decoder for `statusesEntry` */ +export const jStatusesEntry: Json.Loose<statusesEntry> = + Json.jObject({ + valid: Json.jFail(Json.jNumber,'Number expected'), + unknown: Json.jFail(Json.jNumber,'Number expected'), + invalid: Json.jFail(Json.jNumber,'Number expected'), + }); + +/** Safe decoder for `statusesEntry` */ +export const jStatusesEntrySafe: Json.Safe<statusesEntry> = + Json.jFail(jStatusesEntry,'StatusesEntry expected'); + +/** Natural order for `statusesEntry` */ +export const byStatusesEntry: Compare.Order<statusesEntry> = + Compare.byFields + <{ valid: number, unknown: number, invalid: number }>({ + valid: Compare.number, + unknown: Compare.number, + invalid: Compare.number, + }); + +/** Statistics about an Eva analysis. */ +export type statistics = + { coverage: + { functions: { reachable: number, dead: number }, + statements: { reachable: number, dead: number } }, + eva_events: { errors: number, warnings: number }, + kernel_events: { errors: number, warnings: number }, + statuses: + { alarms: statusesEntry, assertions: statusesEntry, + preconds: statusesEntry }, + alarms: { category: alarmCategory, count: number }[] }; + +/** Loose decoder for `statistics` */ +export const jStatistics: Json.Loose<statistics> = + Json.jObject({ + coverage: Json.jFail( + Json.jObject({ + functions: Json.jFail( + Json.jObject({ + reachable: Json.jFail(Json.jNumber, + 'Number expected'), + dead: Json.jFail(Json.jNumber, + 'Number expected'), + }),'Record expected'), + statements: Json.jFail( + Json.jObject({ + reachable: Json.jFail(Json.jNumber, + 'Number expected'), + dead: Json.jFail(Json.jNumber, + 'Number expected'), + }),'Record expected'), + }),'Record expected'), + eva_events: Json.jFail( + Json.jObject({ + errors: Json.jFail(Json.jNumber,'Number expected'), + warnings: Json.jFail(Json.jNumber,'Number expected'), + }),'Record expected'), + kernel_events: Json.jFail( + Json.jObject({ + errors: Json.jFail(Json.jNumber,'Number expected'), + warnings: Json.jFail(Json.jNumber,'Number expected'), + }),'Record expected'), + statuses: Json.jFail( + Json.jObject({ + alarms: jStatusesEntrySafe, + assertions: jStatusesEntrySafe, + preconds: jStatusesEntrySafe, + }),'Record expected'), + alarms: Json.jList( + Json.jObject({ + category: jAlarmCategorySafe, + count: Json.jFail(Json.jNumber,'Number expected'), + })), + }); + +/** Safe decoder for `statistics` */ +export const jStatisticsSafe: Json.Safe<statistics> = + Json.jFail(jStatistics,'Statistics expected'); + +/** Natural order for `statistics` */ +export const byStatistics: Compare.Order<statistics> = + Compare.byFields + <{ coverage: + { functions: { reachable: number, dead: number }, + statements: { reachable: number, dead: number } }, + eva_events: { errors: number, warnings: number }, + kernel_events: { errors: number, warnings: number }, + statuses: + { alarms: statusesEntry, assertions: statusesEntry, + preconds: statusesEntry }, + alarms: { category: alarmCategory, count: number }[] }>({ + coverage: Compare.byFields + <{ functions: { reachable: number, dead: number }, + statements: { reachable: number, dead: number } }>({ + functions: Compare.byFields + <{ reachable: number, dead: number }>({ + reachable: Compare.number, + dead: Compare.number, + }), + statements: Compare.byFields + <{ reachable: number, dead: number }>({ + reachable: Compare.number, + dead: Compare.number, + }), + }), + eva_events: Compare.byFields + <{ errors: number, warnings: number }>({ + errors: Compare.number, + warnings: Compare.number, + }), + kernel_events: Compare.byFields + <{ errors: number, warnings: number }>({ + errors: Compare.number, + warnings: Compare.number, + }), + statuses: Compare.byFields + <{ alarms: statusesEntry, assertions: statusesEntry, + preconds: statusesEntry }>({ + alarms: byStatusesEntry, + assertions: byStatusesEntry, + preconds: byStatusesEntry, + }), + alarms: Compare.array( + Compare.byFields + <{ category: alarmCategory, count: number }>({ + category: byAlarmCategory, + count: Compare.number, + })), + }); + +/** Signal for state [`stats`](#stats) */ +export const signalStats: Server.Signal = { + name: 'plugins.eva.general.signalStats', +}; + +const getStats_internal: Server.GetRequest<null,statistics> = { + kind: Server.RqKind.GET, + name: 'plugins.eva.general.getStats', + input: Json.jNull, + output: jStatistics, +}; +/** Getter for state [`stats`](#stats) */ +export const getStats: Server.GetRequest<null,statistics>= getStats_internal; + +const stats_internal: State.Value<statistics> = { + name: 'plugins.eva.general.stats', + signal: signalStats, + getter: getStats, +}; +/** Statistics about the last Eva analysis */ +export const stats: State.Value<statistics> = stats_internal; + /* ------------------------------------- */ diff --git a/ivette/src/frama-c/plugins/eva/.eslintrc.js b/ivette/src/frama-c/plugins/eva/.eslintrc.js new file mode 100644 index 0000000000000000000000000000000000000000..50624b8675a991f2987e7a1da222c1e323eacc84 --- /dev/null +++ b/ivette/src/frama-c/plugins/eva/.eslintrc.js @@ -0,0 +1,20 @@ +module.exports = { + root: true, + overrides: [ + { + files: ['*.js', '*.jsx', '*.ts', '*.tsx'], + excludedFiles: 'summary.tsx', + extends: ["../../../../.eslintrc.js"] + }, + { + files: ['summary.tsx'], + extends: [ + 'eslint:recommended', + 'plugin:react/recommended', + 'plugin:react-hooks/recommended', + 'plugin:@typescript-eslint/eslint-recommended', + 'plugin:@typescript-eslint/recommended', + ] + } + ] +}; diff --git a/ivette/src/frama-c/plugins/eva/index.tsx b/ivette/src/frama-c/plugins/eva/index.tsx index 9f2c3c733ab8443c010bf7f0a7f1cc1c05993a7a..495b0dde3331865486aa902ecb961e23746f1679 100644 --- a/ivette/src/frama-c/plugins/eva/index.tsx +++ b/ivette/src/frama-c/plugins/eva/index.tsx @@ -36,6 +36,7 @@ import { AutoSizer } from 'react-virtualized'; import { ProbeInfos } from './probeinfos'; import { Dimension, ValuesPanel } from './valuetable'; import { AlarmsInfos, StackInfos } from './valueinfos'; +import { } from './summary'; import './style.css'; // -------------------------------------------------------------------------- diff --git a/ivette/src/frama-c/plugins/eva/summary.tsx b/ivette/src/frama-c/plugins/eva/summary.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b1d8fd74dedd2561577f786e6919be1bb7c694af --- /dev/null +++ b/ivette/src/frama-c/plugins/eva/summary.tsx @@ -0,0 +1,239 @@ +/* ************************************************************************ */ +/* */ +/* This file is part of Frama-C. */ +/* */ +/* Copyright (C) 2007-2021 */ +/* CEA (Commissariat à l'énergie atomique et aux énergies */ +/* alternatives) */ +/* */ +/* you can redistribute it and/or modify it under the terms of the GNU */ +/* Lesser General Public License as published by the Free Software */ +/* Foundation, version 2.1. */ +/* */ +/* It is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU Lesser General Public License for more details. */ +/* */ +/* See the GNU Lesser General Public License version 2.1 */ +/* for more details (enclosed in the file licenses/LGPLv2.1). */ +/* */ +/* ************************************************************************ */ + +// React & Dome +import React from 'react'; +import * as Ivette from 'ivette'; +import { Vfill } from 'dome/layout/boxes'; +import * as States from 'frama-c/states'; +import * as Eva from 'frama-c/api/plugins/eva/general'; + +function percent(reachable: number, total: number): string { + return (reachable * 100 / total).toFixed(1) + '%'; +} + +function FunCoverage(data: Eva.statistics): JSX.Element { + const {coverage: {functions: {reachable, dead}}} = data; + const total = reachable + dead; + const ratio = percent(reachable, total); + return total > 0 ? ( + <> + <span>{reachable}</span> function{total === 1 ? '' : 's'} {' '} + analyzed (out of <span>{total}</span>): <span>{ratio}</span> coverage. + </> + ) : + <>No function to be analyzed.</>; +} + +function StmtCoverage(data: Eva.statistics): JSX.Element { + const {coverage: {statements: {reachable, dead}}} = data; + const total = reachable + dead; + const ratio = percent(reachable, total); + const functions = data.coverage.functions.reachable; + return (functions > 0 && total > 0 ? ( + <> + In {functions > 1 ? 'these functions' : 'this function'},{' '} + <span>{reachable}</span> statements reached + (out of <span>{total}</span>): <span>{ratio}</span> coverage. + </> + ) : + <></> + ); +} + +function CoverageTable(data: Eva.statistics): JSX.Element { + const {coverage: {functions, statements}} = data; + const functionsTotal = functions.reachable + functions.dead; + const statementsTotal = statements.reachable + statements.dead; + return ( + <table> + <thead> + <tr> + <th /> + <th>Analyzed</th> + <th>Total</th> + <th>Coverage</th> + </tr> + </thead> + <tbody> + <tr> + <td>Functions</td> + <td>{functions.reachable}</td> + <td>{functionsTotal}</td> + <td>{percent(functions.reachable, functionsTotal)}</td> + </tr> + <tr> + <td>Statements</td> + <td>{statements.reachable}</td> + <td>{statementsTotal}</td> + <td>{percent(statements.reachable, statementsTotal)}</td> + </tr> + </tbody> + </table> + ); +} + +function plural(count: number): string { + return count === 1 ? '' : 's'; +} + +function Errors(data: Eva.statistics): JSX.Element { + const {eva_events: eva, kernel_events: kernel} = data; + const total = eva.warnings + eva.errors + kernel.warnings + kernel.errors; + return (total > 0 ? ( + <> + Some errors and warnings have been raised during the analysis: + <table> + <tbody> + <tr> + <td>by the Eva analyzer</td> + <td>{eva.errors} error{plural(eva.errors)}</td> + <td>{eva.warnings} warning{plural(eva.warnings)}</td> + </tr> + <tr> + <td>by the Frama-C kernel</td> + <td>{kernel.errors} error{plural(kernel.errors)}</td> + <td>{kernel.warnings} warning{plural(kernel.warnings)}</td> + </tr> + </tbody> + </table> + </> + ) : + <>No errors or warnings raised during the analysis.</> + ); +} + +function Alarms(data: Eva.statistics, + categories: Map<string, States.Tag>): JSX.Element { + const {alarms, statuses: {alarms: {invalid, unknown}}} = data; + const total = unknown + invalid; + + const label = (category: Eva.alarmCategory): string | undefined => + categories.get(category)?.descr; + + return ( + <> + <div>{total} alarms{plural(total)} generated by the analysis</div> + <table> + <tbody> + {alarms.map(entry => ( + <tr key={entry.category}> + <td>{entry.count}</td> + <td>{label(entry.category)}</td> + </tr> + ))} + </tbody> + </table> + {invalid > 0 && ( + <div> + {invalid} of them {invalid === 1 ? 'is a' : 'are'} sure + alarm{plural(invalid)}. + </div> + )} + </> + ); +} + +function Statuses(data: Eva.statistics): JSX.Element { + const { assertions, preconds } = data.statuses; + const all = (entry: Eva.statusesEntry): number => + entry.valid + entry.unknown + entry.invalid; + const totalAssertions = all(assertions); + const totalPreconds = all(preconds); + const total = totalAssertions + totalPreconds; + const proven = assertions.valid + preconds.valid; + if (total > 0) { + return ( + <> + <table> + <tbody> + <tr> + <th>Assertions</th> + <td>{assertions.valid} valid</td> + <td>{assertions.unknown} unknown</td> + <td>{assertions.invalid} invalid</td> + <td>{totalAssertions} total</td> + </tr> + <tr> + <th>Preconditions</th> + <td>{preconds.valid} valid</td> + <td>{preconds.unknown} unknown</td> + <td>{preconds.invalid} invalid</td> + <td>{totalPreconds} total</td> + </tr> + </tbody> + </table> + {percent(proven, total)} of the logical properties reached have been + proven. + </> + ); + } + + return (<>No logical properties have been reached by the analysis.</>); + +} + +export function EvaSummary(): JSX.Element { + const alarmCategories = States.useTags(Eva.alarmCategoryTags); + const data = States.useSyncValue(Eva.stats); + + return (data && alarmCategories ? ( + <> + <h2>Coverage</h2> + <ul> + <li>{FunCoverage(data)}</li> + <li>{StmtCoverage(data)}</li> + </ul> + {CoverageTable(data)} + <h2>Errors</h2> + {Errors(data)} + <h2>Alarms</h2> + {Alarms(data, alarmCategories)} + <h2>Statuses</h2> + {Statuses(data)} + </> + ) : + <></> + ); +} + +function EvaSummaryComponent(): JSX.Element { + return ( + <> + <Ivette.TitleBar /> + <Vfill> + <EvaSummary /> + </Vfill> + </> + ); +} + +Ivette.registerComponent({ + id: 'frama-c.plugins.eva_summary', + group: 'frama-c.plugins', + rank: 10, + label: 'Eva Summary', + title: 'Summary of the Eva analysis', + children: <EvaSummaryComponent />, +}); + +// -------------------------------------------------------------------------- diff --git a/src/plugins/value/api/general_requests.ml b/src/plugins/value/api/general_requests.ml index 283e6dbac434a63b4ab21abc0806b80ed26a4609..78a7ba5c09ffd6fd53904326db777374a312c940 100644 --- a/src/plugins/value/api/general_requests.ml +++ b/src/plugins/value/api/general_requests.ml @@ -206,4 +206,189 @@ let _array = ~iter:Property_status.iter model + +(* ----- Analysis statistics -------------------------------------------- *) + +module AlarmCategory = struct + open Server.Data + + module Tags = + struct + let dictionary = Enum.dictionary () + + (* Give a normal representation of the category *) + let repr = + let e = List.hd Cil_datatype.Exp.reprs in + let lv = List.hd Cil_datatype.Lval.reprs in + function + | Value_results.Division_by_zero -> Alarms.Division_by_zero e + | Memory_access -> Memory_access (lv, For_reading) + | Index_out_of_bound-> Index_out_of_bound (e, None) + | Invalid_shift -> Invalid_shift (e, None) + | Overflow -> Overflow (Signed, e, Integer.one, Lower_bound) + | Uninitialized -> Uninitialized lv + | Dangling -> Dangling lv + | Nan_or_infinite -> Is_nan_or_infinite (e, FFloat) + | Float_to_int -> Float_to_int (e, Integer.one, Lower_bound) + | Other -> assert false + + let register alarm_category = + let name, descr = match alarm_category with + | Value_results.Other -> "other", "Any other alarm" + | alarm_category -> + let alarm = repr alarm_category in + Alarms.(get_short_name alarm, get_description alarm) + in + Enum.tag dictionary + ~name + ~label:(Markdown.plain name) + ~descr:(Markdown.plain descr) + + let division_by_zero = register Division_by_zero + let memory_access = register Memory_access + let index_out_of_bound = register Index_out_of_bound + let invalid_shift = register Invalid_shift + let overflow = register Overflow + let uninitialized = register Uninitialized + let dangling = register Dangling + let nan_or_infinite = register Nan_or_infinite + let float_to_int = register Float_to_int + let other = register Other + + let () = Enum.set_lookup dictionary + begin function + | Value_results.Division_by_zero -> division_by_zero + | Memory_access -> memory_access + | Index_out_of_bound -> index_out_of_bound + | Invalid_shift -> invalid_shift + | Overflow -> overflow + | Uninitialized -> uninitialized + | Dangling -> dangling + | Nan_or_infinite -> nan_or_infinite + | Float_to_int -> float_to_int + | Other -> other + end + end + + let name = "alarmCategory" + let descr = Markdown.plain + "The alarms are counted after being grouped by these categories" + let data = Request.dictionary ~package ~name ~descr Tags.dictionary + + include (val data : S with type t = Value_results.alarm_category) +end + +module CoverageEntry = +struct + open Value_results + let jtype = Package.( + Jrecord [ + "reachable",Jnumber ; + "dead",Jnumber ; + ]) + let to_json x = `Assoc [ + "reachable", `Int x.reachable ; + "dead", `Int x.dead ; + ] +end + +module Coverage = +struct + open Value_results + let jtype = Package.( + Jrecord [ + "functions",CoverageEntry.jtype ; + "statements",CoverageEntry.jtype ; + ]) + let to_json x = `Assoc [ + "functions", CoverageEntry.to_json x.functions ; + "statements", CoverageEntry.to_json x.statements ; + ] +end + +module EventCount = +struct + open Value_results + let jtype = Package.( + Jrecord [ + "errors",Jnumber ; + "warnings",Jnumber ; + ]) + let to_json x = `Assoc [ + "errors", `Int x.errors ; + "warnings", `Int x.warnings ; + ] +end + +module StatusesEntry = +struct + open Value_results + let jtype = + Data.declare ~package + ~name:"statusesEntry" + ~descr:(Markdown.plain "Statuses count.œ") + Package.(Jrecord [ + "valid",Jnumber ; + "unknown",Jnumber ; + "invalid",Jnumber ; + ]) + let to_json x = `Assoc [ + "valid", `Int x.valid ; + "unknown", `Int x.unknown ; + "invalid", `Int x.invalid ; + ] +end + +module Statuses = +struct + open Value_results + let jtype = Package.( + Jrecord [ + "alarms",StatusesEntry.jtype ; + "assertions",StatusesEntry.jtype ; + "preconds",StatusesEntry.jtype ; + ]) + let to_json (x : statuses) = `Assoc [ + "alarms", StatusesEntry.to_json x.alarms ; + "assertions", StatusesEntry.to_json x.assertions ; + "preconds", StatusesEntry.to_json x.preconds ; + ] +end + +module Statistics = struct + open Value_results + type t = stats + let jtype = + Data.declare ~package + ~name:"statistics" + ~descr:(Markdown.plain "Statistics about an Eva analysis.") + Package.(Jrecord [ + "coverage",Coverage.jtype ; + "eva_events",EventCount.jtype ; + "kernel_events",EventCount.jtype ; + "statuses",Statuses.jtype ; + "alarms",Jlist (Jrecord [ + "category", AlarmCategory.jtype ; + "count", Jnumber ])]) + let to_json x = `Assoc [ + "coverage", Coverage.to_json x.coverage ; + "eva_events", EventCount.to_json x.eva_events ; + "kernel_events", EventCount.to_json x.kernel_events ; + "statuses", Statuses.to_json x.statuses ; + "alarms", `List (List.map (fun (a,c) -> `Assoc [ + "category", AlarmCategory.to_json a ; + "count", `Int c ]) (AlarmsStats.bindings x.alarms))] +end + +let () = + let signal = States.register_value ~package + ~name:"stats" + ~descr:(Markdown.plain "Statistics about the last Eva analysis") + ~output:(module Statistics) + ~get:Value_results.compute_stats + () + in + Analysis.register_computed_hook (fun () -> Request.emit signal) + + (**************************************************************************) diff --git a/src/plugins/value/utils/value_results.ml b/src/plugins/value/utils/value_results.ml index 9ca7a64e5c07ea32af4db09682dae6ed5049fc82..1f82e0d16b7c9d143cb2919e045e57af8b7e6680 100644 --- a/src/plugins/value/utils/value_results.ml +++ b/src/plugins/value/utils/value_results.ml @@ -351,128 +351,120 @@ 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 +(* ---------------------- Stats for analysis summary ---------------------- *) + +type alarm_category = + | Division_by_zero + | Memory_access + | Index_out_of_bound + | Invalid_shift + | Overflow + | Uninitialized + | Dangling + | Nan_or_infinite + | Float_to_int + | Other + +module AlarmCategory = +struct + type t = alarm_category + + let of_alarm = function + | Alarms.Division_by_zero _ -> Division_by_zero + | Memory_access _ -> Memory_access + | Index_out_of_bound _ -> Index_out_of_bound + | Invalid_shift _ -> Invalid_shift + | Overflow _ -> Overflow + | Uninitialized _ -> Uninitialized + | Dangling _ -> Dangling + | Is_nan_or_infinite _ + | Is_nan _ -> Nan_or_infinite + | Float_to_int _ -> Float_to_int + | _ -> Other + + let id = function + | Division_by_zero -> 0 + | Memory_access -> 1 + | Index_out_of_bound -> 2 + | Invalid_shift -> 4 + | Overflow -> 5 + | Uninitialized -> 6 + | Dangling -> 7 + | Nan_or_infinite -> 8 + | Float_to_int -> 9 + | Other -> 10 + + let compare c1 c2 = id c1 - id c2 +end -let plural count = if count = 1 then "" else "s" +module AlarmsStats = +struct + include Map.Make (AlarmCategory) + + let get a m = try find a m with Not_found -> 0 + let incr a m = add a (get a m + 1) m +end + +type coverage_entry = + { mutable reachable: int; + mutable dead: int; } + +type coverage = + { functions: coverage_entry; + statements: coverage_entry; } + +type event_count = + { mutable errors: int; + mutable warnings: int; } + +type statuses_entry = + { mutable valid: int; + mutable unknown: int; + mutable invalid: int; } + +type statuses = + { alarms: statuses_entry; + assertions: statuses_entry; + preconds: statuses_entry; } + +type stats = + { coverage: coverage; + eva_events: event_count; + kernel_events: event_count; + statuses: statuses; + alarms: int AlarmsStats.t; } let consider_function vi = not (Cil_builtins.is_builtin vi || Cil_builtins.is_special_builtin vi.vname || Cil.is_in_libc vi.vattr) -let print_coverage fmt = - let dead_function, reachable_function = ref 0, ref 0 - and dead_stmt, reachable_stmt = ref 0, ref 0 in +let compute_coverage () = + let open Cil_types in + let coverage = { + functions = { reachable = 0; dead = 0 }; + statements = { reachable = 0; dead = 0 }; + } + and incr c = function + | false -> c.dead <- c.dead + 1 + | true -> c.reachable <- c.reachable + 1 + in let do_stmt stmt = - incr (if Db.Value.is_reachable_stmt stmt then reachable_stmt else dead_stmt) + incr coverage.statements (Db.Value.is_reachable_stmt stmt) in - let visit fundec = + let do_fun fundec = if consider_function fundec.svar then - if is_called (Globals.Functions.get fundec.svar) - then (incr reachable_function; List.iter do_stmt fundec.sallstmts) - else incr dead_function - in - Globals.Functions.iter_on_fundecs visit; - let total_function = !dead_function + !reachable_function in - if total_function = 0 - then Format.fprintf fmt "No function to be analyzed.@;" - else - Format.fprintf fmt - "%i function%s analyzed (out of %i): %i%% coverage.@;" - !reachable_function (plural !reachable_function) total_function - (!reachable_function * 100 / total_function); - let total_stmt = !dead_stmt + !reachable_stmt in - if !reachable_function > 0 && total_stmt > 0 then - Format.fprintf fmt - "In %s, %i statements reached (out of %i): %i%% coverage.@;" - (if !reachable_function > 1 then "these functions" else "this function") - !reachable_stmt total_stmt (!reachable_stmt * 100 / total_stmt) - -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 + let called = is_called (Globals.Functions.get fundec.svar) in + incr coverage.functions called; + if called then + List.iter do_stmt fundec.sallstmts in - incr counter - -let eva_emitter = Value_util.emitter + Globals.Functions.iter_on_fundecs do_fun; + coverage let get_status ip = + let eva_emitter = Value_util.emitter in let aux_status emitter status acc = let emitter = Emitter.Usable_emitter.get emitter.Property_status.emitter in if Emitter.equal eva_emitter emitter @@ -481,71 +473,151 @@ let get_status ip = in Property_status.fold_on_statuses aux_status ip None -let report_status acc = function +let count_status acc = function | None -> () | Some status -> match status with - | Property_status.Dont_know -> incr acc.unknown - | Property_status.True -> incr acc.valid + | Property_status.Dont_know -> acc.unknown <- acc.unknown + 1 + | Property_status.True -> acc.valid <- acc.valid + 1 | 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 = + | Property_status.False_and_reachable -> acc.invalid <- acc.invalid + 1 + +let compute_statuses () = + let empty () = { valid = 0; unknown = 0; invalid = 0 } in + let statuses = + { alarms = empty (); + assertions = empty (); + preconds = empty (); + } in + let alarms = ref AlarmsStats.empty in + let incr a = + alarms := AlarmsStats.incr a !alarms + in + let do_property ip = match ip with | Property.IPCodeAnnot Property.{ ica_ca; ica_stmt; } when Db.Value.is_reachable_stmt ica_stmt -> begin let status = get_status ip in match Alarms.find ica_ca with - | None -> report_status report.assertions status + | None -> count_status statuses.assertions status | Some alarm -> - let acc_status, acc_alarms = report.alarms in - report_status acc_status status; + count_status statuses.alarms status; match status with | None | Some Property_status.True -> () - | _ -> report_alarm acc_alarms alarm + | _ -> incr (AlarmCategory.of_alarm alarm) end | Property.IPPropertyInstance {Property.ii_stmt} when Db.Value.is_reachable_stmt ii_stmt -> - report_status report.preconds (get_status ip) + count_status statuses.preconds (get_status ip) + | _ -> () + in + Property_status.iter do_property; + statuses, !alarms + +let compute_stats () = + let statuses, alarms = compute_statuses () in + let stats = { + coverage = compute_coverage (); + eva_events = { errors = 0 ; warnings = 0 }; + kernel_events = { errors = 0 ; warnings = 0 }; + statuses; + alarms; + } + and incr_err entry = + entry.errors <- entry.errors + 1 + and incr_warn entry = + entry.warnings <- entry.warnings + 1 + in + let do_event event = + let open Log in + match event.evt_kind, event.evt_plugin with + | Warning, "eva" when event.evt_category <> Some "alarm" -> + incr_warn stats.eva_events + | Warning, name when name = Log.kernel_label_name -> + incr_warn stats.kernel_events + | Error, "eva" when event.evt_category <> Some "alarm" -> + incr_err stats.eva_events + | Error, name when name = Log.kernel_label_name -> + incr_warn stats.kernel_events | _ -> () 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 + Messages.iter do_event; + stats + + +(* ---------------------- Printing an analysis summary ---------------------- *) + +let plural count = if count = 1 then "" else "s" + +let print_coverage fmt c = + let total_function = c.functions.dead + c.functions.reachable in + if total_function = 0 + then Format.fprintf fmt "No function to be analyzed.@;" + else + Format.fprintf fmt + "%i function%s analyzed (out of %i): %i%% coverage.@;" + c.functions.reachable (plural c.functions.reachable) total_function + (c.functions.reachable * 100 / total_function); + let total_stmt = c.statements.dead + c.statements.reachable in + if c.functions.reachable > 0 && total_stmt > 0 then + Format.fprintf fmt + "In %s, %i statements reached (out of %i): %i%% coverage.@;" + (if c.functions.reachable > 1 then "these functions" else "this function") + c.statements.reachable total_stmt + (c.statements.reachable * 100 / total_stmt) + +let print_events fmt stats = + let total = + stats.eva_events.warnings + stats.eva_events.errors + + stats.kernel_events.warnings + stats.kernel_events.errors + in + if total = 0 + then Format.fprintf fmt "No errors or warnings raised during the analysis.@;" + else + let print str e = + Format.fprintf fmt " by %-19s %3i error%s %3i warning%s@;" + (str ^ ":") e.errors (plural e.errors) e.warnings (plural e.warnings) + in + Format.fprintf fmt + "Some errors and warnings have been raised during the analysis:@;"; + print "the Eva analyzer" stats.eva_events; + print "the Frama-C kernel" stats.kernel_events + + +let print_alarm fmt category count = + let str, plural, str' = match category with + | Division_by_zero -> "division", "s", " by zero" + | Memory_access -> "invalid memory access", "es", "" + | Index_out_of_bound -> "access", "es", " out of bounds index" + | Overflow -> "integer overflow", "s", "" + | Invalid_shift -> "invalid shift", "s", "" + | Uninitialized -> "access", "es", " to uninitialized left-values" + | Dangling -> "escaping address", "es", "" + | Nan_or_infinite -> "nan or infinite floating-point value", "s", "" + | Float_to_int -> "illegal conversion", "s", " from floating-point to integer" + | Other -> "other", "s", "" + in + Format.fprintf fmt " %4i %s%s%s@;" + count str (if count > 1 then plural else "") str' + +let print_alarms fmt alarms = + AlarmsStats.iter (print_alarm fmt) alarms + +let print_alarms_statuses fmt stats = + let alarms = stats.alarms and statuses = stats.statuses.alarms in + let total = statuses.unknown + statuses.invalid in Format.fprintf fmt "%i alarm%s generated by the analysis" total (plural total); - if total = !(kind.others) + if total = AlarmsStats.get Other alarms then Format.fprintf fmt ".@;" - else Format.fprintf fmt ":@;%a" print_alarms_kind kind; - let invalid = !(alarms.invalid) in + else Format.fprintf fmt ":@;%a" print_alarms alarms; + let invalid = statuses.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 print_statuses fmt statuses = + let { assertions; preconds } = statuses 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 @@ -557,28 +629,28 @@ let print_properties fmt report = 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; + 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 = 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 + let stats = compute_stats () in Format.fprintf fmt "%s@;" bar; - print_coverage fmt; + print_coverage fmt stats.coverage; Format.fprintf fmt "%s@;" bar; - print_warning fmt; + print_events fmt stats; Format.fprintf fmt "%s@;" bar; - print_alarms fmt report; + print_alarms_statuses fmt stats; Format.fprintf fmt "%s@;" bar; - print_properties fmt report; + print_statuses fmt stats.statuses; Format.fprintf fmt "%s" bar let print_summary () = diff --git a/src/plugins/value/utils/value_results.mli b/src/plugins/value/utils/value_results.mli index 512f45ebeb52538dd4963d1f659172fd9e1c56b2..dda49eb3f3b4c1318a5a59c903742909ec616854 100644 --- a/src/plugins/value/utils/value_results.mli +++ b/src/plugins/value/utils/value_results.mli @@ -46,6 +46,55 @@ val change_callstacks: For technical reasons, the top of the callstack must currently be preserved. *) + +(** {2 Summary } *) + +type alarm_category = + | Division_by_zero + | Memory_access + | Index_out_of_bound + | Invalid_shift + | Overflow + | Uninitialized + | Dangling + | Nan_or_infinite + | Float_to_int + | Other + +module AlarmsStats : Map.S with type key = alarm_category + +type coverage_entry = + { mutable reachable: int; + mutable dead: int; } + +type coverage = + { functions: coverage_entry; + statements: coverage_entry; } + +type event_count = + { mutable errors: int; + mutable warnings: int; } + +type statuses_entry = + { mutable valid: int; + mutable unknown: int; + mutable invalid: int; } + +type statuses = + { alarms: statuses_entry; + assertions: statuses_entry; + preconds: statuses_entry; } + +type stats = + { coverage: coverage; + eva_events: event_count; + kernel_events: event_count; + statuses: statuses; + alarms: int AlarmsStats.t; } + +(** Compute analysis statistics. *) +val compute_stats: unit -> stats + (** Prints a summary of the analysis. *) val print_summary: unit -> unit diff --git a/tests/value/oracle/summary.3.res.oracle b/tests/value/oracle/summary.3.res.oracle index 8c983fd90a289be87579fd40cfe9efbde6414a90..d852f36ef101da04ad12d22bbdd37ab6577ee632 100644 --- a/tests/value/oracle/summary.3.res.oracle +++ b/tests/value/oracle/summary.3.res.oracle @@ -94,8 +94,8 @@ 1 division by zero 2 invalid memory accesses 2 accesses out of bounds index - 3 integer overflows 2 invalid shifts + 3 integer overflows 1 escaping address 2 nan or infinite floating-point values 2 illegal conversions from floating-point to integer diff --git a/tests/value/oracle/summary.4.res.oracle b/tests/value/oracle/summary.4.res.oracle index 7bfb7aff0243d6fb76ac66ee9a5adf16a98cbed9..32a83624c64a9bbb40bae6f216db19c2f668fc41 100644 --- a/tests/value/oracle/summary.4.res.oracle +++ b/tests/value/oracle/summary.4.res.oracle @@ -117,8 +117,8 @@ 1 division by zero 3 invalid memory accesses 2 accesses out of bounds index - 3 integer overflows 2 invalid shifts + 3 integer overflows 1 escaping address 2 nan or infinite floating-point values 2 illegal conversions from floating-point to integer