Graph Copy (Quantified Permissions)
Duplicating a heap-allocated (mutable) graph
field val: Int
field edges: IEdges
// Total function that returns null for those elements that are not present.
domain IEdges {
function edge_lookup(e: IEdges, i: Int): Ref
function has_edge(e: IEdges, i: Int): Bool
function insert_edge(e: IEdges, i: Int, node: Ref): IEdges
function edges_domain(e: IEdges): Set[Int]
function empty_edges(): IEdges
// INSERTION
axiom inserted_edge_present {
forall e: IEdges, i: Int, node: Ref :: edge_lookup(insert_edge(e, i, node), i) == node
}
axiom other_edges_preserved_after_insertion {
forall e: IEdges, i: Int, node: Ref, j: Int :: i != j ==> edge_lookup(e, j) == edge_lookup(insert_edge(e, i, node), j)
}
axiom inserted_edge_defined {
forall e: IEdges, i: Int, node: Ref :: has_edge(insert_edge(e, i, node), i)
}
// HAS EDGE
axiom has_edge_complete {
forall e: IEdges, i: Int :: has_edge(e, i) <==> edge_lookup(e, i) != null
}
// DOMAIN
axiom edges_domain_defined {
forall e: IEdges, i: Int :: i in edges_domain(e) <==> has_edge(e, i)
}
// EMPTY MAP
axiom empty_edges_has_no_nodes {
forall i: Int :: !(has_edge(empty_edges(), i))
}
}
domain INodeMap {
function lookup(node_map: INodeMap, node: Ref): Ref
function has_node(node_map: INodeMap, node: Ref): Bool
function insert(node_map: INodeMap, key_node: Ref, val_node: Ref): INodeMap
function map_domain(node_map: INodeMap): Seq[Ref]
function empty_map(): INodeMap
// INSERTION
axiom inserted_node_present {
forall node_map: INodeMap, key_node: Ref, val_node: Ref :: lookup(insert(node_map, key_node, val_node), key_node) == val_node
}
axiom other_nodes_preserved_after_insertion {
forall node_map: INodeMap, key_node: Ref, val_node: Ref, node: Ref :: node != key_node ==> lookup(node_map, node) == lookup(insert(node_map, key_node, val_node), node)
}
axiom inserted_node_defined {
forall node_map: INodeMap, key_node: Ref, val_node: Ref :: has_node(insert(node_map, key_node, val_node), key_node)
}
// HAS NODE
axiom has_node_complete {
forall node_map: INodeMap, node: Ref :: has_node(node_map, node) <==> lookup(node_map, node) != null
}
// DOMAIN
axiom domain_is_defined {
forall node_map: INodeMap, key: Ref:: key in map_domain(node_map) <==> has_node(node_map, key)
}
// EMPTY MAP
axiom empty_map_has_no_nodes {
forall node: Ref :: !(has_node(empty_map(), node))
}
}
method graph_copy_rec(this: Ref, node_map: INodeMap, setOfRef: Set[Ref], node_map_image: Set[Ref], rd: Perm)
returns (nodeCopy: Ref, res_node_map: INodeMap, res_copy_nodes: Set[Ref])
requires none < rd
requires this != null
// Precondition about setOfRef
requires this in setOfRef
requires |setOfRef intersection node_map_image| == 0
requires forall x: Ref :: x in setOfRef ==> acc(x.val, rd)
requires forall x: Ref :: x in setOfRef ==> acc(x.edges, rd)
requires forall x: Ref, i: Int :: x in setOfRef && i in edges_domain(x.edges) ==> edge_lookup(x.edges, i) in setOfRef
// Precondition about node_map_image
requires forall x: Ref :: x in map_domain(node_map) ==> lookup(node_map, x) in node_map_image
requires forall x: Ref :: x in node_map_image ==> acc(x.val)
requires forall x: Ref :: x in node_map_image ==> acc(x.edges)
ensures nodeCopy != null && nodeCopy in res_copy_nodes
ensures |setOfRef intersection res_copy_nodes| == 0
ensures forall x: Ref :: x in setOfRef ==> acc(x.val, rd)
ensures forall x: Ref :: x in setOfRef ==> x.val == old(x.val)
ensures forall x: Ref :: x in setOfRef ==> acc(x.edges, rd)
ensures forall x: Ref :: x in setOfRef ==> x.edges == old(x.edges)
ensures forall x: Ref, i: Int :: x in setOfRef && i in edges_domain(x.edges) ==> edge_lookup(x.edges, i) in setOfRef
ensures res_copy_nodes == res_copy_nodes union old(node_map_image)
ensures forall x: Ref :: x in map_domain(res_node_map) ==> lookup(res_node_map,x) in res_copy_nodes
ensures forall x: Ref :: x in res_copy_nodes ==> acc(x.val)
ensures forall x: Ref :: x in res_copy_nodes ==> acc(x.edges)
{
var i: Int // an edge index
var S: Set[Int]
if (has_node(node_map, this)) {
nodeCopy := lookup(node_map, this)
res_node_map := node_map
res_copy_nodes := node_map_image
} else {
nodeCopy := new(val, edges)
nodeCopy.val := this.val
res_node_map := insert(node_map, this, nodeCopy)
res_copy_nodes := node_map_image union Set(nodeCopy)
/* The next assert is needed in order to prove that the loop invariant
* assert |setOfRef intersection res_copy_nodes| == 0
* holds before the loop.
*
* See https://bitbucket.org/viperproject/silver/issues/111
*/
assert (setOfRef intersection node_map_image) union (setOfRef intersection Set(nodeCopy))
== setOfRef intersection res_copy_nodes
S := edges_domain(this.edges)
while (|S| > 0)
invariant nodeCopy in res_copy_nodes
invariant this in setOfRef
invariant forall x: Ref :: x in setOfRef ==> acc(x.val, rd)
invariant forall x: Ref :: x in setOfRef ==> x.val == old(x.val)
invariant forall x: Ref :: x in setOfRef ==> acc(x.edges, rd)
invariant forall x: Ref :: x in setOfRef ==> x.edges == old(x.edges)
invariant forall j: Int :: j in S ==> edge_lookup(this.edges, j) in setOfRef
invariant forall r: Ref, j: Int :: r in setOfRef && j in edges_domain(r.edges) ==> edge_lookup(r.edges, j) in setOfRef
invariant node_map_image subset res_copy_nodes
invariant |setOfRef intersection res_copy_nodes| == 0
invariant forall r: Ref :: r in map_domain(res_node_map) ==> lookup(res_node_map,r) in res_copy_nodes
invariant forall r: Ref :: r in res_copy_nodes ==> acc(r.val)
invariant forall r: Ref :: r in res_copy_nodes ==> acc(r.edges)
{
S, i := pop(S)
var newNode: Ref
var newResultMap: INodeMap
var nodeForId: Ref
nodeForId := edge_lookup(this.edges, i)
newNode, res_node_map, res_copy_nodes := graph_copy_rec(nodeForId, res_node_map, setOfRef, res_copy_nodes, rd/2)
nodeCopy.edges := insert_edge(nodeCopy.edges, i, newNode)
}
}
}
/* Non-deterministically remove an int from s1, yielding an updated s2 and the removed int i */
method pop(s1: Set[Int]) returns (s2: Set[Int], i: Int)
requires 0 < |s1|
ensures i in s1
ensures s2 == s1 setminus Set(i)
{ assume false /* Effectively an unimplemented helper method */ }