Skip to content

Commit 2bf64c3

Browse files
committed
test(core): bounds obligations for Sequence.* and printer regression
New tests in StrataTest/Transform/PrecondElim.lean: - Test 10: Sequence.select in a procedure body emits the bounds assert (PrecondElim is unconditional — it inserts regardless of any surrounding requires guard; the SMT solver discharges). - Test 10c: Sequence.select inside a requires clause triggers the $$wf-procedure path (mkContractWFProc). - Test 10d: Sequence.select inside a function body triggers the function-body $$wf path (mkFuncWFStmts). - Test 11: collectPrecondAsserts attaches outOfBoundsAccess metadata for all four partial ops and a nested call. Mirrors OverflowCheckTest.lean. Also verifies Sequence.length emits no obligation (it is total). - Test 12: Sequence.empty printer regression for the commit-1 fix — renders as a generic call, not re.none(). New property-classification tests in StrataTest/Languages/Core/Tests/SarifOutputTests.lean cover all five PropertyType variants, exercising the SARIF wiring fix in commit 3. Collateral test updates for real behavioral changes: - StrataTest/Languages/Core/Examples/Seq.lean: expected VC output includes the new bounds obligations (all SMT-provable from the surrounding context, except the pre-existing contains_yes unknown). - StrataTest/Languages/Core/Tests/ProgramEvalTests.lean: Sequence func signatures now render with the attached requires clauses. - StrataTest/Languages/Core/Examples/Loops.lean: commit-1 printer fix propagates (re.none() -> top, error message format updated).
1 parent 60f3f6d commit 2bf64c3

5 files changed

Lines changed: 203 additions & 6 deletions

File tree

StrataTest/Languages/Core/Examples/Loops.lean

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -379,10 +379,10 @@ before_loop$_15:
379379
loop_entry$_1:
380380
assert [inv$_12]: x >= 0;
381381
assert [inv$_13]: x <= n;
382-
assert [inv$_14]: n < re.none();
382+
assert [inv$_14]: n < top;
383383
384384
-- Errors encountered during conversion:
385-
Unsupported construct in lopToExpr: 0-ary op not found: top
385+
Unsupported construct in handleZeroaryOps: unknown operation, rendering as generic call: top
386386
Context: Global scope:
387387
var loop_measure$_2 : int;
388388
assume [assume_loop_measure$_2]: loop_measure$_2 == n - x;

