From ccb6c0341ebb0fd1a66a47d29a644a24679a100a Mon Sep 17 00:00:00 2001 From: Virgile Prevosto <virgile.prevosto@m4x.org> Date: Tue, 3 Oct 2017 19:07:10 +0200 Subject: [PATCH] preparing manual addition of remarks into the report --- .../markdown-report/Report_markdown.mli | 3 + src/plugins/markdown-report/markdown.ml | 6 + src/plugins/markdown-report/markdown.mli | 3 + src/plugins/markdown-report/md_gen.ml | 507 ++++++++++++------ 4 files changed, 351 insertions(+), 168 deletions(-) diff --git a/src/plugins/markdown-report/Report_markdown.mli b/src/plugins/markdown-report/Report_markdown.mli index c5b9481ff9d..528de49a885 100644 --- a/src/plugins/markdown-report/Report_markdown.mli +++ b/src/plugins/markdown-report/Report_markdown.mli @@ -25,6 +25,7 @@ and block = block_element list and element = | Block of block | Raw of string (** non-markdown element, printed as-is. *) + | Comment of string (** markdown comment, printed <!-- like this --> *) | H1 of text * string option (** optional label. *) | H2 of text * string option | H3 of text * string option @@ -43,6 +44,8 @@ type pandoc_markdown = val plain: string -> text +val plain_format: ('a, Format.formatter, unit, text) format4 -> 'a + (** gives a link whose text is the URL itself. *) val plain_link: string -> inline diff --git a/src/plugins/markdown-report/markdown.ml b/src/plugins/markdown-report/markdown.ml index 19a1f503277..3dc4823cc70 100644 --- a/src/plugins/markdown-report/markdown.ml +++ b/src/plugins/markdown-report/markdown.ml @@ -24,6 +24,7 @@ and block = block_element list and element = | Block of block | Raw of string (** non-markdown element, printed as-is. *) + | Comment of string (** markdown comment, printed <!-- like this --> *) | H1 of text * string option (** optional label. *) | H2 of text * string option | H3 of text * string option @@ -42,6 +43,8 @@ type pandoc_markdown = let plain s = [ Plain s] +let plain_format txt = Format.kasprintf plain txt + let plain_link s = Link ([Inline_code s],s) let rec pp_inline fmt = @@ -168,6 +171,9 @@ and pp_quote fmt l = and pp_element fmt = function | Block b -> Format.fprintf fmt "@[<v>%a@]@\n" pp_block b | Raw s -> Format.pp_print_string fmt s + | Comment s -> + Format.fprintf fmt + "@[<hv>@[<hv 5><!-- %a@]@ -->@]@\n" Format.pp_print_text s | H1(t,lab) -> Format.fprintf fmt "@[<h># %a%a@]@\n" pp_text t pp_lab lab | H2(t,lab) -> Format.fprintf fmt "@[<h>## %a%a@]@\n" pp_text t pp_lab lab | H3(t,lab) -> Format.fprintf fmt "@[<h>### %a%a@]@\n" pp_text t pp_lab lab diff --git a/src/plugins/markdown-report/markdown.mli b/src/plugins/markdown-report/markdown.mli index e8a0a8c797b..1cedad28699 100644 --- a/src/plugins/markdown-report/markdown.mli +++ b/src/plugins/markdown-report/markdown.mli @@ -24,6 +24,7 @@ and block = block_element list and element = | Block of block | Raw of string (** non-markdown element, printed as-is. *) + | Comment of string (** markdown comment, printed <!-- like this --> *) | H1 of text * string option (** optional label. *) | H2 of text * string option | H3 of text * string option @@ -42,6 +43,8 @@ type pandoc_markdown = val plain: string -> text +val plain_format: ('a, Format.formatter, unit, text) format4 -> 'a + (** gives a link whose text is the URL itself. *) val plain_link: string -> inline diff --git a/src/plugins/markdown-report/md_gen.ml b/src/plugins/markdown-report/md_gen.ml index d2fff1bc627..f8a9ba52de6 100644 --- a/src/plugins/markdown-report/md_gen.ml +++ b/src/plugins/markdown-report/md_gen.ml @@ -1,3 +1,5 @@ +open Cil_types + module Self = Plugin.Register( struct @@ -20,6 +22,26 @@ struct let help = "generates an analysis report on the current project" end) +module Gen_draft = Self.False( + struct + let option_name = "-mdr-gen-draft" + let help = + "instead of a full report, generates an empty draft \ + in a format suitable for -mdr-add-remarks" + end) + +module Remarks = Self.Empty_string( +struct + let option_name = "-mdr-remarks" + let arg_name = "f" + let help = + "reads <f> to add additional remarks to various sections of the report. \ + must be in a format compatible with the file produced by -mdr-gen-draft. \ + Remarks themselves must be written in pandoc's markdown, although this is \ + not enforced by the plug-in." +end +) + module Authors = Self.String_list( struct let option_name = "-mdr-authors" @@ -58,6 +80,8 @@ let all_eva_domains = (e.g. `a[i]` when `i` is not precisely known by `Cvalue`)" ] +let insert_marks = [ Comment "BEGIN_REMARK"; Comment "END_REMARK" ] + let plural l s = match l with | [] | [ _ ] -> s @@ -74,23 +98,30 @@ let codelines lang pp code = let lines = String.split_on_char '\n' s in Code_block (lang, lines) -let section_domains () = - let l = get_eva_domains () in - Block - (match l with - | [] -> - [Text - (plain - "Only the base domain (`Cvalue`) has been used for the analysis")] - | _ -> - [Text - (plain - "In addition to the base domain (`Cvalue`), additional domains \ - have been used by EVA"); - DL l] - ) - -let section_stubs () = +let section_domains is_draft = + if is_draft then + Comment + "You can give more information about the choice of EVA domains" + :: insert_marks + else begin + let l = get_eva_domains () in + [ Block + (match l with + | [] -> + [Text + (plain + "Only the base domain (`Cvalue`) \ + has been used for the analysis")] + | _ -> + [Text + (plain + "In addition to the base domain (`Cvalue`), additional \ + domains have been used by EVA"); + DL l] + )] + end + +let section_stubs is_draft = let stubbed_kf = List.concat (List.map @@ -112,62 +143,105 @@ let section_stubs () = (fun s -> String.length s <> 0 && s.[0] <> '@' && s.[0] <> '-') (fun s -> let kf = Globals.Functions.find_by_name s in - [ Text [Inline_code s; Plain "with the following specification"]; - codelines "acsl" Printer.pp_funspec (Annotations.funspec kf)]) + let content = + if is_draft then insert_marks + else + [ Block + [ Text + [Inline_code s; Plain "has the following specification"]; + codelines + "acsl" Printer.pp_funspec (Annotations.funspec kf)]] + in + H4 ([Inline_code s], Some s) :: content) l in let describe_func kf = - [Text - [ Inline_code (Kernel_function.get_name kf); - Plain - (Format.asprintf - "@[<h>(defined at %a)@]" - Cil_datatype.Location.pretty - (Kernel_function.get_location kf)) - ]] + let name = Kernel_function.get_name kf in + let loc = Kernel_function.get_location kf in + let content = + if is_draft then insert_marks + else + [ Block + [ Text + (Inline_code name :: + plain_format + "@[<h>is defined at %a@]" Cil_datatype.Location.pretty loc); + codelines "c" + Printer.pp_global + (GFun (Kernel_function.get_definition kf,loc)) + ] + ] + in + H4 ([Inline_code name], Some name) :: content in let content = if stubbed_kf <> [] then begin - [ Text - (plain - "The following functions have been stubbed with a C definition"); - UL (List.map describe_func stubbed_kf)] + List.map describe_func stubbed_kf end else [] in - let content = - if use_spec <> [] then begin - [ Text - (plain - "The following functions have been stubbed with an \ - ACSL specification"); - UL use_spec] - end else content - in + let content = content @ use_spec in + let content = List.concat content in if content = [] then - Block [Text (plain "No stubs have been used for this analysis")] + if is_draft then + Comment "No stubs have been used" :: insert_marks + else + [ Block [Text (plain "No stubs have been used for this analysis")]] else - Block content - -let gen_context () = [ - H1 (plain "Context of the analysis", Some "context"); - H2 (plain "Input files", Some "c-input"); - Block [ - Text - (plain "The C source files (not including the headers `.h` files)" @ - plain "that have been considered during the analysis are the following:" - ); - UL (List.map (fun x -> [Text [ Inline_code x ]]) (Kernel.Files.get ())); - ]; - H2 (plain "Configuration", Some "options"); - Block [ - Text - (plain "The options that have been used for this analysis \ - are the following.")]; - H3 (plain "EVA Domains", Some "domains"); - section_domains(); - H3 (plain "Stubbed Functions", Some "stubs"); - section_stubs() -] + content + +let gen_context is_draft = + let context = + if is_draft then + Comment "You can add here some overall introduction to the analysis" + :: insert_marks + else [] + in + context @ [ + H1 (plain "Context of the analysis", Some "context"); + H2 (plain "Input files", Some "c-input") + ] @ + (if is_draft then + Comment + "You can add here some remarks about the set of files \ + that is considered by Frama-C" + :: insert_marks + else []) + @ [ + Block [ + Text + (plain "The C source files (not including the headers `.h` files)" @ + plain "that have been considered during the analysis \ + are the following:" + ); + UL (List.map (fun x -> [Text [ Inline_code x ]]) (Kernel.Files.get ())); + ]; + H2 (plain "Configuration", Some "options"); + Block [ + Text + (plain "The options that have been used for this analysis \ + are the following.")] + ] @ + (if is_draft then + Comment + "You can add here some remarks about the options used for the analysis" + :: insert_marks + else []) + @ + H3 (plain "EVA Domains", Some "domains") + :: section_domains is_draft + @ H3 (plain "Stubbed Functions", Some "stubs") + :: section_stubs is_draft + +let string_of_pos pos = + Format.asprintf + "%s:%d" (Filename.basename pos.Lexing.pos_fname) pos.Lexing.pos_lnum + +let string_of_pos_opt = + function + | None -> "Global" + | Some pos -> string_of_pos pos + +let string_of_loc (l1, _) = string_of_pos l1 let make_events_table print_kind caption events = let open Log in @@ -189,18 +263,12 @@ let make_events_table print_kind caption events = | Error -> "User error" | Failure -> "Internal error" in - let pos = function - | None -> "Global" - | Some pos -> - Format.asprintf - "%s:%d" (Filename.basename pos.Lexing.pos_fname) pos.Lexing.pos_lnum - in let treat_event { evt_kind; evt_plugin; evt_source; evt_message } = let evt_message = Str.global_replace (Str.regexp_string "\n") " " evt_message in let line = - [ plain (pos evt_source); + [ plain (string_of_pos_opt evt_source); [ Plain evt_message; Plain "(emitted by"; Inline_code evt_plugin; @@ -221,7 +289,30 @@ let make_warnings_table warnings = make_events_table false (plain (plural warnings "Warning" ^ " reported by Frama-C")) warnings -let gen_section_warnings () = +let section_event is_err nb event = + let open Log in + let title = + Format.asprintf "@[<h>%s %d (%s)@]" + (if is_err then "Error" else "Warning") + nb + (string_of_pos_opt event.evt_source) + in + let lab = + Some + (Format.asprintf "@[<h>%s-%d@]" (if is_err then "err" else "warn") nb) + in + [ H2 (plain title, lab); + Block [ Text (plain "message text is"); Text (plain event.evt_message) ]] + @ insert_marks + +let make_events_list is_err l = + List.concat (List.mapi (section_event is_err) l) + +let make_errors_list = make_events_list true + +let make_warnings_list = make_events_list false + +let gen_section_warnings is_draft = let open Log in Messages.reset_once_flag (); let errs = ref [] in @@ -242,112 +333,166 @@ let gen_section_warnings () = triggered without stopping everything. Applying the same treatment to a Failure catched by an evil plugin cannot hurt. *) - [ H1 (plain "Errors in the analyzer", Some "errors"); - Block [ - Text [Bold "Important warning:"; - Plain "Frama-C did not complete its execution successfully."; - Plain "Analysis results may be inaccurate."; - Plain ((plural errs "The error") ^ " listed below must be"); - Plain "fixed first before examining other warnings and alarms." - ]; - ]; - make_errors_table errs - ] + let content = + if is_draft then + Comment "you can comment on each individual error" :: + make_errors_list errs + else + [ + Block [ + Text [Bold "Important warning:"; + Plain "Frama-C did not complete its execution "; + Plain "successfully. Analysis results may be inaccurate."; + Plain ((plural errs "The error") ^ " listed below must be"); + Plain "fixed first before examining other "; + Plain "warnings and alarms." + ]; + ]; + make_errors_table errs + ] + in + H1 (plain "Errors in the analyzer", Some "errors") :: content end else [] in if Messages.nb_warnings () <> 0 then begin - error_section @ - [ H1 (plain "Warnings", Some "warnings"); - Block [ - Text [ - Plain ("The table below lists the " ^ plural warnings "warning"); - Plain "that have been emitted by the analyzer."; - Plain "They might put additional assumptions on the relevance"; - Plain "of the analysis results and must be reviewed carefully"; - ]; - Text [ - Plain "Note that this does not take into account emitted alarms:"; - Plain "they are reported in"; - Link (plain "in the next section", "#alarms") + let content = + if is_draft then + Comment "you can comment on each individual error" :: + make_warnings_list warnings + else + [ + Block [ + Text [ + Plain ("The table below lists the " ^ plural warnings "warning"); + Plain "that have been emitted by the analyzer."; + Plain "They might put additional assumptions on the relevance"; + Plain "of the analysis results and must be reviewed carefully"; + ]; + Text [ + Plain "Note that this does not take into account emitted alarms:"; + Plain "they are reported in"; + Link (plain "in the next section", "#alarms") + ] + ]; + make_warnings_table warnings ] - ]; - make_warnings_table warnings - ] + in + error_section @ + H1 (plain "Warnings", Some "warnings") :: content end else error_section -let gen_section_alarms () = - let treat_alarm e kf s ~rank:_ alarm annot l = +let gen_section_alarms is_draft = + let treat_alarm e kf s ~rank:_ alarm annot (i, sec, content) = let kind = plain (Alarms.get_name alarm) in + let label = "Alarm-" ^ string_of_int i in + let link = [Link (plain_format "%d" i, "#"^label)] in let func = plain (Kernel_function.get_name kf) in - let loc = - plain - (Format.asprintf - "%a" Cil_datatype.Location.pretty (Cil_datatype.Stmt.loc s)) - in + let loc = string_of_loc (Cil_datatype.Stmt.loc s) in + let loc_text = plain loc in let emitter = plain (Emitter.get_name e) in - let descr = - [ Inline_code(Format.asprintf "%a" Printer.pp_code_annotation annot)] + let descr = codelines "acsl" Printer.pp_code_annotation annot in + let sec_title = plain_format "Alarm %d at %s" i loc in + let sec_content = + if is_draft then + Block [ descr ] :: insert_marks + else + [ + Block + [ + Text + (plain + "The following ACSL assertion must hold to avoid \ + and undefined behavior (" + @ kind @ plain ")"); + descr + ] + ] in - [ kind; emitter; func; loc; descr ] :: l + (i+1, + sec @ H2 (sec_title, Some label) :: sec_content, + [ link; kind; emitter; func; loc_text ] :: content) in - let content = Alarms.fold treat_alarm [] in + let _,sections, content = Alarms.fold treat_alarm (0,[],[]) in match content with | [] -> - [ H1 (plain "Results of the analysis", Some "alarms"); - Block [ - Text - [ Bold "No alarm"; Plain "was found during the analysis"; - Plain "Any execution starting from"; - Inline_code (Kernel.MainFunction.get_function_name ()); - Plain "in a context matching the one used for the analysis"; - Plain "will be immune from any undefined behavior." + let text_content = + if is_draft then + Comment "No alarm!" :: insert_marks + else + [ + Block [ + Text + [ Bold "No alarm"; Plain "was found during the analysis"; + Plain "Any execution starting from"; + Inline_code (Kernel.MainFunction.get_function_name ()); + Plain "in a context matching the one used for the analysis"; + Plain "will be immune from any undefined behavior." + ] ] - ] - ] + ] + in + H1 (plain "Results of the analysis", Some "alarms") :: text_content | _ :: l -> let alarm = if l = [] then "alarm" else "alarms" in let caption = Some (plain (String.capitalize_ascii alarm ^ " emitted by the analysis")) in let header = - [ plain "Kind", Center; + [ plain "No", Center; + plain "Kind", Center; plain "Emitter", Center; plain "Function", Left; plain "Location", Left; - plain "Description", Left; ] in - [ H1 (plain "Results of the analysis", Some "alarms"); - Block [ - Text - [ Plain ("The table below lists the " ^ alarm); - Plain "that have been emitted during the analysis."; - Plain "Any execution starting from"; - Inline_code (Kernel.MainFunction.get_function_name()); - Plain "in a context matching the one used for the analysis"; - Plain "will be immune from any other undefined behavior." - ] - ]; - Table { content; caption; header } - ] + let text_content = + if is_draft then begin + sections + end else + [ + Block [ + Text + [ Plain ("The table below lists the " ^ alarm); + Plain "that have been emitted during the analysis."; + Plain "Any execution starting from"; + Inline_code (Kernel.MainFunction.get_function_name()); + Plain "in a context matching the one used for the analysis"; + Plain "will be immune from any other undefined behavior."; + Plain "More information on each individual alarm is"; + Plain "given in the remainder of this section" + ] + ]; + Table { content; caption; header } + ] + in + H1 (plain "Results of the analysis", Some "alarms") :: text_content -let gen_section_callgraph () = - [ H1 (plain "Flamegraph", Some "flamegraph"); - Block [ - Text [ - Plain "The image below shows the flamegraph ("; - plain_link "http://www.brendangregg.com/flamegraphs.html"; - Plain ") for the chosen entry point." +let gen_section_callgraph is_draft = + let content = + if is_draft then + Comment + "flamegraph allow to visualize the functions and callstacks \ + whose analysis is the most costly." + :: insert_marks + else + [ + Block [ + Text [ + Plain "The image below shows the flamegraph ("; + plain_link "http://www.brendangregg.com/flamegraphs.html"; + Plain ") for the chosen entry point." + ] + ]; + Block + [ Text [Image ("flamegraph", "../server.flamegraph.svg")] ] ] - ]; - Block - [ Text [Image ("flamegraph", "../server.flamegraph.svg")] ] - ] + in + H1 (plain "Flamegraph", Some "flamegraph") :: content -let gen_alarms () = - gen_section_warnings () @ - gen_section_alarms () @ - gen_section_callgraph () +let gen_alarms is_draft = + gen_section_warnings is_draft @ + gen_section_alarms is_draft @ + gen_section_callgraph is_draft let mk_date () = let tm = Unix.gmtime (Unix.time()) in @@ -355,26 +500,52 @@ let mk_date () = (Printf.sprintf "%d-%02d-%02d" (1900 + tm.Unix.tm_year) (1 + tm.Unix.tm_mon) tm.Unix.tm_mday) +let mk_remarks () = + let f = Remarks.get () in + if f <> "" then failwith "writeme" + else Datatype.String.Map.empty + +let gen_report is_draft = + let _remarks = mk_remarks () in + let context = gen_context is_draft in + let alarms = gen_alarms is_draft in + let title = + if is_draft then plain "Frama-C Analysis Report" else plain "Draft report" + in + let authors = List.map (fun x -> plain x) (Authors.get ()) in + let date = mk_date () in + let elements = context @ alarms in + let elements = + if is_draft then + Comment + "This file contains additional remarks that will be added to \ + automatically generated content by Frama-C's Markdown-report plugin. \ + For any section of the document, you can write markdown content \ + between the BEGIN and END comments. Please don't alter the structure \ + of the document as it is used by the plugin to associate content to \ + the relevant section." + :: elements + else elements + in + let doc = { title; authors; date; elements;} in + try + let out = open_out (Output.get()) in + let fmt = Format.formatter_of_out_channel out in + Markdown.pp_pandoc fmt doc; + close_out out + with Sys_error s -> + Self.warning + "Unable to open %s for writing (%s). No report will be generated" + (Output.get()) s + let main () = - if Generate.get () then begin - let context = gen_context () in - let alarms = gen_alarms () in - let doc = - { title = plain "Frama-C Analysis Report"; - authors = List.map (fun x -> plain x) (Authors.get ()); - date = mk_date (); - elements = context @ alarms - } - in - try - let out = open_out (Output.get()) in - let fmt = Format.formatter_of_out_channel out in - Markdown.pp_pandoc fmt doc; - close_out out - with Sys_error s -> - Self.warning - "Unable to open %s for writing (%s). No report will be generated" - (Output.get()) s + if Gen_draft.get () then begin + if Generate.get () then + Self.warning + "-mdr-gen and -mdr-gen-draft can be activated at the \ + same time. Only draft will be generated"; + gen_report true end + else if Generate.get () then gen_report false let () = Db.Main.extend main -- GitLab