1 Introduction
Separation logic [15] is an extension of Hoare logic that has been widely used in program verification. The separating conjunction (“star”) in assertions represents the existence of two disjoint states, one that satisfies and one that satisfies . Formally,
Here, represents the disjoint union of two pieces of state/memory. The concisely expresses address (anti)aliasing. For example, if “” is the assertion that data is stored at address , then says is stored at address , is stored at address , and . Separation logic enables one to verify a Hoare triple locally but use it globally, using the frame rule:
Star has a right adjoint separating implication, a.k.a. “magic wand”:
And the following rules are called adjoint rules:
Magic wands are famously difficult to control [16]. In the early days of separation logic, magic wand was used to generate weakest preconditions and verification conditions for automated program verification. However, those verification conditions are not human readable or understandable, and decision procedures for entailment checking with magic wand are quite complex.
In most works of interactive program verification, magic wand is not a necessary component. Authors tend to use forward verification instead of backward verification since it is easier to understand a program correctness proof that goes in the same direction as program execution. “Forward” Hoare logic rules do not generate magic wand expressions; therefore, most authors find that the expressive power of star is already strong enough. For example, we need to define separation logic predicates for different data structures (like records, arrays, linked list and binary trees) in order to verify related programs. Berdine et al. [4] and Charguéraud [8] show that these predicates can be defined with separating conjunction only.
In this paper, we propose a proof technique: magic wand as frame. Specifically, we propose to use magic wand together with quantifiers to define separation logic predicates for partial data structures.
The main content of this paper is a proof pearl. We use our magicwandasframe technique to verify the C program in Fig. 2, insertion into a binary search tree (BST). The program uses a while loop to walk down from the root to the location to insert the new element.
A pointer to a tree has type struct tree *, but we also need the type pointertopointertotree, which we call treebox. The insert function does not return a new tree, it modifies the old tree, perhaps replacing it entirely (if the old tree were the empty tree).
Consider running insert(,8,“h")", where points to a treebox containing the root of a tree as shown in Fig. 2. After one iteration of the loop or two iterations, variable contains address , which is a treebox containing a pointer to a subtree. This subtree and a partial tree (shown within the dashed line) form the original BST.
Naturally, we can verify such a program using a loop invariant with the following form:
To describe partial trees, most authors [4, 8] would have you introduce a new inductive description of tree with exactly one hole—in addition to the inductive description of ordinary trees—and define a corresponding recursive separationlogic predicate “partial tree ”, in addition to the recursive predicate for ordinary trees. (Similarly, “list segment” is inductively defined as a list with one hole.)
That’s a lot of duplication. We propose a different approach in this paper: using magic wand and universal quantifier to express “”. Specifically, it is defined as:
in which represents the result of filling the hole in with .
It has two benefits. (1) Important properties of the partialtree predicate can now be proved by wandadjoint directly. In our Coq formalization, most of these proofs have only a simple line of tacitcs. In comparions, similar properties were usually proved by induction which are hard to automate. (2) We do not even need to define “partial trees” as a new inductive type in Coq. Instead, we treat partial trees as functions from trees to trees. As a result, we get equations like for free.
We organize the rest of this paper as follows:

We verify this C implementation of BST insert using magicwandasframe.

We formalize this correctness proof in Coq using Verifiable C [3].

We show that magicwandasframe also works for other implementations of BST insert, other operations of BST, and other data structures such as linked lists.

We demonstrate our tactic program for proving partial data structure’s elimination rules and composition rules.

We compare our proofs with traditional approaches which use recursively defined predicates. We discuss the power and limitation of using magic wand and we explain the name of our proof technique magicwandasframe.

We discuss related work of using magic wand and summarize our contributions.
Remark 1. The purpose of this paper is NOT about GENERAL magicwandinvolved program verification. What we do propose is a very disciplined way of using magic wand, which can make program correctness proofs more elegant.
Remark 2. Using magic wand, using quantifiers and using high order logics are known methods and tools in program verification. We do not claim the invention of any one of them. Instead, we show a specific way to put them together and make proofs easier.
2 Proof Pearl: Binary search tree insertion
This section demonstrates the main content of this paper: a magicwandasframe verification of BST insert. Here we use standard mathematical notation; in the next section we give details about the Coq formalization and the proof notation of Verifiable C.
2.1 Specification
Correctness for BSTs means that the insert function—considered as an operation of an abstract data type—implements the update operation on finite maps from the key type (in this case, integer) to the range type (in this case void*). The client of a finite map does not need to know that trees are used; we should hide that information in our specifications. For that purpose, we define separation logic predicates for binary trees, and we define map predicates based on tree predicates. Only map predicates show up in the specification of this insert function.
(1)  
Here represents the searchtree property. That is, at any node of the tree , the keys in the left subtree are strictly less than the key at the node, and the keys in the right subtree are strictly greater. is an abstraction relation, which says that is an abstraction of tree , i.e., the keyvalue pairs in map and tree are identical. and are formally defined in the SearchTree chapter of Verified Functional Algorithms [2]. Their exact definitions are not needed in our proof here.
Our highlevel separationlogic specification of insert function is:
We use to represent the value of program variable . In the postcondition, is the usual update operation on maps.
2.2 Twolevel proof strategy
One could directly prove the correctness of the Clanguage insert function, using the searchtree property as an invariant. But it is more modular and scalable to do a twolevel proof instead [1, 9]: First, prove that the C program (imperatively, destructively) implements the (mathematical, functional) function on binary search trees; then prove that the (pure functional) binary search trees implement (mathematical) finite maps, that implements update, and that preserves the searchtree property. So let us define insertion on purefunctional tree structures:
The SearchTree chapter of VFA [2] proves (via the Abs relation) that this implements update on abstract finite maps. Next, we’ll prove that the C program refines this functional program; then compose the two proofs to show that the C program satisfies its specification given at the end of §2.1.
For that refinement proof, we give a lowlevel separationlogic specification of the insert function, i.e., the C program refines the functional program:
(2)  
2.3 Magic wand for partial trees
The function body of is just one loop. We will need a loop invariant! As shown in Fig. 2, the original binary tree can always be divided into two parts after every loop body iteration: one is a subtree whose root is tracked by program variable (that is, is the address of ’s root node) and another part is a partial tree whose root is identical with the original tree and whose hole is marked by address .
The separation logic predicate for trees (also subtrees) is . We define the separation logic predicate for partial trees as follows. Given a partial tree , which is a function from binary trees to binary trees:
This predicate has some important properties and we will use these properties in the verification of . (3a) and (3b) show how singlelayer partial trees are constructed. (3c) shows the construction of empty partial trees. (3d) shows that a subtree can be filled in the hole of a partial tree. And (3e) shows the composition of partial trees.
equationparentequation
(3a)  
(3b)  
(3c)  
(3d)  
(3e) 
2.4 Wandframe proof rules
These properties are direct instances of the magicwandasframe proof rules, which are all derived rules from minimum firstorder separation logic (i.e. intuitionistic first order logic + commutativity and associativity of separating conjunction + being separating conjunction unit + wandajoint).
Here are the proof rules:
wandQframeintro:  
wandQframeelim:  
wandQframehor:  
wandQframever:  
wandQframerefine:  
wandQframeintro proves (3a), (3b) and (3c). wandQframeelim proves (3d). wandQframever and wandQframerefine together prove (3e).
Remark 1. Theoretically, in order to prove (3a)(3e), these wandQframe rules are the only additional proof rules that we need for magic wand and quantifiers. But in practice, when we formalize these proofs in Coq, we also use other proof rules like wandadjoint and the universal quantifier introduction rule.
2.5 Implementation correctness proof
Now, we can verify the function with the loop invariant,
It says, a partial tree and a tree are stored in disjoint pieces of memory, and if we apply the function to locally and fill the hole in with that result, then we will get the same as directly applying to the original binary tree .
The correctness of is based on the following two facts. First, the precondition of implies this loop invariant because we can instantiate the existential variables , and with , and and apply property (3c). Second, the loop body preserves this loop invariant and every return command satisfies the postcondition of the whole C function. Fig. 3 shows our proof (for conciseness, we omit in all assertions).
This loop body has four branches: two of them end with return commands and the other two end normally. In the first branch, the inserted key does not appear in the original tree. This branch ends with a return command at line 3. We show that the program state at that point satisfies the postcondition of the whole function body (line 3). The transition from line 3 to line 3 is sound due to rule (3d). The second branch contains only one command at line 3. We reestablish our loop invariant in this branch (line 3). The transition from line 3 to line 3 is due to rule (3a) and the transition from line 3 to line 3 is due to rule (3e). The third branch at line 3 is like the second, and the last branch is like the first one.
In summary, the partial tree is established as an empty partial tree () in the beginning. The program merges one small piece of subtree into the partial tree in each iteration of the loop body. Finally, when the program returns, it establishes a local insertion result () and fills it in the hole of that partial tree—we know the result must be equivalent with directly applying insertion to the original binary tree. The diagrams above illustrate the situations of these four branches and our proof verifies this process.
3 Coq formalization in Verifiable C
We machinecheck this proof in Coq, using the Verified Software Toolchain’s Verifiable C program logic [3], which is already proved sound w.r.t. CompCert Clight [6]. We import from Verified Functional Algorithms the definition of purely functional search trees and their properties. Readers can find our Coq development online:
https://github.com/PrincetonUniversity/VST/tree/master/wand_demo
We formalize our proof using Verifiable C’s interactive symbolic execution system in Coq [7]. Until now, Verifiable C had not included much proof theory for wand, except the basic wandadjoint. Now we add the proof rules of wandframe (see wandQ_frame.v) as derived lemmas from Verifiable C’s basic separation logic. We use them in the Coq proof of ’s properties (see §3.1 and bst_lemmas.v).
3.1 Separation logic predicates and properties for BST
Binary trees with keys and values are already formalized in VFA as an inductive data type in Coq. Here, we will formalize the separation logic predicate . Instead of defining directly as in (1), we first define , then define based on that. Finally, we prove that it satisfies the equalities in (1). We choose to do this because C functions for BST operations do not always take arguments with type (struct tree * *) (or equivalently, treebox). For example, a lookup operation does not modify a BST, so it can just take a BST by an argument with type (struct tree *).
Here, val is CompCert Clight’s value type; nullval has type val and represents the value of NULL pointer. The Coq type mpred is the type of Verifiable C’s separation logic predicates. “&&”, “” and “” are notations for conjunction, separating conjunction and existential quantifiers in Verifiable C’s assertion language. “!! _” is the notation that injects Coq propositions into the assertion language. The expression (Vint (Int.repr (Z.of_nat x)) injects a natural number x into the integers, then to a 32bit integer,^{1}^{1}1Mapping to is not injective; in a practical application the client of this searchtree module should prove that . then to CompCert Clight’s value type, val.
Data_at is a mapstolike predicate for C aggregate types. Here,
means that x, v, pa, pb are four fields of the “struct tree” stored at address p. Tsh means top share (full read/write permission). Verifiable C’s field_at is like data_at but permits a field name such as “”.
Our partial tree predicate partialT does not care how treebox_rep works internally. Thus, in defining the proof theory of partial trees, we parameterize over the treebox predicate. In consequence, these parameterized proofs can be applied on both partial_treebox_rep and partial_tree_rep.
As claimed in §2, the soundness of rules (3c) (3d) and (3e) do not depend on the definition of ; we prove them sound for arbitrary partial tree predicates. For example, the following lemma is the generalized version of (3d). We directly prove it by wandQframeelim.
We define based on , and as described in §2; and and are already defined in VFA. Similarly, we define based on ; application of it can be found in §4.
3.2 C program specification and verification
Specification and Coq proof goal. Verifiable C requires users to write C function specification in a canonical form. The following is the specification of C function . The WITH clause there says that this specification is a parameterized Hoare triple—that is, for any p0, x, v, m0, this specific triple is valid. The brackets after PRE hold the C argument list. CompCert Clight turns every C variable into an identifier in the Clight abstract syntax tree defined in Coq. In this argument list, _p is the identifier for C variable , etc. The brackets after POST hold the C function return type.
Both precondition and postcondition are written in a PROP/LOCAL/SEP form. PROP clauses are for programvariableirrelevant pure facts; there happen to be none here. LOCAL clauses talk about the values of program variables. For example, temp _p p0 says . SEP clauses are separating conjuncts. Verifiable C requires users to isolate programs variables in their assertions—SEP conjuncts do not refer directly to C program variables—so we use LOCAL clauses to connect program variables to PROP and SEP clauses.
The proof contains two parts. One is to reduce the abstract specification to the concrete specification (6 lines). The other is forward verification using separation logic (65 lines)—it shares exactly the same structure with the penandpaper proof (30 lines) in Fig. 3. The proof scripts can be found in our Coq development. We omit them here.
4 Other data structures, programs and proofs
Magicwandasframe is a pretty flexible proof technique. We briefly introduce some other possibilities in magicwandasframe proofs here. Interested readers can download our Coq development for more details.
4.1 Other BST operations.
We also verify C implementations of BST delete and lookup operation with the magicwandasframe technique. In the verification of BST delete, we also use to describe partial trees and use rules (3a3e) to complete the proof. In the verification of BST lookup, we define using parameterized partialT (see §3) and prove similar proof rules for it. Especifically, we get the counterparts of (3c3e) for free because we have already proved them for general partialT predicates. Proofs of the other two are also very straightforward using wandQintro.
4.2 Other data structure: linked list.
We also use magicwandasframe to verify linked list append (see verif_list.v). In that proof, we use the following separation logic predicates and proof rules (see list_lemmas.v). These proof rules are direct instances of wandQframe rules. Here, we use to represent the list concatenation of and .
(4) 
4.3 Other data structure: C aggregate type.
Another application of magicwandasframe to verify load/store rules for nested C structs. For example, the following is a nested struct definition in C.
We want to derive the following rules
from the following primary rules:
in which represents a tuple of integer tuples, represents the th element of the th element of and is the result of replacing the th element of the th element of with . And later, we will use to represent the th element of . We will use and to represent results of replacing the th element of , and replacing the th element of .
Comments
There are no comments yet.