Spec_stdlibSpecification of external malloc, free, exit functions
Require VC.Preface. (* Check for the right version of VST *)
Require Import VST.floyd.proofauto.
Require Import VC.stdlib.
Require Import VST.floyd.proofauto.
Require Import VC.stdlib.
In the first view chapters of this volume (Verif_stack,
Verif_triang, etc.) we demonstrated all-in-one-file C programs,
and we used a form of malloc_token with the type,
Require VST.floyd.library. (* Only needed for the next line *)
About VST.floyd.library.malloc_token.
(* library.malloc_token : compspecs -> share -> type -> val -> mpred
Arguments library.malloc_token {cs} *)
About VST.floyd.library.malloc_token.
(* library.malloc_token : compspecs -> share -> type -> val -> mpred
Arguments library.malloc_token {cs} *)
That is, malloc_token takes an implicit compspecs argument,
and the interpretation of the type argument depends on the
compspecs. Basically, the malloc_token needs to know how many
bytes the malloc'd record is, so it needs the compspecs in order
to interpret the type.
But now we are doing modular C programming, in which each .c file
can have its own struct and union types -- that is, its own compspecs.
Our malloc_token with a compspecs parameter cannot assume that
every module will have the same compspecs. For modular programming,
we'll start with a more primitive form, malloc_token_sz, that just
depends on the size of the object, of type Z. No compspecs required.
Later in this file, we show how to recover the type-based
malloc_token within each module.
In the memory-manager APD we have two abstract predicates, mem_mgr
and malloc_token. The predicate mem_mgr represents the state
of the memory manager's free list. That is, when you call free(p)
you might imagine that the memory-block at address p gets
added back into a free list (or some other data structure), and
when you call malloc(n) then it takes a block from the free list.
Since the internal representation of the free list is private to
this module (stdlib), it's an abstract predicate.
Record MallocFreeAPD := {
mem_mgr: globals → mpred;
malloc_token_sz: share → Z → val → mpred;
malloc_token_sz_valid_pointer: ∀ sh sz p,
sz ≤ 0 → malloc_token_sz sh sz p |-- valid_pointer p;
malloc_token_sz_local_facts: ∀ sh sz p,
malloc_token_sz sh sz p |-- !! malloc_compatible sz p
}.
#[export] Hint Resolve malloc_token_sz_valid_pointer : valid_pointer.
#[export] Hint Resolve malloc_token_sz_local_facts : saturate_local.
mem_mgr: globals → mpred;
malloc_token_sz: share → Z → val → mpred;
malloc_token_sz_valid_pointer: ∀ sh sz p,
sz ≤ 0 → malloc_token_sz sh sz p |-- valid_pointer p;
malloc_token_sz_local_facts: ∀ sh sz p,
malloc_token_sz sh sz p |-- !! malloc_compatible sz p
}.
#[export] Hint Resolve malloc_token_sz_valid_pointer : valid_pointer.
#[export] Hint Resolve malloc_token_sz_local_facts : saturate_local.
Abstract Specification Interface
Specification of malloc has the same issue with compspecs that we
discussed above for malloc_token: That is, we need the ASI to be
independent of any particular module's struct/union declarations.
So we will define malloc_spec_sz which is a size-based, not type-based,
specification.
Definition malloc_spec_sz :=
DECLARE _malloc
WITH n:Z, gv: globals
PRE [ size_t ]
PROP (0 ≤ n ≤ Ptrofs.max_unsigned)
PARAMS (Vptrofs (Ptrofs.repr n)) GLOBALS (gv)
SEP (mem_mgr M gv)
POST [ tptr tvoid ] EX p:_,
PROP ()
LOCAL (temp ret_temp p)
SEP (mem_mgr M gv;
if eq_dec p nullval then emp
else (malloc_token_sz M Ews n p × memory_block Ews n p)).
Definition free_spec_sz :=
DECLARE _free
WITH n:Z, p:val, gv: globals
PRE [ tptr tvoid ]
PROP ()
PARAMS (p) GLOBALS (gv)
SEP (mem_mgr M gv;
if eq_dec p nullval then emp
else (malloc_token_sz M Ews n p × memory_block Ews n p))
POST [ Tvoid ]
PROP ()
LOCAL ()
SEP (mem_mgr M gv).
DECLARE _malloc
WITH n:Z, gv: globals
PRE [ size_t ]
PROP (0 ≤ n ≤ Ptrofs.max_unsigned)
PARAMS (Vptrofs (Ptrofs.repr n)) GLOBALS (gv)
SEP (mem_mgr M gv)
POST [ tptr tvoid ] EX p:_,
PROP ()
LOCAL (temp ret_temp p)
SEP (mem_mgr M gv;
if eq_dec p nullval then emp
else (malloc_token_sz M Ews n p × memory_block Ews n p)).
Definition free_spec_sz :=
DECLARE _free
WITH n:Z, p:val, gv: globals
PRE [ tptr tvoid ]
PROP ()
PARAMS (p) GLOBALS (gv)
SEP (mem_mgr M gv;
if eq_dec p nullval then emp
else (malloc_token_sz M Ews n p × memory_block Ews n p))
POST [ Tvoid ]
PROP ()
LOCAL ()
SEP (mem_mgr M gv).
Of course the exit system call is not really part of the malloc/free
library, but this is a convenient place to put its specification.
Definition exit_spec :=
DECLARE _exit
WITH i: Z
PRE [tint]
PROP () PARAMS (Vint (Int.repr i)) GLOBALS () SEP()
POST [ tvoid ]
PROP(False) LOCAL() SEP().
DECLARE _exit
WITH i: Z
PRE [tint]
PROP () PARAMS (Vint (Int.repr i)) GLOBALS () SEP()
POST [ tvoid ]
PROP(False) LOCAL() SEP().
And now we can construct the ASI, which (as usual) is just a list
of (APD-parameterized) funspecs.
Type-based specification of malloc and free
Definition malloc_token {cs: compspecs} (M:MallocFreeAPD) sh t v :=
!! field_compatible t [] v &&
malloc_token_sz M sh (sizeof t) v.
Lemma malloc_token_valid_pointer: ∀ {cs: compspecs} M sh t p,
sizeof t ≤ 0 → malloc_token M sh t p |-- valid_pointer p.
Proof. intros. unfold malloc_token.
apply andp_left2. apply malloc_token_sz_valid_pointer; auto.
Qed.
Ltac malloc_token_data_at_valid_pointer :=
(* If the size of t is unknown, can still prove valid pointer
from (malloc_token sh t p * ... * data_at_ sh t p) *)
match goal with ⊢ ?A |-- valid_pointer ?p ⇒
match A with
| context [malloc_token _ _ ?t p] ⇒
try (assert (sizeof t ≤ 0)
by (simpl sizeof in *; rep_lia); fail 1);
try (assert (sizeof t > 0)
by (simpl sizeof in *; rep_lia); fail 1);
destruct (zlt 0 (sizeof t));
auto with valid_pointer
| context [malloc_token_sz _ _ ?n p] ⇒
try (assert (n ≤ 0) by rep_lia; fail 1);
try (assert (n > 0) by rep_lia; fail 1);
destruct (zlt 0 n);
auto with valid_pointer
end
end.
#[export] Hint Extern 1 (malloc_token _ _ _ _ |-- valid_pointer _) ⇒
(simple apply malloc_token_valid_pointer; data_at_valid_aux) : valid_pointer.
#[export] Hint Extern 4 (_ |-- valid_pointer _) ⇒ malloc_token_data_at_valid_pointer : valid_pointer.
Lemma malloc_token_local_facts: ∀ {cs: compspecs} M sh t p,
malloc_token M sh t p |-- !! (field_compatible t [] p ∧ malloc_compatible (sizeof t) p).
Proof. intros.
unfold malloc_token.
normalize. rewrite prop_and.
apply andp_right. apply prop_right; auto.
apply malloc_token_sz_local_facts.
Qed.
#[export] Hint Resolve malloc_token_local_facts : saturate_local.
Definition malloc_spec (M:MallocFreeAPD) {cs: compspecs} (t: type) :=
DECLARE _malloc
WITH gv: globals
PRE [ size_t ]
PROP (0 ≤ sizeof t ≤ Ptrofs.max_unsigned;
complete_legal_cosu_type t = true;
natural_aligned natural_alignment t = true)
PARAMS (Vptrofs (Ptrofs.repr (sizeof t))) GLOBALS (gv)
SEP (mem_mgr M gv)
POST [ tptr tvoid ] EX p:_,
PROP ()
LOCAL (temp ret_temp p)
SEP (mem_mgr M gv;
if eq_dec p nullval then emp
else (malloc_token M Ews t p × data_at_ Ews t p)).
Definition free_spec (M:MallocFreeAPD) {cs: compspecs} (t: type) :=
DECLARE _free
WITH p: val, gv: globals
PRE [ tptr tvoid ]
PROP ()
PARAMS (p) GLOBALS (gv)
SEP (mem_mgr M gv;
if eq_dec p nullval then emp
else (malloc_token M Ews t p × data_at_ Ews t p))
POST [ Tvoid ]
PROP ()
LOCAL ()
SEP (mem_mgr M gv).
!! field_compatible t [] v &&
malloc_token_sz M sh (sizeof t) v.
Lemma malloc_token_valid_pointer: ∀ {cs: compspecs} M sh t p,
sizeof t ≤ 0 → malloc_token M sh t p |-- valid_pointer p.
Proof. intros. unfold malloc_token.
apply andp_left2. apply malloc_token_sz_valid_pointer; auto.
Qed.
Ltac malloc_token_data_at_valid_pointer :=
(* If the size of t is unknown, can still prove valid pointer
from (malloc_token sh t p * ... * data_at_ sh t p) *)
match goal with ⊢ ?A |-- valid_pointer ?p ⇒
match A with
| context [malloc_token _ _ ?t p] ⇒
try (assert (sizeof t ≤ 0)
by (simpl sizeof in *; rep_lia); fail 1);
try (assert (sizeof t > 0)
by (simpl sizeof in *; rep_lia); fail 1);
destruct (zlt 0 (sizeof t));
auto with valid_pointer
| context [malloc_token_sz _ _ ?n p] ⇒
try (assert (n ≤ 0) by rep_lia; fail 1);
try (assert (n > 0) by rep_lia; fail 1);
destruct (zlt 0 n);
auto with valid_pointer
end
end.
#[export] Hint Extern 1 (malloc_token _ _ _ _ |-- valid_pointer _) ⇒
(simple apply malloc_token_valid_pointer; data_at_valid_aux) : valid_pointer.
#[export] Hint Extern 4 (_ |-- valid_pointer _) ⇒ malloc_token_data_at_valid_pointer : valid_pointer.
Lemma malloc_token_local_facts: ∀ {cs: compspecs} M sh t p,
malloc_token M sh t p |-- !! (field_compatible t [] p ∧ malloc_compatible (sizeof t) p).
Proof. intros.
unfold malloc_token.
normalize. rewrite prop_and.
apply andp_right. apply prop_right; auto.
apply malloc_token_sz_local_facts.
Qed.
#[export] Hint Resolve malloc_token_local_facts : saturate_local.
Definition malloc_spec (M:MallocFreeAPD) {cs: compspecs} (t: type) :=
DECLARE _malloc
WITH gv: globals
PRE [ size_t ]
PROP (0 ≤ sizeof t ≤ Ptrofs.max_unsigned;
complete_legal_cosu_type t = true;
natural_aligned natural_alignment t = true)
PARAMS (Vptrofs (Ptrofs.repr (sizeof t))) GLOBALS (gv)
SEP (mem_mgr M gv)
POST [ tptr tvoid ] EX p:_,
PROP ()
LOCAL (temp ret_temp p)
SEP (mem_mgr M gv;
if eq_dec p nullval then emp
else (malloc_token M Ews t p × data_at_ Ews t p)).
Definition free_spec (M:MallocFreeAPD) {cs: compspecs} (t: type) :=
DECLARE _free
WITH p: val, gv: globals
PRE [ tptr tvoid ]
PROP ()
PARAMS (p) GLOBALS (gv)
SEP (mem_mgr M gv;
if eq_dec p nullval then emp
else (malloc_token M Ews t p × data_at_ Ews t p))
POST [ Tvoid ]
PROP ()
LOCAL ()
SEP (mem_mgr M gv).
How to use the type-based malloc_spec
Lemma malloc_spec_sub:
∀ (M:MallocFreeAPD) {cs: compspecs} (t: type),
funspec_sub (snd (malloc_spec_sz M)) (snd (malloc_spec M t)).
Proof.
do_funspec_sub. rename w into gv. clear H.
Exists (sizeof t, gv) emp. simpl; entailer!.
intros tau ? ?. Exists (eval_id ret_temp tau).
entailer!.
if_tac; auto.
unfold malloc_token.
assert_PROP (field_compatible t [] (eval_id ret_temp tau)).
{ entailer!.
apply malloc_compatible_field_compatible; auto. }
entailer!.
rewrite memory_block_data_at_; auto.
Qed.
Lemma free_spec_sub:
∀ (M:MallocFreeAPD) {cs: compspecs} (t: type),
funspec_sub (snd (free_spec_sz M)) (snd (free_spec M t)).
Proof.
do_funspec_sub. destruct w as [p gv]. clear H.
Exists (sizeof t, p, gv) emp. simpl; entailer!.
if_tac; trivial.
sep_apply data_at__memory_block_cancel.
unfold malloc_token; entailer!.
Qed.
∀ (M:MallocFreeAPD) {cs: compspecs} (t: type),
funspec_sub (snd (malloc_spec_sz M)) (snd (malloc_spec M t)).
Proof.
do_funspec_sub. rename w into gv. clear H.
Exists (sizeof t, gv) emp. simpl; entailer!.
intros tau ? ?. Exists (eval_id ret_temp tau).
entailer!.
if_tac; auto.
unfold malloc_token.
assert_PROP (field_compatible t [] (eval_id ret_temp tau)).
{ entailer!.
apply malloc_compatible_field_compatible; auto. }
entailer!.
rewrite memory_block_data_at_; auto.
Qed.
Lemma free_spec_sub:
∀ (M:MallocFreeAPD) {cs: compspecs} (t: type),
funspec_sub (snd (free_spec_sz M)) (snd (free_spec M t)).
Proof.
do_funspec_sub. destruct w as [p gv]. clear H.
Exists (sizeof t, p, gv) emp. simpl; entailer!.
if_tac; trivial.
sep_apply data_at__memory_block_cancel.
unfold malloc_token; entailer!.
Qed.
Now, the pattern for using malloc_spec will be as shown in
VSU_stack, like this:
forward_call (malloc_spec_sub M t) gv
forward_call (free_spec_sub M (Tstruct _cons noattr)) (q, gv)
It works like this: t (or, for example, Tstruct _cons noattr) is
any type of the client's choosing, interpreted using the client's own
compspecs. forward_call looks up malloc in the imported
ASIs (that is, in the Gprog), and finds malloc_spec_sz as its
specification. Then it uses the funspec_sub proof to adapt
the spec into malloc_spec M {cs} t, where the cs argument is
instantiated using the client's compspecs, as desired.
And similarly for free.
Next Chapter: VSU_stack
(* 2024-01-02 15:44 *)