Newer
Older
(**************************************************************************)
(* *)
(* This file is part of Frama-C. *)
(* *)
(* Copyright (C) 2007-2019 *)
(* 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). *)
(* *)
(**************************************************************************)
open Sarif
let frama_c_sarif =
let name = "frama-c" in
let version = Config.version_and_codename in
let semanticVersion = Config.version in
let fullName = name ^ "-" ^ version in
let downloadUri = "https://frama-c.com/download.html" in
Tool.create
~name ~version ~semanticVersion ~fullName ~downloadUri ()
Virgile Prevosto
committed
let get_remarks () =
let f = Mdr_params.Remarks.get () in
if f <> "" then Parse_remarks.get_remarks f
else Datatype.String.Map.empty
Virgile Prevosto
committed
let get_remark remarks label =
match Datatype.String.Map.find_opt label remarks with
| None -> []
| Some l -> l
let command_line () = Array.to_list Sys.argv
module Analysis_cmdline =
State_builder.Ref(Datatype.List(Datatype.String))
(struct
let name = "Sarif_gen.Analysis_cmdline"
let dependencies = []
let default = command_line
end)
let cl = Analysis_cmdline.get () in
let commandLine = String.concat " " cl in
let arguments = List.tl cl in
Invocation.create ~commandLine ~arguments ()
let gen_remark alarm =
let open Markdown in
[ Block
[ Text
(plain
(Printf.sprintf "This alarms represents a potential %s."
(Alarms.get_description alarm)
)
)
]
]
let level_of_status =
let open Property_status.Feedback in
let open Sarif.Result_level in
function
| Never_tried -> notApplicable
| Considered_valid | Valid | Valid_under_hyp | Valid_but_dead -> pass
| Unknown | Unknown_but_dead -> warning
| Invalid | Invalid_under_hyp | Invalid_but_dead -> error
| Inconsistent -> note
Virgile Prevosto
committed
let make_message alarm annot remark =
let open Markdown in
let name = Alarms.get_name alarm in
let text = name ^ "." in
let kind = plain (name ^ ":") in
let descr = codeblock ~lang:"acsl" "%a" Printer.pp_code_annotation annot in
let summary = Block (Text kind :: descr) in
let markdown =
match remark with
| [] -> summary :: gen_remark alarm
| _ -> summary :: remark
Virgile Prevosto
committed
let richText =
String.trim
(Format.asprintf "@[%a@]" (Markdown.pp_elements ~page:"") markdown)
Virgile Prevosto
committed
in
Message.create ~text ~richText ()
Virgile Prevosto
committed
let gen_results remarks =
Virgile Prevosto
committed
let treat_alarm _e kf s ~rank:_ alarm annot (i, rules, content) =
let prop = Property.ip_of_code_annot_single kf s annot in
Virgile Prevosto
committed
let ruleId = Alarms.get_name alarm in
let rules =
Datatype.String.Map.add ruleId (Alarms.get_description alarm) rules
in
Virgile Prevosto
committed
let label = "Alarm-" ^ string_of_int i in
let level = level_of_status (Property_status.Feedback.get prop) in
Virgile Prevosto
committed
let remark = get_remark remarks label in
let message = make_message alarm annot remark in
let locations = [ Location.of_loc (Cil_datatype.Stmt.loc s) ] in
let res =
Virgile Prevosto
committed
Sarif_result.create ~level ~ruleId ~message ~locations ()
Virgile Prevosto
committed
in
Virgile Prevosto
committed
(i+1, rules, res :: content)
in
let _, rules, content =
Alarms.fold treat_alarm (0, Datatype.String.Map.empty,[])
Virgile Prevosto
committed
in
Virgile Prevosto
committed
rules, List.rev content
Virgile Prevosto
committed
let is_alarm = function
| Property.(IPCodeAnnot { ica_ca }) -> Extlib.has_some (Alarms.find ica_ca)
| _ -> false
let make_ip_message ip =
let text = Format.asprintf "@[%a.@]" Property.short_pretty ip in
Message.plain_text ~text ()
let gen_status ip =
let status = Property_status.Feedback.get ip in
let level = level_of_status status in
let locations = [ Location.of_loc (Property.location ip) ] in
let message = make_ip_message ip in
Sarif_result.create ~level ~locations ~message ()
let gen_statuses () =
let f ip content =
if is_alarm ip then content else (gen_status ip) :: content
in
List.rev (Property_status.fold f [])
let gen_files () =
let add_src_file f =
let key = Filename.chop_extension (Filename.basename f) in
let fileLocation = FileLocation.create ~uri:(Filepath.normalize f) () in
let roles = [ Role.analysisTarget ] in
let mimeType = "text/x-csrc" in
key, File.create ~fileLocation ~roles ~mimeType ()
in
List.map add_src_file (Kernel.Files.get ())
Virgile Prevosto
committed
let add_rule id desc l =
let text = desc ^ "." in
let shortDescription = Message.plain_text ~text () in
let rule = Rule.create ~id ~shortDescription () in
(id, rule) :: l
let make_rule_dictionary rules = Datatype.String.Map.fold add_rule rules []
Virgile Prevosto
committed
let gen_run remarks =
let tool = frama_c_sarif in
let invocations = [gen_invocation ()] in
let rules, results = gen_results remarks in
let user_annot_results = gen_statuses () in
let rules =
match user_annot_results with
| [] -> rules
| _ ->
Datatype.String.Map.add
"user-spec" "User written ACSL specification" rules
in
let rules = make_rule_dictionary rules in
Virgile Prevosto
committed
let resources = Resources.create ~rules () in
let results = results @ user_annot_results in
let files = gen_files () in
Virgile Prevosto
committed
Run.create ~tool ~invocations ~results ~resources ~files ()
Virgile Prevosto
committed
let remarks = get_remarks () in
let runs = [ gen_run remarks ] in
let json = Schema.create ~runs () |> Schema.to_yojson in
let file = Mdr_params.Output.get () in
if file = "" then
Log.print_on_output (fun fmt -> Yojson.Safe.pretty_print fmt json)
else
try
Command.write_file file
(fun out -> Yojson.Safe.pretty_to_channel ~std:true out json) ;
Mdr_params.result "Report %s generated" file
with Sys_error s ->
Mdr_params.abort "Unable to generate %s (%s)" file s