Remove trace suffix from Reaching type

This commit is contained in:
2026-08-09 21:23:21 -05:00
parent df4d072f22
commit fd371ba175
5 changed files with 65 additions and 109 deletions

View File

@@ -137,12 +137,11 @@ theorem analyze_correct {ρ : Env} (hrun : EvalStmt [] prog.rootStmt ρ) :
variablesAt prog.finalState (result ConstLattice prog) ρ := variablesAt prog.finalState (result ConstLattice prog) ρ :=
Forward.analyze_correct ConstLattice prog hrun Forward.analyze_correct ConstLattice prog hrun
theorem analyze_correct_at {ρf : Env} (hrun : EvalStmt [] prog.rootStmt ρf) theorem analyze_correct_at {s : prog.State} {ρin ρout : Env}
{s : prog.State} {ρin ρout : Env} (hr : Reaches s ρin ρout) :
(hr : Reaches (prog.trace hrun) s ρin ρout) :
joinForKey s (result ConstLattice prog) ρin joinForKey s (result ConstLattice prog) ρin
variablesAt s (result ConstLattice prog) ρout := variablesAt s (result ConstLattice prog) ρout :=
Forward.analyze_correct_at ConstLattice prog hrun hr Forward.analyze_correct_at ConstLattice prog hr
end ConstAnalysis end ConstAnalysis

View File

