packages feed

llvm-pretty-bc-parser-0.4.1.0: src/Data/LLVM/BitCode/Assert.hs

{- |
Module      : Data.LLVM.BitCode.Assert
Description : This module implements exceptions and warnings about bitcode.
License     : BSD3
Maintainer  : lbarrett
Stability   : experimental

This module is meant to be imported qualified as @Assert@

-}

{-# LANGUAGE CPP #-}
module Data.LLVM.BitCode.Assert
  ( failWithMsg
  , unknownEntity
  -- ** Record size
  , recordSizeLess
  , recordSizeGreater
  , recordSizeBetween
  , recordSizeIn

  -- ** Types
  , elimPtrTo
  , elimPtrTo_
  ) where

import           Control.Monad (MonadPlus, mplus)
import           Control.Monad (when)
#if !MIN_VERSION_base(4,13,0)
import           Control.Monad.Fail (MonadFail)
#endif
import           Data.LLVM.BitCode.Record (Record)
import qualified Data.LLVM.BitCode.Record as Record
import           Text.LLVM.AST (Type', Ident)
import qualified Text.LLVM.AST as AST

supportedCompilerMessage :: [String]
supportedCompilerMessage =
  [ "Are you sure you're using a supported compiler?"
  , "Check here: https://github.com/GaloisInc/llvm-pretty-bc-parser"
  ]

-- | Call 'fail' with a helpful hint to the user
failWithMsg :: MonadFail m => String -> m a
failWithMsg s = fail $ unlines (s:supportedCompilerMessage)

-- | For when an unknown value of an enumeration is encountered
unknownEntity :: (MonadFail m, Show a) => String -> a -> m b
unknownEntity sort val = failWithMsg ("Unknown " ++ sort ++ " " ++ show val)

----------------------------------------------------------------
-- ** Record sizes

recordSizeCmp :: MonadFail m => String -> (Int -> Bool) -> Record -> m ()
recordSizeCmp msg compare_ record =
  let len = length (Record.recordFields record)
  in when (compare_ len) $ failWithMsg $ unlines $
       [ "Invalid record size: " ++ show len, msg ]

recordSizeLess :: MonadFail m => Record -> Int -> m ()
recordSizeLess r i = recordSizeCmp "Expected size less than" (i <=) r

recordSizeGreater :: MonadFail m => Record -> Int -> m ()
recordSizeGreater r i = recordSizeCmp "Expected size greater than" (<= i) r

recordSizeBetween :: MonadFail m => Record -> Int -> Int -> m ()
recordSizeBetween record lb ub =
  recordSizeGreater record lb >> recordSizeLess record ub

recordSizeIn :: MonadFail m => Record -> [Int] -> m ()
recordSizeIn record ns =
  let len = length (Record.recordFields record)
  in when (not (len `elem` ns)) $ failWithMsg $ unlines $
       [ "Invalid record size: " ++ show len
       , "Expected one of: " ++ show ns
       ]


----------------------------------------------------------------
-- ** Types

-- | Assert that this thing is a @'PtrTo' ty@ and return the underlying @ty@.
--
-- Think carefully before using this function, as it will not work as you would
-- expect when the type is an opaque pointer.
-- See @Note [Pointers and pointee types]@.
elimPtrTo :: (MonadFail m, MonadPlus m) => String -> Type' Ident -> m (Type' Ident)
elimPtrTo msg ptrTy = AST.elimPtrTo ptrTy `mplus`
                        (fail $ unlines [ msg
                                        , "Expected pointer type, found:"
                                        , show ptrTy
                                        ])

-- | Assert that this thing is a 'PtrTo' type.
--
-- Think carefully before using this function, as it will not work as you would
-- expect when the type is an opaque pointer.
-- See @Note [Pointers and pointee types]@.
elimPtrTo_ :: (MonadFail m, MonadPlus m) => String -> Type' Ident -> m ()
elimPtrTo_ msg ptrTy = elimPtrTo msg ptrTy >> pure ()

{-
Note [Pointers and pointee types]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Unlike LLVM itself, llvm-pretty and llvm-pretty-bc-parser allow mixing opaque
and non-opaque pointers. A consequence of this is that we generally avoid
pattern matching on PtrTo (non-opaque pointer) types and inspecting the
underlying pointee types. This sort of code simply won't work for PtrOpaque
types, which lack pointee types.

The elimPtrTo and elimPtrTo_ functions go against this rule, as they retrieve
the pointee type in a PtrTo. These functions are primarily used for supporting
old versions of LLVM which do not store the necessary type information in the
instruction itself.
-}