Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
194 commits
Select commit Hold shift + click to select a range
7e4a8e9
feat(verifier): Implement two-sided verification check
MikaelMayer Feb 26, 2026
d3d5475
feat(cli): Add --check-mode flag and update emoji symbols
MikaelMayer Feb 26, 2026
4c2809a
feat(verifier): Add per-statement check mode annotations
MikaelMayer Feb 26, 2026
3b454e2
test: Add comprehensive VCOutcome tests and fix SMTEncoder tests
MikaelMayer Feb 26, 2026
2b8a1a6
test: Add comprehensive VCOutcome tests and fix SMTEncoder tests
MikaelMayer Feb 26, 2026
4aca450
test: Add comprehensive VCOutcome tests and fix SMTEncoder tests
MikaelMayer Feb 26, 2026
cdb515b
docs: Update implementation summary with completed features
MikaelMayer Feb 26, 2026
7b567c1
fix: Update StrataVerify and StrataMain for VCOutcome API changes
MikaelMayer Feb 26, 2026
74412fb
fix: Remove trailing whitespace in SMTUtils.lean
MikaelMayer Feb 26, 2026
7c705b4
fix: Remove all trailing whitespace in SMTUtils.lean
MikaelMayer Feb 26, 2026
67f42b4
fix: Map old reachCheck metadata to fullCheck for backward compatibility
MikaelMayer Feb 26, 2026
4877fec
feat: Add isAlwaysFalseIfReachable alias for isRefuted
MikaelMayer Feb 26, 2026
6ae4c72
refactor: Rename predicates for consistency and add cross-cutting fil…
MikaelMayer Feb 26, 2026
d35c35a
chore: Remove implementation tracking document
MikaelMayer Feb 26, 2026
3cd66a1
refactor: Make isPass conservative (validity only) and add specific v…
MikaelMayer Feb 26, 2026
3003f0f
refactor: Consistent naming with reachability at end, isSatisfiable c…
MikaelMayer Feb 26, 2026
5081ebf
refactor: Separate base cases (no 'is' prefix) from derived predicates
MikaelMayer Feb 26, 2026
47474da
feat: Add VerificationMode to outcomeToLevel for context-aware severity
MikaelMayer Feb 26, 2026
bd47c89
refactor: Simplify outcomeToLevel - no warnings in deductive mode, us…
MikaelMayer Feb 26, 2026
983bef2
refactor: Rename refutedAndReachable to alwaysFalseAndReachable, unre…
MikaelMayer Feb 26, 2026
149989c
refactor: Use only base case predicates in outcomeToLevel
MikaelMayer Feb 26, 2026
2e33a24
refactor: Make outcome messages neutral and context-independent
MikaelMayer Feb 26, 2026
5f93025
fix: Handle models correctly for alwaysFalseReachabilityUnknown and u…
MikaelMayer Feb 26, 2026
8f8b52a
fix: Remove incorrect model handling for alwaysFalseReachabilityUnknown
MikaelMayer Feb 26, 2026
eaafeb4
refactor: Pattern match directly on satisfiability and validity prope…
MikaelMayer Feb 26, 2026
8f2e3d0
fix: Remove trailing whitespace in SarifOutput.lean
MikaelMayer Feb 26, 2026
75ab1b7
test: Add comprehensive SARIF output tests for all nine outcomes
MikaelMayer Feb 26, 2026
c6bbd37
fix: Update dischargeObligation call signature in test
MikaelMayer Feb 26, 2026
a7f1333
refactor: Consolidate VCOutcome tests to one eval per case
MikaelMayer Feb 26, 2026
d6ae21b
feat: Add --error-level CLI option for SARIF severity mapping
MikaelMayer Feb 26, 2026
2381e59
fix: Update StrataMain for VCOutcome changes and clarify bugFinding c…
MikaelMayer Feb 26, 2026
4d44f75
fix: Update bugFinding SARIF severity levels per review
MikaelMayer Feb 26, 2026
21d5f30
fix: Remove trailing whitespace from Verifier.lean
MikaelMayer Feb 26, 2026
5d2dc07
refactor: Add helper function and improve SARIF output labels in tests
MikaelMayer Feb 26, 2026
f14b6f0
refactor: Improve VCOutcome tests with comprehensive predicate checking
MikaelMayer Feb 26, 2026
a1bf916
feat: Add derived predicate testing to VCOutcome tests
MikaelMayer Feb 26, 2026
a4d7427
refactor: Remove unnecessary section comments from VCOutcome tests
MikaelMayer Feb 26, 2026
77b4eaa
refactor: Redesign verification flags for orthogonal error mode and d…
MikaelMayer Feb 26, 2026
c8c0aa7
refactor: Rename flags to --check-mode and --check-amount, simplify a…
MikaelMayer Feb 26, 2026
97bf7bd
Merge remote-tracking branch 'origin/main' into feat/two-sided-verifi…
MikaelMayer Feb 26, 2026
38b5790
fix: use assert + check-sat for single checks to match pre-PR behavior
MikaelMayer Feb 26, 2026
be79034
test: update expectations for 'pass if reachable' result format
MikaelMayer Feb 27, 2026
c120f3d
feat: clarify reachability claims with 'from declaration entry'
MikaelMayer Feb 27, 2026
b9f05d7
Merge remote-tracking branch 'origin/main' into feat/two-sided-verifi…
MikaelMayer Feb 27, 2026
6e28fed
fix: mask PE and SMT outcomes to respect requested checks
MikaelMayer Feb 27, 2026
d385ac7
fix: update label for validity-only failure to 'can be false if reach…
MikaelMayer Feb 27, 2026
6b937c4
test: update VCOutcomeTests for new label
MikaelMayer Feb 27, 2026
9ffeb7d
Merge branch 'main' into feat/two-sided-verification-check
MikaelMayer Feb 27, 2026
e26b317
fix: remove trailing whitespace
MikaelMayer Feb 27, 2026
9811a66
test: update test expectations for new validity-only outcome labels
MikaelMayer Feb 27, 2026
cd67538
test: fix remaining test expectations for validity-only outcomes
MikaelMayer Feb 27, 2026
c8f69fb
test: update RemoveIrrelevantAxioms expectations
MikaelMayer Feb 27, 2026
ee3512e
test: add VCs section to Regex test with proper formatting
MikaelMayer Feb 27, 2026
b31dffc
test: fix Quantifiers blank line indentation
MikaelMayer Feb 27, 2026
62f753c
test: fix blank line indentation in RemoveIrrelevantAxioms and SafeMap
MikaelMayer Feb 27, 2026
8fd96b0
test: fix blank lines to be truly empty
MikaelMayer Feb 27, 2026
26a2942
test: remove trailing separator causing syntax error
MikaelMayer Feb 27, 2026
80cbee8
test: add single-space blank lines back
MikaelMayer Feb 27, 2026
6db0b2f
test: remove spaces from blank lines in docstrings
MikaelMayer Feb 27, 2026
1f725b9
test: fix Quantifiers test with correct blank line format and remove …
MikaelMayer Feb 27, 2026
078aefd
test: fix Quantifiers first guard_msgs block with DEBUG section
MikaelMayer Feb 27, 2026
9901b25
test: fix Regex trailing separator and RemoveIrrelevantAxioms missing…
MikaelMayer Feb 27, 2026
5a067be
test: fix program name in RemoveIrrelevantAxioms
MikaelMayer Feb 27, 2026
bacb297
test: update outcome labels and replace reachCheck with checkAmount
MikaelMayer Feb 27, 2026
baade24
test: update cover outcomes for satisfiability checks
MikaelMayer Feb 27, 2026
f77eccf
test: add single-space blank lines to docstrings
MikaelMayer Feb 27, 2026
585d8b0
test: remove spaces from blank lines in docstrings
MikaelMayer Feb 27, 2026
4725072
test: disable SarifOutputTests until API is updated
MikaelMayer Feb 27, 2026
25a3e18
test: update outcome labels and disable ExprEvalTest
MikaelMayer Feb 27, 2026
f62d019
test: remove Model output from RemoveIrrelevantAxioms (validity-only …
MikaelMayer Feb 27, 2026
a80af56
test: remove Model output from test files (validity-only checks)
MikaelMayer Feb 27, 2026
579d82f
test: fix unterminated docstring in RemoveIrrelevantAxioms
MikaelMayer Feb 27, 2026
4e23a64
Merge main into feat/two-sided-verification-check
MikaelMayer Mar 2, 2026
0927752
fix: move copyright headers before #exit directives
MikaelMayer Mar 2, 2026
2e060e2
fix: rename Options to VerifyOptions to match main
MikaelMayer Mar 2, 2026
9b73d99
fix: use defaultSolver without namespace prefix
MikaelMayer Mar 2, 2026
791958f
fix: adapt SMTUtils to use monadic encodeSMT API
MikaelMayer Mar 2, 2026
def9c15
fix: convert Term to String using termToSMTString
MikaelMayer Mar 2, 2026
eacbc43
fix: add Core namespace to Options.lean
MikaelMayer Mar 2, 2026
17ae333
fix: use Core.defaultSolver after adding Core namespace
MikaelMayer Mar 2, 2026
e77e519
fix: use Core.defaultSolver in SMTUtils.lean
MikaelMayer Mar 2, 2026
0ee00dc
fix: qualify VerifyOptions with Core in Strata namespace
MikaelMayer Mar 2, 2026
b0e76a7
fix: fix VerifyVerifyOptions double prefix and Regex.lean
MikaelMayer Mar 2, 2026
46e6f5e
test: fix test expectations for Cover, RemoveIrrelevantAxioms, and ot…
MikaelMayer Mar 2, 2026
b5a71d8
fix: update diagnostic messages and remove Model output from tests
MikaelMayer Mar 2, 2026
8e26650
fix: pass variable ids to checkSat to generate models, remove extra c…
MikaelMayer Mar 2, 2026
2e1b510
fix: add Model output to tests, no get-value for two-sided checks
MikaelMayer Mar 2, 2026
1a24e6a
fix: suppress unused variable warning for md parameter
MikaelMayer Mar 2, 2026
e52ad30
fix: restore checkSat in dischargeObligation, remove from encodeCore …
MikaelMayer Mar 2, 2026
048b29b
test: update BoogieToStrata expected output for new outcome labels
MikaelMayer Mar 2, 2026
1ad3044
test: update BoogieToStrata expected output for unknown emoji
MikaelMayer Mar 2, 2026
75805e8
test: update Examples expected output for new outcome labels
MikaelMayer Mar 2, 2026
3a70839
test: update Python expected output for new outcome labels
MikaelMayer Mar 2, 2026
fa0f5fe
test: fix Python expected output format for assertion failures
MikaelMayer Mar 2, 2026
484db41
test: update SARIF validation to expect error level for unknown outcomes
MikaelMayer Mar 2, 2026
467e7af
fix: enable models in full mode by passing ids to checkSatAssuming an…
MikaelMayer Mar 2, 2026
d4a64b0
Merge main into feat/two-sided-verification-check
MikaelMayer Mar 3, 2026
da742c1
test: update new datatype test expectations for new outcome labels
MikaelMayer Mar 3, 2026
0feaf12
test: fix Assertion failed format in Python expected files after merge
MikaelMayer Mar 3, 2026
dd7f470
refactor: rename canBeFalseAndReachable to canBeFalseAndIsReachable
MikaelMayer Mar 3, 2026
c8412b3
docs: add VCOutcome outcome table as doc comment
MikaelMayer Mar 3, 2026
8fea91b
fix: pass raw Q to encodeCore, handle negation for validity checks there
MikaelMayer Mar 3, 2026
78c3ddc
fix: remove unused obligationStr variable
MikaelMayer Mar 3, 2026
eb547f5
test: update BoogieToStrata expected labels for canBeFalseAndIsReacha…
MikaelMayer Mar 3, 2026
4424552
Merge main into feat/two-sided-verification-check
MikaelMayer Mar 4, 2026
d27beea
feat: distinguish unreachable cover (error) from unreachable assert (…
MikaelMayer Mar 4, 2026
feebcdf
docs: clarify check-sat-assuming comment per review
MikaelMayer Mar 4, 2026
dd75bdc
fix: simplify unreachable cover label to just emoji + unreachable
MikaelMayer Mar 4, 2026
2842157
refactor: rename indecisive to canBeTrueOrFalse, cover with sat alway…
MikaelMayer Mar 4, 2026
0dd3b37
refactor: independent PE shortcuts per check, solver fills in the rest
MikaelMayer Mar 4, 2026
1062867
docs: separate assert and cover outcome tables in VCOutcome doc comment
MikaelMayer Mar 4, 2026
3050058
docs: single outcome table with cover notes, rename refuted if reacha…
MikaelMayer Mar 4, 2026
eafaaff
fix: always continue after PE resolves both checks, fixing duplicate …
MikaelMayer Mar 4, 2026
3f617cb
refactor: rename outcome labels for clarity
MikaelMayer Mar 4, 2026
5ee7518
fix: update .expected files with new outcome labels
MikaelMayer Mar 4, 2026
af5bc5f
refactor: change 'if reachable' to 'if reached' for clarity
MikaelMayer Mar 5, 2026
e230874
feat: add minimal/full check amount display modes
MikaelMayer Mar 5, 2026
8fe0902
fix: update remaining test expectations for minimal/full modes
MikaelMayer Mar 5, 2026
34fe172
Merge main into feat/two-sided-verification-check
MikaelMayer Mar 5, 2026
0bea884
fix: restore CounterEx as Map Ident SMT.Term and lexprModel field
MikaelMayer Mar 5, 2026
726321e
feat: add bugFindingAssumingCompleteSpec check mode
MikaelMayer Mar 5, 2026
72cf9a7
fix: update T3_ControlFlow test expectation for Test failed error
MikaelMayer Mar 5, 2026
b3783b1
Merge branch 'main' into feat/two-sided-verification-check
MikaelMayer Mar 5, 2026
144470d
fix: force full check level for bugFindingAssumingCompleteSpec mode
MikaelMayer Mar 5, 2026
adb8ad9
fix: update Python expected files for minimal mode labels
MikaelMayer Mar 5, 2026
f0f1846
fix: rename checkAmount to checkLevel in StrataVerify CLI
MikaelMayer Mar 5, 2026
1877b51
fix: update model format in test expectations
MikaelMayer Mar 5, 2026
0ac0b55
fix: restore BoogieToStrata test expectations from main
MikaelMayer Mar 5, 2026
e9fd8c3
fix: add Test failed expectation to more Laurel tests
MikaelMayer Mar 5, 2026
273b05f
Merge branch 'main' into feat/two-sided-verification-check
MikaelMayer Mar 5, 2026
45e4d84
chore: trigger CI rebuild
MikaelMayer Mar 5, 2026
1fd5dd0
Merge branch 'main' into feat/two-sided-verification-check
MikaelMayer Mar 5, 2026
98fe907
chore: force rebuild by touching Verifier.lean
MikaelMayer Mar 6, 2026
e481f15
fix: restore minimal mode diagnostic messages
MikaelMayer Mar 6, 2026
3743dab
Merge main into feat/two-sided-verification-check
MikaelMayer Mar 6, 2026
66e5244
fix: remove CexParser from SMT.lean module imports
MikaelMayer Mar 6, 2026
85670e1
fix: update StrataMain to use vcResult.outcome instead of vcResult.re…
MikaelMayer Mar 6, 2026
04a9037
fix: add missing mode parameter to writeSarifOutput calls in StrataMain
MikaelMayer Mar 6, 2026
d5f7c26
fix: restore Python expected files and remove Test failed from Laurel…
MikaelMayer Mar 6, 2026
c0146e7
fix: update BoogieToStrata .expect files to match actual output
MikaelMayer Mar 6, 2026
a2b0585
fix: restore main's formatting for unreachable and model values
MikaelMayer Mar 6, 2026
c427e66
fix: restore BoogieToStrata .expect files from main with updated unkn…
MikaelMayer Mar 6, 2026
23e2480
fix: disable SarifOutputTests with TODO for API update
MikaelMayer Mar 6, 2026
7466449
fix: complete SarifOutputTests update and remove orphaned CexParser
MikaelMayer Mar 6, 2026
58a386a
feat: add minimalVerbose check level
MikaelMayer Mar 6, 2026
970a496
fix: update test expectations for emoji and unreachable format changes
MikaelMayer Mar 6, 2026
aa4bad5
temp: update BoogieToStrata .expect for CI cache issue
MikaelMayer Mar 6, 2026
1d38baf
fix: revert BoogieToStrata .expect to minimal mode labels
MikaelMayer Mar 6, 2026
89f1206
temp: update BoogieToStrata .expect to match CI output
MikaelMayer Mar 6, 2026
3bb1188
fix: update BoogieToStrata .expect for minimalVerbose output
MikaelMayer Mar 7, 2026
0a1f36b
fix: update Examples .expected files and remove trailing whitespace
MikaelMayer Mar 7, 2026
03944a0
fix: update Python expected files for minimalVerbose output
MikaelMayer Mar 7, 2026
1f734a4
fix: update Python expected files for failure label
MikaelMayer Mar 7, 2026
5f07dd9
fix: explicit Inhabited instance for CheckLevel and revert to minimal…
MikaelMayer Mar 9, 2026
624aebc
fix: change VCResult checkLevel default from .full to .minimal
MikaelMayer Mar 9, 2026
67fc7f3
fix: update all .expected files to match CI minimalVerbose output
MikaelMayer Mar 9, 2026
bf7a0bc
Merge branch 'main' into feat/two-sided-verification-check
MikaelMayer Mar 9, 2026
df8d732
fix: restore all .expected files to main's minimal mode labels
MikaelMayer Mar 9, 2026
f907449
fix: change label/emoji default parameters to .minimal
MikaelMayer Mar 9, 2026
03e0abb
fix: explicitly pass .full to VCOutcomeTests label/emoji calls
MikaelMayer Mar 9, 2026
7b49af2
chore: trigger CI
MikaelMayer Mar 9, 2026
5d98e1e
Merge main into feat/two-sided-verification-check
MikaelMayer Mar 9, 2026
69057b3
fix: add divisionByZero cases to label, emoji, and check selection
MikaelMayer Mar 9, 2026
2a94193
refactor: simplify validate_sarif.py by merging duplicate error checks
MikaelMayer Mar 9, 2026
e4febf2
fix: update SarifOutputTests JSON expectations for properties field
MikaelMayer Mar 9, 2026
506262e
fix: update Python expected files to use ❓ emoji for unknown
MikaelMayer Mar 9, 2026
96f6636
fix: update DiffTestCore to use new VCResult.outcome API
MikaelMayer Mar 9, 2026
adf9d51
fix: correct DiffTestCore error handling for new API
MikaelMayer Mar 9, 2026
7658a56
fix: remove duplicate implementationError case in DiffTestCore
MikaelMayer Mar 9, 2026
f85f408
Merge branch 'main' into feat/two-sided-verification-check
MikaelMayer Mar 10, 2026
322e33f
fix: address review comments
MikaelMayer Mar 10, 2026
18ef9f5
chore: remove monitor_pr.sh (unrelated to PR)
MikaelMayer Mar 10, 2026
7078f36
Merge branch 'main' into feat/two-sided-verification-check
MikaelMayer Mar 10, 2026
e75509e
docs: update toSMTTerms docstring to explain negation handling
MikaelMayer Mar 10, 2026
2f5d548
chore: remove monitor_pr.py from PR
MikaelMayer Mar 10, 2026
908522f
fix: inline isFail in second pyAnalyze block where helper is out of s…
MikaelMayer Mar 10, 2026
c078631
refactor: use VCResult.isFailure method instead of inline checks
MikaelMayer Mar 10, 2026
fee5dfa
Merge branch 'main' into feat/two-sided-verification-check
MikaelMayer Mar 10, 2026
82f8ae1
fix: update VCOutcomeTests for 'Can be false and is reachable' message
MikaelMayer Mar 10, 2026
0110ca9
Merge branch 'main' into feat/two-sided-verification-check
MikaelMayer Mar 10, 2026
c529f1a
Merge branch 'main' into feat/two-sided-verification-check
MikaelMayer Mar 10, 2026
2f4636f
fix: address 9 review comments
MikaelMayer Mar 10, 2026
be58beb
Merge branch 'main' into feat/two-sided-verification-check
MikaelMayer Mar 10, 2026
be7378c
fix: check unreachable before isPass in diagnostics, fix Verify.lean …
MikaelMayer Mar 10, 2026
daef0db
fix: add checkSat to encodeArithToSMTTerms
MikaelMayer Mar 10, 2026
553f1f3
fix: suppress unused variable warning for md in dischargeObligation
MikaelMayer Mar 10, 2026
ed4d238
Merge main: take main's Python expected files
MikaelMayer Mar 10, 2026
cf646a7
fix: update Python expected files 🟡→❓ for unknown emoji after merge
MikaelMayer Mar 10, 2026
38c0d3e
fix: address 8 review comments (round 2)
MikaelMayer Mar 11, 2026
d7d5f96
Merge branch 'main' into feat/two-sided-verification-check
MikaelMayer Mar 11, 2026
812b96c
test: add guard_msgs test to verify outcome table entries
MikaelMayer Mar 11, 2026
56c39a3
test: add cover column and headers to outcome table test
MikaelMayer Mar 11, 2026
006b945
Merge branch 'main' into feat/two-sided-verification-check
MikaelMayer Mar 11, 2026
a86e643
chore: add reviewer response for aqjune-aws
MikaelMayer Mar 11, 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
11 changes: 6 additions & 5 deletions DiffTestCore.lean
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,12 @@ def checkMatch (pyRegex testStr : String) (mode : MatchMode)
let vcResults ← verify pgm inputCtx none .quiet
match vcResults[0]? with
| none => return .smtError "no VCs generated"
| some vc => return match vc.result with
| .pass => .match
| .fail => .noMatch
| .unknown => .smtError "unknown"
| .implementationError msg => .smtError s!"impl: {msg}"
| some vc => return match vc.outcome with
| .ok o =>
if o.isPass then .match
else if o.isRefuted || o.isCanBeTrueOrFalse then .noMatch
else .smtError "unknown"
| .error msg => .smtError s!"impl: {msg}"

def main (args : List String) : IO UInt32 := do
let logDir : Option String ← match args with
Expand Down
6 changes: 6 additions & 0 deletions Strata/DL/Imperative/EvalContext.lean
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ inductive PropertyType where
| divisionByZero
deriving Repr, DecidableEq

/-- Whether an unreachable path counts as pass for this property type.
Assertions pass vacuously when unreachable; covers fail. -/
def PropertyType.passWhenUnreachable : PropertyType → Bool
| .assert | .divisionByZero => true
| .cover => false

instance : ToFormat PropertyType where
format p := match p with
| .cover => "cover"
Expand Down
32 changes: 32 additions & 0 deletions Strata/DL/Imperative/MetaData.lean
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,12 @@ def MetaData.fileRange : MetaDataElem.Field P := .label "fileRange"

def MetaData.reachCheck : MetaDataElem.Field P := .label "reachCheck"

def MetaData.fullCheck : MetaDataElem.Field P := .label "fullCheck"

def MetaData.validityCheck : MetaDataElem.Field P := .label "validityCheck"

def MetaData.satisfiabilityCheck : MetaDataElem.Field P := .label "satisfiabilityCheck"

def MetaData.hasReachCheck {P : PureExpr} [BEq P.Ident] (md : MetaData P) : Bool :=
match md.findElem MetaData.reachCheck with
| some elem =>
Expand All @@ -188,6 +194,32 @@ def MetaData.hasReachCheck {P : PureExpr} [BEq P.Ident] (md : MetaData P) : Bool
| _ => false
| none => false

def MetaData.hasFullCheck {P : PureExpr} [BEq P.Ident] (md : MetaData P) : Bool :=
match md.findElem MetaData.fullCheck with
| some elem =>
match elem.value with
| .switch true => true
| _ => false
| none =>
-- Backward compatibility: reachCheck maps to fullCheck
md.hasReachCheck

def MetaData.hasValidityCheck {P : PureExpr} [BEq P.Ident] (md : MetaData P) : Bool :=
match md.findElem MetaData.validityCheck with
| some elem =>
match elem.value with
| .switch true => true
| _ => false
| none => false

def MetaData.hasSatisfiabilityCheck {P : PureExpr} [BEq P.Ident] (md : MetaData P) : Bool :=
match md.findElem MetaData.satisfiabilityCheck with
| some elem =>
match elem.value with
| .switch true => true
| _ => false
| none => false

def getFileRange {P : PureExpr} [BEq P.Ident] (md: MetaData P) : Option FileRange := do
let fileRangeElement <- md.findElem Imperative.MetaData.fileRange
match fileRangeElement.value with
Expand Down
117 changes: 61 additions & 56 deletions Strata/DL/Imperative/SMTUtils.lean
Original file line number Diff line number Diff line change
Expand Up @@ -184,54 +184,62 @@ private def processModel {P : PureExpr} [ToFormat P.Ident]
/--
Interprets the output of SMT solver.

Both the verdict line (sat/unsat/unknown) and the model (when sat) are
parsed using the SMTResponse DDM dialect. The verdict is parsed as a full
`Command`, while the model is parsed by targeting the
`SMTResponse.GetValueResponse` category directly via
`parseCategoryFromDialect`.

When `reachCheck` is `true`, the solver output contains two verdict lines:
the first is the reachability check result (are the path-condition assumptions
satisfiable?), and the second is the proof check result. The reachability
result is returned as `some Result`; when `reachCheck` is `false`, it is
`none`.
When two-sided checking is enabled, the solver output contains two verdict lines:
the first is the satisfiability check result (can the property be true?),
and the second is the validity check result (can the property be false?).
The satisfiability result is returned as the first element of the pair;
the validity result is the second element.

When only one check is enabled, the other is returned as `unknown`.
-/
def solverResult {P : PureExpr} [ToFormat P.Ident]
(typedVarToSMTFn : P.Ident → P.Ty → Except Format (String × Strata.SMT.TermType))
(vars : List P.TypedIdent) (output : IO.Process.Output)
(E : Strata.SMT.EncoderState) (smtsolver : String)
(reachCheck : Bool := false)
: IO (Except Format (Option (Result P.Ident) × Result P.Ident)) := do
(satisfiabilityCheck validityCheck : Bool)
: IO (Except Format (Result P.Ident × Result P.Ident)) := do
let stdout := output.stdout
-- Split the next line from the remaining stdout, returning (line, rest).
let splitLine (s : String) : String × String :=
let pos := s.find (· == '\n')
let line := (s.extract s.startPos pos).trimAscii.toString
let rest := s.extract pos s.endPos
(line, rest)
-- When reachCheck is true, the first line is the reachability verdict.
let (reachResult, proofStdout) ← if reachCheck then do
let (reachLine, remaining) := splitLine stdout
let reachResult : Result P.Ident ← do
match ← parseVerdict reachLine with
| some (.sat _) => pure (.sat [])
| some .unsat => pure .unsat
| _ => pure .unknown
pure (some reachResult, remaining.drop 1 |>.toString)
else
pure (none, stdout)
-- Parse the proof verdict from the (possibly trimmed) stdout.
let (verdictStr, rest) := splitLine proofStdout
match ← parseVerdict verdictStr with
| some (.sat _) =>
-- Parse model via SMTDDM targeting GetValueResponse category directly.
let pairs ← parseModelDDM rest
match processModel typedVarToSMTFn vars pairs E with
| .ok model => return .ok (reachResult, .sat model)
| .error _ => return .ok (reachResult, .sat [])
| some .unsat => return .ok (reachResult, .unsat)
| some .unknown => return .ok (reachResult, .unknown)
| _ =>

-- Helper to parse a single verdict and model
-- Skip lines until we find a verdict (sat/unsat/unknown) or run out of input.
-- This is needed because get-value commands in the file may produce error
-- output when the preceding check-sat returned unsat.
let skipToNextVerdict (input : String) : String :=
let lines := input.splitOn "\n"
let rest := lines.dropWhile (fun l =>
let t := l.trimAscii.toString
t != "sat" && t != "unsat" && t != "unknown" && !t.isEmpty)
"\n".intercalate rest

let parseVerdict (input : String) : IO (Option (Result P.Ident × String)) := do
let pos := input.find (· == '\n')
let verdict := input.extract input.startPos pos |>.trimAscii
let rest := (input.extract pos input.endPos |>.drop 1).toString
match verdict with
| "sat" =>
let rawModel ← parseModelDDM rest
match (processModel typedVarToSMTFn vars rawModel E) with
| .ok model => return some (.sat model, skipToNextVerdict rest)
| .error _ => return some (.sat [], skipToNextVerdict rest)
| "unsat" => return some (.unsat, skipToNextVerdict rest)
| "unknown" => return some (.unknown, skipToNextVerdict rest)
| _ => return none

-- Parse results based on which checks are enabled
match ← (if satisfiabilityCheck then parseVerdict stdout else pure (some (.unknown, stdout))) with
| some (satResult, remaining) =>
match ← (if validityCheck then parseVerdict remaining else pure (some (.unknown, remaining))) with
| some (validityResult, _) => return .ok (satResult, validityResult)
| none =>
let stderr := output.stderr
let hasExecError := stderr.contains "could not execute external process"
let hasFileError := stderr.contains "No such file or directory"
let suggestion :=
if (hasExecError || hasFileError) && smtsolver == Core.defaultSolver then
s!" \nEnsure {Core.defaultSolver} is on your PATH or use --solver to specify another SMT solver."
else ""
return .error s!"stderr:{stderr}{suggestion}\nsolver stdout: {output.stdout}\n"
| none =>
let stderr := output.stderr
let hasExecError := stderr.contains "could not execute external process"
let hasFileError := stderr.contains "No such file or directory"
Expand All @@ -258,34 +266,31 @@ def addLocationInfo {P : PureExpr} [BEq P.Ident]
Writes the proof obligation to file, discharge the obligation using SMT solver,
and parse the output of the SMT solver.

When `reachCheck` is `true`, the generated SMT file will contain two
`(check-sat)` commands (one for reachability, one for the proof obligation),
and the return value includes the reachability decision.
When two-sided checking is enabled, the generated SMT file will contain two
`(check-sat-assuming)` commands, one for `P ∧ Q` and one for `P ∧ ¬Q`,
and the return value includes both decisions.
-/
def dischargeObligation {P : PureExpr} [ToFormat P.Ident] [BEq P.Ident]
(encodeSMT : Strata.SMT.SolverM (List String × Strata.SMT.EncoderState))
(typedVarToSMTFn : P.Ident → P.Ty → Except Format (String × Strata.SMT.TermType))
(vars : List P.TypedIdent)
(md : Imperative.MetaData P)
(_md : Imperative.MetaData P)
(smtsolver filename : String)
(solver_options : Array String) (printFilename : Bool)
(reachCheck : Bool := false) :
IO (Except Format (Option (Result P.Ident) × Result P.Ident × Strata.SMT.EncoderState)) := do
(satisfiabilityCheck validityCheck : Bool) :
IO (Except Format (Result P.Ident × Result P.Ident × Strata.SMT.EncoderState)) := do
let handle ← IO.FS.Handle.mk filename IO.FS.Mode.write
let solver ← Strata.SMT.Solver.fileWriter handle

let encodeAndCheck : Strata.SMT.SolverM (List String × Strata.SMT.EncoderState) := do
let result ← encodeSMT
addLocationInfo md ("sat-message", s!"\"Assertion cannot be proven\"")
let _ ← Strata.SMT.Solver.checkSat result.1 -- Will return unknown for Solver.fileWriter
return result
let ((_ids, estate), _solverState) ← encodeAndCheck.run solver
-- encodeSMT (which calls encodeCore) emits check-sat commands internally
let ((_ids, estate), _solverState) ← encodeSMT.run solver

if printFilename then IO.println s!"Wrote problem to {filename}."

let solver_output ← runSolver smtsolver (#[filename] ++ solver_options)
match ← solverResult typedVarToSMTFn vars solver_output estate smtsolver (reachCheck := reachCheck) with
match ← solverResult typedVarToSMTFn vars solver_output estate smtsolver satisfiabilityCheck validityCheck with
| .error e => return .error e
| .ok (reachDecision, result) => return .ok (reachDecision, result, estate)
| .ok (satResult, validityResult) => return .ok (satResult, validityResult, estate)

---------------------------------------------------------------------
end -- public section
Expand Down
16 changes: 16 additions & 0 deletions Strata/DL/SMT/Solver.lean
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,22 @@ def checkSat (vars : List String) : SolverM Decision := do
return Decision.unknown
| other => throw (IO.userError s!"Unrecognized solver output: {other}")

def checkSatAssuming (assumptions : List String) (vars : List String) : SolverM Decision := do
let assumptionsStr := String.intercalate " " assumptions
emitln s!"(check-sat-assuming ({assumptionsStr}))"
let result := (← readlnD "unknown").trimAscii.toString
match result with
| "sat" =>
if !vars.isEmpty then
getValue vars
return Decision.sat
| "unsat" => return Decision.unsat
| "unknown" =>
if !vars.isEmpty then
getValue vars
return Decision.unknown
| other => throw (IO.userError s!"Unrecognized solver output: {other}")

def reset : SolverM Unit :=
emitln "(reset)"

Expand Down
43 changes: 26 additions & 17 deletions Strata/Languages/Core/Options.lean
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ def VerboseMode.toNat (v : VerboseMode) : Nat :=
| .normal => 2
| .debug => 3

/-- Verification mode for SARIF error level mapping -/
inductive VerificationMode where
| deductive -- Prove correctness (unknown is error)
| bugFinding -- Find bugs assuming incomplete preconditions (only definite bugs are errors)
| bugFindingAssumingCompleteSpec -- Find bugs assuming complete preconditions (any counterexample is error)
deriving Repr, DecidableEq

instance : Inhabited VerificationMode where
default := .deductive

def VerboseMode.ofBool (b : Bool) : VerboseMode :=
match b with
| false => .quiet
Expand All @@ -45,6 +55,16 @@ instance : DecidableRel (fun a b : VerboseMode => a ≤ b) :=
/-- Default SMT solver to use -/
def defaultSolver : String := "cvc5"

/-- Check level: how much information to gather and display -/
inductive CheckLevel where
| minimal -- One check, simple messages (pass/fail/unknown)
| minimalVerbose -- One check, detailed messages (always true if reached, etc.)
| full -- Both checks, detailed messages (all 9 outcomes)
deriving Repr, DecidableEq

instance : Inhabited CheckLevel where
default := .minimal

structure VerifyOptions where
verbose : VerboseMode
parseOnly : Bool
Expand All @@ -62,22 +82,10 @@ structure VerifyOptions where
solver : String
/-- Directory to store VCs -/
vcDirectory : Option System.FilePath
/--
Enable reachability checks for all assert and cover statements.
When enabled, the verifier emits an extra `(check-sat)` before each
proof obligation to test whether the path-condition assumptions are
satisfiable. If they are not (i.e., the code path is unreachable),
the obligation is reported as `❗ unreachable` instead of the
normal pass/fail outcome.

This flag applies to **every** obligation that reaches the verifier,
including obligations generated by transforms such as call elimination
(precondition asserts) and procedure inlining. Individual statements
can also opt in via the `@[reachCheck]` annotation without enabling
this global flag.

Off by default. CLI: `--reach-check`. -/
reachCheck : Bool
/-- Check mode: deductive (prove correctness) or bugFinding (find bugs) -/
checkMode : VerificationMode
/-- Check amount: minimal (only necessary checks) or full (both checks for better messages) -/
checkLevel : CheckLevel

def VerifyOptions.default : VerifyOptions := {
verbose := .normal,
Expand All @@ -91,7 +99,8 @@ def VerifyOptions.default : VerifyOptions := {
outputSarif := false,
solver := defaultSolver
vcDirectory := .none
reachCheck := false
checkMode := .deductive
checkLevel := .minimal
}

instance : Inhabited VerifyOptions where
Expand Down
29 changes: 4 additions & 25 deletions Strata/Languages/Core/SMTEncoder.lean
Original file line number Diff line number Diff line change
Expand Up @@ -650,26 +650,9 @@ def toSMTTerms (E : Env) (es : List (LExpr CoreLParams.mono)) (ctx : SMT.Context
.ok ((et :: erestt), ctx)

/--
Encode a proof obligation -- which may be of type `assert` or `cover` -- into
SMTLIB.

Under conditions `P`, `assert(Q)` is encoded into SMTLib as follows:
```
(assert P)
(assert (not Q))
(check-sat)
```
If the result is `unsat`, then `P ∧ ¬Q` is unsatisfiable, which means `P => Q`
is valid. If the result is `sat`, then the assertion is violated.

Under conditions `P`, `cover(Q)` is encoded into SMTLib as follows:
```
(assert P)
(assert Q)
(check-sat)
```
If the result is `unsat`, then `P ∧ Q` is unsatisfiable, which means that the
cover is violated. If the result is `sat`, then the cover succeeds.
Encode a proof obligation into SMT terms: path conditions (P) and obligation (Q).
The obligation Q is returned without negation; see `encodeCore` in Verifier.lean
for the check-sat encoding that applies negation for validity checks.
-/
def ProofObligation.toSMTTerms (E : Env)
(d : Imperative.ProofObligation Expression) (ctx : SMT.Context := SMT.Context.default)
Expand All @@ -682,11 +665,7 @@ def ProofObligation.toSMTTerms (E : Env)
(λ ts => Term.app (.core .distinct) ts .bool)
let (assumptions_terms, ctx) ← Core.toSMTTerms E assumptions ctx useArrayTheory
let (obligation_pos_term, ctx) ← Core.toSMTTerm E [] d.obligation ctx useArrayTheory
let obligation_term :=
if d.property == .cover then
obligation_pos_term
else
Factory.not obligation_pos_term
let obligation_term := obligation_pos_term
.ok (distinct_assumptions ++ assumptions_terms, obligation_term, ctx)

---------------------------------------------------------------------
Expand Down
Loading
Loading