test_cpp_proof.v
(*
* Copyright (c) 2025 BlueRock Systems, Inc.
* This software is distributed under the terms of the BedRock Open-Source License.
* See the LICENSE-BedRock file in the repository root for details.
*)
This demonstrates a simple way of specifying forward_list and its iterators.
Rather than precisely tracking the index that the iterator is in, we instead
simply track whether the iterator references an actual node or a past-the-end node.
This ensures memory safety.
The specifications of the standard library will support functional correctness reasoning.
Require Import bluerock.auto.cpp.prelude.spec.
Require Import bluerock.auto.cpp.prelude.proof.
Require Import bluerock.cpp.demo.forward_list_v1.test_cpp.
Implicit Type (p : ptr).
(* BEGIN: Upstream *)
Section with_Σ.
Context `{Σ : cpp_logic}.
Definition wand_p_CX {A : Type} p (Q : A -> Rep) := wand.wand_CX (λ y, p |-> Q y).
End with_Σ.
Create HintDb wand_db discriminated.
#[local] Hint Resolve wand_p_CX : wand_db.
Ltac merge_wand := wname [bi_wand] "W"; iDestruct ("W" with "[$]") as "?".
Ltac admit_spec spec := iAssert spec as "#?"; first admit.
(* END: Upstream *)
NES.Begin std.forward_list.
Notation N := "std::__1::forward_list<int, std::__1::allocator<int>>"%cpp_name.
Notation T := "std::__1::forward_list<int, std::__1::allocator<int>>"%cpp_type.
Goal T = Tnamed N. done. Abort.
(* The name of the iterator type. *)
Notation itN := "std::__1::__forward_list_iterator<std::__1::__forward_list_node<int, void*>*>"%cpp_name.
Representation predicate for forward_list<int>.
br.lock Definition R `{Σ : cpp_logic, σ : genv} (q : cQp.t) (xs : list Z): Rep :=
structR N q.
#[only(type_ptr,cfracsplittable)] derive R.
(** [b] is true for non-past-the-end iterators. *)
Representation predicate for forward_list<int>::iterator.
own is a snapshot of the list model at the time that the iterator was created.
b = true means that this iterator is not at the end.
br.lock Definition itR `{Σ : cpp_logic, σ : genv}
(own : option (ptr * cQp.t * list Z)) q (b : bool): Rep :=
structR itN q.
#[only(type_ptr,cfracsplittable)] derive itR.
Section with_Σ.
Context `{Σ : cpp_logic, σ : genv}.
Context `{MOD : test_cpp.source ⊧ σ}.
#[global] Instance R_learn
: Cbn (Learn (any ==> learn_eq ==> learn_hints.fin) R) := ltac:(solve_learnable).
#[global] Instance itR_learn
: Cbn (Learn (learn_eq ==> any ==> learn_eq ==> learn_hints.fin) itR) := ltac:(solve_learnable).
cpp.spec "std::forward_list<int, std::allocator<int>>::forward_list()"
as ctor_spec with
(\this this
\post this |-> R 1$m []).
cpp.spec "std::forward_list<int, std::allocator<int>>::~forward_list()"
as dtor_spec with
(\this this
\pre{xs} this |-> R 1$m xs
\post emp).
cpp.spec "std::forward_list<int, std::allocator<int>>::push_front(int&&)"
as push_front_move_spec with
(\this this
\pre{ns} this |-> R 1$m ns
\arg{p} "n" (Vref p)
\prepost{q n} p |-> intR q n
\post this |-> R 1$m (n :: ns)).
cpp.spec "std::forward_list<int, std::allocator<int>>::pop_front()"
as pop_front_move_spec with
(\this this
\pre{n ns} this |-> R 1$m (n :: ns)
\post this |-> R 1$m ns).
cpp.spec "std::forward_list<int, std::allocator<int>>::empty() const"
as empty_spec with
(\this this
\prepost{ns} this |-> R 1$m ns
\post[Vbool (bool_decide (ns = []))] emp).
This specification consumes the entire list in order to check out a single value. This is similar to Rust's handling of array access, once you perform a mutable borrow of an element from an array, you can not borrow more elements.
Stronger specifications exist, but they require finer-grained tracking of the values.
cpp.spec "std::forward_list<int, std::allocator<int>>::front()"
as front_spec with
(\this this
\pre{n ns} this |-> R 1 (n :: ns)
\post{p}[Vref p]
p |-> intR 1$m n **
(p |-> intR 1$m n -* this |-> R 1 (n :: ns))).
Iterators
Creating an iterator consumes part of the underlying list. This is necessary to prevent concurrent modifications to the list.
cpp.spec "std::forward_list<int, std::allocator<int>>::begin()"
as begin_spec with (
\this this
\pre{q ns} this |-> R q ns
\post{p}[Vptr p] p |-> itR (Some (this, q, ns)) 1$m (bool_decide (ns <> nil))
(* ^^ Return a past-the-end iterator for empty lists. *)
).
cpp.spec "std::forward_list<int, std::allocator<int>>::end()"
as end_spec with (
\this this
\pre{q ns} this |-> R q ns
\post{p}[Vptr p] p |-> itR (Some (this, q, ns)) 1$m false
).
cpp.spec "std::operator==(const std::__forward_list_iterator<std::__forward_list_node<int, void*>*>&, const std::__forward_list_iterator<std::__forward_list_node<int, void*>*>&)"
as it_eq_spec with (
\arg{r1} "" (Vref r1)
\arg{r2} "" (Vref r2)
\prepost{m1 q1 b1} r1 |-> itR m1 q1 b1
\prepost{m2 q2 b2} r2 |-> itR m2 q2 b2
\post{b}[Vbool b] [| if b then b1 = b2
else b1 = true \/ b2 = true |]
).
cpp.spec "std::operator!=(const std::__forward_list_iterator<std::__forward_list_node<int, void*>*>&, const std::__forward_list_iterator<std::__forward_list_node<int, void*>*>&)"
as it_ne_spec with (
\arg{r1} "" (Vref r1)
\arg{r2} "" (Vref r2)
\prepost{m1 q1 b1} r1 |-> itR m1 q1 b1
\prepost{m2 q2 b2} r2 |-> itR m2 q2 b2
\post{b}[Vbool b] [| if bool_decide (b = false) then b1 = b2
else b1 = true \/ b2 = true |]
).
cpp.spec "std::__forward_list_iterator<std::__forward_list_node<int, void*>*>::~__forward_list_iterator()"
as it_dtor_spec with (
\this this
\pre{m b} this |-> itR m 1$m b
\post match m with
| None => emp
| Some (p, q, ls) => p |-> R q ls
end
).
cpp.spec "std::__forward_list_iterator<std::__forward_list_node<int, void*>*>::operator++()"
as it_op_pre_plusplus
with (
\this this
\pre{m} this |-> itR m 1$m true
\post{b'}[Vptr this] this |-> itR m 1$m b'
).
Similar to front above, this specification consumes the entire iterator
ownership in order to check out the current value.
See the comment there for more information.
cpp.spec "std::__forward_list_iterator<std::__forward_list_node<int, void*>*>::operator*() const"
as it_op_star_spec with (
\this this
\let{lp q ls} m := Some (lp, q, ls)
\pre this |-> itR m 1$m true
\post{p}[Vptr p] ∃ i,
p |-> intR q i **
(p |-> intR q i -* this |-> itR m 1$m true)
(* TODO AUTO: this type error produces a raw [False] *)
).
End with_Σ.
NES.End std.forward_list.
Section with_Σ.
Context `{Σ : cpp_logic, σ : genv}.
NES.Open std.forward_list.
Section with_MOD.
Context `{MOD : test_cpp.source ⊧ σ}.
cpp.spec "test(bool)" as test_spec with (
\arg "b" (Vbool true)
\post emp
).
(* TODO: aliases do not work under <<template>> because we do not have access to the
template AST. *)
cpp.spec "TestBasic<template std::__1::forward_list>()" as test_basic with (
\post emp
).
cpp.spec "main()" as main with (
\post[Vint 0] emp
).
cpp.spec "TestIterators<template std::__1::forward_list>()" as test_iterators with (
\post{n}[Vint n] emp).
End with_MOD.
Section iterators.
Lemma test_iterators_ok : verify[source] test_iterators.
Proof.
verify_spec. go.
progress name_locals.
wp_for (fun ρ =>
\pre{b} __begin0_addr |-> itR ?[m] 1$m b
\pre{n} acc_addr |-> uintR 1$m n
\post* ∃ n', acc_addr |-> uintR 1$m n'
\post Exists b', __begin0_addr |-> itR ?m 1$m b').
go.
wp_if; first last. { go. }
go.
simplify_eq/=.
destruct_or!; try by exfalso.
ego.
Qed.
End iterators.
Section basic.
Lemma test_basic_ok : verify[source] test_basic.
Proof.
verify_spec. go.
go with #wand_db.
Qed.
End basic.
End with_Σ.