;;;; The cleanup paths nothing was watching. ;;;; ;;;; Found by mutation testing: each of the six claims below could be broken in ;;;; lib/check.ml, lib/emit.ml or runtime/flan_rt.c and the whole suite stayed ;;;; green. Every case here is one a plausible wrong version gets wrong, and ;;;; the numbers differ per failure so a single wrong answer names its cause. (defstruct Missing [id i32]) (defstruct Other [id i32]) (defvar log i64) (defvar order i64) (defvar seen i64) (defn note [n i64] (set order (+ (* order 10) n))) ;;; (1) An early return must run the defers registered above it, and (2) it ;;; must run them innermost-first. A defer that (8) *calls* something is the ;;; case that puts a guard inside the defer, on the transfer path. (defn early [] i64 (defer (note 1)) (defer (note 2)) (return 7) 0) ;;; (5) A transfer out of a handler-bind has to pop its frames on the way past, ;;; and (6) a handler-bind with two clauses has to pop both, innermost-first. ;;; If either leaks, the stack keeps a frame pointing into a function that has ;;; gone, and the next signal calls into it. (defn deep [] i32 (signal (Missing {.id 1})) 0) (defn leaky [] i32 (restart-case (handler-bind [(Other [c] (set seen (+ seen 1000))) (Missing [c] (invoke-restart 'skip))] (deep)) (skip [] 42))) ;;; (7) Once a handler has answered a signal by transferring, the walk stops: ;;; an outer handler of the same type must not also run. (defn nested [] i32 (restart-case (handler-bind [(Missing [c] (set seen (+ seen 100)))] (handler-bind [(Missing [c] (invoke-restart 'stop))] (deep))) (stop [] 5))) (defn main [] i32 (print (early)) (println "") ; 7 (print order) (println "") ; 21 — innermost first, both ran (print (leaky)) (println "") ; 42 ;; The handler stack must be empty again. If a frame leaked, this signal ;; reaches it and seen moves. (deep) (print seen) (println "") ; 0 (set seen 0) (print (nested)) (println "") ; 5 (print seen) (println "") ; 0 — the outer handler did not run (print log) (println "") ; 0 0)