packages feed

copilot-verifier 4.2 → 4.3

raw patch · 9 files changed

+273/−74 lines, 9 filesdep +copilot-librariesdep ~copilotdep ~copilot-c99dep ~copilot-corePVP ok

version bump matches the API change (PVP)

Dependencies added: copilot-libraries

Dependency ranges changed: copilot, copilot-c99, copilot-core, copilot-language, copilot-prettyprinter, copilot-theorem

API changes (from Hackage documentation)

+ Copilot.Verifier.Examples.ShouldPass.FPNegation: clock :: Stream Int64
+ Copilot.Verifier.Examples.ShouldPass.FPNegation: ftp :: Stream Bool
+ Copilot.Verifier.Examples.ShouldPass.FPNegation: input :: Stream Float
+ Copilot.Verifier.Examples.ShouldPass.FPNegation: notPreviousNot :: Stream Bool -> Stream Bool
+ Copilot.Verifier.Examples.ShouldPass.FPNegation: pre :: Stream Bool -> Stream Bool
+ Copilot.Verifier.Examples.ShouldPass.FPNegation: propMyProperty :: Stream Bool
+ Copilot.Verifier.Examples.ShouldPass.FPNegation: spec :: Spec
+ Copilot.Verifier.Examples.ShouldPass.FPNegation: tpre :: Stream Bool -> Stream Bool
+ Copilot.Verifier.Examples.ShouldPass.FPNegation: verifySpec :: Verbosity -> IO ()

Files

