Skip to content

Commit 6fbff0e

Browse files
committed
Move tutorial.md to mkdocs
1 parent 494d3d3 commit 6fbff0e

18 files changed

+2169
-136
lines changed

docs/README.md

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,65 @@
11
# Welcome to CN
22

3-
Head to [Getting Started](getting-started/what-is-cn.md) to get started with
4-
the CN docs and tutorial.
3+
CN is an extension of the C programming language for testing and verifying the
4+
correctness of C code, especially on low-level systems code. Compared to
5+
standard C, CN checks not only that expressions and statements follow the
6+
correct typing discipline for C-types, but also that the C code executes
7+
_safely_ — does not raise C undefined behaviour — and _correctly_ — satisfying
8+
to strong, user-defined specifications.
9+
10+
CN provides utilities for verifying specifications at compile time as well as
11+
automatically generating unit and integration tests to test specifications at
12+
runtime.
13+
14+
This documentation is a work in progress -- your suggestions are greatly
15+
appreciated!
16+
17+
<div class="grid cards" markdown>
18+
19+
- :material-clock-fast:{ .lg .middle } __Set up in 5 minutes__
20+
21+
---
22+
23+
Build and install CN and get up and running in minutes
24+
25+
[:octicons-arrow-right-24: Installing CN](getting-started/installation)
26+
27+
- :fontawesome-brands-markdown:{ .lg .middle } __Your first spec__
28+
29+
---
30+
31+
Check out the Hello World tutorial to write, test, and verify your first
32+
spec
33+
34+
[:octicons-arrow-right-24: Hello World](getting-started/hello-world)
35+
36+
- :material-format-font:{ .lg .middle } __Tutorials__
37+
38+
---
39+
40+
Find tutorials covering common tasks and introducing CN features
41+
42+
[:octicons-arrow-right-24: Tutorials](getting-started/tutorials)
43+
44+
- :material-scale-balance:{ .lg .middle } __Language reference__
45+
46+
---
47+
48+
Quick reference for CN specification syntax
49+
50+
[:octicons-arrow-right-24: Language reference](reference)
51+
52+
</div>
53+
54+
## Origins
55+
CN was first described in [CN: Verifying Systems C Code with Separation-Logic Refinement Types](https://dl.acm.org/doi/10.1145/3571194) by Christopher Pulte, Dhruv C. Makwana, Thomas Sewell, Kayvan Memarian, Peter Sewell, and Neel Krishnaswami.
56+
To accurately handle the complex semantics of C, CN builds on the [Cerberus semantics for C](https://github.com/rems-project/cerberus/).
57+
Some of the examples in this tutorial are adapted from Arthur Charguéraud’s excellent
58+
[Separation Logic Foundations](https://softwarefoundations.cis.upenn.edu) textbook, and one of the case studies is based on an
59+
extended exercise due to Bryan Parno.
60+
61+
## Acknowledgment of Support and Disclaimer
62+
This material is based upon work supported by the Air Force Research Laboratory (AFRL) and Defense Advanced Research Projects Agencies (DARPA) under Contract No. FA8750-24-C-B044, a European Research Council (ERC) Advanced Grant “ELVER” under the European Union’s Horizon 2020 research and innovation programme (grant agreement no. 789108), and additional funding from Google. The opinions, findings, and conclusions or recommendations expressed in this material are those of the authors and do not necessarily reflect the views of the Air Force Research Laboratory (AFRL).
563

664
## Building these docs
765

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,232 @@
11
# Doubly-linked Lists
2+
3+
<!-- TODO: BCP: The rest of the tutorial (from here to the end) needs to be checked for consistency of naming and capitalization conventions. -->
4+
5+
A doubly linked list is a linked list where each node has a pointer
6+
to both the next node and the previous node. This allows for O(1)
7+
operations for adding or removing nodes anywhere in the list.
8+
9+
Because of all the sharing in this data structure, the separation
10+
reasoning is a bit tricky. We'll give you the core definitions and
11+
then invite you to help fill in the annotations for some of the
12+
functions that manipulate doubly linked lists.
13+
14+
First, here is the C type definition:
15+
16+
```c title="exercises/dll/c_types.h"
17+
--8<--
18+
exercises/dll/c_types.h
19+
--8<--
20+
```
21+
22+
The idea behind the representation of this list is that we don't keep
23+
track of the front or back, but rather we take any node in the list
24+
and have a sequence to the left and to the right of that node. The `left`
25+
and `right` are from the point of view of the node itself, so `left`
26+
is kept in reverse order. Additionally, similarly to in the
27+
`Imperative Queues` example, we can reuse the `List` type.
28+
29+
```c title="exercises/dll/cn_types.h"
30+
--8<--
31+
exercises/dll/cn_types.h
32+
--8<--
33+
```
34+
35+
The predicate for this datatype is a bit complicated. The idea is that
36+
we first own the node that is passed in. Then we follow all of the
37+
`prev` pointers to own everything backwards from the node, and finally
38+
all the `next` pointers to own everything forwards from the node, to
39+
construct the `left` and `right` fields.
40+
41+
<!-- TODO: BCP: Maybe rethink the Own_Forwards / Backwards naming -- would something like Queue_At_Left and Queue_At_Right be clearer?? -->
42+
43+
```c title="exercises/dll/predicates.h"
44+
--8<--
45+
exercises/dll/predicates.h
46+
--8<--
47+
```
48+
49+
Note that `Dll_at` takes ownership of the node passed in, and then
50+
calls `Own_Backwards` and `Own_Forwards`, which recursively take
51+
ownership of the rest of the list.
52+
53+
Also, notice that `Own_Forwards` and `Own_Backwards` include `ptr_eq`
54+
assertions for the `prev` and `next` pointers. This is to ensure that
55+
the nodes in the list are correctly doubly linked. For example, the
56+
line `assert (ptr_eq(n.prev, prev_pointer));` in `Own_Forwards`
57+
ensures that the current node correctly points backward to the
58+
previous node in the list. The line `assert(ptr_eq(prev_node.next,
59+
p));` ensures that the previous node correctly points forward to the
60+
current node.
61+
62+
Before we move on to the functions that manipulate doubly linked
63+
lists, we need to define a few "getter" functions that will allow us
64+
to access the fields of our `Dll` datatype. This will make the
65+
specifications easier to write.
66+
67+
```c title="exercises/dll/getters.h"
68+
--8<--
69+
exercises/dll/getters.h
70+
--8<--
71+
```
72+
73+
We also need some boilerplate for allocation and deallocation.
74+
75+
```c title="exercises/dll/malloc_free.h"
76+
--8<--
77+
exercises/dll/malloc_free.h
78+
--8<--
79+
```
80+
81+
For convenience, we gather all of these files into a single header file.
82+
83+
```c title="exercises/dll/headers.h"
84+
--8<--
85+
exercises/dll/headers.h
86+
--8<--
87+
```
88+
89+
<!-- ====================================================================== -->
90+
91+
Now we can move on to an initialization function. Since an empty list
92+
is represented as a null pointer, we will look at initializing a
93+
singleton list (or in other words, a list with only one item).
94+
95+
```c title="exercises/dll/singleton.c"
96+
--8<--
97+
exercises/dll/singleton.c
98+
--8<--
99+
```
100+
101+
<!-- ====================================================================== -->
102+
103+
The `add` and `remove` functions are where it gets a little tricker.
104+
Let's start with `add`. Here is the unannotated version:
105+
106+
```c title="exercises/dll/add_orig.broken.c"
107+
--8<--
108+
exercises/dll/add_orig.broken.c
109+
--8<--
110+
```
111+
112+
_Exercise_: Before reading on, see if you can figure out what
113+
specification is appropriate and what other are needed.
114+
115+
<!-- TODO: BCP: I rather doubt they are going to be able to come up with this specification on their own! We need to set it up earlier with a simpler example (maybe in a whoile earlier section) showing how to use conditionals in specs. -->
116+
117+
Now, here is the annotated version of the `add` operation:
118+
119+
```c title="exercises/dll/add.c"
120+
--8<--
121+
exercises/dll/add.c
122+
--8<--
123+
```
124+
125+
First, let's look at the pre- and post-conditions. The `requires`
126+
clause is straightforward. We need to own the list centered around
127+
the node that `n` points to. `Before` is a `Dll`
128+
that is either empty, or it has a List to the left,
129+
the current node that `n` points to, and a List to the right.
130+
This corresponds to the state of the list when it is passed in.
131+
132+
In the ensures clause, we again establish ownership of the list, but
133+
this time it is centered around the added node. This means that
134+
`After` is a `Dll` structure similar to `Before`, except that the node
135+
`curr` is now the created node. The old `curr` is pushed into the left
136+
part of the new list. The conditional operator in the `ensures` clause
137+
is saying that if the list was empty coming in, it now is a singleton
138+
list. Otherwise, the left left part of the list now has the data from
139+
the old `curr` node, the new `curr` node is the added node, and the
140+
right part of the list is the same as before.
141+
142+
Now, let's look at the annotations in the function body. CN can
143+
figure out the empty list case for itself, but it needs some help with
144+
the non-empty list case. The `split_case` on `is_null(n->prev)`
145+
tells CN to unpack the `Own_Backwards` predicate. Without this
146+
annotation, CN cannot reason that we didn't lose the left half of the
147+
list before we return, and will claim we are missing a resource for
148+
returning. The `split_case` on `is_null(n->next->next)` is similar,
149+
but for unpacking the `Own_Forwards` predicate. Note that we have to
150+
go one more node forward to make sure that everything past `n->next`
151+
is still owned at the end of the function.
152+
153+
Now let's look at the `remove` operation. Traditionally, a `remove`
154+
operation for a list returns the integer that was removed. However we
155+
also want all of our functions to return a pointer to the
156+
list. Because of this, we define a `struct` that includes an `int`
157+
and a `node`.
158+
159+
```c title="exercises/dll/dllist_and_int.h"
160+
--8<--
161+
exercises/dll/dllist_and_int.h
162+
--8<--
163+
```
164+
165+
Now we can look at the code for the `remove` operation. Here is the un-annotated version:
166+
167+
```c title="exercises/dll/remove_orig.broken.c"
168+
--8<--
169+
exercises/dll/remove_orig.broken.c
170+
--8<--
171+
```
172+
173+
_Exercise_: Before reading on, see if you can figure out what
174+
specification is appropriate and what annotations are needed.
175+
176+
<!-- TODO: BCP: Again, unlikely the reader is going to be able to figure this out without help. We need some hints. -->
177+
178+
Now, here is the fully annotated version of the `remove` operation:
179+
180+
```c title="exercises/dll/remove.c"
181+
--8<--
182+
exercises/dll/remove.c
183+
--8<--
184+
```
185+
186+
First, let's look at the pre- and post-conditions. The `requires` clause says that we cannot remove a node from an empty list, so the pointer passed in must not be null. Then we take ownership of the list, and we
187+
assign the node of that list to the identifier `del`
188+
to make our spec more readable. So `Before` refers to the `Dll` when the function is called, and `del` refers to the node that will be deleted.
189+
190+
Then in the `ensures` clause, we must take ownership
191+
of the `node_and_int` struct as well as the `Dll` that
192+
the node is part of. Here, `After` refers to the `Dll`
193+
when the function returns. We ensure that the int that is returned is the value of the deleted node, as intended. Then we have a complicated nested ternary conditional that ensures that `After` is the same as `Before` except for the deleted node. Let's break down this conditional:
194+
195+
- The first guard asks if both `del.prev` and `del.next` are null. In this case, we are removing the only node in the list, so the resulting list will be empty. The `else` branch of this conditional contains its own conditional.
196+
197+
- For the following conditional, the guard checks if 'del.prev' is
198+
_not_ null. This means that the returned node is `del.next`,
199+
regardless of whether or not `del.prev` is null. If this is the
200+
case, `After` is now centered around `del.next`, and the left part
201+
of the list is the same as before. Since `del.next` was previously
202+
the head of the right side, the right side loses its head in
203+
`After`. This is where we get `After == Dll{left:
204+
Left_Sublist(Before), curr: Node(After), right: Tl(Right(Before))}`.
205+
206+
- The final `else` branch is the case where `del.next` is null, but
207+
`del.prev` is not null. In this case, the returned node is
208+
`del.prev`. This branch follows the same logic as the one before it,
209+
except now we are taking the head of the left side rather than the
210+
right side. Now the right side is unchanged, and the left side is just
211+
the tail, as seen shown in `After == Dll{left:
212+
Tl(Left_Sublist(Before)), curr: Node(After), right: Right(Before)};`
213+
214+
The annotations in the function body are similar to in the `add`
215+
function. Both of these `split_case` annotations are needed to unpack
216+
the `Own_Forwards` and `Own_Backwards` predicates. Without them, CN
217+
will not be able to reason that we didn't lose the left or right half
218+
of the list before we return and will claim we are missing a resource
219+
for returning.
220+
221+
<!-- ====================================================================== -->
222+
223+
_Exercise_: There are many other functions that one might want to
224+
implement for a doubly linked list. For example, one might want to
225+
implement a function that appends one list to another, or a function
226+
that reverses a list. Try implementing a few of these functions and
227+
writing their specifications.
228+
229+
_Exercise_: For extra practice, try coming up with one or two
230+
variations on the Dll data structure itself (there are many!).
231+
232+

0 commit comments

Comments
 (0)