From a178c8e5c2f97ba80bcecabeb1730c38fce4f399 Mon Sep 17 00:00:00 2001 From: Dhruv Makwana Date: Wed, 19 Jun 2024 14:07:41 +0100 Subject: [PATCH 001/152] Fix queue pop lemma example --- ...e_pop_lemma_stages.broken.c => queue_pop_lemma_stages.c} | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) rename src/examples/{queue_pop_lemma_stages.broken.c => queue_pop_lemma_stages.c} (94%) diff --git a/src/examples/queue_pop_lemma_stages.broken.c b/src/examples/queue_pop_lemma_stages.c similarity index 94% rename from src/examples/queue_pop_lemma_stages.broken.c rename to src/examples/queue_pop_lemma_stages.c index 24e76291..66043b8d 100644 --- a/src/examples/queue_pop_lemma_stages.broken.c +++ b/src/examples/queue_pop_lemma_stages.c @@ -1,3 +1,5 @@ +#include "queue_headers.h" + // Step 1: Understand the state we have upon lemma entry accurately. // This is a sanity check that keeps your lemmas honest. @@ -65,7 +67,7 @@ ensures type_synonym result = { datatype seq after, datatype seq before } -predicate (datatype seq) Queue_pop_lemma(pointer front, pointer back, i32 popped) { +predicate (result) Queue_pop_lemma(pointer front, pointer back, i32 popped) { if (is_null(front)) { return { after: Seq_Nil{}, before: snoc(Seq_Nil{}, popped) }; } else { @@ -95,7 +97,7 @@ lemma snoc_fact_unified(pointer front, pointer back, i32 popped) requires take Q = Queue_pop_lemma(front, back, popped); ensures - take NewQ = Post(front, back, popped); + take NewQ = Queue_pop_lemma(front, back, popped); Q == NewQ; Q.after == tl(Q.before); popped == hd(Q.before); From 4660b011aea9987244620b06c916bac9e16a6294 Mon Sep 17 00:00:00 2001 From: Christopher Pulte Date: Thu, 20 Jun 2024 13:29:30 +0100 Subject: [PATCH 002/152] timeout-broken example no-longer timing out --- .../broken/{error-timeout => error-proof}/00143.timeout.c | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/example-archive/c-testsuite/broken/{error-timeout => error-proof}/00143.timeout.c (100%) diff --git a/src/example-archive/c-testsuite/broken/error-timeout/00143.timeout.c b/src/example-archive/c-testsuite/broken/error-proof/00143.timeout.c similarity index 100% rename from src/example-archive/c-testsuite/broken/error-timeout/00143.timeout.c rename to src/example-archive/c-testsuite/broken/error-proof/00143.timeout.c From adb8131f7fae57d6e460c35668dc64553180a8ad Mon Sep 17 00:00:00 2001 From: Christopher Pulte Date: Thu, 20 Jun 2024 18:26:10 +0100 Subject: [PATCH 003/152] ... and move back again. Apparently different OCaml/z3 versions behave differently for this example, as revealed by the CI. --- .../broken/{error-proof => error-timeout}/00143.timeout.c | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/example-archive/c-testsuite/broken/{error-proof => error-timeout}/00143.timeout.c (100%) diff --git a/src/example-archive/c-testsuite/broken/error-proof/00143.timeout.c b/src/example-archive/c-testsuite/broken/error-timeout/00143.timeout.c similarity index 100% rename from src/example-archive/c-testsuite/broken/error-proof/00143.timeout.c rename to src/example-archive/c-testsuite/broken/error-timeout/00143.timeout.c From 885017890bea80d9c2922c2bd9763d6cc2e97d10 Mon Sep 17 00:00:00 2001 From: Dhruv Makwana Date: Mon, 24 Jun 2024 11:59:06 +0100 Subject: [PATCH 004/152] Update README.md Fix https://github.com/rems-project/cerberus/issues/326 --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 11feeffc..a33ace09 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ -# CN-tutorial +# CN Tutorial -In order to build the tutorial, you will first need to install [asciidoctor](https://asciidoctor.org/). +View the tutorial here: https://rems-project.github.io/cn-tutorial/ + +## Building + +Install dependencies: [asciidoctor](https://asciidoctor.org/). Run `make` to produce `build/tutorial.html` and its dependencies: especially, `build/exercises/*.c` and `build/solutions/*c`. From 0bbfe645219938d66b1e3cbad25c8c44bdda3f35 Mon Sep 17 00:00:00 2001 From: Christopher Pulte Date: Tue, 25 Jun 2024 19:23:45 +0100 Subject: [PATCH 005/152] fix https://github.com/rems-project/cerberus/issues/350 --- src/tutorial.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tutorial.adoc b/src/tutorial.adoc index 043ea6e3..1a8447f0 100644 --- a/src/tutorial.adoc +++ b/src/tutorial.adoc @@ -59,7 +59,7 @@ This tutorial is a work in progress -- your suggestions are greatly appreciated! == Installing CN -To fetch and install CN, check the Cerberus repository at https://github.com/rems-project/cerberus and follow the instructions in https://github.com/rems-project/cerberus/blob/master/backend/cn/INSTALL.md[backend/cn/INSTALL.md]. +To fetch and install CN, check the Cerberus repository at https://github.com/rems-project/cerberus and follow the instructions in https://github.com/rems-project/cerberus/blob/master/backend/cn/README.md[backend/cn/README.md]. Once completed, type `+cn --help+` in your terminal to ensure CN is installed and found by your system. This should print the list of available options CN can be executed with. From 1ab85045338c6fe1e226cbc6fdb95b7596b7bfe3 Mon Sep 17 00:00:00 2001 From: Mike Dodds Date: Thu, 4 Jul 2024 12:26:43 -0700 Subject: [PATCH 006/152] Add a few more small examples and error cases (#29) --- .../broken/error-cerberus/pred_1.c | 24 ++++++ .../broken/error-crash/array_crash_2.c | 3 + .../broken/error-crash/array_crash_3.c | 7 ++ .../broken/error-proof/long_type_err_1.c | 8 ++ .../broken/error-proof/sizeof_1.c | 11 +++ .../simple-examples/working/int_to_char_1.c | 14 ++++ .../simple-examples/working/lemma_1.c | 13 +++ .../simple-examples/working/pred_2.c | 79 +++++++++++++++++++ 8 files changed, 159 insertions(+) create mode 100644 src/example-archive/simple-examples/broken/error-cerberus/pred_1.c create mode 100644 src/example-archive/simple-examples/broken/error-crash/array_crash_3.c create mode 100644 src/example-archive/simple-examples/broken/error-proof/long_type_err_1.c create mode 100644 src/example-archive/simple-examples/broken/error-proof/sizeof_1.c create mode 100644 src/example-archive/simple-examples/working/int_to_char_1.c create mode 100644 src/example-archive/simple-examples/working/lemma_1.c create mode 100644 src/example-archive/simple-examples/working/pred_2.c diff --git a/src/example-archive/simple-examples/broken/error-cerberus/pred_1.c b/src/example-archive/simple-examples/broken/error-cerberus/pred_1.c new file mode 100644 index 00000000..80c71112 --- /dev/null +++ b/src/example-archive/simple-examples/broken/error-cerberus/pred_1.c @@ -0,0 +1,24 @@ +// An example of defining a simple CN predicate. Broken as of 2024-7-4 thanks +// to a CN parsing issue. See: https://github.com/rems-project/cerberus/issues/266 + +/*@ +predicate (i32) TestMemoryEqZero_1(pointer p) { + take PVal = Owned(p); + if (PVal == 0i32) { + return 1i32; + } else { + return 0i32; + } +} +@*/ + +void pred_1(int *p) +/*@ requires + take PreP = Owned(p); + PreP == 0i32; @*/ +/*@ ensures + take TestP = TestMemoryEqZero_1(p); + TestP == 1i32; @*/ +{ + ; +} diff --git a/src/example-archive/simple-examples/broken/error-crash/array_crash_2.c b/src/example-archive/simple-examples/broken/error-crash/array_crash_2.c index 6fe73a97..bf66bdd5 100644 --- a/src/example-archive/simple-examples/broken/error-crash/array_crash_2.c +++ b/src/example-archive/simple-examples/broken/error-crash/array_crash_2.c @@ -1 +1,4 @@ +// Examples of failed array initialization. +// See: https://github.com/rems-project/cerberus/issues/253 + void a() { ({}); } diff --git a/src/example-archive/simple-examples/broken/error-crash/array_crash_3.c b/src/example-archive/simple-examples/broken/error-crash/array_crash_3.c new file mode 100644 index 00000000..e509fe43 --- /dev/null +++ b/src/example-archive/simple-examples/broken/error-crash/array_crash_3.c @@ -0,0 +1,7 @@ +// Examples of failed array initialization. +// See: https://github.com/rems-project/cerberus/issues/253 + +int a[][1][1] = {{{8}}}; + +int b[1][1] = {5, {a}}; + diff --git a/src/example-archive/simple-examples/broken/error-proof/long_type_err_1.c b/src/example-archive/simple-examples/broken/error-proof/long_type_err_1.c new file mode 100644 index 00000000..abf9b50b --- /dev/null +++ b/src/example-archive/simple-examples/broken/error-proof/long_type_err_1.c @@ -0,0 +1,8 @@ +// Example of negating a long integer literal. Broken as of 2024-7-4. +// See: https://github.com/rems-project/cerberus/issues/257 + +// Type error: +void long_type_err_1() { + -1l; + return; +} \ No newline at end of file diff --git a/src/example-archive/simple-examples/broken/error-proof/sizeof_1.c b/src/example-archive/simple-examples/broken/error-proof/sizeof_1.c new file mode 100644 index 00000000..06d1ce9d --- /dev/null +++ b/src/example-archive/simple-examples/broken/error-proof/sizeof_1.c @@ -0,0 +1,11 @@ +// Bug: throws a type error +// Derived from src/example-archive/c-testsuite/broken/error-proof/00038.err1.c +// See https://github.com/rems-project/cerberus/issues/272 + +int +main() +{ + int size1 = sizeof(int); // <- works + size1 = size1 + 1; // <- also works + int size2 = sizeof(int) + 1; // <- fails +} diff --git a/src/example-archive/simple-examples/working/int_to_char_1.c b/src/example-archive/simple-examples/working/int_to_char_1.c new file mode 100644 index 00000000..bbe6d9d1 --- /dev/null +++ b/src/example-archive/simple-examples/working/int_to_char_1.c @@ -0,0 +1,14 @@ +// Take an integer and require that it is small enough to represent as a char. +// Then safely store the result in a char variable. Without the requires clause +// this would be UB + +void +int_to_char_1(int a) +/*@ requires + (i32) MINu8() <= (i32) a; + (i32) a <= (i32) MAXu8(); @*/ +{ + char b; + b = a; + return; +} \ No newline at end of file diff --git a/src/example-archive/simple-examples/working/lemma_1.c b/src/example-archive/simple-examples/working/lemma_1.c new file mode 100644 index 00000000..bc701fee --- /dev/null +++ b/src/example-archive/simple-examples/working/lemma_1.c @@ -0,0 +1,13 @@ +// An example of defining and applying a trivial lemma + +/*@ + lemma lem_trivial () + requires true; + ensures true; +@*/ + +void lemma_1() +{ + /*@ apply lem_trivial(); @*/ + ; +} \ No newline at end of file diff --git a/src/example-archive/simple-examples/working/pred_2.c b/src/example-archive/simple-examples/working/pred_2.c new file mode 100644 index 00000000..cee0756f --- /dev/null +++ b/src/example-archive/simple-examples/working/pred_2.c @@ -0,0 +1,79 @@ +// Examples encoding control-flow for predicates. These are a contrived to work +// around a CN parser issue. See https://github.com/rems-project/cerberus/issues/266 + +// Variant 1 - this works: +/*@ +predicate (i32) TestMemoryEqZero_2_var1(pointer p) { + take PVal = Owned(p); + let rval = test_if_zero(PVal); + return rval; +} + +function (i32) test_if_zero(i32 x) { + if (x == 0i32) { + 1i32 + } else { + 0i32 + } +} +@*/ + +void pred_2_var1(int *p) +/*@ requires + take PreP = Owned(p); + PreP == 0i32; @*/ +/*@ ensures + take TestP = TestMemoryEqZero_2_var1(p); + TestP == 1i32; @*/ +{ + ; +} + +// Variant 2 - this works: +/*@ +predicate (i32) TestMemoryEqZero_2_var2(pointer p) { + take PVal = Owned(p); + take rval = TestMemoryEqZero_2_Helper(p, PVal); + return rval; +} + +predicate (i32) TestMemoryEqZero_2_Helper(pointer p, i32 x) { + if (x == 0i32) { + return 1i32; + } else { + return 0i32; + } +} +@*/ + +void pred_2_var2(int *p) +/*@ requires + take PreP = Owned(p); + PreP == 0i32; @*/ +/*@ ensures + take TestP = TestMemoryEqZero_2_var2(p); + TestP == 1i32; @*/ +{ + ; +} + +// Variant 3 - this works: +/*@ +predicate (i32) TestMemoryEqZero_2_var3(pointer p) { + take PVal = Owned(p); + let rval = (PVal == 0i32 ? 1i32 : 0i32); + return rval; +} +@*/ + +void pred_2_var3(int *p) +/*@ requires + take PreP = Owned(p); + PreP == 0i32; @*/ +/*@ ensures + take TestP = TestMemoryEqZero_2_var3(p); + TestP == 1i32; @*/ +{ + ; +} + From f5adb3d96c374f9d2a77b332057675d328252517 Mon Sep 17 00:00:00 2001 From: Christopher Pulte Date: Thu, 4 Jul 2024 22:42:23 +0100 Subject: [PATCH 007/152] recategorise files with different timeout behaviour due to switch to CN SMTLIB backend --- .../00011_dependen_specifications.c | 0 .../broken/{error-crash => error-timeout}/00005_bit_switch.c | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/example-archive/java_program_verification_challenges/broken/{error-timeout => error-proof}/00011_dependen_specifications.c (100%) rename src/example-archive/java_program_verification_challenges/broken/{error-crash => error-timeout}/00005_bit_switch.c (100%) diff --git a/src/example-archive/java_program_verification_challenges/broken/error-timeout/00011_dependen_specifications.c b/src/example-archive/java_program_verification_challenges/broken/error-proof/00011_dependen_specifications.c similarity index 100% rename from src/example-archive/java_program_verification_challenges/broken/error-timeout/00011_dependen_specifications.c rename to src/example-archive/java_program_verification_challenges/broken/error-proof/00011_dependen_specifications.c diff --git a/src/example-archive/java_program_verification_challenges/broken/error-crash/00005_bit_switch.c b/src/example-archive/java_program_verification_challenges/broken/error-timeout/00005_bit_switch.c similarity index 100% rename from src/example-archive/java_program_verification_challenges/broken/error-crash/00005_bit_switch.c rename to src/example-archive/java_program_verification_challenges/broken/error-timeout/00005_bit_switch.c From 204c33074aa6140c82fbad3ddeda53f051164974 Mon Sep 17 00:00:00 2001 From: septract Date: Thu, 4 Jul 2024 18:24:19 -0700 Subject: [PATCH 008/152] Make the build script fail correctly on build errors --- .github/workflows/deploy-to-web.yml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/deploy-to-web.yml b/.github/workflows/deploy-to-web.yml index 9aac8514..9c970110 100644 --- a/.github/workflows/deploy-to-web.yml +++ b/.github/workflows/deploy-to-web.yml @@ -30,11 +30,14 @@ jobs: - name: Install AsciiDoctor run: gem install asciidoctor - - name: Clean and build the tutorial - run: | - rm -rf build/* - make - mv build/tutorial.html build/index.html + - name: Clean the build directory + run: rm -rf build/* + + - name: Build the tutorial + run: make + + - name: Move the tutorial file + run: mv build/tutorial.html build/index.html - name: Setup Pages uses: actions/configure-pages@v5 From 3340e3be2186d26847bba8fc047ac0a9b66faa18 Mon Sep 17 00:00:00 2001 From: Mike Dodds Date: Fri, 5 Jul 2024 10:39:19 -0700 Subject: [PATCH 009/152] Add negative examples in directory `should-fail` (#28) --- src/example-archive/README.md | 1 + src/example-archive/check-all.sh | 2 +- src/example-archive/should-fail/README.md | 2 ++ .../should-fail/broken/error-proof/arith_neg_1.c | 9 +++++++++ .../broken/error-proof/memory_neg_1.c | 8 ++++++++ .../broken/error-proof/overflow_neg_1.c | 9 +++++++++ .../broken/error-proof/overflow_neg_2.c | 9 +++++++++ .../broken/error-proof/ownership_neg_1.c | 10 ++++++++++ .../broken/error-proof/ownership_neg_2.c | 10 ++++++++++ .../broken/error-proof/ownership_neg_3.c | 12 ++++++++++++ .../broken/error-proof/trivial_neg_1.c | 8 ++++++++ .../broken/error-proof/ownership_1.c | 16 ++++++++++++++++ 12 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 src/example-archive/should-fail/README.md create mode 100644 src/example-archive/should-fail/broken/error-proof/arith_neg_1.c create mode 100644 src/example-archive/should-fail/broken/error-proof/memory_neg_1.c create mode 100644 src/example-archive/should-fail/broken/error-proof/overflow_neg_1.c create mode 100644 src/example-archive/should-fail/broken/error-proof/overflow_neg_2.c create mode 100644 src/example-archive/should-fail/broken/error-proof/ownership_neg_1.c create mode 100644 src/example-archive/should-fail/broken/error-proof/ownership_neg_2.c create mode 100644 src/example-archive/should-fail/broken/error-proof/ownership_neg_3.c create mode 100644 src/example-archive/should-fail/broken/error-proof/trivial_neg_1.c create mode 100644 src/example-archive/simple-examples/broken/error-proof/ownership_1.c diff --git a/src/example-archive/README.md b/src/example-archive/README.md index 22a7d058..2f271f21 100644 --- a/src/example-archive/README.md +++ b/src/example-archive/README.md @@ -9,6 +9,7 @@ This directory contains examples for CN. Each subdirectory contains examples fro [here](https://dafny.org/dafny/OnlineTutorial/guide.html). * `Rust` - C versions of Rust programs, with CN annotations that provide the same guarantees as the Rust type-checker. * `SAW` - Examples derived from the [Software Analysis Workbench (SAW)](https://saw.galois.com) repository and tutorial. +* `should-fail` - Small examples that should always fail. * `open-sut` - Examples inspired by the VERSE Open System Under Test (Open SUT). ## Organization diff --git a/src/example-archive/check-all.sh b/src/example-archive/check-all.sh index 4f712ac3..5bbe1b38 100755 --- a/src/example-archive/check-all.sh +++ b/src/example-archive/check-all.sh @@ -12,7 +12,7 @@ subdirs=( "c-testsuite" "dafny-tutorial" "java_program_verification_challenges" - "negative-examples" + "should-fail" "open-sut" "Rust" "SAW" diff --git a/src/example-archive/should-fail/README.md b/src/example-archive/should-fail/README.md new file mode 100644 index 00000000..49e793ab --- /dev/null +++ b/src/example-archive/should-fail/README.md @@ -0,0 +1,2 @@ +This folder contains deliberately incorrect proofs, which are intended to serve +as negative test cases for CN. \ No newline at end of file diff --git a/src/example-archive/should-fail/broken/error-proof/arith_neg_1.c b/src/example-archive/should-fail/broken/error-proof/arith_neg_1.c new file mode 100644 index 00000000..01ef29d6 --- /dev/null +++ b/src/example-archive/should-fail/broken/error-proof/arith_neg_1.c @@ -0,0 +1,9 @@ +// Negative test case: proof should fail + +// The specification claims the function returns a non-zero value, but the +// implementation returns zero. +int arith_neg_1() +/*@ ensures return != 0i32; @*/ +{ + return 0; +} \ No newline at end of file diff --git a/src/example-archive/should-fail/broken/error-proof/memory_neg_1.c b/src/example-archive/should-fail/broken/error-proof/memory_neg_1.c new file mode 100644 index 00000000..1e22d86c --- /dev/null +++ b/src/example-archive/should-fail/broken/error-proof/memory_neg_1.c @@ -0,0 +1,8 @@ +// Negative test case: proof should fail + +// The implementation writes to memory location *p, but this location is not +// included in the specification +void memory_neg_1( int *p ) +{ + *p = 1; +} \ No newline at end of file diff --git a/src/example-archive/should-fail/broken/error-proof/overflow_neg_1.c b/src/example-archive/should-fail/broken/error-proof/overflow_neg_1.c new file mode 100644 index 00000000..f3a4088a --- /dev/null +++ b/src/example-archive/should-fail/broken/error-proof/overflow_neg_1.c @@ -0,0 +1,9 @@ +// Negative test case: proof should fail + +// The precondition constraints i to be the maximum allowed value of an i32. The +// function increments this value, which overflows the value and causes UB +void overflow_neg_1(int i) +/*@ requires i == MAXi32(); @*/ +{ + i = i + 1; +} diff --git a/src/example-archive/should-fail/broken/error-proof/overflow_neg_2.c b/src/example-archive/should-fail/broken/error-proof/overflow_neg_2.c new file mode 100644 index 00000000..3542e353 --- /dev/null +++ b/src/example-archive/should-fail/broken/error-proof/overflow_neg_2.c @@ -0,0 +1,9 @@ +// Negative test case: proof should fail + +// The precondition constraints i to be the minimum allowed value of an i32. The +// function decrements this value, which overflows the value and causes UB +void overflow_neg_2(int i) +/*@ requires i == MINi32(); @*/ +{ + i = i - 1; +} \ No newline at end of file diff --git a/src/example-archive/should-fail/broken/error-proof/ownership_neg_1.c b/src/example-archive/should-fail/broken/error-proof/ownership_neg_1.c new file mode 100644 index 00000000..7b788719 --- /dev/null +++ b/src/example-archive/should-fail/broken/error-proof/ownership_neg_1.c @@ -0,0 +1,10 @@ +// Negative test case: proof should fail + +// Precondition includes access to the resource Owned(p), which disappears in +// the postcondition +void ownership_neg_1(int *p) +/*@ requires take P = Owned(p); @*/ +/*@ ensures true; @*/ +{ + ; +} \ No newline at end of file diff --git a/src/example-archive/should-fail/broken/error-proof/ownership_neg_2.c b/src/example-archive/should-fail/broken/error-proof/ownership_neg_2.c new file mode 100644 index 00000000..42f8044b --- /dev/null +++ b/src/example-archive/should-fail/broken/error-proof/ownership_neg_2.c @@ -0,0 +1,10 @@ +// Negative test case: proof should fail + +// Precondition takes ownership of no resources, but then the postcondition +// claims ownership of Owned(p) +void ownership_neg_2(int *p) +/*@ requires true; @*/ +/*@ ensures take P_ = Owned(p); @*/ +{ + ; +} \ No newline at end of file diff --git a/src/example-archive/should-fail/broken/error-proof/ownership_neg_3.c b/src/example-archive/should-fail/broken/error-proof/ownership_neg_3.c new file mode 100644 index 00000000..b0bff2d3 --- /dev/null +++ b/src/example-archive/should-fail/broken/error-proof/ownership_neg_3.c @@ -0,0 +1,12 @@ +// Negative test case: proof should fail + +// Precondition includes access to the resource Owned(p), which is duplicated in +// the postcondition +void ownership_neg_3(int *p) +/*@ requires take P = Owned(p); @*/ +/*@ ensures + take P_ = Owned(p); + take Q_ = Owned(p); @*/ +{ + ; +} \ No newline at end of file diff --git a/src/example-archive/should-fail/broken/error-proof/trivial_neg_1.c b/src/example-archive/should-fail/broken/error-proof/trivial_neg_1.c new file mode 100644 index 00000000..4ef975bf --- /dev/null +++ b/src/example-archive/should-fail/broken/error-proof/trivial_neg_1.c @@ -0,0 +1,8 @@ +// Negative test case: proof should fail + +// The specification has a false postcondition +void trivial_neg_1() +/*@ ensures false; @*/ +{ + ; +} \ No newline at end of file diff --git a/src/example-archive/simple-examples/broken/error-proof/ownership_1.c b/src/example-archive/simple-examples/broken/error-proof/ownership_1.c new file mode 100644 index 00000000..62b7f3e9 --- /dev/null +++ b/src/example-archive/simple-examples/broken/error-proof/ownership_1.c @@ -0,0 +1,16 @@ +// This example should be provable because Owned locations should be +// disjoint. But CN currently doesn't enforce this property. +// See: https://github.com/rems-project/cerberus/issues/295 + +void ownership_1(int *a, int *b) +/*@ +requires + take P1 = Owned(a); + take P2 = Owned(b); +ensures + a != b; +@*/ +{ + /*@ split_case a == b; @*/ + ; +} \ No newline at end of file From a8d390cfa26d4685154efecb6c265b08b6d5887c Mon Sep 17 00:00:00 2001 From: septract Date: Fri, 5 Jul 2024 11:44:00 -0700 Subject: [PATCH 010/152] Add a workflow to run CN on examples --- .github/workflows/run-cn-examples.yml | 100 ++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 .github/workflows/run-cn-examples.yml diff --git a/.github/workflows/run-cn-examples.yml b/.github/workflows/run-cn-examples.yml new file mode 100644 index 00000000..f10ced27 --- /dev/null +++ b/.github/workflows/run-cn-examples.yml @@ -0,0 +1,100 @@ +# Modified from rems-project/cerberus/blob/master/.github/workflows/ci.yml + +name: Test all examples with CN + +on: + pull_request: + push: + branches: + - main + +# cancel in-progress job when a new push is performed +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + strategy: + matrix: + version: [4.14.1] + + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v4 + with: + repository: rems-project/cerberus + + - name: System dependencies (ubuntu) + run: | + sudo apt install build-essential libgmp-dev z3 opam + + - name: Restore cached opam + id: cache-opam-restore + uses: actions/cache/restore@v4 + with: + path: ~/.opam + key: ${{ matrix.version }} + + - name: Setup opam + if: steps.cache-opam-restore.outputs.cache-hit != 'true' + run: | + opam init --yes --no-setup --shell=sh --compiler=${{ matrix.version }} + opam install --deps-only --yes ./cerberus-lib.opam + opam switch create with_coq ${{ matrix.version }} + eval $(opam env --switch=with_coq) + opam repo add --yes --this-switch coq-released https://coq.inria.fr/opam/released + opam pin --yes -n coq-struct-tact https://github.com/uwplse/StructTact.git + opam repo add --yes --this-switch iris-dev https://gitlab.mpi-sws.org/iris/opam.git + opam pin --yes -n coq-sail-stdpp https://github.com/rems-project/coq-sail.git#f319aad + opam pin --yes -n coq-cheri-capabilities https://github.com/rems-project/coq-cheri-capabilities.git + opam install --deps-only --yes ./cerberus-lib.opam ./cerberus-cheri.opam + + - name: Save cached opam + if: steps.cache-opam-restore.outputs.cache-hit != 'true' + id: cache-opam-save + uses: actions/cache/save@v4 + with: + path: ~/.opam + key: ${{ steps.cache-opam-restore.outputs.cache-primary-key }} + + - name: Install Cerberus + run: | + opam switch ${{ matrix.version }} + eval $(opam env --switch=${{ matrix.version }}) + opam pin --yes --no-action add cerberus-lib . + opam pin --yes --no-action add cerberus . + opam install --yes cerberus + + - name: Install CN + run: | + opam switch ${{ matrix.version }} + eval $(opam env --switch=${{ matrix.version }}) + opam pin --yes --no-action add cn . + opam install --yes cn + + - name: Download cvc5 release + uses: robinraju/release-downloader@v1 + with: + repository: cvc5/cvc5 + tag: cvc5-1.1.2 + fileName: cvc5-Linux-static.zip + + - name: Unzip and install cvc5 + run: | + unzip cvc5-Linux-static.zip + chmod +x cvc5-Linux-static/bin/cvc5 + sudo cp cvc5-Linux-static/bin/cvc5 /usr/local/bin/ + + - name: Checkout cn-tutorial + uses: actions/checkout@v4 + with: + repository: rems-project/cn-tutorial + path: cn-tutorial + + - name: Run CN Tutorial CI tests + run: | + opam switch ${{ matrix.version }} + eval $(opam env --switch=${{ matrix.version }}) + USE_OPAM='' tests/run-cn-tutorial-ci.sh cn-tutorial From 507b76b0b9711fa87e294718eced7ae871d5a24a Mon Sep 17 00:00:00 2001 From: septract Date: Fri, 5 Jul 2024 14:25:56 -0700 Subject: [PATCH 011/152] Properly thread through CN tool name and args --- src/example-archive/check-all.sh | 2 +- src/example-archive/check.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/example-archive/check-all.sh b/src/example-archive/check-all.sh index 5bbe1b38..e05c55e8 100755 --- a/src/example-archive/check-all.sh +++ b/src/example-archive/check-all.sh @@ -23,7 +23,7 @@ FAILURE=0 for subdir in "${subdirs[@]}"; do cd "$subdir" || continue - ../check.sh $CN + ../check.sh "$CN" if [ $? != 0 ] then diff --git a/src/example-archive/check.sh b/src/example-archive/check.sh index a246a369..1365076b 100755 --- a/src/example-archive/check.sh +++ b/src/example-archive/check.sh @@ -43,7 +43,7 @@ check_file() { local expected_exit_code=$2 printf "[$file]... " - timeout 10 cn "$file" > /dev/null 2>&1 + timeout 10 $CN "$file" > /dev/null 2>&1 local result=$? if [ $result -eq $expected_exit_code ]; then From b6f95eb13e032d04a3f9d514852cd14d3eb4f657 Mon Sep 17 00:00:00 2001 From: Christopher Pulte Date: Tue, 9 Jul 2024 21:35:02 +0100 Subject: [PATCH 012/152] record that one example was broken by a recent Cerberus change --- .../{working/00111.working.c => broken/error-proof/00111.err1.c} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/example-archive/c-testsuite/{working/00111.working.c => broken/error-proof/00111.err1.c} (100%) diff --git a/src/example-archive/c-testsuite/working/00111.working.c b/src/example-archive/c-testsuite/broken/error-proof/00111.err1.c similarity index 100% rename from src/example-archive/c-testsuite/working/00111.working.c rename to src/example-archive/c-testsuite/broken/error-proof/00111.err1.c From 20e598fd436182fc191957b348cc04fc218474ae Mon Sep 17 00:00:00 2001 From: Cole Schlesinger <63367934+thatplguy@users.noreply.github.com> Date: Tue, 9 Jul 2024 17:34:10 -0700 Subject: [PATCH 013/152] Add `make check` target (#14) Adds `make check`, `make check-tutorial` and `make check-archive` targets. Also makes it possible to call these targets non-locally eg. `make -f some/path/cn-tutorial/Makefile check` (Note that other targets do not work unless called locally) --------- Co-authored-by: Mike Dodds --- Makefile | 20 +++++++++++++++++++- README.md | 8 ++++++-- check.sh | 4 +++- src/example-archive/check-all.sh | 8 ++++---- 4 files changed, 32 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index a446ce7e..c8fa399f 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,6 @@ -.PHONY: default clean exercises +.PHONY: default check-archive check-tutorial check clean exercises + +MAKEFILE_DIR:=$(dir $(realpath $(lastword $(MAKEFILE_LIST)))) default: build exercises build/tutorial.html build/exercises.zip @@ -39,6 +41,22 @@ build/solutions/%: src/examples/% build/exercises.zip: $(EXERCISES) cd build; zip -r exercises.zip exercises > /dev/null + +############################################################################## +# Check that the examples all run correctly + +CN_PATH ?= cn + +check-archive: + @echo Check archive examples + @$(MAKEFILE_DIR)/src/example-archive/check-all.sh "$(CN_PATH)" + +check-tutorial: + @echo Check tutorial examples + @$(MAKEFILE_DIR)/check.sh "$(CN_PATH)" + +check: check-tutorial check-archive + ############################################################################## # Tutorial document diff --git a/README.md b/README.md index a33ace09..a94df9b7 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,12 @@ Install dependencies: [asciidoctor](https://asciidoctor.org/). Run `make` to produce `build/tutorial.html` and its dependencies: especially, `build/exercises/*.c` and `build/solutions/*c`. -Run `./check.sh` to check that all examples have working solutions (except tests with names `*.broken.c`, which are expected to fail). Note that this step will not work until after you have installed CN, which is the first part of the tutorial. +Run `make check-tutorial` to check that all examples in the tutorial have working solutions (except tests with names `*.broken.c`, which are expected to fail). Note that this step will not work until after you have installed CN, which is the first part of the tutorial. + +Run `make check` to checks both the tutorial and archive examples (see below). ## CN Example Archive -The subdirectory `src/example-archive` includes many more examples of CN proofs, both working and broken. See [the README](./src/example-archive/README.md) for a description how these examples are organized and instructions for running CN on them. +The subdirectory `src/example-archive` includes many more examples of CN proofs, both working and broken. See [the README](./src/example-archive/README.md) for a description how these examples are organized. + +Run `make check-archive` to check all examples in the example archive. \ No newline at end of file diff --git a/check.sh b/check.sh index 4f6c810a..50e82a5e 100755 --- a/check.sh +++ b/check.sh @@ -1,5 +1,7 @@ #!/usr/bin/env bash +SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" + if [ -n "$1" ] then echo "using CN=$1 in $PWD" @@ -12,7 +14,7 @@ good=0 bad=0 declare -a bad_tests -for file in src/examples/*c; +for file in $SCRIPT_DIR/src/examples/*c; do echo "Checking $file ..." $CN $file diff --git a/src/example-archive/check-all.sh b/src/example-archive/check-all.sh index e05c55e8..2784fb3e 100755 --- a/src/example-archive/check-all.sh +++ b/src/example-archive/check-all.sh @@ -1,5 +1,7 @@ #!/usr/bin/env bash +SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" + if [ -n "$1" ] then echo "check-all.sh: using CN=$1 in $PWD" @@ -21,16 +23,14 @@ subdirs=( FAILURE=0 for subdir in "${subdirs[@]}"; do - cd "$subdir" || continue + cd "$SCRIPT_DIR/$subdir" || continue - ../check.sh "$CN" + ../check.sh "$CN" if [ $? != 0 ] then FAILURE=1 fi - - cd .. done if [ $FAILURE -eq 0 ]; From e22b93c3230be49eca7a1ede53820d3cd18ed32c Mon Sep 17 00:00:00 2001 From: Christopher Pulte Date: Wed, 10 Jul 2024 11:55:52 +0100 Subject: [PATCH 014/152] move previously-broken example back, in preparation for Kayvan's fix --- .../{broken/error-proof/00111.err1.c => working/00111.working.c} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/example-archive/c-testsuite/{broken/error-proof/00111.err1.c => working/00111.working.c} (100%) diff --git a/src/example-archive/c-testsuite/broken/error-proof/00111.err1.c b/src/example-archive/c-testsuite/working/00111.working.c similarity index 100% rename from src/example-archive/c-testsuite/broken/error-proof/00111.err1.c rename to src/example-archive/c-testsuite/working/00111.working.c From 69a7a75a6fc4fe8aaa19236779ae67ea4d83fb8b Mon Sep 17 00:00:00 2001 From: scuellar Date: Tue, 28 May 2024 13:22:23 -0400 Subject: [PATCH 015/152] Add README with how to build coq lemmas --- src/example-archive/coq-lemmas/README.md | 67 ++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 src/example-archive/coq-lemmas/README.md diff --git a/src/example-archive/coq-lemmas/README.md b/src/example-archive/coq-lemmas/README.md new file mode 100644 index 00000000..d5169dfe --- /dev/null +++ b/src/example-archive/coq-lemmas/README.md @@ -0,0 +1,67 @@ +## Examples + +CN examples using lemmas that can be extracted to Coq. The examples are split into: + +- Trivial +- Lists +- Recursive +- Advanced + +## Generating Coq Lemmas + +To generate Coq lemma for a given `example-file.c` run + +``` +cn --lemmata=build/theories/ExportedLemmas.v example-file.c +``` + +File `build/theories/ExportedLemmas.v` should be generated with the +right definitions. Each lemma is exported as a new definition and then +added as a parameter in the module named `Lemma_Spec`. It should look +something like this + +``` + +Module Defs (P : Parameters). + + Import Types P. + Open Scope Z. + + + Definition my_lemma_type : Prop := + Is_true true. + +End Defs. + + +Module Type Lemma_Spec (P : Parameters). + + Module D := Defs(P). + Import D. + + Parameter my_lemma : my_lemma_type. + +End Lemma_Spec. +``` + +## Prooving the Coq Lemmas + +To prove the lemmas, instantiate a new module with type `Lemma_Spec` containing each of parameters as lemmas and their proofs. For the example above, the proofs look like this + +``` + +Module MyP: Parameters. +End MyP. + +Module Proofs : Lemma_Spec MyP. + Module D := Defs(MyP). + Import D. + + Lemma just_arith2 : my_lemma_type. + Proof. + solve [hnf; trivial]. + Qed. + +End Proofs. + +``` From 12f6e66df78d6b62fb84cb031bb043f6c5f31778 Mon Sep 17 00:00:00 2001 From: scuellar Date: Tue, 28 May 2024 13:23:46 -0400 Subject: [PATCH 016/152] Add examples --- .../coq-lemmas/inprogress/trivial-002.c | 10 ++++++++++ .../coq-lemmas/inprogress/trivial-003.c | 10 ++++++++++ .../coq-lemmas/inprogress/trivial-004.c | 10 ++++++++++ .../coq-lemmas/inprogress/trivial-005.c | 10 ++++++++++ .../coq-lemmas/inprogress/trivial-006.c | 10 ++++++++++ .../coq-lemmas/inprogress/trivial-007.c | 19 +++++++++++++++++++ .../coq-lemmas/inprogress/trivial-008.c | 11 +++++++++++ .../coq-lemmas/working/trivial-001.c | 10 ++++++++++ 8 files changed, 90 insertions(+) create mode 100644 src/example-archive/coq-lemmas/inprogress/trivial-002.c create mode 100644 src/example-archive/coq-lemmas/inprogress/trivial-003.c create mode 100644 src/example-archive/coq-lemmas/inprogress/trivial-004.c create mode 100644 src/example-archive/coq-lemmas/inprogress/trivial-005.c create mode 100644 src/example-archive/coq-lemmas/inprogress/trivial-006.c create mode 100644 src/example-archive/coq-lemmas/inprogress/trivial-007.c create mode 100644 src/example-archive/coq-lemmas/inprogress/trivial-008.c create mode 100644 src/example-archive/coq-lemmas/working/trivial-001.c diff --git a/src/example-archive/coq-lemmas/inprogress/trivial-002.c b/src/example-archive/coq-lemmas/inprogress/trivial-002.c new file mode 100644 index 00000000..0d2cc978 --- /dev/null +++ b/src/example-archive/coq-lemmas/inprogress/trivial-002.c @@ -0,0 +1,10 @@ +/*@ + lemma lem_impossible_in_coq (u32 x) + requires true; + ensures x <= 4294967295u32; +@*/ + +void nothing() +{ + ; +} diff --git a/src/example-archive/coq-lemmas/inprogress/trivial-003.c b/src/example-archive/coq-lemmas/inprogress/trivial-003.c new file mode 100644 index 00000000..4716d24d --- /dev/null +++ b/src/example-archive/coq-lemmas/inprogress/trivial-003.c @@ -0,0 +1,10 @@ +/*@ + lemma lem_ineq (u32 x, u32 y) + requires x > 0u32; y > 0u32; + ensures x > 0u32; y > 0u32; +@*/ + +void trivial() +{ + ; +} diff --git a/src/example-archive/coq-lemmas/inprogress/trivial-004.c b/src/example-archive/coq-lemmas/inprogress/trivial-004.c new file mode 100644 index 00000000..51c47e76 --- /dev/null +++ b/src/example-archive/coq-lemmas/inprogress/trivial-004.c @@ -0,0 +1,10 @@ +/*@ + lemma lem_impossible_in_Coq (u32 x) + requires true; + ensures (x + 1u32) - 1u32 == x; +@*/ + +void trivial() +{ + ; +} diff --git a/src/example-archive/coq-lemmas/inprogress/trivial-005.c b/src/example-archive/coq-lemmas/inprogress/trivial-005.c new file mode 100644 index 00000000..13ce2e09 --- /dev/null +++ b/src/example-archive/coq-lemmas/inprogress/trivial-005.c @@ -0,0 +1,10 @@ +/*@ + lemma lem_bit_wise_and (u32 x) + requires true; + ensures x & x == x; +@*/ + +void trivial() +{ + ; +} diff --git a/src/example-archive/coq-lemmas/inprogress/trivial-006.c b/src/example-archive/coq-lemmas/inprogress/trivial-006.c new file mode 100644 index 00000000..d07ffbc2 --- /dev/null +++ b/src/example-archive/coq-lemmas/inprogress/trivial-006.c @@ -0,0 +1,10 @@ +/*@ + lemma lem_bit_wise_or (u32 x) + requires true; + ensures x | x == x; +@*/ + +void trivial() +{ + ; +} diff --git a/src/example-archive/coq-lemmas/inprogress/trivial-007.c b/src/example-archive/coq-lemmas/inprogress/trivial-007.c new file mode 100644 index 00000000..d2f8cea1 --- /dev/null +++ b/src/example-archive/coq-lemmas/inprogress/trivial-007.c @@ -0,0 +1,19 @@ +#include +/*@ + predicate (bool) MyCondition (u32 x){ + if (x > 4294967295u32){ + return false; + } else { + return true; + } + } + + lemma lem_bit_wise_or (u32 x) + requires MyCondition(x) == true; + ensures true; +@*/ + +void trivial() +{ + ; +} diff --git a/src/example-archive/coq-lemmas/inprogress/trivial-008.c b/src/example-archive/coq-lemmas/inprogress/trivial-008.c new file mode 100644 index 00000000..423311ff --- /dev/null +++ b/src/example-archive/coq-lemmas/inprogress/trivial-008.c @@ -0,0 +1,11 @@ +#include +/*@ + lemma my_lemma () + requires true; + ensures true; +@*/ + +void trivial() +{ + ; +} diff --git a/src/example-archive/coq-lemmas/working/trivial-001.c b/src/example-archive/coq-lemmas/working/trivial-001.c new file mode 100644 index 00000000..529f1dbe --- /dev/null +++ b/src/example-archive/coq-lemmas/working/trivial-001.c @@ -0,0 +1,10 @@ +/*@ + lemma lem_trivial () + requires true; + ensures true; +@*/ + +void lemma_1() +{ + ; +} From db0b91690060c154a708cca28d340ebc47b3fe0e Mon Sep 17 00:00:00 2001 From: scuellar Date: Tue, 28 May 2024 13:25:14 -0400 Subject: [PATCH 017/152] Add build files --- src/example-archive/coq-lemmas/build/Makefile | 8 ++++ .../coq-lemmas/build/_CoqProject | 7 +++ .../coq-lemmas/build/theories/CN_Lib.v | 29 +++++++++++++ .../build/theories/ExportedLemmas.v | 43 +++++++++++++++++++ .../coq-lemmas/build/theories/Gen_Spec.v | 43 +++++++++++++++++++ 5 files changed, 130 insertions(+) create mode 100644 src/example-archive/coq-lemmas/build/Makefile create mode 100644 src/example-archive/coq-lemmas/build/_CoqProject create mode 100644 src/example-archive/coq-lemmas/build/theories/CN_Lib.v create mode 100644 src/example-archive/coq-lemmas/build/theories/ExportedLemmas.v create mode 100644 src/example-archive/coq-lemmas/build/theories/Gen_Spec.v diff --git a/src/example-archive/coq-lemmas/build/Makefile b/src/example-archive/coq-lemmas/build/Makefile new file mode 100644 index 00000000..819ea988 --- /dev/null +++ b/src/example-archive/coq-lemmas/build/Makefile @@ -0,0 +1,8 @@ + + + +all: + coq_makefile -f _CoqProject -o Makefile.coq + cn ../../SantiagosExamples/trivial-lemma.c --lemmata theories/Gen_Spec.v + make -f Makefile.coq + diff --git a/src/example-archive/coq-lemmas/build/_CoqProject b/src/example-archive/coq-lemmas/build/_CoqProject new file mode 100644 index 00000000..917968e6 --- /dev/null +++ b/src/example-archive/coq-lemmas/build/_CoqProject @@ -0,0 +1,7 @@ + +-Q theories CN_Lemmas + +theories/CN_Lib.v +theories/ExportedLemmas.v + + diff --git a/src/example-archive/coq-lemmas/build/theories/CN_Lib.v b/src/example-archive/coq-lemmas/build/theories/CN_Lib.v new file mode 100644 index 00000000..e17d738a --- /dev/null +++ b/src/example-archive/coq-lemmas/build/theories/CN_Lib.v @@ -0,0 +1,29 @@ +Require List. +Require Import ZArith Bool. +Require Import Lia. +Require NArith. + +Definition wrapI (minInt : Z) (maxInt : Z) x := + let delta := ((maxInt - minInt) + 1)%Z in + let r := Z.modulo x delta in + (if (r <=? maxInt) then r else r - delta)%Z. + +Lemma wrapI_idem: + forall (minInt maxInt x : Z), + (minInt <= x <= maxInt)%Z -> + (minInt <= 0 < maxInt)%Z -> + wrapI minInt maxInt x = x. +Proof. + Open Scope Z. + intros. + unfold wrapI. + pose (delta := ((maxInt - minInt) + 1)). + destruct (0 <=? x) eqn: x_neg. + - rewrite Z.mod_small by lia. + rewrite Zle_imp_le_bool by lia. + reflexivity. + - rewrite (Znumtheory.Zdivide_mod_minus _ _ (x + delta)). + + destruct (x + delta <=? maxInt) eqn: leb; lia. + + lia. + + exists (-1); lia. +Qed. diff --git a/src/example-archive/coq-lemmas/build/theories/ExportedLemmas.v b/src/example-archive/coq-lemmas/build/theories/ExportedLemmas.v new file mode 100644 index 00000000..ffe0f0be --- /dev/null +++ b/src/example-archive/coq-lemmas/build/theories/ExportedLemmas.v @@ -0,0 +1,43 @@ +(* build/theories/ExportedLemmas.v: generated lemma specifications from CN *) + +Require Import ZArith Bool. +Require CN_Lemmas.CN_Lib. + + +Module Types. + + (* no type definitions required *) + +End Types. + + +Module Type Parameters. + Import Types. + + (* no parameters required *) + +End Parameters. + + +Module Defs (P : Parameters). + + Import Types P. + Open Scope Z. + + + Definition my_lemma_type : Prop := + Is_true true. + +End Defs. + + +Module Type Lemma_Spec (P : Parameters). + + Module D := Defs(P). + Import D. + + Parameter my_lemma : my_lemma_type. + +End Lemma_Spec. + + diff --git a/src/example-archive/coq-lemmas/build/theories/Gen_Spec.v b/src/example-archive/coq-lemmas/build/theories/Gen_Spec.v new file mode 100644 index 00000000..7254a094 --- /dev/null +++ b/src/example-archive/coq-lemmas/build/theories/Gen_Spec.v @@ -0,0 +1,43 @@ +(* theories/Gen_Spec.v: generated lemma specifications from CN *) + +Require Import ZArith Bool. +Require CN_Lemmas.CN_Lib. + + +Module Types. + + (* no type definitions required *) + +End Types. + + +Module Type Parameters. + Import Types. + + (* no parameters required *) + +End Parameters. + + +Module Defs (P : Parameters). + + Import Types P. + Open Scope Z. + + + Definition lem_trivial_type : Prop := + Is_true true. + +End Defs. + + +Module Type Lemma_Spec (P : Parameters). + + Module D := Defs(P). + Import D. + + Parameter lem_trivial : lem_trivial_type. + +End Lemma_Spec. + + From a7d0950d9129bc9d34b34fdc6746a488cfdbb918 Mon Sep 17 00:00:00 2001 From: scuellar Date: Mon, 10 Jun 2024 13:41:49 -0400 Subject: [PATCH 018/152] Add and reorder examples --- src/example-archive/coq-lemmas/README.md | 2 +- .../error-cerberus}/trivial-005.c | 0 .../error-cerberus}/trivial-006.c | 0 .../coq-lemmas/inprogress/recursive-001.c | 85 +++++++++++++++++++ .../coq-lemmas/inprogress/trivial-002.c | 2 +- .../coq-lemmas/inprogress/trivial-003.c | 10 --- .../coq-lemmas/inprogress/trivial-004.c | 10 --- .../coq-lemmas/inprogress/trivial-007.c | 5 +- 8 files changed, 88 insertions(+), 26 deletions(-) rename src/example-archive/coq-lemmas/{inprogress => broken/error-cerberus}/trivial-005.c (100%) rename src/example-archive/coq-lemmas/{inprogress => broken/error-cerberus}/trivial-006.c (100%) create mode 100644 src/example-archive/coq-lemmas/inprogress/recursive-001.c delete mode 100644 src/example-archive/coq-lemmas/inprogress/trivial-003.c delete mode 100644 src/example-archive/coq-lemmas/inprogress/trivial-004.c diff --git a/src/example-archive/coq-lemmas/README.md b/src/example-archive/coq-lemmas/README.md index d5169dfe..535457ad 100644 --- a/src/example-archive/coq-lemmas/README.md +++ b/src/example-archive/coq-lemmas/README.md @@ -3,8 +3,8 @@ CN examples using lemmas that can be extracted to Coq. The examples are split into: - Trivial -- Lists - Recursive +- Lists - Advanced ## Generating Coq Lemmas diff --git a/src/example-archive/coq-lemmas/inprogress/trivial-005.c b/src/example-archive/coq-lemmas/broken/error-cerberus/trivial-005.c similarity index 100% rename from src/example-archive/coq-lemmas/inprogress/trivial-005.c rename to src/example-archive/coq-lemmas/broken/error-cerberus/trivial-005.c diff --git a/src/example-archive/coq-lemmas/inprogress/trivial-006.c b/src/example-archive/coq-lemmas/broken/error-cerberus/trivial-006.c similarity index 100% rename from src/example-archive/coq-lemmas/inprogress/trivial-006.c rename to src/example-archive/coq-lemmas/broken/error-cerberus/trivial-006.c diff --git a/src/example-archive/coq-lemmas/inprogress/recursive-001.c b/src/example-archive/coq-lemmas/inprogress/recursive-001.c new file mode 100644 index 00000000..5f31a1f6 --- /dev/null +++ b/src/example-archive/coq-lemmas/inprogress/recursive-001.c @@ -0,0 +1,85 @@ +#include +/*@ + +datatype a_list { + LsNil {}, + LsCons {u32 i, a_list t} +} + +@*/ + +/* The predicates relating A/B trees to their C encoding. */ + +enum { + NUM_NODES = 16, + LEN_LIMIT = (1 << 16), +}; + +struct node; + +typedef struct node * tree; + +struct node { + int v; + tree nodes[NUM_NODES]; +}; + +/*@ + + + +datatype tree_arc { + Arc_End {}, + Arc_Step {i32 i, datatype tree_arc tail} +} + +datatype tree_node_option { + Node_None {}, + Node {i32 v} +} + +function (map) empty () +function (map) construct + (i32 v, map > ts) + +function (map >) default_ns () + +predicate {map t, + i32 v, map > ns} + Tree (pointer p) +{ + if (is_null(p)) { + return {t: (empty ()), v: 0i32, ns: default_ns ()}; + } + else { + take V = Owned(member_shift(p,v)); + let nodes_ptr = member_shift(p,nodes); + take Ns = each (i32 i; (0i32 <= i) && (i < (num_nodes ()))) + {Indirect_Tree(array_shift(nodes_ptr, i))}; + let t = construct (V, Ns); + return {t: t, v: V, ns: Ns}; + } +} +@*/ + + +/* + + match ls { + LsNil {} => + take value = Owned(p); + assert (value[0] == 0); + assert (is_null(p+1)); + return 0; + LsCons {u32 i, a_list tl} => + take value = Owned(p); + assert(value[0] == i); + assert (is_array(p+1, tl)) + return i; + } + + */ +void trivial() +{ + ; +} diff --git a/src/example-archive/coq-lemmas/inprogress/trivial-002.c b/src/example-archive/coq-lemmas/inprogress/trivial-002.c index 0d2cc978..bb6d2466 100644 --- a/src/example-archive/coq-lemmas/inprogress/trivial-002.c +++ b/src/example-archive/coq-lemmas/inprogress/trivial-002.c @@ -1,7 +1,7 @@ /*@ lemma lem_impossible_in_coq (u32 x) requires true; - ensures x <= 4294967295u32; + ensures x <= 2147483647u32; @*/ void nothing() diff --git a/src/example-archive/coq-lemmas/inprogress/trivial-003.c b/src/example-archive/coq-lemmas/inprogress/trivial-003.c deleted file mode 100644 index 4716d24d..00000000 --- a/src/example-archive/coq-lemmas/inprogress/trivial-003.c +++ /dev/null @@ -1,10 +0,0 @@ -/*@ - lemma lem_ineq (u32 x, u32 y) - requires x > 0u32; y > 0u32; - ensures x > 0u32; y > 0u32; -@*/ - -void trivial() -{ - ; -} diff --git a/src/example-archive/coq-lemmas/inprogress/trivial-004.c b/src/example-archive/coq-lemmas/inprogress/trivial-004.c deleted file mode 100644 index 51c47e76..00000000 --- a/src/example-archive/coq-lemmas/inprogress/trivial-004.c +++ /dev/null @@ -1,10 +0,0 @@ -/*@ - lemma lem_impossible_in_Coq (u32 x) - requires true; - ensures (x + 1u32) - 1u32 == x; -@*/ - -void trivial() -{ - ; -} diff --git a/src/example-archive/coq-lemmas/inprogress/trivial-007.c b/src/example-archive/coq-lemmas/inprogress/trivial-007.c index d2f8cea1..1752d92a 100644 --- a/src/example-archive/coq-lemmas/inprogress/trivial-007.c +++ b/src/example-archive/coq-lemmas/inprogress/trivial-007.c @@ -7,10 +7,7 @@ return true; } } - - lemma lem_bit_wise_or (u32 x) - requires MyCondition(x) == true; - ensures true; + @*/ void trivial() From 9ea5766df0ed4bf70bc04bde7e691091ae461966 Mon Sep 17 00:00:00 2001 From: scuellar Date: Mon, 10 Jun 2024 17:07:10 -0400 Subject: [PATCH 019/152] Add examples and ignore automatically generated files --- src/example-archive/coq-lemmas/.gitignore | 7 +++++++ src/example-archive/coq-lemmas/working/trivial-002.c | 10 ++++++++++ src/example-archive/coq-lemmas/working/trivial-003.c | 10 ++++++++++ src/example-archive/coq-lemmas/working/trivial-004.c | 10 ++++++++++ 4 files changed, 37 insertions(+) create mode 100644 src/example-archive/coq-lemmas/.gitignore create mode 100644 src/example-archive/coq-lemmas/working/trivial-002.c create mode 100644 src/example-archive/coq-lemmas/working/trivial-003.c create mode 100644 src/example-archive/coq-lemmas/working/trivial-004.c diff --git a/src/example-archive/coq-lemmas/.gitignore b/src/example-archive/coq-lemmas/.gitignore new file mode 100644 index 00000000..f1dd468a --- /dev/null +++ b/src/example-archive/coq-lemmas/.gitignore @@ -0,0 +1,7 @@ +build/**/*.vo +build/**/*.vos +build/**/*.vok +build/**/*.glob +build/**/*.aux +Makefile.coq +Makefile.coq.conf diff --git a/src/example-archive/coq-lemmas/working/trivial-002.c b/src/example-archive/coq-lemmas/working/trivial-002.c new file mode 100644 index 00000000..0d2cc978 --- /dev/null +++ b/src/example-archive/coq-lemmas/working/trivial-002.c @@ -0,0 +1,10 @@ +/*@ + lemma lem_impossible_in_coq (u32 x) + requires true; + ensures x <= 4294967295u32; +@*/ + +void nothing() +{ + ; +} diff --git a/src/example-archive/coq-lemmas/working/trivial-003.c b/src/example-archive/coq-lemmas/working/trivial-003.c new file mode 100644 index 00000000..4716d24d --- /dev/null +++ b/src/example-archive/coq-lemmas/working/trivial-003.c @@ -0,0 +1,10 @@ +/*@ + lemma lem_ineq (u32 x, u32 y) + requires x > 0u32; y > 0u32; + ensures x > 0u32; y > 0u32; +@*/ + +void trivial() +{ + ; +} diff --git a/src/example-archive/coq-lemmas/working/trivial-004.c b/src/example-archive/coq-lemmas/working/trivial-004.c new file mode 100644 index 00000000..51c47e76 --- /dev/null +++ b/src/example-archive/coq-lemmas/working/trivial-004.c @@ -0,0 +1,10 @@ +/*@ + lemma lem_impossible_in_Coq (u32 x) + requires true; + ensures (x + 1u32) - 1u32 == x; +@*/ + +void trivial() +{ + ; +} From 14457db1ffdea1b3c62c615af17e2e6cf3c12848 Mon Sep 17 00:00:00 2001 From: scuellar Date: Mon, 10 Jun 2024 17:15:24 -0400 Subject: [PATCH 020/152] Ignore more automatic coq files --- src/example-archive/coq-lemmas/.gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/example-archive/coq-lemmas/.gitignore b/src/example-archive/coq-lemmas/.gitignore index f1dd468a..5820fc02 100644 --- a/src/example-archive/coq-lemmas/.gitignore +++ b/src/example-archive/coq-lemmas/.gitignore @@ -5,3 +5,5 @@ build/**/*.glob build/**/*.aux Makefile.coq Makefile.coq.conf +.Makefile.coq.d +/build/.lia.cache \ No newline at end of file From f71b2409b9e9c3e2682be2b1d96b962ab81f58dc Mon Sep 17 00:00:00 2001 From: scuellar Date: Wed, 19 Jun 2024 14:30:16 -0400 Subject: [PATCH 021/152] adding examples --- .../build/theories/ExportedLemmas.v | 37 -------- .../coq-lemmas/inprogress/recursive-001.c | 85 ------------------- .../coq-lemmas/inprogress/trivial-002.c | 10 --- .../coq-lemmas/inprogress/trivial-009.c | 8 ++ .../coq-lemmas/working/recursive-001.c | 40 +++++++++ .../coq-lemmas/working/recursive-002.c | 56 ++++++++++++ 6 files changed, 104 insertions(+), 132 deletions(-) delete mode 100644 src/example-archive/coq-lemmas/inprogress/recursive-001.c delete mode 100644 src/example-archive/coq-lemmas/inprogress/trivial-002.c create mode 100644 src/example-archive/coq-lemmas/inprogress/trivial-009.c create mode 100644 src/example-archive/coq-lemmas/working/recursive-001.c create mode 100644 src/example-archive/coq-lemmas/working/recursive-002.c diff --git a/src/example-archive/coq-lemmas/build/theories/ExportedLemmas.v b/src/example-archive/coq-lemmas/build/theories/ExportedLemmas.v index ffe0f0be..e8fec956 100644 --- a/src/example-archive/coq-lemmas/build/theories/ExportedLemmas.v +++ b/src/example-archive/coq-lemmas/build/theories/ExportedLemmas.v @@ -4,40 +4,3 @@ Require Import ZArith Bool. Require CN_Lemmas.CN_Lib. -Module Types. - - (* no type definitions required *) - -End Types. - - -Module Type Parameters. - Import Types. - - (* no parameters required *) - -End Parameters. - - -Module Defs (P : Parameters). - - Import Types P. - Open Scope Z. - - - Definition my_lemma_type : Prop := - Is_true true. - -End Defs. - - -Module Type Lemma_Spec (P : Parameters). - - Module D := Defs(P). - Import D. - - Parameter my_lemma : my_lemma_type. - -End Lemma_Spec. - - diff --git a/src/example-archive/coq-lemmas/inprogress/recursive-001.c b/src/example-archive/coq-lemmas/inprogress/recursive-001.c deleted file mode 100644 index 5f31a1f6..00000000 --- a/src/example-archive/coq-lemmas/inprogress/recursive-001.c +++ /dev/null @@ -1,85 +0,0 @@ -#include -/*@ - -datatype a_list { - LsNil {}, - LsCons {u32 i, a_list t} -} - -@*/ - -/* The predicates relating A/B trees to their C encoding. */ - -enum { - NUM_NODES = 16, - LEN_LIMIT = (1 << 16), -}; - -struct node; - -typedef struct node * tree; - -struct node { - int v; - tree nodes[NUM_NODES]; -}; - -/*@ - - - -datatype tree_arc { - Arc_End {}, - Arc_Step {i32 i, datatype tree_arc tail} -} - -datatype tree_node_option { - Node_None {}, - Node {i32 v} -} - -function (map) empty () -function (map) construct - (i32 v, map > ts) - -function (map >) default_ns () - -predicate {map t, - i32 v, map > ns} - Tree (pointer p) -{ - if (is_null(p)) { - return {t: (empty ()), v: 0i32, ns: default_ns ()}; - } - else { - take V = Owned(member_shift(p,v)); - let nodes_ptr = member_shift(p,nodes); - take Ns = each (i32 i; (0i32 <= i) && (i < (num_nodes ()))) - {Indirect_Tree(array_shift(nodes_ptr, i))}; - let t = construct (V, Ns); - return {t: t, v: V, ns: Ns}; - } -} -@*/ - - -/* - - match ls { - LsNil {} => - take value = Owned(p); - assert (value[0] == 0); - assert (is_null(p+1)); - return 0; - LsCons {u32 i, a_list tl} => - take value = Owned(p); - assert(value[0] == i); - assert (is_array(p+1, tl)) - return i; - } - - */ -void trivial() -{ - ; -} diff --git a/src/example-archive/coq-lemmas/inprogress/trivial-002.c b/src/example-archive/coq-lemmas/inprogress/trivial-002.c deleted file mode 100644 index bb6d2466..00000000 --- a/src/example-archive/coq-lemmas/inprogress/trivial-002.c +++ /dev/null @@ -1,10 +0,0 @@ -/*@ - lemma lem_impossible_in_coq (u32 x) - requires true; - ensures x <= 2147483647u32; -@*/ - -void nothing() -{ - ; -} diff --git a/src/example-archive/coq-lemmas/inprogress/trivial-009.c b/src/example-archive/coq-lemmas/inprogress/trivial-009.c new file mode 100644 index 00000000..0583285e --- /dev/null +++ b/src/example-archive/coq-lemmas/inprogress/trivial-009.c @@ -0,0 +1,8 @@ +/*@ function [rec] (u32) my_spec(u32 n) { 42u32 } @*/ + +unsigned int factorial(unsigned int n) +/*@ ensures return == my_spec(n); @*/ +{ + /*@ unfold my_spec(n); @*/ + return 42; +} diff --git a/src/example-archive/coq-lemmas/working/recursive-001.c b/src/example-archive/coq-lemmas/working/recursive-001.c new file mode 100644 index 00000000..b45f7289 --- /dev/null +++ b/src/example-archive/coq-lemmas/working/recursive-001.c @@ -0,0 +1,40 @@ +/*@ +function [rec] (u32) fact_spec(u32 n) +{ + if (n <= 0u32) { 1u32 } + else { n * fact_spec(n - 1u32) } +} +@*/ + + +/*@ +lemma spec_lemma (u32 n) + requires true; + ensures fact_spec(n) == n * fact_spec(n-1u32); +@*/ + +// Function to calculate the factorial of a number +unsigned int factorial(unsigned int n) +/*@ requires 0u32 <= n; n <= 4294967294u32; @*/ +/*@ requires fact_spec(n) < 4294967295u32; @*/ +/*@ ensures return == fact_spec(n); @*/ +{ + if (n <= 0) { + /*@ unfold fact_spec(n); @*/ + return 1; // Return 1 + } + + unsigned result = 1; + /*@ unfold fact_spec(1u32-1u32); @*/ + for (unsigned int i = 1; i <= n; i++) + /*@ inv {n}unchanged; @*/ + /*@ inv 0u32 < n; i <= n+1u32; @*/ + /*@ inv 0u32 < i; @*/ + /*@ inv result == fact_spec(i-1u32); @*/ + { + result *= i; + /*@ apply spec_lemma((i+1u32)-1u32); @*/ + } + + return result; +} diff --git a/src/example-archive/coq-lemmas/working/recursive-002.c b/src/example-archive/coq-lemmas/working/recursive-002.c new file mode 100644 index 00000000..d245d459 --- /dev/null +++ b/src/example-archive/coq-lemmas/working/recursive-002.c @@ -0,0 +1,56 @@ +// Fails to export becasue of recursive definition + +/*@ +function [rec] (u32) fib_spec(u32 n) +{ + if (n <= 0u32) { + 1u32 + } else { + if (n == 1u32) { + 1u32 + } else { + fib_spec(n - 1u32) + fib_spec(n - 2u32) + } + } +} +@*/ + + +/*@ +lemma fib_lemma (u32 n) + requires true; + ensures fib_spec(n) == fib_spec(n - 1u32) + fib_spec(n - 2u32); +@*/ + +// Function to calculate the factorial of a number +unsigned int fibonacci(unsigned int n) +/*@ requires 0u32 <= n; n <= 4294967294u32; @*/ +/*@ requires fib_spec(n) < 4294967295u32; @*/ +/*@ ensures return == fib_spec(n); @*/ +{ + if (n <= 1) { + /*@ unfold fib_spec(n); @*/ + return 1; // Return 1 + } + + unsigned previous = 1; + unsigned tmp = 1; + unsigned result = 1; + + /*@ unfold fib_spec(1u32-1u32); @*/ + /*@ unfold fib_spec(1u32); @*/ + for (unsigned int i = 1; i < n; i++) + /*@ inv {n}unchanged; @*/ + /*@ inv 0u32 < n; i <= n; @*/ + /*@ inv 0u32 < i; @*/ + /*@ inv previous == fib_spec(i-1u32); @*/ + /*@ inv result == fib_spec(i); @*/ + { + tmp = previous; + previous = result; + result = tmp + previous; + /*@ apply fib_lemma(i+1u32); @*/ + } + + return result; +} From 1cfbca239a570f5cdf97b18d3d7d9a184dea57de Mon Sep 17 00:00:00 2001 From: scuellar Date: Mon, 24 Jun 2024 13:18:27 -0400 Subject: [PATCH 022/152] Clarity --- src/example-archive/coq-lemmas/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/example-archive/coq-lemmas/README.md b/src/example-archive/coq-lemmas/README.md index 535457ad..a01de3c5 100644 --- a/src/example-archive/coq-lemmas/README.md +++ b/src/example-archive/coq-lemmas/README.md @@ -4,8 +4,8 @@ CN examples using lemmas that can be extracted to Coq. The examples are split in - Trivial - Recursive -- Lists -- Advanced +- Lists (WIP) +- Advanced (WIP) ## Generating Coq Lemmas From 93a6f2566bcdd919dd1fb9f7a065489c8f38b03f Mon Sep 17 00:00:00 2001 From: scuellar Date: Tue, 9 Jul 2024 10:49:16 -0400 Subject: [PATCH 023/152] Typos --- src/example-archive/coq-lemmas/README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/example-archive/coq-lemmas/README.md b/src/example-archive/coq-lemmas/README.md index a01de3c5..d6a39163 100644 --- a/src/example-archive/coq-lemmas/README.md +++ b/src/example-archive/coq-lemmas/README.md @@ -1,6 +1,7 @@ ## Examples -CN examples using lemmas that can be extracted to Coq. The examples are split into: +CN examples using lemmas that can be extracted to Coq. The examples +are split into: - Trivial - Recursive @@ -44,9 +45,11 @@ Module Type Lemma_Spec (P : Parameters). End Lemma_Spec. ``` -## Prooving the Coq Lemmas +## Proving the Coq Lemmas -To prove the lemmas, instantiate a new module with type `Lemma_Spec` containing each of parameters as lemmas and their proofs. For the example above, the proofs look like this +To prove the lemmas, instantiate a new module with type `Lemma_Spec` +containing each of parameters as lemmas and their proofs. For the +example above, the proofs look like this ``` From 1c88cfd630f4f3c40ac65b04d9a92c2709ee7b43 Mon Sep 17 00:00:00 2001 From: scuellar Date: Tue, 9 Jul 2024 10:50:27 -0400 Subject: [PATCH 024/152] Clarify examples --- src/example-archive/coq-lemmas/README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/example-archive/coq-lemmas/README.md b/src/example-archive/coq-lemmas/README.md index d6a39163..6bd82f61 100644 --- a/src/example-archive/coq-lemmas/README.md +++ b/src/example-archive/coq-lemmas/README.md @@ -4,9 +4,7 @@ CN examples using lemmas that can be extracted to Coq. The examples are split into: - Trivial -- Recursive -- Lists (WIP) -- Advanced (WIP) +- Recursive (Not yet handled by the export) ## Generating Coq Lemmas From d99d111cedf8333b3ae322f6e7a5a82d9ffe440c Mon Sep 17 00:00:00 2001 From: scuellar Date: Wed, 10 Jul 2024 13:15:05 -0400 Subject: [PATCH 025/152] Add new script for checking coq extraction --- .../coq-lemmas/coq-build/_CoqProject | 4 +++ .../coq-lemmas/coq-build/theories/.CN_Lib.aux | 7 +++++ .../coq-build/theories/.ExportedLemmas.aux | 2 ++ .../coq-lemmas/coq-build/theories/.lia.cache | Bin 0 -> 1312 bytes .../coq-lemmas/coq-build/theories/CN_Lib.v | 29 ++++++++++++++++++ .../broken-export}/recursive-001.c | 0 .../broken-export}/recursive-002.c | 0 .../{ => coq}/working/trivial-001.c | 0 .../{ => coq}/working/trivial-002.c | 0 .../{ => coq}/working/trivial-003.c | 0 .../{ => coq}/working/trivial-004.c | 0 11 files changed, 42 insertions(+) create mode 100644 src/example-archive/coq-lemmas/coq-build/_CoqProject create mode 100644 src/example-archive/coq-lemmas/coq-build/theories/.CN_Lib.aux create mode 100644 src/example-archive/coq-lemmas/coq-build/theories/.ExportedLemmas.aux create mode 100644 src/example-archive/coq-lemmas/coq-build/theories/.lia.cache create mode 100644 src/example-archive/coq-lemmas/coq-build/theories/CN_Lib.v rename src/example-archive/coq-lemmas/{working => coq/broken-export}/recursive-001.c (100%) rename src/example-archive/coq-lemmas/{working => coq/broken-export}/recursive-002.c (100%) rename src/example-archive/coq-lemmas/{ => coq}/working/trivial-001.c (100%) rename src/example-archive/coq-lemmas/{ => coq}/working/trivial-002.c (100%) rename src/example-archive/coq-lemmas/{ => coq}/working/trivial-003.c (100%) rename src/example-archive/coq-lemmas/{ => coq}/working/trivial-004.c (100%) diff --git a/src/example-archive/coq-lemmas/coq-build/_CoqProject b/src/example-archive/coq-lemmas/coq-build/_CoqProject new file mode 100644 index 00000000..065c4ea8 --- /dev/null +++ b/src/example-archive/coq-lemmas/coq-build/_CoqProject @@ -0,0 +1,4 @@ +-Q theories CN_Lemmas + +theories/CN_Lib.v +theories/ExportedLemmas.v \ No newline at end of file diff --git a/src/example-archive/coq-lemmas/coq-build/theories/.CN_Lib.aux b/src/example-archive/coq-lemmas/coq-build/theories/.CN_Lib.aux new file mode 100644 index 00000000..540ba548 --- /dev/null +++ b/src/example-archive/coq-lemmas/coq-build/theories/.CN_Lib.aux @@ -0,0 +1,7 @@ +COQAUX1 633e54b5a22b850599c444b8737dbf06 /Users/Santiago/Projects/cn-tutorial/src/example-archive/coq-lemmas/working/trivial-001-build/theories/CN_Lib.v +0 0 VernacProof "tac:no using:no" +748 752 proof_build_time "0.009" +0 0 wrapI_idem "0.009" +399 412 context_used "" +748 752 proof_check_time "0.003" +0 0 vo_compile_time "0.191" diff --git a/src/example-archive/coq-lemmas/coq-build/theories/.ExportedLemmas.aux b/src/example-archive/coq-lemmas/coq-build/theories/.ExportedLemmas.aux new file mode 100644 index 00000000..a9c2953c --- /dev/null +++ b/src/example-archive/coq-lemmas/coq-build/theories/.ExportedLemmas.aux @@ -0,0 +1,2 @@ +COQAUX1 3900f32800d8cfc6593331bb6ddc51b3 /Users/Santiago/Projects/cn-tutorial/src/example-archive/coq-lemmas/working/trivial-001-build/theories/ExportedLemmas.v +0 0 vo_compile_time "0.168" diff --git a/src/example-archive/coq-lemmas/coq-build/theories/.lia.cache b/src/example-archive/coq-lemmas/coq-build/theories/.lia.cache new file mode 100644 index 0000000000000000000000000000000000000000..c936905014c614dd5ec5054ef868ae50e0441f2c GIT binary patch literal 1312 zcmb`G&nrbi6vszzQWk`bof50}SaW;IyxvMFJ1MLzgd}@QOJ(jtZ=>9o*s0h311M!- zX<<)USy?KD?>Tek{&KUqrq0~?dFJyy=iK)G!Gu!t&wID`$494EH%h4!*fQ*qEQ)%q zR&C+iwJ3_MH72$pbr@mLrp09Lrn=vB-6=~8G}gm%xEp46us&|mGgEGh{gHcPqs!2u z8GH;LnLPXm1TzNSwo-jVWJOH>dWy%-5^*`9CB%_(M7*2Ri8(8pNy%A1cRDbh`~tU_ zUIZi`QZlx?)D$H>DA|IF0kSeB`9N1xq8n+(yprisC3Al%fkj`OIWXgyrf{pBhRO{74#UBspfkiE}+Db;ob&S-FLMs;OLNaAN)I z?m$SI^u5>k90JtjL_W_Q6gwUXh&ZC2?O2bCw*qKk$xA A>i_@% literal 0 HcmV?d00001 diff --git a/src/example-archive/coq-lemmas/coq-build/theories/CN_Lib.v b/src/example-archive/coq-lemmas/coq-build/theories/CN_Lib.v new file mode 100644 index 00000000..e17d738a --- /dev/null +++ b/src/example-archive/coq-lemmas/coq-build/theories/CN_Lib.v @@ -0,0 +1,29 @@ +Require List. +Require Import ZArith Bool. +Require Import Lia. +Require NArith. + +Definition wrapI (minInt : Z) (maxInt : Z) x := + let delta := ((maxInt - minInt) + 1)%Z in + let r := Z.modulo x delta in + (if (r <=? maxInt) then r else r - delta)%Z. + +Lemma wrapI_idem: + forall (minInt maxInt x : Z), + (minInt <= x <= maxInt)%Z -> + (minInt <= 0 < maxInt)%Z -> + wrapI minInt maxInt x = x. +Proof. + Open Scope Z. + intros. + unfold wrapI. + pose (delta := ((maxInt - minInt) + 1)). + destruct (0 <=? x) eqn: x_neg. + - rewrite Z.mod_small by lia. + rewrite Zle_imp_le_bool by lia. + reflexivity. + - rewrite (Znumtheory.Zdivide_mod_minus _ _ (x + delta)). + + destruct (x + delta <=? maxInt) eqn: leb; lia. + + lia. + + exists (-1); lia. +Qed. diff --git a/src/example-archive/coq-lemmas/working/recursive-001.c b/src/example-archive/coq-lemmas/coq/broken-export/recursive-001.c similarity index 100% rename from src/example-archive/coq-lemmas/working/recursive-001.c rename to src/example-archive/coq-lemmas/coq/broken-export/recursive-001.c diff --git a/src/example-archive/coq-lemmas/working/recursive-002.c b/src/example-archive/coq-lemmas/coq/broken-export/recursive-002.c similarity index 100% rename from src/example-archive/coq-lemmas/working/recursive-002.c rename to src/example-archive/coq-lemmas/coq/broken-export/recursive-002.c diff --git a/src/example-archive/coq-lemmas/working/trivial-001.c b/src/example-archive/coq-lemmas/coq/working/trivial-001.c similarity index 100% rename from src/example-archive/coq-lemmas/working/trivial-001.c rename to src/example-archive/coq-lemmas/coq/working/trivial-001.c diff --git a/src/example-archive/coq-lemmas/working/trivial-002.c b/src/example-archive/coq-lemmas/coq/working/trivial-002.c similarity index 100% rename from src/example-archive/coq-lemmas/working/trivial-002.c rename to src/example-archive/coq-lemmas/coq/working/trivial-002.c diff --git a/src/example-archive/coq-lemmas/working/trivial-003.c b/src/example-archive/coq-lemmas/coq/working/trivial-003.c similarity index 100% rename from src/example-archive/coq-lemmas/working/trivial-003.c rename to src/example-archive/coq-lemmas/coq/working/trivial-003.c diff --git a/src/example-archive/coq-lemmas/working/trivial-004.c b/src/example-archive/coq-lemmas/coq/working/trivial-004.c similarity index 100% rename from src/example-archive/coq-lemmas/working/trivial-004.c rename to src/example-archive/coq-lemmas/coq/working/trivial-004.c From 4dbd8b7e4653e6ee831021d8150586ed0e5a7417 Mon Sep 17 00:00:00 2001 From: scuellar Date: Wed, 10 Jul 2024 13:19:50 -0400 Subject: [PATCH 026/152] add the script for checking coq extraction --- src/example-archive/check.sh | 110 +++++++++++++++++++++++++++++++++-- 1 file changed, 106 insertions(+), 4 deletions(-) diff --git a/src/example-archive/check.sh b/src/example-archive/check.sh index 1365076b..ac275950 100755 --- a/src/example-archive/check.sh +++ b/src/example-archive/check.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +set -x if [ -n "$1" ] then @@ -54,11 +55,112 @@ check_file() { fi } -process_files "working" "*.c" check_file 0 +process_files "working" "*.c" check_file 0 process_files "broken/error-cerberus" "*.c" check_file 2 -process_files "broken/error-crash" "*.c" check_file 125 -process_files "broken/error-proof" "*.c" check_file 1 -process_files "broken/error-timeout" "*.c" check_file 124 +process_files "broken/error-crash" "*.c" check_file 125 +process_files "broken/error-proof" "*.c" check_file 1 +process_files "broken/error-timeout" "*.c" check_file 124 +process_files "coq/broken-build" "*.c" check_file 0 +process_files "coq/broken-export" "*.c" check_file 0 +process_files "coq/working" "*.c" check_file 0 + +# ==================== +# Check Coq Exports +# ==================== + + +# We allow several types of failure that can be intended +readonly SUCCESS=0 +readonly FAIL_EXPORT=1 +readonly FAIL_COQ_BUILD=2 + + +check_coq_exports_end() { + ## Call this funciton at the end of a coq export check. It will + ## print the right message and return to the original directory + ## with popd. It will also increse the failure count if necessary. + local FAILED=$1 + local MESSAGE=$2 + + if [[FAILED]]; then + printf "\033[31mFAIL\033[0m (${MESSAGE})\n" + failures=$(( $failures + 1 )) + else + printf "\033[32mPASS\033[0m\n" + fi + + # Return to the directory where the parent function was called + popd > /dev/null +} + +check_coq_exports() { + local FILE=$1 + local FAIL_MODE=$2 + local PROTOTYPE_BUILD_DIR="coq-build" + local EXPORTED_LEMMAS="ExportedLemmas.v" + local result=0 #^track if the build completed as much as expected + + printf "[$FILE]... " + + # Make a copy of the build directory but only if it doesn't + # already exists + local BUILD_DIR="${FILE%.*}-build" + + # Copy the build directory, and/or missing/new files + rsync -a "${PROTOTYPE_BUILD_DIR}/" "$BUILD_DIR" + + # Export the CN lemmas + cn "--lemmata=${BUILD_DIR}/theories/${EXPORTED_LEMMAS}" $FILE > /dev/null 2>&1 + # Check the result is as expected + local cn_result=$? + if [[ $cn_result -ne 0 && $FAIL_MODE -eq $FAIL_EXPORT ]]; then + # The export is expected to fail and there is nothing else to + # be done. Return successfully. + check_coq_exports_end ${result} "" + return ${result} + elif [[ $cn_result -eq 0 && $FAIL_MODE -ne $FAIL_EXPORT ]]; then + : # Export succeeded, as expected, continue the build + else + # Otherwise fail + result=1 + check_coq_exports_end ${result} "Unexpected return code during export: $cn_result" + return ${result} + fi + + # The rest of the commands must be performed in the build directory + pushd "$BUILD_DIR" > /dev/null + + # Create the Coq Makefile + # (We don't expect this to fail) + coq_makefile -f _CoqProject -o Makefile.coq > /dev/null + + # Build the Coq files + make -f Makefile.coq > /dev/null + # Check the result is as expected + local coq_result=$? + if [[ $coq_result -ne 0 && $FAIL_MODE -eq $FAIL_COQ_BUILD ]]; then + # The coq build is expected to fail and there is nothing else to + # be done. Return successfully. + check_coq_exports_end ${result} "" + return ${result} + elif [[ $coq_result -eq 0 && $FAIL_MODE -ne $FAIL_COQ_BUILD ]]; then + : # Export succeeded, as expected + else + result=1 + check_coq_exports_end ${result} "Unexpected return code during coq build: $coq_result" + return ${result} + fi + + # At this point everythink built successfully. + check_coq_exports_end ${result} "" + return ${result} + +} + +printf "=========\nChecking Coq builds\n\n" +check_coq_exports "coq/working/trivial-001.c" ${SUCCESS} +check_coq_exports "coq/broken-export/recursive-001.c" ${FAIL_EXPORT} + if [[ "$failures" = 0 ]] then From f50f637ed6f9941741b9a7bd7e0966e7c5dba726 Mon Sep 17 00:00:00 2001 From: scuellar Date: Wed, 10 Jul 2024 13:20:40 -0400 Subject: [PATCH 027/152] Explain the new organization, including Coq lemmas. --- src/example-archive/README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/example-archive/README.md b/src/example-archive/README.md index 2f271f21..610eebc4 100644 --- a/src/example-archive/README.md +++ b/src/example-archive/README.md @@ -26,6 +26,11 @@ are categorized according to the following schema. * `/broken/error-proof` - examples where CN fails with error 1, indicating the proof failed. * `/broken/error-timeout` - examples where CN times out after 60s. +* `/coq/*` - examples that CN verifies without error and have lemmas that should be extracted. According to the following rules + * `/coq/working` - Examples where Coq lemmas can be extracted and the Coq project can be built. + * `/coq/broken-build` - Examples where Coq lemmas can be extracted, but the Coq build process fails. + * `/coq/broken-export` - Examples where Coq extraction fails. These should still be verifiable with CN. +* `/coq/working` - examples that CN verifies without error. * `/inprogress` - unfinished examples. ## Check script From f85a420ec70dd749eab064951a8909d8345e596d Mon Sep 17 00:00:00 2001 From: scuellar Date: Wed, 10 Jul 2024 14:10:08 -0400 Subject: [PATCH 028/152] Generalize process_files and use it to check Coq extraction --- src/example-archive/check.sh | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/example-archive/check.sh b/src/example-archive/check.sh index ac275950..f41101ca 100755 --- a/src/example-archive/check.sh +++ b/src/example-archive/check.sh @@ -1,5 +1,4 @@ #!/usr/bin/env bash -set -x if [ -n "$1" ] then @@ -9,13 +8,11 @@ else CN=cn fi - - process_files() { local dir=$1 local pattern=$2 local action=$3 - local expected_exit_code=$4 + local action_argument=$4 if [ -d "$dir" ]; then # Array to hold files matching the pattern @@ -26,7 +23,7 @@ process_files() { for file in "${files[@]}"; do # Ensure the file variable is not empty if [[ -n "$file" ]]; then - "$action" "$file" "$expected_exit_code" + "$action" "$file" "$action_argument" fi done else @@ -39,6 +36,10 @@ process_files() { failures=0 +# ==================== +# Check CN verification +# ==================== + check_file() { local file=$1 local expected_exit_code=$2 @@ -82,7 +83,7 @@ check_coq_exports_end() { local FAILED=$1 local MESSAGE=$2 - if [[FAILED]]; then + if [[ $FAILED -ne 0 ]]; then printf "\033[31mFAIL\033[0m (${MESSAGE})\n" failures=$(( $failures + 1 )) else @@ -158,8 +159,10 @@ check_coq_exports() { } printf "=========\nChecking Coq builds\n\n" -check_coq_exports "coq/working/trivial-001.c" ${SUCCESS} -check_coq_exports "coq/broken-export/recursive-001.c" ${FAIL_EXPORT} + +process_files "coq/working" "*.c" check_coq_exports $SUCCESS +process_files "coq/broken-build" "*.c" check_coq_exports $FAIL_COQ_BUILD +process_files "coq/broken-export" "*.c" check_coq_exports $FAIL_EXPORT if [[ "$failures" = 0 ]] From b5b722d3c1c058e1eec0a8022665e69db5bbcb42 Mon Sep 17 00:00:00 2001 From: scuellar Date: Wed, 10 Jul 2024 14:23:28 -0400 Subject: [PATCH 029/152] Ignore the build files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index f12c5c6f..384f6f98 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ build/* /.vscode/ +/src/example-archive/**/coq/**/*-build \ No newline at end of file From b7e3ca0ea31d11e893ca31c08c63d1d48fa20dcb Mon Sep 17 00:00:00 2001 From: scuellar Date: Wed, 10 Jul 2024 16:26:28 -0400 Subject: [PATCH 030/152] Ignore coq files, except the proofs. This affects only the example-archive --- .gitignore | 3 +-- src/example-archive/.gitignore | 3 +++ 2 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 src/example-archive/.gitignore diff --git a/.gitignore b/.gitignore index 384f6f98..2a39c64d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ build/* -/.vscode/ -/src/example-archive/**/coq/**/*-build \ No newline at end of file +/.vscode/ \ No newline at end of file diff --git a/src/example-archive/.gitignore b/src/example-archive/.gitignore new file mode 100644 index 00000000..c69bf9fd --- /dev/null +++ b/src/example-archive/.gitignore @@ -0,0 +1,3 @@ +/**/coq/**/*-build + +!/**/coq/**/*-build/theories/Proofs.v \ No newline at end of file From 35deed994d53fe1a141b3bbf99f644abd3c33de0 Mon Sep 17 00:00:00 2001 From: scuellar Date: Wed, 10 Jul 2024 16:53:20 -0400 Subject: [PATCH 031/152] Add the proofs --- src/example-archive/.gitignore | 7 +++-- .../coq-lemmas/coq-build/_CoqProject | 5 ++-- .../trivial-002-build/theories/Proofs.v | 24 +++++++++++++++++ .../trivial-004-build/theories/Proofs.v | 27 +++++++++++++++++++ .../trivial-001-build/theories/Proofs.v | 21 +++++++++++++++ .../trivial-003-build/theories/Proofs.v | 23 ++++++++++++++++ 6 files changed, 102 insertions(+), 5 deletions(-) create mode 100644 src/example-archive/coq-lemmas/coq/broken-build/trivial-002-build/theories/Proofs.v create mode 100644 src/example-archive/coq-lemmas/coq/broken-build/trivial-004-build/theories/Proofs.v create mode 100644 src/example-archive/coq-lemmas/coq/working/trivial-001-build/theories/Proofs.v create mode 100644 src/example-archive/coq-lemmas/coq/working/trivial-003-build/theories/Proofs.v diff --git a/src/example-archive/.gitignore b/src/example-archive/.gitignore index c69bf9fd..d863d5fb 100644 --- a/src/example-archive/.gitignore +++ b/src/example-archive/.gitignore @@ -1,3 +1,6 @@ -/**/coq/**/*-build +/**/coq/**/*-build/* +/**/coq/**/*-build/theories/* -!/**/coq/**/*-build/theories/Proofs.v \ No newline at end of file +!**/coq/**/*-build/ +!**/coq/**/*-build/theories/ +!**/coq/**/*-build/theories/Proofs.v \ No newline at end of file diff --git a/src/example-archive/coq-lemmas/coq-build/_CoqProject b/src/example-archive/coq-lemmas/coq-build/_CoqProject index 065c4ea8..efc08743 100644 --- a/src/example-archive/coq-lemmas/coq-build/_CoqProject +++ b/src/example-archive/coq-lemmas/coq-build/_CoqProject @@ -1,4 +1,3 @@ --Q theories CN_Lemmas +-R theories CN_Lemmas -theories/CN_Lib.v -theories/ExportedLemmas.v \ No newline at end of file +theories \ No newline at end of file diff --git a/src/example-archive/coq-lemmas/coq/broken-build/trivial-002-build/theories/Proofs.v b/src/example-archive/coq-lemmas/coq/broken-build/trivial-002-build/theories/Proofs.v new file mode 100644 index 00000000..7a08e7ed --- /dev/null +++ b/src/example-archive/coq-lemmas/coq/broken-build/trivial-002-build/theories/Proofs.v @@ -0,0 +1,24 @@ +Require Import ZArith Bool. +Require CN_Lemmas.CN_Lib. +Require Import CN_Lemmas.ExportedLemmas. + + +(*Parameters*) +Module ConcreteParameters <:Parameters. +End ConcreteParameters. + +(*Definitions*) +Module ConcreteDefs := Defs(ConcreteParameters). + +Module ConcreteLemmaSpec <: Lemma_Spec(ConcreteParameters). + Module D := ConcreteDefs. + Import D. + + Definition lem_impossible_in_coq : lem_impossible_in_coq_type. + Proof. unfold lem_impossible_in_coq_type. + (* Goal: forall x : Z, x <= 4294967295*) + (* That's impossible to prove! *) + Abort. Qed. + +End ConcreteLemmaSpec. + diff --git a/src/example-archive/coq-lemmas/coq/broken-build/trivial-004-build/theories/Proofs.v b/src/example-archive/coq-lemmas/coq/broken-build/trivial-004-build/theories/Proofs.v new file mode 100644 index 00000000..c17457a0 --- /dev/null +++ b/src/example-archive/coq-lemmas/coq/broken-build/trivial-004-build/theories/Proofs.v @@ -0,0 +1,27 @@ +Require Import ZArith Bool. +Require CN_Lemmas.CN_Lib. +Require Import CN_Lemmas.ExportedLemmas. + + +(*Parameters*) +Module ConcreteParameters <:Parameters. +End ConcreteParameters. + +(*Definitions*) +Module ConcreteDefs := Defs(ConcreteParameters). + +Module ConcreteLemmaSpec <: Lemma_Spec(ConcreteParameters). + Module D := ConcreteDefs. + Import D. + + Definition lem_impossible_in_Coq : lem_impossible_in_Coq_type. + Proof. unfold lem_impossible_in_Coq_type. + (* Goal: forall x : Z, + CN_Lib.wrapI 0 4294967295 + (CN_Lib.wrapI 0 4294967295 (x + 1) - 1) = x *) + (* That's impossible to prove! The LHS is always in bound, + but x can be any Z *) + Abort. Qed. + +End ConcreteLemmaSpec. + diff --git a/src/example-archive/coq-lemmas/coq/working/trivial-001-build/theories/Proofs.v b/src/example-archive/coq-lemmas/coq/working/trivial-001-build/theories/Proofs.v new file mode 100644 index 00000000..6101a8b4 --- /dev/null +++ b/src/example-archive/coq-lemmas/coq/working/trivial-001-build/theories/Proofs.v @@ -0,0 +1,21 @@ +Require Import ZArith Bool. +Require CN_Lemmas.CN_Lib. +Require Import CN_Lemmas.ExportedLemmas. + + +(*Parameters*) +Module ConcreteParameters <:Parameters. +End ConcreteParameters. + +(*Definitions*) +Module ConcreteDefs := Defs(ConcreteParameters). + +Module ConcreteLemmaSpec <: Lemma_Spec(ConcreteParameters). + Module D := ConcreteDefs. + Import D. + + Definition lem_trivial : lem_trivial_type. + Proof. unfold lem_trivial_type; exact I. Qed. + +End ConcreteLemmaSpec. + diff --git a/src/example-archive/coq-lemmas/coq/working/trivial-003-build/theories/Proofs.v b/src/example-archive/coq-lemmas/coq/working/trivial-003-build/theories/Proofs.v new file mode 100644 index 00000000..91d03fc8 --- /dev/null +++ b/src/example-archive/coq-lemmas/coq/working/trivial-003-build/theories/Proofs.v @@ -0,0 +1,23 @@ +Require Import ZArith Bool. +Require CN_Lemmas.CN_Lib. +Require Import CN_Lemmas.ExportedLemmas. + + +(*Parameters*) +Module ConcreteParameters <:Parameters. +End ConcreteParameters. + +(*Definitions*) +Module ConcreteDefs := Defs(ConcreteParameters). + +Module ConcreteLemmaSpec <: Lemma_Spec(ConcreteParameters). + Module D := ConcreteDefs. + Import D. + + Definition lem_ineq : lem_ineq_type. + Proof. unfold lem_ineq_type. + constructor; assumption. + Qed. + +End ConcreteLemmaSpec. + From 55e525f4ad7b9e73d12d8c4141c32a69f8795b73 Mon Sep 17 00:00:00 2001 From: scuellar Date: Wed, 10 Jul 2024 16:54:33 -0400 Subject: [PATCH 032/152] Move examples that fail to build/incomplete proofs. --- .../coq-lemmas/coq/working/trivial-002.c | 10 ---------- .../coq-lemmas/coq/working/trivial-004.c | 10 ---------- 2 files changed, 20 deletions(-) delete mode 100644 src/example-archive/coq-lemmas/coq/working/trivial-002.c delete mode 100644 src/example-archive/coq-lemmas/coq/working/trivial-004.c diff --git a/src/example-archive/coq-lemmas/coq/working/trivial-002.c b/src/example-archive/coq-lemmas/coq/working/trivial-002.c deleted file mode 100644 index 0d2cc978..00000000 --- a/src/example-archive/coq-lemmas/coq/working/trivial-002.c +++ /dev/null @@ -1,10 +0,0 @@ -/*@ - lemma lem_impossible_in_coq (u32 x) - requires true; - ensures x <= 4294967295u32; -@*/ - -void nothing() -{ - ; -} diff --git a/src/example-archive/coq-lemmas/coq/working/trivial-004.c b/src/example-archive/coq-lemmas/coq/working/trivial-004.c deleted file mode 100644 index 51c47e76..00000000 --- a/src/example-archive/coq-lemmas/coq/working/trivial-004.c +++ /dev/null @@ -1,10 +0,0 @@ -/*@ - lemma lem_impossible_in_Coq (u32 x) - requires true; - ensures (x + 1u32) - 1u32 == x; -@*/ - -void trivial() -{ - ; -} From a2a431a8d4cce73bba1bcd9f5f67851c0686e9be Mon Sep 17 00:00:00 2001 From: scuellar Date: Wed, 10 Jul 2024 16:55:15 -0400 Subject: [PATCH 033/152] Fix the popd bug and add titles to the two sections. --- src/example-archive/check.sh | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/example-archive/check.sh b/src/example-archive/check.sh index f41101ca..bc60a94a 100755 --- a/src/example-archive/check.sh +++ b/src/example-archive/check.sh @@ -56,6 +56,7 @@ check_file() { fi } +printf "== Check CN verification \n" process_files "working" "*.c" check_file 0 process_files "broken/error-cerberus" "*.c" check_file 2 process_files "broken/error-crash" "*.c" check_file 125 @@ -89,9 +90,6 @@ check_coq_exports_end() { else printf "\033[32mPASS\033[0m\n" fi - - # Return to the directory where the parent function was called - popd > /dev/null } check_coq_exports() { @@ -136,30 +134,31 @@ check_coq_exports() { coq_makefile -f _CoqProject -o Makefile.coq > /dev/null # Build the Coq files - make -f Makefile.coq > /dev/null + make -f Makefile.coq > /dev/null 2>&1 # Check the result is as expected local coq_result=$? if [[ $coq_result -ne 0 && $FAIL_MODE -eq $FAIL_COQ_BUILD ]]; then # The coq build is expected to fail and there is nothing else to # be done. Return successfully. check_coq_exports_end ${result} "" - return ${result} elif [[ $coq_result -eq 0 && $FAIL_MODE -ne $FAIL_COQ_BUILD ]]; then - : # Export succeeded, as expected + # Export succeeded, as expected + check_coq_exports_end ${result} "" else result=1 check_coq_exports_end ${result} "Unexpected return code during coq build: $coq_result" - return ${result} fi # At this point everythink built successfully. - check_coq_exports_end ${result} "" - return ${result} - -} -printf "=========\nChecking Coq builds\n\n" + # Return to the directory where the script was called (from the + # build directory) + popd > /dev/null + + return ${result} +} +printf "== Check lemma export\n" process_files "coq/working" "*.c" check_coq_exports $SUCCESS process_files "coq/broken-build" "*.c" check_coq_exports $FAIL_COQ_BUILD process_files "coq/broken-export" "*.c" check_coq_exports $FAIL_EXPORT From 32b2a8c2f733bed4e8cef022c15ac0a08c51ed7d Mon Sep 17 00:00:00 2001 From: scuellar Date: Wed, 10 Jul 2024 18:08:06 -0400 Subject: [PATCH 034/152] Move the build folder prototype to the parent directory to be used by any other examples set. --- src/example-archive/check.sh | 2 +- src/example-archive/coq-build/README.md | 5 +++++ .../{coq-lemmas => }/coq-build/_CoqProject | 0 .../{coq-lemmas => }/coq-build/theories/CN_Lib.v | 0 .../coq-lemmas/coq-build/theories/.CN_Lib.aux | 7 ------- .../coq-build/theories/.ExportedLemmas.aux | 2 -- .../coq-lemmas/coq-build/theories/.lia.cache | Bin 1312 -> 0 bytes 7 files changed, 6 insertions(+), 10 deletions(-) create mode 100644 src/example-archive/coq-build/README.md rename src/example-archive/{coq-lemmas => }/coq-build/_CoqProject (100%) rename src/example-archive/{coq-lemmas => }/coq-build/theories/CN_Lib.v (100%) delete mode 100644 src/example-archive/coq-lemmas/coq-build/theories/.CN_Lib.aux delete mode 100644 src/example-archive/coq-lemmas/coq-build/theories/.ExportedLemmas.aux delete mode 100644 src/example-archive/coq-lemmas/coq-build/theories/.lia.cache diff --git a/src/example-archive/check.sh b/src/example-archive/check.sh index bc60a94a..d6981561 100755 --- a/src/example-archive/check.sh +++ b/src/example-archive/check.sh @@ -95,7 +95,7 @@ check_coq_exports_end() { check_coq_exports() { local FILE=$1 local FAIL_MODE=$2 - local PROTOTYPE_BUILD_DIR="coq-build" + local PROTOTYPE_BUILD_DIR="../coq-build" local EXPORTED_LEMMAS="ExportedLemmas.v" local result=0 #^track if the build completed as much as expected diff --git a/src/example-archive/coq-build/README.md b/src/example-archive/coq-build/README.md new file mode 100644 index 00000000..7816aca6 --- /dev/null +++ b/src/example-archive/coq-build/README.md @@ -0,0 +1,5 @@ +## Lemma export build forlder + +This folder will be copied to be used as a build folder for exported lemmas. Lemmas should be extracted into the `theories/ExportedLemmas.v` and proofs, if any, should be written in `theories/Proofs.v` + +From the `_CoqProject` a Makefile should be generated automatically. diff --git a/src/example-archive/coq-lemmas/coq-build/_CoqProject b/src/example-archive/coq-build/_CoqProject similarity index 100% rename from src/example-archive/coq-lemmas/coq-build/_CoqProject rename to src/example-archive/coq-build/_CoqProject diff --git a/src/example-archive/coq-lemmas/coq-build/theories/CN_Lib.v b/src/example-archive/coq-build/theories/CN_Lib.v similarity index 100% rename from src/example-archive/coq-lemmas/coq-build/theories/CN_Lib.v rename to src/example-archive/coq-build/theories/CN_Lib.v diff --git a/src/example-archive/coq-lemmas/coq-build/theories/.CN_Lib.aux b/src/example-archive/coq-lemmas/coq-build/theories/.CN_Lib.aux deleted file mode 100644 index 540ba548..00000000 --- a/src/example-archive/coq-lemmas/coq-build/theories/.CN_Lib.aux +++ /dev/null @@ -1,7 +0,0 @@ -COQAUX1 633e54b5a22b850599c444b8737dbf06 /Users/Santiago/Projects/cn-tutorial/src/example-archive/coq-lemmas/working/trivial-001-build/theories/CN_Lib.v -0 0 VernacProof "tac:no using:no" -748 752 proof_build_time "0.009" -0 0 wrapI_idem "0.009" -399 412 context_used "" -748 752 proof_check_time "0.003" -0 0 vo_compile_time "0.191" diff --git a/src/example-archive/coq-lemmas/coq-build/theories/.ExportedLemmas.aux b/src/example-archive/coq-lemmas/coq-build/theories/.ExportedLemmas.aux deleted file mode 100644 index a9c2953c..00000000 --- a/src/example-archive/coq-lemmas/coq-build/theories/.ExportedLemmas.aux +++ /dev/null @@ -1,2 +0,0 @@ -COQAUX1 3900f32800d8cfc6593331bb6ddc51b3 /Users/Santiago/Projects/cn-tutorial/src/example-archive/coq-lemmas/working/trivial-001-build/theories/ExportedLemmas.v -0 0 vo_compile_time "0.168" diff --git a/src/example-archive/coq-lemmas/coq-build/theories/.lia.cache b/src/example-archive/coq-lemmas/coq-build/theories/.lia.cache deleted file mode 100644 index c936905014c614dd5ec5054ef868ae50e0441f2c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1312 zcmb`G&nrbi6vszzQWk`bof50}SaW;IyxvMFJ1MLzgd}@QOJ(jtZ=>9o*s0h311M!- zX<<)USy?KD?>Tek{&KUqrq0~?dFJyy=iK)G!Gu!t&wID`$494EH%h4!*fQ*qEQ)%q zR&C+iwJ3_MH72$pbr@mLrp09Lrn=vB-6=~8G}gm%xEp46us&|mGgEGh{gHcPqs!2u z8GH;LnLPXm1TzNSwo-jVWJOH>dWy%-5^*`9CB%_(M7*2Ri8(8pNy%A1cRDbh`~tU_ zUIZi`QZlx?)D$H>DA|IF0kSeB`9N1xq8n+(yprisC3Al%fkj`OIWXgyrf{pBhRO{74#UBspfkiE}+Db;ob&S-FLMs;OLNaAN)I z?m$SI^u5>k90JtjL_W_Q6gwUXh&ZC2?O2bCw*qKk$xA A>i_@% From be83fa4733bc45c8574d379db29e799cd1d791ef Mon Sep 17 00:00:00 2001 From: scuellar Date: Wed, 10 Jul 2024 18:08:35 -0400 Subject: [PATCH 035/152] Foolproof build --- src/example-archive/coq-lemmas/README.md | 107 ++++++++++++----------- 1 file changed, 57 insertions(+), 50 deletions(-) diff --git a/src/example-archive/coq-lemmas/README.md b/src/example-archive/coq-lemmas/README.md index 6bd82f61..6996f0f2 100644 --- a/src/example-archive/coq-lemmas/README.md +++ b/src/example-archive/coq-lemmas/README.md @@ -6,63 +6,70 @@ are split into: - Trivial - Recursive (Not yet handled by the export) -## Generating Coq Lemmas - -To generate Coq lemma for a given `example-file.c` run - -``` -cn --lemmata=build/theories/ExportedLemmas.v example-file.c -``` - -File `build/theories/ExportedLemmas.v` should be generated with the -right definitions. Each lemma is exported as a new definition and then -added as a parameter in the module named `Lemma_Spec`. It should look -something like this +## Batch build + +To export and build lemmas for all examples just run +`../check.sh`. For each file in the `coq` folder, the script first +checks the CN verification and then exports lemmas to Coq and builds +the Coq files. When proofs are provided, those will be built too. + +To provide proofs or test individual examples, see below. + +## Testing individual examples + +From this folder, to export lemmas from the example `path/to/example.c`, do the following: + +0. (optional) Check CN verification, without exporting lemmas, `cn path/to/example.c` +1. Create a copy of the build folder with `rsync -a ../coq-build + path/to/example-build`. This copies a template build folder that + conveniently contains a `_CoqProject` file and the CN coq library + `CN_Lib.v`. If the folder already excists, `rsync` just updates the files. +2. Extract the lemmas with `cn + "--lemmata=path/to/example-build/theories/ExportedLemmas.v" + path/to/example.c`. This should create a new file + `path/to/example-build/theories/ExportedLemmas.v` with all the + exported types, definitions and lemmas from the file + `path/to/example.c`. +3. Go to the build directory with `pushd path/to/example-build`. This + will also store your current location to return later. +4. Create or update the Coq Makefile with `coq_makefile -f _CoqProject -o Makefile.coq` +5. Build the Coq files with `make -f Makefile.coq`. This should create + `*.vo` files for every `*.v` file in the `theories` directory. +6. Return to your starting folder with `popd` + +To add proofs, after running the steps above, create a file `Proofs.v` +in the `theories` folder, next to the generated +`ExportedLemmas.v`. The file must contain instances of the module +types defined in `ExportedLemmas.v`: `Parameters`, `Defs`, and +`Lemma_Spec` module type (see below for more details). + +Your file will look something like this: ``` +Require Import ZArith Bool. +Require CN_Lemmas.CN_Lib. +Require Import CN_Lemmas.ExportedLemmas. -Module Defs (P : Parameters). - - Import Types P. - Open Scope Z. +(*Parameters*) +Module ConcreteParameters <:Parameters. +(*Fill parameters here, if any*) +End ConcreteParameters. - Definition my_lemma_type : Prop := - Is_true true. +(*Definitions*) +Module ConcreteDefs := Defs(ConcreteParameters). -End Defs. - - -Module Type Lemma_Spec (P : Parameters). - - Module D := Defs(P). +Module ConcreteLemmaSpec <: Lemma_Spec(ConcreteParameters). + Module D := ConcreteDefs. Import D. - Parameter my_lemma : my_lemma_type. - -End Lemma_Spec. -``` - -## Proving the Coq Lemmas - -To prove the lemmas, instantiate a new module with type `Lemma_Spec` -containing each of parameters as lemmas and their proofs. For the -example above, the proofs look like this - -``` - -Module MyP: Parameters. -End MyP. - -Module Proofs : Lemma_Spec MyP. - Module D := Defs(MyP). - Import D. - - Lemma just_arith2 : my_lemma_type. - Proof. - solve [hnf; trivial]. - Qed. - -End Proofs. + (*Prove the lemmas, if any. *) + Definition example_lemma : example_lemma_type. + Proof. (*Add here the proof*) . Qed. +End ConcreteLemmaSpec. ``` + +Once all the proofs have been completed in `Proofs.v`, repeat steps +3-6 above to build all files. If `Proofs.vo` is generated correctly, +the extracted lemmas have been proven. From 6913e1e1bba74d44343f71030d84f4e2049e00bf Mon Sep 17 00:00:00 2001 From: scuellar Date: Wed, 10 Jul 2024 18:11:21 -0400 Subject: [PATCH 036/152] README details --- src/example-archive/README.md | 1 + src/example-archive/coq-lemmas/README.md | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/example-archive/README.md b/src/example-archive/README.md index 610eebc4..d133b3a2 100644 --- a/src/example-archive/README.md +++ b/src/example-archive/README.md @@ -11,6 +11,7 @@ This directory contains examples for CN. Each subdirectory contains examples fro * `SAW` - Examples derived from the [Software Analysis Workbench (SAW)](https://saw.galois.com) repository and tutorial. * `should-fail` - Small examples that should always fail. * `open-sut` - Examples inspired by the VERSE Open System Under Test (Open SUT). +* `coq-lemmas` - Examples with declared lemmas that can be exported to Coq for manual proofs. ## Organization diff --git a/src/example-archive/coq-lemmas/README.md b/src/example-archive/coq-lemmas/README.md index 6996f0f2..57a8488b 100644 --- a/src/example-archive/coq-lemmas/README.md +++ b/src/example-archive/coq-lemmas/README.md @@ -6,6 +6,8 @@ are split into: - Trivial - Recursive (Not yet handled by the export) +See README in parent directory for directory organization details. + ## Batch build To export and build lemmas for all examples just run From f9d179f0a46b1aacb602444c9726705d0f23a86d Mon Sep 17 00:00:00 2001 From: scuellar Date: Wed, 10 Jul 2024 18:14:49 -0400 Subject: [PATCH 037/152] remove old build folder --- src/example-archive/coq-lemmas/build/Makefile | 8 ---- .../coq-lemmas/build/_CoqProject | 7 --- .../coq-lemmas/build/theories/CN_Lib.v | 29 ------------- .../build/theories/ExportedLemmas.v | 6 --- .../coq-lemmas/build/theories/Gen_Spec.v | 43 ------------------- 5 files changed, 93 deletions(-) delete mode 100644 src/example-archive/coq-lemmas/build/Makefile delete mode 100644 src/example-archive/coq-lemmas/build/_CoqProject delete mode 100644 src/example-archive/coq-lemmas/build/theories/CN_Lib.v delete mode 100644 src/example-archive/coq-lemmas/build/theories/ExportedLemmas.v delete mode 100644 src/example-archive/coq-lemmas/build/theories/Gen_Spec.v diff --git a/src/example-archive/coq-lemmas/build/Makefile b/src/example-archive/coq-lemmas/build/Makefile deleted file mode 100644 index 819ea988..00000000 --- a/src/example-archive/coq-lemmas/build/Makefile +++ /dev/null @@ -1,8 +0,0 @@ - - - -all: - coq_makefile -f _CoqProject -o Makefile.coq - cn ../../SantiagosExamples/trivial-lemma.c --lemmata theories/Gen_Spec.v - make -f Makefile.coq - diff --git a/src/example-archive/coq-lemmas/build/_CoqProject b/src/example-archive/coq-lemmas/build/_CoqProject deleted file mode 100644 index 917968e6..00000000 --- a/src/example-archive/coq-lemmas/build/_CoqProject +++ /dev/null @@ -1,7 +0,0 @@ - --Q theories CN_Lemmas - -theories/CN_Lib.v -theories/ExportedLemmas.v - - diff --git a/src/example-archive/coq-lemmas/build/theories/CN_Lib.v b/src/example-archive/coq-lemmas/build/theories/CN_Lib.v deleted file mode 100644 index e17d738a..00000000 --- a/src/example-archive/coq-lemmas/build/theories/CN_Lib.v +++ /dev/null @@ -1,29 +0,0 @@ -Require List. -Require Import ZArith Bool. -Require Import Lia. -Require NArith. - -Definition wrapI (minInt : Z) (maxInt : Z) x := - let delta := ((maxInt - minInt) + 1)%Z in - let r := Z.modulo x delta in - (if (r <=? maxInt) then r else r - delta)%Z. - -Lemma wrapI_idem: - forall (minInt maxInt x : Z), - (minInt <= x <= maxInt)%Z -> - (minInt <= 0 < maxInt)%Z -> - wrapI minInt maxInt x = x. -Proof. - Open Scope Z. - intros. - unfold wrapI. - pose (delta := ((maxInt - minInt) + 1)). - destruct (0 <=? x) eqn: x_neg. - - rewrite Z.mod_small by lia. - rewrite Zle_imp_le_bool by lia. - reflexivity. - - rewrite (Znumtheory.Zdivide_mod_minus _ _ (x + delta)). - + destruct (x + delta <=? maxInt) eqn: leb; lia. - + lia. - + exists (-1); lia. -Qed. diff --git a/src/example-archive/coq-lemmas/build/theories/ExportedLemmas.v b/src/example-archive/coq-lemmas/build/theories/ExportedLemmas.v deleted file mode 100644 index e8fec956..00000000 --- a/src/example-archive/coq-lemmas/build/theories/ExportedLemmas.v +++ /dev/null @@ -1,6 +0,0 @@ -(* build/theories/ExportedLemmas.v: generated lemma specifications from CN *) - -Require Import ZArith Bool. -Require CN_Lemmas.CN_Lib. - - diff --git a/src/example-archive/coq-lemmas/build/theories/Gen_Spec.v b/src/example-archive/coq-lemmas/build/theories/Gen_Spec.v deleted file mode 100644 index 7254a094..00000000 --- a/src/example-archive/coq-lemmas/build/theories/Gen_Spec.v +++ /dev/null @@ -1,43 +0,0 @@ -(* theories/Gen_Spec.v: generated lemma specifications from CN *) - -Require Import ZArith Bool. -Require CN_Lemmas.CN_Lib. - - -Module Types. - - (* no type definitions required *) - -End Types. - - -Module Type Parameters. - Import Types. - - (* no parameters required *) - -End Parameters. - - -Module Defs (P : Parameters). - - Import Types P. - Open Scope Z. - - - Definition lem_trivial_type : Prop := - Is_true true. - -End Defs. - - -Module Type Lemma_Spec (P : Parameters). - - Module D := Defs(P). - Import D. - - Parameter lem_trivial : lem_trivial_type. - -End Lemma_Spec. - - From 7f8d63264a77305f1b9bb77bfd5faf2df20c6f36 Mon Sep 17 00:00:00 2001 From: scuellar Date: Wed, 10 Jul 2024 18:17:27 -0400 Subject: [PATCH 038/152] clean up --- src/example-archive/coq-lemmas/README.md | 48 +++++++++++++++++------- 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/src/example-archive/coq-lemmas/README.md b/src/example-archive/coq-lemmas/README.md index 57a8488b..9c01a672 100644 --- a/src/example-archive/coq-lemmas/README.md +++ b/src/example-archive/coq-lemmas/README.md @@ -11,7 +11,9 @@ See README in parent directory for directory organization details. ## Batch build To export and build lemmas for all examples just run -`../check.sh`. For each file in the `coq` folder, the script first +`../check.sh`. + +For each file in the `coq` folder, the script first checks the CN verification and then exports lemmas to Coq and builds the Coq files. When proofs are provided, those will be built too. @@ -19,25 +21,45 @@ To provide proofs or test individual examples, see below. ## Testing individual examples -From this folder, to export lemmas from the example `path/to/example.c`, do the following: +From this folder, to export lemmas from example `path/to/example.c`, do the following: + +0. (optional) Check CN verification, without exporting lemmas, with + + `cn path/to/example.c` + +1. Create a copy of the build folder with -0. (optional) Check CN verification, without exporting lemmas, `cn path/to/example.c` -1. Create a copy of the build folder with `rsync -a ../coq-build + `rsync -a ../coq-build + path/to/example-build`. This copies a template build folder that conveniently contains a `_CoqProject` file and the CN coq library `CN_Lib.v`. If the folder already excists, `rsync` just updates the files. -2. Extract the lemmas with `cn - "--lemmata=path/to/example-build/theories/ExportedLemmas.v" - path/to/example.c`. This should create a new file +2. Extract the lemmas with + + `cn --lemmata=path/to/example-build/theories/ExportedLemmas.v path/to/example.c` + + This should create a new file `path/to/example-build/theories/ExportedLemmas.v` with all the exported types, definitions and lemmas from the file `path/to/example.c`. -3. Go to the build directory with `pushd path/to/example-build`. This - will also store your current location to return later. -4. Create or update the Coq Makefile with `coq_makefile -f _CoqProject -o Makefile.coq` -5. Build the Coq files with `make -f Makefile.coq`. This should create - `*.vo` files for every `*.v` file in the `theories` directory. -6. Return to your starting folder with `popd` +3. Go to the build directory with + + `pushd path/to/example-build` + + This will also store your current location to return later. +4. Create or update the Coq Makefile with + + `coq_makefile -f _CoqProject -o Makefile.coq` + +5. Build the Coq files with + + `make -f Makefile.coq` + + This should create `*.vo` files for every `*.v` file in the + `theories` directory. +6. Return to your starting folder with + + `popd` To add proofs, after running the steps above, create a file `Proofs.v` in the `theories` folder, next to the generated From be21ea39c0027ce7309d06e0f8f88e45e6a371fa Mon Sep 17 00:00:00 2001 From: scuellar Date: Thu, 11 Jul 2024 09:41:09 -0400 Subject: [PATCH 039/152] Fix Typos --- src/example-archive/coq-lemmas/README.md | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/example-archive/coq-lemmas/README.md b/src/example-archive/coq-lemmas/README.md index 9c01a672..38fa117e 100644 --- a/src/example-archive/coq-lemmas/README.md +++ b/src/example-archive/coq-lemmas/README.md @@ -8,6 +8,11 @@ are split into: See README in parent directory for directory organization details. +## Tools needed + +To build the generated Coq lemmas, you will need to [download and +install Coq](download). + ## Batch build To export and build lemmas for all examples just run @@ -29,11 +34,11 @@ From this folder, to export lemmas from example `path/to/example.c`, do the foll 1. Create a copy of the build folder with - `rsync -a ../coq-build - - path/to/example-build`. This copies a template build folder that - conveniently contains a `_CoqProject` file and the CN coq library - `CN_Lib.v`. If the folder already excists, `rsync` just updates the files. + `rsync -a ../coq-build path/to/example-build` + + This copies a template build folder that conveniently contains a + `_CoqProject` file and the CN coq library `CN_Lib.v`. If the folder + already excists, `rsync` just updates the files. 2. Extract the lemmas with `cn --lemmata=path/to/example-build/theories/ExportedLemmas.v path/to/example.c` @@ -65,9 +70,9 @@ To add proofs, after running the steps above, create a file `Proofs.v` in the `theories` folder, next to the generated `ExportedLemmas.v`. The file must contain instances of the module types defined in `ExportedLemmas.v`: `Parameters`, `Defs`, and -`Lemma_Spec` module type (see below for more details). +`Lemma_Spec` module type. -Your file will look something like this: +Your `theories/Proofs.v` file will look something like this: ``` Require Import ZArith Bool. From 60eb8955c219ea244385bb4dab072af9dc1445e9 Mon Sep 17 00:00:00 2001 From: scuellar Date: Thu, 11 Jul 2024 09:46:45 -0400 Subject: [PATCH 040/152] Add clarity --- src/example-archive/coq-lemmas/README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/example-archive/coq-lemmas/README.md b/src/example-archive/coq-lemmas/README.md index 38fa117e..805426a6 100644 --- a/src/example-archive/coq-lemmas/README.md +++ b/src/example-archive/coq-lemmas/README.md @@ -16,6 +16,7 @@ install Coq](download). ## Batch build To export and build lemmas for all examples just run + `../check.sh`. For each file in the `coq` folder, the script first @@ -34,9 +35,10 @@ From this folder, to export lemmas from example `path/to/example.c`, do the foll 1. Create a copy of the build folder with - `rsync -a ../coq-build path/to/example-build` + `rsync -a ../coq-build/ path/to/example-build` - This copies a template build folder that conveniently contains a + (note trailing `/` after the first directory). This + copies a template build folder that conveniently contains a `_CoqProject` file and the CN coq library `CN_Lib.v`. If the folder already excists, `rsync` just updates the files. 2. Extract the lemmas with From 08667b87b52033a5bf51234861e5d82189615880 Mon Sep 17 00:00:00 2001 From: septract Date: Thu, 11 Jul 2024 14:18:14 -0700 Subject: [PATCH 041/152] Tidy up typos and some script things --- src/example-archive/check-all.sh | 15 +- src/example-archive/check.sh | 176 +++++++++++------------ src/example-archive/coq-lemmas/README.md | 50 +++---- 3 files changed, 121 insertions(+), 120 deletions(-) diff --git a/src/example-archive/check-all.sh b/src/example-archive/check-all.sh index 2784fb3e..1fdaba6d 100755 --- a/src/example-archive/check-all.sh +++ b/src/example-archive/check-all.sh @@ -4,10 +4,10 @@ SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" if [ -n "$1" ] then - echo "check-all.sh: using CN=$1 in $PWD" - CN="$1" + echo "check-all.sh: using CN=$1 in $PWD" + CN="$1" else - CN=cn + CN=cn fi subdirs=( @@ -19,6 +19,7 @@ subdirs=( "Rust" "SAW" "simple-examples" + "coq-lemmas" ) FAILURE=0 @@ -35,9 +36,9 @@ done if [ $FAILURE -eq 0 ]; then - printf "\n\033[32mTest suite passes:\033[0m all CN tests in the example archive produced expected return codes\n" - exit 0 + printf "\n\033[32mTest suite passes:\033[0m all CN tests in the example archive produced expected return codes\n" + exit 0 else - printf "\n\033[31mTest suite fails:\033[0m one or more CN tests in the example archive produced an unexpected return code\n" - exit 1 + printf "\n\033[31mTest suite fails:\033[0m one or more CN tests in the example archive produced an unexpected return code\n" + exit 1 fi \ No newline at end of file diff --git a/src/example-archive/check.sh b/src/example-archive/check.sh index d6981561..c99cf6e2 100755 --- a/src/example-archive/check.sh +++ b/src/example-archive/check.sh @@ -2,10 +2,10 @@ if [ -n "$1" ] then - echo "check.sh: using CN=$1 in $PWD" - CN="$1" + echo "check.sh: using CN=$1 in $PWD" + CN="$1" else - CN=cn + CN=cn fi process_files() { @@ -15,20 +15,20 @@ process_files() { local action_argument=$4 if [ -d "$dir" ]; then - # Array to hold files matching the pattern - local files=($(find "$dir" -maxdepth 1 -type f -name "$pattern" | sort)) - - # Check if the array is not empty - if [ "${#files[@]}" -gt 0 ]; then - for file in "${files[@]}"; do - # Ensure the file variable is not empty - if [[ -n "$file" ]]; then - "$action" "$file" "$action_argument" - fi - done - else - echo "No files matching '$pattern' found in $dir" + # Array to hold files matching the pattern + local files=($(find "$dir" -maxdepth 1 -type f -name "$pattern" | sort)) + + # Check if the array is not empty + if [ "${#files[@]}" -gt 0 ]; then + for file in "${files[@]}"; do + # Ensure the file variable is not empty + if [[ -n "$file" ]]; then + "$action" "$file" "$action_argument" fi + done + else + echo "No files matching '$pattern' found in $dir" + fi else echo "Directory $dir does not exist" fi @@ -78,84 +78,84 @@ readonly FAIL_COQ_BUILD=2 check_coq_exports_end() { - ## Call this funciton at the end of a coq export check. It will - ## print the right message and return to the original directory - ## with popd. It will also increse the failure count if necessary. - local FAILED=$1 - local MESSAGE=$2 - - if [[ $FAILED -ne 0 ]]; then - printf "\033[31mFAIL\033[0m (${MESSAGE})\n" - failures=$(( $failures + 1 )) - else - printf "\033[32mPASS\033[0m\n" - fi + ## Call this function at the end of a coq export check. It will + ## print the right message and return to the original directory + ## with popd. It will also increse the failure count if necessary. + local FAILED=$1 + local MESSAGE=$2 + + if [[ $FAILED -ne 0 ]]; then + printf "\033[31mFAIL\033[0m (${MESSAGE})\n" + failures=$(( $failures + 1 )) + else + printf "\033[32mPASS\033[0m\n" + fi } - + check_coq_exports() { - local FILE=$1 - local FAIL_MODE=$2 - local PROTOTYPE_BUILD_DIR="../coq-build" - local EXPORTED_LEMMAS="ExportedLemmas.v" - local result=0 #^track if the build completed as much as expected - - printf "[$FILE]... " - - # Make a copy of the build directory but only if it doesn't - # already exists - local BUILD_DIR="${FILE%.*}-build" - - # Copy the build directory, and/or missing/new files - rsync -a "${PROTOTYPE_BUILD_DIR}/" "$BUILD_DIR" - - # Export the CN lemmas - cn "--lemmata=${BUILD_DIR}/theories/${EXPORTED_LEMMAS}" $FILE > /dev/null 2>&1 - # Check the result is as expected - local cn_result=$? - if [[ $cn_result -ne 0 && $FAIL_MODE -eq $FAIL_EXPORT ]]; then - # The export is expected to fail and there is nothing else to - # be done. Return successfully. - check_coq_exports_end ${result} "" - return ${result} - elif [[ $cn_result -eq 0 && $FAIL_MODE -ne $FAIL_EXPORT ]]; then - : # Export succeeded, as expected, continue the build - else - # Otherwise fail - result=1 - check_coq_exports_end ${result} "Unexpected return code during export: $cn_result" - return ${result} - fi + local FILE=$1 + local FAIL_MODE=$2 + local PROTOTYPE_BUILD_DIR="../coq-build" + local EXPORTED_LEMMAS="ExportedLemmas.v" + local result=0 #^track if the build completed as much as expected + + printf "[$FILE]... " + + # Make a copy of the build directory but only if it doesn't + # already exists + local BUILD_DIR="${FILE%.*}-build" + + # Copy the build directory, and/or missing/new files + rsync -a "${PROTOTYPE_BUILD_DIR}/" "$BUILD_DIR" + + # Export the CN lemmas + $CN "--lemmata=${BUILD_DIR}/theories/${EXPORTED_LEMMAS}" $FILE > /dev/null 2>&1 + # Check the result is as expected + local cn_result=$? + if [[ $cn_result -ne 0 && $FAIL_MODE -eq $FAIL_EXPORT ]]; then + # The export is expected to fail and there is nothing else to + # be done. Return successfully. + check_coq_exports_end ${result} "" + return ${result} + elif [[ $cn_result -eq 0 && $FAIL_MODE -ne $FAIL_EXPORT ]]; then + : # Export succeeded, as expected, continue the build + else + # Otherwise fail + result=1 + check_coq_exports_end ${result} "Unexpected return code during export: $cn_result" + return ${result} + fi - # The rest of the commands must be performed in the build directory - pushd "$BUILD_DIR" > /dev/null - - # Create the Coq Makefile - # (We don't expect this to fail) - coq_makefile -f _CoqProject -o Makefile.coq > /dev/null - - # Build the Coq files - make -f Makefile.coq > /dev/null 2>&1 - # Check the result is as expected - local coq_result=$? - if [[ $coq_result -ne 0 && $FAIL_MODE -eq $FAIL_COQ_BUILD ]]; then - # The coq build is expected to fail and there is nothing else to - # be done. Return successfully. - check_coq_exports_end ${result} "" - elif [[ $coq_result -eq 0 && $FAIL_MODE -ne $FAIL_COQ_BUILD ]]; then - # Export succeeded, as expected - check_coq_exports_end ${result} "" - else - result=1 - check_coq_exports_end ${result} "Unexpected return code during coq build: $coq_result" - fi + # The rest of the commands must be performed in the build directory + pushd "$BUILD_DIR" > /dev/null + + # Create the Coq Makefile + # (We don't expect this to fail) + coq_makefile -f _CoqProject -o Makefile.coq > /dev/null + + # Build the Coq files + make -f Makefile.coq > /dev/null 2>&1 + # Check the result is as expected + local coq_result=$? + if [[ $coq_result -ne 0 && $FAIL_MODE -eq $FAIL_COQ_BUILD ]]; then + # The coq build is expected to fail and there is nothing else to + # be done. Return successfully. + check_coq_exports_end ${result} "" + elif [[ $coq_result -eq 0 && $FAIL_MODE -ne $FAIL_COQ_BUILD ]]; then + # Export succeeded, as expected + check_coq_exports_end ${result} "" + else + result=1 + check_coq_exports_end ${result} "Unexpected return code during coq build: $coq_result" + fi - # At this point everythink built successfully. + # At this point everythink built successfully. - # Return to the directory where the script was called (from the - # build directory) - popd > /dev/null + # Return to the directory where the script was called (from the + # build directory) + popd > /dev/null - return ${result} + return ${result} } printf "== Check lemma export\n" diff --git a/src/example-archive/coq-lemmas/README.md b/src/example-archive/coq-lemmas/README.md index 805426a6..1e3a9698 100644 --- a/src/example-archive/coq-lemmas/README.md +++ b/src/example-archive/coq-lemmas/README.md @@ -27,46 +27,46 @@ To provide proofs or test individual examples, see below. ## Testing individual examples -From this folder, to export lemmas from example `path/to/example.c`, do the following: +From this folder, to export lemmas from example `path/to/EXAMPLENAME.c`, do the following: 0. (optional) Check CN verification, without exporting lemmas, with - `cn path/to/example.c` + `cn path/to/EXAMPLENAME.c` 1. Create a copy of the build folder with - `rsync -a ../coq-build/ path/to/example-build` - - (note trailing `/` after the first directory). This - copies a template build folder that conveniently contains a - `_CoqProject` file and the CN coq library `CN_Lib.v`. If the folder - already excists, `rsync` just updates the files. + `rsync -a ../coq-build/ path/to/EXAMPLENAME-build` + + (note trailing `/` after the first directory). This + copies a template build folder that conveniently contains a + `_CoqProject` file and the CN coq library `CN_Lib.v`. If the folder + already exists, `rsync` just updates the files. 2. Extract the lemmas with - - `cn --lemmata=path/to/example-build/theories/ExportedLemmas.v path/to/example.c` - - This should create a new file - `path/to/example-build/theories/ExportedLemmas.v` with all the - exported types, definitions and lemmas from the file - `path/to/example.c`. + + `cn --lemmata=path/to/EXAMPLENAME-build/theories/ExportedLemmas.v path/to/EXAMPLENAME.c` + + This should create a new file + `path/to/EXAMPLENAME-build/theories/ExportedLemmas.v` with all the + exported types, definitions and lemmas from the file + `path/to/EXAMPLENAME.c`. 3. Go to the build directory with - `pushd path/to/example-build` - - This will also store your current location to return later. + `pushd path/to/EXAMPLENAME-build` + + This will also store your current location to return later. 4. Create or update the Coq Makefile with - `coq_makefile -f _CoqProject -o Makefile.coq` - + `coq_makefile -f _CoqProject -o Makefile.coq` + 5. Build the Coq files with - `make -f Makefile.coq` - - This should create `*.vo` files for every `*.v` file in the - `theories` directory. + `make -f Makefile.coq` + + This should create `*.vo` files for every `*.v` file in the + `theories` directory. 6. Return to your starting folder with - `popd` + `popd` To add proofs, after running the steps above, create a file `Proofs.v` in the `theories` folder, next to the generated From 47a0d5c37a6e0932714a330c40628a442f4fdb86 Mon Sep 17 00:00:00 2001 From: septract Date: Thu, 11 Jul 2024 14:43:39 -0700 Subject: [PATCH 042/152] Readme tweaks --- src/example-archive/coq-lemmas/README.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/example-archive/coq-lemmas/README.md b/src/example-archive/coq-lemmas/README.md index 1e3a9698..bde0add4 100644 --- a/src/example-archive/coq-lemmas/README.md +++ b/src/example-archive/coq-lemmas/README.md @@ -1,17 +1,22 @@ -## Examples +# Coq Examples CN examples using lemmas that can be extracted to Coq. The examples are split into: -- Trivial -- Recursive (Not yet handled by the export) +- Trivial - named `trivial-*.c` +- Recursive - named `recursive-*.c` _(export for these examples is not supported by CN yet)_ + +Some examples are accompanied by Coq proofs of the lemmas extracted from CN. These are stored as follows: + +- Originating C file: `coq/working/EXAMPLENAME.c` +- Coq proof: `coq/working/EXAMPLENAME-build/Proof.v` See README in parent directory for directory organization details. ## Tools needed To build the generated Coq lemmas, you will need to [download and -install Coq](download). +install Coq](https://coq.inria.fr/download). ## Batch build From ad166c555651fad17d7794508203c3c5b7cf95d4 Mon Sep 17 00:00:00 2001 From: septract Date: Thu, 11 Jul 2024 14:48:11 -0700 Subject: [PATCH 043/152] Deactivate CI for Coq to make the merge easier --- src/example-archive/check-all.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/example-archive/check-all.sh b/src/example-archive/check-all.sh index 1fdaba6d..8c87ccdd 100755 --- a/src/example-archive/check-all.sh +++ b/src/example-archive/check-all.sh @@ -19,7 +19,7 @@ subdirs=( "Rust" "SAW" "simple-examples" - "coq-lemmas" + # "coq-lemmas" ) FAILURE=0 From 2ffc2cf3439232193a0a0beb7b7873fb1f9f3438 Mon Sep 17 00:00:00 2001 From: Christopher Pulte Date: Wed, 17 Jul 2024 10:47:55 +0100 Subject: [PATCH 044/152] increase timeout, for handling https://github.com/rems-project/cerberus/pull/365#event-13497283175 --- src/example-archive/check.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/example-archive/check.sh b/src/example-archive/check.sh index c99cf6e2..2878ab60 100755 --- a/src/example-archive/check.sh +++ b/src/example-archive/check.sh @@ -45,7 +45,7 @@ check_file() { local expected_exit_code=$2 printf "[$file]... " - timeout 10 $CN "$file" > /dev/null 2>&1 + timeout 20 $CN "$file" > /dev/null 2>&1 local result=$? if [ $result -eq $expected_exit_code ]; then From bc3314a947123ff0a70667018ac3c9466350b53f Mon Sep 17 00:00:00 2001 From: Christopher Pulte Date: Wed, 17 Jul 2024 10:57:12 +0100 Subject: [PATCH 045/152] recategorise that test --- .../broken/{error-timeout => error-crash}/00005_bit_switch.c | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/example-archive/java_program_verification_challenges/broken/{error-timeout => error-crash}/00005_bit_switch.c (100%) diff --git a/src/example-archive/java_program_verification_challenges/broken/error-timeout/00005_bit_switch.c b/src/example-archive/java_program_verification_challenges/broken/error-crash/00005_bit_switch.c similarity index 100% rename from src/example-archive/java_program_verification_challenges/broken/error-timeout/00005_bit_switch.c rename to src/example-archive/java_program_verification_challenges/broken/error-crash/00005_bit_switch.c From 4e3d3ddd64fcbe2760875e9884613e3a7ecb84d6 Mon Sep 17 00:00:00 2001 From: Liz Austell Date: Thu, 20 Jun 2024 14:50:51 -0400 Subject: [PATCH 046/152] add constructors and begin (broken) push function --- src/examples/Linked_List/WIPlinklist.c | 112 +++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 src/examples/Linked_List/WIPlinklist.c diff --git a/src/examples/Linked_List/WIPlinklist.c b/src/examples/Linked_List/WIPlinklist.c new file mode 100644 index 00000000..c7f3b6bf --- /dev/null +++ b/src/examples/Linked_List/WIPlinklist.c @@ -0,0 +1,112 @@ +#include "../list.h" +#include "../list_length.c" + +struct linkedList { + struct linkedListCell* front; + struct linkedListCell* back; + unsigned int length; +}; + +struct linkedListCell { + int data; + struct linkedListCell* prev; + struct linkedListCell* next; +}; + +/*@ +predicate (datatype seq) LinkedListPtr (pointer l) { + take L = Owned(l); + assert ( (is_null(L.front) && is_null(L.back)) + || (!is_null(L.front) && !is_null(L.back))); + take inner = LinkedListFB(L.front, L.back); + assert ( L.length == length(inner)); + return inner; +} + + +predicate (datatype seq) LinkedListFB (pointer front, pointer back) { + if (is_null(front)) { + return Seq_Nil{}; + } else { + take B = Owned(back); + assert (is_null(B.next)); + take F = Owned(front); + assert(is_null(F.prev)); + take L = LinkedListAux (F.next, back); + return Seq_Cons{ head: F.data, tail: snoc(L, B.data)}; + } +} + +predicate (datatype seq) LinkedListAux (pointer f, pointer b) { + if (ptr_eq(f,b)) { + return Seq_Nil{}; + } else { + take F = Owned(f); + assert (!is_null(F.next)); + take B = LinkedListAux(F.next, b); + return Seq_Cons{head: F.data, tail: B}; + } +} +@*/ + +extern struct linkedList *mallocLinkedList(); +/*@ spec mallocLinkedList(); + requires true; + ensures take u = Block(return); + !ptr_eq(return,NULL); +@*/ + +extern void freeLinkedList (struct linkedList *p); +/*@ spec freeLinkedList(pointer p); + requires take u = Block(p); + ensures true; +@*/ + +extern struct linkedListCell *mallocLinkedListCell(); +/*@ spec mallocLinkedListCell(); + requires true; + ensures take u = Block(return); + !is_null(return); +@*/ + +extern void freeLinkedListCell (struct linkedListCell *p); +/*@ spec freeLinkedListCell(pointer p); + requires take u = Block(p); + ensures true; +@*/ + +struct linkedList* LinkedList_empty () +/*@ ensures take ret = LinkedListPtr(return); + ret == Seq_Nil{}; +@*/ +{ + struct linkedList *p = mallocLinkedList(); + p->front = 0; + p->back = 0; + p->length = 0; + /*@ unfold length(Seq_Nil{}); @*/ + return p; +} + +// Given a linked list pointer, inserts a new node +// to the head of the list +void push (struct linkedList* l, int element) +/*@ requires (!is_null(l)); + take L1 = LinkedListPtr(l); + ensures take L2 = LinkedListPtr(l); + L2 == Seq_Cons{head: element, tail: L1}; + length(L2) == length(L1) + 1u32; +@*/ +{ + struct linkedListCell *newCell = mallocLinkedListCell(); + newCell->data = element; + newCell->next = l->front; + newCell->prev = 0; + + if (l->front != 0) { + l->front->prev = newCell; + } + + l->front = newCell; + l->length = l->length + 1; +} \ No newline at end of file From fbe03c21e39222a629ee763f388c762f59169bac Mon Sep 17 00:00:00 2001 From: Liz Austell Date: Thu, 20 Jun 2024 14:51:54 -0400 Subject: [PATCH 047/152] add snoc to linked list file --- src/examples/Linked_List/WIPlinklist.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/examples/Linked_List/WIPlinklist.c b/src/examples/Linked_List/WIPlinklist.c index c7f3b6bf..c046ec06 100644 --- a/src/examples/Linked_List/WIPlinklist.c +++ b/src/examples/Linked_List/WIPlinklist.c @@ -1,5 +1,6 @@ #include "../list.h" #include "../list_length.c" +#include "../list_snoc.h" struct linkedList { struct linkedListCell* front; From 021b90244deed361c1b0bf1f3ad79ec51ca6279b Mon Sep 17 00:00:00 2001 From: Liz Austell Date: Thu, 20 Jun 2024 15:48:32 -0400 Subject: [PATCH 048/152] add predicates that traverse backwards --- src/examples/Linked_List/WIPlinklist.c | 34 ++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/examples/Linked_List/WIPlinklist.c b/src/examples/Linked_List/WIPlinklist.c index c046ec06..cd78378d 100644 --- a/src/examples/Linked_List/WIPlinklist.c +++ b/src/examples/Linked_List/WIPlinklist.c @@ -48,6 +48,40 @@ predicate (datatype seq) LinkedListAux (pointer f, pointer b) { return Seq_Cons{head: F.data, tail: B}; } } + +predicate (datatype seq) LinkedListPtr_backwards (pointer l) { + take L = Owned(l); + assert ( (is_null(L.front) && is_null(L.back)) + || (!is_null(L.front) && !is_null(L.back))); + take inner = LinkedListFB_backwards(L.front, L.back); + assert ( L.length == length(inner)); + return inner; +} + +predicate (datatype seq) LinkedListFB_backwards (pointer front, pointer back) { + if (is_null(back)) { + return Seq_Nil{}; + } else { + take B = Owned(back); + assert (is_null(B.next)); + take F = Owned(front); + assert(is_null(F.prev)); + take L = LinkedListAux (front, B.prev); + return Seq_Cons{ head: F.data, tail: snoc(L, B.data)}; + } +} + + +predicate (datatype seq) LinkedListAux_backwards (pointer f, pointer b) { + if (ptr_eq(f,b)) { + return Seq_Nil{}; + } else { + take B = Owned(b); + assert (!is_null(B.prev)); + take F = LinkedListAux(f, B.prev); + return snoc(F, B.data); + } +} @*/ extern struct linkedList *mallocLinkedList(); From 3f99051d9d479223b7b38f0219440eff9c49de20 Mon Sep 17 00:00:00 2001 From: Liz Austell Date: Thu, 20 Jun 2024 17:07:32 -0400 Subject: [PATCH 049/152] begin implementing append (broken) --- src/examples/Linked_List/WIPlinklist.c | 98 ++++++++++++++++++++++---- 1 file changed, 83 insertions(+), 15 deletions(-) diff --git a/src/examples/Linked_List/WIPlinklist.c b/src/examples/Linked_List/WIPlinklist.c index cd78378d..d135cb6f 100644 --- a/src/examples/Linked_List/WIPlinklist.c +++ b/src/examples/Linked_List/WIPlinklist.c @@ -29,12 +29,19 @@ predicate (datatype seq) LinkedListFB (pointer front, pointer back) { if (is_null(front)) { return Seq_Nil{}; } else { - take B = Owned(back); - assert (is_null(B.next)); - take F = Owned(front); - assert(is_null(F.prev)); - take L = LinkedListAux (F.next, back); - return Seq_Cons{ head: F.data, tail: snoc(L, B.data)}; + if (front == back) { + take F = Owned(front); + assert (is_null(F.next)); + assert (is_null(F.prev)); + return Seq_Cons {head: F.data, tail: Seq_Nil{}}; + } else { + take B = Owned(back); + assert (is_null(B.next)); + take F = Owned(front); //problem if only one element + assert(is_null(F.prev)); + take L = LinkedListAux (F.next, back); + return Seq_Cons{ head: F.data, tail: snoc(L, B.data)}; + } } } @@ -123,25 +130,86 @@ struct linkedList* LinkedList_empty () return p; } +// // Given a linked list pointer, inserts a new node +// // to the head of the list +// void push (struct linkedList* l, int element) +// /*@ requires (!is_null(l)); +// take L1 = LinkedListPtr(l); +// ensures take L2 = LinkedListPtr(l); +// L2 == Seq_Cons{head: element, tail: L1}; +// length(L2) == length(L1) + 1u32; +// @*/ +// { +// struct linkedListCell *newCell = mallocLinkedListCell(); +// newCell->data = element; +// newCell->next = l->front; +// newCell->prev = 0; + +// if (l->front != 0) { +// l->front->prev = newCell; +// } + +// l->front = newCell; +// l->length = l->length + 1; +// } + + +/*@ +lemma append_lemma_ll (pointer front, pointer p) + requires + take L = LinkedListAux(front, p); + take P = Owned(p); + ensures + take NewL = LinkedListAux(front, P.next); + NewL == snoc(L, P.data); +@*/ + +/*@ +lemma snoc_facts (pointer front, pointer back, i32 x) + requires + take L = LinkedListAux(front, back); + take B = Owned(back); + ensures + take NewL = LinkedListAux(front, back); + take NewB = Owned(back); + L == NewL; B == NewB; + let S = snoc (Seq_Cons{head: x, tail: L}, B.data); + hd(S) == x; + tl(S) == snoc (L, B.data); +@*/ + // Given a linked list pointer, inserts a new node -// to the head of the list -void push (struct linkedList* l, int element) +// at the end of the list. +void append (struct linkedList* l, int element) /*@ requires (!is_null(l)); take L1 = LinkedListPtr(l); ensures take L2 = LinkedListPtr(l); - L2 == Seq_Cons{head: element, tail: L1}; + L2 == snoc(L1, element); length(L2) == length(L1) + 1u32; @*/ { struct linkedListCell *newCell = mallocLinkedListCell(); newCell->data = element; - newCell->next = l->front; + newCell->next = 0; newCell->prev = 0; - if (l->front != 0) { - l->front->prev = newCell; + // empty list case + if (l->back == 0) { + /*@ assert(L1 == Seq_Nil{}); @*/ + l->front = newCell; + l->back = newCell; + l->length = l->length + 1; + /*@ unfold length(Seq_Nil{}); @*/ + /*@ unfold length(Seq_Cons {head: element, tail: Seq_Nil{}}); @*/ + /*@ unfold snoc(L1, element); @*/ + return; + } else { + struct linkedListCell *oldback = l->back; + // l->back->next = newCell; + l->back = newCell; + newCell->prev = oldback; + l->length = l->length + 1; + /*@ apply append_lemma_ll((*l).front, oldback); @*/ + return; } - - l->front = newCell; - l->length = l->length + 1; } \ No newline at end of file From b0ec83e30ed26baee1e9bdea93eab48cdd47008e Mon Sep 17 00:00:00 2001 From: Liz Austell Date: Thu, 20 Jun 2024 17:15:51 -0400 Subject: [PATCH 050/152] create todo list --- src/examples/Linked_List/TODO.md | 43 ++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src/examples/Linked_List/TODO.md diff --git a/src/examples/Linked_List/TODO.md b/src/examples/Linked_List/TODO.md new file mode 100644 index 00000000..a6da69c0 --- /dev/null +++ b/src/examples/Linked_List/TODO.md @@ -0,0 +1,43 @@ +# Functions and Lemmas that should be implemented for doubly linked lists + +push +* WIP +* Insert a new node at the front of a list +* Time Complexity: O(1) +* Auxiliary Space: O(1) + +insertAfter +* Insert a new node after a given node +* Time Complexity: O(1) +* Auxiliary Space: O(1) + +insertBefore +* Insert a new node before a given node +* Time Complexity: O(1) +* Auxiliary Space: O(1) + +append +* WIP +* Insert a new node at the end of a list +* Time Complexity: O(1) +* Auxiliary Space: O(1) + +removeHead +* Remove the head node from the list +* Time Complexity: O(1) +* Auxiliary Space: O(1) + +removeTail +* Remove the tail node from the list +* Time Complexity: O(1) +* Auxiliary Space: O(1) + +removeNode +* Remove a given node from the list +* Time Complexity: O(1) +* Auxiliary Space: O(1) + +insertAt +* insert a new node at the given index +* Time Complexity: O(n) +* Auxiliary Space: O(1) \ No newline at end of file From 4d19bcd79257e1201f312560729b2ca4a9d87e5a Mon Sep 17 00:00:00 2001 From: Liz Austell Date: Mon, 24 Jun 2024 12:27:09 -0400 Subject: [PATCH 051/152] add constructors that go out from middle node --- src/examples/Linked_List/WIPlinklist2.c | 43 +++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src/examples/Linked_List/WIPlinklist2.c diff --git a/src/examples/Linked_List/WIPlinklist2.c b/src/examples/Linked_List/WIPlinklist2.c new file mode 100644 index 00000000..ab2ff778 --- /dev/null +++ b/src/examples/Linked_List/WIPlinklist2.c @@ -0,0 +1,43 @@ +#include "../list.h" +#include "../list_length.c" +#include "../list_snoc.h" +#include "../list_append.h" + +struct Node { + int data; + struct Node* prev; + struct Node* next; +}; + +/*@ +predicate (datatype seq) LinkedList (pointer p) { + if (is_null(p)) { + return Seq_Nil{}; + } else { + take N = Owned(p); + take first = OwnBackwards(N.prev); + take rest = OwnForwards(N.next); + return append(first, Seq_Cons{head: N.data, tail: rest}); + } +} + +predicate (datatype seq) OwnForwards(pointer p) { + if (is_null(p)) { + return Seq_Nil{}; + } else { + take N = Owned(p); + take rest = OwnForwards(N.next); + return Seq_Cons{head: N.data, tail: rest}; + } +} + +predicate (datatype seq) OwnBackwards(pointer p) { + if (is_null(p)) { + return Seq_Nil{}; + } else { + take N = Owned(p); + take first = OwnBackwards(N.prev); + return snoc(first, N.data); + } +} +@*/ \ No newline at end of file From 15a97a265fbb9bc5ef7645fb469823b63e9607ec Mon Sep 17 00:00:00 2001 From: Liz Austell Date: Mon, 24 Jun 2024 12:33:16 -0400 Subject: [PATCH 052/152] add malloc and free --- src/examples/Linked_List/WIPlinklist2.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/examples/Linked_List/WIPlinklist2.c b/src/examples/Linked_List/WIPlinklist2.c index ab2ff778..5e65fe38 100644 --- a/src/examples/Linked_List/WIPlinklist2.c +++ b/src/examples/Linked_List/WIPlinklist2.c @@ -40,4 +40,17 @@ predicate (datatype seq) OwnBackwards(pointer p) { return snoc(first, N.data); } } +@*/ + +extern struct Node *mallocNode(); +/*@ spec mallocNode(); + requires true; + ensures take u = Block(return); + !ptr_eq(return,NULL); +@*/ + +extern void freeNode (struct Node *p); +/*@ spec freeNode(pointer p); + requires take u = Block(p); + ensures true; @*/ \ No newline at end of file From f65f80dc4a660f6cd22072e49aa76082a5f7c038 Mon Sep 17 00:00:00 2001 From: Liz Austell Date: Mon, 24 Jun 2024 14:34:23 -0400 Subject: [PATCH 053/152] add singleton constructor --- src/examples/Linked_List/WIPlinklist2.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/examples/Linked_List/WIPlinklist2.c b/src/examples/Linked_List/WIPlinklist2.c index 5e65fe38..087bb842 100644 --- a/src/examples/Linked_List/WIPlinklist2.c +++ b/src/examples/Linked_List/WIPlinklist2.c @@ -53,4 +53,17 @@ extern void freeNode (struct Node *p); /*@ spec freeNode(pointer p); requires take u = Block(p); ensures true; -@*/ \ No newline at end of file +@*/ + +struct Node *singleton(int element) +/*@ ensures take ret = LinkedList(return); + ret == Seq_Cons{head: element, tail: Seq_Nil{}}; +@*/ +{ + struct Node *n = mallocNode(); + n->data = element; + n->prev = 0; + n->next = 0; + /*@ unfold append(Seq_Nil{}, (Seq_Cons {head: element, tail: Seq_Nil{}})); @*/ + return n; +} \ No newline at end of file From 0297948fc0a2dca3e3f3d20e461381e0e693ba5a Mon Sep 17 00:00:00 2001 From: Liz Austell Date: Mon, 24 Jun 2024 14:34:52 -0400 Subject: [PATCH 054/152] add remove function --- src/examples/Linked_List/WIPlinklist2.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/examples/Linked_List/WIPlinklist2.c b/src/examples/Linked_List/WIPlinklist2.c index 087bb842..e47f5c67 100644 --- a/src/examples/Linked_List/WIPlinklist2.c +++ b/src/examples/Linked_List/WIPlinklist2.c @@ -66,4 +66,29 @@ struct Node *singleton(int element) n->next = 0; /*@ unfold append(Seq_Nil{}, (Seq_Cons {head: element, tail: Seq_Nil{}})); @*/ return n; +} + +int remove(struct Node *n) +/*@ +requires take del = Owned(n); + take rest = OwnForwards(del.next); + take first = OwnBackwards(del.prev); + !is_null(del.prev) || !is_null(del.next); +ensures take rest1 = OwnForwards(del.next); + take first1 = OwnBackwards(del.prev); +@*/ +{ + if (n->prev == 0) { + // n is the head + n->next->prev = 0; + } else if (n->next == 0) { + // n is the tail + n->prev->next = 0; + } else { + n->next->prev = 0; + n->prev->next = 0; + } + int temp = n->data; + freeNode(n); + return temp; } \ No newline at end of file From 73c651991a8af0c5f39d9e8c20c51c9280b4ce47 Mon Sep 17 00:00:00 2001 From: Liz Austell Date: Mon, 24 Jun 2024 14:39:55 -0400 Subject: [PATCH 055/152] include that the rest of the list is unchanged in remove function --- src/examples/Linked_List/WIPlinklist2.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/examples/Linked_List/WIPlinklist2.c b/src/examples/Linked_List/WIPlinklist2.c index e47f5c67..cd53f647 100644 --- a/src/examples/Linked_List/WIPlinklist2.c +++ b/src/examples/Linked_List/WIPlinklist2.c @@ -74,8 +74,10 @@ requires take del = Owned(n); take rest = OwnForwards(del.next); take first = OwnBackwards(del.prev); !is_null(del.prev) || !is_null(del.next); -ensures take rest1 = OwnForwards(del.next); - take first1 = OwnBackwards(del.prev); +ensures take rest_ = OwnForwards(del.next); + take first_ = OwnBackwards(del.prev); + rest == rest_; + first == first_; @*/ { if (n->prev == 0) { @@ -91,4 +93,5 @@ ensures take rest1 = OwnForwards(del.next); int temp = n->data; freeNode(n); return temp; -} \ No newline at end of file +} + From ae7611f6e90d2decd6fb0587e6db6522d18adff0 Mon Sep 17 00:00:00 2001 From: Liz Austell Date: Mon, 24 Jun 2024 17:37:03 -0400 Subject: [PATCH 056/152] implement hacky add function --- src/examples/Linked_List/WIPlinklist2.c | 32 ++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/examples/Linked_List/WIPlinklist2.c b/src/examples/Linked_List/WIPlinklist2.c index cd53f647..007181a8 100644 --- a/src/examples/Linked_List/WIPlinklist2.c +++ b/src/examples/Linked_List/WIPlinklist2.c @@ -9,7 +9,7 @@ struct Node { struct Node* next; }; -/*@ +/*@ predicate (datatype seq) LinkedList (pointer p) { if (is_null(p)) { return Seq_Nil{}; @@ -95,3 +95,33 @@ ensures take rest_ = OwnForwards(del.next); return temp; } +// // Creates a new node with value `element` and adds it between `prevNode` +// // and `nextNode` in the list +// // +// // Note: I had to include the two nodes it goes between, +// // because otherwise there is a fight for ownership over the +// // next node. +struct Node *add_between(int element, struct Node *prevNode, struct Node *nextNode) +/*@ requires !is_null(prevNode) && !is_null(nextNode); + take prev = Owned(prevNode); + take next = Owned(nextNode); + take rest = OwnForwards(next.next); + take first = OwnBackwards(prev.prev); + ensures take prev_ = Owned(prevNode); + take next_ = Owned(nextNode); + take rest_ = OwnForwards(next_.next); + take first_ = OwnBackwards(prev_.prev); + take u = Owned(return); +@*/ +{ + struct Node *newNode = mallocNode(); + + newNode->prev = prevNode; + newNode->data = element; + newNode->next = nextNode; + + prevNode->next = newNode; + nextNode->prev = newNode; + + return newNode; +} \ No newline at end of file From 92fb60344b99d2ba3612c460dce185be1d18f9aa Mon Sep 17 00:00:00 2001 From: Liz Austell Date: Mon, 24 Jun 2024 17:43:01 -0400 Subject: [PATCH 057/152] add correctness check to add_between --- src/examples/Linked_List/WIPlinklist2.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/examples/Linked_List/WIPlinklist2.c b/src/examples/Linked_List/WIPlinklist2.c index 007181a8..14c372eb 100644 --- a/src/examples/Linked_List/WIPlinklist2.c +++ b/src/examples/Linked_List/WIPlinklist2.c @@ -107,11 +107,8 @@ struct Node *add_between(int element, struct Node *prevNode, struct Node *nextNo take next = Owned(nextNode); take rest = OwnForwards(next.next); take first = OwnBackwards(prev.prev); - ensures take prev_ = Owned(prevNode); - take next_ = Owned(nextNode); - take rest_ = OwnForwards(next_.next); - take first_ = OwnBackwards(prev_.prev); - take u = Owned(return); + ensures take result = LinkedList(return); + result == append(snoc(first,prev.data), Seq_Cons{head: element, tail: Seq_Cons{head: next.data, tail: rest}}); @*/ { struct Node *newNode = mallocNode(); From 17229f032f5dc019bb8604c6fc340407d9498946 Mon Sep 17 00:00:00 2001 From: Liz Austell Date: Mon, 24 Jun 2024 18:33:43 -0400 Subject: [PATCH 058/152] try to implement add --- src/examples/Linked_List/WIPlinklist2.c | 59 +++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/src/examples/Linked_List/WIPlinklist2.c b/src/examples/Linked_List/WIPlinklist2.c index 14c372eb..9d9dbaa7 100644 --- a/src/examples/Linked_List/WIPlinklist2.c +++ b/src/examples/Linked_List/WIPlinklist2.c @@ -15,6 +15,8 @@ predicate (datatype seq) LinkedList (pointer p) { return Seq_Nil{}; } else { take N = Owned(p); + // assert (is_null(N.prev) || N.prev.next == N); + // assert (is_null(N.next) || N.next.prev == N); take first = OwnBackwards(N.prev); take rest = OwnForwards(N.next); return append(first, Seq_Cons{head: N.data, tail: rest}); @@ -27,6 +29,7 @@ predicate (datatype seq) OwnForwards(pointer p) { } else { take N = Owned(p); take rest = OwnForwards(N.next); + // assert (is_null(N.next) || (*(N.next)).prev == N); return Seq_Cons{head: N.data, tail: rest}; } } @@ -36,6 +39,7 @@ predicate (datatype seq) OwnBackwards(pointer p) { return Seq_Nil{}; } else { take N = Owned(p); + // assert (is_null(N.prev) || N.prev.next == N); take first = OwnBackwards(N.prev); return snoc(first, N.data); } @@ -107,8 +111,40 @@ struct Node *add_between(int element, struct Node *prevNode, struct Node *nextNo take next = Owned(nextNode); take rest = OwnForwards(next.next); take first = OwnBackwards(prev.prev); + prev.next == nextNode; + next.prev == prevNode; ensures take result = LinkedList(return); result == append(snoc(first,prev.data), Seq_Cons{head: element, tail: Seq_Cons{head: next.data, tail: rest}}); + result != Seq_Nil{}; +@*/ +{ + struct Node *newNode = mallocNode(); + + newNode->prev = prevNode; + newNode->data = element; + newNode->next = nextNode; + + prevNode->next = newNode; + nextNode->prev = newNode; + /*@ unfold append(snoc(first,prev.data), Seq_Cons{head: element, tail: Seq_Cons{head: next.data, tail: rest}}); @*/ + return newNode; +} + +struct Node *add_between_worse(int element, struct Node *prevNode, struct Node *nextNode) +/*@ requires !is_null(prevNode) && !is_null(nextNode); + take prev = Owned(prevNode); + take next = Owned(nextNode); + take rest = OwnForwards(next.next); + take first = OwnBackwards(prev.prev); + // prev.next == nextNode; + // next.prev == prevNode; + ensures take prev_ = Owned(prevNode); + take next_ = Owned(nextNode); + take rest_ = OwnForwards(next_.next); + take first_ = OwnBackwards(prev.prev); + take u = Owned(return); + // ensures take result = LinkedList(return); + // result == append(snoc(first,prev.data), Seq_Cons{head: element, tail: Seq_Cons{head: next.data, tail: rest}}); @*/ { struct Node *newNode = mallocNode(); @@ -121,4 +157,27 @@ struct Node *add_between(int element, struct Node *prevNode, struct Node *nextNo nextNode->prev = newNode; return newNode; +} + +struct Node *add_worse(int element, struct Node *prevNode) +/*@ requires !is_null(prevNode); + take n = Owned(prevNode); + take prev = OwnBackwards(n.prev); + take rest = OwnForwards(n.next); + ensures take n_ = Owned(prevNode); + take prev_ = OwnBackwards(n.prev); + take rest_ = OwnForwards(n.next); + take u = Owned(return); +@*/ +{ + if (prevNode->next != 0) { + return add_between_worse(element, prevNode, prevNode->next); + } else { + struct Node *newNode = mallocNode(); + newNode->prev = prevNode; + newNode->data = element; + newNode->next = 0; + prevNode->next = newNode; + return newNode; + } } \ No newline at end of file From 62172cfbce09a431a59edced5c66b917b5b73f67 Mon Sep 17 00:00:00 2001 From: Liz Austell Date: Mon, 24 Jun 2024 18:40:27 -0400 Subject: [PATCH 059/152] add comments for clarification --- src/examples/Linked_List/WIPlinklist.c | 3 +++ src/examples/Linked_List/WIPlinklist2.c | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/src/examples/Linked_List/WIPlinklist.c b/src/examples/Linked_List/WIPlinklist.c index d135cb6f..77480ad1 100644 --- a/src/examples/Linked_List/WIPlinklist.c +++ b/src/examples/Linked_List/WIPlinklist.c @@ -1,3 +1,6 @@ +// NOTE: look at WIPlinklist2.c for more recent/successful developments. +// This file reasons about a linked list structure with a head and tail pointer. + #include "../list.h" #include "../list_length.c" #include "../list_snoc.h" diff --git a/src/examples/Linked_List/WIPlinklist2.c b/src/examples/Linked_List/WIPlinklist2.c index 9d9dbaa7..2e7d5ea1 100644 --- a/src/examples/Linked_List/WIPlinklist2.c +++ b/src/examples/Linked_List/WIPlinklist2.c @@ -1,3 +1,5 @@ +// More up to date version of the linked list example. This representation of a linked list +// has no head or tail, but rather is simply made up of nodes. #include "../list.h" #include "../list_length.c" #include "../list_snoc.h" @@ -130,6 +132,10 @@ struct Node *add_between(int element, struct Node *prevNode, struct Node *nextNo return newNode; } + +// This is an add_between function that works as a helper to a regular add function (`add_worse`). +// It is worse because it does not require that the two input nodes are connected, and does +// not reason about the list after the add. struct Node *add_between_worse(int element, struct Node *prevNode, struct Node *nextNode) /*@ requires !is_null(prevNode) && !is_null(nextNode); take prev = Owned(prevNode); @@ -159,6 +165,8 @@ struct Node *add_between_worse(int element, struct Node *prevNode, struct Node * return newNode; } +// This add works with only one node as input instead of two, however it +// does not reason about correctness of the list after the add. struct Node *add_worse(int element, struct Node *prevNode) /*@ requires !is_null(prevNode); take n = Owned(prevNode); From 680a15824742e7d669f8c97461f3fea5a3217c8a Mon Sep 17 00:00:00 2001 From: Liz Austell <122940625+elaustell@users.noreply.github.com> Date: Wed, 26 Jun 2024 10:43:28 -0400 Subject: [PATCH 060/152] Delete src/examples/Linked_List/WIPlinklist2.c --- src/examples/Linked_List/WIPlinklist2.c | 191 ------------------------ 1 file changed, 191 deletions(-) delete mode 100644 src/examples/Linked_List/WIPlinklist2.c diff --git a/src/examples/Linked_List/WIPlinklist2.c b/src/examples/Linked_List/WIPlinklist2.c deleted file mode 100644 index 2e7d5ea1..00000000 --- a/src/examples/Linked_List/WIPlinklist2.c +++ /dev/null @@ -1,191 +0,0 @@ -// More up to date version of the linked list example. This representation of a linked list -// has no head or tail, but rather is simply made up of nodes. -#include "../list.h" -#include "../list_length.c" -#include "../list_snoc.h" -#include "../list_append.h" - -struct Node { - int data; - struct Node* prev; - struct Node* next; -}; - -/*@ -predicate (datatype seq) LinkedList (pointer p) { - if (is_null(p)) { - return Seq_Nil{}; - } else { - take N = Owned(p); - // assert (is_null(N.prev) || N.prev.next == N); - // assert (is_null(N.next) || N.next.prev == N); - take first = OwnBackwards(N.prev); - take rest = OwnForwards(N.next); - return append(first, Seq_Cons{head: N.data, tail: rest}); - } -} - -predicate (datatype seq) OwnForwards(pointer p) { - if (is_null(p)) { - return Seq_Nil{}; - } else { - take N = Owned(p); - take rest = OwnForwards(N.next); - // assert (is_null(N.next) || (*(N.next)).prev == N); - return Seq_Cons{head: N.data, tail: rest}; - } -} - -predicate (datatype seq) OwnBackwards(pointer p) { - if (is_null(p)) { - return Seq_Nil{}; - } else { - take N = Owned(p); - // assert (is_null(N.prev) || N.prev.next == N); - take first = OwnBackwards(N.prev); - return snoc(first, N.data); - } -} -@*/ - -extern struct Node *mallocNode(); -/*@ spec mallocNode(); - requires true; - ensures take u = Block(return); - !ptr_eq(return,NULL); -@*/ - -extern void freeNode (struct Node *p); -/*@ spec freeNode(pointer p); - requires take u = Block(p); - ensures true; -@*/ - -struct Node *singleton(int element) -/*@ ensures take ret = LinkedList(return); - ret == Seq_Cons{head: element, tail: Seq_Nil{}}; -@*/ -{ - struct Node *n = mallocNode(); - n->data = element; - n->prev = 0; - n->next = 0; - /*@ unfold append(Seq_Nil{}, (Seq_Cons {head: element, tail: Seq_Nil{}})); @*/ - return n; -} - -int remove(struct Node *n) -/*@ -requires take del = Owned(n); - take rest = OwnForwards(del.next); - take first = OwnBackwards(del.prev); - !is_null(del.prev) || !is_null(del.next); -ensures take rest_ = OwnForwards(del.next); - take first_ = OwnBackwards(del.prev); - rest == rest_; - first == first_; -@*/ -{ - if (n->prev == 0) { - // n is the head - n->next->prev = 0; - } else if (n->next == 0) { - // n is the tail - n->prev->next = 0; - } else { - n->next->prev = 0; - n->prev->next = 0; - } - int temp = n->data; - freeNode(n); - return temp; -} - -// // Creates a new node with value `element` and adds it between `prevNode` -// // and `nextNode` in the list -// // -// // Note: I had to include the two nodes it goes between, -// // because otherwise there is a fight for ownership over the -// // next node. -struct Node *add_between(int element, struct Node *prevNode, struct Node *nextNode) -/*@ requires !is_null(prevNode) && !is_null(nextNode); - take prev = Owned(prevNode); - take next = Owned(nextNode); - take rest = OwnForwards(next.next); - take first = OwnBackwards(prev.prev); - prev.next == nextNode; - next.prev == prevNode; - ensures take result = LinkedList(return); - result == append(snoc(first,prev.data), Seq_Cons{head: element, tail: Seq_Cons{head: next.data, tail: rest}}); - result != Seq_Nil{}; -@*/ -{ - struct Node *newNode = mallocNode(); - - newNode->prev = prevNode; - newNode->data = element; - newNode->next = nextNode; - - prevNode->next = newNode; - nextNode->prev = newNode; - /*@ unfold append(snoc(first,prev.data), Seq_Cons{head: element, tail: Seq_Cons{head: next.data, tail: rest}}); @*/ - return newNode; -} - - -// This is an add_between function that works as a helper to a regular add function (`add_worse`). -// It is worse because it does not require that the two input nodes are connected, and does -// not reason about the list after the add. -struct Node *add_between_worse(int element, struct Node *prevNode, struct Node *nextNode) -/*@ requires !is_null(prevNode) && !is_null(nextNode); - take prev = Owned(prevNode); - take next = Owned(nextNode); - take rest = OwnForwards(next.next); - take first = OwnBackwards(prev.prev); - // prev.next == nextNode; - // next.prev == prevNode; - ensures take prev_ = Owned(prevNode); - take next_ = Owned(nextNode); - take rest_ = OwnForwards(next_.next); - take first_ = OwnBackwards(prev.prev); - take u = Owned(return); - // ensures take result = LinkedList(return); - // result == append(snoc(first,prev.data), Seq_Cons{head: element, tail: Seq_Cons{head: next.data, tail: rest}}); -@*/ -{ - struct Node *newNode = mallocNode(); - - newNode->prev = prevNode; - newNode->data = element; - newNode->next = nextNode; - - prevNode->next = newNode; - nextNode->prev = newNode; - - return newNode; -} - -// This add works with only one node as input instead of two, however it -// does not reason about correctness of the list after the add. -struct Node *add_worse(int element, struct Node *prevNode) -/*@ requires !is_null(prevNode); - take n = Owned(prevNode); - take prev = OwnBackwards(n.prev); - take rest = OwnForwards(n.next); - ensures take n_ = Owned(prevNode); - take prev_ = OwnBackwards(n.prev); - take rest_ = OwnForwards(n.next); - take u = Owned(return); -@*/ -{ - if (prevNode->next != 0) { - return add_between_worse(element, prevNode, prevNode->next); - } else { - struct Node *newNode = mallocNode(); - newNode->prev = prevNode; - newNode->data = element; - newNode->next = 0; - prevNode->next = newNode; - return newNode; - } -} \ No newline at end of file From 7e66c47c0e2c7077e94d11e696cd9e1bb1acf8ca Mon Sep 17 00:00:00 2001 From: Liz Austell Date: Wed, 26 Jun 2024 10:44:07 -0400 Subject: [PATCH 061/152] add new way of reasoning about linked lists --- src/examples/Linked_List/WIPlinklist.c | 276 +++++++++---------------- 1 file changed, 100 insertions(+), 176 deletions(-) diff --git a/src/examples/Linked_List/WIPlinklist.c b/src/examples/Linked_List/WIPlinklist.c index 77480ad1..045ba774 100644 --- a/src/examples/Linked_List/WIPlinklist.c +++ b/src/examples/Linked_List/WIPlinklist.c @@ -1,218 +1,142 @@ -// NOTE: look at WIPlinklist2.c for more recent/successful developments. -// This file reasons about a linked list structure with a head and tail pointer. - #include "../list.h" #include "../list_length.c" #include "../list_snoc.h" +#include "../list_append.h" -struct linkedList { - struct linkedListCell* front; - struct linkedListCell* back; - unsigned int length; -}; - -struct linkedListCell { +struct Node { int data; - struct linkedListCell* prev; - struct linkedListCell* next; + struct Node* prev; + struct Node* next; }; /*@ -predicate (datatype seq) LinkedListPtr (pointer l) { - take L = Owned(l); - assert ( (is_null(L.front) && is_null(L.back)) - || (!is_null(L.front) && !is_null(L.back))); - take inner = LinkedListFB(L.front, L.back); - assert ( L.length == length(inner)); - return inner; +datatype dblList { + dbl_list {datatype seq first, struct Node node, datatype seq rest} } +datatype emptyList { + empty_list{} +} -predicate (datatype seq) LinkedListFB (pointer front, pointer back) { - if (is_null(front)) { - return Seq_Nil{}; - } else { - if (front == back) { - take F = Owned(front); - assert (is_null(F.next)); - assert (is_null(F.prev)); - return Seq_Cons {head: F.data, tail: Seq_Nil{}}; - } else { - take B = Owned(back); - assert (is_null(B.next)); - take F = Owned(front); //problem if only one element - assert(is_null(F.prev)); - take L = LinkedListAux (F.next, back); - return Seq_Cons{ head: F.data, tail: snoc(L, B.data)}; +datatype dblListOption { + Some {datatype dblList l}, + None {datatype emptyList nil} +} + +function (struct Node) getNode(datatype dblList l) { + match l { + dbl_list {first: f, node: n, rest: r} => { + n } } } -predicate (datatype seq) LinkedListAux (pointer f, pointer b) { - if (ptr_eq(f,b)) { - return Seq_Nil{}; + +function (datatype seq) flatten(datatype dblList l) { + match l { + dbl_list {first: f, node: n, rest: r} => { + append(f, Seq_Cons{head: n.data, tail: r}) + } + } +} + +function (datatype seq) flattenOption(datatype dblListOption l) { + match l { + Some {l: l1} => { + flatten(l1) + } + None {nil: n} => { + Seq_Nil{} + } + } +} + +function (datatype seq) getFirst(datatype dblList l) { + match l { + dbl_list {first: f, node: n, rest: r} => { + f + } + } +} + +function (datatype seq) getRest(datatype dblList l) { + match l { + dbl_list {first: f, node: n, rest: r} => { + r + } + } +} + +predicate (datatype dblListOption) LinkedList (pointer p) { + if (is_null(p)) { + return None{nil: empty_list{}}; } else { - take F = Owned(f); - assert (!is_null(F.next)); - take B = LinkedListAux(F.next, b); - return Seq_Cons{head: F.data, tail: B}; + take N = Owned(p); + take ret = LinkedListHelper(p,N); + return ret; } } -predicate (datatype seq) LinkedListPtr_backwards (pointer l) { - take L = Owned(l); - assert ( (is_null(L.front) && is_null(L.back)) - || (!is_null(L.front) && !is_null(L.back))); - take inner = LinkedListFB_backwards(L.front, L.back); - assert ( L.length == length(inner)); - return inner; +predicate (datatype dblListOption) LinkedListHelper (pointer p, struct Node N) { + if (N.next == p && N.prev == p) { + return None{nil: empty_list{}}; + } else { + // assert (is_null(N.next) || N.next.prev == N); + take first = OwnBackwards(N.prev); + // assert (is_null(N.prev) || N.prev.next == N); + take rest = OwnForwards(N.next); + // let nextNode = + // return dbl_list{first: first, node: N, rest: rest}; + return Some { l: dbl_list {first: first, node: N, rest: rest} }; + + } } -predicate (datatype seq) LinkedListFB_backwards (pointer front, pointer back) { - if (is_null(back)) { + +predicate (datatype seq) OwnForwards(pointer p) { + if (is_null(p)) { return Seq_Nil{}; } else { - take B = Owned(back); - assert (is_null(B.next)); - take F = Owned(front); - assert(is_null(F.prev)); - take L = LinkedListAux (front, B.prev); - return Seq_Cons{ head: F.data, tail: snoc(L, B.data)}; + take N = Owned(p); + take rest = OwnForwards(N.next); + // assert (is_null(N.next) || (*(N.next)).prev == N); + return Seq_Cons{head: N.data, tail: rest}; } } - -predicate (datatype seq) LinkedListAux_backwards (pointer f, pointer b) { - if (ptr_eq(f,b)) { +predicate (datatype seq) OwnBackwards(pointer p) { + if (is_null(p)) { return Seq_Nil{}; } else { - take B = Owned(b); - assert (!is_null(B.prev)); - take F = LinkedListAux(f, B.prev); - return snoc(F, B.data); + take N = Owned(p); + // assert (is_null(N.prev) || N.prev.next == N); + take first = OwnBackwards(N.prev); + return snoc(first, N.data); } } @*/ -extern struct linkedList *mallocLinkedList(); -/*@ spec mallocLinkedList(); +extern struct Node *mallocNode(); +/*@ spec mallocNode(); requires true; - ensures take u = Block(return); + ensures take u = Block(return); !ptr_eq(return,NULL); @*/ -extern void freeLinkedList (struct linkedList *p); -/*@ spec freeLinkedList(pointer p); - requires take u = Block(p); +extern void freeNode (struct Node *p); +/*@ spec freeNode(pointer p); + requires take u = Block(p); ensures true; @*/ -extern struct linkedListCell *mallocLinkedListCell(); -/*@ spec mallocLinkedListCell(); - requires true; - ensures take u = Block(return); - !is_null(return); -@*/ - -extern void freeLinkedListCell (struct linkedListCell *p); -/*@ spec freeLinkedListCell(pointer p); - requires take u = Block(p); - ensures true; -@*/ - -struct linkedList* LinkedList_empty () -/*@ ensures take ret = LinkedListPtr(return); - ret == Seq_Nil{}; -@*/ -{ - struct linkedList *p = mallocLinkedList(); - p->front = 0; - p->back = 0; - p->length = 0; - /*@ unfold length(Seq_Nil{}); @*/ - return p; -} - -// // Given a linked list pointer, inserts a new node -// // to the head of the list -// void push (struct linkedList* l, int element) -// /*@ requires (!is_null(l)); -// take L1 = LinkedListPtr(l); -// ensures take L2 = LinkedListPtr(l); -// L2 == Seq_Cons{head: element, tail: L1}; -// length(L2) == length(L1) + 1u32; -// @*/ -// { -// struct linkedListCell *newCell = mallocLinkedListCell(); -// newCell->data = element; -// newCell->next = l->front; -// newCell->prev = 0; - -// if (l->front != 0) { -// l->front->prev = newCell; -// } - -// l->front = newCell; -// l->length = l->length + 1; -// } - - -/*@ -lemma append_lemma_ll (pointer front, pointer p) - requires - take L = LinkedListAux(front, p); - take P = Owned(p); - ensures - take NewL = LinkedListAux(front, P.next); - NewL == snoc(L, P.data); -@*/ - -/*@ -lemma snoc_facts (pointer front, pointer back, i32 x) - requires - take L = LinkedListAux(front, back); - take B = Owned(back); - ensures - take NewL = LinkedListAux(front, back); - take NewB = Owned(back); - L == NewL; B == NewB; - let S = snoc (Seq_Cons{head: x, tail: L}, B.data); - hd(S) == x; - tl(S) == snoc (L, B.data); -@*/ - -// Given a linked list pointer, inserts a new node -// at the end of the list. -void append (struct linkedList* l, int element) -/*@ requires (!is_null(l)); - take L1 = LinkedListPtr(l); - ensures take L2 = LinkedListPtr(l); - L2 == snoc(L1, element); - length(L2) == length(L1) + 1u32; +struct Node *empty() +/*@ ensures take ret = LinkedList(return); + flattenOption(ret) == Seq_Nil{}; @*/ { - struct linkedListCell *newCell = mallocLinkedListCell(); - newCell->data = element; - newCell->next = 0; - newCell->prev = 0; - - // empty list case - if (l->back == 0) { - /*@ assert(L1 == Seq_Nil{}); @*/ - l->front = newCell; - l->back = newCell; - l->length = l->length + 1; - /*@ unfold length(Seq_Nil{}); @*/ - /*@ unfold length(Seq_Cons {head: element, tail: Seq_Nil{}}); @*/ - /*@ unfold snoc(L1, element); @*/ - return; - } else { - struct linkedListCell *oldback = l->back; - // l->back->next = newCell; - l->back = newCell; - newCell->prev = oldback; - l->length = l->length + 1; - /*@ apply append_lemma_ll((*l).front, oldback); @*/ - return; - } + struct Node *n = mallocNode(); + n->data = 0; + n->prev = n; + n->next = n; + return n; } \ No newline at end of file From cc2a8cde4357bbbeb0b31ea1eae0877575ceca57 Mon Sep 17 00:00:00 2001 From: Liz Austell Date: Thu, 27 Jun 2024 15:00:13 -0400 Subject: [PATCH 062/152] rework everything with opts and seqs --- src/examples/Linked_List/WIPlinklist.c | 142 +++++++++++++------------ 1 file changed, 75 insertions(+), 67 deletions(-) diff --git a/src/examples/Linked_List/WIPlinklist.c b/src/examples/Linked_List/WIPlinklist.c index 045ba774..e4391b4f 100644 --- a/src/examples/Linked_List/WIPlinklist.c +++ b/src/examples/Linked_List/WIPlinklist.c @@ -1,7 +1,8 @@ #include "../list.h" #include "../list_length.c" -#include "../list_snoc.h" +// #include "../list_snoc.h" #include "../list_append.h" +#include "../list_rev.h" struct Node { int data; @@ -11,107 +12,112 @@ struct Node { /*@ datatype dblList { - dbl_list {datatype seq first, struct Node node, datatype seq rest} -} - -datatype emptyList { - empty_list{} + opts {datatype dblListOption first, datatype dblListOption rest}, + seqs {datatype seq f, struct Node n, datatype seq r} } datatype dblListOption { - Some {datatype dblList l}, - None {datatype emptyList nil} + empty {}, + nonEmpty {struct Node node, datatype seq tail} } -function (struct Node) getNode(datatype dblList l) { - match l { - dbl_list {first: f, node: n, rest: r} => { - n - } - } -} - - function (datatype seq) flatten(datatype dblList l) { match l { - dbl_list {first: f, node: n, rest: r} => { - append(f, Seq_Cons{head: n.data, tail: r}) + opts{ first: f, rest: r} => { + match f { + empty{} => { + match r { + empty{} => { + Seq_Nil{} + } + nonEmpty{node: n, tail: t} => { + Seq_Cons{head: n.data, tail: t} + } + } + } + nonEmpty{node: n, tail: t} => { + match r { + empty{} => { + rev(Seq_Cons{head: n.data, tail: t}) + } + nonEmpty{node: n2, tail: t2} => { + append(rev(Seq_Cons{head: n.data, tail: t}), Seq_Cons{head: n2.data, tail: t2}) + } + } + } + } } - } + seqs {f: f, n: n, r: r} => { + append(rev(f), Seq_Cons{head: n.data, tail: r}) + } + } } -function (datatype seq) flattenOption(datatype dblListOption l) { - match l { - Some {l: l1} => { - flatten(l1) - } - None {nil: n} => { - Seq_Nil{} - } - } -} - -function (datatype seq) getFirst(datatype dblList l) { - match l { - dbl_list {first: f, node: n, rest: r} => { - f - } +predicate (datatype dblList) LinkedList (pointer p) { + if (is_null(p)) { + return opts{first: empty{}, rest: empty{}}; + } else { + take N = Owned(p); + take ret = LinkedListHelper(p,N); + return ret; } } -function (datatype seq) getRest(datatype dblList l) { - match l { - dbl_list {first: f, node: n, rest: r} => { - r - } +predicate (datatype dblList) LinkedListHelper (pointer p, struct Node N) { + if (ptr_eq(N.next,p) && ptr_eq(N.prev,p)) { + return opts{first: empty{}, rest: empty{}}; + } else { + take first = OwnBackwards(N.prev, p, N); + take rest = OwnForwards(N.next, p, N); + return opts{first: first, rest: rest}; } } -predicate (datatype dblListOption) LinkedList (pointer p) { +predicate (datatype dblListOption) OwnForwards(pointer p, pointer PrevPointer, struct Node PrevNode) { if (is_null(p)) { - return None{nil: empty_list{}}; + return empty{}; } else { take N = Owned(p); - take ret = LinkedListHelper(p,N); - return ret; + assert (ptr_eq(N.prev, PrevPointer)); + assert(ptr_eq(PrevNode.next,p)); + take rest = OwnForwardsAux(N.next, p, N); + return nonEmpty{node: N, tail: rest}; } } -predicate (datatype dblListOption) LinkedListHelper (pointer p, struct Node N) { - if (N.next == p && N.prev == p) { - return None{nil: empty_list{}}; +predicate (datatype seq) OwnForwardsAux(pointer p, pointer PrevPointer, struct Node PrevNode) { + if (is_null(p)) { + return Seq_Nil{}; } else { - // assert (is_null(N.next) || N.next.prev == N); - take first = OwnBackwards(N.prev); - // assert (is_null(N.prev) || N.prev.next == N); - take rest = OwnForwards(N.next); - // let nextNode = - // return dbl_list{first: first, node: N, rest: rest}; - return Some { l: dbl_list {first: first, node: N, rest: rest} }; - + take N = Owned(p); + assert (ptr_eq(N.prev, PrevPointer)); + assert(ptr_eq(PrevNode.next,p)); + take rest = OwnForwardsAux(N.next, p, N); + return Seq_Cons{head: N.data, tail: rest}; } } - -predicate (datatype seq) OwnForwards(pointer p) { +predicate (datatype dblListOption) OwnBackwards(pointer p, pointer NextPointer, struct Node NextNode) { if (is_null(p)) { - return Seq_Nil{}; + return empty{}; } else { take N = Owned(p); - take rest = OwnForwards(N.next); - // assert (is_null(N.next) || (*(N.next)).prev == N); - return Seq_Cons{head: N.data, tail: rest}; + assert (ptr_eq(N.next,NextPointer)); + assert(ptr_eq(NextNode.prev,p)); + take first = OwnBackwardsAux(N.prev, p, N); + return nonEmpty{node: N, tail: first}; } } -predicate (datatype seq) OwnBackwards(pointer p) { +predicate (datatype seq) OwnBackwardsAux(pointer p, pointer NextPointer, struct Node NextNode) { if (is_null(p)) { return Seq_Nil{}; } else { take N = Owned(p); - // assert (is_null(N.prev) || N.prev.next == N); - take first = OwnBackwards(N.prev); - return snoc(first, N.data); + assert (ptr_eq(N.next,NextPointer)); + assert(ptr_eq(NextNode.prev,p)); + take first = OwnBackwardsAux(N.prev, p, N); + return Seq_Cons{head: N.data, tail: first}; } } @*/ @@ -131,12 +137,14 @@ extern void freeNode (struct Node *p); struct Node *empty() /*@ ensures take ret = LinkedList(return); - flattenOption(ret) == Seq_Nil{}; + // ret == dbl_list{first: Seq_Nil{}, rest: Seq_Nil{}}; + flatten(ret) == Seq_Nil{}; @*/ { struct Node *n = mallocNode(); n->data = 0; n->prev = n; n->next = n; +// /*@ unfold append(Seq_Nil{}, Seq_Nil{}); @*/ return n; } \ No newline at end of file From c84ccd20df72d11dd201c6d8d4cb38348b65136c Mon Sep 17 00:00:00 2001 From: Liz Austell Date: Fri, 28 Jun 2024 16:17:47 -0400 Subject: [PATCH 063/152] add simple functions with new dblList and dblListOption types --- src/examples/Linked_List/WIPlinklist.c | 114 ++++++++++++++++++++++--- 1 file changed, 102 insertions(+), 12 deletions(-) diff --git a/src/examples/Linked_List/WIPlinklist.c b/src/examples/Linked_List/WIPlinklist.c index e4391b4f..579de362 100644 --- a/src/examples/Linked_List/WIPlinklist.c +++ b/src/examples/Linked_List/WIPlinklist.c @@ -12,8 +12,8 @@ struct Node { /*@ datatype dblList { - opts {datatype dblListOption first, datatype dblListOption rest}, - seqs {datatype seq f, struct Node n, datatype seq r} + FirstRest {datatype dblListOption first, datatype dblListOption rest}, + FirstNodeRest {datatype dblListOption f, struct Node n, datatype dblListOption r} } datatype dblListOption { @@ -23,7 +23,7 @@ datatype dblListOption { function (datatype seq) flatten(datatype dblList l) { match l { - opts{ first: f, rest: r} => { + FirstRest{ first: f, rest: r} => { match f { empty{} => { match r { @@ -47,29 +47,51 @@ function (datatype seq) flatten(datatype dblList l) { } } } - seqs {f: f, n: n, r: r} => { - append(rev(f), Seq_Cons{head: n.data, tail: r}) + FirstNodeRest {f: f, n: n, r: r} => { + // append(rev(f), Seq_Cons{head: n.data, tail: r}) + match f { + empty{} => { + match r { + empty{} => { + Seq_Cons{head: n.data, tail: Seq_Nil{}} + } + nonEmpty{node: n2, tail: t} => { + Seq_Cons{head: n.data, tail: Seq_Cons{head: n2.data, tail: t}} + } + } + } + nonEmpty{node: n1, tail: t1} => { + match r { + empty{} => { + rev(Seq_Cons{head: n.data, tail: Seq_Cons{head: n1.data, tail: t1}}) + } + nonEmpty{node: n2, tail: t2} => { + append(rev(Seq_Cons{head: n.data, tail: Seq_Cons{head: n1.data, tail: t1}}), Seq_Cons{head: n2.data, tail: t2}) + } + } + } + } } } } predicate (datatype dblList) LinkedList (pointer p) { if (is_null(p)) { - return opts{first: empty{}, rest: empty{}}; + return FirstRest{first: empty{}, rest: empty{}}; } else { take N = Owned(p); - take ret = LinkedListHelper(p,N); + take ret = LinkedListAux(p,N); return ret; } } -predicate (datatype dblList) LinkedListHelper (pointer p, struct Node N) { +predicate (datatype dblList) LinkedListAux (pointer p, struct Node N) { if (ptr_eq(N.next,p) && ptr_eq(N.prev,p)) { - return opts{first: empty{}, rest: empty{}}; + return FirstRest{first: empty{}, rest: empty{}}; } else { take first = OwnBackwards(N.prev, p, N); take rest = OwnForwards(N.next, p, N); - return opts{first: first, rest: rest}; + return FirstNodeRest{f: first, n: N, r: rest}; } } @@ -97,6 +119,8 @@ predicate (datatype seq) OwnForwardsAux(pointer p, pointer PrevPointer, struct N } } + + predicate (datatype dblListOption) OwnBackwards(pointer p, pointer NextPointer, struct Node NextNode) { if (is_null(p)) { return empty{}; @@ -137,7 +161,7 @@ extern void freeNode (struct Node *p); struct Node *empty() /*@ ensures take ret = LinkedList(return); - // ret == dbl_list{first: Seq_Nil{}, rest: Seq_Nil{}}; + ret == FirstRest{first: empty{}, rest: empty{}}; flatten(ret) == Seq_Nil{}; @*/ { @@ -145,6 +169,72 @@ struct Node *empty() n->data = 0; n->prev = n; n->next = n; -// /*@ unfold append(Seq_Nil{}, Seq_Nil{}); @*/ return n; +} + +struct Node *singleton(int element) +/*@ ensures take ret = LinkedList(return); + flatten(ret) == Seq_Cons{head: element, tail: Seq_Nil{}}; +@*/ +{ + struct Node *n = mallocNode(); + n->data = element; + n->prev = 0; + n->next = 0; + return n; +} + +// struct Node *foo() +// /*@ ensures take ret = Owned(return); +// @*/ +// { +// struct Node *n = mallocNode(); +// n->data = 0; +// n->prev = 0; +// n->next = 0; +// return n; +// } + +// is it possible to make the pre and post conditions more concise? +struct Node *add_between_verbose(int element, struct Node *prevNode, struct Node *nextNode) +/*@ requires !is_null(prevNode) && !is_null(nextNode); + take prev = Owned(prevNode); + take next = Owned(nextNode); + take rest = LinkedListAux(nextNode, next); + take first = LinkedListAux(prevNode, prev); + + ptr_eq(prev.next, nextNode); + ptr_eq(next.prev, prevNode); + + ensures take ret = Owned(return); + take prev_ = Owned(prevNode); + take next_ = Owned(nextNode); + take rest_ = LinkedListAux(nextNode, next); + take first_ = LinkedListAux(prevNode, prev); + + ptr_eq(prev_.next, return); + ptr_eq(ret.prev, prevNode); + ptr_eq(ret.next, nextNode); + ptr_eq(next_.prev, return); + ptr_eq(prev_.prev, prev.prev); + ptr_eq(next_.next, next.next); + + rest == rest_; + first == first_; + // take l = LinkedListAux(return, ret); + // result == append(snoc(first,prev.data), Seq_Cons{head: element, tail: Seq_Cons{head: next.data, tail: rest}}); + // result != Seq_Nil{}; +@*/ +{ + struct Node *newNode = mallocNode(); + + newNode->prev = prevNode; + newNode->data = element; + newNode->next = nextNode; + + + prevNode->next = newNode; + nextNode->prev = newNode; + // /*@ unfold append(snoc(first,prev.data), Seq_Cons{head: element, tail: Seq_Cons{head: next.data, tail: rest}}); @*/ + return newNode; } \ No newline at end of file From b22410cf20d991df3dbabdf024594b5df4bf6b3c Mon Sep 17 00:00:00 2001 From: Liz Austell Date: Fri, 28 Jun 2024 16:27:47 -0400 Subject: [PATCH 064/152] another attempt at add_between function --- src/examples/Linked_List/WIPlinklist.c | 39 ++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/examples/Linked_List/WIPlinklist.c b/src/examples/Linked_List/WIPlinklist.c index 579de362..31806664 100644 --- a/src/examples/Linked_List/WIPlinklist.c +++ b/src/examples/Linked_List/WIPlinklist.c @@ -233,6 +233,45 @@ struct Node *add_between_verbose(int element, struct Node *prevNode, struct Node newNode->next = nextNode; + prevNode->next = newNode; + nextNode->prev = newNode; + // /*@ unfold append(snoc(first,prev.data), Seq_Cons{head: element, tail: Seq_Cons{head: next.data, tail: rest}}); @*/ + return newNode; +} + +struct Node *add_between_WIP(int element, struct Node *prevNode, struct Node *nextNode) +/*@ requires !is_null(prevNode) && !is_null(nextNode); + take prev = Owned(prevNode); + take next = Owned(nextNode); + take l = LinkedListAux(nextNode, next); + + ptr_eq(prev.next, nextNode); + ptr_eq(next.prev, prevNode); + + ensures take ret = Owned(return); + take prev_ = Owned(prevNode); + take next_ = Owned(nextNode); + take l_ = LinkedListAux(nextNode, next); + + ptr_eq(prev_.next, return); + ptr_eq(ret.prev, prevNode); + ptr_eq(ret.next, nextNode); + ptr_eq(next_.prev, return); + ptr_eq(prev_.prev, prev.prev); + ptr_eq(next_.next, next.next); + + // needs something about relationship between l and l_ + // flatten(l_) != Seq_Nil{}; + // length = length + 1 ?? +@*/ +{ + struct Node *newNode = mallocNode(); + + newNode->prev = prevNode; + newNode->data = element; + newNode->next = nextNode; + + prevNode->next = newNode; nextNode->prev = newNode; // /*@ unfold append(snoc(first,prev.data), Seq_Cons{head: element, tail: Seq_Cons{head: next.data, tail: rest}}); @*/ From 4b4e44581a843937011596fbbadee433055b2daa Mon Sep 17 00:00:00 2001 From: Liz Austell Date: Mon, 1 Jul 2024 16:58:42 -0400 Subject: [PATCH 065/152] more work --- src/examples/Linked_List/WIPlinklist.c | 225 +++++++++++++++++++++---- 1 file changed, 193 insertions(+), 32 deletions(-) diff --git a/src/examples/Linked_List/WIPlinklist.c b/src/examples/Linked_List/WIPlinklist.c index 31806664..cf33bad1 100644 --- a/src/examples/Linked_List/WIPlinklist.c +++ b/src/examples/Linked_List/WIPlinklist.c @@ -21,6 +21,61 @@ datatype dblListOption { nonEmpty {struct Node node, datatype seq tail} } +function (struct Node) getNodeOption(dblListOption l) { + match l { + empty{} => { + default + } + nonEmpty{node: n2, tail: t} => { + n2 + } + } +} + +function (struct Node) getNode(dblList l) { + match l { + FirstRest{first: f, rest: r} => { + default + } + FirstNodeRest{f: f, n: n, r: r} => { + n + } + } +} + +function (datatype dblListOption) getFirst(dblList l) { + match l { + FirstRest{first: f, rest: r} => { + f + } + FirstNodeRest{f: f, n: n, r: r} => { + f + } + } +} + +function (datatype dblListOption) getRest(dblList l) { + match l { + FirstRest{first: f, rest: r} => { + r + } + FirstNodeRest{f: f, n: n, r: r} => { + r + } + } +} + +// function (datatype seq) getFirstOption(dblListOption l) { +// match l { +// empty{} => { +// Seq_Nil{} +// } +// nonEmpty{node: n2, tail: t} => { +// t +// } +// } +// } + function (datatype seq) flatten(datatype dblList l) { match l { FirstRest{ first: f, rest: r} => { @@ -144,6 +199,27 @@ predicate (datatype seq) OwnBackwardsAux(pointer p, pointer NextPointer, struct return Seq_Cons{head: N.data, tail: first}; } } + +predicate (datatype dblListOption) OwnForwardsAlternate(pointer p) { + if (is_null(p)) { + return empty{}; + } else { + take N = Owned(p); + take rest = OwnForwardsAux(N.next, p, N); + return nonEmpty{node: N, tail: rest}; + } +} + +predicate (datatype dblListOption) OwnBackwardsAlternate(pointer p) { + if (is_null(p)) { + return empty{}; + } else { + take N = Owned(p); + take first = OwnBackwardsAux(N.prev, p, N); + return nonEmpty{node: N, tail: first}; + } +} + @*/ extern struct Node *mallocNode(); @@ -184,17 +260,6 @@ struct Node *singleton(int element) return n; } -// struct Node *foo() -// /*@ ensures take ret = Owned(return); -// @*/ -// { -// struct Node *n = mallocNode(); -// n->data = 0; -// n->prev = 0; -// n->next = 0; -// return n; -// } - // is it possible to make the pre and post conditions more concise? struct Node *add_between_verbose(int element, struct Node *prevNode, struct Node *nextNode) /*@ requires !is_null(prevNode) && !is_null(nextNode); @@ -212,18 +277,18 @@ struct Node *add_between_verbose(int element, struct Node *prevNode, struct Node take rest_ = LinkedListAux(nextNode, next); take first_ = LinkedListAux(prevNode, prev); - ptr_eq(prev_.next, return); - ptr_eq(ret.prev, prevNode); - ptr_eq(ret.next, nextNode); - ptr_eq(next_.prev, return); - ptr_eq(prev_.prev, prev.prev); - ptr_eq(next_.next, next.next); - - rest == rest_; - first == first_; - // take l = LinkedListAux(return, ret); - // result == append(snoc(first,prev.data), Seq_Cons{head: element, tail: Seq_Cons{head: next.data, tail: rest}}); - // result != Seq_Nil{}; + ptr_eq(prev_.next, return); + ptr_eq(ret.prev, prevNode); + ptr_eq(ret.next, nextNode); + ptr_eq(next_.prev, return); + ptr_eq(prev_.prev, prev.prev); + ptr_eq(next_.next, next.next); + + rest == rest_; + first == first_; + // take l = LinkedListAux(return, ret); + // result == append(snoc(first,prev.data), Seq_Cons{head: element, tail: Seq_Cons{head: next.data, tail: rest}}); + // result != Seq_Nil{}; @*/ { struct Node *newNode = mallocNode(); @@ -239,7 +304,7 @@ struct Node *add_between_verbose(int element, struct Node *prevNode, struct Node return newNode; } -struct Node *add_between_WIP(int element, struct Node *prevNode, struct Node *nextNode) +struct Node *add_between_verbose2(int element, struct Node *prevNode, struct Node *nextNode) /*@ requires !is_null(prevNode) && !is_null(nextNode); take prev = Owned(prevNode); take next = Owned(nextNode); @@ -253,14 +318,13 @@ struct Node *add_between_WIP(int element, struct Node *prevNode, struct Node *ne take next_ = Owned(nextNode); take l_ = LinkedListAux(nextNode, next); - ptr_eq(prev_.next, return); - ptr_eq(ret.prev, prevNode); - ptr_eq(ret.next, nextNode); - ptr_eq(next_.prev, return); - ptr_eq(prev_.prev, prev.prev); - ptr_eq(next_.next, next.next); + ptr_eq(prev_.next, return); + ptr_eq(ret.prev, prevNode); + ptr_eq(ret.next, nextNode); + ptr_eq(next_.prev, return); + ptr_eq(prev_.prev, prev.prev); + ptr_eq(next_.next, next.next); - // needs something about relationship between l and l_ // flatten(l_) != Seq_Nil{}; // length = length + 1 ?? @*/ @@ -274,6 +338,103 @@ struct Node *add_between_WIP(int element, struct Node *prevNode, struct Node *ne prevNode->next = newNode; nextNode->prev = newNode; - // /*@ unfold append(snoc(first,prev.data), Seq_Cons{head: element, tail: Seq_Cons{head: next.data, tail: rest}}); @*/ + return newNode; +} + +// struct Node *add_between_WIP(int element, struct Node *prevNode, struct Node *nextNode) +// /*@ requires !is_null(prevNode) && !is_null(nextNode); +// take l = LinkedList(prevNode); +// let prev = getNode(l); +// let next = getNodeOption(getRest(l)); + +// ptr_eq(prev.next, nextNode); +// ptr_eq(next.prev, prevNode); + +// ensures take ret = Owned(return); +// take prev_ = Owned(prevNode); +// take next_ = Owned(nextNode); +// take l_ = LinkedListAux(nextNode, next); + +// ptr_eq(prev_.next, return); +// ptr_eq(ret.prev, prevNode); +// ptr_eq(ret.next, nextNode); +// ptr_eq(next_.prev, return); +// ptr_eq(prev_.prev, prev.prev); +// ptr_eq(next_.next, next.next); + +// // flatten(l_) != Seq_Nil{}; +// // length = length + 1 ?? +// @*/ +// { +// struct Node *newNode = mallocNode(); + +// newNode->prev = prevNode; +// newNode->data = element; +// newNode->next = nextNode; + + +// prevNode->next = newNode; +// nextNode->prev = newNode; + +// return newNode; +// } + +int remove_helper(struct Node *node) +/*@ requires take del = Owned(node); + !is_null(del.prev) || !is_null(del.next); + take first = Owned(del.prev); + take rest = Owned(del.next); + take l = LinkedList(del.next); + + ensures take first_ = Owned(del.prev); + take rest_ = Owned(del.next); + take l_ = LinkedList(del.next); +@*/ +{ + struct Node *prev = node->prev; + struct Node *next = node->next; + + prev->next = next; + next->prev = prev; + + int data = node->data; + freeNode(node); + return data; +} + +int remove(struct Node *n) +/*@ requires take del = Owned(n); + take first = OwnForwardsAlternate(del.next); + take rest = OwnBackwardsAlternate(del.prev); + !is_null(del.prev) || !is_null(del.next); + ensures take first_ = OwnForwardsAlternate(del.next); + take rest_ = OwnBackwardsAlternate(del.prev); + rest == rest_; + first == first_; +@*/ +{ + /*@ split_case(is_null(n)); @*/ + /*@ split_case(is_null((*n).next)); @*/ + /*@ split_case(is_null((*n).prev)); @*/ + + + if (n->prev == 0) { + /*@ assert(is_null((*n).prev)); @*/ + /*@ assert(!is_null((*n).next)); @*/ + + // n is the head + // n->next->prev = 0; + } else if (n->next == 0) { + /*@ assert(is_null((*n).next)); @*/ + /*@ assert(!is_null((*n).prev)); @*/ + // n is the tail + // n->prev->next = 0; + } else { + // n->next->prev = 0; + // n->prev->next = 0; + } + int temp = n->data; + freeNode(n); + return temp; } \ No newline at end of file From 9a6b40031f0df1cebbc79c85450e40662c84e48c Mon Sep 17 00:00:00 2001 From: Liz Austell Date: Mon, 1 Jul 2024 17:37:11 -0400 Subject: [PATCH 066/152] add remove function (working) --- src/examples/Linked_List/WIPlinklist.c | 68 +++++++++++++++++--------- 1 file changed, 44 insertions(+), 24 deletions(-) diff --git a/src/examples/Linked_List/WIPlinklist.c b/src/examples/Linked_List/WIPlinklist.c index cf33bad1..a22f862f 100644 --- a/src/examples/Linked_List/WIPlinklist.c +++ b/src/examples/Linked_List/WIPlinklist.c @@ -21,6 +21,17 @@ datatype dblListOption { nonEmpty {struct Node node, datatype seq tail} } +function (bool) is_empty(datatype dblListOption l) { + match l { + empty{} => { + true + } + nonEmpty{node: n, tail: t} => { + false + } + } +} + function (struct Node) getNodeOption(dblListOption l) { match l { empty{} => { @@ -130,6 +141,17 @@ function (datatype seq) flatten(datatype dblList l) { } } +function (datatype seq) flattenOption(dblListOption l) { + match l { + empty{} => { + Seq_Nil{} + } + nonEmpty{node: n, tail: t} => { + Seq_Cons{head: n.data, tail: t} + } + } +} + predicate (datatype dblList) LinkedList (pointer p) { if (is_null(p)) { return FirstRest{first: empty{}, rest: empty{}}; @@ -405,35 +427,33 @@ int remove_helper(struct Node *node) int remove(struct Node *n) /*@ requires take del = Owned(n); - take first = OwnForwardsAlternate(del.next); - take rest = OwnBackwardsAlternate(del.prev); + take rest = OwnForwardsAlternate(del.next); + take first = OwnBackwardsAlternate(del.prev); !is_null(del.prev) || !is_null(del.next); - ensures take first_ = OwnForwardsAlternate(del.next); - take rest_ = OwnBackwardsAlternate(del.prev); - rest == rest_; - first == first_; + ensures take rest_ = OwnForwardsAlternate(del.next); + take first_ = OwnBackwardsAlternate(del.prev); + flattenOption(first) == flattenOption(first_); + flattenOption(rest) == flattenOption(rest_); + is_empty(first_) ? true : ptr_eq(getNodeOption(first_).next,del.next); + is_empty(rest_) ? true : ptr_eq(getNodeOption(rest_).prev,del.prev); @*/ { - /*@ split_case(is_null(n)); @*/ - /*@ split_case(is_null((*n).next)); @*/ - /*@ split_case(is_null((*n).prev)); @*/ - - - if (n->prev == 0) { - /*@ assert(is_null((*n).prev)); @*/ - /*@ assert(!is_null((*n).next)); @*/ - - // n is the head - // n->next->prev = 0; - } else if (n->next == 0) { - /*@ assert(is_null((*n).next)); @*/ - /*@ assert(!is_null((*n).prev)); @*/ - // n is the tail - // n->prev->next = 0; + if (n->prev == 0) { // n is the head + /*@ split_case(is_null((*(*n).next).next)); @*/ + n->next->prev = 0; + } else if (n->next == 0) { // n is the tail + /*@ split_case(is_null((*(*n).prev).prev)); @*/ + n->prev->next = 0; } else { - // n->next->prev = 0; - // n->prev->next = 0; + /*@ split_case(is_null((*(*n).prev).prev)); @*/ + /*@ split_case(is_null((*(*n).next).next)); @*/ + struct Node *next = n->next; + struct Node *prev = n->prev; + + n->next->prev = prev; + n->prev->next = next; } + int temp = n->data; freeNode(n); return temp; From 4dc56cf1f78647f39bb2220c8a4a28434b310d97 Mon Sep 17 00:00:00 2001 From: Liz Austell Date: Tue, 2 Jul 2024 14:00:42 -0400 Subject: [PATCH 067/152] work on add functions --- src/examples/Linked_List/WIPlinklist.c | 187 +++++++++++++++---------- 1 file changed, 115 insertions(+), 72 deletions(-) diff --git a/src/examples/Linked_List/WIPlinklist.c b/src/examples/Linked_List/WIPlinklist.c index a22f862f..c45fb300 100644 --- a/src/examples/Linked_List/WIPlinklist.c +++ b/src/examples/Linked_List/WIPlinklist.c @@ -76,17 +76,6 @@ function (datatype dblListOption) getRest(dblList l) { } } -// function (datatype seq) getFirstOption(dblListOption l) { -// match l { -// empty{} => { -// Seq_Nil{} -// } -// nonEmpty{node: n2, tail: t} => { -// t -// } -// } -// } - function (datatype seq) flatten(datatype dblList l) { match l { FirstRest{ first: f, rest: r} => { @@ -364,67 +353,6 @@ struct Node *add_between_verbose2(int element, struct Node *prevNode, struct Nod return newNode; } -// struct Node *add_between_WIP(int element, struct Node *prevNode, struct Node *nextNode) -// /*@ requires !is_null(prevNode) && !is_null(nextNode); -// take l = LinkedList(prevNode); -// let prev = getNode(l); -// let next = getNodeOption(getRest(l)); - -// ptr_eq(prev.next, nextNode); -// ptr_eq(next.prev, prevNode); - -// ensures take ret = Owned(return); -// take prev_ = Owned(prevNode); -// take next_ = Owned(nextNode); -// take l_ = LinkedListAux(nextNode, next); - -// ptr_eq(prev_.next, return); -// ptr_eq(ret.prev, prevNode); -// ptr_eq(ret.next, nextNode); -// ptr_eq(next_.prev, return); -// ptr_eq(prev_.prev, prev.prev); -// ptr_eq(next_.next, next.next); - -// // flatten(l_) != Seq_Nil{}; -// // length = length + 1 ?? -// @*/ -// { -// struct Node *newNode = mallocNode(); - -// newNode->prev = prevNode; -// newNode->data = element; -// newNode->next = nextNode; - - -// prevNode->next = newNode; -// nextNode->prev = newNode; - -// return newNode; -// } - -int remove_helper(struct Node *node) -/*@ requires take del = Owned(node); - !is_null(del.prev) || !is_null(del.next); - take first = Owned(del.prev); - take rest = Owned(del.next); - take l = LinkedList(del.next); - - ensures take first_ = Owned(del.prev); - take rest_ = Owned(del.next); - take l_ = LinkedList(del.next); -@*/ -{ - struct Node *prev = node->prev; - struct Node *next = node->next; - - prev->next = next; - next->prev = prev; - - int data = node->data; - freeNode(node); - return data; -} - int remove(struct Node *n) /*@ requires take del = Owned(n); take rest = OwnForwardsAlternate(del.next); @@ -457,4 +385,119 @@ int remove(struct Node *n) int temp = n->data; freeNode(n); return temp; +} + +// void append_dep(int element, struct Node *n) +// /*@ requires !is_null(n); +// take tailNode = Owned(n); +// is_null(tailNode.next); +// take l = LinkedListAux(n, tailNode); +// ensures take oldTail = Owned(n); +// take l_ = LinkedListAux(n, oldTail); +// !is_null(oldTail.next); +// // l_ == dbl_list{first: getFirst(l), rest: snoc(getRest(l), element)}; +// @*/ +// { +// /*@ assert(!is_null(n)); @*/ +// /*@ assert(!(ptr_eq((*n).next, n) && ptr_eq((*n).prev, n))); @*/ +// /*@ split_case(ptr_eq((*n).next, n) && ptr_eq((*n).prev, n)); @*/ +// // (ptr_eq(N.next,p) && ptr_eq(N.prev,p)) +// struct Node *newNode = mallocNode(); +// newNode->data = element; +// newNode->prev = n; +// newNode->next = 0; +// n->next = newNode; +// } + +struct Node* append(int element, struct Node *n) +/*@ requires !is_null(n); + take tailNode = Owned(n); + is_null(tailNode.next); + take l = OwnBackwards(tailNode.prev, n, tailNode); + ensures take oldTail = Owned(n); + take newTail = Owned(return); + take l_ = OwnBackwards(tailNode.prev, n, tailNode); + ptr_eq(oldTail.next, return); + is_null(newTail.next); + ptr_eq(newTail.prev, n); + l_ == l; +@*/ +{ + struct Node *newNode = mallocNode(); + newNode->data = element; + newNode->prev = n; + newNode->next = 0; + n->next = newNode; + + return newNode; +} + +void append2(int element, struct Node *n) +/*@ requires !is_null(n); + take tailNode = Owned(n); + is_null(tailNode.next); + take l = LinkedListAux(n, tailNode); + ensures !is_null(n); + take oldTail = Owned(n); + take l_ = LinkedListAux(n, oldTail); + // ptr_eq(oldTail.next, return); + // is_null(newTail.next); + // ptr_eq(newTail.prev, n); + // l_ == l; +@*/ +{ + /*@ split_case(ptr_eq((*n).next, n) && ptr_eq((*n).prev, n)); @*/ + struct Node *newNode = mallocNode(); + newNode->data = element; + newNode->prev = n; + newNode->next = 0; + n->next = newNode; + + /*@ assert(ptr_eq((*n).next,newNode)); @*/ + /*@ assert(ptr_eq((*newNode).prev,n)); @*/ + return; +} + +void add_helper(int element, struct Node *n) +/*@ requires !is_null(n); + take node = Owned(n); + take l = LinkedListAux(n, node); + ensures !is_null(n); + take node_ = Owned(n); + take l_ = LinkedListAux(n, node_); +@*/ +{ + if (n->next == 0) { + // append(element,n); + } else { + // struct Node *newNode = mallocNode(); + // newNode->data = element; + // newNode->prev = n; + // newNode->next = n->next; + // n->next->prev = newNode; + // n->next = newNode; + return; + } +} + +void add_WIP(int element, struct Node *n) +/*@ requires !is_null(n); + take node = Owned(n); + take l = LinkedListAux(n, node); + ensures !is_null(n); + take node_ = Owned(n); + take l_ = LinkedListAux(n, node_); +@*/ +{ + /*@ split_case(ptr_eq((*n).next, n) && ptr_eq((*n).prev, n)); @*/ + struct Node *next = n->next; + if (next == n->prev && next == n) { // empty list case + n->data = element; + n->prev = 0; + n->next = 0; + return; + } else { + add_helper(element,n); + return; + } } \ No newline at end of file From 12953f15d13c9975e4ce5c7615ab84e41874a949 Mon Sep 17 00:00:00 2001 From: Liz Austell Date: Tue, 9 Jul 2024 16:51:33 -0400 Subject: [PATCH 068/152] more changes --- src/examples/Linked_List/WIPlinklist.c | 142 ++++++++++++++++--------- 1 file changed, 93 insertions(+), 49 deletions(-) diff --git a/src/examples/Linked_List/WIPlinklist.c b/src/examples/Linked_List/WIPlinklist.c index c45fb300..ea7a79b4 100644 --- a/src/examples/Linked_List/WIPlinklist.c +++ b/src/examples/Linked_List/WIPlinklist.c @@ -432,14 +432,16 @@ struct Node* append(int element, struct Node *n) return newNode; } -void append2(int element, struct Node *n) +struct Node* append3(int element, struct Node *n) /*@ requires !is_null(n); take tailNode = Owned(n); is_null(tailNode.next); take l = LinkedListAux(n, tailNode); - ensures !is_null(n); - take oldTail = Owned(n); - take l_ = LinkedListAux(n, oldTail); + ensures take oldTail = Owned(n); + take l_ = LinkedListAux(n, oldTail); + + // take newTail = Owned(return); + // take l_ = OwnBackwards(tailNode.prev, n, tailNode); // ptr_eq(oldTail.next, return); // is_null(newTail.next); // ptr_eq(newTail.prev, n); @@ -447,57 +449,99 @@ void append2(int element, struct Node *n) @*/ { /*@ split_case(ptr_eq((*n).next, n) && ptr_eq((*n).prev, n)); @*/ + /*@ assert(!(ptr_eq((*n).next, n) && ptr_eq((*n).prev, n))); @*/ struct Node *newNode = mallocNode(); + /*@ assert(!ptr_eq(n, newNode));@*/ + newNode->data = element; newNode->prev = n; newNode->next = 0; n->next = newNode; - /*@ assert(ptr_eq((*n).next,newNode)); @*/ - /*@ assert(ptr_eq((*newNode).prev,n)); @*/ - return; -} + // /*@ assert(ptr_eq((*n).next,newNode)); @*/ + /*@ split_case(is_null((*n).next)); @*/ + /*@ assert(!ptr_eq(n, newNode));@*/ + /*@ assert(!(ptr_eq((*n).next, n) && ptr_eq((*n).prev, n))); @*/ -void add_helper(int element, struct Node *n) -/*@ requires !is_null(n); - take node = Owned(n); - take l = LinkedListAux(n, node); - ensures !is_null(n); - take node_ = Owned(n); - take l_ = LinkedListAux(n, node_); -@*/ -{ - if (n->next == 0) { - // append(element,n); - } else { - // struct Node *newNode = mallocNode(); - // newNode->data = element; - // newNode->prev = n; - // newNode->next = n->next; - // n->next->prev = newNode; - // n->next = newNode; - return; - } + return newNode; } -void add_WIP(int element, struct Node *n) -/*@ requires !is_null(n); - take node = Owned(n); - take l = LinkedListAux(n, node); - ensures !is_null(n); - take node_ = Owned(n); - take l_ = LinkedListAux(n, node_); -@*/ -{ - /*@ split_case(ptr_eq((*n).next, n) && ptr_eq((*n).prev, n)); @*/ - struct Node *next = n->next; - if (next == n->prev && next == n) { // empty list case - n->data = element; - n->prev = 0; - n->next = 0; - return; - } else { - add_helper(element,n); - return; - } -} \ No newline at end of file + + +// void append2(int element, struct Node *n) +// /*@ requires !is_null(n); +// take tailNode = Owned(n); +// is_null(tailNode.next); +// take l = LinkedListAux(n, tailNode); +// ensures !is_null(n); +// take oldTail = Owned(n); +// take l_ = LinkedListAux(n, oldTail); +// // ptr_eq(oldTail.next, return); +// // is_null(newTail.next); +// // ptr_eq(newTail.prev, n); +// // l_ == l; +// @*/ +// { +// /*@ split_case(ptr_eq((*n).next, n) && ptr_eq((*n).prev, n)); @*/ +// struct Node *newNode = mallocNode(); +// newNode->data = element; +// newNode->prev = n; +// newNode->next = 0; +// n->next = newNode; + +// /*@ assert(ptr_eq((*n).next,newNode)); @*/ +// /*@ assert(ptr_eq((*newNode).prev,n)); @*/ +// return; +// } + +// void add_helper(int element, struct Node *n) +// /*@ requires !is_null(n); +// take node = Owned(n); +// take l = LinkedListAux(n, node); +// ensures !is_null(n); +// take node_ = Owned(n); +// take l_ = LinkedListAux(n, node_); +// @*/ +// { +// if (n->next == 0) { +// // append(element,n); +// } else { +// // struct Node *newNode = mallocNode(); +// // newNode->data = element; +// // newNode->prev = n; +// // newNode->next = n->next; +// // n->next->prev = newNode; +// // n->next = newNode; +// return; +// } +// } + +// void add_WIP(int element, struct Node *n) +// /*@ requires !is_null(n); +// take node = Owned(n); +// take l = LinkedListAux(n, node); +// ensures !is_null(n); +// take node_ = Owned(n); +// take l_ = LinkedListAux(n, node_); +// @*/ +// { +// /*@ split_case(ptr_eq((*n).next, n) && ptr_eq((*n).prev, n)); @*/ +// struct Node *next = n->next; +// if (next == n->prev && next == n) { // empty list case +// n->data = element; +// n->prev = 0; +// n->next = 0; +// return; +// } else { +// add_helper(element,n); +// return; +// } +// } + +/*@ lemma ownedStuff(pointer x, pointer y) +requires take x_ = Owned(x); + take y_ = Owned(y); +ensures !ptr_eq(x,y); + !is_null(x); + !is_null(y); +@*/ \ No newline at end of file From 8e2e261f6d7a473d3c030446cb10e9fe6070e079 Mon Sep 17 00:00:00 2001 From: Liz Austell <122940625+elaustell@users.noreply.github.com> Date: Tue, 9 Jul 2024 17:29:15 -0400 Subject: [PATCH 069/152] Update WIPlinklist.c --- src/examples/Linked_List/WIPlinklist.c | 478 +++---------------------- 1 file changed, 54 insertions(+), 424 deletions(-) diff --git a/src/examples/Linked_List/WIPlinklist.c b/src/examples/Linked_List/WIPlinklist.c index ea7a79b4..2b623dc8 100644 --- a/src/examples/Linked_List/WIPlinklist.c +++ b/src/examples/Linked_List/WIPlinklist.c @@ -1,8 +1,13 @@ +// Consider an empty list being a null pointer, and have every function return a pointer +// to some part of the list (null pointer if empty list). + + #include "../list.h" -#include "../list_length.c" +// #include "../list_length.c" // #include "../list_snoc.h" #include "../list_append.h" #include "../list_rev.h" +#include "./pointereq.c" struct Node { int data; @@ -11,165 +16,43 @@ struct Node { }; /*@ -datatype dblList { - FirstRest {datatype dblListOption first, datatype dblListOption rest}, - FirstNodeRest {datatype dblListOption f, struct Node n, datatype dblListOption r} -} - -datatype dblListOption { - empty {}, - nonEmpty {struct Node node, datatype seq tail} -} - -function (bool) is_empty(datatype dblListOption l) { - match l { - empty{} => { - true - } - nonEmpty{node: n, tail: t} => { - false - } - } -} - -function (struct Node) getNodeOption(dblListOption l) { - match l { - empty{} => { - default - } - nonEmpty{node: n2, tail: t} => { - n2 - } - } -} - -function (struct Node) getNode(dblList l) { - match l { - FirstRest{first: f, rest: r} => { - default - } - FirstNodeRest{f: f, n: n, r: r} => { - n - } - } -} - -function (datatype dblListOption) getFirst(dblList l) { - match l { - FirstRest{first: f, rest: r} => { - f - } - FirstNodeRest{f: f, n: n, r: r} => { - f - } - } -} - -function (datatype dblListOption) getRest(dblList l) { - match l { - FirstRest{first: f, rest: r} => { - r - } - FirstNodeRest{f: f, n: n, r: r} => { - r - } - } +datatype dll { + empty_dll {}, + dll {datatype nodeSeq first, struct Node node, datatype nodeSeq rest} } -function (datatype seq) flatten(datatype dblList l) { - match l { - FirstRest{ first: f, rest: r} => { - match f { - empty{} => { - match r { - empty{} => { - Seq_Nil{} - } - nonEmpty{node: n, tail: t} => { - Seq_Cons{head: n.data, tail: t} - } - } - } - nonEmpty{node: n, tail: t} => { - match r { - empty{} => { - rev(Seq_Cons{head: n.data, tail: t}) - } - nonEmpty{node: n2, tail: t2} => { - append(rev(Seq_Cons{head: n.data, tail: t}), Seq_Cons{head: n2.data, tail: t2}) - } - } - } - } - } - FirstNodeRest {f: f, n: n, r: r} => { - // append(rev(f), Seq_Cons{head: n.data, tail: r}) - match f { - empty{} => { - match r { - empty{} => { - Seq_Cons{head: n.data, tail: Seq_Nil{}} - } - nonEmpty{node: n2, tail: t} => { - Seq_Cons{head: n.data, tail: Seq_Cons{head: n2.data, tail: t}} - } - } - } - nonEmpty{node: n1, tail: t1} => { - match r { - empty{} => { - rev(Seq_Cons{head: n.data, tail: Seq_Cons{head: n1.data, tail: t1}}) - } - nonEmpty{node: n2, tail: t2} => { - append(rev(Seq_Cons{head: n.data, tail: Seq_Cons{head: n1.data, tail: t1}}), Seq_Cons{head: n2.data, tail: t2}) - } - } - } - } - } - } +datatype nodeSeq { + nodeSeq_Nil {}, + nodeSeq_Cons {struct Node node, datatype seq tail} } -function (datatype seq) flattenOption(dblListOption l) { +function (datatype nodeSeq) getRest(datatype dll l) { match l { - empty{} => { - Seq_Nil{} - } - nonEmpty{node: n, tail: t} => { - Seq_Cons{head: n.data, tail: t} - } + empty_dll {} => { nodeSeq_Nil {} } + dll {first: _, node: _, rest: r} => { r } } } -predicate (datatype dblList) LinkedList (pointer p) { +predicate (datatype dll) LinkedList (pointer p) { if (is_null(p)) { - return FirstRest{first: empty{}, rest: empty{}}; + return empty_dll{}; } else { take N = Owned(p); - take ret = LinkedListAux(p,N); - return ret; - } -} - -predicate (datatype dblList) LinkedListAux (pointer p, struct Node N) { - if (ptr_eq(N.next,p) && ptr_eq(N.prev,p)) { - return FirstRest{first: empty{}, rest: empty{}}; - } else { take first = OwnBackwards(N.prev, p, N); take rest = OwnForwards(N.next, p, N); - return FirstNodeRest{f: first, n: N, r: rest}; + return dll{first: first, node: N, rest: rest}; } } -predicate (datatype dblListOption) OwnForwards(pointer p, pointer PrevPointer, struct Node PrevNode) { +predicate (datatype nodeSeq) OwnForwards(pointer p, pointer PrevPointer, struct Node PrevNode) { if (is_null(p)) { - return empty{}; + return nodeSeq_Nil{}; } else { take N = Owned(p); assert (ptr_eq(N.prev, PrevPointer)); assert(ptr_eq(PrevNode.next,p)); take rest = OwnForwardsAux(N.next, p, N); - return nonEmpty{node: N, tail: rest}; + return nodeSeq_Cons{node: N, tail: rest}; } } @@ -187,15 +70,15 @@ predicate (datatype seq) OwnForwardsAux(pointer p, pointer PrevPointer, struct N -predicate (datatype dblListOption) OwnBackwards(pointer p, pointer NextPointer, struct Node NextNode) { +predicate (datatype nodeSeq) OwnBackwards(pointer p, pointer NextPointer, struct Node NextNode) { if (is_null(p)) { - return empty{}; + return nodeSeq_Nil{}; } else { take N = Owned(p); assert (ptr_eq(N.next,NextPointer)); assert(ptr_eq(NextNode.prev,p)); take first = OwnBackwardsAux(N.prev, p, N); - return nonEmpty{node: N, tail: first}; + return nodeSeq_Cons{node: N, tail: first}; } } @@ -211,26 +94,25 @@ predicate (datatype seq) OwnBackwardsAux(pointer p, pointer NextPointer, struct } } -predicate (datatype dblListOption) OwnForwardsAlternate(pointer p) { +predicate (datatype nodeSeq) OwnForwardsAlternate(pointer p) { if (is_null(p)) { - return empty{}; + return nodeSeq_Nil{}; } else { take N = Owned(p); take rest = OwnForwardsAux(N.next, p, N); - return nonEmpty{node: N, tail: rest}; + return nodeSeq_Cons{node: N, tail: rest}; } } -predicate (datatype dblListOption) OwnBackwardsAlternate(pointer p) { +predicate (datatype nodeSeq) OwnBackwardsAlternate(pointer p) { if (is_null(p)) { - return empty{}; + return nodeSeq_Nil{}; } else { take N = Owned(p); take first = OwnBackwardsAux(N.prev, p, N); - return nonEmpty{node: N, tail: first}; + return nodeSeq_Cons{node: N, tail: first}; } } - @*/ extern struct Node *mallocNode(); @@ -246,22 +128,9 @@ extern void freeNode (struct Node *p); ensures true; @*/ -struct Node *empty() -/*@ ensures take ret = LinkedList(return); - ret == FirstRest{first: empty{}, rest: empty{}}; - flatten(ret) == Seq_Nil{}; -@*/ -{ - struct Node *n = mallocNode(); - n->data = 0; - n->prev = n; - n->next = n; - return n; -} - struct Node *singleton(int element) /*@ ensures take ret = LinkedList(return); - flatten(ret) == Seq_Cons{head: element, tail: Seq_Nil{}}; + ret == dll{first: nodeSeq_Nil{}, node: struct Node{data: element, prev: NULL, next: NULL}, rest: nodeSeq_Nil{}}; @*/ { struct Node *n = mallocNode(); @@ -271,277 +140,38 @@ struct Node *singleton(int element) return n; } -// is it possible to make the pre and post conditions more concise? -struct Node *add_between_verbose(int element, struct Node *prevNode, struct Node *nextNode) -/*@ requires !is_null(prevNode) && !is_null(nextNode); - take prev = Owned(prevNode); - take next = Owned(nextNode); - take rest = LinkedListAux(nextNode, next); - take first = LinkedListAux(prevNode, prev); - - ptr_eq(prev.next, nextNode); - ptr_eq(next.prev, prevNode); - - ensures take ret = Owned(return); - take prev_ = Owned(prevNode); - take next_ = Owned(nextNode); - take rest_ = LinkedListAux(nextNode, next); - take first_ = LinkedListAux(prevNode, prev); - - ptr_eq(prev_.next, return); - ptr_eq(ret.prev, prevNode); - ptr_eq(ret.next, nextNode); - ptr_eq(next_.prev, return); - ptr_eq(prev_.prev, prev.prev); - ptr_eq(next_.next, next.next); - - rest == rest_; - first == first_; - // take l = LinkedListAux(return, ret); - // result == append(snoc(first,prev.data), Seq_Cons{head: element, tail: Seq_Cons{head: next.data, tail: rest}}); - // result != Seq_Nil{}; -@*/ -{ - struct Node *newNode = mallocNode(); - - newNode->prev = prevNode; - newNode->data = element; - newNode->next = nextNode; - - - prevNode->next = newNode; - nextNode->prev = newNode; - // /*@ unfold append(snoc(first,prev.data), Seq_Cons{head: element, tail: Seq_Cons{head: next.data, tail: rest}}); @*/ - return newNode; -} - -struct Node *add_between_verbose2(int element, struct Node *prevNode, struct Node *nextNode) -/*@ requires !is_null(prevNode) && !is_null(nextNode); - take prev = Owned(prevNode); - take next = Owned(nextNode); - take l = LinkedListAux(nextNode, next); - - ptr_eq(prev.next, nextNode); - ptr_eq(next.prev, prevNode); - - ensures take ret = Owned(return); - take prev_ = Owned(prevNode); - take next_ = Owned(nextNode); - take l_ = LinkedListAux(nextNode, next); - - ptr_eq(prev_.next, return); - ptr_eq(ret.prev, prevNode); - ptr_eq(ret.next, nextNode); - ptr_eq(next_.prev, return); - ptr_eq(prev_.prev, prev.prev); - ptr_eq(next_.next, next.next); - - // flatten(l_) != Seq_Nil{}; - // length = length + 1 ?? -@*/ -{ - struct Node *newNode = mallocNode(); - - newNode->prev = prevNode; - newNode->data = element; - newNode->next = nextNode; - - - prevNode->next = newNode; - nextNode->prev = newNode; - - return newNode; -} - -int remove(struct Node *n) -/*@ requires take del = Owned(n); - take rest = OwnForwardsAlternate(del.next); - take first = OwnBackwardsAlternate(del.prev); - !is_null(del.prev) || !is_null(del.next); - ensures take rest_ = OwnForwardsAlternate(del.next); - take first_ = OwnBackwardsAlternate(del.prev); - flattenOption(first) == flattenOption(first_); - flattenOption(rest) == flattenOption(rest_); - is_empty(first_) ? true : ptr_eq(getNodeOption(first_).next,del.next); - is_empty(rest_) ? true : ptr_eq(getNodeOption(rest_).prev,del.prev); -@*/ -{ - if (n->prev == 0) { // n is the head - /*@ split_case(is_null((*(*n).next).next)); @*/ - n->next->prev = 0; - } else if (n->next == 0) { // n is the tail - /*@ split_case(is_null((*(*n).prev).prev)); @*/ - n->prev->next = 0; - } else { - /*@ split_case(is_null((*(*n).prev).prev)); @*/ - /*@ split_case(is_null((*(*n).next).next)); @*/ - struct Node *next = n->next; - struct Node *prev = n->prev; - - n->next->prev = prev; - n->prev->next = next; - } - - int temp = n->data; - freeNode(n); - return temp; -} - -// void append_dep(int element, struct Node *n) -// /*@ requires !is_null(n); -// take tailNode = Owned(n); -// is_null(tailNode.next); -// take l = LinkedListAux(n, tailNode); -// ensures take oldTail = Owned(n); -// take l_ = LinkedListAux(n, oldTail); -// !is_null(oldTail.next); -// // l_ == dbl_list{first: getFirst(l), rest: snoc(getRest(l), element)}; -// @*/ -// { -// /*@ assert(!is_null(n)); @*/ -// /*@ assert(!(ptr_eq((*n).next, n) && ptr_eq((*n).prev, n))); @*/ -// /*@ split_case(ptr_eq((*n).next, n) && ptr_eq((*n).prev, n)); @*/ -// // (ptr_eq(N.next,p) && ptr_eq(N.prev,p)) -// struct Node *newNode = mallocNode(); -// newNode->data = element; -// newNode->prev = n; -// newNode->next = 0; -// n->next = newNode; -// } - -struct Node* append(int element, struct Node *n) -/*@ requires !is_null(n); - take tailNode = Owned(n); - is_null(tailNode.next); - take l = OwnBackwards(tailNode.prev, n, tailNode); - ensures take oldTail = Owned(n); - take newTail = Owned(return); - take l_ = OwnBackwards(tailNode.prev, n, tailNode); - ptr_eq(oldTail.next, return); - is_null(newTail.next); - ptr_eq(newTail.prev, n); - l_ == l; +// Appends `second` to the end of `first`, where `first` is the tail of the first list and +// `second` is the head of the second list. +struct Node *append (struct Node *first, struct Node *second) +/*@ requires take n1 = Owned(first); + take n2 = Owned(second); + take l = OwnBackwards(n1.prev, first, n1); + take r = OwnForwards(n2.next, second, n2); + is_null(n1.next) && is_null(n2.prev); + ensures take n1_ = Owned(first); + take n2_ = Owned(second); + take l_ = OwnBackwards(n1.prev, first, n1); + take r_ = OwnForwards(n2.next, second, n2); + ptr_eq(n1_.next,second) && ptr_eq(n2_.prev, first); + l == l_ && r == r_; @*/ { - struct Node *newNode = mallocNode(); - newNode->data = element; - newNode->prev = n; - newNode->next = 0; - n->next = newNode; + first->next = second; + second->prev = first; - return newNode; + return first; } -struct Node* append3(int element, struct Node *n) -/*@ requires !is_null(n); - take tailNode = Owned(n); - is_null(tailNode.next); - take l = LinkedListAux(n, tailNode); - ensures take oldTail = Owned(n); - take l_ = LinkedListAux(n, oldTail); - // take newTail = Owned(return); - // take l_ = OwnBackwards(tailNode.prev, n, tailNode); - // ptr_eq(oldTail.next, return); - // is_null(newTail.next); - // ptr_eq(newTail.prev, n); - // l_ == l; +struct Node *test(int element, struct Node *n) +/*@ requires take l = LinkedList(n); + ensures take l_ = LinkedList(n); @*/ { - /*@ split_case(ptr_eq((*n).next, n) && ptr_eq((*n).prev, n)); @*/ - /*@ assert(!(ptr_eq((*n).next, n) && ptr_eq((*n).prev, n))); @*/ struct Node *newNode = mallocNode(); - /*@ assert(!ptr_eq(n, newNode));@*/ - newNode->data = element; - newNode->prev = n; + newNode->prev = 0; newNode->next = 0; - n->next = newNode; - - // /*@ assert(ptr_eq((*n).next,newNode)); @*/ - /*@ split_case(is_null((*n).next)); @*/ - /*@ assert(!ptr_eq(n, newNode));@*/ - /*@ assert(!(ptr_eq((*n).next, n) && ptr_eq((*n).prev, n))); @*/ - - return newNode; + freeNode(newNode); + return n; } - - - -// void append2(int element, struct Node *n) -// /*@ requires !is_null(n); -// take tailNode = Owned(n); -// is_null(tailNode.next); -// take l = LinkedListAux(n, tailNode); -// ensures !is_null(n); -// take oldTail = Owned(n); -// take l_ = LinkedListAux(n, oldTail); -// // ptr_eq(oldTail.next, return); -// // is_null(newTail.next); -// // ptr_eq(newTail.prev, n); -// // l_ == l; -// @*/ -// { -// /*@ split_case(ptr_eq((*n).next, n) && ptr_eq((*n).prev, n)); @*/ -// struct Node *newNode = mallocNode(); -// newNode->data = element; -// newNode->prev = n; -// newNode->next = 0; -// n->next = newNode; - -// /*@ assert(ptr_eq((*n).next,newNode)); @*/ -// /*@ assert(ptr_eq((*newNode).prev,n)); @*/ -// return; -// } - -// void add_helper(int element, struct Node *n) -// /*@ requires !is_null(n); -// take node = Owned(n); -// take l = LinkedListAux(n, node); -// ensures !is_null(n); -// take node_ = Owned(n); -// take l_ = LinkedListAux(n, node_); -// @*/ -// { -// if (n->next == 0) { -// // append(element,n); -// } else { -// // struct Node *newNode = mallocNode(); -// // newNode->data = element; -// // newNode->prev = n; -// // newNode->next = n->next; -// // n->next->prev = newNode; -// // n->next = newNode; -// return; -// } -// } - -// void add_WIP(int element, struct Node *n) -// /*@ requires !is_null(n); -// take node = Owned(n); -// take l = LinkedListAux(n, node); -// ensures !is_null(n); -// take node_ = Owned(n); -// take l_ = LinkedListAux(n, node_); -// @*/ -// { -// /*@ split_case(ptr_eq((*n).next, n) && ptr_eq((*n).prev, n)); @*/ -// struct Node *next = n->next; -// if (next == n->prev && next == n) { // empty list case -// n->data = element; -// n->prev = 0; -// n->next = 0; -// return; -// } else { -// add_helper(element,n); -// return; -// } -// } - -/*@ lemma ownedStuff(pointer x, pointer y) -requires take x_ = Owned(x); - take y_ = Owned(y); -ensures !ptr_eq(x,y); - !is_null(x); - !is_null(y); -@*/ \ No newline at end of file From 92e0b14021b15dc8ce2b0bdc80ad3adac238c7be Mon Sep 17 00:00:00 2001 From: Liz Austell Date: Tue, 9 Jul 2024 17:51:20 -0400 Subject: [PATCH 070/152] make add work --- src/examples/Linked_List/WIPlinklist.c | 55 ++++++++++++++++++++------ 1 file changed, 43 insertions(+), 12 deletions(-) diff --git a/src/examples/Linked_List/WIPlinklist.c b/src/examples/Linked_List/WIPlinklist.c index 2b623dc8..914e948a 100644 --- a/src/examples/Linked_List/WIPlinklist.c +++ b/src/examples/Linked_List/WIPlinklist.c @@ -140,6 +140,49 @@ struct Node *singleton(int element) return n; } +// Adds after the given node +struct Node *add(int element, struct Node *n) +/*@ requires take l = LinkedList(n); + ensures take l_ = LinkedList(return); + // take ret = LinkedList(return); + + // ret == dll{first: nodeSeq_Nil{}, node: struct Node{data: element, prev: prev, next: prev.next}, rest: nodeSeq_Cons{node: struct Node{data: prev.data, prev: prev.prev, next: return}, tail: nodeSeq_Nil{}}}; +@*/ +{ + struct Node *newNode = mallocNode(); + newNode->data = element; + newNode->prev = 0; + newNode->next = 0; + + // /*@ apply assert_not_equal(newNode, n); @*/ + // /*@ assert (!ptr_eq(newNode, n)); @*/ + // /*@ assert (!is_null(newNode)); @*/ + + // /*@ split_case(is_null(n)); @*/ + if (n == 0) //empty list case + { + newNode->prev = 0; + newNode->next = 0; + return newNode; + } else { + /*@ split_case(is_null((*n).next)); @*/ + /*@ split_case(is_null((*n).prev)); @*/ + + + newNode->next = n->next; + newNode->prev = n; + + if (n->next !=0) { + // /*@ assert( !is_null((*n).next)); @*/ + /*@ split_case(is_null((*(*n).next).next)); @*/ + n->next->prev = newNode; + } + + n->next = newNode; + return newNode; + } +} + // Appends `second` to the end of `first`, where `first` is the tail of the first list and // `second` is the head of the second list. struct Node *append (struct Node *first, struct Node *second) @@ -163,15 +206,3 @@ struct Node *append (struct Node *first, struct Node *second) } -struct Node *test(int element, struct Node *n) -/*@ requires take l = LinkedList(n); - ensures take l_ = LinkedList(n); -@*/ -{ - struct Node *newNode = mallocNode(); - newNode->data = element; - newNode->prev = 0; - newNode->next = 0; - freeNode(newNode); - return n; -} From 66b0572b3d4ee447bf36b73978a0b4b1fe17ac5a Mon Sep 17 00:00:00 2001 From: Liz Austell Date: Wed, 10 Jul 2024 11:04:49 -0400 Subject: [PATCH 071/152] add remove function --- src/examples/Linked_List/WIPlinklist.c | 95 ++++++++++++++++++++++---- 1 file changed, 83 insertions(+), 12 deletions(-) diff --git a/src/examples/Linked_List/WIPlinklist.c b/src/examples/Linked_List/WIPlinklist.c index 914e948a..271ef05e 100644 --- a/src/examples/Linked_List/WIPlinklist.c +++ b/src/examples/Linked_List/WIPlinklist.c @@ -7,7 +7,7 @@ // #include "../list_snoc.h" #include "../list_append.h" #include "../list_rev.h" -#include "./pointereq.c" +// #include "./pointereq.c" struct Node { int data; @@ -15,6 +15,11 @@ struct Node { struct Node* next; }; +struct NodeandInt { + struct Node* node; + int data; +}; + /*@ datatype dll { empty_dll {}, @@ -26,13 +31,59 @@ datatype nodeSeq { nodeSeq_Cons {struct Node node, datatype seq tail} } -function (datatype nodeSeq) getRest(datatype dll l) { +function (datatype nodeSeq) dllGetRest(datatype dll l) { match l { empty_dll {} => { nodeSeq_Nil {} } dll {first: _, node: _, rest: r} => { r } } } +function (datatype nodeSeq) dllGetFirst(datatype dll l) { + match l { + empty_dll {} => { nodeSeq_Nil {} } + dll {first: f, node: _, rest: _} => { f } + } +} + +function (struct Node) dllGetNode(datatype dll l) { + match l { + empty_dll {} => { default } + dll {first: _, node: n, rest: _} => { n } + } +} + +function (struct Node) nodeSeqHead(datatype nodeSeq l) { + match l { + nodeSeq_Nil {} => { default } + nodeSeq_Cons {node: n, tail: _} => { n } + } +} + +function (datatype seq) nodeSeqTail (datatype nodeSeq l) { + match l { + nodeSeq_Nil {} => { Seq_Nil {} } + nodeSeq_Cons {node: _, tail: t} => { t } + } +} + +function (datatype seq) flatten(datatype dll l) { + match l { + empty_dll {} => { Seq_Nil {} } + dll {first: f, node: n, rest: r} => { + append (rev( Seq_Cons {head: nodeSeqHead(f).data, tail: nodeSeqTail(f)}), + Seq_Cons {head: n.data, tail: Seq_Cons { head: nodeSeqHead(r).data, tail: nodeSeqTail(r)}}) + } + } +} + +function (datatype seq) nodeSeqtoSeq(datatype nodeSeq l) { + match l { + nodeSeq_Nil {} => { Seq_Nil {} } + nodeSeq_Cons {node: n, tail: t} => { Seq_Cons {head: n.data, tail: t } } + } +} + + predicate (datatype dll) LinkedList (pointer p) { if (is_null(p)) { return empty_dll{}; @@ -143,22 +194,14 @@ struct Node *singleton(int element) // Adds after the given node struct Node *add(int element, struct Node *n) /*@ requires take l = LinkedList(n); - ensures take l_ = LinkedList(return); - // take ret = LinkedList(return); - - // ret == dll{first: nodeSeq_Nil{}, node: struct Node{data: element, prev: prev, next: prev.next}, rest: nodeSeq_Cons{node: struct Node{data: prev.data, prev: prev.prev, next: return}, tail: nodeSeq_Nil{}}}; + ensures take l_ = LinkedList(return); @*/ { struct Node *newNode = mallocNode(); newNode->data = element; newNode->prev = 0; newNode->next = 0; - - // /*@ apply assert_not_equal(newNode, n); @*/ - // /*@ assert (!ptr_eq(newNode, n)); @*/ - // /*@ assert (!is_null(newNode)); @*/ - // /*@ split_case(is_null(n)); @*/ if (n == 0) //empty list case { newNode->prev = 0; @@ -173,7 +216,6 @@ struct Node *add(int element, struct Node *n) newNode->prev = n; if (n->next !=0) { - // /*@ assert( !is_null((*n).next)); @*/ /*@ split_case(is_null((*(*n).next).next)); @*/ n->next->prev = newNode; } @@ -183,6 +225,35 @@ struct Node *add(int element, struct Node *n) } } +// removes the given node from the list and returns another pointer +// to somewhere in the list, or a null pointer if the list is empty. +// TODO: should also return an int from the deleted node. +struct Node *remove(struct Node *n) +/*@ requires take l = LinkedList(n); + ensures take l_ = LinkedList(return); +@*/ +{ + if (n == 0) { //empty list case + return n; //null pointer + } else { + struct Node *temp = 0; + if (n->prev != 0) { + /*@ split_case(is_null((*(*n).prev).prev)); @*/ + + n->prev->next = n->next; + temp = n->prev; + } + if (n->next != 0) { + /*@ split_case(is_null((*(*n).next).next)); @*/ + n->next->prev = n->prev; + temp = n->next; + } + + freeNode(n); + return temp; + } +} + // Appends `second` to the end of `first`, where `first` is the tail of the first list and // `second` is the head of the second list. struct Node *append (struct Node *first, struct Node *second) From 7407333eb058dcc108262d62506ac3452b378b1b Mon Sep 17 00:00:00 2001 From: Liz Austell Date: Thu, 11 Jul 2024 11:31:41 -0400 Subject: [PATCH 072/152] Add correctness check for remove --- src/examples/Linked_List/WIPlinklist.c | 115 ++++++++++++++++++------- 1 file changed, 85 insertions(+), 30 deletions(-) diff --git a/src/examples/Linked_List/WIPlinklist.c b/src/examples/Linked_List/WIPlinklist.c index 271ef05e..f13de74e 100644 --- a/src/examples/Linked_List/WIPlinklist.c +++ b/src/examples/Linked_List/WIPlinklist.c @@ -70,8 +70,28 @@ function (datatype seq) flatten(datatype dll l) { match l { empty_dll {} => { Seq_Nil {} } dll {first: f, node: n, rest: r} => { - append (rev( Seq_Cons {head: nodeSeqHead(f).data, tail: nodeSeqTail(f)}), - Seq_Cons {head: n.data, tail: Seq_Cons { head: nodeSeqHead(r).data, tail: nodeSeqTail(r)}}) + match f { + nodeSeq_Nil {} => { + match r { + nodeSeq_Nil {} => { + Seq_Cons {head: n.data, tail: Seq_Nil {}} + } + nodeSeq_Cons {node: nextNode, tail: t} => { + Seq_Cons {head: n.data, tail: Seq_Cons{ head: nextNode.data, tail: t}} + } + } + } + nodeSeq_Cons {node: prevNode, tail: t} => { + match r { + nodeSeq_Nil {} => { + rev(Seq_Cons {head: n.data, tail: Seq_Cons {head: prevNode.data, tail: t}}) + } + nodeSeq_Cons {node: nextNode, tail: t2} => { + append(rev(Seq_Cons {head: prevNode.data, tail: t2}), Seq_Cons {head: n.data, tail: Seq_Cons{ head: nextNode.data, tail: t2}}) + } + } + } + } } } } @@ -179,6 +199,19 @@ extern void freeNode (struct Node *p); ensures true; @*/ +extern struct NodeandInt *mallocNodeandInt(); +/*@ spec mallocNodeandInt(); + requires true; + ensures take u = Block(return); + !ptr_eq(return,NULL); +@*/ + +extern void freeNodeandInt (struct NodeandInt *p); +/*@ spec freeNodeandInt(pointer p); + requires take u = Block(p); + ensures true; +@*/ + struct Node *singleton(int element) /*@ ensures take ret = LinkedList(return); ret == dll{first: nodeSeq_Nil{}, node: struct Node{data: element, prev: NULL, next: NULL}, rest: nodeSeq_Nil{}}; @@ -225,34 +258,6 @@ struct Node *add(int element, struct Node *n) } } -// removes the given node from the list and returns another pointer -// to somewhere in the list, or a null pointer if the list is empty. -// TODO: should also return an int from the deleted node. -struct Node *remove(struct Node *n) -/*@ requires take l = LinkedList(n); - ensures take l_ = LinkedList(return); -@*/ -{ - if (n == 0) { //empty list case - return n; //null pointer - } else { - struct Node *temp = 0; - if (n->prev != 0) { - /*@ split_case(is_null((*(*n).prev).prev)); @*/ - - n->prev->next = n->next; - temp = n->prev; - } - if (n->next != 0) { - /*@ split_case(is_null((*(*n).next).next)); @*/ - n->next->prev = n->prev; - temp = n->next; - } - - freeNode(n); - return temp; - } -} // Appends `second` to the end of `first`, where `first` is the tail of the first list and // `second` is the head of the second list. @@ -276,4 +281,54 @@ struct Node *append (struct Node *first, struct Node *second) return first; } +// removes the given node from the list and returns another pointer +// to somewhere in the list, or a null pointer if the list is empty. +// TODO: should also return an int from the deleted node. +struct NodeandInt *remove(struct Node *n) +/*@ requires !is_null(n); + take del = Owned(n); + take first = OwnBackwards(del.prev, n, del); + take rest = OwnForwards(del.next, n, del); + ensures take ret = Owned(return); + take l = LinkedList(ret.node); + let first_ = dllGetFirst(l); + let rest_ = dllGetRest(l); + let node = dllGetNode(l); + nodeSeqtoSeq(first_ )== nodeSeqtoSeq(first) || nodeSeqtoSeq(rest_) == nodeSeqtoSeq(rest); + !is_null(ret.node) implies (nodeSeqtoSeq(first_ ) == nodeSeqtoSeq(first) implies nodeSeqtoSeq(rest) == Seq_Cons{head: node.data, tail: nodeSeqtoSeq(rest_)}); + + !is_null(ret.node) implies (nodeSeqtoSeq(rest_ ) == nodeSeqtoSeq(rest) implies nodeSeqtoSeq(first) == Seq_Cons{head: node.data, tail: nodeSeqtoSeq(first_)}); + + nodeSeqtoSeq(first) == Seq_Cons{head: node.data, tail: nodeSeqtoSeq(first_)} || nodeSeqtoSeq(rest) == Seq_Cons{head: node.data, tail: nodeSeqtoSeq(rest_)} || (nodeSeqtoSeq(first) == Seq_Nil{} && nodeSeqtoSeq(rest) == Seq_Nil{}); + // flatten(l) == append(rev(nodeSeqtoSeq(first)), nodeSeqtoSeq(rest)); + +@*/ +{ + if (n == 0) { //empty list case + struct NodeandInt *pair = mallocNodeandInt(); + pair->node = 0; //null pointer + pair->data = 0; + return pair; + } else { + struct Node *temp = 0; + if (n->prev != 0) { + /*@ split_case(is_null((*(*n).prev).prev)); @*/ + + n->prev->next = n->next; + temp = n->prev; + } + if (n->next != 0) { + /*@ split_case(is_null((*(*n).next).next)); @*/ + n->next->prev = n->prev; + temp = n->next; + } + + struct NodeandInt *pair = mallocNodeandInt(); + pair->node = temp; + pair->data = n->data; + + freeNode(n); + return pair; + } +} \ No newline at end of file From afdf2e5617ae2ad8851d13c973d3d6b2b8b96f20 Mon Sep 17 00:00:00 2001 From: Liz Austell Date: Thu, 11 Jul 2024 13:33:04 -0400 Subject: [PATCH 073/152] add correctness checks to add function --- src/examples/Linked_List/WIPlinklist.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/examples/Linked_List/WIPlinklist.c b/src/examples/Linked_List/WIPlinklist.c index f13de74e..f1da17b3 100644 --- a/src/examples/Linked_List/WIPlinklist.c +++ b/src/examples/Linked_List/WIPlinklist.c @@ -225,9 +225,26 @@ struct Node *singleton(int element) } // Adds after the given node +// TODO: fix correctness checks struct Node *add(int element, struct Node *n) /*@ requires take l = LinkedList(n); + let node = dllGetNode(l); + let first = dllGetFirst(l); + let rest = dllGetRest(l); ensures take l_ = LinkedList(return); + let first_ = dllGetFirst(l_); + let rest_ = dllGetRest(l_); + let newNode = dllGetNode(l_); + + ptr_eq(newNode.prev,n); + let node_ = nodeSeqHead(first_); + !is_null(n) implies ptr_eq(node_.next, return); + !is_null(n) implies ptr_eq(newNode.next, node.next); + !is_null(return); + + + !is_null(n) implies nodeSeqtoSeq(first_) == Seq_Cons { head: node.data, tail: nodeSeqtoSeq(first)}; + nodeSeqtoSeq(rest) == nodeSeqtoSeq(rest_); @*/ { struct Node *newNode = mallocNode(); @@ -283,7 +300,6 @@ struct Node *append (struct Node *first, struct Node *second) // removes the given node from the list and returns another pointer // to somewhere in the list, or a null pointer if the list is empty. -// TODO: should also return an int from the deleted node. struct NodeandInt *remove(struct Node *n) /*@ requires !is_null(n); take del = Owned(n); From 1b0971ebccf7542f60c1b3d83b48bd3bebf6caa3 Mon Sep 17 00:00:00 2001 From: Liz Austell Date: Thu, 11 Jul 2024 15:40:01 -0400 Subject: [PATCH 074/152] add find head and tail functions --- src/examples/Linked_List/WIPlinklist.c | 82 +++++++++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/src/examples/Linked_List/WIPlinklist.c b/src/examples/Linked_List/WIPlinklist.c index f1da17b3..c0ef7575 100644 --- a/src/examples/Linked_List/WIPlinklist.c +++ b/src/examples/Linked_List/WIPlinklist.c @@ -278,6 +278,7 @@ struct Node *add(int element, struct Node *n) // Appends `second` to the end of `first`, where `first` is the tail of the first list and // `second` is the head of the second list. +// TODO: fix so that any nodes can be passed in, not just head and tail struct Node *append (struct Node *first, struct Node *second) /*@ requires take n1 = Owned(first); take n2 = Owned(second); @@ -347,4 +348,83 @@ struct NodeandInt *remove(struct Node *n) freeNode(n); return pair; } -} \ No newline at end of file +} + +struct Node *findHeadAux(struct Node *n) +/*@ requires !is_null(n); + take node = Owned(n); + take l = OwnBackwards(node.prev, n, node); + ensures take node_ = Owned(n); + take l_ = OwnBackwards(node_.prev, n, node_); + node == node_; + l == l_; +@*/ +{ + /*@ split_case(is_null(n)); @*/ + /*@ split_case(is_null((*n).prev)); @*/ + if (n->prev == 0) + { + return n; + } else { + /*@ split_case(is_null((*(*n).prev).prev)); @*/ + return findHeadAux(n->prev); + } +} + +// Takes any node in the list and returns the head of the list +// TODO: correctness check +struct Node *findHead(struct Node *n) +/*@ requires take l = LinkedList(n); + ensures take l_ = LinkedList(n); + l == l_; +@*/ +{ + /*@ split_case(is_null(n)); @*/ + if (n == 0) + { + return 0; + } else { + /*@ split_case(is_null((*n).prev)); @*/ + return findHeadAux(n); + } +} + + +struct Node *findTailAux(struct Node *n) +/*@ requires !is_null(n); + take node = Owned(n); + take l = OwnForwards(node.next, n, node); + ensures take node_ = Owned(n); + take l_ = OwnForwards(node_.next, n, node_); + node == node_; + l == l_; +@*/ +{ + /*@ split_case(is_null(n)); @*/ + /*@ split_case(is_null((*n).next)); @*/ + if (n->next == 0) + { + return n; + } else { + /*@ split_case(is_null((*(*n).next).next)); @*/ + return findTailAux(n->next); + } +} + +// Takes any node in the list and returns the tail of the list +// TODO: correctness check +struct Node *findTail(struct Node *n) +/*@ requires take l = LinkedList(n); + ensures take l_ = LinkedList(n); + l == l_; +@*/ +{ + /*@ split_case(is_null(n)); @*/ + if (n == 0) + { + return 0; + } else { + /*@ split_case(is_null((*n).next)); @*/ + return findTailAux(n); + } +} From 17a8cd1efa73b81c0b66c9ac76600a379c581b79 Mon Sep 17 00:00:00 2001 From: Liz Austell Date: Mon, 15 Jul 2024 10:20:10 -0400 Subject: [PATCH 075/152] convert to more systematic naming conventions --- src/examples/Linked_List/WIPlinklist.c | 391 ++++++++++++------------- 1 file changed, 192 insertions(+), 199 deletions(-) diff --git a/src/examples/Linked_List/WIPlinklist.c b/src/examples/Linked_List/WIPlinklist.c index c0ef7575..85fb7114 100644 --- a/src/examples/Linked_List/WIPlinklist.c +++ b/src/examples/Linked_List/WIPlinklist.c @@ -1,92 +1,85 @@ -// Consider an empty list being a null pointer, and have every function return a pointer -// to some part of the list (null pointer if empty list). - - #include "../list.h" -// #include "../list_length.c" -// #include "../list_snoc.h" #include "../list_append.h" #include "../list_rev.h" -// #include "./pointereq.c" -struct Node { +struct node { int data; - struct Node* prev; - struct Node* next; + struct node* prev; + struct node* next; }; -struct NodeandInt { - struct Node* node; +struct node_and_int { + struct node* node; int data; }; /*@ -datatype dll { - empty_dll {}, - dll {datatype nodeSeq first, struct Node node, datatype nodeSeq rest} +datatype Dll { + Empty_Dll {}, + Dll {datatype Seq_Node first, struct node n, datatype Seq_Node rest} } -datatype nodeSeq { - nodeSeq_Nil {}, - nodeSeq_Cons {struct Node node, datatype seq tail} +datatype Seq_Node { + Seq_Node_Nil {}, + Seq_Node_Cons {struct node n, datatype seq tail} } -function (datatype nodeSeq) dllGetRest(datatype dll l) { - match l { - empty_dll {} => { nodeSeq_Nil {} } - dll {first: _, node: _, rest: r} => { r } +function (datatype Seq_Node) Dll_Rest (datatype Dll L) { + match L { + Empty_Dll {} => { Seq_Node_Nil {} } + Dll {first: _, n: _, rest: r} => { r } } } -function (datatype nodeSeq) dllGetFirst(datatype dll l) { - match l { - empty_dll {} => { nodeSeq_Nil {} } - dll {first: f, node: _, rest: _} => { f } +function (datatype Seq_Node) Dll_First(datatype Dll L) { + match L { + Empty_Dll {} => { Seq_Node_Nil {} } + Dll {first: f, n: _, rest: _} => { f } } } -function (struct Node) dllGetNode(datatype dll l) { - match l { - empty_dll {} => { default } - dll {first: _, node: n, rest: _} => { n } +function (struct node) Dll_Node (datatype Dll L) { + match L { + Empty_Dll {} => { default } + Dll {first: _, n: n, rest: _} => { n } } } -function (struct Node) nodeSeqHead(datatype nodeSeq l) { - match l { - nodeSeq_Nil {} => { default } - nodeSeq_Cons {node: n, tail: _} => { n } +function (struct node) Seq_Node_Head(datatype Seq_Node S) { + match S { + Seq_Node_Nil {} => { default } + Seq_Node_Cons {n: n, tail: _} => { n } } } -function (datatype seq) nodeSeqTail (datatype nodeSeq l) { - match l { - nodeSeq_Nil {} => { Seq_Nil {} } - nodeSeq_Cons {node: _, tail: t} => { t } +function (datatype seq) Seq_Node_Tail (datatype Seq_Node S) { + match S { + Seq_Node_Nil {} => { Seq_Nil {} } + Seq_Node_Cons {n: _, tail: t} => { t } } } -function (datatype seq) flatten(datatype dll l) { - match l { - empty_dll {} => { Seq_Nil {} } - dll {first: f, node: n, rest: r} => { +function (datatype seq) flatten (datatype Dll L) { + match L { + Empty_Dll {} => { Seq_Nil {} } + Dll {first: f, n: n, rest: r} => { match f { - nodeSeq_Nil {} => { + Seq_Node_Nil {} => { match r { - nodeSeq_Nil {} => { + Seq_Node_Nil {} => { Seq_Cons {head: n.data, tail: Seq_Nil {}} } - nodeSeq_Cons {node: nextNode, tail: t} => { + Seq_Node_Cons {n: nextNode, tail: t} => { Seq_Cons {head: n.data, tail: Seq_Cons{ head: nextNode.data, tail: t}} } } } - nodeSeq_Cons {node: prevNode, tail: t} => { + Seq_Node_Cons {n: prevNode, tail: t} => { match r { - nodeSeq_Nil {} => { + Seq_Node_Nil {} => { rev(Seq_Cons {head: n.data, tail: Seq_Cons {head: prevNode.data, tail: t}}) } - nodeSeq_Cons {node: nextNode, tail: t2} => { + Seq_Node_Cons {n: nextNode, tail: t2} => { append(rev(Seq_Cons {head: prevNode.data, tail: t2}), Seq_Cons {head: n.data, tail: Seq_Cons{ head: nextNode.data, tail: t2}}) } } @@ -96,128 +89,128 @@ function (datatype seq) flatten(datatype dll l) { } } -function (datatype seq) nodeSeqtoSeq(datatype nodeSeq l) { - match l { - nodeSeq_Nil {} => { Seq_Nil {} } - nodeSeq_Cons {node: n, tail: t} => { Seq_Cons {head: n.data, tail: t } } +function (datatype seq) Seq_Node_to_Seq(datatype Seq_Node L) { + match L { + Seq_Node_Nil {} => { Seq_Nil {} } + Seq_Node_Cons {n: n, tail: t} => { Seq_Cons {head: n.data, tail: t } } } } -predicate (datatype dll) LinkedList (pointer p) { +predicate (datatype Dll) LinkedList (pointer p) { if (is_null(p)) { - return empty_dll{}; + return Empty_Dll{}; } else { - take N = Owned(p); - take first = OwnBackwards(N.prev, p, N); - take rest = OwnForwards(N.next, p, N); - return dll{first: first, node: N, rest: rest}; + take n = Owned(p); + take First = Own_Backwards(n.prev, p, n); + take Rest = Own_Forwards(n.next, p, n); + return Dll{first: First, n: n, rest: Rest}; } } -predicate (datatype nodeSeq) OwnForwards(pointer p, pointer PrevPointer, struct Node PrevNode) { +predicate (datatype Seq_Node) Own_Forwards(pointer p, pointer prev_pointer, struct node prev_node) { if (is_null(p)) { - return nodeSeq_Nil{}; + return Seq_Node_Nil{}; } else { - take N = Owned(p); - assert (ptr_eq(N.prev, PrevPointer)); - assert(ptr_eq(PrevNode.next,p)); - take rest = OwnForwardsAux(N.next, p, N); - return nodeSeq_Cons{node: N, tail: rest}; + take n = Owned(p); + assert (ptr_eq(n.prev, prev_pointer)); + assert(ptr_eq(prev_node.next,p)); + take Rest = Own_Forwards_Aux(n.next, p, n); + return Seq_Node_Cons{n: n, tail: Rest}; } } -predicate (datatype seq) OwnForwardsAux(pointer p, pointer PrevPointer, struct Node PrevNode) { +predicate (datatype seq) Own_Forwards_Aux(pointer p, pointer prev_pointer, struct node prev_node) { if (is_null(p)) { return Seq_Nil{}; } else { - take N = Owned(p); - assert (ptr_eq(N.prev, PrevPointer)); - assert(ptr_eq(PrevNode.next,p)); - take rest = OwnForwardsAux(N.next, p, N); - return Seq_Cons{head: N.data, tail: rest}; + take n = Owned(p); + assert (ptr_eq(n.prev, prev_pointer)); + assert(ptr_eq(prev_node.next, p)); + take Rest = Own_Forwards_Aux(n.next, p, n); + return Seq_Cons{head: n.data, tail: Rest}; } } -predicate (datatype nodeSeq) OwnBackwards(pointer p, pointer NextPointer, struct Node NextNode) { +predicate (datatype Seq_Node) Own_Backwards(pointer p, pointer next_pointer, struct node next_node) { if (is_null(p)) { - return nodeSeq_Nil{}; + return Seq_Node_Nil{}; } else { - take N = Owned(p); - assert (ptr_eq(N.next,NextPointer)); - assert(ptr_eq(NextNode.prev,p)); - take first = OwnBackwardsAux(N.prev, p, N); - return nodeSeq_Cons{node: N, tail: first}; + take n = Owned(p); + assert (ptr_eq(n.next, next_pointer)); + assert(ptr_eq(next_node.prev, p)); + take First = Own_Backwards_Aux(n.prev, p, n); + return Seq_Node_Cons{n: n, tail: First}; } } -predicate (datatype seq) OwnBackwardsAux(pointer p, pointer NextPointer, struct Node NextNode) { +predicate (datatype seq) Own_Backwards_Aux(pointer p, pointer next_pointer, struct node next_node) { if (is_null(p)) { return Seq_Nil{}; } else { - take N = Owned(p); - assert (ptr_eq(N.next,NextPointer)); - assert(ptr_eq(NextNode.prev,p)); - take first = OwnBackwardsAux(N.prev, p, N); - return Seq_Cons{head: N.data, tail: first}; - } -} - -predicate (datatype nodeSeq) OwnForwardsAlternate(pointer p) { - if (is_null(p)) { - return nodeSeq_Nil{}; - } else { - take N = Owned(p); - take rest = OwnForwardsAux(N.next, p, N); - return nodeSeq_Cons{node: N, tail: rest}; + take n = Owned(p); + assert (ptr_eq(n.next, next_pointer)); + assert(ptr_eq(next_node.prev, p)); + take First = Own_Backwards_Aux(n.prev, p, n); + return Seq_Cons{head: n.data, tail: First}; } } -predicate (datatype nodeSeq) OwnBackwardsAlternate(pointer p) { - if (is_null(p)) { - return nodeSeq_Nil{}; - } else { - take N = Owned(p); - take first = OwnBackwardsAux(N.prev, p, N); - return nodeSeq_Cons{node: N, tail: first}; - } -} -@*/ - -extern struct Node *mallocNode(); -/*@ spec mallocNode(); +// predicate (datatype nodeSeq) OwnForwardsAlternate(pointer p) { +// if (is_null(p)) { +// return nodeSeq_Nil{}; +// } else { +// take N = Owned(p); +// take rest = OwnForwardsAux(N.next, p, N); +// return nodeSeq_Cons{node: N, tail: rest}; +// } +// } + +// predicate (datatype nodeSeq) OwnBackwardsAlternate(pointer p) { +// if (is_null(p)) { +// return nodeSeq_Nil{}; +// } else { +// take N = Owned(p); +// take first = OwnBackwardsAux(N.prev, p, N); +// return nodeSeq_Cons{node: N, tail: first}; +// } +// } +// @*/ + +extern struct node *malloc_node(); +/*@ spec malloc_node(); requires true; - ensures take u = Block(return); + ensures take u = Block(return); !ptr_eq(return,NULL); @*/ -extern void freeNode (struct Node *p); -/*@ spec freeNode(pointer p); - requires take u = Block(p); +extern void free_node (struct node *p); +/*@ spec free_node(pointer p); + requires take u = Block(p); ensures true; @*/ -extern struct NodeandInt *mallocNodeandInt(); -/*@ spec mallocNodeandInt(); +extern struct node_and_int *malloc_node_and_int(); +/*@ spec malloc_node_and_int(); requires true; - ensures take u = Block(return); + ensures take u = Block(return); !ptr_eq(return,NULL); @*/ -extern void freeNodeandInt (struct NodeandInt *p); -/*@ spec freeNodeandInt(pointer p); - requires take u = Block(p); +extern void free_node_and_int (struct node_and_int *p); +/*@ spec free_node_and_int(pointer p); + requires take u = Block(p); ensures true; @*/ -struct Node *singleton(int element) -/*@ ensures take ret = LinkedList(return); - ret == dll{first: nodeSeq_Nil{}, node: struct Node{data: element, prev: NULL, next: NULL}, rest: nodeSeq_Nil{}}; +struct node *singleton(int element) +/*@ ensures take Ret = LinkedList(return); + Ret == Dll{first: Seq_Node_Nil{}, n: struct node{data: element, prev: NULL, next: NULL}, rest: Seq_Node_Nil{}}; @*/ { - struct Node *n = mallocNode(); + struct node *n = malloc_node(); n->data = element; n->prev = 0; n->next = 0; @@ -226,52 +219,52 @@ struct Node *singleton(int element) // Adds after the given node // TODO: fix correctness checks -struct Node *add(int element, struct Node *n) -/*@ requires take l = LinkedList(n); - let node = dllGetNode(l); - let first = dllGetFirst(l); - let rest = dllGetRest(l); - ensures take l_ = LinkedList(return); - let first_ = dllGetFirst(l_); - let rest_ = dllGetRest(l_); - let newNode = dllGetNode(l_); - - ptr_eq(newNode.prev,n); - let node_ = nodeSeqHead(first_); +struct node *add(int element, struct node *n) +/*@ requires take L = LinkedList(n); + let node = Dll_Node(L); + let First = Dll_First(L); + let Rest = Dll_Rest(L); + ensures take L_ = LinkedList(return); + let First_ = Dll_First(L_); + let Rest_ = Dll_Rest(L_); + let new_node = Dll_Node(L_); + + ptr_eq(new_node.prev, n); + let node_ = Seq_Node_Head(First_); !is_null(n) implies ptr_eq(node_.next, return); - !is_null(n) implies ptr_eq(newNode.next, node.next); + !is_null(n) implies ptr_eq(new_node.next, node.next); !is_null(return); - !is_null(n) implies nodeSeqtoSeq(first_) == Seq_Cons { head: node.data, tail: nodeSeqtoSeq(first)}; - nodeSeqtoSeq(rest) == nodeSeqtoSeq(rest_); + !is_null(n) implies Seq_Node_to_Seq(First_) == Seq_Cons { head: node.data, tail: Seq_Node_to_Seq(First)}; + Seq_Node_to_Seq(Rest) == Seq_Node_to_Seq(Rest_); @*/ { - struct Node *newNode = mallocNode(); - newNode->data = element; - newNode->prev = 0; - newNode->next = 0; + struct node *new_node = malloc_node(); + new_node->data = element; + new_node->prev = 0; + new_node->next = 0; if (n == 0) //empty list case { - newNode->prev = 0; - newNode->next = 0; - return newNode; + new_node->prev = 0; + new_node->next = 0; + return new_node; } else { /*@ split_case(is_null((*n).next)); @*/ /*@ split_case(is_null((*n).prev)); @*/ - newNode->next = n->next; - newNode->prev = n; + new_node->next = n->next; + new_node->prev = n; if (n->next !=0) { /*@ split_case(is_null((*(*n).next).next)); @*/ - n->next->prev = newNode; + n->next->prev = new_node; } - n->next = newNode; - return newNode; + n->next = new_node; + return new_node; } } @@ -279,18 +272,18 @@ struct Node *add(int element, struct Node *n) // Appends `second` to the end of `first`, where `first` is the tail of the first list and // `second` is the head of the second list. // TODO: fix so that any nodes can be passed in, not just head and tail -struct Node *append (struct Node *first, struct Node *second) -/*@ requires take n1 = Owned(first); - take n2 = Owned(second); - take l = OwnBackwards(n1.prev, first, n1); - take r = OwnForwards(n2.next, second, n2); +struct node *append (struct node *first, struct node *second) +/*@ requires take n1 = Owned(first); + take n2 = Owned(second); + take L = Own_Backwards(n1.prev, first, n1); + take R = Own_Forwards(n2.next, second, n2); is_null(n1.next) && is_null(n2.prev); - ensures take n1_ = Owned(first); - take n2_ = Owned(second); - take l_ = OwnBackwards(n1.prev, first, n1); - take r_ = OwnForwards(n2.next, second, n2); + ensures take n1_ = Owned(first); + take n2_ = Owned(second); + take L_ = Own_Backwards(n1.prev, first, n1); + take R_ = Own_Forwards(n2.next, second, n2); ptr_eq(n1_.next,second) && ptr_eq(n2_.prev, first); - l == l_ && r == r_; + L == L_ && R == R_; @*/ { first->next = second; @@ -301,34 +294,34 @@ struct Node *append (struct Node *first, struct Node *second) // removes the given node from the list and returns another pointer // to somewhere in the list, or a null pointer if the list is empty. -struct NodeandInt *remove(struct Node *n) +struct node_and_int *remove(struct node *n) /*@ requires !is_null(n); - take del = Owned(n); - take first = OwnBackwards(del.prev, n, del); - take rest = OwnForwards(del.next, n, del); - ensures take ret = Owned(return); - take l = LinkedList(ret.node); - let first_ = dllGetFirst(l); - let rest_ = dllGetRest(l); - let node = dllGetNode(l); - nodeSeqtoSeq(first_ )== nodeSeqtoSeq(first) || nodeSeqtoSeq(rest_) == nodeSeqtoSeq(rest); - !is_null(ret.node) implies (nodeSeqtoSeq(first_ ) == nodeSeqtoSeq(first) implies nodeSeqtoSeq(rest) == Seq_Cons{head: node.data, tail: nodeSeqtoSeq(rest_)}); + take del = Owned(n); + take First = Own_Backwards(del.prev, n, del); + take Rest = Own_Forwards(del.next, n, del); + ensures take ret = Owned(return); + take L = LinkedList(ret.node); + let First_ = Dll_First(L); + let Rest_ = Dll_Rest(L); + let node = Dll_Node(L); + Seq_Node_to_Seq(First_ )== Seq_Node_to_Seq(First) || Seq_Node_to_Seq(Rest_) == Seq_Node_to_Seq(Rest); + !is_null(ret.node) implies (Seq_Node_to_Seq(First_ ) == Seq_Node_to_Seq(First) implies Seq_Node_to_Seq(Rest) == Seq_Cons{head: node.data, tail: Seq_Node_to_Seq(Rest_)}); - !is_null(ret.node) implies (nodeSeqtoSeq(rest_ ) == nodeSeqtoSeq(rest) implies nodeSeqtoSeq(first) == Seq_Cons{head: node.data, tail: nodeSeqtoSeq(first_)}); + !is_null(ret.node) implies (Seq_Node_to_Seq(Rest_ ) == Seq_Node_to_Seq(Rest) implies Seq_Node_to_Seq(First) == Seq_Cons{head: node.data, tail: Seq_Node_to_Seq(First_)}); - nodeSeqtoSeq(first) == Seq_Cons{head: node.data, tail: nodeSeqtoSeq(first_)} || nodeSeqtoSeq(rest) == Seq_Cons{head: node.data, tail: nodeSeqtoSeq(rest_)} || (nodeSeqtoSeq(first) == Seq_Nil{} && nodeSeqtoSeq(rest) == Seq_Nil{}); + Seq_Node_to_Seq(First) == Seq_Cons{head: node.data, tail: Seq_Node_to_Seq(First_)} || Seq_Node_to_Seq(Rest) == Seq_Cons{head: node.data, tail: Seq_Node_to_Seq(Rest_)} || (Seq_Node_to_Seq(First) == Seq_Nil{} && Seq_Node_to_Seq(Rest) == Seq_Nil{}); // flatten(l) == append(rev(nodeSeqtoSeq(first)), nodeSeqtoSeq(rest)); @*/ { if (n == 0) { //empty list case - struct NodeandInt *pair = mallocNodeandInt(); + struct node_and_int *pair = malloc_node_and_int(); pair->node = 0; //null pointer pair->data = 0; return pair; } else { - struct Node *temp = 0; + struct node *temp = 0; if (n->prev != 0) { /*@ split_case(is_null((*(*n).prev).prev)); @*/ @@ -341,23 +334,23 @@ struct NodeandInt *remove(struct Node *n) temp = n->next; } - struct NodeandInt *pair = mallocNodeandInt(); + struct node_and_int *pair = malloc_node_and_int(); pair->node = temp; pair->data = n->data; - freeNode(n); + free_node(n); return pair; } } -struct Node *findHeadAux(struct Node *n) +struct node *find_head_aux(struct node *n) /*@ requires !is_null(n); - take node = Owned(n); - take l = OwnBackwards(node.prev, n, node); - ensures take node_ = Owned(n); - take l_ = OwnBackwards(node_.prev, n, node_); + take node = Owned(n); + take L = Own_Backwards(node.prev, n, node); + ensures take node_ = Owned(n); + take L_ = Own_Backwards(node_.prev, n, node_); node == node_; - l == l_; + L == L_; @*/ { /*@ split_case(is_null(n)); @*/ @@ -367,16 +360,16 @@ struct Node *findHeadAux(struct Node *n) return n; } else { /*@ split_case(is_null((*(*n).prev).prev)); @*/ - return findHeadAux(n->prev); + return find_head_aux(n->prev); } } // Takes any node in the list and returns the head of the list // TODO: correctness check -struct Node *findHead(struct Node *n) -/*@ requires take l = LinkedList(n); - ensures take l_ = LinkedList(n); - l == l_; +struct node *find_head(struct node *n) +/*@ requires take L = LinkedList(n); + ensures take L_ = LinkedList(n); + L == L_; @*/ { /*@ split_case(is_null(n)); @*/ @@ -385,19 +378,19 @@ struct Node *findHead(struct Node *n) return 0; } else { /*@ split_case(is_null((*n).prev)); @*/ - return findHeadAux(n); + return find_head_aux(n); } } -struct Node *findTailAux(struct Node *n) +struct node *find_tail_aux(struct node *n) /*@ requires !is_null(n); - take node = Owned(n); - take l = OwnForwards(node.next, n, node); - ensures take node_ = Owned(n); - take l_ = OwnForwards(node_.next, n, node_); + take node = Owned(n); + take L = Own_Forwards(node.next, n, node); + ensures take node_ = Owned(n); + take L_ = Own_Forwards(node_.next, n, node_); node == node_; - l == l_; + L == L_; @*/ { /*@ split_case(is_null(n)); @*/ @@ -407,16 +400,16 @@ struct Node *findTailAux(struct Node *n) return n; } else { /*@ split_case(is_null((*(*n).next).next)); @*/ - return findTailAux(n->next); + return find_tail_aux(n->next); } } // Takes any node in the list and returns the tail of the list // TODO: correctness check -struct Node *findTail(struct Node *n) -/*@ requires take l = LinkedList(n); - ensures take l_ = LinkedList(n); - l == l_; +struct node *findTail(struct node *n) +/*@ requires take L = LinkedList(n); + ensures take L_ = LinkedList(n); + L == L_; @*/ { /*@ split_case(is_null(n)); @*/ @@ -425,6 +418,6 @@ struct Node *findTail(struct Node *n) return 0; } else { /*@ split_case(is_null((*n).next)); @*/ - return findTailAux(n); + return find_tail_aux(n); } } From fddfcfc99b39be1dc1ca8add63f4e2a27df5ab1c Mon Sep 17 00:00:00 2001 From: Liz Austell Date: Mon, 15 Jul 2024 10:49:01 -0400 Subject: [PATCH 076/152] organize into folders and add explanations of specs --- src/examples/Linked_List/TODO.md | 43 ------------- src/examples/Linked_List/WIPlinklist.c | 22 +------ src/examples/Linked_List/add.c | 72 ++++++++++++++++++++++ src/examples/Linked_List/append.c | 24 ++++++++ src/examples/Linked_List/c_types.h | 10 +++ src/examples/Linked_List/cn_types.h | 11 ++++ src/examples/Linked_List/converters.h | 38 ++++++++++++ src/examples/Linked_List/getters.h | 36 +++++++++++ src/examples/Linked_List/head_tail.c | 84 ++++++++++++++++++++++++++ src/examples/Linked_List/headers.h | 10 +++ src/examples/Linked_List/malloc_free.h | 25 ++++++++ src/examples/Linked_List/predicates.h | 62 +++++++++++++++++++ src/examples/Linked_List/remove.c | 69 +++++++++++++++++++++ src/examples/Linked_List/singleton.c | 13 ++++ 14 files changed, 455 insertions(+), 64 deletions(-) delete mode 100644 src/examples/Linked_List/TODO.md create mode 100644 src/examples/Linked_List/add.c create mode 100644 src/examples/Linked_List/append.c create mode 100644 src/examples/Linked_List/c_types.h create mode 100644 src/examples/Linked_List/cn_types.h create mode 100644 src/examples/Linked_List/converters.h create mode 100644 src/examples/Linked_List/getters.h create mode 100644 src/examples/Linked_List/head_tail.c create mode 100644 src/examples/Linked_List/headers.h create mode 100644 src/examples/Linked_List/malloc_free.h create mode 100644 src/examples/Linked_List/predicates.h create mode 100644 src/examples/Linked_List/remove.c create mode 100644 src/examples/Linked_List/singleton.c diff --git a/src/examples/Linked_List/TODO.md b/src/examples/Linked_List/TODO.md deleted file mode 100644 index a6da69c0..00000000 --- a/src/examples/Linked_List/TODO.md +++ /dev/null @@ -1,43 +0,0 @@ -# Functions and Lemmas that should be implemented for doubly linked lists - -push -* WIP -* Insert a new node at the front of a list -* Time Complexity: O(1) -* Auxiliary Space: O(1) - -insertAfter -* Insert a new node after a given node -* Time Complexity: O(1) -* Auxiliary Space: O(1) - -insertBefore -* Insert a new node before a given node -* Time Complexity: O(1) -* Auxiliary Space: O(1) - -append -* WIP -* Insert a new node at the end of a list -* Time Complexity: O(1) -* Auxiliary Space: O(1) - -removeHead -* Remove the head node from the list -* Time Complexity: O(1) -* Auxiliary Space: O(1) - -removeTail -* Remove the tail node from the list -* Time Complexity: O(1) -* Auxiliary Space: O(1) - -removeNode -* Remove a given node from the list -* Time Complexity: O(1) -* Auxiliary Space: O(1) - -insertAt -* insert a new node at the given index -* Time Complexity: O(n) -* Auxiliary Space: O(1) \ No newline at end of file diff --git a/src/examples/Linked_List/WIPlinklist.c b/src/examples/Linked_List/WIPlinklist.c index 85fb7114..5e9356a0 100644 --- a/src/examples/Linked_List/WIPlinklist.c +++ b/src/examples/Linked_List/WIPlinklist.c @@ -157,27 +157,7 @@ predicate (datatype seq) Own_Backwards_Aux(pointer p, pointer next_pointer, stru return Seq_Cons{head: n.data, tail: First}; } } - -// predicate (datatype nodeSeq) OwnForwardsAlternate(pointer p) { -// if (is_null(p)) { -// return nodeSeq_Nil{}; -// } else { -// take N = Owned(p); -// take rest = OwnForwardsAux(N.next, p, N); -// return nodeSeq_Cons{node: N, tail: rest}; -// } -// } - -// predicate (datatype nodeSeq) OwnBackwardsAlternate(pointer p) { -// if (is_null(p)) { -// return nodeSeq_Nil{}; -// } else { -// take N = Owned(p); -// take first = OwnBackwardsAux(N.prev, p, N); -// return nodeSeq_Cons{node: N, tail: first}; -// } -// } -// @*/ +@*/ extern struct node *malloc_node(); /*@ spec malloc_node(); diff --git a/src/examples/Linked_List/add.c b/src/examples/Linked_List/add.c new file mode 100644 index 00000000..53d7fbcc --- /dev/null +++ b/src/examples/Linked_List/add.c @@ -0,0 +1,72 @@ +#include "./headers.h" + +/* TODO: I want the spec to say that the first half and the second half of the list + * (First and Rest) are the same, there is just an extra node in the middle. + * Currently the spec is saying the following: + + * When we first call LinkedList(n), we get the first part, the node n points to, + * and the rest of the list. When we add a new node and call LinkedList on the new node, + * the old node is squished into the first part. Then we have the new node and then the + * rest of the list stays the same. The first paragraph of specs is making sure all of the + * pointers are correct. The second paragraph is making sure that the two halves of the list + * are the same, except for the new node added to the beginning of First. + + * We have to include in these specs whether or not the given node was null. + + * I believe the spec currently is correct, but it is very verbose and hard to understand. + * I would like to find a simpler version. Maybe even write a correctness function? +*/ + + + + +// Adds after the given node +struct node *add(int element, struct node *n) +/*@ requires take L = LinkedList(n); + let node = Dll_Node(L); + let First = Dll_First(L); + let Rest = Dll_Rest(L); + ensures take L_ = LinkedList(return); + let First_ = Dll_First(L_); + let Rest_ = Dll_Rest(L_); + let new_node = Dll_Node(L_); + + ptr_eq(new_node.prev, n); + let node_ = Seq_Node_Head(First_); + !is_null(n) implies ptr_eq(node_.next, return); + !is_null(n) implies ptr_eq(new_node.next, node.next); + !is_null(return); + + + !is_null(n) implies Seq_Node_to_Seq(First_) == Seq_Cons { head: node.data, tail: Seq_Node_to_Seq(First)}; + Seq_Node_to_Seq(Rest) == Seq_Node_to_Seq(Rest_); + is_null(n) implies flatten(L_) == Seq_Cons{head: element, tail: Seq_Nil{}}; +@*/ +{ + struct node *new_node = malloc_node(); + new_node->data = element; + new_node->prev = 0; + new_node->next = 0; + + if (n == 0) //empty list case + { + new_node->prev = 0; + new_node->next = 0; + return new_node; + } else { + /*@ split_case(is_null((*n).next)); @*/ + /*@ split_case(is_null((*n).prev)); @*/ + + + new_node->next = n->next; + new_node->prev = n; + + if (n->next !=0) { + /*@ split_case(is_null((*(*n).next).next)); @*/ + n->next->prev = new_node; + } + + n->next = new_node; + return new_node; + } +} diff --git a/src/examples/Linked_List/append.c b/src/examples/Linked_List/append.c new file mode 100644 index 00000000..11700b42 --- /dev/null +++ b/src/examples/Linked_List/append.c @@ -0,0 +1,24 @@ +#include "./headers.h" + +// Appends `second` to the end of `first`, where `first` is the tail of the first list and +// `second` is the head of the second list. +// TODO: fix so that any nodes can be passed in, not just head and tail +struct node *append (struct node *first, struct node *second) +/*@ requires take n1 = Owned(first); + take n2 = Owned(second); + take L = Own_Backwards(n1.prev, first, n1); + take R = Own_Forwards(n2.next, second, n2); + is_null(n1.next) && is_null(n2.prev); + ensures take n1_ = Owned(first); + take n2_ = Owned(second); + take L_ = Own_Backwards(n1.prev, first, n1); + take R_ = Own_Forwards(n2.next, second, n2); + ptr_eq(n1_.next,second) && ptr_eq(n2_.prev, first); + L == L_ && R == R_; +@*/ +{ + first->next = second; + second->prev = first; + + return first; +} \ No newline at end of file diff --git a/src/examples/Linked_List/c_types.h b/src/examples/Linked_List/c_types.h new file mode 100644 index 00000000..830a8923 --- /dev/null +++ b/src/examples/Linked_List/c_types.h @@ -0,0 +1,10 @@ +struct node { + int data; + struct node* prev; + struct node* next; +}; + +struct node_and_int { + struct node* node; + int data; +}; \ No newline at end of file diff --git a/src/examples/Linked_List/cn_types.h b/src/examples/Linked_List/cn_types.h new file mode 100644 index 00000000..9bc80c3a --- /dev/null +++ b/src/examples/Linked_List/cn_types.h @@ -0,0 +1,11 @@ +/*@ +datatype Dll { + Empty_Dll {}, + Dll {datatype Seq_Node first, struct node n, datatype Seq_Node rest} +} + +datatype Seq_Node { + Seq_Node_Nil {}, + Seq_Node_Cons {struct node n, datatype seq tail} +} +@*/ \ No newline at end of file diff --git a/src/examples/Linked_List/converters.h b/src/examples/Linked_List/converters.h new file mode 100644 index 00000000..8394745d --- /dev/null +++ b/src/examples/Linked_List/converters.h @@ -0,0 +1,38 @@ +/*@ +function (datatype seq) flatten (datatype Dll L) { + match L { + Empty_Dll {} => { Seq_Nil {} } + Dll {first: f, n: n, rest: r} => { + match f { + Seq_Node_Nil {} => { + match r { + Seq_Node_Nil {} => { + Seq_Cons {head: n.data, tail: Seq_Nil {}} + } + Seq_Node_Cons {n: nextNode, tail: t} => { + Seq_Cons {head: n.data, tail: Seq_Cons{ head: nextNode.data, tail: t}} + } + } + } + Seq_Node_Cons {n: prevNode, tail: t} => { + match r { + Seq_Node_Nil {} => { + rev(Seq_Cons {head: n.data, tail: Seq_Cons {head: prevNode.data, tail: t}}) + } + Seq_Node_Cons {n: nextNode, tail: t2} => { + append(rev(Seq_Cons {head: prevNode.data, tail: t2}), Seq_Cons {head: n.data, tail: Seq_Cons{ head: nextNode.data, tail: t2}}) + } + } + } + } + } + } +} + +function (datatype seq) Seq_Node_to_Seq(datatype Seq_Node L) { + match L { + Seq_Node_Nil {} => { Seq_Nil {} } + Seq_Node_Cons {n: n, tail: t} => { Seq_Cons {head: n.data, tail: t } } + } +} +@*/ \ No newline at end of file diff --git a/src/examples/Linked_List/getters.h b/src/examples/Linked_List/getters.h new file mode 100644 index 00000000..9cc250a7 --- /dev/null +++ b/src/examples/Linked_List/getters.h @@ -0,0 +1,36 @@ +/*@ +function (datatype Seq_Node) Dll_Rest (datatype Dll L) { + match L { + Empty_Dll {} => { Seq_Node_Nil {} } + Dll {first: _, n: _, rest: r} => { r } + } +} + +function (datatype Seq_Node) Dll_First(datatype Dll L) { + match L { + Empty_Dll {} => { Seq_Node_Nil {} } + Dll {first: f, n: _, rest: _} => { f } + } +} + +function (struct node) Dll_Node (datatype Dll L) { + match L { + Empty_Dll {} => { default } + Dll {first: _, n: n, rest: _} => { n } + } +} + +function (struct node) Seq_Node_Head(datatype Seq_Node S) { + match S { + Seq_Node_Nil {} => { default } + Seq_Node_Cons {n: n, tail: _} => { n } + } +} + +function (datatype seq) Seq_Node_Tail (datatype Seq_Node S) { + match S { + Seq_Node_Nil {} => { Seq_Nil {} } + Seq_Node_Cons {n: _, tail: t} => { t } + } +} +@*/ \ No newline at end of file diff --git a/src/examples/Linked_List/head_tail.c b/src/examples/Linked_List/head_tail.c new file mode 100644 index 00000000..0e18a627 --- /dev/null +++ b/src/examples/Linked_List/head_tail.c @@ -0,0 +1,84 @@ +#include "headers.h" + +// TODO: currently the correctness checks ensure that the list is +// unchanged, but I would also like to specify that the function +// correctly finds the head or tail, respectively. + +struct node *find_head_aux(struct node *n) +/*@ requires !is_null(n); + take node = Owned(n); + take L = Own_Backwards(node.prev, n, node); + ensures take node_ = Owned(n); + take L_ = Own_Backwards(node_.prev, n, node_); + node == node_; + L == L_; +@*/ +{ + /*@ split_case(is_null(n)); @*/ + /*@ split_case(is_null((*n).prev)); @*/ + if (n->prev == 0) + { + return n; + } else { + /*@ split_case(is_null((*(*n).prev).prev)); @*/ + return find_head_aux(n->prev); + } +} + +// Takes any node in the list and returns the head of the list +// TODO: correctness check +struct node *find_head(struct node *n) +/*@ requires take L = LinkedList(n); + ensures take L_ = LinkedList(n); + L == L_; +@*/ +{ + /*@ split_case(is_null(n)); @*/ + if (n == 0) + { + return 0; + } else { + /*@ split_case(is_null((*n).prev)); @*/ + return find_head_aux(n); + } +} + + +struct node *find_tail_aux(struct node *n) +/*@ requires !is_null(n); + take node = Owned(n); + take L = Own_Forwards(node.next, n, node); + ensures take node_ = Owned(n); + take L_ = Own_Forwards(node_.next, n, node_); + node == node_; + L == L_; +@*/ +{ + /*@ split_case(is_null(n)); @*/ + /*@ split_case(is_null((*n).next)); @*/ + if (n->next == 0) + { + return n; + } else { + /*@ split_case(is_null((*(*n).next).next)); @*/ + return find_tail_aux(n->next); + } +} + +// Takes any node in the list and returns the tail of the list +// TODO: correctness check +struct node *findTail(struct node *n) +/*@ requires take L = LinkedList(n); + ensures take L_ = LinkedList(n); + L == L_; +@*/ +{ + /*@ split_case(is_null(n)); @*/ + if (n == 0) + { + return 0; + } else { + /*@ split_case(is_null((*n).next)); @*/ + return find_tail_aux(n); + } +} \ No newline at end of file diff --git a/src/examples/Linked_List/headers.h b/src/examples/Linked_List/headers.h new file mode 100644 index 00000000..35506b5b --- /dev/null +++ b/src/examples/Linked_List/headers.h @@ -0,0 +1,10 @@ +#include "../list.h" +#include "../list_append.h" +#include "../list_rev.h" + +#include "./c_types.h" +#include "./cn_types.h" +#include "./converters.h" +#include "./getters.h" +#include "./malloc_free.h" +#include "./predicates.h" \ No newline at end of file diff --git a/src/examples/Linked_List/malloc_free.h b/src/examples/Linked_List/malloc_free.h new file mode 100644 index 00000000..df919dca --- /dev/null +++ b/src/examples/Linked_List/malloc_free.h @@ -0,0 +1,25 @@ +extern struct node *malloc_node(); +/*@ spec malloc_node(); + requires true; + ensures take u = Block(return); + !ptr_eq(return,NULL); +@*/ + +extern void free_node (struct node *p); +/*@ spec free_node(pointer p); + requires take u = Block(p); + ensures true; +@*/ + +extern struct node_and_int *malloc_node_and_int(); +/*@ spec malloc_node_and_int(); + requires true; + ensures take u = Block(return); + !ptr_eq(return,NULL); +@*/ + +extern void free_node_and_int (struct node_and_int *p); +/*@ spec free_node_and_int(pointer p); + requires take u = Block(p); + ensures true; +@*/ \ No newline at end of file diff --git a/src/examples/Linked_List/predicates.h b/src/examples/Linked_List/predicates.h new file mode 100644 index 00000000..96a61599 --- /dev/null +++ b/src/examples/Linked_List/predicates.h @@ -0,0 +1,62 @@ +/*@ +predicate (datatype Dll) LinkedList (pointer p) { + if (is_null(p)) { + return Empty_Dll{}; + } else { + take n = Owned(p); + take First = Own_Backwards(n.prev, p, n); + take Rest = Own_Forwards(n.next, p, n); + return Dll{first: First, n: n, rest: Rest}; + } +} + +predicate (datatype Seq_Node) Own_Forwards(pointer p, pointer prev_pointer, struct node prev_node) { + if (is_null(p)) { + return Seq_Node_Nil{}; + } else { + take n = Owned(p); + assert (ptr_eq(n.prev, prev_pointer)); + assert(ptr_eq(prev_node.next,p)); + take Rest = Own_Forwards_Aux(n.next, p, n); + return Seq_Node_Cons{n: n, tail: Rest}; + } +} + +predicate (datatype seq) Own_Forwards_Aux(pointer p, pointer prev_pointer, struct node prev_node) { + if (is_null(p)) { + return Seq_Nil{}; + } else { + take n = Owned(p); + assert (ptr_eq(n.prev, prev_pointer)); + assert(ptr_eq(prev_node.next, p)); + take Rest = Own_Forwards_Aux(n.next, p, n); + return Seq_Cons{head: n.data, tail: Rest}; + } +} + + + +predicate (datatype Seq_Node) Own_Backwards(pointer p, pointer next_pointer, struct node next_node) { + if (is_null(p)) { + return Seq_Node_Nil{}; + } else { + take n = Owned(p); + assert (ptr_eq(n.next, next_pointer)); + assert(ptr_eq(next_node.prev, p)); + take First = Own_Backwards_Aux(n.prev, p, n); + return Seq_Node_Cons{n: n, tail: First}; + } +} + +predicate (datatype seq) Own_Backwards_Aux(pointer p, pointer next_pointer, struct node next_node) { + if (is_null(p)) { + return Seq_Nil{}; + } else { + take n = Owned(p); + assert (ptr_eq(n.next, next_pointer)); + assert(ptr_eq(next_node.prev, p)); + take First = Own_Backwards_Aux(n.prev, p, n); + return Seq_Cons{head: n.data, tail: First}; + } +} +@*/ \ No newline at end of file diff --git a/src/examples/Linked_List/remove.c b/src/examples/Linked_List/remove.c new file mode 100644 index 00000000..47a368dc --- /dev/null +++ b/src/examples/Linked_List/remove.c @@ -0,0 +1,69 @@ +#include "./headers.h" + +/* TODO: correctness checks. + + * Currently, the correctness check says that either the first part or the last part + * of the list is unchanged. We don't know which, because the function might return a + * pointer to the element before or after the removed node. + + * We go through the cases for if it's the first part or the second part that we are + * pointing to, and say that the other part must have a node removed. + + * The last spec says that either the first part has one less element, the second part has one + * less element, or the list was singleton and so the first and second parts were both empty. + + * Currently, I believe the check is correct however it is very verbose and confusing. + * I would like to find a simpler version. Maybe even write a correctness function? +*/ + + +// removes the given node from the list and returns another pointer +// to somewhere in the list, or a null pointer if the list is empty. +struct node_and_int *remove(struct node *n) +/*@ requires !is_null(n); + take del = Owned(n); + take First = Own_Backwards(del.prev, n, del); + take Rest = Own_Forwards(del.next, n, del); + ensures take ret = Owned(return); + take L = LinkedList(ret.node); + let First_ = Dll_First(L); + let Rest_ = Dll_Rest(L); + let node = Dll_Node(L); + Seq_Node_to_Seq(First_ )== Seq_Node_to_Seq(First) || Seq_Node_to_Seq(Rest_) == Seq_Node_to_Seq(Rest); + !is_null(ret.node) implies (Seq_Node_to_Seq(First_ ) == Seq_Node_to_Seq(First) implies Seq_Node_to_Seq(Rest) == Seq_Cons{head: node.data, tail: Seq_Node_to_Seq(Rest_)}); + + !is_null(ret.node) implies (Seq_Node_to_Seq(Rest_ ) == Seq_Node_to_Seq(Rest) implies Seq_Node_to_Seq(First) == Seq_Cons{head: node.data, tail: Seq_Node_to_Seq(First_)}); + + Seq_Node_to_Seq(First) == Seq_Cons{head: node.data, tail: Seq_Node_to_Seq(First_)} || Seq_Node_to_Seq(Rest) == Seq_Cons{head: node.data, tail: Seq_Node_to_Seq(Rest_)} || (Seq_Node_to_Seq(First) == Seq_Nil{} && Seq_Node_to_Seq(Rest) == Seq_Nil{}); + + // flatten(l) == append(rev(nodeSeqtoSeq(first)), nodeSeqtoSeq(rest)); + +@*/ +{ + if (n == 0) { //empty list case + struct node_and_int *pair = malloc_node_and_int(); + pair->node = 0; //null pointer + pair->data = 0; + return pair; + } else { + struct node *temp = 0; + if (n->prev != 0) { + /*@ split_case(is_null((*(*n).prev).prev)); @*/ + + n->prev->next = n->next; + temp = n->prev; + } + if (n->next != 0) { + /*@ split_case(is_null((*(*n).next).next)); @*/ + n->next->prev = n->prev; + temp = n->next; + } + + struct node_and_int *pair = malloc_node_and_int(); + pair->node = temp; + pair->data = n->data; + + free_node(n); + return pair; + } +} \ No newline at end of file diff --git a/src/examples/Linked_List/singleton.c b/src/examples/Linked_List/singleton.c new file mode 100644 index 00000000..07283709 --- /dev/null +++ b/src/examples/Linked_List/singleton.c @@ -0,0 +1,13 @@ +#include "./headers.h" + +struct node *singleton(int element) +/*@ ensures take Ret = LinkedList(return); + Ret == Dll{first: Seq_Node_Nil{}, n: struct node{data: element, prev: NULL, next: NULL}, rest: Seq_Node_Nil{}}; +@*/ +{ + struct node *n = malloc_node(); + n->data = element; + n->prev = 0; + n->next = 0; + return n; +} \ No newline at end of file From 1991e83741a5c8d7017457f9a2c2dacdf6b98e42 Mon Sep 17 00:00:00 2001 From: Liz Austell Date: Mon, 15 Jul 2024 17:13:18 -0400 Subject: [PATCH 077/152] Change Dll datatype to only use seqs --- src/examples/Linked_List/add.c | 48 ++++++++++++++------------- src/examples/Linked_List/cn_types.h | 7 +--- src/examples/Linked_List/converters.h | 32 ++---------------- src/examples/Linked_List/getters.h | 30 +++++------------ src/examples/Linked_List/head_tail.c | 8 ++--- src/examples/Linked_List/predicates.h | 46 ++++++------------------- src/examples/Linked_List/remove.c | 16 ++++----- src/examples/Linked_List/singleton.c | 4 +-- 8 files changed, 60 insertions(+), 131 deletions(-) diff --git a/src/examples/Linked_List/add.c b/src/examples/Linked_List/add.c index 53d7fbcc..579ed2dd 100644 --- a/src/examples/Linked_List/add.c +++ b/src/examples/Linked_List/add.c @@ -7,40 +7,42 @@ * When we first call LinkedList(n), we get the first part, the node n points to, * and the rest of the list. When we add a new node and call LinkedList on the new node, * the old node is squished into the first part. Then we have the new node and then the - * rest of the list stays the same. The first paragraph of specs is making sure all of the - * pointers are correct. The second paragraph is making sure that the two halves of the list + * rest of the list stays the same. + + * The first paragraph of specs is making sure all of the pointers are correct. + * (TODO: is this already checked through the LinkedList predicate?) + + * The second paragraph is making sure that the two halves of the list * are the same, except for the new node added to the beginning of First. * We have to include in these specs whether or not the given node was null. * I believe the spec currently is correct, but it is very verbose and hard to understand. * I would like to find a simpler version. Maybe even write a correctness function? -*/ + * Maybe something like + !is_null(n) implies flatten(L_) == append(rev(Seq_Node_to_Seq(First)), Seq_Cons{head: node.data, tail: Seq_Cons{head: new_node.data, tail: Seq_Node_to_Seq(Rest)}}); + is_null(n) implies flatten(L_) == Seq_Cons{head: element, tail: Seq_Nil{}}; + * But this became very hard to prove with tons of unfolds -// Adds after the given node -struct node *add(int element, struct node *n) -/*@ requires take L = LinkedList(n); - let node = Dll_Node(L); - let First = Dll_First(L); - let Rest = Dll_Rest(L); - ensures take L_ = LinkedList(return); - let First_ = Dll_First(L_); - let Rest_ = Dll_Rest(L_); - let new_node = Dll_Node(L_); - - ptr_eq(new_node.prev, n); - let node_ = Seq_Node_Head(First_); - !is_null(n) implies ptr_eq(node_.next, return); - !is_null(n) implies ptr_eq(new_node.next, node.next); - !is_null(return); +*/ +//TODO: make first and rest into left and right +//TODO: add D to Lknked list predicate name or OwnedDLL ? or (DLL_at best one rn) +// TODO: get rid of node seq and just use seq on either side - !is_null(n) implies Seq_Node_to_Seq(First_) == Seq_Cons { head: node.data, tail: Seq_Node_to_Seq(First)}; - Seq_Node_to_Seq(Rest) == Seq_Node_to_Seq(Rest_); - is_null(n) implies flatten(L_) == Seq_Cons{head: element, tail: Seq_Nil{}}; +// Adds after the given node and returns a pointer to the new node +struct node *add(int element, struct node *n) +/*@ requires take L = Owned_Dll(n); + let n_node = Node(L); + let Left = Left(L); + let Right = Right(L); + ensures take L_ = Owned_Dll(return); + let new_node = Node(L_); + is_null(n) ? L_ == Dll { left: Seq_Nil{}, n: new_node, right: Seq_Nil{}} + : L_ == Dll { left: Seq_Cons{head: n_node.data, tail: Left}, n: new_node, right: Right}; @*/ { struct node *new_node = malloc_node(); @@ -69,4 +71,4 @@ struct node *add(int element, struct node *n) n->next = new_node; return new_node; } -} +} \ No newline at end of file diff --git a/src/examples/Linked_List/cn_types.h b/src/examples/Linked_List/cn_types.h index 9bc80c3a..8324f22f 100644 --- a/src/examples/Linked_List/cn_types.h +++ b/src/examples/Linked_List/cn_types.h @@ -1,11 +1,6 @@ /*@ datatype Dll { Empty_Dll {}, - Dll {datatype Seq_Node first, struct node n, datatype Seq_Node rest} -} - -datatype Seq_Node { - Seq_Node_Nil {}, - Seq_Node_Cons {struct node n, datatype seq tail} + Dll {datatype seq left, struct node n, datatype seq right} } @*/ \ No newline at end of file diff --git a/src/examples/Linked_List/converters.h b/src/examples/Linked_List/converters.h index 8394745d..d5c9a50e 100644 --- a/src/examples/Linked_List/converters.h +++ b/src/examples/Linked_List/converters.h @@ -2,37 +2,9 @@ function (datatype seq) flatten (datatype Dll L) { match L { Empty_Dll {} => { Seq_Nil {} } - Dll {first: f, n: n, rest: r} => { - match f { - Seq_Node_Nil {} => { - match r { - Seq_Node_Nil {} => { - Seq_Cons {head: n.data, tail: Seq_Nil {}} - } - Seq_Node_Cons {n: nextNode, tail: t} => { - Seq_Cons {head: n.data, tail: Seq_Cons{ head: nextNode.data, tail: t}} - } - } - } - Seq_Node_Cons {n: prevNode, tail: t} => { - match r { - Seq_Node_Nil {} => { - rev(Seq_Cons {head: n.data, tail: Seq_Cons {head: prevNode.data, tail: t}}) - } - Seq_Node_Cons {n: nextNode, tail: t2} => { - append(rev(Seq_Cons {head: prevNode.data, tail: t2}), Seq_Cons {head: n.data, tail: Seq_Cons{ head: nextNode.data, tail: t2}}) - } - } - } - } + Dll {left: l, n: n, right: r} => { + append(l, Seq_Cons {head: n.data, tail: r}) } } } - -function (datatype seq) Seq_Node_to_Seq(datatype Seq_Node L) { - match L { - Seq_Node_Nil {} => { Seq_Nil {} } - Seq_Node_Cons {n: n, tail: t} => { Seq_Cons {head: n.data, tail: t } } - } -} @*/ \ No newline at end of file diff --git a/src/examples/Linked_List/getters.h b/src/examples/Linked_List/getters.h index 9cc250a7..a8545bfd 100644 --- a/src/examples/Linked_List/getters.h +++ b/src/examples/Linked_List/getters.h @@ -1,36 +1,22 @@ /*@ -function (datatype Seq_Node) Dll_Rest (datatype Dll L) { +function (datatype seq) Right (datatype Dll L) { match L { - Empty_Dll {} => { Seq_Node_Nil {} } - Dll {first: _, n: _, rest: r} => { r } + Empty_Dll {} => { Seq_Nil{} } + Dll {left: _, n: _, right: r} => { r } } } -function (datatype Seq_Node) Dll_First(datatype Dll L) { +function (datatype seq) Left (datatype Dll L) { match L { - Empty_Dll {} => { Seq_Node_Nil {} } - Dll {first: f, n: _, rest: _} => { f } + Empty_Dll {} => { Seq_Nil {} } + Dll {left: l, n: _, right: _} => { l } } } -function (struct node) Dll_Node (datatype Dll L) { +function (struct node) Node (datatype Dll L) { match L { Empty_Dll {} => { default } - Dll {first: _, n: n, rest: _} => { n } - } -} - -function (struct node) Seq_Node_Head(datatype Seq_Node S) { - match S { - Seq_Node_Nil {} => { default } - Seq_Node_Cons {n: n, tail: _} => { n } - } -} - -function (datatype seq) Seq_Node_Tail (datatype Seq_Node S) { - match S { - Seq_Node_Nil {} => { Seq_Nil {} } - Seq_Node_Cons {n: _, tail: t} => { t } + Dll {left: _, n: n, right: _} => { n } } } @*/ \ No newline at end of file diff --git a/src/examples/Linked_List/head_tail.c b/src/examples/Linked_List/head_tail.c index 0e18a627..81f63d1e 100644 --- a/src/examples/Linked_List/head_tail.c +++ b/src/examples/Linked_List/head_tail.c @@ -28,8 +28,8 @@ struct node *find_head_aux(struct node *n) // Takes any node in the list and returns the head of the list // TODO: correctness check struct node *find_head(struct node *n) -/*@ requires take L = LinkedList(n); - ensures take L_ = LinkedList(n); +/*@ requires take L = Owned_Dll(n); + ensures take L_ = Owned_Dll(n); L == L_; @*/ { @@ -68,8 +68,8 @@ struct node *find_tail_aux(struct node *n) // Takes any node in the list and returns the tail of the list // TODO: correctness check struct node *findTail(struct node *n) -/*@ requires take L = LinkedList(n); - ensures take L_ = LinkedList(n); +/*@ requires take L = Owned_Dll(n); + ensures take L_ = Owned_Dll(n); L == L_; @*/ { diff --git a/src/examples/Linked_List/predicates.h b/src/examples/Linked_List/predicates.h index 96a61599..a085087a 100644 --- a/src/examples/Linked_List/predicates.h +++ b/src/examples/Linked_List/predicates.h @@ -1,62 +1,36 @@ /*@ -predicate (datatype Dll) LinkedList (pointer p) { +predicate (datatype Dll) Owned_Dll (pointer p) { if (is_null(p)) { return Empty_Dll{}; } else { take n = Owned(p); - take First = Own_Backwards(n.prev, p, n); - take Rest = Own_Forwards(n.next, p, n); - return Dll{first: First, n: n, rest: Rest}; + take Left = Own_Backwards(n.prev, p, n); + take Right = Own_Forwards(n.next, p, n); + return Dll{left: Left, n: n, right: Right}; } } -predicate (datatype Seq_Node) Own_Forwards(pointer p, pointer prev_pointer, struct node prev_node) { - if (is_null(p)) { - return Seq_Node_Nil{}; - } else { - take n = Owned(p); - assert (ptr_eq(n.prev, prev_pointer)); - assert(ptr_eq(prev_node.next,p)); - take Rest = Own_Forwards_Aux(n.next, p, n); - return Seq_Node_Cons{n: n, tail: Rest}; - } -} - -predicate (datatype seq) Own_Forwards_Aux(pointer p, pointer prev_pointer, struct node prev_node) { +predicate (datatype seq) Own_Forwards(pointer p, pointer prev_pointer, struct node prev_node) { if (is_null(p)) { return Seq_Nil{}; } else { take n = Owned(p); assert (ptr_eq(n.prev, prev_pointer)); assert(ptr_eq(prev_node.next, p)); - take Rest = Own_Forwards_Aux(n.next, p, n); - return Seq_Cons{head: n.data, tail: Rest}; - } -} - - - -predicate (datatype Seq_Node) Own_Backwards(pointer p, pointer next_pointer, struct node next_node) { - if (is_null(p)) { - return Seq_Node_Nil{}; - } else { - take n = Owned(p); - assert (ptr_eq(n.next, next_pointer)); - assert(ptr_eq(next_node.prev, p)); - take First = Own_Backwards_Aux(n.prev, p, n); - return Seq_Node_Cons{n: n, tail: First}; + take Right = Own_Forwards(n.next, p, n); + return Seq_Cons{head: n.data, tail: Right}; } } -predicate (datatype seq) Own_Backwards_Aux(pointer p, pointer next_pointer, struct node next_node) { +predicate (datatype seq) Own_Backwards(pointer p, pointer next_pointer, struct node next_node) { if (is_null(p)) { return Seq_Nil{}; } else { take n = Owned(p); assert (ptr_eq(n.next, next_pointer)); assert(ptr_eq(next_node.prev, p)); - take First = Own_Backwards_Aux(n.prev, p, n); - return Seq_Cons{head: n.data, tail: First}; + take Left = Own_Backwards(n.prev, p, n); + return Seq_Cons{head: n.data, tail: Left}; } } @*/ \ No newline at end of file diff --git a/src/examples/Linked_List/remove.c b/src/examples/Linked_List/remove.c index 47a368dc..8d76ae82 100644 --- a/src/examples/Linked_List/remove.c +++ b/src/examples/Linked_List/remove.c @@ -25,16 +25,16 @@ struct node_and_int *remove(struct node *n) take First = Own_Backwards(del.prev, n, del); take Rest = Own_Forwards(del.next, n, del); ensures take ret = Owned(return); - take L = LinkedList(ret.node); - let First_ = Dll_First(L); - let Rest_ = Dll_Rest(L); - let node = Dll_Node(L); - Seq_Node_to_Seq(First_ )== Seq_Node_to_Seq(First) || Seq_Node_to_Seq(Rest_) == Seq_Node_to_Seq(Rest); - !is_null(ret.node) implies (Seq_Node_to_Seq(First_ ) == Seq_Node_to_Seq(First) implies Seq_Node_to_Seq(Rest) == Seq_Cons{head: node.data, tail: Seq_Node_to_Seq(Rest_)}); + take L = Owned_Dll(ret.node); + let First_ = Left(L); + let Rest_ = Right(L); + let node = Node(L); + First_ == First || Rest_ == Rest; + !is_null(ret.node) implies (First_ == First implies Rest == Seq_Cons{head: node.data, tail: Rest_}); - !is_null(ret.node) implies (Seq_Node_to_Seq(Rest_ ) == Seq_Node_to_Seq(Rest) implies Seq_Node_to_Seq(First) == Seq_Cons{head: node.data, tail: Seq_Node_to_Seq(First_)}); + !is_null(ret.node) implies (Rest_ == Rest implies First == Seq_Cons{head: node.data, tail: First_}); - Seq_Node_to_Seq(First) == Seq_Cons{head: node.data, tail: Seq_Node_to_Seq(First_)} || Seq_Node_to_Seq(Rest) == Seq_Cons{head: node.data, tail: Seq_Node_to_Seq(Rest_)} || (Seq_Node_to_Seq(First) == Seq_Nil{} && Seq_Node_to_Seq(Rest) == Seq_Nil{}); + First == Seq_Cons{head: node.data, tail: First_} || Rest == Seq_Cons{head: node.data, tail: Rest_} || (First == Seq_Nil{} && Rest == Seq_Nil{}); // flatten(l) == append(rev(nodeSeqtoSeq(first)), nodeSeqtoSeq(rest)); diff --git a/src/examples/Linked_List/singleton.c b/src/examples/Linked_List/singleton.c index 07283709..421253cd 100644 --- a/src/examples/Linked_List/singleton.c +++ b/src/examples/Linked_List/singleton.c @@ -1,8 +1,8 @@ #include "./headers.h" struct node *singleton(int element) -/*@ ensures take Ret = LinkedList(return); - Ret == Dll{first: Seq_Node_Nil{}, n: struct node{data: element, prev: NULL, next: NULL}, rest: Seq_Node_Nil{}}; +/*@ ensures take Ret = Owned_Dll(return); + Ret == Dll{left: Seq_Nil{}, n: struct node{data: element, prev: NULL, next: NULL}, right: Seq_Nil{}}; @*/ { struct node *n = malloc_node(); From 9a60e3a9b0f8788b4daeb10ff4f8de34eb896d6b Mon Sep 17 00:00:00 2001 From: Liz Austell Date: Tue, 16 Jul 2024 12:40:07 -0400 Subject: [PATCH 078/152] fix remove spec to be more concise --- src/examples/Linked_List/add.c | 37 ++----------------------- src/examples/Linked_List/head_tail.c | 8 +++--- src/examples/Linked_List/predicates.h | 2 +- src/examples/Linked_List/remove.c | 39 +++++---------------------- src/examples/Linked_List/singleton.c | 2 +- 5 files changed, 14 insertions(+), 74 deletions(-) diff --git a/src/examples/Linked_List/add.c b/src/examples/Linked_List/add.c index 579ed2dd..2475d954 100644 --- a/src/examples/Linked_List/add.c +++ b/src/examples/Linked_List/add.c @@ -1,45 +1,12 @@ #include "./headers.h" -/* TODO: I want the spec to say that the first half and the second half of the list - * (First and Rest) are the same, there is just an extra node in the middle. - * Currently the spec is saying the following: - - * When we first call LinkedList(n), we get the first part, the node n points to, - * and the rest of the list. When we add a new node and call LinkedList on the new node, - * the old node is squished into the first part. Then we have the new node and then the - * rest of the list stays the same. - - * The first paragraph of specs is making sure all of the pointers are correct. - * (TODO: is this already checked through the LinkedList predicate?) - - * The second paragraph is making sure that the two halves of the list - * are the same, except for the new node added to the beginning of First. - - * We have to include in these specs whether or not the given node was null. - - * I believe the spec currently is correct, but it is very verbose and hard to understand. - * I would like to find a simpler version. Maybe even write a correctness function? - - - * Maybe something like - !is_null(n) implies flatten(L_) == append(rev(Seq_Node_to_Seq(First)), Seq_Cons{head: node.data, tail: Seq_Cons{head: new_node.data, tail: Seq_Node_to_Seq(Rest)}}); - is_null(n) implies flatten(L_) == Seq_Cons{head: element, tail: Seq_Nil{}}; - - * But this became very hard to prove with tons of unfolds - -*/ - -//TODO: make first and rest into left and right -//TODO: add D to Lknked list predicate name or OwnedDLL ? or (DLL_at best one rn) -// TODO: get rid of node seq and just use seq on either side - // Adds after the given node and returns a pointer to the new node struct node *add(int element, struct node *n) -/*@ requires take L = Owned_Dll(n); +/*@ requires take L = Dll_at(n); let n_node = Node(L); let Left = Left(L); let Right = Right(L); - ensures take L_ = Owned_Dll(return); + ensures take L_ = Dll_at(return); let new_node = Node(L_); is_null(n) ? L_ == Dll { left: Seq_Nil{}, n: new_node, right: Seq_Nil{}} : L_ == Dll { left: Seq_Cons{head: n_node.data, tail: Left}, n: new_node, right: Right}; diff --git a/src/examples/Linked_List/head_tail.c b/src/examples/Linked_List/head_tail.c index 81f63d1e..6485b42e 100644 --- a/src/examples/Linked_List/head_tail.c +++ b/src/examples/Linked_List/head_tail.c @@ -28,8 +28,8 @@ struct node *find_head_aux(struct node *n) // Takes any node in the list and returns the head of the list // TODO: correctness check struct node *find_head(struct node *n) -/*@ requires take L = Owned_Dll(n); - ensures take L_ = Owned_Dll(n); +/*@ requires take L = Dll_at(n); + ensures take L_ = Dll_at(n); L == L_; @*/ { @@ -68,8 +68,8 @@ struct node *find_tail_aux(struct node *n) // Takes any node in the list and returns the tail of the list // TODO: correctness check struct node *findTail(struct node *n) -/*@ requires take L = Owned_Dll(n); - ensures take L_ = Owned_Dll(n); +/*@ requires take L = Dll_at(n); + ensures take L_ = Dll_at(n); L == L_; @*/ { diff --git a/src/examples/Linked_List/predicates.h b/src/examples/Linked_List/predicates.h index a085087a..5111b461 100644 --- a/src/examples/Linked_List/predicates.h +++ b/src/examples/Linked_List/predicates.h @@ -1,5 +1,5 @@ /*@ -predicate (datatype Dll) Owned_Dll (pointer p) { +predicate (datatype Dll) Dll_at (pointer p) { if (is_null(p)) { return Empty_Dll{}; } else { diff --git a/src/examples/Linked_List/remove.c b/src/examples/Linked_List/remove.c index 8d76ae82..02acd312 100644 --- a/src/examples/Linked_List/remove.c +++ b/src/examples/Linked_List/remove.c @@ -1,43 +1,16 @@ #include "./headers.h" -/* TODO: correctness checks. - - * Currently, the correctness check says that either the first part or the last part - * of the list is unchanged. We don't know which, because the function might return a - * pointer to the element before or after the removed node. - - * We go through the cases for if it's the first part or the second part that we are - * pointing to, and say that the other part must have a node removed. - - * The last spec says that either the first part has one less element, the second part has one - * less element, or the list was singleton and so the first and second parts were both empty. - - * Currently, I believe the check is correct however it is very verbose and confusing. - * I would like to find a simpler version. Maybe even write a correctness function? -*/ - - // removes the given node from the list and returns another pointer // to somewhere in the list, or a null pointer if the list is empty. struct node_and_int *remove(struct node *n) /*@ requires !is_null(n); - take del = Owned(n); - take First = Own_Backwards(del.prev, n, del); - take Rest = Own_Forwards(del.next, n, del); + take before = Dll_at(n); + let del = Node(before); ensures take ret = Owned(return); - take L = Owned_Dll(ret.node); - let First_ = Left(L); - let Rest_ = Right(L); - let node = Node(L); - First_ == First || Rest_ == Rest; - !is_null(ret.node) implies (First_ == First implies Rest == Seq_Cons{head: node.data, tail: Rest_}); - - !is_null(ret.node) implies (Rest_ == Rest implies First == Seq_Cons{head: node.data, tail: First_}); - - First == Seq_Cons{head: node.data, tail: First_} || Rest == Seq_Cons{head: node.data, tail: Rest_} || (First == Seq_Nil{} && Rest == Seq_Nil{}); - - // flatten(l) == append(rev(nodeSeqtoSeq(first)), nodeSeqtoSeq(rest)); - + take after = Dll_at(ret.node); + (is_null(del.prev) && is_null(del.next)) ? after == Empty_Dll{} + : (!is_null(del.next) ? after == Dll{left: Left(before), n: Node(after), right: tl(Right(before))} + : after == Dll{left: tl(Left(before)), n: Node(after), right: Right(before)}); @*/ { if (n == 0) { //empty list case diff --git a/src/examples/Linked_List/singleton.c b/src/examples/Linked_List/singleton.c index 421253cd..ebd7b32c 100644 --- a/src/examples/Linked_List/singleton.c +++ b/src/examples/Linked_List/singleton.c @@ -1,7 +1,7 @@ #include "./headers.h" struct node *singleton(int element) -/*@ ensures take Ret = Owned_Dll(return); +/*@ ensures take Ret = Dll_at(return); Ret == Dll{left: Seq_Nil{}, n: struct node{data: element, prev: NULL, next: NULL}, right: Seq_Nil{}}; @*/ { From 10284013444dd47d8599d341a1a689939f33b843 Mon Sep 17 00:00:00 2001 From: Liz Austell Date: Tue, 16 Jul 2024 14:53:40 -0400 Subject: [PATCH 079/152] rename for clarity --- src/examples/Linked_List/add.c | 4 ++-- src/examples/Linked_List/cn_types.h | 2 +- src/examples/Linked_List/converters.h | 2 +- src/examples/Linked_List/getters.h | 6 +++--- src/examples/Linked_List/predicates.h | 2 +- src/examples/Linked_List/remove.c | 4 ++-- src/examples/Linked_List/singleton.c | 2 +- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/examples/Linked_List/add.c b/src/examples/Linked_List/add.c index 2475d954..01c1cd4c 100644 --- a/src/examples/Linked_List/add.c +++ b/src/examples/Linked_List/add.c @@ -8,8 +8,8 @@ struct node *add(int element, struct node *n) let Right = Right(L); ensures take L_ = Dll_at(return); let new_node = Node(L_); - is_null(n) ? L_ == Dll { left: Seq_Nil{}, n: new_node, right: Seq_Nil{}} - : L_ == Dll { left: Seq_Cons{head: n_node.data, tail: Left}, n: new_node, right: Right}; + is_null(n) ? L_ == Dll { left: Seq_Nil{}, curr: new_node, right: Seq_Nil{}} + : L_ == Dll { left: Seq_Cons{head: n_node.data, tail: Left}, curr: new_node, right: Right}; @*/ { struct node *new_node = malloc_node(); diff --git a/src/examples/Linked_List/cn_types.h b/src/examples/Linked_List/cn_types.h index 8324f22f..59278fb2 100644 --- a/src/examples/Linked_List/cn_types.h +++ b/src/examples/Linked_List/cn_types.h @@ -1,6 +1,6 @@ /*@ datatype Dll { Empty_Dll {}, - Dll {datatype seq left, struct node n, datatype seq right} + Dll {datatype seq left, struct node curr, datatype seq right} } @*/ \ No newline at end of file diff --git a/src/examples/Linked_List/converters.h b/src/examples/Linked_List/converters.h index d5c9a50e..f96e8f37 100644 --- a/src/examples/Linked_List/converters.h +++ b/src/examples/Linked_List/converters.h @@ -2,7 +2,7 @@ function (datatype seq) flatten (datatype Dll L) { match L { Empty_Dll {} => { Seq_Nil {} } - Dll {left: l, n: n, right: r} => { + Dll {left: l, curr: n, right: r} => { append(l, Seq_Cons {head: n.data, tail: r}) } } diff --git a/src/examples/Linked_List/getters.h b/src/examples/Linked_List/getters.h index a8545bfd..9e180623 100644 --- a/src/examples/Linked_List/getters.h +++ b/src/examples/Linked_List/getters.h @@ -2,21 +2,21 @@ function (datatype seq) Right (datatype Dll L) { match L { Empty_Dll {} => { Seq_Nil{} } - Dll {left: _, n: _, right: r} => { r } + Dll {left: _, curr: _, right: r} => { r } } } function (datatype seq) Left (datatype Dll L) { match L { Empty_Dll {} => { Seq_Nil {} } - Dll {left: l, n: _, right: _} => { l } + Dll {left: l, curr: _, right: _} => { l } } } function (struct node) Node (datatype Dll L) { match L { Empty_Dll {} => { default } - Dll {left: _, n: n, right: _} => { n } + Dll {left: _, curr: n, right: _} => { n } } } @*/ \ No newline at end of file diff --git a/src/examples/Linked_List/predicates.h b/src/examples/Linked_List/predicates.h index 5111b461..a5353591 100644 --- a/src/examples/Linked_List/predicates.h +++ b/src/examples/Linked_List/predicates.h @@ -6,7 +6,7 @@ predicate (datatype Dll) Dll_at (pointer p) { take n = Owned(p); take Left = Own_Backwards(n.prev, p, n); take Right = Own_Forwards(n.next, p, n); - return Dll{left: Left, n: n, right: Right}; + return Dll{left: Left, curr: n, right: Right}; } } diff --git a/src/examples/Linked_List/remove.c b/src/examples/Linked_List/remove.c index 02acd312..713cceb3 100644 --- a/src/examples/Linked_List/remove.c +++ b/src/examples/Linked_List/remove.c @@ -9,8 +9,8 @@ struct node_and_int *remove(struct node *n) ensures take ret = Owned(return); take after = Dll_at(ret.node); (is_null(del.prev) && is_null(del.next)) ? after == Empty_Dll{} - : (!is_null(del.next) ? after == Dll{left: Left(before), n: Node(after), right: tl(Right(before))} - : after == Dll{left: tl(Left(before)), n: Node(after), right: Right(before)}); + : (!is_null(del.next) ? after == Dll{left: Left(before), curr: Node(after), right: tl(Right(before))} + : after == Dll{left: tl(Left(before)), curr: Node(after), right: Right(before)}); @*/ { if (n == 0) { //empty list case diff --git a/src/examples/Linked_List/singleton.c b/src/examples/Linked_List/singleton.c index ebd7b32c..15c8fd8a 100644 --- a/src/examples/Linked_List/singleton.c +++ b/src/examples/Linked_List/singleton.c @@ -2,7 +2,7 @@ struct node *singleton(int element) /*@ ensures take Ret = Dll_at(return); - Ret == Dll{left: Seq_Nil{}, n: struct node{data: element, prev: NULL, next: NULL}, right: Seq_Nil{}}; + Ret == Dll{left: Seq_Nil{}, curr: struct node{data: element, prev: NULL, next: NULL}, right: Seq_Nil{}}; @*/ { struct node *n = malloc_node(); From e7984fff883d290f61d91b6a376911bacc33442a Mon Sep 17 00:00:00 2001 From: Liz Austell Date: Tue, 16 Jul 2024 15:01:43 -0400 Subject: [PATCH 080/152] change Linked_List to Dbl_Linked_list --- .../{Linked_List => Dbl_Linked_List}/add.c | 0 .../c_types.h | 0 .../cn_types.h | 0 .../getters.h | 0 .../headers.h | 0 .../malloc_free.h | 0 .../predicates.h | 0 .../{Linked_List => Dbl_Linked_List}/remove.c | 0 .../singleton.c | 0 src/examples/Linked_List/WIPlinklist.c | 403 ------------------ src/examples/Linked_List/append.c | 24 -- src/examples/Linked_List/converters.h | 10 - src/examples/Linked_List/head_tail.c | 84 ---- 13 files changed, 521 deletions(-) rename src/examples/{Linked_List => Dbl_Linked_List}/add.c (100%) rename src/examples/{Linked_List => Dbl_Linked_List}/c_types.h (100%) rename src/examples/{Linked_List => Dbl_Linked_List}/cn_types.h (100%) rename src/examples/{Linked_List => Dbl_Linked_List}/getters.h (100%) rename src/examples/{Linked_List => Dbl_Linked_List}/headers.h (100%) rename src/examples/{Linked_List => Dbl_Linked_List}/malloc_free.h (100%) rename src/examples/{Linked_List => Dbl_Linked_List}/predicates.h (100%) rename src/examples/{Linked_List => Dbl_Linked_List}/remove.c (100%) rename src/examples/{Linked_List => Dbl_Linked_List}/singleton.c (100%) delete mode 100644 src/examples/Linked_List/WIPlinklist.c delete mode 100644 src/examples/Linked_List/append.c delete mode 100644 src/examples/Linked_List/converters.h delete mode 100644 src/examples/Linked_List/head_tail.c diff --git a/src/examples/Linked_List/add.c b/src/examples/Dbl_Linked_List/add.c similarity index 100% rename from src/examples/Linked_List/add.c rename to src/examples/Dbl_Linked_List/add.c diff --git a/src/examples/Linked_List/c_types.h b/src/examples/Dbl_Linked_List/c_types.h similarity index 100% rename from src/examples/Linked_List/c_types.h rename to src/examples/Dbl_Linked_List/c_types.h diff --git a/src/examples/Linked_List/cn_types.h b/src/examples/Dbl_Linked_List/cn_types.h similarity index 100% rename from src/examples/Linked_List/cn_types.h rename to src/examples/Dbl_Linked_List/cn_types.h diff --git a/src/examples/Linked_List/getters.h b/src/examples/Dbl_Linked_List/getters.h similarity index 100% rename from src/examples/Linked_List/getters.h rename to src/examples/Dbl_Linked_List/getters.h diff --git a/src/examples/Linked_List/headers.h b/src/examples/Dbl_Linked_List/headers.h similarity index 100% rename from src/examples/Linked_List/headers.h rename to src/examples/Dbl_Linked_List/headers.h diff --git a/src/examples/Linked_List/malloc_free.h b/src/examples/Dbl_Linked_List/malloc_free.h similarity index 100% rename from src/examples/Linked_List/malloc_free.h rename to src/examples/Dbl_Linked_List/malloc_free.h diff --git a/src/examples/Linked_List/predicates.h b/src/examples/Dbl_Linked_List/predicates.h similarity index 100% rename from src/examples/Linked_List/predicates.h rename to src/examples/Dbl_Linked_List/predicates.h diff --git a/src/examples/Linked_List/remove.c b/src/examples/Dbl_Linked_List/remove.c similarity index 100% rename from src/examples/Linked_List/remove.c rename to src/examples/Dbl_Linked_List/remove.c diff --git a/src/examples/Linked_List/singleton.c b/src/examples/Dbl_Linked_List/singleton.c similarity index 100% rename from src/examples/Linked_List/singleton.c rename to src/examples/Dbl_Linked_List/singleton.c diff --git a/src/examples/Linked_List/WIPlinklist.c b/src/examples/Linked_List/WIPlinklist.c deleted file mode 100644 index 5e9356a0..00000000 --- a/src/examples/Linked_List/WIPlinklist.c +++ /dev/null @@ -1,403 +0,0 @@ -#include "../list.h" -#include "../list_append.h" -#include "../list_rev.h" - -struct node { - int data; - struct node* prev; - struct node* next; -}; - -struct node_and_int { - struct node* node; - int data; -}; - -/*@ -datatype Dll { - Empty_Dll {}, - Dll {datatype Seq_Node first, struct node n, datatype Seq_Node rest} -} - -datatype Seq_Node { - Seq_Node_Nil {}, - Seq_Node_Cons {struct node n, datatype seq tail} -} - -function (datatype Seq_Node) Dll_Rest (datatype Dll L) { - match L { - Empty_Dll {} => { Seq_Node_Nil {} } - Dll {first: _, n: _, rest: r} => { r } - } -} - -function (datatype Seq_Node) Dll_First(datatype Dll L) { - match L { - Empty_Dll {} => { Seq_Node_Nil {} } - Dll {first: f, n: _, rest: _} => { f } - } -} - -function (struct node) Dll_Node (datatype Dll L) { - match L { - Empty_Dll {} => { default } - Dll {first: _, n: n, rest: _} => { n } - } -} - -function (struct node) Seq_Node_Head(datatype Seq_Node S) { - match S { - Seq_Node_Nil {} => { default } - Seq_Node_Cons {n: n, tail: _} => { n } - } -} - -function (datatype seq) Seq_Node_Tail (datatype Seq_Node S) { - match S { - Seq_Node_Nil {} => { Seq_Nil {} } - Seq_Node_Cons {n: _, tail: t} => { t } - } -} - -function (datatype seq) flatten (datatype Dll L) { - match L { - Empty_Dll {} => { Seq_Nil {} } - Dll {first: f, n: n, rest: r} => { - match f { - Seq_Node_Nil {} => { - match r { - Seq_Node_Nil {} => { - Seq_Cons {head: n.data, tail: Seq_Nil {}} - } - Seq_Node_Cons {n: nextNode, tail: t} => { - Seq_Cons {head: n.data, tail: Seq_Cons{ head: nextNode.data, tail: t}} - } - } - } - Seq_Node_Cons {n: prevNode, tail: t} => { - match r { - Seq_Node_Nil {} => { - rev(Seq_Cons {head: n.data, tail: Seq_Cons {head: prevNode.data, tail: t}}) - } - Seq_Node_Cons {n: nextNode, tail: t2} => { - append(rev(Seq_Cons {head: prevNode.data, tail: t2}), Seq_Cons {head: n.data, tail: Seq_Cons{ head: nextNode.data, tail: t2}}) - } - } - } - } - } - } -} - -function (datatype seq) Seq_Node_to_Seq(datatype Seq_Node L) { - match L { - Seq_Node_Nil {} => { Seq_Nil {} } - Seq_Node_Cons {n: n, tail: t} => { Seq_Cons {head: n.data, tail: t } } - } -} - - -predicate (datatype Dll) LinkedList (pointer p) { - if (is_null(p)) { - return Empty_Dll{}; - } else { - take n = Owned(p); - take First = Own_Backwards(n.prev, p, n); - take Rest = Own_Forwards(n.next, p, n); - return Dll{first: First, n: n, rest: Rest}; - } -} - -predicate (datatype Seq_Node) Own_Forwards(pointer p, pointer prev_pointer, struct node prev_node) { - if (is_null(p)) { - return Seq_Node_Nil{}; - } else { - take n = Owned(p); - assert (ptr_eq(n.prev, prev_pointer)); - assert(ptr_eq(prev_node.next,p)); - take Rest = Own_Forwards_Aux(n.next, p, n); - return Seq_Node_Cons{n: n, tail: Rest}; - } -} - -predicate (datatype seq) Own_Forwards_Aux(pointer p, pointer prev_pointer, struct node prev_node) { - if (is_null(p)) { - return Seq_Nil{}; - } else { - take n = Owned(p); - assert (ptr_eq(n.prev, prev_pointer)); - assert(ptr_eq(prev_node.next, p)); - take Rest = Own_Forwards_Aux(n.next, p, n); - return Seq_Cons{head: n.data, tail: Rest}; - } -} - - - -predicate (datatype Seq_Node) Own_Backwards(pointer p, pointer next_pointer, struct node next_node) { - if (is_null(p)) { - return Seq_Node_Nil{}; - } else { - take n = Owned(p); - assert (ptr_eq(n.next, next_pointer)); - assert(ptr_eq(next_node.prev, p)); - take First = Own_Backwards_Aux(n.prev, p, n); - return Seq_Node_Cons{n: n, tail: First}; - } -} - -predicate (datatype seq) Own_Backwards_Aux(pointer p, pointer next_pointer, struct node next_node) { - if (is_null(p)) { - return Seq_Nil{}; - } else { - take n = Owned(p); - assert (ptr_eq(n.next, next_pointer)); - assert(ptr_eq(next_node.prev, p)); - take First = Own_Backwards_Aux(n.prev, p, n); - return Seq_Cons{head: n.data, tail: First}; - } -} -@*/ - -extern struct node *malloc_node(); -/*@ spec malloc_node(); - requires true; - ensures take u = Block(return); - !ptr_eq(return,NULL); -@*/ - -extern void free_node (struct node *p); -/*@ spec free_node(pointer p); - requires take u = Block(p); - ensures true; -@*/ - -extern struct node_and_int *malloc_node_and_int(); -/*@ spec malloc_node_and_int(); - requires true; - ensures take u = Block(return); - !ptr_eq(return,NULL); -@*/ - -extern void free_node_and_int (struct node_and_int *p); -/*@ spec free_node_and_int(pointer p); - requires take u = Block(p); - ensures true; -@*/ - -struct node *singleton(int element) -/*@ ensures take Ret = LinkedList(return); - Ret == Dll{first: Seq_Node_Nil{}, n: struct node{data: element, prev: NULL, next: NULL}, rest: Seq_Node_Nil{}}; -@*/ -{ - struct node *n = malloc_node(); - n->data = element; - n->prev = 0; - n->next = 0; - return n; -} - -// Adds after the given node -// TODO: fix correctness checks -struct node *add(int element, struct node *n) -/*@ requires take L = LinkedList(n); - let node = Dll_Node(L); - let First = Dll_First(L); - let Rest = Dll_Rest(L); - ensures take L_ = LinkedList(return); - let First_ = Dll_First(L_); - let Rest_ = Dll_Rest(L_); - let new_node = Dll_Node(L_); - - ptr_eq(new_node.prev, n); - let node_ = Seq_Node_Head(First_); - !is_null(n) implies ptr_eq(node_.next, return); - !is_null(n) implies ptr_eq(new_node.next, node.next); - !is_null(return); - - - !is_null(n) implies Seq_Node_to_Seq(First_) == Seq_Cons { head: node.data, tail: Seq_Node_to_Seq(First)}; - Seq_Node_to_Seq(Rest) == Seq_Node_to_Seq(Rest_); -@*/ -{ - struct node *new_node = malloc_node(); - new_node->data = element; - new_node->prev = 0; - new_node->next = 0; - - if (n == 0) //empty list case - { - new_node->prev = 0; - new_node->next = 0; - return new_node; - } else { - /*@ split_case(is_null((*n).next)); @*/ - /*@ split_case(is_null((*n).prev)); @*/ - - - new_node->next = n->next; - new_node->prev = n; - - if (n->next !=0) { - /*@ split_case(is_null((*(*n).next).next)); @*/ - n->next->prev = new_node; - } - - n->next = new_node; - return new_node; - } -} - - -// Appends `second` to the end of `first`, where `first` is the tail of the first list and -// `second` is the head of the second list. -// TODO: fix so that any nodes can be passed in, not just head and tail -struct node *append (struct node *first, struct node *second) -/*@ requires take n1 = Owned(first); - take n2 = Owned(second); - take L = Own_Backwards(n1.prev, first, n1); - take R = Own_Forwards(n2.next, second, n2); - is_null(n1.next) && is_null(n2.prev); - ensures take n1_ = Owned(first); - take n2_ = Owned(second); - take L_ = Own_Backwards(n1.prev, first, n1); - take R_ = Own_Forwards(n2.next, second, n2); - ptr_eq(n1_.next,second) && ptr_eq(n2_.prev, first); - L == L_ && R == R_; -@*/ -{ - first->next = second; - second->prev = first; - - return first; -} - -// removes the given node from the list and returns another pointer -// to somewhere in the list, or a null pointer if the list is empty. -struct node_and_int *remove(struct node *n) -/*@ requires !is_null(n); - take del = Owned(n); - take First = Own_Backwards(del.prev, n, del); - take Rest = Own_Forwards(del.next, n, del); - ensures take ret = Owned(return); - take L = LinkedList(ret.node); - let First_ = Dll_First(L); - let Rest_ = Dll_Rest(L); - let node = Dll_Node(L); - Seq_Node_to_Seq(First_ )== Seq_Node_to_Seq(First) || Seq_Node_to_Seq(Rest_) == Seq_Node_to_Seq(Rest); - !is_null(ret.node) implies (Seq_Node_to_Seq(First_ ) == Seq_Node_to_Seq(First) implies Seq_Node_to_Seq(Rest) == Seq_Cons{head: node.data, tail: Seq_Node_to_Seq(Rest_)}); - - !is_null(ret.node) implies (Seq_Node_to_Seq(Rest_ ) == Seq_Node_to_Seq(Rest) implies Seq_Node_to_Seq(First) == Seq_Cons{head: node.data, tail: Seq_Node_to_Seq(First_)}); - - Seq_Node_to_Seq(First) == Seq_Cons{head: node.data, tail: Seq_Node_to_Seq(First_)} || Seq_Node_to_Seq(Rest) == Seq_Cons{head: node.data, tail: Seq_Node_to_Seq(Rest_)} || (Seq_Node_to_Seq(First) == Seq_Nil{} && Seq_Node_to_Seq(Rest) == Seq_Nil{}); - - // flatten(l) == append(rev(nodeSeqtoSeq(first)), nodeSeqtoSeq(rest)); - -@*/ -{ - if (n == 0) { //empty list case - struct node_and_int *pair = malloc_node_and_int(); - pair->node = 0; //null pointer - pair->data = 0; - return pair; - } else { - struct node *temp = 0; - if (n->prev != 0) { - /*@ split_case(is_null((*(*n).prev).prev)); @*/ - - n->prev->next = n->next; - temp = n->prev; - } - if (n->next != 0) { - /*@ split_case(is_null((*(*n).next).next)); @*/ - n->next->prev = n->prev; - temp = n->next; - } - - struct node_and_int *pair = malloc_node_and_int(); - pair->node = temp; - pair->data = n->data; - - free_node(n); - return pair; - } -} - -struct node *find_head_aux(struct node *n) -/*@ requires !is_null(n); - take node = Owned(n); - take L = Own_Backwards(node.prev, n, node); - ensures take node_ = Owned(n); - take L_ = Own_Backwards(node_.prev, n, node_); - node == node_; - L == L_; -@*/ -{ - /*@ split_case(is_null(n)); @*/ - /*@ split_case(is_null((*n).prev)); @*/ - if (n->prev == 0) - { - return n; - } else { - /*@ split_case(is_null((*(*n).prev).prev)); @*/ - return find_head_aux(n->prev); - } -} - -// Takes any node in the list and returns the head of the list -// TODO: correctness check -struct node *find_head(struct node *n) -/*@ requires take L = LinkedList(n); - ensures take L_ = LinkedList(n); - L == L_; -@*/ -{ - /*@ split_case(is_null(n)); @*/ - if (n == 0) - { - return 0; - } else { - /*@ split_case(is_null((*n).prev)); @*/ - return find_head_aux(n); - } -} - - -struct node *find_tail_aux(struct node *n) -/*@ requires !is_null(n); - take node = Owned(n); - take L = Own_Forwards(node.next, n, node); - ensures take node_ = Owned(n); - take L_ = Own_Forwards(node_.next, n, node_); - node == node_; - L == L_; -@*/ -{ - /*@ split_case(is_null(n)); @*/ - /*@ split_case(is_null((*n).next)); @*/ - if (n->next == 0) - { - return n; - } else { - /*@ split_case(is_null((*(*n).next).next)); @*/ - return find_tail_aux(n->next); - } -} - -// Takes any node in the list and returns the tail of the list -// TODO: correctness check -struct node *findTail(struct node *n) -/*@ requires take L = LinkedList(n); - ensures take L_ = LinkedList(n); - L == L_; -@*/ -{ - /*@ split_case(is_null(n)); @*/ - if (n == 0) - { - return 0; - } else { - /*@ split_case(is_null((*n).next)); @*/ - return find_tail_aux(n); - } -} diff --git a/src/examples/Linked_List/append.c b/src/examples/Linked_List/append.c deleted file mode 100644 index 11700b42..00000000 --- a/src/examples/Linked_List/append.c +++ /dev/null @@ -1,24 +0,0 @@ -#include "./headers.h" - -// Appends `second` to the end of `first`, where `first` is the tail of the first list and -// `second` is the head of the second list. -// TODO: fix so that any nodes can be passed in, not just head and tail -struct node *append (struct node *first, struct node *second) -/*@ requires take n1 = Owned(first); - take n2 = Owned(second); - take L = Own_Backwards(n1.prev, first, n1); - take R = Own_Forwards(n2.next, second, n2); - is_null(n1.next) && is_null(n2.prev); - ensures take n1_ = Owned(first); - take n2_ = Owned(second); - take L_ = Own_Backwards(n1.prev, first, n1); - take R_ = Own_Forwards(n2.next, second, n2); - ptr_eq(n1_.next,second) && ptr_eq(n2_.prev, first); - L == L_ && R == R_; -@*/ -{ - first->next = second; - second->prev = first; - - return first; -} \ No newline at end of file diff --git a/src/examples/Linked_List/converters.h b/src/examples/Linked_List/converters.h deleted file mode 100644 index f96e8f37..00000000 --- a/src/examples/Linked_List/converters.h +++ /dev/null @@ -1,10 +0,0 @@ -/*@ -function (datatype seq) flatten (datatype Dll L) { - match L { - Empty_Dll {} => { Seq_Nil {} } - Dll {left: l, curr: n, right: r} => { - append(l, Seq_Cons {head: n.data, tail: r}) - } - } -} -@*/ \ No newline at end of file diff --git a/src/examples/Linked_List/head_tail.c b/src/examples/Linked_List/head_tail.c deleted file mode 100644 index 6485b42e..00000000 --- a/src/examples/Linked_List/head_tail.c +++ /dev/null @@ -1,84 +0,0 @@ -#include "headers.h" - -// TODO: currently the correctness checks ensure that the list is -// unchanged, but I would also like to specify that the function -// correctly finds the head or tail, respectively. - -struct node *find_head_aux(struct node *n) -/*@ requires !is_null(n); - take node = Owned(n); - take L = Own_Backwards(node.prev, n, node); - ensures take node_ = Owned(n); - take L_ = Own_Backwards(node_.prev, n, node_); - node == node_; - L == L_; -@*/ -{ - /*@ split_case(is_null(n)); @*/ - /*@ split_case(is_null((*n).prev)); @*/ - if (n->prev == 0) - { - return n; - } else { - /*@ split_case(is_null((*(*n).prev).prev)); @*/ - return find_head_aux(n->prev); - } -} - -// Takes any node in the list and returns the head of the list -// TODO: correctness check -struct node *find_head(struct node *n) -/*@ requires take L = Dll_at(n); - ensures take L_ = Dll_at(n); - L == L_; -@*/ -{ - /*@ split_case(is_null(n)); @*/ - if (n == 0) - { - return 0; - } else { - /*@ split_case(is_null((*n).prev)); @*/ - return find_head_aux(n); - } -} - - -struct node *find_tail_aux(struct node *n) -/*@ requires !is_null(n); - take node = Owned(n); - take L = Own_Forwards(node.next, n, node); - ensures take node_ = Owned(n); - take L_ = Own_Forwards(node_.next, n, node_); - node == node_; - L == L_; -@*/ -{ - /*@ split_case(is_null(n)); @*/ - /*@ split_case(is_null((*n).next)); @*/ - if (n->next == 0) - { - return n; - } else { - /*@ split_case(is_null((*(*n).next).next)); @*/ - return find_tail_aux(n->next); - } -} - -// Takes any node in the list and returns the tail of the list -// TODO: correctness check -struct node *findTail(struct node *n) -/*@ requires take L = Dll_at(n); - ensures take L_ = Dll_at(n); - L == L_; -@*/ -{ - /*@ split_case(is_null(n)); @*/ - if (n == 0) - { - return 0; - } else { - /*@ split_case(is_null((*n).next)); @*/ - return find_tail_aux(n); - } -} \ No newline at end of file From 16b7594c2d9d06ce1c764dc45d55edb30b7d0edf Mon Sep 17 00:00:00 2001 From: Liz Austell Date: Tue, 16 Jul 2024 15:17:39 -0400 Subject: [PATCH 081/152] make add spec more concise --- src/examples/Dbl_Linked_List/add.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/examples/Dbl_Linked_List/add.c b/src/examples/Dbl_Linked_List/add.c index 01c1cd4c..cd0805c8 100644 --- a/src/examples/Dbl_Linked_List/add.c +++ b/src/examples/Dbl_Linked_List/add.c @@ -3,13 +3,10 @@ // Adds after the given node and returns a pointer to the new node struct node *add(int element, struct node *n) /*@ requires take L = Dll_at(n); - let n_node = Node(L); - let Left = Left(L); - let Right = Right(L); ensures take L_ = Dll_at(return); let new_node = Node(L_); is_null(n) ? L_ == Dll { left: Seq_Nil{}, curr: new_node, right: Seq_Nil{}} - : L_ == Dll { left: Seq_Cons{head: n_node.data, tail: Left}, curr: new_node, right: Right}; + : L_ == Dll { left: Seq_Cons{head: Node(L).data, tail: Left(L)}, curr: new_node, right: Right(L)}; @*/ { struct node *new_node = malloc_node(); From 3dd4f71d0cf86b44dce66598770d25f07fe4fc5c Mon Sep 17 00:00:00 2001 From: Liz Austell Date: Tue, 16 Jul 2024 16:31:20 -0400 Subject: [PATCH 082/152] change spec identifiers to Before and After --- src/examples/Dbl_Linked_List/add.c | 12 +++++------- src/examples/Dbl_Linked_List/remove.c | 14 +++++++------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/examples/Dbl_Linked_List/add.c b/src/examples/Dbl_Linked_List/add.c index cd0805c8..dd6f4188 100644 --- a/src/examples/Dbl_Linked_List/add.c +++ b/src/examples/Dbl_Linked_List/add.c @@ -2,11 +2,11 @@ // Adds after the given node and returns a pointer to the new node struct node *add(int element, struct node *n) -/*@ requires take L = Dll_at(n); - ensures take L_ = Dll_at(return); - let new_node = Node(L_); - is_null(n) ? L_ == Dll { left: Seq_Nil{}, curr: new_node, right: Seq_Nil{}} - : L_ == Dll { left: Seq_Cons{head: Node(L).data, tail: Left(L)}, curr: new_node, right: Right(L)}; +/*@ requires take Before = Dll_at(n); + ensures take After = Dll_at(return); + + is_null(n) ? After == Dll { left: Seq_Nil{}, curr: Node(After), right: Seq_Nil{}} + : After == Dll { left: Seq_Cons{head: Node(Before).data, tail: Left(Before)}, curr: Node(After), right: Right(Before)}; @*/ { struct node *new_node = malloc_node(); @@ -20,10 +20,8 @@ struct node *add(int element, struct node *n) new_node->next = 0; return new_node; } else { - /*@ split_case(is_null((*n).next)); @*/ /*@ split_case(is_null((*n).prev)); @*/ - new_node->next = n->next; new_node->prev = n; diff --git a/src/examples/Dbl_Linked_List/remove.c b/src/examples/Dbl_Linked_List/remove.c index 713cceb3..d7315661 100644 --- a/src/examples/Dbl_Linked_List/remove.c +++ b/src/examples/Dbl_Linked_List/remove.c @@ -4,18 +4,18 @@ // to somewhere in the list, or a null pointer if the list is empty. struct node_and_int *remove(struct node *n) /*@ requires !is_null(n); - take before = Dll_at(n); - let del = Node(before); + take Before = Dll_at(n); + let del = Node(Before); ensures take ret = Owned(return); - take after = Dll_at(ret.node); - (is_null(del.prev) && is_null(del.next)) ? after == Empty_Dll{} - : (!is_null(del.next) ? after == Dll{left: Left(before), curr: Node(after), right: tl(Right(before))} - : after == Dll{left: tl(Left(before)), curr: Node(after), right: Right(before)}); + take After = Dll_at(ret.node); + (is_null(del.prev) && is_null(del.next)) ? After == Empty_Dll{} + : (!is_null(del.next) ? After == Dll{left: Left(Before), curr: Node(After), right: tl(Right(Before))} + : After == Dll{left: tl(Left(Before)), curr: Node(After), right: Right(Before)}); @*/ { if (n == 0) { //empty list case struct node_and_int *pair = malloc_node_and_int(); - pair->node = 0; //null pointer + pair->node = 0; pair->data = 0; return pair; } else { From 2431d5cb4e1f00b1f7055197db82f33a15bb6422 Mon Sep 17 00:00:00 2001 From: Liz Austell Date: Tue, 16 Jul 2024 18:15:46 -0400 Subject: [PATCH 083/152] add doubly linked list to tutorial --- src/examples/Dbl_Linked_List/add.c | 1 - .../Dbl_Linked_List/add_orig.broken.c | 27 ++++ src/examples/Dbl_Linked_List/c_types.h | 5 - src/examples/Dbl_Linked_List/malloc_free.h | 13 -- src/examples/Dbl_Linked_List/node_and_int.h | 17 +++ src/examples/Dbl_Linked_List/remove.c | 42 +++--- .../Dbl_Linked_List/remove_orig.broken.c | 24 +++ src/tutorial.adoc | 140 ++++++++++++++++++ 8 files changed, 226 insertions(+), 43 deletions(-) create mode 100644 src/examples/Dbl_Linked_List/add_orig.broken.c create mode 100644 src/examples/Dbl_Linked_List/node_and_int.h create mode 100644 src/examples/Dbl_Linked_List/remove_orig.broken.c diff --git a/src/examples/Dbl_Linked_List/add.c b/src/examples/Dbl_Linked_List/add.c index dd6f4188..0bd50510 100644 --- a/src/examples/Dbl_Linked_List/add.c +++ b/src/examples/Dbl_Linked_List/add.c @@ -21,7 +21,6 @@ struct node *add(int element, struct node *n) return new_node; } else { /*@ split_case(is_null((*n).prev)); @*/ - new_node->next = n->next; new_node->prev = n; diff --git a/src/examples/Dbl_Linked_List/add_orig.broken.c b/src/examples/Dbl_Linked_List/add_orig.broken.c new file mode 100644 index 00000000..f8b9b306 --- /dev/null +++ b/src/examples/Dbl_Linked_List/add_orig.broken.c @@ -0,0 +1,27 @@ +#include "./headers.h" + +// Adds after the given node and returns a pointer to the new node +struct node *add(int element, struct node *n) +{ + struct node *new_node = malloc_node(); + new_node->data = element; + new_node->prev = 0; + new_node->next = 0; + + if (n == 0) //empty list case + { + new_node->prev = 0; + new_node->next = 0; + return new_node; + } else { + new_node->next = n->next; + new_node->prev = n; + + if (n->next !=0) { + n->next->prev = new_node; + } + + n->next = new_node; + return new_node; + } +} \ No newline at end of file diff --git a/src/examples/Dbl_Linked_List/c_types.h b/src/examples/Dbl_Linked_List/c_types.h index 830a8923..2e3e7f16 100644 --- a/src/examples/Dbl_Linked_List/c_types.h +++ b/src/examples/Dbl_Linked_List/c_types.h @@ -2,9 +2,4 @@ struct node { int data; struct node* prev; struct node* next; -}; - -struct node_and_int { - struct node* node; - int data; }; \ No newline at end of file diff --git a/src/examples/Dbl_Linked_List/malloc_free.h b/src/examples/Dbl_Linked_List/malloc_free.h index df919dca..1937a5a3 100644 --- a/src/examples/Dbl_Linked_List/malloc_free.h +++ b/src/examples/Dbl_Linked_List/malloc_free.h @@ -9,17 +9,4 @@ extern void free_node (struct node *p); /*@ spec free_node(pointer p); requires take u = Block(p); ensures true; -@*/ - -extern struct node_and_int *malloc_node_and_int(); -/*@ spec malloc_node_and_int(); - requires true; - ensures take u = Block(return); - !ptr_eq(return,NULL); -@*/ - -extern void free_node_and_int (struct node_and_int *p); -/*@ spec free_node_and_int(pointer p); - requires take u = Block(p); - ensures true; @*/ \ No newline at end of file diff --git a/src/examples/Dbl_Linked_List/node_and_int.h b/src/examples/Dbl_Linked_List/node_and_int.h new file mode 100644 index 00000000..faef87c7 --- /dev/null +++ b/src/examples/Dbl_Linked_List/node_and_int.h @@ -0,0 +1,17 @@ +struct node_and_int { + struct node* node; + int data; +}; + +extern struct node_and_int *malloc_node_and_int(); +/*@ spec malloc_node_and_int(); + requires true; + ensures take u = Block(return); + !ptr_eq(return,NULL); +@*/ + +extern void free_node_and_int (struct node_and_int *p); +/*@ spec free_node_and_int(pointer p); + requires take u = Block(p); + ensures true; +@*/ \ No newline at end of file diff --git a/src/examples/Dbl_Linked_List/remove.c b/src/examples/Dbl_Linked_List/remove.c index d7315661..21199609 100644 --- a/src/examples/Dbl_Linked_List/remove.c +++ b/src/examples/Dbl_Linked_List/remove.c @@ -1,4 +1,5 @@ #include "./headers.h" +#include "./node_and_int.h" // removes the given node from the list and returns another pointer // to somewhere in the list, or a null pointer if the list is empty. @@ -8,35 +9,28 @@ struct node_and_int *remove(struct node *n) let del = Node(Before); ensures take ret = Owned(return); take After = Dll_at(ret.node); + ret.data == del.data; (is_null(del.prev) && is_null(del.next)) ? After == Empty_Dll{} : (!is_null(del.next) ? After == Dll{left: Left(Before), curr: Node(After), right: tl(Right(Before))} : After == Dll{left: tl(Left(Before)), curr: Node(After), right: Right(Before)}); @*/ { - if (n == 0) { //empty list case - struct node_and_int *pair = malloc_node_and_int(); - pair->node = 0; - pair->data = 0; - return pair; - } else { - struct node *temp = 0; - if (n->prev != 0) { - /*@ split_case(is_null((*(*n).prev).prev)); @*/ - - n->prev->next = n->next; - temp = n->prev; - } - if (n->next != 0) { - /*@ split_case(is_null((*(*n).next).next)); @*/ - n->next->prev = n->prev; - temp = n->next; - } + struct node *temp = 0; + if (n->prev != 0) { + /*@ split_case(is_null((*(*n).prev).prev)); @*/ + n->prev->next = n->next; + temp = n->prev; + } + if (n->next != 0) { + /*@ split_case(is_null((*(*n).next).next)); @*/ + n->next->prev = n->prev; + temp = n->next; + } - struct node_and_int *pair = malloc_node_and_int(); - pair->node = temp; - pair->data = n->data; + struct node_and_int *pair = malloc_node_and_int(); + pair->node = temp; + pair->data = n->data; - free_node(n); - return pair; - } + free_node(n); + return pair; } \ No newline at end of file diff --git a/src/examples/Dbl_Linked_List/remove_orig.broken.c b/src/examples/Dbl_Linked_List/remove_orig.broken.c new file mode 100644 index 00000000..b2617c69 --- /dev/null +++ b/src/examples/Dbl_Linked_List/remove_orig.broken.c @@ -0,0 +1,24 @@ +#include "./headers.h" +#include "./node_and_int.h" + +// removes the given node from the list and returns another pointer +// to somewhere in the list, or a null pointer if the list is empty. +struct node_and_int *remove(struct node *n) +{ + struct node *temp = 0; + if (n->prev != 0) { + n->prev->next = n->next; + temp = n->prev; + } + if (n->next != 0) { + n->next->prev = n->prev; + temp = n->next; + } + + struct node_and_int *pair = malloc_node_and_int(); + pair->node = temp; + pair->data = n->data; + + free_node(n); + return pair; +} \ No newline at end of file diff --git a/src/tutorial.adoc b/src/tutorial.adoc index 1a8447f0..ff04711e 100644 --- a/src/tutorial.adoc +++ b/src/tutorial.adoc @@ -1263,6 +1263,146 @@ lemma before the `+return+` will be a bit more complicated. Note: Again, this has not been shown to be possible, but Dhruv believes it should be! +=== Doubly Linked Lists + +A doubly linked list is a linked list where each node has a pointer +to both the next node and the previous node. This allows for O(1) +operations for adding or removing nodes anywhere in the list. Here is +the C type definition: + +include_example(exercises/Dbl_Linked_List/c_types.h) + +The idea behind the representation of this list is that we don't keep +track of the front or back, but rather we take any node in the list +and have a sequence to the left and to the right of that node. The `left` +and `right` are from the point of view of the node itself, so `left` +is kept in reverse order. Additionally, similarly to in the +`Imperative Queues` example, we can reuse the `+seq+` type. + +include_example(exercises/Dbl_Linked_List/cn_types.h) + +The predicate for this datatype is a bit complicated. The idea is that +we first want to own the node that is passed in. Then, we want to +follow all of the `prev` pointers to own everything backwards from the +node. We want to do the same for the `next` pointers to own everything +forwards from the node. This is how we construct our `left` and `right` +fields. + +include_example(exercises/Dbl_Linked_List/predicates.h) + +Note that `Dll_at` takes ownership of the node passed in, and then +calls `Own_Backwards` and `Own_Forwards` which recursively take +ownership of the rest of the list and add their values to the `left` +and `right` sequences, respectively. + +Additionally, you will notice that `Own_Forwards` and `Own_Backwards` +include `ptr_eq` assertions for the `prev` and `next` pointers. This +is to ensure that the nodes in the list are correctly +doubly linked. For example, the line +`assert (ptr_eq(n.prev, prev_pointer));` in `Own_Forwards` ensures +that the current node correctly points backward to the previous node in the +list. The line `assert(ptr_eq(prev_node.next, p));` ensures that the +previous node correctly points forward to the current node. The same can be +said for these assertions in `Own_Backwards`. + +All three of these predicates stop once they reach a null pointer. In +this way, we can ensure that the only null pointers in the list are at +the beginning and end of the list. + +Before we move on to the functions that manipulate the doubly linked +list, we need to define a few "getter" functions that will allow us +to access the fields of our `Dll` datatype. This will make our +specifications much easier to write. + +include_example(exercises/Dbl_Linked_List/getters.h) + +We also must include some boilerplate code for allocation and +deallocation. + +include_example(exercises/Dbl_Linked_List/malloc_free.h) + +And we compile all of these files into a single header file. + +include_example(exercises/Dbl_Linked_List/headers.h) + +Lastly, an important note about this representation of a doubly linked list is that there is no higher level representation of the list (such as the `int_queue` structure in the `Imperative Queues` section). This makes it difficult to reason about adding and removing things from a list that may be empty at some times. If we have an empty list, we do not want any identifier of this list to disappear altogether. To work around this problem, we represent an empty list as a null pointer and require that every function that manipulates the list must return a pointer to somewhere in the list. This way, we can always have a pointer to the list, even if it is empty. + +// ====================================================================== + +Now we can move on to an initialization function. Since an empty list is represented as a null pointer, we will look at initializing +a singleton list (or in other words, a list with only one item). + +include_example(exercises/Dbl_Linked_List/singleton.c) + +// ====================================================================== + +The `add` and `remove` functions are where it gets a little tricker. +Let's start with `add`. Here is the unannotated version: + +include_example(exercises/Dbl_Linked_List/add_orig.broken.c) + +*Exercise*: Before reading on, see if you can figure out what specifications are needed. + +Now, here is the annotated version of the `add` operation: + +include_example(exercises/Dbl_Linked_List/add.c) + +First, let's look at the pre and post conditions. The `requires` +clause is straightforward. We need to own the list centered around +the node that `n` points to. `Before` is a `Dll` +that is either empty, or it has a seq to the left, +the current node that `n` points to, and a seq to the right. +This corresponds to the state of the list when it is passed in. + +In the ensures clause, we again establish ownership of the list, but this time it is centered around the added node. This means that `After` is a `Dll` structure similar to `Before`, except that the node `curr` is +now the created node. The old `curr` is pushed into the +left part of the new list. The ternary operator in the `ensures` clause is saying that if the list was empty +coming in, it now is a singleton list. Otherwise, the left left part of the list now has the data from the old `curr` node, the new `curr` node is the added node, +and the right part of the list is the same as before. + +Now, let's look at the annotations in the function body. +CN can figure out the empty list case for itself, but it needs some help with the non-empty list case. The +`split_case` on `is_null((\*n).prev)` tells CN to unpack the `Own_Backwards` predicate. Without this annotation, +CN cannot reason that we didn't lose the left half of the list before we return, and will claim we are missing a resource for returning. The `split_case` on `is_null((*(*n).next).next)` is similar, but for unpacking the `Own_Forwards` predicate. Note that we +have to go one more node forward to make sure that everything past `n->next` is still owned at the end of the function. + + +Now let's look at the `remove` operation. Traditionally, a `remove` operation for a list returns the integer that was removed. However we also want all of our functions to return a pointer to the list. Because of this, we define a `+struct+` that includes an `int` and a `node`. + +include_example(exercises/Dbl_Linked_List/node_and_int.h) + +Now we can look at the code for the `remove` operation. Here is the un-annotated version: + +include_example(exercises/Dbl_Linked_List/remove_orig.broken.c) + +*Exercise*: Before reading on, see if you can figure out what specifications are needed. + +Now, here is the fully annotated version of the `remove` operation: + +include_example(exercises/Dbl_Linked_List/remove.c) + +First, let's look at the pre and post conditions. The `requires` clause says that we cannot remove a node from an empty list, so the pointer passed in must not be null. Then we take ownership of the list, and we +assign the node of that list to the identifier `del` +to make our spec more readable. So `Before` refers to the `Dll` when the function is called, and `del` refers to the node that will be deleted. + +Then in the `ensures` clause, we must take ownership +of the `node_and_int` struct as well as the `Dll` that +the node is part of. Here, `After` refers to the `Dll` +when the function returns. We ensure that the int that is returned is the value of the deleted node, as intended. Then we have a complicated nested ternary conditional that ensures that `After` is the same as `Before` except for the deleted node. Let's break down this conditional: + +- The first guard asks if both `del.prev` and `del.next` are null. In this case, we are removing the only node in the list, so the resulting list will be empty. The `else` branch of this conditional contains it's own conditional. + +- For the following conditional, the guard checks if 'del.prev' is NOT null. Note that in the code, this means that the returned node is `del.next`, regardless of whether or not `del.prev` is null. If this is the case, `After` is now centered around `del.next`, and the left part of the list is the same as before. Since `del.next` was previously the head of the right side, the right side loses its head in `After`. This is where we get `After == Dll{left: Left(Before), curr: Node(After), right: tl(Right(Before))}`. + +- The final `else` branch is the case where `del.next` is null, but `del.prev` is not null. In this case, the returned node is `del.prev`. This branch follows the same logic as the one before it, except now we are taking the head of the left side rather than the right side. Now the right side is unchanged, and the left side is just the tail, as seen shown in +`After == Dll{left: tl(Left(Before)), curr: Node(After), right: Right(Before)};` + +Now, let's look at the annotations in the function body. These are similar to in the `add` function. Both of these `split_case` annotations are needed to unpack the `Own_Forwards` and `Own_Backwards` predicates. Without these annotations, CN will not be able to reason that we didn't lose the left or right half of the list before we return, and will claim we are missing a resource for returning. + +// ====================================================================== + +*Exercise*: There are many other functions that one might want to implement for a doubly linked list. For example, one might want to implement a function that appends one list to another, or a function that reverses a list. Try implementing these functions and writing their specifications. + //// === The Bridge Controller From 5419725ad2f86425fefe777e95c658d20f4fdf67 Mon Sep 17 00:00:00 2001 From: Christopher Pulte Date: Wed, 17 Jul 2024 11:34:56 +0100 Subject: [PATCH 084/152] gitignore ./check --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 2a39c64d..098ccd9c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ build/* -/.vscode/ \ No newline at end of file +/.vscode/ +check \ No newline at end of file From f159166341fb664a2d28af2f55afd02820a0409b Mon Sep 17 00:00:00 2001 From: Dhruv Makwana Date: Tue, 16 Jul 2024 18:26:16 +0100 Subject: [PATCH 085/152] Update tutorial and examples for Owned disjoint https://github.com/rems-project/cerberus/pull/385 re-introduced deriving disjointness and non-null constraints for pairs of Owned resources in the context, and so some work-arounds in the tutorial can now be eliminated. --- src/examples/list_c_types.h | 3 +-- src/examples/queue_allocation.h | 2 -- src/examples/queue_push_induction.c | 39 ++++++++++++----------------- src/tutorial.adoc | 9 ------- 4 files changed, 17 insertions(+), 36 deletions(-) diff --git a/src/examples/list_c_types.h b/src/examples/list_c_types.h index 7fe13ebd..c9b6d7a0 100644 --- a/src/examples/list_c_types.h +++ b/src/examples/list_c_types.h @@ -7,8 +7,7 @@ extern struct int_list *mallocIntList(); /*@ spec mallocIntList(); requires true; ensures take u = Block(return); - !ptr_eq(return, NULL); -@*/ // 'return != NULL' should not be needed +@*/ extern void freeIntList (struct int_list *p); /*@ spec freeIntList(pointer p); diff --git a/src/examples/queue_allocation.h b/src/examples/queue_allocation.h index ee222de8..6415601f 100644 --- a/src/examples/queue_allocation.h +++ b/src/examples/queue_allocation.h @@ -2,7 +2,6 @@ extern struct int_queue *mallocIntQueue(); /*@ spec mallocIntQueue(); requires true; ensures take u = Block(return); - !ptr_eq(return,NULL); @*/ extern void freeIntQueue (struct int_queue *p); @@ -15,7 +14,6 @@ extern struct int_queueCell *mallocIntQueueCell(); /*@ spec mallocIntQueueCell(); requires true; ensures take u = Block(return); - !is_null(return); @*/ extern void freeIntQueueCell (struct int_queueCell *p); diff --git a/src/examples/queue_push_induction.c b/src/examples/queue_push_induction.c index 38f0a161..9d0f897a 100644 --- a/src/examples/queue_push_induction.c +++ b/src/examples/queue_push_induction.c @@ -1,33 +1,27 @@ #include "queue_headers.h" -/*@ -lemma assert_not_equal(pointer x, pointer y) -requires - true; -ensures - !ptr_eq(x, y); -@*/ - -void push_induction(struct int_queueCell* front, struct int_queueCell* p) +void push_induction(struct int_queueCell* front + , struct int_queueCell* second_last + , struct int_queueCell* last) /*@ requires - take Q = IntQueueAux(front, p); - take P = Owned(p); - !ptr_eq(front, P.next); - !is_null(P.next); + take Q = IntQueueAux(front, second_last); + take Second_last = Owned(second_last); + ptr_eq(Second_last.next, last); + take Last = Owned(last); ensures - take NewQ = IntQueueAux(front, P.next); - NewQ == snoc(Q, P.first); + take NewQ = IntQueueAux(front, last); + take Last2 = Owned(last); + NewQ == snoc(Q, Second_last.first); + Last == Last2; @*/ { - if (front == p) { - /*@ unfold snoc(Q, P.first); @*/ + if (front == second_last) { + /*@ unfold snoc(Q, Second_last.first); @*/ return; } else { - // Should be derived automatically - /*@ apply assert_not_equal((*front).next, (*p).next); @*/ - push_induction(front->next, p); - /*@ unfold snoc(Q, P.first); @*/ + push_induction(front->next, second_last, last); + /*@ unfold snoc(Q, Second_last.first); @*/ return; } } @@ -39,7 +33,6 @@ void IntQueue_push (int x, struct int_queue *q) @*/ { struct int_queueCell *c = mallocIntQueueCell(); - /*@ apply assert_not_equal((*q).front, c); @*/ c->first = x; c->next = 0; if (q->back == 0) { @@ -50,7 +43,7 @@ void IntQueue_push (int x, struct int_queue *q) struct int_queueCell *oldback = q->back; q->back->next = c; q->back = c; - push_induction(q->front, oldback); + push_induction(q->front, oldback, c); return; } } diff --git a/src/tutorial.adoc b/src/tutorial.adoc index ff04711e..2dd42bd5 100644 --- a/src/tutorial.adoc +++ b/src/tutorial.adoc @@ -715,15 +715,6 @@ in the pre- and postconditions that captures both cases together: include_example(exercises/slf_incr2.c) -**Note**: At the moment, CN does not derive pointer disjointness -constraints from resources: from simultaneous ownership of the -resources `+Owned(p)+` and `+Owned(q)+` CN does not automatically -learn `+(p != q)+`, even though that’s clearly implied. This was -turned off for performance reasons at some point, but once -performance is back to normal again it should come back. In the mean -time, we have to add `+(p != q)+` as an additional precondition to -`+call_both+`. - == Allocating and Deallocating Memory At the moment, CN does not understand the `+malloc+` and `+free+` From 24d574802c452c0b87c69f1591de42a6e1dfa340 Mon Sep 17 00:00:00 2001 From: Dhruv Makwana Date: Thu, 18 Jul 2024 13:29:13 +0100 Subject: [PATCH 086/152] Temporarily delete division crash files https://github.com/rems-project/cerberus/pull/354 implements division so these tests no longer crash. As such, to keep the CI working, this commit temporarily deletes these files (and will re-add them once the PR is merged in). --- .../broken/error-crash/00064.err125.c | 13 ---- .../broken/error-crash/binary_search.c | 49 ------------- .../broken/error-crash/00004_exceptions.c | 71 ------------------- .../broken/error-crash/division_crash_1.c | 1 - 4 files changed, 134 deletions(-) delete mode 100644 src/example-archive/c-testsuite/broken/error-crash/00064.err125.c delete mode 100644 src/example-archive/dafny-tutorial/broken/error-crash/binary_search.c delete mode 100644 src/example-archive/java_program_verification_challenges/broken/error-crash/00004_exceptions.c delete mode 100644 src/example-archive/simple-examples/broken/error-crash/division_crash_1.c diff --git a/src/example-archive/c-testsuite/broken/error-crash/00064.err125.c b/src/example-archive/c-testsuite/broken/error-crash/00064.err125.c deleted file mode 100644 index 177f8d41..00000000 --- a/src/example-archive/c-testsuite/broken/error-crash/00064.err125.c +++ /dev/null @@ -1,13 +0,0 @@ -/* -cn: internal error, uncaught exception: - Failure("todo") -*/ -// Cause: `/` operator - -#define X 6 / 2 - -int -main() -{ - return X - 3; -} diff --git a/src/example-archive/dafny-tutorial/broken/error-crash/binary_search.c b/src/example-archive/dafny-tutorial/broken/error-crash/binary_search.c deleted file mode 100644 index e83786a4..00000000 --- a/src/example-archive/dafny-tutorial/broken/error-crash/binary_search.c +++ /dev/null @@ -1,49 +0,0 @@ -// Binary search algorithm. The functional correctness of this algorithm depends -// on the array being sorted - -int binary_search(int *a, int length, int value) -/*@ requires - let MAXi32 = (i64) 2147483647i64; // TODO: lift to library - - 0i32 <= length; - (2i64 * (i64) length) <= MAXi32; - take IndexPre = each (i32 j; 0i32 <= j && j < length) - {Owned(a + j)}; @*/ -/*@ ensures - take IndexPost = each (i32 j; 0i32 <= j && j < length) - {Owned(a + j)}; - IndexPost == IndexPre; - (return < 0i32) || (IndexPost[return] == value); @*/ -{ - int low = 0; - int high = length; - - while (low < high) - /*@ inv - {a}unchanged; {length}unchanged; {value}unchanged; - 0i32 <= low; - low <= high; - high <= length; - ((i64) low + (i64) high) <= MAXi32; - take IndexInv = each (i32 j; 0i32 <= j && j < length) - {Owned(a + j)}; - IndexInv == IndexPre; @*/ - { - int mid = (low + high) / 2; - /*@ extract Owned, mid; @*/ - /*@ instantiate good, mid; @*/ - if (a[mid] < value) - { - low = mid + 1; - } - else if (value < a[mid]) - { - high = mid; - } - else if (value == a[mid]) - { - return mid; - } - }; - return -1; -} diff --git a/src/example-archive/java_program_verification_challenges/broken/error-crash/00004_exceptions.c b/src/example-archive/java_program_verification_challenges/broken/error-crash/00004_exceptions.c deleted file mode 100644 index 94a50bf5..00000000 --- a/src/example-archive/java_program_verification_challenges/broken/error-crash/00004_exceptions.c +++ /dev/null @@ -1,71 +0,0 @@ -// Tags: main, java - -/** Source -Examples translated to C from their original Java source in - -Jacobs, Bart, Joseph Kiniry, and Martijn Warnier. "Java program -verification challenges." Formal Methods for Components and Objects: -First International Symposium, FMCO 2002, Leiden, The Netherlands, -November 5-8, 2002, Revised Lectures 1. Springer Berlin Heidelberg, -2003. - - */ - -/** Description - - Typical of Java is its systematic use of exceptions, via its statements for throwing -and catching. They require a suitable control flow semantics. Special care is -needed for the ‘finally’ part of a try-catch-finally construction. Figure 4 contains -a simple example (adapted from [17]) that combines many aspects. The subtle -point is that the assignment m+=10 in the finally block will still be executed, -despite the earlier return statements, but has no effect on the value that is -returned. The reason is that this value is bound earlier. - -NOTE: C doesn't handle exceptions. In the translation below, we use an -if statement to discriminate the input, making the example rather -trivial. - -*/ - -//#include - -int m; // Global variable m - -/* Normal-behavior - * @ requires true; - * @ assignable m; - * @ ensures \result == ((d == 0) ? \old(m) : \old(m) / d) - * && m == \old(m) + 10; - */ -int returnfinally(int d) - /*@ requires take vp0 = Owned(&m); - let m10 = (i64)vp0 + 10i64; - m10 <= 2147483647i64; - ensures take vp1 = Owned(&m); - @*/ - { - int result; - - if (d == 0) { - result = m; // Handle division by zero case - } else { - result = m / d; // Normal division - } - - m += 10; // This corresponds to the 'finally' block in Java, executed regardless of exception - - return result; -} - -int main() - /*@ requires take vp0 = Owned(&m); - ensures take vp1 = Block(&m); - return == 0i32; - @*/ -{ - m = 20; // Initialize m - int d = 0; // Example divisor, change this value to test different paths - //printf("Result: %d\n", returnfinally(d)); - //printf("Updated m: %d\n", m); - return 0; -} diff --git a/src/example-archive/simple-examples/broken/error-crash/division_crash_1.c b/src/example-archive/simple-examples/broken/error-crash/division_crash_1.c deleted file mode 100644 index 62d286b9..00000000 --- a/src/example-archive/simple-examples/broken/error-crash/division_crash_1.c +++ /dev/null @@ -1 +0,0 @@ -void a() { 1 / 1; } From e5e80ea10ec5c222a5905d5af73a23ac0670dca2 Mon Sep 17 00:00:00 2001 From: Dhruv Makwana Date: Thu, 18 Jul 2024 15:43:11 +0100 Subject: [PATCH 087/152] Re-add division files --- .../c-testsuite/working/00064.working.c | 7 ++ .../dafny-tutorial/working/binary_search.c | 49 +++++++++++++ .../working/00004_exceptions.c | 71 +++++++++++++++++++ .../simple-examples/working/division.c | 1 + 4 files changed, 128 insertions(+) create mode 100644 src/example-archive/c-testsuite/working/00064.working.c create mode 100644 src/example-archive/dafny-tutorial/working/binary_search.c create mode 100644 src/example-archive/java_program_verification_challenges/working/00004_exceptions.c create mode 100644 src/example-archive/simple-examples/working/division.c diff --git a/src/example-archive/c-testsuite/working/00064.working.c b/src/example-archive/c-testsuite/working/00064.working.c new file mode 100644 index 00000000..2b3d701b --- /dev/null +++ b/src/example-archive/c-testsuite/working/00064.working.c @@ -0,0 +1,7 @@ +#define X 6 / 2 + +int +main() +{ + return X - 3; +} diff --git a/src/example-archive/dafny-tutorial/working/binary_search.c b/src/example-archive/dafny-tutorial/working/binary_search.c new file mode 100644 index 00000000..e83786a4 --- /dev/null +++ b/src/example-archive/dafny-tutorial/working/binary_search.c @@ -0,0 +1,49 @@ +// Binary search algorithm. The functional correctness of this algorithm depends +// on the array being sorted + +int binary_search(int *a, int length, int value) +/*@ requires + let MAXi32 = (i64) 2147483647i64; // TODO: lift to library + + 0i32 <= length; + (2i64 * (i64) length) <= MAXi32; + take IndexPre = each (i32 j; 0i32 <= j && j < length) + {Owned(a + j)}; @*/ +/*@ ensures + take IndexPost = each (i32 j; 0i32 <= j && j < length) + {Owned(a + j)}; + IndexPost == IndexPre; + (return < 0i32) || (IndexPost[return] == value); @*/ +{ + int low = 0; + int high = length; + + while (low < high) + /*@ inv + {a}unchanged; {length}unchanged; {value}unchanged; + 0i32 <= low; + low <= high; + high <= length; + ((i64) low + (i64) high) <= MAXi32; + take IndexInv = each (i32 j; 0i32 <= j && j < length) + {Owned(a + j)}; + IndexInv == IndexPre; @*/ + { + int mid = (low + high) / 2; + /*@ extract Owned, mid; @*/ + /*@ instantiate good, mid; @*/ + if (a[mid] < value) + { + low = mid + 1; + } + else if (value < a[mid]) + { + high = mid; + } + else if (value == a[mid]) + { + return mid; + } + }; + return -1; +} diff --git a/src/example-archive/java_program_verification_challenges/working/00004_exceptions.c b/src/example-archive/java_program_verification_challenges/working/00004_exceptions.c new file mode 100644 index 00000000..94a50bf5 --- /dev/null +++ b/src/example-archive/java_program_verification_challenges/working/00004_exceptions.c @@ -0,0 +1,71 @@ +// Tags: main, java + +/** Source +Examples translated to C from their original Java source in + +Jacobs, Bart, Joseph Kiniry, and Martijn Warnier. "Java program +verification challenges." Formal Methods for Components and Objects: +First International Symposium, FMCO 2002, Leiden, The Netherlands, +November 5-8, 2002, Revised Lectures 1. Springer Berlin Heidelberg, +2003. + + */ + +/** Description + + Typical of Java is its systematic use of exceptions, via its statements for throwing +and catching. They require a suitable control flow semantics. Special care is +needed for the ‘finally’ part of a try-catch-finally construction. Figure 4 contains +a simple example (adapted from [17]) that combines many aspects. The subtle +point is that the assignment m+=10 in the finally block will still be executed, +despite the earlier return statements, but has no effect on the value that is +returned. The reason is that this value is bound earlier. + +NOTE: C doesn't handle exceptions. In the translation below, we use an +if statement to discriminate the input, making the example rather +trivial. + +*/ + +//#include + +int m; // Global variable m + +/* Normal-behavior + * @ requires true; + * @ assignable m; + * @ ensures \result == ((d == 0) ? \old(m) : \old(m) / d) + * && m == \old(m) + 10; + */ +int returnfinally(int d) + /*@ requires take vp0 = Owned(&m); + let m10 = (i64)vp0 + 10i64; + m10 <= 2147483647i64; + ensures take vp1 = Owned(&m); + @*/ + { + int result; + + if (d == 0) { + result = m; // Handle division by zero case + } else { + result = m / d; // Normal division + } + + m += 10; // This corresponds to the 'finally' block in Java, executed regardless of exception + + return result; +} + +int main() + /*@ requires take vp0 = Owned(&m); + ensures take vp1 = Block(&m); + return == 0i32; + @*/ +{ + m = 20; // Initialize m + int d = 0; // Example divisor, change this value to test different paths + //printf("Result: %d\n", returnfinally(d)); + //printf("Updated m: %d\n", m); + return 0; +} diff --git a/src/example-archive/simple-examples/working/division.c b/src/example-archive/simple-examples/working/division.c new file mode 100644 index 00000000..62d286b9 --- /dev/null +++ b/src/example-archive/simple-examples/working/division.c @@ -0,0 +1 @@ +void a() { 1 / 1; } From e01ed1a1527666c29dbfbb907f2b8ff1395f7e6d Mon Sep 17 00:00:00 2001 From: Liz Austell Date: Tue, 16 Jul 2024 12:55:18 -0400 Subject: [PATCH 088/152] add runway example to tutorial --- src/examples/runway/funcs1.h | 45 +++++++ src/examples/runway/funcs2.c | 205 ++++++++++++++++++++++++++++++ src/examples/runway/state.h | 18 +++ src/examples/runway/valid_state.h | 16 +++ src/tutorial.adoc | 63 ++++++++- 5 files changed, 343 insertions(+), 4 deletions(-) create mode 100644 src/examples/runway/funcs1.h create mode 100644 src/examples/runway/funcs2.c create mode 100644 src/examples/runway/state.h create mode 100644 src/examples/runway/valid_state.h diff --git a/src/examples/runway/funcs1.h b/src/examples/runway/funcs1.h new file mode 100644 index 00000000..2d2dd2c6 --- /dev/null +++ b/src/examples/runway/funcs1.h @@ -0,0 +1,45 @@ +struct State init() +/*@ requires true; + ensures valid_state(return); +@*/ +{ + struct State initial = {INACTIVE,INACTIVE,0,0,0,0}; + return initial; +} + +struct State increment_Plane_Counter(struct State s) +/*@ requires valid_state(s); + 0i32 <= s.Plane_Counter; + s.Plane_Counter <= 2i32; + s.ModeA == ACTIVE() || s.ModeD == ACTIVE(); + s.ModeA == ACTIVE() implies s.W_D > 0i32; + s.ModeD == ACTIVE() implies s.W_A > 0i32; + ensures valid_state(return); + s.Plane_Counter == return.Plane_Counter - 1i32; + s.Runway_Time == return.Runway_Time; + s.ModeA == return.ModeA; + s.ModeD == return.ModeD; + s.W_A == return.W_A; + s.W_D == return.W_D; +@*/ +{ + struct State temp = s; + temp.Plane_Counter = s.Plane_Counter + 1; + return temp; +} + +struct State reset_Plane_Counter(struct State s) +/*@ requires valid_state(s); + ensures valid_state(return); + return.Plane_Counter == 0i32; + s.Runway_Time == return.Runway_Time; + s.ModeA == return.ModeA; + s.ModeD == return.ModeD; + s.W_A == return.W_A; + s.W_D == return.W_D; +@*/ +{ + struct State temp = s; + temp.Plane_Counter = 0; + return temp; +} \ No newline at end of file diff --git a/src/examples/runway/funcs2.c b/src/examples/runway/funcs2.c new file mode 100644 index 00000000..635ae972 --- /dev/null +++ b/src/examples/runway/funcs2.c @@ -0,0 +1,205 @@ +#include "state.h" +#include "valid_state.h" +#include "funcs1.h" + +struct State increment_Runway_Time(struct State s) +/* --BEGIN-- */ +/*@ requires valid_state(s); + 0i32 <= s.Runway_Time; + s.Runway_Time <= 4i32; + s.ModeA == ACTIVE() || s.ModeD == ACTIVE(); + ensures valid_state(return); + s.Plane_Counter == return.Plane_Counter; + s.ModeA == return.ModeA; + s.ModeD == return.ModeD; +@*/ +/* --END-- */ +{ + struct State temp = s; + temp.Runway_Time = s.Runway_Time + 1; + return temp; +} + + +struct State reset_Runway_Time(struct State s) +/* --BEGIN-- */ +/*@ requires valid_state(s); + ensures valid_state(return); + return.Runway_Time == 0i32; + s.ModeA == return.ModeA; + s.ModeD == return.ModeD; + s.W_A == return.W_A; + s.W_D == return.W_D; + s.Plane_Counter == return.Plane_Counter; +@*/ +/* --END-- */ +{ + struct State temp = s; + temp.Runway_Time = 0; + return temp; +} + +struct State arrive(struct State s) +/* --BEGIN-- */ +/*@ requires valid_state(s); + s.ModeA == ACTIVE() && s.W_A >= 1i32; + s.Plane_Counter <= 2i32; + ensures valid_state(return); + s.Runway_Time == return.Runway_Time; + s.ModeA == return.ModeA; + s.ModeD == return.ModeD; + s.W_D == return.W_D; + s.W_D == 0i32 implies s.Plane_Counter == return.Plane_Counter; + s.W_D > 0i32 implies s.Plane_Counter == return.Plane_Counter - 1i32; +@*/ +/* --END-- */ +{ + struct State temp = s; + temp.W_A = s.W_A - 1; + if (s.W_D > 0) { + temp = increment_Plane_Counter(temp); + } + return temp; +} + +struct State depart(struct State s) +/* --BEGIN-- */ +/*@ requires valid_state(s); + s.ModeD == ACTIVE() && s.W_D >=1i32; + s.Plane_Counter <= 2i32; + ensures valid_state(return); + s.Runway_Time == return.Runway_Time; + s.ModeA == return.ModeA; + s.ModeD == return.ModeD; + s.W_A == return.W_A; +@*/ +/* --END-- */ +{ + struct State temp = s; + temp.W_D = s.W_D - 1; + if (s.W_A > 0) { + temp = increment_Plane_Counter(temp); + } + return temp; +} + +struct State switch_modes(struct State s) +/* --BEGIN-- */ +/*@ requires valid_state(s); + s.ModeA == ACTIVE() || s.ModeD == ACTIVE(); + s.Plane_Counter == 0i32; + ensures valid_state(return); + return.ModeA == ACTIVE() || return.ModeD == ACTIVE(); + return.ModeA == s.ModeD; + return.ModeD == s.ModeA; + return.W_A == s.W_A; + return.W_D == s.W_D; + return.Runway_Time == s.Runway_Time; + s.Plane_Counter == return.Plane_Counter; +@*/ +/* --END-- */ +{ + struct State temp = s; + if (s.ModeA == INACTIVE) { + // if arrival mode is inactive, make it active + temp.ModeA = ACTIVE; + } else { + // if arrival mode is active, make it inactive + temp.ModeA = INACTIVE; + } + if (s.ModeD == INACTIVE) { + // if departure mode is inactive, make it active + temp.ModeD = ACTIVE; + } else { + // if departure mode is active, make it inactive + temp.ModeD = INACTIVE; + } + return temp; +} + + +// This function represents what happens every minute at the airport. +// One `tick` corresponds to one minute. +struct State tick(struct State s) +/* --BEGIN-- */ +/*@ requires valid_state(s); + (i64) s.Plane_Counter < 2147483647i64; + (i64) s.W_A < 2147483647i64; + (i64) s.W_D < 2147483647i64; + ensures valid_state(return); + (s.W_A > 0i32 && s.W_D == 0i32 && s.Runway_Time == 0i32 implies return.ModeA == ACTIVE()); + (s.W_D > 0i32 && s.W_A == 0i32 && s.Runway_Time == 0i32 implies return.ModeD == ACTIVE()); +@*/ +/* --END-- */ +{ + + // Plane on the runway + if (s.Runway_Time > 0) + { + if (s.Runway_Time == 5) + { + s = reset_Runway_Time(s); + } else { + s = increment_Runway_Time(s); + } + return s; + } + // Plane chosen to arrive + else if (s.ModeA == ACTIVE && s.W_A > 0 && s.Plane_Counter < 3) { + s = arrive(s); + s = increment_Runway_Time(s); + return s; + } + // Plane chosen to depart + else if (s.ModeD == ACTIVE && s.W_D > 0 && s.Plane_Counter < 3) { + s = depart(s); + s = increment_Runway_Time(s); + return s; + } + // Need to switch modes + else { + // Need to switch from arrival to departure mode + if (s.ModeA == ACTIVE) { + s = reset_Plane_Counter(s); + s = switch_modes(s); + // If there are planes waiting to depart, let one depart + if (s.W_D > 0) { + s = depart(s); + s = increment_Runway_Time(s); + } + return s; + } + // Need to switch from departure to arrival mode + else if (s.ModeD == ACTIVE) { + s = reset_Plane_Counter(s); + s = switch_modes(s); + + + // If there are planes waiting to arrive, let one arrive + if (s.W_A > 0) { + s = arrive(s); + + s = increment_Runway_Time(s); + } + return s; + } + // If neither mode is active, then it must be the beginning of the day. + else { + if (s.W_A > 0) { + s.ModeA = ACTIVE; + s = arrive(s); + s = increment_Runway_Time(s); + return s; + } else if (s.W_D > 0) { + s.ModeD = ACTIVE; + s = depart(s); + s = increment_Runway_Time(s); + return s; + } else { + // No planes are waiting, so we choose arrival mode and wait + s.ModeA = ACTIVE; + return s; + } + } + } +} \ No newline at end of file diff --git a/src/examples/runway/state.h b/src/examples/runway/state.h new file mode 100644 index 00000000..ecfe5b18 --- /dev/null +++ b/src/examples/runway/state.h @@ -0,0 +1,18 @@ +#define INACTIVE 0 + /*@ function (i32) INACTIVE () @*/ + static int c_INACTIVE() /*@ cn_function INACTIVE; @*/ { return INACTIVE; } + +#define ACTIVE 1 + /*@ function (i32) ACTIVE () @*/ + static int c_ACTIVE() /*@ cn_function ACTIVE; @*/ { return ACTIVE; } + +struct State { + int ModeA; + int ModeD; + + int W_A; + int W_D; + + int Runway_Time; + int Plane_Counter; +}; \ No newline at end of file diff --git a/src/examples/runway/valid_state.h b/src/examples/runway/valid_state.h new file mode 100644 index 00000000..fa0b267a --- /dev/null +++ b/src/examples/runway/valid_state.h @@ -0,0 +1,16 @@ +/*@ +function (bool) valid_state (struct State s) { + (s.ModeA == INACTIVE() || s.ModeA == ACTIVE()) && + (s.ModeD == INACTIVE() || s.ModeD == ACTIVE()) && + (s.ModeA == INACTIVE() || s.ModeD == INACTIVE()) && + (s.W_A >= 0i32 && s.W_D >= 0i32) && + (0i32 <= s.Runway_Time && s.Runway_Time <= 5i32) && + (0i32 <= s.Plane_Counter && s.Plane_Counter <= 3i32) && + (s.ModeA == INACTIVE() && s.ModeD == INACTIVE() implies s.Plane_Counter == 0i32) && + (s.Runway_Time > 0i32 implies (s.ModeA == ACTIVE() || s.ModeD == ACTIVE())) && + + (s.Plane_Counter > 0i32 && s.ModeA == ACTIVE() implies s.W_D > 0i32) && + (s.Plane_Counter > 0i32 && s.ModeD == ACTIVE() implies s.W_A > 0i32) + +} +@*/ \ No newline at end of file diff --git a/src/tutorial.adoc b/src/tutorial.adoc index 2dd42bd5..b2b17434 100644 --- a/src/tutorial.adoc +++ b/src/tutorial.adoc @@ -1394,12 +1394,67 @@ Now, let's look at the annotations in the function body. These are similar to in *Exercise*: There are many other functions that one might want to implement for a doubly linked list. For example, one might want to implement a function that appends one list to another, or a function that reverses a list. Try implementing these functions and writing their specifications. -//// -=== The Bridge Controller +=== The Runway -(Liz's stuff goes here) +Suppose we have been tasked with writing a program that simulates a runway at an airport. This airport is very small, so it only has one runway that is used for both takeoffs and landings. We want to verify that the runway is always safe by implementing the following specifications into CN: + +1. The runway has two modes: departure mode and arrival mode. The two modes can never be active at the same time, and neither mode is active at the beginning of the day. + +2. There is always a waitlist of planes that need to land at the airport and planes that need to leave the airport at a given moment. These can be modeled with counters `W_A` for the number of planes waiting to arrive, and `W_D` for the number of planes waiting to depart. + +3. At any time, a plane is either waiting to arrive, waiting to depart, or on the runway. Once a plane has started arriving or departing, the corresponding counter (`W_A` or `W_D`) is decremented. There is no need to keep track of planes once they have arrived or departed. Additionally, once a plane is waiting to arrive or depart, it continues waiting until it has arrived or departed. + + +4. Let’s say it takes 5 minutes for a plane to arrive or depart. During these 5 minutes, no other plane may use the runway. We can keep track of how long a plane has been on the runway with the `Runway_Counter`. If the `Runway_Counter` is at 0, then there is currently no plane using the runway, and it is clear for another plane to begin arriving or departing. Once the `Runway_Counter` reaches 5, we can reset it at the next clock tick. One clock tick represents 1 minute. + +5. If there is at least one plane waiting to depart and no cars waiting to arrive, then the runway is set to departure mode (and vice versa for arrivals). + +6. If both modes of the runway are inactive and planes become ready to depart and arrive simultaneously, the runway will activate arrival mode first. If the runway is in arrival mode and there are planes waiting to depart, no more than 3 planes may arrive from that time point. When either no more planes are waiting to arrive or 3 planes have arrived, the runway switches to departure mode. If the runway is on arrival mode and no planes are waiting to depart, then the runway may stay in arrival mode until a plane is ready to depart, from which time the 3-plane limit is imposed (and vice versa for departures). Put simply, if any planes are waiting for a mode that is inactive, that mode will become active no more than 15 minutes later (5 minutes for each of 3 planes). + +To encode all this in CN, we first need a way to describe the state of the runway at a given time. We can use a *struct* that includes the following fields: + +- `ModeA` and `ModeD` to represent the arrival and departure modes, respectively. We can define constants for `ACTIVE` and `INACTIVE`, as described in the `Constants` section above. +- `W_A` and `W_D` to represent the number of planes waiting to arrive and depart, respectively. +- `Runway_Time` to represent the time (in minutes) that a plane has spent on the runway while arriving or departing. +- `Plane_Counter` to represent the number of planes that have arrived or departed while planes are waiting for the other mode. This will help us keep track of the 3-plane limit as described in *(6)*. + + +include_example(exercises/runway/state.h) + +Next, we need to specify what makes a state valid. We must define a rigorous specification in order to ensure that the runway is always safe and working as intended. Try thinking about what this might look like before looking at the code below. + +include_example(exercises/runway/valid_state.h) + +Let's walk through the specifications in `valid_state`: + +- The first two lines ensure that both modes in our model behave as intended: they can only be active or inactive. Any other value for these fields would be invalid. + +- The third line says that either arrival mode or departure mode must be inactive. This specification ensures that the runway is never in both modes at the same time. + +- The fourth line says that the number of planes waiting to arrive or depart must be non-negative. This makes sense: we can't have a negative number of planes! + +- The fifth line ensures that the runway time is between 0 and 5. This addresses how a plane takes 5 minutes on the runway as described in *(4)*. + +- The sixth line ensures that the plane counter is between 0 and 3. This is important for the 3-plane limit as described in *(6)*. + +- The seventh line refers to the state at the beginning of the day. If both modes are inactive, then the day has just begun, and thus no planes have departed yet. This is why the plane counter must be 0. + +- The eighth line says that if there is a plane on the runway, then one of the modes must be active. This is because a plane can only be on the runway if it is either arriving or departing. + +- The final two lines ensure that we are incrementing `Plane_Counter` only if there are planes waiting for the other mode, as described in *(6)*. + +Now that we have the tools to reason about the state of the runway formally, let's start writing some functions. + +First, let's look at an initialization function and functions to update `Plane_Counter`. Step through these yourself and make sure you understand the reasoning behind each specification. + +include_example(exercises/runway/funcs1.h) + +*Exercise*: Now try adding your own specifications to the following functions. Make sure that you specify a valid state as a pre and post condition for every function. If you get stuck, the solution is in the solutions folder. + +include_example(exercises/runway/funcs2.c) + +*Exercise*: For extra practice, try coming up with different specifications or variations for this exercise and implementing them yourself! -//// // ====================================================================== //// From 7781ee6095b586a25310e4797a119616f165e4a4 Mon Sep 17 00:00:00 2001 From: Mike Dodds Date: Fri, 19 Jul 2024 17:14:41 -0700 Subject: [PATCH 089/152] Removed hard-coded repo name in CI script (#48) --- .github/workflows/run-cn-examples.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/run-cn-examples.yml b/.github/workflows/run-cn-examples.yml index f10ced27..dd27f9ea 100644 --- a/.github/workflows/run-cn-examples.yml +++ b/.github/workflows/run-cn-examples.yml @@ -90,7 +90,6 @@ jobs: - name: Checkout cn-tutorial uses: actions/checkout@v4 with: - repository: rems-project/cn-tutorial path: cn-tutorial - name: Run CN Tutorial CI tests From 9983c508ed20793f77727f9017ea2bcb8c143bc7 Mon Sep 17 00:00:00 2001 From: Sam Cowger Date: Fri, 19 Jul 2024 20:33:34 -0700 Subject: [PATCH 090/152] Create example of reading a zero-initialized array (#49) --- .../simple-examples/working/array_read.c | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/example-archive/simple-examples/working/array_read.c diff --git a/src/example-archive/simple-examples/working/array_read.c b/src/example-archive/simple-examples/working/array_read.c new file mode 100644 index 00000000..5ae6fbe4 --- /dev/null +++ b/src/example-archive/simple-examples/working/array_read.c @@ -0,0 +1,44 @@ +/** Get the first element of a zeroed, nonempty array */ +int head(int *arr, unsigned long len) +/*@ +requires + take arr_in = each(u64 i; i < len) { + Owned(array_shift(arr, i)) + }; + each(u64 i; i < len) { + arr_in[i] == 0i32 + }; + len > 0u64; + +ensures + take arr_out = each(u64 i; i < len) { + Owned(array_shift(arr, i)) + }; + each(u64 i; i < len) { + arr_out[i] == 0i32 + }; + return == 0i32; +@*/ +{ + unsigned long idx = 0; + + // First, we apply `extract` to direct CN to the relevant element of the + // iterated resource `arr_in`, which it needs in order to verify the + // following read: + /*@ extract Owned, idx; @*/ + + int hd = arr[idx]; + + // Now, we need to demonstrate that `hd` is zero in order to return it and + // satisfy the third `ensures` clause. To do so, we should think of our + // second `requires` clause as introducing a quantified constraint, which is + // approximately `forall i. len > i ==> arr_in[i] == 0` (`==>` being logical + // implication). In order to leverage this constraint, we need to + // instantiate `i` with our choice of index `idx`: + /*@ instantiate idx; @*/ + + // Now, our constraint has evolved into `len > 0 ==> arr_in[0] == 0`. From + // here, verification of this constraint can proceed automatically. (Recall + // that we already required that `len > 0u64`.) + return hd; +} From 88a8a15be32688e8de379ff8cb52de45f6d14b83 Mon Sep 17 00:00:00 2001 From: Dhruv Makwana Date: Mon, 22 Jul 2024 12:32:13 +0100 Subject: [PATCH 091/152] Fix #55 - Use cn verify --- check.sh | 2 +- src/example-archive/check-all.sh | 4 ++-- src/example-archive/check.sh | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/check.sh b/check.sh index 50e82a5e..0d439f14 100755 --- a/check.sh +++ b/check.sh @@ -7,7 +7,7 @@ then echo "using CN=$1 in $PWD" CN="$1" else - CN=cn + CN=cn verify fi good=0 diff --git a/src/example-archive/check-all.sh b/src/example-archive/check-all.sh index 8c87ccdd..085b17ce 100755 --- a/src/example-archive/check-all.sh +++ b/src/example-archive/check-all.sh @@ -7,7 +7,7 @@ then echo "check-all.sh: using CN=$1 in $PWD" CN="$1" else - CN=cn + CN=cn verify fi subdirs=( @@ -41,4 +41,4 @@ then else printf "\n\033[31mTest suite fails:\033[0m one or more CN tests in the example archive produced an unexpected return code\n" exit 1 -fi \ No newline at end of file +fi diff --git a/src/example-archive/check.sh b/src/example-archive/check.sh index 2878ab60..3870c927 100755 --- a/src/example-archive/check.sh +++ b/src/example-archive/check.sh @@ -5,7 +5,7 @@ then echo "check.sh: using CN=$1 in $PWD" CN="$1" else - CN=cn + CN=cn verify fi process_files() { From 3b577293144beab4cae3fc96efdf02ac2017c318 Mon Sep 17 00:00:00 2001 From: septract Date: Fri, 19 Jul 2024 20:46:00 -0700 Subject: [PATCH 092/152] Remove redundant instantiate, add stronger post to write_5 example --- .../dafny-tutorial/working/linear_search.c | 1 - src/example-archive/simple-examples/working/write_5.c | 9 +++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/example-archive/dafny-tutorial/working/linear_search.c b/src/example-archive/dafny-tutorial/working/linear_search.c index 8d9b0781..17b42980 100644 --- a/src/example-archive/dafny-tutorial/working/linear_search.c +++ b/src/example-archive/dafny-tutorial/working/linear_search.c @@ -26,7 +26,6 @@ int linear_search(int *a, int length, int key) each (i32 j; 0i32 <= j && j < idx) {IndexPre[j] != key}; @*/ { /*@ extract Owned, idx; @*/ - /*@ instantiate good, idx; @*/ if (*(a + idx) == key) { return idx; diff --git a/src/example-archive/simple-examples/working/write_5.c b/src/example-archive/simple-examples/working/write_5.c index 6beede19..2fe0c727 100644 --- a/src/example-archive/simple-examples/working/write_5.c +++ b/src/example-archive/simple-examples/working/write_5.c @@ -6,7 +6,9 @@ void write_5(int *pair) take Cell2Pre = Owned(pair + 1i32); @*/ /*@ ensures take Cell1Post = Owned(pair); - take Cell2Post = Owned(pair + 1i32); @*/ + take Cell2Post = Owned(pair + 1i32); + Cell1Post == 7i32; + Cell2Post == 8i32; @*/ { pair[0] = 7; pair[1] = 8; @@ -18,7 +20,10 @@ void write_5_alt(int *pair) /*@ requires take PairPre = each (i32 j; j == 0i32 || j == 1i32) {Owned(pair + j)}; @*/ /*@ ensures - take PairPost = each (i32 j; j == 0i32 || j == 1i32) {Owned(pair + j)}; @*/ + take PairPost = each (i32 j; j == 0i32 || j == 1i32) {Owned(pair + j)}; + PairPost[0i32] == 7i32; + PairPost[1i32] == 8i32; + @*/ { /*@ extract Owned, 0i32; @*/ pair[0] = 7; From 625aaeada27e53d53c5373f1aa013e5b864475b7 Mon Sep 17 00:00:00 2001 From: Sam Cowger Date: Mon, 22 Jul 2024 13:02:53 -0700 Subject: [PATCH 093/152] ci: install pygments gem during web deployment --- .github/workflows/deploy-to-web.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-to-web.yml b/.github/workflows/deploy-to-web.yml index 9c970110..211520a6 100644 --- a/.github/workflows/deploy-to-web.yml +++ b/.github/workflows/deploy-to-web.yml @@ -28,7 +28,7 @@ jobs: ruby-version: '2.7' - name: Install AsciiDoctor - run: gem install asciidoctor + run: gem install asciidoctor pygments.rb - name: Clean the build directory run: rm -rf build/* From 2ba6b4a5809a2622d8f495f88df6532b448773f3 Mon Sep 17 00:00:00 2001 From: septract Date: Fri, 19 Jul 2024 20:52:29 -0700 Subject: [PATCH 094/152] Move wrongly located examples --- .../error-proof/{error-crash => }/00007-string-transfer-no-io.c | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/example-archive/Rust/broken/error-proof/{error-crash => }/00007-string-transfer-no-io.c (100%) diff --git a/src/example-archive/Rust/broken/error-proof/error-crash/00007-string-transfer-no-io.c b/src/example-archive/Rust/broken/error-proof/00007-string-transfer-no-io.c similarity index 100% rename from src/example-archive/Rust/broken/error-proof/error-crash/00007-string-transfer-no-io.c rename to src/example-archive/Rust/broken/error-proof/00007-string-transfer-no-io.c From 8b24fddebfe264afa527b4835d510e1152e80914 Mon Sep 17 00:00:00 2001 From: septract Date: Fri, 19 Jul 2024 20:53:53 -0700 Subject: [PATCH 095/152] Remove old annotated tutorial versions --- src/tutorial-with-bcp-comments.md | 578 ------------- src/tutorial-with-cht-comments.adoc | 1168 --------------------------- src/tutorial-with-ps-comments.adoc | 964 ---------------------- 3 files changed, 2710 deletions(-) delete mode 100644 src/tutorial-with-bcp-comments.md delete mode 100644 src/tutorial-with-cht-comments.adoc delete mode 100644 src/tutorial-with-ps-comments.adoc diff --git a/src/tutorial-with-bcp-comments.md b/src/tutorial-with-bcp-comments.md deleted file mode 100644 index 872ef700..00000000 --- a/src/tutorial-with-bcp-comments.md +++ /dev/null @@ -1,578 +0,0 @@ -include(src/setup.m4) - - - - ---- -title: CN tutorial -fontsize: 18px -mainfont: sans-serif -linestretch: 1.4 -maxwidth: 45em -lang: en-GB -toc-title: Table of contents -header-includes: | - ---- - - -CN is a type system for verifying C code, focusing especially on low-level systems code. Compared to the normal C type system, CN checks not only that expressions and statements follow the correct typing discipline for C-types, but also that the C code executes *safely* --- does not raise C undefined behaviour --- and *correctly* --- according to strong user-defined specifications. To accurately handle the complex semantics of C, CN builds on the [Cerberus semantics for C](https://github.com/rems-project/cerberus/). - -This tutorial introduces CN along a series of examples, starting with basic usage of CN on simple arithmetic functions and slowly moving towards more elaborate separation logic specifications of data structures. - -Many examples are taken from Arthur Charguéraud's excellent [Separation Logic Foundations](https://softwarefoundations.cis.upenn.edu). These example have names starting with "slf...". - -## Installation - - -To fetch and install CN, check the Cerberus repository at and follow the instructions in [`backend/cn/INSTALL.md`](https://github.com/rems-project/cerberus/blob/master/backend/cn/INSTALL.md). - -Once completed, type `cn --help` in your terminal to ensure CN is installed and found by your system. This should print the list of available options CN can be executed with. - -To apply CN to a C file, run `cn CFILE`. - - - - -## Basic usage - -### First example - -For a first example, let's look at a simple arithmetic function: `add`, shown below, takes two `int` arguments, `x` and `y`, and returns their sum. - -include_example(exercises/0.c) - -Running CN on the example produces an error message: -``` -cn exercises/0.c -[1/1]: add -exercises/0.c:3:10: error: Undefined behaviour - return x+y; - ~^~ -an exceptional condition occurs during the evaluation of an expression (§6.5#5) -Consider the state in /var/folders/_v/ndl32wpj4bb3y9dg11rvc8ph0000gn/T/state_393431.html -``` - -CN rejects the program because it has *C undefined behaviour*, meaning it is not safe to execute. CN points to the relevant source location, the addition `x+y`, and paragraph §6.5#5 of the C language standard that specifies the undefined behaviour. It also puts a link to an HTML file with more details on the error to help in diagnosing the problem. - -Inspecting this HTML report (as we do in a moment) gives us possible example values for `x` and `y` that cause the undefined behaviour and hint at the problem: for very large values for `x` and `y`, such as $1073741825$ and $1073741824$, the sum of `x` and `y` can exceed the representable range of a C `int` value: $1073741825 + 1073741824 = 2^31+1$, so their sum is larger than the maximal `int` value, $2^31-1$. - -Here `x` and `y` are *signed integers*, and in C, signed integer *overflow* is undefined behaviour (UB). Hence, `add` is only safe to execute for smaller values. Similarly, *large negative* values of `x` and `y` can cause signed integer *underflow*, also UB in C. We therefore need to rule out too large values for `x` and `y`, both positive and negative, which we do by writing a CN function specification. - - -### First function specification - -Shown below is our first function specification, for `add`, with a precondition that constraints `x` and `y` such that the sum of `x` and `y` lies between $-2147483648$ and $2147483647$, so within the representable range of a C `int` value. - -include_example(solutions/0.c) - -In detail: - -- Function specification are given using special `/*@ ... @*/` comments, placed in-between the function argument list and the function body. - -- The keyword `requires` starts the pre-condition, a list of one or more CN conditions separated by semicolons. - -- In function specifications, the names of the function arguments, here `x` and `y`, refer to their *initial values*. (Function arguments are mutable in C.) - -- `let sum = (i64) x + (i64) y` is a let-binding, which defines `sum` as the value `(i64) x + (i64) y` in the remainder of the function specification. - -- Instead of C syntax, CN uses Rust-like syntax for integer types, such as `u32` for 32-bit unsigned integers and `i64` for signed 64-bit integers to make their sizes unambiguous. Here, `x` and `y`, of C-type `int`, have CN type `i32`. - -- To define `sum` we cast `x` and `y` to the larger `i64` type, using syntax `(i64)`, which is large enough to hold the sum of any two `i32` values. - -- Finally, we require this sum to be in-between the minimal and maximal `int` value. Integer constants, such as `-2147483648i64`, must specifiy their CN type (`i64`). - -Running CN on the annotated program passes without errors. This means with our specified precondition, `add` is safe to execute. - -We may, however, wish to be more precise. So far the specification gives no information to callers of `add` about its output. To also specify the return values we add a postcondition, using the `ensures` keyword. - -include_example(solutions/1.c) - -Here we use the keyword `return`, only available in function postconditions, to refer to the return value, and equate it to `sum` as defined in the preconditions, cast back to `i32` type: `add` returns the sum of `x` and `y`. - - - -Running CN confirms that this postcondition also holds. - - -### Error reports - -In the original example CN reported a type error due to C undefined behaviour. While that example was perhaps simple enough to guess the problem and solution, this can become quite challenging as program and specification complexity increases. Diagnosing type errors is therefore an important part of using CN. CN tries to help with that by producting detailed error information, in the form of an HTML error report. - -Let's return to the type error from earlier (`add` without precondition) and take a closer look at this report. The report comprises two sections. - -BCP: Can it be bigger in the HTML output? Hard to read... - -![**CN error report**](src/images/0.error.png) - -**Path.** The first section, "Path to error", contains information about the control-flow path leading to the error. - -When type checking a C function, CN checks each possible control-flow path through the program individually. If CN detects UB or a violation of user-defined specifications, CN reports the problematic control-flow path, as a nested structure of statements: paths are split into sections, which group together statements between high-level control-flow positions (e.g. function entry, the start of a loop, the invocation of a `continue`, `break`, or `return` statement, etc.); within each section, statements are listed by source code location; finally, per statement, CN lists the typechecked sub-expressions, and the memory accesses and function calls within these. - -In our example, there is only one possible control-flow path: entering the function body (section "function body") and executing the block from lines 2 to 4, followed by the return statement at line 3. The entry for the latter contains the sequence of sub-expressions in the return statement, including reads of the variables `x` and `y`. - -In C, local variables in a function, including its arguments, are mutable and their address can be taken and passed as a value. CN therefore represents local variables as memory allocations that are manipulated using memory reads and writes. Here, type checking the return statement includes checking memory reads for `x` and `y`, at their locations `&ARG0` and `&ARG1`. The path report lists these reads and their return values: the read at `&ARG0` returns `x` (that is, the value of `x` originally passed to `add`); the read at `&ARG1` returns `y`. Alongside this symbolic information, CN displays concrete values: - -- `1073741825i32 /* 0x40000001 */` for x (the first value is the decimal representation, the second, in `/*...*/` comments, the hex equivalent) and - -- `1073741824i32 /* 0x40000000 */` for `y`. - -For now, ignore the pointer values `{@0; 4}` for `x` and `{@0; 0}` for `y`. - -These concrete values are part of a *counterexample*: a concrete valuation of variables and pointers in the program that that leads to the error. (The exact values may vary on your machine, depending on the version of Z3 installed on your system.) - - -**Proof context.** The second section, below the error trace, lists the proof context CN has reached along this control-flow path. - -"Available resources" lists the owned resources, as discussed in later sections. - -"Variables" lists counterexample values for program variables and pointers. In addition to `x` and `y`, assigned the same values as above, this includes values for their memory locations `&ARG0` and `&ARG1`, function pointers in scope, and the `__cn_alloc_history`, all of which we ignore for now. - -Finally, "Constraints" records all logical facts CN has learned along the path. This includes user-specified assumptions from preconditions or loop invariants, value ranges inferred from the C-types of variables, and facts learned during the type checking of the statements. Here (`add` without precondition) the only constraints are some contraints inferred from C-types in the code. - -- For instance, `good(x)` says that the initial value of `x` is a "good" `signed int` value (i.e. in range). Here `signed int` is the same type as `int`, CN just makes the sign explicit. For integer types `T`, `good` requires the value to be in range of type `T`; for pointer types `T` it also requires the pointer to be aligned. For structs and arrays this extends in the obvious way to struct members or array cells. - -- `repr` requires representability (not alignment) at type `T`, so `repr(&ARGO)`, for instance, records that the pointer to `x` is representable at C-type `signed int*`; - -- `aligned(&ARGO, 4u64)`, moreover, states that it is 4-byte aligned. - - - - -### Another arithmetic example - -Let's apply what we know so far to another simple arithmetic example. - -The function `doubled`, shown below, takes an int `n`, defines `a` as `n` incremented, `b` as `n` decremented, and returns the sum of the two. - -include_example(exercises/slf1_basic_example_let.signed.c) - -We would like to verify this is safe, and that `doubled` returns twice the value of `n`. Running CN on `doubled` leads to a type error: the increment of `a` has undefined behaviour. - -As in the first example, we need to ensure that `n+1` does not overflow and `n-1` does not underflow. Similarly also `a+b` has to be representable at `int` type. - -include_example(solutions/slf1_basic_example_let.signed.c) - -We can specify these using a similar style of precondition as in the first example. We first define `n_` as `n` cast to type `i64` --- i.e. a type large enough to hold `n+1`, `n-1` and `a+b` for any possible `i32` value for `n`. Then we specify that decrementing `n_` does not go below the minimal `int` value, that incrementing `n_` does not go above the maximal value, and that `n` doubled is also in range. These preconditions together guarantee safe execution. - -To capture the functional behaviour, the postcondition specifies that `return` is twice the value of `n`. - -BCP: Why don't we have constants we can use for maxint and minint of given size? Remembering / recognizing these huge numbers feels like a burden... - -### Exercise - -**Quadruple.** Specify the precondition needed to ensure safety of the C function `quadruple`, and a postcondition that describes its return value. - -include_example(exercises/slf2_basic_quadruple.signed.c) - -BCP: Leaving off a semicolon like this... - /*@ requires let n_ = (i64) n; - -2147483647i64 <= n_ + n_ + n_ + n_ - n_ + n_ + n_ + n_ <= 2147483647i64 - ensures return == n * 4i32 - @*/ -...leads to an exciting run-time failure: - cn slf2_basic_quadruple.signed.c - cn: internal error, uncaught exception: - Invalid_argument("String.sub / Bytes.sub") - Raised at Stdlib.invalid_arg in file "stdlib.ml", line 30, characters 20-45 - Called from Stdlib__String.sub in file "string.ml" (inlined), line 43, characters 2-23 - Called from Cerb_location.string_at_line in file "util/cerb_location.ml", line 384, characters 28-66 - Called from Cerb_location.head_pos_of_location in file "util/cerb_location.ml", line 423, characters 14-69 - Called from Dune__exe__Pp.error in file "backend/cn/pp.ml", line 271, characters 20-54 - Called from Dune__exe__Main.main in file "backend/cn/main.ml", line 216, characters 64-95 - Re-raised at Dune__exe__Main.main in file "backend/cn/main.ml", line 222, characters 8-73 - Called from Cmdliner_term.app.(fun) in file "cmdliner_term.ml", line 24, characters 19-24 - Called from Cmdliner_eval.run_parser in file "cmdliner_eval.ml", line 34, characters 37-44 -And attempting to rewrite the spec a little... - /*@ requires let n_ = (i64) n; - -2147483647i64 <= n_ * 4; - n_ + n_ + n_ + n_ <= 2147483647i64 - ensures return == n * 4i32 - @*/ -...leads to a similar failure: - cn slf2_basic_quadruple.signed.c - cn: internal error, uncaught exception: - Invalid_argument("String.sub / Bytes.sub") - Raised at Stdlib.invalid_arg in file "stdlib.ml", line 30, characters 20-45 - Called from Stdlib__String.sub in file "string.ml" (inlined), line 43, characters 2-23 - Called from Cerb_location.string_at_line in file "util/cerb_location.ml", line 384, characters 28-66 - Called from Cerb_location.head_pos_of_location in file "util/cerb_location.ml", line 423, characters 14-69 - Called from Dune__exe__Pp.error in file "backend/cn/pp.ml", line 271, characters 20-54 - Called from Dune__exe__Main.main in file "backend/cn/main.ml", line 216, characters 64-95 - Re-raised at Dune__exe__Main.main in file "backend/cn/main.ml", line 222, characters 8-73 - Called from Cmdliner_term.app.(fun) in file "cmdliner_term.ml", line 24, characters 19-24 - Called from Cmdliner_eval.run_parser in file "cmdliner_eval.ml", line 34, characters 37-44 - - -**Abs.** Give a specification to the C function `abs`, which computes the absolute value of a given `int` value. To describe the return value, use CN's ternary "`_ ? _ : _`" operator. Given a boolean `b`, and expressions `e1` and `e2` of the same basetype, `b ? e1 : e2` returns `e1` if `b` holds and `e2` otherwise. - -include_example(exercises/abs.c) - -BCP: There seem to be a LOT of ways to get this Invalid_argument error! -I "fixed" it by hacking line 384 of util/cerb_location.ml like this: - try - " ..." ^ String.sub l_ start (term_col - 5 - 3) ^ "..." - with _ -> ("OOPS: " ^ l_)) - -BCP: I actually found this exercise rather hard -- was not sure how to think about all the different integer widths, which one to use where, etc. Here was the solution I finally arrived at: - /*@ requires let x_ = (i64) x; - -2147483647i64 <= x_; x_ <= 2147483647i64 - ensures return == (x_ <= 0i64 ? 0i32 - x : x) @*/ -Not sure how best to guide people to the correct solution, but maybe we need a few more exercises stressing this stuff? - -## Pointers and simple ownership - -So far we've only considered example functions manipulating integer values. Verification becomes more interesting and challenging when *pointers* are involved, because the safety of memory accesses via pointers has to be verified. - -CN uses *separation logic resource types* and the concept of *ownership* to reason about memory accesses. A resource is the permission to access a region of memory. Unlike logical constraints, resource ownership is *unique*, meaning resources cannot be duplicated. - -Let's look at a simple example. The function `read` takes an `int` pointer `p` and returns the pointee value. - -include_example(exercises/read.c) - -Running CN on this example produces the following error: -``` -cn exercises/read.c -[1/1]: read -exercises/read.c:3:10: error: Missing resource for reading - return *p; - ^~ -Resource needed: Owned(p) -Consider the state in /var/folders/_v/ndl32wpj4bb3y9dg11rvc8ph0000gn/T/state_403624.html -``` - -For the read `*p` to be safe, ownership of a resource is missing: a resource `Owned(p)`. - -### The Owned resource type - -Given a C-type `T` and pointer `p`, the resource `Owned(p)` asserts ownership of a memory cell at location `p` of the size of C-type `T`. It is is CN's equivalent of a points-to assertion in separation logic (indexed by C-types `T`). - -In this example we can ensure the safe execution of `read` by adding a precondition that requires ownership of `Owned(p)`, as shown below. For now ignore the notation `take ... = Owned(p)`. Since `read` maintains this ownership, we also add a corresponding post-condition, whereby `read` returns ownership of `p` after it is finished executing, in the form of another `Owned(p)` resource. - -include_example(solutions/read.c) - -This specifications means that - -- any function calling `read` has to be able to provide a resource `Owned(p)` to pass into `read`, and - -- the caller will receive back a resource `Owned(p)` when `read` returns. - -BCP: What are v1 and v2? Can I leave them out or replace with _? - -### Resource outputs - -However, a caller of `read` may also wish to know that `read` actually returns the correct value, the pointee of `p`, and also that it does not change memory at location `p`. To phrase both we need a way to refer to the pointee of `p`. - -In CN resources have *outputs*. Each resource outputs the information that can be derived from ownership of the resource. What information is returned is specific to the type of resource. A resource `Owned(p)` (for some C-type `T`) outputs the *pointee value* of `p`, since that can be derived from the resource ownership: assume you have a pointer `p` and the associated ownership, then this uniquely determines the pointee value of `p`. - -CN uses the `take`-notation seen in the example above to refer to the output of a resource, introducing a new name binding for the output. The precondition `take v1 = Owned(p)` from the precondition does two things: (1) it assert ownership of resource `Owned(p)`, and (2) it binds the name `v1` to the resource output, here the pointee value of `p` at the start of the function. Similarly, the postcondition introduces the name `v2` for the pointee value on function return. - -That means we can use the resource outputs from the pre- and postcondition to strengthen the specification of `read` as planned. We add two new postconditions: we specify - -#. that `read` returns `v1` (the initial pointee value of `p`), and - -#. that the pointee values `v1` and `v2` before and after execution of `read` (respectively) are the same. - -include_example(solutions/read2.c) - - -**Aside.** In standard separation logic the equivalent specification for `read` could have been phrased as follows (where `return` binds the return value in the postcondition): -``` -{ ∃v1. p ↦ v1 } -read(p) -{ return. ∃v2. (p ↦ v2) * (return = v1 /\ v1 = v2) } -``` -BCP: Really?? The uses of v1 on the last line look ill scoped... -CN's `take` notation is just an alternative syntax for existential quantification over the values of resources (e.g. `take v1 = Owned<...>(p)` vs. `∃v1. p ↦ v1`), but a useful one: the `take` notation syntactically restricts how these quantifiers can be used to ensure CN can always infer them. - - -### Exercises - -**Quadruple**. Specify the function `quadruple_mem`, that is similar to the earlier `quadruple` function, except that the input is passed as an `int` pointer. Write a specification that takes ownership of this pointer on entry and returns this ownership on exit, leaving the pointee value unchanged. - -include_example(exercises/slf_quadruple_mem.c) - -**Abs**. Give a specification to the function `abs_mem`, which computes the absolute value of a number passed as an `int` pointer. - -include_example(exercises/abs_mem.c) - - -### Linear resource ownership - -In the specifications we have written so far, functions that receive resources as part of their precondition also return this ownership in their postcondition. - -Let's try the `read` example from earlier again, but with a postcondition that does not return the ownership: - -include_example(exercises/read.broken.c) - -CN rejects this program with the following message: -``` -cn build/exercises/read.broken.c -[1/1]: read -build/exercises/read.broken.c:4:3: error: Left-over unused resource 'Owned(p)(v1)' - return *p; - ^~~~~~~~~~ -Consider the state in /var/folders/_v/ndl32wpj4bb3y9dg11rvc8ph0000gn/T/state_17eb4a.html -``` - -CN has typechecked the function, verified that it is safe to execute under the precondition (given ownership `Owned(p)`), and that the function (vacuously) satisfies its postcondition. But, following the check of the postcondition it finds that not all resources have been "used up". - -Given the above specification, `read` leaks memory: it takes ownership, does not return it, but also does not deallocate the owned memory or otherwise dispose of it. In CN this is a type error. - -CN's resource types are *linear* (as opposed to affine). This means not only that resources cannot be duplicated, resources also cannot simply be dropped or "forgotten". Every resource passed into a function has to either be used up by it, by returning it or passing it to another function that consumes it, or destroyed, by deallocating the owned area of memory (as we shall see later). - -CN's motivation for linear tracking of resources is its focus on low-level systems software. CN checks C programs, in which, unlike higher-level garbage-collected languages, memory is managed manually, and memory leaks are typically very undesirable. - -As a consequence, function specifications have to do precise "book-keeping" of their resource footprint, and, in particular, return any unused resources back to the caller. - - - -### The Block resource type - -Aside from the `Owned` resource seen so far, CN has another built-in resource type: `Block`. Given a C-type `T` and pointer `p`, `Block(p)` asserts the same ownership as `Owned(p)` --- so ownership of a memory cell at `p` the size of type `T` --- but in contrast to `Owned`, `Block` memory is not necessarily initialised. - -CN uses this distinction to prevent reads from uninitialised memory: - -- A read at C-type `T` and pointer `p` requires a resource `Owned(p)`, so ownership of *initialised* memory at the right C-type. The load returns the resource unchanged. - -- A write at C-type `T` and pointer `p` needs only a `Block(p)` (so, unlike reads, writes to uninitialised memory are fine). The write consumes ownership of the resource (it destroys it) and returns a new resource `Owned(p)` with the value written as the output. This means the resource returned from a write records the fact that this memory cell is now initialised and can be read from. - -Since `Owned` carries the same ownership as `Block`, just with the additional information that the `Owned` memory is initalised, a resource `Owned(p)` is "at least as good" as `Block(p)` --- an `Owned(p)` resource can be used whenever `Block(p)` is needed. For instance CN's type checking of a write to `p` requires a `Block(p)`, but if an `Owned(p)` resource is what is available, this can be used just the same. (In other words, an already-initialised memory cell can, of course, be over-written again.) - -Unlike `Owned`, whose output is the pointee value, `Block` has no meaningful output: its output is `void`/`unit`. - - -### Write example - -Let's explore resources and their outputs in another example. The C function `incr` takes an `int` pointer `p` and increments the pointee value. - -include_example(solutions/slf0_basic_incr.signed.c) - -In the precondition we assert ownership of resource `Owned(p)`, binding its output/pointee value to `v1`, and use `v1` to specify that `p` must point to a sufficiently small value at the start of the function not to overflow when incremented. The postcondition asserts ownership of `p` with output `v2`, similar to before, and uses this to express that the value `p` points to is incremented by `incr`: `v2 == v1+1i32`. - - -If we incorrectly tweaked this specification and used `Block(p)` instead of `Owned(p)` in the precondition, as below, then CN would reject the program. - -include_example(exercises/slf0_basic_incr.signed.broken.c) - -CN reports: -``` -build/solutions/slf0_basic_incr.signed.broken.c:6:11: error: Missing resource for reading - int n = *p; - ^~ -Resource needed: Owned(p) -Consider the state in /var/folders/_v/ndl32wpj4bb3y9dg11rvc8ph0000gn/T/state_5da0f3.html -``` - -The `Owned(p)` resource required for reading is missing, since, as per precondition, only `Block(p)` is available. Checking the linked HTML file confirms this. Here the section "Available resources" lists all resource ownership at the point of the failure: - -- `Block(p)(u)`, so ownership of uninitialised memory at location `p`; the output is a `void`/`unit` value `u` (specified in the second pair of parentheses) - -- `Owned(&ARG0)(p)`, the ownership of (initialised) memory at location `&ARG0`, so the memory location where the first function argument is stored; its output is the pointer `p` (not to be confused with the pointee of `p`); and finally - -- `__CN_Alloc(&ARG0)(void)` is a resource that records allocation information for location `&ARG0`; this is related to CN's memory-object semantics, which we ignore for the moment. - - -### Exercises - -**Zero.** Write a specification for the function `zero`, which takes a pointer to *uninitialised* memory and initialises it to $0$. - -include_example(exercises/zero.c) - -**In-place double.** Give a specification for the function `inplace_double`, which takes an `int` pointer `p` and doubles the pointee value: specify the precondition needed to guarantee safe execution and a postcondition that captures the function's behaviour. - -include_example(exercises/slf3_basic_inplace_double.c) - - -### Multiple owned pointers - -When functions manipulate multiple pointers, we can assert their ownership just like before. However (as in standard separation logic) pointer ownership is unique, so simultaneous ownership of `Owned` or `Block` resources for two pointers requires these pointers to be disjoint. - -The following example shows the use of two `Owned` resources for accessing two different pointers: function `add` reads two `int` values in memory, at locations `p` and `q`, and returns their sum. - -include_example(exercises/add_read.c) - -This time we use C's `unsigned int` type. In C, over- and underflow of unsigned integers is not undefined behaviour, so we do not need any special preconditions to rule this out. Instead, when an arithmetic operation at unsigned type goes outside the representable range, the value "wraps around". - -The CN variables `m` and `n` (resp. `m2` and `n2`) for the pointee values of `p` and `q` before (resp. after) the execution of `add` have CN basetype `u32`, so unsigned 32-bit integers, matching the C `unsigned int` type. Like C's unsigned integer arithmetic, CN unsigned int values wrap around when exceeding the value range of the type. - -Hence, the postcondition `return == m+n` holds also when the sum of `m` and `n` is greater than the maximal `unsigned int` value. - -In the following we will sometimes use unsigned integer types to focus on specifying memory ownership, rather than the conditions necessary to show absence of C arithmetic undefined behaviour. - -BCP: Maybe it would be better to use unsigned throughout the early parts of the tutorial, to emphasize higher-level aspects of reasoning in CN, and then deal with all the intricacies of numbers all at once. - -### Exercises - -**Swap.** Specify the function `swap`, which takes two owned `unsigned int` pointers and swaps their values. - -include_example(exercises/swap.c) - -**Transfer.** Write a specification for the function `transfer`, shown below. - -include_example(exercises/slf8_basic_transfer.c) - -BCP: I started off with this wrong annotation - /*@ requires take p_ = Owned(p); - take q_ = Owned(q); - -2147483648i64 <= ((i64) p) + ((i64) q); - ((i64) p) + ((i64) q) <= 2147483647i64 - ensures take p_1 = Owned(p); - take q_1 = Owned(q); - p_1 == p_ + q_; - q_1 == 0i32 - @*/ -but I was confused by the error message: - slf8_basic_transfer.c:12:20: error: Missing resource for reading - unsigned int n = *p; - ^~ - Resource needed: Owned(p) -Then I fiddled some more and somehow wound up with this annotation (which also didn't work, but it was not immediately clear why): - /*@ requires take p_ = Owned(p); - take q_ = Owned(q) - ensures take p_1 = Owned(p); - take q_1 = Owned(q); - p_1 == p_ + q_; - q_1 == 0u32 - @*/ - - - -## Ownership of compound objects - -So far all examples have worked with just integers and pointers, but larger programs typically also manipulate compound values, often represented using C struct types. Specifying functions manipulating structs works in much the same way as with basic types. - -For instance, the following example uses a `struct` `point` to represent a point in two-dimensional space. The function `transpose` swaps a point's `x` and `y` coordinates. - -include_example(exercises/transpose.c) - -Here the precondition asserts ownership for `p`, at type `struct point`; the output `s` is a value of CN type `struct point`, i.e. a record with members `i32` `x` and `i32` `y`. The postcondition similarly asserts ownership of `p`, with output `s2`, and asserts the coordinates have been swapped, by referring to the members of `s` and `s2` individually. - -**Note.** In CN, as in C, structurally equal struct types with *different tags* are not the same type. - -BCP: ??? - -### Compound Owned and Block resources - -While one might like to think of a struct as a single (compound) object that is manipulated as a whole, C permits more flexible struct manipulation: given a struct pointer, programmers can construct pointers to *individual struct members* and pass these as values, even to other functions. - -CN therefore cannot treat resources for compound C types, such as structs, as primitive, indivisible units. Instead, `Owned` and `Block` are defined inductively in the structure of the C-type `T`. - -For struct types `T`, the `Owned` resource is defined as the collection of `Owned` resources for its members, as well as `Block` resources for any padding bytes in-between them. The resource `Block`, similarly, is made up of `Block` resources for all members and padding bytes. - -To handle code that manipulates pointers into parts of a struct object, CN can automatically decompose a struct resource into the member resources, and recompose it, as needed. The following example illustrates this. - -Recall the function `zero` from our earlier exercise. It takes an `int` pointer to uninitialised memory, with `Block` ownership, and initialises the value to zero, returning an `Owned` resource with output $0$. - -Now consider the new function `init_point`, shown below, which takes a pointer `p` to a `struct point` and zero-initialises its members by calling `zero` twice, once with a pointer to struct member `x`, and once with a pointer to `y`. - -include_example(exercises/init_point.c) - -As per precondition, `init_point` receives ownership `Block(p)`. The `zero` function, however, works on `int` pointers and requires `Block` ownership. - -CN can prove the calls to `zero` with `&p->x` and `&p->y`are safe because it decomposes the `Block(p)` into two `Block`, one for member `x`, one for member `y`. Later, the reverse happens: following the two calls to `zero`, as per `zero`'s precondition, `init_point` has ownership of two adjacent `Owned` resources: ownership for the two struct member pointers, with the member now initialised. Since the postcondition of `init_point` requires ownership `Owned(p)`, CN combines these back into a compound resource. The resulting `Owned` resource has for an output the struct value `s2` that is composed of the zeroed member values for `x` and `y`. - -### Resource inference - -To handle the required resource inference, CN "eagerly" decomposes all `struct` resources into resources for the struct members, and "lazily" re-composes them as needed. - -We can see this if we experimentally, for instance, change the `transpose` example from above to force a type error. Let's insert an `/*@ assert(false) @*/` CN assertion in the middle of the `transpose` function (more on CN assertions later), so we can inspect CN's proof context shown in the error report. - -include_example(exercises/transpose.broken.c) - -The precondition of `transpose` asserts ownership of an `Owned(p)` resource. The error report now instead lists under "Available resources" two resources: - -- `Owned(member_shift(p, x))` with output `s.x` and - -- `Owned(member_shift(p, y))` with output `s.y` - -Here `member_shift(p,m)` is the CN expression that constructs, from a `struct s` pointer `p`, the "shifted" pointer for its member `m`. - -When the function returns the two member resources are recombined "on demand" to satisfy the postcondition `Owned(p)`. - - - -### Exercises - -**Init point.** Insert CN `assert(false)` statements in different statement positions of `init_point` and check how the available resources evolve. - -**Transpose (again).** Recreate the transpose function from before, now using the swap function verified earlier (for `struct upoint`, with unsigned member values). - -include_example(exercises/transpose2.c) - - - - - -## TODO: Datastructures and resource predicates: linked lists - -C linked list example. This requires a different ownership pattern from before because the structure is recursive. Introduce **resource predicates** for ownership of recursive datastructures. - -Example of CN linked list resource predicate without output ("ignore `void` return"). Verify safety of some simple list manipulating functions? List length? - -Feeing all elements of a list (HLMC). - - -CN's **resource inference for resource predicates** unfolds resource predicates automatically as new information is learned about the inputs. Show with an example, ideally where this unfolding is not available at first, but then works after branching on whether the input pointer is null. - -Maybe also show **manual case splitting**? - - -### TODO: CN datatypes and logical functions - -For going beyond safety proofs there needs to be a way to refer to the data represented in memory. User-defined resource predicates have user-defined outputs that return information derived from inputs and owned memory. For instance, resource predicate that returns length or sum of the list as output (VeriFast?). - -We need a way to talk about recursive data in specifications. Define CN list **datatype** and **CN pattern matching**. Modify linked list predicate to output the represented list. - -Verify C implementations of list nil, cons, head, tail, with functional specification (HLMC?). (For the following: HLMC linked list library?) - -For more complicated operations there needs to be a way to express recursive definitions on lists: introduce **(recursive) specification functions** . - -Define CN list append, zero'ing out a list. Summing all the values and computing the length of a list? (VeriFast) -- so far without matching C functions. - -Recursive CN functions cannot be handled automatically in proof. CN only knows f(args) = f(args') when args = args' for recursive functions, nothing else. - -Show list append example, written as recursive C function, where **unfold** is used to unfold the definition of a recursive specification function to verify the example. Do the same for zeroing out, summing up the values, counting the list. - -In all cases the C function should have a recursive structure matching the CN logical function, so unfolding works well. - - - -### TODO: Loops, loop invariants, lemmata - -Alternative formulation of list append using a while loop. This requires a loop invariant: basically this works the same as function preconditions, but more variables are in scope and function arguments are mutable. - -To phrase the specification we need a predicate for partial linked lists/linked list segments. Verify list append safey without functional correctness; maybe other examples. - -When we move to functional correctness we'll find that often `unfold` does not work, because the shape of the loop does not align nicely with the recursion of the specification function. - -Instead, inductive reasoning is required, and we need to **specify and apply lemmata**. Apply that to the append example. (There's also the option of VeriFast-style C programs as proofs?) - - -### TODO: Binary trees? - - -## TODO: Iterated resources and quantified constraints - -CN computed pointers and aliasing? - -Now move to arrays, and explain **iterated resources**, first using just Owned. - -Verify safety of some simple example program that loops over an array. Specify the ownership using iterated resources. CN rejects the program; fix by adding `extract`. - -Verify a functional property of the same code. The **output of an iterated resource** is a map from indices to values. - - -Reasoning about functional properties of an array may require specifying properties that hold for all values of an array: we need **logical quantifiers**. - -Specify such a property using quantifiers, and show how to **instantiate** it manually using `instantiate`. diff --git a/src/tutorial-with-cht-comments.adoc b/src/tutorial-with-cht-comments.adoc deleted file mode 100644 index e4697141..00000000 --- a/src/tutorial-with-cht-comments.adoc +++ /dev/null @@ -1,1168 +0,0 @@ -:source-highlighter: pygments -:pygments-style: manni -// :pygments-style: tango -:nofooter: -:prewrap!: - -++++ - -++++ - -// CHT: had the same issue as PS with "/bin/sh: 1: [[: not found", but it worked when I used bash -// CHT: adding to PS' comment that you have to `make` in the tutorial to get the `exercises` - they are linked as exercises/* in here, -// but they're in ../build/exercises from here; none of the links work for me -// CHT: I'm confused by the file structure generally; what is the role of "solutions" vs. "examples" -// (also is there a purpose for the duplicate files - 0.c vs. 1.c, list_rev_lemmas.h vs. liste_rev_lemmas.h) - -// __________________________________________________________________________ - -= CN tutorial - -CN is a type system for verifying C code, focusing especially on low-level systems code. Compared to the normal C type system, CN checks not only that expressions and statements follow the correct typing discipline for C-types, but also that the C code executes _safely_ — does not raise C undefined behaviour — and _correctly_ — according to strong user-defined specifications. To accurately handle the complex semantics of C, CN builds on the https://github.com/rems-project/cerberus/[Cerberus semantics for C]. - -This tutorial introduces CN along a series of examples, starting with basic usage of CN on simple arithmetic functions and slowly moving towards more elaborate separation logic specifications of data structures. - -Many examples are taken from Arthur Charguéraud’s excellent https://softwarefoundations.cis.upenn.edu[Separation Logic Foundations]. These example have names starting with "`slf...`". - -== Installing CN - -To fetch and install CN, check the Cerberus repository at https://github.com/rems-project/cerberus and follow the instructions in https://github.com/rems-project/cerberus/blob/master/backend/cn/INSTALL.md[backend/cn/INSTALL.md]. - -Once completed, type `+cn --help+` in your terminal to ensure CN is installed and found by your system. This should print the list of available options CN can be executed with. - -To apply CN to a C file, run `+cn CFILE+`. - -== Source files for the exercises and examples - -The source files can be downloaded from link:exercises.zip[here]. - -== Basic usage - -=== First example - -For a first example, let’s look at a simple arithmetic function: `+add+`, shown below, takes two `+int+` arguments, `+x+` and `+y+`, and returns their sum. - -// BCP: We should probably adopt the convention that all the files in -// the exercises directory have a comment at the top giving their name. -// (We could actually auto-generate those header comments when we process -// /src/examples into build/exercises, to avoid having to maintain them -// and possibly get them wrong...) -[source,c] ----- -include::exercises/0.c[] ----- - -Running CN on the example produces an error message: - -.... -cn exercises/0.c -[1/1]: add -exercises/0.c:3:10: error: Undefined behaviour - return x+y; - ~^~ -an exceptional condition occurs during the evaluation of an expression (§6.5#5) -// CHT: The default state file path doesn't seem to exist or get created for me; I ended up using the --state-file flag -Consider the state in /var/folders/_v/ndl32wpj4bb3y9dg11rvc8ph0000gn/T/state_393431.html -.... - -CN rejects the program because it has _C undefined behaviour_, meaning it is not safe to execute. CN points to the relevant source location, the addition `+x+y+`, and paragraph §6.5#5 of the C language standard that specifies the undefined behaviour. It also puts a link to an HTML file with more details on the error to help in diagnosing the problem. - -Inspecting this HTML report (as we do in a moment) gives us possible example values for `+x+` and `+y+` that cause the undefined behaviour and hint at the problem: for very large values for `+x+` and `+y+`, such as `+1073741825+` and `+1073741824+`, the sum of `+x+` and `+y+` can exceed the representable range of a C `+int+` value: `+1073741825 + 1073741824 = 2^31+1+`, so their sum is larger than the maximal `+int+` value, `+2^31-1+`. - -Here `+x+` and `+y+` are _signed integers_, and in C, signed integer _overflow_ is undefined behaviour (UB). Hence, `+add+` is only safe to execute for smaller values. Similarly, _large negative_ values of `+x+` and `+y+` can cause signed integer _underflow_, also UB in C. We therefore need to rule out too large values for `+x+` and `+y+`, both positive and negative, which we do by writing a CN function specification. - -=== First function specification - -Shown below is our first function specification, for `+add+`, with a precondition that constrains `+x+` and `+y+` such that the sum of `+x+` and `+y+` lies between `+-2147483648+` and `+2147483647+`, so within the representable range of a C `+int+` value. - -[source,c] ----- -include::solutions/0.c[] ----- - -In detail: - -* Function specifications are given using special `+/*@ ... @*/+` comments, placed in-between the function argument list and the function body. - -* The keyword `+requires+` starts the precondition, a list of one or more CN conditions separated by semicolons. - -* In function specifications, the names of the function arguments, here `+x+` and `+y+`, refer to their _initial values_. (Function arguments are mutable in C.) - -* `+let sum = (i64) x + (i64) y+` is a let-binding, which defines `+sum+` as the value `+(i64) x + (i64) y+` in the remainder of the function specification. - -* Instead of C syntax, CN uses Rust-like syntax for integer types, such as `+u32+` for 32-bit unsigned integers and `+i64+` for signed 64-bit integers to make their sizes unambiguous. Here, `+x+` and `+y+`, of C-type `+int+`, have CN type `+i32+`. - -* To define `+sum+` we cast `+x+` and `+y+` to the larger `+i64+` type, using syntax `+(i64)+`, which is large enough to hold the sum of any two `+i32+` values. - -* Finally, we require this sum to be in-between the minimal and maximal `+int+` value. Integer constants, such as `+-2147483648i64+`, must specifiy their CN type (`+i64+`). - -Running CN on the annotated program passes without errors. This means with our specified precondition, `+add+` is safe to execute. - -We may, however, wish to be more precise. So far the specification gives no information to callers of `+add+` about its output. To also specify the return values we add a postcondition, using the `+ensures+` keyword. - -[source,c] ----- -include::solutions/1.c[] ----- - -Here we use the keyword `+return+`, only available in function postconditions, to refer to the return value, and equate it to `+sum+` as defined in the preconditions, cast back to `+i32+` type: `+add+` returns the sum of `+x+` and `+y+`. - -Running CN confirms that this postcondition also holds. - -=== Error reports - -In the original example CN reported a type error due to C undefined behaviour. While that example was perhaps simple enough to guess the problem and solution, this can become quite challenging as program and specification complexity increases. Diagnosing type errors is therefore an important part of using CN. CN tries to help with that by producting detailed error information, in the form of an HTML error report. - -Let’s return to the type error from earlier (`+add+` without precondition) and take a closer look at this report. The report comprises two sections. - -.*CN error report* -image::images/0.error.png[*CN error report*] - -*Path.* The first section, "`Path to error`", contains information about the control-flow path leading to the error. - -When type checking a C function, CN checks each possible control-flow path through the program individually. If CN detects UB or a violation of user-defined specifications, CN reports the problematic control-flow path, as a nested structure of statements: paths are split into sections, which group together statements between high-level control-flow positions (e.g. function entry, the start of a loop, the invocation of a `+continue+`, `+break+`, or `+return+` statement, etc.); within each section, statements are listed by source code location; finally, per statement, CN lists the typechecked sub-expressions, and the memory accesses and function calls within these. - -In our example, there is only one possible control-flow path: entering the function body (section "`function body`") and executing the block from lines 2 to 4, followed by the return statement at line 3. The entry for the latter contains the sequence of sub-expressions in the return statement, including reads of the variables `+x+` and `+y+`. - -In C, local variables in a function, including its arguments, are mutable and their address can be taken and passed as a value. CN therefore represents local variables as memory allocations that are manipulated using memory reads and writes. Here, type checking the return statement includes checking memory reads for `+x+` and `+y+`, at their locations `+&ARG0+` and `+&ARG1+`. The path report lists these reads and their return values: the read at `+&ARG0+` returns `+x+` (that is, the value of `+x+` originally passed to `+add+`); the read at `+&ARG1+` returns `+y+`. Alongside this symbolic information, CN displays concrete values: - -* `+1073741825i32 /* 0x40000001 */+` for x (the first value is the decimal representation, the second, in `+/*...*/+` comments, the hex equivalent) and - -* `+1073741824i32 /* 0x40000000 */+` for `+y+`. - -For now, ignore the pointer values `+{@0; 4}+` for `+x+` and `+{@0; 0}+` for `+y+`. - -These concrete values are part of a _counterexample_: a concrete valuation of variables and pointers in the program that that leads to the error. (The exact values may vary on your machine, depending on the version of Z3 installed on your system.) - -*Proof context.* The second section, below the error trace, lists the proof context CN has reached along this control-flow path. - -"`Available resources`" lists the owned resources, as discussed in later sections. - -"`Variables`" lists counterexample values for program variables and pointers. In addition to `+x+` and `+y+`, assigned the same values as above, this includes values for their memory locations `+&ARG0+` and `+&ARG1+`, function pointers in scope, and the `+__cn_alloc_history+`, all of which we ignore for now. - -Finally, "`Constraints`" records all logical facts CN has learned along the path. This includes user-specified assumptions from preconditions or loop invariants, value ranges inferred from the C-types of variables, and facts learned during the type checking of the statements. Here (`+add+` without precondition) the only constraints are some contraints inferred from C-types in the code. - -* For instance, `+good(x)+` says that the initial value of `+x+` is a "`good`" `+signed int+` value (i.e. in range). Here `+signed int+` is the same type as `+int+`, CN just makes the sign explicit. For integer types `+T+`, `+good+` requires the value to be in range of type `+T+`; for pointer types `+T+` it also requires the pointer to be aligned. For structs and arrays this extends in the obvious way to struct members or array cells. - -* `+repr+` requires representability (not alignment) at type `+T+`, so `+repr(&ARGO)+`, for instance, records that the pointer to `+x+` is representable at C-type `+signed int*+`; - -* `+aligned(&ARGO, 4u64)+`, moreover, states that it is 4-byte aligned. - -=== Another arithmetic example - -Let’s apply what we know so far to another simple arithmetic example. - -The function `+doubled+`, shown below, takes an int `+n+`, defines `+a+` as `+n+` incremented, `+b+` as `+n+` decremented, and returns the sum of the two. - -// BCP: Is it important to number the slf examples? If so, we should do it consistently, but IMO it is not. -[source,c] ----- -include::exercises/slf1_basic_example_let.signed.c[] ----- - -We would like to verify this is safe, and that `+doubled+` returns twice the value of `+n+`. Running CN on `+doubled+` leads to a type error: the increment of `+a+` has undefined behaviour. - -As in the first example, we need to ensure that `+n+1+` does not overflow and `+n-1+` does not underflow. Similarly also `+a+b+` has to be representable at `+int+` type. - -[source,c] ----- -include::solutions/slf1_basic_example_let.signed.c[] ----- - -We can specify these using a similar style of precondition as in the first example. We first define `+n_+` as `+n+` cast to type `+i64+` — i.e. a type large enough to hold `+n+1+`, `+n-1+` and `+a+b+` for any possible `+i32+` value for `+n+`. Then we specify that decrementing `+n_+` does not go below the minimal `+int+` value, that incrementing `+n_+` does not go above the maximal value, and that `+n+` doubled is also in range. These preconditions together guarantee safe execution. - -To capture the functional behaviour, the postcondition specifies that `+return+` is twice the value of `+n+`. - -=== Exercise - -*Quadruple.* Specify the precondition needed to ensure safety of the C function `+quadruple+`, and a postcondition that describes its return value. - -[source,c] ----- -include::exercises/slf2_basic_quadruple.signed.c[] ----- - -*Abs.* Give a specification to the C function `+abs+`, which computes the absolute value of a given `+int+` value. To describe the return value, use CN’s ternary "`+_ ? _ : _+`" operator. Given a boolean `+b+`, and expressions `+e1+` and `+e2+` of the same basetype, `+b ? e1 : e2+` returns `+e1+` if `+b+` holds and `+e2+` otherwise. - -[source,c] ----- -include::exercises/abs.c[] ----- - -== Pointers and simple ownership - -So far we’ve only considered example functions manipulating integer values. Verification becomes more interesting and challenging when _pointers_ are involved, because the safety of memory accesses via pointers has to be verified. - -CN uses _separation logic resource types_ and the concept of _ownership_ to reason about memory accesses. A resource is the permission to access a region of memory. Unlike logical constraints, resource ownership is _unique_, meaning resources cannot be duplicated. - -Let’s look at a simple example. The function `+read+` takes an `+int+` pointer `+p+` and returns the pointee value. - -[source,c] ----- -include::exercises/read.c[] ----- - -Running CN on this example produces the following error: - -.... -cn exercises/read.c -[1/1]: read -exercises/read.c:3:10: error: Missing resource for reading - return *p; - ^~ -Resource needed: Owned(p) -Consider the state in /var/folders/_v/ndl32wpj4bb3y9dg11rvc8ph0000gn/T/state_403624.html -.... - -For the read `+*p+` to be safe, ownership of a resource is missing: a resource `+Owned(p)+`. - -=== The Owned resource type - -Given a C-type `+T+` and pointer `+p+`, the resource `+Owned(p)+` asserts ownership of a memory cell at location `+p+` of the size of C-type `+T+`. It is CN’s equivalent of a points-to assertion in separation logic (indexed by C-types `+T+`). - -In this example we can ensure the safe execution of `+read+` by adding a precondition that requires ownership of `+Owned(p)+`, as shown below. For now ignore the notation `+take ... = Owned(p)+`. Since `+read+` maintains this ownership, we also add a corresponding postcondition, whereby `+read+` returns ownership of `+p+` after it is finished executing, in the form of another `+Owned(p)+` resource. - -[source,c] ----- -include::solutions/read.c[] ----- - -This specifications means that - -* any function calling `+read+` has to be able to provide a resource `+Owned(p)+` to pass into `+read+`, and - -* the caller will receive back a resource `+Owned(p)+` when `+read+` returns. - -=== Resource outputs - -However, a caller of `+read+` may also wish to know that `+read+` actually returns the correct value, the pointee of `+p+`, and also that it does not change memory at location `+p+`. To phrase both we need a way to refer to the pointee of `+p+`. - -In CN resources have _outputs_. Each resource outputs the information that can be derived from ownership of the resource. What information is returned is specific to the type of resource. A resource `+Owned(p)+` (for some C-type `+T+`) outputs the _pointee value_ of `+p+`, since that can be derived from the resource ownership: assume you have a pointer `+p+` and the associated ownership, then this uniquely determines the pointee value of `+p+`. - -CN uses the `+take+`-notation seen in the example above to refer to the output of a resource, introducing a new name binding for the output. The precondition `+take v1 = Owned(p)+` from the precondition does two things: (1) it assert ownership of resource `+Owned(p)+`, and (2) it binds the name `+v1+` to the resource output, here the pointee value of `+p+` at the start of the function. Similarly, the postcondition introduces the name `+v2+` for the pointee value on function return. - -That means we can use the resource outputs from the pre- and postcondition to strengthen the specification of `+read+` as planned. We add two new postconditions: we specify - -. that `+read+` returns `+v1+` (the initial pointee value of `+p+`), and -. that the pointee values `+v1+` and `+v2+` before and after execution of `+read+` (respectively) are the same. - -[source,c] ----- -include::solutions/read2.c[] ----- - -*Aside.* In standard separation logic the equivalent specification for `+read+` could have been phrased as follows (where `+return+` binds the return value in the postcondition): - -.... -∀p. -∀v1. { p ↦ v1 } - read(p) - { return. ∃v2. (p ↦ v2) /\ return = v1 /\ v1 = v2 } -.... - -CN’s `+take+` notation is just alternative syntax for quantification over the values of resources, but a useful one: the `+take+` notation syntactically restricts how these quantifiers can be used to ensure CN can always infer them. -// CHT: can we elaborate on the part of this sentence after the colon? - -=== Exercises - -*Quadruple*. Specify the function `+quadruple_mem+`, that is similar to the earlier `+quadruple+` function, except that the input is passed as an `+int+` pointer. Write a specification that takes ownership of this pointer on entry and returns this ownership on exit, leaving the pointee value unchanged. - -[source,c] ----- -include::exercises/slf_quadruple_mem.c[] ----- - -*Abs*. Give a specification to the function `+abs_mem+`, which computes the absolute value of a number passed as an `+int+` pointer. - -[source,c] ----- -include::exercises/abs_mem.c[] ----- - -=== Linear resource ownership - -In the specifications we have written so far, functions that receive resources as part of their precondition also return this ownership in their postcondition. - -Let’s try the `+read+` example from earlier again, but with a postcondition that does not return the ownership: - -[source,c] ----- -include::exercises/read.broken.c[] ----- - -CN rejects this program with the following message: - -.... -cn build/exercises/read.broken.c -[1/1]: read -build/exercises/read.broken.c:4:3: error: Left-over unused resource 'Owned(p)(v1)' - return *p; - ^~~~~~~~~~ -Consider the state in /var/folders/_v/ndl32wpj4bb3y9dg11rvc8ph0000gn/T/state_17eb4a.html -.... - -CN has typechecked the function, verified that it is safe to execute under the precondition (given ownership `+Owned(p)+`), and that the function (vacuously) satisfies its postcondition. But, following the check of the postcondition it finds that not all resources have been "`used up`". - -Given the above specification, `+read+` leaks memory: it takes ownership, does not return it, but also does not deallocate the owned memory or otherwise dispose of it. In CN this is a type error. - -CN’s resource types are _linear_ (as opposed to affine). This means not only that resources cannot be duplicated, but also that resources cannot simply be dropped or "`forgotten`". Every resource passed into a function has to either be used up by it, by returning it or passing it to another function that consumes it, or destroyed, by deallocating the owned area of memory (as we shall see later). - -CN’s motivation for linear tracking of resources is its focus on low-level systems software. CN checks C programs, in which, unlike higher-level garbage-collected languages, memory is managed manually, and memory leaks are typically very undesirable. - -As a consequence, function specifications have to do precise "`book-keeping`" of their resource footprint, and, in particular, return any unused resources back to the caller. - -=== The Block resource type - -Aside from the `+Owned+` resource seen so far, CN has another built-in resource type: `+Block+`. Given a C-type `+T+` and pointer `+p+`, `+Block(p)+` asserts the same ownership as `+Owned(p)+` — so ownership of a memory cell at `+p+` the size of type `+T+` — but in contrast to `+Owned+`, `+Block+` memory is not necessarily initialised. - -CN uses this distinction to prevent reads from uninitialised memory: - -* A read at C-type `+T+` and pointer `+p+` requires a resource `+Owned(p)+`, i.e., ownership of _initialised_ memory at the right C-type. The load returns the `+Owned+` resource unchanged. - -* A write at C-type `+T+` and pointer `+p+` needs only a `+Block(p)+` (so, unlike reads, writes to uninitialised memory are fine). The write consumes ownership of the `+Block+` resource (it destroys it) and returns a new resource `+Owned(p)+` with the value written as the output. This means the resource returned from a write records the fact that this memory cell is now initialised and can be read from. - -Since `+Owned+` carries the same ownership as `+Block+`, just with the additional information that the `+Owned+` memory is initalised, a resource `+Owned(p)+` is "`at least as good`" as `+Block(p)+` — an `+Owned(p)+` resource can be used whenever `+Block(p)+` is needed. For instance CN’s type checking of a write to `+p+` requires a `+Block(p)+`, but if an `+Owned(p)+` resource is what is available, this can be used just the same. This allows an already-initialised memory cell to be over-written again. - -Unlike `+Owned+`, whose output is the pointee value, `+Block+` has no meaningful output: its output is `+void+`/`+unit+`. - -=== Write example - -Let’s explore resources and their outputs in another example. The C function `+incr+` takes an `+int+` pointer `+p+` and increments the pointee value. - -[source,c] ----- -include::solutions/slf0_basic_incr.signed.c[] ----- - -In the precondition we assert ownership of resource `+Owned(p)+`, binding its output/pointee value to `+v1+`, and use `+v1+` to specify that `+p+` must point to a sufficiently small value at the start of the function not to overflow when incremented. The postcondition asserts ownership of `+p+` with output `+v2+`, as before, and uses this to express that the value `+p+` points to is incremented by `+incr+`: `+v2 == v1+1i32+`. - -If we incorrectly tweaked this specification and used `+Block(p)+` instead of `+Owned(p)+` in the precondition, as below, then CN would reject the program. - -[source,c] ----- -include::exercises/slf0_basic_incr.signed.broken.c[] ----- - -CN reports: - -.... -build/solutions/slf0_basic_incr.signed.broken.c:6:11: error: Missing resource for reading - int n = *p; - ^~ -Resource needed: Owned(p) -Consider the state in /var/folders/_v/ndl32wpj4bb3y9dg11rvc8ph0000gn/T/state_5da0f3.html -.... - -The `+Owned(p)+` resource required for reading is missing, since, as per precondition, only `+Block(p)+` is available. Checking the linked HTML file confirms this. Here the section "`Available resources`" lists all resource ownership at the point of the failure: - -* `+Block(p)(u)+`, so ownership of uninitialised memory at location `+p+`; the output is a `+void+`/`+unit+` value `+u+` (specified in the second pair of parentheses) - -* `+Owned(&ARG0)(p)+`, the ownership of (initialised) memory at location `+&ARG0+`, so the memory location where the first function argument is stored; its output is the pointer `+p+` (not to be confused with the pointee of `+p+`); and finally - -* `+__CN_Alloc(&ARG0)(void)+` is a resource that records allocation information for location `+&ARG0+`; this is related to CN’s memory-object semantics, which we ignore for the moment. - -=== Exercises - -*Zero.* Write a specification for the function `+zero+`, which takes a pointer to _uninitialised_ memory and initialises it to `+0+`. - -[source,c] ----- -include::exercises/zero.c[] ----- - -*In-place double.* Give a specification for the function `+inplace_double+`, which takes an `+int+` pointer `+p+` and doubles the pointee value: specify the precondition needed to guarantee safe execution and a postcondition that captures the function’s behaviour. - -// CHT: are there any existing features for noticing if the precondition is stronger than necessary for safety -// (e.g., if you typo -2i64 <= sum instead of -2147483648i64 <= sum) - -[source,c] ----- -include::exercises/slf3_basic_inplace_double.c[] ----- - -=== Multiple owned pointers - -When functions manipulate multiple pointers, we can assert their ownership just like before. However (as in standard separation logic) pointer ownership is unique, so simultaneous ownership of `+Owned+` or `+Block+` resources for two pointers requires these pointers to be disjoint. - -The following example shows the use of two `+Owned+` resources for accessing two different pointers: function `+add+` reads two `+int+` values in memory, at locations `+p+` and `+q+`, and returns their sum. - -[source,c] ----- -include::exercises/add_read.c[] ----- - -This time we use C’s `+unsigned int+` type. In C, over- and underflow of unsigned integers is not undefined behaviour, so we do not need any special preconditions to rule this out. Instead, when an arithmetic operation at unsigned type goes outside the representable range, the value "`wraps around`". - -The CN variables `+m+` and `+n+` (resp. `+m2+` and `+n2+`) for the pointee values of `+p+` and `+q+` before (resp. after) the execution of `+add+` have CN basetype `+u32+`, so unsigned 32-bit integers, matching the C `+unsigned int+` type. Like C’s unsigned integer arithmetic, CN unsigned int values wrap around when exceeding the value range of the type. - -Hence, the postcondition `+return == m+n+` holds also when the sum of `+m+` and `+n+` is greater than the maximal `+unsigned int+` value. - -In the following we will sometimes use unsigned integer types to focus on specifying memory ownership, rather than the conditions necessary to show absence of C arithmetic undefined behaviour. - -=== Exercises - -*Swap.* Specify the function `+swap+`, which takes two owned `+unsigned int+` pointers and swaps their values. - -[source,c] ----- -include::exercises/swap.c[] ----- - -*Transfer.* Write a specification for the function `+transfer+`, shown below. - -[source,c] ----- -include::exercises/slf8_basic_transfer.c[] ----- - -== Ownership of compound objects - -So far all examples have worked with just integers and pointers, but larger programs typically also manipulate compound values, often represented using C struct types. Specifying functions manipulating structs works in much the same way as with basic types. - -For instance, the following example uses a `+struct+` `+point+` to represent a point in two-dimensional space. The function `+transpose+` swaps a point’s `+x+` and `+y+` coordinates. - -[source,c] ----- -include::exercises/transpose.c[] ----- - -Here the precondition asserts ownership for `+p+`, at type `+struct point+`; the output `+s+` is a value of CN type `+struct point+`, i.e. a record with members `+i32+` `+x+` and `+i32+` `+y+`. The postcondition similarly asserts ownership of `+p+`, with output `+s2+`, and asserts the coordinates have been swapped, by referring to the members of `+s+` and `+s2+` individually. - -=== Compound Owned and Block resources - -While one might like to think of a struct as a single (compound) object that is manipulated as a whole, C permits more flexible struct manipulation: given a struct pointer, programmers can construct pointers to _individual struct members_ and pass these as values, even to other functions. - -CN therefore cannot treat resources for compound C types, such as structs, as primitive, indivisible units. Instead, `+Owned+` and `+Block+` are defined inductively in the structure of the C-type `+T+`. - -For struct types `+T+`, the `+Owned+` resource is defined as the collection of `+Owned+` resources for its members (as well as `+Block+` resources for any padding bytes in-between). The resource `+Block+`, similarly, is made up of `+Block+` resources for all members (and padding bytes). - -To handle code that manipulates pointers into parts of a struct object, CN can automatically decompose a struct resource into the member resources, and recompose it, as needed. The following example illustrates this. - -Recall the function `+zero+` from our earlier exercise. It takes an `+int+` pointer to uninitialised memory, with `+Block+` ownership, and initialises the value to zero, returning an `+Owned+` resource with output `+0+`. - -Now consider the function `+init_point+`, shown below, which takes a pointer `+p+` to a `+struct point+` and zero-initialises its members by calling `+zero+` twice, once with a pointer to struct member `+x+`, and once with a pointer to `+y+`. - -[source,c] ----- -include::exercises/init_point.c[] ----- - -As stated in its precondition, `+init_point+` receives ownership `+Block(p)+`. The `+zero+` function, however, works on `+int+` pointers and requires `+Block+` ownership. - -CN can prove the calls to `+zero+` with `+&p->x+` and `+&p->y+` are safe because it decomposes the `+Block(p)+` into two `+Block+`, one for member `+x+`, one for member `+y+`. Later, the reverse happens: following the two calls to `+zero+`, as per `+zero+`’s precondition, `+init_point+` has ownership of two adjacent `+Owned+` resources – ownership for the two struct member pointers, with the member now initialised. Since the postcondition of `+init_point+` requires ownership `+Owned(p)+`, CN combines these back into a compound resource. The resulting `+Owned+` resource has for an output the struct value `+s2+` that is composed of the zeroed member values for `+x+` and `+y+`. - -=== Resource inference - -To handle the required resource inference, CN "`eagerly`" decomposes all `+struct+` resources into resources for the struct members, and "`lazily`" re-composes them as needed. - -We can see this if, for instance, we experimentally change the `+transpose+` example from above to force a type error. Let’s insert an `+/*@ assert(false) @*/+` CN assertion in the middle of the `+transpose+` function (more on CN assertions later), so we can inspect CN’s proof context shown in the error report. - -// CHT: seconding others' comment that it would be helpful to have a better way of seeing intermediate proof context - -[source,c] ----- -include::exercises/transpose.broken.c[] ----- - -The precondition of `+transpose+` asserts ownership of an `+Owned(p)+` resource. The error report now instead lists under "`Available resources`" two resources: - -* `+Owned(member_shift(p, x))+` with output `+s.x+` and - -* `+Owned(member_shift(p, y))+` with output `+s.y+` - -Here `+member_shift(p,m)+` is the CN expression that constructs, from a `+struct s+` pointer `+p+`, the "`shifted`" pointer for its member `+m+`. - -When the function returns the two member resources are recombined "`on demand`" to satisfy the postcondition `+Owned(p)+`. - -=== Exercises - -*Init point.* Insert CN `+assert(false)+` statements in different statement positions of `+init_point+` and check how the available resources evolve. - -*Transpose (again).* Recreate the transpose function from before, now using the swap function verified earlier (for `+struct upoint+`, with unsigned member values). - -[source,c] ----- -include::exercises/transpose2.c[] ----- - -//// -BCP: Some more things to think about including... - - Something about CN's version of the frame rule (see - bcp_framerule.c, though the example is arguably a bit - unnatural). - - Examples from Basic.v with allocation - there are lots of - interesting ones! -CP: Agreed. For now continuing with arrays, but will return to this later. -//// - -== Arrays and loops - -Another common datatype in C is arrays. Reasoning about memory ownership for arrays is more difficult than for the datatypes we have seen so far: C allows the programmer to access arrays using _computed pointers_, and the size of an array does not need to be known as a constant at compile time. - -To support reasoning about code manipulating arrays and computed pointers, CN has _iterated resources_. For instance, to specify ownership of an `+int+` array with 10 cells starting at pointer `+p+`, CN uses the iterated resource - -[source,c] ----- -each (i32 i; 0i32 <= i && i < 10i32) - { Owned(array_shift(p,i)) } ----- - -In detail, this can be read as follows: - -* for each integer `+i+` of CN type `+i32+`, … - -* if `+i+` is between `+0+` and `+10+`, … - -* assert ownership of a resource `+Owned+` … - -* for cell `+i+` of the array with base-address `+p+`. - -Here `+array_shift(p,i)+` computes a pointer into the array at pointer `+p+`, appropriately offset for index `+i+`. - -In general, iterated resource specifications take the form - -[source,c] ----- -each (BT Q; GUARD) { RESOURCE } ----- - -comprising three parts: - -* `+BT Q+`, for some CN type `+BT+` and name `+Q+`, introduces the quantifier `+Q+` of basetype `+BT+`, which is bound in `+GUARD+` and `+RESOURCE+`; - -* `+GUARD+` is a boolean-typed expression delimiting the instances of `+Q+` for which ownership is asserted; and - -* `+RESOURCE+` is any non-iterated CN resource. - -=== First array example - -Let’s see how this applies to a first example of an array-manipulating function. Function `+read+` takes three arguments: the base pointer `+p+` of an `+int+` array, the length `+n+` of the array, and an index `+i+` into the array; `+read+` then returns the value of the `+i+`-th array cell. - -[source,c] ----- -include::exercises/array_load.broken.c[] ----- - -The CN precondition requires - -- ownership of the array on entry — one `+Owned+` resource for each array index between `+0+` and `+n+` — and -- that `+i+` lies within the range of owned indices. - -On exit the array ownership is returned again. - -This specification, in principle, should ensure that the access `+p[i]+` is safe. However, running CN on the example produces an error: CN is unable to find the required ownership for reading `+p[i]+`. - -.... -cn build/solutions/array_load.broken.c -[1/1]: read -build/solutions/array_load.broken.c:5:10: error: Missing resource for reading - return p[i]; - ^~~~ -Resource needed: Owned(array_shift(p, (u64)i)) -.... - -The reason is that when searching for a required resource, such as the `+Owned+` resource for `+p[i]+` here, CN’s resource inference does not consider iterated resources. Quantifiers, as used by iterated resources, can make verification undecidable, so, in order to maintain predictable type checking, CN delegates this aspect of the reasoning to the user. - -To make the `+Owned+` resource required for accessing `+p[i]+` available to CN’s resource inference we have to "`extract`" ownership for index `+i+` out of the iterated resource. - -[source,c] ----- -include::exercises/array_load.c[] ----- - -Here the CN comment `+/*@ extract Owned, i; @*/+` is a CN "`ghost statement`"/proof hint that instructs CN to instantiate any available iterated `+Owned+` resource for index `+i+`. In our example this operation splits the iterated resource into two: - -[source,c] ----- -each(i32 j; 0i32 <= j && j < n) { Owned(array_shift(p,j)) } ----- - -is split into - -1. the instantiation of the iterated resource at `+i+` -+ -[source,c] ----- -Owned(array_shift(p,i)) ----- -2. the remainder of the iterated resource, the ownership for all indices except `+i+` -+ -[source,c] ----- -each(i32 j; 0i32 <= j && j < n && j != i) - { Owned(array_shift(p,j)) } ----- - -After this extraction step, CN can use the (former) extracted resource to justify the access `+p[i]+`. - -Following an `+extract+` statement, CN moreover remembers the extracted index and can automatically "`reverse`" the extraction when needed: after type checking the access `+p[i]+` CN must ensure the function’s postcondition holds, which needs the full array ownership again (including the extracted index `+i+`); remembering the index `+i+`, CN then automatically merges resources (1) and (2) again to obtain the required full array ownership, and completes the verification of the function. - -So far the specification only guarantees safe execution but does not specify the behaviour of `+read+`. To address this, let’s return to the iterated resources in the function specification. When we specify `+take a1 = each ...+` here, what is `+a1+`? In CN, the output of an iterated resource is a _map_ from indices to resource outputs. In this example, where index `+j+` has CN type `+i32+` and the iterated resource is `+Owned+`, the output `+a1+` is a map from `+i32+` indices to `+i32+` values — CN type `+map+`. (If the type of `+j+` was `+i64+` and the resource `+Owned+`, `+a1+` would have type `+map+`.) - -We can use this to refine our specification with information about the functional behaviour of `+read+`. - -[source,c] ----- -include::exercises/array_load2.c[] ----- - -We specify that `+read+` does not change the array — the outputs `+a1+` and `+a2+`, before (resp. after) running the function, are the same — and that the value returned is `+a1[i]+`, `+a1+` at index `+i+`. - -=== Exercises - - -*Array read two.* Specify and verify the following function, `+array_read_two+`, which takes the base pointer `+p+` of an `+unsigned int+` array, the array length `+n+`, and two indices `+i+` and `+j+`. Assuming `+i+` and `+j+` are different, it returns the sum of the values at these two indices. -// CHT: is there a good way to write this that is agnostic as to whether i and j are different - -[source,c] ----- -include::exercises/add_two_array.c[] ----- - -//// -BCP: In this one I got quite tangled up in different kinds of integers, then got tangled up in (I think) putting the extract declarations in the wrong place. (I didn't save the not-working version, I'm afraid.) -//// - -*Swap array.* Specify and verify `+swap_array+`, which swaps the values of two cells of an `+int+` array. Assume again that `+i+` and `+j+` are different, and describe the effect of `+swap_array+` on the array value using the CN map update expression `+a[i:v]+`, which denotes the same map as `+a+`, except with index `+i+` updated to `+v+`. - -[source,c] ----- -include::exercises/swap_array.c[] ----- - -//// -BCP: I wrote this, which seemed natural but did not work -- I still don't fully understand why. I think this section will need some more examples / exercises to be fully digestible, or perhaps this is just yet another symptom of my imperfecdt understanding of how the numeric stuff works. - - void swap_array (int *p, int n, int i, int j) - /*@ requires take a1 = each(i32 k; 0i32 <= k && k < n) { Owned(array_shift(p,k)) }; - 0i32 <= i && i < n; - 0i32 <= j && j < n; - j != i; - take xi = Owned(array_shift(p,i)); - take xj = Owned(array_shift(p,j)) - ensures take a2 = each(i32 k; 0i32 <= k && k < n) { Owned(array_shift(p,k)) }; - a1[i:xj][j:xi] == a2 - @*/ - { - extract Owned, i; - extract Owned, j; - int tmp = p[i]; - p[i] = p[j]; - p[j] = tmp; - } -//// - -=== Loops - -The array examples covered so far manipulate one or two individual cells of an array. Another typical pattern in code working over arrays is to *loop*, uniformly accessing all cells of an array, or sub-ranges of it. - -In order to verify code with loops, CN requires the user to supply loop invariants -- CN specifications of all owned resources and the constraints required to verify each iteration of the loop. - - -Let's take a look at a simple first example. The following function, `+init_array+`, takes the base pointer `+p+` of a `+char+` array and the array length `+n+` and writes `+0+` to each array cell. -[source,c] ----- -include::exercises/init_array.c[] ----- - -If, for the moment, we focus just on proving safe execution of `+init_array+`, ignoring its functional behaviour, a specification might look as above: on entry `+init_array+` takes ownership of an iterated `+Owned+` resource -- one `+Owned+` resource for each index `+i+` of type `+u32+` (so necessarily greater or equal to `+0+`) up to `+n+`; on exit `+init_array+` returns the ownership. - -To verify this, we have to supply a loop invariant that specifies all resource ownership and the necessary constraints that hold before and after each iteration of the loop. Loop invariants are specified using the keyword `inv`, followed by CN specifications using the same syntax as in function pre- and postconditions. The variables in scope for loop invariants are all in-scope C variables, as well as CN variables introduced in the function precondition. *In loop invariants, the name of a C variable refers to its current value* (more on this shortly). - -// CHT: details on how 'inv' compares to 'requires' and 'ensures' would be helpful - i.e., should we read it as though the body of 'inv' -// is required at the start of the loop and ensured at the end of the loop? -// this would be helpful for understanding how linearity/resource ownership works in this example - -[source,c] ----- -include::solutions/init_array.c[] ----- -//// -BCP: Concrete syntax: Why not write something like "unchanged {p,n}" or "unchanged: p,n"? -//// -// CHT: seconding the above; as written it's unclear if the curly braces mean something other than set notation for things that are unchanged - -The main condition here is unsurprising: we specify ownership of an iterated resource for an array just like in the the pre- and postcondition. - -The second thing we need to do, however, is less straightforward. Recall that, as discussed at the start of the tutorial, function arguments in C are mutable, and so CN permits this as well.While in this example it is obvious that `+p+` and `+n+` do not change, CN currently requires the loop invariant to explicitly state this, using special notation `+{p} unchanged+` (and similarly for `+n+`). - -**Note.** If we forget to specify `+unchanged+`, this can lead to confusing errors. In this example, for instance, CN would verify the loop against the loop invariant, but would be unable to prove a function postcondition seemingly directly implied by the loop invariant (lacking the information that the postcondition's `+p+` and `+n+` are the same as the loop invariant's). Future CN versions may handle loop invariants differently and treat variables as immutable by default. -//// -BCP: This seems like a good idea! -//// - -The final piece needed in the verification is an `+extract+` statement, as used in the previous examples: to separate the individual `+Owned+` resource for index `+j+` out of the iterated `+Owned+` resource and make it available to the resource inference, we specify `+extract Owned, j;+`. - - -With the `+extract+` statements in place, CN accepts the function. - -=== Second loop example - -However, on closer look, the specification of `+init_array+` is overly strong: it requires an iterated `+Owned+` resource for the array on entry. If, as the name suggests, the purpose of `+init_array+` is to initialise the array, then a precondition asserting only an iterated `+Block+` resource for the array should also be sufficient. The modified specification is then as follows. - -[source,c] ----- -include::exercises/init_array2.c[] ----- - -This specification *should* hold: assuming ownership of an uninitialised array on entry, each iteration of the loop initialises one cell of the array, moving it from `+Block+` to `+Owned+` "`state`", so that on function return the full array is initialised. (Recall that stores only require `+Block+` ownership of the written memory location, so ownership of not-necessarily-initialised memory.) - -To verify this modified example we again need a loop invariant. This time, the loop invariant is more involved, however: since each iteration of the loop initialises one more array cell, the loop invariant has to do precise book-keeping of the initialisation status of the array. - -To do so, we partition the array ownership into two parts: for each index of the array the loop has already visited, we have an `+Owned+` resource, for all other array indices we have the (unchanged) `+Block+` ownership. - -[source,c] ----- -include::solutions/init_array2.c[] ----- - -Let's go through this line-by-line: - -- We assert ownership of an iterated `+Owned+` resource, one for each index `+i+` strictly smaller than loop variable `+j+`. - -- All remaining indices `+i+`, between `+j+` and `+n+` are still uninitialised, so part of the iterated `+Block+` resource. - -- As in the previous example, we assert `+p+` and `+n+` are unchanged. - -- Finally, unlike in the previous example, this loop invariant involves `+j+`. We therefore also need to know that `+j+` does not exceed the array length `+n+`. Otherwise CN would not be able to prove that, on completing the last loop iteration, `+j=n+` holds. This, in turn, is needed to show that when the function returns, ownership of the iterated `+Owned+` resource --- as specified in the loop invariant --- is fully consumed by the function's post-condition and there is no left-over unused resource. - -As before, we also have to instruct CN to `+extract+` ownership of individual array cells out of the iterated resources: - -- to allow CN to extract the individual `+Block+` to be written we use `+extract Block, j;+`; - -- the store returns a matching `+Owned+` resource for index `+j+`; - -- finally, we put `+extract Owned, j;+` to allow CN to "`attach`" this resource to the iterated `+Owned+` resource. CN issues a warning, because nothing is, in fact, extracted: we are using `+extract+` only for the "`reverse`" direction. -// CHT: can we elaborate on this last sentence? - - -=== Exercises - -**Init array reverse.** Verify the function `+init_array_rev+`, which has the same specification as `+init_array2+`, but reverses the array in decreasing index order (from right to left). - -[source,c] ----- -include::exercises/init_array_rev.c[] ----- - - - -//// -___________________________________________________________________________ -___________________________________________________________________________ -___________________________________________________________________________ -___________________________________________________________________________ -___________________________________________________________________________ - -BCP: I'll put my new stuff below here... -//// - -== Defining Predicates - -// We should show how to define predicates earlier -- -// - e.g., with numeric ranges!! - -//// -BCP: The text becomes a bit sketchy from here on! But hopefully there's -still enough structure here to make sense of the examples... -//// - -Suppose we want to write a function that takes *two* pointers to -integers and increments the contents of both of them. - -First, let's deal with the "normal" case where the two arguments do -not alias... - -// CHT: many of these examples repeatedly throw "warning: CN pointer equality is not the same as C's (will not warn again)" -// can we elaborate on the difference - -// CHT: is there a subtle reason for the use of "m" here instead of using "n + 1" directly or is that style? - -[source,c] ----- -include::exercises/slf_incr2_noalias.c[] ----- - -But what if they do alias? The clunky solution is to write a whole -different version of incr2 with a different embedded specification... - -[source,c] ----- -include::exercises/slf_incr2_alias.c[] ----- - -This is horrible. Much better is to define a predicate to use -in the pre- and postconditions that captures both cases together: - -[source,c] ----- -include::exercises/slf_incr2.c[] ----- - -**Note**: At the moment, CN does not derive pointer disjointness -constraints from resources: from simultaneous ownership of the -resources `+Owned(p)+` and `+Owned(q)+` CN does not automatically -learn `+(p != q)+`, even though that’s clearly implied. This was -turned off for performance reasons at some point, but once -performance is back to normal again it should come back. In the mean -time, we have to add `+(p != q)+` as an additional precondition to -`+call_both+`. - -== Allocating and Deallocating Memory - -At the moment, CN does not understand the `+malloc+` and `+free+` -functions. They are a bit tricky because they rely on a bit of -polymorphism and a typecast between `+char*+` -- the result type of -`+malloc+` and argument type of `+free+` -- and the actual type of the -object being allocated or deallocated. - -However, for any given type, we can define a type-specific function -that allocates heap storage with exactly that type. The -implementation of this function cannot be checked by CN, but we can -give just the spec, together with a promise to link against an -external C library providing the implementation: -[source,c] ----- -include::exercises/malloc.h[] ----- - -(Alternatively we can include an implementation written in arbitrary C -inside a CN file by marking it with the keyword `+trusted+` at the top -of its CN specification.) - -Similarly: -[source,c] ----- -include::exercises/free.h[] ----- - -Now we can write code that allocates and frees memory: -[source,c] ----- -include::exercises/slf17_get_and_free.c[] ----- - -We can also define a "safer", ML-style version of `+malloc+` that -handles both allocation and initialization: - -[source,c] ----- -include::exercises/ref.h[] ----- - -//// -BCP: This example is a bit broken: the file `+slf0_basic_incr.c+` does not appear at all in the tutorial, though a slightly different version (with signed numbers) does... -//// - -[source,c] ----- -include::exercises/slf16_basic_succ_using_incr.c[] ----- - -=== Exercises - -// BCP: There should be a non-ref-using version of this earlier, for comparison. - -Prove a specification for the following program that reveals *only* -that it returns a pointer to a number that is greater than the number -pointed to by its argument. -// CHT: agree with bcp, it's a bit of a jump (I can look into adding this) - -[source,c] ----- -include::exercises/slf_ref_greater.c[] ----- - -== Side Note - -Here is another syntax for external / unknown -functions, together with an example of a loose specification: - -//// -BCP: This is a bit random -- it's not clear people need to know about this alternate syntax, and it's awkwardly mixed with a semi-interesting example that's not relevant to this section. -//// - -[source,c] ----- -include::exercises/slf18_two_dice.c[] ----- - -== Lists - -Now it's time to look at some more interesting heap structures. - -To begin with, here is a C definition for linked list cells, together -with allocation and deallocation functions: - -// CHT: it's surprising to me that CN lets you take Block(null). -// this seems to be the cause of the return != null check with the comment saying it should not be needed - -[source,c] ----- -include::exercises/list1.h[] ----- - -To write specifications for C functions that manipulate lists, we need -to define a CN "predicate" that describes *mathematical* list -structures, as one would do in ML, Haskell, or Coq. (We call them -"sequences" here to avoid overloading the word "list".) - - -Intuitively, the `+IntList+` predicate walks over a pointer structure -in the C heap and extracts an `+Owned+` version of the mathematical -list that it represents. - -[source,c] ----- -include::exercises/list2.h[] ----- - -We can also write specification-level "functions" by ordinary -functional programming (in slightly strange, unholy-union-of-C-and-ML -syntax): - -[source,c] ----- -include::exercises/list3.h[] ----- - -We use the `+IntList+` predicate to specify functions returning the -empty list and the cons of a number and a list. - -[source,c] ----- -include::exercises/list4.h[] ----- - -Finally, we can collect all this stuff into a single header file and -add the usual C `+#ifndef+` gorp to avoid complaints from the compiler -if it happens to get included twice from the same source file later. - -[source,c] ----- -include::exercises/list.h[] ----- - -//// -BCP: What does this comment mean? - 'return != NULL' should not be needed -//// - -=== Append - -With this basic infrastructure in place, we can start specifying and -verifying list-manipulating functions. First, `+append+`. - -Here is its specification (in a separate file, because we'll want to -use it multiple times below.) - -[source,c] ----- -include::exercises/append.h[] ----- - -Here is a simple destructive `+append+` function. Note the two uses -of the `+unfold+` annotation in the body, which are needed to help the -CN typechecker. - -// BCP: Can someone add a more technical explanation of why they are needed and exactly what they do? -// CHT: I'm reading this as similar to Rocq's "unfold", and as necessary because doing it eagerly could -// cause nontermination. Is that accurate? - -[source,c] ----- -include::exercises/append.c[] ----- - -=== List copy - -Here is an allocating list copy function with a pleasantly light -annotation burden. - -[source,c] ----- -include::exercises/list_copy.c[] ----- - -=== Merge sort - -Finally, here is a slightly tricky in-place version of merge sort that -avoids allocating any new list cells in the splitting step by taking -alternate cells from the original list and linking them together into -two new lists of roughly equal lengths. - -[source,c] ----- -include::exercises/mergesort.c[] ----- - -=== Exercises - -*Allocating append*. Fill in the CN annotations on -`+IntList_append2+`. (You will need some in the body as well as at -the top.) - -[source,c] ----- -include::exercises/append2.c[] ----- - -Note that it would not make sense to do the usual -functional-programming trick of copying xs but sharing ys. (Why?) - -*Length*. Add annotations as appropriate: - -[source,c] ----- -include::exercises/list_length.c[] ----- - -// CHT: I used this file to test CN's behavior with nonterminating programs, and I think -// it could use some explanation for users with limited experience with partial correctness - does -// this already exist somewhere? - -*List deallocation*. Fill in the body of the following procedure and -add annotations as appropriate: - -[source,c] ----- -include::exercises/list_free.c[] ----- - -*Length with an accumulator*. Add annotations as appropriate: -// BCP: Removing / forgetting the unfold in this one gives a truly -// bizarre error message saying that the constraint "n == (n + length(L1))" -// is unsatisfiable... - -[source,c] ----- -include::exercises/slf_length_acc.c[] ----- - -== Working with External Lemmas - -**TODO**: This section should also show what the proof of the lemmas -looks like on the Coq side! - -// BCP: This needs to be filled in urgently!! - -=== List reverse - -The specification of list reversal in CN relies on the familiar -recursive list reverse function, with a recursive helper. - -[source,c] ----- -include::exercises/list_rev_spec.h[] ----- - -// CHT: I believe the definition of snoc is wrong (the body is just a copy of rev), should be: -// function [rec] (datatype seq) snoc(datatype seq xs, i32 y) { -// match xs { -// Seq_Nil {} => { -// Seq_Cons {head: y, tail : Seq_Nil {}} -// } -// Seq_Cons {head : h, tail : zs} => { -// Seq_Cons {head: h, tail : snoc(zs, y)} -// } -// } -// } - -To reason about the C implementation of list reverse, we need to help -the SMT solver by enriching its knowledge base with a couple of facts -about lists. The proofs of these facts require induction, so in CN we -simply state them as lemmas and defer the proofs to Coq. - -[source,c] ----- -include::exercises/list_rev_lemmas.h[] ----- - -Having stated these lemmnas, we can now complete the specification and -proof of `+IntList_rev+`. Note the two places where `+apply+` is used -to tell the SMT solver where to pay attention to the lemmas. - -//// -BCP: Why can't it always pay attention to them? (I guess -"performance", but at least it would be nice to be able to declare a -general scope where a given set of lemmas might be needed, rather than -specifying exactly where to use them.) -//// - -[source,c] ----- -include::exercises/list_rev.c[] ----- - -For comparison, here is another way to write the program, using a -while loop instead of recursion, with its specification and proof. - -// BCP: Why 0 instead of NULL?? (Is 0 better?) - -[source,c] ----- -include::exercises/list_rev_alt.c[] ----- - -=== Exercises - -**Sized stacks:** Fill in annotations where requested: - -[source,c] ----- -include::exercises/slf_sized_stack.c[] ----- - -// CHT: the solution here seems underspecified (e.g. S_.d == tl(S.d); return == hd(S.d)), -// and the need for unfold in exactly one branch in the solution (and both with the above constraints added) -// is surprising; is there intuition for the unfold placement here? - -// TODO: Trees: -// - deep copy -// - sum -// - maybe the accumulating sum - -//// -Further exercises: - - Some exercises that get THEM to write predicates, datatype - declarations, etc. - -Misc things to do: - - create a top-level TAGS file for emacs - - Figure out the smoothest way to do multiple includes - - Figure out how to display filenames -//// diff --git a/src/tutorial-with-ps-comments.adoc b/src/tutorial-with-ps-comments.adoc deleted file mode 100644 index 7723960c..00000000 --- a/src/tutorial-with-ps-comments.adoc +++ /dev/null @@ -1,964 +0,0 @@ -:source-highlighter: pygments -:pygments-style: manni -// :pygments-style: tango -:nofooter: -:prewrap!: - - -++++ - -++++ - - -**TODO PS: the tutorial doesn't say that you have to `make` in the tutorial to get the `exercises` and `solutions` directories** - -**TODO PS: the build still fails for me, complaining `/bin/sh: 1: [[: not found -R` ** - -= CN tutorial - -CN is a type system for verifying C code, focusing especially on low-level systems code. Compared to the normal C type system, CN checks not only that expressions and statements follow the correct typing discipline for C-types, but also that the C code executes _safely_ — does not raise C undefined behaviour — and _correctly_ — according to strong user-defined specifications. To accurately handle the complex semantics of C, CN builds on the https://github.com/rems-project/cerberus/[Cerberus semantics for C]. - -This tutorial introduces CN along a series of examples, starting with basic usage of CN on simple arithmetic functions and slowly moving towards more elaborate separation logic specifications of data structures. - -Many examples are taken from Arthur Charguéraud’s excellent https://softwarefoundations.cis.upenn.edu[Separation Logic Foundations]. These example have names starting with "`slf...`". - -== Installation - -To fetch and install CN, check the Cerberus repository at https://github.com/rems-project/cerberus and follow the instructions in https://github.com/rems-project/cerberus/blob/master/backend/cn/INSTALL.md[backend/cn/INSTALL.md]. - -Once completed, type `+cn --help+` in your terminal to ensure CN is installed and found by your system. This should print the list of available options CN can be executed with. - -To apply CN to a C file, run `+cn CFILE+`. - -== Basic usage - -=== First example - -For a first example, let’s look at a simple arithmetic function: `+add+`, shown below, takes two `+int+` arguments, `+x+` and `+y+`, and returns their sum. - -**TODO PS: this says `cn exercises/0.c` but the examples seem to be in `CN-tutorial/src/examples`. And the checked-in `0.c` is the version with the CN precondition, not the version in the tutorial `built_doc` without. ** - -[source,c] ----- -include::exercises/0.c[] ----- - -Running CN on the example produces an error message: - -.... -cn exercises/0.c -[1/1]: add -exercises/0.c:3:10: error: Undefined behaviour - return x+y; - ~^~ -an exceptional condition occurs during the evaluation of an expression (§6.5#5) -Consider the state in /var/folders/_v/ndl32wpj4bb3y9dg11rvc8ph0000gn/T/state_393431.html -.... - -**TODO PS: on ubuntu the file ends up in `/tmp/state_35eadb.html`, and for some reason I don't understand, firefox refuses to see it or open it. I had to install `lynx` to look at it. Does this output really exploit html, or could it just (maybe optionally with a CN command-line flag?) print to the terminal? ** - -**TODO PS: the CN error message `an exceptional condition occurs during the evaluation of an expression (§6.5#5)` is not super-informative. It won't even be clear to most people (who are not reading the tutorial when they see it) that 6.5#5 is a reference to a place in the C standard, or where they can find that (http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf is a publically accessible final committee draft for C11). Kayvan has the corresponding text; I don't know whether it's trivial to print that paragraph, which explains what an exceptional condition is? ("If an exceptional condition occurs during the evaluation of an expression (that is, if the -result is not mathematically defined or not in the range of representable values for its type), the behavior is undefined.")** - - -CN rejects the program because it has _C undefined behaviour_, meaning it is not safe to execute. CN points to the relevant source location, the addition `+x+y+`, and paragraph §6.5#5 of the C language standard that specifies the undefined behaviour. It also puts a link to an HTML file with more details on the error to help in diagnosing the problem. - -Inspecting this HTML report (as we do in a moment) gives us possible example values for `+x+` and `+y+` that cause the undefined behaviour and hint at the problem: for very large values for `+x+` and `+y+`, such as `+1073741825+` and `+1073741824+`, the sum of `+x+` and `+y+` can exceed the representable range of a C `+int+` value: `+1073741825 + 1073741824 = 2^31+1+`, so their sum is larger than the maximal `+int+` value, `+2^31-1+`. - -Here `+x+` and `+y+` are _signed integers_, and in C, signed integer _overflow_ is undefined behaviour (UB). Hence, `+add+` is only safe to execute for smaller values. Similarly, _large negative_ values of `+x+` and `+y+` can cause signed integer _underflow_, also UB in C. We therefore need to rule out too large values for `+x+` and `+y+`, both positive and negative, which we do by writing a CN function specification. - -=== First function specification - -Shown below is our first function specification, for `+add+`, with a precondition that constraints `+x+` and `+y+` such that the sum of `+x+` and `+y+` lies between `+-2147483648+` and `+2147483647+`, so within the representable range of a C `+int+` value. - -[source,c] ----- -include::solutions/0.c[] ----- - -**TODO PS: I don't see a `solutions` directory - that example seems to be the `examples/0.c` ** - -In detail: - -* Function specification are given using special `+/*@ ... @*/+` comments, placed in-between the function argument list and the function body. - -* The keyword `+requires+` starts the precondition, a list of one or more CN conditions separated by semicolons. - -* In function specifications, the names of the function arguments, here `+x+` and `+y+`, refer to their _initial values_. (Function arguments are mutable in C.) - -* `+let sum = (i64) x + (i64) y+` is a let-binding, which defines `+sum+` as the value `+(i64) x + (i64) y+` in the remainder of the function specification. - -* Instead of C syntax, CN uses Rust-like syntax for integer types, such as `+u32+` for 32-bit unsigned integers and `+i64+` for signed 64-bit integers to make their sizes unambiguous. Here, `+x+` and `+y+`, of C-type `+int+`, have CN type `+i32+`. - -**TODO PS: those are not ISO C names, but they are Linux kernel integer type names (in addition to being Rust-like)** - -* To define `+sum+` we cast `+x+` and `+y+` to the larger `+i64+` type, using syntax `+(i64)+`, which is large enough to hold the sum of any two `+i32+` values. - -**TODO PS: The bitvector world does push us into a fairly horrible spec here. E.g., what is the user supposed to do if the arguments are already 64-bit types?** - -* Finally, we require this sum to be in-between the minimal and maximal `+int+` value. Integer constants, such as `+-2147483648i64+`, must specifiy their CN type (`+i64+`). - -Running CN on the annotated program passes without errors. This means with our specified precondition, `+add+` is safe to execute. - -We may, however, wish to be more precise. So far the specification gives no information to callers of `+add+` about its output. To also specify the return values we add a postcondition, using the `+ensures+` keyword. - -[source,c] ----- -include::solutions/1.c[] ----- - -Here we use the keyword `+return+`, only available in function postconditions, to refer to the return value, and equate it to `+sum+` as defined in the preconditions, cast back to `+i32+` type: `+add+` returns the sum of `+x+` and `+y+`. - -Running CN confirms that this postcondition also holds. - -=== Error reports - -In the original example CN reported a type error due to C undefined behaviour. While that example was perhaps simple enough to guess the problem and solution, this can become quite challenging as program and specification complexity increases. Diagnosing type errors is therefore an important part of using CN. CN tries to help with that by producting detailed error information, in the form of an HTML error report. - -**TODO PS: typo `producting` -> `producing` ** - -Let’s return to the type error from earlier (`+add+` without precondition) and take a closer look at this report. The report comprises two sections. - -.*CN error report* -image::images/0.error.png[*CN error report*] - -**TOOD PS: figure doesn't appear in the `built_doc` version ** - -*Path.* The first section, "`Path to error`", contains information about the control-flow path leading to the error. - -When type checking a C function, CN checks each possible control-flow path through the program individually. If CN detects UB or a violation of user-defined specifications, CN reports the problematic control-flow path, as a nested structure of statements: paths are split into sections, which group together statements between high-level control-flow positions (e.g. function entry, the start of a loop, the invocation of a `+continue+`, `+break+`, or `+return+` statement, etc.); within each section, statements are listed by source code location; finally, per statement, CN lists the typechecked sub-expressions, and the memory accesses and function calls within these. - -In our example, there is only one possible control-flow path: entering the function body (section "`function body`") and executing the block from lines 2 to 4, followed by the return statement at line 3. The entry for the latter contains the sequence of sub-expressions in the return statement, including reads of the variables `+x+` and `+y+`. - -In C, local variables in a function, including its arguments, are mutable and their address can be taken and passed as a value. CN therefore represents local variables as memory allocations that are manipulated using memory reads and writes. Here, type checking the return statement includes checking memory reads for `+x+` and `+y+`, at their locations `+&ARG0+` and `+&ARG1+`. The path report lists these reads and their return values: the read at `+&ARG0+` returns `+x+` (that is, the value of `+x+` originally passed to `+add+`); the read at `+&ARG1+` returns `+y+`. Alongside this symbolic information, CN displays concrete values: - -* `+1073741825i32 /* 0x40000001 */+` for x (the first value is the decimal representation, the second, in `+/*...*/+` comments, the hex equivalent) and - -* `+1073741824i32 /* 0x40000000 */+` for `+y+`. - -For now, ignore the pointer values `+{@0; 4}+` for `+x+` and `+{@0; 0}+` for `+y+`. - -These concrete values are part of a _counterexample_: a concrete valuation of variables and pointers in the program that that leads to the error. (The exact values may vary on your machine, depending on the version of Z3 installed on your system.) - -*Proof context.* The second section, below the error trace, lists the proof context CN has reached along this control-flow path. - -"`Available resources`" lists the owned resources, as discussed in later sections. - -"`Variables`" lists counterexample values for program variables and pointers. In addition to `+x+` and `+y+`, assigned the same values as above, this includes values for their memory locations `+&ARG0+` and `+&ARG1+`, function pointers in scope, and the `+__cn_alloc_history+`, all of which we ignore for now. - -Finally, "`Constraints`" records all logical facts CN has learned along the path. This includes user-specified assumptions from preconditions or loop invariants, value ranges inferred from the C-types of variables, and facts learned during the type checking of the statements. Here (`+add+` without precondition) the only constraints are some contraints inferred from C-types in the code. - -**TODO PS: typo `contraints` (spell-check the thing)** - -**TODO PS: delete `For instance,` below** - -* For instance, `+good(x)+` says that the initial value of `+x+` is a "`good`" `+signed int+` value (i.e. in range). Here `+signed int+` is the same type as `+int+`, CN just makes the sign explicit. For integer types `+T+`, `+good+` requires the value to be in range of type `+T+`; for pointer types `+T+` it also requires the pointer to be aligned. For structs and arrays this extends in the obvious way to struct members or array cells. - -* `+repr+` requires representability (not alignment) at type `+T+`, so `+repr(&ARGO)+`, for instance, records that the pointer to `+x+` is representable at C-type `+signed int*+`; - - -**TODO PS: I don't know what that means. If I take it literally, it'd normally be false, as on 64-bit archs, pointers are typically *not* representable at `signed int` or `unsigned int` types.** - -* `+aligned(&ARGO, 4u64)+`, moreover, states that it is 4-byte aligned. - -=== Another arithmetic example - -Let’s apply what we know so far to another simple arithmetic example. - -The function `+doubled+`, shown below, takes an int `+n+`, defines `+a+` as `+n+` incremented, `+b+` as `+n+` decremented, and returns the sum of the two. - -[source,c] ----- -include::exercises/slf1_basic_example_let.signed.c[] ----- - -We would like to verify this is safe, and that `+doubled+` returns twice the value of `+n+`. Running CN on `+doubled+` leads to a type error: the increment of `+a+` has undefined behaviour. - -**TODO PS: when explaining CN, perhaps we want to use another phrase to talk about refinement-type errors: the user will typically expect "type error" to refer to something like a C type error. Perhaps "verification error"?? ** - -As in the first example, we need to ensure that `+n+1+` does not overflow and `+n-1+` does not underflow. Similarly also `+a+b+` has to be representable at `+int+` type. - -[source,c] ----- -include::solutions/slf1_basic_example_let.signed.c[] ----- - -We can specify these using a similar style of precondition as in the first example. We first define `+n_+` as `+n+` cast to type `+i64+` — i.e. a type large enough to hold `+n+1+`, `+n-1+` and `+a+b+` for any possible `+i32+` value for `+n+`. Then we specify that decrementing `+n_+` does not go below the minimal `+int+` value, that incrementing `+n_+` does not go above the maximal value, and that `+n+` doubled is also in range. These preconditions together guarantee safe execution. - -To capture the functional behaviour, the postcondition specifies that `+return+` is twice the value of `+n+`. - -=== Exercise - -*Quadruple.* Specify the precondition needed to ensure safety of the C function `+quadruple+`, and a postcondition that describes its return value. - -[source,c] ----- -include::exercises/slf2_basic_quadruple.signed.c[] ----- - -*Abs.* Give a specification to the C function `+abs+`, which computes the absolute value of a given `+int+` value. To describe the return value, use CN’s ternary "`+_ ? _ : _+`" operator. Given a boolean `+b+`, and expressions `+e1+` and `+e2+` of the same basetype, `+b ? e1 : e2+` returns `+e1+` if `+b+` holds and `+e2+` otherwise. - -[source,c] ----- -include::exercises/abs.c[] ----- - -== Pointers and simple ownership - -So far we’ve only considered example functions manipulating integer values. Verification becomes more interesting and challenging when _pointers_ are involved, because the safety of memory accesses via pointers has to be verified. - -CN uses _separation logic resource types_ and the concept of _ownership_ to reason about memory accesses. A resource is the permission to access a region of memory. Unlike logical constraints, resource ownership is _unique_, meaning resources cannot be duplicated. - -Let’s look at a simple example. The function `+read+` takes an `+int+` pointer `+p+` and returns the pointee value. - -[source,c] ----- -include::exercises/read.c[] ----- - -Running CN on this example produces the following error: - -.... -cn exercises/read.c -[1/1]: read -exercises/read.c:3:10: error: Missing resource for reading - return *p; - ^~ -Resource needed: Owned(p) -Consider the state in /var/folders/_v/ndl32wpj4bb3y9dg11rvc8ph0000gn/T/state_403624.html -.... - -For the read `+*p+` to be safe, ownership of a resource is missing: a resource `+Owned(p)+`. - -=== The Owned resource type - -Given a C-type `+T+` and pointer `+p+`, the resource `+Owned(p)+` asserts ownership of a memory cell at location `+p+` of the size of C-type `+T+`. It is is CN’s equivalent of a points-to assertion in separation logic (indexed by C-types `+T+`). - -In this example we can ensure the safe execution of `+read+` by adding a precondition that requires ownership of `+Owned(p)+`, as shown below. For now ignore the notation `+take ... = Owned(p)+`. Since `+read+` maintains this ownership, we also add a corresponding postcondition, whereby `+read+` returns ownership of `+p+` after it is finished executing, in the form of another `+Owned(p)+` resource. - -[source,c] ----- -include::solutions/read.c[] ----- - -This specifications means that - -* any function calling `+read+` has to be able to provide a resource `+Owned(p)+` to pass into `+read+`, and - -* the caller will receive back a resource `+Owned(p)+` when `+read+` returns. - -=== Resource outputs - -However, a caller of `+read+` may also wish to know that `+read+` actually returns the correct value, the pointee of `+p+`, and also that it does not change memory at location `+p+`. To phrase both we need a way to refer to the pointee of `+p+`. - -In CN resources have _outputs_. Each resource outputs the information that can be derived from ownership of the resource. What information is returned is specific to the type of resource. A resource `+Owned(p)+` (for some C-type `+T+`) outputs the _pointee value_ of `+p+`, since that can be derived from the resource ownership: assume you have a pointer `+p+` and the associated ownership, then this uniquely determines the pointee value of `+p+`. - -CN uses the `+take+`-notation seen in the example above to refer to the output of a resource, introducing a new name binding for the output. The precondition `+take v1 = Owned(p)+` from the precondition does two things: (1) it assert ownership of resource `+Owned(p)+`, and (2) it binds the name `+v1+` to the resource output, here the pointee value of `+p+` at the start of the function. Similarly, the postcondition introduces the name `+v2+` for the pointee value on function return. - -That means we can use the resource outputs from the pre- and postcondition to strengthen the specification of `+read+` as planned. We add two new postconditions: we specify - -. that `+read+` returns `+v1+` (the initial pointee value of `+p+`), and -. that the pointee values `+v1+` and `+v2+` before and after execution of `+read+` (respectively) are the same. - -[source,c] ----- -include::solutions/read2.c[] ----- - -*Aside.* In standard separation logic the equivalent specification for `+read+` could have been phrased as follows (where `+return+` binds the return value in the postcondition): - -.... -∀p. -∀v1. { p ↦ v1 } - read(p) - { return. ∃v2. (p ↦ v2) /\ return = v1 /\ v1 = v2 } -.... - -CN’s `+take+` notation is just alternative syntax for quantification over the values of resources, but a useful one: the `+take+` notation syntactically restricts how these quantifiers can be used to ensure CN can always infer them. - -=== Exercises - -*Quadruple*. Specify the function `+quadruple_mem+`, that is similar to the earlier `+quadruple+` function, except that the input is passed as an `+int+` pointer. Write a specification that takes ownership of this pointer on entry and returns this ownership on exit, leaving the pointee value unchanged. - -[source,c] ----- -include::exercises/slf_quadruple_mem.c[] ----- - -*Abs*. Give a specification to the function `+abs_mem+`, which computes the absolute value of a number passed as an `+int+` pointer. - -[source,c] ----- -include::exercises/abs_mem.c[] ----- - -=== Linear resource ownership - -In the specifications we have written so far, functions that receive resources as part of their precondition also return this ownership in their postcondition. - -Let’s try the `+read+` example from earlier again, but with a postcondition that does not return the ownership: - -[source,c] ----- -include::exercises/read.broken.c[] ----- - -CN rejects this program with the following message: - -.... -cn build/exercises/read.broken.c -[1/1]: read -build/exercises/read.broken.c:4:3: error: Left-over unused resource 'Owned(p)(v1)' - return *p; - ^~~~~~~~~~ -Consider the state in /var/folders/_v/ndl32wpj4bb3y9dg11rvc8ph0000gn/T/state_17eb4a.html -.... - -CN has typechecked the function, verified that it is safe to execute under the precondition (given ownership `+Owned(p)+`), and that the function (vacuously) satisfies its postcondition. But, following the check of the postcondition it finds that not all resources have been "`used up`". - -Given the above specification, `+read+` leaks memory: it takes ownership, does not return it, but also does not deallocate the owned memory or otherwise dispose of it. In CN this is a type error. - -CN’s resource types are _linear_ (as opposed to affine). This means not only that resources cannot be duplicated, but also that resources cannot simply be dropped or "`forgotten`". Every resource passed into a function has to either be used up by it, by returning it or passing it to another function that consumes it, or destroyed, by deallocating the owned area of memory (as we shall see later). - -CN’s motivation for linear tracking of resources is its focus on low-level systems software. CN checks C programs, in which, unlike higher-level garbage-collected languages, memory is managed manually, and memory leaks are typically very undesirable. - -As a consequence, function specifications have to do precise "`book-keeping`" of their resource footprint, and, in particular, return any unused resources back to the caller. - -=== The Block resource type - -Aside from the `+Owned+` resource seen so far, CN has another built-in resource type: `+Block+`. Given a C-type `+T+` and pointer `+p+`, `+Block(p)+` asserts the same ownership as `+Owned(p)+` — so ownership of a memory cell at `+p+` the size of type `+T+` — but in contrast to `+Owned+`, `+Block+` memory is not necessarily initialised. - -CN uses this distinction to prevent reads from uninitialised memory: - -* A read at C-type `+T+` and pointer `+p+` requires a resource `+Owned(p)+`, i.e., ownership of _initialised_ memory at the right C-type. The load returns the `+Owned+` resource unchanged. - -* A write at C-type `+T+` and pointer `+p+` needs only a `+Block(p)+` (so, unlike reads, writes to uninitialised memory are fine). The write consumes ownership of the `+Block+` resource (it destroys it) and returns a new resource `+Owned(p)+` with the value written as the output. This means the resource returned from a write records the fact that this memory cell is now initialised and can be read from. - -Since `+Owned+` carries the same ownership as `+Block+`, just with the additional information that the `+Owned+` memory is initalised, a resource `+Owned(p)+` is "`at least as good`" as `+Block(p)+` — an `+Owned(p)+` resource can be used whenever `+Block(p)+` is needed. For instance CN’s type checking of a write to `+p+` requires a `+Block(p)+`, but if an `+Owned(p)+` resource is what is available, this can be used just the same. This allows an already-initialised memory cell to be over-written again. - -Unlike `+Owned+`, whose output is the pointee value, `+Block+` has no meaningful output: its output is `+void+`/`+unit+`. - -=== Write example - -Let’s explore resources and their outputs in another example. The C function `+incr+` takes an `+int+` pointer `+p+` and increments the pointee value. - -[source,c] ----- -include::solutions/slf0_basic_incr.signed.c[] ----- - -In the precondition we assert ownership of resource `+Owned(p)+`, binding its output/pointee value to `+v1+`, and use `+v1+` to specify that `+p+` must point to a sufficiently small value at the start of the function not to overflow when incremented. The postcondition asserts ownership of `+p+` with output `+v2+`, as before, and uses this to express that the value `+p+` points to is incremented by `+incr+`: `+v2 == v1+1i32+`. - -If we incorrectly tweaked this specification and used `+Block(p)+` instead of `+Owned(p)+` in the precondition, as below, then CN would reject the program. - -[source,c] ----- -include::exercises/slf0_basic_incr.signed.broken.c[] ----- - -CN reports: - -.... -build/solutions/slf0_basic_incr.signed.broken.c:6:11: error: Missing resource for reading - int n = *p; - ^~ -Resource needed: Owned(p) -Consider the state in /var/folders/_v/ndl32wpj4bb3y9dg11rvc8ph0000gn/T/state_5da0f3.html -.... - -The `+Owned(p)+` resource required for reading is missing, since, as per precondition, only `+Block(p)+` is available. Checking the linked HTML file confirms this. Here the section "`Available resources`" lists all resource ownership at the point of the failure: - -* `+Block(p)(u)+`, so ownership of uninitialised memory at location `+p+`; the output is a `+void+`/`+unit+` value `+u+` (specified in the second pair of parentheses) - -* `+Owned(&ARG0)(p)+`, the ownership of (initialised) memory at location `+&ARG0+`, so the memory location where the first function argument is stored; its output is the pointer `+p+` (not to be confused with the pointee of `+p+`); and finally - -* `+__CN_Alloc(&ARG0)(void)+` is a resource that records allocation information for location `+&ARG0+`; this is related to CN’s memory-object semantics, which we ignore for the moment. - -=== Exercises - -*Zero.* Write a specification for the function `+zero+`, which takes a pointer to _uninitialised_ memory and initialises it to `+0+`. - -[source,c] ----- -include::exercises/zero.c[] ----- - -*In-place double.* Give a specification for the function `+inplace_double+`, which takes an `+int+` pointer `+p+` and doubles the pointee value: specify the precondition needed to guarantee safe execution and a postcondition that captures the function’s behaviour. - -[source,c] ----- -include::exercises/slf3_basic_inplace_double.c[] ----- - -=== Multiple owned pointers - -When functions manipulate multiple pointers, we can assert their ownership just like before. However (as in standard separation logic) pointer ownership is unique, so simultaneous ownership of `+Owned+` or `+Block+` resources for two pointers requires these pointers to be disjoint. - -The following example shows the use of two `+Owned+` resources for accessing two different pointers: function `+add+` reads two `+int+` values in memory, at locations `+p+` and `+q+`, and returns their sum. - -[source,c] ----- -include::exercises/add_read.c[] ----- - -This time we use C’s `+unsigned int+` type. In C, over- and underflow of unsigned integers is not undefined behaviour, so we do not need any special preconditions to rule this out. Instead, when an arithmetic operation at unsigned type goes outside the representable range, the value "`wraps around`". - -The CN variables `+m+` and `+n+` (resp. `+m2+` and `+n2+`) for the pointee values of `+p+` and `+q+` before (resp. after) the execution of `+add+` have CN basetype `+u32+`, so unsigned 32-bit integers, matching the C `+unsigned int+` type. Like C’s unsigned integer arithmetic, CN unsigned int values wrap around when exceeding the value range of the type. - -Hence, the postcondition `+return == m+n+` holds also when the sum of `+m+` and `+n+` is greater than the maximal `+unsigned int+` value. - -In the following we will sometimes use unsigned integer types to focus on specifying memory ownership, rather than the conditions necessary to show absence of C arithmetic undefined behaviour. - -=== Exercises - -*Swap.* Specify the function `+swap+`, which takes two owned `+unsigned int+` pointers and swaps their values. - -[source,c] ----- -include::exercises/swap.c[] ----- - -*Transfer.* Write a specification for the function `+transfer+`, shown below. - -[source,c] ----- -include::exercises/slf8_basic_transfer.c[] ----- - -== Ownership of compound objects - -So far all examples have worked with just integers and pointers, but larger programs typically also manipulate compound values, often represented using C struct types. Specifying functions manipulating structs works in much the same way as with basic types. - -For instance, the following example uses a `+struct+` `+point+` to represent a point in two-dimensional space. The function `+transpose+` swaps a point’s `+x+` and `+y+` coordinates. - -[source,c] ----- -include::exercises/transpose.c[] ----- - -Here the precondition asserts ownership for `+p+`, at type `+struct point+`; the output `+s+` is a value of CN type `+struct point+`, i.e. a record with members `+i32+` `+x+` and `+i32+` `+y+`. The postcondition similarly asserts ownership of `+p+`, with output `+s2+`, and asserts the coordinates have been swapped, by referring to the members of `+s+` and `+s2+` individually. - -=== Compound Owned and Block resources - -While one might like to think of a struct as a single (compound) object that is manipulated as a whole, C permits more flexible struct manipulation: given a struct pointer, programmers can construct pointers to _individual struct members_ and pass these as values, even to other functions. - -CN therefore cannot treat resources for compound C types, such as structs, as primitive, indivisible units. Instead, `+Owned+` and `+Block+` are defined inductively in the structure of the C-type `+T+`. - -For struct types `+T+`, the `+Owned+` resource is defined as the collection of `+Owned+` resources for its members (as well as `+Block+` resources for any padding bytes in-between). The resource `+Block+`, similarly, is made up of `+Block+` resources for all members (and padding bytes). - -To handle code that manipulates pointers into parts of a struct object, CN can automatically decompose a struct resource into the member resources, and recompose it, as needed. The following example illustrates this. - -Recall the function `+zero+` from our earlier exercise. It takes an `+int+` pointer to uninitialised memory, with `+Block+` ownership, and initialises the value to zero, returning an `+Owned+` resource with output `+0+`. - -Now consider the function `+init_point+`, shown below, which takes a pointer `+p+` to a `+struct point+` and zero-initialises its members by calling `+zero+` twice, once with a pointer to struct member `+x+`, and once with a pointer to `+y+`. - -[source,c] ----- -include::exercises/init_point.c[] ----- - -As stated in its precondition, `+init_point+` receives ownership `+Block(p)+`. The `+zero+` function, however, works on `+int+` pointers and requires `+Block+` ownership. - -CN can prove the calls to `+zero+` with `+&p->x+` and `+&p->y+` are safe because it decomposes the `+Block(p)+` into two `+Block+`, one for member `+x+`, one for member `+y+`. Later, the reverse happens: following the two calls to `+zero+`, as per `+zero+`’s precondition, `+init_point+` has ownership of two adjacent `+Owned+` resources – ownership for the two struct member pointers, with the member now initialised. Since the postcondition of `+init_point+` requires ownership `+Owned(p)+`, CN combines these back into a compound resource. The resulting `+Owned+` resource has for an output the struct value `+s2+` that is composed of the zeroed member values for `+x+` and `+y+`. - -=== Resource inference - -To handle the required resource inference, CN "`eagerly`" decomposes all `+struct+` resources into resources for the struct members, and "`lazily`" re-composes them as needed. - -We can see this if, for instance, we experimentally change the `+transpose+` example from above to force a type error. Let’s insert an `+/*@ assert(false) @*/+` CN assertion in the middle of the `+transpose+` function (more on CN assertions later), so we can inspect CN’s proof context shown in the error report. - -[source,c] ----- -include::exercises/transpose.broken.c[] ----- - -The precondition of `+transpose+` asserts ownership of an `+Owned(p)+` resource. The error report now instead lists under "`Available resources`" two resources: - -* `+Owned(member_shift(p, x))+` with output `+s.x+` and - -* `+Owned(member_shift(p, y))+` with output `+s.y+` - -Here `+member_shift(p,m)+` is the CN expression that constructs, from a `+struct s+` pointer `+p+`, the "`shifted`" pointer for its member `+m+`. - -When the function returns the two member resources are recombined "`on demand`" to satisfy the postcondition `+Owned(p)+`. - -=== Exercises - -*Init point.* Insert CN `+assert(false)+` statements in different statement positions of `+init_point+` and check how the available resources evolve. - -*Transpose (again).* Recreate the transpose function from before, now using the swap function verified earlier (for `+struct upoint+`, with unsigned member values). - -[source,c] ----- -include::exercises/transpose2.c[] ----- - -//// -BCP: Some more things to think about including... - - Something about CN's version of the frame rule (see - bcp_framerule.c, though the example is arguably a bit - unnatural). - - Examples from Basic.v with allocation - there are lots of - interesting ones! -CP: Agreed. For now continuing with arrays, but will return to this later. -//// - -== Arrays and loops - -Another common datatype in C is arrays. Reasoning about memory ownership for arrays is more difficult than for the datatypes we have seen so far: C allows the programmer to access arrays using _computed pointers_, and the size of an array does not need to be known as a constant at compile time. - -To support reasoning about code manipulating arrays and computed pointers, CN has _iterated resources_. For instance, to specify ownership of an `+int+` array with 10 cells starting at pointer `+p+`, CN uses the iterated resource - -[source,c] ----- -each (i32 i; 0i32 <= i && i < 10i32) - { Owned(array_shift(p,i)) } ----- - -In detail, this can be read as follows: - -* for each integer `+i+` of CN type `+i32+`, … - -* if `+i+` is between `+0+` and `+10+`, … - -* assert ownership of a resource `+Owned+` … - -* for cell `+i+` of the array with base-address `+p+`. - -Here `+array_shift(p,i)+` computes a pointer into the array at pointer `+p+`, appropriately offset for index `+i+`. - -In general, iterated resource specifications take the form - -[source,c] ----- -each (BT Q; GUARD) { RESOURCE } ----- - -comprising three parts: - -* `+BT Q+`, for some CN type `+BT+` and name `+Q+`, introduces the quantifier `+Q+` of basetype `+BT+`, which is bound in `+GUARD+` and `+RESOURCE+`; - -* `+GUARD+` is a boolean-typed expression delimiting the instances of `+Q+` for which ownership is asserted; and - -* `+RESOURCE+` is any non-iterated CN resource. - -=== First array example - -Let’s see how this applies to a first example of an array-manipulating function. Function `+read+` takes three arguments: the base pointer `+p+` of an `+int+` array, the length `+n+` of the array, and an index `+i+` into the array; `+read+` then returns the value of the `+i+`-th array cell. - -[source,c] ----- -include::exercises/array_load.broken.c[] ----- - -The CN precondition requires - -- ownership of the array on entry — one `+Owned+` resource for each array index between `+0+` and `+n+` — and -- that `+i+` lies within the range of owned indices. - -On exit the array ownership is returned again. - -This specification, in principle, should ensure that the access `+p[i]+` is safe. However, running CN on the example produces an error: CN is unable to find the required ownership for reading `+p[i]+`. - -.... -cn build/solutions/array_load.broken.c -[1/1]: read -build/solutions/array_load.broken.c:5:10: error: Missing resource for reading - return p[i]; - ^~~~ -Resource needed: Owned(array_shift(p, (u64)i)) -.... - -The reason is that when searching for a required resource, such as the `+Owned+` resource for `+p[i]+` here, CN’s resource inference does not consider iterated resources. Quantifiers, as used by iterated resources, can make verification undecidable, so, in order to maintain predictable type checking, CN delegates this aspect of the reasoning to the user. - -To make the `+Owned+` resource required for accessing `+p[i]+` available to CN’s resource inference we have to "`extract`" ownership for index `+i+` out of the iterated resource. - -[source,c] ----- -include::exercises/array_load.c[] ----- - -Here the CN comment `+/*@ extract Owned, i; @*/+` is a CN "`ghost statement`"/proof hint that instructs CN to instantiate any available iterated `+Owned+` resource for index `+i+`. In our example this operation splits the iterated resource into two: - -[source,c] ----- -each(i32 j; 0i32 <= j && j < n) { Owned(array_shift(p,j)) } ----- - -is split into - -1. the instantiation of the iterated resource at `+i+` -+ -[source,c] ----- -Owned(array_shift(p,i)) ----- -2. the remainder of the iterated resource, the ownership for all indices except `+i+` -+ -[source,c] ----- -each(i32 j; 0i32 <= j && j < n && j != i) - { Owned(array_shift(p,j)) } ----- - -After this extraction step, CN can use the (former) extracted resource to justify the access `+p[i]+`. - -Following an `+extract+` statement, CN moreover remembers the extracted index and can automatically "`reverse`" the extraction when needed: after type checking the access `+p[i]+` CN must ensure the function’s postcondition holds, which needs the full array ownership again (including the extracted index `+i+`); remembering the index `+i+`, CN then automatically merges resources (1) and (2) again to obtain the required full array ownership, and completes the verification of the function. - -So far the specification only guarantees safe execution but does not specify the behaviour of `+read+`. To address this, let’s return to the iterated resources in the function specification. When we specify `+take a1 = each ...+` here, what is `+a1+`? In CN, the output of an iterated resource is a _map_ from indices to resource outputs. In this example, where index `+j+` has CN type `+i32+` and the iterated resource is `+Owned+`, the output `+a1+` is a map from `+i32+` indices to `+i32+` values — CN type `+map+`. (If the type of `+j+` was `+i64+` and the resource `+Owned+`, `+a1+` would have type `+map+`.) - -We can use this to refine our specification with information about the functional behaviour of `+read+`. - -[source,c] ----- -include::exercises/array_load2.c[] ----- - -We specify that `+read+` does not change the array — the outputs `+a1+` and `+a2+`, before resp. after running the function, are the same — and that the value returned is `+a1[i]+`, `+a1+` at index `+i+`. - -=== Exercises - - -*Array read two.* Specify and verify the following function, `+array_read_two+`, which takes the base pointer `+p+` of an `+unsigned int+` array, the array length `+n+`, and two indices `+i+` and `+j+`. Assuming `+i+` and `+j+` are different, it returns the sum of the values at these two indices. - - -[source,c] ----- -include::exercises/add_two_array.c[] ----- - -//// -BCP: In this one I got quite tangled up in different kinds of integers, then got tangled up in (I think) putting the extract declarations in the wrong place. (I didn't save the not-working version, I'm afraid.) -//// - -*Swap array.* Specify and verify `+swap_array+`, which swaps the values of two cells of an `+int+` array. Assume again that `+i+` and `+j+` are different, and describe the effect of `+swap_array+` on the array value using the CN map update expression `+a[i:v]+`, which denotes the same map as `+a+`, except with index `+i+` updated to `+v+`. - -[source,c] ----- -include::exercises/swap_array.c[] ----- - -//// -BCP: I wrote this, which seemed natural but did not work -- I still don't fully understand why. I think this section will need some more examples / exercises to be fully digestible, or perhaps this is just yet another symptom of my imperfecdt understanding of how the numeric stuff works. - - void swap_array (int *p, int n, int i, int j) - /*@ requires take a1 = each(i32 k; 0i32 <= k && k < n) { Owned(array_shift(p,k)) }; - 0i32 <= i && i < n; - 0i32 <= j && j < n; - j != i; - take xi = Owned(array_shift(p,i)); - take xj = Owned(array_shift(p,j)) - ensures take a2 = each(i32 k; 0i32 <= k && k < n) { Owned(array_shift(p,k)) }; - a1[i:xj][j:xi] == a2 - @*/ - { - extract Owned, i; - extract Owned, j; - int tmp = p[i]; - p[i] = p[j]; - p[j] = tmp; - } -//// - -=== Loops - -The array examples covered so far manipulate one or two individual cells of an array. Another typical pattern in code working over arrays is to *loop*, uniformly accessing all cells of an array, or sub-ranges of it. - -In order to verify code with loops, CN requires the user to supply loop invariants -- CN specifications of all owned resources and the constraints required to verify each iteration of the loop. - - -Let's take a look at a simple first example. The following function, `+init_array+`, takes the base pointer `+p+` of a `+char+` array and the array length `+n+` and writes `+0+` to each array cell. -[source,c] ----- -include::exercises/init_array.c[] ----- - -If, for the moment, we focus just on proving safe execution of `+init_array+`, ignoring its functional behaviour, a specification might look as above: on entry `+init_array+` takes ownership of an iterated `+Owned+` resource -- one `+Owned+` resource for each index `+i+` of type `+u32+` (so necessarily greater or equal to `+0+`) up to `+n+`; on exit `+init_array+` returns the ownership. - -To verify this, we have to supply a loop invariant that specifies all resource ownership and the necessary constraints that hold before and after each iteration of the loop. Loop invariants are specified using the keyword `inv`, followed by CN specifications using the same syntax as in function pre- and postconditions. The variables in scope for loop invariants are all in-scope C variables, as well as CN variables introduced in the function precondition. *In loop invariants, the name of a C variable refers to its current value* (more on this shortly). - -[source,c] ----- -include::solutions/init_array.c[] ----- -//// -BCP: Concrete syntax: Why not write something like "unchanged {p,n}" or "unchanged: p,n"? -//// - -The main condition here is unsurprising: we specify ownership of an iterated resource for an array just like in the the pre- and postcondition. - -The second thing we need to do, however, is less straightforward. Recall that, as discussed at the start of the tutorial, function arguments in C are mutable, and so CN permits this as well.While in this example it is obvious that `+p+` and `+n+` do not change, CN currently requires the loop invariant to explicitly state this, using special notation `+{p} unchanged+` (and similarly for `+n+`). - -**Note.** If we forget to specify `+unchanged+`, this can lead to confusing errors. In this example, for instance, CN would verify the loop against the loop invariant, but would be unable to prove a function postcondition seemingly directly implied by the loop invariant (lacking the information that the postcondition's `+p+` and `+n+` are the same as the loop invariant's). Future CN versions may handle loop invariants differently and treat variables as immutable by default. -//// -BCP: This seems like a good idea! -//// - -The final piece needed in the verification is an `+extract+` statement, as used in the previous examples: to separate the individual `+Owned+` resource for index `+j+` out of the iterated `+Owned+` resource and make it available to the resource inference, we specify `+extract Owned, j;+`. - - -With the `+extract+` statements in place, CN accepts the function. - -=== Second loop example - -However, on closer look, the specification of `+init_array+` is overly strong: it requires an iterated `+Owned+` resource for the array on entry. If, as the name suggests, the purpose of `+init_array+` is to initialise the array, then a precondition asserting only an iterated `+Block+` resource for the array should also be sufficient. The modified specification is then as follows. - -[source,c] ----- -include::exercises/init_array2.c[] ----- - -This specification *should* hold: assuming ownership of an uninitialised array on entry, each iteration of the loop initialises one cell of the array, moving it from `+Block+` to `+Owned+` "`state`", so that on function return the full array is initialised. (Recall that stores only require `+Block+` ownership of the written memory location, so ownership of not-necessarily-initialised memory.) - -To verify this modified example we again need a loop invariant. This time, the loop invariant is more involved, however: since each iteration of the loop initialises one more array cell, the loop invariant has to do precise book-keeping of the initialisation status of the array. - -To do so, we partition the array ownership into two parts: for each index of the array the loop has already visited, we have an `+Owned+` resource, for all other array indices we have the (unchanged) `+Block+` ownership. - -[source,c] ----- -include::solutions/init_array2.c[] ----- - -Let's go through this line-by-line: - -- We assert ownership of an iterated `+Owned+` resource, one for each index `+i+` strictly smaller than loop variable `+j+`. - -- All remaining indices `+i+`, between `+j+` and `+n+` are still uninitialised, so part of the iterated `+Block+` resource. - -- As in the previous example, we assert `+p+` and `+n+` are unchanged. - -- Finally, unlike in the previous example, this loop invariant involves `+j+`. We therefore also need to know that `+j+` does not exceed the array length `+n+`. Otherwise CN would not be able to prove that, on completing the last loop iteration, `+j=n+` holds. This, in turn, is needed to show that when the function returns, ownership of the iterated `+Owned+` resource --- as specified in the loop invariant --- is fully consumed by the function's post-condition and there is no left-over unused resource. - -As before, we also have to instruct CN to `+extract+` ownership of individual array cells out of the iterated resources: - -- to allow CN to extract the individual `+Block+` to be written we use `+extract Block, j;+`; - -- the store returns a matching `+Owned+` resource for index `+j+`; - -- finally, we put `+extract Owned, j;+` to allow CN to "`attach`" this resource to the iterated `+Owned+` resource. CN issues a warning, because nothing is, in fact, extracted: we are using `+extract+` only for the "`reverse`" direction. - - -=== Exercises - -**Init array reverse.** Verify the function `+init_array_rev+`, which has the same specification as `+init_array2+`, but reverses the array in decreasing index order ("`from right to left`"). - -(BCP: Currently broken...) -//// -[source,c] ----- -include::exercises/init_array_rev.broken.c[] ----- -//// - - - - - - -//// _____________________________________________________________________ -//// -BCP: I'll put my new stuff below here... -//// - -== Defining Predicates - -// We should show how to define predicates earlier -- -// - e.g., with numeric ranges!! - -(Text is needed from here on! But hopefully there's enough structure to -make sense of the examples...) - -[source,c] ----- -include::exercises/bcp_incr2.c[] ----- - -== Allocating and Deallocating Memory - -[source,c] ----- -include::exercises/malloc.h[] ----- - -[source,c] ----- -include::exercises/free.h[] ----- - -[source,c] ----- -include::exercises/slf17_get_and_free.c[] ----- - -[source,c] ----- -include::exercises/slf_ref.h[] ----- - -[source,c] ----- -include::exercises/slf16_basic_succ_using_incr.c[] ----- - -=== Exercises - -// BCP: There should be a non-ref-using version of this earlier, for comparison. - -Prove a specification for the following program that reveals *only* that -it returns a number that is greater than its argument. - -[source,c] ----- -include::exercises/slf_ref_greater.c[] ----- - -=== Side note: External functions - -Another way to specify external / unknown functions: - -[source,c] ----- -include::exercises/slf18_two_dice.c[] ----- - - -== Lists - -Common definitions for lists: - -[source,c] ----- -include::exercises/list.h[] ----- - -The spec of append: -[source,c] ----- -include::exercises/append.h[] ----- - -A simple destructive append: - -[source,c] ----- -include::exercises/bcp_append.c[] ----- - -A non-allocating merge sort: - -[source,c] ----- -include::exercises/bcp_mergesort.c[] ----- - -An allocating list-copy: - -[source,c] ----- -include::exercises/bcp_copy.c[] ----- - -=== Exercises - -*Allocating append*. Fill in the CN annotations on IntList_append2. (You will need -some in the body as well as at the top.) - -[source,c] ----- -include::exercises/bcp_append2.c[] ----- - -Note that it would not make sense to do the usual -functional-programming trick of copying xs but sharing ys. (Why?) - -*Length*. Add annotations as appropriate: - -[source,c] ----- -include::exercises/bcp_length.c[] ----- - -*List deallocation*. Fill in the body of the following procedure and -add annotations as appropriate: - -[source,c] ----- -include::exercises/list_free.c[] ----- - -*Length with an accumulator*. Add annotations as appropriate: -// BCP: Removing / forgetting the unfold in this one gives a truly -// bizarre error message saying that the constraint "n == (n + length(L1))" -// is unsatisfiable... - -[source,c] ----- -include::exercises/slf_length_acc.c[] ----- - -// BCP: We should include the file names of the exercises so that people do -// not have to cut and paste. - -//// -== External Lemmas - -[source,c] ----- -include::exercises/list_rev.c[] ----- -//// - -// TODO (exercise?): sized stacks (significant exercise) - -// TODO: Trees: -// - deep copy -// - sum -// - maybe the accumulating sum - -//// -Further exercises: - - Some exercises that get THEM to write predicates, datatype - declarations, etc. - -Misc things to do: - - create a top-level TAGS file for emacs - - remove the bcp_ prefix from filenames (and here) - - Figure out the smoothest way to do multiple includes - - Figure out how to display filenames -//// From 36a8c520d5770a786d6dd32eff1a4fb1bd1bcc8f Mon Sep 17 00:00:00 2001 From: septract Date: Mon, 22 Jul 2024 18:29:47 -0700 Subject: [PATCH 096/152] Modify examples to use a->b syntax --- src/examples/Dbl_Linked_List/add.c | 4 ++-- src/examples/Dbl_Linked_List/remove.c | 4 ++-- src/examples/queue_pop.c | 4 ++-- src/examples/queue_pop_unified.c | 4 ++-- src/examples/queue_push.c | 2 +- src/queue-example-notes.md | 2 +- src/tutorial.adoc | 13 +++++-------- 7 files changed, 15 insertions(+), 18 deletions(-) diff --git a/src/examples/Dbl_Linked_List/add.c b/src/examples/Dbl_Linked_List/add.c index 0bd50510..b988d9a4 100644 --- a/src/examples/Dbl_Linked_List/add.c +++ b/src/examples/Dbl_Linked_List/add.c @@ -20,12 +20,12 @@ struct node *add(int element, struct node *n) new_node->next = 0; return new_node; } else { - /*@ split_case(is_null((*n).prev)); @*/ + /*@ split_case(is_null(n->prev)); @*/ new_node->next = n->next; new_node->prev = n; if (n->next !=0) { - /*@ split_case(is_null((*(*n).next).next)); @*/ + /*@ split_case(is_null(n->next->next)); @*/ n->next->prev = new_node; } diff --git a/src/examples/Dbl_Linked_List/remove.c b/src/examples/Dbl_Linked_List/remove.c index 21199609..5f63c124 100644 --- a/src/examples/Dbl_Linked_List/remove.c +++ b/src/examples/Dbl_Linked_List/remove.c @@ -17,12 +17,12 @@ struct node_and_int *remove(struct node *n) { struct node *temp = 0; if (n->prev != 0) { - /*@ split_case(is_null((*(*n).prev).prev)); @*/ + /*@ split_case(is_null(n->prev->prev); @*/ n->prev->next = n->next; temp = n->prev; } if (n->next != 0) { - /*@ split_case(is_null((*(*n).next).next)); @*/ + /*@ split_case(is_null(n->next->next); @*/ n->next->prev = n->prev; temp = n->next; } diff --git a/src/examples/queue_pop.c b/src/examples/queue_pop.c index ed6e0837..d1053c25 100644 --- a/src/examples/queue_pop.c +++ b/src/examples/queue_pop.c @@ -9,7 +9,7 @@ int IntQueue_pop (struct int_queue *q) return == hd(before); @*/ { - /*@ split_case is_null((*q).front); @*/ + /*@ split_case is_null(q->front); @*/ struct int_queueCell* h = q->front; if (h == q->back) { int x = h->first; @@ -20,7 +20,7 @@ int IntQueue_pop (struct int_queue *q) return x; } else { int x = h->first; - /*@ apply snoc_facts((*h).next, (*q).back, x); @*/ + /*@ apply snoc_facts(h->next, q->back, x); @*/ q->front = h->next; freeIntQueueCell(h); return x; diff --git a/src/examples/queue_pop_unified.c b/src/examples/queue_pop_unified.c index 3f67d305..da0b8018 100644 --- a/src/examples/queue_pop_unified.c +++ b/src/examples/queue_pop_unified.c @@ -58,9 +58,9 @@ int IntQueue_pop (struct int_queue *q) return == hd(before); @*/ { - /*@ split_case is_null((*q).front); @*/ + /*@ split_case is_null(q->front); @*/ struct int_queueCell* h = q->front; - /*@ split_case ptr_eq(h,(*q).back); @*/ + /*@ split_case ptr_eq(h, q->back); @*/ int x = h->first; q->front = h->next; freeIntQueueCell(h); diff --git a/src/examples/queue_push.c b/src/examples/queue_push.c index bffa378c..e22dd152 100644 --- a/src/examples/queue_push.c +++ b/src/examples/queue_push.c @@ -18,7 +18,7 @@ void IntQueue_push (int x, struct int_queue *q) struct int_queueCell *oldback = q->back; q->back->next = c; q->back = c; - /*@ apply push_lemma ((*q).front, oldback); @*/ + /*@ apply push_lemma (q->front, oldback); @*/ return; } } diff --git a/src/queue-example-notes.md b/src/queue-example-notes.md index 6150a10b..09eb24c9 100644 --- a/src/queue-example-notes.md +++ b/src/queue-example-notes.md @@ -153,7 +153,7 @@ This tells us to look at snoc, which turns out to be very wrong! c->first = x; c->next = 0; if (q->tail == 0) { - /*@ split_case (*q).head == NULL; @*/ + /*@ split_case q->head == NULL; @*/ /*@ apply snac_nil(x); @*/ q->head = c; q->tail = c; diff --git a/src/tutorial.adoc b/src/tutorial.adoc index b2b17434..efafaa5c 100644 --- a/src/tutorial.adoc +++ b/src/tutorial.adoc @@ -1143,10 +1143,10 @@ as part of the rest (so that the new `+back+` cell can now be treated specially). One interesting technicality is worth noting: After the assignment -`+q->back = c+` we can no longer prove `+IntQueueFB((*q).front, +`+q->back = c+` we can no longer prove `+IntQueueFB(q->front, oldback)+`, but we don't care, since we want to prove -`+IntQueueFB((*q).front, (*q).back)+`. However, crucially, -`+IntQueueAux((*q).front, oldback)+` is still true. +`+IntQueueFB(q->front, q->back)+`. However, crucially, +`+IntQueueAux(q->front, oldback)+` is still true. // ====================================================================== @@ -1165,7 +1165,7 @@ include_example(exercises/queue_pop.c) There are three annotations to explain. Let's consider them in order. -First, the `+split_case+` on `+is_null((*q).front)+` is needed to tell +First, the `+split_case+` on `+is_null(q->front)+` is needed to tell CN which of the branches of the `+if+` at the beginning of the `+IntQueueFB+` predicate it can "unpack". (`+IntQueuePtr+` can be unpacked immediately because it is unconditional, but `+IntQueueFB+` @@ -1354,7 +1354,7 @@ and the right part of the list is the same as before. Now, let's look at the annotations in the function body. CN can figure out the empty list case for itself, but it needs some help with the non-empty list case. The `split_case` on `is_null((\*n).prev)` tells CN to unpack the `Own_Backwards` predicate. Without this annotation, -CN cannot reason that we didn't lose the left half of the list before we return, and will claim we are missing a resource for returning. The `split_case` on `is_null((*(*n).next).next)` is similar, but for unpacking the `Own_Forwards` predicate. Note that we +CN cannot reason that we didn't lose the left half of the list before we return, and will claim we are missing a resource for returning. The `split_case` on `is_null(n->next->next)` is similar, but for unpacking the `Own_Forwards` predicate. Note that we have to go one more node forward to make sure that everything past `n->next` is still owned at the end of the function. @@ -1485,9 +1485,6 @@ Further exercises: Misc things to do: - replace 0 with NULL in specs - - why do we have to write (*(*q).front).next instead of - q->front->next in CN expressions? (Answer: not implemented yet!) - - naming issues - rename == to ptr_eq everywhere in specs - rename list to seq in filenames. or go more radical and rename seq to cnlist From 8570786c6d77ff0ad4073c95770bb4e8fe2f7293 Mon Sep 17 00:00:00 2001 From: Dhruv Makwana Date: Tue, 23 Jul 2024 16:43:12 +0100 Subject: [PATCH 097/152] Temporarily remove crashing bitwise tests --- .../broken/error-crash/00104.err125.c | 29 ------------------- .../broken/error-crash/00126.err125.c | 21 -------------- .../broken/error-crash/neg_crash_1.c | 1 - 3 files changed, 51 deletions(-) delete mode 100644 src/example-archive/c-testsuite/broken/error-crash/00104.err125.c delete mode 100644 src/example-archive/c-testsuite/broken/error-crash/00126.err125.c delete mode 100644 src/example-archive/simple-examples/broken/error-crash/neg_crash_1.c diff --git a/src/example-archive/c-testsuite/broken/error-crash/00104.err125.c b/src/example-archive/c-testsuite/broken/error-crash/00104.err125.c deleted file mode 100644 index f21cb997..00000000 --- a/src/example-archive/c-testsuite/broken/error-crash/00104.err125.c +++ /dev/null @@ -1,29 +0,0 @@ -/* -internal error: todo: M_BW_COMPL -cn: internal error, uncaught exception: - Failure("internal error: todo: M_BW_COMPL") -*/ -// Cause: unknown - -#include - -int -main() -{ - int32_t x; - int64_t l; - - x = 0; - l = 0; - - x = ~x; - if (x != 0xffffffff) - return 1; - - l = ~l; - if (x != 0xffffffffffffffff) - return 2; - - - return 0; -} diff --git a/src/example-archive/c-testsuite/broken/error-crash/00126.err125.c b/src/example-archive/c-testsuite/broken/error-crash/00126.err125.c deleted file mode 100644 index 45ccb60a..00000000 --- a/src/example-archive/c-testsuite/broken/error-crash/00126.err125.c +++ /dev/null @@ -1,21 +0,0 @@ -/* -internal error: todo: M_BW_COMPL -cn: internal error, uncaught exception: - Failure("internal error: todo: M_BW_COMPL") -*/ -// Cause: unknown - -int -main() -{ - int x; - - x = 3; - x = !x; - x = !x; - x = ~x; - x = -x; - if(x != 2) - return 1; - return 0; -} diff --git a/src/example-archive/simple-examples/broken/error-crash/neg_crash_1.c b/src/example-archive/simple-examples/broken/error-crash/neg_crash_1.c deleted file mode 100644 index 0880e08b..00000000 --- a/src/example-archive/simple-examples/broken/error-crash/neg_crash_1.c +++ /dev/null @@ -1 +0,0 @@ -void a() { ~0; } From 2297bf373de7650e23becf0158af93a74eac15e3 Mon Sep 17 00:00:00 2001 From: Christopher Pulte Date: Tue, 23 Jul 2024 20:17:24 +0100 Subject: [PATCH 098/152] make asciidoc produce table of contents (and move stylesheet out to separate file to make that work) --- Makefile | 4 +- src/asciidoctor.css | 424 ++++++++++++++++++++++++++++++++++++++++++++ src/style.css | 40 +++++ src/tutorial.adoc | 46 +---- 4 files changed, 470 insertions(+), 44 deletions(-) create mode 100644 src/asciidoctor.css create mode 100644 src/style.css diff --git a/Makefile b/Makefile index c8fa399f..0febba9c 100644 --- a/Makefile +++ b/Makefile @@ -60,9 +60,11 @@ check: check-tutorial check-archive ############################################################################## # Tutorial document -build/tutorial.adoc: src/tutorial.adoc +build/tutorial.adoc build/style.css build/asciidoctor.css: src/tutorial.adoc @echo Create build/tutorial.adoc @sed -E 's/include_example\((.+)\)/.link:\1[\1]\n[source,c]\n----\ninclude::\1\[\]\n----/g' $< > $@ + @cp src/style.css build + @cp src/asciidoctor.css build build/images: src/images cp -r $< $@ diff --git a/src/asciidoctor.css b/src/asciidoctor.css new file mode 100644 index 00000000..843fe415 --- /dev/null +++ b/src/asciidoctor.css @@ -0,0 +1,424 @@ +/*! Asciidoctor default stylesheet | MIT License | https://asciidoctor.org */ +/* Uncomment the following line when using as a custom stylesheet */ +/* @import "https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700"; */ +html{font-family:sans-serif;-webkit-text-size-adjust:100%} +a{background:none} +a:focus{outline:thin dotted} +a:active,a:hover{outline:0} +h1{font-size:2em;margin:.67em 0} +b,strong{font-weight:bold} +abbr{font-size:.9em} +abbr[title]{cursor:help;border-bottom:1px dotted #dddddf;text-decoration:none} +dfn{font-style:italic} +hr{height:0} +mark{background:#ff0;color:#000} +code,kbd,pre,samp{font-family:monospace;font-size:1em} +pre{white-space:pre-wrap} +q{quotes:"\201C" "\201D" "\2018" "\2019"} +small{font-size:80%} +sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline} +sup{top:-.5em} +sub{bottom:-.25em} +img{border:0} +svg:not(:root){overflow:hidden} +figure{margin:0} +audio,video{display:inline-block} +audio:not([controls]){display:none;height:0} +fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em} +legend{border:0;padding:0} +button,input,select,textarea{font-family:inherit;font-size:100%;margin:0} +button,input{line-height:normal} +button,select{text-transform:none} +button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer} +button[disabled],html input[disabled]{cursor:default} +input[type=checkbox],input[type=radio]{padding:0} +button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0} +textarea{overflow:auto;vertical-align:top} +table{border-collapse:collapse;border-spacing:0} +*,::before,::after{box-sizing:border-box} +html,body{font-size:100%} +body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"Noto Serif","DejaVu Serif",serif;line-height:1;position:relative;cursor:auto;-moz-tab-size:4;-o-tab-size:4;tab-size:4;word-wrap:anywhere;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased} +a:hover{cursor:pointer} +img,object,embed{max-width:100%;height:auto} +object,embed{height:100%} +img{-ms-interpolation-mode:bicubic} +.left{float:left!important} +.right{float:right!important} +.text-left{text-align:left!important} +.text-right{text-align:right!important} +.text-center{text-align:center!important} +.text-justify{text-align:justify!important} +.hide{display:none} +img,object,svg{display:inline-block;vertical-align:middle} +textarea{height:auto;min-height:50px} +select{width:100%} +.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#7a2518;font-weight:400;margin-top:0;margin-bottom:.25em} +div,dl,dt,dd,ul,ol,li,h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0} +a{color:#2156a5;text-decoration:underline;line-height:inherit} +a:hover,a:focus{color:#1d4b8f} +a img{border:0} +p{line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility} +p aside{font-size:.875em;line-height:1.35;font-style:italic} +h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Open Sans","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#ba3925;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em} +h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0} +h1{font-size:2.125em} +h2{font-size:1.6875em} +h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em} +h4,h5{font-size:1.125em} +h6{font-size:1em} +hr{border:solid #dddddf;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em} +em,i{font-style:italic;line-height:inherit} +strong,b{font-weight:bold;line-height:inherit} +small{font-size:60%;line-height:inherit} +code{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;color:rgba(0,0,0,.9)} +ul,ol,dl{line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit} +ul,ol{margin-left:1.5em} +ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0} +ul.circle{list-style-type:circle} +ul.disc{list-style-type:disc} +ul.square{list-style-type:square} +ul.circle ul:not([class]),ul.disc ul:not([class]),ul.square ul:not([class]){list-style:inherit} +ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0} +dl dt{margin-bottom:.3125em;font-weight:bold} +dl dd{margin-bottom:1.25em} +blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd} +blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)} +@media screen and (min-width:768px){h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2} +h1{font-size:2.75em} +h2{font-size:2.3125em} +h3,#toctitle,.sidebarblock>.content>.title{font-size:1.6875em} +h4{font-size:1.4375em}} +table{background:#fff;margin-bottom:1.25em;border:1px solid #dedede;word-wrap:normal} +table thead,table tfoot{background:#f7f8f7} +table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:rgba(0,0,0,.8);text-align:left} +table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(0,0,0,.8)} +table tr.even,table tr.alt{background:#f8f8f7} +table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{line-height:1.6} +h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2;word-spacing:-.05em} +h1 strong,h2 strong,h3 strong,#toctitle strong,.sidebarblock>.content>.title strong,h4 strong,h5 strong,h6 strong{font-weight:400} +.center{margin-left:auto;margin-right:auto} +.stretch{width:100%} +.clearfix::before,.clearfix::after,.float-group::before,.float-group::after{content:" ";display:table} +.clearfix::after,.float-group::after{clear:both} +:not(pre).nobreak{word-wrap:normal} +:not(pre).nowrap{white-space:nowrap} +:not(pre).pre-wrap{white-space:pre-wrap} +:not(pre):not([class^=L])>code{font-size:.9375em;font-style:normal!important;letter-spacing:0;padding:.1em .5ex;word-spacing:-.15em;background:#f7f7f8;border-radius:4px;line-height:1.45;text-rendering:optimizeSpeed} +pre{color:rgba(0,0,0,.9);font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;line-height:1.45;text-rendering:optimizeSpeed} +pre code,pre pre{color:inherit;font-size:inherit;line-height:inherit} +pre>code{display:block} +pre.nowrap,pre.nowrap pre{white-space:pre;word-wrap:normal} +em em{font-style:normal} +strong strong{font-weight:400} +.keyseq{color:rgba(51,51,51,.8)} +kbd{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;display:inline-block;color:rgba(0,0,0,.8);font-size:.65em;line-height:1.45;background:#f7f7f7;border:1px solid #ccc;border-radius:3px;box-shadow:0 1px 0 rgba(0,0,0,.2),inset 0 0 0 .1em #fff;margin:0 .15em;padding:.2em .5em;vertical-align:middle;position:relative;top:-.1em;white-space:nowrap} +.keyseq kbd:first-child{margin-left:0} +.keyseq kbd:last-child{margin-right:0} +.menuseq,.menuref{color:#000} +.menuseq b:not(.caret),.menuref{font-weight:inherit} +.menuseq{word-spacing:-.02em} +.menuseq b.caret{font-size:1.25em;line-height:.8} +.menuseq i.caret{font-weight:bold;text-align:center;width:.45em} +b.button::before,b.button::after{position:relative;top:-1px;font-weight:400} +b.button::before{content:"[";padding:0 3px 0 2px} +b.button::after{content:"]";padding:0 2px 0 3px} +p a>code:hover{color:rgba(0,0,0,.9)} +#header,#content,#footnotes,#footer{width:100%;margin:0 auto;max-width:62.5em;*zoom:1;position:relative;padding-left:.9375em;padding-right:.9375em} +#header::before,#header::after,#content::before,#content::after,#footnotes::before,#footnotes::after,#footer::before,#footer::after{content:" ";display:table} +#header::after,#content::after,#footnotes::after,#footer::after{clear:both} +#content{margin-top:1.25em} +#content::before{content:none} +#header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0} +#header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #dddddf} +#header>h1:only-child{border-bottom:1px solid #dddddf;padding-bottom:8px} +#header .details{border-bottom:1px solid #dddddf;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:flex;flex-flow:row wrap} +#header .details span:first-child{margin-left:-.125em} +#header .details span.email a{color:rgba(0,0,0,.85)} +#header .details br{display:none} +#header .details br+span::before{content:"\00a0\2013\00a0"} +#header .details br+span.author::before{content:"\00a0\22c5\00a0";color:rgba(0,0,0,.85)} +#header .details br+span#revremark::before{content:"\00a0|\00a0"} +#header #revnumber{text-transform:capitalize} +#header #revnumber::after{content:"\00a0"} +#content>h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #dddddf;padding-bottom:8px;margin-top:0;padding-top:1rem;margin-bottom:1.25rem} +#toc{border-bottom:1px solid #e7e7e9;padding-bottom:.5em} +#toc>ul{margin-left:.125em} +#toc ul.sectlevel0>li>a{font-style:italic} +#toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0} +#toc ul{font-family:"Open Sans","DejaVu Sans",sans-serif;list-style-type:none} +#toc li{line-height:1.3334;margin-top:.3334em} +#toc a{text-decoration:none} +#toc a:active{text-decoration:underline} +#toctitle{color:#7a2518;font-size:1.2em} +@media screen and (min-width:768px){#toctitle{font-size:1.375em} +body.toc2{padding-left:15em;padding-right:0} +body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #dddddf;padding-bottom:8px} +#toc.toc2{margin-top:0!important;background:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #e7e7e9;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;padding:1.25em 1em;height:100%;overflow:auto} +#toc.toc2 #toctitle{margin-top:0;margin-bottom:.8rem;font-size:1.2em} +#toc.toc2>ul{font-size:.9em;margin-bottom:0} +#toc.toc2 ul ul{margin-left:0;padding-left:1em} +#toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em} +body.toc2.toc-right{padding-left:0;padding-right:15em} +body.toc2.toc-right #toc.toc2{border-right-width:0;border-left:1px solid #e7e7e9;left:auto;right:0}} +@media screen and (min-width:1280px){body.toc2{padding-left:20em;padding-right:0} +#toc.toc2{width:20em} +#toc.toc2 #toctitle{font-size:1.375em} +#toc.toc2>ul{font-size:.95em} +#toc.toc2 ul ul{padding-left:1.25em} +body.toc2.toc-right{padding-left:0;padding-right:20em}} +#content #toc{border:1px solid #e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;border-radius:4px} +#content #toc>:first-child{margin-top:0} +#content #toc>:last-child{margin-bottom:0} +#footer{max-width:none;background:rgba(0,0,0,.8);padding:1.25em} +#footer-text{color:hsla(0,0%,100%,.8);line-height:1.44} +#content{margin-bottom:.625em} +.sect1{padding-bottom:.625em} +@media screen and (min-width:768px){#content{margin-bottom:1.25em} +.sect1{padding-bottom:1.25em}} +.sect1:last-child{padding-bottom:0} +.sect1+.sect1{border-top:1px solid #e7e7e9} +#content h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400} +#content h1>a.anchor::before,h2>a.anchor::before,h3>a.anchor::before,#toctitle>a.anchor::before,.sidebarblock>.content>.title>a.anchor::before,h4>a.anchor::before,h5>a.anchor::before,h6>a.anchor::before{content:"\00A7";font-size:.85em;display:block;padding-top:.1em} +#content h1:hover>a.anchor,#content h1>a.anchor:hover,h2:hover>a.anchor,h2>a.anchor:hover,h3:hover>a.anchor,#toctitle:hover>a.anchor,.sidebarblock>.content>.title:hover>a.anchor,h3>a.anchor:hover,#toctitle>a.anchor:hover,.sidebarblock>.content>.title>a.anchor:hover,h4:hover>a.anchor,h4>a.anchor:hover,h5:hover>a.anchor,h5>a.anchor:hover,h6:hover>a.anchor,h6>a.anchor:hover{visibility:visible} +#content h1>a.link,h2>a.link,h3>a.link,#toctitle>a.link,.sidebarblock>.content>.title>a.link,h4>a.link,h5>a.link,h6>a.link{color:#ba3925;text-decoration:none} +#content h1>a.link:hover,h2>a.link:hover,h3>a.link:hover,#toctitle>a.link:hover,.sidebarblock>.content>.title>a.link:hover,h4>a.link:hover,h5>a.link:hover,h6>a.link:hover{color:#a53221} +details,.audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em} +details{margin-left:1.25rem} +details>summary{cursor:pointer;display:block;position:relative;line-height:1.6;margin-bottom:.625rem;outline:none;-webkit-tap-highlight-color:transparent} +details>summary::-webkit-details-marker{display:none} +details>summary::before{content:"";border:solid transparent;border-left:solid;border-width:.3em 0 .3em .5em;position:absolute;top:.5em;left:-1.25rem;transform:translateX(15%)} +details[open]>summary::before{border:solid transparent;border-top:solid;border-width:.5em .3em 0;transform:translateY(15%)} +details>summary::after{content:"";width:1.25rem;height:1em;position:absolute;top:.3em;left:-1.25rem} +.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{text-rendering:optimizeLegibility;text-align:left;font-family:"Noto Serif","DejaVu Serif",serif;font-size:1rem;font-style:italic} +table.tableblock.fit-content>caption.title{white-space:nowrap;width:0} +.paragraph.lead>p,#preamble>.sectionbody>[class=paragraph]:first-of-type p{font-size:1.21875em;line-height:1.6;color:rgba(0,0,0,.85)} +.admonitionblock>table{border-collapse:separate;border:0;background:none;width:100%} +.admonitionblock>table td.icon{text-align:center;width:80px} +.admonitionblock>table td.icon img{max-width:none} +.admonitionblock>table td.icon .title{font-weight:bold;font-family:"Open Sans","DejaVu Sans",sans-serif;text-transform:uppercase} +.admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #dddddf;color:rgba(0,0,0,.6);word-wrap:anywhere} +.admonitionblock>table td.content>:last-child>:last-child{margin-bottom:0} +.exampleblock>.content{border:1px solid #e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#fff;border-radius:4px} +.sidebarblock{border:1px solid #dbdbd6;margin-bottom:1.25em;padding:1.25em;background:#f3f3f2;border-radius:4px} +.sidebarblock>.content>.title{color:#7a2518;margin-top:0;text-align:center} +.exampleblock>.content>:first-child,.sidebarblock>.content>:first-child{margin-top:0} +.exampleblock>.content>:last-child,.exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0} +.literalblock pre,.listingblock>.content>pre{border-radius:4px;overflow-x:auto;padding:1em;font-size:.8125em} +@media screen and (min-width:768px){.literalblock pre,.listingblock>.content>pre{font-size:.90625em}} +@media screen and (min-width:1280px){.literalblock pre,.listingblock>.content>pre{font-size:1em}} +.literalblock pre,.listingblock>.content>pre:not(.highlight),.listingblock>.content>pre[class=highlight],.listingblock>.content>pre[class^="highlight "]{background:#f7f7f8} +.literalblock.output pre{color:#f7f7f8;background:rgba(0,0,0,.9)} +.listingblock>.content{position:relative} +.listingblock code[data-lang]::before{display:none;content:attr(data-lang);position:absolute;font-size:.75em;top:.425rem;right:.5rem;line-height:1;text-transform:uppercase;color:inherit;opacity:.5} +.listingblock:hover code[data-lang]::before{display:block} +.listingblock.terminal pre .command::before{content:attr(data-prompt);padding-right:.5em;color:inherit;opacity:.5} +.listingblock.terminal pre .command:not([data-prompt])::before{content:"$"} +.listingblock pre.highlightjs{padding:0} +.listingblock pre.highlightjs>code{padding:1em;border-radius:4px} +.listingblock pre.prettyprint{border-width:0} +.prettyprint{background:#f7f7f8} +pre.prettyprint .linenums{line-height:1.45;margin-left:2em} +pre.prettyprint li{background:none;list-style-type:inherit;padding-left:0} +pre.prettyprint li code[data-lang]::before{opacity:1} +pre.prettyprint li:not(:first-child) code[data-lang]::before{display:none} +table.linenotable{border-collapse:separate;border:0;margin-bottom:0;background:none} +table.linenotable td[class]{color:inherit;vertical-align:top;padding:0;line-height:inherit;white-space:normal} +table.linenotable td.code{padding-left:.75em} +table.linenotable td.linenos,pre.pygments .linenos{border-right:1px solid;opacity:.35;padding-right:.5em;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none} +pre.pygments span.linenos{display:inline-block;margin-right:.75em} +.quoteblock{margin:0 1em 1.25em 1.5em;display:table} +.quoteblock:not(.excerpt)>.title{margin-left:-1.5em;margin-bottom:.75em} +.quoteblock blockquote,.quoteblock p{color:rgba(0,0,0,.85);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify} +.quoteblock blockquote{margin:0;padding:0;border:0} +.quoteblock blockquote::before{content:"\201c";float:left;font-size:2.75em;font-weight:bold;line-height:.6em;margin-left:-.6em;color:#7a2518;text-shadow:0 1px 2px rgba(0,0,0,.1)} +.quoteblock blockquote>.paragraph:last-child p{margin-bottom:0} +.quoteblock .attribution{margin-top:.75em;margin-right:.5ex;text-align:right} +.verseblock{margin:0 1em 1.25em} +.verseblock pre{font-family:"Open Sans","DejaVu Sans",sans-serif;font-size:1.15rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility} +.verseblock pre strong{font-weight:400} +.verseblock .attribution{margin-top:1.25rem;margin-left:.5ex} +.quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic} +.quoteblock .attribution br,.verseblock .attribution br{display:none} +.quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.025em;color:rgba(0,0,0,.6)} +.quoteblock.abstract blockquote::before,.quoteblock.excerpt blockquote::before,.quoteblock .quoteblock blockquote::before{display:none} +.quoteblock.abstract blockquote,.quoteblock.abstract p,.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{line-height:1.6;word-spacing:0} +.quoteblock.abstract{margin:0 1em 1.25em;display:block} +.quoteblock.abstract>.title{margin:0 0 .375em;font-size:1.15em;text-align:center} +.quoteblock.excerpt>blockquote,.quoteblock .quoteblock{padding:0 0 .25em 1em;border-left:.25em solid #dddddf} +.quoteblock.excerpt,.quoteblock .quoteblock{margin-left:0} +.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{color:inherit;font-size:1.0625rem} +.quoteblock.excerpt .attribution,.quoteblock .quoteblock .attribution{color:inherit;font-size:.85rem;text-align:left;margin-right:0} +p.tableblock:last-child{margin-bottom:0} +td.tableblock>.content{margin-bottom:1.25em;word-wrap:anywhere} +td.tableblock>.content>:last-child{margin-bottom:-1.25em} +table.tableblock,th.tableblock,td.tableblock{border:0 solid #dedede} +table.grid-all>*>tr>*{border-width:1px} +table.grid-cols>*>tr>*{border-width:0 1px} +table.grid-rows>*>tr>*{border-width:1px 0} +table.frame-all{border-width:1px} +table.frame-ends{border-width:1px 0} +table.frame-sides{border-width:0 1px} +table.frame-none>colgroup+*>:first-child>*,table.frame-sides>colgroup+*>:first-child>*{border-top-width:0} +table.frame-none>:last-child>:last-child>*,table.frame-sides>:last-child>:last-child>*{border-bottom-width:0} +table.frame-none>*>tr>:first-child,table.frame-ends>*>tr>:first-child{border-left-width:0} +table.frame-none>*>tr>:last-child,table.frame-ends>*>tr>:last-child{border-right-width:0} +table.stripes-all>*>tr,table.stripes-odd>*>tr:nth-of-type(odd),table.stripes-even>*>tr:nth-of-type(even),table.stripes-hover>*>tr:hover{background:#f8f8f7} +th.halign-left,td.halign-left{text-align:left} +th.halign-right,td.halign-right{text-align:right} +th.halign-center,td.halign-center{text-align:center} +th.valign-top,td.valign-top{vertical-align:top} +th.valign-bottom,td.valign-bottom{vertical-align:bottom} +th.valign-middle,td.valign-middle{vertical-align:middle} +table thead th,table tfoot th{font-weight:bold} +tbody tr th{background:#f7f8f7} +tbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:rgba(0,0,0,.8);font-weight:bold} +p.tableblock>code:only-child{background:none;padding:0} +p.tableblock{font-size:1em} +ol{margin-left:1.75em} +ul li ol{margin-left:1.5em} +dl dd{margin-left:1.125em} +dl dd:last-child,dl dd:last-child>:last-child{margin-bottom:0} +li p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.625em} +ul.checklist,ul.none,ol.none,ul.no-bullet,ol.no-bullet,ol.unnumbered,ul.unstyled,ol.unstyled{list-style-type:none} +ul.no-bullet,ol.no-bullet,ol.unnumbered{margin-left:.625em} +ul.unstyled,ol.unstyled{margin-left:0} +li>p:empty:only-child::before{content:"";display:inline-block} +ul.checklist>li>p:first-child{margin-left:-1em} +ul.checklist>li>p:first-child>.fa-square-o:first-child,ul.checklist>li>p:first-child>.fa-check-square-o:first-child{width:1.25em;font-size:.8em;position:relative;bottom:.125em} +ul.checklist>li>p:first-child>input[type=checkbox]:first-child{margin-right:.25em} +ul.inline{display:flex;flex-flow:row wrap;list-style:none;margin:0 0 .625em -1.25em} +ul.inline>li{margin-left:1.25em} +.unstyled dl dt{font-weight:400;font-style:normal} +ol.arabic{list-style-type:decimal} +ol.decimal{list-style-type:decimal-leading-zero} +ol.loweralpha{list-style-type:lower-alpha} +ol.upperalpha{list-style-type:upper-alpha} +ol.lowerroman{list-style-type:lower-roman} +ol.upperroman{list-style-type:upper-roman} +ol.lowergreek{list-style-type:lower-greek} +.hdlist>table,.colist>table{border:0;background:none} +.hdlist>table>tbody>tr,.colist>table>tbody>tr{background:none} +td.hdlist1,td.hdlist2{vertical-align:top;padding:0 .625em} +td.hdlist1{font-weight:bold;padding-bottom:1.25em} +td.hdlist2{word-wrap:anywhere} +.literalblock+.colist,.listingblock+.colist{margin-top:-.5em} +.colist td:not([class]):first-child{padding:.4em .75em 0;line-height:1;vertical-align:top} +.colist td:not([class]):first-child img{max-width:none} +.colist td:not([class]):last-child{padding:.25em 0} +.thumb,.th{line-height:0;display:inline-block;border:4px solid #fff;box-shadow:0 0 0 1px #ddd} +.imageblock.left{margin:.25em .625em 1.25em 0} +.imageblock.right{margin:.25em 0 1.25em .625em} +.imageblock>.title{margin-bottom:0} +.imageblock.thumb,.imageblock.th{border-width:6px} +.imageblock.thumb>.title,.imageblock.th>.title{padding:0 .125em} +.image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0} +.image.left{margin-right:.625em} +.image.right{margin-left:.625em} +a.image{text-decoration:none;display:inline-block} +a.image object{pointer-events:none} +sup.footnote,sup.footnoteref{font-size:.875em;position:static;vertical-align:super} +sup.footnote a,sup.footnoteref a{text-decoration:none} +sup.footnote a:active,sup.footnoteref a:active,#footnotes .footnote a:first-of-type:active{text-decoration:underline} +#footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em} +#footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em;border-width:1px 0 0} +#footnotes .footnote{padding:0 .375em 0 .225em;line-height:1.3334;font-size:.875em;margin-left:1.2em;margin-bottom:.2em} +#footnotes .footnote a:first-of-type{font-weight:bold;text-decoration:none;margin-left:-1.05em} +#footnotes .footnote:last-of-type{margin-bottom:0} +#content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0} +div.unbreakable{page-break-inside:avoid} +.big{font-size:larger} +.small{font-size:smaller} +.underline{text-decoration:underline} +.overline{text-decoration:overline} +.line-through{text-decoration:line-through} +.aqua{color:#00bfbf} +.aqua-background{background:#00fafa} +.black{color:#000} +.black-background{background:#000} +.blue{color:#0000bf} +.blue-background{background:#0000fa} +.fuchsia{color:#bf00bf} +.fuchsia-background{background:#fa00fa} +.gray{color:#606060} +.gray-background{background:#7d7d7d} +.green{color:#006000} +.green-background{background:#007d00} +.lime{color:#00bf00} +.lime-background{background:#00fa00} +.maroon{color:#600000} +.maroon-background{background:#7d0000} +.navy{color:#000060} +.navy-background{background:#00007d} +.olive{color:#606000} +.olive-background{background:#7d7d00} +.purple{color:#600060} +.purple-background{background:#7d007d} +.red{color:#bf0000} +.red-background{background:#fa0000} +.silver{color:#909090} +.silver-background{background:#bcbcbc} +.teal{color:#006060} +.teal-background{background:#007d7d} +.white{color:#bfbfbf} +.white-background{background:#fafafa} +.yellow{color:#bfbf00} +.yellow-background{background:#fafa00} +span.icon>.fa{cursor:default} +a span.icon>.fa{cursor:inherit} +.admonitionblock td.icon [class^="fa icon-"]{font-size:2.5em;text-shadow:1px 1px 2px rgba(0,0,0,.5);cursor:default} +.admonitionblock td.icon .icon-note::before{content:"\f05a";color:#19407c} +.admonitionblock td.icon .icon-tip::before{content:"\f0eb";text-shadow:1px 1px 2px rgba(155,155,0,.8);color:#111} +.admonitionblock td.icon .icon-warning::before{content:"\f071";color:#bf6900} +.admonitionblock td.icon .icon-caution::before{content:"\f06d";color:#bf3400} +.admonitionblock td.icon .icon-important::before{content:"\f06a";color:#bf0000} +.conum[data-value]{display:inline-block;color:#fff!important;background:rgba(0,0,0,.8);border-radius:50%;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold} +.conum[data-value] *{color:#fff!important} +.conum[data-value]+b{display:none} +.conum[data-value]::after{content:attr(data-value)} +pre .conum[data-value]{position:relative;top:-.125em} +b.conum *{color:inherit!important} +.conum:not([data-value]):empty{display:none} +dt,th.tableblock,td.content,div.footnote{text-rendering:optimizeLegibility} +h1,h2,p,td.content,span.alt,summary{letter-spacing:-.01em} +p strong,td.content strong,div.footnote strong{letter-spacing:-.005em} +p,blockquote,dt,td.content,td.hdlist1,span.alt,summary{font-size:1.0625rem} +p{margin-bottom:1.25rem} +.sidebarblock p,.sidebarblock dt,.sidebarblock td.content,p.tableblock{font-size:1em} +.exampleblock>.content{background:#fffef7;border-color:#e0e0dc;box-shadow:0 1px 4px #e0e0dc} +.print-only{display:none!important} +@page{margin:1.25cm .75cm} +@media print{*{box-shadow:none!important;text-shadow:none!important} +html{font-size:80%} +a{color:inherit!important;text-decoration:underline!important} +a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none!important} +a[href^="http:"]:not(.bare)::after,a[href^="https:"]:not(.bare)::after{content:"(" attr(href) ")";display:inline-block;font-size:.875em;padding-left:.25em} +abbr[title]{border-bottom:1px dotted} +abbr[title]::after{content:" (" attr(title) ")"} +pre,blockquote,tr,img,object,svg{page-break-inside:avoid} +thead{display:table-header-group} +svg{max-width:100%} +p,blockquote,dt,td.content{font-size:1em;orphans:3;widows:3} +h2,h3,#toctitle,.sidebarblock>.content>.title{page-break-after:avoid} +#header,#content,#footnotes,#footer{max-width:none} +#toc,.sidebarblock,.exampleblock>.content{background:none!important} +#toc{border-bottom:1px solid #dddddf!important;padding-bottom:0!important} +body.book #header{text-align:center} +body.book #header>h1:first-child{border:0!important;margin:2.5em 0 1em} +body.book #header .details{border:0!important;display:block;padding:0!important} +body.book #header .details span:first-child{margin-left:0!important} +body.book #header .details br{display:block} +body.book #header .details br+span::before{content:none!important} +body.book #toc{border:0!important;text-align:left!important;padding:0!important;margin:0!important} +body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-break-before:always} +.listingblock code[data-lang]::before{display:block} +#footer{padding:0 .9375em} +.hide-on-print{display:none!important} +.print-only{display:block!important} +.hide-for-print{display:none!important} +.show-for-print{display:inherit!important}} +@media amzn-kf8,print{#header>h1:first-child{margin-top:1.25rem} +.sect1{padding:0!important} +.sect1+.sect1{border:0} +#footer{background:none} +#footer-text{color:rgba(0,0,0,.6);font-size:.9em}} +@media amzn-kf8{#header,#content,#footnotes,#footer{padding:0}} \ No newline at end of file diff --git a/src/style.css b/src/style.css new file mode 100644 index 00000000..c7d0cf63 --- /dev/null +++ b/src/style.css @@ -0,0 +1,40 @@ +@import "asciidoctor.css"; + +body { + max-width: 1100px; + /* margin: auto; */ + font-family: sans-serif; + // font-size: 18px; +} + +#preamble .sectionbody .paragraph p { + // font-size: 18px; +} + +// h1 { font-size: 32px; margin-top: 4em; } +// h2 { font-size: 26px; margin-top: 2em; } +// h3 { font-size: 22px; margin-top: 2em; } + +h1, h2, h3, h4, h5 { + color: hsl(219, 50%, 50%); + font-family: sans-serif; + font-weight: bold; +} + +#toctitle { + color: black; +} + +.listingblock .title, .imageblock .title { + font-family: sans-serif; +} + +.sect1 { border-top-width: 0px; } + +body > .sourceCode { + padding: 5px; + border-radius: 5px; + border: 1px solid hsl(44, 7%, 80%); + background-color: hsl(44, 7%, 96%); +} + diff --git a/src/tutorial.adoc b/src/tutorial.adoc index efafaa5c..e4d78dc0 100644 --- a/src/tutorial.adoc +++ b/src/tutorial.adoc @@ -1,51 +1,11 @@ += CN tutorial :source-highlighter: pygments :pygments-style: manni -// :pygments-style: tango :nofooter: :prewrap!: :sectanchors: - -++++ - -++++ - -// __________________________________________________________________________ - -= CN tutorial +:toc: left +:stylesheet: style.css CN is a type system for verifying C code, focusing especially on low-level systems code. Compared to the normal C type system, CN checks not only that expressions and statements follow the correct typing discipline for C-types, but also that the C code executes _safely_ — does not raise C undefined behaviour — and _correctly_ — according to strong user-defined specifications. To accurately handle the complex semantics of C, CN builds on the https://github.com/rems-project/cerberus/[Cerberus semantics for C]. From 8039cb4a0a34c0c6eb99da47ffdfc1e5f9d71c98 Mon Sep 17 00:00:00 2001 From: Dhruv Makwana Date: Tue, 23 Jul 2024 17:18:43 +0100 Subject: [PATCH 099/152] Re-add bitwise complement examples (now working) --- .../c-testsuite/working/00104.c | 29 +++++++++++++++++++ .../c-testsuite/working/00126.c | 21 ++++++++++++++ .../simple-examples/working/bit_compl_1.c | 1 + 3 files changed, 51 insertions(+) create mode 100644 src/example-archive/c-testsuite/working/00104.c create mode 100644 src/example-archive/c-testsuite/working/00126.c create mode 100644 src/example-archive/simple-examples/working/bit_compl_1.c diff --git a/src/example-archive/c-testsuite/working/00104.c b/src/example-archive/c-testsuite/working/00104.c new file mode 100644 index 00000000..f21cb997 --- /dev/null +++ b/src/example-archive/c-testsuite/working/00104.c @@ -0,0 +1,29 @@ +/* +internal error: todo: M_BW_COMPL +cn: internal error, uncaught exception: + Failure("internal error: todo: M_BW_COMPL") +*/ +// Cause: unknown + +#include + +int +main() +{ + int32_t x; + int64_t l; + + x = 0; + l = 0; + + x = ~x; + if (x != 0xffffffff) + return 1; + + l = ~l; + if (x != 0xffffffffffffffff) + return 2; + + + return 0; +} diff --git a/src/example-archive/c-testsuite/working/00126.c b/src/example-archive/c-testsuite/working/00126.c new file mode 100644 index 00000000..45ccb60a --- /dev/null +++ b/src/example-archive/c-testsuite/working/00126.c @@ -0,0 +1,21 @@ +/* +internal error: todo: M_BW_COMPL +cn: internal error, uncaught exception: + Failure("internal error: todo: M_BW_COMPL") +*/ +// Cause: unknown + +int +main() +{ + int x; + + x = 3; + x = !x; + x = !x; + x = ~x; + x = -x; + if(x != 2) + return 1; + return 0; +} diff --git a/src/example-archive/simple-examples/working/bit_compl_1.c b/src/example-archive/simple-examples/working/bit_compl_1.c new file mode 100644 index 00000000..0880e08b --- /dev/null +++ b/src/example-archive/simple-examples/working/bit_compl_1.c @@ -0,0 +1 @@ +void a() { ~0; } From 386b59933e2e38ee269b4a6e212fd3f638e368c9 Mon Sep 17 00:00:00 2001 From: Christopher Pulte Date: Thu, 25 Jul 2024 15:39:41 +0100 Subject: [PATCH 100/152] fix makefile to use `cn verify` --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 0febba9c..1b9d27d3 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,7 @@ build/solutions/%: src/examples/% @if [ `which cn` ]; then \ if [[ "$<" = *".c"* ]]; then \ if [[ "$<" != *"broken"* ]]; then \ - echo cn $< && cn $<; \ + echo cn $< && cn verify $<; \ fi; \ fi \ fi From 0562b8d034b5b21840fad96cd59391ad896322cf Mon Sep 17 00:00:00 2001 From: Benjamin Pierce Date: Thu, 25 Jul 2024 10:42:23 -0400 Subject: [PATCH 101/152] Quick fixes for broken build --- Makefile | 4 +++- src/examples/Dbl_Linked_List/headers.h | 3 +-- src/examples/Dbl_Linked_List/remove.c | 6 +++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 1b9d27d3..f915ad35 100644 --- a/Makefile +++ b/Makefile @@ -19,6 +19,8 @@ SRC_EXAMPLES=$(shell find src/examples -type f) SOLUTIONS=$(patsubst src/examples/%, build/solutions/%, $(SRC_EXAMPLES)) EXERCISES=$(patsubst src/examples/%, build/exercises/%, $(SRC_EXAMPLES)) +CN=cn verify + exercises: $(EXERCISES) $(SOLUTIONS) build/exercises/%: src/examples/% @@ -31,7 +33,7 @@ build/solutions/%: src/examples/% @if [ `which cn` ]; then \ if [[ "$<" = *".c"* ]]; then \ if [[ "$<" != *"broken"* ]]; then \ - echo cn $< && cn verify $<; \ + echo $(CN) $< && $(CN) $<; \ fi; \ fi \ fi diff --git a/src/examples/Dbl_Linked_List/headers.h b/src/examples/Dbl_Linked_List/headers.h index 35506b5b..963902fb 100644 --- a/src/examples/Dbl_Linked_List/headers.h +++ b/src/examples/Dbl_Linked_List/headers.h @@ -4,7 +4,6 @@ #include "./c_types.h" #include "./cn_types.h" -#include "./converters.h" #include "./getters.h" #include "./malloc_free.h" -#include "./predicates.h" \ No newline at end of file +#include "./predicates.h" diff --git a/src/examples/Dbl_Linked_List/remove.c b/src/examples/Dbl_Linked_List/remove.c index 5f63c124..2a610db8 100644 --- a/src/examples/Dbl_Linked_List/remove.c +++ b/src/examples/Dbl_Linked_List/remove.c @@ -17,12 +17,12 @@ struct node_and_int *remove(struct node *n) { struct node *temp = 0; if (n->prev != 0) { - /*@ split_case(is_null(n->prev->prev); @*/ + /*@ split_case(is_null(n->prev->prev)); @*/ n->prev->next = n->next; temp = n->prev; } if (n->next != 0) { - /*@ split_case(is_null(n->next->next); @*/ + /*@ split_case(is_null(n->next->next)); @*/ n->next->prev = n->prev; temp = n->next; } @@ -33,4 +33,4 @@ struct node_and_int *remove(struct node *n) free_node(n); return pair; -} \ No newline at end of file +} From aee33cac24283e6ae6f62c2e93c26cd025be7c18 Mon Sep 17 00:00:00 2001 From: Dhruv Makwana Date: Mon, 29 Jul 2024 15:35:28 +0100 Subject: [PATCH 102/152] Temporarily remove crashing modulo test --- .../broken/error-crash/00009.err125.c | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 src/example-archive/c-testsuite/broken/error-crash/00009.err125.c diff --git a/src/example-archive/c-testsuite/broken/error-crash/00009.err125.c b/src/example-archive/c-testsuite/broken/error-crash/00009.err125.c deleted file mode 100644 index 18f8fd2d..00000000 --- a/src/example-archive/c-testsuite/broken/error-crash/00009.err125.c +++ /dev/null @@ -1,16 +0,0 @@ -/* - Failure("todo") -*/ -// Cause: `/` and `%` operators - -int -main() -{ - int x; - - x = 1; - x = x * 10; - x = x / 2; - x = x % 3; - return x - 2; -} From c9f3b4f1bb3eb6a21c9b508920d4a9a9292e02dd Mon Sep 17 00:00:00 2001 From: Christopher Pulte Date: Mon, 29 Jul 2024 16:37:54 +0100 Subject: [PATCH 103/152] recategorise now-working examples --- .../{broken/error-proof/00134.err1.c => working/00134.c} | 0 .../{broken/error-proof/00135.err1.c => working/00135.c} | 0 .../{broken/error-proof/long_type_err_1.c => working/long_type.c} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename src/example-archive/c-testsuite/{broken/error-proof/00134.err1.c => working/00134.c} (100%) rename src/example-archive/c-testsuite/{broken/error-proof/00135.err1.c => working/00135.c} (100%) rename src/example-archive/simple-examples/{broken/error-proof/long_type_err_1.c => working/long_type.c} (100%) diff --git a/src/example-archive/c-testsuite/broken/error-proof/00134.err1.c b/src/example-archive/c-testsuite/working/00134.c similarity index 100% rename from src/example-archive/c-testsuite/broken/error-proof/00134.err1.c rename to src/example-archive/c-testsuite/working/00134.c diff --git a/src/example-archive/c-testsuite/broken/error-proof/00135.err1.c b/src/example-archive/c-testsuite/working/00135.c similarity index 100% rename from src/example-archive/c-testsuite/broken/error-proof/00135.err1.c rename to src/example-archive/c-testsuite/working/00135.c diff --git a/src/example-archive/simple-examples/broken/error-proof/long_type_err_1.c b/src/example-archive/simple-examples/working/long_type.c similarity index 100% rename from src/example-archive/simple-examples/broken/error-proof/long_type_err_1.c rename to src/example-archive/simple-examples/working/long_type.c From 7018bb7ba5226eee366661032760d6e094778b26 Mon Sep 17 00:00:00 2001 From: Dhruv Makwana Date: Tue, 30 Jul 2024 13:55:00 +0100 Subject: [PATCH 104/152] Add broken proof example from Cerberus repo here The test script in the Cerberus repo doesn't really have a good category for "this is broken, but it should not be". Consequently, placing these tests here makes more sense. --- src/example-archive/should-fail/README.md | 3 ++- .../should-fail/working/c_sequencing_race.c | 10 ++++++++++ .../should-fail/working/letweak01.c | 8 ++++++++ .../broken/error-proof/self_ref_init.c | 19 +++++++++++++++++++ 4 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 src/example-archive/should-fail/working/c_sequencing_race.c create mode 100644 src/example-archive/should-fail/working/letweak01.c create mode 100644 src/example-archive/simple-examples/broken/error-proof/self_ref_init.c diff --git a/src/example-archive/should-fail/README.md b/src/example-archive/should-fail/README.md index 49e793ab..7d639c08 100644 --- a/src/example-archive/should-fail/README.md +++ b/src/example-archive/should-fail/README.md @@ -1,2 +1,3 @@ This folder contains deliberately incorrect proofs, which are intended to serve -as negative test cases for CN. \ No newline at end of file +as negative test cases for CN, i.e. the ones in `broken` are behaving as +expected and the ones in `working` show a bug of permissiveness which needs fixing. diff --git a/src/example-archive/should-fail/working/c_sequencing_race.c b/src/example-archive/should-fail/working/c_sequencing_race.c new file mode 100644 index 00000000..c91abf15 --- /dev/null +++ b/src/example-archive/should-fail/working/c_sequencing_race.c @@ -0,0 +1,10 @@ + + +int +f (int *x) +/*@ requires take xv = Owned(x); @*/ +/*@ requires 0i32 <= xv && xv < 500i32; @*/ +/*@ ensures take xv2 = Owned(x); @*/ +{ + return ((*x) + (*x)); +} diff --git a/src/example-archive/should-fail/working/letweak01.c b/src/example-archive/should-fail/working/letweak01.c new file mode 100644 index 00000000..be7d7fd6 --- /dev/null +++ b/src/example-archive/should-fail/working/letweak01.c @@ -0,0 +1,8 @@ + +int +f (int x) { + x = (x = 1); + + return x + 2; +} + diff --git a/src/example-archive/simple-examples/broken/error-proof/self_ref_init.c b/src/example-archive/simple-examples/broken/error-proof/self_ref_init.c new file mode 100644 index 00000000..6195b3c9 --- /dev/null +++ b/src/example-archive/simple-examples/broken/error-proof/self_ref_init.c @@ -0,0 +1,19 @@ + +/* based on a problematic example from linux kvm/pgtable.c */ + +struct str { + int x; + int y; +}; + +int f (int x) +/*@ requires (0i32 <= x) && (x < 200i32); @*/ +{ + struct str str_inst = { + .x = x + 2, + .y = str_inst.x + 3, + }; + + return str_inst.y; +} + From 8e9c148e024600f6d04f770baae12e8df6998f04 Mon Sep 17 00:00:00 2001 From: Iavor Diatchki Date: Fri, 2 Aug 2024 15:53:00 -0700 Subject: [PATCH 105/152] Update tests for PR #459 in the Cerberus repo --- .../{error-cerberus/00088.err2.c => error-proof/00088.err1.c} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/example-archive/c-testsuite/broken/{error-cerberus/00088.err2.c => error-proof/00088.err1.c} (100%) diff --git a/src/example-archive/c-testsuite/broken/error-cerberus/00088.err2.c b/src/example-archive/c-testsuite/broken/error-proof/00088.err1.c similarity index 100% rename from src/example-archive/c-testsuite/broken/error-cerberus/00088.err2.c rename to src/example-archive/c-testsuite/broken/error-proof/00088.err1.c From 75296166cca02ed859e05db19f37060b3ba9fada Mon Sep 17 00:00:00 2001 From: Christopher Pulte Date: Tue, 6 Aug 2024 19:01:44 +0100 Subject: [PATCH 106/152] put in authors, funding ack, and reference for CN paper --- src/style.css | 4 ++++ src/tutorial.adoc | 28 ++++++++++++++++++++++------ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/style.css b/src/style.css index c7d0cf63..1669a8c7 100644 --- a/src/style.css +++ b/src/style.css @@ -38,3 +38,7 @@ body > .sourceCode { background-color: hsl(44, 7%, 96%); } +.quoteblock.abstract p { + font-style: normal; + text-align: left; +} diff --git a/src/tutorial.adoc b/src/tutorial.adoc index e4d78dc0..97436fbf 100644 --- a/src/tutorial.adoc +++ b/src/tutorial.adoc @@ -6,16 +6,32 @@ :sectanchors: :toc: left :stylesheet: style.css - -CN is a type system for verifying C code, focusing especially on low-level systems code. Compared to the normal C type system, CN checks not only that expressions and statements follow the correct typing discipline for C-types, but also that the C code executes _safely_ — does not raise C undefined behaviour — and _correctly_ — according to strong user-defined specifications. To accurately handle the complex semantics of C, CN builds on the https://github.com/rems-project/cerberus/[Cerberus semantics for C]. - -This tutorial introduces CN along a series of examples, starting with basic usage of CN on simple arithmetic functions and slowly moving towards more elaborate separation logic specifications of data structures. - -Some of the examples are adapted from Arthur Charguéraud’s excellent +Christopher Pulte; Benjamin C. Pierce; with contributions from Elizabeth Austell + +[abstract] +-- +CN is a type system for verifying C code, focusing especially on low-level systems code. Compared to the normal C type system, CN checks not only that expressions and statements follow the correct typing discipline for C-types, but also that the C code executes _safely_ — does not raise C undefined behaviour — and _correctly_ — according to strong user-defined specifications. +// +This tutorial introduces CN along a series of examples, starting with basic usage of CN on simple arithmetic functions and slowly moving towards more elaborate separation logic specifications of data structures. + +CN was first described in https://dl.acm.org/doi/10.1145/3571194[CN: Verifying Systems C Code with Separation-Logic Refinement Types] by Christopher Pulte, Dhruv C. Makwana, Thomas Sewell, Kayvan Memarian, Peter Sewell, Neel Krishnaswami. +// +To accurately handle the complex semantics of C, CN builds on the https://github.com/rems-project/cerberus/[Cerberus semantics for C]. +// +Some of the examples in this tutorial are adapted from Arthur Charguéraud’s excellent https://softwarefoundations.cis.upenn.edu[Separation Logic Foundations] textbook. This tutorial is a work in progress -- your suggestions are greatly appreciated! +-- + +{nbsp} + +{nbsp} + + + + +**Acknowledgment of Support and Disclaimer.** +This material is based upon work supported by the Air Force Research Laboratory (AFRL) and Defense Advanced Research Projects Agencies (DARPA) under Contract No. FA8750-24-C-B044, a European Research Council (ERC) Advanced Grant “ELVER” under the European Union’s Horizon 2020 research and innovation programme (grant agreement no. 789108), and additional funding from Google. Any opinion, findings and conclusions or recommendations expressed in this material are those of the author(s) and do not necessarily reflect the views of the Air Force Research Laboratory (AFRL). == Installing CN From 91198d2a02a5cebb1f81a96d19c546b5e16451cc Mon Sep 17 00:00:00 2001 From: Benjamin Pierce Date: Tue, 6 Aug 2024 17:35:18 -0400 Subject: [PATCH 107/152] Tidy --- src/tutorial.adoc | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/tutorial.adoc b/src/tutorial.adoc index 97436fbf..e641a65c 100644 --- a/src/tutorial.adoc +++ b/src/tutorial.adoc @@ -6,7 +6,7 @@ :sectanchors: :toc: left :stylesheet: style.css -Christopher Pulte; Benjamin C. Pierce; with contributions from Elizabeth Austell +By Christopher Pulte and Benjamin C. Pierce, with contributions from Elizabeth Austell [abstract] -- @@ -15,23 +15,20 @@ CN is a type system for verifying C code, focusing especially on low-level syste This tutorial introduces CN along a series of examples, starting with basic usage of CN on simple arithmetic functions and slowly moving towards more elaborate separation logic specifications of data structures. CN was first described in https://dl.acm.org/doi/10.1145/3571194[CN: Verifying Systems C Code with Separation-Logic Refinement Types] by Christopher Pulte, Dhruv C. Makwana, Thomas Sewell, Kayvan Memarian, Peter Sewell, Neel Krishnaswami. -// +// To accurately handle the complex semantics of C, CN builds on the https://github.com/rems-project/cerberus/[Cerberus semantics for C]. -// +// Some of the examples in this tutorial are adapted from Arthur Charguéraud’s excellent https://softwarefoundations.cis.upenn.edu[Separation Logic -Foundations] textbook. +Foundations] textbook, and one of the case studies is based on an +extended exercise due to Bryan Parno. This tutorial is a work in progress -- your suggestions are greatly appreciated! --- - -{nbsp} + -{nbsp} + - - **Acknowledgment of Support and Disclaimer.** -This material is based upon work supported by the Air Force Research Laboratory (AFRL) and Defense Advanced Research Projects Agencies (DARPA) under Contract No. FA8750-24-C-B044, a European Research Council (ERC) Advanced Grant “ELVER” under the European Union’s Horizon 2020 research and innovation programme (grant agreement no. 789108), and additional funding from Google. Any opinion, findings and conclusions or recommendations expressed in this material are those of the author(s) and do not necessarily reflect the views of the Air Force Research Laboratory (AFRL). +This material is based upon work supported by the Air Force Research Laboratory (AFRL) and Defense Advanced Research Projects Agencies (DARPA) under Contract No. FA8750-24-C-B044, a European Research Council (ERC) Advanced Grant “ELVER” under the European Union’s Horizon 2020 research and innovation programme (grant agreement no. 789108), and additional funding from Google. The opinions, findings, and conclusions or recommendations expressed in this material are those of the authors and do not necessarily reflect the views of the Air Force Research Laboratory (AFRL). + +-- == Installing CN From 58faff9699b83e3d137d9c30c996e62fb238b1b9 Mon Sep 17 00:00:00 2001 From: Christopher Pulte Date: Mon, 12 Aug 2024 22:09:46 +0100 Subject: [PATCH 108/152] recategorise some tests following Kayvan's https://github.com/cp526/cerberus/commit/e0640bcc7384a8dd876672ed2fd227ac9cac498b --- .../broken/{error-crash => error-proof}/00005_bit_switch.c | 0 .../broken/{error-crash => error-proof}/instrumentation_impl.c | 0 .../broken/{error-crash => error-proof}/shift_crash_1.c | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename src/example-archive/java_program_verification_challenges/broken/{error-crash => error-proof}/00005_bit_switch.c (100%) rename src/example-archive/open-sut/broken/{error-crash => error-proof}/instrumentation_impl.c (100%) rename src/example-archive/simple-examples/broken/{error-crash => error-proof}/shift_crash_1.c (100%) diff --git a/src/example-archive/java_program_verification_challenges/broken/error-crash/00005_bit_switch.c b/src/example-archive/java_program_verification_challenges/broken/error-proof/00005_bit_switch.c similarity index 100% rename from src/example-archive/java_program_verification_challenges/broken/error-crash/00005_bit_switch.c rename to src/example-archive/java_program_verification_challenges/broken/error-proof/00005_bit_switch.c diff --git a/src/example-archive/open-sut/broken/error-crash/instrumentation_impl.c b/src/example-archive/open-sut/broken/error-proof/instrumentation_impl.c similarity index 100% rename from src/example-archive/open-sut/broken/error-crash/instrumentation_impl.c rename to src/example-archive/open-sut/broken/error-proof/instrumentation_impl.c diff --git a/src/example-archive/simple-examples/broken/error-crash/shift_crash_1.c b/src/example-archive/simple-examples/broken/error-proof/shift_crash_1.c similarity index 100% rename from src/example-archive/simple-examples/broken/error-crash/shift_crash_1.c rename to src/example-archive/simple-examples/broken/error-proof/shift_crash_1.c From 596be8b177b38ce969134b0265ad85a6286b1b64 Mon Sep 17 00:00:00 2001 From: Dhruv Makwana Date: Tue, 13 Aug 2024 15:17:23 +0100 Subject: [PATCH 109/152] Temporarily remove crashing pointer dec tests --- .../broken/error-crash/00032.err125.c | 35 ------------------- .../broken/error-crash/00073.err125.c | 20 ----------- .../broken/error-crash/pointerdec_crash_1.c | 5 --- .../broken/error-crash/pointerdec_crash_2.c | 7 ---- .../broken/error-crash/pointerdec_crash_3.c | 9 ----- 5 files changed, 76 deletions(-) delete mode 100644 src/example-archive/c-testsuite/broken/error-crash/00032.err125.c delete mode 100644 src/example-archive/c-testsuite/broken/error-crash/00073.err125.c delete mode 100644 src/example-archive/simple-examples/broken/error-crash/pointerdec_crash_1.c delete mode 100644 src/example-archive/simple-examples/broken/error-crash/pointerdec_crash_2.c delete mode 100644 src/example-archive/simple-examples/broken/error-crash/pointerdec_crash_3.c diff --git a/src/example-archive/c-testsuite/broken/error-crash/00032.err125.c b/src/example-archive/c-testsuite/broken/error-crash/00032.err125.c deleted file mode 100644 index 61fe917c..00000000 --- a/src/example-archive/c-testsuite/broken/error-crash/00032.err125.c +++ /dev/null @@ -1,35 +0,0 @@ -// Crash: caused by pointer decrement - -int -main() -/*@ ensures return == 0i32; @*/ -{ - int arr[2]; - int *p; - - /*@ extract Block, 0u64; @*/ - arr[0] = 2; - /*@ extract Block, 1u64; @*/ - arr[1] = 3; - p = &arr[0]; - if(*(p++) != 2) - return 1; - if(*(p++) != 3) - return 2; - - p = &arr[1]; - if(*(p--) != 3) - return 1; - if(*(p--) != 2) - return 2; - - p = &arr[0]; - if(*(++p) != 3) - return 1; - - p = &arr[1]; - if(*(--p) != 2) // <-- cause of the crash - return 1; - - return 0; -} diff --git a/src/example-archive/c-testsuite/broken/error-crash/00073.err125.c b/src/example-archive/c-testsuite/broken/error-crash/00073.err125.c deleted file mode 100644 index f32b1df7..00000000 --- a/src/example-archive/c-testsuite/broken/error-crash/00073.err125.c +++ /dev/null @@ -1,20 +0,0 @@ -/* -cn: internal error, uncaught exception: - Failure("todo") -*/ -// Cause: `-=` assignment operator - -int -main() -{ - int arr[2]; - int *p; - - p = &arr[1]; - p -= 1; - *p = 123; - - if(arr[0] != 123) - return 1; - return 0; -} diff --git a/src/example-archive/simple-examples/broken/error-crash/pointerdec_crash_1.c b/src/example-archive/simple-examples/broken/error-crash/pointerdec_crash_1.c deleted file mode 100644 index a5d208da..00000000 --- a/src/example-archive/simple-examples/broken/error-crash/pointerdec_crash_1.c +++ /dev/null @@ -1,5 +0,0 @@ -int a; -void b() { - int *c = &a; - c -= 1; -} diff --git a/src/example-archive/simple-examples/broken/error-crash/pointerdec_crash_2.c b/src/example-archive/simple-examples/broken/error-crash/pointerdec_crash_2.c deleted file mode 100644 index 542a2108..00000000 --- a/src/example-archive/simple-examples/broken/error-crash/pointerdec_crash_2.c +++ /dev/null @@ -1,7 +0,0 @@ -// Derived from src/example-archive/c-testsuite/broken/error-proof/00032.err1.c - -int a; -void b() { - int *c = &a; - --c; -} diff --git a/src/example-archive/simple-examples/broken/error-crash/pointerdec_crash_3.c b/src/example-archive/simple-examples/broken/error-crash/pointerdec_crash_3.c deleted file mode 100644 index 1b59c111..00000000 --- a/src/example-archive/simple-examples/broken/error-crash/pointerdec_crash_3.c +++ /dev/null @@ -1,9 +0,0 @@ -// Crash - -void -pointerdec_crash_3() -{ - int arr[2]; - int *p = &arr[1]; - *(--p); -} From 18bf66c90d8f7e193ac4c019573f61c5ae53bb05 Mon Sep 17 00:00:00 2001 From: Dhruv Makwana Date: Tue, 13 Aug 2024 16:00:13 +0100 Subject: [PATCH 110/152] Re-add pointer dec tests --- .../broken/error-proof/00073.err1.c | 20 +++++++++++ .../c-testsuite/working/00032.c | 35 +++++++++++++++++++ .../broken/error-proof/pointer_dec3.c | 9 +++++ .../simple-examples/working/pointer_dec1.c | 5 +++ .../simple-examples/working/pointer_dec2.c | 7 ++++ 5 files changed, 76 insertions(+) create mode 100644 src/example-archive/c-testsuite/broken/error-proof/00073.err1.c create mode 100644 src/example-archive/c-testsuite/working/00032.c create mode 100644 src/example-archive/simple-examples/broken/error-proof/pointer_dec3.c create mode 100644 src/example-archive/simple-examples/working/pointer_dec1.c create mode 100644 src/example-archive/simple-examples/working/pointer_dec2.c diff --git a/src/example-archive/c-testsuite/broken/error-proof/00073.err1.c b/src/example-archive/c-testsuite/broken/error-proof/00073.err1.c new file mode 100644 index 00000000..f32b1df7 --- /dev/null +++ b/src/example-archive/c-testsuite/broken/error-proof/00073.err1.c @@ -0,0 +1,20 @@ +/* +cn: internal error, uncaught exception: + Failure("todo") +*/ +// Cause: `-=` assignment operator + +int +main() +{ + int arr[2]; + int *p; + + p = &arr[1]; + p -= 1; + *p = 123; + + if(arr[0] != 123) + return 1; + return 0; +} diff --git a/src/example-archive/c-testsuite/working/00032.c b/src/example-archive/c-testsuite/working/00032.c new file mode 100644 index 00000000..61fe917c --- /dev/null +++ b/src/example-archive/c-testsuite/working/00032.c @@ -0,0 +1,35 @@ +// Crash: caused by pointer decrement + +int +main() +/*@ ensures return == 0i32; @*/ +{ + int arr[2]; + int *p; + + /*@ extract Block, 0u64; @*/ + arr[0] = 2; + /*@ extract Block, 1u64; @*/ + arr[1] = 3; + p = &arr[0]; + if(*(p++) != 2) + return 1; + if(*(p++) != 3) + return 2; + + p = &arr[1]; + if(*(p--) != 3) + return 1; + if(*(p--) != 2) + return 2; + + p = &arr[0]; + if(*(++p) != 3) + return 1; + + p = &arr[1]; + if(*(--p) != 2) // <-- cause of the crash + return 1; + + return 0; +} diff --git a/src/example-archive/simple-examples/broken/error-proof/pointer_dec3.c b/src/example-archive/simple-examples/broken/error-proof/pointer_dec3.c new file mode 100644 index 00000000..1b59c111 --- /dev/null +++ b/src/example-archive/simple-examples/broken/error-proof/pointer_dec3.c @@ -0,0 +1,9 @@ +// Crash + +void +pointerdec_crash_3() +{ + int arr[2]; + int *p = &arr[1]; + *(--p); +} diff --git a/src/example-archive/simple-examples/working/pointer_dec1.c b/src/example-archive/simple-examples/working/pointer_dec1.c new file mode 100644 index 00000000..a5d208da --- /dev/null +++ b/src/example-archive/simple-examples/working/pointer_dec1.c @@ -0,0 +1,5 @@ +int a; +void b() { + int *c = &a; + c -= 1; +} diff --git a/src/example-archive/simple-examples/working/pointer_dec2.c b/src/example-archive/simple-examples/working/pointer_dec2.c new file mode 100644 index 00000000..542a2108 --- /dev/null +++ b/src/example-archive/simple-examples/working/pointer_dec2.c @@ -0,0 +1,7 @@ +// Derived from src/example-archive/c-testsuite/broken/error-proof/00032.err1.c + +int a; +void b() { + int *c = &a; + --c; +} From f11f32f54c34a51d87251855124b1dda48307d43 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Wed, 14 Aug 2024 09:08:29 -0400 Subject: [PATCH 111/152] use cn verify in check.sh --- check.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/check.sh b/check.sh index 0d439f14..0720a001 100755 --- a/check.sh +++ b/check.sh @@ -5,7 +5,7 @@ SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" if [ -n "$1" ] then echo "using CN=$1 in $PWD" - CN="$1" + CN="$1 verify" else CN=cn verify fi From 0350aa9d96a2899ae92ae6508a214d1174e8e1dd Mon Sep 17 00:00:00 2001 From: Christopher Pulte Date: Thu, 15 Aug 2024 10:57:33 +0100 Subject: [PATCH 112/152] bump up timeout another time --- src/example-archive/check.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/example-archive/check.sh b/src/example-archive/check.sh index 3870c927..40addd36 100755 --- a/src/example-archive/check.sh +++ b/src/example-archive/check.sh @@ -45,7 +45,7 @@ check_file() { local expected_exit_code=$2 printf "[$file]... " - timeout 20 $CN "$file" > /dev/null 2>&1 + timeout 25 $CN "$file" > /dev/null 2>&1 local result=$? if [ $result -eq $expected_exit_code ]; then From 19dc4073f4fa3763f9413cb4cf2de7b833ea95ba Mon Sep 17 00:00:00 2001 From: septract Date: Fri, 16 Aug 2024 17:25:48 -0700 Subject: [PATCH 113/152] update to use `boolean` kw, fix build script issues --- Makefile | 8 ++++---- src/example-archive/check-all.sh | 4 ++-- src/example-archive/check.sh | 2 +- src/example-archive/open-sut/working/mps_1.c | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index f915ad35..4d3ff06d 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ SRC_EXAMPLES=$(shell find src/examples -type f) SOLUTIONS=$(patsubst src/examples/%, build/solutions/%, $(SRC_EXAMPLES)) EXERCISES=$(patsubst src/examples/%, build/exercises/%, $(SRC_EXAMPLES)) -CN=cn verify +CN="cn verify" exercises: $(EXERCISES) $(SOLUTIONS) @@ -47,15 +47,15 @@ build/exercises.zip: $(EXERCISES) ############################################################################## # Check that the examples all run correctly -CN_PATH ?= cn +CN_PATH?="cn verify" check-archive: @echo Check archive examples - @$(MAKEFILE_DIR)/src/example-archive/check-all.sh "$(CN_PATH)" + @$(MAKEFILE_DIR)/src/example-archive/check-all.sh $(CN_PATH) check-tutorial: @echo Check tutorial examples - @$(MAKEFILE_DIR)/check.sh "$(CN_PATH)" + @$(MAKEFILE_DIR)/check.sh $(CN_PATH) check: check-tutorial check-archive diff --git a/src/example-archive/check-all.sh b/src/example-archive/check-all.sh index 085b17ce..37a0d476 100755 --- a/src/example-archive/check-all.sh +++ b/src/example-archive/check-all.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +# #!/usr/bin/env bash SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" @@ -7,7 +7,7 @@ then echo "check-all.sh: using CN=$1 in $PWD" CN="$1" else - CN=cn verify + CN="cn verify" fi subdirs=( diff --git a/src/example-archive/check.sh b/src/example-archive/check.sh index 40addd36..6f713d5c 100755 --- a/src/example-archive/check.sh +++ b/src/example-archive/check.sh @@ -5,7 +5,7 @@ then echo "check.sh: using CN=$1 in $PWD" CN="$1" else - CN=cn verify + CN="cn verify" fi process_files() { diff --git a/src/example-archive/open-sut/working/mps_1.c b/src/example-archive/open-sut/working/mps_1.c index 8f3e29f9..9f64148c 100644 --- a/src/example-archive/open-sut/working/mps_1.c +++ b/src/example-archive/open-sut/working/mps_1.c @@ -19,7 +19,7 @@ typedef uint8_t w2; typedef uint8_t w8; /*@ - function (bool) P_Coincidence_2_4(bool a, bool b, bool c, bool d) { + function (boolean) P_Coincidence_2_4(boolean a, boolean b, boolean c, boolean d) { (a&&b) || ((a||b) && (c||d)) || (c&&d) } From 3fa9acdea026deaaa61ce6cebfa1ae416ea247f6 Mon Sep 17 00:00:00 2001 From: septract Date: Fri, 16 Aug 2024 17:40:19 -0700 Subject: [PATCH 114/152] Fix more `bool` cases, fix Makefile --- Makefile | 2 +- src/example-archive/simple-examples/working/list_preds.h | 2 +- src/examples/runway/valid_state.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 4d3ff06d..bd46ecb1 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ SRC_EXAMPLES=$(shell find src/examples -type f) SOLUTIONS=$(patsubst src/examples/%, build/solutions/%, $(SRC_EXAMPLES)) EXERCISES=$(patsubst src/examples/%, build/exercises/%, $(SRC_EXAMPLES)) -CN="cn verify" +CN=cn verify exercises: $(EXERCISES) $(SOLUTIONS) diff --git a/src/example-archive/simple-examples/working/list_preds.h b/src/example-archive/simple-examples/working/list_preds.h index bb8d3e67..90f67d16 100644 --- a/src/example-archive/simple-examples/working/list_preds.h +++ b/src/example-archive/simple-examples/working/list_preds.h @@ -52,7 +52,7 @@ function [rec] (datatype seq) append(datatype seq xs, datatype seq ys) { @*/ // /*@ -// function [rec] (bool) fold_eq(datatype seq xs, i32 test) { +// function [rec] (boolean) fold_eq(datatype seq xs, i32 test) { // match xs { // Seq_Nil {} => { // true diff --git a/src/examples/runway/valid_state.h b/src/examples/runway/valid_state.h index fa0b267a..c36c7e73 100644 --- a/src/examples/runway/valid_state.h +++ b/src/examples/runway/valid_state.h @@ -1,5 +1,5 @@ /*@ -function (bool) valid_state (struct State s) { +function (boolean) valid_state (struct State s) { (s.ModeA == INACTIVE() || s.ModeA == ACTIVE()) && (s.ModeD == INACTIVE() || s.ModeD == ACTIVE()) && (s.ModeA == INACTIVE() || s.ModeD == INACTIVE()) && From 35bedb2b1e5b995071d24824f975849ae0c07612 Mon Sep 17 00:00:00 2001 From: septract Date: Fri, 16 Aug 2024 17:46:47 -0700 Subject: [PATCH 115/152] One more instance of `bool` --- src/example-archive/coq-lemmas/inprogress/trivial-007.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/example-archive/coq-lemmas/inprogress/trivial-007.c b/src/example-archive/coq-lemmas/inprogress/trivial-007.c index 1752d92a..b689d3ee 100644 --- a/src/example-archive/coq-lemmas/inprogress/trivial-007.c +++ b/src/example-archive/coq-lemmas/inprogress/trivial-007.c @@ -1,6 +1,6 @@ #include /*@ - predicate (bool) MyCondition (u32 x){ + predicate (boolean) MyCondition (u32 x){ if (x > 4294967295u32){ return false; } else { From d78811b9239949e97e7d3e79ffa7fac9cf608a4c Mon Sep 17 00:00:00 2001 From: septract Date: Fri, 16 Aug 2024 18:13:26 -0700 Subject: [PATCH 116/152] Build script tweak --- check.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/check.sh b/check.sh index 0720a001..2eb54c26 100755 --- a/check.sh +++ b/check.sh @@ -5,9 +5,9 @@ SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" if [ -n "$1" ] then echo "using CN=$1 in $PWD" - CN="$1 verify" + CN="$1" else - CN=cn verify + CN="cn verify" fi good=0 From 4282590a9abfa916a05f074301f117d2ac778470 Mon Sep 17 00:00:00 2001 From: septract Date: Fri, 16 Aug 2024 18:18:41 -0700 Subject: [PATCH 117/152] Typo --- src/example-archive/check-all.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/example-archive/check-all.sh b/src/example-archive/check-all.sh index 37a0d476..ae2b3171 100755 --- a/src/example-archive/check-all.sh +++ b/src/example-archive/check-all.sh @@ -1,4 +1,4 @@ -# #!/usr/bin/env bash +#!/usr/bin/env bash SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" From 1d2bde15e03e371b3b221d966c1a485e531299ff Mon Sep 17 00:00:00 2001 From: septract Date: Fri, 16 Aug 2024 18:41:52 -0700 Subject: [PATCH 118/152] Fix Makefile issue --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index bd46ecb1..1bb28078 100644 --- a/Makefile +++ b/Makefile @@ -47,15 +47,15 @@ build/exercises.zip: $(EXERCISES) ############################################################################## # Check that the examples all run correctly -CN_PATH?="cn verify" +CN_PATH?=cn verify check-archive: @echo Check archive examples - @$(MAKEFILE_DIR)/src/example-archive/check-all.sh $(CN_PATH) + @$(MAKEFILE_DIR)/src/example-archive/check-all.sh "$(CN_PATH)" check-tutorial: @echo Check tutorial examples - @$(MAKEFILE_DIR)/check.sh $(CN_PATH) + @$(MAKEFILE_DIR)/check.sh "$(CN_PATH)" check: check-tutorial check-archive From 6445df47e7dfa43b31d4e1a5619aed08dc3f7b59 Mon Sep 17 00:00:00 2001 From: septract Date: Wed, 17 Jul 2024 22:48:20 -0700 Subject: [PATCH 119/152] Log SMT output from recently changed files --- build-smt-log.sh | 53 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100755 build-smt-log.sh diff --git a/build-smt-log.sh b/build-smt-log.sh new file mode 100755 index 00000000..5a49f4c9 --- /dev/null +++ b/build-smt-log.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash + +BRANCH="main" +INTERVAL="1.month" + +if [ -n "$1" ] +then + echo "using CN=$1 in $PWD" + CN="$1" +else + CN="cn" +fi + +EXAMPLE_RXP="^src/(examples/[^/]+\.c|example-archive/[^/]+/working/[^/]+\.c)$" +FILTER_RXP="broken\.c$" + +FILES=$(git log --since="$INTERVAL" --name-only --pretty=format: "$BRANCH" | sort | uniq) +FILTERED_FILES=$(echo "$FILES" | grep -E "$EXAMPLE_RXP" | grep -Ev "$FILTER_RXP") + +TEMP_DIR=$(realpath "$(mktemp -d ./tmp.XXXXXX)") +ROOT_DIR=$(pwd) + +LOGGED_FILES="" + +for FILEPATH in $FILTERED_FILES; do + if [ -f "$FILEPATH" ]; then + DIR=$(dirname "$FILEPATH") + FILE=$(basename "$FILEPATH") + + LOG_DIR="$TEMP_DIR/$FILEPATH-log" + mkdir -p "$LOG_DIR" + + cd $DIR + $CN "$FILE" --solver-type=cvc5 --solver-logging="$LOG_DIR" + cp "$FILE" "$LOG_DIR" + cd "$ROOT_DIR" + + LOGGED_FILES="$LOGGED_FILES $FILEPATH-log/" + fi +done + +DATE_STRING=$(date +"%Y-%m-%d_%H-%M-%S") +echo "Log date: $DATE_STRING" >> "$TEMP_DIR/manifest.txt" +echo "" >> "$TEMP_DIR/manifest.txt" + +for FILE in $LOGGED_FILES; do + echo "$FILE" >> "$TEMP_DIR/manifest.txt" +done + +cd "$TEMP_DIR" +zip -r "$ROOT_DIR/SMT-log-$DATE_STRING.zip" ./* + +rm -r "$TEMP_DIR" \ No newline at end of file From 98606828fa5525df34754cb4a855d85ef8c81cee Mon Sep 17 00:00:00 2001 From: septract Date: Wed, 17 Jul 2024 23:13:43 -0700 Subject: [PATCH 120/152] Tidy up script, add comments --- build-smt-log.sh | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/build-smt-log.sh b/build-smt-log.sh index 5a49f4c9..d476a666 100755 --- a/build-smt-log.sh +++ b/build-smt-log.sh @@ -3,6 +3,7 @@ BRANCH="main" INTERVAL="1.month" +# Allow the CI process to pass a location for CN if [ -n "$1" ] then echo "using CN=$1 in $PWD" @@ -11,43 +12,47 @@ else CN="cn" fi +# Generate a list of recently-changed files +FILES=$(git log --since="$INTERVAL" --name-only --pretty=format: "$BRANCH" | sort | uniq) + +# Regexp selecting files in the correct location EXAMPLE_RXP="^src/(examples/[^/]+\.c|example-archive/[^/]+/working/[^/]+\.c)$" +# Regexp filtering files marked broken FILTER_RXP="broken\.c$" - -FILES=$(git log --since="$INTERVAL" --name-only --pretty=format: "$BRANCH" | sort | uniq) +# Filter the list for files of interest FILTERED_FILES=$(echo "$FILES" | grep -E "$EXAMPLE_RXP" | grep -Ev "$FILTER_RXP") +# Create a temporary directory TEMP_DIR=$(realpath "$(mktemp -d ./tmp.XXXXXX)") -ROOT_DIR=$(pwd) -LOGGED_FILES="" +# Create a log file listing the locations of SMT logs +DATE_STRING=$(date +"%Y-%m-%d_%H-%M-%S") +echo "# Log date: $DATE_STRING" >> "$TEMP_DIR/manifest.md" +echo "" >> "$TEMP_DIR/manifest.md" +ROOT_DIR=$(pwd) for FILEPATH in $FILTERED_FILES; do if [ -f "$FILEPATH" ]; then DIR=$(dirname "$FILEPATH") FILE=$(basename "$FILEPATH") + # Create the log directory for each file LOG_DIR="$TEMP_DIR/$FILEPATH-log" mkdir -p "$LOG_DIR" cd $DIR + # Run CN on the target file $CN "$FILE" --solver-type=cvc5 --solver-logging="$LOG_DIR" cp "$FILE" "$LOG_DIR" cd "$ROOT_DIR" - LOGGED_FILES="$LOGGED_FILES $FILEPATH-log/" + # Record the file in the manifest + echo "$FILEPATH-log/" >> "$TEMP_DIR/manifest.md" fi done -DATE_STRING=$(date +"%Y-%m-%d_%H-%M-%S") -echo "Log date: $DATE_STRING" >> "$TEMP_DIR/manifest.txt" -echo "" >> "$TEMP_DIR/manifest.txt" - -for FILE in $LOGGED_FILES; do - echo "$FILE" >> "$TEMP_DIR/manifest.txt" -done - +# Create a zip file of the SMT logs cd "$TEMP_DIR" -zip -r "$ROOT_DIR/SMT-log-$DATE_STRING.zip" ./* +zip -r "$ROOT_DIR/SMT-log-$DATE_STRING.zip" * rm -r "$TEMP_DIR" \ No newline at end of file From f0b262cfc2df5b142ffe3712764961654e403338 Mon Sep 17 00:00:00 2001 From: septract Date: Fri, 19 Jul 2024 17:35:59 -0700 Subject: [PATCH 121/152] Tweak log generator for use in CI --- build-smt-log.sh | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/build-smt-log.sh b/build-smt-log.sh index d476a666..5ab58734 100755 --- a/build-smt-log.sh +++ b/build-smt-log.sh @@ -9,7 +9,7 @@ then echo "using CN=$1 in $PWD" CN="$1" else - CN="cn" + CN="cn verify" fi # Generate a list of recently-changed files @@ -28,6 +28,7 @@ TEMP_DIR=$(realpath "$(mktemp -d ./tmp.XXXXXX)") # Create a log file listing the locations of SMT logs DATE_STRING=$(date +"%Y-%m-%d_%H-%M-%S") echo "# Log date: $DATE_STRING" >> "$TEMP_DIR/manifest.md" +echo "# $(cn --version)" >> "$TEMP_DIR/manifest.md" echo "" >> "$TEMP_DIR/manifest.md" ROOT_DIR=$(pwd) @@ -42,17 +43,19 @@ for FILEPATH in $FILTERED_FILES; do cd $DIR # Run CN on the target file - $CN "$FILE" --solver-type=cvc5 --solver-logging="$LOG_DIR" + $CN "$FILE" --solver-type=cvc5 --solver-logging="$LOG_DIR" 1>&2 cp "$FILE" "$LOG_DIR" cd "$ROOT_DIR" # Record the file in the manifest - echo "$FILEPATH-log/" >> "$TEMP_DIR/manifest.md" + echo "$FILEPATH-log/" >> "$TEMP_DIR/manifest.md" fi done # Create a zip file of the SMT logs cd "$TEMP_DIR" -zip -r "$ROOT_DIR/SMT-log-$DATE_STRING.zip" * +zip -r "$ROOT_DIR/SMT-log-$DATE_STRING.zip" * 1>&2 -rm -r "$TEMP_DIR" \ No newline at end of file +rm -r "$TEMP_DIR" + +echo "$ROOT_DIR/SMT-log-$DATE_STRING.zip" \ No newline at end of file From 3e1592eabfeb11bcf45914f290d5069f5e99dbb3 Mon Sep 17 00:00:00 2001 From: septract Date: Mon, 19 Aug 2024 16:20:00 -0700 Subject: [PATCH 122/152] Revise script per dialog with CVC5 team --- build-smt-log.sh | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/build-smt-log.sh b/build-smt-log.sh index 5ab58734..5fbe2a6f 100755 --- a/build-smt-log.sh +++ b/build-smt-log.sh @@ -17,7 +17,7 @@ FILES=$(git log --since="$INTERVAL" --name-only --pretty=format: "$BRANCH" | sor # Regexp selecting files in the correct location EXAMPLE_RXP="^src/(examples/[^/]+\.c|example-archive/[^/]+/working/[^/]+\.c)$" -# Regexp filtering files marked broken +# Regexp filtering out files marked 'broken' FILTER_RXP="broken\.c$" # Filter the list for files of interest FILTERED_FILES=$(echo "$FILES" | grep -E "$EXAMPLE_RXP" | grep -Ev "$FILTER_RXP") @@ -27,6 +27,7 @@ TEMP_DIR=$(realpath "$(mktemp -d ./tmp.XXXXXX)") # Create a log file listing the locations of SMT logs DATE_STRING=$(date +"%Y-%m-%d_%H-%M-%S") +DATE_STRING_SHORT=$(date +"%Y-%m-%d") echo "# Log date: $DATE_STRING" >> "$TEMP_DIR/manifest.md" echo "# $(cn --version)" >> "$TEMP_DIR/manifest.md" echo "" >> "$TEMP_DIR/manifest.md" @@ -43,18 +44,22 @@ for FILEPATH in $FILTERED_FILES; do cd $DIR # Run CN on the target file - $CN "$FILE" --solver-type=cvc5 --solver-logging="$LOG_DIR" 1>&2 + $CN "$FILE" --solver-type=cvc5 --solver-logging="$LOG_DIR" --quiet cp "$FILE" "$LOG_DIR" cd "$ROOT_DIR" + # Remove model files - should probably not emit these + rm "$LOG_DIR"/model_send*.smt + # Record the file in the manifest echo "$FILEPATH-log/" >> "$TEMP_DIR/manifest.md" fi done -# Create a zip file of the SMT logs +# Create a zip file and tar.gz file of the SMT logs cd "$TEMP_DIR" -zip -r "$ROOT_DIR/SMT-log-$DATE_STRING.zip" * 1>&2 +zip -r "$ROOT_DIR/${DATE_STRING_SHORT}_CN-SMT-log.zip" * 1>&2 +tar -czvf "$ROOT_DIR/${DATE_STRING_SHORT}_CN-SMT-log.tar.gz" * 1>&2 rm -r "$TEMP_DIR" From d3b44c1e44c87b28d803b267dc676311cc77b9b7 Mon Sep 17 00:00:00 2001 From: Dhruv Makwana Date: Tue, 20 Aug 2024 13:13:02 +0100 Subject: [PATCH 123/152] Fix IntListSeg predicate https://github.com/rems-project/cerberus/pull/494 made this tests fail, but it wasn't quite phrased correctly anyways. It may need further changes for more VIP features later, but this should suffice for now. --- src/example-archive/simple-examples/working/list_preds.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/example-archive/simple-examples/working/list_preds.h b/src/example-archive/simple-examples/working/list_preds.h index 90f67d16..61008b04 100644 --- a/src/example-archive/simple-examples/working/list_preds.h +++ b/src/example-archive/simple-examples/working/list_preds.h @@ -25,11 +25,10 @@ datatype seq { /*@ predicate (datatype seq) IntListSeg(pointer p, pointer tail) { - if (addr_eq(p,tail)) { + if (ptr_eq(p,tail)) { return Seq_Nil{}; } else { take H = Owned(p); - assert (is_null(H.next) || H.next != NULL); take tl = IntListSeg(H.next, tail); return (Seq_Cons { val: H.val, next: tl }); } @@ -62,4 +61,4 @@ function [rec] (datatype seq) append(datatype seq xs, datatype seq ys) { // } // } // } -// @*/ \ No newline at end of file +// @*/ From b7bda5dba748163a8a671ca7ece53d07abf5ba56 Mon Sep 17 00:00:00 2001 From: Dhruv Makwana Date: Mon, 26 Aug 2024 14:05:43 +0100 Subject: [PATCH 124/152] Work-around Z3 bug https://github.com/rems-project/cerberus/pull/494 exposed an issue in the Z3 which is a bit difficult to work around in the implementation itself and so we have this hacky work-around instead whilst it is fixed upstream https://github.com/Z3Prover/z3/issues/7352 --- check.sh | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/check.sh b/check.sh index 2eb54c26..697d143f 100755 --- a/check.sh +++ b/check.sh @@ -14,7 +14,20 @@ good=0 bad=0 declare -a bad_tests -for file in $SCRIPT_DIR/src/examples/*c; +# https://github.com/rems-project/cerberus/pull/494 exposed an issue in +# the Z3 which is a bit difficult to work around in the implementation +# itself and so we have this hacky work-around instead whilst it is fixed +# upstream https://github.com/Z3Prover/z3/issues/7352 +if [[ "${CN}" == "cn verify" ]] \ + || [[ "${CN}" == *"--solver-type=z3"* ]]; then + FILES=($(find "${SCRIPT_DIR}/src/examples" -name '*.c' \ + ! -name queue_pop.c \ + ! -name queue_push_induction.c)) +else + FILES=($(find "${SCRIPT_DIR}/src/examples" -name '*.c')) +fi + +for file in "${FILES[@]}" do echo "Checking $file ..." $CN $file From ff994f93eef7303045d8779caa98f6423b662fd2 Mon Sep 17 00:00:00 2001 From: Dhruv Makwana Date: Mon, 26 Aug 2024 16:39:08 +0100 Subject: [PATCH 125/152] Revert "Work-around Z3 bug" This reverts commit https://github.com/rems-project/cn-tutorial/commit/b54ec3437ebb4445a81cc82fba9fd8c72d534f6e I figured out a work-around in CN. --- check.sh | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/check.sh b/check.sh index 697d143f..2eb54c26 100755 --- a/check.sh +++ b/check.sh @@ -14,20 +14,7 @@ good=0 bad=0 declare -a bad_tests -# https://github.com/rems-project/cerberus/pull/494 exposed an issue in -# the Z3 which is a bit difficult to work around in the implementation -# itself and so we have this hacky work-around instead whilst it is fixed -# upstream https://github.com/Z3Prover/z3/issues/7352 -if [[ "${CN}" == "cn verify" ]] \ - || [[ "${CN}" == *"--solver-type=z3"* ]]; then - FILES=($(find "${SCRIPT_DIR}/src/examples" -name '*.c' \ - ! -name queue_pop.c \ - ! -name queue_push_induction.c)) -else - FILES=($(find "${SCRIPT_DIR}/src/examples" -name '*.c')) -fi - -for file in "${FILES[@]}" +for file in $SCRIPT_DIR/src/examples/*c; do echo "Checking $file ..." $CN $file From c98d4ac4f7737af8330a05ddbe8e857e444e9c44 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Wed, 28 Aug 2024 12:50:44 -0400 Subject: [PATCH 126/152] use == instead of ptr_eq to compare u32 values (#83) --- src/examples/slf_incr2.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/examples/slf_incr2.c b/src/examples/slf_incr2.c index 94de75c6..cd4b9c23 100644 --- a/src/examples/slf_incr2.c +++ b/src/examples/slf_incr2.c @@ -16,10 +16,8 @@ predicate { u32 pv, u32 qv } BothOwned (pointer p, pointer q) void incr2 (unsigned int *p, unsigned int *q) /*@ requires take vs = BothOwned(p,q); ensures take vs_ = BothOwned(p,q); - ptr_eq (vs_.pv, - (!ptr_eq(p,q)) ? (vs.pv + 1u32) : (vs.pv + 2u32)); - ptr_eq (vs_.qv, - (!ptr_eq(p,q)) ? (vs.qv + 1u32) : vs_.pv); + vs_.pv == (!ptr_eq(p,q) ? (vs.pv + 1u32) : (vs.pv + 2u32)); + vs_.qv == (!ptr_eq(p,q) ? (vs.qv + 1u32) : vs_.pv); @*/ { /*@ split_case ptr_eq(p,q); @*/ From 24057b870eff39244c3b8623105bddc7bb682659 Mon Sep 17 00:00:00 2001 From: Christopher Pulte Date: Wed, 11 Sep 2024 17:04:18 +0100 Subject: [PATCH 127/152] bump timeout again --- src/example-archive/check.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/example-archive/check.sh b/src/example-archive/check.sh index 6f713d5c..e0eac7f5 100755 --- a/src/example-archive/check.sh +++ b/src/example-archive/check.sh @@ -45,7 +45,7 @@ check_file() { local expected_exit_code=$2 printf "[$file]... " - timeout 25 $CN "$file" > /dev/null 2>&1 + timeout 30 $CN "$file" > /dev/null 2>&1 local result=$? if [ $result -eq $expected_exit_code ]; then From 3d5cb82f1c3c3718509fa46797aee2d67b015570 Mon Sep 17 00:00:00 2001 From: Christopher Pulte Date: Sun, 29 Sep 2024 10:17:29 +0100 Subject: [PATCH 128/152] recategorise that test --- .../broken/{error-proof => error-crash}/00001_aliasing.c | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/example-archive/java_program_verification_challenges/broken/{error-proof => error-crash}/00001_aliasing.c (100%) diff --git a/src/example-archive/java_program_verification_challenges/broken/error-proof/00001_aliasing.c b/src/example-archive/java_program_verification_challenges/broken/error-crash/00001_aliasing.c similarity index 100% rename from src/example-archive/java_program_verification_challenges/broken/error-proof/00001_aliasing.c rename to src/example-archive/java_program_verification_challenges/broken/error-crash/00001_aliasing.c From f003208c01377d882febb1e0590bf2054ddfc4d8 Mon Sep 17 00:00:00 2001 From: Dhruv Makwana Date: Mon, 30 Sep 2024 17:18:45 +0100 Subject: [PATCH 129/152] Temporarily remove test (#87) https://github.com/rems-project/cerberus/pull/602 fixes a bug in the solver which changes the behaviour of this test, so I'm deleting it so that the CI passes (to re-add it later once the PR is merged). From 3e1d0eb95875d9984e664ace8b5bfb6403705991 Mon Sep 17 00:00:00 2001 From: Dhruv Makwana Date: Mon, 30 Sep 2024 17:22:16 +0100 Subject: [PATCH 130/152] Temporarily remove test https://github.com/rems-project/cerberus/pull/602 fixes a bug in the solver which changes the behaviour of this test, so I'm deleting it so that the CI passes (to re-add it later once the PR is merged). --- .../broken/error-crash/00001_aliasing.c | 48 ------------------- 1 file changed, 48 deletions(-) delete mode 100644 src/example-archive/java_program_verification_challenges/broken/error-crash/00001_aliasing.c diff --git a/src/example-archive/java_program_verification_challenges/broken/error-crash/00001_aliasing.c b/src/example-archive/java_program_verification_challenges/broken/error-crash/00001_aliasing.c deleted file mode 100644 index f5501f0e..00000000 --- a/src/example-archive/java_program_verification_challenges/broken/error-crash/00001_aliasing.c +++ /dev/null @@ -1,48 +0,0 @@ -// Tags: main, pointers, structs, alias, java, malloc - -//#include -#include - -// Struct corresponding to the Java class C -typedef struct C { - struct C *a; // Pointer to same struct type to mimic object reference - int i; -} C; - -/* Normal-behavior - * @ requires true; - * @ assignable a, i; - * @ ensures a == NULL && i == 1; - */ -C* createC() { - C* c = (C*) malloc(sizeof(C)); // Allocate memory for C - c->a = NULL; // Initialize as null - c->i = 1; // Initialize i to 1 - return c; -} - -// Struct corresponding to the Java class Alias -/* typedef struct Alias { */ -/* // No direct fields needed */ -/* } Alias; */ - -/* Normal-behavior - * @ requires true; - * @ assignable nothing; - * @ ensures result == 4; - */ -int m() { - C* c = createC(); // Similar to 'C c = new C();' - c->a = c; // Alias to itself - c->i = 2; // Change i to 2 - int result = c->i + c->a->i; // Equivalent to 'return c.i + c.a.i;' - free(c); // Clean up allocated memory - return result; -} - -int main() { - /* Alias alias; // Instantiate Alias */ - int result = m(); // Call m and store result - //printf("Result: %d\n", result); // Print the result - return result; -} From a551d3fab60856b0c4409373c6f24a4d973af709 Mon Sep 17 00:00:00 2001 From: Dhruv Makwana Date: Mon, 30 Sep 2024 17:47:37 +0100 Subject: [PATCH 131/152] Re-add removed broken-crash test as broken-proof https://github.com/rems-project/cerberus/pull/602 changed the behaviour of this test --- .../broken/error-proof/00001_aliasing.c | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 src/example-archive/java_program_verification_challenges/broken/error-proof/00001_aliasing.c diff --git a/src/example-archive/java_program_verification_challenges/broken/error-proof/00001_aliasing.c b/src/example-archive/java_program_verification_challenges/broken/error-proof/00001_aliasing.c new file mode 100644 index 00000000..f5501f0e --- /dev/null +++ b/src/example-archive/java_program_verification_challenges/broken/error-proof/00001_aliasing.c @@ -0,0 +1,48 @@ +// Tags: main, pointers, structs, alias, java, malloc + +//#include +#include + +// Struct corresponding to the Java class C +typedef struct C { + struct C *a; // Pointer to same struct type to mimic object reference + int i; +} C; + +/* Normal-behavior + * @ requires true; + * @ assignable a, i; + * @ ensures a == NULL && i == 1; + */ +C* createC() { + C* c = (C*) malloc(sizeof(C)); // Allocate memory for C + c->a = NULL; // Initialize as null + c->i = 1; // Initialize i to 1 + return c; +} + +// Struct corresponding to the Java class Alias +/* typedef struct Alias { */ +/* // No direct fields needed */ +/* } Alias; */ + +/* Normal-behavior + * @ requires true; + * @ assignable nothing; + * @ ensures result == 4; + */ +int m() { + C* c = createC(); // Similar to 'C c = new C();' + c->a = c; // Alias to itself + c->i = 2; // Change i to 2 + int result = c->i + c->a->i; // Equivalent to 'return c.i + c.a.i;' + free(c); // Clean up allocated memory + return result; +} + +int main() { + /* Alias alias; // Instantiate Alias */ + int result = m(); // Call m and store result + //printf("Result: %d\n", result); // Print the result + return result; +} From f284ac2ed1188d593ff425fea0787b1450f6d9e8 Mon Sep 17 00:00:00 2001 From: Dhruv Makwana Date: Mon, 30 Sep 2024 18:09:31 +0100 Subject: [PATCH 132/152] Add support for VIP This commit is a backwards compatible change to some tests to enable VIP by default in CN in an upcoming commit. --- src/example-archive/c-testsuite/working/00032.c | 2 +- src/example-archive/simple-examples/working/cast_1.c | 4 +++- src/example-archive/simple-examples/working/cast_2.c | 2 +- src/example-archive/simple-examples/working/cast_3.c | 2 +- src/example-archive/simple-examples/working/cast_4.c | 2 +- src/example-archive/simple-examples/working/pointer_dec1.c | 4 ++-- src/example-archive/simple-examples/working/pointer_dec2.c | 4 ++-- src/examples/queue_cn_types_2.h | 1 + src/examples/queue_cn_types_3.h | 3 ++- src/examples/queue_pop.c | 1 + src/examples/queue_push.c | 2 +- src/examples/queue_push_induction.c | 2 ++ src/examples/queue_push_lemma.h | 2 ++ 13 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/example-archive/c-testsuite/working/00032.c b/src/example-archive/c-testsuite/working/00032.c index 61fe917c..1867ba73 100644 --- a/src/example-archive/c-testsuite/working/00032.c +++ b/src/example-archive/c-testsuite/working/00032.c @@ -20,7 +20,7 @@ main() p = &arr[1]; if(*(p--) != 3) return 1; - if(*(p--) != 2) + if(*p != 2) return 2; p = &arr[0]; diff --git a/src/example-archive/simple-examples/working/cast_1.c b/src/example-archive/simple-examples/working/cast_1.c index b2b785dc..25877d83 100644 --- a/src/example-archive/simple-examples/working/cast_1.c +++ b/src/example-archive/simple-examples/working/cast_1.c @@ -1,4 +1,6 @@ // Cast a pointer to an int, and back +// In regular VIP, this does not require a copy_alloc_id but as implemented +// currently in CN, it does. #include // For uintptr_t, intptr_t @@ -12,7 +14,7 @@ int cast_1() uintptr_t ptr_as_int = (uintptr_t) ptr_original; // Cast back to pointer - int *ptr_restored = (int *)ptr_as_int; + int *ptr_restored = __cerbvar_copy_alloc_id(ptr_as_int, &x); // Dereference the pointer int ret = *ptr_restored; diff --git a/src/example-archive/simple-examples/working/cast_2.c b/src/example-archive/simple-examples/working/cast_2.c index 82af441b..e2b548f6 100644 --- a/src/example-archive/simple-examples/working/cast_2.c +++ b/src/example-archive/simple-examples/working/cast_2.c @@ -18,7 +18,7 @@ int cast_2() if (ptr_as_int < ptr_as_int_copy) // Check for overflow { ptr_as_int_copy = ptr_as_int_copy - 1; - int *ptr_restored = (int *)ptr_as_int_copy; + int *ptr_restored = __cerbvar_copy_alloc_id(ptr_as_int_copy, &x); int ret = *ptr_restored; diff --git a/src/example-archive/simple-examples/working/cast_3.c b/src/example-archive/simple-examples/working/cast_3.c index f9ef0cbc..09150429 100644 --- a/src/example-archive/simple-examples/working/cast_3.c +++ b/src/example-archive/simple-examples/working/cast_3.c @@ -19,7 +19,7 @@ int cast_3() if (ptr_as_int < ptr_as_int_copy) // Check for overflow { ptr_as_int_copy = ptr_as_int_copy - OFFSET; - int *ptr_restored = (int *)ptr_as_int_copy; + int *ptr_restored = __cerbvar_copy_alloc_id(ptr_as_int_copy, &x); int ret = *ptr_restored; diff --git a/src/example-archive/simple-examples/working/cast_4.c b/src/example-archive/simple-examples/working/cast_4.c index d66d9b03..5510b6c5 100644 --- a/src/example-archive/simple-examples/working/cast_4.c +++ b/src/example-archive/simple-examples/working/cast_4.c @@ -19,7 +19,7 @@ int cast_4(int *ptr_original) if (ptr_as_int < ptr_as_int_copy) // Check for overflow { ptr_as_int_copy = ptr_as_int_copy - OFFSET; - int *ptr_restored = (int *)ptr_as_int_copy; + int *ptr_restored = __cerbvar_copy_alloc_id(ptr_as_int_copy, ptr_original); int ret = *ptr_restored; diff --git a/src/example-archive/simple-examples/working/pointer_dec1.c b/src/example-archive/simple-examples/working/pointer_dec1.c index a5d208da..33e87bae 100644 --- a/src/example-archive/simple-examples/working/pointer_dec1.c +++ b/src/example-archive/simple-examples/working/pointer_dec1.c @@ -1,5 +1,5 @@ -int a; +int a[2]; void b() { - int *c = &a; + int *c = &a[1]; c -= 1; } diff --git a/src/example-archive/simple-examples/working/pointer_dec2.c b/src/example-archive/simple-examples/working/pointer_dec2.c index 542a2108..058880fc 100644 --- a/src/example-archive/simple-examples/working/pointer_dec2.c +++ b/src/example-archive/simple-examples/working/pointer_dec2.c @@ -1,7 +1,7 @@ // Derived from src/example-archive/c-testsuite/broken/error-proof/00032.err1.c -int a; +int a[2]; void b() { - int *c = &a; + int *c = &a[1]; --c; } diff --git a/src/examples/queue_cn_types_2.h b/src/examples/queue_cn_types_2.h index d8e724f3..440a2cb2 100644 --- a/src/examples/queue_cn_types_2.h +++ b/src/examples/queue_cn_types_2.h @@ -5,6 +5,7 @@ predicate (datatype seq) IntQueueFB (pointer front, pointer back) { } else { take B = Owned(back); assert (is_null(B.next)); + assert (ptr_eq(front, back) || !addr_eq(front, back)); take L = IntQueueAux (front, back); return snoc(L, B.first); } diff --git a/src/examples/queue_cn_types_3.h b/src/examples/queue_cn_types_3.h index 99f22eb0..83deddf8 100644 --- a/src/examples/queue_cn_types_3.h +++ b/src/examples/queue_cn_types_3.h @@ -4,7 +4,8 @@ predicate (datatype seq) IntQueueAux (pointer f, pointer b) { return Seq_Nil{}; } else { take F = Owned(f); - assert (!is_null(F.next)); + assert (!is_null(F.next)); + assert (ptr_eq(F.next, b) || !addr_eq(F.next, b)); take B = IntQueueAux(F.next, b); return Seq_Cons{head: F.first, tail: B}; } diff --git a/src/examples/queue_pop.c b/src/examples/queue_pop.c index d1053c25..56f8a87a 100644 --- a/src/examples/queue_pop.c +++ b/src/examples/queue_pop.c @@ -12,6 +12,7 @@ int IntQueue_pop (struct int_queue *q) /*@ split_case is_null(q->front); @*/ struct int_queueCell* h = q->front; if (h == q->back) { + /*@ assert ((alloc_id) h == (alloc_id) (q->back)); @*/ int x = h->first; freeIntQueueCell(h); q->front = 0; diff --git a/src/examples/queue_push.c b/src/examples/queue_push.c index e22dd152..98e9826f 100644 --- a/src/examples/queue_push.c +++ b/src/examples/queue_push.c @@ -18,7 +18,7 @@ void IntQueue_push (int x, struct int_queue *q) struct int_queueCell *oldback = q->back; q->back->next = c; q->back = c; - /*@ apply push_lemma (q->front, oldback); @*/ + /*@ apply push_lemma(q->front, oldback); @*/ return; } } diff --git a/src/examples/queue_push_induction.c b/src/examples/queue_push_induction.c index 9d0f897a..dc1b012d 100644 --- a/src/examples/queue_push_induction.c +++ b/src/examples/queue_push_induction.c @@ -5,11 +5,13 @@ void push_induction(struct int_queueCell* front , struct int_queueCell* last) /*@ requires + ptr_eq(front, second_last) || !addr_eq(front, second_last); take Q = IntQueueAux(front, second_last); take Second_last = Owned(second_last); ptr_eq(Second_last.next, last); take Last = Owned(last); ensures + ptr_eq(front, last) || !addr_eq(front, last); take NewQ = IntQueueAux(front, last); take Last2 = Owned(last); NewQ == snoc(Q, Second_last.first); diff --git a/src/examples/queue_push_lemma.h b/src/examples/queue_push_lemma.h index d3052dc2..a93abe50 100644 --- a/src/examples/queue_push_lemma.h +++ b/src/examples/queue_push_lemma.h @@ -1,9 +1,11 @@ /*@ lemma push_lemma (pointer front, pointer p) requires + ptr_eq(front, p) || !addr_eq(front, p); take Q = IntQueueAux(front, p); take P = Owned(p); ensures + ptr_eq(front, P.next) || !addr_eq(front, P.next); take NewQ = IntQueueAux(front, P.next); NewQ == snoc(Q, P.first); @*/ From 7c0a52c4ef2438c654c7433d50c50fd9929f0d3e Mon Sep 17 00:00:00 2001 From: Cole Schlesinger <63367934+thatplguy@users.noreply.github.com> Date: Wed, 9 Oct 2024 10:28:57 -0700 Subject: [PATCH 133/152] Adds style guide and naming conventions (#93) Adds a style guide for CN style and naming conventions to distinguish between CN identifiers and C identifiers. There will be changes to the style guide shortly. See the PR for more details: https://github.com/rems-project/cn-tutorial/pull/84 Co-authored-by: Liz Austell --- Makefile | 5 + NAMING-CONVENTIONS.md | 105 ++ .../SAW/broken/error-cerberus/00001.swap.c | 2 +- .../simple-examples/working/malloc_1.c | 6 +- src/examples/3.c | 9 - src/examples/Dbl_Linked_List/add.c | 35 - .../Dbl_Linked_List/add_orig.broken.c | 27 - src/examples/Dbl_Linked_List/c_types.h | 5 - src/examples/Dbl_Linked_List/cn_types.h | 6 - src/examples/Dbl_Linked_List/getters.h | 22 - src/examples/Dbl_Linked_List/malloc_free.h | 12 - src/examples/Dbl_Linked_List/node_and_int.h | 17 - src/examples/Dbl_Linked_List/predicates.h | 36 - src/examples/Dbl_Linked_List/remove.c | 36 - src/examples/Dbl_Linked_List/singleton.c | 13 - src/examples/abs.c | 3 +- src/examples/abs_mem.c | 4 +- src/examples/add_0.c | 4 +- src/examples/add_1.c | 6 +- src/examples/add_2.c | 6 +- src/examples/add_read.c | 12 +- src/examples/add_two_array.c | 10 +- src/examples/append.c | 19 - src/examples/append2.c | 44 - src/examples/array_load.broken.c | 7 +- src/examples/array_load.c | 11 +- src/examples/dll/add.c | 42 + src/examples/dll/add_orig.broken.c | 27 + src/examples/dll/c_types.h | 5 + src/examples/dll/cn_types.h | 8 + src/examples/dll/dllist_and_int.h | 17 + src/examples/dll/getters.h | 22 + .../{Dbl_Linked_List => dll}/headers.h | 7 +- src/examples/dll/malloc_free.h | 12 + src/examples/dll/predicates.h | 40 + src/examples/dll/remove.c | 42 + .../remove_orig.broken.c | 12 +- src/examples/dll/singleton.c | 18 + src/examples/free.h | 4 +- src/examples/init_array.c | 12 +- src/examples/init_array2.c | 12 +- src/examples/init_array_rev.c | 14 +- src/examples/init_point.c | 18 +- src/examples/list.h | 9 - src/examples/list/append.c | 19 + src/examples/list/append.h | 12 + src/examples/list/append2.c | 44 + src/examples/list/c_types.h | 17 + src/examples/list/cn_types.h | 16 + src/examples/list/constructors.h | 19 + src/examples/list/copy.c | 17 + src/examples/list/free.c | 14 + src/examples/list/hdtl.h | 23 + src/examples/list/headers.h | 9 + src/examples/list/length.c | 38 + src/examples/list/mergesort.c | 134 +++ src/examples/list/rev.c | 32 + src/examples/list/rev.h | 14 + src/examples/list/rev_alt.c | 33 + src/examples/list/rev_lemmas.h | 10 + src/examples/list/snoc.h | 12 + src/examples/list_append.h | 14 - src/examples/list_c_types.h | 17 - src/examples/list_cn_types.h | 16 - src/examples/list_constructors.h | 19 - src/examples/list_copy.c | 17 - src/examples/list_free.c | 14 - src/examples/list_hdtl.h | 23 - src/examples/list_length.c | 40 - src/examples/list_rev.c | 32 - src/examples/list_rev.h | 14 - src/examples/list_rev_alt.c | 33 - src/examples/list_rev_lemmas.h | 10 - src/examples/list_snoc.h | 12 - src/examples/liste_rev_lemmas.h | 10 - src/examples/malloc.h | 5 +- src/examples/mergesort.c | 133 --- src/examples/queue/allocation.h | 23 + src/examples/queue/c_types.h | 9 + .../cn_types_1.h} | 6 +- src/examples/queue/cn_types_2.h | 13 + src/examples/queue/cn_types_3.h | 13 + src/examples/queue/empty.c | 14 + src/examples/queue/headers.h | 11 + src/examples/queue/pop.c | 29 + src/examples/queue/pop_lemma.h | 13 + .../pop_orig.broken.c} | 10 +- src/examples/queue/pop_unified.c | 71 ++ src/examples/queue/push.c | 24 + .../push_induction.c} | 30 +- .../push_lemma.h} | 8 +- .../push_orig.broken.c} | 8 +- src/examples/queue_allocation.h | 23 - src/examples/queue_c_types.h | 9 - src/examples/queue_cn_types_2.h | 13 - src/examples/queue_cn_types_3.h | 13 - src/examples/queue_empty.c | 14 - src/examples/queue_headers.h | 11 - src/examples/queue_pop.c | 29 - src/examples/queue_pop_lemma.h | 13 - src/examples/queue_pop_lemma_stages.c | 105 -- src/examples/queue_pop_unified.c | 71 -- src/examples/queue_push.c | 24 - src/examples/read.c | 4 +- src/examples/read2.c | 10 +- src/examples/ref.h | 9 +- src/examples/runway/funcs2.c | 19 +- src/examples/runway/valid_state.h | 11 +- src/examples/slf0_basic_incr.c | 2 - src/examples/slf0_basic_incr.signed.broken.c | 4 +- src/examples/slf0_basic_incr.signed.c | 10 +- src/examples/slf17_get_and_free.c | 5 +- src/examples/slf1_basic_example_let.signed.c | 6 +- src/examples/slf2_basic_quadruple.signed.c | 4 +- src/examples/slf3_basic_inplace_double.c | 10 +- src/examples/slf8_basic_transfer.c | 12 +- src/examples/slf_incr2.c | 40 +- src/examples/slf_incr2_alias.c | 31 +- src/examples/slf_incr2_noalias.c | 12 +- src/examples/slf_length_acc.c | 32 +- src/examples/slf_quadruple_mem.c | 12 +- src/examples/slf_ref_greater.c | 12 +- src/examples/slf_sized_stack.c | 139 +-- src/examples/swap.c | 10 +- src/examples/transpose.broken.c | 8 +- src/examples/transpose.c | 8 +- src/examples/transpose2.c | 20 +- src/examples/zero.c | 6 +- src/queue-example-notes.md | 222 ---- src/tutorial.adoc | 1052 +++++++++++------ src/underconstruction/bst/README.md | 6 + src/underconstruction/bst/c_types.h | 21 + src/underconstruction/bst/cn_getters.h | 48 + src/underconstruction/bst/cn_types.h | 26 + src/underconstruction/bst/constructors.h | 53 + src/underconstruction/bst/contains.c | 65 + src/underconstruction/bst/copy.c | 26 + src/underconstruction/bst/create_node.c | 19 + src/underconstruction/bst/depth.c | 49 + src/underconstruction/bst/free.c | 20 + src/underconstruction/bst/getter.c | 63 + src/underconstruction/bst/headers.h | 10 + src/underconstruction/bst/insert.c | 61 + src/underconstruction/bst/length.c | 49 + src/underconstruction/bst/search.c | 63 + src/underconstruction/bst/sum.c | 51 + 146 files changed, 2698 insertions(+), 1904 deletions(-) create mode 100644 NAMING-CONVENTIONS.md delete mode 100644 src/examples/3.c delete mode 100644 src/examples/Dbl_Linked_List/add.c delete mode 100644 src/examples/Dbl_Linked_List/add_orig.broken.c delete mode 100644 src/examples/Dbl_Linked_List/c_types.h delete mode 100644 src/examples/Dbl_Linked_List/cn_types.h delete mode 100644 src/examples/Dbl_Linked_List/getters.h delete mode 100644 src/examples/Dbl_Linked_List/malloc_free.h delete mode 100644 src/examples/Dbl_Linked_List/node_and_int.h delete mode 100644 src/examples/Dbl_Linked_List/predicates.h delete mode 100644 src/examples/Dbl_Linked_List/remove.c delete mode 100644 src/examples/Dbl_Linked_List/singleton.c delete mode 100644 src/examples/append.c delete mode 100644 src/examples/append2.c create mode 100644 src/examples/dll/add.c create mode 100644 src/examples/dll/add_orig.broken.c create mode 100644 src/examples/dll/c_types.h create mode 100644 src/examples/dll/cn_types.h create mode 100644 src/examples/dll/dllist_and_int.h create mode 100644 src/examples/dll/getters.h rename src/examples/{Dbl_Linked_List => dll}/headers.h (60%) create mode 100644 src/examples/dll/malloc_free.h create mode 100644 src/examples/dll/predicates.h create mode 100644 src/examples/dll/remove.c rename src/examples/{Dbl_Linked_List => dll}/remove_orig.broken.c (63%) create mode 100644 src/examples/dll/singleton.c delete mode 100644 src/examples/list.h create mode 100644 src/examples/list/append.c create mode 100644 src/examples/list/append.h create mode 100644 src/examples/list/append2.c create mode 100644 src/examples/list/c_types.h create mode 100644 src/examples/list/cn_types.h create mode 100644 src/examples/list/constructors.h create mode 100644 src/examples/list/copy.c create mode 100644 src/examples/list/free.c create mode 100644 src/examples/list/hdtl.h create mode 100644 src/examples/list/headers.h create mode 100644 src/examples/list/length.c create mode 100644 src/examples/list/mergesort.c create mode 100644 src/examples/list/rev.c create mode 100644 src/examples/list/rev.h create mode 100644 src/examples/list/rev_alt.c create mode 100644 src/examples/list/rev_lemmas.h create mode 100644 src/examples/list/snoc.h delete mode 100644 src/examples/list_append.h delete mode 100644 src/examples/list_c_types.h delete mode 100644 src/examples/list_cn_types.h delete mode 100644 src/examples/list_constructors.h delete mode 100644 src/examples/list_copy.c delete mode 100644 src/examples/list_free.c delete mode 100644 src/examples/list_hdtl.h delete mode 100644 src/examples/list_length.c delete mode 100644 src/examples/list_rev.c delete mode 100644 src/examples/list_rev.h delete mode 100644 src/examples/list_rev_alt.c delete mode 100644 src/examples/list_rev_lemmas.h delete mode 100644 src/examples/list_snoc.h delete mode 100644 src/examples/liste_rev_lemmas.h delete mode 100644 src/examples/mergesort.c create mode 100644 src/examples/queue/allocation.h create mode 100644 src/examples/queue/c_types.h rename src/examples/{queue_cn_types_1.h => queue/cn_types_1.h} (50%) create mode 100644 src/examples/queue/cn_types_2.h create mode 100644 src/examples/queue/cn_types_3.h create mode 100644 src/examples/queue/empty.c create mode 100644 src/examples/queue/headers.h create mode 100644 src/examples/queue/pop.c create mode 100644 src/examples/queue/pop_lemma.h rename src/examples/{queue_pop_orig.broken.c => queue/pop_orig.broken.c} (52%) create mode 100644 src/examples/queue/pop_unified.c create mode 100644 src/examples/queue/push.c rename src/examples/{queue_push_induction.c => queue/push_induction.c} (50%) rename src/examples/{queue_push_lemma.h => queue/push_lemma.h} (51%) rename src/examples/{queue_push_orig.broken.c => queue/push_orig.broken.c} (50%) delete mode 100644 src/examples/queue_allocation.h delete mode 100644 src/examples/queue_c_types.h delete mode 100644 src/examples/queue_cn_types_2.h delete mode 100644 src/examples/queue_cn_types_3.h delete mode 100644 src/examples/queue_empty.c delete mode 100644 src/examples/queue_headers.h delete mode 100644 src/examples/queue_pop.c delete mode 100644 src/examples/queue_pop_lemma.h delete mode 100644 src/examples/queue_pop_lemma_stages.c delete mode 100644 src/examples/queue_pop_unified.c delete mode 100644 src/examples/queue_push.c delete mode 100644 src/queue-example-notes.md create mode 100644 src/underconstruction/bst/README.md create mode 100644 src/underconstruction/bst/c_types.h create mode 100644 src/underconstruction/bst/cn_getters.h create mode 100644 src/underconstruction/bst/cn_types.h create mode 100644 src/underconstruction/bst/constructors.h create mode 100644 src/underconstruction/bst/contains.c create mode 100644 src/underconstruction/bst/copy.c create mode 100644 src/underconstruction/bst/create_node.c create mode 100644 src/underconstruction/bst/depth.c create mode 100644 src/underconstruction/bst/free.c create mode 100644 src/underconstruction/bst/getter.c create mode 100644 src/underconstruction/bst/headers.h create mode 100644 src/underconstruction/bst/insert.c create mode 100644 src/underconstruction/bst/length.c create mode 100644 src/underconstruction/bst/search.c create mode 100644 src/underconstruction/bst/sum.c diff --git a/Makefile b/Makefile index 1bb28078..07f53e90 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,7 @@ MAKEFILE_DIR:=$(dir $(realpath $(lastword $(MAKEFILE_LIST)))) default: build exercises build/tutorial.html build/exercises.zip +all: default clean: rm -rf build TAGS @@ -43,6 +44,10 @@ build/solutions/%: src/examples/% build/exercises.zip: $(EXERCISES) cd build; zip -r exercises.zip exercises > /dev/null +WORKING=$(wildcard src/examples/list_*.c) +WORKING_AUX=$(patsubst src/examples/%, build/solutions/%, $(WORKING)) +temp: $(WORKING_AUX) build +# build/tutorial.html ############################################################################## # Check that the examples all run correctly diff --git a/NAMING-CONVENTIONS.md b/NAMING-CONVENTIONS.md new file mode 100644 index 00000000..55051066 --- /dev/null +++ b/NAMING-CONVENTIONS.md @@ -0,0 +1,105 @@ +CN Naming Conventions +--------------------- + +This document describes our (Benjamin and Liz's) current best shot at +a good set of conventions for naming identifiers in CN, based on +numerous discussions and worked examples. Everything in the tutorial +(in src/examples) follows these conventions. Future CN coders are +encouraged to follow suit. + +# Principles + +- When similar concepts exist in both C and CN, they should be named + so that the correspondence is immediately obvious. + - In particular, the C and CN versions of a given data structure + should have very similar names. + +- In text, we use the modifiers _CN-level_ vs. _C-level_ to + distinguish the two worlds. + +# General conventions + + ## For new code + +When writing both C and CN code from scratch (e.g., in the tutorial), +aim for maximal correspondence between + +- In general, identifiers are written in `snake_case` (or + `Snake_Case`) rather than `camlCase` (or `CamlCase`). + +- C-level identifiers are `lowercase` wherever possible. + +- CN-level identifiers are `Uppercase_Consistently_Throughout`. + +- A CN identifier that represents the state of a mutable data + structure after some function returns should be named the same as + the starting state of the data structure, with an `_post` at the + end. + - E.g., The list copy function takes a linked list `l` + representing a sequence `L` and leaves `l` at the end pointing + to a final sequence `L_post` such that `L == L_post`. + (Moreover, it returns a new sequence `Ret` with `L == Ret`.) + +- Predicates that extract some structure from the heap should be named + the same as the structure they extract, plus the suffix `_At`. + E.g., the result type of the `Queue` predicate is also called + `Queue_At`. + +## For existing code + +In existing C codebases, uppercase-initial identifiers are often used +for typedefs, structs, and enumerations. We should choose a +recommended standard convention for such cases -- e.g., "prefix +CN-level identifiers with `CN` when needed to avoid confusion with +C-level identifiers". Some experimentation will be needed to see +which convention we like best; this is left for future discussions. + +# Built-ins + +This proposal may ultimately suggest changing some built-ins for +consistency. + + - `i32` should change to `I32`, `u64` to `U64` + - `is_null` to `Is_null` (or `Is_Null`) + +*Discussion*: One point against this change is that CN currently tries +to use names reminiscent of Rust (`i32`, `u64`, etc.). I (BCP) do not +personally find this argument persuasive -- internal consistency seems +more important than miscellaeous points of similarity with some other +language. One way or the other, this will require a global decision. + +# Polymorphism + +One particularly tricky issue is how to name the "monomorphic +instances" of "morally polymorphic" functions (i.e., whether to write +`append__Int` or `append__List_Int` rather than just `append`). On +one hand, `append__Int` is "more correct". On the other hand, these +extra annotations can get pretty heavy. + +We propose a compromise: + +1. If a project needs to use two or more instances of some polymorphic + type, then the names of the C and CN types, the C and CN functions + operating over them, and the CN predicates describing them are all + suffixed with `__xxx`, where `xxx` is the appropriate "type + argument". E.g., if some codebase uses lists of both signed and + unsigned 32-bit ints, then we would use names like this: + - `list__int` / `list__uint` + - `append__int` / `append__uint` + - `List__I32` / `List__U32` + - `Cons__I32` / `Cons__U32` + - etc. + +2. However, if, in a given project, a set of "morally polymorphic" + type definitions and library functions is *only used at one + monomorphic instance* (e.g., if some codebase only ever uses lists + of 32-bit signed ints), then the `__int` or `__I32` annotations are + omitted. + + This convention is used in the CN tutorial, for example. + +*Discussion*: One downside of this convention is that it might +sometimes require some after-the-fact renaming: If a project starts +out using just lists of signed ints and later needs to introduce lists +of unsigned ints, the old signed operations will need to be renamed. +This seems like an acceptable cost for keeping things light. diff --git a/src/example-archive/SAW/broken/error-cerberus/00001.swap.c b/src/example-archive/SAW/broken/error-cerberus/00001.swap.c index 7b7169e1..d9ceae6b 100644 --- a/src/example-archive/SAW/broken/error-cerberus/00001.swap.c +++ b/src/example-archive/SAW/broken/error-cerberus/00001.swap.c @@ -75,7 +75,7 @@ let argmin_spec len = do { // NOTE: Another mistake I made was to use the override below. // This works for size a_len, but on the second time through the loop the override -// doesn't match because the size of the list has changed! Ultimately need a +// doesn't match because the size of the ./headers.has changed! Ultimately need a // loop here. {-/ diff --git a/src/example-archive/simple-examples/working/malloc_1.c b/src/example-archive/simple-examples/working/malloc_1.c index cd837ed7..9259c221 100644 --- a/src/example-archive/simple-examples/working/malloc_1.c +++ b/src/example-archive/simple-examples/working/malloc_1.c @@ -3,19 +3,19 @@ // malloc() is not defined by default in CN. We can define a fake malloc() // function that only works on ints. -int *my_malloc_int() +int *my_malloc__int() /*@ trusted; @*/ /*@ ensures take New = Block(return); @*/ {} -int *malloc_1() +int *malloc__1() /*@ ensures take New = Owned(return); New == 7i32; *return == 7i32; @*/ // <-- Alternative syntax { int *new; - new = my_malloc_int(); + new = my_malloc__int(); *new = 7; // Have to initialize the memory before it's owned return new; } diff --git a/src/examples/3.c b/src/examples/3.c deleted file mode 100644 index 0ce28df8..00000000 --- a/src/examples/3.c +++ /dev/null @@ -1,9 +0,0 @@ -void zero (int *p) -/* --BEGIN-- */ -/*@ requires take v1 = Block(p); - ensures take v2 = Owned(p); -@*/ -/* --END-- */ -{ - *p = 0; -} diff --git a/src/examples/Dbl_Linked_List/add.c b/src/examples/Dbl_Linked_List/add.c deleted file mode 100644 index b988d9a4..00000000 --- a/src/examples/Dbl_Linked_List/add.c +++ /dev/null @@ -1,35 +0,0 @@ -#include "./headers.h" - -// Adds after the given node and returns a pointer to the new node -struct node *add(int element, struct node *n) -/*@ requires take Before = Dll_at(n); - ensures take After = Dll_at(return); - - is_null(n) ? After == Dll { left: Seq_Nil{}, curr: Node(After), right: Seq_Nil{}} - : After == Dll { left: Seq_Cons{head: Node(Before).data, tail: Left(Before)}, curr: Node(After), right: Right(Before)}; -@*/ -{ - struct node *new_node = malloc_node(); - new_node->data = element; - new_node->prev = 0; - new_node->next = 0; - - if (n == 0) //empty list case - { - new_node->prev = 0; - new_node->next = 0; - return new_node; - } else { - /*@ split_case(is_null(n->prev)); @*/ - new_node->next = n->next; - new_node->prev = n; - - if (n->next !=0) { - /*@ split_case(is_null(n->next->next)); @*/ - n->next->prev = new_node; - } - - n->next = new_node; - return new_node; - } -} \ No newline at end of file diff --git a/src/examples/Dbl_Linked_List/add_orig.broken.c b/src/examples/Dbl_Linked_List/add_orig.broken.c deleted file mode 100644 index f8b9b306..00000000 --- a/src/examples/Dbl_Linked_List/add_orig.broken.c +++ /dev/null @@ -1,27 +0,0 @@ -#include "./headers.h" - -// Adds after the given node and returns a pointer to the new node -struct node *add(int element, struct node *n) -{ - struct node *new_node = malloc_node(); - new_node->data = element; - new_node->prev = 0; - new_node->next = 0; - - if (n == 0) //empty list case - { - new_node->prev = 0; - new_node->next = 0; - return new_node; - } else { - new_node->next = n->next; - new_node->prev = n; - - if (n->next !=0) { - n->next->prev = new_node; - } - - n->next = new_node; - return new_node; - } -} \ No newline at end of file diff --git a/src/examples/Dbl_Linked_List/c_types.h b/src/examples/Dbl_Linked_List/c_types.h deleted file mode 100644 index 2e3e7f16..00000000 --- a/src/examples/Dbl_Linked_List/c_types.h +++ /dev/null @@ -1,5 +0,0 @@ -struct node { - int data; - struct node* prev; - struct node* next; -}; \ No newline at end of file diff --git a/src/examples/Dbl_Linked_List/cn_types.h b/src/examples/Dbl_Linked_List/cn_types.h deleted file mode 100644 index 59278fb2..00000000 --- a/src/examples/Dbl_Linked_List/cn_types.h +++ /dev/null @@ -1,6 +0,0 @@ -/*@ -datatype Dll { - Empty_Dll {}, - Dll {datatype seq left, struct node curr, datatype seq right} -} -@*/ \ No newline at end of file diff --git a/src/examples/Dbl_Linked_List/getters.h b/src/examples/Dbl_Linked_List/getters.h deleted file mode 100644 index 9e180623..00000000 --- a/src/examples/Dbl_Linked_List/getters.h +++ /dev/null @@ -1,22 +0,0 @@ -/*@ -function (datatype seq) Right (datatype Dll L) { - match L { - Empty_Dll {} => { Seq_Nil{} } - Dll {left: _, curr: _, right: r} => { r } - } -} - -function (datatype seq) Left (datatype Dll L) { - match L { - Empty_Dll {} => { Seq_Nil {} } - Dll {left: l, curr: _, right: _} => { l } - } -} - -function (struct node) Node (datatype Dll L) { - match L { - Empty_Dll {} => { default } - Dll {left: _, curr: n, right: _} => { n } - } -} -@*/ \ No newline at end of file diff --git a/src/examples/Dbl_Linked_List/malloc_free.h b/src/examples/Dbl_Linked_List/malloc_free.h deleted file mode 100644 index 1937a5a3..00000000 --- a/src/examples/Dbl_Linked_List/malloc_free.h +++ /dev/null @@ -1,12 +0,0 @@ -extern struct node *malloc_node(); -/*@ spec malloc_node(); - requires true; - ensures take u = Block(return); - !ptr_eq(return,NULL); -@*/ - -extern void free_node (struct node *p); -/*@ spec free_node(pointer p); - requires take u = Block(p); - ensures true; -@*/ \ No newline at end of file diff --git a/src/examples/Dbl_Linked_List/node_and_int.h b/src/examples/Dbl_Linked_List/node_and_int.h deleted file mode 100644 index faef87c7..00000000 --- a/src/examples/Dbl_Linked_List/node_and_int.h +++ /dev/null @@ -1,17 +0,0 @@ -struct node_and_int { - struct node* node; - int data; -}; - -extern struct node_and_int *malloc_node_and_int(); -/*@ spec malloc_node_and_int(); - requires true; - ensures take u = Block(return); - !ptr_eq(return,NULL); -@*/ - -extern void free_node_and_int (struct node_and_int *p); -/*@ spec free_node_and_int(pointer p); - requires take u = Block(p); - ensures true; -@*/ \ No newline at end of file diff --git a/src/examples/Dbl_Linked_List/predicates.h b/src/examples/Dbl_Linked_List/predicates.h deleted file mode 100644 index a5353591..00000000 --- a/src/examples/Dbl_Linked_List/predicates.h +++ /dev/null @@ -1,36 +0,0 @@ -/*@ -predicate (datatype Dll) Dll_at (pointer p) { - if (is_null(p)) { - return Empty_Dll{}; - } else { - take n = Owned(p); - take Left = Own_Backwards(n.prev, p, n); - take Right = Own_Forwards(n.next, p, n); - return Dll{left: Left, curr: n, right: Right}; - } -} - -predicate (datatype seq) Own_Forwards(pointer p, pointer prev_pointer, struct node prev_node) { - if (is_null(p)) { - return Seq_Nil{}; - } else { - take n = Owned(p); - assert (ptr_eq(n.prev, prev_pointer)); - assert(ptr_eq(prev_node.next, p)); - take Right = Own_Forwards(n.next, p, n); - return Seq_Cons{head: n.data, tail: Right}; - } -} - -predicate (datatype seq) Own_Backwards(pointer p, pointer next_pointer, struct node next_node) { - if (is_null(p)) { - return Seq_Nil{}; - } else { - take n = Owned(p); - assert (ptr_eq(n.next, next_pointer)); - assert(ptr_eq(next_node.prev, p)); - take Left = Own_Backwards(n.prev, p, n); - return Seq_Cons{head: n.data, tail: Left}; - } -} -@*/ \ No newline at end of file diff --git a/src/examples/Dbl_Linked_List/remove.c b/src/examples/Dbl_Linked_List/remove.c deleted file mode 100644 index 2a610db8..00000000 --- a/src/examples/Dbl_Linked_List/remove.c +++ /dev/null @@ -1,36 +0,0 @@ -#include "./headers.h" -#include "./node_and_int.h" - -// removes the given node from the list and returns another pointer -// to somewhere in the list, or a null pointer if the list is empty. -struct node_and_int *remove(struct node *n) -/*@ requires !is_null(n); - take Before = Dll_at(n); - let del = Node(Before); - ensures take ret = Owned(return); - take After = Dll_at(ret.node); - ret.data == del.data; - (is_null(del.prev) && is_null(del.next)) ? After == Empty_Dll{} - : (!is_null(del.next) ? After == Dll{left: Left(Before), curr: Node(After), right: tl(Right(Before))} - : After == Dll{left: tl(Left(Before)), curr: Node(After), right: Right(Before)}); -@*/ -{ - struct node *temp = 0; - if (n->prev != 0) { - /*@ split_case(is_null(n->prev->prev)); @*/ - n->prev->next = n->next; - temp = n->prev; - } - if (n->next != 0) { - /*@ split_case(is_null(n->next->next)); @*/ - n->next->prev = n->prev; - temp = n->next; - } - - struct node_and_int *pair = malloc_node_and_int(); - pair->node = temp; - pair->data = n->data; - - free_node(n); - return pair; -} diff --git a/src/examples/Dbl_Linked_List/singleton.c b/src/examples/Dbl_Linked_List/singleton.c deleted file mode 100644 index 15c8fd8a..00000000 --- a/src/examples/Dbl_Linked_List/singleton.c +++ /dev/null @@ -1,13 +0,0 @@ -#include "./headers.h" - -struct node *singleton(int element) -/*@ ensures take Ret = Dll_at(return); - Ret == Dll{left: Seq_Nil{}, curr: struct node{data: element, prev: NULL, next: NULL}, right: Seq_Nil{}}; -@*/ -{ - struct node *n = malloc_node(); - n->data = element; - n->prev = 0; - n->next = 0; - return n; -} \ No newline at end of file diff --git a/src/examples/abs.c b/src/examples/abs.c index 206ee580..79da4d60 100644 --- a/src/examples/abs.c +++ b/src/examples/abs.c @@ -7,8 +7,7 @@ int abs (int x) { if (x >= 0) { return x; - } - else { + } else { return -x; } } diff --git a/src/examples/abs_mem.c b/src/examples/abs_mem.c index 488c2992..978fefa0 100644 --- a/src/examples/abs_mem.c +++ b/src/examples/abs_mem.c @@ -2,8 +2,8 @@ int abs_mem (int *p) /* --BEGIN-- */ /*@ requires take x = Owned(p); MINi32() < x; - ensures take x2 = Owned(p); - x == x2; + ensures take x_post = Owned(p); + x == x_post; return == ((x >= 0i32) ? x : (0i32-x)); @*/ /* --END-- */ diff --git a/src/examples/add_0.c b/src/examples/add_0.c index 56a35cfd..f0f52745 100644 --- a/src/examples/add_0.c +++ b/src/examples/add_0.c @@ -1,7 +1,7 @@ int add(int x, int y) /* --BEGIN-- */ -/*@ requires let sum = (i64) x + (i64) y; - -2147483648i64 <= sum; sum <= 2147483647i64; @*/ +/*@ requires let Sum = (i64) x + (i64) y; + -2147483648i64 <= Sum; Sum <= 2147483647i64; @*/ /* --END-- */ { return x+y; diff --git a/src/examples/add_1.c b/src/examples/add_1.c index ebd141df..ac30ccc5 100644 --- a/src/examples/add_1.c +++ b/src/examples/add_1.c @@ -1,8 +1,8 @@ int add(int x, int y) /* --BEGIN-- */ -/*@ requires let sum = (i64) x + (i64) y; - -2147483648i64 <= sum; sum <= 2147483647i64; - ensures return == (i32) sum; +/*@ requires let Sum = (i64) x + (i64) y; + -2147483648i64 <= Sum; Sum <= 2147483647i64; + ensures return == (i32) Sum; @*/ /* --END-- */ { diff --git a/src/examples/add_2.c b/src/examples/add_2.c index 17339707..0afd69e0 100644 --- a/src/examples/add_2.c +++ b/src/examples/add_2.c @@ -1,8 +1,8 @@ int add(int x, int y) /* --BEGIN-- */ -/*@ requires let sum = (i64) x + (i64) y; - (i64)MINi32() <= sum; sum <= (i64)MAXi32(); - ensures return == (i32) sum; +/*@ requires let Sum = (i64) x + (i64) y; + (i64)MINi32() <= Sum; Sum <= (i64)MAXi32(); + ensures return == (i32) Sum; @*/ /* --END-- */ { diff --git a/src/examples/add_read.c b/src/examples/add_read.c index c15c6484..2341bd04 100644 --- a/src/examples/add_read.c +++ b/src/examples/add_read.c @@ -1,10 +1,10 @@ unsigned int add (unsigned int *p, unsigned int *q) -/*@ requires take m = Owned(p); - take n = Owned(q); - ensures take m2 = Owned(p); - take n2 = Owned(q); - m == m2 && n == n2; - return == m + n; +/*@ requires take P = Owned(p); + take Q = Owned(q); + ensures take P_post = Owned(p); + take Q_post = Owned(q); + P == P_post && Q == Q_post; + return == P + Q; @*/ { unsigned int m = *p; diff --git a/src/examples/add_two_array.c b/src/examples/add_two_array.c index 799edaf7..70a8e5fc 100644 --- a/src/examples/add_two_array.c +++ b/src/examples/add_two_array.c @@ -1,12 +1,14 @@ unsigned int array_read_two (unsigned int *p, int n, int i, int j) /* --BEGIN-- */ -/*@ requires take a1 = each(i32 k; 0i32 <= k && k < n) { Owned(array_shift(p,k)) }; +/*@ requires take A = each(i32 k; 0i32 <= k && k < n) { + Owned(array_shift(p,k)) }; 0i32 <= i && i < n; 0i32 <= j && j < n; j != i; - ensures take a2 = each(i32 k; 0i32 <= k && k < n) { Owned(array_shift(p,k)) }; - a1 == a2; - return == a1[i] + a1[j]; + ensures take A_post = each(i32 k; 0i32 <= k && k < n) { + Owned(array_shift(p,k)) }; + A == A_post; + return == A[i] + A[j]; @*/ /* --END-- */ { diff --git a/src/examples/append.c b/src/examples/append.c deleted file mode 100644 index ff3309a0..00000000 --- a/src/examples/append.c +++ /dev/null @@ -1,19 +0,0 @@ -#include "list.h" -#include "list_append.h" - -struct int_list* IntList_append(struct int_list* xs, struct int_list* ys) -/*@ requires take L1 = IntList(xs); @*/ -/*@ requires take L2 = IntList(ys); @*/ -/*@ ensures take L3 = IntList(return); @*/ -/*@ ensures L3 == append(L1, L2); @*/ -{ - if (xs == 0) { - /*@ unfold append(L1, L2); @*/ - return ys; - } else { - /*@ unfold append(L1, L2); @*/ - struct int_list *new_tail = IntList_append(xs->tail, ys); - xs->tail = new_tail; - return xs; - } -} diff --git a/src/examples/append2.c b/src/examples/append2.c deleted file mode 100644 index a966e428..00000000 --- a/src/examples/append2.c +++ /dev/null @@ -1,44 +0,0 @@ -#include "list.h" -#include "list_append.h" - -struct int_list* IntList_copy (struct int_list *xs) -/*@ requires take L1 = IntList(xs); - ensures take L1_ = IntList(xs); - take L2 = IntList(return); - L1 == L1_; - L1 == L2; -@*/ -{ - if (xs == 0) { - return IntList_nil(); - } else { - struct int_list *new_tail = IntList_copy(xs->tail); - return IntList_cons(xs->head, new_tail); - } -} - -struct int_list* IntList_append2 (struct int_list *xs, struct int_list *ys) -/* --BEGIN-- */ -/*@ requires take L1 = IntList(xs); @*/ -/*@ requires take L2 = IntList(ys); @*/ -/*@ ensures take L1_ = IntList(xs); @*/ -/*@ ensures take L2_ = IntList(ys); @*/ -/*@ ensures take L3 = IntList(return); @*/ -/*@ ensures L3 == append(L1, L2); @*/ -/*@ ensures L1 == L1_; @*/ -/*@ ensures L2 == L2_; @*/ -/* --END-- */ -{ - if (xs == 0) { -/* --BEGIN-- */ - /*@ unfold append(L1, L2); @*/ -/* --END-- */ - return IntList_copy(ys); - } else { -/* --BEGIN-- */ - /*@ unfold append(L1, L2); @*/ -/* --END-- */ - struct int_list *new_tail = IntList_append2(xs->tail, ys); - return IntList_cons(xs->head, new_tail); - } -} diff --git a/src/examples/array_load.broken.c b/src/examples/array_load.broken.c index 3f4b6e63..446072cf 100644 --- a/src/examples/array_load.broken.c +++ b/src/examples/array_load.broken.c @@ -1,7 +1,10 @@ int read (int *p, int n, int i) -/*@ requires take a1 = each(i32 j; 0i32 <= j && j < n) { Owned(array_shift(p,j)) }; +/*@ requires take A = each(i32 j; 0i32 <= j && j < n) { + Owned(array_shift(p,j)) }; 0i32 <= i && i < n; - ensures take a2 = each(i32 j; 0i32 <= j && j < n) { Owned(array_shift(p,j)) }; @*/ + ensures take A_post = each(i32 j; 0i32 <= j && j < n) { + Owned(array_shift(p,j)) }; +@*/ { return p[i]; } diff --git a/src/examples/array_load.c b/src/examples/array_load.c index eef0dab2..6cd87216 100644 --- a/src/examples/array_load.c +++ b/src/examples/array_load.c @@ -1,9 +1,10 @@ int read (int *p, int n, int i) -/*@ requires take a1 = each(i32 j; 0i32 <= j && j < n) { - Owned(array_shift(p,j)) }; - 0i32 <= i && i < n; - ensures take a2 = each(i32 j; 0i32 <= j && j < n) { - Owned(array_shift(p,j)) }; @*/ +/*@ requires take A = each(i32 j; 0i32 <= j && j < n) { + Owned(array_shift(p,j)) }; + 0i32 <= i && i < n; + ensures take A_post = each(i32 j; 0i32 <= j && j < n) { + Owned(array_shift(p,j)) }; +@*/ { /*@ extract Owned, i; @*/ return p[i]; diff --git a/src/examples/dll/add.c b/src/examples/dll/add.c new file mode 100644 index 00000000..2962a820 --- /dev/null +++ b/src/examples/dll/add.c @@ -0,0 +1,42 @@ +#include "./headers.h" + +// Adds after the given node and returns a pointer to the new node +struct dllist *add(int element, struct dllist *n) +/*@ requires take Before = Dll_at(n); + ensures take After = Dll_at(return); + is_null(n) ? + After == Nonempty_Dll { + left: Nil{}, + curr: Node(After), + right: Nil{}} + : After == Nonempty_Dll { + left: Cons {Head: Node(Before).data, + Tail: Left_Sublist(Before)}, + curr: Node(After), + right: Right_Sublist(Before)}; +@*/ +{ + struct dllist *new_dllist = malloc__dllist(); + new_dllist->data = element; + new_dllist->prev = 0; + new_dllist->next = 0; + + if (n == 0) //empty list case + { + new_dllist->prev = 0; + new_dllist->next = 0; + return new_dllist; + } else { + /*@ split_case(is_null(n->prev)); @*/ + new_dllist->next = n->next; + new_dllist->prev = n; + + if (n->next != 0) { + /*@ split_case(is_null(n->next->next)); @*/ + n->next->prev = new_dllist; + } + + n->next = new_dllist; + return new_dllist; + } +} diff --git a/src/examples/dll/add_orig.broken.c b/src/examples/dll/add_orig.broken.c new file mode 100644 index 00000000..8d23ea48 --- /dev/null +++ b/src/examples/dll/add_orig.broken.c @@ -0,0 +1,27 @@ +#include "./headers.h" + +// Adds after the given node and returns a pointer to the new node +struct dllist *add(int element, struct dllist *n) +{ + struct dllist *new_dllist = malloc__dllist(); + new_dllist->data = element; + new_dllist->prev = 0; + new_dllist->next = 0; + + if (n == 0) //empty list case + { + new_dllist->prev = 0; + new_dllist->next = 0; + return new_dllist; + } else { + new_dllist->next = n->next; + new_dllist->prev = n; + + if (n->next != 0) { + n->next->prev = new_dllist; + } + + n->next = new_dllist; + return new_dllist; + } +} diff --git a/src/examples/dll/c_types.h b/src/examples/dll/c_types.h new file mode 100644 index 00000000..9a7dc233 --- /dev/null +++ b/src/examples/dll/c_types.h @@ -0,0 +1,5 @@ +struct dllist { + int data; + struct dllist* prev; + struct dllist* next; +}; \ No newline at end of file diff --git a/src/examples/dll/cn_types.h b/src/examples/dll/cn_types.h new file mode 100644 index 00000000..479d3626 --- /dev/null +++ b/src/examples/dll/cn_types.h @@ -0,0 +1,8 @@ +/*@ +datatype Dll { + Empty_Dll {}, + Nonempty_Dll {datatype List left, + struct dllist curr, + datatype List right} +} +@*/ diff --git a/src/examples/dll/dllist_and_int.h b/src/examples/dll/dllist_and_int.h new file mode 100644 index 00000000..229350e4 --- /dev/null +++ b/src/examples/dll/dllist_and_int.h @@ -0,0 +1,17 @@ +struct dllist_and_int { + struct dllist* dllist; + int data; +}; + +extern struct dllist_and_int *malloc__dllist_and_int(); +/*@ spec malloc__dllist_and_int(); + requires true; + ensures take R = Block(return); + !ptr_eq(return,NULL); +@*/ + +extern void free__dllist_and_int (struct dllist_and_int *p); +/*@ spec free__dllist_and_int(pointer p); + requires take R = Block(p); + ensures true; +@*/ diff --git a/src/examples/dll/getters.h b/src/examples/dll/getters.h new file mode 100644 index 00000000..4f75bf23 --- /dev/null +++ b/src/examples/dll/getters.h @@ -0,0 +1,22 @@ +/*@ +function (datatype List) Right_Sublist (datatype Dll L) { + match L { + Empty_Dll {} => { Nil{} } + Nonempty_Dll {left: _, curr: _, right: r} => { r } + } +} + +function (datatype List) Left_Sublist (datatype Dll L) { + match L { + Empty_Dll {} => { Nil {} } + Nonempty_Dll {left: l, curr: _, right: _} => { l } + } +} + +function (struct dllist) Node (datatype Dll L) { + match L { + Empty_Dll {} => { default } + Nonempty_Dll {left: _, curr: n, right: _} => { n } + } +} +@*/ diff --git a/src/examples/Dbl_Linked_List/headers.h b/src/examples/dll/headers.h similarity index 60% rename from src/examples/Dbl_Linked_List/headers.h rename to src/examples/dll/headers.h index 963902fb..2a029dde 100644 --- a/src/examples/Dbl_Linked_List/headers.h +++ b/src/examples/dll/headers.h @@ -1,7 +1,6 @@ -#include "../list.h" -#include "../list_append.h" -#include "../list_rev.h" - +#include "../list/headers.h" +#include "../list/append.h" +#include "../list/rev.h" #include "./c_types.h" #include "./cn_types.h" #include "./getters.h" diff --git a/src/examples/dll/malloc_free.h b/src/examples/dll/malloc_free.h new file mode 100644 index 00000000..a18b81c6 --- /dev/null +++ b/src/examples/dll/malloc_free.h @@ -0,0 +1,12 @@ +extern struct dllist *malloc__dllist(); +/*@ spec malloc__dllist(); + requires true; + ensures take R = Block(return); + !ptr_eq(return,NULL); +@*/ + +extern void free__dllist (struct dllist *p); +/*@ spec free__dllist(pointer p); + requires take R = Block(p); + ensures true; +@*/ diff --git a/src/examples/dll/predicates.h b/src/examples/dll/predicates.h new file mode 100644 index 00000000..52a00a66 --- /dev/null +++ b/src/examples/dll/predicates.h @@ -0,0 +1,40 @@ +/*@ +predicate (datatype Dll) Dll_at (pointer p) { + if (is_null(p)) { + return Empty_Dll{}; + } else { + take n = Owned(p); + take L = Own_Backwards(n.prev, p, n); + take R = Own_Forwards(n.next, p, n); + return Nonempty_Dll{left: L, curr: n, right: R}; + } +} + +predicate (datatype List) Own_Forwards (pointer p, + pointer prev_pointer, + struct dllist prev_dllist) { + if (is_null(p)) { + return Nil{}; + } else { + take P = Owned(p); + assert (ptr_eq(P.prev, prev_pointer)); + assert(ptr_eq(prev_dllist.next, p)); + take T = Own_Forwards(P.next, p, P); + return Cons{Head: P.data, Tail: T}; + } +} + +predicate (datatype List) Own_Backwards (pointer p, + pointer next_pointer, + struct dllist next_dllist) { + if (is_null(p)) { + return Nil{}; + } else { + take P = Owned(p); + assert (ptr_eq(P.next, next_pointer)); + assert(ptr_eq(next_dllist.prev, p)); + take T = Own_Backwards(P.prev, p, P); + return Cons{Head: P.data, Tail: T}; + } +} +@*/ diff --git a/src/examples/dll/remove.c b/src/examples/dll/remove.c new file mode 100644 index 00000000..7c87360c --- /dev/null +++ b/src/examples/dll/remove.c @@ -0,0 +1,42 @@ +#include "./headers.h" +#include "./dllist_and_int.h" + +// Remove the given node from the list and returns another pointer +// to somewhere in the list, or a null pointer if the list is empty +struct dllist_and_int *remove(struct dllist *n) +/*@ requires !is_null(n); + take Before = Dll_at(n); + let Del = Node(Before); + ensures take Ret = Owned(return); + take After = Dll_at(Ret.dllist); + Ret.data == Del.data; + (is_null(Del.prev) && is_null(Del.next)) + ? After == Empty_Dll{} + : (!is_null(Del.next) ? + After == Nonempty_Dll {left: Left_Sublist(Before), + curr: Node(After), + right: Tl(Right_Sublist(Before))} + : After == Nonempty_Dll {left: Tl(Left_Sublist(Before)), + curr: Node(After), + right: Right_Sublist(Before)}); +@*/ +{ + struct dllist *temp = 0; + if (n->prev != 0) { + /*@ split_case(is_null(n->prev->prev)); @*/ + n->prev->next = n->next; + temp = n->prev; + } + if (n->next != 0) { + /*@ split_case(is_null(n->next->next)); @*/ + n->next->prev = n->prev; + temp = n->next; + } + + struct dllist_and_int *pair = malloc__dllist_and_int(); + pair->dllist = temp; + pair->data = n->data; + + free__dllist(n); + return pair; +} diff --git a/src/examples/Dbl_Linked_List/remove_orig.broken.c b/src/examples/dll/remove_orig.broken.c similarity index 63% rename from src/examples/Dbl_Linked_List/remove_orig.broken.c rename to src/examples/dll/remove_orig.broken.c index b2617c69..ba1ff586 100644 --- a/src/examples/Dbl_Linked_List/remove_orig.broken.c +++ b/src/examples/dll/remove_orig.broken.c @@ -1,11 +1,11 @@ #include "./headers.h" -#include "./node_and_int.h" +#include "./dllist_and_int.h" // removes the given node from the list and returns another pointer // to somewhere in the list, or a null pointer if the list is empty. -struct node_and_int *remove(struct node *n) +struct dllist_and_int *remove(struct dllist *n) { - struct node *temp = 0; + struct dllist *temp = 0; if (n->prev != 0) { n->prev->next = n->next; temp = n->prev; @@ -15,10 +15,10 @@ struct node_and_int *remove(struct node *n) temp = n->next; } - struct node_and_int *pair = malloc_node_and_int(); - pair->node = temp; + struct dllist_and_int *pair = malloc__dllist_and_int(); + pair->dllist = temp; pair->data = n->data; - free_node(n); + free__dllist(n); return pair; } \ No newline at end of file diff --git a/src/examples/dll/singleton.c b/src/examples/dll/singleton.c new file mode 100644 index 00000000..31679c9f --- /dev/null +++ b/src/examples/dll/singleton.c @@ -0,0 +1,18 @@ +#include "./headers.h" + +struct dllist *singleton(int element) +/*@ ensures take Ret = Dll_at(return); + Ret == Nonempty_Dll { + left: Nil{}, + curr: struct dllist {prev: NULL, + data: element, + next: NULL}, + right: Nil{}}; +@*/ +{ + struct dllist *n = malloc__dllist(); + n->data = element; + n->prev = 0; + n->next = 0; + return n; +} diff --git a/src/examples/free.h b/src/examples/free.h index 9643f4f3..b5eb5832 100644 --- a/src/examples/free.h +++ b/src/examples/free.h @@ -1,13 +1,13 @@ extern void freeInt (int *p); /*@ spec freeInt(pointer p); - requires take v = Block(p); + requires take P = Block(p); ensures true; @*/ extern void freeUnsignedInt (unsigned int *p); /*@ spec freeUnsignedInt(pointer p); - requires take v = Block(p); + requires take P = Block(p); ensures true; @*/ diff --git a/src/examples/init_array.c b/src/examples/init_array.c index 5fb3c97a..b288cb1c 100644 --- a/src/examples/init_array.c +++ b/src/examples/init_array.c @@ -1,13 +1,17 @@ void init_array (char *p, unsigned int n) -/*@ requires take a1 = each(u32 i; i < n) { Owned( array_shift(p, i)) }; - ensures take a2 = each(u32 i; i < n) { Owned( array_shift(p, i)) }; +/*@ requires take A = each(u32 i; i < n) { + Owned( array_shift(p, i)) }; + ensures take A_post = each(u32 i; i < n) { + Owned( array_shift(p, i)) }; @*/ { unsigned int j = 0; while (j < n) /* --BEGIN-- */ - /*@ inv take ai = each(u32 i; i < n) { Owned( array_shift(p, i)) }; - {p} unchanged; {n} unchanged; + /*@ inv take Ai = each(u32 i; i < n) { + Owned( array_shift(p, i)) }; + {p} unchanged; + {n} unchanged; @*/ /* --END-- */ { diff --git a/src/examples/init_array2.c b/src/examples/init_array2.c index e1217845..f447f553 100644 --- a/src/examples/init_array2.c +++ b/src/examples/init_array2.c @@ -1,13 +1,17 @@ void init_array2 (char *p, unsigned int n) -/*@ requires take a1 = each(u32 i; i < n) { Block( array_shift(p, i)) }; - ensures take a2 = each(u32 i; i < n) { Owned( array_shift(p, i)) }; +/*@ requires take A = each(u32 i; i < n) { + Block( array_shift(p, i)) }; + ensures take A_post = each(u32 i; i < n) { + Owned( array_shift(p, i)) }; @*/ { unsigned int j = 0; while (j < n) /* --BEGIN-- */ - /*@ inv take al = each(u32 i; i < j) { Owned( array_shift(p, i)) }; - take ar = each(u32 i; j <= i && i < n) { Block( array_shift(p, i)) }; + /*@ inv take Al = each(u32 i; i < j) { + Owned( array_shift(p, i)) }; + take Ar = each(u32 i; j <= i && i < n) { + Block( array_shift(p, i)) }; {p} unchanged; {n} unchanged; j <= n; @*/ diff --git a/src/examples/init_array_rev.c b/src/examples/init_array_rev.c index dd8de2b7..e46b4f3f 100644 --- a/src/examples/init_array_rev.c +++ b/src/examples/init_array_rev.c @@ -1,14 +1,18 @@ -void init_array2 (char *p, unsigned int n) -/*@ requires take a1 = each(u32 i; i < n) { Block( array_shift(p, i)) }; +void init_array_rev (char *p, unsigned int n) +/*@ requires take A = each(u32 i; i < n) { + Block( array_shift(p, i)) }; n > 0u32; - ensures take a2 = each(u32 i; i < n) { Owned( array_shift(p, i)) }; + ensures take A_post = each(u32 i; i < n) { + Owned( array_shift(p, i)) }; @*/ { unsigned int j = 0; while (j < n) /* --BEGIN-- */ - /*@ inv take al = each(u32 i; i < n-j) { Block( array_shift(p, i)) }; - take ar = each(u32 i; n-j <= i && i < n) { Owned( array_shift(p, i)) }; + /*@ inv take Al = each(u32 i; i < n-j) { + Block( array_shift(p, i)) }; + take Ar = each(u32 i; n-j <= i && i < n) { + Owned( array_shift(p, i)) }; {p} unchanged; {n} unchanged; 0u32 <= j && j <= n; @*/ diff --git a/src/examples/init_point.c b/src/examples/init_point.c index b907a950..4ecb67f3 100644 --- a/src/examples/init_point.c +++ b/src/examples/init_point.c @@ -1,18 +1,18 @@ -void zero (int *p) -/*@ requires take u = Block(p); - ensures take v = Owned(p); - v == 0i32; @*/ +void zero (int *coord) +/*@ requires take Coord = Block(coord); + ensures take Coord_post = Owned(coord); + Coord_post == 0i32; @*/ { - *p = 0; + *coord = 0; } struct point { int x; int y; }; void init_point(struct point *p) -/*@ requires take s = Block(p); - ensures take s2 = Owned(p); - s2.x == 0i32; - s2.y == 0i32; +/*@ requires take P = Block(p); + ensures take P_post = Owned(p); + P_post.x == 0i32; + P_post.y == 0i32; @*/ { zero(&p->x); diff --git a/src/examples/list.h b/src/examples/list.h deleted file mode 100644 index d93830dc..00000000 --- a/src/examples/list.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef _LIST_H -#define _LIST_H - -#include "list_c_types.h" -#include "list_cn_types.h" -#include "list_hdtl.h" -#include "list_constructors.h" - -#endif //_LIST_H diff --git a/src/examples/list/append.c b/src/examples/list/append.c new file mode 100644 index 00000000..18f516c7 --- /dev/null +++ b/src/examples/list/append.c @@ -0,0 +1,19 @@ +#include "./headers.h" +#include "./append.h" + +struct sllist* IntList_append(struct sllist* xs, struct sllist* ys) +/*@ requires take L1 = SLList_At(xs); + take L2 = SLList_At(ys); @*/ +/*@ ensures take L3 = SLList_At(return); + L3 == Append(L1, L2); @*/ +{ + if (xs == 0) { + /*@ unfold Append(L1, L2); @*/ + return ys; + } else { + /*@ unfold Append(L1, L2); @*/ + struct sllist *new_tail = IntList_append(xs->tail, ys); + xs->tail = new_tail; + return xs; + } +} diff --git a/src/examples/list/append.h b/src/examples/list/append.h new file mode 100644 index 00000000..2c95a823 --- /dev/null +++ b/src/examples/list/append.h @@ -0,0 +1,12 @@ +/*@ +function [rec] (datatype List) Append(datatype List L1, datatype List L2) { + match L1 { + Nil {} => { + L2 + } + Cons {Head : H, Tail : T} => { + Cons {Head: H, Tail: Append(T, L2)} + } + } +} +@*/ diff --git a/src/examples/list/append2.c b/src/examples/list/append2.c new file mode 100644 index 00000000..5b6979dd --- /dev/null +++ b/src/examples/list/append2.c @@ -0,0 +1,44 @@ +#include "./headers.h" +#include "./append.h" + +struct sllist* IntList_copy (struct sllist *xs) +/*@ requires take Xs = SLList_At(xs); + ensures take Xs_post = SLList_At(xs); + take R = SLList_At(return); + Xs == Xs_post; + Xs == R; +@*/ +{ + if (xs == 0) { + return slnil(); + } else { + struct sllist *new_tail = IntList_copy(xs->tail); + return slcons(xs->head, new_tail); + } +} + +struct sllist* IntList_append2 (struct sllist *xs, struct sllist *ys) +/* --BEGIN-- */ +/*@ requires take Xs = SLList_At(xs); @*/ +/*@ requires take Ys = SLList_At(ys); @*/ +/*@ ensures take Xs_post = SLList_At(xs); @*/ +/*@ ensures take Ys_post = SLList_At(ys); @*/ +/*@ ensures take Ret = SLList_At(return); @*/ +/*@ ensures Ret == Append(Xs, Ys); @*/ +/*@ ensures Xs == Xs_post; @*/ +/*@ ensures Ys == Ys_post; @*/ +/* --END-- */ +{ + if (xs == 0) { +/* --BEGIN-- */ + /*@ unfold Append(Xs, Ys); @*/ +/* --END-- */ + return IntList_copy(ys); + } else { +/* --BEGIN-- */ + /*@ unfold Append(Xs, Ys); @*/ +/* --END-- */ + struct sllist *new_tail = IntList_append2(xs->tail, ys); + return slcons(xs->head, new_tail); + } +} diff --git a/src/examples/list/c_types.h b/src/examples/list/c_types.h new file mode 100644 index 00000000..7caa03b3 --- /dev/null +++ b/src/examples/list/c_types.h @@ -0,0 +1,17 @@ +struct sllist { + int head; + struct sllist* tail; +}; + +extern struct sllist *malloc__sllist(); +/*@ spec malloc__sllist(); + requires true; + ensures take R = Block(return); +@*/ + +extern void free__sllist (struct sllist *p); +/*@ spec free__sllist(pointer p); + requires take P = Block(p); + ensures true; +@*/ + diff --git a/src/examples/list/cn_types.h b/src/examples/list/cn_types.h new file mode 100644 index 00000000..102923c5 --- /dev/null +++ b/src/examples/list/cn_types.h @@ -0,0 +1,16 @@ +/*@ +datatype List { + Nil {}, + Cons {i32 Head, datatype List Tail} +} + +predicate (datatype List) SLList_At(pointer p) { + if (is_null(p)) { + return Nil{}; + } else { + take H = Owned(p); + take T = SLList_At(H.tail); + return (Cons { Head: H.head, Tail: T }); + } +} +@*/ diff --git a/src/examples/list/constructors.h b/src/examples/list/constructors.h new file mode 100644 index 00000000..fdc2e343 --- /dev/null +++ b/src/examples/list/constructors.h @@ -0,0 +1,19 @@ +struct sllist* slnil() +/*@ ensures take Ret = SLList_At(return); + Ret == Nil{}; + @*/ +{ + return 0; +} + +struct sllist* slcons(int h, struct sllist* t) +/*@ requires take T = SLList_At(t); + ensures take Ret = SLList_At(return); + Ret == Cons{ Head: h, Tail: T}; + @*/ +{ + struct sllist *p = malloc__sllist(); + p->head = h; + p->tail = t; + return p; +} diff --git a/src/examples/list/copy.c b/src/examples/list/copy.c new file mode 100644 index 00000000..f76e9a98 --- /dev/null +++ b/src/examples/list/copy.c @@ -0,0 +1,17 @@ +#include "./headers.h" + +struct sllist* slcopy (struct sllist *l) +/*@ requires take L = SLList_At(l); + ensures take L_ = SLList_At(l); + take Ret = SLList_At(return); + L == L_; + L == Ret; +@*/ +{ + if (l == 0) { + return slnil(); + } else { + struct sllist *new_tail = slcopy(l->tail); + return slcons(l->head, new_tail); + } +} diff --git a/src/examples/list/free.c b/src/examples/list/free.c new file mode 100644 index 00000000..92e1037a --- /dev/null +++ b/src/examples/list/free.c @@ -0,0 +1,14 @@ +#include "./headers.h" + +void free__rec_sllist(struct sllist* l) +// You fill in the rest... +/* --BEGIN-- */ +/*@ requires take L = SLList_At(l); @*/ +{ + if (l == 0) { + } else { + free__rec_sllist(l->tail); + free__sllist(l); + } +} +/* --END-- */ diff --git a/src/examples/list/hdtl.h b/src/examples/list/hdtl.h new file mode 100644 index 00000000..e36738ff --- /dev/null +++ b/src/examples/list/hdtl.h @@ -0,0 +1,23 @@ +/*@ +function (i32) Hd (datatype List L) { + match L { + Nil {} => { + 0i32 + } + Cons {Head : H, Tail : _} => { + H + } + } +} + +function (datatype List) Tl (datatype List L) { + match L { + Nil {} => { + Nil{} + } + Cons {Head : _, Tail : T} => { + T + } + } +} +@*/ diff --git a/src/examples/list/headers.h b/src/examples/list/headers.h new file mode 100644 index 00000000..5f787e85 --- /dev/null +++ b/src/examples/list/headers.h @@ -0,0 +1,9 @@ +#ifndef _LIST_H +#define _LIST_H + +#include "./c_types.h" +#include "./cn_types.h" +#include "./hdtl.h" +#include "./constructors.h" + +#endif //_LIST_H diff --git a/src/examples/list/length.c b/src/examples/list/length.c new file mode 100644 index 00000000..0dc195cf --- /dev/null +++ b/src/examples/list/length.c @@ -0,0 +1,38 @@ +#include "./headers.h" + +/* --BEGIN-- */ +/*@ +function [rec] (u32) Length(datatype List L) { + match L { + Nil {} => { + 0u32 + } + Cons {Head: H, Tail : T} => { + 1u32 + Length(T) + } + } +} +@*/ + +/* --END-- */ +unsigned int length (struct sllist *l) +/* --BEGIN-- */ +/*@ requires take L = SLList_At(l); + ensures take L_post = SLList_At(l); + L == L_post; + return == Length(L); +@*/ +/* --END-- */ +{ + if (l == 0) { +/* --BEGIN-- */ + /*@ unfold Length(L); @*/ +/* --END-- */ + return 0; + } else { +/* --BEGIN-- */ + /*@ unfold Length(L); @*/ +/* --END-- */ + return 1 + length(l->tail); + } +} diff --git a/src/examples/list/mergesort.c b/src/examples/list/mergesort.c new file mode 100644 index 00000000..8a36b63b --- /dev/null +++ b/src/examples/list/mergesort.c @@ -0,0 +1,134 @@ +#include "./headers.h" + +/*@ +function [rec] ({datatype List fst, datatype List snd}) + split (datatype List xs) +{ + match xs { + Nil {} => { + {fst: Nil{}, snd: Nil{}} + } + Cons {Head: h1, Tail: Nil{}} => { + {fst: Nil{}, snd: xs} + } + Cons {Head: h1, Tail: Cons {Head : h2, Tail : tl2 }} => { + let P = split(tl2); + {fst: Cons { Head: h1, Tail: P.fst}, + snd: Cons { Head: h2, Tail: P.snd}} + } + } +} + +function [rec] (datatype List) merge(datatype List xs, datatype List ys) { + match xs { + Nil {} => { ys } + Cons {Head: x, Tail: xs1} => { + match ys { + Nil {} => { xs } + Cons{ Head: y, Tail: ys1} => { + (x < y) ? + (Cons{ Head: x, Tail: merge(xs1, ys) }) + : (Cons{ Head: y, Tail: merge(xs, ys1) }) + } + } + } + } +} + +function [rec] (datatype List) mergesort(datatype List xs) { + match xs { + Nil{} => { xs } + Cons{Head: x, Tail: Nil{}} => { xs } + Cons{Head: x, Tail: Cons{Head: y, Tail: zs}} => { + let P = split(xs); + let L1 = mergesort(P.fst); + let L2 = mergesort(P.snd); + merge(L1, L2) + } + } +} +@*/ + +struct sllist_pair { + struct sllist* fst; + struct sllist* snd; +}; + +struct sllist_pair split(struct sllist *xs) +/*@ requires take Xs = SLList_At(xs); @*/ +/*@ ensures take Ys = SLList_At(return.fst); @*/ +/*@ ensures take Zs = SLList_At(return.snd); @*/ +/*@ ensures {fst: Ys, snd: Zs} == split(Xs); @*/ +{ + if (xs == 0) { + /*@ unfold split(Xs); @*/ + struct sllist_pair r = {.fst = 0, .snd = 0}; + return r; + } else { + struct sllist *cdr = xs -> tail; + if (cdr == 0) { + /*@ unfold split(Xs); @*/ + struct sllist_pair r = {.fst = 0, .snd = xs}; + return r; + } else { + /*@ unfold split(Xs); @*/ + struct sllist_pair p = split(cdr->tail); + xs->tail = p.fst; + cdr->tail = p.snd; + struct sllist_pair r = {.fst = xs, .snd = cdr}; + return r; + } + } +} + +struct sllist* merge(struct sllist *xs, struct sllist *ys) +/*@ requires take Xs = SLList_At(xs); @*/ +/*@ requires take Ys = SLList_At(ys); @*/ +/*@ ensures take Zs = SLList_At(return); @*/ +/*@ ensures Zs == merge(Xs, Ys); @*/ +{ + if (xs == 0) { + /*@ unfold merge(Xs, Ys); @*/ + return ys; + } else { + /*@ unfold merge(Xs, Ys); @*/ + if (ys == 0) { + /*@ unfold merge(Xs, Ys); @*/ + return xs; + } else { + /*@ unfold merge(Xs, Ys); @*/ + if (xs->head < ys->head) { + struct sllist *zs = merge(xs->tail, ys); + xs->tail = zs; + return xs; + } else { + struct sllist *zs = merge(xs, ys->tail); + ys->tail = zs; + return ys; + } + } + } +} + +struct sllist* mergesort(struct sllist *xs) +/*@ requires take Xs = SLList_At(xs); @*/ +/*@ ensures take Ys = SLList_At(return); @*/ +/*@ ensures Ys == mergesort(Xs); @*/ +{ + if (xs == 0) { + /*@ unfold mergesort(Xs); @*/ + return xs; + } else { + struct sllist *tail = xs->tail; + if (tail == 0) { + /*@ unfold mergesort(Xs); @*/ + return xs; + } else { + /*@ unfold mergesort(Xs); @*/ + struct sllist_pair p = split(xs); + p.fst = mergesort(p.fst); + p.snd = mergesort(p.snd); + return merge(p.fst, p.snd); + } + } +} diff --git a/src/examples/list/rev.c b/src/examples/list/rev.c new file mode 100644 index 00000000..ee1b4eed --- /dev/null +++ b/src/examples/list/rev.c @@ -0,0 +1,32 @@ +#include "./headers.h" +#include "./append.h" +#include "./rev.h" +#include "./rev_lemmas.h" + +struct sllist* rev_aux(struct sllist* l1, struct sllist* l2) +/*@ requires take L1 = SLList_At(l1); @*/ +/*@ requires take L2 = SLList_At(l2); @*/ +/*@ ensures take R = SLList_At(return); @*/ +/*@ ensures R == Append(RevList(L2), L1); @*/ +{ + if (l2 == 0) { + /*@ unfold RevList(L2); @*/ + /*@ unfold Append(Nil{}, L1); @*/ + return l1; + } else { + /*@ unfold RevList(L2); @*/ + /*@ apply Append_Cons_RList(RevList(Tl(L2)), Hd(L2), L1); @*/ + struct sllist *tmp = l2->tail; + l2->tail = l1; + return rev_aux(l2, tmp); + } +} + +struct sllist* rev(struct sllist* l1) +/*@ requires take L1 = SLList_At(l1); @*/ +/*@ ensures take L1_Rev = SLList_At(return); @*/ +/*@ ensures L1_Rev == RevList(L1); @*/ +{ + /*@ apply Append_Nil_RList(RevList(L1)); @*/ + return rev_aux (0, l1); +} diff --git a/src/examples/list/rev.h b/src/examples/list/rev.h new file mode 100644 index 00000000..a06e6342 --- /dev/null +++ b/src/examples/list/rev.h @@ -0,0 +1,14 @@ +#include "./snoc.h" + +/*@ +function [rec] (datatype List) RevList(datatype List L) { + match L { + Nil {} => { + Nil {} + } + Cons {Head : H, Tail : T} => { + Snoc (RevList(T), H) + } + } +} +@*/ diff --git a/src/examples/list/rev_alt.c b/src/examples/list/rev_alt.c new file mode 100644 index 00000000..6a651f32 --- /dev/null +++ b/src/examples/list/rev_alt.c @@ -0,0 +1,33 @@ +#include "./headers.h" +#include "./append.h" +#include "./rev.h" +#include "./rev_lemmas.h" + +struct sllist* rev_loop(struct sllist *l) +/*@ requires take L = SLList_At(l); + ensures take L_post = SLList_At(return); + L_post == RevList(L); +@*/ +{ + struct sllist *last = 0; + struct sllist *cur = l; + /*@ apply Append_Nil_RList(RevList(L)); @*/ + while(1) + /*@ inv take Last = SLList_At(last); + take Cur = SLList_At(cur); + Append(RevList(Cur), Last) == RevList(L); + @*/ + { + if (cur == 0) { + /*@ unfold RevList(Nil{}); @*/ + /*@ unfold Append(Nil{}, Last); @*/ + return last; + } + struct sllist *tmp = cur->tail; + cur->tail = last; + last = cur; + cur = tmp; + /*@ unfold RevList(Cur); @*/ + /*@ apply Append_Cons_RList(RevList(Tl(Cur)), Hd(Cur), Last); @*/ + } +} diff --git a/src/examples/list/rev_lemmas.h b/src/examples/list/rev_lemmas.h new file mode 100644 index 00000000..8e6dd044 --- /dev/null +++ b/src/examples/list/rev_lemmas.h @@ -0,0 +1,10 @@ +/*@ +lemma Append_Nil_RList (datatype List L1) + requires true; + ensures Append(L1, Nil{}) == L1; + +lemma Append_Cons_RList (datatype List L1, i32 X, datatype List L2) + requires true; + ensures Append(L1, Cons {Head: X, Tail: L2}) + == Append(Snoc(L1, X), L2); +@*/ diff --git a/src/examples/list/snoc.h b/src/examples/list/snoc.h new file mode 100644 index 00000000..97a27f77 --- /dev/null +++ b/src/examples/list/snoc.h @@ -0,0 +1,12 @@ +/*@ +function [rec] (datatype List) Snoc(datatype List Xs, i32 Y) { + match Xs { + Nil {} => { + Cons {Head: Y, Tail: Nil{}} + } + Cons {Head: X, Tail: Zs} => { + Cons{Head: X, Tail: Snoc (Zs, Y)} + } + } +} +@*/ diff --git a/src/examples/list_append.h b/src/examples/list_append.h deleted file mode 100644 index d6305abb..00000000 --- a/src/examples/list_append.h +++ /dev/null @@ -1,14 +0,0 @@ -// append.h - -/*@ -function [rec] (datatype seq) append(datatype seq xs, datatype seq ys) { - match xs { - Seq_Nil {} => { - ys - } - Seq_Cons {head : h, tail : zs} => { - Seq_Cons {head: h, tail: append(zs, ys)} - } - } -} -@*/ diff --git a/src/examples/list_c_types.h b/src/examples/list_c_types.h deleted file mode 100644 index c9b6d7a0..00000000 --- a/src/examples/list_c_types.h +++ /dev/null @@ -1,17 +0,0 @@ -struct int_list { - int head; - struct int_list* tail; -}; - -extern struct int_list *mallocIntList(); -/*@ spec mallocIntList(); - requires true; - ensures take u = Block(return); -@*/ - -extern void freeIntList (struct int_list *p); -/*@ spec freeIntList(pointer p); - requires take u = Block(p); - ensures true; -@*/ - diff --git a/src/examples/list_cn_types.h b/src/examples/list_cn_types.h deleted file mode 100644 index d45e6537..00000000 --- a/src/examples/list_cn_types.h +++ /dev/null @@ -1,16 +0,0 @@ -/*@ -datatype seq { - Seq_Nil {}, - Seq_Cons {i32 head, datatype seq tail} -} - -predicate (datatype seq) IntList(pointer p) { - if (is_null(p)) { - return Seq_Nil{}; - } else { - take H = Owned(p); - take tl = IntList(H.tail); - return (Seq_Cons { head: H.head, tail: tl }); - } -} -@*/ diff --git a/src/examples/list_constructors.h b/src/examples/list_constructors.h deleted file mode 100644 index 2108a861..00000000 --- a/src/examples/list_constructors.h +++ /dev/null @@ -1,19 +0,0 @@ -struct int_list* IntList_nil() -/*@ ensures take ret = IntList(return); - ret == Seq_Nil{}; - @*/ -{ - return 0; -} - -struct int_list* IntList_cons(int h, struct int_list* t) -/*@ requires take l = IntList(t); - ensures take ret = IntList(return); - ret == Seq_Cons{ head: h, tail: l}; - @*/ -{ - struct int_list *p = mallocIntList(); - p->head = h; - p->tail = t; - return p; -} diff --git a/src/examples/list_copy.c b/src/examples/list_copy.c deleted file mode 100644 index 4a1f8ef6..00000000 --- a/src/examples/list_copy.c +++ /dev/null @@ -1,17 +0,0 @@ -#include "list.h" - -struct int_list* IntList_copy (struct int_list *xs) -/*@ requires take L1 = IntList(xs); - ensures take L1_ = IntList(xs); - take L2 = IntList(return); - L1 == L1_; - L1 == L2; -@*/ -{ - if (xs == 0) { - return IntList_nil(); - } else { - struct int_list *new_tail = IntList_copy(xs->tail); - return IntList_cons(xs->head, new_tail); - } -} diff --git a/src/examples/list_free.c b/src/examples/list_free.c deleted file mode 100644 index f1441f4b..00000000 --- a/src/examples/list_free.c +++ /dev/null @@ -1,14 +0,0 @@ -#include "list.h" - -void IntList_free_list(struct int_list* xs) -// You fill in the rest... -/* --BEGIN-- */ -/*@ requires take L1 = IntList(xs); @*/ -{ - if (xs == 0) { - } else { - IntList_free_list(xs->tail); - freeIntList(xs); - } -} -/* --END-- */ diff --git a/src/examples/list_hdtl.h b/src/examples/list_hdtl.h deleted file mode 100644 index e92b51ed..00000000 --- a/src/examples/list_hdtl.h +++ /dev/null @@ -1,23 +0,0 @@ -/*@ -function (i32) hd (datatype seq xs) { - match xs { - Seq_Nil {} => { - 0i32 - } - Seq_Cons {head : h, tail : _} => { - h - } - } -} - -function (datatype seq) tl (datatype seq xs) { - match xs { - Seq_Nil {} => { - Seq_Nil {} - } - Seq_Cons {head : _, tail : tail} => { - tail - } - } -} -@*/ diff --git a/src/examples/list_length.c b/src/examples/list_length.c deleted file mode 100644 index 047b2c30..00000000 --- a/src/examples/list_length.c +++ /dev/null @@ -1,40 +0,0 @@ -/* list_length.c */ - -#include "list.h" - -/* --BEGIN-- */ -/*@ -function [rec] (u32) length(datatype seq xs) { - match xs { - Seq_Nil {} => { - 0u32 - } - Seq_Cons {head : h, tail : zs} => { - 1u32 + length(zs) - } - } -} -@*/ - -/* --END-- */ -unsigned int IntList_length (struct int_list *xs) -/* --BEGIN-- */ -/*@ requires take L1 = IntList(xs); - ensures take L1_ = IntList(xs); - L1 == L1_; - return == length(L1); -@*/ -/* --END-- */ -{ - if (xs == 0) { -/* --BEGIN-- */ - /*@ unfold length(L1); @*/ -/* --END-- */ - return 0; - } else { -/* --BEGIN-- */ - /*@ unfold length(L1); @*/ -/* --END-- */ - return 1 + IntList_length (xs->tail); - } -} diff --git a/src/examples/list_rev.c b/src/examples/list_rev.c deleted file mode 100644 index 653ad0af..00000000 --- a/src/examples/list_rev.c +++ /dev/null @@ -1,32 +0,0 @@ -#include "list.h" -#include "list_append.h" -#include "list_rev.h" -#include "list_rev_lemmas.h" - -struct int_list* IntList_rev_aux(struct int_list* xs, struct int_list* ys) -/*@ requires take L1 = IntList(xs); @*/ -/*@ requires take L2 = IntList(ys); @*/ -/*@ ensures take R = IntList(return); @*/ -/*@ ensures R == append(rev(L2), L1); @*/ -{ - if (ys == 0) { - /*@ unfold rev(L2); @*/ - /*@ unfold append(Seq_Nil {},L1); @*/ - return xs; - } else { - /*@ unfold rev(L2); @*/ - /*@ apply append_cons_r(rev(tl(L2)), hd(L2), L1); @*/ - struct int_list *tmp = ys->tail; - ys->tail = xs; - return IntList_rev_aux(ys, tmp); - } -} - -struct int_list* IntList_rev(struct int_list* xs) -/*@ requires take L1 = IntList(xs); @*/ -/*@ ensures take L1_rev = IntList(return); @*/ -/*@ ensures L1_rev == rev(L1); @*/ -{ - /*@ apply append_nil_r(rev(L1)); @*/ - return IntList_rev_aux (0, xs); -} diff --git a/src/examples/list_rev.h b/src/examples/list_rev.h deleted file mode 100644 index 7260f3eb..00000000 --- a/src/examples/list_rev.h +++ /dev/null @@ -1,14 +0,0 @@ -#include "list_snoc.h" - -/*@ -function [rec] (datatype seq) rev(datatype seq xs) { - match xs { - Seq_Nil {} => { - Seq_Nil {} - } - Seq_Cons {head : h, tail : zs} => { - snoc (rev(zs), h) - } - } -} -@*/ diff --git a/src/examples/list_rev_alt.c b/src/examples/list_rev_alt.c deleted file mode 100644 index 7cb4eaa5..00000000 --- a/src/examples/list_rev_alt.c +++ /dev/null @@ -1,33 +0,0 @@ -#include "list.h" -#include "list_append.h" -#include "list_rev.h" -#include "list_rev_lemmas.h" - -struct int_list* IntList_rev_loop(struct int_list *xs) -/*@ requires take L = IntList(xs); - ensures take L_ = IntList(return); - L_ == rev(L); -@*/ -{ - struct int_list *last = 0; - struct int_list *cur = xs; - /*@ apply append_nil_r(rev(L)); @*/ - while(1) - /*@ inv take L1 = IntList(last); - take L2 = IntList(cur); - append(rev(L2), L1) == rev(L); - @*/ - { - if (cur == 0) { - /*@ unfold rev(Seq_Nil {}); @*/ - /*@ unfold append(Seq_Nil {}, L1); @*/ - return last; - } - struct int_list *tmp = cur->tail; - cur->tail = last; - last = cur; - cur = tmp; - /*@ unfold rev(L2); @*/ - /*@ apply append_cons_r (rev (tl(L2)), hd(L2), L1); @*/ - } -} diff --git a/src/examples/list_rev_lemmas.h b/src/examples/list_rev_lemmas.h deleted file mode 100644 index 28f21937..00000000 --- a/src/examples/list_rev_lemmas.h +++ /dev/null @@ -1,10 +0,0 @@ -/*@ -lemma append_nil_r (datatype seq l1) - requires true; - ensures append(l1, Seq_Nil {}) == l1; - -lemma append_cons_r (datatype seq l1, i32 x, datatype seq l2) - requires true; - ensures append(l1, Seq_Cons {head: x, tail: l2}) - == append(snoc(l1, x), l2); -@*/ diff --git a/src/examples/list_snoc.h b/src/examples/list_snoc.h deleted file mode 100644 index 52cbdcb4..00000000 --- a/src/examples/list_snoc.h +++ /dev/null @@ -1,12 +0,0 @@ -/*@ -function [rec] (datatype seq) snoc(datatype seq xs, i32 y) { - match xs { - Seq_Nil {} => { - Seq_Cons {head: y, tail: Seq_Nil{}} - } - Seq_Cons {head: x, tail: zs} => { - Seq_Cons{head: x, tail: snoc (zs, y)} - } - } -} -@*/ diff --git a/src/examples/liste_rev_lemmas.h b/src/examples/liste_rev_lemmas.h deleted file mode 100644 index c4554e40..00000000 --- a/src/examples/liste_rev_lemmas.h +++ /dev/null @@ -1,10 +0,0 @@ -/*@ -lemma append_nil_r (datatype seq l1) - requires true - ensures append(l1, Seq_Nil {}) == l1 - -lemma append_cons_r (datatype seq l1, i32 x, datatype seq l2) - requires true - ensures append(l1, Seq_Cons {head: x, tail: l2}) - == append(snoc(l1, x), l2) -@*/ diff --git a/src/examples/malloc.h b/src/examples/malloc.h index 7182d1a2..fe2c2fc2 100644 --- a/src/examples/malloc.h +++ b/src/examples/malloc.h @@ -1,13 +1,12 @@ extern int *mallocInt (); /*@ spec mallocInt(); requires true; - ensures take v = Block(return); + ensures take R = Block(return); @*/ - extern unsigned int *mallocUnsignedInt (); /*@ spec mallocUnsignedInt(); requires true; - ensures take v = Block(return); + ensures take R = Block(return); @*/ diff --git a/src/examples/mergesort.c b/src/examples/mergesort.c deleted file mode 100644 index 5c6c13bd..00000000 --- a/src/examples/mergesort.c +++ /dev/null @@ -1,133 +0,0 @@ -#include "list.h" - -/*@ -function [rec] ({datatype seq fst, datatype seq snd}) split(datatype seq xs) -{ - match xs { - Seq_Nil {} => { - {fst: Seq_Nil{}, snd: Seq_Nil{}} - } - Seq_Cons {head: h1, tail: Seq_Nil{}} => { - {fst: Seq_Nil{}, snd: xs} - } - Seq_Cons {head: h1, tail: Seq_Cons {head : h2, tail : tl2 }} => { - let P = split(tl2); - {fst: Seq_Cons { head: h1, tail: P.fst}, - snd: Seq_Cons { head: h2, tail: P.snd}} - } - } -} - -function [rec] (datatype seq) merge(datatype seq xs, datatype seq ys) { - match xs { - Seq_Nil {} => { ys } - Seq_Cons {head: x, tail: xs1} => { - match ys { - Seq_Nil {} => { xs } - Seq_Cons{ head: y, tail: ys1} => { - (x < y) ? - (Seq_Cons{ head: x, tail: merge(xs1, ys) }) - : (Seq_Cons{ head: y, tail: merge(xs, ys1) }) - } - } - } - } -} - -function [rec] (datatype seq) mergesort(datatype seq xs) { - match xs { - Seq_Nil{} => { xs } - Seq_Cons{head: x, tail: Seq_Nil{}} => { xs } - Seq_Cons{head: x, tail: Seq_Cons{head: y, tail: zs}} => { - let P = split(xs); - let L1 = mergesort(P.fst); - let L2 = mergesort(P.snd); - merge(L1, L2) - } - } -} -@*/ - -struct int_list_pair { - struct int_list* fst; - struct int_list* snd; -}; - -struct int_list_pair split(struct int_list *xs) -/*@ requires take Xs = IntList(xs); @*/ -/*@ ensures take Ys = IntList(return.fst); @*/ -/*@ ensures take Zs = IntList(return.snd); @*/ -/*@ ensures {fst: Ys, snd: Zs} == split(Xs); @*/ -{ - if (xs == 0) { - /*@ unfold split(Xs); @*/ - struct int_list_pair r = {.fst = 0, .snd = 0}; - return r; - } else { - struct int_list *cdr = xs -> tail; - if (cdr == 0) { - /*@ unfold split(Xs); @*/ - struct int_list_pair r = {.fst = 0, .snd = xs}; - return r; - } else { - /*@ unfold split(Xs); @*/ - struct int_list_pair p = split(cdr->tail); - xs->tail = p.fst; - cdr->tail = p.snd; - struct int_list_pair r = {.fst = xs, .snd = cdr}; - return r; - } - } -} - -struct int_list* merge(struct int_list *xs, struct int_list *ys) -/*@ requires take Xs = IntList(xs); @*/ -/*@ requires take Ys = IntList(ys); @*/ -/*@ ensures take Zs = IntList(return); @*/ -/*@ ensures Zs == merge(Xs, Ys); @*/ -{ - if (xs == 0) { - /*@ unfold merge(Xs, Ys); @*/ - return ys; - } else { - /*@ unfold merge(Xs, Ys); @*/ - if (ys == 0) { - /*@ unfold merge(Xs, Ys); @*/ - return xs; - } else { - /*@ unfold merge(Xs, Ys); @*/ - if (xs->head < ys->head) { - struct int_list *zs = merge(xs->tail, ys); - xs->tail = zs; - return xs; - } else { - struct int_list *zs = merge(xs, ys->tail); - ys->tail = zs; - return ys; - } - } - } -} - -struct int_list* mergesort(struct int_list *xs) -/*@ requires take Xs = IntList(xs); @*/ -/*@ ensures take Ys = IntList(return); @*/ -/*@ ensures Ys == mergesort(Xs); @*/ -{ - if (xs == 0) { - /*@ unfold mergesort(Xs); @*/ - return xs; - } else { - struct int_list *tail = xs->tail; - if (tail == 0) { - /*@ unfold mergesort(Xs); @*/ - return xs; - } else { - /*@ unfold mergesort(Xs); @*/ - struct int_list_pair p = split(xs); - p.fst = mergesort(p.fst); - p.snd = mergesort(p.snd); - return merge(p.fst, p.snd); - } - } -} diff --git a/src/examples/queue/allocation.h b/src/examples/queue/allocation.h new file mode 100644 index 00000000..c507bf2e --- /dev/null +++ b/src/examples/queue/allocation.h @@ -0,0 +1,23 @@ +extern struct queue *malloc_queue(); +/*@ spec malloc_queue(); + requires true; + ensures take R = Block(return); +@*/ + +extern void free_queue (struct queue *p); +/*@ spec free_queue(pointer p); + requires take P = Block(p); + ensures true; +@*/ + +extern struct queue_cell *malloc_queue_cell(); +/*@ spec malloc_queue_cell(); + requires true; + ensures take R = Block(return); +@*/ + +extern void free_queue_cell (struct queue_cell *p); +/*@ spec free_queue_cell(pointer p); + requires take P = Block(p); + ensures true; +@*/ diff --git a/src/examples/queue/c_types.h b/src/examples/queue/c_types.h new file mode 100644 index 00000000..9c915508 --- /dev/null +++ b/src/examples/queue/c_types.h @@ -0,0 +1,9 @@ +struct queue { + struct queue_cell* front; + struct queue_cell* back; +}; + +struct queue_cell { + int first; + struct queue_cell* next; +}; diff --git a/src/examples/queue_cn_types_1.h b/src/examples/queue/cn_types_1.h similarity index 50% rename from src/examples/queue_cn_types_1.h rename to src/examples/queue/cn_types_1.h index 705eff65..9bc4e1d0 100644 --- a/src/examples/queue_cn_types_1.h +++ b/src/examples/queue/cn_types_1.h @@ -1,9 +1,9 @@ /*@ -predicate (datatype seq) IntQueuePtr (pointer q) { - take Q = Owned(q); +predicate (datatype List) QueuePtr_At (pointer q) { + take Q = Owned(q); assert ( (is_null(Q.front) && is_null(Q.back)) || (!is_null(Q.front) && !is_null(Q.back))); - take L = IntQueueFB(Q.front, Q.back); + take L = QueueFB(Q.front, Q.back); return L; } @*/ diff --git a/src/examples/queue/cn_types_2.h b/src/examples/queue/cn_types_2.h new file mode 100644 index 00000000..b478b7e5 --- /dev/null +++ b/src/examples/queue/cn_types_2.h @@ -0,0 +1,13 @@ +/*@ +predicate (datatype List) QueueFB (pointer front, pointer back) { + if (is_null(front)) { + return Nil{}; + } else { + take B = Owned(back); + assert (is_null(B.next)); + assert (ptr_eq(front, back) || !addr_eq(front, back)); + take L = QueueAux (front, back); + return Snoc(L, B.first); + } +} +@*/ diff --git a/src/examples/queue/cn_types_3.h b/src/examples/queue/cn_types_3.h new file mode 100644 index 00000000..73bfca7f --- /dev/null +++ b/src/examples/queue/cn_types_3.h @@ -0,0 +1,13 @@ +/*@ +predicate (datatype List) QueueAux (pointer f, pointer b) { + if (ptr_eq(f,b)) { + return Nil{}; + } else { + take F = Owned(f); + assert (!is_null(F.next)); + assert (ptr_eq(F.next, b) || !addr_eq(F.next, b)); + take B = QueueAux(F.next, b); + return Cons{Head: F.first, Tail: B}; + } +} +@*/ diff --git a/src/examples/queue/empty.c b/src/examples/queue/empty.c new file mode 100644 index 00000000..ccbd8119 --- /dev/null +++ b/src/examples/queue/empty.c @@ -0,0 +1,14 @@ +#include "./headers.h" + +struct queue* empty_queue () +/* --BEGIN-- */ +/*@ ensures take ret = QueuePtr_At(return); + ret == Nil{}; +@*/ +/* --END-- */ +{ + struct queue *p = malloc_queue(); + p->front = 0; + p->back = 0; + return p; +} diff --git a/src/examples/queue/headers.h b/src/examples/queue/headers.h new file mode 100644 index 00000000..87fbffc7 --- /dev/null +++ b/src/examples/queue/headers.h @@ -0,0 +1,11 @@ +#include "../list/c_types.h" +#include "../list/cn_types.h" +#include "../list/hdtl.h" +#include "../list/snoc.h" + +#include "./c_types.h" +#include "./cn_types_1.h" +#include "./cn_types_2.h" +#include "./cn_types_3.h" + +#include "./allocation.h" diff --git a/src/examples/queue/pop.c b/src/examples/queue/pop.c new file mode 100644 index 00000000..e840be00 --- /dev/null +++ b/src/examples/queue/pop.c @@ -0,0 +1,29 @@ +#include "./headers.h" +#include "./pop_lemma.h" + +int pop_queue (struct queue *q) +/*@ requires take Q = QueuePtr_At(q); + Q != Nil{}; + ensures take Q_post = QueuePtr_At(q); + Q_post == Tl(Q); + return == Hd(Q); +@*/ +{ + /*@ split_case is_null(q->front); @*/ + struct queue_cell* h = q->front; + if (h == q->back) { + /*@ assert ((alloc_id) h == (alloc_id) (q->back)); @*/ + int x = h->first; + free_queue_cell(h); + q->front = 0; + q->back = 0; + /*@ unfold Snoc(Nil{}, x); @*/ + return x; + } else { + int x = h->first; + /*@ apply snoc_facts(h->next, q->back, x); @*/ + q->front = h->next; + free_queue_cell(h); + return x; + } +} diff --git a/src/examples/queue/pop_lemma.h b/src/examples/queue/pop_lemma.h new file mode 100644 index 00000000..0188cda5 --- /dev/null +++ b/src/examples/queue/pop_lemma.h @@ -0,0 +1,13 @@ +/*@ +lemma snoc_facts (pointer front, pointer back, i32 x) + requires + take Q = QueueAux(front, back); + take B = Owned(back); + ensures + take Q_post = QueueAux(front, back); + take B_post = Owned(back); + Q == Q_post; B == B_post; + let L = Snoc (Cons{Head: x, Tail: Q}, B.first); + Hd(L) == x; + Tl(L) == Snoc (Q, B.first); +@*/ diff --git a/src/examples/queue_pop_orig.broken.c b/src/examples/queue/pop_orig.broken.c similarity index 52% rename from src/examples/queue_pop_orig.broken.c rename to src/examples/queue/pop_orig.broken.c index ea203a7f..f6d28084 100644 --- a/src/examples/queue_pop_orig.broken.c +++ b/src/examples/queue/pop_orig.broken.c @@ -1,18 +1,18 @@ -#include "queue_headers.h" +#include "./headers.h" -int IntQueue_pop (struct int_queue *q) +int pop_queue (struct queue *q) { - struct int_queueCell* h = q->front; + struct queue_cell* h = q->front; if (h == q->back) { int x = h->first; - freeIntQueueCell(h); + free_queue_cell(h); q->front = 0; q->back = 0; return x; } else { int x = h->first; q->front = h->next; - freeIntQueueCell(h); + free_queue_cell(h); return x; } } diff --git a/src/examples/queue/pop_unified.c b/src/examples/queue/pop_unified.c new file mode 100644 index 00000000..cecaa39b --- /dev/null +++ b/src/examples/queue/pop_unified.c @@ -0,0 +1,71 @@ +#include "./headers.h" + +/*@ +type_synonym result = { datatype List after, datatype List before } + +predicate (result) Queue_pop_lemma(pointer front, pointer back, i32 popped) { + if (is_null(front)) { + return { after: Nil{}, before: Snoc(Nil{}, popped) }; + } else { + take B = Owned(back); + assert (is_null(B.next)); + take L = QueueAux (front, back); + return { after: Snoc(L, B.first), before: Snoc(Cons {Head: popped, Tail: L}, B.first) }; + } +} +@*/ + +void snoc_fact(struct queue_cell *front, struct queue_cell *back, int x) +/*@ +requires + take Q = QueueAux(front, back); + take B = Owned(back); +ensures + take Q_post = QueueAux(front, back); + take B_post = Owned(back); + Q == Q_post; B == B_post; + let L = Snoc (Cons{Head: x, Tail: Q}, B.first); + Hd(L) == x; + Tl(L) == Snoc (Q, B.first); +@*/ +{ + /*@ unfold Snoc (Cons{Head: x, Tail: Q}, B.first); @*/ +} + +void snoc_fact_unified(struct queue_cell *front, struct queue_cell *back, int x) +/*@ +requires + take AB = Queue_pop_lemma(front, back, x); +ensures + take NewAB = Queue_pop_lemma(front, back, x); + AB == NewAB; + AB.after == Tl(AB.before); + x == Hd(AB.before); +@*/ +{ + if (!front) { + /*@ unfold Snoc(Nil{}, x); @*/ + } else { + snoc_fact(front, back, x); + } +} + +int pop_queue (struct queue *q) +/*@ requires take before = QueuePtr_At(q); + before != Nil{}; + ensures take after = QueuePtr_At(q); + after == Tl(before); + return == Hd(before); +@*/ +{ + /*@ split_case is_null(q->front); @*/ + struct queue_cell* h = q->front; + /*@ split_case ptr_eq(h, q->back); @*/ + int x = h->first; + q->front = h->next; + free_queue_cell(h); + if (!q->front) q->back = 0; + snoc_fact_unified(q->front, q->back, x); + return x; +} + diff --git a/src/examples/queue/push.c b/src/examples/queue/push.c new file mode 100644 index 00000000..19e27637 --- /dev/null +++ b/src/examples/queue/push.c @@ -0,0 +1,24 @@ +#include "./headers.h" +#include "./push_lemma.h" + +void push_queue (int x, struct queue *q) +/*@ requires take Q = QueuePtr_At(q); + ensures take Q_post = QueuePtr_At(q); + Q_post == Snoc (Q, x); +@*/ +{ + struct queue_cell *c = malloc_queue_cell(); + c->first = x; + c->next = 0; + if (q->back == 0) { + q->front = c; + q->back = c; + return; + } else { + struct queue_cell *oldback = q->back; + q->back->next = c; + q->back = c; + /*@ apply push_lemma (q->front, oldback); @*/ + return; + } +} diff --git a/src/examples/queue_push_induction.c b/src/examples/queue/push_induction.c similarity index 50% rename from src/examples/queue_push_induction.c rename to src/examples/queue/push_induction.c index dc1b012d..06c0117f 100644 --- a/src/examples/queue_push_induction.c +++ b/src/examples/queue/push_induction.c @@ -1,40 +1,40 @@ -#include "queue_headers.h" +#include "./headers.h" -void push_induction(struct int_queueCell* front - , struct int_queueCell* second_last - , struct int_queueCell* last) +void push_induction(struct queue_cell* front + , struct queue_cell* second_last + , struct queue_cell* last) /*@ requires ptr_eq(front, second_last) || !addr_eq(front, second_last); - take Q = IntQueueAux(front, second_last); + take Q = QueueAux(front, second_last); take Second_last = Owned(second_last); ptr_eq(Second_last.next, last); take Last = Owned(last); ensures ptr_eq(front, last) || !addr_eq(front, last); - take NewQ = IntQueueAux(front, last); + take Q_post = QueueAux(front, last); take Last2 = Owned(last); - NewQ == snoc(Q, Second_last.first); + Q_post == Snoc(Q, Second_last.first); Last == Last2; @*/ { if (front == second_last) { - /*@ unfold snoc(Q, Second_last.first); @*/ + /*@ unfold Snoc(Q, Second_last.first); @*/ return; } else { push_induction(front->next, second_last, last); - /*@ unfold snoc(Q, Second_last.first); @*/ + /*@ unfold Snoc(Q, Second_last.first); @*/ return; } } -void IntQueue_push (int x, struct int_queue *q) -/*@ requires take before = IntQueuePtr(q); - ensures take after = IntQueuePtr(q); - after == snoc (before, x); +void push_queue (int x, struct queue *q) +/*@ requires take before = QueuePtr_At(q); + ensures take after = QueuePtr_At(q); + after == Snoc (before, x); @*/ { - struct int_queueCell *c = mallocIntQueueCell(); + struct queue_cell *c = malloc_queue_cell(); c->first = x; c->next = 0; if (q->back == 0) { @@ -42,7 +42,7 @@ void IntQueue_push (int x, struct int_queue *q) q->back = c; return; } else { - struct int_queueCell *oldback = q->back; + struct queue_cell *oldback = q->back; q->back->next = c; q->back = c; push_induction(q->front, oldback, c); diff --git a/src/examples/queue_push_lemma.h b/src/examples/queue/push_lemma.h similarity index 51% rename from src/examples/queue_push_lemma.h rename to src/examples/queue/push_lemma.h index a93abe50..0692c1d0 100644 --- a/src/examples/queue_push_lemma.h +++ b/src/examples/queue/push_lemma.h @@ -2,10 +2,10 @@ lemma push_lemma (pointer front, pointer p) requires ptr_eq(front, p) || !addr_eq(front, p); - take Q = IntQueueAux(front, p); - take P = Owned(p); + take Q = QueueAux(front, p); + take P = Owned(p); ensures ptr_eq(front, P.next) || !addr_eq(front, P.next); - take NewQ = IntQueueAux(front, P.next); - NewQ == snoc(Q, P.first); + take Q_post = QueueAux(front, P.next); + Q_post == Snoc(Q, P.first); @*/ diff --git a/src/examples/queue_push_orig.broken.c b/src/examples/queue/push_orig.broken.c similarity index 50% rename from src/examples/queue_push_orig.broken.c rename to src/examples/queue/push_orig.broken.c index d342ef2b..97116c4a 100644 --- a/src/examples/queue_push_orig.broken.c +++ b/src/examples/queue/push_orig.broken.c @@ -1,8 +1,8 @@ -#include "queue_headers.h" +#include "./headers.h" -void IntQueue_push (int x, struct int_queue *q) +void push_queue (int x, struct queue *q) { - struct int_queueCell *c = mallocIntQueueCell(); + struct queue_cell *c = malloc_queue_cell(); c->first = x; c->next = 0; if (q->back == 0) { @@ -10,7 +10,7 @@ void IntQueue_push (int x, struct int_queue *q) q->back = c; return; } else { - struct int_queueCell *oldback = q->back; + struct queue_cell *oldback = q->back; q->back->next = c; q->back = c; return; diff --git a/src/examples/queue_allocation.h b/src/examples/queue_allocation.h deleted file mode 100644 index 6415601f..00000000 --- a/src/examples/queue_allocation.h +++ /dev/null @@ -1,23 +0,0 @@ -extern struct int_queue *mallocIntQueue(); -/*@ spec mallocIntQueue(); - requires true; - ensures take u = Block(return); -@*/ - -extern void freeIntQueue (struct int_queue *p); -/*@ spec freeIntQueue(pointer p); - requires take u = Block(p); - ensures true; -@*/ - -extern struct int_queueCell *mallocIntQueueCell(); -/*@ spec mallocIntQueueCell(); - requires true; - ensures take u = Block(return); -@*/ - -extern void freeIntQueueCell (struct int_queueCell *p); -/*@ spec freeIntQueueCell(pointer p); - requires take u = Block(p); - ensures true; -@*/ diff --git a/src/examples/queue_c_types.h b/src/examples/queue_c_types.h deleted file mode 100644 index bbc89972..00000000 --- a/src/examples/queue_c_types.h +++ /dev/null @@ -1,9 +0,0 @@ -struct int_queue { - struct int_queueCell* front; - struct int_queueCell* back; -}; - -struct int_queueCell { - int first; - struct int_queueCell* next; -}; diff --git a/src/examples/queue_cn_types_2.h b/src/examples/queue_cn_types_2.h deleted file mode 100644 index 440a2cb2..00000000 --- a/src/examples/queue_cn_types_2.h +++ /dev/null @@ -1,13 +0,0 @@ -/*@ -predicate (datatype seq) IntQueueFB (pointer front, pointer back) { - if (is_null(front)) { - return Seq_Nil{}; - } else { - take B = Owned(back); - assert (is_null(B.next)); - assert (ptr_eq(front, back) || !addr_eq(front, back)); - take L = IntQueueAux (front, back); - return snoc(L, B.first); - } -} -@*/ diff --git a/src/examples/queue_cn_types_3.h b/src/examples/queue_cn_types_3.h deleted file mode 100644 index 83deddf8..00000000 --- a/src/examples/queue_cn_types_3.h +++ /dev/null @@ -1,13 +0,0 @@ -/*@ -predicate (datatype seq) IntQueueAux (pointer f, pointer b) { - if (ptr_eq(f,b)) { - return Seq_Nil{}; - } else { - take F = Owned(f); - assert (!is_null(F.next)); - assert (ptr_eq(F.next, b) || !addr_eq(F.next, b)); - take B = IntQueueAux(F.next, b); - return Seq_Cons{head: F.first, tail: B}; - } -} -@*/ diff --git a/src/examples/queue_empty.c b/src/examples/queue_empty.c deleted file mode 100644 index d2a8f3c7..00000000 --- a/src/examples/queue_empty.c +++ /dev/null @@ -1,14 +0,0 @@ -#include "queue_headers.h" - -struct int_queue* IntQueue_empty () -/* --BEGIN-- */ -/*@ ensures take ret = IntQueuePtr(return); - ret == Seq_Nil{}; -@*/ -/* --END-- */ -{ - struct int_queue *p = mallocIntQueue(); - p->front = 0; - p->back = 0; - return p; -} diff --git a/src/examples/queue_headers.h b/src/examples/queue_headers.h deleted file mode 100644 index 5b0d9b02..00000000 --- a/src/examples/queue_headers.h +++ /dev/null @@ -1,11 +0,0 @@ -#include "list_c_types.h" -#include "list_cn_types.h" -#include "list_hdtl.h" -#include "list_snoc.h" - -#include "queue_c_types.h" -#include "queue_cn_types_1.h" -#include "queue_cn_types_2.h" -#include "queue_cn_types_3.h" - -#include "queue_allocation.h" diff --git a/src/examples/queue_pop.c b/src/examples/queue_pop.c deleted file mode 100644 index 56f8a87a..00000000 --- a/src/examples/queue_pop.c +++ /dev/null @@ -1,29 +0,0 @@ -#include "queue_headers.h" -#include "queue_pop_lemma.h" - -int IntQueue_pop (struct int_queue *q) -/*@ requires take before = IntQueuePtr(q); - before != Seq_Nil{}; - ensures take after = IntQueuePtr(q); - after == tl(before); - return == hd(before); -@*/ -{ - /*@ split_case is_null(q->front); @*/ - struct int_queueCell* h = q->front; - if (h == q->back) { - /*@ assert ((alloc_id) h == (alloc_id) (q->back)); @*/ - int x = h->first; - freeIntQueueCell(h); - q->front = 0; - q->back = 0; - /*@ unfold snoc(Seq_Nil{}, x); @*/ - return x; - } else { - int x = h->first; - /*@ apply snoc_facts(h->next, q->back, x); @*/ - q->front = h->next; - freeIntQueueCell(h); - return x; - } -} diff --git a/src/examples/queue_pop_lemma.h b/src/examples/queue_pop_lemma.h deleted file mode 100644 index 80d2ab90..00000000 --- a/src/examples/queue_pop_lemma.h +++ /dev/null @@ -1,13 +0,0 @@ -/*@ -lemma snoc_facts (pointer front, pointer back, i32 x) - requires - take Q = IntQueueAux(front, back); - take B = Owned(back); - ensures - take NewQ = IntQueueAux(front, back); - take NewB = Owned(back); - Q == NewQ; B == NewB; - let L = snoc (Seq_Cons{head: x, tail: Q}, B.first); - hd(L) == x; - tl(L) == snoc (Q, B.first); -@*/ diff --git a/src/examples/queue_pop_lemma_stages.c b/src/examples/queue_pop_lemma_stages.c deleted file mode 100644 index 66043b8d..00000000 --- a/src/examples/queue_pop_lemma_stages.c +++ /dev/null @@ -1,105 +0,0 @@ -#include "queue_headers.h" - -// Step 1: Understand the state we have upon lemma entry accurately. -// This is a sanity check that keeps your lemmas honest. - -/*@ - -predicate (datatype seq) Pre(pointer front, pointer back, i32 popped, datatype seq before) { - if (is_null(front)) { - let after = Seq_Nil{}; - assert (before == snoc(Seq_Nil{}, popped)); - return after; - } else { - take B = Owned(back); - assert (is_null(B.next)); - take L = IntQueueAux (front, back); - assert (before == snoc(Seq_Cons {head: popped, tail: L}, B.first)); - let after = snoc(L, B.first); - return after; - } -} - -lemma lemma1(pointer front, pointer back, i32 popped, datatype seq before) -requires - take Q = Pre(front, back, popped, before); -ensures - take NewQ = Pre(front, back, popped, before); - Q == NewQ; -@*/ - -// Step 2: Copy the state into the post-condition, adding the asserts the SMT solver can't manage. - -/*@ - -predicate (datatype seq) Post(pointer front, pointer back, i32 popped, datatype seq before) { - if (is_null(front)) { - assert (before == snoc(Seq_Nil{}, popped)); - let after = Seq_Nil{}; - assert (after == tl(before)); - assert (popped == hd(before)); - return after; - } else { - take B = Owned(back); - assert (is_null(B.next)); - take L = IntQueueAux (front, back); - assert (before == snoc(Seq_Cons {head: popped, tail: L}, B.first)); - let after = snoc(L, B.first); - assert (after == tl(before)); - assert (popped == hd(before)); - return after; - } -} - -lemma lemma2(pointer front, pointer back, i32 popped, datatype seq before) -requires - take Q = Pre(front, back, popped, before); -ensures - take NewQ = Post(front, back, popped, before); - Q == NewQ; -@*/ - -// Step 3: Expose the values of the predicate you wish to constrain as an output. -// Arguments used for only for the sanity check are now deleted from the predicate. -// Assertions are moved outside the predicate, and into the lemma. - -/*@ - -type_synonym result = { datatype seq after, datatype seq before } - -predicate (result) Queue_pop_lemma(pointer front, pointer back, i32 popped) { - if (is_null(front)) { - return { after: Seq_Nil{}, before: snoc(Seq_Nil{}, popped) }; - } else { - take B = Owned(back); - assert (is_null(B.next)); - take L = IntQueueAux (front, back); - return { after: snoc(L, B.first), before: snoc(Seq_Cons {head: popped, tail: L}, B.first) }; - } -} - -lemma lemma3(pointer front, pointer back, i32 popped, datatype seq before) -requires - take Q = Queue_pop_lemma(front, back, popped); - before == Q.before; -ensures - take NewQ = Queue_pop_lemma(front, back, popped); - Q == NewQ; - Q.after == tl(Q.before); - popped == hd(Q.before); -@*/ - -// Step 4 (optional): Remove the sanity checking from the pre-condition. - -/*@ - -lemma snoc_fact_unified(pointer front, pointer back, i32 popped) -requires - take Q = Queue_pop_lemma(front, back, popped); -ensures - take NewQ = Queue_pop_lemma(front, back, popped); - Q == NewQ; - Q.after == tl(Q.before); - popped == hd(Q.before); - -@*/ diff --git a/src/examples/queue_pop_unified.c b/src/examples/queue_pop_unified.c deleted file mode 100644 index da0b8018..00000000 --- a/src/examples/queue_pop_unified.c +++ /dev/null @@ -1,71 +0,0 @@ -#include "queue_headers.h" - -/*@ -type_synonym result = { datatype seq after, datatype seq before } - -predicate (result) Queue_pop_lemma(pointer front, pointer back, i32 popped) { - if (is_null(front)) { - return { after: Seq_Nil{}, before: snoc(Seq_Nil{}, popped) }; - } else { - take B = Owned(back); - assert (is_null(B.next)); - take L = IntQueueAux (front, back); - return { after: snoc(L, B.first), before: snoc(Seq_Cons {head: popped, tail: L}, B.first) }; - } -} -@*/ - -void snoc_fact(struct int_queueCell *front, struct int_queueCell *back, int x) -/*@ -requires - take Q = IntQueueAux(front, back); - take B = Owned(back); -ensures - take NewQ = IntQueueAux(front, back); - take NewB = Owned(back); - Q == NewQ; B == NewB; - let L = snoc (Seq_Cons{head: x, tail: Q}, B.first); - hd(L) == x; - tl(L) == snoc (Q, B.first); -@*/ -{ - /*@ unfold snoc (Seq_Cons{head: x, tail: Q}, B.first); @*/ -} - -void snoc_fact_unified(struct int_queueCell *front, struct int_queueCell *back, int x) -/*@ -requires - take AB = Queue_pop_lemma(front, back, x); -ensures - take NewAB = Queue_pop_lemma(front, back, x); - AB == NewAB; - AB.after == tl(AB.before); - x == hd(AB.before); -@*/ -{ - if (!front) { - /*@ unfold snoc(Seq_Nil{}, x); @*/ - } else { - snoc_fact(front, back, x); - } -} - -int IntQueue_pop (struct int_queue *q) -/*@ requires take before = IntQueuePtr(q); - before != Seq_Nil{}; - ensures take after = IntQueuePtr(q); - after == tl(before); - return == hd(before); -@*/ -{ - /*@ split_case is_null(q->front); @*/ - struct int_queueCell* h = q->front; - /*@ split_case ptr_eq(h, q->back); @*/ - int x = h->first; - q->front = h->next; - freeIntQueueCell(h); - if (!q->front) q->back = 0; - snoc_fact_unified(q->front, q->back, x); - return x; -} - diff --git a/src/examples/queue_push.c b/src/examples/queue_push.c deleted file mode 100644 index 98e9826f..00000000 --- a/src/examples/queue_push.c +++ /dev/null @@ -1,24 +0,0 @@ -#include "queue_headers.h" -#include "queue_push_lemma.h" - -void IntQueue_push (int x, struct int_queue *q) -/*@ requires take before = IntQueuePtr(q); - ensures take after = IntQueuePtr(q); - after == snoc (before, x); -@*/ -{ - struct int_queueCell *c = mallocIntQueueCell(); - c->first = x; - c->next = 0; - if (q->back == 0) { - q->front = c; - q->back = c; - return; - } else { - struct int_queueCell *oldback = q->back; - q->back->next = c; - q->back = c; - /*@ apply push_lemma(q->front, oldback); @*/ - return; - } -} diff --git a/src/examples/read.c b/src/examples/read.c index 5a09f6dd..04892f56 100644 --- a/src/examples/read.c +++ b/src/examples/read.c @@ -1,7 +1,7 @@ int read (int *p) /* --BEGIN-- */ -/*@ requires take v1 = Owned(p); - ensures take v2 = Owned(p); +/*@ requires take P = Owned(p); + ensures take P_post = Owned(p); @*/ /* --END-- */ { diff --git a/src/examples/read2.c b/src/examples/read2.c index d7fb90a7..94ca64ea 100644 --- a/src/examples/read2.c +++ b/src/examples/read2.c @@ -1,11 +1,9 @@ int read (int *p) -/* --BEGIN-- */ -/*@ requires take v1 = Owned(p); - ensures take v2 = Owned(p); - return == v1; - v1 == v2; +/*@ requires take P = Owned(p); + ensures take P_post = Owned(p); + return == P; + P_post == P; @*/ -/* --END-- */ { return *p; } diff --git a/src/examples/ref.h b/src/examples/ref.h index cbd68d5f..bbb83fa0 100644 --- a/src/examples/ref.h +++ b/src/examples/ref.h @@ -1,15 +1,14 @@ extern unsigned int *refUnsignedInt (unsigned int v); /*@ spec refUnsignedInt(u32 v); requires true; - ensures take vr = Owned(return); - vr == v; + ensures take R = Owned(return); + R == v; @*/ - extern int *refInt (int v); /*@ spec refInt(i32 v); requires true; - ensures take vr = Owned(return); - vr == v; + ensures take R = Owned(return); + R == v; @*/ diff --git a/src/examples/runway/funcs2.c b/src/examples/runway/funcs2.c index 635ae972..2b0fd609 100644 --- a/src/examples/runway/funcs2.c +++ b/src/examples/runway/funcs2.c @@ -20,7 +20,6 @@ struct State increment_Runway_Time(struct State s) return temp; } - struct State reset_Runway_Time(struct State s) /* --BEGIN-- */ /*@ requires valid_state(s); @@ -49,8 +48,10 @@ struct State arrive(struct State s) s.ModeA == return.ModeA; s.ModeD == return.ModeD; s.W_D == return.W_D; - s.W_D == 0i32 implies s.Plane_Counter == return.Plane_Counter; - s.W_D > 0i32 implies s.Plane_Counter == return.Plane_Counter - 1i32; + s.W_D == 0i32 + implies s.Plane_Counter == return.Plane_Counter; + s.W_D > 0i32 + implies s.Plane_Counter == return.Plane_Counter - 1i32; @*/ /* --END-- */ { @@ -117,7 +118,6 @@ struct State switch_modes(struct State s) return temp; } - // This function represents what happens every minute at the airport. // One `tick` corresponds to one minute. struct State tick(struct State s) @@ -127,12 +127,13 @@ struct State tick(struct State s) (i64) s.W_A < 2147483647i64; (i64) s.W_D < 2147483647i64; ensures valid_state(return); - (s.W_A > 0i32 && s.W_D == 0i32 && s.Runway_Time == 0i32 implies return.ModeA == ACTIVE()); - (s.W_D > 0i32 && s.W_A == 0i32 && s.Runway_Time == 0i32 implies return.ModeD == ACTIVE()); + (s.W_A > 0i32 && s.W_D == 0i32 && s.Runway_Time == 0i32 + implies return.ModeA == ACTIVE()); + (s.W_D > 0i32 && s.W_A == 0i32 && s.Runway_Time == 0i32 + implies return.ModeD == ACTIVE()); @*/ /* --END-- */ { - // Plane on the runway if (s.Runway_Time > 0) { @@ -174,11 +175,9 @@ struct State tick(struct State s) s = reset_Plane_Counter(s); s = switch_modes(s); - // If there are planes waiting to arrive, let one arrive if (s.W_A > 0) { s = arrive(s); - s = increment_Runway_Time(s); } return s; @@ -202,4 +201,4 @@ struct State tick(struct State s) } } } -} \ No newline at end of file +} diff --git a/src/examples/runway/valid_state.h b/src/examples/runway/valid_state.h index c36c7e73..b7e8b2e5 100644 --- a/src/examples/runway/valid_state.h +++ b/src/examples/runway/valid_state.h @@ -3,14 +3,17 @@ function (boolean) valid_state (struct State s) { (s.ModeA == INACTIVE() || s.ModeA == ACTIVE()) && (s.ModeD == INACTIVE() || s.ModeD == ACTIVE()) && (s.ModeA == INACTIVE() || s.ModeD == INACTIVE()) && + (s.W_A >= 0i32 && s.W_D >= 0i32) && (0i32 <= s.Runway_Time && s.Runway_Time <= 5i32) && (0i32 <= s.Plane_Counter && s.Plane_Counter <= 3i32) && - (s.ModeA == INACTIVE() && s.ModeD == INACTIVE() implies s.Plane_Counter == 0i32) && - (s.Runway_Time > 0i32 implies (s.ModeA == ACTIVE() || s.ModeD == ACTIVE())) && + + (s.ModeA == INACTIVE() && s.ModeD == INACTIVE() + implies s.Plane_Counter == 0i32) && + (s.Runway_Time > 0i32 + implies (s.ModeA == ACTIVE() || s.ModeD == ACTIVE())) && (s.Plane_Counter > 0i32 && s.ModeA == ACTIVE() implies s.W_D > 0i32) && (s.Plane_Counter > 0i32 && s.ModeD == ACTIVE() implies s.W_A > 0i32) - } -@*/ \ No newline at end of file +@*/ diff --git a/src/examples/slf0_basic_incr.c b/src/examples/slf0_basic_incr.c index f954c842..51aa8f43 100644 --- a/src/examples/slf0_basic_incr.c +++ b/src/examples/slf0_basic_incr.c @@ -1,10 +1,8 @@ void incr (unsigned int *p) -/* --BEGIN-- */ /*@ requires take n1 = Owned(p); ensures take n2 = Owned(p); n2 == n1 + 1u32; @*/ -/* --END-- */ { unsigned int n = *p; unsigned int m = n+1; diff --git a/src/examples/slf0_basic_incr.signed.broken.c b/src/examples/slf0_basic_incr.signed.broken.c index a4959035..3fbd627d 100644 --- a/src/examples/slf0_basic_incr.signed.broken.c +++ b/src/examples/slf0_basic_incr.signed.broken.c @@ -1,6 +1,6 @@ void incr (int *p) -/*@ requires take u = Block(p); - ensures take v = Owned(p); +/*@ requires take P = Block(p); + ensures take P_post = Owned(p); @*/ { *p = *p+1; diff --git a/src/examples/slf0_basic_incr.signed.c b/src/examples/slf0_basic_incr.signed.c index ae0656d5..2986c617 100644 --- a/src/examples/slf0_basic_incr.signed.c +++ b/src/examples/slf0_basic_incr.signed.c @@ -1,11 +1,9 @@ void incr (int *p) -/* --BEGIN-- */ -/*@ requires take v1 = Owned(p); - ((i64) v1) + 1i64 <= (i64)MAXi32(); - ensures take v2 = Owned(p); - v2 == v1+1i32; +/*@ requires take P = Owned(p); + ((i64) P) + 1i64 <= (i64) MAXi32(); + ensures take P_post = Owned(p); + P_post == P + 1i32; @*/ -/* --END-- */ { *p = *p+1; } diff --git a/src/examples/slf17_get_and_free.c b/src/examples/slf17_get_and_free.c index 8c6e59aa..a2238315 100644 --- a/src/examples/slf17_get_and_free.c +++ b/src/examples/slf17_get_and_free.c @@ -1,8 +1,9 @@ #include "free.h" unsigned int get_and_free (unsigned int *p) -/*@ requires take v1_ = Owned(p); - ensures return == v1_; @*/ +/*@ requires take P = Owned(p); + ensures return == P; +@*/ { unsigned int v = *p; freeUnsignedInt (p); diff --git a/src/examples/slf1_basic_example_let.signed.c b/src/examples/slf1_basic_example_let.signed.c index c3102646..44064ebd 100644 --- a/src/examples/slf1_basic_example_let.signed.c +++ b/src/examples/slf1_basic_example_let.signed.c @@ -1,8 +1,8 @@ int doubled (int n) /* --BEGIN-- */ -/*@ requires let n_ = (i64) n; - (i64)MINi32() <= n_ - 1i64; n_ + 1i64 <= (i64)MAXi32(); - (i64)MINi32() <= n_ + n_; n_ + n_ <= (i64)MAXi32(); +/*@ requires let N = (i64) n; + (i64)MINi32() <= N - 1i64; N + 1i64 <= (i64)MAXi32(); + (i64)MINi32() <= N + N; N + N <= (i64)MAXi32(); ensures return == n * 2i32; @*/ /* --END-- */ diff --git a/src/examples/slf2_basic_quadruple.signed.c b/src/examples/slf2_basic_quadruple.signed.c index 23a1d495..92e71f5d 100644 --- a/src/examples/slf2_basic_quadruple.signed.c +++ b/src/examples/slf2_basic_quadruple.signed.c @@ -1,7 +1,7 @@ int quadruple (int n) /* --BEGIN-- */ -/*@ requires let n_ = (i64) n; - (i64)MINi32() <= n_ * 4i64; n_ * 4i64 <= (i64)MAXi32(); +/*@ requires let N = (i64) n; + (i64)MINi32() <= N * 4i64; N * 4i64 <= (i64)MAXi32(); ensures return == 4i32 * n; @*/ /* --END-- */ diff --git a/src/examples/slf3_basic_inplace_double.c b/src/examples/slf3_basic_inplace_double.c index 065a96f5..ca7cddde 100644 --- a/src/examples/slf3_basic_inplace_double.c +++ b/src/examples/slf3_basic_inplace_double.c @@ -1,10 +1,10 @@ void inplace_double (int *p) /* --BEGIN-- */ -/*@ requires take n_ = Owned(p); - let r = 2i64 * ((i64) n_); - (i64)MINi32() <= r; r <= (i64)MAXi32(); - ensures take m_ = Owned(p); - m_ == (i32) r; +/*@ requires take P = Owned(p); + let M = 2i64 * ((i64) P); + (i64) MINi32() <= M; M <= (i64) MAXi32(); + ensures take P_post = Owned(p); + P_post == (i32) M; @*/ /* --END-- */ { diff --git a/src/examples/slf8_basic_transfer.c b/src/examples/slf8_basic_transfer.c index ffa37489..71a80c6a 100644 --- a/src/examples/slf8_basic_transfer.c +++ b/src/examples/slf8_basic_transfer.c @@ -1,11 +1,11 @@ void transfer (unsigned int *p, unsigned int *q) /* --BEGIN-- */ -/*@ requires take n1 = Owned(p); - take m1 = Owned(q); - ensures take n2 = Owned(p); - take m2 = Owned(q); - n2 == n1 + m1; - m2 == 0u32; +/*@ requires take P = Owned(p); + take Q = Owned(q); + ensures take P_post = Owned(p); + take Q_post = Owned(q); + P_post == P + Q; + Q_post == 0u32; @*/ /* --END-- */ { diff --git a/src/examples/slf_incr2.c b/src/examples/slf_incr2.c index cd4b9c23..6ed25927 100644 --- a/src/examples/slf_incr2.c +++ b/src/examples/slf_incr2.c @@ -1,42 +1,42 @@ /*@ -predicate { u32 pv, u32 qv } BothOwned (pointer p, pointer q) +predicate { u32 P, u32 Q } BothOwned (pointer p, pointer q) { if (ptr_eq(p,q)) { - take pv = Owned(p); - return {pv: pv, qv: pv}; + take PX = Owned(p); + return {P: PX, Q: PX}; } else { - take pv = Owned(p); - take qv = Owned(q); - return {pv: pv, qv: qv}; + take PX = Owned(p); + take QX = Owned(q); + return {P: PX, Q: QX}; } } @*/ -void incr2 (unsigned int *p, unsigned int *q) -/*@ requires take vs = BothOwned(p,q); - ensures take vs_ = BothOwned(p,q); - vs_.pv == (!ptr_eq(p,q) ? (vs.pv + 1u32) : (vs.pv + 2u32)); - vs_.qv == (!ptr_eq(p,q) ? (vs.qv + 1u32) : vs_.pv); +void incr2(unsigned int *p, unsigned int *q) +/*@ requires take PQ = BothOwned(p,q); + ensures take PQ_post = BothOwned(p,q); + PQ_post.P == (!ptr_eq(p,q) ? (PQ.P + 1u32) : (PQ.P + 2u32)); + PQ_post.Q == (!ptr_eq(p,q) ? (PQ.Q + 1u32) : PQ_post.P); @*/ { /*@ split_case ptr_eq(p,q); @*/ unsigned int n = *p; - unsigned int m = n+1; + unsigned int m = n + 1; *p = m; n = *q; - m = n+1; + m = n + 1; *q = m; } -void call_both_better (unsigned int *p, unsigned int *q) -/*@ requires take pv = Owned(p); - take qv = Owned(q); +void call_both_better(unsigned int *p, unsigned int *q) +/*@ requires take P = Owned(p); + take Q = Owned(q); !ptr_eq(p,q); - ensures take pv_ = Owned(p); - take qv_ = Owned(q); - pv_ == pv + 3u32; - qv_ == qv + 1u32; + ensures take P_post = Owned(p); + take Q_post = Owned(q); + P_post == P + 3u32; + Q_post == Q + 1u32; @*/ { incr2(p, q); diff --git a/src/examples/slf_incr2_alias.c b/src/examples/slf_incr2_alias.c index 2eff193b..2f8cd75c 100644 --- a/src/examples/slf_incr2_alias.c +++ b/src/examples/slf_incr2_alias.c @@ -1,9 +1,28 @@ +// Increment two different pointers (same as above) +void incr2a (unsigned int *p, unsigned int *q) +/*@ requires take P = Owned(p); + take Q = Owned(q); + ensures take P_post = Owned(p); + take Q_post = Owned(q); + P_post == P + 1u32; + Q_post == Q + 1u32; +@*/ +{ + unsigned int n = *p; + unsigned int m = n+1; + *p = m; + n = *q; + m = n+1; + *q = m; +} + +// Increment the same pointer twice void incr2b (unsigned int *p, unsigned int *q) -/*@ requires take pv = Owned(p); +/*@ requires take P = Owned(p); ptr_eq(q,p); - ensures take pv_ = Owned(p); + ensures take P_post = Owned(p); ptr_eq(q,p); - pv_ == pv + 2u32; + P_post == P + 2u32; @*/ { unsigned int n = *p; @@ -14,8 +33,6 @@ void incr2b (unsigned int *p, unsigned int *q) *q = m; } -#include "slf_incr2_noalias.c" - void call_both (unsigned int *p, unsigned int *q) /*@ requires take pv = Owned(p); take qv = Owned(q); @@ -25,6 +42,6 @@ void call_both (unsigned int *p, unsigned int *q) qv_ == qv + 1u32; @*/ { - incr2a(p, q); - incr2b(p, p); + incr2a(p, q); // increment two different pointers + incr2b(p, p); // increment the same pointer twice } diff --git a/src/examples/slf_incr2_noalias.c b/src/examples/slf_incr2_noalias.c index d4966069..7c7e2369 100644 --- a/src/examples/slf_incr2_noalias.c +++ b/src/examples/slf_incr2_noalias.c @@ -1,10 +1,10 @@ void incr2a (unsigned int *p, unsigned int *q) -/*@ requires take pv = Owned(p); - take qv = Owned(q); - ensures take pv_ = Owned(p); - take qv_ = Owned(q); - pv_ == pv + 1u32; - qv_ == qv + 1u32; +/*@ requires take P = Owned(p); + take Q = Owned(q); + ensures take P_post = Owned(p); + take Q_post = Owned(q); + P_post == P + 1u32; + Q_post == Q + 1u32; @*/ { unsigned int n = *p; diff --git a/src/examples/slf_length_acc.c b/src/examples/slf_length_acc.c index 34d85294..f3fa4389 100644 --- a/src/examples/slf_length_acc.c +++ b/src/examples/slf_length_acc.c @@ -1,27 +1,27 @@ -#include "list.h" +#include "list/headers.h" #include "ref.h" #include "free.h" /*@ -function [rec] (u32) length(datatype seq xs) { +function [rec] (u32) length(datatype List xs) { match xs { - Seq_Nil {} => { + Nil {} => { 0u32 } - Seq_Cons {head : h, tail : zs} => { + Cons {Head: h, Tail: zs} => { 1u32 + length(zs) } } } @*/ -void IntList_length_acc_aux (struct int_list *xs, unsigned int *p) -/*@ requires take L1 = IntList(xs); - take n = Owned(p); - ensures take L1_ = IntList(xs); - take n_ = Owned(p); - L1 == L1_; - n_ == n + length(L1); +void IntList_length_acc_aux (struct sllist *xs, unsigned int *p) +/*@ requires take L1 = SLList_At(xs); + take P = Owned(p); + ensures take L1_post = SLList_At(xs); + take P_post = Owned(p); + L1 == L1_post; + P_post == P + length(L1); @*/ { /*@ unfold length(L1); @*/ @@ -32,11 +32,11 @@ void IntList_length_acc_aux (struct int_list *xs, unsigned int *p) } } -unsigned int IntList_length_acc (struct int_list *xs) -/*@ requires take L1 = IntList(xs); - ensures take L1_ = IntList(xs); - L1 == L1_; - return == length(L1); +unsigned int IntList_length_acc (struct sllist *xs) +/*@ requires take Xs = SLList_At(xs); + ensures take Xs_post = SLList_At(xs); + Xs == Xs_post; + return == length(Xs); @*/ { unsigned int *p = refUnsignedInt(0); diff --git a/src/examples/slf_quadruple_mem.c b/src/examples/slf_quadruple_mem.c index 0c7e647b..c7a86dc3 100644 --- a/src/examples/slf_quadruple_mem.c +++ b/src/examples/slf_quadruple_mem.c @@ -1,11 +1,11 @@ int quadruple_mem (int *p) /* --BEGIN-- */ -/*@ requires take n = Owned(p); - let n_ = (i64) n; - (i64)MINi32() <= n_ * 4i64; n_ * 4i64 <= (i64)MAXi32(); - ensures take n2 = Owned(p); - n2 == n; - return == 4i32 * n; +/*@ requires take P = Owned(p); + let P64 = (i64) P; + (i64)MINi32() <= P64 * 4i64; P64 * 4i64 <= (i64)MAXi32(); + ensures take P_post = Owned(p); + P_post == P; + return == 4i32 * P; @*/ /* --END-- */ { diff --git a/src/examples/slf_ref_greater.c b/src/examples/slf_ref_greater.c index 23c54c9c..96138e91 100644 --- a/src/examples/slf_ref_greater.c +++ b/src/examples/slf_ref_greater.c @@ -2,12 +2,12 @@ unsigned int *ref_greater_abstract (unsigned int *p) /* --BEGIN-- */ -/*@ requires take m1 = Owned(p); - m1 < 4294967295u32; - ensures take m2 = Owned(p); - take n2 = Owned(return); - m1 == m2; - m1 <= n2; +/*@ requires take P = Owned(p); + P < 4294967295u32; + ensures take P_post = Owned(p); + take R = Owned(return); + P == P_post; + P <= R; @*/ /* --END-- */ { diff --git a/src/examples/slf_sized_stack.c b/src/examples/slf_sized_stack.c index a258057d..929d6bef 100644 --- a/src/examples/slf_sized_stack.c +++ b/src/examples/slf_sized_stack.c @@ -1,119 +1,124 @@ -#include "list.h" -#include "list_length.c" +#include "list/headers.h" +#include "list/length.c" -struct sized_stack { +struct sized_stack +{ unsigned int size; - struct int_list* data; + struct sllist *data; }; /*@ -type_synonym sizeAndData = {u32 s, datatype seq d} +type_synonym SizedStack = {u32 Size, datatype List Data} -predicate (sizeAndData) SizedStack(pointer p) { - take S = Owned(p); - let s = S.size; - take d = IntList(S.data); - assert(s == length(d)); - return { s: s, d: d }; +predicate (SizedStack) SizedStack_At (pointer p) { + take P = Owned(p); + take D = SLList_At(P.data); + assert(P.size == Length(D)); + return { Size: P.size, Data: D }; } @*/ -extern struct sized_stack *malloc_sized_stack (); +extern struct sized_stack *malloc__sized_stack(); /*@ -spec malloc_sized_stack(); +spec malloc__sized_stack(); requires true; - ensures take u = Block(return); + ensures take R = Block(return); @*/ -extern void *free_sized_stack (struct sized_stack *p); +extern void *free__sized_stack(struct sized_stack *s); /*@ -spec free_sized_stack(pointer p); - requires take u = Block(p); +spec free__sized_stack(pointer s); + requires take R = Block(s); ensures true; @*/ -struct sized_stack* create() -/*@ ensures take S = SizedStack(return); - S.s == 0u32; +struct sized_stack *create() +/*@ ensures take R = SizedStack_At(return); + R.Size == 0u32; @*/ { - struct sized_stack *p = malloc_sized_stack(); - p->size = 0; - p->data = 0; - /*@ unfold length(Seq_Nil {}); @*/ - return p; + struct sized_stack *s = malloc__sized_stack(); + s->size = 0; + s->data = 0; + /*@ unfold Length(Nil {}); @*/ + return s; } -unsigned int sizeOf (struct sized_stack *p) +unsigned int sizeOf(struct sized_stack *s) /* FILL IN HERE */ /* ---BEGIN--- */ -/*@ requires take S = SizedStack(p); - ensures take S_ = SizedStack(p); - S_ == S; - return == S.s; +/*@ requires take S = SizedStack_At(s); + ensures take S_post = SizedStack_At(s); + S_post == S; + return == S.Size; @*/ /* ---END--- */ { - return p->size; + return s->size; } -void push (struct sized_stack *p, int x) +void push(struct sized_stack *s, int x) /* FILL IN HERE */ /* ---BEGIN--- */ -/*@ requires take S = SizedStack(p); - ensures take S_ = SizedStack(p); - S_.d == Seq_Cons {head:x, tail:S.d}; +/*@ requires take S = SizedStack_At(s); + ensures take S_post = SizedStack_At(s); + S_post.Data == Cons {Head:x, Tail:S.Data}; @*/ /* ---END--- */ { - struct int_list *data = IntList_cons(x, p->data); - p->size++; - p->data = data; -/* ---BEGIN--- */ - /*@ unfold length (Seq_Cons {head: x, tail: S.d}); @*/ -/* ---END--- */ + struct sllist *data = slcons(x, s->data); + s->size++; + s->data = data; + /* ---BEGIN--- */ + /*@ unfold Length (Cons {Head: x, Tail: S.Data}); @*/ + /* ---END--- */ } -int pop (struct sized_stack *p) +int pop(struct sized_stack *s) /* FILL IN HERE */ /* ---BEGIN--- */ -/*@ requires take S = SizedStack(p); - S.s > 0u32; - ensures take S_ = SizedStack(p); - S_.d == tl(S.d); +/*@ requires take S = SizedStack_At(s); + S.Size > 0u32; + ensures take S_post = SizedStack_At(s); + S_post.Data == Tl(S.Data); + return == Hd(S.Data); @*/ /* ---END--- */ { - struct int_list *data = p->data; - if (data != 0) { + struct sllist *data = s->data; + /* ---BEGIN--- */ + /*@ unfold Length(S.Data); @*/ + // from S.Size > 0u32 it follows that the 'else' branch is impossible + /* ---END--- */ + if (data != 0) + { int head = data->head; - struct int_list *tail = data->tail; - freeIntList(data); - p->data = tail; - p->size--; -/* ---BEGIN--- */ - /*@ unfold length(S.d); @*/ -/* ---END--- */ + struct sllist *tail = data->tail; + free__sllist(data); + s->data = tail; + s->size--; return head; } return 42; } -int top (struct sized_stack *p) -/*@ requires take S = SizedStack(p); - S.s > 0u32; - ensures take S_ = SizedStack(p); - S_ == S; - return == hd(S.d); +int top(struct sized_stack *s) +/*@ requires take S = SizedStack_At(s); + S.Size > 0u32; + ensures take S_post = SizedStack_At(s); + S_post == S; + return == Hd(S.Data); @*/ { - /*@ unfold length(S.d); @*/ - // from S.s > 0u32 it follows that the 'else' branch is impossible - if (p->data != 0) { - return (p->data)->head; + /*@ unfold Length(S.Data); @*/ + // from S.Size > 0u32 it follows that the 'else' branch is impossible + if (s->data != 0) + { + return (s->data)->head; } - else { + else + { // provably dead code - return 42; + return 42; } } diff --git a/src/examples/swap.c b/src/examples/swap.c index 31a8c119..435ef1b0 100644 --- a/src/examples/swap.c +++ b/src/examples/swap.c @@ -1,10 +1,10 @@ void swap (unsigned int *p, unsigned int *q) /* --BEGIN-- */ -/*@ requires take v = Owned(p); - take w = Owned(q); - ensures take v2 = Owned(p); - take w2 = Owned(q); - v2 == w && w2 == v; +/*@ requires take P = Owned(p); + take Q = Owned(q); + ensures take P_post = Owned(p); + take Q_post = Owned(q); + P_post == Q && Q_post == P; @*/ /* --END-- */ { diff --git a/src/examples/transpose.broken.c b/src/examples/transpose.broken.c index 3bb2ca95..47ef9479 100644 --- a/src/examples/transpose.broken.c +++ b/src/examples/transpose.broken.c @@ -1,10 +1,10 @@ struct point { int x; int y; }; void transpose (struct point *p) -/*@ requires take s = Owned(p); - ensures take s2 = Owned(p); - s2.x == s.y; - s2.y == s.x; +/*@ requires take P = Owned(p); + ensures take P_post = Owned(p); + P_post.x == P.y; + P_post.y == P.x; @*/ { int temp_x = p->x; diff --git a/src/examples/transpose.c b/src/examples/transpose.c index 5bab82bf..03b1d9d8 100644 --- a/src/examples/transpose.c +++ b/src/examples/transpose.c @@ -1,10 +1,10 @@ struct point { int x; int y; }; void transpose (struct point *p) -/*@ requires take s = Owned(p); - ensures take s2 = Owned(p); - s2.x == s.y; - s2.y == s.x; +/*@ requires take P = Owned(p); + ensures take P_post = Owned(p); + P_post.x == P.y; + P_post.y == P.x; @*/ { int temp_x = p->x; diff --git a/src/examples/transpose2.c b/src/examples/transpose2.c index 6a903ad3..31b390fd 100644 --- a/src/examples/transpose2.c +++ b/src/examples/transpose2.c @@ -1,9 +1,9 @@ void swap (unsigned int *p, unsigned int *q) -/*@ requires take v = Owned(p); - take w = Owned(q); - ensures take v2 = Owned(p); - take w2 = Owned(q); - v2 == w && w2 == v; +/*@ requires take P = Owned(p); + take Q = Owned(q); + ensures take P_post = Owned(p); + take Q_post = Owned(q); + P_post == Q && Q_post == P; @*/ { unsigned int m = *p; @@ -16,12 +16,12 @@ struct upoint { unsigned int x; unsigned int y; }; void transpose2 (struct upoint *p) /* --BEGIN-- */ -/*@ requires take s = Owned(p); - ensures take s2 = Owned(p); - s2.x == s.y; - s2.y == s.x; +/*@ requires take P = Owned(p); + ensures take P_post = Owned(p); + P_post.x == P.y; + P_post.y == P.x; @*/ +/* --END-- */ { swap(&p->x, &p->y); } -/* --END-- */ diff --git a/src/examples/zero.c b/src/examples/zero.c index e6ac6cdd..c7fbf8c7 100644 --- a/src/examples/zero.c +++ b/src/examples/zero.c @@ -1,8 +1,8 @@ void zero (int *p) /* --BEGIN-- */ -/*@ requires take u = Block(p); - ensures take v = Owned(p); - v == 0i32; +/*@ requires take P = Block(p); + ensures take P_post = Owned(p); + P_post == 0i32; @*/ /* --END-- */ { diff --git a/src/queue-example-notes.md b/src/queue-example-notes.md deleted file mode 100644 index 09eb24c9..00000000 --- a/src/queue-example-notes.md +++ /dev/null @@ -1,222 +0,0 @@ -2# Notes - -- Bad definition of snoc (same as rev). How to spot? Look at constraint context, specifically snoc(listQ, x) == match listQ {Seq\_Nil {} => {Seq\_Nil {}}Seq\_Cons {head: h, tail: zs} => {snoc(rev(zs), h)}}. Other big clue: applying lemma snoc_nil results in an inconsistent context. This is really nasty because snoc(Seq_Nil{}, x) ends up reducing to itself. - -- Code used q->tail == 0 but predicate was testing q->head. Can adjust predicate, code, or use a split_case. - -- Under-constrained counter-examples are something to be aware of (though the inconsitency came because of the definition of snoc here rather than l here). - -d99e65ed01c7a35408b0e409af3f17ece25bc0bf is the tutorial commit (with the correct definition of snoc, but the point at which you originally asked for help) - -More notes: - - path explosion means you can't look at path to error in HTML output - - it can help to move the return statement (manually explode!) - to see which path is failing - - (should be able to annotate after a conditional to pull things - back together) - - conclude that l is not properly constrained - because the SMT solver is making crazy choices - -# -------------------------------------------------------------------------- -# Original version - -Here's the predicate for queues: - - predicate (datatype seq) IntQueue(pointer q) { - take H = Owned(q); - take Q = IntQueue1(q,H); - return Q; - } - - predicate (datatype seq) IntQueue1(pointer dummy, struct int_queue H) { - if (is_null(H.head)) { - assert (is_null(H.tail)); - return (Seq_Nil{}); - } else { - assert (!is_null(H.tail)); - take Q = IntQueueAux (H.head, H.tail); - return Q; - } - } - - predicate (datatype seq) IntQueueAux(pointer h, pointer t) { - take C = Owned(h); - take L = IntQueueAux1(h, C, t); - return L; - } - - predicate (datatype seq) IntQueueAux1 - (pointer h, struct int_queueCell C, pointer t) { - if (is_null(C.next)) { - assert (h == t); - return (Seq_Cons{head: C.first, tail: Seq_Nil{}}); - } else { - take TL = IntQueueAux(C.next, t); - return (Seq_Cons { head: C.first, tail: TL }); - } - } - -And here's the push operation. - - void IntQueue_push (int x, struct int_queue *q) - /*@ requires take l = IntQueue(q); - ensures take ret = IntQueue(q); - ret == snoc (l, x); - @*/ - { - struct int_queueCell *c = mallocIntQueueCell(); - c->first = x; - c->next = 0; - if (q->tail == 0) { - q->head = c; - q->tail = c; - } else { - q->tail->next = c; - q->tail = c; - } - } - -This fails because there are not enough annotations in the body of push. - -Confusingly, the HTML error report gives this as the unproven constraint - - Seq_Cons {head: x, tail: Seq_Nil {}} == snoc(l, x) - -while the list of Terms shows that - - Seq_Cons {head: x, tail: Seq_Nil {}} == snoc(l, x) - -has value false! - -I.e., something is very wrong. - -First, we have to find the path to the error. Either decode the HTML -or put in some returns in the branches of the if. This tells us that -the problem is in the first branch. - -We see - - temp-queue1b.broken.c:95:5: error: Unprovable constraint - return; - ^~~~~~~ - Constraint from temp-queue1b.broken.c:86:13: - ret == snoc (l, x); - ^~~~~~~~~~~~~~~~~~~ - -To make progress, we need to unfold snoc. How do we know this? -Because the constraint that is problematic involves a snoc, and snoc -is recursive, so we should expect to have to unfold at some point. -(Non-recursive things are always inlined, but recursive ones obviously -not, so even to look "one level deep" we need an unfold.) - -Once we've unfolded, we get some more hints: - - - Look at the value of l in Terms: Seq_Cons {head: 0i32, tail: Seq_Nil {}} - - But we are in the empty queue case, so this seems fishy. - - Now, in the constraints, we see l == unpack_IntQueue1.Q - - Then look at the resources and see that unpack_IntQueue1.Q has not - been unpacked in the final line: - IntQueue1(q, unpack_IntQueue1.H)(unpack_IntQueue1.Q) - - This means that CN did not have enough information to decide which - way the conditional at the beginning of IntQueue1 is going to go. - - But the condition is testing H.head, while the conditional in the - code is testing the tail field! - - We could get around this mismatch by adjusting the condition - itself, or by adjusting the predicate. E.g., we could change the - predicate to test *both* for null at the beginning, so that it - doesn't matter which one you test. - -This tells us to look at snoc, which turns out to be very wrong! - - function [rec] (datatype seq) snoc(datatype seq xs, i32 y) { - match xs { - Seq_Nil {} => { - Seq_Nil {} - } - Seq_Cons {head : h, tail : zs} => { - snoc (rev(zs), h) - } - } - } - - -# -------------------------------------------------------------------------- -# Next try - - void IntQueue_push (int x, struct int_queue *q) - /*@ requires take l = IntQueue(q); - ensures take ret = IntQueue(q); - ret == snoc (l, x); - @*/ - { - struct int_queueCell *c = mallocIntQueueCell(); - c->first = x; - c->next = 0; - if (q->tail == 0) { - /*@ split_case q->head == NULL; @*/ - /*@ apply snac_nil(x); @*/ - q->head = c; - q->tail = c; - } else { - q->tail->next = c; - q->tail = c; - } - } - -This time the error is: - - temp-queue2.broken.c:86:5: error: Missing resource for writing - q->tail->next = c; - ~~~~~~~~~~~~~~^~~ - Resource needed: Block(member_shift(unpack_IntQueue1 - .H - .tail, next)) - -This makes more sense. [But how to articulate the sense that it -makes??] - -# -------------------------------------------------------------------------- -# Getting closer - -We could fix this by rewriting the push function so that, instead of -following the tail pointer, it recurses down from the head until it -reaches the tail. This would work (might be a good exercise?), but it -nullifies the whole purpose of having the tail pointer in the first -place. - -Instead, we need to rearrange IntQueue and friends so that we take -ownership of the very last cell in the list at the very beginning, -instead of at the very end. - - predicate (datatype seq) IntQueue(pointer q) { - take H = Owned(q); - take Q = IntQueue1(q,H); - return Q; - } - - predicate (datatype seq) IntQueue1(pointer dummy, struct int_queue H) { - if (is_null(H.head)) { - assert (is_null(H.tail)); - return (Seq_Nil{}); - } else { - assert (!is_null(H.tail)); - take T = Owned(H.tail); - assert (is_null(T.next)); - take Q = IntQueueAux (H.head, H.tail, T.first); - return Q; - } - } - - predicate (datatype seq) IntQueueAux (pointer h, pointer t, i32 lastVal) { - if (h == t) { - return (Seq_Cons{head: lastVal, tail: Seq_Nil{}}); - } else { - take C = Owned(h); - take TL = IntQueueAux(C.next, t, lastVal); - return (Seq_Cons { head: C.first, tail: TL }); - } - } - -This matches the access pattern of the push implementation, and -it... works?? diff --git a/src/tutorial.adoc b/src/tutorial.adoc index e641a65c..fa7744b4 100644 --- a/src/tutorial.adoc +++ b/src/tutorial.adoc @@ -10,11 +10,13 @@ By Christopher Pulte and Benjamin C. Pierce, with contributions from Elizabeth A [abstract] -- -CN is a type system for verifying C code, focusing especially on low-level systems code. Compared to the normal C type system, CN checks not only that expressions and statements follow the correct typing discipline for C-types, but also that the C code executes _safely_ — does not raise C undefined behaviour — and _correctly_ — according to strong user-defined specifications. -// -This tutorial introduces CN along a series of examples, starting with basic usage of CN on simple arithmetic functions and slowly moving towards more elaborate separation logic specifications of data structures. +CN is an extension of the C programming language for verifying the correctness of C code, especially on low-level systems code. Compared to standard C, CN checks not only that expressions and statements follow the correct typing discipline for C-types, but also that the C code executes _safely_ — does not raise C undefined behaviour — and _correctly_ — satisfying to strong, user-defined specifications. +This tutorial introduces CN through a series of examples, starting with basic usage of CN on simple arithmetic functions and slowly moving towards more elaborate separation logic specifications of data structures. -CN was first described in https://dl.acm.org/doi/10.1145/3571194[CN: Verifying Systems C Code with Separation-Logic Refinement Types] by Christopher Pulte, Dhruv C. Makwana, Thomas Sewell, Kayvan Memarian, Peter Sewell, Neel Krishnaswami. +This tutorial is a work in progress -- your suggestions are greatly appreciated! + +**Origins.** +CN was first described in https://dl.acm.org/doi/10.1145/3571194[CN: Verifying Systems C Code with Separation-Logic Refinement Types] by Christopher Pulte, Dhruv C. Makwana, Thomas Sewell, Kayvan Memarian, Peter Sewell, and Neel Krishnaswami. // To accurately handle the complex semantics of C, CN builds on the https://github.com/rems-project/cerberus/[Cerberus semantics for C]. // @@ -23,55 +25,49 @@ https://softwarefoundations.cis.upenn.edu[Separation Logic Foundations] textbook, and one of the case studies is based on an extended exercise due to Bryan Parno. -This tutorial is a work in progress -- your suggestions are greatly appreciated! +-- **Acknowledgment of Support and Disclaimer.** This material is based upon work supported by the Air Force Research Laboratory (AFRL) and Defense Advanced Research Projects Agencies (DARPA) under Contract No. FA8750-24-C-B044, a European Research Council (ERC) Advanced Grant “ELVER” under the European Union’s Horizon 2020 research and innovation programme (grant agreement no. 789108), and additional funding from Google. The opinions, findings, and conclusions or recommendations expressed in this material are those of the authors and do not necessarily reflect the views of the Air Force Research Laboratory (AFRL). --- - == Installing CN -To fetch and install CN, check the Cerberus repository at https://github.com/rems-project/cerberus and follow the instructions in https://github.com/rems-project/cerberus/blob/master/backend/cn/README.md[backend/cn/README.md]. +To fetch and install CN, visit the Cerberus repository at https://github.com/rems-project/cerberus and follow the instructions in https://github.com/rems-project/cerberus/blob/master/backend/cn/README.md[backend/cn/README.md]. -Once completed, type `+cn --help+` in your terminal to ensure CN is installed and found by your system. This should print the list of available options CN can be executed with. +Once the installation is completed, type `+cn --help+` in your terminal to ensure CN is installed and found by your system. This should print the list of available options CN can be executed with. -To apply CN to a C file, run `+cn CFILE+`. +To apply CN to a C file, run `+cn verify CFILE+`. -== Source files for the exercises and examples +== Source files -The source files can be downloaded from link:exercises.zip[here]. +The source files for all the exercises and examples below can be downloaded +from link:exercises.zip[here]. -== Basic usage +== Basics === First example -For a first example, let’s look at a simple arithmetic function: `+add+`, shown below, takes two `+int+` arguments, `+x+` and `+y+`, and returns their sum. +The simple arithmetic function: `+add+` shown below takes two `+int+` arguments, `+x+` and `+y+`, and returns their sum. -// TODO: BCP: We should probably adopt the convention that all the files in -// the exercises directory have a comment at the top giving their name. -// (We could actually auto-generate those header comments when we process -// /src/examples into build/exercises, to avoid having to maintain them -// and possibly get them wrong...) include_example(exercises/add_0.c) Running CN on the example produces an error message: .... -cn exercises/add_0.c +cn verify exercises/add_0.c [1/1]: add exercises/add_0.c:3:10: error: Undefined behaviour return x+y; ~^~ an exceptional condition occurs during the evaluation of an expression (§6.5#5) -Consider the state in /var/folders/_v/ndl32wpj4bb3y9dg11rvc8ph0000gn/T/state_393431.html +Consider the state in /var/folders/_v/ndl32rvc8ph0000gn/T/state_393431.html .... -CN rejects the program because it has _C undefined behaviour_, meaning it is not safe to execute. CN points to the relevant source location, the addition `+x+y+`, and paragraph §6.5#5 of the C language standard that specifies the undefined behaviour. It also puts a link to an HTML file with more details on the error to help in diagnosing the problem. +CN rejects the program because it has _undefined behaviour_ according to the C standard, meaning it is not safe to execute. CN points to the relevant source location, the addition `+x+y+`, and paragraph §6.5#5 of the standard, which specifies the undefined behaviour. It also includes a link to an HTML file with more details on the error to help in diagnosing the problem. Inspecting this HTML report (as we do in a moment) gives us possible example values for `+x+` and `+y+` that cause the undefined behaviour and hint at the problem: for very large values for `+x+` and `+y+`, such as `+1073741825+` and `+1073741824+`, the sum of `+x+` and `+y+` can exceed the representable range of a C `+int+` value: `+1073741825 + 1073741824 = 2^31+1+`, so their sum is larger than the maximal `+int+` value, `+2^31-1+`. -Here `+x+` and `+y+` are _signed integers_, and in C, signed integer _overflow_ is undefined behaviour (UB). Hence, `+add+` is only safe to execute for smaller values. Similarly, _large negative_ values of `+x+` and `+y+` can cause signed integer _underflow_, also UB in C. We therefore need to rule out too large values for `+x+` and `+y+`, both positive and negative, which we do by writing a CN function specification. +Here `+x+` and `+y+` are _signed integers_, and in C, signed integer _overflow_ is undefined behaviour (UB). Hence, `+add+` is only safe to execute for smaller values. Similarly, _large negative_ values of `+x+` and `+y+` can cause signed integer _underflow_, also UB in C. We therefore need to rule out too-large values for `+x+` and `+y+`, both positive and negative, which we do by writing a CN function specification. === First function specification @@ -89,22 +85,28 @@ In detail: * In function specifications, the names of the function arguments, here `+x+` and `+y+`, refer to their _initial values_. (Function arguments are mutable in C.) -* `+let sum = (i64) x + (i64) y+` is a let-binding, which defines `+sum+` as the value `+(i64) x + (i64) y+` in the remainder of the function specification. +* `+let Sum = (i64) x + (i64) y+` is a let-binding, which defines `+Sum+` as the value `+(i64) x + (i64) y+` in the remainder of the function specification. -* Instead of C syntax, CN uses Rust-like syntax for integer types, such as `+u32+` for 32-bit unsigned integers and `+i64+` for signed 64-bit integers to make their sizes unambiguous. Here, `+x+` and `+y+`, of C-type `+int+`, have CN type `+i32+`. +* Instead of C syntax, CN uses Rust-like syntax for integer types, such as `+u32+` for 32-bit unsigned integers and `+i64+` for signed 64-bit integers, to make their sizes unambiguous. Here, `+x+` and `+y+`, of C-type `+int+`, have CN type `+i32+`. // TODO: BCP: I understand this reasoning, but I wonder whether it introduces more confusion than it avoids -- it means there are two ways of writing everything, and people have to remember whether the particular thing they are writing right now is C or CN... +// BCP: Hopefully we are moving toward unifying these notations anyway? -* To define `+sum+` we cast `+x+` and `+y+` to the larger `+i64+` type, using syntax `+(i64)+`, which is large enough to hold the sum of any two `+i32+` values. +* To define `+Sum+` we cast `+x+` and `+y+` to the larger `+i64+` type, using syntax `+(i64)+`, which is large enough to hold the sum of any two `+i32+` values. -* Finally, we require this sum to be in-between the minimal and maximal `+int+` value. Integer constants, such as `+-2147483648i64+`, must specifiy their CN type (`+i64+`). +* Finally, we require this sum to be between the minimal and maximal `+int+` values. Integer constants, such as `+-2147483648i64+`, must specifiy their CN type (`+i64+`). +// TODO: BCP: We should use the new ' syntax (or whatever it turned out to be) for numeric constants +// Dhruv: Yet to be implemented: rems-project/cerberus#337 -Running CN on the annotated program passes without errors. This means with our specified precondition, `+add+` is safe to execute. +Running CN on the annotated program passes without errors. This means that, with our specified precondition, `+add+` is safe to execute. -We may, however, wish to be more precise. So far the specification gives no information to callers of `+add+` about its output. To also specify the return values we add a postcondition, using the `+ensures+` keyword. +We may, however, wish to be more precise. So far, the specification gives no information to callers of `+add+` about its output. To describe its return value we add a postcondition to the specification using the `+ensures+` keyword. include_example(solutions/add_1.c) -Here we use the keyword `+return+`, only available in function postconditions, to refer to the return value, and equate it to `+sum+` as defined in the preconditions, cast back to `+i32+` type: `+add+` returns the sum of `+x+` and `+y+`. +Here we use the keyword `+return+`, which is only available in function +postconditions, to refer to the return value, and we equate it to `+Sum+` +as defined in the precondition, cast back to `+i32+` type: that is, `+add+` +returns the sum of `+x+` and `+y+`. Running CN confirms that this postcondition also holds. @@ -112,77 +114,136 @@ One final refinement of this example. CN defines constant functions `MINi32`, ` include_example(solutions/add_2.c) -Two things to note: - * These are constant _functions_, so they require a following `()`. - * The type of `MINi32()` is `i32`, so if we want to use it as a 64-bit constant - we need to add the explicit coercion `(i64)`. +Two things to note: (1) These are constant _functions_, so they +require a following `()`. And (2) The type of `MINi32()` is `i32`, so +if we want to use it as a 64-bit constant we need to add the explicit +coercion `(i64)`. === Error reports -In the original example CN reported a type error due to C undefined behaviour. While that example was perhaps simple enough to guess the problem and solution, this can become quite challenging as program and specification complexity increases. Diagnosing type errors is therefore an important part of using CN. CN tries to help with that by producing detailed error information, in the form of an HTML error report. +In the original example, CN reported a type error due to C undefined +behaviour. While that example was perhaps simple enough to guess the +problem and solution, this can become quite challenging as program and +specification complexity increases. Diagnosing errors is +therefore an important part of using CN. CN tries to help with this by +producing detailed error information, in the form of an HTML error +report. -Let’s return to the type error from earlier (`+add+` without precondition) and take a closer look at this report. The report comprises two sections. +Let’s return to the type error from earlier -- `+add+` without +precondition -- and take a closer look at this report. It +comprises two sections: -// TODO: BCP: It looks different now! +// TODO: BCP: It looks quite different now! .*CN error report* image::images/0.error.png[*CN error report*] -*Path.* The first section, "`Path to error`", contains information about the control-flow path leading to the error. - -When type checking a C function, CN checks each possible control-flow path through the program individually. If CN detects UB or a violation of user-defined specifications, CN reports the problematic control-flow path, as a nested structure of statements: paths are split into sections, which group together statements between high-level control-flow positions (e.g. function entry, the start of a loop, the invocation of a `+continue+`, `+break+`, or `+return+` statement, etc.); within each section, statements are listed by source code location; finally, per statement, CN lists the typechecked sub-expressions, and the memory accesses and function calls within these. - -In our example, there is only one possible control-flow path: entering the function body (section "`function body`") and executing the block from lines 2 to 4, followed by the return statement at line 3. The entry for the latter contains the sequence of sub-expressions in the return statement, including reads of the variables `+x+` and `+y+`. - -In C, local variables in a function, including its arguments, are mutable and their address can be taken and passed as a value. CN therefore represents local variables as memory allocations that are manipulated using memory reads and writes. Here, type checking the return statement includes checking memory reads for `+x+` and `+y+`, at their locations `+&ARG0+` and `+&ARG1+`. The path report lists these reads and their return values: the read at `+&ARG0+` returns `+x+` (that is, the value of `+x+` originally passed to `+add+`); the read at `+&ARG1+` returns `+y+`. Alongside this symbolic information, CN displays concrete values: +*Path to error.* The first section contains information about the +control-flow path leading to the error. + +When checking a C function, CN examines each possible control-flow +path individually. If it detects UB or a violation of user-defined +specifications, CN reports the problematic control-flow path as a +nested structure of statements: the path is split into sections that +group together statements between high-level control-flow positions +(e.g. function entry, the start of a loop, the invocation of a +`+continue+`, `+break+`, or `+return+` statement, etc.); within each +section, statements are listed by source code location; finally, per +statement, CN lists the typechecked sub-expressions, and the memory +accesses and function calls within these. + +In our example, there is only one possible control-flow path: entering +the function body (section "`function body`") and executing the block +from lines 2 to 4, followed by the return statement at line 3. The +entry for the latter contains the sequence of sub-expressions in the +return statement, including reads of the variables `+x+` and `+y+`. + +In C, local variables in a function, including its arguments, are +mutable, their addresses can be taken and passed as values. CN +therefore represents local variables as memory allocations that are +manipulated using memory reads and writes. Here, type checking the +return statement includes checking memory reads for `+x+` and `+y+`, +at their locations `+&ARG0+` and `+&ARG1+`. The path report lists +these reads and their return values: the read at `+&ARG0+` returns +`+x+` (that is, the value of `+x+` originally passed to `+add+`); the +read at `+&ARG1+` returns `+y+`. Alongside this symbolic information, +CN displays concrete values: * `+1073741825i32 /* 0x40000001 */+` for x (the first value is the decimal representation, the second, in `+/*...*/+` comments, the hex equivalent) and * `+1073741824i32 /* 0x40000000 */+` for `+y+`. For now, ignore the pointer values `+{@0; 4}+` for `+x+` and `+{@0; 0}+` for `+y+`. +// TODO: BCP: Where are these things discussed? Anywhere? (When) are they useful? +// Dhruv: These are part of VIP memory model things I'm working on, which will hopefully be implemented and enabled in the next few weeks. -These concrete values are part of a _counterexample_: a concrete valuation of variables and pointers in the program that that leads to the error. (The exact values may vary on your machine, depending on the version of Z3 installed on your system.) +These concrete values are part of a _counterexample_: a concrete +valuation of variables and pointers in the program that that leads to +the error. (The exact values may vary on your machine, depending on +the SMT solver -- i.e., the particular version of Z3, CVC5, or +whatever installed on your system.) *Proof context.* The second section, below the error trace, lists the proof context CN has reached along this control-flow path. "`Available resources`" lists the owned resources, as discussed in later sections. "`Variables`" lists counterexample values for program variables and pointers. In addition to `+x+` and `+y+`, assigned the same values as above, this includes values for their memory locations `+&ARG0+` and `+&ARG1+`, function pointers in scope, and the `+__cn_alloc_history+`, all of which we ignore for now. - -Finally, "`Constraints`" records all logical facts CN has learned along the path. This includes user-specified assumptions from preconditions or loop invariants, value ranges inferred from the C-types of variables, and facts learned during the type checking of the statements. Here (`+add+` without precondition) the only constraints are some contraints inferred from C-types in the code. - -* For instance, `+good(x)+` says that the initial value of `+x+` is a "`good`" `+signed int+` value (i.e. in range). Here `+signed int+` is the same type as `+int+`, CN just makes the sign explicit. For integer types `+T+`, `+good+` requires the value to be in range of type `+T+`; for pointer types `+T+` it also requires the pointer to be aligned. For structs and arrays this extends in the obvious way to struct members or array cells. +// TODO: BCP: Again, where are these things discussed? Should they be? +// Dhruv: Also VIP. + +Finally, "`Constraints`" records all logical facts CN has learned along the path. This includes user-specified assumptions from preconditions or loop invariants, value ranges inferred from the C-types of variables, and facts learned during the type checking of the statements. Here -- when checking `+add+` without precondition -- the only constraints are those inferred from C-types in the code: + +* For instance, `+good(x)+` says that the initial value of +`+x+` is a "`good`" `+signed int+` value (i.e. in range). Here +`+signed int+` is the same type as `+int+`, CN just makes the sign +explicit. +// TODO: BCP: Yikes! This seems potentially confusing +For an integer type `+T+`, the type `+good+` requires the value to +be in range of type `+T+`; for pointer types `+T+`, it also requires +the pointer to be aligned. For structs and arrays, this extends in the +obvious way to struct members or array cells. +// TODO: BCP: Is this information actually ever useful? Is it currently suppressed? * `+repr+` requires representability (not alignment) at type `+T+`, so `+repr(&ARGO)+`, for instance, records that the pointer to `+x+` is representable at C-type `+signed int*+`; * `+aligned(&ARGO, 4u64)+`, moreover, states that it is 4-byte aligned. +// TODO: URGENT: BCP: Some of the above (especially the bit at the end) feels like TMI for many/most users, especially at this point in the tutorial. +// Dhruv: Probably true, we actually even hide some of these by default. +// BCP: I propose we hide the rest and move this discussion to somewhere else ("Gory Details" section of the tutorial, or better yet reference manual). +// Dhruv: Thumbs up + === Another arithmetic example Let’s apply what we know so far to another simple arithmetic example. The function `+doubled+`, shown below, takes an int `+n+`, defines `+a+` as `+n+` incremented, `+b+` as `+n+` decremented, and returns the sum of the two. -// TODO: BCP: Is it important to number the slf examples? If so, we should do it consistently, but IMO it is not. +// TODO: BCP: Is it important to number the slf examples? If so, we should do it consistently, but IMO it is not. Better to rename them without numbers. include_example(exercises/slf1_basic_example_let.signed.c) We would like to verify this is safe, and that `+doubled+` returns twice the value of `+n+`. Running CN on `+doubled+` leads to a type error: the increment of `+a+` has undefined behaviour. -As in the first example, we need to ensure that `+n+1+` does not overflow and `+n-1+` does not underflow. Similarly also `+a+b+` has to be representable at `+int+` type. +As in the first example, we need to ensure that `+n+1+` does not overflow and `+n-1+` does not underflow. Similarly `+a+b+` has to be representable at `+int+` type. include_example(solutions/slf1_basic_example_let.signed.c) +// TODO: BCP: WHy n_+n_ in some places and n*2i32 in others? +// Dhruv: Unlikely to be meaningful, either is fine. -We can specify these using a similar style of precondition as in the first example. We first define `+n_+` as `+n+` cast to type `+i64+` — i.e. a type large enough to hold `+n+1+`, `+n-1+` and `+a+b+` for any possible `+i32+` value for `+n+`. Then we specify that decrementing `+n_+` does not go below the minimal `+int+` value, that incrementing `+n_+` does not go above the maximal value, and that `+n+` doubled is also in range. These preconditions together guarantee safe execution. +We encode these expectations using a similar style of precondition as in the first example. We first define `+N+` as `+n+` cast to type `+i64+` — i.e. a type large enough to hold `+n+1+`, `+n-1+`, and `+a+b+` for any possible `+i32+` value for `+n+`. Then we specify that decrementing `+N+` does not go below the minimal `+int+` value, that incrementing `+N+` does not go above the maximal value, and that `+n+` doubled is also in range. These preconditions together guarantee safe execution. +// TODO: BCP: How about renaming N to n64? +// Dhruv: Sensible. +// BCP: (someone do it on next pass) To capture the functional behaviour, the postcondition specifies that `+return+` is twice the value of `+n+`. -=== Exercise +=== Exercises *Quadruple.* Specify the precondition needed to ensure safety of the C function `+quadruple+`, and a postcondition that describes its return value. include_example(exercises/slf2_basic_quadruple.signed.c) *Abs.* Give a specification to the C function `+abs+`, which computes the absolute value of a given `+int+` value. To describe the return value, use CN’s ternary "`+_ ? _ : _+`" operator. Given a boolean `+b+`, and expressions `+e1+` and `+e2+` of the same basetype, `+b ? e1 : e2+` returns `+e1+` if `+b+` holds and `+e2+` otherwise. +Note that most binary operators in CN have higher precedence than the ternary operator, so depending on your solution you may need to place the ternary expression in parentheses. include_example(exercises/abs.c) @@ -190,7 +251,7 @@ include_example(exercises/abs.c) So far we’ve only considered example functions manipulating integer values. Verification becomes more interesting and challenging when _pointers_ are involved, because the safety of memory accesses via pointers has to be verified. -CN uses _separation logic resource types_ and the concept of _ownership_ to reason about memory accesses. A resource is the permission to access a region of memory. Unlike logical constraints, resource ownership is _unique_, meaning resources cannot be duplicated. +CN uses _separation logic resources_ and the concept of _ownership_ to reason about memory accesses. A resource is the permission to access a region of memory. Unlike logical constraints, resource ownership is _unique_, meaning resources cannot be duplicated. Let’s look at a simple example. The function `+read+` takes an `+int+` pointer `+p+` and returns the pointee value. @@ -199,7 +260,7 @@ include_example(exercises/read.c) Running CN on this example produces the following error: .... -cn exercises/read.c +cn verify exercises/read.c [1/1]: read exercises/read.c:3:10: error: Missing resource for reading return *p; @@ -210,15 +271,28 @@ Consider the state in /var/folders/_v/ndl32wpj4bb3y9dg11rvc8ph0000gn/T/state_403 For the read `+*p+` to be safe, ownership of a resource is missing: a resource `+Owned(p)+`. -=== The Owned resource type +=== Owned resources + +// TODO: BCP: Perhaps this is a good time for one last discussion of the keyword "Owned", which I have never found very helpful: the resource itself isn't owned -- it's a description of something that *can* be owned. (It's "take" that does the owning.) Moreover, "Owned" and "Block" are badly non-parallel, both grammatically and semantically. I suggest "Resource" instead of "Owned". (We can keep "Block" -- it's not too bad, IMO.) +//// +Dhruv: +We use the word "resources" to describe any "resource predicate" owned, or user-defined, (and eventually live allocations and locks) so I'm not sure that suggestion works any better. It is just a points-to with read and write permissions, so perhaps a RW(p)? (or ReadWrite(p)?). + +@bcpierce00 +Both of these are better than Owned! + +(And then Block can become WriteOnly.) + +BCP: I think this discussion is reflected in the GitHub exchange +//// Given a C-type `+T+` and pointer `+p+`, the resource `+Owned(p)+` asserts ownership of a memory cell at location `+p+` of the size of C-type `+T+`. It is CN’s equivalent of a points-to assertion in separation logic (indexed by C-types `+T+`). -In this example we can ensure the safe execution of `+read+` by adding a precondition that requires ownership of `+Owned(p)+`, as shown below. For now ignore the notation `+take ... = Owned(p)+`. Since `+read+` maintains this ownership, we also add a corresponding postcondition, whereby `+read+` returns ownership of `+p+` after it is finished executing, in the form of another `+Owned(p)+` resource. +In this example we can ensure the safe execution of `+read+` by adding a precondition that requires ownership of `+Owned(p)+`, as shown below. For now ignore the notation `+take ... = Owned(p)+`. Since reading the pointer does not disturb its value, we also add a corresponding postcondition, whereby `+read+` returns ownership of `+p+` after it is finished executing, in the form of another `+Owned(p)+` resource. include_example(solutions/read.c) -This specifications means that +This specification means that: * any function calling `+read+` has to be able to provide a resource `+Owned(p)+` to pass into `+read+`, and @@ -226,39 +300,42 @@ This specifications means that === Resource outputs -However, a caller of `+read+` may also wish to know that `+read+` actually returns the correct value, the pointee of `+p+`, and also that it does not change memory at location `+p+`. To phrase both we need a way to refer to the pointee of `+p+`. +A caller of `+read+` may also wish to know that `+read+` actually returns the correct value, the pointee of `+p+`, and also that it does not change memory at location `+p+`. To phrase both we need a way to refer to the pointee of `+p+`. -In CN resources have _outputs_. Each resource outputs the information that can be derived from ownership of the resource. What information is returned is specific to the type of resource. A resource `+Owned(p)+` (for some C-type `+T+`) outputs the _pointee value_ of `+p+`, since that can be derived from the resource ownership: assume you have a pointer `+p+` and the associated ownership, then this uniquely determines the pointee value of `+p+`. +// TODO: BCP: The idea that "resources have outputs" is very mind-boggling to many new users, *especially* folks with some separation logic background. Needs to be explained very carefully. Also, there's some semantic muddle in the terminology: Is a resource (1) a thing in the heap, (2) a thing in the heap that one is currently holding, or (3) the act of holding a thing in the heap? These are definitely not at all the same thing, but our language at different points suggests all three! To me, (1) is the natural sense of the word "resource"; (2) is somewhat awkward, and (3) is extremely awkward. -CN uses the `+take+`-notation seen in the example above to refer to the output of a resource, introducing a new name binding for the output. The precondition `+take v1 = Owned(p)+` from the precondition does two things: (1) it assert ownership of resource `+Owned(p)+`, and (2) it binds the name `+v1+` to the resource output, here the pointee value of `+p+` at the start of the function. Similarly, the postcondition introduces the name `+v2+` for the pointee value on function return. +In CN, resources have _outputs_. Each resource outputs the information that can be derived from ownership of the resource. What information is returned is specific to the type of resource. A resource `+Owned(p)+` (for some C-type `+T+`) outputs the _pointee value_ of `+p+`, since that can be derived from the resource ownership: assume you have a pointer `+p+` and the associated ownership, then this uniquely determines the pointee value of `+p+`. +// TODO: BCP: ... in a given heap! (The real problem here is that "and the associated ownership" is pretty vague.) +// Dhruv: Perhaps mentioning sub-heaps will help? -That means we can use the resource outputs from the pre- and postcondition to strengthen the specification of `+read+` as planned. We add two new postconditions: we specify +CN uses the `+take+`-notation seen in the example above to bind the output of a resource to a new name. The precondition `+take P = Owned(p)+` does two things: (1) it assert ownership of resource `+Owned(p)+`, and (2) it binds the name `+P+` to the resource output, here the pointee value of `+p+` at the start of the function. Similarly, the postcondition introduces the name `+P_post+` for the pointee value on function return. -. that `+read+` returns `+v1+` (the initial pointee value of `+p+`), and -. that the pointee values `+v1+` and `+v2+` before and after execution of `+read+` (respectively) are the same. +// TODO: BCP: But, as we've discussed, the word "take" in the postcondition is quite confusing: What it's doing is precisely the *opposite* of "taking" the resournce, not taking it but giving it back!! It would be much better if we could choose a more neutral word that doesn't imply either taking or giving. E.g. "resource". -include_example(solutions/read2.c) +// TODO: BCP: This might be a good place for a comment on naming conventions. Plus a pointer to a longer discussion if needed -*Aside.* In standard separation logic the equivalent specification for `+read+` could have been phrased as follows (where `+return+` binds the return value in the postcondition): +That means we can use the resource outputs from the pre- and postcondition to strengthen the specification of `+read+` as planned. We add two new postconditions specifying -.... -∀p. -∀v1. { p ↦ v1 } - read(p) - { return. ∃v2. (p ↦ v2) /\ return = v1 /\ v1 = v2 } -.... +. that `+read+` returns `+P+` (the initial pointee value of `+p+`), and +. that the pointee values `+P+` and `+P_post+` before and after execution of `+read+` (respectively) are the same. -CN’s `+take+` notation is just alternative syntax for quantification over the values of resources, but a useful one: the `+take+` notation syntactically restricts how these quantifiers can be used to ensure CN can always infer them. +include_example(exercises/read2.c) -=== Exercises +*Aside.* In standard separation logic, the equivalent specification for `+read+` could have been phrased as follows (where `+\return+` binds the return value in the postcondition): -*Quadruple*. Specify the function `+quadruple_mem+`, that is similar to the earlier `+quadruple+` function, except that the input is passed as an `+int+` pointer. Write a specification that takes ownership of this pointer on entry and returns this ownership on exit, leaving the pointee value unchanged. +// TODO: Sainati: as a separation logic noob, I would love a more detailed explanation about what is going on here. -include_example(exercises/slf_quadruple_mem.c) +// Why do we need to have v2 existentially quantified, for example, when p is only pointing to a single value? -*Abs*. Give a specification to the function `+abs_mem+`, which computes the absolute value of a number passed as an `+int+` pointer. +.... +∀p. + ∀v1. + { p ↦ P } + read(p) + { \return. ∃P_post. (p ↦ P_post) /\ return = P /\ P = P_post } +.... -include_example(exercises/abs_mem.c) +CN’s `+take+` notation is just an alternative syntax for quantification over the values of resources, but a useful one: the `+take+` notation syntactically restricts how these quantifiers can be used to ensure CN can always infer them. === Linear resource ownership @@ -271,45 +348,89 @@ include_example(exercises/read.broken.c) CN rejects this program with the following message: .... -cn build/exercises/read.broken.c +cn verify exercises/read.broken.c [1/1]: read -build/exercises/read.broken.c:4:3: error: Left-over unused resource 'Owned(p)(v1)' +build/exercises/read.broken.c:4:3: error: Left_Sublist-over unused resource 'Owned(p)(v1)' return *p; ^~~~~~~~~~ Consider the state in /var/folders/_v/ndl32wpj4bb3y9dg11rvc8ph0000gn/T/state_17eb4a.html .... -CN has typechecked the function, verified that it is safe to execute under the precondition (given ownership `+Owned(p)+`), and that the function (vacuously) satisfies its postcondition. But, following the check of the postcondition it finds that not all resources have been "`used up`". +CN has typechecked the function and verified (1) that it is safe to +execute under the precondition (given ownership `+Owned(p)+`) +and (2) that the function (vacuously) satisfies its postcondition. But +following the check of the postcondition it finds that not all +resources have been "`used up`". + +Indeed, given the above specification, `+read+` leaks memory: it takes ownership, does not return it, but also does not deallocate the owned memory or otherwise dispose of it. In CN this is a type error. -Given the above specification, `+read+` leaks memory: it takes ownership, does not return it, but also does not deallocate the owned memory or otherwise dispose of it. In CN this is a type error. +CN’s resources are _linear_. This means not only that resources cannot be duplicated, but also that resources cannot simply be dropped or "`forgotten`". Every resource passed into a function has to be either _returned_ to the caller or else _destroyed_ by deallocating the owned area of memory (as we shall see later). -CN’s resource types are _linear_ (as opposed to affine). This means not only that resources cannot be duplicated, but also that resources cannot simply be dropped or "`forgotten`". Every resource passed into a function has to either be used up by it, by returning it or passing it to another function that consumes it, or destroyed, by deallocating the owned area of memory (as we shall see later). +CN’s motivation for linear tracking of resources is its focus on +low-level systems software in which memory is managed manually; in +this context, memory leaks are typically very undesirable. As a +consequence, function specifications have to do precise bookkeeping of +their resource footprint and, in particular, return any unused +resources back to the caller. + +=== Exercises + +*Quadruple*. Specify the function `+quadruple_mem+`, which is similar to the earlier `+quadruple+` function, except that the input is passed as an `+int+` pointer. Write a specification that takes ownership of this pointer on entry and returns this ownership on exit, leaving the pointee value unchanged. + +include_example(exercises/slf_quadruple_mem.c) -CN’s motivation for linear tracking of resources is its focus on low-level systems software. CN checks C programs, in which, unlike higher-level garbage-collected languages, memory is managed manually, and memory leaks are typically very undesirable. +*Abs*. Give a specification to the function `+abs_mem+`, which computes the absolute value of a number passed as an `+int+` pointer. -As a consequence, function specifications have to do precise "`book-keeping`" of their resource footprint, and, in particular, return any unused resources back to the caller. +include_example(exercises/abs_mem.c) -=== The Block resource type +=== Block resources -Aside from the `+Owned+` resource seen so far, CN has another built-in resource type: `+Block+`. Given a C-type `+T+` and pointer `+p+`, `+Block(p)+` asserts the same ownership as `+Owned(p)+` — so ownership of a memory cell at `+p+` the size of type `+T+` — but in contrast to `+Owned+`, `+Block+` memory is not necessarily initialised. +Aside from the `+Owned+` resources seen so far, CN has another +built-in type of resource called `+Block+`. Given a C-type `+T+` and +pointer `+p+`, `+Block(p)+` asserts the same ownership as +`+Owned(p)+` — ownership of a memory cell at `+p+` the size of type +`+T+` — but, in contrast to `+Owned+`, `+Block+` memory is not assumed +to be initialised. CN uses this distinction to prevent reads from uninitialised memory: -* A read at C-type `+T+` and pointer `+p+` requires a resource `+Owned(p)+`, i.e., ownership of _initialised_ memory at the right C-type. The load returns the `+Owned+` resource unchanged. +* A read at C-type `+T+` and pointer `+p+` requires a resource + `+Owned(p)+`, i.e., ownership of _initialised_ memory at the + right C-type. The load returns the `+Owned+` resource unchanged. -* A write at C-type `+T+` and pointer `+p+` needs only a `+Block(p)+` (so, unlike reads, writes to uninitialised memory are fine). The write consumes ownership of the `+Block+` resource (it destroys it) and returns a new resource `+Owned(p)+` with the value written as the output. This means the resource returned from a write records the fact that this memory cell is now initialised and can be read from. +* A write at C-type `+T+` and pointer `+p+` needs only a + `+Block(p)+` (so, unlike reads, writes to uninitialised memory + are fine). The write consumes ownership of the `+Block+` resource + (it destroys it) and returns a new resource `+Owned(p)+` with the + value written as the output. This means the resource returned from a + write records the fact that this memory cell is now initialised and + can be read from. +// TODO: BCP: Not sure I understand "returns a new resource `+Owned(p)+` with the value written as the output" -- perhaps in part because I don't understand what the output of a resource means when the resource is not in the context o a take expression. -Since `+Owned+` carries the same ownership as `+Block+`, just with the additional information that the `+Owned+` memory is initalised, a resource `+Owned(p)+` is "`at least as good`" as `+Block(p)+` — an `+Owned(p)+` resource can be used whenever `+Block(p)+` is needed. For instance CN’s type checking of a write to `+p+` requires a `+Block(p)+`, but if an `+Owned(p)+` resource is what is available, this can be used just the same. This allows an already-initialised memory cell to be over-written again. +Since `+Owned+` carries the same ownership as `+Block+`, just with the +additional information that the `+Owned+` memory is initalised, a +resource `+Owned(p)+` is "`at least as good`" as `+Block(p)+` — +an `+Owned(p)+` resource can be used whenever `+Block(p)+` is +needed. For instance CN’s type checking of a write to `+p+` requires a +`+Block(p)+`, but if an `+Owned(p)+` resource is what is +available, this can be used just the same. This allows an +already-initialised memory cell to be over-written again. -Unlike `+Owned+`, whose output is the pointee value, `+Block+` has no meaningful output: its output is `+void+`/`+unit+`. +Unlike `+Owned+`, whose output is the pointee value, `+Block+` has no meaningful output. -=== Write example +=== Writing through pointers -Let’s explore resources and their outputs in another example. The C function `+incr+` takes an `+int+` pointer `+p+` and increments the pointee value. +Let’s explore resources and their outputs in another example. The C function `+incr+` takes an `+int+` pointer `+p+` and increments the value in the memory cell that it poinbts to. -include_example(solutions/slf0_basic_incr.signed.c) +include_example(exercises/slf0_basic_incr.signed.c) -In the precondition we assert ownership of resource `+Owned(p)+`, binding its output/pointee value to `+v1+`, and use `+v1+` to specify that `+p+` must point to a sufficiently small value at the start of the function not to overflow when incremented. The postcondition asserts ownership of `+p+` with output `+v2+`, as before, and uses this to express that the value `+p+` points to is incremented by `+incr+`: `+v2 == v1+1i32+`. +In the precondition we assert ownership of resource `+Owned(p)+`, +binding its output/pointee value to `+P+`, and use `+P+` to specify +that `+p+` must point to a sufficiently small value at the start of +the function so as not to overflow when incremented. The postcondition +asserts ownership of `+p+` with output `+P_post+`, as before, and uses +this to express that the value `+p+` points to is incremented by +`+incr+`: `+P_post == P + 1i32+`. If we incorrectly tweaked this specification and used `+Block(p)+` instead of `+Owned(p)+` in the precondition, as below, then CN would reject the program. @@ -325,13 +446,23 @@ Resource needed: Owned(p) Consider the state in /var/folders/_v/ndl32wpj4bb3y9dg11rvc8ph0000gn/T/state_5da0f3.html .... -The `+Owned(p)+` resource required for reading is missing, since, as per precondition, only `+Block(p)+` is available. Checking the linked HTML file confirms this. Here the section "`Available resources`" lists all resource ownership at the point of the failure: +The `+Owned(p)+` resource required for reading is missing, since, per the precondition, only `+Block(p)+` is available. Checking the linked HTML file confirms this. Here the section "`Available resources`" lists all resource ownership at the point of the failure: -* `+Block(p)(u)+`, so ownership of uninitialised memory at location `+p+`; the output is a `+void+`/`+unit+` value `+u+` (specified in the second pair of parentheses) +* `+Block(p)(u)+`, i.e., ownership of uninitialised memory + at location `+p+`; the output is a `+void+`/`+unit+` value `+u+` + (specified in the second pair of parentheses) -* `+Owned(&ARG0)(p)+`, the ownership of (initialised) memory at location `+&ARG0+`, so the memory location where the first function argument is stored; its output is the pointer `+p+` (not to be confused with the pointee of `+p+`); and finally +* `+Owned(&ARG0)(p)+`, the ownership of (initialised) + memory at location `+&ARG0+`, i.e., the memory location where the + first function argument is stored; its output is the pointer `+p+` + (not to be confused with the pointee of `+p+`); and finally -* `+__CN_Alloc(&ARG0)(void)+` is a resource that records allocation information for location `+&ARG0+`; this is related to CN’s memory-object semantics, which we ignore for the moment. +* `+__CN_Alloc(&ARG0)(void)+` is a resource that records allocation + information for location `+&ARG0+`; this is related to CN’s + memory-object semantics, which we ignore for the moment. + +// TODO: BCP: These bullet points are all a bit mysterious and maybe TMI. More generally, we should double check that this is actually the information displayed in the current HTML output... +// Dhruv: It is displayed, but hidden. And perhaps TMI right now, but once the memory model lands properly, will sadly be the price of entry to writing verifiable (semantically well-defined) C. === Exercises @@ -345,17 +476,30 @@ include_example(exercises/slf3_basic_inplace_double.c) === Multiple owned pointers -When functions manipulate multiple pointers, we can assert their ownership just like before. However (as in standard separation logic) pointer ownership is unique, so simultaneous ownership of `+Owned+` or `+Block+` resources for two pointers requires these pointers to be disjoint. +When functions manipulate multiple pointers, we can assert their +ownership just like before. However +pointer ownership in CN is unique -- that is, simultaneously owning +`+Owned+` or `+Block+` resources for two pointers implies that these +pointers are disjoint. + +The following example shows the use of two `+Owned+` resources for +accessing two different pointers by a function `+add+`, which reads +two `+int+` values in memory, at locations `+p+` and `+q+`, and +returns their sum. -The following example shows the use of two `+Owned+` resources for accessing two different pointers: function `+add+` reads two `+int+` values in memory, at locations `+p+` and `+q+`, and returns their sum. +// TODO: BCP: Hmmm -- I'm not very sure that the way I've been naming things is actually working that well. The problem is that in examples like this we computer "thing pointed to by p" at both C and CN levels. At the C level, the thing pointed to by p obviously cannot also be called p, so it doesn't make sense for it to be called P at the CN level, right? Maybe we need to think again, but hoinestly I am not certain that it is *not* working either. So I'm going to opush on for now... include_example(exercises/add_read.c) This time we use C’s `+unsigned int+` type. In C, over- and underflow of unsigned integers is not undefined behaviour, so we do not need any special preconditions to rule this out. Instead, when an arithmetic operation at unsigned type goes outside the representable range, the value "`wraps around`". -The CN variables `+m+` and `+n+` (resp. `+m2+` and `+n2+`) for the pointee values of `+p+` and `+q+` before (resp. after) the execution of `+add+` have CN basetype `+u32+`, so unsigned 32-bit integers, matching the C `+unsigned int+` type. Like C’s unsigned integer arithmetic, CN unsigned int values wrap around when exceeding the value range of the type. +The CN variables `+P+` and `+Q+` (resp. `+P_post+` and `+Q_post+`) for the pointee values of `+p+` and `+q+` before (resp. after) the execution of `+add+` have CN basetype `+u32+`, so unsigned 32-bit integers, matching the C `+unsigned int+` type. Like C’s unsigned integer arithmetic, CN unsigned int values wrap around when exceeding the value range of the type. -Hence, the postcondition `+return == m+n+` holds also when the sum of `+m+` and `+n+` is greater than the maximal `+unsigned int+` value. +Hence, the postcondition `+return == P + Q+` holds also when the sum of `+P+` and `+Q+` is greater than the maximal `+unsigned int+` value. + +// TODO: BCP: I wonder whether we should uniformly use i32 integers everywhere in the tutorial (just mentioning in the bullet list below that there are other integer types, and using i64 for calculations that may overflow). Forgetting which integer type I was using was a common (and silly) failure mode when I was first working through the tutorial. +// Dhruv: Sensible. +// BCP: ... On second thought, maybe settling on u32 instead of i32 in most places is better (fewer things to prove). Or maybe it doesn't matter much. For the start of the tutorial, i32 is important because the examples are all about overflow. But after that we could go either way. In the following we will sometimes use unsigned integer types to focus on specifying memory ownership, rather than the conditions necessary to show absence of C arithmetic undefined behaviour. @@ -371,23 +515,38 @@ include_example(exercises/slf8_basic_transfer.c) == Ownership of compound objects -So far all examples have worked with just integers and pointers, but larger programs typically also manipulate compound values, often represented using C struct types. Specifying functions manipulating structs works in much the same way as with basic types. +So far, our examples have worked with just integers and pointers, but larger programs typically also manipulate compound values, often represented using C struct types. Specifying functions manipulating structs works in much the same way as with basic types. For instance, the following example uses a `+struct+` `+point+` to represent a point in two-dimensional space. The function `+transpose+` swaps a point’s `+x+` and `+y+` coordinates. include_example(exercises/transpose.c) -Here the precondition asserts ownership for `+p+`, at type `+struct point+`; the output `+s+` is a value of CN type `+struct point+`, i.e. a record with members `+i32+` `+x+` and `+i32+` `+y+`. The postcondition similarly asserts ownership of `+p+`, with output `+s2+`, and asserts the coordinates have been swapped, by referring to the members of `+s+` and `+s2+` individually. +Here the precondition asserts ownership for `+p+`, at type `+struct +point+`; the output `+P_post+` is a value of CN type `+struct point+`, +i.e. a record with members `+i32+` `+x+` and `+i32+` `+y+`. The +postcondition similarly asserts ownership of `+p+`, with output +`+P_post+`, and asserts the coordinates have been swapped, by referring to +the members of `+P+` and `+P_post+` individually. + +// TODO: BCP: This paragraph is quite confusing if read carefully: it seems to say that the "take" in the requires clause returns a different type than the "tajke" in the "ensures" clause. Moreover, even if the reader decides that this cannot be the case and they have to return the same type, they may wonder whether thius type is a C type (which is what it looks like, since there is only one struct declaration, and it is not in a magic comment) or a CN type (which might be expected, since it is the result of a "take"). I *guess* what's going on here is that every C type is automatically reflected as a CN type with the same name. But this story is also not 100% satisfying, since the basic numeric types don't work this way: each C numeric type has an *analog* in CN, but with a different name. +//// +// Dhruv: +C supports strong updates in certain situations and so take _ = Owned(p) in the requires clause could very well have a different C type than take _ = Owned(p) in the ensures clause. + +The reason Owned needs a C-type is so that it can (a) figure out the size of the sub-heap being claimed and (b) figure out how one may need to destructure the type (unions, struct fields and padding, arrays). The relationship is that for take x = Owned(expr), expr : pointer, x : to_basetype(ct). + +There is a design decision to consider here rems-project/cerberus#349 +//// === Compound Owned and Block resources -While one might like to think of a struct as a single (compound) object that is manipulated as a whole, C permits more flexible struct manipulation: given a struct pointer, programmers can construct pointers to _individual struct members_ and pass these as values, even to other functions. +While one might like to think of a struct as a single (compound) object that is manipulated as a whole, C permits more flexible struct manipulation: given a struct pointer, programmers can construct pointers to _individual struct members_ and manipulate these as values, including even passing them to other functions. -CN therefore cannot treat resources for compound C types, such as structs, as primitive, indivisible units. Instead, `+Owned+` and `+Block+` are defined inductively in the structure of the C-type `+T+`. +CN therefore cannot treat resources for compound C types like structs as primitive, indivisible units. Instead, `+Owned+` and `+Block+` are defined inductively on the structure of the C-type `+T+`. -For struct types `+T+`, the `+Owned+` resource is defined as the collection of `+Owned+` resources for its members (as well as `+Block+` resources for any padding bytes in-between). The resource `+Block+`, similarly, is made up of `+Block+` resources for all members (and padding bytes). +For struct types `+T+`, the `+Owned+` resource is defined as the collection of `+Owned+` resources for its members (as well as `+Block+` resources for any padding bytes in-between them). The resource `+Block+`, similarly, is made up of `+Block+` resources for all members (and padding bytes). -To handle code that manipulates pointers into parts of a struct object, CN can automatically decompose a struct resource into the member resources, and recompose it, as needed. The following example illustrates this. +To handle code that manipulates pointers into parts of a struct object, CN can automatically decompose a struct resource into the member resources, and it can recompose the struct later, as needed. The following example illustrates this. Recall the function `+zero+` from our earlier exercise. It takes an `+int+` pointer to uninitialised memory, with `+Block+` ownership, and initialises the value to zero, returning an `+Owned+` resource with output `+0+`. @@ -397,25 +556,29 @@ include_example(exercises/init_point.c) As stated in its precondition, `+init_point+` receives ownership `+Block(p)+`. The `+zero+` function, however, works on `+int+` pointers and requires `+Block+` ownership. -CN can prove the calls to `+zero+` with `+&p->x+` and `+&p->y+` are safe because it decomposes the `+Block(p)+` into two `+Block+`, one for member `+x+`, one for member `+y+`. Later, the reverse happens: following the two calls to `+zero+`, as per `+zero+`’s precondition, `+init_point+` has ownership of two adjacent `+Owned+` resources – ownership for the two struct member pointers, with the member now initialised. Since the postcondition of `+init_point+` requires ownership `+Owned(p)+`, CN combines these back into a compound resource. The resulting `+Owned+` resource has for an output the struct value `+s2+` that is composed of the zeroed member values for `+x+` and `+y+`. +CN can prove the calls to `+zero+` with `+&p->x+` and `+&p->y+` are safe because it decomposes the `+Block(p)+` into a `+Block+` for member `+x+` and a `+Block+` for member `+y+`. Later, the reverse happens: following the two calls to `+zero+`, as per `+zero+`’s precondition, `+init_point+` has ownership of two adjacent `+Owned+` resources – ownership for the two struct member pointers, with the member now initialised. Since the postcondition of `+init_point+` requires ownership `+Owned(p)+`, CN combines these back into a compound resource. The resulting `+Owned+` resource has for an output the struct value `+P_post+` that is composed of the zeroed member values for `+x+` and `+y+`. === Resource inference To handle the required resource inference, CN "`eagerly`" decomposes all `+struct+` resources into resources for the struct members, and "`lazily`" re-composes them as needed. -We can see this if, for instance, we experimentally change the `+transpose+` example from above to force a type error. Let’s insert an `+/*@ assert(false) @*/+` CN assertion in the middle of the `+transpose+` function (more on CN assertions later), so we can inspect CN’s proof context shown in the error report. +We can see this if, for instance, we experimentally change the `+transpose+` example from above to force a type error. Let’s insert an `+/*@ assert(false) @*/+` CN assertion in the middle of the `+transpose+` function, so we can inspect CN’s proof context shown in the error report. (More on CN assertions later.) + +// TODO: BCP: Recheck that what we say here matches what it actually looks like include_example(exercises/transpose.broken.c) The precondition of `+transpose+` asserts ownership of an `+Owned(p)+` resource. The error report now instead lists under "`Available resources`" two resources: -* `+Owned(member_shift(p, x))+` with output `+s.x+` and +* `+Owned(member_shift(p, x))+` with output `+P.x+` and -* `+Owned(member_shift(p, y))+` with output `+s.y+` +* `+Owned(member_shift(p, y))+` with output `+P.y+` + +// TODO: BCP: We should verify that it really does say this. Here `+member_shift(p,m)+` is the CN expression that constructs, from a `+struct s+` pointer `+p+`, the "`shifted`" pointer for its member `+m+`. -When the function returns the two member resources are recombined "`on demand`" to satisfy the postcondition `+Owned(p)+`. +When the function returns, the two member resources are recombined "`on demand`" to satisfy the postcondition `+Owned(p)+`. === Exercises @@ -437,9 +600,11 @@ CP: Agreed. For now continuing with arrays, but will return to this later. == Arrays and loops -Another common datatype in C is arrays. Reasoning about memory ownership for arrays is more difficult than for the datatypes we have seen so far: C allows the programmer to access arrays using _computed pointers_, and the size of an array does not need to be known as a constant at compile time. +Another common datatype in C is arrays. Reasoning about memory ownership for arrays is more difficult than for the datatypes we have seen so far, for two reasons: (1) C allows the programmer to access arrays using _computed pointers_, and (2) the size of an array does not need to be known as a constant at compile time. + +To support reasoning about code manipulating arrays and computed pointers, CN has _iterated resources_. For instance, to specify ownership of an `+int+` array with 10 cells starting at pointer `+p+`, CN uses the following iterated resource: -To support reasoning about code manipulating arrays and computed pointers, CN has _iterated resources_. For instance, to specify ownership of an `+int+` array with 10 cells starting at pointer `+p+`, CN uses the iterated resource +// TODO: BCP: Another tricky naming / capitalization puzzle: The index of an "each" has CN type i32, so strictly speaking I believe it should be written with a capital "I". But insisting on this feels like insisting on a distinction that most CN programmers would never even notice, much less be confused by. I think this is another instance of the way C and CN integer types are partly but not completely squished together. [source,c] ---- @@ -476,7 +641,7 @@ comprising three parts: === First array example -Let’s see how this applies to a first example of an array-manipulating function. Function `+read+` takes three arguments: the base pointer `+p+` of an `+int+` array, the length `+n+` of the array, and an index `+i+` into the array; `+read+` then returns the value of the `+i+`-th array cell. +Let’s see how this applies to a simple array-manipulating function. Function `+read+` takes three arguments: the base pointer `+p+` of an `+int+` array, the length `+n+` of the array, and an index `+i+` into the array; `+read+` then returns the value of the `+i+`-th array cell. include_example(exercises/array_load.broken.c) @@ -490,7 +655,7 @@ On exit the array ownership is returned again. This specification, in principle, should ensure that the access `+p[i]+` is safe. However, running CN on the example produces an error: CN is unable to find the required ownership for reading `+p[i]+`. .... -cn build/solutions/array_load.broken.c +cn verify solutions/array_load.broken.c [1/1]: read build/solutions/array_load.broken.c:5:10: error: Missing resource for reading return p[i]; @@ -498,13 +663,13 @@ build/solutions/array_load.broken.c:5:10: error: Missing resource for reading Resource needed: Owned(array_shift(p, (u64)i)) .... -The reason is that when searching for a required resource, such as the `+Owned+` resource for `+p[i]+` here, CN’s resource inference does not consider iterated resources. Quantifiers, as used by iterated resources, can make verification undecidable, so, in order to maintain predictable type checking, CN delegates this aspect of the reasoning to the user. +The reason is that, when searching for a required resource, such as the `+Owned+` resource for `+p[i]+` here, CN’s resource inference does not consider iterated resources. Quantifiers, as used by iterated resources, can make verification undecidable, so, in order to maintain predictable type checking, CN delegates this aspect of the reasoning to the user. -To make the `+Owned+` resource required for accessing `+p[i]+` available to CN’s resource inference we have to "`extract`" ownership for index `+i+` out of the iterated resource. +To make the `+Owned+` resource required for accessing `+p[i]+` available to CN’s resource inference we have to explicitly "`extract`" ownership for index `+i+` out of the iterated resource. include_example(exercises/array_load.c) -Here the CN comment `+/*@ extract Owned, i; @*/+` is a CN "`ghost statement`"/proof hint that instructs CN to instantiate any available iterated `+Owned+` resource for index `+i+`. In our example this operation splits the iterated resource into two: +Here the CN comment `+/*@ extract Owned, i; @*/+` is a proof hint in the form of a "`ghost statement`" that instructs CN to instantiate any available iterated `+Owned+` resource for index `+i+`. In our example this operation splits the iterated resource into two: [source,c] ---- @@ -527,27 +692,42 @@ each(i32 j; 0i32 <= j && j < n && j != i) { Owned(array_shift(p,j)) } ---- -After this extraction step, CN can use the (former) extracted resource to justify the access `+p[i]+`. +After this extraction step, CN can use the (former) extracted resource to justify the access `+p[i]+`. Note that an `+extract+` statement's second argument can be any arithmetic expression, not just a single identifier like in this example. -Following an `+extract+` statement, CN moreover remembers the extracted index and can automatically "`reverse`" the extraction when needed: after type checking the access `+p[i]+` CN must ensure the function’s postcondition holds, which needs the full array ownership again (including the extracted index `+i+`); remembering the index `+i+`, CN then automatically merges resources (1) and (2) again to obtain the required full array ownership, and completes the verification of the function. +Following an `+extract+` statement, CN remembers the extracted index and can automatically "`reverse`" the extraction when needed: after type checking the access `+p[i]+` CN must ensure the function’s postcondition holds, which needs the full array ownership again (including the extracted index `+i+`); remembering the index `+i+`, CN then automatically merges resources (1) and (2) again to obtain the required full array ownership, and completes the verification of the function. -So far the specification only guarantees safe execution but does not specify the behaviour of `+read+`. To address this, let’s return to the iterated resources in the function specification. When we specify `+take a1 = each ...+` here, what is `+a1+`? In CN, the output of an iterated resource is a _map_ from indices to resource outputs. In this example, where index `+j+` has CN type `+i32+` and the iterated resource is `+Owned+`, the output `+a1+` is a map from `+i32+` indices to `+i32+` values — CN type `+map+`. (If the type of `+j+` was `+i64+` and the resource `+Owned+`, `+a1+` would have type `+map+`.) +So far the specification only guarantees safe execution but does not +specify the behaviour of `+read+`. To address this, let’s return to +the iterated resources in the function specification. When we specify +`+take A = each ...+` here, what is `+A+`? In CN, the output of an +iterated resource is a _map_ from indices to resource outputs. In this +example, where index `+j+` has CN type `+i32+` and the iterated +resource is `+Owned+`, the output `+A+` is a map from `+i32+` +indices to `+i32+` values — CN type `+map+`. If the type of +`+j+` was `+i64+` and the resource `+Owned+`, `+A+` would have +type `+map+`. We can use this to refine our specification with information about the functional behaviour of `+read+`. include_example(exercises/array_load2.c) -We specify that `+read+` does not change the array — the outputs `+a1+` and `+a2+`, taken before and after running the function, are the same — and that the value returned is `+a1[i]+`, `+a1+` at index `+i+`. +We specify that `+read+` does not change the array — the outputs of `+Owned+`, +`+A+` and `+A_post+`, taken before and after running the function, are +the same — and that the value returned is `+A[i]+`. === Exercises *Array read two.* Specify and verify the following function, `+array_read_two+`, which takes the base pointer `+p+` of an `+unsigned int+` array, the array length `+n+`, and two indices `+i+` and `+j+`. Assuming `+i+` and `+j+` are different, it returns the sum of the values at these two indices. +// TODO: BCP: When we get around to renaming files in the examples directory, we should call this one array_swap or something else beginning with "array". + include_example(exercises/add_two_array.c) //// TODO: BCP: In this one I got quite tangled up in different kinds of integers, then got tangled up in (I think) putting the extract declarations in the wrong place. (I didn't save the not-working version, I'm afraid.) + +TODO: Sainati: I think it would be useful to have a int array version of this exercise as a worked example; I am not sure, for example, how one would express bounds requirements on the contents of an array in CN, as you would need to do here to ensure that p[i] + p[j] doesn’t overflow if p's contents are signed ints //// *Swap array.* Specify and verify `+swap_array+`, which swaps the values of two cells of an `+int+` array. Assume again that `+i+` and `+j+` are different, and describe the effect of `+swap_array+` on the array value using the CN map update expression `+a[i:v]+`, which denotes the same map as `+a+`, except with index `+i+` updated to `+v+`. @@ -578,15 +758,17 @@ TODO: BCP: I wrote this, which seemed natural but did not work -- I still don't === Loops -The array examples covered so far manipulate one or two individual cells of an array. Another typical pattern in code working over arrays is to *loop*, uniformly accessing all cells of an array, or sub-ranges of it. +The array examples covered so far manipulate one or two individual cells of an array. Another typical pattern in code working over arrays is to *loop*, uniformly accessing all cells of an array or a sub-range of it. In order to verify code with loops, CN requires the user to supply loop invariants -- CN specifications of all owned resources and the constraints required to verify each iteration of the loop. - Let's take a look at a simple first example. The following function, `+init_array+`, takes the base pointer `+p+` of a `+char+` array and the array length `+n+` and writes `+0+` to each array cell. + +// TODO: BCP: Rename to array_init.c + include_example(exercises/init_array.c) -If, for the moment, we focus just on proving safe execution of `+init_array+`, ignoring its functional behaviour, a specification might look as above: on entry `+init_array+` takes ownership of an iterated `+Owned+` resource -- one `+Owned+` resource for each index `+i+` of type `+u32+` (so necessarily greater or equal to `+0+`) up to `+n+`; on exit `+init_array+` returns the ownership. +If, for the moment, we focus just on proving safe execution of `+init_array+`, ignoring its functional behaviour, a specification might look as above: on entry, `+init_array+` takes ownership of an iterated `+Owned+` resource -- one `+Owned+` resource for each index `+i+` of type `+u32+` (so necessarily greater or equal to `+0+`) up to `+n+`; on exit `+init_array+` returns the ownership. To verify this, we have to supply a loop invariant that specifies all resource ownership and the necessary constraints that hold before and after each iteration of the loop. Loop invariants are specified using the keyword `inv`, followed by CN specifications using the same syntax as in function pre- and postconditions. The variables in scope for loop invariants are all in-scope C variables, as well as CN variables introduced in the function precondition. *In loop invariants, the name of a C variable refers to its current value* (more on this shortly). @@ -597,7 +779,7 @@ TODO: BCP: Concrete syntax: Why not write something like "unchanged {p,n}" or "u The main condition here is unsurprising: we specify ownership of an iterated resource for an array just like in the the pre- and postcondition. -The second thing we need to do, however, is less straightforward. Recall that, as discussed at the start of the tutorial, function arguments in C are mutable, and so CN permits this as well.While in this example it is obvious that `+p+` and `+n+` do not change, CN currently requires the loop invariant to explicitly state this, using special notation `+{p} unchanged+` (and similarly for `+n+`). +The second thing we need to do, however, is less straightforward. Recall that, as discussed at the start of the tutorial, function arguments in C are mutable. Although, in this example, it is obvious that `+p+` and `+n+` do not change, CN currently requires the loop invariant to explicitly state this, using special notation `+{p} unchanged+` (and similarly for `+n+`). **Note.** If we forget to specify `+unchanged+`, this can lead to confusing errors. In this example, for instance, CN would verify the loop against the loop invariant, but would be unable to prove a function postcondition seemingly directly implied by the loop invariant (lacking the information that the postcondition's `+p+` and `+n+` are the same as the loop invariant's). Future CN versions may handle loop invariants differently and treat variables as immutable by default. //// @@ -607,19 +789,23 @@ TODO: BCP: This seems like a good idea! The final piece needed in the verification is an `+extract+` statement, as used in the previous examples: to separate the individual `+Owned+` resource for index `+j+` out of the iterated `+Owned+` resource and make it available to the resource inference, we specify `+extract Owned, j;+`. -With the `+extract+` statements in place, CN accepts the function. +With the `+inv+` and `+extract+` statements in place, CN accepts the function. === Second loop example -However, on closer look, the specification of `+init_array+` is overly strong: it requires an iterated `+Owned+` resource for the array on entry. If, as the name suggests, the purpose of `+init_array+` is to initialise the array, then a precondition asserting only an iterated `+Block+` resource for the array should also be sufficient. The modified specification is then as follows. +The specification of `+init_array+` is overly strong: it requires an iterated `+Owned+` resource for the array on entry. If, as the name suggests, the purpose of `+init_array+` is to initialise the array, then a precondition asserting only an iterated `+Block+` resource for the array should also be sufficient. The modified specification is then as follows. include_example(exercises/init_array2.c) -This specification *should* hold: assuming ownership of an uninitialised array on entry, each iteration of the loop initialises one cell of the array, moving it from `+Block+` to `+Owned+` "`state`", so that on function return the full array is initialised. (Recall that stores only require `+Block+` ownership of the written memory location, so ownership of not-necessarily-initialised memory.) +This specification *should* hold: assuming ownership of an uninitialised array on entry, each iteration of the loop initialises one cell of the array, moving it from `+Block+` to `+Owned+` "`state`", so that on function return the full array is initialised. (Recall that stores only require `+Block+` ownership of the written memory location, i.e., ownership of not-necessarily-initialised memory.) -To verify this modified example we again need a loop invariant. This time, the loop invariant is more involved, however: since each iteration of the loop initialises one more array cell, the loop invariant has to do precise book-keeping of the initialisation status of the array. +To verify this modified example we again need a loop Invariant. But +this time the loop invariant is more involved: since each iteration of +the loop initialises one more array cell, the loop invariant has to do +precise book-keeping of the initialisation status of the different +sections of the array. -To do so, we partition the array ownership into two parts: for each index of the array the loop has already visited, we have an `+Owned+` resource, for all other array indices we have the (unchanged) `+Block+` ownership. +To do this, we partition the array ownership into two parts: for each index of the array the loop has already visited, we have an `+Owned+` resource, for all other array indices we have the (unchanged) `+Block+` ownership. include_example(solutions/init_array2.c) @@ -631,16 +817,18 @@ Let's go through this line-by-line: - As in the previous example, we assert `+p+` and `+n+` are unchanged. -- Finally, unlike in the previous example, this loop invariant involves `+j+`. We therefore also need to know that `+j+` does not exceed the array length `+n+`. Otherwise CN would not be able to prove that, on completing the last loop iteration, `+j=n+` holds. This, in turn, is needed to show that when the function returns, ownership of the iterated `+Owned+` resource --- as specified in the loop invariant --- is fully consumed by the function's post-condition and there is no left-over unused resource. +- Finally, unlike in the previous example, this loop invariant involves `+j+`. We therefore also need to know that `+j+` does not exceed the array length `+n+`. Otherwise CN would not be able to prove that, on completing the last loop iteration, `+j=n+` holds. This, in turn, is needed to show that, when the function returns, ownership of the iterated `+Owned+` resource --- as specified in the loop invariant --- is fully consumed by the function's post-condition and there is no left-over unused resource. As before, we also have to instruct CN to `+extract+` ownership of individual array cells out of the iterated resources: -- to allow CN to extract the individual `+Block+` to be written we use `+extract Block, j;+`; +- to allow CN to extract the individual `+Block+` to be written, we use `+extract Block, j;+`; - the store returns a matching `+Owned+` resource for index `+j+`; -- finally, we put `+extract Owned, j;+` to allow CN to "`attach`" this resource to the iterated `+Owned+` resource. CN issues a warning, because nothing is, in fact, extracted: we are using `+extract+` only for the "`reverse`" direction. +- finally, we add `+extract Owned, j;+` to allow CN to "`attach`" this resource to the iterated `+Owned+` resource. CN issues a warning, because nothing is, in fact, extracted: we are using `+extract+` only for the "`reverse`" direction. +// TODO: BCP: That last bit is mysterious. +// Dhruv: See long explanation and issue here: rems-project/cerberus#498 === Exercises @@ -648,7 +836,7 @@ As before, we also have to instruct CN to `+extract+` ownership of individual ar include_example(exercises/init_array_rev.c) - +// TODO: BCP: The transition to the next section is awkward. Needs a sentence or two to signal that we're changing topics. Some better visual indication would also be nice. //// ___________________________________________________________________________ @@ -679,17 +867,35 @@ not alias... include_example(exercises/slf_incr2_noalias.c) But what if they do alias? The clunky solution is to write a whole -different version of incr2 with a different embedded specification... +different version of `+incr2+` with a different embedded specification... include_example(exercises/slf_incr2_alias.c) -This is horrible. Much better is to define a predicate to use -in the pre- and postconditions that captures both cases together: +This version does correctly state that the final values of `+p+` and `+q+` are,m respectively, `+3+` and `+1+` more than their original values. But the way we got there -- by duplicating the whole function `+incr2+`, is horrible. + +// TODO: Sainati: I think it would be useful here to add an explanation for how CN's type checking works. +// For example, in the definition of BothOwned here, how is CN able to prove that `+take pv = Owned(p);+` +// type checks, since all we know about `p` in the definition of the predicate is that it's a pointer? + +A better way is to define a *predicate* that captures both the aliased +and the non-aliased cases together and use it in the pre- and +postconditions: + +// TODO: Sainati: I think it would be useful here to add an explanation for how CN's type checking works. +// For example, in the definition of BothOwned here, how is CN able to prove that `+take pv = Owned(p);+` +// type checks, since all we know about `p` in the definition of the predicate is that it's a pointer? include_example(exercises/slf_incr2.c) +// TODO: BCP: "BothOwned" is a pretty awkward name. +// TODO: BCP: We haven't introduced CN records. In particular, C programmers may be surprised that we don't have to pre-declare record types. +// TODO: BCP: the annotation on incr2 needs some unpacking for readers!! +// TODO: BCP: first use of the "split_case" annotation + == Allocating and Deallocating Memory +// TODO: BCP: Again, more text is needed to set up this discussion. + At the moment, CN does not understand the `+malloc+` and `+free+` functions. They are a bit tricky because they rely on a bit of polymorphism and a typecast between `+char*+` -- the result type of @@ -700,7 +906,7 @@ However, for any given type, we can define a type-specific function that allocates heap storage with exactly that type. The implementation of this function cannot be checked by CN, but we can give just the spec, together with a promise to link against an -external C library providing the implementation: +external C library providing a correct (but not verified!) implementation: include_example(exercises/malloc.h) @@ -709,9 +915,11 @@ inside a CN file by marking it with the keyword `+trusted+` at the top of its CN specification.) Similarly: + include_example(exercises/free.h) Now we can write code that allocates and frees memory: + include_example(exercises/slf17_get_and_free.c) We can also define a "safer", ML-style version of `+malloc+` that @@ -735,53 +943,61 @@ pointed to by its argument. include_example(exercises/slf_ref_greater.c) -=== Side Note +=== Side note Here is another syntax for external / unknown functions, together with an example of a loose specification: //// -TODO: BCP: This is a bit random -- it's not clear people need to know about this alternate syntax, and it's awkwardly mixed with a semi-interesting example that's not relevant to this section. +TODO: BCP: This is a bit random -- it's not clear people need to know about this alternate syntax, and it's awkwardly mixed with a semi-interesting example that's not relevant to this section. Also awkwardly placed, right here. //// include_example(exercises/slf18_two_dice.c) == Lists +// TODO: BCP: Better intro needed + Now it's time to look at some more interesting heap structures. To begin with, here is a C definition for linked list cells, together with allocation and deallocation functions: -include_example(exercises/list_c_types.h) +// TODO: BCP: Here and in several other places, we should use the "take _ = ..." syntax when the owned value is not used. And we should explain it the first time we use it. + +include_example(exercises/list/c_types.h) + +// TODO: BCP: Per discussion with Christopher, Cassia, and Daniel, the word "predicate" is quite confusing for newcomers (in logic, predicates do not return things!). A more neutral word might make for significantly easier onboarding. +// Dhruv: Or no keyword? rems-project/cerberus#304 How about traversal? +// BCP: No keyword sounds even better. But "traversal" in the interim is not bad. Or maybe "extractor" or something like that? To write specifications for C functions that manipulate lists, we need -to define a CN "predicate" that describes *mathematical* list -structures, as one would do in ML, Haskell, or Coq. (We call them -"sequences" here to avoid overloading the word "list".) +to define a CN "predicate" that describes specification-level list +structures, as one would do in ML, Haskell, or Coq. We use the +datatype `+List+` for CN-level lists. -Intuitively, the `+IntList+` predicate walks over a pointer structure -in the C heap and extracts an `+Owned+` version of the mathematical -list that it represents. +Intuitively, the `+SLList_At+` predicate walks over a singly-linked +pointer structure in the C heap and extracts an `+Owned+` version of +the CN-level list that it represents. -include_example(exercises/list_cn_types.h) +include_example(exercises/list/cn_types.h) -We can also write specification-level "functions" by ordinary -functional programming (in slightly strange, unholy-union-of-C-and-ML +We can also write *functions* on CN-level lists by ordinary functional +programming (in a slightly strange, unholy-union-of-C-and-Rust syntax): -include_example(exercises/list_hdtl.h) +include_example(exercises/list/hdtl.h) -We use the `+IntList+` predicate to specify functions returning the +We use the `+SLList_At+` predicate to specify functions returning the empty list and the cons of a number and a list. -include_example(exercises/list_constructors.h) +include_example(exercises/list/constructors.h) -Finally, we can collect all this stuff into a single header file and +Finally, we can collect all this stuff into a single header file. (We add the usual C `+#ifndef+` gorp to avoid complaints from the compiler -if it happens to get included twice from the same source file later. +if it happens to get included twice from the same source file later.) -include_example(exercises/list.h) +include_example(exercises/list/headers.h) //// TODO: BCP: The 'return != NULL' should not be needed, but to remove it @@ -797,22 +1013,23 @@ verifying list-manipulating functions. First, `+append+`. Here is its specification (in a separate file, because we'll want to use it multiple times below.) -include_example(exercises/list_append.h) +include_example(exercises/list/append.h) Here is a simple destructive `+append+` function. Note the two uses of the `+unfold+` annotation in the body, which are needed to help the -CN typechecker. +CN typechecker. The `+unfold+` annotation is an instruction to CN to replace a call to a recursive (CN) function (in this case `+append+`) +with its definition, and is necessary because CN is unable to automatically determine when and where to expand recursive definitions on its own. // TODO: BCP: Can someone add a more technical explanation of why they are needed and exactly what they do? -include_example(exercises/append.c) +include_example(exercises/list/append.c) === List copy Here is an allocating list copy function with a pleasantly light annotation burden. -include_example(exercises/list_copy.c) +include_example(exercises/list/copy.c) === Merge sort @@ -823,7 +1040,11 @@ avoids allocating any new list cells in the splitting step by taking alternate cells from the original list and linking them together into two new lists of roughly equal lengths. -include_example(exercises/mergesort.c) +// TODO: BCP: We've heard from more than one reader that this example is particularly hard to digest without some additional help + +// TODO: BCP: Nit: Merge multiple requires and ensures clauses into one + +include_example(exercises/list/mergesort.c) === Exercises @@ -831,48 +1052,60 @@ include_example(exercises/mergesort.c) `+IntList_append2+`. (You will need some in the body as well as at the top.) -include_example(exercises/append2.c) +include_example(exercises/list/append2.c) Note that it would not make sense to do the usual functional-programming trick of copying xs but sharing ys. (Why?) *Length*. Add annotations as appropriate: -include_example(exercises/list_length.c) +include_example(exercises/list/length.c) *List deallocation*. Fill in the body of the following procedure and add annotations as appropriate: -include_example(exercises/list_free.c) +include_example(exercises/list/free.c) *Length with an accumulator*. Add annotations as appropriate: // TODO: BCP: Removing / forgetting the unfold in this one gives a truly // bizarre error message saying that the constraint "n == (n + length(L1))" // is unsatisfiable... +// TODO: Sainati: when I went through the tutorial, the file provided for this exercise was already "complete" in that +// it already had all the necessary annotations present for CN to verify it + include_example(exercises/slf_length_acc.c) == Working with External Lemmas -**TODO**: This section should also show what the proof of the lemmas +// TODO: BCP: This section should also show what the proof of the lemmas looks like on the Coq side! // TODO: BCP: This needs to be filled in urgently!! +// Dhruyv: There are some examples in the Cerberus repo tests? rems-project/cerberus@20d9d5c + +//// +TODO: BCP: +think about capitalization, etc., for lemma names + push_lemma should be Push_lemma, I guess? Or lemma_push? + snoc_facts should be lemma_Snoc or something + others? +//// === List reverse The specification of list reversal in CN relies on the familiar recursive list reverse function, with a recursive helper. -include_example(exercises/list_snoc.h) -include_example(exercises/list_rev.h) +include_example(exercises/list/snoc.h) +include_example(exercises/list/rev.h) To reason about the C implementation of list reverse, we need to help the SMT solver by enriching its knowledge base with a couple of facts about lists. The proofs of these facts require induction, so in CN we simply state them as lemmas and defer the proofs to Coq. -include_example(exercises/list_rev_lemmas.h) +include_example(exercises/list/rev_lemmas.h) Having stated these lemmas, we can now complete the specification and proof of `+IntList_rev+`. Note the two places where `+apply+` is used @@ -885,19 +1118,21 @@ general scope where a given set of lemmas might be needed, rather than specifying exactly where to use them.) //// -include_example(exercises/list_rev.c) +include_example(exercises/list/rev.c) For comparison, here is another way to write the program, using a while loop instead of recursion, with its specification and proof. // TODO: BCP: Why 0 instead of NULL?? (Is 0 better?) -include_example(exercises/list_rev_alt.c) +include_example(exercises/list/rev_alt.c) === Exercises **Sized stacks:** Fill in annotations where requested: +// TODO: BCP: type_synonym has not been introduced yet + include_example(exercises/slf_sized_stack.c) // ====================================================================== @@ -914,6 +1149,8 @@ include_example(exercises/slf_sized_stack.c) == CN Style +// TODO: BCP: If we are agreed on the naming conventions suggested in /NAMING-CONVENTIONS.md, we could move that material here. + This section gathers some advice on stylistic conventions and best practices in CN. @@ -955,45 +1192,48 @@ include_example(exercises/const_example_lessgood.c) potential source of nasty bugs! +// ====================================================================== +// ====================================================================== // ====================================================================== == Case Studies To close out the tutorial, let's look at some larger examples. -=== Imperative Queues +=== Case Study: Imperative Queues A queue is a linked list with O(1) operations for adding things to one end (the "back") and removing them from the other (the "front"). Here are the C type definitions: -include_example(exercises/queue_c_types.h) +include_example(exercises/queue/c_types.h) A queue consists of a pair of pointers, one pointing to the front -element, which is the first in a linked list of `+int_queueCell+`s, +element, which is the first in a linked list of `+queue_cell+`s, the other pointing directly to the last cell in this list. If the queue is empty, both pointers are NULL. -Abstractly, a queue just represents a list, so we can reuse the `+seq+` +Abstractly, a queue just represents a list, so we can reuse the `+List+` type from the list examples earlier in the tutorial. -include_example(exercises/queue_cn_types_1.h) -//// -TODO: BCP: If we're going to call this IntQueuePtr (Dhruv's suggestion), then -we have to rename other things above for consistency... -//// +include_example(exercises/queue/cn_types_1.h) -Given a pointer to an `+int_queue+` struct, this predicate grabs -ownership of the struct, asserts that the `+front+` and `+back+` pointers -must either both be NULL or both be non-NULL, and then hands off to an -auxiliary predicate `+IntQueueFB+`. (Conceptually, `+IntQueueFB+` is -part of `+IntQueuePTR+`, but CN currently allows conditional -expressions only at the beginning of predicate definitions, not after -a `+take+`.) +Given a pointer to a `+queue+` struct, this predicate grabs ownership +of the struct, asserts that the `+front+` and `+back+` pointers must +either both be NULL or both be non-NULL, and then hands off to an +auxiliary predicate `+QueueFB+`. Note that `+QueuePtr_At+` returns a +`+List+` -- that is, the abstract view of a queue heap structure is +simply the sequence of elements that it contains. The difference +between a queue and a singly or doubly linked list is simply one of +concrete representation. -`+IntQueueFB+` is where the interesting part starts: +`+QueueFB+` is where the interesting part starts. (Conceptually, +`+QueueFB+` is part of `+QueuePTR+`, but CN currently allows +conditional expressions only at the beginning of predicate +definitions, not after a `+take+`, so we need to make a separate +auxiliary predicate.) -include_example(exercises/queue_cn_types_2.h) +include_example(exercises/queue/cn_types_2.h) First, we case on whether the `+front+` of the queue is NULL. If so, then the queue is empty and we return the empty sequence. @@ -1001,7 +1241,7 @@ then the queue is empty and we return the empty sequence. If the queue is not empty, we need to walk down the linked list of elements and gather up all their values into a sequence. But we must treat the last element of the queue specially, for two reasons. -First, because the `+push+` operation is going to follow the `+back+` +First, since the `+push+` operation is going to follow the `+back+` pointer directly to the last list cell without traversing all the others, we need to `+take+` that element now rather than waiting to get to it at the end of the recursion starting from the `+front+`. @@ -1011,10 +1251,10 @@ the second to last cell (or the `+front+` pointer, if there is only one cell in the list), so we need to be careful not to `+take+` this cell twice. -Accordingly, we begin by `+take+`ing the tail cell and passing it -separately to the `+IntQueueAux+` predicate, which has the job of +Accordingly, we begin by `+take+`-ing the tail cell and passing it +separately to the `+QueueAux+` predicate, which has the job of walking down the cells from the front and gathering all the rest of -them into a sequence. We take the result from `+IntQueueAux+` and +them into a sequence. We take the result from `+QueueAux+` and `+snoc+` on the very last element. The `+assert (is_null(B.next))+` here gives the CN verifier a crucial @@ -1024,38 +1264,38 @@ its `+next+` field will always be NULL. // TODO: BCP: How to help people guess that this is needed?? -Finally, the `+IntQueueAux+` predicate recurses down the list of -cells. +Finally, the `+QueueAux+` predicate recurses down the list of +cells and returns a list of their contents. -include_example(exercises/queue_cn_types_3.h) +include_example(exercises/queue/cn_types_3.h) Its first argument (`+f+`) starts out at `+front+` and progresses -through the list on recursive calls; its `+b+` argument is always a +through the queue on recursive calls; its `+b+` argument is always a pointer to the very last cell. When `+f+` and `+b+` are equal, we have reached the last cell and -there is nothing to do. (We don't even have to build a singleton -list: that's going to happen one level up, in `+IntQueueFB+`.) +there is nothing to do. We don't even have to build a singleton +list: that's going to happen one level up, in `+QueueFB+`. Otherwise, we `+take+` the fields of the `+f+`, make a recurive -call to `+IntQueueAux+` to process the rest of the cells, and cons the +call to `+QueueAux+` to process the rest of the cells, and cons the `+first+` field of this cell onto the resulting sequence before -returning it. (Again, we need to help the CN verifier by explicitly +returning it. Again, we need to help the CN verifier by explicitly informing it of the invariant that we know, that `+C.next+` cannot be -null if `+f+` and `+b+` are different.) +null if `+f+` and `+b+` are different. Now we need a bit of boilerplate: just as with linked lists, we need to be able to allocate and deallocate queues and queue cells. There are no interesting novelties here. -include_example(exercises/queue_allocation.h) +include_example(exercises/queue/allocation.h) // ====================================================================== *Exercise*: The function for creating an empty queue just needs to set both of its fields to NULL. See if you can fill in its specification. -include_example(exercises/queue_empty.c) +include_example(exercises/queue/empty.c) // ====================================================================== @@ -1064,12 +1304,12 @@ first. Here's the unannotated C code -- make sure you understand it. -include_example(exercises/queue_push_orig.broken.c) +include_example(exercises/queue/push_orig.broken.c) *Exercise*: Before reading on, see if you can write down a reasonable top-level specification for this operation. -(One thing you might find odd about this code is that there's a +One thing you might find odd about this code is that there's a `+return+` statement at the end of each branch of the conditional, rather than a single `+return+` at the bottom. The reason for this is that, when CN analyzes a function body containing a conditional, it @@ -1078,11 +1318,11 @@ the branches. Then, if verification encounters an error related to this code -- e.g., "you didn't establish the `+ensures+` conditions at the point of returning -- the error message will be confusing because it will not be clear which branch of the conditional it is associated -with.) +with. Now, here is the annotated version of the `+push+` operation. -include_example(exercises/queue_push.c) +include_example(exercises/queue/push.c) The case where the queue starts out empty (`+q->back == 0+`) is easy. CN can work it out all by itself. @@ -1093,7 +1333,10 @@ elements, so we should expect that validating `+push+` is going to require some reasoning about this sequence. Here, in fact, is the lemma we need. -include_example(exercises/queue_push_lemma.h) +// TODO: BCP: Not sure I can explain what "pointer" means here, or why we don't need to declare more specific types for these arguments to the lemma. +// Dhruv: See above comments about strong updates: in a requires/ensures, the types are given by the arguments in scope, but here we don't have that. + +include_example(exercises/queue/push_lemma.h) This says, in effect, that we have two choices for how to read out the values in some chain of queue cells of length at least 2, starting @@ -1116,17 +1359,17 @@ as part of the rest (so that the new `+back+` cell can now be treated specially). One interesting technicality is worth noting: After the assignment -`+q->back = c+` we can no longer prove `+IntQueueFB(q->front, -oldback)+`, but we don't care, since we want to prove -`+IntQueueFB(q->front, q->back)+`. However, crucially, -`+IntQueueAux(q->front, oldback)+` is still true. +`+q->back = c+`, we can no longer prove `+QueueFB(q->front, +oldback)+`, but we don't care about this, since we want to prove +`+QueueFB(q->front, q->back)+`. However, crucially, +`+QueueAux(q->front, oldback)+` is still true. // ====================================================================== Now let's look at the `+pop+` operation. Here is the un-annotated version: -include_example(exercises/queue_pop_orig.broken.c) +include_example(exercises/queue/pop_orig.broken.c) *Exercise*: Again, before reading on, see if you can write down a plausible top-level specification. (For extra credit, see how far you @@ -1134,23 +1377,23 @@ can get with verifying it!) Here is the fully annotated `+pop+` code: -include_example(exercises/queue_pop.c) +include_example(exercises/queue/pop.c) There are three annotations to explain. Let's consider them in order. First, the `+split_case+` on `+is_null(q->front)+` is needed to tell CN which of the branches of the `+if+` at the beginning of the -`+IntQueueFB+` predicate it can "unpack". (`+IntQueuePtr+` can be -unpacked immediately because it is unconditional, but `+IntQueueFB+` +`+QueueFB+` predicate it can "unpack". (`+QueuePtr_At+` can be +unpacked immediately because it is unconditional, but `+QueueFB+` cannot.) // TODO: BCP: the word "unpack" is mysterious here. -The guard/condition for `+IntQueueFB+` is `+is_null(front)+`, which is +The guard/condition for `+QueueFB+` is `+is_null(front)+`, which is why we need to do a `+split_case+` on this value. On one branch of the -`+split_case+`, we have a contradiction: the fact that `+before == -Seq_Nil{}+` (from `+IntQueueFB+`) conflicts with `+before != Seq_Nil+` +`+split_case+` we have a contradiction: the fact that `+before == +Nil{}+` (from `+QueueFB+`) conflicts with `+before != Nil+` from the precondition, so that case is immediate. On the other -branch, CN now knows that the queue is non-empty as required and type +branch, CN now knows that the queue is non-empty, as required, and type checking proceeds. When `+h == q->back+`, we are in the case where the queue contains @@ -1165,7 +1408,7 @@ the `+front+` field of the queue structure to point to the next cell. To push the verification through, we need a simple lemma about the `+snoc+` function: -include_example(exercises/queue_pop_lemma.h) +include_example(exercises/queue/pop_lemma.h) The crucial part of this lemma is the last three lines, which express a simple, general fact about `+snoc+`: @@ -1198,17 +1441,16 @@ telltale clues in the error report that suggest what the problem was? * Remove `+assert (is_null(B.next));+` from `+InqQueueFB+`. * Remove `+assert (is_null(B.next));+` from `+InqQueueAux+`. -* Remove one or both of occurrences of `+freeIntQueueCell(f)+` in - `+IntQueue_pop+`. +* Remove one or both of occurrences of `+free_queue_cell(f)+` in + `+pop_queue+`. * Remove, in turn, each of the CN annotations in the bodies of - `+IntQueue_pop+` and `+IntQueue_push+`. + `+pop_queue+` and `+push_queue+`. *Exercise*: The conditional in the `+pop+` function tests whether or not `+f == b+` to find out whether we have reached the last element of the queue. Another way to get the same information would be to test whether `+f->next == 0+`. Can you verify this version? - -Note: I (BCP) have not worked out the details, so am not sure how hard +*Note*: I (BCP) have not worked out the details, so am not sure how hard this is (or if it is even not possible, though I'd be surprised). Please let me know if you get it working! @@ -1216,136 +1458,155 @@ Please let me know if you get it working! it might seem reasonable to move the identical assignments to `+x+` in both branches to above the `+if+`. This doesn't "just work" because the ownership reasoning is different. In the first case, ownership of -`+h+` comes from `+IntQueueFB+` (because `+h == q->back+`). In the -second case, it comes from `+IntQueueAux+` (because `+h != +`+h+` comes from `+QueueFB+` (because `+h == q->back+`). In the +second case, it comes from `+QueueAux+` (because `+h != q->back+`). Can you generalize the `+snoc_facts+` lemma to handle both cases? You can get past the dereference with a `+split_case+` but formulating the lemma before the `+return+` will be a bit more complicated. - -Note: Again, this has not been shown to be possible, but Dhruv +// +*Note*: Again, this has not been shown to be possible, but Dhruv believes it should be! -=== Doubly Linked Lists +// ====================================================================== +// ====================================================================== +// ====================================================================== +=== Case Study: Doubly Linked Lists + +// TODO: BCP: The rest of the tutorial (from here to the end) needs to be checked for consistency of naming and capitalization conventions. A doubly linked list is a linked list where each node has a pointer to both the next node and the previous node. This allows for O(1) -operations for adding or removing nodes anywhere in the list. Here is -the C type definition: +operations for adding or removing nodes anywhere in the list. -include_example(exercises/Dbl_Linked_List/c_types.h) +Because of all the sharing in this data structure, the separation +reasoning is a bit tricky. We'll give you the core definitions and +then invite you to help fill in the annotations for some of the +functions that manipulate doubly linked lists. -The idea behind the representation of this list is that we don't keep -track of the front or back, but rather we take any node in the list -and have a sequence to the left and to the right of that node. The `left` -and `right` are from the point of view of the node itself, so `left` -is kept in reverse order. Additionally, similarly to in the -`Imperative Queues` example, we can reuse the `+seq+` type. +First, here is the C type definition: -include_example(exercises/Dbl_Linked_List/cn_types.h) +include_example(exercises/dll/c_types.h) -The predicate for this datatype is a bit complicated. The idea is that -we first want to own the node that is passed in. Then, we want to -follow all of the `prev` pointers to own everything backwards from the -node. We want to do the same for the `next` pointers to own everything -forwards from the node. This is how we construct our `left` and `right` -fields. +The idea behind the representation of this list is that we don't keep +track of the front or back, but rather we take any node in the list +and have a sequence to the left and to the right of that node. The `left` +and `right` are from the point of view of the node itself, so `left` +is kept in reverse order. Additionally, similarly to in the +`Imperative Queues` example, we can reuse the `+List+` type. -include_example(exercises/Dbl_Linked_List/predicates.h) +include_example(exercises/dll/cn_types.h) -Note that `Dll_at` takes ownership of the node passed in, and then -calls `Own_Backwards` and `Own_Forwards` which recursively take -ownership of the rest of the list and add their values to the `left` -and `right` sequences, respectively. +The predicate for this datatype is a bit complicated. The idea is that +we first own the node that is passed in. Then we follow all of the +`prev` pointers to own everything backwards from the node, and finally +all the `next` pointers to own everything forwards from the node, to +construct the `left` and `right` fields. -Additionally, you will notice that `Own_Forwards` and `Own_Backwards` -include `ptr_eq` assertions for the `prev` and `next` pointers. This -is to ensure that the nodes in the list are correctly -doubly linked. For example, the line -`assert (ptr_eq(n.prev, prev_pointer));` in `Own_Forwards` ensures -that the current node correctly points backward to the previous node in the -list. The line `assert(ptr_eq(prev_node.next, p));` ensures that the -previous node correctly points forward to the current node. The same can be -said for these assertions in `Own_Backwards`. +// TODO: BCP: Maybe rethink the Own_Forwards / Backwards naming -- would something like Queue_At_Left and Queue_At_Right be clearer?? +include_example(exercises/dll/predicates.h) -All three of these predicates stop once they reach a null pointer. In -this way, we can ensure that the only null pointers in the list are at -the beginning and end of the list. +Note that `Dll_at` takes ownership of the node passed in, and then +calls `Own_Backwards` and `Own_Forwards`, which recursively take +ownership of the rest of the list. -Before we move on to the functions that manipulate the doubly linked -list, we need to define a few "getter" functions that will allow us -to access the fields of our `Dll` datatype. This will make our -specifications much easier to write. +Also, notice that `Own_Forwards` and `Own_Backwards` include `ptr_eq` +assertions for the `prev` and `next` pointers. This is to ensure that +the nodes in the list are correctly doubly linked. For example, the +line `assert (ptr_eq(n.prev, prev_pointer));` in `Own_Forwards` +ensures that the current node correctly points backward to the +previous node in the list. The line `assert(ptr_eq(prev_node.next, +p));` ensures that the previous node correctly points forward to the +current node. -include_example(exercises/Dbl_Linked_List/getters.h) +Before we move on to the functions that manipulate doubly linked +lists, we need to define a few "getter" functions that will allow us +to access the fields of our `Dll` datatype. This will make the +specifications easier to write. -We also must include some boilerplate code for allocation and -deallocation. +include_example(exercises/dll/getters.h) -include_example(exercises/Dbl_Linked_List/malloc_free.h) +We also need some boilerplate for allocation and deallocation. -And we compile all of these files into a single header file. +include_example(exercises/dll/malloc_free.h) -include_example(exercises/Dbl_Linked_List/headers.h) +For convenience, we gather all of these files into a single header file. -Lastly, an important note about this representation of a doubly linked list is that there is no higher level representation of the list (such as the `int_queue` structure in the `Imperative Queues` section). This makes it difficult to reason about adding and removing things from a list that may be empty at some times. If we have an empty list, we do not want any identifier of this list to disappear altogether. To work around this problem, we represent an empty list as a null pointer and require that every function that manipulates the list must return a pointer to somewhere in the list. This way, we can always have a pointer to the list, even if it is empty. +include_example(exercises/dll/headers.h) // ====================================================================== -Now we can move on to an initialization function. Since an empty list is represented as a null pointer, we will look at initializing -a singleton list (or in other words, a list with only one item). +Now we can move on to an initialization function. Since an empty list +is represented as a null pointer, we will look at initializing a +singleton list (or in other words, a list with only one item). -include_example(exercises/Dbl_Linked_List/singleton.c) +include_example(exercises/dll/singleton.c) // ====================================================================== The `add` and `remove` functions are where it gets a little tricker. Let's start with `add`. Here is the unannotated version: -include_example(exercises/Dbl_Linked_List/add_orig.broken.c) +include_example(exercises/dll/add_orig.broken.c) -*Exercise*: Before reading on, see if you can figure out what specifications are needed. +*Exercise*: Before reading on, see if you can figure out what +specification is appropriate and what other are needed. +// TODO: BCP: I rather doubt they are going to be able to come up with this specification on their own! We need to set it up earlier with a simpler example (maybe in a whoile earlier section) showing how to use conditionals in specs. Now, here is the annotated version of the `add` operation: -include_example(exercises/Dbl_Linked_List/add.c) +include_example(exercises/dll/add.c) -First, let's look at the pre and post conditions. The `requires` -clause is straightforward. We need to own the list centered around +First, let's look at the pre- and post-conditions. The `requires` +clause is straightforward. We need to own the list centered around the node that `n` points to. `Before` is a `Dll` -that is either empty, or it has a seq to the left, -the current node that `n` points to, and a seq to the right. +that is either empty, or it has a List to the left, +the current node that `n` points to, and a List to the right. This corresponds to the state of the list when it is passed in. -In the ensures clause, we again establish ownership of the list, but this time it is centered around the added node. This means that `After` is a `Dll` structure similar to `Before`, except that the node `curr` is -now the created node. The old `curr` is pushed into the -left part of the new list. The ternary operator in the `ensures` clause is saying that if the list was empty -coming in, it now is a singleton list. Otherwise, the left left part of the list now has the data from the old `curr` node, the new `curr` node is the added node, -and the right part of the list is the same as before. - -Now, let's look at the annotations in the function body. -CN can figure out the empty list case for itself, but it needs some help with the non-empty list case. The -`split_case` on `is_null((\*n).prev)` tells CN to unpack the `Own_Backwards` predicate. Without this annotation, -CN cannot reason that we didn't lose the left half of the list before we return, and will claim we are missing a resource for returning. The `split_case` on `is_null(n->next->next)` is similar, but for unpacking the `Own_Forwards` predicate. Note that we -have to go one more node forward to make sure that everything past `n->next` is still owned at the end of the function. - - -Now let's look at the `remove` operation. Traditionally, a `remove` operation for a list returns the integer that was removed. However we also want all of our functions to return a pointer to the list. Because of this, we define a `+struct+` that includes an `int` and a `node`. - -include_example(exercises/Dbl_Linked_List/node_and_int.h) +In the ensures clause, we again establish ownership of the list, but +this time it is centered around the added node. This means that +`After` is a `Dll` structure similar to `Before`, except that the node +`curr` is now the created node. The old `curr` is pushed into the left +part of the new list. The conditional operator in the `ensures` clause +is saying that if the list was empty coming in, it now is a singleton +list. Otherwise, the left left part of the list now has the data from +the old `curr` node, the new `curr` node is the added node, and the +right part of the list is the same as before. + +Now, let's look at the annotations in the function body. CN can +figure out the empty list case for itself, but it needs some help with +the non-empty list case. The `split_case` on `is_null(n->prev)` +tells CN to unpack the `Own_Backwards` predicate. Without this +annotation, CN cannot reason that we didn't lose the left half of the +list before we return, and will claim we are missing a resource for +returning. The `split_case` on `is_null(n->next->next)` is similar, +but for unpacking the `Own_Forwards` predicate. Note that we have to +go one more node forward to make sure that everything past `n->next` +is still owned at the end of the function. + +Now let's look at the `remove` operation. Traditionally, a `remove` +operation for a list returns the integer that was removed. However we +also want all of our functions to return a pointer to the +list. Because of this, we define a `+struct+` that includes an `int` +and a `node`. + +include_example(exercises/dll/dllist_and_int.h) Now we can look at the code for the `remove` operation. Here is the un-annotated version: -include_example(exercises/Dbl_Linked_List/remove_orig.broken.c) +include_example(exercises/dll/remove_orig.broken.c) -*Exercise*: Before reading on, see if you can figure out what specifications are needed. +*Exercise*: Before reading on, see if you can figure out what +specification is appropriate and what annotations are needed. +// TODO: BCP: Again, unlikely the reader is going to be able to figure this out without help. We need some hints. Now, here is the fully annotated version of the `remove` operation: -include_example(exercises/Dbl_Linked_List/remove.c) +include_example(exercises/dll/remove.c) -First, let's look at the pre and post conditions. The `requires` clause says that we cannot remove a node from an empty list, so the pointer passed in must not be null. Then we take ownership of the list, and we +First, let's look at the pre- and post-conditions. The `requires` clause says that we cannot remove a node from an empty list, so the pointer passed in must not be null. Then we take ownership of the list, and we assign the node of that list to the identifier `del` to make our spec more readable. So `Before` refers to the `Dll` when the function is called, and `del` refers to the node that will be deleted. @@ -1354,47 +1615,125 @@ of the `node_and_int` struct as well as the `Dll` that the node is part of. Here, `After` refers to the `Dll` when the function returns. We ensure that the int that is returned is the value of the deleted node, as intended. Then we have a complicated nested ternary conditional that ensures that `After` is the same as `Before` except for the deleted node. Let's break down this conditional: -- The first guard asks if both `del.prev` and `del.next` are null. In this case, we are removing the only node in the list, so the resulting list will be empty. The `else` branch of this conditional contains it's own conditional. - -- For the following conditional, the guard checks if 'del.prev' is NOT null. Note that in the code, this means that the returned node is `del.next`, regardless of whether or not `del.prev` is null. If this is the case, `After` is now centered around `del.next`, and the left part of the list is the same as before. Since `del.next` was previously the head of the right side, the right side loses its head in `After`. This is where we get `After == Dll{left: Left(Before), curr: Node(After), right: tl(Right(Before))}`. - -- The final `else` branch is the case where `del.next` is null, but `del.prev` is not null. In this case, the returned node is `del.prev`. This branch follows the same logic as the one before it, except now we are taking the head of the left side rather than the right side. Now the right side is unchanged, and the left side is just the tail, as seen shown in -`After == Dll{left: tl(Left(Before)), curr: Node(After), right: Right(Before)};` - -Now, let's look at the annotations in the function body. These are similar to in the `add` function. Both of these `split_case` annotations are needed to unpack the `Own_Forwards` and `Own_Backwards` predicates. Without these annotations, CN will not be able to reason that we didn't lose the left or right half of the list before we return, and will claim we are missing a resource for returning. +- The first guard asks if both `del.prev` and `del.next` are null. In this case, we are removing the only node in the list, so the resulting list will be empty. The `else` branch of this conditional contains its own conditional. + +- For the following conditional, the guard checks if 'del.prev' is + _not_ null. This means that the returned node is `del.next`, + regardless of whether or not `del.prev` is null. If this is the + case, `After` is now centered around `del.next`, and the left part + of the list is the same as before. Since `del.next` was previously + the head of the right side, the right side loses its head in + `After`. This is where we get `After == Dll{left: + Left_Sublist(Before), curr: Node(After), right: Tl(Right(Before))}`. + +- The final `else` branch is the case where `del.next` is null, but +`del.prev` is not null. In this case, the returned node is +`del.prev`. This branch follows the same logic as the one before it, +except now we are taking the head of the left side rather than the +right side. Now the right side is unchanged, and the left side is just +the tail, as seen shown in `After == Dll{left: +Tl(Left_Sublist(Before)), curr: Node(After), right: Right(Before)};` + +The annotations in the function body are similar to in the `add` +function. Both of these `split_case` annotations are needed to unpack +the `Own_Forwards` and `Own_Backwards` predicates. Without them, CN +will not be able to reason that we didn't lose the left or right half +of the list before we return and will claim we are missing a resource +for returning. // ====================================================================== -*Exercise*: There are many other functions that one might want to implement for a doubly linked list. For example, one might want to implement a function that appends one list to another, or a function that reverses a list. Try implementing these functions and writing their specifications. - -=== The Runway - -Suppose we have been tasked with writing a program that simulates a runway at an airport. This airport is very small, so it only has one runway that is used for both takeoffs and landings. We want to verify that the runway is always safe by implementing the following specifications into CN: - -1. The runway has two modes: departure mode and arrival mode. The two modes can never be active at the same time, and neither mode is active at the beginning of the day. - -2. There is always a waitlist of planes that need to land at the airport and planes that need to leave the airport at a given moment. These can be modeled with counters `W_A` for the number of planes waiting to arrive, and `W_D` for the number of planes waiting to depart. +*Exercise*: There are many other functions that one might want to + implement for a doubly linked list. For example, one might want to + implement a function that appends one list to another, or a function + that reverses a list. Try implementing a few of these functions and + writing their specifications. -3. At any time, a plane is either waiting to arrive, waiting to depart, or on the runway. Once a plane has started arriving or departing, the corresponding counter (`W_A` or `W_D`) is decremented. There is no need to keep track of planes once they have arrived or departed. Additionally, once a plane is waiting to arrive or depart, it continues waiting until it has arrived or departed. - - -4. Let’s say it takes 5 minutes for a plane to arrive or depart. During these 5 minutes, no other plane may use the runway. We can keep track of how long a plane has been on the runway with the `Runway_Counter`. If the `Runway_Counter` is at 0, then there is currently no plane using the runway, and it is clear for another plane to begin arriving or departing. Once the `Runway_Counter` reaches 5, we can reset it at the next clock tick. One clock tick represents 1 minute. - -5. If there is at least one plane waiting to depart and no cars waiting to arrive, then the runway is set to departure mode (and vice versa for arrivals). - -6. If both modes of the runway are inactive and planes become ready to depart and arrive simultaneously, the runway will activate arrival mode first. If the runway is in arrival mode and there are planes waiting to depart, no more than 3 planes may arrive from that time point. When either no more planes are waiting to arrive or 3 planes have arrived, the runway switches to departure mode. If the runway is on arrival mode and no planes are waiting to depart, then the runway may stay in arrival mode until a plane is ready to depart, from which time the 3-plane limit is imposed (and vice versa for departures). Put simply, if any planes are waiting for a mode that is inactive, that mode will become active no more than 15 minutes later (5 minutes for each of 3 planes). - -To encode all this in CN, we first need a way to describe the state of the runway at a given time. We can use a *struct* that includes the following fields: - -- `ModeA` and `ModeD` to represent the arrival and departure modes, respectively. We can define constants for `ACTIVE` and `INACTIVE`, as described in the `Constants` section above. -- `W_A` and `W_D` to represent the number of planes waiting to arrive and depart, respectively. -- `Runway_Time` to represent the time (in minutes) that a plane has spent on the runway while arriving or departing. -- `Plane_Counter` to represent the number of planes that have arrived or departed while planes are waiting for the other mode. This will help us keep track of the 3-plane limit as described in *(6)*. +*Exercise*: For extra practice, try coming up with one or two +variations on the Dll data structure itself (there are many!). +// ====================================================================== +// ====================================================================== +// ====================================================================== +=== Case Study: Airport Simulation + +// TODO: BCP: I'm nervous about this case study -- it is not nearly as well debugged as the others, and it seems potentially quite confusing. I propose deleting it, but if other like it we can try to whip it into better shape... + +Suppose we have been tasked with writing a program that simulates a +runway at an airport. This airport is very small, so it only has one +runway, which is used for both takeoffs and landings. We want to +verify that the runway is always used safely, by checking the +following informal specification: + +1. The runway has two modes: departure mode and arrival mode. The two + modes can never be active at the same time. Neither mode is active + at the beginning of the day. +// TODO: BCP: Would it be simpler to say it is in arrival mode at the beginning of the day? What difference would that make? (Saying there are two modes and then immediately introducing a third one is a bit confusing.) + +2. At any given moment, there is a waiting list of planes that need to + land at the airport and planes that need to leave the + airport. These are modeled with counters `W_A` for the number of + planes waiting to arrive, and `W_D` for the number of planes + waiting to depart. + +3. At any moment, a plane is either waiting to arrive, waiting to + depart, or on the runway. Once a plane has started arriving or + departing, the corresponding counter (`W_A` or `W_D`) is + decremented. There is no need to keep track of planes once they + have arrived or departed. Additionally, once a plane is waiting to + arrive or depart, it continues waiting until it has arrived or + departed. + +4. It takes 5 minutes for a plane to arrive or depart. During these 5 + minutes, no other plane may use the runway. We can keep track of + how long a plane has been on the runway with the + `Runway_Counter`. If the `Runway_Counter` is at 0, then there is + currently no plane using the runway, and it is clear for another + plane to begin arriving or departing. Once the `Runway_Counter` + reaches 5, we can reset it at the next clock tick. One clock tick + represents 1 minute. + +5. If there is at least one plane waiting to depart and no cars + waiting to arrive, then the runway is set to departure mode (and + vice versa for arrivals). + +6. If both modes of the runway are inactive and planes become ready to + depart and arrive simultaneously, the runway will activate arrival + mode first. If the runway is in arrival mode and there are planes + waiting to depart, no more than 3 planes may arrive from that time + point. When either no more planes are waiting to arrive or 3 planes + have arrived, the runway switches to departure mode. If the runway + is on arrival mode and no planes are waiting to depart, then the + runway may stay in arrival mode until a plane is ready to depart, + from which time the 3-plane limit is imposed (and vice versa for + departures). Put simply, if any planes are waiting for a mode that + is inactive, that mode will become active no more than 15 minutes + later (5 minutes for each of 3 planes). + +To encode all this in CN, we first need a way to describe the state of +the runway at a given time. We can use a *struct* that includes the +following fields: + +- `ModeA` and `ModeD` to represent the arrival and departure modes, + respectively. We can define constants for `ACTIVE` and `INACTIVE`, + as described in the `Constants` section above. + +- `W_A` and `W_D` to represent the number of planes waiting to arrive + and depart, respectively. + +- `Runway_Time` to represent the time (in minutes) that a plane has + spent on the runway while arriving or departing. + +- `Plane_Counter` to represent the number of planes that have arrived + or departed while planes are waiting for the other mode. This will + help us keep track of the 3-plane limit as described in *(6)*. include_example(exercises/runway/state.h) -Next, we need to specify what makes a state valid. We must define a rigorous specification in order to ensure that the runway is always safe and working as intended. Try thinking about what this might look like before looking at the code below. +Next, we need to specify what makes a state valid. We must define a +rigorous specification in order to ensure that the runway is always +safe and working as intended. Try thinking about what this might look +like before looking at the code below. include_example(exercises/runway/valid_state.h) @@ -1422,11 +1761,18 @@ First, let's look at an initialization function and functions to update `Plane_C include_example(exercises/runway/funcs1.h) -*Exercise*: Now try adding your own specifications to the following functions. Make sure that you specify a valid state as a pre and post condition for every function. If you get stuck, the solution is in the solutions folder. +*Exercise*: Now try adding your own specifications to the following +functions. Make sure that you specify a valid state as a pre- and +post-condition for every function. If you get stuck, the solution is +in the solutions folder. include_example(exercises/runway/funcs2.c) -*Exercise*: For extra practice, try coming up with different specifications or variations for this exercise and implementing them yourself! +// ====================================================================== + +== Acknowledgment of Support and Disclaimer + +This material is based upon work supported by the Air Force Research Laboratory (AFRL) and Defense Advanced Research Projects Agencies (DARPA) under Contract No. FA8750-24-C-B044, a European Research Council (ERC) Advanced Grant “ELVER” under the European Union’s Horizon 2020 research and innovation programme (grant agreement no. 789108), and additional funding from Google. The opinions, findings, and conclusions or recommendations expressed in this material are those of the authors and do not necessarily reflect the views of the Air Force Research Laboratory (AFRL). // ====================================================================== @@ -1447,9 +1793,9 @@ Further topics: https://www.geeksforgeeks.org/data-structures/#most-popular-data-structures - Maybe add some explanation of -- or at least a pointer to -- Dhruv's Iris-in-C examples: - queue_pop_lemma_stages.c - queue_push_induction.c - queue_pop_unified.c + pop_queue_lemma_stages.c + push_queue_induction.c + pop_queue_unified.c Further exercises: - Some exercises that get THEM to write predicates, datatype @@ -1460,15 +1806,15 @@ Misc things to do: - naming issues - rename == to ptr_eq everywhere in specs - - rename list to seq in filenames. or go more radical and rename seq to cnlist - - consider renaming IntList to just List (and int_list to just list, + - rename list to List in filenames. or go more radical and rename List to cnlist + - consider renaming SLList_At to just List (and sllist to just list, etc.) everywhere (since we are only dealing with one kind of list in the tutorial, the extra pedantry is not getting us much; and this simplification would avoid trying to fix conventions that all CN code should use everywhere...) - - related: the name Seq_Cons is awkward for several reasons: + - related: the name Cons is awkward for several reasons: - long / verbose (nothing to do about that, I guess) - - Seq is capitalized, but it means seq + - Seq is capitalized, but it means List - most important part is buried in the middle - What are the established C conventions here?? @@ -1481,9 +1827,9 @@ Misc things to do: defining an "exit" function" with trivial pre- and postconditions (true / false). - - In queue.c, when I tried /*@ unfold IntQueueAux (F.front, F.back, + - In queue.c, when I tried /*@ unfold QueueAux (F.front, F.back, B.first); @*/ I was confused by "the specification function - `IntQueueAux' is not declared". I guess this is, again, the + `QueueAux' is not declared". I guess this is, again, the distinction between functions and predicates...? - In debugging the queue example, The fact that some of the diff --git a/src/underconstruction/bst/README.md b/src/underconstruction/bst/README.md new file mode 100644 index 00000000..3a203df5 --- /dev/null +++ b/src/underconstruction/bst/README.md @@ -0,0 +1,6 @@ +This example still needs some work, for an amusing reason: It gives a +wrong version of the BST invariant, then specifies each function +slightly incorrectly in an analogous way! + +It would be good to fix the specs and then make an extended exercise +out of it for the tutorial. diff --git a/src/underconstruction/bst/c_types.h b/src/underconstruction/bst/c_types.h new file mode 100644 index 00000000..196270b3 --- /dev/null +++ b/src/underconstruction/bst/c_types.h @@ -0,0 +1,21 @@ +// Defines structure of Binary Tree Nodes +// Defines the specs for allocating and freeing the Nodes + +struct node { + int data; + struct node* left; + struct node* right; +}; + +extern struct node* malloc_node(); +/*@ spec malloc_node(); + requires true; + ensures take R = Block(return); + !ptr_eq(return, NULL); +@*/ + +extern void freenode (struct node *t); +/*@ spec freenode(pointer t); + requires take R = Block(t); + ensures true; +@*/ diff --git a/src/underconstruction/bst/cn_getters.h b/src/underconstruction/bst/cn_getters.h new file mode 100644 index 00000000..d0a84d31 --- /dev/null +++ b/src/underconstruction/bst/cn_getters.h @@ -0,0 +1,48 @@ +// Defines basic CN functions to extract a member of the Tree datatype + +/*@ +function (i32) Data_Of (datatype Tree sapling) +{ + match sapling + { + Leaf {} => + { + default + } + Node {Left : _, Data : dat, Right: _} => + { + dat + } + } +} + +function (datatype Tree) get_lb (datatype Tree sapling) +{ + match sapling + { + Leaf {} => + { + Leaf {} + } + Node {Left : left, Data : _ , Right: _} => + { + left + } + } +} + +function (datatype Tree) get_rb (datatype Tree sapling) +{ + match sapling + { + Leaf {} => + { + Leaf {} + } + Node {Left : _, Data : _ , Right: right} => + { + right + } + } +} +@*/ diff --git a/src/underconstruction/bst/cn_types.h b/src/underconstruction/bst/cn_types.h new file mode 100644 index 00000000..024b73de --- /dev/null +++ b/src/underconstruction/bst/cn_types.h @@ -0,0 +1,26 @@ +// FILL IN: This is where we define the Tree datatype and the +// associated Tree_At predicate + +/*@ +datatype Tree { + Leaf {}, + Node {datatype Tree Left, i32 Data, datatype Tree Right} +} + +predicate (datatype Tree) Tree_At (pointer t) +{ + if (is_null(t)) + { + return Leaf{}; + } + else + { + take T = Owned(t); + take L = Tree_At(T.left); + assert (L == Leaf{} || Data_Of(L) < T.data); + take R = Tree_At(T.right); + assert (R == Leaf{} || Data_Of(R) >= T.data); + return (Node {Left: L, Data: T.data, Right: R}); + } +} +@*/ diff --git a/src/underconstruction/bst/constructors.h b/src/underconstruction/bst/constructors.h new file mode 100644 index 00000000..cfc368a6 --- /dev/null +++ b/src/underconstruction/bst/constructors.h @@ -0,0 +1,53 @@ +// Constructors for trees + +struct node* node_nil() +/*@ ensures take Ret = Tree_At(return); + Ret == Leaf{}; + @*/ +{ + return 0; +} + +struct node* node_cons_left(int val, struct node* left_b) +/*@ requires take L = Tree_At(left_b); + (L == Leaf{} || Data_Of(L) < val); + ensures take Ret = Tree_At(return); + Ret == Node{Left: L, Data: val, Right: Leaf{}}; + @*/ +{ + struct node *t = malloc_node(); + t->data = val; + t->left = left_b; + t->right = 0; + return t; +} + +struct node* node_cons_right(int val, struct node* right_b) +/*@ requires take R = Tree_At(right_b); + (R == Leaf{} || Data_Of(R) >= val); + ensures take Ret = Tree_At(return); + Ret == Node{Left: Leaf{}, Data: val, Right: R}; + @*/ +{ + struct node *t = malloc_node(); + t->data = val; + t->left = 0; + t->right = right_b; + return t; +} + +struct node* node_cons_both(int val, struct node* left_b, struct node* right_b) +/*@ requires take L = Tree_At(left_b); + take R = Tree_At(right_b); + (L == Leaf{} || Data_Of(L) < val); + (R == Leaf{} || Data_Of(R) >= val); + ensures take Ret = Tree_At(return); + Ret == Node{Left: L, Data: val, Right: R}; +@*/ +{ + struct node *t = malloc_node(); + t->data = val; + t->left = left_b; + t->right = right_b; + return t; +} diff --git a/src/underconstruction/bst/contains.c b/src/underconstruction/bst/contains.c new file mode 100644 index 00000000..aee1293e --- /dev/null +++ b/src/underconstruction/bst/contains.c @@ -0,0 +1,65 @@ +#include "./headers.h" +// returns true (1u32) or false (u32), if value is an node in the binary Tree + +/* FILL IN CN FUNCTION SPEC DEFINTION HERE */ +/* --BEGIN-- */ +/*@ +function [rec] (u32) exists(datatype Tree sapling, i32 value) +{ + match sapling + { + Leaf {} => + { + 0u32 + } + Node {Left: l, Data: dat, Right: r} => + { + let lb_exist = exists(l, value); + let rb_exist = exists(r, value); + ((value == dat) + ? 1u32 + : ((value < dat) ? lb_exist : rb_exist)) + } + } +} +@*/ +/* --END-- */ + +typedef enum { false, true } bool; + +bool node_containsValue (struct node* t, int value) +/* FILL IN HERE */ +/* --BEGIN-- */ +/*@ requires take t1 = Tree_At(t); + ensures take t2 = Tree_At(t); + t1 == t2; + return == exists(t1, value); +@*/ +/* --END-- */ +{ + /* --BEGIN-- */ + /*@ unfold exists(t1, value); @*/ + /* --END-- */ + if (t == 0) + { + return false; + } + else + { + if (t->data == value) + { + return true; + } + else + { + if (value < t->data) + { + return node_containsValue(t->left, value); + } + else + { + return node_containsValue(t->right, value); + } + } + } +} diff --git a/src/underconstruction/bst/copy.c b/src/underconstruction/bst/copy.c new file mode 100644 index 00000000..4b80c7a0 --- /dev/null +++ b/src/underconstruction/bst/copy.c @@ -0,0 +1,26 @@ +#include "./headers.h" +#include "./constructors.h" +// takes in binary tree, Returns copy of it + +struct node* node_copy (struct node* t) +/* FILL IN HERE */ +/* --BEGIN-- */ +/*@ requires take T1 = Tree_At(t); + ensures take T1_ = Tree_At(t); + take T2 = Tree_At(return); + T1 == T1_; + T1 == T2; +@*/ +/* --END-- */ +{ + if (t == 0) + { + return node_nil(); + } + else + { + struct node* new_left = node_copy(t->left); + struct node* new_right = node_copy(t->right); + return node_cons_both(t->data, new_left, new_right); + } +} \ No newline at end of file diff --git a/src/underconstruction/bst/create_node.c b/src/underconstruction/bst/create_node.c new file mode 100644 index 00000000..075ba82e --- /dev/null +++ b/src/underconstruction/bst/create_node.c @@ -0,0 +1,19 @@ +#include "./headers.h" +// Initializes new node with value given as its argument + +struct node* node_create_node(int value) +/* FILL IN HERE */ +/* --BEGIN-- */ +/*@ ensures take T = Tree_At(return); + T == Node {Left: Leaf{}, Data: value, Right: Leaf{}}; + !is_null(return); + Data_Of(T) == value; +@*/ +/* --END-- */ +{ + struct node* node = malloc_node(); + node->data = value; + node->left = 0; + node->right = 0; + return node; +} \ No newline at end of file diff --git a/src/underconstruction/bst/depth.c b/src/underconstruction/bst/depth.c new file mode 100644 index 00000000..2798cc0f --- /dev/null +++ b/src/underconstruction/bst/depth.c @@ -0,0 +1,49 @@ +#include "./headers.h" +// counts the furthest distance from the root to the leaf node + +/* FILL IN CN FUNCTION SPEC DEFINTION HERE */ +/* --BEGIN-- */ +/*@ +function [rec] (u32) depth (datatype Tree sapling) +{ + match sapling + { + Leaf {} => + { + 0u32 + } + Node {Left: l, Data: dat, Right: r} => + { + let left_b = depth(l); + let right_b = depth(r); + ((left_b > right_b) ? (1u32 + left_b) : (1u32 + right_b)) + } + } +} +@*/ +/* --END-- */ + +unsigned int node_depth(struct node* t) +/* FILL IN HERE */ +/* --BEGIN-- */ +/*@ requires take t1 = Tree_At(t); + ensures take t2 = Tree_At(t); + t1 == t2; + return == depth(t1); +@*/ +/* --END-- */ +{ + /* --BEGIN-- */ + /*@ unfold depth(t1); @*/ + /* --END-- */ + if (t == 0) + { + return 0; + } + else + { + unsigned int left_b = node_depth(t->left); + unsigned int right_b = node_depth(t->right); + return ((left_b > right_b) ? (1 + left_b) : (1 + right_b)); + } +} diff --git a/src/underconstruction/bst/free.c b/src/underconstruction/bst/free.c new file mode 100644 index 00000000..84f3204c --- /dev/null +++ b/src/underconstruction/bst/free.c @@ -0,0 +1,20 @@ +#include "./headers.h" +// deallocates all the nodes in the binary tree + +void node_free_tree (struct node* t) +/* FILL IN HERE */ +/* --BEGIN-- */ +/*@ requires take v1 = Tree_At(t); @*/ +/* --END-- */ +{ + if (t == 0) + { + return; + } + else + { + node_free_tree(t->right); + node_free_tree(t->left); + freenode(t); + } +} \ No newline at end of file diff --git a/src/underconstruction/bst/getter.c b/src/underconstruction/bst/getter.c new file mode 100644 index 00000000..f3983af8 --- /dev/null +++ b/src/underconstruction/bst/getter.c @@ -0,0 +1,63 @@ +#include "./headers.h" +// Extracts the members of a given Tree node + +int get_Tree_Data (struct node *t) +/* FILL IN HERE */ +/* --BEGIN-- */ +/*@ requires take v1 = Tree_At(t); + ensures take v2 = Tree_At(t); + v1 == v2; + return == (is_null(t) ? 0i32 : Data_Of(v2)); @*/ +/* --END-- */ +{ + if (t) + { + return t->data; + } + else + { + return 0; + } +} + +struct node* get_Tree_Left (struct node *t) +/* FILL IN HERE */ +/* --BEGIN-- */ +/*@ requires take v1 = Owned(t); + take v1_left = Owned(v1.left); + ensures take v2 = Owned(t); + take v2_left = Owned(v2.left); + v1 == v2 && v1_left == v2_left; + ptr_eq(return, ((is_null(t)) ? (t) : (v1.left))); @*/ +/* --END-- */ +{ + if (t) + { + return t->left; + } + else + { + return 0; + } +} + +struct node* get_Tree_Right (struct node *t) +/* FILL IN HERE */ +/* --BEGIN-- */ +/*@ requires take v1 = Owned(t); + take v1_right = Owned(v1.right); + ensures take v2 = Owned(t); + take v2_right = Owned(v2.right); + v1 == v2 && v1_right == v2_right; + ptr_eq(return, ((is_null(t)) ? (t) : (v1.right))); @*/ +/* --END-- */ +{ + if (t) + { + return t->right; + } + else + { + return 0; + } +} \ No newline at end of file diff --git a/src/underconstruction/bst/headers.h b/src/underconstruction/bst/headers.h new file mode 100644 index 00000000..8538a522 --- /dev/null +++ b/src/underconstruction/bst/headers.h @@ -0,0 +1,10 @@ +// Header file for basic node definitions and functionality + +#ifndef _TREE_H +#define _TREE_H + +#include "./c_types.h" +#include "./cn_types.h" +#include "./cn_getters.h" + +#endif //_TREE_H diff --git a/src/underconstruction/bst/insert.c b/src/underconstruction/bst/insert.c new file mode 100644 index 00000000..8b67415c --- /dev/null +++ b/src/underconstruction/bst/insert.c @@ -0,0 +1,61 @@ +#include "./headers.h" +#include "create_node.c" +// inserts a new node into binary tree + +/* FILL IN CN FUNCTION SPEC HERE */ +/* --BEGIN-- */ +/*@ +function [rec] (datatype Tree) insert (datatype Tree sapling, i32 value) +{ + match sapling + { + Leaf{} => + { + Node{Left: Leaf{}, Data: value, Right: Leaf{}} + } + Node{Left: l, Data: dat, Right: r} => + { + + ((value < dat) ? Node {Left: insert(l, value), Data: dat, Right: r} : + Node {Left: l, Data: dat, Right: insert(r, value)}) + } + } +} +@*/ +/* --END-- */ + +struct node* node_insert(struct node* t, int value) +/* FILL IN HERE */ +/* --BEGIN-- */ +/*@ requires take T1 = Tree_At(t); + ensures take Ret = Tree_At(return); + Ret == insert(T1,value); + T1 != Leaf{} implies Data_Of(Ret) == Data_Of(T1); + !is_null(return); +@*/ +/* --END-- */ +{ + /* --BEGIN-- */ + /*@ unfold insert(Leaf{}, value); @*/ + /*@ unfold insert(T1, value); @*/ + /* --END-- */ + if (t == 0) + { + struct node* add = node_create_node(value); + return add; + } + else + { + if (value < t->data) + { + t->left = node_insert(t->left, value); + return t; + + } + else + { + t->right = node_insert(t->right, value); + return t; + } + } +} diff --git a/src/underconstruction/bst/length.c b/src/underconstruction/bst/length.c new file mode 100644 index 00000000..9b90b129 --- /dev/null +++ b/src/underconstruction/bst/length.c @@ -0,0 +1,49 @@ +#include "./headers.h" +// Function which counts all the nodes in the tree + +/* FILL IN CN FUNCTION SPEC DEFINTION HERE */ +/* --BEGIN-- */ +/*@ +function [rec] (u32) length (datatype Tree sapling) +{ + match sapling + { + Leaf {} => + { + 0u32 + } + Node {Left: l, Data: dat, Right: r} => + { + let left_b = length(l); + let right_b = length(r); + (1u32 + left_b + right_b) + } + } +} +@*/ +/* --END-- */ + +unsigned int node_length(struct node* t) +/* FILL IN HERE */ +/* --BEGIN-- */ +/*@ requires take t1 = Tree_At(t); + ensures take t2 = Tree_At(t); + t1 == t2; + return == length(t1); +@*/ +/* --END-- */ +{ + /* --BEGIN-- */ + /*@ unfold length(t1); @*/ + /* --END-- */ + if (t == 0) + { + return 0; + } + else + { + unsigned int left_b = node_length(t->left); + unsigned int right_b = node_length(t->right); + return (1 + left_b + right_b); + } +} \ No newline at end of file diff --git a/src/underconstruction/bst/search.c b/src/underconstruction/bst/search.c new file mode 100644 index 00000000..1e8653ac --- /dev/null +++ b/src/underconstruction/bst/search.c @@ -0,0 +1,63 @@ +#include "./headers.h" +// Searches for a node with the given value in the binary Tree + +/* FILL IN CN FUNCTION SPEC DEFINTION HERE */ +/* --BEGIN-- */ +/*@ +function [rec] (datatype Tree) search(datatype Tree sapling, i32 value) +{ + match sapling + { + Leaf {} => + { + Leaf{} + } + Node {Left: l, Data: dat, Right: r} => + { + ((value == dat) ? sapling : + ((value < dat) ? search(l, value) : search(r, value))) + } + } +} +@*/ +/* --END-- */ + +struct node* node_search(struct node* t, int value) +/* FILL IN HERE */ +/* --BEGIN-- */ +/*@ requires take t1 = Tree_At(t); + ensures take t2 = Tree_At(t); + t1 == t2; + let Ret = search(t1, value); + (Ret == Leaf{} ? is_null(return) : Data_Of(Ret) == value); +@*/ +/* --END-- */ +{ + /* --BEGIN-- */ + /*@ unfold search(t1, value); @*/ + /* --END-- */ + if (t == 0) + { + + return 0; + } + else + { + + if (t->data == value) + { + return t; + } + else + { + if (value < t->data) + { + return node_search(t->left, value); + } + else + { + return node_search(t->right, value); + } + } + } +} \ No newline at end of file diff --git a/src/underconstruction/bst/sum.c b/src/underconstruction/bst/sum.c new file mode 100644 index 00000000..70fb6393 --- /dev/null +++ b/src/underconstruction/bst/sum.c @@ -0,0 +1,51 @@ +#include "./headers.h" +// Sums up the data values of the nodes of the binary tree + +/* FILL IN CN FUNCTION SPEC DEFINTION HERE */ +/* --BEGIN-- */ +/*@ +function [rec] (u32) sum(datatype Tree sapling) +{ + match sapling + { + Leaf {} => + { + 0u32 + } + Node {Left: l, Data: dat, Right: r} => + { + let rt_val = (u32) dat; + let sum_lb = sum(l); + let sum_rb = sum(r); + (rt_val + sum_lb + sum_rb) + } + } +} +@*/ +/* --END-- */ + +unsigned int node_sum(struct node* t) +/* FILL IN HERE */ +/* --BEGIN-- */ +/*@ requires take t1 = Tree_At(t); + ensures take t2 = Tree_At(t); + t1 == t2; + return == sum(t1); +@*/ +/* --END-- */ +{ + /* --BEGIN-- */ + /*@ unfold sum(t1); @*/ + /* --END-- */ + if (t == 0) + { + return 0; + } + else + { + unsigned int sum_lb = node_sum(t->left); + unsigned int sum_rb = node_sum(t->right); + unsigned int root_val = t->data; + return (root_val + sum_lb + sum_rb); + } +} \ No newline at end of file From 97366ef1fd6f7bcbc7b70766c2f70603391063cf Mon Sep 17 00:00:00 2001 From: Iavor Diatchki Date: Tue, 15 Oct 2024 13:33:46 -0700 Subject: [PATCH 134/152] Checkpoint: working on a binary search tree specificaion --- src/examples/bst/bst_map.c | 165 ++++++++++++++++++++++++++++++++++ src/examples/bst/bst_map.h | 29 ++++++ src/examples/bst/bst_map_cn.c | 10 +++ src/examples/bst/bst_map_cn.h | 153 +++++++++++++++++++++++++++++++ src/examples/bst/bst_sem_cn.h | 53 +++++++++++ 5 files changed, 410 insertions(+) create mode 100644 src/examples/bst/bst_map.c create mode 100644 src/examples/bst/bst_map.h create mode 100644 src/examples/bst/bst_map_cn.c create mode 100644 src/examples/bst/bst_map_cn.h create mode 100644 src/examples/bst/bst_sem_cn.h diff --git a/src/examples/bst/bst_map.c b/src/examples/bst/bst_map.c new file mode 100644 index 00000000..d6cbf1e7 --- /dev/null +++ b/src/examples/bst/bst_map.c @@ -0,0 +1,165 @@ + +#include "bst_map.h" +#include "bst_map_cn.h" + + +#if 0 +/* Allocate a new singleton node */ +struct MapNode *newNode(KEY key, VALUE value) +/*@ +requires + true; +ensures + take node = Owned(return); + node.key == key; + node.value == value; + is_null(node.smaller); + is_null(node.larger); +@*/ +{ + struct MapNode *node = malloc_MapNode(); + node->key = key; + node->value = value; + node->smaller = NULL; + node->larger = NULL; + return node; +} +#endif + +#if 0 +// Demonstrates a recursive traversal of a tree. +size_t count(struct MapNode const *node) +/*@ +requires + take before = BST(node); +ensures + take after = BST(node); + before == after; +@*/ +{ + if (node == NULL) return 0; + return (size_t)1 + count(node->smaller) + count(node->larger); +} +#endif + + +#if 1 +// A no-op function, just shows hows to traverse a tree with a loop. +void traverse(struct MapNode *node) +/*@ +requires + take start = BST(node, anyKey()); +ensures + take end = BST(node, anyKey()); + // start == end; +@*/ +{ + struct MapNode *cur = node; + + /*@ split_case is_null(cur); @*/ + while (cur) + /*@ + inv + {node} unchanged; + take focus = BSTFocus(node,cur,anyKey()); + // start == unfocus(focus); + let cur_prev = cur; + @*/ + { + cur = cur->smaller; + /*@ apply GoSmaller(node,cur_prev,anyKey()); @*/ + } +} +#endif + + +#if 0 +struct MapNode *findLeast(struct MapNode *p) +/*@ +requires + take tree = BST(p); +ensures + take final_tree = BST(p); + tree == final_tree; +@*/ +{ + struct MapNode *parent = NULL; + struct MapNode *cur = p; + while(cur != NULL) + /*@ + inv + {p} unchanged; + take front = BSTUpTo(cur,parent); + @*/ + { + parent = cur; + cur = p->smaller; + + } + return parent; +} +#endif + + +#if 0 +/* Look for a node and its parent */ +struct MapNode *findParent(struct MapNode **node, KEY key) +{ + struct MapNode *parent = NULL; + struct MapNode *cur = *node; + while (cur != NULL) + { + KEY k = cur->key; + if (k == key) { *node = cur; return parent; } + parent = cur; + cur = k < key? cur->larger : cur->smaller; + } + *node = cur; + return parent; +} +#endif + + +#if 0 +/* Create an empty set */ +struct Map map_empty() +/*@ @*/ +{ + return (struct Map) { .root = NULL }; +} +#endif + +#if 0 +/* Lookup a value in a map */ +bool map_lookup(struct Map map, KEY key, VALUE *value) { + struct MapNode *search = map.root; + findParent(&search, key); + if (search == NULL) { return false; } + *value = search->value; + return true; +} +#endif + +#if 0 +/* Insert an element into a map. Overwrites previous if already present. */ +void map_insert(struct Map *map, KEY key, VALUE value) { + struct MapNode *search = map->root; + struct MapNode *parent = findParent(&search, key); + if (search != NULL) { + search->value = value; + return; + } + + if (parent == NULL) { + map->root = newNode(key,value); + return; + } + + struct MapNode *new_node = newNode(key,value); + if (parent->key < key) { + parent->larger = new_node; + } else { + parent->smaller = new_node; + } +} +#endif diff --git a/src/examples/bst/bst_map.h b/src/examples/bst/bst_map.h new file mode 100644 index 00000000..bc28dd72 --- /dev/null +++ b/src/examples/bst/bst_map.h @@ -0,0 +1,29 @@ +#ifndef SET_H +#define SET_H +/* A set defined as binary search tree */ + +#include + +#define KEY int +#define VALUE long + +struct MapNode { + KEY key; + int ignore; + VALUE value; + struct MapNode *smaller; + struct MapNode *larger; +}; + +struct MapNode* malloc_MapNode(); + + +struct Map { + struct MapNode *root; +}; + +struct Map map_empty(); +bool map_lookup(struct Map map, KEY key, VALUE *value); + + +#endif // SET_H diff --git a/src/examples/bst/bst_map_cn.c b/src/examples/bst/bst_map_cn.c new file mode 100644 index 00000000..ac5dbddc --- /dev/null +++ b/src/examples/bst/bst_map_cn.c @@ -0,0 +1,10 @@ +#include +#include + +#include "bst_map_cn.h" + +struct MapNode *malloc_MapNode() { + struct MapNode *result = malloc(sizeof(struct MapNode)); + assert(result != NULL); + return result; +} diff --git a/src/examples/bst/bst_map_cn.h b/src/examples/bst/bst_map_cn.h new file mode 100644 index 00000000..b0296da9 --- /dev/null +++ b/src/examples/bst/bst_map_cn.h @@ -0,0 +1,153 @@ +#ifndef BST_MAP_CN_H +#define BST_MAP_CN_H + +#include "bst_map.h" +#include "bst_sem_cn.h" + +// Specialized `malloc` +extern struct MapNode *malloc_MapNode(); +/*@ +spec malloc_MapNode(); +requires + true; +ensures + take v = Block(return); +@*/ + + + +/*@ + +// ***************************************************************************** +// Consuming an entire tree +// ***************************************************************************** + + +// Semantic data stored at a node +function (NodeData) getNodeData(struct MapNode node) { + { key: node.key, value: node.value } +} + + +// A binary search tree, where all keys are in the given range. +predicate BST BST(pointer root, Interval range) { + if (is_null(root)) { + return Leaf {}; + } else { + take node = Owned(root); + let data = getNodeData(node); + assert(inInterval(node.key, range)); + let ranges = splitInterval(node.key, range); + take smaller = BST(node.smaller, ranges.lower); + take larger = BST(node.larger, ranges.upper); + return Node { data: data, smaller: smaller, larger: larger }; + } +} + + + + +// ***************************************************************************** +// Focusing on a node in the tree +// ***************************************************************************** + +type_synonym BSTFocus = { + // Indicates if we are at a node or a leaf. + boolean at_leaf, + + // Rest of the tree + BST done, + + // Focused node + struct MapNode node, + BST smaller, + BST larger +} + +predicate BSTFocus BSTFocus(pointer root, pointer child, Interval range) { + if (is_null(child)) { + take tree = BST(root, range); + return { at_leaf: true, done: tree, + node: default, + smaller: default, + larger: default }; + } else { + take node = Owned(child); + take result = BSTNodeUpTo(root, child, node, range); + let ranges = splitInterval(node.key,result.range); + take smaller = BST(node.smaller, ranges.lower); + take larger = BST(node.larger, ranges.upper); + return { at_leaf: false, done: result.tree, node: node, + smaller: smaller, larger: larger }; + } +} + +// Consume parts of the tree starting at `p` until we get to `c`. +// We do not consume `c`. +// `child` is the node stored at `c`. +predicate { BST tree, Interval range } + BSTNodeUpTo(pointer p, pointer c, struct MapNode child, Interval range) { + if (ptr_eq(p,c)) { + return { tree: Leaf {}, range: range }; + } else { + take parent = Owned(p); + assert(inInterval(parent.key, range)); + let ranges = splitInterval(parent.key, range); + take result = BSTNodeChildUpTo(c, child, parent, ranges); + return result; + } +} + +// Starting at a parent with data `data` and children `smaller` and `larger`, +// we go toward `c`, guided by its value, `target`. +predicate { BST tree, Interval range } + BSTNodeChildUpTo(pointer c, struct MapNode target, struct MapNode parent, Intervals ranges) { + if (parent.key < target.key) { + take small = BST(parent.smaller, ranges.lower); + take large = BSTNodeUpTo(parent.larger, c, target, ranges.upper); + return { tree: Node { data: getNodeData(parent), smaller: small, larger: large.tree }, + range: large.range }; + } else { + if (parent.key > target.key) { + take small = BSTNodeUpTo(parent.smaller, c, target, ranges.lower); + take large = BST(parent.larger, ranges.upper); + return { tree: Node { data: getNodeData(parent), smaller: small.tree, larger: large }, + range: small.range }; + } else { + // We should never get here, but asserting `false` is not allowed + return default<{ BST tree, Interval range }>; + }} +} + +function (BST) unfocus(BSTFocus focus) { + if (focus.at_leaf) { + focus.done + } else { + let node = focus.node; + let bst = Node { data: getNodeData(node), + smaller: focus.smaller, + larger: focus.larger + }; + setKey(node.key, focus.done, bst) + } +} + +lemma FocusUnfocus(pointer root, pointer cur, Interval range) + requires + take x = BSTFocus(root,cur,range); + ensures + take y = BST(root,range); + unfocus(x) == y; + +lemma GoSmaller(pointer root, pointer cur, Interval range) + requires + !is_null(cur); + take focus = BSTFocus(root,cur,range); + ensures + take focus_smaller = BSTFocus(root,focus.node.smaller,range); + unfocus(focus) == unfocus(focus_smaller); + + + +@*/ +#endif diff --git a/src/examples/bst/bst_sem_cn.h b/src/examples/bst/bst_sem_cn.h new file mode 100644 index 00000000..a26899a0 --- /dev/null +++ b/src/examples/bst/bst_sem_cn.h @@ -0,0 +1,53 @@ +#ifndef BST_SEM_CN_H +#define BST_SEM_CN_H + +// Functional Sepcification of Binary Search Tree + +/*@ +type_synonym KEY = i32 +type_synonym VALUE = i64 +type_synonym NodeData = { KEY key, VALUE value } + +function (KEY) minKey() { MINi32() } +function (KEY) maxKey() { MAXi32() } +function (KEY) incKey(KEY x) { if (x < maxKey()) { x + 1i32 } else { x } } +function (KEY) decKey(KEY x) { if (x > minKey()) { x - 1i32 } else { x } } + +type_synonym Interval = { KEY lower, KEY upper } +function (Interval) anyKey() {{ lower: minKey(), upper: maxKey() }} + +type_synonym Intervals = { Interval lower, Interval upper } +function (Intervals) splitInterval(KEY x, Interval i) { + { lower: { lower: i.lower, upper: decKey(x) }, + upper: { lower: incKey(x), upper: i.upper } + } +} + +function (boolean) inInterval(KEY x, Interval i) { + i.lower <= x && x <= i.upper +} + + +// A binary dearch tree +datatype BST { + Leaf {}, + Node { NodeData data, BST smaller, BST larger } +} + +function [rec] (BST) setKey(KEY k, BST root, BST value) { + match root { + Leaf {} => { value } + Node { data: data, smaller: smaller, larger: larger } => { + if (k < data.key) { + Node { data: data, smaller: setKey(k, smaller, value), larger: larger } + } else { + Node { data: data, smaller: smaller, larger: setKey(k, larger, value) } + } + } + } +} + + +@*/ + +#endif \ No newline at end of file From c1858fb5eb53c769df90a99f9818db30f3a8a48b Mon Sep 17 00:00:00 2001 From: Iavor Diatchki Date: Tue, 15 Oct 2024 14:43:38 -0700 Subject: [PATCH 135/152] Make `traverse` preserve the tree. --- src/examples/bst/bst_map.c | 5 +-- src/examples/bst/bst_map_cn.h | 57 ++++++++++++++++------------------- src/examples/bst/bst_sem_cn.h | 10 ++++++ 3 files changed, 39 insertions(+), 33 deletions(-) diff --git a/src/examples/bst/bst_map.c b/src/examples/bst/bst_map.c index d6cbf1e7..73bfb8d4 100644 --- a/src/examples/bst/bst_map.c +++ b/src/examples/bst/bst_map.c @@ -51,18 +51,19 @@ requires take start = BST(node, anyKey()); ensures take end = BST(node, anyKey()); - // start == end; + start == end; @*/ { struct MapNode *cur = node; /*@ split_case is_null(cur); @*/ + /*@ unfold setKey(fromBSTNode(start).data.key, Leaf {}, start); @*/ while (cur) /*@ inv {node} unchanged; take focus = BSTFocus(node,cur,anyKey()); - // start == unfocus(focus); + start == unfocus(focus); let cur_prev = cur; @*/ { diff --git a/src/examples/bst/bst_map_cn.h b/src/examples/bst/bst_map_cn.h index b0296da9..1e73dc24 100644 --- a/src/examples/bst/bst_map_cn.h +++ b/src/examples/bst/bst_map_cn.h @@ -51,34 +51,36 @@ predicate BST BST(pointer root, Interval range) { // Focusing on a node in the tree // ***************************************************************************** -type_synonym BSTFocus = { - // Indicates if we are at a node or a leaf. - boolean at_leaf, +type_synonym BSTNodeFocus = + { BST done, struct MapNode node, BST smaller, BST larger } - // Rest of the tree - BST done, +datatype BSTFocus { + AtLeaf { BST tree }, + AtNode { BST done, struct MapNode node, BST smaller, BST larger } +} - // Focused node - struct MapNode node, - BST smaller, - BST larger +// Access focus data, when we already know that we are at a node. +function (BSTNodeFocus) fromBSTFocusNode(BSTFocus focus) { + match focus { + AtLeaf { tree: _ } => { default } + AtNode { done: done, node: node, smaller: smaller, larger: larger } => { + { done: done, node: node, smaller: smaller, larger: larger } + } + } } predicate BSTFocus BSTFocus(pointer root, pointer child, Interval range) { if (is_null(child)) { take tree = BST(root, range); - return { at_leaf: true, done: tree, - node: default, - smaller: default, - larger: default }; + return AtLeaf { tree: tree }; } else { take node = Owned(child); take result = BSTNodeUpTo(root, child, node, range); let ranges = splitInterval(node.key,result.range); take smaller = BST(node.smaller, ranges.lower); take larger = BST(node.larger, ranges.upper); - return { at_leaf: false, done: result.tree, node: node, - smaller: smaller, larger: larger }; + return AtNode { done: result.tree, node: node, + smaller: smaller, larger: larger }; } } @@ -120,31 +122,24 @@ predicate { BST tree, Interval range } } function (BST) unfocus(BSTFocus focus) { - if (focus.at_leaf) { - focus.done - } else { - let node = focus.node; - let bst = Node { data: getNodeData(node), - smaller: focus.smaller, - larger: focus.larger - }; - setKey(node.key, focus.done, bst) + match focus { + AtLeaf { tree: tree } => { tree } + AtNode { done: tree, node: node, smaller: smaller, larger: larger } => { + let bst = Node { data: getNodeData(node), smaller: smaller, larger: larger }; + setKey(node.key, tree, bst) + } } } -lemma FocusUnfocus(pointer root, pointer cur, Interval range) - requires - take x = BSTFocus(root,cur,range); - ensures - take y = BST(root,range); - unfocus(x) == y; + lemma GoSmaller(pointer root, pointer cur, Interval range) requires !is_null(cur); take focus = BSTFocus(root,cur,range); ensures - take focus_smaller = BSTFocus(root,focus.node.smaller,range); + let node = fromBSTFocusNode(focus).node; + take focus_smaller = BSTFocus(root,node.smaller,range); unfocus(focus) == unfocus(focus_smaller); diff --git a/src/examples/bst/bst_sem_cn.h b/src/examples/bst/bst_sem_cn.h index a26899a0..153034ad 100644 --- a/src/examples/bst/bst_sem_cn.h +++ b/src/examples/bst/bst_sem_cn.h @@ -34,6 +34,16 @@ datatype BST { Node { NodeData data, BST smaller, BST larger } } +// A selector for the case when we know that the tree is a `Node`. +function ({ NodeData data, BST smaller, BST larger }) fromBSTNode(BST node) { + match node { + Leaf {} => { default<{ NodeData data, BST smaller, BST larger }> } + Node { data: data, smaller: smaller, larger: larger } => { + { data: data, smaller: smaller, larger: larger } + } + } +} + function [rec] (BST) setKey(KEY k, BST root, BST value) { match root { Leaf {} => { value } From 47aca9f8bd95b0f90848084922102ab0dd0a76e6 Mon Sep 17 00:00:00 2001 From: Iavor Diatchki Date: Wed, 16 Oct 2024 15:00:39 -0700 Subject: [PATCH 136/152] Checkpoint: lookup with lots of duplication --- src/examples/bst/bst_map.c | 130 ++++++++++++++++++++++++++-------- src/examples/bst/bst_map_cn.h | 124 ++++++++++++++++++++++---------- src/examples/bst/bst_sem_cn.h | 66 ++++++++++++----- 3 files changed, 236 insertions(+), 84 deletions(-) diff --git a/src/examples/bst/bst_map.c b/src/examples/bst/bst_map.c index 73bfb8d4..6a7ba897 100644 --- a/src/examples/bst/bst_map.c +++ b/src/examples/bst/bst_map.c @@ -26,31 +26,16 @@ ensures } #endif -#if 0 -// Demonstrates a recursive traversal of a tree. -size_t count(struct MapNode const *node) -/*@ -requires - take before = BST(node); -ensures - take after = BST(node); - before == after; -@*/ -{ - if (node == NULL) return 0; - return (size_t)1 + count(node->smaller) + count(node->larger); -} -#endif -#if 1 +#if 0 // A no-op function, just shows hows to traverse a tree with a loop. void traverse(struct MapNode *node) /*@ requires - take start = BST(node, anyKey()); + take start = BST(node); ensures - take end = BST(node, anyKey()); + take end = BST(node); start == end; @*/ { @@ -62,13 +47,13 @@ ensures /*@ inv {node} unchanged; - take focus = BSTFocus(node,cur,anyKey()); + take focus = BSTFocus(node,cur); start == unfocus(focus); let cur_prev = cur; @*/ { cur = cur->smaller; - /*@ apply GoSmaller(node,cur_prev,anyKey()); @*/ + /*@ apply FocusedGo(node,cur_prev,true); @*/ } } #endif @@ -84,36 +69,123 @@ ensures tree == final_tree; @*/ { - struct MapNode *parent = NULL; + struct MapNode *parent = 0; struct MapNode *cur = p; - while(cur != NULL) + /*@ split_case is_null(cur); @*/ + /*@ unfold setKey(fromBSTNode(tree).data.key, Leaf {}, tree); @*/ + while(cur) /*@ inv {p} unchanged; - take front = BSTUpTo(cur,parent); + take front = BSTFocus(p,cur); + tree == unfocus(front); + let cur_prev = cur; @*/ { parent = cur; - cur = p->smaller; - + cur = cur->smaller; + /*@ apply FocusedGoSmaller(p,cur_prev); @*/ } return parent; } #endif +#if 1 +/* Look for a node and its parent */ +struct MapNode *findNode(struct MapNode *root, KEY key) +/*@ +requires + take tree = BST(root); +ensures + take focus = BSTFocus(root, return); + unfocus(focus) == tree; + match focus { + AtLeaf { tree: _ } => { !member(key,tree) } + AtNode { done: _, node: node, smaller: _, larger: _ } => { + node.key == key + } + }; +@*/ +{ + struct MapNode *cur = root; + /*@ split_case is_null(cur); @*/ + /*@ unfold setKey(fromBSTNode(tree).data.key, Leaf {}, tree); @*/ + /*@ unfold member(key, Leaf {}); @*/ + while (cur) + /*@ inv + {root} unchanged; + {key} unchanged; + take focus = BSTFocus(root,cur); + unfocus(focus) == tree; + !member(key, focusDone(focus)); + let cur_prev = cur; + @*/ + { + KEY k = cur->key; + if (k == key) return cur; + cur = k < key? cur->larger : cur->smaller; + /*@ apply FocusedGoKey(root, cur_prev, k > key, key); @*/ + } + return 0; +} +#endif + + +/*@ +predicate BSTFocus FindParentFocus(pointer tree_ptr, pointer cur_ptr, pointer parent_ptr, KEY key) { + if (is_null(cur_ptr)) { + take focus = BSTFocus(tree_ptr, parent_ptr); + let tree_after = unfocus(focus); + assert(!member(key,tree_after)); // More? + return focus; + } else { + // Found in tree + take focus = BSTFocus(tree_ptr, cur_ptr); + let at_node = fromBSTFocusNode(focus); + assert(at_node.node.key == key); + return focus; + } +} +@*/ + #if 0 /* Look for a node and its parent */ struct MapNode *findParent(struct MapNode **node, KEY key) +/*@ +requires + take tree_ptr = Owned(node); + take tree = BST(tree_ptr); +ensures + take cur_ptr = Owned(node); + let parent_ptr = return; + take focus = FindParentFocus(tree_ptr, parent_ptr, cur_ptr, key); + let tree_after = unfocus(focus); + tree == tree_after; +@*/ { - struct MapNode *parent = NULL; + struct MapNode *parent = 0; struct MapNode *cur = *node; - while (cur != NULL) - { + /*@ split_case is_null(cur); @*/ + while (cur) + /*@ inv + {node} unchanged; + {key} unchanged; + take node_ptr = Owned(node); + ptr_eq(node_ptr,tree_ptr); + take focus = BSTFocus(tree_ptr, cur); + let cur_prev = cur; + @*/ +{ KEY k = cur->key; - if (k == key) { *node = cur; return parent; } + if (k == key) { + *node = cur; + /*@ split_case is_null(cur); @*/ + return parent; + } parent = cur; cur = k < key? cur->larger : cur->smaller; + /*@ apply FocusedGo(tree_ptr, cur_prev, k > key); @*/ } *node = cur; return parent; diff --git a/src/examples/bst/bst_map_cn.h b/src/examples/bst/bst_map_cn.h index 1e73dc24..091f1156 100644 --- a/src/examples/bst/bst_map_cn.h +++ b/src/examples/bst/bst_map_cn.h @@ -28,22 +28,47 @@ function (NodeData) getNodeData(struct MapNode node) { { key: node.key, value: node.value } } +type_synonym RangedBST = { BST tree, Interval range } +type_synonym RangedNode = { + struct MapNode node, + BST smaller, + BST larger, + Interval range +} + +function (boolean) validBST(struct MapNode node, Interval smaller, Interval larger) { + (smaller.empty || smaller.upper < node.key) && + (larger.empty || node.key < larger.lower) +} + + +predicate RangedNode RangedNode(pointer root) { + take node = Owned(root); + take smaller = RangedBST(node.smaller); + take larger = RangedBST(node.larger); + assert (validBST(node, smaller.range, larger.range)); + return { node: node, smaller: smaller.tree, larger: larger.tree, + range: joinInterval(smaller.range, larger.range) }; +} -// A binary search tree, where all keys are in the given range. -predicate BST BST(pointer root, Interval range) { +// A binary search tree, and the interval for all its keys. +predicate RangedBST RangedBST(pointer root) { if (is_null(root)) { - return Leaf {}; + return { tree: Leaf {}, range: emptyInterval() }; } else { - take node = Owned(root); - let data = getNodeData(node); - assert(inInterval(node.key, range)); - let ranges = splitInterval(node.key, range); - take smaller = BST(node.smaller, ranges.lower); - take larger = BST(node.larger, ranges.upper); - return Node { data: data, smaller: smaller, larger: larger }; + take node = RangedNode(root); + let data = getNodeData(node.node); + return { tree: Node { data: data, smaller: node.smaller, larger: node.larger }, + range: node.range }; } } +// An arbitrary binary search tree. +predicate BST BST(pointer root) { + take result = RangedBST(root); + return result.tree; +} + @@ -59,6 +84,7 @@ datatype BSTFocus { AtNode { BST done, struct MapNode node, BST smaller, BST larger } } + // Access focus data, when we already know that we are at a node. function (BSTNodeFocus) fromBSTFocusNode(BSTFocus focus) { match focus { @@ -69,55 +95,51 @@ function (BSTNodeFocus) fromBSTFocusNode(BSTFocus focus) { } } -predicate BSTFocus BSTFocus(pointer root, pointer child, Interval range) { +predicate BSTFocus BSTFocus(pointer root, pointer child) { if (is_null(child)) { - take tree = BST(root, range); + take tree = BST(root); return AtLeaf { tree: tree }; } else { - take node = Owned(child); - take result = BSTNodeUpTo(root, child, node, range); - let ranges = splitInterval(node.key,result.range); - take smaller = BST(node.smaller, ranges.lower); - take larger = BST(node.larger, ranges.upper); - return AtNode { done: result.tree, node: node, - smaller: smaller, larger: larger }; + take node = RangedNode(child); + take result = BSTNodeUpTo(root, child, node.node, node.range); + return AtNode { done: result.tree, node: node.node, + smaller: node.smaller, larger: node.larger }; } } // Consume parts of the tree starting at `p` until we get to `c`. // We do not consume `c`. // `child` is the node stored at `c`. -predicate { BST tree, Interval range } - BSTNodeUpTo(pointer p, pointer c, struct MapNode child, Interval range) { +predicate RangedBST BSTNodeUpTo(pointer p, pointer c, struct MapNode child, Interval range) { if (ptr_eq(p,c)) { return { tree: Leaf {}, range: range }; } else { take parent = Owned(p); - assert(inInterval(parent.key, range)); - let ranges = splitInterval(parent.key, range); - take result = BSTNodeChildUpTo(c, child, parent, ranges); + take result = BSTNodeChildUpTo(c, child, range, parent); return result; } } // Starting at a parent with data `data` and children `smaller` and `larger`, // we go toward `c`, guided by its value, `target`. -predicate { BST tree, Interval range } - BSTNodeChildUpTo(pointer c, struct MapNode target, struct MapNode parent, Intervals ranges) { +predicate RangedBST + BSTNodeChildUpTo(pointer c, struct MapNode target, Interval range, struct MapNode parent) { if (parent.key < target.key) { - take small = BST(parent.smaller, ranges.lower); - take large = BSTNodeUpTo(parent.larger, c, target, ranges.upper); - return { tree: Node { data: getNodeData(parent), smaller: small, larger: large.tree }, - range: large.range }; + take small = RangedBST(parent.smaller); + take large = BSTNodeUpTo(parent.larger, c, target, range); + assert(validBST(parent, small.range, large.range)); + return { tree: Node { data: getNodeData(parent), smaller: small.tree, larger: large.tree }, + range: joinInterval(small.range,large.range) }; } else { if (parent.key > target.key) { - take small = BSTNodeUpTo(parent.smaller, c, target, ranges.lower); - take large = BST(parent.larger, ranges.upper); - return { tree: Node { data: getNodeData(parent), smaller: small.tree, larger: large }, - range: small.range }; + take small = BSTNodeUpTo(parent.smaller, c, target, range); + take large = RangedBST(parent.larger); + assert(validBST(parent, small.range, large.range)); + return { tree: Node { data: getNodeData(parent), smaller: small.tree, larger: large.tree }, + range: joinInterval(small.range,large.range) }; } else { // We should never get here, but asserting `false` is not allowed - return default<{ BST tree, Interval range }>; + return default; }} } @@ -131,16 +153,40 @@ function (BST) unfocus(BSTFocus focus) { } } +function (BST) focusDone(BSTFocus focus) { + match focus { + AtLeaf { tree: tree } => { tree } + AtNode { done: tree, node: _, smaller: _, larger: _ } => { tree } + } +} + + + +lemma FocusedGo(pointer root, pointer cur, boolean smaller) + requires + !is_null(cur); + take focus = BSTFocus(root,cur); + ensures + let node = fromBSTFocusNode(focus).node; + take new_focus = BSTFocus(root, if (smaller) { node.smaller } else { node.larger }); + unfocus(focus) == unfocus(new_focus); -lemma GoSmaller(pointer root, pointer cur, Interval range) +// It's quite unfortunate that we have to copy the lemma here. +lemma FocusedGoKey(pointer root, pointer cur, boolean smaller, KEY key) requires !is_null(cur); - take focus = BSTFocus(root,cur,range); + take focus = BSTFocus(root,cur); ensures let node = fromBSTFocusNode(focus).node; - take focus_smaller = BSTFocus(root,node.smaller,range); - unfocus(focus) == unfocus(focus_smaller); + take new_focus = BSTFocus(root, if (smaller) { node.smaller } else { node.larger }); + unfocus(focus) == unfocus(new_focus); + + if (!member(key, focusDone(focus)) && node.key != key) { + !member(key, focusDone(new_focus)) + } else { + true + }; diff --git a/src/examples/bst/bst_sem_cn.h b/src/examples/bst/bst_sem_cn.h index 153034ad..20aa88c4 100644 --- a/src/examples/bst/bst_sem_cn.h +++ b/src/examples/bst/bst_sem_cn.h @@ -8,26 +8,25 @@ type_synonym KEY = i32 type_synonym VALUE = i64 type_synonym NodeData = { KEY key, VALUE value } -function (KEY) minKey() { MINi32() } -function (KEY) maxKey() { MAXi32() } -function (KEY) incKey(KEY x) { if (x < maxKey()) { x + 1i32 } else { x } } -function (KEY) decKey(KEY x) { if (x > minKey()) { x - 1i32 } else { x } } - -type_synonym Interval = { KEY lower, KEY upper } -function (Interval) anyKey() {{ lower: minKey(), upper: maxKey() }} - -type_synonym Intervals = { Interval lower, Interval upper } -function (Intervals) splitInterval(KEY x, Interval i) { - { lower: { lower: i.lower, upper: decKey(x) }, - upper: { lower: incKey(x), upper: i.upper } - } -} +type_synonym Interval = { KEY lower, KEY upper, boolean empty } -function (boolean) inInterval(KEY x, Interval i) { - i.lower <= x && x <= i.upper +function (Interval) emptyInterval() { + { lower: default, upper: default, empty: true } } + +function (Interval) joinInterval(Interval smaller, Interval larger) { + if (smaller.empty) { + larger + } else { + if (larger.empty) { + smaller + } else { + { lower: smaller.lower, upper: larger.upper, empty: false } + }} +} + // A binary dearch tree datatype BST { Leaf {}, @@ -44,6 +43,41 @@ function ({ NodeData data, BST smaller, BST larger }) fromBSTNode(BST node) { } } +datatype VALUEOption { + VALUENone {}, + VALUESome { VALUE value } +} + +function [rec] (VALUEOption) lookup(KEY key, BST tree) { + match tree { + Leaf {} => { VALUENone {} } + Node { data: data, smaller: smaller, larger: larger } => { + if (data.key == key) { + VALUESome { value: data.value } + } else { + if (data.key < key) { + lookup(key,larger) + } else { + lookup(key,smaller) + } + } + } + } +} + +function [rec] (boolean) member(KEY k, BST tree) { + match tree { + Leaf {} => { false } + Node { data: data, smaller: smaller, larger: larger } => { + data.key == k || + k < data.key && member(k,smaller) || + k > data.key && member(k,larger) + } + } +} + + + function [rec] (BST) setKey(KEY k, BST root, BST value) { match root { Leaf {} => { value } From b47b8f858083ce0fb11daf6d196a267614eb09dc Mon Sep 17 00:00:00 2001 From: Iavor Diatchki Date: Thu, 17 Oct 2024 08:29:41 -0700 Subject: [PATCH 137/152] Modify "lookup" to work in combination with "member" --- src/examples/bst/bst_sem_cn.h | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/examples/bst/bst_sem_cn.h b/src/examples/bst/bst_sem_cn.h index 20aa88c4..2e2caa89 100644 --- a/src/examples/bst/bst_sem_cn.h +++ b/src/examples/bst/bst_sem_cn.h @@ -43,17 +43,13 @@ function ({ NodeData data, BST smaller, BST larger }) fromBSTNode(BST node) { } } -datatype VALUEOption { - VALUENone {}, - VALUESome { VALUE value } -} -function [rec] (VALUEOption) lookup(KEY key, BST tree) { +function [rec] (VALUE) lookup(KEY key, BST tree) { match tree { - Leaf {} => { VALUENone {} } + Leaf {} => { default } Node { data: data, smaller: smaller, larger: larger } => { if (data.key == key) { - VALUESome { value: data.value } + data.value } else { if (data.key < key) { lookup(key,larger) @@ -76,6 +72,23 @@ function [rec] (boolean) member(KEY k, BST tree) { } } +function [rec] (BST) insert(KEY key, VALUE value, BST tree) { + match tree { + Leaf {} => { Node { data: { key: key, value: value }, smaller: Leaf {}, larger: Leaf {} } } + Node { data: data, smaller: smaller, larger: larger } => { + if (data.key == key) { + Node { data: { key: key, value: value }, smaller: smaller, larger: larger } + } else { + if (data.key < key) { + Node { data: data, smaller: smaller, larger: insert(key,value,larger) } + } else { + Node { data: data, smaller: insert(key,value,smaller), larger: larger } + } + } + } + } +} + function [rec] (BST) setKey(KEY k, BST root, BST value) { From 19ede1cc11325415f6f701bf95b48aec184fbdcd Mon Sep 17 00:00:00 2001 From: Iavor Diatchki Date: Wed, 23 Oct 2024 15:21:28 -0700 Subject: [PATCH 138/152] Some examples of using CN to generate tests --- src/examples/testing/README | 1 + src/examples/testing/do-test | 29 +++++++++++++++++ src/examples/testing/src/arr-01.c | 11 +++++++ src/examples/testing/src/list-01.c | 39 +++++++++++++++++++++++ src/examples/testing/src/num-01.c | 14 +++++++++ src/examples/testing/src/num-02.c | 17 ++++++++++ src/examples/testing/src/num-03.c | 17 ++++++++++ src/examples/testing/src/num-04.c | 18 +++++++++++ src/examples/testing/src/ptr-01.c | 7 +++++ src/examples/testing/src/ptr-02.c | 14 +++++++++ src/examples/testing/src/ptr-03.c | 14 +++++++++ src/examples/testing/src/ptr-04.c | 12 +++++++ src/examples/testing/src/ptr-05.c | 28 +++++++++++++++++ src/examples/testing/src/ptr-06.c | 50 ++++++++++++++++++++++++++++++ src/examples/testing/src/ptr-07.c | 22 +++++++++++++ 15 files changed, 293 insertions(+) create mode 100644 src/examples/testing/README create mode 100755 src/examples/testing/do-test create mode 100644 src/examples/testing/src/arr-01.c create mode 100644 src/examples/testing/src/list-01.c create mode 100644 src/examples/testing/src/num-01.c create mode 100644 src/examples/testing/src/num-02.c create mode 100644 src/examples/testing/src/num-03.c create mode 100644 src/examples/testing/src/num-04.c create mode 100644 src/examples/testing/src/ptr-01.c create mode 100644 src/examples/testing/src/ptr-02.c create mode 100644 src/examples/testing/src/ptr-03.c create mode 100644 src/examples/testing/src/ptr-04.c create mode 100644 src/examples/testing/src/ptr-05.c create mode 100644 src/examples/testing/src/ptr-06.c create mode 100644 src/examples/testing/src/ptr-07.c diff --git a/src/examples/testing/README b/src/examples/testing/README new file mode 100644 index 00000000..653e8a99 --- /dev/null +++ b/src/examples/testing/README @@ -0,0 +1 @@ +This directory contains examples on how to use CN's testing capabilities. diff --git a/src/examples/testing/do-test b/src/examples/testing/do-test new file mode 100755 index 00000000..f985fc12 --- /dev/null +++ b/src/examples/testing/do-test @@ -0,0 +1,29 @@ +#!/bin/bash + +set -e + +CN_INSTALL="$OPAM_SWITCH_PREFIX/lib/cn/runtime" + +if [ "$#" != "1" ] +then + echo "USAGE: $0 FILE" + exit 1 +fi +FILE=$1 +BASE=$(basename "$FILE" .c) + +BUILD_DIR=$(mktemp -d "cn-test-XXXXXXXX") +function cleanup { + echo "GENERATED: $BUILD_DIR" + rm -rf $BUILD_DIR +} +trap cleanup EXIT + +TEST_FILE="$BUILD_DIR/${BASE}_test.c" +TGT="$BUILD_DIR/run" +cn test $1 --no-run --output-dir="$BUILD_DIR" --with-ownership-checking +cc -g -o"$TGT" -I"$CN_INSTALL/include" -L"$CN_INSTALL" "$TEST_FILE" -lcn +$TGT + + + diff --git a/src/examples/testing/src/arr-01.c b/src/examples/testing/src/arr-01.c new file mode 100644 index 00000000..6df01e0b --- /dev/null +++ b/src/examples/testing/src/arr-01.c @@ -0,0 +1,11 @@ +// XXX: Testing arrays causes a number of crashes at the moment + +void preserve(int size, int *p) +/*@ +requires + take a1 = each(u64 i; i < (u64)size) { Owned(array_shift(p,i)) }; +ensures + take a2 = each(u64 i; i < (u64)size) { Owned(array_shift(p,i)) }; +@*/ +{ +} \ No newline at end of file diff --git a/src/examples/testing/src/list-01.c b/src/examples/testing/src/list-01.c new file mode 100644 index 00000000..746bcd82 --- /dev/null +++ b/src/examples/testing/src/list-01.c @@ -0,0 +1,39 @@ +struct List +{ + int value; + struct List* next; +}; + +/*@ +datatype IntList { + Nil {}, + Cons { i32 head, IntList tail } +} + +predicate IntList ListSegment(pointer from, pointer to) { + if (ptr_eq(from,to)) { + return Nil {}; + } else { + take head = Owned(from); + take tail = ListSegment(head.next, to); + return Cons { head: head.value, tail: tail }; + } +} +@*/ + +int sum(struct List* xs) +/*@ + requires + take l1 = ListSegment(xs,NULL); + ensures + take l2 = ListSegment(xs,NULL); + l1 == l2; +@*/ +{ + int result = 0; + while(xs) { + result += xs->value; + xs = xs->next; + } + return result; +} \ No newline at end of file diff --git a/src/examples/testing/src/num-01.c b/src/examples/testing/src/num-01.c new file mode 100644 index 00000000..9bbbe3f4 --- /dev/null +++ b/src/examples/testing/src/num-01.c @@ -0,0 +1,14 @@ + +// An example where the testing should catch the difference between +// the spec and the code. + +int f(int x) +/*@ + requires + true; + ensures + return == x; +@*/ +{ + return x + 1; +} diff --git a/src/examples/testing/src/num-02.c b/src/examples/testing/src/num-02.c new file mode 100644 index 00000000..52ec16a5 --- /dev/null +++ b/src/examples/testing/src/num-02.c @@ -0,0 +1,17 @@ + +// Find a needle in a heystack +// We do use the `requires` spec to guide test generation. +// +// Note that had we not used the `requires` clause, random inputs +// would lead to a result violating the `ensures` clause. + +int f(int x) +/*@ + requires + x == 12345678i32; + ensures + return == 12345679i32; +@*/ +{ + return x + 1; +} \ No newline at end of file diff --git a/src/examples/testing/src/num-03.c b/src/examples/testing/src/num-03.c new file mode 100644 index 00000000..2effff29 --- /dev/null +++ b/src/examples/testing/src/num-03.c @@ -0,0 +1,17 @@ + +// Find a needle in a heystack. +// We don't use the source code to guide test generation. +// +// Because of this, we are unlikely to find the mismatch +// between the spec and the implementation. + +int f(int x) +/*@ + requires + true; + ensures + return == x; +@*/ +{ + return x + (x == 12345567 ? 1 : 0); +} \ No newline at end of file diff --git a/src/examples/testing/src/num-04.c b/src/examples/testing/src/num-04.c new file mode 100644 index 00000000..87bde37a --- /dev/null +++ b/src/examples/testing/src/num-04.c @@ -0,0 +1,18 @@ + +// Find a needle in a heystack. +// We don't use the `ensures` spec in test generation. +// +// Because of this, we are unlikely to find the mismatch +// between the spec and the implementation. + +int f(int x) +/*@ + requires + true; + ensures + let delta = if (x == 127i32) { 1i32 } else { 0i32 }; + return == x + delta; +@*/ +{ + return x; +} \ No newline at end of file diff --git a/src/examples/testing/src/ptr-01.c b/src/examples/testing/src/ptr-01.c new file mode 100644 index 00000000..d87e95f9 --- /dev/null +++ b/src/examples/testing/src/ptr-01.c @@ -0,0 +1,7 @@ + +// Bad spec: input might be null + +void f(int *p) +{ + *p = 2; +} \ No newline at end of file diff --git a/src/examples/testing/src/ptr-02.c b/src/examples/testing/src/ptr-02.c new file mode 100644 index 00000000..b9763400 --- /dev/null +++ b/src/examples/testing/src/ptr-02.c @@ -0,0 +1,14 @@ +// Bad spec. +// The problem here is that the function does not dealloacte the pointer, +// but the `ensure` clause does return the ownership it took. + +void f(int *p) +/*@ +requires + take unussed_1 = Block(p); +ensures + true; +@*/ +{ + *p = 2; +} \ No newline at end of file diff --git a/src/examples/testing/src/ptr-03.c b/src/examples/testing/src/ptr-03.c new file mode 100644 index 00000000..5ed89810 --- /dev/null +++ b/src/examples/testing/src/ptr-03.c @@ -0,0 +1,14 @@ +// Bad spec. +// Allocations are OK, but value in memory is wrong. + +void f(int *p) +/*@ +requires + take unussed_1 = Block(p); +ensures + take x = Owned(p); + x == 3i32; +@*/ +{ + *p = 2; +} \ No newline at end of file diff --git a/src/examples/testing/src/ptr-04.c b/src/examples/testing/src/ptr-04.c new file mode 100644 index 00000000..b494cb8b --- /dev/null +++ b/src/examples/testing/src/ptr-04.c @@ -0,0 +1,12 @@ +// Spec and implementation match. +void f(int *p, int x) +/*@ +requires + take unussed_1 = Block(p); +ensures + take v = Owned(p); + v == x; +@*/ +{ + *p = x; +} \ No newline at end of file diff --git a/src/examples/testing/src/ptr-05.c b/src/examples/testing/src/ptr-05.c new file mode 100644 index 00000000..cea69d55 --- /dev/null +++ b/src/examples/testing/src/ptr-05.c @@ -0,0 +1,28 @@ +// Swap the values in 2 pointers. +// Note that the `Owned` precicates implicitly assert that there +// is no aliasing between `p` and `q`. + + +void swap(int *p, int *q) +/*@ +requires + take p1 = Owned(p); + take q1 = Owned(q); +ensures + take p2 = Owned(p); + take q2 = Owned(q); + p1 == q2; + q1 == p2; +@*/ +{ + int x = *p; + *p = *q; + *q = x; +} + +// This should fail, because of the aliasing. +void swap_same() +{ + int x; + swap(&x,&x); +} \ No newline at end of file diff --git a/src/examples/testing/src/ptr-06.c b/src/examples/testing/src/ptr-06.c new file mode 100644 index 00000000..918e83fd --- /dev/null +++ b/src/examples/testing/src/ptr-06.c @@ -0,0 +1,50 @@ +// Swap the contents of two pointers, allowing for aliasing. + +/*@ +type_synonym SwapState = { boolean same, i32 p, i32 q } + +predicate SwapState SwapPre(pointer p, pointer q) { + if (ptr_eq(p,q)) { + return { same: true, p: 0i32, q: 0i32 }; + } else { + // Not aliased, both pointers initialized. + take x = Owned(p); + take y = Owned(q); + return { same: false, p: x, q: y }; + } +} + +predicate (boolean) SwapPost(pointer p, pointer q, SwapState pre) { + if (pre.same) { + return true; + } else { + take x = Owned(p); + take y = Owned(q); + assert(x == pre.q); + assert(y == pre.p); + return true; + } +} +@*/ + +void swap(int *p, int *q) +/*@ +requires + take pre = SwapPre(p,q); +ensures + take unused = SwapPost(p,q,pre); +@*/ +{ + if (p == q) return; + int x = *p; + *p = *q; + *q = x; +} + +void swap_same() +{ + int x; + swap(&x,&x); // Aliasing OK + swap(0,0); // We don't touch the pointers when they are the same. + // swap(0,&x) will cause an error +} \ No newline at end of file diff --git a/src/examples/testing/src/ptr-07.c b/src/examples/testing/src/ptr-07.c new file mode 100644 index 00000000..dd6c50d0 --- /dev/null +++ b/src/examples/testing/src/ptr-07.c @@ -0,0 +1,22 @@ +// Testing will succeed even though verification will not. +// In this case the specificaiton for `f` is too weak: +// after calling `f` we know only that `p` is allocated, not that +// it is initialized or what is its value. + +void f(int *p) +/*@ + requires + take unused = Block(p); + ensures + take x = Block(p); +@*/ +{ + *p = 2; +} + +void g() { + int x; + f(&x); + /*@ assert(x == 2i32); @*/ + // Succeeds despite weak spec, because check is done at runtime. +} \ No newline at end of file From 7c46185e84d9c687a2d85e3d2751f5303b120d1a Mon Sep 17 00:00:00 2001 From: Iavor Diatchki Date: Mon, 4 Nov 2024 15:23:04 -0800 Subject: [PATCH 139/152] Fix up some examples, and add sorted lists, which doesn't work at the moment --- src/examples/testing/do-test | 2 +- src/examples/testing/src/arr-01.c | 8 ++--- src/examples/testing/src/list-01.c | 28 +++++++++++++++- src/examples/testing/src/list-02.c | 52 ++++++++++++++++++++++++++++++ src/examples/testing/src/ptr-02.c | 3 +- 5 files changed, 86 insertions(+), 7 deletions(-) create mode 100644 src/examples/testing/src/list-02.c diff --git a/src/examples/testing/do-test b/src/examples/testing/do-test index f985fc12..2594908b 100755 --- a/src/examples/testing/do-test +++ b/src/examples/testing/do-test @@ -21,7 +21,7 @@ trap cleanup EXIT TEST_FILE="$BUILD_DIR/${BASE}_test.c" TGT="$BUILD_DIR/run" -cn test $1 --no-run --output-dir="$BUILD_DIR" --with-ownership-checking +cn test $1 --no-run --output-dir="$BUILD_DIR" cc -g -o"$TGT" -I"$CN_INSTALL/include" -L"$CN_INSTALL" "$TEST_FILE" -lcn $TGT diff --git a/src/examples/testing/src/arr-01.c b/src/examples/testing/src/arr-01.c index 6df01e0b..77aee26d 100644 --- a/src/examples/testing/src/arr-01.c +++ b/src/examples/testing/src/arr-01.c @@ -1,11 +1,11 @@ -// XXX: Testing arrays causes a number of crashes at the moment +// Tests should pass: we just assert that we have a valid chunk of memory. void preserve(int size, int *p) /*@ requires - take a1 = each(u64 i; i < (u64)size) { Owned(array_shift(p,i)) }; + take a1 = each(i32 i; 0i32 <= i && i < size) { Owned(array_shift(p,i)) }; ensures - take a2 = each(u64 i; i < (u64)size) { Owned(array_shift(p,i)) }; + take a2 = each(i32 i; 0i32 <= i && i < size) { Owned(array_shift(p,i)) }; @*/ { -} \ No newline at end of file +} diff --git a/src/examples/testing/src/list-01.c b/src/examples/testing/src/list-01.c index 746bcd82..3f7d8ab5 100644 --- a/src/examples/testing/src/list-01.c +++ b/src/examples/testing/src/list-01.c @@ -1,3 +1,5 @@ + + struct List { int value; @@ -21,6 +23,8 @@ predicate IntList ListSegment(pointer from, pointer to) { } @*/ + +// This is a valid spec, even though to verify with CN we'd need a loop invariant. int sum(struct List* xs) /*@ requires @@ -28,6 +32,7 @@ int sum(struct List* xs) ensures take l2 = ListSegment(xs,NULL); l1 == l2; + true; @*/ { int result = 0; @@ -36,4 +41,25 @@ int sum(struct List* xs) xs = xs->next; } return result; -} \ No newline at end of file +} + + +// This is an invalid spec, list is modified. +int sum_and_modify(struct List* xs) +/*@ + requires + take l1 = ListSegment(xs,NULL); + ensures + take l2 = ListSegment(xs,NULL); + l1 == l2; + true; +@*/ +{ + int result = 0; + while(xs) { + result += xs->value; + xs->value = 7; + xs = xs->next; + } + return result; +} diff --git a/src/examples/testing/src/list-02.c b/src/examples/testing/src/list-02.c new file mode 100644 index 00000000..62dd7226 --- /dev/null +++ b/src/examples/testing/src/list-02.c @@ -0,0 +1,52 @@ +// Sorted list + +struct List +{ + int value; + struct List* next; +}; + +/*@ +datatype IntList { + Nil {}, + Cons { i32 head, IntList tail } +} + +function (boolean) validCons(i32 head, IntList tail) { + match tail { + Nil {} => { true } + Cons { head: next, tail: _ } => { head <= next } + } +} + +predicate IntList ListSegment(pointer from, pointer to) { + if (ptr_eq(from,to)) { + return Nil {}; + } else { + take head = Owned(from); + take tail = ListSegment(head.next, to); + assert(validCons(head.value,tail)); + return Cons { head: head.value, tail: tail }; + } +} +@*/ + + +// This is a valid spec, even though to verify with CN we'd need a loop invariant. +int sum(struct List* xs) +/*@ + requires + take l1 = ListSegment(xs,NULL); + ensures + take l2 = ListSegment(xs,NULL); + l1 == l2; + true; +@*/ +{ + int result = 0; + while(xs) { + result += xs->value; + xs = xs->next; + } + return result; +} diff --git a/src/examples/testing/src/ptr-02.c b/src/examples/testing/src/ptr-02.c index b9763400..567db98a 100644 --- a/src/examples/testing/src/ptr-02.c +++ b/src/examples/testing/src/ptr-02.c @@ -1,6 +1,7 @@ // Bad spec. // The problem here is that the function does not dealloacte the pointer, -// but the `ensure` clause does return the ownership it took. +// but the `ensure` clause doesn't return the ownership it took, so +// the memory has "leaked". void f(int *p) /*@ From fe2a505265e366a9aa042caae3f819ffaaa430a1 Mon Sep 17 00:00:00 2001 From: Iavor Diatchki Date: Mon, 4 Nov 2024 16:01:43 -0800 Subject: [PATCH 140/152] Implement a simple vector type, no CN specs yet --- src/examples/vector/vector.c | 45 ++++++++++++++++++++++++++++++++++++ src/examples/vector/vector.h | 19 +++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 src/examples/vector/vector.c create mode 100644 src/examples/vector/vector.h diff --git a/src/examples/vector/vector.c b/src/examples/vector/vector.c new file mode 100644 index 00000000..ee9b1248 --- /dev/null +++ b/src/examples/vector/vector.c @@ -0,0 +1,45 @@ +#include "vector.h" +#include "string.h" +#include "stdlib.h" + + +// Assumes malloc does not fail +Vector *vec_empty(size_t capacity) { + Vector *p = (Vector*) malloc(sizeof(Vector)); + p->size = 0; + p->capacity = capacity; + p->data = (VEC_ELEMENT*) malloc(sizeof(VEC_ELEMENT[capacity])); + return p; +} + +// capacity >= size +void vec_resize(Vector *vec, size_t capacity) { + VEC_ELEMENT *new_data = (VEC_ELEMENT*) malloc(sizeof(VEC_ELEMENT[capacity])); + memcpy(new_data, vec->data, sizeof(VEC_ELEMENT[vec->size])); + free(vec->data); + vec->data = new_data; + vec->capacity = capacity; +} + +// Assumes malloc does not fail +void vec_push_back(Vector* vec, VEC_ELEMENT el) { + if (vec->size == vec->capacity) + vec_resize(vec, vec->capacity > 0? 2 * vec->capacity : 1); + vec->data[vec->size++] = el; +} + +// i < size +VEC_ELEMENT *vec_index(Vector const* vec, size_t i) { + return vec->data + i; +} + +// size > 0 +void vec_pop_back(Vector* vec) { + size_t new_capacity = vec->capacity >> 1; + if (--vec->size < new_capacity) vec_resize(vec,new_capacity); +} + +void vec_free(Vector* vec) { + free(vec->data); + free(vec); +} diff --git a/src/examples/vector/vector.h b/src/examples/vector/vector.h new file mode 100644 index 00000000..5583a723 --- /dev/null +++ b/src/examples/vector/vector.h @@ -0,0 +1,19 @@ +#ifndef VECTOR_H +#define VECTOR_H + +#include + +typedef int VEC_ELEMENT; + +typedef struct { + size_t size; + size_t capacity; + VEC_ELEMENT *data; +} Vector; + +Vector *vec_empty(); +VEC_ELEMENT *vec_index(Vector const *vec, size_t index); +void vec_push_back(Vector *vec, VEC_ELEMENT el); +void vec_pop_back(Vector *vec); +void vec_free(Vector *vec); +#endif From 5f8334ad4589b7b90bc8da52bfe366a1005f8f5e Mon Sep 17 00:00:00 2001 From: Iavor Diatchki Date: Tue, 5 Nov 2024 10:04:26 -0800 Subject: [PATCH 141/152] Insertion in a sorted list --- src/examples/testing/src/list-02.c | 69 ++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/src/examples/testing/src/list-02.c b/src/examples/testing/src/list-02.c index 62dd7226..a4e4fa34 100644 --- a/src/examples/testing/src/list-02.c +++ b/src/examples/testing/src/list-02.c @@ -19,6 +19,23 @@ function (boolean) validCons(i32 head, IntList tail) { } } +function [rec] (IntList) insertList(boolean dups, i32 x, IntList xs) { + match xs { + Nil {} => { Cons { head: x, tail: Nil {} } } + Cons { head: head, tail: tail } => { + if (head < x) { + Cons { head: head, tail: insertList(dups, x,tail) } + } else { + if (!dups && head == x) { + xs + } else { + Cons { head: x, tail: xs } + } + } + } + } +} + predicate IntList ListSegment(pointer from, pointer to) { if (ptr_eq(from,to)) { return Nil {}; @@ -50,3 +67,55 @@ int sum(struct List* xs) } return result; } + +void *cn_malloc(unsigned long size); + + +// This is invalid because we don't preserve the sorted invariant. +void cons(int x, struct List** xs) +/*@ + requires + take list_ptr = Owned(xs); + take list = ListSegment(list_ptr,NULL); + ensures + take new_list_ptr = Owned(xs); + take new_list = ListSegment(new_list_ptr,NULL); +@*/ +{ + struct List *node = (struct List*) cn_malloc(sizeof(struct List)); + node->value = x; + node->next = *xs; + *xs = node; +} + +void insert(int x, struct List **xs) +/*@ + requires + take list_ptr = Owned(xs); + take list = ListSegment(list_ptr,NULL); + ensures + take new_list_ptr = Owned(xs); + take new_list = ListSegment(new_list_ptr,NULL); + // new_list == insertList(false,x,list); // bug + new_list == insertList(true,x,list); +@*/ +{ + struct List *node = (struct List*) cn_malloc(sizeof(struct List)); + node->value = x; + + struct List* prev = 0; + struct List* cur = *xs; + while (cur && cur->value < x) { + prev = cur; + cur = cur->next; + } + + if (prev) { + prev->next = node; + node->next = cur; + } else { + node->next = *xs; + *xs = node; + } + +} From 9bb2a8d99d99652613131a95975ff4280f5ff880 Mon Sep 17 00:00:00 2001 From: Iavor Diatchki Date: Tue, 5 Nov 2024 11:51:56 -0800 Subject: [PATCH 142/152] Use `char` as elements, and show various issues with testing. --- src/examples/testing/src/list-02.c | 79 +++---------------------- src/examples/testing/src/list-03.c | 56 ++++++++++++++++++ src/examples/testing/src/list-04.c | 87 ++++++++++++++++++++++++++++ src/examples/testing/src/list-05.c | 92 ++++++++++++++++++++++++++++++ 4 files changed, 242 insertions(+), 72 deletions(-) create mode 100644 src/examples/testing/src/list-03.c create mode 100644 src/examples/testing/src/list-04.c create mode 100644 src/examples/testing/src/list-05.c diff --git a/src/examples/testing/src/list-02.c b/src/examples/testing/src/list-02.c index a4e4fa34..4eb7f400 100644 --- a/src/examples/testing/src/list-02.c +++ b/src/examples/testing/src/list-02.c @@ -1,41 +1,28 @@ // Sorted list +#define ELEMENT unsigned char + struct List { - int value; + ELEMENT value; struct List* next; }; /*@ +type_synonym ELEMENT = u8 + datatype IntList { Nil {}, - Cons { i32 head, IntList tail } + Cons { ELEMENT head, IntList tail } } -function (boolean) validCons(i32 head, IntList tail) { +function (boolean) validCons(ELEMENT head, IntList tail) { match tail { Nil {} => { true } Cons { head: next, tail: _ } => { head <= next } } } -function [rec] (IntList) insertList(boolean dups, i32 x, IntList xs) { - match xs { - Nil {} => { Cons { head: x, tail: Nil {} } } - Cons { head: head, tail: tail } => { - if (head < x) { - Cons { head: head, tail: insertList(dups, x,tail) } - } else { - if (!dups && head == x) { - xs - } else { - Cons { head: x, tail: xs } - } - } - } - } -} - predicate IntList ListSegment(pointer from, pointer to) { if (ptr_eq(from,to)) { return Nil {}; @@ -48,7 +35,6 @@ predicate IntList ListSegment(pointer from, pointer to) { } @*/ - // This is a valid spec, even though to verify with CN we'd need a loop invariant. int sum(struct List* xs) /*@ @@ -68,54 +54,3 @@ int sum(struct List* xs) return result; } -void *cn_malloc(unsigned long size); - - -// This is invalid because we don't preserve the sorted invariant. -void cons(int x, struct List** xs) -/*@ - requires - take list_ptr = Owned(xs); - take list = ListSegment(list_ptr,NULL); - ensures - take new_list_ptr = Owned(xs); - take new_list = ListSegment(new_list_ptr,NULL); -@*/ -{ - struct List *node = (struct List*) cn_malloc(sizeof(struct List)); - node->value = x; - node->next = *xs; - *xs = node; -} - -void insert(int x, struct List **xs) -/*@ - requires - take list_ptr = Owned(xs); - take list = ListSegment(list_ptr,NULL); - ensures - take new_list_ptr = Owned(xs); - take new_list = ListSegment(new_list_ptr,NULL); - // new_list == insertList(false,x,list); // bug - new_list == insertList(true,x,list); -@*/ -{ - struct List *node = (struct List*) cn_malloc(sizeof(struct List)); - node->value = x; - - struct List* prev = 0; - struct List* cur = *xs; - while (cur && cur->value < x) { - prev = cur; - cur = cur->next; - } - - if (prev) { - prev->next = node; - node->next = cur; - } else { - node->next = *xs; - *xs = node; - } - -} diff --git a/src/examples/testing/src/list-03.c b/src/examples/testing/src/list-03.c new file mode 100644 index 00000000..a262ee22 --- /dev/null +++ b/src/examples/testing/src/list-03.c @@ -0,0 +1,56 @@ +// Sorted list + +#define ELEMENT unsigned char + +struct List +{ + ELEMENT value; + struct List* next; +}; + +/*@ +type_synonym ELEMENT = u8 + +datatype IntList { + Nil {}, + Cons { ELEMENT head, IntList tail } +} + +function (boolean) validCons(ELEMENT head, IntList tail) { + match tail { + Nil {} => { true } + Cons { head: next, tail: _ } => { head <= next } + } +} + +predicate IntList ListSegment(pointer from, pointer to) { + if (ptr_eq(from,to)) { + return Nil {}; + } else { + take head = Owned(from); + take tail = ListSegment(head.next, to); + assert(validCons(head.value,tail)); + return Cons { head: head.value, tail: tail }; + } +} +@*/ + + +void *cn_malloc(unsigned long size); + +// This is invalid because we don't preserve the sorted invariant. +void cons(ELEMENT x, struct List** xs) +/*@ + requires + take list_ptr = Owned(xs); + take list = ListSegment(list_ptr,NULL); + ensures + take new_list_ptr = Owned(xs); + take new_list = ListSegment(new_list_ptr,NULL); +@*/ +{ + struct List *node = (struct List*) cn_malloc(sizeof(struct List)); + node->value = x; + node->next = *xs; + *xs = node; +} diff --git a/src/examples/testing/src/list-04.c b/src/examples/testing/src/list-04.c new file mode 100644 index 00000000..ab6d729f --- /dev/null +++ b/src/examples/testing/src/list-04.c @@ -0,0 +1,87 @@ +// Sorted list + +#define ELEMENT unsigned char + +struct List +{ + ELEMENT value; + struct List* next; +}; + +/*@ +type_synonym ELEMENT = u8 + +datatype IntList { + Nil {}, + Cons { ELEMENT head, IntList tail } +} + +function (boolean) validCons(ELEMENT head, IntList tail) { + match tail { + Nil {} => { true } + Cons { head: next, tail: _ } => { head <= next } + } +} + +function [rec] (IntList) insertList(boolean dups, ELEMENT x, IntList xs) { + match xs { + Nil {} => { Cons { head: x, tail: Nil {} } } + Cons { head: head, tail: tail } => { + if (head < x) { + Cons { head: head, tail: insertList(dups, x,tail) } + } else { + if (!dups && head == x) { + xs + } else { + Cons { head: x, tail: xs } + } + } + } + } +} + +predicate IntList ListSegment(pointer from, pointer to) { + if (ptr_eq(from,to)) { + return Nil {}; + } else { + take head = Owned(from); + take tail = ListSegment(head.next, to); + assert(validCons(head.value,tail)); + return Cons { head: head.value, tail: tail }; + } +} +@*/ + + +void *cn_malloc(unsigned long size); + +void insert(ELEMENT x, struct List **xs) +/*@ + requires + take list_ptr = Owned(xs); + take list = ListSegment(list_ptr,NULL); + ensures + take new_list_ptr = Owned(xs); + take new_list = ListSegment(new_list_ptr,NULL); + new_list == insertList(true,x,list); +@*/ +{ + struct List *node = (struct List*) cn_malloc(sizeof(struct List)); + node->value = x; + + struct List* prev = 0; + struct List* cur = *xs; + while (cur && cur->value < x) { + prev = cur; + cur = cur->next; + } + + if (prev) { + prev->next = node; + node->next = cur; + } else { + node->next = *xs; + *xs = node; + } + +} diff --git a/src/examples/testing/src/list-05.c b/src/examples/testing/src/list-05.c new file mode 100644 index 00000000..2d111bee --- /dev/null +++ b/src/examples/testing/src/list-05.c @@ -0,0 +1,92 @@ +// Sorted list + +#define ELEMENT unsigned char + +struct List +{ + ELEMENT value; + struct List* next; +}; + +/*@ +type_synonym ELEMENT = u8 + +datatype IntList { + Nil {}, + Cons { ELEMENT head, IntList tail } +} + +function (boolean) validCons(ELEMENT head, IntList tail) { + match tail { + Nil {} => { true } + Cons { head: next, tail: _ } => { head <= next } + } +} + +function [rec] (IntList) insertList(boolean dups, ELEMENT x, IntList xs) { + match xs { + Nil {} => { Cons { head: x, tail: Nil {} } } + Cons { head: head, tail: tail } => { + if (head < x) { + Cons { head: head, tail: insertList(dups, x,tail) } + } else { + if (!dups && head == x) { + xs + } else { + Cons { head: x, tail: xs } + } + } + } + } +} + +predicate IntList ListSegment(pointer from, pointer to) { + if (ptr_eq(from,to)) { + return Nil {}; + } else { + take head = Owned(from); + take tail = ListSegment(head.next, to); + assert(validCons(head.value,tail)); + return Cons { head: head.value, tail: tail }; + } +} +@*/ + + +void *cn_malloc(unsigned long size); + +// This fails because the spec says that insertion discards duplicates, +// but the implementation does not. +// Note that at present we sample types uniformly, so we have to use +// a relatively small type, such as `unsigned int` to catch the error, +// and even then we don't catch the error on each run. +void insert(ELEMENT x, struct List **xs) +/*@ + requires + take list_ptr = Owned(xs); + take list = ListSegment(list_ptr,NULL); + ensures + take new_list_ptr = Owned(xs); + take new_list = ListSegment(new_list_ptr,NULL); + new_list == insertList(false,x,list); +@*/ +{ + struct List *node = (struct List*) cn_malloc(sizeof(struct List)); + node->value = x; + + struct List* prev = 0; + struct List* cur = *xs; + while (cur && cur->value < x) { + prev = cur; + cur = cur->next; + } + + if (prev) { + prev->next = node; + node->next = cur; + } else { + node->next = *xs; + *xs = node; + } + +} From b556cbd076b86d7b8f9fcc812f1dcef761fe60d7 Mon Sep 17 00:00:00 2001 From: Iavor Diatchki Date: Tue, 5 Nov 2024 17:12:20 -0800 Subject: [PATCH 143/152] Add specifications to the vector type --- src/examples/vector/vector.c | 148 +++++++++++++++++++++++++++++------ src/examples/vector/vector.h | 19 ----- 2 files changed, 123 insertions(+), 44 deletions(-) delete mode 100644 src/examples/vector/vector.h diff --git a/src/examples/vector/vector.c b/src/examples/vector/vector.c index ee9b1248..5af79476 100644 --- a/src/examples/vector/vector.c +++ b/src/examples/vector/vector.c @@ -1,45 +1,143 @@ -#include "vector.h" -#include "string.h" -#include "stdlib.h" +#include + +typedef int VEC_ELEMENT; + +struct Vector { + int size; + int capacity; + VEC_ELEMENT *data; +}; + + +void *cn_malloc(size_t size); +void cn_free_sized(void *ptr, size_t size); + +/*@ + +type_synonym VEC_ELEMENT = i32 + +type_synonym Vec = { + struct Vector node, + map elements +} + +predicate (Vec) Vec(pointer p) { + take node = Owned(p); + assert(0i32 <= node.size); + assert(node.size <= node.capacity); + let data = node.data; + take used = each(i32 i; 0i32 <= i && i < node.size) { + Owned(array_shift(data,i)) + }; + take unused = each(i32 i; node.size <= i && i < node.capacity) { + Block(array_shift(data,i)) + }; + return { node: node, elements: used }; +} + + +@*/ // Assumes malloc does not fail -Vector *vec_empty(size_t capacity) { - Vector *p = (Vector*) malloc(sizeof(Vector)); +struct Vector *vec_empty(int capacity) +/*@ +requires + 0i32 <= capacity && capacity <= 100i32; +ensures + take xs = Vec(return); + xs.node.size == 0i32; + xs.node.capacity == capacity; +@*/ +{ + struct Vector *p = (struct Vector*) cn_malloc(sizeof(struct Vector)); p->size = 0; p->capacity = capacity; - p->data = (VEC_ELEMENT*) malloc(sizeof(VEC_ELEMENT[capacity])); + p->data = (VEC_ELEMENT*) cn_malloc(sizeof(VEC_ELEMENT) * capacity); return p; } -// capacity >= size -void vec_resize(Vector *vec, size_t capacity) { - VEC_ELEMENT *new_data = (VEC_ELEMENT*) malloc(sizeof(VEC_ELEMENT[capacity])); - memcpy(new_data, vec->data, sizeof(VEC_ELEMENT[vec->size])); - free(vec->data); + +void vec_free(struct Vector* vec) +/*@ +requires + take xs = Vec(vec); +ensures + true; +@*/ +{ + cn_free_sized(vec->data,sizeof(VEC_ELEMENT) * vec->capacity); + cn_free_sized(vec, sizeof(struct Vector)); +} + + +VEC_ELEMENT *vec_index(struct Vector const* vec, int i) +/*@ +requires + take xs = Vec(vec); + 0i32 <= i && i < xs.node.size; +ensures + take ys = Vec(vec); + xs == ys; + array_shift(xs.node.data,i) == return; +@*/ +{ + return vec->data + i; +} + + +void vec_resize(struct Vector *vec, int capacity) +/*@ +requires + take xs = Vec(vec); + xs.node.size <= capacity; +ensures + take ys = Vec(vec); + ys.node.size == xs.node.size; + ys.node.capacity == capacity; + xs.elements == ys.elements; +@*/ +{ + VEC_ELEMENT *new_data = (VEC_ELEMENT*) cn_malloc(sizeof(VEC_ELEMENT) * capacity); + size_t i = 0; + while (i < vec->size) { + new_data[i] = vec->data[i]; + ++i; + } + cn_free_sized(vec->data, sizeof(VEC_ELEMENT) * vec->capacity); vec->data = new_data; vec->capacity = capacity; } -// Assumes malloc does not fail -void vec_push_back(Vector* vec, VEC_ELEMENT el) { + +void vec_push_back (struct Vector* vec, VEC_ELEMENT el) +/*@ +requires + take xs = Vec(vec); +ensures + take ys = Vec(vec); + xs.node.size + 1i32 == ys.node.size; + xs.elements[xs.node.size: el] == ys.elements; +@*/ +{ if (vec->size == vec->capacity) vec_resize(vec, vec->capacity > 0? 2 * vec->capacity : 1); vec->data[vec->size++] = el; } -// i < size -VEC_ELEMENT *vec_index(Vector const* vec, size_t i) { - return vec->data + i; -} -// size > 0 -void vec_pop_back(Vector* vec) { +void vec_pop_back(struct Vector* vec) +/*@ +requires + take xs = Vec(vec); + xs.node.size > 0i32; +ensures + take ys = Vec(vec); + ys.node.size + 1i32 == xs.node.size; + let last = ys.node.size; + ys.elements[last: xs.elements[last]] == xs.elements; +@*/ +{ size_t new_capacity = vec->capacity >> 1; if (--vec->size < new_capacity) vec_resize(vec,new_capacity); -} - -void vec_free(Vector* vec) { - free(vec->data); - free(vec); -} +} \ No newline at end of file diff --git a/src/examples/vector/vector.h b/src/examples/vector/vector.h deleted file mode 100644 index 5583a723..00000000 --- a/src/examples/vector/vector.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef VECTOR_H -#define VECTOR_H - -#include - -typedef int VEC_ELEMENT; - -typedef struct { - size_t size; - size_t capacity; - VEC_ELEMENT *data; -} Vector; - -Vector *vec_empty(); -VEC_ELEMENT *vec_index(Vector const *vec, size_t index); -void vec_push_back(Vector *vec, VEC_ELEMENT el); -void vec_pop_back(Vector *vec); -void vec_free(Vector *vec); -#endif From 3456417cc5aac69d80ef2bd4d200d82469f7b24c Mon Sep 17 00:00:00 2001 From: Iavor Diatchki Date: Wed, 6 Nov 2024 12:12:18 -0800 Subject: [PATCH 144/152] A script to remove Etna mutants --- src/examples/testing/do-test | 9 ++++++--- src/examples/testing/unetna.hs | 35 ++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) create mode 100755 src/examples/testing/unetna.hs diff --git a/src/examples/testing/do-test b/src/examples/testing/do-test index 2594908b..d3b9a05b 100755 --- a/src/examples/testing/do-test +++ b/src/examples/testing/do-test @@ -12,12 +12,15 @@ fi FILE=$1 BASE=$(basename "$FILE" .c) -BUILD_DIR=$(mktemp -d "cn-test-XXXXXXXX") +BUILD_DIR="" function cleanup { - echo "GENERATED: $BUILD_DIR" - rm -rf $BUILD_DIR + if [ $BUILD_DIR != "" ]; then + echo "GENERATED: $BUILD_DIR" + rm -rf $BUILD_DIR + fi } trap cleanup EXIT +BUILD_DIR=$(mktemp -d "cn-test-XXXXXXXX") TEST_FILE="$BUILD_DIR/${BASE}_test.c" TGT="$BUILD_DIR/run" diff --git a/src/examples/testing/unetna.hs b/src/examples/testing/unetna.hs new file mode 100755 index 00000000..fb08e534 --- /dev/null +++ b/src/examples/testing/unetna.hs @@ -0,0 +1,35 @@ +#!/usr/bin/env runhaskell + +{- +This script removes Etna mutants for CN. +We make some simplifyinf assumptions, namely: + * Etna comments are on their own line + * There's at least one non-enta line between the mutants for one + block and the following Etna block. +-} + +import Data.Char(isSpace) + + + +main = interact (unlines . strip . lines) + +strip ls + | null ls = ls + | otherwise = + case break (etnaLine NonMut) ls of + (as,_:bs) -> as ++ code ++ strip (dropWhile (etnaLine Any) muts) + where + (code,muts) = break (etnaLine Mut) bs + _ -> ls + +data EtnaLine = Any | Mut | NonMut + +etnaLine ty l = + case dropWhile isSpace l of + '/' : '/' : '!' : more -> + case ty of + Any -> True + Mut -> take 1 more == "!" + NonMut -> take 1 more /= "!" + _ -> False From 4f09ded68a78f2bb37822b42af81f69689895d0e Mon Sep 17 00:00:00 2001 From: Iavor Diatchki Date: Wed, 6 Nov 2024 16:35:00 -0800 Subject: [PATCH 145/152] Testable vector and bst (the vector doesn't quite work due to #698) --- src/examples/testing/src/bst.c | 409 ++++++++++++++++++ src/examples/{vector => testing/src}/vector.c | 0 2 files changed, 409 insertions(+) create mode 100644 src/examples/testing/src/bst.c rename src/examples/{vector => testing/src}/vector.c (100%) diff --git a/src/examples/testing/src/bst.c b/src/examples/testing/src/bst.c new file mode 100644 index 00000000..1354ab10 --- /dev/null +++ b/src/examples/testing/src/bst.c @@ -0,0 +1,409 @@ +#include + +#define KEY int +#define VALUE long + +struct MapNode { + KEY key; + VALUE value; + struct MapNode *smaller; + struct MapNode *larger; +}; + +extern void* cn_malloc(size_t size); +extern void cn_free_sized(void *ptr, size_t size); + + +/*@ + +type_synonym KEY = i32 +type_synonym VALUE = i64 +type_synonym NodeData = { KEY key, VALUE value } + +function (KEY) defaultKey() { 0i32 } + +datatype ValueOption { + ValueNone {}, + ValueSome { VALUE value } +} + + +// ----------------------------------------------------------------------------- +// Intervals + +// Non-empty, closed intervals +type_synonym Interval = { KEY lower, KEY upper } + +function (Interval) defaultInterval() { + { lower: defaultKey(), upper: defaultKey() } +} + +datatype IntervalOption { + IntervalNone {}, + IntervalSome { Interval i } +} + +function (boolean) isIntervalSome(IntervalOption i) { + match i { + IntervalNone {} => { false } + IntervalSome { i:_ } => { true } + } +} + +function (Interval) fromIntervalOption(IntervalOption i) { + match i { + IntervalNone {} => { defaultInterval() } + IntervalSome { i:j } => { j } + } +} + + +function (IntervalOption) + joinInterval(IntervalOption optSmaller, KEY val, IntervalOption optLarger) { + match optSmaller { + IntervalNone {} => { + match optLarger { + IntervalNone {} => { + IntervalSome { i: { lower: val, upper: val } } + } + IntervalSome { i: larger } => { + if (val < larger.lower) { + IntervalSome { i: { lower: val, upper: larger.upper } } + } else { + IntervalNone {} + } + } + } + } + IntervalSome { i: smaller } => { + if (val > smaller.upper) { + match optLarger { + IntervalNone {} => { + IntervalSome { i: { lower: smaller.lower, upper: val } } + } + IntervalSome { i: larger } => { + if (val < larger.lower) { + IntervalSome { i: { lower: smaller.lower, upper: larger.upper } } + } else { + IntervalNone {} + } + } + } + } else { + IntervalNone {} + } + } + } +} + + + +// ----------------------------------------------------------------------------- + + + + +// A binary dearch tree +datatype BST { + Leaf {}, + Node { NodeData data, BST smaller, BST larger } +} + +function (boolean) hasRoot(KEY key, BST tree) { + match tree { + Leaf {} => { false } + Node { data: data, smaller: _, larger: _ } => { data.key == key } + } +} + +function [rec] (ValueOption) lookup(KEY key, BST tree) { + match tree { + Leaf {} => { ValueNone {} } + Node { data: data, smaller: smaller, larger: larger } => { + if (data.key == key) { + ValueSome { value: data.value } + } else { + if (data.key < key) { + lookup(key,larger) + } else { + lookup(key,smaller) + } + } + } + } +} + +function [rec] (boolean) member(KEY k, BST tree) { + match tree { + Leaf {} => { false } + Node { data: data, smaller: smaller, larger: larger } => { + data.key == k || + k < data.key && member(k,smaller) || + k > data.key && member(k,larger) + } + } +} + +function [rec] (BST) insert(KEY key, VALUE value, BST tree) { + match tree { + Leaf {} => { Node { data: { key: key, value: value }, + smaller: Leaf {}, larger: Leaf {} } } + Node { data: data, smaller: smaller, larger: larger } => { + if (data.key == key) { + Node { data: { key: key, value: value }, + smaller: smaller, larger: larger } + } else { + if (data.key < key) { + Node { data: data, + smaller: smaller, larger: insert(key,value,larger) } + } else { + Node { data: data, + smaller: insert(key,value,smaller), larger: larger } + } + } + } + } +} + +function [rec] (BST) setKey(KEY k, BST root, BST value) { + match root { + Leaf {} => { value } + Node { data: data, smaller: smaller, larger: larger } => { + if (k < data.key) { + Node { data: data, smaller: setKey(k, smaller, value), larger: larger } + } else { + Node { data: data, smaller: smaller, larger: setKey(k, larger, value) } + } + } + } +} + + + + +// ***************************************************************************** +// Consuming an entire tree +// ***************************************************************************** + + +// Semantic data stored at a node +function (NodeData) getNodeData(struct MapNode node) { + { key: node.key, value: node.value } +} + +type_synonym RangedBST = { BST tree, IntervalOption range } +type_synonym RangedNode = { + struct MapNode node, + BST smaller, + BST larger, + Interval range +} + +predicate RangedNode RangedNode(pointer root) { + take node = Owned(root); + take smaller = RangedBST(node.smaller); + take larger = RangedBST(node.larger); + let rangeOpt = joinInterval(smaller.range, node.key, larger.range); + assert (isIntervalSome(rangeOpt)); + return { node: node, smaller: smaller.tree, larger: larger.tree, + range: fromIntervalOption(rangeOpt) }; +} + +// A binary search tree, and the interval for all its keys. +predicate RangedBST RangedBST(pointer root) { + if (is_null(root)) { + return { tree: Leaf {}, range: IntervalNone{} }; + } else { + take node = RangedNode(root); + let data = getNodeData(node.node); + return { tree: Node { data: data, smaller: node.smaller, larger: node.larger }, + range: IntervalSome { i: node.range } }; + } +} + +// An arbitrary binary search tree. +predicate BST BST(pointer root) { + take result = RangedBST(root); + return result.tree; +} + + + + +// ***************************************************************************** +// Focusing on a node in the tree +// ***************************************************************************** + +type_synonym BSTNodeFocus = + { BST done, struct MapNode node, BST smaller, BST larger } + +datatype BSTFocus { + AtLeaf { BST tree }, + AtNode { BST done, struct MapNode node, BST smaller, BST larger } +} + +predicate BSTFocus BSTFocus(pointer root, pointer child) { + if (is_null(child)) { + take tree = BST(root); + return AtLeaf { tree: tree }; + } else { + take node = RangedNode(child); + take result = BSTNodeUpTo(root, child, node.node, node.range); + return AtNode { done: result.tree, node: node.node, + smaller: node.smaller, larger: node.larger }; + } +} + +// Consume parts of the tree starting at `p` until we get to `c`. +// We do not consume `c`. +// `child` is the node stored at `c`. +predicate RangedBST BSTNodeUpTo(pointer p, pointer c, struct MapNode child, Interval range) { + if (ptr_eq(p,c)) { + return { tree: Leaf {}, range: IntervalSome { i: range } }; + } else { + take parent = Owned(p); + take result = BSTNodeChildUpTo(c, child, range, parent); + return result; + } +} + +// Starting at a parent with data `data` and children `smaller` and `larger`, +// we go toward `c`, guided by its value, `target`. +predicate RangedBST + BSTNodeChildUpTo(pointer c, struct MapNode target, Interval range, struct MapNode parent) { + if (parent.key < target.key) { + take small = RangedBST(parent.smaller); + take large = BSTNodeUpTo(parent.larger, c, target, range); + let node = getNodeData(parent); + let optRange = joinInterval(small.range, node.key, large.range); + assert(isIntervalSome(optRange)); + return { tree: Node { data: node, smaller: small.tree, larger: large.tree }, + range: optRange }; + } else { + if (parent.key > target.key) { + take small = BSTNodeUpTo(parent.smaller, c, target, range); + take large = RangedBST(parent.larger); + let node = getNodeData(parent); + let optRange = joinInterval(small.range, node.key, large.range); + assert(isIntervalSome(optRange)); + return { tree: Node { data: node, smaller: small.tree, larger: large.tree }, + range: optRange }; + } else { + // We should never get here, but asserting `false` is not allowed + return { tree: Leaf {}, range: IntervalNone {} }; + }} +} + +function (BST) unfocus(BSTFocus focus) { + match focus { + AtLeaf { tree: tree } => { tree } + AtNode { done: tree, node: node, smaller: smaller, larger: larger } => { + let bst = Node { data: getNodeData(node), smaller: smaller, larger: larger }; + setKey(node.key, tree, bst) + } + } +} + +function (BST) focusDone(BSTFocus focus) { + match focus { + AtLeaf { tree: tree } => { tree } + AtNode { done: tree, node: _, smaller: _, larger: _ } => { tree } + } +} + + + +@*/ + + +/* Allocate a new singleton node */ +struct MapNode *newNode(KEY key, VALUE value) +/*@ +requires + true; +ensures + take node = Owned(return); + node.key == key; + node.value == value; + is_null(node.smaller); + is_null(node.larger); +@*/ +{ + struct MapNode *node = (struct MapNode*)cn_malloc(sizeof(struct MapNode)); + node->key = key; + node->value = value; + node->smaller = 0; + node->larger = 0; + return node; +} + + +struct MapNode *findParent(struct MapNode **node, KEY key) +/*@ +requires + take tree_ptr = Owned(node); + take tree = BST(tree_ptr); +ensures + take cur_ptr = Owned(node); + let not_found = is_null(cur_ptr); + not_found == !member(key, tree); + take focus = BSTFocus(tree_ptr, return); + unfocus(focus) == tree; + match focus { + AtLeaf { tree: _ } => { + not_found || ptr_eq(cur_ptr,tree_ptr) && hasRoot(key, tree) + } + AtNode { done: _, node: parent, smaller: _, larger: _ } => { + let tgt = if (key < parent.key) { parent.smaller } else { parent.larger }; + ptr_eq(cur_ptr,tgt) + } + }; +@*/ +{ + struct MapNode *parent = 0; + struct MapNode *cur = *node; + while (cur) + { + KEY k = cur->key; + if (k == key) { + *node = cur; + return parent; + } + parent = cur; + cur = k < key? cur->larger : cur->smaller; + } + *node = cur; + return parent; +} + +/* Insert an element into a map. Overwrites previous if already present. */ +void map_insert(struct MapNode **root, KEY key, VALUE value) +/*@ +requires + take root_ptr = Owned(root); + take tree = BST(root_ptr); +ensures + take new_root = Owned(root); + take new_tree = BST(new_root); + new_tree == insert(key, value, tree); +@*/ +{ + struct MapNode *search = *root; + struct MapNode *parent = findParent(&search, key); + if (search) { + search->value = value; + return; + } + + if (!parent) { + *root = newNode(key,value); + return; + } + + struct MapNode *new_node = newNode(key,value); + if (parent->key < key) { + parent->larger = new_node; + } else { + parent->smaller = new_node; + } +} \ No newline at end of file diff --git a/src/examples/vector/vector.c b/src/examples/testing/src/vector.c similarity index 100% rename from src/examples/vector/vector.c rename to src/examples/testing/src/vector.c From f80fa492487496fde319d8846c3033cf01716afd Mon Sep 17 00:00:00 2001 From: Iavor Diatchki Date: Thu, 7 Nov 2024 09:59:28 -0800 Subject: [PATCH 146/152] Rename to caml case --- src/examples/testing/src/bst.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/examples/testing/src/bst.c b/src/examples/testing/src/bst.c index 1354ab10..c791bf68 100644 --- a/src/examples/testing/src/bst.c +++ b/src/examples/testing/src/bst.c @@ -1,5 +1,6 @@ #include + #define KEY int #define VALUE long @@ -377,7 +378,7 @@ ensures } /* Insert an element into a map. Overwrites previous if already present. */ -void map_insert(struct MapNode **root, KEY key, VALUE value) +void setNodeKey(struct MapNode **root, KEY key, VALUE value) /*@ requires take root_ptr = Owned(root); From c6985f55ca75cf53fa172f19fde7a2c4d0bfaab1 Mon Sep 17 00:00:00 2001 From: Iavor Diatchki Date: Thu, 7 Nov 2024 15:40:32 -0800 Subject: [PATCH 147/152] Delete from BST --- src/examples/testing/src/bst.c | 151 +++++++++++++++++++++++++++++++-- 1 file changed, 146 insertions(+), 5 deletions(-) diff --git a/src/examples/testing/src/bst.c b/src/examples/testing/src/bst.c index c791bf68..49d9e966 100644 --- a/src/examples/testing/src/bst.c +++ b/src/examples/testing/src/bst.c @@ -22,6 +22,10 @@ type_synonym VALUE = i64 type_synonym NodeData = { KEY key, VALUE value } function (KEY) defaultKey() { 0i32 } +function (VALUE) defaultValue() { 0i64 } +function (NodeData) defaultNodeData() { + { key: defaultKey(), value: defaultValue() } +} datatype ValueOption { ValueNone {}, @@ -117,6 +121,13 @@ function (boolean) hasRoot(KEY key, BST tree) { } } +function (boolean) isLeaf(BST tree) { + match tree { + Leaf {} => { true } + Node { data: _, smaller: _, larger: _ } => { false } + } +} + function [rec] (ValueOption) lookup(KEY key, BST tree) { match tree { Leaf {} => { ValueNone {} } @@ -179,6 +190,44 @@ function [rec] (BST) setKey(KEY k, BST root, BST value) { } } +function [rec] ({ NodeData data, BST tree }) delLeast(BST root) { + match root { + Leaf {} => { { data: defaultNodeData(), tree: Leaf {} } } + Node { data: data, smaller: smaller, larger: larger } => { + if (isLeaf(smaller)) { + { data: data, tree: larger } + } else { + let res = delLeast(smaller); + { data: res.data, + tree: Node { data: data, smaller: res.tree, larger: larger } + } + } + } + } +} + +function [rec] (BST) delKey(KEY key, BST root) { + match root { + Leaf {} => { Leaf {} } + Node { data: data, smaller: smaller, larger: larger } => { + if (key == data.key) { + let res = delLeast(larger); + if (isLeaf(res.tree)) { + smaller + } else { + Node { data: res.data, smaller: smaller, larger: res.tree } + } + } else { + if (key < data.key) { + Node { data: data, smaller: delKey(key, smaller), larger: larger } + } else { + Node { data: data, smaller: smaller, larger: delKey(key, larger) } + } + } + } + } +} + @@ -389,10 +438,12 @@ ensures new_tree == insert(key, value, tree); @*/ { - struct MapNode *search = *root; - struct MapNode *parent = findParent(&search, key); - if (search) { - search->value = value; + struct MapNode *found = *root; + struct MapNode *parent = findParent(&found, key); + + + if (found) { + found->value = value; return; } @@ -407,4 +458,94 @@ ensures } else { parent->smaller = new_node; } -} \ No newline at end of file +} + + +void deleteTree(struct MapNode *root) +/*@ + requires + take tree = BST(root); + ensures + true; +@*/ +{ + if (!root) return; + deleteTree(root->smaller); + deleteTree(root->larger); + cn_free_sized(root, sizeof(struct MapNode)); +} + + +/*@ +predicate (void) DeleteSmallest(pointer cur, NodeData data) { + if (is_null(cur)) { + return; + } else { + take node = Owned(cur); + assert(node.key == data.key); + assert(node.value == data.value); + return; + } +} +@*/ + +struct MapNode* deleteSmallest(struct MapNode **root) +/*@ + requires + take root_ptr = Owned(root); + take tree = BST(root_ptr); + ensures + take new_root = Owned(root); + take new_tree = BST(new_root); + let res = delLeast(tree); + new_tree == res.tree; + take unsued = DeleteSmallest(return, res.data); +@*/ +{ + struct MapNode *cur = *root; + if (!cur) return 0; + + struct MapNode *parent = 0; + while (cur->smaller) { + parent = cur; + cur = cur->smaller; + } + + if (parent) { + parent->smaller = cur->larger; + } + else *root = cur->larger; + + return cur; +} + + +void deleteKey(struct MapNode **root, KEY key) +/*@ +requires + take root_ptr = Owned(root); + take tree = BST(root_ptr); +ensures + take new_ptr = Owned(root); + take new_tree = BST(new_ptr); + delKey(key, tree) == new_tree; +@*/ +{ + struct MapNode *found = *root; + struct MapNode *parent = findParent(&found, key); + + if (!found) return; + struct MapNode *remove = deleteSmallest(&found->larger); + if (remove) { + found->key = remove->key; + found->value = remove->value; + } else { + remove = found; + if (parent) { + parent->smaller = found->smaller; + } else { + *root = found->smaller; + } + } + cn_free_sized(remove, sizeof(struct MapNode)); +} From 6d25b99a25f6bc8ec1483eec31338f65ae0abd75 Mon Sep 17 00:00:00 2001 From: Iavor Diatchki Date: Thu, 7 Nov 2024 15:47:05 -0800 Subject: [PATCH 148/152] Add a couple of mutants --- src/examples/testing/src/bst.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/examples/testing/src/bst.c b/src/examples/testing/src/bst.c index 49d9e966..3c98e6bb 100644 --- a/src/examples/testing/src/bst.c +++ b/src/examples/testing/src/bst.c @@ -511,10 +511,11 @@ struct MapNode* deleteSmallest(struct MapNode **root) cur = cur->smaller; } - if (parent) { - parent->smaller = cur->larger; - } + if (parent) parent->smaller = cur->larger; + //!// else *root = cur->larger; + //!! forget_to_update_root // + //!// return cur; } @@ -541,11 +542,12 @@ ensures found->value = remove->value; } else { remove = found; - if (parent) { - parent->smaller = found->smaller; - } else { + //!// + if (parent) parent->smaller = found->smaller; + else + //!! always_update_root_instead_of_parent // + //!// *root = found->smaller; - } } cn_free_sized(remove, sizeof(struct MapNode)); } From 2c8f00fa3e32b9e9fd0b698d692af4b93e421caf Mon Sep 17 00:00:00 2001 From: Iavor Diatchki Date: Mon, 11 Nov 2024 15:19:22 -0800 Subject: [PATCH 149/152] Generate a coverage report --- src/examples/testing/do-test | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/examples/testing/do-test b/src/examples/testing/do-test index d3b9a05b..3d99a4ac 100755 --- a/src/examples/testing/do-test +++ b/src/examples/testing/do-test @@ -16,17 +16,26 @@ BUILD_DIR="" function cleanup { if [ $BUILD_DIR != "" ]; then echo "GENERATED: $BUILD_DIR" - rm -rf $BUILD_DIR + # rm -rf $BUILD_DIR fi } trap cleanup EXIT BUILD_DIR=$(mktemp -d "cn-test-XXXXXXXX") TEST_FILE="$BUILD_DIR/${BASE}_test.c" -TGT="$BUILD_DIR/run" +EXE="run" +TGT="$BUILD_DIR/$EXE" cn test $1 --no-run --output-dir="$BUILD_DIR" -cc -g -o"$TGT" -I"$CN_INSTALL/include" -L"$CN_INSTALL" "$TEST_FILE" -lcn -$TGT - +cc -g \ + -fprofile-arcs -ftest-coverage \ + -o"$TGT" -I"$CN_INSTALL/include" \ + -L"$CN_INSTALL" "$TEST_FILE" -lcn + +pushd "$BUILD_DIR" +"./$EXE" || true +gcov "$EXE-${BASE}_test.c" +lcov --capture --directory . --output-file "coverage.info" +genhtml coverage.info --output-director html +popd From 89971ed663bb8946c5fcc417091a5c9461969cf5 Mon Sep 17 00:00:00 2001 From: Iavor Diatchki Date: Mon, 11 Nov 2024 15:19:59 -0800 Subject: [PATCH 150/152] A BST formulation that separates memory layout and the invariant --- src/examples/testing/src/bst1.c | 577 ++++++++++++++++++++++++++++++++ 1 file changed, 577 insertions(+) create mode 100644 src/examples/testing/src/bst1.c diff --git a/src/examples/testing/src/bst1.c b/src/examples/testing/src/bst1.c new file mode 100644 index 00000000..efc0ed32 --- /dev/null +++ b/src/examples/testing/src/bst1.c @@ -0,0 +1,577 @@ +#include + + +#define KEY int +#define VALUE long + +struct MapNode { + KEY key; + VALUE value; + struct MapNode *smaller; + struct MapNode *larger; +}; + +extern void* cn_malloc(size_t size); +extern void cn_free_sized(void *ptr, size_t size); + + +/*@ + + +// ***************************************************************************** +// Keys and Values +// ***************************************************************************** + +type_synonym KEY = i32 +type_synonym VALUE = i64 +type_synonym NodeData = { KEY key, VALUE value } + +function (KEY) defaultKey() { 0i32 } + +function (VALUE) defaultValue() { 0i64 } + +function (NodeData) defaultNodeData() { + { + key: defaultKey(), + value: defaultValue() + } +} + +// Semantic data stored at a node +function (NodeData) getNodeData(struct MapNode node) { + { + key: node.key, + value: node.value + } +} + +datatype ValueOption { + ValueNone {}, + ValueSome { VALUE value } +} + +function (boolean) isValueSome(ValueOption v) { + match v { + ValueNone {} => { false } + ValueSome { value: _ } => { true } + } +} + + + +// ***************************************************************************** +// Binary Trees +// ***************************************************************************** + +// An in-memory binary tree +datatype BSTMem { + LeafMem {}, + NodeMem { + struct MapNode data, + BSTMem smaller, + BSTMem larger + } +} + +// The values in a binary tree. +datatype BST { + Leaf {}, + Node { + NodeData data, + BST smaller, + BST larger + } +} + +// Describes the layout of an initialized binary tree in memory +predicate BSTMem BSTMem(pointer root) { + if (is_null(root)) { + return LeafMem {}; + } else { + take data = Owned(root); + take smaller = BSTMem(data.smaller); + take larger = BSTMem(data.larger); + return + NodeMem { + data: data, + smaller: smaller, + larger: larger + }; + } +} + +// Forget the pointers for an in-memory tree. +function [rec] (BST) treeValue(BSTMem tree) { + match tree { + LeafMem {} => { Leaf {} } + NodeMem { data: data, smaller: smaller, larger: larger } => { + Node { + data: getNodeData(data), + smaller: treeValue(smaller), + larger: treeValue(larger) + } + } + } +} + +function (boolean) isLeaf(BST tree) { + match tree { + Leaf {} => { true } + Node { data: _, smaller: _, larger: _ } => { false } + } +} + + + + + + + +// ***************************************************************************** +// Intervals +// ***************************************************************************** + +// Closed, non-empty interval +type_synonym NonEmptyI = { KEY lower, KEY upper } + +// Non-empty, closed intervals +datatype Interval { + EmptyI {}, + NonEmptyI { NonEmptyI i } +} + +function (Interval) defaultInterval() { + EmptyI {} +} + +datatype IntervalOption { + IntervalNone {}, + IntervalSome { Interval i } +} + +function (boolean) isIntervalSome(IntervalOption i) { + match i { + IntervalNone {} => { false } + IntervalSome { i:_ } => { true } + } +} + +function (Interval) fromIntervalSome(IntervalOption i) { + match i { + IntervalNone {} => { defaultInterval() } + IntervalSome { i:j } => { j } + } +} + +function (Interval) singletonI(KEY k) { + NonEmptyI { i: { lower: k, upper: k } } +} + +function (IntervalOption) joinIntervals(Interval lower, Interval upper) { + match lower { + EmptyI {} => { + IntervalSome { i: upper } + } + NonEmptyI { i: lowerI } => { + match upper { + EmptyI {} => { + IntervalSome { i: lower } + } + NonEmptyI { i: upperI } => { + if (lowerI.upper < upperI.lower) { + IntervalSome { i: NonEmptyI { i: { lower: lowerI.lower, upper: upperI.upper } } } + } else { + IntervalNone {} + } + } + } + } + } +} + +function (IntervalOption) + joinIntervalOpt(IntervalOption lowerOpt, IntervalOption upperOpt) { + match lowerOpt { + IntervalNone {} => { IntervalNone {} } + IntervalSome { i: lower } => { + match upperOpt { + IntervalNone {} => { IntervalNone {} } + IntervalSome { i: upper } => { + joinIntervals(lower,upper) + } + } + } + } +} + + + +// ***************************************************************************** +// Binary Search Tree +// ***************************************************************************** + +function [rec] (IntervalOption) bstRange(BST tree) { + match tree { + Leaf {} => { IntervalSome { i: EmptyI {} } } + Node { data: data, smaller: smaller, larger: larger } => { + let smallerOpt = bstRange(smaller); + let largerOpt = bstRange(larger); + let thisI = IntervalSome { i: singletonI(data.key) }; + let mid = joinIntervalOpt(smallerOpt, thisI); + joinIntervalOpt(mid,largerOpt) + } + } +} + +function (boolean) validBST(BST tree) { + isIntervalSome(bstRange(tree)) +} + +function (boolean) hasRoot(KEY key, BST tree) { + match tree { + Leaf {} => { false } + Node { data: data, smaller: _, larger: _ } => { data.key == key } + } +} + +function [rec] (ValueOption) lookup(KEY key, BST tree) { + match tree { + Leaf {} => { ValueNone {} } + Node { data: data, smaller: smaller, larger: larger } => { + if (data.key == key) { + ValueSome { value: data.value } + } else { + if (data.key < key) { + lookup(key,larger) + } else { + lookup(key,smaller) + } + } + } + } +} + +function (boolean) member(KEY k, BST tree) { + isValueSome(lookup(k,tree)) +} + +function [rec] (BST) insert(KEY key, VALUE value, BST tree) { + match tree { + Leaf {} => { + Node { + data: { key: key, value: value }, + smaller: Leaf {}, + larger: Leaf {} + } + } + Node { data: data, smaller: smaller, larger: larger } => { + if (data.key == key) { + Node { + data: { key: key, value: value }, + smaller: smaller, + larger: larger + } + } else { + if (data.key < key) { + Node { + data: data, + smaller: smaller, + larger: insert(key,value,larger) + } + } else { + Node { + data: data, + smaller: insert(key,value,smaller), + larger: larger + } + } + } + } + } +} + + +function [rec] ({ NodeData data, BST tree }) delLeast(BST root) { + match root { + Leaf {} => { + { + data: defaultNodeData(), + tree: Leaf {} + } + } + Node { data: data, smaller: smaller, larger: larger } => { + if (isLeaf(smaller)) { + { + data: data, + tree: larger + } + } else { + let res = delLeast(smaller); + { + data: res.data, + tree: + Node { + data: data, + smaller: res.tree, + larger: larger + } + } + } + } + } +} + +function [rec] (BST) delKey(KEY key, BST root) { + match root { + Leaf {} => { Leaf {} } + Node { data: data, smaller: smaller, larger: larger } => { + if (key == data.key) { + let res = delLeast(larger); + if (isLeaf(res.tree)) { + smaller + } else { + Node { + data: res.data, + smaller: smaller, + larger: res.tree + } + } + } else { + if (key < data.key) { + Node { + data: data, + smaller: delKey(key, smaller), + larger: larger + } + } else { + Node { + data: data, + smaller: smaller, + larger: delKey(key, larger) + } + } + } + } + } +} + +// Retruns `LeafMem` if the pointer is not a node in the tree. +function [rec] (BSTMem) findNode(pointer tgt, pointer tree_ptr, BSTMem tree) { + if (ptr_eq(tgt,tree_ptr)) { + tree + } else { + match tree { + LeafMem {} => { tree } + NodeMem { data: data, smaller: smaller, larger: larger } => { + let inSmaller = findNode(tgt, data.smaller, smaller); + match inSmaller { + LeafMem {} => { findNode(tgt, data.larger, larger) } + NodeMem { data: _, smaller: _, larger: _ } => { inSmaller } + } + } + } + } +} + + +@*/ + + + + + +/* Allocate a new singleton node */ +struct MapNode *newNode(KEY key, VALUE value) +/*@ +requires + true; +ensures + take node = Owned(return); + node.key == key; + node.value == value; + is_null(node.smaller); + is_null(node.larger); +@*/ +{ + struct MapNode *node = (struct MapNode*)cn_malloc(sizeof(struct MapNode)); + node->key = key; + node->value = value; + node->smaller = 0; + node->larger = 0; + return node; +} + + +struct MapNode *findParent(struct MapNode **node, KEY key) +/*@ +requires + take tree_ptr = Owned(node); + take mem_tree = BSTMem(tree_ptr); + let tree = treeValue(mem_tree); + validBST(tree); +ensures + take new_mem_tree = BSTMem(tree_ptr); + new_mem_tree == mem_tree; + take cur_ptr = Owned(node); + let parent_node = findNode(return, tree_ptr, mem_tree); + match parent_node { + LeafMem {} => { + isLeaf(tree) && is_null(cur_ptr) || + hasRoot(key, tree) && ptr_eq(cur_ptr,tree_ptr) + } + NodeMem { data: parent, smaller: smaller, larger: larger } => { + let tgt = + if (key < parent.key) { + { ptr: parent.smaller, tree: smaller } + } else { + { ptr: parent.larger, tree: larger } + }; + let tgtTree = treeValue(tgt.tree); + ptr_eq(cur_ptr,tgt.ptr) && (isLeaf(tgtTree) || hasRoot(key,tgtTree)) + } + }; +@*/ +{ + struct MapNode *parent = 0; + struct MapNode *cur = *node; + while (cur) + { + KEY k = cur->key; + if (k == key) { + *node = cur; + return parent; + } + parent = cur; + cur = k < key? cur->larger : cur->smaller; + } + *node = cur; + return parent; +} + +#if 0 +/* Insert an element into a map. Overwrites previous if already present. */ +void setNodeKey(struct MapNode **root, KEY key, VALUE value) +/* +requires + take root_ptr = Owned(root); + take tree = BST(root_ptr); +ensures + take new_root = Owned(root); + take new_tree = BST(new_root); + new_tree == insert(key, value, tree); +*/ +{ + struct MapNode *found = *root; + struct MapNode *parent = findParent(&found, key); + + + if (found) { + found->value = value; + return; + } + + if (!parent) { + *root = newNode(key,value); + return; + } + + struct MapNode *new_node = newNode(key,value); + if (parent->key < key) { + parent->larger = new_node; + } else { + parent->smaller = new_node; + } +} + + +void deleteTree(struct MapNode *root) +/* + requires + take tree = BST(root); + ensures + true; +*/ +{ + if (!root) return; + deleteTree(root->smaller); + deleteTree(root->larger); + cn_free_sized(root, sizeof(struct MapNode)); +} + + +/* +predicate (void) DeleteSmallest(pointer cur, NodeData data) { + if (is_null(cur)) { + return; + } else { + take node = Owned(cur); + assert(node.key == data.key); + assert(node.value == data.value); + return; + } +} +*/ + +struct MapNode* deleteSmallest(struct MapNode **root) +/* + requires + take root_ptr = Owned(root); + take tree = BST(root_ptr); + ensures + take new_root = Owned(root); + take new_tree = BST(new_root); + let res = delLeast(tree); + new_tree == res.tree; + take unsued = DeleteSmallest(return, res.data); +*/ +{ + struct MapNode *cur = *root; + if (!cur) return 0; + + struct MapNode *parent = 0; + while (cur->smaller) { + parent = cur; + cur = cur->smaller; + } + + if (parent) parent->smaller = cur->larger; + //!// + else *root = cur->larger; + //!! forget_to_update_root // + //!// + + return cur; +} + + +void deleteKey(struct MapNode **root, KEY key) +/* +requires + take root_ptr = Owned(root); + take tree = BST(root_ptr); +ensures + take new_ptr = Owned(root); + take new_tree = BST(new_ptr); + delKey(key, tree) == new_tree; +*/ +{ + struct MapNode *found = *root; + struct MapNode *parent = findParent(&found, key); + + if (!found) return; + struct MapNode *remove = deleteSmallest(&found->larger); + if (remove) { + found->key = remove->key; + found->value = remove->value; + } else { + remove = found; + //!// + if (parent) parent->smaller = found->smaller; + else + //!! always_update_root_instead_of_parent // + //!// + *root = found->smaller; + } + cn_free_sized(remove, sizeof(struct MapNode)); +} +#endif \ No newline at end of file From 045a2d7546669c4ea736f11fc264b233e35f0fcc Mon Sep 17 00:00:00 2001 From: Iavor Diatchki Date: Tue, 12 Nov 2024 11:55:04 -0800 Subject: [PATCH 151/152] Fix implementation and spec for deletion, and add specs for other operations --- src/examples/testing/src/bst1.c | 87 +++++++++++++++++++-------------- 1 file changed, 51 insertions(+), 36 deletions(-) diff --git a/src/examples/testing/src/bst1.c b/src/examples/testing/src/bst1.c index efc0ed32..17ebfe1f 100644 --- a/src/examples/testing/src/bst1.c +++ b/src/examples/testing/src/bst1.c @@ -291,19 +291,21 @@ function [rec] (BST) insert(KEY key, VALUE value, BST tree) { } -function [rec] ({ NodeData data, BST tree }) delLeast(BST root) { +function [rec] ({ boolean empty, NodeData data, BST tree }) delLeast(BST root) { match root { Leaf {} => { { data: defaultNodeData(), - tree: Leaf {} + tree: Leaf {}, + empty: true } } Node { data: data, smaller: smaller, larger: larger } => { if (isLeaf(smaller)) { { data: data, - tree: larger + tree: larger, + empty: false } } else { let res = delLeast(smaller); @@ -314,7 +316,8 @@ function [rec] ({ NodeData data, BST tree }) delLeast(BST root) { data: data, smaller: res.tree, larger: larger - } + }, + empty: false } } } @@ -327,7 +330,7 @@ function [rec] (BST) delKey(KEY key, BST root) { Node { data: data, smaller: smaller, larger: larger } => { if (key == data.key) { let res = delLeast(larger); - if (isLeaf(res.tree)) { + if (res.empty) { smaller } else { Node { @@ -448,18 +451,21 @@ ensures return parent; } -#if 0 /* Insert an element into a map. Overwrites previous if already present. */ void setNodeKey(struct MapNode **root, KEY key, VALUE value) -/* +/*@ requires take root_ptr = Owned(root); - take tree = BST(root_ptr); + take mem_tree = BSTMem(root_ptr); + let tree = treeValue(mem_tree); + validBST(tree); ensures take new_root = Owned(root); - take new_tree = BST(new_root); + take new_mem_tree = BSTMem(new_root); + let new_tree = treeValue(new_mem_tree); + validBST(new_tree); new_tree == insert(key, value, tree); -*/ +@*/ { struct MapNode *found = *root; struct MapNode *parent = findParent(&found, key); @@ -485,12 +491,12 @@ ensures void deleteTree(struct MapNode *root) -/* +/*@ requires - take tree = BST(root); + take tree = BSTMem(root); ensures true; -*/ +@*/ { if (!root) return; deleteTree(root->smaller); @@ -499,7 +505,7 @@ void deleteTree(struct MapNode *root) } -/* +/*@ predicate (void) DeleteSmallest(pointer cur, NodeData data) { if (is_null(cur)) { return; @@ -510,20 +516,23 @@ predicate (void) DeleteSmallest(pointer cur, NodeData data) { return; } } -*/ +@*/ struct MapNode* deleteSmallest(struct MapNode **root) -/* +/*@ requires take root_ptr = Owned(root); - take tree = BST(root_ptr); + take tree_mem = BSTMem(root_ptr); + let tree = treeValue(tree_mem); + validBST(tree); ensures - take new_root = Owned(root); - take new_tree = BST(new_root); - let res = delLeast(tree); + take new_root = Owned(root); + take new_tree_mem = BSTMem(new_root); + let new_tree = treeValue(new_tree_mem); + let res = delLeast(tree); new_tree == res.tree; take unsued = DeleteSmallest(return, res.data); -*/ +@*/ { struct MapNode *cur = *root; if (!cur) return 0; @@ -535,43 +544,49 @@ struct MapNode* deleteSmallest(struct MapNode **root) } if (parent) parent->smaller = cur->larger; - //!// else *root = cur->larger; - //!! forget_to_update_root // - //!// - return cur; } void deleteKey(struct MapNode **root, KEY key) -/* +/*@ requires take root_ptr = Owned(root); - take tree = BST(root_ptr); + take tree_mem = BSTMem(root_ptr); + let tree = treeValue(tree_mem); + validBST(tree); ensures take new_ptr = Owned(root); - take new_tree = BST(new_ptr); + take new_tree_mem = BSTMem(new_ptr); + let new_tree = treeValue(new_tree_mem); delKey(key, tree) == new_tree; -*/ +@*/ { struct MapNode *found = *root; struct MapNode *parent = findParent(&found, key); + assert(!parent || parent->smaller == found || parent->larger == found); - if (!found) return; + if (!found) { return; } struct MapNode *remove = deleteSmallest(&found->larger); + if (remove) { found->key = remove->key; found->value = remove->value; } else { remove = found; - //!// - if (parent) parent->smaller = found->smaller; - else - //!! always_update_root_instead_of_parent // - //!// + if (parent) { + if (parent->smaller == found) { + parent->smaller = found->smaller; + } else { + parent->larger = found->smaller; + } + } + else { *root = found->smaller; + } } cn_free_sized(remove, sizeof(struct MapNode)); } -#endif \ No newline at end of file + + From 76168d5d8eadee264a0658b39935a07d152cd9ef Mon Sep 17 00:00:00 2001 From: Iavor Diatchki Date: Tue, 12 Nov 2024 11:57:54 -0800 Subject: [PATCH 152/152] Fix deletion --- src/examples/testing/src/bst.c | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/examples/testing/src/bst.c b/src/examples/testing/src/bst.c index 3c98e6bb..488abe43 100644 --- a/src/examples/testing/src/bst.c +++ b/src/examples/testing/src/bst.c @@ -190,15 +190,16 @@ function [rec] (BST) setKey(KEY k, BST root, BST value) { } } -function [rec] ({ NodeData data, BST tree }) delLeast(BST root) { +function [rec] ({ boolean empty, NodeData data, BST tree }) delLeast(BST root) { match root { - Leaf {} => { { data: defaultNodeData(), tree: Leaf {} } } + Leaf {} => { { empty: true, data: defaultNodeData(), tree: Leaf {} } } Node { data: data, smaller: smaller, larger: larger } => { if (isLeaf(smaller)) { - { data: data, tree: larger } + { empty: false, data: data, tree: larger } } else { let res = delLeast(smaller); - { data: res.data, + { empty: false, + data: res.data, tree: Node { data: data, smaller: res.tree, larger: larger } } } @@ -212,7 +213,7 @@ function [rec] (BST) delKey(KEY key, BST root) { Node { data: data, smaller: smaller, larger: larger } => { if (key == data.key) { let res = delLeast(larger); - if (isLeaf(res.tree)) { + if (res.empty) { smaller } else { Node { data: res.data, smaller: smaller, larger: res.tree } @@ -512,10 +513,7 @@ struct MapNode* deleteSmallest(struct MapNode **root) } if (parent) parent->smaller = cur->larger; - //!// else *root = cur->larger; - //!! forget_to_update_root // - //!// return cur; } @@ -535,18 +533,21 @@ ensures struct MapNode *found = *root; struct MapNode *parent = findParent(&found, key); - if (!found) return; + if (!found) { return; } struct MapNode *remove = deleteSmallest(&found->larger); if (remove) { found->key = remove->key; found->value = remove->value; } else { remove = found; - //!// - if (parent) parent->smaller = found->smaller; + if (parent) { + if (found == parent->smaller) { + parent->smaller = found->smaller; + } else { + parent->larger = found->smaller; + } + } else - //!! always_update_root_instead_of_parent // - //!// *root = found->smaller; } cn_free_sized(remove, sizeof(struct MapNode));