crucible 0.7.2 → 0.10
raw patch · 58 files changed
Files
- CHANGELOG.md +56/−0
- crucible.cabal +134/−15
- src/Lang/Crucible/Analysis/Fixpoint.hs +13/−10
- src/Lang/Crucible/Analysis/Fixpoint/Components.hs +2/−1
- src/Lang/Crucible/Analysis/ForwardDataflow.hs +0/−309
- src/Lang/Crucible/Analysis/Postdom.hs +12/−12
- src/Lang/Crucible/Analysis/Reachable.hs +3/−3
- src/Lang/Crucible/Backend.hs +42/−8
- src/Lang/Crucible/Backend/AssumptionStack.hs +17/−16
- src/Lang/Crucible/Backend/Assumptions.hs +70/−7
- src/Lang/Crucible/Backend/Goals.hs +32/−0
- src/Lang/Crucible/Backend/Online.hs +229/−241
- src/Lang/Crucible/Backend/ProofGoals.hs +96/−4
- src/Lang/Crucible/Backend/Prove.hs +74/−24
- src/Lang/Crucible/Backend/Simple.hs +16/−7
- src/Lang/Crucible/CFG/Common.hs +4/−4
- src/Lang/Crucible/CFG/Core.hs +6/−6
- src/Lang/Crucible/CFG/EarlyMergeLoops.hs +4/−4
- src/Lang/Crucible/CFG/Expr.hs +18/−6
- src/Lang/Crucible/CFG/ExtractSubgraph.hs +9/−8
- src/Lang/Crucible/CFG/Generator.hs +24/−22
- src/Lang/Crucible/CFG/Reg.hs +7/−7
- src/Lang/Crucible/CFG/SSAConversion.hs +47/−47
- src/Lang/Crucible/Concretize.hs +274/−28
- src/Lang/Crucible/FunctionHandle.hs +1/−1
- src/Lang/Crucible/README.hs +114/−0
- src/Lang/Crucible/Simulator.hs +3/−0
- src/Lang/Crucible/Simulator/BoundedExec.hs +58/−50
- src/Lang/Crucible/Simulator/BoundedRecursion.hs +44/−34
- src/Lang/Crucible/Simulator/Breakpoint.hs +0/−109
- src/Lang/Crucible/Simulator/CallFrame.hs +37/−21
- src/Lang/Crucible/Simulator/Cut.hs +110/−0
- src/Lang/Crucible/Simulator/EvalStmt.hs +22/−19
- src/Lang/Crucible/Simulator/Evaluation.hs +7/−2
- src/Lang/Crucible/Simulator/ExecutionTree.hs +484/−102
- src/Lang/Crucible/Simulator/Operations.hs +41/−38
- src/Lang/Crucible/Simulator/OverrideSim.hs +45/−12
- src/Lang/Crucible/Simulator/PathSatisfiability.hs +15/−9
- src/Lang/Crucible/Simulator/PathSplitting.hs +3/−4
- src/Lang/Crucible/Simulator/PositionTracking.hs +2/−3
- src/Lang/Crucible/Simulator/Profiling.hs +14/−13
- src/Lang/Crucible/Simulator/RecordAndReplay.hs +368/−0
- src/Lang/Crucible/Simulator/RegValue.hs +40/−0
- src/Lang/Crucible/Simulator/SimError.hs +58/−15
- src/Lang/Crucible/Simulator/SymSequence.hs +36/−1
- src/Lang/Crucible/Syntax.hs +12/−10
- src/Lang/Crucible/Types.hs +2/−2
- src/Lang/Crucible/Utils/BitSet.hs +15/−14
- src/Lang/Crucible/Utils/CoreRewrite.hs +2/−2
- src/Lang/Crucible/Utils/MuxTree.hs +1/−2
- src/Lang/Crucible/Utils/Structural.hs +2/−1
- src/Lang/Crucible/Vector.hs +1/−1
- test/absint/Max.hs +1/−1
- test/absint/WTO.hs +3/−2
- test/helpers/Main.hs +9/−5
- test/helpers/SymSequence.hs +15/−0
- test/helpers/SymSequence/Properties.hs +308/−0
- test/helpers/SymSequence/Reverse.hs +174/−0
CHANGELOG.md view
@@ -1,3 +1,59 @@+# 0.10 -- 2026-09-10++* Add support for GHC 9.12 (at 9.12.2) and bump from 9.10.1 to 9.10.3.+* Fix `uniquelyConcRegMap` blocking clause: use disjunction (OR) instead of+ conjunction (AND). The old code would falsely report a `RegMap` as uniquely+ concretized whenever any single component was unique, even if other components+ had multiple possible values.+* **BREAKING:** Rename various bits associated with the "breakpoint"+ feature in accordance with renaming the feature to "cut" or+ "cutpoint".+ Exported Haskell symbols renamed:+ - `Lang.Crucible.Simulator.Breakpoint` -> `Lang.Crucible.Simulator.Cut`+ - `BreakpointName` -> `CutpointName`+ - `Breakpoint` -> `Cut`+ - `addBreakpointStmt` -> `addCutStmt`+ - `breakAndReturn` -> `cutAndReturn`+ - `breakpointPostdomInfo` -> `cutpointPostdomInfo`+ - `cfgBreakpoints` -> `cfgCutpoints`+ - `setFrameBreakpointPostdomInfo` -> `setFrameCutpointPostdomInfo`+* Add `reverseSymSequence` to `Lang.Crucible.Simulator.SymSequence`+* **BREAKING:** Add `FloatRound` constructor tp `App` in+ `Lang.Crucible.CFG.Expr` for rounding floating-point values to the nearest+ representable integral value.+* **BREAKING:** Add `SequenceReverse` constructor to `App` in+ `Lang.Crucible.CFG.Expr` for reversing symbolic sequences.+* **BREAKING:** Change the signature of `getRecordedTrace` in+ `Lang.Crucible.Simulator.RecordAndReplay` to remove the `evalBool` parameter,+ as the implementation no longer concretizes the trace.+* Add `Lang.Crucible.Simulator.RecordAndReplay.getConcreteRecordedTrace` for+ performant trace reversal when concretization is desired.+* Add `withStateBackend` and additional infrastructure for including stack+ traces in `SimError` and `IsSymBackend`+* Fix `onlineProve` to properly negate goals before checking satisfiability+* Avoid sending trivially-true goals to the solver in `Backend.Prove`.+* Fix bug in `execResultGlobals` that caused it to erroneously always return the+ non-aborted branch of a `FinishedResult ... (PartialRes ...)`.++# 0.9 -- 2026-01-29++# 0.8.0 -- 2025-11-09++* Add `setExecResultContext`, `setExecStateContext`+* Add `Lang.Crucible.Simulator.RecordAndReplay`, a module with two new execution+ features for recording and replaying control-flow traces.+* Add a `GlobalPair` argument to `AbortedExit`.+* Add new helpers for extracting `SymGlobalState`s: `exec{Result,State}Globals`.+* Add `typedOverride` for constructing `TypedOverride`s with statically-known+ signatures.+* Add `bindTypedOverride` for binding `TypedOverride`s to `FnHandle`s.+* Add `FunctorF`, `FoldableF`, and `TraversableF` instances for `CrucibleEvent`,+ `CrucibleAssumption`, and `CrucibleAssumptions`.+* Add `gcAddTopLevelAssume`, for making top-level assumptions.+* Rename functions in `Lang.Crucible.Concretize` to match What4's conventions.+ In particular, rename `concRegValue` to `groundRegValue`, `concRegEntry` to+ `groundRegEntry`, and `concRegMap` to `groundRegMap`.+ # 0.7.2 -- 2025-03-21 * Add support for Bitwuzla as an online SMT solver backend.
crucible.cabal view
@@ -1,6 +1,6 @@ Cabal-version: 2.2 Name: crucible-Version: 0.7.2+Version: 0.10 Author: Galois Inc. Maintainer: rscott@galois.com, kquick@galois.com, langston@galois.com Copyright: (c) Galois, Inc 2014-2022@@ -14,6 +14,9 @@ (SSA) form control flow graphs, and a symbolic simulation engine for executing programs expressed in this format. It also provides support for communicating with a variety of SAT and SMT solvers, including Z3, CVC4, Yices, STP, and dReal.+ .+ For an overview of Crucible please have a look at "Lang.Crucible.README"+ extra-doc-files: CHANGELOG.md source-repository head@@ -31,12 +34,120 @@ Default: True common bldflags- ghc-options: -Wall- -Werror=incomplete-patterns- -Werror=missing-methods- -Werror=overlapping-patterns- -Wpartial-fields- -Wincomplete-uni-patterns+ -- Note [Export lists]+ -- -------------------+ --+ -- We don't allow modules without export lists.+ --+ -- From the GHC docs:+ --+ -- Declaring an explicit export list [...] enables GHC dead code analysis,+ -- prevents accidental export of names and can ease optimizations like+ -- inlining.+ --+ -- It also makes it easier to organize the Haddocks using section headers, and+ -- allows for internal/hidden definitions and constructors.+ --+ -- We can't use -Werror=missing-export-lists here because it interferes with+ -- ghcid. However, we enable it in CI, see ../cabal.project.ci.+ ghc-options:+ -Wmissing-export-lists++ -- Specifying -Wall and -Werror can cause the project to fail to build on+ -- newer versions of GHC simply due to new warnings being added to -Wall. To+ -- prevent this from happening we manually list which warnings should be+ -- considered errors. We also list some warnings that are not in -Wall, though+ -- try to avoid "opinionated" warnings (though this judgement is clearly+ -- subjective).+ --+ -- Warnings are grouped by the GHC version that introduced them, and then+ -- alphabetically.+ --+ -- A list of warnings and the GHC version in which they were introduced is+ -- available here:+ -- https://ghc.gitlab.haskell.org/ghc/doc/users_guide/using-warnings.html++ -- Since GHC 9.6 or earlier:+ ghc-options:+ -Wall+ -Werror=ambiguous-fields+ -Werror=deferred-type-errors+ -Werror=deprecated-flags+ -Werror=deprecations+ -Werror=deriving-defaults+ -Werror=deriving-typeable+ -Werror=dodgy-foreign-imports+ -Werror=duplicate-exports+ -Werror=empty-enumerations+ -Werror=gadt-mono-local-binds+ -Werror=identities+ -Werror=inaccessible-code+ -Werror=incomplete-patterns+ -Werror=incomplete-record-updates+ -Werror=incomplete-uni-patterns+ -Werror=inline-rule-shadowing+ -Werror=misplaced-pragmas+ -Werror=missed-extra-shared-lib+ -Werror=missing-exported-signatures+ -Werror=missing-fields+ -Werror=missing-home-modules+ -Werror=missing-methods+ -Werror=missing-pattern-synonym-signatures+ -Werror=missing-signatures+ -Werror=name-shadowing+ -Werror=noncanonical-monad-instances+ -Werror=noncanonical-monoid-instances+ -Werror=operator-whitespace+ -Werror=operator-whitespace-ext-conflict+ -Werror=orphans+ -Werror=overflowed-literals+ -Werror=overlapping-patterns+ -Werror=partial-fields+ -Werror=partial-type-signatures+ -Werror=redundant-bang-patterns+ -Werror=redundant-record-wildcards+ -Werror=redundant-strictness-flags+ -Werror=simplifiable-class-constraints+ -Werror=star-binder+ -Werror=star-is-type+ -Werror=tabs+ -Werror=type-defaults+ -Werror=typed-holes+ -Werror=type-equality-out-of-scope+ -Werror=type-equality-requires-operators+ -Werror=unicode-bidirectional-format-characters+ -Werror=unrecognised-pragmas+ -Werror=unrecognised-warning-flags+ -Werror=unsupported-calling-conventions+ -Werror=unsupported-llvm-version+ -Werror=unused-do-bind+ -Werror=unused-imports+ -Werror=unused-record-wildcards+ -Werror=warnings-deprecations+ -Werror=wrong-do-bind++ if impl(ghc < 9.8)+ ghc-options:+ -Werror=forall-identifier++ if impl(ghc >= 9.8)+ ghc-options:+ -Werror=incomplete-export-warnings+ -Werror=inconsistent-flags++ if impl(ghc >= 9.10)+ ghc-options:+ -Werror=badly-staged-types+ -Werror=data-kinds-tc+ -Werror=incomplete-record-selectors+ + if impl(ghc < 9.12)+ ghc-options:+ -Werror=compat-unqualified-imports++ -- TODO(#1308): Enable and fix this warning when GHC 9.6 is dropped from CI+ -- -Werror=deprecated-type-abstractions+ ghc-prof-options: -O2 -fprof-auto-exported default-language: Haskell2010 @@ -45,7 +156,7 @@ import: bldflags build-depends: async,- base >= 4.13 && < 4.20,+ base >= 4.13 && < 4.22, bimap, bv-sized >= 1.0.0 && < 1.1, containers >= 0.5.9.0,@@ -53,15 +164,17 @@ fgl, hashable, json >= 0.9 && < 1.0,- lens,+ microlens >= 0.5,+ microlens-mtl,+ microlens-th, mtl, panic >= 0.3,- parameterized-utils >= 1.0.8 && < 2.2,+ parameterized-utils >= 2.3 && < 2.4, prettyprinter >= 1.7.0, template-haskell, text, time >= 1.8 && < 2.0,- th-abstraction >=0.1 && <0.7,+ th-abstraction >=0.1 && <0.8, transformers, unordered-containers, vector,@@ -78,7 +191,6 @@ exposed-modules: Lang.Crucible.Analysis.DFS- Lang.Crucible.Analysis.ForwardDataflow Lang.Crucible.Analysis.Fixpoint Lang.Crucible.Analysis.Fixpoint.Components Lang.Crucible.Analysis.Postdom@@ -101,11 +213,12 @@ Lang.Crucible.CFG.SSAConversion Lang.Crucible.CFG.EarlyMergeLoops Lang.Crucible.FunctionHandle+ Lang.Crucible.README Lang.Crucible.Simulator- Lang.Crucible.Simulator.Breakpoint Lang.Crucible.Simulator.BoundedExec Lang.Crucible.Simulator.BoundedRecursion Lang.Crucible.Simulator.CallFrame+ Lang.Crucible.Simulator.Cut Lang.Crucible.Simulator.Evaluation Lang.Crucible.Simulator.EvalStmt Lang.Crucible.Simulator.ExecutionTree@@ -117,6 +230,7 @@ Lang.Crucible.Simulator.PathSplitting Lang.Crucible.Simulator.PositionTracking Lang.Crucible.Simulator.Profiling+ Lang.Crucible.Simulator.RecordAndReplay Lang.Crucible.Simulator.RegMap Lang.Crucible.Simulator.RegValue Lang.Crucible.Simulator.SimError@@ -163,15 +277,20 @@ import: bldflags type: exitcode-stdio-1.0 hs-source-dirs: test/helpers--- other-modules:+ other-modules:+ SymSequence+ SymSequence.Properties+ SymSequence.Reverse main-is: Main.hs build-depends: base, hspec >= 2.5, crucible,- lens,+ hedgehog,+ microlens, panic >= 0.3, parameterized-utils, tasty >= 0.10, tasty-hspec >= 1.1,+ tasty-hedgehog >= 1.2, tasty-hunit, what4
src/Lang/Crucible/Analysis/Fixpoint.hs view
@@ -51,11 +51,13 @@ ) where import Control.Applicative-import Control.Lens.Operators ( (^.), (%=), (.~), (&), (%~) ) import qualified Control.Monad.State.Strict as St+import Data.Function ((&)) import qualified Data.Functor.Identity as I import Data.Kind import qualified Data.Set as S+import Lens.Micro ((^.), (.~), (%~))+import Lens.Micro.Mtl ((%=)) import Text.Printf import Prelude@@ -68,6 +70,7 @@ import Lang.Crucible.CFG.Core import Lang.Crucible.CFG.Extension import Lang.Crucible.Analysis.Fixpoint.Components+import Lang.Crucible.Panic (panic) -- | A wrapper around widening strategies data WideningStrategy = WideningStrategy (Int -> Bool)@@ -472,14 +475,14 @@ let assignment' = interpWriteGlobal interp gv reg assignment in maybe assignment (joinPointAbstractions dom assignment) assignment' - FreshConstant{} -> error "transferStmt: FreshConstant not supported"- FreshFloat{} -> error "transferStmt: FreshFloat not supported"- FreshNat{} -> error "transferStmt: FreshNat not supported"- NewEmptyRefCell{} -> error "transferStmt: NewEmptyRefCell not supported"- NewRefCell {} -> error "transferStmt: NewRefCell not supported"- ReadRefCell {} -> error "transferStmt: ReadRefCell not supported"- WriteRefCell {} -> error "transferStmt: WriteRefCell not supported"- DropRefCell {} -> error "transferStmt: DropRefCell not supported"+ FreshConstant{} -> panic "transferStmt" ["FreshConstant not supported"]+ FreshFloat{} -> panic "transferStmt" ["FreshFloat not supported"]+ FreshNat{} -> panic "transferStmt" ["FreshNat not supported"]+ NewEmptyRefCell{} -> panic "transferStmt" ["NewEmptyRefCell not supported"]+ NewRefCell {} -> panic "transferStmt" ["NewRefCell not supported"]+ ReadRefCell {} -> panic "transferStmt" ["ReadRefCell not supported"]+ WriteRefCell {} -> panic "transferStmt" ["WriteRefCell not supported"]+ DropRefCell {} -> panic "transferStmt" ["DropRefCell not supported"] -- Transfer a block terminator statement. transferTerm :: forall ctx'@@ -528,7 +531,7 @@ isRetAbstr %= domJoin dom absVal return S.empty - VariantElim {} -> error "transferTerm: VariantElim terminator not supported"+ VariantElim {} -> panic "transferTerm" ["VariantElim terminator not supported"] transferJump :: forall ctx'
src/Lang/Crucible/Analysis/Fixpoint/Components.hs view
@@ -40,6 +40,7 @@ import Data.Parameterized.Some (Some(Some)) import Lang.Crucible.CFG.Core (CFG, BlockID) import qualified Lang.Crucible.CFG.Core as CFG+import Lang.Crucible.Panic (panic) -- | Compute a weak topological ordering over a control flow graph. --@@ -109,7 +110,7 @@ -- Otherwise, unwind the stack and add a full component unwindStack elt v makeComponent v- Nothing -> error "Pop attempted on empty stack (Components:visit)"+ Nothing -> panic "visit" ["Pop attempted on empty stack"] -- We return the least label in the strongly-connected component -- containing this vertex, which is used if we have to unwind back -- to the SCC head vertex.
− src/Lang/Crucible/Analysis/ForwardDataflow.hs
@@ -1,309 +0,0 @@---------------------------------------------------------------------------- |--- Module : Lang.Crucible.Analysis.ForwardDataflow--- Description : Forward dataflow analysis framework based on Kildall's algorithm--- Copyright : (c) Galois, Inc 2015--- License : BSD3--- Maintainer : Rob Dockins <rdockins@galois.com>--- Stability : provisional------ This module defines a generic framework for forward dataflow analysis,--- with some additional control-flow data on the side.------ We calculate a fixpoint of a given analysis via the straightforward--- method of iterating the transfer function until no more updates occur.------ Our current method for doing this is quite naive, and more efficient--- methods exist.---------------------------------------------------------------------------{-# LANGUAGE CPP #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE PolyKinds #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE KindSignatures #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE Rank2Types #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE TypeOperators #-}-{-# LANGUAGE TypeSynonymInstances #-}--module Lang.Crucible.Analysis.ForwardDataflow-{-# DEPRECATED "Lang.Crucible.Analysis.Fixpoint is a better implementation of these ideas" #-}-where--import Control.Lens-import Control.Monad.State.Strict-import Data.Kind-import Data.Parameterized.Context ( Assignment )-import qualified Data.Parameterized.Context as Ctx-import Data.Parameterized.TraversableFC-import Data.Set (Set)-import qualified Data.Set as Set-import Prelude hiding (foldr)-import Prettyprinter---import Lang.Crucible.Types-import Lang.Crucible.CFG.Core-import Lang.Crucible.CFG.Expr--import qualified Debug.Trace as Debug--------------------------data SymDom = Dead | Symbolic | Concrete- deriving (Eq, Ord, Show)--symbolicResults- :: IsSyntaxExtension ext- => CFG ext blocks init ret- -- -> Assignment (Ignore SymDom) init- -> String- -- -> (Assignment (KildallPair (Assignment (Ignore SymDom)) SymDom) blocks, Ignore SymDom ret, SymDom)-symbolicResults cfg = show $ kildall_forward symbolicAnalysis cfg (begin, Concrete)- where sz = Ctx.size (blockInputs (getBlock (cfgEntryBlockID cfg) (cfgBlockMap cfg)))- begin = Ctx.generate sz (\_ -> Ignore Symbolic)---symlub :: SymDom -> SymDom -> SymDom-symlub Dead x = x-symlub x Dead = x-symlub Symbolic _ = Symbolic-symlub _ Symbolic = Symbolic-symlub Concrete Concrete = Concrete--sym_reg_transfer :: Reg ctx tp -> Assignment (Ignore SymDom) ctx -> SymDom-sym_reg_transfer reg asgn = ignoreOut $ asgn Ctx.! (regIndex reg)--sym_expr_transfer :: IsSyntaxExtension ext => Expr ext ctx tp -> Assignment (Ignore SymDom) ctx -> SymDom-sym_expr_transfer (App a) asgn- = foldApp (\r z -> symlub z $ sym_reg_transfer r asgn) Dead a---- FIXME this whole shabang is bogus, and should be replace by something that works...--- we assume every function other than "matlabFunctionHandle" returns a symbolic--- output, but does not have control flow that depends on symbolic data...-sym_call_transfer- :: CtxRepr args- -> TypeRepr ret- -> Reg ctx (FunctionHandleType args ret)- -> Ignore SymDom (FunctionHandleType args ret)- -> Assignment a args- -> Ignore SymDom ret-sym_call_transfer _ _ ex _ _- = Debug.trace (show $ pretty ex) $ Ignore Symbolic--symbolicAnalysis :: IsSyntaxExtension ext => KildallForward ext blocks (Ignore SymDom) SymDom-symbolicAnalysis =- KildallForward- { kfwd_lub = \(Ignore x) (Ignore y) -> Ignore (symlub x y)- , kfwd_bot = Ignore Dead- , kfwd_club = symlub- , kfwd_cbot = Dead- , kfwd_same = \(Ignore x) (Ignore y) -> x == y- , kfwd_csame = \x y -> x == y- , kfwd_br = \_ (Ignore x) y -> let z = symlub x y in (z, z)- , kfwd_maybe = \_ _ (Ignore x) y -> let z = symlub x y in (z, Ignore x, z)- , kfwd_reg = \_ ex asgn -> Ignore $ sym_reg_transfer ex asgn- , kfwd_expr = \_ ex asgn -> Ignore $ sym_expr_transfer ex asgn- , kfwd_call = sym_call_transfer- , kfwd_rdglobal = \_ -> Ignore Symbolic- -- FIXME, here we make the totally pessimistic assumption- -- that every global variable read is symbolic- , kfwd_onentry = \_ x -> x- }-----------------------data KildallPair (a::k -> Type) (c :: Type) (tp::k) = KP (a tp) c--instance (ShowF a, Show c) => Show (KildallPair a c tp) where- show (KP x y) = "(" ++ showF x ++ ", " ++ show y ++ ")"--instance (ShowF a, Show c) => ShowF (KildallPair a c)--newtype Ignore a (b::k) = Ignore { ignoreOut :: a }- deriving (Eq, Ord)--instance Show a => Show (Ignore a tp) where- show (Ignore x) = show x--instance Show a => ShowF (Ignore a)---data KildallForward ext blocks (a :: CrucibleType -> Type) c- = KildallForward- { kfwd_lub :: forall tp. a tp -> a tp -> a tp- , kfwd_bot :: forall tp. a tp- , kfwd_club :: c -> c -> c- , kfwd_cbot :: c- , kfwd_same :: forall tp. a tp -> a tp -> Bool- , kfwd_csame :: c -> c -> Bool- , kfwd_br :: forall ctx. Reg ctx BoolType -> a BoolType -> c -> (c, c)- , kfwd_maybe :: forall ctx tp. TypeRepr tp -> Reg ctx (MaybeType tp) -> a (MaybeType tp) -> c -> (c, a tp, c)- , kfwd_reg :: !(forall ctx tp. TypeRepr tp -> Reg ctx tp -> Assignment a ctx -> a tp)- , kfwd_expr :: !(forall ctx tp. TypeRepr tp -> Expr ext ctx tp -> Assignment a ctx -> a tp)- , kfwd_call :: forall ctx args ret. CtxRepr args- -> TypeRepr ret- -> Reg ctx (FunctionHandleType args ret)- -> a (FunctionHandleType args ret)- -> Assignment a args- -> a ret- , kfwd_rdglobal :: forall tp. GlobalVar tp -> a tp- , kfwd_onentry :: forall ctx. BlockID blocks ctx -> (Assignment a ctx, c) -> (Assignment a ctx, c)- }--kildall_transfer- :: forall ext a c blocks ret ctx- . KildallForward ext blocks a c- -> TypeRepr ret- -> Block ext blocks ret ctx- -> (Assignment a ctx, c)- -> State (Assignment (KildallPair (Assignment a) c) blocks, a ret, c) (Set (Some (BlockID blocks)))-kildall_transfer analysis retRepr blk = transfer_seq (_blockStmts blk)- where transfer_seq :: forall ctx'- . StmtSeq ext blocks ret ctx'- -> (Assignment a ctx', c)- -> State (Assignment (KildallPair (Assignment a) c) blocks, a ret, c) (Set (Some (BlockID blocks)))-- transfer_seq (ConsStmt _loc stmt ss) x = transfer_seq ss (transfer_stmt stmt x)- transfer_seq (TermStmt _loc term) x = transfer_term term x-- transfer_stmt :: forall ctx1 ctx2. Stmt ext ctx1 ctx2 -> (Assignment a ctx1, c) -> (Assignment a ctx2, c)- transfer_stmt (SetReg tp ex) (asgn, c) = (Ctx.extend asgn (kfwd_expr analysis tp ex asgn), c)- transfer_stmt (CallHandle rettp ex argstp actuals) (asgn, c) =- let xs = Ctx.zipWith (\tp act -> kfwd_reg analysis tp act asgn) argstp actuals- ex_sh = kfwd_reg analysis (FunctionHandleRepr argstp rettp) ex asgn- a' = kfwd_call analysis argstp rettp ex ex_sh xs- in (Ctx.extend asgn a', c)- transfer_stmt (Print _) asgn = asgn- transfer_stmt (ReadGlobal gv) (asgn, c) = (Ctx.extend asgn (kfwd_rdglobal analysis gv), c)- transfer_stmt FreshConstant{} _ = error "forward dataflow: fresh constant!"- transfer_stmt FreshFloat{} _ = error "forward dataflow: fresh float!"- transfer_stmt FreshNat{} _ = error "forward dataflow: fresh nat!"- transfer_stmt ExtendAssign{} _ = error "extension statement!"- transfer_stmt NewRefCell{} _ = error "forward dataflow: reference cell!"- transfer_stmt NewEmptyRefCell{} _ = error "forward dataflow: reference cell!"- transfer_stmt ReadRefCell{} _ = error "forward dataflow: reference cell!"- transfer_stmt WriteRefCell{} _ = error "forward dataflow: reference cell!"- transfer_stmt DropRefCell{} _ = error "forward dataflow: reference cell!"- transfer_stmt (WriteGlobal _ _) asgnc = asgnc -- FIXME? need to check something here, perhaps?- transfer_stmt (Assert _ _) asgnc = asgnc -- FIXME? is it useful to remember assertions some way?- transfer_stmt (Assume _ _) asgnc = asgnc -- FIXME? is it useful to remember assertions some way?-- transfer_term :: forall ctx'- . TermStmt blocks ret ctx'- -> (Assignment a ctx', c)- -> State (Assignment (KildallPair (Assignment a) c) blocks, a ret, c) (Set (Some (BlockID blocks)))-- transfer_term (ErrorStmt _) _ = return Set.empty-- transfer_term (Jump tgt) x = transfer_jump tgt x-- transfer_term (Br ex tgt1 tgt2) (asgn,c) = do- let a = kfwd_reg analysis knownRepr ex asgn- let (c1,c2) = kfwd_br analysis ex a c- s1 <- transfer_jump tgt1 (asgn,c1)- s2 <- transfer_jump tgt2 (asgn,c2)- return (Set.union s1 s2)-- transfer_term (Return ex) (asgn, c) = do- let a = kfwd_reg analysis retRepr ex asgn- modify (\ (x,r,rc) -> (x, kfwd_lub analysis r a, kfwd_club analysis rc c))- return Set.empty-- transfer_term (TailCall fn callargs actuals) (asgn, c) = do- let xs = Ctx.zipWith (\tp act -> kfwd_reg analysis tp act asgn) callargs actuals- let fn_sh = kfwd_reg analysis (FunctionHandleRepr callargs retRepr) fn asgn- let a' = kfwd_call analysis callargs retRepr fn fn_sh xs- modify (\ (x,r,rc) -> (x, kfwd_lub analysis r a', kfwd_club analysis rc c))- return Set.empty-- transfer_term (MaybeBranch tp ex swtgt jmptgt) (asgn, c) = do- let a = kfwd_reg analysis (MaybeRepr tp) ex asgn- let (c1, a1, c2) = kfwd_maybe analysis tp ex a c- s1 <- transfer_switch swtgt a1 (asgn, c1)- s2 <- transfer_jump jmptgt (asgn, c2)- return (Set.union s1 s2)-- transfer_term (VariantElim _ctx _ex _switch) (_asgn, _c) = do- error "FIXME: transfer_term for VariantElim not implemented"-- transfer_switch :: forall ctx' tp- . SwitchTarget blocks ctx' tp- -> a tp- -> (Assignment a ctx', c)- -> State (Assignment (KildallPair (Assignment a) c) blocks, a ret, c) (Set (Some (BlockID blocks)))- transfer_switch (SwitchTarget tgt argstp actuals) a1 (asgn, c) = do- let xs = Ctx.zipWith (\tp act -> kfwd_reg analysis tp act asgn) argstp actuals- let xs' = Ctx.extend xs a1- transfer_target tgt (xs', c)-- transfer_jump :: forall ctx'- . JumpTarget blocks ctx'- -> (Assignment a ctx', c)- -> State (Assignment (KildallPair (Assignment a) c) blocks, a ret, c) (Set (Some (BlockID blocks)))-- transfer_jump (JumpTarget tgt argstp actuals) (asgn, c) = do- let xs = Ctx.zipWith (\tp act -> kfwd_reg analysis tp act asgn) argstp actuals- transfer_target tgt (xs, c)-- transfer_target :: forall ctx'- . BlockID blocks ctx'- -> (Assignment a ctx', c)- -> State (Assignment (KildallPair (Assignment a) c) blocks, a ret, c) (Set (Some (BlockID blocks)))- transfer_target tgt@(BlockID idx) (asgn, c) = do- (x,r,rc) <- get- let KP old oldc = x Ctx.! idx- let new = Ctx.zipWith (\a b -> kfwd_lub analysis a b) old asgn- let zipsame = Ctx.zipWith (\a b -> Ignore $ kfwd_same analysis a b) old new- let samex = foldlFC (\a (Ignore b) -> a && b) True zipsame- let newc = kfwd_club analysis c oldc- let same = samex && kfwd_csame analysis oldc newc- if same- then return Set.empty- else do put (x & ixF idx .~ KP new newc, r, rc)- return (Set.singleton (Some tgt))----kildall_forward- :: forall ext a c blocks ret init- . KildallForward ext blocks a c- -> CFG ext blocks init ret- -> (Assignment a init, c)- -> (Assignment (KildallPair (Assignment a) c) blocks, a ret, c)-kildall_forward analysis cfg (asgn0,c0) =- let initblk@(BlockID idx) = cfgEntryBlockID cfg-- freshAsgn :: Ctx.Index blocks ctx -> Assignment a ctx- freshAsgn i = fmapFC (\_ -> kfwd_bot analysis)- (blockInputs (getBlock (BlockID i) (cfgBlockMap cfg)))-- in execState (loop (Set.singleton (Some initblk)))- ( Ctx.generate (Ctx.size (cfgBlockMap cfg)) $ \i ->- case testEquality i idx of- Just Refl -> KP asgn0 c0- Nothing -> KP (freshAsgn i) (kfwd_cbot analysis)- , kfwd_bot analysis- , kfwd_cbot analysis- )-- where visit :: Block ext blocks ret ctx- -> (Assignment a ctx, c)- -> Set (Some (BlockID blocks))- -> State (Assignment (KildallPair (Assignment a) c) blocks, a ret, c) ()- visit blk start worklist = do- s <- kildall_transfer analysis (cfgReturnType cfg) blk start- loop (Set.union s worklist)-- loop worklist =- case Set.minView worklist of- Nothing -> return ()- Just (Some tgt@(BlockID idx), worklist') ->- do (x,_,_) <- get- let (KP a c) = x Ctx.! idx- (a',c') = kfwd_onentry analysis tgt (a,c)- visit (getBlock tgt (cfgBlockMap cfg)) (a',c') worklist'
src/Lang/Crucible/Analysis/Postdom.hs view
@@ -15,7 +15,7 @@ {-# LANGUAGE TupleSections #-} module Lang.Crucible.Analysis.Postdom ( postdomInfo- , breakpointPostdomInfo+ , cutpointPostdomInfo , validatePostdom ) where @@ -49,12 +49,12 @@ Just l -> (\(Some n) -> toNode n `reverseEdge` b) <$> l inEdgeGraph :: BlockMap ext blocks ret -> [Some (BlockID blocks)] -> G.UGr-inEdgeGraph m breakpointIds = G.mkGraph ((,()) <$> nodes) edges+inEdgeGraph m cutpointIds = G.mkGraph ((,()) <$> nodes) edges where nodes = 0 : toListFC (toNode . blockID) m cfgEdges = foldMapFC inEdges m- breakpointEdges = map (\(Some bid) -> reverseEdge 0 (getBlock bid m))- breakpointIds- edges = cfgEdges ++ breakpointEdges+ cutpointEdges = map (\(Some bid) -> reverseEdge 0 (getBlock bid m))+ cutpointIds+ edges = cfgEdges ++ cutpointEdges -- | Return subgraph of nodes reachable from given node. reachableSubgraph :: G.Node -> G.UGr -> G.UGr@@ -76,8 +76,8 @@ . BlockMap ext blocks ret -> [Some (BlockID blocks)] -> Map (Some (BlockID blocks)) [Some (BlockID blocks)]-postdomMap m breakpointIds = r- where g0 = inEdgeGraph m breakpointIds+postdomMap m cutpointIds = r+ where g0 = inEdgeGraph m cutpointIds g = reachableSubgraph 0 g0 idMap = nodeToBlockIDMap m@@ -98,8 +98,8 @@ . BlockMap ext blocks ret -> [Some (BlockID blocks)] -> CFGPostdom blocks-postdomAssignment m breakpointIds = fmapFC go m- where pd = postdomMap m breakpointIds+postdomAssignment m cutpointIds = fmapFC go m+ where pd = postdomMap m cutpointIds go :: Block ext blocks ret c -> Const [Some (BlockID blocks)] c go b = Const $ fromMaybe [] (Map.lookup (Some (blockID b)) pd) @@ -107,9 +107,9 @@ postdomInfo :: CFG ext b i r -> CFGPostdom b postdomInfo g = postdomAssignment (cfgBlockMap g) [] -breakpointPostdomInfo :: CFG ext b i r -> [BreakpointName] -> CFGPostdom b-breakpointPostdomInfo g breakpointNames = postdomAssignment (cfgBlockMap g) $- mapMaybe (\nm -> Bimap.lookup nm (cfgBreakpoints g)) breakpointNames+cutpointPostdomInfo :: CFG ext b i r -> [CutpointName] -> CFGPostdom b+cutpointPostdomInfo g cutpointNames = postdomAssignment (cfgBlockMap g) $+ mapMaybe (\nm -> Bimap.lookup nm (cfgCutpoints g)) cutpointNames blockEndsWithError :: Block ext blocks ret args -> Bool blockEndsWithError b =
src/Lang/Crucible/Analysis/Reachable.hs view
@@ -98,7 +98,7 @@ exploreReachable' _ [] r = r exploreReachable' m (Some h:l) r = case Map.lookup (Some h) r of- Just c -> exploreReachable' m l (Map.insert (Some h) (c+1) r)+ Just c -> exploreReachable' m l (Map.insert (Some h) (c + 1) r) Nothing -> do let b = getBlock h m exploreReachable' m (nextBlocks b ++ l) (Map.insert (Some h) 1 r)@@ -119,11 +119,11 @@ SomeCFG g' where oldToNew = mkOldMap newToOld new_map = remapBlockMap oldToNew newToOld- new_breakpoints = Bimap.mapR (mapSome $ remapBlockID oldToNew) (cfgBreakpoints g)+ new_cutpoints = Bimap.mapR (mapSome $ remapBlockID oldToNew) (cfgCutpoints g) g' = CFG { cfgHandle = cfgHandle g , cfgBlockMap = new_map , cfgEntryBlockID = remapBlockID oldToNew entry_id- , cfgBreakpoints = new_breakpoints+ , cfgCutpoints = new_cutpoints } where old_map = cfgBlockMap g entry_id = cfgEntryBlockID g
src/Lang/Crucible/Backend.hs view
@@ -4,8 +4,8 @@ License : BSD3 Maintainer : Joe Hendrix <jhendrix@galois.com> -This module provides an interface that symbolic backends must provide-for interacting with the symbolic simulator.+This module provides the interface that the symbolic simulator uses when+interacting with symbolic backends (i.e., SMT solvers). Compared to the solver connections provided by What4, Crucible backends provide a facility for managing an /assumption stack/ (see 'AS.AssumptionStack'). Note@@ -81,16 +81,17 @@ , ppProofObligation , backendOptions , assertThenAssumeConfigOption+ , ppAssumptionState ) where import Control.Exception(Exception(..), throwIO)-import Control.Lens ((^.)) import Control.Monad import Control.Monad.IO.Class import Data.Foldable (toList) import Data.Set (Set)-import qualified Prettyprinter as PP import GHC.Stack+import Lens.Micro ((^.))+import qualified Prettyprinter as PP import Data.Parameterized.Map (MapF) @@ -110,6 +111,7 @@ import qualified Lang.Crucible.Backend.AssumptionStack as AS import qualified Lang.Crucible.Backend.ProofGoals as PG import Lang.Crucible.Simulator.SimError+import Lang.Crucible.Backend.ProofGoals (ppGoalCollector) type Assertion sym = LabeledPred (Pred sym) SimError type ProofObligation sym = AS.ProofGoal (Assumptions sym) (Assertion sym)@@ -311,6 +313,17 @@ resetAssumptionState :: bak -> IO () resetAssumptionState bak = restoreAssumptionState bak PG.emptyGoalCollector + -- | Get the state of the backend+ --+ -- In contrast to 'saveAssumptionState', this also includes the goals.+ getBackendState :: bak -> IO (AssumptionState sym)++ -- | An additional exception context for more detailed SimErrors+ getExceptionContext :: bak -> Maybe ProgramStack++ -- | Make a backend just like this but with a different error context+ withExceptionContext :: bak -> ProgramStack -> bak + assertThenAssumeConfigOption :: ConfigOption BaseBoolType assertThenAssumeConfigOption = configOption knownRepr "assertThenAssume" @@ -368,7 +381,8 @@ assert bak p msg = do let sym = backendGetSym bak loc <- getCurrentProgramLoc sym- addAssertion bak (LabeledPred p (SimError loc msg))+ let ctx = getExceptionContext bak+ addAssertion bak (LabeledPred p (mkSimError loc msg ctx)) -- | Add a proof obligation for False. This always aborts execution -- of the current path, because after asserting false, we get to assume it,@@ -378,7 +392,8 @@ addFailedAssertion bak msg = do let sym = backendGetSym bak loc <- getCurrentProgramLoc sym- let err = SimError loc msg+ let ctx = getExceptionContext bak+ let err = mkSimError loc msg ctx addProofObligation bak (LabeledPred (falsePred sym) err) abortExecBecause (AssertionFailure err) @@ -417,7 +432,8 @@ readPartExpr bak (PE p v) msg = do let sym = backendGetSym bak loc <- getCurrentProgramLoc sym- addAssertion bak (LabeledPred p (SimError loc msg))+ let ctx = getExceptionContext bak+ addAssertion bak (LabeledPred p (mkSimError loc msg ctx)) return v @@ -489,4 +505,22 @@ ppGl = PP.indent 2 $- PP.vsep [ppSimError (gl^.labeledPredMsg), printSymExpr (gl^.labeledPred)]+ PP.vsep [ppSimError (gl ^. labeledPredMsg), printSymExpr (gl ^. labeledPred)]++-- | Pretty-printer for 'AssumptionState'.+ppAssumptionState ::+ IsExpr (SymExpr sym) =>+ proxy sym ->+ AssumptionState sym ->+ PP.Doc ann+ppAssumptionState _proxy = ppGoalCollector ppAssumptions ppPred+ where+ ppPred (LabeledPred p simErr) =+ PP.vcat+ [ "Labeled predicate:"+ , PP.indent 2 $+ PP.vcat+ [ printSymExpr p+ , ppSimError simErr+ ]+ ]
src/Lang/Crucible/Backend/AssumptionStack.hs view
@@ -4,17 +4,16 @@ License : BSD3 Maintainer : Rob Dockins <rdockins@galois.com> -This module provides management support for keeping track-of a context of logical assumptions. The API provided here-is similar to the interactive mode of an SMT solver. Logical-conditions can be assumed into the current context, and bundles-of assumptions are organized into frames which are pushed and-popped by the user to manage the state.+This module provides management support for keeping track of a context of+logical assumptions and proof obligations that arise from symbolic simulation.+The API provided here is similar to the interactive mode of an SMT solver.+Logical conditions can be assumed into the current context, and conjunctions+of assumptions are organized into frames which are pushed and popped by the+simulator to manage the state. -Additionally, proof goals can be asserted to the system. These will be-turned into complete logical statements by assuming the current context-and be stashed in a collection of remembered goals for later dispatch to-solvers.+Additionally, proof goals can be asserted to the system. These will be turned+into complete logical statements by assuming the current context and will be+stashed in a collection of remembered goals for later dispatch to SMT solvers. -} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DataKinds #-}@@ -70,11 +69,13 @@ , assumeFrameCond :: asmp } --- | An assumption stack is a data structure for tracking--- logical assumptions and proof obligations. Assumptions--- can be added to the current stack frame, and stack frames--- may be pushed (to remember a previous state) or popped--- to restore a previous state.+-- | An assumption stack is a data structure for tracking logical assumptions+-- and proof obligations that arise from symbolic simulation. Assumptions+-- can be added to the current stack frame, and stack frames may be pushed (to+-- remember a previous state) or popped (to restore a previous state).+--+-- The main use of 'AssumptionStack' is as the state of the simple or online+-- backends. data AssumptionStack asmp ast = AssumptionStack { assumeStackGen :: IO FrameIdentifier@@ -175,7 +176,7 @@ case gcPop gc of Left (ident', _assumes, mg, gc1) | ident == ident' -> (gc',n)- | otherwise -> go (n+1) gc'+ | otherwise -> go (n + 1) gc' where gc' = case mg of Nothing -> gc1 Just g -> gcAddGoals g gc1
src/Lang/Crucible/Backend/Assumptions.hs view
@@ -41,15 +41,19 @@ , assumptionsPred , flattenAssumptions , assumptionsTopLevelLocs+ , ppAssumptions'+ , ppAssumptions ) where -import Control.Lens (Traversal, folded) import Data.Kind (Type)-import Data.Functor.Identity+import qualified Data.Foldable as F import Data.Functor.Const-import qualified Data.Sequence as Seq+import Data.Functor.Identity+import qualified Data.Parameterized.TraversableF as TF import Data.Sequence (Seq)+import qualified Data.Sequence as Seq+import Lens.Micro (Traversal, folded) import qualified Prettyprinter as PP import What4.Expr.Builder@@ -76,6 +80,13 @@ -- ^ An assumption justified by a proof of the impossibility of -- a certain simulator error. +instance TF.FunctorF CrucibleAssumption where+ fmapF = TF.fmapFDefault+instance TF.FoldableF CrucibleAssumption where+ foldMapF = TF.foldMapFDefault+instance TF.TraversableF CrucibleAssumption where+ traverseF = traverseAssumption+ -- | This type describes events we can track during program execution. data CrucibleEvent (e :: BaseType -> Type) where -- | This event describes the creation of a symbolic variable.@@ -91,13 +102,24 @@ ProgramLoc -> CrucibleEvent e +instance TF.FunctorF CrucibleEvent where+ fmapF = TF.fmapFDefault+instance TF.FoldableF CrucibleEvent where+ foldMapF = TF.foldMapFDefault+instance TF.TraversableF CrucibleEvent where+ traverseF = traverseEvent+ -- | Pretty print an event-ppEvent :: IsExpr e => CrucibleEvent e -> PP.Doc ann-ppEvent (CreateVariableEvent loc nm _tpr v) =- "create var" PP.<+> PP.pretty nm PP.<+> "=" PP.<+> printSymExpr v PP.<+> "at" PP.<+> PP.pretty (plSourceLoc loc)-ppEvent (LocationReachedEvent loc) =+ppEvent' :: (forall t. e t -> PP.Doc ann) -> CrucibleEvent e -> PP.Doc ann+ppEvent' ppExp (CreateVariableEvent loc nm _tpr v) =+ "create var" PP.<+> PP.pretty nm PP.<+> "=" PP.<+> ppExp v PP.<+> "at" PP.<+> PP.pretty (plSourceLoc loc)+ppEvent' _ppExp (LocationReachedEvent loc) = "reached" PP.<+> PP.pretty (plSourceLoc loc) PP.<+> "in" PP.<+> PP.pretty (plFunction loc) +-- | Pretty print an event+ppEvent :: IsExpr e => CrucibleEvent e -> PP.Doc ann+ppEvent = ppEvent' printSymExpr+ -- | Return the program location associated with an event eventLoc :: CrucibleEvent e -> ProgramLoc eventLoc (CreateVariableEvent loc _ _ _) = loc@@ -152,6 +174,21 @@ instance Monoid (CrucibleAssumptions e) where mempty = ManyAssumptions mempty +instance TF.FunctorF CrucibleAssumptions where+ fmapF = TF.fmapFDefault+instance TF.FoldableF CrucibleAssumptions where+ foldMapF = TF.foldMapFDefault+instance TF.TraversableF CrucibleAssumptions where+ traverseF f = \case+ SingleAssumption a ->+ SingleAssumption <$> TF.traverseF f a+ SingleEvent e ->+ SingleEvent <$> TF.traverseF f e+ ManyAssumptions xs ->+ ManyAssumptions <$> traverse (TF.traverseF f) xs+ MergeAssumptions c xs ys ->+ MergeAssumptions <$> f c <*> TF.traverseF f xs <*> TF.traverseF f ys+ singleAssumption :: CrucibleAssumption e -> CrucibleAssumptions e singleAssumption x = SingleAssumption x @@ -239,6 +276,8 @@ ppAssumption :: (forall tp. e tp -> PP.Doc ann) -> CrucibleAssumption e -> PP.Doc ann ppAssumption ppDoc e =+ -- TODO(lb): These should really all be `align`ed, but that breaks a bunch+ -- of tests. case e of GenericAssumption l msg p -> PP.vsep [ ppLocated l (PP.pretty msg)@@ -266,3 +305,27 @@ ppLoc :: ProgramLoc -> PP.Doc ann ppLoc l = PP.pretty (plSourceLoc l)++-- | Pretty-print 'CrucibleAssumptions'.+ppAssumptions' ::+ -- | How to print expressions. If @'IsExpr' e@ holds, then see 'ppAssumptions'+ -- for a version that uses 'printSymExpr'.+ (forall tp. e tp -> PP.Doc ann) ->+ CrucibleAssumptions e ->+ PP.Doc ann+ppAssumptions' ppExp =+ \case+ SingleAssumption asmp -> ppAssumption ppExp asmp+ SingleEvent e -> ppEvent' ppExp e+ ManyAssumptions asmps -> PP.list (map (ppAssumptions' ppExp) (F.toList asmps))+ MergeAssumptions b thn els ->+ PP.align $+ PP.vcat+ [ "if " <> PP.align (ppExp b)+ , "then " <> PP.align (ppAssumptions' ppExp thn)+ , "else " <> PP.align (ppAssumptions' ppExp els)+ ]++-- | @'ppAssumptions' = `ppAssumptions'` 'printSymExpr'@+ppAssumptions :: IsExpr e => CrucibleAssumptions e -> PP.Doc ann+ppAssumptions = ppAssumptions' printSymExpr
src/Lang/Crucible/Backend/Goals.hs view
@@ -7,12 +7,14 @@ proof obligations, and the current state of assumptions. -} +{-# LANGUAGE LambdaCase #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE TupleSections #-} module Lang.Crucible.Backend.Goals ( ProofGoal(..) , Goals(..)+ , ppGoals , goalsToList , assuming , proveAll@@ -29,6 +31,7 @@ import Data.Functor.Const (Const(..)) import Data.Sequence (Seq) import qualified Data.Sequence as Seq+import qualified Prettyprinter as PP -- | A proof goal consists of a collection of assumptions -- that were in scope when an assertion was made, together@@ -52,6 +55,35 @@ -- | A conjunction of two goals. | ProveConj !(Goals asmp goal) !(Goals asmp goal) deriving Show++-- | Intended for debugging, this is not generally a user-facing datatype.+ppGoals ::+ (asmp -> PP.Doc ann) ->+ (goal -> PP.Doc ann) ->+ Goals asmp goal ->+ PP.Doc ann+ppGoals ppAsmp ppGoal =+ \case+ Assuming asmp gls ->+ PP.align $+ PP.vcat+ [ PP.pretty "Assuming:"+ , PP.indent 2 (ppAsmp asmp)+ , PP.pretty "Prove:"+ , PP.indent 2 (ppGoals ppAsmp ppGoal gls)+ ]+ Prove gl -> ppGoal gl+ ProveConj gls gls' ->+ PP.align $+ PP.vcat+ [ PP.pretty "Prove both:"+ , PP.indent 2 (ppGoals ppAsmp ppGoal gls)+ , PP.indent 2 (ppGoals ppAsmp ppGoal gls')+ ]++-- | Intended for debugging, this is not generally a user-facing datatype.+instance (PP.Pretty asmp, PP.Pretty goal) => PP.Pretty (Goals asmp goal) where+ pretty = ppGoals PP.pretty PP.pretty -- | Construct a goal that first assumes a collection of -- assumptions and then states a goal.
src/Lang/Crucible/Backend/Online.hs view
@@ -4,19 +4,26 @@ -- Description : A solver backend that maintains a persistent connection -- Copyright : (c) Galois, Inc 2015-2016 -- License : BSD3--- Maintainer : Joe Hendrix <jhendrix@galois.com>+-- Maintainer : Ryan Scott <rscott@galois.com>, Langston Barrett <langston@galois.com> -- Stability : provisional ----- The online backend maintains an open connection to an SMT solver--- that is used to prune unsatisfiable execution traces during simulation.--- At every symbolic branch point, the SMT solver is queried to determine--- if one or both symbolic branches are unsatisfiable.--- Only branches with satisfiable branch conditions are explored.+-- A solver backend ('IsSymBackend') that maintains an open connection to an+-- SMT solver (in contrast to "Lang.Crucible.Backend.Simple"). ----- The online backend also allows override definitions access to a--- persistent SMT solver connection. This can be useful for some--- kinds of algorithms that benefit from quickly performing many--- small solver queries in a tight interaction loop.+-- The primary intended use-case is to prune unsatisfiable execution+-- traces during simulation using the execution feature provided by+-- "Lang.Crucible.Simulator.PathSatisfiability". That execution feature is+-- parameterized over a function argument that can be instantiated with this+-- module's 'considerSatisfiability'.+--+-- The online backend also allows override definitions access to a persistent+-- SMT solver connection. This can be useful for some kinds of algorithms+-- that benefit from quickly performing many small solver queries in a tight+-- interaction loop.+--+-- The online backend is not currently used to dispatch proof obligations during+-- symbolic execution, see [GaloisInc/crucible#369, \"Interleave proof with+-- simulation\"](https://github.com/GaloisInc/crucible/issues/369). ------------------------------------------------------------------------ {-# LANGUAGE DeriveDataTypeable #-}@@ -27,9 +34,14 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE ScopedTypeVariables #-}+ module Lang.Crucible.Backend.Online- ( -- * OnlineBackend- OnlineBackend+ ( -- * Configuration options+ solverInteractionFile+ , enableOnlineBackend+ , onlineBackendOptions+ -- * OnlineBackend+ , OnlineBackend , withOnlineBackend , newOnlineBackend , checkSatisfiable@@ -39,13 +51,10 @@ , restoreSolverState , UnsatFeatures(..) , unsatFeaturesToProblemFeatures- -- ** Configuration options- , solverInteractionFile- , enableOnlineBackend- , onlineBackendOptions- -- ** Branch satisfiability+ -- * Branch satisfiability , BranchResult(..) , considerSatisfiability+ -- * Backends for different solvers -- ** Yices , YicesOnlineBackend , withYicesOnlineBackend@@ -69,21 +78,19 @@ , withSTPOnlineBackend ) where --import Control.Lens ( (^.) ) import Control.Monad-import Control.Monad.Fix (mfix) import Control.Monad.Catch+import Control.Monad.Fix (mfix) import Control.Monad.IO.Class import Data.Bits import Data.Data (Data) import Data.Foldable import Data.IORef-import Data.Typeable (Typeable)-import GHC.Generics (Generic)-import System.IO import qualified Data.Text as Text+import GHC.Generics (Generic)+import Lens.Micro ((^.)) import qualified Prettyprinter as PP+import System.IO import What4.Config import What4.Concrete@@ -104,10 +111,13 @@ import qualified What4.Solver.Z3 as Z3 import Lang.Crucible.Backend-import Lang.Crucible.Backend.AssumptionStack as AS+import qualified Lang.Crucible.Backend.AssumptionStack as AS import qualified Lang.Crucible.Backend.ProofGoals as PG import Lang.Crucible.Simulator.SimError +--------------------------------------------------------------------------------+-- Configuration options+ data UnsatFeatures = NoUnsatFeatures -- ^ Do not compute unsat cores or assumptions@@ -173,6 +183,8 @@ -- ^ action for checking if online features are currently enabled , onlineExprBuilder :: B.ExprBuilder scope st fs++ , onlineExceptionContext :: !(Maybe ProgramStack) } newOnlineBackend ::@@ -181,7 +193,7 @@ ProblemFeatures -> IO (OnlineBackend solver scope st fs) newOnlineBackend sym feats =- do stk <- initAssumptionStack (sym ^. B.exprCounter)+ do stk <- AS.initAssumptionStack (sym ^. B.exprCounter) procref <- newIORef SolverNotStarted featref <- newIORef feats @@ -198,6 +210,7 @@ , currentFeatures = featref , onlineEnabled = getOpt enableOpt , onlineExprBuilder = sym+ , onlineExceptionContext = Nothing } -- | Do something with an online backend.@@ -224,176 +237,6 @@ ) -type YicesOnlineBackend scope st fs = OnlineBackend Yices.Connection scope st fs---- | Do something with a Yices online backend.--- The backend is only valid in the continuation.------ The Yices configuration options will be automatically--- installed into the backend configuration object.------ n.b. the explicit forall allows the fs to be expressed as the--- first argument so that it can be dictated easily from the caller.--- Example:------ > withYicesOnlineBackend FloatRealRepr ng f'-withYicesOnlineBackend ::- (MonadIO m, MonadMask m) =>- B.ExprBuilder scope st fs ->- UnsatFeatures ->- ProblemFeatures ->- (YicesOnlineBackend scope st fs -> m a) ->- m a-withYicesOnlineBackend sym unsatFeat extraFeatures action =- let feat = Yices.yicesDefaultFeatures .|. unsatFeaturesToProblemFeatures unsatFeat .|. extraFeatures in- withOnlineBackend sym feat $ \bak ->- do liftIO $ tryExtendConfig Yices.yicesOptions (getConfiguration sym)- action bak--type Z3OnlineBackend scope st fs = OnlineBackend (SMT2.Writer Z3.Z3) scope st fs---- | Do something with a Z3 online backend.--- The backend is only valid in the continuation.------ The Z3 configuration options will be automatically--- installed into the backend configuration object.------ n.b. the explicit forall allows the fs to be expressed as the--- first argument so that it can be dictated easily from the caller.--- Example:------ > withz3OnlineBackend FloatRealRepr ng f'-withZ3OnlineBackend ::- (MonadIO m, MonadMask m) =>- B.ExprBuilder scope st fs ->- UnsatFeatures ->- ProblemFeatures ->- (Z3OnlineBackend scope st fs -> m a) ->- m a-withZ3OnlineBackend sym unsatFeat extraFeatures action =- let feat = (SMT2.defaultFeatures Z3.Z3 .|. unsatFeaturesToProblemFeatures unsatFeat .|. extraFeatures) in- withOnlineBackend sym feat $ \bak ->- do liftIO $ tryExtendConfig Z3.z3Options (getConfiguration sym)- action bak--type BitwuzlaOnlineBackend scope st fs = OnlineBackend (SMT2.Writer Bitwuzla.Bitwuzla) scope st fs---- | Do something with a Bitwuzla online backend.--- The backend is only valid in the continuation.------ The Bitwuzla configuration options will be automatically--- installed into the backend configuration object.------ > withBitwuzlaOnineBackend FloatRealRepr ng f'-withBitwuzlaOnlineBackend ::- (MonadIO m, MonadMask m) =>- B.ExprBuilder scope st fs ->- UnsatFeatures ->- ProblemFeatures ->- (BitwuzlaOnlineBackend scope st fs -> m a) ->- m a-withBitwuzlaOnlineBackend sym unsatFeat extraFeatures action =- let feat = (SMT2.defaultFeatures Bitwuzla.Bitwuzla .|. unsatFeaturesToProblemFeatures unsatFeat .|. extraFeatures) in- withOnlineBackend sym feat $ \bak -> do- liftIO $ tryExtendConfig Bitwuzla.bitwuzlaOptions (getConfiguration sym)- action bak--type BoolectorOnlineBackend scope st fs = OnlineBackend (SMT2.Writer Boolector.Boolector) scope st fs---- | Do something with a Boolector online backend.--- The backend is only valid in the continuation.------ The Boolector configuration options will be automatically--- installed into the backend configuration object.------ > withBoolectorOnineBackend FloatRealRepr ng f'-withBoolectorOnlineBackend ::- (MonadIO m, MonadMask m) =>- B.ExprBuilder scope st fs ->- UnsatFeatures ->- (BoolectorOnlineBackend scope st fs -> m a) ->- m a-withBoolectorOnlineBackend sym unsatFeat action =- let feat = (SMT2.defaultFeatures Boolector.Boolector .|. unsatFeaturesToProblemFeatures unsatFeat) in- withOnlineBackend sym feat $ \bak -> do- liftIO $ tryExtendConfig Boolector.boolectorOptions (getConfiguration sym)- action bak--type CVC4OnlineBackend scope st fs = OnlineBackend (SMT2.Writer CVC4.CVC4) scope st fs---- | Do something with a CVC4 online backend.--- The backend is only valid in the continuation.------ The CVC4 configuration options will be automatically--- installed into the backend configuration object.------ n.b. the explicit forall allows the fs to be expressed as the--- first argument so that it can be dictated easily from the caller.--- Example:------ > withCVC4OnlineBackend FloatRealRepr ng f'-withCVC4OnlineBackend ::- (MonadIO m, MonadMask m) =>- B.ExprBuilder scope st fs ->- UnsatFeatures ->- ProblemFeatures ->- (CVC4OnlineBackend scope st fs -> m a) ->- m a-withCVC4OnlineBackend sym unsatFeat extraFeatures action =- let feat = (SMT2.defaultFeatures CVC4.CVC4 .|. unsatFeaturesToProblemFeatures unsatFeat .|. extraFeatures) in- withOnlineBackend sym feat $ \bak -> do- liftIO $ tryExtendConfig CVC4.cvc4Options (getConfiguration sym)- action bak--type CVC5OnlineBackend scope st fs = OnlineBackend (SMT2.Writer CVC5.CVC5) scope st fs---- | Do something with a CVC5 online backend.--- The backend is only valid in the continuation.------ The CVC5 configuration options will be automatically--- installed into the backend configuration object.------ n.b. the explicit forall allows the fs to be expressed as the--- first argument so that it can be dictated easily from the caller.--- Example:------ > withCVC5OnlineBackend FloatRealRepr ng f'-withCVC5OnlineBackend ::- (MonadIO m, MonadMask m) =>- B.ExprBuilder scope st fs ->- UnsatFeatures ->- ProblemFeatures ->- (CVC5OnlineBackend scope st fs -> m a) ->- m a-withCVC5OnlineBackend sym unsatFeat extraFeatures action =- let feat = (SMT2.defaultFeatures CVC5.CVC5 .|. unsatFeaturesToProblemFeatures unsatFeat .|. extraFeatures) in- withOnlineBackend sym feat $ \bak -> do- liftIO $ tryExtendConfig CVC5.cvc5Options (getConfiguration sym)- action bak--type STPOnlineBackend scope st fs = OnlineBackend (SMT2.Writer STP.STP) scope st fs---- | Do something with a STP online backend.--- The backend is only valid in the continuation.------ The STO configuration options will be automatically--- installed into the backend configuration object.------ n.b. the explicit forall allows the fs to be expressed as the--- first argument so that it can be dictated easily from the caller.--- Example:------ > withSTPOnlineBackend FloatRealRepr ng f'-withSTPOnlineBackend ::- (MonadIO m, MonadMask m) =>- B.ExprBuilder scope st fs ->- (STPOnlineBackend scope st fs -> m a) ->- m a-withSTPOnlineBackend sym action =- withOnlineBackend sym (SMT2.defaultFeatures STP.STP) $ \bak -> do- liftIO $ tryExtendConfig STP.stpOptions (getConfiguration sym)- action bak- -- | Shutdown any currently-active solver process. -- A fresh solver process will be started on the -- next call to `getSolverProcess`.@@ -497,30 +340,13 @@ withSolverConn bak k = withSolverProcess bak (pure ()) (k . solverConn) --- | Result of attempting to branch on a predicate.-data BranchResult- -- | The both branches of the predicate might be satisfiable- -- (although satisfiablility of either branch is not guaranteed).- = IndeterminateBranchResult-- -- | Commit to the branch where the given predicate is equal to- -- the returned boolean. The opposite branch is unsatisfiable- -- (although the given branch is not necessarily satisfiable).- | NoBranch !Bool-- -- | The context before considering the given predicate was already- -- unsatisfiable.- | UnsatisfiableContext- deriving (Data, Eq, Generic, Ord, Typeable)-- restoreAssumptionFrames :: OnlineSolver solver => OnlineBackend solver scope st fs -> SolverProcess scope solver ->- AssumptionFrames (CrucibleAssumptions (B.Expr scope)) ->+ AS.AssumptionFrames (CrucibleAssumptions (B.Expr scope)) -> IO ()-restoreAssumptionFrames bak proc (AssumptionFrames base frms) =+restoreAssumptionFrames bak proc (AS.AssumptionFrames base frms) = do let sym = onlineExprBuilder bak -- assume the base-level assumptions SMT.assume (solverConn proc) =<< assumptionsPred sym base@@ -530,29 +356,6 @@ do push proc SMT.assume (solverConn proc) =<< assumptionsPred sym frm -considerSatisfiability ::- OnlineSolver solver =>- OnlineBackend solver scope st fs ->- Maybe ProgramLoc ->- B.BoolExpr scope ->- IO BranchResult-considerSatisfiability bak mbPloc p =- let sym = onlineExprBuilder bak in- withSolverProcess bak (pure IndeterminateBranchResult) $ \proc ->- do pnot <- notPred sym p- let locDesc = case mbPloc of- Just ploc -> show (plSourceLoc ploc)- Nothing -> "(unknown location)"- let rsn = "branch sat: " ++ locDesc- p_res <- checkSatisfiable proc rsn p- pnot_res <- checkSatisfiable proc rsn pnot- case (p_res, pnot_res) of- (Unsat{}, Unsat{}) -> return UnsatisfiableContext- (_ , Unsat{}) -> return (NoBranch True)- (Unsat{}, _ ) -> return (NoBranch False)- _ -> return IndeterminateBranchResult-- instance HasSymInterface (B.ExprBuilder t st fs) (OnlineBackend solver t st fs) where backendGetSym = onlineExprBuilder @@ -587,7 +390,7 @@ withSolverConn bak $ \conn -> SMT.assume conn p -- Add assertions to list- appendAssumptions as (assumptionStack bak)+ AS.appendAssumptions as (assumptionStack bak) collectAssumptions bak = AS.collectAssumptions (assumptionStack bak)@@ -596,11 +399,11 @@ -- NB, don't push a frame in the assumption stack unless -- pushing to the solver succeeded do withSolverProcess bak (pure ()) push- pushFrame (assumptionStack bak)+ AS.pushFrame (assumptionStack bak) popAssumptionFrame bak ident = -- NB, pop the frame whether or not the solver pop succeeds- do frm <- popFrame ident (assumptionStack bak)+ do frm <- AS.popFrame ident (assumptionStack bak) withSolverProcess bak (pure ()) pop return frm @@ -612,7 +415,7 @@ popAssumptionFrameAndObligations bak ident = do -- NB, pop the frames whether or not the solver pop succeeds- do frmAndGls <- popFrameAndGoals ident (assumptionStack bak)+ do frmAndGls <- AS.popFrameAndGoals ident (assumptionStack bak) withSolverProcess bak (pure ()) pop return frmAndGls @@ -629,3 +432,188 @@ do restoreSolverState bak gc -- restore the previous assumption stack AS.restoreAssumptionStack gc (assumptionStack bak)++ getBackendState bak = readIORef (AS.proofObligations (assumptionStack bak))++ getExceptionContext = onlineExceptionContext+ withExceptionContext bak ec = bak { onlineExceptionContext = Just ec }++--------------------------------------------------------------------------------+-- Branch satisfiability++-- | Result of attempting to branch on a predicate.+data BranchResult+ -- | The both branches of the predicate might be satisfiable+ -- (although satisfiablility of either branch is not guaranteed).+ = IndeterminateBranchResult++ -- | Commit to the branch where the given predicate is equal to+ -- the returned boolean. The opposite branch is unsatisfiable+ -- (although the given branch is not necessarily satisfiable).+ | NoBranch !Bool++ -- | The context before considering the given predicate was already+ -- unsatisfiable.+ | UnsatisfiableContext+ deriving (Data, Eq, Generic, Ord)++considerSatisfiability ::+ OnlineSolver solver =>+ OnlineBackend solver scope st fs ->+ Maybe ProgramLoc ->+ B.BoolExpr scope ->+ IO BranchResult+considerSatisfiability bak mbPloc p =+ let sym = onlineExprBuilder bak in+ withSolverProcess bak (pure IndeterminateBranchResult) $ \proc ->+ do pnot <- notPred sym p+ let locDesc = case mbPloc of+ Just ploc -> show (plSourceLoc ploc)+ Nothing -> "(unknown location)"+ let rsn = "branch sat: " ++ locDesc+ p_res <- checkSatisfiable proc rsn p+ pnot_res <- checkSatisfiable proc rsn pnot+ case (p_res, pnot_res) of+ (Unsat{}, Unsat{}) -> return UnsatisfiableContext+ (_ , Unsat{}) -> return (NoBranch True)+ (Unsat{}, _ ) -> return (NoBranch False)+ _ -> return IndeterminateBranchResult++--------------------------------------------------------------------------------+-- Backends for different solvers++type YicesOnlineBackend scope st fs = OnlineBackend Yices.Connection scope st fs++-- | Do something with a Yices online backend.+-- The backend is only valid in the continuation.+--+-- The Yices configuration options will be automatically+-- installed into the backend configuration object.+withYicesOnlineBackend ::+ (MonadIO m, MonadMask m) =>+ B.ExprBuilder scope st fs ->+ UnsatFeatures ->+ ProblemFeatures ->+ (YicesOnlineBackend scope st fs -> m a) ->+ m a+withYicesOnlineBackend sym unsatFeat extraFeatures action =+ let feat = Yices.yicesDefaultFeatures .|. unsatFeaturesToProblemFeatures unsatFeat .|. extraFeatures in+ withOnlineBackend sym feat $ \bak ->+ do liftIO $ tryExtendConfig Yices.yicesOptions (getConfiguration sym)+ action bak++type Z3OnlineBackend scope st fs = OnlineBackend (SMT2.Writer Z3.Z3) scope st fs++-- | Do something with a Z3 online backend.+-- The backend is only valid in the continuation.+--+-- The Z3 configuration options will be automatically+-- installed into the backend configuration object.+withZ3OnlineBackend ::+ (MonadIO m, MonadMask m) =>+ B.ExprBuilder scope st fs ->+ UnsatFeatures ->+ ProblemFeatures ->+ (Z3OnlineBackend scope st fs -> m a) ->+ m a+withZ3OnlineBackend sym unsatFeat extraFeatures action =+ let feat = (SMT2.defaultFeatures Z3.Z3 .|. unsatFeaturesToProblemFeatures unsatFeat .|. extraFeatures) in+ withOnlineBackend sym feat $ \bak ->+ do liftIO $ tryExtendConfig Z3.z3Options (getConfiguration sym)+ action bak++type BitwuzlaOnlineBackend scope st fs = OnlineBackend (SMT2.Writer Bitwuzla.Bitwuzla) scope st fs++-- | Do something with a Bitwuzla online backend.+-- The backend is only valid in the continuation.+--+-- The Bitwuzla configuration options will be automatically+-- installed into the backend configuration object.+withBitwuzlaOnlineBackend ::+ (MonadIO m, MonadMask m) =>+ B.ExprBuilder scope st fs ->+ UnsatFeatures ->+ ProblemFeatures ->+ (BitwuzlaOnlineBackend scope st fs -> m a) ->+ m a+withBitwuzlaOnlineBackend sym unsatFeat extraFeatures action =+ let feat = (SMT2.defaultFeatures Bitwuzla.Bitwuzla .|. unsatFeaturesToProblemFeatures unsatFeat .|. extraFeatures) in+ withOnlineBackend sym feat $ \bak -> do+ liftIO $ tryExtendConfig Bitwuzla.bitwuzlaOptions (getConfiguration sym)+ action bak++type BoolectorOnlineBackend scope st fs = OnlineBackend (SMT2.Writer Boolector.Boolector) scope st fs++-- | Do something with a Boolector online backend.+-- The backend is only valid in the continuation.+--+-- The Boolector configuration options will be automatically+-- installed into the backend configuration object.+withBoolectorOnlineBackend ::+ (MonadIO m, MonadMask m) =>+ B.ExprBuilder scope st fs ->+ UnsatFeatures ->+ (BoolectorOnlineBackend scope st fs -> m a) ->+ m a+withBoolectorOnlineBackend sym unsatFeat action =+ let feat = (SMT2.defaultFeatures Boolector.Boolector .|. unsatFeaturesToProblemFeatures unsatFeat) in+ withOnlineBackend sym feat $ \bak -> do+ liftIO $ tryExtendConfig Boolector.boolectorOptions (getConfiguration sym)+ action bak++type CVC4OnlineBackend scope st fs = OnlineBackend (SMT2.Writer CVC4.CVC4) scope st fs++-- | Do something with a CVC4 online backend.+-- The backend is only valid in the continuation.+--+-- The CVC4 configuration options will be automatically+-- installed into the backend configuration object.+withCVC4OnlineBackend ::+ (MonadIO m, MonadMask m) =>+ B.ExprBuilder scope st fs ->+ UnsatFeatures ->+ ProblemFeatures ->+ (CVC4OnlineBackend scope st fs -> m a) ->+ m a+withCVC4OnlineBackend sym unsatFeat extraFeatures action =+ let feat = (SMT2.defaultFeatures CVC4.CVC4 .|. unsatFeaturesToProblemFeatures unsatFeat .|. extraFeatures) in+ withOnlineBackend sym feat $ \bak -> do+ liftIO $ tryExtendConfig CVC4.cvc4Options (getConfiguration sym)+ action bak++type CVC5OnlineBackend scope st fs = OnlineBackend (SMT2.Writer CVC5.CVC5) scope st fs++-- | Do something with a CVC5 online backend.+-- The backend is only valid in the continuation.+--+-- The CVC5 configuration options will be automatically+-- installed into the backend configuration object.+withCVC5OnlineBackend ::+ (MonadIO m, MonadMask m) =>+ B.ExprBuilder scope st fs ->+ UnsatFeatures ->+ ProblemFeatures ->+ (CVC5OnlineBackend scope st fs -> m a) ->+ m a+withCVC5OnlineBackend sym unsatFeat extraFeatures action =+ let feat = (SMT2.defaultFeatures CVC5.CVC5 .|. unsatFeaturesToProblemFeatures unsatFeat .|. extraFeatures) in+ withOnlineBackend sym feat $ \bak -> do+ liftIO $ tryExtendConfig CVC5.cvc5Options (getConfiguration sym)+ action bak++type STPOnlineBackend scope st fs = OnlineBackend (SMT2.Writer STP.STP) scope st fs++-- | Do something with a STP online backend.+-- The backend is only valid in the continuation.+--+-- The STO configuration options will be automatically+-- installed into the backend configuration object.+withSTPOnlineBackend ::+ (MonadIO m, MonadMask m) =>+ B.ExprBuilder scope st fs ->+ (STPOnlineBackend scope st fs -> m a) ->+ m a+withSTPOnlineBackend sym action =+ withOnlineBackend sym (SMT2.defaultFeatures STP.STP) $ \bak -> do+ liftIO $ tryExtendConfig STP.stpOptions (getConfiguration sym)+ action bak
src/Lang/Crucible/Backend/ProofGoals.hs view
@@ -3,10 +3,11 @@ Copyright : (c) Galois, Inc 2014-2018 License : BSD3 -This module defines a data strucutre for storing a collection of-proof obligations, and the current state of assumptions.+This module defines a data structure ('GoalCollector') for storing the current+state of assumptions and a collection of proof obligations. -} +{-# LANGUAGE LambdaCase #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TupleSections #-}@@ -22,6 +23,7 @@ -- * Goal collector , FrameIdentifier(..), GoalCollector , emptyGoalCollector+ , ppGoalCollector -- ** traversals , traverseGoalCollector@@ -29,7 +31,7 @@ -- ** Context management , gcAddAssumes, gcProve- , gcPush, gcPop, gcAddGoals,+ , gcPush, gcPop, gcAddGoals, gcAddTopLevelAssume, -- ** Global operations on context gcRemoveObligations, gcRestore, gcReset, gcFinish@@ -40,9 +42,11 @@ where import Control.Monad.Reader+import qualified Data.Foldable as F import Data.Sequence (Seq) import qualified Data.Sequence as Seq import Data.Word (Word64)+import qualified Prettyprinter as PP import Lang.Crucible.Backend.Goals @@ -55,16 +59,80 @@ deriving(Eq,Ord,Show) --- | A data-strucutre that can incrementally collect goals in context.+-- | A data-structure that can incrementally collect goals in context. -- It keeps track both of the collection of assumptions that lead to -- the current state, as well as any proof obligations incurred along -- the way.+--+-- The main use of 'GoalCollector' is as the state of an+-- 'Lang.Crucible.Backend.AssumptionStack.AssumptionStack', which itself is+-- part of the state of the simple and online backends.+--+-- 'GoalCollector' can be somewhat counter-intuitive. The "top"+-- ('TopCollector') is the *leaf* when 'GoalCollector' is considered as+-- a tree (which is a common way to conceptualize recursive algebraic+-- data types such as this one). A 'GoalCollector' is shaped like a+-- cons-list with three different cons-like constructors ('CollectorFrame',+-- 'CollectingAssumptions', and 'CollectingGoals') and one nil-like+-- constructor 'TopCollector'. That is to say, a 'GoalCollector' is a sequence+-- that always ends in a single 'TopCollector'.+--+-- Furthermore, the frame identified by the first ('FrameIdentifier') argument+-- of 'CollectorFrame' does not conceptually contain the goals *inside* the+-- second ('GoalCollector') argument, but rather contains all the assumptions+-- and goals in whatever 'GoalCollector' *contains* the 'CollectorFrame'+-- constructor (everything *outside* of the 'CollectorFrame'). Concretely, in+-- the expression+-- @+-- 'CollectingGoals' gls ('CollectingAssumptions' asmps ('CollectorFrame' frm ('TopCollector' gls0)))+-- @+-- the goals @gls@ and assumptions @asmps@ are in the frame @frm@, rather than+-- the top-level goals @gls0@.+--+-- This inside-out structure is reflected in the pretty-printer+-- 'ppGoalCollector' below. The Crucible-CLI test-case @assumption-state@+-- shows this pretty-printer in action in a Crucible program with branching,+-- which can be helpful in understanding 'GoalCollector'. data GoalCollector asmp goal = TopCollector !(Seq (Goals asmp goal)) | CollectorFrame !FrameIdentifier !(GoalCollector asmp goal) | CollectingAssumptions !asmp !(GoalCollector asmp goal) | CollectingGoals !(Seq (Goals asmp goal)) !(GoalCollector asmp goal) +ppGoalCollector ::+ forall asmp goal ann.+ (asmp -> PP.Doc ann) ->+ (goal -> PP.Doc ann) ->+ GoalCollector asmp goal ->+ PP.Doc ann+ppGoalCollector ppAsmp ppGoal = go mempty+ where+ go :: PP.Doc ann -> GoalCollector asmp goal -> PP.Doc ann+ go remainder =+ \case+ TopCollector gls ->+ PP.vcat+ [ PP.pretty "Top-level goals:"+ , PP.list (map (ppGoals ppAsmp ppGoal) (F.toList gls))+ , remainder+ ]+ CollectorFrame (FrameIdentifier fid) gc ->+ let pLines = [PP.pretty "Frame " <> PP.viaShow fid <> PP.pretty ":", remainder] in+ go (PP.hang 2 (PP.vcat pLines)) gc+ CollectingAssumptions asmp gc ->+ let pLines = [PP.pretty "Assumptions:" , ppAsmp asmp, remainder] in+ go (PP.hang 2 (PP.vcat pLines)) gc+ CollectingGoals gls gc ->+ let pLines = [ PP.pretty "Prove all:"+ , PP.list (map (ppGoals ppAsmp ppGoal) (F.toList gls))+ , remainder+ ] in+ go (PP.hang 2 (PP.vcat pLines)) gc++-- | Intended for debugging, this is not generally a user-facing datatype.+instance (PP.Pretty asmp, PP.Pretty goal) => PP.Pretty (GoalCollector asmp goal) where+ pretty = ppGoalCollector PP.pretty PP.pretty+ -- | A collector with no goals and no context. emptyGoalCollector :: GoalCollector asmp goal emptyGoalCollector = TopCollector mempty@@ -141,6 +209,30 @@ gcAddGoals g (TopCollector gs) = TopCollector (gs Seq.|> g) gcAddGoals g (CollectingGoals gs gc) = CollectingGoals (gs Seq.|> g) gc gcAddGoals g gc = CollectingGoals (Seq.singleton g) gc++-- | Add an assumption that is in scope for all goals, even ones in earlier+-- frames.+gcAddTopLevelAssume ::+ Monoid asmp =>+ asmp ->+ GoalCollector asmp goal ->+ GoalCollector asmp goal+gcAddTopLevelAssume asmp =+ \case+ TopCollector gls ->+ -- Syntactically, it appears that `asmp` is duplicated here, perhaps+ -- unnecessarily. In fact, this is necessary. The `CollectingAssumptions`+ -- constructor brings the assumption into scope for all the goals+ -- *outside* of the top-level (see the comment on `GoalCollector` for+ -- the "inside-out" structure of `GoalCollector`), whereas the `assuming`+ -- brings it into scope for top-level goals.+ CollectingAssumptions asmp (TopCollector (assuming asmp <$> gls))+ CollectorFrame frm gc ->+ CollectorFrame frm (gcAddTopLevelAssume asmp gc)+ CollectingAssumptions asmp' gc ->+ CollectingAssumptions asmp' (gcAddTopLevelAssume asmp gc)+ CollectingGoals gls gc ->+ CollectingGoals gls (gcAddTopLevelAssume asmp gc) -- | Add a new proof obligation to the current context. gcProve :: goal -> GoalCollector asmp goal -> GoalCollector asmp goal
src/Lang/Crucible/Backend/Prove.hs view
@@ -73,11 +73,11 @@ , proveCurrentObligations ) where -import Control.Lens ((^.)) import Control.Monad.Catch (MonadMask) import Control.Monad.Error.Class (MonadError, liftEither) import Control.Monad.IO.Class (MonadIO(liftIO)) import qualified Control.Monad.Reader as Reader+import Lens.Micro ((^.)) import qualified What4.Interface as W4 import qualified What4.Expr as WE@@ -111,19 +111,21 @@ consumeGoalsWithAssumptions :: forall asmp goal a. Monoid asmp =>- -- | Consume a 'Prove'+ -- | What to do at 'Assuming' nodes (e.g., call 'proverAssume')+ (asmp -> a -> a) ->+ -- | Consume a 'Prove' with accumulated assumptions (asmp -> goal -> a) -> -- | Consume a 'ProveConj' (a -> a -> a) -> CB.Goals asmp goal -> a-consumeGoalsWithAssumptions onGoal onConj goals =+consumeGoalsWithAssumptions onAssumption onGoal onConj goals = Reader.runReader (go goals) mempty where go :: CB.Goals asmp goal -> Reader.Reader asmp a go = consumeGoals- (\asmp gl -> Reader.local (<> asmp) gl)+ (\asmp gl -> onAssumption asmp <$> Reader.local (<> asmp) gl) (\gl -> Reader.asks (\asmps -> onGoal asmps gl)) (\g1 g2 -> onConj <$> g1 <*> g2) @@ -255,6 +257,44 @@ --------------------------------------------------------------------- -- *** Offline +-- | Check if a goal is trivially true without calling the solver.+--+-- Returns a @Right@ with proof result if the goal is trivially true, or+-- @Left (asmps and not goal)@ if consulting the solver is needed. See the+-- module-level Haddock for intepreting @asmps and not goal@.+--+-- We don't optimize trivially false goals because we need the solver to provide+-- concrete counterexample values ('WE.GroundEvalFn') for debugging and error+-- reporting.+--+-- You might wonder if this is redundant with e.g., the check in @Backend@'s+-- @addProofObligation@ or with 'Crucible.Backend.Assumption.trivialAssumption'.+-- It is not. Consider a situation with fresh booleans @p@ and @q@ and+-- assumptions @p@ and @not p@ and goal @q@. What4's simplifications allow us+-- to conclude that the conjunction of the assumptions and the negation of the+-- goal is trivially unsatisfiable, even though each assumption and the goal are+-- individually nontrivial.+checkTrivialGoal ::+ W4.IsSymExprBuilder sym =>+ sym ->+ Assumptions sym ->+ CB.Assertion sym ->+ ProofConsumer sym t r ->+ IO (Either (W4.Pred sym) (SubgoalResult r))+checkTrivialGoal sym asmps goal (ProofConsumer k) = do+ -- See module Haddock for intepreting (asmps and not goal)+ asmpsPred <- CB.assumptionsPred sym asmps+ notGoal <- W4.notPred sym (goal ^. CB.labeledPred)+ asmpsAndNotGoal <- W4.andPred sym asmpsPred notGoal++ case W4.asConstantPred asmpsAndNotGoal of+ Just False -> -- false = definitely unsat = proved+ let r' = Proved in+ Right <$> (SubgoalResult (isProved r') <$> k (CB.ProofGoal asmps goal) r')+ _ ->+ -- return this so that offlineProve doesn't need to re-compute it+ pure (Left asmpsAndNotGoal)+ -- Not exported offlineProveIO :: (sym ~ WE.ExprBuilder t st fs) =>@@ -266,17 +306,18 @@ CB.Assertion sym -> ProofConsumer sym t r -> IO (SubgoalResult r)-offlineProveIO sym ld adapter asmps goal (ProofConsumer k) = do- let goalPred = goal ^. CB.labeledPred- asmsPred <- CB.assumptionsPred sym asmps- notGoal <- W4.notPred sym goalPred- WSA.solver_adapter_check_sat adapter sym ld [asmsPred, notGoal] $ \r ->- let r' =- case r of- W4R.Sat (gfn, binds) -> Disproved gfn binds- W4R.Unsat () -> Proved- W4R.Unknown -> Unknown- in SubgoalResult (isProved r') <$> k (CB.ProofGoal asmps goal) r'+offlineProveIO sym ld adapter asmps goal k@(ProofConsumer kFn) =+ checkTrivialGoal sym asmps goal k >>=+ \case+ Right result -> pure result+ Left asmpsAndNotGoal ->+ WSA.solver_adapter_check_sat adapter sym ld [asmpsAndNotGoal] $ \r ->+ let r' =+ case r of+ W4R.Sat (gfn, binds) -> Disproved gfn binds+ W4R.Unsat () -> Proved+ W4R.Unknown -> Unknown+ in SubgoalResult (isProved r') <$> kFn (CB.ProofGoal asmps goal) r' -- | Prove a goal using an \"offline\" solver (i.e., one process per goal). --@@ -353,19 +394,27 @@ W4SMT.SMTReadWriter solver => (sym ~ WE.ExprBuilder t st fs) => W4.IsSymExprBuilder sym =>+ sym -> WPO.SolverProcess t solver -> Assumptions sym -> CB.Assertion sym -> ProofConsumer sym t r -> m (SubgoalResult r)-onlineProve sProc asmps goal (ProofConsumer k) =- liftIO $ WPO.checkSatisfiableWithModel sProc "prove" (goal ^. CB.labeledPred) $ \r ->- let r' =- case r of- W4R.Sat gfn -> Disproved gfn Nothing- W4R.Unsat () -> Proved- W4R.Unknown -> Unknown- in SubgoalResult (isProved r') <$> k (CB.ProofGoal asmps goal) r'+onlineProve sym sProc asmps goal k@(ProofConsumer kFn) =+ liftIO (checkTrivialGoal sym asmps goal k) >>=+ \case+ Right result -> pure result+ Left _ -> liftIO $ do+ -- Note: assumptions are established via proverAssume before this is called+ let goalPred = goal ^. CB.labeledPred+ notGoal <- W4.notPred sym goalPred+ WPO.checkSatisfiableWithModel sProc "prove" notGoal $ \r ->+ let r' =+ case r of+ W4R.Sat gfn -> Disproved gfn Nothing+ W4R.Unsat () -> Proved+ W4R.Unknown -> Unknown+ in SubgoalResult (isProved r') <$> kFn (CB.ProofGoal asmps goal) r' -- | Add an assumption by @push@ing a new frame ('WPO.inNewFrame'). onlineAssume :: @@ -404,7 +453,7 @@ Prover sym m t r onlineProver sym sProc = Prover- { proverProve = onlineProve sProc+ { proverProve = onlineProve sym sProc , proverAssume = onlineAssume sym sProc } @@ -421,6 +470,7 @@ proveGoals (ProofStrategy prover (Combiner comb)) goals k = fmap subgoalResult $ consumeGoalsWithAssumptions+ (proverAssume prover) (\asmps gl -> proverProve prover asmps gl k) comb goals
src/Lang/Crucible/Backend/Simple.hs view
@@ -4,13 +4,13 @@ -- Description : The "simple" solver backend -- Copyright : (c) Galois, Inc 2015-2016 -- License : BSD3--- Maintainer : Rob Dockins <rdockins@galois.com>+-- Maintainer : Ryan Scott <rscott@galois.com>, Langston Barrett <langston@galois.com> -- Stability : provisional ----- An "offline" backend for communicating with solvers. This backend--- does not maintain a persistent connection to a solver, and does--- not perform satisfiability checks at symbolic branch points.-------------------------------------------------------------------------+-- An "offline" backend for communicating with SMT solvers. In contrast to+-- "Lang.Crucible.Backend.Online", this backend does not maintain a persistent+-- connection to a solver.+-- ---------------------------------------------------------------------- {-# LANGUAGE GADTs #-} {-# LANGUAGE FlexibleContexts #-}@@ -30,8 +30,9 @@ , B.Flags ) where -import Control.Lens ( (^.) ) import Control.Monad (void)+import Data.IORef (readIORef)+import Lens.Micro ((^.)) import What4.Config import What4.Interface@@ -42,7 +43,7 @@ import Lang.Crucible.Simulator.SimError --------------------------------------------------------------------------- SimpleBackendState+-- SimpleBackend -- | This represents the state of the backend along a given execution. -- It contains the current assertion stack.@@ -55,8 +56,10 @@ SimpleBackend { sbAssumptionStack :: AS t , sbExprBuilder :: B.ExprBuilder t st fs+ , sbExceptionContext :: Maybe ProgramStack } + newSimpleBackend :: B.ExprBuilder t st fs -> IO (SimpleBackend t st fs)@@ -66,6 +69,7 @@ return SimpleBackend { sbAssumptionStack = as , sbExprBuilder = sym+ , sbExceptionContext = Nothing } instance HasSymInterface (B.ExprBuilder t st fs) (SimpleBackend t st fs) where@@ -111,3 +115,8 @@ restoreAssumptionState bak newstk = do AS.restoreAssumptionStack newstk (sbAssumptionStack bak)++ getBackendState bak = readIORef (AS.proofObligations (sbAssumptionStack bak))++ withExceptionContext bak ec = bak { sbExceptionContext = Just ec }+ getExceptionContext = sbExceptionContext
src/Lang/Crucible/CFG/Common.hs view
@@ -15,7 +15,7 @@ ( -- * Global variables GlobalVar(..) , freshGlobalVar- , BreakpointName(..)+ , CutpointName(..) ) where import Data.Text (Text)@@ -65,8 +65,8 @@ , globalType = tp } -newtype BreakpointName = BreakpointName { breakpointNameText :: Text }+newtype CutpointName = CutpointName { cutpointNameText :: Text } deriving (Eq, Ord, Show) -instance Pretty BreakpointName where- pretty = pretty . breakpointNameText+instance Pretty CutpointName where+ pretty = pretty . cutpointNameText
src/Lang/Crucible/CFG/Core.hs view
@@ -91,7 +91,6 @@ ) where import Control.Applicative-import Control.Lens import Data.Bimap (Bimap) import Data.Maybe (fromMaybe) import Data.Kind (Type)@@ -100,6 +99,7 @@ import Data.Parameterized.Some import Data.Parameterized.TraversableFC import Data.String+import Lens.Micro ((^.), Lens', lens) import Prettyprinter import What4.ProgramLoc@@ -673,19 +673,19 @@ -- ^ The sequence of statements in this block } -blockStmts :: Simple Lens (Block ext b r c) (StmtSeq ext b r c)+blockStmts :: Lens' (Block ext b r c) (StmtSeq ext b r c) blockStmts = lens _blockStmts (\b s -> b { _blockStmts = s }) -- | Return location of start of block. blockLoc :: Block ext blocks ret ctx -> ProgramLoc-blockLoc b = firstStmtLoc (b^.blockStmts)+blockLoc b = firstStmtLoc (b ^. blockStmts) -- | Get the terminal statement of a basic block. This is implemented -- in a CPS style due to the block context. withBlockTermStmt :: Block ext blocks ret args -> (forall ctx . ProgramLoc -> TermStmt blocks ret ctx -> r) -> r-withBlockTermStmt b f = getConst (stmtSeqTermStmt (Const . uncurry f) (b^.blockStmts))+withBlockTermStmt b f = getConst (stmtSeqTermStmt (Const . uncurry f) (b ^. blockStmts)) nextBlocks :: Block ext b r a -> [Some (BlockID b)] nextBlocks b =@@ -709,7 +709,7 @@ -- ^ Block to print. -> Doc ann ppBlock ppLineNumbers ppBlockArgs mPda b = do- let stmts = ppStmtSeq ppLineNumbers (blockInputCount b) (b^.blockStmts)+ let stmts = ppStmtSeq ppLineNumbers (blockInputCount b) (b ^. blockStmts) let mPostdom = flip fmap mPda $ \ pda -> let Const pd = pda ! blockIDIndex (blockID b) in if Prelude.null pd@@ -781,7 +781,7 @@ = CFG { cfgHandle :: FnHandle init ret , cfgBlockMap :: !(BlockMap ext blocks ret) , cfgEntryBlockID :: !(BlockID blocks init)- , cfgBreakpoints :: !(Bimap BreakpointName (Some (BlockID blocks)))+ , cfgCutpoints :: !(Bimap CutpointName (Some (BlockID blocks))) } cfgArgTypes :: CFG ext blocks init ret -> CtxRepr init
src/Lang/Crucible/CFG/EarlyMergeLoops.hs view
@@ -56,7 +56,7 @@ import Lang.Crucible.CFG.Expr import Lang.Crucible.CFG.Reg-import Lang.Crucible.Panic+import Lang.Crucible.Panic (panic) import Lang.Crucible.Types --------------------------@@ -342,7 +342,7 @@ Print {} -> orig Assert {} -> orig Assume {} -> orig- Breakpoint {} -> orig+ Cut {} -> orig where orig = pure (Seq.fromList [st])@@ -380,7 +380,7 @@ Print {} -> lowerAtomReads ng pvals atomsToLower st Assert {} -> lowerAtomReads ng pvals atomsToLower st Assume {} -> lowerAtomReads ng pvals atomsToLower st- Breakpoint {} -> lowerAtomReads ng pvals atomsToLower st+ Cut {} -> lowerAtomReads ng pvals atomsToLower st where atomsToLower :: [Some (Atom s)] atomsToLower = Set.toList (foldStmtInputs addIfLowered (pos_val st) mempty)@@ -758,7 +758,7 @@ | Just Refl <- testEquality (lambdaId ll) (lambdaId l1) -> Output l1 (lambdaAtom l2) (_, _:rest) -> bidToTerm origOut rest _ ->- error "Output blocks mismatched in routePaths"+ panic "routePaths" ["Output blocks mismatched"] mkMapping = Ctx.generateM sz $ \idx -> do n <- freshNonce ng
src/Lang/Crucible/CFG/Expr.hs view
@@ -402,6 +402,11 @@ -> !RoundingMode -> !(f (FloatType fi')) -> App ext f (FloatType fi)+ FloatRound+ :: !(FloatInfoRepr fi)+ -> !RoundingMode+ -> !(f (FloatType fi))+ -> App ext f (FloatType fi) FloatFromBinary :: !(FloatInfoRepr fi) -> !(f (BVType (FloatInfoToBitWidth fi)))@@ -530,6 +535,11 @@ -> !(f (SequenceType tp)) -> App ext f (MaybeType (StructType (EmptyCtx ::> tp ::> SequenceType tp))) + -- Reverse a sequence+ SequenceReverse :: !(TypeRepr tp)+ -> !(f (SequenceType tp))+ -> App ext f (SequenceType tp)+ ---------------------------------------------------------------------- -- Vector @@ -577,7 +587,7 @@ -- Create a closure that captures the last argument. Closure :: !(CtxRepr args) -> !(TypeRepr ret)- -> !(f (FunctionHandleType (args::>tp) ret))+ -> !(f (FunctionHandleType (args ::> tp) ret)) -> !(TypeRepr tp) -> !(f tp) -> App ext f (FunctionHandleType args ret)@@ -628,12 +638,12 @@ BVLit :: (1 <= w) => NatRepr w -> BV.BV w -> App ext f (BVType w) -- concatenate two bitvectors- BVConcat :: (1 <= u, 1 <= v, 1 <= u+v)+ BVConcat :: (1 <= u, 1 <= v, 1 <= u + v) => !(NatRepr u) -> !(NatRepr v) -> !(f (BVType u)) -- Most significant bits -> !(f (BVType v)) -- Least significant bits- -> App ext f (BVType (u+v))+ -> App ext f (BVType (u + v)) -- BVSelect idx n bv chooses bits [idx, .. , idx+n-1] from bitvector bv. -- The resulting bitvector will have width n.@@ -645,19 +655,19 @@ -> !(f (BVType w)) -> App ext f (BVType len) - BVTrunc :: (1 <= r, r+1 <= w)+ BVTrunc :: (1 <= r, r + 1 <= w) => !(NatRepr r) -> !(NatRepr w) -> !(f (BVType w)) -> App ext f (BVType r) - BVZext :: (1 <= w, 1 <= r, w+1 <= r)+ BVZext :: (1 <= w, 1 <= r, w + 1 <= r) => !(NatRepr r) -> !(NatRepr w) -> !(f (BVType w)) -> App ext f (BVType r) - BVSext :: (1 <= w, 1 <= r, w+1 <= r)+ BVSext :: (1 <= w, 1 <= r, w + 1 <= r) => !(NatRepr r) -> !(NatRepr w) -> !(f (BVType w))@@ -1176,6 +1186,7 @@ FloatFpApart{} -> knownRepr FloatIte fi _ _ _ -> FloatRepr fi FloatCast fi _ _ -> FloatRepr fi+ FloatRound fi _ _ -> FloatRepr fi FloatFromBinary fi _ -> FloatRepr fi FloatToBinary fi _ -> case floatInfoToBVTypeRepr fi of BaseBVRepr w -> BVRepr w@@ -1227,6 +1238,7 @@ MaybeRepr (StructRepr (Ctx.Empty Ctx.:> tpr Ctx.:> SequenceRepr tpr)) SequenceLength{} -> knownRepr SequenceTail tpr _ -> MaybeRepr (SequenceRepr tpr)+ SequenceReverse tpr _ -> SequenceRepr tpr ---------------------------------------------------------------------- -- SymbolicArrayType
src/Lang/Crucible/CFG/ExtractSubgraph.hs view
@@ -17,19 +17,20 @@ ( extractSubgraph ) where -import Control.Lens import qualified Data.Bimap as Bimap import Data.Parameterized.Context as Ctx import Data.Parameterized.Map as MapF import Data.Set as S import qualified Data.Map as Map import Debug.Trace+import Lens.Micro ((^.)) import What4.FunctionName import What4.ProgramLoc import Lang.Crucible.CFG.Core import Lang.Crucible.FunctionHandle+import Lang.Crucible.Panic (panic) -- | Given a CFG @cfg@, a set of blocks @cuts@ that take the return type as their sole -- argument, and a block @bi@ that takes the CFG's init type as its sole argument,@@ -43,7 +44,7 @@ -> BlockID blocks init -> HandleAllocator -> IO (Maybe (SomeCFG ext init ret))-extractSubgraph (CFG{cfgBlockMap = orig, cfgBreakpoints = breakpoints}) cuts bi halloc =+extractSubgraph (CFG{cfgBlockMap = orig, cfgCutpoints = cutpoints}) cuts bi halloc = extractSubgraphFirst orig cuts MapF.empty zeroSize bi $ \(SubgraphIntermediate finalMap finalInitMap _sz entryID cb) -> do hn <- mkHandle halloc startFunctionName@@ -53,9 +54,9 @@ { cfgBlockMap = bm , cfgEntryBlockID = entryID , cfgHandle = hn- , cfgBreakpoints = Bimap.fromList $ Map.toList $+ , cfgCutpoints = Bimap.fromList $ Map.toList $ Map.mapMaybe (viewSome $ \bid -> Some <$> MapF.lookup bid finalMap) $- Bimap.toMap breakpoints+ Bimap.toMap cutpoints } -- | Type for carrying intermediate results through subraph extraction@@ -95,7 +96,7 @@ visitChildNode orig cuts bi1 sgi1 $ \sgi2 -> visitChildNode orig cuts bi2 sgi2 f Return _ -> f- _ -> error "extractSubgraph': unexpected case!")+ _ -> panic "extractSubgraph'" ["unexpected case!"]) (SubgraphIntermediate (MapF.insert bi (BlockID $ nextIndex sz) (MapF.map extendBlockID mapF)) (MapF.map extendBlockID initMap)@@ -126,7 +127,7 @@ visitChildNode orig cuts bi1 sgi1 $ \sgi2 -> visitChildNode orig cuts bi2 sgi2 f Return _ -> f- _ -> error "extractSubgraphFirst: unexpected case!")+ _ -> panic "extractSubgraphFirst" ["unexpected case!"]) (SubgraphIntermediate (if case S.minView cuts of Just (bi', _) -> case testEquality (blockInputs block) (blockInputs $ orig Ctx.! blockIDIndex bi') of@@ -194,7 +195,7 @@ cloneBlock :: MapF (BlockID old) (BlockID new) -> BlockID new ctx -> Block ext old ret ctx -> Maybe (Block ext new ret ctx) cloneBlock mapF newID b = do- stmts' <- cloneStmtSeq mapF (b^.blockStmts)+ stmts' <- cloneStmtSeq mapF (b ^. blockStmts) return Block{ blockID = newID , blockInputs = blockInputs b , _blockStmts = stmts'@@ -215,7 +216,7 @@ jt2' <- cloneJumpTarget mapF jt2 return $ Br reg jt1' jt2' cloneTerm _mapF (Return reg) = Just $ Return reg-cloneTerm _ _ = error "cloneTerm: unexpected case!"+cloneTerm _ _ = panic "cloneTerm" ["unexpected case!"] cloneJumpTarget :: MapF (BlockID blocks1) (BlockID blocks2) -> JumpTarget blocks1 t
src/Lang/Crucible/CFG/Generator.hs view
@@ -60,7 +60,7 @@ , assertExpr , assumeExpr , addPrintStmt- , addBreakpointStmt+ , addCutStmt , extensionStmt , mkAtom , mkFresh@@ -109,7 +109,6 @@ , module Lang.Crucible.CFG.EarlyMergeLoops ) where -import Control.Lens hiding (Index) import Control.Monad ((>=>)) import qualified Control.Monad.Fail as F import Control.Monad.IO.Class (MonadIO(..))@@ -117,6 +116,7 @@ import Control.Monad.Trans.Class (MonadTrans(..)) import Control.Monad.Catch import qualified Data.Foldable as Fold+import Data.Function ((&)) import Data.Kind import Data.Parameterized.Context as Ctx import Data.Parameterized.Nonce@@ -127,6 +127,8 @@ import qualified Data.Set as Set import Data.Text (Text) import Data.Void+import Lens.Micro ((^.), (.~), (%~), Lens', Lens, SimpleGetter, lens, to)+import Lens.Micro.Mtl (use, (.=), (%=)) import What4.ProgramLoc import What4.Symbol@@ -162,7 +164,7 @@ } -- | Statements translated so far in this block.-cbsStmts :: Simple Lens (CurrentBlockState ext s) (StmtSeq ext s)+cbsStmts :: Lens' (CurrentBlockState ext s) (StmtSeq ext s) cbsStmts = lens _cbsStmts (\s v -> s { _cbsStmts = v }) ------------------------------------------------------------------------@@ -186,14 +188,14 @@ IxGeneratorState ext s t ret m () -- | Label for entry block.-gsEntryLabel :: Getter (IxGeneratorState ext s t ret m i) (Label s)+gsEntryLabel :: SimpleGetter (IxGeneratorState ext s t ret m i) (Label s) gsEntryLabel = to _gsEntryLabel -- | List of previously processed blocks.-gsBlocks :: Simple Lens (IxGeneratorState ext s t ret m i) (Seq (Block ext s ret))+gsBlocks :: Lens' (IxGeneratorState ext s t ret m i) (Seq (Block ext s ret)) gsBlocks = lens _gsBlocks (\s v -> s { _gsBlocks = v }) -gsNonceGen :: Getter (IxGeneratorState ext s t ret m i) (NonceGenerator m s)+gsNonceGen :: SimpleGetter (IxGeneratorState ext s t ret m i) (NonceGenerator m s) gsNonceGen = to _gsNonceGen -- | Information about current block.@@ -201,15 +203,15 @@ gsCurrent = lens _gsCurrent (\s v -> s { _gsCurrent = v }) -- | Current source position.-gsPosition :: Simple Lens (IxGeneratorState ext s t ret m i) Position+gsPosition :: Lens' (IxGeneratorState ext s t ret m i) Position gsPosition = lens _gsPosition (\s v -> s { _gsPosition = v }) -- | User state for current block. This gets reset between blocks.-gsState :: Simple Lens (IxGeneratorState ext s t ret m i) (t s)+gsState :: Lens' (IxGeneratorState ext s t ret m i) (t s) gsState = lens _gsState (\s v -> s { _gsState = v }) -- | List of functions seen by current generator.-seenFunctions :: Simple Lens (IxGeneratorState ext s t ret m i) [AnyCFG ext]+seenFunctions :: Lens' (IxGeneratorState ext s t ret m i) [AnyCFG ext] seenFunctions = lens _seenFunctions (\s v -> s { _seenFunctions = v }) ------------------------------------------------------------------------@@ -229,10 +231,10 @@ GeneratorState ext s t ret m -> EndState ext s t ret m terminateBlock term gs =- do let p = gs^.gsPosition- let cbs = gs^.gsCurrent+ do let p = gs ^. gsPosition+ let cbs = gs ^. gsCurrent -- Define block- let b = mkBlock (cbsBlockID cbs) (cbsInputValues cbs) (cbs^.cbsStmts) (Posd p term)+ let b = mkBlock (cbsBlockID cbs) (cbsInputValues cbs) (cbs ^. cbsStmts) (Posd p term) -- Store block let gs' = gs & gsCurrent .~ () & gsBlocks %~ (Seq.|> b)@@ -455,13 +457,13 @@ do e_a <- mkAtom e addStmt (Print e_a) --- | Add a breakpoint.-addBreakpointStmt ::+-- | Add a cutpoint.+addCutStmt :: (Monad m, IsSyntaxExtension ext) =>- Text {- ^ breakpoint name -} ->- Assignment (Value s) args {- ^ breakpoint values -} ->+ Text {- ^ cutpoint name -} ->+ Assignment (Value s) args {- ^ cutpoint values -} -> Generator ext s t r m ()-addBreakpointStmt nm args = addStmt $ Breakpoint (BreakpointName nm) args+addCutStmt nm args = addStmt $ Cut (CutpointName nm) args -- | Add an assert statement. assertExpr ::@@ -565,8 +567,8 @@ do let gs1 = startBlock l (gs0 & gsCurrent .~ ()) gs2 <- runGenerator next gs1 -- Reset current block and state.- let gs3 = gs2 & gsPosition .~ gs0^.gsPosition- & gsCurrent .~ gs0^.gsCurrent+ let gs3 = gs2 & gsPosition .~ gs0 ^. gsPosition+ & gsCurrent .~ gs0 ^. gsCurrent cont () gs3 -- | Define a block with an ordinary label.@@ -891,8 +893,8 @@ -> CFG ext s init ret cfgFromGenerator h s = CFG { cfgHandle = h- , cfgEntryLabel = s^.gsEntryLabel- , cfgBlocks = Fold.toList (s^.gsBlocks)+ , cfgEntryLabel = s ^. gsEntryLabel+ , cfgBlocks = Fold.toList (s ^. gsBlocks) } -- | Given the arguments, this returns the initial state, and an action for@@ -953,4 +955,4 @@ } ts' <- runGenerator (action >>= returnFromFunction) $! ts g <- optPass ng (cfgFromGenerator h ts')- return (SomeCFG g, ts'^.seenFunctions)+ return (SomeCFG g, ts' ^. seenFunctions)
src/Lang/Crucible/CFG/Reg.hs view
@@ -586,7 +586,7 @@ | Assert !(Atom s BoolType) !(Atom s (StringType Unicode)) -- | Assume the given expression. | Assume !(Atom s BoolType) !(Atom s (StringType Unicode))- | forall args . Breakpoint BreakpointName !(Assignment (Value s) args)+ | forall args . Cut CutpointName !(Assignment (Value s) args) instance PrettyExt ext => Show (Stmt ext s) where show = show . pretty@@ -602,7 +602,7 @@ Print v -> "print" <+> pretty v Assert c m -> "assert" <+> pretty c <+> pretty m Assume c m -> "assume" <+> pretty c <+> pretty m- Breakpoint nm args -> "breakpoint" <+> pretty nm <+> parens (commas (toListFC pretty args))+ Cut nm args -> "cut" <+> pretty nm <+> parens (commas (toListFC pretty args)) -- | Return local value assigned by this statement or @Nothing@ if this -- does not modify a register.@@ -617,7 +617,7 @@ Print{} -> Nothing Assert{} -> Nothing Assume{} -> Nothing- Breakpoint{} -> Nothing+ Cut{} -> Nothing -- | Fold all registers that are inputs tostmt. foldStmtInputs :: TraverseExt ext => (forall x . Value s x -> b -> b) -> Stmt ext s -> b -> b@@ -631,7 +631,7 @@ Print e -> f (AtomValue e) b Assert c m -> f (AtomValue c) (f (AtomValue m) b) Assume c m -> f (AtomValue c) (f (AtomValue m) b)- Breakpoint _ args -> foldrFC' f b args+ Cut _ args -> foldrFC' f b args substStmt :: ( Applicative m, TraverseExt ext ) => (forall (x :: CrucibleType). Nonce s x -> m (Nonce s' x))@@ -647,7 +647,7 @@ Print e -> Print <$> substAtom f e Assert c m -> Assert <$> substAtom f c <*> substAtom f m Assume c m -> Assume <$> substAtom f c <*> substAtom f m- Breakpoint nm args -> Breakpoint nm <$> traverseFC (substValue f) args+ Cut nm args -> Cut nm <$> traverseFC (substValue f) args mapStmtAtom :: ( Applicative m, TraverseExt ext ) => (forall (x :: CrucibleType). Atom s x -> m (Atom s x))@@ -663,7 +663,7 @@ Print e -> Print <$> f e Assert c m -> Assert <$> f c <*> f m Assume c m -> Assume <$> f c <*> f m- Breakpoint nm args -> Breakpoint nm <$> traverseFC (substValueAtom f) args+ Cut nm args -> Cut nm <$> traverseFC (substValueAtom f) args substPosdStmt :: ( Applicative m, TraverseExt ext ) => (forall (x :: CrucibleType). Nonce s x -> m (Nonce s' x))@@ -922,7 +922,7 @@ cfgEntryBlock :: CFG ext s init ret -> Block ext s ret cfgEntryBlock g = fromMaybe- (error "Missing entry block")+ (panic "cfgEntryBlock" ["Missing entry block"]) (Fold.find (\b -> blockID b == LabelID (cfgEntryLabel g)) (cfgBlocks g)) cfgInputTypes :: CFG ext s init ret -> CtxRepr init
src/Lang/Crucible/CFG/SSAConversion.hs view
@@ -30,11 +30,11 @@ ) where import Control.Exception (assert)-import Control.Lens ((&)) import Control.Monad.State.Strict import Data.Bimap (Bimap) import qualified Data.Bimap as Bimap import qualified Data.Foldable as Fold+import Data.Function ((&)) import Data.Map.Strict (Map) import qualified Data.Map.Strict as Map import Data.Maybe (isJust, fromMaybe)@@ -129,11 +129,11 @@ , binputTerm :: !(Posd (ExtendedTermStmt s blocks ret)) } --- The Breakpoint non-terminator statement becomes a jump during SSA conversion.--- This datatype temporarily adds breakpoint as a terminator statement.+-- The Cut non-terminator statement becomes a jump during SSA conversion.+-- This datatype temporarily adds cut as a terminator statement. data ExtendedTermStmt s blocks ret where BaseTermStmt :: TermStmt s ret -> ExtendedTermStmt s blocks ret- BreakStmt :: JumpInfo s blocks -> ExtendedTermStmt s blocks ret+ CutStmt :: JumpInfo s blocks -> ExtendedTermStmt s blocks ret type BlockInputAssignment ext s blocks ret = Assignment (BlockInput ext s blocks ret)@@ -144,21 +144,21 @@ extBlockInput :: BlockInput ext s blocks ret args -> BlockInput ext s (blocks ::> tp) ret arg-extBreakpoints ::- Bimap BreakpointName (Some (C.BlockID blocks)) ->- Bimap BreakpointName (Some (C.BlockID (blocks ::> tp)))+extCutpoints ::+ Bimap CutpointName (Some (C.BlockID blocks)) ->+ Bimap CutpointName (Some (C.BlockID (blocks ::> tp))) #ifdef UNSAFE_OPS extBlockInputAssignment = unsafeCoerce extBlockInput = unsafeCoerce -extBreakpoints = unsafeCoerce+extCutpoints = unsafeCoerce #else extBlockInputAssignment = fmapFC extBlockInput extBlockInput bi = bi { binputID = C.extendBlockID (binputID bi) } -extBreakpoints = Bimap.mapR (mapSome C.extendBlockID)+extCutpoints = Bimap.mapR (mapSome C.extendBlockID) #endif ------------------------------------------------------------------------@@ -280,7 +280,7 @@ Just (SomeSwitchInfo tr si) -> Just $ case testEquality tr (typeOfAtom (lambdaAtom l)) of Just Refl -> si- Nothing -> error "Lang.Crucible.SSAConversion.lookupSwitchInfo: type mismatch!"+ Nothing -> panic "lookupSwitchInfo" ["type mismatch!"] -- | Extend switch target extSwitchInfo :: SwitchInfo s blocks tp -> SwitchInfo s (blocks::>args) tp@@ -295,11 +295,11 @@ let blocks' = extBlockInputAssignment $ biBlocks bi let jump_info' = extJumpInfoMap $ biJumpInfo bi let switch_info' = extSwitchInfoMap $ biSwitchInfo bi- let breakpoints' = extBreakpoints $ biBreakpoints bi+ let cutpoints' = extCutpoints $ biCutpoints bi BI { biBlocks = extend blocks' binput , biJumpInfo = jump_info' , biSwitchInfo = switch_info'- , biBreakpoints = breakpoints'+ , biCutpoints = cutpoints' } ------------------------------------------------------------------------@@ -393,7 +393,7 @@ = BI { biBlocks :: !(Assignment (BlockInput ext s blocks ret) blocks) , biJumpInfo :: !(JumpInfoMap s blocks) , biSwitchInfo :: !(SwitchInfoMap s blocks)- , biBreakpoints :: !(Bimap BreakpointName (Some (C.BlockID blocks)))+ , biCutpoints :: !(Bimap CutpointName (Some (C.BlockID blocks))) } -- | This infers the information given a set of blocks.@@ -403,7 +403,7 @@ bi0 = BI { biBlocks = empty , biJumpInfo = emptyJumpInfoMap , biSwitchInfo = emptySwitchInfoMap- , biBreakpoints = Bimap.empty+ , biCutpoints = Bimap.empty } resolveBlocks :: BlockInfo ext s ret blocks ->@@ -432,7 +432,7 @@ let bi' = extBlockInfo bi binput let ji = JumpInfo block_id crepr ra let bi'' = bi' { biJumpInfo = insertJumpInfo l ji (biJumpInfo bi') }- splitLastBlockInputOnBreakpoints bi'' rest+ splitLastBlockInputOnCutpoints bi'' rest LambdaID l -> do let block_id = C.BlockID (nextIndex sz) let lastArg = AtomValue (lambdaAtom l)@@ -445,16 +445,16 @@ let bi' = extBlockInfo bi binput let si = SwitchInfo block_id crepr ra let bi'' = bi' { biSwitchInfo = insertSwitchInfo l si (biSwitchInfo bi') }- splitLastBlockInputOnBreakpoints bi'' rest- splitLastBlockInputOnBreakpoints ::+ splitLastBlockInputOnCutpoints bi'' rest+ splitLastBlockInputOnCutpoints :: BlockInfo ext s ret blocks -> [Block ext s ret] -> Some (BlockInfo ext s ret)- splitLastBlockInputOnBreakpoints bi rest+ splitLastBlockInputOnCutpoints bi rest | first_binputs :> last_binput <- biBlocks bi- , (first_stmts, break_stmt Seq.:<| last_stmts) <-- Seq.breakl isBreakpoint (binputStmts last_binput)- , Breakpoint nm args <- pos_val break_stmt = do+ , (first_stmts, cut_stmt Seq.:<| last_stmts) <-+ Seq.breakl isCut (binputStmts last_binput)+ , Cut nm args <- pos_val cut_stmt = do let block_id = C.BlockID $ nextIndex $ size $ biBlocks bi let first_binputs' = extBlockInputAssignment $ first_binputs@@ -462,7 +462,7 @@ let jump_info = JumpInfo block_id (fmapFC typeOfValue args) args let last_binput' = (extBlockInput last_binput) { binputStmts = first_stmts- , binputTerm = break_stmt { pos_val = BreakStmt jump_info }+ , binputTerm = cut_stmt { pos_val = CutStmt jump_info } } let new_binput = (extBlockInput last_binput)@@ -471,23 +471,23 @@ , binputStmts = last_stmts } - let new_breakpoints = do- let try_new_breakpoints = Bimap.tryInsert nm (Some block_id) $- extBreakpoints $ biBreakpoints bi- if Bimap.pairMember (nm, (Some block_id)) try_new_breakpoints- then try_new_breakpoints- else error $ "Duplicate breakpoint: " ++ show nm+ let new_cutpoints = do+ let try_new_cutpoints = Bimap.tryInsert nm (Some block_id) $+ extCutpoints $ biCutpoints bi+ if Bimap.pairMember (nm, (Some block_id)) try_new_cutpoints+ then try_new_cutpoints+ else error $ "Duplicate cutpoint: " ++ show nm let bi' = BI { biBlocks = first_binputs' :> last_binput' :> new_binput , biJumpInfo = extJumpInfoMap $ biJumpInfo bi , biSwitchInfo = extSwitchInfoMap $ biSwitchInfo bi- , biBreakpoints = new_breakpoints+ , biCutpoints = new_cutpoints }- splitLastBlockInputOnBreakpoints bi' rest- splitLastBlockInputOnBreakpoints bi rest = resolveBlocks bi rest- isBreakpoint :: Posd (Stmt ext s) -> Bool- isBreakpoint = \case- Posd _ Breakpoint{} -> True+ splitLastBlockInputOnCutpoints bi' rest+ splitLastBlockInputOnCutpoints bi rest = resolveBlocks bi rest+ isCut :: Posd (Stmt ext s) -> Bool+ isCut = \case+ Posd _ Cut{} -> True _ -> False @@ -621,7 +621,7 @@ -> C.JumpTarget blocks ctx resolveJumpTarget bi reg_map next_lbl = do case lookupJumpInfo next_lbl (biJumpInfo bi) of- Nothing -> error "Could not find label in resolveJumpTarget"+ Nothing -> panic "resolveJumpTarget" ["Could not find label"] Just (JumpInfo next_id types inputs) -> do let args = fmapFC (resolveReg reg_map) inputs C.JumpTarget next_id types args@@ -634,7 +634,7 @@ -> C.JumpTarget blocks ctx resolveLambdaAsJump bi reg_map next_lbl output = case lookupSwitchInfo next_lbl (biSwitchInfo bi) of- Nothing -> error "Could not find label in resolveLambdaAsJump"+ Nothing -> panic "resolveLambdaAsJump" ["Could not find label"] Just (SwitchInfo block_id types inputs) -> do let types' = types :> typeOfAtom (lambdaAtom next_lbl) let args = fmapFC (resolveReg reg_map) inputs@@ -648,7 +648,7 @@ -> C.SwitchTarget blocks ctx tp resolveLambdaAsSwitch bi reg_map next_lbl = case lookupSwitchInfo next_lbl (biSwitchInfo bi) of- Nothing -> error "Could not find label in resolveLambdaAsSwitch"+ Nothing -> panic "resolveLambdaAsSwitch" ["Could not find label"] Just (SwitchInfo block_id types inputs) -> do let args = fmapFC (resolveReg reg_map) inputs C.SwitchTarget block_id types args@@ -695,7 +695,7 @@ ErrorStmt e -> C.ErrorStmt (resolveAtom reg_map e) Output l e -> C.Jump (resolveLambdaAsJump bi reg_map l (resolveAtom reg_map e))-resolveTermStmt _ reg_map _ (BreakStmt (JumpInfo next_id types inputs)) = do+resolveTermStmt _ reg_map _ (CutStmt (JumpInfo next_id types inputs)) = do let args = fmapFC (resolveReg reg_map) inputs C.Jump $ C.JumpTarget next_id types args @@ -746,7 +746,7 @@ Nothing -> Nothing Just (SomeReg tp r) | Just Refl <- testEquality tp (C.appType app) -> Just r- _ -> error "appRegMap_lookup: impossible!"+ _ -> panic "appRegMap_lookup" ["impossible!"] appRegMap_empty :: AppRegMap ext ctx@@ -915,14 +915,14 @@ (resolveAtom reg_map m)) (resolveStmts nm bi sz reg_map bindings appMap rest t) - -- breakpoint statements are eliminated during the inferBlockInfo phase- Breakpoint{} -> error $- "Unexpected breakpoint at position " ++ show p ++ ": " ++ show (Pretty.pretty s0)+ -- cut statements are eliminated during the inferBlockInfo phase+ Cut{} -> error $+ "Unexpected cut at position " ++ show p ++ ": " ++ show (Pretty.pretty s0) data SomeBlockMap ext ret where SomeBlockMap :: Ctx.Index blocks tp ->- Bimap BreakpointName (Some (C.BlockID blocks)) ->+ Bimap CutpointName (Some (C.BlockID blocks)) -> C.BlockMap ext blocks ret -> SomeBlockMap ext ret @@ -950,9 +950,9 @@ case inferBlockInfo blocks of Some bi -> case lookupJumpInfo entry (biJumpInfo bi) of- Nothing -> error "Missing initial block."+ Nothing -> panic "resolveBlockMap" ["Missing initial block."] Just (JumpInfo (C.BlockID idx) _ _) ->- SomeBlockMap idx (biBreakpoints bi) $+ SomeBlockMap idx (biCutpoints bi) $ fmapFC (resolveBlock bi) (biBlocks bi) ------------------------------------------------------------------------@@ -970,7 +970,7 @@ let entry = cfgEntryLabel g let blocks = cfgBlocks g case resolveBlockMap (handleName h) entry blocks of- SomeBlockMap idx breakpoints block_map -> do+ SomeBlockMap idx cutpoints block_map -> do let b = block_map ! idx case C.blockInputs b `testEquality` initTypes of Nothing -> error $@@ -981,6 +981,6 @@ let g' = C.CFG { C.cfgHandle = h , C.cfgBlockMap = block_map , C.cfgEntryBlockID = C.BlockID idx- , C.cfgBreakpoints = breakpoints+ , C.cfgCutpoints = cutpoints } reachableCFG g'
src/Lang/Crucible/Concretize.hs view
@@ -7,15 +7,27 @@ -- Maintainer : Langston Barrett <langston@galois.com> -- Stability : provisional ----- This module defines 'concRegValue', a function that takes a 'RegValue' (i.e.,--- a symbolic value), and a model from the SMT solver ('W4GE.GroundEvalFn'), and--- returns the concrete value that the symbolic value takes in the model.+-- This module defines three different kinds of functions. In order of how much+-- work they perform: ----- This can be used to report specific values that lead to violations of--- assertions, including safety assertions.+-- * /Grounding/ functions (e.g., 'groundRegValue') take symbolic values+-- ('RegValue's) and a model from an SMT solver ('W4GE.GroundEvalFn'), and+-- return the concrete value ('ConcRegValue') that the symbolic value takes in+-- the model. These functions can be used to report specific values that lead+-- to violations of assertions, including safety assertions.+-- * /Concretization/ functions (e.g., 'concRegValue') request a model that is+-- consistent with the current assumptions (e.g., path conditions) from the+-- symbolic backend, and then ground a value in that model. These can be used+-- to reduce the size and complexity of later queries to SMT solvers, at the+-- cost of no longer being sound from a verification standpoint.+-- * /Unique concretization/ functions (e.g., 'uniquelyConcRegValue') do the+-- same thing as concretization functions, but then check if the concrete+-- value is the /only possible/ value for the given symbolic expression in+-- /any/ model. ------------------------------------------------------------------------ {-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE StandaloneKindSignatures #-}@@ -30,24 +42,38 @@ module Lang.Crucible.Concretize ( ConcRegValue , ConcRV'(..)+ , asConcRegValue+ , asConcRegEntry+ , asConcRegMap , ConcAnyValue(..) , ConcIntrinsic , IntrinsicConcFn(..) , ConcCtx(..)+ -- * Grounding+ , groundRegValue+ , groundRegEntry+ , groundRegMap+ -- * Concretization , concRegValue , concRegEntry , concRegMap+ -- * Unique concretization+ , uniquelyConcRegValue+ , uniquelyConcRegEntry+ , uniquelyConcRegMap -- * There and back again , IntrinsicConcToSymFn(..) , concToSym ) where import qualified Data.Foldable as F+import Data.Functor.Const (Const(..)) import Data.Kind (Type) import Data.List.NonEmpty (NonEmpty) import qualified Data.List.NonEmpty as NE import Data.Map (Map) import qualified Data.Map as Map+import Data.Proxy (Proxy(Proxy)) import Data.Sequence (Seq) import Data.Text (Text) import qualified Data.Text as Text@@ -57,14 +83,20 @@ import qualified Data.Parameterized.Context as Ctx import Data.Parameterized.Map (MapF) import qualified Data.Parameterized.Map as MapF-import Data.Parameterized.TraversableFC (traverseFC)+import Data.Parameterized.TraversableFC (traverseFC, foldlMFC) +import qualified What4.Concretize as W4C+import qualified What4.Config as W4Cfg import What4.Expr (Expr, ExprBuilder, Flags, FloatModeRepr(..)) import qualified What4.Expr.GroundEval as W4GE import What4.Interface (SymExpr) import qualified What4.Interface as W4I import qualified What4.Partial as W4P+import qualified What4.Protocol.Online as WPO+import qualified What4.SatResult as WSat +import qualified Lang.Crucible.Backend as CB+import qualified Lang.Crucible.Backend.Online as CBO import Lang.Crucible.FunctionHandle (FnHandle, RefCell) import Lang.Crucible.Simulator.Intrinsics (Intrinsic) import Lang.Crucible.Simulator.RegMap (RegEntry, RegMap)@@ -84,7 +116,7 @@ -- | Defines the \"concrete\" interpretations of 'CrucibleType' (as opposed -- to the \"symbolic\" interpretations, which are defined by 'RegValue'), as--- returned by 'concRegValue'.+-- returned by 'groundRegValue'. -- -- Unlike What4\'s 'W4GE.GroundValue', this type family is parameterized -- by @sym@, the symbolic backend. This is because Crucible makes use of@@ -112,10 +144,40 @@ ConcRegValue sym (IntrinsicType nm ctx) = ConcIntrinsic nm ctx ConcRegValue sym (StringMapType tp) = Map Text (ConcRV' sym tp) +-- | Check if a 'RegValue' is actually concrete+asConcRegValue ::+ W4I.IsExpr (SymExpr sym) =>+ proxy sym ->+ TypeRepr tp ->+ RegValue sym tp ->+ Maybe (ConcRegValue sym tp)+asConcRegValue _proxy tp val =+ -- TODO: More cases could be added here.+ case asBaseType tp of+ AsBaseType {} -> W4GE.asGround val+ _ -> Nothing++-- | Check if a 'RM.RegEntry' is actually concrete+asConcRegEntry ::+ forall sym tp.+ W4I.IsExpr (SymExpr sym) =>+ RM.RegEntry sym tp ->+ Maybe (ConcRegValue sym tp)+asConcRegEntry (RM.RegEntry t v) = asConcRegValue (Proxy @sym) t v++-- | Check if a 'RM.RegMap' is actually concrete+asConcRegMap ::+ forall sym tp.+ W4I.IsExpr (SymExpr sym) =>+ RM.RegMap sym tp ->+ Maybe (Ctx.Assignment (ConcRV' sym) tp)+asConcRegMap (RM.RegMap assign) =+ traverseFC (\re -> ConcRV' <$> asConcRegEntry re) assign+ --------------------------------------------------------------------- -- * ConcCtx --- | Context needed for 'concRegValue'+-- | Context needed for 'groundRegValue' -- -- The @t@ parameter matches that on 'W4GE.GroundEvalFn' and 'Expr', namely, it -- is a phantom type brand used to relate nonces to a specific nonce generator@@ -172,7 +234,7 @@ W4P.Partial (W4I.Pred sym) (RegValue sym tp) -> IO (Maybe (ConcRegValue sym tp)) concPartial ctx tp (W4P.Partial p v) =- iteIO ctx p (Just <$> concRegValue ctx tp v) (pure Nothing)+ iteIO ctx p (Just <$> groundRegValue ctx tp v) (pure Nothing) -- | Helper, not exported concPartialWithErr ::@@ -224,13 +286,13 @@ --------------------------------------------------------------------- -- * Any --- | An 'AnyValue' concretized by 'concRegValue'+-- | An 'AnyValue' concretized by 'groundRegValue' data ConcAnyValue sym = forall tp. ConcAnyValue (TypeRepr tp) (ConcRV' sym tp) --------------------------------------------------------------------- -- * FnVal --- | A 'FnVal' concretized by 'concRegValue'+-- | A 'FnVal' concretized by 'groundRegValue' data ConcFnVal (sym :: Type) (args :: Ctx CrucibleType) (res :: CrucibleType) where ConcClosureFnVal :: !(ConcFnVal sym (args ::> tp) ret) ->@@ -260,7 +322,7 @@ \case RV.ClosureFnVal fv t v -> do concV <- concFnVal ctx (args Ctx.:> t) ret fv- v' <- concRegValue ctx t v+ v' <- groundRegValue ctx t v pure (ConcClosureFnVal concV t (ConcRV' v')) RV.VarargsFnVal hdl extra -> pure (ConcVarargsFnVal hdl extra)@@ -306,7 +368,7 @@ concSymSequence ctx tp = SymSeq.concretizeSymSequence (ground ctx)- (fmap ConcRV' . concRegValue ctx tp)+ (fmap ConcRV' . groundRegValue ctx tp) --------------------------------------------------------------------- -- * StringMap@@ -331,7 +393,7 @@ --------------------------------------------------------------------- -- * Variant --- | Note that we do not attempt to \"normalize\" variants in 'concRegValue'+-- | Note that we do not attempt to \"normalize\" variants in 'groundRegValue' -- in any way. If the model reports that multiple branches of a variant are -- plausible, then multiple branches might be included as 'Just's. newtype ConcVariantBranch sym tp@@ -356,21 +418,21 @@ Nothing -> pure (ConcVariantBranch Nothing) ------------------------------------------------------------------------ * 'concRegValue'+-- * 'groundRegValue' -- | Pick a feasible concrete value from the model -- -- This function does not attempt to \"normalize\" variants nor mux trees in any -- way. If the model reports that multiple branches of a variant or mux tree are -- plausible, then multiple branches might be included in the result.-concRegValue ::+groundRegValue :: (SymExpr sym ~ Expr t) => W4I.IsExprBuilder sym => ConcCtx sym t -> TypeRepr tp -> RegValue sym tp -> IO (ConcRegValue sym tp)-concRegValue ctx tp v =+groundRegValue ctx tp v = case (tp, v) of -- Base types (BoolRepr, _) -> ground ctx v@@ -391,13 +453,13 @@ -- Simple recursive cases (AnyRepr, RV.AnyValue tp' v') ->- ConcAnyValue tp' . ConcRV' <$> concRegValue ctx tp' v'+ ConcAnyValue tp' . ConcRV' <$> groundRegValue ctx tp' v' (RecursiveRepr symb tyCtx, RV.RolledType v') ->- concRegValue ctx (unrollType symb tyCtx) v'+ groundRegValue ctx (unrollType symb tyCtx) v' (StructRepr tps, _) ->- Ctx.zipWithM (\tp' (RV.RV v') -> ConcRV' <$> concRegValue ctx tp' v') tps v+ Ctx.zipWithM (\tp' (RV.RV v') -> ConcRV' <$> groundRegValue ctx tp' v') tps v (VectorRepr tp', _) ->- traverse (fmap ConcRV' . concRegValue ctx tp') v+ traverse (fmap ConcRV' . groundRegValue ctx tp') v -- Cases with helper functions (MaybeRepr tp', _) ->@@ -422,26 +484,210 @@ -- Incomplete cases (WordMapRepr _ _, _) -> pure () --- | Like 'concRegValue', but for 'RegEntry'-concRegEntry ::+-- | Like 'groundRegValue', but for 'RegEntry'+groundRegEntry :: (SymExpr sym ~ Expr t) => W4I.IsExprBuilder sym => ConcCtx sym t -> RegEntry sym tp -> IO (ConcRegValue sym tp)-concRegEntry ctx e = concRegValue ctx (RM.regType e) (RM.regValue e)+groundRegEntry ctx e = groundRegValue ctx (RM.regType e) (RM.regValue e) --- | Like 'concRegEntry', but for a whole 'RegMap'-concRegMap ::+-- | Like 'groundRegEntry', but for a whole 'RegMap'+groundRegMap :: (SymExpr sym ~ Expr t) => W4I.IsExprBuilder sym => ConcCtx sym t -> RegMap sym tps -> IO (Ctx.Assignment (ConcRV' sym) tps)-concRegMap ctx (RM.RegMap m) = traverseFC (fmap ConcRV' . concRegEntry ctx) m+groundRegMap ctx (RM.RegMap m) = traverseFC (fmap ConcRV' . groundRegEntry ctx) m ------------------------------------------------------------------------ * concToSym+-- * 'concRegValue'++-- | Generate a model and pick a feasible concrete value from it+concRegValue ::+ forall tp sym bak solver scope st fs.+ ( CB.IsSymBackend sym bak+ , sym ~ ExprBuilder scope st fs+ , SymExpr sym ~ Expr scope+ , bak ~ CBO.OnlineBackend solver scope st fs+ , WPO.OnlineSolver solver+ ) =>+ bak ->+ MapF SymbolRepr (IntrinsicConcFn scope) ->+ TypeRepr tp ->+ RegValue sym tp ->+ IO (Either W4C.ConcretizationFailure (ConcRegValue sym tp))+concRegValue bak iFns tp v = concRegEntry bak iFns (RM.RegEntry tp v)++-- | Generate a model and pick a feasible concrete value from it+concRegEntry ::+ forall tp sym bak solver scope st fs.+ ( CB.IsSymBackend sym bak+ , sym ~ ExprBuilder scope st fs+ , SymExpr sym ~ Expr scope+ , bak ~ CBO.OnlineBackend solver scope st fs+ , WPO.OnlineSolver solver+ ) =>+ bak ->+ MapF SymbolRepr (IntrinsicConcFn scope) ->+ RM.RegEntry sym tp ->+ IO (Either W4C.ConcretizationFailure (ConcRegValue sym tp))+concRegEntry bak iFns re = do+ res <- concRegMap bak iFns (RM.RegMap (Ctx.singleton re))+ case res of+ Left e -> pure (Left e)+ Right (Ctx.Empty Ctx.:> ConcRV' concV) -> pure (Right concV)++-- | Like 'concRegValue', but for a whole 'RegMap'+concRegMap ::+ forall tps sym bak solver scope st fs.+ ( CB.IsSymBackend sym bak+ , sym ~ ExprBuilder scope st fs+ , SymExpr sym ~ Expr scope+ , bak ~ CBO.OnlineBackend solver scope st fs+ , WPO.OnlineSolver solver+ ) =>+ bak ->+ MapF SymbolRepr (IntrinsicConcFn scope) ->+ RegMap sym tps ->+ IO (Either W4C.ConcretizationFailure (Ctx.Assignment (ConcRV' sym) tps))+concRegMap bak iFns m = do+ case asConcRegMap m of+ Just concM -> pure (Right concM)+ Nothing ->+ withEnabledOnline $ do+ let err = panic "concRegValue" ["requires online solving to be enabled"]+ cond <- CB.getPathCondition bak+ CBO.withSolverProcess bak err $ \sp -> do+ msat <- WPO.checkWithAssumptionsAndModel sp "concRegValue" [cond]+ case msat of+ WSat.Unknown -> pure $ Left W4C.SolverUnknown+ WSat.Unsat {} -> pure $ Left W4C.UnsatInitialAssumptions+ WSat.Sat mdl -> do+ let ctx = ConcCtx { model = mdl, intrinsicConcFuns = iFns }+ expr <- groundRegMap @sym ctx m+ pure (Right expr)+ where+ withEnabledOnline f = do+ let sym = CB.backendGetSym bak+ let conf = W4I.getConfiguration sym+ enabledOpt <- W4Cfg.getOptionSetting CBO.enableOnlineBackend conf+ wasEnabled <- W4Cfg.getOpt enabledOpt+ _ <- W4Cfg.setOpt enabledOpt True+ r <- f+ _ <- W4Cfg.setOpt enabledOpt wasEnabled+ pure r++---------------------------------------------------------------------+-- * 'uniquelyConcRegValue'++-- | Generate a model and pick a feasible concrete value from it+uniquelyConcRegValue ::+ forall tp sym bak solver scope st fm.+ ( CB.IsSymBackend sym bak+ , sym ~ ExprBuilder scope st (Flags fm)+ , SymExpr sym ~ Expr scope+ , bak ~ CBO.OnlineBackend solver scope st (Flags fm)+ , WPO.OnlineSolver solver+ ) =>+ bak ->+ FloatModeRepr fm ->+ MapF SymbolRepr (IntrinsicConcFn scope) ->+ MapF SymbolRepr IntrinsicConcToSymFn ->+ TypeRepr tp ->+ RegValue sym tp ->+ IO (Either W4C.UniqueConcretizationFailure (ConcRegValue sym tp))+uniquelyConcRegValue bak fm iFns sFns tp v =+ uniquelyConcRegEntry bak fm iFns sFns (RM.RegEntry tp v)++-- | Generate a model and pick a feasible concrete value from it+uniquelyConcRegEntry ::+ forall tp sym bak solver scope st fm.+ ( CB.IsSymBackend sym bak+ , sym ~ ExprBuilder scope st (Flags fm)+ , SymExpr sym ~ Expr scope+ , bak ~ CBO.OnlineBackend solver scope st (Flags fm)+ , WPO.OnlineSolver solver+ ) =>+ bak ->+ FloatModeRepr fm ->+ MapF SymbolRepr (IntrinsicConcFn scope) ->+ MapF SymbolRepr IntrinsicConcToSymFn ->+ RM.RegEntry sym tp ->+ IO (Either W4C.UniqueConcretizationFailure (ConcRegValue sym tp))+uniquelyConcRegEntry bak fm iFns sFns re = do+ res <- uniquelyConcRegMap bak fm iFns sFns (RM.RegMap (Ctx.singleton re))+ case res of+ Left e -> pure (Left e)+ Right (Ctx.Empty Ctx.:> ConcRV' concV) -> pure (Right concV)++-- | Like 'concRegValue', but for a whole 'RegMap'+uniquelyConcRegMap ::+ forall tps sym bak solver scope st fm.+ ( CB.IsSymBackend sym bak+ , sym ~ ExprBuilder scope st (Flags fm)+ , SymExpr sym ~ Expr scope+ , bak ~ CBO.OnlineBackend solver scope st (Flags fm)+ , WPO.OnlineSolver solver+ ) =>+ bak ->+ FloatModeRepr fm ->+ MapF SymbolRepr (IntrinsicConcFn scope) ->+ MapF SymbolRepr IntrinsicConcToSymFn ->+ RegMap sym tps ->+ IO (Either W4C.UniqueConcretizationFailure (Ctx.Assignment (ConcRV' sym) tps))+uniquelyConcRegMap bak fm iFns sFns m = do+ case asConcRegMap m of+ Just concM -> pure (Right concM)+ Nothing -> do+ -- First, check to see if there are a models of the symbolic values.+ concM_ <- concRegMap bak iFns m+ case concM_ of+ Left e -> pure (Left (W4C.GroundingFailure e))+ Right concM -> do+ -- We found a model, so check to see if this is the only possible+ -- model for these symbolic values. We do this by adding a blocking+ -- clause that assumes the `RegValue`s are /not/ equal to the+ -- model we found in the previous step. If this is unsatisfiable,+ -- the `RegValue`s can only be equal to the first model, so we can+ -- conclude they are concrete. If it is satisfiable, on the other+ -- hand, the `RegValue`s can be multiple values, so they are truly+ -- symbolic.+ let sym = CB.backendGetSym bak+ let notEq ::+ forall tp.+ ConcRV' sym tp ->+ RM.RegEntry sym tp ->+ IO (Const (W4I.Pred sym) tp)+ notEq (ConcRV' concV) (RM.RegEntry tp v) = do+ symV <- concToSym sym sFns fm tp concV+ p <- W4I.notPred sym =<< RV.eqRegValue sym tp symV v+ pure (Const p)+ let RM.RegMap mAssign = m+ preds <- Ctx.zipWithM notEq concM mAssign+ -- not (modelA_1 == modelB_1) \/ ... \/ not (modelA_n == modelB_n)+ p <-+ foldlMFC+ (\p (Const p') -> W4I.orPred sym p p')+ (W4I.falsePred sym)+ preds++ frm <- CB.pushAssumptionFrame bak+ loc <- W4I.getCurrentProgramLoc sym+ CB.addAssumption bak (CB.GenericAssumption loc "uniquelyConcRegMap" p)+ concM_' <- concRegMap bak iFns m+ res <-+ case concM_' of+ Left W4C.UnsatInitialAssumptions -> pure (Right concM)+ Left e -> pure (Left (W4C.GroundingFailure e))+ Right _ -> pure (Left W4C.MultipleModels)+ _ <- CB.popAssumptionFrame bak frm+ pure res++---------------------------------------------------------------------+-- * 'concToSym' -- | Function for re-symbolizing an intrinsic type type IntrinsicConcToSymFn :: Symbol -> Type
src/Lang/Crucible/FunctionHandle.hs view
@@ -195,7 +195,7 @@ -- FnHandleMap data HandleElt (f :: Ctx CrucibleType -> CrucibleType -> Type) ctx where- HandleElt :: FnHandle args ret -> f args ret -> HandleElt f (args::>ret)+ HandleElt :: FnHandle args ret -> f args ret -> HandleElt f (args ::> ret) newtype FnHandleMap f = FnHandleMap (MapF (Nonce GlobalNonceGenerator) (HandleElt f))
+ src/Lang/Crucible/README.hs view
@@ -0,0 +1,114 @@+{- | This module is only for documentation purposes, and provides a high+level overview of Crucible aimed at developers. -}+{-# OPTIONS_GHC -Wno-unused-imports #-}+{-# OPTIONS_GHC -Wno-missing-export-lists #-}+module Lang.Crucible.README where++import What4.Interface+import What4.Expr.App+import What4.BaseTypes+import Lang.Crucible.Backend++import Lang.Crucible.Types+import Lang.Crucible.CFG.Expr+import Lang.Crucible.CFG.Core hiding (Expr)+import Lang.Crucible.CFG.SSAConversion++import Lang.Crucible.Simulator.RegValue+import Lang.Crucible.Simulator.RegMap+import Lang.Crucible.Simulator.ExecutionTree+import Lang.Crucible.Simulator.EvalStmt+import Lang.Crucible.Simulator.Evaluation+import Lang.Crucible.Simulator.Intrinsics++++-- * Crucible Types++{- $+The types of the Crucible language are defined in "Lang.Crucible.Types".+Types are encoded using [singletons](https://github.com/Galoisinc/parameterized-utils?tab=readme-ov-file#parameterized-types-motivation):++* 'CrucibleType' is the Haskell type-level description of all Crucible types+* 'TypeRepr' are the associated value-level singletons, which+ are used when we pass around types, or store them in data structures.+-}++-- * Crucible Values++{- $+The inhabitants of each type are specified via the type function 'RegValue'.+We also have 'RegValue'' which is just a @newtype@ wrapper around 'RegValue',+because in Haskell type families may not be partially applied but @newtype@s can.++An important subset of the Crucible types are the base types (see 'BaseToType'),+which is for the symbolic expression we can construct+(see 'SymExpr' in [what4](https://github.com/Galoisinc/what4)).+Only these types may contain variables. In practice, we always use @what4@'s+'Expr' type to represent symbolic expressions.++Also, in some cases we use 'RegEntry' which+is just a pair of a 'RegValue' and its associated 'TypeRepr'.++There's also 'BaseTerm', which is similar to 'RegEntry' but+for base types---it contains a @what4@'s 'BaseTypeRepr' and a value of the corresponding+base type (usually; the type is parameterized on exactly what we package+with the type).+-}+ +-- * Crucible Programs+ +{- $+The program executed by the simulator is in the form of a control flow+graph (CFG). A typical way to construct them is as follows:++ 1. use the functions in "Lang.Crucible.CFG.Generator" to produce a CFG with + assignments ("Lang.Crucible.CFG.Reg")+ 2. use 'toSSA' to translate this to a CFG without assignments+ ("Lang.Crucible.CFG.Core")++The core 'CFG' contains basic blocks with 'Stmt's and terminated+by 'TermStmt'. The expression language for the core 'Core.CFG' is+the type 'App'.+-}+ +-- * Symbolic Simulator+ +{- $+The state of a running simulator is described in "Lang.Crucible.Simulator.ExecutionTree":++ * 'ExecState' is the current state of execution.+ We start with 'InitialState', and keep performing steps until we get+ to a 'ResultState'.+ * As the simulator executes, it keeps track of its state in 'SimState',+ which is stored in the current `ExecState`.+ * 'SimContext' is the part of the state that persists across branches+ (e.g, after we explore the @then@ part of an @if@ statement, we'll+ roll back some of the state changes before simulating the @else@ part,+ but the data in 'SimContext's persists). An important part of the+ 'SimContext' is the simulator's backend ('_ctxBackend'), which is how the+ simulator communicates with a solver, and builds symbolic expressions+ ('IsSymBackend').+++To evaluate a 'CFG' we evaluate the statements as described in+"Lang.Crucible.Simulator.EvalStmt" (details in 'stepStmt', 'stepTerm').+Details about expressions evaluation are in 'evalApp' in "Lang.Crucible.Simulator.Evaluation".++A lot of useful functionality relevant to the simulator can be accessed+from module "Lang.Crucible.Simulator".++The simulator supports mutable global variables. Our tools use one such+global to store a language specific memory model, which records information+about various memory operations.+-}++-- * Intrinsics++{- $++Crucible type may be extended using 'IntrinsicType's. An intrinsic type is+a type-level string, which can be given meaning by making an instance of+'IntrinsicClass'.+-}+
src/Lang/Crucible/Simulator.hs view
@@ -87,11 +87,14 @@ -- ** SimContext record , IsSymInterfaceProof , SimContext(..)+ , ExceptionContextConfig(..) , initSimContext , ctxSymInterface , functionBindings , cruciblePersonality , profilingMetrics+ , exceptionContextConfig+ , parseExceptionContextConfig -- * SimState , SimState
src/Lang/Crucible/Simulator/BoundedExec.hs view
@@ -30,8 +30,8 @@ ( boundedExecFeature ) where -import Control.Lens ( (^.), to, (&), (%~), (.~) ) import Control.Monad ( when )+import Data.Function ((&)) import Data.IORef import Data.Map (Map) import qualified Data.Map as Map@@ -40,7 +40,7 @@ import qualified Data.Sequence as Seq import qualified Data.Text as Text import Data.Word-+import Lens.Micro ((^.), to, (%~), (.~)) import qualified Data.Parameterized.Context as Ctx import qualified Data.Parameterized.Map as MapF@@ -82,13 +82,13 @@ buildWTOMap = snd . go 0 0 Map.empty where go :: Int -> Int -> Map Int (Int,Int) -> [WTOComponent (Some (BlockID blocks))] -> (Int, Map Int (Int,Int))- go !x !_ m [] = (x,m)- go !x !d m (Vertex (Some bid) : cs) =+ go !x !_ !m [] = (x,m)+ go x d m (Vertex (Some bid) : cs) = let m' = Map.insert (Ctx.indexVal (blockIDIndex bid)) (x,d) m- in go (x+1) d m' cs- go !x !d m (SCC scc : cs) =- let m' = viewSome (\hd -> Map.insert (Ctx.indexVal (blockIDIndex hd)) (x,d+1) m) (wtoHead scc)- (x',m'') = go (x+1) (d+1) m' $ wtoComps scc+ in go (x + 1) d m' cs+ go x d m (SCC scc : cs) =+ let m' = viewSome (\hd -> Map.insert (Ctx.indexVal (blockIDIndex hd)) (x,d + 1) m) (wtoHead scc)+ (x',m'') = go (x + 1) (d + 1) m' $ wtoComps scc in go x' d m'' cs @@ -100,8 +100,8 @@ incrementBoundCount cs depth = case Seq.lookup depth cs of Just n ->- do let n' = n+1- let cs' = Seq.update depth n' $ Seq.take (depth+1) cs+ do let n' = n + 1+ let cs' = Seq.update depth n' $ Seq.take (depth + 1) cs n' `seq` cs' `seq` (cs', n') Nothing -> do let cs' = cs <> Seq.replicate (depth - Seq.length cs) 0 <> Seq.singleton 1@@ -183,7 +183,7 @@ Bool {- ^ Produce a proof obligation when resources are exhausted? -} -> IO (GenericExecutionFeature sym) boundedExecFeature getLoopBounds generateSideConditions =- do gvRef <- newIORef (error "Global variable for BoundedExecFrameData not initialized")+ do gvRef <- newIORef Nothing return $ GenericExecutionFeature $ onStep gvRef where@@ -202,78 +202,86 @@ } checkBackedge ::- IORef BoundedExecGlobal ->+ IORef (Maybe BoundedExecGlobal) -> Some (BlockID blocks) -> BlockID blocks tgt_args -> SymGlobalState sym -> IO (SymGlobalState sym, Maybe Word64)- checkBackedge gvRef (Some bid_curr) bid_tgt globals =- do gv <- readIORef gvRef- case fromMaybe [] (lookupGlobal gv globals) of- ( Right fbd : rest ) ->- do let id_curr = Ctx.indexVal (blockIDIndex bid_curr)- let id_tgt = Ctx.indexVal (blockIDIndex bid_tgt)- let m = frameWtoMap fbd- case (Map.lookup id_curr m, Map.lookup id_tgt m) of- (Just (cx, _cd), Just (tx, td)) | tx <= cx ->- do let cs = frameBoundCounts fbd- let (cs', q) = incrementBoundCount cs td- let fbd' = fbd{ frameBoundCounts = cs' }- let globals' = insertGlobal gv (Right fbd' : rest) globals- if q > frameBoundLimit fbd then- return (globals', Just (frameBoundLimit fbd))- else- return (globals', Nothing)-- _ -> return (globals, Nothing)- _ -> return (globals, Nothing)+ checkBackedge gvRef (Some bid_curr) bid_tgt globals = do+ let err = panic "checkBackedge" ["Global not initialized"]+ currGv <- readIORef gvRef+ let gv = fromMaybe err currGv+ case fromMaybe [] (lookupGlobal gv globals) of+ (Right fbd : rest) -> do+ let id_curr = Ctx.indexVal (blockIDIndex bid_curr)+ let id_tgt = Ctx.indexVal (blockIDIndex bid_tgt)+ let m = frameWtoMap fbd+ case (Map.lookup id_curr m, Map.lookup id_tgt m) of+ (Just (cx, _cd), Just (tx, td)) | tx <= cx -> do+ let cs = frameBoundCounts fbd+ let (cs', q) = incrementBoundCount cs td+ let fbd' = fbd{frameBoundCounts = cs'}+ let globals' = insertGlobal gv (Right fbd' : rest) globals+ if q > frameBoundLimit fbd+ then+ return (globals', Just (frameBoundLimit fbd))+ else+ return (globals', Nothing)+ _ -> return (globals, Nothing)+ _ -> return (globals, Nothing) modifyStackState ::- IORef BoundedExecGlobal ->+ IORef (Maybe BoundedExecGlobal) -> (SimState p sym ext rtp f args -> ExecState p sym ext rtp) -> SimState p sym ext rtp f args -> ([Either FunctionName FrameBoundData] -> [Either FunctionName FrameBoundData]) -> IO (ExecutionFeatureResult p sym ext rtp)- modifyStackState gvRef mkSt st f =- do gv <- readIORef gvRef- let xs = case lookupGlobal gv (st ^. stateGlobals) of- Nothing -> error "bounded execution global not defined!"- Just v -> v- let st' = st & stateGlobals %~ insertGlobal gv (f xs)- return (ExecutionFeatureModifiedState (mkSt st'))+ modifyStackState gvRef mkSt st f = do+ currGv <- readIORef gvRef+ let err = panic "modifyStackState" ["Global variable not initialized"]+ let gv = fromMaybe err currGv+ let xs = case lookupGlobal gv (st ^. stateGlobals) of+ Nothing -> panic "modifyStackState" ["Global variable not defined!"]+ Just v -> v+ let st' = st & stateGlobals %~ insertGlobal gv (f xs)+ return (ExecutionFeatureModifiedState (mkSt st')) onTransition ::- IORef BoundedExecGlobal ->+ IORef (Maybe BoundedExecGlobal) -> BlockID blocks tgt_args -> ControlResumption p sym ext rtp (CrucibleLang blocks ret) -> SimState p sym ext rtp (CrucibleLang blocks ret) ('Just a) -> IO (ExecutionFeatureResult p sym ext rtp) onTransition gvRef tgt_id res st = stateSolverProof st $- do let sym = st^.stateSymInterface- let simCtx = st^.stateContext- (globals', overLimit) <- checkBackedge gvRef (st^.stateCrucibleFrame.frameBlockID) tgt_id (st^.stateGlobals)+ do let sym = st ^. stateSymInterface+ (globals', overLimit) <- checkBackedge gvRef (st ^. stateCrucibleFrame.frameBlockID) tgt_id (st ^. stateGlobals) let st' = st & stateGlobals .~ globals' case overLimit of Just n -> do let msg = "reached maximum number of loop iterations (" ++ show n ++ ")"- let loc = st^.stateCrucibleFrame.to frameProgramLoc+ let loc = st ^. stateCrucibleFrame.to frameProgramLoc let err = SimError loc (ResourceExhausted msg)- when generateSideConditions $ withBackend simCtx $ \bak ->+ when generateSideConditions $ withStateBackend st $ \bak -> addProofObligation bak (LabeledPred (falsePred sym) err) return (ExecutionFeatureNewState (AbortState (AssertionFailure err) st')) Nothing -> return (ExecutionFeatureModifiedState (ControlTransferState res st')) onStep ::- IORef BoundedExecGlobal ->+ IORef (Maybe BoundedExecGlobal) -> ExecState p sym ext rtp -> IO (ExecutionFeatureResult p sym ext rtp) onStep gvRef = \case InitialState simctx globals ah ret cont -> do let halloc = simHandleAllocator simctx- gv <- freshGlobalVar halloc (Text.pack "BoundedExecFrameData") knownRepr- writeIORef gvRef gv- let globals' = insertGlobal gv [Left "_init"] globals+ currGv <- readIORef gvRef+ ngv <- case currGv of+ Nothing -> do+ gv <- freshGlobalVar halloc (Text.pack "BoundedExecFrameData") knownRepr+ writeIORef gvRef (Just gv)+ pure gv+ Just gv -> pure gv+ let globals' = insertGlobal ngv [Left "_init"] globals let simctx' = simctx{ ctxIntrinsicTypes = MapF.insert (knownSymbol @"BoundedExecFrameData") IntrinsicMuxFn (ctxIntrinsicTypes simctx) } return (ExecutionFeatureModifiedState (InitialState simctx' globals' ah ret cont))
src/Lang/Crucible/Simulator/BoundedRecursion.hs view
@@ -32,13 +32,14 @@ ( boundedRecursionFeature ) where -import Control.Lens ( (^.), (&), (%~) ) import Control.Monad (when)+import Data.Function ((&)) import Data.IORef+import qualified Data.Map.Strict as Map import Data.Maybe import qualified Data.Text as Text import Data.Word-import qualified Data.Map.Strict as Map+import Lens.Micro ((^.), (%~)) import Data.Parameterized.Ctx import qualified Data.Parameterized.Map as MapF@@ -81,56 +82,60 @@ IO (GenericExecutionFeature sym) boundedRecursionFeature getRecursionBound generateSideConditions =- do gvRef <- newIORef (error "Global variable for BoundedRecursionData not initialized")+ do gvRef <- newIORef Nothing return $ GenericExecutionFeature $ onStep gvRef where popFrame ::- IORef BoundedRecursionGlobal ->+ IORef (Maybe BoundedRecursionGlobal) -> (SimState p sym ext rtp f args -> ExecState p sym ext rtp) -> SimState p sym ext rtp f args -> IO (ExecutionFeatureResult p sym ext rtp)- popFrame gvRef mkSt st =- do gv <- readIORef gvRef- case lookupGlobal gv (st ^. stateGlobals) of- Nothing -> panic "bounded recursion" ["global not defined!"]- Just [] -> panic "bounded recursion" ["pop on empty stack!"]- Just (_:xs) ->- do let st' = st & stateGlobals %~ insertGlobal gv xs- return (ExecutionFeatureModifiedState (mkSt st'))+ popFrame gvRef mkSt st = do+ currGv <- readIORef gvRef+ let err = panic "bounded recursion" ["gv not initialized"]+ let gv = fromMaybe err currGv+ case lookupGlobal gv (st ^. stateGlobals) of+ Nothing -> panic "bounded recursion" ["global not defined!"]+ Just [] -> panic "bounded recursion" ["pop on empty stack!"]+ Just (_ : xs) -> do+ let st' = st & stateGlobals %~ insertGlobal gv xs+ return (ExecutionFeatureModifiedState (mkSt st')) pushFrame ::- IORef BoundedRecursionGlobal ->+ IORef (Maybe BoundedRecursionGlobal) -> (BoundedRecursionMap -> BoundedRecursionMap -> [BoundedRecursionMap] -> [BoundedRecursionMap]) -> SomeHandle -> (SimState p sym ext rtp f args -> ExecState p sym ext rtp) -> SimState p sym ext rtp f args -> IO (ExecutionFeatureResult p sym ext rtp) pushFrame gvRef rebuildStack h mkSt st = stateSolverProof st $- do let sym = st^.stateSymInterface- let simCtx = st^.stateContext- gv <- readIORef gvRef+ do let sym = st ^. stateSymInterface+ currGv <- readIORef gvRef+ let err = panic "pushFrame" ["Uninitialized global!"]+ let gv = fromMaybe err currGv case lookupGlobal gv (st ^. stateGlobals) of Nothing -> panic "bounded recursion" ["global not defined!"] Just [] -> panic "bounded recursion" ["empty stack!"]- Just (x:xs) ->- do mb <- getRecursionBound h- let v = 1 + fromMaybe 0 (Map.lookup h x)- case mb of- Just b | v > b ->- do loc <- getCurrentProgramLoc sym- let msg = ("reached maximum number of recursive calls to function " ++ show h ++ " (" ++ show b ++ ")")- let err = SimError loc (ResourceExhausted msg)- when generateSideConditions $ withBackend simCtx $ \bak ->- addProofObligation bak (LabeledPred (falsePred sym) err)- return (ExecutionFeatureNewState (AbortState (AssertionFailure err) st))- _ ->- do let x' = Map.insert h v x- let st' = st & stateGlobals %~ insertGlobal gv (rebuildStack x' x xs)- x' `seq` return (ExecutionFeatureModifiedState (mkSt st'))+ Just (x:xs) -> do+ mb <- getRecursionBound h+ let v = 1 + fromMaybe 0 (Map.lookup h x)+ case mb of+ Just b | v > b -> do+ loc <- getCurrentProgramLoc sym+ let msg = ("reached maximum number of recursive calls to function " ++ show h ++ " (" ++ show b ++ ")")+ let simerr = SimError loc (ResourceExhausted msg)+ when generateSideConditions $ withStateBackend st $ \bak ->+ addProofObligation bak (LabeledPred (falsePred sym) simerr)+ return (ExecutionFeatureNewState (AbortState (AssertionFailure simerr) st))+ _ -> do+ let x' = Map.insert h v x+ let st' = st & stateGlobals %~ insertGlobal gv (rebuildStack x' x xs)+ x' `seq` return (ExecutionFeatureModifiedState (mkSt st')) + onStep ::- IORef BoundedRecursionGlobal ->+ IORef (Maybe BoundedRecursionGlobal) -> ExecState p sym ext rtp -> IO (ExecutionFeatureResult p sym ext rtp) @@ -138,8 +143,13 @@ InitialState simctx globals ah ret cont -> do let halloc = simHandleAllocator simctx- gv <- freshGlobalVar halloc (Text.pack "BoundedRecursionData") knownRepr- writeIORef gvRef gv+ currGv <- readIORef gvRef+ gv <- case currGv of+ Just gv -> pure gv+ Nothing -> do+ gv <- freshGlobalVar halloc (Text.pack "BoundedRecursionData") knownRepr+ writeIORef gvRef (Just gv)+ pure gv let simctx' = simctx{ ctxIntrinsicTypes = MapF.insert (knownSymbol @"BoundedRecursionData") IntrinsicMuxFn
− src/Lang/Crucible/Simulator/Breakpoint.hs
@@ -1,109 +0,0 @@--------------------------------------------------------------------------- |--- Module : Lang.Crucible.Simulator.Breakpoint--- Description : Support for symbolic execution breakpoints--- Copyright : (c) Galois, Inc 2019--- License : BSD3--- Maintainer : Andrei Stefanescu <andrei@galois.com>--- Stability : provisional------ This module provides execution features for changing the state on--- breakpoints.-------------------------------------------------------------------------{-# LANGUAGE GADTs #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE RecordWildCards #-}-module Lang.Crucible.Simulator.Breakpoint- ( breakAndReturn- ) where--import Control.Lens-import Control.Monad.Reader-import qualified Data.Bimap as Bimap-import Data.HashMap.Strict (HashMap)-import qualified Data.HashMap.Strict as HashMap--import Data.Parameterized.Classes-import qualified Data.Parameterized.Context as Ctx-import Data.Parameterized.Some-import Data.Parameterized.TraversableFC--import qualified Lang.Crucible.Backend as C-import qualified Lang.Crucible.CFG.Core as C-import qualified Lang.Crucible.CFG.Expr as C-import qualified Lang.Crucible.Simulator.CallFrame as C-import qualified Lang.Crucible.Simulator.EvalStmt as C-import qualified Lang.Crucible.Simulator.ExecutionTree as C-import qualified Lang.Crucible.Simulator.Operations as C-import qualified Lang.Crucible.Simulator.OverrideSim as C-import qualified Lang.Crucible.Simulator.RegValue as C-import qualified What4.FunctionName as W---- | This execution feature registers an override for a breakpoint.--- The override summarizes the execution from the breakpoint--- to the return from the function (similar to a tail call).--- This feature requires a map from each function handle--- to the list of breakpoints in the respective function with this--- execution feature.-breakAndReturn ::- (C.IsSymInterface sym, C.IsSyntaxExtension ext) =>- C.CFG ext blocks init ret ->- C.BreakpointName ->- Ctx.Assignment C.TypeRepr args ->- C.TypeRepr ret ->- C.OverrideSim p sym ext rtp args ret (C.RegValue sym ret) ->- HashMap C.SomeHandle [C.BreakpointName] ->- IO (C.ExecutionFeature p sym ext rtp)-breakAndReturn C.CFG{..} breakpoint_name arg_types ret_type override all_breakpoints =- case Bimap.lookup breakpoint_name cfgBreakpoints of- Just (Some breakpoint_block_id)- | breakpoint_block <- C.getBlock breakpoint_block_id cfgBlockMap- , Just Refl <- testEquality (C.blockInputs breakpoint_block) arg_types ->- return $ C.ExecutionFeature $ \case- C.RunningState (C.RunPostBranchMerge block_id) state- | frame <- state ^. C.stateCrucibleFrame- , C.SomeHandle cfgHandle == C.frameHandle frame- , Just Refl <- testEquality- (fmapFC C.blockInputs cfgBlockMap)- (fmapFC C.blockInputs $ C.frameBlockMap frame)- , Just Refl <- testEquality breakpoint_block_id block_id- , Just Refl <- testEquality ret_type (C.frameReturnType frame) -> do- let override_frame = C.OF $ C.OverrideFrame- { _override = W.functionNameFromText $- C.breakpointNameText breakpoint_name- , _overrideHandle = C.frameHandle frame- , _overrideRegMap = state ^.- C.stateCrucibleFrame . C.frameRegs- }- result_state <- runReaderT (C.runOverrideSim ret_type override) $- state & C.stateTree %~- C.pushCallFrame C.TailReturnToCrucible override_frame- return $ C.ExecutionFeatureNewState result_state- C.CallState return_handler (C.CrucibleCall block_id frame) state- | Just breakpoints <- HashMap.lookup- (C.frameHandle frame)- all_breakpoints -> do- let result_frame = C.setFrameBreakpointPostdomInfo- breakpoints- frame- result_state <- runReaderT- (C.performFunctionCall- return_handler- (C.CrucibleCall block_id result_frame))- state- return $ C.ExecutionFeatureNewState result_state- C.TailCallState value_from_value (C.CrucibleCall block_id frame) state- | Just breakpoints <- HashMap.lookup- (C.frameHandle frame)- all_breakpoints -> do- let result_frame = C.setFrameBreakpointPostdomInfo- breakpoints- frame- result_state <- runReaderT- (C.performTailCall- value_from_value- (C.CrucibleCall block_id result_frame))- state- return $ C.ExecutionFeatureNewState result_state- _ -> return C.ExecutionFeatureNoChange- _ -> fail $ "unexpected breakpoint: " ++ show breakpoint_name
src/Lang/Crucible/Simulator/CallFrame.hs view
@@ -38,7 +38,7 @@ , framePostdom , frameProgramLoc , setFrameBlock- , setFrameBreakpointPostdomInfo+ , setFrameCutpointPostdomInfo , extendFrame , updateFrame , mergeCallFrame@@ -58,15 +58,17 @@ , fromCallFrame , fromReturnFrame , frameFunctionName+ , frameStackLoc ) where -import Control.Lens+import Data.Functor.Const (getConst) import Data.Kind import qualified Data.Parameterized.Context as Ctx+import Lens.Micro ((^.), Lens', Lens, SimpleGetter, lens, to) import What4.FunctionName import What4.Interface ( Pred )-import What4.ProgramLoc ( ProgramLoc )+import What4.ProgramLoc ( ProgramLoc, mkProgramLoc, Position(..) ) import Lang.Crucible.Analysis.Postdom import Lang.Crucible.CFG.Core@@ -129,22 +131,22 @@ frameReturnType :: CallFrame sym ext blocks ret ctx -> TypeRepr ret frameReturnType CallFrame { _frameCFG = g } = cfgReturnType g -framePostdomMap :: Simple Lens (CallFrame sym ext blocks ret ctx) (CFGPostdom blocks)+framePostdomMap :: Lens' (CallFrame sym ext blocks ret ctx) (CFGPostdom blocks) framePostdomMap = lens _framePostdomMap (\s x -> s{ _framePostdomMap = x }) -frameBlockID :: Simple Lens (CallFrame sym ext blocks ret ctx) (Some (BlockID blocks))+frameBlockID :: Lens' (CallFrame sym ext blocks ret ctx) (Some (BlockID blocks)) frameBlockID = lens _frameBlockID (\s v -> s { _frameBlockID = v }) -- | List of statements to execute next.-frameStmts :: Simple Lens (CallFrame sym ext blocks ret ctx) (StmtSeq ext blocks ret ctx)+frameStmts :: Lens' (CallFrame sym ext blocks ret ctx) (StmtSeq ext blocks ret ctx) frameStmts = lens _frameStmts (\s v -> s { _frameStmts = v }) {-# INLINE frameStmts #-} -frameRegs :: Simple Lens (CallFrame sym ext blocks ret args) (RegMap sym args)+frameRegs :: Lens' (CallFrame sym ext blocks ret args) (RegMap sym args) frameRegs = lens _frameRegs (\s v -> s { _frameRegs = v }) -- | List of statements to execute next.-framePostdom :: Simple Lens (CallFrame sym ext blocks ret ctx) (Some (CrucibleBranchTarget (CrucibleLang blocks ret)))+framePostdom :: Lens' (CallFrame sym ext blocks ret ctx) (Some (CrucibleBranchTarget (CrucibleLang blocks ret))) framePostdom = lens _framePostdom (\s v -> s { _framePostdom = v }) -- | Create a new call frame.@@ -171,7 +173,7 @@ , _framePostdomMap = pdInfo , _frameBlockID = Some bid , _frameRegs = args- , _frameStmts = b^.blockStmts+ , _frameStmts = b ^. blockStmts , _framePostdom = mkFramePostdom pds } @@ -182,7 +184,7 @@ -- | Return program location associated with frame. frameProgramLoc :: CallFrame sym ext blocks ret ctx -> ProgramLoc-frameProgramLoc cf = firstStmtLoc (cf^.frameStmts)+frameProgramLoc cf = firstStmtLoc (cf ^. frameStmts) setFrameBlock :: BlockID blocks args -> RegMap sym args@@ -190,20 +192,20 @@ -> CallFrame sym ext blocks ret args setFrameBlock bid@(BlockID block_id) args f = f' where b = frameBlockMap f Ctx.! block_id- pds = getConst $ (f^.framePostdomMap.ixF block_id)+ pds = getConst $ (f ^. framePostdomMap.ixF block_id) f' = f { _frameBlockID = Some bid , _frameRegs = args- , _frameStmts = b^.blockStmts+ , _frameStmts = b ^. blockStmts , _framePostdom = mkFramePostdom pds } -setFrameBreakpointPostdomInfo ::- [BreakpointName] ->+setFrameCutpointPostdomInfo ::+ [CutpointName] -> CallFrame sym ext blocks ret ctx -> CallFrame sym ext blocks ret ctx-setFrameBreakpointPostdomInfo breakpoints f = case f of+setFrameCutpointPostdomInfo cutpoints f = case f of CallFrame{ _frameCFG = g, _frameBlockID = Some (BlockID block_id) } -> do- let pdInfo = breakpointPostdomInfo g breakpoints+ let pdInfo = cutpointPostdomInfo g cutpoints f { _framePostdomMap = pdInfo , _framePostdom = mkFramePostdom (getConst $ pdInfo Ctx.! block_id) }@@ -257,10 +259,10 @@ -- ^ Arguments to override. } -override :: Simple Lens (OverrideFrame sym ret args) FunctionName+override :: Lens' (OverrideFrame sym ret args) FunctionName override = lens _override (\o x -> o{ _override = x }) -overrideHandle :: Simple Lens (OverrideFrame sym ret args) SomeHandle+overrideHandle :: Lens' (OverrideFrame sym ret args) SomeHandle overrideHandle = lens _overrideHandle (\o x -> o { _overrideHandle = x }) overrideRegMap :: Lens (OverrideFrame sym ret args) (OverrideFrame sym ret args')@@ -281,7 +283,13 @@ FrameRetType (CrucibleLang b r) = r FrameRetType (OverrideLang r) = r -data SimFrame sym ext l (args :: Maybe (Ctx CrucibleType)) where+-- | A frame on the stack.+--+-- Type parameters:+--+-- - @f@: the type of the top frame ('CrucibleLang' or 'OverrideLang')+-- - @args@: arguments; 'Just' for call frames, 'Nothing' for a return frame+data SimFrame sym ext f (args :: Maybe (Ctx CrucibleType)) where -- | Custom code to execute, typically for "overrides" OF :: !(OverrideFrame sym ret args) -> SimFrame sym ext (OverrideLang ret) ('Just args)@@ -317,8 +325,16 @@ -> RegEntry sym (FrameRetType f) fromReturnFrame (RF _ x) = x -frameFunctionName :: Getter (SimFrame sym ext f a) FunctionName+frameFunctionName :: SimpleGetter (SimFrame sym ext f a) FunctionName frameFunctionName = to $ \case- OF f -> f^.override+ OF f -> f ^. override MF f -> case frameHandle f of SomeHandle h -> handleName h RF n _ -> n++-- | Get a location for a stack frame, or Nothing if this is a return frame+frameStackLoc :: (SimFrame sym ext f a) -> Maybe ProgramLoc+frameStackLoc frame =+ case frame of+ sf@(OF _) -> Just $ mkProgramLoc (sf ^. frameFunctionName) InternalPos+ (MF f) -> Just $ frameProgramLoc f+ (RF _ _) -> Nothing
+ src/Lang/Crucible/Simulator/Cut.hs view
@@ -0,0 +1,110 @@+-----------------------------------------------------------------------+-- |+-- Module : Lang.Crucible.Simulator.Cut+-- Description : Support for symbolic execution cuts+-- Copyright : (c) Galois, Inc 2019+-- License : BSD3+-- Maintainer : Andrei Stefanescu <andrei@galois.com>+-- Stability : provisional+--+-- This module provides execution features for changing the state on+-- cutpoints.+-----------------------------------------------------------------------+{-# LANGUAGE GADTs #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE RecordWildCards #-}+module Lang.Crucible.Simulator.Cut+ ( cutAndReturn+ ) where++import Control.Monad.Reader+import qualified Data.Bimap as Bimap+import Data.Function ((&))+import Data.HashMap.Strict (HashMap)+import qualified Data.HashMap.Strict as HashMap+import Lens.Micro ((^.), (%~))++import Data.Parameterized.Classes+import qualified Data.Parameterized.Context as Ctx+import Data.Parameterized.Some+import Data.Parameterized.TraversableFC++import qualified Lang.Crucible.Backend as C+import qualified Lang.Crucible.CFG.Core as C+import qualified Lang.Crucible.CFG.Expr as C+import qualified Lang.Crucible.Simulator.CallFrame as C+import qualified Lang.Crucible.Simulator.EvalStmt as C+import qualified Lang.Crucible.Simulator.ExecutionTree as C+import qualified Lang.Crucible.Simulator.Operations as C+import qualified Lang.Crucible.Simulator.OverrideSim as C+import qualified Lang.Crucible.Simulator.RegValue as C+import qualified What4.FunctionName as W++-- | This execution feature registers an override for a cutpoint.+-- The override summarizes the execution from the cutpoint+-- to the return from the function (similar to a tail call).+-- This feature requires a map from each function handle+-- to the list of cutpoints in the respective function with this+-- execution feature.+cutAndReturn ::+ (C.IsSymInterface sym, C.IsSyntaxExtension ext) =>+ C.CFG ext blocks init ret ->+ C.CutpointName ->+ Ctx.Assignment C.TypeRepr args ->+ C.TypeRepr ret ->+ C.OverrideSim p sym ext rtp args ret (C.RegValue sym ret) ->+ HashMap C.SomeHandle [C.CutpointName] ->+ IO (C.ExecutionFeature p sym ext rtp)+cutAndReturn C.CFG{..} cutpoint_name arg_types ret_type override all_cutpoints =+ case Bimap.lookup cutpoint_name cfgCutpoints of+ Just (Some cutpoint_block_id)+ | cutpoint_block <- C.getBlock cutpoint_block_id cfgBlockMap+ , Just Refl <- testEquality (C.blockInputs cutpoint_block) arg_types ->+ return $ C.ExecutionFeature $ \case+ C.RunningState (C.RunPostBranchMerge block_id) state+ | frame <- state ^. C.stateCrucibleFrame+ , C.SomeHandle cfgHandle == C.frameHandle frame+ , Just Refl <- testEquality+ (fmapFC C.blockInputs cfgBlockMap)+ (fmapFC C.blockInputs $ C.frameBlockMap frame)+ , Just Refl <- testEquality cutpoint_block_id block_id+ , Just Refl <- testEquality ret_type (C.frameReturnType frame) -> do+ let override_frame = C.OF $ C.OverrideFrame+ { _override = W.functionNameFromText $+ C.cutpointNameText cutpoint_name+ , _overrideHandle = C.frameHandle frame+ , _overrideRegMap = state ^.+ C.stateCrucibleFrame . C.frameRegs+ }+ result_state <- runReaderT (C.runOverrideSim ret_type override) $+ state & C.stateTree %~+ C.pushCallFrame C.TailReturnToCrucible override_frame+ return $ C.ExecutionFeatureNewState result_state+ C.CallState return_handler (C.CrucibleCall block_id frame) state+ | Just cutpoints <- HashMap.lookup+ (C.frameHandle frame)+ all_cutpoints -> do+ let result_frame = C.setFrameCutpointPostdomInfo+ cutpoints+ frame+ result_state <- runReaderT+ (C.performFunctionCall+ return_handler+ (C.CrucibleCall block_id result_frame))+ state+ return $ C.ExecutionFeatureNewState result_state+ C.TailCallState value_from_value (C.CrucibleCall block_id frame) state+ | Just cutpoints <- HashMap.lookup+ (C.frameHandle frame)+ all_cutpoints -> do+ let result_frame = C.setFrameCutpointPostdomInfo+ cutpoints+ frame+ result_state <- runReaderT+ (C.performTailCall+ value_from_value+ (C.CrucibleCall block_id result_frame))+ state+ return $ C.ExecutionFeatureNewState result_state+ _ -> return C.ExecutionFeatureNoChange+ _ -> fail $ "unexpected cutpoint: " ++ show cutpoint_name
src/Lang/Crucible/Simulator/EvalStmt.hs view
@@ -45,15 +45,17 @@ ) where import qualified Control.Exception as Ex-import Control.Lens import Control.Monad (foldM, when) import Control.Monad.IO.Class (MonadIO(..))-import Control.Monad.Reader (ReaderT(..), withReaderT)+import Control.Monad.Reader (ReaderT(..), withReaderT, ask)+import Data.Function ((&)) import Data.Maybe (fromMaybe) import qualified Data.Parameterized.Context as Ctx import Data.Parameterized.TraversableFC import qualified Data.Text as Text import Data.Time.Clock+import Lens.Micro ((^.), (.~), (%~), to)+import Lens.Micro.Mtl (view) import System.IO import System.IO.Error as Ex import Prettyprinter@@ -100,7 +102,7 @@ String -> IO () evalLogFn verb s n msg = do- let h = s^.stateContext.to printHandle+ let h = s ^. stateContext.to printHandle if verb >= n then do hPutStr h msg hFlush h@@ -114,12 +116,11 @@ Expr ext ctx tp -> ReaderT (CrucibleState p sym ext rtp blocks r ctx) IO (RegValue sym tp) evalExpr verb (App a) = ReaderT $ \s ->- do let iteFns = s^.stateIntrinsicTypes- let simCtx = s^.stateContext+ do let iteFns = s ^. stateIntrinsicTypes let logFn = evalLogFn verb s- r <- withBackend simCtx $ \bak ->+ r <- withStateBackend s $ \bak -> evalApp bak iteFns logFn- (extensionEval (extensionImpl (s^.stateContext)) bak iteFns logFn s)+ (extensionEval (extensionImpl (s ^. stateContext)) bak iteFns logFn s) (\r -> runReaderT (evalReg r) s) a return $! r@@ -138,7 +139,7 @@ Monad m => Ctx.Assignment (Reg ctx) args -> ReaderT (CrucibleState p sym ext rtp blocks r ctx) m (RegMap sym args)-evalArgs args = ReaderT $ \s -> return $! evalArgs' (s^.stateCrucibleFrame.frameRegs) args+evalArgs args = ReaderT $ \s -> return $! evalArgs' (s ^. stateCrucibleFrame.frameRegs) args {-# INLINE evalArgs #-} -- | Resolve the arguments for a jump.@@ -207,8 +208,10 @@ StmtSeq ext blocks r ctx' {- ^ Remaining statements in the block -} -> ExecCont p sym ext rtp (CrucibleLang blocks r) ('Just ctx) stepStmt verb stmt rest =- do ctx <- view stateContext- let sym = ctx^.ctxSymInterface++ do st <- ask+ ctx <- view stateContext+ let sym = ctx ^. ctxSymInterface let iTypes = ctxIntrinsicTypes ctx globals <- view (stateTree.actFrame.gpGlobals) @@ -217,7 +220,7 @@ ExecCont p sym ext rtp' f a continueWith f = withReaderT f (checkConsTerm verb) - withBackend ctx $ \bak ->+ withStateBackend st $ \bak -> case stmt of NewRefCell tpr x -> do let halloc = simHandleAllocator ctx@@ -402,8 +405,8 @@ stepTerm _ (ErrorStmt msg) = do msg' <- evalReg msg- simCtx <- view stateContext- withBackend simCtx $ \bak -> liftIO $+ st <- ask+ withStateBackend st $ \bak -> liftIO $ case asString msg' of Just (UnicodeLiteral txt) -> addFailedAssertion bak@@ -421,9 +424,9 @@ checkConsTerm verb = do cf <- view stateCrucibleFrame - case cf^.frameStmts of+ case cf ^. frameStmts of ConsStmt _ _ _ -> stepBasicBlock verb- TermStmt _ _ -> continue (RunBlockEnd (cf^.frameBlockID))+ TermStmt _ _ -> continue (RunBlockEnd (cf ^. frameBlockID)) -- | Main evaluation operation for running a single step of -- basic block evaluation.@@ -435,15 +438,15 @@ ExecCont p sym ext rtp (CrucibleLang blocks r) ('Just ctx) stepBasicBlock verb = do ctx <- view stateContext- let sym = ctx^.ctxSymInterface+ let sym = ctx ^. ctxSymInterface let h = printHandle ctx cf <- view stateCrucibleFrame - case cf^.frameStmts of+ case cf ^. frameStmts of ConsStmt pl stmt rest -> do liftIO $ do setCurrentProgramLoc sym pl- let sz = regMapSize (cf^.frameRegs)+ let sz = regMapSize (cf ^. frameRegs) when (verb >= 4) $ ppStmtAndLoc h (frameHandle cf) pl (ppStmt sz stmt) stepStmt verb stmt rest @@ -495,7 +498,7 @@ k cont st AbortState rsn st ->- let (AH handler) = st^.abortHandler in+ let (AH handler) = st ^. abortHandler in k (handler rsn) st OverrideState ovr st ->
src/Lang/Crucible/Simulator/Evaluation.hs view
@@ -35,14 +35,15 @@ import Prelude hiding (pred) import qualified Control.Exception as Ex-import Control.Lens import Control.Monad import qualified Data.BitVector.Sized as BV+import Data.Function ((&)) import qualified Data.Map.Strict as Map import Data.Maybe import qualified Data.Text as Text import qualified Data.Vector as V import Data.Word+import Lens.Micro ((.~)) import Numeric ( showHex ) import Numeric.Natural import GHC.Stack@@ -80,7 +81,7 @@ -- Coercion functions integerAsChar :: Integer -> Word16-integerAsChar i = fromInteger ((i `max` 0) `min` (2^(16::Int)-1))+integerAsChar i = fromInteger ((i `max` 0) `min` (2 ^ (16::Int)-1)) complexRealAsChar :: (MonadFail m, IsExpr val) => val BaseComplexType@@ -490,6 +491,8 @@ do xs' <- evalSub xs mu <- unconsSymSequence sym (muxRegForType sym itefns tpr) xs' traverse (\ (h,tl) -> pure (Ctx.Empty Ctx.:> RV h Ctx.:> RV tl)) mu+ SequenceReverse _tpr xs ->+ reverseSymSequence sym =<< evalSub xs -------------------------------------------------------------------- -- Symbolic Arrays@@ -646,6 +649,8 @@ iFloatFpApart @_ @fi sym x y FloatCast fi rm (x_expr :: f (FloatType fi')) -> iFloatCast @_ @_ @fi' sym fi rm =<< evalSub x_expr+ FloatRound _ rm (x_expr :: f (FloatType fi)) ->+ iFloatRound @_ @fi sym rm =<< evalSub x_expr FloatFromBinary fi x_expr -> iFloatFromBinary sym fi =<< evalSub x_expr FloatToBinary fi x_expr -> iFloatToBinary sym fi =<< evalSub x_expr FloatFromBV fi rm x_expr -> iBVToFloat sym fi rm =<< evalSub x_expr
src/Lang/Crucible/Simulator/ExecutionTree.hs view
@@ -30,6 +30,7 @@ {-# LANGUAGE PolyKinds #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneKindSignatures #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} {-# OPTIONS_GHC -fprint-explicit-kinds -Wall #-}@@ -68,8 +69,12 @@ , ResolvedCall(..) , resolvedCallHandle , execResultContext+ , setExecResultContext , execStateContext+ , setExecStateContext , execStateSimState+ , execResultGlobals+ , execStateGlobals -- * Simulator context trees -- ** Main context data structures@@ -111,12 +116,16 @@ , IsSymInterfaceProof , SimContext(..) , Metric(..)+ , ExceptionContextConfig(..) , initSimContext , withBackend+ , withStateBackend , ctxSymInterface , functionBindings , cruciblePersonality , profilingMetrics+ , exceptionContextConfig+ , parseExceptionContextConfig -- * SimState , SimState(..)@@ -138,18 +147,22 @@ , stateOverrideFrame , stateGlobals , stateConfiguration+ , stateProgramStack ) where -import Control.Lens import Control.Monad.Reader+import Data.Function ((&)) import Data.Kind import Data.Map.Strict (Map) import qualified Data.Map.Strict as Map+import Data.Maybe(maybeToList) import Data.Parameterized.Ctx import qualified Data.Parameterized.Context as Ctx import Data.Text (Text)+import Lens.Micro ((^.), (.~), Lens', Lens, SimpleGetter, Traversal', lens, to) import System.Exit (ExitCode) import System.IO+import Text.Read(readMaybe) import qualified Prettyprinter as PP import What4.Config (Config)@@ -169,13 +182,20 @@ import Lang.Crucible.Simulator.GlobalState (SymGlobalState) import Lang.Crucible.Simulator.Intrinsics (IntrinsicTypes) import Lang.Crucible.Simulator.RegMap (RegMap, emptyRegMap, RegValue, RegEntry)+import Lang.Crucible.Simulator.SimError(ProgramStack(..)) import Lang.Crucible.Types ------------------------------------------------------------------------ -- GlobalPair --- | A value of some type 'v' together with a global state.-data GlobalPair sym (v :: Type) =+-- | A value of some type @v@ together with a global state.+--+-- Type parameters:+--+-- - @sym@: instance of 'Lang.Crucible.Backend.IsSymInterface'+-- - @v@: type of the value+type GlobalPair :: Type -> Type -> Type+data GlobalPair sym v = GlobalPair { _gpValue :: !v , _gpGlobals :: !(SymGlobalState sym)@@ -186,7 +206,7 @@ gpValue = lens _gpValue (\s v -> s { _gpValue = v }) -- | Access the globals stored in the global pair.-gpGlobals :: Simple Lens (GlobalPair sym u) (SymGlobalState sym)+gpGlobals :: Lens' (GlobalPair sym u) (SymGlobalState sym) gpGlobals = lens _gpGlobals (\s v -> s { _gpGlobals = v }) @@ -194,7 +214,15 @@ -- TopFrame -- | The currently-executing frame plus the global state associated with it.-type TopFrame sym ext f a = GlobalPair sym (SimFrame sym ext f a)+--+-- Type parameters:+--+-- - @sym@: instance of 'Lang.Crucible.Backend.IsSymInterface'+-- - @ext@: language extension, see "Lang.Crucible.CFG.Extension"+-- - @f@: the type of the top frame ('CrucibleLang' or 'OverrideLang')+-- - @args@: arguments to this frame (see 'SimFrame')+type TopFrame :: Type -> Type -> Type -> Maybe (Ctx CrucibleType) -> Type+type TopFrame sym ext f args = GlobalPair sym (SimFrame sym ext f args) -- | Access the Crucible call frame inside a 'TopFrame'. crucibleTopFrame ::@@ -222,6 +250,12 @@ -- path might abort because it became infeasible (inconsistent path -- conditions), because the program called an exit primitive, or -- because of a true error condition (e.g., a failed assertion).+--+-- Type parameters:+--+-- - @sym@: instance of 'Lang.Crucible.Backend.IsSymInterface'+-- - @ext@: language extension, see "Lang.Crucible.CFG.Extension"+type AbortedResult :: Type -> Type -> Type data AbortedResult sym ext where -- | A single aborted execution with the execution state at time of the abort and the reason. AbortedExec ::@@ -229,9 +263,10 @@ !(GlobalPair sym (SimFrame sym ext l args)) -> AbortedResult sym ext - -- | An aborted execution that was ended by a call to 'exit'.+ -- | An aborted execution that was ended by a call to @exit@. AbortedExit :: !ExitCode ->+ !(GlobalPair sym (SimFrame sym ext l args)) -> AbortedResult sym ext -- | Two separate threads of execution aborted after a symbolic branch,@@ -248,7 +283,10 @@ -- | This represents an execution frame where its frame type -- and arguments have been hidden.-data SomeFrame (f :: fk -> argk -> Type) = forall l a . SomeFrame !(f l a)+--+-- The type parameter @f@ is usually 'SimFrame'.+type SomeFrame :: forall fk argk. (fk -> argk -> Type) -> Type+data SomeFrame f = forall l a . SomeFrame !(f l a) -- | Return the program locations of all the Crucible frames. filterCrucibleFrames :: SomeFrame (SimFrame sym ext) -> Maybe ProgramLoc@@ -256,11 +294,13 @@ filterCrucibleFrames _ = Nothing -- | Iterate over frames in the result.-arFrames :: Simple Traversal (AbortedResult sym ext) (SomeFrame (SimFrame sym ext))+arFrames :: Traversal' (AbortedResult sym ext) (SomeFrame (SimFrame sym ext)) arFrames h (AbortedExec e p) = (\(SomeFrame f') -> AbortedExec e (p & gpValue .~ f'))- <$> h (SomeFrame (p^.gpValue))-arFrames _ (AbortedExit ec) = pure (AbortedExit ec)+ <$> h (SomeFrame (p ^. gpValue))+arFrames h (AbortedExit ec p) =+ (\(SomeFrame f') -> AbortedExit ec (p & gpValue .~ f'))+ <$> h (SomeFrame (p ^. gpValue)) arFrames h (AbortedBranch predicate loc r s) = AbortedBranch predicate loc <$> arFrames h r <*> arFrames h s@@ -272,7 +312,7 @@ where pp :: SomeFrame (SimFrame sym ext) -> PP.Doc ann pp (SomeFrame (OF f)) =- PP.pretty "When calling" PP.<+> PP.viaShow (f^.override)+ PP.pretty "When calling" PP.<+> PP.viaShow (f ^. override) pp (SomeFrame (MF f)) = PP.pretty "In" PP.<+> PP.viaShow (frameHandle f) PP.<+> PP.pretty "at" PP.<+> PP.pretty (plSourceLoc (frameProgramLoc f))@@ -289,7 +329,14 @@ -- 'PartialResult', then some of the computation paths that led to -- this result aborted for some reason, and the resulting value is -- only defined if the associated condition is true.-data PartialResult sym ext (v :: Type)+--+-- Type parameters:+--+-- - @sym@: instance of 'Lang.Crucible.Backend.IsSymInterface'+-- - @ext@: language extension, see "Lang.Crucible.CFG.Extension"+-- - @v@: Type of the result of the computation+type PartialResult :: Type -> Type -> Type -> Type+data PartialResult sym ext v {- | A 'TotalRes' indicates that the the global pair is always defined. -} = TotalRes !(GlobalPair sym v)@@ -318,6 +365,14 @@ {-# INLINE partialValue #-} -- | The result of resolving a function call.+--+-- Type parameters:+--+-- - @p@: see 'cruciblePersonality'+-- - @sym@: instance of 'Lang.Crucible.Backend.IsSymInterface'+-- - @ext@: language extension, see "Lang.Crucible.CFG.Extension"+-- - @ret@: 'CrucibleType' of the return value+type ResolvedCall :: Type -> Type -> Type -> CrucibleType -> Type data ResolvedCall p sym ext ret where -- | A resolved function call to an override. OverrideCall ::@@ -341,15 +396,23 @@ -- | Executions that have completed either due to (partial or total) -- successful completion or by some abort condition.-data ExecResult p sym ext (r :: Type)+--+-- Type parameters:+--+-- - @p@: see 'cruciblePersonality'+-- - @sym@: instance of 'Lang.Crucible.Backend.IsSymInterface'+-- - @ext@: language extension, see "Lang.Crucible.CFG.Extension"+-- - @rtp@: type of the return value+type ExecResult :: Type -> Type -> Type -> Type -> Type+data ExecResult p sym ext rtp = -- | At least one execution path resulted in some return result.- FinishedResult !(SimContext p sym ext) !(PartialResult sym ext r)+ FinishedResult !(SimContext p sym ext) !(PartialResult sym ext rtp) -- | All execution paths resulted in an abort condition, and there is -- no result to return. | AbortedResult !(SimContext p sym ext) !(AbortedResult sym ext) -- | An execution stopped somewhere in the middle of a run because -- a timeout condition occurred.- | TimeoutResult !(ExecState p sym ext r)+ | TimeoutResult !(ExecState p sym ext rtp) execResultContext :: ExecResult p sym ext r -> SimContext p sym ext@@ -357,21 +420,49 @@ execResultContext (AbortedResult ctx _) = ctx execResultContext (TimeoutResult exst) = execStateContext exst +setExecResultContext ::+ SimContext p sym ext ->+ ExecResult p sym ext r ->+ ExecResult p sym ext r+setExecResultContext ctx =+ \case+ FinishedResult _ x -> FinishedResult ctx x+ AbortedResult _ x -> AbortedResult ctx x+ TimeoutResult execState -> TimeoutResult (setExecStateContext ctx execState)+ execStateContext :: ExecState p sym ext r -> SimContext p sym ext execStateContext = \case ResultState res -> execResultContext res- AbortState _ st -> st^.stateContext- UnwindCallState _ _ st -> st^.stateContext- CallState _ _ st -> st^.stateContext- TailCallState _ _ st -> st^.stateContext- ReturnState _ _ _ st -> st^.stateContext- ControlTransferState _ st -> st^.stateContext- RunningState _ st -> st^.stateContext- SymbolicBranchState _ _ _ _ st -> st^.stateContext- OverrideState _ st -> st^.stateContext- BranchMergeState _ st -> st^.stateContext+ AbortState _ st -> st ^. stateContext+ UnwindCallState _ _ st -> st ^. stateContext+ CallState _ _ st -> st ^. stateContext+ TailCallState _ _ st -> st ^. stateContext+ ReturnState _ _ _ st -> st ^. stateContext+ ControlTransferState _ st -> st ^. stateContext+ RunningState _ st -> st ^. stateContext+ SymbolicBranchState _ _ _ _ st -> st ^. stateContext+ OverrideState _ st -> st ^. stateContext+ BranchMergeState _ st -> st ^. stateContext InitialState stctx _ _ _ _ -> stctx +setExecStateContext ::+ SimContext p sym ext ->+ ExecState p sym ext r ->+ ExecState p sym ext r+setExecStateContext ctx = \case+ ResultState res -> ResultState (setExecResultContext ctx res)+ AbortState x st -> AbortState x (st & stateContext .~ ctx)+ UnwindCallState x y st -> UnwindCallState x y (st & stateContext .~ ctx)+ CallState x y st -> CallState x y (st & stateContext .~ ctx)+ TailCallState x y st -> TailCallState x y (st & stateContext .~ ctx)+ ReturnState x y z st -> ReturnState x y z (st & stateContext .~ ctx)+ ControlTransferState x st -> ControlTransferState x (st & stateContext .~ ctx)+ RunningState x st -> RunningState x (st & stateContext .~ ctx)+ SymbolicBranchState u v x y st -> SymbolicBranchState u v x y (st & stateContext .~ ctx)+ OverrideState x st -> OverrideState x (st & stateContext .~ ctx)+ BranchMergeState x st -> BranchMergeState x (st & stateContext .~ ctx)+ InitialState _ u v x y -> InitialState ctx u v x y+ execStateSimState :: ExecState p sym ext r -> Maybe (SomeSimState p sym ext r) execStateSimState = \case@@ -388,6 +479,72 @@ BranchMergeState _ st -> Just (SomeSimState st) InitialState _ _ _ _ _ -> Nothing +abortedGlobals ::+ Monad f =>+ -- | How to handle branches (e.g., 'AbortedBranch', 'PartialRes').+ --+ -- Common options include concretizing the 'Pred' or returning a partial+ -- result (e.g., 'Nothing').+ (ProgramLoc -> Pred sym -> SymGlobalState sym -> SymGlobalState sym -> f (SymGlobalState sym)) ->+ AbortedResult sym ext ->+ f (SymGlobalState sym)+abortedGlobals handleBranch =+ \case+ AbortedExec _ gp -> pure (gp ^. gpGlobals)+ AbortedExit _ gp -> pure (gp ^. gpGlobals)+ AbortedBranch loc p rl rr -> do+ l <- abortedGlobals handleBranch rl+ r <- abortedGlobals handleBranch rr+ handleBranch loc p l r++-- | Extract the 'SymGlobalState' from an 'ExecResult'.+execResultGlobals ::+ Monad f =>+ -- | How to handle branches (e.g., 'AbortedBranch', 'PartialRes').+ --+ -- Common options include concretizing the 'Pred' or returning a partial+ -- result (e.g., 'Nothing').+ (SimContext p sym ext -> ProgramLoc -> Pred sym -> SymGlobalState sym -> SymGlobalState sym -> f (SymGlobalState sym)) ->+ ExecResult p sym ext rtp ->+ f (SymGlobalState sym)+execResultGlobals handleBranch =+ \case+ FinishedResult simCtx partial ->+ case partial of+ TotalRes gp -> pure (gp ^. gpGlobals)+ PartialRes loc p gp aborted -> do+ let l = gp ^. gpGlobals+ r <- abortedGlobals (handleBranch simCtx) aborted+ handleBranch simCtx loc p l r+ TimeoutResult st -> execStateGlobals handleBranch st+ AbortedResult simCtx aborted ->+ abortedGlobals (handleBranch simCtx) aborted++-- | Extract the 'SymGlobalState' from an 'ExecState'.+execStateGlobals ::+ Monad f =>+ -- | How to handle branches (e.g., 'AbortedBranch', 'PartialRes').+ --+ -- Common options include concretizing the 'Pred' or returning a partial+ -- result (e.g., 'Nothing').+ (SimContext p sym ext -> ProgramLoc -> Pred sym -> SymGlobalState sym -> SymGlobalState sym -> f (SymGlobalState sym)) ->+ ExecState p sym ext rtp ->+ f (SymGlobalState sym)+execStateGlobals handleBranch =+ \case+ AbortState _ st -> pure (st ^. stateGlobals)+ BranchMergeState _ st -> pure (st ^. stateGlobals)+ CallState _ _ st -> pure (st ^. stateGlobals)+ ControlTransferState _ st -> pure (st ^. stateGlobals)+ InitialState _ globState _ _ _ -> pure globState+ OverrideState _ st -> pure (st ^. stateGlobals)+ ResultState r -> execResultGlobals handleBranch r+ ReturnState _ _ _ st -> pure (st ^. stateGlobals)+ RunningState _ st -> pure (st ^. stateGlobals)+ SymbolicBranchState _ _ _ _ st -> pure (st ^. stateGlobals)+ TailCallState _ _ st -> pure (st ^. stateGlobals)+ UnwindCallState _ _ st -> pure (st ^. stateGlobals)+ ----------------------------------------------------------------------- -- ExecState @@ -395,7 +552,15 @@ -- Crucible program. The Crucible simulator executes by transitioning -- between these different states until it results in a 'ResultState', -- indicating the program has completed.-data ExecState p sym ext (rtp :: Type)+--+-- Type parameters:+--+-- - @p@: see 'cruciblePersonality'+-- - @sym@: instance of 'Lang.Crucible.Backend.IsSymInterface'+-- - @ext@: language extension, see "Lang.Crucible.CFG.Extension"+-- - @rtp@: type of the return value+type ExecState :: Type -> Type -> Type -> Type -> Type+data ExecState p sym ext rtp {- | The 'ResultState' is used to indicate that the program has completed. -} = ResultState !(ExecResult p sym ext rtp)@@ -532,11 +697,27 @@ -- | An action which will construct an 'ExecState' given a current -- 'SimState'. Such continuations correspond to a single transition -- of the simulator transition system.-type ExecCont p sym ext r f a =- ReaderT (SimState p sym ext r f a) IO (ExecState p sym ext r)+--+-- Type parameters:+--+-- - @p@: see 'cruciblePersonality'+-- - @sym@: instance of 'Lang.Crucible.Backend.IsSymInterface'+-- - @ext@: language extension, see "Lang.Crucible.CFG.Extension"+-- - @rtp@: type of the return value+-- - @f@: the type of the top frame ('CrucibleLang' or 'OverrideLang')+-- - @args@: arguments to the current frame (see 'SimFrame')+type ExecCont :: Type -> Type -> Type -> Type -> Type -> Maybe (Ctx.Ctx CrucibleType) -> Type+type ExecCont p sym ext rtp f args =+ ReaderT (SimState p sym ext rtp f args) IO (ExecState p sym ext rtp) -- | Some additional information attached to a @RunningState@ -- that indicates how we got to this running state.+--+-- Type parameters:+--+-- - @blocks@: types of variables in scope from previous blocks+-- - @args@: arguments to this block+type RunningStateInfo :: Ctx (Ctx CrucibleType) -> Ctx CrucibleType -> Type data RunningStateInfo blocks args -- | This indicates that we are now in a @RunningState@ because -- we transferred execution to the start of a basic block.@@ -554,6 +735,12 @@ -- | A 'ResolvedJump' is a block label together with a collection of -- actual arguments that are expected by that block. These data -- are sufficient to actually transfer control to the named label.+--+-- Type parameters:+--+-- - @sym@: instance of 'Lang.Crucible.Backend.IsSymInterface'+-- - @blocks@: types of variables in scope from previous blocks+type ResolvedJump :: Type -> Ctx (Ctx CrucibleType) -> Type data ResolvedJump sym blocks = forall args. ResolvedJump@@ -564,6 +751,15 @@ -- (while it first explores other paths), a 'ControlResumption' -- indicates what actions must later be taken in order to resume -- execution of that path.+--+-- Type parameters:+--+-- - @p@: see 'cruciblePersonality'+-- - @sym@: instance of 'Lang.Crucible.Backend.IsSymInterface'+-- - @ext@: language extension, see "Lang.Crucible.CFG.Extension"+-- - @rtp@: type of the return value+-- - @f@: the type of the top frame ('CrucibleLang' or 'OverrideLang')+type ControlResumption :: Type -> Type -> Type -> Type -> Type -> Type data ControlResumption p sym ext rtp f where {- | When resuming a paused frame with a @ContinueResumption@, no special work needs to be done, simply begin executing@@ -603,6 +799,15 @@ -- while other paths are explored. It consists of a (potentially partial) -- 'SimFrame' together with some information about how to resume execution -- of that frame.+--+-- Type parameters:+--+-- - @p@: see 'cruciblePersonality'+-- - @sym@: instance of 'Lang.Crucible.Backend.IsSymInterface'+-- - @ext@: language extension, see "Lang.Crucible.CFG.Extension"+-- - @rtp@: type of the return value+-- - @f@: the type of the top frame ('CrucibleLang' or 'OverrideLang')+type PausedFrame :: Type -> Type -> Type -> Type -> Type -> Type data PausedFrame p sym ext rtp f = forall old_args. PausedFrame@@ -619,6 +824,16 @@ -- stored in the 'VFFCompletePath' state until the second path also -- reaches its merge point. The two paths will then be merged, -- and execution will continue beyond the merge point.+--+-- Type parameters:+--+-- - @p@: see 'cruciblePersonality'+-- - @sym@: instance of 'Lang.Crucible.Backend.IsSymInterface'+-- - @ext@: language extension, see "Lang.Crucible.CFG.Extension"+-- - @rtp@: type of the return value+-- - @f@: the type of the top frame ('CrucibleLang' or 'OverrideLang')+-- - @args@: arguments to this frame (see 'SimFrame')+type VFFOtherPath :: Type -> Type -> Type -> Type -> Type -> Maybe (Ctx CrucibleType) -> Type data VFFOtherPath p sym ext ret f args {- | This corresponds the a path that still needs to be analyzed. -}@@ -639,20 +854,17 @@ of the branching structure of a program. The 'ValueFromFrame' states correspond to the structure of symbolic branching that occurs within a single function call. -The type parameters have the following meanings:-- * @p@ is the personality of the simulator (i.e., custom user state).-- * @sym@ is the simulator backend being used.-- * @ext@ specifies what extensions to the Crucible language are enabled-- * @ret@ is the global return type of the entire execution.+Type parameters: - * @f@ is the type of the top frame.+- @p@: see 'cruciblePersonality'+- @sym@: instance of 'Lang.Crucible.Backend.IsSymInterface'+- @ext@: language extension, see "Lang.Crucible.CFG.Extension"+- @ret@: global return type of the entire execution+- @f@: the type of the top frame ('CrucibleLang' or 'OverrideLang') -} -data ValueFromFrame p sym ext (ret :: Type) (f :: Type)+type ValueFromFrame :: Type -> Type -> Type -> Type -> Type -> Type+data ValueFromFrame p sym ext ret f {- | We are working on a branch; this could be the first or the second of both branches (see the 'VFFOtherPath' field). -}@@ -715,6 +927,7 @@ -- occur or not. If the context sill expects a merge, we need to -- take some actions to indicate that the merge will not occur; -- otherwise there is no special work to be done.+type PendingPartialMerges :: Type data PendingPartialMerges = {- | Don't indicate an abort condition in the context -} NoNeedToAbort@@ -728,19 +941,14 @@ of the branching structure of a program. The 'ValueFromValue' states correspond to stack call frames in a more traditional simulator environment. -The type parameters have the following meanings:-- * @p@ is the personality of the simulator (i.e., custom user state).-- * @sym@ is the simulator backend being used.-- * @ext@ specifies what extensions to the Crucible language are enabled-- * @ret@ is the global return type of the entire computation-- * @top_return@ is the return type of the top-most call on the stack.+- @p@: see 'cruciblePersonality'+- @sym@: instance of 'Lang.Crucible.Backend.IsSymInterface'+- @ext@: language extension, see "Lang.Crucible.CFG.Extension"+- @ret@: global return type of the entire execution+- @top_return@: return type of the top-most call on the stack. -}-data ValueFromValue p sym ext (ret :: Type) (top_return :: CrucibleType)+type ValueFromValue :: Type -> Type -> Type -> Type -> CrucibleType -> Type+data ValueFromValue p sym ext ret top_return {- | 'VFVCall' denotes a call site in the outer context, and represents the point to which a function higher on the stack will@@ -851,23 +1059,18 @@ executing in a caller's context once a function call has completed and the return value is available. -The type parameters have the following meanings:-- * @ret@ is the type of the return value that is expected.-- * @p@ is the personality of the simulator (i.e., custom user state).-- * @sym@ is the simulator backend being used.-- * @ext@ specifies what extensions to the Crucible language are enabled.-- * @root@ is the global return type of the entire computation.-- * @f@ is the stack type of the caller.+Type parameters: - * @args@ is the type of the local variables in scope prior to the call.+- @ret@: the type of the return value that is expected+- @p@: see 'cruciblePersonality'+- @sym@: instance of 'Lang.Crucible.Backend.IsSymInterface'+- @ext@: language extension, see "Lang.Crucible.CFG.Extension"+- @root@: global return type of the entire computation+- @f@: the frame type of the caller ('CrucibleLang' or 'OverrideLang')+- @args@: types of the local variables in scope prior to the call (see 'SimFrame') -}-data ReturnHandler (ret :: CrucibleType) p sym ext root f args where+type ReturnHandler :: CrucibleType -> Type -> Type -> Type -> Type -> Type -> Maybe (Ctx CrucibleType) -> Type+data ReturnHandler ret p sym ext root f args where {- | The 'ReturnToOverride' constructor indicates that the calling context is primitive code written directly in Haskell. -}@@ -899,13 +1102,25 @@ ------------------------------------------------------------------------ -- ActiveTree +type PartialResultFrame :: Type -> Type -> Type -> Maybe (Ctx CrucibleType) -> Type type PartialResultFrame sym ext f args = PartialResult sym ext (SimFrame sym ext f args) {- | An active execution tree contains at least one active execution. The data structure is organized so that the current execution- can be accessed rapidly. -}-data ActiveTree p sym ext root (f :: Type) args+ can be accessed rapidly.++ Type parameters:++ - @p@: see 'cruciblePersonality'+ - @sym@: instance of 'Lang.Crucible.Backend.IsSymInterface'+ - @ext@: language extension, see "Lang.Crucible.CFG.Extension"+ - @root@: global return type of the entire computation+ - @f@: the frame type of the caller ('CrucibleLang' or 'OverrideLang')+-- - @args@: arguments to the current frame (see 'SimFrame')+-}+type ActiveTree :: Type -> Type -> Type -> Type -> Type -> Maybe (Ctx.Ctx CrucibleType) -> Type+data ActiveTree p sym ext root f args = ActiveTree { _actContext :: !(ValueFromFrame p sym ext root f) , _actResult :: !(PartialResultFrame sym ext f args)@@ -953,13 +1168,22 @@ activeFrames :: ActiveTree ctx sym ext root a args -> [SomeFrame (SimFrame sym ext)] activeFrames (ActiveTree ctx ar) =- SomeFrame (ar^.partialValue^.gpValue) : parentFrames ctx+ SomeFrame (ar ^. partialValue ^. gpValue) : parentFrames ctx ------------------------------------------------------------------------ -- SimContext -- | A definition of a function's semantics, given as a Haskell action.+--+-- Type parameters:+--+-- - @p@: see 'cruciblePersonality'+-- - @sym@: instance of 'Lang.Crucible.Backend.IsSymInterface'+-- - @ext@: language extension, see "Lang.Crucible.CFG.Extension"+-- - @args@: types of arguments to the override+-- - @ret@: return type of the override+type Override :: Type -> Type -> Type -> Ctx CrucibleType -> CrucibleType -> Type data Override p sym ext (args :: Ctx CrucibleType) ret = Override { overrideName :: FunctionName , overrideHandler :: forall r. ExecCont p sym ext r (OverrideLang ret) ('Just args)@@ -968,16 +1192,39 @@ -- | State used to indicate what to do when function is called. A function -- may either be defined by writing a Haskell 'Override' or by giving -- a Crucible control-flow graph representation.-data FnState p sym ext (args :: Ctx CrucibleType) (ret :: CrucibleType)+--+-- Type parameters:+--+-- - @p@: see 'cruciblePersonality'+-- - @sym@: instance of 'Lang.Crucible.Backend.IsSymInterface'+-- - @ext@: language extension, see "Lang.Crucible.CFG.Extension"+-- - @args@: argument types+-- - @ret@: return type+type FnState :: Type -> Type -> Type -> Ctx CrucibleType -> CrucibleType -> Type+data FnState p sym ext args ret = UseOverride !(Override p sym ext args ret) | forall blocks . UseCFG !(CFG ext blocks args ret) !(CFGPostdom blocks) -- | A map from function handles to their semantics.+--+-- Type parameters:+--+-- - @p@: see 'cruciblePersonality'+-- - @sym@: instance of 'Lang.Crucible.Backend.IsSymInterface'+-- - @ext@: language extension, see "Lang.Crucible.CFG.Extension"+type FunctionBindings :: Type -> Type -> Type -> Type newtype FunctionBindings p sym ext = FnBindings { fnBindings :: FnHandleMap (FnState p sym ext) } -- | The type of functions that interpret extension statements. These -- have access to the main simulator state, and can make fairly arbitrary -- changes to it.+--+-- Type parameters:+--+-- - @p@: see 'cruciblePersonality'+-- - @sym@: instance of 'Lang.Crucible.Backend.IsSymInterface'+-- - @ext@: language extension, see "Lang.Crucible.CFG.Extension"+type EvalStmtFunc :: Type -> Type -> Type -> Type type EvalStmtFunc p sym ext = forall rtp blocks r ctx tp'. StmtExtension ext (RegEntry sym) tp' ->@@ -987,6 +1234,13 @@ -- | In order to start executing a simulator, one must provide an implementation -- of the extension syntax. This includes an evaluator for the added -- expression forms, and an evaluator for the added statement forms.+--+-- Type parameters:+--+-- - @p@: see 'cruciblePersonality'+-- - @sym@: instance of 'Lang.Crucible.Backend.IsSymInterface'+-- - @ext@: language extension, see "Lang.Crucible.CFG.Extension"+type ExtensionImpl :: Type -> Type -> Type -> Type data ExtensionImpl p sym ext = ExtensionImpl { extensionEval ::@@ -1010,30 +1264,66 @@ , extensionExec = \case } +type IsSymInterfaceProof :: Type -> Type -> Type type IsSymInterfaceProof sym a = (IsSymInterface sym => a) -> a +-- | Some kind of 'Integer' to be collected during execution.+--+-- Type parameters:+--+-- - @p@: see 'cruciblePersonality'+-- - @sym@: instance of 'Lang.Crucible.Backend.IsSymInterface'+-- - @ext@: language extension, see "Lang.Crucible.CFG.Extension"+type Metric :: Type -> Type -> Type -> Type newtype Metric p sym ext = Metric { runMetric :: forall rtp f args. SimState p sym ext rtp f args -> IO Integer } +data ExceptionContextConfig = + ECCNone -- ^ do not include exceptions with context + | ECCLimited Int+ | ECCNoLimit+ deriving(Eq, Ord, Show, Read)++parseExceptionContextConfig :: String -> Either String ExceptionContextConfig+parseExceptionContextConfig s =+ case s of+ "none" -> Right ECCNone+ "nolimit" -> Right ECCNoLimit+ _ | Just limit <- readMaybe s ->+ if limit > 0 + then Right (ECCLimited limit)+ else Left "exception context frame limit cannot be 0 or less"+ _ -> Left "invalid exception context config - valid inputs are `none`, `nolimit`, and integers greater than 0"++ -- | Top-level state record for the simulator. The state contained in this record -- remains persistent across all symbolic simulator actions. In particular, it -- is not rolled back when the simulator returns previous program points to -- explore additional paths, etc.-data SimContext (personality :: Type) (sym :: Type) (ext :: Type)- = SimContext { _ctxBackend :: !(SomeBackend sym)+--+-- Type parameters:+--+-- - @p@: see 'cruciblePersonality'+-- - @sym@: instance of 'Lang.Crucible.Backend.IsSymInterface'+-- - @ext@: language extension, see "Lang.Crucible.CFG.Extension"+type SimContext :: Type -> Type -> Type -> Type+data SimContext p sym ext+ = SimContext { _ctxBackend :: !(SomeBackend sym) -- | Class dictionary for @'IsSymInterface' sym@- , ctxSolverProof :: !(forall a . IsSymInterfaceProof sym a)- , ctxIntrinsicTypes :: !(IntrinsicTypes sym)+ , ctxSolverProof :: !(forall a . IsSymInterfaceProof sym a)+ , ctxIntrinsicTypes :: !(IntrinsicTypes sym) -- | Allocator for function handles- , simHandleAllocator :: !(HandleAllocator)+ , simHandleAllocator :: !(HandleAllocator) -- | Handle to write messages to.- , printHandle :: !Handle- , extensionImpl :: ExtensionImpl personality sym ext- , _functionBindings :: !(FunctionBindings personality sym ext)- , _cruciblePersonality :: !personality- , _profilingMetrics :: !(Map Text (Metric personality sym ext))+ , printHandle :: !Handle+ , extensionImpl :: ExtensionImpl p sym ext+ , _functionBindings :: !(FunctionBindings p sym ext)+ -- | See 'cruciblePersonality'.+ , _cruciblePersonality :: !p+ , _profilingMetrics :: !(Map Text (Metric p sym ext))+ , _exceptionContextConfig :: ExceptionContextConfig } -- | Create a new 'SimContext' with the given bindings.@@ -1048,15 +1338,16 @@ personality {- ^ Initial value for custom user state -} -> SimContext personality sym ext initSimContext bak muxFns halloc h bindings extImpl personality =- SimContext { _ctxBackend = SomeBackend bak- , ctxSolverProof = \a -> a- , ctxIntrinsicTypes = muxFns- , simHandleAllocator = halloc- , printHandle = h- , extensionImpl = extImpl- , _functionBindings = bindings- , _cruciblePersonality = personality- , _profilingMetrics = Map.empty+ SimContext { _ctxBackend = SomeBackend bak+ , ctxSolverProof = \a -> a+ , ctxIntrinsicTypes = muxFns+ , simHandleAllocator = halloc+ , printHandle = h+ , extensionImpl = extImpl+ , _functionBindings = bindings+ , _cruciblePersonality = personality+ , _profilingMetrics = Map.empty+ , _exceptionContextConfig = ECCNone } withBackend ::@@ -1065,8 +1356,27 @@ a withBackend ctx f = case _ctxBackend ctx of SomeBackend bak -> f bak +-- | Get a backend from a SimState and populate the error context+-- from the current simulation state. This differs from `withBackend`+-- because it can use the dynamic state of the simulator for things+-- like getting stack traces for exceptions and should be preferred+-- to `withBackend` where it is possible to use.+withStateBackend ::+ SimState p sym ext rtp f args ->+ (forall bak. IsSymBackend sym bak => bak -> a) -> a+withStateBackend st f = + if shouldHaveContext then + let ec = stateProgramStack st+ in withBackend (st ^. stateContext) $ \bak ->+ f (withExceptionContext bak ec)+ else + withBackend (st ^. stateContext) f+ where+ shouldHaveContext = + st ^. stateContext . exceptionContextConfig /= ECCNone+ -- | Access the symbolic backend inside a 'SimContext'.-ctxSymInterface :: Getter (SimContext p sym ext) sym+ctxSymInterface :: SimpleGetter (SimContext p sym ext) sym ctxSymInterface = to (\ctx -> case _ctxBackend ctx of SomeBackend bak -> backendGetSym bak)@@ -1075,13 +1385,39 @@ functionBindings :: Lens' (SimContext p sym ext) (FunctionBindings p sym ext) functionBindings = lens _functionBindings (\s v -> s { _functionBindings = v }) --- | Access the custom user-state inside the 'SimContext'.+-- | Custom state inside the 'SimContext'.+--+-- Crucible itself is entirely polymorphic over @p@. Downstream applications can+-- instantiate it to any sort of state that they would like to associate with a+-- 'SimContext'.+--+-- For example, applications based on+-- [@macaw-symbolic@](https://github.com/GaloisInc/macaw/tree/master/symbolic)+-- can instantiate this to a structure holding enough information to perform+-- incremental code discovery. See @ambient-verifier@'s+-- [@AmbientSimulatorState@](https://github.com/GaloisInc/ambient-verifier/blob/eab04abb9750825a25ec0cbe0379add63f05f6c6/src/Ambient/Extensions.hs#L1092-1137).+--+-- Code that needs to store some state in the personality but doesn\'t wish to+-- fix a particular type can use the \"classy lenses\" approach, e.g.,+--+-- @+-- class HasFooState p where+-- fooState :: `Lens'` p FooState+-- @+--+-- For examples of this approach, see+--+-- * [@HasMacawLazySimulatorState@](https://github.com/GaloisInc/macaw/blob/cbec559b428fdd194398d07fc08c8c570a1d3bab/symbolic/src/Data/Macaw/Symbolic/MemOps.hs#L385-L394)+-- * [@HasGreaseSimulatorState@](https://github.com/GaloisInc/grease/blob/a50d54d2f414d15974dcf2d21654fbfe3527f0fa/src/Grease/Macaw/SimulatorState.hs#L90-L97) cruciblePersonality :: Lens' (SimContext p sym ext) p cruciblePersonality = lens _cruciblePersonality (\s v -> s{ _cruciblePersonality = v }) profilingMetrics :: Lens' (SimContext p sym ext) (Map Text (Metric p sym ext)) profilingMetrics = lens _profilingMetrics (\s v -> s { _profilingMetrics = v }) +exceptionContextConfig :: Lens' (SimContext p sym ext) ExceptionContextConfig+exceptionContextConfig = lens _exceptionContextConfig (\s v -> s { _exceptionContextConfig = v })+ ------------------------------------------------------------------------ -- SimState @@ -1094,6 +1430,13 @@ -- may be desirable to take additional or alternate actions on abort -- events; in which case, the library user may replace the default -- abort handler with their own.+--+-- Type parameters:+--+-- - @p@: see 'cruciblePersonality'+-- - @sym@: instance of 'Lang.Crucible.Backend.IsSymInterface'+-- - @ext@: language extension, see "Lang.Crucible.CFG.Extension"+-- - @rtp@: type of the return value newtype AbortHandler p sym ext rtp = AH { runAH :: forall (l :: Type) args. AbortExecReason ->@@ -1103,12 +1446,23 @@ -- | A SimState contains the execution context, an error handler, and -- the current execution tree. It captures the entire state -- of the symbolic simulator.-data SimState p sym ext rtp f (args :: Maybe (Ctx.Ctx CrucibleType))+--+-- Type parameters:+--+-- - @p@: see 'cruciblePersonality'+-- - @sym@: instance of 'Lang.Crucible.Backend.IsSymInterface'+-- - @ext@: language extension, see "Lang.Crucible.CFG.Extension"+-- - @rtp@: type of the return value+-- - @f@: the type of the top frame ('CrucibleLang' or 'OverrideLang')+-- - @args@: arguments to the current frame (see 'SimFrame')+type SimState :: Type -> Type -> Type -> Type -> Type -> Maybe (Ctx.Ctx CrucibleType) -> Type+data SimState p sym ext rtp f args = SimState { _stateContext :: !(SimContext p sym ext) , _abortHandler :: !(AbortHandler p sym ext rtp) , _stateTree :: !(ActiveTree p sym ext rtp f args) } +type SomeSimState :: Type -> Type -> Type -> Type -> Type data SomeSimState p sym ext rtp = forall f args. SomeSimState !(SimState p sym ext rtp f args) @@ -1139,23 +1493,23 @@ } -stateLocation :: Getter (SimState p sym ext r f a) (Maybe ProgramLoc)+stateLocation :: SimpleGetter (SimState p sym ext r f a) (Maybe ProgramLoc) stateLocation = to f where f :: SimState p sym ext r f a -> Maybe ProgramLoc- f st = case st^.stateTree . actFrame . gpValue of+ f st = case st ^. stateTree . actFrame . gpValue of MF cf -> Just $! (frameProgramLoc cf) OF _ -> Nothing RF _ _ -> Nothing -- | Access the 'SimContext' inside a 'SimState'-stateContext :: Simple Lens (SimState p sym ext r f a) (SimContext p sym ext)+stateContext :: Lens' (SimState p sym ext r f a) (SimContext p sym ext) stateContext = lens _stateContext (\s v -> s { _stateContext = v }) {-# INLINE stateContext #-} -- | Access the current abort handler of a state.-abortHandler :: Simple Lens (SimState p sym ext r f a) (AbortHandler p sym ext r)+abortHandler :: Lens' (SimState p sym ext r f a) (AbortHandler p sym ext r) abortHandler = lens _abortHandler (\s v -> s { _abortHandler = v }) -- | Access the active tree associated with a state.@@ -1186,21 +1540,49 @@ stateOverrideFrame = stateTree . actFrame . gpValue . overrideSimFrame -- | Access the globals inside a 'SimState'-stateGlobals :: Simple Lens (SimState p sym ext q f args) (SymGlobalState sym)+stateGlobals :: Lens' (SimState p sym ext q f args) (SymGlobalState sym) stateGlobals = stateTree . actFrame . gpGlobals -- | Get the symbolic interface out of a 'SimState'-stateSymInterface :: Getter (SimState p sym ext r f a) sym+stateSymInterface :: SimpleGetter (SimState p sym ext r f a) sym stateSymInterface = stateContext . ctxSymInterface -- | Get the intrinsic type map out of a 'SimState'-stateIntrinsicTypes :: Getter (SimState p sym ext r f args) (IntrinsicTypes sym)+stateIntrinsicTypes :: SimpleGetter (SimState p sym ext r f args) (IntrinsicTypes sym) stateIntrinsicTypes = stateContext . to ctxIntrinsicTypes -- | Get the configuration object out of a 'SimState'-stateConfiguration :: Getter (SimState p sym ext r f args) Config-stateConfiguration = to (\s -> stateSolverProof s (getConfiguration (s^.stateSymInterface)))+stateConfiguration :: SimpleGetter (SimState p sym ext r f args) Config+stateConfiguration = to (\s -> stateSolverProof s (getConfiguration (s ^. stateSymInterface))) -- | Provide the 'IsSymInterface' typeclass dictionary from a 'SimState' stateSolverProof :: SimState p sym ext r f args -> (forall a . IsSymInterfaceProof sym a)-stateSolverProof s = ctxSolverProof (s^.stateContext)+stateSolverProof s = ctxSolverProof (s ^. stateContext)++-- | Get the program stack from a SimState+stateProgramStack :: SimState p sym ext r f args -> ProgramStack+stateProgramStack st = + ProgramStack+ { psFrameOmitCount = length omitted+ , psFrames = relevantFrames+ }+ where+ + (relevantFrames, omitted) = + case eccConfig of+ ECCNoLimit -> (rawFrames, [])+ ECCLimited limit -> splitAt limit rawFrames+ ECCNone -> ([],[])++ eccConfig = st ^. stateContext . exceptionContextConfig++ isStartFrame (SomeFrame (OF frm)) = (frm ^. override) == startFunctionName + isStartFrame _ = False+ removeStartFrame [] = []+ removeStartFrame [f] | isStartFrame f = []+ removeStartFrame (h:t) = h:removeStartFrame t++ rawFrames = + [ loc | SomeFrame sf <- removeStartFrame $ activeFrames (st ^. stateTree) + , loc <- maybeToList (frameStackLoc sf)+ ]
src/Lang/Crucible/Simulator/Operations.hs view
@@ -76,17 +76,19 @@ import Prelude hiding (pred) import qualified Control.Exception as Ex-import Control.Lens import Control.Monad (when, void) import Control.Monad.IO.Class (MonadIO(..))-import Control.Monad.Reader (ReaderT(..), withReaderT)+import Control.Monad.Reader (ReaderT(..), ask, withReaderT) import Control.Monad.Trans.Class (MonadTrans(..))-import Data.Maybe (fromMaybe)+import Data.Function ((&)) import Data.List (isPrefixOf)+import Data.Maybe (fromMaybe) import qualified Data.Parameterized.Context as Ctx import Data.Parameterized.Some-import qualified Data.Vector as V import Data.Type.Equality hiding (sym)+import qualified Data.Vector as V+import Lens.Micro ((^.), (.~), (%~), _2, to, traverseOf)+import Lens.Micro.Mtl (view) import System.IO import qualified Prettyprinter as PP @@ -116,8 +118,8 @@ MuxFn p (SymGlobalState sym) -> MuxFn p (GlobalPair sym v) mergeGlobalPair merge_fn global_fn c x y =- GlobalPair <$> merge_fn c (x^.gpValue) (y^.gpValue)- <*> global_fn c (x^.gpGlobals) (y^.gpGlobals)+ GlobalPair <$> merge_fn c (x ^. gpValue) (y ^. gpValue)+ <*> global_fn c (x ^. gpGlobals) (y ^. gpGlobals) mergeAbortedResult :: ProgramLoc {- ^ Program location of control-flow branching -} ->@@ -125,8 +127,8 @@ AbortedResult sym ext -> AbortedResult sym ext -> AbortedResult sym ext-mergeAbortedResult _ _ (AbortedExit ec) _ = AbortedExit ec-mergeAbortedResult _ _ _ (AbortedExit ec) = AbortedExit ec+mergeAbortedResult _ _ ae@(AbortedExit {}) _ = ae+mergeAbortedResult _ _ _ ae@(AbortedExit {}) = ae mergeAbortedResult loc pred q r = AbortedBranch loc pred q r mergePartialAndAbortedResult ::@@ -156,12 +158,12 @@ BlockTarget _b_id -> do let x = fromCallFrame x0 let y = fromCallFrame y0- z <- mergeRegs sym muxFns p (x^.frameRegs) (y^.frameRegs)+ z <- mergeRegs sym muxFns p (x ^. frameRegs) (y ^. frameRegs) pure $! MF (x & frameRegs .~ z) ReturnTarget -> do let x = fromReturnFrame x0 let y = fromReturnFrame y0- RF (x0^.frameFunctionName) <$> muxRegEntry sym muxFns p x y+ RF (x0 ^. frameFunctionName) <$> muxRegEntry sym muxFns p x y mergePartialResult ::@@ -170,8 +172,8 @@ CrucibleBranchTarget f args -> MuxFn (Pred sym) (PartialResultFrame sym ext f args) mergePartialResult s tgt pred x y =- let sym = s^.stateSymInterface- iteFns = s^.stateIntrinsicTypes+ let sym = s ^. stateSymInterface+ iteFns = s ^. stateIntrinsicTypes merge_val = mergeCrucibleFrame sym iteFns tgt merge_fn = mergeGlobalPair merge_val (globalMuxFn sym iteFns) in@@ -254,7 +256,7 @@ SimFrame sym ext f a' -> IO (SimFrame sym ext f a') abortCrucibleFrame sym intrinsicFns (BlockTarget _) (MF x') =- do r' <- abortBranchRegs sym intrinsicFns (x'^.frameRegs)+ do r' <- abortBranchRegs sym intrinsicFns (x' ^. frameRegs) return $! MF (x' & frameRegs .~ r') abortCrucibleFrame sym intrinsicFns ReturnTarget (RF nm x') =@@ -268,8 +270,8 @@ PartialResultFrame sym ext f a' -> IO (PartialResultFrame sym ext f a') abortPartialResult s tgt pr =- let sym = s^.stateSymInterface- muxFns = s^.stateIntrinsicTypes+ let sym = s ^. stateSymInterface+ muxFns = s ^. stateIntrinsicTypes abtGp (GlobalPair v g) = GlobalPair <$> abortCrucibleFrame sym muxFns tgt v <*> globalAbortBranch sym muxFns g in partialValue abtGp pr@@ -367,7 +369,7 @@ resolvedCallName :: ResolvedCall p sym ext ret -> FunctionName-resolvedCallName (OverrideCall _ f) = f^.override+resolvedCallName (OverrideCall _ f) = f ^. override resolvedCallName (CrucibleCall _ f) = case frameHandle f of SomeHandle h -> handleName h ---------------------------------------------------------------------@@ -404,11 +406,12 @@ SimState p sym ext rtp f args {- ^ Simulator state prior to the abort -} -> IO (ExecState p sym ext rtp) runErrorHandler msg st =- let ctx = st^.stateContext- sym = ctx^.ctxSymInterface- in withBackend ctx $ \bak ->+ let ctx = st ^. stateContext+ sym = ctx ^. ctxSymInterface+ in withStateBackend st $ \bak -> do loc <- getCurrentProgramLoc sym- let err = SimError loc msg+ let stk = stateProgramStack st+ let err = mkSimError loc msg (Just stk) addProofObligation bak (LabeledPred (falsePred sym) err) return (AbortState (AssertionFailure err) st) @@ -464,7 +467,7 @@ ExecCont p sym ext rtp (CrucibleLang blocks ret) ('Just ctx) conditionalBranch p xjmp yjmp = do top_frame <- view (stateTree.actFrame)- Some pd <- return (top_frame^.crucibleTopFrame.framePostdom)+ Some pd <- return (top_frame ^. crucibleTopFrame.framePostdom) x_frame <- cruciblePausedFrame xjmp top_frame pd y_frame <- cruciblePausedFrame yjmp top_frame pd@@ -495,7 +498,7 @@ variantCases ((p,jmp) : cs) = do top_frame <- view (stateTree.actFrame)- Some pd <- return (top_frame^.crucibleTopFrame.framePostdom)+ Some pd <- return (top_frame ^. crucibleTopFrame.framePostdom) x_frame <- cruciblePausedFrame jmp top_frame pd let y_frame = PausedFrame (TotalRes top_frame) (SwitchResumption cs) Nothing@@ -581,9 +584,9 @@ performIntraFrameMerge tgt = do ActiveTree ctx0 er <- view stateTree- simCtx <- view stateContext+ st <- ask sym <- view stateSymInterface- withBackend simCtx $ \bak ->+ withStateBackend st $ \bak -> case ctx0 of VFFBranch ctx assume_frame loc pred other_branch tgt' @@ -639,9 +642,9 @@ continue (RunPostBranchMerge bid) ReturnTarget -> handleSimReturn- (er^.partialValue.gpValue.frameFunctionName)+ (er ^. partialValue.gpValue.frameFunctionName) (returnContext ctx0)- (er^.partialValue.gpValue.to fromReturnFrame)+ (er ^. partialValue.gpValue.to fromReturnFrame) --------------------------------------------------------------------- -- Abort handling@@ -701,9 +704,9 @@ AbortedResult sym ext {- ^ The execution that is being aborted. -} -> ExecCont p sym ext r g args resumeValueFromFrameAbort ctx0 ar0 = do- simCtx <- view stateContext+ st <- ask sym <- view stateSymInterface- withBackend simCtx $ \bak ->+ withStateBackend st $ \bak -> case ctx0 of -- This is the first abort.@@ -862,7 +865,7 @@ ExecCont p sym ext rtp (OverrideLang r) ('Just args) overrideSymbolicBranch p thn_args thn thn_pos els_args els els_pos = do top_frm <- view (stateTree.actFrame)- let fnm = top_frm^.gpValue.overrideSimFrame.override+ let fnm = top_frm ^. gpValue.overrideSimFrame.override let thn_loc = mkProgramLoc fnm <$> thn_pos let els_loc = mkProgramLoc fnm <$> els_pos let thn_frm = PausedFrame (TotalRes top_frm) (OverrideResumption thn thn_args) thn_loc@@ -910,9 +913,9 @@ intra_branch p t_label f_label tgt = do ctx <- asContFrame <$> view stateTree- simCtx <- view stateContext+ st <- ask sym <- view stateSymInterface- withBackend simCtx $ \bak ->+ withStateBackend st $ \bak -> case asConstantPred p of Nothing -> ReaderT $ return . SymbolicBranchState p t_label f_label tgt@@ -943,13 +946,13 @@ ExecCont p sym ext rtp f ('Just dc_args) performIntraFrameSplit p a_frame o_frame tgt = do ctx <- asContFrame <$> view stateTree- simCtx <- view stateContext+ st <- ask sym <- view stateSymInterface loc <- liftIO $ getCurrentProgramLoc sym a_frame' <- pushPausedFrame a_frame o_frame' <- pushPausedFrame o_frame - assume_frame <- withBackend simCtx $ \bak ->+ assume_frame <- withStateBackend st $ \bak -> liftIO $ assumeInNewFrame bak (BranchCondition loc (pausedLoc a_frame') p) -- Create context for paused frame.@@ -1090,8 +1093,8 @@ ActiveTree p sym ext ret f args -> ActiveTree p sym ext ret f args extractCurrentPath t =- ActiveTree (vffSingleContext (t^.actContext))- (TotalRes (t^.actFrame))+ ActiveTree (vffSingleContext (t ^. actContext))+ (TotalRes (t ^. actFrame)) vffSingleContext :: ValueFromFrame p sym ext ret f ->@@ -1118,9 +1121,9 @@ -- -- | Return all branch conditions along path to this node. -- branchConditions :: ActiveTree ctx sym ext ret f args -> [Pred sym] -- branchConditions t =--- case t^.actResult of--- TotalRes _ -> vffBranchConditions (t^.actContext)--- PartialRes p _ _ -> p : vffBranchConditions (t^.actContext)+-- case t ^. actResult of+-- TotalRes _ -> vffBranchConditions (t ^. actContext)+-- PartialRes p _ _ -> p : vffBranchConditions (t ^. actContext) -- vffBranchConditions :: ValueFromFrame p sym ext ret f -- -> [Pred sym]
src/Lang/Crucible/Simulator/OverrideSim.hs view
@@ -75,32 +75,35 @@ , useIntrinsic -- * Typed overrides , TypedOverride(..)+ , typedOverride , SomeTypedOverride(..) , runTypedOverride+ , bindTypedOverride -- * Re-exports , Lang.Crucible.Simulator.ExecutionTree.Override ) where import Control.Exception-import Control.Lens import Control.Monad hiding (fail) import qualified Control.Monad.Catch as X import Control.Monad.IO.Class (MonadIO(..)) import Control.Monad.Reader (ReaderT(..)) import Control.Monad.ST import Control.Monad.State.Strict (StateT(..))-import Data.List (foldl')+import qualified Data.Foldable as Foldable+import Data.Function ((&)) import qualified Data.Parameterized.Context as Ctx+import Data.Parameterized.TraversableFC (fmapFC) import Data.Proxy import qualified Data.Text as T import Data.Traversable (for)+import Lens.Micro ((^.), (.~), to)+import Lens.Micro.Mtl (use, view, (.=), (%=)) import Numeric.Natural (Natural) import System.Exit import System.IO import System.IO.Error -import Data.Parameterized.TraversableFC (fmapFC)- import What4.Config import What4.Interface import What4.FunctionName@@ -135,7 +138,7 @@ -- -- Type parameters: ----- * 'p' the "personality", i.e. user-defined state parameterized by @sym@+-- * 'p' see 'Lang.Crucible.Simulator.ExecutionTree.cruciblePersonality' -- * 'sym' the symbolic backend -- * 'ext' the syntax extension ("Lang.Crucible.CFG.Extension") -- * 'rtp' global return type@@ -156,8 +159,14 @@ -- | Exit from the current execution by ignoring the continuation -- and immediately returning an aborted execution result. exitExecution :: IsSymInterface sym => ExitCode -> OverrideSim p sym ext rtp args r a-exitExecution ec = Sim $ StateContT $ \_c s ->- return $ ResultState $ AbortedResult (s^.stateContext) (AbortedExit ec)+exitExecution ec = do+ ActiveTree _ctx ar0 <- use stateTree+ let gp =+ case ar0 of+ TotalRes e -> e+ PartialRes _loc _pred ex _ar1 -> ex+ Sim $ StateContT $ \_c s ->+ return $ ResultState $ AbortedResult (s ^. stateContext) (AbortedExit ec gp) bindOverrideSim :: OverrideSim p sym ext rtp args r a ->@@ -216,13 +225,14 @@ (forall bak. IsSymBackend sym bak => bak -> OverrideSim p sym ext rtp args ret a) -> OverrideSim p sym ext rtp args ret a ovrWithBackend k =- do simCtx <- use stateContext- ctxSolverProof simCtx (withBackend simCtx k)+ do simSt <- get+ simCtx <- use stateContext+ ctxSolverProof simCtx (withStateBackend simSt k) instance MonadVerbosity (OverrideSim p sym ext rtp args ret) where getVerbosity = do ctx <- getContext- let cfg = ctxSolverProof ctx (getConfiguration (ctx^.ctxSymInterface))+ let cfg = ctxSolverProof ctx (getConfiguration (ctx ^. ctxSymInterface)) v <- liftIO (getOpt =<< getOptionSetting verbosity cfg) return (fromInteger v) @@ -572,7 +582,7 @@ go !i ((p,m,mpos):xs) = let msg = T.pack ("after branch " ++ show i) m' = ReaderT (runStateContT (unSim m) c')- in overrideSymbolicBranch p all_args m' mpos old_args (go (i+1) xs) (Just (OtherPos msg))+ in overrideSymbolicBranch p all_args m' mpos old_args (go (i + 1) xs) (Just (OtherPos msg)) go (0::Integer) xs0 -- | Non-deterministically choose among several feasible branches.@@ -627,7 +637,7 @@ -- | Build a map of function bindings from a list of -- handle/binding pairs. fnBindingsFromList :: [FnBinding p sym ext] -> FunctionBindings p sym ext-fnBindingsFromList = foldl' insertFnBinding $ FnBindings emptyHandleMap+fnBindingsFromList = Foldable.foldl' insertFnBinding $ FnBindings emptyHandleMap registerFnBinding :: FnBinding p sym ext -> OverrideSim p sym ext rtp a r ()@@ -690,6 +700,21 @@ , typedOverrideRet :: TypeRepr ret } +-- | Create a 'TypedOverride' with a statically-known signature+typedOverride ::+ KnownRepr (Ctx.Assignment TypeRepr) args =>+ KnownRepr TypeRepr ret =>+ (forall rtp args' ret'.+ Ctx.Assignment (RegValue' sym) args ->+ OverrideSim p sym ext rtp args' ret' (RegValue sym ret)) ->+ TypedOverride p sym ext args ret+typedOverride handler =+ TypedOverride+ { typedOverrideHandler = handler+ , typedOverrideArgs = knownRepr+ , typedOverrideRet = knownRepr+ }+ -- | A 'TypedOverride' with the type parameters @args@, @ret@ existentially -- quantified data SomeTypedOverride p sym ext =@@ -703,3 +728,11 @@ runTypedOverride nm typedOvr = mkOverride' nm (typedOverrideRet typedOvr) $ do RegMap args <- getOverrideArgs typedOverrideHandler typedOvr (fmapFC (RV . regValue) args)++-- | Bind a 'TypedOverride' to a 'FnHandle'+bindTypedOverride ::+ FnHandle args ret ->+ TypedOverride p sym ext args ret ->+ OverrideSim p sym ext rtp args' ret' ()+bindTypedOverride hdl ov =+ bindFnHandle hdl (UseOverride (runTypedOverride (handleName hdl) ov))
src/Lang/Crucible/Simulator/PathSatisfiability.hs view
@@ -5,7 +5,7 @@ -- at symbolic branch points -- Copyright : (c) Galois, Inc 2018 -- License : BSD3--- Maintainer : Rob Dockins <rdockins@galois.com>+-- Maintainer : Ryan Scott <rscott@galois.com>, Langston Barrett <langston@galois.com> -- Stability : provisional ------------------------------------------------------------------------ {-# LANGUAGE DataKinds #-}@@ -25,8 +25,8 @@ , BranchResult(..) ) where -import Control.Lens( (^.) ) import Control.Monad.Reader+import Lens.Micro ((^.)) import qualified Prettyprinter as PP import Lang.Crucible.Backend@@ -54,14 +54,20 @@ (Just (ConcreteBool True)) ] -+-- | Prune unsatisfiable execution traces during simulation.+--+-- At every symbolic branch point, an SMT solver is queried to determine if one+-- or both symbolic branches are unsatisfiable. Only branches with satisfiable+-- branch conditions are explored. pathSatisfiabilityFeature :: forall sym. IsSymInterface sym => sym ->- (Maybe ProgramLoc -> Pred sym -> IO BranchResult)- {- ^ An action for considering the satisfiability of a predicate.- In the current state of the symbolic interface, indicate what- we can determine about the given predicate. -} ->+ -- | An action for considering the satisfiability of a predicate. In the+ -- current state of the symbolic interface, indicate what we can determine+ -- about the given predicate.+ --+ -- Usually, this is set to 'Lang.Crucible.Backend.Online.considerSatisfiability'.+ (Maybe ProgramLoc -> Pred sym -> IO BranchResult) -> IO (GenericExecutionFeature sym) pathSatisfiabilityFeature sym considerSatisfiability = do tryExtendConfig pathSatOptions (getConfiguration sym)@@ -82,11 +88,11 @@ considerSatisfiability ploc p >>= \case IndeterminateBranchResult -> return ExecutionFeatureNoChange- NoBranch chosen_branch -> withBackend (st ^. stateContext) $ \bak ->+ NoBranch chosen_branch -> withStateBackend st $ \bak -> do p' <- if chosen_branch then return p else notPred sym p let frm = if chosen_branch then tp else fp addAssumption bak (BranchCondition loc (pausedLoc frm) p')- ExecutionFeatureNewState <$> runReaderT (resumeFrame frm (asContFrame (st^.stateTree))) st+ ExecutionFeatureNewState <$> runReaderT (resumeFrame frm (asContFrame (st ^. stateTree))) st UnsatisfiableContext -> return (ExecutionFeatureNewState (AbortState (InfeasibleBranch loc) st)) where
src/Lang/Crucible/Simulator/PathSplitting.hs view
@@ -35,12 +35,12 @@ , executeCrucibleDFSPaths ) where -import Control.Lens ( (^.) ) import Control.Monad.Reader import Data.IORef import Data.Sequence( Seq ) import qualified Data.Sequence as Seq import Data.Word+import Lens.Micro ((^.)) import What4.Interface import What4.ProgramLoc@@ -97,8 +97,7 @@ IO (ExecState p sym ext rtp) restoreWorkItem (WorkItem branchPred loc frm st assumes) = do let sym = st ^. stateSymInterface- let simCtx = st ^. stateContext- withBackend simCtx $ \bak ->+ withStateBackend st $ \bak -> do setCurrentProgramLoc sym loc restoreAssumptionState bak assumes addAssumption bak (BranchCondition loc (pausedLoc frm) branchPred)@@ -117,7 +116,7 @@ ExecutionFeature p sym ext rtp pathSplittingFeature wl = ExecutionFeature $ \case SymbolicBranchState p trueFrame falseFrame _bt st ->- withBackend (st^.stateContext) $ \bak ->+ withStateBackend st $ \bak -> do let sym = st ^. stateSymInterface pnot <- notPred sym p assumes <- saveAssumptionState bak
src/Lang/Crucible/Simulator/PositionTracking.hs view
@@ -21,8 +21,8 @@ ( positionTrackingFeature ) where -import Control.Lens ((^.), to) import Control.Monad.IO.Class+import Lens.Micro ((^.), to) import Lang.Crucible.Backend import Lang.Crucible.Simulator.CallFrame@@ -44,8 +44,7 @@ IO (ExecutionFeatureResult p sym ext rtp) onStep exst@(RunningState (RunBlockStart _bid) st) = do let loc = st ^. (stateCrucibleFrame.to frameProgramLoc)- let simCtx = st ^. stateContext- liftIO $ withBackend simCtx $ \bak ->+ liftIO $ withStateBackend st $ \bak -> addAssumptions bak (singleEvent (LocationReachedEvent loc)) return (ExecutionFeatureModifiedState exst)
src/Lang/Crucible/Simulator/Profiling.hs view
@@ -47,9 +47,9 @@ ) where import qualified Control.Exception as Ex-import Control.Lens import Control.Monad ((<=<), when) import Data.Foldable (toList)+import Data.Functor.Identity (Identity(..), runIdentity) import Data.Hashable import Data.HashSet (HashSet) import qualified Data.HashSet as HashSet@@ -64,6 +64,7 @@ import Data.Time.Clock import Data.Time.Clock.POSIX import Data.Time.Format+import Lens.Micro ((^.)) import System.IO (withFile, IOMode(..), hPutStrLn) import Text.JSON import GHC.Generics (Generic)@@ -210,7 +211,7 @@ symProUIString :: String -> String -> ProfilingTable -> IO String symProUIString nm source tbl = do js <- symProUIJSON nm source tbl- return ("data.receiveData("++ encode js ++ ");")+ return ("data.receiveData(" ++ encode js ++ ");") symProUIJSON :: String -> String -> ProfilingTable -> IO JSValue@@ -348,7 +349,7 @@ nextEventID :: ProfilingTable -> IO Integer nextEventID tbl = do i <- readIORef (eventIDRef tbl)- writeIORef (eventIDRef tbl) $! (i+1)+ writeIORef (eventIDRef tbl) $! (i + 1) return i dedupEvent :: ProfilingTable -> EventDedup -> IO () -> IO ()@@ -456,18 +457,18 @@ InitialState _ _ _ _ _ -> enterEvent tbl startFunctionName Nothing CallState _rh call st ->- enterEvent tbl (resolvedCallName call) (st^.stateLocation)+ enterEvent tbl (resolvedCallName call) (st ^. stateLocation) ReturnState nm _ _ _ -> exitEvent tbl nm TailCallState _ call st ->- do exitEvent tbl (st^.stateTree.actFrame.gpValue.frameFunctionName)- enterEvent tbl (resolvedCallName call) (st^.stateLocation)+ do exitEvent tbl (st ^. stateTree.actFrame.gpValue.frameFunctionName)+ enterEvent tbl (resolvedCallName call) (st ^. stateLocation) SymbolicBranchState{} -> modifyIORef' (metricSplits (metrics tbl)) succ AbortState{} -> modifyIORef' (metricAborts (metrics tbl)) succ UnwindCallState _ _ st ->- exitEvent tbl (st^.stateTree.actFrame.gpValue.frameFunctionName)+ exitEvent tbl (st ^. stateTree.actFrame.gpValue.frameFunctionName) BranchMergeState tgt st -> when (isMergeState tgt st) (modifyIORef' (metricMerges (metrics tbl)) succ)@@ -476,16 +477,16 @@ when (recordCoverage filt) $ case exst of ControlTransferState res st ->- let funcName = st^.stateTree.actFrame.gpValue.frameFunctionName in+ let funcName = st ^. stateTree.actFrame.gpValue.frameFunctionName in case res of ContinueResumption (ResolvedJump blk _) ->- blockEvent tbl funcName (st^.stateLocation) (Some blk)+ blockEvent tbl funcName (st ^. stateLocation) (Some blk) CheckMergeResumption (ResolvedJump blk _) ->- blockEvent tbl funcName (st^.stateLocation) (Some blk)+ blockEvent tbl funcName (st ^. stateLocation) (Some blk) _ -> return () RunningState (RunBlockEnd _) st ->- let funcName = st^.stateTree.actFrame.gpValue.frameFunctionName in- case st^.stateTree.actFrame.gpValue.crucibleSimFrame.frameStmts of+ let funcName = st ^. stateTree.actFrame.gpValue.frameFunctionName in+ case st ^. stateTree.actFrame.gpValue.crucibleSimFrame.frameStmts of TermStmt loc term | Just blocks <- termStmtNextBlocks term, length blocks >= 2 ->@@ -498,7 +499,7 @@ SimState p sym ext root f args -> Bool isMergeState tgt st =- case st^.stateTree.actContext of+ case st ^. stateTree.actContext of VFFBranch _ctx _assume_frame _loc _p other_branch tgt' | Just Refl <- testEquality tgt tgt' -> case other_branch of
+ src/Lang/Crucible/Simulator/RecordAndReplay.hs view
@@ -0,0 +1,368 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE FunctionalDependencies #-}+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE StandaloneKindSignatures #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeFamilies #-}++module Lang.Crucible.Simulator.RecordAndReplay (+ HasRecordState(..),+ RecordState,+ mkRecordState,+ HasReplayState(..),+ ReplayState,+ mkReplayState,+ recordTraceLength,+ replayTraceLength,+ RecordedTrace,+ getRecordedTrace,+ getConcreteRecordedTrace,+ recordFeature,+ replayFeature,+ initialTrace,+ traceGlobal,+ emptyRecordedTrace+) where++import Control.Exception qualified as X+import Data.Foldable qualified as F+import Data.Function ((&))+import Data.Kind (Type)+import Data.Sequence qualified as Seq+import Data.Text qualified as Text+import Lang.Crucible.Backend qualified as CB+import Lang.Crucible.CFG.Core qualified as C+import Lang.Crucible.FunctionHandle qualified as C+import Lang.Crucible.Simulator qualified as C+import Lang.Crucible.Simulator.EvalStmt qualified as C+import Lang.Crucible.Simulator.ExecutionTree qualified as C+import Lang.Crucible.Simulator.GlobalState qualified as C+import Lang.Crucible.Simulator.SymSequence qualified as CSSS+import Lang.Crucible.Types qualified as CT+import Lens.Micro ((^.), (%~))+import Lens.Micro qualified as Lens+import Lens.Micro.TH (makeLenses)+import What4.Interface qualified as W4+import What4.Partial qualified as W4P++-- | A trace consists of the 'W4.ProgramLoc's returned by+-- 'W4.getCurrentProgramLoc' in 'C.RunningState's during symbolic execution.+--+-- Intentionally not part of the API so as to keep the implementation abstract.+type TraceType = CT.SequenceType (CT.StringType W4.Unicode)++-- | Type parameters:+--+-- * @p@: see 'C.cruciblePersonality'+-- * @sym@: instance of 'Lang.Crucible.Backend.IsSymInterface'+-- * @ext@: language extension, see "Lang.Crucible.CFG.Extension"+-- * @rtp@: type of the simulator return value+type RecordState :: Type -> Type -> Type -> Type -> Type+newtype RecordState p sym ext rtp+ = RecordState (C.GlobalVar TraceType)+ -- ^ constructor intentionally not exported++{- | A trace from 'recordFeature', processed and ready for consumption by+'replayFeature'.+-}+newtype RecordedTrace sym+ = RecordedTrace (C.RegValue sym TraceType)++-- | Type parameters:+--+-- * @p@: see 'C.cruciblePersonality'+-- * @sym@: instance of 'Lang.Crucible.Backend.IsSymInterface'+-- * @ext@: language extension, see "Lang.Crucible.CFG.Extension"+-- * @rtp@: type of the simulator return value+type ReplayState :: Type -> Type -> Type -> Type -> Type+data ReplayState p sym ext rtp+ = ReplayState+ { _traceGlobal :: (C.GlobalVar TraceType)+ , _initialTrace :: (RecordedTrace sym)+ }+ -- ^ constructor intentionally not exported+makeLenses ''ReplayState++-- | Constructor for 'RecordState'+mkRecordState ::+ C.HandleAllocator -> IO (RecordState p sym ext rtp)+mkRecordState halloc =+ RecordState <$> C.freshGlobalVar halloc "recordState" W4.knownRepr++-- | Constructor for 'ReplayState'+mkReplayState ::+ C.HandleAllocator -> RecordedTrace sym -> IO (ReplayState p sym ext rtp)+mkReplayState halloc rt =+ ReplayState <$> C.freshGlobalVar halloc "replayState" W4.knownRepr <*> pure rt++-- | A class for Crucible personality types @p@ which contain a+-- 'RecordState'. This execution feature is polymorphic over+-- 'RecordState' so that downstream users can supply their own+-- personality types that extend 'RecordState' further.+class HasRecordState p r sym ext rtp | p -> r sym ext rtp where+ recordState :: Lens.Lens' p (RecordState r sym ext rtp)++instance HasRecordState (RecordState p sym ext rtp) p sym ext rtp where+ recordState = id+ {-# INLINE recordState #-}++-- | A class for Crucible personality types @p@ which contain a+-- 'ReplayState'. This execution feature is polymorphic over+-- 'ReplayState' so that downstream users can supply their own+-- personality types that extend 'ReplayState' further.+class HasReplayState p r sym ext rtp | p -> r sym ext rtp where+ replayState :: Lens.Lens' p (ReplayState r sym ext rtp)++instance HasReplayState (ReplayState p sym ext rtp) p sym ext rtp where+ replayState = id+ {-# INLINE replayState #-}++data TraceGlobalNotDefined = TraceGlobalNotDefined++instance Show TraceGlobalNotDefined where+ show _ = "record and replay trace global not defined"++instance X.Exception TraceGlobalNotDefined++locAsStr ::+ W4.IsExprBuilder sym =>+ sym ->+ IO (C.RegValue sym (CT.StringType W4.Unicode))+locAsStr sym = do+ loc <- W4.getCurrentProgramLoc sym+ let txtLoc = Text.pack (show loc)+ W4.stringLit sym (W4.UnicodeLiteral txtLoc)++emptyRecordedTrace :: sym -> IO (RecordedTrace sym)+emptyRecordedTrace sym = RecordedTrace <$> CSSS.nilSymSequence sym++getRecordTrace ::+ HasRecordState p p sym ext rtp =>+ C.SimState p sym ext rtp f args ->+ Maybe (C.RegValue sym TraceType)+getRecordTrace simState = do+ let ctx = simState ^. C.stateContext+ let RecordState g = ctx ^. C.cruciblePersonality . recordState+ C.lookupGlobal g (simState ^. C.stateGlobals)++-- | Get the length of the currently recorded trace+recordTraceLength ::+ W4.IsExprBuilder sym =>+ HasRecordState p p sym ext rtp =>+ C.SimState p sym ext rtp f args ->+ IO (Maybe (W4.SymNat sym))+recordTraceLength simState = do+ let sym = simState ^. C.stateSymInterface+ case getRecordTrace simState of+ Nothing -> pure Nothing+ Just s -> Just <$> CSSS.lengthSymSequence sym s++getReplayTrace ::+ HasReplayState p p sym ext rtp =>+ C.SimState p sym ext rtp f args ->+ Maybe (C.RegValue sym TraceType)+getReplayTrace simState = do+ let ctx = simState ^. C.stateContext+ let g = ctx ^. C.cruciblePersonality . replayState . traceGlobal+ C.lookupGlobal g (simState ^. C.stateGlobals)++-- | Get the length of the trace being replayed+replayTraceLength ::+ W4.IsExprBuilder sym =>+ HasReplayState p p sym ext rtp =>+ C.SimState p sym ext rtp f args ->+ IO (Maybe (W4.SymNat sym))+replayTraceLength simState = do+ let sym = simState ^. C.stateSymInterface+ case getReplayTrace simState of+ Nothing -> pure Nothing+ Just s -> Just <$> CSSS.lengthSymSequence sym s++-- | An 'C.ExecutionFeature' to record traces.+--+-- During execution this logs program locations to a Crucible global variable.+-- After execution, this variable may be read with 'getRecordedTrace' and the+-- 'RecordedTrace' can be passed to 'replayFeature' to \"replay\" it, i.e., to+-- abort all branches that deviate from it.+--+-- If this is not called with 'C.InitialState' before any other 'C.ExecState',+-- it may throw a 'TraceGlobalNotDefined' exception.+recordFeature ::+ ( HasRecordState p p sym ext rtp+ , W4.IsExprBuilder sym+ ) =>+ C.ExecutionFeature p sym ext rtp+recordFeature =+ C.ExecutionFeature $+ \case+ C.InitialState simCtx globals abortHandler retTy cont -> do+ globals' <- insertNewTrace simCtx globals+ let iState = C.InitialState simCtx globals' abortHandler retTy cont+ return $ C.ExecutionFeatureModifiedState iState+ C.RunningState runStateInfo st -> do+ loc <- locAsStr (st ^. C.stateSymInterface)+ st' <- consTrace st loc+ let rState = C.RunningState runStateInfo st'+ return $ C.ExecutionFeatureModifiedState rState+ _ -> pure C.ExecutionFeatureNoChange+ where+ insertNewTrace ::+ HasRecordState p p sym ext rtp =>+ C.SimContext p sym ext ->+ C.SymGlobalState sym ->+ IO (C.SymGlobalState sym)+ insertNewTrace simCtx globals = do+ let RecordState g = simCtx ^. C.cruciblePersonality . recordState+ let sym = simCtx ^. C.ctxSymInterface+ nil <- CSSS.nilSymSequence sym+ return (C.insertGlobal g nil globals)++ getTraceOrThrow ::+ HasRecordState p p sym ext rtp =>+ C.SimState p sym ext rtp f args ->+ IO (C.RegValue sym TraceType)+ getTraceOrThrow st =+ case getRecordTrace st of+ Nothing -> X.throw TraceGlobalNotDefined+ Just t -> pure t++ insertTrace ::+ HasRecordState p p sym ext rtp =>+ C.SimState p sym ext rtp f args ->+ C.RegValue sym TraceType ->+ C.SimState p sym ext rtp f args+ insertTrace st v = do+ let simCtx = st ^. C.stateContext+ let RecordState g = simCtx ^. C.cruciblePersonality . recordState+ st & C.stateGlobals %~ C.insertGlobal g v++ consTrace ::+ HasRecordState p p sym ext rtp =>+ C.SimState p sym ext rtp f args ->+ C.RegValue sym (CT.StringType W4.Unicode) ->+ IO (C.SimState p sym ext rtp f args)+ consTrace st v = do+ s <- getTraceOrThrow st+ let sym = st ^. C.stateSymInterface+ s' <- CSSS.consSymSequence sym v s+ pure (insertTrace st s')+++ -- ^ constructor intentionally not exported to keep 'TraceType' out of the+ -- API, but it could be exported in the future if necessary.++-- | Obtain a 'RecordedTrace' after execution.+getRecordedTrace ::+ C.SymGlobalState sym ->+ RecordState p sym ext rtp ->+ sym ->+ IO (RecordedTrace sym)+getRecordedTrace globals (RecordState g) sym = do+ case C.lookupGlobal g globals of+ Nothing -> X.throw TraceGlobalNotDefined+ Just s -> RecordedTrace <$> CSSS.reverseSymSequence sym s++-- | Obtain a 'RecordedTrace' after execution using concrete evaluation.+--+-- When a concrete evaluation function for 'W4.Pred's is available and only the+-- concretized trace is desired, this is more performant than the more general+-- 'getRecordedTrace'.+getConcreteRecordedTrace ::+ W4.IsExprBuilder sym =>+ C.SymGlobalState sym ->+ RecordState p sym ext rtp ->+ sym ->+ -- | Evaluation for booleans, usually a 'What4.Expr.GroundEval.GroundEvalFn'+ (W4.Pred sym -> IO Bool) ->+ IO (RecordedTrace sym)+getConcreteRecordedTrace globals (RecordState g) sym evalBool = do+ case C.lookupGlobal g globals of+ Nothing -> X.throw TraceGlobalNotDefined+ Just s -> RecordedTrace <$> concretizeAndReverseTrace s+ where+ concretizeAndReverseTrace s = do+ concretized <- CSSS.concretizeSymSequence evalBool pure s+ let reversed = Seq.reverse concretized+ CSSS.fromListSymSequence sym (F.toList reversed)++{- | Inserts a recorded trace into the state's replay trace variable+The replay feature will follow this trace if it is enabled+-}+insertReplayTrace ::+ (HasReplayState p p sym ext rtp) =>+ C.SimState p sym ext rtp f args ->+ C.RegValue sym TraceType ->+ C.SimState p sym ext rtp f args+insertReplayTrace st v = do+ let simCtx = st ^. C.stateContext+ let g = simCtx ^. C.cruciblePersonality . replayState . traceGlobal+ st & C.stateGlobals %~ C.insertGlobal g v++-- | An 'C.ExecutionFeature' to replay traces recorded with 'recordFeature'.+--+-- Branches that deviate from the given trace will be aborted with+-- 'C.InfeasibleBranch'.+--+-- If this is not called with 'C.InitialState' before any other 'C.ExecState',+-- it may throw a 'TraceGlobalNotDefined' exception.+replayFeature ::+ ( HasReplayState p p sym ext rtp+ , W4.IsExprBuilder sym+ ) =>+ -- | Whether to stop at the end of the trace. If this is 'True' and execution+ -- has exhausted the trace, then any further execution will be aborted via+ -- 'C.InfeasibleBranch'.+ Bool ->+ C.ExecutionFeature p sym ext rtp+replayFeature stop =+ C.ExecutionFeature $+ \case+ C.InitialState simCtx globals abortHandler retTy cont -> do+ let rstate = simCtx ^. C.cruciblePersonality . replayState+ let g = rstate ^. traceGlobal+ let RecordedTrace trace = rstate ^. initialTrace+ let globals' = C.insertGlobal g trace globals+ let iState = C.InitialState simCtx globals' abortHandler retTy cont+ return $ C.ExecutionFeatureModifiedState iState+ C.RunningState runStateInfo st -> do+ let sym = st ^. C.stateSymInterface+ s <- getTraceOrThrow st+ partExpr <- CSSS.unconsSymSequence sym (W4.stringIte sym) s+ let badPath = do+ loc <- W4.getCurrentProgramLoc sym+ let st' = C.AbortState (CB.InfeasibleBranch loc) st+ pure (C.ExecutionFeatureNewState st')+ case partExpr of+ W4P.Unassigned+ | stop -> badPath+ | otherwise -> pure C.ExecutionFeatureNoChange+ W4P.PE valid (expectedLoc, rest) ->+ C.withStateBackend st $ \bak -> do+ let msg = "Trace must be valid"+ CB.assert bak valid (C.AssertFailureSimError msg "")++ currLoc <- locAsStr sym+ atExpectedLoc <- W4.stringEq sym currLoc expectedLoc+ case W4.asConstantPred atExpectedLoc of+ Just False -> badPath+ _ -> do+ let msg' = "Execution deviated from trace"+ CB.assert bak atExpectedLoc (C.AssertFailureSimError msg' "")+ let st' = insertReplayTrace st rest+ let rState = C.RunningState runStateInfo st'+ pure (C.ExecutionFeatureModifiedState rState)++ _ -> pure C.ExecutionFeatureNoChange+ where+ getTraceOrThrow ::+ HasReplayState p p sym ext rtp =>+ C.SimState p sym ext rtp f args ->+ IO (C.RegValue sym TraceType)+ getTraceOrThrow st =+ case getReplayTrace st of+ Nothing -> X.throw TraceGlobalNotDefined+ Just t -> pure t
src/Lang/Crucible/Simulator/RegValue.hs view
@@ -48,6 +48,8 @@ , muxVector , muxSymSequence , muxHandle+ -- * Equality+ , eqRegValue ) where import Control.Monad@@ -361,3 +363,41 @@ p (unVB (x Ctx.! i)) (unVB (y Ctx.! i))++------------------------------------------------------------------------+-- Equality++-- | Equality of 'RegValue's.+--+-- This is only supported for a few types, see #1582.+eqRegValue ::+ forall sym tp.+ IsInterpretedFloatExprBuilder sym =>+ sym ->+ TypeRepr tp ->+ RegValue sym tp ->+ RegValue sym tp ->+ IO (Pred sym)+eqRegValue sym tp x y =+ case tp of+ -- Base types+ BoolRepr -> eqPred sym x y+ BVRepr _width -> bvEq sym x y+ ComplexRealRepr -> cplxEq sym x y+ FloatRepr @fi _ -> iFloatEq @_ @fi sym x y+ IEEEFloatRepr _fpp -> floatEq sym x y+ IntegerRepr -> intEq sym x y+ NatRepr -> natEq sym x y+ RealValRepr -> realEq sym x y+ SymbolicStructRepr _tys -> structEq sym x y+ SymbolicArrayRepr _idxs _tp -> arrayEq sym x y+ StringRepr _si -> stringEq sym x y++ -- Trivial cases+ UnitRepr -> pure (truePred sym)+ CharRepr ->+ if x == y+ then pure (truePred sym)+ else pure (falsePred sym)++ _ -> fail ("eqRegValue not supported for " ++ show tp)
src/Lang/Crucible/Simulator/SimError.hs view
@@ -16,23 +16,30 @@ {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-}+{-# LANGUAGE PatternSynonyms #-} module Lang.Crucible.Simulator.SimError ( SimErrorReason(..)- , SimError(..)+ , SimError(.., SimError)+ , ProgramStack(..)+ , mkSimError+ , simErrorReason+ , simErrorLoc+ , simErrorContext , simErrorReasonMsg , simErrorDetailsMsg , ppSimError+ , ppProgramStack ) where import GHC.Stack (CallStack) import Control.Exception import Data.String-import Data.Typeable import Prettyprinter import What4.ProgramLoc + ------------------------------------------------------------------------ -- SimError @@ -49,15 +56,23 @@ | ResourceExhausted String -- ^ A loop iteration count, or similar resource limit, -- was exceeded.- deriving (Typeable) -data SimError- = SimError- { simErrorLoc :: !ProgramLoc- , simErrorReason :: !SimErrorReason- }- deriving (Typeable)+data SimError + = SimErrorWithContext !ProgramLoc !SimErrorReason !(Maybe ProgramStack)+ +-- | This pattern synonym constructs SimErrors without a program stack context when used +-- as an expression and ignores the program stack when used as a pattern. It exists+-- because SimError did not used to have a `ProgramStack`, and there are many usages+-- in the code of the previous constructor which is approximated by this pattern.+--+-- Using SimErrorWithContext should be preferred.+pattern SimError :: ProgramLoc -> SimErrorReason -> SimError+pattern SimError { simErrorLoc, simErrorReason } <- SimErrorWithContext simErrorLoc simErrorReason _+ where SimError loc reason = SimErrorWithContext loc reason Nothing +simErrorContext :: SimError -> Maybe ProgramStack+simErrorContext (SimErrorWithContext _ _ c) = c+ simErrorReasonMsg :: SimErrorReason -> String simErrorReasonMsg (GenericSimError msg) = msg simErrorReasonMsg (Unsupported _ msg) = "Unsupported feature: " ++ msg@@ -70,6 +85,9 @@ simErrorDetailsMsg (Unsupported stk _) = show stk simErrorDetailsMsg _ = "" +mkSimError :: ProgramLoc -> SimErrorReason -> Maybe ProgramStack -> SimError+mkSimError loc reason mbCtx = SimErrorWithContext loc reason mbCtx+ instance IsString SimErrorReason where fromString = GenericSimError @@ -83,13 +101,38 @@ ppSimError er = vcat $ [ pretty (plSourceLoc loc) <> pretty ": error: in" <+> pretty (plFunction loc) , pretty (simErrorReasonMsg rsn)- ] ++ if null details- then []- else [ pretty "Details:"- , indent 2 (vcat (pretty <$> lines details))- ]+ ] ++ (if null details+ then []+ else [ pretty "Details:"+ , indent 2 (vcat (pretty <$> lines details))+ ])+ ++ (case simErrorContext er of+ Nothing -> []+ Just (ProgramStack _ []) -> []+ Just ctx -> [ pretty "Context:"+ , indent 2 (ppProgramStack ctx)+ ]) where loc = simErrorLoc er details = simErrorDetailsMsg rsn- rsn = simErrorReason er+ rsn = simErrorReason er +-- | Representation of the program stack for providing dynamic+-- context for SimErrors+data ProgramStack = ProgramStack + { -- | Number of calling frames omitted in the stack trace+ psFrameOmitCount :: Int+ -- | The visible part of the stack strace+ , psFrames :: [ProgramLoc]+ }++ppProgramStack :: ProgramStack -> Doc ann+ppProgramStack (ProgramStack omittedCount frames) = vcat ((ppLoc <$> frames) ++ omitLine)+ where+ omitLine =+ if omittedCount <= 0+ then []+ else [ pretty "..." <+> pretty omittedCount <+> pretty "calling frames omitted" ]+ ppLoc l = pretty (plSourceLoc l) <> pretty ":" <+> pretty (plFunction l)+ instance Exception SimError+
src/Lang/Crucible/Simulator/SymSequence.hs view
@@ -25,6 +25,7 @@ , traverseSymSequence , concreteizeSymSequence , concretizeSymSequence+, reverseSymSequence , prettySymSequence -- * Low-level evaluation primitives@@ -34,6 +35,7 @@ ) where import Control.Monad.State+import Data.Coerce (coerce) import Data.Functor.Const import Data.Kind (Type) import Data.IORef@@ -195,6 +197,39 @@ pure (SymSequenceAppend n xs ys) +-- | Reverse a 'SymSequence'+reverseSymSequence :: forall sym a. sym -> SymSequence sym a -> IO (SymSequence sym a)+reverseSymSequence sym = \s -> coerce (evalWithFreshCache f s)+ where+ f :: (SymSequence sym a -> IO (Const (SymSequence sym a) a))+ -> SymSequence sym a -> IO (Const (SymSequence sym a) a)+ f loop = \case+ SymSequenceNil -> pure (coerce SymSequenceNil)+ s@(SymSequenceCons{}) -> coerce (reverseConsSpine loop SymSequenceNil s)+ SymSequenceAppend _ xs ys ->+ do xs' <- coerce (loop xs)+ ys' <- coerce (loop ys)+ coerce (appendSymSequence sym ys' xs')+ SymSequenceMerge _ p xs ys ->+ do xs' <- coerce (loop xs)+ ys' <- coerce (loop ys)+ coerce (muxSymSequence sym p xs' ys')++ -- Walk a cons-spine with an accumulator, producing a flat cons-list.+ -- Falls back to cached 'loop' when a non-Cons node is reached.+ reverseConsSpine ::+ (SymSequence sym a -> IO (Const (SymSequence sym a) a)) ->+ SymSequence sym a ->+ SymSequence sym a ->+ IO (SymSequence sym a)+ reverseConsSpine loop acc = \case+ SymSequenceCons _ v tl -> do+ acc' <- consSymSequence sym v acc+ reverseConsSpine loop acc' tl+ other -> do+ otherReversed <- coerce (loop other)+ appendSymSequence sym otherReversed acc+ -- | Test if a sequence is nil (is empty) isNilSymSequence :: forall sym a. IsExprBuilder sym =>@@ -458,7 +493,7 @@ computeOccMap = loop where visit n k m- | Just i <- Map.lookup n m = Map.insert n (i+1) m+ | Just i <- Map.lookup n m = Map.insert n (i + 1) m | otherwise = k (Map.insert n 1 m) loop SymSequenceNil = id
src/Lang/Crucible/Syntax.hs view
@@ -85,18 +85,20 @@ , littleEndianStore ) where -import Control.Lens import qualified Data.BitVector.Sized as BV+import Data.Function ((&)) import Data.Kind import Data.Parameterized.Classes import qualified Data.Parameterized.Context as Ctx import Data.Parameterized.Some import Data.Text (Text) import qualified Data.Vector as V+import Lens.Micro ((.~)) import Numeric.Natural import Lang.Crucible.CFG.Expr import Lang.Crucible.FunctionHandle+import Lang.Crucible.Panic (panic) import Lang.Crucible.Types import What4.Utils.StringLiteral@@ -330,7 +332,7 @@ , KnownRepr TypeRepr ret , KnownCtx TypeRepr args )- => e (FunctionHandleType (args::>tp) ret)+ => e (FunctionHandleType (args ::> tp) ret) -> e tp -> e (FunctionHandleType args ret) closure h a = app (Closure knownRepr knownRepr h knownRepr a)@@ -408,7 +410,7 @@ (app $ BVAdd addrWidth basePtr (app $ BVLit addrWidth (BV.mkBV addrWidth (toInteger (n-1))))) (app $ BVSelect idx cellWidth valWidth v) (go (n-1))- go _ = error "bad size parameters in bigEndianStore!"+ go _ = panic "bigEndianStore" ["bad size parameters!"] littleEndianStore :: (IsExpr expr, 1 <= addrWidth, 1 <= valWidth, 1 <= cellWidth)@@ -429,7 +431,7 @@ (app $ BVAdd addrWidth basePtr (app $ BVLit addrWidth (BV.mkBV addrWidth (toInteger (n-1))))) (app $ BVSelect idx cellWidth valWidth v) (go (n-1))- go _ = error "bad size parameters in littleEndianStore!"+ go _ = panic "littleEndianStore" ["bad size parameters!"] concatExprs :: forall w a expr . (IsExpr expr, 1 <= w)@@ -438,7 +440,7 @@ -> (forall w'. (1 <= w') => NatRepr w' -> expr (BVType w') -> a) -> a -concatExprs _ [] = \_ -> error "Cannot concatenate 0 elements together"+concatExprs _ [] = \_ -> panic "concatExprs" ["Cannot concatenate 0 elements together"] concatExprs w (a:as) = go a as where go :: (1 <= w)@@ -448,7 +450,7 @@ -> a go x0 [] k = k w x0 go x0 (x:xs) k = go x xs (\(w'::NatRepr w') z ->- withLeqProof (leqAdd LeqProof w' :: LeqProof 1 (w+w'))+ withLeqProof (leqAdd LeqProof w' :: LeqProof 1 (w + w')) (k (addNat w w') (app $ BVConcat w w' x0 z))) bigEndianLoad@@ -470,7 +472,7 @@ concatExprs cellWidth segs $ \w x -> case testEquality w valWidth of Just Refl -> x- Nothing -> error "bad size parameters in bigEndianLoad!"+ Nothing -> panic "bigEndianLoad" ["bad size parameters!"] bigEndianLoadDef@@ -494,7 +496,7 @@ concatExprs cellWidth segs $ \w x -> case testEquality w valWidth of Just Refl -> x- Nothing -> error "bad size parameters in bigEndianLoadDef!"+ Nothing -> panic "bigEndianLoadDef" ["bad size parameters!"] littleEndianLoad :: (IsExpr expr, 1 <= addrWidth, 1 <= valWidth, 1 <= cellWidth)@@ -515,7 +517,7 @@ concatExprs cellWidth segs $ \w x -> case testEquality w valWidth of Just Refl -> x- Nothing -> error "bad size parameters in littleEndianLoad!"+ Nothing -> panic "littleEndianLoad" ["bad size parameters!"] littleEndianLoadDef :: (IsExpr expr, 1 <= addrWidth, 1 <= valWidth, 1 <= cellWidth)@@ -538,4 +540,4 @@ concatExprs cellWidth segs $ \w x -> case testEquality w valWidth of Just Refl -> x- Nothing -> error "bad size parameters in littleEndianLoadDef!"+ Nothing -> panic "littleEndianLoadDef" ["bad size parameters!"]
src/Lang/Crucible/Types.hs view
@@ -383,9 +383,9 @@ StringMapRepr :: !(TypeRepr tp) -> TypeRepr (StringMapType tp) - SymbolicArrayRepr :: !(Ctx.Assignment BaseTypeRepr (idx::>tp))+ SymbolicArrayRepr :: !(Ctx.Assignment BaseTypeRepr (idx ::> tp)) -> !(BaseTypeRepr t)- -> TypeRepr (SymbolicArrayType (idx::>tp) t)+ -> TypeRepr (SymbolicArrayType (idx ::> tp) t) -- A reference to a symbolic struct. SymbolicStructRepr :: Ctx.Assignment BaseTypeRepr ctx
src/Lang/Crucible/Utils/BitSet.hs view
@@ -12,6 +12,7 @@ -- built on top of GHC-native Integers. ------------------------------------------------------------------------ module Lang.Crucible.Utils.BitSet+{-# DEPRECATED "This module is deprecated" #-} ( BitSet , getBits , empty@@ -35,7 +36,7 @@ import Data.Word import Data.Hashable import qualified Data.List as List-import Prelude hiding (null, foldr, foldl)+import Prelude hiding (null, foldr, foldl, foldl') newtype BitSet a = BitSet { getBits :: Integer } deriving (Show, Eq, Ord)@@ -47,7 +48,7 @@ empty = BitSet zeroBits null :: BitSet a -> Bool-null = (0==) . getBits+null = (0 ==) . getBits singleton :: Enum a => a -> BitSet a singleton a = BitSet (bit (fromEnum a))@@ -81,19 +82,19 @@ where go :: Enum a => Integer -> Int -> [a] go 0 _ = [] go x i- | y .&. 0xffffffff == 0 = go (shiftR x 32) $! (i+32)- | y .&. 0x0000ffff == 0 = go (shiftR x 16) $! (i+16)- | y .&. 0x000000ff == 0 = go (shiftR x 8) $! (i+ 8)+ | y .&. 0xffffffff == 0 = go (shiftR x 32) $! (i + 32)+ | y .&. 0x0000ffff == 0 = go (shiftR x 16) $! (i + 16)+ | y .&. 0x000000ff == 0 = go (shiftR x 8) $! (i + 8) | otherwise = concat- [ if testBit y 0 then [toEnum (i+0)] else []- , if testBit y 1 then [toEnum (i+1)] else []- , if testBit y 2 then [toEnum (i+2)] else []- , if testBit y 3 then [toEnum (i+3)] else []- , if testBit y 4 then [toEnum (i+4)] else []- , if testBit y 5 then [toEnum (i+5)] else []- , if testBit y 6 then [toEnum (i+6)] else []- , if testBit y 7 then [toEnum (i+7)] else []- , go (shiftR x 8) $! (i+8)+ [ if testBit y 0 then [toEnum (i + 0)] else []+ , if testBit y 1 then [toEnum (i + 1)] else []+ , if testBit y 2 then [toEnum (i + 2)] else []+ , if testBit y 3 then [toEnum (i + 3)] else []+ , if testBit y 4 then [toEnum (i + 4)] else []+ , if testBit y 5 then [toEnum (i + 5)] else []+ , if testBit y 6 then [toEnum (i + 6)] else []+ , if testBit y 7 then [toEnum (i + 7)] else []+ , go (shiftR x 8) $! (i + 8) ] where y :: Word32
src/Lang/Crucible/Utils/CoreRewrite.hs view
@@ -22,11 +22,11 @@ ( annotateCFGStmts ) where -import Control.Lens-+import Data.Function ((&)) import qualified Data.Parameterized.Context as Ctx import Data.Parameterized.Map (Pair(..)) import Data.Parameterized.TraversableFC+import Lens.Micro ((^.), (%~)) import Lang.Crucible.CFG.Core import Lang.Crucible.CFG.Extension
src/Lang/Crucible/Utils/MuxTree.hs view
@@ -34,11 +34,10 @@ , muxTreeGt ) where -import Control.Lens (folded)- import Data.Map.Strict (Map) import qualified Data.Map.Strict as Map import qualified Data.Map.Merge.Strict as Map+import Lens.Micro (folded) import What4.Interface import Lang.Crucible.Panic
src/Lang/Crucible/Utils/Structural.hs view
@@ -23,6 +23,7 @@ import Data.Parameterized.TH.GADT import Data.Parameterized.TraversableFC +import Lang.Crucible.Panic (panic) import Lang.Crucible.Utils.PrettyPrint (ppFn, commas) ------------------------------------------------------------------------@@ -52,7 +53,7 @@ let vars = varE <$> nms let nm' = case nameBase nm of c:r -> toLower c : r- [] -> error "matchPretty given constructor with empty name."+ [] -> panic "matchPretty" ["given constructor with empty name"] let mkPP0 v tp = do me <- matchPat tp case me of
src/Lang/Crucible/Vector.hs view
@@ -102,6 +102,6 @@ Endian -> NatRepr i {- ^ Split bit-vectors in this many parts -} -> NatRepr w {- ^ Length of bit-vectors in the result -} ->- Vector n (f (BVType (i * w))) -> Vector (n*i) (f (BVType w))+ Vector n (f (BVType (i * w))) -> Vector (n * i) (f (BVType w)) splitVecBV e i w xs = join i (fromBV e i w <$> xs) {-# Inline splitVecBV #-}
test/absint/Max.hs view
@@ -32,7 +32,7 @@ maxDom :: Domain Max' maxDom = d where- d = pointed j (==) (WTOWidening (>10) w)+ d = pointed j (==) (WTOWidening (> 10) w) j (Max i1) (Max i2) = Pointed (Max (max i1 i2)) w _ _ = Top
test/absint/WTO.hs view
@@ -18,6 +18,7 @@ import qualified Test.Tasty.QuickCheck as T import Lang.Crucible.Analysis.Fixpoint.Components+import Lang.Crucible.Panic (panic) wtoTests :: T.TestTree wtoTests = T.testGroup "WeakTopologicalOrdering" [@@ -97,7 +98,7 @@ -- -- The graphs are not all connected. mkRandomGraph :: Int -> QC.Gen RandomGraph-mkRandomGraph ((+1) -> sz) = do+mkRandomGraph ((+ 1) -> sz) = do nEdges <- QC.choose (2, 2*sz) srcs <- replicateM nEdges (QC.choose (0, sz)) dsts <- replicateM nEdges (QC.choose (0, sz))@@ -161,7 +162,7 @@ -- -- Not defined for empty graphs toCFG :: RandomGraph -> (Int, (Int -> [Int]))-toCFG (RG []) = error "Empty graph"+toCFG (RG []) = panic "toCFG" ["Empty graph"] toCFG (RG edges@((s0, _) : _)) = (s0, \n -> [ dst | (src, dst) <- edges, n == src])
test/helpers/Main.hs view
@@ -1,9 +1,10 @@ {-# LANGUAGE AllowAmbiguousTypes #-}-module Main where -import Control.Lens ((^.))+module Main (main) where+ import Data.List (isInfixOf) import Data.Maybe (fromMaybe)+import Lens.Micro ((^.)) import Test.Hspec import Test.Tasty@@ -26,9 +27,12 @@ import qualified Panic as P +import qualified SymSequence as S+ main :: IO ()-main =- defaultMain =<< panicTests+main = do+ p <- panicTests+ defaultMain (testGroup "crucible" [p, backendTests, S.tests]) mkBackend :: IO (Some SomeBackend) mkBackend = do@@ -81,7 +85,7 @@ c <- W4I.freshConstant sym (W4I.safeSymbol "c") W4I.BaseBoolRepr assumePred bak "assuming c" c d <- W4I.freshConstant sym (W4I.safeSymbol "d") W4I.BaseBoolRepr- LCB.assert bak c (GenericSimError "asserting d")+ LCB.assert bak d (GenericSimError "asserting d") (_asmps, mbGoals) <- LCB.popAssumptionFrameAndObligations bak frm [LCB.ProofGoal asmps gl] <- pure (fromMaybe [] (LCB.goalsToList <$> mbGoals)) asmpsPred <- LCB.assumptionsPred sym asmps
+ test/helpers/SymSequence.hs view
@@ -0,0 +1,15 @@+{-# LANGUAGE ImportQualifiedPost #-}++module SymSequence (tests) where++import Test.Tasty qualified as TT++import SymSequence.Properties qualified as Properties+import SymSequence.Reverse qualified as Reverse++tests :: TT.TestTree+tests =+ TT.testGroup "SymSequence"+ [ Properties.tests+ , Reverse.tests+ ]
+ test/helpers/SymSequence/Properties.hs view
@@ -0,0 +1,308 @@+{-# LANGUAGE EmptyDataDeriving #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-}++module SymSequence.Properties (tests) where++import Control.Monad.IO.Class (liftIO)+import Data.Foldable qualified as F+import Data.List qualified as List+import Data.Maybe qualified as Maybe+import Data.Parameterized.Nonce qualified as Nonce+import Data.Parameterized.Some (Some(Some))+import Hedgehog (Gen)+import Hedgehog qualified as H+import Hedgehog.Gen qualified as Gen+import Hedgehog.Range qualified as Range+import Lang.Crucible.Backend (SomeBackend(SomeBackend), backendGetSym)+import Lang.Crucible.Backend.Simple (newSimpleBackend)+import Lang.Crucible.Panic (panic)+import Lang.Crucible.Simulator.SymSequence (SymSequence)+import Lang.Crucible.Simulator.SymSequence qualified as S+import Test.Tasty qualified as TT+import Test.Tasty.Hedgehog qualified as TTH+import What4.Expr (EmptyExprBuilderState(EmptyExprBuilderState))+import What4.Expr.Builder (newExprBuilder)+import What4.FloatMode (FloatModeRepr(FloatIEEERepr))+import What4.Interface qualified as WI+import What4.Partial qualified as WP++---------------------------------------------------------------------+-- Tests++tests :: TT.TestTree+tests =+ TTH.testProperty+ "propSame"+ -- This is a big API, so we want adequate coverage (default is 100)+ (H.withTests 4096 propSame)++-- | Check that a generated API interaction has the same effect when interpreted+-- with either 'SymSequence' or lists.+propSame :: H.Property+propSame =+ H.property $ do+ Some (SomeBackend bak) <- liftIO mkBackend+ let sym = backendGetSym bak+ op <- H.forAll (Gen.sized $ \n -> genList (H.unSize n) Gen.bool)+ let l = opList op+ s <- liftIO (opSeq sym op)+ l' <- liftIO (F.toList <$> asSeq sym s)+ l H.=== l'+ where+ asSeq sym =+ S.concretizeSymSequence (pure . asConstPred (Just sym)) pure++---------------------------------------------------------------------+-- Helpers++mkBackend :: IO (Some SomeBackend)+mkBackend = do+ sym <- newExprBuilder FloatIEEERepr EmptyExprBuilderState Nonce.globalNonceGenerator+ Some . SomeBackend <$> newSimpleBackend sym++-- Requires that the predicate is concrete+asConstPred ::+ WI.IsExprBuilder sym =>+ proxy sym ->+ WI.Pred sym ->+ Bool+asConstPred _proxy p =+ case WI.asConstantPred p of+ Just True -> True+ Just False -> False+ Nothing -> panic "asConstPred" ["non-constant predicate"]++---------------------------------------------------------------------+-- Op++data Elem a deriving Show++data List a deriving Show++-- TODO: Replace with `Seq` for performance+type family AsList t where+ AsList (List a) = [a]+ AsList (Elem a) = a+ AsList (Maybe a) = Maybe (AsList a)+ AsList (a, b) = (AsList a, AsList b)+ AsList a = a++type family AsSeq sym t where+ AsSeq sym (List a) = SymSequence sym a+ AsSeq sym (Elem a) = a+ AsSeq sym (Maybe a) = Maybe (AsSeq sym a)+ AsSeq sym (a, b) = (AsSeq sym a, AsSeq sym b)+ AsSeq sym a = a++-- | An interaction with the 'SymSequence' API+data Op a t where+ -- Generic functions+ OTrue :: Op a Bool+ OFalse :: Op a Bool+ OFst :: Op a (l, r) -> Op a l+ OSnd :: Op a (l, r) -> Op a r+ OElem :: a -> Op a (Elem a)+ OFromMaybe :: Op a t -> Op a (Maybe t) -> Op a t++ -- Constructors+ ONil :: Op a (List a)+ OCons :: Op a (Elem a) -> Op a (List a) -> Op a (List a)+ OAppend :: Op a (List a) -> Op a (List a) -> Op a (List a)+ OMux :: Op a Bool -> Op a (List a) -> Op a (List a) -> Op a (List a)++ -- Operations+ OUncons :: Op a (List a) -> Op a (Maybe (Elem a), (List a))+ OLength :: Op a (List a) -> Op a Integer+ OReverse :: Op a (List a) -> Op a (List a)+ -- TODO: isNil, head, tail++sexp :: [String] -> String+sexp s = '(' : (unwords s ++ ")")++fun :: String -> [String] -> String+fun f s = sexp (f:s)++fun1 :: Show a => String -> a -> String+fun1 f a = fun f [show a]++fun2 :: (Show a, Show b) => String -> a -> b -> String+fun2 f a b = fun f [show a, show b]++fun3 :: (Show a, Show b, Show c) => String -> a -> b -> c -> String+fun3 f a b c = fun f [show a, show b, show c]++instance Show a => Show (Op a t) where+ show =+ \case+ -- Generic functions+ OTrue -> "true"+ OFalse -> "false"+ OFst t -> fun1 "fst" t+ OSnd t -> fun1 "snd" t+ OElem a -> show a+ OFromMaybe a m -> fun2 "fromMaybe" a m++ -- Constructors+ ONil -> "nil"+ OCons l r -> fun2 "cons" l r+ OAppend l r -> fun2 "append" l r+ OMux b l r -> fun3 "mux" b l r++ -- Operations+ OUncons l -> fun1 "uncons" l+ OLength l -> fun1 "length" l+ OReverse l -> fun1 "reverse" l++---------------------------------------------------------------------+-- Generating Op++genBool :: Gen (Op a Bool)+genBool =+ Gen.choice+ [ pure OTrue+ , pure OFalse+ ]++genElem ::+ Int ->+ Gen a ->+ Gen (Op a (Elem a))+genElem sz genA =+ if sz <= 0+ then OElem <$> genA+ else+ Gen.choice+ [ OElem <$> genA+ , OFromMaybe+ <$> genElem (sz - 1) genA+ <*> (OFst <$> (OUncons <$> genList (sz - 1) genA))+ ]++genList ::+ Int ->+ Gen a ->+ Gen (Op a (List a))+genList sz genA =+ if sz <= 0+ then pure ONil+ else+ Gen.choice+ [ genCons+ , genAppend+ , genMux+ , genReverse+ ]+ where+ sub1 = genList (sz - 1) genA+ sub2 = do+ let budget = max 0 (sz - 1)+ bl <- Gen.integral (Range.linear 0 budget)+ let br = max 0 (budget - bl)+ l <- genList bl genA+ r <- genList br genA+ pure (l, r)++ genCons = OCons <$> genElem (sz - 1) genA <*> sub1++ genAppend = uncurry OAppend <$> sub2++ genMux = do+ b <- genBool+ uncurry (OMux b) <$> sub2++ genReverse = OReverse <$> sub1++---------------------------------------------------------------------+-- Interpreting Op++opList :: Op a t -> AsList t+opList =+ \case+ -- Generic functions+ OTrue -> True+ OFalse -> False+ OFst t -> fst (opList t)+ OSnd t -> snd (opList t)+ OElem a -> a+ OFromMaybe a m -> Maybe.fromMaybe (opList a) (opList m)++ -- Constructors+ ONil -> []+ OCons a l -> opList a : opList l+ OAppend l r -> opList l ++ opList r+ OMux b l r -> if opList b then opList l else opList r++ -- Operations+ OUncons l ->+ let l' = opList l in+ case List.uncons l' of+ Just (hd, tl) -> (Just hd, tl)+ Nothing -> (Nothing, l')+ OLength l -> fromIntegral @Int @Integer (length (opList l)) -- safe+ OReverse l -> reverse (opList l)++opSeq ::+ WI.IsExprBuilder sym =>+ sym ->+ Op a t ->+ IO (AsSeq sym t)+opSeq sym =+ \case+ -- Generic functions+ OTrue -> pure True+ OFalse -> pure False+ OFst t -> fst <$> opSeq sym t+ OSnd t -> snd <$> opSeq sym t+ OElem a -> pure a+ OFromMaybe a m ->+ Maybe.fromMaybe+ <$> opSeq sym a+ <*> opSeq sym m++ -- Constructors+ ONil -> pure S.SymSequenceNil+ OCons a l ->+ S.SymSequenceCons+ <$> Nonce.freshNonce Nonce.globalNonceGenerator+ <*> opSeq sym a+ <*> opSeq sym l+ OAppend l r ->+ S.SymSequenceAppend+ <$> Nonce.freshNonce Nonce.globalNonceGenerator+ <*> opSeq sym l+ <*> opSeq sym r+ OMux b l r -> do+ b' <- opSeq sym b+ let b'' = if b' then WI.truePred sym else WI.falsePred sym+ S.SymSequenceMerge+ <$> Nonce.freshNonce Nonce.globalNonceGenerator+ <*> pure b''+ <*> opSeq sym l+ <*> opSeq sym r++ -- Operations+ OUncons l -> do+ l' <- opSeq sym l+ let interpPred p x y =+ if asConstPred (Just sym) p+ then pure x+ else pure y+ pe <- S.unconsSymSequence sym interpPred l'+ case pe of+ WP.Unassigned -> pure (Nothing, l')+ WP.PE _ (hd, tl) -> -- TODO: assert pred is truePred+ pure (Just hd, tl)+ OLength s -> do+ l <- S.lengthSymSequence sym =<< opSeq sym s+ case WI.asInteger (WI.natToIntegerPure l) of+ Just l' -> pure l'+ Nothing -> panic "opSeq" ["SymSequence: symbolic length"]+ OReverse l -> S.reverseSymSequence sym =<< opSeq sym l
+ test/helpers/SymSequence/Reverse.hs view
@@ -0,0 +1,174 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE RankNTypes #-}++module SymSequence.Reverse (tests) where++import Data.Parameterized.Nonce qualified as Nonce+import Data.Parameterized.Some (Some(Some))+import Lang.Crucible.Backend (SomeBackend(SomeBackend), backendGetSym)+import Lang.Crucible.Backend.Simple (newSimpleBackend)+import Lang.Crucible.Simulator.SymSequence (SymSequence)+import Lang.Crucible.Simulator.SymSequence qualified as S+import System.Timeout (timeout)+import Test.Tasty qualified as TT+import Test.Tasty.HUnit qualified as TTU+import What4.Expr (EmptyExprBuilderState(EmptyExprBuilderState))+import What4.Expr.Builder (newExprBuilder)+import What4.FloatMode (FloatModeRepr(FloatIEEERepr))+import What4.Interface qualified as WI++---------------------------------------------------------------------+-- Tests++tests :: TT.TestTree+tests = TT.testGroup "reverse performance"++ -- Pure cons-list (no branching, no appends):+ --+ -- Cons -> Cons -> ... -> Cons -> Nil+ --+ [ reverseTest "reverse large cons-list" $ \sym ->+ buildSeq sym 1500000 $ \s _ acc ->+ S.consSymSequence s True acc++ -- Cons-list with a Mux node every 100 elements.+ -- At each Mux both branches point to the same shared tail:+ --+ -- /--true---\+ -- Cons -> ... -> Cons -> Mux -> Cons -> ... -> Nil+ -- \--false--/+ --+ , reverseTest "reverse cons-list with periodic muxes" $ \sym ->+ buildSeq sym 250000 $ \s i acc -> do+ acc' <-+ if i `mod` 100 == 0+ then do+ t <- S.consSymSequence s True acc+ f <- S.consSymSequence s False acc+ S.muxSymSequence s (WI.truePred s) t f+ else pure acc+ S.consSymSequence s True acc'++ -- Left-nested chain of Appends:+ --+ -- Append -> Append -> ... -> Append -> Nil+ --+ , reverseTest "reverse left-nested appends" $ \sym ->+ buildSeq sym 250000 $ \s _ acc -> do+ singleton <- S.consSymSequence s True S.SymSequenceNil+ S.appendSymSequence s acc singleton++ -- Right-nested chain of Appends:+ --+ -- Append -> Append -> ... -> Append -> Nil+ --+ , reverseTest "reverse right-nested appends" $ \sym ->+ buildSeq sym 250000 $ \s _ acc -> do+ singleton <- S.consSymSequence s True S.SymSequenceNil+ S.appendSymSequence s singleton acc++ -- Left-deep chain of Muxes (true branch grows):+ --+ -- Mux -> Mux -> Mux -> ... -> Mux -> Nil+ --+ , reverseTest "reverse left-deep mux chain" $ \sym ->+ buildSeq sym 250000 $ \s _ acc -> do+ singleton <- S.consSymSequence s True S.SymSequenceNil+ S.muxSymSequence s (WI.truePred s) acc singleton++ -- Right-deep chain of Muxes (true branch is a leaf):+ --+ -- Mux -> Mux -> Mux -> ... -> Mux -> Nil+ --+ , reverseTest "reverse right-deep mux chain" $ \sym ->+ buildSeq sym 250000 $ \s _ acc -> do+ singleton <- S.consSymSequence s True S.SymSequenceNil+ S.muxSymSequence s (WI.truePred s) singleton acc++ -- Balanced binary tree of Appends over singletons:+ --+ -- Append+ -- / \+ -- Append Append+ -- / \ / \+ -- [1] [2] [3] [4] ...+ --+ , reverseTest "reverse balanced appends" $ \sym ->+ buildBalanced sym 250000 $ \s acc1 acc2 ->+ S.appendSymSequence s acc1 acc2++ -- Balanced binary tree of Muxes over singletons:+ --+ -- Mux+ -- / \+ -- Mux Mux+ -- / \ / \+ -- [1] [2] [3] [4] ...+ --+ , reverseTest "reverse balanced muxes" $ \sym ->+ buildBalanced sym 250000 $ \s acc1 acc2 ->+ S.muxSymSequence s (WI.truePred s) acc1 acc2+ ]++---------------------------------------------------------------------+-- Helpers++mkBackend :: IO (Some SomeBackend)+mkBackend = do+ sym <- newExprBuilder FloatIEEERepr EmptyExprBuilderState Nonce.globalNonceGenerator+ Some . SomeBackend <$> newSimpleBackend sym++-- | Build a sequence, reverse it, and check that length is preserved.+-- Must complete within 5 seconds.+reverseTest ::+ String ->+ (forall sym. WI.IsExprBuilder sym => sym -> IO (SymSequence sym Bool)) ->+ TT.TestTree+reverseTest name build = TTU.testCase name $ do+ Some (SomeBackend bak) <- mkBackend+ let sym = backendGetSym bak+ s <- build sym+ origLen <- S.lengthSymSequence sym s+ result <- timeout (5 * 1000000) $ do+ r <- S.reverseSymSequence sym s+ S.lengthSymSequence sym r+ case result of+ Nothing -> TTU.assertFailure (name ++ " timed out (>5s)")+ Just revLen ->+ TTU.assertEqual "length preserved"+ (WI.asInteger (WI.natToIntegerPure origLen))+ (WI.asInteger (WI.natToIntegerPure revLen))++-- | Iterate a step function n times starting from nil.+buildSeq ::+ WI.IsExprBuilder sym =>+ sym ->+ Int ->+ (sym -> Int -> SymSequence sym Bool -> IO (SymSequence sym Bool)) ->+ IO (SymSequence sym Bool)+buildSeq sym n step = go n S.SymSequenceNil+ where+ go 0 acc = pure acc+ go i acc = go (i - 1) =<< step sym i acc++-- | Build a balanced binary tree of n singletons combined with the given+-- binary operation (e.g. appendSymSequence or muxSymSequence).+buildBalanced ::+ WI.IsExprBuilder sym =>+ sym ->+ Int ->+ (sym -> SymSequence sym Bool -> SymSequence sym Bool -> IO (SymSequence sym Bool)) ->+ IO (SymSequence sym Bool)+buildBalanced sym n combine = do+ leaves <- mapM (\_ -> S.consSymSequence sym True S.SymSequenceNil) [1..n]+ reduce leaves+ where+ reduce [] = pure S.SymSequenceNil+ reduce [x] = pure x+ reduce xs = reduce =<< pairUp xs+ pairUp [] = pure []+ pairUp [x] = pure [x]+ pairUp (x:y:rest) = do+ combined <- combine sym x y+ (combined :) <$> pairUp rest