4 {1 VerifyThis @ ETAPS 2016 competition, Challenge 2: Binary Tree Traversal}
8 The following is the original description of the verification task,
9 reproduced verbatim from
10 <a href="http://http://etaps2016.verifythis.org/challenges">the competition web site</a>
14 Challenge 2: Binary Tree Traversal
16 Consider a binary tree:
19 Tree left, right, parent;
23 We are given a binary tree with the following properties:
24 It is well formed, in the sense that following a child pointer (left or right) and then following a parent pointer brings us to the original node. Moreover, the parent pointer of the root is null.
25 It has at least one node, and each node has 0 or 2 children.
26 We do not know the initial value of the mark fields.
28 Our goal is to set all mark fields to true. The algorithm below (Morris 1979) works in time linear in the number of nodes, as usual, but uses only a constant amount of extra space.
30 void markTree(Tree root) {
35 if (x.left == null && x.right == null) {
48 upon termination of the algorithm, all mark fields are set
49 the tree shape does not change
50 the code does not crash, and
53 As a bonus, prove that the nodes are visited in depth-first order
56 The following is a solution by Martin Clochard (Université Paris-Sud)
57 who entered the competition.
61 (** Component-as-array memory model with null pointers. *)
65 (** `loc` is the type of memory locations (e.g pointers) *)
67 val predicate eq (x y:loc) ensures { result <-> x = y }
68 (** Kinds of pointer fields. *)
69 type kind = Parent | Left | Right
70 (** Convenience alias for a pointer field table. *)
71 type pmem = map kind (map loc loc)
72 type memory = abstract {
73 mutable accessor : pmem;
74 mutable mark : map loc bool;
76 (** `memo` represent memory. *)
78 (** the memory model has a `null` pointer. *)
80 val null () : loc ensures { result = null }
81 val is_null (l:loc) : bool ensures { result <-> l = null }
82 (** Memory getter & setters. non-null preconditions to
83 check absence of null pointer access. *)
84 val get_mark (l:loc) : bool
85 requires { l <> null }
87 ensures { result = memo.mark[l] }
88 val set_mark (l:loc) (b:bool) : unit
89 requires { l <> null }
91 ensures { memo.mark = (old memo).mark[l <- b] }
92 val get_acc (p:kind) (l:loc) : loc
93 requires { l <> null }
95 ensures { result = memo.accessor[p][l] }
96 val set_acc (p:kind) (l d:loc) : unit
97 requires { l <> null }
98 writes { memo.accessor }
99 ensures { memo.accessor =
100 (old memo).accessor[p <- (old memo).accessor[p][l <- d]] }
101 (** Ghost global accessors. Those are technical tools
102 to create ghost witnesses of past states. *)
103 val ghost get_all_accs () : pmem
105 ensures { result = memo.accessor }
106 val ghost get_all_marks () : map loc bool
108 ensures { result = memo.mark }
110 (** Non-deterministic initialization. In the original alorithm,
111 the variable y starts uninitialized. *)
113 type non_det = abstract { mutable non_det_field : non_det_magic }
114 val non_det : non_det
115 val anyloc () : loc writes { non_det }
119 (** In module `TreeShape` we describe the correlation between
120 the memory and the binary tree model. We also gives
121 elements of the algorithm specification, and prove
122 some frame/separation properties. *)
132 (** The described structure can be modeled as a tree of locations *)
133 type treel = tree loc
135 (** `is_tree memo t c p` describe the fact that `c` points to
136 a well-formed tree modeled by `t`, whose root parent node is `p`. *)
137 predicate is_tree (memo:pmem) (t:treel) (c p:loc) = match t with
139 | Node l m r -> c <> null /\ c = m /\ memo[Parent][c] = p /\
140 let cl = memo[Left][c] in
141 let cr = memo[Right][c] in
142 (cl = null <-> cr = null) /\
143 is_tree memo l cl c /\ is_tree memo r cr c
146 (** `footprint t` is the memory footprint of `t`, e.g the set of
147 locations occuring in the tree. *)
148 function footprint (t:treel) : set loc = match t with
150 | Node l m r -> add m (union (footprint l) (footprint r))
153 (** `ext s memo1 memo2` mean that the pointer fields associated to
154 locations in set `s` are identical in memo1 and memo2. *)
155 predicate ext (s:set loc) (memo1 memo2:pmem) =
156 forall k x. mem x s -> memo1[k][x] = memo2[k][x]
157 (** `unchanged memo1 memo2` mean that all pointer fields in
158 `memo1` and `memo2` are identical. *)
159 predicate unchanged (memo1 memo2:pmem) =
160 forall k x. memo1[k][x] = memo2[k][x]
161 (** `was_marked t memo1 memo2` mean that `memo2` is the update
162 of `memo1` obtained by marking all elements in `t`. *)
163 predicate was_marked (t:treel) (memo1 memo2:map loc bool) =
164 (forall l. mem l (footprint t) -> memo2[l]) /\
165 (forall l. not mem l (footprint t) -> memo2[l] = memo1[l])
167 (** General extensionality property. *)
168 let rec ghost ext_set (s:set loc) (memo1 memo2:pmem)
169 (t:treel) (c p:loc) : unit
170 requires { ext s memo1 memo2 }
171 requires { subset (footprint t) s }
172 requires { is_tree memo1 t c p }
173 ensures { is_tree memo2 t c p }
175 = let ghost rc = ext_set s memo1 memo2 in
178 | Node l _ r -> rc l (memo1[Left][c]) c; rc r (memo1[Right][c]) c
181 (** Specialized for our use case. *)
182 let ghost ext_cur (memo1:pmem) (t:treel) (c p:loc) : unit
184 requires { ext (footprint t) memo1 memo.accessor }
185 requires { is_tree memo1 t c p }
186 ensures { is_tree memo.accessor t c p }
187 = ext_set (footprint t) memo1 (get_all_accs ()) t c p
189 (** The tree model corresponding to a given pointer is unique. *)
190 let rec ghost unicity (memo:pmem) (t1 t2:treel) (c p1 p2:loc) : unit
191 requires { is_tree memo t1 c p1 /\ is_tree memo t2 c p2 }
196 | Empty, _ | _, Empty -> absurd
197 | Node l1 _ r1, Node l2 _ r2 ->
198 unicity memo l1 l2 (memo[Left][c]) c c;
199 unicity memo r1 r2 (memo[Right][c]) c c
202 (** In a non-empty well formed tree, the top pointer
203 cannot occur in the child subtree. Otherwise,
204 their would be an infinite branch in the tree,
205 which is impossible in our inductive tree setting. *)
206 let ghost not_below (memo:pmem) (lf rg:treel) (c p:loc) : unit
207 requires { is_tree memo (Node lf c rg) c p }
208 ensures { not mem c (footprint lf) /\ not mem c (footprint rg) }
209 = let t0 = Node lf c rg in
210 let rec aux (t:treel) (c2 p2:loc) : unit
211 requires { size t < size t0 }
212 requires { is_tree memo t c2 p2 }
213 ensures { not mem c (footprint t) }
218 if eq c2 c then (unicity memo t t0 c p2 p;absurd);
219 aux l (memo[Left][c2]) c2; aux r (memo[Right][c2]) c2
221 aux lf (memo[Left][c]) c; aux rg (memo[Right][c]) c
223 (** Algorithm phases.
224 `GoLeft` mean that the algorithm will try to explore the left
226 `GoRight` mean that the algorithm will explore the right subtree
227 `GoBack` mean that the algorithm will go back to the parent node
228 `Finish` mean that the alogrithm will exit *)
235 function next_phase (ph:phase) : phase =
242 (** `rotated memo1 memo2 ph c` describe how `c` is rotated in
243 `memo2` with respect to its initial position in `memo1` for
244 current phase `ph`. In final phase, it is not rotated but
246 predicate rotated (memo1 memo2:pmem) (ph:phase) (c:loc) =
247 (forall k x. x <> c -> memo1[k][x] = memo2[k][x]) /\
248 (ph <> Finish -> c <> null) /\
250 | GoLeft -> unchanged memo1 memo2
251 | GoRight -> memo2[Left][c] = memo1[Right][c] /\
252 memo2[Right][c] = memo1[Parent][c] /\
253 memo2[Parent][c] = memo1[Left][c]
254 | GoBack -> memo1[Left][c] = memo2[Right][c] /\
255 memo1[Right][c] = memo2[Parent][c] /\
256 memo1[Parent][c] = memo2[Left][c]
262 (** In this module, we prove the algorithm by modifying
263 the code to put it in recursive form. We justify that
264 our limited usage of recursion makes the code equivalent
265 to the one proposed in the challenge. *)
274 (** Recursion-based proof of the algorithm.
275 The principal idea is the following: in its recursive
276 fashion, the algorithm is a standard tree traversal
277 (which is easy to prove correct).
278 Hence we nest the algorithm inside a recursive procedure
279 to link it with its recursive version. However,
280 the obtained algorithm does not really need the call stack
281 to run. Here is how we achieve this:
282 - We create a non-recursive sub-routine `bloc` corresponding to
283 one turn of the while loop in the challenge's code.
284 It uses an exception to simulate exit.
285 Also, this sub-routine does not have any non-ghost argument,
286 so calling `bloc` really amounts to advancing in the loop execution.
287 - We forbids use of any side-effect in the recursive procedure
288 except those obtained by calling `bloc`. Hence calling
289 the recursive procedure is equivalent to run a certain
290 amounts of turns in the loop execution.
291 Note that since we will prove that the recursive procedure
292 terminates, it might not run a finite amount of turns and diverges
294 - After the topmost call to the recursive procedure,
295 we add an infinite loop who only calls `bloc` at every turns.
296 This call and the loop are enclosed in an exception catching
297 construction to detect loop termination.
298 Hence it is justifiable that the algorithm with
299 the recursive procedure is equivalent (in the sense that
300 they have the same side-effects) as an infinite loop of `bloc` calls,
301 encapsulated in an exception catching expression. And this
302 algorithm is evidently equivalent to the original challenge code.
307 let markTree (ghost t:treel) (root:loc) : unit
308 (* Requires a well-formed non-empty tree *)
309 requires { is_tree memo.accessor t root null /\ root <> null }
310 writes { memo, non_det }
311 (* Tree shape is unchanged *)
312 ensures { is_tree memo.accessor t root null }
313 ensures { unchanged (old memo).accessor memo.accessor }
314 (* All nodes are marked. *)
315 ensures { was_marked t (old memo).mark memo.mark }
316 = let x = ref (anyloc ()) in (* ==> Tree x, y *)
317 let y = ref (anyloc ()) in
318 x := root; (* ==> x = root *)
319 let entered = ref false in
320 (* ^ Used to encode the do { .. } while() loop as a while() {..}.
321 Iterations will set the flag. *)
322 let ghost z = ref (null ()) in (* Store previously explored node. *)
323 (* Loop body. `mem0` is memory state at start of recursive call,
325 let bloc (ghost mem0:pmem) (ghost ph:phase) : unit
326 (* current node `!x` is rotated correctly with
327 respect to the phase. *)
328 requires { rotated mem0 memo.accessor ph !x }
329 requires { ph = Finish -> !entered }
330 writes { memo, x, y, z, entered }
331 (* Caracterise `x` and `z` reference updates. *)
332 ensures { !z = !(old x) /\ !x = memo.accessor[Parent][!z] }
333 (* `Finish` phase never ends normally *)
334 ensures { ph <> Finish /\ !entered }
335 (* Node is marked, and other nodes do not change. *)
336 ensures { memo.mark[!z] }
337 ensures { forall l. l <> !z -> memo.mark[l] = (old memo).mark[l] }
338 (* Describe phase shift. *)
340 if (old memo).accessor[Left][!z] = null
341 = (old memo).accessor[Right][!z]
342 then memo.accessor = (old memo).accessor
343 else rotated mem0 memo.accessor (next_phase ph) !z }
344 (* In `Finish` phase, the code throws `Stop` without changing
346 raises { Stop -> ph = Finish /\ memo = old memo }
347 = if !entered && is_null !x then raise Stop; (* ==> do { BODY *)
348 entered := true; (* } while (x != null); *)
350 set_mark !x true; (* ==> x.mark = true; *)
351 if is_null (get_acc Left !x) && is_null (get_acc Right !x)
352 (* ==> if (x.left = null && x.right == null) { *)
353 then y := get_acc Parent !x (* ==> y = x.parent *)
354 else begin (* ==> } else { *)
355 y := get_acc Left !x; (* ==> y = x.left; *)
356 set_acc Left !x (get_acc Right !x); (* ==> x.left = x.right; *)
357 set_acc Right !x (get_acc Parent !x); (* ==> x.right = x.parent; *)
358 set_acc Parent !x !y; (* ==> x.parent = y; *)
360 x := !y (* ==> x = y; *)
362 (* 'Recursive proof', corresponding to the
363 expected depth-first traversal. *)
364 let rec aux (ghost t:treel) : unit
365 requires { !x <> null }
366 requires { is_tree memo.accessor t !x !z }
367 writes { memo, x, y, z, entered }
368 ensures { unchanged (old memo).accessor memo.accessor }
369 ensures { !x = old !z /\ !z = old !x }
370 ensures { was_marked t (old memo).mark memo.mark }
372 raises { Stop -> false }
374 = let ghost mem0 = get_all_accs () in
376 let _lf = get_acc Left !x in
377 let _rg = get_acc Right !x in
378 let lf, rg = ghost match t with
382 (ghost not_below mem0 lf rg _c !z);
384 let b = begin ensures { result <-> _lf = null /\ _rg = null }
385 ensures { result -> lf = Empty /\ rg = Empty }
386 is_null _lf && is_null _rg end in
388 (ghost ext_cur mem0 lf _lf _c);
391 (ghost ext_cur mem0 rg _rg _c);
396 let ghost mem0 = get_all_accs () in
400 invariant { memo = (memo at I) }
401 invariant { rotated mem0 memo.accessor Finish !x /\ !entered }
405 with Stop -> (ghost ext_cur mem0 t root (null ())); () end
409 (** In this module we provide a proof of the initial algorithm by
410 derecursiving the previous one *)
423 (** Snapshot of all relevant memory values in the program *)
428 marks : map loc bool;
431 (** Stack frame in the recursive version. Fields are
432 assigned as the code pointer increase. *)
434 (* Memory & ghost argument at call site (pc>=0). *)
437 (* right & left trees/pointers (pc>=1) *)
442 (* Memory after first bloc call (pc>=2) *)
444 (* Memory after first recursive call (pc >= 4) *)
446 (* Memory after second bloc call (pc >= 5) *)
448 (* Memory after second recursive call (pc >= 6) *)
452 (** Find out current memory state with respect to code pointer. *)
453 function frame_memo (f:frame) (pc:int) : snap =
454 if pc <= 0 then f.memo0
455 else if pc <= 1 then f.memo1
456 else if pc <= 2 then f.memo2
457 else if pc <= 3 then f.memo3
460 (** Postcondition relation for `bloc` calls. *)
461 predicate bloc_rel (mem0:pmem) (ph:phase) (s1 s2:snap) =
462 s2.parent = s1.cursor /\ s2.cursor = s2.pointers[Parent][s2.parent] /\
463 s2.marks[s2.parent] /\
464 (forall l. l <> s2.parent -> s2.marks[l] = s1.marks[l]) /\
465 if s1.pointers[Left][s1.cursor] = null = s1.pointers[Right][s1.cursor]
466 then s2.pointers = s1.pointers
467 else rotated mem0 s2.pointers (next_phase ph) s2.parent
469 (** postcondition relation for recursive (`aux`) calls. *)
470 predicate rec_rel (t:treel) (s1 s2:snap) =
471 unchanged s1.pointers s2.pointers /\
472 s2.cursor = s1.parent /\ s2.parent = s1.cursor /\
473 was_marked t s1.marks s2.marks
478 | Running stack int frame
481 (** Describe a valid call stack. Mostly precise which relation
482 between states holds. Note that in the previous version,
483 Why3 did that part for us via the weakest precondition calculus. *)
484 predicate is_stack (t:treel) (stop scur:snap)
485 (s:stack) (calls:option treel) =
487 | Bottom -> stop = scur /\ calls = Some t
490 (* Correctness of caller stack. *)
492 is_stack t stop m0 s (Some f.tree) /\
493 (* Precondition for recursive call. *)
494 m0.cursor <> null /\ is_tree m0.pointers f.tree m0.cursor m0.parent /\
495 (* Initially obtained pointers & subtrees. *)
496 f.tree = Node f.tleft m0.cursor f.tright /\
497 f.pleft = m0.pointers[Left][m0.cursor] /\
498 f.pright = m0.pointers[Right][m0.cursor] /\
499 (* First bloc run, if completed normally. *)
500 (pc >= 1 -> bloc_rel m0.pointers GoLeft m0 f.memo1 /\
501 f.pleft <> null /\ f.pright <> null) /\
502 (* First recursive call. *)
503 (pc >= 2 -> rec_rel f.tleft f.memo1 f.memo2) /\
504 (* Second bloc run. *)
505 (pc >= 3 -> bloc_rel m0.pointers GoRight f.memo2 f.memo3) /\
506 (* Second recursive call. *)
507 (pc >= 4 -> rec_rel f.tright f.memo3 f.memo4) /\
508 (* Current memory state. *)
509 frame_memo f pc = scur /\
511 | None -> pc <> 1 /\ pc <> 3
513 if pc = 1 then u = f.tleft else pc = 3 /\ u = f.tright
515 | Done -> rec_rel t stop scur /\ calls = None
518 (** Termination argument. In fully general cases, we would need
519 a lexicographic ordering but here an integer size suffice. *)
520 constant large_enough : int = 100
521 function stack_size (st:stack) : int = match st with
525 stack_size s + (large_enough - pc) + if pc = 0
526 then large_enough * (size f.tleft + size f.tright)
528 then large_enough * size f.tright
532 let rec lemma stack_size_pos (st:stack) : unit
533 requires { exists t stop scur calls. is_stack t stop scur st calls }
534 ensures { stack_size st >= 0 }
536 = match st with Running s _ _ -> stack_size_pos s | _ -> () end
538 (** Create a stack frame for a recursive call *)
539 let ghost opening (t ct:treel) (stop scur:snap) (ghost st:ref stack)
540 requires { is_stack t stop scur !st (Some ct) }
541 requires { is_tree scur.pointers ct scur.cursor scur.parent }
542 requires { scur.cursor <> null }
544 ensures { is_stack t stop scur !st None }
545 ensures { stack_size !st <= stack_size !(old st) +
546 large_enough * size ct }
551 memo0 = scur; tree = ct; tleft = lf; tright = rg;
552 pleft = scur.pointers[Left][scur.cursor];
553 pright = scur.pointers[Right][scur.cursor];
554 memo1 = scur; memo2 = scur; memo3 = scur; memo4 = scur;
556 st := Running !st 0 f
559 (** Remove a stack frame at end of recursive call simulation *)
560 let ghost closing (t ct:treel) (stop sprev scur:snap)
561 (ghost st:ref stack) : unit
562 requires { is_stack t stop sprev !st (Some ct) }
563 requires { rec_rel ct sprev scur }
565 ensures { is_stack t stop scur !st None }
566 ensures { stack_size !st < stack_size !(old st) }
568 | Bottom -> st := Done
571 let f = if pc = 1 then { f with memo2 = scur }
572 else { f with memo4 = scur } in
573 st := Running s (pc+1) f
576 (** Advance code pointer when a bloc is run. Takes care to
577 open/close new frame as needed. *)
578 let ghost continuing (t:treel) (stop sprev scur:snap)
580 requires { is_stack t stop sprev !st None }
581 requires { match !st with
582 | Bottom | Done -> false
585 if pc = 0 then GoLeft else if pc = 2 then GoRight else GoBack in
586 bloc_rel f.memo0.pointers ph sprev scur
589 ensures { is_stack t stop scur !st None }
590 ensures { stack_size !st < stack_size !(old st) }
592 | Bottom | Done -> absurd
594 not_below f.memo0.pointers f.tleft f.tright
595 f.memo0.cursor f.memo0.parent;
597 if is_null f.pleft && is_null f.pright
600 assert { match f.tleft, f.tright with
601 Empty, Empty -> true | _ -> false end };
602 closing t f.tree stop f.memo0 scur st
604 let f = { f with memo1 = scur } in
605 st := Running s (pc+1) f;
606 ext_set (footprint f.tleft) f.memo0.pointers scur.pointers
607 f.tleft f.pleft f.memo0.cursor;
608 opening t f.tleft stop scur st;
610 else if pc = 2 then begin
611 let f = { f with memo3 = scur } in
612 st := Running s (pc+1) f;
613 ext_set (footprint f.tright) f.memo0.pointers scur.pointers
614 f.tright f.pright f.memo0.cursor;
615 opening t f.tright stop scur st;
616 end else if pc = 4 then begin
618 assert { unchanged scur.pointers f.memo0.pointers };
619 closing t f.tree stop f.memo0 scur st
623 (** The main algorithm. *)
624 let markTree (ghost t:treel) (root:loc) : unit
625 (* Requires a well-formed non-empty tree *)
626 requires { is_tree memo.accessor t root null /\ root <> null }
627 writes { memo, non_det }
628 (* Tree shape is unchanged *)
629 ensures { is_tree memo.accessor t root null }
630 ensures { unchanged (old memo).accessor memo.accessor }
631 (* All nodes are marked. *)
632 ensures { was_marked t (old memo).mark memo.mark }
633 = let x = ref (anyloc ()) in (* ==> Tree x, y *)
634 let y = ref (anyloc ()) in
635 let ghost z = ref (null ()) in
636 let ghost snapshot () : snap
637 ensures { result.pointers = memo.accessor }
638 ensures { result.cursor = !x }
639 ensures { result.parent = !z }
640 ensures { result.marks = memo.mark }
641 = { pointers = get_all_accs ();
642 cursor = !x; parent = !z;
643 marks = get_all_marks () } in
644 x := root; (* ==> x = root; *)
645 let ghost stop = snapshot () in
646 let ghost scur = ref stop in
647 let ghost st = ref Bottom in
648 ghost opening t t stop stop st;
649 let entered = ref false in (* encode do-while loop. *)
650 while not (begin ensures { result <-> !x = null }
651 !entered && is_null !x end)
652 do (* ==> do { BODY } while(x != null); *)
653 invariant { !x = null -> !entered }
654 invariant { !scur.pointers = memo.accessor }
655 invariant { !scur.cursor = !x }
656 invariant { !scur.parent = !z }
657 invariant { !scur.marks = memo.mark }
658 invariant { is_stack t stop !scur !st None }
659 variant { stack_size !st }
662 set_mark !x true; (* ==> x.mark = true; *)
663 if is_null (get_acc Left !x) && is_null (get_acc Right !x)
664 (* ==> if (x.left == null && x.right == null) { *)
666 y := get_acc Parent !x; (* ==> y = x.parent; *)
668 else begin (* ==> } else { *)
669 y := get_acc Left !x; (* ==> y = x.left; *)
670 set_acc Left !x (get_acc Right !x); (* ==> x.left = x.right; *)
671 set_acc Right !x (get_acc Parent !x); (* ==> x.right = x.parent; *)
672 set_acc Parent !x !y; (* ==> x.parent = y; *)
674 x := !y; (* ==> x = y; *)
675 let ghost sprev = !scur in
676 ghost scur := snapshot ();
677 ghost continuing t stop sprev !scur st;
678 done; (* ==> end 'do while' *)
679 ghost (match !st with Done -> () | _ -> absurd end);
680 ghost ext_cur stop.pointers t root (null ())