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