Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
5c43184
Add SARIF output format support with comprehensive tests
tautschnig Dec 22, 2025
09ba853
Address feedback
tautschnig Dec 22, 2025
6515425
Update StrataTest/Languages/Boogie/SarifOutputTests.lean
tautschnig Dec 22, 2025
5e6f19e
Update Strata/Languages/Boogie/SarifOutput.lean
tautschnig Dec 22, 2025
763c240
Update StrataVerify.lean
tautschnig Dec 22, 2025
c5cfe82
Refactor to address Aaron's comments
tautschnig Jan 7, 2026
feac468
Merge remote-tracking branch 'origin/main' into tautschnig/SARIF-output
tautschnig Jan 7, 2026
636225f
Fixup merge after #296 got merged in
tautschnig Jan 7, 2026
db83267
Merge remote-tracking branch 'origin/main' into tautschnig/SARIF-output
tautschnig Jan 7, 2026
4c10bb1
Merge remote-tracking branch 'origin/main' into tautschnig/SARIF-output
tautschnig Jan 15, 2026
248beb7
Update to latest main
tautschnig Jan 15, 2026
bf37a14
Merge branch 'main' into tautschnig/SARIF-output
shigoel Jan 15, 2026
e452315
Update StrataTest/Languages/Boogie/SarifOutputTests.lean
tautschnig Jan 20, 2026
1b345bc
Address comments
tautschnig Jan 20, 2026
5caead0
Merge remote-tracking branch 'origin/main' into tautschnig/SARIF-output
tautschnig Jan 20, 2026
4a4fb61
Fixup merge
tautschnig Jan 20, 2026
43ce2bf
Test fixes
tautschnig Jan 20, 2026
4709802
Merge remote-tracking branch 'origin/main' into tautschnig/SARIF-output
tautschnig Jan 20, 2026
fbac11d
Merge fix-up
tautschnig Jan 20, 2026
121d58d
Update StrataTest/Languages/Core/SarifOutputTests.lean
tautschnig Jan 20, 2026
2768771
Fix remaining Boogie reference
tautschnig Jan 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions Examples/SarifTest.boogie.st
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;
};
4 changes: 4 additions & 0 deletions Strata.lean
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,12 @@ import Strata.DL.SMT.SMT
import Strata.DL.Lambda.Lambda
import Strata.DL.Imperative.Imperative

/- Utilities -/
import Strata.Util.Sarif

/- Strata Core -/
import Strata.Languages.Core.StatementSemantics
import Strata.Languages.Core.SarifOutput

/- Code Transforms -/
import Strata.Transform.CallElimCorrect
Expand Down
5 changes: 4 additions & 1 deletion Strata/Languages/Core/Options.lean
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ structure Options where
removeIrrelevantAxioms : Bool
/-- Solver time limit in seconds -/
solverTimeout : Nat
/-- Output results in SARIF format -/
outputSarif : Bool

def Options.default : Options := {
verbose := .normal,
Expand All @@ -53,7 +55,8 @@ def Options.default : Options := {
checkOnly := false,
stopOnFirstError := false,
removeIrrelevantAxioms := false,
solverTimeout := 10
solverTimeout := 10,
outputSarif := false
}

instance : Inhabited Options where
Expand Down
85 changes: 85 additions & 0 deletions Strata/Languages/Core/SarifOutput.lean
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.Core.Verifier
import Strata.Util.Sarif

/-!
# Core SARIF Output

This module provides Core-specific conversion functions for SARIF output.
-/

namespace Core.Sarif

open Strata.Sarif Strata.SMT

/-! ## Core-Specific Conversion Functions -/

/-- Convert Core Outcome to SARIF Level -/
def outcomeToLevel : Outcome → Level
| .pass => .none
| .fail => .error
| .unknown => .warning
| .implementationError _ => .error

/-- Convert Core 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
| .file2dRange 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 Core.Sarif
144 changes: 144 additions & 0 deletions Strata/Util/Sarif.lean
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/-
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
Copy link
Contributor

Choose a reason for hiding this comment

The 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!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't call it magical - that's basically turning SARIF fields into datatypes. Unless I misunderstood your comment?


/-- SARIF artifact location representing a file URI -/
structure ArtifactLocation where
uri : String
deriving Repr, ToJson, FromJson, DecidableEq

/-- SARIF region representing a source code region with line and column information -/
structure Region where
startLine : Nat
startColumn : Nat
deriving Repr, ToJson, FromJson, DecidableEq

/-- SARIF physical location with region information -/
structure PhysicalLocation where
artifactLocation : ArtifactLocation
region : Region
deriving Repr, ToJson, FromJson, DecidableEq

/-- SARIF location wrapper -/
structure SarifLocation where
physicalLocation : PhysicalLocation
deriving Repr, ToJson, FromJson, DecidableEq

/-- SARIF message -/
structure Message where
text : String
deriving Repr, ToJson, FromJson, DecidableEq

/-- 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
/-- Stable identifier of the rule that was evaluated to produce the result --/
ruleId : String
level : Level
message : Message
locations : Array SarifLocation := #[]
deriving Repr, ToJson, FromJson, DecidableEq

instance : Inhabited Result where
default := { ruleId := "", level := .none, message := { text := "" } }

/-- SARIF tool driver information -/
structure Driver where
/-- The exact command-line tool in Strata --/
name : String
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
Loading
Loading