Files
agda-spa/lean/Spa/Analysis/Forward.lean
Danila Fedorin 8cd053a242 Migrate Reaching.lean to projections via a generic Trace.steps
Finish the projection migration for reaching definitions by replacing the
accumulator-style runOfTrace*From definitions and their hand-rolled
re-association lemmas with a single analysis-agnostic projection:
Trace.steps / Traceₗ.steps, the chronological List of executed
(index, statement) pairs. Its four simp lemmas are one-line inductions,
with all re-association falling out of mathlib's List.append_assoc and
List.reverse_append.

Run is now an abbrev for List (State × BasicStmt) (latest-first, so
LastAssign keeps its first-match structure) and runOfTrace is just
steps.reverse.

Also hoist the generic reaches_final_post into Forward.lean, letting
analyze_correct' be stated directly about S.Post (prog.trace hrun).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 09:01:09 -05:00

175 lines
7.4 KiB
Lean4
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Spa.Analysis.Forward.Lattices
import Spa.Analysis.Forward.Evaluation
import Spa.Analysis.Forward.Adapters
import Spa.Fixedpoint
namespace Spa
namespace Forward
variable {L : Type} [FiniteHeightLattice L] {prog : Program} [E : StmtEvaluator L prog]
def updateVariablesForState (s : prog.State) (sv : StateVariables L prog) :
VariableValues L prog := E.eval s (variablesAt s sv)
lemma updateVariablesForState_mono (s : prog.State) :
Monotone (updateVariablesForState (L := L) s) := fun _ _ hle =>
E.eval_mono s (variablesAt_le hle s)
def updateAll (sv : StateVariables L prog) : StateVariables L prog :=
FiniteMap.generalizedUpdate id updateVariablesForState
prog.states sv
lemma updateAll_mono : Monotone (updateAll (L := L) (prog := prog)) :=
FiniteMap.generalizedUpdate_monotone monotone_id updateVariablesForState_mono
lemma updateAll_mem_eq {s : prog.State} {vs : VariableValues L prog}
{sv : StateVariables L prog} (hmem : (s, vs) updateAll sv) :
vs = updateVariablesForState s sv :=
FiniteMap.generalizedUpdate_mem_eq (prog.states_complete s) hmem
lemma variablesAt_updateAll (s : prog.State) (sv : StateVariables L prog) :
variablesAt s (updateAll sv) = updateVariablesForState s sv :=
updateAll_mem_eq (variablesAt_mem s (updateAll sv))
def analyze (sv : StateVariables L prog) : StateVariables L prog :=
updateAll (joinAll sv)
lemma analyze_mono : Monotone (analyze (L := L) (prog := prog)) := fun _ _ hle =>
updateAll_mono (joinAll_mono hle)
variable [DecidableEq L]
variable (L prog) in
def result : StateVariables L prog :=
Fixedpoint.aFix analyze analyze_mono
variable (L prog) in
lemma result_eq : result L prog = analyze (result L prog) :=
Fixedpoint.aFix_eq analyze analyze_mono
lemma joinForKey_initialState :
joinForKey prog.initialState (result L prog) = botV L prog := by
rw [joinForKey, prog.incoming_initialState_eq_nil]
rfl
class ValidStateEvaluator (L : Type) [FiniteHeightLattice L] (prog : Program)
[E : StmtEvaluator L prog] [S : StateInterpretation L prog] where
valid : (s₁ s₂ : prog.State) {ρ₁ ρ₂ ρ₃: Env}
{vs : VariableValues L prog},
(tr : Traceₗ prog.cfg s₁ s₂ ρ₁ ρ₂)
(hbs : EvalBasicStmtOpt ρ₂ (prog.cfg.nodes s₂) ρ₃) vs (S.Pre tr)
E.eval s₂ vs (S.Post (tr ++ hbs))
botV_init : botV L prog (S.Pre (Traceₗ.single prog.cfg prog.initialState []))
instance [LatticeInterpretation L] [ValidStmtEvaluator L prog] :
ValidStateEvaluator L prog where
valid := by intro _ _ _ _ _ _ tr hbs hvs; exact ValidStmtEvaluator.valid hbs hvs
botV_init := by intro k l _ v hmem; cases hmem
section
variable [S : StateInterpretation L prog] [V : ValidStateEvaluator L prog]
omit [DecidableEq L] in
lemma updateAll_matches {s₁ s₂ : prog.State} {sv : StateVariables L prog}
{ρ₁ ρ₂ ρ₃ : Env}
(tr : Traceₗ prog.cfg s₁ s₂ ρ₁ ρ₂)
(hnode : EvalBasicStmtOpt ρ₂ (prog.code s₂) ρ₃)
(hvs : variablesAt s₂ sv (S.Pre tr)) :
variablesAt s₂ (updateAll sv) (S.Post (tr ++ hnode)) := by
rw [variablesAt_updateAll]
exact V.valid s₁ s₂ tr hnode hvs
lemma stepTrace {s₁ s₂ : prog.State} {ρ₁ ρ₂ : Env}
(tr : Traceₗ prog.cfg s₁ s₂ ρ₁ ρ₂)
(hjoin : joinForKey s₂ (result L prog) (S.Pre tr))
(hnode : EvalBasicStmtOpt ρ₂ (prog.code s₂) ρ₃) :
variablesAt s₂ (result L prog) (S.Post (tr ++ hnode)) := by
rw [result_eq L prog]
refine updateAll_matches tr hnode ?_
rw [variablesAt_joinAll]
exact hjoin
/-- Soundness at *every* visited node: if the analysis result over-approximates the
incoming environment at the start of the trace, then at each node reached along the
way it over-approximates both the environment entering that node (via `joinForKey`)
and the environment leaving it (via `variablesAt`). The intermediate `variablesAt`
evidence used to be computed and discarded inside `walkTrace`; here it is returned. -/
lemma walkTrace_reaches {s₁ s₂ s₃: prog.State} {ρ₁ ρ₂ ρ₃: Env}
{s : prog.State} {ρin ρout : Env}
{tr : Trace prog.cfg s₂ s₃ ρ₂ ρ₃}
(hr : Reaches tr s ρin ρout)
(trₗ : Traceₗ prog.cfg s₁ s₂ ρ₁ ρ₂)
(hjoin : joinForKey s₂ (result L prog) (S.Pre trₗ)) :
joinForKey s (result L prog) (S.Pre (trₗ ++ hr.pre))
variablesAt s (result L prog) (S.Post (trₗ ++ hr.post)) := by
induction hr with
| single_here hnode =>
simp [Reaches.pre, Reaches.post]
refine ?_, ?_ <;> try simpa [HAppend.hAppend]
exact stepTrace trₗ hjoin hnode
| edge_here hnode hedge rest =>
simp [Reaches.pre, Reaches.post]
refine ?_, ?_ <;> try simpa [HAppend.hAppend]
exact stepTrace trₗ hjoin hnode
| edge_there hnode hedge rest hr' ih =>
have hstep := stepTrace trₗ hjoin hnode
have hmem := FiniteMap.mem_valuesAt prog.states_nodup
(prog.mem_incoming_of_edge hedge) (variablesAt_mem _ (result L prog))
simpa [Reaches.pre, Reaches.post, HAppend.hAppend] using
ih ((trₗ ++ hnode).addEdge hedge)
(interp_foldr (S.post_pre (trₗ ++ hnode) hedge hstep) hmem)
omit [DecidableEq L] in
/-- The final node of a trace is always reached, with the environment/state the trace
ends in. Used to recover the final-state soundness theorem from `walkTrace_reaches`. -/
def reaches_final {s₁ s₂ : prog.State} {ρ₁ ρ₂ : Env}
(tr : Trace prog.cfg s₁ s₂ ρ₁ ρ₂) :
Σ ρin, Reaches tr s₂ ρin ρ₂ :=
match tr with
| .single hnode => _, .single_here hnode
| .edge hnode hedge rest =>
let ρin, r' := reaches_final rest; ρin, .edge_there hnode hedge _ r'
omit [DecidableEq L] in
/-- Reaching the final node covers the whole trace. -/
@[simp] lemma reaches_final_post {s₁ s₂ : prog.State} {ρ₁ ρ₂ : Env}
(tr : Trace prog.cfg s₁ s₂ ρ₁ ρ₂) :
(reaches_final tr).2.post = tr := by
induction tr with
| single hnode => rfl
| edge hnode hedge rest ih => simp [reaches_final, Reaches.post, ih]
variable (L prog) in
/-- Soundness at every program point reached during execution: for any node `s` visited
by the run `hrun` (witnessed by `hr`), the analysis result over-approximates both the
environment entering `s` and the one leaving it. The final-state theorem
`analyze_correct_state` is the special case where `s` is `prog.finalState`. -/
theorem analyze_correct_at {ρf : Env} (hrun : EvalStmt [] prog.rootStmt ρf)
{s : prog.State} {ρin ρout : Env}
(hr : Reaches (prog.trace hrun) s ρin ρout) :
joinForKey s (result L prog) (S.Pre hr.pre)
variablesAt s (result L prog) (S.Post hr.post) := by
refine walkTrace_reaches hr (Traceₗ.single _ _ []) ?_
rw [joinForKey_initialState]
exact ValidStateEvaluator.botV_init
variable (L prog) in
theorem analyze_correct'
{ρ : Env} (hrun : EvalStmt [] prog.rootStmt ρ) :
variablesAt prog.finalState (result L prog) (S.Post (prog.trace hrun)) := by
have h := (analyze_correct_at L prog hrun (reaches_final (prog.trace hrun)).2).2
rwa [reaches_final_post] at h
end
variable (L prog) in
theorem analyze_correct [LatticeInterpretation L] [ValidStmtEvaluator L prog]
{ρ : Env} (hrun : EvalStmt [] prog.rootStmt ρ) :
variablesAt prog.finalState (result L prog) ρ :=
analyze_correct' L prog hrun
end Forward
end Spa