StrataTest/Languages/Core/Examples/Seq.lean

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,20 +68,41 @@ s_empty: Sequence.length(s) == 0
6868
Obligation:
6969
Sequence.length(Sequence.build(Sequence.build(Sequence.build(s, 10), 20), 30)) == 3
7070
71+
Label: assert_t_0_calls_Sequence.select_0
72+
Property: out-of-bounds access check
73+
Assumptions:
74+
s_empty: Sequence.length(s) == 0
75+
Obligation:
76+
true && 0 < Sequence.length(Sequence.build(Sequence.build(Sequence.build(s, 10), 20), 30))
77+
7178
Label: t_0
7279
Property: assert
7380
Assumptions:
7481
s_empty: Sequence.length(s) == 0
7582
Obligation:
7683
Sequence.select(Sequence.build(Sequence.build(Sequence.build(s, 10), 20), 30), 0) == 10
7784
85+
Label: assert_t_1_calls_Sequence.select_0
86+
Property: out-of-bounds access check
87+
Assumptions:
88+
s_empty: Sequence.length(s) == 0
89+
Obligation:
90+
true && 1 < Sequence.length(Sequence.build(Sequence.build(Sequence.build(s, 10), 20), 30))
91+
7892
Label: t_1
7993
Property: assert
8094
Assumptions:
8195
s_empty: Sequence.length(s) == 0
8296
Obligation:
8397
Sequence.select(Sequence.build(Sequence.build(Sequence.build(s, 10), 20), 30), 1) == 20
8498
99+
Label: assert_t_2_calls_Sequence.select_0
100+
Property: out-of-bounds access check
101+
Assumptions:
102+
s_empty: Sequence.length(s) == 0
103+
Obligation:
104+
true && 2 < Sequence.length(Sequence.build(Sequence.build(Sequence.build(s, 10), 20), 30))
105+
85106
Label: t_2
86107
Property: assert
87108
Assumptions:
@@ -102,14 +123,26 @@ Obligation: t_length
102123
Property: assert
103124
Result: ✅ pass
104125
126+
Obligation: assert_t_0_calls_Sequence.select_0
127+
Property: out-of-bounds access check
128+
Result: ✅ pass
129+
105130
Obligation: t_0
106131
Property: assert
107132
Result: ✅ pass
108133
134+
Obligation: assert_t_1_calls_Sequence.select_0
135+
Property: out-of-bounds access check
136+
Result: ✅ pass
137+
109138
Obligation: t_1
110139
Property: assert
111140
Result: ✅ pass
112141
142+
Obligation: assert_t_2_calls_Sequence.select_0
143+
Property: out-of-bounds access check
144+
Result: ✅ pass
145+
113146
Obligation: t_2
114147
Property: assert
115148
Result: ✅ pass
@@ -185,34 +218,69 @@ s_empty: Sequence.length(s) == 0
185218
Obligation:
186219
Sequence.length(Sequence.append(Sequence.build(Sequence.build(Sequence.build(s, 10), 20), 30), Sequence.build(Sequence.build(s, 40), 50))) == 5
187220
221+
Label: assert_append_elem_0_calls_Sequence.select_0
222+
Property: out-of-bounds access check
223+
Assumptions:
224+
s_empty: Sequence.length(s) == 0
225+
Obligation:
226+
true && 0 < Sequence.length(Sequence.append(Sequence.build(Sequence.build(Sequence.build(s, 10), 20), 30), Sequence.build(Sequence.build(s, 40), 50)))
227+
188228
Label: append_elem_0
189229
Property: assert
190230
Assumptions:
191231
s_empty: Sequence.length(s) == 0
192232
Obligation:
193233
Sequence.select(Sequence.append(Sequence.build(Sequence.build(Sequence.build(s, 10), 20), 30), Sequence.build(Sequence.build(s, 40), 50)), 0) == 10
194234
235+
Label: assert_append_elem_4_calls_Sequence.select_0
236+
Property: out-of-bounds access check
237+
Assumptions:
238+
s_empty: Sequence.length(s) == 0
239+
Obligation:
240+
true && 4 < Sequence.length(Sequence.append(Sequence.build(Sequence.build(Sequence.build(s, 10), 20), 30), Sequence.build(Sequence.build(s, 40), 50)))
241+
195242
Label: append_elem_4
196243
Property: assert
197244
Assumptions:
198245
s_empty: Sequence.length(s) == 0
199246
Obligation:
200247
Sequence.select(Sequence.append(Sequence.build(Sequence.build(Sequence.build(s, 10), 20), 30), Sequence.build(Sequence.build(s, 40), 50)), 4) == 50
201248
249+
Label: set_u_calls_Sequence.update_0
250+
Property: out-of-bounds access check
251+
Assumptions:
252+
s_empty: Sequence.length(s) == 0
253+
Obligation:
254+
true && 1 < Sequence.length(Sequence.build(Sequence.build(Sequence.build(s, 10), 20), 30))
255+
202256
Label: update_length
203257
Property: assert
204258
Assumptions:
205259
s_empty: Sequence.length(s) == 0
206260
Obligation:
207261
Sequence.length(Sequence.update(Sequence.build(Sequence.build(Sequence.build(s, 10), 20), 30), 1, 99)) == 3
208262
263+
Label: assert_update_same_calls_Sequence.select_0
264+
Property: out-of-bounds access check
265+
Assumptions:
266+
s_empty: Sequence.length(s) == 0
267+
Obligation:
268+
true && 1 < Sequence.length(Sequence.update(Sequence.build(Sequence.build(Sequence.build(s, 10), 20), 30), 1, 99))
269+
209270
Label: update_same
210271
Property: assert
211272
Assumptions:
212273
s_empty: Sequence.length(s) == 0
213274
Obligation:
214275
Sequence.select(Sequence.update(Sequence.build(Sequence.build(Sequence.build(s, 10), 20), 30), 1, 99), 1) == 99
215276
277+
Label: assert_update_other_calls_Sequence.select_0
278+
Property: out-of-bounds access check
279+
Assumptions:
280+
s_empty: Sequence.length(s) == 0
281+
Obligation:
282+
true && 0 < Sequence.length(Sequence.update(Sequence.build(Sequence.build(Sequence.build(s, 10), 20), 30), 1, 99))
283+
216284
Label: update_other
217285
Property: assert
218286
Assumptions:
@@ -227,27 +295,55 @@ s_empty: Sequence.length(s) == 0
227295
Obligation:
228296
Sequence.contains(Sequence.build(Sequence.build(Sequence.build(s, 10), 20), 30), 20)
229297
298+
Label: set_u_calls_Sequence.take_0
299+
Property: out-of-bounds access check
300+
Assumptions:
301+
s_empty: Sequence.length(s) == 0
302+
Obligation:
303+
true && 2 <= Sequence.length(Sequence.build(Sequence.build(Sequence.build(s, 10), 20), 30))
304+
230305
Label: take_length
231306
Property: assert
232307
Assumptions:
233308
s_empty: Sequence.length(s) == 0
234309
Obligation:
235310
Sequence.length(Sequence.take(Sequence.build(Sequence.build(Sequence.build(s, 10), 20), 30), 2)) == 2
236311
312+
Label: assert_take_elem_calls_Sequence.select_0
313+
Property: out-of-bounds access check
314+
Assumptions:
315+
s_empty: Sequence.length(s) == 0
316+
Obligation:
317+
true && 0 < Sequence.length(Sequence.take(Sequence.build(Sequence.build(Sequence.build(s, 10), 20), 30), 2))
318+
237319
Label: take_elem
238320
Property: assert
239321
Assumptions:
240322
s_empty: Sequence.length(s) == 0
241323
Obligation:
242324
Sequence.select(Sequence.take(Sequence.build(Sequence.build(Sequence.build(s, 10), 20), 30), 2), 0) == 10
243325
326+
Label: set_u_calls_Sequence.drop_0
327+
Property: out-of-bounds access check
328+
Assumptions:
329+
s_empty: Sequence.length(s) == 0
330+
Obligation:
331+
true && 1 <= Sequence.length(Sequence.build(Sequence.build(Sequence.build(s, 10), 20), 30))
332+
244333
Label: drop_length
245334
Property: assert
246335
Assumptions:
247336
s_empty: Sequence.length(s) == 0
248337
Obligation:
249338
Sequence.length(Sequence.drop(Sequence.build(Sequence.build(Sequence.build(s, 10), 20), 30), 1)) == 2
250339
340+
Label: assert_drop_elem_calls_Sequence.select_0
341+
Property: out-of-bounds access check
342+
Assumptions:
343+
s_empty: Sequence.length(s) == 0
344+
Obligation:
345+
true && 0 < Sequence.length(Sequence.drop(Sequence.build(Sequence.build(Sequence.build(s, 10), 20), 30), 1))
346+
251347
Label: drop_elem
252348
Property: assert
253349
Assumptions:
@@ -261,22 +357,42 @@ Obligation: append_length
261357
Property: assert
262358
Result: ✅ pass
263359
360+
Obligation: assert_append_elem_0_calls_Sequence.select_0
361+
Property: out-of-bounds access check
362+
Result: ✅ pass
363+
264364
Obligation: append_elem_0
265365
Property: assert
266366
Result: ✅ pass
267367
368+
Obligation: assert_append_elem_4_calls_Sequence.select_0
369+
Property: out-of-bounds access check
370+
Result: ✅ pass
371+
268372
Obligation: append_elem_4
269373
Property: assert
270374
Result: ✅ pass
271375
376+
Obligation: set_u_calls_Sequence.update_0
377+
Property: out-of-bounds access check
378+
Result: ✅ pass
379+
272380
Obligation: update_length
273381
Property: assert
274382
Result: ✅ pass
275383
384+
Obligation: assert_update_same_calls_Sequence.select_0
385+
Property: out-of-bounds access check
386+
Result: ✅ pass
387+
276388
Obligation: update_same
277389
Property: assert
278390
Result: ✅ pass
279391
392+
Obligation: assert_update_other_calls_Sequence.select_0
393+
Property: out-of-bounds access check
394+
Result: ✅ pass
395+
280396
Obligation: update_other
281397
Property: assert
282398
Result: ✅ pass
@@ -285,18 +401,34 @@ Obligation: contains_yes
285401
Property: assert
286402
Result: ❓ unknown
287403
404+
Obligation: set_u_calls_Sequence.take_0
405+
Property: out-of-bounds access check
406+
Result: ✅ pass
407+
288408
Obligation: take_length
289409
Property: assert
290410
Result: ✅ pass
291411
412+
Obligation: assert_take_elem_calls_Sequence.select_0
413+
Property: out-of-bounds access check
414+
Result: ✅ pass
415+
292416
Obligation: take_elem
293417
Property: assert
294418
Result: ✅ pass
295419
420+
Obligation: set_u_calls_Sequence.drop_0
421+
Property: out-of-bounds access check
422+
Result: ✅ pass
423+
296424
Obligation: drop_length
297425
Property: assert
298426
Result: ✅ pass
299427
428+
Obligation: assert_drop_elem_calls_Sequence.select_0
429+
Property: out-of-bounds access check
430+
Result: ✅ pass
431+
300432
Obligation: drop_elem
301433
Property: assert
302434
Result: ✅ pass

