Skip to content

Commit eee972e

Browse files
keyboardDrummer-botshigoelthanhnguyen-awskeyboardDrummertautschnig
authored
Add modifies * wildcard support to Laurel (strata-org#1031)
Adds `modifies *` wildcard syntax to Laurel, as described in strata-org#1030. ## Changes **Grammar** (`LaurelGrammar.st`): Added `modifiesWildcard` rule that parses `modifies *`. **Parser** (`ConcreteToAbstractTreeTranslator.lean`): Handles `modifiesWildcard` by producing `StmtExpr.All` in the modifies list. **Formatter** (`AbstractToConcreteTreeTranslator.lean`): Formats a modifies list containing `All` back as `modifies *`. **ModifiesClauses pass** (`ModifiesClauses.lean`): - Added `hasModifiesWildcard` to detect `All` in the modifies list. - `transformModifiesClauses` skips frame condition generation for `modifies *` — the procedure may modify anything on the heap. - `filterBodyNonCompositeModifies` preserves the wildcard through the filter pass. **HeapParameterization**: No changes needed — the existing `!modif.isEmpty` check already marks procedures with `modifies *` as heap writers, even without a body. **Tests** (`T2_ModifiesClauses.lean`): Added three test cases: - `modifiesWildcardBodiless`: bodiless procedure with `modifies *` - `modifiesWildcardBodilessCaller`: caller that verifies heap state is lost after calling a `modifies *` procedure (assertion correctly fails) - `modifiesWildcardWithBody`: procedure with body and `modifies *` (no frame condition error) All 48 Laurel tests pass. Closes strata-org#1030 --------- Co-authored-by: keyboardDrummer-bot <keyboardDrummer-bot@users.noreply.github.com> Co-authored-by: Shilpi Goel <shigoel@gmail.com> Co-authored-by: thanhnguyen-aws <ntson@amazon.com> Co-authored-by: Remy Willems <rwillems@amazon.com> Co-authored-by: Michael Tautschnig <mt@debian.org>
1 parent 759865d commit eee972e

7 files changed

Lines changed: 70 additions & 10 deletions

File tree

Strata/Languages/Laurel/Grammar/AbstractToConcreteTreeTranslator.lean

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -188,9 +188,12 @@ private def ensuresClauseToArg (c : Condition) : Arg :=
188188
laurelOp "errorSummary" #[.strlit sr msg])
189189
laurelOp "ensuresClause" #[stmtExprToArg c.condition, errOpt]
190190

191-
private def modifiesClauseToArg (modifies : List StmtExprMd) : Arg :=
192-
let refs := modifies.map stmtExprToArg |>.toArray
193-
laurelOp "modifiesClause" #[commaSep refs]
191+
private def modifiesClausesToArgs (modifies : List StmtExprMd) : Array Arg :=
192+
let (wildcards, specific) := modifies.partition StmtExprMd.isWildcard
193+
let wildcardArgs := wildcards.map (fun _ => laurelOp "modifiesWildcard" #[]) |>.toArray
194+
let specificArgs := if specific.isEmpty then #[]
195+
else #[laurelOp "modifiesClause" #[commaSep (specific.map stmtExprToArg |>.toArray)]]
196+
wildcardArgs ++ specificArgs
194197

195198
private def procedureToOp (proc : Procedure) : Strata.Operation :=
196199
let opName := if proc.isFunctional then "function" else "procedure"
@@ -219,7 +222,7 @@ private def procedureToOp (proc : Procedure) : Strata.Operation :=
219222
(optionArg none, optionArg (some (laurelOp "body" #[stmtExprToArg body])))
220223
| .Opaque postconds impl modifies =>
221224
let ens := postconds.map ensuresClauseToArg |>.toArray
222-
let mods := if modifies.isEmpty then #[] else #[modifiesClauseToArg modifies]
225+
let mods := if modifies.isEmpty then #[] else modifiesClausesToArgs modifies
223226
let body := optionArg (impl.map fun e => laurelOp "body" #[stmtExprToArg e])
224227
(optionArg (some (laurelOp "opaqueSpec" #[seqArg ens, seqArg mods])), body)
225228
| .Abstract postconds =>

Strata/Languages/Laurel/Grammar/ConcreteToAbstractTreeTranslator.lean

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,11 @@ def translateModifiesClauses (arg : Arg) : TransM (List StmtExprMd) := do
378378
| q`Laurel.modifiesClause, #[refsArg] =>
379379
let refs ← translateModifiesExprs refsArg
380380
allModifies := allModifies ++ refs
381+
| q`Laurel.modifiesWildcard, #[] =>
382+
let src ← match (← get).uri with
383+
| some uri => pure (some (SourceRange.toFileRange uri clauseOp.ann))
384+
| none => pure none
385+
allModifies := allModifies ++ [mkStmtExprMd .All src]
381386
| _, _ => TransM.error s!"Expected modifiesClause operation, got {repr clauseOp.name}"
382387
| _ => TransM.error s!"Expected modifiesClause operation in modifies sequence"
383388
pure allModifies

Strata/Languages/Laurel/Grammar/LaurelGrammar.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ module
99
-- Laurel dialect definition, loaded from LaurelGrammar.st
1010
-- NOTE: Changes to LaurelGrammar.st are not automatically tracked by the build system.
1111
-- Update this file (e.g. this comment) to trigger a recompile after modifying LaurelGrammar.st.
12-
-- Last grammar change: added opaque keyword between invokeOn and ensures in procedure/function ops.
12+
-- Last grammar change: added modifiesWildcard for `modifies *` and opaque keyword
1313
public import Strata.DDM.Integration.Lean
1414
public meta import Strata.DDM.Integration.Lean
1515

Strata/Languages/Laurel/Grammar/LaurelGrammar.st

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ op ensuresClause(cond: StmtExpr, errorMessage: Option ErrorSummary): EnsuresClau
157157

158158
category ModifiesClause;
159159
op modifiesClause(refs: CommaSepBy StmtExpr): ModifiesClause => "\n modifies " refs;
160+
op modifiesWildcard: ModifiesClause => "\n modifies *";
160161

161162
category ReturnParameters;
162163
op returnParameters(parameters: CommaSepBy Parameter): ReturnParameters => "\n returns " "(" parameters ")";

Strata/Languages/Laurel/Laurel.lean

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,13 @@ def HighType.isBool : HighType → Bool
402402
| TBool => true
403403
| _ => false
404404

405+
/-- Check whether a single modifies entry is the wildcard (`*`). -/
406+
def StmtExprMd.isWildcard (m : StmtExprMd) : Bool := match m.val with | .All => true | _ => false
407+
408+
/-- Check whether a modifies list contains the wildcard (`*`). -/
409+
def hasModifiesWildcard (modifiesExprs : List StmtExprMd) : Bool :=
410+
modifiesExprs.any StmtExprMd.isWildcard
411+
405412
def Body.isExternal : Body → Bool
406413
| .External => true
407414
| _ => false

Strata/Languages/Laurel/ModifiesClauses.lean

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,9 @@ def hasHeapOut (proc : Procedure) : Bool :=
140140
Transform a single procedure: if it has modifies clauses, generate the frame
141141
condition and conjoin it with the postcondition, then clear the modifies list.
142142
143+
If the procedure has `modifies *`, no frame condition is generated (the procedure
144+
may modify anything on the heap), and the modifies list is simply cleared.
145+
143146
If the procedure has a `$heap` but no modifies clause, adds a postcondition
144147
that all allocated objects are preserved between heaps:
145148
`forall $obj: Composite, $fld: Field => $obj < $heap_in.nextReference ==> readField($heap_in, $obj, $fld) == readField($heap, $obj, $fld)`
@@ -149,7 +152,10 @@ def transformModifiesClauses (model: SemanticModel)
149152
match proc.body with
150153
| .External => .ok proc
151154
| .Opaque postconds impl modifiesExprs =>
152-
if hasHeapOut proc then
155+
if hasModifiesWildcard modifiesExprs then
156+
-- modifies * means the procedure can modify anything; no frame condition
157+
.ok { proc with body := .Opaque postconds impl [] }
158+
else if hasHeapOut proc then
153159
let heapInName : Identifier := "$heap_in"
154160
let heapName : Identifier := "$heap"
155161
let frameCondition := buildModifiesEnsures proc model modifiesExprs heapInName heapName
@@ -172,10 +178,13 @@ def filterBodyNonCompositeModifies (model : SemanticModel) (body : Body)
172178
match body with
173179
| .Opaque posts impl mods =>
174180
let (kept, diags) := mods.foldl (fun (acc, ds) e =>
175-
let ty := (computeExprType model e).val
176-
if isHeapRelevantType ty then (acc ++ [e], ds)
177-
else
178-
(acc, ds ++ [diagnosticFromSource e.source s!"modifies clause entry has non-composite type '{formatHighTypeVal ty}' and will be ignored"])
181+
match e.val with
182+
| .All => (acc ++ [e], ds) -- wildcard is always kept
183+
| _ =>
184+
let ty := (computeExprType model e).val
185+
if isHeapRelevantType ty then (acc ++ [e], ds)
186+
else
187+
(acc, ds ++ [diagnosticFromSource e.source s!"modifies clause entry has non-composite type '{formatHighTypeVal ty}' and will be ignored"])
179188
) ([], [])
180189
(.Opaque posts impl kept, diags)
181190
| other => (other, [])

StrataTest/Languages/Laurel/Examples/Objects/T2_ModifiesClauses.lean

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,41 @@ procedure newObjectDoNotCountForModifies()
105105
var c: Container := new Container;
106106
c#value := 1
107107
};
108+
109+
procedure modifiesWildcardBodiless(c: Container, d: Container)
110+
opaque
111+
modifies *;
112+
113+
procedure modifiesWildcardBodilessCaller() {
114+
var c: Container := new Container;
115+
var d: Container := new Container;
116+
var x: int := d#value;
117+
modifiesWildcardBodiless(c, d);
118+
assert x == d#value // this should fail because modifies * means anything can change
119+
//^^^^^^^^^^^^^^^^^^^ error: assertion does not hold
120+
};
121+
122+
procedure modifiesWildcardWithBody(c: Container, d: Container)
123+
opaque
124+
modifies *
125+
{
126+
c#value := 2;
127+
d#value := 3
128+
};
129+
130+
procedure modifiesWildcardAndSpecific(c: Container, d: Container)
131+
opaque
132+
modifies c
133+
modifies *;
134+
135+
procedure modifiesWildcardAndSpecificCaller() {
136+
var c: Container := new Container;
137+
var d: Container := new Container;
138+
var x: int := d#value;
139+
modifiesWildcardAndSpecific(c, d);
140+
assert x == d#value // fails because modifies * subsumes modifies c
141+
//^^^^^^^^^^^^^^^^^^^ error: assertion does not hold
142+
};
108143
"
109144

110145
#guard_msgs (drop info, error) in

0 commit comments

Comments
 (0)