packages feed

crucible-0.9: src/Lang/Crucible/Backend/Assumptions.hs

{-|
Module      : Lang.Crucible.Backend.Assumptions
Copyright   : (c) Galois, Inc 2014-2024
License     : BSD3
Maintainer  : Langston Barrett <langston@galois.com>
-}

{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE ViewPatterns #-}

module Lang.Crucible.Backend.Assumptions
  ( CrucibleAssumption(..)
  , CrucibleEvent(..)
  , CrucibleAssumptions(..)
  , Assumption
  , Assumptions

  , concretizeEvents
  , ppEvent
  , singleEvent
  , singleAssumption
  , trivialAssumption
  , ppAssumption
  , assumptionLoc
  , eventLoc
  , mergeAssumptions
  , assumptionPred
  , forgetAssumption
  , assumptionsPred
  , flattenAssumptions
  , assumptionsTopLevelLocs
  , ppAssumptions'
  , ppAssumptions
  ) where


import           Control.Lens (Traversal, folded)
import           Data.Kind (Type)
import qualified Data.Foldable as F
import           Data.Functor.Identity
import           Data.Functor.Const
import qualified Data.Parameterized.TraversableF as TF
import qualified Data.Sequence as Seq
import           Data.Sequence (Seq)
import qualified Prettyprinter as PP

import           What4.Expr.Builder
import           What4.Interface
import           What4.ProgramLoc
import           What4.Expr (GroundValue, GroundValueWrapper(..))

import           Lang.Crucible.Simulator.SimError

type Assumption sym = CrucibleAssumption (SymExpr sym)
type Assumptions sym = CrucibleAssumptions (SymExpr sym)

-- | This type describes assumptions made at some point during program execution.
data CrucibleAssumption (e :: BaseType -> Type)
  = GenericAssumption ProgramLoc String (e BaseBoolType)
    -- ^ An unstructured description of the source of an assumption.

  | BranchCondition ProgramLoc (Maybe ProgramLoc) (e BaseBoolType)
    -- ^ This arose because we want to explore a specific path.
    -- The first location is the location of the branch predicate.
    -- The second one is the location of the branch target.

  | AssumingNoError SimError (e BaseBoolType)
    -- ^ 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.
  CreateVariableEvent ::
    ProgramLoc {- ^ location where the variable was created -} ->
    String {- ^ user-provided name for the variable -} ->
    BaseTypeRepr tp {- ^ type of the variable -} ->
    e tp {- ^ the variable expression -} ->
    CrucibleEvent e

  -- | This event describes reaching a particular program location.
  LocationReachedEvent ::
    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' :: (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
eventLoc (LocationReachedEvent loc) = loc

-- | Return the program location associated with an assumption
assumptionLoc :: CrucibleAssumption e -> ProgramLoc
assumptionLoc r =
  case r of
    GenericAssumption l _ _ -> l
    BranchCondition  l _ _   -> l
    AssumingNoError s _    -> simErrorLoc s

-- | Get the predicate associated with this assumption
assumptionPred :: CrucibleAssumption e -> e BaseBoolType
assumptionPred (AssumingNoError _ p) = p
assumptionPred (BranchCondition _ _ p) = p
assumptionPred (GenericAssumption _ _ p) = p

forgetAssumption :: CrucibleAssumption e -> CrucibleAssumption (Const ())
forgetAssumption = runIdentity . traverseAssumption (\_ -> Identity (Const ()))

-- | Check if an assumption is trivial (always true)
trivialAssumption :: IsExpr e => CrucibleAssumption e -> Bool
trivialAssumption a = asConstantPred (assumptionPred a) == Just True

traverseAssumption :: Traversal (CrucibleAssumption e) (CrucibleAssumption e') (e BaseBoolType) (e' BaseBoolType)
traverseAssumption f = \case
  GenericAssumption loc msg p -> GenericAssumption loc msg <$> f p
  BranchCondition l t p -> BranchCondition l t <$> f p
  AssumingNoError err p -> AssumingNoError err <$> f p

-- | This type tracks both logical assumptions and program events
--   that are relevant when evaluating proof obligations arising
--   from simulation.
data CrucibleAssumptions (e :: BaseType -> Type) where
  SingleAssumption :: CrucibleAssumption e -> CrucibleAssumptions e
  SingleEvent      :: CrucibleEvent e -> CrucibleAssumptions e
  ManyAssumptions  :: Seq (CrucibleAssumptions e) -> CrucibleAssumptions e
  MergeAssumptions ::
    e BaseBoolType {- ^ branch condition -} ->
    CrucibleAssumptions e {- ^ "then" assumptions -} ->
    CrucibleAssumptions e {- ^ "else" assumptions -} ->
    CrucibleAssumptions e

instance Semigroup (CrucibleAssumptions e) where
  ManyAssumptions xs <> ManyAssumptions ys = ManyAssumptions (xs <> ys)
  ManyAssumptions xs <> y = ManyAssumptions (xs Seq.|> y)
  x <> ManyAssumptions ys = ManyAssumptions (x Seq.<| ys)
  x <> y = ManyAssumptions (Seq.fromList [x,y])

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

singleEvent :: CrucibleEvent e -> CrucibleAssumptions e
singleEvent x = SingleEvent x

-- | Collect the program locations of all assumptions and
--   events that did not occur in the context of a symbolic branch.
--   These are locations that every program path represented by
--   this @CrucibleAssumptions@ structure must have passed through.
assumptionsTopLevelLocs :: CrucibleAssumptions e -> [ProgramLoc]
assumptionsTopLevelLocs (SingleEvent e)      = [eventLoc e]
assumptionsTopLevelLocs (SingleAssumption a) = [assumptionLoc a]
assumptionsTopLevelLocs (ManyAssumptions as) = concatMap assumptionsTopLevelLocs as
assumptionsTopLevelLocs MergeAssumptions{}   = []

-- | Compute the logical predicate corresponding to this collection of assumptions.
assumptionsPred :: IsExprBuilder sym => sym -> Assumptions sym -> IO (Pred sym)
assumptionsPred sym (SingleEvent _) =
  return (truePred sym)
assumptionsPred _sym (SingleAssumption a) =
  return (assumptionPred a)
assumptionsPred sym (ManyAssumptions xs) =
  andAllOf sym folded =<< traverse (assumptionsPred sym) xs
assumptionsPred sym (MergeAssumptions c xs ys) =
  do xs' <- assumptionsPred sym xs
     ys' <- assumptionsPred sym ys
     itePred sym c xs' ys'

traverseEvent :: Applicative m =>
  (forall tp. e tp -> m (e' tp)) ->
  CrucibleEvent e -> m (CrucibleEvent e')
traverseEvent f (CreateVariableEvent loc nm tpr v) = CreateVariableEvent loc nm tpr <$> f v
traverseEvent _ (LocationReachedEvent loc) = pure (LocationReachedEvent loc)

-- | Given a ground evaluation function, compute a linear, ground-valued
--   sequence of events corresponding to this program run.
concretizeEvents ::
  IsExpr e =>
  (forall tp. e tp -> IO (GroundValue tp)) ->
  CrucibleAssumptions e ->
  IO [CrucibleEvent GroundValueWrapper]
concretizeEvents f = loop
  where
    loop (SingleEvent e) =
      do e' <- traverseEvent (\v -> GVW <$> f v) e
         return [e']
    loop (SingleAssumption _) = return []
    loop (ManyAssumptions as) = concat <$> traverse loop as
    loop (MergeAssumptions p xs ys) =
      do b <- f p
         if b then loop xs else loop ys

-- | Given a @CrucibleAssumptions@ structure, flatten all the muxed assumptions into
--   a flat sequence of assumptions that have been appropriately weakened.
--   Note, once these assumptions have been flattened, their order might no longer
--   strictly correspond to any concrete program run.
flattenAssumptions :: IsExprBuilder sym => sym -> Assumptions sym -> IO [Assumption sym]
flattenAssumptions sym = loop Nothing
  where
    loop _mz (SingleEvent _) = return []
    loop mz (SingleAssumption a) =
      do a' <- maybe (pure a) (\z -> traverseAssumption (impliesPred sym z) a) mz
         if trivialAssumption a' then return [] else return [a']
    loop mz (ManyAssumptions as) =
      concat <$> traverse (loop mz) as
    loop mz (MergeAssumptions p xs ys) =
      do pnot <- notPred sym p
         px <- maybe (pure p) (andPred sym p) mz
         py <- maybe (pure pnot) (andPred sym pnot) mz
         xs' <- loop (Just px) xs
         ys' <- loop (Just py) ys
         return (xs' <> ys')

-- | Merge the assumptions collected from the branches of a conditional.
mergeAssumptions ::
  IsExprBuilder sym =>
  sym ->
  Pred sym ->
  Assumptions sym ->
  Assumptions sym ->
  IO (Assumptions sym)
mergeAssumptions _sym p thens elses =
  return (MergeAssumptions p thens elses)

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)
              , ppDoc p
              ]
    BranchCondition l Nothing p ->
      PP.vsep [ "The branch in" PP.<+> ppFn l PP.<+> "at" PP.<+> ppLoc l
              , ppDoc p
              ]
    BranchCondition l (Just t) p ->
      PP.vsep [ "The branch in" PP.<+> ppFn l PP.<+> "from" PP.<+> ppLoc l PP.<+> "to" PP.<+> ppLoc t
              , ppDoc p
              ]
    AssumingNoError simErr p ->
      PP.vsep [ "Assuming the following error does not occur:"
              , PP.indent 2 (ppSimError simErr)
              , ppDoc p
              ]
  where
    ppLocated :: ProgramLoc -> PP.Doc ann -> PP.Doc ann
    ppLocated l x = "in" PP.<+> ppFn l PP.<+> ppLoc l PP.<> ":" PP.<+> x

    ppFn :: ProgramLoc -> PP.Doc ann
    ppFn l = PP.pretty (plFunction l)

    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