CHANGELOG view
@@ -1,3 +1,8 @@+2025-03-10+        * Version bump (4.3). (#82)+        * Add `smtSolver` option to `VerifierOptions`. (#78)+        * Add `smtFloatMode` option to `VerifierOptions`. (#79)+ 2025-01-20         * Version bump (4.2). (#76)         * Reject specs that use multiple triggers with the same name. (#74)
README.md view
@@ -38,6 +38,13 @@ * Ensure that you have the `z3` SMT solver in your `PATH`. `z3` binaries are   available at https://github.com/Z3Prover/z3/releases. +  Alternatively, the verifier can be configured to use one of the following+  SMT solvers instead:++  * `cvc4`: https://cvc4.github.io/downloads.html+  * `cvc5`: https://github.com/cvc5/cvc5/releases/+  * `yices`: https://yices.csl.sri.com+ Then, clone the repo and run:  ```
copilot-verifier.cabal view
@@ -1,6 +1,6 @@ Cabal-version: 2.2 Name:          copilot-verifier-Version:       4.2+Version:       4.3 Author:        Galois Inc. Maintainer:    rscott@galois.com Copyright:     (c) Galois, Inc 2021-2024@@ -45,9 +45,9 @@     bv-sized >= 1.0.0 && < 1.1,     bytestring,     containers >= 0.5.9.0,-    copilot-c99 >= 4.2 && < 4.3,-    copilot-core >= 4.2 && < 4.3,-    copilot-theorem >= 4.2 && < 4.3,+    copilot-c99 >= 4.3 && < 4.4,+    copilot-core >= 4.3 && < 4.4,+    copilot-theorem >= 4.3 && < 4.4,     crucible >= 0.7.1 && < 0.8,     crucible-llvm >= 0.7 && < 0.8,     crux >= 0.7.1 && < 0.8,@@ -70,7 +70,9 @@   hs-source-dirs: src   exposed-modules:     Copilot.Verifier+    Copilot.Verifier.FloatMode     Copilot.Verifier.Log+    Copilot.Verifier.Solver  library copilot-verifier-examples   import: bldflags@@ -78,9 +80,10 @@   hs-source-dirs: examples   build-depends:     case-insensitive,-    copilot >= 4.2 && < 4.3,-    copilot-language >= 4.2 && < 4.3,-    copilot-prettyprinter >= 4.2 && < 4.3,+    copilot >= 4.3 && < 4.4,+    copilot-language >= 4.3 && < 4.4,+    copilot-libraries >= 4.3 && < 4.4,+    copilot-prettyprinter >= 4.3 && < 4.4,     copilot-verifier   exposed-modules:     Copilot.Verifier.Examples@@ -103,6 +106,7 @@     Copilot.Verifier.Examples.ShouldPass.Clock     Copilot.Verifier.Examples.ShouldPass.Counter     Copilot.Verifier.Examples.ShouldPass.Engine+    Copilot.Verifier.Examples.ShouldPass.FPNegation     Copilot.Verifier.Examples.ShouldPass.FPOps     Copilot.Verifier.Examples.ShouldPass.Heater     Copilot.Verifier.Examples.ShouldPass.IntOps
examples/Copilot/Verifier/Examples.hs view
@@ -28,6 +28,7 @@ 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.FPNegation               as FPNegation 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@@ -70,6 +71,7 @@     , example "Clock" (Clock.verifySpec verb)     , example "Counter" (Counter.verifySpec verb)     , example "Engine" (Engine.verifySpec verb)+    , example "FPNegation" (FPNegation.verifySpec verb)     , example "FPOps" (FPOps.verifySpec verb)     , example "Heater" (Heater.verifySpec verb)     , example "IntOps" (IntOps.verifySpec verb)
+ examples/Copilot/Verifier/Examples/ShouldPass/FPNegation.hs view
@@ -0,0 +1,62 @@+{-# LANGUAGE NoImplicitPrelude #-}++-- | This will not succeed when 'smtFloatMode' is 'FloatUninterpreted', as Clang+-- will turn @input /= 30.0@ below into a more complicated expression that also+-- checks if @30.0@ is NaN or not. This is logically equivalent to the original+-- specification, but an SMT solver cannot conclude this when all of the+-- floating-point operations involved are treated as uninterpreted functions.+--+-- To make this work, we set 'smtFloatMode' to 'FloatIEEE' instead. This works+-- because all of the floating-point operations in the specification below are+-- native to SMT-LIB.+module Copilot.Verifier.Examples.ShouldPass.FPNegation where++import Copilot.Compile.C99 (mkDefaultCSettings)+import Copilot.Verifier ( Verbosity, VerifierOptions(..)+                        , defaultVerifierOptions, verifyWithOptions )+import Copilot.Verifier.FloatMode (FloatMode(..))+import qualified Copilot.Library.PTLTL as PTLTL+import Language.Copilot++input :: Stream Float+input = extern "input" Nothing++-- | MyProperty+--   @+--   Input is never 30.0+--   @+propMyProperty :: Stream Bool+propMyProperty = PTLTL.alwaysBeen (input /= 30.0)++-- | Clock that increases in one-unit steps.+clock :: Stream Int64+clock = [0] ++ (clock + 1)++-- | First Time Point+ftp :: Stream Bool+ftp = [True] ++ false++pre :: Stream Bool -> Stream Bool+pre = ([False] ++)++tpre :: Stream Bool -> Stream Bool+tpre = ([True] ++)++notPreviousNot :: Stream Bool -> Stream Bool+notPreviousNot = not . PTLTL.previous . not++-- | Complete specification. Calls C handler functions when properties are+-- violated.+spec :: Spec+spec = do+  trigger "handlerMyProperty" (not propMyProperty) []++verifySpec :: Verbosity -> IO ()+verifySpec verb = do+  spec' <- reify spec+  verifyWithOptions+    (defaultVerifierOptions+      { verbosity = verb+      , smtFloatMode = FloatIEEE+      })+    mkDefaultCSettings [] "fpNegation" spec'
src/Copilot/Verifier.hs view
@@ -3,6 +3,7 @@ {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE ExplicitNamespaces #-}+{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ImplicitParams #-}@@ -49,7 +50,9 @@  import qualified Copilot.Theorem.What4 as CW4 +import qualified Copilot.Verifier.FloatMode as FloatMode import qualified Copilot.Verifier.Log as Log+import qualified Copilot.Verifier.Solver as Solver  import Data.Parameterized.Ctx (EmptyCtx) import Data.Parameterized.Context (pattern Empty)@@ -72,10 +75,10 @@   , labeledPred, labeledPredMsg   -- , ProofObligations, proofGoal, goalsToList, labeledPredMsg   )-import Lang.Crucible.Backend.Simple (newSimpleBackend)+import Lang.Crucible.Backend.Simple (SimpleBackend, newSimpleBackend) import Lang.Crucible.CFG.Core (AnyCFG(..), cfgArgTypes, cfgReturnType) import Lang.Crucible.CFG.Common ( freshGlobalVar )-import Lang.Crucible.FunctionHandle (newHandleAllocator)+import Lang.Crucible.FunctionHandle (HandleAllocator, newHandleAllocator) import Lang.Crucible.Simulator   ( SimContext(..), ctxSymInterface, ExecResult(..), ExecState(..)   , defaultAbortHandler, runOverrideSim, partialValue, gpValue@@ -150,7 +153,7 @@   , getAnnotation, natAdd, natEq, natIte, natLit   ) import What4.Expr.Builder-  ( FloatModeRepr(..), ExprBuilder, BoolExpr, startCaching+  ( Flags, ExprBuilder, BoolExpr, startCaching   , newExprBuilder   ) import What4.FunctionName (functionName)@@ -160,7 +163,6 @@   ) 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@@ -202,6 +204,22 @@     -- * @<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.+  , smtSolver :: Solver.Solver+    -- ^ Which SMT solver to use when solving proof goals.+  , smtFloatMode :: FloatMode.FloatMode+    -- ^ How the verifier should interpret floating-point operations when+    -- translating them to SMT.+    --+    -- By default, the verifier will treat all floating-point operations as+    -- uninterpreted functions ('FloatMode.FloatUninterpreted'). This allows the+    -- verifier to perform some limited reasoning about floating-point+    -- operations that SMT solvers do not have built-in operations for (@sin@,+    -- @cos@, @tan@, etc.), but at the expense of not being able to verify C+    -- code in which the compiler optimizes floating-point expressions. One can+    -- also opt into an alternative mode where floating-point values are treated+    -- as IEEE-754 floats ('FloatMode.FloatIEEE'), but this comes with the+    -- drawback that the verifier will not be able to perform /any/ reasoning+    -- about @sin@, @cos@, @tan@, etc.   } deriving stock Show  -- | The default 'VerifierOptions':@@ -212,11 +230,18 @@ -- * Do not assume any side conditions related to partial operations. -- -- * Do not log any SMT solver interactions.+--+-- * Use the Z3 SMT solver.+--+-- * Treat all floating-point operations as uninterpreted functions when+--   translating to SMT. defaultVerifierOptions :: VerifierOptions defaultVerifierOptions = VerifierOptions   { verbosity = Default   , assumePartialSideConds = False   , logSmtInteractions = False+  , smtSolver = Solver.Z3+  , smtFloatMode = FloatMode.FloatUninterpreted   }  -- | Like 'defaultVerifierOptions', except that the verifier will assume side@@ -330,84 +355,97 @@   FilePath    {- ^ Path to the bitcode file to verify -} ->   IO () verifyBitcode opts csettings properties spec cruxOpts llvmOpts cFile bcFile =+  FloatMode.withFloatMode (smtFloatMode opts) $ \fm ->   do -- Set up the expression builder and symbolic backend      halloc <- newHandleAllocator-     sym <- newExprBuilder FloatUninterpretedRepr CopilotVerifierData globalNonceGenerator+     sym <- newExprBuilder fm 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+     FloatMode.withInterpretedFloatExprBuilder sym fm $+       verifyWithSymBackend bak halloc+  where+    verifyWithSymBackend ::+      forall t st fm.+      IsInterpretedFloatExprBuilder (ExprBuilder t st (Flags fm)) =>+      SimpleBackend t st (Flags fm) ->+      HandleAllocator ->+      IO ()+    verifyWithSymBackend bak halloc = do+      let sym = backendGetSym bak+      -- 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 solver to use for verification.+      let adapter :: SolverAdapter st+          adapter = Solver.solverAdapter (smtSolver opts)+      extendConfig (solver_adapter_config_options adapter) (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 }+      -- 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+      -- 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+      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+      -- 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 $+      llvmPtrWidth llvmCtxt $ \ptrW ->+        withPtrWidth ptrW $ -       do -- Compute the LLVM memory state with global variables allocated-          -- but not initialized-          emptyMem   <- initializeAllMemory bak llvmCtxt llvmMod+        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+           -- 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+           -- 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-          initGoals <--            verifyInitialState cruxOptsInit adapters clRefs simctx initialMem-              (CW4.initialStreamState proofStateBundle)+           -- First check that the initial state of the program matches the starting+           -- segment of the associated Copilot streams.+           let cruxOptsInit = setCruxOfflineSolverOutput "initial-step" cruxOpts+           initGoals <-+             verifyInitialState cruxOptsInit [adapter] clRefs simctx initialMem+               (CW4.initialStreamState proofStateBundle) -          -- Now, the real meat. Carry out the bisimulation step of the proof.-          let cruxOptsTrans = setCruxOfflineSolverOutput "transition-step" cruxOpts-          bisimGoals <--            verifyStepBisimulation opts cruxOptsTrans adapters csettings-              clRefs simctx llvmMod trans memVar initialMem proofStateBundle+           -- Now, the real meat. Carry out the bisimulation step of the proof.+           let cruxOptsTrans = setCruxOfflineSolverOutput "transition-step" cruxOpts+           bisimGoals <-+             verifyStepBisimulation opts cruxOptsTrans [adapter] csettings+               clRefs simctx llvmMod trans memVar initialMem proofStateBundle -          Log.sayCopilot $ Log.SuccessfulProofSummary cFile initGoals bisimGoals-          -- We only want to inform users about Noisy if the verbosity level is-          -- sufficiently low. Crux's logging framework isn't particularly-          -- suited to doing this, as it assumes that all log messages enabled-          -- for low verbosity levels should also be enabled for higher-          -- verbosity levels. That is a reasonable assumption most of the time,-          -- but not here.-          ---          -- To compensate, we hack around the issue by manually checking the-          -- verbosity level before logging the message.-          when (verbosity opts < Noisy) $-            Log.sayCopilot Log.NoisyVerbositySuggestion-  where+           Log.sayCopilot $ Log.SuccessfulProofSummary cFile initGoals bisimGoals+           -- We only want to inform users about Noisy if the verbosity level is+           -- sufficiently low. Crux's logging framework isn't particularly+           -- suited to doing this, as it assumes that all log messages enabled+           -- for low verbosity levels should also be enabled for higher+           -- verbosity levels. That is a reasonable assumption most of the time,+           -- but not here.+           --+           -- To compensate, we hack around the issue by manually checking the+           -- verbosity level before logging the message.+           when (verbosity opts < Noisy) $+             Log.sayCopilot Log.NoisyVerbositySuggestion+     -- If @logSmtInteractions@ is enabled, enable offline solver output in the     -- supplied 'CruxOptions' with the supplied file template. Otherwise, return     -- the supplied 'CruxOptions' unaltered.@@ -976,7 +1014,7 @@     loop (CW4.XFloat x)  (FloatRepr SingleFloatRepr) = return x     loop (CW4.XDouble x) (FloatRepr DoubleFloatRepr) = return x -    loop CW4.XEmptyArray (VectorRepr _tpr) =+    loop (CW4.XEmptyArray _ctp) (VectorRepr _tpr) =       pure V.empty     loop (CW4.XArray xs) (VectorRepr tpr) =       V.generateM (PVec.lengthInt xs) (\i -> loop (PVec.elemAtUnsafe i xs) tpr)@@ -1037,7 +1075,7 @@     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 =+    loop (Array _ctp) (CW4.XEmptyArray _ctp2) (VectorRepr _tpr) vs =       pure $ backendPred sym $ V.null vs     loop (Array ctp) (CW4.XArray xs) (VectorRepr tpr) vs       | PVec.lengthInt xs == V.length vs
+ src/Copilot/Verifier/FloatMode.hs view
@@ -0,0 +1,56 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE RankNTypes #-}++module Copilot.Verifier.FloatMode+  ( FloatMode(..)+  , withFloatMode+  , withInterpretedFloatExprBuilder+  ) where++import qualified What4.Expr.Builder as W4+import qualified What4.InterpretedFloatingPoint as W4++-- | How the verifier should interpret floating-point operations.+data FloatMode+  = -- | Floating-point values are treated as bitvectors of the appropriate+    -- width, and all operations on them are translated as uninterpreted+    -- functions. This is the verifier's default interpretation, and it is what+    -- allows the verifier to perform any reasoning at all over floating-point+    -- operations that are not native to SMT-LIB (@sin@, @cos@, @tan@, etc.).+    FloatUninterpreted++  | -- | Floating-point values are treated as bit-precise IEEE-754 floats. This+    -- interpretation can perform deeper reasoning about floating-point+    -- operations that are native to SMT-LIB, but at the expense of causing the+    -- verifier to throw an error if it encounters operations that are not+    -- native to SMT-LIB (@sin@, @cos@, @tan@, etc.). Use at your own risk.+    FloatIEEE+  deriving Show++-- | Convert a 'FloatMode' into a What4 'W4.FloatModeRepr' and pass it to a+-- continuation.+withFloatMode ::+  FloatMode ->+  (forall fm. W4.FloatModeRepr fm -> r) ->+  r+withFloatMode fm k =+  case fm of+    FloatUninterpreted ->+      k W4.FloatUninterpretedRepr+    FloatIEEE ->+      k W4.FloatIEEERepr++-- | Match on a 'W4.FloatModeRepr', compute evidence that it gives rise to an+-- instance of 'W4.IsInterpretedFloatExprBuilder', and pass the evidence to a+-- continutation.+withInterpretedFloatExprBuilder ::+  W4.ExprBuilder t st (W4.Flags fm) ->+  W4.FloatModeRepr fm ->+  (W4.IsInterpretedFloatExprBuilder (W4.ExprBuilder t st (W4.Flags fm)) => r) ->+  r+withInterpretedFloatExprBuilder _sym fm k =+  case fm of+    W4.FloatUninterpretedRepr -> k+    W4.FloatIEEERepr -> k+    W4.FloatRealRepr -> k
src/Copilot/Verifier/Log.hs view
@@ -799,7 +799,7 @@     CW4.XWord64 w -> WI.printSymExpr w     CW4.XFloat  f -> WI.printSymExpr f     CW4.XDouble d -> WI.printSymExpr d-    CW4.XEmptyArray -> "[]"+    CW4.XEmptyArray {} -> "[]"     CW4.XArray  a   -> ppBracesWith ppCopilotValue (PV.toList a)     CW4.XStruct s   -> ppBracketsWith ppCopilotValue s 
+ src/Copilot/Verifier/Solver.hs view
@@ -0,0 +1,25 @@+module Copilot.Verifier.Solver+  ( Solver(..)+  , solverAdapter+  ) where++import qualified What4.Solver.Adapter as W4+import qualified What4.Solver.CVC4 as W4.CVC4+import qualified What4.Solver.CVC5 as W4.CVC5+import qualified What4.Solver.Yices as W4.Yices+import qualified What4.Solver.Z3 as W4.Z3++-- | General-purpose SMT solvers that @copilot-verifier@ supports.+data Solver+  = CVC4+  | CVC5+  | Yices+  | Z3+  deriving Show++-- | Return the @what4@ 'W4.SolverAdapter' corresponding to a given 'Solver'.+solverAdapter :: Solver -> W4.SolverAdapter st+solverAdapter CVC4  = W4.CVC4.cvc4Adapter+solverAdapter CVC5  = W4.CVC5.cvc5Adapter+solverAdapter Yices = W4.Yices.yicesAdapter+solverAdapter Z3    = W4.Z3.z3Adapter