packages feed

crucible-0.10: src/Lang/Crucible/Simulator/SimError.hs

-----------------------------------------------------------------------
-- |
-- Module           : Lang.Crucible.Simulator.SimError
-- Description      : Data structure the execution state of the simulator
-- Copyright        : (c) Galois, Inc 2014
-- License          : BSD3
-- Maintainer       : Joe Hendrix <jhendrix@galois.com>
-- Stability        : provisional
------------------------------------------------------------------------
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE PatternSynonyms #-}
module Lang.Crucible.Simulator.SimError (
    SimErrorReason(..)
  , SimError(.., SimError)
  , ProgramStack(..)
  , mkSimError
  , simErrorReason
  , simErrorLoc
  , simErrorContext
  , simErrorReasonMsg
  , simErrorDetailsMsg
  , ppSimError
  , ppProgramStack
  ) where

import GHC.Stack (CallStack)

import Control.Exception
import Data.String
import Prettyprinter

import What4.ProgramLoc


------------------------------------------------------------------------
-- SimError

-- | Class for exceptions generated by simulator.
data SimErrorReason
   = GenericSimError !String
   | Unsupported !CallStack !String
      -- ^ We can't do that (yet?).  The call stack identifies where in the
      --   Haskell code the error occured.
   | ReadBeforeWriteSimError !String -- FIXME? include relevant data instead of a string?
   | AssertFailureSimError !String !String
     -- ^ An assertion failed. The first parameter is a short
     -- description. The second is a more detailed explanation.
   | ResourceExhausted String
      -- ^ A loop iteration count, or similar resource limit,
      --   was exceeded.

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
simErrorReasonMsg (ReadBeforeWriteSimError msg) = msg
simErrorReasonMsg (AssertFailureSimError msg _) = msg
simErrorReasonMsg (ResourceExhausted msg) = "Resource exhausted: " ++ msg

simErrorDetailsMsg :: SimErrorReason -> String
simErrorDetailsMsg (AssertFailureSimError _ msg) = msg
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

instance Show SimErrorReason where
  show = simErrorReasonMsg

instance Show SimError where
  show = show . ppSimError

ppSimError :: SimError -> Doc ann
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))
                    ])
          ++ (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          

-- | 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