sarif_gen.ml 9.5 KB
Newer Older
1
2
3
4
(**************************************************************************)
(*                                                                        *)
(*  This file is part of Frama-C.                                         *)
(*                                                                        *)
Andre Maroneze's avatar
Andre Maroneze committed
5
(*  Copyright (C) 2007-2020                                               *)
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
(*    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).            *)
(*                                                                        *)
(**************************************************************************)

23
24
open Sarif

25
let frama_c_sarif () =
26
  let name = "frama-c" in
27
28
  let version, semanticVersion =
    if Mdr_params.SarifDeterministic.get () then
29
      "0+omitted-for-deterministic-output", ""
30
31
32
    else
      Fc_config.version_and_codename, Fc_config.version
  in
33
34
  let fullName = name ^ "-" ^ version in
  let downloadUri = "https://frama-c.com/download.html" in
35
  let informationUri = "https://frama-c.com" in
36
  Tool.create
37
38
    (Driver.create ~name ~version ~semanticVersion ~fullName ~downloadUri
       ~informationUri ())
39
40
41
42
43
44

let get_remarks () =
  let f = Mdr_params.Remarks.get () in
  if f <> "" then Parse_remarks.get_remarks f
  else Datatype.String.Map.empty

45
46
47
48
49
let get_remark remarks label =
  match Datatype.String.Map.find_opt label remarks with
  | None -> []
  | Some l -> l

50
51
52
53
(* keep track of command line arguments for all invocations of Frama-C during
   a save/load sequence. Note that the list is in reverse order
   (newest invocation first).
*)
54
module Analysis_cmdline =
55
  State_builder.List_ref(Datatype.List(Datatype.String))
56
57
58
59
60
    (struct
      let name = "Sarif_gen.Analysis_cmdline"
      let dependencies = []
    end)

61
62
63
64
65
66
let command_line () = Array.to_list Sys.argv

let update_cmdline () = Analysis_cmdline.add (command_line())

let () = Cmdline.run_after_loading_stage update_cmdline

67
let gen_invocation () =
68
69
70
71
72
73
74
75
76
  let cls = Analysis_cmdline.get () in
  let gen_one cl =
    (* The first argument is _always_ the binary name, but to avoid printing it
       as an absolute path to binlevel.opt, we replace it with 'frama-c' *)
    let cl = "frama-c" :: List.tl cl in
    let commandLine = String.concat " " cl in
    let arguments = List.tl cl in
    Invocation.create ~commandLine ~arguments ()
  in
77
  List.rev_map gen_one cls
78

79
80
81
82
83
let gen_remark alarm =
  let open Markdown in
  [ Block
      [ Text
          (plain
84
             (Printf.sprintf "This alarm represents a potential %s."
85
86
87
88
89
90
                (Alarms.get_description alarm)
             )
          )
      ]
  ]

Andre Maroneze's avatar
Andre Maroneze committed
91
let kind_of_status =
92
  let open Property_status.Feedback in
Andre Maroneze's avatar
Andre Maroneze committed
93
  let open Sarif.Result_kind in
94
95
96
  function
  | Never_tried -> notApplicable
  | Considered_valid | Valid | Valid_under_hyp | Valid_but_dead -> pass
Andre Maroneze's avatar
Andre Maroneze committed
97
98
99
100
101
102
103
104
105
106
107
  | Unknown | Unknown_but_dead -> open_
  | Invalid | Invalid_under_hyp | Invalid_but_dead -> fail
  | Inconsistent -> review

let level_of_status =
  let open Property_status.Feedback in
  let open Sarif.Result_level in
  function
  | Never_tried -> none
  | Considered_valid | Valid | Valid_under_hyp | Valid_but_dead -> none
  | Unknown | Unknown_but_dead -> none
108
  | Invalid | Invalid_under_hyp | Invalid_but_dead -> error
Andre Maroneze's avatar
Andre Maroneze committed
109
  | Inconsistent -> none
110

111
112
let make_message alarm annot remark =
  let open Markdown in
113
114
115
  let name = Alarms.get_name alarm in
  let text = name ^ "." in
  let kind = plain (name ^ ":") in
116
  let descr = codeblock ~lang:"acsl" "%a" Printer.pp_code_annotation annot in
117
  let summary = Block (Text kind :: descr) in
118
119
  let markdown =
    match remark with
Virgile Prevosto's avatar
Virgile Prevosto committed
120
121
    | [] -> summary :: gen_remark alarm
    | _ -> summary :: remark
122
  in
123
  let markdown =
124
125
    String.trim
      (Format.asprintf "@[%a@]" (Markdown.pp_elements ~page:"") markdown)
126
  in
127
  Message.create ~text ~markdown ()
128

129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
let kf_is_in_libc kf =
  let g = Kernel_function.get_global kf in
  Cil.hasAttribute "fc_stdlib" (Cil_datatype.Global.attr g)

let ip_is_in_libc ip =
  match Property.get_kf ip with
  | None ->
    (* possibly an identified lemma; check its attributes *)
    begin
      match ip with
      | IPAxiomatic {iax_attrs=attrs}
      | IPLemma {il_attrs=attrs}
      | IPAxiom {il_attrs=attrs} ->
        Cil.hasAttribute "fc_stdlib" attrs
      | _ ->
        false
    end
  | Some kf ->
    kf_is_in_libc kf