StrataTest/Languages/Core/Tests/GenericCallFallbackTest.lean

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,20 @@ info: "assert [q1]: fourArgFn(a, b, c, d);\n\n-- Errors encountered during conve
114114
let e := mkCall4 "fourArgFn" (mkFvar "a") (mkFvar "b") (mkFvar "c") (mkFvar "d")
115115
IO.println (repr (fmtStmt (mkAssert "q1" e) #["a", "b", "c", "d"]))
116116

117+
-- -----------------------------------------------------------------------
118+
-- Test: unknown 0-ary operation renders as a generic call (e.g.
119+
-- `Sequence.empty`, which is registered in the factory but not yet
120+
-- parseable in raw Core source).
121+
-- -----------------------------------------------------------------------
122+
123+
/--
124+
info: "assert [z1]: Sequence.empty;\n\n-- Errors encountered during conversion:\nUnsupported construct in handleZeroaryOps: unknown operation, rendering as generic call: Sequence.empty\nContext: Global scope:"
125+
-/
126+
#guard_msgs in
127+
#eval do
128+
let e := mkOp "Sequence.empty"
129+
IO.println (repr (fmtStmt (mkAssert "z1" e) #[]))
130+
117131
-- -----------------------------------------------------------------------
118132
-- Test: known operations still render correctly (regression check)
119133
-- -----------------------------------------------------------------------

StrataTest/Languages/Core/Tests/ProgramEvalTests.lean

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,12 +85,16 @@ func update : ∀[k, v]. ((m : (Map k v)) (i : k) (x : v)) → (Map k v);
8585
func Sequence.length : ∀[a]. ((s : (Sequence a))) → int;
8686
func Sequence.empty : ∀[a]. () → (Sequence a);
8787
func Sequence.append : ∀[a]. ((s1 : (Sequence a)) (s2 : (Sequence a))) → (Sequence a);
88-
func Sequence.select : ∀[a]. ((s : (Sequence a)) (i : int)) → a;
88+
func Sequence.select : ∀[a]. ((s : (Sequence a)) (i : int)) → a
89+
requires 0 <= i && i < Sequence.length(s);
8990
func Sequence.build : ∀[a]. ((s : (Sequence a)) (v : a)) → (Sequence a);
90-
func Sequence.update : ∀[a]. ((s : (Sequence a)) (i : int) (v : a)) → (Sequence a);
91+
func Sequence.update : ∀[a]. ((s : (Sequence a)) (i : int) (v : a)) → (Sequence a)
92+
requires 0 <= i && i < Sequence.length(s);
9193
func Sequence.contains : ∀[a]. ((s : (Sequence a)) (v : a)) → bool;
92-
func Sequence.take : ∀[a]. ((s : (Sequence a)) (n : int)) → (Sequence a);
93-
func Sequence.drop : ∀[a]. ((s : (Sequence a)) (n : int)) → (Sequence a);
94+
func Sequence.take : ∀[a]. ((s : (Sequence a)) (n : int)) → (Sequence a)
95+
requires 0 <= n && n <= Sequence.length(s);
96+
func Sequence.drop : ∀[a]. ((s : (Sequence a)) (n : int)) → (Sequence a)
97+
requires 0 <= n && n <= Sequence.length(s);
9498
func Triggers.empty : () → Triggers;
9599
func Triggers.addGroup : ((g : TriggerGroup) (t : Triggers)) → Triggers;
96100
func TriggerGroup.empty : () → TriggerGroup;

StrataTest/Transform/PrecondElim.lean

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,4 +418,51 @@ procedure test (inout g : int, y : int)
418418
#guard_msgs in
419419
#eval (Std.format (transformProgram loopGuardPrecondPgm))
420420

421+
/-! ### Test 10: `collectPrecondAsserts` tags Sequence bounds obligations with `outOfBoundsAccess`
422+
423+
Exercises the full `collectPrecondAsserts` path — the code called by
424+
`transformStmt` / `mkContractWFProc` / `mkFuncWFProc` — and inspects the
425+
metadata on the generated assert. Mirrors `OverflowCheckTest.lean`. -/
426+
427+
section SeqBoundsObligations
428+
429+
open Strata Core Lambda Core.PrecondElim Imperative
430+
431+
/-- Shared fvar fixtures so each per-op case below is a one-liner. -/
432+
private def fxS : Core.Expression.Expr := .fvar () ⟨"s", ()⟩ (some (Core.seqTy .int))
433+
private def fxI : Core.Expression.Expr := .fvar () ⟨"i", ()⟩ (some .int)
434+
private def fxV : Core.Expression.Expr := .fvar () ⟨"v", ()⟩ (some .int)
435+
private def fxN : Core.Expression.Expr := .fvar () ⟨"n", ()⟩ (some .int)
436+
private def fxJ : Core.Expression.Expr := .fvar () ⟨"j", ()⟩ (some .int)
437+
438+
/-- Check that `collectPrecondAsserts` produces exactly `expectedCount`
439+
obligations from `expr`, each tagged with `outOfBoundsAccess`. -/
440+
private def assertOutOfBoundsObligations
441+
(expr : Core.Expression.Expr) (expectedCount : Nat) : IO Unit := do
442+
let stmts := collectPrecondAsserts Core.Factory expr "test" #[]
443+
assert! stmts.length == expectedCount
444+
for s in stmts do
445+
let md : MetaData Core.Expression := match s with
446+
| Statement.assert _ _ md => md | _ => #[]
447+
assert! md.getPropertyType == some MetaData.outOfBoundsAccess
448+
449+
-- Sequence.select / update / take / drop each emit one out-of-bounds obligation.
450+
#eval assertOutOfBoundsObligations (LExpr.mkApp () Core.seqSelectOp [fxS, fxI]) 1
451+
#eval assertOutOfBoundsObligations (LExpr.mkApp () Core.seqUpdateOp [fxS, fxI, fxV]) 1
452+
#eval assertOutOfBoundsObligations (LExpr.mkApp () Core.seqTakeOp [fxS, fxN]) 1
453+
#eval assertOutOfBoundsObligations (LExpr.mkApp () Core.seqDropOp [fxS, fxN]) 1
454+
455+
-- Nested: `Sequence.select(Sequence.update(s, i, v), j)` emits two
456+
-- obligations (one per partial call), both tagged `outOfBoundsAccess`.
457+
#eval assertOutOfBoundsObligations
458+
(LExpr.mkApp () Core.seqSelectOp [LExpr.mkApp () Core.seqUpdateOp [fxS, fxI, fxV], fxJ]) 2
459+
460+
-- Sequence.length is total: no precondition obligations generated.
461+
#eval do
462+
let stmts := collectPrecondAsserts Core.Factory
463+
(LExpr.mkApp () Core.seqLengthOp [fxS]) "test" #[]
464+
assert! stmts.isEmpty
465+
466+
end SeqBoundsObligations
467+
421468
end PrecondElimTests

0 commit comments

Comments
 (0)