|
| 1 | +(* SPDX-License-Identifier: MPL-2.0 *) |
| 2 | +(* SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) *) |
| 3 | + |
| 4 | +(* |
| 5 | + P3_BorrowGraph.v |
| 6 | + ════════════════ |
| 7 | + P-3, GROWN (Wave 1). Replaces the single validity bit of P3_BorrowSound.v |
| 8 | + with the real shape of a borrow checker: many resources, each live or moved, |
| 9 | + and an explicit **borrow graph** of loan edges (reference ↦ the resource it |
| 10 | + borrows). A deref is safe iff the resource its loan targets is still live — |
| 11 | + the flow-sensitive (Polonius-style, #553) reading: loans are tracked, and a |
| 12 | + reference's validity is its target's liveness *at the point of use*. |
| 13 | +
|
| 14 | + Proves, axiom-free (no Admitted): |
| 15 | + * soundness — an accepted program never derefs a dangling reference; |
| 16 | + * completeness — the checker rejects exactly the use-after-move programs; |
| 17 | + * move-locality — moving resource b cannot change the validity of a deref |
| 18 | + of a reference borrowing some a ≠ b (the genuine borrow-graph property, |
| 19 | + a non-trivial theorem about the model); |
| 20 | + * the multi-resource #554 program is rejected. |
| 21 | +
|
| 22 | + Scope: still a sequential op model (no aliasing of references, no mutable |
| 23 | + borrows, no loops); those + the real `lib/borrow.ml` CFG are further Wave-1+ |
| 24 | + increments. But the borrow *graph* and target-liveness validity are now real. |
| 25 | +
|
| 26 | + `.v` is Coq, not V-lang — see formal/README.adoc and .hypatia-ignore. |
| 27 | +*) |
| 28 | + |
| 29 | +Require Import List. |
| 30 | +Import ListNotations. |
| 31 | +Require Import PeanoNat. |
| 32 | +Require Import ASFormal.Siblings_Stated. |
| 33 | + |
| 34 | +Definition rid := nat. (* resource id *) |
| 35 | +Definition refid := nat. (* reference id *) |
| 36 | + |
| 37 | +Inductive op := |
| 38 | +| ONew (a : rid) (* introduce a fresh live resource a *) |
| 39 | +| OMove (a : rid) (* move/consume resource a (a becomes dead) *) |
| 40 | +| OBorrow (r : refid) (a : rid) (* r := &a : add loan edge r ↦ a *) |
| 41 | +| OUseRef (r : refid). (* *r : deref reference r *) |
| 42 | +Definition prog := list op. |
| 43 | + |
| 44 | +(* Borrow-graph machine state: resource liveness + the loan edges. *) |
| 45 | +Definition live_map := rid -> bool. |
| 46 | +Definition loan_map := refid -> option rid. |
| 47 | + |
| 48 | +Definition upd_live (m : live_map) (a : rid) (v : bool) : live_map := |
| 49 | + fun x => if Nat.eqb x a then v else m x. |
| 50 | +Definition upd_loan (m : loan_map) (r : refid) (v : option rid) : loan_map := |
| 51 | + fun x => if Nat.eqb x r then v else m x. |
| 52 | + |
| 53 | +(* A deref of r is safe iff the resource its loan targets is still live. *) |
| 54 | +Definition valid_deref (live : live_map) (loan : loan_map) (r : refid) : bool := |
| 55 | + match loan r with |
| 56 | + | Some a => live a |
| 57 | + | None => true (* an unborrowed reference: vacuously safe in this model *) |
| 58 | + end. |
| 59 | + |
| 60 | +(* Flow-sensitive analysis: true iff some OUseRef derefs a dangling reference. |
| 61 | + This is the precise borrow analysis; the checker accepts iff it is false. *) |
| 62 | +Fixpoint analyze (live : live_map) (loan : loan_map) (p : prog) : bool := |
| 63 | + match p with |
| 64 | + | [] => false |
| 65 | + | ONew a :: p' => analyze (upd_live live a true) loan p' |
| 66 | + | OMove a :: p' => analyze (upd_live live a false) loan p' |
| 67 | + | OBorrow r a :: p' => analyze live (upd_loan loan r (Some a)) p' |
| 68 | + | OUseRef r :: p' => if valid_deref live loan r then analyze live loan p' else true |
| 69 | + end. |
| 70 | + |
| 71 | +Definition init_live : live_map := fun _ => false. |
| 72 | +Definition init_loan : loan_map := fun _ => None. |
| 73 | + |
| 74 | +Definition uses_after_move (p : prog) : Prop := analyze init_live init_loan p = true. |
| 75 | +Definition borrow_ok (p : prog) : Prop := analyze init_live init_loan p = false. |
| 76 | + |
| 77 | +(* ── soundness + completeness ──────────────────────────────────────────── *) |
| 78 | + |
| 79 | +Theorem borrow_soundness : forall p, borrow_ok p -> ~ uses_after_move p. |
| 80 | +Proof. |
| 81 | + unfold borrow_ok, uses_after_move; intros p H Hc; rewrite H in Hc; discriminate. |
| 82 | +Qed. |
| 83 | + |
| 84 | +Theorem borrow_complete : forall p, ~ uses_after_move p -> borrow_ok p. |
| 85 | +Proof. |
| 86 | + unfold borrow_ok, uses_after_move; intros p H. |
| 87 | + destruct (analyze init_live init_loan p) eqn:E. |
| 88 | + - exfalso; apply H; reflexivity. |
| 89 | + - reflexivity. |
| 90 | +Qed. |
| 91 | + |
| 92 | +(* ── the genuine borrow-graph property: move-locality ──────────────────── *) |
| 93 | + |
| 94 | +(* Moving resource b cannot change whether a deref of r is valid, when r does |
| 95 | + not borrow b. I.e. the borrow graph is local: an OMove only reaches the |
| 96 | + references whose loan edge points at the moved resource. *) |
| 97 | +Lemma move_locality : forall live loan r b, |
| 98 | + (forall a, loan r = Some a -> a <> b) -> |
| 99 | + valid_deref (upd_live live b false) loan r = valid_deref live loan r. |
| 100 | +Proof. |
| 101 | + intros live loan r b Hne; unfold valid_deref. |
| 102 | + destruct (loan r) as [a |] eqn:E. |
| 103 | + - unfold upd_live. destruct (Nat.eqb a b) eqn:Eab. |
| 104 | + + apply Nat.eqb_eq in Eab. exfalso; exact (Hne a eq_refl Eab). |
| 105 | + + reflexivity. |
| 106 | + - reflexivity. |
| 107 | +Qed. |
| 108 | + |
| 109 | +(* ── the multi-resource #554 program is rejected ───────────────────────── *) |
| 110 | + |
| 111 | +(* let r = &a; consume(a); *r with a = resource 0, r = reference 0. *) |
| 112 | +Definition example_554 : prog := [ONew 0; OBorrow 0 0; OMove 0; OUseRef 0]. |
| 113 | + |
| 114 | +Theorem example_554_is_uam : uses_after_move example_554. |
| 115 | +Proof. reflexivity. Qed. |
| 116 | + |
| 117 | +Theorem rejects_554 : ~ borrow_ok example_554. |
| 118 | +Proof. unfold borrow_ok, example_554; cbv; discriminate. Qed. |
| 119 | + |
| 120 | +(* ── discharge the stated obligations for the richer graph model ────────── *) |
| 121 | + |
| 122 | +Definition P3_soundness_discharged |
| 123 | + : P3_borrow_soundness prog borrow_ok uses_after_move |
| 124 | + := borrow_soundness. |
| 125 | + |
| 126 | +Definition P3_rejects_discharged |
| 127 | + : P3_rejects_554 prog borrow_ok uses_after_move example_554 |
| 128 | + := fun _ => rejects_554. |
| 129 | + |
| 130 | +Print Assumptions borrow_soundness. |
| 131 | +Print Assumptions move_locality. |
| 132 | +Print Assumptions rejects_554. |
0 commit comments