├── .dir-locals.el ├── .editorconfig ├── .git-hooks └── pre-commit ├── .github ├── dependabot.yml └── workflows │ ├── deploy.yml │ └── format.yml ├── .gitignore ├── .prettierignore ├── .prettierrc.json ├── .vscode └── spellright.dict ├── LICENSE ├── README.md ├── docs ├── .markdownlint.yaml ├── .vuepress │ ├── config.ts │ ├── public │ │ ├── assignments │ │ │ └── assignment2_template.tex │ │ ├── favicon.png │ │ └── logo.png │ └── styles │ │ ├── config.scss │ │ └── index.scss ├── README.md ├── assets │ ├── Dafny.tmLanguage.json │ └── smt.tmLanguage.json ├── assignments │ ├── README.md │ ├── assignment1.md │ ├── assignment1_part2.md │ ├── assignment2.md │ ├── assignment3 │ │ ├── README.md │ │ ├── factorial_proof.md │ │ ├── infer_specs.md │ │ ├── intro.md │ │ ├── linked_list_proof.md │ │ └── queue_proof.md │ ├── project.md │ ├── setup.md │ └── sharded_hashmap.md ├── calendar.snippet.md ├── notes │ ├── README.md │ ├── adt_invariants.md │ ├── adt_specs.md │ ├── atomic_specs.md │ ├── barrier.md │ ├── beyond-class.md │ ├── conclusion.md │ ├── concurrency.md │ ├── coq-intro.md │ ├── ghost_state.md │ ├── goose.md │ ├── hoare.md │ ├── induction.md │ ├── invariants.md │ ├── ipm.md │ ├── lec1.md │ ├── liveness.md │ ├── loop_invariants.md │ ├── ltac.md │ ├── macros.snippet.md │ ├── ownership.md │ ├── pbt.md │ ├── persistently.md │ ├── program-proofs │ │ ├── auth_set.md │ │ ├── barrier_proof.md │ │ ├── decide.md │ │ ├── fibonacci_proof.md │ │ ├── fractions.md │ │ ├── input.md │ │ ├── integers.md │ │ ├── named.md │ │ ├── notation.md │ │ ├── search_tree.md │ │ ├── stack_proof.md │ │ └── types.md │ ├── resource-algebra.md │ ├── sep-logic.md │ └── smt.md ├── resources.md └── syllabus.md ├── package.json ├── pnpm-lock.yaml └── tsconfig.json /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ;;; Directory Local Variables -*- no-byte-compile: t -*- 2 | ;;; For more information see (info "(emacs) Directory Variables") 3 | 4 | ((markdown-mode . ((eval . (auto-fill-mode -1))))) 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | insert_final_newline = true 6 | indent_style = space 7 | indent_size = 2 8 | -------------------------------------------------------------------------------- /.git-hooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | FILES=$(git diff --cached --name-only --diff-filter=ACMR | sed 's| |\\ |g') 4 | [ -z "$FILES" ] && exit 0 5 | 6 | # Prettify all selected files 7 | if [ ! -x ./node_modules/.bin/prettier ]; then 8 | echo "run pnpm install to run prettier" 1>&2 9 | exit 1 10 | fi 11 | echo "$FILES" | xargs ./node_modules/.bin/prettier --ignore-unknown --write 12 | 13 | # Add back the modified/prettified files to staging 14 | echo "$FILES" | xargs git add 15 | 16 | exit 0 17 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | updates: 4 | - package-ecosystem: "npm" 5 | directory: "/" 6 | schedule: 7 | interval: "weekly" 8 | - package-ecosystem: "github-actions" 9 | directory: "/" 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: build & deploy 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 10 | permissions: 11 | contents: read 12 | pages: write 13 | id-token: write 14 | 15 | jobs: 16 | build: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v4 21 | with: 22 | # enables history-based dates 23 | fetch-depth: 0 24 | - name: Install pnpm 25 | uses: pnpm/action-setup@v4 26 | - name: Setup Node.js 27 | uses: actions/setup-node@v4 28 | with: 29 | node-version: "22.x" 30 | cache: pnpm 31 | - name: Install dependencies 32 | run: pnpm install --frozen-lockfile 33 | 34 | - name: Build Docs 35 | env: 36 | NODE_OPTIONS: --max_old_space_size=8192 37 | run: pnpm run build 38 | - name: Upload pages artifact 39 | uses: actions/upload-pages-artifact@v3 40 | if: github.ref == 'refs/heads/main' 41 | with: 42 | path: ./docs/.vuepress/dist 43 | 44 | # Deployment job 45 | deploy: 46 | # runs only on main (skipped for PRs) 47 | needs: build 48 | if: github.ref == 'refs/heads/main' 49 | environment: 50 | name: github-pages 51 | url: ${{ steps.deployment.outputs.page_url }} 52 | concurrency: 53 | group: "pages" 54 | cancel-in-progress: true 55 | runs-on: ubuntu-latest 56 | steps: 57 | - name: Deploy to GitHub Pages 58 | id: deployment 59 | uses: actions/deploy-pages@v4 60 | -------------------------------------------------------------------------------- /.github/workflows/format.yml: -------------------------------------------------------------------------------- 1 | name: Check formatting 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | format: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Checkout repository 17 | uses: actions/checkout@v4 18 | - name: Install pnpm 19 | uses: pnpm/action-setup@v4 20 | - name: Setup Node.js 21 | uses: actions/setup-node@v4 22 | with: 23 | node-version: "22.x" 24 | cache: pnpm 25 | - name: Install dependencies 26 | run: pnpm install --frozen-lockfile 27 | - name: Check code formatting 28 | run: pnpm prettier --check . 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | /docs/.vuepress/.temp/ 3 | /docs/.vuepress/.cache/ 4 | /docs/.vuepress/dist/ 5 | /.vscode/settings.json 6 | assignment2_template.pdf 7 | latex.out/ 8 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | pnpm-lock.yaml 2 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/prettierrc", 3 | "overrides": [ 4 | { 5 | "files": "*.md", 6 | "options": { 7 | "proseWrap": "never" 8 | } 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.vscode/spellright.dict: -------------------------------------------------------------------------------- 1 | toc 2 | lsaquo 3 | rsaquo 4 | sys-verif-fa24-proofs 5 | autograder 6 | Fixpoints 7 | Neovim 8 | Coqtail 9 | forall 10 | leq 11 | texttt 12 | ldotp 13 | Vellvm 14 | CertiKOS 15 | vespene 16 | VuePress 17 | shiki 18 | config.ts 19 | frontmatter 20 | corepack 21 | Node.js 22 | uhs.wisc.edu 23 | geq 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Systems verification Fall 2024 course website 2 | 3 | [![deploy](https://github.com/tchajed/sys-verif-fa24/actions/workflows/deploy.yml/badge.svg)](https://github.com/tchajed/sys-verif-fa24/actions/workflows/deploy.yml) [![website](https://img.shields.io/badge/website-blue?logo=web)](https://tchajed.github.io/sys-verif-fa24/) 4 | 5 | [![CC BY-NC 4.0][cc-by-nc-shield]][cc-by-nc] 6 | 7 | [cc-by-nc]: https://creativecommons.org/licenses/by-nc/4.0/ 8 | [cc-by-nc-shield]: https://img.shields.io/badge/License-CC%20BY--NC%204.0-lightgrey.svg 9 | 10 | ## Developing 11 | 12 | You'll need Node.js and pnpm. You can probably use `corepack enable pnpm` to install pnpm (corepack is now packaged with node), but follow the [corepack](https://pnpm.io/installation#using-corepack) instructions if that doesn't work. 13 | 14 | Install the dependencies: `pnpm install`. 15 | 16 | Run a dev server to preview changes: `pnpm dev`. The dev server auto-updates and hot-reloads page content, but not structure. Restart it if you make structural changes that affect the sidebar (e.g., add new files), or start it with `pnpm dev --debug` to do a more expensive reload on every change. 17 | 18 | Auto-format code (with [prettier](https://prettier.io/)): `pnpm fmt`. 19 | 20 | Build static site: `pnpm build`. 21 | 22 | ## Contributing 23 | 24 | Make sure to preview your change with `pnpm dev`: confirm that it compiles, and if any LaTeX is involved, make sure the output doesn't render with red errors. 25 | 26 | Run `pnpm fmt`. 27 | 28 | You can use the provided git pre-commit hook (in `.git-hooks/`) to automatically format your code (you will need to run `pnpm install` first). Set it up with `git config core.hooksPath .git-hooks/`. 29 | 30 | ## Tech stack 31 | 32 | - pnpm for package management 33 | - [VuePress](https://vuepress.vuejs.org/) 34 | - Using the [VuePress Hope theme](https://theme-hope.vuejs.press/) (rather than the default) 35 | - Pretty much the whole setup is in [config.ts](docs/.vuepress/config.ts). YAML frontmatter in individual pages takes care of some aspects. 36 | - Build and deploy via [GitHub Pages workflow](./.github/workflows/deploy.yml) 37 | -------------------------------------------------------------------------------- /docs/.markdownlint.yaml: -------------------------------------------------------------------------------- 1 | ## markdownlint-config-schema 2 | # allow inline HTML (used for Vue components) 3 | MD033: false 4 | # allow long lines (single-line paragraphs) 5 | MD013: false 6 | -------------------------------------------------------------------------------- /docs/.vuepress/config.ts: -------------------------------------------------------------------------------- 1 | import { hopeTheme, navbar, sidebar } from "vuepress-theme-hope"; 2 | import { defineUserConfig } from "vuepress"; 3 | import { viteBundler } from "@vuepress/bundler-vite"; 4 | import { ShikiLang } from "@vuepress/plugin-shiki"; 5 | import { googleAnalyticsPlugin } from "@vuepress/plugin-google-analytics"; 6 | import * as fs from "fs"; 7 | 8 | // Vue Router picks up configuration for paths in the navbar and sidebar from 9 | // the YAML frontmatter, for example the 'title' (or first h1), 'shortTitle', 10 | // and 'icon'. 11 | // 12 | // README.md is used for the index page of a directory. 13 | 14 | const navbarConfig = navbar([ 15 | "/", 16 | "/assignments/", 17 | "/notes/", 18 | { 19 | text: "Other", 20 | icon: "circle-question", 21 | children: [ 22 | { 23 | text: "Calendar", 24 | icon: "calendar", 25 | link: "/#calendar", 26 | }, 27 | "/syllabus.md", 28 | "/resources.md", 29 | ], 30 | }, 31 | ]); 32 | const sidebarConfig = sidebar({ 33 | "/": [ 34 | "", 35 | "syllabus", 36 | "resources", 37 | { 38 | text: "Assignments", 39 | icon: "list-check", 40 | prefix: "assignments/", 41 | link: "/assignments/", 42 | children: "structure", 43 | }, 44 | { 45 | text: "Lecture notes", 46 | icon: "lightbulb", 47 | prefix: "notes/", 48 | link: "/notes/", 49 | children: "structure", 50 | collapsible: true, 51 | expanded: true, 52 | }, 53 | ], 54 | "/notes/": "structure", 55 | "/assignments/": "structure", 56 | }); 57 | 58 | function readShikiLang(path: string, name: string): ShikiLang { 59 | const grammar = JSON.parse(fs.readFileSync(`docs/assets/${path}`, "utf-8")); 60 | return { 61 | ...grammar, 62 | name: name, 63 | }; 64 | } 65 | 66 | const dafnyLang: ShikiLang = readShikiLang("Dafny.tmLanguage.json", "dafny"); 67 | const smtLang: ShikiLang = readShikiLang("smt.tmLanguage.json", "smt2"); 68 | 69 | export default defineUserConfig({ 70 | lang: "en-US", 71 | 72 | // The path to the hosted website from its domain. We deploy to GitHub pages 73 | // which automatically puts websites at .github.io/, 74 | // unless using a custom domain. 75 | base: "/sys-verif-fa24/", 76 | 77 | title: "CS 839", 78 | description: "Systems Verification Fall 2024", 79 | 80 | // .snippet.md files are usable as '@include' files but don't produce output 81 | // pages. 82 | pagePatterns: ["**/*.md", "!**/*.snippet.md", "!.vuepress", "!node_modules"], 83 | 84 | theme: hopeTheme({ 85 | navbar: navbarConfig, 86 | repo: "https://github.com/tchajed/sys-verif-fa24", 87 | toc: { 88 | levels: [2, 3], 89 | }, 90 | sidebar: sidebarConfig, 91 | 92 | plugins: { 93 | git: process.env.NODE_ENV === "production", 94 | // see https://ecosystem.vuejs.press/plugins/markdown/shiki.html for the below config 95 | copyCode: false, 96 | // https://ecosystem.vuejs.press/plugins/search/search.html 97 | search: { 98 | hotKeys: [{ key: "k", ctrl: true }, "/"], 99 | locales: { 100 | "/": { 101 | placeholder: "Search", 102 | }, 103 | }, 104 | }, 105 | photoSwipe: false, 106 | icon: { 107 | assets: "fontawesome", 108 | }, 109 | }, 110 | 111 | markdown: { 112 | // NOTE: this doesn't work (non-existent pages go to a 404 page) 113 | linksCheck: { 114 | dev: true, 115 | build: "error", 116 | }, 117 | tasklist: true, 118 | include: true, 119 | // allow {#custom-id} attributes 120 | attrs: { 121 | allowed: ["id"], 122 | }, 123 | mermaid: true, 124 | math: { 125 | type: "katex", 126 | // copy as text (change to true to copy as LaTeX source) 127 | copy: false, 128 | // the rest of the config is passed to KaTeX, see 129 | // https://katex.org/docs/options.html 130 | macros: { 131 | "□": "\\square", 132 | "∗": "\\sep", 133 | "⊢": "\\entails", 134 | }, 135 | }, 136 | highlighter: { 137 | type: "shiki", 138 | langs: [dafnyLang, smtLang, "coq", "go", "bash", "asm"], 139 | // customized from one-light and one-dark-pro 140 | themes: { 141 | light: "catppuccin-latte", 142 | dark: "catppuccin-macchiato", 143 | }, 144 | // add something like {1,7-9} to the ```lang line 145 | // TODO: disabled for now since text="goal 1" is parsed as highlighting line 1 146 | highlightLines: false, 147 | // add // [!code ++] or // [!code --] to the end of a code line (emitted from template compiler for Coq output diffs) 148 | notationDiff: true, 149 | // add // [!code highlight] to the end of a line 150 | notationHighlight: true, 151 | // add :line-numbers to ```lang line to enable selectively 152 | lineNumbers: false, 153 | }, 154 | }, 155 | 156 | // control page meta information shown 157 | // see https://theme-hope.vuejs.press/guide/feature/meta.html 158 | contributors: false, 159 | editLink: false, // feedback is better than edits/PRs 160 | // Could add "ReadingTime" (and reduce words/minute, default is 300) or 161 | // "Word" to give length estimate. 162 | pageInfo: ["Date", "Category", "Tag"], 163 | print: false, // no need to offer print button 164 | 165 | author: "Tej Chajed", 166 | license: "CC-BY-NC 4.0", 167 | logo: "/logo.png", 168 | favicon: "/favicon.png", 169 | }), 170 | 171 | plugins: [ 172 | googleAnalyticsPlugin({ 173 | id: "G-RMW4PR7J1M", 174 | }), 175 | ], 176 | bundler: viteBundler(), 177 | host: "localhost", 178 | }); 179 | -------------------------------------------------------------------------------- /docs/.vuepress/public/assignments/assignment2_template.tex: -------------------------------------------------------------------------------- 1 | \documentclass[11pt,twoside]{exam} 2 | 3 | \usepackage{amsmath} 4 | \usepackage{color} 5 | \usepackage{hyperref} 6 | 7 | \title{Assignment 2: Separation logic theory} 8 | \author{\textcolor{red}{FILL IN NAME}} 9 | 10 | %% These are the same macros used in the lecture notes, from macros.snippet.md. 11 | %%%%%%%%%%%% macros 12 | 13 | %% basic math 14 | \def\intersect{\cap} 15 | \def\union{\cup} 16 | \def\dom{\operatorname{dom}} 17 | \def\disjoint{\mathrel{\bot}} 18 | \def\finto{\overset{\text{fin}}{\rightharpoonup}} 19 | 20 | %% language 21 | \def\ife#1#2#3{\text{\textbf{if} } #1 \text{ \textbf{then} } #2 \text{ \textbf{else} } #3} 22 | \def\lete#1#2{\text{\textbf{let} } #1 := #2 \text{ \textbf{in} }} 23 | \def\letV#1#2{&\text{\textbf{let} } #1 := #2 \text{ \textbf{in} }} 24 | \def\num#1{\overline{#1}} 25 | \def\true{\mathrm{true}} 26 | \def\false{\mathrm{false}} 27 | \def\fun#1{\lambda #1.\,} 28 | \def\funblank{\fun{\_}} 29 | \def\rec#1#2{\text{\textbf{rec} } #1 \; #2.\;\,} 30 | \def\app#1#2{#1 \, #2} 31 | \def\then{;\;} 32 | \def\assert#1{\operatorname{assert} \, #1} 33 | \def\val{\mathrm{val}} 34 | \def\purestep{\xrightarrow{\text{pure}}} 35 | 36 | %% hoare logic 37 | \def\False{\mathrm{False}} 38 | \def\True{\mathrm{True}} 39 | \def\hoare#1#2#3{\left\{#1\right\} \, #2 \, \left\{#3\right\}} 40 | \def\hoareV#1#2#3{\begin{aligned}% 41 | &\left\{#1\right\} \\ &\quad #2 \\ &\left\{#3\right\}% 42 | \end{aligned}} 43 | \def\wp{\operatorname{wp}} 44 | \def\outlineSpec#1{\left\{#1\right\}} 45 | \def\entails{\vdash} 46 | \def\bient{\dashv\vdash} 47 | \def\eqnlabel#1{\:\:\text{#1}} 48 | \def\lift#1{\lceil #1 \rceil} 49 | 50 | %% separation logic 51 | % imperative constructs 52 | \def\load#1{{!}\,#1} 53 | \def\store#1#2{#1 \mathbin{\gets} #2} 54 | \def\free#1{\operatorname{free} \, #1} 55 | \def\alloc#1{\operatorname{alloc} \, #1} 56 | % logic 57 | \def\sep{\mathbin{\raisebox{1pt}{$\star$}}} 58 | \def\bigsep{\mathop{\vcenter{\LARGE\hbox{$\star$}}}} 59 | \def\wand{\mathbin{\raisebox{1pt}{$-\hspace{-0.06em}\star$}}} 60 | \def\emp{\mathrm{emp}} 61 | \def\pointsto{\mapsto} 62 | \def\Heap{\mathrm{Heap}} 63 | \def\Loc{\mathrm{loc}} 64 | 65 | %%%%%% end of macros 66 | 67 | %% ``exam'' setup 68 | \pointsinmargin 69 | \pointformat{} 70 | \printanswers 71 | 72 | \begin{document} 73 | \maketitle 74 | 75 | \begin{questions} 76 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 77 | 78 | %% Exercise 1 79 | \question[5] 80 | Prove the rule of consequence for Hoare logic, from the soundness definition for 81 | a Hoare triple. 82 | 83 | \paragraph{Consequence} 84 | \[ 85 | \frac{P' \entails P \quad (\forall v.\, Q(v) \entails Q'(v)) \quad \hoare{P}{e}{Q(v)}}{% 86 | \hoare{P'}{e}{\fun{v} Q'(v)} 87 | } 88 | \] 89 | 90 | \paragraph{Soundness} 91 | $\hoare{P}{e}{\fun{v} Q(v)}$ means if $P$ holds and $e \to^* e'$, then either 92 | (a) $\exists e''.\, e' \to e''$ ($e'$ is not stuck), or (b) there exists a value 93 | $v'$ such that $e' = v'$ and $Q(v')$ holds. 94 | 95 | \begin{solution} 96 | YOUR SOLUTION HERE 97 | \end{solution} 98 | 99 | %% Exercise 2 100 | \question 101 | For each proposition of separation logic below, state precisely the set of heaps 102 | it describes. 103 | 104 | \begin{parts} 105 | \part[1] $\exists v.\, \ell \pointsto v$ 106 | \begin{solution} 107 | YOUR SOLUTION HERE 108 | \end{solution} 109 | \part[1] $\exists \ell'.\, \ell' \pointsto v$ 110 | \begin{solution} 111 | YOUR SOLUTION HERE 112 | \end{solution} 113 | \part[1] $\forall v.\, \ell \pointsto v$ 114 | \begin{solution} 115 | YOUR SOLUTION HERE 116 | \end{solution} 117 | \part[1] $(\ell \pointsto v) \sep \True$ 118 | \begin{solution} 119 | YOUR SOLUTION HERE 120 | \end{solution} 121 | \part[1] $(\ell \pointsto v) \sep (\True \land \emp)$ 122 | \begin{solution} 123 | YOUR SOLUTION HERE 124 | \end{solution} 125 | \part[1] $\ell \pointsto v \land \ell \pointsto v$ 126 | \begin{solution} 127 | YOUR SOLUTION HERE 128 | \end{solution} 129 | \part[1] $\ell \pointsto v \sep \ell \pointsto v$ 130 | \begin{solution} 131 | YOUR SOLUTION HERE 132 | \end{solution} 133 | \part[1] $(\exists v.\, \ell_1 \pointsto v) \sep (\exists \ell.\, \ell \pointsto \num{3})$ 134 | \begin{solution} 135 | YOUR SOLUTION HERE 136 | \end{solution} 137 | \part[1] $(\exists v.\, \ell \pointsto v) \land (\exists \ell'.\, \ell' \pointsto \num{3})$ 138 | \begin{solution} 139 | YOUR SOLUTION HERE 140 | \end{solution} 141 | \part[1] $(\exists x. \lift{x > 2}) \sep (\exists x. \lift{x < 2})$ 142 | \begin{solution} 143 | YOUR SOLUTION HERE 144 | \end{solution} 145 | \end{parts} 146 | 147 | %% Exercise 3 148 | \question 149 | Compare the separation logic frame rule to the rule for weakening. Explain why the weaken rule does not imply the frame rule. Explain why the frame rule does not imply the weaken rule. These explanations are not meant to be fully formal proofs (which is well beyond the scope of this class for this question), but should aim to be convincing explanations --- in this case, convincing me that you've understood what these rules mean. 150 | 151 | \begin{solution} 152 | YOUR SOLUTION HERE 153 | \end{solution} 154 | 155 | %% Exercise 4 156 | \question 157 | Your friend Ben Bitdiddle has read the section on recursion and loops and thinks he has found a bug in separation logic. He claims to have proven the following triple: 158 | 159 | \[ 160 | \hoare{\True}{(\rec{f}{x} f \, x) \, ()}{\fun{\_} \False} 161 | \] 162 | 163 | ``This proves False with no hypotheses, which makes separation logic unsound!'' he exclaims, a little too excited at having broken what you spent so much time learning. 164 | 165 | What do you say to Ben? 166 | 167 | \begin{solution} 168 | YOUR SOLUTION HERE 169 | \end{solution} 170 | 171 | %% Exercise 5 172 | \question 173 | 174 | We will develop a linked list implementation in our ``lecture notes'' programming language (which I'll call \texttt{expr} here) and give it a specification using a \emph{representation predicate}. 175 | 176 | This problem will follow the presentation of linked lists in Robbert Krebbers's 177 | program verification course. We will be following the notes from that class on 178 | \href{https://gitlab.science.ru.nl/program-verification/course-2023-2024/-/blob/master/lectures/week10.md}{separation 179 | logic}. 180 | 181 | \begin{parts} 182 | \part[0] 183 | 184 | See the \href{https://tchajed.github.io/sys-verif-fa24/assignments/assignment2.html}{web version} for the problem setup. 185 | 186 | \part[10] 187 | Consider the following function for appending two linked lists. It's your job to figure out exactly how it works (that is, how does it manage the memory of the two lists?). 188 | 189 | \begin{verbatim} 190 | Fixpoint app_list (l1: ref (llist A)) (l2: ref (llist A)) := 191 | match !l1 with 192 | | lnil => l1 <- !l2; free l2; () 193 | | lcons hd tl => app_list tl y 194 | end 195 | \end{verbatim} 196 | 197 | Write a specification for \texttt{app\_list l1 l2}. You may assume an affine separation logic, so the postcondition can drop any facts you don't think are relevant. 198 | 199 | \begin{solution} 200 | YOUR SOLUTION HERE 201 | \end{solution} 202 | 203 | \part[15] 204 | Write a proof outline that shows \texttt{app\_list l1 l2} meets your specification. You may assume an affine separation logic. 205 | 206 | Remember that if you think something is wrong here, you may need to re-visit your specification. I can check your specification and give feedback before you spend too much time proving it. 207 | 208 | %% LaTeX advice: 209 | 210 | You can write your solution in a \texttt{verbatim} environment, similar to the 211 | separation logic course's notes. Use \texttt{|->} as an ASCII substitute for the 212 | points-to assertion. Or, you can typeset it in an \texttt{aligned} environment 213 | in math mode. 214 | 215 | \begin{solution} 216 | YOUR SOLUTION HERE 217 | \end{solution} 218 | 219 | \part[10] 220 | 221 | Let us now implement a function to get the tail of a list: 222 | 223 | \begin{verbatim} 224 | Definition tail (l: llist A) : llist A := 225 | match x with 226 | | lnil => () 227 | | lcons hd tl => !tl 228 | \end{verbatim} 229 | 230 | Consider the following specification for \texttt{tail}: 231 | 232 | \begin{verbatim} 233 | [ list_rep l (x :: xs) ] 234 | tail l 235 | [ v. list_rep l (x :: xs) * list_rep v xs ] 236 | \end{verbatim} 237 | 238 | Is this specification true? If yes, prove this specification. If not, explain why, find another valid specification, and prove it. 239 | 240 | \begin{solution} 241 | YOUR SOLUTION HERE 242 | \end{solution} 243 | 244 | \end{parts} 245 | 246 | %% Bonus question 247 | \question 248 | 249 | Find a typo in the lecture notes on Hoare logic or Separation Logic. Submit a 250 | GitHub pull request with a fix. 251 | 252 | In your solution here just mention your GitHub username or what pull request 253 | number(s) you submitted. 254 | 255 | \begin{solution} 256 | YOUR SOLUTION HERE 257 | \end{solution} 258 | 259 | 260 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 261 | \end{questions} 262 | \end{document} 263 | -------------------------------------------------------------------------------- /docs/.vuepress/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tchajed/sys-verif-fa24/a85ac6fb967b5fece1cdacf7ef351ff7b7d712b3/docs/.vuepress/public/favicon.png -------------------------------------------------------------------------------- /docs/.vuepress/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tchajed/sys-verif-fa24/a85ac6fb967b5fece1cdacf7ef351ff7b7d712b3/docs/.vuepress/public/logo.png -------------------------------------------------------------------------------- /docs/.vuepress/styles/config.scss: -------------------------------------------------------------------------------- 1 | // this green is the default 2 | $theme-color: #3eaf7c; 3 | -------------------------------------------------------------------------------- /docs/.vuepress/styles/index.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | --hint-font-size: 1rem; 3 | } 4 | 5 | // see https://theme-hope.vuejs.press/guide/customize/color.html 6 | .language-coq { 7 | --code-c-bg: hsl(0, 0%, 97%); 8 | [data-theme="dark"] & { 9 | --code-c-bg: hsl(240, 10%, 25%); 10 | } 11 | } 12 | 13 | /* 14 | .language-txt[data-title="goal diff"] { 15 | } 16 | .language-txt[data-title*="goal "] { 17 | } 18 | .language-txt[data-title="coq output"] { 19 | } 20 | */ 21 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "CS 839: Systems Verification" 3 | shortTitle: "Home" 4 | icon: "circle-check" 5 | toc: false 6 | date: 2024-09-04 08:00:00 -5 7 | --- 8 | 9 | This is a graduate-level class on _systems verification_: how to mathematically prove the correctness of programs with machine-checked proofs. A core technique throughout the class is _separation logic_, which we will use to reason about real-world programs written in Go, including using loop invariants, reasoning about slices, pointer-based data structures, and concurrency. Programming assignments will involve proving theorems with the Coq proof assistant. 10 | 11 | ::: info Course info 12 | 13 | **Instructor**: Tej Chajed ‹› \ 14 | **Office**: CS 7361 \ 15 | **Office hours**: Mon/Wed 2-3pm \ 16 | **Lecture**: Mon/Wed 9:30-10:45am \ 17 | **Classroom**: Engineering Hall 2349 18 | 19 | ::: 20 | 21 | We'll use [Canvas](https://canvas.wisc.edu/courses/425519) for submitting assignments and [Piazza](https://piazza.com/wisc/fall2024/cs839007) for questions (or email me). 22 | 23 | The source of this website is on GitHub at [tchajed/sys-verif-fa24](https://github.com/tchajed/sys-verif-fa24). You can open an Issue or use Discussions if anything is confusing about the lecture notes, or post on Piazza. Your fellow students as well as students in future iterations of this class will thank you! 24 | 25 | ## Resources 26 | 27 | See the course [lecture notes](./notes/) and the [resources](./resources.md) page. 28 | 29 | ## Calendar 30 | 31 | 32 | 33 | ## Note for instructors 34 | 35 | If you're interested in this class please send me an email at . I'd love to hear what you think! 36 | 37 | The course material (including this website) is licensed under the Creative Commons Attribution-NonCommercial 4.0 International License. See [CC-BY-NC 4.0](https://creativecommons.org/licenses/by-nc/4.0/) for the text of the license. 38 | -------------------------------------------------------------------------------- /docs/assets/Dafny.tmLanguage.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileTypes": ["dfy"], 3 | "foldingStartMarker": "(\\{\\s*(//.*)?$|^\\s*// \\{\\{\\{)", 4 | "foldingStopMarker": "^\\s*(\\}|// \\}\\}\\}$)", 5 | "name": "Dafny", 6 | "patterns": [ 7 | { 8 | "begin": "(\\bvar\\b)\\s*", 9 | "beginCaptures": { 10 | "1": { 11 | "name": "keyword.control.dafny" 12 | } 13 | }, 14 | "end": ":=|:\\||;|\\)|\\}|(?=(?:abstract|class|const|constructor|export|function|ghost|greatest|import|include|iterator|least|lemma|method|module|opaque|opened|predicate|refines|static|trait|twostate|var)\\b)", 15 | "name": "meta.vardecl.dafny", 16 | "patterns": [ 17 | { 18 | "include": "#attributes" 19 | }, 20 | { 21 | "include": "#comments" 22 | }, 23 | { 24 | "include": "#comments-inline" 25 | }, 26 | { 27 | "include": "#generics" 28 | }, 29 | { 30 | "include": "#formalTypeDeclaration" 31 | } 32 | ] 33 | }, 34 | { 35 | "begin": "(class\\b)\\s*([\\w'?]+\\b(?!<))?\\s*", 36 | "beginCaptures": { 37 | "1": { 38 | "name": "keyword.control.dafny" 39 | }, 40 | "2": { 41 | "name": "storage.type.dafny" 42 | } 43 | }, 44 | "end": "([\\w'?]+)?\\s*\\{(?!:)", 45 | "endCaptures": { 46 | "1": { 47 | "name": "storage.type.dafny" 48 | } 49 | }, 50 | "name": "meta.class.identifier.dafny", 51 | "patterns": [ 52 | { 53 | "include": "#attributes" 54 | }, 55 | { 56 | "include": "#comments" 57 | }, 58 | { 59 | "include": "#comments-inline" 60 | }, 61 | { 62 | "include": "#generics" 63 | }, 64 | { 65 | "match": "(extends)\\s+([\\w'?]+)", 66 | "captures": { 67 | "1": { 68 | "name": "keyword.control.dafny" 69 | }, 70 | "2": { 71 | "name": "storage.type.dafny" 72 | } 73 | }, 74 | "name": "meta.class.extends.identifier" 75 | } 76 | ] 77 | }, 78 | { 79 | "include": "#code" 80 | } 81 | ], 82 | "repository": { 83 | "formalTypeDeclaration": { 84 | "name": "meta.type.formaldeclaration", 85 | "begin": "(?(?=\\())", 95 | "beginCaptures": { 96 | "1": { 97 | "name": "entity.name.function.dafny" 98 | } 99 | }, 100 | "end": ">(?=\\()", 101 | "name": "meta.method.genericfunctioncall", 102 | "patterns": [ 103 | { 104 | "include": "#generics" 105 | }, 106 | { 107 | "match": "[\\w'?]+", 108 | "name": "storage.type.dafny" 109 | } 110 | ] 111 | }, 112 | "functioncalls": { 113 | "begin": "([\\w'?]+)\\s*\\(", 114 | "beginCaptures": { 115 | "1": { 116 | "name": "entity.name.function.dafny" 117 | } 118 | }, 119 | "end": "\\)", 120 | "name": "meta.method.functioncall", 121 | "patterns": [ 122 | { 123 | "include": "#parameters" 124 | }, 125 | { 126 | "include": "#keywords" 127 | }, 128 | { 129 | "include": "#keywordsIfNotHandled" 130 | } 131 | ] 132 | }, 133 | "code": { 134 | "patterns": [ 135 | { 136 | "include": "#comments" 137 | }, 138 | { 139 | "include": "#comments-inline" 140 | }, 141 | { 142 | "include": "#attributes" 143 | }, 144 | { 145 | "include": "#strings" 146 | }, 147 | { 148 | "include": "#keywords" 149 | }, 150 | { 151 | "include": "#genericfunctioncalls" 152 | }, 153 | { 154 | "include": "#generics" 155 | }, 156 | { 157 | "include": "#parameters" 158 | }, 159 | { 160 | "include": "#functioncalls" 161 | }, 162 | { 163 | "include": "#typedeclaration" 164 | }, 165 | { 166 | "include": "#keywordsIfNotHandled" 167 | } 168 | ] 169 | }, 170 | "attributes": { 171 | "patterns": [ 172 | { 173 | "begin": "@([\\w'\\?]+)\\(", 174 | "beginCaptures": { 175 | "1": { 176 | "name": "entity.other.attribute-name.dafny" 177 | } 178 | }, 179 | "end": "\\)", 180 | "name": "attribute", 181 | "patterns": [ 182 | { 183 | "include": "#code" 184 | } 185 | ] 186 | }, 187 | { 188 | "begin": "@([\\w'\\?]+)(?!\\()", 189 | "beginCaptures": { 190 | "1": { 191 | "name": "entity.other.attribute-name.dafny" 192 | } 193 | }, 194 | "end": "(?<=[\\w'\\?]+)", 195 | "name": "attribute", 196 | "patterns": [] 197 | }, 198 | { 199 | "begin": "\\{:(\\w+)", 200 | "beginCaptures": { 201 | "1": { 202 | "name": "entity.other.attribute-name.dafny" 203 | } 204 | }, 205 | "end": "\\}", 206 | "name": "attribute", 207 | "patterns": [ 208 | { 209 | "include": "#strings" 210 | }, 211 | { 212 | "include": "#keywords" 213 | } 214 | ] 215 | } 216 | ] 217 | }, 218 | "comments": { 219 | "patterns": [ 220 | { 221 | "captures": { 222 | "0": { 223 | "name": "punctuation.definition.comment.dafny" 224 | } 225 | }, 226 | "match": "/\\*\\*/", 227 | "name": "comment.block.empty.dafny" 228 | }, 229 | { 230 | "include": "#comments-inline" 231 | } 232 | ] 233 | }, 234 | "comments-inline": { 235 | "patterns": [ 236 | { 237 | "begin": "/\\*", 238 | "captures": { 239 | "0": { 240 | "name": "punctuation.definition.comment.dafny" 241 | } 242 | }, 243 | "end": "\\*/", 244 | "name": "comment.block.dafny", 245 | "patterns": [ 246 | { 247 | "include": "#comments-inline" 248 | } 249 | ] 250 | }, 251 | { 252 | "captures": { 253 | "1": { 254 | "name": "comment.line.double-slash.dafny" 255 | }, 256 | "2": { 257 | "name": "punctuation.definition.comment.dafny" 258 | } 259 | }, 260 | "match": "\\s*((//).*$\\n?)" 261 | } 262 | ] 263 | }, 264 | "strings": { 265 | "patterns": [ 266 | { 267 | "begin": "@\"", 268 | "end": "\"(?!\")", 269 | "name": "string.multiline.dafny", 270 | "patterns": [ 271 | { 272 | "match": "\"\"" 273 | } 274 | ] 275 | }, 276 | { 277 | "begin": "\"", 278 | "end": "(\"|\\n)", 279 | "name": "string.singleline.dafny", 280 | "patterns": [ 281 | { 282 | "match": "\\\\((\\\\)|(\"))" 283 | } 284 | ] 285 | } 286 | ] 287 | }, 288 | "typedeclaration": { 289 | "patterns": [ 290 | { 291 | "begin": "\\b(datatype|codatatype|type|newtype)(?=.*=)\\b", 292 | "beginCaptures": { 293 | "1": { 294 | "name": "keyword.control.dafny" 295 | } 296 | }, 297 | "end": "=", 298 | "patterns": [ 299 | { 300 | "include": "#generics" 301 | }, 302 | { 303 | "match": "[\\w'?]+", 304 | "name": "storage.type.dafny" 305 | } 306 | ], 307 | "name": "datatype.declaration" 308 | } 309 | ] 310 | }, 311 | "keywordsIfNotHandled": { 312 | "patterns": [ 313 | { 314 | "match": "\\b(datatype|codatatype|type|newtype)\\b", 315 | "name": "keyword.control.dafny" 316 | } 317 | ] 318 | }, 319 | "keywords": { 320 | "patterns": [ 321 | { 322 | "match": "\\b(abstract|allocated|as|assert|assume|break|by|calc|case|class|const|constructor|continue|downto|else|exists|expect|export|extends|false|for|forall|fresh|function|ghost|greatest|if|import|in|include|is|iterator|label|least|lemma|match|method|modify|module|nameonly|new|null|old|opaque|opened|predicate|print|refines|return|returns|reveal|static|then|to|trait|true|twostate|unchanged|var|while|witness|yield|yields)\\b", 323 | "name": "keyword.control.dafny" 324 | }, 325 | { 326 | "match": "\\b(function)\\b", 327 | "name": "entity.name.function" 328 | }, 329 | { 330 | "match": ";", 331 | "name": "punctuation.terminator.dafny" 332 | }, 333 | { 334 | "match": "\\b(requires|ensures|modifies|reads|invariant|decreases|reveals|provides)\\b", 335 | "name": "keyword.control.verify.dafny" 336 | }, 337 | { 338 | "match": "\\b(bool|char|real|multiset|map|imap|nat|int|ORDINAL|object|string|set|iset|seq|array|array[1-9]\\d*|bv0|bv[1-9]\\d*)\\b", 339 | "name": "keyword.type.dafny" 340 | }, 341 | { 342 | "match": "\\bthis\\b", 343 | "name": "storage.type.dafny" 344 | }, 345 | { 346 | "match": "\\|:", 347 | "name": "keyword.control.dafny" 348 | }, 349 | { 350 | "match": "(==|!=|<=|>=|<|>)", 351 | "name": "keyword.operator.comparison.dafny" 352 | }, 353 | { 354 | "match": "(:=)", 355 | "name": "keyword.operator.assignment.dafny" 356 | }, 357 | { 358 | "match": "(\\-|\\+|\\*|\\/|%)", 359 | "name": "keyword.operator.arithmetic.dafny" 360 | }, 361 | { 362 | "match": "(!|&&|\\|\\||<==>|==>|<==)", 363 | "name": "keyword.operator.logical.dafny" 364 | } 365 | ] 366 | }, 367 | "parameters": { 368 | "patterns": [ 369 | { 370 | "begin": "\\(", 371 | "end": "\\)", 372 | "name": "meta.parameters.parenthesized", 373 | "patterns": [ 374 | { 375 | "include": "#parameters" 376 | }, 377 | { 378 | "include": "#keywords" 379 | }, 380 | { 381 | "include": "#keywordsIfNotHandled" 382 | } 383 | ] 384 | }, 385 | { 386 | "include": "#formalTypeDeclaration" 387 | }, 388 | { 389 | "include": "#functioncalls" 390 | }, 391 | { 392 | "include": "#generics" 393 | }, 394 | { 395 | "include": "#comments" 396 | }, 397 | { 398 | "include": "#comments-inline" 399 | }, 400 | { 401 | "include": "#strings" 402 | } 403 | ] 404 | }, 405 | "generics": { 406 | "patterns": [ 407 | { 408 | "begin": "([a-zA-Z][\\w'?]*)<(?=[\\w'?]+)", 409 | "beginCaptures": { 410 | "1": { 411 | "name": "storage.type.dafny" 412 | } 413 | }, 414 | "end": ">", 415 | "name": "meta.type.generic.dafny", 416 | "patterns": [ 417 | { 418 | "include": "#generics" 419 | }, 420 | { 421 | "match": "[\\w'?]+", 422 | "name": "storage.type.dafny" 423 | } 424 | ] 425 | } 426 | ] 427 | } 428 | }, 429 | "scopeName": "text.dfy.dafny", 430 | "uuid": "f4eb6552-5503-47cf-9d18-6388d0981235" 431 | } 432 | -------------------------------------------------------------------------------- /docs/assets/smt.tmLanguage.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", 3 | "name": "SMT", 4 | "scopeName": "source.smt", 5 | "fileTypes": ["smt"], 6 | "patterns": [ 7 | { 8 | "include": "#command" 9 | }, 10 | { 11 | "include": "#reserved" 12 | }, 13 | { 14 | "include": "#keyword" 15 | }, 16 | { 17 | "include": "#sort" 18 | }, 19 | { 20 | "include": "#spec_constant" 21 | }, 22 | { 23 | "include": "#symbol" 24 | }, 25 | { 26 | "include": "#comment" 27 | } 28 | ], 29 | "repository": { 30 | "command": { 31 | "patterns": [ 32 | { 33 | "match": "\\b(apply|assert-not|assert-soft|check-sat-using|dbg-[a-z\\-]+|declare-map|declare-rel|declare-tactic|declare-var|display-dimacs|display|euf-project|eufi|eval|get-consequences|get-interpolant|get-objectives|get-proof-graph|get-user-tactics|help-tactic|help|include|labels|maximize|mbi|mbp|minimize|query|rule|simplify|define-const|model-add|model-del)\\b", 34 | "captures": { 35 | "1": { 36 | "name": "entity.name.tag.smt" 37 | } 38 | } 39 | }, 40 | { 41 | "match": "\\b(block-model-values|block-model|declare-codatatypes|declare-codatatype|declare-heap|declare-pool|get-abduct-next|get-abduct|get-difficulty|get-interpol-next|get-interpol|get-learned-literals|get-qe-disjunct|get-qe)\\b", 42 | "captures": { 43 | "1": { 44 | "name": "entity.name.tag.smt" 45 | } 46 | } 47 | }, 48 | { 49 | "match": "\\b(assume|constraint|check-synth-next|check-synth|declare-var|inv-constraint|set-feature|synth-fun|synth-inv)\\b", 50 | "captures": { 51 | "1": { 52 | "name": "entity.name.tag.smt" 53 | } 54 | } 55 | }, 56 | { 57 | "match": "\\b(assert|check-sat-assuming|check-sat|declare-const|declare-datatypes|declare-datatype|declare-fun|declare-sort|define-fun-rec|define-fun|define-funs-rec|define-sort|echo|exit|get-assertions|get-assignment|get-info|get-model|get-option|get-proof|get-unsat-assumptions|get-unsat-core|get-value|pop|push|reset-assertions|reset|set-info|set-logic|set-option)\\b", 58 | "captures": { 59 | "1": { 60 | "name": "keyword.other.smt" 61 | } 62 | } 63 | } 64 | ] 65 | }, 66 | "reserved": { 67 | "match": "\\b(_|!|as|exists|forall|let|match|par)\\b", 68 | "captures": { 69 | "1": { 70 | "name": "keyword.control.smt" 71 | } 72 | } 73 | }, 74 | "keyword": { 75 | "match": ":\\b([a-zA-Z~!@$%^&*_\\-+=<>.?/]|[a-zA-Z~!@$%^&*_\\-+=<>.?/][a-zA-Z0-9~!@$%^&*_\\-+=<>.?/]+)\\b", 76 | "captures": { 77 | "1": { 78 | "name": "keyword.control.smt" 79 | } 80 | } 81 | }, 82 | "sort": { 83 | "patterns": [ 84 | { 85 | "match": "\\b([A-Z][a-zA-Z~!@$%^&*_\\-+=<>.?/]|[A-Z][a-zA-Z0-9~!@$%^&*_\\-+=<>.?/]*)\\b", 86 | "captures": { 87 | "1": { 88 | "name": "support.type.smt" 89 | } 90 | } 91 | }, 92 | { 93 | "match": "\\|\\b([A-Z][a-zA-Z~!@$%^&*_\\-+=<>.?/]|[A-Z][a-zA-Z0-9~!@$%^&*_\\-+=<>.?/]*)\\b\\|", 94 | "captures": { 95 | "1": { 96 | "name": "support.type.smt" 97 | } 98 | } 99 | } 100 | ] 101 | }, 102 | "spec_constant": { 103 | "patterns": [ 104 | { 105 | "include": "#decimal" 106 | }, 107 | { 108 | "include": "#numeral" 109 | }, 110 | { 111 | "include": "#hexadecimal" 112 | }, 113 | { 114 | "include": "#binary" 115 | }, 116 | { 117 | "include": "#string" 118 | }, 119 | { 120 | "include": "#b_value" 121 | } 122 | ] 123 | }, 124 | "symbol": { 125 | "patterns": [ 126 | { 127 | "match": "\\b([a-zA-Z~!@$%^&*_\\-+=<>.?/]|[a-zA-Z~!@$%^&*_\\-+=<>.?/][a-zA-Z0-9~!@$%^&*_\\-+=<>.?/]+)\\b", 128 | "captures": { 129 | "1": { 130 | "name": "variable.other.smt" 131 | } 132 | } 133 | }, 134 | { 135 | "begin": "\\|", 136 | "end": "\\|", 137 | "contentName": "variable.other.smt" 138 | } 139 | ] 140 | }, 141 | "comment": { 142 | "match": ";.*", 143 | "name": "comment.smt" 144 | }, 145 | "decimal": { 146 | "match": "\\b(\\d+\\.\\d+)\\b", 147 | "captures": { 148 | "1": { 149 | "name": "constant.numeric.smt" 150 | } 151 | } 152 | }, 153 | "numeral": { 154 | "match": "\\b(\\d+)\\b", 155 | "captures": { 156 | "1": { 157 | "name": "constant.numeric.smt" 158 | } 159 | } 160 | }, 161 | "hexadecimal": { 162 | "match": "\\s(#x\\h+)\\b", 163 | "captures": { 164 | "1": { 165 | "name": "constant.numeric.smt" 166 | } 167 | } 168 | }, 169 | "binary": { 170 | "match": "\\s(#b[01]+)\\b", 171 | "captures": { 172 | "1": { 173 | "name": "constant.numeric.smt" 174 | } 175 | } 176 | }, 177 | "string": { 178 | "begin": "\"", 179 | "end": "\"", 180 | "name": "string.quoted.double.smt", 181 | "patterns": [ 182 | { 183 | "match": "\"\"", 184 | "name": "constant.character.escape.smt" 185 | } 186 | ] 187 | }, 188 | "b_value": { 189 | "match": "\\b(true|false)\\b", 190 | "captures": { 191 | "1": { 192 | "name": "constant.language.smt" 193 | } 194 | } 195 | } 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /docs/assignments/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: "list-check" 3 | index: false 4 | dir: 5 | collapsible: true 6 | expanded: true 7 | link: true 8 | --- 9 | 10 | # Assignments 11 | 12 | 13 | -------------------------------------------------------------------------------- /docs/assignments/assignment1.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 2 3 | shortTitle: "Assignment 1" 4 | icon: file-lines 5 | date: 2024-09-04 08:00:00 -5 6 | --- 7 | 8 | # Assignment 1: Coq 9 | 10 | This assignment has two purposes: 11 | 12 | 1. Learn how to use Coq to prove theorems. This is a skill you'll build on and use to verify programs in the rest of the class, so it's important you put enough practice in now. 13 | 2. Practice with functional programs. This is a warm up to imperative and concurrent programs as well as useful on its own, since functional programs are often used in the _specification_ of imperative programs. 14 | 15 | ## Submitting 16 | 17 | See [submitting assignments](./setup#submitting-assignments) on the assignment setup page. 18 | 19 | **The assignment is due Tuesday, Oct 1st at 11pm.** However, you are welcome and encouraged to submit early with partial progress to keep me informed of how the class is doing. If you have questions, just mention them briefly in the Canvas submission so I know to look. 20 | 21 | I would recommend aiming to finish parts 0 and 1 by Tuesday, Sep 17th to be on track. 22 | 23 | ## Part 0: setup 24 | 25 | The [assignment setup page](./setup) has instructions on getting the sys-verif-fa24-proofs repo and installing Coq. Follow those first. 26 | 27 | ::: warning 28 | 29 | You might run into unexpected difficulties with installing the software, so do it early and ask for help quickly if you get stuck. It isn't a goal of the class to teach you to install software, but it is necessary to make progress on anything else. 30 | 31 | ::: 32 | 33 | ## Part 1: Software Foundations exercises 34 | 35 | We'll use the free online textbook Software Foundations to learn the basics of using Coq to prove theorems. You'll read the three early chapters ([Basics](https://softwarefoundations.cis.upenn.edu/lf-current/Basics.html), [Induction](https://softwarefoundations.cis.upenn.edu/lf-current/Induction.html), and [Logic](https://softwarefoundations.cis.upenn.edu/lf-current/Logic.html)) and do (a selection of) the exercises in Coq. I also strongly encourage you to look at the [Polymorphism](https://softwarefoundations.cis.upenn.edu/lf-current/Poly.html) chapter, particularly if you haven't used generics. 36 | 37 | Software Foundations is written as a Coq file per chapter, with places for you to fill in your answers. You should do the exercises in the sys-verif-fa24-proofs repo by filling out the missing definitions and proofs in the three chapter files in `src/software_foundations/`, `Basics.v`, `Induction.v`, and `Logic.v`. You should also read the chapters, either online or within VS Code (the HTML version is a nice rendering of the Coq sources). 38 | 39 | Most of these chapters is assigned (including the exercises marked "optional" in the text), but there are a few optional parts: 40 | 41 | - Basics: 42 | - You can skip the "Fixpoints and Structural Recursion" section. 43 | - We aren't using the autograder described in "Testing your solutions". Just run `make` (in the VS Code terminal if you're using the Docker container). 44 | - Induction: 45 | - You can skip the last section, "Bin to Nat and Back to Bin" (but do the previous one, "Nat to Bin and Back to Nat"). 46 | - Logic: 47 | - You should read "Working with Decidable Properties" but don't need to do the proofs. We'll use an approach slightly different from what the chapter explains, and you won't need to do any related proofs. 48 | - You can skip the last section, "The Logic of Coq" (though that material is interesting if you care about theoretical issues) 49 | - Poly: 50 | - This is only assigned as optional reading, but don't let that stop you from doing the exercises. 51 | - The last section on Church Numerals is a theoretical topic that isn't relevant to this class. 52 | 53 | ::: important 54 | 55 | These chapters have a lot of small, easy exercises to get you practice. If you find anything difficult, come to office hours sooner rather than later to get it sorted out. If they're easy, note that proofs will become more difficult quickly after this. 56 | 57 | ::: 58 | 59 | ## Part 2: verifying functional programs 60 | 61 | Finish the exercises in `src/sys_verif/coq/assignment1_part2.v`. You can view a rendering of [Assignment 1 part 2](./assignment1_part2.md) as a reference. 62 | -------------------------------------------------------------------------------- /docs/assignments/assignment1_part2.md: -------------------------------------------------------------------------------- 1 | --- 2 | # Auto-generated from literate source. DO NOT EDIT. 3 | order: 2.5 4 | icon: file-lines 5 | tags: literate 6 | index: false 7 | --- 8 | 9 | # Assignment 1: Part 2 10 | 11 | ::: info 12 | 13 | This is a rendered version of the Coq assignment. You should do the assignment in the file `src/sys_verif/coq/assignment1_part2.v`. 14 | 15 | ::: 16 | 17 | Remember that part 1 of this assignment is to complete some of the chapters of Software Foundations (and you should do that first); see the [main assignment](./assignment1.md) for the full description. 18 | 19 | ```coq 20 | From sys_verif Require Import options. 21 | From stdpp Require Import fin_maps fin_sets gmap. 22 | 23 | Open Scope Z_scope. 24 | 25 | ``` 26 | 27 | ## Fixing type errors 28 | 29 | ```coq 30 | Module list_defs. 31 | ``` 32 | 33 | Here you'll get practice fixing type-checking errors. 34 | 35 | The first group of definitions uses the `concat` function, which takes a list of lists and produces a single list that concatenates all the elements. However, some of the calls below were written by a slightly confused developer. 36 | 37 | For each `Fail Definition` below, fix the definition so it passes the type checker and remove the `Fail`. You should try to preserve the "intent" of the original definition; don't just replace the whole thing with something trivial that works. 38 | 39 | ```coq 40 | Lemma good_concat : concat [[2; 3]; [1]; [4; 5]] = [2; 3; 1; 4; 5]. 41 | Proof. reflexivity. Qed. 42 | 43 | Fail Definition bad_concat_1 := concat [2; 3]. 44 | 45 | Fail Definition bad_concat_2 := concat [[2; 3; 4]; concat [[1]]; [[7; 10]]]. 46 | 47 | ``` 48 | 49 | Assume that `(x: list nat)` is correct in this definition (that is, don't change that part). 50 | 51 | ```coq 52 | Fail Definition bad_concat_3 (x: list nat) := 53 | concat [x ++ x; [1%nat; 3]; []; [3; 4]%nat]. 54 | 55 | ``` 56 | 57 | This next group is a bit tricker, because of how `l !! x` is overloaded, but we intend to always use it with lists, where `x` should be of type `nat`. 58 | 59 | ```coq 60 | Definition good_lookup_fact (x: list nat): Prop := 61 | x !! 3%nat = Some 1%nat. 62 | 63 | Fail Definition bad_lookup_1 (x: list bool) := 64 | x !! 3%nat = true. 65 | 66 | Fail Definition bad_lookup_2 (x: list bool) := 67 | x !! 3%nat = Some 7. 68 | 69 | Fail Definition bad_lookup_3 (x: list Z) := 70 | x !! 3 = Some 4. 71 | 72 | End list_defs. 73 | 74 | Module map_proofs. 75 | 76 | Lemma map_lookup_delete_insert (m: gmap Z nat) (k: Z) (v: nat) : 77 | delete k (<[ k := v ]> m) !! k = None. 78 | Proof. 79 | Search ((delete _ _) !! _). 80 | ``` 81 | 82 | :::: note Output 83 | 84 | ```txt title="coq output" 85 | lookup_delete_lt: 86 | ∀ {A : Type} (l : list A) (i j : nat), 87 | (j < i)%nat → delete i l !! j = l !! j 88 | lookup_delete_ge: 89 | ∀ {A : Type} (l : list A) (i j : nat), 90 | (i <= j)%nat → delete i l !! j = l !! S j 91 | lookup_delete: 92 | ∀ {K : Type} {M : Type → Type} {H : FMap M} {H0 : 93 | ∀ A : Type, 94 | Lookup K A (M A)} 95 | {H1 : ∀ A : Type, Empty (M A)} {H2 : ∀ A : Type, PartialAlter K A (M A)} 96 | {H3 : OMap M} {H4 : Merge M} {H5 : ∀ A : Type, MapFold K A (M A)} 97 | {EqDecision0 : EqDecision K}, 98 | FinMap K M → ∀ {A : Type} (m : M A) (i : K), delete i m !! i = None 99 | lookup_delete_ne: 100 | ∀ {K : Type} {M : Type → Type} {H : FMap M} {H0 : 101 | ∀ A : Type, 102 | Lookup K A (M A)} 103 | {H1 : ∀ A : Type, Empty (M A)} {H2 : ∀ A : Type, PartialAlter K A (M A)} 104 | {H3 : OMap M} {H4 : Merge M} {H5 : ∀ A : Type, MapFold K A (M A)} 105 | {EqDecision0 : EqDecision K}, 106 | FinMap K M 107 | → ∀ {A : Type} (m : M A) (i j : K), i ≠ j → delete i m !! j = m !! j 108 | lookup_delete_is_Some: 109 | ∀ {K : Type} {M : Type → Type} {H : FMap M} {H0 : 110 | ∀ A : Type, 111 | Lookup K A (M A)} 112 | {H1 : ∀ A : Type, Empty (M A)} {H2 : ∀ A : Type, PartialAlter K A (M A)} 113 | {H3 : OMap M} {H4 : Merge M} {H5 : ∀ A : Type, MapFold K A (M A)} 114 | {EqDecision0 : EqDecision K}, 115 | FinMap K M 116 | → ∀ {A : Type} (m : M A) (i j : K), 117 | is_Some (delete i m !! j) ↔ i ≠ j ∧ is_Some (m !! j) 118 | lookup_delete_None: 119 | ∀ {K : Type} {M : Type → Type} {H : FMap M} {H0 : 120 | ∀ A : Type, 121 | Lookup K A (M A)} 122 | {H1 : ∀ A : Type, Empty (M A)} {H2 : ∀ A : Type, PartialAlter K A (M A)} 123 | {H3 : OMap M} {H4 : Merge M} {H5 : ∀ A : Type, MapFold K A (M A)} 124 | {EqDecision0 : EqDecision K}, 125 | FinMap K M 126 | → ∀ {A : Type} (m : M A) (i j : K), 127 | delete i m !! j = None ↔ i = j ∨ m !! j = None 128 | lookup_delete_Some: 129 | ∀ {K : Type} {M : Type → Type} {H : FMap M} {H0 : 130 | ∀ A : Type, 131 | Lookup K A (M A)} 132 | {H1 : ∀ A : Type, Empty (M A)} {H2 : ∀ A : Type, PartialAlter K A (M A)} 133 | {H3 : OMap M} {H4 : Merge M} {H5 : ∀ A : Type, MapFold K A (M A)} 134 | {EqDecision0 : EqDecision K}, 135 | FinMap K M 136 | → ∀ {A : Type} (m : M A) (i j : K) (y : A), 137 | delete i m !! j = Some y ↔ i ≠ j ∧ m !! j = Some y 138 | ``` 139 | 140 | :::: 141 | 142 | ```coq 143 | rewrite lookup_delete //. 144 | Qed. 145 | 146 | ``` 147 | 148 | ### Exercise: finish the following proof and replace [Admitted] with [Qed]. 149 | 150 | ```coq 151 | Lemma map_delete_insert' (m: gmap Z nat) (k: Z) (v: nat) : 152 | delete k (<[ k := v ]> m) = delete k m. 153 | Proof. 154 | apply map_eq. 155 | ``` 156 | 157 | :::: info Goal 158 | 159 | ```txt title="goal 1" 160 | m : gmap Z nat 161 | k : Z 162 | v : nat 163 | ============================ 164 | ∀ i : Z, delete k (<[k:=v]> m) !! i = delete k m !! i 165 | ``` 166 | 167 | :::: 168 | 169 | ```coq 170 | intros k'. 171 | destruct (decide (k = k')). 172 | - subst. 173 | rewrite !lookup_delete //. 174 | - (* look for other lookup delete lemmas *) 175 | rewrite -> !lookup_delete_ne by auto. 176 | 177 | Admitted. 178 | End map_proofs. 179 | 180 | 181 | ``` 182 | 183 | ## Using the set solver automation 184 | 185 | ```coq 186 | Module set_proofs. 187 | ``` 188 | 189 | ### Exercise 190 | 191 | `set_solver` on its own fails on the proof below. For this exercise, finish the proof by `assert`ing the right fact, proving it, then calling `set_solver`. This is good practice for thinking through why the property holds and what the automation is missing. 192 | 193 | (In the future, we won't restrict what tactics you can use to prove theorems, but early on it's good for practice.) 194 | 195 | ```coq 196 | Lemma set_property3 (s1 s2: gset Z) : 197 | (∀ x, x ∈ s1 → 3 < x) → 198 | (s1 ∪ s2) ∖ {[2]} = s1 ∪ (s2 ∖ {[2]}). 199 | Proof. 200 | Admitted. 201 | ``` 202 | 203 | An alternate proof is to use `set_solver by lia`, a feature of the set solver that extends the automation with the ability to call `lia` when needed. This extra power is enough to do the proof above. 204 | 205 | ```coq 206 | Lemma set_property3_alt_proof (s1 s2: gset Z) : 207 | (∀ x, x ∈ s1 → 3 < x) → 208 | (s1 ∪ s2) ∖ {[2]} = s1 ∪ (s2 ∖ {[2]}). 209 | Proof. 210 | set_solver by lia. 211 | Qed. 212 | 213 | End set_proofs. 214 | 215 | 216 | ``` 217 | 218 | ## Verification of a functional interval library 219 | 220 | In this last sub-section, you'll prove the correctness of some operations on intervals. 221 | 222 | ```coq 223 | Module interval_verification. 224 | 225 | Record interval := 226 | mkInterval { low: Z; high: Z }. 227 | 228 | ``` 229 | 230 | All of the interval specifications will be proven in terms of `in_interval` below, which says when `x: Z` is an element of an interval. 231 | 232 | You should think of an interval as abstractly representing a set of integers, and this definition defines that set. In the future, the gap between what the code and the abstraction will be larger (e.g., the code won't have unbounded integers at all), so it'll be easier to see what the difference is. On the other hand, because the code and the spec are closely related in this example, the specs and proofs are quite short. 233 | 234 | ```coq 235 | Instance in_interval : ElemOf Z interval := 236 | (* this gets printed as [low i ≤ x ≤ high i], which is just a notation *) 237 | λ x i, low i <= x ∧ x <= high i. 238 | 239 | ``` 240 | 241 | Making the `in_interval` definition an `Instance` of `ElemOf` extends the `∈` notation to have a meaning for our intervals, "overloading" its more common meaning as element-of for sets. 242 | 243 | This is what it looks like to use `in_interval` in a theorem. Notice that we unfold `elem_of`, which is what the `∈` notation is defined as, and then we can unfold `in_interval`. 244 | 245 | This unfolding is required for `lia` to work, since otherwise it doesn't understand that `x ∈ i` is a useful arithmetic fact. 246 | 247 | ```coq 248 | Lemma in_interval_fact x (i: interval) : 249 | x ∈ i → low i <= x. 250 | Proof. 251 | rewrite /elem_of. 252 | ``` 253 | 254 | :::: info Goal 255 | 256 | ```txt title="goal 1" 257 | x : Z 258 | i : interval 259 | ============================ 260 | in_interval x i → low i ≤ x 261 | ``` 262 | 263 | :::: 264 | 265 | ```coq 266 | rewrite /in_interval. 267 | lia. 268 | Qed. 269 | 270 | ``` 271 | 272 | First, we give you definitions of `union` and `intersect` and their specifications. Prove the implementations meet these specifications. 273 | 274 | ```coq 275 | Definition union (i1 i2: interval): interval := 276 | {| low := Z.min (low i1) (low i2); 277 | high := Z.max (high i1) (high i2); 278 | |}. 279 | 280 | Definition intersect (i1 i2: interval): interval := 281 | {| low := Z.max (low i1) (low i2); 282 | high := Z.min (high i1) (high i2); 283 | |}. 284 | 285 | Lemma union_spec x i1 i2 : 286 | x ∈ i1 ∨ x ∈ i2 → x ∈ union i1 i2. 287 | Proof. 288 | rewrite /union /elem_of /in_interval /=. 289 | intros Hin. 290 | destruct Hin as [Hin1 | Hin2]. 291 | - lia. 292 | - lia. 293 | Qed. 294 | 295 | Lemma intersect_spec x i1 i2 : 296 | x ∈ intersect i1 i2 → x ∈ i1 ∧ x ∈ i2. 297 | Proof. 298 | rewrite /intersect /elem_of /in_interval /=. 299 | intros Hin. 300 | split. 301 | - lia. 302 | - lia. 303 | Qed. 304 | 305 | ``` 306 | 307 | Aside: we proved specifications only in one direction; do you see why the other direction isn't true? 308 | 309 | ```coq 310 | (* Next, implement `is_empty` and prove the specification below for it. *) 311 | Definition is_empty (i: interval): Prop := 312 | True. 313 | 314 | (* [x ∉ i] is just notation for [~ (x ∈ i)] (and this is notation for [x ∈ i → 315 | False]) *) 316 | Lemma is_empty_spec i : 317 | is_empty i ↔ (∀ x, x ∉ i). 318 | Proof. 319 | Admitted. 320 | 321 | ``` 322 | 323 | `contains` is supposed to be true when `i1` is completely inside `i2`. 324 | 325 | ```coq 326 | Definition contains i1 i2 := 327 | low i2 <= low i1 ∧ high i1 <= high i2. 328 | 329 | ``` 330 | 331 | The specification below is false. Add the right precondition to make it true. 332 | 333 | ```coq 334 | Lemma contains_spec i1 i2 : 335 | contains i1 i2 ↔ (∀ x, x ∈ i1 → x ∈ i2). 336 | Proof. 337 | Admitted. 338 | 339 | ``` 340 | 341 | As a sanity check of your `contains_spec` precondition, we've used that theorem to prove a specific `contains` fact. This proof won't go through if, for example, you add the precondition `False`, which we consider to be an incorrect solution. You should not need to change the proof below. 342 | 343 | ```coq 344 | Lemma contains_spec_check : 345 | contains (mkInterval 2 4) (mkInterval 2 7). 346 | Proof. 347 | apply contains_spec; rewrite /elem_of /in_interval; simpl; lia. 348 | Qed. 349 | 350 | ``` 351 | 352 | ```coq 353 | End interval_verification. 354 | ``` 355 | -------------------------------------------------------------------------------- /docs/assignments/assignment2.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 3 3 | shortTitle: Assignment 2 4 | icon: pen-fancy 5 | date: 2024-10-04 08:00:00 -5 6 | --- 7 | 8 | # Assignment 2: Separation logic theory 9 | 10 | ::: info 11 | 12 | This is a theory assignment: you'll submit written responses rather than doing proofs in a computer. 13 | 14 | ::: 15 | 16 | In this assignment, you'll answer some questions about Hoare logic and separation logic on paper rather than submitting mechanized proofs. The goal is to build and exercise your mental model for the specifications, proof rules, and program proofs, without having to work out all the details or translate your argument to something Coq understands. The two downsides are that you have to do all the calculations yourself, and there's no computer patiently checking your work and confirming when you're done; you'll have to think about it and wait for (much slower) feedback from me. 17 | 18 | 19 | 20 | **This assignment is due Tuesday, Oct 22, 2024 at 11pm.** Submit solutions to [Canvas](https://canvas.wisc.edu/courses/425519/assignments/2471003). You can submit either handwritten (digital or paper) answers, or typeset your solution with LaTeX using the assignment 2 template . 21 | 22 | ## Exercise 1 23 | 24 | > Rule of consequence (5 points) 25 | 26 | [Prove the rule of consequence](../notes/hoare.md#hoare-rules) for Hoare logic, from the [soundness definition](../notes/hoare.md#soundness) for a Hoare triple. 27 | 28 | ## Exercise 2 29 | 30 | > Understanding separation logic propositions (10 points) 31 | 32 | For each separation logic proposition below, describe precisely the set of heaps where it is true. If you think the answer is non-trivial or are unsure of your answer, briefly explain so I can see your thought process. Assume a linear separation logic - remember that $\True(h)$ holds for any heap, even in a linear logic. 33 | 34 | Note that the overlapping conjunction $(P \land Q)(h)$ is defined to be true when $P(h)$ and $Q(h)$. While not commonly used, this is a perfectly valid separation logic proposition. 35 | 36 | If a variable is _free_ (that is, not bound by a $\forall$ or $\exists$ quantifier), assume it to be a constant throughout the problem (but each problem is independent). 37 | 38 | Here is a worked example: 39 | 40 | $\ell_1 \mapsto v \sep \ell_2 \mapsto v$. If $\ell_1 \neq \ell_2$, holds for only the two-element heap $\{\ell_1 \mapsto v; \ell_2 \mapsto v\}$; if $\ell_1 = \ell_2$, never holds. 41 | 42 | - (a) $\exists v.\, \ell \pointsto v$ 43 | - (b) $\exists \ell'.\, \ell' \pointsto v$ 44 | - (c) $\forall v.\, \ell \pointsto v$ 45 | - (d) $(\ell \pointsto v) \sep \True$ 46 | - (e) $(\ell \pointsto v) \sep (\True \land \emp)$ 47 | - (f) $\ell \pointsto v \land \ell \pointsto v$ 48 | - (g) $\ell \pointsto v \sep \ell \pointsto v$ 49 | - (h) $(\exists v.\, \ell_1 \pointsto v) \sep (\exists \ell.\, \ell \pointsto \num{3})$ 50 | - (i) $(\exists v.\, \ell \pointsto v) \land (\exists \ell'.\, \ell' \pointsto \num{3})$ 51 | - (j) $(\exists x. \lift{x > 2}) \sep (\exists x. \lift{x < 2})$ 52 | 53 | ## Exercise 3 54 | 55 | > Frame rule vs consequence (10 points) 56 | 57 | Compare the separation logic frame rule to the consequence/weaken rule. Explain why the consequence rule does not imply the frame rule. Explain why the frame rule does not imply the consequence rule. These explanations are not meant to be fully formal proofs (which is well beyond the scope of this class for this question), but should aim to be convincing explanations - in this case, convincing me that you've understood what these rules mean. 58 | 59 | ::: info Rule of consequence = weaken rule 60 | 61 | This assignment originally only used the term "weaken rule", but the notes say "rule of consequence". These are the same thing; sorry for the confusion. Note that this question _not_ referring to weakening propositions, $P \sep Q \entails P$. 62 | 63 | ::: 64 | 65 | ## Exercise 4 66 | 67 | > A bug in separation logic? (15 points) 68 | 69 | Your friend Ben Bitdiddle has read the section on [recursion and loops](../notes/sep-logic.md#recursion-loops) and thinks he has found a bug in separation logic. He claims to have proven the following triple: 70 | 71 | $$\hoare{\True}{(\rec{f}{x} f \, x) \, ()}{\fun{\_} \False}$$ 72 | 73 | "This proves False with no hypotheses, which makes separation logic unsound!" he exclaims, a little too excited at having broken what you spent so much time learning. 74 | 75 | What do you say to Ben? 76 | 77 | ## Exercise 5 78 | 79 | > Verifying linked list implementation (35 points, total) 80 | 81 | We will develop a linked list implementation in our "lecture notes" programming language (which I'll call `expr` here) and give it a specification using a _representation predicate_. 82 | 83 | This problem will follow the presentation of linked lists in Robbert Krebbers's program verification course. We will be following the notes from that class on [separation logic](https://gitlab.science.ru.nl/program-verification/course-2023-2024/-/blob/master/lectures/week10.md). 84 | 85 | ### Exercise 5a 86 | 87 | > Reading (0 points) 88 | 89 | Read the notes linked above. You should be able to skim the first part, since it's very similar to our own class notes. The new material relevant to this problem starts at [Linked data structures and representation predicates](https://gitlab.science.ru.nl/program-verification/course-2023-2024/-/blob/master/lectures/week10.md#linked-data-structures-and-representation-predicates). 90 | 91 | Be careful reading the notation: code in the language is written in Coq syntax, with type annotations to clarify what's going on, but these are not functional programs. The types are mostly straightforward to understand, except that `ref T` is used for a pointer to a `T` (ref stands for "reference"); a value of this type will always be a location, and when dereferenced will return a value of type `T`. The syntax `alloc`, `!x`, and `x ← v` is the same as our programming language. 92 | 93 | Our language did not have a way of doing pattern matching, which makes it awkward to implement linked lists. For the purpose of this exercise let's assume it has a way to represent the inductive type for a linked list holding values of type `A`: 94 | 95 | ```coq title="expr" 96 | llist A := 97 | | lnil 98 | | lcons (hd: A) (tl: ref (llist A)) 99 | ``` 100 | 101 | Notice that this is not like the Coq `list A` type in that the `tl` field is a _pointer_ to the rest of the list. 102 | 103 | To use this inductive, we need pattern matching. Assume a `match` construct in `expr` that behaves like Coq's `match`; here's an example, the `sum_list` from the notes linked above: 104 | 105 | ```coq title="expr" 106 | Fixpoint sum_list (x: ref nat) (l: ilist nat) := 107 | match l 108 | | lnil => () 109 | | lcons hd tl => 110 | x <- !x + hd 111 | let k := !tl in 112 | sum_list x k 113 | end 114 | ``` 115 | 116 | We use `Fixpoint` for clarity but this function can also be written using the recursive anonymous function $\rec{f}{x}{e}$ in the notes. 117 | 118 | The program verification notes linked above include several examples of proof outlines, especially those using pattern matching. 119 | 120 | To specify functions over linked lists, we will use the following _representation invariant_ for linked lists (this is a Coq definition for a separation logic proposition, except that `v` would just be a `val`; we give here its type in the expr language for clarity): 121 | 122 | ```coq 123 | Fixpoint list_rep (v: llist A) (xs: list A): hProp := 124 | match xs with 125 | | [] => v = lnil 126 | | hd :: xs' => ∃ tl v', 127 | (v = lcons hd tl) ∗ 128 | (tl ↦ v') ∗ 129 | list_rep v' xs' 130 | end. 131 | ``` 132 | 133 | This definition says that `v` is the value of a linked list that holds abstract values `xs` in the current heap. It relates a programming value `v`, which has references, to a purely mathematical `list`. Importantly, `list_rep` is a separation logic proposition `hProp`; it only makes sense to have a linked list in some heap, since the code representation involves pointers. However the mathematical part does _not_ involve pointers. 134 | 135 | This is the separation logic analog to the ADT specifications we saw earlier for functional programs. The model of a linked list will be a Coq `list`, as expected. We will be using [abstraction relations](./adt_invariants#abstraction-relations) rather than functions. If linked lists were implemented in a functional programming language, we would specify `sum_list` using an abstraction relation `list_absr l xs : Prop` that said `∀ l xs, list_absr l xs → sum_list l = sum xs`. Now, a similar specification is written as a triple `{list_rep l xs} sum_list l {λv. v = sum xs * list_rep l xs}`. Notice that we need to re-state `list_rep l xs` in the postcondition; otherwise, the specification allows `sum_list` to de-allocate `l`, or re-use its memory for something else. 136 | 137 | ::: info Side note: where does "xs" come from? 138 | 139 | The name `xs` is meant to evoke `x`s, the plural of `x`. It's a common variable name for a list of values (similarly you'll see `ys`) in functional languages like OCaml or Haskell. 140 | 141 | ::: 142 | 143 | This shorthand is also useful for a reference to a linked list, since that is how the tail is stored in an `llist A`: 144 | 145 | ```coq 146 | Definition list_ref_rep (v: ref (llist A)) (xs: list A): hProp := 147 | ∃ (p: loc) (l: llist A), (v = p) * (p ↦ l) * list_rep l xs. 148 | ``` 149 | 150 | There is nothing to turn in for this part. 151 | 152 | ### Exercise 5b 153 | 154 | > Specification for append (10 points) 155 | 156 | Consider the following function for appending two linked lists. It's your job to figure out exactly how it works (that is, how does it manage the memory of the two lists?). 157 | 158 | ```coq title="expr" 159 | Fixpoint app_list (l1: ref (llist A)) (l2: ref (llist A)) := 160 | match !l1 with 161 | | lnil => l1 <- !l2; free l2; () 162 | | lcons hd tl => app_list tl l2 163 | end. 164 | ``` 165 | 166 | Write a specification for `app_list l1 l2`. **You may assume an affine separation logic**, so the postcondition can drop any facts you don't think are relevant. 167 | 168 | ### Exercise 5c 169 | 170 | > Proof of append (15 points) 171 | 172 | Write a proof outline that shows `app_list l1 l2` meets your specification. You may assume an affine separation logic. 173 | 174 | Remember that if you think something is wrong here, you may need to re-visit your specification. I can check your specification and give feedback before you spend too much time proving it. 175 | 176 | ## Exercise 5d 177 | 178 | > Specification for tail (10 points) 179 | 180 | Let us now implement a function to get the tail of a list: 181 | 182 | ```coq title="expr" 183 | Definition tail (l: llist A) : llist A := 184 | match l with 185 | | lnil => () 186 | | lcons hd tl => !tl 187 | end. 188 | ``` 189 | 190 | Consider the following specification for `tail`: 191 | 192 | ```txt 193 | [ list_rep l (x :: xs) ] 194 | tail l 195 | [ v. list_rep l (x :: xs) * list_rep v xs ] 196 | ``` 197 | 198 | Is this specification true? If yes, prove this specification. If not, explain why, find another valid specification, and prove it. 199 | 200 | ## Bonus exercise (optional) 201 | 202 | > 5-15 bonus points (depending on importance of contribution) 203 | 204 | Find a typo in the lecture notes on Hoare logic or Separation Logic. Submit a GitHub pull request with a fix. 205 | -------------------------------------------------------------------------------- /docs/assignments/assignment3/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | dir: 3 | order: 4 4 | icon: file-lines 5 | text: Assignment 3 6 | shortTitle: Assignment 3 (start here) 7 | --- 8 | 9 | # Assignment 3: Verifying Go programs 10 | 11 | In this assignment you'll verify imperative programs written in Go. All your proofs should be carried out in the exercises repo (sys-verif-fa24-proofs). The various parts of the assignment are divided among files in `src/sys_verif/assignment3`; each part is independent. 12 | 13 | Submit your work by running `./etc/prepare-submit` and uploading `hw.tar.gz` to the Canvas assignment. 14 | 15 | Part 1 is a tutorial and part 2 is a warmup. Part 5 has an open-ended component (you'll write almost the entire proof yourself). Please take this into account when planning how to work on the assignment. 16 | 17 | The point values add up to 90, but the assignment will be weighted in your overall grade as specified in the syllabus. 18 | 19 | ## Part 1: Introduction to program proofs 20 | 21 | > 5 points 22 | 23 | See [Intro to program proofs](./intro.md) 24 | 25 | ## Part 2: Inferring specifications 26 | 27 | > 25 points 28 | 29 | See [Inferring specifications](./infer_specs.md). 30 | 31 | ## Part 3: Verify factorial 32 | 33 | > 10 points 34 | 35 | See [Factorial](./factorial_proof.md). 36 | 37 | ## Part 4: Linked lists 38 | 39 | > 15 points 40 | 41 | See [Linked lists](./linked_list_proof.md). 42 | 43 | ## Part 5: Queue using two stacks 44 | 45 | > 35 points 46 | 47 | See [Queue](./queue_proof.md). 48 | -------------------------------------------------------------------------------- /docs/assignments/assignment3/factorial_proof.md: -------------------------------------------------------------------------------- 1 | --- 2 | # Auto-generated from literate source. DO NOT EDIT. 3 | category: assignment 4 | tags: literate 5 | order: 3 6 | shortTitle: "Assignment 3: factorial" 7 | --- 8 | 9 | # Assignment 3: verify factorial function 10 | 11 | The code in [go/functional/functional.go](https://github.com/tchajed/sys-verif-fa24-proofs/blob/main/go/functional/functional.go) implements `func Factorial(n uint6) uint64`. 12 | 13 | Here, you'll prove this function correct, proving that the imperative, loop-based implementation Go is equivalent to a recursive, functional implementation in Gallina. 14 | 15 | Before starting, **you should read the [fibonacci demo](/notes/program-proofs/fibonacci_proof.md)**, which has a very similar structure to this proof. 16 | 17 | ```coq 18 | From sys_verif.program_proof Require Import prelude empty_ffi. 19 | From Goose.sys_verif_code Require Import functional. 20 | 21 | Section proof. 22 | Context `{hG: !heapGS Σ}. 23 | 24 | ``` 25 | 26 | A functional implementation of the factorial function is already provided as `fact` by the Coq standard library, which is what we'll use. It looks as you'd expect: 27 | 28 | ```coq 29 | Fixpoint fact (n: nat): nat := 30 | match n with 31 | | 0%nat => 1%nat 32 | | S n' => (S n' * fact n')%nat 33 | end. 34 | ``` 35 | 36 | ```coq 37 | Lemma fact_monotonic (n m: nat) : 38 | (n ≤ m)%nat → 39 | (fact n ≤ fact m)%nat. 40 | Proof. 41 | (* this is already in the standard library *) 42 | apply Coq.Arith.Factorial.fact_le. 43 | Qed. 44 | 45 | Lemma fact_S (n: nat) : 46 | fact (S n) = ((S n) * fact n)%nat. 47 | Proof. 48 | (* this follows from the definition itself *) 49 | reflexivity. 50 | Qed. 51 | 52 | (* You will need to use `rewrite word.unsigned_mul_nowrap` yourself in this 53 | proof, which the `word` tactic will not (currently) do automatically. *) 54 | 55 | Lemma wp_Factorial (n: w64) : 56 | {{{ ⌜Z.of_nat (fact (uint.nat n)) < 2^64⌝ }}} 57 | Factorial #n 58 | {{{ (c: w64), RET #c; ⌜uint.nat c = fact (uint.nat n)⌝ }}}. 59 | Proof. 60 | Admitted. 61 | 62 | End proof. 63 | ``` 64 | -------------------------------------------------------------------------------- /docs/assignments/assignment3/infer_specs.md: -------------------------------------------------------------------------------- 1 | --- 2 | # Auto-generated from literate source. DO NOT EDIT. 3 | category: assignment 4 | tags: literate 5 | order: 2 6 | shortTitle: "Assignment 3: infer specs" 7 | --- 8 | 9 | # Assignment 3: Inferring specifications 10 | 11 | For each `Example` function in [go/heap/exercises.go](https://github.com/tchajed/sys-verif-fa24-proofs/blob/main/go/heap/exercises.go), come up with a general specification of the snippet's behavior, state it in Coq, and prove it correct. A specification for `ExampleA` is provided below as an example. 12 | 13 | ```coq 14 | From sys_verif Require Import prelude empty_ffi. 15 | From Goose.sys_verif_code Require Import heap. 16 | 17 | Section goose. 18 | Context `{!heapGS Σ}. 19 | 20 | (* worked example of a general specification *) 21 | Lemma wp_ExampleA (x_l y_l: loc) (z: w64) (x: bool) (y: w64) q : 22 | {{{ "x" :: x_l ↦[boolT]{q} #x ∗ "y" :: y_l ↦[uint64T] #y }}} 23 | ExampleA #x_l #y_l #z 24 | {{{ RET #(); x_l ↦[boolT]{q} #x ∗ 25 | y_l ↦[uint64T] (if x then #z else #0) }}}. 26 | Proof. 27 | wp_start as "H". iNamed "H". 28 | Admitted. 29 | 30 | Lemma wp_ExampleB : 31 | {{{ True }}} 32 | ExampleB #() 33 | {{{ RET #(); True }}}. 34 | Proof. 35 | (* FILL IN HERE *) 36 | Admitted. 37 | 38 | Lemma wp_ExampleC : 39 | {{{ True }}} 40 | ExampleC #() 41 | {{{ RET #(); True }}}. 42 | Proof. 43 | (* FILL IN HERE *) 44 | Admitted. 45 | 46 | ``` 47 | 48 | **Warning**: this one is a bit harder than the rest in both specification and proof. 49 | 50 | ```coq 51 | Lemma wp_ExampleD : 52 | {{{ True }}} 53 | ExampleD #() 54 | {{{ RET #(); True }}}. 55 | Proof. 56 | (* FILL IN HERE *) 57 | Admitted. 58 | 59 | (* you will not need to directly use this, it will be used automatically *) 60 | Lemma slice_nil_ty ty : val_ty slice.nil (slice.T ty). 61 | Proof. 62 | change slice.nil with (slice_val Slice.nil). 63 | apply slice_val_ty. 64 | Qed. 65 | 66 | Hint Resolve slice_nil_ty : core. 67 | 68 | Lemma wp_ExampleE : 69 | {{{ True }}} 70 | ExampleE #() 71 | {{{ RET #(); True }}}. 72 | Proof. 73 | (* FILL IN HERE *) 74 | Admitted. 75 | 76 | ``` 77 | 78 | **Hint:** you can check `exercises_test.go` file to figure out what this function does. 79 | 80 | ```coq 81 | Lemma wp_ExampleG : 82 | {{{ True }}} 83 | ExampleG #() 84 | {{{ RET #(); True }}}. 85 | Proof. 86 | (* FILL IN HERE *) 87 | Admitted. 88 | End goose. 89 | ``` 90 | -------------------------------------------------------------------------------- /docs/assignments/assignment3/intro.md: -------------------------------------------------------------------------------- 1 | --- 2 | # Auto-generated from literate source. DO NOT EDIT. 3 | tags: literate 4 | order: 1 5 | shortTitle: "Assignment 3: intro" 6 | --- 7 | 8 | # Introduction to program proofs 9 | 10 | These exercises should help you get started with doing program proofs with Goose. They're split into two parts: using the Iris Proof Mode for proving $P ⊢ Q$ with separation logic assertions (without any proofs about programs), and proving specifications using the weakest precondition tactics and lemmas. 11 | 12 | ```coq 13 | From sys_verif.program_proof Require Import prelude empty_ffi. 14 | From Goose.sys_verif_code Require Import functional heap. 15 | 16 | Section proof. 17 | Context `{!heapGS Σ}. 18 | 19 | ``` 20 | 21 | ## IPM exercises 22 | 23 | Here is a detailed proof of a simple separation logic entailment in the IPM, one small step at a time. 24 | 25 | ```coq 26 | Lemma example_sep_comm_v1 (P Q: iProp Σ) : 27 | P ∗ Q -∗ Q ∗ P. 28 | Proof. 29 | iIntros "H". iDestruct "H" as "[HP HQ]". 30 | iSplitL "HQ". 31 | - iExact "HQ". 32 | - iExact "HP". 33 | Qed. 34 | 35 | ``` 36 | 37 | Now let's see the same proof more concisely. 38 | 39 | ```coq 40 | Lemma example_sep_comm_v2 (P Q: iProp Σ) : 41 | P ∗ Q -∗ Q ∗ P. 42 | Proof. 43 | 44 | ``` 45 | 46 | We can use a destruct pattern right away when doing an `iIntros`, without naming the intermediate result. 47 | 48 | ```coq 49 | iIntros "[HP HQ]". 50 | iSplitL "HQ". 51 | - iFrame. 52 | - iFrame. 53 | Qed. 54 | 55 | ``` 56 | 57 | :::: info Exercise: typing special symbols 58 | 59 | You should make sure you know how to type the special symbols in these specifications. 60 | 61 | Delete each specification in this file and type it out again. 62 | 63 | Refer to the [input guide](/notes/program-proofs/input.md) for guidance on how to type each symbol. 64 | 65 | One easy thing to miss is that `P ∗ Q` (separating conjunction) requires you to type \sep. It's not the same as `x * y` (multiplication of integers). 66 | 67 | :::: 68 | 69 | ```coq 70 | Lemma example_sep_comm_v3 (P Q: iProp Σ) : 71 | P ∗ Q -∗ Q ∗ P. 72 | Proof. 73 | iIntros "[HP HQ]". 74 | 75 | ``` 76 | 77 | Using `iFrame` here is more than just more concise: `iFrame` automatically decides the split so we don't have to. 78 | 79 | ```coq 80 | iFrame. 81 | Qed. 82 | 83 | ``` 84 | 85 | **Exercise:** complete the proof 86 | 87 | ```coq 88 | Lemma ex_rearrange_1 (P Q R: iProp Σ) : 89 | R -∗ Q ∗ P -∗ P ∗ Q ∗ R. 90 | Proof. 91 | Admitted. 92 | 93 | ``` 94 | 95 | In this example we use a Coq-level implication. In this case, it comes from a premise in the lemma, but it will often be a previously proven lemma; using such a lemma looks the same as this use of a hypothesis in the lemma. 96 | 97 | ```coq 98 | Lemma example_pure_impl_v1 (P Q R: iProp Σ) : 99 | (P -∗ Q) → 100 | P ∗ R -∗ Q ∗ R. 101 | Proof. 102 | iIntros (HPQ) "[HP HR]". 103 | iDestruct (HPQ with "[$HP]") as "HQ". 104 | iFrame. 105 | Qed. 106 | 107 | ``` 108 | 109 | Make sure you understand the following alternatives to the `iDestruct` above (try them out and see what happens); 110 | 111 | - `iDestruct (HPQ with "[]") as "HQ"` 112 | - `iDestruct (HPQ with "[HP]") as "HQ"` 113 | - `iDestruct (HPQ with "[$HP]") as "HQ"` 114 | 115 | There is also `iDestruct (HPQ with "HP")`. This is similar to `iDestruct (HPQ with "[$HP]")` but requires `"HP"` to _exactly_ match the premise of `HPQ`. It is primarily useful when the premise is very simple, but it is reasonable for you to forget about it and always use the framing version, rather than remembering one more variant of specialization patterns. 116 | 117 | The previous proof was in _forward_ style (working from the hypotheses). We can also do a backward proof. 118 | 119 | ```coq 120 | Lemma example_pure_impl_v2 (P Q R: iProp Σ) : 121 | (P -∗ Q) → 122 | P ∗ R -∗ Q ∗ R. 123 | Proof. 124 | iIntros (HPQ) "[HP HR]". 125 | 126 | ``` 127 | 128 | In this proof, the `R` part of the proof can be handled separately; we don't need `R` any more. 129 | 130 | ```coq 131 | iFrame. 132 | iApply HPQ. iFrame. 133 | Qed. 134 | 135 | ``` 136 | 137 | **Exercise:** complete the proof 138 | 139 | You'll need to find the right lemma to use here. 140 | 141 | ```coq 142 | Lemma ex_pure_impl s q (bs: list w8) : 143 | length bs = 3%nat → 144 | own_slice_small s byteT q bs -∗ 145 | ⌜Slice.sz s = 3%Z⌝ ∗ own_slice_small s byteT q bs. 146 | Proof. 147 | Admitted. 148 | 149 | ``` 150 | 151 | **Exercise:** complete the proof 152 | 153 | Read about [structs](../../notes/ownership.md#proofs-using-structs) (in particular `wp_ExamplePersonRef`) to see how to do this. 154 | 155 | ```coq 156 | Lemma ex_split_struct l s : 157 | l ↦[struct.t S1] (#(W64 3), (s, #())) -∗ 158 | l ↦[S1 :: "a"] #(W64 3) ∗ 159 | l ↦[S1 :: "b"] s. 160 | Proof. 161 | Admitted. 162 | 163 | ``` 164 | 165 | You will need `iModIntro` to "introduce" the `▷P` (later) and `|==> P` (update) modalities. The update modality in particular is often needed at the end of a program proof, before proving the postcondition. 166 | 167 | ```coq 168 | Lemma example_update_modality (P: iProp Σ) : 169 | P -∗ |==> P. 170 | Proof. 171 | iIntros "HP". 172 | iModIntro. 173 | ``` 174 | 175 | :::: info Goal diff 176 | 177 | ```txt title="goal diff" 178 | Σ : gFunctors 179 | heapGS0 : heapGS Σ 180 | P : iProp Σ 181 | ============================ 182 | "HP" : P 183 | --------------------------------------∗ 184 | |==> P // [!code --] 185 | P // [!code ++] 186 | ``` 187 | 188 | :::: 189 | 190 | ```coq 191 | iApply "HP". 192 | Qed. 193 | 194 | ``` 195 | 196 | These modalities both _weaken_ a proposition: `P ⊢ ▷P` and `P ⊢ |==> P`. Of course there will be situations where you can prove `▷P` but not `P` (otherwise there would be no point in having the modality), but we won't actually see those for later, and the update modality won't come up for a while. 197 | 198 | Nonetheless you will want to be able to switch from proving `▷P` to `P`, using the `P ⊢ ▷P` rule (do you see why that makes sense as a backward step?). This is done with `iModIntro`. 199 | 200 | **Exercise:** complete the proof 201 | 202 | ```coq 203 | Lemma ex_later_modality (P Q: iProp Σ) : 204 | P ∗ (P -∗ Q) -∗ ▷ Q. 205 | Proof. 206 | Admitted. 207 | 208 | ``` 209 | 210 | ## Program proofs 211 | 212 | Here is a worked example. It demonstrates a number of tactics: 213 | 214 | - `wp_bind` 215 | - `iApply` with a `wp_*` theorem 216 | - `wp_apply` 217 | - `wp_load` and `wp_store` 218 | 219 | ```coq 220 | Lemma wp_Swap (l1 l2: loc) (x y: w64) : 221 | {{{ l1 ↦[uint64T] #x ∗ l2 ↦[uint64T] #y }}} 222 | Swap #l1 #l2 223 | {{{ RET #(); l1 ↦[uint64T] #y ∗ l2 ↦[uint64T] #x }}}. 224 | Proof. 225 | wp_start as "[Hx Hy]". 226 | wp_pures. 227 | 228 | (* The next instruction to run is a load from [l2]. *) 229 | wp_bind (![_] #l2)%E. 230 | iApply (wp_LoadAt with "[Hy]"). 231 | { iFrame. } 232 | iModIntro. (* remove the ▷ ("later") modality from the goal - you can ignore 233 | this *) 234 | (* Notice that the load spec requires ownership over the address [l1] for the 235 | duration of the call, then returns it in the postcondition. It does this in 236 | the form of an implication, so we have to use [iIntros] to put the hypothesis 237 | back in the context. *) 238 | iIntros "Hy". 239 | 240 | (* [wp_apply] automates the process of finding where to apply the spec, so we 241 | don't have to use [wp_bind]. It also automatically removes the ▷ from the 242 | resulting goal. *) 243 | wp_apply (wp_LoadAt with "[$Hx]"). iIntros "Hx". 244 | 245 | (* Loading and storing variables is common enough that there's a tactic 246 | [wp_load] which automates the work of [wp_bind], finding the right hypothesis 247 | with a points-to fact (that is, something like [l2 ↦[uint64T] #y]), and also 248 | re-introducing the hypothesis after using the [wp_LoadAt] or [wp_StoreAt] 249 | lemma. *) 250 | 251 | wp_store. 252 | wp_store. 253 | 254 | iModIntro. 255 | iApply "HΦ". 256 | iFrame. 257 | Qed. 258 | 259 | ``` 260 | 261 | **Exercise:** complete the proof. 262 | 263 | Re-do above proof, but with the automation tactics. 264 | 265 | ```coq 266 | Lemma wp_Swap_ex (l1 l2: loc) (x y: w64) : 267 | {{{ l1 ↦[uint64T] #x ∗ l2 ↦[uint64T] #y }}} 268 | Swap #l1 #l2 269 | {{{ RET #(); l1 ↦[uint64T] #y ∗ l2 ↦[uint64T] #x }}}. 270 | Proof. 271 | Admitted. 272 | 273 | ``` 274 | 275 | **Exercise:** complete the proof. 276 | 277 | Compare this specification to the one we saw in the separation logic notes. 278 | 279 | Prove it using the IPM. You may need to find the specification for `Assert` using `Search` (or you can guess what it's called). 280 | 281 | ```coq 282 | Lemma wp_IgnoreOneLocF (x_l y_l: loc) : 283 | {{{ x_l ↦[uint64T] #(W64 0) }}} 284 | IgnoreOneLocF #x_l #y_l 285 | {{{ RET #(); x_l ↦[uint64T] #(W64 42) }}}. 286 | Proof. 287 | Admitted. 288 | 289 | Lemma wp_UseIgnoreOneLocOwnership : 290 | {{{ True }}} 291 | UseIgnoreOneLocOwnership #() 292 | {{{ RET #(); True }}}. 293 | Proof. 294 | Admitted. 295 | 296 | ``` 297 | 298 | **Exercise:** read this function and compare it to the specification. 299 | 300 | The `(x_l: loc)` in the postcondition should be read as "there exists (x_l: loc), ..." where the ... is the rest of the postcondition. Special syntax is used so that `x_l` can be used in the `RET` clause itself. 301 | 302 | ```coq 303 | Lemma example_stack_escape : 304 | {{{ True }}} 305 | StackEscape #() 306 | {{{ (x_l: loc), RET #x_l; x_l ↦[uint64T] #(W64 42) }}}. 307 | Proof. 308 | wp_start as "_". 309 | 310 | ``` 311 | 312 | `wp_alloc` is a helper for using the allocation specifications, much like `wp_load` and `wp_store`. 313 | 314 | ```coq 315 | wp_alloc x_l as "Hx". 316 | wp_pures. 317 | iModIntro. iApply "HΦ". iFrame. 318 | Qed. 319 | 320 | End proof. 321 | ``` 322 | -------------------------------------------------------------------------------- /docs/assignments/assignment3/linked_list_proof.md: -------------------------------------------------------------------------------- 1 | --- 2 | # Auto-generated from literate source. DO NOT EDIT. 3 | category: assignment 4 | tags: literate 5 | order: 4 6 | shortTitle: "Assignment 3: linked lists" 7 | --- 8 | 9 | # Assignment 3: Linked lists as lists 10 | 11 | This proof develops a specification for the linked-list implementation at [go/heap/linked_list.go](https://github.com/tchajed/sys-verif-fa24-proofs/blob/main/go/heap/linked_list.go). 12 | 13 | You should start by reading the Go code. 14 | 15 | The idea of this proof is similar to what you saw in Assignment 2's exercise 5, but with the code written in Go (and thus using nil pointers rather than an inductive data type) and with the proof written in Coq (so you have the Iris Proof Mode rather than writing a proof outline). 16 | 17 | ```coq 18 | From sys_verif.program_proof Require Import prelude. 19 | From Goose.sys_verif_code Require Import heap. 20 | 21 | 22 | Section proof. 23 | Context `{hG: heapGS Σ, !ffi_semantics _ _, !ext_types _}. 24 | 25 | (* We abbreviate "linked list" to "ll" in some of these definitions to keep 26 | specs and other theorem statements concise. *) 27 | 28 | ``` 29 | 30 | After reading the code, read the definition of `ll_rep` and understand how it relates a list pointer (which will be a `n *Node`) to a list of values `xs: list w64`. 31 | 32 | ```coq 33 | Fixpoint ll_rep (l: loc) (xs: list w64) : iProp Σ := 34 | match xs with 35 | | nil => "%Heq" :: ⌜l = null⌝ 36 | | cons x xs' => (∃ (next_l: loc), 37 | "elem" :: l ↦[Node :: "elem"] #x ∗ 38 | "next" :: l ↦[Node :: "next"] #next_l ∗ 39 | "Hnext_l" :: ll_rep next_l xs')%I 40 | end. 41 | 42 | ``` 43 | 44 | The proofs will work by analysis on `xs`, but the code checks if `l` is `nil` or not. We relate the two with the following two lemmas (note that the Gallina `null` is the model of the Go `nil` pointer). 45 | 46 | ```coq 47 | Definition ll_rep_null l : 48 | ll_rep l [] -∗ ⌜l = null⌝. 49 | Proof. 50 | simpl. auto. 51 | Qed. 52 | 53 | Definition ll_rep_non_null l x xs : 54 | ll_rep l (x::xs) -∗ ⌜l ≠ null⌝. 55 | Proof. 56 | simpl. iIntros "H". iNamed "H". 57 | iDestruct (struct_field_pointsto_not_null with "elem") as %Hnot_null. 58 | { reflexivity. } 59 | { simpl. lia. } 60 | auto. 61 | Qed. 62 | 63 | ``` 64 | 65 | Prove this specification. 66 | 67 | ```coq 68 | Lemma wp_NewList : 69 | {{{ True }}} 70 | NewList #() 71 | {{{ (l: loc), RET #l; ll_rep l [] }}}. 72 | Proof. 73 | Admitted. 74 | 75 | 76 | ``` 77 | 78 | Fill in a postcondition here and prove this specification. 79 | 80 | ```coq 81 | Lemma wp_Node__Insert (l: loc) (xs: list w64) (elem: w64) : 82 | {{{ ll_rep l xs }}} 83 | Node__Insert #l #elem 84 | {{{ (l': loc), RET #l'; 85 | False }}}. 86 | Proof. 87 | Admitted. 88 | 89 | ``` 90 | 91 | Prove this specification. 92 | 93 | ```coq 94 | Lemma wp_Node__Pop (l: loc) (xs: list w64) : 95 | {{{ ll_rep l xs }}} 96 | Node__Pop #l 97 | {{{ (x: w64) (l': loc) (ok: bool), RET (#x, #l', #ok); 98 | if ok then ∃ xs', ⌜xs = cons x xs'⌝ ∗ 99 | ll_rep l' xs' 100 | else ⌜xs = []⌝ 101 | }}}. 102 | Proof. 103 | Admitted. 104 | 105 | 106 | ``` 107 | 108 | Fill in this specification. (You should read the code to see what it does and how it manages the memory of the two lists.) 109 | 110 | A general structure is provided for the proof (which you are allowed to change if you find it helpful); fill in the rest of the proof. 111 | 112 | ```coq 113 | Lemma wp_Node__Append l1 xs1 l2 xs2 : 114 | {{{ ll_rep l1 xs1 ∗ ll_rep l2 xs2 }}} 115 | Node__Append #l1 #l2 116 | {{{ (l2': loc), RET #l2'; 117 | False }}}. 118 | Proof. 119 | generalize dependent xs2. 120 | generalize dependent l2. 121 | generalize dependent l1. 122 | induction xs1 as [|x1 xs1 IH_wp_Append]. 123 | - intros l1 l2 xs2. wp_start as "[Hl1 Hl2]". 124 | iDestruct (ll_rep_null with "Hl1") as %Hnull. 125 | admit. 126 | - intros l1 l2 xs2. wp_start as "[Hl1 Hl2]". 127 | (* Notice the hypothesis `IH_wp_Append`, which is available due to the use 128 | of `induction`. You'll need it to reason about the recursive call. *) 129 | iDestruct (ll_rep_non_null with "Hl1") as %Hnull. 130 | admit. 131 | Admitted. 132 | 133 | End proof. 134 | ``` 135 | -------------------------------------------------------------------------------- /docs/assignments/assignment3/queue_proof.md: -------------------------------------------------------------------------------- 1 | --- 2 | # Auto-generated from literate source. DO NOT EDIT. 3 | category: assignment 4 | tags: literate 5 | order: 5 6 | shortTitle: "Assignment 3: queue" 7 | --- 8 | 9 | # Assignment 3: queue using two stacks 10 | 11 | The code at [go/heap/queue.go](https://github.com/tchajed/sys-verif-fa24-proofs/blob/main/go/heap/queue.go) implements a queue using two stacks. Don't worry if you haven't seen this before (or don't remember it); part of the assignment is figuring out how the data structure works enough to verify it. 12 | 13 | To simplify the code and the proof, the stack has been factored out into its own data structure. You can see its specifications and proofs in [src/sys_verif/program_proof/heap_proof/stack_proof.v](https://github.com/tchajed/sys-verif-fa24-proofs/blob/main/src/sys_verif/program_proof/heap_proof/stack_proof.v). That file has already been imported here, so you can refer to its definitions and specifications. 14 | 15 | This part of the assignment intentionally provides you with almost nothing: you'll write a representation invariant, the specifications, and the proofs. **I suggest starting this part early**, since you will likely need to go back and forth between `queue_rep`, the specifications, the loop invariants, and the proofs; working on later parts of this sequence will cause you to discover bugs in earlier parts. 16 | 17 | ```coq 18 | From sys_verif.program_proof Require Import prelude empty_ffi. 19 | From sys_verif.program_proof Require Import heap_proof.stack_proof. 20 | From Goose.sys_verif_code Require Import heap. 21 | 22 | Section proof. 23 | Context `{hG: !heapGS Σ}. 24 | 25 | Definition queue_rep (v: val) (els: list w64): iProp Σ := 26 | False. 27 | Hint Rewrite @length_reverse : len. 28 | 29 | Lemma wp_NewQueue : False. 30 | Proof. 31 | Admitted. 32 | 33 | Lemma wp_Queue__Push : False. 34 | Proof. 35 | Admitted. 36 | 37 | Lemma wp_Queue__emptyBack : False. 38 | Proof. 39 | Admitted. 40 | 41 | Lemma wp_Queue__Pop : False. 42 | Proof. 43 | Admitted. 44 | 45 | End proof. 46 | ``` 47 | -------------------------------------------------------------------------------- /docs/assignments/project.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: -1 3 | icon: code 4 | --- 5 | 6 | # Final project 7 | 8 | ::: warning Replaced with Assignment 4 9 | 10 | If you haven't already talked to me about a project, you should do [Assignment 4](./sharded_hashmap.md) instead. 11 | 12 | ::: 13 | 14 | For the final project, you can pick something open-ended. 15 | 16 | The idea here is for you to explore something you're interested in and complete a more substantial proof. Whatever you work on you need to write a short, 1-2 page proposal explaining what you're planning to do, and/or schedule a meeting with me, so I can give feedback (mainly so I can confirm the scope is reasonable and that you have some intermediate goals). 17 | 18 | The project will include a (short) written report on what you did. If you don't finish the proofs, that's okay - it's still important that you communicate what you did and what challenges you ran into. The report should be about 2--5 pages explaining what problem you tackled and what progress you made. You will submit your code, but the report is what I will start with to make sense of what you did so spend some time making that clear. 19 | 20 | ## "Easy" projects 21 | 22 | These are projects that I believe are doable, except that I haven't done them myself. 23 | 24 | ### Verify Go's sort.Find 25 | 26 | We saw a simplified binary search implementation in the notes. Specify and verify the Go binary search implementation, [sort.Find](https://pkg.go.dev/sort#Find) from the standard library. 27 | 28 | ### Make Memoize more useful 29 | 30 | Memoization is mostly useful when memoizing a recursive function and caching its recursive subcalls. The interface of the example in the [Persistence lecture](/notes/persistently.md) does not allow this - there is a circularity where the function is required to create a `Memoize`, but we want to call the `Memoize` in recursive calls. 31 | 32 | Re-implement memoization to solve this problem. See these [lecture notes](https://www.cs.cornell.edu/courses/cs3110/2011sp/Lectures/lec22-memoization/memo.htm) (specifically the last section, "Automatic Memoization Using Higher Order Functions") for how this works. Implement a memoized fibonacci function and verify it using the new specification. 33 | 34 | ## "Medium" projects 35 | 36 | These are projects that you might not finish, and which require you to figure out the scope. 37 | 38 | ### Verify a range map data structure 39 | 40 | Verify a data structure that can efficiently map a (potentially large) range of keys to a single value. Example use cases include processing a firewall configuration, where the keys would be IP addresses and rules apply to whole IP address ranges, or for efficiently filtering based on `.gitignore` rules. 41 | 42 | You can simplify the implementation by only supporting integer keys. A more sophisticated implementation would support strings; Goose does not (currently) support string comparison with `s1 < s2`, so you would need to implement this yourself. 43 | 44 | This requires some data structure design. A starting point would be to maintain a list of sorted ranges that you search with binary search. 45 | 46 | ### Verify a buddy allocator 47 | 48 | Verify a memory allocator, using the [buddy allocator algorithm](https://www.geeksforgeeks.org/buddy-system-memory-allocation-technique/). The Go implementation should keep a large byte slice as the backing storage, and return a struct on allocation with both the data and any metadata required by the algorithm (this is a simplification since working with the pointers alone is difficult in Go). 49 | 50 | ### Verify the Cloudflare trie-hard data structure 51 | 52 | Port [trie-hard](https://github.com/cloudflare/trie-hard) to Go and verify it. Specialize that implementation to u64. 53 | 54 | This is a trie (an efficient prefix-search data structure, especially for a fixed set of search strings), but with the twist of encoding the pointers into the bits of an integer for space efficiency (without this Cloudflare found it wasn't faster than a Rust `HashMap`). 55 | 56 | ## Open-ended projects 57 | 58 | These projects are more open ended. 59 | 60 | ### Make Goose easier to use 61 | 62 | Did you find something difficult while doing the previous assignments? It's an open project option to fix a pain point you experienced. This could involve primarily writing documentation, but it would need to be substantial and involve code examples. 63 | 64 | This is open-ended but need not require any challenging proofs. 65 | 66 | ### Verify the Go sync.Map 67 | 68 | The Go standard library has [sync.Map](https://pkg.go.dev/sync#Map), a concurrent hash map. 69 | 70 | Adapt the [implementation](https://cs.opensource.google/go/go/+/refs/tags/go1.23.0:src/sync/map.go;l=38) to develop a version that works with Goose (I can help you with this part) and verify it. You don't need to deal with the `any` type; feel free to specialize to a map from integers to integers. Focus on a few key operations, such as Load, Store, and CompareAndSwap; they'll capture the essential difficulty. 71 | 72 | ### Verify a UTF-8 library 73 | 74 | Verify a library for UTF-8; a minimum implementation would be [Valid](https://pkg.go.dev/unicode/utf8@go1.23.1#Valid) (which checks if a byte sequence has only valid UTF-8-encoded runes) and [DecodeRune](https://pkg.go.dev/unicode/utf8@go1.23.1#DecodeRune) (which find the end of the first rune in a byte sequence). 75 | 76 | Many programs rely on a such a library for manipulating text encoded as UTF-8. This code is a bit tricky to write, and needs to be high performance. 77 | 78 | A principle task for you in this project is to understand and encode the UTF-8 spec using pure Coq functions; this would be of independent interest and you'll learn something valuable. 79 | 80 | Note that implementing Unicode on top of UTF-8 is a massive task. I haven't looked into what this would entail and if there's a useful and small-enough starting point, but you're welcome to do that study and report back. 81 | -------------------------------------------------------------------------------- /docs/assignments/setup.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: gears 3 | order: 1 4 | --- 5 | 6 | # Setup 7 | 8 | ## Assignment repo 9 | 10 | The programming assignments will be distributed in [sys-verif-fa24-proofs](https://github.com/tchajed/sys-verif-fa24-proofs). You'll need it as a git repository so you can get updates with `git pull`, since this is how assignments and demos in lecture will be released. (If you have trouble getting updates, please let me know.) 11 | 12 | ::: danger Clone, don't fork 13 | 14 | **Please do not fork the repo**, since this will make your submission public. GitHub does not support private forks of public repositories. 15 | 16 | Instead, you can either clone and work locally, or follow the below instructions to work in a private repo that is not a GitHub fork (I do recommend doing that so you have a backup). 17 | 18 | ::: 19 | 20 | ::: details Creating a private GitHub repo for your work 21 | 22 | Create a new private repo called sys-verif-fa24-proofs (without any content). (As a student, you can get unlimited private repos with the [student pack](https://education.github.com/pack/join).) Follow these instructions to set it up (fill in your username in the first step): 23 | 24 | ```bash 25 | GH_USER= 26 | git clone https://github.com/tchajed/sys-verif-fa24-proofs 27 | cd sys-verif-fa24-proofs 28 | git remote rename origin upstream 29 | git remote add origin git@github.com:$GH_USER/sys-verif-fa24-proofs.git 30 | git push --set-upstream origin main 31 | git submodule update --init --recursive 32 | ``` 33 | 34 | You now have a copy of the repo, with the `main` branch tracking your private repo (using a remote called origin, as is typical in git), and with a second remote `upstream` pointing to the class repo. To get updates: run `git fetch upstream` to get new commits from the `tchajed/sys-verif-fa24-proofs` repo, then `git merge upstream/main` to pull in new changes into your own repo. 35 | 36 | You don't actually have to call your repo sys-verif-fa24-proofs, but that's how I'll refer to it. If you accidentally forked the course repo, don't panic; you can just delete the fork and re-create it. 37 | 38 | ::: 39 | 40 | ## Installing Coq 41 | 42 | The setup I recommend is to use Docker, VS Code, and a container I created for this class. 43 | 44 | ::: details Option 1: VS Code + Docker dev container 45 | 46 | This setup should work well on macOS and Linux; it should also be workable in Windows with WSL, but I don't have much experience with that. 47 | 48 | Install [Docker](https://www.docker.com/get-started/) 49 | 50 | Install [VS Code](https://code.visualstudio.com/) 51 | 52 | Install the [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers). 53 | 54 | - You can install it directly in VS Code through [this link](vscode:extension/ms-vscode-remote.remote-containers). 55 | - Alternate option 2: you can also install the extension from the Extensions sidebar item. 56 | - Alternate option 3: at the command line you can run `code --install-extension ms-vscode-remote.remote-containers`. 57 | 58 | The most important VS Code feature to learn is the Command Palette, accessed from View > Command Palette. The shortcut is worth learning (ctrl-shift-p, cmd-shift-p on macOS). The command palette gives search access to most editor functionality and shows keyboard shortcuts if you want to learn them. 59 | 60 | Once you have the dev container extension, use the "Dev Containers: Reopen in Container" command on the sys-verif-fa24-proofs repo. This will use Coq from the container while still running VS Code natively. You should now use the built-in VS Code terminal to run `make` to ensure your code compiles. 61 | 62 | ::: 63 | 64 | If you feel very comfortable setting up your own tools, you can instead install Coq on your own. 65 | 66 | ::: details Option 2: install Coq on your own 67 | 68 | Install Coq 8.20.0 for compatibility (Coq 8.19 is very likely to work if you can't get the latest version). 69 | 70 | You will need an IDE for Coq: 71 | 72 | - I'd recommend VS Code with the VSCoq extension. 73 | - If you use Emacs, then [Proof General](https://proofgeneral.github.io/) is excellent (this is what I personally use, with Doom Emacs and vim keybindings). 74 | - If you use Vim or Neovim, then [Coqtail](https://github.com/whonore/Coqtail) is also decent. 75 | 76 | Once you have Coq installed, run `make` to make sure everything is working. 77 | 78 | If you don't use VS Code, you'll need to follow the [Iris editor setup instructions](https://gitlab.mpi-sws.org/iris/iris/-/blob/master/docs/editor.md?ref_type=heads) to be able to input Unicode characters easily. For VS code that setup is already provided by the assignment repo. 79 | 80 | ::: 81 | 82 | ## Submitting assignments 83 | 84 | We'll use the course [Canvas page](https://canvas.wisc.edu/courses/425519) for submitting assignments (and not much else). 85 | 86 | You'll do all the programming in the sys-verif-fa24-proofs repo. To submit your code, run the script `./etc/prepare-submit` to generate `hw.tar.gz`. Then, go to the assignment's page on Canvas and submit that file. I'm having you submit all the code in the repo (not just for the relevant assignment) to simplify the setup for you. 87 | 88 | For all assignments, you're **strongly encouraged** to submit early with partial progress, once a week. There aren't many assignments, but you should still be doing some work every week, and this will give me an idea of how far along you are in between due dates. If you want any specific feedback, please add a comment on Canvas so I take a look quickly and respond. 89 | -------------------------------------------------------------------------------- /docs/calendar.snippet.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | | | Day | Lecture | 4 | | --: | --- | --- | 5 | | 1 | Wed Sep 4 | [Introduction](./notes/lec1.md) | 6 | | | | **Part 1: Functional programs** | 7 | | 2 | Mon Sep 9 | [Coq introduction](./notes/coq-intro.md) | 8 | | 3 | Wed Sep 11 | [Induction](./notes/induction.md) | 9 | | 4 | Mon Sep 16 | [ADT specification](./notes/adt_specs.md) | 10 | | 5 | Wed Sep 18 | [ADTs with invariants](./notes/adt_invariants.md) | 11 | | | | **Part 2: Imperative programs** | 12 | | 6 | Mon Sep 23 | [Hoare logic (part 1)](./notes/hoare.md) | 13 | | 7 | Wed Sep 25 | [Hoare logic (part 2)](./notes/hoare.md) | 14 | | 8 | Mon Sep 30 | [Separation logic (part 1)](./notes/sep-logic.md) | 15 | | | Tue Oct 1 | _[Assignment 1](./assignments/assignment1.md) due (11pm)_ | 16 | | 9 | Wed Oct 2 | [Separation logic (part 2)](./notes/sep-logic.md) | 17 | | 10 | Mon Oct 7 | [Iris Proof Mode](./notes/ipm.md) | 18 | | 11 | Wed Oct 9 | [Modeling Go programs](./notes/goose.md) | 19 | | 12 | Mon Oct 14 | [Loop invariants](./notes/loop_invariants.md) | 20 | | 13 | Wed Oct 16 | [Ownership](./notes/ownership.md) | 21 | | 14 | Mon Oct 21 | In-class work | 22 | | | Tue Oct 22 | _[Assignment 2](./assignments/assignment2.md) due (11pm)_ | 23 | | 15 | Wed Oct 23 | [Persistence](./notes/persistently.md) | 24 | | | | **Part 3: Concurrency** | 25 | | 16 | Mon Oct 28 | [Concurrency intro](./notes/concurrency.md) | 26 | | 17 | Wed Oct 30 | [Lock invariants](./notes/invariants.md) | 27 | | 18 | Mon Nov 4 | **No class** | 28 | | 19 | Wed Nov 6 | **No class** | 29 | | 18 | Mon Nov 11 | [Resource algebras](./notes/resource-algebra.md) | 30 | | 19 | Wed Nov 13 | [Ghost state](./notes/ghost_state.md) | 31 | | | Thu Nov 14 | _[Assignment 3](./assignments/assignment3/) due (11pm)_ | 32 | | 20 | Mon Nov 18 | [Atomic specs](./notes/atomic_specs.md) | 33 | | 21 | Wed Nov 20 | [Barrier proof (spec)](./notes/barrier.md) | 34 | | 22 | Mon Nov 25 | [Barrier proof](./notes/barrier.md) | 35 | | 23 | Wed Nov 27 | In-class work | 36 | | 24 | Mon Dec 2 | [Property-based testing](./notes/pbt.md) | 37 | | 25 | Wed Dec 4 | [SMT-based verification](./notes/smt.md) | 38 | | 26 | Mon Dec 9 | [Liveness](./notes/liveness.md) | 39 | | 27 | Wed Dec 11 | [Course wrap-up](./notes/conclusion.md) | 40 | | | Mon Dec 16 | _[Assignment 4](./assignments/sharded_hashmap.md) due (11pm)_ | 41 | -------------------------------------------------------------------------------- /docs/notes/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: lightbulb 3 | index: false 4 | dir: 5 | link: true 6 | --- 7 | 8 | # Lecture notes 9 | 10 | These notes are the textbook of the class. I encourage you to read the lecture notes for a lecture afterward. The notes will include additional details and exercises that we won't get to in class. 11 | 12 | Some chapters include a tag "literate" at the top. These are generated from Coq sources that are also distributed in the course [exercises repo](https://github.com/tchajed/sys-verif-fa24-proofs). 13 | 14 | There are exercises throughout the notes, many with solutions. I _highly_ recommend making an attempt at any exercise before looking at the solution, and if you're skimming to resist the temptation to open up the solutions. You'll learn much more by doing the exercises on your own than by passively reading. A small number of exericses are graded homework, but don't let that stop you from doing them and writing out the answers - and if you're unsure feel free to send me a solution and ask about it. 15 | 16 | 17 | -------------------------------------------------------------------------------- /docs/notes/beyond-class.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 28 3 | shortTitle: "Beyond this class" 4 | --- 5 | 6 | # Systems verification beyond this class 7 | 8 | Here are a few pointers to verification research. 9 | 10 | ## Distributed systems 11 | 12 | [IronFleet](https://www.andrew.cmu.edu/user/bparno/papers/ironfleet.pdf) is a good starting point to learn about verifying distributed systems. 13 | 14 | [Grove](https://pdos.csail.mit.edu/papers/grove:sosp23.pdf) extends Perennial with distributed systems reasoning. Add support for reasoning about multiple machines, a network, and reasoning about failures of individual machines. 15 | 16 | There's lots of work on reasoning about protocols independently of implementing those protocols. The [Ivy](https://dl.acm.org/doi/pdf/10.1145/2908080.2908118) paper is a useful starting point. Most of the followup work has been about automating finding inductive invariants to prove safety of a protocol. 17 | 18 | ## New specifications 19 | 20 | Iris is well suited to extensions to handle new language features and specifications. For example: 21 | 22 | - Verifying [randomized algorithms](https://iris-project.org/pdfs/2024-popl-clutch.pdf) where the probability of an outcome is part of the specification. 23 | - Verifying [storage systems](https://pdos.csail.mit.edu/papers/perennial:sosp19.pdf) that want to reason about crashes at any time (for example due to power failure). 24 | - One part of verifying a system is secure is proving confidentiality. Formalizing confidentiality often involves some form of [non-interference specification](https://iris-project.org/pdfs/2021-popl-tiniris-final.pdf). 25 | - [Weak memory](https://people.mpi-sws.org/~dreyer/papers/compass/paper.pdf), a fundamental feature of concurrent programs on real hardware. 26 | - [Persistent memory](https://iris-project.org/pdfs/2023-oopsla-spirea.pdf) is like memory but persists across reboots. These proofs need to combine techniques for crashes with techniques for weak memory. 27 | 28 | Note that in each of these areas there is other work not using Iris that tackles the same underlying problem. 29 | 30 | ## Practical verification 31 | 32 | The techniques in class require learning quite a bit before you can apply them to real code. One line of research is making verification easier to apply, including scaling up to real code, usable by engineers, and integrated with usual software development. 33 | 34 | AWS used verification as a core part of their [process for the Cedar policy language](https://dl.acm.org/doi/pdf/10.1145/3663529.3663854). 35 | 36 | AWS also uses [verification for their distributed protocols](https://assets.amazon.science/67/f9/92733d574c11ba1a11bd08bfb8ae/how-amazon-web-services-uses-formal-methods.pdf). In addition to TLA+ as described by that article, they also use [P](https://p-org.github.io/P/). 37 | 38 | [Verus](https://www.andrew.cmu.edu/user/bparno/papers/verus-sys.pdf) is a new language for verification using Rust. 39 | -------------------------------------------------------------------------------- /docs/notes/conclusion.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: lecture 3 | order: 27 4 | shortTitle: "Lecture 27: Conclusion" 5 | pageInfo: ["Date", "Category", "Tag", "Word"] 6 | --- 7 | 8 | # Lecture 27: Conclusion 9 | 10 | In order to stabilize what you've learned in the class, take some time to recall what you learned and organize it. This outline give you some keywords to help you remember the broad concepts you've seen. 11 | 12 | ## Functional programs 13 | 14 | Inductive data types, recursive functions 15 | 16 | Proofs by induction, computation, and rewriting 17 | 18 | Specifying (functional) abstract data types 19 | 20 | ## Imperative programs 21 | 22 | Hoare logic; weakest preconditions 23 | 24 | Separation logic to deal with the heap; frame rule 25 | 26 | Soundness theorem 27 | 28 | Specifying heap-based abstract data types 29 | 30 | ## Concurrent programs 31 | 32 | Weakest precondition for Fork 33 | 34 | Concurrent soundness theorem 35 | 36 | Fractional permissions 37 | 38 | Resource algebras and ghost state 39 | 40 | Specifying concurrent abstract data types with HOCAP 41 | 42 | ## Coq 43 | 44 | Writing definitions and theorems 45 | 46 | Writing proofs with tactics 47 | 48 | Translating informal proof strategy to tactics 49 | 50 | Interactive proofs: reading the context, finding existing lemmas 51 | 52 | Separation logic in Iris: understanding weakest preconditions, and Hoare triples in Coq 53 | 54 | Iris Proof Mode tactics 55 | 56 | GooseLang: connecting code to Goose model, writing specifications for Go functions 57 | -------------------------------------------------------------------------------- /docs/notes/invariants.md: -------------------------------------------------------------------------------- 1 | --- 2 | # Auto-generated from literate source. DO NOT EDIT. 3 | category: lecture 4 | tags: literate 5 | order: 17 6 | shortTitle: "Lecture 17: Lock invariants" 7 | pageInfo: ["Date", "Category", "Tag", "Word"] 8 | --- 9 | 10 | # Lecture 17: Lock invariants 11 | 12 | > Follow these notes in Coq at [src/sys_verif/program_proof/invariants.v](https://github.com/tchajed/sys-verif-fa24-proofs/blob/main/src/sys_verif/program_proof/invariants.v). 13 | 14 | In this lecture we'll introduce concurrent separation logic and lock invariants, our first tool for reasoning about concurrent programs. 15 | 16 | ## Learning outcomes 17 | 18 | 1. Understand how concurrent separation logic extends sequential separation logic. 19 | 2. Recall the rules for using lock invariants. 20 | 21 | --- 22 | 23 | 24 | 25 | ## Motivation 26 | 27 | We have concurrent programs with `go`, we modeled them with $\spawn$, now how do we prove something about them? 28 | 29 | ## Concurrent separation logic 30 | 31 | Concurrent separation logic (CSL) extends separation logic to handle this new language where multiple threads can be running. What do we need to adapt? We need a way to reason about the new spawn construct, and we need to adapt the soundness theorem to talk about the new threadpool semantics. 32 | 33 | ### Soundness 34 | 35 | Let's start with soundness: 36 | 37 | **Definition** (_CSL soundness_): For some pure $Φ(v)$ (a Prop), if $\hoare{P}{e}{\fun{v} Φ(v)}$ and $([e], h) \leadsto_{tp} ([e'] \listapp T, h')$, then if $e'$ is an expression then $([e'] \listapp T, h')$ is not stuck, or $e' = v'$ for some value $v'$ and $Φ(v')$ holds. Furthermore, no thread in $T$ is stuck in $h'$. 38 | 39 | This should look familiar to the definition of [pure soundness for sequential separation logic](/notes/sep-logic.md#soundness). We only use the threadpool semantics, and describe the return value of the main thread. The spawned threads are mostly ignored but we do state that none of them is stuck. 40 | 41 | ### Exercise: soundness for spawned threads 42 | 43 | Suppose we omitted the last sentence of the soundness theorem, and defined $(T, h)$ to be stuck if _no_ threads could take a step. What program and specification $\hoare{P}{e}{Q}$ would be true under the alternate definition that wasn't with the real definition? Why does this motivate the stronger definition of soundness that we're actually using? 44 | 45 | ### Reasoning about spawn 46 | 47 | The rule for reasoning about spawn is deceptively simple: 48 | 49 | $$ 50 | \hoareV{\wp(e, \True)}{\spawn \, e}{\fun{v} \lift{v = ()}} 51 | \eqnlabel{wp-spawn} 52 | $$ 53 | 54 | Let's see a derived rule that's a little easier to explain: 55 | 56 | $$ 57 | \dfrac{\hoare{P}{e'}{Q}}{ 58 | \hoare{\wp(e, \True) ∗ P}{\spawn \, e \then e'}{Q} 59 | } 60 | $$ 61 | 62 | Notice how we go from proving something about $\spawn \, e \then e'$ to proving a regular triple $\hoare{P}{e'}{Q}$ for the code after the spawn. To do so, we need to _separately_ prove that (a) $e$ is safe to run, with just the postcondition $\True$, and (b) establish the precondition for the rest of the code $e'$. 63 | 64 | The proof of $\wp(e, \True)$ will in general consume some of the resources available, whatever should be owned initially by the background thread. These are basically lost, since the spawned thread never needs to "join" with the parent (unlike a more complex thread-creation mechanism), but we will later see how the spawned thread can communicate with the parent. 65 | 66 | The resources $P$ need to be proven right away, unlike if we were verifying $e; e'$, since the scheduler could certainly choose to run $e'$ next (partly or even to completion). The postcondition of $Q$ makes sense for the whole construct because after spawning $e'$ takes over, and it establishes the postcondition $Q$. 67 | 68 | Let's see this in action. 69 | 70 | ```coq 71 | From sys_verif.program_proof Require Import prelude empty_ffi. 72 | From Goose.sys_verif_code Require Import concurrent. 73 | 74 | Section goose. 75 | Context `{hG: !heapGS Σ}. 76 | 77 | Let N := nroot .@ "lock". 78 | 79 | Lemma wp_SetX (x_l: loc) (x: w64) : 80 | {{{ x_l ↦[uint64T] #x }}} 81 | SetX #x_l 82 | {{{ RET #(); x_l ↦[uint64T] #(W64 1) }}}. 83 | Proof. 84 | wp_start as "x". 85 | wp_store. 86 | iModIntro. iApply "HΦ". iFrame. 87 | Qed. 88 | 89 | Lemma wp_NoGo : 90 | {{{ True }}} 91 | NoGo #() 92 | {{{ RET #(); True }}}. 93 | Proof. 94 | wp_start as "_". 95 | wp_alloc x_l as "x". 96 | wp_pures. 97 | wp_apply (wp_SetX with "[$x]"). 98 | iIntros "x". 99 | ``` 100 | 101 | :::: info Goal 102 | 103 | ```txt title="goal 1" 104 | Σ : gFunctors 105 | hG : heapGS Σ 106 | N := nroot.@"lock" : namespace 107 | Φ : val → iPropI Σ 108 | x_l : loc 109 | ============================ 110 | "HΦ" : True -∗ Φ #() 111 | "x" : x_l ↦[uint64T] #(W64 1) 112 | --------------------------------------∗ 113 | WP #();; #() {{ v, Φ v }} 114 | ``` 115 | 116 | :::: 117 | 118 | ```coq 119 | wp_pures. 120 | iModIntro. iApply "HΦ". done. 121 | Qed. 122 | 123 | Lemma wp_FirstGo : 124 | {{{ True }}} 125 | FirstGo #() 126 | {{{ RET #(); True }}}. 127 | Proof. 128 | wp_start as "_". 129 | wp_alloc x_l as "x". 130 | (* The actual GooseLang construct for creating threads is called Fork. The 131 | specification for Fork is equivalent to the wp-spawn above, but is written in 132 | continuation-passing style. *) 133 | wp_apply (wp_fork with "[x]"). 134 | { iModIntro. 135 | wp_apply (wp_SetX with "[$x]"). iIntros "x". 136 | ``` 137 | 138 | :::: info Goal 139 | 140 | ```txt title="goal 1" 141 | Σ : gFunctors 142 | hG : heapGS Σ 143 | N := nroot.@"lock" : namespace 144 | Φ : val → iPropI Σ 145 | x_l : loc 146 | ============================ 147 | "x" : x_l ↦[uint64T] #(W64 1) 148 | --------------------------------------∗ 149 | True 150 | ``` 151 | 152 | :::: 153 | 154 | ```coq 155 | done. 156 | } 157 | wp_pures. 158 | iModIntro. iApply "HΦ". done. 159 | Qed. 160 | 161 | ``` 162 | 163 | ## Lock invariants 164 | 165 | Recall the API for mutexes: 166 | 167 | ```go 168 | new(sync.Mutex) // to create a new lock 169 | func (m *sync.Mutex) Lock() 170 | func (m *sync.Mutex) Unlock() 171 | ``` 172 | 173 | We can use mutexes (also commonly called locks) to ensure that critical sections of our code run atomically. 174 | 175 | The way to reason about locked code in separation logic is via _lock invariants_. The intuition is that the program uses a lock to protect some memory, which will only be accessed with the lock held. We translate this idea to separation logic by associating a separation logic proposition called a lock invariant with each mutex. The proposition includes any memory protected by the lock; it can include any other separation logic propositions as well, which is what makes lock invariants both interesting and useful. 176 | 177 | So what are the rules for lock invariants? The basic idea for the lock invariant $R$ associated with some mutex $\ell_m$ (which we're naming by the location of the pointer to that mutex) is that it is a separation logic assertion that holds whenever the lock is _free_. Because it holds when the lock is free, when a thread initially acquires the lock, it gets to assume $R$. Separation logic assertions are in general not duplicable, but because of mutual exclusion, a thread that acquires a mutex gets full ownership over $R$. However, when it wants to unlock the same mutex, it has to _give up_ ownership over $R$. 178 | 179 | Formally, we have the following specification for Lock and Unlock: 180 | 181 | $$ 182 | \hoare{\isLock(\ell_m, R)}{\operatorname{Lock} \, \ell_m}{R} \\ 183 | 184 | \hoare{\isLock(\ell_m, R) ∗ R}{\operatorname{Unlock} \, 185 | \ell_m}{\True} 186 | $$ 187 | 188 | To initially get $\isLock(\ell_m, R)$, which associates the lock invariant $R$ with the lock $\ell_m$, we have to use the following rule: 189 | 190 | $$ 191 | \hoareV{R}{\operatorname{newMutex} \, ()}{\fun{v} ∃ \ell_m, v = \ell_m ∗ \isLock(\ell_m, R)} 192 | $$ 193 | 194 | When we create a new mutex, we pick the lock invariant $R$ that represents what the mutex protects, and we also have to prove and give up $R$. This is what ensures the lock invariant holds initially. 195 | 196 | An important aspect of this specification is that $\isLock(\ell_m, R)$ is _persistent_. This is needed since for mutexes to be useful, $\isLock(\ell_m, R)$ has to be available from multiple threads simultaneously. The fact that it is persistent also explains why we don't return it in the Lock and Unlock postconditions. Note that the assertion $\isLock(\ell_m, R)$ can safely be persistent even if $R$ is not persistent because it merely asserts that the lock invariant for the mutex $\ell_m$ is $R$; to actually get a copy of $R$, the thread has to call Lock, and the implementation of mutexes guarantees mutual exclusion at that point. 197 | 198 | **Exercise:** Suppose we could somehow acquire $\isLock(\ell_m, R_1) ∗ \isLock(\ell_m, R_2)$ (notice these are the same mutex pointer), for arbitrarily chosen $R_1$ and $R_2$. What could go wrong? 199 | 200 | Let's see our first example of using locks with Goose. 201 | 202 | Code being verified: 203 | 204 | ```go 205 | func FirstLock() uint64 { 206 | var x uint64 207 | m := new(sync.Mutex) 208 | go func() { 209 | m.Lock() 210 | x = 1 211 | m.Unlock() 212 | }() 213 | m.Lock() 214 | y := x 215 | m.Unlock() 216 | return y 217 | } 218 | ``` 219 | 220 | Let's try a first proof that just shows this code is safe. Even with no interesting postcondition, the GooseLang model requires us to prove in this example that there are no race conditions on `x`; due to weak memory considerations, it isn't quite sound to model loads and stores of even a single variable as being atomic. The mutex in this example ensures the absence of races. 221 | 222 | ```coq 223 | Lemma wp_FirstLock_v1 : 224 | {{{ True }}} 225 | FirstLock #() 226 | {{{ (y: w64), RET #y; True }}}. 227 | Proof. 228 | wp_start as "_". 229 | wp_alloc x_l as "x". wp_pures. 230 | wp_apply (wp_newMutex N _ (∃ (y: w64), x_l ↦[uint64T] #y)%I 231 | with "[x]"). 232 | { iFrame. } 233 | iIntros (m_l) "#Hlock". 234 | wp_pures. 235 | wp_apply wp_fork. 236 | { wp_apply (wp_Mutex__Lock). 237 | { iExact "Hlock". } 238 | iIntros "[Hlocked Hinv]". 239 | ``` 240 | 241 | :::: info Goal 242 | 243 | ```txt title="goal 1" 244 | Σ : gFunctors 245 | hG : heapGS Σ 246 | N := nroot.@"lock" : namespace 247 | Φ : val → iPropI Σ 248 | x_l, m_l : loc 249 | ============================ 250 | "Hlock" : is_lock N #m_l (∃ y : w64, x_l ↦[uint64T] #y) 251 | --------------------------------------□ 252 | "Hlocked" : lock.locked #m_l 253 | "Hinv" : ∃ y : w64, x_l ↦[uint64T] #y 254 | --------------------------------------∗ 255 | WP #();; #x_l <-[uint64T] #(W64 1);; Mutex__Unlock #m_l {{ _, True }} 256 | ``` 257 | 258 | :::: 259 | 260 | After calling Lock, the lock invariant appears in our hypotheses. 261 | 262 | ```coq 263 | iNamed "Hinv". 264 | wp_store. 265 | wp_apply (wp_Mutex__Unlock with "[Hlocked Hinv]"). 266 | { iFrame "Hlock Hlocked". 267 | 268 | ``` 269 | 270 | To call Unlock, we need to prove the same lock invariant. 271 | 272 | ```coq 273 | iModIntro. 274 | ``` 275 | 276 | :::: info Goal 277 | 278 | ```txt title="goal 1" 279 | Σ : gFunctors 280 | hG : heapGS Σ 281 | N := nroot.@"lock" : namespace 282 | Φ : val → iPropI Σ 283 | x_l, m_l : loc 284 | y : w64 285 | ============================ 286 | "Hlock" : is_lock N #m_l (∃ y0 : w64, x_l ↦[uint64T] #y0) 287 | --------------------------------------□ 288 | "Hinv" : x_l ↦[uint64T] #(W64 1) 289 | --------------------------------------∗ 290 | ∃ y0 : w64, x_l ↦[uint64T] #y0 291 | ``` 292 | 293 | :::: 294 | 295 | ```coq 296 | iFrame. } 297 | done. } 298 | wp_pures. 299 | wp_apply (wp_Mutex__Lock with "[$Hlock]"). iIntros "[Hlocked Hinv]". iNamed "Hinv". 300 | wp_load. 301 | wp_apply (wp_Mutex__Unlock with "[$Hlock $Hlocked $Hinv]"). 302 | wp_pures. 303 | iModIntro. 304 | iApply "HΦ". done. 305 | Qed. 306 | 307 | Lemma wp_FirstLock_v2 : 308 | {{{ True }}} 309 | FirstLock #() 310 | {{{ (y: w64), RET #y; ⌜uint.Z y = 0 ∨ uint.Z y = 1⌝ }}}. 311 | Proof. 312 | wp_start as "_". 313 | wp_alloc x_l as "x". wp_pures. 314 | wp_apply (wp_newMutex N _ (∃ (y: w64), 315 | "x" :: x_l ↦[uint64T] #y ∗ 316 | "%Hx" :: ⌜uint.Z y = 0 ∨ uint.Z y = 1⌝)%I 317 | with "[x]"). 318 | { iFrame. iPureIntro. left; word. } 319 | iIntros (m_l) "#Hlock". 320 | wp_pures. 321 | wp_apply wp_fork. 322 | { wp_apply (wp_Mutex__Lock). 323 | { iExact "Hlock". } 324 | iIntros "[Hlocked Hinv]". 325 | iNamed "Hinv". 326 | wp_store. 327 | wp_apply (wp_Mutex__Unlock with "[Hlocked x]"). 328 | { iFrame "Hlock Hlocked". 329 | iModIntro. 330 | iFrame. 331 | iPureIntro. right; word. } 332 | done. } 333 | wp_pures. 334 | wp_apply (wp_Mutex__Lock with "[$Hlock]"). iIntros "[Hlocked Hinv]". iNamed "Hinv". 335 | wp_load. 336 | wp_pures. 337 | wp_apply (wp_Mutex__Unlock with "[$Hlock $Hlocked $x]"). 338 | { iPureIntro. auto. } 339 | wp_pures. 340 | iModIntro. 341 | iApply "HΦ". iPureIntro. done. 342 | Qed. 343 | 344 | End goose. 345 | ``` 346 | -------------------------------------------------------------------------------- /docs/notes/macros.snippet.md: -------------------------------------------------------------------------------- 1 | 8 | 9 | $$ 10 | %% basic math 11 | \gdef\intersect{\cap} 12 | \gdef\union{\cup} 13 | \gdef\dom{\operatorname{dom}} 14 | \gdef\disjoint{\mathrel{\bot}} 15 | \gdef\finto{\overset{\text{fin}}{\rightharpoonup}} 16 | \gdef\listapp{\mathbin{+\mkern-10mu+}} 17 | \gdef\bool{\operatorname{bool}} 18 | \gdef\box{\Box} 19 | 20 | \gdef\iProp{\operatorname{iProp}} 21 | \gdef\Prop{\operatorname{Prop}} 22 | 23 | 24 | %% language 25 | \gdef\ife#1#2#3{\text{\textbf{if} } #1 \text{ \textbf{then} } #2 \text{ \textbf{else} } #3} 26 | \gdef\lete#1#2{\text{\textbf{let} } #1 := #2 \text{ \textbf{in} }} 27 | \gdef\letV#1#2{&\text{\textbf{let} } #1 := #2 \text{ \textbf{in} }} 28 | \gdef\num#1{\overline{#1}} 29 | \gdef\true{\mathrm{true}} 30 | \gdef\false{\mathrm{false}} 31 | \gdef\skip{\mathrm{skip}} 32 | \gdef\fun#1{\lambda #1.\,} 33 | \gdef\funblank{\fun{\_}} 34 | \gdef\rec#1#2{\text{\textbf{rec} } #1 \; #2.\;\,} 35 | \gdef\app#1#2{#1 \, #2} 36 | \gdef\then{;\;} 37 | \gdef\assert#1{\operatorname{assert} \, #1} 38 | \gdef\val{\mathrm{val}} 39 | \gdef\purestep{\xrightarrow{\text{pure}}} 40 | \gdef\spawn{\text{\textbf{spawn}}} 41 | 42 | %% hoare logic 43 | \gdef\False{\mathrm{False}} 44 | \gdef\True{\mathrm{True}} 45 | \gdef\hoare#1#2#3{\left\{#1\right\} \, #2 \, \left\{#3\right\}} 46 | \gdef\hoareV#1#2#3{\begin{aligned}% 47 | &\left\{#1\right\} \\ &\quad #2 \\ &\left\{#3\right\}% 48 | \end{aligned}} 49 | \gdef\wp{\operatorname{wp}} 50 | \gdef\outlineSpec#1{\left\{#1\right\}} 51 | \gdef\entails{\vdash} 52 | \gdef\bient{\dashv\vdash} 53 | \gdef\eqnlabel#1{\:\:\text{#1}} 54 | \gdef\lift#1{\lceil #1 \rceil} 55 | 56 | %% separation logic 57 | % imperative constructs 58 | \gdef\load#1{{!}\,#1} 59 | \gdef\store#1#2{#1 \mathbin{\gets} #2} 60 | \gdef\free#1{\operatorname{free} \, #1} 61 | \gdef\alloc#1{\operatorname{alloc} \, #1} 62 | % logic 63 | \gdef\sep{\mathbin{\raisebox{1pt}{$\star$}}} 64 | %% Iris actually uses \ast (6-pointed) not \star (5-pointed) 65 | %\gdef\sep{\mathbin{\ast}} 66 | \gdef\bigsep{\mathop{\vcenter{\LARGE\hbox{$\star$}}}} 67 | \gdef\bigast{\mathop{\vcenter{\LARGE\hbox{$\ast$}}}} 68 | \gdef\wand{\mathbin{\raisebox{1pt}{$-\hspace{-0.06em}\star$}}} 69 | \gdef\emp{\mathrm{emp}} 70 | \gdef\pointsto{\mapsto} 71 | \gdef\Heap{\mathrm{Heap}} 72 | \gdef\Loc{\mathrm{loc}} 73 | \gdef\valid{\text{\checkmark}} 74 | \gdef\pcore{\operatorname{pcore}} 75 | \gdef\errorval{\bot} 76 | \gdef\option{\operatorname{option}} 77 | \gdef\Some{\operatorname{Some}} 78 | \gdef\None{\operatorname{None}} 79 | \gdef\own{\operatorname{own}} 80 | 81 | %% concurrent separation logic 82 | \gdef\isLock{\mathrm{isLock}} 83 | % \pvs is the iris.sty macro for a basic update modality (without the super dot) 84 | \gdef\pvs{\mathord{{{\mid\kern-0.5ex\Rrightarrow\kern-0.25ex}}\kern0.2ex}} 85 | \gdef\vs{\Rrightarrow} 86 | \gdef\vsWand{\displaystyle\equiv\kern-1.6ex-\kern-1.5ex\smash{\bigast}\kern-0.2ex} 87 | % frame-preserving updates (macro matches iris.sty) 88 | \gdef\mupd{\rightsquigarrow} 89 | $$ 90 | -------------------------------------------------------------------------------- /docs/notes/pbt.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: lecture 3 | order: 24 4 | shortTitle: "Lecture 24: PBT" 5 | pageInfo: ["Date", "Category", "Tag", "Word"] 6 | --- 7 | 8 | # Lecture 24: Property-based testing (PBT) 9 | 10 | In this lecture we'll do something different and talk about _testing_. Don't worry - there is still a close relationship to verification. 11 | 12 | ## Learning outcomes 13 | 14 | 1. Explain the relationship between PBT and separation logic proofs. 15 | 2. Understand how to apply PBT to a function. 16 | 3. Articulate the tradeoff between using PBT and using verification. 17 | 18 | ## Motivation 19 | 20 | We've spent a lot of time talking about specifications and proofs. The separation logic specifications describe code's behavior in detail (including low-level details about how memory is handled) and the proofs give high confidence that the code really meets the specification for all inputs. However, we've also spent a lot of time on proofs. 21 | 22 | In this lecture we'll see Property-Based Testing (PBT), an approach to testing that combines thinking about specifications (similar to verification) with tooling practical to apply to code as it is being developed (similar to unit testing you've already used). 23 | 24 | ## Teaser: topological sort 25 | 26 | ```mermaid 27 | %%{init: {"flowchart": {"nodeSpacing": 20, "rankSpacing": 30}}}%% 28 | graph TD 29 | oven --> dry[combine dry] 30 | oven --> eggs 31 | dry --> batter[mix batter] 32 | eggs --> wet[mix wet] 33 | wet --> batter 34 | %% batter --> cake 35 | 36 | style oven fill:#f9f,stroke:#333,stroke-width:2px 37 | style dry fill:#ff9,stroke:#333,stroke-width:2px 38 | style eggs fill:#9ff,stroke:#333,stroke-width:2px 39 | style wet fill:#fc9,stroke:#333,stroke-width:2px 40 | style batter fill:#9f9,stroke:#333,stroke-width:2px 41 | %% style cake fill:#f99,stroke:#333,stroke-width:2px 42 | ``` 43 | 44 | What order do I actually do things in? Let's say we write a function `f` that takes the dependencies and outputs this linear order: 45 | 46 | ```mermaid 47 | %%{init: {"flowchart": {"nodeSpacing": 20, "rankSpacing": 30}}}%% 48 | graph LR 49 | oven --- eggs 50 | eggs --- dry 51 | dry --- wet 52 | wet --- batter 53 | style oven fill:#f9f,stroke:#333,stroke-width:2px 54 | style dry fill:#ff9,stroke:#333,stroke-width:2px 55 | style eggs fill:#9ff,stroke:#333,stroke-width:2px 56 | style wet fill:#fc9,stroke:#333,stroke-width:2px 57 | style batter fill:#9f9,stroke:#333,stroke-width:2px 58 | linkStyle default display: none; 59 | ``` 60 | 61 | Let's say we change our algorithm so it emits this: 62 | 63 | ```mermaid 64 | %%{init: {"flowchart": {"nodeSpacing": 20, "rankSpacing": 30}}}%% 65 | graph LR 66 | oven --> eggs 67 | eggs --> wet 68 | wet --> dry 69 | dry --> batter 70 | style oven fill:#f9f,stroke:#333,stroke-width:2px 71 | style dry fill:#ff9,stroke:#333,stroke-width:2px 72 | style eggs fill:#9ff,stroke:#333,stroke-width:2px 73 | style wet fill:#fc9,stroke:#333,stroke-width:2px 74 | style batter fill:#9f9,stroke:#333,stroke-width:2px 75 | linkStyle default display: none; 76 | ``` 77 | 78 | Should we update our test to check for this output now? 79 | 80 | ## Testing sorting algorithm 81 | 82 | Consider this programming task: 83 | 84 | ```go 85 | type Person struct { 86 | Name string 87 | Age uint64 88 | } 89 | 90 | // Sort sorts arr by increasing Age. 91 | func Sort(arr []Person) { 92 | // Bubble sort arr in-place 93 | // ... code committed ... 94 | } 95 | ``` 96 | 97 | This example has the same feature as we saw before: if two `Person` objects have the same `Age` field, then the function does not specify what order they will be sorted in. There's more than one correct output for the same input (if Ages are repeated). 98 | 99 | **Exercise:** What's the verification perspective on this function? Think of a specification for this example that we might verify. You can be informal about how to represent a Person mathematically; it doesn't need to be an actual Goose or Coq specification. 100 | 101 | One thing we can say is that the new array after calling `Sort` should be sorted by age. We'll use $arr'$ to distinguish from the contents of the array prior to sorting, which we'll call $arr$. 102 | 103 | $$ 104 | \forall i, 0 \leq i+1 < \mathtt{len}(\mathrm{arr}') \to \\ 105 | arr'[i].\mathtt{Age} \leq arr'[i+1].\mathtt{Age} \eqnlabel{(sorted)} 106 | $$ 107 | 108 | We should also say for correctness that the set of elements in the new array is the same as before (it would be a more precise specification to say $arr'$ is a permutation of $arr$, or to compare the _multiset_ of the two lists, but those take a little more work to write down): 109 | 110 | $$\mathrm{set}(arr') = \mathrm{set}(arr) \eqnlabel{(same elements)}$$ 111 | 112 | Now we have a specification $P(arr, arr')$ that captures the important correctness properties of Sort: the combination of the "sorted" and "same elements" properties above. In fact, $P$ covers pretty much everything about the behavior of Sort you'd want to know. This is a _relational specification_ for `Sort`, in that it relates the input and output of `Sort`. 113 | 114 | ## Property-based testing 115 | 116 | Given a relational specification for a function, the idea of Property-Based Testing is to: 117 | 118 | 1. Implement our relational specification as a _property_ in Go (or whatever programming language we're using). This allows us to run the property and see if it held up. 119 | 2. Test the property on many random inputs; if it ever fails, our function (or property!) is wrong. 120 | 3. (optionally) On failure, find a simpler test case, record it, and turn it into a regression test. 121 | 122 | There are a few limitations to where PBT applies: 123 | 124 | We must be able to test the property with an implementation. This causes trouble with any quantifiers we might want in a specification: any "forall" or "exists" will be implemented in general by enumerating the possibilities, and this must be both possible (because the domain is finite) and efficient enough (because the domain is small enough) for PBT to be effective. In the sorting example, if the specification said the output is a permutation of the input, testing this by enumerating all permutations and checking them would be extremely slow. 125 | 126 | We also must be able to generate inputs to our algorithm, and more subtly, we need to generate _interesting-enough_ inputs to actually exercise the behavior of the algorithm and find bugs. Sorting takes a list of elements, and random permutations are probably good enough. For more sophisticated inputs, generating valid inputs can be quite hard. A basic approach is to generate and then filter the valid ones. However, done naively this doesn't work: if only 1% of random inputs are valid, it will be very slow to generate inputs and the test coverage will be poor. More sophisticated testing will involve building a custom generator, and PBT frameworks have support for this. A real-world sophisticated example I know if is a generator for Rust [Cargo configurations](https://github.com/jonhoo/cargo-index-transit/blob/main/tests/proptest.rs). 127 | 128 | If you want to learn more about the forefront of PBT I recommend reading [Property-Based Testing in Practice](https://dl.acm.org/doi/pdf/10.1145/3597503.3639581), which talks about some challenges in applying PBT at scale. 129 | 130 | Finally, PBT works best when the code is a function. Stateful code like a data structure is difficult to get good coverage on (because we have to test sequences of inputs in order to also test functionality in different states) and harder to write properties for. A related technique for ADTs is _model-based testing_: instead of the property being relational, we compare our code under test to a simpler _model_, much like the specifications we used for ADTs. Like PBT, we can drive both the model and code under test using random sequences of inputs. Unlike the ADT proofs, the model has to be implemented in code and not just math. 131 | 132 | ## Returning to the topological sort example 133 | 134 | Given a graph G with nodes and directed dependencies, topo(G) (should) _topologically sort_ G. It outputs a list of nodes. 135 | 136 | **Exercise:** what relational specification does `topo(G) = ns` satisfy? 137 | 138 | ## PBT framework in Go 139 | 140 | We'll now take a look at the API of and applying it to these two examples as actual Go code. 141 | 142 | Start by reading the [godoc](https://pkg.go.dev/pgregory.net/rapid). 143 | 144 | The API consists of two ideas: 145 | 146 | ```go 147 | // Check runs prop many times to try to find a failure 148 | rapid.Check(t *testing.T, prop func(*rapid.T)) 149 | // Error within a property signals failure 150 | func (t *rapid.T) Error(args ...any) 151 | 152 | type Generator[V any] struct { ... } 153 | // Draw within a property creates a random input 154 | func (g *Generator[V]) Draw(t *rapid.T, label string) V 155 | func (g *Generator[V]) Filter(fn func(V) bool) *Generator[V] 156 | 157 | // many ways to construct generators, for example: 158 | func IntRange(min int, max int) *Generator[int] 159 | ``` 160 | 161 | There are two basic ideas: we write a property which signals failure with `t.Error()`, and we get input for that property by creating `Generator[V]`s and calling `Draw()` on them. The property should be interpreted as stating "for all values the generators produce, no errors will occur". Then, `rapid.Check(t, prop)` runs the property many times (with different random inputs), and if it fails it sends the failure to Go's testing infrastructure which is based on an argument `t *testing.T`. 162 | 163 | When you use rapid, every test produces more random inputs. Eventually you might encounter a failure on a hard-to-generate input. At that point, rapid has a feature to facilitate debugging: if a test fails, rapid records the random inputs generated so that the exact failure can be reproduced. It looks like this (for a bug injected into `go/algo/topo_sort.go`): 164 | 165 | ```txt 166 | Error: elements differ 167 | 168 | extra elements in list A: 169 | ([]interface {}) (len=3) { 170 | (uint32) 4, 171 | (uint32) 7, 172 | (uint32) 1 173 | } 174 | 175 | 176 | listA: 177 | ([]uint32) (len=4) { 178 | (uint32) 4, 179 | (uint32) 7, 180 | (uint32) 1, 181 | (uint32) 2 182 | } 183 | 184 | 185 | listB: 186 | ([]uint32) (len=1) { 187 | (uint32) 2 188 | } 189 | Test: TestTopoSortProperties 190 | 191 | To reproduce, specify -run="TestTopoSortProperties" -rapid.failfile="testdata/rapid/TestTopoSortProperties/TestTopoSortProperties-20241201192450-2252.fail" (or -rapid.seed=1969236383784157701) 192 | ``` 193 | 194 | The line I want to highlight is the last one: 195 | 196 | > To reproduce, specify -run="TestTopoSortProperties" -rapid.failfile="testdata/rapid/TestTopoSortProperties/TestTopoSortProperties-20241201192450-2252.fail" (or 197 | 198 | What that file in `testdata` actually contains is almost binary data (it looks like the sequence of random numbers generated by the pseudo-random number generator), but it's everything rapid needs to re-run this exact failure scenario. 199 | 200 | For the topological sort test, we need the input graph to be acyclic for the algorithm to work - it actually seems to go into an infinite loop on a cyclic graph. Checking if a graph is acyclic is a bit tricky, but so is generating random edges that make up an acyclic graph. For the test in this example I implemented a function `isAcyclic` and wrote some ordinary example-based tests to get some confidence in it. This first example already illustrates one difficulty of PBT: generating valid inputs can be quite challenging. 201 | 202 | One solution that would have made this easier is to _strengthen the specification_ of our original topological sort. Rather than making `isAcylic(g)` a precondition, we could instead adjust the algorithm to check for cycles and return a separate boolean if the input is cyclic. This would have let us easily generate valid inputs (since any graph would do), but it wouldn't solve the fundamental issue that the property of a graph being cyclic requires a quantifier over all paths through the graph and is difficult to check in an obviously correct way. 203 | -------------------------------------------------------------------------------- /docs/notes/program-proofs/auth_set.md: -------------------------------------------------------------------------------- 1 | --- 2 | # Auto-generated from literate source. DO NOT EDIT. 3 | category: demo 4 | tags: literate 5 | order: -1 6 | shortTitle: "Auth set ghost state" 7 | --- 8 | 9 | # Auth set ghost library 10 | 11 | This library is a dependency for the barrier proof. It's also a self-contained example of creating a new ghost theory in Iris. 12 | 13 | As is typical, we don't define a resource algebra from scratch, but instead build one out of existing primitives. However, we do prove lemmas about the ownership of that RA (that, is the `own` predicate) to make using ghost state of this type more convenient. 14 | 15 | You should think of an instance of `auth_set A` (that is, a ghost variable that uses this RA) as being a variable of type `gset A`; the whole construction is parameterized by a type `A` of elements. It has two predicates: `auth_set_auth (γ: gname) (s: gset A)` which says exactly what the set for the variable named γ is and `auth_set_frag (γ: gname) (x: A)`, which asserts ownership of one element `x ∈ s`. The `_auth` stands for "authoritative" and there is only one copy of that predicate for any γ; think of this as the value of the ghost variable. The `_frag` stands for "fragment" and there can be many fragments, one for each element of the authoritative set. 16 | 17 | ```coq 18 | From iris.algebra Require Import auth gset. 19 | From iris.proofmode Require Import proofmode. 20 | From iris.base_logic.lib Require Export own. 21 | 22 | Set Default Proof Using "Type". 23 | Set Default Goal Selector "!". 24 | 25 | Class auth_setG Σ (A: Type) `{Countable A} := AuthSetG { 26 | auth_set_inG :: inG Σ (authUR (gset_disjUR A)); 27 | }. 28 | Global Hint Mode auth_setG - ! - - : typeclass_instances. 29 | 30 | Definition auth_setΣ A `{Countable A} : gFunctors := 31 | #[ GFunctor (authRF (gset_disjUR A)) ]. 32 | 33 | #[global] Instance subG_auth_setG Σ A `{Countable A} : 34 | subG (auth_setΣ A) Σ → auth_setG Σ A. 35 | Proof. solve_inG. Qed. 36 | 37 | ``` 38 | 39 | auth_set is a thin wrapper around the resource algebra `authUR (gset_disjUR A)`. 40 | 41 | ```coq 42 | Local Definition auth_set_auth_def `{auth_setG Σ A} 43 | (γ : gname) (s: gset A) : iProp Σ := 44 | own γ (● GSet s). 45 | Local Definition auth_set_auth_aux : seal (@auth_set_auth_def). Proof. by eexists. Qed. 46 | Definition auth_set_auth := auth_set_auth_aux.(unseal). 47 | Local Definition auth_set_auth_unseal : 48 | @auth_set_auth = @auth_set_auth_def := auth_set_auth_aux.(seal_eq). 49 | Global Arguments auth_set_auth {Σ A _ _ _} γ s. 50 | 51 | #[local] Notation "○ a" := (auth_frag a) (at level 20). 52 | 53 | Local Definition auth_set_frag_def `{auth_setG Σ A} 54 | (γ : gname) (a: A) : iProp Σ := 55 | own γ (○ GSet {[a]}). 56 | Local Definition auth_set_frag_aux : seal (@auth_set_frag_def). Proof. by eexists. Qed. 57 | Definition auth_set_frag := auth_set_frag_aux.(unseal). 58 | Local Definition auth_set_frag_unseal : 59 | @auth_set_frag = @auth_set_frag_def := auth_set_frag_aux.(seal_eq). 60 | Global Arguments auth_set_frag {Σ A _ _ _} γ a. 61 | 62 | Local Ltac unseal := rewrite ?auth_set_auth_unseal ?auth_set_frag_unseal /auth_set_auth_def /auth_set_frag_def. 63 | 64 | Section lemmas. 65 | Context `{auth_setG Σ A}. 66 | 67 | Implicit Types (s: gset A) (a: A). 68 | 69 | #[global] Instance auth_set_auth_timeless γ s : 70 | Timeless (auth_set_auth γ s). 71 | Proof. unseal. apply _. Qed. 72 | #[global] Instance auth_set_frag_timeless γ a : 73 | Timeless (auth_set_frag γ a). 74 | Proof. unseal. apply _. Qed. 75 | 76 | ``` 77 | 78 | The definition of auth_set is designed to make these ghost updates true. This as the API for this construction, in that the user of the library will not use the definitions above, only these lemmas. However, we have to carefully choose the definitions to make all of these rules true. We create an auth_set variable with an empty set and thus no fragments. 79 | 80 | ```coq 81 | Lemma auth_set_init : 82 | ⊢ |==> ∃ γ, auth_set_auth γ (∅: gset A). 83 | Proof. 84 | unseal. 85 | iApply (own_alloc (● GSet (∅: gset A))). 86 | apply auth_auth_valid. done. 87 | Qed. 88 | 89 | ``` 90 | 91 | We can add to the set and produce a new fragment that controls the new element. `a ∉ s` is required since there can only be one `auth_set_frag γ a` for a given value of `a`. 92 | 93 | ```coq 94 | Lemma auth_set_alloc a γ s : 95 | a ∉ s → 96 | auth_set_auth γ s ==∗ 97 | auth_set_auth γ ({[a]} ∪ s) ∗ auth_set_frag γ a. 98 | Proof. 99 | unseal. 100 | iIntros (Hnotin) "Hauth". 101 | rewrite -own_op. 102 | iApply (own_update with "Hauth"). 103 | apply auth_update_alloc. 104 | apply gset_disj_alloc_empty_local_update. 105 | set_solver. 106 | Qed. 107 | 108 | ``` 109 | 110 | Because a fragment expresses ownership of a part of the authoritative set, we have this rule which says that fragments agree with the authoritative predicate: 111 | 112 | ```coq 113 | Lemma auth_set_elem γ s a : 114 | auth_set_auth γ s -∗ auth_set_frag γ a -∗ ⌜a ∈ s⌝. 115 | Proof. 116 | unseal. iIntros "Hauth Hfrag". 117 | iDestruct (own_valid_2 with "Hauth Hfrag") as %Hin. 118 | iPureIntro. 119 | apply auth_both_valid_discrete in Hin as [Hin _]. 120 | apply gset_disj_included in Hin. 121 | apply singleton_subseteq_l in Hin. 122 | auto. 123 | Qed. 124 | 125 | ``` 126 | 127 | If we control an element via `auth_set_frag γ a`, it's also possible to delete that element from the authoritative set (as long as we also give up ownership of the fragment). 128 | 129 | ```coq 130 | Lemma auth_set_dealloc γ s a : 131 | auth_set_auth γ s ∗ auth_set_frag γ a ==∗ 132 | auth_set_auth γ (s ∖ {[a]}). 133 | Proof. 134 | unseal. iIntros "[Hauth Hfrag]". 135 | iApply (own_update_2 with "Hauth Hfrag"). 136 | apply auth_update_dealloc. 137 | apply gset_disj_dealloc_local_update. 138 | Qed. 139 | 140 | End lemmas. 141 | ``` 142 | -------------------------------------------------------------------------------- /docs/notes/program-proofs/decide.md: -------------------------------------------------------------------------------- 1 | --- 2 | # Auto-generated from literate source. DO NOT EDIT. 3 | tags: literate 4 | shortTitle: decide 5 | --- 6 | 7 | # What is `decide`? 8 | 9 | What is this `if decide` thing? And what is `bool_decide`? And how do I do proofs involving them? 10 | 11 | ```coq 12 | From stdpp Require Import numbers. 13 | From sys_verif Require Import options. 14 | From Coq.Arith Require Import Peano_dec. 15 | 16 | ``` 17 | 18 | We often use the pattern `if decide P then ... else ...`. Alternately, you may try to write `if (x = y) then ... else ...` it will fail, in which case you _should_ be writing `if decide (x = y) then ... else ...`. Alternately, you may see `bool_decide` or `decide` show up in program proofs since they are used by Goose. 19 | 20 | This explanation gives you a terse overview of how `decide` works and how to do proofs involving it. 21 | 22 | If your goal involves `decide`, one thing you can do is `destruct (decide P)`, which will split into two cases: 23 | 24 | ```coq 25 | (* this theorem is obviously false, it's just a demo of the proof tactics *) 26 | Lemma example_destruct_1 (x y: Z) : 27 | (if decide (x = y) then (x+1) else (y-2)) = 7. 28 | Proof. 29 | destruct (decide (x = y)). 30 | ``` 31 | 32 | :::: info Goals 33 | 34 | ```txt title="goal 1" 35 | x, y : Z 36 | e : x = y 37 | ============================ 38 | x + 1 = 7 39 | ``` 40 | 41 | ```txt title="goal 2" 42 | x, y : Z 43 | n : x ≠ y 44 | ============================ 45 | y - 2 = 7 46 | ``` 47 | 48 | :::: 49 | 50 | Notice the `x ≠ y` hypothesis in the second goal. This is how `~(x = y)` is printed. You will see `~(x < y)` if you destruct an inequality; `lia` and `word` know how to deal with that. 51 | 52 | ```coq 53 | Abort. 54 | 55 | Lemma example_destruct_2 (x y: Z) : 56 | (if decide (x = y) then (x+1) else (y-2)) = 7. 57 | Proof. 58 | (* You can pass a pattern with underscores to `destruct` and it will fill them 59 | in with anything seen in the goal; this isn't specific to `decide` but the two 60 | are often convenient together. This has the same effect as `destruct (decide (x = y))` in this goal. *) 61 | destruct (decide _). 62 | Abort. 63 | 64 | Lemma example_destruct_3 (x y: Z) : 65 | (if decide (x = y) then (x+1) else (y-2)) = 7. 66 | Proof. 67 | (* You can pass a pattern with underscores to `destruct` and it will fill them 68 | in with anything seen in the goal; this isn't specific to `decide` but the two 69 | are often convenient together. This has the same effect as `destruct (decide (x = y))` in this goal. *) 70 | destruct (decide _). 71 | Abort. 72 | 73 | ``` 74 | 75 | ## bool_decide 76 | 77 | In some contexts you will see `bool_decide` and not `decide`. It also takes a proposition as an argument, but produces a boolean, which is sometimes needed in program proofs or other contexts. There are a few lemmas and tricks to work with it. 78 | 79 | Here's a cheatsheet: 80 | 81 | - The tactic `case_bool_decide` to destruct a `bool_decide` and do a proof in each case. 82 | - `rewrite bool_decide_eq_true_2` gives `bool_decide P = true` and produces `P` as a side condition 83 | - `rewrite bool_decide_eq_false_2` gives `bool_decide P = false` and produces `~P` as a side condition 84 | 85 | First of all, you don't want to do `destruct (bool_decide P)` like we did above. 86 | 87 | ```coq 88 | Lemma bad_bool_decide_destruct (x y: Z) : 89 | 3 ≤ (if bool_decide (x < 3) then 3 else x). 90 | Proof. 91 | destruct (bool_decide _). 92 | ``` 93 | 94 | :::: info Goals 95 | 96 | ```txt title="goal 1" 97 | x, y : Z 98 | ============================ 99 | 3 ≤ 3 100 | ``` 101 | 102 | ```txt title="goal 2" 103 | x, y : Z 104 | ============================ 105 | 3 ≤ x 106 | ``` 107 | 108 | :::: 109 | 110 | ```coq 111 | - lia. 112 | - (* not provable: we have nothing about `~(x < 3)` *) 113 | Abort. 114 | 115 | Lemma example_bool_decide_destruct (x y: Z) : 116 | 3 ≤ (if bool_decide (x < 3) then 3 else x). 117 | Proof. 118 | case_bool_decide. 119 | ``` 120 | 121 | :::: info Goals 122 | 123 | ```txt title="goal 1" 124 | x, y : Z 125 | H : x < 3 126 | ============================ 127 | 3 ≤ 3 128 | ``` 129 | 130 | ```txt title="goal 2" 131 | x, y : Z 132 | H : ¬ x < 3 133 | ============================ 134 | 3 ≤ x 135 | ``` 136 | 137 | :::: 138 | 139 | ```coq 140 | - lia. 141 | - (* this is provable *) 142 | lia. 143 | Qed. 144 | 145 | Lemma example_bool_decide_rewrite (x y: Z) : 146 | x < 1 → 147 | (if bool_decide (x < 3) then x else y) = x. 148 | Proof. 149 | intros Hlt. 150 | (* I'd usually do `rewrite bool_decide_eq_true_2 //` to automatically prove 151 | the trivial subgoals (the second one in this case), but want to illustrate the goals here. *) 152 | rewrite bool_decide_eq_true_2. 153 | ``` 154 | 155 | :::: info Goals 156 | 157 | ```txt title="goal 1" 158 | x, y : Z 159 | Hlt : x < 1 160 | ============================ 161 | x < 3 162 | ``` 163 | 164 | ```txt title="goal 2" 165 | x, y : Z 166 | Hlt : x < 1 167 | ============================ 168 | x = x 169 | ``` 170 | 171 | :::: 172 | 173 | ```coq 174 | - lia. 175 | - rewrite //. 176 | Qed. 177 | 178 | ``` 179 | 180 | ## Missing Decision P instance 181 | 182 | The second thing you may encounter is that sometimes, `decide P` won't type check because of a missing `Decision P` instance: 183 | 184 | ```coq 185 | Inductive color := red | green | blue. 186 | Fail Definition failed_color_dec (c: color) := 187 | if decide (c = red) then true else false. 188 | ``` 189 | 190 | :::: note Output 191 | 192 | ```txt title="coq output" 193 | 194 | ``` 195 | 196 | :::: 197 | 198 | You should really read the error message and try to make sense of it. 199 | 200 | The reason this fails is that `Decision P` says that there's a _function_ that determines if P is true vs if ~P is true. Coq's logic is such that we actually can't do this for arbitrary propositions `P`; it requires that we can _compute_ which of `P` or `~P` is true. 201 | 202 | `decide` is just looking up this function using typeclasses, but the actual function doesn't get implemented for us automatically. We can provide an instance of equality between arbitrary colors; here's a manual version of that which we'll abandon in favor of using the powerful `solve_decision` automation tactic. 203 | 204 | ```coq 205 | Instance color_eq_dec : ∀ (c1 c2: color), Decision (c1 = c2). 206 | Proof. 207 | intros c1 c2. rewrite /Decision. 208 | destruct c1, c2. 209 | ``` 210 | 211 | :::: info Goals 212 | 213 | ```txt title="goal 1" 214 | ============================ 215 | {red = red} + {red ≠ red} 216 | ``` 217 | 218 | ```txt title="goal 2" 219 | ============================ 220 | {red = green} + {red ≠ green} 221 | ``` 222 | 223 | ```txt title="goal 3" 224 | ============================ 225 | {red = blue} + {red ≠ blue} 226 | ``` 227 | 228 | ```txt title="goal 4" 229 | ============================ 230 | {green = red} + {green ≠ red} 231 | ``` 232 | 233 | ```txt title="goal 5" 234 | ============================ 235 | {green = green} + {green ≠ green} 236 | ``` 237 | 238 | :::: 239 | 240 | ```coq 241 | - left. auto. 242 | - right. congruence. 243 | (* Yikes, this looks tedious. *) 244 | Abort. 245 | 246 | Instance color_eq_dec : ∀ (c1 c2: color), Decision (c1 = c2). 247 | Proof. solve_decision. Qed. 248 | 249 | ``` 250 | 251 | Now Coq will use the instance we just defined when we write `decide`. 252 | 253 | ```coq 254 | Definition use_color_dec (c: color) := if decide (c = red) then true else false. 255 | 256 | ``` 257 | 258 | ## Implementation 259 | 260 | The implementation (which comes from std++) is actually very short, so let's show that. 261 | 262 | ```coq 263 | Module decide_playground. 264 | (* `decide` is the single member of a type class `Decision P` *) 265 | Class Decision (P : Prop) := decide : {P} + {~P}. 266 | 267 | 268 | ``` 269 | 270 | What is that type for `decide`? `{P} + {~P}` is notation for `sumbool` from the Coq standard library, which has the following definition: 271 | 272 | ```coq 273 | Inductive sumbool (A B : Prop): Type := 274 | | left (H: A) 275 | | right (H: B). 276 | 277 | 278 | ``` 279 | 280 | This definition says `sumbool A B` has either an element of `A` or a proof of `B`, and I call them proofs because `A` and `B` are `Prop`s. This is the Curry-Howard correspondence: we call a proposition `P` true if it has an `pf: P`, and we call such an element a proof. See Software Foundations's [Curry-Howard Correspondence chapter](https://softwarefoundations.cis.upenn.edu/lf-current/ProofObjects.html) for more. 281 | 282 | The definition of `sumbool` is actually the same as `or P Q` (always written `P ∨ Q`), except that it is annotated as `{P} + {Q} : Type` whereas `P ∨ Q : Prop`. The difference is that we can do computations with something in `Type`, while facts in `Prop` can only be used in proofs. 283 | 284 | ```coq 285 | End decide_playground. 286 | ``` 287 | -------------------------------------------------------------------------------- /docs/notes/program-proofs/fibonacci_proof.md: -------------------------------------------------------------------------------- 1 | --- 2 | # Auto-generated from literate source. DO NOT EDIT. 3 | category: demo 4 | tags: literate 5 | order: -1 6 | pageInfo: ["Date", "Category", "Tag"] 7 | shortTitle: "Demo: fibonacci" 8 | --- 9 | 10 | # Demo: verifying fibonacci function 11 | 12 | > The Coq code for this file is at [src/sys_verif/program_proof/demos/fibonacci_proof.v](https://github.com/tchajed/sys-verif-fa24-proofs/blob/main/src/sys_verif/program_proof/demos/fibonacci_proof.v). 13 | 14 | The code in [go/functional/functional.go](https://github.com/tchajed/sys-verif-fa24-proofs/blob/main/go/functional/functional.go) implements `func Fibonacci(n uint6) uint64`, computing the nth Fibonacci number. 15 | 16 | We prove this function correct, proving that the imperative, loop-based implementation Go is equivalent to a recursive, functional implementation in Gallina. 17 | 18 | ```coq 19 | From sys_verif.program_proof Require Import prelude empty_ffi. 20 | From Goose.sys_verif_code Require Import functional. 21 | 22 | Section proof. 23 | Context `{hG: !heapGS Σ}. 24 | 25 | Fixpoint fibonacci (n: nat): nat := 26 | match n with 27 | | 0 => 0 28 | | 1 => 1 29 | (* a little care is needed for Coq to accept that this function terminates *) 30 | | S (S n_minus_2 as n_minus_1) => fibonacci n_minus_2 + fibonacci n_minus_1 31 | end%nat. 32 | 33 | ``` 34 | 35 | We will need some helper lemmas. These are only required because the specification will assume that the final result doesn't overflow, and we will use that to show that the intermediate results also don't overflow. 36 | 37 | ```coq 38 | Lemma fibonacci_S i : 39 | (1 ≤ i)%nat → 40 | fibonacci (S i) = (fibonacci i + fibonacci (i-1))%nat. 41 | Proof. 42 | intros Hle. 43 | destruct i; simpl. 44 | { lia. (* contradicts assumption *) } 45 | replace (i - 0)%nat with i by lia. 46 | lia. 47 | Qed. 48 | 49 | Lemma fibonacci_monotonic i1 i2 : 50 | (i1 ≤ i2)%nat → 51 | fibonacci i1 ≤ fibonacci i2. 52 | Proof. 53 | intros Hle. 54 | destruct (decide (i1 = i2)). 55 | { subst; lia. } 56 | 57 | assert (∃ d, i2 = S (d + i1)) as H. 58 | { exists (i2 - i1 - 1)%nat. lia. } 59 | destruct H as [d ?]; subst. 60 | 61 | move: Hle. 62 | clear. 63 | induction d. 64 | - destruct i1; simpl; lia. 65 | - simpl in *; lia. 66 | Qed. 67 | 68 | ``` 69 | 70 | Here is the statement of what it means for `Fibonacci` (the Go function) to be correct. 71 | 72 | ```coq 73 | Lemma wp_Fibonacci (n: w64) : 74 | {{{ ⌜Z.of_nat (fibonacci (uint.nat n)) < 2^64⌝ }}} 75 | Fibonacci #n 76 | {{{ (c: w64), RET #c; ⌜uint.nat c = fibonacci (uint.nat n)⌝ }}}. 77 | Proof. 78 | wp_start as "%Hoverflow". 79 | wp_pures. 80 | wp_if_destruct. 81 | { iModIntro. 82 | iApply "HΦ". 83 | iPureIntro. 84 | reflexivity. 85 | } 86 | wp_alloc fib_prev as "fib_prev". 87 | wp_alloc fib_cur as "fib_cur". 88 | wp_alloc i_l as "i". 89 | wp_pures. 90 | 91 | ``` 92 | 93 | The core of the proof's argument is this loop invariant about the `prev` and `cur` variables. 94 | 95 | ```coq 96 | wp_apply (wp_forUpto' 97 | (λ i, ∃ (prev cur: w64), 98 | "fib_prev" ∷ fib_prev ↦[uint64T] #prev ∗ 99 | "fib_cur" ∷ fib_cur ↦[uint64T] #cur ∗ 100 | "%Hi_ge" ∷ ⌜1 ≤ uint.Z i⌝ ∗ 101 | "%Hprev" ∷ ⌜uint.nat prev = fibonacci (uint.nat i - 1)⌝ ∗ 102 | "%Hcur" ∷ ⌜uint.nat cur = fibonacci (uint.nat i)⌝ 103 | )%I 104 | with "[$i $fib_prev $fib_cur]"). 105 | - iPureIntro. 106 | split. 107 | { word. } 108 | split. 109 | { word. } 110 | split. 111 | { reflexivity. } 112 | reflexivity. 113 | - clear Φ. 114 | iIntros "!>" (i Φ) "[IH (i & %Hle)] HΦ". 115 | iNamed "IH". 116 | wp_pures. 117 | repeat (wp_load || wp_store || wp_pures). 118 | iModIntro. 119 | iApply "HΦ". 120 | iFrame. 121 | iPureIntro. 122 | split. 123 | + word. 124 | + split. 125 | { rewrite Hcur. f_equal; word. } 126 | replace (uint.nat (word.add i (W64 1))) with 127 | (S (uint.nat i)) by word. 128 | rewrite fibonacci_S; [ | word ]. 129 | (* we need to show the [word.add] doesn't overflow to finish the proof *) 130 | assert (uint.Z cur + uint.Z prev < 2^64) as Hsum. 131 | { 132 | (* to prove this, we'll use the fact that [uint.nat cur + uint.nat 133 | prev = fibonacci (S i)], and that [fibonacci] is monotonic. We know that 134 | [i < n] as part of the loop invariant. *) 135 | pose proof (fibonacci_monotonic (S (uint.nat i)) (uint.nat n)) as Hi_n. 136 | rewrite -> fibonacci_S in Hi_n by word. 137 | word. } 138 | word. 139 | - iIntros "[IH i]". iNamed "IH". 140 | wp_load. 141 | iModIntro. 142 | iApply "HΦ". 143 | auto. 144 | Qed. 145 | 146 | End proof. 147 | ``` 148 | -------------------------------------------------------------------------------- /docs/notes/program-proofs/fractions.md: -------------------------------------------------------------------------------- 1 | --- 2 | # Auto-generated from literate source. DO NOT EDIT. 3 | tags: literate 4 | --- 5 | 6 | # Fractional permissions 7 | 8 | Fractional permissions are a feature of separation logic that supports read-only permissions. This is especially important for concurrency. 9 | 10 | Going into this document, I assume you have some familiarity with separation logic, enough to understand what `l ↦ v` means as a separation logic proposition (an `iProp` specifically). 11 | 12 | ## Setup: normal permissions 13 | 14 | In GooseLang, the "basic points-to fact" is written with a type (the reasons are explained separately), but this doesn't affect the discussion here. We'll show examples only using the uint64 type. The `l ↦[uint64T] #x` permission allows both reads and writes. 15 | 16 | ```coq 17 | Lemma read_spec (l: loc) (x: w64) : 18 | {{{ l ↦[uint64T] #x }}} 19 | ![uint64T] #l 20 | {{{ RET #x; l ↦[uint64T] #x }}}. 21 | Proof. 22 | wp_start as "H". wp_load. 23 | iModIntro. iApply "HΦ". iFrame. 24 | Qed. 25 | 26 | Lemma write_spec (l: loc) (x x': w64) : 27 | {{{ l ↦[uint64T] #x }}} 28 | #l <-[uint64T] #x' 29 | {{{ RET #(); l ↦[uint64T] #x' }}}. 30 | Proof. 31 | wp_start as "H". wp_store. 32 | iModIntro. iApply "HΦ". iFrame. 33 | Qed. 34 | 35 | ``` 36 | 37 | ## Fractional read-only permissions 38 | 39 | Especially with concurrency, we might want to make a location (that is, a variable) read-only, and in exchange it should be safe to read from multiple locations. Fractional permissions are a logical feature that permits such reasoning while remaining _sound_; it won't allow us to prove something false. 40 | 41 | The idea is to index every points-to fact with a fraction `q ∈ (0, 1]`, written `l ↦[uint64T]{#q} #x` in Iris (we'll get back to the `#q` in bit). This new permission has the following properties: 42 | 43 | - A permission can be split into fractional parts, `l ↦[uint64T]{#1} #x ⊣⊢ l ↦[uint64T]{#1/2} #x ∗ l ↦[uint64T]{#1/2} #x` (recall `⊣⊢` is like "if and only if"). 44 | - `l ↦[uint64T]{q} #x` is enough to read (note that `q > 0`, which is required for this setup to work!) 45 | - `l ↦[uint64T]{1} #x` is written `l ↦[uint64T] #x` and gives read and write permission. 46 | 47 | Let's see these principles in action in Perennial. 48 | 49 | This proof shows some features integrated into the IPM related to fractions. Most of the proofs in this file aren't that interesting, but this one has some non-obvious tricks. 50 | 51 | ```coq 52 | Lemma fraction_split l (x: w64) : 53 | l ↦[uint64T]{#1} #x ⊣⊢ l ↦[uint64T]{#(1/2)} #x ∗ l ↦[uint64T]{#(1/2)} #x. 54 | Proof. 55 | iSplit. 56 | - iIntros "H". 57 | (* [iDestruct] can split a permission into fractions, by default into halves *) 58 | iDestruct "H" as "[H1 H2]". 59 | ``` 60 | 61 | :::: info Goal diff 62 | 63 | ```txt title="goal diff" 64 | Σ : gFunctors 65 | hG : heapGS Σ 66 | l : loc 67 | x : w64 68 | ============================ 69 | "H" : l ↦[uint64T] #x // [!code --] 70 | "H1" : l ↦[uint64T]{#1 / 2} #x // [!code ++] 71 | "H2" : l ↦[uint64T]{#1 / 2} #x // [!code ++] 72 | --------------------------------------∗ 73 | l ↦[uint64T]{#1 / 2} #x ∗ l ↦[uint64T]{#1 / 2} #x 74 | ``` 75 | 76 | :::: 77 | 78 | ```coq 79 | iFrame. 80 | - iIntros "[H1 H2]". 81 | (* [iCombine] is a tactic that does the opposite of [iDestruct] - not often 82 | needed, but especially useful when dealing with fractions *) 83 | iCombine "H1 H2" as "H". 84 | ``` 85 | 86 | :::: info Goal diff 87 | 88 | ```txt title="goal diff" 89 | Σ : gFunctors 90 | hG : heapGS Σ 91 | l : loc 92 | x : w64 93 | ============================ 94 | "H1" : l ↦[uint64T]{#1 / 2} #x // [!code --] 95 | "H2" : l ↦[uint64T]{#1 / 2} #x // [!code --] 96 | "H" : l ↦[uint64T] #x // [!code ++] 97 | --------------------------------------∗ 98 | l ↦[uint64T] #x 99 | ``` 100 | 101 | :::: 102 | 103 | ```coq 104 | iFrame. 105 | Qed. 106 | 107 | ``` 108 | 109 | I said a fraction was `q ∈ (0, 1]`. This is realized with a custom type in Coq, `Qp` (the name is supposed to evoke "positive rational"). 110 | 111 | ```coq 112 | Lemma read_frac_spec l (x: w64) (q: Qp) : 113 | {{{ l ↦[uint64T]{#q} #x }}} 114 | ![uint64T] #l 115 | {{{ RET #x; l ↦[uint64T]{#q} #x }}}. 116 | Proof. 117 | wp_start as "H". wp_load. 118 | iModIntro. iApply "HΦ". iFrame. 119 | Qed. 120 | 121 | ``` 122 | 123 | The left and right hand sides of this equality parse to the same term. 124 | 125 | This is a case where we have to put `%I` to parse this using all the Iris notation. 126 | 127 | ```coq 128 | Lemma frac_1_abbreviation (l: loc) (x: w64) : 129 | (l ↦[uint64T]{#1} #x)%I = (l ↦[uint64T] #x)%I. 130 | Proof. 131 | ``` 132 | 133 | :::: info Goal 134 | 135 | ```txt title="goal 1" 136 | Σ : gFunctors 137 | hG : heapGS Σ 138 | l : loc 139 | x : w64 140 | ============================ 141 | (l ↦[uint64T] #x)%I = (l ↦[uint64T] #x)%I 142 | ``` 143 | 144 | :::: 145 | 146 | ```coq 147 | reflexivity. 148 | Qed. 149 | 150 | ``` 151 | 152 | What have we accomplished with this? We can now reason about a program that allocates a reference obtaining a full 1 permission, then "subdivides" that permission in a purely logical way (that is, no code is required to split the permission), uses those permissions in multiple threads, and then even re-combines them to get back to a full 1 permission and does some writes. 153 | 154 | Furthermore, we don't have to split just `1` into `1/2 + 1/2`; a thread with a `1/2` permission can subdivide it again, as many times as needed. 155 | 156 | ## Discardable fractions: fully read-only permission 157 | 158 | In some situations a pointer is only ever going to be read-only. It would be nice to take advantage of this fact. 159 | 160 | It is possible to work with the permission `ro_ptsto l x := (∃ q, l ↦[uint64T]{#q} #x)`. `ro_ptsto` can be split into as many copies as needed. However, in Iris it is convenient to have a _persistent_ proposition, and unfortunately `ro_ptsto` is not persistent. 161 | 162 | As recent development in Iris called _discardable fractions_ has enabled persistent, fractional permissions, by changing how fractions are represented. 163 | 164 | The intuition is to create a new type `dfrac` that replaces `Qp` (recall that was a positive rational number). Intuitively, a `dfrac` is still like a positive rational, but it can also have a special "ε" value that represents an infinitesimal (but positive) fraction. It will be possible to obtain an "ε" fraction by _discarding_ some fraction, making it permanently impossible to recover the 1 permission, but in exchange getting persistent read-only permissions. 165 | 166 | Let's see how this is realized in Iris. 167 | 168 | First, the normal permissions are written `DfracOwn q` (with `q : Qp`). Above we're using a notation with `#` that accomplishes the same thing, hence why we wrote `#1` and `#(1/2)`. 169 | 170 | Second, the ε permission is actually written `DfracDiscarded`, and more commonly has notations written with `□` (because it's persistent), e.g., `l ↦[uint64T]□ #v`. 171 | 172 | Third, discarding the fraction involves an Iris "update", a change in ghost state. 173 | 174 | ## Discardable fractions in proofs 175 | 176 | ```coq 177 | Lemma alloc_ro_spec (x: w64) : 178 | {{{ True }}} 179 | ref_to uint64T #x 180 | {{{ (l: loc), RET (#l: val); l ↦[uint64T]□ #x }}}. 181 | Proof. 182 | (* This proof is a bit odd because it's just a single allocation, so the 183 | tactics don't quite do the right thing. We'll need to do the work of 184 | [wp_start] manually, and to use [rewrite -wp_fupd] to be able to use [iMod] 185 | when needed. |*) 186 | iIntros (Φ) "_ HΦ". 187 | rewrite -wp_fupd. 188 | wp_alloc l as "H". 189 | 190 | ``` 191 | 192 | This is the step where we persist the points-to permission and turn it into a persistent, read-only fact. Also notice that the output (renamed to "Hro" for clarity) is put into the persistent context. 193 | 194 | ```coq 195 | iMod (struct_pointsto_persist with "H") as "#Hro". 196 | ``` 197 | 198 | :::: info Goal diff 199 | 200 | ```txt title="goal diff" 201 | Σ : gFunctors 202 | hG : heapGS Σ 203 | x : w64 204 | Φ : val → iPropI Σ 205 | l : loc 206 | ============================ 207 | "Hro" : l ↦[uint64T]□ #x // [!code ++] 208 | --------------------------------------□ // [!code ++] 209 | "HΦ" : ∀ l0 : loc, l0 ↦[uint64T]□ #x -∗ Φ #l0 210 | "H" : l ↦[uint64T] #x // [!code --] 211 | --------------------------------------∗ 212 | |={⊤}=> Φ #l 213 | ``` 214 | 215 | :::: 216 | 217 | ```coq 218 | iModIntro. 219 | iApply "HΦ". 220 | iFrame "Hro". 221 | Qed. 222 | 223 | ``` 224 | 225 | With a persistent permission, it's reasonable (and expected) that the permission need not be returned in the postcondition. 226 | 227 | ```coq 228 | Lemma read_discarded_spec (l: loc) (x: w64) : 229 | {{{ l ↦[uint64T]□ #x }}} 230 | ![uint64T] #l 231 | {{{ RET #x; True }}}. 232 | Proof. 233 | wp_start as "#H". 234 | wp_apply (wp_LoadAt with "H"). iIntros "_". 235 | iApply "HΦ". auto. 236 | Qed. 237 | 238 | ``` 239 | -------------------------------------------------------------------------------- /docs/notes/program-proofs/input.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 1 3 | --- 4 | 5 | # Inputting special symbols 6 | 7 | One early question when you start using Perennial (and Iris, which it is based on) is "how do you type all those funny symbols?". 8 | 9 | The answer is that these are simply Unicode characters, and we have some IDE setup to input them by typing their typical LaTeX commands; when I type `\forall` it automatically turns into `∀` (in a Coq file). 10 | 11 | For this class, the setup should be performed for VS Code via the `.vscode/settings.json` file in the assignment repo. 12 | 13 | In general (for other IDEs, for example) you should look at the [Iris editor setup instructions](https://gitlab.mpi-sws.org/iris/iris/-/blob/master/docs/editor.md?ref_type=heads). 14 | 15 | Once you have the setup, you'll need to use the right LaTeX commands. Here's a reference to the commonly used symbols, especially if you're less familiar with LaTeX. 16 | 17 | | input | symbol | meaning | 18 | | --- | --- | --- | 19 | | `\forall` | ∀ | forall quantifier | 20 | | `\exists` | ∃ | exists quantifier | 21 | | `\land` | ∧ | **l**ogical **and** | 22 | | `\lor` | ∨ | **l**ogical **or** | 23 | | `\sep` | ∗ | separating conjunction (note: not same as usual `*`) | 24 | | `\mapsto` | ↦ | points-to for separation logic | 25 | | `\lc` and `\rc` | ⌜ and ⌝ | brackets for pure propositions | 26 | | `\"o` | ö | used to type Löb | 27 | | `\union` | ∪ | map/set union | 28 | 29 | Here are some notations that you don't have to use because they have ASCII equivalents, but you will see in existing code. 30 | 31 | | input | symbol | meaning | ASCII | 32 | | -------- | ------ | --------------------------- | ------------------ | 33 | | `\leq` | ≤ | less or equal | `<=` | 34 | | `\to` | → | function type | `->` | 35 | | `\named` | ∷ | used for named propositions | `::` | 36 | | `\gamma` | γ | Greek letter gamma | use any other name | 37 | 38 | Note that `∀` and `∃` are overloaded for use in Coq propositions and Iris propositions (`iProp`), and within Iris only the Unicode symbol is supported, so I recommend you stick with that. However, you can write `forall` and `exists` instead. 39 | 40 | Note that for named propositions `\named` produces the Unicode symbol `∷` but you can use `::` (two ASCII colons) instead which looks almost identical in many fonts. 41 | 42 | Similarly, `∧` can usually be written `/\` and `∨` can be written `\/`. I find in this case that the symbols are easier to type and look better. You'll also need `∨` within Iris, which doesn't support `\/` within an `iProp`. 43 | -------------------------------------------------------------------------------- /docs/notes/program-proofs/integers.md: -------------------------------------------------------------------------------- 1 | --- 2 | # Auto-generated from literate source. DO NOT EDIT. 3 | tags: literate 4 | shortTitle: Integers 5 | --- 6 | 7 | # Integers in GooseLang 8 | 9 | ```coq 10 | From sys_verif.program_proof Require Import prelude empty_ffi. 11 | From Goose.sys_verif_code Require Import functional. 12 | Section goose. 13 | Context `{hG: !heapGS Σ}. 14 | 15 | ``` 16 | 17 | You'll be pervasively working with integers in GooseLang. A few hints will help make sense of all the types and functions for reasoning about them in program proofs, in Coq. 18 | 19 | First, Goose primarily supports the Go type `uint64`, unsigned 64-bit integers. There is also support for `uint32` and preliminary support for signed integers `int64`, but it's easiest if you stick to `uint64`. Note that most Go code would use `int` as the basic integer type, which is a signed integer whose size matches the architecture. 20 | 21 | Next, the type `w64` represents a 64-bit integer value. This is used both in GooseLang and in proofs. 22 | 23 | In GooseLang, to use a w64 we have to turn it into a value. This is almost always written `#x`. Technically, this produces the term `LitV (LitInt x)`, but `LitInt` is inserted implicitly (this is the Coq's coercions feature) and `#` is notation for `LitV`. 24 | 25 | ```coq 26 | Set Printing Coercions. Unset Printing Notations. 27 | Eval simpl in (fun (x: w64) => #x). 28 | ``` 29 | 30 | :::: note Output 31 | 32 | ```txt title="coq output" 33 | = fun x : w64 => LitV (LitInt x) 34 | : forall _ : w64, val 35 | ``` 36 | 37 | :::: 38 | 39 | ```coq 40 | Unset Printing Coercions. Set Printing Notations. 41 | 42 | 43 | ``` 44 | 45 | ## w64 to Z and back 46 | 47 | Reasoning about integers is always done via their mathematical representation as a `Z`, a signed integer. 48 | 49 | - `uint.Z : w64 → Z` gives the abstract value of a concrete integer. 50 | - `W64 : Z → w64` converts an abstract value to a concrete integer. This ends up taking the value mod $2^{64}$ since w64 is bounded. 51 | - `uint.nat : w64 → nat` is a shorthand for `fun x => Z.of_nat (uint.Z x)`, for cases where you need to use a `nat`. This happens often because the list functions all require `nat` and not `Z`. (I hope to eventually fix this in a future version of GooseLang.) 52 | 53 | ## word tactic 54 | 55 | The `word` tactic is an extension of `lia` that will help you prove goals involving words and `uint.Z`, including handling overflow reasoning. 56 | 57 | The word tactic, among other things, uses lemmas like `word.unsigned_add` and `word.unsigned_mul` to reason about the value of `uint.Z (word.add x y)` and `uint.Z (word.mul x y)`. In some cases you may want to `rewrite` with those lemmas directly, especially if you want to do something unusual with overflow, or if `word` isn't able to prove that overflow doesn't occur. 58 | 59 | ## An example specification 60 | 61 | You can see all of this integer reasoning in action in this proof of `Add`, a function which takes two `uint64`s and just returns their sum. 62 | 63 | We need `%Z` in the postcondition to force parsing using the Z notations (`+` is overloaded and is otherwise interpreted as something for types). 64 | 65 | This style of postcondition is common with integers: we say there exists a `z: w64` that is returned, and then describe `uint.Z z`. This avoids having to specifically write down how the word itself is constructed, and we can just use operations on Z. 66 | 67 | ```coq 68 | Lemma wp_Add_bounded (x y: w64) : 69 | {{{ ⌜uint.Z x + uint.Z y < 2^64⌝ }}} 70 | Add #x #y 71 | {{{ (z: w64), RET #z; ⌜uint.Z z = (uint.Z x + uint.Z y)%Z⌝ }}}. 72 | Proof. 73 | wp_start as "%Hbound". 74 | wp_pures. 75 | ``` 76 | 77 | :::: info Goal 78 | 79 | ```txt title="goal 1" 80 | Σ : gFunctors 81 | hG : heapGS Σ 82 | x, y : w64 83 | Φ : val → iPropI Σ 84 | Hbound : uint.Z x + uint.Z y < 2 ^ 64 85 | ============================ 86 | "HΦ" : ∀ z : w64, ⌜uint.Z z = (uint.Z x + uint.Z y)%Z⌝ -∗ Φ #z 87 | --------------------------------------∗ 88 | |==>​ Φ #(word.add x y) 89 | ``` 90 | 91 | :::: 92 | 93 | You can see in this goal that the specific word being returned is `word.add x y`. 94 | 95 | ```coq 96 | iModIntro. 97 | iApply "HΦ". 98 | iPureIntro. 99 | ``` 100 | 101 | :::: info Goal 102 | 103 | ```txt title="goal 1" 104 | Σ : gFunctors 105 | hG : heapGS Σ 106 | x, y : w64 107 | Φ : val → iPropI Σ 108 | Hbound : uint.Z x + uint.Z y < 2 ^ 64 109 | ============================ 110 | uint.Z (word.add x y) = uint.Z x + uint.Z y 111 | ``` 112 | 113 | :::: 114 | 115 | This goal is only true because the sum doesn't overflow - in general `uint.Z (word.add x y) = (uint.Z x + uint.Z y) % 2^64`. 116 | 117 | ```coq 118 | word. 119 | Qed. 120 | 121 | ``` 122 | 123 | If for whatever reason you want to just specify the exact word being returned, you can use `word.add` directly, but it's not common to want this. 124 | 125 | ```coq 126 | Lemma wp_Add_general (x y: w64) : 127 | {{{ True }}} 128 | Add #x #y 129 | {{{ RET #(LitInt (word.add x y)); True }}}. 130 | Proof. 131 | wp_start as "_". wp_pures. 132 | iModIntro. iApply "HΦ". done. 133 | Qed. 134 | 135 | ``` 136 | 137 | ## Pointers to integers 138 | 139 | You'll also see `uint64T` used as an argument as in `l ↦[uint64T] #x` and `own_slice s q uint64T xs`. This is a GooseLang type that represents the Go type `uint64`. 140 | 141 | ```coq 142 | End goose. 143 | ``` 144 | -------------------------------------------------------------------------------- /docs/notes/program-proofs/named.md: -------------------------------------------------------------------------------- 1 | --- 2 | # Auto-generated from literate source. DO NOT EDIT. 3 | tags: 4 | - literate 5 | --- 6 | 7 | ```coq 8 | From sys_verif.program_proof Require Import prelude empty_ffi. 9 | 10 | ``` 11 | 12 | # Named propositions 13 | 14 | We used _named propositions_ in Iris, which have the syntax `"H" :: P` where `P: iProp Σ`. The name in a named proposition has no effect on its meaning; it's only a convenience for using the proposition. 15 | 16 | The basic idea is that a proposition `"HP" :: P ∗ "HQ" :: Q` is like `P ∗ Q`, except that if you used the `iNamed` to destruct it, it "knows" to name the conjuncts `"HP"` and `"HQ"`. Putting the names in the definitions has three advantages: 17 | 18 | - The names are often easier to come up with right next to the statements in the definition rather than when you're destructing something. 19 | - If you destruct the same definition several times in different proofs, it's convenient to right the names once and nice to have consistency across proofs. 20 | - If a definition changes its easier to add some new names for the new conjuncts and update the proofs compared to changing all the `iDestruct` calls. 21 | 22 | Named propositions are typically used in three places: 23 | 24 | - In definitions (especially representation predicates), to name each conjunct. 25 | - In loop invariants, again to name the conjuncts. 26 | - After `iApply struct_fields_split`, the resulting hypothesis internally uses named propositions to automatically name the conjuncts according to the field names. 27 | 28 | The names are actually Iris intro patterns. We tend to use this power in only two ways: plain old `"HP"` is an intro pattern that just names the conjunct, and `"%H"` is an intro pattern that produces the Coq hypothesis `H` rather than a separation logic hypothesis. 29 | 30 | See the documentation in the source code at [named_props.v](https://github.com/tchajed/iris-named-props/blob/main/src/named_props.v) for more details on the API. 31 | 32 | You may see a use of `iNamed 1`, which is just `iIntros "Hfresh"; iNamed "Hfresh"`; if the goal is `foo -∗ ...` it applied the named struct to `foo` after introducing it. (This syntax may seem mysterious but it mirrors a Coq feature where `destruct 1` will destruct the first premise if the goal is `P → ...`.) 33 | 34 | ```coq 35 | Section goose. 36 | Context `{!heapGS Σ}. 37 | 38 | Lemma simple_inamed (P Q: iProp Σ) : 39 | ("HP" ∷ P ∗ "HQ" ∷ Q) -∗ P. 40 | Proof. 41 | iIntros "H". iNamed "H". 42 | ``` 43 | 44 | :::: info Goal diff 45 | 46 | ```txt title="goal diff" 47 | Σ : gFunctors 48 | heapGS0 : heapGS Σ 49 | P, Q : iProp Σ 50 | ============================ 51 | "H" : "HP" ∷ P ∗ "HQ" ∷ Q // [!code --] 52 | "HP" : P // [!code ++] 53 | "HQ" : Q // [!code ++] 54 | --------------------------------------∗ 55 | P 56 | ``` 57 | 58 | :::: 59 | 60 | ```coq 61 | iExact "HP". 62 | Qed. 63 | 64 | Definition own_foo l: iProp Σ := 65 | ∃ (p r: w64), 66 | "HP" :: l ↦[uint64T] #p ∗ 67 | "HR" :: (l +ₗ 2) ↦[uint64T] #r ∗ 68 | "%Hbound" :: ⌜uint.Z p < uint.Z r⌝. 69 | 70 | Lemma own_foo_read_P l : 71 | own_foo l -∗ ∃ (p: w64), l ↦[uint64T] #p. 72 | Proof. 73 | iIntros "H". 74 | iNamed "H". 75 | ``` 76 | 77 | :::: info Goal diff 78 | 79 | ```txt title="goal diff" 80 | Σ : gFunctors 81 | heapGS0 : heapGS Σ 82 | l : loc 83 | p, r : w64 // [!code ++] 84 | Hbound : uint.Z p < uint.Z r // [!code ++] 85 | ============================ 86 | "H" : own_foo l // [!code --] 87 | "HP" : l ↦[uint64T] #p // [!code ++] 88 | "HR" : (l +ₗ 2) ↦[uint64T] #r // [!code ++] 89 | --------------------------------------∗ 90 | ∃ p : w64, l ↦[uint64T] #p // [!code --] 91 | ∃ p0 : w64, l ↦[uint64T] #p0 // [!code ++] 92 | ``` 93 | 94 | :::: 95 | 96 | ```coq 97 | iFrame. 98 | Qed. 99 | 100 | End goose. 101 | ``` 102 | -------------------------------------------------------------------------------- /docs/notes/program-proofs/notation.md: -------------------------------------------------------------------------------- 1 | --- 2 | # Auto-generated from literate source. DO NOT EDIT. 3 | tags: literate 4 | --- 5 | 6 | # Notation 7 | 8 | Coq has a very general mechanism for extending its syntax with what are called "notations". Even if you don't write your own notations, you'll work with them, so it's helpful to understand some aspects of their implementation. 9 | 10 | For more details beyond this document you can read the Coq reference manual documentation on [syntax extensions](https://coq.inria.fr/doc/master/refman/user-extensions/syntax-extensions.html). 11 | 12 | ## A first example 13 | 14 | Let's start with an example; we'll write our own list type `List` and give it notations with a slightly different syntax than the `[1; 2; 3]` notation for normal `list`s. Ignore the 6 commands of setup for now and look at the overall effect in the examples. 15 | 16 | ```coq 17 | Inductive List {A:Type} := 18 | | Nil 19 | | Cons (x: A) (l: List). 20 | Arguments List A : clear implicits. 21 | 22 | Declare Scope angle_list_scope. 23 | Delimit Scope angle_list_scope with A. 24 | Open Scope angle_list_scope. 25 | 26 | Notation "<>" := Nil : angle_list_scope. 27 | Notation "<< x >>" := (Cons x Nil) : angle_list_scope. 28 | Notation "<< x ; y ; .. ; z >>" := (Cons x (Cons y .. (Cons z Nil) .. )) 29 | (format "<< '[' x ; '/' y ; '/' .. ; '/' z ']' >>") : angle_list_scope . 30 | 31 | ``` 32 | 33 | The notations have extended Coq's syntax: 34 | 35 | ```coq 36 | Definition ex1: List bool := <>. 37 | Definition ex2: List Z := << 1; 34; 4; 7 >>. 38 | 39 | ``` 40 | 41 | Not only that, but Coq will use these notations in _reverse_ for printing as well: 42 | 43 | ```coq 44 | Print ex2. 45 | ``` 46 | 47 | :::: note Output 48 | 49 | ```txt title="coq output" 50 | ex2 = <<1; 34; 4; 7>> 51 | : List Z 52 | ``` 53 | 54 | :::: 55 | 56 | The notations are truly being used for printing; it's not just that printing `ex2` shows how the term was defined. Here's an example where we write a list without the notation but it gets printed with it: 57 | 58 | ```coq 59 | Check (Cons 1 (Cons 3 (Cons 7 Nil))). 60 | ``` 61 | 62 | :::: note Output 63 | 64 | ```txt title="coq output" 65 | <<1; 3; 7>> 66 | : List Z 67 | ``` 68 | 69 | :::: 70 | 71 | ## How notations work 72 | 73 | The most important aspect of notations to remember is that they are organized into _scopes_ so they can be selectively enabled and to pick between two different interpretations of the same notation. For the notations above, we used `Declare Scope angle_list_scope` to create a new scope. 74 | 75 | A typical example is the numeric notations, where the notation for `nat` numbers is defined in scope `nat_scope` while for `Z` is defined in `Z_scope`. There is a **scope stack** that determines what scopes are "open" and in what order, which determines what notation is used by default. For the notations above, we wanted them to be available right away so we used `Open Scope angle_list_scope`. 76 | 77 | Currently, due to the import of Zarith at the top of the file, numbers are parsed as `Z`. The syntax `e%nat` parses `e` but with `nat` as the first scope, which allows us to override the default temporarily to parse a literal as a `nat`. 78 | 79 | ```coq 80 | Check 3 : Z. 81 | Check 3%nat : nat. 82 | 83 | ``` 84 | 85 | Unfortunately this override mechanism requires yet another concept of a **scope delimiting key**. The above command `Delimit Scope angle_list_scope with A` says that `%A` should be used for `angle_list_scope`; similarly `%nat` is used for `nat_scope` and `%Z` is used for `%Z_scope`. If we didn't have `angle_list_scope` open, we could still use it with the delimiting key: 86 | 87 | ```coq 88 | Close Scope angle_list_scope. 89 | 90 | Fail Definition ex3 := << 3 >>. 91 | Definition ex3': List Z := << 3 >>%A. 92 | 93 | (* the scope is used for the whole expression, so we don't have to put `%nat` on 94 | each number individually. *) 95 | Definition ex4: List nat := << 3; 4; 5 >>%nat%A. 96 | 97 | ``` 98 | 99 | There are a few more details about notations you should know, although I won't detail how to use them. 100 | 101 | Some of the syntax you use routinely in Coq is actually not built-in but provided as notations; for example, the pair notation `(x, y)` (actually even the `pair` data type isn't built-in). The most surprising one might be that `A -> B` is actually notation for `forall (_: A), B`, but this starts getting into dependent types which I won't explain here. 102 | 103 | Notations extend the Coq parser (which is based on the camlp5 library) at runtime. This makes it complicated to describe what's required for notations to be parseable (without ambiguity for example), and to give good error messages. One aspect that might help is to note that when notations are created, they create new tokens in the parser (really, the lexer) for the sequences of symbols (like `<<` and `>>` above) and any newly-created keywords. 104 | 105 | Notations can be recursive, like the `<< x ; y ; .. ; z >>` list notation above. 106 | 107 | The way notations are printed can be customized; specifically, we can control whitespace, how line breaks are inserted, and indentation if a notation overflows a line while printing. This is what enables large multi-line notations that are still printed nicely. You can actually see an example of such control above in the `format` specifier for the recursive list notation. That one is copied from Coq's built-in list notation; one of the things it does is to break up a long list by putting a newline between elements, after each semicolon, and without indentation. 108 | 109 | Notations are by default used for printing and parsing, as we saw above. They can be defined as _parsing only_ or _printing only_ instead. We might have one general notation used for printing things, but several shorthands that are useful to save typing. Printing-only notations are a bit more obscure. In the Iris Proof Mode they're used to create the goal displays you see. It turns out the goals are just an ordinary Coq proposition that the IPM tactics manipulate, but with a (fairly fancy) printing-only notation. 110 | 111 | The numeric notations are subject to scoping, but the way they're defined uses some features specific to parsing numbers; obviously there isn't a `Notation` command for every possible number literal. See the documentation on [number notations](https://coq.inria.fr/doc/master/refman/user-extensions/syntax-extensions.html#number-notations) for the interface Coq provides. 112 | 113 | ## Controlling notation scope 114 | 115 | Coq has some features that allow controlling the notation scope without requiring an annotation like `%nat`. Unfortunately, these can be a bit confusing if you don't know about them, since they silently change how things are parsed. 116 | 117 | The first is that notation scopes can be attached to types. This has already been done for `nat`, but here's the command that does it: 118 | 119 | ```coq 120 | Bind Scope nat_scope with nat. 121 | 122 | ``` 123 | 124 | Now, if an argument to a function is known to be of type nat (because of the function's type), then the expression we put there will be parsed in `nat_scope`. In the following example, the constant `3` would normally be parsed as a Z except for the type of `Nat.add` and the scope binding. 125 | 126 | ```coq 127 | Definition use_nat_add (x: nat) := Nat.add x 3. 128 | 129 | ``` 130 | 131 | This example uses the same feature in a slightly different way. The notation `x + y` is ambiguous (it could be `Z.add` or `Nat.add`), and `Z.add` is the default. Because of the return type annotation here, `x + y` gets parsed as a `nat` addition. 132 | 133 | ```coq 134 | Definition use_nat_plus (x y: nat): nat := x + y. 135 | ``` 136 | 137 | In Iris specifically this is commonly why we write `: iProp Σ` for proposition definitions, since it causes them to be parsed in the correct scope to disambiguate the logical operators like `∀` and `∃`; `iProp` is associated with `bi_scope`. Sometimes nothing else causes `bi_scope` to be used and you'll need to write `%I` explicitly. 138 | 139 | As one last scope control feature, it's also possible to associate a notation scope to each argument of a definition, regardless of the types involved. If you ever need to see what these are, you can use `About` which includes scope info. 140 | 141 | ```coq 142 | Definition takes_nat_scope {A: Type} (x: A) := x. 143 | Arguments takes_nat_scope {A} x%nat. 144 | Check takes_nat_scope 3 : nat. 145 | 146 | ``` 147 | 148 | The relevant part of the About output is `Arguments takes_nat_scope {A} x%nat_scope`, in which `x%nat_scope` means that argument will be parsed in `nat_scope`. 149 | 150 | ```coq 151 | About takes_nat_scope. 152 | ``` 153 | 154 | :::: note Output 155 | 156 | ```txt title="coq output" 157 | takes_nat_scope : ∀ {A : Type}, A → A 158 | 159 | takes_nat_scope is not universe polymorphic 160 | Arguments takes_nat_scope {A} x%nat_scope 161 | takes_nat_scope is transparent 162 | Expands to: Constant __input.takes_nat_scope 163 | ``` 164 | 165 | :::: 166 | -------------------------------------------------------------------------------- /docs/notes/program-proofs/stack_proof.md: -------------------------------------------------------------------------------- 1 | --- 2 | # Auto-generated from literate source. DO NOT EDIT. 3 | category: demo 4 | tags: literate 5 | order: -1 6 | pageInfo: ["Date", "Category", "Tag"] 7 | shortTitle: "Demo: stack" 8 | --- 9 | 10 | # Demo: stack data structure 11 | 12 | This is a stack used to implement a queue (using two stacks). 13 | 14 | --- 15 | 16 | ```coq 17 | From sys_verif.program_proof Require Import prelude empty_ffi. 18 | From Goose.sys_verif_code Require Import heap. 19 | 20 | Section proof. 21 | Context `{hG: !heapGS Σ}. 22 | 23 | ``` 24 | 25 | The stack representation invariant has one interesting detail: when we "push" to the stack it will go from `stack_rep l xs` to `stack_rep l (cons x xs)`, even though the code appends. This is allowed; it just requires that the logical elements of the stack are the reverse of the physical elements of the slice. 26 | 27 | Understanding this is not needed to use these specifications (by design, you do not need to read the definition of `stack_rep` to understand how to use it). However, for the purposes of the class it's important you understand the difference between the physical state and the abstract representation and why it's okay to have this `reverse` here. 28 | 29 | ```coq 30 | Definition stack_rep (l: loc) (xs: list w64): iProp Σ := 31 | ∃ (s: Slice.t), 32 | "elements" ∷ l ↦[Stack :: "elements"] (slice_val s) ∗ 33 | (* The code appends because this is both easier and more efficient, thus the 34 | code elements are reversed compared to the abstract state. *) 35 | "Hels" ∷ own_slice s uint64T (DfracOwn 1) (reverse xs). 36 | 37 | Lemma wp_NewStack : 38 | {{{ True }}} 39 | NewStack #() 40 | {{{ l, RET #l; stack_rep l [] }}}. 41 | Proof. 42 | wp_start as "_". 43 | wp_apply wp_NewSlice_0; iIntros (s) "Hels". 44 | wp_alloc l as "H". 45 | iApply struct_fields_split in "H". iNamed "H". 46 | iApply "HΦ". 47 | iFrame. 48 | Qed. 49 | 50 | Lemma wp_Stack__Push l xs (x: w64) : 51 | {{{ stack_rep l xs }}} 52 | Stack__Push #l #x 53 | {{{ RET #(); stack_rep l (x :: xs) }}}. 54 | Proof. 55 | wp_start as "Hstack". 56 | iNamed "Hstack". 57 | wp_loadField. 58 | wp_apply (wp_SliceAppend with "Hels"). iIntros (s') "Hs". 59 | wp_storeField. 60 | iModIntro. 61 | iApply "HΦ". 62 | iFrame "elements". 63 | rewrite reverse_cons. 64 | iFrame "Hs". 65 | Qed. 66 | 67 | (* It's convenient to factor out the spec for stack a bit, to deal with the way 68 | Go handles returning failure (the boolean in Pop's return value). *) 69 | Definition stack_pop (xs: list w64) : w64 * bool * list w64 := 70 | match xs with 71 | | [] => (W64 0, false, []) 72 | | x :: xs => (x, true, xs) 73 | end. 74 | 75 | Lemma stack_pop_rev (xs: list w64) : 76 | (xs = [] ∧ stack_pop (reverse xs) = (W64 0, false, [])) ∨ 77 | (∃ x xs', xs = xs' ++ [x] ∧ stack_pop (reverse xs) = (x, true, reverse xs')). 78 | Proof. 79 | induction xs using rev_ind. 80 | - simpl. left. auto. 81 | - right. exists x, xs. 82 | split; [ done | ]. 83 | rewrite reverse_app //=. 84 | Qed. 85 | 86 | Hint Rewrite @length_reverse : len. 87 | 88 | Lemma wp_Stack__Pop l xs : 89 | {{{ stack_rep l xs }}} 90 | Stack__Pop #l 91 | {{{ (x: w64) (ok: bool) xs', RET (#x, #ok); 92 | stack_rep l xs' ∗ 93 | ⌜(x, ok, xs') = stack_pop xs⌝ 94 | }}}. 95 | Proof. 96 | wp_start as "Hstack". iNamed "Hstack". 97 | wp_loadField. 98 | iDestruct (own_slice_sz with "Hels") as %Hlen. 99 | wp_apply wp_slice_len. 100 | wp_if_destruct. 101 | { 102 | wp_pures. 103 | iModIntro. 104 | iApply "HΦ". 105 | iFrame. 106 | iPureIntro. 107 | rewrite /stack_pop. 108 | assert (xs = []). 109 | { destruct xs; auto. 110 | autorewrite with len in Hlen. 111 | apply (f_equal uint.Z) in Heqb. 112 | move: Heqb. 113 | word. 114 | } 115 | subst. auto. 116 | } 117 | wp_loadField. 118 | assert (0 < length xs). 119 | { rewrite length_reverse in Hlen. word. } 120 | wp_apply wp_slice_len. 121 | wp_loadField. 122 | list_elem (reverse xs) (uint.nat (Slice.sz s) - 1)%nat as x_last. 123 | { word. } 124 | iDestruct (own_slice_split with "Hels") as "[Hels Hcap]". 125 | wp_apply (wp_SliceGet with "[$Hels]"). 126 | { iPureIntro. 127 | replace (uint.nat (word.sub (Slice.sz s) (W64 1))) with (uint.nat (Slice.sz s) - 1)%nat by word. 128 | eauto. } 129 | iIntros "Hels". 130 | wp_pures. 131 | wp_loadField. 132 | iDestruct (own_slice_split with "[$Hels $Hcap]") as "Hels". 133 | iDestruct (own_slice_wf with "Hels") as %Hcap. 134 | wp_apply wp_slice_len. 135 | wp_loadField. 136 | wp_apply (wp_SliceTake_full with "Hels"). 137 | { word. } 138 | iIntros "Hels". 139 | wp_storeField. 140 | wp_pures. 141 | iModIntro. iApply "HΦ". 142 | rewrite /stack_rep. 143 | iFrame "elements". 144 | iSplit. 145 | { 146 | replace (uint.nat (word.sub (Slice.sz s) (W64 1))) with 147 | (uint.nat (Slice.sz s) - 1)%nat by word. 148 | rewrite take_reverse. rewrite length_reverse in Hlen. 149 | replace (length xs - (uint.nat (Slice.sz s) - 1))%nat with 1%nat by word. 150 | iFrame. 151 | } 152 | iPureIntro. 153 | rewrite /stack_pop. 154 | destruct xs; auto. 155 | - exfalso. simpl in *; lia. 156 | - simpl. rewrite drop_0. 157 | f_equal. 158 | f_equal. 159 | apply reverse_lookup_Some in Hx_last_lookup as [Hget ?]. 160 | rewrite length_reverse in Hlen. 161 | simpl in Hget, Hlen. 162 | replace (length xs - (uint.nat (Slice.sz s) - 1))%nat with 0%nat 163 | in Hget by lia. 164 | simpl in Hget. 165 | congruence. 166 | Qed. 167 | 168 | End proof. 169 | ``` 170 | -------------------------------------------------------------------------------- /docs/notes/program-proofs/types.md: -------------------------------------------------------------------------------- 1 | --- 2 | # Auto-generated from literate source. DO NOT EDIT. 3 | tags: 4 | - literate 5 | - draft 6 | --- 7 | 8 | ::: warning Draft 9 | 10 | This is still very much a work-in-progress. 11 | 12 | ::: 13 | 14 | # Types in GooseLang 15 | 16 | GooseLang pervasively uses types when interacting with memory (eg, loads, stores, pointers, slices). Here we explain how those types work. 17 | 18 | ```coq 19 | (* enables defining structs with nice notation, something typically only done in 20 | auto-generated code *) 21 | Open Scope struct_scope. 22 | 23 | 24 | ``` 25 | 26 | Let's start with a classic Swap example, with pointers to integers: 27 | 28 | ```go 29 | func Swap(x *uint64, y *uint64) { 30 | old_y := *y 31 | *y = *x 32 | *x = old_y 33 | } 34 | ``` 35 | 36 | Here's what its goose translation looks like. Notice every load and store is annotated with its type, here `uint64T` for all four load/stores. 37 | 38 | ```coq 39 | Definition Swap: val := 40 | rec: <> "x" "y" := 41 | let: "old_y" := ![uint64T] "y" in 42 | "y" <-[uint64T] ![uint64T] "x";; 43 | "x" <-[uint64T] "old_y";; #(). 44 | 45 | ``` 46 | 47 | What is `uint64T`? It has type `ty`, which is a Coq definition modeling a small subset of the Go type system: 48 | 49 | ```coq 50 | Print ty. 51 | ``` 52 | 53 | :::: note Output 54 | 55 | ```txt title="coq output" 56 | Inductive ty (val_tys : val_types) : Type := 57 | baseT : typing.base_ty → ty 58 | | prodT : ty → ty → ty 59 | | listT : ty → ty 60 | | sumT : ty → ty → ty 61 | | arrowT : ty → ty → ty 62 | | arrayT : ty → ty 63 | | ptrT : ty 64 | | structRefT : list ty → ty 65 | | mapValT : ty → ty → ty 66 | | chanValT : ty → ty 67 | | extT : ext_tys → ty 68 | | prophT : ty. 69 | 70 | Arguments ty {val_tys} 71 | Arguments baseT {val_tys} t 72 | Arguments prodT {val_tys} (t1 t2)%heap_type 73 | Arguments listT {val_tys} t1%heap_type 74 | Arguments sumT {val_tys} (t1 t2)%heap_type 75 | Arguments arrowT {val_tys} (t1 t2)%heap_type 76 | Arguments arrayT {val_tys} t%heap_type 77 | Arguments ptrT {val_tys} 78 | Arguments structRefT {val_tys} ts%list_scope 79 | Arguments mapValT {val_tys} (kt vt)%heap_type 80 | Arguments chanValT {val_tys} vt%heap_type 81 | Arguments extT {val_tys} x 82 | Arguments prophT {val_tys} 83 | ``` 84 | 85 | :::: 86 | 87 | `uint64T` itself is an example of a `baseT`. Types are being used in this translation as part of the _behavior_ or _semantics_ of this program. Typically types are part of what is formally called a _type system_: types as well as a way of checking that a program's type annotations are correct. The primary goal of a type system is to catch bugs before running your code (and beyond that, a sound type system can also give a _soundness guarantee_ about programs that type check, but that isn't covered in this class). There are some secondary benefits we won't talk about here, such as better performance from compiled code, and information hiding. 88 | 89 | We do not have a type system that relates GooseLang expressions to these `ty`s. Instead, their main purpose is related to handling structs in memory, which we'll introduce next. 90 | 91 | ## Structs 92 | 93 | The purpose of types in GooseLang is to model structs, and in particular references to structs. Let's walk through that. 94 | 95 | As a running example, we'll use this Go struct: 96 | 97 | ```go 98 | type Pair struct { 99 | x uint64 100 | y uint64 101 | } 102 | ``` 103 | 104 | What goose translates this struct to is a struct _descriptor_, which is a list of fields and types for the struct. 105 | 106 | ```coq 107 | Definition Pair := struct.decl [ 108 | "x" :: uint64T; 109 | "y" :: uint64T 110 | ]. 111 | 112 | ``` 113 | 114 | TODO: oops, this is getting too much into the motivation. Explain axiomatically starting from what a struct points-to is. 115 | 116 | How is `Pair` represented as a value in GooseLang? To keep the language as simple as possible, it doesn't have a notion of a "struct value" or even fields. A `Pair` is actually represented as a tuple, with a `#()` tacked on at the end (don't worry about why - it just makes the recursive code that deals with struct fields easier). So what in Go would be `Pair { x: 2, y: 6 }` will be the value `(#2, (#6, #()))` in GooseLang. 117 | 118 | When we have a `*Pair` - that is, a pointer to a Pair - things get more interesting. The problem we need to solve is that the code can take a `p *Pair` and use `&p.x` and `&p.y` as independent pointers; this is both safe and expected in concurrent code. Thus instead of having a single heap location with the whole Pair, the model splits it into two adjacent cells, one for `x` and one for `y` (the unit `#()` is not stored), and these cells can be independently written from different threads. The actual splitting is handled in the semantics of allocation, which "flattens" values. 119 | 120 | However, if structs are flattened in memory, Goose has to emit non-trivial code for accessing fields individually, for example to model `*p.y` from the Pair pointer `p`. This is modeled by _implementing_ field access in GooseLang, using the field name "y" and the struct descriptor Pair, which together tell us that `y` will be one cell offset from the value of `p`. GooseLang has pointer arithmetic as a feature, even though it isn't a Go feature, so to read `y` we just dereference `p +ₗ 1` (the language uses the notation `+ₗ` for pointer arithmetic to distinguish it from regular addition). 121 | 122 | The actual implementation doesn't emit `p +ₗ 1`, because that would result in very hard to read GooseLang code. Instead, it emits a Gallina function `struct.loadField Pair "y" #p`, where `Pair` is exactly the descriptor above, "x" is a string that is used to find the right offset within `Pair`, and `#p` is the pointer to be loaded from. 123 | 124 | TODO: 125 | 126 | - explain how a struct is loaded 127 | - explain how points-to fact for a struct needs to account for different fields 128 | - explain how struct points-to is implemented in terms of a primitive single-cell points-to (which we never need as users) 129 | - explain how store needs to maintain the type invariant in the struct points-to and thus requires a type proof 130 | -------------------------------------------------------------------------------- /docs/resources.md: -------------------------------------------------------------------------------- 1 | --- 2 | shortTitle: "Resources" 3 | icon: "book" 4 | --- 5 | 6 | # Additional resources 7 | 8 | A core resource is the course [lecture notes](./notes/), which include some explanations and references not tied to any specific lecture. 9 | 10 | ## Coq 11 | 12 | If you want more practice I encourage you to read [Software Foundations](https://softwarefoundations.cis.upenn.edu/lf-current/toc.html) beyond the assigned chapters, and even to do additional exercises. 13 | 14 | This [Tactics Cheatsheet](https://www.cs.cornell.edu/courses/cs3110/2018sp/a5/coq-tactics-cheatsheet.html) is much more complete than the [Ltac reference](./notes/ltac.md) I wrote. As a reminder, you're allowed to use any tactic in Coq (unless specifically forbidden). 15 | 16 | For std++ sets and maps in particular, `Search` doesn't work especially well, since the definitions are so general. There I recommend looking at the coqdoc documentation for [finite sets](https://plv.mpi-sws.org/coqdoc/stdpp/stdpp.fin_sets.html) and [finite maps](https://plv.mpi-sws.org/coqdoc/stdpp/stdpp.fin_maps.html). I'll be putting together a tutorial on these as the class progresses. 17 | 18 | The [Coq reference manual](https://coq.inria.fr/doc/master/refman/index.html) can be helpful, if you know what you're looking for. You should specifically use the [tactic reference](https://coq.inria.fr/doc/master/refman/coq-tacindex.html) if you're using a built-in Coq tactic. 19 | 20 | The textbook [Certified Programming with Dependent Types (CPDT)](http://adam.chlipala.net/cpdt/cpdt.pdf) is excellent for many advanced topics. 21 | 22 | The textbook [Verified Functional Algorithms](https://softwarefoundations.cis.upenn.edu/vfa-current/index.html) (part of Software Foundations) gives detailed examples of data structure proofs using purely functional code. 23 | 24 | ## Go 25 | 26 | We will be working with Go code in this class. It will help to have at least reading familiarity with Go, which you can get by following [A Tour of Go](https://go.dev/tour/welcome/1) (you only really need the Basics and Methods chapters). 27 | 28 | To verify code, it must also be in the subset of Go supported by [Goose](https://github.com/goose-lang/goose). You'll mostly be verifying provided code so won't need to understand these restrictions, but if your project involves writing new code I'll work with you to help you write it in the Goose-supported subset. 29 | 30 | ## Iris 31 | 32 | Use the [IPM documentation](https://gitlab.mpi-sws.org/iris/iris/blob/master/docs/proof_mode.md) as a reference for the tactics. 33 | 34 | The new [Iris tutorial](https://github.com/logsem/iris-tutorial) is a Software Foundations-style introduction to Iris. It does not use GooseLang, so programs will look different from this class, but the high-level program reasoning and low-level tactics are otherwise the same. 35 | 36 | The lecture notes for [Semantics of Type Systems](https://plv.mpi-sws.org/semantics-course/lecturenotes.pdf) at MPI explains Iris "on paper". However, some background in programming language theory is needed to skip to the part on Iris and understand it. 37 | 38 | The [Iris lecture notes](https://iris-project.org/tutorial-material.html) from Aarhus University explain Iris "on paper", in a self-contained manner. However, there is no connection to Coq and some knowledge of logic is expected to understand the material. 39 | 40 | [Iris from the ground up](https://people.mpi-sws.org/~dreyer/papers/iris-ground-up/paper.pdf) is an excellent reference if you want to understand how Iris works internally (though way too much if you just want to use it). 41 | 42 | While not Iris-specific, the CS 240 notes on [program correctness](https://pages.cs.wisc.edu/~cs240-1/readings/07_Program_Correctness.pdf) give a fantastic introduction to invariants and writing specifications. (Don't be fooled by the fact that this is a 200-level classes: most discrete math classes don't cover this material, so it is likely to be new to you.) 43 | 44 | ## Papers 45 | 46 | These papers are directly relevant to the class: 47 | 48 | - [Separation Logic (CACM 2019)](https://dl.acm.org/doi/pdf/10.1145/3211968) 49 | - [Separation Logic for Sequential Programs (ICFP 2020)](https://www.chargueraud.org/research/2020/seq_seplogic/seq_seplogic.pdf) 50 | - [A beginner's guide to Iris, Coq and separation logic (Indiana University Bachelor's Thesis, 2021)](https://arxiv.org/pdf/2105.12077) 51 | - [Interactive Proofs in Higher-Order Concurrent Separation Logic](https://iris-project.org/pdfs/2017-popl-proofmode-final.pdf) 52 | 53 | These are interesting papers for verification more broadly: 54 | 55 | - [Formal verification of a realistic compiler (CACM 2009)](https://dl.acm.org/doi/pdf/10.1145/1538788.1538814) (CompCert) 56 | - [IronFleet: Proving Practical Distributed Systems Correct (SOSP 2015)](https://www.andrew.cmu.edu/user/bparno/papers/ironfleet.pdf) 57 | - [How Amazon Web Services Uses Formal Methods (CACM 2015)](https://dl.acm.org/doi/pdf/10.1145/2699417) 58 | - [BP: Formal Proofs, the Fine Print and Side Effects (IEEE SecDev 2018)](https://6826.csail.mit.edu/2020/papers/secdev2018.pdf) 59 | - [Simple High-Level Code For Cryptographic Arithmetic – With Proofs, Without Compromises (IEEE S&P 2019)](https://jasongross.github.io/papers/2019-fiat-crypto-ieee-sp.pdf) (Fiat Crypto) 60 | -------------------------------------------------------------------------------- /docs/syllabus.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: "circle-exclamation" 3 | --- 4 | 5 | # Syllabus 6 | 7 | This class is an introduction to _systems verification_. The core idea is understanding how to write a mathematical proof that a program is correct, a process called (program) _verification_. The class is called "_systems_ verification" because we will focus on techniques applicable to low-level systems software with features like pointers and concurrency (in some future version of this class I hope to also cover distributed systems, but the techniques aren't quite there yet). 8 | 9 | ::: important Read the syllabus! 10 | 11 | This syllabus is a bit long. I suggest you skim it at the beginning of class so you know what's covered, and then refer back to it if something comes up. 12 | 13 | ::: 14 | 15 | The class is divided into three sections: 16 | 17 | **Functional programs**: We'll start by learning how to write and verify _functional_ programs, a style of programming which emphasizes functions and where we won't have complications like modifying variables. This is also where we'll introduce the Coq proof assistant, which we'll use to do the proofs in this class. 18 | 19 | **Imperative programs**: Next, we'll introduce the techniques to reason about imperative programs, which can modify heap-allocated variables. We'll also increase the realism by switching to reasoning about programs written in Go. The theoretical tool that allows us to reason about the heap is separation logic. 20 | 21 | **Concurrent programs**: Finally, we'll introduce techniques to reason about concurrency. It turns out separation logic will help once again with this challenge. 22 | 23 | ## General identifying information 24 | 25 | - **Institution name:** University of Wisconsin-Madison 26 | - **Course number**: CS 839-007: Systems Verification 27 | - **Credits**: 3 credit hours 28 | - **Requisites**: mathematical maturity and programming experience (see [below](#prerequisites)) 29 | - **Meeting time and location**: Monday/Wednesday 9:30-10:45am, Engineering Hall 2349 30 | - **Instruction modality**: in-person 31 | - **Instructor contact info**: \ 32 | Prof. Tej Chajed ‹› \ 33 | office hours: Monday/Wednesday 2-3pm in CS 7361 34 | 35 | ## Course learning outcomes 36 | 37 | By the end of this class, you should be able to: 38 | 39 | 1. Prove theorems in the Coq proof assistant 40 | 2. Verify imperative programs using separation logic 41 | 3. Verify (small) concurrent programs 42 | 4. Articulate the guarantees of formal verification 43 | 44 | Hopefully you also will have had fun. 45 | 46 | ## Expected workload 47 | 48 | This class is 3 credit hours. That means you can expect to spend 3 hours in lecture and 6 hours outside of lecture each week. 49 | 50 | You'll spend most of the time on this class outside of lecture doing the assignments. There aren't many due dates, but you should still be putting in some time each week so you make steady progress and have time to ask questions, think about the material, re-read lecture notes, and get unstuck. If you start an assignment a day or two before it's due you're going to have a bad time. 51 | 52 | ## Prerequisites 53 | 54 | The two main requirements are "mathematical maturity" and "programming experience." Mathematical maturity means you're comfortable with the precision of using and learning new math, which is required to understand program proofs. Programming experience is needed since the proofs will be programs written in the Coq proof assistant (which you'll need to be able to learn efficiently). 55 | 56 | You do not need to have any experience with the Coq proof assistant. 57 | 58 | You do not need to have used Go before. We will verify code written in Go, but you won't be writing or modifying it (except optionally as part of the project). You should be able to get up-to-speed in reading Go quickly if you have some familiarity with C syntax. 59 | 60 | ## Assignments and grading 61 | 62 | There will be two programming assignments in Coq, one written theory assignment, and a final project in Coq. See the [assignments page](./assignments/) for details. 63 | 64 | Grading: 65 | 66 | - Assignment 1: 20% 67 | - Assignment 2 (theory): 15% 68 | - Assignment 3: 25% 69 | - Final project: 40% 70 | 71 | Here's a grading scale to give you a rough idea, but cutoffs may be lower if the assigned points are unexpectedly low: 72 | 73 | | grade | percentage | 74 | | ----: | ---------- | 75 | | A | $\geq$ 93 | 76 | | AB | $\geq$ 86 | 77 | | B | $\geq$ 80 | 78 | | BC | $\geq$ 74 | 79 | 80 | The idea behind grading is that if you make an earnest attempt at every assignment, and if you get stuck you're able to explain what you tried, you should get at least an AB. 81 | 82 | Hand-in: submit to Canvas. See more details in the [assignment setup](./assignments/setup) page. 83 | 84 | ## Office hours 85 | 86 | I'll hold office hours twice a week, using my office, CS 7361. Office hours are time I've blocked off for you, so please use them! You can stop by and ask whatever you want, including but not limited to: 87 | 88 | 1. Help with a Coq programming assignment 89 | 2. A conceptual question about the lecture material 90 | 3. A question about something beyond the lecture 91 | 4. Advice on anything communication related 92 | 93 | I encourage all of you to stop by office hours just to introduce yourself and say hi during the first two weeks of the semester. 94 | 95 | ## Collaboration 96 | 97 | You can work on assignments and the final project in groups of up to two. Both of you should submit, but it's okay if the submissions are identical. Please clearly state who you worked with by putting your partner's name in a comment at the top of each file you modify. 98 | 99 | The first assignment is crucial to learning Coq, so I would suggest that even if you have a partner you type out the solutions independently so you both get experience using Coq as an interactive tool. 100 | 101 | ## Generative AI policy 102 | 103 | If you use generative AI like ChatGPT or another LLM, you are required to explain how you used it. You may use GitHub CoPilot without an explanation. 104 | 105 | I do not believe ChatGPT does well on Coq proofs in general, and especially on the course material, but I would be happy to be proven wrong. 106 | 107 | You will need to read and understand what ChatGPT says. Identifying its mistakes is likely to be good for your learning, which is why it's permitted in the first place, but it is important that you not blindly follow it. 108 | 109 | ## Late policy 110 | 111 | For the programming exercises and theory assignment, you get 3 "late days" to use throughout the semester. You don't need to tell me to use them, just submit late. If you have extenuating circumstances and need more time, let me know as soon as possible. 112 | 113 | ## Student well-being 114 | 115 | I realize this class isn't the only thing in your life. If this class ever feels overwhelming on top of whatever else you're responsible for, please let me know and we can work something out. Do this early! I don't want it to stress you out, I want you to learn something. 116 | 117 | Students are encouraged to learn about and utilize UW-Madison's mental health services and/or other resources as needed. Visit [uhs.wisc.edu](https://www.uhs.wisc.edu/) or call University Health Services at (608) 265-5600 to learn more. 118 | 119 | ## Academic policies 120 | 121 | See this list of [standard syllabus statements](https://teachlearn.wisc.edu/course-syllabi/#policies) with university policies, including academic integrity, diversity & inclusion, and accommodations for students with disabilities. 122 | 123 | ## Course communication 124 | 125 | If you have any questions, you can either (a) email me the question at , (b) post on Piazza, (c) come to office hours, or (d) (if office hours don't work for you) schedule a time by sending me an email with some suggested times. 126 | 127 | ## Asking questions 128 | 129 | Asking questions is a skill, an extremely useful one. When you ask a question, you should: 130 | 131 | 1. Give sufficient context for your question. For this class, including your proof so far and current proof state (a screenshot often works for this) is helpful and often necessary for me to help. You can also all of your code as an attachment if I need to open it up and try something myself, by using `./etc/prepare-submit` and attaching `hw.tar.gz` in an email. 132 | 2. Describe what you're trying to do and your understanding of the proof state. 133 | 3. Describe what you've tried so far. 134 | 4. Aim to make your question easy to read. 135 | 136 | Some questions will be about conceptual challenges and others will be about the mechanics of using Coq. Both are fine to ask. If it's a conceptual difficulty, try to ask something that isn't too specific to the Coq code. If it's a mechanical question about Coq, try to explain your informal proof argument or how you want to manipulate the proof state - for example, if you say you have a theorem that says `∀ n, P n ∨ Q` and in a proof you want to consider the two cases `P 3` and `Q`, then I know this is a purely mechanical question about using Coq and can give you a direct answer. 137 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sys-verif-fa24", 3 | "version": "0.0.1", 4 | "description": "Systems Verification Fall 2024 course website", 5 | "license": "CC0-1.0", 6 | "type": "module", 7 | "scripts": { 8 | "build": "vuepress build docs", 9 | "clean-dev": "vuepress dev docs --clean-cache", 10 | "dev": "vuepress dev docs", 11 | "update-package": "pnpm dlx vp-update", 12 | "fmt": "prettier --write . --log-level error" 13 | }, 14 | "devDependencies": { 15 | "@vuepress/bundler-vite": "2.0.0-rc.22", 16 | "@vuepress/plugin-google-analytics": "2.0.0-rc.103", 17 | "@vuepress/plugin-search": "2.0.0-rc.102", 18 | "@vuepress/plugin-shiki": "2.0.0-rc.105", 19 | "katex": "^0.16.22", 20 | "mathjax-full": "^3.2.2", 21 | "mermaid": "^11.6.0", 22 | "prettier": "^3.5.3", 23 | "sass-embedded": "^1.89.0", 24 | "vue": "^3.5.14", 25 | "vuepress": "2.0.0-rc.22", 26 | "vuepress-theme-hope": "2.0.0-rc.87" 27 | }, 28 | "packageManager": "pnpm@10.11.0+sha512.6540583f41cc5f628eb3d9773ecee802f4f9ef9923cc45b69890fb47991d4b092964694ec3a4f738a420c918a333062c8b925d312f42e4f0c263eb603551f977" 29 | } 30 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["docs/.vuepress/**/*.ts"], 3 | "exclude": ["node_modules"], 4 | "compilerOptions": { 5 | "strict": true, 6 | "skipLibCheck": true 7 | } 8 | } 9 | --------------------------------------------------------------------------------