Skip to content

Commit a636afc

Browse files
authored
Add learning exercises (#1771)
This change adds exercises for learning SAW and integrates those exercises into the CI system so they do not go stale with future SAW changes
1 parent f9b6194 commit a636afc

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+3860
-0
lines changed

.github/workflows/ci.yml

+34
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,39 @@ jobs:
571571
chmod +x bin/*
572572
make ${{ matrix.s2n-target }}
573573
574+
exercises:
575+
name: "Test SAW exercises"
576+
needs: build
577+
runs-on: ubuntu-22.04
578+
strategy:
579+
matrix:
580+
ghc: ["8.10.7"]
581+
steps:
582+
- uses: actions/checkout@v2
583+
- run: |
584+
mkdir -p exercises/bin
585+
586+
- name: Download previously-built SAW
587+
uses: actions/download-artifact@v2
588+
with:
589+
name: "${{ runner.os }}-bins"
590+
path: ./exercises/bin
591+
592+
- uses: satackey/[email protected]
593+
continue-on-error: true
594+
595+
- shell: bash
596+
name: "make exercises container"
597+
working-directory: exercises
598+
run: docker build -t exercises .
599+
600+
- shell: bash
601+
name: "run exercises"
602+
working-directory: exercises
603+
run: |
604+
chmod +x bin/*
605+
docker run -v $PWD/bin:/saw-bin exercises
606+
574607
# Indicates sufficient CI success for the purposes of mergify merging the pull
575608
# request, see .github/mergify.yml. This is done instead of enumerating each
576609
# instance of each job in the mergify configuration for a number of reasons:
@@ -585,5 +618,6 @@ jobs:
585618
- saw-remote-api-tests
586619
- cabal-test
587620
- s2n-tests
621+
- exercises
588622
steps:
589623
- run: "true"

exercises/.dockerignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
bin

exercises/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.bc

exercises/Dockerfile

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
FROM ubuntu:22.04
2+
3+
RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections
4+
RUN apt-get update && apt-get install -y clang-12 make
5+
6+
RUN find /usr/bin/ -name "*-12" -exec basename {} \; | sed "s/\-12//" | xargs -I{} ln -s /usr/bin/'{}'-12 /usr/bin/'{}'
7+
8+
COPY . /workdir
9+
10+
ENTRYPOINT [ "/workdir/ci-entrypoint.sh" ]
11+
CMD [ "/bin/bash" ]

exercises/README.md

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# SAW Exercises #
2+
3+
This directory contains exercises to help SAW learners gain confidence in their
4+
proof writing abilities by working on simple, well-defined proofs. Each
5+
exercise folder contains a well-commented `exercise.saw` file containing
6+
problems to work on, as well as a `solutions.saw` file with a sample solution.
7+
Some exercises have more than one valid solution, though the `solutions.saw`
8+
file will only list one. We designed the exercises to be completed in
9+
following order:
10+
11+
1. `memory-safety/popcount`
12+
2. `memory-safety/swap`
13+
3. `memory-safety/u128`
14+
4. `memory-safety/point`
15+
5. `functional-correctness/popcount`
16+
6. `functional-correctness/u128`
17+
7. `functional-correctness/point`
18+
8. `functional-correctness/swap`
19+
9. `sha512`
20+
21+
You'll also find a `salsa20` exercise in the `memory-safety` and
22+
`functional-correctness` folders. Unlike the other exercises, these exercises
23+
lack an `exercise.saw` file. It is an opportunity to test what you've learned
24+
to write a proof from scratch. The `salsa20` proofs are simpler than the
25+
`sha512` proof, but the challenge comes from writing a proof without any
26+
signposting or helper functions.
27+
28+
## Building Bitcode ##
29+
30+
To run the exercises and solutions, you'll first need to build the bitcode for
31+
all of the C programs. To do this, simply run the `Makefile` in `common`:
32+
33+
```bash
34+
cd common
35+
make
36+
```
37+
38+
## Continuous Integration (CI) ##
39+
40+
To ensure these exercises stay up to date, we have integrated them with our CI
41+
system. Our CI runs the `ci-entrypoint.sh` script in a docker container
42+
defined by `Dockerfile`, which in term runs SAW over all of the exercises and
43+
solutions. This script and docker image are not intended to be used outside of
44+
a CI system.

exercises/ci-entrypoint.sh

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#!/usr/bin/env bash
2+
set -xe
3+
4+
# This script checks all of the exercise and solution files. It is only
5+
# intended to be executed by the saw-script CI system.
6+
7+
cd /workdir
8+
mkdir bin
9+
cp /saw-bin/saw bin/saw
10+
cp /saw-bin/abc bin/abc
11+
cp /saw-bin/yices bin/yices
12+
cp /saw-bin/yices-smt2 bin/yices-smt2
13+
cp /saw-bin/z3 bin/z3
14+
chmod +x bin/*
15+
16+
export PATH=/workdir/bin:$PATH
17+
18+
abc -h || true
19+
z3 --version
20+
yices --version
21+
yices-smt2 --version
22+
23+
# Build bitcode for all C programs
24+
(cd common && make)
25+
26+
# Run SAW over all exercises and solutions
27+
find . -name solution.saw -o -name exercise.saw | xargs -I % sh -c "saw % || exit 255"

exercises/common/Makefile

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
.SUFFIXES: .c .bc
2+
.PHONY: all
3+
4+
C_FILES = $(shell find .. -name '*.c')
5+
BC_FILES = $(patsubst %.c, %.bc, $(C_FILES))
6+
7+
all: $(BC_FILES)
8+
9+
%.bc : %.c
10+
clang -g -O0 -c -emit-llvm $< -o $@

exercises/common/helpers.saw

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
/*
7+
* SAW helpers
8+
*/
9+
// Given a value `v` of type `ty`, allocates and returns a pointer to memory
10+
// storing `v`
11+
let alloc_init ty v = do {
12+
p <- crucible_alloc ty;
13+
crucible_points_to p v;
14+
return p;
15+
};
16+
17+
// Given a value `v` of type `ty`, allocates and returns a read only pointer to
18+
// memory storing `v`
19+
let alloc_init_readonly ty v = do {
20+
p <- crucible_alloc_readonly ty;
21+
crucible_points_to p v;
22+
return p;
23+
};
24+
25+
// Given a name `n` and a type `ty`, allocates a fresh variable `x` of type
26+
// `ty` and returns a tuple of `x` and a pointer to `x`.
27+
let ptr_to_fresh n ty = do {
28+
x <- crucible_fresh_var n ty;
29+
p <- alloc_init ty (crucible_term x);
30+
return (x, p);
31+
};
32+
33+
// Given a name `n` and a type `ty`, allocates a fresh variable `x` of type
34+
// `ty` and returns a tuple of `x` and a read only pointer to `x`.
35+
let ptr_to_fresh_readonly n ty = do {
36+
x <- crucible_fresh_var n ty;
37+
p <- alloc_init_readonly ty (crucible_term x);
38+
return (x, p);
39+
};
40+
41+
// Given a name `n` and a value `v`, assert that the `n` has a value of `v`
42+
let global_points_to n v = do {
43+
crucible_points_to (crucible_global n) (crucible_term v);
44+
};
45+
46+
// Given a name `n` and a value `v`, declare that n is initialized, and assert that has value v
47+
let global_alloc_init n v = do {
48+
crucible_alloc_global n;
49+
global_points_to n v;
50+
};
51+
52+
// llvm integer type aliases
53+
let i8 = llvm_int 8;
54+
let i16 = llvm_int 16;
55+
let i32 = llvm_int 32;
56+
let i64 = llvm_int 64;
57+
let i128 = llvm_int 128;
58+
let i384 = llvm_int 384;
59+
let i512 = llvm_int 512;
60+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#include <stdint.h>
2+
3+
uint32_t add(uint32_t x, uint32_t y) { return x + y; }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
let add_spec = do {
2+
// Create fresh variables for `x` and `y`
3+
x <- llvm_fresh_var "x" (llvm_int 32);
4+
y <- llvm_fresh_var "y" (llvm_int 32);
5+
6+
// Invoke the function with the fresh variables
7+
llvm_execute_func [llvm_term x, llvm_term y];
8+
9+
// The function returns a value containing the sum of x and y
10+
llvm_return (llvm_term {{ x + y }});
11+
};
12+
13+
// Load LLVM bitcode to verify
14+
m <- llvm_load_module "add.bc";
15+
16+
// Verify the `add` function satisfies its specification
17+
llvm_verify m "add" [] true add_spec (do {
18+
//simplify (cryptol_ss());
19+
//print_goal;
20+
z3;
21+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
module Point where
2+
3+
type Point = {x : [32], y : [32]}
4+
5+
POINT_ZERO : Point
6+
POINT_ZERO = zero
7+
8+
point_add : Point -> Point -> Point
9+
point_add p1 p2 = { x = p1.x + p2.x, y = p1.y + p2.y }
10+
11+
point_add_commutes : Point -> Point -> Bit
12+
property point_add_commutes p1 p2 = point_add p1 p2 == point_add p2 p1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
///////////////////////////////////////////////////////////////////////////////
2+
// Exercise: Point Functional Correctness
3+
///////////////////////////////////////////////////////////////////////////////
4+
5+
6+
include "../../common/helpers.saw";
7+
import "Point.cry";
8+
9+
m <- llvm_load_module "point.bc";
10+
11+
///////////////////////////////////////////////////////////////////////////////
12+
// Part 1: Simple Point Equality
13+
///////////////////////////////////////////////////////////////////////////////
14+
15+
// Convert the memory safety spec below into a functional correctness
16+
// specification.
17+
18+
// You can specify the values in a struct by using llvm_struct_value:
19+
// llvm_struct_value [ <field_0>, <field_1>, ... <field_n> ];
20+
21+
let point_eq_spec = do {
22+
(p1, p1_ptr) <- ptr_to_fresh_readonly "p1" (llvm_struct "struct.point");
23+
(p2, p2_ptr) <- ptr_to_fresh_readonly "p2" (llvm_struct "struct.point");
24+
25+
llvm_execute_func [p1_ptr, p2_ptr];
26+
27+
ret <- llvm_fresh_var "ret" (llvm_int 1);
28+
llvm_return (llvm_term ret);
29+
};
30+
31+
point_eq_ov <- llvm_verify m "point_eq" [] true point_eq_spec z3;
32+
33+
///////////////////////////////////////////////////////////////////////////////
34+
// Part 2: Point Allocation
35+
///////////////////////////////////////////////////////////////////////////////
36+
37+
// Convert the memory safety proofs below into functional correctness proofs.
38+
39+
let point_new_spec = do {
40+
p_x <- llvm_fresh_var "p_x" (llvm_int 32);
41+
p_y <- llvm_fresh_var "p_y" (llvm_int 32);
42+
43+
llvm_execute_func [ llvm_term p_x, llvm_term p_y ];
44+
45+
(ret, ret_ptr) <- ptr_to_fresh "ret" (llvm_struct "struct.point");
46+
llvm_return ret_ptr;
47+
};
48+
49+
point_new_ov <- llvm_verify m "point_new" [] true point_new_spec z3;
50+
51+
let point_copy_spec = do {
52+
(p, p_ptr) <- ptr_to_fresh_readonly "p" (llvm_struct "struct.point");
53+
54+
llvm_execute_func [p_ptr];
55+
56+
(ret, ret_ptr) <- ptr_to_fresh "ret" (llvm_struct "struct.point");
57+
llvm_return ret_ptr;
58+
};
59+
60+
point_copy_ov <- llvm_verify m "point_copy" [point_new_ov] true point_copy_spec z3;
61+
62+
///////////////////////////////////////////////////////////////////////////////
63+
// Part 3: Point Addition
64+
///////////////////////////////////////////////////////////////////////////////
65+
66+
// Convert the memory safety proof below into a proof that the C `point_add`
67+
// function is equivalent to the Cryptol `point_add` function in Point.cry.
68+
69+
let point_add_spec = do {
70+
llvm_alloc_global "ZERO";
71+
zero_global <- llvm_fresh_var "zero_global" (llvm_struct "struct.point");
72+
llvm_points_to (llvm_global "ZERO") (llvm_term zero_global);
73+
74+
(p1, p1_ptr) <- ptr_to_fresh_readonly "p1" (llvm_struct "struct.point");
75+
(p2, p2_ptr) <- ptr_to_fresh_readonly "p2" (llvm_struct "struct.point");
76+
77+
llvm_execute_func [p1_ptr, p2_ptr];
78+
79+
(ret, ret_ptr) <- ptr_to_fresh "ret" (llvm_struct "struct.point");
80+
llvm_return ret_ptr;
81+
};
82+
83+
llvm_verify m "point_add"
84+
[point_new_ov, point_copy_ov, point_eq_ov]
85+
true
86+
point_add_spec
87+
z3;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#include <stdbool.h>
2+
#include <stdint.h>
3+
#include <stdlib.h>
4+
5+
typedef struct point {
6+
uint32_t x;
7+
uint32_t y;
8+
} point;
9+
10+
point ZERO = {0, 0};
11+
12+
// Check whether two points are equal
13+
bool point_eq(const point *p1, const point *p2) {
14+
return p1->x == p2->x && p1->y == p2-> y;
15+
}
16+
17+
// Allocate and return a new point
18+
point* point_new(uint32_t x, uint32_t y) {
19+
point* ret = malloc(sizeof(point));
20+
ret->x = x;
21+
ret->y = y;
22+
return ret;
23+
}
24+
25+
// Return a new point containing a copy of `p`
26+
point* point_copy(const point* p) {
27+
return point_new(p->x, p->y);
28+
}
29+
30+
// Add two points
31+
point* point_add(const point *p1, const point *p2) {
32+
// Save an addition by checking for zero
33+
if (point_eq(p1, &ZERO)) {
34+
return point_copy(p2);
35+
}
36+
37+
if (point_eq(p2, &ZERO)) {
38+
return point_copy(p1);
39+
}
40+
41+
return point_new(p1->x + p2->x, p1->y + p2->y);
42+
}

0 commit comments

Comments
 (0)