Theory Nonrelational_Transfer
theory Nonrelational_Transfer
imports
Nonrelational_Ops
"Voblint_Framework.Check_Answer"
"Voblint_Result.DG_Analysis"
begin
section ‹What each kind of CFG edge does to one abstract value per variable›
text ‹
A non-relational domain gives every variable one abstract value. Say what an
expression evaluates to, what the whole-value element is, what ‹Min›/‹Max› do,
and how a guard refines its operands, and every edge operation is already
fixed: an assignment overwrites its target with the evaluated right-hand side, a
return writes the same into the return variable, a guard runs the backward
branch the refinement operations determine, procedure entry resets the callee
frame to the whole-value element and binds the formals, and skip,
procedure-body entry and check observation leave the store alone.
‹sound_nonrelational_ops› is the certificate for one \<^type>‹nonrelational_ops›
bundle: the special operations are sound, the refinement operations form a
reductive backward domain over the bundle's evaluator, and the queries are
sound checks over the same evaluator. From it this theory states every edge
operation once and proves each sound --- a concrete store described by the
input is still described by the output --- ending in the transfer contract the
analysis framework asks a domain for. ‹mono_nonrelational_ops› adds
monotonicity for a bundle whose evaluator, special operations and refinement
are monotone. A domain proves its capability certificates, interprets one of
the two locales once at its bundle, and names the results; it proves nothing
here again.
‹tf_abs› is the per-edge dispatcher these operations add up to, and
‹tf_abs_eq_generic› identifies it with \<^const>‹generic_tf_abs› at the derived
branch, which is what lets ‹tf_st_for_commute› inherit the executable mirror's
commutation instead of restating it.
›
locale sound_nonrelational_ops =
sound_minmax_ops "n_special ops" "n_aval ops"
+ backward: sound_refinement "r_intersect (n_refine ops)" "n_aval ops"
"r_tobool (n_refine ops)" "r_inv_less (n_refine ops)" "r_inv_eq (n_refine ops)"
"r_inv_plus (n_refine ops)" "r_inv_minus (n_refine ops)" "r_inv_times (n_refine ops)"
+ check: sound_check_query "q_less (n_query ops)" "q_eq (n_query ops)" gamma_state
"n_aval ops"
for ops :: "'a::numeric_domain nonrelational_ops"
begin
subsection ‹Assignment, return, and the operations that do nothing›
text ‹
‹assign› overwrites the target with the evaluated expression and ‹ret› does the
same for ‹ret_var›. ‹skip›, ‹body› and ‹event› are the identity. Each
operation gets its soundness lemma here.
›
definition assign :: "vname ⇒ exp ⇒ 'a abs_state ⇒ 'a abs_state" where
"assign x a d = d(x := n_aval ops a d)"
definition skip :: "'a abs_state ⇒ 'a abs_state" where
"skip d = d"
definition body :: "pname ⇒ 'a abs_state ⇒ 'a abs_state" where
"body p d = d"
text ‹A check observes its condition but never refines the store --- narrowing a
state against a checked condition is ‹sound_check_query›'s job --- so
‹event› is the identity like ‹skip› and ‹body›.›
definition event :: "analysis_event ⇒ 'a abs_state ⇒ 'a abs_state" where
"event evt d = d"
definition ret :: "exp option ⇒ pname ⇒ 'a abs_state ⇒ 'a abs_state" where
"ret e p d = (case e of None ⇒ d | Some a ⇒ assign ret_var a d)"
lemma assign_sound:
assumes 𝒢: "s ∈ γ d"
shows "s(x := ⟦a⟧⇩e s) ∈ γ (assign x a d)"
unfolding assign_def
using gamma_stateD[OF 𝒢] 𝒢 by blast
lemma skip_sound: "s ∈ γ d ⟹ s ∈ γ (skip d)"
by (simp add: skip_def)
lemma body_sound: "s ∈ γ d ⟹ s ∈ γ (body p d)"
by (simp add: body_def)
lemma event_sound: "s ∈ γ d ⟹ s ∈ γ (event evt d)"
by (simp add: event_def)
lemma ret_sound:
assumes 𝒢: "s ∈ γ d"
shows "s(ret_var := (case e of None ⇒ s ret_var | Some a ⇒ ⟦a⟧⇩e s)) ∈ γ (ret e p d)"
using assign_sound[OF 𝒢] 𝒢 by (cases e) (simp_all add: ret_def)
subsection ‹Classifier-parametric procedure entry›
text ‹
Entry is the only operation that consults a classifier, inside
\<^const>‹enter_frame›: assignment, guard and return never do, so everything above
is classifier-free and only these three constants take one.
›
definition enter_frame_for :: "(vname ⇒ bool) ⇒ 'a abs_state ⇒ 'a abs_state" where
"enter_frame_for 𝒢 = enter_frame 𝒢 top"
definition enter_for ::
"(vname ⇒ bool) ⇒ vname list ⇒ exp list ⇒ 'a abs_state ⇒ 'a abs_state" where
"enter_for 𝒢 = enter_binding 𝒢 top (n_aval ops)"
definition enter_ci_for ::
"(vname ⇒ bool) ⇒ call_info ⇒ 'a abs_state ⇒ 'a abs_state" where
"enter_ci_for 𝒢 ci = enter_for 𝒢 (ci_formals ci) (ci_args ci)"
lemma enter_for_sound:
assumes 𝒢: "s ∈ γ d"
shows "bind_formals xs (map (λe. ⟦e⟧⇩e s) es) (enter_state cls s)
∈ γ (enter_for cls xs es d)"
unfolding enter_for_def enter_binding_concrete[symmetric]
proof (rule enter_binding_sound[OF 𝒢 gamma_top])
fix e
show "⟦e⟧⇩e s ∈ γ (n_aval ops e d)"
using gamma_stateD[OF 𝒢] by blast
qed
lemma enter_ci_for_sound:
assumes 𝒢: "s ∈ γ d"
shows "bind_formals (ci_formals ci) (map (λe. ⟦e⟧⇩e s) (ci_args ci)) (enter_state cls s)
∈ γ (enter_ci_for cls ci d)"
using enter_for_sound[OF 𝒢, of ‹ci_formals ci› ‹ci_args ci›]
by (simp add: enter_ci_for_def)
subsection ‹The transfer contract, and the per-edge dispatcher›
text ‹The eight operations discharge the framework's contract in one
‹unfold_locales›: the call boundary the contract fixes is the structural one,
so only the per-edge operations and the callee entry are the domain's own.›
lemma is_sound_nonrelational_transfer:
"sound_nonrelational_transfer 𝒢 skip assign special_transfer backward.branch body ret (enter_ci_for 𝒢) event"
by unfold_locales
(simp_all add: assign_sound special_transfer_sound backward.branch_sound skip_sound body_sound
ret_sound enter_ci_for_sound event_sound)
text ‹The per-edge dispatcher. The executable mirror a domain runs dispatches on
the action, so the equations relating the two through the represented function are stated at this shape
rather than one lemma per operation.›
definition tf_abs :: "edge_action ⇒ 'a abs_state ⇒ 'a abs_state" where
"tf_abs = local_spec_step skip assign special_transfer backward.branch body ret event"
lemma tf_abs_simps [simp]:
"tf_abs EA_Nop = skip"
"tf_abs (EA_Assign x e) = assign x e"
"tf_abs (EA_Special sc y) = special_transfer sc y"
"tf_abs (EA_Assume b) = backward.branch b True"
"tf_abs (EA_AssumeNot b) = backward.branch b False"
"tf_abs (EA_Body p) = body p"
"tf_abs (EA_Ret eo p) = ret eo p"
"tf_abs (EA_Check l c) = event (Check_Event l c)"
by (simp_all add: tf_abs_def)
text ‹The bundle an executable mirror unfolds against. \<^const>‹tf_abs› itself is
not in it: ‹tf_abs_simps› is already ‹[simp]›, so a proof reaches the operation
the action selects without naming the dispatcher.›
lemmas op_defs =
assign_def skip_def body_def event_def ret_def
enter_frame_for_def enter_for_def enter_ci_for_def
subsection ‹Agreement with the executable mirror›
text ‹
Each operation above is the one the bundle determines --- ‹Nondet_Int›'s
\<^const>‹top›, and every other case is definitional ---
so this domain's dispatcher is \<^const>‹generic_tf_abs› at its own bundle and
branch. That identification is the whole content: the executable mirror's
commutation is already proved once against \<^const>‹generic_tf_abs›.
›
lemma tf_abs_eq_generic: "tf_abs = generic_tf_abs ops backward.branch"
proof (rule ext, rule ext)
fix a :: edge_action and d :: "'a abs_state"
show "tf_abs a d = generic_tf_abs ops backward.branch a d"
by (cases a)
(simp_all add: op_defs split: special_call.splits option.splits)
qed
text ‹
The derived guard filter is the backward domain's executable branch, so it
commutes with the abstract guard on every live store, and the whole
executable step with it.
›
theorem tf_st_for_commute:
assumes "live_default_st 𝒢 s"
shows
"ρ⇘𝒢⇙ (generic_tf_st_for ops 𝒢 a s) =
tf_abs a (ρ⇘𝒢⇙ s)"
unfolding tf_abs_eq_generic
by (rule generic_tf_st_for_commute)
(simp add: backward.branch_st_with_ops [simplified] backward.branch_st_commute[OF assms])
lemma enter_st_for_commute:
"ρ⇘𝒢⇙ (generic_enter_st_for ops 𝒢 ci s) =
enter_ci_for 𝒢 ci (ρ⇘𝒢⇙ s)"
by (simp add: generic_enter_st_for_def op_defs enter_binding_def enter_frame_def)
subsection ‹Registering the bundle with the pipeline›
text ‹
The bundle already settles everything \<^locale>‹dg_analysis_exec› asks of the domain:
the derived transfer is sound, the executable step and entry represent it, and the
derived classifier is sound in both directions. What remains belongs to the context
policy (the routing agreement and the seed key), the solver (a
\<^locale>‹certified_solver›) and the initial state, and stays a premise.
›
theorem dg_analysis_execI:
assumes route_agree:
"⋀𝒢 u ctx d ca. route 𝒢 u ctx d ca
= route_abs 𝒢 u ctx (ρ⇘𝒢⇙ d) ca"
and seed_ne_buffer_key: "⋀v ctx. seed v ctx ≠ buffer_key"
and solver: "certified_solver solve solve_dom solve_c"
and init_sound: "⋀𝒢. cinit_stores 𝒢 ⊆ default_st_gamma 𝒢 init_st"
and own_key: "⋀n. global_of n = buffer_key"
shows "dg_analysis_exec (generic_tf_st_for ops) (generic_enter_st_for ops) init_st buffer_key
global_of seed
route solve solve_dom bot check.classify_check
skip assign special_transfer backward.branch body ret enter_ci_for event route_abs solve_c"
proof (rule dg_analysis_exec.intro[OF solver dg_analysis_exec_axioms.intro], goal_cases)
case (1 𝒢) show ?case by (rule is_sound_nonrelational_transfer)
next
case (2 𝒢 a s) then show ?case
unfolding tf_abs_def[symmetric] by (rule tf_st_for_commute)
next
case (3 𝒢 ci s) show ?case
by (rule enter_st_for_commute)
qed (fact route_agree seed_ne_buffer_key init_sound own_key
check.classify_check_proved check.classify_check_refuted refl)+
subsection ‹The specification and its soundness, before any context›
text ‹
What the bundle supplies to the framework on its own: a whole-state D/G
specification over the executable step and entry, and its soundness against the
concretization of the read-back local value, ‹λd g. γ (ρ⇘𝒢⇙ d)›. Neither
mentions a context, a routing function, a seed key or a solver, so every domain
reads them off its bundle instead of restating them.
›
definition spec_exec ::
"(vname ⇒ bool) ⇒ ('a default_st ⇒ bool)
⇒ ('x, 'k, unit, 'a default_st lifted, 'a default_st lifted) dg_spec" where
"spec_exec 𝒢 empty_pred =
exec_dg_spec 𝒢 empty_pred (generic_tf_st_for ops 𝒢) (generic_enter_st_for ops 𝒢)"
lemma dg_spec_wf_spec_exec [intro, simp]: "dg_spec_wf (spec_exec 𝒢 empty_pred)"
by (simp add: spec_exec_def)
context
includes default_st_syntax
fixes 𝒢 :: "vname ⇒ bool" and empty_pred :: "'a default_st ⇒ bool"
assumes exact: "⋀s. empty_pred s = is_empty_state (ρ⇘𝒢⇙ s)"
begin
interpretation dom: dg_domain_exec
𝒢 empty_pred "generic_tf_st_for ops 𝒢" "generic_enter_st_for ops 𝒢"
skip assign special_transfer backward.branch body ret "enter_ci_for 𝒢" event
by unfold_locales
(rule tf_st_for_commute[unfolded tf_abs_def], assumption,
rule enter_st_for_commute, rule exact)
lemma gamma_exec_readback: "dom.gamma_exec = (λd g. γ (ρ⇘𝒢⇙ d))"
by (intro ext) (simp add: dom.gamma_exec_def gamma_lift_default_st_gamma_to_fun)
theorem sound_exec:
"analysis_contract (spec_exec 𝒢 empty_pred) (λd g. γ (ρ⇘𝒢⇙ d)) 𝒢"
proof -
have "analysis_contract dom.spec_st (λd e. dom.gamma_exec d (e ())) 𝒢"
by (rule dom.analysis_contract_st[OF is_sound_nonrelational_transfer])
then show ?thesis by (simp add: gamma_exec_readback spec_exec_def)
qed
text ‹Entry is stated apart from \<^locale>‹analysis_contract›, so a routed instance cites
it separately; the alternative list is the singleton this entry answers.›
theorem entry_cover_exec:
assumes "s ∈ γ (ρ⇘𝒢⇙ d)"
shows "entry_pairs_cover (λd'. γ (ρ⇘𝒢⇙ d')) s
(call_enter 𝒢 (CallEdge (ci_dst ci) (ci_formals ci) (ci_args ci)) s)
[(d, transfer_lift empty_pred (generic_enter_st_for ops 𝒢 ci) d)]"
proof -
have "s ∈ gamma_lift (default_st_gamma 𝒢) d"
using assms by (simp add: gamma_lift_default_st_gamma_to_fun)
from dom.entry_pairs_cover_st [OF is_sound_nonrelational_transfer this]
show ?thesis by (simp add: gamma_lift_default_st_gamma_to_fun)
qed
end
end
section ‹Monotone bundles›
text ‹
Monotonicity is a separate certificate: the transfer contract asks only for
soundness, and a bundle whose evaluator or refinement has no monotonicity
proof (Int's fixpoint refinement) is still a sound one. A monotone bundle additionally has
monotone special operations and a monotone backward domain, and then every
derived operation is monotone.
›
locale mono_nonrelational_ops = sound_nonrelational_ops ops
+ mono_minmax_ops "n_special ops" "n_aval ops"
+ backward: mono_refinement "r_intersect (n_refine ops)" "n_aval ops"
"r_tobool (n_refine ops)" "r_inv_less (n_refine ops)" "r_inv_eq (n_refine ops)"
"r_inv_plus (n_refine ops)" "r_inv_minus (n_refine ops)" "r_inv_times (n_refine ops)"
for ops :: "'a::numeric_domain nonrelational_ops"
begin
lemma assign_mono: "d1 ≤ d2 ⟹ assign x a d1 ≤ assign x a d2"
unfolding assign_def by (simp add: aval_abs_mono le_funD le_funI)
lemma ret_mono: "d1 ≤ d2 ⟹ ret e p d1 ≤ ret e p d2"
by (cases e) (simp_all add: ret_def assign_mono)
lemma enter_for_mono:
assumes "d1 ≤ d2"
shows "enter_for 𝒢 xs es d1 ≤ enter_for 𝒢 xs es d2"
unfolding enter_for_def
by (rule enter_binding_mono[OF assms]) (rule aval_abs_mono[OF assms])
end
subsection ‹Certifying a bundle from its capability certificates›
text ‹
‹sound_nonrelational_opsI› and ‹mono_nonrelational_opsI› assemble the bundle
certificates from the special-call, refinement and check-query certificates, so
a domain never unfolds the combined locales itself.
›
lemma sound_nonrelational_opsI:
assumes "sound_minmax_ops (n_special ops) (n_aval ops)"
and "sound_refinement (r_intersect (n_refine ops)) (n_aval ops)
(r_tobool (n_refine ops)) (r_inv_less (n_refine ops)) (r_inv_eq (n_refine ops))
(r_inv_plus (n_refine ops)) (r_inv_minus (n_refine ops)) (r_inv_times (n_refine ops))"
and "sound_check_query (q_less (n_query ops)) (q_eq (n_query ops)) gamma_state (n_aval ops)"
shows "sound_nonrelational_ops ops"
by (intro sound_nonrelational_ops.intro assms)
lemma mono_nonrelational_opsI:
assumes special: "mono_minmax_ops (n_special ops) (n_aval ops)"
and backward: "mono_refinement (r_intersect (n_refine ops)) (n_aval ops)
(r_tobool (n_refine ops)) (r_inv_less (n_refine ops)) (r_inv_eq (n_refine ops))
(r_inv_plus (n_refine ops)) (r_inv_minus (n_refine ops)) (r_inv_times (n_refine ops))"
and "sound_check_query (q_less (n_query ops)) (q_eq (n_query ops)) gamma_state (n_aval ops)"
shows "mono_nonrelational_ops ops"
proof -
interpret mono_minmax_ops "n_special ops" "n_aval ops" by (rule special)
interpret mono_refinement "r_intersect (n_refine ops)" "n_aval ops"
"r_tobool (n_refine ops)" "r_inv_less (n_refine ops)" "r_inv_eq (n_refine ops)"
"r_inv_plus (n_refine ops)" "r_inv_minus (n_refine ops)" "r_inv_times (n_refine ops)"
by (rule backward)
show ?thesis
by (intro mono_nonrelational_ops.intro sound_nonrelational_opsI assms)
unfold_locales
qed
end