copilot-verifier (empty) → 0.1
raw patch · 41 files changed
+4236/−0 lines, 41 filesdep +aesondep +basedep +bv-sized
Dependencies added: aeson, base, bv-sized, bytestring, case-insensitive, containers, copilot, copilot-c99, copilot-core, copilot-language, copilot-libraries, copilot-prettyprinter, copilot-theorem, copilot-verifier, crucible, crucible-llvm, crux, crux-llvm, filepath, lens, llvm-pretty, mtl, optparse-applicative, panic, parameterized-utils, prettyprinter, silently, tasty, tasty-expected-failure, tasty-hunit, text, transformers, vector, what4
Files
- CHANGELOG.md +5/−0
- LICENSE +30/−0
- README.md +327/−0
- copilot-verifier.cabal +146/−0
- examples/Copilot/Verifier/Examples.hs +91/−0
- examples/Copilot/Verifier/Examples/ShouldFail/Partial/AbsIntMin.hs +28/−0
- examples/Copilot/Verifier/Examples/ShouldFail/Partial/AddSignedWrap.hs +27/−0
- examples/Copilot/Verifier/Examples/ShouldFail/Partial/DivByZero.hs +27/−0
- examples/Copilot/Verifier/Examples/ShouldFail/Partial/IndexOutOfBounds.hs +31/−0
- examples/Copilot/Verifier/Examples/ShouldFail/Partial/ModByZero.hs +27/−0
- examples/Copilot/Verifier/Examples/ShouldFail/Partial/MulSignedWrap.hs +31/−0
- examples/Copilot/Verifier/Examples/ShouldFail/Partial/ShiftLTooLarge.hs +32/−0
- examples/Copilot/Verifier/Examples/ShouldFail/Partial/ShiftRTooLarge.hs +32/−0
- examples/Copilot/Verifier/Examples/ShouldFail/Partial/SubSignedWrap.hs +27/−0
- examples/Copilot/Verifier/Examples/ShouldPass/Arith.hs +52/−0
- examples/Copilot/Verifier/Examples/ShouldPass/Array.hs +40/−0
- examples/Copilot/Verifier/Examples/ShouldPass/ArrayGen.hs +21/−0
- examples/Copilot/Verifier/Examples/ShouldPass/ArrayOfStructs.hs +29/−0
- examples/Copilot/Verifier/Examples/ShouldPass/ArrayTriggerArgument.hs +23/−0
- examples/Copilot/Verifier/Examples/ShouldPass/Clock.hs +46/−0
- examples/Copilot/Verifier/Examples/ShouldPass/Counter.hs +56/−0
- examples/Copilot/Verifier/Examples/ShouldPass/Engine.hs +44/−0
- examples/Copilot/Verifier/Examples/ShouldPass/FPOps.hs +88/−0
- examples/Copilot/Verifier/Examples/ShouldPass/Heater.hs +60/−0
- examples/Copilot/Verifier/Examples/ShouldPass/IntOps.hs +64/−0
- examples/Copilot/Verifier/Examples/ShouldPass/Partial/AbsIntMin.hs +19/−0
- examples/Copilot/Verifier/Examples/ShouldPass/Partial/AddSignedWrap.hs +19/−0
- examples/Copilot/Verifier/Examples/ShouldPass/Partial/DivByZero.hs +19/−0
- examples/Copilot/Verifier/Examples/ShouldPass/Partial/IndexOutOfBounds.hs +19/−0
- examples/Copilot/Verifier/Examples/ShouldPass/Partial/ModByZero.hs +19/−0
- examples/Copilot/Verifier/Examples/ShouldPass/Partial/MulSignedWrap.hs +19/−0
- examples/Copilot/Verifier/Examples/ShouldPass/Partial/ShiftLTooLarge.hs +19/−0
- examples/Copilot/Verifier/Examples/ShouldPass/Partial/ShiftRTooLarge.hs +19/−0
- examples/Copilot/Verifier/Examples/ShouldPass/Partial/SubSignedWrap.hs +19/−0
- examples/Copilot/Verifier/Examples/ShouldPass/Structs.hs +92/−0
- examples/Copilot/Verifier/Examples/ShouldPass/Voting.hs +64/−0
- examples/Copilot/Verifier/Examples/ShouldPass/WCV.hs +194/−0
- exe/VerifyExamples.hs +53/−0
- src/Copilot/Verifier.hs +1517/−0
- src/Copilot/Verifier/Log.hs +729/−0
- test/Test.hs +32/−0
+ CHANGELOG.md view
@@ -0,0 +1,5 @@+# Release history for copilot-verifier++## 0.1 (February 2024)++* Initial release of `copilot-verifier.
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2021-2024 Galois Inc.+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions+are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above copyright+ notice, this list of conditions and the following disclaimer in+ the documentation and/or other materials provided with the+ distribution.++ * Neither the name of Galois, Inc. nor the names of its contributors+ may be used to endorse or promote products derived from this+ software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,327 @@+# Copilot Verifier++This "Copilot Verifier" is an add-on to the+[Copilot Stream DSL](https://copilot-language.github.io)+for verifying the correctness of C code generated+by the `copilot-c99` package.++The main idea of the verifier is to use the+[Crucible symbolic simulator](https://github.com/galoisinc/crucible)+to interpret the semantics of the generated C program and+and to produce verification conditions sufficient to guarantee+that the meaning of the generated program corresponds in a precise+way to the meaning of the original stream specification. The generated+verification conditions are then dispatched to SMT solvers to+be automatically solved. We will have more to say about exactly+what is meant by this correspondence below.++## Building++To build the verifier from source, first make sure you have met the following+prerequisites:++* Ensure that you have the `cabal` and `ghc` executables in your `PATH`. If you+ don't already have them, we recommend using `ghcup` to install them:+ https://www.haskell.org/ghcup/. We recommend `Cabal` 3.10 or newer, and one of+ GHC 8.10, 9.2, or 9.4.++* Ensure that you have the `clang` and `llvm-link` utilities from LLVM in your+ `PATH`. Currently, LLVM versions up to 16 are supported. LLVM binaries are+ available at https://github.com/llvm/llvm-project/releases.++* Ensure that you have the `z3` SMT solver in your `PATH`. `z3` binaries are+ available at https://github.com/Z3Prover/z3/releases.++Then, clone the repo and run:++```+$ git submodule update --init+$ cabal test copilot-verifier+```++This will clone the repository, build the verifier, and run the associated test+suite. If you have performed all of the steps above correctly, the test suite+should pass.++## Using the Copilot Verifier++The main interface to the verifier is the `Copilot.Verifier.verify`+function, which takes some options to drive the code generation+process and a Copilot `Spec` object. It will invoke the Copilot+code generator to obtain C sources, compile the C sources into+LLVM bitcode using the `clang` compiler front-end, then+parse and interpret the bitcode using `crucible`. After generating+verification conditions, it will dispatch them to an SMT solver,+and the result of those queries will be presented to the user.++There are a number of examples (based on similar examples+from the main Copilot repository) demonstrating how to+incorporate the verifier into a Copilot program.+See the `copilot-verifier/examples` subdirectory of this repository.++## Details of the Verification++### Synopsis of Copilot semantics++The Copilot DSL represents a certain nicely-behaved+class of discrete-time stream programs. Each `Stream`+in Copilot denotes an infinite stream of values; one may+just as well think that `Stream a` represents a pure mathematical+function `ℕ → a` from natural numbers to values of type `a`.+See the+[Copilot manual](https://ntrs.nasa.gov/api/citations/20200003164/downloads/20200003164.pdf)+for more details of the Copilot language itself and its semantics.++One of the central design considerations for Copilot is that is should+be possible to implement stream programs using a fixed, finite amount+of storage. As a result, Copilot will only accept stream programs+that access a bounded amount of their input streams (including any+recursive stream references). This allows an+implementation strategy where the generated C code can use fixed-size+ring buffers to store a limited number of previous stream values.++The execution model for the generated programs is that the program+starts in a state corresponding to stream values at time `t = 0`;+"external" stream input values are placed in distinguished global+variables by the calling environment, which then executes a `step()`+function to move to the next time step. The `step()` function captures+the values of these external inputs and does whatever computation is+necessary to update its internal state from time `t=n` to time+`t=n+1`. In addition, it will call any defined "trigger" functions+if the stream state at that time step satisfies the defined guard condition.+In short, the generated C program steps one moment at a time through+the stream program, consuming a sequence of input values provided by+a cooperating environment and calling handler functions whenever+states of interest occur.++### The Desired Correspondence++What does it mean, then, for a generated C program in this style+to correctly implement a given stream program? The intuition+is basically that after `n` calls to the `step` function,+the state of the ring-buffers of the C program should correctly+compute the value of the corresponding stream expression+evaluated at index `n`, assuming the C program has been fed+inputs corresponding to the first `n` values of the external stream+inputs. Moreover, the trigger functions should be called from+the `step` function exactly at the time values when the stream expressions+evaluate to true.++The notion of correspondence for the values flowing in streams is+relatively straightforward: these values consist of fixed-width+machine integers, floating-point values, structs and fixed-length+arrays. For each, the appropriate notion of equality is fairly clear.++Both the original `Stream` program and the generated C program+can be viewed straightforwardly as a transition system, and under+this view, the correspondence we want to establish is a bisimulation+between the states of the high-level stream program and the low-level+C program. The proof method for bisimulation requires us to provide+a "correspondence" relation between the program states, and then prove+three things about this relation:++1. that the initial states of the programs are in the relation;+2. if we assume two arbitrary program states begin in the relation+and each takes a single transition (consuming corresponding inputs),+the resulting states are back in the relation;+3. that any observable+actions taken by one program are exactly mirrored by the other.++On the high-level side of the bisimulation, the program+state is essentially just the value of the current time step `n`,+whereas on the C side it consists of the regions of global memory that+contain the ring-buffers and their current indices. The transition+relation for the high-level program just increments the time value by+one, and the transition for the C program is defined by the action+of the generated `step()` function.++Suppose `s` is one of the stream definitions in the original Copilot+program which is required to retain `k` previous values;+let `buf` be the global variable name of the ring-buffer in the C+program, and `idx` be the global variable name maintaining the+current index into the ring buffer. Then the correspondence+relation is basically that `0 <= idx < k` and+`s[n+i] = buf[(idx+i) mod k]` as `i` ranges from `0 .. k-1`.+By abuse of notation, here we mean that `s[j]` is+the value of the stream expression `s` evaluated at index `j`,+whereas `buf[j]` means the value obtained by reading the `j`th value+of the buffer `buf` from memory. The overall correspondence relation+is a conjunction of statements like this, one for each stream+expression that is realized via a buffer.++### Implementing the Bisimulation proof steps++The kind of program correspondence property we desire is a largely+mechanical affair. As the code under consideration is automatically+generated, it has a very regular structure and is specifically+intended to implement the semantics we wich to verify it against. As+such, we expect these proofs to be highly automatable.++The proof principle of bisimulation itself is not amenable+to reduction to SMT, as if falls outside the first-order theories+those solvers understand. Likewise, the semantics of Copilot+and C might possibly be reducible directly to SMT, but it would be+impractical to do so. However, we can reduce the individual+proof obligations mentioned above into a series of lower-level+logical statements that are amenable to SMT proof by+defining the logical semantics of stream programs, and using+symbolic simulation techniques to interpret the semantics of the+C program. Performing this reduction is the key contribution+of `copilot-verifier`.++#### Initial state correspondence++The proof first obligation we must discharge is to show that+the initial states of the two programs correspond. For each+stream `s` there is a corresponding `k`, which is the length of+the ring-buffer implementing it. We must simply verify that+the C program initializes its buffer with the first `k` values+of the stream `s`, and that the `idx` value starts at 0.+Because of the restrictions Copilot places on programs, these+first `k` values must be specified concretely and will not be+able to depend on any external inputs. As such, this step+is quite easily performed, as it requires only direct evaluation+of concrete inputs.++#### Transition correspondence++The bulk of the proof effort consists of demonstrating that+the bisimulation relation is preserved by transitions.+In order to do this step, we must begin with totally symbolic+initial states: we know nothing except that we are at some+arbitrary time value `n`, and that the C program buffers+correspond to their stream values as required by the relation.+Thus, we create fresh symbolic variables for the streams+from `n` to `n + k-1`, and for the values of all the involved+external streams at time `n`. Then, we run forward the Copilot+program by evaluating the stream recurrence expression to+compute the value of each stream at time `n+k`.++Next we set up an initial state of the C program by choosing,+for each ring buffer, an arbitrary value for its current index+within its allowed range, and then writing the variables+corresponding to each stream value into the buffers at+their appropriate offsets. The symbolic simulator is then+invoked to compute the state update effects of the `step()`+function. Afterward, we read the poststate values from the+ring-buffers and verify that they correspond to the stream+values from `n+1` up to `n+k`.++As part of symbolic simulation, Crucible may also generate+side-conditions that relate to memory safety of the program, or to+error conditions that must be avoided. All of the bisimulation+equivalence conditions and the safety side-conditions will be+submitted to an SMT solver.++#### Observable effects++For our purposes, the only observable effects of a Copilot program+relate to any "trigger" functions defined in the spec. Our task is to+show that the generated C code calls the external trigger functions if+and only if the corresponding guard condition is true, and that the+arguments passed to those functions are as expected.+This proof obligation is proved in the same phase along with+the transition relation proof above because the `step()` function+is responsible for both invoking triggers and for performing state+updates.++The technique we use to perform this aspect of the proof is to+install "override" functions to the external symbols corresponding+to the C trigger functions before we begin symbolic simulation.+In a real system, the external environment would be responsible+for implementing these functions and taking whatever appropriate+action is necessary when the triggers fire. However, we are verifying+the generated code in isolation from its environment, so we have no+implementation in hand. Instead, the override will+essentially implement a stub function that simply captures its+arguments and the path condition under which it was called.+After simulation completes, the captured arguments and path condition+are required to be equivalent to the corresponding trigger guard+and arguments from the Copilot spec. These conditions are+discharged to the SMT solver at the same time as the transition+relation obligations.++Because of the way we model the trigger functions, we make a number of+implicit assumptions about how the actual implementations of those+functions must behave. The most important of those assumptions is that+the trigger functions must not modify any memory under the control of+the Copilot program, including its ring buffers and stack. We also+assume that the trigger functions are well defined, i.e. they are+memory safe and do not perform any undefined behavior.++#### Partial operations++A generated C program may make use of partial operations. These range from+division, which can fail if the second argument is zero, to signed integer+arithmetic, which can overflow and result in undefined behavior. The verifier+has two modes for dealing with partial operations:++1. Any invocation of a partial operation on undefined inputs in the generated+ C program will result in an error, provided that the user did not add an+ assertion that assumes this behavior will not occur.++2. If the generated C program invokes a partial operation on undefined inputs,+ the verifier will check if this coincides with a corresponding invocation+ of a partial operation in the Copilot spec. If so, the verification will+ succeed. In other words, the verifier will check that the spec and the+ C program are "crash-equivalent".++The verifier uses mode (1) by default, but mode (2) can be enabled by using+`Copilot.Verifier.verifyWithOptions sideCondVerifierOptions`. In this mode,+the verifier will analyze any invocation of a operation which could be partial+and generate a side condition that this operation will only be invoked on+well defined inputs. During the transition step of the bisimulation proof,+the verifier will add these side conditions as assumptions. Therefore, if+simulating the C program generates any side conditions due to invoking partial+operations, these side conditions from the C program should be dischargeable+using the corresponding side conditions from the Copilot spec.++Mode (2) has the caveat that `clang` may compile C code to LLVM bitcode in+which a partial function is no longer applied to undefined inputs. For+instance, `clang` will sometimes promote 16-bit integer values to 32 bits+before performing arithmetic on them. This can turn an operation that would+result in signed 16-bit integer overflow into a 32-bit integer operation+that does _not_ overflow, for instance.++### Caveats About the Verifier++We rely on the `clang` compiler front-end to consume C source files+and produce LLVM intermediate language, which then becomes the input+to the later verification steps. To the extent that the input program+is not fully-portable C, `clang` may make implementation-specific+decisions about how to compile the program which might be made+different if compiled by a different compiler (e.g. `gcc`). We expect+this aspect to be mitigated by the fact that Copilot programs are+automatically generated into a rather simple subset of the C language,+and is designed to be as simple as possible.+Any code-generation bugs in `clang` itself may affect the soundness+of our verifier. Again, however, Copilot generates a well-understood+subset of the language, and we expect `clang` to be well-tested on+the code patterns produced.++The semantics of LLVM bitcode, as encoded in the `crucible-llvm`+package, may have errors that affect soundness. We mitigate this risk+by testing our semantics against a corpus of verification problems+produced for the SV-COMP verification competition, paying special+attention to any soundness issues that arise. `Crux`, a standalone+verification system based on `crucible-llvm`, was a participant in the+2022 edition of SV-COMP.++The semantics of Copilot programs, as encoded in the+`Copilot.Theorem.What4` module may have errors that affect soundness.+For the moment we do not have an effective mitigation strategy for+this risk other than manual examination and comparison against the+intended semantics of Copilot, as encoded in the interpreter.++There is limited SMT solver support for floating-point values,+especially for transcendental functions like the trig primitives. As a+result, we reason about floating point expressions via uninterpreted+functions. In other words, we leave the semantics of the+floating-point operations totally abstract, and simply verify that the+Copilot program and the corresponding C program apply the same+operations in the same order. This is sound, but leaves the possibility+that the compiler will apply some correct transformation to+floating-point expressions that we are nonetheless unable to verify.+However, on low optimizations and without the `--fast-math` flag,+compilers generally (and `clang` in particular) are very reluctant to+rearrange floating-point code, and the verifications generally succeed.
+ copilot-verifier.cabal view
@@ -0,0 +1,146 @@+Cabal-version: 2.2+Name: copilot-verifier+Version: 0.1+Author: Galois Inc.+Maintainer: rscott@galois.com+Copyright: (c) Galois, Inc 2021-2024+License: BSD-3-Clause+License-file: LICENSE+Build-type: Simple+Category: Language+Synopsis: System for verifying the correctness of generated Copilot programs+Description:+ @copilot-verifier@ is an add-on to the <https://copilot-language.github.io+ Copilot Stream DSL> for verifying the correctness of C code generated by the+ @copilot-c99@ package.+ .+ @copilot-verifier@ uses the <https://github.com/galoisinc/crucible Crucible+ symbolic simulator> to interpret the semantics of the generated C program and+ and to produce verification conditions sufficient to guarantee that the+ meaning of the generated program corresponds in a precise way to the meaning+ of the original stream specification. The generated verification conditions+ are then dispatched to SMT solvers to be automatically solved.+extra-doc-files: CHANGELOG.md, README.md++source-repository head+ type: git+ location: https://github.com/GaloisInc/copilot-verifier+ subdir: copilot-verifier++common bldflags+ ghc-options: -Wall+ -Werror=incomplete-patterns+ -Werror=missing-methods+ -Werror=overlapping-patterns+ -Wpartial-fields+ -Wincomplete-uni-patterns+ ghc-prof-options: -O2 -fprof-auto-exported+ default-language: Haskell2010+ default-extensions:+ NondecreasingIndentation+ build-depends:+ aeson >= 1.5 && < 2.3,+ base >= 4.8 && < 4.18,+ bv-sized >= 1.0.0 && < 1.1,+ bytestring,+ containers >= 0.5.9.0,+ copilot >= 3.18.1 && < 3.19,+ copilot-c99 >= 3.18.1 && < 3.19,+ copilot-core >= 3.18.1 && < 3.19,+ copilot-libraries >= 3.18.1 && < 3.19,+ copilot-language >= 3.18.1 && < 3.19,+ copilot-prettyprinter >= 3.18.1 && < 3.19,+ copilot-theorem >= 3.18.1 && < 3.19,+ crucible >= 0.7 && < 0.8,+ crucible-llvm >= 0.6 && < 0.7,+ crux >= 0.7 && < 0.8,+ crux-llvm >= 0.8 && < 0.9,+ filepath,+ lens,+ llvm-pretty,+ mtl,+ panic >= 0.3,+ parameterized-utils >= 2.1.4 && < 2.2,+ prettyprinter >= 1.7.0,+ text,+ transformers,+ vector,+ what4 >= 0.4++library+ import: bldflags++ hs-source-dirs: src+ exposed-modules:+ Copilot.Verifier+ Copilot.Verifier.Log++library copilot-verifier-examples+ import: bldflags++ hs-source-dirs: examples+ build-depends:+ case-insensitive,+ copilot-verifier+ exposed-modules:+ Copilot.Verifier.Examples++ Copilot.Verifier.Examples.ShouldFail.Partial.AbsIntMin+ Copilot.Verifier.Examples.ShouldFail.Partial.AddSignedWrap+ Copilot.Verifier.Examples.ShouldFail.Partial.DivByZero+ Copilot.Verifier.Examples.ShouldFail.Partial.IndexOutOfBounds+ Copilot.Verifier.Examples.ShouldFail.Partial.ModByZero+ Copilot.Verifier.Examples.ShouldFail.Partial.MulSignedWrap+ Copilot.Verifier.Examples.ShouldFail.Partial.ShiftLTooLarge+ Copilot.Verifier.Examples.ShouldFail.Partial.ShiftRTooLarge+ Copilot.Verifier.Examples.ShouldFail.Partial.SubSignedWrap++ Copilot.Verifier.Examples.ShouldPass.Array+ Copilot.Verifier.Examples.ShouldPass.ArrayGen+ Copilot.Verifier.Examples.ShouldPass.ArrayOfStructs+ Copilot.Verifier.Examples.ShouldPass.ArrayTriggerArgument+ Copilot.Verifier.Examples.ShouldPass.Arith+ Copilot.Verifier.Examples.ShouldPass.Clock+ Copilot.Verifier.Examples.ShouldPass.Counter+ Copilot.Verifier.Examples.ShouldPass.Engine+ Copilot.Verifier.Examples.ShouldPass.FPOps+ Copilot.Verifier.Examples.ShouldPass.Heater+ Copilot.Verifier.Examples.ShouldPass.IntOps+ Copilot.Verifier.Examples.ShouldPass.Partial.AbsIntMin+ Copilot.Verifier.Examples.ShouldPass.Partial.AddSignedWrap+ Copilot.Verifier.Examples.ShouldPass.Partial.DivByZero+ Copilot.Verifier.Examples.ShouldPass.Partial.IndexOutOfBounds+ Copilot.Verifier.Examples.ShouldPass.Partial.ModByZero+ Copilot.Verifier.Examples.ShouldPass.Partial.MulSignedWrap+ Copilot.Verifier.Examples.ShouldPass.Partial.ShiftLTooLarge+ Copilot.Verifier.Examples.ShouldPass.Partial.ShiftRTooLarge+ Copilot.Verifier.Examples.ShouldPass.Partial.SubSignedWrap+ Copilot.Verifier.Examples.ShouldPass.Structs+ Copilot.Verifier.Examples.ShouldPass.Voting+ Copilot.Verifier.Examples.ShouldPass.WCV++executable verify-examples+ import: bldflags++ hs-source-dirs: exe+ main-is: VerifyExamples.hs+ build-depends:+ case-insensitive,+ copilot-verifier,+ copilot-verifier-examples,+ optparse-applicative++test-suite copilot-verifier-test+ import: bldflags++ type: exitcode-stdio-1.0+ hs-source-dirs: test+ build-depends:+ case-insensitive,+ copilot-verifier,+ copilot-verifier-examples,+ silently >= 1.2,+ tasty >= 0.10,+ tasty-expected-failure >= 0.12,+ tasty-hunit >= 0.10+ main-is: Test.hs
+ examples/Copilot/Verifier/Examples.hs view
@@ -0,0 +1,91 @@+{-# LANGUAGE OverloadedStrings #-}+module Copilot.Verifier.Examples+ ( shouldFailExamples+ , shouldPassExamples+ ) where++import qualified Data.CaseInsensitive as CI+import Data.CaseInsensitive (CI)+import qualified Data.Map as Map+import Data.Map (Map)+import Data.Text (Text)++import Copilot.Verifier (Verbosity)+import qualified Copilot.Verifier.Examples.ShouldFail.Partial.AbsIntMin as Fail.AbsIntMin+import qualified Copilot.Verifier.Examples.ShouldFail.Partial.AddSignedWrap as Fail.AddSignedWrap+import qualified Copilot.Verifier.Examples.ShouldFail.Partial.DivByZero as Fail.DivByZero+import qualified Copilot.Verifier.Examples.ShouldFail.Partial.IndexOutOfBounds as Fail.IndexOutOfBounds+import qualified Copilot.Verifier.Examples.ShouldFail.Partial.ModByZero as Fail.ModByZero+import qualified Copilot.Verifier.Examples.ShouldFail.Partial.MulSignedWrap as Fail.MulSignedWrap+import qualified Copilot.Verifier.Examples.ShouldFail.Partial.ShiftLTooLarge as Fail.ShiftLTooLarge+import qualified Copilot.Verifier.Examples.ShouldFail.Partial.ShiftRTooLarge as Fail.ShiftRTooLarge+import qualified Copilot.Verifier.Examples.ShouldFail.Partial.SubSignedWrap as Fail.SubSignedWrap+import qualified Copilot.Verifier.Examples.ShouldPass.Array as Array+import qualified Copilot.Verifier.Examples.ShouldPass.ArrayGen as ArrayGen+import qualified Copilot.Verifier.Examples.ShouldPass.ArrayOfStructs as ArrayOfStructs+import qualified Copilot.Verifier.Examples.ShouldPass.ArrayTriggerArgument as ArrayTriggerArgument+import qualified Copilot.Verifier.Examples.ShouldPass.Arith as Arith+import qualified Copilot.Verifier.Examples.ShouldPass.Clock as Clock+import qualified Copilot.Verifier.Examples.ShouldPass.Counter as Counter+import qualified Copilot.Verifier.Examples.ShouldPass.Engine as Engine+import qualified Copilot.Verifier.Examples.ShouldPass.FPOps as FPOps+import qualified Copilot.Verifier.Examples.ShouldPass.Heater as Heater+import qualified Copilot.Verifier.Examples.ShouldPass.IntOps as IntOps+import qualified Copilot.Verifier.Examples.ShouldPass.Partial.AbsIntMin as Pass.AbsIntMin+import qualified Copilot.Verifier.Examples.ShouldPass.Partial.AddSignedWrap as Pass.AddSignedWrap+import qualified Copilot.Verifier.Examples.ShouldPass.Partial.IndexOutOfBounds as Pass.IndexOutOfBounds+import qualified Copilot.Verifier.Examples.ShouldPass.Partial.DivByZero as Pass.DivByZero+import qualified Copilot.Verifier.Examples.ShouldPass.Partial.ModByZero as Pass.ModByZero+import qualified Copilot.Verifier.Examples.ShouldPass.Partial.MulSignedWrap as Pass.MulSignedWrap+import qualified Copilot.Verifier.Examples.ShouldPass.Partial.ShiftLTooLarge as Pass.ShiftLTooLarge+import qualified Copilot.Verifier.Examples.ShouldPass.Partial.ShiftRTooLarge as Pass.ShiftRTooLarge+import qualified Copilot.Verifier.Examples.ShouldPass.Partial.SubSignedWrap as Pass.SubSignedWrap+import qualified Copilot.Verifier.Examples.ShouldPass.Structs as Structs+import qualified Copilot.Verifier.Examples.ShouldPass.Voting as Voting+import qualified Copilot.Verifier.Examples.ShouldPass.WCV as WCV++shouldFailExamples :: Verbosity -> Map (CI Text) (IO ())+shouldFailExamples verb = Map.fromList+ [ -- Partial operation tests+ example "AbsIntMin-fail" (Fail.AbsIntMin.verifySpec verb)+ , example "AddSignedWrap-fail" (Fail.AddSignedWrap.verifySpec verb)+ , example "DivByZero-fail" (Fail.DivByZero.verifySpec verb)+ , example "IndexOutOfBounds-fail" (Fail.IndexOutOfBounds.verifySpec verb)+ , example "ModByZero-fail" (Fail.ModByZero.verifySpec verb)+ , example "MulSignedWrap-fail" (Fail.MulSignedWrap.verifySpec verb)+ , example "ShiftLTooLarge-fail" (Fail.ShiftLTooLarge.verifySpec verb)+ , example "ShiftRTooLarge-fail" (Fail.ShiftRTooLarge.verifySpec verb)+ , example "SubSignedWrap-fail" (Fail.SubSignedWrap.verifySpec verb)+ ]++shouldPassExamples :: Verbosity -> Map (CI Text) (IO ())+shouldPassExamples verb = Map.fromList+ [ example "Array" (Array.verifySpec verb)+ , example "ArrayGen" (ArrayGen.verifySpec verb)+ , example "ArrayOfStructs" (ArrayOfStructs.verifySpec verb)+ , example "ArrayTriggerArgument" (ArrayTriggerArgument.verifySpec verb)+ , example "Arith" (Arith.verifySpec verb)+ , example "Clock" (Clock.verifySpec verb)+ , example "Counter" (Counter.verifySpec verb)+ , example "Engine" (Engine.verifySpec verb)+ , example "FPOps" (FPOps.verifySpec verb)+ , example "Heater" (Heater.verifySpec verb)+ , example "IntOps" (IntOps.verifySpec verb)+ , example "Structs" (Structs.verifySpec verb)+ , example "Voting" (Voting.verifySpec verb)+ , example "WCV" (WCV.verifySpec verb)++ -- Partial operation tests+ , example "AbsIntMin-pass" (Pass.AbsIntMin.verifySpec verb)+ , example "AddSignedWrap-pass" (Pass.AddSignedWrap.verifySpec verb)+ , example "DivByZero-pass" (Pass.DivByZero.verifySpec verb)+ , example "IndexOutOfBounds-pass" (Pass.IndexOutOfBounds.verifySpec verb)+ , example "ModByZero-pass" (Pass.ModByZero.verifySpec verb)+ , example "MulSignedWrap-pass" (Pass.MulSignedWrap.verifySpec verb)+ , example "ShiftLTooLarge-pass" (Pass.ShiftLTooLarge.verifySpec verb)+ , example "ShiftRTooLarge-pass" (Pass.ShiftRTooLarge.verifySpec verb)+ , example "SubSignedWrap-pass" (Pass.SubSignedWrap.verifySpec verb)+ ]++example :: Text -> IO () -> (CI Text, IO ())+example name action = (CI.mk name, action)
+ examples/Copilot/Verifier/Examples/ShouldFail/Partial/AbsIntMin.hs view
@@ -0,0 +1,28 @@+{-# LANGUAGE NoImplicitPrelude #-}++-- | This will fail to verify since the verification does not assume the+-- @notIntMin@ property, which is needed to prevent undefined behavior when+-- invoking the 'abs' function.+module Copilot.Verifier.Examples.ShouldFail.Partial.AbsIntMin where++import Language.Copilot+import Copilot.Compile.C99+import Copilot.Verifier ( Verbosity, VerifierOptions(..)+ , defaultVerifierOptions, verifyWithOptions )++spec :: Spec+spec = do+ let stream :: Stream Int32+ stream = extern "stream" Nothing++ _ <- prop "notIntMin" (forAll (stream > constI32 minBound))+ trigger "streamAbs" (abs stream == 1) [arg stream]++verifySpec :: Verbosity -> IO ()+verifySpec verb = do+ spec' <- reify spec+ verifyWithOptions defaultVerifierOptions{verbosity = verb}+ mkDefaultCSettings+ -- ["notIntMin"]+ []+ "absIntMinFail" spec'
+ examples/Copilot/Verifier/Examples/ShouldFail/Partial/AddSignedWrap.hs view
@@ -0,0 +1,27 @@+{-# LANGUAGE NoImplicitPrelude #-}++-- | This will fail to verify since the verification does not assume the+-- @notIntMax@ property, which is needed to prevent signed integer overflow.+module Copilot.Verifier.Examples.ShouldFail.Partial.AddSignedWrap where++import Language.Copilot+import Copilot.Compile.C99+import Copilot.Verifier ( Verbosity, VerifierOptions(..)+ , defaultVerifierOptions, verifyWithOptions )++spec :: Spec+spec = do+ let stream :: Stream Int32+ stream = extern "stream" Nothing++ _ <- prop "notIntMax" (forAll (stream < constI32 maxBound))+ trigger "streamAddSigned" ((stream + 1) == 1) [arg stream]++verifySpec :: Verbosity -> IO ()+verifySpec verb = do+ spec' <- reify spec+ verifyWithOptions defaultVerifierOptions{verbosity = verb}+ mkDefaultCSettings+ -- ["notIntMax"]+ []+ "addSignedWrapFail" spec'
+ examples/Copilot/Verifier/Examples/ShouldFail/Partial/DivByZero.hs view
@@ -0,0 +1,27 @@+{-# LANGUAGE NoImplicitPrelude #-}++-- | This will fail to verify since the verification does not assume the+-- @nonzero@ property, which is needed to prevent a division-by-zero error.+module Copilot.Verifier.Examples.ShouldFail.Partial.DivByZero where++import Language.Copilot+import Copilot.Compile.C99+import Copilot.Verifier ( Verbosity, VerifierOptions(..)+ , defaultVerifierOptions, verifyWithOptions )++spec :: Spec+spec = do+ let stream :: Stream Int16+ stream = extern "stream" Nothing++ _ <- prop "nonzero" (forAll (stream /= 0))+ trigger "streamDiv" ((stream `div` stream) == 1) [arg stream]++verifySpec :: Verbosity -> IO ()+verifySpec verb = do+ spec' <- reify spec+ verifyWithOptions defaultVerifierOptions{verbosity = verb}+ mkDefaultCSettings+ -- ["nonzero"]+ []+ "divByZeroFail" spec'
+ examples/Copilot/Verifier/Examples/ShouldFail/Partial/IndexOutOfBounds.hs view
@@ -0,0 +1,31 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE NoImplicitPrelude #-}++-- | This will fail to verify since the verification does not assume the+-- @withinBounds@ property, which is needed to prevent an out-of-bounds array index.+module Copilot.Verifier.Examples.ShouldFail.Partial.IndexOutOfBounds where++import Language.Copilot+import Copilot.Compile.C99+import Copilot.Verifier ( Verbosity, VerifierOptions(..)+ , defaultVerifierOptions, verifyWithOptions )++spec :: Spec+spec = do+ let stream1 :: Stream (Array 2 Int16)+ stream1 = constant (array [27, 42])++ stream2 :: Stream Word32+ stream2 = extern "stream2" Nothing++ _ <- prop "withinBounds" (forAll (stream2 < constW32 2))+ trigger "streamIndex" ((stream1 .!! stream2) == 1) [arg stream1, arg stream2]++verifySpec :: Verbosity -> IO ()+verifySpec verb = do+ spec' <- reify spec+ verifyWithOptions defaultVerifierOptions{verbosity = verb}+ mkDefaultCSettings+ -- ["withinBounds"]+ []+ "indexFail" spec'
+ examples/Copilot/Verifier/Examples/ShouldFail/Partial/ModByZero.hs view
@@ -0,0 +1,27 @@+{-# LANGUAGE NoImplicitPrelude #-}++-- | This will fail to verify since the verification does not assume the+-- @nonzero@ property, which is needed to prevent a division-by-zero error.+module Copilot.Verifier.Examples.ShouldFail.Partial.ModByZero where++import Language.Copilot+import Copilot.Compile.C99+import Copilot.Verifier ( Verbosity, VerifierOptions(..)+ , defaultVerifierOptions, verifyWithOptions )++spec :: Spec+spec = do+ let stream :: Stream Int16+ stream = extern "stream" Nothing++ _ <- prop "nonzero" (forAll (stream /= 0))+ trigger "streamMod" ((stream `mod` stream) == 1) [arg stream]++verifySpec :: Verbosity -> IO ()+verifySpec verb = do+ spec' <- reify spec+ verifyWithOptions defaultVerifierOptions{verbosity = verb}+ mkDefaultCSettings+ -- ["nonzero"]+ []+ "modByZeroFail" spec'
+ examples/Copilot/Verifier/Examples/ShouldFail/Partial/MulSignedWrap.hs view
@@ -0,0 +1,31 @@+{-# LANGUAGE NoImplicitPrelude #-}++-- | This will fail to verify since the verification does not assume the+-- @withinRange@ property, which is needed to prevent signed integer underflow or overflow.+module Copilot.Verifier.Examples.ShouldFail.Partial.MulSignedWrap where++import qualified Prelude as P++import Language.Copilot+import Copilot.Compile.C99+import Copilot.Verifier ( Verbosity, VerifierOptions(..)+ , defaultVerifierOptions, verifyWithOptions )++spec :: Spec+spec = do+ let stream :: Stream Int32+ stream = extern "stream" Nothing++ _ <- prop "withinRange" (forAll+ (constI32 ((minBound P.+ 1) P.* 2) < stream+ && stream < constI32 ((maxBound P.- 1) `P.div` 2)))+ trigger "streamMulSigned" ((stream * 2) == 2) [arg stream]++verifySpec :: Verbosity -> IO ()+verifySpec verb = do+ spec' <- reify spec+ verifyWithOptions defaultVerifierOptions{verbosity = verb}+ mkDefaultCSettings+ -- ["withinRange"]+ []+ "mulSignedWrapFail" spec'
+ examples/Copilot/Verifier/Examples/ShouldFail/Partial/ShiftLTooLarge.hs view
@@ -0,0 +1,32 @@+{-# LANGUAGE NoImplicitPrelude #-}++-- | This will fail to verify since the verification does not assume the+-- @lessThanBitWidth@ property, which is needed to prevent shifting by too+-- large of a value.+module Copilot.Verifier.Examples.ShouldFail.Partial.ShiftLTooLarge where++import Language.Copilot+import Copilot.Compile.C99+import Copilot.Verifier ( Verbosity, VerifierOptions(..)+ , defaultVerifierOptions, verifyWithOptions )++spec :: Spec+spec = do+ let stream1 :: Stream Int32+ stream1 = extern "stream1" Nothing++ stream2 :: Stream Int64+ stream2 = extern "stream2" Nothing++ _ <- prop "lessThanBitWidth" (forAll+ (constI64 0 <= stream2 && stream2 < constI64 32))+ trigger "streamShiftL" ((stream1 .<<. stream2) == 1) [arg stream1, arg stream2]++verifySpec :: Verbosity -> IO ()+verifySpec verb = do+ spec' <- reify spec+ verifyWithOptions defaultVerifierOptions{verbosity = verb}+ mkDefaultCSettings+ -- ["lessThanBitWidth"]+ []+ "shiftLTooLargeFail" spec'
+ examples/Copilot/Verifier/Examples/ShouldFail/Partial/ShiftRTooLarge.hs view
@@ -0,0 +1,32 @@+{-# LANGUAGE NoImplicitPrelude #-}++-- | This will fail to verify since the verification does not assume the+-- @lessThanBitWidth@ property, which is needed to prevent shifting by too+-- large of a value.+module Copilot.Verifier.Examples.ShouldFail.Partial.ShiftRTooLarge where++import Language.Copilot+import Copilot.Compile.C99+import Copilot.Verifier ( Verbosity, VerifierOptions(..)+ , defaultVerifierOptions, verifyWithOptions )++spec :: Spec+spec = do+ let stream1 :: Stream Int32+ stream1 = extern "stream1" Nothing++ stream2 :: Stream Int64+ stream2 = extern "stream2" Nothing++ _ <- prop "lessThanBitWidth" (forAll+ (constI64 0 <= stream2 && stream2 < constI64 32))+ trigger "streamShiftR" ((stream1 .<<. stream2) == 1) [arg stream1, arg stream2]++verifySpec :: Verbosity -> IO ()+verifySpec verb = do+ spec' <- reify spec+ verifyWithOptions defaultVerifierOptions{verbosity = verb}+ mkDefaultCSettings+ -- ["lessThanBitWidth"]+ []+ "shiftRTooLargeFail" spec'
+ examples/Copilot/Verifier/Examples/ShouldFail/Partial/SubSignedWrap.hs view
@@ -0,0 +1,27 @@+{-# LANGUAGE NoImplicitPrelude #-}++-- | This will fail to verify since the verification does not assume the+-- @notIntMin@ property, which is needed to prevent signed integer underflow.+module Copilot.Verifier.Examples.ShouldFail.Partial.SubSignedWrap where++import Language.Copilot+import Copilot.Compile.C99+import Copilot.Verifier ( Verbosity, VerifierOptions(..)+ , defaultVerifierOptions, verifyWithOptions )++spec :: Spec+spec = do+ let stream :: Stream Int32+ stream = extern "stream" Nothing++ _ <- prop "notIntMin" (forAll (stream > constI32 minBound))+ trigger "streamSubSigned" ((stream - 1) == 1) [arg stream]++verifySpec :: Verbosity -> IO ()+verifySpec verb = do+ spec' <- reify spec+ verifyWithOptions defaultVerifierOptions{verbosity = verb}+ mkDefaultCSettings+ -- ["notIntMin"]+ []+ "subSignedWrapFail" spec'
+ examples/Copilot/Verifier/Examples/ShouldPass/Arith.hs view
@@ -0,0 +1,52 @@++{-# LANGUAGE RebindableSyntax #-}++module Copilot.Verifier.Examples.ShouldPass.Arith where++import Control.Monad (when)+import qualified Prelude as P++import Language.Copilot+import Copilot.Compile.C99+import Copilot.Verifier ( Verbosity(..), VerifierOptions(..)+ , defaultVerifierOptions, verifyWithOptions )+import Copilot.Theorem.What4 (prove, Solver(..))++-- The largest unsigned 32-bit prime+lastPrime :: Stream Word32+lastPrime = 31+--lastPrime = 4294967291 -- Whelp, this prime seems too big for the solvers to handle well++multRingSpec :: Spec+multRingSpec = do+ _ <- prop "clamp nonzero" (forAll ((clamp > 0) && (clamp < lastPrime)))+ _ <- prop "reduced" (forAll (acc < lastPrime))+ _ <- prop "nonzero" (forAll (acc > 0 && (acc < lastPrime)))++ trigger "outofrange" (not (acc > 0 && acc < lastPrime)) [arg acc]++ return ()++ where+ -- a stream of external values+ vals = externW32 "values" Nothing++ -- Generate a value in [1, lastPrime), which+ -- is the multiplictive group of Z_p+ clamp :: Stream Word32+ clamp = (vals `mod` (lastPrime - 1)) + 1++ -- Successively multiply new values+ acc :: Stream Word32+ acc = [1] ++ unsafeCast ((cast acc * cast clamp) `mod` (cast lastPrime :: Stream Word64))++verifySpec :: Verbosity -> IO ()+verifySpec verb =+ do s <- reify multRingSpec+ r <- prove Z3 s+ when (verb P.>= Default) $+ print r+ verifyWithOptions defaultVerifierOptions{verbosity = verb}+ mkDefaultCSettings ["reduced"] "multRingSpec" s++--verifySpec _ = interpret 10 engineMonitor
+ examples/Copilot/Verifier/Examples/ShouldPass/Array.hs view
@@ -0,0 +1,40 @@+--------------------------------------------------------------------------------+-- Copyright © 2019 National Institute of Aerospace / Galois, Inc.+--------------------------------------------------------------------------------++-- | This is a simple example for arrays. As a program, it does not make much+-- sense, however it shows of the features of arrays nicely.++-- | Enable compiler extension for type-level data, necesary for the array+-- length.++{-# LANGUAGE RebindableSyntax #-}+{-# LANGUAGE DataKinds #-}++module Copilot.Verifier.Examples.ShouldPass.Array where++import Language.Copilot+import Copilot.Compile.C99+import Copilot.Verifier ( Verbosity, VerifierOptions(..)+ , defaultVerifierOptions, verifyWithOptions )++-- Lets define an array of length 2.+-- Make the buffer of the streams 3 elements long.+arr :: Stream (Array 2 Bool)+arr = [ array [True, False]+ , array [True, True]+ , array [False, False]] ++ arr++spec :: Spec+spec = do+ -- A trigger that fires 'func' when the first element of 'arr' is True.+ -- It passes the current value of arr as an argument.+ -- The prototype of 'func' would be:+ -- void func (int8_t arg[3]);+ trigger "func" (arr .!! 0) [arg arr]++-- Compile the spec+verifySpec :: Verbosity -> IO ()+verifySpec verb = reify spec >>= verifyWithOptions defaultVerifierOptions{verbosity = verb}+ mkDefaultCSettings [] "array"+-- verifySpec _ = interpret 30 spec
+ examples/Copilot/Verifier/Examples/ShouldPass/ArrayGen.hs view
@@ -0,0 +1,21 @@+{-# LANGUAGE DataKinds #-}+module Copilot.Verifier.Examples.ShouldPass.ArrayGen where++import Copilot.Compile.C99+import Copilot.Verifier ( Verbosity, VerifierOptions(..)+ , defaultVerifierOptions, verifyWithOptions )+import Language.Copilot+import qualified Prelude hiding ((++), (>))++spec :: Spec+spec = trigger "f" (stream .!! 0 > 0) [arg stream]+ where+ stream :: Stream (Array 2 Int16)+ stream = [array [3,4]] ++ rest++ rest :: Stream (Array 2 Int16)+ rest = constant $ array [5,6]++verifySpec :: Verbosity -> IO ()+verifySpec verb = reify spec >>= verifyWithOptions defaultVerifierOptions{verbosity = verb}+ mkDefaultCSettings [] "arrayGen"
+ examples/Copilot/Verifier/Examples/ShouldPass/ArrayOfStructs.hs view
@@ -0,0 +1,29 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE NoImplicitPrelude #-}+module Copilot.Verifier.Examples.ShouldPass.ArrayOfStructs where++import Copilot.Compile.C99+import Copilot.Verifier ( Verbosity, VerifierOptions(..)+ , defaultVerifierOptions, verifyWithOptions )+import Language.Copilot++data S = S { field :: Field "field" Int16 }++instance Struct S where+ typename _ = "s"+ toValues s = [Value Int16 (field s)]++instance Typed S where+ typeOf = Struct (S (Field 0))++spec :: Spec+spec = trigger "f" ((stream .!! 0)#field == 27) [arg stream]+ where+ stream :: Stream (Array 2 S)+ stream = [array [S (Field 27), S (Field 42)]] ++ stream++verifySpec :: Verbosity -> IO ()+verifySpec verb = do+ spec' <- reify spec+ verifyWithOptions defaultVerifierOptions{verbosity = verb}+ mkDefaultCSettings [] "arrayOfStructs" spec'
+ examples/Copilot/Verifier/Examples/ShouldPass/ArrayTriggerArgument.hs view
@@ -0,0 +1,23 @@+{-# LANGUAGE DataKinds #-}++-- | A regression test for+-- <https://github.com/Copilot-Language/copilot/issues/431>.+module Copilot.Verifier.Examples.ShouldPass.ArrayTriggerArgument where++import Language.Copilot+import Copilot.Compile.C99+import Copilot.Verifier ( Verbosity, VerifierOptions(..)+ , defaultVerifierOptions, verifyWithOptions )++spec :: Spec+spec = trigger "f" true [arg stream]+ where+ stream :: Stream (Array 2 Int16)+ stream = constant (array [3,4])++verifySpec :: Verbosity -> IO ()+verifySpec verb = do+ spec' <- reify spec+ verifyWithOptions+ defaultVerifierOptions{verbosity = verb}+ mkDefaultCSettings [] "arrayTriggerArgument" spec'
+ examples/Copilot/Verifier/Examples/ShouldPass/Clock.hs view
@@ -0,0 +1,46 @@+--------------------------------------------------------------------------------+-- Copyright © 2019 National Institute of Aerospace / Galois, Inc.+--------------------------------------------------------------------------------++-- | Example showing usage of clocks to generate periodically recurring truth+-- values.++module Copilot.Verifier.Examples.ShouldPass.Clock where++import Control.Monad (when)+import qualified Prelude as P++import Language.Copilot+import Copilot.Compile.C99+import Copilot.Verifier ( Verbosity(..), VerifierOptions(..)+ , defaultVerifierOptions, verifyWithOptions )+import Copilot.Theorem.What4 (prove, Solver(..))++-- | We need to force a type for the argument of `period`.+p :: Word8+p = 5++-- | Both have the same period, but a different phase.+clkStream :: Stream Bool+clkStream = clk (period p) (phase 0)++clkStream' :: Stream Bool+clkStream' = clk (period p) (phase 2)++spec :: Spec+spec = do+ observer "clk" clkStream+ observer "clk'" clkStream'+ _ <- prop "clksPhase" (forAll (clkStream == drop 2 clkStream'))+ _ <- prop "clksDistinct" (forAll (not (clkStream && clkStream')))+ trigger "clksHigh" (clkStream && clkStream') []+++verifySpec :: Verbosity -> IO ()+verifySpec verb =+ do s <- reify spec+ r <- prove Z3 s+ when (verb P.>= Default) $+ print r+ verifyWithOptions defaultVerifierOptions{verbosity = verb}+ mkDefaultCSettings [] "clock" s
+ examples/Copilot/Verifier/Examples/ShouldPass/Counter.hs view
@@ -0,0 +1,56 @@+-------------------------------------------------------------------------------+-- Copyright © 2019 National Institute of Aerospace / Galois, Inc.+--------------------------------------------------------------------------------++-- | Example showing an implementation of a resettable counter.++{-# LANGUAGE RebindableSyntax #-}++module Copilot.Verifier.Examples.ShouldPass.Counter where++import Control.Monad (when)+import qualified Prelude as P++import Language.Copilot+import Copilot.Compile.C99+import Copilot.Verifier ( Verbosity(..), VerifierOptions(..)+ , defaultVerifierOptions, verifyWithOptions )+import Copilot.Theorem.What4 (prove, Solver(..))++-- A resettable counter+counter :: (Typed a, Integral a) => Stream Bool -> Stream Bool -> Stream a+counter inc reset = cnt+ where+ cnt = if reset then 0+ else if inc then z + 1+ else z+ z = [0] ++ cnt++-- Counter that resets when it reaches 256+bytecounter :: Stream Int32+bytecounter = counter true (resetcounter `mod` 256 == 0)++resetcounter :: Stream Word32+resetcounter = counter true false++bytecounter2 :: Stream Int32+bytecounter2 = counter true ([False] ++ bytecounter2 == 255)++spec :: Spec+spec =+ do _ <- prop "range" (forAll (bytecounter == unsafeCast (resetcounter `mod` 256)))+ _ <- prop "range2" (forAll (0 <= bytecounter2 && bytecounter2 <= 255))+ _ <- prop "same" (forAll ((0 <= bytecounter2 && bytecounter2 <= 255) &&+ (bytecounter == unsafeCast (resetcounter `mod` 256)) &&+ (bytecounter == bytecounter2)))+ trigger "counter" true [arg $ bytecounter, arg $ bytecounter2]++verifySpec :: Verbosity -> IO ()+-- verifSpec _ = interpret 1280 spec+verifySpec verb =+ do s <- reify spec+ r <- prove Z3 s+ when (verb P.>= Default) $+ print r+ verifyWithOptions defaultVerifierOptions{verbosity = verb}+ mkDefaultCSettings ["range", "range2"] "counter" s
+ examples/Copilot/Verifier/Examples/ShouldPass/Engine.hs view
@@ -0,0 +1,44 @@+--------------------------------------------------------------------------------+-- Copyright © 2011 National Institute of Aerospace / Galois, Inc.+--------------------------------------------------------------------------------++-- | Example implementing an engine cooling control system.++{-# LANGUAGE RebindableSyntax #-}++module Copilot.Verifier.Examples.ShouldPass.Engine where++import Language.Copilot+import Copilot.Compile.C99+import Copilot.Verifier ( Verbosity, VerifierOptions(..)+ , defaultVerifierOptions, verifyWithOptions )++import qualified Prelude as P++{- If the majority of the engine temperature probes exeeds 250 degrees, then+ - the cooler is engaged and remains engaged until the majority of the engine+ - temperature probes drop to 250 or below. Otherwise, trigger an immediate+ - shutdown of the engine.+-}++engineMonitor :: Spec+engineMonitor = do+ trigger "shutoff" (not ok) [arg maj]++ where+ vals = [ externW8 "tmp_probe_0" two51+ , externW8 "tmp_probe_1" two51+ , externW8 "tmp_probe_2" zero]+ exceed = map (> 250) vals+ maj = majority exceed+ checkMaj = aMajority exceed maj+ ok = alwaysBeen ((maj && checkMaj) ==> extern "cooler" cooler)++ two51 = Just $ [251, 251] P.++ repeat (250 :: Word8)+ zero = Just $ repeat (0 :: Word8)+ cooler = Just $ [True, True] P.++ repeat False++verifySpec :: Verbosity -> IO ()+verifySpec verb = reify engineMonitor >>= verifyWithOptions defaultVerifierOptions{verbosity = verb}+ mkDefaultCSettings [] "engine"+--verifySpec _ = interpret 10 engineMonitor
+ examples/Copilot/Verifier/Examples/ShouldPass/FPOps.hs view
@@ -0,0 +1,88 @@+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+module Copilot.Verifier.Examples.ShouldPass.FPOps where++import Copilot.Compile.C99 (mkDefaultCSettings)+import qualified Copilot.Language.Stream as Copilot+import Copilot.Verifier ( Verbosity, VerifierOptions(..)+ , defaultVerifierOptions, verifyWithOptions )+import Data.Proxy (Proxy(..))+import Language.Copilot+import qualified Prelude as P++mkSpecFor :: forall a proxy. (RealFloat a, Typed a) => proxy a -> String -> Spec+mkSpecFor _ nameSuffix = do+ let mkName :: String -> String+ mkName namePrefix = namePrefix P.++ nameSuffix++ stream :: Stream a+ stream = extern (mkName "stream") Nothing++ triggerOp1 (mkName "abs") abs stream+ triggerOp1 (mkName "signum") signum stream+ triggerOp1 (mkName "recip") recip stream+ triggerOp1 (mkName "exp") exp stream+ triggerOp1 (mkName "sqrt") sqrt stream+ triggerOp1 (mkName "log") log stream+ triggerOp1 (mkName "sin") sin stream+ triggerOp1 (mkName "tan") tan stream+ triggerOp1 (mkName "cos") cos stream+ triggerOp1 (mkName "asin") asin stream+ triggerOp1 (mkName "atan") atan stream+ triggerOp1 (mkName "acos") acos stream+ triggerOp1 (mkName "sinh") sinh stream+ triggerOp1 (mkName "tanh") tanh stream+ triggerOp1 (mkName "cosh") cosh stream+ triggerOp1 (mkName "asinh") asinh stream+ triggerOp1 (mkName "atanh") atanh stream+ triggerOp1 (mkName "acosh") acosh stream+ triggerOp1 (mkName "ceiling") Copilot.ceiling stream+ triggerOp1 (mkName "floor") Copilot.floor stream++ triggerOp2 (mkName "add") (+) stream+ triggerOp2 (mkName "sub") (-) stream+ triggerOp2 (mkName "mul") (*) stream+ triggerOp2 (mkName "div") (/) stream+ triggerOp2 (mkName "pow") (**) stream+ triggerOp2 (mkName "logBase") logBase stream+ triggerOp2 (mkName "atan2") Copilot.atan2 stream++spec :: Spec+spec = do+ mkSpecFor (Proxy @Float) "F"+ mkSpecFor (Proxy @Double) "D"++triggerOp1 :: (RealFloat a, Typed a) =>+ String ->+ (Stream a -> Stream a) ->+ Stream a ->+ Spec+triggerOp1 name op stream =+ trigger (name P.++ "Trigger") (testOp1 op stream) [arg stream]++triggerOp2 :: (RealFloat a, Typed a) =>+ String ->+ (Stream a -> Stream a -> Stream a) ->+ Stream a ->+ Spec+triggerOp2 name op stream =+ trigger (name P.++ "Trigger") (testOp2 op stream) [arg stream]++testOp1 :: (RealFloat a, Typed a) =>+ (Stream a -> Stream a) -> Stream a -> Stream Bool+testOp1 op stream =+ -- NB: Use (>=) rather than (==) here, as floating-point equality gets weird+ -- due to NaNs.+ op stream >= op stream++testOp2 :: (RealFloat a, Typed a) =>+ (Stream a -> Stream a -> Stream a) -> Stream a -> Stream Bool+testOp2 op stream =+ op stream stream >= op stream stream++verifySpec :: Verbosity -> IO ()+verifySpec verb = do+ spec' <- reify spec+ verifyWithOptions defaultVerifierOptions{verbosity = verb}+ mkDefaultCSettings [] "fpOps" spec'
+ examples/Copilot/Verifier/Examples/ShouldPass/Heater.hs view
@@ -0,0 +1,60 @@+--------------------------------------------------------------------------------+-- Copyright 2019 National Institute of Aerospace / Galois, Inc.+--------------------------------------------------------------------------------++-- This is a simple example with basic usage. It implements a simple home+-- heating system: It heats when temp gets too low, and stops when it is high+-- enough. It read temperature as a byte (range -50C to 100C) and translates+-- this to Celcius.++module Copilot.Verifier.Examples.ShouldPass.Heater where++import Language.Copilot+import Copilot.Compile.C99+import Copilot.PrettyPrint as PP+--import Copilot.Language.Prelude++import Copilot.Verifier ( Verbosity(..), VerifierOptions(..)+ , defaultVerifierOptions, verifyWithOptions )++import qualified Prelude as P+import Control.Monad (when)++-- External temperature as a byte, range of -50C to 100C+temp :: Stream Word8+temp = extern "temperature" Nothing++-- Calculate temperature in Celcius.+-- We need to cast the Word8 to a Float. Note that it is an unsafeCast, as there+-- is no direct relation between Word8 and Float.+ctemp :: Stream Float+ctemp = (unsafeCast temp * constant (150.0/255.0)) - constant 50.0++-- width of the sliding window+window :: Int+window = 5++-- Compute the sliding average of the last 5 temps+-- (Here, 19.5 is the average of 18.0 and 21.0, the two temperature extremes+-- that we check for in the spec.)+avgTemp :: Stream Float+avgTemp = (sum window (replicate window 19.5 ++ ctemp)) / fromIntegral window++spec :: Spec+spec = do+ trigger "heaton" (avgTemp < 18.0) [arg avgTemp]+ trigger "heatoff" (avgTemp > 21.0) [arg avgTemp]++-- Compile the spec+verifySpec :: Verbosity -> IO ()+verifySpec verb =+ do rspec <- reify spec+ when (verb P.>= Default) $ putStrLn (PP.prettyPrint rspec)+ verifyWithOptions defaultVerifierOptions{verbosity = verb}+ mkDefaultCSettings [] "heater"+ rspec++{-+ do spec' <- reify spec+ putStrLn $ prettyPrintDot spec'+-}
+ examples/Copilot/Verifier/Examples/ShouldPass/IntOps.hs view
@@ -0,0 +1,64 @@+{-# LANGUAGE NoImplicitPrelude #-}+module Copilot.Verifier.Examples.ShouldPass.IntOps where++import Copilot.Compile.C99 (mkDefaultCSettings)+import Copilot.Verifier ( Verbosity, VerifierOptions(..)+ , defaultVerifierOptions, verifyWithOptions )+import Language.Copilot+import qualified Prelude as P++spec :: Spec+spec = do+ let stream :: Stream Int16+ stream = extern "stream" Nothing++ shiftBy :: Stream Int16+ shiftBy = extern "shiftBy" Nothing++ _ <- prop "nonzero" (forAll (stream /= 0))+ _ <- prop "shiftByBits" (forAll (0 <= shiftBy && shiftBy < 16))++ triggerOp1 "abs" abs stream+ triggerOp1 "signum" signum stream+ triggerOp1 "bwNot" complement stream++ triggerOp2 "add" (+) stream stream+ triggerOp2 "sub" (-) stream stream+ triggerOp2 "mul" (*) stream stream+ triggerOp2 "mod" mod stream stream+ triggerOp2 "div" div stream stream+ triggerOp2 "bwAnd" (.&.) stream stream+ triggerOp2 "bwOr" (.|.) stream stream+ triggerOp2 "bwXor" (.^.) stream stream+ triggerOp2 "bwShiftL" (.<<.) stream shiftBy+ triggerOp2 "bwShiftR" (.>>.) stream shiftBy++triggerOp1 :: String ->+ (Stream Int16 -> Stream Int16) ->+ Stream Int16 ->+ Spec+triggerOp1 name op stream =+ trigger (name P.++ "Trigger") (testOp1 op stream) [arg stream]++triggerOp2 :: String ->+ (Stream Int16 -> Stream Int16 -> Stream Int16) ->+ Stream Int16 -> Stream Int16 ->+ Spec+triggerOp2 name op stream1 stream2 =+ trigger (name P.++ "Trigger") (testOp2 op stream1 stream2) [arg stream1, arg stream2]++testOp1 :: (Stream Int16 -> Stream Int16) -> Stream Int16 -> Stream Bool+testOp1 op stream =+ op stream == op stream++testOp2 :: (Stream Int16 -> Stream Int16 -> Stream Int16) ->+ Stream Int16 -> Stream Int16 ->+ Stream Bool+testOp2 op stream1 stream2 =+ op stream1 stream2 == op stream1 stream2++verifySpec :: Verbosity -> IO ()+verifySpec verb = do+ spec' <- reify spec+ verifyWithOptions defaultVerifierOptions{verbosity = verb}+ mkDefaultCSettings ["nonzero", "shiftByBits"] "intOps" spec'
+ examples/Copilot/Verifier/Examples/ShouldPass/Partial/AbsIntMin.hs view
@@ -0,0 +1,19 @@+{-# LANGUAGE NoImplicitPrelude #-}++-- | This will succeed with 'sideCondVerifierOptions', as Copilot's @Abs@+-- operation is well defined precisely when C's @abs@ function is well defined.+module Copilot.Verifier.Examples.ShouldPass.Partial.AbsIntMin where++import Language.Copilot+import Copilot.Compile.C99+import Copilot.Verifier ( Verbosity, VerifierOptions(..)+ , sideCondVerifierOptions, verifyWithOptions )+import Copilot.Verifier.Examples.ShouldFail.Partial.AbsIntMin (spec)++verifySpec :: Verbosity -> IO ()+verifySpec verb = do+ spec' <- reify spec+ verifyWithOptions sideCondVerifierOptions{verbosity = verb}+ mkDefaultCSettings+ []+ "absIntMinPass" spec'
+ examples/Copilot/Verifier/Examples/ShouldPass/Partial/AddSignedWrap.hs view
@@ -0,0 +1,19 @@+{-# LANGUAGE NoImplicitPrelude #-}++-- | This will succeed with 'sideCondVerifierOptions', as Copilot's @Add@+-- operation should overflow on signed integers precisely when C addition does.+module Copilot.Verifier.Examples.ShouldPass.Partial.AddSignedWrap where++import Language.Copilot+import Copilot.Compile.C99+import Copilot.Verifier ( Verbosity, VerifierOptions(..)+ , sideCondVerifierOptions, verifyWithOptions )+import Copilot.Verifier.Examples.ShouldFail.Partial.AddSignedWrap (spec)++verifySpec :: Verbosity -> IO ()+verifySpec verb = do+ spec' <- reify spec+ verifyWithOptions sideCondVerifierOptions{verbosity = verb}+ mkDefaultCSettings+ []+ "addSignedWrapPass" spec'
+ examples/Copilot/Verifier/Examples/ShouldPass/Partial/DivByZero.hs view
@@ -0,0 +1,19 @@+{-# LANGUAGE NoImplicitPrelude #-}++-- | This will succeed with 'sideCondVerifierOptions', as Copilot's @Div@+-- operation is well defined precisely when C division is well defined.+module Copilot.Verifier.Examples.ShouldPass.Partial.DivByZero where++import Language.Copilot+import Copilot.Compile.C99+import Copilot.Verifier ( Verbosity, VerifierOptions(..)+ , sideCondVerifierOptions, verifyWithOptions )+import Copilot.Verifier.Examples.ShouldFail.Partial.DivByZero (spec)++verifySpec :: Verbosity -> IO ()+verifySpec verb = do+ spec' <- reify spec+ verifyWithOptions sideCondVerifierOptions{verbosity = verb}+ mkDefaultCSettings+ []+ "divByZeroPass" spec'
+ examples/Copilot/Verifier/Examples/ShouldPass/Partial/IndexOutOfBounds.hs view
@@ -0,0 +1,19 @@+{-# LANGUAGE NoImplicitPrelude #-}++-- | This will succeed with 'sideCondVerifierOptions', as Copilot's indexing+-- operation should be out of bounds precisely when C array indexes are out of bounds.+module Copilot.Verifier.Examples.ShouldPass.Partial.IndexOutOfBounds where++import Language.Copilot+import Copilot.Compile.C99+import Copilot.Verifier ( Verbosity, VerifierOptions(..)+ , sideCondVerifierOptions, verifyWithOptions )+import Copilot.Verifier.Examples.ShouldFail.Partial.IndexOutOfBounds (spec)++verifySpec :: Verbosity -> IO ()+verifySpec verb = do+ spec' <- reify spec+ verifyWithOptions sideCondVerifierOptions{verbosity = verb}+ mkDefaultCSettings+ []+ "indexPass" spec'
+ examples/Copilot/Verifier/Examples/ShouldPass/Partial/ModByZero.hs view
@@ -0,0 +1,19 @@+{-# LANGUAGE NoImplicitPrelude #-}++-- | This will succeed with 'sideCondVerifierOptions', as Copilot's @Mod@+-- operation is well defined precisely when C's @%@ operator is well defined.+module Copilot.Verifier.Examples.ShouldPass.Partial.ModByZero where++import Language.Copilot+import Copilot.Compile.C99+import Copilot.Verifier ( Verbosity, VerifierOptions(..)+ , sideCondVerifierOptions, verifyWithOptions )+import Copilot.Verifier.Examples.ShouldFail.Partial.ModByZero (spec)++verifySpec :: Verbosity -> IO ()+verifySpec verb = do+ spec' <- reify spec+ verifyWithOptions sideCondVerifierOptions{verbosity = verb}+ mkDefaultCSettings+ []+ "modByZeroPass" spec'
+ examples/Copilot/Verifier/Examples/ShouldPass/Partial/MulSignedWrap.hs view
@@ -0,0 +1,19 @@+{-# LANGUAGE NoImplicitPrelude #-}++-- | This will succeed with 'sideCondVerifierOptions', as Copilot's @Mul@+-- operation should wrap on signed integers precisely when C multiplication does.+module Copilot.Verifier.Examples.ShouldPass.Partial.MulSignedWrap where++import Language.Copilot+import Copilot.Compile.C99+import Copilot.Verifier ( Verbosity, VerifierOptions(..)+ , sideCondVerifierOptions, verifyWithOptions )+import Copilot.Verifier.Examples.ShouldFail.Partial.MulSignedWrap (spec)++verifySpec :: Verbosity -> IO ()+verifySpec verb = do+ spec' <- reify spec+ verifyWithOptions sideCondVerifierOptions{verbosity = verb}+ mkDefaultCSettings+ []+ "mulSignedWrapPass" spec'
+ examples/Copilot/Verifier/Examples/ShouldPass/Partial/ShiftLTooLarge.hs view
@@ -0,0 +1,19 @@+{-# LANGUAGE NoImplicitPrelude #-}++-- | This will succeed with 'sideCondVerifierOptions', as Copilot's @BwShiftL@+-- operation should only accept second arguments as large as C's @<<@ operation does.+module Copilot.Verifier.Examples.ShouldPass.Partial.ShiftLTooLarge where++import Language.Copilot+import Copilot.Compile.C99+import Copilot.Verifier ( Verbosity, VerifierOptions(..)+ , sideCondVerifierOptions, verifyWithOptions )+import Copilot.Verifier.Examples.ShouldFail.Partial.ShiftLTooLarge (spec)++verifySpec :: Verbosity -> IO ()+verifySpec verb = do+ spec' <- reify spec+ verifyWithOptions sideCondVerifierOptions{verbosity = verb}+ mkDefaultCSettings+ []+ "shiftLTooLargePass" spec'
+ examples/Copilot/Verifier/Examples/ShouldPass/Partial/ShiftRTooLarge.hs view
@@ -0,0 +1,19 @@+{-# LANGUAGE NoImplicitPrelude #-}++-- | This will succeed with 'sideCondVerifierOptions', as Copilot's @BwShiftR@+-- operation should only accept second arguments as large as C's @>>@ operation does.+module Copilot.Verifier.Examples.ShouldPass.Partial.ShiftRTooLarge where++import Language.Copilot+import Copilot.Compile.C99+import Copilot.Verifier ( Verbosity, VerifierOptions(..)+ , sideCondVerifierOptions, verifyWithOptions )+import Copilot.Verifier.Examples.ShouldFail.Partial.ShiftRTooLarge (spec)++verifySpec :: Verbosity -> IO ()+verifySpec verb = do+ spec' <- reify spec+ verifyWithOptions sideCondVerifierOptions{verbosity = verb}+ mkDefaultCSettings+ []+ "shiftRTooLargePass" spec'
+ examples/Copilot/Verifier/Examples/ShouldPass/Partial/SubSignedWrap.hs view
@@ -0,0 +1,19 @@+{-# LANGUAGE NoImplicitPrelude #-}++-- | This will succeed with 'sideCondVerifierOptions', as Copilot's @Sub@+-- operation should underflow on signed integers precisely when C subtraction does.+module Copilot.Verifier.Examples.ShouldPass.Partial.SubSignedWrap where++import Language.Copilot+import Copilot.Compile.C99+import Copilot.Verifier ( Verbosity, VerifierOptions(..)+ , sideCondVerifierOptions, verifyWithOptions )+import Copilot.Verifier.Examples.ShouldFail.Partial.SubSignedWrap (spec)++verifySpec :: Verbosity -> IO ()+verifySpec verb = do+ spec' <- reify spec+ verifyWithOptions sideCondVerifierOptions{verbosity = verb}+ mkDefaultCSettings+ []+ "subSignedWrapPass" spec'
+ examples/Copilot/Verifier/Examples/ShouldPass/Structs.hs view
@@ -0,0 +1,92 @@+-- | An example showing the usage of the What4 backend in copilot-theorem for+-- structs and arrays. Particular focus is on nested structs.+-- For general usage of structs, refer to the general structs example.++{-# LANGUAGE DataKinds #-}+{-# LANGUAGE NoImplicitPrelude #-}+module Copilot.Verifier.Examples.ShouldPass.Structs where++import Control.Monad (void, forM_, when)+import qualified Prelude as P++import Language.Copilot+import Copilot.Compile.C99+import Copilot.Theorem.What4+import Copilot.Verifier ( Verbosity(..), VerifierOptions(..)+ , defaultVerifierOptions, verifyWithOptions )+++-- | Definition for `Volts`.+data Volts = Volts+ { numVolts :: Field "numVolts" Word16+ , flag :: Field "flag" Bool+ }++-- | `Struct` instance for `Volts`.+instance Struct Volts where+ typename _ = "volts"+ toValues vlts = [ Value Word16 (numVolts vlts)+ , Value Bool (flag vlts)+ ]++-- | `Volts` instance for `Typed`.+instance Typed Volts where+ typeOf = Struct (Volts (Field 0) (Field False))++data Battery = Battery+ { temp :: Field "temp" Word16+ , volts :: Field "volts" (Array 10 Volts)+ , other :: Field "other" (Array 10 (Array 5 Word32))+ }++-- | `Battery` instance for `Struct`.+instance Struct Battery where+ typename _ = "battery"+ toValues battery = [ Value typeOf (temp battery)+ , Value typeOf (volts battery)+ , Value typeOf (other battery)+ ]++-- | `Battery` instance for `Typed`. Note that `undefined` is used as an+-- argument to `Field`. This argument is never used, so `undefined` will never+-- throw an error.+instance Typed Battery where+ typeOf = Struct (Battery (Field 0) (Field undefined) (Field undefined))++spec :: Spec+spec = do+ let battery :: Stream Battery+ battery = extern "battery" Nothing++ -- Check equality, indexing into nested structs and arrays. Note that this is+ -- trivial by equality.+ void $ prop "Example 1" $ forAll $+ (((battery#volts) .!! 0)#numVolts) == (((battery#volts) .!! 0)#numVolts)++ -- Same as previous example, but get a different array index (so should be+ -- false).+ void $ prop "Example 2" $ forAll $+ (((battery#other) .!! 2) .!! 3) == (((battery#other) .!! 2) .!! 4)++ -- Same as previous example, but in trigger form+ trigger "otherTrig" ((((battery#other) .!! 2) .!! 3) == (((battery#other) .!! 2) .!! 4))+ [arg battery]++verifySpec :: Verbosity -> IO ()+verifySpec verb = do+ spec' <- reify spec++ -- Use Z3 to prove the properties.+ results <- prove Z3 spec'++ -- Print the results.+ when (verb P.>= Default) $+ forM_ results $ \(nm, res) -> do+ putStr $ nm <> ": "+ case res of+ Valid -> putStrLn "valid"+ Invalid -> putStrLn "invalid"+ Unknown -> putStrLn "unknown"++ verifyWithOptions defaultVerifierOptions{verbosity = verb}+ mkDefaultCSettings [] "structs" spec'
+ examples/Copilot/Verifier/Examples/ShouldPass/Voting.hs view
@@ -0,0 +1,64 @@+--------------------------------------------------------------------------------+-- Copyright © 2019 National Institute of Aerospace / Galois, Inc.+--------------------------------------------------------------------------------++-- | Fault-tolerant voting examples.++{-# LANGUAGE RebindableSyntax #-}++module Copilot.Verifier.Examples.ShouldPass.Voting where++import Language.Copilot+import Copilot.Compile.C99+import Copilot.Verifier ( Verbosity, VerifierOptions(..)+ , defaultVerifierOptions, verifyWithOptions )++vote :: Spec+vote = do+ -- majority selects element with the biggest occurance.+ trigger "maj" true [arg maj]++ -- aMajority checks if the selected element has a majority.+ trigger "aMaj" true [arg $ aMajority inputs maj]++ where+ maj = majority inputs++ -- 26 input streams to vote on+ inputs :: [Stream Word32]+ inputs = [ a, b, c, d, e, f, g, h, i, j, k, l, m+ -- , n, o, p, q, r, s, t, u, v, w, x, y, z+ ]+ a = [0] ++ a + 1+ b = [0] ++ b + 1+ c = [0] ++ c + 1+ d = [0] ++ d + 1+ e = [1] ++ e + 1+ f = [1] ++ f + 1+ g = [1] ++ g + 1+ h = [1] ++ h + 1+ i = [1] ++ i + 1+ j = [1] ++ j + 1+ k = [1] ++ k + 1+ l = [1] ++ l + 1+ m = [1] ++ m + 1+{-+ n = [1] ++ n + 1+ o = [1] ++ o + 1+ p = [1] ++ p + 1+ q = [1] ++ q + 1+ r = [1] ++ r + 1+ s = [1] ++ s + 1+ t = [1] ++ t + 1+ u = [1] ++ u + 1+ v = [1] ++ v + 1+ w = [1] ++ w + 1+ x = [1] ++ x + 1+ y = [1] ++ y + 1+ z = [1] ++ z + 1+-}++verifySpec :: Verbosity -> IO ()+--verifySpec _ = interpret 30 vote+verifySpec verb = reify vote >>= verifyWithOptions defaultVerifierOptions{verbosity = verb}+ mkDefaultCSettings [] "voting"
+ examples/Copilot/Verifier/Examples/ShouldPass/WCV.hs view
@@ -0,0 +1,194 @@+-- | This example shows an implementation of the Well-Clear Violation+-- algorithm, it follows the implementation described in 'Analysis of+-- Well-Clear Bounday Models for the Integration of UAS in the NAS',+-- https://ntrs.nasa.gov/citations/20140010078.++{-# LANGUAGE DataKinds #-}+{-# LANGUAGE RebindableSyntax #-}++module Copilot.Verifier.Examples.ShouldPass.WCV where++import Language.Copilot+import qualified Copilot.Theorem.What4 as CT+import Copilot.Compile.C99+import Copilot.Verifier ( Verbosity, VerifierOptions(..)+ , defaultVerifierOptions, verifyWithOptions )++import qualified Prelude as P+import Data.Foldable (forM_)+import qualified Control.Monad as Monad+++-- | `dthr` is the horizontal distance threshold.+dthr :: Stream Double+dthr = extern "dthr" Nothing++-- | `tthr` is the horizontal time threshold.+tthr :: Stream Double+tthr = extern "tthr" Nothing++-- | `zthr` is the vertical distance / altitude threshold.+zthr :: Stream Double+zthr = extern "zthr" Nothing++-- | `tcoathr` is the vertical time threshold.+tcoathr :: Stream Double+tcoathr = extern "tcoathr" Nothing++type Vect2 = (Stream Double, Stream Double)+++--------------------------------+-- External streams for relative position and velocity.+--------------------------------++-- | The relative x velocity between ownship and the intruder.+vx :: Stream Double+vx = extern "relative_velocity_x" Nothing++-- | The relative y velocity between ownship and the intruder.+vy :: Stream Double+vy = extern "relative_velocity_y" Nothing++-- | The relative z velocity between ownship and the intruder.+vz :: Stream Double+vz = extern "relative_velocity_z" Nothing++-- | The relative velocity as a 2D vector.+v :: (Stream Double, Stream Double)+v = (vx, vy)+++-- | The relative x position between ownship and the intruder.+sx :: Stream Double+sx = extern "relative_position_x" Nothing++-- | The relative y position between ownship and the intruder.+sy :: Stream Double+sy = extern "relative_position_y" Nothing++-- | The relative z position between ownship and the intruder.+sz :: Stream Double+sz = extern "relative_position_z" Nothing++-- | The relative position as a 2D vector.+s :: (Stream Double, Stream Double)+s = (sx, sy)+++------------------+-- The following section contains basic libraries for working with vectors.+------------------++-- | Multiply two Vectors.+(|*|) :: Vect2 -> Vect2 -> Stream Double+(|*|) (x1, y1) (x2, y2) = (x1 * x2) + (y1 * y2)++-- | Calculate the square of a vector.+sq :: Vect2 -> Stream Double+sq x = x |*| x++-- | Calculate the length of a vector.+norm :: Vect2 -> Stream Double+norm = sqrt . sq++-- | Calculate the determinant of two vectors.+det :: Vect2 -> Vect2 -> Stream Double+det (x1, y1) (x2, y2) = (x1 * y2) - (x2 * y1)++-- | Compare two vectors, taking into account the small error that is+-- introduced by the usage of `Double`s.+(~=) :: Stream Double -> Stream Double -> Stream Bool+a ~= b = (abs (a - b)) < 0.001++-- | Negate a vector.+neg :: Vect2 -> Vect2+neg (x, y) = (negate x, negate y)+++--------------------+-- From here on the algorithm, as described by the paper mentioned on the top+-- of this file, is implemented. Please refer to the paper for details.+--------------------++tau :: Vect2 -> Vect2 -> Stream Double+tau s v = if s |*| v < 0+ then (-(sq s)) / (s |*| v)+ else -1++tcpa :: Vect2 -> Vect2 -> Stream Double+tcpa s v@(vx, vy) = if vx ~= 0 && vy ~= 0+ then 0+ else -(s |*| v)/(sq v)++taumod :: Vect2 -> Vect2 -> Stream Double+taumod s v = if s |*| v < 0+ then (dthr * dthr - (sq s))/(s |*| v)+ else -1++tep :: Vect2 -> Vect2 -> Stream Double+tep s v = if (s |*| v < 0) && ((delta s v dthr) >= 0)+ then theta s v dthr (-1)+ else -1++delta :: Vect2 -> Vect2 -> Stream Double -> Stream Double+delta s v d = (d*d) * (sq v) - ((det s v)*(det s v))+-- Here the formula says : (s . orth v)^2 which is the same as det(s,v)^2++theta :: Vect2 -> Vect2 -> Stream Double -> Stream Double -> Stream Double+theta s v d e = (-(s |*| v) + e * (sqrt $ delta s v d)) / (sq v)+++tcoa :: Stream Double -> Stream Double -> Stream Double+tcoa sz vz = if (sz * vz) < 0+ then (-sz) / vz+ else -1++dcpa :: Vect2 -> Vect2 -> Stream Double+dcpa s@(sx, sy) v@(vx, vy) = norm (sx + (tcpa s v) * vx, sy + (tcpa s v) * vy)+++--------------------------+-- Well clear Violation --+--------------------------++-- | Determines if the well clear property is violated or not.+wcv :: (Vect2 -> Vect2 -> Stream Double) ->+ Vect2 -> Stream Double ->+ Vect2 -> Stream Double ->+ Stream Bool+wcv tvar s sz v vz = (horizontalWCV tvar s v) && (verticalWCV sz vz)++verticalWCV :: Stream Double -> Stream Double -> Stream Bool+verticalWCV sz vz =+ ((abs $ sz) <= zthr) ||+ (0 <= (tcoa sz vz) && (tcoa sz vz) <= tcoathr)++horizontalWCV :: (Vect2 -> Vect2 -> Stream Double) -> Vect2 -> Vect2 -> Stream Bool+horizontalWCV tvar s v =+ (norm s <= dthr) ||+ (((dcpa s v) <= dthr) && (0 <= (tvar s v)) && ((tvar s v) <= tthr))++spec = do+ Monad.void $ prop "1a" (forAll $ (tau s v) ~= (tau (neg s) (neg v)))+ -- Monad.void $ prop "3d" (forAll $ (wcv tep s sz v vz) == (wcv tep (neg s) (-sz) (neg v) (-vz)))+ trigger "well_clear_violation" (wcv tep s sz v vz) []++verifySpec :: Verbosity -> IO ()+verifySpec verb = do+ spec' <- reify spec+ verifyWithOptions defaultVerifierOptions{verbosity = verb}+ mkDefaultCSettings [] "wcv" spec'++{-+ -- Use Z3 to prove the properties.+ results <- CT.prove CT.Z3 spec'++ -- Print the results.+ forM_ results $ \(nm, res) -> do+ putStr $ nm <> ": "+ case res of+ CT.Valid -> putStrLn "valid"+ CT.Invalid -> putStrLn "invalid"+ CT.Unknown -> putStrLn "unknown"+-}
+ exe/VerifyExamples.hs view
@@ -0,0 +1,53 @@+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE OverloadedStrings #-}+module Main (main) where++import qualified Data.CaseInsensitive as CI+import Data.CaseInsensitive (CI)+import Data.Foldable (for_)+import qualified Data.Map as Map+import qualified Data.Text as Text+import qualified Data.Text.IO as Text+import Data.Text (Text)+import Data.Traversable (for)+import Options.Applicative++import Copilot.Verifier (Verbosity(..))+import Copilot.Verifier.Examples (shouldFailExamples, shouldPassExamples)++newtype Options = Options+ { examples :: [CI Text]+ } deriving Show++optsParser :: Parser Options+optsParser = Options+ <$> (some . strArgument)+ ( metavar "EXAMPLE1 [EXAMPLE2 ...]"+ <> help "The names of the examples to run" )++main :: IO ()+main = execParser opts >>= verifyExamples+ where+ opts = info (optsParser <**> helper)+ ( fullDesc+ <> header "Run one or more copilot-verifier examples"+ <> progDesc (unlines [ "Run one or more examples from the copilot-verifier/examples directory."+ , "Each EXAMPLE must correspond to an example name."+ ]))++verifyExamples :: Options -> IO ()+verifyExamples Options{examples} = do+ -- Check that all requested examples exist+ examplesWithMain <- for examples $ \example ->+ case Map.lookup example (shouldFailExamples Default `Map.union` shouldPassExamples Default) of+ Just m -> pure (example, m)+ Nothing -> fail $ "No example named " ++ Text.unpack (CI.original example)++ -- Run the examples+ for_ examplesWithMain $ \(example, exampleMain) -> do+ Text.putStrLn "====="+ Text.putStrLn $ "== Running the " <> CI.original example <> " example..."+ Text.putStrLn "====="+ Text.putStrLn ""+ exampleMain+ Text.putStrLn ""
+ src/Copilot/Verifier.hs view
@@ -0,0 +1,1517 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE ExplicitNamespaces #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ImplicitParams #-}+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeOperators #-}++module Copilot.Verifier+ ( verify+ , verifyWithOptions+ , VerifierOptions(..)+ , defaultVerifierOptions+ , sideCondVerifierOptions+ , Verbosity(..)+ ) where++import Control.Lens (view, (^.), to)+import Control.Monad (foldM, forM_, when)+import Control.Monad.IO.Class (liftIO)+import Control.Monad.State (execStateT, lift, StateT(..))+import Data.Aeson (ToJSON)+import Data.Foldable (traverse_)+import Data.Functor (void)+import qualified Data.Text as Text+import qualified Data.Map.Strict as Map+import Data.IORef (newIORef, modifyIORef', readIORef, IORef)+import qualified Text.LLVM.AST as L+import Data.List (genericLength)+import Data.List.NonEmpty (NonEmpty(..))+import qualified Data.List.NonEmpty as NE+import qualified Data.Vector as V+import qualified Data.BitVector.Sized as BV+import GHC.Generics (Generic)+import qualified Prettyprinter as PP+import System.Exit (exitFailure)+import System.FilePath ((</>), (<.>))++import Copilot.Compile.C99 (CSettings(..), compileWith)+import Copilot.Core+import qualified Copilot.Core.Type as CT++import qualified Copilot.Theorem.What4 as CW4++import qualified Copilot.Verifier.Log as Log++import Data.Parameterized.Ctx (EmptyCtx)+import Data.Parameterized.Context (pattern Empty)+import qualified Data.Parameterized.Context as Ctx+import Data.Parameterized.NatRepr (intValue, natValue, testEquality, knownNat, type (<=) )+import Data.Parameterized.Nonce (globalNonceGenerator)+import Data.Parameterized.Some (Some(..))+import Data.Parameterized.TraversableFC (toListFC)+import Data.Parameterized.TraversableFC.WithIndex (ifoldlMFC)+import qualified Data.Parameterized.Vector as PVec++import Lang.Crucible.Backend+ ( IsSymInterface, Goals(..), Assumptions, Assertion+ , pushAssumptionFrame, popUntilAssumptionFrame+ , getProofObligations, clearProofObligations+ , LabeledPred(..), abortExecBecause, AbortExecReason(..), addAssumption+ , addDurableProofObligation, assert, CrucibleAssumption(..), ppAbortExecReason+ , IsSymBackend(..), HasSymInterface(..)+ , labeledPred, labeledPredMsg+ -- , ProofObligations, proofGoal, goalsToList, labeledPredMsg+ )+import Lang.Crucible.Backend.Simple (newSimpleBackend)+import Lang.Crucible.CFG.Core (AnyCFG(..), cfgArgTypes, cfgReturnType)+import Lang.Crucible.CFG.Common ( freshGlobalVar )+import Lang.Crucible.FunctionHandle (newHandleAllocator)+import Lang.Crucible.Simulator+ ( SimContext(..), ctxSymInterface, ExecResult(..), ExecState(..)+ , defaultAbortHandler, runOverrideSim, partialValue, gpValue+ , GlobalVar, executeCrucible, OverrideSim, regValue+ , readGlobal, modifyGlobal, callCFG, emptyRegMap, RegEntry(..)+ , AbortedResult(..)+ )+import Lang.Crucible.Simulator.ExecutionTree ( withBackend )+import Lang.Crucible.Simulator.GlobalState ( insertGlobal )+import Lang.Crucible.Simulator.RegValue (RegValue, RegValue'(..))+import Lang.Crucible.Simulator.SimError (SimError(..), SimErrorReason(..)) -- ppSimError+import Lang.Crucible.Types+ ( TypeRepr(..), (:~:)(..), KnownRepr(..), NatType )++import Lang.Crucible.LLVM (llvmGlobals, registerLazyModule, register_llvm_overrides)+import Lang.Crucible.LLVM.Bytes (bitsToBytes)+import Lang.Crucible.LLVM.DataLayout (Alignment, DataLayout)+import Lang.Crucible.LLVM.Errors (BadBehavior)+import Lang.Crucible.LLVM.Extension (LLVM, ArchWidth)+import Lang.Crucible.LLVM.Globals (initializeAllMemory, populateAllGlobals)+import Lang.Crucible.LLVM.Intrinsics+ ( IntrinsicsOptions, OverrideTemplate, basic_llvm_override, LLVMOverride(..) )++import Lang.Crucible.LLVM.MemType+ ( MemType(..), SymType(..)+ , i1, i8, i16, i32, i64+ , memTypeSize, memTypeAlign+ , mkStructInfo+ )+import Lang.Crucible.LLVM.MemModel+ ( mkMemVar, withPtrWidth, HasLLVMAnn, LLVMAnnMap, MemImpl+ , HasPtrWidth, doResolveGlobal, doStore+ , LLVMPtr, LLVMVal, MemOptions, PartLLVMVal(..), StorageType, bitvectorType+ , ptrAdd, toStorableType, projectLLVM_bv+ , pattern LLVMPointerRepr, pattern PtrRepr, loadRaw, llvmPointer_bv+ , memRepr, Mem, unpackMemValue+ )+import Lang.Crucible.LLVM.MemModel.CallStack (CallStack)+import Lang.Crucible.LLVM.MemModel.Partial (BoolAnn(..))+import Lang.Crucible.LLVM.PrettyPrint (ppSymbol)+import Lang.Crucible.LLVM.Translation+ ( LLVMTranslationWarning(..), ModuleTranslation+ , getTranslatedCFG, translateModule, globalInitMap+ , transContext, llvmPtrWidth, llvmTypeCtx, llvmTypeAsRepr+ )+import Lang.Crucible.LLVM.TypeContext (TypeContext, llvmDataLayout)++import Crux (defaultOutputConfig)+import Crux.Config (cfgJoin, Config(..))+import Crux.Config.Load (fromFile, fromEnv)+import Crux.Config.Common+ ( cruxOptions, CruxOptions(..), postprocessOptions, outputOptions+ , OutputOptions(..)+ )+import Crux.Goal (proveGoalsOffline, provedGoalsTree)+import qualified Crux.Log as Log+import Crux.Types (SimCtxt, Crux, ProcessedGoals(..), ProofResult(..))++import Crux.LLVM.Config (llvmCruxConfig, LLVMOptions(..))+import Crux.LLVM.Compile (genBitCode)+import qualified Crux.LLVM.Log as Log+import Crux.LLVM.Simulate (setupSimCtxt, parseLLVM, explainFailure)+import CruxLLVMMain (processLLVMOptions)++import What4.Config+ (extendConfig)+import What4.Interface+ ( Pred, bvLit, bvAdd, bvUrem, bvMul, bvIsNonzero, bvEq, isEq+ , getConfiguration, freshBoundedBV, predToBV+ , getCurrentProgramLoc, printSymExpr+ , truePred, falsePred, andPred, annotateTerm, backendPred+ , getAnnotation, natAdd, natEq, natIte, natLit+ )+import What4.Expr.Builder+ ( FloatModeRepr(..), ExprBuilder, BoolExpr, startCaching+ , newExprBuilder+ )+import What4.FunctionName (functionName)+import What4.InterpretedFloatingPoint+ ( FloatInfoRepr(..), IsInterpretedFloatExprBuilder(..)+ , SingleFloat, DoubleFloat+ )+import What4.ProgramLoc (ProgramLoc, mkProgramLoc, plFunction, Position(..))+import What4.Solver.Adapter (SolverAdapter(..))+import What4.Solver.Z3 (z3Adapter)+import What4.Symbol (safeSymbol)++-- | @'verify' csettings props prefix spec@ verifies the Copilot specification+-- @spec@ under the assumptions @props@ matches the behavior of the C program+-- compiled with @csettings@ within a directory prefixed by @prefix@.+verify :: CSettings -> [String] -> String -> Spec -> IO ()+verify = verifyWithOptions defaultVerifierOptions++-- | Options for configuring the behavior of the verifier.+data VerifierOptions = VerifierOptions+ { verbosity :: Verbosity+ -- ^ How much output the verifier should produce.+ , assumePartialSideConds :: Bool+ -- ^ If 'True', the verifier will determine the conditions under which+ -- a Copilot specification's partial operations are well defined and+ -- add these side conditions as assumptions. As a result, even if the+ -- generated C code performs a partial operation, the verification will+ -- succeed if this partial operation coincides with a corresponding+ -- operation on the Copilot side.+ --+ -- If 'False', the verifier will not assume any side conditions related+ -- to partial operations in the Copilot specification. As a result, any+ -- use of a partial operation in the generated C code will cause+ -- verification to fail unless the user adds their own assumptions.+ , logSmtInteractions :: Bool+ -- ^ If 'True', create log files corresponding to the SMT solver+ -- interactions used to discharge each proof goal. The file will be named+ -- @<step>-<goal number>-<solver>.smt2@, where:+ --+ -- * @<step>@ will be either @initial-step@ or @transition-step@, depending+ -- on which step of the proof the goal corresponds to.+ --+ -- * @<goal number>@ will be the number of the goal, starting at 0 and+ -- counting up. Note that each step of the proof has its own goal+ -- numbers. This means that there can be both an+ -- @initial-step-0-<solver>.smt2@ and a @transition-step-0-<solver>.smt2@,+ -- and similarly for other numbers.+ --+ -- * @<solver>@ is the name of the SMT solver used to discharge the proof+ -- goal. Currently, this will always be @z3@, although we might make this+ -- configurable in the future.+ } deriving stock Show++-- | The default 'VerifierOptions':+--+-- * Produce a reasonable amount of diagnostics as verification proceeds+-- ('Default').+--+-- * Do not assume any side conditions related to partial operations.+--+-- * Do not log any SMT solver interactions.+defaultVerifierOptions :: VerifierOptions+defaultVerifierOptions = VerifierOptions+ { verbosity = Default+ , assumePartialSideConds = False+ , logSmtInteractions = False+ }++-- | Like 'defaultVerifierOptions', except that the verifier will assume side+-- conditions related to partial operations used in the Copilot spec.+sideCondVerifierOptions :: VerifierOptions+sideCondVerifierOptions = defaultVerifierOptions+ { assumePartialSideConds = True+ }++-- | How much output should verification produce?+--+-- The data constructors are listed in increasing order of how many diagnostics+-- they produce.+data Verbosity+ = Quiet -- ^ Don't produce any diagnostics.+ | Default -- ^ Produce a reasonable amount of diagnostics as verification proceeds.+ | Noisy -- ^ Produce as many diagnostics as possible.+ deriving stock (Eq, Ord, Show)++-- | Like 'verify', but with 'VerifierOptions' to more finely control the+-- verifier's behavior.+verifyWithOptions :: VerifierOptions -> CSettings -> [String] -> String -> Spec -> IO ()+verifyWithOptions opts csettings0 properties prefix spec =+ withCopilotLogging $+ do -- munge options structures into the necessary forms+ (ocfg, cruxOpts, llvmOpts, csettings, csrc) <- computeConfiguration opts csettings0 prefix+ let ?outputConfig = ocfg++ -- Compile the Copilot spec into C source code, using+ -- preexisting Copilot library calls.+ compileWith csettings prefix spec+ Log.sayCopilot $ Log.GeneratedCFile csrc++ -- Compile the C source into LLVM bitcode, using preexisting+ -- Crux library calls.+ bcFile <- genBitCode cruxOpts llvmOpts+ Log.sayCopilot $ Log.CompiledBitcodeFile prefix bcFile++ -- Run the main verification procedure+ verifyBitcode opts csettings properties spec cruxOpts llvmOpts bcFile+++-- | Do the (surprisingly large amount) of options munging necessary to set up+-- the crucible/crux environment.+computeConfiguration ::+ Log.SupportsCruxLogMessage CopilotLogging =>+ VerifierOptions -> CSettings -> FilePath ->+ IO (Log.OutputConfig CopilotLogging, CruxOptions, LLVMOptions, CSettings, FilePath)+computeConfiguration opts csettings0 prefix =+ do ocfg1 <- defaultOutputConfig copilotLoggingToSayWhat+ let quiet = verbosity opts == Quiet+ let ocfg2 mbOutputOpts = (ocfg1 mbOutputOpts) { Log._quiet = quiet }+ llvmcfg <- llvmCruxConfig+ let cfg = cfgJoin cruxOptions llvmcfg+ -- TODO, load from an actual configuration file?+ fileOpts <- fromFile "copilot-verifier" cfg Nothing+ (cruxOpts0, llvmOpts0) <- foldM fromEnv fileOpts (cfgEnv cfg)+ let odir0 = cSettingsOutputDirectory csettings0+ let odir = -- A bit grimy, but this corresponds to how crux-llvm sets+ -- its output directory.+ if odir0 == "."+ then "results" </> prefix+ else odir0+ let csettings = csettings0{ cSettingsOutputDirectory = odir }+ let csrc = odir </> prefix ++ ".c"+ let cruxOpts1 = cruxOpts0{ outDir = odir, bldDir = odir, inputFiles = [csrc]+ , outputOptions =+ (outputOptions cruxOpts0)+ { quietMode = quiet+ , simVerbose = if verbosity opts > Default+ then 2+ else 0+ }+ }+ let ?outputConfig = ocfg2 (Just (outputOptions cruxOpts1))+ cruxOpts2 <- postprocessOptions cruxOpts1++ -- Tweak the options passed to Clang:+ --+ -- - Fix the optimization level to -O0.+ --+ -- - Pass -ffp-contract=off to prevent sequences of floating-point+ -- multiplications/additions from being optimized to llvm.fmuladd+ -- intrinsics, which makes floating-point verification fragile.+ let llvmOpts1 = llvmOpts0+ { optLevel = 0+ , clangOpts = "-ffp-contract=off" : clangOpts llvmOpts0+ }+ (cruxOpts3, llvmOpts2) <- processLLVMOptions (cruxOpts2, llvmOpts1)++ let ocfg3 = ocfg2 (Just (outputOptions cruxOpts3))+ return (ocfg3, cruxOpts3, llvmOpts2, csettings, csrc)+++data CopilotVerifierData t = CopilotVerifierData+++-- | Main entry point for the verifier.+verifyBitcode ::+ Log.Logs msgs =>+ Log.SupportsCruxLogMessage msgs =>+ Log.SupportsCruxLLVMLogMessage msgs =>+ Log.SupportsCopilotLogMessage msgs =>+ VerifierOptions {- ^ Verifier-specific settings -} ->+ CSettings {- ^ Settings used to compile the Copilot spec. Used to find the names of functions and variables. -} ->+ [String] {- ^ Names of properties to assume during verification. -} ->+ Spec {- ^ The input Copilot specification -} ->+ CruxOptions {- ^ Crux options -} ->+ LLVMOptions {- ^ CruxLLVM options -} ->+ FilePath {- ^ Path to the bitcode file to verify -} ->+ IO ()+verifyBitcode opts csettings properties spec cruxOpts llvmOpts bcFile =+ do -- Set up the expression builder and symbolic backend+ halloc <- newHandleAllocator+ sym <- newExprBuilder FloatUninterpretedRepr CopilotVerifierData globalNonceGenerator+ bak <- newSimpleBackend sym+ -- turn on hash-consing+ startCaching sym++ -- capture LLVM side-condition information for use in error reporting+ clRefs <- newCopilotLogRefs+ let ?recordLLVMAnnotation = recordLLVMAnnotation clRefs++ -- Set up the solver to use for verification. Right now we hard-code this to Z3.+ let adapters = [z3Adapter] -- TODO? configurable+ extendConfig (solver_adapter_config_options z3Adapter) (getConfiguration sym)++ -- Set up the Crucible/LLVM simulation context+ memVar <- mkMemVar "llvm_memory" halloc+ let simctx = (setupSimCtxt halloc bak (memOpts llvmOpts) memVar)+ { printHandle = view Log.outputHandle ?outputConfig }++ -- Load and translate the input LLVM module+ llvmMod <- parseLLVM bcFile+ Some trans <-+ let ?transOpts = transOpts llvmOpts+ in translateModule halloc memVar llvmMod++ Log.sayCopilot Log.TranslatedToCrucible++ -- Grab some metadata from the bitcode file and options;+ -- make the available via implicit arguments to the places+ -- that expect them.+ let llvmCtxt = trans ^. transContext+ let ?lc = llvmCtxt ^. llvmTypeCtx+ let ?memOpts = memOpts llvmOpts+ let ?intrinsicsOpts = intrinsicsOpts llvmOpts++ llvmPtrWidth llvmCtxt $ \ptrW ->+ withPtrWidth ptrW $++ do -- Compute the LLVM memory state with global variables allocated+ -- but not initialized+ emptyMem <- initializeAllMemory bak llvmCtxt llvmMod++ -- Compute the LLVM memory state with global variables initialized+ -- to their initial values.+ initialMem <- populateAllGlobals bak (trans ^. globalInitMap) emptyMem++ -- Use the Copilot spec directly to compute the symbolic states+ -- necessary to carry out the states of the bisimulation proof.+ Log.sayCopilot Log.GeneratingProofState+ proofStateBundle <- CW4.computeBisimulationProofBundle sym properties spec++ -- First check that the initial state of the program matches the starting+ -- segment of the associated Copilot streams.+ let cruxOptsInit = setCruxOfflineSolverOutput "initial-step" cruxOpts+ verifyInitialState cruxOptsInit adapters clRefs simctx initialMem+ (CW4.initialStreamState proofStateBundle)++ -- Now, the real meat. Carry out the bisimulation step of the proof.+ let cruxOptsTrans = setCruxOfflineSolverOutput "transition-step" cruxOpts+ verifyStepBisimulation opts cruxOptsTrans adapters csettings+ clRefs simctx llvmMod trans memVar initialMem proofStateBundle+ where+ -- If @logSmtInteractions@ is enabled, enable offline solver output in the+ -- supplied 'CruxOptions' with the supplied file template. Otherwise, return+ -- the supplied 'CruxOptions' unaltered.+ setCruxOfflineSolverOutput :: FilePath -> CruxOptions -> CruxOptions+ setCruxOfflineSolverOutput template cruxOpts'+ | logSmtInteractions opts+ = cruxOpts'+ { offlineSolverOutput = Just $ outDir cruxOpts' </> template <.> "smt2" }+ | otherwise+ = cruxOpts'++-- | Capture LLVM side-condition information for use in error reporting.+recordLLVMAnnotation ::+ IsSymInterface sym =>+ CopilotLogRefs sym ->+ CallStack ->+ BoolAnn sym ->+ BadBehavior sym ->+ IO ()+recordLLVMAnnotation clRefs stk bann bb =+ modifyIORef' (llvmAnnMapRef clRefs) (Map.insert bann (stk, bb))++-- | Prove that the state of the global variables at program startup+-- matches the expected initial segments of the associated Copilot+-- streams.+verifyInitialState ::+ IsSymInterface sym =>+ Log.Logs msgs =>+ Log.SupportsCruxLogMessage msgs =>+ Log.SupportsCopilotLogMessage msgs =>+ sym ~ ExprBuilder t st fs =>+ HasPtrWidth wptr =>+ HasLLVMAnn sym =>+ (?memOpts :: MemOptions) =>+ (?lc :: TypeContext) =>++ CruxOptions ->+ [SolverAdapter st] ->+ CopilotLogRefs sym ->+ SimCtxt Crux sym LLVM ->+ MemImpl sym ->+ CW4.BisimulationProofState sym ->+ IO ()+verifyInitialState cruxOpts adapters clRefs simctx mem initialState =+ withBackend simctx $ \bak ->+ do Log.sayCopilot $ Log.ComputingConditions Log.InitialState+ frm <- pushAssumptionFrame bak++ assertStateRelation bak clRefs mem initialState++ popUntilAssumptionFrame bak frm++ Log.sayCopilot $ Log.ProvingConditions Log.InitialState+ proveObls cruxOpts adapters clRefs Log.InitialState simctx+++verifyStepBisimulation ::+ IsSymInterface sym =>+ Log.Logs msgs =>+ Log.SupportsCruxLogMessage msgs =>+ Log.SupportsCruxLLVMLogMessage msgs =>+ Log.SupportsCopilotLogMessage msgs =>+ sym ~ ExprBuilder t st fs =>+ HasPtrWidth wptr =>+ HasLLVMAnn sym =>+ (1 <= ArchWidth arch) =>+ HasPtrWidth (ArchWidth arch) =>+ (?memOpts :: MemOptions) =>+ (?lc :: TypeContext) =>+ (?intrinsicsOpts :: IntrinsicsOptions) =>++ VerifierOptions ->+ CruxOptions ->+ [SolverAdapter st] ->+ CSettings ->+ CopilotLogRefs sym ->+ SimCtxt Crux sym LLVM ->+ L.Module ->+ ModuleTranslation arch ->+ GlobalVar Mem ->+ MemImpl sym ->+ CW4.BisimulationProofBundle sym ->+ IO ()+verifyStepBisimulation opts cruxOpts adapters csettings clRefs simctx llvmMod modTrans memVar mem prfbundle =+ withBackend simctx $ \bak ->+ do Log.sayCopilot $ Log.ComputingConditions Log.StepBisimulation++ frm <- pushAssumptionFrame bak++ do -- set up the memory image+ mem' <- setupPrestate bak mem prfbundle++ -- sanity check, verify that we set up the memory in the expected relation+ assertStateRelation bak clRefs mem' (CW4.preStreamState prfbundle)++ -- set up trigger guard global variables+ let halloc = simHandleAllocator simctx+ -- See Note [Global variables for trigger functions]+ let prepTrigger (nm, guard, _) =+ do gv <- freshGlobalVar halloc (Text.pack (nm ++ "_called")) NatRepr+ return (nm, gv, guard)+ triggerGlobals <- mapM prepTrigger (CW4.triggerState prfbundle)++ -- execute the step function+ let overrides = zipWith (triggerOverride clRefs) triggerGlobals (CW4.triggerState prfbundle)+ mem'' <- executeStep opts csettings clRefs simctx memVar mem' llvmMod modTrans triggerGlobals overrides (CW4.assumptions prfbundle) (CW4.sideConds prfbundle)++ -- assert the poststate is in the relation+ assertStateRelation bak clRefs mem'' (CW4.postStreamState prfbundle)++ popUntilAssumptionFrame bak frm++ Log.sayCopilot $ Log.ProvingConditions Log.StepBisimulation+ proveObls cruxOpts adapters clRefs Log.StepBisimulation simctx+++-- | Set up the "trigger override" functions. These dummy functions+-- take the place of the external functions called by the Copilot+-- monitor when a guarded condition occurs.+--+-- Each trigger statement has a corresponding global variable that+-- is used to record if the trigger function was called; initially+-- the global is false, and is set to true when the trigger function+-- is called. At the end of verification, we check that the value+-- of this global variable is true iff the corresponding trigger guard+-- condition is true.+--+-- The other function of the trigger overrides is to check that, when called,+-- the functions are given the expected argument values.+--+-- Otherwise, the override functions have no effects, which corresponds+-- to the assumption that the external environment makes no changes to the+-- program state that are observable to the Copilot monitor.+triggerOverride :: forall sym t arch msgs.+ IsSymInterface sym =>+ Log.Logs msgs =>+ Log.SupportsCopilotLogMessage msgs =>+ (?memOpts :: MemOptions) =>+ (?lc :: TypeContext) =>+ (?intrinsicsOpts :: IntrinsicsOptions) =>+ (1 <= ArchWidth arch) =>+ HasPtrWidth (ArchWidth arch) =>+ HasLLVMAnn sym =>++ CopilotLogRefs sym ->+ (Name, GlobalVar NatType, Pred sym) ->+ (Name, BoolExpr t, [(Some Type, CW4.XExpr sym)]) ->+ OverrideTemplate (Crux sym) sym arch (RegEntry sym Mem) EmptyCtx Mem+triggerOverride clRefs (_,triggerGlobal,_) (nm, _guard, args) =+ let args' = map toTypeRepr args in+ case Ctx.fromList args' of+ Some argCtx ->+ basic_llvm_override $+ LLVMOverride decl argCtx UnitRepr $+ \memOps bak calledArgs ->+ do let sym = backendGetSym bak+ modifyGlobal triggerGlobal $ \count -> do+ -- See Note [Global variables for trigger functions]+ countPlusOne <- liftIO $ do+ one <- natLit sym 1+ natAdd sym count one+ pure ((), countPlusOne)+ mem <- readGlobal memOps+ liftIO $ checkArgs bak mem (toListFC Some calledArgs) args+ return ()++ where+ decl = L.Declare+ { L.decLinkage = Nothing+ , L.decVisibility = Nothing+ , L.decRetType = L.PrimType L.Void+ , L.decName = L.Symbol nm+ , L.decArgs = map llvmArgTy args+ , L.decVarArgs = False+ , L.decAttrs = []+ , L.decComdat = Nothing+ }++ -- Use the `-CompositePtr` functions here to ensure that arguments with array+ -- or struct types are treated as pointers. See Note [Arrays and structs].+ toTypeRepr (Some ctp, _) = llvmTypeAsRepr (copilotTypeToMemTypeCompositePtr (llvmDataLayout ?lc) ctp) Some+ llvmArgTy (Some ctp, _) = copilotTypeToLLVMTypeCompositePtr ctp++ checkArgs :: forall bak. IsSymBackend sym bak =>+ bak -> MemImpl sym -> [Some (RegEntry sym)] -> [(Some Type, CW4.XExpr sym)] -> IO ()+ checkArgs bak mem = loop (0::Integer)+ where+ loop i (x:xs) ((ctp,v):vs) = checkArg bak mem i x ctp v >> loop (i+1) xs vs+ loop _ [] [] = return ()+ loop _ _ _ = fail $ "Argument list mismatch in " ++ nm++ checkArg :: forall bak. IsSymBackend sym bak =>+ bak -> MemImpl sym -> Integer -> Some (RegEntry sym) -> Some Type -> CW4.XExpr sym -> IO ()+ checkArg bak mem i (Some (RegEntry tp v)) (Some ctp) x =+ do let sym = backendGetSym bak+ eq <- computeEqualVals bak clRefs mem ctp x tp v+ (ann, eq') <- annotateTerm sym eq+ let shortmsg = "Trigger " ++ show nm ++ " argument " ++ show i+ let longmsg = show (printSymExpr eq')+ let rsn = AssertFailureSimError shortmsg longmsg+ loc <- getCurrentProgramLoc sym+ modifyIORef' (verifierAssertionMapRef clRefs)+ $ Map.insert (BoolAnn ann)+ $ Log.TriggerArgumentEqualityAssertion+ $ Log.SomeSome+ $ Log.TriggerArgumentEquality sym loc nm i ctp x tp v+ addDurableProofObligation bak (LabeledPred eq' (SimError loc rsn))+++-- | Actually execute the Crucible simulator on the generated "step" function.+-- This will record proof side-conditions into the symbolic backend, and+-- return the memory state corresponding to the function post-state.+--+-- This function will record side-conditions that arise from the semantics+-- of C itself (e.g., memory is accessed in bounds and signed arithmetic+-- doesn't overflow) as well as the conditions related to trigger functions.+executeStep :: forall sym arch msgs.+ IsSymInterface sym =>+ Log.Logs msgs =>+ Log.SupportsCruxLLVMLogMessage msgs =>+ Log.SupportsCopilotLogMessage msgs =>+ (?memOpts :: MemOptions) =>+ (?lc :: TypeContext) =>+ (?intrinsicsOpts :: IntrinsicsOptions) =>+ (1 <= ArchWidth arch) =>+ HasPtrWidth (ArchWidth arch) =>+ HasLLVMAnn sym =>++ VerifierOptions ->+ CSettings ->+ CopilotLogRefs sym ->+ SimCtxt Crux sym LLVM ->+ GlobalVar Mem ->+ MemImpl sym ->+ L.Module ->+ ModuleTranslation arch ->+ [(Name, GlobalVar NatType, Pred sym)] ->+ [OverrideTemplate (Crux sym) sym arch (RegEntry sym Mem) EmptyCtx Mem] ->+ [Pred sym] {- User-provided property assumptions -} ->+ [Pred sym] {- Side conditions related to partial operations -} ->+ IO (MemImpl sym)+executeStep opts csettings clRefs simctx memVar mem llvmmod modTrans triggerGlobals triggerOverrides assums sideConds =+ do globSt <- foldM setupTrigger (llvmGlobals memVar mem) triggerGlobals+ let initSt = InitialState simctx globSt defaultAbortHandler memRepr $+ runOverrideSim memRepr runStep+ res <- executeCrucible [] initSt+ case res of+ FinishedResult _ pr -> return (pr^.partialValue.gpValue.to regValue)+ AbortedResult _ abortRes -> fail $ show $ ppAbortedResult abortRes+ TimeoutResult{} -> fail "simulation timed out!"++ where+ -- See Note [Global variables for trigger functions]+ setupTrigger gs (_,gv,_) = do+ zero <- liftIO $ natLit sym 0+ pure $ insertGlobal gv zero gs+ llvm_ctx = modTrans ^. transContext+ stepName = cSettingsStepFunctionName csettings+ sym = simctx^.ctxSymInterface++ -- TODO, would be lovely to be able to do better than dummy positions for all these things+ -- so we can correspond assumptions and asserts back to the parts of the original spec that+ -- gave rise to them.+ dummyLoc = mkProgramLoc "<>" InternalPos++ assumeProperty b =+ withBackend simctx $ \bak ->+ addAssumption bak (GenericAssumption dummyLoc "Property assumption" b)++ assumeSideCond b =+ withBackend simctx $ \bak ->+ addAssumption bak (GenericAssumption dummyLoc "Side condition for partial operation" b)++ ppAbortedResult :: AbortedResult sym ext -> PP.Doc ann+ ppAbortedResult abortRes =+ case gatherReasons abortRes of+ reason :| [] -> reason+ reasons -> PP.vcat $ "Simulation aborted for multiple reasons."+ : NE.toList reasons++ gatherReasons :: AbortedResult sym ext -> NonEmpty (PP.Doc ann)+ gatherReasons (AbortedExec rsn _) =+ PP.vcat ["Simulation aborted!", ppAbortExecReason rsn] :| []+ gatherReasons (AbortedExit ec) =+ PP.vcat ["Simulation called exit!", PP.viaShow ec] :| []+ gatherReasons (AbortedBranch _ _ t f) =+ gatherReasons t <> gatherReasons f++ -- Simulator entry point+ runStep :: OverrideSim (Crux sym) sym LLVM (RegEntry sym Mem) EmptyCtx Mem (MemImpl sym)+ runStep =+ do -- set up built-in functions and trigger overrides+ register_llvm_overrides llvmmod [] triggerOverrides llvm_ctx+ -- set up functions defined in the module+ registerLazyModule sayTranslationWarning modTrans++ -- make any property assumptions+ liftIO (mapM_ assumeProperty assums)++ -- assume side conditions related to partial operations+ when (assumePartialSideConds opts) $ liftIO $+ mapM_ assumeSideCond sideConds++ -- look up and call the step function+ mbCfg <- liftIO $ getTranslatedCFG modTrans (L.Symbol stepName)+ () <- case mbCfg of+ Just (_, AnyCFG anyCfg, warns) -> do+ liftIO $ mapM_ sayTranslationWarning warns+ case (cfgArgTypes anyCfg, cfgReturnType anyCfg) of+ (Empty, UnitRepr) -> regValue <$> callCFG anyCfg emptyRegMap+ _ -> fail $ unwords [show stepName, "should take no arguments and return void"]+ Nothing -> fail $ unwords ["Could not find step function named", show stepName]++ -- Assert that the trigger functions were called exactly once iff the+ -- associated guard condition was true.+ -- See Note [Global variables for trigger functions].+ forM_ triggerGlobals $ \(nm, gv, guard) ->+ do expectedCount <- liftIO $ do+ one <- natLit sym 1+ zero <- natLit sym 0+ natIte sym guard one zero+ actualCount <- readGlobal gv+ eq <- liftIO $ natEq sym expectedCount actualCount+ (ann, eq') <- liftIO $ annotateTerm sym eq+ let shortmsg = "Trigger guard equality condition: " ++ show nm+ let longmsg = show (printSymExpr eq')+ let rsn = AssertFailureSimError shortmsg longmsg+ liftIO+ $ modifyIORef' (verifierAssertionMapRef clRefs)+ $ Map.insert (BoolAnn ann)+ $ Log.TriggersInvokedCorrespondinglyAssertion+ $ Log.TriggersInvokedCorrespondingly dummyLoc nm expectedCount actualCount+ withBackend simctx $ \bak ->+ liftIO $ addDurableProofObligation bak (LabeledPred eq' (SimError dummyLoc rsn))++ -- return the final state of the memory+ readGlobal memVar++-- | Given a bisimulation proof bundle and an empty initial state,+-- populate the global ring-buffer variables with abstract state+-- values, and write the abstract values of the external stream+-- values into their proper locations.+setupPrestate ::+ IsSymBackend sym bak =>+ HasPtrWidth wptr =>+ HasLLVMAnn sym =>+ (?memOpts :: MemOptions) =>+ (?lc :: TypeContext) =>++ bak ->+ MemImpl sym ->+ CW4.BisimulationProofBundle sym ->+ IO (MemImpl sym)+setupPrestate bak mem0 prfbundle =+ do mem' <- foldM setupStreamState mem0 (CW4.streamState (CW4.preStreamState prfbundle))+ foldM setupExternalInput mem' (CW4.externalInputs prfbundle)++ where+ sym = backendGetSym bak++ sizeTStorage :: StorageType+ sizeTStorage = bitvectorType (bitsToBytes (intValue ?ptrWidth))++ setupExternalInput mem (nm, Some ctp, v) =+ do -- Compute LLVM/Crucible type information from the Copilot type+ let memTy = copilotTypeToMemTypeBool8 (llvmDataLayout ?lc) ctp+ let typeAlign = memTypeAlign (llvmDataLayout ?lc) memTy+ stType <- toStorableType memTy+ Some typeRepr <- return (llvmTypeAsRepr memTy Some)++ -- resolve the global variable to a pointers+ ptrVal <- doResolveGlobal bak mem (L.Symbol nm)++ -- write the value into the global+ regVal <- copilotExprToRegValue sym v typeRepr+ doStore bak mem ptrVal typeRepr stType typeAlign regVal++ setupStreamState mem (nm, Some ctp, vs) =+ do -- TODO, should get these from somewhere inside copilot instead of building these names directly+ let idxName = "s" ++ show nm ++ "_idx"+ let bufName = "s" ++ show nm+ let buflen = genericLength vs :: Integer++ -- Compute LLVM/Crucible type information from the Copilot type+ let memTy = copilotTypeToMemTypeBool8 (llvmDataLayout ?lc) ctp+ let typeLen = memTypeSize (llvmDataLayout ?lc) memTy+ let typeAlign = memTypeAlign (llvmDataLayout ?lc) memTy+ stType <- toStorableType memTy+ Some typeRepr <- return (llvmTypeAsRepr memTy Some)++ -- Resolve the global names into base pointers+ idxPtr <- doResolveGlobal bak mem (L.Symbol idxName)+ bufPtr <- doResolveGlobal bak mem (L.Symbol bufName)++ -- Create a fresh index value in the proper range+ idxVal <- freshBoundedBV sym (safeSymbol idxName) ?ptrWidth+ (Just 0) (Just (fromIntegral (buflen - 1)))+ idxVal' <- llvmPointer_bv sym idxVal++ -- store the index value in the correct location+ let sizeTAlign = memTypeAlign (llvmDataLayout ?lc) (IntType (natValue ?ptrWidth))+ mem' <- doStore bak mem idxPtr (LLVMPointerRepr ?ptrWidth) sizeTStorage sizeTAlign idxVal'++ buflen' <- bvLit sym ?ptrWidth (BV.mkBV ?ptrWidth buflen)+ typeLen' <- bvLit sym ?ptrWidth (BV.mkBV ?ptrWidth (toInteger typeLen))++ -- Write each value of the stream ring buffer into its correct location+ flip execStateT mem' $+ forM_ (zip vs [0 ..]) $ \(v,i) ->+ do ptrVal <- lift $+ do x1 <- bvAdd sym idxVal =<< bvLit sym ?ptrWidth (BV.mkBV ?ptrWidth i)+ x2 <- bvUrem sym x1 buflen'+ x3 <- bvMul sym x2 typeLen'+ ptrAdd sym ?ptrWidth bufPtr x3++ regVal <- lift $ copilotExprToRegValue sym v typeRepr+ StateT $ \m ->+ do m' <- doStore bak m ptrVal typeRepr stType typeAlign regVal+ return ((),m')++-- | Given a memory image and a "proof state", assert that the global values+-- for each stream ring buffer have values that correspond to the given+-- stream state. This collection of assertions defines the bisimulation+-- relation.+assertStateRelation ::+ IsSymBackend sym bak =>+ Log.Logs msgs =>+ HasPtrWidth wptr =>+ HasLLVMAnn sym =>+ (?memOpts :: MemOptions) =>+ (?lc :: TypeContext) =>++ bak ->+ CopilotLogRefs sym ->+ MemImpl sym ->+ CW4.BisimulationProofState sym ->+ IO ()+assertStateRelation bak clRefs mem prfst =+ -- For each stream in the proof state, assert that the+ -- generated ring buffer global contains the corresponding+ -- values.+ forM_ (CW4.streamState prfst) assertStreamState++ where+ sym = backendGetSym bak++ sizeTStorage :: StorageType+ sizeTStorage = bitvectorType (bitsToBytes (intValue ?ptrWidth))++ assertStreamState (nm, Some ctp, vs) =+ do -- TODO, should get these from somewhere inside copilot instead of building these names directly+ let idxName = "s" ++ show nm ++ "_idx"+ let bufName = "s" ++ show nm+ let buflen = genericLength vs :: Integer++ -- Compute LLVM/Crucible type information from the Copilot type+ let memTy = copilotTypeToMemTypeBool8 (llvmDataLayout ?lc) ctp+ let typeLen = memTypeSize (llvmDataLayout ?lc) memTy+ let typeAlign = memTypeAlign (llvmDataLayout ?lc) memTy+ stType <- toStorableType memTy+ Some typeRepr <- return (llvmTypeAsRepr memTy Some)++ -- Resolve the global names into base pointers+ idxPtr <- doResolveGlobal bak mem (L.Symbol idxName)+ bufPtr <- doResolveGlobal bak mem (L.Symbol bufName)++ -- read the value of the ring buffer index+ let sizeTAlign = memTypeAlign (llvmDataLayout ?lc) (IntType (natValue ?ptrWidth))+ (bannIdxVal, pIdxVal, idxVal) <-+ doLoadWithAnn bak mem idxPtr sizeTStorage (LLVMPointerRepr ?ptrWidth) sizeTAlign+ idxVal' <- projectLLVM_bv bak idxVal+ locIdxVal <- getCurrentProgramLoc sym+ modifyIORef' (verifierAssertionMapRef clRefs)+ $ Map.insert bannIdxVal+ $ Log.RingBufferIndexLoadAssertion+ $ Log.RingBufferIndexLoad sym locIdxVal (Text.pack idxName) pIdxVal++ buflen' <- bvLit sym ?ptrWidth (BV.mkBV ?ptrWidth buflen)+ typeLen' <- bvLit sym ?ptrWidth (BV.mkBV ?ptrWidth (toInteger typeLen))++ -- For each value in the stream description, read a corresponding value from+ -- memory and assert that they are equal.+ forM_ (zip vs [0 ..]) $ \(v,i) ->+ do ptrVal <-+ do x1 <- bvAdd sym idxVal' =<< bvLit sym ?ptrWidth (BV.mkBV ?ptrWidth i)+ x2 <- bvUrem sym x1 buflen'+ x3 <- bvMul sym x2 typeLen'+ ptrAdd sym ?ptrWidth bufPtr x3++ (bannv', pv', v') <- doLoadWithAnn bak mem ptrVal stType typeRepr typeAlign+ locv' <- getCurrentProgramLoc sym+ let bufNameT = Text.pack bufName+ modifyIORef' (verifierAssertionMapRef clRefs)+ $ Map.insert bannv'+ $ Log.RingBufferLoadAssertion+ $ Log.SomeSome+ $ Log.RingBufferLoad sym locv' bufNameT i buflen ctp typeRepr pv'+ eq <- computeEqualVals bak clRefs mem ctp v typeRepr v'+ (ann, eq') <- annotateTerm sym eq+ let shortmsg = "State equality condition: " ++ show nm ++ " index value " ++ show i+ let longmsg = show (printSymExpr eq')+ let rsn = AssertFailureSimError shortmsg longmsg+ let loc = mkProgramLoc "<>" InternalPos+ modifyIORef' (verifierAssertionMapRef clRefs)+ $ Map.insert (BoolAnn ann)+ $ Log.StreamValueEqualityAssertion+ $ Log.SomeSome+ $ Log.StreamValueEquality sym loc bufNameT i buflen ctp v typeRepr v'+ addDurableProofObligation bak (LabeledPred eq' (SimError loc rsn))++ return ()++-- | Translate the @XExpr@ values from the "Copilot.Theorem.What4" module into+-- Crucible @RegValue@s suitable for injection into the Crucible simulator.+copilotExprToRegValue :: forall sym tp.+ IsSymInterface sym =>+ sym ->+ CW4.XExpr sym ->+ TypeRepr tp ->+ IO (RegValue sym tp)+copilotExprToRegValue sym = loop+ where+ loop :: forall tp'. CW4.XExpr sym -> TypeRepr tp' -> IO (RegValue sym tp')++ loop (CW4.XBool b) (LLVMPointerRepr w) | Just Refl <- testEquality w (knownNat @1) =+ llvmPointer_bv sym =<< predToBV sym b knownRepr+ loop (CW4.XBool b) (LLVMPointerRepr w) | Just Refl <- testEquality w (knownNat @8) =+ llvmPointer_bv sym =<< predToBV sym b knownRepr+ loop (CW4.XInt8 x) (LLVMPointerRepr w) | Just Refl <- testEquality w (knownNat @8) =+ llvmPointer_bv sym x+ loop (CW4.XInt16 x) (LLVMPointerRepr w) | Just Refl <- testEquality w (knownNat @16) =+ llvmPointer_bv sym x+ loop (CW4.XInt32 x) (LLVMPointerRepr w) | Just Refl <- testEquality w (knownNat @32) =+ llvmPointer_bv sym x+ loop (CW4.XInt64 x) (LLVMPointerRepr w) | Just Refl <- testEquality w (knownNat @64) =+ llvmPointer_bv sym x+ loop (CW4.XWord8 x) (LLVMPointerRepr w) | Just Refl <- testEquality w (knownNat @8) =+ llvmPointer_bv sym x+ loop (CW4.XWord16 x) (LLVMPointerRepr w) | Just Refl <- testEquality w (knownNat @16) =+ llvmPointer_bv sym x+ loop (CW4.XWord32 x) (LLVMPointerRepr w) | Just Refl <- testEquality w (knownNat @32) =+ llvmPointer_bv sym x+ loop (CW4.XWord64 x) (LLVMPointerRepr w) | Just Refl <- testEquality w (knownNat @64) =+ llvmPointer_bv sym x++ loop (CW4.XFloat x) (FloatRepr SingleFloatRepr) = return x+ loop (CW4.XDouble x) (FloatRepr DoubleFloatRepr) = return x++ loop CW4.XEmptyArray (VectorRepr _tpr) =+ pure V.empty+ loop (CW4.XArray xs) (VectorRepr tpr) =+ V.generateM (PVec.lengthInt xs) (\i -> loop (PVec.elemAtUnsafe i xs) tpr)+ loop (CW4.XStruct xs) (StructRepr ctx) =+ Ctx.traverseWithIndex+ (\i tpr -> RV <$> loop (xs !! Ctx.indexVal i) tpr)+ ctx++ loop x tpr =+ fail $ unlines ["Mismatch between Copilot value and crucible value", show x, show tpr]+++-- | Given an @XExpr@ from from the "Copilot.Theorem.What4" module, and+-- a Crucible @RegValue@ which is expected to match, compute an equality+-- predicate between the values. The Crucible values may be pointers,+-- requiring us to resolve the indirection through memory; this is necessary+-- for array and struct values, but would also work for scalars.+computeEqualVals :: forall sym bak tp a wptr.+ IsSymBackend sym bak =>+ HasPtrWidth wptr =>+ HasLLVMAnn sym =>+ (?lc :: TypeContext) =>+ (?memOpts :: MemOptions) =>+ bak ->+ CopilotLogRefs sym ->+ MemImpl sym ->+ Type a ->+ CW4.XExpr sym ->+ TypeRepr tp ->+ RegValue sym tp ->+ IO (Pred sym)+computeEqualVals bak clRefs mem = loop+ where+ sym = backendGetSym bak++ loop :: forall tp' a'. Type a' -> CW4.XExpr sym -> TypeRepr tp' -> RegValue sym tp' -> IO (Pred sym)+ loop Bool (CW4.XBool b) (LLVMPointerRepr w) v | Just Refl <- testEquality w (knownNat @1) =+ isEq sym b =<< bvIsNonzero sym =<< projectLLVM_bv bak v+ loop Bool (CW4.XBool b) (LLVMPointerRepr w) v | Just Refl <- testEquality w (knownNat @8) =+ isEq sym b =<< bvIsNonzero sym =<< projectLLVM_bv bak v+ loop Int8 (CW4.XInt8 x) (LLVMPointerRepr w) v | Just Refl <- testEquality w (knownNat @8) =+ bvEq sym x =<< projectLLVM_bv bak v+ loop Int16 (CW4.XInt16 x) (LLVMPointerRepr w) v | Just Refl <- testEquality w (knownNat @16) =+ bvEq sym x =<< projectLLVM_bv bak v+ loop Int32 (CW4.XInt32 x) (LLVMPointerRepr w) v | Just Refl <- testEquality w (knownNat @32) =+ bvEq sym x =<< projectLLVM_bv bak v+ loop Int64 (CW4.XInt64 x) (LLVMPointerRepr w) v | Just Refl <- testEquality w (knownNat @64) =+ bvEq sym x =<< projectLLVM_bv bak v+ loop Word8 (CW4.XWord8 x) (LLVMPointerRepr w) v | Just Refl <- testEquality w (knownNat @8) =+ bvEq sym x =<< projectLLVM_bv bak v+ loop Word16 (CW4.XWord16 x) (LLVMPointerRepr w) v | Just Refl <- testEquality w (knownNat @16) =+ bvEq sym x =<< projectLLVM_bv bak v+ loop Word32 (CW4.XWord32 x) (LLVMPointerRepr w) v | Just Refl <- testEquality w (knownNat @32) =+ bvEq sym x =<< projectLLVM_bv bak v+ loop Word64 (CW4.XWord64 x) (LLVMPointerRepr w) v | Just Refl <- testEquality w (knownNat @64) =+ bvEq sym x =<< projectLLVM_bv bak v++ loop Float (CW4.XFloat x) (FloatRepr SingleFloatRepr) v = iFloatEq @_ @SingleFloat sym x v+ loop Double (CW4.XDouble x) (FloatRepr DoubleFloatRepr) v = iFloatEq @_ @DoubleFloat sym x v++ loop (Array _ctp) CW4.XEmptyArray (VectorRepr _tpr) vs =+ pure $ backendPred sym $ V.null vs+ loop (Array ctp) (CW4.XArray xs) (VectorRepr tpr) vs+ | PVec.lengthInt xs == V.length vs+ = V.ifoldM (\pAcc i v -> andPred sym pAcc =<< loop ctp (PVec.elemAtUnsafe i xs) tpr v)+ (truePred sym) vs+ | otherwise+ = pure (falsePred sym)+ loop (Struct struct) (CW4.XStruct xs) (StructRepr ctx) vs+ | length copilotVals == Ctx.sizeInt (Ctx.size vs)+ = ifoldlMFC (\i pAcc tpr ->+ case copilotVals !! Ctx.indexVal i of+ (Value ctp _, x) ->+ andPred sym pAcc =<< loop ctp x tpr (unRV (vs Ctx.! i)))+ (truePred sym) ctx+ | otherwise+ = pure (falsePred sym)+ where+ copilotVals :: [(Value a', CW4.XExpr sym)]+ copilotVals = zip (toValues struct) xs++ -- If we encounter a pointer, read the memory that it points to and recurse,+ -- using the Copilot type as a guide for how much memory to read. This is+ -- needed to make array- or struct-typed arguments work (see+ -- Note [Arrays and structs]), although there is nothing about this code+ -- that is array- or struct-specific. In fact, this code could also work+ -- for pointer arguments of any other type.+ loop ctp x PtrRepr v =+ do let memTy = copilotTypeToMemTypeBool8 (llvmDataLayout ?lc) ctp+ typeAlign = memTypeAlign (llvmDataLayout ?lc) memTy+ stp <- toStorableType memTy+ llvmTypeAsRepr memTy $ \tpr ->+ do loc <- getCurrentProgramLoc sym+ (bann, p, regVal) <- doLoadWithAnn bak mem v stp tpr typeAlign+ modifyIORef' (verifierAssertionMapRef clRefs)+ $ Map.insert bann+ $ Log.PointerArgumentLoadAssertion+ $ Log.SomeSome+ $ Log.PointerArgumentLoad sym loc ctp tpr p+ loop ctp x tpr regVal++ loop _ctp x tpr _v =+ fail $ unlines ["Mismatch between Copilot value and crucible value", show x, show tpr]++-- | Convert a Copilot 'CT.Type' to a Crucible 'MemType'. 'CT.Bool's are+-- assumed to be one bit in size. See @Note [How LLVM represents bool]@.+copilotTypeToMemType ::+ DataLayout ->+ CT.Type a ->+ MemType+copilotTypeToMemType dl = loop+ where+ loop :: forall t. CT.Type t -> MemType+ loop CT.Bool = i1+ loop CT.Int8 = i8+ loop CT.Int16 = i16+ loop CT.Int32 = i32+ loop CT.Int64 = i64+ loop CT.Word8 = i8+ loop CT.Word16 = i16+ loop CT.Word32 = i32+ loop CT.Word64 = i64+ loop CT.Float = FloatType+ loop CT.Double = DoubleType+ loop t0@(CT.Array tp) =+ let len = fromIntegral (typeLength t0) in+ ArrayType len (copilotTypeToMemTypeBool8 dl tp)+ loop (CT.Struct v) =+ StructType (mkStructInfo dl False (map val (CT.toValues v)))++ val :: forall t. CT.Value t -> MemType+ val (CT.Value tp _) = copilotTypeToMemTypeBool8 dl tp++-- | Like 'copilotTypeToMemType', except that 'CT.Bool's are assumed to be+-- eight bits, not one bit. See @Note [How LLVM represents bool]@.+copilotTypeToMemTypeBool8 ::+ DataLayout ->+ CT.Type a ->+ MemType+copilotTypeToMemTypeBool8 _dl CT.Bool = i8+copilotTypeToMemTypeBool8 dl tp = copilotTypeToMemType dl tp++-- | Like 'copilotTypeToMemType', except that composite types (i.e.,+-- 'CT.Array's and 'CT.Struct's) are converted to 'PtrType's instead of direct+-- 'ArrayType's or 'StructType's. See @Note [Arrays and structs]@.+copilotTypeToMemTypeCompositePtr ::+ DataLayout ->+ CT.Type a ->+ MemType+copilotTypeToMemTypeCompositePtr dl (CT.Array tp) =+ PtrType (MemType (copilotTypeToMemTypeBool8 dl tp))+copilotTypeToMemTypeCompositePtr _dl (CT.Struct struct) =+ PtrType (Alias (copilotStructIdent struct))+copilotTypeToMemTypeCompositePtr dl tp = copilotTypeToMemType dl tp++-- | Convert a Copilot 'CT.Type' to an LLVM 'L.Type'. 'CT.Bool's are+-- assumed to be one bit in size. See @Note [How LLVM represents bool]@.+copilotTypeToLLVMType ::+ CT.Type a ->+ L.Type+copilotTypeToLLVMType = loop+ where+ loop :: forall t. CT.Type t -> L.Type+ loop CT.Bool = L.PrimType (L.Integer 1)+ loop CT.Int8 = L.PrimType (L.Integer 8)+ loop CT.Int16 = L.PrimType (L.Integer 16)+ loop CT.Int32 = L.PrimType (L.Integer 32)+ loop CT.Int64 = L.PrimType (L.Integer 64)+ loop CT.Word8 = L.PrimType (L.Integer 8)+ loop CT.Word16 = L.PrimType (L.Integer 16)+ loop CT.Word32 = L.PrimType (L.Integer 32)+ loop CT.Word64 = L.PrimType (L.Integer 64)+ loop CT.Float = L.PrimType (L.FloatType L.Float)+ loop CT.Double = L.PrimType (L.FloatType L.Double)+ loop t0@(CT.Array tp) =+ let len = fromIntegral (typeLength t0) in+ L.Array len (copilotTypeToLLVMTypeBool8 tp)+ loop (CT.Struct v) =+ -- NB: Don't use L.Struct here. That represents a literal, unnamed+ -- struct, but all of the structs used in a copilot-c99 program are+ -- named structs. As such, we must identify the struct by its alias.+ L.Alias (copilotStructIdent v)++-- | Like 'copilotTypeToLLVMType', except that 'CT.Bool's are assumed to be+-- eight bits, not one bit. See @Note [How LLVM represents bool]@.+copilotTypeToLLVMTypeBool8 ::+ CT.Type a ->+ L.Type+copilotTypeToLLVMTypeBool8 CT.Bool = L.PrimType (L.Integer 8)+copilotTypeToLLVMTypeBool8 tp = copilotTypeToLLVMType tp++-- | Like 'copilotTypeToLLVMType', except that composite types (i.e.,+-- 'CT.Array's and 'CT.Struct's) are given special treatment involving+-- pointers. See @Note [Arrays and structs]@.+copilotTypeToLLVMTypeCompositePtr ::+ CT.Type a ->+ L.Type+copilotTypeToLLVMTypeCompositePtr (CT.Array tp) =+ L.PtrTo (copilotTypeToLLVMTypeBool8 tp)+copilotTypeToLLVMTypeCompositePtr (CT.Struct struct) =+ L.PtrTo (L.Alias (copilotStructIdent struct))+copilotTypeToLLVMTypeCompositePtr tp = copilotTypeToLLVMType tp++-- | Given a struct @s@, construct the name @struct.s@ as an LLVM identifier.+copilotStructIdent :: Struct a => a -> L.Ident+copilotStructIdent struct = L.Ident $ "struct." ++ typeName struct++{-+Note [How LLVM represents bool]+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+How are C values of type `bool` represented in LLVM? It depends. If it's being+stored directly a `bool`, it's represented with `i1` (i.e., a single bit). If+a `bool` is a member of some composite type, such as a pointer, array, or+struct, however, it's representing with `i8` (i.e., eight bits). This means+that we have to be careful when converting Bool-typed Copilot values, as they+can become `i1` or `i8` depending on the context.++copilot-verifier handles this by having both `copilotTypeToLLVMType` and+`copilotTypeToLLVMTypeBool8` functions. The former function treats `bool`s as+`i1`, whereas the latter treats `bool`s as `i8`. The former is used when+converting "top-level" types (e.g., the argument types in a trigger override),+whereas the latter is used when converting types that are part of a larger+composite type (e.g., the element type in an array).++The story for the `copilotTypeToMemType` and `copilotTypeToMemTypeBool8`+functions is similar.++Note [Arrays and structs]+~~~~~~~~~~~~~~~~~~~~~~~~~+When Clang compiles a function with an array argument, such as this trigger+function:++ void func(int32_t func_arg0[2]) { ... }++It will produce the following LLVM code:++ declare void @func(i32*) { ... }++Note that the argument is an i32*, not a [2 x i32]. As a result, we can't+translate Copilot array types directly to LLVM array types when they're used as+arguments to a function. This impedance mismatch is handled in two places:++1. The `copilotTypeToMemTypeCompositePtr`/`copilotTypeToLLVMTypeCompositePtr`+ functions special-case Copilot arrays such that they are translated to+ pointers. These functions are used when declaring the argument types of an+ override for a trigger function (see `triggerOverride`).+2. The `computeEqualVals` function has a special case for pointer+ arguments—see the case that matches on `PtrRepr`. When a `PtrRepr` is+ encountered, the underlying array values that it points to are read from+ memory. Because `PtrRepr` doesn't record the type of the thing being pointed+ to, `computeEqualVals` uses the corresponding Copilot type as a guide to+ determine how much memory to read and at what type the memory should be+ used. After this, `computeEqualVals` reads from the read array+ element-by-element—see the `VectorRepr` cases.++ Note that unlike `computeEqualVals`, `copilotExprToRegValue` does not need+ a `PtrRepr` case. This is because `copilotExprToRegValue` is ultimately used+ in service of calling writing elements of streams to memory, and streams do+ not store pointer values (at least, not in today's Copilot).++There is a very similar story for structs. Copilot passes structs by reference+in trigger functions (e.g., `void trigger(struct s *ss)`), so we must also load+from a `PtrRepr` in `computeEqualVals` to handle structs.++See Note [Global variables for trigger functions]+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+As part of verifying that the behavior of a Copilot specification's trigger+functions behave the same way as the trigger functions in the corresponding C+program, we check that each trigger function in the C program is invoked the+appropriate number of times. That is, if the guard condition for a trigger is+true, the C trigger function should be invoked exactly once, and if the guard+condition is false, then the trigger function should not be invoked at all.++To check this, we create a Nat-valued global variable for each trigger function+and initialize it to zero. Whenever we simulate a trigger function, we increment+the value of the corresponding global variable. At the end of simulation, we+check that the value in each global variable is equal to+`if guard_cond then 1 else 0`.+-}++-- | Like @crucible-llvm@'s @doLoad@, but also returning the 'BoolAnn' and+-- 'Pred' asserting the validity of the load.+doLoadWithAnn ::+ ( IsSymBackend sym bak, HasPtrWidth wptr, HasLLVMAnn sym+ , ?memOpts :: MemOptions ) =>+ bak ->+ MemImpl sym ->+ LLVMPtr sym wptr {- ^ pointer to load from -} ->+ StorageType {- ^ type of value to load -} ->+ TypeRepr tp {- ^ crucible type of the result -} ->+ Alignment {- ^ assumed pointer alignment -} ->+ IO (BoolAnn sym, Pred sym, RegValue sym tp)+doLoadWithAnn bak mem ptr valType tpr alignment = do+ partLLVMVal <- loadRaw sym mem ptr valType alignment+ (bann, p, llvmVal) <- assertSafeWithAnn bak partLLVMVal+ regVal <- unpackMemValue sym tpr llvmVal+ pure (bann, p, regVal)+ where+ sym = backendGetSym bak++-- | Like @crucible-llvm@'s @assertSafe@, but also returning the 'BoolAnn' and+-- 'Pred' corresponding the assertion.+assertSafeWithAnn ::+ IsSymBackend sym bak =>+ bak ->+ PartLLVMVal sym ->+ IO (BoolAnn sym, Pred sym, LLVMVal sym)+assertSafeWithAnn bak partVal =+ case partVal of+ NoErr p v -> do+ (ann, p') <- annotateTerm sym p+ assert bak p' rsn+ return (BoolAnn ann, p', v)+ Err p -> do+ loc <- getCurrentProgramLoc sym+ let err = SimError loc rsn+ (_ann, p') <- annotateTerm sym p+ addProofObligation bak (LabeledPred p' err)+ abortExecBecause (AssertionFailure err)+ where+ rsn = AssertFailureSimError "Error during memory load" ""+ sym = backendGetSym bak++-- | Given a simulator state, extract any collected proof obligations,+-- attempt to prove them, and present the results to the user.+--+-- Afterward, the simulator state will be cleared of any proof obligations,+-- regardless of if they could all be proved.+proveObls ::+ IsSymInterface sym =>+ sym ~ ExprBuilder t st fs =>+ Log.Logs msgs =>+ Log.SupportsCruxLogMessage msgs =>+ Log.SupportsCopilotLogMessage msgs =>+ CruxOptions ->+ [SolverAdapter st] ->+ CopilotLogRefs sym ->+ Log.VerificationStep ->+ SimCtxt Crux sym LLVM ->+ IO ()+proveObls cruxOpts adapters clRefs step simctx =+ withBackend simctx $ \bak ->+ do let sym = backendGetSym bak+ obls <- getProofObligations bak+ clearProofObligations bak++-- mapM_ (print . ppSimError) (summarizeObls sym obls)++ vaMap <- readIORef $ verifierAssertionMapRef clRefs+ let laMapRef = llvmAnnMapRef clRefs+ laMap <- readIORef laMapRef+ results <- proveGoalsOffline adapters cruxOpts simctx (explainFailure sym laMapRef) obls+ presentResults sym vaMap laMap step results++{-+summarizeObls :: sym -> ProofObligations sym -> [SimError]+summarizeObls _ Nothing = []+summarizeObls _ (Just obls) = map (view labeledPredMsg . proofGoal) (goalsToList obls)+-}++presentResults ::+ Log.Logs msgs =>+ Log.SupportsCopilotLogMessage msgs =>+ IsSymInterface sym =>+ sym ->+ Map.Map (BoolAnn sym) (Log.VerifierAssertion sym) ->+ LLVMAnnMap sym ->+ Log.VerificationStep ->+ (ProcessedGoals, Maybe (Goals (Assumptions sym) (Assertion sym, [ProgramLoc], ProofResult sym))) ->+ IO ()+presentResults sym vaMap laMap step (num, goals)+ | numTotalGoals == 0+ = Log.sayCopilot Log.AllGoalsProved++ -- All goals were proven+ | numProvedGoals == numTotalGoals+ = do traverse_ (logVerifierAssertions sym vaMap laMap step num) goals+ printGoals++ -- There were some unproved goals, so fail with exit code 1+ | otherwise+ = do printGoals+ exitFailure+ where+ numTotalGoals = totalProcessedGoals num+ numProvedGoals = provedGoals num++ printGoals =+ do Log.sayCopilot $ Log.OnlySomeGoalsProved numProvedGoals numTotalGoals+ goals' <- provedGoalsTree sym goals+ case goals' of+ Just g -> Log.logGoal g+ Nothing -> return ()++-- | Upon a successful verification, log the various assertions that the+-- verifier makes. These assertions will be visible in the output if the+-- 'verbosity' is set to 'Noisy'.+logVerifierAssertions ::+ forall sym msgs.+ IsSymInterface sym =>+ Log.Logs msgs =>+ Log.SupportsCopilotLogMessage msgs =>+ sym ->+ Map.Map (BoolAnn sym) (Log.VerifierAssertion sym) ->+ LLVMAnnMap sym ->+ Log.VerificationStep ->+ ProcessedGoals ->+ Goals (Assumptions sym) (Assertion sym, [ProgramLoc], ProofResult sym) ->+ IO ()+logVerifierAssertions sym vaMap laMap step num goals = void $ go 0 goals+ where+ numTotalGoals = totalProcessedGoals num++ go :: Integer ->+ Goals (Assumptions sym) (Assertion sym, [ProgramLoc], ProofResult sym) ->+ IO Integer+ go goalIdx gs =+ case gs of+ Assuming _ gs' ->+ go goalIdx gs'++ Prove (gl, locs, _) -> do+ let p = gl^.labeledPred+ nearestLoc = nearestProgramLoc locs++ -- First, obtain the verifier assertion.+ va <- case getAnnotation sym p of+ -- If the assertion has a BoolAnn, look it up in the assertion maps+ -- that we have accumulated during verification.+ Just ann+ | Just va <- Map.lookup (BoolAnn ann) vaMap+ -> pure va+ | Just (stk, bb) <- Map.lookup (BoolAnn ann) laMap+ -> pure $ Log.LLVMBadBehaviorCheckAssertion+ $ Log.LLVMBadBehaviorCheck sym nearestLoc stk bb p+ | otherwise+ -> fail $ unlines+ [ "Cannot find BoolAnn for assertion"+ , show $ gl^.labeledPredMsg+ , show $ printSymExpr p+ ]+ -- If the assertion does not have a BoolAnn, fall back to using+ -- heuristics to guess what kind of assertion it is.+ Nothing -> pure $ verifierAssertionHeuristics sym nearestLoc p++ -- Log the assertion.+ case va of+ Log.StreamValueEqualityAssertion (Log.SomeSome a) ->+ Log.sayCopilot $+ Log.StreamValueEqualityProofGoal step goalIdx numTotalGoals a+ Log.TriggersInvokedCorrespondinglyAssertion a ->+ Log.sayCopilot $+ Log.TriggersInvokedCorrespondinglyProofGoal step goalIdx numTotalGoals a+ Log.TriggerArgumentEqualityAssertion (Log.SomeSome a) ->+ Log.sayCopilot $+ Log.TriggerArgumentEqualityProofGoal step goalIdx numTotalGoals a+ Log.RingBufferLoadAssertion (Log.SomeSome a) ->+ Log.sayCopilot $+ Log.RingBufferLoadProofGoal step goalIdx numTotalGoals a+ Log.RingBufferIndexLoadAssertion a ->+ Log.sayCopilot $+ Log.RingBufferIndexLoadProofGoal step goalIdx numTotalGoals a+ Log.PointerArgumentLoadAssertion (Log.SomeSome a) ->+ Log.sayCopilot $+ Log.PointerArgumentLoadProofGoal step goalIdx numTotalGoals a+ Log.AccessorFunctionLoadAssertion a ->+ Log.sayCopilot $+ Log.AccessorFunctionLoadProofGoal step goalIdx numTotalGoals a+ Log.GuardFunctionLoadAssertion a ->+ Log.sayCopilot $+ Log.GuardFunctionLoadProofGoal step goalIdx numTotalGoals a+ Log.UnknownFunctionLoadAssertion a ->+ Log.sayCopilot $+ Log.UnknownFunctionLoadProofGoal step goalIdx numTotalGoals a+ Log.LLVMBadBehaviorCheckAssertion a ->+ Log.sayCopilot $+ Log.LLVMBadBehaviorCheckProofGoal step goalIdx numTotalGoals a++ -- Finally, return the current goal index.+ pure goalIdx++ ProveConj gs1 gs2 -> do+ goalIdx' <- go goalIdx gs1+ go (goalIdx' + 1) gs2++-- | If a verifier assertion does not have a corresponding 'BoolAnn', then we+-- must use heuristics to guess what kind of assertion it is. These heuristics+-- are not perfect, and we fall back to 'Log.UnknownFunctionLoad' in the event+-- that we cannot figure out a more obvious cause for the assertion.+verifierAssertionHeuristics ::+ IsSymInterface sym =>+ sym ->+ ProgramLoc ->+ Pred sym ->+ Log.VerifierAssertion sym+verifierAssertionHeuristics sym loc p+ | "_get" `Text.isSuffixOf` functionName funName+ = Log.AccessorFunctionLoadAssertion $+ Log.AccessorFunctionLoad sym loc funName p++ | "_guard" `Text.isSuffixOf` functionName funName+ = Log.GuardFunctionLoadAssertion $+ Log.GuardFunctionLoad sym loc funName p++ | otherwise+ = Log.UnknownFunctionLoadAssertion $+ Log.UnknownFunctionLoad sym loc funName p+ where+ funName = plFunction loc++-- | Pick the most recent 'ProgramLoc' in a trace of locations. If there are+-- no locations available, return a dummy location.+nearestProgramLoc :: [ProgramLoc] -> ProgramLoc+nearestProgramLoc locs =+ case locs of+ loc:_ -> loc+ _ -> mkProgramLoc "<>" InternalPos++-- | A collection of 'IORef's used to accumulate log messages that the verifier+-- may display at the end of verification.+data CopilotLogRefs sym = CopilotLogRefs+ { verifierAssertionMapRef :: !(IORef (Map.Map (BoolAnn sym) (Log.VerifierAssertion sym)))+ -- ^ A map of 'BoolAnn's (i.e., unique numbers) to 'Log.VerifierAssertions'.+ , llvmAnnMapRef :: !(IORef (LLVMAnnMap sym))+ -- ^ A map of 'BoolAnn's (i.e., unique numbers) to assertions about checks+ -- for bad behavior in LLVM.++ -- This is kept in a separate 'IORef' for technical reasons, as+ -- @crucible-llvm@'s 'explainFailure' function expects this 'IORef' as an+ -- argument. We could put everything into 'verifierAssertionMapRef', but+ -- that would require some tiresome 'IORef' massaging to make work.+ }++-- | Create a new 'CopilotLogRefs' value.+newCopilotLogRefs :: IsSymInterface sym => IO (CopilotLogRefs sym)+newCopilotLogRefs = do+ vaMapRef <- newIORef mempty+ laMapRef <- newIORef mempty+ pure $ CopilotLogRefs+ { verifierAssertionMapRef = vaMapRef+ , llvmAnnMapRef = laMapRef+ }++data CopilotLogging+ = LoggingCrux Log.CruxLogMessage+ | LoggingCruxLLVM Log.CruxLLVMLogMessage+ | LoggingCopilot Log.CopilotLogMessage+ deriving stock Generic+ deriving anyclass ToJSON++copilotLoggingToSayWhat :: CopilotLogging -> Log.SayWhat+copilotLoggingToSayWhat (LoggingCrux msg) = Log.cruxLogMessageToSayWhat msg+copilotLoggingToSayWhat (LoggingCruxLLVM msg) = Log.cruxLLVMLogMessageToSayWhat msg+copilotLoggingToSayWhat (LoggingCopilot msg) = Log.copilotLogMessageToSayWhat msg++withCopilotLogging ::+ ( ( Log.SupportsCruxLogMessage CopilotLogging+ , Log.SupportsCruxLLVMLogMessage CopilotLogging+ , Log.SupportsCopilotLogMessage CopilotLogging+ ) => computation ) ->+ computation+withCopilotLogging computation = do+ let ?injectCruxLogMessage = LoggingCrux+ ?injectCruxLLVMLogMessage = LoggingCruxLLVM+ ?injectCopilotLogMessage = LoggingCopilot+ in computation++sayTranslationWarning ::+ Log.Logs msgs =>+ Log.SupportsCruxLLVMLogMessage msgs =>+ LLVMTranslationWarning -> IO ()+sayTranslationWarning = Log.sayCruxLLVM . f+ where+ f (LLVMTranslationWarning s p msg) =+ Log.TranslationWarning (Text.pack (show (ppSymbol s))) (Text.pack (show p)) msg
+ src/Copilot/Verifier/Log.hs view
@@ -0,0 +1,729 @@+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE ImplicitParams #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE TemplateHaskell #-}+module Copilot.Verifier.Log+ ( SupportsCopilotLogMessage+ , CopilotLogMessage(..)+ , VerificationStep(..)+ , VerifierAssertion(..)+ , SomeSome(..)+ , StreamValueEquality(..)+ , TriggersInvokedCorrespondingly(..)+ , TriggerArgumentEquality(..)+ , RingBufferLoad(..)+ , RingBufferIndexLoad(..)+ , PointerArgumentLoad(..)+ , AccessorFunctionLoad(..)+ , GuardFunctionLoad(..)+ , UnknownFunctionLoad(..)+ , LLVMBadBehaviorCheck(..)+ , sayCopilot+ , copilotLogMessageToSayWhat+ ) where++import Crux (SayLevel (..), SayWhat (..))+import qualified Crux.Log as Log+import Data.Aeson (ToJSON (..), Value (..))+import Data.Aeson.TH (defaultOptions, deriveToJSON)+import Data.Kind (Type)+import qualified Data.Parameterized.Context as Ctx+import qualified Data.Parameterized.Vector as PV+import qualified Data.Parameterized.TraversableFC.WithIndex as PWI+import Data.Text (Text)+import qualified Data.Text as T+import qualified Data.Vector as V+import GHC.Generics (Generic)+import qualified Prettyprinter as PP+import qualified Prettyprinter.Render.Text as PP++import qualified Copilot.Core.Expr as CE+import qualified Copilot.Core.Type as CT+import qualified Copilot.Theorem.What4 as CW4++import qualified Lang.Crucible.Simulator as LCS+import qualified Lang.Crucible.Types as LCT+import qualified Lang.Crucible.LLVM.Errors as LCLE+import qualified Lang.Crucible.LLVM.Errors.MemoryError as LCLEME+import qualified Lang.Crucible.LLVM.Errors.UndefinedBehavior as LCLEUB+import qualified Lang.Crucible.LLVM.MemModel as LCLM+import qualified Lang.Crucible.LLVM.MemModel.CallStack as LCLMCS+import qualified What4.FunctionName as WF+import qualified What4.Interface as WI+import qualified What4.ProgramLoc as WPL++data CopilotLogMessage where+ GeneratedCFile ::+ FilePath+ -- ^ The path of the generated C File+ -> CopilotLogMessage+ CompiledBitcodeFile ::+ String+ -- ^ The prefix to use in the compiled bitcode's directory+ -> FilePath+ -- ^ The name of the generated LLVM bitcode file+ -> CopilotLogMessage+ TranslatedToCrucible :: CopilotLogMessage+ GeneratingProofState :: CopilotLogMessage+ ComputingConditions :: VerificationStep -> CopilotLogMessage+ ProvingConditions :: VerificationStep -> CopilotLogMessage+ AllGoalsProved :: CopilotLogMessage+ OnlySomeGoalsProved ::+ Integer+ -- ^ Number of goals proved+ -> Integer+ -- ^ Number of total goals+ -> CopilotLogMessage++ -----+ -- Types of proof goals the verifier emits+ --+ -- The first three arguments to each constructor are:+ --+ -- * Which step of the verifier we are on+ -- * The current goal number (zero-indexed)+ -- * The total number of goals+ -----++ StreamValueEqualityProofGoal ::+ WI.IsSymExprBuilder sym+ => VerificationStep+ -> Integer+ -> Integer+ -> StreamValueEquality sym copilotType crucibleType+ -> CopilotLogMessage++ TriggersInvokedCorrespondinglyProofGoal ::+ WI.IsSymExprBuilder sym+ => VerificationStep+ -> Integer+ -> Integer+ -> TriggersInvokedCorrespondingly sym+ -> CopilotLogMessage++ TriggerArgumentEqualityProofGoal ::+ WI.IsSymExprBuilder sym+ => VerificationStep+ -> Integer+ -> Integer+ -> TriggerArgumentEquality sym copilotType crucibleType+ -> CopilotLogMessage++ RingBufferLoadProofGoal ::+ WI.IsSymExprBuilder sym+ => VerificationStep+ -> Integer+ -> Integer+ -> RingBufferLoad sym copilotType crucibleType+ -> CopilotLogMessage++ RingBufferIndexLoadProofGoal ::+ WI.IsSymExprBuilder sym+ => VerificationStep+ -> Integer+ -> Integer+ -> RingBufferIndexLoad sym+ -> CopilotLogMessage++ PointerArgumentLoadProofGoal ::+ WI.IsSymExprBuilder sym+ => VerificationStep+ -> Integer+ -> Integer+ -> PointerArgumentLoad sym copilotType crucibleType+ -> CopilotLogMessage++ AccessorFunctionLoadProofGoal ::+ WI.IsSymExprBuilder sym+ => VerificationStep+ -> Integer+ -> Integer+ -> AccessorFunctionLoad sym+ -> CopilotLogMessage++ GuardFunctionLoadProofGoal ::+ WI.IsSymExprBuilder sym+ => VerificationStep+ -> Integer+ -> Integer+ -> GuardFunctionLoad sym+ -> CopilotLogMessage++ UnknownFunctionLoadProofGoal ::+ WI.IsSymExprBuilder sym+ => VerificationStep+ -> Integer+ -> Integer+ -> UnknownFunctionLoad sym+ -> CopilotLogMessage++ LLVMBadBehaviorCheckProofGoal ::+ WI.IsSymExprBuilder sym+ => VerificationStep+ -> Integer+ -> Integer+ -> LLVMBadBehaviorCheck sym+ -> CopilotLogMessage++data VerificationStep+ = InitialState+ | StepBisimulation+ deriving stock Generic+ deriving anyclass ToJSON++-- | Types of assertions that the verifier can make, which will count towards+-- the total number of proof goals.+data VerifierAssertion sym+ = StreamValueEqualityAssertion (SomeSome (StreamValueEquality sym))+ | TriggersInvokedCorrespondinglyAssertion (TriggersInvokedCorrespondingly sym)+ | TriggerArgumentEqualityAssertion (SomeSome (TriggerArgumentEquality sym))+ | RingBufferLoadAssertion (SomeSome (RingBufferLoad sym))+ | RingBufferIndexLoadAssertion (RingBufferIndexLoad sym)+ | PointerArgumentLoadAssertion (SomeSome (PointerArgumentLoad sym))+ | AccessorFunctionLoadAssertion (AccessorFunctionLoad sym)+ | GuardFunctionLoadAssertion (GuardFunctionLoad sym)+ | UnknownFunctionLoadAssertion (UnknownFunctionLoad sym)+ | LLVMBadBehaviorCheckAssertion (LLVMBadBehaviorCheck sym)++-- | Like @Some@ in @parameterized-utils@, but existentially closing over two+-- type parameters instead of just one.+data SomeSome (f :: j -> k -> Type) where+ SomeSome :: f x y -> SomeSome f++-- | An assertion that an element in a Copilot stream is equal to the+-- corresponding element in a C ring buffer.+data StreamValueEquality sym copilotType crucibleType where+ StreamValueEquality ::+ sym+ -> WPL.ProgramLoc+ -- ^ The locations of the values+ -> Text+ -- ^ The name of the buffer+ -> Integer+ -- ^ The offset from the buffer's index, which is used to compute the+ -- element of the buffer to load+ -> Integer+ -- ^ The number of elements in the buffer+ -> CT.Type copilotType+ -- ^ The Copilot type+ -> CW4.XExpr sym+ -- ^ The Copilot value+ -> LCT.TypeRepr crucibleType+ -- ^ The Crucible type+ -> LCS.RegValue sym crucibleType+ -- ^ The Crucible value+ -> StreamValueEquality sym copilotType crucibleType++-- | An assertion that, given a Copilot trigger stream and its corresponding C+-- trigger function on a particular time step, either both fired at the same+-- time or both did not fire at all.+data TriggersInvokedCorrespondingly sym where+ TriggersInvokedCorrespondingly ::+ WPL.ProgramLoc+ -- ^ The location of the trigger+ -> CE.Name+ -- ^ The trigger name+ -> WI.SymNat sym+ -- ^ The expected number of times the trigger was fired this step+ -- (should be either 1 or 0).+ -> WI.SymNat sym+ -- ^ The actual number of times the trigger was fired this step.+ -> TriggersInvokedCorrespondingly sym++-- | An assertion that an argument to a Copilot trigger is equal to the+-- corresponding argument to a C trigger function.+data TriggerArgumentEquality sym copilotType crucibleType where+ TriggerArgumentEquality ::+ sym+ -> WPL.ProgramLoc+ -- ^ The locations of the arguments+ -> CE.Name+ -- ^ The trigger name+ -> Integer+ -- ^ The number of the argument (starting from 0)+ -> CT.Type copilotType+ -- ^ The Copilot type+ -> CW4.XExpr sym+ -- ^ The Copilot value+ -> LCT.TypeRepr crucibleType+ -- ^ The Crucible type+ -> LCS.RegValue sym crucibleType+ -- ^ The Crucible value+ -> TriggerArgumentEquality sym copilotType crucibleType++-- | An assertion that a load from a ring buffer in C is valid.+data RingBufferLoad sym copilotType crucibleType where+ RingBufferLoad ::+ sym+ -> WPL.ProgramLoc+ -- ^ The location of the trigger+ -> Text+ -- ^ The name of the buffer+ -> Integer+ -- ^ The offset from the buffer's index, which is used to compute the+ -- element of the buffer to load+ -> Integer+ -- ^ The number of elements in the buffer+ -> CT.Type copilotType+ -- ^ The Copilot type of the elements of the array+ -> LCT.TypeRepr crucibleType+ -- ^ The Crucible type of the elements of the array+ -> WI.Pred sym+ -- ^ The assertion that must hold in order for this load to be valid+ -> RingBufferLoad sym copilotType crucibleType++-- | An assertion that a load from a global variable representing a ring+-- buffer's index in C is valid.+data RingBufferIndexLoad sym where+ RingBufferIndexLoad ::+ sym+ -> WPL.ProgramLoc+ -- ^ The location of the trigger+ -> Text+ -- ^ The name of the global index+ -> WI.Pred sym+ -- ^ The assertion that must hold in order for this load to be valid+ -> RingBufferIndexLoad sym++-- | An assertion that a load from a pointer argument to a trigger function in C+-- is valid.+data PointerArgumentLoad sym copilotType crucibleType where+ PointerArgumentLoad ::+ sym+ -> WPL.ProgramLoc+ -- ^ The location of the pointer+ -> CT.Type copilotType+ -- ^ The Copilot type of the underlying memory+ -> LCT.TypeRepr crucibleType+ -- ^ The Crucible type of the underlying memory+ -> WI.Pred sym+ -- ^ The assertion that must hold in order for this load to be valid+ -> PointerArgumentLoad sym copilotType crucibleType++-- | An assertion that a load occurring from somewhere in a stream accessor+-- function in C (e.g., @s0_get@) is valid. This is a somewhat imprecise+-- assertion, as it doesn't identify /why/ the load occurs. (Most likely, it+-- happens because of an array index.)+data AccessorFunctionLoad sym where+ AccessorFunctionLoad ::+ sym+ -> WPL.ProgramLoc+ -- ^ The location of the accessor function+ -> WF.FunctionName+ -- ^ The name of the accessor function+ -> WI.Pred sym+ -- ^ The assertion that must hold in order for this load to be valid+ -> AccessorFunctionLoad sym++-- | An assertion that a load occurring from somewhere in a trigger guard+-- function in C (e.g., @even_guard@) is valid. This is a somewhat imprecise+-- assertion, as it doesn't identify /why/ the load occurs. (Most likely, it+-- happens because of an array index.)+data GuardFunctionLoad sym where+ GuardFunctionLoad ::+ sym+ -> WPL.ProgramLoc+ -- ^ The location of the guard function+ -> WF.FunctionName+ -- ^ The name of the guard function+ -> WI.Pred sym+ -- ^ The assertion that must hold in order for this load to be valid+ -> GuardFunctionLoad sym++-- | An assertion that a load occurring in some function is valid. If you+-- see this assertion, it is because the heuristics used to identify where+-- load-related assertions come from could not identify a more precise cause+-- for a load.+data UnknownFunctionLoad sym where+ UnknownFunctionLoad ::+ sym+ -> WPL.ProgramLoc+ -- ^ The location of the function+ -> WF.FunctionName+ -- ^ The name of the function+ -> WI.Pred sym+ -- ^ The assertion that must hold in order for this load to be valid+ -> UnknownFunctionLoad sym++-- | An assertion that checks that some form of bad behavior in LLVM does not+-- occur. Bad behavior includes both undefined behavior and memory unsafety.+data LLVMBadBehaviorCheck sym where+ LLVMBadBehaviorCheck ::+ sym+ -> WPL.ProgramLoc+ -- ^ The location of the check+ -> LCLMCS.CallStack+ -- ^ A call stack for the check, if one exists+ -> LCLE.BadBehavior sym+ -- ^ What type of LLVM bad behavior is being checked for+ -> WI.Pred sym+ -- ^ The assertion that must hold in order for this check to succeed+ -> LLVMBadBehaviorCheck sym++-- Silly ToJSON instances. Crux is only requiring a ToJSON constraint for+-- IDE-related functionality that we do not make use of, so the behavior of+-- these instances aren't very important.++instance ToJSON (StreamValueEquality sym copilotType crucibleType) where+ toJSON _ = String "StreamValueEquality"++instance ToJSON (TriggersInvokedCorrespondingly sym) where+ toJSON _ = String "TriggersInvokedCorrespondingly"++instance ToJSON (TriggerArgumentEquality sym copilotType crucibleType) where+ toJSON _ = String "TriggerArgumentEquality"++instance ToJSON (RingBufferLoad sym copilotType crucibleType) where+ toJSON _ = String "RingBufferLoad"++instance ToJSON (RingBufferIndexLoad sym) where+ toJSON _ = String "RingBufferIndexLoad"++instance ToJSON (PointerArgumentLoad sym copilotType crucibleType) where+ toJSON _ = String "PointerArgumentLoad"++instance ToJSON (AccessorFunctionLoad sym) where+ toJSON _ = String "AccessorFunctionLoad"++instance ToJSON (GuardFunctionLoad sym) where+ toJSON _ = String "GuardFunctionLoad"++instance ToJSON (UnknownFunctionLoad sym) where+ toJSON _ = String "UnknownFunctionLoad"++instance ToJSON (LLVMBadBehaviorCheck sym) where+ toJSON _ = String "LLVMBadBehaviorCheck"++type SupportsCopilotLogMessage msgs =+ (?injectCopilotLogMessage :: CopilotLogMessage -> msgs)++sayCopilot ::+ Log.Logs msgs =>+ SupportsCopilotLogMessage msgs =>+ CopilotLogMessage ->+ IO ()+sayCopilot msg =+ let ?injectMessage = ?injectCopilotLogMessage+ in Log.say msg++copilotTag :: Text+copilotTag = "copilot-verifier"++-- copilotFail :: Text -> SayWhat+-- copilotFail = SayWhat Fail copilotTag++copilotSimply :: Text -> SayWhat+copilotSimply = SayWhat Simply copilotTag++copilotNoisily :: Text -> SayWhat+copilotNoisily = SayWhat Noisily copilotTag++-- copilotWarn :: Text -> SayWhat+-- copilotWarn = SayWhat Warn copilotTag++copilotLogMessageToSayWhat :: CopilotLogMessage -> SayWhat+copilotLogMessageToSayWhat (GeneratedCFile csrc) =+ copilotSimply $ "Generated " <> T.pack (show csrc)+copilotLogMessageToSayWhat (CompiledBitcodeFile prefix bcFile) =+ copilotSimply $ "Compiled " <> T.pack prefix <> " into " <> T.pack bcFile+copilotLogMessageToSayWhat TranslatedToCrucible =+ copilotSimply "Translated bitcode into Crucible"+copilotLogMessageToSayWhat GeneratingProofState =+ copilotSimply "Generating proof state data"+copilotLogMessageToSayWhat (ComputingConditions step) =+ copilotSimply $ "Computing " <> describeVerificationStep step <> " verification conditions"+copilotLogMessageToSayWhat (ProvingConditions step) =+ copilotSimply $ "Proving " <> describeVerificationStep step <> " verification conditions"+copilotLogMessageToSayWhat AllGoalsProved =+ copilotSimply "All obligations proved by concrete simplification"+copilotLogMessageToSayWhat (OnlySomeGoalsProved numProvedGoals numTotalGoals) =+ copilotSimply $ T.unwords+ [ "Proved", T.pack (show numProvedGoals)+ , "of"+ , T.pack (show numTotalGoals), "total goals"+ ]+copilotLogMessageToSayWhat+ (StreamValueEqualityProofGoal step goalIdx numTotalGoals+ (StreamValueEquality+ sym loc+ bufName offset len+ copilotTy copilotVal+ crucibleTy crucibleVal)) =+ copilotNoisily $+ displayProofGoal+ step goalIdx numTotalGoals+ "asserting the equality between two stream values"+ [ renderStrict $ ppProgramLoc loc+ , "* Ring buffer name: " <> bufName+ , "* Offset into buffer (from current index): " <> T.pack (show offset)+ , "* Number of elements in buffer: " <> T.pack (show len)+ , "* Copilot type: " <> T.pack (showsCopilotType 0 copilotTy "")+ , "* Copilot value:"+ , renderStrict $ PP.indent 4 $ ppCopilotValue copilotVal+ , "* Crucible value:"+ , renderStrict $ PP.indent 4 $ ppCrucibleValue sym crucibleTy crucibleVal+ ]+copilotLogMessageToSayWhat+ (TriggersInvokedCorrespondinglyProofGoal step goalIdx numTotalGoals+ (TriggersInvokedCorrespondingly loc name expected actual)) =+ copilotNoisily $+ displayProofGoal+ step goalIdx numTotalGoals+ "asserting triggers fired in corresponding ways"+ [ renderStrict $ ppProgramLoc loc+ , "* Trigger name: " <> T.pack name+ , "* Expected number of times trigger was fired:"+ , renderStrict $ PP.indent 4 $ WI.printSymNat expected+ , "* Actual number of times trigger was fired:"+ , renderStrict $ PP.indent 4 $ WI.printSymNat actual+ ]+copilotLogMessageToSayWhat+ (TriggerArgumentEqualityProofGoal step goalIdx numTotalGoals+ (TriggerArgumentEquality+ sym loc+ triggerName argNum+ copilotTy copilotVal+ crucibleTy crucibleVal)) =+ copilotNoisily $+ displayProofGoal+ step goalIdx numTotalGoals+ "asserting the equality between two trigger arguments"+ [ renderStrict $ ppProgramLoc loc+ , "* Trigger name: " <> T.pack triggerName+ , "* Number of argument: " <> T.pack (show argNum)+ , "* Copilot type: " <> T.pack (showsCopilotType 0 copilotTy "")+ , "* Copilot value:"+ , renderStrict $ PP.indent 4 $ ppCopilotValue copilotVal+ , "* Crucible value:"+ , renderStrict $ PP.indent 4 $ ppCrucibleValue sym crucibleTy crucibleVal+ ]+copilotLogMessageToSayWhat+ (RingBufferLoadProofGoal step goalIdx numTotalGoals+ (RingBufferLoad+ _sym loc bufName offset len copilotTy _crucibleTy p)) =+ copilotNoisily $+ displayProofGoal+ step goalIdx numTotalGoals+ "asserting the validity of a memory load from a stream's ring buffer in C"+ [ renderStrict $ ppProgramLoc loc+ , "* Ring buffer name: " <> bufName+ , "* Offset into buffer (from current index): " <> T.pack (show offset)+ , "* Number of elements in buffer: " <> T.pack (show len)+ , "* Copilot type of buffer elements:" <> T.pack (showsCopilotType 0 copilotTy "")+ , "* Validity predicate:"+ , renderStrict $ PP.indent 4 $ WI.printSymExpr p+ ]+copilotLogMessageToSayWhat+ (RingBufferIndexLoadProofGoal step goalIdx numTotalGoals+ (RingBufferIndexLoad _sym loc idxName p)) =+ copilotNoisily $+ displayProofGoal+ step goalIdx numTotalGoals+ "asserting the validity of a memory load from the index to a stream's ring buffer in C"+ [ renderStrict $ ppProgramLoc loc+ , "* Ring buffer index name: " <> idxName+ , "* Validity predicate:"+ , renderStrict $ PP.indent 4 $ WI.printSymExpr p+ ]+copilotLogMessageToSayWhat+ (PointerArgumentLoadProofGoal step goalIdx numTotalGoals+ (PointerArgumentLoad+ _sym loc copilotTy _crucibleTy p)) =+ copilotNoisily $+ displayProofGoal+ step goalIdx numTotalGoals+ "asserting the validity of a memory load from a pointer argument to a trigger"+ [ renderStrict $ ppProgramLoc loc+ , "* Copilot type: " <> T.pack (showsCopilotType 0 copilotTy "")+ , "* Validity predicate:"+ , renderStrict $ PP.indent 4 $ WI.printSymExpr p+ ]+copilotLogMessageToSayWhat+ (AccessorFunctionLoadProofGoal step goalIdx numTotalGoals+ (AccessorFunctionLoad _sym loc accessorName p)) =+ copilotNoisily $+ displayProofGoal+ step goalIdx numTotalGoals+ "asserting the validity of a memory load from a stream accessor function"+ [ renderStrict $ ppProgramLoc loc+ , "* Accessor function name: " <> WF.functionName accessorName+ , "* Validity predicate:"+ , renderStrict $ PP.indent 4 $ WI.printSymExpr p+ ]+copilotLogMessageToSayWhat+ (GuardFunctionLoadProofGoal step goalIdx numTotalGoals+ (GuardFunctionLoad _sym loc accessorName p)) =+ copilotNoisily $+ displayProofGoal+ step goalIdx numTotalGoals+ "asserting the validity of a memory load from a trigger guard function"+ [ renderStrict $ ppProgramLoc loc+ , "* Guard function name: " <> WF.functionName accessorName+ , "* Validity predicate:"+ , renderStrict $ PP.indent 4 $ WI.printSymExpr p+ ]+copilotLogMessageToSayWhat+ (UnknownFunctionLoadProofGoal step goalIdx numTotalGoals+ (UnknownFunctionLoad _sym loc accessorName p)) =+ copilotNoisily $+ displayProofGoal+ step goalIdx numTotalGoals+ "asserting the validity of a memory load from an unknown function"+ [ renderStrict $ ppProgramLoc loc+ , "* Function name: " <> WF.functionName accessorName+ , "* Validity predicate:"+ , renderStrict $ PP.indent 4 $ WI.printSymExpr p+ ]+copilotLogMessageToSayWhat+ (LLVMBadBehaviorCheckProofGoal step goalIdx numTotalGoals+ (LLVMBadBehaviorCheck _sym loc stk bb p)) =+ let ppLoc = renderStrict $ ppProgramLoc loc+ ppCallStackLines =+ [ "* Call stack:"+ , " " <> renderCallStack stk+ ]+ ppValidPredLines =+ [ "* Validity predicate:"+ , renderStrict $ PP.indent 4 $ WI.printSymExpr p+ ] in+ case bb of+ LCLE.BBUndefinedBehavior ub ->+ copilotNoisily $+ displayProofGoal+ step goalIdx numTotalGoals+ "asserting that LLVM undefined behavior does not occur"+ $ ppLoc : ppCallStackLines +++ [ "* Undefined behavior description:"+ , renderStrict $ PP.indent 4 $ LCLEUB.ppDetails ub+ ] ++ ppValidPredLines+ LCLE.BBMemoryError me ->+ copilotNoisily $+ displayProofGoal+ step goalIdx numTotalGoals+ "asserting that LLVM memory unsafety does not occur"+ $ ppLoc : ppCallStackLines +++ [ "* Memory unsafety description:"+ , renderStrict $ PP.indent 4 $ LCLEME.explain me+ ] ++ ppValidPredLines++describeVerificationStep :: VerificationStep -> Text+describeVerificationStep InitialState = "initial state"+describeVerificationStep StepBisimulation = "step bisimulation"++-- | Display information about an emitted proof goal.+displayProofGoal ::+ VerificationStep+ -> Integer+ -> Integer+ -> Text+ -> [Text]+ -> Text+displayProofGoal step goalIdx numTotalGoals why ls = T.unlines $+ [ banner+ , "Emitted a proof goal (" <> why <> ")"+ , " During the " <> displayStep+ , " Proof goal " <> T.pack (show goalIdx)+ <> " ("+ <> T.pack (show numTotalGoals)+ <> " total)"+ , ""+ ]+ ++ ls ++ [banner]+ where+ banner = "====="+ displayStep =+ case step of+ InitialState ->+ "initial bisimulation state step"+ StepBisimulation ->+ "transition step of bisimulation"++ppProgramLoc :: WPL.ProgramLoc -> PP.Doc a+ppProgramLoc pl = PP.vcat+ [ "* Function:" PP.<+> PP.pretty (WPL.plFunction pl)+ , " Position:" PP.<+> PP.pretty (WPL.plSourceLoc pl)+ ]++renderCallStack :: LCLMCS.CallStack -> Text+renderCallStack cs+ | T.null ppText+ = "<no call stack available>"+ | otherwise+ = ppText+ where+ ppText = renderStrict $ LCLMCS.ppCallStack cs++showsCopilotType :: Int -> CT.Type tp -> ShowS+showsCopilotType prec tp =+ case tp of+ CT.Bool -> showString "Bool"+ CT.Int8 -> showString "Int8"+ CT.Int16 -> showString "Int16"+ CT.Int32 -> showString "Int32"+ CT.Int64 -> showString "Int64"+ CT.Word8 -> showString "Word8"+ CT.Word16 -> showString "Word16"+ CT.Word32 -> showString "Word32"+ CT.Word64 -> showString "Word64"+ CT.Float -> showString "Float"+ CT.Double -> showString "Double"+ CT.Array t -> showParen (prec >= 11) $+ showString "Array" .+ showsPrec 11 (CT.typeSize tp) .+ showsCopilotType 11 t+ CT.Struct x -> showString $ CT.typeName x++ppCopilotValue :: WI.IsSymExprBuilder sym => CW4.XExpr sym -> PP.Doc a+ppCopilotValue val =+ case val of+ CW4.XBool b -> WI.printSymExpr b+ CW4.XInt8 i -> WI.printSymExpr i+ CW4.XInt16 i -> WI.printSymExpr i+ CW4.XInt32 i -> WI.printSymExpr i+ CW4.XInt64 i -> WI.printSymExpr i+ CW4.XWord8 w -> WI.printSymExpr w+ CW4.XWord16 w -> WI.printSymExpr w+ CW4.XWord32 w -> WI.printSymExpr w+ CW4.XWord64 w -> WI.printSymExpr w+ CW4.XFloat f -> WI.printSymExpr f+ CW4.XDouble d -> WI.printSymExpr d+ CW4.XEmptyArray -> "[]"+ CW4.XArray a -> ppBracesWith ppCopilotValue (PV.toList a)+ CW4.XStruct s -> ppBracketsWith ppCopilotValue s++ppCrucibleValue :: WI.IsSymExprBuilder sym+ => sym+ -> LCT.TypeRepr tp+ -> LCS.RegValue sym tp+ -> PP.Doc a+ppCrucibleValue sym tp val =+ case tp of+ LCLM.LLVMPointerRepr _ -> LCLM.ppPtr val+ LCT.FloatRepr _ -> WI.printSymExpr val+ LCT.VectorRepr tpr -> ppBracketsWith (ppCrucibleValue sym tpr) (V.toList val)+ LCT.StructRepr ctx -> withBraces $+ PWI.itoListFC (\i (LCS.RV v) -> ppCrucibleValue sym (ctx Ctx.! i) v) val+ _ -> error $ "ppCrucibleValue: Unsupported type: " ++ show tp++renderStrict :: PP.Doc a -> Text+renderStrict = PP.renderStrict . PP.layoutPretty PP.defaultLayoutOptions++ppBracketsWith :: (a -> PP.Doc b) -> [a] -> PP.Doc b+ppBracketsWith f = PP.align . PP.list . map f++ppBracesWith :: (a -> PP.Doc b) -> [a] -> PP.Doc b+ppBracesWith f = withBraces . map f++withBraces :: [PP.Doc a] -> PP.Doc a+withBraces =+ PP.align+ . PP.encloseSep (PP.flatAlt "{ " "{") (PP.flatAlt " }" "}") ", "++$(deriveToJSON defaultOptions ''CopilotLogMessage)
+ test/Test.hs view
@@ -0,0 +1,32 @@+module Main (main) where++import qualified Data.CaseInsensitive as CI+import qualified Data.Map as Map+import qualified Data.Text as Text+import System.IO (stderr, stdout)+import System.IO.Silently (hSilence)+import Test.Tasty+import Test.Tasty.ExpectedFailure+import Test.Tasty.HUnit++import Copilot.Verifier (Verbosity(..))+import Copilot.Verifier.Examples (shouldFailExamples, shouldPassExamples)++main :: IO ()+main = defaultMain $+ testGroup "copilot-verifier-examples tests"+ [ testGroup "should-fail tests" $+ -- Why use hSilence for the should-fail tests when we are passing+ -- Quiet? It's because crux-llvm errors are logged at the highest+ -- severity possible, and even Crux's quietMode isn't enough to+ -- suppress those messages. We could try messing with things on the+ -- Crux side to avoid this, but it's simpler just to use hSilence here.+ -- After all, we don't really care about the output of failing tests+ -- anyway, just their exit codes.+ map (\(name, action) -> expectFail (testCase (Text.unpack (CI.original name))+ (hSilence [stderr, stdout] action)))+ (Map.toAscList (shouldFailExamples Quiet))+ , testGroup "should-pass tests" $+ map (\(name, action) -> testCase (Text.unpack (CI.original name)) action)+ (Map.toAscList (shouldPassExamples Quiet))+ ]