Skip to content

Commit 1b05546

Browse files
authored
Fix issue #297 and #268, make proc inlining visit subblocks (#302)
This patch fixes - Issue #297: BoogiePrelude.lean's `timedelta` was not updating `hours_i` - Issue #268: procedure inlining was reducing nondeterminism because it was missing havocs to output vars - In fact, this depends on how the "correctness" of procedure inlining is defined; if we say that procedure inlining is allowed to reduce nondeterminism, the original implementation was correct of course, but since this makes procedure inlining inherently unsound to use for deductive verification I chose to fix it. This is all under the assumption that the spec of the inlining procedure exactly represents the actual behavior of the contents of the procedure. - Procedure inlining wasn't visiting subblocks, which was fixed here. This required small updates in the function signature of `inlineCallCmd` and `callElimCmd` so that they receive Command rather than Statement. For these changes, checks are needed to confirm whether Python applications of interest are not affected. *Issue #, if available:* *Description of changes:* By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.
1 parent a685d97 commit 1b05546

10 files changed

Lines changed: 188 additions & 110 deletions

File tree

Strata/Languages/Python/BoogiePrelude.lean

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -190,14 +190,13 @@ procedure timedelta(days: IntOrNone, hours: IntOrNone) returns (delta : int, may
190190
spec{
191191
}
192192
{
193-
havoc delta;
194193
var days_i : int := 0;
195194
if (IntOrNone..isIntOrNone_mk_int(days)) {
196195
days_i := IntOrNone_int_val(days);
197196
}
198197
var hours_i : int := 0;
199198
if (IntOrNone..isIntOrNone_mk_int(hours)) {
200-
days_i := IntOrNone_int_val(hours);
199+
hours_i := IntOrNone_int_val(hours);
201200
}
202201
assume [assume_timedelta_sign_matches]: (delta == (((days_i * 24) + hours_i) * 3600) * 1000000);
203202
};
@@ -244,7 +243,6 @@ spec {
244243
ensures (Datetime_get_timedelta(d) == Timedelta_mk(0,0,0));
245244
}
246245
{
247-
havoc d;
248246
assume [assume_datetime_now]: (Datetime_get_timedelta(d) == Timedelta_mk(0,0,0));
249247
};
250248

@@ -253,7 +251,6 @@ spec {
253251
ensures (Datetime_get_timedelta(d) == Timedelta_mk(0,0,0));
254252
}
255253
{
256-
havoc d;
257254
assume [assume_datetime_now]: (Datetime_get_timedelta(d) == Timedelta_mk(0,0,0));
258255
};
259256

@@ -282,8 +279,7 @@ axiom [Datetime_lt_ax]:
282279

283280
type Date;
284281
procedure datetime_date(dt: Datetime) returns (d : Datetime, maybe_except: ExceptOrNone)
285-
spec{}
286-
{havoc d;};
282+
spec{};
287283

288284
function datetime_to_str(dt : Datetime) : string;
289285

@@ -295,7 +291,6 @@ spec{
295291
ensures [ensures_str_strp_reverse]: (forall dt : Datetime :: {d == dt} ((time == datetime_to_str(dt)) <==> (d == dt)));
296292
}
297293
{
298-
havoc d;
299294
assume [assume_str_strp_reverse]: (forall dt : Datetime :: {d == dt} ((time == datetime_to_str(dt)) <==> (d == dt)));
300295
};
301296

@@ -310,24 +305,16 @@ procedure import(names : ListStr) returns ();
310305
procedure print(msg : string, opt : StrOrNone) returns ();
311306

312307
procedure json_dumps(msg : DictStrAny, opt_indent : IntOrNone) returns (s: string, maybe_except: ExceptOrNone)
313-
spec{}
314-
{havoc s;}
315-
;
308+
spec{};
316309

317310
procedure json_loads(msg : string) returns (d: DictStrAny, maybe_except: ExceptOrNone)
318-
spec{}
319-
{havoc d;}
320-
;
311+
spec{};
321312

322313
procedure input(msg : string) returns (result: string, maybe_except: ExceptOrNone)
323-
spec{}
324-
{havoc result;}
325-
;
314+
spec{};
326315

327316
procedure random_choice(l : ListStr) returns (result: string, maybe_except: ExceptOrNone)
328-
spec{}
329-
{havoc result;}
330-
;
317+
spec{};
331318

332319
function str_in_list_str(s : string, l: ListStr) : bool;
333320

Strata/Transform/BoogieTransform.lean

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -187,32 +187,64 @@ def createOldVarsSubst
187187
| ((v', _), v) => (v, createFvar v')
188188

189189

190-
191190
/- Generic runner functions -/
192191

193-
def runStmts (f : Statement → Program → BoogieTransformM (List Statement))
192+
-- Only visit top-level statements and run f
193+
def runStmts (f : Command → Program → BoogieTransformM (List Statement))
194194
(ss : List Statement) (inputProg : Program)
195195
: BoogieTransformM (List Statement) := do
196196
match ss with
197197
| [] => return []
198198
| s :: ss =>
199-
let s' := (f s inputProg)
199+
let s' := match s with
200+
| .cmd c => f c inputProg
201+
| s => return [s]
200202
let ss' := (runStmts f ss inputProg)
201203
return (← s') ++ (← ss')
202204

203-
def runProcedures (f : Statement → Program → BoogieTransformM (List Statement))
205+
-- Recursively visit all blocks and run f
206+
def runStmtsRec (f : Command → Program → BoogieTransformM (List Statement))
207+
(ss : List Statement) (inputProg : Program)
208+
: BoogieTransformM (List Statement) := do
209+
match ss with
210+
| [] => return []
211+
| s :: ss' =>
212+
let ss'' ← (runStmtsRec f ss' inputProg)
213+
let sres ← (match s with
214+
| .cmd c => do
215+
let res ← f c inputProg
216+
return res
217+
| .block lbl b md => do
218+
let b' ← runStmtsRec f b inputProg
219+
return [.block lbl b' md]
220+
| .ite c thenb elseb md => do
221+
let thenb' ← runStmtsRec f thenb inputProg
222+
let elseb' ← runStmtsRec f elseb inputProg
223+
return [.ite c thenb' elseb' md]
224+
| .loop guard measure invariant body md => do
225+
let body' ← runStmtsRec f body inputProg
226+
return [.loop guard measure invariant body' md]
227+
| .goto _lbl _md =>
228+
return [s])
229+
return (sres ++ ss'')
230+
termination_by sizeOf ss
231+
decreasing_by
232+
all_goals (unfold Imperative.instSizeOfBlock; decreasing_tactic)
233+
234+
def runProcedures (f : Command → Program → BoogieTransformM (List Statement))
204235
(dcls : List Decl) (inputProg : Program)
205236
: BoogieTransformM (List Decl) := do
206237
match dcls with
207238
| [] => return []
208239
| d :: ds =>
209240
match d with
210241
| .proc p md =>
211-
return Decl.proc { p with body := ← (runStmts f p.body inputProg ) } md ::
212-
(← (runProcedures f ds inputProg))
242+
return Decl.proc {
243+
p with body := ← (runStmtsRec f p.body inputProg)
244+
} md :: (← (runProcedures f ds inputProg))
213245
| _ => return d :: (← (runProcedures f ds inputProg))
214246

215-
def runProgram (f : Statement → Program → BoogieTransformM (List Statement))
247+
def runProgram (f : Command → Program → BoogieTransformM (List Statement))
216248
(p : Program) : BoogieTransformM Program := do
217249
let newDecls ← runProcedures f p.decls p
218250
return { decls := newDecls }

Strata/Transform/CallElim.lean

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ namespace CallElim
1414
open Boogie.Transform
1515

1616
/--
17-
The main call elimination transformation algorithm on a single statement.
17+
The main call elimination transformation algorithm on a single command.
1818
The returned result is a sequence of statements
1919
-/
20-
def callElimStmt (st: Statement) (p : Program)
20+
def callElimCmd (cmd: Command) (p : Program)
2121
: BoogieTransformM (List Statement) := do
22-
match st with
22+
match cmd with
2323
| .call lhs procName args _ =>
2424

2525
let some proc := Program.Procedure.find? p procName | throw s!"Procedure {procName} not found in program"
@@ -83,18 +83,19 @@ def callElimStmt (st: Statement) (p : Program)
8383
(arg_subst ++ ret_subst)
8484

8585
return argInit ++ outInit ++ oldInit ++ asserts ++ havocs ++ assumes
86-
| _ => return [ st ]
86+
| _ => return [ .cmd cmd ]
8787

88+
-- Visits top-level statements and do call elimination
8889
def callElimStmts (ss: List Statement) (prog : Program) :=
89-
runStmts callElimStmt ss prog
90+
runStmts callElimCmd ss prog
9091

9192
def callElimL (dcls : List Decl) (prog : Program) :=
92-
runProcedures callElimStmt dcls prog
93+
runProcedures callElimCmd dcls prog
9394

9495
/-- Call Elimination for an entire program by walking through all procedure
9596
bodies -/
9697
def callElim' (p : Program) : BoogieTransformM Program :=
97-
runProgram callElimStmt p
98+
runProgram callElimCmd p
9899

99100
end CallElim
100101
end Boogie

Strata/Transform/CallElimCorrect.lean

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ theorem callElimBlockNoExcept :
173173
-- ∧ WF.WFStatementsProp p sts
174174
:= by
175175
intros st p wf
176-
simp [Transform.run, runStmts, CallElim.callElimStmts, CallElim.callElimStmt]
176+
simp [Transform.run, runStmts, CallElim.callElimStmts, CallElim.callElimCmd]
177177
cases st with
178178
| block l b md => exists [.block l b md]
179179
| ite cd tb eb md => exists [.ite cd tb eb md]
@@ -188,6 +188,7 @@ theorem callElimBlockNoExcept :
188188
next heq =>
189189
cases heq
190190
next st =>
191+
simp only [] -- reduce match
191192
split <;>
192193
simp only [StateT.run, bind, ExceptT.bind, ExceptT.mk, StateT.bind, genArgExprIdentsTrip, ne_eq, liftM,
193194
monadLift, MonadLift.monadLift, ExceptT.lift, Functor.map, List.unzip_snd, ite_not, ExceptT.bindCont, ExceptT.map,
@@ -272,10 +273,7 @@ theorem callElimBlockNoExcept :
272273
unfold BoogieIdent.unres at *
273274
split at wf <;> simp_all
274275
. -- other case
275-
exfalso
276-
next st Hfalse =>
277-
specialize Hfalse lhs procName args md
278-
simp_all
276+
grind
279277

280278
theorem postconditions_subst_unwrap :
281279
substPost ∈
@@ -3291,7 +3289,7 @@ theorem callElimStatementCorrect [LawfulBEq Expression.Expr] :
32913289
:= by
32923290
intros Hp Hgv Heval Hwfc Hwf Hwfp Hwfgen Hwfgenst Helim
32933291
cases st <;>
3294-
simp [Transform.runWith, StateT.run, callElimStmts, runStmts, callElimStmt,
3292+
simp [Transform.runWith, StateT.run, callElimStmts, runStmts, callElimCmd,
32953293
pure, ExceptT.pure, ExceptT.mk, StateT.pure,
32963294
bind, ExceptT.bind, ExceptT.bindCont, StateT.bind,
32973295
] at Helim

Strata/Transform/ProcedureInlining.lean

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -191,12 +191,15 @@ This function does not update the specification because inlineCallStmt will not
191191
use the specification. This will have to change if Strata also wants to support
192192
the reachability query.
193193
-/
194-
def inlineCallStmt (st: Statement) (p : Program)
194+
def inlineCallCmd (excluded_calls:List String := [])
195+
(cmd: Command) (p : Program)
195196
: BoogieTransformM (List Statement) :=
196197
open Lambda in do
197-
match st with
198+
match cmd with
198199
| .call lhs procName args _ =>
199200

201+
if procName ∈ excluded_calls then return [.cmd cmd] else
202+
200203
let some proc := Program.Procedure.find? p procName
201204
| throw s!"Procedure {procName} not found in program"
202205

@@ -223,12 +226,14 @@ def inlineCallStmt (st: Statement) (p : Program)
223226

224227
-- Create a fresh var statement for each LHS
225228
let outputTrips ← genOutExprIdentsTrip sigOutputs sigOutputs.unzip.fst
226-
let outputInit := createInitVars
229+
let outputInits := createInitVars
227230
(outputTrips.map (fun ((tmpvar,ty),orgvar) => ((orgvar,ty),tmpvar)))
231+
let outputHavocs := outputTrips.map (fun
232+
(_,orgvar) => Statement.havoc orgvar)
228233
-- Create a var statement for each procedure input arguments.
229234
-- The input parameter expression is assigned to these new vars.
230235
--let inputTrips ← genArgExprIdentsTrip sigInputs args
231-
let inputInit := createInits (sigInputs.zip args)
236+
let inputInits := createInits (sigInputs.zip args)
232237
-- Assign the output variables in the signature to the actual output
233238
-- variables used in the callee.
234239
let outputSetStmts :=
@@ -243,29 +248,20 @@ def inlineCallStmt (st: Statement) (p : Program)
243248
outs_lhs_and_sig
244249

245250
let stmts:List (Imperative.Stmt Boogie.Expression Boogie.Command)
246-
:= inputInit ++ outputInit ++ proc.body ++ outputSetStmts
251+
:= inputInits ++ outputInits ++ outputHavocs ++ proc.body ++
252+
outputSetStmts
247253

248254
return [.block (procName ++ "$inlined") stmts]
249-
| _ => return [st]
250255

251-
def inlineCallStmts (ss: List Statement) (prog : Program)
252-
: BoogieTransformM (List Statement) := do match ss with
253-
| [] => return []
254-
| s :: ss =>
255-
let s' := (inlineCallStmt s prog)
256-
let ss' := (inlineCallStmts ss prog)
257-
return (← s') ++ (← ss')
256+
| _ => return [.cmd cmd]
257+
258+
def inlineCallStmtsRec (ss: List Statement) (prog : Program)
259+
: BoogieTransformM (List Statement) :=
260+
runStmtsRec inlineCallCmd ss prog
258261

259262
def inlineCallL (dcls : List Decl) (prog : Program)
260263
: BoogieTransformM (List Decl) :=
261-
match dcls with
262-
| [] => return []
263-
| d :: ds =>
264-
match d with
265-
| .proc p md =>
266-
return Decl.proc { p with body := ← (inlineCallStmts p.body prog ) } md ::
267-
(← (inlineCallL ds prog))
268-
| _ => return d :: (← (inlineCallL ds prog))
264+
runProcedures inlineCallCmd dcls prog
269265

270266
end ProcedureInlining
271267
end Boogie

StrataMain.lean

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ import Strata.DDM.Elab
99
import Strata.DDM.Ion
1010
import Strata.Util.IO
1111

12-
import Strata.Languages.Python.Python
1312
import Strata.DDM.Integration.Java.Gen
14-
import StrataTest.Transform.ProcedureInlining
13+
import Strata.Languages.Python.Python
14+
import Strata.Transform.BoogieTransform
15+
import Strata.Transform.ProcedureInlining
1516

1617
def exitFailure {α} (message : String) : IO α := do
1718
IO.eprintln (message ++ "\n\nRun strata --help for additional help.")
@@ -202,18 +203,22 @@ def pyAnalyzeCommand : Command where
202203
let newPgm : Boogie.Program := { decls := preludePgm.decls ++ bpgm.decls }
203204
if verbose then
204205
IO.print newPgm
205-
let newPgm := runInlineCall newPgm
206-
if verbose then
207-
IO.println "Inlined: "
208-
IO.print newPgm
209-
let solverName : String := "Strata/Languages/Python/z3_parallel.py"
210-
let vcResults ← EIO.toIO (fun f => IO.Error.userError (toString f))
211-
(Boogie.verify solverName newPgm { Options.default with stopOnFirstError := false, verbose, removeIrrelevantAxioms := true }
212-
(moreFns := Strata.Python.ReFactory))
213-
let mut s := ""
214-
for vcResult in vcResults do
215-
s := s ++ s!"\n{vcResult.obligation.label}: {Std.format vcResult.result}\n"
216-
IO.println s
206+
match Boogie.Transform.runProgram
207+
(Boogie.ProcedureInlining.inlineCallCmd (excluded_calls := ["main"]))
208+
newPgm .emp with
209+
| ⟨.error e, _⟩ => panic! e
210+
| ⟨.ok newPgm, _⟩ =>
211+
if verbose then
212+
IO.println "Inlined: "
213+
IO.print newPgm
214+
let solverName : String := "Strata/Languages/Python/z3_parallel.py"
215+
let vcResults ← EIO.toIO (fun f => IO.Error.userError (toString f))
216+
(Boogie.verify solverName newPgm { Options.default with stopOnFirstError := false, verbose, removeIrrelevantAxioms := true }
217+
(moreFns := Strata.Python.ReFactory))
218+
let mut s := ""
219+
for vcResult in vcResults do
220+
s := s ++ s!"\n{vcResult.obligation.label}: {Std.format vcResult.result}\n"
221+
IO.println s
217222

218223
def javaGenCommand : Command where
219224
name := "javaGen"

StrataTest/Languages/Boogie/ExprEvalTest.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ open Lambda.LTy.Syntax
186186
(.app () (.app () (.op () (BoogieIdent.unres "Int.Add") .none) eb[#100]) eb[#50]))
187187

188188

189-
-- This may take a while (~ 1min)
189+
-- This may take a while (~ 5min)
190190
#eval (checkFactoryOps false)
191191

192192
open Plausible TestGen

0 commit comments

Comments
 (0)