-
Notifications
You must be signed in to change notification settings - Fork 23
Add SARIF output format support with comprehensive tests #290
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
5c43184
09ba853
6515425
5e6f19e
763c240
c5cfe82
feac468
636225f
db83267
4c10bb1
248beb7
bf37a14
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| program Boogie; | ||
|
|
||
| // Simple test program for SARIF output | ||
| procedure AddPositive(x : int, y : int) returns (z : int) | ||
| spec { | ||
| requires [x_positive]: (x > 0); | ||
| requires [y_positive]: (y > 0); | ||
| ensures [result_positive]: (z > 0); | ||
| ensures [result_sum]: (z == x + y); | ||
| } | ||
| { | ||
| z := x + y; | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| /- | ||
| Copyright Strata Contributors | ||
|
|
||
| SPDX-License-Identifier: Apache-2.0 OR MIT | ||
| -/ | ||
|
|
||
| import Strata.Languages.Boogie.Verifier | ||
| import Strata.Util.Sarif | ||
|
|
||
| /-! | ||
| # Boogie SARIF Output | ||
|
|
||
| This module provides Boogie-specific conversion functions for SARIF output. | ||
| -/ | ||
|
|
||
| namespace Boogie.Sarif | ||
|
|
||
| open Strata.Sarif Strata.SMT | ||
|
|
||
| /-! ## Boogie-Specific Conversion Functions -/ | ||
|
|
||
| /-- Convert Boogie Outcome to SARIF Level -/ | ||
| def outcomeToLevel : Outcome → Level | ||
| | .pass => .none | ||
| | .fail => .error | ||
| | .unknown => .warning | ||
| | .implementationError _ => .error | ||
|
|
||
| /-- Convert Boogie Outcome to a descriptive message -/ | ||
| def outcomeToMessage (outcome : Outcome) (smtResult : SMT.Result) : String := | ||
| match outcome with | ||
| | .pass => "Verification succeeded" | ||
| | .fail => | ||
| match smtResult with | ||
| | .sat m => | ||
| if m.isEmpty then | ||
| "Verification failed" | ||
| else | ||
| s!"Verification failed with counterexample: {Std.format m}" | ||
| | _ => "Verification failed" | ||
| | .unknown => "Verification result unknown (solver timeout or incomplete)" | ||
| | .implementationError msg => s!"Verification error: {msg}" | ||
|
|
||
| /-- Extract location information from metadata -/ | ||
| def extractLocation (md : Imperative.MetaData Expression) : Option Location := do | ||
| let fileRangeElem ← md.findElem Imperative.MetaData.fileRange | ||
| match fileRangeElem.value with | ||
| | .fileRange fr => | ||
| let uri := match fr.file with | ||
| | .file path => path | ||
| pure { uri, startLine := fr.start.line, startColumn := fr.start.column } | ||
| | _ => none | ||
|
|
||
| /-- Convert a VCResult to a SARIF Result -/ | ||
| def vcResultToSarifResult (vcr : VCResult) : Strata.Sarif.Result := | ||
| let ruleId := vcr.obligation.label | ||
| let level := outcomeToLevel vcr.result | ||
| let messageText := outcomeToMessage vcr.result vcr.smtResult | ||
| let message : Strata.Sarif.Message := { text := messageText } | ||
|
|
||
| let locations := match extractLocation vcr.obligation.metadata with | ||
| | some loc => #[locationToSarif loc] | ||
| | none => #[] | ||
|
|
||
| { ruleId, level, message, locations } | ||
|
|
||
| /-- Convert VCResults to a SARIF document -/ | ||
| def vcResultsToSarif (vcResults : VCResults) : Strata.Sarif.SarifDocument := | ||
| let tool : Strata.Sarif.Tool := { | ||
| driver := { | ||
| name := "Strata", | ||
| version := "0.1.0", | ||
| informationUri := "https://github.com/strata-org/Strata" | ||
| } | ||
| } | ||
|
|
||
| let results := vcResults.map vcResultToSarifResult | ||
|
|
||
| let run : Strata.Sarif.Run := { tool, results } | ||
|
|
||
| { version := "2.1.0", | ||
| schema := "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", | ||
| runs := #[run] } | ||
|
|
||
| end Boogie.Sarif |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,142 @@ | ||
| /- | ||
| Copyright Strata Contributors | ||
|
|
||
| SPDX-License-Identifier: Apache-2.0 OR MIT | ||
| -/ | ||
|
|
||
| import Lean.Data.Json | ||
|
|
||
| /-! | ||
| # SARIF Output | ||
|
|
||
| This module provides support for outputting results in SARIF | ||
| (Static Analysis Results Interchange Format) version 2.1.0. | ||
|
|
||
| SARIF specification: https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html | ||
| -/ | ||
|
|
||
| namespace Strata.Sarif | ||
|
|
||
| open Lean (Json ToJson FromJson) | ||
|
|
||
| /-! ## SARIF Data Structures -/ | ||
|
|
||
| /-- SARIF location representing a position in source code -/ | ||
| structure Location where | ||
| uri : String | ||
| startLine : Nat | ||
| startColumn : Nat | ||
| deriving Repr, ToJson, FromJson, BEq | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do these instances just magically give us JSON that's in the right form for SARIF? Pretty cool, if so! |
||
|
|
||
| /-- SARIF artifact location representing a file URI -/ | ||
| structure ArtifactLocation where | ||
| uri : String | ||
| deriving Repr, ToJson, FromJson | ||
|
|
||
| /-- SARIF region representing a source code region with line and column information -/ | ||
| structure Region where | ||
| startLine : Nat | ||
| startColumn : Nat | ||
| deriving Repr, ToJson, FromJson | ||
|
|
||
| /-- SARIF physical location with region information -/ | ||
| structure PhysicalLocation where | ||
| artifactLocation : ArtifactLocation | ||
| region : Region | ||
| deriving Repr, ToJson, FromJson | ||
|
|
||
| /-- SARIF location wrapper -/ | ||
| structure SarifLocation where | ||
| physicalLocation : PhysicalLocation | ||
| deriving Repr, ToJson, FromJson | ||
|
|
||
| /-- SARIF message -/ | ||
| structure Message where | ||
| text : String | ||
| deriving Repr, ToJson, FromJson | ||
|
|
||
| /-- SARIF result level -/ | ||
| inductive Level where | ||
| | none -- Verification passed | ||
| | note -- Informational | ||
| | warning -- Unknown result or potential issue | ||
| | error -- Verification failed | ||
| deriving Repr, DecidableEq | ||
|
|
||
| instance : ToString Level where | ||
| toString | ||
| | .none => "none" | ||
| | .note => "note" | ||
| | .warning => "warning" | ||
| | .error => "error" | ||
|
|
||
| instance : ToJson Level where | ||
| toJson level := Json.str (toString level) | ||
|
|
||
| instance : FromJson Level where | ||
| fromJson? j := do | ||
| let s ← j.getStr? | ||
| match s with | ||
| | "none" => pure .none | ||
| | "note" => pure .note | ||
| | "warning" => pure .warning | ||
| | "error" => pure .error | ||
| | _ => throw s!"Invalid SARIF level: {s}" | ||
|
|
||
| /-- SARIF result representing a single verification result -/ | ||
| structure Result where | ||
| ruleId : String | ||
| level : Level | ||
| message : Message | ||
| locations : Array SarifLocation := #[] | ||
| deriving Repr, ToJson, FromJson | ||
|
|
||
| instance : Inhabited Result where | ||
| default := { ruleId := "", level := .none, message := { text := "" } } | ||
|
|
||
| /-- SARIF tool driver information -/ | ||
| structure Driver where | ||
| name : String | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's a "Driver"? Is that a command line a tool name or something else?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have a similar question about ruleId. I think a short comment with an example would help or maybe just link to a URL for the Sarif reference at the top of model so it is easy for someone not familiar with Sarif to understand what some of the strings are for. |
||
| version : String := "0.1.0" | ||
| informationUri : String := "https://github.com/strata-org/Strata" | ||
| deriving Repr, ToJson, FromJson, Inhabited | ||
|
|
||
| /-- SARIF tool information -/ | ||
| structure Tool where | ||
| driver : Driver | ||
| deriving Repr, ToJson, FromJson, Inhabited | ||
|
|
||
| /-- SARIF run representing a single analysis run -/ | ||
| structure Run where | ||
| tool : Tool | ||
| results : Array Result | ||
| deriving Repr, ToJson, FromJson | ||
|
|
||
| instance : Inhabited Run where | ||
| default := { tool := default, results := #[] } | ||
|
|
||
| /-- Top-level SARIF document -/ | ||
| structure SarifDocument where | ||
| version : String := "2.1.0" | ||
| /-- Schema URI as specified by SARIF 2.1.0 -/ | ||
| schema : String := "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json" | ||
| runs : Array Run | ||
| deriving Repr, ToJson, FromJson | ||
|
|
||
| /-! ## Utility Functions -/ | ||
|
|
||
| /-- Convert a location to SARIF format -/ | ||
| def locationToSarif (loc : Location) : SarifLocation := | ||
| let artifactLocation : ArtifactLocation := { uri := loc.uri } | ||
| let region : Region := { startLine := loc.startLine, startColumn := loc.startColumn } | ||
| { physicalLocation := { artifactLocation, region } } | ||
|
|
||
| /-- Convert SARIF document to JSON string -/ | ||
| def toJsonString (sarif : SarifDocument) : String := | ||
| Json.compress (ToJson.toJson sarif) | ||
|
|
||
| /-- Convert SARIF document to pretty-printed JSON string -/ | ||
| def toPrettyJsonString (sarif : SarifDocument) : String := | ||
| Json.pretty (ToJson.toJson sarif) | ||
|
|
||
| end Strata.Sarif | ||
Uh oh!
There was an error while loading. Please reload this page.