149
150
151
let opt_physical_location_of_loc loc =
  if loc = Cil_datatype.Location.unknown then []
  else [ Location.of_loc loc ]
152
(* Cil_types *)
153
let gen_results remarks =
154
  let treat_alarm _e kf s ~rank:_ alarm annot (i, rules, content) =
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
    if not (Mdr_params.PrintLibc.get ()) && kf_is_in_libc kf then
      (* skip alarm in libc *)
      (i, rules, content)
    else
      let prop = Property.ip_of_code_annot_single kf s annot in
      let ruleId = Alarms.get_name alarm in
      let rules =
        Datatype.String.Map.add ruleId (Alarms.get_description alarm) rules
      in
      let label = "Alarm-" ^ string_of_int i in
      let kind = kind_of_status (Property_status.Feedback.get prop) in
      let level = level_of_status (Property_status.Feedback.get prop) in
      let remark = get_remark remarks label in
      let message = make_message alarm annot remark in
      let locations = opt_physical_location_of_loc (Cil_datatype.Stmt.loc s) in
      let res =
        Sarif_result.create ~kind ~level ~ruleId ~message ~locations ()
      in
      (i+1, rules, res :: content)
174
175
176
  in
  let _, rules, content =
    Alarms.fold treat_alarm (0, Datatype.String.Map.empty,[])
177
  in
178
  rules, List.rev content
179

180
let is_alarm = function
181
  | Property.(IPCodeAnnot { ica_ca }) -> Extlib.has_some (Alarms.find ica_ca)
182
183
184
  | _ -> false

let make_ip_message ip =
185
  let text = Format.asprintf "@[%a.@]" Property.short_pretty ip in
186
187
  Message.plain_text ~text ()

188
189
let user_annot_id = "user-spec"

190
191
192
let gen_status ip =
  let status = Property_status.Feedback.get ip in
  let level = level_of_status status in
193
  let locations = opt_physical_location_of_loc (Property.location ip) in
194
  let message = make_ip_message ip in
195
  Sarif_result.create ~ruleId:user_annot_id ~level ~locations ~message ()
196
197
198

let gen_statuses () =
  let f ip content =
199
200
201
202
203
    let exclude =
      is_alarm ip ||
      (not (Mdr_params.PrintLibc.get ()) && ip_is_in_libc ip)
    in
    if exclude then content else (gen_status ip) :: content
204
205
206
  in
  List.rev (Property_status.fold f [])

207
let gen_artifacts () =
208
  let add_src_file f =
209
210
    let uriBaseId, uri = Filepath.Normalized.to_base_uri f in
    let location = ArtifactLocation.create ~uri ?uriBaseId () in
211
212
    let roles = [ Role.analysisTarget ] in
    let mimeType = "text/x-csrc" in
213
    Artifact.create ~location ~roles ~mimeType ()
214
215
216
  in
  List.map add_src_file (Kernel.Files.get ())

217
218
let add_rule id desc l =
  let text = desc ^ "." in
219
220
221
  let shortDescription = MultiformatMessageString.create ~text () in
  let rule = ReportingDescriptor.create ~id ~shortDescription () in
  rule :: l
222

223
let make_taxonomies rules = Datatype.String.Map.fold add_rule rules []
224

225
let gen_run remarks =
226
  let tool = frama_c_sarif () in
227
  let name = "frama-c" in
228
  let invocations = gen_invocation () in
Virgile Prevosto's avatar
Virgile Prevosto committed
229
230
231
232
  let rules, results = gen_results remarks in
  let user_annot_results = gen_statuses () in
  let rules =
    match user_annot_results with
Virgile Prevosto's avatar
Virgile Prevosto committed
233
234
235
    | [] -> rules
    | _ ->
      Datatype.String.Map.add
236
        user_annot_id "User-written ACSL specification" rules
Virgile Prevosto's avatar
Virgile Prevosto committed
237
  in
238
  let rules = make_taxonomies rules in
239
  let taxonomies = [ToolComponent.create ~name ~rules ()] in
Virgile Prevosto's avatar
Virgile Prevosto committed
240
  let results = results @ user_annot_results in
241
  let artifacts = gen_artifacts () in
242
243
244
  let uriBases = ("PWD", Sys.getcwd ()) :: Filepath.all_symbolic_dirs () in
  let uriBasesJson =
    List.fold_left (fun acc (name, dir) ->
245
246
        let baseUri =
          if Mdr_params.SarifDeterministic.get () then
247
            "file:///omitted-for-deterministic-output/"
248
249
250
          else  "file://" ^ dir ^ "/"
        in
        (name, `Assoc [("uri", `String baseUri)]) :: acc
251
252
253
254
255
256
257
258
259
      ) [] uriBases
  in
  let originalUriBaseIds =
    match ArtifactLocationDictionary.of_yojson (`Assoc uriBasesJson) with
    | Ok x -> x
    | Error s -> failwith s
  in
  Run.create ~tool ~invocations ~results ~taxonomies ~artifacts
    ~originalUriBaseIds ()
260
261

let generate () =
262
263
  let remarks = get_remarks () in
  let runs = [ gen_run remarks ] in
264
265
266
267
268
269
270
271
272
273
274
  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