diff --git a/src/plugins/markdown-report/mdr_params.ml b/src/plugins/markdown-report/mdr_params.ml index aa1f44492edcbf6bf96b42274c482d762f36f279..b63ea6261a1ee48bd6a1d7032e58a6b43dba795e 100644 --- a/src/plugins/markdown-report/mdr_params.ml +++ b/src/plugins/markdown-report/mdr_params.ml @@ -20,6 +20,8 @@ (* *) (**************************************************************************) +module Pervasives_string = String + include Plugin.Register( struct let name = "Markdown report" @@ -27,14 +29,6 @@ include Plugin.Register( let help = "generates a report in markdown format" end) -module Output = String( - struct - let option_name = "-mdr-out" - let arg_name = "f" - let default = "report.md" - let help = "sets the name of the output file to <f>" - end) - module Generate = String( struct let option_name = "-mdr-gen" @@ -45,6 +39,26 @@ module Generate = String( none (default), md, draft and sarif" end) +module Output : Parameter_sig.String = +struct + include String( + struct + let option_name = "-mdr-out" + let arg_name = "f" + let default = "report" + let help = "sets the name of the output file to <f>.@ \ + If <f> has no extension, it is chosen automatically based on \ + the report kind" + end) + let get () = + let s = get () in + if Pervasives_string.contains (Filename.basename s) '.' then s + else + let kind = Generate.get () in + let ext = if kind = "sarif" then ".sarif" else ".md" in + s ^ ext +end + let () = Generate.set_possible_values [ "none"; "md"; "draft"; "sarif" ] diff --git a/src/plugins/markdown-report/sarif.ml b/src/plugins/markdown-report/sarif.ml index 00977f9a56c77251ec178b290ee62451f9c83358..e50d9262a43d86b80a48ebdccb28b14f4bc8a12c 100644 --- a/src/plugins/markdown-report/sarif.ml +++ b/src/plugins/markdown-report/sarif.ml @@ -20,7 +20,7 @@ (* *) (**************************************************************************) -(** OCaml representation for the sarif 2.0 schema. *) +(** OCaml representation for the sarif 2.1 schema. *) (** ppx_deriving_yojson generates parser and printer that are recursive by default: we must thus silence spurious let rec warning (39). *) @@ -32,6 +32,15 @@ module type Json_type = sig val to_yojson: t -> Yojson.Safe.t end +module Json_string: Json_type with type t = string = +struct + type t = string + let of_yojson = function + | `String s -> Ok s + | _ -> Error "string" + let to_yojson s = `String s +end + module Json_dictionary(J: Json_type): Json_type with type t = (string * J.t) list = struct @@ -55,6 +64,8 @@ struct `Assoc json_l end +module JsonStringDictionary = Json_dictionary(Json_string) + module Uri: sig include Json_type with type t = private string val sarif_github:t @@ -63,74 +74,141 @@ end struct type t = string[@@deriving yojson] let sarif_github = - "https://github.com/oasis-tcs/sarif-spec/blob/master/Documents/CommitteeSpecificationDrafts/v2.0-CSD.1/sarif-schema.json" + "https://github.com/oasis-tcs/sarif-spec/blob/master/Documents/CommitteeSpecificationDrafts/v2.1.0-CSD.1/sarif-schema-2.1.0.json" end module Version: sig include Json_type with type t = private string - val v2_0_0: t + val v2_1_0: t end = struct type t = string[@@deriving yojson] - let v2_0_0 = "2.0.0" + let v2_1_0 = "2.1.0" +end + +module ArtifactLocation = struct + type t = { + uri: string; + uriBaseId: (string [@default ""]) + }[@@deriving yojson] + + let create ~uri ?(uriBaseId = "") () = { uri; uriBaseId } + + let default = create ~uri:"" () + + let of_loc loc = + let open Filepath in + (* by construction, we have an absolute path here, no need for uriBase *) + let uri = ((fst loc).pos_path :> string) in + create ~uri () +end + +module ArtifactLocationDictionary = Json_dictionary(ArtifactLocation) + +module Custom_properties = + Json_dictionary(struct + type t = Yojson.Safe.t + let of_yojson x = Ok x + let to_yojson x = x + end) + +module Properties = struct + type tags = string list [@@deriving yojson] + + type t = { + tags: tags; + additional_properties: Custom_properties.t + } + + let default = { tags = []; additional_properties = [] } + + let create additional_properties = + let tags = List.map fst additional_properties in + { tags; additional_properties } + + let of_yojson = function + | `Null -> Ok default + | `Assoc l -> + (match List.assoc_opt "tags" l with + | None -> Error "properties" + | Some json -> + (match tags_of_yojson json with + | Ok tags -> + let additional_properties = List.remove_assoc "tags" l in + Ok { tags; additional_properties } + | Error loc -> Error ("properties." ^ loc))) + | _ -> Error "properties" + + let to_yojson { tags; additional_properties } = + match tags with + | [] -> `Null + | _ -> `Assoc (("tags", tags_to_yojson tags)::additional_properties) end module Message = struct type t = { text: (string [@default ""]); - messageId: (string [@default ""]); - richText: (string [@default ""]); - richMessageId: (string [@default ""]); + id: (string [@default ""]); + markdown: (string [@default ""]); arguments: (string list [@default []]); + properties: (Properties.t [@default Properties.default]); }[@@deriving yojson] let create ?(text="") - ?(messageId="") - ?(richText="") - ?(richMessageId="") + ?(id="") + ?(markdown="") ?(arguments=[]) + ?(properties=Properties.default) () = - { text; messageId; richText; richMessageId; arguments } + { text; id; markdown; arguments; properties } - let plain_text ~text ?id:messageId ?arguments () = - create ~text ?messageId ?arguments () + let plain_text ~text ?id ?arguments () = + create ~text ?id ?arguments () - let markdown ~markdown ?id:richMessageId ?arguments () = + let markdown ~markdown ?id ?arguments () = let pp fmt = Markdown.pp_elements fmt in - let richText = String.trim (Format.asprintf "@[%a@]" pp markdown) + let markdown = String.trim (Format.asprintf "@[%a@]" pp markdown) in - create ~richText ?richMessageId ?arguments () + create ~markdown ?id ?arguments () let default = create () end -module FileLocation = struct +module MultiformatMessageString = struct type t = { - uri: string; - uriBaseId: (string [@default ""]) + text: string; + markdown: (string [@default ""]); + properties: (Properties.t [@default Properties.default]) }[@@deriving yojson] - let create ~uri ?(uriBaseId = "") () = { uri; uriBaseId } + let create ~text ?(markdown="") ?(properties=Properties.default) () = + { text; markdown; properties } - let default = create ~uri:"" () - - let of_loc loc = - let open Filepath in - (* by construction, we have an absolute path here, no need for uriBase *) - let uri = ((fst loc).pos_path :> string) in - create ~uri () + let default = create ~text:"default" () end -module FileContent = struct +module MultiformatMessageStringDictionary = + Json_dictionary(MultiformatMessageString) + +module ArtifactContent = struct type t = - | Text of string [@name "text"] - | Binary of string [@name "binary"] + { text: (string [@default ""]); + binary: (string [@default ""]); + rendered: + (MultiformatMessageString.t [@default MultiformatMessageString.default]); + properties: (Properties.t [@default Properties.default]) + } [@@deriving yojson] - let default = Text "" + let create ?(text="") ?(binary="") + ?(rendered=MultiformatMessageString.default) + ?(properties=Properties.default) () = + { text; binary; rendered; properties } + + let default = create () end module Region = struct @@ -143,7 +221,7 @@ module Region = struct charLength: (int [@default 0]); byteOffset: (int [@default 0]); byteLength: (int [@default 0]); - snippet: (FileContent.t [@default FileContent.default]); + snippet: (ArtifactContent.t [@default ArtifactContent.default]); message: (Message.t [@default Message.default]) }[@@deriving yojson] @@ -156,7 +234,7 @@ module Region = struct ?(charLength = 0) ?(byteOffset = 0) ?(byteLength = 0) - ?(snippet = FileContent.default) + ?(snippet = ArtifactContent.default) ?(message = Message.default) () = @@ -187,69 +265,29 @@ module Rectangle = struct [@@deriving yojson] end -module Custom_properties = - Json_dictionary(struct - type t = Yojson.Safe.t - let of_yojson x = Ok x - let to_yojson x = x - end) - -module Properties = struct - type tags = string list [@@deriving yojson] - - type t = { - tags: tags; - additional_properties: Custom_properties.t - } - - let default = { tags = []; additional_properties = [] } - - let create additional_properties = - let tags = List.map fst additional_properties in - { tags; additional_properties } - - let of_yojson = function - | `Null -> Ok default - | `Assoc l -> - (match List.assoc_opt "tags" l with - | None -> Error "properties" - | Some json -> - (match tags_of_yojson json with - | Ok tags -> - let additional_properties = List.remove_assoc "tags" l in - Ok { tags; additional_properties } - | Error loc -> Error ("properties." ^ loc))) - | _ -> Error "properties" - - let to_yojson { tags; additional_properties } = - match tags with - | [] -> `Null - | _ -> `Assoc (("tags", tags_to_yojson tags)::additional_properties) -end - module PhysicalLocation = struct type t = { id: (string [@default ""]); - fileLocation: FileLocation.t; + artifactLocation: ArtifactLocation.t; region: (Region.t [@default Region.default]); contextRegion: (Region.t [@default Region.default]); }[@@deriving yojson] let create ?(id = "") - ~fileLocation + ~artifactLocation ?(region = Region.default) ?(contextRegion = Region.default) () = - { id; fileLocation; region; contextRegion } + { id; artifactLocation; region; contextRegion } - let default = create ~fileLocation:FileLocation.default () + let default = create ~artifactLocation:ArtifactLocation.default () let of_loc loc = - let fileLocation = FileLocation.of_loc loc in + let artifactLocation = ArtifactLocation.of_loc loc in let region = Region.of_loc loc in - create ~fileLocation ~region () + create ~artifactLocation ~region () end @@ -356,7 +394,7 @@ end module Attachment = struct type t = { description: (Message.t [@default Message.default ]); - fileLocation: FileLocation.t; + artifactLocation: ArtifactLocation.t; regions: (Region.t list [@default []]); rectangles: (Rectangle.t list [@default []]) } [@@deriving yojson] @@ -416,7 +454,7 @@ module Notification = struct }[@@deriving yojson] end -module Tool = struct +module Driver = struct type t = { name: string; fullName: (string [@default ""]); @@ -445,7 +483,16 @@ module Tool = struct downloadUri; sarifLoggerVersion; language; properties } let default = create ~name:"" () +end + +module Tool = struct + type t = { + driver: Driver.t + }[@@deriving yojson] + let create driver = { driver; } + + let default = create Driver.default end module Invocation = struct @@ -453,7 +500,7 @@ module Invocation = struct type t = { commandLine: string; arguments: string list; - responseFiles: (FileLocation.t list [@default []]); + responseFiles: (ArtifactLocation.t list [@default []]); attachments: (Attachment.t list [@default []]); startTime: (string [@default ""]); endTime: (string [@default ""]); @@ -464,18 +511,18 @@ module Invocation = struct exitSignalName: (string [@default ""]); exitSignalNumber: (int [@default 0]); processStartFailureMessage: (string [@default ""]); - toolExecutionSuccessful: bool; + executionSuccessful: bool; machine: (string [@default ""]); account: (string [@default ""]); processId: (int [@default 0]); - executableLocation: (FileLocation.t [@default FileLocation.default]); - workingDirectory: (FileLocation.t [@default FileLocation.default]); + executableLocation: (ArtifactLocation.t [@default ArtifactLocation.default]); + workingDirectory: (ArtifactLocation.t [@default ArtifactLocation.default]); environmentVariables: (Additional_properties.t [@default Additional_properties.default]); - stdin: (FileLocation.t [@default FileLocation.default]); - stdout: (FileLocation.t [@default FileLocation.default]); - stderr: (FileLocation.t [@default FileLocation.default]); - stdoutStderr: (FileLocation.t [@default FileLocation.default]); + stdin: (ArtifactLocation.t [@default ArtifactLocation.default]); + stdout: (ArtifactLocation.t [@default ArtifactLocation.default]); + stderr: (ArtifactLocation.t [@default ArtifactLocation.default]); + stdoutStderr: (ArtifactLocation.t [@default ArtifactLocation.default]); properties: (Properties.t [@default Properties.default]); }[@@deriving yojson] @@ -493,17 +540,17 @@ module Invocation = struct ?(exitSignalName = "") ?(exitSignalNumber = 0) ?(processStartFailureMessage = "") - ?(toolExecutionSuccessful = true) + ?(executionSuccessful = true) ?(machine = "") ?(account = "") ?(processId = 0) - ?(executableLocation = FileLocation.default) - ?(workingDirectory = FileLocation.default) + ?(executableLocation = ArtifactLocation.default) + ?(workingDirectory = ArtifactLocation.default) ?(environmentVariables = Additional_properties.default) - ?(stdin = FileLocation.default) - ?(stdout = FileLocation.default) - ?(stderr = FileLocation.default) - ?(stdoutStderr = FileLocation.default) + ?(stdin = ArtifactLocation.default) + ?(stdout = ArtifactLocation.default) + ?(stderr = ArtifactLocation.default) + ?(stdoutStderr = ArtifactLocation.default) ?(properties = Properties.default) () = @@ -521,7 +568,7 @@ module Invocation = struct exitSignalName; exitSignalNumber; processStartFailureMessage; - toolExecutionSuccessful; + executionSuccessful; machine; account; processId; @@ -543,13 +590,13 @@ module Conversion = struct type t = { tool: Tool.t; invocation: (Invocation.t [@default Invocation.default]); - analysisToolLogFiles: (FileLocation.t [@default FileLocation.default]); + analysisToolLogFiles: (ArtifactLocation.t [@default ArtifactLocation.default]); } [@@deriving yojson] let default = { - tool = Tool.default; + tool = {driver = Driver.default}; invocation = Invocation.default; - analysisToolLogFiles = FileLocation.default; + analysisToolLogFiles = ArtifactLocation.default; } end @@ -650,48 +697,53 @@ end module Replacement = struct type t = { deletedRegion: Region.t; - insertedContent: (FileContent.t [@default FileContent.default]) + insertedContent: (ArtifactContent.t [@default ArtifactContent.default]) }[@@deriving yojson] end -module File = struct +module Artifact = struct type t = { - fileLocation: (FileLocation.t [@default FileLocation.default]); - parentKey: (string [@default ""]); + description: (Message.t [@default Message.default]); + location: (ArtifactLocation.t [@default ArtifactLocation.default]); + parentIndex: (int [@default -1]); offset: (int [@default 0]); - length: (int [@default 0]); + length: (int [@default -1]); roles: (Role.t list [@default []]); mimeType: (string [@default ""]); - contents: (FileContent.t [@default FileContent.default]); + contents: (ArtifactContent.t [@default ArtifactContent.default]); encoding: (string [@default ""]); - hashes: (Hash.t list [@default []]); - lastModifiedTime: (string [@default ""]); + sourceLanguage: (string [@default ""]); + hashes: (JsonStringDictionary.t [@default []]); + lastModifiedTimeUtc: (string [@default ""]); properties: (Properties.t [@default Properties.default]); }[@@deriving yojson] let create - ?(fileLocation = FileLocation.default) - ?(parentKey = "") + ?(description = Message.default) + ?(location = ArtifactLocation.default) + ?(parentIndex = -1) ?(offset = 0) - ?(length = 0) + ?(length = -1) ?(roles = []) ?(mimeType = "") - ?(contents = FileContent.default) + ?(contents = ArtifactContent.default) ?(encoding = "") + ?(sourceLanguage = "") ?(hashes = []) - ?(lastModifiedTime = "") + ?(lastModifiedTimeUtc = "") ?(properties = Properties.default) () = { - fileLocation; parentKey; offset; length; roles; mimeType; contents; - encoding; hashes; lastModifiedTime; properties + description; location; parentIndex; offset; length; roles; mimeType; + contents; encoding; sourceLanguage; hashes; lastModifiedTimeUtc; + properties } end module FileChange = struct type t = { - fileLocation: FileLocation.t; + artifactLocation: ArtifactLocation.t; replacements: Replacement.t list }[@@deriving yojson] end @@ -705,13 +757,13 @@ end module ExternalFiles = struct type t = { - conversion: (FileLocation.t [@default FileLocation.default]); - files: (FileLocation.t [@default FileLocation.default]); - graphs: (FileLocation.t [@default FileLocation.default]); - invocations: (FileLocation.t list [@default []]); - logicalLocations: (FileLocation.t [@default FileLocation.default]); - resources: (FileLocation.t [@default FileLocation.default]); - results: (FileLocation.t [@default FileLocation.default]); + conversion: (ArtifactLocation.t [@default ArtifactLocation.default]); + files: (ArtifactLocation.t [@default ArtifactLocation.default]); + graphs: (ArtifactLocation.t [@default ArtifactLocation.default]); + invocations: (ArtifactLocation.t list [@default []]); + logicalLocations: (ArtifactLocation.t [@default ArtifactLocation.default]); + resources: (ArtifactLocation.t [@default ArtifactLocation.default]); + results: (ArtifactLocation.t [@default ArtifactLocation.default]); }[@@deriving yojson] end @@ -728,104 +780,169 @@ end module RuleConfigLevel: sig include Json_type with type t = private string + val cl_none: t val cl_note: t val cl_warning: t val cl_error: t - val cl_open: t end = struct type t = string [@@deriving yojson] + let cl_none = "none" let cl_note = "note" let cl_warning = "warning" let cl_error = "error" - let cl_open = "open" end -module RuleConfiguration = struct +module ReportingConfiguration = struct type t = { enabled: (bool [@default false]); - defaultLevel: (RuleConfigLevel.t [@default RuleConfigLevel.cl_open]); - parameters: (Properties.t [@default Properties.default]) + defaultLevel: (RuleConfigLevel.t [@default RuleConfigLevel.cl_none]); + rank: (int [@default -1]); + parameters: (Properties.t [@default Properties.default]); + properties: (Properties.t [@default Properties.default]); }[@@deriving yojson] let default = { enabled = false; - defaultLevel = RuleConfigLevel.cl_open; + defaultLevel = RuleConfigLevel.cl_none; + rank = -1; parameters = Properties.default; + properties = Properties.default; } end -module Rule = struct +module ToolComponentReference =struct type t = { - id: (string [@default ""]); name: (string [@default ""]); - shortDescription: (Message.t [@default Message.default]); - fullDescription: (Message.t [@default Message.default]); - messageStrings: - (Additional_properties.t [@default Additional_properties.default]); - richMessageStrings: - (Additional_properties.t [@default Additional_properties.default]); - configuration: (RuleConfiguration.t [@default RuleConfiguration.default]); - helpUri: (string [@default ""]); + index: (int [@default -1]); + guid: (string [@default ""]); properties: (Properties.t [@default Properties.default]); }[@@deriving yojson] - let default = { - id = ""; - name = ""; - shortDescription = Message.default; - fullDescription = Message.default; - messageStrings = Additional_properties.default; - richMessageStrings = Additional_properties.default; - configuration = RuleConfiguration.default; - helpUri = ""; - properties = Properties.default; - } + let create + ?(name="") ?(index = -1) ?(guid = "") ?(properties=Properties.default) () = + { name; index; guid; properties } + + let default = create () + +end + +module ReportingDescriptorReference = +struct + type t = { + id: (string [@default ""]); + index: (int [@default -1]); + guid: (string [@default ""]); + toolComponent: + (ToolComponentReference.t [@default ToolComponentReference.default]); + properties: (Properties.t [@default Properties.default]); + }[@@deriving yojson] let create - ~id - ?(name="") - ?(shortDescription=Message.default) - ?(fullDescription=Message.default) - ?(messageStrings=Additional_properties.default) - ?(richMessageStrings=Additional_properties.default) - ?(configuration=RuleConfiguration.default) - ?(helpUri="") - ?(properties=Properties.default) - () - = - { id; name; shortDescription; fullDescription; messageStrings; - richMessageStrings; configuration; helpUri; properties } + ?(id="") ?(index = -1) ?(guid="") + ?(toolComponent=ToolComponentReference.default) + ?(properties=Properties.default) () = + { id; index; guid; toolComponent; properties } + let default = create () end -module Rule_dictionary = Json_dictionary(Rule) +module ReportingDescriptorRelationship = struct + type t = { + target: ReportingDescriptorReference.t; + kinds: (string list [@default ["relevant"]]); + description: (Message.t [@default Message.default]); + properties: (Properties.t [@default Properties.default]); + }[@@deriving yojson] -module Resources = struct + let create + ~target + ?(kinds=["relevant"]) + ?(description=Message.default) + ?(properties=Properties.default) () = + { target; kinds; description; properties } + + let default = create ~target:ReportingDescriptorReference.default () +end + +module ReportingDescriptor = struct type t = { + id: string; + deprecatedIds: (string list [@default []]); + guid: (string [@default ""]); + deprecatedGuids: (string list [@default []]); + name: (string [@default ""]); + deprecatedNames: (string list [@default []]); + shortDescription: + (MultiformatMessageString.t [@default MultiformatMessageString.default]); + fullDescription: + (MultiformatMessageString.t [@default MultiformatMessageString.default]); messageStrings: - (Additional_properties.t [@default Additional_properties.default]); - rules: (Rule_dictionary.t [@default []]); + (MultiformatMessageStringDictionary.t [@default []]); + defaultConfiguration: + (ReportingConfiguration.t [@default ReportingConfiguration.default]); + helpUri: (string [@default ""]); + help: + (MultiformatMessageString.t [@default MultiformatMessageString.default]); + relationships: + (ReportingDescriptorRelationship.t list [@default []]); + properties: (Properties.t [@default Properties.default]); }[@@deriving yojson] - let default = { - messageStrings = Additional_properties.default; - rules = [] } - let create - ?(messageStrings=Additional_properties.default) - ?(rules=[]) + ~id + ?(deprecatedIds=[]) + ?(guid="") + ?(deprecatedGuids=[]) + ?(name="") + ?(deprecatedNames=[]) + ?(shortDescription=MultiformatMessageString.default) + ?(fullDescription=MultiformatMessageString.default) + ?(messageStrings=[]) + ?(defaultConfiguration=ReportingConfiguration.default) + ?(helpUri="") + ?(help=MultiformatMessageString.default) + ?(relationships=[]) + ?(properties=Properties.default) () = - { messageStrings; rules } + { id; deprecatedIds; guid; deprecatedGuids; name; deprecatedNames; + shortDescription; fullDescription; messageStrings; + defaultConfiguration; helpUri; help; relationships; properties } + + let default = create ~id:"id" () + end -module Result_level: +module Result_kind: sig type t = private string val notApplicable: t val pass: t + val fail: t + val review: t + val open_: t + val informational: t + + val to_yojson: t -> Yojson.Safe.t + val of_yojson: Yojson.Safe.t -> (t,string) result +end += +struct + type t = string[@@deriving yojson] + let notApplicable = "notApplicable" + let pass = "pass" + let fail = "fail" + let review = "review" + let open_ = "open" + let informational = "informational" +end + +module Result_level: +sig + type t = private string + val none: t val note: t val warning: t val error: t @@ -836,8 +953,7 @@ end = struct type t = string[@@deriving yojson] - let notApplicable = "notApplicable" - let pass = "pass" + let none = "none" let note = "note" let warning = "warning" let error = "error" @@ -875,9 +991,10 @@ end module Sarif_result = struct type t = { ruleId: (string [@default ""]); - level: (Result_level.t[@default Result_level.notApplicable]); + kind: (Result_kind.t[@default Result_kind.fail]); + level: (Result_level.t[@default Result_level.warning]); message: (Message.t [@default Message.default]); - analysisTarget: (FileLocation.t [@default FileLocation.default]); + analysisTarget: (ArtifactLocation.t [@default ArtifactLocation.default]); locations: (Location.t list [@default []]); instanceGuid: (string [@default ""]); correlationGuid: (string [@default ""]); @@ -902,10 +1019,11 @@ module Sarif_result = struct }[@@deriving yojson] let create - ?(ruleId = "") - ?(level=Result_level.notApplicable) + ~ruleId + ?(kind=Result_kind.pass) + ?(level=Result_level.none) ?(message=Message.default) - ?(analysisTarget=FileLocation.default) + ?(analysisTarget=ArtifactLocation.default) ?(locations=[]) ?(instanceGuid="") ?(correlationGuid="") @@ -927,7 +1045,7 @@ module Sarif_result = struct () = { - ruleId;level; message; analysisTarget; locations; instanceGuid; + ruleId; kind; level; message; analysisTarget; locations; instanceGuid; correlationGuid; occurrenceCount; partialFingerprints; fingerprints; stacks; codeFlows; graphs; graphTraversals; relatedLocations; suppressionStates; baselineState; attachments; workItemsUris; @@ -946,10 +1064,6 @@ module VersionControlDetails = struct }[@@deriving yojson] end -module File_dictionary = Json_dictionary(File) - -module LogicalLocation_dictionary = Json_dictionary(LogicalLocation) - module ColumnKind: sig include Json_type with type t = private string val utf16CodeUnits: t @@ -962,75 +1076,355 @@ struct let unicodeCodePoints = "unicodeCodePoints" end +module RunAutomationDetails = struct + type t = { + description: (Message.t [@default Message.default]); + id: (string [@default ""]); + guid: (string [@default ""]); + correlationGuid: (string [@default ""]); + properties: (Properties.t [@default Properties.default]); + } [@@deriving yojson] + + let create + ?(description=Message.default) ?(id="") ?(guid="") ?(correlationGuid="") + ?(properties=Properties.default) () = + { description; id; guid; correlationGuid; properties } + + let default = create () +end + +module ExternalPropertyFileReferences = struct + type t = { + location: (ArtifactLocation.t [@default ArtifactLocation.default]); + guid: (string [@default ""]); + itemCount: (int [@default -1]); + properties: (Properties.t [@default Properties.default]); + } [@@deriving yojson] + + let create + ?(location = ArtifactLocation.default) + ?(guid = "") + ?(itemCount = -1) + ?(properties = Properties.default) + () = + { location; guid; itemCount; properties } + + let default = create () +end + +module TranslationMetadata = struct + type t = { + name: (string [@default ""]); + fullName: (string [@default ""]); + shortDescription: + (MultiformatMessageString.t [@default MultiformatMessageString.default]); + fullDescription: + (MultiformatMessageString.t [@default MultiformatMessageString.default]); + downloadUri: (string [@default ""]); + informationUri: (string [@default ""]); + properties: (Properties.t [@default Properties.default]); + } [@@deriving yojson] + + let create + ~name + ?(fullName = "") + ?(shortDescription = MultiformatMessageString.default) + ?(fullDescription = MultiformatMessageString.default) + ?(downloadUri = "") + ?(informationUri = "") + ?(properties = Properties.default) + () + = + { name; fullName; shortDescription; fullDescription; + downloadUri; informationUri; properties } + + let default = create ~name:"" () +end + +module ToolComponent = struct + module Contents: sig + include Json_type with type t = private string + val localizedData: t + val nonLocalizedData: t + end = struct + type t = string [@@deriving yojson] + let localizedData = "localizedData" + let nonLocalizedData = "nonLocalizedData" + end + type t = { + guid: (string [@default ""]); + name: (string [@default ""]); + organization: (string [@default ""]); + product: (string [@default ""]); + productSuite: (string [@default ""]); + shortDescription: + (MultiformatMessageString.t [@default MultiformatMessageString.default]); + fullDescription: + (MultiformatMessageString.t [@default MultiformatMessageString.default]); + fullName: (string [@default ""]); + version: (string [@default ""]); + semanticVersion: (string [@default ""]); + dottedQuadFileVersion: (string [@default ""]); + releaseDateUtc: (string [@default ""]); + downloadUri: (string [@default ""]); + informationUri: (string [@default ""]); + globalMessageStrings: (MultiformatMessageStringDictionary.t [@default []]); + notifications: (ReportingDescriptor.t list [@default []]); + rules: (ReportingDescriptor.t list [@default []]); + taxa: (ReportingDescriptor.t list [@default []]); + locations: (ArtifactLocation.t list [@default []]); + language: (string [@default "en-US"]); + contents: (Contents.t list [@default []]); + isComprehensive: (bool [@default false]); + localizedDataSemanticVersion: (string [@default ""]); + minimumRequiredLocalizedDataSemanticVersion: (string [@default ""]); + associateComponent: + (ToolComponentReference.t [@default ToolComponentReference.default]); + translationMetadata: + (TranslationMetadata.t [@default TranslationMetadata.default]); + supportedTaxonomies: (ToolComponentReference.t list [@default []]); + properties: (Properties.t [@default Properties.default]); + }[@@deriving yojson] + let create + ?(guid="") + ~name + ?(organization="") + ?(product="") + ?(productSuite="") + ?(shortDescription=MultiformatMessageString.default) + ?(fullDescription=MultiformatMessageString.default) + ?(fullName="") + ?(version="") + ?(semanticVersion="") + ?(dottedQuadFileVersion="") + ?(releaseDateUtc="") + ?(downloadUri="") + ?(informationUri="") + ?(globalMessageStrings=[]) + ?(notifications=[]) + ?(rules=[]) + ?(taxa=[]) + ?(locations=[]) + ?(language="en-US") + ?(contents=[Contents.nonLocalizedData]) + ?(isComprehensive=false) + ?(localizedDataSemanticVersion="") + ?(minimumRequiredLocalizedDataSemanticVersion="") + ?(associateComponent=ToolComponentReference.default) + ?(translationMetadata=TranslationMetadata.default) + ?(supportedTaxonomies=[]) + ?(properties=Properties.default) + () + = + { guid; name; organization; product; productSuite; shortDescription; + fullDescription; fullName; version; semanticVersion; + dottedQuadFileVersion; releaseDateUtc; downloadUri; informationUri; + globalMessageStrings; notifications; rules; taxa; locations; language; + contents; isComprehensive; localizedDataSemanticVersion; + minimumRequiredLocalizedDataSemanticVersion; + associateComponent; translationMetadata; supportedTaxonomies; properties } + let default = create ~name:"" () +end + +module Address = struct + type t = { + absoluteAddress: (int [@default -1]); + relativeAddress: (int [@default 0]); + length: (int [@default 0]); + kind: (string [@default ""]); + name: (string [@default ""]); + fullyQualifiedName: (string [@default ""]); + offsetFromParent: (int [@default 0]); + index: (int [@default -1]); + parentIndex: (int [@default -1]); + properties: (Properties.t [@default Properties.default]); + } [@@deriving yojson] + + let create + ?(absoluteAddress = -1) + ?(relativeAddress = 0) + ?(length = 0) + ?(kind = "") + ?(name = "") + ?(fullyQualifiedName = "") + ?(offsetFromParent = 0) + ?(index = -1) + ?(parentIndex = -1) + ?(properties = Properties.default) + () + = + { absoluteAddress; relativeAddress; length; kind; name; + fullyQualifiedName; offsetFromParent; index; parentIndex; properties } + + let default = create () +end + +module WebRequest = struct + type t = { + index: (int [@default -1]); + protocol: (string [@default ""]); + version: (string [@default ""]); + target: (string [@default ""]); + method_: (string [@default ""]) [@key "method"]; + headers: (JsonStringDictionary.t [@default []]); + parameters: (JsonStringDictionary.t [@default []]); + body: (ArtifactContent.t [@default ArtifactContent.default]); + properties: (Properties.t [@default Properties.default]); + } [@@deriving yojson] + + let create + ?(index = -1) + ?(protocol = "") + ?(version = "") + ?(target = "") + ?(method_ = "") + ?(headers = []) + ?(parameters = []) + ?(body = ArtifactContent.default) + ?(properties = Properties.default) + () + = + { index; protocol; version; target; method_; headers; parameters; + body; properties } + + let default = create () + +end + +module WebResponse = struct + type t = { + index: (int [@default -1]); + protocol: (string [@default ""]); + version: (string [@default ""]); + statusCode: (int [@default 0]); + reasonPhrase: (string [@default ""]); + headers: (JsonStringDictionary.t [@default []]); + body: (ArtifactContent.t [@default ArtifactContent.default]); + noResponseReceived: (bool [@default false]); + properties: (Properties.t [@default Properties.default]); + } [@@deriving yojson] + + let create + ?(index = -1) + ?(protocol = "") + ?(version = "") + ?(statusCode = 0) + ?(reasonPhrase = "") + ?(headers = []) + ?(body = ArtifactContent.default) + ?(noResponseReceived = false) + ?(properties = Properties.default) + () + = + { index; protocol; version; statusCode; reasonPhrase; + headers; body; noResponseReceived; properties } + + let default = create () + +end + +module SpecialLocations = struct + type t = { + displayBase: (ArtifactLocation.t [@default ArtifactLocation.default]); + properties: (Properties.t [@default Properties.default]) + } [@@deriving yojson] + let create + ?(displayBase = ArtifactLocation.default) + ?(properties = Properties.default) + () + = + { displayBase; properties } + + let default = create () +end + module Run = struct type t = { tool: Tool.t; invocations: (Invocation.t list [@default []]); conversion: (Conversion.t [@default Conversion.default]); + language: (string [@default "en-US"]); versionControlProvenance: (VersionControlDetails.t list [@default []]); originalUriBaseIds: - (Additional_properties.t [@default Additional_properties.default]); - files: (File_dictionary.t [@default []]); - logicalLocations: (LogicalLocation_dictionary.t [@default []]); - graphs: (Graph_dictionary.t [@default []]); + (ArtifactLocationDictionary.t [@default []]); + artifacts: (Artifact.t list [@default []]); + logicalLocations: (LogicalLocation.t list [@default []]); + graphs: (Graph.t list [@default []]); results: (Sarif_result.t list [@default []]); - resources: (Resources.t [@default Resources.default]); - instanceGuid: (string [@default ""]); - correlationGuid: (string [@default ""]); - logicalId: (string [@default ""]); - description: (Message.t [@default Message.default]); - automationLogicalId: (string [@default ""]); - baselineInstanceGuid: (string [@default ""]); - architecture: (string [@default ""]); - richMessageMimeType: (string [@default "text/markdown;variant=GFM" ]); - redactionToken: (string [@default ""]); - defaultFileEncoding: (string [@default "utf-8"]); + automationDetails: + (RunAutomationDetails.t [@default RunAutomationDetails.default]); + runAggregates: (RunAutomationDetails.t list [@default []]); + baselineGuid: (string [@default ""]); + redactionToken: (string list [@default []]); + defaultEncoding: (string [@default "utf-8"]); + defaultSourceLanguage: (string [@default ""]); + newlineSequences: (string list [@default ["\r\n"; "\n"]]); columnKind: (ColumnKind.t [@default ColumnKind.unicodeCodePoints]); + externalPropertyFileReferences: + (ExternalPropertyFileReferences.t + [@default ExternalPropertyFileReferences.default]); + threadFlowLocations: (ThreadFlowLocation.t list [@default []]); + taxonomies: (ToolComponent.t list [@default []]); + addresses: (Address.t list [@default []]); + translations: (ToolComponent.t list [@default []]); + policies: (ToolComponent.t list [@default[]]); + webRequests: (WebRequest.t list [@default[]]); + webResponses: (WebResponse.t list [@default[]]); + specialLocations: (SpecialLocations.t [@default SpecialLocations.default]); properties: (Properties.t [@default Properties.default]); } [@@deriving yojson] let create ~tool - ~invocations + ?(invocations=[]) ?(conversion=Conversion.default) + ?(language="en-US") ?(versionControlProvenance=[]) - ?(originalUriBaseIds=Additional_properties.default) - ?(files=[]) + ?(originalUriBaseIds=[]) + ?(artifacts=[]) ?(logicalLocations=[]) ?(graphs=[]) ?(results=[]) - ?(resources=Resources.default) - ?(instanceGuid="") - ?(correlationGuid="") - ?(logicalId="") - ?(description=Message.default) - ?(automationLogicalId="") - ?(baselineInstanceGuid="") - ?(architecture="") - ?(richMessageMimeType="text/markdown;variant=GFM") - ?(redactionToken="") - ?(defaultFileEncoding="utf-8") + ?(automationDetails=RunAutomationDetails.default) + ?(runAggregates=[]) + ?(baselineGuid="") + ?(redactionToken=[]) + ?(defaultEncoding="utf-8") + ?(defaultSourceLanguage="C") + ?(newlineSequences=["\r\n"; "\n"]) ?(columnKind=ColumnKind.unicodeCodePoints) + ?(externalPropertyFileReferences=ExternalPropertyFileReferences.default) + ?(threadFlowLocations=[]) + ?(taxonomies=[]) + ?(addresses=[]) + ?(translations=[]) + ?(policies=[]) + ?(webRequests=[]) + ?(webResponses=[]) + ?(specialLocations=SpecialLocations.default) ?(properties=Properties.default) () = { - tool; invocations; conversion; versionControlProvenance; originalUriBaseIds; - files; logicalLocations; graphs; results; resources; instanceGuid; - correlationGuid; logicalId; description; automationLogicalId; - baselineInstanceGuid; architecture; richMessageMimeType; - redactionToken; defaultFileEncoding; columnKind; properties + tool; invocations; conversion; versionControlProvenance; + language; originalUriBaseIds; + artifacts; logicalLocations; graphs; results; + automationDetails; runAggregates; baselineGuid; redactionToken; + defaultEncoding; defaultSourceLanguage; newlineSequences; columnKind; + externalPropertyFileReferences; threadFlowLocations; taxonomies; + addresses; translations; policies; webRequests; webResponses; + specialLocations; properties; } end module Schema = struct type t = { - schema: (Uri.t [@default Uri.sarif_github]) [@key "$schema"]; + schema: Uri.t [@key "$schema"]; version: Version.t; runs: Run.t list } [@@deriving yojson] - let create ?(schema=Uri.sarif_github) ?(version=Version.v2_0_0) ~runs () = + let create ?(schema=Uri.sarif_github) ?(version=Version.v2_1_0) ~runs () = { schema; version; runs } end diff --git a/src/plugins/markdown-report/sarif_gen.ml b/src/plugins/markdown-report/sarif_gen.ml index f3e3ed9226d9aefc42ce456d75c9be0db374004c..278073346ff33b4e1250082da7ab6c53033016af 100644 --- a/src/plugins/markdown-report/sarif_gen.ml +++ b/src/plugins/markdown-report/sarif_gen.ml @@ -29,8 +29,7 @@ let frama_c_sarif = let fullName = name ^ "-" ^ version in let downloadUri = "https://frama-c.com/download.html" in Tool.create - ~name ~version ~semanticVersion ~fullName ~downloadUri () - + (Driver.create ~name ~version ~semanticVersion ~fullName ~downloadUri ()) let get_remarks () = let f = Mdr_params.Remarks.get () in @@ -63,22 +62,32 @@ let gen_remark alarm = [ Block [ Text (plain - (Printf.sprintf "This alarms represents a potential %s." + (Printf.sprintf "This alarm represents a potential %s." (Alarms.get_description alarm) ) ) ] ] -let level_of_status = +let kind_of_status = let open Property_status.Feedback in - let open Sarif.Result_level in + let open Sarif.Result_kind in function | Never_tried -> notApplicable | Considered_valid | Valid | Valid_under_hyp | Valid_but_dead -> pass - | Unknown | Unknown_but_dead -> warning + | 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 | Invalid | Invalid_under_hyp | Invalid_but_dead -> error - | Inconsistent -> note + | Inconsistent -> none let make_message alarm annot remark = let open Markdown in @@ -92,11 +101,15 @@ let make_message alarm annot remark = | [] -> summary :: gen_remark alarm | _ -> summary :: remark in - let richText = + let markdown = String.trim (Format.asprintf "@[%a@]" (Markdown.pp_elements ~page:"") markdown) in - Message.create ~text ~richText () + Message.create ~text ~markdown () + +let opt_physical_location_of_loc loc = + if loc = Cil_datatype.Location.unknown then [] + else [ Location.of_loc loc ] let gen_results remarks = let treat_alarm _e kf s ~rank:_ alarm annot (i, rules, content) = @@ -106,12 +119,13 @@ let gen_results remarks = 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 = [ Location.of_loc (Cil_datatype.Stmt.loc s) ] in + let locations = opt_physical_location_of_loc (Cil_datatype.Stmt.loc s) in let res = - Sarif_result.create ~level ~ruleId ~message ~locations () + Sarif_result.create ~kind ~level ~ruleId ~message ~locations () in (i+1, rules, res :: content) in @@ -128,12 +142,14 @@ let make_ip_message ip = let text = Format.asprintf "@[%a.@]" Property.short_pretty ip in Message.plain_text ~text () +let user_annot_id = "user-spec" + 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 locations = opt_physical_location_of_loc (Property.location ip) in let message = make_ip_message ip in - Sarif_result.create ~level ~locations ~message () + Sarif_result.create ~ruleId:user_annot_id ~level ~locations ~message () let gen_statuses () = let f ip content = @@ -141,29 +157,27 @@ let gen_statuses () = in List.rev (Property_status.fold f []) -let gen_files () = +let gen_artifacts () = let add_src_file f = - let key = - let fname = Filepath.Normalized.to_pretty_string f in - Filename.chop_extension (Filename.basename fname) - in - let fileLocation = FileLocation.create ~uri:(f :> string) () in + let uri = (f:Filepath.Normalized.t :> string) in + let location = ArtifactLocation.create ~uri () in let roles = [ Role.analysisTarget ] in let mimeType = "text/x-csrc" in - key, File.create ~fileLocation ~roles ~mimeType () + Artifact.create ~location ~roles ~mimeType () in List.map add_src_file (Kernel.Files.get ()) 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 shortDescription = MultiformatMessageString.create ~text () in + let rule = ReportingDescriptor.create ~id ~shortDescription () in + rule :: l -let make_rule_dictionary rules = Datatype.String.Map.fold add_rule rules [] +let make_taxonomies rules = Datatype.String.Map.fold add_rule rules [] let gen_run remarks = let tool = frama_c_sarif in + let name = "frama-c" in let invocations = [gen_invocation ()] in let rules, results = gen_results remarks in let user_annot_results = gen_statuses () in @@ -172,13 +186,13 @@ let gen_run remarks = | [] -> rules | _ -> Datatype.String.Map.add - "user-spec" "User written ACSL specification" rules + user_annot_id "User-written ACSL specification" rules in - let rules = make_rule_dictionary rules in - let resources = Resources.create ~rules () in + let rules = make_taxonomies rules in + let taxonomies = [ToolComponent.create ~name ~rules ()] in let results = results @ user_annot_results in - let files = gen_files () in - Run.create ~tool ~invocations ~results ~resources ~files () + let artifacts = gen_artifacts () in + Run.create ~tool ~invocations ~results ~taxonomies ~artifacts () let generate () = let remarks = get_remarks () in