@@ -90,75 +90,54 @@ lemma stepTrace {s₁ s₂ : prog.State} {ρ₁ ρ₂ : Env}
rw [variablesAt_joinAll] rw [variablesAt_joinAll]
exact hjoin exact hjoin
/-- Soundness at *every* visited node: if the analysis result over-approximates the /-- Soundness propagates along an execution prefix: if the analysis is sound at
incoming environment at the start of the trace, then at each node reached along the `s₂` for the run so far (`trₗ`), then it is sound wherever the further prefix
way it over-approximates both the environment entering that node (via `joinForKey`) `mid` ends up. -/
and the environment leaving it (via `variablesAt`). The intermediate `variablesAt` lemma walkPrefix : {s₂ s : prog.State} {ρ₂ ρin : Env}
evidence used to be computed and discarded inside `walkTrace`; here it is returned. -/ (mid : Traceₗ prog.cfg s₂ s ρ₂ ρin) {s₁ : prog.State} {ρ₁ : Env}
lemma walkTrace_reaches {s₁ s₂ s₃: prog.State} {ρ ρ ρ: Env} (trₗ : Traceₗ prog.cfg s s ρ ρ₂),
{s : prog.State} {ρin ρout : Env} joinForKey s (result L prog) (S.Pre trₗ)
{tr : Trace prog.cfg s₂ s₃ ρ₂ ρ₃} joinForKey s (result L prog) (S.Pre (trₗ ++ mid)) := by
(hr : Reaches tr s ρin ρout) intro s₂ s ρ₂ ρin mid
(trₗ : Traceₗ prog.cfg s₁ s₂ ρ₁ ρ₂) induction mid with
(hjoin : joinForKey s (result L prog) (S.Pre trₗ)) : | nil => intro s ρ₁ trₗ hjoin; simpa [HAppend.hAppend, Traceₗ.append] using hjoin
joinForKey s (result L prog) (S.Pre (trₗ ++ hr.pre)) | cons hnode hedge rest ih =>
variablesAt s (result L prog) (S.Post (trₗ ++ hr.post)) := by intro s₁ ρ₁ trₗ hjoin
induction hr with have hstep := stepTrace trₗ hjoin hnode
| single_here hnode => have hmem := FiniteMap.mem_valuesAt prog.states_nodup
simp [Reaches.pre, Reaches.post] (prog.mem_incoming_of_edge hedge) (variablesAt_mem _ (result L prog))
refine ?_, ?_ <;> try simpa [HAppend.hAppend] simpa [HAppend.hAppend, Traceₗ.append] using
exact stepTrace trₗ hjoin hnode ih ((trₗ ++ hnode).addEdge hedge)
| edge_here hnode hedge rest => (interp_foldr (S.post_pre (trₗ ++ hnode) hedge hstep) hmem)
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 omit [DecidableEq L] in
/-- The final node of a trace is always reached, with the environment/state the trace /-- 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`. -/ ends in. Used to recover the final-state soundness theorem from `walkPrefix`. -/
def reaches_final {s s₂ : prog.State} {ρ ρ₂ : Env} def reaches_final {s : prog.State} {ρ : Env}
(tr : Trace prog.cfg s₁ s₂ ρ₁ ρ₂) : (tr : Trace prog.cfg prog.initialState s [] ρ) : Σ ρin, Reaches s ρin ρ :=
Σ ρin, Reaches tr s ρin ρ₂ := _, tr.split.2.1, tr.split.2.2
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 omit [DecidableEq L] in
/-- Reaching the final node covers the whole trace. -/ @[simp] lemma reaches_final_post {s : prog.State} {ρ : Env}
@[simp] lemma reaches_final_post {s₁ s₂ : prog.State} {ρ₁ ρ : Env} (tr : Trace prog.cfg prog.initialState s [] ρ) :
(tr : Trace prog.cfg s₁ s₂ ρ₁ ρ₂) : (reaches_final tr).2.post = tr := Trace.split_append tr
(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 variable (L prog) in
/-- Soundness at every program point reached during execution: for any node `s` visited /-- Soundness at every program point an execution actually visits: the analysis
by the run `hrun` (witnessed by `hr`), the analysis result over-approximates both the over-approximates both the environment entering that point and the one leaving
environment entering `s` and the one leaving it. The final-state theorem it. -/
`analyze_correct_state` is the special case where `s` is `prog.finalState`. -/ theorem analyze_correct_at {s : prog.State} {ρin ρout : Env} (hr : Reaches s ρin ρout) :
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) joinForKey s (result L prog) (S.Pre hr.pre)
variablesAt s (result L prog) (S.Post hr.post) := by variablesAt s (result L prog) (S.Post hr.post) :=
refine walkTrace_reaches hr (Traceₗ.single _ _ []) ?_ have hpre := walkPrefix hr.pre Traceₗ.nil
rw [joinForKey_initialState] (by rw [joinForKey_initialState]; exact ValidStateEvaluator.botV_init)
exact ValidStateEvaluator.botV_init hpre, stepTrace hr.pre hpre hr.step
variable (L prog) in variable (L prog) in
theorem analyze_correct' theorem analyze_correct'
{ρ : Env} (hrun : EvalStmt [] prog.rootStmt ρ) : {ρ : Env} (hrun : EvalStmt [] prog.rootStmt ρ) :
variablesAt prog.finalState (result L prog) (S.Post (prog.trace hrun)) := by 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 have h := (analyze_correct_at L prog (reaches_final (prog.trace hrun)).2).2
rwa [reaches_final_post] at h rwa [reaches_final_post] at h
end end

View File

@@ -118,12 +118,11 @@ theorem analyze_correct {ρ : Env} (hrun : EvalStmt [] prog.rootStmt ρ) :
(runOfTrace prog (prog.trace hrun)) := (runOfTrace prog (prog.trace hrun)) :=
Forward.analyze_correct' (DefSet prog) prog hrun Forward.analyze_correct' (DefSet prog) prog hrun
theorem analyze_correct_at {ρf : Env} (hrun : EvalStmt [] prog.rootStmt ρf) theorem analyze_correct_at {s : prog.State} {ρin ρout : Env}
{s : prog.State} {ρin ρout : Env} (hr : Reaches s ρin ρout) :
(hr : Reaches (prog.trace hrun) s ρin ρout) :
joinForKey s (result (DefSet prog) prog) (runOfTraceₗ prog hr.pre) joinForKey s (result (DefSet prog) prog) (runOfTraceₗ prog hr.pre)
variablesAt s (result (DefSet prog) prog) (runOfTrace prog hr.post) := variablesAt s (result (DefSet prog) prog) (runOfTrace prog hr.post) :=
Forward.analyze_correct_at (DefSet prog) prog hrun hr Forward.analyze_correct_at (DefSet prog) prog hr
end ReachingAnalysis end ReachingAnalysis

View File

@@ -228,12 +228,11 @@ theorem analyze_correct {ρ : Env} (hrun : EvalStmt [] prog.rootStmt ρ) :
variablesAt prog.finalState (result SignLattice prog) ρ := variablesAt prog.finalState (result SignLattice prog) ρ :=
Forward.analyze_correct SignLattice prog hrun Forward.analyze_correct SignLattice prog hrun
theorem analyze_correct_at {ρf : Env} (hrun : EvalStmt [] prog.rootStmt ρf) theorem analyze_correct_at {s : prog.State} {ρin ρout : Env}
{s : prog.State} {ρin ρout : Env} (hr : Reaches s ρin ρout) :
(hr : Reaches (prog.trace hrun) s ρin ρout) :
joinForKey s (result SignLattice prog) ρin joinForKey s (result SignLattice prog) ρin
variablesAt s (result SignLattice prog) ρout := variablesAt s (result SignLattice prog) ρout :=
Forward.analyze_correct_at SignLattice prog hrun hr Forward.analyze_correct_at SignLattice prog hr
end SignAnalysis end SignAnalysis

View File

@@ -217,50 +217,30 @@ inductive EndToEndTrace (g : Graph) (ρ₁ ρ₂ : Env) : Type
(idx₂ : g.Index) (idx₂_mem : idx₂ g.outputs) (idx₂ : g.Index) (idx₂_mem : idx₂ g.outputs)
(trace : Trace g idx₁ idx₂ ρ₁ ρ₂) : EndToEndTrace g ρ₁ ρ₂ (trace : Trace g idx₁ idx₂ ρ₁ ρ₂) : EndToEndTrace g ρ₁ ρ₂
inductive Reaches {prog : Program} : {s₁ s₂ : prog.State} {ρ₁ ρ₂ : Env} /-- Every trace splits into the prefix that arrives at its last node and that node's own step. -/
Trace prog.cfg s s ρ₁ ρ₂ def Trace.split {g : Graph} {i i : g.Index} {ρ₁ ρ₂ : Env} :
(s : prog.State) (ρin ρout : Env) Type Trace g i₁ i₂ ρ₁ ρ₂ Σ ρ, Traceₗ g i₁ i₂ ρ ρ × EvalBasicStmtOpt ρ (g.nodes i₂) ρ₂
| single_here {s₁ : prog.State} {ρ₁ ρ₂ : Env} | .single hnode => _, .nil, hnode
(hnode : EvalBasicStmtOpt ρ₁ (prog.code s₁) ρ₂) : | .edge hnode hedge rest =>
Reaches (.single hnode) s ρ₁ ρ₂ let ρ, pre, step := rest.split
| edge_here {s₁ s₂ s₃ : prog.State} {ρ₁ ρ₂ ρ₃ : Env} ρ, .cons hnode hedge pre, step
(hnode : EvalBasicStmtOpt ρ₁ (prog.code s₁) ρ₂)
(hedge : (s₁, s₂) prog.cfg.edges) (rest : Trace prog.cfg s₂ s₃ ρ₂ ρ₃) :
Reaches (.edge hnode hedge rest) s₁ ρ₁ ρ₂
| edge_there {s₁ s₂ s₃ : prog.State} {ρ₁ ρ₂ ρ₃ : Env}
(hnode : EvalBasicStmtOpt ρ₁ (prog.code s₁) ρ₂)
(hedge : (s₁, s₂) prog.cfg.edges) (rest : Trace prog.cfg s₂ s₃ ρ₂ ρ₃)
{s : prog.State} {ρin ρout : Env} :
Reaches rest s ρin ρout
Reaches (.edge hnode hedge rest) s ρin ρout
def Reaches.pre {prog : Program} {s s s: prog.State} @[simp] lemma Trace.split_append {g : Graph} {i i : g.Index} {ρ₁ ρ₂ : Env}
{ρ₁ ρ₂ ρin ρout : Env} {tr : Trace prog.cfg s₁ s₂ ρ₁ ρ₂} : (tr : Trace g i₁ i₂ ρ₁ ρ₂) : tr.split.2.1 ++ tr.split.2.2 = tr := by
(r : Reaches tr s ρin ρout) Traceₗ prog.cfg s₁ s ρ₁ ρin induction tr with
| .single_here _ => .nil | single hnode => rfl
| .edge_here _ _ _ => .nil | edge hnode hedge rest ih =>
| .edge_there hnode hedge _ r => .cons hnode hedge r.pre show Traceₗ.appendStep _ _ = _
simpa [Trace.split, Traceₗ.appendStep, Traceₗ.appendTrace] using ih
def Reaches.post {prog : Program} {s₁ s s: prog.State} structure Reaches {prog : Program} (s : prog.State) (ρin ρout : Env) : Type where
{ρ₁ ρ₂ ρin ρout : Env} {tr : Trace prog.cfg s₁ s₂ ρ₁ ρ₂} : pre : Trace prog.cfg prog.initialState s [] ρin
(r : Reaches tr s ρin ρout) Trace prog.cfg s s ρ₁ ρout step : EvalBasicStmtOpt ρin (prog.code s) ρout
| .single_here hnode => .single hnode
| .edge_here hnode _ _ => .single hnode
| .edge_there hnode hedge _ r => .edge hnode hedge r.post
def Reaches.first {prog : Program} {s₁ s₂ s: prog.State} /-- Forget the environment before the last evaluated state. -/
{ρ₁ ρ₂ ρin ρout : Env} {tr : Trace prog.cfg s₁ s₂ ρ₁ ρ₂} : def Reaches.post {prog : Program} {s : prog.State} {ρin ρout : Env}
(r : Reaches tr s ρin ρout) Σ ρ₁', Reaches tr s₁ ρ ρ₁' (r : Reaches s ρin ρout) : Trace prog.cfg prog.initialState s [] ρout :=
| .single_here hnode => _, .single_here hnode r.pre ++ r.step
| .edge_here hnode hedge hrest => _, .edge_here hnode hedge hrest
| .edge_there hnode hedge hrest tmp' => _, .edge_here hnode hedge hrest
def Reaches.step {prog : Program} {s₁ s₂ s: prog.State}
{ρ₁ ρ₂ ρin ρout : Env} {tr : Trace prog.cfg s₁ s₂ ρ₁ ρ₂} :
(r : Reaches tr s ρin ρout) EvalBasicStmtOpt ρin (prog.code s) ρout
| .single_here hnode => hnode
| .edge_here hnode hedge hrest => hnode
| .edge_there hnode hedge hrest tmp' => tmp'.step
end Spa end Spa